diff --git a/.eslintrc.js b/.eslintrc.js index 43f48e99..0bb426c 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -54,12 +54,15 @@ module.exports = { "error", { replacements: { + prop: false, props: false, ref: false, }, }, ], "unicorn/no-null": "off", + // .textContent is very different from .innerText, not interchangeable + "unicorn/prefer-dom-node-text-content": "off", /** @see https://medium.com/weekly-webtips/how-to-sort-imports-like-a-pro-in-typescript-4ee8afd7258a */ "import/order": [ diff --git a/package.json b/package.json index 562d845..b03de1b 100644 --- a/package.json +++ b/package.json @@ -40,22 +40,25 @@ "react-hook-form": "^7.42.0", "react-jwt": "^1.1.8", "react-qr-barcode-scanner": "^1.0.6", + "react-quill": "^2.0.0", "react-redux": "^8.0.5", + "sanitize-html": "^2.11.0", "sharp": "^0.31.3", "string-similarity": "^4.0.4", "swr": "^2.0.0", "validator": "^13.7.0" }, "devDependencies": { - "@boklisten/bl-model": "^0.25.16", + "@boklisten/bl-model": "^0.25.30", "@commitlint/cli": "^17.7.1", "@commitlint/config-conventional": "^17.7.0", "@testing-library/react": "^13.4.0", "@types/draft-js": "^0.11.10", "@types/node": "18.11.18", "@types/react": "18.0.26", - "@types/react-draft-wysiwyg": "^1.13.4", + "@types/react-draft-wysiwyg": "^1.13.5", "@types/react-redux": "^7.1.25", + "@types/sanitize-html": "^2.9.5", "@types/string-similarity": "^4.0.0", "@types/validator": "^13.7.10", "@typescript-eslint/eslint-plugin": "^6.6.0", diff --git a/src/api/api.ts b/src/api/api.ts index 37f431a..a2e8fb1 100644 --- a/src/api/api.ts +++ b/src/api/api.ts @@ -18,20 +18,19 @@ export const get = async ( headers: getHeaders(), }) .catch(async (error) => { - if (error.status === 404 || error.response.status === 404) { - throw new Error("Not found"); + if ((error as AxiosError)?.response?.status === 404) { + throw new NotFoundError(`API request resulted in 404: ${url}`); } - console.log(error); - + console.error(error); if ( - (error.status === 401 || error.response.status === 401) && + (error?.status === 401 || error?.response?.status === 401) && !noRetryTokens ) { await fetchNewTokens(); return await get(url, query, true); } - throw new Error("Unknown API error"); + throw new Error(`Unknown API error: ${error}`); }); }; @@ -45,6 +44,36 @@ export const add = async (collection: string, data: unknown) => { }); }; +export const put = async ( + url: string, + data: T, + noRetryTokens?: boolean, +): Promise => { + if (!url || url.length === 0) { + throw new Error("url is undefined"); + } + + await axios + .put(apiPath(url), data, { + headers: getHeaders(), + }) + .catch(async (error) => { + if ( + (error.status === 401 || error.response.status === 401) && + !noRetryTokens + ) { + await fetchNewTokens(); + return await put(url, data, true); + } + + if ((error as AxiosError).response?.status === 404) { + throw new NotFoundError(`API request resulted in 404: ${url}`); + } + + throw new Error("Unknown API error", error); + }); +}; + export const addWithEndpoint = async ( collection: string, endpoint: string, @@ -72,13 +101,5 @@ export class NotFoundError extends Error { // eslint-disable-next-line @typescript-eslint/no-explicit-any export const apiFetcher = async (url: string): Promise => { - try { - return await get<{ data: T }>(url).then((response) => response.data.data); - } catch (error) { - if (!((error as AxiosError).response?.status === 404)) { - throw error; - } - - throw new NotFoundError(`API request resulted in 404: ${url}`); - } + return await get<{ data: T }>(url).then((response) => response.data.data); }; diff --git a/src/components/editableText/EditableTextEditor.tsx b/src/components/editableText/EditableTextEditor.tsx new file mode 100644 index 0000000..5a400fd --- /dev/null +++ b/src/components/editableText/EditableTextEditor.tsx @@ -0,0 +1,171 @@ +import "react-quill/dist/quill.snow.css"; + +import { Box, Button, Container, styled } from "@mui/material"; +import dynamic from "next/dynamic"; +import { useRouter } from "next/router"; +import React, { useRef, useState } from "react"; +import ReactQuill from "react-quill"; + +import { put } from "api/api"; +import { EditorProps } from "components/editableText/EditableTextElement"; +import { EditableTextRenderer } from "components/editableText/EditableTextRenderer"; +import BL_CONFIG from "utils/bl-config"; +import useExitInterceptor from "utils/useExitInterceptor"; + +const Quill = styled( + dynamic(import("react-quill"), { ssr: false }), +)({}); + +export const EditableTextEditor = ({ editableText }: EditorProps) => { + const initialValue = editableText.text ?? ""; + + const editorState = useRef(initialValue); + const editorRef = useRef(null); + + const [readOnly, setReadOnly] = useState(true); + + const router = useRouter(); + useExitInterceptor(!readOnly); + + const onEdit = () => { + setReadOnly(false); + }; + + const onEditorSave = async () => { + if (!(await router.replace(router.asPath))) { + throw new Error("Unable to refresh"); + } + }; + + const onSave = () => { + setReadOnly(true); + if (editorRef.current?.innerText.trim().length === 0) { + editorState.current = ""; + } + put(`${BL_CONFIG.collection.editableText}/${editableText.id}/`, { + ...editableText, + text: editorState.current, + }) + .then(async () => { + await onEditorSave(); + return; + }) + .catch((error) => { + throw new Error("Failed to save editable text", { cause: error }); + }); + }; + + const onCancel = () => { + editorState.current = initialValue; + setReadOnly(true); + }; + + return ( + + {readOnly ? ( + + ) : ( + + + + + )} + + { + editorState.current = changedState; + }} + sx={{ + width: "100%", + "& .ql-editor": { + minHeight: "10em", + }, + }} + /> + + {readOnly && } + + ); +}; + +const quillModules = { + toolbar: [ + [{ header: "1" }, { header: "2" }, { header: "3" }], + // Disabled until bl-web no longer needs to be supported + // [{ size: [] }], + ["bold", "italic", "underline", "strike", "blockquote"], + [ + { list: "ordered" }, + { list: "bullet" }, + { indent: "-1" }, + { indent: "+1" }, + ], + ["link"], + // Disabled until bl-web no longer needs to be supported + // [{ align: [] }], + ["clean"], + ], + clipboard: { + // toggle to add extra line breaks when pasting HTML: + matchVisual: false, + }, +}; + +/* + * Quill editor formats + * See https://quilljs.com/docs/formats/ + */ +const quillFormats = [ + "header", + "size", + "bold", + "italic", + "underline", + "strike", + "blockquote", + "list", + "bullet", + "indent", + "align", + "link", +]; diff --git a/src/components/editableText/EditableTextElement.tsx b/src/components/editableText/EditableTextElement.tsx new file mode 100644 index 0000000..af0b628 --- /dev/null +++ b/src/components/editableText/EditableTextElement.tsx @@ -0,0 +1,25 @@ +import React from "react"; + +import "react-draft-wysiwyg/dist/react-draft-wysiwyg.css"; + +import { isAdmin } from "api/auth"; +import { EditableTextEditor } from "components/editableText/EditableTextEditor"; +import { EditableTextRenderer } from "components/editableText/EditableTextRenderer"; +import { MaybeEmptyEditableText } from "utils/types"; +import useIsHydrated from "utils/useIsHydrated"; + +export interface EditorProps { + editableText: MaybeEmptyEditableText; +} + +const EditableTextElement = ({ editableText }: EditorProps) => { + const hydrated = useIsHydrated(); + + if (hydrated && isAdmin()) { + return ; + } + + return ; +}; + +export default EditableTextElement; diff --git a/src/components/editableText/EditableTextRenderer.tsx b/src/components/editableText/EditableTextRenderer.tsx new file mode 100644 index 0000000..82761a0 --- /dev/null +++ b/src/components/editableText/EditableTextRenderer.tsx @@ -0,0 +1,26 @@ +import "react-quill/dist/quill.core.css"; +import { Box } from "@mui/material"; +import React from "react"; + +import { sanitizeQuillHtml } from "utils/sanitizeHtml"; +import { MaybeEmptyEditableText } from "utils/types"; + +export const EditableTextRenderer = ({ + editableText, +}: { + editableText: MaybeEmptyEditableText; +}) => { + if (!editableText.text) { + return null; + } + const content = sanitizeQuillHtml(editableText.text); + return ( + + ); +}; diff --git a/src/components/editableText/NewsBanner.tsx b/src/components/editableText/NewsBanner.tsx new file mode 100644 index 0000000..cb408a2 --- /dev/null +++ b/src/components/editableText/NewsBanner.tsx @@ -0,0 +1,34 @@ +import { Box } from "@mui/material"; +import { ComponentProps } from "react"; + +import EditableTextElement from "components/editableText/EditableTextElement"; +import theme from "utils/theme"; + +const NewsBanner = (props: ComponentProps) => { + if ( + props.editableText.text === null || + props.editableText.text.length === 0 + ) { + return ; + } + return ( + + + + ); +}; + +export default NewsBanner; diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 120e16b..7aa1c45 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,11 +1,35 @@ import { Box, Typography } from "@mui/material"; import type { NextPage } from "next"; +import { GetStaticProps } from "next"; import Head from "next/head"; import Image from "next/image"; +import React from "react"; import DynamicLink from "components/DynamicLink"; +import NewsBanner from "components/editableText/NewsBanner"; +import { editableTextIds } from "utils/constants"; +import { MaybeEmptyEditableText } from "utils/types"; +import getEditableText from "utils/useEditableText"; -const Home: NextPage = () => { +interface HomeProps { + newsBannerText: MaybeEmptyEditableText; +} + +const revalidate = 10; + +export const getStaticProps: GetStaticProps = async () => { + const newsBannerText = await getEditableText( + editableTextIds.frontPage.newsBanner, + ); + return { + props: { + newsBannerText, + }, + revalidate, + }; +}; + +const Home: NextPage = ({ newsBannerText }) => { return ( <> @@ -22,9 +46,10 @@ const Home: NextPage = () => { alignItems: "center", width: "100%", textAlign: "center", + position: "relative", }} > - + { Vi i Boklisten.no er veldig opptatt av lærebøker, derfor vil vi gjøre det så enkelt som mulig for deg å få tak i dem. + Til gamle boklisten.no diff --git a/src/utils/bl-config.ts b/src/utils/bl-config.ts index db9e943..15b5171 100644 --- a/src/utils/bl-config.ts +++ b/src/utils/bl-config.ts @@ -62,6 +62,7 @@ const BL_CONFIG = { item: "items", branch: "branches", branchItem: "branchitems", + editableText: "editableTexts", openingHour: "openingHours", userDetail: "userdetails", customerItem: "customerItems", diff --git a/src/utils/constants.ts b/src/utils/constants.ts index b7b233f..0df990b 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -32,3 +32,12 @@ export const contactInfo = { phone: "91002211", address: "Postboks 8, 1316 Eiksmarka", }; + +export const editableTextIds = { + frontPage: { + newsBanner: "65a7f68e81488330ddcd6fd3", + }, + info: { + general: "650ae3eaa4c00a2d1c0824b3", + }, +}; diff --git a/src/utils/sanitizeHtml.ts b/src/utils/sanitizeHtml.ts new file mode 100644 index 0000000..044b867 --- /dev/null +++ b/src/utils/sanitizeHtml.ts @@ -0,0 +1,12 @@ +import sanitizeHtml from "sanitize-html"; + +const quillRegex = /^ql-.*$/; +const quillClasses = ["span", "blockquote", "p", "em"]; + +export const sanitizeQuillHtml = (text: string): string => { + return sanitizeHtml(text, { + allowedClasses: Object.fromEntries( + quillClasses.map((tag) => [tag, [quillRegex]]), + ), + }); +}; diff --git a/src/utils/theme.ts b/src/utils/theme.ts index 8b87d79..0e7a99d 100644 --- a/src/utils/theme.ts +++ b/src/utils/theme.ts @@ -1,4 +1,4 @@ -import { red } from "@mui/material/colors"; +import { red, orange, grey } from "@mui/material/colors"; import { createTheme, responsiveFontSizes } from "@mui/material/styles"; /** @@ -18,6 +18,12 @@ const theme = responsiveFontSizes( error: { main: red.A400, }, + warning: { + main: orange["500"], + light: orange["100"], + dark: orange["700"], + contrastText: grey["900"], + }, }, typography: { h1: { @@ -78,6 +84,13 @@ const theme = responsiveFontSizes( }, }, }, + MuiButton: { + styleOverrides: { + root: { + paddingX: 5, + }, + }, + }, }, breakpoints: { values: { diff --git a/src/utils/types.ts b/src/utils/types.ts index 0491b0a..1260f04 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -76,3 +76,8 @@ export enum TextType { } export type ScannedTextType = TextType.BLID | TextType.ISBN | TextType.UNKNOWN; + +export interface MaybeEmptyEditableText { + id: string; + text: string | null; +} diff --git a/src/utils/useEditableText.ts b/src/utils/useEditableText.ts new file mode 100644 index 0000000..cef4610 --- /dev/null +++ b/src/utils/useEditableText.ts @@ -0,0 +1,29 @@ +import { EditableText } from "@boklisten/bl-model"; + +import { apiFetcher, NotFoundError } from "api/api"; +import BL_CONFIG from "utils/bl-config"; +import { MaybeEmptyEditableText } from "utils/types"; + +const useEditableText = async ( + editableTextId: string, +): Promise => { + try { + const [result] = await apiFetcher<[EditableText]>( + `${BL_CONFIG.collection.editableText}/${editableTextId}`, + ); + return result; + } catch (error) { + if (!(error instanceof NotFoundError)) { + console.error( + "Could not fetch EditableText and it was not a 404:", + error, + ); + } + return { + id: editableTextId, + text: null, + }; + } +}; + +export default useEditableText; diff --git a/src/utils/useExitInterceptor.ts b/src/utils/useExitInterceptor.ts new file mode 100644 index 0000000..03ea2e6 --- /dev/null +++ b/src/utils/useExitInterceptor.ts @@ -0,0 +1,23 @@ +import { useEffect } from "react"; + +import useIsHydrated from "utils/useIsHydrated"; + +const useExitInterceptor = (preventExit: boolean) => { + const hydrated = useIsHydrated(); + + useEffect(() => { + if (hydrated) { + const onBeforeUnload = (event: BeforeUnloadEvent) => { + if (preventExit) { + event.preventDefault(); + } + }; + + window.addEventListener("beforeunload", onBeforeUnload); + return () => window.removeEventListener("beforeunload", onBeforeUnload); + } + return; + }, [hydrated, preventExit]); +}; + +export default useExitInterceptor; diff --git a/src/utils/useIsHydrated.ts b/src/utils/useIsHydrated.ts new file mode 100644 index 0000000..60db35d --- /dev/null +++ b/src/utils/useIsHydrated.ts @@ -0,0 +1,13 @@ +import { useEffect, useState } from "react"; + +const useIsHydrated = () => { + const [hydrated, setHydrated] = useState(false); + + useEffect(() => { + setHydrated(true); + }, []); + + return hydrated; +}; + +export default useIsHydrated; diff --git a/yarn.lock b/yarn.lock index e3fcddc..1f26dd1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -73,12 +73,12 @@ "@babel/helper-validator-identifier" "^7.19.1" to-fast-properties "^2.0.0" -"@boklisten/bl-model@^0.25.16": - version "0.25.16" - resolved "https://registry.yarnpkg.com/@boklisten/bl-model/-/bl-model-0.25.16.tgz#e2d6f82dccbd5de2e48daf4dbec8ffac73d86223" - integrity sha512-MJ3rIJz1ozxkaVlqFvngOPG2iO8SnIpBGCDRxTiHK8zEuNnphRQVj0i0k1bmz5bcKuXztkgvw0a/HcSbPUwR2w== +"@boklisten/bl-model@^0.25.30": + version "0.25.30" + resolved "https://registry.yarnpkg.com/@boklisten/bl-model/-/bl-model-0.25.30.tgz#8989f5fba19d8aa5486d8488d5be7e12f3778083" + integrity sha512-rh250euu/zLldrFNxB9rfTATQ0e3eS6wAFiMJGQ2AUSJTkOXUe9+ozm1/Ft6xkx7Pn7LWsmnLHzuadHLYA/g0A== dependencies: - typescript "^4.5.2" + typescript "^5.2.2" "@colors/colors@1.5.0": version "1.5.0" @@ -886,6 +886,13 @@ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf" integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w== +"@types/quill@^1.3.10": + version "1.3.10" + resolved "https://registry.yarnpkg.com/@types/quill/-/quill-1.3.10.tgz#dc1f7b6587f7ee94bdf5291bc92289f6f0497613" + integrity sha512-IhW3fPW+bkt9MLNlycw8u8fWb7oO7W5URC9MfZYHBlA24rex9rs23D5DETChu1zvgVdc5ka64ICjJOgQMr6Shw== + dependencies: + parchment "^1.1.2" + "@types/react-dom@^18.0.0": version "18.0.10" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.10.tgz#3b66dec56aa0f16a6cc26da9e9ca96c35c0b4352" @@ -893,10 +900,10 @@ dependencies: "@types/react" "*" -"@types/react-draft-wysiwyg@^1.13.4": - version "1.13.4" - resolved "https://registry.yarnpkg.com/@types/react-draft-wysiwyg/-/react-draft-wysiwyg-1.13.4.tgz#df951c76afb47e311061d363f41a10c76de04ac8" - integrity sha512-wasD1t78JDmQvdPDRPf/mf5FSHMlncunW0F6KMOKB3awzi3Wi21yHMGsRAUOkfTr3R8F+yceG8fSLz0kYWu/QA== +"@types/react-draft-wysiwyg@^1.13.5": + version "1.13.5" + resolved "https://registry.yarnpkg.com/@types/react-draft-wysiwyg/-/react-draft-wysiwyg-1.13.5.tgz#5889da9c940b0d9c6e17eefb9181bff6ce69718c" + integrity sha512-XfCHpRfhlPvcahQ1QAKZujkMFNNn8nEpnEFXmzFzuEvZvJmr9JutKHgLH6ESk1S95G+lBVCTTSToVmn9PeuqwQ== dependencies: "@types/draft-js" "*" "@types/react" "*" @@ -943,6 +950,13 @@ "@types/scheduler" "*" csstype "^3.0.2" +"@types/sanitize-html@^2.9.5": + version "2.9.5" + resolved "https://registry.yarnpkg.com/@types/sanitize-html/-/sanitize-html-2.9.5.tgz#e8b2214c8afc7bb88d62f9c3bbbc5b4ecc80a25d" + integrity sha512-2Sr1vd8Dw+ypsg/oDDfZ57OMSG2Befs+l2CMyCC5bVSK3CpE7lTB2aNlbbWzazgVA+Qqfuholwom6x/mWd1qmw== + dependencies: + htmlparser2 "^8.0.0" + "@types/scheduler@*": version "0.16.2" resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39" @@ -1637,6 +1651,11 @@ cliui@^8.0.1: strip-ansi "^6.0.1" wrap-ansi "^7.0.0" +clone@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" + integrity sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w== + clsx@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" @@ -1927,6 +1946,18 @@ decompress-response@^6.0.0: dependencies: mimic-response "^3.1.0" +deep-equal@^1.0.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a" + integrity sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g== + dependencies: + is-arguments "^1.0.4" + is-date-object "^1.0.1" + is-regex "^1.0.4" + object-is "^1.0.1" + object-keys "^1.1.1" + regexp.prototype.flags "^1.2.0" + deep-equal@^2.0.5: version "2.2.0" resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-2.2.0.tgz#5caeace9c781028b9ff459f33b779346637c43e6" @@ -1960,6 +1991,20 @@ deep-is@^0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== +deepmerge@^4.2.2: + version "4.3.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" + integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== + +define-data-property@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.0.tgz#0db13540704e1d8d479a0656cf781267531b9451" + integrity sha512-UzGwzcjyv3OtAvolTj1GoyNYzfFR+iqbGjcnBEENZVCpM4/Ng1yhGNvS3lR/xDS74Tb2wGG9WzNSNIOS9UVb2g== + dependencies: + get-intrinsic "^1.2.1" + gopd "^1.0.1" + has-property-descriptors "^1.0.0" + define-lazy-prop@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" @@ -2030,6 +2075,36 @@ dom-helpers@^5.0.1: "@babel/runtime" "^7.8.7" csstype "^3.0.2" +dom-serializer@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53" + integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.2" + entities "^4.2.0" + +domelementtype@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" + integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== + +domhandler@^5.0.2, domhandler@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31" + integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w== + dependencies: + domelementtype "^2.3.0" + +domutils@^3.0.1: + version "3.1.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.1.0.tgz#c47f551278d3dc4b0b1ab8cbb42d751a6f0d824e" + integrity sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA== + dependencies: + dom-serializer "^2.0.0" + domelementtype "^2.3.0" + domhandler "^5.0.3" + dot-prop@^5.1.0: version "5.3.0" resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88" @@ -2111,6 +2186,11 @@ enquirer@^2.3.6: dependencies: ansi-colors "^4.1.1" +entities@^4.2.0, entities@^4.4.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" + integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== + error-ex@^1.3.1: version "1.3.2" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" @@ -2555,6 +2635,11 @@ eventemitter2@6.4.7: resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.7.tgz#a7f6c4d7abf28a14c1ef3442f21cb306a054271d" integrity sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg== +eventemitter3@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-2.0.3.tgz#b5e1079b59fb5e1ba2771c0a993be060a58c99ba" + integrity sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg== + eventemitter3@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" @@ -2617,7 +2702,7 @@ expand-template@^2.0.3: resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== -extend@~3.0.2: +extend@^3.0.2, extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== @@ -2648,6 +2733,11 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== +fast-diff@1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.1.2.tgz#4b62c42b8e03de3f848460b639079920695d0154" + integrity sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig== + fast-glob@^3.2.11, fast-glob@^3.2.9: version "3.2.12" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80" @@ -3152,6 +3242,16 @@ html-tokenize@^2.0.0: readable-stream "~1.0.27-1" through2 "~0.4.1" +htmlparser2@^8.0.0: + version "8.0.2" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-8.0.2.tgz#f002151705b383e62433b5cf466f5b716edaec21" + integrity sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.3" + domutils "^3.0.1" + entities "^4.4.0" + http-signature@~1.3.6: version "1.3.6" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.3.6.tgz#cb6fbfdf86d1c974f343be94e87f7fc128662cf9" @@ -3260,7 +3360,7 @@ internal-slot@^1.0.5: has "^1.0.3" side-channel "^1.0.4" -is-arguments@^1.1.1: +is-arguments@^1.0.4, is-arguments@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA== @@ -3423,7 +3523,12 @@ is-plain-obj@^1.1.0: resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" integrity sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg== -is-regex@^1.1.4: +is-plain-object@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" + integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== + +is-regex@^1.0.4, is-regex@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== @@ -3801,7 +3906,7 @@ lodash.upperfirst@^4.3.1: resolved "https://registry.yarnpkg.com/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz#1365edf431480481ef0d1c68957a5ed99d49f7ce" integrity sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg== -lodash@^4.17.15, lodash@^4.17.21: +lodash@^4.17.15, lodash@^4.17.21, lodash@^4.17.4: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -4006,6 +4111,11 @@ nanoid@^3.3.4: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab" integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw== +nanoid@^3.3.7: + version "3.3.7" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" + integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== + napi-build-utils@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806" @@ -4104,7 +4214,7 @@ object-inspect@^1.12.2, object-inspect@^1.12.3, object-inspect@^1.9.0: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9" integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g== -object-is@^1.1.5: +object-is@^1.0.1, object-is@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac" integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw== @@ -4264,6 +4374,11 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== +parchment@^1.1.2, parchment@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/parchment/-/parchment-1.1.4.tgz#aeded7ab938fe921d4c34bc339ce1168bc2ffde5" + integrity sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg== + parent-module@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" @@ -4281,6 +4396,11 @@ parse-json@^5.0.0: json-parse-even-better-errors "^2.3.0" lines-and-columns "^1.1.6" +parse-srcset@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/parse-srcset/-/parse-srcset-1.0.2.tgz#f2bd221f6cc970a938d88556abc589caaaa2bde1" + integrity sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q== + path-exists@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" @@ -4355,6 +4475,15 @@ postcss@8.4.14: picocolors "^1.0.0" source-map-js "^1.0.2" +postcss@^8.3.11: + version "8.4.33" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.33.tgz#1378e859c9f69bf6f638b990a0212f43e2aaa742" + integrity sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg== + dependencies: + nanoid "^3.3.7" + picocolors "^1.0.0" + source-map-js "^1.0.2" + prebuild-install@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.1.tgz#de97d5b34a70a0c81334fd24641f2a1702352e45" @@ -4475,6 +4604,27 @@ quick-lru@^4.0.1: resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== +quill-delta@^3.6.2: + version "3.6.3" + resolved "https://registry.yarnpkg.com/quill-delta/-/quill-delta-3.6.3.tgz#b19fd2b89412301c60e1ff213d8d860eac0f1032" + integrity sha512-wdIGBlcX13tCHOXGMVnnTVFtGRLoP0imqxM696fIPwIf5ODIYUHIvHbZcyvGlZFiFhK5XzDC2lpjbxRhnM05Tg== + dependencies: + deep-equal "^1.0.1" + extend "^3.0.2" + fast-diff "1.1.2" + +quill@^1.3.7: + version "1.3.7" + resolved "https://registry.yarnpkg.com/quill/-/quill-1.3.7.tgz#da5b2f3a2c470e932340cdbf3668c9f21f9286e8" + integrity sha512-hG/DVzh/TiknWtE6QmWAF/pxoZKYxfe3J/d/+ShUWkDvvkZQVTPeVmUJVu1uE6DDooC4fWTiCLh84ul89oNz5g== + dependencies: + clone "^2.1.1" + deep-equal "^1.0.1" + eventemitter3 "^2.0.3" + extend "^3.0.2" + parchment "^1.1.4" + quill-delta "^3.6.2" + rc@^1.2.7: version "1.2.8" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" @@ -4540,6 +4690,15 @@ react-qr-barcode-scanner@^1.0.6: "@zxing/library" "^0.17.0" react-webcam "^5.0.1" +react-quill@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/react-quill/-/react-quill-2.0.0.tgz#67a0100f58f96a246af240c9fa6841b363b3e017" + integrity sha512-4qQtv1FtCfLgoD3PXAur5RyxuUbPXQGOHgTlFie3jtxp43mXDtzCKaOgQ3mLyZfi1PUlyjycfivKelFhy13QUg== + dependencies: + "@types/quill" "^1.3.10" + lodash "^4.17.4" + quill "^1.3.7" + react-redux@^8.0.5: version "8.0.5" resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-8.0.5.tgz#e5fb8331993a019b8aaf2e167a93d10af469c7bd" @@ -4655,6 +4814,15 @@ regexp-tree@^0.1.27: resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.27.tgz#2198f0ef54518ffa743fe74d983b56ffd631b6cd" integrity sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA== +regexp.prototype.flags@^1.2.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz#90ce989138db209f81492edd734183ce99f9677e" + integrity sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + set-function-name "^2.0.0" + regexp.prototype.flags@^1.4.3: version "1.4.3" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz#87cab30f80f66660181a3bb7bf5981a872b367ac" @@ -4828,6 +4996,18 @@ safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== +sanitize-html@^2.11.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-2.11.0.tgz#9a6434ee8fcaeddc740d8ae7cd5dd71d3981f8f6" + integrity sha512-BG68EDHRaGKqlsNjJ2xUB7gpInPA8gVx/mvjO743hZaeMCZ2DwzW7xvsqZ+KNU4QKwj86HJ3uu2liISf2qBBUA== + dependencies: + deepmerge "^4.2.2" + escape-string-regexp "^4.0.0" + htmlparser2 "^8.0.0" + is-plain-object "^5.0.0" + parse-srcset "^1.0.2" + postcss "^8.3.11" + scheduler@^0.23.0: version "0.23.0" resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe" @@ -4840,7 +5020,7 @@ scheduler@^0.23.0: resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -semver@7.5.4, semver@^7.5.4: +semver@7.5.4, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.8, semver@^7.5.4: version "7.5.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== @@ -4857,12 +5037,14 @@ semver@^6.3.1: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.8: - version "7.3.8" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798" - integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A== +set-function-name@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.1.tgz#12ce38b7954310b9f61faa12701620a0c882793a" + integrity sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA== dependencies: - lru-cache "^6.0.0" + define-data-property "^1.0.1" + functions-have-names "^1.2.3" + has-property-descriptors "^1.0.0" setimmediate@^1.0.5: version "1.0.5" @@ -5466,16 +5648,21 @@ typed-array-length@^1.0.4: for-each "^0.3.3" is-typed-array "^1.1.9" -typescript@^4.5.2, typescript@^4.9.4: - version "4.9.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.4.tgz#a2a3d2756c079abda241d75f149df9d561091e78" - integrity sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg== - "typescript@^4.6.4 || ^5.0.0": version "5.2.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.2.2.tgz#5ebb5e5a5b75f085f22bc3f8460fba308310fa78" integrity sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w== +typescript@^4.9.4: + version "4.9.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.4.tgz#a2a3d2756c079abda241d75f149df9d561091e78" + integrity sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg== + +typescript@^5.2.2: + version "5.3.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37" + integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw== + ua-parser-js@^0.7.18: version "0.7.32" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.32.tgz#cd8c639cdca949e30fa68c44b7813ef13e36d211"