Merge branch 'main' of github.com:formbricks/formbricks

This commit is contained in:
Matthias Nannt
2023-04-24 20:14:32 +02:00
42 changed files with 2084 additions and 207 deletions

View File

@@ -0,0 +1,125 @@
"use client";
import type { CTAQuestion } from "@formbricks/types/questions";
import { Editor, Input, Label } from "@formbricks/ui";
import { RadioGroup, RadioGroupItem } from "@formbricks/ui";
import { useState } from "react";
import { md } from "@formbricks/lib/markdownIt";
interface CTAQuestionFormProps {
question: CTAQuestion;
questionIdx: number;
updateQuestion: (questionIdx: number, updatedAttributes: any) => void;
lastQuestion: boolean;
}
export default function CTAQuestionForm({
question,
questionIdx,
updateQuestion,
lastQuestion,
}: CTAQuestionFormProps) {
const [firstRender, setFirstRender] = useState(true);
return (
<form>
<div className="mt-3">
<Label htmlFor="headline">Question</Label>
<div className="mt-2">
<Input
id="headline"
name="headline"
value={question.headline}
onChange={(e) => updateQuestion(questionIdx, { headline: e.target.value })}
/>
</div>
</div>
<div className="mt-3">
<Label htmlFor="subheader">Description</Label>
<div className="mt-2">
{/* <Input
id="subheader"
name="subheader"
value={question.subheader}
onChange={(e) => updateQuestion(questionIdx, { subheader: e.target.value })}
/> */}
<Editor
getText={() => md.render(question.html || "")}
setText={(value: string) => {
updateQuestion(questionIdx, { html: value });
}}
excludedToolbarItems={["blockType"]}
disableLists
firstRender={firstRender}
setFirstRender={setFirstRender}
/>
</div>
</div>
<RadioGroup
className="mt-3 flex"
defaultValue="internal"
value={question.buttonExternal ? "external" : "internal"}
onValueChange={(e) => updateQuestion(questionIdx, { buttonExternal: e === "external" })}>
<div className="flex items-center space-x-2 rounded-lg border border-slate-200 p-3 dark:border-slate-500">
<RadioGroupItem value="internal" id="internal" className="bg-slate-50" />
<Label htmlFor="internal" className="cursor-pointer dark:text-slate-200">
Button to continue in survey
</Label>
</div>
<div className="flex items-center space-x-2 rounded-lg border border-slate-200 p-3 dark:border-slate-500">
<RadioGroupItem value="external" id="external" className="bg-slate-50" />
<Label htmlFor="external" className="cursor-pointer dark:text-slate-200">
Button to link to external URL
</Label>
</div>
</RadioGroup>
<div className="mt-3 flex justify-between gap-8">
<div className="flex-1">
<Label htmlFor="buttonLabel">Button Label</Label>
<div className="mt-2">
<Input
id="buttonLabel"
name="buttonLabel"
value={question.buttonLabel}
placeholder={lastQuestion ? "Finish" : "Next"}
onChange={(e) => updateQuestion(questionIdx, { buttonLabel: e.target.value })}
/>
</div>
</div>
{question.buttonExternal && (
<div className="flex-1">
<Label htmlFor="buttonLabel">Button URL</Label>
<div className="mt-2">
<Input
id="buttonUrl"
name="buttonUrl"
value={question.buttonUrl}
placeholder="https://website.com"
onChange={(e) => updateQuestion(questionIdx, { buttonUrl: e.target.value })}
/>
</div>
</div>
)}
</div>
<div className="mt-3">
{!question.required && (
<div className="flex-1">
<Label htmlFor="buttonLabel">Skip Button Label</Label>
<div className="mt-2">
<Input
id="dismissButtonLabel"
name="dismissButtonLabel"
value={question.dismissButtonLabel}
placeholder="Skip"
onChange={(e) => updateQuestion(questionIdx, { dismissButtonLabel: e.target.value })}
/>
</div>
</div>
)}
</div>
</form>
);
}

View File

