Fixes formatted description in email (#7696)
* fix description in email * add styling for lists * sanitize editor input before saving * sanitize eventTypeDescription * santize html when used dangerouslySetInnerHTML * fix server side sanitation * add missing formatting and sanitation * move @ts-expect-error to correct line * fix type error and add yarn.lock * fix build error * sanitize description for booking page and availability page * rename to markdownAndSanitize * always add list formatting * handle empty description in markdownAndSanitize for cleaner code * create function for clientside markdown rendering and sanitizing * fix type error * code clean up * Now that eventType.descriptionAsSafeHTML is added at all the missing places, we can do away with ts-ignore and get type safety * Remove unused variable * Remove one more ts-expect-error --------- Co-authored-by: CarinaWolli <wollencarina@gmail.com> Co-authored-by: Hariom Balhara <hariombalhara@gmail.com> Co-authored-by: Peer Richelsen <peeroke@gmail.com>pull/7899/head
parent
520e7fe036
commit
f6d7568d0a
|
@ -1,11 +1,11 @@
|
|||
export type EventTypeDescriptionSafeProps = {
|
||||
eventType: { description: string | null };
|
||||
eventType: { description: string | null; descriptionAsSafeHTML: string | null };
|
||||
};
|
||||
|
||||
export const EventTypeDescriptionSafeHTML = ({ eventType }: EventTypeDescriptionSafeProps) => {
|
||||
const props: JSX.IntrinsicElements["div"] = { suppressHydrationWarning: true };
|
||||
// @ts-expect-error: @see packages/prisma/middleware/eventTypeDescriptionParseAndSanitize.ts
|
||||
if (eventType.description) props.dangerouslySetInnerHTML = { __html: eventType.descriptionAsSafeHTML };
|
||||
if (eventType.description)
|
||||
props.dangerouslySetInnerHTML = { __html: eventType.descriptionAsSafeHTML || "" };
|
||||
return <div {...props} />;
|
||||
};
|
||||
|
||||
|
|
|
@ -2,13 +2,14 @@ import Link from "next/link";
|
|||
|
||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { md } from "@calcom/lib/markdownIt";
|
||||
import type { TeamWithMembers } from "@calcom/lib/server/queries/teams";
|
||||
import { Avatar } from "@calcom/ui";
|
||||
|
||||
type TeamType = NonNullable<TeamWithMembers>;
|
||||
type MembersType = TeamType["members"];
|
||||
type MemberType = MembersType[number];
|
||||
type MemberType = MembersType[number] & { safeBio: string | null };
|
||||
|
||||
type TeamTypeWithSafeHtml = Omit<TeamType, "members"> & { members: MemberType[] };
|
||||
|
||||
const Member = ({ member, teamName }: { member: MemberType; teamName: string | null }) => {
|
||||
const { t } = useLocale();
|
||||
|
@ -30,7 +31,7 @@ const Member = ({ member, teamName }: { member: MemberType; teamName: string | n
|
|||
<>
|
||||
<div
|
||||
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(member.bio || "") }}
|
||||
dangerouslySetInnerHTML={{ __html: member.safeBio || "" }}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
|
@ -43,7 +44,7 @@ const Member = ({ member, teamName }: { member: MemberType; teamName: string | n
|
|||
);
|
||||
};
|
||||
|
||||
const Members = ({ members, teamName }: { members: MembersType; teamName: string | null }) => {
|
||||
const Members = ({ members, teamName }: { members: MemberType[]; teamName: string | null }) => {
|
||||
if (!members || members.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
@ -57,7 +58,7 @@ const Members = ({ members, teamName }: { members: MembersType; teamName: string
|
|||
);
|
||||
};
|
||||
|
||||
const Team = ({ team }: { team: TeamType }) => {
|
||||
const Team = ({ team }: { team: TeamTypeWithSafeHtml }) => {
|
||||
return (
|
||||
<div>
|
||||
<Members members={team.members} teamName={team.name} />
|
||||
|
|
|
@ -67,6 +67,7 @@
|
|||
"accept-language-parser": "^1.5.0",
|
||||
"async": "^3.2.4",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"canvas": "^2.11.0",
|
||||
"classnames": "^2.3.1",
|
||||
"dotenv-cli": "^6.0.0",
|
||||
"entities": "^4.4.0",
|
||||
|
@ -77,6 +78,7 @@
|
|||
"ical.js": "^1.4.0",
|
||||
"ics": "^2.37.0",
|
||||
"jose": "^4.13.1",
|
||||
"jsdom": "^21.1.1",
|
||||
"kbar": "^0.1.0-beta.36",
|
||||
"libphonenumber-js": "^1.10.12",
|
||||
"lodash": "^4.17.21",
|
||||
|
|
|
@ -24,7 +24,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 { markdownAndSanitize } from "@calcom/lib/markdownAndSanitize";
|
||||
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { baseEventTypeSelect } from "@calcom/prisma/selects";
|
||||
|
@ -147,7 +147,7 @@ export default function User(props: inferSSRProps<typeof getServerSideProps> & E
|
|||
<>
|
||||
<div
|
||||
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 || "") }}
|
||||
dangerouslySetInnerHTML={{ __html: props.safeBio }}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
@ -343,6 +343,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
|||
const eventTypes = eventTypesRaw.map((eventType) => ({
|
||||
...eventType,
|
||||
metadata: EventTypeMetaDataSchema.parse(eventType.metadata || {}),
|
||||
descriptionAsSafeHTML: markdownAndSanitize(eventType.description),
|
||||
}));
|
||||
|
||||
const isSingleUser = users.length === 1;
|
||||
|
@ -352,9 +353,12 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
|||
})
|
||||
: [];
|
||||
|
||||
const safeBio = markdownAndSanitize(user.bio) || "";
|
||||
|
||||
return {
|
||||
props: {
|
||||
users,
|
||||
safeBio,
|
||||
profile,
|
||||
user: {
|
||||
emailMd5: crypto.createHash("md5").update(user.email).digest("hex"),
|
||||
|
|
|
@ -5,7 +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 { markdownAndSanitize } from "@calcom/lib/markdownAndSanitize";
|
||||
import type { User } from "@calcom/prisma/client";
|
||||
|
||||
import { isBrandingHidden } from "@lib/isBrandingHidden";
|
||||
|
@ -59,7 +59,6 @@ Type.isThemeSupported = true;
|
|||
const paramsSchema = z.object({ type: z.string(), user: z.string() });
|
||||
async function getUserPageProps(context: GetStaticPropsContext) {
|
||||
// load server side dependencies
|
||||
const MarkdownIt = await import("markdown-it").then((mod) => mod.default);
|
||||
const prisma = await import("@calcom/prisma").then((mod) => mod.default);
|
||||
const { privacyFilteredLocations } = await import("@calcom/app-store/locations");
|
||||
const { parseRecurringEvent } = await import("@calcom/lib/isRecurringEvent");
|
||||
|
@ -125,8 +124,6 @@ async function getUserPageProps(context: GetStaticPropsContext) {
|
|||
},
|
||||
});
|
||||
|
||||
const md = new MarkdownIt("default", { html: true, breaks: true, linkify: true });
|
||||
|
||||
if (!user || !user.eventTypes.length) return { notFound: true };
|
||||
|
||||
const [eventType]: ((typeof user.eventTypes)[number] & {
|
||||
|
@ -153,7 +150,7 @@ async function getUserPageProps(context: GetStaticPropsContext) {
|
|||
metadata: EventTypeMetaDataSchema.parse(eventType.metadata || {}),
|
||||
recurringEvent: parseRecurringEvent(eventType.recurringEvent),
|
||||
locations: privacyFilteredLocations(locations),
|
||||
descriptionAsSafeHTML: eventType.description ? addListFormatting(md.render(eventType.description)) : null,
|
||||
descriptionAsSafeHTML: markdownAndSanitize(eventType.description),
|
||||
});
|
||||
// Check if the user you are logging into has any active teams or premium user name
|
||||
const hasActiveTeam =
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
getUsernameList,
|
||||
} from "@calcom/lib/defaultEvents";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { markdownAndSanitize } from "@calcom/lib/markdownAndSanitize";
|
||||
import prisma, { bookEventTypeSelect } from "@calcom/prisma";
|
||||
import {
|
||||
customInputSchema,
|
||||
|
@ -189,6 +190,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
|||
slug: u.username,
|
||||
theme: u.theme,
|
||||
})),
|
||||
descriptionAsSafeHTML: markdownAndSanitize(eventType.description),
|
||||
};
|
||||
})[0];
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import type { LocationObject } from "@calcom/core/location";
|
|||
import { privacyFilteredLocations } from "@calcom/core/location";
|
||||
import { parseRecurringEvent } from "@calcom/lib";
|
||||
import { getWorkingHours } from "@calcom/lib/availability";
|
||||
import { markdownAndSanitize } from "@calcom/lib/markdownAndSanitize";
|
||||
import { availiblityPageEventTypeSelect } from "@calcom/prisma";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
||||
|
@ -119,6 +120,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
|||
hideBranding: u.hideBranding,
|
||||
timeZone: u.timeZone,
|
||||
})),
|
||||
descriptionAsSafeHTML: markdownAndSanitize(hashedLink.eventType.description),
|
||||
});
|
||||
|
||||
const [user] = users;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import type { GetServerSidePropsContext } from "next";
|
||||
|
||||
import { parseRecurringEvent } from "@calcom/lib";
|
||||
import { markdownAndSanitize } from "@calcom/lib/markdownAndSanitize";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { bookEventTypeSelect } from "@calcom/prisma/selects";
|
||||
import { customInputSchema, eventTypeBookingFields, EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
||||
|
@ -93,6 +94,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
|||
brandColor: u.brandColor,
|
||||
darkBrandColor: u.darkBrandColor,
|
||||
})),
|
||||
descriptionAsSafeHTML: markdownAndSanitize(eventType.description),
|
||||
};
|
||||
})[0];
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ import { APP_NAME, CAL_URL, WEBAPP_URL } from "@calcom/lib/constants";
|
|||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import useMediaQuery from "@calcom/lib/hooks/useMediaQuery";
|
||||
import { useTypedQuery } from "@calcom/lib/hooks/useTypedQuery";
|
||||
import { markdownAndSanitize } from "@calcom/lib/markdownAndSanitizeClientSide";
|
||||
import type { RouterOutputs } from "@calcom/trpc/react";
|
||||
import { trpc, TRPCClientError } from "@calcom/trpc/react";
|
||||
import {
|
||||
|
@ -141,7 +142,7 @@ const Item = ({ type, group, readOnly }: { type: EventType; group: EventTypeGrou
|
|||
</div>
|
||||
<EventTypeDescription
|
||||
// @ts-expect-error FIXME: We have a type mismatch here @hariombalhara @sean-brydon
|
||||
eventType={type}
|
||||
eventType={{ ...type, descriptionAsSafeHTML: markdownAndSanitize(type.description) }}
|
||||
shortenDescription
|
||||
/>
|
||||
</Link>
|
||||
|
|
|
@ -10,7 +10,7 @@ import { CAL_URL } from "@calcom/lib/constants";
|
|||
import { getPlaceholderAvatar } from "@calcom/lib/defaultAvatarImage";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import useTheme from "@calcom/lib/hooks/useTheme";
|
||||
import { md } from "@calcom/lib/markdownIt";
|
||||
import { markdownAndSanitize } from "@calcom/lib/markdownAndSanitize";
|
||||
import { getTeamWithMembers } from "@calcom/lib/server/queries/teams";
|
||||
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
@ -113,7 +113,7 @@ function TeamPage({ team, isUnpublished }: TeamPageProps) {
|
|||
<>
|
||||
<div
|
||||
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(team.bio || "") }}
|
||||
dangerouslySetInnerHTML={{ __html: team.safeBio }}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
@ -187,11 +187,18 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
|||
...user,
|
||||
avatar: CAL_URL + "/" + user.username + "/avatar.png",
|
||||
})),
|
||||
descriptionAsSafeHTML: markdownAndSanitize(type.description),
|
||||
}));
|
||||
|
||||
const safeBio = markdownAndSanitize(team.bio) || "";
|
||||
|
||||
const members = team.members.map((member) => {
|
||||
return { ...member, safeBio: markdownAndSanitize(member.bio || "") };
|
||||
});
|
||||
|
||||
return {
|
||||
props: {
|
||||
team,
|
||||
team: { ...team, safeBio, members },
|
||||
trpcState: ssr.dehydrate(),
|
||||
},
|
||||
} as const;
|
||||
|
|
|
@ -5,6 +5,7 @@ import { privacyFilteredLocations } from "@calcom/core/location";
|
|||
import { getBookingFieldsWithSystemFields } from "@calcom/features/bookings/lib/getBookingFields";
|
||||
import { parseRecurringEvent } from "@calcom/lib";
|
||||
import { getWorkingHours } from "@calcom/lib/availability";
|
||||
import { markdownAndSanitize } from "@calcom/lib/markdownAndSanitize";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
||||
|
||||
|
@ -171,6 +172,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
|||
hideBranding,
|
||||
timeZone,
|
||||
})),
|
||||
descriptionAsSafeHTML: markdownAndSanitize(eventType.description),
|
||||
});
|
||||
|
||||
eventTypeObject.availability = [];
|
||||
|
|
|
@ -5,6 +5,7 @@ import type { LocationObject } from "@calcom/app-store/locations";
|
|||
import { privacyFilteredLocations } from "@calcom/app-store/locations";
|
||||
import { getBookingFieldsWithSystemFields } from "@calcom/features/bookings/lib/getBookingFields";
|
||||
import { parseRecurringEvent } from "@calcom/lib";
|
||||
import { markdownAndSanitize } from "@calcom/lib/markdownAndSanitize";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { customInputSchema, eventTypeBookingFields, EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
||||
|
||||
|
@ -121,6 +122,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
|||
image: u.avatar,
|
||||
slug: u.username,
|
||||
})),
|
||||
descriptionAsSafeHTML: markdownAndSanitize(eventType.description),
|
||||
};
|
||||
})[0];
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { markdownAndSanitize } from "@calcom/lib/markdownAndSanitize";
|
||||
|
||||
const Spacer = () => <p style={{ height: 6 }} />;
|
||||
|
||||
export const Info = (props: {
|
||||
|
@ -6,8 +8,14 @@ export const Info = (props: {
|
|||
extraInfo?: React.ReactNode;
|
||||
withSpacer?: boolean;
|
||||
lineThrough?: boolean;
|
||||
formatted?: boolean;
|
||||
}) => {
|
||||
if (!props.description || props.description === "") return null;
|
||||
|
||||
const descriptionCSS = "color: '#101010'; font-weight: 400; line-height: 24px; margin: 0;";
|
||||
|
||||
const safeDescription = markdownAndSanitize(props.description.toString()) || "";
|
||||
|
||||
return (
|
||||
<>
|
||||
{props.withSpacer && <Spacer />}
|
||||
|
@ -21,7 +29,18 @@ export const Info = (props: {
|
|||
whiteSpace: "pre-wrap",
|
||||
textDecoration: props.lineThrough ? "line-through" : undefined,
|
||||
}}>
|
||||
{props.description}
|
||||
{props.formatted ? (
|
||||
<p
|
||||
className="dark:text-darkgray-600 mt-2 text-sm text-gray-500 [&_a]:text-blue-500 [&_a]:underline [&_a]:hover:text-blue-600"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: safeDescription
|
||||
.replaceAll("<p>", `<p style="${descriptionCSS}">`)
|
||||
.replaceAll("<li>", `<li style="${descriptionCSS}">`),
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
props.description
|
||||
)}
|
||||
</p>
|
||||
{props.extraInfo}
|
||||
</div>
|
||||
|
|
|
@ -76,7 +76,7 @@ export const BaseScheduledEmail = (
|
|||
<WhenInfo calEvent={props.calEvent} t={t} timeZone={timeZone} />
|
||||
<WhoInfo calEvent={props.calEvent} t={t} />
|
||||
<LocationInfo calEvent={props.calEvent} t={t} />
|
||||
<Info label={t("description")} description={props.calEvent.description} withSpacer />
|
||||
<Info label={t("description")} description={props.calEvent.description} withSpacer formatted />
|
||||
<Info label={t("additional_notes")} description={props.calEvent.additionalNotes} withSpacer />
|
||||
{props.includeAppsStatus && <AppsStatus calEvent={props.calEvent} t={t} />}
|
||||
<UserFieldsResponses calEvent={props.calEvent} />
|
||||
|
|
|
@ -344,13 +344,15 @@ function keepParentInformedAboutDimensionChanges() {
|
|||
// Use, .height as that gives more accurate value in floating point. Also, do a ceil on the total sum so that whatever happens there is enough iframe size to avoid scroll.
|
||||
const contentHeight = Math.ceil(
|
||||
parseFloat(mainElementStyles.height) +
|
||||
parseFloat(mainElementStyles.marginTop) +
|
||||
parseFloat(mainElementStyles.marginBottom));
|
||||
parseFloat(mainElementStyles.marginTop) +
|
||||
parseFloat(mainElementStyles.marginBottom)
|
||||
);
|
||||
const contentWidth = Math.ceil(
|
||||
parseFloat(mainElementStyles.width) +
|
||||
parseFloat(mainElementStyles.marginLeft) +
|
||||
parseFloat(mainElementStyles.marginRight));
|
||||
|
||||
parseFloat(mainElementStyles.marginLeft) +
|
||||
parseFloat(mainElementStyles.marginRight)
|
||||
);
|
||||
|
||||
// During first render let iframe tell parent that how much is the expected height to avoid scroll.
|
||||
// Parent would set the same value as the height of iframe which would prevent scroll.
|
||||
// On subsequent renders, consider html height as the height of the iframe. If we don't do this, then if iframe get's bigger in height, it would never shrink
|
||||
|
|
|
@ -10,6 +10,7 @@ import { z } from "zod";
|
|||
import { IS_TEAM_BILLING_ENABLED, WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { getPlaceholderAvatar } from "@calcom/lib/defaultAvatarImage";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { markdownAndSanitize } from "@calcom/lib/markdownAndSanitizeClientSide";
|
||||
import { md } from "@calcom/lib/markdownIt";
|
||||
import objectKeys from "@calcom/lib/objectKeys";
|
||||
import turndown from "@calcom/lib/turndownService";
|
||||
|
@ -256,7 +257,7 @@ const ProfileView = () => {
|
|||
<Label className="mt-5 text-black">{t("about")}</Label>
|
||||
<div
|
||||
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(team.bio || "") }}
|
||||
dangerouslySetInnerHTML={{ __html: markdownAndSanitize(team.bio) }}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
|
|
@ -7,7 +7,6 @@ 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 { addListFormatting, md } from "@calcom/lib/markdownIt";
|
||||
import type { baseEventTypeSelect } from "@calcom/prisma";
|
||||
import type { EventTypeModel } from "@calcom/prisma/zod";
|
||||
import { Badge } from "@calcom/ui";
|
||||
|
@ -26,8 +25,9 @@ export type EventTypeDescriptionProps = {
|
|||
z.infer<typeof EventTypeModel>,
|
||||
Exclude<keyof typeof baseEventTypeSelect, "recurringEvent"> | "metadata"
|
||||
> & {
|
||||
descriptionAsSafeHTML?: string | null;
|
||||
recurringEvent: Prisma.JsonValue;
|
||||
seatsPerTimeSlot?: number;
|
||||
seatsPerTimeSlot?: number | null;
|
||||
};
|
||||
className?: string;
|
||||
shortenDescription?: boolean;
|
||||
|
@ -57,7 +57,7 @@ export const EventTypeDescription = ({
|
|||
shortenDescription ? "line-clamp-4" : ""
|
||||
)}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: addListFormatting(md.render(eventType.description)),
|
||||
__html: eventType.descriptionAsSafeHTML || "",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
import DOMPurify from "dompurify";
|
||||
import { JSDOM } from "jsdom";
|
||||
|
||||
import { md } from "@calcom/lib/markdownIt";
|
||||
|
||||
export function markdownAndSanitize(markdown: string | null) {
|
||||
if (!markdown) return null;
|
||||
|
||||
const window = new JSDOM("").window;
|
||||
// @ts-expect-error as suggested here: https://github.com/cure53/DOMPurify/issues/437#issuecomment-632021941
|
||||
const purify = DOMPurify(window);
|
||||
|
||||
const html = md
|
||||
.render(markdown)
|
||||
.replaceAll(
|
||||
"<ul>",
|
||||
"<ul style='list-style-type: disc; list-style-position: inside; margin-left: 12px; margin-bottom: 4px'>"
|
||||
)
|
||||
.replaceAll(
|
||||
"<ol>",
|
||||
"<ol style='list-style-type: decimal; list-style-position: inside; margin-left: 12px; margin-bottom: 4px'>"
|
||||
);
|
||||
const safeHtml = purify.sanitize(html);
|
||||
return safeHtml;
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
import DOMPurify from "dompurify";
|
||||
|
||||
import { md } from "./markdownIt";
|
||||
|
||||
export function markdownAndSanitize(markdown: string | null) {
|
||||
if (!markdown) return "";
|
||||
|
||||
const html = md
|
||||
.render(markdown)
|
||||
.replaceAll(
|
||||
"<ul>",
|
||||
"<ul style='list-style-type: disc; list-style-position: inside; margin-left: 12px; margin-bottom: 4px'>"
|
||||
)
|
||||
.replaceAll(
|
||||
"<ol>",
|
||||
"<ol style='list-style-type: decimal; list-style-position: inside; margin-left: 12px; margin-bottom: 4px'>"
|
||||
);
|
||||
|
||||
const safeHtml = DOMPurify.sanitize(html);
|
||||
return safeHtml;
|
||||
}
|
|
@ -1,12 +1,3 @@
|
|||
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'>"
|
||||
);
|
||||
}
|
||||
|
|
|
@ -203,7 +203,8 @@
|
|||
"categories": ["video"],
|
||||
"slug": "facetime",
|
||||
"type": "facetime_video",
|
||||
"isTemplate": false},
|
||||
"isTemplate": false
|
||||
},
|
||||
{
|
||||
"dirName": "zohocrm",
|
||||
"categories": ["other"],
|
||||
|
|
|
@ -12,6 +12,7 @@ import { $createHeadingNode, $isHeadingNode } from "@lexical/rich-text";
|
|||
import { $isAtNodeEnd, $wrapNodes } from "@lexical/selection";
|
||||
import { $getNearestNodeOfType, mergeRegister } from "@lexical/utils";
|
||||
import classNames from "classnames";
|
||||
import DOMPurify from "dompurify";
|
||||
import type { EditorState, GridSelection, LexicalEditor, NodeSelection, RangeSelection } from "lexical";
|
||||
import {
|
||||
$createParagraphNode,
|
||||
|
@ -351,8 +352,8 @@ export default function ToolbarPlugin(props: TextEditorProps) {
|
|||
|
||||
editor.registerUpdateListener(({ editorState, prevEditorState }) => {
|
||||
editorState.read(() => {
|
||||
const textInHtml = $generateHtmlFromNodes(editor);
|
||||
props.setText(textInHtml);
|
||||
const textInHtml = $generateHtmlFromNodes(editor).replace(/</g, "<").replace(/>/g, ">");
|
||||
props.setText(DOMPurify.sanitize(textInHtml));
|
||||
});
|
||||
if (!prevEditorState._selection) editor.blur();
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue