diff --git a/.github/workflows/build-test-pull-request.yml b/.github/workflows/build-test-pull-request.yml index 6dc7ae1e5e5..c74975f48ef 100644 --- a/.github/workflows/build-test-pull-request.yml +++ b/.github/workflows/build-test-pull-request.yml @@ -36,15 +36,13 @@ jobs: - name: Build Plane's Main App if: steps.changed-files.outputs.web_any_changed == 'true' run: | - cd web yarn - yarn build + yarn build --filter=web - name: Build Plane's Deploy App if: steps.changed-files.outputs.deploy_any_changed == 'true' run: | - cd space yarn - yarn build + yarn build --filter=space diff --git a/.gitignore b/.gitignore index 1e99e102ad5..9439ea3acad 100644 --- a/.gitignore +++ b/.gitignore @@ -73,3 +73,7 @@ pnpm-lock.yaml pnpm-workspace.yaml .npmrc + + +## packages +dist diff --git a/package.json b/package.json index 1f2f9641448..ba0fd9a6119 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,11 @@ "workspaces": [ "web", "space", - "packages/*" + "packages/editor/*", + "packages/eslint-config-custom", + "packages/tailwind-config-custom", + "packages/tsconfig", + "packages/ui" ], "scripts": { "build": "turbo run build", @@ -25,5 +29,8 @@ "tailwindcss": "^3.3.3", "turbo": "latest" }, + "resolutions": { + "@types/react": "18.2.0" + }, "packageManager": "yarn@1.22.19" } diff --git a/packages/editor/core/Readme.md b/packages/editor/core/Readme.md new file mode 100644 index 00000000000..56d1a502c79 --- /dev/null +++ b/packages/editor/core/Readme.md @@ -0,0 +1,112 @@ +# @plane/editor-core + +## Description + +The `@plane/editor-core` package serves as the foundation for our editor system. It provides the base functionality for our other editor packages, but it will not be used directly in any of the projects but only for extending other editors. + +## Utilities + +We provide a wide range of utilities for extending the core itself. + +1. Merging classes and custom styling +2. Adding new extensions +3. Adding custom props +4. Base menu items, and their commands + +This allows for extensive customization and flexibility in the Editors created using our `editor-core` package. + +### Here's a detailed overview of what's exported + +1. useEditor - A hook that you can use to extend the Plane editor. + + | Prop | Type | Description | + | --- | --- | --- | + | `extensions` | `Extension[]` | An array of custom extensions you want to add into the editor to extend it's core features | + | `editorProps` | `EditorProps` | Extend the editor props by passing in a custom props object | + | `uploadFile` | `(file: File) => Promise` | A function that handles file upload. It takes a file as input and handles the process of uploading that file. | + | `deleteFile` | `(assetUrlWithWorkspaceId: string) => Promise` | A function that handles deleting an image. It takes the asset url from your bucket and handles the process of deleting that image. | + | `value` | `html string` | The initial content of the editor. | + | `debouncedUpdatesEnabled` | `boolean` | If set to true, the `onChange` event handler is debounced, meaning it will only be invoked after the specified delay (default 1500ms) once the user has stopped typing. | + | `onChange` | `(json: any, html: string) => void` | This function is invoked whenever the content of the editor changes. It is passed the new content in both JSON and HTML formats. | + | `setIsSubmitting` | `(isSubmitting: "submitting" \| "submitted" \| "saved") => void` | This function is called to update the submission status. | + | `setShouldShowAlert` | `(showAlert: boolean) => void` | This function is used to show or hide an alert in case of content not being "saved". | + | `forwardedRef` | `any` | Pass this in whenever you want to control the editor's state from an external component | + +2. useReadOnlyEditor - A hook that can be used to extend a Read Only instance of the core editor. + + | Prop | Type | Description | + | --- | --- | --- | + | `value` | `string` | The initial content of the editor. | + | `forwardedRef` | `any` | Pass this in whenever you want to control the editor's state from an external component | + | `extensions` | `Extension[]` | An array of custom extensions you want to add into the editor to extend it's core features | + | `editorProps` | `EditorProps` | Extend the editor props by passing in a custom props object | + +3. Items and Commands - H1, H2, H3, task list, quote, code block, etc's methods. + +4. UI Wrappers + +- `EditorContainer` - Wrap your Editor Container with this to apply base classes and styles. +- `EditorContentWrapper` - Use this to get Editor's Content and base menus. + +5. Extending with Custom Styles + +```ts +const customEditorClassNames = getEditorClassNames({ noBorder, borderOnFocus, customClassName }); +``` + +## Core features + +- **Content Trimming**: The Editor’s content is now automatically trimmed of empty line breaks from the start and end before submitting it to the backend. This ensures cleaner, more consistent data. +- **Value Cleaning**: The Editor’s value is cleaned at the editor core level, eliminating the need for additional validation before sending from our app. This results in cleaner code and less potential for errors. +- **Turbo Pipeline**: Added a turbo pipeline for both dev and build tasks for projects depending on the editor package. + +```json + "web#develop": { + "cache": false, + "persistent": true, + "dependsOn": [ + "@plane/lite-text-editor#build", + "@plane/rich-text-editor#build" + ] + }, + "space#develop": { + "cache": false, + "persistent": true, + "dependsOn": [ + "@plane/lite-text-editor#build", + "@plane/rich-text-editor#build" + ] + }, + "web#build": { + "cache": true, + "dependsOn": [ + "@plane/lite-text-editor#build", + "@plane/rich-text-editor#build" + ] + }, + "space#build": { + "cache": true, + "dependsOn": [ + "@plane/lite-text-editor#build", + "@plane/rich-text-editor#build" + ] + }, + +``` + +## Base extensions included + +- BulletList +- OrderedList +- Blockquote +- Code +- Gapcursor +- Link +- Image +- Basic Marks + - Underline + - TextStyle + - Color +- TaskList +- Markdown +- Table diff --git a/packages/editor/core/package.json b/packages/editor/core/package.json new file mode 100644 index 00000000000..86921af0cd8 --- /dev/null +++ b/packages/editor/core/package.json @@ -0,0 +1,74 @@ +{ + "name": "@plane/editor-core", + "version": "0.0.1", + "description": "Core Editor that powers Plane", + "main": "./dist/index.mjs", + "module": "./dist/index.mjs", + "types": "./dist/index.d.mts", + "files": [ + "dist/**/*" + ], + "exports": { + ".": { + "types": "./dist/index.d.mts", + "import": "./dist/index.mjs", + "module": "./dist/index.mjs" + } + }, + "scripts": { + "build": "tsup", + "dev": "tsup --watch", + "check-types": "tsc --noEmit" + }, + "peerDependencies": { + "react": "^18.2.0", + "react-dom": "18.2.0", + "next": "12.3.2", + "next-themes": "^0.2.1" + }, + "dependencies": { + "@blueprintjs/popover2": "^2.0.10", + "@tiptap/core": "^2.1.7", + "@tiptap/extension-color": "^2.1.11", + "@tiptap/extension-image": "^2.1.7", + "@tiptap/extension-link": "^2.1.7", + "@tiptap/extension-task-item": "^2.1.7", + "@tiptap/extension-task-list": "^2.1.7", + "@tiptap/extension-text-style": "^2.1.11", + "@tiptap/extension-underline": "^2.1.7", + "@tiptap/pm": "^2.1.7", + "@tiptap/react": "^2.1.7", + "@tiptap/starter-kit": "^2.1.10", + "@types/react": "^18.2.5", + "@types/react-dom": "18.0.11", + "@types/node": "18.15.3", + "class-variance-authority": "^0.7.0", + "clsx": "^1.2.1", + "eslint": "8.36.0", + "eslint-config-next": "13.2.4", + "eventsource-parser": "^0.1.0", + "lucide-react": "^0.244.0", + "react-markdown": "^8.0.7", + "tailwind-merge": "^1.14.0", + "tippy.js": "^6.3.7", + "tiptap-markdown": "^0.8.2", + "use-debounce": "^9.0.4", + "@tiptap/prosemirror-tables": "^1.1.4", + "jsx-dom-cjs": "^8.0.3" + }, + "devDependencies": { + "eslint": "^7.32.0", + "postcss": "^8.4.29", + "tailwind-config-custom": "*", + "tsconfig": "*", + "tsup": "^7.2.0", + "typescript": "4.9.5" + }, + "keywords": [ + "editor", + "rich-text", + "markdown", + "nextjs", + "react" + ] +} diff --git a/packages/editor/core/postcss.config.js b/packages/editor/core/postcss.config.js new file mode 100644 index 00000000000..07aa434b2bf --- /dev/null +++ b/packages/editor/core/postcss.config.js @@ -0,0 +1,9 @@ +// If you want to use other PostCSS plugins, see the following: +// https://tailwindcss.com/docs/using-with-preprocessors + +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/packages/editor/core/src/index.ts b/packages/editor/core/src/index.ts new file mode 100644 index 00000000000..13fb14297d5 --- /dev/null +++ b/packages/editor/core/src/index.ts @@ -0,0 +1,21 @@ +// styles +// import "./styles/tailwind.css"; +// import "./styles/editor.css"; + +export * from "./ui/extensions/table/table"; + +// utils +export * from "./lib/utils"; +export { startImageUpload } from "./ui/plugins/upload-image"; + +// components +export { EditorContainer } from "./ui/components/editor-container"; +export { EditorContentWrapper } from "./ui/components/editor-content"; + +// hooks +export { useEditor } from "./ui/hooks/useEditor"; +export { useReadOnlyEditor } from "./ui/hooks/useReadOnlyEditor"; + +// helper items +export * from "./ui/menus/menu-items"; +export * from "./lib/editor-commands"; diff --git a/packages/editor/core/src/lib/editor-commands.ts b/packages/editor/core/src/lib/editor-commands.ts new file mode 100644 index 00000000000..497a63ca61a --- /dev/null +++ b/packages/editor/core/src/lib/editor-commands.ts @@ -0,0 +1,91 @@ +import { Editor, Range } from "@tiptap/core"; +import { UploadImage } from "../types/upload-image"; +import { startImageUpload } from "../ui/plugins/upload-image"; + +export const toggleHeadingOne = (editor: Editor, range?: Range) => { + if (range) editor.chain().focus().deleteRange(range).setNode("heading", { level: 1 }).run(); + else editor.chain().focus().toggleHeading({ level: 1 }).run() +}; + +export const toggleHeadingTwo = (editor: Editor, range?: Range) => { + if (range) editor.chain().focus().deleteRange(range).setNode("heading", { level: 2 }).run(); + else editor.chain().focus().toggleHeading({ level: 2 }).run() +}; + +export const toggleHeadingThree = (editor: Editor, range?: Range) => { + if (range) editor.chain().focus().deleteRange(range).setNode("heading", { level: 3 }).run(); + else editor.chain().focus().toggleHeading({ level: 3 }).run() +}; + +export const toggleBold = (editor: Editor, range?: Range) => { + if (range) editor.chain().focus().deleteRange(range).toggleBold().run(); + else editor.chain().focus().toggleBold().run(); +}; + +export const toggleItalic = (editor: Editor, range?: Range) => { + if (range) editor.chain().focus().deleteRange(range).toggleItalic().run(); + else editor.chain().focus().toggleItalic().run(); +}; + +export const toggleUnderline = (editor: Editor, range?: Range) => { + if (range) editor.chain().focus().deleteRange(range).toggleUnderline().run(); + else editor.chain().focus().toggleUnderline().run(); +}; + +export const toggleCode = (editor: Editor, range?: Range) => { + if (range) editor.chain().focus().deleteRange(range).toggleCode().run(); + else editor.chain().focus().toggleCode().run(); +}; +export const toggleOrderedList = (editor: Editor, range?: Range) => { + if (range) editor.chain().focus().deleteRange(range).toggleOrderedList().run(); + else editor.chain().focus().toggleOrderedList().run(); +}; + +export const toggleBulletList = (editor: Editor, range?: Range) => { + if (range) editor.chain().focus().deleteRange(range).toggleBulletList().run(); + else editor.chain().focus().toggleBulletList().run(); +}; + +export const toggleTaskList = (editor: Editor, range?: Range) => { + if (range) editor.chain().focus().deleteRange(range).toggleTaskList().run(); + else editor.chain().focus().toggleTaskList().run() +}; + +export const toggleStrike = (editor: Editor, range?: Range) => { + if (range) editor.chain().focus().deleteRange(range).toggleStrike().run(); + else editor.chain().focus().toggleStrike().run(); +}; + +export const toggleBlockquote = (editor: Editor, range?: Range) => { + if (range) editor.chain().focus().deleteRange(range).toggleNode("paragraph", "paragraph").toggleBlockquote().run(); + else editor.chain().focus().toggleNode("paragraph", "paragraph").toggleBlockquote().run(); +}; + +export const insertTableCommand = (editor: Editor, range?: Range) => { + if (range) editor.chain().focus().deleteRange(range).insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run(); + else editor.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run(); +}; + +export const unsetLinkEditor = (editor: Editor) => { + editor.chain().focus().unsetLink().run(); +}; + +export const setLinkEditor = (editor: Editor, url: string) => { + editor.chain().focus().setLink({ href: url }).run(); +}; + +export const insertImageCommand = (editor: Editor, uploadFile: UploadImage, setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void, range?: Range) => { + if (range) editor.chain().focus().deleteRange(range).run(); + const input = document.createElement("input"); + input.type = "file"; + input.accept = "image/*"; + input.onchange = async () => { + if (input.files?.length) { + const file = input.files[0]; + const pos = editor.view.state.selection.from; + startImageUpload(file, editor.view, pos, uploadFile, setIsSubmitting); + } + }; + input.click(); +}; + diff --git a/packages/editor/core/src/lib/utils.ts b/packages/editor/core/src/lib/utils.ts new file mode 100644 index 00000000000..48467478022 --- /dev/null +++ b/packages/editor/core/src/lib/utils.ts @@ -0,0 +1,45 @@ +import { clsx, type ClassValue } from "clsx"; +import { twMerge } from "tailwind-merge"; +interface EditorClassNames { + noBorder?: boolean; + borderOnFocus?: boolean; + customClassName?: string; +} + +export const getEditorClassNames = ({ noBorder, borderOnFocus, customClassName }: EditorClassNames) => cn( + 'relative w-full max-w-full sm:rounded-lg mt-2 p-3 relative focus:outline-none rounded-md', + noBorder ? '' : 'border border-custom-border-200', + borderOnFocus ? 'focus:border border-custom-border-300' : 'focus:border-0', + customClassName +); + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} + +export const findTableAncestor = ( + node: Node | null +): HTMLTableElement | null => { + while (node !== null && node.nodeName !== "TABLE") { + node = node.parentNode; + } + return node as HTMLTableElement; +}; + +export const getTrimmedHTML = (html: string) => { + html = html.replace(/^(

<\/p>)+/, ''); + html = html.replace(/(

<\/p>)+$/, ''); + return html; +} + +export const isValidHttpUrl = (string: string): boolean => { + let url: URL; + + try { + url = new URL(string); + } catch (_) { + return false; + } + + return url.protocol === "http:" || url.protocol === "https:"; +} diff --git a/packages/editor/core/src/styles/editor.css b/packages/editor/core/src/styles/editor.css new file mode 100644 index 00000000000..9987c5f067c --- /dev/null +++ b/packages/editor/core/src/styles/editor.css @@ -0,0 +1,231 @@ +.ProseMirror p.is-editor-empty:first-child::before { + content: attr(data-placeholder); + float: left; + color: rgb(var(--color-text-400)); + pointer-events: none; + height: 0; +} + +.ProseMirror .is-empty::before { + content: attr(data-placeholder); + float: left; + color: rgb(var(--color-text-400)); + pointer-events: none; + height: 0; +} + +/* Custom image styles */ + +.ProseMirror img { + transition: filter 0.1s ease-in-out; + + &:hover { + cursor: pointer; + filter: brightness(90%); + } + + &.ProseMirror-selectednode { + outline: 3px solid #5abbf7; + filter: brightness(90%); + } +} + +.ProseMirror-gapcursor:after { + border-top: 1px solid rgb(var(--color-text-100)) !important; +} + +/* Custom TODO list checkboxes – shoutout to this awesome tutorial: https://moderncss.dev/pure-css-custom-checkbox-style/ */ + +ul[data-type="taskList"] li > label { + margin-right: 0.2rem; + user-select: none; +} + +@media screen and (max-width: 768px) { + ul[data-type="taskList"] li > label { + margin-right: 0.5rem; + } +} + +ul[data-type="taskList"] li > label input[type="checkbox"] { + -webkit-appearance: none; + appearance: none; + background-color: rgb(var(--color-background-100)); + margin: 0; + cursor: pointer; + width: 1.2rem; + height: 1.2rem; + position: relative; + border: 2px solid rgb(var(--color-text-100)); + margin-right: 0.3rem; + display: grid; + place-content: center; + + &:hover { + background-color: rgb(var(--color-background-80)); + } + + &:active { + background-color: rgb(var(--color-background-90)); + } + + &::before { + content: ""; + width: 0.65em; + height: 0.65em; + transform: scale(0); + transition: 120ms transform ease-in-out; + box-shadow: inset 1em 1em; + transform-origin: center; + clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%); + } + + &:checked::before { + transform: scale(1); + } +} + +ul[data-type="taskList"] li[data-checked="true"] > div > p { + color: rgb(var(--color-text-200)); + text-decoration: line-through; + text-decoration-thickness: 2px; +} + +/* Overwrite tippy-box original max-width */ + +.tippy-box { + max-width: 400px !important; +} + +.ProseMirror { + position: relative; + word-wrap: break-word; + white-space: pre-wrap; + -moz-tab-size: 4; + tab-size: 4; + -webkit-user-select: text; + -moz-user-select: text; + -ms-user-select: text; + user-select: text; + outline: none; + cursor: text; + line-height: 1.2; + font-family: inherit; + font-size: 14px; + color: inherit; + -moz-box-sizing: border-box; + box-sizing: border-box; + appearance: textfield; + -webkit-appearance: textfield; + -moz-appearance: textfield; +} + +.fadeIn { + opacity: 1; + transition: opacity 0.3s ease-in; +} + +.fadeOut { + opacity: 0; + transition: opacity 0.2s ease-out; +} + +.img-placeholder { + position: relative; + width: 35%; + + &:before { + content: ""; + box-sizing: border-box; + position: absolute; + top: 50%; + left: 45%; + width: 20px; + height: 20px; + border-radius: 50%; + border: 3px solid rgba(var(--color-text-200)); + border-top-color: rgba(var(--color-text-800)); + animation: spinning 0.6s linear infinite; + } +} + +@keyframes spinning { + to { + transform: rotate(360deg); + } +} + +#editor-container { + table { + border-collapse: collapse; + table-layout: fixed; + margin: 0; + border: 1px solid rgb(var(--color-border-200)); + width: 100%; + + td, + th { + min-width: 1em; + border: 1px solid rgb(var(--color-border-200)); + padding: 10px 15px; + vertical-align: top; + box-sizing: border-box; + position: relative; + transition: background-color 0.3s ease; + + > * { + margin-bottom: 0; + } + } + + th { + font-weight: bold; + text-align: left; + background-color: rgb(var(--color-primary-100)); + } + + td:hover { + background-color: rgba(var(--color-primary-300), 0.1); + } + + .selectedCell:after { + z-index: 2; + position: absolute; + content: ""; + left: 0; + right: 0; + top: 0; + bottom: 0; + background-color: rgba(var(--color-primary-300), 0.1); + pointer-events: none; + } + + .column-resize-handle { + position: absolute; + right: -2px; + top: 0; + bottom: -2px; + width: 2px; + background-color: rgb(var(--color-primary-400)); + pointer-events: none; + } + } +} + +/* .tableWrapper { */ +/* overflow-x: auto; */ +/* } */ + +.resize-cursor { + cursor: ew-resize; + cursor: col-resize; +} + +.ProseMirror table * p { + padding: 0px 1px; + margin: 6px 2px; +} + +.ProseMirror table * .is-empty::before { + opacity: 0; +} diff --git a/packages/editor/core/src/styles/tailwind.css b/packages/editor/core/src/styles/tailwind.css new file mode 100644 index 00000000000..b5c61c95671 --- /dev/null +++ b/packages/editor/core/src/styles/tailwind.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/packages/editor/core/src/types/delete-image.ts b/packages/editor/core/src/types/delete-image.ts new file mode 100644 index 00000000000..40bfffe2fd3 --- /dev/null +++ b/packages/editor/core/src/types/delete-image.ts @@ -0,0 +1 @@ +export type DeleteImage = (assetUrlWithWorkspaceId: string) => Promise; diff --git a/packages/editor/core/src/types/upload-image.ts b/packages/editor/core/src/types/upload-image.ts new file mode 100644 index 00000000000..3cf1408d22b --- /dev/null +++ b/packages/editor/core/src/types/upload-image.ts @@ -0,0 +1 @@ +export type UploadImage = (file: File) => Promise; diff --git a/packages/editor/core/src/ui/components/editor-container.tsx b/packages/editor/core/src/ui/components/editor-container.tsx new file mode 100644 index 00000000000..bb35fef681a --- /dev/null +++ b/packages/editor/core/src/ui/components/editor-container.tsx @@ -0,0 +1,20 @@ +import { Editor } from "@tiptap/react"; +import { ReactNode } from "react"; + +interface EditorContainerProps { + editor: Editor | null; + editorClassNames: string; + children: ReactNode; +} + +export const EditorContainer = ({ editor, editorClassNames, children }: EditorContainerProps) => ( +

{ + editor?.chain().focus().run(); + }} + className={`cursor-text editorContainer ${editorClassNames}`} + > + {children} +
+); diff --git a/packages/editor/core/src/ui/components/editor-content.tsx b/packages/editor/core/src/ui/components/editor-content.tsx new file mode 100644 index 00000000000..80c3a8cadcf --- /dev/null +++ b/packages/editor/core/src/ui/components/editor-content.tsx @@ -0,0 +1,17 @@ +import { Editor, EditorContent } from "@tiptap/react"; +import { ReactNode } from "react"; +import { ImageResizer } from "../extensions/image/image-resize"; + +interface EditorContentProps { + editor: Editor | null; + editorContentCustomClassNames: string | undefined; + children?: ReactNode; +} + +export const EditorContentWrapper = ({ editor, editorContentCustomClassNames = '', children }: EditorContentProps) => ( +
+ + {editor?.isActive("image") && } + {children} +
+); diff --git a/packages/editor/core/src/ui/extensions/drag-drop-older.tsx b/packages/editor/core/src/ui/extensions/drag-drop-older.tsx new file mode 100644 index 00000000000..1a9f68c192c --- /dev/null +++ b/packages/editor/core/src/ui/extensions/drag-drop-older.tsx @@ -0,0 +1,238 @@ +import { Extension } from "@tiptap/core"; + +import { NodeSelection, Plugin } from "@tiptap/pm/state"; +// @ts-ignore +import { __serializeForClipboard, EditorView } from "@tiptap/pm/view"; + +export interface DragHandleOptions { + /** + * The width of the drag handle + */ + dragHandleWidth: number; +} +function absoluteRect(node: Element) { + const data = node.getBoundingClientRect(); + + return { + top: data.top, + left: data.left, + width: data.width, + }; +} + +function nodeDOMAtCoords(coords: { x: number; y: number }) { + return document + .elementsFromPoint(coords.x, coords.y) + .find( + (elem: Element) => + elem.parentElement?.matches?.(".ProseMirror") || + elem.matches( + [ + "li", + "p:not(:first-child)", + "pre", + "code", + "blockquote", + "h1, h2, h3", + "hr", + "[data-type=horizontalRule]", + ".tableWrapper", + ].join(", "), + ), + ); +} + +function nodePosAtDOM(node: Element, view: EditorView) { + const boundingRect = node.getBoundingClientRect(); + + return view.posAtCoords({ + left: boundingRect.left + 1, + top: boundingRect.top + 1, + })?.inside; +} + +function DragHandle(options: DragHandleOptions) { + function handleDragStart(event: DragEvent, view: EditorView) { + view.focus(); + + if (!event.dataTransfer) return; + + const node = nodeDOMAtCoords({ + x: event.clientX + 50 + options.dragHandleWidth, + y: event.clientY, + }); + + if (!(node instanceof Element)) return; + + // const nodePos = nodePosAtDOM(node, view); + + let nodePos = view.posAtCoords({ + left: event.clientX + 50 + options.dragHandleWidth, + top: event.clientY, + })?.inside; + + if (nodePos === null) { + // Try positions to the right of the given coordinates + const offsets = [1, 2, 3, 4, 5]; + for (const offset of offsets) { + const pos = view.posAtCoords({ + left: event.clientX + 50 + options.dragHandleWidth + offset, + top: event.clientY, + })?.inside; + if (pos !== null && view.state.doc.nodeAt(pos as number) !== null) { + nodePos = pos; + break; + } + } + } + + if (nodePos == null || nodePos < 0) return; + + view.dispatch( + view.state.tr.setSelection(NodeSelection.create(view.state.doc, nodePos)), + ); + + const slice = view.state.selection.content(); + const { dom, text } = __serializeForClipboard(view, slice); + + event.dataTransfer.clearData(); + event.dataTransfer.setData("text/html", dom.innerHTML); + event.dataTransfer.setData("text/plain", text); + event.dataTransfer.effectAllowed = "copyMove"; + + event.dataTransfer.setDragImage(node, 0, 0); + + view.dragging = { slice, move: event.ctrlKey }; + } + + function handleClick(event: MouseEvent, view: EditorView) { + view.focus(); + + view.dom.classList.remove("dragging"); + + const node = nodeDOMAtCoords({ + x: event.clientX + 50 + options.dragHandleWidth, + y: event.clientY, + }); + + if (!(node instanceof Element)) return; + + const nodePos = nodePosAtDOM(node, view); + if (!nodePos) return; + + view.dispatch( + view.state.tr.setSelection(NodeSelection.create(view.state.doc, nodePos)), + ); + } + + let dragHandleElement: HTMLElement | null = null; + + function hideDragHandle() { + if (dragHandleElement) { + dragHandleElement.classList.add("hidden"); + } + } + + function showDragHandle() { + if (dragHandleElement) { + dragHandleElement.classList.remove("hidden"); + } + } + + return new Plugin({ + view: (view) => { + dragHandleElement = document.createElement("div"); + dragHandleElement.draggable = true; + dragHandleElement.dataset.dragHandle = ""; + dragHandleElement.classList.add("drag-handle"); + dragHandleElement.addEventListener("dragstart", (e) => { + handleDragStart(e, view); + }); + dragHandleElement.addEventListener("click", (e) => { + handleClick(e, view); + }); + + hideDragHandle(); + + view?.dom?.parentElement?.appendChild(dragHandleElement); + + return { + destroy: () => { + dragHandleElement?.remove?.(); + dragHandleElement = null; + }, + }; + }, + props: { + handleDOMEvents: { + mousemove: (view, event) => { + if (!view.editable) { + return; + } + + const node = nodeDOMAtCoords({ + x: event.clientX + 50 + options.dragHandleWidth, + y: event.clientY, + }); + + if (!(node instanceof Element) || node.matches("ul, ol")) { + hideDragHandle(); + return; + } + + const compStyle = window.getComputedStyle(node); + const lineHeight = parseInt(compStyle.lineHeight, 10); + const paddingTop = parseInt(compStyle.paddingTop, 10); + + const rect = absoluteRect(node); + + rect.top += (lineHeight - 24) / 2; + rect.top += paddingTop; + // Li markers + if (node.matches("ul:not([data-type=taskList]) li, ol li")) { + rect.left -= options.dragHandleWidth; + } + rect.width = options.dragHandleWidth; + + if (!dragHandleElement) return; + + dragHandleElement.style.left = `${rect.left - rect.width}px`; + dragHandleElement.style.top = `${rect.top}px`; + showDragHandle(); + }, + keydown: () => { + hideDragHandle(); + }, + mousewheel: () => { + hideDragHandle(); + }, + // dragging class is used for CSS + dragstart: (view) => { + view.dom.classList.add("dragging"); + }, + drop: (view) => { + view.dom.classList.remove("dragging"); + }, + dragend: (view) => { + view.dom.classList.remove("dragging"); + }, + }, + }, + }); +} + +interface DragAndDropOptions {} + +const DragAndDrop = Extension.create({ + name: "dragAndDrop", + + addProseMirrorPlugins() { + return [ + DragHandle({ + dragHandleWidth: 24, + }), + ]; + }, +}); + +export default DragAndDrop; diff --git a/packages/editor/core/src/ui/extensions/drag-drop.tsx b/packages/editor/core/src/ui/extensions/drag-drop.tsx new file mode 100644 index 00000000000..31f8469bb51 --- /dev/null +++ b/packages/editor/core/src/ui/extensions/drag-drop.tsx @@ -0,0 +1,245 @@ +import { Extension } from "@tiptap/core"; + +import { NodeSelection, Plugin } from "@tiptap/pm/state"; +// @ts-ignore +import { __serializeForClipboard, EditorView } from "@tiptap/pm/view"; + +export interface DragHandleOptions { + dragHandleWidth: number; +} +function absoluteRect(node: Element) { + const data = node.getBoundingClientRect(); + + return { + top: data.top, + left: data.left, + width: data.width, + }; +} + +function nodeDOMAtCoords(coords: { x: number; y: number }) { + return document + .elementsFromPoint(coords.x, coords.y) + .find( + (elem: Element) => + elem.parentElement?.matches?.(".ProseMirror") || + elem.matches( + [ + "li", + "p:not(:first-child)", + "pre", + "code", + "blockquote", + "h1, h2, h3", + "hr", + "[data-type=horizontalRule]", + ".tableWrapper", + ].join(", "), + ), + ); +} + +function nodePosAtDOM(node: Element, view: EditorView) { + const boundingRect = node.getBoundingClientRect(); + + return view.posAtCoords({ + left: boundingRect.left + 1, + top: boundingRect.top + 1, + })?.inside; +} + +function DragHandle(options: DragHandleOptions) { + function handleDragStart(event: DragEvent, view: EditorView) { + view.focus(); + + if (!event.dataTransfer) return; + + const node = nodeDOMAtCoords({ + x: event.clientX + 50 + options.dragHandleWidth, + y: event.clientY, + }); + + if (!(node instanceof Element)) return; + + // const nodePos = view.posAtCoords({ left: event.clientX + 50 + options.dragHandleWidth, top: event.clientY })?.inside; + let nodePos = view.posAtCoords({ + left: event.clientX + 50 + options.dragHandleWidth, + top: event.clientY, + })?.inside; + + if (nodePos === null) { + // Try positions to the right of the given coordinates + const offsets = [1, 2, 3, 4, 5]; + for (const offset of offsets) { + const pos = view.posAtCoords({ + left: event.clientX + 50 + options.dragHandleWidth + offset, + top: event.clientY, + })?.inside; + if (pos !== null && view.state.doc.nodeAt(pos as number) !== null) { + nodePos = pos; + break; + } + } + } + + // const nodePos = nodePosAtDOM(node, view); + if (!nodePos || nodePos < 0) return; + + view.dispatch( + view.state.tr.setSelection(NodeSelection.create(view.state.doc, nodePos)), + ); + + const slice = view.state.selection.content(); + const { dom, text } = __serializeForClipboard(view, slice); + + event.dataTransfer.clearData(); + event.dataTransfer.setData("text/html", dom.innerHTML); + event.dataTransfer.setData("text/plain", text); + event.dataTransfer.effectAllowed = "copyMove"; + + event.dataTransfer.setDragImage(node, 0, 0); + + view.dragging = { slice, move: event.ctrlKey }; + } + + function handleClick(event: MouseEvent, view: EditorView) { + view.focus(); + + view.dom.classList.remove("dragging"); + + const node = nodeDOMAtCoords({ + x: event.clientX + 50 + options.dragHandleWidth, + y: event.clientY, + }); + + if (!(node instanceof Element)) return; + + // const nodePos = view.posAtCoords({ left: event.clientX + 50 + options.dragHandleWidth, top: event.clientY })?.inside; + // console.log("sadfa", pos) + const nodePos = nodePosAtDOM(node, view); + if (!nodePos || nodePos < 0) return; + console.log("nodePos:", nodePos); + console.log("content at nodePos:", view.state.doc.nodeAt(nodePos)); + + const parentPos = view.state.doc.resolve(nodePos).parentOffset; + const parentNode = view.state.doc.nodeAt(parentPos); + + console.log("parentNode:", parentNode); + console.log("parentNode content expression:", parentNode?.type); + + view.dispatch( + view.state.tr.setSelection(NodeSelection.create(view.state.doc, nodePos)), + ); + } + + let dragHandleElement: HTMLElement | null = null; + + function hideDragHandle() { + if (dragHandleElement) { + dragHandleElement.classList.add("hidden"); + } + } + + function showDragHandle() { + if (dragHandleElement) { + dragHandleElement.classList.remove("hidden"); + } + } + + return new Plugin({ + view: (view) => { + dragHandleElement = document.createElement("div"); + dragHandleElement.draggable = true; + dragHandleElement.dataset.dragHandle = ""; + dragHandleElement.classList.add("drag-handle"); + dragHandleElement.addEventListener("dragstart", (e) => { + handleDragStart(e, view); + }); + dragHandleElement.addEventListener("click", (e) => { + handleClick(e, view); + }); + + hideDragHandle(); + + view?.dom?.parentElement?.appendChild(dragHandleElement); + + return { + destroy: () => { + dragHandleElement?.remove?.(); + dragHandleElement = null; + }, + }; + }, + props: { + handleDOMEvents: { + mousemove: (view, event) => { + if (!view.editable) { + return; + } + + const node = nodeDOMAtCoords({ + x: event.clientX + options.dragHandleWidth, + y: event.clientY, + }); + + if (!(node instanceof Element)) { + hideDragHandle(); + return; + } + + const compStyle = window.getComputedStyle(node); + const lineHeight = parseInt(compStyle.lineHeight, 10); + const paddingTop = parseInt(compStyle.paddingTop, 10); + + const rect = absoluteRect(node); + + rect.top += (lineHeight - 24) / 2; + rect.top += paddingTop; + // Li markers + if (node.matches("ul:not([data-type=taskList]) li, ol li")) { + rect.left -= options.dragHandleWidth; + } + rect.width = options.dragHandleWidth; + + if (!dragHandleElement) return; + + dragHandleElement.style.left = `${rect.left - rect.width}px`; + dragHandleElement.style.top = `${rect.top}px`; + showDragHandle(); + }, + keydown: () => { + hideDragHandle(); + }, + mousewheel: () => { + hideDragHandle(); + }, + // dragging class is used for CSS + dragstart: (view) => { + view.dom.classList.add("dragging"); + }, + drop: (view) => { + view.dom.classList.remove("dragging"); + }, + dragend: (view) => { + view.dom.classList.remove("dragging"); + }, + }, + }, + }); +} + +interface DragAndDropOptions {} + +const DragAndDrop = Extension.create({ + name: "dragAndDrop", + + addProseMirrorPlugins() { + return [ + DragHandle({ + dragHandleWidth: 24, + }), + ]; + }, +}); + +export default DragAndDrop; diff --git a/space/components/tiptap/extensions/image-resize.tsx b/packages/editor/core/src/ui/extensions/image/image-resize.tsx similarity index 100% rename from space/components/tiptap/extensions/image-resize.tsx rename to packages/editor/core/src/ui/extensions/image/image-resize.tsx diff --git a/packages/editor/core/src/ui/extensions/image/index.tsx b/packages/editor/core/src/ui/extensions/image/index.tsx new file mode 100644 index 00000000000..f9345509d98 --- /dev/null +++ b/packages/editor/core/src/ui/extensions/image/index.tsx @@ -0,0 +1,23 @@ +import Image from "@tiptap/extension-image"; +import TrackImageDeletionPlugin from "../../plugins/delete-image"; +import UploadImagesPlugin from "../../plugins/upload-image"; +import { DeleteImage } from "../../../types/delete-image"; + +const ImageExtension = (deleteImage: DeleteImage) => Image.extend({ + addProseMirrorPlugins() { + return [UploadImagesPlugin(), TrackImageDeletionPlugin(deleteImage)]; + }, + addAttributes() { + return { + ...this.parent?.(), + width: { + default: "35%", + }, + height: { + default: null, + }, + }; + }, +}); + +export default ImageExtension; diff --git a/packages/editor/core/src/ui/extensions/image/read-only-image.tsx b/packages/editor/core/src/ui/extensions/image/read-only-image.tsx new file mode 100644 index 00000000000..73a763d0428 --- /dev/null +++ b/packages/editor/core/src/ui/extensions/image/read-only-image.tsx @@ -0,0 +1,17 @@ +import Image from "@tiptap/extension-image"; + +const ReadOnlyImageExtension = Image.extend({ + addAttributes() { + return { + ...this.parent?.(), + width: { + default: "35%", + }, + height: { + default: null, + }, + }; + }, +}); + +export default ReadOnlyImageExtension; diff --git a/packages/editor/core/src/ui/extensions/index.tsx b/packages/editor/core/src/ui/extensions/index.tsx new file mode 100644 index 00000000000..147fc69d856 --- /dev/null +++ b/packages/editor/core/src/ui/extensions/index.tsx @@ -0,0 +1,97 @@ +import StarterKit from "@tiptap/starter-kit"; +import TiptapLink from "@tiptap/extension-link"; +import TiptapUnderline from "@tiptap/extension-underline"; +import TextStyle from "@tiptap/extension-text-style"; +import { Color } from "@tiptap/extension-color"; +import TaskItem from "@tiptap/extension-task-item"; +import TaskList from "@tiptap/extension-task-list"; +import { Markdown } from "tiptap-markdown"; +import Gapcursor from "@tiptap/extension-gapcursor"; + +import ImageExtension from "./image"; + +import { DeleteImage } from "../../types/delete-image"; +import { isValidHttpUrl } from "../../lib/utils"; +import TableHeader from "./table/table-header/table-header"; +import Table from "./table/table"; +import TableCell from "./table/table-cell/table-cell"; +import TableRow from "./table/table-row/table-row"; +import DragAndDrop from "./drag-drop"; + +export const CoreEditorExtensions = ( + deleteFile: DeleteImage, +) => [ + StarterKit.configure({ + bulletList: { + HTMLAttributes: { + class: "list-disc list-outside leading-3 -mt-2", + }, + }, + orderedList: { + HTMLAttributes: { + class: "list-decimal list-outside leading-3 -mt-2", + }, + }, + listItem: { + HTMLAttributes: { + class: "leading-normal -mb-2", + }, + }, + blockquote: { + HTMLAttributes: { + class: "border-l-4 border-custom-border-300", + }, + }, + code: { + HTMLAttributes: { + class: + "rounded-md bg-custom-primary-30 mx-1 px-1 py-1 font-mono font-medium text-custom-text-1000", + spellcheck: "false", + }, + }, + codeBlock: false, + horizontalRule: false, + dropcursor: { + color: "rgba(var(--color-text-100))", + width: 2, + }, + gapcursor: false, + }), + Gapcursor, + TiptapLink.configure({ + protocols: ["http", "https"], + validate: (url) => isValidHttpUrl(url), + HTMLAttributes: { + class: + "text-custom-primary-300 underline underline-offset-[3px] hover:text-custom-primary-500 transition-colors cursor-pointer", + }, + }), + ImageExtension(deleteFile).configure({ + HTMLAttributes: { + class: "rounded-lg border border-custom-border-300", + }, + }), + TiptapUnderline, + TextStyle, + Color, + TaskList.configure({ + HTMLAttributes: { + class: "not-prose pl-2", + }, + }), + TaskItem.configure({ + HTMLAttributes: { + class: "flex items-start my-4", + }, + nested: true, + }), + Markdown.configure({ + html: true, + transformCopiedText: true, + }), + Table, + TableHeader, + TableCell, + TableRow, + DragAndDrop, + ]; diff --git a/packages/editor/core/src/ui/extensions/table/table-cell/index.ts b/packages/editor/core/src/ui/extensions/table/table-cell/index.ts new file mode 100644 index 00000000000..b39fe7104e5 --- /dev/null +++ b/packages/editor/core/src/ui/extensions/table/table-cell/index.ts @@ -0,0 +1 @@ +export { default as default } from "./table-cell" diff --git a/packages/editor/core/src/ui/extensions/table/table-cell/table-cell.ts b/packages/editor/core/src/ui/extensions/table/table-cell/table-cell.ts new file mode 100644 index 00000000000..ac43875dac8 --- /dev/null +++ b/packages/editor/core/src/ui/extensions/table/table-cell/table-cell.ts @@ -0,0 +1,58 @@ +import { mergeAttributes, Node } from "@tiptap/core" + +export interface TableCellOptions { + HTMLAttributes: Record +} + +export default Node.create({ + name: "tableCell", + + addOptions() { + return { + HTMLAttributes: {} + } + }, + + content: "paragraph+", + + addAttributes() { + return { + colspan: { + default: 1 + }, + rowspan: { + default: 1 + }, + colwidth: { + default: null, + parseHTML: (element) => { + const colwidth = element.getAttribute("colwidth") + const value = colwidth ? [parseInt(colwidth, 10)] : null + + return value + } + }, + background: { + default: "none" + } + } + }, + + tableRole: "cell", + + isolating: true, + + parseHTML() { + return [{ tag: "td" }] + }, + + renderHTML({ node, HTMLAttributes }) { + return [ + "td", + mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, { + style: `background-color: ${node.attrs.background}` + }), + 0 + ] + } +}) diff --git a/packages/editor/core/src/ui/extensions/table/table-header/index.ts b/packages/editor/core/src/ui/extensions/table/table-header/index.ts new file mode 100644 index 00000000000..57137dedd43 --- /dev/null +++ b/packages/editor/core/src/ui/extensions/table/table-header/index.ts @@ -0,0 +1 @@ +export { default as default } from "./table-header" diff --git a/packages/editor/core/src/ui/extensions/table/table-header/table-header.ts b/packages/editor/core/src/ui/extensions/table/table-header/table-header.ts new file mode 100644 index 00000000000..712ca65f073 --- /dev/null +++ b/packages/editor/core/src/ui/extensions/table/table-header/table-header.ts @@ -0,0 +1,57 @@ +import { mergeAttributes, Node } from "@tiptap/core" + +export interface TableHeaderOptions { + HTMLAttributes: Record +} +export default Node.create({ + name: "tableHeader", + + addOptions() { + return { + HTMLAttributes: {} + } + }, + + content: "paragraph+", + + addAttributes() { + return { + colspan: { + default: 1 + }, + rowspan: { + default: 1 + }, + colwidth: { + default: null, + parseHTML: (element) => { + const colwidth = element.getAttribute("colwidth") + const value = colwidth ? [parseInt(colwidth, 10)] : null + + return value + } + }, + background: { + default: "rgb(var(--color-primary-100))" + } + } + }, + + tableRole: "header_cell", + + isolating: true, + + parseHTML() { + return [{ tag: "th" }] + }, + + renderHTML({ node, HTMLAttributes }) { + return [ + "th", + mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, { + style: `background-color: ${node.attrs.background}` + }), + 0 + ] + } +}) diff --git a/packages/editor/core/src/ui/extensions/table/table-row/index.ts b/packages/editor/core/src/ui/extensions/table/table-row/index.ts new file mode 100644 index 00000000000..9ecc2c0ae57 --- /dev/null +++ b/packages/editor/core/src/ui/extensions/table/table-row/index.ts @@ -0,0 +1 @@ +export { default as default } from "./table-row" diff --git a/packages/editor/core/src/ui/extensions/table/table-row/table-row.ts b/packages/editor/core/src/ui/extensions/table/table-row/table-row.ts new file mode 100644 index 00000000000..e922e7fa197 --- /dev/null +++ b/packages/editor/core/src/ui/extensions/table/table-row/table-row.ts @@ -0,0 +1,31 @@ +import { mergeAttributes, Node } from "@tiptap/core" + +export interface TableRowOptions { + HTMLAttributes: Record +} + +export default Node.create({ + name: "tableRow", + + addOptions() { + return { + HTMLAttributes: {} + } + }, + + content: "(tableCell | tableHeader)*", + + tableRole: "row", + + parseHTML() { + return [{ tag: "tr" }] + }, + + renderHTML({ HTMLAttributes }) { + return [ + "tr", + mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), + 0 + ] + } +}) diff --git a/packages/editor/core/src/ui/extensions/table/table/icons.ts b/packages/editor/core/src/ui/extensions/table/table/icons.ts new file mode 100644 index 00000000000..d24209c8ae1 --- /dev/null +++ b/packages/editor/core/src/ui/extensions/table/table/icons.ts @@ -0,0 +1,11 @@ +const icons = { + insertColumnLeft: ``, + insertColumnRight: ``, + insertRowTop: ``, + insertRowBottom: ``, + colorPicker: ``, + deleteColumn: ``, + deleteRow: `` +} + +export default icons diff --git a/packages/editor/core/src/ui/extensions/table/table/index.ts b/packages/editor/core/src/ui/extensions/table/table/index.ts new file mode 100644 index 00000000000..5dbd0f38a45 --- /dev/null +++ b/packages/editor/core/src/ui/extensions/table/table/index.ts @@ -0,0 +1 @@ +export { default as default } from "./table" diff --git a/packages/editor/core/src/ui/extensions/table/table/table-controls.ts b/packages/editor/core/src/ui/extensions/table/table/table-controls.ts new file mode 100644 index 00000000000..afcb9103e9f --- /dev/null +++ b/packages/editor/core/src/ui/extensions/table/table/table-controls.ts @@ -0,0 +1,118 @@ +import { Plugin, PluginKey, TextSelection } from "@tiptap/pm/state" +import { findParentNode } from "@tiptap/core" +import { DecorationSet, Decoration } from "@tiptap/pm/view" + +const key = new PluginKey("tableControls") + +export function tableControls() { + return new Plugin({ + key, + state: { + init() { + return new TableControlsState() + }, + apply(tr, prev) { + return prev.apply(tr) + } + }, + props: { + handleDOMEvents: { + mousemove: (view, event) => { + const pluginState = key.getState(view.state) + + if ( + !(event.target as HTMLElement).closest( + ".tableWrapper" + ) && + pluginState.values.hoveredTable + ) { + return view.dispatch( + view.state.tr.setMeta(key, { + setHoveredTable: null, + setHoveredCell: null + }) + ) + } + + const pos = view.posAtCoords({ + left: event.clientX, + top: event.clientY + }) + + if (!pos) return + + const table = findParentNode( + (node) => node.type.name === "table" + )(TextSelection.create(view.state.doc, pos.pos)) + const cell = findParentNode( + (node) => + node.type.name === "tableCell" || + node.type.name === "tableHeader" + )(TextSelection.create(view.state.doc, pos.pos)) + + if (!table || !cell) return + + if (pluginState.values.hoveredCell?.pos !== cell.pos) { + return view.dispatch( + view.state.tr.setMeta(key, { + setHoveredTable: table, + setHoveredCell: cell + }) + ) + } + } + }, + decorations: (state) => { + const pluginState = key.getState(state) + if (!pluginState) { + return null + } + + const { hoveredTable, hoveredCell } = pluginState.values + if (hoveredTable) { + const decorations = [ + Decoration.node( + hoveredTable.pos, + hoveredTable.pos + hoveredTable.node.nodeSize, + {}, + { + hoveredTable, + hoveredCell + } + ) + ] + + return DecorationSet.create(state.doc, decorations) + } + + return null + } + } + }) +} + +class TableControlsState { + values + + constructor(props = {}) { + this.values = { + hoveredTable: null, + hoveredCell: null, + ...props + } + } + + apply(tr: any) { + const actions = tr.getMeta(key) + + if (actions?.setHoveredTable !== undefined) { + this.values.hoveredTable = actions.setHoveredTable + } + + if (actions?.setHoveredCell !== undefined) { + this.values.hoveredCell = actions.setHoveredCell + } + + return this + } +} diff --git a/packages/editor/core/src/ui/extensions/table/table/table-view.tsx b/packages/editor/core/src/ui/extensions/table/table/table-view.tsx new file mode 100644 index 00000000000..14f75efe798 --- /dev/null +++ b/packages/editor/core/src/ui/extensions/table/table/table-view.tsx @@ -0,0 +1,500 @@ +import { h } from "jsx-dom-cjs" +import { Node as ProseMirrorNode } from "@tiptap/pm/model" +import { Decoration, NodeView } from "@tiptap/pm/view" +import tippy, { Instance, Props } from "tippy.js" + +import { Editor } from "@tiptap/core" +import { + CellSelection, + TableMap, + updateColumnsOnResize +} from "@tiptap/prosemirror-tables" + +import icons from "./icons" + +export function updateColumns( + node: ProseMirrorNode, + colgroup: HTMLElement, + table: HTMLElement, + cellMinWidth: number, + overrideCol?: number, + overrideValue?: any +) { + let totalWidth = 0 + let fixedWidth = true + let nextDOM = colgroup.firstChild as HTMLElement + const row = node.firstChild + + if (!row) return + + for (let i = 0, col = 0; i < row.childCount; i += 1) { + const { colspan, colwidth } = row.child(i).attrs + + for (let j = 0; j < colspan; j += 1, col += 1) { + const hasWidth = + overrideCol === col ? overrideValue : colwidth && colwidth[j] + const cssWidth = hasWidth ? `${hasWidth}px` : "" + + totalWidth += hasWidth || cellMinWidth + + if (!hasWidth) { + fixedWidth = false + } + + if (!nextDOM) { + colgroup.appendChild( + document.createElement("col") + ).style.width = cssWidth + } else { + if (nextDOM.style.width !== cssWidth) { + nextDOM.style.width = cssWidth + } + + nextDOM = nextDOM.nextSibling as HTMLElement + } + } + } + + while (nextDOM) { + const after = nextDOM.nextSibling + + nextDOM.parentNode?.removeChild(nextDOM) + nextDOM = after as HTMLElement + } + + if (fixedWidth) { + table.style.width = `${totalWidth}px` + table.style.minWidth = "" + } else { + table.style.width = "" + table.style.minWidth = `${totalWidth}px` + } +} + +const defaultTippyOptions: Partial = { + allowHTML: true, + arrow: false, + trigger: "click", + animation: "scale-subtle", + theme: "light-border no-padding", + interactive: true, + hideOnClick: true, + placement: "right" +} + +function setCellsBackgroundColor(editor: Editor, backgroundColor) { + return editor + .chain() + .focus() + .updateAttributes("tableCell", { + background: backgroundColor + }) + .updateAttributes("tableHeader", { + background: backgroundColor + }) + .run() +} + +const columnsToolboxItems = [ + { + label: "Add Column Before", + icon: icons.insertColumnLeft, + action: ({ editor }: { editor: Editor }) => editor.chain().focus().addColumnBefore().run() + }, + { + label: "Add Column After", + icon: icons.insertColumnRight, + action: ({ editor }: { editor: Editor }) => editor.chain().focus().addColumnAfter().run() + }, + { + label: "Pick Column Color", + icon: icons.colorPicker, + action: ({ editor, triggerButton, controlsContainer }: { editor: Editor, triggerButton: HTMLElement, controlsContainer }) => { + createColorPickerToolbox({ + triggerButton, + tippyOptions: { + appendTo: controlsContainer + }, + onSelectColor: (color) => setCellsBackgroundColor(editor, color) + }) + } + }, + { + label: "Delete Column", + icon: icons.deleteColumn, + action: ({ editor }: { editor: Editor }) => editor.chain().focus().deleteColumn().run() + } +] + +const rowsToolboxItems = [ + { + label: "Add Row Above", + icon: icons.insertRowTop, + action: ({ editor }: { editor: Editor }) => editor.chain().focus().addRowBefore().run() + }, + { + label: "Add Row Below", + icon: icons.insertRowBottom, + action: ({ editor }: { editor: Editor }) => editor.chain().focus().addRowAfter().run() + }, + { + label: "Pick a Color", + icon: icons.colorPicker, + action: ({ editor, triggerButton, controlsContainer }: { editor: Editor, triggerButton: HTMLButtonElement, controlsContainer: Element | "parent" | ((ref: Element) => Element) | undefined }) => { + createColorPickerToolbox({ + triggerButton, + tippyOptions: { + appendTo: controlsContainer + }, + onSelectColor: (color) => setCellsBackgroundColor(editor, color) + }) + } + }, + { + label: "Delete Row", + icon: icons.deleteRow, + action: ({ editor }: { editor: Editor }) => editor.chain().focus().deleteRow().run() + } +] + +function createToolbox({ + triggerButton, + items, + tippyOptions, + onClickItem +}: { triggerButton: HTMLElement, items: { icon: string, label: string }[], tippyOptions: any, onClickItem: any }): Instance { + const toolbox = tippy(triggerButton, { + content: h( + "div", + { className: "tableToolbox" }, + items.map((item) => + h( + "div", + { + className: "toolboxItem", + onClick() { + onClickItem(item) + } + }, + [ + h("div", { + className: "iconContainer", + innerHTML: item.icon + }), + h("div", { className: "label" }, item.label) + ] + ) + ) + ), + ...tippyOptions + }) + + return Array.isArray(toolbox) ? toolbox[0] : toolbox +} + +function createColorPickerToolbox({ + triggerButton, + tippyOptions, + onSelectColor = () => { } +}: { + triggerButton: HTMLElement + tippyOptions: Partial + onSelectColor?: (color: string) => void +}) { + const items = { + "Default": "rgb(var(--color-primary-100))", + "Light gray": "#e7f3f8", + "Dark gray": "#c7d2d7", + "Light blue": "#e7f3f8", + "Light red": "#ffc4c7", + "Light yellow": "#fbf3db" + } + + const colorPicker = tippy(triggerButton, { + ...defaultTippyOptions, + content: h( + "div", + { className: "tableColorPickerToolbox" }, + Object.entries(items).map(([key, value]) => + h( + "div", + { + className: "toolboxItem", + onClick: () => { + onSelectColor(value) + colorPicker.hide() + } + }, + [ + h("div", { + className: "colorContainer", + style: { + backgroundColor: value + } + }), + h( + "div", + { + className: "label" + }, + key + ) + ] + ) + ) + ), + onHidden: (instance) => { + instance.destroy() + }, + showOnCreate: true, + ...tippyOptions + }) + + return colorPicker +} + +export class TableView implements NodeView { + node: ProseMirrorNode + cellMinWidth: number + decorations: Decoration[] + editor: Editor + getPos: () => number + hoveredCell + map: TableMap + root: HTMLElement + table: HTMLElement + colgroup: HTMLElement + tbody: HTMLElement + rowsControl?: HTMLElement + columnsControl?: HTMLElement + columnsToolbox?: Instance + rowsToolbox?: Instance + controls?: HTMLElement + + get dom() { + return this.root + } + + get contentDOM() { + return this.tbody + } + + constructor( + node: ProseMirrorNode, + cellMinWidth: number, + decorations: Decoration[], + editor: Editor, + getPos: () => number + ) { + this.node = node + this.cellMinWidth = cellMinWidth + this.decorations = decorations + this.editor = editor + this.getPos = getPos + this.hoveredCell = null + this.map = TableMap.get(node) + + /** + * DOM + */ + + // Controllers + if (editor.isEditable) { + this.rowsControl = h( + "div", + { className: "rowsControl" }, + h("button", { + onClick: () => this.selectRow() + }) + ) + + this.columnsControl = h( + "div", + { className: "columnsControl" }, + h("button", { + onClick: () => this.selectColumn() + }) + ) + + this.controls = h( + "div", + { className: "tableControls", contentEditable: "false" }, + this.rowsControl, + this.columnsControl + ) + + this.columnsToolbox = createToolbox({ + triggerButton: this.columnsControl.querySelector("button"), + items: columnsToolboxItems, + tippyOptions: { + ...defaultTippyOptions, + appendTo: this.controls + }, + onClickItem: (item) => { + item.action({ + editor: this.editor, + triggerButton: this.columnsControl?.firstElementChild, + controlsContainer: this.controls + }) + this.columnsToolbox?.hide() + } + }) + + this.rowsToolbox = createToolbox({ + triggerButton: this.rowsControl.firstElementChild, + items: rowsToolboxItems, + tippyOptions: { + ...defaultTippyOptions, + appendTo: this.controls + }, + onClickItem: (item) => { + item.action({ + editor: this.editor, + triggerButton: this.rowsControl?.firstElementChild, + controlsContainer: this.controls + }) + this.rowsToolbox?.hide() + } + }) + } + + // Table + + this.colgroup = h( + "colgroup", + null, + Array.from({ length: this.map.width }, () => 1).map(() => h("col")) + ) + this.tbody = h("tbody") + this.table = h("table", null, this.colgroup, this.tbody) + + this.root = h( + "div", + { + className: "tableWrapper controls--disabled" + }, + this.controls, + this.table + ) + + this.render() + } + + update(node: ProseMirrorNode, decorations) { + if (node.type !== this.node.type) { + return false + } + + this.node = node + this.decorations = decorations + this.map = TableMap.get(this.node) + + if (this.editor.isEditable) { + this.updateControls() + } + + this.render() + + return true + } + + render() { + if (this.colgroup.children.length !== this.map.width) { + const cols = Array.from({ length: this.map.width }, () => 1).map( + () => h("col") + ) + this.colgroup.replaceChildren(...cols) + } + + updateColumnsOnResize( + this.node, + this.colgroup, + this.table, + this.cellMinWidth + ) + } + + ignoreMutation() { + return true + } + + updateControls() { + const { hoveredTable: table, hoveredCell: cell } = Object.values( + this.decorations + ).reduce((acc, curr) => { + if (curr.spec.hoveredCell !== undefined) { + acc["hoveredCell"] = curr.spec.hoveredCell + } + + if (curr.spec.hoveredTable !== undefined) { + acc["hoveredTable"] = curr.spec.hoveredTable + } + return acc + }, {}) as any + + if (table === undefined || cell === undefined) { + return this.root.classList.add("controls--disabled") + } + + this.root.classList.remove("controls--disabled") + this.hoveredCell = cell + + const cellDom = this.editor.view.nodeDOM(cell.pos) as HTMLElement + + const tableRect = this.table.getBoundingClientRect() + const cellRect = cellDom.getBoundingClientRect() + + this.columnsControl.style.left = `${cellRect.left - + tableRect.left - + this.table.parentElement!.scrollLeft + }px` + this.columnsControl.style.width = `${cellRect.width}px` + + this.rowsControl.style.top = `${cellRect.top - tableRect.top}px` + this.rowsControl.style.height = `${cellRect.height}px` + } + + selectColumn() { + if (!this.hoveredCell) return + + const colIndex = this.map.colCount( + this.hoveredCell.pos - (this.getPos() + 1) + ) + const anchorCellPos = this.hoveredCell.pos + const headCellPos = + this.map.map[colIndex + this.map.width * (this.map.height - 1)] + + (this.getPos() + 1) + + const cellSelection = CellSelection.create( + this.editor.view.state.doc, + anchorCellPos, + headCellPos + ) + this.editor.view.dispatch( + // @ts-ignore + this.editor.state.tr.setSelection(cellSelection) + ) + } + + selectRow() { + if (!this.hoveredCell) return + + const anchorCellPos = this.hoveredCell.pos + const anchorCellIndex = this.map.map.indexOf( + anchorCellPos - (this.getPos() + 1) + ) + const headCellPos = + this.map.map[anchorCellIndex + (this.map.width - 1)] + + (this.getPos() + 1) + + const cellSelection = CellSelection.create( + this.editor.state.doc, + anchorCellPos, + headCellPos + ) + this.editor.view.dispatch( + // @ts-ignore + this.editor.view.state.tr.setSelection(cellSelection) + ) + } +} diff --git a/packages/editor/core/src/ui/extensions/table/table/table.ts b/packages/editor/core/src/ui/extensions/table/table/table.ts new file mode 100644 index 00000000000..eab3cad92e0 --- /dev/null +++ b/packages/editor/core/src/ui/extensions/table/table/table.ts @@ -0,0 +1,298 @@ +import { TextSelection } from "@tiptap/pm/state" + +import { callOrReturn, getExtensionField, mergeAttributes, Node, ParentConfig } from "@tiptap/core" +import { + addColumnAfter, + addColumnBefore, + addRowAfter, + addRowBefore, + CellSelection, + columnResizing, + deleteColumn, + deleteRow, + deleteTable, + fixTables, + goToNextCell, + mergeCells, + setCellAttr, + splitCell, + tableEditing, + toggleHeader, + toggleHeaderCell +} from "@tiptap/prosemirror-tables" + +import { tableControls } from "./table-controls" +import { TableView } from "./table-view" +import { createTable } from "./utilities/create-table" +import { deleteTableWhenAllCellsSelected } from "./utilities/delete-table-when-all-cells-selected" + +export interface TableOptions { + HTMLAttributes: Record + resizable: boolean + handleWidth: number + cellMinWidth: number + lastColumnResizable: boolean + allowTableNodeSelection: boolean +} + +declare module "@tiptap/core" { + interface Commands { + table: { + insertTable: (options?: { + rows?: number + cols?: number + withHeaderRow?: boolean + }) => ReturnType + addColumnBefore: () => ReturnType + addColumnAfter: () => ReturnType + deleteColumn: () => ReturnType + addRowBefore: () => ReturnType + addRowAfter: () => ReturnType + deleteRow: () => ReturnType + deleteTable: () => ReturnType + mergeCells: () => ReturnType + splitCell: () => ReturnType + toggleHeaderColumn: () => ReturnType + toggleHeaderRow: () => ReturnType + toggleHeaderCell: () => ReturnType + mergeOrSplit: () => ReturnType + setCellAttribute: (name: string, value: any) => ReturnType + goToNextCell: () => ReturnType + goToPreviousCell: () => ReturnType + fixTables: () => ReturnType + setCellSelection: (position: { + anchorCell: number + headCell?: number + }) => ReturnType + } + } + + interface NodeConfig { + tableRole?: + | string + | ((this: { + name: string + options: Options + storage: Storage + parent: ParentConfig>["tableRole"] + }) => string) + } +} + +export default Node.create({ + name: "table", + + addOptions() { + return { + HTMLAttributes: {}, + resizable: true, + handleWidth: 5, + cellMinWidth: 100, + lastColumnResizable: true, + allowTableNodeSelection: true + } + }, + + content: "tableRow+", + + tableRole: "table", + + isolating: true, + + group: "block", + + allowGapCursor: false, + + parseHTML() { + return [{ tag: "table" }] + }, + + renderHTML({ HTMLAttributes }) { + return [ + "table", + mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), + ["tbody", 0] + ] + }, + + addCommands() { + return { + insertTable: + ({ rows = 3, cols = 3, withHeaderRow = true} = {}) => + ({ tr, dispatch, editor }) => { + const node = createTable( + editor.schema, + rows, + cols, + withHeaderRow + ) + + if (dispatch) { + const offset = tr.selection.anchor + 1 + + tr.replaceSelectionWith(node) + .scrollIntoView() + .setSelection( + TextSelection.near(tr.doc.resolve(offset)) + ) + } + + return true + }, + addColumnBefore: + () => + ({ state, dispatch }) => addColumnBefore(state, dispatch), + addColumnAfter: + () => + ({ state, dispatch }) => addColumnAfter(state, dispatch), + deleteColumn: + () => + ({ state, dispatch }) => deleteColumn(state, dispatch), + addRowBefore: + () => + ({ state, dispatch }) => addRowBefore(state, dispatch), + addRowAfter: + () => + ({ state, dispatch }) => addRowAfter(state, dispatch), + deleteRow: + () => + ({ state, dispatch }) => deleteRow(state, dispatch), + deleteTable: + () => + ({ state, dispatch }) => deleteTable(state, dispatch), + mergeCells: + () => + ({ state, dispatch }) => mergeCells(state, dispatch), + splitCell: + () => + ({ state, dispatch }) => splitCell(state, dispatch), + toggleHeaderColumn: + () => + ({ state, dispatch }) => toggleHeader("column")(state, dispatch), + toggleHeaderRow: + () => + ({ state, dispatch }) => toggleHeader("row")(state, dispatch), + toggleHeaderCell: + () => + ({ state, dispatch }) => toggleHeaderCell(state, dispatch), + mergeOrSplit: + () => + ({ state, dispatch }) => { + if (mergeCells(state, dispatch)) { + return true + } + + return splitCell(state, dispatch) + }, + setCellAttribute: + (name, value) => + ({ state, dispatch }) => setCellAttr(name, value)(state, dispatch), + goToNextCell: + () => + ({ state, dispatch }) => goToNextCell(1)(state, dispatch), + goToPreviousCell: + () => + ({ state, dispatch }) => goToNextCell(-1)(state, dispatch), + fixTables: + () => + ({ state, dispatch }) => { + if (dispatch) { + fixTables(state) + } + + return true + }, + setCellSelection: + (position) => + ({ tr, dispatch }) => { + if (dispatch) { + const selection = CellSelection.create( + tr.doc, + position.anchorCell, + position.headCell + ) + + // @ts-ignore + tr.setSelection(selection) + } + + return true + } + } + }, + + addKeyboardShortcuts() { + return { + Tab: () => { + if (this.editor.commands.goToNextCell()) { + return true + } + + if (!this.editor.can().addRowAfter()) { + return false + } + + return this.editor.chain().addRowAfter().goToNextCell().run() + }, + "Shift-Tab": () => this.editor.commands.goToPreviousCell(), + Backspace: deleteTableWhenAllCellsSelected, + "Mod-Backspace": deleteTableWhenAllCellsSelected, + Delete: deleteTableWhenAllCellsSelected, + "Mod-Delete": deleteTableWhenAllCellsSelected + } + }, + + addNodeView() { + return ({ editor, getPos, node, decorations }) => { + const { cellMinWidth } = this.options + + return new TableView( + node, + cellMinWidth, + decorations, + editor, + getPos as () => number + ) + } + }, + + addProseMirrorPlugins() { + const isResizable = this.options.resizable && this.editor.isEditable + + const plugins = [ + tableEditing({ + allowTableNodeSelection: this.options.allowTableNodeSelection + }), + tableControls() + ] + + if (isResizable) { + plugins.unshift( + columnResizing({ + handleWidth: this.options.handleWidth, + cellMinWidth: this.options.cellMinWidth, + // View: TableView, + + // @ts-ignore + lastColumnResizable: this.options.lastColumnResizable + }) + ) + } + + return plugins + }, + + extendNodeSchema(extension) { + const context = { + name: extension.name, + options: extension.options, + storage: extension.storage + } + + return { + tableRole: callOrReturn( + getExtensionField(extension, "tableRole", context) + ) + } + } +}) diff --git a/packages/editor/core/src/ui/extensions/table/table/utilities/create-cell.ts b/packages/editor/core/src/ui/extensions/table/table/utilities/create-cell.ts new file mode 100644 index 00000000000..a3d7f2da814 --- /dev/null +++ b/packages/editor/core/src/ui/extensions/table/table/utilities/create-cell.ts @@ -0,0 +1,12 @@ +import { Fragment, Node as ProsemirrorNode, NodeType } from "prosemirror-model" + +export function createCell( + cellType: NodeType, + cellContent?: Fragment | ProsemirrorNode | Array +): ProsemirrorNode | null | undefined { + if (cellContent) { + return cellType.createChecked(null, cellContent) + } + + return cellType.createAndFill() +} diff --git a/packages/editor/core/src/ui/extensions/table/table/utilities/create-table.ts b/packages/editor/core/src/ui/extensions/table/table/utilities/create-table.ts new file mode 100644 index 00000000000..75bf7cb41db --- /dev/null +++ b/packages/editor/core/src/ui/extensions/table/table/utilities/create-table.ts @@ -0,0 +1,45 @@ +import { Fragment, Node as ProsemirrorNode, Schema } from "@tiptap/pm/model" + +import { createCell } from "./create-cell" +import { getTableNodeTypes } from "./get-table-node-types" + +export function createTable( + schema: Schema, + rowsCount: number, + colsCount: number, + withHeaderRow: boolean, + cellContent?: Fragment | ProsemirrorNode | Array +): ProsemirrorNode { + const types = getTableNodeTypes(schema) + const headerCells: ProsemirrorNode[] = [] + const cells: ProsemirrorNode[] = [] + + for (let index = 0; index < colsCount; index += 1) { + const cell = createCell(types.cell, cellContent) + + if (cell) { + cells.push(cell) + } + + if (withHeaderRow) { + const headerCell = createCell(types.header_cell, cellContent) + + if (headerCell) { + headerCells.push(headerCell) + } + } + } + + const rows: ProsemirrorNode[] = [] + + for (let index = 0; index < rowsCount; index += 1) { + rows.push( + types.row.createChecked( + null, + withHeaderRow && index === 0 ? headerCells : cells + ) + ) + } + + return types.table.createChecked(null, rows) +} diff --git a/packages/editor/core/src/ui/extensions/table/table/utilities/delete-table-when-all-cells-selected.ts b/packages/editor/core/src/ui/extensions/table/table/utilities/delete-table-when-all-cells-selected.ts new file mode 100644 index 00000000000..dcb20b3239f --- /dev/null +++ b/packages/editor/core/src/ui/extensions/table/table/utilities/delete-table-when-all-cells-selected.ts @@ -0,0 +1,39 @@ +import { findParentNodeClosestToPos, KeyboardShortcutCommand } from "@tiptap/core" + +import { isCellSelection } from "./is-cell-selection" + +export const deleteTableWhenAllCellsSelected: KeyboardShortcutCommand = ({ + editor +}) => { + const { selection } = editor.state + + if (!isCellSelection(selection)) { + return false + } + + let cellCount = 0 + const table = findParentNodeClosestToPos( + selection.ranges[0].$from, + (node) => node.type.name === "table" + ) + + table?.node.descendants((node) => { + if (node.type.name === "table") { + return false + } + + if (["tableCell", "tableHeader"].includes(node.type.name)) { + cellCount += 1 + } + }) + + const allCellsSelected = cellCount === selection.ranges.length + + if (!allCellsSelected) { + return false + } + + editor.commands.deleteTable() + + return true +} diff --git a/packages/editor/core/src/ui/extensions/table/table/utilities/get-table-node-types.ts b/packages/editor/core/src/ui/extensions/table/table/utilities/get-table-node-types.ts new file mode 100644 index 00000000000..293878cb0a4 --- /dev/null +++ b/packages/editor/core/src/ui/extensions/table/table/utilities/get-table-node-types.ts @@ -0,0 +1,21 @@ +import { NodeType, Schema } from "prosemirror-model" + +export function getTableNodeTypes(schema: Schema): { [key: string]: NodeType } { + if (schema.cached.tableNodeTypes) { + return schema.cached.tableNodeTypes + } + + const roles: { [key: string]: NodeType } = {} + + Object.keys(schema.nodes).forEach((type) => { + const nodeType = schema.nodes[type] + + if (nodeType.spec.tableRole) { + roles[nodeType.spec.tableRole] = nodeType + } + }) + + schema.cached.tableNodeTypes = roles + + return roles +} diff --git a/packages/editor/core/src/ui/extensions/table/table/utilities/is-cell-selection.ts b/packages/editor/core/src/ui/extensions/table/table/utilities/is-cell-selection.ts new file mode 100644 index 00000000000..3c36bf055e2 --- /dev/null +++ b/packages/editor/core/src/ui/extensions/table/table/utilities/is-cell-selection.ts @@ -0,0 +1,5 @@ +import { CellSelection } from "@tiptap/prosemirror-tables" + +export function isCellSelection(value: unknown): value is CellSelection { + return value instanceof CellSelection +} diff --git a/packages/editor/core/src/ui/hooks/useEditor.tsx b/packages/editor/core/src/ui/hooks/useEditor.tsx new file mode 100644 index 00000000000..7a6e68286ad --- /dev/null +++ b/packages/editor/core/src/ui/hooks/useEditor.tsx @@ -0,0 +1,87 @@ +import { useEditor as useCustomEditor, Editor } from "@tiptap/react"; +import { useImperativeHandle, useRef, MutableRefObject } from "react"; +import { useDebouncedCallback } from "use-debounce"; +import { DeleteImage } from "../../types/delete-image"; +import { CoreEditorProps } from "../props"; +import { CoreEditorExtensions } from "../extensions"; +import { EditorProps } from "@tiptap/pm/view"; +import { getTrimmedHTML } from "../../lib/utils"; +import { UploadImage } from "../../types/upload-image"; + +const DEBOUNCE_DELAY = 1500; + +interface CustomEditorProps { + uploadFile: UploadImage; + setIsSubmitting?: ( + isSubmitting: "submitting" | "submitted" | "saved", + ) => void; + setShouldShowAlert?: (showAlert: boolean) => void; + value: string; + deleteFile: DeleteImage; + debouncedUpdatesEnabled?: boolean; + onChange?: (json: any, html: string) => void; + extensions?: any; + editorProps?: EditorProps; + forwardedRef?: any; +} + +export const useEditor = ({ + uploadFile, + deleteFile, + editorProps = {}, + value, + extensions = [], + onChange, + setIsSubmitting, + debouncedUpdatesEnabled, + forwardedRef, + setShouldShowAlert, +}: CustomEditorProps) => { + console.log("useEditor", typeof value === "string" && value.trim() !== "" ? value : "

"); + const editor = useCustomEditor({ + editorProps: { + ...CoreEditorProps(uploadFile, setIsSubmitting), + ...editorProps, + }, + extensions: [...CoreEditorExtensions(deleteFile), ...extensions], + content: + typeof value === "string" && value.trim() !== "" ? value : "

", + onUpdate: async ({ editor }) => { + // for instant feedback loop + setIsSubmitting?.("submitting"); + setShouldShowAlert?.(true); + if (debouncedUpdatesEnabled) { + debouncedUpdates({ onChange: onChange, editor }); + } else { + onChange?.(editor.getJSON(), getTrimmedHTML(editor.getHTML())); + } + }, + }); + + const editorRef: MutableRefObject = useRef(null); + editorRef.current = editor; + + useImperativeHandle(forwardedRef, () => ({ + clearEditor: () => { + editorRef.current?.commands.clearContent(); + }, + setEditorValue: (content: string) => { + editorRef.current?.commands.setContent(content); + }, + })); + + const debouncedUpdates = useDebouncedCallback( + async ({ onChange, editor }) => { + if (onChange) { + onChange(editor.getJSON(), getTrimmedHTML(editor.getHTML())); + } + }, + DEBOUNCE_DELAY, + ); + + if (!editor) { + return null; + } + + return editor; +}; diff --git a/packages/editor/core/src/ui/hooks/useReadOnlyEditor.tsx b/packages/editor/core/src/ui/hooks/useReadOnlyEditor.tsx new file mode 100644 index 00000000000..3e32c5044c8 --- /dev/null +++ b/packages/editor/core/src/ui/hooks/useReadOnlyEditor.tsx @@ -0,0 +1,43 @@ +import { useEditor as useCustomEditor, Editor } from "@tiptap/react"; +import { useImperativeHandle, useRef, MutableRefObject } from "react"; +import { CoreReadOnlyEditorExtensions } from "../../ui/read-only/extensions"; +import { CoreReadOnlyEditorProps } from "../../ui/read-only/props"; +import { EditorProps } from '@tiptap/pm/view'; + +interface CustomReadOnlyEditorProps { + value: string; + forwardedRef?: any; + extensions?: any; + editorProps?: EditorProps; +} + +export const useReadOnlyEditor = ({ value, forwardedRef, extensions = [], editorProps = {} }: CustomReadOnlyEditorProps) => { + const editor = useCustomEditor({ + editable: false, + content: (typeof value === "string" && value.trim() !== "") ? value : "

", + editorProps: { + ...CoreReadOnlyEditorProps, + ...editorProps, + }, + extensions: [...CoreReadOnlyEditorExtensions, ...extensions], + }); + + const editorRef: MutableRefObject = useRef(null); + editorRef.current = editor; + + useImperativeHandle(forwardedRef, () => ({ + clearEditor: () => { + editorRef.current?.commands.clearContent(); + }, + setEditorValue: (content: string) => { + editorRef.current?.commands.setContent(content); + }, + })); + + + if (!editor) { + return null; + } + + return editor; +}; diff --git a/packages/editor/core/src/ui/index.tsx b/packages/editor/core/src/ui/index.tsx new file mode 100644 index 00000000000..3c64e8ba68d --- /dev/null +++ b/packages/editor/core/src/ui/index.tsx @@ -0,0 +1,92 @@ +"use client" +import * as React from 'react'; +import { Extension } from "@tiptap/react"; +import { UploadImage } from '../types/upload-image'; +import { DeleteImage } from '../types/delete-image'; +import { getEditorClassNames } from '../lib/utils'; +import { EditorProps } from '@tiptap/pm/view'; +import { useEditor } from './hooks/useEditor'; +import { EditorContainer } from '../ui/components/editor-container'; +import { EditorContentWrapper } from '../ui/components/editor-content'; + +interface ICoreEditor { + value: string; + uploadFile: UploadImage; + deleteFile: DeleteImage; + noBorder?: boolean; + borderOnFocus?: boolean; + customClassName?: string; + editorContentCustomClassNames?: string; + onChange?: (json: any, html: string) => void; + setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void; + setShouldShowAlert?: (showAlert: boolean) => void; + editable?: boolean; + forwardedRef?: any; + debouncedUpdatesEnabled?: boolean; + accessValue: string; + onAccessChange: (accessKey: string) => void; + commentAccess: { + icon: string; + key: string; + label: "Private" | "Public"; + }[]; + extensions?: Extension[]; + editorProps?: EditorProps; +} + +interface EditorCoreProps extends ICoreEditor { + forwardedRef?: React.Ref; +} + +interface EditorHandle { + clearEditor: () => void; + setEditorValue: (content: string) => void; +} + +const CoreEditor = ({ + onChange, + debouncedUpdatesEnabled, + editable, + setIsSubmitting, + setShouldShowAlert, + editorContentCustomClassNames, + value, + uploadFile, + deleteFile, + noBorder, + borderOnFocus, + customClassName, + forwardedRef, +}: EditorCoreProps) => { + const editor = useEditor({ + onChange, + debouncedUpdatesEnabled, + editable, + setIsSubmitting, + setShouldShowAlert, + value, + uploadFile, + deleteFile, + forwardedRef, + }); + + const editorClassNames = getEditorClassNames({ noBorder, borderOnFocus, customClassName }); + + if (!editor) return null; + + return ( + +
+ +
+
+ ); +}; + +const CoreEditorWithRef = React.forwardRef((props, ref) => ( + +)); + +CoreEditorWithRef.displayName = "CoreEditorWithRef"; + +export { CoreEditor, CoreEditorWithRef }; diff --git a/packages/editor/core/src/ui/menus/menu-items/index.tsx b/packages/editor/core/src/ui/menus/menu-items/index.tsx new file mode 100644 index 00000000000..f31b6601e77 --- /dev/null +++ b/packages/editor/core/src/ui/menus/menu-items/index.tsx @@ -0,0 +1,109 @@ +import { BoldIcon, Heading1, CheckSquare, Heading2, Heading3, QuoteIcon, ImageIcon, TableIcon, ListIcon, ListOrderedIcon, ItalicIcon, UnderlineIcon, StrikethroughIcon, CodeIcon } from "lucide-react"; +import { Editor } from "@tiptap/react"; +import { UploadImage } from "../../../types/upload-image"; +import { insertImageCommand, insertTableCommand, toggleBlockquote, toggleBold, toggleBulletList, toggleCode, toggleHeadingOne, toggleHeadingThree, toggleHeadingTwo, toggleItalic, toggleOrderedList, toggleStrike, toggleTaskList, toggleUnderline, } from "../../../lib/editor-commands"; + +export interface EditorMenuItem { + name: string; + isActive: () => boolean; + command: () => void; + icon: typeof BoldIcon; +} + +export const HeadingOneItem = (editor: Editor): EditorMenuItem => ({ + name: "H1", + isActive: () => editor.isActive("heading", { level: 1 }), + command: () => toggleHeadingOne(editor), + icon: Heading1, +}) + +export const HeadingTwoItem = (editor: Editor): EditorMenuItem => ({ + name: "H2", + isActive: () => editor.isActive("heading", { level: 2 }), + command: () => toggleHeadingTwo(editor), + icon: Heading2, +}) + +export const HeadingThreeItem = (editor: Editor): EditorMenuItem => ({ + name: "H3", + isActive: () => editor.isActive("heading", { level: 3 }), + command: () => toggleHeadingThree(editor), + icon: Heading3, +}) + +export const BoldItem = (editor: Editor): EditorMenuItem => ({ + name: "bold", + isActive: () => editor?.isActive("bold"), + command: () => toggleBold(editor), + icon: BoldIcon, +}) + +export const ItalicItem = (editor: Editor): EditorMenuItem => ({ + name: "italic", + isActive: () => editor?.isActive("italic"), + command: () => toggleItalic(editor), + icon: ItalicIcon, +}) + +export const UnderLineItem = (editor: Editor): EditorMenuItem => ({ + name: "underline", + isActive: () => editor?.isActive("underline"), + command: () => toggleUnderline(editor), + icon: UnderlineIcon, +}) + +export const StrikeThroughItem = (editor: Editor): EditorMenuItem => ({ + name: "strike", + isActive: () => editor?.isActive("strike"), + command: () => toggleStrike(editor), + icon: StrikethroughIcon, +}) + +export const CodeItem = (editor: Editor): EditorMenuItem => ({ + name: "code", + isActive: () => editor?.isActive("code"), + command: () => toggleCode(editor), + icon: CodeIcon, +}) + +export const BulletListItem = (editor: Editor): EditorMenuItem => ({ + name: "bullet-list", + isActive: () => editor?.isActive("bulletList"), + command: () => toggleBulletList(editor), + icon: ListIcon, +}) + +export const TodoListItem = (editor: Editor): EditorMenuItem => ({ + name: "To-do List", + isActive: () => editor.isActive("taskItem"), + command: () => toggleTaskList(editor), + icon: CheckSquare, +}) + +export const NumberedListItem = (editor: Editor): EditorMenuItem => ({ + name: "ordered-list", + isActive: () => editor?.isActive("orderedList"), + command: () => toggleOrderedList(editor), + icon: ListOrderedIcon +}) + +export const QuoteItem = (editor: Editor): EditorMenuItem => ({ + name: "quote", + isActive: () => editor?.isActive("quote"), + command: () => toggleBlockquote(editor), + icon: QuoteIcon +}) + +export const TableItem = (editor: Editor): EditorMenuItem => ({ + name: "quote", + isActive: () => editor?.isActive("table"), + command: () => insertTableCommand(editor), + icon: TableIcon +}) + +export const ImageItem = (editor: Editor, uploadFile: UploadImage, setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void): EditorMenuItem => ({ + name: "image", + isActive: () => editor?.isActive("image"), + command: () => insertImageCommand(editor, uploadFile, setIsSubmitting), + icon: ImageIcon, +}) diff --git a/web/components/tiptap/plugins/delete-image.tsx b/packages/editor/core/src/ui/plugins/delete-image.tsx similarity index 84% rename from web/components/tiptap/plugins/delete-image.tsx rename to packages/editor/core/src/ui/plugins/delete-image.tsx index fdf515ccc99..ba21d686d75 100644 --- a/web/components/tiptap/plugins/delete-image.tsx +++ b/packages/editor/core/src/ui/plugins/delete-image.tsx @@ -1,6 +1,6 @@ import { EditorState, Plugin, PluginKey, Transaction } from "@tiptap/pm/state"; import { Node as ProseMirrorNode } from "@tiptap/pm/model"; -import fileService from "services/file.service"; +import { DeleteImage } from "../../types/delete-image"; const deleteKey = new PluginKey("delete-image"); const IMAGE_NODE_TYPE = "image"; @@ -12,7 +12,7 @@ interface ImageNode extends ProseMirrorNode { }; } -const TrackImageDeletionPlugin = (): Plugin => +const TrackImageDeletionPlugin = (deleteImage: DeleteImage): Plugin => new Plugin({ key: deleteKey, appendTransaction: (transactions: readonly Transaction[], oldState: EditorState, newState: EditorState) => { @@ -45,7 +45,7 @@ const TrackImageDeletionPlugin = (): Plugin => removedImages.forEach(async (node) => { const src = node.attrs.src; - await onNodeDeleted(src); + await onNodeDeleted(src, deleteImage); }); }); @@ -55,10 +55,10 @@ const TrackImageDeletionPlugin = (): Plugin => export default TrackImageDeletionPlugin; -async function onNodeDeleted(src: string): Promise { +async function onNodeDeleted(src: string, deleteImage: DeleteImage): Promise { try { const assetUrlWithWorkspaceId = new URL(src).pathname.substring(1); - const resStatus = await fileService.deleteImage(assetUrlWithWorkspaceId); + const resStatus = await deleteImage(assetUrlWithWorkspaceId); if (resStatus === 204) { console.log("Image deleted successfully"); } diff --git a/web/components/tiptap/plugins/upload-image.tsx b/packages/editor/core/src/ui/plugins/upload-image.tsx similarity index 78% rename from web/components/tiptap/plugins/upload-image.tsx rename to packages/editor/core/src/ui/plugins/upload-image.tsx index bc0acdc540d..cdd62ae4836 100644 --- a/web/components/tiptap/plugins/upload-image.tsx +++ b/packages/editor/core/src/ui/plugins/upload-image.tsx @@ -1,6 +1,6 @@ +import { UploadImage } from "../../types/upload-image"; import { EditorState, Plugin, PluginKey } from "@tiptap/pm/state"; import { Decoration, DecorationSet, EditorView } from "@tiptap/pm/view"; -import fileService from "services/file.service"; const uploadKey = new PluginKey("upload-image"); @@ -57,7 +57,7 @@ export async function startImageUpload( file: File, view: EditorView, pos: number, - workspaceSlug: string, + uploadFile: UploadImage, setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void ) { if (!file.type.includes("image/")) { @@ -82,11 +82,8 @@ export async function startImageUpload( view.dispatch(tr); }; - if (!workspaceSlug) { - return; - } setIsSubmitting?.("submitting"); - const src = await UploadImageHandler(file, workspaceSlug); + const src = await UploadImageHandler(file, uploadFile); const { schema } = view.state; pos = findPlaceholder(view.state, id); @@ -100,28 +97,30 @@ export async function startImageUpload( view.dispatch(transaction); } -const UploadImageHandler = (file: File, workspaceSlug: string): Promise => { - if (!workspaceSlug) { - return Promise.reject("Workspace slug is missing"); - } +const UploadImageHandler = (file: File, + uploadFile: UploadImage +): Promise => { try { - const formData = new FormData(); - formData.append("asset", file); - formData.append("attributes", JSON.stringify({})); - return new Promise(async (resolve, reject) => { - const imageUrl = await fileService - .uploadFile(workspaceSlug, formData) - .then((response) => response.asset); + try { + const imageUrl = await uploadFile(file) - const image = new Image(); - image.src = imageUrl; - image.onload = () => { - resolve(imageUrl); - }; + const image = new Image(); + image.src = imageUrl; + image.onload = () => { + resolve(imageUrl); + }; + } catch (error) { + if (error instanceof Error) { + console.log(error.message); + } + reject(error); + } }); } catch (error) { - console.log(error); + if (error instanceof Error) { + console.log(error.message); + } return Promise.reject(error); } }; diff --git a/web/components/tiptap/props.tsx b/packages/editor/core/src/ui/props.tsx similarity index 85% rename from web/components/tiptap/props.tsx rename to packages/editor/core/src/ui/props.tsx index 8233e3ab4ab..8f002b76c4c 100644 --- a/web/components/tiptap/props.tsx +++ b/packages/editor/core/src/ui/props.tsx @@ -1,9 +1,10 @@ import { EditorProps } from "@tiptap/pm/view"; +import { findTableAncestor } from "../lib/utils"; import { startImageUpload } from "./plugins/upload-image"; -import { findTableAncestor } from "./table-menu"; +import { UploadImage } from "../types/upload-image"; -export function TiptapEditorProps( - workspaceSlug: string, +export function CoreEditorProps( + uploadFile: UploadImage, setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void ): EditorProps { return { @@ -35,7 +36,7 @@ export function TiptapEditorProps( event.preventDefault(); const file = event.clipboardData.files[0]; const pos = view.state.selection.from; - startImageUpload(file, view, pos, workspaceSlug, setIsSubmitting); + startImageUpload(file, view, pos, uploadFile, setIsSubmitting); return true; } return false; @@ -57,9 +58,8 @@ export function TiptapEditorProps( left: event.clientX, top: event.clientY, }); - // here we deduct 1 from the pos or else the image will create an extra node if (coordinates) { - startImageUpload(file, view, coordinates.pos - 1, workspaceSlug, setIsSubmitting); + startImageUpload(file, view, coordinates.pos - 1, uploadFile, setIsSubmitting); } return true; } diff --git a/packages/editor/core/src/ui/read-only/extensions.tsx b/packages/editor/core/src/ui/read-only/extensions.tsx new file mode 100644 index 00000000000..1aad2afb1a4 --- /dev/null +++ b/packages/editor/core/src/ui/read-only/extensions.tsx @@ -0,0 +1,93 @@ +import StarterKit from "@tiptap/starter-kit"; +import TiptapLink from "@tiptap/extension-link"; +import TiptapUnderline from "@tiptap/extension-underline"; +import TextStyle from "@tiptap/extension-text-style"; +import { Color } from "@tiptap/extension-color"; +import TaskItem from "@tiptap/extension-task-item"; +import TaskList from "@tiptap/extension-task-list"; +import { Markdown } from "tiptap-markdown"; +import Gapcursor from "@tiptap/extension-gapcursor"; + +// import { CustomTableCell } from "../extensions/table/table-cell"; +// import { Table } from "../extensions/table"; +// import { TableHeader } from "../extensions/table/table-header"; +// import { TableRow } from "@tiptap/extension-table-row"; + +import ReadOnlyImageExtension from "../extensions/image/read-only-image"; +import { isValidHttpUrl } from "../../lib/utils"; + +export const CoreReadOnlyEditorExtensions = [ + StarterKit.configure({ + bulletList: { + HTMLAttributes: { + class: "list-disc list-outside leading-3 -mt-2", + }, + }, + orderedList: { + HTMLAttributes: { + class: "list-decimal list-outside leading-3 -mt-2", + }, + }, + listItem: { + HTMLAttributes: { + class: "leading-normal -mb-2", + }, + }, + blockquote: { + HTMLAttributes: { + class: "border-l-4 border-custom-border-300", + }, + }, + code: { + HTMLAttributes: { + class: + "rounded-md bg-custom-primary-30 mx-1 px-1 py-1 font-mono font-medium text-custom-text-1000", + spellcheck: "false", + }, + }, + codeBlock: false, + horizontalRule: false, + dropcursor: { + color: "rgba(var(--color-text-100))", + width: 2, + }, + gapcursor: false, + }), + Gapcursor, + TiptapLink.configure({ + protocols: ["http", "https"], + validate: (url) => isValidHttpUrl(url), + HTMLAttributes: { + class: + "text-custom-primary-300 underline underline-offset-[3px] hover:text-custom-primary-500 transition-colors cursor-pointer", + }, + }), + ReadOnlyImageExtension.configure({ + HTMLAttributes: { + class: "rounded-lg border border-custom-border-300", + }, + }), + TiptapUnderline, + TextStyle, + Color, + TaskList.configure({ + HTMLAttributes: { + class: "not-prose pl-2", + }, + }), + TaskItem.configure({ + HTMLAttributes: { + class: "flex items-start my-4", + }, + nested: true, + }), + Markdown.configure({ + html: true, + transformCopiedText: true, + transformPastedText: true + }), + // Table, + // TableHeader, + // CustomTableCell, + // TableRow, + ]; diff --git a/packages/editor/core/src/ui/read-only/props.tsx b/packages/editor/core/src/ui/read-only/props.tsx new file mode 100644 index 00000000000..25db2b68c1a --- /dev/null +++ b/packages/editor/core/src/ui/read-only/props.tsx @@ -0,0 +1,8 @@ +import { EditorProps } from "@tiptap/pm/view"; + +export const CoreReadOnlyEditorProps: EditorProps = +{ + attributes: { + class: `prose prose-brand max-w-full prose-headings:font-display font-default focus:outline-none`, + }, +}; diff --git a/packages/editor/core/tailwind.config.js b/packages/editor/core/tailwind.config.js new file mode 100644 index 00000000000..f3206315889 --- /dev/null +++ b/packages/editor/core/tailwind.config.js @@ -0,0 +1,6 @@ +const sharedConfig = require("tailwind-config-custom/tailwind.config.js"); + +module.exports = { + // prefix ui lib classes to avoid conflicting with the app + ...sharedConfig, +}; diff --git a/packages/editor/core/tsconfig.json b/packages/editor/core/tsconfig.json new file mode 100644 index 00000000000..61a60f3244c --- /dev/null +++ b/packages/editor/core/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "tsconfig/react.json", + "include": [ + "src/**/*", + "index.d.ts" + ], + "exclude": [ + "dist", + "build", + "node_modules" + ] +} diff --git a/packages/editor/core/tsup.config.ts b/packages/editor/core/tsup.config.ts new file mode 100644 index 00000000000..5e89e04afad --- /dev/null +++ b/packages/editor/core/tsup.config.ts @@ -0,0 +1,11 @@ +import { defineConfig, Options } from "tsup"; + +export default defineConfig((options: Options) => ({ + entry: ["src/index.ts"], + format: ["cjs", "esm"], + dts: true, + clean: false, + external: ["react"], + injectStyle: true, + ...options, +})); diff --git a/packages/editor/lite-text-editor/Readme.md b/packages/editor/lite-text-editor/Readme.md new file mode 100644 index 00000000000..948e2c34b2d --- /dev/null +++ b/packages/editor/lite-text-editor/Readme.md @@ -0,0 +1,97 @@ +# @plane/lite-text-editor + +## Description + +The `@plane/lite-text-editor` package extends from the `editor-core` package, inheriting its base functionality while adding its own unique features of Custom control over Enter key, etc. + +## Key Features + +- **Exported Components**: There are two components exported from the Lite text editor (with and without Ref), you can choose to use the `withRef` instance whenever you want to control the Editor’s state via a side effect of some external action from within the application code. + + `LiteTextEditor` & `LiteTextEditorWithRef` + +- **Read Only Editor Instances**: We have added a really light weight *Read Only* Editor instance for the Lite editor types (with and without Ref) + `LiteReadOnlyEditor` &`LiteReadOnlyEditorWithRef` + +## LiteTextEditor + +| Prop | Type | Description | +| --- | --- | --- | +| `uploadFile` | `(file: File) => Promise` | A function that handles file upload. It takes a file as input and handles the process of uploading that file. | +| `deleteFile` | `(assetUrlWithWorkspaceId: string) => Promise` | A function that handles deleting an image. It takes the asset url from your bucket and handles the process of deleting that image. | +| `value` | `html string` | The initial content of the editor. | +| `onEnterKeyPress` | `(e) => void` | The event that happens on Enter key press | +| `debouncedUpdatesEnabled` | `boolean` | If set to true, the `onChange` event handler is debounced, meaning it will only be invoked after the specified delay (default 1500ms) once the user has stopped typing. | +| `onChange` | `(json: any, html: string) => void` | This function is invoked whenever the content of the editor changes. It is passed the new content in both JSON and HTML formats. | +| `setIsSubmitting` | `(isSubmitting: "submitting" \| "submitted" \| "saved") => void` | This function is called to update the submission status. | +| `setShouldShowAlert` | `(showAlert: boolean) => void` | This function is used to show or hide an alert incase of content not being "saved". | +| `noBorder` | `boolean` | If set to true, the editor will not have a border. | +| `borderOnFocus` | `boolean` | If set to true, the editor will show a border when it is focused. | +| `customClassName` | `string` | This is a custom CSS class that can be applied to the editor. | +| `editorContentCustomClassNames` | `string` | This is a custom CSS class that can be applied to the editor content. | + +### Usage + +1. Here is an example of how to use the `RichTextEditor` component + +```tsx + { + onChange(comment_html); + }} + /> +``` + +2. Example of how to use the `LiteTextEditorWithRef` component + +```tsx + const editorRef = useRef(null); + + // can use it to set the editor's value + editorRef.current?.setEditorValue(`${watch("description_html")}`); + + // can use it to clear the editor + editorRef?.current?.clearEditor(); + + return ( + { + onChange(comment_html); + }} + /> +) +``` + +## LiteReadOnlyEditor + +| Prop | Type | Description | +| --- | --- | --- | +| `value` | `html string` | The initial content of the editor. | +| `noBorder` | `boolean` | If set to true, the editor will not have a border. | +| `borderOnFocus` | `boolean` | If set to true, the editor will show a border when it is focused. | +| `customClassName` | `string` | This is a custom CSS class that can be applied to the editor. | +| `editorContentCustomClassNames` | `string` | This is a custom CSS class that can be applied to the editor content. | + +### Usage + +Here is an example of how to use the `RichReadOnlyEditor` component + +```tsx + +``` diff --git a/packages/editor/lite-text-editor/package.json b/packages/editor/lite-text-editor/package.json new file mode 100644 index 00000000000..47ef154c69e --- /dev/null +++ b/packages/editor/lite-text-editor/package.json @@ -0,0 +1,63 @@ +{ + "name": "@plane/lite-text-editor", + "version": "0.0.1", + "description": "Package that powers Plane's Comment Editor", + "main": "./dist/index.mjs", + "module": "./dist/index.mjs", + "types": "./dist/index.d.mts", + "files": [ + "dist/**/*" + ], + "exports": { + ".": { + "types": "./dist/index.d.mts", + "import": "./dist/index.mjs", + "module": "./dist/index.mjs" + } + }, + "scripts": { + "build": "tsup", + "dev": "tsup --watch", + "check-types": "tsc --noEmit" + }, + "peerDependencies": { + "next": "12.3.2", + "next-themes": "^0.2.1", + "react": "^18.2.0", + "react-dom": "18.2.0" + }, + "dependencies": { + "@plane/editor-core": "*", + "@tiptap/extension-list-item": "^2.1.11", + "@types/node": "18.15.3", + "@types/react": "^18.2.5", + "@types/react-dom": "18.0.11", + "class-variance-authority": "^0.7.0", + "clsx": "^1.2.1", + "eslint": "8.36.0", + "eslint-config-next": "13.2.4", + "eventsource-parser": "^0.1.0", + "lowlight": "^2.9.0", + "lucide-react": "^0.244.0", + "react-markdown": "^8.0.7", + "tailwind-merge": "^1.14.0", + "tippy.js": "^6.3.7", + "tiptap-markdown": "^0.8.2", + "use-debounce": "^9.0.4" + }, + "devDependencies": { + "eslint": "^7.32.0", + "postcss": "^8.4.29", + "tailwind-config-custom": "*", + "tsconfig": "*", + "tsup": "^7.2.0", + "typescript": "4.9.5" + }, + "keywords": [ + "editor", + "rich-text", + "markdown", + "nextjs", + "react" + ] +} diff --git a/packages/editor/lite-text-editor/postcss.config.js b/packages/editor/lite-text-editor/postcss.config.js new file mode 100644 index 00000000000..07aa434b2bf --- /dev/null +++ b/packages/editor/lite-text-editor/postcss.config.js @@ -0,0 +1,9 @@ +// If you want to use other PostCSS plugins, see the following: +// https://tailwindcss.com/docs/using-with-preprocessors + +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/packages/editor/lite-text-editor/src/index.ts b/packages/editor/lite-text-editor/src/index.ts new file mode 100644 index 00000000000..de9323b3c5d --- /dev/null +++ b/packages/editor/lite-text-editor/src/index.ts @@ -0,0 +1,2 @@ +export { LiteTextEditor, LiteTextEditorWithRef } from "./ui"; +export { LiteReadOnlyEditor, LiteReadOnlyEditorWithRef } from "./ui/read-only"; diff --git a/packages/editor/lite-text-editor/src/ui/extensions/custom-list-extension.tsx b/packages/editor/lite-text-editor/src/ui/extensions/custom-list-extension.tsx new file mode 100644 index 00000000000..f0bc70cffd2 --- /dev/null +++ b/packages/editor/lite-text-editor/src/ui/extensions/custom-list-extension.tsx @@ -0,0 +1,9 @@ +import ListItem from '@tiptap/extension-list-item' + +export const CustomListItem = ListItem.extend({ + addKeyboardShortcuts() { + return { + 'Shift-Enter': () => this.editor.chain().focus().splitListItem('listItem').run(), + } + }, +}) diff --git a/packages/editor/lite-text-editor/src/ui/extensions/enter-key-extension.tsx b/packages/editor/lite-text-editor/src/ui/extensions/enter-key-extension.tsx new file mode 100644 index 00000000000..04c4a1fbe92 --- /dev/null +++ b/packages/editor/lite-text-editor/src/ui/extensions/enter-key-extension.tsx @@ -0,0 +1,16 @@ +import { Extension } from '@tiptap/core'; + +export const EnterKeyExtension = (onEnterKeyPress?: () => void) => Extension.create({ + name: 'enterKey', + + addKeyboardShortcuts() { + return { + 'Enter': () => { + if (onEnterKeyPress) { + onEnterKeyPress(); + } + return true; + }, + } + }, +}); diff --git a/packages/editor/lite-text-editor/src/ui/extensions/index.tsx b/packages/editor/lite-text-editor/src/ui/extensions/index.tsx new file mode 100644 index 00000000000..ccd04a395f9 --- /dev/null +++ b/packages/editor/lite-text-editor/src/ui/extensions/index.tsx @@ -0,0 +1,7 @@ +import { CustomListItem } from "./custom-list-extension"; +import { EnterKeyExtension } from "./enter-key-extension"; + +export const LiteTextEditorExtensions = (onEnterKeyPress?: () => void) => [ + CustomListItem, + EnterKeyExtension(onEnterKeyPress), +]; diff --git a/packages/editor/lite-text-editor/src/ui/index.tsx b/packages/editor/lite-text-editor/src/ui/index.tsx new file mode 100644 index 00000000000..7a497d9aab8 --- /dev/null +++ b/packages/editor/lite-text-editor/src/ui/index.tsx @@ -0,0 +1,95 @@ +"use client" +import * as React from 'react'; +import { EditorContainer, EditorContentWrapper, getEditorClassNames, useEditor } from '@plane/editor-core'; +import { FixedMenu } from './menus/fixed-menu'; +import { LiteTextEditorExtensions } from './extensions'; + +export type UploadImage = (file: File) => Promise; +export type DeleteImage = (assetUrlWithWorkspaceId: string) => Promise; + +interface ILiteTextEditor { + value: string; + uploadFile: UploadImage; + deleteFile: DeleteImage; + noBorder?: boolean; + borderOnFocus?: boolean; + customClassName?: string; + editorContentCustomClassNames?: string; + onChange?: (json: any, html: string) => void; + setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void; + setShouldShowAlert?: (showAlert: boolean) => void; + forwardedRef?: any; + debouncedUpdatesEnabled?: boolean; + commentAccessSpecifier?: { + accessValue: string, + onAccessChange: (accessKey: string) => void, + showAccessSpecifier: boolean, + commentAccess: { + icon: string; + key: string; + label: "Private" | "Public"; + }[] + }; + onEnterKeyPress?: (e?: any) => void; +} + +interface LiteTextEditorProps extends ILiteTextEditor { + forwardedRef?: React.Ref; +} + +interface EditorHandle { + clearEditor: () => void; + setEditorValue: (content: string) => void; +} + +const LiteTextEditor = ({ + onChange, + debouncedUpdatesEnabled, + setIsSubmitting, + setShouldShowAlert, + editorContentCustomClassNames, + value, + uploadFile, + deleteFile, + noBorder, + borderOnFocus, + customClassName, + forwardedRef, + commentAccessSpecifier, + onEnterKeyPress +}: LiteTextEditorProps) => { + const editor = useEditor({ + onChange, + debouncedUpdatesEnabled, + setIsSubmitting, + setShouldShowAlert, + value, + uploadFile, + deleteFile, + forwardedRef, + extensions: LiteTextEditorExtensions(onEnterKeyPress), + }); + + const editorClassNames = getEditorClassNames({ noBorder, borderOnFocus, customClassName }); + + if (!editor) return null; + + return ( + +
+ +
+ +
+
+
+ ); +}; + +const LiteTextEditorWithRef = React.forwardRef((props, ref) => ( + +)); + +LiteTextEditorWithRef.displayName = "LiteTextEditorWithRef"; + +export { LiteTextEditor, LiteTextEditorWithRef }; diff --git a/packages/editor/lite-text-editor/src/ui/menus/fixed-menu/icon.tsx b/packages/editor/lite-text-editor/src/ui/menus/fixed-menu/icon.tsx new file mode 100644 index 00000000000..c0006b3f257 --- /dev/null +++ b/packages/editor/lite-text-editor/src/ui/menus/fixed-menu/icon.tsx @@ -0,0 +1,13 @@ +import React from "react"; + +type Props = { + iconName: string; + className?: string; +}; + +export const Icon: React.FC = ({ iconName, className = "" }) => ( + + {iconName} + +); + diff --git a/packages/editor/lite-text-editor/src/ui/menus/fixed-menu/index.tsx b/packages/editor/lite-text-editor/src/ui/menus/fixed-menu/index.tsx new file mode 100644 index 00000000000..72a537e7e37 --- /dev/null +++ b/packages/editor/lite-text-editor/src/ui/menus/fixed-menu/index.tsx @@ -0,0 +1,170 @@ +import { Editor } from "@tiptap/react"; +import { BoldIcon } from "lucide-react"; + +import { BoldItem, BulletListItem, cn, CodeItem, ImageItem, ItalicItem, NumberedListItem, QuoteItem, StrikeThroughItem, TableItem, UnderLineItem } from "@plane/editor-core"; +import { Icon } from "./icon"; +import { Tooltip } from "../../tooltip"; +import { UploadImage } from "../.."; + +export interface BubbleMenuItem { + name: string; + isActive: () => boolean; + command: () => void; + icon: typeof BoldIcon; +} + +type EditorBubbleMenuProps = { + editor: Editor; + commentAccessSpecifier?: { + accessValue: string, + onAccessChange: (accessKey: string) => void, + showAccessSpecifier: boolean, + commentAccess: { + icon: string; + key: string; + label: "Private" | "Public"; + }[] | undefined; + } + uploadFile: UploadImage; + setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void; +} + +export const FixedMenu = (props: EditorBubbleMenuProps) => { + const basicMarkItems: BubbleMenuItem[] = [ + BoldItem(props.editor), + ItalicItem(props.editor), + UnderLineItem(props.editor), + StrikeThroughItem(props.editor), + ]; + + const listItems: BubbleMenuItem[] = [ + BulletListItem(props.editor), + NumberedListItem(props.editor), + ]; + + const userActionItems: BubbleMenuItem[] = [ + QuoteItem(props.editor), + CodeItem(props.editor), + ]; + + const complexItems: BubbleMenuItem[] = [ + TableItem(props.editor), + ImageItem(props.editor, props.uploadFile, props.setIsSubmitting), + ]; + + const handleAccessChange = (accessKey: string) => { + props.commentAccessSpecifier?.onAccessChange(accessKey); + }; + + + return ( +
+ {props.commentAccessSpecifier && (
+ {props?.commentAccessSpecifier.commentAccess?.map((access) => ( + + + + ))} +
)} +
+ {basicMarkItems.map((item, index) => ( + + ))} +
+
+ {listItems.map((item, index) => ( + + ))} +
+
+ {userActionItems.map((item, index) => ( + + ))} +
+
+ {complexItems.map((item, index) => ( + + ))} +
+
+ ); +}; diff --git a/packages/editor/lite-text-editor/src/ui/read-only/index.tsx b/packages/editor/lite-text-editor/src/ui/read-only/index.tsx new file mode 100644 index 00000000000..3990cb734f5 --- /dev/null +++ b/packages/editor/lite-text-editor/src/ui/read-only/index.tsx @@ -0,0 +1,54 @@ +"use client" +import { EditorContainer, EditorContentWrapper, getEditorClassNames, useReadOnlyEditor } from '@plane/editor-core'; +import * as React from 'react'; + +interface ICoreReadOnlyEditor { + value: string; + editorContentCustomClassNames?: string; + noBorder?: boolean; + borderOnFocus?: boolean; + customClassName?: string; +} + +interface EditorCoreProps extends ICoreReadOnlyEditor { + forwardedRef?: React.Ref; +} + +interface EditorHandle { + clearEditor: () => void; + setEditorValue: (content: string) => void; +} + +const LiteReadOnlyEditor = ({ + editorContentCustomClassNames, + noBorder, + borderOnFocus, + customClassName, + value, + forwardedRef, +}: EditorCoreProps) => { + const editor = useReadOnlyEditor({ + value, + forwardedRef, + }); + + const editorClassNames = getEditorClassNames({ noBorder, borderOnFocus, customClassName }); + + if (!editor) return null; + + return ( + +
+ +
+
+ ); +}; + +const LiteReadOnlyEditorWithRef = React.forwardRef((props, ref) => ( + +)); + +LiteReadOnlyEditorWithRef.displayName = "LiteReadOnlyEditorWithRef"; + +export { LiteReadOnlyEditor , LiteReadOnlyEditorWithRef }; diff --git a/packages/editor/lite-text-editor/src/ui/tooltip.tsx b/packages/editor/lite-text-editor/src/ui/tooltip.tsx new file mode 100644 index 00000000000..f29d8a49177 --- /dev/null +++ b/packages/editor/lite-text-editor/src/ui/tooltip.tsx @@ -0,0 +1,77 @@ +import * as React from 'react'; + +// next-themes +import { useTheme } from "next-themes"; +// tooltip2 +import { Tooltip2 } from "@blueprintjs/popover2"; + +type Props = { + tooltipHeading?: string; + tooltipContent: string | React.ReactNode; + position?: + | "top" + | "right" + | "bottom" + | "left" + | "auto" + | "auto-end" + | "auto-start" + | "bottom-left" + | "bottom-right" + | "left-bottom" + | "left-top" + | "right-bottom" + | "right-top" + | "top-left" + | "top-right"; + children: JSX.Element; + disabled?: boolean; + className?: string; + openDelay?: number; + closeDelay?: number; +}; + +export const Tooltip: React.FC = ({ + tooltipHeading, + tooltipContent, + position = "top", + children, + disabled = false, + className = "", + openDelay = 200, + closeDelay, +}) => { + const { theme } = useTheme(); + + return ( + + {tooltipHeading && ( +
+ {tooltipHeading} +
+ )} + {tooltipContent} + + } + position={position} + renderTarget={({ isOpen: isTooltipOpen, ref: eleReference, ...tooltipProps }) => + React.cloneElement(children, { ref: eleReference, ...tooltipProps, ...children.props }) + } + /> + ); +}; diff --git a/packages/editor/lite-text-editor/tailwind.config.js b/packages/editor/lite-text-editor/tailwind.config.js new file mode 100644 index 00000000000..f3206315889 --- /dev/null +++ b/packages/editor/lite-text-editor/tailwind.config.js @@ -0,0 +1,6 @@ +const sharedConfig = require("tailwind-config-custom/tailwind.config.js"); + +module.exports = { + // prefix ui lib classes to avoid conflicting with the app + ...sharedConfig, +}; diff --git a/packages/editor/lite-text-editor/tsconfig.json b/packages/editor/lite-text-editor/tsconfig.json new file mode 100644 index 00000000000..61a60f3244c --- /dev/null +++ b/packages/editor/lite-text-editor/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "tsconfig/react.json", + "include": [ + "src/**/*", + "index.d.ts" + ], + "exclude": [ + "dist", + "build", + "node_modules" + ] +} diff --git a/packages/editor/lite-text-editor/tsup.config.ts b/packages/editor/lite-text-editor/tsup.config.ts new file mode 100644 index 00000000000..5e89e04afad --- /dev/null +++ b/packages/editor/lite-text-editor/tsup.config.ts @@ -0,0 +1,11 @@ +import { defineConfig, Options } from "tsup"; + +export default defineConfig((options: Options) => ({ + entry: ["src/index.ts"], + format: ["cjs", "esm"], + dts: true, + clean: false, + external: ["react"], + injectStyle: true, + ...options, +})); diff --git a/packages/editor/rich-text-editor/Readme.md b/packages/editor/rich-text-editor/Readme.md new file mode 100644 index 00000000000..c8414f62d6a --- /dev/null +++ b/packages/editor/rich-text-editor/Readme.md @@ -0,0 +1,99 @@ +# @plane/rich-text-editor + +## Description + +The `@plane/rich-text-editor` package extends from the `editor-core` package, inheriting its base functionality while adding its own unique features of Slash Commands and many more. + +## Key Features + +- **Exported Components**: There are two components exported from the Rich text editor (with and without Ref), you can choose to use the `withRef` instance whenever you want to control the Editor’s state via a side effect of some external action from within the application code. + + `RichTextEditor` & `RichTextEditorWithRef` + +- **Read Only Editor Instances**: We have added a really light weight *Read Only* Editor instance for the Rich editor types (with and without Ref) + `RichReadOnlyEditor` &`RichReadOnlyEditorWithRef` + +## RichTextEditor + +| Prop | Type | Description | +| --- | --- | --- | +| `uploadFile` | `(file: File) => Promise` | A function that handles file upload. It takes a file as input and handles the process of uploading that file. | +| `deleteFile` | `(assetUrlWithWorkspaceId: string) => Promise` | A function that handles deleting an image. It takes the asset url from your bucket and handles the process of deleting that image. | +| `value` | `html string` | The initial content of the editor. | +| `debouncedUpdatesEnabled` | `boolean` | If set to true, the `onChange` event handler is debounced, meaning it will only be invoked after the specified delay (default 1500ms) once the user has stopped typing. | +| `onChange` | `(json: any, html: string) => void` | This function is invoked whenever the content of the editor changes. It is passed the new content in both JSON and HTML formats. | +| `setIsSubmitting` | `(isSubmitting: "submitting" \| "submitted" \| "saved") => void` | This function is called to update the submission status. | +| `setShouldShowAlert` | `(showAlert: boolean) => void` | This function is used to show or hide an alert incase of content not being "saved". | +| `noBorder` | `boolean` | If set to true, the editor will not have a border. | +| `borderOnFocus` | `boolean` | If set to true, the editor will show a border when it is focused. | +| `customClassName` | `string` | This is a custom CSS class that can be applied to the editor. | +| `editorContentCustomClassNames` | `string` | This is a custom CSS class that can be applied to the editor content. | + +### Usage + +1. Here is an example of how to use the `RichTextEditor` component + +```tsx + { + setShowAlert(true); + setIsSubmitting("submitting"); + onChange(description_html); + // custom stuff you want to do + }} +/> +``` + +2. Example of how to use the `RichTextEditorWithRef` component + +```tsx + const editorRef = useRef(null); + + // can use it to set the editor's value + editorRef.current?.setEditorValue(`${watch("description_html")}`); + + // can use it to clear the editor + editorRef?.current?.clearEditor(); + + return ( { + onChange(description_html); + // custom stuff you want to do + } } />) +``` + +## RichReadOnlyEditor + +| Prop | Type | Description | +| --- | --- | --- | +| `value` | `html string` | The initial content of the editor. | +| `noBorder` | `boolean` | If set to true, the editor will not have a border. | +| `borderOnFocus` | `boolean` | If set to true, the editor will show a border when it is focused. | +| `customClassName` | `string` | This is a custom CSS class that can be applied to the editor. | +| `editorContentCustomClassNames` | `string` | This is a custom CSS class that can be applied to the editor content. | + +### Usage + +Here is an example of how to use the `RichReadOnlyEditor` component + +```tsx + +``` diff --git a/packages/editor/rich-text-editor/package.json b/packages/editor/rich-text-editor/package.json new file mode 100644 index 00000000000..7bdd0a58b1d --- /dev/null +++ b/packages/editor/rich-text-editor/package.json @@ -0,0 +1,61 @@ +{ + "name": "@plane/rich-text-editor", + "version": "0.0.1", + "description": "Rich Text Editor that powers Plane", + "main": "./dist/index.mjs", + "module": "./dist/index.mjs", + "types": "./dist/index.d.mts", + "files": [ + "dist/**/*" + ], + "exports": { + ".": { + "types": "./dist/index.d.mts", + "import": "./dist/index.mjs", + "module": "./dist/index.mjs" + } + }, + "scripts": { + "build": "tsup", + "dev": "tsup --watch", + "check-types": "tsc --noEmit" + }, + "peerDependencies": { + "next": "12.3.2", + "next-themes": "^0.2.1", + "react": "^18.2.0", + "react-dom": "18.2.0", + "@tiptap/core": "^2.1.11" + }, + "dependencies": { + "@plane/editor-core": "*", + "@tiptap/extension-code-block-lowlight": "^2.1.11", + "@tiptap/extension-horizontal-rule": "^2.1.11", + "@tiptap/extension-placeholder": "^2.1.11", + "class-variance-authority": "^0.7.0", + "@tiptap/suggestion": "^2.1.7", + "clsx": "^1.2.1", + "highlight.js": "^11.8.0", + "lowlight": "^3.0.0", + "lucide-react": "^0.244.0" + }, + "devDependencies": { + "@types/node": "18.15.3", + "@types/react": "^18.2.5", + "@types/react-dom": "18.0.11", + "eslint": "^7.32.0", + "postcss": "^8.4.29", + "react": "^18.2.0", + "tailwind-config-custom": "*", + "tsconfig": "*", + "tsup": "^7.2.0", + "typescript": "4.9.5" + }, + "keywords": [ + "editor", + "rich-text", + "markdown", + "nextjs", + "react" + ] +} diff --git a/packages/editor/rich-text-editor/postcss.config.js b/packages/editor/rich-text-editor/postcss.config.js new file mode 100644 index 00000000000..07aa434b2bf --- /dev/null +++ b/packages/editor/rich-text-editor/postcss.config.js @@ -0,0 +1,9 @@ +// If you want to use other PostCSS plugins, see the following: +// https://tailwindcss.com/docs/using-with-preprocessors + +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/packages/editor/rich-text-editor/src/index.ts b/packages/editor/rich-text-editor/src/index.ts new file mode 100644 index 00000000000..36d0a95f903 --- /dev/null +++ b/packages/editor/rich-text-editor/src/index.ts @@ -0,0 +1,4 @@ +import "./styles/github-dark.css"; + +export { RichTextEditor, RichTextEditorWithRef } from "./ui"; +export { RichReadOnlyEditor, RichReadOnlyEditorWithRef } from "./ui/read-only"; diff --git a/packages/editor/rich-text-editor/src/styles/github-dark.css b/packages/editor/rich-text-editor/src/styles/github-dark.css new file mode 100644 index 00000000000..20a7f4e66bf --- /dev/null +++ b/packages/editor/rich-text-editor/src/styles/github-dark.css @@ -0,0 +1,2 @@ +pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px} +.hljs{color:#c9d1d9;background:#0d1117}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#ff7b72}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#d2a8ff}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable{color:#79c0ff}.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#a5d6ff}.hljs-built_in,.hljs-symbol{color:#ffa657}.hljs-code,.hljs-comment,.hljs-formula{color:#8b949e}.hljs-name,.hljs-quote,.hljs-selector-pseudo,.hljs-selector-tag{color:#7ee787}.hljs-subst{color:#c9d1d9}.hljs-section{color:#1f6feb;font-weight:700}.hljs-bullet{color:#f2cc60}.hljs-emphasis{color:#c9d1d9;font-style:italic}.hljs-strong{color:#c9d1d9;font-weight:700}.hljs-addition{color:#aff5b4;background-color:#033a16}.hljs-deletion{color:#ffdcd7;background-color:#67060c} diff --git a/packages/editor/rich-text-editor/src/ui/extensions/index.tsx b/packages/editor/rich-text-editor/src/ui/extensions/index.tsx new file mode 100644 index 00000000000..1b5db0de30d --- /dev/null +++ b/packages/editor/rich-text-editor/src/ui/extensions/index.tsx @@ -0,0 +1,74 @@ +import HorizontalRule from "@tiptap/extension-horizontal-rule"; +import Placeholder from "@tiptap/extension-placeholder"; +import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight"; +import { common, createLowlight } from "lowlight"; +import { InputRule, mergeAttributes } from "@tiptap/core"; + +import ts from "highlight.js/lib/languages/typescript"; + +import SlashCommand from "./slash-command"; +import { UploadImage } from "../"; + +const lowlight = createLowlight(common); +lowlight.register("ts", ts); + +export const RichTextEditorExtensions = ( + uploadFile: UploadImage, + setIsSubmitting?: ( + isSubmitting: "submitting" | "submitted" | "saved", + ) => void, +) => [ + HorizontalRule.extend({ + parseHTML() { + return [ + { + tag: `div[data-type="${this.name}"]`, + }, + ]; + }, + + renderHTML({ HTMLAttributes }) { + return [ + "div", + mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, { + "data-type": this.name, + }), + ["div", {}], + ]; + }, + addInputRules() { + return [ + new InputRule({ + find: /^(?:---|—-|___\s|\*\*\*\s)$/, + handler: ({ state, range, commands }) => { + commands.splitBlock(); + + const attributes = {}; + const { tr } = state; + const start = range.from; + const end = range.to; + // @ts-ignore + tr.replaceWith(start - 1, end, this.type.create(attributes)); + }, + }), + ]; + }, + }), + SlashCommand(uploadFile, setIsSubmitting), + CodeBlockLowlight.configure({ + lowlight, + }), + Placeholder.configure({ + placeholder: ({ node }) => { + if (node.type.name === "heading") { + return `Heading ${node.attrs.level}`; + } + if (node.type.name === "image" || node.type.name === "table") { + return ""; + } + + return "Press '/' for commands..."; + }, + includeChildren: true, + }), +]; diff --git a/space/components/tiptap/slash-command/index.tsx b/packages/editor/rich-text-editor/src/ui/extensions/slash-command.tsx similarity index 51% rename from space/components/tiptap/slash-command/index.tsx rename to packages/editor/rich-text-editor/src/ui/extensions/slash-command.tsx index 46bf5ea5a34..e00585dd82a 100644 --- a/space/components/tiptap/slash-command/index.tsx +++ b/packages/editor/rich-text-editor/src/ui/extensions/slash-command.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useCallback, ReactNode, useRef, useLayoutEffect } from "react"; +import { useState, useEffect, useCallback, ReactNode, useRef, useLayoutEffect } from "react"; import { Editor, Range, Extension } from "@tiptap/core"; import Suggestion from "@tiptap/suggestion"; import { ReactRenderer } from "@tiptap/react"; @@ -17,8 +17,8 @@ import { ImageIcon, Table, } from "lucide-react"; -import { startImageUpload } from "../plugins/upload-image"; -import { cn } from "../utils"; +import { UploadImage } from "../"; +import { cn, insertTableCommand, toggleBlockquote, toggleBulletList, toggleOrderedList, toggleTaskList, insertImageCommand, toggleHeadingOne, toggleHeadingTwo, toggleHeadingThree } from "@plane/editor-core"; interface CommandItemProps { title: string; @@ -58,151 +58,128 @@ const Command = Extension.create({ const getSuggestionItems = ( - workspaceSlug: string, + uploadFile: UploadImage, setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void ) => - ({ query }: { query: string }) => - [ - { - title: "Text", - description: "Just start typing with plain text.", - searchTerms: ["p", "paragraph"], - icon: , - command: ({ editor, range }: CommandProps) => { - editor.chain().focus().deleteRange(range).toggleNode("paragraph", "paragraph").run(); + ({ query }: { query: string }) => + [ + { + title: "Text", + description: "Just start typing with plain text.", + searchTerms: ["p", "paragraph"], + icon: , + command: ({ editor, range }: CommandProps) => { + editor.chain().focus().deleteRange(range).toggleNode("paragraph", "paragraph").run(); + }, }, - }, - { - title: "Heading 1", - description: "Big section heading.", - searchTerms: ["title", "big", "large"], - icon: , - command: ({ editor, range }: CommandProps) => { - editor.chain().focus().deleteRange(range).setNode("heading", { level: 1 }).run(); + { + title: "Heading 1", + description: "Big section heading.", + searchTerms: ["title", "big", "large"], + icon: , + command: ({ editor, range }: CommandProps) => { + toggleHeadingOne(editor, range); + }, }, - }, - { - title: "Heading 2", - description: "Medium section heading.", - searchTerms: ["subtitle", "medium"], - icon: , - command: ({ editor, range }: CommandProps) => { - editor.chain().focus().deleteRange(range).setNode("heading", { level: 2 }).run(); + { + title: "Heading 2", + description: "Medium section heading.", + searchTerms: ["subtitle", "medium"], + icon: , + command: ({ editor, range }: CommandProps) => { + toggleHeadingTwo(editor, range); + }, }, - }, - { - title: "Heading 3", - description: "Small section heading.", - searchTerms: ["subtitle", "small"], - icon: , - command: ({ editor, range }: CommandProps) => { - editor.chain().focus().deleteRange(range).setNode("heading", { level: 3 }).run(); + { + title: "Heading 3", + description: "Small section heading.", + searchTerms: ["subtitle", "small"], + icon: , + command: ({ editor, range }: CommandProps) => { + toggleHeadingThree(editor, range); + }, }, - }, - { - title: "To-do List", - description: "Track tasks with a to-do list.", - searchTerms: ["todo", "task", "list", "check", "checkbox"], - icon: , - command: ({ editor, range }: CommandProps) => { - editor.chain().focus().deleteRange(range).toggleTaskList().run(); + { + title: "To-do List", + description: "Track tasks with a to-do list.", + searchTerms: ["todo", "task", "list", "check", "checkbox"], + icon: , + command: ({ editor, range }: CommandProps) => { + toggleTaskList(editor, range) + }, }, - }, - { - title: "Bullet List", - description: "Create a simple bullet list.", - searchTerms: ["unordered", "point"], - icon: , - command: ({ editor, range }: CommandProps) => { - editor.chain().focus().deleteRange(range).toggleBulletList().run(); + { + title: "Bullet List", + description: "Create a simple bullet list.", + searchTerms: ["unordered", "point"], + icon: , + command: ({ editor, range }: CommandProps) => { + toggleBulletList(editor, range); + }, }, - }, - { - title: "Divider", - description: "Visually divide blocks", - searchTerms: ["line", "divider", "horizontal", "rule", "separate"], - icon: , - command: ({ editor, range }: CommandProps) => { - editor.chain().focus().deleteRange(range).setHorizontalRule().run(); + { + title: "Divider", + description: "Visually divide blocks", + searchTerms: ["line", "divider", "horizontal", "rule", "separate"], + icon: , + command: ({ editor, range }: CommandProps) => { + editor.chain().focus().deleteRange(range).setHorizontalRule().run(); + }, }, - }, - { - title: "Table", - description: "Create a Table", - searchTerms: ["table", "cell", "db", "data", "tabular"], - icon: , - command: ({ editor, range }: CommandProps) => { - editor - .chain() - .focus() - .deleteRange(range) - .insertTable({ rows: 3, cols: 3, withHeaderRow: true }) - .run(); + { + title: "Table", + description: "Create a Table", + searchTerms: ["table", "cell", "db", "data", "tabular"], + icon:
, + command: ({ editor, range }: CommandProps) => { + insertTableCommand(editor, range); + }, }, - }, - { - title: "Numbered List", - description: "Create a list with numbering.", - searchTerms: ["ordered"], - icon: , - command: ({ editor, range }: CommandProps) => { - editor.chain().focus().deleteRange(range).toggleOrderedList().run(); + { + title: "Numbered List", + description: "Create a list with numbering.", + searchTerms: ["ordered"], + icon: , + command: ({ editor, range }: CommandProps) => { + toggleOrderedList(editor, range) + }, }, - }, - { - title: "Quote", - description: "Capture a quote.", - searchTerms: ["blockquote"], - icon: , - command: ({ editor, range }: CommandProps) => - editor - .chain() - .focus() - .deleteRange(range) - .toggleNode("paragraph", "paragraph") - .toggleBlockquote() - .run(), - }, - { - title: "Code", - description: "Capture a code snippet.", - searchTerms: ["codeblock"], - icon: , - command: ({ editor, range }: CommandProps) => - editor.chain().focus().deleteRange(range).toggleCodeBlock().run(), - }, - { - title: "Image", - description: "Upload an image from your computer.", - searchTerms: ["photo", "picture", "media"], - icon: , - command: ({ editor, range }: CommandProps) => { - editor.chain().focus().deleteRange(range).run(); - // upload image - const input = document.createElement("input"); - input.type = "file"; - input.accept = "image/*"; - input.onchange = async () => { - if (input.files?.length) { - const file = input.files[0]; - const pos = editor.view.state.selection.from; - startImageUpload(file, editor.view, pos, workspaceSlug, setIsSubmitting); - } - }; - input.click(); + { + title: "Quote", + description: "Capture a quote.", + searchTerms: ["blockquote"], + icon: , + command: ({ editor, range }: CommandProps) => + toggleBlockquote(editor, range) }, - }, - ].filter((item) => { - if (typeof query === "string" && query.length > 0) { - const search = query.toLowerCase(); - return ( - item.title.toLowerCase().includes(search) || - item.description.toLowerCase().includes(search) || - (item.searchTerms && item.searchTerms.some((term: string) => term.includes(search))) - ); - } - return true; - }); + { + title: "Code", + description: "Capture a code snippet.", + searchTerms: ["codeblock"], + icon: , + command: ({ editor, range }: CommandProps) => + editor.chain().focus().deleteRange(range).toggleCodeBlock().run(), + }, + { + title: "Image", + description: "Upload an image from your computer.", + searchTerms: ["photo", "picture", "media"], + icon: , + command: ({ editor, range }: CommandProps) => { + insertImageCommand(editor, uploadFile, setIsSubmitting, range); + }, + }, + ].filter((item) => { + if (typeof query === "string" && query.length > 0) { + const search = query.toLowerCase(); + return ( + item.title.toLowerCase().includes(search) || + item.description.toLowerCase().includes(search) || + (item.searchTerms && item.searchTerms.some((term: string) => term.includes(search))) + ); + } + return true; + }); export const updateScrollView = (container: HTMLElement, item: HTMLElement) => { const containerHeight = container.offsetHeight; @@ -312,13 +289,14 @@ const renderItems = () => { onStart: (props: { editor: Editor; clientRect: DOMRect }) => { component = new ReactRenderer(CommandList, { props, + // @ts-ignore editor: props.editor, }); // @ts-ignore popup = tippy("body", { getReferenceClientRect: props.clientRect, - appendTo: () => document.querySelector("#tiptap-container"), + appendTo: () => document.querySelector("#editor-container"), content: component.element, showOnCreate: true, interactive: true, @@ -352,12 +330,12 @@ const renderItems = () => { }; export const SlashCommand = ( - workspaceSlug: string, + uploadFile: UploadImage, setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void ) => Command.configure({ suggestion: { - items: getSuggestionItems(workspaceSlug, setIsSubmitting), + items: getSuggestionItems(uploadFile, setIsSubmitting), render: renderItems, }, }); diff --git a/packages/editor/rich-text-editor/src/ui/index.tsx b/packages/editor/rich-text-editor/src/ui/index.tsx new file mode 100644 index 00000000000..ce14962c806 --- /dev/null +++ b/packages/editor/rich-text-editor/src/ui/index.tsx @@ -0,0 +1,80 @@ +"use client" +import * as React from 'react'; +import { EditorContainer, EditorContentWrapper, getEditorClassNames, useEditor } from '@plane/editor-core'; +import { EditorBubbleMenu } from './menus/bubble-menu'; +import { RichTextEditorExtensions } from './extensions'; + +export type UploadImage = (file: File) => Promise; +export type DeleteImage = (assetUrlWithWorkspaceId: string) => Promise; + +interface IRichTextEditor { + value: string; + uploadFile: UploadImage; + deleteFile: DeleteImage; + noBorder?: boolean; + borderOnFocus?: boolean; + customClassName?: string; + editorContentCustomClassNames?: string; + onChange?: (json: any, html: string) => void; + setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void; + setShouldShowAlert?: (showAlert: boolean) => void; + forwardedRef?: any; + debouncedUpdatesEnabled?: boolean; +} + +interface RichTextEditorProps extends IRichTextEditor { + forwardedRef?: React.Ref; +} + +interface EditorHandle { + clearEditor: () => void; + setEditorValue: (content: string) => void; +} + +const RichTextEditor = ({ + onChange, + debouncedUpdatesEnabled, + setIsSubmitting, + setShouldShowAlert, + editorContentCustomClassNames, + value, + uploadFile, + deleteFile, + noBorder, + borderOnFocus, + customClassName, + forwardedRef, +}: RichTextEditorProps) => { + const editor = useEditor({ + onChange, + debouncedUpdatesEnabled, + setIsSubmitting, + setShouldShowAlert, + value, + uploadFile, + deleteFile, + forwardedRef, + extensions: RichTextEditorExtensions(uploadFile, setIsSubmitting) + }); + + const editorClassNames = getEditorClassNames({ noBorder, borderOnFocus, customClassName }); + + if (!editor) return null; + + return ( + + {editor && } +
+ +
+
+ ); +}; + +const RichTextEditorWithRef = React.forwardRef((props, ref) => ( + +)); + +RichTextEditorWithRef.displayName = "RichTextEditorWithRef"; + +export { RichTextEditor, RichTextEditorWithRef}; diff --git a/space/components/tiptap/bubble-menu/index.tsx b/packages/editor/rich-text-editor/src/ui/menus/bubble-menu/index.tsx similarity index 62% rename from space/components/tiptap/bubble-menu/index.tsx rename to packages/editor/rich-text-editor/src/ui/menus/bubble-menu/index.tsx index 217317ea105..9f4ab6fe80e 100644 --- a/space/components/tiptap/bubble-menu/index.tsx +++ b/packages/editor/rich-text-editor/src/ui/menus/bubble-menu/index.tsx @@ -1,10 +1,17 @@ -import { BubbleMenu, BubbleMenuProps } from "@tiptap/react"; +import { BubbleMenu, BubbleMenuProps, isNodeSelection } from "@tiptap/react"; import { FC, useState } from "react"; -import { BoldIcon, ItalicIcon, UnderlineIcon, StrikethroughIcon, CodeIcon } from "lucide-react"; +import { BoldIcon } from "lucide-react"; import { NodeSelector } from "./node-selector"; import { LinkSelector } from "./link-selector"; -import { cn } from "../utils"; +import { + BoldItem, + cn, + CodeItem, + ItalicItem, + StrikeThroughItem, + UnderLineItem, +} from "@plane/editor-core"; export interface BubbleMenuItem { name: string; @@ -17,48 +24,27 @@ type EditorBubbleMenuProps = Omit; export const EditorBubbleMenu: FC = (props: any) => { const items: BubbleMenuItem[] = [ - { - name: "bold", - isActive: () => props.editor?.isActive("bold"), - command: () => props.editor?.chain().focus().toggleBold().run(), - icon: BoldIcon, - }, - { - name: "italic", - isActive: () => props.editor?.isActive("italic"), - command: () => props.editor?.chain().focus().toggleItalic().run(), - icon: ItalicIcon, - }, - { - name: "underline", - isActive: () => props.editor?.isActive("underline"), - command: () => props.editor?.chain().focus().toggleUnderline().run(), - icon: UnderlineIcon, - }, - { - name: "strike", - isActive: () => props.editor?.isActive("strike"), - command: () => props.editor?.chain().focus().toggleStrike().run(), - icon: StrikethroughIcon, - }, - { - name: "code", - isActive: () => props.editor?.isActive("code"), - command: () => props.editor?.chain().focus().toggleCode().run(), - icon: CodeIcon, - }, + BoldItem(props.editor), + ItalicItem(props.editor), + UnderLineItem(props.editor), + StrikeThroughItem(props.editor), + CodeItem(props.editor), ]; const bubbleMenuProps: EditorBubbleMenuProps = { ...props, - shouldShow: ({ editor }) => { - if (!editor.isEditable) { - return false; - } - if (editor.isActive("image")) { + shouldShow: ({ state, editor }) => { + const { selection } = state; + const { empty } = selection; + + // don't show bubble menu if: + // - the selected node is an image + // - the selection is empty + // - the selection is a node selection (for drag handles) + if (editor.isActive("image") || !editor.isEditable || empty || isNodeSelection(selection)) { return false; } - return editor.view.state.selection.content().size > 0; + return true; }, tippyOptions: { moveTransition: "transform 0.15s ease-out", @@ -105,7 +91,7 @@ export const EditorBubbleMenu: FC = (props: any) => { "p-2 text-custom-text-300 hover:bg-custom-primary-100/5 active:bg-custom-primary-100/5 transition-colors", { "text-custom-text-100 bg-custom-primary-100/5": item.isActive(), - } + }, )} > = ({ editor, isOpen, setIsOpen const input = inputRef.current; const url = input?.value; if (url && isValidHttpUrl(url)) { - editor.chain().focus().setLink({ href: url }).run(); + setLinkEditor(editor, url); setIsOpen(false); } }, [editor, inputRef, setIsOpen]); @@ -68,7 +68,7 @@ export const LinkSelector: FC = ({ editor, isOpen, setIsOpen type="button" className="flex items-center rounded-sm p-1 text-red-600 transition-all hover:bg-red-100 dark:hover:bg-red-800" onClick={() => { - editor.chain().focus().unsetLink().run(); + unsetLinkEditor(editor); setIsOpen(false); }} > diff --git a/web/components/tiptap/bubble-menu/node-selector.tsx b/packages/editor/rich-text-editor/src/ui/menus/bubble-menu/node-selector.tsx similarity index 58% rename from web/components/tiptap/bubble-menu/node-selector.tsx rename to packages/editor/rich-text-editor/src/ui/menus/bubble-menu/node-selector.tsx index 34d40ec06d5..b8b7ffc58db 100644 --- a/web/components/tiptap/bubble-menu/node-selector.tsx +++ b/packages/editor/rich-text-editor/src/ui/menus/bubble-menu/node-selector.tsx @@ -1,20 +1,13 @@ -import { Editor } from "@tiptap/core"; +import { BulletListItem, cn, CodeItem, HeadingOneItem, HeadingThreeItem, HeadingTwoItem, NumberedListItem, QuoteItem, TodoListItem } from "@plane/editor-core"; +import { Editor } from "@tiptap/react"; import { Check, ChevronDown, - Heading1, - Heading2, - Heading3, - TextQuote, - ListOrdered, TextIcon, - Code, - CheckSquare, } from "lucide-react"; import { Dispatch, FC, SetStateAction } from "react"; import { BubbleMenuItem } from "."; -import { cn } from "../utils"; interface NodeSelectorProps { editor: Editor; @@ -33,55 +26,14 @@ export const NodeSelector: FC = ({ editor, isOpen, setIsOpen !editor.isActive("bulletList") && !editor.isActive("orderedList"), }, - { - name: "H1", - icon: Heading1, - command: () => editor.chain().focus().toggleHeading({ level: 1 }).run(), - isActive: () => editor.isActive("heading", { level: 1 }), - }, - { - name: "H2", - icon: Heading2, - command: () => editor.chain().focus().toggleHeading({ level: 2 }).run(), - isActive: () => editor.isActive("heading", { level: 2 }), - }, - { - name: "H3", - icon: Heading3, - command: () => editor.chain().focus().toggleHeading({ level: 3 }).run(), - isActive: () => editor.isActive("heading", { level: 3 }), - }, - { - name: "To-do List", - icon: CheckSquare, - command: () => editor.chain().focus().toggleTaskList().run(), - isActive: () => editor.isActive("taskItem"), - }, - { - name: "Bullet List", - icon: ListOrdered, - command: () => editor.chain().focus().toggleBulletList().run(), - isActive: () => editor.isActive("bulletList"), - }, - { - name: "Numbered List", - icon: ListOrdered, - command: () => editor.chain().focus().toggleOrderedList().run(), - isActive: () => editor.isActive("orderedList"), - }, - { - name: "Quote", - icon: TextQuote, - command: () => - editor.chain().focus().toggleNode("paragraph", "paragraph").toggleBlockquote().run(), - isActive: () => editor.isActive("blockquote"), - }, - { - name: "Code", - icon: Code, - command: () => editor.chain().focus().toggleCodeBlock().run(), - isActive: () => editor.isActive("codeBlock"), - }, + HeadingOneItem(editor), + HeadingTwoItem(editor), + HeadingThreeItem(editor), + TodoListItem(editor), + BulletListItem(editor), + NumberedListItem(editor), + QuoteItem(editor), + CodeItem(editor), ]; const activeItem = items.filter((item) => item.isActive()).pop() ?? { diff --git a/packages/editor/rich-text-editor/src/ui/read-only/index.tsx b/packages/editor/rich-text-editor/src/ui/read-only/index.tsx new file mode 100644 index 00000000000..dc058cf8937 --- /dev/null +++ b/packages/editor/rich-text-editor/src/ui/read-only/index.tsx @@ -0,0 +1,54 @@ +"use client" +import { EditorContainer, EditorContentWrapper, getEditorClassNames, useReadOnlyEditor } from '@plane/editor-core'; +import * as React from 'react'; + +interface IRichTextReadOnlyEditor { + value: string; + editorContentCustomClassNames?: string; + noBorder?: boolean; + borderOnFocus?: boolean; + customClassName?: string; +} + +interface RichTextReadOnlyEditorProps extends IRichTextReadOnlyEditor { + forwardedRef?: React.Ref; +} + +interface EditorHandle { + clearEditor: () => void; + setEditorValue: (content: string) => void; +} + +const RichReadOnlyEditor = ({ + editorContentCustomClassNames, + noBorder, + borderOnFocus, + customClassName, + value, + forwardedRef, +}: RichTextReadOnlyEditorProps) => { + const editor = useReadOnlyEditor({ + value, + forwardedRef, + }); + + const editorClassNames = getEditorClassNames({ noBorder, borderOnFocus, customClassName }); + + if (!editor) return null; + + return ( + +
+ +
+
+ ); +}; + +const RichReadOnlyEditorWithRef = React.forwardRef((props, ref) => ( + +)); + +RichReadOnlyEditorWithRef.displayName = "RichReadOnlyEditorWithRef"; + +export { RichReadOnlyEditor , RichReadOnlyEditorWithRef }; diff --git a/packages/editor/rich-text-editor/tailwind.config.js b/packages/editor/rich-text-editor/tailwind.config.js new file mode 100644 index 00000000000..f3206315889 --- /dev/null +++ b/packages/editor/rich-text-editor/tailwind.config.js @@ -0,0 +1,6 @@ +const sharedConfig = require("tailwind-config-custom/tailwind.config.js"); + +module.exports = { + // prefix ui lib classes to avoid conflicting with the app + ...sharedConfig, +}; diff --git a/packages/editor/rich-text-editor/tsconfig.json b/packages/editor/rich-text-editor/tsconfig.json new file mode 100644 index 00000000000..61a60f3244c --- /dev/null +++ b/packages/editor/rich-text-editor/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "tsconfig/react.json", + "include": [ + "src/**/*", + "index.d.ts" + ], + "exclude": [ + "dist", + "build", + "node_modules" + ] +} diff --git a/packages/editor/rich-text-editor/tsup.config.ts b/packages/editor/rich-text-editor/tsup.config.ts new file mode 100644 index 00000000000..5e89e04afad --- /dev/null +++ b/packages/editor/rich-text-editor/tsup.config.ts @@ -0,0 +1,11 @@ +import { defineConfig, Options } from "tsup"; + +export default defineConfig((options: Options) => ({ + entry: ["src/index.ts"], + format: ["cjs", "esm"], + dts: true, + clean: false, + external: ["react"], + injectStyle: true, + ...options, +})); diff --git a/packages/tailwind-config-custom/package.json b/packages/tailwind-config-custom/package.json index 6edaa0ec44f..1336379b713 100644 --- a/packages/tailwind-config-custom/package.json +++ b/packages/tailwind-config-custom/package.json @@ -4,7 +4,12 @@ "description": "common tailwind configuration across monorepo", "main": "index.js", "devDependencies": { - "@tailwindcss/typography": "^0.5.10", - "tailwindcss-animate": "^1.0.7" + "@tailwindcss/typography": "^0.5.9", + "autoprefixer": "^10.4.14", + "postcss": "^8.4.21", + "prettier": "^2.8.8", + "prettier-plugin-tailwindcss": "^0.3.0", + "tailwindcss": "^3.2.7", + "tailwindcss-animate": "^1.0.6" } } diff --git a/packages/tailwind-config-custom/tailwind.config.js b/packages/tailwind-config-custom/tailwind.config.js index 061168c4f78..dd2f9291b5a 100644 --- a/packages/tailwind-config-custom/tailwind.config.js +++ b/packages/tailwind-config-custom/tailwind.config.js @@ -1,14 +1,19 @@ const convertToRGB = (variableName) => `rgba(var(${variableName}))`; +/** @type {import('tailwindcss').Config} */ module.exports = { darkMode: "class", - content: [ - "./components/**/*.tsx", - "./constants/**/*.{js,ts,jsx,tsx}", - "./layouts/**/*.tsx", - "./pages/**/*.tsx", - "./ui/**/*.tsx", - ], + content: { + relative: true, + files: [ + "./components/**/*.tsx", + "./constants/**/*.{js,ts,jsx,tsx}", + "./layouts/**/*.tsx", + "./pages/**/*.tsx", + "./ui/**/*.tsx", + "../packages/editor/**/*.{js,ts,jsx,tsx}" + ] + }, theme: { extend: { boxShadow: { diff --git a/packages/tsconfig/base.json b/packages/tsconfig/base.json index d72a9f3a278..2825abe07ce 100644 --- a/packages/tsconfig/base.json +++ b/packages/tsconfig/base.json @@ -16,5 +16,7 @@ "skipLibCheck": true, "strict": true }, - "exclude": ["node_modules"] + "exclude": [ + "node_modules" + ] } diff --git a/packages/tsconfig/react-library.json b/packages/tsconfig/react.json similarity index 77% rename from packages/tsconfig/react-library.json rename to packages/tsconfig/react.json index bdd954367b0..36b62be3894 100644 --- a/packages/tsconfig/react-library.json +++ b/packages/tsconfig/react.json @@ -3,8 +3,8 @@ "display": "React Library", "extends": "./base.json", "compilerOptions": { - "jsx": "react", - "lib": ["ES2015"], + "jsx": "react-jsx", + "lib": ["ES2015", "DOM"], "module": "ESNext", "target": "es6" } diff --git a/space/components/issues/peek-overview/comment/add-comment.tsx b/space/components/issues/peek-overview/comment/add-comment.tsx index 3ea4308d731..2595dab4283 100644 --- a/space/components/issues/peek-overview/comment/add-comment.tsx +++ b/space/components/issues/peek-overview/comment/add-comment.tsx @@ -11,7 +11,9 @@ import { SecondaryButton } from "components/ui"; // types import { Comment } from "types/issue"; // components -import { TipTapEditor } from "components/tiptap"; +import { LiteTextEditorWithRef } from "@plane/lite-text-editor"; +import fileService from "services/file.service"; +// service const defaultValues: Partial = { comment_html: "", @@ -69,8 +71,14 @@ export const AddComment: React.FC = observer((props) => { name="comment_html" control={control} render={({ field: { value, onChange } }) => ( - { + userStore.requiredLogin(() => { + handleSubmit(onSubmit)(e); + }); + }} + uploadFile={fileService.getUploadFileFunction(workspace_slug as string)} + deleteFile={fileService.deleteImage} ref={editorRef} value={ !value || value === "" || (typeof value === "object" && Object.keys(value).length === 0) diff --git a/space/components/issues/peek-overview/comment/comment-detail-card.tsx b/space/components/issues/peek-overview/comment/comment-detail-card.tsx index 0b4d2d5b08f..f6b4d9bf38b 100644 --- a/space/components/issues/peek-overview/comment/comment-detail-card.tsx +++ b/space/components/issues/peek-overview/comment/comment-detail-card.tsx @@ -9,7 +9,8 @@ import { Menu, Transition } from "@headlessui/react"; // lib import { useMobxStore } from "lib/mobx/store-provider"; // components -import { TipTapEditor } from "components/tiptap"; +import { LiteReadOnlyEditorWithRef, LiteTextEditorWithRef } from "@plane/lite-text-editor"; + import { CommentReactions } from "components/issues/peek-overview"; // icons import { ChatBubbleLeftEllipsisIcon, CheckIcon, XMarkIcon, EllipsisVerticalIcon } from "@heroicons/react/24/outline"; @@ -17,6 +18,8 @@ import { ChatBubbleLeftEllipsisIcon, CheckIcon, XMarkIcon, EllipsisVerticalIcon import { timeAgo } from "helpers/date-time.helper"; // types import { Comment } from "types/issue"; +import fileService from "services/file.service"; +// services type Props = { workspaceSlug: string; @@ -100,8 +103,10 @@ export const CommentCard: React.FC = observer((props) => { control={control} name="comment_html" render={({ field: { onChange, value } }) => ( - = observer((props) => {
- @@ -147,7 +150,7 @@ export const CommentCard: React.FC = observer((props) => { {}} + onClick={() => { }} className="relative grid place-items-center rounded p-1 text-custom-text-200 hover:text-custom-text-100 outline-none cursor-pointer hover:bg-custom-background-80" > @@ -171,9 +174,8 @@ export const CommentCard: React.FC = observer((props) => { onClick={() => { setIsEditing(true); }} - className={`w-full select-none truncate rounded px-1 py-1.5 text-left text-custom-text-200 hover:bg-custom-background-80 ${ - active ? "bg-custom-background-80" : "" - }`} + className={`w-full select-none truncate rounded px-1 py-1.5 text-left text-custom-text-200 hover:bg-custom-background-80 ${active ? "bg-custom-background-80" : "" + }`} > Edit @@ -186,9 +188,8 @@ export const CommentCard: React.FC = observer((props) => { diff --git a/space/components/issues/peek-overview/issue-details.tsx b/space/components/issues/peek-overview/issue-details.tsx index 0b329568c78..24dd656513b 100644 --- a/space/components/issues/peek-overview/issue-details.tsx +++ b/space/components/issues/peek-overview/issue-details.tsx @@ -1,6 +1,5 @@ import { IssueReactions } from "components/issues/peek-overview"; -import { TipTapEditor } from "components/tiptap"; -import { useRouter } from "next/router"; +import { RichReadOnlyEditor } from "@plane/rich-text-editor"; // types import { IIssue } from "types/issue"; @@ -8,33 +7,22 @@ type Props = { issueDetails: IIssue; }; -export const PeekOverviewIssueDetails: React.FC = ({ issueDetails }) => { - const router = useRouter(); - const { workspace_slug } = router.query; - - return ( -
-
- {issueDetails.project_detail.identifier}-{issueDetails.sequence_id} -
-

{issueDetails.name}

- {issueDetails.description_html !== "" && issueDetails.description_html !== "

" && ( -

" - : issueDetails.description_html - } - customClassName="p-3 min-h-[50px] shadow-sm" - debouncedUpdatesEnabled={false} - editable={false} - /> - )} - -
- ); -}; +export const PeekOverviewIssueDetails: React.FC = ({ issueDetails }) => ( +
+
+ {issueDetails.project_detail.identifier}-{issueDetails.sequence_id} +
+

{issueDetails.name}

+ {issueDetails.description_html !== "" && issueDetails.description_html !== "

" && ( +

" + : issueDetails.description_html} + customClassName="p-3 min-h-[50px] shadow-sm" /> + )} + +
+); diff --git a/space/components/tiptap/bubble-menu/link-selector.tsx b/space/components/tiptap/bubble-menu/link-selector.tsx deleted file mode 100644 index 559521db66f..00000000000 --- a/space/components/tiptap/bubble-menu/link-selector.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import { Editor } from "@tiptap/core"; -import { Check, Trash } from "lucide-react"; -import { Dispatch, FC, SetStateAction, useCallback, useEffect, useRef } from "react"; -import { cn } from "../utils"; -import isValidHttpUrl from "./utils/link-validator"; -interface LinkSelectorProps { - editor: Editor; - isOpen: boolean; - setIsOpen: Dispatch>; -} - -export const LinkSelector: FC = ({ editor, isOpen, setIsOpen }) => { - const inputRef = useRef(null); - - const onLinkSubmit = useCallback(() => { - const input = inputRef.current; - const url = input?.value; - if (url && isValidHttpUrl(url)) { - editor.chain().focus().setLink({ href: url }).run(); - setIsOpen(false); - } - }, [editor, inputRef, setIsOpen]); - - useEffect(() => { - inputRef.current && inputRef.current?.focus(); - }); - - return ( -
- - {isOpen && ( -
{ - if (e.key === "Enter") { - e.preventDefault(); - onLinkSubmit(); - } - }} - > - - {editor.getAttributes("link").href ? ( - - ) : ( - - )} -
- )} -
- ); -}; diff --git a/space/components/tiptap/bubble-menu/node-selector.tsx b/space/components/tiptap/bubble-menu/node-selector.tsx deleted file mode 100644 index 34d40ec06d5..00000000000 --- a/space/components/tiptap/bubble-menu/node-selector.tsx +++ /dev/null @@ -1,130 +0,0 @@ -import { Editor } from "@tiptap/core"; -import { - Check, - ChevronDown, - Heading1, - Heading2, - Heading3, - TextQuote, - ListOrdered, - TextIcon, - Code, - CheckSquare, -} from "lucide-react"; -import { Dispatch, FC, SetStateAction } from "react"; - -import { BubbleMenuItem } from "."; -import { cn } from "../utils"; - -interface NodeSelectorProps { - editor: Editor; - isOpen: boolean; - setIsOpen: Dispatch>; -} - -export const NodeSelector: FC = ({ editor, isOpen, setIsOpen }) => { - const items: BubbleMenuItem[] = [ - { - name: "Text", - icon: TextIcon, - command: () => editor.chain().focus().toggleNode("paragraph", "paragraph").run(), - isActive: () => - editor.isActive("paragraph") && - !editor.isActive("bulletList") && - !editor.isActive("orderedList"), - }, - { - name: "H1", - icon: Heading1, - command: () => editor.chain().focus().toggleHeading({ level: 1 }).run(), - isActive: () => editor.isActive("heading", { level: 1 }), - }, - { - name: "H2", - icon: Heading2, - command: () => editor.chain().focus().toggleHeading({ level: 2 }).run(), - isActive: () => editor.isActive("heading", { level: 2 }), - }, - { - name: "H3", - icon: Heading3, - command: () => editor.chain().focus().toggleHeading({ level: 3 }).run(), - isActive: () => editor.isActive("heading", { level: 3 }), - }, - { - name: "To-do List", - icon: CheckSquare, - command: () => editor.chain().focus().toggleTaskList().run(), - isActive: () => editor.isActive("taskItem"), - }, - { - name: "Bullet List", - icon: ListOrdered, - command: () => editor.chain().focus().toggleBulletList().run(), - isActive: () => editor.isActive("bulletList"), - }, - { - name: "Numbered List", - icon: ListOrdered, - command: () => editor.chain().focus().toggleOrderedList().run(), - isActive: () => editor.isActive("orderedList"), - }, - { - name: "Quote", - icon: TextQuote, - command: () => - editor.chain().focus().toggleNode("paragraph", "paragraph").toggleBlockquote().run(), - isActive: () => editor.isActive("blockquote"), - }, - { - name: "Code", - icon: Code, - command: () => editor.chain().focus().toggleCodeBlock().run(), - isActive: () => editor.isActive("codeBlock"), - }, - ]; - - const activeItem = items.filter((item) => item.isActive()).pop() ?? { - name: "Multiple", - }; - - return ( -
- - - {isOpen && ( -
- {items.map((item, index) => ( - - ))} -
- )} -
- ); -}; diff --git a/space/components/tiptap/bubble-menu/utils/link-validator.tsx b/space/components/tiptap/bubble-menu/utils/link-validator.tsx deleted file mode 100644 index 9af366c0266..00000000000 --- a/space/components/tiptap/bubble-menu/utils/link-validator.tsx +++ /dev/null @@ -1,11 +0,0 @@ -export default function isValidHttpUrl(string: string): boolean { - let url; - - try { - url = new URL(string); - } catch (_) { - return false; - } - - return url.protocol === "http:" || url.protocol === "https:"; -} diff --git a/space/components/tiptap/extensions/index.tsx b/space/components/tiptap/extensions/index.tsx deleted file mode 100644 index 8ad4e07b4d5..00000000000 --- a/space/components/tiptap/extensions/index.tsx +++ /dev/null @@ -1,149 +0,0 @@ -import StarterKit from "@tiptap/starter-kit"; -import HorizontalRule from "@tiptap/extension-horizontal-rule"; -import TiptapLink from "@tiptap/extension-link"; -import Placeholder from "@tiptap/extension-placeholder"; -import TiptapUnderline from "@tiptap/extension-underline"; -import TextStyle from "@tiptap/extension-text-style"; -import { Color } from "@tiptap/extension-color"; -import TaskItem from "@tiptap/extension-task-item"; -import TaskList from "@tiptap/extension-task-list"; -import { Markdown } from "tiptap-markdown"; -import Highlight from "@tiptap/extension-highlight"; -import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight"; -import { lowlight } from "lowlight/lib/core"; -import SlashCommand from "../slash-command"; -import { InputRule } from "@tiptap/core"; -import Gapcursor from "@tiptap/extension-gapcursor"; - -import ts from "highlight.js/lib/languages/typescript"; - -import "highlight.js/styles/github-dark.css"; -import UpdatedImage from "./updated-image"; -import isValidHttpUrl from "../bubble-menu/utils/link-validator"; -import { CustomTableCell } from "./table/table-cell"; -import { Table } from "./table/table"; -import { TableHeader } from "./table/table-header"; -import { TableRow } from "@tiptap/extension-table-row"; - -lowlight.registerLanguage("ts", ts); - -export const TiptapExtensions = ( - workspaceSlug: string, - setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void -) => [ - StarterKit.configure({ - bulletList: { - HTMLAttributes: { - class: "list-disc list-outside leading-3 -mt-2", - }, - }, - orderedList: { - HTMLAttributes: { - class: "list-decimal list-outside leading-3 -mt-2", - }, - }, - listItem: { - HTMLAttributes: { - class: "leading-normal -mb-2", - }, - }, - blockquote: { - HTMLAttributes: { - class: "border-l-4 border-custom-border-300", - }, - }, - code: { - HTMLAttributes: { - class: - "rounded-md bg-custom-primary-30 mx-1 px-1 py-1 font-mono font-medium text-custom-text-1000", - spellcheck: "false", - }, - }, - codeBlock: false, - horizontalRule: false, - dropcursor: { - color: "rgba(var(--color-text-100))", - width: 2, - }, - gapcursor: false, - }), - CodeBlockLowlight.configure({ - lowlight, - }), - HorizontalRule.extend({ - addInputRules() { - return [ - new InputRule({ - find: /^(?:---|—-|___\s|\*\*\*\s)$/, - handler: ({ state, range, commands }) => { - commands.splitBlock(); - - const attributes = {}; - const { tr } = state; - const start = range.from; - const end = range.to; - // @ts-ignore - tr.replaceWith(start - 1, end, this.type.create(attributes)); - }, - }), - ]; - }, - }).configure({ - HTMLAttributes: { - class: "mb-6 border-t border-custom-border-300", - }, - }), - Gapcursor, - TiptapLink.configure({ - protocols: ["http", "https"], - validate: (url) => isValidHttpUrl(url), - HTMLAttributes: { - class: - "text-custom-primary-300 underline underline-offset-[3px] hover:text-custom-primary-500 transition-colors cursor-pointer", - }, - }), - UpdatedImage.configure({ - HTMLAttributes: { - class: "rounded-lg border border-custom-border-300", - }, - }), - Placeholder.configure({ - placeholder: ({ node }) => { - if (node.type.name === "heading") { - return `Heading ${node.attrs.level}`; - } - if (node.type.name === "image" || node.type.name === "table") { - return ""; - } - - return "Press '/' for commands..."; - }, - includeChildren: true, - }), - SlashCommand(workspaceSlug, setIsSubmitting), - TiptapUnderline, - TextStyle, - Color, - Highlight.configure({ - multicolor: true, - }), - TaskList.configure({ - HTMLAttributes: { - class: "not-prose pl-2", - }, - }), - TaskItem.configure({ - HTMLAttributes: { - class: "flex items-start my-4", - }, - nested: true, - }), - Markdown.configure({ - html: true, - transformCopiedText: true, - }), - Table, - TableHeader, - CustomTableCell, - TableRow, - ]; diff --git a/space/components/tiptap/extensions/table/table-cell.ts b/space/components/tiptap/extensions/table/table-cell.ts deleted file mode 100644 index 643cb8c64a7..00000000000 --- a/space/components/tiptap/extensions/table/table-cell.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { TableCell } from "@tiptap/extension-table-cell"; - -export const CustomTableCell = TableCell.extend({ - addAttributes() { - return { - ...this.parent?.(), - isHeader: { - default: false, - parseHTML: (element) => { - isHeader: element.tagName === "TD"; - }, - renderHTML: (attributes) => { - tag: attributes.isHeader ? "th" : "td"; - }, - }, - }; - }, - renderHTML({ HTMLAttributes }) { - if (HTMLAttributes.isHeader) { - return [ - "th", - { - ...HTMLAttributes, - class: `relative ${HTMLAttributes.class}`, - }, - ["span", { class: "absolute top-0 right-0" }], - 0, - ]; - } - return ["td", HTMLAttributes, 0]; - }, -}); diff --git a/space/components/tiptap/extensions/table/table-header.ts b/space/components/tiptap/extensions/table/table-header.ts deleted file mode 100644 index f23aa93ef55..00000000000 --- a/space/components/tiptap/extensions/table/table-header.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { TableHeader as BaseTableHeader } from "@tiptap/extension-table-header"; - -const TableHeader = BaseTableHeader.extend({ - content: "paragraph", -}); - -export { TableHeader }; diff --git a/space/components/tiptap/extensions/table/table.ts b/space/components/tiptap/extensions/table/table.ts deleted file mode 100644 index 9b727bb51bd..00000000000 --- a/space/components/tiptap/extensions/table/table.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Table as BaseTable } from "@tiptap/extension-table"; - -const Table = BaseTable.configure({ - resizable: true, - cellMinWidth: 100, - allowTableNodeSelection: true, -}); - -export { Table }; diff --git a/space/components/tiptap/extensions/updated-image.tsx b/space/components/tiptap/extensions/updated-image.tsx deleted file mode 100644 index b620509535e..00000000000 --- a/space/components/tiptap/extensions/updated-image.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import Image from "@tiptap/extension-image"; -import TrackImageDeletionPlugin from "../plugins/delete-image"; -import UploadImagesPlugin from "../plugins/upload-image"; - -const UpdatedImage = Image.extend({ - addProseMirrorPlugins() { - return [UploadImagesPlugin(), TrackImageDeletionPlugin()]; - }, - addAttributes() { - return { - ...this.parent?.(), - width: { - default: "35%", - }, - height: { - default: null, - }, - }; - }, -}); - -export default UpdatedImage; diff --git a/space/components/tiptap/index.tsx b/space/components/tiptap/index.tsx deleted file mode 100644 index 84f691c35d5..00000000000 --- a/space/components/tiptap/index.tsx +++ /dev/null @@ -1,110 +0,0 @@ -import { useImperativeHandle, useRef, forwardRef, useEffect } from "react"; -import { useEditor, EditorContent, Editor } from "@tiptap/react"; -import { useDebouncedCallback } from "use-debounce"; -// components -import { EditorBubbleMenu } from "./bubble-menu"; -import { TiptapExtensions } from "./extensions"; -import { TiptapEditorProps } from "./props"; -import { ImageResizer } from "./extensions/image-resize"; -import { TableMenu } from "./table-menu"; - -export interface ITipTapRichTextEditor { - value: string; - noBorder?: boolean; - borderOnFocus?: boolean; - customClassName?: string; - editorContentCustomClassNames?: string; - onChange?: (json: any, html: string) => void; - setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void; - setShouldShowAlert?: (showAlert: boolean) => void; - workspaceSlug: string; - editable?: boolean; - forwardedRef?: any; - debouncedUpdatesEnabled?: boolean; -} - -const Tiptap = (props: ITipTapRichTextEditor) => { - const { - onChange, - debouncedUpdatesEnabled, - forwardedRef, - editable, - setIsSubmitting, - setShouldShowAlert, - editorContentCustomClassNames, - value, - noBorder, - workspaceSlug, - borderOnFocus, - customClassName, - } = props; - - const editor = useEditor({ - editable: editable ?? true, - editorProps: TiptapEditorProps(workspaceSlug, setIsSubmitting), - extensions: TiptapExtensions(workspaceSlug, setIsSubmitting), - content: value, - onUpdate: async ({ editor }) => { - // for instant feedback loop - setIsSubmitting?.("submitting"); - setShouldShowAlert?.(true); - if (debouncedUpdatesEnabled) { - debouncedUpdates({ onChange, editor }); - } else { - onChange?.(editor.getJSON(), editor.getHTML()); - } - }, - }); - - const editorRef: React.MutableRefObject = useRef(null); - - useImperativeHandle(forwardedRef, () => ({ - clearEditor: () => { - editorRef.current?.commands.clearContent(); - }, - setEditorValue: (content: string) => { - editorRef.current?.commands.setContent(content); - }, - })); - - const debouncedUpdates = useDebouncedCallback(async ({ onChange, editor }) => { - setTimeout(async () => { - if (onChange) { - onChange(editor.getJSON(), editor.getHTML()); - } - }, 500); - }, 1000); - - const editorClassNames = `relative w-full max-w-full sm:rounded-lg mt-2 p-3 relative focus:outline-none rounded-md - ${noBorder ? "" : "border border-custom-border-200"} ${ - borderOnFocus ? "focus:border border-custom-border-300" : "focus:border-0" - } ${customClassName}`; - - if (!editor) return null; - editorRef.current = editor; - - return ( -
{ - editor?.chain().focus().run(); - }} - className={`tiptap-editor-container cursor-text ${editorClassNames}`} - > - {editor && } -
- - - {editor?.isActive("image") && } -
-
- ); -}; - -const TipTapEditor = forwardRef((props, ref) => ( - -)); - -TipTapEditor.displayName = "TipTapEditor"; - -export { TipTapEditor }; diff --git a/space/components/tiptap/plugins/delete-image.tsx b/space/components/tiptap/plugins/delete-image.tsx deleted file mode 100644 index fdf515ccc99..00000000000 --- a/space/components/tiptap/plugins/delete-image.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { EditorState, Plugin, PluginKey, Transaction } from "@tiptap/pm/state"; -import { Node as ProseMirrorNode } from "@tiptap/pm/model"; -import fileService from "services/file.service"; - -const deleteKey = new PluginKey("delete-image"); -const IMAGE_NODE_TYPE = "image"; - -interface ImageNode extends ProseMirrorNode { - attrs: { - src: string; - id: string; - }; -} - -const TrackImageDeletionPlugin = (): Plugin => - new Plugin({ - key: deleteKey, - appendTransaction: (transactions: readonly Transaction[], oldState: EditorState, newState: EditorState) => { - const newImageSources = new Set(); - newState.doc.descendants((node) => { - if (node.type.name === IMAGE_NODE_TYPE) { - newImageSources.add(node.attrs.src); - } - }); - - transactions.forEach((transaction) => { - if (!transaction.docChanged) return; - - const removedImages: ImageNode[] = []; - - oldState.doc.descendants((oldNode, oldPos) => { - if (oldNode.type.name !== IMAGE_NODE_TYPE) return; - if (oldPos < 0 || oldPos > newState.doc.content.size) return; - if (!newState.doc.resolve(oldPos).parent) return; - - const newNode = newState.doc.nodeAt(oldPos); - - // Check if the node has been deleted or replaced - if (!newNode || newNode.type.name !== IMAGE_NODE_TYPE) { - if (!newImageSources.has(oldNode.attrs.src)) { - removedImages.push(oldNode as ImageNode); - } - } - }); - - removedImages.forEach(async (node) => { - const src = node.attrs.src; - await onNodeDeleted(src); - }); - }); - - return null; - }, - }); - -export default TrackImageDeletionPlugin; - -async function onNodeDeleted(src: string): Promise { - try { - const assetUrlWithWorkspaceId = new URL(src).pathname.substring(1); - const resStatus = await fileService.deleteImage(assetUrlWithWorkspaceId); - if (resStatus === 204) { - console.log("Image deleted successfully"); - } - } catch (error) { - console.error("Error deleting image: ", error); - } -} diff --git a/space/components/tiptap/plugins/upload-image.tsx b/space/components/tiptap/plugins/upload-image.tsx deleted file mode 100644 index bc0acdc540d..00000000000 --- a/space/components/tiptap/plugins/upload-image.tsx +++ /dev/null @@ -1,127 +0,0 @@ -import { EditorState, Plugin, PluginKey } from "@tiptap/pm/state"; -import { Decoration, DecorationSet, EditorView } from "@tiptap/pm/view"; -import fileService from "services/file.service"; - -const uploadKey = new PluginKey("upload-image"); - -const UploadImagesPlugin = () => - new Plugin({ - key: uploadKey, - state: { - init() { - return DecorationSet.empty; - }, - apply(tr, set) { - set = set.map(tr.mapping, tr.doc); - // See if the transaction adds or removes any placeholders - const action = tr.getMeta(uploadKey); - if (action && action.add) { - const { id, pos, src } = action.add; - - const placeholder = document.createElement("div"); - placeholder.setAttribute("class", "img-placeholder"); - const image = document.createElement("img"); - image.setAttribute("class", "opacity-10 rounded-lg border border-custom-border-300"); - image.src = src; - placeholder.appendChild(image); - const deco = Decoration.widget(pos + 1, placeholder, { - id, - }); - set = set.add(tr.doc, [deco]); - } else if (action && action.remove) { - set = set.remove(set.find(undefined, undefined, (spec) => spec.id == action.remove.id)); - } - return set; - }, - }, - props: { - decorations(state) { - return this.getState(state); - }, - }, - }); - -export default UploadImagesPlugin; - -function findPlaceholder(state: EditorState, id: {}) { - const decos = uploadKey.getState(state); - const found = decos.find( - undefined, - undefined, - (spec: { id: number | undefined }) => spec.id == id - ); - return found.length ? found[0].from : null; -} - -export async function startImageUpload( - file: File, - view: EditorView, - pos: number, - workspaceSlug: string, - setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void -) { - if (!file.type.includes("image/")) { - return; - } - - const id = {}; - - const tr = view.state.tr; - if (!tr.selection.empty) tr.deleteSelection(); - - const reader = new FileReader(); - reader.readAsDataURL(file); - reader.onload = () => { - tr.setMeta(uploadKey, { - add: { - id, - pos, - src: reader.result, - }, - }); - view.dispatch(tr); - }; - - if (!workspaceSlug) { - return; - } - setIsSubmitting?.("submitting"); - const src = await UploadImageHandler(file, workspaceSlug); - const { schema } = view.state; - pos = findPlaceholder(view.state, id); - - if (pos == null) return; - const imageSrc = typeof src === "object" ? reader.result : src; - - const node = schema.nodes.image.create({ src: imageSrc }); - const transaction = view.state.tr - .replaceWith(pos, pos, node) - .setMeta(uploadKey, { remove: { id } }); - view.dispatch(transaction); -} - -const UploadImageHandler = (file: File, workspaceSlug: string): Promise => { - if (!workspaceSlug) { - return Promise.reject("Workspace slug is missing"); - } - try { - const formData = new FormData(); - formData.append("asset", file); - formData.append("attributes", JSON.stringify({})); - - return new Promise(async (resolve, reject) => { - const imageUrl = await fileService - .uploadFile(workspaceSlug, formData) - .then((response) => response.asset); - - const image = new Image(); - image.src = imageUrl; - image.onload = () => { - resolve(imageUrl); - }; - }); - } catch (error) { - console.log(error); - return Promise.reject(error); - } -}; diff --git a/space/components/tiptap/props.tsx b/space/components/tiptap/props.tsx deleted file mode 100644 index 8233e3ab4ab..00000000000 --- a/space/components/tiptap/props.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { EditorProps } from "@tiptap/pm/view"; -import { startImageUpload } from "./plugins/upload-image"; -import { findTableAncestor } from "./table-menu"; - -export function TiptapEditorProps( - workspaceSlug: string, - setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void -): EditorProps { - return { - attributes: { - class: `prose prose-brand max-w-full prose-headings:font-display font-default focus:outline-none`, - }, - handleDOMEvents: { - keydown: (_view, event) => { - // prevent default event listeners from firing when slash command is active - if (["ArrowUp", "ArrowDown", "Enter"].includes(event.key)) { - const slashCommand = document.querySelector("#slash-command"); - if (slashCommand) { - return true; - } - } - }, - }, - handlePaste: (view, event) => { - if (typeof window !== "undefined") { - const selection: any = window?.getSelection(); - if (selection.rangeCount !== 0) { - const range = selection.getRangeAt(0); - if (findTableAncestor(range.startContainer)) { - return; - } - } - } - if (event.clipboardData && event.clipboardData.files && event.clipboardData.files[0]) { - event.preventDefault(); - const file = event.clipboardData.files[0]; - const pos = view.state.selection.from; - startImageUpload(file, view, pos, workspaceSlug, setIsSubmitting); - return true; - } - return false; - }, - handleDrop: (view, event, _slice, moved) => { - if (typeof window !== "undefined") { - const selection: any = window?.getSelection(); - if (selection.rangeCount !== 0) { - const range = selection.getRangeAt(0); - if (findTableAncestor(range.startContainer)) { - return; - } - } - } - if (!moved && event.dataTransfer && event.dataTransfer.files && event.dataTransfer.files[0]) { - event.preventDefault(); - const file = event.dataTransfer.files[0]; - const coordinates = view.posAtCoords({ - left: event.clientX, - top: event.clientY, - }); - // here we deduct 1 from the pos or else the image will create an extra node - if (coordinates) { - startImageUpload(file, view, coordinates.pos - 1, workspaceSlug, setIsSubmitting); - } - return true; - } - return false; - }, - }; -} diff --git a/space/components/tiptap/table-menu/InsertBottomTableIcon.tsx b/space/components/tiptap/table-menu/InsertBottomTableIcon.tsx deleted file mode 100644 index 0e42ba64824..00000000000 --- a/space/components/tiptap/table-menu/InsertBottomTableIcon.tsx +++ /dev/null @@ -1,16 +0,0 @@ -const InsertBottomTableIcon = (props: any) => ( - - - -); - -export default InsertBottomTableIcon; diff --git a/space/components/tiptap/table-menu/InsertLeftTableIcon.tsx b/space/components/tiptap/table-menu/InsertLeftTableIcon.tsx deleted file mode 100644 index 1fd75fe8754..00000000000 --- a/space/components/tiptap/table-menu/InsertLeftTableIcon.tsx +++ /dev/null @@ -1,15 +0,0 @@ -const InsertLeftTableIcon = (props: any) => ( - - - -); -export default InsertLeftTableIcon; diff --git a/space/components/tiptap/table-menu/InsertRightTableIcon.tsx b/space/components/tiptap/table-menu/InsertRightTableIcon.tsx deleted file mode 100644 index 1a65709694b..00000000000 --- a/space/components/tiptap/table-menu/InsertRightTableIcon.tsx +++ /dev/null @@ -1,16 +0,0 @@ -const InsertRightTableIcon = (props: any) => ( - - - -); - -export default InsertRightTableIcon; diff --git a/space/components/tiptap/table-menu/InsertTopTableIcon.tsx b/space/components/tiptap/table-menu/InsertTopTableIcon.tsx deleted file mode 100644 index 8f04f4f6126..00000000000 --- a/space/components/tiptap/table-menu/InsertTopTableIcon.tsx +++ /dev/null @@ -1,15 +0,0 @@ -const InsertTopTableIcon = (props: any) => ( - - - -); -export default InsertTopTableIcon; diff --git a/space/components/tiptap/table-menu/index.tsx b/space/components/tiptap/table-menu/index.tsx deleted file mode 100644 index 94f9c0f8d87..00000000000 --- a/space/components/tiptap/table-menu/index.tsx +++ /dev/null @@ -1,143 +0,0 @@ -import { useState, useEffect } from "react"; -import { Rows, Columns, ToggleRight } from "lucide-react"; -import { cn } from "../utils"; -import { Tooltip } from "components/ui"; -import InsertLeftTableIcon from "./InsertLeftTableIcon"; -import InsertRightTableIcon from "./InsertRightTableIcon"; -import InsertTopTableIcon from "./InsertTopTableIcon"; -import InsertBottomTableIcon from "./InsertBottomTableIcon"; - -interface TableMenuItem { - command: () => void; - icon: any; - key: string; - name: string; -} - -export const findTableAncestor = (node: Node | null): HTMLTableElement | null => { - while (node !== null && node.nodeName !== "TABLE") { - node = node.parentNode; - } - return node as HTMLTableElement; -}; - -export const TableMenu = ({ editor }: { editor: any }) => { - const [tableLocation, setTableLocation] = useState({ bottom: 0, left: 0 }); - const isOpen = editor?.isActive("table"); - - const items: TableMenuItem[] = [ - { - command: () => editor.chain().focus().addColumnBefore().run(), - icon: InsertLeftTableIcon, - key: "insert-column-left", - name: "Insert 1 column left", - }, - { - command: () => editor.chain().focus().addColumnAfter().run(), - icon: InsertRightTableIcon, - key: "insert-column-right", - name: "Insert 1 column right", - }, - { - command: () => editor.chain().focus().addRowBefore().run(), - icon: InsertTopTableIcon, - key: "insert-row-above", - name: "Insert 1 row above", - }, - { - command: () => editor.chain().focus().addRowAfter().run(), - icon: InsertBottomTableIcon, - key: "insert-row-below", - name: "Insert 1 row below", - }, - { - command: () => editor.chain().focus().deleteColumn().run(), - icon: Columns, - key: "delete-column", - name: "Delete column", - }, - { - command: () => editor.chain().focus().deleteRow().run(), - icon: Rows, - key: "delete-row", - name: "Delete row", - }, - { - command: () => editor.chain().focus().toggleHeaderRow().run(), - icon: ToggleRight, - key: "toggle-header-row", - name: "Toggle header row", - }, - ]; - - useEffect(() => { - if (!window) return; - - const handleWindowClick = () => { - const selection: any = window?.getSelection(); - - if (selection.rangeCount !== 0) { - const range = selection.getRangeAt(0); - const tableNode = findTableAncestor(range.startContainer); - - let parent = tableNode?.parentElement; - - if (tableNode) { - const tableRect = tableNode.getBoundingClientRect(); - const tableCenter = tableRect.left + tableRect.width / 2; - const menuWidth = 45; - const menuLeft = tableCenter - menuWidth / 2; - const tableBottom = tableRect.bottom; - - setTableLocation({ bottom: tableBottom, left: menuLeft }); - - while (parent) { - if (!parent.classList.contains("disable-scroll")) - parent.classList.add("disable-scroll"); - parent = parent.parentElement; - } - } else { - const scrollDisabledContainers = document.querySelectorAll(".disable-scroll"); - - scrollDisabledContainers.forEach((container) => { - container.classList.remove("disable-scroll"); - }); - } - } - }; - - window.addEventListener("click", handleWindowClick); - - return () => { - window.removeEventListener("click", handleWindowClick); - }; - }, [tableLocation, editor]); - - return ( -
- {items.map((item, index) => ( - - - - ))} -
- ); -}; diff --git a/space/components/tiptap/utils.ts b/space/components/tiptap/utils.ts deleted file mode 100644 index a5ef193506d..00000000000 --- a/space/components/tiptap/utils.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { clsx, type ClassValue } from "clsx"; -import { twMerge } from "tailwind-merge"; - -export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)); -} diff --git a/space/package.json b/space/package.json index b539fbb65d4..86248e2ac5a 100644 --- a/space/package.json +++ b/space/package.json @@ -3,7 +3,8 @@ "version": "0.13.2", "private": true, "scripts": { - "dev": "next dev -p 4000", + "dev": "turbo run develop", + "develop": "next dev -p 4000", "build": "next build", "start": "next start -p 4000", "lint": "next lint" @@ -17,26 +18,6 @@ "@heroicons/react": "^2.0.12", "@mui/icons-material": "^5.14.1", "@mui/material": "^5.14.1", - "@tiptap/extension-code-block-lowlight": "^2.0.4", - "@tiptap/extension-color": "^2.0.4", - "@tiptap/extension-gapcursor": "^2.1.7", - "@tiptap/extension-highlight": "^2.0.4", - "@tiptap/extension-horizontal-rule": "^2.0.4", - "@tiptap/extension-image": "^2.0.4", - "@tiptap/extension-link": "^2.0.4", - "@tiptap/extension-placeholder": "^2.0.4", - "@tiptap/extension-table": "^2.1.6", - "@tiptap/extension-table-cell": "^2.1.6", - "@tiptap/extension-table-header": "^2.1.6", - "@tiptap/extension-table-row": "^2.1.6", - "@tiptap/extension-task-item": "^2.0.4", - "@tiptap/extension-task-list": "^2.0.4", - "@tiptap/extension-text-style": "^2.0.4", - "@tiptap/extension-underline": "^2.0.4", - "@tiptap/pm": "^2.0.4", - "@tiptap/react": "^2.0.4", - "@tiptap/starter-kit": "^2.0.4", - "@tiptap/suggestion": "^2.0.4", "axios": "^1.3.4", "clsx": "^2.0.0", "js-cookie": "^3.0.1", @@ -51,13 +32,11 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-hook-form": "^7.38.0", - "react-moveable": "^0.54.1", "swr": "^2.2.2", "tailwind-merge": "^1.14.0", - "tiptap-markdown": "^0.8.2", "typescript": "4.9.5", - "use-debounce": "^9.0.4", - "uuid": "^9.0.0" + "uuid": "^9.0.0", + "@plane/rich-text-editor": "*" }, "devDependencies": { "@types/js-cookie": "^3.0.3", diff --git a/space/services/file.service.ts b/space/services/file.service.ts index 6df6423f4d7..1ba4cd4d292 100644 --- a/space/services/file.service.ts +++ b/space/services/file.service.ts @@ -25,19 +25,37 @@ interface UnSplashImageUrls { small_s3: string; } -class FileServices extends APIService { +class FileService extends APIService { constructor() { super(API_BASE_URL); + this.uploadFile = this.uploadFile.bind(this); + this.deleteImage = this.deleteImage.bind(this); } async uploadFile(workspaceSlug: string, file: FormData): Promise { - return this.mediaUpload(`/api/workspaces/${workspaceSlug}/file-assets/`, file) + return this.post(`/api/workspaces/${workspaceSlug}/file-assets/`, file, { + headers: { + ...this.getHeaders(), + "Content-Type": "multipart/form-data", + }, + }) .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); } + getUploadFileFunction(workspaceSlug: string): (file: File) => Promise { + return async (file: File) => { + const formData = new FormData(); + formData.append("asset", file); + formData.append("attributes", JSON.stringify({})); + + const data = await this.uploadFile(workspaceSlug, formData); + return data.asset; + }; + } + async deleteImage(assetUrlWithWorkspaceId: string): Promise { return this.delete(`/api/workspaces/file-assets/${assetUrlWithWorkspaceId}/`) .then((response) => response?.status) @@ -76,6 +94,6 @@ class FileServices extends APIService { } } -const fileServices = new FileServices(); +const fileService = new FileService(); -export default fileServices; +export default fileService; diff --git a/space/styles/editor.css b/space/styles/editor.css index 9da250dd108..85d881eeb46 100644 --- a/space/styles/editor.css +++ b/space/styles/editor.css @@ -155,7 +155,7 @@ ul[data-type="taskList"] li[data-checked="true"] > div > p { } } -#tiptap-container { +#editor-container { table { border-collapse: collapse; table-layout: fixed; diff --git a/turbo.json b/turbo.json index e40a56ab7be..4b64d6231a6 100644 --- a/turbo.json +++ b/turbo.json @@ -28,11 +28,60 @@ ], "pipeline": { "build": { - "dependsOn": ["^build"], - "outputs": [".next/**", "dist/**"] + "dependsOn": [ + "^build" + ], + "outputs": [ + ".next/**", + "dist/**" + ] + }, + "web#develop": { + "cache": false, + "persistent": true, + "dependsOn": [ + "@plane/lite-text-editor#build", + "@plane/rich-text-editor#build" + ] + }, + "space#develop": { + "cache": false, + "persistent": true, + "dependsOn": [ + "@plane/lite-text-editor#build", + "@plane/rich-text-editor#build" + ] + }, + "web#build": { + "cache": true, + "dependsOn": [ + "@plane/lite-text-editor#build", + "@plane/rich-text-editor#build" + ] + }, + "space#build": { + "cache": true, + "dependsOn": [ + "@plane/lite-text-editor#build", + "@plane/rich-text-editor#build" + ] + }, + "@plane/lite-text-editor#build": { + "cache": true, + "dependsOn": [ + "@plane/editor-core#build" + ] + }, + "@plane/rich-text-editor#build": { + "cache": true, + "dependsOn": [ + "@plane/editor-core#build" + ] }, "test": { - "dependsOn": ["^build"], + "dependsOn": [ + "^build" + ], "outputs": [] }, "lint": { @@ -41,6 +90,9 @@ "dev": { "cache": false }, + "develop": { + "cache": false + }, "start": { "cache": false }, diff --git a/web/components/core/modals/gpt-assistant-modal.tsx b/web/components/core/modals/gpt-assistant-modal.tsx index 81c9b7d7129..65495805aee 100644 --- a/web/components/core/modals/gpt-assistant-modal.tsx +++ b/web/components/core/modals/gpt-assistant-modal.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState, forwardRef, useRef } from "react"; +import React, { useEffect, useState, useRef } from "react"; import { useRouter } from "next/router"; // react-hook-form import { useForm } from "react-hook-form"; @@ -10,7 +10,8 @@ import useToast from "hooks/use-toast"; import useUserAuth from "hooks/use-user-auth"; // ui import { Input, PrimaryButton, SecondaryButton } from "components/ui"; -import { TipTapEditor } from "components/tiptap"; +// components +import { RichReadOnlyEditor, RichReadOnlyEditorWithRef } from "@plane/rich-text-editor"; // types import { IIssue, IPageBlock } from "types"; @@ -133,20 +134,17 @@ export const GptAssistantModal: React.FC = ({ return (
{((content && content !== "") || (htmlContent && htmlContent !== "

")) && (
Content: - ${content}

`} customClassName="-m-3" noBorder borderOnFocus={false} - editable={false} ref={editorRef} />
@@ -154,13 +152,11 @@ export const GptAssistantModal: React.FC = ({ {response !== "" && (
Response: - ${response}

`} customClassName="-mx-3 -my-3" noBorder borderOnFocus={false} - editable={false} />
)} @@ -174,11 +170,10 @@ export const GptAssistantModal: React.FC = ({ type="text" name="task" register={register} - placeholder={`${ - content && content !== "" - ? "Tell AI what action to perform on this content..." - : "Ask AI anything..." - }`} + placeholder={`${content && content !== "" + ? "Tell AI what action to perform on this content..." + : "Ask AI anything..." + }`} autoComplete="off" />
@@ -214,8 +209,8 @@ export const GptAssistantModal: React.FC = ({ {isSubmitting ? "Generating response..." : response === "" - ? "Generate response" - : "Generate again"} + ? "Generate response" + : "Generate again"}
diff --git a/web/components/core/modals/image-upload-modal.tsx b/web/components/core/modals/image-upload-modal.tsx index df4f21e12b7..e1795ed0485 100644 --- a/web/components/core/modals/image-upload-modal.tsx +++ b/web/components/core/modals/image-upload-modal.tsx @@ -7,7 +7,7 @@ import { useDropzone } from "react-dropzone"; // headless ui import { Transition, Dialog } from "@headlessui/react"; // services -import fileServices from "services/file.service"; +import fileService from "services/file.service"; // hooks import useWorkspaceDetails from "hooks/use-workspace-details"; // ui @@ -64,7 +64,7 @@ export const ImageUploadModal: React.FC = ({ formData.append("attributes", JSON.stringify({})); if (userImage) { - fileServices + fileService .uploadUserFile(formData) .then((res) => { const imageUrl = res.asset; @@ -73,13 +73,13 @@ export const ImageUploadModal: React.FC = ({ setIsImageUploading(false); setImage(null); - if (value) fileServices.deleteUserFile(value); + if (value) fileService.deleteUserFile(value); }) .catch((err) => { console.error(err); }); } else - fileServices + fileService .uploadFile(workspaceSlug as string, formData) .then((res) => { const imageUrl = res.asset; @@ -87,7 +87,7 @@ export const ImageUploadModal: React.FC = ({ setIsImageUploading(false); setImage(null); - if (value && workspaceDetails) fileServices.deleteFile(workspaceDetails.id, value); + if (value && workspaceDetails) fileService.deleteFile(workspaceDetails.id, value); }) .catch((err) => { console.error(err); diff --git a/web/components/issues/comment/add-comment.tsx b/web/components/issues/comment/add-comment.tsx index 33d7f2289c7..b5d91adee16 100644 --- a/web/components/issues/comment/add-comment.tsx +++ b/web/components/issues/comment/add-comment.tsx @@ -3,11 +3,13 @@ import { useRouter } from "next/router"; // react-hook-form import { useForm, Controller } from "react-hook-form"; // components -import { TipTapEditor } from "components/tiptap"; +import { LiteTextEditorWithRef } from "@plane/lite-text-editor"; // ui -import { Icon, SecondaryButton, Tooltip } from "components/ui"; +import { SecondaryButton } from "components/ui"; // types import type { IIssueComment } from "types"; +// services +import fileService from "services/file.service"; const defaultValues: Partial = { access: "INTERNAL", @@ -20,7 +22,12 @@ type Props = { showAccessSpecifier?: boolean; }; -const commentAccess = [ +type commentAccessType = { + icon: string; + key: string; + label: "Private" | "Public"; +} +const commentAccess: commentAccessType[] = [ { icon: "lock", key: "INTERNAL", @@ -64,50 +71,27 @@ export const AddComment: React.FC = ({
- {showAccessSpecifier && ( -
+ ( ( -
- {commentAccess.map((access) => ( - - - - ))} -
+ render={({ field: { onChange: onCommentChange, value: commentValue } }) => ( +

" : commentValue} + customClassName="p-3 min-h-[100px] shadow-sm" + debouncedUpdatesEnabled={false} + onChange={(comment_json: Object, comment_html: string) => onCommentChange(comment_html)} + commentAccessSpecifier={{ accessValue, onAccessChange, showAccessSpecifier, commentAccess }} + /> )} /> -
- )} - ( -

" : value} - customClassName="p-3 min-h-[100px] shadow-sm" - debouncedUpdatesEnabled={false} - onChange={(comment_json: Object, comment_html: string) => onChange(comment_html)} - /> )} />
diff --git a/web/components/issues/comment/comment-card.tsx b/web/components/issues/comment/comment-card.tsx index 3d3e43b3967..d59dce7d32f 100644 --- a/web/components/issues/comment/comment-card.tsx +++ b/web/components/issues/comment/comment-card.tsx @@ -9,11 +9,13 @@ import useUser from "hooks/use-user"; // ui import { CustomMenu, Icon } from "components/ui"; import { CommentReaction } from "components/issues"; -import { TipTapEditor } from "components/tiptap"; +import { LiteTextEditorWithRef, LiteReadOnlyEditorWithRef } from "@plane/lite-text-editor"; // helpers import { timeAgo } from "helpers/date-time.helper"; // types import type { IIssueComment } from "types"; +// services +import fileService from "services/file.service"; type Props = { comment: IIssueComment; @@ -110,8 +112,10 @@ export const CommentCard: React.FC = ({ onSubmit={handleSubmit(onEnter)} >
- = ({ />
)} - diff --git a/web/components/issues/description-form.tsx b/web/components/issues/description-form.tsx index 54ea0a9760d..70aa53f396c 100644 --- a/web/components/issues/description-form.tsx +++ b/web/components/issues/description-form.tsx @@ -7,9 +7,10 @@ import useReloadConfirmations from "hooks/use-reload-confirmation"; import { useDebouncedCallback } from "use-debounce"; // components import { TextArea } from "components/ui"; -import { TipTapEditor } from "components/tiptap"; // types import { IIssue } from "types"; +import { RichTextEditor } from "@plane/rich-text-editor"; +import fileService from "services/file.service"; export interface IssueDescriptionFormValues { name: string; @@ -84,10 +85,8 @@ export const IssueDescriptionForm: FC = ({ }, [issue, reset]); const debouncedTitleSave = useDebouncedCallback(async () => { - setTimeout(async () => { - handleSubmit(handleDescriptionFormSubmit)().finally(() => setIsSubmitting("submitted")); - }, 500); - }, 1000); + handleSubmit(handleDescriptionFormSubmit)().finally(() => setIsSubmitting("submitted")); + }, 1500); return (
@@ -99,7 +98,7 @@ export const IssueDescriptionForm: FC = ({ placeholder="Enter issue name" register={register} onFocus={() => setCharacterLimit(true)} - onChange={(e) => { + onChange={() => { setCharacterLimit(false); setIsSubmitting("submitting"); debouncedTitleSave(); @@ -131,36 +130,27 @@ export const IssueDescriptionForm: FC = ({ name="description_html" control={control} render={({ field: { value, onChange } }) => { - if (!value) return <>; - - return ( -

" - : value - } - workspaceSlug={workspaceSlug} - debouncedUpdatesEnabled={true} - setShouldShowAlert={setShowAlert} - setIsSubmitting={setIsSubmitting} - customClassName={ - isAllowed ? "min-h-[150px] shadow-sm" : "!p-0 !pt-2 text-custom-text-200" - } - noBorder={!isAllowed} - onChange={(description: Object, description_html: string) => { - setShowAlert(true); - setIsSubmitting("submitting"); - onChange(description_html); - handleSubmit(handleDescriptionFormSubmit)().finally(() => - setIsSubmitting("submitted") - ); - }} - editable={isAllowed} - /> - ); + console.log(value,'value'); + return ( { + setShowAlert(true); + setIsSubmitting("submitting"); + onChange(description_html); + handleSubmit(handleDescriptionFormSubmit)().finally(() => + setIsSubmitting("submitted") + ); + }} + />); }} />
= { project: "", @@ -411,29 +414,23 @@ export const DraftIssueForm: FC = (props) => { { - if (!value && !watch("description_html")) return <>; - - return ( - { - onChange(description_html); - setValue("description", description); - }} - /> - ); - }} + render={({ field: { value, onChange } }) => ( + { + onChange(description_html); + setValue("description", description); + } } /> + )} /> = { project: "", @@ -350,9 +354,8 @@ export const IssueForm: FC = (props) => { {issueName && issueName !== "" && (
= (props) => { ? "Updating Issue..." : "Update Issue" : isSubmitting - ? "Adding Issue..." - : "Add Issue"} + ? "Adding Issue..." + : "Add Issue"}
diff --git a/web/components/issues/sub-issues/issue.tsx b/web/components/issues/sub-issues/issue.tsx index a53d18bb963..4bb8ea23441 100644 --- a/web/components/issues/sub-issues/issue.tsx +++ b/web/components/issues/sub-issues/issue.tsx @@ -19,6 +19,7 @@ import { SubIssuesRootList } from "./issues-list"; import { IssueProperty } from "./properties"; // ui import { Tooltip, CustomMenu } from "components/ui"; + // types import { ICurrentUserResponse, IIssue } from "types"; import { ISubIssuesRootLoaders, ISubIssuesRootLoadersHandler } from "./root"; diff --git a/web/components/issues/sub-issues/issues-list.tsx b/web/components/issues/sub-issues/issues-list.tsx index 9fc77992e72..c79e64e9162 100644 --- a/web/components/issues/sub-issues/issues-list.tsx +++ b/web/components/issues/sub-issues/issues-list.tsx @@ -5,6 +5,7 @@ import useSWR from "swr"; import { SubIssues } from "./issue"; // types import { ICurrentUserResponse, IIssue } from "types"; + import { ISubIssuesRootLoaders, ISubIssuesRootLoadersHandler } from "./root"; // services import issuesService from "services/issues.service"; diff --git a/web/components/issues/sub-issues/root.tsx b/web/components/issues/sub-issues/root.tsx index 4b29b97c9b2..4119a834548 100644 --- a/web/components/issues/sub-issues/root.tsx +++ b/web/components/issues/sub-issues/root.tsx @@ -14,7 +14,9 @@ import { ProgressBar } from "./progressbar"; import { CustomMenu } from "components/ui"; // hooks import { useProjectMyMembership } from "contexts/project-member.context"; + import useToast from "hooks/use-toast"; + // helpers import { copyTextToClipboard } from "helpers/string.helper"; // types @@ -48,6 +50,7 @@ export const SubIssuesRoot: React.FC = ({ parentIssue, user }) = }; const { memberRole } = useProjectMyMembership(); + const { setToastAlert } = useToast(); const { data: issues, isLoading } = useSWR( @@ -119,6 +122,7 @@ export const SubIssuesRoot: React.FC = ({ parentIssue, user }) = const payload = { sub_issue_ids: data.map((i) => i.id), }; + await issuesService.addSubIssues(workspaceSlug, projectId, issueId, payload).finally(() => { if (issueId) mutate(SUB_ISSUES(issueId)); }); diff --git a/web/components/pages/create-update-block-inline.tsx b/web/components/pages/create-update-block-inline.tsx index cee9fe8e30d..49d6fbeedf2 100644 --- a/web/components/pages/create-update-block-inline.tsx +++ b/web/components/pages/create-update-block-inline.tsx @@ -11,12 +11,14 @@ import aiService from "services/ai.service"; import useToast from "hooks/use-toast"; // components import { GptAssistantModal } from "components/core"; -import { TipTapEditor } from "components/tiptap"; +import { RichTextEditorWithRef } from "@plane/rich-text-editor"; import { PrimaryButton, SecondaryButton, TextArea } from "components/ui"; // types import { ICurrentUserResponse, IPageBlock } from "types"; // fetch-keys import { PAGE_BLOCKS_LIST } from "constants/fetch-keys"; +// services +import fileService from "services/file.service"; type Props = { handleClose: () => void; @@ -279,8 +281,9 @@ export const CreateUpdateBlockInline: React.FC = ({ render={({ field: { value, onChange } }) => { if (!data) return ( -

"} debouncedUpdatesEnabled={false} @@ -299,8 +302,9 @@ export const CreateUpdateBlockInline: React.FC = ({ ); return ( - 0 diff --git a/web/components/pages/single-page-block.tsx b/web/components/pages/single-page-block.tsx index e4c1d94acd9..e738c78e49b 100644 --- a/web/components/pages/single-page-block.tsx +++ b/web/components/pages/single-page-block.tsx @@ -19,7 +19,7 @@ import useOutsideClickDetector from "hooks/use-outside-click-detector"; // components import { GptAssistantModal } from "components/core"; import { CreateUpdateBlockInline } from "components/pages"; -import { TipTapEditor } from "components/tiptap"; +import { RichTextEditor } from "@plane/rich-text-editor"; // ui import { CustomMenu, TextArea } from "components/ui"; // icons @@ -39,6 +39,7 @@ import { copyTextToClipboard } from "helpers/string.helper"; import { ICurrentUserResponse, IIssue, IPageBlock, IProject } from "types"; // fetch-keys import { PAGE_BLOCKS_LIST } from "constants/fetch-keys"; +import fileService from "services/file.service"; type Props = { block: IPageBlock; @@ -451,8 +452,9 @@ export const SinglePageBlock: React.FC = ({ {showBlockDetails ? block.description_html.length > 7 && ( - boolean; - command: () => void; - icon: typeof BoldIcon; -} - -type EditorBubbleMenuProps = Omit; - -export const EditorBubbleMenu: FC = (props: any) => { - const items: BubbleMenuItem[] = [ - { - name: "bold", - isActive: () => props.editor?.isActive("bold"), - command: () => props.editor?.chain().focus().toggleBold().run(), - icon: BoldIcon, - }, - { - name: "italic", - isActive: () => props.editor?.isActive("italic"), - command: () => props.editor?.chain().focus().toggleItalic().run(), - icon: ItalicIcon, - }, - { - name: "underline", - isActive: () => props.editor?.isActive("underline"), - command: () => props.editor?.chain().focus().toggleUnderline().run(), - icon: UnderlineIcon, - }, - { - name: "strike", - isActive: () => props.editor?.isActive("strike"), - command: () => props.editor?.chain().focus().toggleStrike().run(), - icon: StrikethroughIcon, - }, - { - name: "code", - isActive: () => props.editor?.isActive("code"), - command: () => props.editor?.chain().focus().toggleCode().run(), - icon: CodeIcon, - }, - ]; - - const bubbleMenuProps: EditorBubbleMenuProps = { - ...props, - shouldShow: ({ editor }) => { - if (!editor.isEditable) { - return false; - } - if (editor.isActive("image")) { - return false; - } - return editor.view.state.selection.content().size > 0; - }, - tippyOptions: { - moveTransition: "transform 0.15s ease-out", - onHidden: () => { - setIsNodeSelectorOpen(false); - setIsLinkSelectorOpen(false); - }, - }, - }; - - const [isNodeSelectorOpen, setIsNodeSelectorOpen] = useState(false); - const [isLinkSelectorOpen, setIsLinkSelectorOpen] = useState(false); - - return ( - - {!props.editor.isActive("table") && ( - { - setIsNodeSelectorOpen(!isNodeSelectorOpen); - setIsLinkSelectorOpen(false); - }} - /> - )} - { - setIsLinkSelectorOpen(!isLinkSelectorOpen); - setIsNodeSelectorOpen(false); - }} - /> -
- {items.map((item, index) => ( - - ))} -
-
- ); -}; diff --git a/web/components/tiptap/bubble-menu/utils/link-validator.tsx b/web/components/tiptap/bubble-menu/utils/link-validator.tsx deleted file mode 100644 index 9af366c0266..00000000000 --- a/web/components/tiptap/bubble-menu/utils/link-validator.tsx +++ /dev/null @@ -1,11 +0,0 @@ -export default function isValidHttpUrl(string: string): boolean { - let url; - - try { - url = new URL(string); - } catch (_) { - return false; - } - - return url.protocol === "http:" || url.protocol === "https:"; -} diff --git a/web/components/tiptap/extensions/image-resize.tsx b/web/components/tiptap/extensions/image-resize.tsx deleted file mode 100644 index 448b8811cc1..00000000000 --- a/web/components/tiptap/extensions/image-resize.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { Editor } from "@tiptap/react"; -import Moveable from "react-moveable"; - -export const ImageResizer = ({ editor }: { editor: Editor }) => { - const updateMediaSize = () => { - const imageInfo = document.querySelector(".ProseMirror-selectednode") as HTMLImageElement; - if (imageInfo) { - const selection = editor.state.selection; - editor.commands.setImage({ - src: imageInfo.src, - width: Number(imageInfo.style.width.replace("px", "")), - height: Number(imageInfo.style.height.replace("px", "")), - } as any); - editor.commands.setNodeSelection(selection.from); - } - }; - - return ( - <> - { - delta[0] && (target!.style.width = `${width}px`); - delta[1] && (target!.style.height = `${height}px`); - }} - onResizeEnd={() => { - updateMediaSize(); - }} - scalable={true} - renderDirections={["w", "e"]} - onScale={({ target, transform }: any) => { - target!.style.transform = transform; - }} - /> - - ); -}; diff --git a/web/components/tiptap/extensions/index.tsx b/web/components/tiptap/extensions/index.tsx deleted file mode 100644 index 8ad4e07b4d5..00000000000 --- a/web/components/tiptap/extensions/index.tsx +++ /dev/null @@ -1,149 +0,0 @@ -import StarterKit from "@tiptap/starter-kit"; -import HorizontalRule from "@tiptap/extension-horizontal-rule"; -import TiptapLink from "@tiptap/extension-link"; -import Placeholder from "@tiptap/extension-placeholder"; -import TiptapUnderline from "@tiptap/extension-underline"; -import TextStyle from "@tiptap/extension-text-style"; -import { Color } from "@tiptap/extension-color"; -import TaskItem from "@tiptap/extension-task-item"; -import TaskList from "@tiptap/extension-task-list"; -import { Markdown } from "tiptap-markdown"; -import Highlight from "@tiptap/extension-highlight"; -import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight"; -import { lowlight } from "lowlight/lib/core"; -import SlashCommand from "../slash-command"; -import { InputRule } from "@tiptap/core"; -import Gapcursor from "@tiptap/extension-gapcursor"; - -import ts from "highlight.js/lib/languages/typescript"; - -import "highlight.js/styles/github-dark.css"; -import UpdatedImage from "./updated-image"; -import isValidHttpUrl from "../bubble-menu/utils/link-validator"; -import { CustomTableCell } from "./table/table-cell"; -import { Table } from "./table/table"; -import { TableHeader } from "./table/table-header"; -import { TableRow } from "@tiptap/extension-table-row"; - -lowlight.registerLanguage("ts", ts); - -export const TiptapExtensions = ( - workspaceSlug: string, - setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void -) => [ - StarterKit.configure({ - bulletList: { - HTMLAttributes: { - class: "list-disc list-outside leading-3 -mt-2", - }, - }, - orderedList: { - HTMLAttributes: { - class: "list-decimal list-outside leading-3 -mt-2", - }, - }, - listItem: { - HTMLAttributes: { - class: "leading-normal -mb-2", - }, - }, - blockquote: { - HTMLAttributes: { - class: "border-l-4 border-custom-border-300", - }, - }, - code: { - HTMLAttributes: { - class: - "rounded-md bg-custom-primary-30 mx-1 px-1 py-1 font-mono font-medium text-custom-text-1000", - spellcheck: "false", - }, - }, - codeBlock: false, - horizontalRule: false, - dropcursor: { - color: "rgba(var(--color-text-100))", - width: 2, - }, - gapcursor: false, - }), - CodeBlockLowlight.configure({ - lowlight, - }), - HorizontalRule.extend({ - addInputRules() { - return [ - new InputRule({ - find: /^(?:---|—-|___\s|\*\*\*\s)$/, - handler: ({ state, range, commands }) => { - commands.splitBlock(); - - const attributes = {}; - const { tr } = state; - const start = range.from; - const end = range.to; - // @ts-ignore - tr.replaceWith(start - 1, end, this.type.create(attributes)); - }, - }), - ]; - }, - }).configure({ - HTMLAttributes: { - class: "mb-6 border-t border-custom-border-300", - }, - }), - Gapcursor, - TiptapLink.configure({ - protocols: ["http", "https"], - validate: (url) => isValidHttpUrl(url), - HTMLAttributes: { - class: - "text-custom-primary-300 underline underline-offset-[3px] hover:text-custom-primary-500 transition-colors cursor-pointer", - }, - }), - UpdatedImage.configure({ - HTMLAttributes: { - class: "rounded-lg border border-custom-border-300", - }, - }), - Placeholder.configure({ - placeholder: ({ node }) => { - if (node.type.name === "heading") { - return `Heading ${node.attrs.level}`; - } - if (node.type.name === "image" || node.type.name === "table") { - return ""; - } - - return "Press '/' for commands..."; - }, - includeChildren: true, - }), - SlashCommand(workspaceSlug, setIsSubmitting), - TiptapUnderline, - TextStyle, - Color, - Highlight.configure({ - multicolor: true, - }), - TaskList.configure({ - HTMLAttributes: { - class: "not-prose pl-2", - }, - }), - TaskItem.configure({ - HTMLAttributes: { - class: "flex items-start my-4", - }, - nested: true, - }), - Markdown.configure({ - html: true, - transformCopiedText: true, - }), - Table, - TableHeader, - CustomTableCell, - TableRow, - ]; diff --git a/web/components/tiptap/extensions/table/table-cell.ts b/web/components/tiptap/extensions/table/table-cell.ts deleted file mode 100644 index 643cb8c64a7..00000000000 --- a/web/components/tiptap/extensions/table/table-cell.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { TableCell } from "@tiptap/extension-table-cell"; - -export const CustomTableCell = TableCell.extend({ - addAttributes() { - return { - ...this.parent?.(), - isHeader: { - default: false, - parseHTML: (element) => { - isHeader: element.tagName === "TD"; - }, - renderHTML: (attributes) => { - tag: attributes.isHeader ? "th" : "td"; - }, - }, - }; - }, - renderHTML({ HTMLAttributes }) { - if (HTMLAttributes.isHeader) { - return [ - "th", - { - ...HTMLAttributes, - class: `relative ${HTMLAttributes.class}`, - }, - ["span", { class: "absolute top-0 right-0" }], - 0, - ]; - } - return ["td", HTMLAttributes, 0]; - }, -}); diff --git a/web/components/tiptap/extensions/table/table-header.ts b/web/components/tiptap/extensions/table/table-header.ts deleted file mode 100644 index f23aa93ef55..00000000000 --- a/web/components/tiptap/extensions/table/table-header.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { TableHeader as BaseTableHeader } from "@tiptap/extension-table-header"; - -const TableHeader = BaseTableHeader.extend({ - content: "paragraph", -}); - -export { TableHeader }; diff --git a/web/components/tiptap/extensions/table/table.ts b/web/components/tiptap/extensions/table/table.ts deleted file mode 100644 index 9b727bb51bd..00000000000 --- a/web/components/tiptap/extensions/table/table.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Table as BaseTable } from "@tiptap/extension-table"; - -const Table = BaseTable.configure({ - resizable: true, - cellMinWidth: 100, - allowTableNodeSelection: true, -}); - -export { Table }; diff --git a/web/components/tiptap/extensions/updated-image.tsx b/web/components/tiptap/extensions/updated-image.tsx deleted file mode 100644 index b620509535e..00000000000 --- a/web/components/tiptap/extensions/updated-image.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import Image from "@tiptap/extension-image"; -import TrackImageDeletionPlugin from "../plugins/delete-image"; -import UploadImagesPlugin from "../plugins/upload-image"; - -const UpdatedImage = Image.extend({ - addProseMirrorPlugins() { - return [UploadImagesPlugin(), TrackImageDeletionPlugin()]; - }, - addAttributes() { - return { - ...this.parent?.(), - width: { - default: "35%", - }, - height: { - default: null, - }, - }; - }, -}); - -export default UpdatedImage; diff --git a/web/components/tiptap/index.tsx b/web/components/tiptap/index.tsx deleted file mode 100644 index 44076234e60..00000000000 --- a/web/components/tiptap/index.tsx +++ /dev/null @@ -1,110 +0,0 @@ -import { useImperativeHandle, useRef, forwardRef, useEffect } from "react"; -import { useEditor, EditorContent, Editor } from "@tiptap/react"; -import { useDebouncedCallback } from "use-debounce"; -// components -import { EditorBubbleMenu } from "./bubble-menu"; -import { TiptapExtensions } from "./extensions"; -import { TiptapEditorProps } from "./props"; -import { ImageResizer } from "./extensions/image-resize"; -import { TableMenu } from "./table-menu"; - -export interface ITipTapRichTextEditor { - value: string; - noBorder?: boolean; - borderOnFocus?: boolean; - customClassName?: string; - editorContentCustomClassNames?: string; - onChange?: (json: any, html: string) => void; - setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void; - setShouldShowAlert?: (showAlert: boolean) => void; - workspaceSlug: string; - editable?: boolean; - forwardedRef?: any; - debouncedUpdatesEnabled?: boolean; -} - -const Tiptap = (props: ITipTapRichTextEditor) => { - const { - onChange, - debouncedUpdatesEnabled, - forwardedRef, - editable, - setIsSubmitting, - setShouldShowAlert, - editorContentCustomClassNames, - value, - noBorder, - workspaceSlug, - borderOnFocus, - customClassName, - } = props; - - const editor = useEditor({ - editable: editable ?? true, - editorProps: TiptapEditorProps(workspaceSlug, setIsSubmitting), - extensions: TiptapExtensions(workspaceSlug, setIsSubmitting), - content: value, - onUpdate: async ({ editor }) => { - // for instant feedback loop - setIsSubmitting?.("submitting"); - setShouldShowAlert?.(true); - if (debouncedUpdatesEnabled) { - debouncedUpdates({ onChange, editor }); - } else { - onChange?.(editor.getJSON(), editor.getHTML()); - } - }, - }); - - const editorRef: React.MutableRefObject = useRef(null); - - useImperativeHandle(forwardedRef, () => ({ - clearEditor: () => { - editorRef.current?.commands.clearContent(); - }, - setEditorValue: (content: string) => { - editorRef.current?.commands.setContent(content); - }, - })); - - const debouncedUpdates = useDebouncedCallback(async ({ onChange, editor }) => { - setTimeout(async () => { - if (onChange) { - onChange(editor.getJSON(), editor.getHTML()); - } - }, 500); - }, 1000); - - const editorClassNames = `relative w-full max-w-full sm:rounded-lg mt-2 p-3 relative focus:outline-none rounded-md - ${noBorder ? "" : "border border-custom-border-200"} ${ - borderOnFocus ? "focus:border border-custom-border-300" : "focus:border-0" - } ${customClassName}`; - - if (!editor) return null; - editorRef.current = editor; - - return ( -
{ - editor?.chain().focus().run(); - }} - className={`tiptap-editor-container relative cursor-text ${editorClassNames}`} - > - {editor && } -
- - - {editor?.isActive("image") && } -
-
- ); -}; - -const TipTapEditor = forwardRef((props, ref) => ( - -)); - -TipTapEditor.displayName = "TipTapEditor"; - -export { TipTapEditor }; diff --git a/web/components/tiptap/slash-command/index.tsx b/web/components/tiptap/slash-command/index.tsx deleted file mode 100644 index 46bf5ea5a34..00000000000 --- a/web/components/tiptap/slash-command/index.tsx +++ /dev/null @@ -1,365 +0,0 @@ -import React, { useState, useEffect, useCallback, ReactNode, useRef, useLayoutEffect } from "react"; -import { Editor, Range, Extension } from "@tiptap/core"; -import Suggestion from "@tiptap/suggestion"; -import { ReactRenderer } from "@tiptap/react"; -import tippy from "tippy.js"; -import { - Heading1, - Heading2, - Heading3, - List, - ListOrdered, - Text, - TextQuote, - Code, - MinusSquare, - CheckSquare, - ImageIcon, - Table, -} from "lucide-react"; -import { startImageUpload } from "../plugins/upload-image"; -import { cn } from "../utils"; - -interface CommandItemProps { - title: string; - description: string; - icon: ReactNode; -} - -interface CommandProps { - editor: Editor; - range: Range; -} - -const Command = Extension.create({ - name: "slash-command", - addOptions() { - return { - suggestion: { - char: "/", - command: ({ editor, range, props }: { editor: Editor; range: Range; props: any }) => { - props.command({ editor, range }); - }, - }, - }; - }, - addProseMirrorPlugins() { - return [ - Suggestion({ - editor: this.editor, - allow({ editor }) { - return !editor.isActive("table"); - }, - ...this.options.suggestion, - }), - ]; - }, -}); - -const getSuggestionItems = - ( - workspaceSlug: string, - setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void - ) => - ({ query }: { query: string }) => - [ - { - title: "Text", - description: "Just start typing with plain text.", - searchTerms: ["p", "paragraph"], - icon: , - command: ({ editor, range }: CommandProps) => { - editor.chain().focus().deleteRange(range).toggleNode("paragraph", "paragraph").run(); - }, - }, - { - title: "Heading 1", - description: "Big section heading.", - searchTerms: ["title", "big", "large"], - icon: , - command: ({ editor, range }: CommandProps) => { - editor.chain().focus().deleteRange(range).setNode("heading", { level: 1 }).run(); - }, - }, - { - title: "Heading 2", - description: "Medium section heading.", - searchTerms: ["subtitle", "medium"], - icon: , - command: ({ editor, range }: CommandProps) => { - editor.chain().focus().deleteRange(range).setNode("heading", { level: 2 }).run(); - }, - }, - { - title: "Heading 3", - description: "Small section heading.", - searchTerms: ["subtitle", "small"], - icon: , - command: ({ editor, range }: CommandProps) => { - editor.chain().focus().deleteRange(range).setNode("heading", { level: 3 }).run(); - }, - }, - { - title: "To-do List", - description: "Track tasks with a to-do list.", - searchTerms: ["todo", "task", "list", "check", "checkbox"], - icon: , - command: ({ editor, range }: CommandProps) => { - editor.chain().focus().deleteRange(range).toggleTaskList().run(); - }, - }, - { - title: "Bullet List", - description: "Create a simple bullet list.", - searchTerms: ["unordered", "point"], - icon: , - command: ({ editor, range }: CommandProps) => { - editor.chain().focus().deleteRange(range).toggleBulletList().run(); - }, - }, - { - title: "Divider", - description: "Visually divide blocks", - searchTerms: ["line", "divider", "horizontal", "rule", "separate"], - icon: , - command: ({ editor, range }: CommandProps) => { - editor.chain().focus().deleteRange(range).setHorizontalRule().run(); - }, - }, - { - title: "Table", - description: "Create a Table", - searchTerms: ["table", "cell", "db", "data", "tabular"], - icon:
, - command: ({ editor, range }: CommandProps) => { - editor - .chain() - .focus() - .deleteRange(range) - .insertTable({ rows: 3, cols: 3, withHeaderRow: true }) - .run(); - }, - }, - { - title: "Numbered List", - description: "Create a list with numbering.", - searchTerms: ["ordered"], - icon: , - command: ({ editor, range }: CommandProps) => { - editor.chain().focus().deleteRange(range).toggleOrderedList().run(); - }, - }, - { - title: "Quote", - description: "Capture a quote.", - searchTerms: ["blockquote"], - icon: , - command: ({ editor, range }: CommandProps) => - editor - .chain() - .focus() - .deleteRange(range) - .toggleNode("paragraph", "paragraph") - .toggleBlockquote() - .run(), - }, - { - title: "Code", - description: "Capture a code snippet.", - searchTerms: ["codeblock"], - icon: , - command: ({ editor, range }: CommandProps) => - editor.chain().focus().deleteRange(range).toggleCodeBlock().run(), - }, - { - title: "Image", - description: "Upload an image from your computer.", - searchTerms: ["photo", "picture", "media"], - icon: , - command: ({ editor, range }: CommandProps) => { - editor.chain().focus().deleteRange(range).run(); - // upload image - const input = document.createElement("input"); - input.type = "file"; - input.accept = "image/*"; - input.onchange = async () => { - if (input.files?.length) { - const file = input.files[0]; - const pos = editor.view.state.selection.from; - startImageUpload(file, editor.view, pos, workspaceSlug, setIsSubmitting); - } - }; - input.click(); - }, - }, - ].filter((item) => { - if (typeof query === "string" && query.length > 0) { - const search = query.toLowerCase(); - return ( - item.title.toLowerCase().includes(search) || - item.description.toLowerCase().includes(search) || - (item.searchTerms && item.searchTerms.some((term: string) => term.includes(search))) - ); - } - return true; - }); - -export const updateScrollView = (container: HTMLElement, item: HTMLElement) => { - const containerHeight = container.offsetHeight; - const itemHeight = item ? item.offsetHeight : 0; - - const top = item.offsetTop; - const bottom = top + itemHeight; - - if (top < container.scrollTop) { - container.scrollTop -= container.scrollTop - top + 5; - } else if (bottom > containerHeight + container.scrollTop) { - container.scrollTop += bottom - containerHeight - container.scrollTop + 5; - } -}; - -const CommandList = ({ - items, - command, -}: { - items: CommandItemProps[]; - command: any; - editor: any; - range: any; -}) => { - const [selectedIndex, setSelectedIndex] = useState(0); - - const selectItem = useCallback( - (index: number) => { - const item = items[index]; - if (item) { - command(item); - } - }, - [command, items] - ); - - useEffect(() => { - const navigationKeys = ["ArrowUp", "ArrowDown", "Enter"]; - const onKeyDown = (e: KeyboardEvent) => { - if (navigationKeys.includes(e.key)) { - e.preventDefault(); - if (e.key === "ArrowUp") { - setSelectedIndex((selectedIndex + items.length - 1) % items.length); - return true; - } - if (e.key === "ArrowDown") { - setSelectedIndex((selectedIndex + 1) % items.length); - return true; - } - if (e.key === "Enter") { - selectItem(selectedIndex); - return true; - } - return false; - } - }; - document.addEventListener("keydown", onKeyDown); - return () => { - document.removeEventListener("keydown", onKeyDown); - }; - }, [items, selectedIndex, setSelectedIndex, selectItem]); - - useEffect(() => { - setSelectedIndex(0); - }, [items]); - - const commandListContainer = useRef(null); - - useLayoutEffect(() => { - const container = commandListContainer?.current; - - const item = container?.children[selectedIndex] as HTMLElement; - - if (item && container) updateScrollView(container, item); - }, [selectedIndex]); - - return items.length > 0 ? ( -
- {items.map((item: CommandItemProps, index: number) => ( - - ))} -
- ) : null; -}; - -const renderItems = () => { - let component: ReactRenderer | null = null; - let popup: any | null = null; - - return { - onStart: (props: { editor: Editor; clientRect: DOMRect }) => { - component = new ReactRenderer(CommandList, { - props, - editor: props.editor, - }); - - // @ts-ignore - popup = tippy("body", { - getReferenceClientRect: props.clientRect, - appendTo: () => document.querySelector("#tiptap-container"), - content: component.element, - showOnCreate: true, - interactive: true, - trigger: "manual", - placement: "bottom-start", - }); - }, - onUpdate: (props: { editor: Editor; clientRect: DOMRect }) => { - component?.updateProps(props); - - popup && - popup[0].setProps({ - getReferenceClientRect: props.clientRect, - }); - }, - onKeyDown: (props: { event: KeyboardEvent }) => { - if (props.event.key === "Escape") { - popup?.[0].hide(); - - return true; - } - - // @ts-ignore - return component?.ref?.onKeyDown(props); - }, - onExit: () => { - popup?.[0].destroy(); - component?.destroy(); - }, - }; -}; - -export const SlashCommand = ( - workspaceSlug: string, - setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void -) => - Command.configure({ - suggestion: { - items: getSuggestionItems(workspaceSlug, setIsSubmitting), - render: renderItems, - }, - }); - -export default SlashCommand; diff --git a/web/components/tiptap/table-menu/InsertBottomTableIcon.tsx b/web/components/tiptap/table-menu/InsertBottomTableIcon.tsx deleted file mode 100644 index 0e42ba64824..00000000000 --- a/web/components/tiptap/table-menu/InsertBottomTableIcon.tsx +++ /dev/null @@ -1,16 +0,0 @@ -const InsertBottomTableIcon = (props: any) => ( - - - -); - -export default InsertBottomTableIcon; diff --git a/web/components/tiptap/table-menu/InsertLeftTableIcon.tsx b/web/components/tiptap/table-menu/InsertLeftTableIcon.tsx deleted file mode 100644 index 1fd75fe8754..00000000000 --- a/web/components/tiptap/table-menu/InsertLeftTableIcon.tsx +++ /dev/null @@ -1,15 +0,0 @@ -const InsertLeftTableIcon = (props: any) => ( - - - -); -export default InsertLeftTableIcon; diff --git a/web/components/tiptap/table-menu/InsertRightTableIcon.tsx b/web/components/tiptap/table-menu/InsertRightTableIcon.tsx deleted file mode 100644 index 1a65709694b..00000000000 --- a/web/components/tiptap/table-menu/InsertRightTableIcon.tsx +++ /dev/null @@ -1,16 +0,0 @@ -const InsertRightTableIcon = (props: any) => ( - - - -); - -export default InsertRightTableIcon; diff --git a/web/components/tiptap/table-menu/InsertTopTableIcon.tsx b/web/components/tiptap/table-menu/InsertTopTableIcon.tsx deleted file mode 100644 index 8f04f4f6126..00000000000 --- a/web/components/tiptap/table-menu/InsertTopTableIcon.tsx +++ /dev/null @@ -1,15 +0,0 @@ -const InsertTopTableIcon = (props: any) => ( - - - -); -export default InsertTopTableIcon; diff --git a/web/components/tiptap/table-menu/index.tsx b/web/components/tiptap/table-menu/index.tsx deleted file mode 100644 index daa8f695364..00000000000 --- a/web/components/tiptap/table-menu/index.tsx +++ /dev/null @@ -1,125 +0,0 @@ -import { useState, useEffect } from "react"; -import { Rows, Columns, ToggleRight } from "lucide-react"; -import { cn } from "../utils"; -import { Tooltip } from "components/ui"; -import InsertLeftTableIcon from "./InsertLeftTableIcon"; -import InsertRightTableIcon from "./InsertRightTableIcon"; -import InsertTopTableIcon from "./InsertTopTableIcon"; -import InsertBottomTableIcon from "./InsertBottomTableIcon"; - -interface TableMenuItem { - command: () => void; - icon: any; - key: string; - name: string; -} - -export const findTableAncestor = (node: Node | null): HTMLTableElement | null => { - while (node !== null && node.nodeName !== "TABLE") { - node = node.parentNode; - } - return node as HTMLTableElement; -}; - -export const TableMenu = ({ editor }: { editor: any }) => { - const [tableLocation, setTableLocation] = useState({ bottom: 0, left: 0 }); - const isOpen = editor?.isActive("table"); - - const items: TableMenuItem[] = [ - { - command: () => editor.chain().focus().addColumnBefore().run(), - icon: InsertLeftTableIcon, - key: "insert-column-left", - name: "Insert 1 column left", - }, - { - command: () => editor.chain().focus().addColumnAfter().run(), - icon: InsertRightTableIcon, - key: "insert-column-right", - name: "Insert 1 column right", - }, - { - command: () => editor.chain().focus().addRowBefore().run(), - icon: InsertTopTableIcon, - key: "insert-row-above", - name: "Insert 1 row above", - }, - { - command: () => editor.chain().focus().addRowAfter().run(), - icon: InsertBottomTableIcon, - key: "insert-row-below", - name: "Insert 1 row below", - }, - { - command: () => editor.chain().focus().deleteColumn().run(), - icon: Columns, - key: "delete-column", - name: "Delete column", - }, - { - command: () => editor.chain().focus().deleteRow().run(), - icon: Rows, - key: "delete-row", - name: "Delete row", - }, - { - command: () => editor.chain().focus().toggleHeaderRow().run(), - icon: ToggleRight, - key: "toggle-header-row", - name: "Toggle header row", - }, - ]; - - useEffect(() => { - if (!window) return; - - const handleWindowClick = () => { - const selection: any = window?.getSelection(); - - if (selection.rangeCount !== 0) { - const range = selection.getRangeAt(0); - const tableNode = findTableAncestor(range.startContainer); - - if (tableNode) { - const tableRect = tableNode.getBoundingClientRect(); - const tableCenter = tableRect.left + tableRect.width / 2; - const menuWidth = 45; - const menuLeft = tableCenter - menuWidth / 2; - const tableBottom = tableRect.bottom; - - setTableLocation({ bottom: tableBottom, left: menuLeft }); - } - } - }; - - window.addEventListener("click", handleWindowClick); - - return () => { - window.removeEventListener("click", handleWindowClick); - }; - }, [tableLocation, editor]); - - return ( -
- {items.map((item, index) => ( - - - - ))} -
- ); -}; diff --git a/web/components/tiptap/utils.ts b/web/components/tiptap/utils.ts deleted file mode 100644 index a5ef193506d..00000000000 --- a/web/components/tiptap/utils.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { clsx, type ClassValue } from "clsx"; -import { twMerge } from "tailwind-merge"; - -export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)); -} diff --git a/web/components/web-view/add-comment.tsx b/web/components/web-view/add-comment.tsx index b4f49d7beb0..fe7e3eb04d6 100644 --- a/web/components/web-view/add-comment.tsx +++ b/web/components/web-view/add-comment.tsx @@ -10,16 +10,16 @@ import { useForm, Controller } from "react-hook-form"; import useProjectDetails from "hooks/use-project-details"; // components -import { TipTapEditor } from "components/tiptap"; - +import { LiteTextEditorWithRef } from "@plane/lite-text-editor"; // icons import { Send } from "lucide-react"; // ui -import { Icon, SecondaryButton, Tooltip, PrimaryButton } from "components/ui"; +import { PrimaryButton } from "components/ui"; // types import type { IIssueComment } from "types"; +import fileService from "services/file.service"; const defaultValues: Partial = { access: "INTERNAL", @@ -31,7 +31,13 @@ type Props = { onSubmit: (data: IIssueComment) => Promise; }; -const commentAccess = [ +type commentAccessType = { + icon: string; + key: string; + label: "Private" | "Public"; +} + +const commentAccess: commentAccessType[] = [ { icon: "lock", key: "INTERNAL", @@ -52,7 +58,7 @@ export const AddComment: React.FC = ({ disabled = false, onSubmit }) => { const { projectDetails } = useProjectDetails(); - const showAccessSpecifier = projectDetails?.is_deployed; + const showAccessSpecifier = projectDetails?.is_deployed || false; const { control, @@ -73,48 +79,26 @@ export const AddComment: React.FC = ({ disabled = false, onSubmit }) => { return (
- {showAccessSpecifier && ( -
+ ( ( -
- {commentAccess.map((access) => ( - - - - ))} -
+ render={({ field: { onChange: onCommentChange, value: commentValue } }) => ( +

" : commentValue} + customClassName="p-3 min-h-[100px] shadow-sm" + debouncedUpdatesEnabled={false} + onChange={(comment_json: Object, comment_html: string) => onCommentChange(comment_html)} + commentAccessSpecifier={{ accessValue, onAccessChange, showAccessSpecifier, commentAccess }} + /> )} /> -
- )} - ( -

" : value} - customClassName="p-3 min-h-[100px] shadow-sm" - debouncedUpdatesEnabled={false} - onChange={(comment_json: Object, comment_html: string) => onChange(comment_html)} - /> )} />
diff --git a/web/components/web-view/issue-web-view-form.tsx b/web/components/web-view/issue-web-view-form.tsx index ff9383fd0af..f48cf094a2f 100644 --- a/web/components/web-view/issue-web-view-form.tsx +++ b/web/components/web-view/issue-web-view-form.tsx @@ -16,11 +16,13 @@ import useReloadConfirmations from "hooks/use-reload-confirmation"; import { TextArea } from "components/ui"; // components -import { TipTapEditor } from "components/tiptap"; +import { RichTextEditor } from "@plane/rich-text-editor"; import { Label } from "components/web-view"; // types import type { IIssue } from "types"; +import fileService from "services/file.service"; +// services type Props = { isAllowed: boolean; @@ -117,38 +119,29 @@ export const IssueWebViewForm: React.FC = (props) => { { - if (!value) return <>; - - return ( -

" - : value - } - workspaceSlug={workspaceSlug!.toString()} - debouncedUpdatesEnabled={true} - setShouldShowAlert={setShowAlert} - setIsSubmitting={setIsSubmitting} - customClassName={ - isAllowed ? "min-h-[150px] shadow-sm" : "!p-0 !pt-2 text-custom-text-200" - } - noBorder={!isAllowed} - onChange={(description: Object, description_html: string) => { - setShowAlert(true); - setIsSubmitting("submitting"); - onChange(description_html); - handleSubmit(handleDescriptionFormSubmit)().finally(() => - setIsSubmitting("submitted") - ); - }} - editable={isAllowed} - /> - ); - }} + render={({ field: { value, onChange } }) => ( +

" + : value} + debouncedUpdatesEnabled={true} + setShouldShowAlert={setShowAlert} + setIsSubmitting={setIsSubmitting} + customClassName={isAllowed ? "min-h-[150px] shadow-sm" : "!p-0 !pt-2 text-custom-text-200"} + noBorder={!isAllowed} + onChange={(description: Object, description_html: string) => { + setShowAlert(true); + setIsSubmitting("submitting"); + onChange(description_html); + handleSubmit(handleDescriptionFormSubmit)().finally(() => setIsSubmitting("submitted") + ); + } } + editable={isAllowed} /> + )} />
{ const [user, setUser] = useState(); @@ -134,7 +135,9 @@ const Editor: NextPage = () => { name="description_html" control={control} render={({ field: { value, onChange } }) => ( - { } editable={editable === "true"} noBorder={true} - workspaceSlug={cookies.MOBILE_slug ?? ""} debouncedUpdatesEnabled={true} setShouldShowAlert={setShowAlert} setIsSubmitting={setIsSubmitting} diff --git a/web/pages/[workspaceSlug]/me/profile/activity.tsx b/web/pages/[workspaceSlug]/me/profile/activity.tsx index d8588390e86..9305577cf0c 100644 --- a/web/pages/[workspaceSlug]/me/profile/activity.tsx +++ b/web/pages/[workspaceSlug]/me/profile/activity.tsx @@ -9,7 +9,7 @@ import userService from "services/user.service"; import { WorkspaceAuthorizationLayout } from "layouts/auth-layout"; // components import { ActivityIcon, ActivityMessage } from "components/core"; -import { TipTapEditor } from "components/tiptap"; +import { RichReadOnlyEditor } from "@plane/rich-text-editor"; // icons import { ArrowTopRightOnSquareIcon, ChatBubbleLeftEllipsisIcon } from "@heroicons/react/24/outline"; // ui @@ -96,8 +96,7 @@ const ProfileActivity = () => {

- { customClassName="text-xs border border-custom-border-200 bg-custom-background-100" noBorder borderOnFocus={false} - editable={false} />
@@ -117,11 +115,11 @@ const ProfileActivity = () => { const message = activityItem.verb === "created" && - activityItem.field !== "cycles" && - activityItem.field !== "modules" && - activityItem.field !== "attachment" && - activityItem.field !== "link" && - activityItem.field !== "estimate" ? ( + activityItem.field !== "cycles" && + activityItem.field !== "modules" && + activityItem.field !== "attachment" && + activityItem.field !== "link" && + activityItem.field !== "estimate" ? ( created{" "} {
{activityItem.field === "archived_at" && - activityItem.new_value !== "restore" ? ( + activityItem.new_value !== "restore" ? ( Plane ) : activityItem.actor_detail.is_bot ? ( diff --git a/web/pages/_app.tsx b/web/pages/_app.tsx index b94e52d6bd8..53383fbe1ea 100644 --- a/web/pages/_app.tsx +++ b/web/pages/_app.tsx @@ -6,6 +6,7 @@ import NProgress from "nprogress"; // styles import "styles/globals.css"; import "styles/editor.css"; +import "styles/tables.css"; import "styles/command-pallette.css"; import "styles/nprogress.css"; import "styles/react-datepicker.css"; diff --git a/web/pages/m/[workspaceSlug]/editor.tsx b/web/pages/m/[workspaceSlug]/editor.tsx index 2bfac63b85a..e385eef9f3f 100644 --- a/web/pages/m/[workspaceSlug]/editor.tsx +++ b/web/pages/m/[workspaceSlug]/editor.tsx @@ -14,8 +14,10 @@ import { Controller, useForm } from "react-hook-form"; import WebViewLayout from "layouts/web-view-layout"; // components -import { TipTapEditor } from "components/tiptap"; +import { RichTextEditor } from "@plane/rich-text-editor"; import { PrimaryButton, Spinner } from "components/ui"; +import fileService from "services/file.service"; +// services const Editor: NextPage = () => { const [isLoading, setIsLoading] = useState(false); @@ -52,7 +54,9 @@ const Editor: NextPage = () => { name="data_html" control={control} render={({ field: { value, onChange } }) => ( - { } editable={isEditable} noBorder={true} - workspaceSlug={workspaceSlug?.toString() ?? ""} debouncedUpdatesEnabled={true} customClassName="min-h-[150px] shadow-sm" editorContentCustomClassNames="pb-9" diff --git a/web/postcss.config.js b/web/postcss.config.js index 129aa7f59ec..6887c82624a 100644 --- a/web/postcss.config.js +++ b/web/postcss.config.js @@ -1 +1,8 @@ -module.exports = require("tailwind-config-custom/postcss.config"); +module.exports = { + plugins: { + "postcss-import": {}, + "tailwindcss/nesting": {}, + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/web/services/file.service.ts b/web/services/file.service.ts index 15cdc1786f6..2b7bdb651f8 100644 --- a/web/services/file.service.ts +++ b/web/services/file.service.ts @@ -26,19 +26,37 @@ interface UnSplashImageUrls { small_s3: string; } -class FileServices extends APIService { +class FileService extends APIService { constructor() { super(API_BASE_URL); + this.uploadFile = this.uploadFile.bind(this); + this.deleteImage = this.deleteImage.bind(this); } async uploadFile(workspaceSlug: string, file: FormData): Promise { - return this.mediaUpload(`/api/workspaces/${workspaceSlug}/file-assets/`, file) + return this.post(`/api/workspaces/${workspaceSlug}/file-assets/`, file, { + headers: { + ...this.getHeaders(), + "Content-Type": "multipart/form-data", + }, + }) .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); } + getUploadFileFunction(workspaceSlug: string): (file: File) => Promise { + return async (file: File) => { + const formData = new FormData(); + formData.append("asset", file); + formData.append("attributes", JSON.stringify({})); + + const data = await this.uploadFile(workspaceSlug, formData); + return data.asset; + }; + } + async deleteImage(assetUrlWithWorkspaceId: string): Promise { return this.delete(`/api/workspaces/file-assets/${assetUrlWithWorkspaceId}/`) .then((response) => response?.status) @@ -57,6 +75,7 @@ class FileServices extends APIService { throw error?.response?.data; }); } + async uploadUserFile(file: FormData): Promise { return this.mediaUpload(`/api/users/file-assets/`, file) .then((response) => response?.data) @@ -97,4 +116,6 @@ class FileServices extends APIService { } } -export default new FileServices(); +const fileService = new FileService(); + +export default fileService; diff --git a/web/styles/editor.css b/web/styles/editor.css index 9da250dd108..0f4da3dff1d 100644 --- a/web/styles/editor.css +++ b/web/styles/editor.css @@ -14,6 +14,15 @@ height: 0; } +.ProseMirror pre { + background-color: rgb(var(--color-background-400)); + padding: 0.5rem; + border-radius: 0.25rem; + border: 1px solid rgb(var(--color-border-200)); + margin: 0.5rem 0; + overflow-x: auto; +} + /* Custom image styles */ .ProseMirror img { @@ -155,7 +164,7 @@ ul[data-type="taskList"] li[data-checked="true"] > div > p { } } -#tiptap-container { +#editor-container { table { border-collapse: collapse; table-layout: fixed; @@ -229,3 +238,62 @@ ul[data-type="taskList"] li[data-checked="true"] > div > p { .ProseMirror table * .is-empty::before { opacity: 0; } + +.ProseMirror:not(.dragging) .ProseMirror-selectednode { + outline: none !important; + border-radius: 0.2rem; + background-color: var(--novel-highlight-blue); + transition: background-color 0.2s; + box-shadow: none; +} + +.drag-handle { + position: fixed; + opacity: 1; + transition: opacity ease-in 0.2s; + border-radius: 0.25rem; + + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 10' style='fill: rgba(0, 0, 0, 0.5)'%3E%3Cpath d='M3,2 C2.44771525,2 2,1.55228475 2,1 C2,0.44771525 2.44771525,0 3,0 C3.55228475,0 4,0.44771525 4,1 C4,1.55228475 3.55228475,2 3,2 Z M3,6 C2.44771525,6 2,5.55228475 2,5 C2,4.44771525 2.44771525,4 3,4 C3.55228475,4 4,4.44771525 4,5 C4,5.55228475 3.55228475,6 3,6 Z M3,10 C2.44771525,10 2,9.55228475 2,9 C2,8.44771525 2.44771525,8 3,8 C3.55228475,8 4,8.44771525 4,9 C4,9.55228475 3.55228475,10 3,10 Z M7,2 C6.44771525,2 6,1.55228475 6,1 C6,0.44771525 6.44771525,0 7,0 C7.55228475,0 8,0.44771525 8,1 C8,1.55228475 7.55228475,2 7,2 Z M7,6 C6.44771525,6 6,5.55228475 6,5 C6,4.44771525 6.44771525,4 7,4 C7.55228475,4 8,4.44771525 8,5 C8,5.55228475 7.55228475,6 7,6 Z M7,10 C6.44771525,10 6,9.55228475 6,9 C6,8.44771525 6.44771525,8 7,8 C7.55228475,8 8,8.44771525 8,9 C8,9.55228475 7.55228475,10 7,10 Z'%3E%3C/path%3E%3C/svg%3E"); + background-size: calc(0.5em + 0.375rem) calc(0.5em + 0.375rem); + background-repeat: no-repeat; + background-position: center; + width: 1.2rem; + height: 1.5rem; + z-index: 50; + cursor: grab; + + &:hover { + background-color: var(--novel-stone-100); + transition: background-color 0.2s; + } + + &:active { + background-color: var(--novel-stone-200); + transition: background-color 0.2s; + } + + &.hide { + opacity: 0; + pointer-events: none; + } + + @media screen and (max-width: 600px) { + display: none; + pointer-events: none; + } +} + +.drag-handle { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 10' style='fill: rgba(255, 255, 255, 0.5)'%3E%3Cpath d='M3,2 C2.44771525,2 2,1.55228475 2,1 C2,0.44771525 2.44771525,0 3,0 C3.55228475,0 4,0.44771525 4,1 C4,1.55228475 3.55228475,2 3,2 Z M3,6 C2.44771525,6 2,5.55228475 2,5 C2,4.44771525 2.44771525,4 3,4 C3.55228475,4 4,4.44771525 4,5 C4,5.55228475 3.55228475,6 3,6 Z M3,10 C2.44771525,10 2,9.55228475 2,9 C2,8.44771525 2.44771525,8 3,8 C3.55228475,8 4,8.44771525 4,9 C4,9.55228475 3.55228475,10 3,10 Z M7,2 C6.44771525,2 6,1.55228475 6,1 C6,0.44771525 6.44771525,0 7,0 C7.55228475,0 8,0.44771525 8,1 C8,1.55228475 7.55228475,2 7,2 Z M7,6 C6.44771525,6 6,5.55228475 6,5 C6,4.44771525 6.44771525,4 7,4 C7.55228475,4 8,4.44771525 8,5 C8,5.55228475 7.55228475,6 7,6 Z M7,10 C6.44771525,10 6,9.55228475 6,9 C6,8.44771525 6.44771525,8 7,8 C7.55228475,8 8,8.44771525 8,9 C8,9.55228475 7.55228475,10 7,10 Z'%3E%3C/path%3E%3C/svg%3E"); +} + +div[data-type="horizontalRule"] { + line-height: 0; + padding: 0.25rem 0; + margin-top: 0; + margin-bottom: 0; + + & > div { + border-bottom: 1px solid rgb(var(--color-text-100)); + } +} diff --git a/web/styles/tables.css b/web/styles/tables.css new file mode 100644 index 00000000000..8b3f105f4cd --- /dev/null +++ b/web/styles/tables.css @@ -0,0 +1,200 @@ +.tableWrapper { + overflow-x: auto; + padding: 2px; + width: fit-content; + max-width: 100%; +} + +.tableWrapper table { + border-collapse: collapse; + table-layout: fixed; + margin: 0; + border: 1px solid rgb(var(--color-border-200)); + width: 100%; +} + +.tableWrapper table td, +.tableWrapper table th { + min-width: 1em; + border: 1px solid rgb(var(--color-border-200)); + padding: 10px 15px; + vertical-align: top; + box-sizing: border-box; + position: relative; + transition: background-color 0.3s ease; + + >* { + margin-bottom: 0; + } +} + +.tableWrapper table td>*, +.tableWrapper table th>* { + margin: 0 !important; + padding: 0.25rem 0 !important; +} + +.tableWrapper table td.has-focus, +.tableWrapper table th.has-focus { + box-shadow: rgba(var(--color-primary-300), 0.1) 0px 0px 0px 2px inset !important; +} + +.tableWrapper table th { + font-weight: bold; + text-align: left; + background-color: rgb(var(--color-primary-100)); +} + +/* .tableWrapper table td:hover{ */ +/* background-color: rgba(var(--color-primary-300), 0.1); */ +/* } */ + +.tableWrapper table th * { + font-weight: 600; +} + +.tableWrapper table .selectedCell:after { + z-index: 2; + position: absolute; + content: ""; + left: 0; + right: 0; + top: 0; + bottom: 0; + background-color: rgba(var(--color-primary-300), 0.1); + pointer-events: none; +} + +.tableWrapper table .column-resize-handle { + position: absolute; + right: -2px; + top: 0; + bottom: -2px; + width: 4px; + z-index: 99; + background-color: rgb(var(--color-primary-400)); + pointer-events: none; +} + +.tableWrapper .tableControls { + position: absolute; +} + +.tableWrapper .tableControls .columnsControl, +.tableWrapper .tableControls .rowsControl { + transition: opacity ease-in 100ms; + position: absolute; + z-index: 99; + display: flex; + justify-content: center; + align-items: center; +} + +.tableWrapper .tableControls .columnsControl { + height: 20px; + transform: translateY(-50%); +} + +.tableWrapper .tableControls .columnsControl>button { + color: white; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='24' height='24'%3E%3Cpath fill='none' d='M0 0h24v24H0z'/%3E%3Cpath fill='%238F95B2' d='M4.5 10.5c-.825 0-1.5.675-1.5 1.5s.675 1.5 1.5 1.5S6 12.825 6 12s-.675-1.5-1.5-1.5zm15 0c-.825 0-1.5.675-1.5 1.5s.675 1.5 1.5 1.5S21 12.825 21 12s-.675-1.5-1.5-1.5zm-7.5 0c-.825 0-1.5.675-1.5 1.5s.675 1.5 1.5 1.5 1.5-.675 1.5-1.5-.675-1.5-1.5-1.5z'/%3E%3C/svg%3E"); + width: 30px; + height: 15px; +} + +.tableWrapper .tableControls .rowsControl { + width: 20px; + transform: translateX(-50%); +} + +.tableWrapper .tableControls .rowsControl>button { + color: white; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='24' height='24'%3E%3Cpath fill='none' d='M0 0h24v24H0z'/%3E%3Cpath fill='%238F95B2' d='M12 3c-.825 0-1.5.675-1.5 1.5S11.175 6 12 6s1.5-.675 1.5-1.5S12.825 3 12 3zm0 15c-.825 0-1.5.675-1.5 1.5S11.175 21 12 21s1.5-.675 1.5-1.5S12.825 18 12 18zm0-7.5c-.825 0-1.5.675-1.5 1.5s.675 1.5 1.5 1.5 1.5-.675 1.5-1.5-.675-1.5-1.5-1.5z'/%3E%3C/svg%3E"); + height: 30px; + width: 15px; +} + +.tableWrapper .tableControls button { + background-color: rgb(var(--color-primary-100)); + border: 1px solid rgb(var(--color-border-200)); + border-radius: 2px; + background-size: 1.25rem; + background-repeat: no-repeat; + background-position: center; + transition: transform ease-out 100ms, background-color ease-out 100ms; + outline: none; + box-shadow: #000 0px 2px 4px; + cursor: pointer; +} + +/* .tableWrapper .tableControls button:hover { */ +/* transform: scale(1.2, 1.2); */ +/* background-color: var(--color-n50); */ +/* } */ + +.tableWrapper .tableControls .tableToolbox, +.tableWrapper .tableControls .tableColorPickerToolbox { + padding: 0.25rem; + display: flex; + flex-direction: column; + width: 200px; + gap: 0.25rem; +} + +.tableWrapper .tableControls .tableToolbox .toolboxItem, +.tableWrapper .tableControls .tableColorPickerToolbox .toolboxItem { + background-color: rgb(var(--color-background-100)); + display: flex; + align-items: center; + gap: 0.5rem; + border: none; + padding: 0.1rem; + border-radius: 4px; + cursor: pointer; + transition: all 0.2s; +} + +.tableWrapper .tableControls .tableToolbox .toolboxItem:hover, +.tableWrapper .tableControls .tableColorPickerToolbox .toolboxItem:hover { + background-color: var(--color-n100); +} + +.tableWrapper .tableControls .tableToolbox .toolboxItem .iconContainer, +.tableWrapper .tableControls .tableColorPickerToolbox .toolboxItem .iconContainer, +.tableWrapper .tableControls .tableToolbox .toolboxItem .colorContainer, +.tableWrapper .tableControls .tableColorPickerToolbox .toolboxItem .colorContainer { + border: 1px solid #e6e8f0; + border-radius: 3px; + padding: 4px; + display: flex; + align-items: center; + justify-content: center; + width: 1.75rem; + height: 1.75rem; +} + +.tableWrapper .tableControls .tableToolbox .toolboxItem .iconContainer svg, +.tableWrapper .tableControls .tableColorPickerToolbox .toolboxItem .iconContainer svg, +.tableWrapper .tableControls .tableToolbox .toolboxItem .colorContainer svg, +.tableWrapper .tableControls .tableColorPickerToolbox .toolboxItem .colorContainer svg { + width: 1rem; + height: 1rem; +} + +.tableToolbox { + background-color: rgb(var(--color-background-100)); +} + +.tableWrapper .tableControls .tableToolbox .toolboxItem .label, +.tableWrapper .tableControls .tableColorPickerToolbox .toolboxItem .label { + font-size: 0.95rem; + color: var(--color-black); +} + +.resize-cursor .tableWrapper .tableControls .rowsControl, +.tableWrapper.controls--disabled .tableControls .rowsControl, +.resize-cursor .tableWrapper .tableControls .columnsControl, +.tableWrapper.controls--disabled .tableControls .columnsControl { + opacity: 0; + pointer-events: none; +} diff --git a/web/tailwind.config.js b/web/tailwind.config.js index 1e1e598260f..05bc93bdcd2 100644 --- a/web/tailwind.config.js +++ b/web/tailwind.config.js @@ -1 +1,5 @@ -module.exports = require("tailwind-config-custom/tailwind.config"); +const sharedConfig = require("tailwind-config-custom/tailwind.config.js"); + +module.exports = { + presets: [sharedConfig], +}; diff --git a/yarn.lock b/yarn.lock index 9d18f4ceed4..8d340b3ea5a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -44,26 +44,26 @@ "@babel/highlight" "^7.22.13" chalk "^2.4.2" -"@babel/compat-data@^7.22.6", "@babel/compat-data@^7.22.9": - version "7.22.9" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.9.tgz#71cdb00a1ce3a329ce4cbec3a44f9fef35669730" - integrity sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ== +"@babel/compat-data@^7.22.20", "@babel/compat-data@^7.22.6", "@babel/compat-data@^7.22.9": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.20.tgz#8df6e96661209623f1975d66c35ffca66f3306d0" + integrity sha512-BQYjKbpXjoXwFW5jGqiizJQQT/aC7pFm9Ok1OWssonuguICi264lbgMzRp2ZMmRSlfkX6DsWDDcsrctK8Rwfiw== "@babel/core@^7.11.1": - version "7.22.17" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.17.tgz#2f9b0b395985967203514b24ee50f9fd0639c866" - integrity sha512-2EENLmhpwplDux5PSsZnSbnSkB3tZ6QTksgO25xwEL7pIDcNOMhF5v/s6RzwjMZzZzw9Ofc30gHv5ChCC8pifQ== + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.20.tgz#e3d0eed84c049e2a2ae0a64d27b6a37edec385b7" + integrity sha512-Y6jd1ahLubuYweD/zJH+vvOY141v4f9igNQAQ+MBgq9JlHS2iTsZKn1aMsb3vGccZsXI16VzTBw52Xx0DWmtnA== dependencies: "@ampproject/remapping" "^2.2.0" "@babel/code-frame" "^7.22.13" "@babel/generator" "^7.22.15" "@babel/helper-compilation-targets" "^7.22.15" - "@babel/helper-module-transforms" "^7.22.17" + "@babel/helper-module-transforms" "^7.22.20" "@babel/helpers" "^7.22.15" "@babel/parser" "^7.22.16" "@babel/template" "^7.22.15" - "@babel/traverse" "^7.22.17" - "@babel/types" "^7.22.17" + "@babel/traverse" "^7.22.20" + "@babel/types" "^7.22.19" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.2" @@ -140,10 +140,10 @@ lodash.debounce "^4.0.8" resolve "^1.14.2" -"@babel/helper-environment-visitor@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz#f06dd41b7c1f44e1f8da6c4055b41ab3a09a7e98" - integrity sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q== +"@babel/helper-environment-visitor@^7.22.20", "@babel/helper-environment-visitor@^7.22.5": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" + integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== "@babel/helper-function-name@^7.22.5": version "7.22.5" @@ -160,7 +160,7 @@ dependencies: "@babel/types" "^7.22.5" -"@babel/helper-member-expression-to-functions@^7.22.15", "@babel/helper-member-expression-to-functions@^7.22.5": +"@babel/helper-member-expression-to-functions@^7.22.15": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.15.tgz#b95a144896f6d491ca7863576f820f3628818621" integrity sha512-qLNsZbgrNh0fDQBCPocSL8guki1hcPvltGDv/NxvUoABwFq7GkKSu1nRXeJkVZc+wJvne2E0RKQz+2SQrz6eAA== @@ -174,16 +174,16 @@ dependencies: "@babel/types" "^7.22.15" -"@babel/helper-module-transforms@^7.22.15", "@babel/helper-module-transforms@^7.22.17", "@babel/helper-module-transforms@^7.22.5", "@babel/helper-module-transforms@^7.22.9": - version "7.22.17" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.22.17.tgz#7edf129097a51ccc12443adbc6320e90eab76693" - integrity sha512-XouDDhQESrLHTpnBtCKExJdyY4gJCdrvH2Pyv8r8kovX2U8G0dRUOT45T9XlbLtuu9CLXP15eusnkprhoPV5iQ== +"@babel/helper-module-transforms@^7.22.15", "@babel/helper-module-transforms@^7.22.20", "@babel/helper-module-transforms@^7.22.5", "@babel/helper-module-transforms@^7.22.9": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.22.20.tgz#da9edc14794babbe7386df438f3768067132f59e" + integrity sha512-dLT7JVWIUUxKOs1UnJUBR3S70YK+pKX6AbJgB2vMIvEkZkrfJDbYDJesnPshtKV4LhDOR3Oc5YULeDizRek+5A== dependencies: - "@babel/helper-environment-visitor" "^7.22.5" + "@babel/helper-environment-visitor" "^7.22.20" "@babel/helper-module-imports" "^7.22.15" "@babel/helper-simple-access" "^7.22.5" "@babel/helper-split-export-declaration" "^7.22.6" - "@babel/helper-validator-identifier" "^7.22.15" + "@babel/helper-validator-identifier" "^7.22.20" "@babel/helper-optimise-call-expression@^7.22.5": version "7.22.5" @@ -198,21 +198,21 @@ integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg== "@babel/helper-remap-async-to-generator@^7.22.5", "@babel/helper-remap-async-to-generator@^7.22.9": - version "7.22.17" - resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.17.tgz#dabaa50622b3b4670bd6546fc8db23eb12d89da0" - integrity sha512-bxH77R5gjH3Nkde6/LuncQoLaP16THYPscurp1S8z7S9ZgezCyV3G8Hc+TZiCmY8pz4fp8CvKSgtJMW0FkLAxA== + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz#7b68e1cb4fa964d2996fd063723fb48eca8498e0" + integrity sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw== dependencies: "@babel/helper-annotate-as-pure" "^7.22.5" - "@babel/helper-environment-visitor" "^7.22.5" - "@babel/helper-wrap-function" "^7.22.17" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-wrap-function" "^7.22.20" "@babel/helper-replace-supers@^7.22.5", "@babel/helper-replace-supers@^7.22.9": - version "7.22.9" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.22.9.tgz#cbdc27d6d8d18cd22c81ae4293765a5d9afd0779" - integrity sha512-LJIKvvpgPOPUThdYqcX6IXRuIcTkcAub0IaDRGCZH0p5GPUp7PhRU9QVgFcDDd51BaPkk77ZjqFwh6DZTAEmGg== + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz#e37d367123ca98fe455a9887734ed2e16eb7a793" + integrity sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw== dependencies: - "@babel/helper-environment-visitor" "^7.22.5" - "@babel/helper-member-expression-to-functions" "^7.22.5" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-member-expression-to-functions" "^7.22.15" "@babel/helper-optimise-call-expression" "^7.22.5" "@babel/helper-simple-access@^7.22.5": @@ -241,24 +241,24 @@ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f" integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw== -"@babel/helper-validator-identifier@^7.22.15", "@babel/helper-validator-identifier@^7.22.5": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.15.tgz#601fa28e4cc06786c18912dca138cec73b882044" - integrity sha512-4E/F9IIEi8WR94324mbDUMo074YTheJmd7eZF5vITTeYchqAi6sYXRLHUVsmkdmY4QjfKTcB2jB7dVP3NaBElQ== +"@babel/helper-validator-identifier@^7.22.19", "@babel/helper-validator-identifier@^7.22.20", "@babel/helper-validator-identifier@^7.22.5": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" + integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== "@babel/helper-validator-option@^7.22.15": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz#694c30dfa1d09a6534cdfcafbe56789d36aba040" integrity sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA== -"@babel/helper-wrap-function@^7.22.17": - version "7.22.17" - resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.22.17.tgz#222ac3ff9cc8f9b617cc1e5db75c0b538e722801" - integrity sha512-nAhoheCMlrqU41tAojw9GpVEKDlTS8r3lzFmF0lP52LwblCPbuFSO7nGIZoIcoU5NIm1ABrna0cJExE4Ay6l2Q== +"@babel/helper-wrap-function@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.22.20.tgz#15352b0b9bfb10fc9c76f79f6342c00e3411a569" + integrity sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw== dependencies: "@babel/helper-function-name" "^7.22.5" "@babel/template" "^7.22.15" - "@babel/types" "^7.22.17" + "@babel/types" "^7.22.19" "@babel/helpers@^7.22.15": version "7.22.15" @@ -270,11 +270,11 @@ "@babel/types" "^7.22.15" "@babel/highlight@^7.10.4", "@babel/highlight@^7.22.13": - version "7.22.13" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.13.tgz#9cda839e5d3be9ca9e8c26b6dd69e7548f0cbf16" - integrity sha512-C/BaXcnnvBCmHTpz/VGZ8jgtE2aYlW4hxDhseJAWZb7gqGM/qtCK6iZUb0TyKFf7BOUsBH7Q7fkRsDRhg1XklQ== + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.20.tgz#4ca92b71d80554b01427815e06f2df965b9c1f54" + integrity sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg== dependencies: - "@babel/helper-validator-identifier" "^7.22.5" + "@babel/helper-validator-identifier" "^7.22.20" chalk "^2.4.2" js-tokens "^4.0.0" @@ -820,11 +820,11 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/preset-env@^7.11.0": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.22.15.tgz#142716f8e00bc030dae5b2ac6a46fbd8b3e18ff8" - integrity sha512-tZFHr54GBkHk6hQuVA8w4Fmq+MSPsfvMG0vPnOYyTnJpyfMqybL8/MbNCPRT9zc2KBO2pe4tq15g6Uno4Jpoag== + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.22.20.tgz#de9e9b57e1127ce0a2f580831717f7fb677ceedb" + integrity sha512-11MY04gGC4kSzlPHRfvVkNAZhUxOvm7DCJ37hPDnUENwe06npjIRAfInEMTGSb4LZK5ZgDFkv5hw0lGebHeTyg== dependencies: - "@babel/compat-data" "^7.22.9" + "@babel/compat-data" "^7.22.20" "@babel/helper-compilation-targets" "^7.22.15" "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-validator-option" "^7.22.15" @@ -898,7 +898,7 @@ "@babel/plugin-transform-unicode-regex" "^7.22.5" "@babel/plugin-transform-unicode-sets-regex" "^7.22.5" "@babel/preset-modules" "0.1.6-no-external-plugins" - "@babel/types" "^7.22.15" + "@babel/types" "^7.22.19" babel-plugin-polyfill-corejs2 "^0.4.5" babel-plugin-polyfill-corejs3 "^0.8.3" babel-plugin-polyfill-regenerator "^0.5.2" @@ -935,29 +935,29 @@ "@babel/parser" "^7.22.15" "@babel/types" "^7.22.15" -"@babel/traverse@^7.22.15", "@babel/traverse@^7.22.17": - version "7.22.17" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.17.tgz#b23c203ab3707e3be816043081b4a994fcacec44" - integrity sha512-xK4Uwm0JnAMvxYZxOVecss85WxTEIbTa7bnGyf/+EgCL5Zt3U7htUpEOWv9detPlamGKuRzCqw74xVglDWpPdg== +"@babel/traverse@^7.22.15", "@babel/traverse@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.20.tgz#db572d9cb5c79e02d83e5618b82f6991c07584c9" + integrity sha512-eU260mPZbU7mZ0N+X10pxXhQFMGTeLb9eFS0mxehS8HZp9o1uSnFeWQuG1UPrlxgA7QoUzFhOnilHDp0AXCyHw== dependencies: "@babel/code-frame" "^7.22.13" "@babel/generator" "^7.22.15" - "@babel/helper-environment-visitor" "^7.22.5" + "@babel/helper-environment-visitor" "^7.22.20" "@babel/helper-function-name" "^7.22.5" "@babel/helper-hoist-variables" "^7.22.5" "@babel/helper-split-export-declaration" "^7.22.6" "@babel/parser" "^7.22.16" - "@babel/types" "^7.22.17" + "@babel/types" "^7.22.19" debug "^4.1.0" globals "^11.1.0" -"@babel/types@^7.22.15", "@babel/types@^7.22.17", "@babel/types@^7.22.5", "@babel/types@^7.4.4": - version "7.22.17" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.17.tgz#f753352c4610ffddf9c8bc6823f9ff03e2303eee" - integrity sha512-YSQPHLFtQNE5xN9tHuZnzu8vPr61wVTBZdfv1meex1NBosa4iT05k/Jw06ddJugi4bk7The/oSwQGFcksmEJQg== +"@babel/types@^7.22.15", "@babel/types@^7.22.19", "@babel/types@^7.22.5", "@babel/types@^7.4.4": + version "7.22.19" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.19.tgz#7425343253556916e440e662bb221a93ddb75684" + integrity sha512-P7LAw/LbojPzkgp5oznjE6tQEIWbp4PkkfrZDINTro9zgBRtI324/EYsiSI7lhPbpIQ+DCeR2NNmMWANGGfZsg== dependencies: "@babel/helper-string-parser" "^7.22.5" - "@babel/helper-validator-identifier" "^7.22.15" + "@babel/helper-validator-identifier" "^7.22.19" to-fast-properties "^2.0.0" "@blueprintjs/colors@^4.2.1": @@ -967,6 +967,13 @@ dependencies: tslib "~2.5.0" +"@blueprintjs/colors@^5.0.2": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@blueprintjs/colors/-/colors-5.0.2.tgz#c1308fbf156b6ebc3e22e88eaad47dc274c2a4b4" + integrity sha512-icP/d5sheRT8ReRy6jf6WunvLmDQWXFjFU97/xKsqF5SMOWIYC92I0b/705dmc+z5lAXntkU67pCMRuNWSZ9lQ== + dependencies: + tslib "~2.5.0" + "@blueprintjs/core@^4.16.3", "@blueprintjs/core@^4.20.2": version "4.20.2" resolved "https://registry.yarnpkg.com/@blueprintjs/core/-/core-4.20.2.tgz#ae1bbaf13bd1bf887b506760c478cc940f6d6e20" @@ -984,6 +991,20 @@ react-transition-group "^4.4.5" tslib "~2.5.0" +"@blueprintjs/core@^5.3.0": + version "5.3.0" + resolved "https://registry.yarnpkg.com/@blueprintjs/core/-/core-5.3.0.tgz#5e4d00797c684f6e417e3d1707ac2141f67f70a0" + integrity sha512-Pzd/ptszeX/Vt5rMa7AFSRlxw8sMs2xhCs4Xje2tTsQUaElbmH1oJbzDyAjSs50na6ncKEmPIvkhXz5ggB1rrA== + dependencies: + "@blueprintjs/colors" "^5.0.2" + "@blueprintjs/icons" "^5.1.6" + "@popperjs/core" "^2.11.7" + classnames "^2.3.1" + normalize.css "^8.0.1" + react-popper "^2.3.0" + react-transition-group "^4.4.5" + tslib "~2.5.0" + "@blueprintjs/icons@^4.16.0": version "4.16.0" resolved "https://registry.yarnpkg.com/@blueprintjs/icons/-/icons-4.16.0.tgz#47f9e8abe64d84fc18721080b8f191d8aac075d8" @@ -993,6 +1014,15 @@ classnames "^2.3.1" tslib "~2.5.0" +"@blueprintjs/icons@^5.1.6": + version "5.1.6" + resolved "https://registry.yarnpkg.com/@blueprintjs/icons/-/icons-5.1.6.tgz#3882cd3a01a1f83dbe584851955a7459d0a78704" + integrity sha512-W87oUP082sZ+dh5oxbgARCHGDyELVKXC+ffxkMYeK3M3hDn0UBSCUZgWZMLltf0qGfFRXlkY+Vn3G08at6xXSw== + dependencies: + change-case "^4.1.2" + classnames "^2.3.1" + tslib "~2.5.0" + "@blueprintjs/popover2@^1.13.3": version "1.14.11" resolved "https://registry.yarnpkg.com/@blueprintjs/popover2/-/popover2-1.14.11.tgz#0698fdeaf6710460cef0b71bed592ca37f40d1f9" @@ -1006,6 +1036,15 @@ react-popper "^2.3.0" tslib "~2.5.0" +"@blueprintjs/popover2@^2.0.10": + version "2.0.10" + resolved "https://registry.yarnpkg.com/@blueprintjs/popover2/-/popover2-2.0.10.tgz#916718688a7d2a9dfc6a5af0a806a8ecef8273f6" + integrity sha512-NE6lgzu6MXfI4lruTw0sXkQ0i7H4RyEWJ0nCEAW1FGdy2KJOkJ7U5RNtBKjT/F4ChaKmoHVL94CaCwqQtB7yOQ== + dependencies: + "@blueprintjs/core" "^5.3.0" + classnames "^2.3.1" + tslib "~2.5.0" + "@cfcs/core@^0.0.6": version "0.0.6" resolved "https://registry.yarnpkg.com/@cfcs/core/-/core-0.0.6.tgz#9f8499dcd2ad29fd96d8fa72055411cd4a249121" @@ -1147,6 +1186,116 @@ resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz#d0fce5d07b0620caa282b5131c297bb60f9d87e6" integrity sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww== +"@esbuild/android-arm64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz#984b4f9c8d0377443cc2dfcef266d02244593622" + integrity sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ== + +"@esbuild/android-arm@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.18.20.tgz#fedb265bc3a589c84cc11f810804f234947c3682" + integrity sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw== + +"@esbuild/android-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.18.20.tgz#35cf419c4cfc8babe8893d296cd990e9e9f756f2" + integrity sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg== + +"@esbuild/darwin-arm64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz#08172cbeccf95fbc383399a7f39cfbddaeb0d7c1" + integrity sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA== + +"@esbuild/darwin-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz#d70d5790d8bf475556b67d0f8b7c5bdff053d85d" + integrity sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ== + +"@esbuild/freebsd-arm64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz#98755cd12707f93f210e2494d6a4b51b96977f54" + integrity sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw== + +"@esbuild/freebsd-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz#c1eb2bff03915f87c29cece4c1a7fa1f423b066e" + integrity sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ== + +"@esbuild/linux-arm64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz#bad4238bd8f4fc25b5a021280c770ab5fc3a02a0" + integrity sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA== + +"@esbuild/linux-arm@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz#3e617c61f33508a27150ee417543c8ab5acc73b0" + integrity sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg== + +"@esbuild/linux-ia32@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz#699391cccba9aee6019b7f9892eb99219f1570a7" + integrity sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA== + +"@esbuild/linux-loong64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz#e6fccb7aac178dd2ffb9860465ac89d7f23b977d" + integrity sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg== + +"@esbuild/linux-mips64el@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz#eeff3a937de9c2310de30622a957ad1bd9183231" + integrity sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ== + +"@esbuild/linux-ppc64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz#2f7156bde20b01527993e6881435ad79ba9599fb" + integrity sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA== + +"@esbuild/linux-riscv64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz#6628389f210123d8b4743045af8caa7d4ddfc7a6" + integrity sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A== + +"@esbuild/linux-s390x@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz#255e81fb289b101026131858ab99fba63dcf0071" + integrity sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ== + +"@esbuild/linux-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz#c7690b3417af318a9b6f96df3031a8865176d338" + integrity sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w== + +"@esbuild/netbsd-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz#30e8cd8a3dded63975e2df2438ca109601ebe0d1" + integrity sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A== + +"@esbuild/openbsd-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz#7812af31b205055874c8082ea9cf9ab0da6217ae" + integrity sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg== + +"@esbuild/sunos-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz#d5c275c3b4e73c9b0ecd38d1ca62c020f887ab9d" + integrity sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ== + +"@esbuild/win32-arm64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz#73bc7f5a9f8a77805f357fab97f290d0e4820ac9" + integrity sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg== + +"@esbuild/win32-ia32@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz#ec93cbf0ef1085cc12e71e0d661d20569ff42102" + integrity sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g== + +"@esbuild/win32-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz#786c5f41f043b07afb1af37683d7c33668858f6d" + integrity sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ== + "@eslint-community/eslint-utils@^4.2.0": version "4.4.0" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" @@ -1189,7 +1338,7 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/eslintrc@^2.1.2": +"@eslint/eslintrc@^2.0.1", "@eslint/eslintrc@^2.1.2": version "2.1.2" resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.2.tgz#c6936b4b328c64496692f76944e755738be62396" integrity sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g== @@ -1204,25 +1353,30 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" +"@eslint/js@8.36.0": + version "8.36.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.36.0.tgz#9837f768c03a1e4a30bd304a64fb8844f0e72efe" + integrity sha512-lxJ9R5ygVm8ZWgYdUweoq5ownDlJ4upvoWmO4eLxBYHdMo+vZ/Rx0EN6MbKWDJOSUGrqJy2Gt+Dyv/VKml0fjg== + "@eslint/js@8.49.0": version "8.49.0" resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.49.0.tgz#86f79756004a97fa4df866835093f1df3d03c333" integrity sha512-1S8uAY/MTJqVx0SC4epBq+N2yhuwtNwLbJYNZyhL2pO1ZVKn5HFXav5T41Ryzy9K9V7ZId2JB2oy/W4aCd9/2w== -"@floating-ui/core@^1.4.1": - version "1.4.1" - resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.4.1.tgz#0d633f4b76052668afb932492ac452f7ebe97f17" - integrity sha512-jk3WqquEJRlcyu7997NtR5PibI+y5bi+LS3hPmguVClypenMsCY3CBa3LAQnozRCtCrYWSEtAdiskpamuJRFOQ== +"@floating-ui/core@^1.4.2": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.5.0.tgz#5c05c60d5ae2d05101c3021c1a2a350ddc027f8c" + integrity sha512-kK1h4m36DQ0UHGj5Ah4db7R0rHemTqqO0QLvUqi1/mUUp3LuAWbWxdxSIf/XsnH9VS6rRVPLJCncjRzUvyCLXg== dependencies: - "@floating-ui/utils" "^0.1.1" + "@floating-ui/utils" "^0.1.3" "@floating-ui/dom@^1.5.1": - version "1.5.2" - resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.5.2.tgz#6812e89d1d4d4ea32f10d15c3b81feb7f9836d89" - integrity sha512-6ArmenS6qJEWmwzczWyhvrXRdI/rI78poBcW0h/456+onlabit+2G+QxHx5xTOX60NBJQXjsCLFbW2CmsXpUog== + version "1.5.3" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.5.3.tgz#54e50efcb432c06c23cd33de2b575102005436fa" + integrity sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA== dependencies: - "@floating-ui/core" "^1.4.1" - "@floating-ui/utils" "^0.1.1" + "@floating-ui/core" "^1.4.2" + "@floating-ui/utils" "^0.1.3" "@floating-ui/react-dom@^2.0.2": version "2.0.2" @@ -1231,10 +1385,10 @@ dependencies: "@floating-ui/dom" "^1.5.1" -"@floating-ui/utils@^0.1.1": - version "0.1.2" - resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.1.2.tgz#b7e9309ccce5a0a40ac482cb894f120dba2b357f" - integrity sha512-ou3elfqG/hZsbmF4bxeJhPHIf3G2pm0ujc39hYEZrfVqt7Vk/Zji6CXc3W0pmYM8BW1g40U+akTl9DKZhFhInQ== +"@floating-ui/utils@^0.1.3": + version "0.1.4" + resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.1.4.tgz#19654d1026cc410975d46445180e70a5089b3e7d" + integrity sha512-qprfWkn82Iw821mcKofJ5Pk9wgioHicxcQMxx+5zt5GSKoqdWvgG5AxVmpmUUjzTLPVSH5auBrhI93Deayn/DA== "@headlessui/react@^1.7.13", "@headlessui/react@^1.7.3": version "1.7.17" @@ -1347,23 +1501,23 @@ resolved "https://registry.yarnpkg.com/@juggle/resize-observer/-/resize-observer-3.4.0.tgz#08d6c5e20cf7e4cc02fd181c4b0c225cd31dbb60" integrity sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA== -"@mui/base@5.0.0-beta.15": - version "5.0.0-beta.15" - resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-beta.15.tgz#76bebd377cc3b7fdc80924759a4100e5319ed0f9" - integrity sha512-Xtom3YSdi0iwYPtyVRFUEGoRwi6IHWixPwifDKaK+4PkEPtUWMU5YOIJfTsmC59ri+dFvA3oBNSiTPUGGrklZw== +"@mui/base@5.0.0-beta.16": + version "5.0.0-beta.16" + resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-beta.16.tgz#5869b8cc83ea5da0083bb11790bda007c2384564" + integrity sha512-OYxhC81c9bO0wobGcM8rrY5bRwpCXAI21BL0P2wz/2vTv4ek7ALz9+U5M8wgdmtRNUhmCmAB4L2WRwFRf5Cd8Q== dependencies: "@babel/runtime" "^7.22.15" "@floating-ui/react-dom" "^2.0.2" "@mui/types" "^7.2.4" - "@mui/utils" "^5.14.9" + "@mui/utils" "^5.14.10" "@popperjs/core" "^2.11.8" clsx "^2.0.0" prop-types "^15.8.1" -"@mui/core-downloads-tracker@^5.14.9": - version "5.14.9" - resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.14.9.tgz#97a4e2decce1583983b4a0cded8bcb2be1b1cb31" - integrity sha512-JAU/R5hM3l2zP1Q4KnioDRhq5V3vZ4mmjEZ+TwARDb2xFhg3p59McacQuzkSu0sUHJnH9aJos36+hU5sPQBcFQ== +"@mui/core-downloads-tracker@^5.14.10": + version "5.14.10" + resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.14.10.tgz#32a8581be98344bbda5ed31fc7b41788bd2e3bc5" + integrity sha512-kPHu/NhZq1k+vSZR5wq3AyUfD4bnfWAeuKpps0+8PS7ZHQ2Lyv1cXJh+PlFdCIOa0PK98rk3JPwMzS8BMhdHwQ== "@mui/icons-material@^5.14.1": version "5.14.9" @@ -1373,16 +1527,16 @@ "@babel/runtime" "^7.22.15" "@mui/material@^5.14.1": - version "5.14.9" - resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.14.9.tgz#d536505a3728441cfe8003443f143ae87457767b" - integrity sha512-pbBy5kc5iUGXPxgbb+t+yEPvLK5nE3bPUb8WbAafJ8iZ40ZGui0xC4xiiIyzbVexzsLmyN7MaSo4LkxLmPKqUQ== + version "5.14.10" + resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.14.10.tgz#b8c6ba17c25c0df54053cb0f1bb35083bc91dace" + integrity sha512-ejFMppnO+lzBXpzju+N4SSz0Mhmi5sihXUGcr5FxpgB6bfUP0Lpe32O0Sw/3s8xlmLEvG1fqVT0rRyAVMlCA+A== dependencies: "@babel/runtime" "^7.22.15" - "@mui/base" "5.0.0-beta.15" - "@mui/core-downloads-tracker" "^5.14.9" - "@mui/system" "^5.14.9" + "@mui/base" "5.0.0-beta.16" + "@mui/core-downloads-tracker" "^5.14.10" + "@mui/system" "^5.14.10" "@mui/types" "^7.2.4" - "@mui/utils" "^5.14.9" + "@mui/utils" "^5.14.10" "@types/react-transition-group" "^4.4.6" clsx "^2.0.0" csstype "^3.1.2" @@ -1390,36 +1544,35 @@ react-is "^18.2.0" react-transition-group "^4.4.5" -"@mui/private-theming@^5.14.9": - version "5.14.9" - resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-5.14.9.tgz#085041c44cc28c25f3431a293339922ec3d9b5f8" - integrity sha512-0PzoUFqFXTXiNchhR7K4b7kZunasPOjx6Qf7AagCmfZDNASHedA0x6evHVhnST918x/AHY9xykYNKfB0Z4xMBg== +"@mui/private-theming@^5.14.10": + version "5.14.10" + resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-5.14.10.tgz#42b176b27435931aff40d50833413d10150ac007" + integrity sha512-f67xOj3H06wWDT9xBg7hVL/HSKNF+HG1Kx0Pm23skkbEqD2Ef2Lif64e5nPdmWVv+7cISCYtSuE2aeuzrZe78w== dependencies: "@babel/runtime" "^7.22.15" - "@mui/utils" "^5.14.9" + "@mui/utils" "^5.14.10" prop-types "^15.8.1" -"@mui/styled-engine@^5.14.9": - version "5.14.9" - resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-5.14.9.tgz#bc2121db1399bb84ea5390b40beac742b6be7023" - integrity sha512-LEQxLrW9oWvea33pge08+oyNeTz704jb6Nhe26xEJKojXWd34Rr327Zzx3dmo70AcS4h0b99vQjEpUzm6ASqUw== +"@mui/styled-engine@^5.14.10": + version "5.14.10" + resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-5.14.10.tgz#2ec443031e48425cd6fda63be498cfa262c1d3a0" + integrity sha512-EJckxmQHrsBvDbFu1trJkvjNw/1R7jfNarnqPSnL+jEQawCkQIqVELWLrlOa611TFtxSJGkdUfCFXeJC203HVg== dependencies: "@babel/runtime" "^7.22.15" "@emotion/cache" "^11.11.0" csstype "^3.1.2" prop-types "^15.8.1" - react "^18.2.0" -"@mui/system@^5.14.9": - version "5.14.9" - resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.14.9.tgz#90a20473a85622ddabf5a2409de1980fad98f38d" - integrity sha512-Z00Wj590QXk5+SIxmxayBo7SWrao+y433LKGChneJxO4QcT/caSCeEWtyeoLs1Q8ys0zOzl2kkKee6n8TaKzhQ== +"@mui/system@^5.14.10": + version "5.14.10" + resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.14.10.tgz#b125f8370c1c92af04f1839c40e034d4edc4ad29" + integrity sha512-QQmtTG/R4gjmLiL5ECQ7kRxLKDm8aKKD7seGZfbINtRVJDyFhKChA1a+K2bfqIAaBo1EMDv+6FWNT1Q5cRKjFA== dependencies: "@babel/runtime" "^7.22.15" - "@mui/private-theming" "^5.14.9" - "@mui/styled-engine" "^5.14.9" + "@mui/private-theming" "^5.14.10" + "@mui/styled-engine" "^5.14.10" "@mui/types" "^7.2.4" - "@mui/utils" "^5.14.9" + "@mui/utils" "^5.14.10" clsx "^2.0.0" csstype "^3.1.2" prop-types "^15.8.1" @@ -1429,12 +1582,13 @@ resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.2.4.tgz#b6fade19323b754c5c6de679a38f068fd50b9328" integrity sha512-LBcwa8rN84bKF+f5sDyku42w1NTxaPgPyYKODsh01U1fVstTClbUoSA96oyRBnSNyEiAVjKm6Gwx9vjR+xyqHA== -"@mui/utils@^5.14.9": - version "5.14.9" - resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.14.9.tgz#eeefef88dbee687ac90e8972c63f0d41f19348a3" - integrity sha512-9ysB5e+RwS7ofn0n3nwAg1/3c81vBTmSvauD3EuK9LmqMzhmF//BFDaC44U4yITvB/0m1kWyDqg924Ll3VHCcg== +"@mui/utils@^5.14.10": + version "5.14.10" + resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.14.10.tgz#4b0a2a26f1ee12323010daa9d7aecf3384acfc3c" + integrity sha512-Rn+vYQX7FxkcW0riDX/clNUwKuOJFH45HiULxwmpgnzQoQr3A0lb+QYwaZ+FAkZrR7qLoHKmLQlcItu6LT0y/Q== dependencies: "@babel/runtime" "^7.22.15" + "@types/prop-types" "^15.7.5" prop-types "^15.8.1" react-is "^18.2.0" @@ -1464,6 +1618,13 @@ dependencies: glob "7.1.7" +"@next/eslint-plugin-next@13.2.4": + version "13.2.4" + resolved "https://registry.yarnpkg.com/@next/eslint-plugin-next/-/eslint-plugin-next-13.2.4.tgz#3e124cd10ce24dab5d3448ce04104b4f1f4c6ca7" + integrity sha512-ck1lI+7r1mMJpqLNa3LJ5pxCfOB1lfJncKmRJeJxcJqcngaFwylreLP7da6Rrjr6u2gVRTfmnkSkjc80IiQCwQ== + dependencies: + glob "7.1.7" + "@next/swc-android-arm-eabi@12.3.2": version "12.3.2" resolved "https://registry.yarnpkg.com/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-12.3.2.tgz#806e3be9741bc14aafdfad0f0c4c6a8de5b77ee1" @@ -1995,9 +2156,9 @@ picomatch "^2.3.1" "@rushstack/eslint-patch@^1.1.3": - version "1.3.3" - resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.3.3.tgz#16ab6c727d8c2020a5b6e4a176a243ecd88d8d69" - integrity sha512-0xd7qez0AQ+MbHatZTlI1gu5vkG8r7MYRUJAHPAHJBmGLs16zpkrpAVLvjQKQOqaXPDUBwOiJzNc00znHSCVBw== + version "1.4.0" + resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.4.0.tgz#77e948b9760bd22736a5d26e335a690f76fda37b" + integrity sha512-cEjvTPU32OM9lUFegJagO0mRnIn+rbqrG89vV8/xLnLFX0DoR0r1oy5IlTga71Q7uT3Qus7qm7wgeiMT/+Irlg== "@scena/dragscroll@^1.4.0": version "1.4.0" @@ -2164,7 +2325,7 @@ dependencies: tslib "^2.4.0" -"@tailwindcss/typography@^0.5.10": +"@tailwindcss/typography@^0.5.9": version "0.5.10" resolved "https://registry.yarnpkg.com/@tailwindcss/typography/-/typography-0.5.10.tgz#2abde4c6d5c797ab49cf47610830a301de4c1e0a" integrity sha512-Pe8BuPJQJd3FfRnm6H0ulKIGoMEQS+Vq01R6M5aCrFB/ccR/shT+0kXLjouGC1gFLm9hopTFN+DMP0pfwRWzPw== @@ -2174,191 +2335,176 @@ lodash.merge "^4.6.2" postcss-selector-parser "6.0.10" -"@tiptap/core@^2.1.8": - version "2.1.8" - resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-2.1.8.tgz#4555dc7d86580dee790d4aded1ce7fb79319da70" - integrity sha512-QTGgqki7hkonLJ93gWqCUkD6cCAQ3rEX9gbMLwzfnegIZ+/BKLQYKYCozsEMZnMPXgdRrKuyRBOL+RH+IolMeA== +"@tiptap/core@^2.1.10", "@tiptap/core@^2.1.7": + version "2.1.10" + resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-2.1.10.tgz#6d8f3c777f1700dcc6c903b1185576754175e366" + integrity sha512-yhUKsac6nlqbPQfwQnp+4Jb110EqmzocXKoZacLwzHpM7JVsr2+LXMDu9kahtrvHNJErJljhnQvDHRsrrYeJkQ== -"@tiptap/extension-blockquote@^2.1.8": - version "2.1.8" - resolved "https://registry.yarnpkg.com/@tiptap/extension-blockquote/-/extension-blockquote-2.1.8.tgz#eb3f70d03807b2d51645cf5450a1e84ccb53633b" - integrity sha512-NhTE90ZDb/BbtkgeNjwLYPYMryAfCXCM+Zpk8AMsVODZ+bDy+lsqpnDw7uRxUK3guLMnqKgSe2eTaXqx7AKE+A== +"@tiptap/extension-blockquote@^2.1.10": + version "2.1.10" + resolved "https://registry.yarnpkg.com/@tiptap/extension-blockquote/-/extension-blockquote-2.1.10.tgz#dc475bef70dd460fc730a14b3b4cc18f37cd1b2d" + integrity sha512-lpBF/a+qgv4Bdf7HYisTkMFdFdGfn2SqspsydvG8UI7N9B/PfnCCrtoMaC3bqTaT6u8ZVxyM3Y3vnq2AxXJvBw== -"@tiptap/extension-bold@^2.1.8": - version "2.1.8" - resolved "https://registry.yarnpkg.com/@tiptap/extension-bold/-/extension-bold-2.1.8.tgz#2047345c814cad672d150b303436928d46aecbc1" - integrity sha512-rDdmir78a0JTiV+vrycGh3yS1ZzRF1bRvBt4jr7Rne0LOl03kc7Wm936ommiL3McWUpZZV37ZpCm5JfE8rQb+w== +"@tiptap/extension-bold@^2.1.10": + version "2.1.10" + resolved "https://registry.yarnpkg.com/@tiptap/extension-bold/-/extension-bold-2.1.10.tgz#fb71c2575087d3d2a9c6d214b3c1587da931cc61" + integrity sha512-I43WCwc7pyz5vtKGj24Rjv7HN0EK5S4PlADQPBuhC1qQvfCTFvjrBB6ZmsekUMGmllW0qMOFVLSjtffpckqshA== -"@tiptap/extension-bubble-menu@^2.1.8": - version "2.1.8" - resolved "https://registry.yarnpkg.com/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.1.8.tgz#26d5f2ebc198553c5339d9ea6d06fcb02b6a938f" - integrity sha512-Na9Maz20jS+3UrHtAGLkfFt3uu+HD9SSK3+3WyNeylkWciJa/qkZKqwhptHrjpin0IHSF2JNche+ZA+hSmnm2Q== +"@tiptap/extension-bubble-menu@^2.1.10": + version "2.1.10" + resolved "https://registry.yarnpkg.com/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.1.10.tgz#551e93219b98f097331b30865123d36e95c37404" + integrity sha512-XxgJajXkfAj/fChXkIwKBs7/3pd7OxV1uGc6Opx1qW/nSRYx/rr97654Sx/sg6auwIlbpRoqTmyqjbykGX1/yA== dependencies: tippy.js "^6.3.7" -"@tiptap/extension-bullet-list@^2.1.8": - version "2.1.8" - resolved "https://registry.yarnpkg.com/@tiptap/extension-bullet-list/-/extension-bullet-list-2.1.8.tgz#d9d4ee46d1adb2b70d3b8c19540ebcdd8a1eaaec" - integrity sha512-VWj3XZMwJQVb7e4ZM0N+o6o+905lyMMS4C35yw/sxN5CDw4TJpQMSPSAmBVNtK469XUdlGOxeLc/+Q00aU+S8A== - -"@tiptap/extension-code-block-lowlight@^2.0.4": - version "2.1.8" - resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block-lowlight/-/extension-code-block-lowlight-2.1.8.tgz#7577ade0e00be3d28629e2f8e6c5e7b9b4155946" - integrity sha512-qiUIh8JRfvd2rhDKFjHCxBp+nRy3HedovQoVFX9YEnBbg6so+I1nLE2Eck4t3KhBVfVRBrxBKZPLVb83zQ0s4w== - -"@tiptap/extension-code-block@^2.1.8": - version "2.1.8" - resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block/-/extension-code-block-2.1.8.tgz#d2dccf64a583bfb12e2ebd04b3724b7e9430549d" - integrity sha512-EjegLBBz8ATvIuJlqosGrcOsKNu8YveI8rogGfUmnXWMNcPSSqBDoWK2EpLTUzGccPWRxo7yBsr5wItikfPPYA== - -"@tiptap/extension-code@^2.1.8": - version "2.1.8" - resolved "https://registry.yarnpkg.com/@tiptap/extension-code/-/extension-code-2.1.8.tgz#c3dccd1a12972cab8d0c98f75a3960bab64905bd" - integrity sha512-dQL8aUYzSEkES5P4sBYZ6SiCMnFK1cUKKGruaRV1TJyFu/ClZ8Y+BKS2GCCMcyH0tKjqsibYsNFBWz9/Q5gjEg== - -"@tiptap/extension-color@^2.0.4": - version "2.1.8" - resolved "https://registry.yarnpkg.com/@tiptap/extension-color/-/extension-color-2.1.8.tgz#7692e82c0a50e63d6bb474a6e109852c5d657ea0" - integrity sha512-eAfJhUbqqNFTdgWraLcg6O1d7YWj6Ivga0YVfhnBpWmZiS8JXAmgOHq9b7sRrDOMdEp6D2njDBEIWpltHNmi1A== - -"@tiptap/extension-document@^2.1.8": - version "2.1.8" - resolved "https://registry.yarnpkg.com/@tiptap/extension-document/-/extension-document-2.1.8.tgz#fa4dce27fd5d25b54f7e7e9a93db69606a624b96" - integrity sha512-mLPZqd5QUv3FKo+5zOaf7dGqZPci7Myr92U1Y6Vw0V+hCRC9Emm3I/xssQYGsWXmXQuyNJ5WRlpXgag3Ae+CkA== - -"@tiptap/extension-dropcursor@^2.1.8": - version "2.1.8" - resolved "https://registry.yarnpkg.com/@tiptap/extension-dropcursor/-/extension-dropcursor-2.1.8.tgz#f7128aebe9e2bb05cd6508d782deaf436ae3c46e" - integrity sha512-KilbUHApYya2Q6brq5qW+B+pPkb6lvgnjRfuFuv6doM/v+lfEdozUE1Ma8C19UXtzl7BmPDut9HRMDL17Pqwyg== - -"@tiptap/extension-floating-menu@^2.1.8": - version "2.1.8" - resolved "https://registry.yarnpkg.com/@tiptap/extension-floating-menu/-/extension-floating-menu-2.1.8.tgz#49a7f83c3a0769c044d4e83352aae0f86d63f7c5" - integrity sha512-lc8bjHGqWSgXKmoU2HAlBFWzu7wnFKb5Vg0R3PECBrOZ9hXkmNA0mHxrvHglwjLtfe7XOfZf4FLySG/5S+BdeQ== +"@tiptap/extension-bullet-list@^2.1.10": + version "2.1.10" + resolved "https://registry.yarnpkg.com/@tiptap/extension-bullet-list/-/extension-bullet-list-2.1.10.tgz#e7d7fb578502da6c6208a4daa3e2fe4249ae6280" + integrity sha512-e6aFr29OSOmXsjFZB2zt3p8aeCWOx0C9Ayrpdf4QBUCOUJtt6FQPxxiYc+XZcdrYbLGLznA7QJlulCK9SGv2Fw== + +"@tiptap/extension-code-block-lowlight@^2.1.11": + version "2.1.11" + resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block-lowlight/-/extension-code-block-lowlight-2.1.11.tgz#6eec38c3b8662fae81ec2f117a2d18564f1fbb1a" + integrity sha512-k3olDvsRYO32JR9hyNa6VLqUdhwcpLwvR4Z6tJ66jHag5rsfP/7JZxJhrX9A1AF/jRCILdTiq9DTKybHieFjsw== + +"@tiptap/extension-code-block@^2.1.10": + version "2.1.10" + resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block/-/extension-code-block-2.1.10.tgz#a125a12f716728b271a130178c6fc60237ed46f5" + integrity sha512-M+s89V9mP3tOoS6p/X2Dzw/Z7Fcg9EF0ZXlsMNifdlpwJlhAIYxI7vjPBmkMAFXTDB5eMZblXyNQaZ7v6V2Yeg== + +"@tiptap/extension-code@^2.1.10": + version "2.1.10" + resolved "https://registry.yarnpkg.com/@tiptap/extension-code/-/extension-code-2.1.10.tgz#704798f90a32d6166ce96dc65ef4a541f424f895" + integrity sha512-1yy/kR0FAeMkDdAt1LW/FH6vlyZLqLZqY6BM+wBCiGrr+XeA5FTXih9iT/4gbTRuIzG0EPqx18nvroG7hUsWBg== + +"@tiptap/extension-color@^2.1.11": + version "2.1.11" + resolved "https://registry.yarnpkg.com/@tiptap/extension-color/-/extension-color-2.1.11.tgz#be6989bb2d6630fc8f8e3c81add6fdfe1b77c790" + integrity sha512-xfSfZRnNd40YtFfrXvzpGa2OZsRAZapq0Ce09q7bCEpudhiD7yIIVOjOjggagllOFnafKTwKkFaDLIA0K0eIwg== + +"@tiptap/extension-document@^2.1.10": + version "2.1.10" + resolved "https://registry.yarnpkg.com/@tiptap/extension-document/-/extension-document-2.1.10.tgz#6d2ab2301c86139d711fa460a311aa2c8bb343f8" + integrity sha512-jNlNGQIGg471DvzhADaEoRINa3LNghowrBbKK9d5wGVnbKRykNEPwjCf8zNl+m5NBmCZl3lsdznlwBk5zyh5Bg== + +"@tiptap/extension-dropcursor@^2.1.10": + version "2.1.10" + resolved "https://registry.yarnpkg.com/@tiptap/extension-dropcursor/-/extension-dropcursor-2.1.10.tgz#490c9aa82656592c9820c55214381fb9bfea92f2" + integrity sha512-GhsWsCq6wLb8HJ32BeAm7ndv4lPyu1F7FFwmnARzEF5q54FV20kWSv2zC+Dv0dTvynXR3quXybdUM92xeNDovw== + +"@tiptap/extension-floating-menu@^2.1.10": + version "2.1.10" + resolved "https://registry.yarnpkg.com/@tiptap/extension-floating-menu/-/extension-floating-menu-2.1.10.tgz#82914a02e04e019d8b5da5158b32ffb29d4cce80" + integrity sha512-uChrDrY3usnF9wSegqq+YGaqd229p9gmaB5xyOyMERDs972hKj4Ul95rXzBBiMKAWUMw9eM09i7+ijTzz4KDUw== dependencies: tippy.js "^6.3.7" -"@tiptap/extension-gapcursor@^2.1.7", "@tiptap/extension-gapcursor@^2.1.8": - version "2.1.8" - resolved "https://registry.yarnpkg.com/@tiptap/extension-gapcursor/-/extension-gapcursor-2.1.8.tgz#bc7e745fdc8990a8e370c4c728ab8733ba0910c4" - integrity sha512-0EQgV/kF2dg2dOpw0fTbwwNaubwS8QNhEPPbnXQP8xqZpupuia+DKKgC+ttzbE9XhS4Sv1fGib52Sr7MMIduhA== - -"@tiptap/extension-hard-break@^2.1.8": - version "2.1.8" - resolved "https://registry.yarnpkg.com/@tiptap/extension-hard-break/-/extension-hard-break-2.1.8.tgz#b898c3c9c7f96726307bd51b24f557731e25d12e" - integrity sha512-K86FTizvZu7779Gz2XigW1IxAjZXduyZ7w0ipwe+5QBa/Lh6Vfl9wa8TgV1lFAkC2VATsAa3aa36llMIDBgeew== - -"@tiptap/extension-heading@^2.1.8": - version "2.1.8" - resolved "https://registry.yarnpkg.com/@tiptap/extension-heading/-/extension-heading-2.1.8.tgz#d34db2bdfca559567ecb6f741fa4eb5d4d54a897" - integrity sha512-6PHWzhGPC/QjfswlflU1Cy2UYZiyzwa639bWW7Dl4BHZgK+e09lbc7RwzPrrex6+jA10K4nlww19xsI590ogBw== - -"@tiptap/extension-highlight@^2.0.4": - version "2.1.8" - resolved "https://registry.yarnpkg.com/@tiptap/extension-highlight/-/extension-highlight-2.1.8.tgz#b2d2d995344e06dd36cd8a395a72113b87981bd7" - integrity sha512-OCXtFWCbwsgOHq7IP4Qr02EfjwYeRRcuL1ipv0LojGtMcvnkw7OLhQZ8oocrqi4/6QCOtPLSGlcqrQ6pmN7jww== - -"@tiptap/extension-history@^2.1.8": - version "2.1.8" - resolved "https://registry.yarnpkg.com/@tiptap/extension-history/-/extension-history-2.1.8.tgz#68a65b51effc1d612e3862928c3ccede3ce83592" - integrity sha512-Cyq4YsmosfgHGlaf2wiiU8VaLweUMG8LHuhZ5A2RAoriy3G09Bqgn6eqLmho8KoU1VgvffXTVBaYKxz9gVgu3w== - -"@tiptap/extension-horizontal-rule@^2.0.4", "@tiptap/extension-horizontal-rule@^2.1.8": - version "2.1.8" - resolved "https://registry.yarnpkg.com/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.1.8.tgz#db23468176fd5359240feb8601fd3fcf747d5e6d" - integrity sha512-qUNz8p/p3gth0ueYFkmMdVRcRVmtCwQGJsHWwbx23XrF/a7AJ0FSdiW0sk8YD6Dbw+i1cB3cnRyO+qq9XuWdqw== - -"@tiptap/extension-image@^2.0.4": - version "2.1.8" - resolved "https://registry.yarnpkg.com/@tiptap/extension-image/-/extension-image-2.1.8.tgz#99d78ad1d8c6f513f945beae7de352759f30189f" - integrity sha512-o+vUIYLvYcJHftIMoIukzZZ+fTTfC/gXXvQIYz51p3f1qeYXszD11FbtkaJCgXYj8BcGCO7QuzcCdQg+wyROZw== - -"@tiptap/extension-italic@^2.1.8": - version "2.1.8" - resolved "https://registry.yarnpkg.com/@tiptap/extension-italic/-/extension-italic-2.1.8.tgz#b559c8d6b387e292e047985acd0def48a80a7aa0" - integrity sha512-cR6kSoMraA/dCdwmus8A09WAwpxiZiGG+B0OqsludGF+MdZLilhoGyXDbTeO3aKoKccfqxZGk1YKK13C/gRM1Q== - -"@tiptap/extension-link@^2.0.4": - version "2.1.8" - resolved "https://registry.yarnpkg.com/@tiptap/extension-link/-/extension-link-2.1.8.tgz#644229c309ef9a91db329126df23cba083ec3c61" - integrity sha512-f3yPNbbo3rNuusEX+Xh/oKUWkq/P1yyVip6ZmtUJVrrG4PFeq/w+f1vEVnlC+uZk3qoC4o8J1DTAOrlrZehx/g== +"@tiptap/extension-gapcursor@^2.1.10": + version "2.1.10" + resolved "https://registry.yarnpkg.com/@tiptap/extension-gapcursor/-/extension-gapcursor-2.1.10.tgz#712853ce82642108e50a37014d585ff72af6758d" + integrity sha512-WSBT9X7dzg0HyMoMP/Yyxl28QwIJO90YzobI9z5mav86BQv7C5wU0fQSpbpAbsN3s7lxKhPwNrXkwkpnXT4ZCA== + +"@tiptap/extension-hard-break@^2.1.10": + version "2.1.10" + resolved "https://registry.yarnpkg.com/@tiptap/extension-hard-break/-/extension-hard-break-2.1.10.tgz#e885e83d936b45891bf4dc40c713d042f84eb8c4" + integrity sha512-sYrzpPoV5jQri+duGb50nDTs+hOBQDxXTKlJuZNFfZMwgx6epwxb8xICcGAUJFShuuW8UAWCNcB4jG9tMqgvyw== + +"@tiptap/extension-heading@^2.1.10": + version "2.1.10" + resolved "https://registry.yarnpkg.com/@tiptap/extension-heading/-/extension-heading-2.1.10.tgz#1b32726551466c29987861181966e5675417b28c" + integrity sha512-1OgmrRPMcY52WI7I4799xd4eIsEX/bI813B8mZvNYXLzZI75pLW1hmz1mUvBYyMwlcek74zVTGYgPy11o+2JEg== + +"@tiptap/extension-history@^2.1.10": + version "2.1.10" + resolved "https://registry.yarnpkg.com/@tiptap/extension-history/-/extension-history-2.1.10.tgz#efa60d657a76818361a3af14769660672d4bc227" + integrity sha512-tApuN8MIJMzc0dxvkYJPt3t5cea9NuZBGNiuVedJwMMUF6hbFpMZAt20GW2qwjBaZ76rQwbLp1s3KnImFsPe5A== + +"@tiptap/extension-horizontal-rule@^2.1.10": + version "2.1.10" + resolved "https://registry.yarnpkg.com/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.1.10.tgz#cfdb67530be100054fc8511942d4ec3534acf828" + integrity sha512-91lGpK2d6WMPhrMDPBURS8z8pEg1CUBYy7GmBenKvvgh+JzVhG+U6MtykfWNfm2R4iRXOl1xLbyUOCiOSUXodQ== + +"@tiptap/extension-horizontal-rule@^2.1.11": + version "2.1.11" + resolved "https://registry.yarnpkg.com/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.1.11.tgz#e423a2b41123ef7f8d778a1cd026e6606e7be28b" + integrity sha512-uvHPa2YCKnDhtSBSZB3lk5U4H3wRKP0DNvVx4Y2F7MdQianVzcyOd1pZYO9BQs+lUB1aZots6doE69Zqz3mU2Q== + +"@tiptap/extension-image@^2.1.7": + version "2.1.10" + resolved "https://registry.yarnpkg.com/@tiptap/extension-image/-/extension-image-2.1.10.tgz#6c597ad02285f1f3508fd4aa21e30213657cbd7c" + integrity sha512-d7+d4J2TJ99+phFbVTpsFhi208jAgcrfbdwUDkkwjdF+PQhax5pounSt/8eZPWdyCXj+EWYjCjx0znwsD6+SCA== + +"@tiptap/extension-italic@^2.1.10": + version "2.1.10" + resolved "https://registry.yarnpkg.com/@tiptap/extension-italic/-/extension-italic-2.1.10.tgz#7183119c8c61beb2ac635ca3c2066624530b4a56" + integrity sha512-ebw5m+rWx6K5UoBVXSkz3fpvDJh/wScfYmwl6pkbjc2jNbZiln2LSiLHYc2eIYJ2aTsVxcw/n0Azfk5Lb19InA== + +"@tiptap/extension-link@^2.1.7": + version "2.1.10" + resolved "https://registry.yarnpkg.com/@tiptap/extension-link/-/extension-link-2.1.10.tgz#c2a33fdf33dd2d97f29381ae2163c10318dc371f" + integrity sha512-dXxPTWzJzpbDRAewM4P8jN/n9h8uUH83lOLwweuODYCqHRdjQL/uGkQworFFrgqmRHs+9JjHZ4DETILZVawJ+Q== dependencies: linkifyjs "^4.1.0" -"@tiptap/extension-list-item@^2.1.8": - version "2.1.8" - resolved "https://registry.yarnpkg.com/@tiptap/extension-list-item/-/extension-list-item-2.1.8.tgz#b5dc1e04bfb96ca10a0821821ade5014fa188dbb" - integrity sha512-fiYVRhHvcXMcVuuiXBx/0AFWwGoKzs9784VSuVUeSSzSuH6vOchM1kZCH+v6acs7vltFKNDrluyEiwGIz1b8qA== - -"@tiptap/extension-ordered-list@^2.1.8": - version "2.1.8" - resolved "https://registry.yarnpkg.com/@tiptap/extension-ordered-list/-/extension-ordered-list-2.1.8.tgz#f489ac85ccd93ad811318bed6af7906c035ba313" - integrity sha512-qTVSWTlSjFNRwPNmWmfe9TsW9XL3LQCNJsfaBxtVZfhDN9rhoIZ6rPTBO7f2TTiPK1+uyLTvK+znWYvU9RtD5A== - -"@tiptap/extension-paragraph@^2.1.8": - version "2.1.8" - resolved "https://registry.yarnpkg.com/@tiptap/extension-paragraph/-/extension-paragraph-2.1.8.tgz#f337c3f84cbfddd1ea16860e934f2049c46211ce" - integrity sha512-ZuwvwKaG5GeoYRgeh96PToLk2TjxsLiZKnLN6rkUCsW6aLoseK7/8/7vm3dP2N9dAUN35ESw0/pRk2Q/VK1/+g== - -"@tiptap/extension-placeholder@^2.0.4": - version "2.1.8" - resolved "https://registry.yarnpkg.com/@tiptap/extension-placeholder/-/extension-placeholder-2.1.8.tgz#5ead71aca842c17cdc9cff6fff8809a1f327119d" - integrity sha512-4yhyvvqsXTzXtJs+39cgvsld4df3ppbajCoxkzHYntKoonm3DtgFTSh+lbdEVCQgDmIfIt1o6DKY1n8NAJRQUQ== - -"@tiptap/extension-strike@^2.1.8": - version "2.1.8" - resolved "https://registry.yarnpkg.com/@tiptap/extension-strike/-/extension-strike-2.1.8.tgz#ba6966a9afb9493d8bd30d4c617ffe6966b90379" - integrity sha512-JGPiGudEZAKTiOirua9gtDG+HILHEx4CGODW5PDBMA1xYDfyo7ZJk5xgfJWZ1SOo7YviF26HSY4KKV9ThINq2Q== - -"@tiptap/extension-table-cell@^2.1.6": - version "2.1.8" - resolved "https://registry.yarnpkg.com/@tiptap/extension-table-cell/-/extension-table-cell-2.1.8.tgz#2e28bb008de979943f5efbf5f48f58d26728e6ad" - integrity sha512-cUI3vMfRZ6Utmjsu+aAF8BsH3r3YzWaLJWW5SuH8784K6ImclCwGTyi/HJqsRDHM7ujvtjjc+vmFtSD/eqF15Q== - -"@tiptap/extension-table-header@^2.1.6": - version "2.1.8" - resolved "https://registry.yarnpkg.com/@tiptap/extension-table-header/-/extension-table-header-2.1.8.tgz#ae4a081f2dc7754078fbc7ba11043da8726d5f4a" - integrity sha512-oUYaQaAowbVLYyeYmAwqoe0ZHZS1XP6qV4EyOig/mOElASwBB6xAfydMncRKTxsT9Zq6z/CC6qnH0xlld/KAfQ== - -"@tiptap/extension-table-row@^2.1.6": - version "2.1.8" - resolved "https://registry.yarnpkg.com/@tiptap/extension-table-row/-/extension-table-row-2.1.8.tgz#d1113e7362f675faaef88cc1fb32c29d37855a09" - integrity sha512-rjMCZjaemC3x4T3fUExi8J8ffo1I29u9e8rSHRIna2ObTRq4PeI48uVTET5EREBD5/CbfX6zHFgkZfUZeR0g+A== - -"@tiptap/extension-table@^2.1.6": - version "2.1.8" - resolved "https://registry.yarnpkg.com/@tiptap/extension-table/-/extension-table-2.1.8.tgz#1c777498caf7b649be26cba8eecbd3ebd623e673" - integrity sha512-n+89XGTYmZgKFrvZrqgCG2SbRbIi8xX61KLptLD2DF/w4y0bR4Cr2pJBep/MMEZh3N2CIDQ3mS7eIfASJHk5hQ== - -"@tiptap/extension-task-item@^2.0.4": - version "2.1.8" - resolved "https://registry.yarnpkg.com/@tiptap/extension-task-item/-/extension-task-item-2.1.8.tgz#2036360be6702ab753cbc77b60ab24fb33ff20a6" - integrity sha512-PoY2PDiYEQC44qDQLubzDuhZ3f6OL7sui89960M1HUQR2URnPvToOBaa5veNY8VyACdAolm+LwTpseBKKkcpmw== - -"@tiptap/extension-task-list@^2.0.4": - version "2.1.8" - resolved "https://registry.yarnpkg.com/@tiptap/extension-task-list/-/extension-task-list-2.1.8.tgz#993c415d85d414039baf7379df7c3b19b1d342d9" - integrity sha512-PmEPJHTOgy0AveE6YoxY6w09+bh5OqkrMI/sluY88291cnSPPEf9sFWmBHOrONNj54Ti6ua37arudUY5mqxOCA== - -"@tiptap/extension-text-style@^2.0.4": - version "2.1.8" - resolved "https://registry.yarnpkg.com/@tiptap/extension-text-style/-/extension-text-style-2.1.8.tgz#42e9fa179f76d4e88f73f2c66aee3b06162e659b" - integrity sha512-xnx/Pq5ttt2/gOQPmqVQIBz/jo3MErtYdYk22fUaOyu1xT36X4BDJYsrLyWhcs3aWR/tv1/XylbNOFvhrDOHoQ== - -"@tiptap/extension-text@^2.1.8": - version "2.1.8" - resolved "https://registry.yarnpkg.com/@tiptap/extension-text/-/extension-text-2.1.8.tgz#7f537d0c490feab8b800644e2ad24b6478c67044" - integrity sha512-ha7oTtUdcJdTVLr8CrxbNMucbAmOBCi83MLxdKZclVf1VpdIVpE3NTojfH2mnZCVMvtPhj4PILQp2hGO95SFig== - -"@tiptap/extension-underline@^2.0.4": - version "2.1.8" - resolved "https://registry.yarnpkg.com/@tiptap/extension-underline/-/extension-underline-2.1.8.tgz#a2f0904805f57e118c2f0b165929abe41b5c1fbf" - integrity sha512-vsmdyR8z40xNPZzTSNGLcCMaIf8Tgm9OzsZb1qWILe+PYuv/mIM1LogBbfouEzVpG5sPoxwFTDgxnC+M3Ohgzg== - -"@tiptap/pm@^2.0.4": - version "2.1.8" - resolved "https://registry.yarnpkg.com/@tiptap/pm/-/pm-2.1.8.tgz#9108af0365fd653d64d620d45016e7079961bebc" - integrity sha512-H3NGAu5xdH1PpXa6OQlvecaWJIZR/9tVkc1mdpLanvG7mW85DuY+5fC36Xnv9SPMVcO3zWXS6Ii4os6HbdP6bQ== +"@tiptap/extension-list-item@^2.1.10": + version "2.1.10" + resolved "https://registry.yarnpkg.com/@tiptap/extension-list-item/-/extension-list-item-2.1.10.tgz#0615e4fb68161e6457e6041e195f454bfd537d44" + integrity sha512-rRRyB14vOcSjTMAh8Y+50TRC/jO469CelGwFjOLrK1ZSEag5wmLDaqpWOOb52BFYnvCHuIm1HqZtdL5bTI/J1w== + +"@tiptap/extension-list-item@^2.1.11": + version "2.1.11" + resolved "https://registry.yarnpkg.com/@tiptap/extension-list-item/-/extension-list-item-2.1.11.tgz#466e4d1dcad153b4e52678b08d2bf073339c2dc9" + integrity sha512-YhwHaPGhffsFsg/zjCu1G24//j/BTRDRZbZXmMwp77m1yEqPULcWyoWrI+gUzetQxJRD/ruAucqjLtoLLfICmQ== + +"@tiptap/extension-ordered-list@^2.1.10": + version "2.1.10" + resolved "https://registry.yarnpkg.com/@tiptap/extension-ordered-list/-/extension-ordered-list-2.1.10.tgz#ef5d5ba68baf86e9b66c1b2c1cec458aa111ad44" + integrity sha512-jouo3RHUMxU4dPzZcfZdUzmsLVp1KHrLIAD2YAxBuqArACrBNfJpIhtkTKuGLlaFhKqGr+EmNdNQnK8JOBhLtQ== + +"@tiptap/extension-paragraph@^2.1.10": + version "2.1.10" + resolved "https://registry.yarnpkg.com/@tiptap/extension-paragraph/-/extension-paragraph-2.1.10.tgz#ee1238d2d6e9460b2a929b05a5fd43cfb58a6017" + integrity sha512-kzuHbrxcxpWkha5P+JFzCKT54pNqb4IBKMU5qT9YGhZSdNTtU63ncdCHM+Ad1ukLuvXAv95zh1IQC5j+Z1Qk4A== + +"@tiptap/extension-placeholder@^2.1.11": + version "2.1.11" + resolved "https://registry.yarnpkg.com/@tiptap/extension-placeholder/-/extension-placeholder-2.1.11.tgz#ba115f714dd48d5bbc65df277b74f357ff3b100e" + integrity sha512-laHYRFxJWj6m72Yf1v6Q5nF2nvwWpQlKUj6Yu/yluOOoVE92HpLqCAvA8RamqLtPiw5VxR3v3oCY0WNeQRvyIg== + +"@tiptap/extension-strike@^2.1.10": + version "2.1.10" + resolved "https://registry.yarnpkg.com/@tiptap/extension-strike/-/extension-strike-2.1.10.tgz#ec311395d16af15345b63d2dac2d459b9ad5fa9e" + integrity sha512-KW63lZLPFIir5AIeh2I7UK6Tx1O3jetD7JIPUzEqp1I1BfJlHGHVQxV8VXAmJl0hTOzjQBsHW42PmBxSC97NUg== + +"@tiptap/extension-task-item@^2.1.7": + version "2.1.10" + resolved "https://registry.yarnpkg.com/@tiptap/extension-task-item/-/extension-task-item-2.1.10.tgz#8eb0d3e8b1234fa44205dd91619f3f1937ca3254" + integrity sha512-jiH37e8c41T/UKWXzznOg325huAkAiFtjNkvfQfS23a7UDfIM90IJ+VjvFt/5EEgJ2mozBweQan4yIzlC6uWaQ== + +"@tiptap/extension-task-list@^2.1.7": + version "2.1.10" + resolved "https://registry.yarnpkg.com/@tiptap/extension-task-list/-/extension-task-list-2.1.10.tgz#faf2d520c5f5b7b5084a0804b8e65f69fea361be" + integrity sha512-1nLI81lQ/HYrcxVRSv3EyG8kcWygtaQcOZ9p6PeQjwN+z5D5PoQVHK4+8zOO1Lpz4BDR3mc4nolA7q/Li0ilOw== + +"@tiptap/extension-text-style@^2.1.11": + version "2.1.11" + resolved "https://registry.yarnpkg.com/@tiptap/extension-text-style/-/extension-text-style-2.1.11.tgz#b2fbea4f52b68f339b4941103ea2e03d1ac996a3" + integrity sha512-+JDWmcSUyFKzMDm/1xqlk7e0qPJ1nQ/UKIRuDeRtqgbxTyEw4fNlkV2k7GHCoELXqxUoplzweLID+kM1Vk2OaA== + +"@tiptap/extension-text@^2.1.10": + version "2.1.10" + resolved "https://registry.yarnpkg.com/@tiptap/extension-text/-/extension-text-2.1.10.tgz#db297fb5d2ee50ef7a14650539e3d335f772f755" + integrity sha512-ubU/WQwNB0MVKyMAHr8ka3Nu3jCR03HARGKUwNRzppZYtRXWyXHNlAaJdplNb1NMGb8hd0ElBJmwFlVqmh8haQ== + +"@tiptap/extension-underline@^2.1.7": + version "2.1.10" + resolved "https://registry.yarnpkg.com/@tiptap/extension-underline/-/extension-underline-2.1.10.tgz#d7a3ac39c2363da94651a960abe3a16bb24de398" + integrity sha512-f+rJKviGNqORGv4/1pTLZuVTb9VsKMZMLucL8423M6s8TdrH//sBB8QeU92JSnO9PjAGwxWjS1f23/KtufxP8g== + +"@tiptap/pm@^2.1.7": + version "2.1.10" + resolved "https://registry.yarnpkg.com/@tiptap/pm/-/pm-2.1.10.tgz#84d5ae574568dca00ee62698559523d77e980620" + integrity sha512-Y+AqizKnjQpx4pSaA6m/cCD5QHQRPtALhO4ZO4YFZV1idYmsJA3/S5lgJI3ZL5eAHKHcGk6Vv3/8Y+eej5YIPw== dependencies: prosemirror-changeset "^2.2.0" prosemirror-collab "^1.3.0" @@ -2379,43 +2525,48 @@ prosemirror-transform "^1.7.0" prosemirror-view "^1.28.2" -"@tiptap/react@^2.0.4": - version "2.1.8" - resolved "https://registry.yarnpkg.com/@tiptap/react/-/react-2.1.8.tgz#50a834a9b76b36eebb33e71dcb765aa9cb80f276" - integrity sha512-yTjlin4tOfYNwBdpX4+2CmNxybq2Ms50rX0RIRLABbnCTqhBIKko/eBLFq7DCot/Dwdw6c5Y098/fayKywfJWg== - dependencies: - "@tiptap/extension-bubble-menu" "^2.1.8" - "@tiptap/extension-floating-menu" "^2.1.8" - -"@tiptap/starter-kit@^2.0.4": - version "2.1.8" - resolved "https://registry.yarnpkg.com/@tiptap/starter-kit/-/starter-kit-2.1.8.tgz#d33f04478cd7b4956cb312335bcbed109269b651" - integrity sha512-LfCQgENw501XyTbCEcmiKt1d7XQi+6nTrQQfI16cCwc7lqp+LREz9EOFidkjTtrKuUHwlTaZzS7C76Cfc87mXA== - dependencies: - "@tiptap/core" "^2.1.8" - "@tiptap/extension-blockquote" "^2.1.8" - "@tiptap/extension-bold" "^2.1.8" - "@tiptap/extension-bullet-list" "^2.1.8" - "@tiptap/extension-code" "^2.1.8" - "@tiptap/extension-code-block" "^2.1.8" - "@tiptap/extension-document" "^2.1.8" - "@tiptap/extension-dropcursor" "^2.1.8" - "@tiptap/extension-gapcursor" "^2.1.8" - "@tiptap/extension-hard-break" "^2.1.8" - "@tiptap/extension-heading" "^2.1.8" - "@tiptap/extension-history" "^2.1.8" - "@tiptap/extension-horizontal-rule" "^2.1.8" - "@tiptap/extension-italic" "^2.1.8" - "@tiptap/extension-list-item" "^2.1.8" - "@tiptap/extension-ordered-list" "^2.1.8" - "@tiptap/extension-paragraph" "^2.1.8" - "@tiptap/extension-strike" "^2.1.8" - "@tiptap/extension-text" "^2.1.8" - -"@tiptap/suggestion@^2.0.4": - version "2.1.8" - resolved "https://registry.yarnpkg.com/@tiptap/suggestion/-/suggestion-2.1.8.tgz#ffd61c13aa11c4b51e50a9c83f0813f71ab0881b" - integrity sha512-3QypKFCeZSRrjgSz0n0JE5SimisolaxDZn45GGtkXuJWmKGCmsJw9UsXeH3S9ZuP3pvPImL0P9uAHlhRReRw1w== +"@tiptap/prosemirror-tables@^1.1.4": + version "1.1.4" + resolved "https://registry.yarnpkg.com/@tiptap/prosemirror-tables/-/prosemirror-tables-1.1.4.tgz#e123978f13c9b5f980066ba660ec5df857755916" + integrity sha512-O2XnDhZV7xTHSFxMMl8Ei3UVeCxuMlbGYZ+J2QG8CzkK8mxDpBa66kFr5DdyAhvdi1ptpcH9u7/GMwItQpN4sA== + +"@tiptap/react@^2.1.7": + version "2.1.10" + resolved "https://registry.yarnpkg.com/@tiptap/react/-/react-2.1.10.tgz#51cd96462e61f6fffa0ca4eb359d8d7d15ebf422" + integrity sha512-kzCWzbV2dnD5NmHjN8GiS+k0GOmoEhKnMuMzuuU6FjtOALhJzPTrIXITzWDpU3jL+r/4eeXYhAt64Wp7PVwscg== + dependencies: + "@tiptap/extension-bubble-menu" "^2.1.10" + "@tiptap/extension-floating-menu" "^2.1.10" + +"@tiptap/starter-kit@^2.1.10": + version "2.1.10" + resolved "https://registry.yarnpkg.com/@tiptap/starter-kit/-/starter-kit-2.1.10.tgz#5f19c199c79d90ef5e3b8990ca3aa76ce625d68c" + integrity sha512-h5mH1qv7SDFXWZPbOWC8zpGZ62EnDizRNtM45Gani0HYWJXcbPFpgN1qJmESP/jP+v+0hxtnVEkgfpiy3LRm6A== + dependencies: + "@tiptap/core" "^2.1.10" + "@tiptap/extension-blockquote" "^2.1.10" + "@tiptap/extension-bold" "^2.1.10" + "@tiptap/extension-bullet-list" "^2.1.10" + "@tiptap/extension-code" "^2.1.10" + "@tiptap/extension-code-block" "^2.1.10" + "@tiptap/extension-document" "^2.1.10" + "@tiptap/extension-dropcursor" "^2.1.10" + "@tiptap/extension-gapcursor" "^2.1.10" + "@tiptap/extension-hard-break" "^2.1.10" + "@tiptap/extension-heading" "^2.1.10" + "@tiptap/extension-history" "^2.1.10" + "@tiptap/extension-horizontal-rule" "^2.1.10" + "@tiptap/extension-italic" "^2.1.10" + "@tiptap/extension-list-item" "^2.1.10" + "@tiptap/extension-ordered-list" "^2.1.10" + "@tiptap/extension-paragraph" "^2.1.10" + "@tiptap/extension-strike" "^2.1.10" + "@tiptap/extension-text" "^2.1.10" + +"@tiptap/suggestion@^2.1.7": + version "2.1.10" + resolved "https://registry.yarnpkg.com/@tiptap/suggestion/-/suggestion-2.1.10.tgz#fe6dd160ea93f8135c0831ae4accc7a708bac019" + integrity sha512-k9WTTWT81UkHaxZksjp+wE31E85QL0jyLd0ZEKAs+btW148Pon1KwBeLnODNHILcdQaRPxRvb28a47cRHEKTiw== "@types/debug@^4.0.0": version "4.1.8" @@ -2448,29 +2599,36 @@ "@types/node" "*" "@types/hast@^2.0.0": - version "2.3.5" - resolved "https://registry.yarnpkg.com/@types/hast/-/hast-2.3.5.tgz#08caac88b44d0fdd04dc17a19142355f43bd8a7a" - integrity sha512-SvQi0L/lNpThgPoleH53cdjB3y9zpLlVjRbqB3rH8hx1jiRSBGAhyjV3H+URFjNVRqt2EdYNrbZE5IsGlNfpRg== + version "2.3.6" + resolved "https://registry.yarnpkg.com/@types/hast/-/hast-2.3.6.tgz#bb8b05602112a26d22868acb70c4b20984ec7086" + integrity sha512-47rJE80oqPmFdVDCD7IheXBrVdwuBgsYwoczFvKmwfo2Mzsnt+V9OONsYauFmICb6lQPpCuXYJWejBNs4pDJRg== dependencies: "@types/unist" "^2" +"@types/hast@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/hast/-/hast-3.0.1.tgz#e1705ec9258ac4885659c2d50bac06b4fcd16466" + integrity sha512-hs/iBJx2aydugBQx5ETV3ZgeSS0oIreQrFJ4bjBl0XvM4wAmDjFEALY7p0rTSLt2eL+ibjRAAs9dTPiCLtmbqQ== + dependencies: + "@types/unist" "*" + "@types/hoist-non-react-statics@^3.3.0": - version "3.3.1" - resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f" - integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA== + version "3.3.2" + resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#dc1e9ded53375d37603c479cc12c693b0878aa2a" + integrity sha512-YIQtIg4PKr7ZyqNPZObpxfHsHEmuB8dXCxd6qVcGuQVDK2bpsF7bYNnBJ4Nn7giuACZg+WewExgrtAJ3XnA4Xw== dependencies: "@types/react" "*" hoist-non-react-statics "^3.3.0" "@types/js-cookie@^3.0.2", "@types/js-cookie@^3.0.3": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@types/js-cookie/-/js-cookie-3.0.3.tgz#d6bfbbdd0c187354ca555213d1962f6d0691ff4e" - integrity sha512-Xe7IImK09HP1sv2M/aI+48a20VX+TdRJucfq4vfRVy6nWN8PYPOEnlMRSgxJAgYQIXJVL8dZ4/ilAM7dWNaOww== + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/js-cookie/-/js-cookie-3.0.4.tgz#23475b6d3b03acc84192e7c24da88eb38c1039ef" + integrity sha512-vMMnFF+H5KYqdd/myCzq6wLDlPpteJK+jGFgBus3Da7lw+YsDmx2C8feGTzY2M3Fo823yON+HC2CL240j4OV+w== "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": - version "7.0.12" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb" - integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA== + version "7.0.13" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.13.tgz#02c24f4363176d2d18fc8b70b9f3c54aba178a85" + integrity sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ== "@types/json5@^0.0.29": version "0.0.29" @@ -2525,9 +2683,9 @@ integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA== "@types/node@*": - version "20.6.0" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.6.0.tgz#9d7daa855d33d4efec8aea88cd66db1c2f0ebe16" - integrity sha512-najjVq5KN2vsH2U/xyh2opaSEz6cZMR2SetLIlxlj08nOcmPOemJmUK2o4kUzfLqfrWE0PIrNeE16XhYDd3nqg== + version "20.6.3" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.6.3.tgz#5b763b321cd3b80f6b8dde7a37e1a77ff9358dd9" + integrity sha512-HksnYH4Ljr4VQgEy2lTStbCKv/P590tmPe5HqOnv9Gprffgv5WXAY+Y5Gqniu0GGqeTCUdBnzC3QSrzPkBkAMA== "@types/node@18.0.6": version "18.0.6" @@ -2539,6 +2697,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-18.14.1.tgz#90dad8476f1e42797c49d6f8b69aaf9f876fc69f" integrity sha512-QH+37Qds3E0eDlReeboBxfHbX9omAcBCXEzswCu6jySP642jiM3cYSIkU/REqwhCUqXdonHFuBfJDiAJxMNhaQ== +"@types/node@18.15.3": + version "18.15.3" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.3.tgz#f0b991c32cfc6a4e7f3399d6cb4b8cf9a0315014" + integrity sha512-p6ua9zBxz5otCmbpb5D3U4B5Nanw6Pk3PPyX05xnxbB/fRv71N7CPmORg7uAD5P70T0xmx1pzAx/FUfa5X+3cw== + "@types/nprogress@^0.2.0": version "0.2.0" resolved "https://registry.yarnpkg.com/@types/nprogress/-/nprogress-0.2.0.tgz#86c593682d4199212a0509cc3c4d562bbbd6e45f" @@ -2559,10 +2722,10 @@ resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== -"@types/prop-types@*", "@types/prop-types@^15.0.0": - version "15.7.5" - resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf" - integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w== +"@types/prop-types@*", "@types/prop-types@^15.0.0", "@types/prop-types@^15.7.5": + version "15.7.6" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.6.tgz#bbf819813d6be21011b8f5801058498bec555572" + integrity sha512-RK/kBbYOQQHLYj9Z95eh7S6t7gq4Ojt/NT8HTk8bWVhA5DaF+5SMnxHKkP4gPNN3wAZkKP+VjAf0ebtYzf+fxg== "@types/react-beautiful-dnd@^13.1.2": version "13.1.4" @@ -2627,28 +2790,10 @@ dependencies: "@types/react" "*" -"@types/react@*", "@types/react@^18.0.17": - version "18.2.21" - resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.21.tgz#774c37fd01b522d0b91aed04811b58e4e0514ed9" - integrity sha512-neFKG/sBAwGxHgXiIxnbm3/AAVQ/cMRS93hvBpg8xYRbeQSPVABp9U2bRnPf0iI4+Ucdv3plSxKK+3CW2ENJxA== - dependencies: - "@types/prop-types" "*" - "@types/scheduler" "*" - csstype "^3.0.2" - -"@types/react@18.0.15": - version "18.0.15" - resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.15.tgz#d355644c26832dc27f3e6cbf0c4f4603fc4ab7fe" - integrity sha512-iz3BtLuIYH1uWdsv6wXYdhozhqj20oD4/Hk2DNXIn1kFsmp9x8d9QB6FnPhfkbhd2PgEONt9Q1x/ebkwjfFLow== - dependencies: - "@types/prop-types" "*" - "@types/scheduler" "*" - csstype "^3.0.2" - -"@types/react@18.0.28": - version "18.0.28" - resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.28.tgz#accaeb8b86f4908057ad629a26635fe641480065" - integrity sha512-RD0ivG1kEztNBdoAK7lekI9M+azSnitIn85h4iOiaLjaTrMjzslhaqCGaI4IyCJ1RljWiLCEu4jyrLLgqxBTew== +"@types/react@*", "@types/react@18.0.15", "@types/react@18.0.28", "@types/react@18.2.0", "@types/react@^18.0.17", "@types/react@^18.2.5": + version "18.2.0" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.0.tgz#15cda145354accfc09a18d2f2305f9fc099ada21" + integrity sha512-0FLj93y5USLHdnhIhABk83rm8XEGA7kH3cr+YUlvxoUGp1xNt/DINUMvqPxLyOQMzLmZe8i4RTHbvb8MC7NmrA== dependencies: "@types/prop-types" "*" "@types/scheduler" "*" @@ -2688,6 +2833,11 @@ resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.4.tgz#2b38784cd16957d3782e8e2b31c03bc1d13b4d65" integrity sha512-IDaobHimLQhjwsQ/NMwRVfa/yL7L/wriQPMhw1ZJall0KX6E1oxk29XMDeilW5qTIg5aoiqf5Udy8U/51aNoQQ== +"@types/unist@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/unist/-/unist-3.0.0.tgz#988ae8af1e5239e89f9fbb1ade4c935f4eeedf9a" + integrity sha512-MFETx3tbTjE7Uk6vvnWINA/1iJ7LuMdO4fcq8UfF0pRbj01aGLduVvQcRyswuACJdpnHgg8E3rQLhaRdNEJS0w== + "@types/unist@^2", "@types/unist@^2.0.0": version "2.0.8" resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.8.tgz#bb197b9639aa1a04cf464a617fe800cccd92ad5c" @@ -2699,9 +2849,9 @@ integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== "@types/uuid@^9.0.1": - version "9.0.3" - resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.3.tgz#6cdd939b4316b4f81625de9f06028d848c4a1533" - integrity sha512-taHQQH/3ZyI3zP8M/puluDEIEvtQHVYcC6y3N8ijFtAd28+Ey/G4sg1u2gB01S8MwybLOKAp9/yCMu/uR5l3Ug== + version "9.0.4" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.4.tgz#e884a59338da907bda8d2ed03e01c5c49d036f1c" + integrity sha512-zAuJWQflfx6dYJM62vna+Sn5aeSWhh3OB+wfUEACNcqUSc0AGc5JKl+ycL1vrH7frGTXhJchYjE1Hak8L819dA== "@typescript-eslint/eslint-plugin@^5.48.2", "@typescript-eslint/eslint-plugin@^5.51.0": version "5.62.0" @@ -2980,7 +3130,7 @@ array.prototype.tosorted@^1.1.1: es-shim-unscopables "^1.0.0" get-intrinsic "^1.2.1" -arraybuffer.prototype.slice@^1.0.1: +arraybuffer.prototype.slice@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz#98bd561953e3e74bb34938e77647179dfe6e9f12" integrity sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw== @@ -3030,6 +3180,18 @@ attr-accept@^2.2.2: resolved "https://registry.yarnpkg.com/attr-accept/-/attr-accept-2.2.2.tgz#646613809660110749e92f2c10833b70968d929b" integrity sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg== +autoprefixer@^10.4.14: + version "10.4.16" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.16.tgz#fad1411024d8670880bdece3970aa72e3572feb8" + integrity sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSYJx5GKcpRCzBh7RH4/0dnY+uQ== + dependencies: + browserslist "^4.21.10" + caniuse-lite "^1.0.30001538" + fraction.js "^4.3.6" + normalize-range "^0.1.2" + picocolors "^1.0.0" + postcss-value-parser "^4.2.0" + autoprefixer@^10.4.15: version "10.4.15" resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.15.tgz#a1230f4aeb3636b89120b34a1f513e2f6834d530" @@ -3200,6 +3362,18 @@ builtin-modules@^3.1.0: resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6" integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw== +bundle-require@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/bundle-require/-/bundle-require-4.0.1.tgz#2cc1ad76428043d15e0e7f30990ee3d5404aa2e3" + integrity sha512-9NQkRHlNdNpDBGmLpngF3EFDcwodhMUuLz9PaWYciVcQF9SE4LFjM2DB/xV1Li5JiuDMv7ZUWuC3rGbqR0MAXQ== + dependencies: + load-tsconfig "^0.2.3" + +cac@^6.7.12: + version "6.7.14" + resolved "https://registry.yarnpkg.com/cac/-/cac-6.7.14.tgz#804e1e6f506ee363cb0e3ccbb09cad5dd9870959" + integrity sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ== + call-bind@^1.0.0, call-bind@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" @@ -3227,9 +3401,14 @@ camelcase-css@^2.0.1: integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== caniuse-lite@^1.0.30001406, caniuse-lite@^1.0.30001517, caniuse-lite@^1.0.30001520: - version "1.0.30001534" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001534.tgz#f24a9b2a6d39630bac5c132b5dff89b39a12e7dd" - integrity sha512-vlPVrhsCS7XaSh2VvWluIQEzVhefrUQcEsQWSS5A5V+dM07uv1qHeQzAOTGIMy9i3e9bH15+muvI/UHojVgS/Q== + version "1.0.30001538" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001538.tgz#9dbc6b9af1ff06b5eb12350c2012b3af56744f3f" + integrity sha512-HWJnhnID+0YMtGlzcp3T9drmBJUVDchPJ08tpUGFLs9CYlwWPH2uLgpHn8fND5pCgXVtnGS3H4QR9XLMHVNkHw== + +caniuse-lite@^1.0.30001538: + version "1.0.30001541" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001541.tgz#b1aef0fadd87fb72db4dcb55d220eae17b81cdb1" + integrity sha512-bLOsqxDgTqUBkzxbNlSBt8annkDpQB9NdzdTbO2ooJ+eC/IQcvDspDc058g84ejCelF7vHUx57KIOjEecOHXaw== capital-case@^1.0.4: version "1.0.4" @@ -3293,7 +3472,7 @@ character-entities@^2.0.0: resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-2.0.2.tgz#2d09c2e72cd9523076ccb21157dff66ad43fcc22" integrity sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ== -chokidar@^3.5.3: +chokidar@^3.5.1, chokidar@^3.5.3: version "3.5.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== @@ -3313,6 +3492,13 @@ chownr@^1.1.1: resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== +class-variance-authority@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/class-variance-authority/-/class-variance-authority-0.7.0.tgz#1c3134d634d80271b1837452b06d821915954522" + integrity sha512-jFI8IQw4hczaL4ALINxqLEXQbWcNjoSkloa4IaufXCJr6QawJyw7tuRysRsrE8w2p/4gGaxKIt/hX3qz/IbD1A== + dependencies: + clsx "2.0.0" + classnames@^2.2.6, classnames@^2.3.1, classnames@^2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924" @@ -3330,11 +3516,16 @@ client-only@^0.0.1: resolved "https://registry.yarnpkg.com/client-only/-/client-only-0.0.1.tgz#38bba5d403c41ab150bff64a95c85013cf73bca1" integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA== -clsx@^2.0.0: +clsx@2.0.0, clsx@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.0.0.tgz#12658f3fd98fafe62075595a5c30e43d18f3d00b" integrity sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q== +clsx@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" + integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== + cmdk@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/cmdk/-/cmdk-0.2.0.tgz#53c52d56d8776c8bb8ced1055b5054100c388f7c" @@ -3472,7 +3663,7 @@ crelt@^1.0.0: resolved "https://registry.yarnpkg.com/crelt/-/crelt-1.0.6.tgz#7cc898ea74e190fb6ef9dae57f8f81cf7302df72" integrity sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g== -cross-spawn@^7.0.2: +cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -3621,7 +3812,7 @@ date-fns@^2.0.1, date-fns@^2.30.0: dependencies: "@babel/runtime" "^7.21.0" -debug@4, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4: +debug@4, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -3732,6 +3923,13 @@ detect-node-es@^1.1.0: resolved "https://registry.yarnpkg.com/detect-node-es/-/detect-node-es-1.1.0.tgz#163acdf643330caa0b4cd7c21e7ee7755d6fa493" integrity sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ== +devlop@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/devlop/-/devlop-1.1.0.tgz#4db7c2ca4dc6e0e834c30be70c94bbc976dc7018" + integrity sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA== + dependencies: + dequal "^2.0.0" + didyoumean@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037" @@ -3807,9 +4005,9 @@ ejs@^3.1.6: jake "^10.8.5" electron-to-chromium@^1.4.477: - version "1.4.520" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.520.tgz#c19c25a10d87bd88a9aae2b76cae9235a50c2994" - integrity sha512-Frfus2VpYADsrh1lB3v/ft/WVFlVzOIm+Q0p7U7VqHI6qr7NWHYKe+Wif3W50n7JAFoBsWVsoU0+qDks6WQ60g== + version "1.4.525" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.525.tgz#614284f33901fbecd3e90176c0d60590cd939700" + integrity sha512-GIZ620hDK4YmIqAWkscG4W6RwY6gOx1y5J6f4JUQwctiJrqH2oxZYU4mXHi35oV32tr630UcepBzSBGJ/WYcZA== emoji-regex@^8.0.0: version "8.0.0" @@ -3862,17 +4060,17 @@ error-ex@^1.3.1: is-arrayish "^0.2.1" es-abstract@^1.22.1: - version "1.22.1" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.22.1.tgz#8b4e5fc5cefd7f1660f0f8e1a52900dfbc9d9ccc" - integrity sha512-ioRRcXMO6OFyRpyzV3kE1IIBd4WG5/kltnzdxSCqoP8CMGs/Li+M1uF5o7lOkZVFjDs+NLesthnF66Pg/0q0Lw== + version "1.22.2" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.22.2.tgz#90f7282d91d0ad577f505e423e52d4c1d93c1b8a" + integrity sha512-YoxfFcDmhjOgWPWsV13+2RNjq1F6UQnfs+8TftwNqtzlmFzEXvlUwdrNrYeaizfjQzRMxkZ6ElWMOJIFKdVqwA== dependencies: array-buffer-byte-length "^1.0.0" - arraybuffer.prototype.slice "^1.0.1" + arraybuffer.prototype.slice "^1.0.2" available-typed-arrays "^1.0.5" call-bind "^1.0.2" es-set-tostringtag "^2.0.1" es-to-primitive "^1.2.1" - function.prototype.name "^1.1.5" + function.prototype.name "^1.1.6" get-intrinsic "^1.2.1" get-symbol-description "^1.0.0" globalthis "^1.0.3" @@ -3888,23 +4086,23 @@ es-abstract@^1.22.1: is-regex "^1.1.4" is-shared-array-buffer "^1.0.2" is-string "^1.0.7" - is-typed-array "^1.1.10" + is-typed-array "^1.1.12" is-weakref "^1.0.2" object-inspect "^1.12.3" object-keys "^1.1.1" object.assign "^4.1.4" - regexp.prototype.flags "^1.5.0" - safe-array-concat "^1.0.0" + regexp.prototype.flags "^1.5.1" + safe-array-concat "^1.0.1" safe-regex-test "^1.0.0" - string.prototype.trim "^1.2.7" - string.prototype.trimend "^1.0.6" - string.prototype.trimstart "^1.0.6" + string.prototype.trim "^1.2.8" + string.prototype.trimend "^1.0.7" + string.prototype.trimstart "^1.0.7" typed-array-buffer "^1.0.0" typed-array-byte-length "^1.0.0" typed-array-byte-offset "^1.0.0" typed-array-length "^1.0.4" unbox-primitive "^1.0.2" - which-typed-array "^1.1.10" + which-typed-array "^1.1.11" es-iterator-helpers@^1.0.12: version "1.0.15" @@ -3951,6 +4149,34 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" +esbuild@^0.18.2: + version "0.18.20" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.18.20.tgz#4709f5a34801b43b799ab7d6d82f7284a9b7a7a6" + integrity sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA== + optionalDependencies: + "@esbuild/android-arm" "0.18.20" + "@esbuild/android-arm64" "0.18.20" + "@esbuild/android-x64" "0.18.20" + "@esbuild/darwin-arm64" "0.18.20" + "@esbuild/darwin-x64" "0.18.20" + "@esbuild/freebsd-arm64" "0.18.20" + "@esbuild/freebsd-x64" "0.18.20" + "@esbuild/linux-arm" "0.18.20" + "@esbuild/linux-arm64" "0.18.20" + "@esbuild/linux-ia32" "0.18.20" + "@esbuild/linux-loong64" "0.18.20" + "@esbuild/linux-mips64el" "0.18.20" + "@esbuild/linux-ppc64" "0.18.20" + "@esbuild/linux-riscv64" "0.18.20" + "@esbuild/linux-s390x" "0.18.20" + "@esbuild/linux-x64" "0.18.20" + "@esbuild/netbsd-x64" "0.18.20" + "@esbuild/openbsd-x64" "0.18.20" + "@esbuild/sunos-x64" "0.18.20" + "@esbuild/win32-arm64" "0.18.20" + "@esbuild/win32-ia32" "0.18.20" + "@esbuild/win32-x64" "0.18.20" + escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" @@ -4011,17 +4237,32 @@ eslint-config-next@13.2.1: eslint-plugin-react "^7.31.7" eslint-plugin-react-hooks "^4.5.0" +eslint-config-next@13.2.4: + version "13.2.4" + resolved "https://registry.yarnpkg.com/eslint-config-next/-/eslint-config-next-13.2.4.tgz#8aa4d42da3a575a814634ba9c88c8d25266c5fdd" + integrity sha512-lunIBhsoeqw6/Lfkd6zPt25w1bn0znLA/JCL+au1HoEpSb4/PpsOYsYtgV/q+YPsoKIOzFyU5xnb04iZnXjUvg== + dependencies: + "@next/eslint-plugin-next" "13.2.4" + "@rushstack/eslint-patch" "^1.1.3" + "@typescript-eslint/parser" "^5.42.0" + eslint-import-resolver-node "^0.3.6" + eslint-import-resolver-typescript "^3.5.2" + eslint-plugin-import "^2.26.0" + eslint-plugin-jsx-a11y "^6.5.1" + eslint-plugin-react "^7.31.7" + eslint-plugin-react-hooks "^4.5.0" + eslint-config-prettier@^8.3.0: version "8.10.0" resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.10.0.tgz#3a06a662130807e2502fc3ff8b4143d8a0658e11" integrity sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg== eslint-config-turbo@latest: - version "1.10.13" - resolved "https://registry.yarnpkg.com/eslint-config-turbo/-/eslint-config-turbo-1.10.13.tgz#3743def3c76e27c3969c16222137f5bd913a10e2" - integrity sha512-Ffa0SxkRCPMtfUX/HDanEqsWoLwZTQTAXO9W4IsOtycb2MzJDrVcLmoFW5sMwCrg7gjqbrC4ZJoD+1SPPzIVqg== + version "1.10.14" + resolved "https://registry.yarnpkg.com/eslint-config-turbo/-/eslint-config-turbo-1.10.14.tgz#5e0c5ef1942783a2f7ffb631f4f322906a52663d" + integrity sha512-ZeB+IcuFXy1OICkLuAplVa0euoYbhK+bMEQd0nH9+Lns18lgZRm33mVz/iSoH9VdUzl/1ZmFmoK+RpZc+8R80A== dependencies: - eslint-plugin-turbo "1.10.13" + eslint-plugin-turbo "1.10.14" eslint-import-resolver-node@^0.3.6, eslint-import-resolver-node@^0.3.7: version "0.3.9" @@ -4155,10 +4396,10 @@ eslint-plugin-react@^7.29.4, eslint-plugin-react@^7.31.7: semver "^6.3.1" string.prototype.matchall "^4.0.8" -eslint-plugin-turbo@1.10.13: - version "1.10.13" - resolved "https://registry.yarnpkg.com/eslint-plugin-turbo/-/eslint-plugin-turbo-1.10.13.tgz#d2c14c7e733ee1462c94d79dce29d75f42ced275" - integrity sha512-el4AAmn0zXmvHEyp1h0IQMfse10Vy8g5Vbg4IU3+vD9CSj5sDbX07iFVt8sCKg7og9Q5FAa9mXzlCf7t4vYgzg== +eslint-plugin-turbo@1.10.14: + version "1.10.14" + resolved "https://registry.yarnpkg.com/eslint-plugin-turbo/-/eslint-plugin-turbo-1.10.14.tgz#b9b7ffc2faef52cb158088e00f9ef6fe39966bd9" + integrity sha512-sBdBDnYr9AjT1g4lR3PBkZDonTrMnR4TvuGv5W0OiF7z9az1rI68yj2UHJZvjkwwcGu5mazWA1AfB0oaagpmfg== dependencies: dotenv "16.0.3" @@ -4252,6 +4493,52 @@ eslint@8.34.0: strip-json-comments "^3.1.0" text-table "^0.2.0" +eslint@8.36.0: + version "8.36.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.36.0.tgz#1bd72202200a5492f91803b113fb8a83b11285cf" + integrity sha512-Y956lmS7vDqomxlaaQAHVmeb4tNMp2FWIvU/RnU5BD3IKMD/MJPr76xdyr68P8tV1iNMvN2mRK0yy3c+UjL+bw== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.4.0" + "@eslint/eslintrc" "^2.0.1" + "@eslint/js" "8.36.0" + "@humanwhocodes/config-array" "^0.11.8" + "@humanwhocodes/module-importer" "^1.0.1" + "@nodelib/fs.walk" "^1.2.8" + ajv "^6.10.0" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.3.2" + doctrine "^3.0.0" + escape-string-regexp "^4.0.0" + eslint-scope "^7.1.1" + eslint-visitor-keys "^3.3.0" + espree "^9.5.0" + esquery "^1.4.2" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + find-up "^5.0.0" + glob-parent "^6.0.2" + globals "^13.19.0" + grapheme-splitter "^1.0.4" + ignore "^5.2.0" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + is-path-inside "^3.0.3" + js-sdsl "^4.1.4" + js-yaml "^4.1.0" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.1.2" + natural-compare "^1.4.0" + optionator "^0.9.1" + strip-ansi "^6.0.1" + strip-json-comments "^3.1.0" + text-table "^0.2.0" + eslint@^7.23.0, eslint@^7.32.0: version "7.32.0" resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d" @@ -4350,7 +4637,7 @@ espree@^7.3.0, espree@^7.3.1: acorn-jsx "^5.3.1" eslint-visitor-keys "^1.3.0" -espree@^9.4.0, espree@^9.6.0, espree@^9.6.1: +espree@^9.4.0, espree@^9.5.0, espree@^9.6.0, espree@^9.6.1: version "9.6.1" resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== @@ -4403,6 +4690,26 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== +eventsource-parser@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/eventsource-parser/-/eventsource-parser-0.1.0.tgz#4a6b84751ca8e704040e6f7f50e7d77344fa1b7c" + integrity sha512-M9QjFtEIkwytUarnx113HGmgtk52LSn3jNAtnWKi3V+b9rqSfQeVdLsaD5AG/O4IrGQwmAAHBIsqbmURPTd2rA== + +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + expand-template@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" @@ -4534,14 +4841,14 @@ flat-cache@^3.0.4: rimraf "^3.0.2" flatted@^3.2.7: - version "3.2.7" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787" - integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== + version "3.2.9" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.9.tgz#7eb4c67ca1ba34232ca9d2d93e9886e611ad7daf" + integrity sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ== follow-redirects@^1.15.0: - version "1.15.2" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" - integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== + version "1.15.3" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.3.tgz#fe2f3ef2690afce7e82ed0b44db08165b207123a" + integrity sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q== for-each@^0.3.3: version "0.3.3" @@ -4564,7 +4871,7 @@ format@^0.2.0: resolved "https://registry.yarnpkg.com/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b" integrity sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww== -fraction.js@^4.2.0: +fraction.js@^4.2.0, fraction.js@^4.3.6: version "4.3.6" resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.6.tgz#e9e3acec6c9a28cf7bc36cbe35eea4ceb2c5c92d" integrity sha512-n2aZ9tNfYDwaHhvFTkhFErqOMIb8uyzSQ+vGJBjZyanAKZVbGUQ1sngfk9FdkBw7G26O7AgNjLcecLffD1c7eg== @@ -4604,7 +4911,7 @@ function-bind@^1.1.1: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== -function.prototype.name@^1.1.5: +function.prototype.name@^1.1.5, function.prototype.name@^1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.6.tgz#cdf315b7d90ee77a4c6ee216c3c3362da07533fd" integrity sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg== @@ -4657,6 +4964,11 @@ get-own-enumerable-property-symbols@^3.0.0: resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664" integrity sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g== +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + get-symbol-description@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6" @@ -4757,7 +5069,7 @@ globalthis@^1.0.3: dependencies: define-properties "^1.1.3" -globby@^11.0.4, globby@^11.1.0: +globby@^11.0.3, globby@^11.0.4, globby@^11.1.0: version "11.1.0" resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== @@ -4886,6 +5198,11 @@ https-proxy-agent@^5.0.0: agent-base "6" debug "4" +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + idb@^7.0.1: version "7.1.1" resolved "https://registry.yarnpkg.com/idb/-/idb-7.1.1.tgz#d910ded866d32c7ced9befc5bfdf36f572ced72b" @@ -5205,7 +5522,7 @@ is-symbol@^1.0.2, is-symbol@^1.0.3: dependencies: has-symbols "^1.0.2" -is-typed-array@^1.1.10, is-typed-array@^1.1.9: +is-typed-array@^1.1.10, is-typed-array@^1.1.12, is-typed-array@^1.1.9: version "1.1.12" resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.12.tgz#d0bab5686ef4a76f7a73097b95470ab199c57d4a" integrity sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg== @@ -5291,6 +5608,11 @@ jiti@^1.18.2: resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.20.0.tgz#2d823b5852ee8963585c8dd8b7992ffc1ae83b42" integrity sha512-3TV69ZbrvV6U5DfQimop50jE9Dl6J8O1ja1dvBbMba/sZ3YBEQqJ2VZRoQPVnhlzjNtU1vaXRZVrVjU4qtm8yA== +joycon@^3.0.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/joycon/-/joycon-3.1.1.tgz#bce8596d6ae808f8b68168f5fc69280996894f03" + integrity sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw== + js-cookie@^3.0.1: version "3.0.5" resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-3.0.5.tgz#0b7e2fd0c01552c58ba86e0841f94dc2557dcdbc" @@ -5397,6 +5719,13 @@ jsonpointer@^5.0.0: object.assign "^4.1.4" object.values "^1.1.6" +jsx-dom-cjs@^8.0.3: + version "8.0.7" + resolved "https://registry.yarnpkg.com/jsx-dom-cjs/-/jsx-dom-cjs-8.0.7.tgz#098c54680ebf5bb6f6d12cdea5cde3799c172212" + integrity sha512-dQWnuQ+bTm7o72ZlJU4glzeMX8KLxx5U+ZwmEAzVP1+roL7BSM0MrkWdHjdsuNgmxobZCJ+qgiot9EgbJPOoEg== + dependencies: + csstype "^3.1.2" + keycode@^2.2.0: version "2.2.1" resolved "https://registry.yarnpkg.com/keycode/-/keycode-2.2.1.tgz#09c23b2be0611d26117ea2501c2c391a01f39eff" @@ -5478,6 +5807,11 @@ linkifyjs@^4.1.0: resolved "https://registry.yarnpkg.com/linkifyjs/-/linkifyjs-4.1.1.tgz#73d427e3bbaaf4ca8e71c589ad4ffda11a9a5fde" integrity sha512-zFN/CTVmbcVef+WaDXT63dNzzkfRBKT1j464NJQkV7iSgJU0sLBus9W0HBwnXK13/hf168pbrx/V/bjEHOXNHA== +load-tsconfig@^0.2.3: + version "0.2.5" + resolved "https://registry.yarnpkg.com/load-tsconfig/-/load-tsconfig-0.2.5.tgz#453b8cd8961bfb912dea77eb6c168fe8cca3d3a1" + integrity sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg== + loader-utils@^2.0.0: version "2.0.4" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.4.tgz#8b5cb38b5c34a9a018ee1fc0e6a066d1dfcc528c" @@ -5571,6 +5905,15 @@ lowlight@^2.9.0: fault "^2.0.0" highlight.js "~11.8.0" +lowlight@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/lowlight/-/lowlight-3.0.0.tgz#8772e6514f1c14cd576b5a7a22668f5aa2ddd10b" + integrity sha512-kedX6yxvgak8P4LGh3vKRDQuMbVcnP+qRuDJlve2w+mNJAbEhEQPjYCp9QJnpVL5F2aAAVjeIzzrbQZUKHiDJw== + dependencies: + "@types/hast" "^3.0.0" + devlop "^1.0.0" + highlight.js "~11.8.0" + lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" @@ -5590,6 +5933,11 @@ lru_map@^0.3.3: resolved "https://registry.yarnpkg.com/lru_map/-/lru_map-0.3.3.tgz#b5c8351b9464cbd750335a79650a0ec0e56118dd" integrity sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ== +lucide-react@^0.244.0: + version "0.244.0" + resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.244.0.tgz#9626f44881830280012dad23afda7ddbcffff24b" + integrity sha512-PeDVbx5PlIRrVvdxiuSxPfBo7sK5qrL3LbvvRoGVNiHYRAkBm/48lKqoioxcmp0bgsyJs9lMw7CdtGFvnMJbVg== + lucide-react@^0.263.1: version "0.263.1" resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.263.1.tgz#a456ee0d171aa373929bd3ee20d6f9fb4429c301" @@ -5929,6 +6277,11 @@ mime-types@^2.1.12, mime-types@^2.1.27: dependencies: mime-db "1.52.0" +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + mimic-response@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" @@ -6119,6 +6472,13 @@ normalize.css@^8.0.1: resolved "https://registry.yarnpkg.com/normalize.css/-/normalize.css-8.0.1.tgz#9b98a208738b9cc2634caacbc42d131c97487bf3" integrity sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg== +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + nprogress@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/nprogress/-/nprogress-0.2.0.tgz#cb8f34c53213d895723fcbab907e9422adbcafb1" @@ -6228,6 +6588,13 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0: dependencies: wrappy "1" +onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + optionator@^0.9.1, optionator@^0.9.3: version "0.9.3" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64" @@ -6346,7 +6713,7 @@ path-is-inside@^1.0.2: resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" integrity sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w== -path-key@^3.1.0: +path-key@^3.0.0, path-key@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== @@ -6471,10 +6838,10 @@ postcss@8.4.14: picocolors "^1.0.0" source-map-js "^1.0.2" -postcss@^8.4.23, postcss@^8.4.29: - version "8.4.29" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.29.tgz#33bc121cf3b3688d4ddef50be869b2a54185a1dd" - integrity sha512-cbI+jaqIeu/VGqXEarWkRCCffhjgXc0qjBtXpqJhTBohMUjUQnbBr0xqX3vEKudc4iviTewcJo5ajcec5+wdJw== +postcss@^8.4.21, postcss@^8.4.23, postcss@^8.4.29: + version "8.4.30" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.30.tgz#0e0648d551a606ef2192a26da4cabafcc09c1aa7" + integrity sha512-7ZEao1g4kd68l97aWG/etQKPKq07us0ieSZ2TnFDk11i0ZfDW2AwKHYU8qv4MZKqN2fdBfg+7q0ES06UA73C1g== dependencies: nanoid "^3.3.6" picocolors "^1.0.0" @@ -6503,12 +6870,17 @@ prelude-ls@^1.2.1: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== +prettier-plugin-tailwindcss@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.3.0.tgz#8299b307c7f6467f52732265579ed9375be6c818" + integrity sha512-009/Xqdy7UmkcTBpwlq7jsViDqXAYSOMLDrHAdTMlVZOrKfM2o9Ci7EMWTMZ7SkKBFTG04UM9F9iM2+4i6boDA== + prettier-plugin-tailwindcss@^0.5.4: version "0.5.4" resolved "https://registry.yarnpkg.com/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.5.4.tgz#ebfacbcb90e2ca1df671ffe4083e99f81d72040d" integrity sha512-QZzzB1bID6qPsKHTeA9qPo1APmmxfFrA5DD3LQ+vbTmAnY40eJI7t9Q1ocqel2EKMWNPLJqdTDWZj1hKYgqSgg== -prettier@^2.8.7: +prettier@^2.8.7, prettier@^2.8.8: version "2.8.8" resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== @@ -6788,9 +7160,9 @@ react-css-styled@^1.1.9: framework-utils "^1.1.0" react-datepicker@^4.8.0: - version "4.17.0" - resolved "https://registry.yarnpkg.com/react-datepicker/-/react-datepicker-4.17.0.tgz#1b3eb5d1d60709e991e4c46b049e97911993193c" - integrity sha512-z50H44XbnkYlns7gVHzHK4jWAzLfvQehh5Lvindb09J97yVJKIbsmHs98D0f77tdZc3dSYM7oAqsFY55dBeOGQ== + version "4.18.0" + resolved "https://registry.yarnpkg.com/react-datepicker/-/react-datepicker-4.18.0.tgz#d66301acc47833d31fa6f46f98781b084106da0e" + integrity sha512-0MYt3HmLbHVk1sw4v+RCbLAVg5TA3jWP7RyjZbo53PC+SEi+pjdgc92lB53ai/ENZaTOhbXmgni9GzvMrorMAw== dependencies: "@popperjs/core" "^2.11.8" classnames "^2.2.6" @@ -6868,9 +7240,9 @@ react-markdown@^8.0.7: vfile "^5.0.0" react-moveable@^0.54.1: - version "0.54.1" - resolved "https://registry.yarnpkg.com/react-moveable/-/react-moveable-0.54.1.tgz#3c69748c444184700e6999501b0da953c934205e" - integrity sha512-Kj2ifw9nk3LZvu7ezhst8Z5WBPRr+yVv9oROwrBirFlHmwGHHZXUGk5Gaezu+JGqqNRsQJncVMW5Uf68KSSOvg== + version "0.54.2" + resolved "https://registry.yarnpkg.com/react-moveable/-/react-moveable-0.54.2.tgz#87ce9af3499dc1c8218bce7e174b10264c1bbecf" + integrity sha512-NGaVLbn0i9pb3+BWSKGWFqI/Mgm4+WMeWHxXXQ4Qi1tHxWCXrUrbGvpxEpt69G/hR7dez+/m68ex+fabjnvcUg== dependencies: "@daybrush/utils" "^1.13.0" "@egjs/agent" "^2.2.1" @@ -7026,9 +7398,9 @@ reflect.getprototypeof@^1.0.4: which-builtin-type "^1.1.3" regenerate-unicode-properties@^10.1.0: - version "10.1.0" - resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz#7c3192cab6dd24e21cb4461e5ddd7dd24fa8374c" - integrity sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ== + version "10.1.1" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz#6b0e05489d9076b04c436f318d9b067bba459480" + integrity sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q== dependencies: regenerate "^1.4.2" @@ -7049,7 +7421,7 @@ regenerator-transform@^0.15.2: dependencies: "@babel/runtime" "^7.8.4" -regexp.prototype.flags@^1.2.0, regexp.prototype.flags@^1.5.0: +regexp.prototype.flags@^1.2.0, regexp.prototype.flags@^1.5.0, regexp.prototype.flags@^1.5.1: 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== @@ -7111,15 +7483,20 @@ resolve-from@^4.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + resolve-pkg-maps@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz#616b3dc2c57056b5588c31cdf4b3d64db133720f" integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw== resolve@^1.1.7, resolve@^1.14.2, resolve@^1.19.0, resolve@^1.22.0, resolve@^1.22.2, resolve@^1.22.4: - version "1.22.4" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.4.tgz#1dc40df46554cdaf8948a486a10f6ba1e2026c34" - integrity sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg== + version "1.22.6" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.6.tgz#dd209739eca3aef739c626fea1b4f3c506195362" + integrity sha512-njhxM7mV12JfufShqGy3Rz8j11RPdLy4xi15UurGJeoHLfJpVXKdh3ueuOqbYUcDZnffr6X739JBo5LzyahEsw== dependencies: is-core-module "^2.13.0" path-parse "^1.0.7" @@ -7177,6 +7554,13 @@ rollup@^2.43.1: optionalDependencies: fsevents "~2.3.2" +rollup@^3.2.5: + version "3.29.2" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.29.2.tgz#cbc76cd5b03b9f9e93be991d23a1dff9c6d5b740" + integrity sha512-CJouHoZ27v6siztc21eEQGo0kIcE5D1gVPA571ez0mMYb25LGYGKnVNXpEj5MGlepmDWGXNjDB5q7uNiPHC11A== + optionalDependencies: + fsevents "~2.3.2" + rope-sequence@^1.3.0: version "1.3.4" resolved "https://registry.yarnpkg.com/rope-sequence/-/rope-sequence-1.3.4.tgz#df85711aaecd32f1e756f76e43a415171235d425" @@ -7196,7 +7580,7 @@ sade@^1.7.3: dependencies: mri "^1.1.0" -safe-array-concat@^1.0.0, safe-array-concat@^1.0.1: +safe-array-concat@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.0.1.tgz#91686a63ce3adbea14d61b14c99572a8ff84754c" integrity sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q== @@ -7306,9 +7690,9 @@ set-function-name@^2.0.0, set-function-name@^2.0.1: has-property-descriptors "^1.0.0" sharp@^0.32.1: - version "0.32.5" - resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.32.5.tgz#9ddc78ead6446094f51e50355a2d4ec6e7220cd4" - integrity sha512-0dap3iysgDkNaPOaOL4X/0akdu0ma62GcdC2NBQ+93eqpePdDdr2/LM0sFdDSMmN7yS+odyZtPsb7tx/cYBKnQ== + version "0.32.6" + resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.32.6.tgz#6ad30c0b7cd910df65d5f355f774aa4fce45732a" + integrity sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w== dependencies: color "^4.2.3" detect-libc "^2.0.2" @@ -7340,6 +7724,11 @@ side-channel@^1.0.4: get-intrinsic "^1.0.2" object-inspect "^1.9.0" +signal-exit@^3.0.3: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + simple-concat@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" @@ -7383,11 +7772,6 @@ snake-case@^3.0.4: dot-case "^3.0.4" tslib "^2.0.3" -sonner@^0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/sonner/-/sonner-0.6.2.tgz#d87420e80d8b25b6d2bd6aabcc28465f03962bdc" - integrity sha512-bh4FWhYoNN481ZIW94W4e0kSLBTMGislYg2YXvDS1px1AJJz4erQe9jHV8s5pS1VMVDgfh3CslNSFLaU6Ldrnw== - source-list-map@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" @@ -7406,6 +7790,13 @@ source-map-support@~0.5.20: buffer-from "^1.0.0" source-map "^0.6.0" +source-map@0.8.0-beta.0, source-map@^0.8.0-beta.0: + version "0.8.0-beta.0" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.8.0-beta.0.tgz#d4c1bb42c3f7ee925f005927ba10709e0d1d1f11" + integrity sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA== + dependencies: + whatwg-url "^7.0.0" + source-map@^0.5.7: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" @@ -7416,13 +7807,6 @@ source-map@^0.6.0, source-map@~0.6.1: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== -source-map@^0.8.0-beta.0: - version "0.8.0-beta.0" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.8.0-beta.0.tgz#d4c1bb42c3f7ee925f005927ba10709e0d1d1f11" - integrity sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA== - dependencies: - whatwg-url "^7.0.0" - sourcemap-codec@^1.4.8: version "1.4.8" resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" @@ -7477,7 +7861,7 @@ string.prototype.matchall@^4.0.6, string.prototype.matchall@^4.0.7, string.proto set-function-name "^2.0.0" side-channel "^1.0.4" -string.prototype.trim@^1.2.7: +string.prototype.trim@^1.2.8: version "1.2.8" resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz#f9ac6f8af4bd55ddfa8895e6aea92a96395393bd" integrity sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ== @@ -7486,7 +7870,7 @@ string.prototype.trim@^1.2.7: define-properties "^1.2.0" es-abstract "^1.22.1" -string.prototype.trimend@^1.0.6: +string.prototype.trimend@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz#1bb3afc5008661d73e2dc015cd4853732d6c471e" integrity sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA== @@ -7495,7 +7879,7 @@ string.prototype.trimend@^1.0.6: define-properties "^1.2.0" es-abstract "^1.22.1" -string.prototype.trimstart@^1.0.6: +string.prototype.trimstart@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz#d4cdb44b83a4737ffbac2d406e405d43d0184298" integrity sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg== @@ -7537,6 +7921,11 @@ strip-comments@^2.0.1: resolved "https://registry.yarnpkg.com/strip-comments/-/strip-comments-2.0.1.tgz#4ad11c3fbcac177a67a40ac224ca339ca1c1ba9b" integrity sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw== +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" @@ -7564,7 +7953,7 @@ stylis@4.2.0: resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.2.0.tgz#79daee0208964c8fe695a42fcffcac633a211a51" integrity sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw== -sucrase@^3.32.0: +sucrase@^3.20.3, sucrase@^3.32.0: version "3.34.0" resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.34.0.tgz#1e0e2d8fcf07f8b9c3569067d92fbd8690fb576f" integrity sha512-70/LQEZ07TEcxiU2dz51FKaE6hCTWC6vr7FOk3Gr0U60C3shtAN+H+BFr9XlYe5xqf3RA8nrc+VIwzCfnxuXJw== @@ -7627,12 +8016,12 @@ tailwind-merge@^1.14.0: resolved "https://registry.yarnpkg.com/tailwind-merge/-/tailwind-merge-1.14.0.tgz#e677f55d864edc6794562c63f5001f45093cdb8b" integrity sha512-3mFKyCo/MBcgyOTlrY8T7odzZFx+w+qKSMAmdFzRvqBfLlSigU6TZnlFHK0lkMwj9Bj8OYU+9yW9lmGuS0QEnQ== -tailwindcss-animate@^1.0.7: +tailwindcss-animate@^1.0.6: version "1.0.7" resolved "https://registry.yarnpkg.com/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz#318b692c4c42676cc9e67b19b78775742388bef4" integrity sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA== -tailwindcss@^3.3.3: +tailwindcss@^3.2.7, tailwindcss@^3.3.3: version "3.3.3" resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.3.3.tgz#90da807393a2859189e48e9e7000e6880a736daf" integrity sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w== @@ -7820,6 +8209,11 @@ tr46@~0.0.3: resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== +tree-kill@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" + integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== + trim-lines@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/trim-lines/-/trim-lines-3.0.1.tgz#d802e332a07df861c48802c04321017b1bd87338" @@ -7860,6 +8254,26 @@ tslib@~2.5.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.3.tgz#24944ba2d990940e6e982c4bea147aba80209913" integrity sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w== +tsup@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/tsup/-/tsup-7.2.0.tgz#bb24c0d5e436477900c712e42adc67200607303c" + integrity sha512-vDHlczXbgUvY3rWvqFEbSqmC1L7woozbzngMqTtL2PGBODTtWlRwGDDawhvWzr5c1QjKe4OAKqJGfE1xeXUvtQ== + dependencies: + bundle-require "^4.0.0" + cac "^6.7.12" + chokidar "^3.5.1" + debug "^4.3.1" + esbuild "^0.18.2" + execa "^5.0.0" + globby "^11.0.3" + joycon "^3.0.1" + postcss-load-config "^4.0.1" + resolve-from "^5.0.0" + rollup "^3.2.5" + source-map "0.8.0-beta.0" + sucrase "^3.20.3" + tree-kill "^1.2.2" + tsutils@^3.21.0: version "3.21.0" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" @@ -7874,47 +8288,47 @@ tunnel-agent@^0.6.0: dependencies: safe-buffer "^5.0.1" -turbo-darwin-64@1.10.13: - version "1.10.13" - resolved "https://registry.yarnpkg.com/turbo-darwin-64/-/turbo-darwin-64-1.10.13.tgz#7abc33838bf74c7e7ba2955cb6acc40342d6964c" - integrity sha512-vmngGfa2dlYvX7UFVncsNDMuT4X2KPyPJ2Jj+xvf5nvQnZR/3IeDEGleGVuMi/hRzdinoxwXqgk9flEmAYp0Xw== +turbo-darwin-64@1.10.14: + version "1.10.14" + resolved "https://registry.yarnpkg.com/turbo-darwin-64/-/turbo-darwin-64-1.10.14.tgz#9e8061bc0d706a69bc61d88fbfa7690fa2a897fc" + integrity sha512-I8RtFk1b9UILAExPdG/XRgGQz95nmXPE7OiGb6ytjtNIR5/UZBS/xVX/7HYpCdmfriKdVwBKhalCoV4oDvAGEg== -turbo-darwin-arm64@1.10.13: - version "1.10.13" - resolved "https://registry.yarnpkg.com/turbo-darwin-arm64/-/turbo-darwin-arm64-1.10.13.tgz#89f2d14a28789e5eaf276a17fecd4116d61fd16d" - integrity sha512-eMoJC+k7gIS4i2qL6rKmrIQGP6Wr9nN4odzzgHFngLTMimok2cGLK3qbJs5O5F/XAtEeRAmuxeRnzQwTl/iuAw== +turbo-darwin-arm64@1.10.14: + version "1.10.14" + resolved "https://registry.yarnpkg.com/turbo-darwin-arm64/-/turbo-darwin-arm64-1.10.14.tgz#2589aeb814257601c979dd40ff8a6cace6c9e4e5" + integrity sha512-KAdUWryJi/XX7OD0alOuOa0aJ5TLyd4DNIYkHPHYcM6/d7YAovYvxRNwmx9iv6Vx6IkzTnLeTiUB8zy69QkG9Q== -turbo-linux-64@1.10.13: - version "1.10.13" - resolved "https://registry.yarnpkg.com/turbo-linux-64/-/turbo-linux-64-1.10.13.tgz#803e2b351984ec81034dc4b6935cdfb0a7d7d572" - integrity sha512-0CyYmnKTs6kcx7+JRH3nPEqCnzWduM0hj8GP/aodhaIkLNSAGAa+RiYZz6C7IXN+xUVh5rrWTnU2f1SkIy7Gdg== +turbo-linux-64@1.10.14: + version "1.10.14" + resolved "https://registry.yarnpkg.com/turbo-linux-64/-/turbo-linux-64-1.10.14.tgz#c01d26f62f6c47701026dea083054918fe8caf47" + integrity sha512-BOBzoREC2u4Vgpap/WDxM6wETVqVMRcM8OZw4hWzqCj2bqbQ6L0wxs1LCLWVrghQf93JBQtIGAdFFLyCSBXjWQ== -turbo-linux-arm64@1.10.13: - version "1.10.13" - resolved "https://registry.yarnpkg.com/turbo-linux-arm64/-/turbo-linux-arm64-1.10.13.tgz#92439a927ec98801be1be266016736be5730e40c" - integrity sha512-0iBKviSGQQlh2OjZgBsGjkPXoxvRIxrrLLbLObwJo3sOjIH0loGmVIimGS5E323soMfi/o+sidjk2wU1kFfD7Q== +turbo-linux-arm64@1.10.14: + version "1.10.14" + resolved "https://registry.yarnpkg.com/turbo-linux-arm64/-/turbo-linux-arm64-1.10.14.tgz#7cbcb091995a09134342a57e0672501115b46df2" + integrity sha512-D8T6XxoTdN5D4V5qE2VZG+/lbZX/89BkAEHzXcsSUTRjrwfMepT3d2z8aT6hxv4yu8EDdooZq/2Bn/vjMI32xw== -turbo-windows-64@1.10.13: - version "1.10.13" - resolved "https://registry.yarnpkg.com/turbo-windows-64/-/turbo-windows-64-1.10.13.tgz#870815cdcb20f2480296c208778f9127be61eec6" - integrity sha512-S5XySRfW2AmnTeY1IT+Jdr6Goq7mxWganVFfrmqU+qqq3Om/nr0GkcUX+KTIo9mPrN0D3p5QViBRzulwB5iuUQ== +turbo-windows-64@1.10.14: + version "1.10.14" + resolved "https://registry.yarnpkg.com/turbo-windows-64/-/turbo-windows-64-1.10.14.tgz#935d2d1da7fb1f9a941fb3aa122fd9a70b61c416" + integrity sha512-zKNS3c1w4i6432N0cexZ20r/aIhV62g69opUn82FLVs/zk3Ie0GVkSB6h0rqIvMalCp7enIR87LkPSDGz9K4UA== -turbo-windows-arm64@1.10.13: - version "1.10.13" - resolved "https://registry.yarnpkg.com/turbo-windows-arm64/-/turbo-windows-arm64-1.10.13.tgz#2da38b71b1716732541dfc5bd7475dc0903921f8" - integrity sha512-nKol6+CyiExJIuoIc3exUQPIBjP9nIq5SkMJgJuxsot2hkgGrafAg/izVDRDrRduQcXj2s8LdtxJHvvnbI8hEQ== +turbo-windows-arm64@1.10.14: + version "1.10.14" + resolved "https://registry.yarnpkg.com/turbo-windows-arm64/-/turbo-windows-arm64-1.10.14.tgz#cb3a55d30a75aa76258991e929e92f207cac872f" + integrity sha512-rkBwrTPTxNSOUF7of8eVvvM+BkfkhA2OvpHM94if8tVsU+khrjglilp8MTVPHlyS9byfemPAmFN90oRIPB05BA== turbo@latest: - version "1.10.13" - resolved "https://registry.yarnpkg.com/turbo/-/turbo-1.10.13.tgz#8050bcd09f8a20d5a626f41e86cd8760e49a0df6" - integrity sha512-vOF5IPytgQPIsgGtT0n2uGZizR2N3kKuPIn4b5p5DdeLoI0BV7uNiydT7eSzdkPRpdXNnO8UwS658VaI4+YSzQ== + version "1.10.14" + resolved "https://registry.yarnpkg.com/turbo/-/turbo-1.10.14.tgz#31f3bb3017218191e0de64728da199678a50aa7d" + integrity sha512-hr9wDNYcsee+vLkCDIm8qTtwhJ6+UAMJc3nIY6+PNgUTtXcQgHxCq8BGoL7gbABvNWv76CNbK5qL4Lp9G3ZYRA== optionalDependencies: - turbo-darwin-64 "1.10.13" - turbo-darwin-arm64 "1.10.13" - turbo-linux-64 "1.10.13" - turbo-linux-arm64 "1.10.13" - turbo-windows-64 "1.10.13" - turbo-windows-arm64 "1.10.13" + turbo-darwin-64 "1.10.14" + turbo-darwin-arm64 "1.10.14" + turbo-linux-64 "1.10.14" + turbo-linux-arm64 "1.10.14" + turbo-windows-64 "1.10.14" + turbo-windows-arm64 "1.10.14" type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" @@ -8310,7 +8724,7 @@ which-collection@^1.0.1: is-weakmap "^2.0.1" is-weakset "^2.0.1" -which-typed-array@^1.1.10, which-typed-array@^1.1.11, which-typed-array@^1.1.9: +which-typed-array@^1.1.11, which-typed-array@^1.1.9: version "1.1.11" resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.11.tgz#99d691f23c72aab6768680805a271b69761ed61a" integrity sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==