Fixes formatting issues with lists in event type description and bio (#7505)

* fix ul and ol in editor

* fix imports

* disable list for team about section

* fix event type description in list

---------

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
pull/7458/head^2
Carina Wollendorfer 2023-03-03 18:20:13 -05:00 committed by GitHub
parent c8b01f6992
commit 47e948fbbc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 51 additions and 46 deletions

View File

@ -1,7 +1,6 @@
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { zodResolver } from "@hookform/resolvers/zod";
import { isValidPhoneNumber } from "libphonenumber-js";
import MarkdownIt from "markdown-it";
import { Trans } from "next-i18next";
import Link from "next/link";
import type { EventTypeSetupProps, FormValues } from "pages/event-types/[type]";
@ -14,6 +13,7 @@ import type { EventLocationType } from "@calcom/app-store/locations";
import { getEventLocationType, MeetLocationType, LocationType } from "@calcom/app-store/locations";
import { CAL_URL } from "@calcom/lib/constants";
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 { Button, Editor, Label, Select, SettingsToggle, Skeleton, TextField } from "@calcom/ui";
@ -23,8 +23,6 @@ import { EditLocationDialog } from "@components/dialog/EditLocationDialog";
import type { SingleValueLocationOption, LocationOption } from "@components/ui/form/LocationSelect";
import LocationSelect from "@components/ui/form/LocationSelect";
const md = new MarkdownIt("default", { html: true, breaks: true, linkify: true });
const getLocationFromType = (
type: EventLocationType["type"],
locationOptions: Pick<EventTypeSetupProps, "locationOptions">["locationOptions"]

View File

@ -1,11 +1,11 @@
import { ArrowRightIcon } from "@heroicons/react/solid";
import MarkdownIt from "markdown-it";
import { useRouter } from "next/router";
import type { FormEvent } from "react";
import { useRef, useState } from "react";
import { useForm } from "react-hook-form";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { md } from "@calcom/lib/markdownIt";
import { telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import turndown from "@calcom/lib/turndownService";
import { trpc } from "@calcom/trpc/react";
@ -14,8 +14,6 @@ import { Avatar } from "@calcom/ui";
import type { IOnboardingPageProps } from "../../../pages/getting-started/[[...step]]";
const md = new MarkdownIt("default", { html: true, breaks: true, linkify: true });
type FormData = {
bio: string;
};

View File

@ -1,13 +1,11 @@
import MarkdownIt from "markdown-it";
import Link from "next/link";
import type { TeamPageProps } from "pages/team/[slug]";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { md } from "@calcom/lib/markdownIt";
import { Avatar } from "@calcom/ui";
const md = new MarkdownIt("default", { html: true, breaks: true, linkify: true });
type TeamType = TeamPageProps["team"];
type MembersType = TeamType["members"];
type MemberType = MembersType[number];
@ -19,7 +17,7 @@ const Member = ({ member, teamName }: { member: MemberType; teamName: string | n
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 flex-col space-y-2 rounded-md bg-white p-4 hover:cursor-pointer hover:bg-gray-50 ">
<div className="sm:min-w-80 sm:max-w-80 dark:bg-darkgray-200 dark:hover:bg-darkgray-300 group flex min-h-full flex-col space-y-2 rounded-md bg-white p-4 hover:cursor-pointer hover:bg-gray-50 ">
<Avatar
size="md"
alt={member.name || ""}

View File

@ -1,6 +1,5 @@
import { BadgeCheckIcon } from "@heroicons/react/solid";
import classNames from "classnames";
import MarkdownIt from "markdown-it";
import type { GetServerSidePropsContext } from "next";
import Link from "next/link";
import { useRouter } from "next/router";
@ -24,6 +23,7 @@ import defaultEvents, {
} from "@calcom/lib/defaultEvents";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import useTheme from "@calcom/lib/hooks/useTheme";
import { md } from "@calcom/lib/markdownIt";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import prisma from "@calcom/prisma";
import { baseEventTypeSelect } from "@calcom/prisma/selects";
@ -36,8 +36,6 @@ import type { 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
@ -147,7 +145,7 @@ export default function User(props: inferSSRProps<typeof getServerSideProps> & E
{!isBioEmpty && (
<>
<div
className="dark:text-darkgray-600 text-sm text-gray-500 [&_a]:text-blue-500 [&_a]:underline [&_a]:hover:text-blue-600"
className=" dark:text-darkgray-600 text-sm text-gray-500 [&_a]:text-blue-500 [&_a]:underline [&_a]:hover:text-blue-600"
dangerouslySetInnerHTML={{ __html: md.render(user.bio || "") }}
/>
</>

View File

@ -5,6 +5,7 @@ import type { LocationObject } from "@calcom/app-store/locations";
import { IS_TEAM_BILLING_ENABLED, WEBAPP_URL } from "@calcom/lib/constants";
import hasKeyInMetadata from "@calcom/lib/hasKeyInMetadata";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { addListFormatting } from "@calcom/lib/markdownIt";
import type { User } from "@calcom/prisma/client";
import { isBrandingHidden } from "@lib/isBrandingHidden";
@ -152,7 +153,7 @@ async function getUserPageProps(context: GetStaticPropsContext) {
metadata: EventTypeMetaDataSchema.parse(eventType.metadata || {}),
recurringEvent: parseRecurringEvent(eventType.recurringEvent),
locations: privacyFilteredLocations(locations),
descriptionAsSafeHTML: eventType.description ? md.render(eventType.description) : null,
descriptionAsSafeHTML: eventType.description ? addListFormatting(md.render(eventType.description)) : null,
});
// Check if the user you are logging into has any active teams or premium user name
const hasActiveTeam =

View File

@ -1,6 +1,5 @@
import { IdentityProvider } from "@prisma/client";
import crypto from "crypto";
import MarkdownIt from "markdown-it";
import { signOut } from "next-auth/react";
import type { BaseSyntheticEvent } from "react";
import { useRef, useState } from "react";
@ -10,6 +9,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 { md } from "@calcom/lib/markdownIt";
import turndown from "@calcom/lib/turndownService";
import type { TRPCClientErrorLike } from "@calcom/trpc/client";
import { trpc } from "@calcom/trpc/react";
@ -41,8 +41,6 @@ import { FiAlertTriangle, FiTrash2 } from "@calcom/ui/components/icon";
import TwoFactor from "@components/auth/TwoFactor";
import { UsernameAvailabilityField } from "@components/ui/UsernameAvailability";
const md = new MarkdownIt("default", { html: true, breaks: true, linkify: true });
const SkeletonLoader = ({ title, description }: { title: string; description: string }) => {
return (
<SkeletonContainer>
@ -371,6 +369,7 @@ const ProfileForm = ({
formMethods.setValue("bio", turndown(value), { shouldDirty: true });
}}
excludedToolbarItems={["blockType"]}
disableLists
/>
</div>
<Button disabled={isDisabled} color="primary" className="mt-8" type="submit">

View File

@ -1,5 +1,4 @@
import classNames from "classnames";
import MarkdownIt from "markdown-it";
import type { GetServerSidePropsContext } from "next";
import Link from "next/link";
import { useRouter } from "next/router";
@ -11,6 +10,7 @@ import { CAL_URL } from "@calcom/lib/constants";
import { getPlaceholderAvatar } from "@calcom/lib/getPlaceholderAvatar";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import useTheme from "@calcom/lib/hooks/useTheme";
import { md } from "@calcom/lib/markdownIt";
import { getTeamWithMembers } from "@calcom/lib/server/queries/teams";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import { Avatar, Button, HeadSeo, AvatarGroup } from "@calcom/ui";
@ -23,8 +23,6 @@ import Team from "@components/team/screens/Team";
import { ssrInit } from "@server/lib/ssr";
const md = new MarkdownIt("default", { html: true, breaks: true, linkify: true });
export type TeamPageProps = inferSSRProps<typeof getServerSideProps>;
function TeamPage({ team }: TeamPageProps) {
useTheme(team.theme);

View File

@ -1,7 +1,6 @@
import { zodResolver } from "@hookform/resolvers/zod";
import type { Prisma } from "@prisma/client";
import { MembershipRole } 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 +10,7 @@ import { z } from "zod";
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 { md } from "@calcom/lib/markdownIt";
import objectKeys from "@calcom/lib/objectKeys";
import turndown from "@calcom/lib/turndownService";
import { trpc } from "@calcom/trpc/react";
@ -33,8 +33,6 @@ import { FiExternalLink, FiLink, FiTrash2, FiLogOut } from "@calcom/ui/component
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({
@ -225,6 +223,7 @@ const ProfileView = () => {
getText={() => md.render(form.getValues("bio") || "")}
setText={(value: string) => form.setValue("bio", turndown(value))}
excludedToolbarItems={["blockType"]}
disableLists
/>
</div>
<p className="mt-2 text-sm text-gray-600">{t("team_description")}</p>

View File

@ -1,7 +1,6 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { SchedulingType } from "@prisma/client";
import { isValidPhoneNumber } from "libphonenumber-js";
import MarkdownIt from "markdown-it";
import { useRouter } from "next/router";
import { useForm } from "react-hook-form";
import { z } from "zod";
@ -9,6 +8,7 @@ import { z } from "zod";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { useTypedQuery } from "@calcom/lib/hooks/useTypedQuery";
import { HttpError } from "@calcom/lib/http-error";
import { md } from "@calcom/lib/markdownIt";
import slugify from "@calcom/lib/slugify";
import turndown from "@calcom/lib/turndownService";
import { createEventTypeInput } from "@calcom/prisma/zod/custom/eventtype";
@ -26,8 +26,6 @@ import {
Editor,
} from "@calcom/ui";
const md = new MarkdownIt("default", { html: true, breaks: true, linkify: true });
// this describes the uniform data needed to create a new event type on Profile or Team
export interface EventTypeParent {
teamId: number | null | undefined; // if undefined, then it's a profile
@ -203,26 +201,26 @@ export default function CreateEventTypeDialog() {
message={form.formState.errors.schedulingType.message}
/>
)}
<RadioArea.Group className="flex mt-1 space-x-4">
<RadioArea.Group className="mt-1 flex space-x-4">
<RadioArea.Item
{...register("schedulingType")}
value={SchedulingType.COLLECTIVE}
className="w-1/2 text-sm">
<strong className="block mb-1">{t("collective")}</strong>
<strong className="mb-1 block">{t("collective")}</strong>
<p>{t("collective_description")}</p>
</RadioArea.Item>
<RadioArea.Item
{...register("schedulingType")}
value={SchedulingType.ROUND_ROBIN}
className="w-1/2 text-sm">
<strong className="block mb-1">{t("round_robin")}</strong>
<strong className="mb-1 block">{t("round_robin")}</strong>
<p>{t("round_robin_description")}</p>
</RadioArea.Item>
</RadioArea.Group>
</div>
)}
</div>
<div className="flex flex-row-reverse mt-8 gap-x-2">
<div className="mt-8 flex flex-row-reverse gap-x-2">
<Button type="submit" loading={createMutation.isLoading}>
{t("continue")}
</Button>

View File

@ -1,14 +1,15 @@
import { Prisma, SchedulingType } from "@prisma/client";
import MarkdownIt from "markdown-it";
import type { Prisma } from "@prisma/client";
import { SchedulingType } from "@prisma/client";
import { useMemo } from "react";
import { FormattedNumber, IntlProvider } from "react-intl";
import { z } from "zod";
import type { z } from "zod";
import { classNames, parseRecurringEvent } from "@calcom/lib";
import getPaymentAppData from "@calcom/lib/getPaymentAppData";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { baseEventTypeSelect } from "@calcom/prisma";
import { EventTypeModel } from "@calcom/prisma/zod";
import { addListFormatting, md } from "@calcom/lib/markdownIt";
import type { baseEventTypeSelect } from "@calcom/prisma";
import type { EventTypeModel } from "@calcom/prisma/zod";
import { Badge } from "@calcom/ui";
import {
FiClock,
@ -29,11 +30,9 @@ export type EventTypeDescriptionProps = {
seatsPerTimeSlot?: number;
};
className?: string;
shortenDescription?: true;
shortenDescription?: boolean;
};
const md = new MarkdownIt("default", { html: true, breaks: false, linkify: true });
export const EventTypeDescription = ({
eventType,
className,
@ -58,9 +57,7 @@ export const EventTypeDescription = ({
shortenDescription ? "line-clamp-4" : ""
)}
dangerouslySetInnerHTML={{
__html: shortenDescription
? md.render(eventType.description?.replace(/<p><br><\/p>|\n/g, " "))
: md.render(eventType.description),
__html: addListFormatting(md.render(eventType.description)),
}}
/>
)}

View File

@ -0,0 +1,12 @@
import MarkdownIt from "markdown-it";
export const md = new MarkdownIt("default", { html: true, breaks: true, linkify: true });
export function addListFormatting(html: string) {
return html
.replaceAll("<ul>", "<ul style='list-style-type: disc; list-style-position: inside; margin-left: 12px'>")
.replaceAll(
"<ol>",
"<ol style='list-style-type: decimal; list-style-position: inside; margin-left: 12px'>"
);
}

View File

@ -1,7 +1,6 @@
import type { PrismaClient, EventType } from "@prisma/client";
import MarkdownIt from "markdown-it";
const md = new MarkdownIt("default", { html: true, breaks: true, linkify: true });
import { md } from "@calcom/lib/markdownIt";
function parseAndSanitize(description: string) {
const parsedMarkdown = md.render(description);

View File

@ -31,6 +31,7 @@ export type TextEditorProps = {
variables?: string[];
height?: string;
placeholder?: string;
disableLists?: boolean;
};
const editorConfig = {
@ -74,7 +75,15 @@ export const Editor = (props: TextEditorProps) => {
<ListPlugin />
<LinkPlugin />
<AutoLinkPlugin />
<MarkdownShortcutPlugin transformers={TRANSFORMERS} />
<MarkdownShortcutPlugin
transformers={
props.disableLists
? TRANSFORMERS.filter((value, index) => {
if (index !== 3 && index !== 4) return value;
})
: TRANSFORMERS
}
/>
</div>
</div>
</LexicalComposer>

View File

@ -26,7 +26,7 @@
text-align: left;
border-color: #D1D5DB;
border-width: 1px;
padding: 1px
padding: 1px;
}
.editor-inner {
@ -37,6 +37,7 @@
overflow: scroll;
resize: vertical;
height: auto;
min-height: 40px;
}
.editor-input {