diff --git a/backend/src/lib/report-generator.js b/backend/src/lib/report-generator.js index 2cc08133..f42cd609 100644 --- a/backend/src/lib/report-generator.js +++ b/backend/src/lib/report-generator.js @@ -661,36 +661,47 @@ async function prepAuditData(data, settings) { } async function splitHTMLParagraphs(data) { - var result = []; - if (!data) return result; + if (!data) return []; - var splitted = data.split(/()/); + const result = []; + const splitted = data.split(/()/); + + for (const value of splitted) { + if (!value) continue; - for (var value of splitted) { if (value.startsWith(' 1) src = src[1]; - if (alt && alt.length > 1) alt = _.unescape(alt[1]); - - if (!src.startsWith('data')) { - try { - src = (await Image.getOne(src)).value; - } catch (error) { - src = 'data:image/gif;base64,R0lGODlhAQABAAAAACwAAAAAAQABAAA='; - } - } + const { src, alt } = extractImageAttributes(value); + const processedSrc = await processImageSrc(src); + if (result.length === 0) result.push({ text: '', images: [] }); - result[result.length - 1].images.push({ image: src, caption: alt }); - } else if (value === '') { - continue; + result[result.length - 1].images.push({ image: processedSrc, caption: alt }); } else { result.push({ text: value, images: [] }); } } + return result; } +function extractImageAttributes(value) { + const srcMatch = value.match(/ 1 ? srcMatch[1] : ''; + const alt = altMatch.length > 1 ? _.unescape(altMatch[1]) : ''; + return { src, alt }; +} + +async function processImageSrc(src) { + if (!src.startsWith('data')) { + try { + src = (await Image.getOne(src)).value; + } catch (error) { + src = 'data:image/gif;base64,R0lGODlhAQABAAAAACwAAAAAAQABAAA='; + } + } + return src; +} + function replaceSubTemplating(o, originalData = o) { var regexp = /\{_\{([a-zA-Z0-9\[\]\_\.]{1,})\}_\}/gm; if (Array.isArray(o)) diff --git a/frontend/package.json b/frontend/package.json index 10568834..ca664245 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -12,6 +12,7 @@ "preview": "vite preview" }, "dependencies": { + "@ant-design/icons": "^5.6.0", "@emotion/react": "^11.10.6", "@emotion/styled": "^11.10.6", "@headlessui/react": "^2.1.2", @@ -37,18 +38,21 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "dayjs": "^1.11.7", + "diff": "^7.0.0", "html2canvas": "^1.4.1", "i18next": "^23.12.2", "i18next-browser-languagedetector": "^8.0.0", "jspdf": "^2.5.2", + "katex": "^0.16.21", + "lodash": "^4.17.21", "lucide-react": "^0.435.0", "react": "^18.3.1", "react-chartjs-2": "^5.2.0", "react-dom": "^18.3.1", "react-i18next": "^15.0.0", "react-icons": "^5.3.0", - "react-quill-new": "^3.3.0", "react-router-dom": "^6.25.1", + "reactjs-tiptap-editor": "^0.1.14", "sonner": "^1.5.0", "tailwind-merge": "^2.5.2", "tailwindcss-animate": "^1.0.7" @@ -68,7 +72,7 @@ "eslint-plugin-import": "^2.29.1", "eslint-plugin-jsx-a11y": "^6.9.0", "eslint-plugin-no-secrets": "^1.0.2", - "eslint-plugin-prefer-arrow-functions": "^3.4.1", + "eslint-plugin-prefer-arrow-functions": "3.4.1", "eslint-plugin-prettier": "^5.2.1", "eslint-plugin-react": "^7.35.0", "eslint-plugin-react-hooks": "^4.6.2", diff --git a/frontend/src/components/text/RichText.tsx b/frontend/src/components/text/RichText.tsx index 497c6fd8..d5802eda 100644 --- a/frontend/src/components/text/RichText.tsx +++ b/frontend/src/components/text/RichText.tsx @@ -1,72 +1,173 @@ -import './css/quill.snow.css'; -import './css/quill-styles.css'; +import 'katex/dist/katex.min.css'; +import 'reactjs-tiptap-editor/style.css'; -import { Field, Label } from '@headlessui/react'; -import ReactQuill from 'react-quill-new'; +import { useRef, useState } from 'react'; +import RcTiptapEditor, { + Attachment, + BaseKit, + Blockquote, + Bold, + BulletList, + Clear, + Code, + CodeBlock, + Color, + ColumnActionButton, + Emoji, + Excalidraw, + FontFamily, + FontSize, + FormatPainter, + Heading, + Highlight, + History, + Iframe, + Image, + Indent, + Italic, + Katex, + Link, + Mention, + MoreMark, + OrderedList, + SearchAndReplace, + SlashCommand, + Strike, + Table, + TableOfContents, + TaskList, + Underline, +} from 'reactjs-tiptap-editor'; -type RichTextEditorProps = { +const imagesUrl = import.meta.env.VITE_API_URL + '/api/images'; + +const extensions = [ + BaseKit.configure({ + multiColumn: true, + placeholder: { + showOnlyCurrent: true, + }, + characterCount: { + limit: 50_000, + }, + }), + History, + SearchAndReplace, + TableOfContents, + FormatPainter.configure({ spacer: true }), + Clear, + FontFamily, + Heading.configure({ spacer: true }), + FontSize, + Bold, + Italic, + Underline, + Strike, + MoreMark, + Katex, + Emoji, + Color.configure({ spacer: true }), + Highlight, + BulletList, + OrderedList, + Indent, + TaskList.configure({ + spacer: true, + taskItem: { + nested: true, + }, + }), + Link, + Image.configure({ + upload: async (file: File) => { + // eslint-disable-next-line sonarjs/prefer-immediate-return -- we need to wait for the file to be read + const base64Value = await new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.readAsDataURL(file); + reader.onload = () => { + if (reader.result) { + resolve(reader.result.toString()); + } else { + reject(new Error('File reading failed')); + } + }; + reader.onerror = error => reject(error); + }); + return base64Value; + }, + }), + Blockquote.configure({ spacer: true }), + SlashCommand, + Code.configure({ + toolbar: false, + }), + CodeBlock.configure({ defaultTheme: 'dracula' }), + ColumnActionButton, + Table, + Iframe, + Excalidraw, + Mention, + Attachment.configure({ + upload: async (file: File) => { + const formData = new FormData(); + formData.append('value', await file.text()); + const response = await fetch(imagesUrl, { + method: 'POST', + body: formData, + }); + const data = await response.json(); + return data.value; + }, + }), +]; + +function debounce(func: (value: string) => void, wait: number) { + let timeout: NodeJS.Timeout; + return function (this: unknown, ...args: [string]) { + clearTimeout(timeout); + + timeout = setTimeout(() => func.apply(this, args), wait); + }; +} + +type RichTextProps = { label: string; - value: string; + onChange: (value: string) => void; placeholder: string; - onChange: (content: string) => void; + value: string; }; -const RichText: React.FC = ({ - label, - value, - placeholder, - onChange, -}) => { - const modules = { - toolbar: [ - [{ header: [1, 2, 3, false] }, { font: [] }], - [{ size: [] }], - ['bold', 'italic', 'underline', 'strike', 'blockquote'], - [ - { list: 'ordered' }, - { list: 'bullet' }, - { indent: '-1' }, - { indent: '+1' }, - ], - ['link', 'image', 'code-block'], - ['clean'], - ], - clipboard: { - matchVisual: false, - }, - }; +const RichText = ({ label, onChange, placeholder, value }: RichTextProps) => { + const [content, setContent] = useState(value || placeholder); + const refEditor = useRef(null); + + const disable = false; + + const onValueChange = debounce((value: string) => { + onChange(value); + setContent(value); + }, 300); return ( -
- - - onChange(value)} - placeholder={placeholder} - theme="snow" - value={value} +
+

{label}

+
+ - -
+
+ ); }; + export default RichText; diff --git a/frontend/src/components/text/css/RichText.css b/frontend/src/components/text/css/RichText.css new file mode 100644 index 00000000..3cb16e31 --- /dev/null +++ b/frontend/src/components/text/css/RichText.css @@ -0,0 +1,146 @@ +.editor { + outline: none; +} + +.editor-toolbar { + display: flex; + gap: 8px; + margin-bottom: 8px; +} + +.editor__content { + overflow-wrap: break-word; + word-wrap: break-word; + word-break: break-word; +} + +.ProseMirror { + min-height: 200px; + cursor: auto; +} + +h1 { + font-size: 4.25rem; +} + +pre { + padding: 0.7rem 1rem; + border-radius: 5px; + background: black; + color: white; + font-size: 0.8rem; + overflow-x: auto; + white-space: pre-wrap; +} + +p code { + padding: 0.2rem 0.4rem; + border-radius: 5px; + font-size: 0.8rem; + font-weight: bold; + background: rgba(black, 0.1); + color: rgba(black, 0.8); +} + +ul, +ol { + padding-left: 1rem; +} + +li > p, +li > ol, +li > ul { + margin: 0; +} + +a { + color: inherit; +} + +blockquote { + border-left: 3px solid rgba(black, 0.1); + color: rgba(black, 0.8); + padding-left: 0.8rem; + font-style: italic; +} + +img { + max-width: 100%; + border-radius: 3px; +} + +.selected { + outline-style: solid; + outline-color: blue; +} + +table { + border-collapse: collapse; + table-layout: fixed; + width: 100%; + margin: 0; + overflow: hidden; +} + +td, th { + min-width: 1em; + border: 2px solid grey; + padding: 3px 5px; + vertical-align: top; + box-sizing: border-box; + position: relative; +} + +th { + font-weight: bold; + text-align: left; +} + +.selectedCell:after { + z-index: 2; + position: absolute; + content: ""; + left: 0; right: 0; top: 0; bottom: 0; + background: rgba(200, 200, 255, 0.4); + pointer-events: none; +} + +.column-resize-handle { + position: absolute; + right: -2px; top: 0; bottom: 0; + width: 4px; + z-index: 20; + background-color: #adf; + pointer-events: none; +} + +.tableWrapper { + margin: 1em 0; + overflow-x: auto; +} + +.resize-cursor { + cursor: ew-resize; + cursor: col-resize; +} + +.is-active { + color: green; +} + +.is-active-highlight { + background-color: grey; +} + +.diffrem { + background-color: #fdb8c0; +} + +.diffadd { + background-color: #acf2bd; +} + +.text-negative .editor:not(.q-dark) { + color: var(--q-color-primary) !important; +} + diff --git a/frontend/src/components/text/css/quill-styles.css b/frontend/src/components/text/css/quill-styles.css deleted file mode 100644 index 32211722..00000000 --- a/frontend/src/components/text/css/quill-styles.css +++ /dev/null @@ -1,4 +0,0 @@ -.ql-editor { - min-height: 10em; - max-height: 15em; -} diff --git a/frontend/src/components/text/css/quill.snow.css b/frontend/src/components/text/css/quill.snow.css deleted file mode 100644 index ee39fe27..00000000 --- a/frontend/src/components/text/css/quill.snow.css +++ /dev/null @@ -1,969 +0,0 @@ -/*! - * Quill Editor v1.3.7 - * https://quilljs.com/ - * Copyright (c) 2014, Jason Chen - * Copyright (c) 2013, salesforce.com - */ -.ql-container { - box-sizing: border-box; - font-family: Helvetica, Arial, sans-serif; - font-size: 13px; - height: 100%; - margin: 0px; - position: relative; -} -.ql-container.ql-disabled .ql-tooltip { - visibility: hidden; -} -.ql-container.ql-disabled .ql-editor ul[data-checked] > li::before { - pointer-events: none; -} -.ql-clipboard { - left: -100000px; - height: 1px; - overflow-y: hidden; - position: absolute; - top: 50%; -} -.ql-clipboard p { - margin: 0; - padding: 0; -} -.ql-editor { - box-sizing: border-box; - line-height: 1.42; - height: 100%; - outline: none; - overflow-y: auto; - padding: 12px 15px; - tab-size: 4; - -moz-tab-size: 4; - text-align: left; - white-space: pre-wrap; - word-wrap: break-word; -} -.ql-editor > * { - cursor: text; -} -.ql-editor p, -.ql-editor ol, -.ql-editor ul, -.ql-editor pre, -.ql-editor blockquote, -.ql-editor h1, -.ql-editor h2, -.ql-editor h3, -.ql-editor h4, -.ql-editor h5, -.ql-editor h6 { - margin: 0; - padding: 0; - counter-reset: list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9; -} -.ql-editor ol, -.ql-editor ul { - padding-left: 1.5em; -} -.ql-editor ol > li, -.ql-editor ul > li { - list-style-type: none; -} -.ql-editor ul > li::before { - content: '\2022'; -} -.ql-editor ul[data-checked='true'], -.ql-editor ul[data-checked='false'] { - pointer-events: none; -} -.ql-editor ul[data-checked='true'] > li *, -.ql-editor ul[data-checked='false'] > li * { - pointer-events: all; -} -.ql-editor ul[data-checked='true'] > li::before, -.ql-editor ul[data-checked='false'] > li::before { - color: #777; - cursor: pointer; - pointer-events: all; -} -.ql-editor ul[data-checked='true'] > li::before { - content: '\2611'; -} -.ql-editor ul[data-checked='false'] > li::before { - content: '\2610'; -} -.ql-editor li::before { - display: inline-block; - white-space: nowrap; - width: 1.2em; -} -.ql-editor li:not(.ql-direction-rtl)::before { - margin-left: -1.5em; - margin-right: 0.3em; - text-align: right; -} -.ql-editor li.ql-direction-rtl::before { - margin-left: 0.3em; - margin-right: -1.5em; -} -.ql-editor ol li:not(.ql-direction-rtl), -.ql-editor ul li:not(.ql-direction-rtl) { - padding-left: 1.5em; -} -.ql-editor ol li.ql-direction-rtl, -.ql-editor ul li.ql-direction-rtl { - padding-right: 1.5em; -} -.ql-editor ol li { - counter-reset: list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9; - counter-increment: list-0; -} -.ql-editor ol li:before { - content: counter(list-0, decimal) '. '; -} -.ql-editor ol li.ql-indent-1 { - counter-increment: list-1; -} -.ql-editor ol li.ql-indent-1:before { - content: counter(list-1, lower-alpha) '. '; -} -.ql-editor ol li.ql-indent-1 { - counter-reset: list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9; -} -.ql-editor ol li.ql-indent-2 { - counter-increment: list-2; -} -.ql-editor ol li.ql-indent-2:before { - content: counter(list-2, lower-roman) '. '; -} -.ql-editor ol li.ql-indent-2 { - counter-reset: list-3 list-4 list-5 list-6 list-7 list-8 list-9; -} -.ql-editor ol li.ql-indent-3 { - counter-increment: list-3; -} -.ql-editor ol li.ql-indent-3:before { - content: counter(list-3, decimal) '. '; -} -.ql-editor ol li.ql-indent-3 { - counter-reset: list-4 list-5 list-6 list-7 list-8 list-9; -} -.ql-editor ol li.ql-indent-4 { - counter-increment: list-4; -} -.ql-editor ol li.ql-indent-4:before { - content: counter(list-4, lower-alpha) '. '; -} -.ql-editor ol li.ql-indent-4 { - counter-reset: list-5 list-6 list-7 list-8 list-9; -} -.ql-editor ol li.ql-indent-5 { - counter-increment: list-5; -} -.ql-editor ol li.ql-indent-5:before { - content: counter(list-5, lower-roman) '. '; -} -.ql-editor ol li.ql-indent-5 { - counter-reset: list-6 list-7 list-8 list-9; -} -.ql-editor ol li.ql-indent-6 { - counter-increment: list-6; -} -.ql-editor ol li.ql-indent-6:before { - content: counter(list-6, decimal) '. '; -} -.ql-editor ol li.ql-indent-6 { - counter-reset: list-7 list-8 list-9; -} -.ql-editor ol li.ql-indent-7 { - counter-increment: list-7; -} -.ql-editor ol li.ql-indent-7:before { - content: counter(list-7, lower-alpha) '. '; -} -.ql-editor ol li.ql-indent-7 { - counter-reset: list-8 list-9; -} -.ql-editor ol li.ql-indent-8 { - counter-increment: list-8; -} -.ql-editor ol li.ql-indent-8:before { - content: counter(list-8, lower-roman) '. '; -} -.ql-editor ol li.ql-indent-8 { - counter-reset: list-9; -} -.ql-editor ol li.ql-indent-9 { - counter-increment: list-9; -} -.ql-editor ol li.ql-indent-9:before { - content: counter(list-9, decimal) '. '; -} -.ql-editor .ql-indent-1:not(.ql-direction-rtl) { - padding-left: 3em; -} -.ql-editor li.ql-indent-1:not(.ql-direction-rtl) { - padding-left: 4.5em; -} -.ql-editor .ql-indent-1.ql-direction-rtl.ql-align-right { - padding-right: 3em; -} -.ql-editor li.ql-indent-1.ql-direction-rtl.ql-align-right { - padding-right: 4.5em; -} -.ql-editor .ql-indent-2:not(.ql-direction-rtl) { - padding-left: 6em; -} -.ql-editor li.ql-indent-2:not(.ql-direction-rtl) { - padding-left: 7.5em; -} -.ql-editor .ql-indent-2.ql-direction-rtl.ql-align-right { - padding-right: 6em; -} -.ql-editor li.ql-indent-2.ql-direction-rtl.ql-align-right { - padding-right: 7.5em; -} -.ql-editor .ql-indent-3:not(.ql-direction-rtl) { - padding-left: 9em; -} -.ql-editor li.ql-indent-3:not(.ql-direction-rtl) { - padding-left: 10.5em; -} -.ql-editor .ql-indent-3.ql-direction-rtl.ql-align-right { - padding-right: 9em; -} -.ql-editor li.ql-indent-3.ql-direction-rtl.ql-align-right { - padding-right: 10.5em; -} -.ql-editor .ql-indent-4:not(.ql-direction-rtl) { - padding-left: 12em; -} -.ql-editor li.ql-indent-4:not(.ql-direction-rtl) { - padding-left: 13.5em; -} -.ql-editor .ql-indent-4.ql-direction-rtl.ql-align-right { - padding-right: 12em; -} -.ql-editor li.ql-indent-4.ql-direction-rtl.ql-align-right { - padding-right: 13.5em; -} -.ql-editor .ql-indent-5:not(.ql-direction-rtl) { - padding-left: 15em; -} -.ql-editor li.ql-indent-5:not(.ql-direction-rtl) { - padding-left: 16.5em; -} -.ql-editor .ql-indent-5.ql-direction-rtl.ql-align-right { - padding-right: 15em; -} -.ql-editor li.ql-indent-5.ql-direction-rtl.ql-align-right { - padding-right: 16.5em; -} -.ql-editor .ql-indent-6:not(.ql-direction-rtl) { - padding-left: 18em; -} -.ql-editor li.ql-indent-6:not(.ql-direction-rtl) { - padding-left: 19.5em; -} -.ql-editor .ql-indent-6.ql-direction-rtl.ql-align-right { - padding-right: 18em; -} -.ql-editor li.ql-indent-6.ql-direction-rtl.ql-align-right { - padding-right: 19.5em; -} -.ql-editor .ql-indent-7:not(.ql-direction-rtl) { - padding-left: 21em; -} -.ql-editor li.ql-indent-7:not(.ql-direction-rtl) { - padding-left: 22.5em; -} -.ql-editor .ql-indent-7.ql-direction-rtl.ql-align-right { - padding-right: 21em; -} -.ql-editor li.ql-indent-7.ql-direction-rtl.ql-align-right { - padding-right: 22.5em; -} -.ql-editor .ql-indent-8:not(.ql-direction-rtl) { - padding-left: 24em; -} -.ql-editor li.ql-indent-8:not(.ql-direction-rtl) { - padding-left: 25.5em; -} -.ql-editor .ql-indent-8.ql-direction-rtl.ql-align-right { - padding-right: 24em; -} -.ql-editor li.ql-indent-8.ql-direction-rtl.ql-align-right { - padding-right: 25.5em; -} -.ql-editor .ql-indent-9:not(.ql-direction-rtl) { - padding-left: 27em; -} -.ql-editor li.ql-indent-9:not(.ql-direction-rtl) { - padding-left: 28.5em; -} -.ql-editor .ql-indent-9.ql-direction-rtl.ql-align-right { - padding-right: 27em; -} -.ql-editor li.ql-indent-9.ql-direction-rtl.ql-align-right { - padding-right: 28.5em; -} -.ql-editor .ql-video { - display: block; - max-width: 100%; -} -.ql-editor .ql-video.ql-align-center { - margin: 0 auto; -} -.ql-editor .ql-video.ql-align-right { - margin: 0 0 0 auto; -} -.ql-editor .ql-bg-black { - background-color: #000; -} -.ql-editor .ql-bg-red { - background-color: #e60000; -} -.ql-editor .ql-bg-orange { - background-color: #f90; -} -.ql-editor .ql-bg-yellow { - background-color: #ff0; -} -.ql-editor .ql-bg-green { - background-color: #008a00; -} -.ql-editor .ql-bg-blue { - background-color: #06c; -} -.ql-editor .ql-bg-purple { - background-color: #93f; -} -.ql-editor .ql-color-white { - color: #fff; -} -.ql-editor .ql-color-red { - color: #e60000; -} -.ql-editor .ql-color-orange { - color: #f90; -} -.ql-editor .ql-color-yellow { - color: #ff0; -} -.ql-editor .ql-color-green { - color: #008a00; -} -.ql-editor .ql-color-blue { - color: #06c; -} -.ql-editor .ql-color-purple { - color: #93f; -} -.ql-editor .ql-font-serif { - font-family: - Georgia, - Times New Roman, - serif; -} -.ql-editor .ql-font-monospace { - font-family: - Monaco, - Courier New, - monospace; -} -.ql-editor .ql-size-small { - font-size: 0.75em; -} -.ql-editor .ql-size-large { - font-size: 1.5em; -} -.ql-editor .ql-size-huge { - font-size: 2.5em; -} -.ql-editor .ql-direction-rtl { - direction: rtl; - text-align: inherit; -} -.ql-editor .ql-align-center { - text-align: center; -} -.ql-editor .ql-align-justify { - text-align: justify; -} -.ql-editor .ql-align-right { - text-align: right; -} -.ql-editor.ql-blank::before { - color: rgba(0, 0, 0, 0.6); - content: attr(data-placeholder); - font-style: italic; - left: 15px; - pointer-events: none; - position: absolute; - right: 15px; -} -.ql-snow.ql-toolbar:after, -.ql-snow .ql-toolbar:after { - clear: both; - content: ''; - display: table; -} -.ql-snow.ql-toolbar button, -.ql-snow .ql-toolbar button { - background: none; - border: none; - cursor: pointer; - display: inline-block; - float: left; - height: 24px; - padding: 3px 5px; - width: 28px; -} -.ql-snow.ql-toolbar button svg, -.ql-snow .ql-toolbar button svg { - float: left; - height: 100%; -} -.ql-snow.ql-toolbar button:active:hover, -.ql-snow .ql-toolbar button:active:hover { - outline: none; -} -.ql-snow.ql-toolbar input.ql-image[type='file'], -.ql-snow .ql-toolbar input.ql-image[type='file'] { - display: none; -} -.ql-snow.ql-toolbar button:hover, -.ql-snow .ql-toolbar button:hover, -.ql-snow.ql-toolbar button:focus, -.ql-snow .ql-toolbar button:focus, -.ql-snow.ql-toolbar button.ql-active, -.ql-snow .ql-toolbar button.ql-active, -.ql-snow.ql-toolbar .ql-picker-label:hover, -.ql-snow .ql-toolbar .ql-picker-label:hover, -.ql-snow.ql-toolbar .ql-picker-label.ql-active, -.ql-snow .ql-toolbar .ql-picker-label.ql-active, -.ql-snow.ql-toolbar .ql-picker-item:hover, -.ql-snow .ql-toolbar .ql-picker-item:hover, -.ql-snow.ql-toolbar .ql-picker-item.ql-selected, -.ql-snow .ql-toolbar .ql-picker-item.ql-selected { - color: #06c; -} -.ql-snow.ql-toolbar button:hover .ql-fill, -.ql-snow .ql-toolbar button:hover .ql-fill, -.ql-snow.ql-toolbar button:focus .ql-fill, -.ql-snow .ql-toolbar button:focus .ql-fill, -.ql-snow.ql-toolbar button.ql-active .ql-fill, -.ql-snow .ql-toolbar button.ql-active .ql-fill, -.ql-snow.ql-toolbar .ql-picker-label:hover .ql-fill, -.ql-snow .ql-toolbar .ql-picker-label:hover .ql-fill, -.ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-fill, -.ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-fill, -.ql-snow.ql-toolbar .ql-picker-item:hover .ql-fill, -.ql-snow .ql-toolbar .ql-picker-item:hover .ql-fill, -.ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-fill, -.ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-fill, -.ql-snow.ql-toolbar button:hover .ql-stroke.ql-fill, -.ql-snow .ql-toolbar button:hover .ql-stroke.ql-fill, -.ql-snow.ql-toolbar button:focus .ql-stroke.ql-fill, -.ql-snow .ql-toolbar button:focus .ql-stroke.ql-fill, -.ql-snow.ql-toolbar button.ql-active .ql-stroke.ql-fill, -.ql-snow .ql-toolbar button.ql-active .ql-stroke.ql-fill, -.ql-snow.ql-toolbar .ql-picker-label:hover .ql-stroke.ql-fill, -.ql-snow .ql-toolbar .ql-picker-label:hover .ql-stroke.ql-fill, -.ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-stroke.ql-fill, -.ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-stroke.ql-fill, -.ql-snow.ql-toolbar .ql-picker-item:hover .ql-stroke.ql-fill, -.ql-snow .ql-toolbar .ql-picker-item:hover .ql-stroke.ql-fill, -.ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-stroke.ql-fill, -.ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-stroke.ql-fill { - fill: #06c; -} -.ql-snow.ql-toolbar button:hover .ql-stroke, -.ql-snow .ql-toolbar button:hover .ql-stroke, -.ql-snow.ql-toolbar button:focus .ql-stroke, -.ql-snow .ql-toolbar button:focus .ql-stroke, -.ql-snow.ql-toolbar button.ql-active .ql-stroke, -.ql-snow .ql-toolbar button.ql-active .ql-stroke, -.ql-snow.ql-toolbar .ql-picker-label:hover .ql-stroke, -.ql-snow .ql-toolbar .ql-picker-label:hover .ql-stroke, -.ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-stroke, -.ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-stroke, -.ql-snow.ql-toolbar .ql-picker-item:hover .ql-stroke, -.ql-snow .ql-toolbar .ql-picker-item:hover .ql-stroke, -.ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-stroke, -.ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-stroke, -.ql-snow.ql-toolbar button:hover .ql-stroke-miter, -.ql-snow .ql-toolbar button:hover .ql-stroke-miter, -.ql-snow.ql-toolbar button:focus .ql-stroke-miter, -.ql-snow .ql-toolbar button:focus .ql-stroke-miter, -.ql-snow.ql-toolbar button.ql-active .ql-stroke-miter, -.ql-snow .ql-toolbar button.ql-active .ql-stroke-miter, -.ql-snow.ql-toolbar .ql-picker-label:hover .ql-stroke-miter, -.ql-snow .ql-toolbar .ql-picker-label:hover .ql-stroke-miter, -.ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-stroke-miter, -.ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-stroke-miter, -.ql-snow.ql-toolbar .ql-picker-item:hover .ql-stroke-miter, -.ql-snow .ql-toolbar .ql-picker-item:hover .ql-stroke-miter, -.ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-stroke-miter, -.ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-stroke-miter { - stroke: #06c; -} -@media (pointer: coarse) { - .ql-snow.ql-toolbar button:hover:not(.ql-active), - .ql-snow .ql-toolbar button:hover:not(.ql-active) { - color: #444; - } - .ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-fill, - .ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-fill, - .ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-stroke.ql-fill, - .ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-stroke.ql-fill { - fill: #444; - } - .ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-stroke, - .ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-stroke, - .ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-stroke-miter, - .ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-stroke-miter { - stroke: #444; - } -} -.ql-snow { - box-sizing: border-box; -} -.ql-snow * { - box-sizing: border-box; -} -.ql-snow .ql-hidden { - display: none; -} -.ql-snow .ql-out-bottom, -.ql-snow .ql-out-top { - visibility: hidden; -} -.ql-snow .ql-tooltip { - position: absolute; - transform: translateY(10px); -} -.ql-snow .ql-tooltip a { - cursor: pointer; - text-decoration: none; -} -.ql-snow .ql-tooltip.ql-flip { - transform: translateY(-10px); -} -.ql-snow .ql-formats { - display: inline-block; - vertical-align: middle; -} -.ql-snow .ql-formats:after { - clear: both; - content: ''; - display: table; -} -.ql-snow .ql-stroke { - fill: none; - stroke: #444; - stroke-linecap: round; - stroke-linejoin: round; - stroke-width: 2; -} -.ql-snow .ql-stroke-miter { - fill: none; - stroke: #444; - stroke-miterlimit: 10; - stroke-width: 2; -} -.ql-snow .ql-fill, -.ql-snow .ql-stroke.ql-fill { - fill: #444; -} -.ql-snow .ql-empty { - fill: none; -} -.ql-snow .ql-even { - fill-rule: evenodd; -} -.ql-snow .ql-thin, -.ql-snow .ql-stroke.ql-thin { - stroke-width: 1; -} -.ql-snow .ql-transparent { - opacity: 0.4; -} -.ql-snow .ql-direction svg:last-child { - display: none; -} -.ql-snow .ql-direction.ql-active svg:last-child { - display: inline; -} -.ql-snow .ql-direction.ql-active svg:first-child { - display: none; -} -.ql-snow .ql-editor h1 { - font-size: 2em; -} -.ql-snow .ql-editor h2 { - font-size: 1.5em; -} -.ql-snow .ql-editor h3 { - font-size: 1.17em; -} -.ql-snow .ql-editor h4 { - font-size: 1em; -} -.ql-snow .ql-editor h5 { - font-size: 0.83em; -} -.ql-snow .ql-editor h6 { - font-size: 0.67em; -} -.ql-snow .ql-editor a { - text-decoration: underline; -} -.ql-snow .ql-editor blockquote { - border-left: 4px solid #ccc; - margin-bottom: 5px; - margin-top: 5px; - padding-left: 16px; -} -.ql-snow .ql-editor code, -.ql-snow .ql-editor pre { - background-color: #f0f0f0; - border-radius: 3px; -} -.ql-snow .ql-editor pre { - white-space: pre-wrap; - margin-bottom: 5px; - margin-top: 5px; - padding: 5px 10px; -} -.ql-snow .ql-editor code { - font-size: 85%; - padding: 2px 4px; -} -.ql-snow .ql-editor pre.ql-syntax { - background-color: #23241f; - color: #f8f8f2; - overflow: visible; -} -.ql-snow .ql-editor img { - max-width: 100%; -} -.ql-snow .ql-picker { - color: #444; - display: inline-block; - float: left; - font-size: 14px; - font-weight: 500; - height: 24px; - position: relative; - vertical-align: middle; -} -.ql-snow .ql-picker-label { - cursor: pointer; - display: inline-block; - height: 100%; - padding-left: 8px; - padding-right: 2px; - position: relative; - width: 100%; -} -.ql-snow .ql-picker-label::before { - display: inline-block; - line-height: 22px; -} -.ql-snow .ql-picker-options { - background-color: #fff; - display: none; - min-width: 100%; - padding: 4px 8px; - position: absolute; - white-space: nowrap; -} -.ql-snow .ql-picker-options .ql-picker-item { - cursor: pointer; - display: block; - padding-bottom: 5px; - padding-top: 5px; -} -.ql-snow .ql-picker.ql-expanded .ql-picker-label { - color: #ccc; - z-index: 2; -} -.ql-snow .ql-picker.ql-expanded .ql-picker-label .ql-fill { - fill: #ccc; -} -.ql-snow .ql-picker.ql-expanded .ql-picker-label .ql-stroke { - stroke: #ccc; -} -.ql-snow .ql-picker.ql-expanded .ql-picker-options { - display: block; - margin-top: -1px; - top: 100%; - z-index: 1; -} -.ql-snow .ql-color-picker, -.ql-snow .ql-icon-picker { - width: 28px; -} -.ql-snow .ql-color-picker .ql-picker-label, -.ql-snow .ql-icon-picker .ql-picker-label { - padding: 2px 4px; -} -.ql-snow .ql-color-picker .ql-picker-label svg, -.ql-snow .ql-icon-picker .ql-picker-label svg { - right: 4px; -} -.ql-snow .ql-icon-picker .ql-picker-options { - padding: 4px 0px; -} -.ql-snow .ql-icon-picker .ql-picker-item { - height: 24px; - width: 24px; - padding: 2px 4px; -} -.ql-snow .ql-color-picker .ql-picker-options { - padding: 3px 5px; - width: 152px; -} -.ql-snow .ql-color-picker .ql-picker-item { - border: 1px solid transparent; - float: left; - height: 16px; - margin: 2px; - padding: 0px; - width: 16px; -} -.ql-snow .ql-picker:not(.ql-color-picker):not(.ql-icon-picker) svg { - position: absolute; - margin-top: -9px; - right: 0; - top: 50%; - width: 18px; -} -.ql-snow - .ql-picker.ql-header - .ql-picker-label[data-label]:not([data-label=''])::before, -.ql-snow - .ql-picker.ql-font - .ql-picker-label[data-label]:not([data-label=''])::before, -.ql-snow - .ql-picker.ql-size - .ql-picker-label[data-label]:not([data-label=''])::before, -.ql-snow - .ql-picker.ql-header - .ql-picker-item[data-label]:not([data-label=''])::before, -.ql-snow - .ql-picker.ql-font - .ql-picker-item[data-label]:not([data-label=''])::before, -.ql-snow - .ql-picker.ql-size - .ql-picker-item[data-label]:not([data-label=''])::before { - content: attr(data-label); -} -.ql-snow .ql-picker.ql-header { - width: 98px; -} -.ql-snow .ql-picker.ql-header .ql-picker-label::before, -.ql-snow .ql-picker.ql-header .ql-picker-item::before { - content: 'Normal'; -} -.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='1']::before, -.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='1']::before { - content: 'Heading 1'; -} -.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='2']::before, -.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='2']::before { - content: 'Heading 2'; -} -.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='3']::before, -.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='3']::before { - content: 'Heading 3'; -} -.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='4']::before, -.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='4']::before { - content: 'Heading 4'; -} -.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='5']::before, -.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='5']::before { - content: 'Heading 5'; -} -.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='6']::before, -.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='6']::before { - content: 'Heading 6'; -} -.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='1']::before { - font-size: 2em; -} -.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='2']::before { - font-size: 1.5em; -} -.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='3']::before { - font-size: 1.17em; -} -.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='4']::before { - font-size: 1em; -} -.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='5']::before { - font-size: 0.83em; -} -.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='6']::before { - font-size: 0.67em; -} -.ql-snow .ql-picker.ql-font { - width: 108px; -} -.ql-snow .ql-picker.ql-font .ql-picker-label::before, -.ql-snow .ql-picker.ql-font .ql-picker-item::before { - content: 'Sans Serif'; -} -.ql-snow .ql-picker.ql-font .ql-picker-label[data-value='serif']::before, -.ql-snow .ql-picker.ql-font .ql-picker-item[data-value='serif']::before { - content: 'Serif'; -} -.ql-snow .ql-picker.ql-font .ql-picker-label[data-value='monospace']::before, -.ql-snow .ql-picker.ql-font .ql-picker-item[data-value='monospace']::before { - content: 'Monospace'; -} -.ql-snow .ql-picker.ql-font .ql-picker-item[data-value='serif']::before { - font-family: - Georgia, - Times New Roman, - serif; -} -.ql-snow .ql-picker.ql-font .ql-picker-item[data-value='monospace']::before { - font-family: - Monaco, - Courier New, - monospace; -} -.ql-snow .ql-picker.ql-size { - width: 98px; -} -.ql-snow .ql-picker.ql-size .ql-picker-label::before, -.ql-snow .ql-picker.ql-size .ql-picker-item::before { - content: 'Normal'; -} -.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='small']::before, -.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='small']::before { - content: 'Small'; -} -.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='large']::before, -.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='large']::before { - content: 'Large'; -} -.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='huge']::before, -.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='huge']::before { - content: 'Huge'; -} -.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='small']::before { - font-size: 10px; -} -.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='large']::before { - font-size: 18px; -} -.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='huge']::before { - font-size: 32px; -} -.ql-snow .ql-color-picker.ql-background .ql-picker-item { - background-color: #fff; -} -.ql-snow .ql-color-picker.ql-color .ql-picker-item { - background-color: #000; -} -.ql-toolbar.ql-snow { - border: 1px solid #ccc; - box-sizing: border-box; - font-family: 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif; - padding: 8px; -} -.ql-toolbar.ql-snow .ql-formats { - margin-right: 15px; -} -.ql-toolbar.ql-snow .ql-picker-label { - border: 1px solid transparent; -} -.ql-toolbar.ql-snow .ql-picker-options { - border: 1px solid transparent; - box-shadow: rgba(0, 0, 0, 0.2) 0 2px 8px; -} -.ql-toolbar.ql-snow .ql-picker.ql-expanded .ql-picker-label { - border-color: #ccc; -} -.ql-toolbar.ql-snow .ql-picker.ql-expanded .ql-picker-options { - border-color: #ccc; -} -.ql-toolbar.ql-snow .ql-color-picker .ql-picker-item.ql-selected, -.ql-toolbar.ql-snow .ql-color-picker .ql-picker-item:hover { - border-color: #000; -} -.ql-toolbar.ql-snow + .ql-container.ql-snow { - border-top: 0px; -} -.ql-snow .ql-tooltip { - background-color: #fff; - border: 1px solid #ccc; - box-shadow: 0px 0px 5px #ddd; - color: #444; - padding: 5px 12px; - white-space: nowrap; -} -.ql-snow .ql-tooltip::before { - content: 'Visit URL:'; - line-height: 26px; - margin-right: 8px; -} -.ql-snow .ql-tooltip input[type='text'] { - display: none; - border: 1px solid #ccc; - font-size: 13px; - height: 26px; - margin: 0px; - padding: 3px 5px; - width: 170px; -} -.ql-snow .ql-tooltip a.ql-preview { - display: inline-block; - max-width: 200px; - overflow-x: hidden; - text-overflow: ellipsis; - vertical-align: top; -} -.ql-snow .ql-tooltip a.ql-action::after { - border-right: 1px solid #ccc; - content: 'Edit'; - margin-left: 16px; - padding-right: 8px; -} -.ql-snow .ql-tooltip a.ql-remove::before { - content: 'Remove'; - margin-left: 8px; -} -.ql-snow .ql-tooltip a { - line-height: 26px; -} -.ql-snow .ql-tooltip.ql-editing a.ql-preview, -.ql-snow .ql-tooltip.ql-editing a.ql-remove { - display: none; -} -.ql-snow .ql-tooltip.ql-editing input[type='text'] { - display: inline-block; -} -.ql-snow .ql-tooltip.ql-editing a.ql-action::after { - border-right: 0px; - content: 'Save'; - padding-right: 0px; -} -.ql-snow .ql-tooltip[data-mode='link']::before { - content: 'Enter link:'; -} -.ql-snow .ql-tooltip[data-mode='formula']::before { - content: 'Enter formula:'; -} -.ql-snow .ql-tooltip[data-mode='video']::before { - content: 'Enter video:'; -} -.ql-snow a { - color: #06c; -} -.ql-container.ql-snow { - border: 1px solid #ccc; -} diff --git a/frontend/src/hooks/useAuth.tsx b/frontend/src/hooks/useAuth.tsx index 9f4319d2..1e675e07 100644 --- a/frontend/src/hooks/useAuth.tsx +++ b/frontend/src/hooks/useAuth.tsx @@ -4,6 +4,7 @@ import { useNavigate } from 'react-router-dom'; const tokenUrl = `${import.meta.env.VITE_API_URL}/api/users/token`; const checktokenUrl = `${import.meta.env.VITE_API_URL}/api/users/checktoken`; const refreshTokenUrl = `${import.meta.env.VITE_API_URL}/api/users/refreshtoken`; +const initUrl = `${import.meta.env.VITE_API_URL}/api/users/init`; export const checktoken = async (): Promise => { try { @@ -105,7 +106,7 @@ const useAuth = () => { }); try { - const response = await fetch('/api/users/init', { + const response = await fetch(initUrl, { method: 'POST', headers: { 'Content-Type': 'application/json',