@@ -15,6 +15,7 @@ import OpenQuestionForm from "./OpenQuestionForm";
import QuestionDropdown from "./QuestionDropdown";
import NPSQuestionForm from "./NPSQuestionForm";
import UpdateQuestionId from "./UpdateQuestionId";
import CTAQuestionForm from "./CTAQuestionForm";
interface QuestionCardProps {
localSurvey: Survey;
@@ -131,6 +132,13 @@ export default function QuestionCard({
updateQuestion={updateQuestion}
lastQuestion={lastQuestion}
/>
) : question.type === "cta" ? (
<CTAQuestionForm
question={question}
questionIdx={questionIdx}
updateQuestion={updateQuestion}
lastQuestion={lastQuestion}
/>
) : null}
<div className="mt-4 border-t border-slate-200">
<Collapsible.Root open={openAdvanced} onOpenChange={setOpenAdvanced} className="mt-3">

View File

@@ -56,7 +56,7 @@ export default function SingleResponse({ data, environmentId }: OpenTextSummaryP
{data.responses.map((response, idx) => (
<div key={`${response.id}-${idx}`}>
<p className="text-sm text-slate-500">{response.question}</p>
{typeof response.answer === "string" ? (
{typeof response.answer !== "object" ? (
<p className="ph-no-capture my-1 font-semibold text-slate-700">{response.answer}</p>
) : (
<p className="ph-no-capture my-1 font-semibold text-slate-700">{response.answer.join(", ")}</p>

View File

@@ -0,0 +1,57 @@
import { ProgressBar } from "@formbricks/ui";
import type { QuestionSummary } from "@formbricks/types/responses";
import { InboxStackIcon } from "@heroicons/react/24/solid";
import { useMemo } from "react";
interface CTASummaryProps {
questionSummary: QuestionSummary;
}
interface ChoiceResult {
count: number;
percentage: number;
}
export default function CTASummary({ questionSummary }: CTASummaryProps) {
const ctr: ChoiceResult = useMemo(() => {
const clickedAbs = questionSummary.responses.filter((response) => response.value === "clicked").length;
return {
count: questionSummary.responses.length,
percentage: clickedAbs / questionSummary.responses.length,
};
}, [questionSummary]);
return (
<div className=" rounded-lg border border-slate-200 bg-slate-50 shadow-sm">
<div className="space-y-2 px-6 pb-5 pt-6">
<div>
<h3 className="pb-1 text-xl font-semibold text-slate-900">{questionSummary.question.headline}</h3>
</div>
<div className="flex space-x-2 font-semibold text-slate-600">
<div className="rounded-lg bg-slate-100 p-2 text-sm">Call-to-Action</div>
<div className=" flex items-center rounded-lg bg-slate-100 p-2 text-sm">
<InboxStackIcon className="mr-2 h-4 w-4 " />
{ctr.count} responses
</div>
</div>
</div>
<div className="space-y-5 rounded-b-lg bg-white px-6 pb-6 pt-4">
<div className="text flex justify-between px-2 pb-2">
<div className="mr-8 flex space-x-1">
<p className="font-semibold text-slate-700">Clickthrough Rate (CTR)</p>
<div>
<p className="rounded-lg bg-slate-100 px-2 text-slate-700">
{Math.round(ctr.percentage * 100)}%
</p>
</div>
</div>
<p className="flex w-32 items-end justify-end text-slate-600">
{ctr.count} {ctr.count === 1 ? "response" : "responses"}
</p>
</div>
<ProgressBar barColor="bg-brand" progress={ctr.percentage} />
</div>
</div>
);
}

View File

@@ -17,9 +17,9 @@ interface Result {
}
export default function NPSSummary({ questionSummary }: NPSSummaryProps) {
console.log(questionSummary);
const percentage = (count, total) => {
return count / total;
const result = count / total;
return result || 0;
};
const result: Result = useMemo(() => {

View File

@@ -10,6 +10,7 @@ import { useMemo } from "react";
import MultipleChoiceSummary from "./MultipleChoiceSummary";
import OpenTextSummary from "./OpenTextSummary";
import NPSSummary from "./NPSSummary";
import CTASummary from "./CTASummary";
export default function SummaryList({ environmentId, surveyId }) {
const { responsesData, isLoadingResponses, isErrorResponses } = useResponses(environmentId, surveyId);
@@ -80,6 +81,9 @@ export default function SummaryList({ environmentId, surveyId }) {
if (questionSummary.question.type === "nps") {
return <NPSSummary key={questionSummary.question.id} questionSummary={questionSummary} />;
}
if (questionSummary.question.type === "cta") {
return <CTASummary key={questionSummary.question.id} questionSummary={questionSummary} />;
}
return null;
})}
</>

View File

@@ -0,0 +1,45 @@
import type { CTAQuestion } from "@formbricks/types/questions";
import Headline from "./Headline";
import HtmlBody from "./HtmlBody";
interface CTAQuestionProps {
question: CTAQuestion;
onSubmit: (data: { [x: string]: any }) => void;
lastQuestion: boolean;
brandColor: string;
}
export default function CTAQuestion({ question, onSubmit, lastQuestion, brandColor }: CTAQuestionProps) {
return (
<div>
<Headline headline={question.headline} questionId={question.id} />
<HtmlBody htmlString={question.html || ""} questionId={question.id} />
<div className="mt-4 flex w-full justify-end">
<div></div>
{!question.required && (
<button
type="button"
onClick={() => {
onSubmit({ [question.id]: "dismissed" });
}}
className="mr-4 flex items-center rounded-md px-3 py-3 text-base font-medium leading-4 text-slate-500 hover:opacity-90 focus:outline-none focus:ring-2 focus:ring-slate-500 focus:ring-offset-2 dark:border-slate-400 dark:text-slate-400">
{question.dismissButtonLabel || "Skip"}
</button>
)}
<button
type="button"
onClick={() => {
if (question.buttonExternal && question.buttonUrl) {
window?.open(question.buttonUrl, "_blank")?.focus();
}
onSubmit({ [question.id]: "clicked" });
}}
className="flex items-center rounded-md border border-transparent px-3 py-3 text-base font-medium leading-4 text-white shadow-sm hover:opacity-90 focus:outline-none focus:ring-2 focus:ring-slate-500 focus:ring-offset-2"
style={{ backgroundColor: brandColor }}>
{question.buttonLabel || (lastQuestion ? "Finish" : "Next")}
</button>
</div>
</div>
);
}

View File

@@ -0,0 +1,10 @@
import { cleanHtml } from "@formbricks/lib/cleanHtml";
export default function HtmlBody({ htmlString, questionId }: { htmlString: string; questionId: string }) {
return (
<label
htmlFor={questionId}
className="fb-block fb-text-sm fb-font-normal fb-leading-6 text-slate-600"
dangerouslySetInnerHTML={{ __html: cleanHtml(htmlString) }}></label>
);
}

View File

@@ -3,6 +3,7 @@ import OpenTextQuestion from "./OpenTextQuestion";
import MultipleChoiceSingleQuestion from "./MultipleChoiceSingleQuestion";
import MultipleChoiceMultiQuestion from "./MultipleChoiceMultiQuestion";
import NPSQuestion from "./NPSQuestion";
import CTAQuestion from "./CTAQuestion";
interface QuestionConditionalProps {
question: Question;
@@ -45,5 +46,12 @@ export default function QuestionConditional({
lastQuestion={lastQuestion}
brandColor={brandColor}
/>
) : question.type === "cta" ? (
<CTAQuestion
question={question}
onSubmit={onSubmit}
lastQuestion={lastQuestion}
brandColor={brandColor}
/>
) : null;
}

View File

@@ -1,4 +1,9 @@
import { Bars3BottomLeftIcon, ChartPieIcon, ListBulletIcon } from "@heroicons/react/24/solid";
import {
Bars3BottomLeftIcon,
ChartPieIcon,
ListBulletIcon,
ArrowRightOnRectangleIcon,
} from "@heroicons/react/24/solid";
import { createId } from "@paralleldrive/cuid2";
import { replaceQuestionPresetPlaceholders } from "./templates";
@@ -55,6 +60,16 @@ export const questionTypes: QuestionType[] = [
upperLabel: "Extremely likely",
},
},
{
id: "cta",
label: "Call-to-Action",
description: "Ask your users to perform an action",
icon: ArrowRightOnRectangleIcon,
preset: {
buttonExternal: false,
dismissButtonLabel: "Skip",
},
},
];
export const universalQuestionPresets = {

View File

@@ -27,6 +27,7 @@
"eslint-config-next": "^13.3.0",
"jsonwebtoken": "^9.0.0",
"lucide-react": "^0.161.0",
"markdown-it": "^13.0.1",
"next": "13.2.4",
"next-auth": "^4.22.0",
"nodemailer": "^6.9.1",
@@ -53,6 +54,7 @@
"@tailwindcss/forms": "^0.5.3",
"@tailwindcss/typography": "^0.5.9",
"@types/bcryptjs": "^2.4.2",
"@types/markdown-it": "^12.2.3",
"autoprefixer": "^10.4.14",
"eslint-config-formbricks": "workspace:*",
"postcss": "^8.4.22",

View File

@@ -0,0 +1,47 @@
import { h } from "preact";
import type { CTAQuestion } from "@formbricks/types/questions";
import Headline from "./Headline";
import Subheader from "./Subheader";
import HtmlBody from "./HtmlBody";
interface CTAQuestionProps {
question: CTAQuestion;
onSubmit: (data: { [x: string]: any }) => void;
lastQuestion: boolean;
brandColor: string;
}
export default function CTAQuestion({ question, onSubmit, lastQuestion, brandColor }: CTAQuestionProps) {
return (
<div>
<Headline headline={question.headline} questionId={question.id} />
<HtmlBody htmlString={question.html} questionId={question.id} />
<div className="fb-mt-4 fb-flex fb-w-full fb-justify-end">
<div></div>
{!question.required && (
<button
type="button"
onClick={() => {
onSubmit({ [question.id]: "dismissed" });
}}
className="fb-flex fb-items-center dark:fb-text-slate-400 fb-rounded-md fb-px-3 fb-py-3 fb-text-base fb-font-medium fb-leading-4 fb-hover:opacity-90 fb-focus:outline-none fb-focus:ring-2 fb-focus:ring-slate-500 fb-focus:ring-offset-2 fb-mr-4">
{question.dismissButtonLabel || "Skip"}
</button>
)}
<button
type="button"
onClick={() => {
if (question.buttonExternal && question.buttonUrl) {
window?.open(question.buttonUrl, "_blank")?.focus();
}
onSubmit({ [question.id]: "clicked" });
}}
className="fb-flex fb-items-center fb-rounded-md fb-border fb-border-transparent fb-px-3 fb-py-3 fb-text-base fb-font-medium fb-leading-4 fb-text-white fb-shadow-sm fb-hover:opacity-90 fb-focus:outline-none fb-focus:ring-2 fb-focus:ring-slate-500 fb-focus:ring-offset-2"
style={{ backgroundColor: brandColor }}>
{question.buttonLabel || (lastQuestion ? "Finish" : "Next")}
</button>
</div>
</div>
);
}

View File

@@ -0,0 +1,11 @@
import { h } from "preact";
import { cleanHtml } from "../lib/cleanHtml";
export default function HtmlBody({ htmlString, questionId }: { htmlString?: string; questionId: string }) {
return (
<label
htmlFor={questionId}
className="fb-block fb-text-sm fb-font-normal fb-leading-6 text-slate-600"
dangerouslySetInnerHTML={{ __html: cleanHtml(htmlString) }}></label>
);
}

View File

@@ -4,6 +4,7 @@ import OpenTextQuestion from "./OpenTextQuestion";
import MultipleChoiceSingleQuestion from "./MultipleChoiceSingleQuestion";
import MultipleChoiceMultiQuestion from "./MultipleChoiceMultiQuestion";
import NPSQuestion from "./NPSQuestion";
import CTAQuestion from "./CTAQuestion";
interface QuestionConditionalProps {
question: Question;
@@ -46,5 +47,12 @@ export default function QuestionConditional({
lastQuestion={lastQuestion}
brandColor={brandColor}
/>
) : question.type === "cta" ? (
<CTAQuestion
question={question}
onSubmit={onSubmit}
lastQuestion={lastQuestion}
brandColor={brandColor}
/>
) : null;
}

View File

@@ -2,7 +2,7 @@ import { h } from "preact";
export default function Subheader({ subheader, questionId }: { subheader?: string; questionId: string }) {
return (
<label for={questionId} className="fb-block fb-text-sm fb-font-normal fb-leading-6 text-slate-600">
<label htmlFor={questionId} className="fb-block fb-text-sm fb-font-normal fb-leading-6 text-slate-600">
{subheader}
</label>
);

View File

@@ -0,0 +1,79 @@
/*!
* Sanitize an HTML string
* (c) 2021 Chris Ferdinandi, MIT License, https://gomakethings.com
* @param {String} str The HTML string to sanitize
* @return {String} The sanitized string
*/
export function cleanHtml(str: string): string {
/**
* Convert the string to an HTML document
* @return {Node} An HTML document
*/
function stringToHTML() {
let parser = new DOMParser();
let doc = parser.parseFromString(str, "text/html");
return doc.body || document.createElement("body");
}
/**
* Remove <script> elements
* @param {Node} html The HTML
*/
function removeScripts(html) {
let scripts = html.querySelectorAll("script");
for (let script of scripts) {
script.remove();
}
}
/**
* Check if the attribute is potentially dangerous
* @param {String} name The attribute name
* @param {String} value The attribute value
* @return {Boolean} If true, the attribute is potentially dangerous
*/
function isPossiblyDangerous(name, value) {
let val = value.replace(/\s+/g, "").toLowerCase();
if (["src", "href", "xlink:href"].includes(name)) {
if (val.includes("javascript:") || val.includes("data:")) return true;
}
if (name.startsWith("on")) return true;
}
/**
* Remove potentially dangerous attributes from an element
* @param {Node} elem The element
*/
function removeAttributes(elem) {
// Loop through each attribute
// If it's dangerous, remove it
let atts = elem.attributes;
for (let { name, value } of atts) {
if (!isPossiblyDangerous(name, value)) continue;
elem.removeAttribute(name);
}
}
/**
* Remove dangerous stuff from the HTML document's nodes
* @param {Node} html The HTML document
*/
function clean(html) {
let nodes = html.children;
for (let node of nodes) {
removeAttributes(node);
clean(node);
}
}
// Convert the string to HTML
let html = stringToHTML();
// Sanitize it
removeScripts(html);
clean(html);
// If the user wants HTML nodes back, return them
// Otherwise, pass a sanitized string back
return html.innerHTML;
}

View File

@@ -1,11 +1,12 @@
import css from "../style.css";
import preflight from "../preflight.css";
import editorCss from "../../../ui/components/editor/stylesEditorFrontend.css";
export const addStylesToDom = () => {
if (document.getElementById("formbricks__css") === null) {
const styleElement = document.createElement("style");
styleElement.id = "formbricks__css";
styleElement.innerHTML = preflight + css;
styleElement.innerHTML = preflight + css + editorCss;
document.head.appendChild(styleElement);
}
};

79
packages/lib/cleanHtml.ts Normal file
View File

@@ -0,0 +1,79 @@
/*!
* Sanitize an HTML string
* (c) 2021 Chris Ferdinandi, MIT License, https://gomakethings.com
* @param {String} str The HTML string to sanitize
* @return {String} The sanitized string
*/
export function cleanHtml(str: string): string {
/**
* Convert the string to an HTML document
* @return {Node} An HTML document
*/
function stringToHTML() {
let parser = new DOMParser();
let doc = parser.parseFromString(str, "text/html");
return doc.body || document.createElement("body");
}
/**
* Remove <script> elements
* @param {Node} html The HTML
*/
function removeScripts(html) {
let scripts = html.querySelectorAll("script");
for (let script of scripts) {
script.remove();
}
}
/**
* Check if the attribute is potentially dangerous
* @param {String} name The attribute name
* @param {String} value The attribute value
* @return {Boolean} If true, the attribute is potentially dangerous
*/
function isPossiblyDangerous(name, value) {
let val = value.replace(/\s+/g, "").toLowerCase();
if (["src", "href", "xlink:href"].includes(name)) {
if (val.includes("javascript:") || val.includes("data:")) return true;
}
if (name.startsWith("on")) return true;
}
/**
* Remove potentially dangerous attributes from an element
* @param {Node} elem The element
*/
function removeAttributes(elem) {
// Loop through each attribute
// If it's dangerous, remove it
let atts = elem.attributes;
for (let { name, value } of atts) {
if (!isPossiblyDangerous(name, value)) continue;
elem.removeAttribute(name);
}
}
/**
* Remove dangerous stuff from the HTML document's nodes
* @param {Node} html The HTML document
*/
function clean(html) {
let nodes = html.children;
for (let node of nodes) {
removeAttributes(node);
clean(node);
}
}
// Convert the string to HTML
let html = stringToHTML();
// Sanitize it
removeScripts(html);
clean(html);
// If the user wants HTML nodes back, return them
// Otherwise, pass a sanitized string back
return html.innerHTML;
}

View File

@@ -0,0 +1,3 @@
import MarkdownIt from "markdown-it";
export const md = new MarkdownIt("default", { html: true, breaks: true, linkify: true });

View File

@@ -2,7 +2,8 @@ export type Question =
| OpenTextQuestion
| MultipleChoiceSingleQuestion
| MultipleChoiceMultiQuestion
| NPSQuestion;
| NPSQuestion
| CTAQuestion;
export interface OpenTextQuestion {
id: string;
@@ -45,6 +46,18 @@ export interface NPSQuestion {
upperLabel: string;
}
export interface CTAQuestion {
id: string;
type: "cta";
headline: string;
html?: string;
required: boolean;
buttonLabel?: string;
buttonUrl?: string;
buttonExternal: boolean;
dismissButtonLabel?: string;
}
export interface Choice {
id: string;
label: string;

View File

@@ -1,8 +1,9 @@
import { cn } from "@formbricks/lib/cn";
import { LucideIcon } from "lucide-react";
import Link, { LinkProps } from "next/link";
import React, { AnchorHTMLAttributes, ButtonHTMLAttributes, forwardRef } from "react";
type SVGComponent = React.FunctionComponent<React.SVGProps<SVGSVGElement>>;
type SVGComponent = React.FunctionComponent<React.SVGProps<SVGSVGElement>> | LucideIcon;
export type ButtonBaseProps = {
variant?: "highlight" | "primary" | "secondary" | "minimal" | "warn" | "alert";

View File

@@ -19,6 +19,7 @@ export function HalfCircle({ value }: { value: number }) {
return (
<div className="w-fit">
<div className="relative flex h-28 w-52 items-end justify-center overflow-hidden">
<div className="absolute h-24 w-48 origin-bottom rounded-tl-full rounded-tr-full bg-slate-200"></div>
<div
className="bg-brand absolute h-24 w-48 origin-bottom rounded-tl-full rounded-tr-full"
style={{ rotate: mappedValue }}></div>
@@ -26,7 +27,7 @@ export function HalfCircle({ value }: { value: number }) {
</div>
<div className="flex justify-between text-sm leading-10 text-slate-600">
<p>-100</p>
<p className="text-4xl text-black">{value}</p>
<p className="text-4xl text-black">{Math.round(value)}</p>
<p>100</p>
</div>
</div>

View File

@@ -0,0 +1,106 @@
import { CodeHighlightNode, CodeNode } from "@lexical/code";
import { AutoLinkNode, LinkNode } from "@lexical/link";
import { ListItemNode, ListNode } from "@lexical/list";
import { TRANSFORMERS } from "@lexical/markdown";
import { LexicalComposer } from "@lexical/react/LexicalComposer";
import { ContentEditable } from "@lexical/react/LexicalContentEditable";
import { LinkPlugin } from "@lexical/react/LexicalLinkPlugin";
import { ListPlugin } from "@lexical/react/LexicalListPlugin";
import { MarkdownShortcutPlugin } from "@lexical/react/LexicalMarkdownShortcutPlugin";
import { RichTextPlugin } from "@lexical/react/LexicalRichTextPlugin";
import { HeadingNode, QuoteNode } from "@lexical/rich-text";
import { TableCellNode, TableNode, TableRowNode } from "@lexical/table";
import type { Dispatch, SetStateAction } from "react";
import { cn } from "@formbricks/lib/cn";
import ExampleTheme from "./ExampleTheme";
import AutoLinkPlugin from "./plugins/AutoLinkPlugin";
import ToolbarPlugin from "./plugins/ToolbarPlugin";
import "./stylesEditor.css";
import "./stylesEditorFrontend.css";
/*
Detault toolbar items:
- blockType
- bold
- italic
- link
*/
export type TextEditorProps = {
getText: () => string;
setText: (text: string) => void;
excludedToolbarItems?: string[];
variables?: string[];
height?: string;
placeholder?: string;
disableLists?: boolean;
updateTemplate?: boolean;
firstRender?: boolean;
setFirstRender?: Dispatch<SetStateAction<boolean>>;
editable?: boolean;
};
const editorConfig = {
theme: ExampleTheme,
onError(error: any) {
throw error;
},
namespace: "",
nodes: [
HeadingNode,
ListNode,
ListItemNode,
QuoteNode,
CodeNode,
CodeHighlightNode,
TableNode,
TableCellNode,
TableRowNode,
AutoLinkNode,
LinkNode,
],
};
export const Editor = (props: TextEditorProps) => {
const editable = props.editable ?? true;
return (
<div className="editor rounded-md">
<LexicalComposer initialConfig={{ ...editorConfig, editable }}>
<div className="editor-container rounded-md p-0">
<ToolbarPlugin
getText={props.getText}
setText={props.setText}
editable={editable}
excludedToolbarItems={props.excludedToolbarItems}
variables={props.variables}
updateTemplate={props.updateTemplate}
firstRender={props.firstRender}
setFirstRender={props.setFirstRender}
/>
<div
className={cn("editor-inner scroll-bar", !editable && "bg-muted")}
style={{ height: props.height }}>
<RichTextPlugin
contentEditable={<ContentEditable style={{ height: props.height }} className="editor-input" />}
placeholder={<div className="text-muted -mt-11 p-3 text-sm">{props.placeholder || ""}</div>}
ErrorBoundary={null}
/>
<ListPlugin />
<LinkPlugin />
<AutoLinkPlugin />
<MarkdownShortcutPlugin
transformers={
props.disableLists
? TRANSFORMERS.filter((value, index) => {
if (index !== 3 && index !== 4) return value;
})
: TRANSFORMERS
}
/>
</div>
</div>
</LexicalComposer>
</div>
);
};

View File

@@ -0,0 +1,24 @@
const exampleTheme = {
placeholder: "fb-editor-placeholder",
paragraph: "fb-editor-paragraph",
heading: {
h1: "fb-editor-heading-h1",
h2: "fb-editor-heading-h2",
},
list: {
nested: {
listitem: "fb-editor-nested-listitem",
},
ol: "fb-editor-list-ol",
ul: "fb-editor-list-ul",
listitem: "fb-editor-listitem",
},
image: "fb-editor-image",
link: "fb-editor-link",
text: {
bold: "fb-editor-text-bold",
italic: "fb-editor-text-italic",
},
};
export default exampleTheme;

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-link" viewBox="0 0 16 16">
<path d="M6.354 5.5H4a3 3 0 0 0 0 6h3a3 3 0 0 0 2.83-4H9c-.086 0-.17.01-.25.031A2 2 0 0 1 7 10.5H4a2 2 0 1 1 0-4h1.535c.218-.376.495-.714.82-1z"/>
<path d="M9 5.5a3 3 0 0 0-2.83 4h1.098A2 2 0 0 1 9 6.5h3a2 2 0 1 1 0 4h-1.535a4.02 4.02 0 0 1-.82 1H12a3 3 0 1 0 0-6H9z"/>
</svg>

After

Width:  |  Height:  |  Size: 404 B

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-list-ol" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M5 11.5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5z"/>
<path d="M1.713 11.865v-.474H2c.217 0 .363-.137.363-.317 0-.185-.158-.31-.361-.31-.223 0-.367.152-.373.31h-.59c.016-.467.373-.787.986-.787.588-.002.954.291.957.703a.595.595 0 0 1-.492.594v.033a.615.615 0 0 1 .569.631c.003.533-.502.8-1.051.8-.656 0-1-.37-1.008-.794h.582c.008.178.186.306.422.309.254 0 .424-.145.422-.35-.002-.195-.155-.348-.414-.348h-.3zm-.004-4.699h-.604v-.035c0-.408.295-.844.958-.844.583 0 .96.326.96.756 0 .389-.257.617-.476.848l-.537.572v.03h1.054V9H1.143v-.395l.957-.99c.138-.142.293-.304.293-.508 0-.18-.147-.32-.342-.32a.33.33 0 0 0-.342.338v.041zM2.564 5h-.635V2.924h-.031l-.598.42v-.567l.629-.443h.635V5z"/>
</svg>

After

Width:  |  Height:  |  Size: 984 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-list-ul" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M5 11.5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5zm-3 1a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm0 4a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm0 4a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/>
</svg>

After

Width:  |  Height:  |  Size: 448 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-pencil-fill" viewBox="0 0 16 16">
<path d="M12.854.146a.5.5 0 0 0-.707 0L10.5 1.793 14.207 5.5l1.647-1.646a.5.5 0 0 0 0-.708l-3-3zm.646 6.061L9.793 2.5 3.293 9H3.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.207l6.5-6.5zm-7.468 7.468A.5.5 0 0 1 6 13.5V13h-.5a.5.5 0 0 1-.5-.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.5-.5V10h-.5a.499.499 0 0 1-.175-.032l-.179.178a.5.5 0 0 0-.11.168l-2 5a.5.5 0 0 0 .65.65l5-2a.5.5 0 0 0 .168-.11l.178-.178z"/>
</svg>

After

Width:  |  Height:  |  Size: 590 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-text-paragraph" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M2 12.5a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5zm0-3a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5zm0-3a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5zm4-3a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5z"/>
</svg>

After

Width:  |  Height:  |  Size: 418 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-type-bold" viewBox="0 0 16 16">
<path d="M8.21 13c2.106 0 3.412-1.087 3.412-2.823 0-1.306-.984-2.283-2.324-2.386v-.055a2.176 2.176 0 0 0 1.852-2.14c0-1.51-1.162-2.46-3.014-2.46H3.843V13H8.21zM5.908 4.674h1.696c.963 0 1.517.451 1.517 1.244 0 .834-.629 1.32-1.73 1.32H5.908V4.673zm0 6.788V8.598h1.73c1.217 0 1.88.492 1.88 1.415 0 .943-.643 1.449-1.832 1.449H5.907z"/>
</svg>

After

Width:  |  Height:  |  Size: 471 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-type-h1" viewBox="0 0 16 16">
<path d="M8.637 13V3.669H7.379V7.62H2.758V3.67H1.5V13h1.258V8.728h4.62V13h1.259zm5.329 0V3.669h-1.244L10.5 5.316v1.265l2.16-1.565h.062V13h1.244z"/>
</svg>

After

Width:  |  Height:  |  Size: 283 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-type-h2" viewBox="0 0 16 16">
<path d="M7.638 13V3.669H6.38V7.62H1.759V3.67H.5V13h1.258V8.728h4.62V13h1.259zm3.022-6.733v-.048c0-.889.63-1.668 1.716-1.668.957 0 1.675.608 1.675 1.572 0 .855-.554 1.504-1.067 2.085l-3.513 3.999V13H15.5v-1.094h-4.245v-.075l2.481-2.844c.875-.998 1.586-1.784 1.586-2.953 0-1.463-1.155-2.556-2.919-2.556-1.941 0-2.966 1.326-2.966 2.74v.049h1.223z"/>
</svg>

After

Width:  |  Height:  |  Size: 483 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-type-italic" viewBox="0 0 16 16">
<path d="M7.991 11.674 9.53 4.455c.123-.595.246-.71 1.347-.807l.11-.52H7.211l-.11.52c1.06.096 1.128.212 1.005.807L6.57 11.674c-.123.595-.246.71-1.346.806l-.11.52h3.774l.11-.52c-1.06-.095-1.129-.211-1.006-.806z"/>
</svg>

After

Width:  |  Height:  |  Size: 352 B

View File

@@ -0,0 +1,5 @@
/* Built by the team of cal.com
https://github.com/calcom/cal.com/tree/main/packages/ui/components/editor */
export { Editor } from "./Editor";
export { AddVariablesDropdown } from "./plugins/AddVariablesDropdown";

View File

@@ -0,0 +1,60 @@
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "../../DropdownMenu";
import { ChevronDownIcon } from "@heroicons/react/20/solid";
interface IAddVariablesDropdown {
addVariable: (variable: string) => void;
isTextEditor?: boolean;
variables: string[];
}
export const AddVariablesDropdown = (props: IAddVariablesDropdown) => {
return (
<DropdownMenu>
<DropdownMenuTrigger className="focus:bg-muted pt-[6px]">
<div className="items-center ">
{props.isTextEditor ? (
<>
<div className="hidden sm:flex">
add_variable
<ChevronDownIcon className="ml-1 mt-[2px] h-4 w-4" />
</div>
<div className="block sm:hidden">+</div>
</>
) : (
<div className="flex">
add_variable
<ChevronDownIcon className="ml-1 mt-[2px] h-4 w-4" />
</div>
)}
</div>
</DropdownMenuTrigger>
<DropdownMenuContent>
<div className="pb-1 pt-4">
<div className="text-subtle mb-2 px-4 text-left text-xs">
{"add_dynamic_variables".toLocaleUpperCase()}
</div>
<div className="h-64 overflow-scroll md:h-80">
{props.variables.map((variable) => (
<DropdownMenuItem key={variable} className="hover:ring-0">
<button
key={variable}
type="button"
className="w-full px-4 py-2"
onClick={() => props.addVariable(`${variable}_variable`)}>
<div className="sm:grid sm:grid-cols-2">
<div className="mr-3 text-left md:col-span-1">
{`{${`${variable}_variable`.toUpperCase().replace(/ /g, "_")}}`}
</div>
<div className="text-default hidden text-left sm:col-span-1 sm:flex">
{`${variable}_info`}
</div>
</div>
</button>
</DropdownMenuItem>
))}
</div>
</div>
</DropdownMenuContent>
</DropdownMenu>
);
};

View File

@@ -0,0 +1,36 @@
import { AutoLinkPlugin } from "@lexical/react/LexicalAutoLinkPlugin";
const URL_MATCHER =
/((https?:\/\/(www\.)?)|(www\.))[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/;
const EMAIL_MATCHER =
/(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))/;
const MATCHERS = [
(text: any) => {
const match = URL_MATCHER.exec(text);
return (
match && {
index: match.index,
length: match[0].length,
text: match[0],
url: match[0],
}
);
},
(text: any) => {
const match = EMAIL_MATCHER.exec(text);
return (
match && {
index: match.index,
length: match[0].length,
text: match[0],
url: `mailto:${match[0]}`,
}
);
},
];
export default function PlaygroundAutoLinkPlugin() {
return <AutoLinkPlugin matchers={MATCHERS} />;
}

View File

@@ -0,0 +1,519 @@
"use client";
import { cn } from "@formbricks/lib/cn";
import { $generateHtmlFromNodes, $generateNodesFromDOM } from "@lexical/html";
import { $isLinkNode, TOGGLE_LINK_COMMAND } from "@lexical/link";
import {
$isListNode,
INSERT_ORDERED_LIST_COMMAND,
INSERT_UNORDERED_LIST_COMMAND,
ListNode,
REMOVE_LIST_COMMAND,
} from "@lexical/list";
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
import { $createHeadingNode, $isHeadingNode } from "@lexical/rich-text";
import { $isAtNodeEnd, $wrapNodes } from "@lexical/selection";
import { $getNearestNodeOfType, mergeRegister } from "@lexical/utils";
import type { EditorState, GridSelection, LexicalEditor, NodeSelection, RangeSelection } from "lexical";
import {
$createParagraphNode,
$getRoot,
$getSelection,
$insertNodes,
$isRangeSelection,
FORMAT_TEXT_COMMAND,
SELECTION_CHANGE_COMMAND,
} from "lexical";
import { useCallback, useEffect, useRef, useState } from "react";
import { createPortal } from "react-dom";
import { Bold, ChevronDownIcon, Italic, Link } from "lucide-react";
import { Button } from "../../Button";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "../../DropdownMenu";
import type { TextEditorProps } from "../Editor";
import { AddVariablesDropdown } from "./AddVariablesDropdown";
const LowPriority = 1;
const supportedBlockTypes = new Set(["paragraph", "h1", "h2", "ul", "ol"]);
interface BlockType {
[key: string]: string;
}
const blockTypeToBlockName: BlockType = {
paragraph: "Normal",
ol: "Numbered List",
ul: "Bulleted List",
h1: "Large Heading",
h2: "Small Heading",
};
function positionEditorElement(editor: HTMLInputElement, rect: DOMRect | null) {
if (rect === null) {
editor.style.opacity = "0";
editor.style.top = "-1000px";
editor.style.left = "-1000px";
} else {
editor.style.opacity = "1";
editor.style.top = `${rect.top + rect.height + window.pageYOffset + 10}px`;
editor.style.left = `${rect.left + window.pageXOffset - editor.offsetWidth / 2 + rect.width / 2}px`;
}
}
function FloatingLinkEditor({ editor }: { editor: LexicalEditor }) {
const editorRef = useRef<HTMLInputElement>(null);
const mouseDownRef = useRef(false);
const inputRef = useRef<HTMLInputElement | null>(null);
const [linkUrl, setLinkUrl] = useState("");
const [isEditMode, setEditMode] = useState(false);
const [lastSelection, setLastSelection] = useState<RangeSelection | NodeSelection | GridSelection | null>(
null
);
const updateLinkEditor = useCallback(() => {
const selection = $getSelection();
if ($isRangeSelection(selection)) {
const node = getSelectedNode(selection);
const parent = node.getParent();
if ($isLinkNode(parent)) {
setLinkUrl(parent.getURL());
} else if ($isLinkNode(node)) {
setLinkUrl(node.getURL());
} else {
setLinkUrl("");
}
}
const editorElem = editorRef.current;
const nativeSelection = window.getSelection();
const activeElement = document.activeElement;
if (editorElem === null) {
return;
}
const rootElement = editor.getRootElement();
if (
selection !== null &&
!nativeSelection?.isCollapsed &&
rootElement !== null &&
rootElement.contains(nativeSelection?.anchorNode || null)
) {
const domRange = nativeSelection?.getRangeAt(0);
let rect: DOMRect | undefined;
if (nativeSelection?.anchorNode === rootElement) {
let inner: Element = rootElement;
while (inner.firstElementChild != null) {
inner = inner.firstElementChild;
}
rect = inner.getBoundingClientRect();
} else {
rect = domRange?.getBoundingClientRect();
}
if (!mouseDownRef.current) {
positionEditorElement(editorElem, rect || null);
}
setLastSelection(selection);
} else if (!activeElement || activeElement.className !== "link-input") {
positionEditorElement(editorElem, null);
setLastSelection(null);
setEditMode(false);
setLinkUrl("");
}
return true;
}, [editor]);
useEffect(() => {
return mergeRegister(
editor.registerUpdateListener(({ editorState }: { editorState: EditorState }) => {
editorState.read(() => {
updateLinkEditor();
});
}),
editor.registerCommand(
SELECTION_CHANGE_COMMAND,
() => {
updateLinkEditor();
return true;
},
LowPriority
)
);
}, [editor, updateLinkEditor]);
useEffect(() => {
editor.getEditorState().read(() => {
updateLinkEditor();
});
}, [editor, updateLinkEditor]);
useEffect(() => {
if (isEditMode && inputRef.current) {
inputRef.current.focus();
}
}, [isEditMode]);
return (
<div ref={editorRef} className="link-editor">
{isEditMode ? (
<input
ref={inputRef}
className="link-input"
value={linkUrl}
onChange={(event) => {
setLinkUrl(event.target.value);
}}
onKeyDown={(event) => {
if (event.key === "Enter") {
event.preventDefault();
if (lastSelection !== null) {
if (linkUrl !== "") {
editor.dispatchCommand(TOGGLE_LINK_COMMAND, linkUrl);
}
setEditMode(false);
}
} else if (event.key === "Escape") {
event.preventDefault();
setEditMode(false);
}
}}
/>
) : (
<>
<div className="link-input">
<a href={linkUrl} target="_blank" rel="noopener noreferrer">
{linkUrl}
</a>
<div
className="link-edit"
role="button"
tabIndex={0}
onMouseDown={(event) => event.preventDefault()}
onClick={() => {
setEditMode(true);
}}
/>
</div>
</>
)}
</div>
);
}
function getSelectedNode(selection: RangeSelection) {
const anchor = selection.anchor;
const focus = selection.focus;
const anchorNode = selection.anchor.getNode();
const focusNode = selection.focus.getNode();
if (anchorNode === focusNode) {
return anchorNode;
}
const isBackward = selection.isBackward();
if (isBackward) {
return $isAtNodeEnd(focus) ? anchorNode : focusNode;
} else {
return $isAtNodeEnd(anchor) ? focusNode : anchorNode;
}
}
export default function ToolbarPlugin(props: TextEditorProps) {
const [editor] = useLexicalComposerContext();
const toolbarRef = useRef(null);
const [blockType, setBlockType] = useState("paragraph");
const [isLink, setIsLink] = useState(false);
const [isBold, setIsBold] = useState(false);
const [isItalic, setIsItalic] = useState(false);
// save ref to setText to use it in event listeners safely
const setText = useRef<any>(props.setText);
useEffect(() => {
setText.current = props.setText;
}, [props]);
const formatParagraph = () => {
if (blockType !== "paragraph") {
editor.update(() => {
const selection = $getSelection();
if ($isRangeSelection(selection)) {
$wrapNodes(selection, () => $createParagraphNode());
}
});
}
};
const formatLargeHeading = () => {
if (blockType !== "h1") {
editor.update(() => {
const selection = $getSelection();
if ($isRangeSelection(selection)) {
$wrapNodes(selection, () => $createHeadingNode("h1"));
}
});
}
};
const formatSmallHeading = () => {
if (blockType !== "h2") {
editor.update(() => {
const selection = $getSelection();
if ($isRangeSelection(selection)) {
$wrapNodes(selection, () => $createHeadingNode("h2"));
}
});
}
};
const formatBulletList = () => {
if (blockType !== "ul") {
editor.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND, undefined);
} else {
editor.dispatchCommand(REMOVE_LIST_COMMAND, undefined);
}
};
const formatNumberedList = () => {
if (blockType !== "ol") {
editor.dispatchCommand(INSERT_ORDERED_LIST_COMMAND, undefined);
} else {
editor.dispatchCommand(REMOVE_LIST_COMMAND, undefined);
}
};
const format = (newBlockType: string) => {
switch (newBlockType) {
case "paragraph":
formatParagraph();
break;
case "ul":
formatBulletList();
break;
case "ol":
formatNumberedList();
break;
case "h1":
formatLargeHeading();
break;
case "h2":
formatSmallHeading();
break;
}
};
const updateToolbar = useCallback(() => {
const selection = $getSelection();
if ($isRangeSelection(selection)) {
const anchorNode = selection.anchor.getNode();
const element = anchorNode.getKey() === "root" ? anchorNode : anchorNode.getTopLevelElementOrThrow();
const elementKey = element.getKey();
const elementDOM = editor.getElementByKey(elementKey);
if (elementDOM !== null) {
if ($isListNode(element)) {
const parentList = $getNearestNodeOfType(anchorNode, ListNode);
const type = parentList ? parentList.getTag() : element.getTag();
setBlockType(type);
} else {
const type = $isHeadingNode(element) ? element.getTag() : element.getType();
setBlockType(type);
}
}
setIsBold(selection.hasFormat("bold"));
setIsItalic(selection.hasFormat("italic"));
const node = getSelectedNode(selection);
const parent = node.getParent();
if ($isLinkNode(parent) || $isLinkNode(node)) {
setIsLink(true);
} else {
setIsLink(false);
}
}
}, [editor]);
const addVariable = (variable: string) => {
editor.update(() => {
const selection = $getSelection();
if ($isRangeSelection(selection)) {
editor.update(() => {
const formatedVariable = `{${variable.toUpperCase().replace(/ /g, "_")}}`;
selection?.insertRawText(formatedVariable);
});
}
});
};
useEffect(() => {
if (!props.firstRender) {
editor.update(() => {
const root = $getRoot();
if (root) {
editor.update(() => {
const parser = new DOMParser();
// Create a new TextNode
const dom = parser.parseFromString(props.getText(), "text/html");
const nodes = $generateNodesFromDOM(editor, dom);
const paragraph = $createParagraphNode();
root.clear().append(paragraph);
paragraph.select();
$insertNodes(nodes);
});
}
});
}
}, [props.updateTemplate]);
useEffect(() => {
if (props.setFirstRender) {
props.setFirstRender(false);
editor.update(() => {
const parser = new DOMParser();
const dom = parser.parseFromString(props.getText(), "text/html");
const nodes = $generateNodesFromDOM(editor, dom);
$getRoot().select();
$insertNodes(nodes);
editor.registerUpdateListener(({ editorState, prevEditorState }) => {
editorState.read(() => {
const textInHtml = $generateHtmlFromNodes(editor).replace(/&lt;/g, "<").replace(/&gt;/g, ">");
setText.current(textInHtml);
});
if (!prevEditorState._selection) editor.blur();
});
});
}
}, []);
useEffect(() => {
return mergeRegister(
editor.registerUpdateListener(({ editorState }) => {
editorState.read(() => {
updateToolbar();
});
}),
editor.registerCommand(
SELECTION_CHANGE_COMMAND,
(_payload) => {
updateToolbar();
return false;
},
LowPriority
)
);
}, [editor, updateToolbar]);
const insertLink = useCallback(() => {
if (!isLink) {
editor.dispatchCommand(TOGGLE_LINK_COMMAND, "https://");
} else {
editor.dispatchCommand(TOGGLE_LINK_COMMAND, null);
}
}, [editor, isLink]);
if (!props.editable) return <></>;
return (
<div className="toolbar flex" ref={toolbarRef}>
<>
{!props.excludedToolbarItems?.includes("blockType") && supportedBlockTypes.has(blockType) && (
<>
<DropdownMenu>
<DropdownMenuTrigger className="text-subtle">
<>
<span className={"icon" + blockType} />
<span className="text text-default hidden sm:flex">
{blockTypeToBlockName[blockType as keyof BlockType]}
</span>
<ChevronDownIcon className="text-default ml-2 h-4 w-4" />
</>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
{Object.keys(blockTypeToBlockName).map((key) => {
return (
<DropdownMenuItem key={key} className="outline-none hover:ring-0 focus:ring-0">
<Button
color="minimal"
type="button"
onClick={() => format(key)}
className={cn(
"w-full rounded-none focus:ring-0",
blockType === key ? "bg-subtle w-full" : ""
)}>
<>
<span className={"icon block-type " + key} />
<span>{blockTypeToBlockName[key]}</span>
</>
</Button>
</DropdownMenuItem>
);
})}
</DropdownMenuContent>
</DropdownMenu>
</>
)}
<>
{!props.excludedToolbarItems?.includes("bold") && (
<Button
color="minimal"
variant="minimal"
type="button"
StartIcon={Bold}
onClick={() => {
editor.dispatchCommand(FORMAT_TEXT_COMMAND, "bold");
if (isItalic) {
editor.dispatchCommand(FORMAT_TEXT_COMMAND, "italic");
}
}}
className={isBold ? "bg-subtle" : ""}
/>
)}
{!props.excludedToolbarItems?.includes("italic") && (
<Button
color="minimal"
variant="minimal"
type="button"
StartIcon={Italic}
onClick={() => {
editor.dispatchCommand(FORMAT_TEXT_COMMAND, "italic");
if (isItalic) {
editor.dispatchCommand(FORMAT_TEXT_COMMAND, "bold");
}
}}
className={isItalic ? "bg-subtle" : ""}
/>
)}
{!props.excludedToolbarItems?.includes("link") && (
<>
<Button
color="minimal"
variant="minimal"
type="button"
StartIcon={Link}
onClick={insertLink}
className={isLink ? "bg-subtle" : ""}
/>
{isLink && createPortal(<FloatingLinkEditor editor={editor} />, document.body)}{" "}
</>
)}
</>
{props.variables && (
<div className="ml-auto">
<AddVariablesDropdown
addVariable={addVariable}
isTextEditor={true}
variables={props.variables || []}
/>
</div>
)}
</>
</div>
);
}

View File

@@ -0,0 +1,337 @@
.editor a {
text-decoration: underline;
font-size: 14px;
}
.editor li {
padding-left: 1.28571429em;
text-indent: -1.28571429em;
}
.editor ul {
list-style: disc inside;
}
.editor ol {
list-style: decimal inside;
}
.editor-container {
border-radius: 6px;
position: relative;
line-height: 20px;
font-weight: 400;
text-align: left;
border-color: #cbd5e1;
border-width: 1px;
padding: 1px;
}
.editor-inner {
background: var(--cal-bg);
position: relative;
border-bottom-left-radius: 6px;
border-bottom-right-radius: 6px;
overflow: scroll;
resize: vertical;
height: auto;
min-height: 100px;
}
.editor-input {
height: auto;
font-size: 14px;
position: relative;
tab-size: 1;
outline: 0;
padding: 10px 10px;
outline: none;
}
pre::-webkit-scrollbar {
background: transparent;
width: 10px;
}
pre::-webkit-scrollbar-thumb {
background: #999;
}
.toolbar {
display: flex;
margin-bottom: 1px;
background: var(--cal-bg);
padding: 2px;
border-top-left-radius: 6px;
border-top-right-radius: 6px;
vertical-align: middle;
}
.toolbar button.toolbar-item {
display: flex;
border: 0;
padding: 8px;
cursor: pointer;
justify-self: center;
}
.toolbar button.toolbar-item.spaced {
margin-right: 2px;
}
.icon.paragraph {
background-image: url(images/icons/text-paragraph.svg);
}
.toolbar button.toolbar-item.active {
background-color: var(--cal-bg-inverted);
color: var(--cal-text-inverted);
}
.toolbar button.toolbar-item.active i {
opacity: 1;
}
.toolbar button.toolbar-item:not(.active):not([disabled]) {
background-color: var(--cal-bg);
color: var(--cal-text);
}
.toolbar button.toolbar-item:not(.active):hover {
color: var(--cal-text-default);
background-color: var(--cal-bg-inverted);
}
.toolbar select.toolbar-item {
border: 0;
display: flex;
background: none;
border-radius: 10px;
padding: 8px;
vertical-align: middle;
appearance: none;
-webkit-appearance: none;
-moz-appearance: none;
width: 70px;
font-size: 14px;
color: #777;
text-overflow: ellipsis;
}
.toolbar .toolbar-item .text {
line-height: 20px;
width: 200px;
vertical-align: middle;
font-size: 14px;
text-overflow: ellipsis;
width: 70px;
overflow: hidden;
height: 20px;
text-align: left;
}
.toolbar .toolbar-item .icon {
display: flex;
color: var(--cal-text);
}
#block-controls button:hover {
background-color: #efefef;
}
#block-controls button:focus-visible {
border-color: blue;
}
.dropdown {
z-index: 5;
display: block;
position: absolute;
box-shadow: 0 12px 28px 0 rgba(0, 0, 0, 0.2), 0 2px 4px 0 rgba(0, 0, 0, 0.1),
inset 0 0 0 1px rgba(255, 255, 255, 0.5);
border-radius: 8px;
min-width: 100px;
min-height: 40px;
background-color: #fff;
}
.dropdown .item {
margin: 0 8px 0 8px;
padding: 8px;
color: #050505;
cursor: pointer;
line-height: 16px;
font-size: 15px;
display: flex;
align-content: center;
flex-direction: row;
flex-shrink: 0;
justify-content: space-between;
background-color: #fff;
border-radius: 8px;
border: 0;
min-width: 268px;
}
.dropdown .item .active {
display: flex;
width: 20px;
height: 20px;
background-size: contain;
}
.dropdown .item:first-child {
margin-top: 8px;
}
.dropdown .item:last-child {
margin-bottom: 8px;
}
.dropdown .item:hover {
background-color: #eee;
}
.dropdown .item .text {
display: flex;
line-height: 20px;
flex-grow: 1;
width: 200px;
}
.dropdown .item .icon {
display: flex;
width: 20px;
height: 20px;
user-select: none;
margin-right: 12px;
line-height: 16px;
background-size: contain;
}
.link-editor {
position: absolute;
z-index: 100;
top: -10000px;
left: -10000px;
margin-left: 80px;
margin-top: -6px;
max-width: 300px;
width: 100%;
opacity: 0;
background-color: #fff;
box-shadow: 0px 5px 10px rgba(0, 0, 0, 0.3);
border-radius: 8px;
transition: opacity 0.5s;
}
.link-editor .link-input {
display: block;
width: calc(100% - 24px);
box-sizing: border-box;
margin: 8px 12px;
padding: 8px 12px;
border-radius: 15px;
background-color: #eee;
font-size: 15px;
color: rgb(5, 5, 5);
border: 0;
outline: 0;
position: relative;
font-family: inherit;
}
.link-editor div.link-edit {
background-image: url(images/icons/pencil-fill.svg);
background-size: 16px;
background-position: center;
background-repeat: no-repeat;
width: 35px;
vertical-align: -0.25em;
position: absolute;
right: 0;
top: 0;
bottom: 0;
cursor: pointer;
}
.link-editor .link-input a {
color: rgb(33, 111, 219);
text-decoration: none;
display: block;
white-space: nowrap;
overflow: hidden;
margin-right: 30px;
text-overflow: ellipsis;
}
.link-editor .link-input a:hover {
text-decoration: underline;
}
.link-editor .button {
width: 20px;
height: 20px;
display: inline-block;
padding: 6px;
border-radius: 8px;
cursor: pointer;
margin: 0 2px;
}
.link-editor .button.hovered {
width: 20px;
height: 20px;
display: inline-block;
background-color: #eee;
}
.link-editor .button i,
.actions i {
background-size: contain;
display: inline-block;
height: 20px;
width: 20px;
vertical-align: -0.25em;
}
i,
.icon {
fill: currentColor;
}
.icon.paragraph {
background-image: url(images/icons/text-paragraph.svg);
}
.icon.large-heading,
.icon.h1 {
background-image: url(images/icons/type-h1.svg);
}
.icon.small-heading,
.icon.h2 {
background-image: url(images/icons/type-h2.svg);
}
.icon.bullet-list,
.icon.ul {
background-image: url(images/icons/list-ul.svg);
}
.icon.numbered-list,
.icon.ol {
background-image: url(images/icons/list-ol.svg);
}
i.bold {
background-image: url(images/icons/type-bold.svg);
}
i.italic {
background-image: url(images/icons/type-italic.svg);
}
i.link {
background-image: url(images/icons/link.svg);
}

View File

@@ -0,0 +1,55 @@
.fb-editor-text-bold {
font-weight: bold !important;
}
.fb-editor-text-italic {
font-style: italic !important;
}
.fb-editor-link {
color: #334155 !important;
text-decoration: underline !important;
}
.editor-tokenFunction {
color: #dd4a68 !important;
}
.fb-editor-paragraph {
margin: 0 !important;
position: relative !important;
color: #334155 !important;
}
.fb-editor-paragraph:last-child {
margin-bottom: 0 !important;
}
.fb-editor-heading-h1 {
font-size: 25px !important;
font-weight: 400 !important;
margin-bottom: 20px !important;
font-weight: bold !important;
}
.fb-editor-heading-h2 {
font-size: 20px !important;
font-weight: bold !important;
margin-bottom: 20px !important;
}
.fb-editor-list-ul {
margin-bottom: 12px !important;
}
.fb-editor-list-ol {
margin-bottom: 12px !important;
}
.fb-editor-listitem {
margin: 0px 32px !important;
}
.fb-editor-nested-listitem {
list-style-type: none !important;
}

View File

@@ -23,6 +23,7 @@ export {
DropdownMenuSubTrigger,
DropdownMenuRadioGroup,
} from "./components/DropdownMenu";
export { Editor, AddVariablesDropdown } from "./components/editor";
export { ErrorComponent } from "./components/ErrorComponent";
export { Input } from "./components/Input";
export { Label } from "./components/Label";

View File

@@ -29,6 +29,13 @@
"dependencies": {
"@formbricks/lib": "workspace:*",
"@heroicons/react": "^2.0.17",
"@lexical/code": "^0.10.0",
"@lexical/link": "^0.10.0",
"@lexical/list": "^0.10.0",
"@lexical/markdown": "^0.10.0",
"@lexical/react": "^0.10.0",
"@lexical/rich-text": "^0.10.0",
"@lexical/table": "^0.10.0",
"@radix-ui/react-checkbox": "^1.0.3",
"@radix-ui/react-dropdown-menu": "^2.0.4",
"@radix-ui/react-label": "^2.0.1",

577
pnpm-lock.yaml generated
View File

@@ -9,13 +9,13 @@ importers:
version: 2.26.1
prettier:
specifier: latest
version: 2.8.7
version: 2.8.8
tsx:
specifier: ^3.12.6
version: 3.12.6
turbo:
specifier: latest
version: 1.9.1
version: 1.8.8
apps/demo:
dependencies:
@@ -228,6 +228,9 @@ importers:
lucide-react:
specifier: ^0.161.0
version: 0.161.0(react@18.2.0)
markdown-it:
specifier: ^13.0.1
version: 13.0.1
next:
specifier: 13.2.4
version: 13.2.4(react-dom@18.2.0)(react@18.2.0)
@@ -301,6 +304,9 @@ importers:
'@types/bcryptjs':
specifier: ^2.4.2
version: 2.4.2
'@types/markdown-it':
specifier: ^12.2.3
version: 12.2.3
autoprefixer:
specifier: ^10.4.14
version: 10.4.14(postcss@8.4.22)
@@ -346,7 +352,7 @@ importers:
version: 4.4.1
tsup:
specifier: ^6.7.0
version: 6.7.0(typescript@5.0.3)
version: 6.7.0(postcss@8.4.21)(typescript@5.0.4)
tsx:
specifier: ^3.12.6
version: 3.12.6
@@ -404,7 +410,7 @@ importers:
version: 8.8.0(eslint@8.37.0)
eslint-config-turbo:
specifier: latest
version: 1.9.1(eslint@8.37.0)
version: 1.8.8(eslint@8.37.0)
eslint-plugin-react:
specifier: 7.32.2
version: 7.32.2(eslint@8.37.0)
@@ -545,6 +551,27 @@ importers:
'@heroicons/react':
specifier: ^2.0.17
version: 2.0.17(react@18.2.0)
'@lexical/code':
specifier: ^0.10.0
version: 0.10.0(lexical@0.10.0)
'@lexical/link':
specifier: ^0.10.0
version: 0.10.0(lexical@0.10.0)
'@lexical/list':
specifier: ^0.10.0
version: 0.10.0(lexical@0.10.0)
'@lexical/markdown':
specifier: ^0.10.0
version: 0.10.0(@lexical/clipboard@0.10.0)(@lexical/selection@0.10.0)(lexical@0.10.0)
'@lexical/react':
specifier: ^0.10.0
version: 0.10.0(lexical@0.10.0)(react-dom@18.2.0)(react@18.2.0)(yjs@13.6.0)
'@lexical/rich-text':
specifier: ^0.10.0
version: 0.10.0(@lexical/clipboard@0.10.0)(@lexical/selection@0.10.0)(@lexical/utils@0.10.0)(lexical@0.10.0)
'@lexical/table':
specifier: ^0.10.0
version: 0.10.0(lexical@0.10.0)
'@radix-ui/react-checkbox':
specifier: ^1.0.3
version: 1.0.3(react-dom@18.2.0)(react@18.2.0)
@@ -2144,7 +2171,7 @@ packages:
fs-extra: 7.0.1
lodash.startcase: 4.4.0
outdent: 0.5.0
prettier: 2.8.7
prettier: 2.8.8
resolve-from: 5.0.0
semver: 5.7.1
dev: true
@@ -2312,7 +2339,7 @@ packages:
'@changesets/types': 5.2.1
fs-extra: 7.0.1
human-id: 1.0.2
prettier: 2.8.7
prettier: 2.8.8
dev: true
/@corex/deepmerge@4.0.37:
@@ -3044,6 +3071,230 @@ packages:
resolution: {integrity: sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==}
dev: true
/@lexical/clipboard@0.10.0(lexical@0.10.0):
resolution: {integrity: sha512-k1n93NQdTrGHFMQQ1NxD/G13uoTEBHKOqjgSAV3I3pQjG57zO51LsMjBxgR9BChVI1DotnQ/JQCbx2HCQkCeng==}
peerDependencies:
lexical: 0.10.0
dependencies:
'@lexical/html': 0.10.0(lexical@0.10.0)
'@lexical/list': 0.10.0(lexical@0.10.0)
'@lexical/selection': 0.10.0(lexical@0.10.0)
'@lexical/utils': 0.10.0(lexical@0.10.0)
lexical: 0.10.0
dev: false
/@lexical/code@0.10.0(lexical@0.10.0):
resolution: {integrity: sha512-ZEeoAtj/nXWmmN0Ol4lXSEAtxQozdDd/5I9P23Z4Leobg1YioZBDKwodM/TEITxrFme/cLgk3XCK1N2h2Noakw==}
peerDependencies:
lexical: 0.10.0
dependencies:
'@lexical/utils': 0.10.0(lexical@0.10.0)
lexical: 0.10.0
prismjs: 1.29.0
dev: false
/@lexical/dragon@0.10.0(lexical@0.10.0):
resolution: {integrity: sha512-LBRkUW4NadFFQN9BIL3nz51AtPl1nSzRas61ob+hueSbovgr/+k9QGjLD1L7FIKnna2qzRlJjoI70Ll/V3fiCQ==}
peerDependencies:
lexical: 0.10.0
dependencies:
lexical: 0.10.0
dev: false
/@lexical/hashtag@0.10.0(lexical@0.10.0):
resolution: {integrity: sha512-jy+LssP4ABfwS+bT2KD+W6gWl+dsyFd8Ql27bsfkNoeyIBMeSLRBNBymkkUyZGKfXrbkzMUJoH7Dws/loHY5Ng==}
peerDependencies:
lexical: 0.10.0
dependencies:
'@lexical/utils': 0.10.0(lexical@0.10.0)
lexical: 0.10.0
dev: false
/@lexical/history@0.10.0(lexical@0.10.0):
resolution: {integrity: sha512-yu7FruEtOulZliE6nVgvC+GLVHhN15IcLVvKdYIi+QYzkOorKc6miVuGpLvJRHE7VtK0NMWb2ykyYz9gl5jW3Q==}
peerDependencies:
lexical: 0.10.0
dependencies:
'@lexical/utils': 0.10.0(lexical@0.10.0)
lexical: 0.10.0
dev: false
/@lexical/html@0.10.0(lexical@0.10.0):
resolution: {integrity: sha512-zxPbjojLfZXt4Sx6CMi0NzPUrTJG8McXaCsqPKK2GQQnkKfmXLh5QsK7YhofngBDDQOtqVYzTNhrxMWVP1Sm/A==}
peerDependencies:
lexical: 0.10.0
dependencies:
'@lexical/selection': 0.10.0(lexical@0.10.0)
lexical: 0.10.0
dev: false
/@lexical/link@0.10.0(lexical@0.10.0):
resolution: {integrity: sha512-LehZx9ruUR0UNZmNUMofrgwGkQo75X/yEAHQ/qFXt8Jz0D4g9fS6vsiSVn7BD4XrTdaf2kUaIe0VHScIZPjB0A==}
peerDependencies:
lexical: 0.10.0
dependencies:
'@lexical/utils': 0.10.0(lexical@0.10.0)
lexical: 0.10.0
dev: false
/@lexical/list@0.10.0(lexical@0.10.0):
resolution: {integrity: sha512-xoba6e4RUPIOhD0kJ0X1iI8SgOGDNWtyObd/UBbFfAQDx0VETj31Q7PepSPy9OEeuvnF1Nn9pvUeHyG7cNFuHw==}
peerDependencies:
lexical: 0.10.0
dependencies:
'@lexical/utils': 0.10.0(lexical@0.10.0)
lexical: 0.10.0
dev: false
/@lexical/mark@0.10.0(lexical@0.10.0):
resolution: {integrity: sha512-Xo8G8tADlxV9Es5hbca6MfZjOU5mkjZNdbRJ5dy4STNhtl3FcqbVGSAewFibzsKG3x/s8npFDY6nN/k/lkGaRg==}
peerDependencies:
lexical: 0.10.0
dependencies:
'@lexical/utils': 0.10.0(lexical@0.10.0)
lexical: 0.10.0
dev: false
/@lexical/markdown@0.10.0(@lexical/clipboard@0.10.0)(@lexical/selection@0.10.0)(lexical@0.10.0):
resolution: {integrity: sha512-P6XT8736DtZoTV6KfO0+FM9pkCbGGk0wCim1HIDc3v2S4r9+Pie/FgOOWSz4jKygjhs/6EYgYJmZKq7G4Fo5WA==}
peerDependencies:
lexical: 0.10.0
dependencies:
'@lexical/code': 0.10.0(lexical@0.10.0)
'@lexical/link': 0.10.0(lexical@0.10.0)
'@lexical/list': 0.10.0(lexical@0.10.0)
'@lexical/rich-text': 0.10.0(@lexical/clipboard@0.10.0)(@lexical/selection@0.10.0)(@lexical/utils@0.10.0)(lexical@0.10.0)
'@lexical/text': 0.10.0(lexical@0.10.0)
'@lexical/utils': 0.10.0(lexical@0.10.0)
lexical: 0.10.0
transitivePeerDependencies:
- '@lexical/clipboard'
- '@lexical/selection'
dev: false
/@lexical/offset@0.10.0(lexical@0.10.0):
resolution: {integrity: sha512-vgVmoR7XMjFuQiO6GecLGM/gcNgJlJXitO5uCHARi2yjJDmmCQ07THu5xpfXKXnkJTZXn1VfWWmAGu72Q9fKNw==}
peerDependencies:
lexical: 0.10.0
dependencies:
lexical: 0.10.0
dev: false
/@lexical/overflow@0.10.0(lexical@0.10.0):
resolution: {integrity: sha512-8YfYhjwDGliGzYFyLVH4nCqrgR0p3+vAwEe2WI65HJnPW9TnxRkrak7QkFPTsefiTnb1gnRc+FajP9LfjdQ8YA==}
peerDependencies:
lexical: 0.10.0
dependencies:
lexical: 0.10.0
dev: false
/@lexical/plain-text@0.10.0(@lexical/clipboard@0.10.0)(@lexical/selection@0.10.0)(@lexical/utils@0.10.0)(lexical@0.10.0):
resolution: {integrity: sha512-KcjQR+nHvXQDDRZ9bhcLOWfrY47OXcQc1YMx3otHbMPlEC+f/J75DTUyp+V5fry9X8gHDq0iHm+w8hqgh5ii1Q==}
peerDependencies:
'@lexical/clipboard': 0.10.0
'@lexical/selection': 0.10.0
'@lexical/utils': 0.10.0
lexical: 0.10.0
dependencies:
'@lexical/clipboard': 0.10.0(lexical@0.10.0)
'@lexical/selection': 0.10.0(lexical@0.10.0)
'@lexical/utils': 0.10.0(lexical@0.10.0)
lexical: 0.10.0
dev: false
/@lexical/react@0.10.0(lexical@0.10.0)(react-dom@18.2.0)(react@18.2.0)(yjs@13.6.0):
resolution: {integrity: sha512-7Ql/Y3FZSsPSCObT58CYRUd4tQzKL2U8B1xO0KXZBUbj+sO6gpNbc7/Y7MiZwCQzVNwP84j7mwkXQ1EnUBS52A==}
peerDependencies:
lexical: 0.10.0
react: '>=17.x'
react-dom: '>=17.x'
dependencies:
'@lexical/clipboard': 0.10.0(lexical@0.10.0)
'@lexical/code': 0.10.0(lexical@0.10.0)
'@lexical/dragon': 0.10.0(lexical@0.10.0)
'@lexical/hashtag': 0.10.0(lexical@0.10.0)
'@lexical/history': 0.10.0(lexical@0.10.0)
'@lexical/link': 0.10.0(lexical@0.10.0)
'@lexical/list': 0.10.0(lexical@0.10.0)
'@lexical/mark': 0.10.0(lexical@0.10.0)
'@lexical/markdown': 0.10.0(@lexical/clipboard@0.10.0)(@lexical/selection@0.10.0)(lexical@0.10.0)
'@lexical/overflow': 0.10.0(lexical@0.10.0)
'@lexical/plain-text': 0.10.0(@lexical/clipboard@0.10.0)(@lexical/selection@0.10.0)(@lexical/utils@0.10.0)(lexical@0.10.0)
'@lexical/rich-text': 0.10.0(@lexical/clipboard@0.10.0)(@lexical/selection@0.10.0)(@lexical/utils@0.10.0)(lexical@0.10.0)
'@lexical/selection': 0.10.0(lexical@0.10.0)
'@lexical/table': 0.10.0(lexical@0.10.0)
'@lexical/text': 0.10.0(lexical@0.10.0)
'@lexical/utils': 0.10.0(lexical@0.10.0)
'@lexical/yjs': 0.10.0(lexical@0.10.0)(yjs@13.6.0)
lexical: 0.10.0
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
react-error-boundary: 3.1.4(react@18.2.0)
transitivePeerDependencies:
- yjs
dev: false
/@lexical/rich-text@0.10.0(@lexical/clipboard@0.10.0)(@lexical/selection@0.10.0)(@lexical/utils@0.10.0)(lexical@0.10.0):
resolution: {integrity: sha512-mkg+8h5qh09daCc8+PhzHjczSVhlTOkrr8S4oma89mfQZ/CODJYd0tOOd9y/8JaDrBddTqjd4xTjXwEEJzVA4g==}
peerDependencies:
'@lexical/clipboard': 0.10.0
'@lexical/selection': 0.10.0
'@lexical/utils': 0.10.0
lexical: 0.10.0
dependencies:
'@lexical/clipboard': 0.10.0(lexical@0.10.0)
'@lexical/selection': 0.10.0(lexical@0.10.0)
'@lexical/utils': 0.10.0(lexical@0.10.0)
lexical: 0.10.0
dev: false
/@lexical/selection@0.10.0(lexical@0.10.0):
resolution: {integrity: sha512-dziT1fb+/x8S3PUHI6BuF4DPML2njCDdyY7l9uTgzN9jGXxo9bzy+ySgRRN8IgjWQb8jkntAqkYIWTj4/3Jr7Q==}
peerDependencies:
lexical: 0.10.0
dependencies:
lexical: 0.10.0
dev: false
/@lexical/table@0.10.0(lexical@0.10.0):
resolution: {integrity: sha512-m0MBma4PNn5VV3yAGFK0iCTCZ5z/VBrZWLGxo3yPnY7FHgtUsDY84xYXziZGeixU+ehiQfsSPWVEGEC+EHFGTg==}
peerDependencies:
lexical: 0.10.0
dependencies:
'@lexical/utils': 0.10.0(lexical@0.10.0)
lexical: 0.10.0
dev: false
/@lexical/text@0.10.0(lexical@0.10.0):
resolution: {integrity: sha512-kR/V4KgCIUedjEbdPtIrvsvalETDbAPhCB7HA9MBALYw9FxjXlpYBqP7yY8AxWoYCXMqDtKRu/vmo/OMi3Z8Bw==}
peerDependencies:
lexical: 0.10.0
dependencies:
lexical: 0.10.0
dev: false
/@lexical/utils@0.10.0(lexical@0.10.0):
resolution: {integrity: sha512-HrRZBLxZhe5V8+kNH6VZP9pfhQdBwK76KYBjWpCaQWkmeeuLx8clY3O+SMjy1Pu/Pdh80qqtad8bdJ0l2CxMSQ==}
peerDependencies:
lexical: 0.10.0
dependencies:
'@lexical/list': 0.10.0(lexical@0.10.0)
'@lexical/selection': 0.10.0(lexical@0.10.0)
'@lexical/table': 0.10.0(lexical@0.10.0)
lexical: 0.10.0
dev: false
/@lexical/yjs@0.10.0(lexical@0.10.0)(yjs@13.6.0):
resolution: {integrity: sha512-OV5yLl4XjfDKgRPSb1EyefU7c+MILKZrOzbPlv2xeTgIEd/sJiEszxvJlYsngLLN65wBDNKk90EcCWpH3KLz7A==}
peerDependencies:
lexical: 0.10.0
yjs: '>=13.5.22'
dependencies:
'@lexical/offset': 0.10.0(lexical@0.10.0)
lexical: 0.10.0
yjs: 13.6.0
dev: false
/@manypkg/find-root@1.1.0:
resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==}
dependencies:
@@ -4391,7 +4642,7 @@ packages:
tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1'
dependencies:
mini-svg-data-uri: 1.4.4
tailwindcss: 3.3.1(postcss@8.4.21)
tailwindcss: 3.3.1(postcss@8.4.22)
dev: true
/@tailwindcss/typography@0.5.9(tailwindcss@3.3.1):
@@ -4623,12 +4874,27 @@ packages:
dependencies:
'@types/node': 18.15.11
/@types/linkify-it@3.0.2:
resolution: {integrity: sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==}
dev: true
/@types/markdown-it@12.2.3:
resolution: {integrity: sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==}
dependencies:
'@types/linkify-it': 3.0.2
'@types/mdurl': 1.0.2
dev: true
/@types/mdast@3.0.10:
resolution: {integrity: sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==}
dependencies:
'@types/unist': 2.0.6
dev: false
/@types/mdurl@1.0.2:
resolution: {integrity: sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==}
dev: true
/@types/mdx@2.0.3:
resolution: {integrity: sha512-IgHxcT3RC8LzFLhKwP3gbMPeaK7BM9eBH46OdapPA7yvuIUJ8H6zHZV53J8hGZcTSnt95jANt+rTBNUUc22ACQ==}
dev: false
@@ -6551,7 +6817,7 @@ packages:
minipass-pipeline: 1.2.4
mkdirp: 1.0.4
p-map: 4.0.0
promise-inflight: 1.0.1
promise-inflight: 1.0.1(bluebird@3.7.2)
rimraf: 3.0.2
ssri: 8.0.1
tar: 6.1.12
@@ -8327,6 +8593,11 @@ packages:
resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==}
dev: true
/entities@3.0.1:
resolution: {integrity: sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==}
engines: {node: '>=0.12'}
dev: false
/entities@4.4.0:
resolution: {integrity: sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==}
engines: {node: '>=0.12'}
@@ -8754,7 +9025,7 @@ packages:
eslint: 8.37.0
eslint-import-resolver-node: 0.3.6
eslint-import-resolver-typescript: 3.5.2(eslint-plugin-import@2.26.0)(eslint@8.37.0)
eslint-plugin-import: 2.26.0(eslint@8.37.0)
eslint-plugin-import: 2.26.0(@typescript-eslint/parser@5.55.0)(eslint-import-resolver-typescript@3.5.2)(eslint@8.37.0)
eslint-plugin-jsx-a11y: 6.6.1(eslint@8.37.0)
eslint-plugin-react: 7.32.2(eslint@8.37.0)
eslint-plugin-react-hooks: 4.6.0(eslint@8.37.0)
@@ -8779,7 +9050,7 @@ packages:
eslint: 8.38.0
eslint-import-resolver-node: 0.3.6
eslint-import-resolver-typescript: 3.5.2(eslint-plugin-import@2.26.0)(eslint@8.38.0)
eslint-plugin-import: 2.26.0(eslint@8.38.0)
eslint-plugin-import: 2.26.0(@typescript-eslint/parser@5.55.0)(eslint-import-resolver-typescript@3.5.2)(eslint@8.37.0)
eslint-plugin-jsx-a11y: 6.6.1(eslint@8.38.0)
eslint-plugin-react: 7.32.2(eslint@8.38.0)
eslint-plugin-react-hooks: 4.6.0(eslint@8.38.0)
@@ -8820,13 +9091,13 @@ packages:
eslint: 8.37.0
dev: false
/eslint-config-turbo@1.9.1(eslint@8.37.0):
resolution: {integrity: sha512-tUqm5TxI5bpbDEgClbw+UygVPAwYB20FIpAiQsZI8imJNDz30E40TZkp6uWpAKmxykU8T0+t3jwkYokvXmXc0Q==}
/eslint-config-turbo@1.8.8(eslint@8.37.0):
resolution: {integrity: sha512-+yT22sHOT5iC1sbBXfLIdXfbZuiv9bAyOXsxTxFCWelTeFFnANqmuKB3x274CFvf7WRuZ/vYP/VMjzU9xnFnxA==}
peerDependencies:
eslint: '>6.6.0'
dependencies:
eslint: 8.37.0
eslint-plugin-turbo: 1.9.1(eslint@8.37.0)
eslint-plugin-turbo: 1.8.8(eslint@8.37.0)
dev: false
/eslint-import-resolver-node@0.3.6:
@@ -8848,7 +9119,7 @@ packages:
debug: 4.3.4
enhanced-resolve: 5.12.0
eslint: 8.37.0
eslint-plugin-import: 2.26.0(eslint@8.37.0)
eslint-plugin-import: 2.26.0(@typescript-eslint/parser@5.55.0)(eslint-import-resolver-typescript@3.5.2)(eslint@8.37.0)
get-tsconfig: 4.4.0
globby: 13.1.2
is-core-module: 2.11.0
@@ -8868,7 +9139,7 @@ packages:
debug: 4.3.4
enhanced-resolve: 5.12.0
eslint: 8.38.0
eslint-plugin-import: 2.26.0(eslint@8.38.0)
eslint-plugin-import: 2.26.0(@typescript-eslint/parser@5.55.0)(eslint-import-resolver-typescript@3.5.2)(eslint@8.37.0)
get-tsconfig: 4.4.0
globby: 13.1.2
is-core-module: 2.11.0
@@ -8878,7 +9149,7 @@ packages:
- supports-color
dev: false
/eslint-module-utils@2.7.4(eslint-import-resolver-node@0.3.6)(eslint@8.37.0):
/eslint-module-utils@2.7.4(@typescript-eslint/parser@5.55.0)(eslint-import-resolver-node@0.3.6)(eslint-import-resolver-typescript@3.5.2)(eslint@8.37.0):
resolution: {integrity: sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==}
engines: {node: '>=4'}
peerDependencies:
@@ -8899,37 +9170,11 @@ packages:
eslint-import-resolver-webpack:
optional: true
dependencies:
'@typescript-eslint/parser': 5.55.0(eslint@8.37.0)(typescript@5.0.3)
debug: 3.2.7
eslint: 8.37.0
eslint-import-resolver-node: 0.3.6
transitivePeerDependencies:
- supports-color
dev: false
/eslint-module-utils@2.7.4(eslint-import-resolver-node@0.3.6)(eslint@8.38.0):
resolution: {integrity: sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==}
engines: {node: '>=4'}
peerDependencies:
'@typescript-eslint/parser': '*'
eslint: '*'
eslint-import-resolver-node: '*'
eslint-import-resolver-typescript: '*'
eslint-import-resolver-webpack: '*'
peerDependenciesMeta:
'@typescript-eslint/parser':
optional: true
eslint:
optional: true
eslint-import-resolver-node:
optional: true
eslint-import-resolver-typescript:
optional: true
eslint-import-resolver-webpack:
optional: true
dependencies:
debug: 3.2.7
eslint: 8.38.0
eslint-import-resolver-node: 0.3.6
eslint-import-resolver-typescript: 3.5.2(eslint-plugin-import@2.26.0)(eslint@8.37.0)
transitivePeerDependencies:
- supports-color
dev: false
@@ -8950,7 +9195,7 @@ packages:
semver: 7.3.8
dev: true
/eslint-plugin-import@2.26.0(eslint@8.37.0):
/eslint-plugin-import@2.26.0(@typescript-eslint/parser@5.55.0)(eslint-import-resolver-typescript@3.5.2)(eslint@8.37.0):
resolution: {integrity: sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==}
engines: {node: '>=4'}
peerDependencies:
@@ -8960,43 +9205,14 @@ packages:
'@typescript-eslint/parser':
optional: true
dependencies:
'@typescript-eslint/parser': 5.55.0(eslint@8.37.0)(typescript@5.0.3)
array-includes: 3.1.6
array.prototype.flat: 1.3.1
debug: 2.6.9
doctrine: 2.1.0
eslint: 8.37.0
eslint-import-resolver-node: 0.3.6
eslint-module-utils: 2.7.4(eslint-import-resolver-node@0.3.6)(eslint@8.37.0)
has: 1.0.3
is-core-module: 2.11.0
is-glob: 4.0.3
minimatch: 3.1.2
object.values: 1.1.6
resolve: 1.22.1
tsconfig-paths: 3.14.1
transitivePeerDependencies:
- eslint-import-resolver-typescript
- eslint-import-resolver-webpack
- supports-color
dev: false
/eslint-plugin-import@2.26.0(eslint@8.38.0):
resolution: {integrity: sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==}
engines: {node: '>=4'}
peerDependencies:
'@typescript-eslint/parser': '*'
eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8
peerDependenciesMeta:
'@typescript-eslint/parser':
optional: true
dependencies:
array-includes: 3.1.6
array.prototype.flat: 1.3.1
debug: 2.6.9
doctrine: 2.1.0
eslint: 8.38.0
eslint-import-resolver-node: 0.3.6
eslint-module-utils: 2.7.4(eslint-import-resolver-node@0.3.6)(eslint@8.38.0)
eslint-module-utils: 2.7.4(@typescript-eslint/parser@5.55.0)(eslint-import-resolver-node@0.3.6)(eslint-import-resolver-typescript@3.5.2)(eslint@8.37.0)
has: 1.0.3
is-core-module: 2.11.0
is-glob: 4.0.3
@@ -9140,8 +9356,8 @@ packages:
string.prototype.matchall: 4.0.8
dev: false
/eslint-plugin-turbo@1.9.1(eslint@8.37.0):
resolution: {integrity: sha512-QPd0EG0xkoDkXJLwPQKULxHjkR27VmvJtILW4C9aIrqauLZ+Yc/V7R+A9yVwAi6nkMHxUlCSUsBxmiQP9TIlPw==}
/eslint-plugin-turbo@1.8.8(eslint@8.37.0):
resolution: {integrity: sha512-zqyTIvveOY4YU5jviDWw9GXHd4RiKmfEgwsjBrV/a965w0PpDwJgEUoSMB/C/dU310Sv9mF3DSdEjxjJLaw6rA==}
peerDependencies:
eslint: '>6.6.0'
dependencies:
@@ -11415,6 +11631,10 @@ packages:
- encoding
dev: true
/isomorphic.js@0.2.5:
resolution: {integrity: sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==}
dev: false
/isstream@0.1.2:
resolution: {integrity: sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==}
dev: true
@@ -11529,7 +11749,7 @@ packages:
exit: 0.1.2
graceful-fs: 4.2.10
import-local: 3.1.0
jest-config: 29.5.0
jest-config: 29.5.0(@types/node@18.15.11)
jest-util: 29.5.0
jest-validate: 29.5.0
prompts: 2.4.2
@@ -11540,44 +11760,6 @@ packages:
- ts-node
dev: true
/jest-config@29.5.0:
resolution: {integrity: sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
peerDependencies:
'@types/node': '*'
ts-node: '>=9.0.0'
peerDependenciesMeta:
'@types/node':
optional: true
ts-node:
optional: true
dependencies:
'@babel/core': 7.20.12
'@jest/test-sequencer': 29.5.0
'@jest/types': 29.5.0
babel-jest: 29.5.0(@babel/core@7.20.12)
chalk: 4.1.2
ci-info: 3.7.0
deepmerge: 4.2.2
glob: 7.2.3
graceful-fs: 4.2.10
jest-circus: 29.5.0
jest-environment-node: 29.5.0
jest-get-type: 29.4.3
jest-regex-util: 29.4.3
jest-resolve: 29.5.0
jest-runner: 29.5.0
jest-util: 29.5.0
jest-validate: 29.5.0
micromatch: 4.0.5
parse-json: 5.2.0
pretty-format: 29.5.0
slash: 3.0.0
strip-json-comments: 3.1.1
transitivePeerDependencies:
- supports-color
dev: true
/jest-config@29.5.0(@types/node@18.15.11):
resolution: {integrity: sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -12358,6 +12540,18 @@ packages:
prelude-ls: 1.2.1
type-check: 0.4.0
/lexical@0.10.0:
resolution: {integrity: sha512-/lYJVpjQPOzFVfQWhnxRSP6HHBywiltzx9/pWRsByzXDZL+FTty3yjTsSFiCWy4PoVZaXqH8gYBe9dkxZK7+Hg==}
dev: false
/lib0@0.2.74:
resolution: {integrity: sha512-roj9i46/JwG5ik5KNTkxP2IytlnrssAkD/OhlAVtE+GqectrdkfR+pttszVLrOzMDeXNs1MPt6yo66MUolWSiA==}
engines: {node: '>=14'}
hasBin: true
dependencies:
isomorphic.js: 0.2.5
dev: false
/libnpx@10.2.4:
resolution: {integrity: sha512-BPc0D1cOjBeS8VIBKUu5F80s6njm0wbVt7CsGMrIcJ+SI7pi7V0uVPGpEMH9H5L8csOcclTxAXFE2VAsJXUhfA==}
engines: {node: '>=4'}
@@ -12381,6 +12575,12 @@ packages:
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
dev: true
/linkify-it@4.0.1:
resolution: {integrity: sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==}
dependencies:
uc.micro: 1.0.6
dev: false
/listify@1.0.3:
resolution: {integrity: sha512-083swF7iH7bx8666zdzBColpgEuy46HjN3r1isD4zV6Ix7FuHfb/2/WVnl4CH8hjuoWeFF7P5KkKNXUnJCFEJg==}
engines: {node: '>= 0.4'}
@@ -12666,6 +12866,17 @@ packages:
engines: {node: '>=0.10.0'}
dev: false
/markdown-it@13.0.1:
resolution: {integrity: sha512-lTlxriVoy2criHP0JKRhO2VDG9c2ypWCsT237eDiLqi09rmbKoUetyGHq2uOIRoRS//kfoJckS0eUzzkDR+k2Q==}
hasBin: true
dependencies:
argparse: 2.0.1
entities: 3.0.1
linkify-it: 4.0.1
mdurl: 1.0.1
uc.micro: 1.0.6
dev: false
/markdown-table@3.0.3:
resolution: {integrity: sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==}
dev: false
@@ -12873,6 +13084,10 @@ packages:
resolution: {integrity: sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==}
dev: true
/mdurl@1.0.1:
resolution: {integrity: sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==}
dev: false
/media-typer@0.3.0:
resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
engines: {node: '>= 0.6'}
@@ -14851,22 +15066,6 @@ packages:
postcss: 8.4.22
dev: true
/postcss-load-config@3.1.4:
resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==}
engines: {node: '>= 10'}
peerDependencies:
postcss: '>=8.0.9'
ts-node: '>=9.0.0'
peerDependenciesMeta:
postcss:
optional: true
ts-node:
optional: true
dependencies:
lilconfig: 2.0.6
yaml: 1.10.2
dev: true
/postcss-load-config@3.1.4(postcss@8.4.21):
resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==}
engines: {node: '>= 10'}
@@ -15724,6 +15923,12 @@ packages:
hasBin: true
dev: true
/prettier@2.8.8:
resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==}
engines: {node: '>=10.13.0'}
hasBin: true
dev: true
/pretty-bytes@3.0.1:
resolution: {integrity: sha512-eb7ZAeUTgfh294cElcu51w+OTRp/6ItW758LjwJSK72LDevcuJn0P4eD71PLMDGPwwatXmAmYHTkzvpKlJE3ow==}
engines: {node: '>=0.10.0'}
@@ -15830,15 +16035,6 @@ packages:
engines: {node: '>=0.4.0'}
dev: true
/promise-inflight@1.0.1:
resolution: {integrity: sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==}
peerDependencies:
bluebird: '*'
peerDependenciesMeta:
bluebird:
optional: true
dev: true
/promise-inflight@1.0.1(bluebird@3.7.2):
resolution: {integrity: sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==}
peerDependencies:
@@ -16121,6 +16317,16 @@ packages:
scheduler: 0.23.0
dev: false
/react-error-boundary@3.1.4(react@18.2.0):
resolution: {integrity: sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA==}
engines: {node: '>=10', npm: '>=6'}
peerDependencies:
react: '>=16.13.1'
dependencies:
'@babel/runtime': 7.21.0
react: 18.2.0
dev: false
/react-hook-form@7.43.9(react@18.2.0):
resolution: {integrity: sha512-AUDN3Pz2NSeoxQ7Hs6OhQhDr6gtF9YRuutGDwPQqhSUAHJSgGl2VeY3qN19MG0SucpjgDiuMJ4iC5T5uB+eaNQ==}
engines: {node: '>=12.22.0'}
@@ -18492,42 +18698,6 @@ packages:
- ts-node
dev: true
/tsup@6.7.0(typescript@5.0.3):
resolution: {integrity: sha512-L3o8hGkaHnu5TdJns+mCqFsDBo83bJ44rlK7e6VdanIvpea4ArPcU3swWGsLVbXak1PqQx/V+SSmFPujBK+zEQ==}
engines: {node: '>=14.18'}
hasBin: true
peerDependencies:
'@swc/core': ^1
postcss: ^8.4.12
typescript: '>=4.1.0'
peerDependenciesMeta:
'@swc/core':
optional: true
postcss:
optional: true
typescript:
optional: true
dependencies:
bundle-require: 4.0.1(esbuild@0.17.11)
cac: 6.7.14
chokidar: 3.5.3
debug: 4.3.4
esbuild: 0.17.11
execa: 5.1.1
globby: 11.1.0
joycon: 3.1.1
postcss-load-config: 3.1.4
resolve-from: 5.0.0
rollup: 3.5.1
source-map: 0.8.0-beta.0
sucrase: 3.29.0
tree-kill: 1.2.2
typescript: 5.0.3
transitivePeerDependencies:
- supports-color
- ts-node
dev: true
/tsutils@3.21.0(typescript@5.0.3):
resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
engines: {node: '>= 6'}
@@ -18581,65 +18751,65 @@ packages:
dependencies:
safe-buffer: 5.2.1
/turbo-darwin-64@1.9.1:
resolution: {integrity: sha512-IX/Ph4CO80lFKd9pPx3BWpN2dynt6mcUFifyuHUNVkOP1Usza/G9YuZnKQFG6wUwKJbx40morFLjk1TTeLe04w==}
/turbo-darwin-64@1.8.8:
resolution: {integrity: sha512-18cSeIm7aeEvIxGyq7PVoFyEnPpWDM/0CpZvXKHpQ6qMTkfNt517qVqUTAwsIYqNS8xazcKAqkNbvU1V49n65Q==}
cpu: [x64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/turbo-darwin-arm64@1.9.1:
resolution: {integrity: sha512-6tCbmIboy9dTbhIZ/x9KIpje73nvxbiyVnHbr9xKnsxLJavD0xqjHZzbL5U2tHp8chqmYf0E4WYOXd+XCNg+OQ==}
/turbo-darwin-arm64@1.8.8:
resolution: {integrity: sha512-ruGRI9nHxojIGLQv1TPgN7ud4HO4V8mFBwSgO6oDoZTNuk5ybWybItGR+yu6fni5vJoyMHXOYA2srnxvOc7hjQ==}
cpu: [arm64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/turbo-linux-64@1.9.1:
resolution: {integrity: sha512-ti8XofnJFO1XaadL92lYJXgxb0VBl03Yu9VfhxkOTywFe7USTLBkJcdvQ4EpFk/KZwLiTdCmT2NQVxsG4AxBiQ==}
/turbo-linux-64@1.8.8:
resolution: {integrity: sha512-N/GkHTHeIQogXB1/6ZWfxHx+ubYeb8Jlq3b/3jnU4zLucpZzTQ8XkXIAfJG/TL3Q7ON7xQ8yGOyGLhHL7MpFRg==}
cpu: [x64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/turbo-linux-arm64@1.9.1:
resolution: {integrity: sha512-XYvIbeiCCCr+ENujd2Jtck/lJPTKWb8T2MSL/AEBx21Zy3Sa7HgrQX6LX0a0pNHjaleHz00XXt1D0W5hLeP+tA==}
/turbo-linux-arm64@1.8.8:
resolution: {integrity: sha512-hKqLbBHgUkYf2Ww8uBL9UYdBFQ5677a7QXdsFhONXoACbDUPvpK4BKlz3NN7G4NZ+g9dGju+OJJjQP0VXRHb5w==}
cpu: [arm64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/turbo-windows-64@1.9.1:
resolution: {integrity: sha512-x7lWAspe4/v3XQ0gaFRWDX/X9uyWdhwFBPEfb8BA0YKtnsrPOHkV0mRHCRrXzvzjA7pcDCl2agGzb7o863O+Jg==}
/turbo-windows-64@1.8.8:
resolution: {integrity: sha512-2ndjDJyzkNslXxLt+PQuU21AHJWc8f6MnLypXy3KsN4EyX/uKKGZS0QJWz27PeHg0JS75PVvhfFV+L9t9i+Yyg==}
cpu: [x64]
os: [win32]
requiresBuild: true
dev: true
optional: true
/turbo-windows-arm64@1.9.1:
resolution: {integrity: sha512-QSLNz8dRBLDqXOUv/KnoesBomSbIz2Huef/a3l2+Pat5wkQVgMfzFxDOnkK5VWujPYXz+/prYz+/7cdaC78/kw==}
/turbo-windows-arm64@1.8.8:
resolution: {integrity: sha512-xCA3oxgmW9OMqpI34AAmKfOVsfDljhD5YBwgs0ZDsn5h3kCHhC4x9W5dDk1oyQ4F5EXSH3xVym5/xl1J6WRpUg==}
cpu: [arm64]
os: [win32]
requiresBuild: true
dev: true
optional: true
/turbo@1.9.1:
resolution: {integrity: sha512-Rqe8SP96e53y4Pk29kk2aZbA8EF11UtHJ3vzXJseadrc1T3V6UhzvAWwiKJL//x/jojyOoX1axnoxmX3UHbZ0g==}
/turbo@1.8.8:
resolution: {integrity: sha512-qYJ5NjoTX+591/x09KgsDOPVDUJfU9GoS+6jszQQlLp1AHrf1wRFA3Yps8U+/HTG03q0M4qouOfOLtRQP4QypA==}
hasBin: true
requiresBuild: true
optionalDependencies:
turbo-darwin-64: 1.9.1
turbo-darwin-arm64: 1.9.1
turbo-linux-64: 1.9.1
turbo-linux-arm64: 1.9.1
turbo-windows-64: 1.9.1
turbo-windows-arm64: 1.9.1
turbo-darwin-64: 1.8.8
turbo-darwin-arm64: 1.8.8
turbo-linux-64: 1.8.8
turbo-linux-arm64: 1.8.8
turbo-windows-64: 1.8.8
turbo-windows-arm64: 1.8.8
dev: true
/tween-functions@1.2.0:
@@ -18739,6 +18909,10 @@ packages:
engines: {node: '>=12.20'}
hasBin: true
/uc.micro@1.0.6:
resolution: {integrity: sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==}
dev: false
/uglify-js@3.4.10:
resolution: {integrity: sha512-Y2VsbPVs0FIshJztycsO2SfPk7/KAF/T72qzv9u5EpQ4kB2hQoHlhNQTsNyy6ul7lQtqJN/AoWeS23OzEiEFxw==}
engines: {node: '>=0.8.0'}
@@ -20051,6 +20225,13 @@ packages:
yargs-parser: 21.1.1
dev: true
/yjs@13.6.0:
resolution: {integrity: sha512-tFZtuQV6XamtDa9SfZhUsxchUcTBWe7fzpo1UWZDLXGejTZ5t9MCswGYzyvq7+BDzfEc9oX54QEbzI/4NyS6+g==}
engines: {node: '>=16.0.0', npm: '>=8.0.0'}
dependencies:
lib0: 0.2.74
dev: false
/yocto-queue@0.1.0:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}