feat: Support adding links to checkbox type question using markdown[Blocked on another PR] (#8943)

* Support adding links in boolean checkbox

* Fix and comment

* remove textarea, it was accidentally pushed.
This commit is contained in:
Hariom Balhara authored and GitHub committed 2023-07-21 23:30:28 +05:30
1 parent d353904c7c
commit 61f1a3b21e
8 files changed
+65 -8

No files matched your search

@@ -1,6 +1,8 @@
import type { EventTypeCustomInput, EventType, Prisma, Workflow } from "@prisma/client";
import type { z } from "zod";
import { fieldsThatSupportLabelAsSafeHtml } from "@calcom/features/form-builder/fieldsThatSupportLabelAsSafeHtml";
import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
import slugify from "@calcom/lib/slugify";
import { EventTypeCustomInputType } from "@calcom/prisma/enums";
import {
@@ -328,7 +330,16 @@ export const ensureBookingInputsHaveSystemFields = ({
};
}
}
bookingFields = bookingFields.concat(missingSystemAfterFields);
bookingFields = bookingFields.concat(missingSystemAfterFields).map((f) => {
return {
...f,
// TODO: This has to be a FormBuilder feature and not be specific to bookingFields. Either use zod transform in FormBuilder to add labelAsSafeHtml automatically or add a getter for fields that would do this.
...(fieldsThatSupportLabelAsSafeHtml.includes(f.type)
? { labelAsSafeHtml: markdownToSafeHTML(f.label || null) || "" }
: null),
};
});
return eventTypeBookingFields.brand<"HAS_SYSTEM_FIELDS">().parse(bookingFields);
};
@@ -480,7 +480,9 @@ export const Components: Record<FieldType, Component> = {
placeholder=""
checked={value}
disabled={readOnly}
description={label ?? ""}
description=""
// Form Builder ensures that it would be safe HTML in here if the field type supports it. So, we can safely use label value in `descriptionAsSafeHtml`
descriptionAsSafeHtml={label ?? ""}
/>
</div>
);
+14 -1
View File
@@ -6,6 +6,7 @@ import type { z } from "zod";
import { classNames } from "@calcom/lib";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
import {
Label,
Badge,
@@ -26,6 +27,7 @@ import {
import { ArrowDown, ArrowUp, X, Plus, Trash2 } from "@calcom/ui/components/icon";
import { fieldTypesConfigMap } from "./fieldTypes";
import { fieldsThatSupportLabelAsSafeHtml } from "./fieldsThatSupportLabelAsSafeHtml";
import type { fieldsSchema } from "./schema";
import { getVariantsConfig } from "./utils";
@@ -530,7 +532,18 @@ function FieldLabel({ field }: { field: RhfFormField }) {
const variantsConfig = field.variantsConfig;
const defaultVariant = fieldTypeConfigVariantsConfig?.defaultVariant;
if (!fieldTypeConfigVariants || !variantsConfig) {
return <span>{field.label || t(field.defaultLabel || "")}</span>;
if (fieldsThatSupportLabelAsSafeHtml.includes(field.type)) {
return (
<span
dangerouslySetInnerHTML={{
// Derive from field.label because label might change in b/w and field.labelAsSafeHtml will not be updated.
__html: markdownToSafeHTML(field.label || "") || t(field.defaultLabel || ""),
}}
/>
);
} else {
return <span>{field.label || t(field.defaultLabel || "")}</span>;
}
}
const variant = field.variant || defaultVariant;
if (!variant) {
@@ -10,6 +10,7 @@ import { Info } from "@calcom/ui/components/icon";
import { Components, isValidValueProp } from "./Components";
import { fieldTypesConfigMap } from "./fieldTypes";
import { fieldsThatSupportLabelAsSafeHtml } from "./fieldsThatSupportLabelAsSafeHtml";
import type { fieldsSchema } from "./schema";
import { getVariantsConfig } from "./utils";
@@ -134,7 +135,7 @@ const WithLabel = ({
<div className="mb-2 flex items-center">
<Label className="!mb-0">
<span>{field.label}</span>
<span className="text-emphasis ml-1 -mb-1 text-sm font-medium leading-none">
<span className="text-emphasis -mb-1 ml-1 text-sm font-medium leading-none">
{!readOnly && field.required ? "*" : ""}
</span>
</Label>
@@ -169,7 +170,14 @@ function getAndUpdateNormalizedValues(field: RhfFormFields[number], t: TFunction
}
}
const label = noLabel ? "" : field.label || t(field.defaultLabel || "");
/**
* Instead of passing labelAsSafeHtml props to all the components, FormBuilder components can assume that the label is safe html and use it on a case by case basis after adding checks here
*/
if (fieldsThatSupportLabelAsSafeHtml.includes(field.type) && field.labelAsSafeHtml === undefined) {
throw new Error(`${field.name}:${field.type} type must have labelAsSafeHtml set`);
}
const label = noLabel ? "" : field.labelAsSafeHtml || field.label || t(field.defaultLabel || "");
const placeholder = field.placeholder || t(field.defaultPlaceholder || "");
if (field.variantsConfig?.variants) {
@@ -0,0 +1,7 @@
import type { FieldType } from "./schema";
/**
* Once a component supports `labelAsSafeHtml`, add it's field's type here
* A whitelist is needed because unless we explicitly use dangerouslySetInnerHTML, React will escape the HTML
*/
export const fieldsThatSupportLabelAsSafeHtml: FieldType[] = ["boolean"];
+2
View File
@@ -35,6 +35,8 @@ const baseFieldSchema = z.object({
type: fieldTypeEnum,
// TODO: We should make at least one of `defaultPlaceholder` and `placeholder` required. Do the same for label.
label: z.string().optional(),
labelAsSafeHtml: z.string().optional(),
/**
* It is the default label that will be used when a new field is created.
* Note: It belongs in FieldsTypeConfig, so that changing defaultLabel in code can work for existing fields as well(for fields that are using the default label).
+2 -1
View File
@@ -17,7 +17,8 @@ export function markdownToSafeHTML(markdown: string | null) {
.replace(
/<ol>/g,
"<ol style='list-style-type: decimal; list-style-position: inside; margin-left: 12px; margin-bottom: 4px'>"
);
)
.replace(/<a\s+href=/g, "<a target='_blank' class='text-blue-500 hover:text-blue-600' href=");
return safeHTMLWithListFormatting;
}
@@ -14,6 +14,10 @@ type Props = InputHTMLAttributes<HTMLInputElement> & {
error?: boolean;
className?: string;
descriptionClassName?: string;
/**
* Accepts this special property instead of allowing description itself to be accidentally used in dangerous way.
*/
descriptionAsSafeHtml?: string;
};
const Checkbox = React.forwardRef<
@@ -35,7 +39,7 @@ const Checkbox = React.forwardRef<
Checkbox.displayName = CheckboxPrimitive.Root.displayName;
const CheckboxField = forwardRef<HTMLInputElement, Props>(
({ label, description, error, disabled, ...rest }, ref) => {
({ label, description, error, disabled, descriptionAsSafeHtml, ...rest }, ref) => {
const descriptionAsLabel = !label || rest.descriptionAsLabel;
const id = useId();
return (
@@ -86,7 +90,16 @@ const CheckboxField = forwardRef<HTMLInputElement, Props>(
)}
/>
</div>
<span className={classNames("text-sm", rest.descriptionClassName)}>{description}</span>
{descriptionAsSafeHtml ? (
<span
className={classNames("text-sm", rest.descriptionClassName)}
dangerouslySetInnerHTML={{
__html: descriptionAsSafeHtml,
}}
/>
) : (
<span className={classNames("text-sm", rest.descriptionClassName)}>{description}</span>
)}
</>
)}
{/* {informationIconText && <InfoBadge content={informationIconText}></InfoBadge>} */}