Merge branch 'main' of github.com:formbricks/formbricks
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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;
|
||||
})}
|
||||
</>
|
||||
|
||||
45
apps/web/components/preview/CTAQuestion.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
10
apps/web/components/preview/HtmlBody.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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",
|
||||
|
||||
47
packages/js/src/components/CTAQuestion.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
11
packages/js/src/components/HtmlBody.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
79
packages/js/src/lib/cleanHtml.ts
Normal 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;
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
3
packages/lib/markdownIt.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import MarkdownIt from "markdown-it";
|
||||
|
||||
export const md = new MarkdownIt("default", { html: true, breaks: true, linkify: true });
|
||||
@@ -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;
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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>
|
||||
|
||||
106
packages/ui/components/editor/Editor.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
24
packages/ui/components/editor/ExampleTheme.ts
Normal 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;
|
||||
4
packages/ui/components/editor/images/icons/link.svg
Normal 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 |
4
packages/ui/components/editor/images/icons/list-ol.svg
Normal 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 |
3
packages/ui/components/editor/images/icons/list-ul.svg
Normal 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 |
@@ -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 |
@@ -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 |
3
packages/ui/components/editor/images/icons/type-bold.svg
Normal 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 |
3
packages/ui/components/editor/images/icons/type-h1.svg
Normal 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 |
3
packages/ui/components/editor/images/icons/type-h2.svg
Normal 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 |
@@ -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 |
5
packages/ui/components/editor/index.ts
Normal 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";
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
36
packages/ui/components/editor/plugins/AutoLinkPlugin.tsx
Normal 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} />;
|
||||
}
|
||||
519
packages/ui/components/editor/plugins/ToolbarPlugin.tsx
Normal 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(/</g, "<").replace(/>/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>
|
||||
);
|
||||
}
|
||||
337
packages/ui/components/editor/stylesEditor.css
Normal 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);
|
||||
}
|
||||
55
packages/ui/components/editor/stylesEditorFrontend.css
Normal 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;
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
@@ -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
@@ -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'}
|
||||
|
||||