From 4d6e25222ef66c8875f60a70aad05f540efc4ac8 Mon Sep 17 00:00:00 2001 From: yousefed Date: Thu, 14 Nov 2024 11:23:04 +0100 Subject: [PATCH 001/144] wip: better liveblocks support --- examples/01-basic/01-minimal/App.tsx | 33 +- examples/01-basic/01-minimal/Editor.tsx | 74 + examples/01-basic/01-minimal/Room.tsx | 23 + examples/01-basic/01-minimal/Threads.tsx | 25 + examples/01-basic/01-minimal/globals.css | 23 + examples/01-basic/02-block-objects/App.tsx | 3 +- package-lock.json | 2167 ++++++++++++++--- .../src/api/nodeConversions/nodeToBlock.ts | 4 + packages/core/src/editor/BlockNoteEditor.ts | 19 +- .../core/src/editor/BlockNoteExtensions.ts | 4 +- .../core/src/editor/BlockNoteTipTapEditor.ts | 59 +- .../core/src/extensions/UniqueID/UniqueID.ts | 1 + packages/react/src/editor/BlockNoteView.tsx | 22 +- packages/react/src/editor/EditorContent.tsx | 115 +- .../react/src/schema/@util/ReactRenderUtil.ts | 1 + packages/react/src/schema/ReactStyleSpec.tsx | 3 +- playground/package.json | 7 +- 17 files changed, 2178 insertions(+), 405 deletions(-) create mode 100644 examples/01-basic/01-minimal/Editor.tsx create mode 100644 examples/01-basic/01-minimal/Room.tsx create mode 100644 examples/01-basic/01-minimal/Threads.tsx create mode 100644 examples/01-basic/01-minimal/globals.css diff --git a/examples/01-basic/01-minimal/App.tsx b/examples/01-basic/01-minimal/App.tsx index a3b92bafd2..f35ac2be6f 100644 --- a/examples/01-basic/01-minimal/App.tsx +++ b/examples/01-basic/01-minimal/App.tsx @@ -1,12 +1,33 @@ import "@blocknote/core/fonts/inter.css"; -import { BlockNoteView } from "@blocknote/mantine"; import "@blocknote/mantine/style.css"; -import { useCreateBlockNote } from "@blocknote/react"; +import "@liveblocks/react-tiptap/styles.css"; +import "@liveblocks/react-ui/styles.css"; +import { Editor } from "./Editor.jsx"; +import { Room } from "./Room.jsx"; +import "./globals.css"; export default function App() { - // Creates a new editor instance. - const editor = useCreateBlockNote(); - // Renders the editor instance using a React component. - return ; + return ( + + + + ); } + +/** + * + * TODO: + * - blocking: DOM updates + * - blocking: Extension API + * - fix mount issue + * - - (domelement) + * - - position out of range + * - versioning + * - automatic comment button? + * - animation performance + * - Even simpler API? + * - side menu visibility when composing + * - hide composing box when clicking outside + * - users / mentions / history / notifications + */ diff --git a/examples/01-basic/01-minimal/Editor.tsx b/examples/01-basic/01-minimal/Editor.tsx new file mode 100644 index 0000000000..5df71a2389 --- /dev/null +++ b/examples/01-basic/01-minimal/Editor.tsx @@ -0,0 +1,74 @@ +"use client"; + +import { BlockNoteEditor } from "@blocknote/core"; +import { BlockNoteView } from "@blocknote/mantine"; +import { + FormattingToolbar, + FormattingToolbarController, + blockTypeSelectItems, + getFormattingToolbarItems, + useComponentsContext, + useCreateBlockNote, +} from "@blocknote/react"; +import { useLiveblocksExtension } from "@liveblocks/react-tiptap"; + +export function CustomFormattingToolbar(props: { + editor: BlockNoteEditor; +}) { + const ctx = useComponentsContext()!; + return ( + + {...getFormattingToolbarItems( + blockTypeSelectItems(props.editor.dictionary) + )} + { + debugger; + props.editor?._tiptapEditor.chain().focus().addPendingComment().run(); + }}> + Comment + + + ); +} + +export function Editor() { + const liveblocks = useLiveblocksExtension(); + + const editor = useCreateBlockNote({ + _tiptapOptions: { + extensions: [liveblocks], + }, + disableExtensions: ["history"], + }); + + // const [x, setX] = useState(); + + // useEffect(() => { + // const interval = setInterval(() => { + // setX(Math.random()); + // }, 1000); + // return () => clearInterval(interval); + // }, []); + + return ( + + {/* {editor.prosemirrorView && editor.domElement && ( */} +
+ HELLO + {/* + */} +
+ {/* )} */} + } + /> +
+ ); +} + +function Test(props: { editor: Editor }) { + debugger; + return
TEST
; +} diff --git a/examples/01-basic/01-minimal/Room.tsx b/examples/01-basic/01-minimal/Room.tsx new file mode 100644 index 0000000000..679e4626e8 --- /dev/null +++ b/examples/01-basic/01-minimal/Room.tsx @@ -0,0 +1,23 @@ +"use client"; + +import { + ClientSideSuspense, + LiveblocksProvider, + RoomProvider, +} from "@liveblocks/react/suspense"; +import { ReactNode } from "react"; + +export function Room({ children }: { children: ReactNode }) { + return ( + + + Loading…}> + {children} + + + + ); +} diff --git a/examples/01-basic/01-minimal/Threads.tsx b/examples/01-basic/01-minimal/Threads.tsx new file mode 100644 index 0000000000..968fe84482 --- /dev/null +++ b/examples/01-basic/01-minimal/Threads.tsx @@ -0,0 +1,25 @@ +import { + AnchoredThreads, + FloatingComposer, + FloatingThreads, +} from "@liveblocks/react-tiptap"; +import { useThreads } from "@liveblocks/react/suspense"; +import { Editor } from "@tiptap/react"; + +export function Threads({ editor }: { editor: Editor | null }) { + const { threads } = useThreads({ query: { resolved: false } }); + + return ( + <> +
+ +
+ + + + ); +} diff --git a/examples/01-basic/01-minimal/globals.css b/examples/01-basic/01-minimal/globals.css new file mode 100644 index 0000000000..82cdc90418 --- /dev/null +++ b/examples/01-basic/01-minimal/globals.css @@ -0,0 +1,23 @@ +/* For mobile */ +.floating-threads { + display: none; +} + +/* For desktop */ +.anchored-threads { + display: block; + max-width: 300px; + width: 100%; + position: absolute; + right: 12px; +} + +@media (max-width: 640px) { + .floating-threads { + display: block; + } + + .anchored-threads { + display: none; + } +} diff --git a/examples/01-basic/02-block-objects/App.tsx b/examples/01-basic/02-block-objects/App.tsx index 846df5d180..cecd134b97 100644 --- a/examples/01-basic/02-block-objects/App.tsx +++ b/examples/01-basic/02-block-objects/App.tsx @@ -1,8 +1,8 @@ import { Block } from "@blocknote/core"; import "@blocknote/core/fonts/inter.css"; -import { useCreateBlockNote } from "@blocknote/react"; import { BlockNoteView } from "@blocknote/mantine"; import "@blocknote/mantine/style.css"; +import { useCreateBlockNote } from "@blocknote/react"; import { useState } from "react"; import "./styles.css"; @@ -38,6 +38,7 @@ export default function App() {
BlockNote Editor:
{ // Saves the document JSON to state. diff --git a/package-lock.json b/package-lock.json index 19443c203c..3cec4a4666 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4151,9 +4151,9 @@ } }, "node_modules/@floating-ui/react-dom": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.0.tgz", - "integrity": "sha512-lNzj5EQmEKn5FFKc04+zasr09h/uX8RtJRNj5gUXsSQIXHVWTVh+hVAg1vOMCexkX8EgvemMvIFpQfkosnVNyA==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.2.tgz", + "integrity": "sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==", "dependencies": { "@floating-ui/dom": "^1.0.0" }, @@ -4703,6 +4703,11 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@juggle/resize-observer": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.4.0.tgz", + "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==" + }, "node_modules/@lerna/add": { "version": "5.6.2", "resolved": "https://registry.npmjs.org/@lerna/add/-/add-5.6.2.tgz", @@ -5768,26 +5773,123 @@ } }, "node_modules/@liveblocks/client": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/@liveblocks/client/-/client-1.12.0.tgz", - "integrity": "sha512-TL4sPbWBlrGF7UXLNx2RYuZi/Z51jXALMFAdaWkYE0Qz7mMwxTVSyjPVR7ZXVuqdSo0CTQ1rpPyZeqcoUDtEoQ==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@liveblocks/client/-/client-2.11.0.tgz", + "integrity": "sha512-ZRayUwwOzucYn4QqOTz0vcQSZlqGaP8TBROzE9Ye/PwfACNasyfa3roZqfizhBlYlYQQ/8qQlvGl2n3LPf8Byg==", "dependencies": { - "@liveblocks/core": "1.12.0" + "@liveblocks/core": "2.11.0" } }, "node_modules/@liveblocks/core": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/@liveblocks/core/-/core-1.12.0.tgz", - "integrity": "sha512-cPuVZwSh+EBnJL8DA999h4QDNSlOMFyxHPPHm9qVBf9Cl+NY7/Bg3cyKFiHOki6g7y8dQj8No2tpnAHWdqqalA==" + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@liveblocks/core/-/core-2.11.0.tgz", + "integrity": "sha512-2WQlJvJ1NVJ/CpKhDNJJHXrh2Yzz6eXchqn64JqTHk5TLGbx1s4kaTahEXaAOXmu2abnJWnv06v1mhv3YXYg+A==" + }, + "node_modules/@liveblocks/react": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@liveblocks/react/-/react-2.11.0.tgz", + "integrity": "sha512-uAPEh9ZS03ANTnbbU5PTiFVusI05H7EqOH4aDVaKDrogkKi70RYbCek4PKY8TpK2vLhmutBxyVd3tp2uBwvTFQ==", + "dependencies": { + "@liveblocks/client": "2.11.0", + "@liveblocks/core": "2.11.0", + "use-sync-external-store": "^1.2.2" + }, + "peerDependencies": { + "react": "^16.14.0 || ^17 || ^18 || ^19 || ^19.0.0-rc" + } + }, + "node_modules/@liveblocks/react-tiptap": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@liveblocks/react-tiptap/-/react-tiptap-2.11.0.tgz", + "integrity": "sha512-byqtcZYEZQbBoSLepU84RS/Q7fnGQdeJXLs0bbmbyxzmr6qcdYsXu3Y01h3+x6TzmMLGQU6yDfQbtkZd6mX6Iw==", + "dependencies": { + "@floating-ui/react-dom": "^2.1.2", + "@liveblocks/client": "2.11.0", + "@liveblocks/core": "2.11.0", + "@liveblocks/react": "2.11.0", + "@liveblocks/react-ui": "2.11.0", + "@liveblocks/yjs": "2.11.0", + "@tiptap/core": "^2.7.2", + "@tiptap/react": "^2.7.2", + "@tiptap/suggestion": "^2.7.2", + "use-sync-external-store": "^1.2.2", + "y-prosemirror": "^1.2.12", + "yjs": "^13.6.18" + }, + "peerDependencies": { + "@tiptap/extension-collaboration": "^2.7.2", + "@tiptap/extension-collaboration-cursor": "^2.7.2", + "@tiptap/pm": "^2.7.2", + "@tiptap/react": "^2.7.2", + "@tiptap/suggestion": "^2.7.2", + "react": "^16.14.0 || ^17 || ^18", + "react-dom": "^16.14.0 || ^17 || ^18" + } + }, + "node_modules/@liveblocks/react-ui": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@liveblocks/react-ui/-/react-ui-2.11.0.tgz", + "integrity": "sha512-aBrgLWclSoV+3tOXxf45pL6Uaf8mRvHTmda79kwyCB/8yjqoeqNe2PThcELL8WWyaZsl7XU6Bgjpat3zY5rRHQ==", + "dependencies": { + "@floating-ui/react-dom": "^2.1.2", + "@liveblocks/client": "2.11.0", + "@liveblocks/core": "2.11.0", + "@liveblocks/react": "2.11.0", + "@radix-ui/react-dropdown-menu": "^2.1.2", + "@radix-ui/react-popover": "^1.1.2", + "@radix-ui/react-slot": "^1.1.0", + "@radix-ui/react-toggle": "^1.1.0", + "@radix-ui/react-tooltip": "^1.1.3", + "react-virtuoso": "^4.12.0", + "slate": "^0.110.2", + "slate-history": "^0.110.3", + "slate-hyperscript": "^0.100.0", + "slate-react": "^0.110.3", + "use-sync-external-store": "^1.2.2" + }, + "peerDependencies": { + "react": "^16.14.0 || ^17 || ^18 || ^19 || ^19.0.0-rc" + } + }, + "node_modules/@liveblocks/react-ui/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz", + "integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@liveblocks/react-ui/node_modules/@radix-ui/react-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", + "integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } }, "node_modules/@liveblocks/yjs": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/@liveblocks/yjs/-/yjs-1.12.0.tgz", - "integrity": "sha512-jCGUlfh8hW2Cr5jRrx/c/dBaY3u6psIIVPETDO91AvKgeVld50I7tcYGjFARWwaOYMhSD3c57iAsOjEkGIvpHA==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@liveblocks/yjs/-/yjs-2.11.0.tgz", + "integrity": "sha512-o9RfjrVUMW4htHvHGwXxptZxnjC+evEvEkBqS2uPhOBBtOSMe15rWqzhoHqRXngkMUs8BIZ0EOkM5a0qsFIF0w==", "dependencies": { - "@liveblocks/client": "1.12.0", - "@liveblocks/core": "1.12.0", - "js-base64": "^3.7.5" + "@liveblocks/client": "2.11.0", + "@liveblocks/core": "2.11.0", + "js-base64": "^3.7.7" }, "peerDependencies": { "yjs": "^13.6.1" @@ -7591,24 +7693,95 @@ } }, "node_modules/@radix-ui/react-dropdown-menu": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.0.6.tgz", - "integrity": "sha512-i6TuFOoWmLWq+M/eCLGd/bQ2HfAX1RJgvrBQ6AQLmzfvsLdefxbWu8G9zczcPFfcSPehz9GcpF6K9QYreFV8hA==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.2.tgz", + "integrity": "sha512-GVZMR+eqK8/Kes0a36Qrv+i20bAPXSn8rCBTHx30w+3ECnR5o3xixAlqcVaYvLeyKUsm0aqyhWfmUcqufM8nYA==", "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/primitive": "1.0.1", - "@radix-ui/react-compose-refs": "1.0.1", - "@radix-ui/react-context": "1.0.1", - "@radix-ui/react-id": "1.0.1", - "@radix-ui/react-menu": "2.0.6", - "@radix-ui/react-primitive": "1.0.3", - "@radix-ui/react-use-controllable-state": "1.0.1" + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-menu": "2.1.2", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-controllable-state": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", + "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==" + }, + "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz", + "integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-context": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz", + "integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-id": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz", + "integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", + "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", + "dependencies": { + "@radix-ui/react-slot": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { @@ -7619,6 +7792,68 @@ } } }, + "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", + "integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz", + "integrity": "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz", + "integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz", + "integrity": "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-focus-guards": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.1.tgz", @@ -7703,35 +7938,34 @@ } }, "node_modules/@radix-ui/react-menu": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.0.6.tgz", - "integrity": "sha512-BVkFLS+bUC8HcImkRKPSiVumA1VPOOEC5WBMiT+QAVsPzW1FJzI9KnqgGxVDPBcql5xXrHkD3JOVoXWEXD8SYA==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/primitive": "1.0.1", - "@radix-ui/react-collection": "1.0.3", - "@radix-ui/react-compose-refs": "1.0.1", - "@radix-ui/react-context": "1.0.1", - "@radix-ui/react-direction": "1.0.1", - "@radix-ui/react-dismissable-layer": "1.0.5", - "@radix-ui/react-focus-guards": "1.0.1", - "@radix-ui/react-focus-scope": "1.0.4", - "@radix-ui/react-id": "1.0.1", - "@radix-ui/react-popper": "1.1.3", - "@radix-ui/react-portal": "1.0.4", - "@radix-ui/react-presence": "1.0.1", - "@radix-ui/react-primitive": "1.0.3", - "@radix-ui/react-roving-focus": "1.0.4", - "@radix-ui/react-slot": "1.0.2", - "@radix-ui/react-use-callback-ref": "1.0.1", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.2.tgz", + "integrity": "sha512-lZ0R4qR2Al6fZ4yCCZzu/ReTFrylHFxIqy7OezIpWF4bL0o9biKo0pFIvkaew3TyZ9Fy5gYVrR5zCGZBVbO1zg==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-collection": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-dismissable-layer": "1.1.1", + "@radix-ui/react-focus-guards": "1.1.1", + "@radix-ui/react-focus-scope": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-popper": "1.2.0", + "@radix-ui/react-portal": "1.1.2", + "@radix-ui/react-presence": "1.1.1", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-roving-focus": "1.1.0", + "@radix-ui/react-slot": "1.1.0", + "@radix-ui/react-use-callback-ref": "1.1.0", "aria-hidden": "^1.1.1", - "react-remove-scroll": "2.5.5" + "react-remove-scroll": "2.6.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { @@ -7742,57 +7976,48 @@ } } }, - "node_modules/@radix-ui/react-menu/node_modules/react-remove-scroll": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz", - "integrity": "sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==", + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", + "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==" + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-arrow": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.0.tgz", + "integrity": "sha512-FmlW1rCg7hBpEBwFbjHwCW6AmWLQM6g/v0Sn8XbP9NvmSZ2San1FpQeyPtufzOMSIx7Y4dzjlHoifhp+7NkZhw==", "dependencies": { - "react-remove-scroll-bar": "^2.3.3", - "react-style-singleton": "^2.2.1", - "tslib": "^2.1.0", - "use-callback-ref": "^1.3.0", - "use-sidecar": "^1.1.2" - }, - "engines": { - "node": ">=10" + "@radix-ui/react-primitive": "2.0.0" }, "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { "optional": true + }, + "@types/react-dom": { + "optional": true } } }, - "node_modules/@radix-ui/react-popover": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.0.7.tgz", - "integrity": "sha512-shtvVnlsxT6faMnK/a7n0wptwBD23xc1Z5mdrtKLwVEfsEMXodS0r5s0/g5P0hX//EKYZS2sxUjqfzlg52ZSnQ==", + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-collection": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.0.tgz", + "integrity": "sha512-GZsZslMJEyo1VKm5L1ZJY8tGDxZNPAoUeQUIbKeJfoi7Q4kmig5AsgLMYYuyYbfjd8fBmFORAIwYAkXMnXZgZw==", "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/primitive": "1.0.1", - "@radix-ui/react-compose-refs": "1.0.1", - "@radix-ui/react-context": "1.0.1", - "@radix-ui/react-dismissable-layer": "1.0.5", - "@radix-ui/react-focus-guards": "1.0.1", - "@radix-ui/react-focus-scope": "1.0.4", - "@radix-ui/react-id": "1.0.1", - "@radix-ui/react-popper": "1.1.3", - "@radix-ui/react-portal": "1.0.4", - "@radix-ui/react-presence": "1.0.1", - "@radix-ui/react-primitive": "1.0.3", - "@radix-ui/react-slot": "1.0.2", - "@radix-ui/react-use-controllable-state": "1.0.1", - "aria-hidden": "^1.1.1", - "react-remove-scroll": "2.5.5" + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-slot": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { @@ -7803,23 +8028,41 @@ } } }, - "node_modules/@radix-ui/react-popover/node_modules/react-remove-scroll": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz", - "integrity": "sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==", - "dependencies": { - "react-remove-scroll-bar": "^2.3.3", - "react-style-singleton": "^2.2.1", - "tslib": "^2.1.0", - "use-callback-ref": "^1.3.0", - "use-sidecar": "^1.1.2" + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-context": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.0.tgz", + "integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, - "engines": { - "node": ">=10" + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz", + "integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-context": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz", + "integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==", "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { @@ -7827,28 +8070,1244 @@ } } }, - "node_modules/@radix-ui/react-popper": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.1.3.tgz", - "integrity": "sha512-cKpopj/5RHZWjrbF2846jBNacjQVwkP068DfmgrNJXpvVWrOvlAmE9xSiy5OqeE+Gi8D9fP+oDhUnPqNMY8/5w==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@floating-ui/react-dom": "^2.0.0", - "@radix-ui/react-arrow": "1.0.3", - "@radix-ui/react-compose-refs": "1.0.1", - "@radix-ui/react-context": "1.0.1", + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-direction": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz", + "integrity": "sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.1.tgz", + "integrity": "sha512-QSxg29lfr/xcev6kSz7MAlmDnzbP1eI/Dwn3Tp1ip0KT5CUELsxkekFEMVBEoykI3oV39hKT4TKZzBNMbcTZYQ==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-escape-keydown": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.1.tgz", + "integrity": "sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.0.tgz", + "integrity": "sha512-200UD8zylvEyL8Bx+z76RJnASR2gRMuxlgFCPAe/Q/679a/r0eK3MBVYMb7vZODZcffZBdob1EGnky78xmVvcA==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-id": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz", + "integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-popper": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.0.tgz", + "integrity": "sha512-ZnRMshKF43aBxVWPWvbj21+7TQCvhuULWJ4gNIKYpRlQt5xGRhLx66tMp8pya2UkGHTSlhpXwmjqltDYHhw7Vg==", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0", + "@radix-ui/react-use-rect": "1.1.0", + "@radix-ui/react-use-size": "1.1.0", + "@radix-ui/rect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-popper/node_modules/@radix-ui/react-context": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.0.tgz", + "integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-portal": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.2.tgz", + "integrity": "sha512-WeDYLGPxJb/5EGBoedyJbT0MpoULmwnIPMJMSldkuiMsBAv7N1cRdsTWZWht9vpPOiN3qyiGAtbK2is47/uMFg==", + "dependencies": { + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-presence": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.1.tgz", + "integrity": "sha512-IeFXVi4YS1K0wVZzXNrbaaUvIJ3qdY+/Ih4eHFhWA9SwGR9UDX7Ck8abvL57C4cv3wwMvUE0OG69Qc3NCcTe/A==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", + "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", + "dependencies": { + "@radix-ui/react-slot": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.0.tgz", + "integrity": "sha512-EA6AMGeq9AEeQDeSH0aZgG198qkfHSbvWTf1HvoDmOB5bBG/qTxjYMWUKMnYiV6J/iP/J8MEFSuB2zRU2n7ODA==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-collection": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-context": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.0.tgz", + "integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", + "integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz", + "integrity": "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz", + "integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.0.tgz", + "integrity": "sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz", + "integrity": "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-use-rect": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz", + "integrity": "sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==", + "dependencies": { + "@radix-ui/rect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-use-size": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.0.tgz", + "integrity": "sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/rect": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.0.tgz", + "integrity": "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==" + }, + "node_modules/@radix-ui/react-popover": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.2.tgz", + "integrity": "sha512-u2HRUyWW+lOiA2g0Le0tMmT55FGOEWHwPFt1EPfbLly7uXQExFo5duNKqG2DzmFXIdqOeNd+TpE8baHWJCyP9w==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.1", + "@radix-ui/react-focus-guards": "1.1.1", + "@radix-ui/react-focus-scope": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-popper": "1.2.0", + "@radix-ui/react-portal": "1.1.2", + "@radix-ui/react-presence": "1.1.1", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-slot": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.6.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", + "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==" + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-arrow": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.0.tgz", + "integrity": "sha512-FmlW1rCg7hBpEBwFbjHwCW6AmWLQM6g/v0Sn8XbP9NvmSZ2San1FpQeyPtufzOMSIx7Y4dzjlHoifhp+7NkZhw==", + "dependencies": { + "@radix-ui/react-primitive": "2.0.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz", + "integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-context": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz", + "integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.1.tgz", + "integrity": "sha512-QSxg29lfr/xcev6kSz7MAlmDnzbP1eI/Dwn3Tp1ip0KT5CUELsxkekFEMVBEoykI3oV39hKT4TKZzBNMbcTZYQ==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-escape-keydown": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.1.tgz", + "integrity": "sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.0.tgz", + "integrity": "sha512-200UD8zylvEyL8Bx+z76RJnASR2gRMuxlgFCPAe/Q/679a/r0eK3MBVYMb7vZODZcffZBdob1EGnky78xmVvcA==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-id": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz", + "integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-popper": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.0.tgz", + "integrity": "sha512-ZnRMshKF43aBxVWPWvbj21+7TQCvhuULWJ4gNIKYpRlQt5xGRhLx66tMp8pya2UkGHTSlhpXwmjqltDYHhw7Vg==", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0", + "@radix-ui/react-use-rect": "1.1.0", + "@radix-ui/react-use-size": "1.1.0", + "@radix-ui/rect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-popper/node_modules/@radix-ui/react-context": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.0.tgz", + "integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-portal": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.2.tgz", + "integrity": "sha512-WeDYLGPxJb/5EGBoedyJbT0MpoULmwnIPMJMSldkuiMsBAv7N1cRdsTWZWht9vpPOiN3qyiGAtbK2is47/uMFg==", + "dependencies": { + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-presence": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.1.tgz", + "integrity": "sha512-IeFXVi4YS1K0wVZzXNrbaaUvIJ3qdY+/Ih4eHFhWA9SwGR9UDX7Ck8abvL57C4cv3wwMvUE0OG69Qc3NCcTe/A==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", + "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", + "dependencies": { + "@radix-ui/react-slot": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", + "integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz", + "integrity": "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz", + "integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.0.tgz", + "integrity": "sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz", + "integrity": "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-use-rect": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz", + "integrity": "sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==", + "dependencies": { + "@radix-ui/rect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-use-size": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.0.tgz", + "integrity": "sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/rect": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.0.tgz", + "integrity": "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==" + }, + "node_modules/@radix-ui/react-popper": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.1.3.tgz", + "integrity": "sha512-cKpopj/5RHZWjrbF2846jBNacjQVwkP068DfmgrNJXpvVWrOvlAmE9xSiy5OqeE+Gi8D9fP+oDhUnPqNMY8/5w==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.0.3", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-use-callback-ref": "1.0.1", + "@radix-ui/react-use-layout-effect": "1.0.1", + "@radix-ui/react-use-rect": "1.0.1", + "@radix-ui/react-use-size": "1.0.1", + "@radix-ui/rect": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.0.4.tgz", + "integrity": "sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-primitive": "1.0.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.0.1.tgz", + "integrity": "sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-use-layout-effect": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.3.tgz", + "integrity": "sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-slot": "1.0.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.0.4.tgz", + "integrity": "sha512-2mUg5Mgcu001VkGy+FfzZyzbmuUWzgWkj3rvv4yu+mLw03+mTzbxZHvfcGyFp2b8EkQeMkpRQ5FiA2Vr2O6TeQ==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-collection": "1.0.3", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-direction": "1.0.1", + "@radix-ui/react-id": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-use-callback-ref": "1.0.1", + "@radix-ui/react-use-controllable-state": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.0.0.tgz", + "integrity": "sha512-RH5b7af4oHtkcHS7pG6Sgv5rk5Wxa7XI8W5gvB1N/yiuDGZxko1ynvOiVhFM7Cis2A8zxF9bTOUVbRDzPepe6w==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/number": "1.0.1", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-collection": "1.0.3", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-direction": "1.0.1", + "@radix-ui/react-dismissable-layer": "1.0.5", + "@radix-ui/react-focus-guards": "1.0.1", + "@radix-ui/react-focus-scope": "1.0.4", + "@radix-ui/react-id": "1.0.1", + "@radix-ui/react-popper": "1.1.3", + "@radix-ui/react-portal": "1.0.4", "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-slot": "1.0.2", "@radix-ui/react-use-callback-ref": "1.0.1", + "@radix-ui/react-use-controllable-state": "1.0.1", "@radix-ui/react-use-layout-effect": "1.0.1", - "@radix-ui/react-use-rect": "1.0.1", - "@radix-ui/react-use-size": "1.0.1", - "@radix-ui/rect": "1.0.1" + "@radix-ui/react-use-previous": "1.0.1", + "@radix-ui/react-visually-hidden": "1.0.3", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.5.5" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/react-remove-scroll": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz", + "integrity": "sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==", + "dependencies": { + "react-remove-scroll-bar": "^2.3.3", + "react-style-singleton": "^2.2.1", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.0", + "use-sidecar": "^1.1.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz", + "integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.0.4.tgz", + "integrity": "sha512-egZfYY/+wRNCflXNHx+dePvnz9FbmssDTJBtgRfDY7e8SE5oIo3Py2eCB1ckAbh1Q7cQ/6yJZThJ++sgbxibog==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-direction": "1.0.1", + "@radix-ui/react-id": "1.0.1", + "@radix-ui/react-presence": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-roving-focus": "1.0.4", + "@radix-ui/react-use-controllable-state": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.0.tgz", + "integrity": "sha512-gwoxaKZ0oJ4vIgzsfESBuSgJNdc0rv12VhHgcqN0TEJmmZixXG/2XpsLK8kzNWYcnaoRIEEQc0bEi3dIvdUpjw==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-controllable-state": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle/node_modules/@radix-ui/primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", + "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==" + }, + "node_modules/@radix-ui/react-toggle/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz", + "integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle/node_modules/@radix-ui/react-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", + "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", + "dependencies": { + "@radix-ui/react-slot": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle/node_modules/@radix-ui/react-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", + "integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle/node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz", + "integrity": "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle/node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz", + "integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.1.3.tgz", + "integrity": "sha512-Z4w1FIS0BqVFI2c1jZvb/uDVJijJjJ2ZMuPV81oVgTZ7g3BZxobplnMVvXtFWgtozdvYJ+MFWtwkM5S2HnAong==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.1", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-popper": "1.2.0", + "@radix-ui/react-portal": "1.1.2", + "@radix-ui/react-presence": "1.1.1", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-slot": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-visually-hidden": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", + "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==" + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-arrow": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.0.tgz", + "integrity": "sha512-FmlW1rCg7hBpEBwFbjHwCW6AmWLQM6g/v0Sn8XbP9NvmSZ2San1FpQeyPtufzOMSIx7Y4dzjlHoifhp+7NkZhw==", + "dependencies": { + "@radix-ui/react-primitive": "2.0.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz", + "integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-context": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz", + "integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.1.tgz", + "integrity": "sha512-QSxg29lfr/xcev6kSz7MAlmDnzbP1eI/Dwn3Tp1ip0KT5CUELsxkekFEMVBEoykI3oV39hKT4TKZzBNMbcTZYQ==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-escape-keydown": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { @@ -7859,43 +9318,44 @@ } } }, - "node_modules/@radix-ui/react-portal": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.0.4.tgz", - "integrity": "sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q==", + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-id": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz", + "integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==", "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-primitive": "1.0.3" + "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { "optional": true - }, - "@types/react-dom": { - "optional": true } } }, - "node_modules/@radix-ui/react-presence": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.0.1.tgz", - "integrity": "sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==", + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-popper": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.0.tgz", + "integrity": "sha512-ZnRMshKF43aBxVWPWvbj21+7TQCvhuULWJ4gNIKYpRlQt5xGRhLx66tMp8pya2UkGHTSlhpXwmjqltDYHhw7Vg==", "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-compose-refs": "1.0.1", - "@radix-ui/react-use-layout-effect": "1.0.1" + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0", + "@radix-ui/react-use-rect": "1.1.0", + "@radix-ui/react-use-size": "1.1.0", + "@radix-ui/rect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { @@ -7906,19 +9366,33 @@ } } }, - "node_modules/@radix-ui/react-primitive": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.3.tgz", - "integrity": "sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==", + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-popper/node_modules/@radix-ui/react-context": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.0.tgz", + "integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-portal": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.2.tgz", + "integrity": "sha512-WeDYLGPxJb/5EGBoedyJbT0MpoULmwnIPMJMSldkuiMsBAv7N1cRdsTWZWht9vpPOiN3qyiGAtbK2is47/uMFg==", "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-slot": "1.0.2" + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { @@ -7929,27 +9403,19 @@ } } }, - "node_modules/@radix-ui/react-roving-focus": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.0.4.tgz", - "integrity": "sha512-2mUg5Mgcu001VkGy+FfzZyzbmuUWzgWkj3rvv4yu+mLw03+mTzbxZHvfcGyFp2b8EkQeMkpRQ5FiA2Vr2O6TeQ==", + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-presence": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.1.tgz", + "integrity": "sha512-IeFXVi4YS1K0wVZzXNrbaaUvIJ3qdY+/Ih4eHFhWA9SwGR9UDX7Ck8abvL57C4cv3wwMvUE0OG69Qc3NCcTe/A==", "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/primitive": "1.0.1", - "@radix-ui/react-collection": "1.0.3", - "@radix-ui/react-compose-refs": "1.0.1", - "@radix-ui/react-context": "1.0.1", - "@radix-ui/react-direction": "1.0.1", - "@radix-ui/react-id": "1.0.1", - "@radix-ui/react-primitive": "1.0.3", - "@radix-ui/react-use-callback-ref": "1.0.1", - "@radix-ui/react-use-controllable-state": "1.0.1" + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { @@ -7960,39 +9426,18 @@ } } }, - "node_modules/@radix-ui/react-select": { + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-primitive": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.0.0.tgz", - "integrity": "sha512-RH5b7af4oHtkcHS7pG6Sgv5rk5Wxa7XI8W5gvB1N/yiuDGZxko1ynvOiVhFM7Cis2A8zxF9bTOUVbRDzPepe6w==", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", + "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/number": "1.0.1", - "@radix-ui/primitive": "1.0.1", - "@radix-ui/react-collection": "1.0.3", - "@radix-ui/react-compose-refs": "1.0.1", - "@radix-ui/react-context": "1.0.1", - "@radix-ui/react-direction": "1.0.1", - "@radix-ui/react-dismissable-layer": "1.0.5", - "@radix-ui/react-focus-guards": "1.0.1", - "@radix-ui/react-focus-scope": "1.0.4", - "@radix-ui/react-id": "1.0.1", - "@radix-ui/react-popper": "1.1.3", - "@radix-ui/react-portal": "1.0.4", - "@radix-ui/react-primitive": "1.0.3", - "@radix-ui/react-slot": "1.0.2", - "@radix-ui/react-use-callback-ref": "1.0.1", - "@radix-ui/react-use-controllable-state": "1.0.1", - "@radix-ui/react-use-layout-effect": "1.0.1", - "@radix-ui/react-use-previous": "1.0.1", - "@radix-ui/react-visually-hidden": "1.0.3", - "aria-hidden": "^1.1.1", - "react-remove-scroll": "2.5.5" + "@radix-ui/react-slot": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { @@ -8003,23 +9448,30 @@ } } }, - "node_modules/@radix-ui/react-select/node_modules/react-remove-scroll": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz", - "integrity": "sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==", + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", + "integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==", "dependencies": { - "react-remove-scroll-bar": "^2.3.3", - "react-style-singleton": "^2.2.1", - "tslib": "^2.1.0", - "use-callback-ref": "^1.3.0", - "use-sidecar": "^1.1.2" + "@radix-ui/react-compose-refs": "1.1.0" }, - "engines": { - "node": ">=10" + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz", + "integrity": "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==", "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { @@ -8027,17 +9479,16 @@ } } }, - "node_modules/@radix-ui/react-slot": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz", - "integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==", + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz", + "integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==", "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-compose-refs": "1.0.1" + "@radix-ui/react-use-callback-ref": "1.1.0" }, "peerDependencies": { "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { @@ -8045,85 +9496,83 @@ } } }, - "node_modules/@radix-ui/react-tabs": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.0.4.tgz", - "integrity": "sha512-egZfYY/+wRNCflXNHx+dePvnz9FbmssDTJBtgRfDY7e8SE5oIo3Py2eCB1ckAbh1Q7cQ/6yJZThJ++sgbxibog==", + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.0.tgz", + "integrity": "sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==", "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/primitive": "1.0.1", - "@radix-ui/react-context": "1.0.1", - "@radix-ui/react-direction": "1.0.1", - "@radix-ui/react-id": "1.0.1", - "@radix-ui/react-presence": "1.0.1", - "@radix-ui/react-primitive": "1.0.3", - "@radix-ui/react-roving-focus": "1.0.4", - "@radix-ui/react-use-controllable-state": "1.0.1" + "@radix-ui/react-use-callback-ref": "1.1.0" }, "peerDependencies": { "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { "optional": true - }, - "@types/react-dom": { + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz", + "integrity": "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { "optional": true } } }, - "node_modules/@radix-ui/react-toggle": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.0.3.tgz", - "integrity": "sha512-Pkqg3+Bc98ftZGsl60CLANXQBBQ4W3mTFS9EJvNxKMZ7magklKV69/id1mlAlOFDDfHvlCms0fx8fA4CMKDJHg==", + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-use-rect": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz", + "integrity": "sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==", "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/primitive": "1.0.1", - "@radix-ui/react-primitive": "1.0.3", - "@radix-ui/react-use-controllable-state": "1.0.1" + "@radix-ui/rect": "1.1.0" }, "peerDependencies": { "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { "optional": true - }, - "@types/react-dom": { + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-use-size": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.0.tgz", + "integrity": "sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { "optional": true } } }, - "node_modules/@radix-ui/react-tooltip": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.0.7.tgz", - "integrity": "sha512-lPh5iKNFVQ/jav/j6ZrWq3blfDJ0OH9R6FlNUHPMqdLuQ9vwDgFsRxvl8b7Asuy5c8xmoojHUxKHQSOAvMHxyw==", + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-visually-hidden": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.0.tgz", + "integrity": "sha512-N8MDZqtgCgG5S3aV60INAB475osJousYpZ4cTJ2cFbMpdHS5Y6loLTH8LPtkj2QN0x93J30HT/M3qJXM0+lyeQ==", "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/primitive": "1.0.1", - "@radix-ui/react-compose-refs": "1.0.1", - "@radix-ui/react-context": "1.0.1", - "@radix-ui/react-dismissable-layer": "1.0.5", - "@radix-ui/react-id": "1.0.1", - "@radix-ui/react-popper": "1.1.3", - "@radix-ui/react-portal": "1.0.4", - "@radix-ui/react-presence": "1.0.1", - "@radix-ui/react-primitive": "1.0.3", - "@radix-ui/react-slot": "1.0.2", - "@radix-ui/react-use-controllable-state": "1.0.1", - "@radix-ui/react-visually-hidden": "1.0.3" + "@radix-ui/react-primitive": "2.0.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { @@ -8134,6 +9583,11 @@ } } }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/rect": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.0.tgz", + "integrity": "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==" + }, "node_modules/@radix-ui/react-use-callback-ref": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz", @@ -9627,9 +11081,9 @@ } }, "node_modules/@tiptap/core": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.7.1.tgz", - "integrity": "sha512-/sOJ3J2OWxQrho6MWgE9xaRBln5MC4BEuevTYIGia4zrc523lX9s+h/lUeLtCPhI0+J6z9Vz+v3G/uoEqWCL+A==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.9.1.tgz", + "integrity": "sha512-tifnLL/ARzQ6/FGEJjVwj9UT3v+pENdWHdk9x6F3X0mB1y0SeCjV21wpFLYESzwNdBPAj8NMp8Behv7dBnhIfw==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -9651,9 +11105,9 @@ } }, "node_modules/@tiptap/extension-bubble-menu": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.7.1.tgz", - "integrity": "sha512-ZQh2Q2bAu61Z249b8eRLMKk0WU2ILvUz9JM9uxjxXaGE9L8nQbv0Pc5sZxIecOKmdX9N5Nq6mSoh/kD+klUOzA==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.9.1.tgz", + "integrity": "sha512-DWUF6NG08/bZDWw0jCeotSTvpkyqZTi4meJPomG9Wzs/Ol7mEwlNCsCViD999g0+IjyXFatBk4DfUq1YDDu++Q==", "dependencies": { "tippy.js": "^6.3.7" }, @@ -9679,9 +11133,9 @@ } }, "node_modules/@tiptap/extension-collaboration": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@tiptap/extension-collaboration/-/extension-collaboration-2.7.1.tgz", - "integrity": "sha512-u6eDKDuzJ9Fd9ZPul7wAnSzqaq+ovRaVsDRYrNDYUYT2QlbWJtI8HKE/5Mt1Ml0Xnpwn3UcLwJdo0iN4/A4IOQ==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-collaboration/-/extension-collaboration-2.9.1.tgz", + "integrity": "sha512-AaS66O4X0rx1WLnCC3LW5fLk7ZULr0t7/gj/7dNgbBtyg4NhoYc3PhSBiimLbgD+R8wFrfm1lsHb02deaqQB6w==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -9693,9 +11147,9 @@ } }, "node_modules/@tiptap/extension-collaboration-cursor": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@tiptap/extension-collaboration-cursor/-/extension-collaboration-cursor-2.7.1.tgz", - "integrity": "sha512-aYVuztLFTFfoh+bQMQV+f/+iTrq6L99YJF+TRW4CfnD6gsThX3fk4tbGXWVoE8Zd6SQMAjote4TZZBwGbANtdg==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-collaboration-cursor/-/extension-collaboration-cursor-2.9.1.tgz", + "integrity": "sha512-mbNYEdlp22UpKY0SULVWRvOm3U19V80DokC+iDIDZXGXI2nCevB4vMQVkXmiQtiz3qT9I9mIN0vSgy3w75A7WA==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -9706,9 +11160,9 @@ } }, "node_modules/@tiptap/extension-floating-menu": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-2.7.1.tgz", - "integrity": "sha512-t6nANJPDudo8lOTN2zxXUBqMK8K8ZZgGfC8j3vVbOzLCLEgffU1ZX6YfYqOWPRBua0TXLJPZnGD6nff4pxzG5g==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-2.9.1.tgz", + "integrity": "sha512-MxZ7acNNsoNaKpetxfwi3Z11Bgrh0T2EJlCV77v9N1vWK38+st3H1WJanmLbPNtc2ocvhHJrz+DjDz3CWxQ9rQ==", "dependencies": { "tippy.js": "^6.3.7" }, @@ -9885,9 +11339,9 @@ } }, "node_modules/@tiptap/pm": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.7.1.tgz", - "integrity": "sha512-gG++eBQu9SObWCmxZDv6tkwFHVmbg7phowy0F7Nihq9Um7/oae5Ag9skfiG8GG9eYdw54paEAY/MP+tE3x/smA==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.9.1.tgz", + "integrity": "sha512-mvV86fr7kEuDYEApQ2uMPCKL2uagUE0BsXiyyz3KOkY1zifyVm1fzdkscb24Qy1GmLzWAIIihA+3UHNRgYdOlQ==", "dependencies": { "prosemirror-changeset": "^2.2.1", "prosemirror-collab": "^1.3.1", @@ -9906,7 +11360,7 @@ "prosemirror-tables": "^1.4.0", "prosemirror-trailing-node": "^3.0.0", "prosemirror-transform": "^1.10.0", - "prosemirror-view": "^1.33.10" + "prosemirror-view": "^1.34.3" }, "funding": { "type": "github", @@ -9914,12 +11368,12 @@ } }, "node_modules/@tiptap/react": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@tiptap/react/-/react-2.7.1.tgz", - "integrity": "sha512-FlH40q0hGNkSO3uE3hvBYYcVEoWvUpolwB6J76P+WAjUl+sPnw5ARtQ4eYnDtYi+ehnVJPoRh8ifnBMXLV1nDA==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/react/-/react-2.9.1.tgz", + "integrity": "sha512-LQJ34ZPfXtJF36SZdcn4Fiwsl2WxZ9YRJI87OLnsjJ45O+gV/PfBzz/4ap+LF8LOS0AbbGhTTjBOelPoNm+aYA==", "dependencies": { - "@tiptap/extension-bubble-menu": "^2.7.1", - "@tiptap/extension-floating-menu": "^2.7.1", + "@tiptap/extension-bubble-menu": "^2.9.1", + "@tiptap/extension-floating-menu": "^2.9.1", "@types/use-sync-external-store": "^0.0.6", "fast-deep-equal": "^3", "use-sync-external-store": "^1.2.2" @@ -9935,6 +11389,19 @@ "react-dom": "^17.0.0 || ^18.0.0" } }, + "node_modules/@tiptap/suggestion": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/suggestion/-/suggestion-2.9.1.tgz", + "integrity": "sha512-MMxwpbtocxUsbmc8qtFY1AQYNTW5i/M4aNSv9zsKKRISaS5hMD7XVrw2eod0x0yEqZU3izLiPDZPmgr8glF+jQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0", + "@tiptap/pm": "^2.7.0" + } + }, "node_modules/@tootallnate/once": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", @@ -14209,6 +15676,18 @@ "node": ">=8" } }, + "node_modules/direction": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/direction/-/direction-1.0.4.tgz", + "integrity": "sha512-GYqKi1aH7PJXxdhTeZBFrg8vUBeKXi+cNprXsC1kpJcbcVnV9wBsrOu1cQEdG0WeQwlfHiy3XvnKfIrJ2R0NzQ==", + "bin": { + "direction": "cli.js" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/dlv": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", @@ -17698,6 +19177,15 @@ "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" }, + "node_modules/immer": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz", + "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -18227,6 +19715,11 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/is-hotkey": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/is-hotkey/-/is-hotkey-0.2.0.tgz", + "integrity": "sha512-UknnZK4RakDmTgz4PI1wIph5yxSs/mvChWs9ifnlXsKuXgWmOkY/hAE0H/k2MIqH0RlRye0i1oC07MCRSD28Mw==" + }, "node_modules/is-inside-container": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", @@ -19213,9 +20706,9 @@ } }, "node_modules/lib0": { - "version": "0.2.94", - "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.94.tgz", - "integrity": "sha512-hZ3p54jL4Wpu7IOg26uC7dnEWiMyNlUrb9KoG7+xYs45WkQwpVvKFndVq2+pqLYKe1u8Fp3+zAfZHVvTK34PvQ==", + "version": "0.2.98", + "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.98.tgz", + "integrity": "sha512-XteTiNO0qEXqqweWx+b21p/fBnNHUA1NwAtJNJek1oPrewEZs2uiT4gWivHKr9GqCjDPAhchz0UQO8NwU3bBNA==", "dependencies": { "isomorphic.js": "^0.2.4" }, @@ -24769,9 +26262,9 @@ } }, "node_modules/prosemirror-view": { - "version": "1.34.2", - "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.34.2.tgz", - "integrity": "sha512-tPX/V2Xd70vrAGQ/V9CppJtPKnQyQMypJGlLylvdI94k6JaG+4P6fVmXPR1zc1eVTW0gq3c6zsfqwJKCRLaG9Q==", + "version": "1.36.0", + "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.36.0.tgz", + "integrity": "sha512-U0GQd5yFvV5qUtT41X1zCQfbw14vkbbKwLlQXhdylEmgpYVHkefXYcC4HHwWOfZa3x6Y8wxDLUBv7dxN5XQ3nA==", "dependencies": { "prosemirror-model": "^1.20.0", "prosemirror-state": "^1.0.0", @@ -24987,9 +26480,9 @@ } }, "node_modules/react-remove-scroll": { - "version": "2.5.10", - "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.10.tgz", - "integrity": "sha512-m3zvBRANPBw3qxVVjEIPEQinkcwlFZ4qyomuWVpNJdv4c6MvHfXV0C3L9Jx5rr3HeBHKNRX+1jreB5QloDIJjA==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.0.tgz", + "integrity": "sha512-I2U4JVEsQenxDAKaVa3VZ/JeJZe0/2DxPWL8Tj8yLKctQJQiZM52pn/GWFpSp8dftjM3pSAHVJZscAnC/y+ySQ==", "dependencies": { "react-remove-scroll-bar": "^2.3.6", "react-style-singleton": "^2.2.1", @@ -25114,6 +26607,18 @@ "react-dom": ">=16.6.0" } }, + "node_modules/react-virtuoso": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/react-virtuoso/-/react-virtuoso-4.12.0.tgz", + "integrity": "sha512-oHrKlU7xHsrnBQ89ecZoMPAK0tHnI9s1hsFW3KKg5ZGeZ5SWvbGhg/QFJFY4XETAzoCUeu+Xaxn1OUb/PGtPlA==", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=16 || >=17 || >= 18", + "react-dom": ">=16 || >=17 || >= 18" + } + }, "node_modules/read": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", @@ -26756,6 +28261,57 @@ "node": ">=8" } }, + "node_modules/slate": { + "version": "0.110.2", + "resolved": "https://registry.npmjs.org/slate/-/slate-0.110.2.tgz", + "integrity": "sha512-4xGULnyMCiEQ0Ml7JAC1A6HVE6MNpPJU7Eq4cXh1LxlrR0dFXC3XC+rNfQtUJ7chHoPkws57x7DDiWiZAt+PBA==", + "dependencies": { + "immer": "^10.0.3", + "is-plain-object": "^5.0.0", + "tiny-warning": "^1.0.3" + } + }, + "node_modules/slate-history": { + "version": "0.110.3", + "resolved": "https://registry.npmjs.org/slate-history/-/slate-history-0.110.3.tgz", + "integrity": "sha512-sgdff4Usdflmw5ZUbhDkxFwCBQ2qlDKMMkF93w66KdV48vHOgN2BmLrf+2H8SdX8PYIpP/cTB0w8qWC2GwhDVA==", + "dependencies": { + "is-plain-object": "^5.0.0" + }, + "peerDependencies": { + "slate": ">=0.65.3" + } + }, + "node_modules/slate-hyperscript": { + "version": "0.100.0", + "resolved": "https://registry.npmjs.org/slate-hyperscript/-/slate-hyperscript-0.100.0.tgz", + "integrity": "sha512-fb2KdAYg6RkrQGlqaIi4wdqz3oa0S4zKNBJlbnJbNOwa23+9FLD6oPVx9zUGqCSIpy+HIpOeqXrg0Kzwh/Ii4A==", + "dependencies": { + "is-plain-object": "^5.0.0" + }, + "peerDependencies": { + "slate": ">=0.65.3" + } + }, + "node_modules/slate-react": { + "version": "0.110.3", + "resolved": "https://registry.npmjs.org/slate-react/-/slate-react-0.110.3.tgz", + "integrity": "sha512-AS8PPjwmsFS3Lq0MOEegLVlFoxhyos68G6zz2nW4sh3WeTXV7pX0exnwtY1a/docn+J3LGQO11aZXTenPXA/kg==", + "dependencies": { + "@juggle/resize-observer": "^3.4.0", + "direction": "^1.0.4", + "is-hotkey": "^0.2.0", + "is-plain-object": "^5.0.0", + "lodash": "^4.17.21", + "scroll-into-view-if-needed": "^3.1.0", + "tiny-invariant": "1.3.1" + }, + "peerDependencies": { + "react": ">=18.2.0", + "react-dom": ">=18.2.0", + "slate": ">=0.99.0" + } + }, "node_modules/smart-buffer": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", @@ -27607,6 +29163,16 @@ "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==" }, + "node_modules/tiny-invariant": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.1.tgz", + "integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==" + }, + "node_modules/tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" + }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -30003,11 +31569,11 @@ } }, "node_modules/yjs": { - "version": "13.6.16", - "resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.16.tgz", - "integrity": "sha512-uEq+n/dFIecBElEdeQea8nDnltScBfuhCSyAxDw4CosveP9Ag0eW6iZi2mdpW7EgxSFT7VXK2MJl3tKaLTmhAQ==", + "version": "13.6.20", + "resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.20.tgz", + "integrity": "sha512-Z2YZI+SYqK7XdWlloI3lhMiKnCdFCVC4PchpdO+mCYwtiTwncjUbnRK9R1JmkNfdmHyDXuWN3ibJAt0wsqTbLQ==", "dependencies": { - "lib0": "^0.2.86" + "lib0": "^0.2.98" }, "engines": { "node": ">=16.0.0", @@ -30869,8 +32435,11 @@ "@blocknote/xl-multi-column": "^0.19.0", "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.5", - "@liveblocks/client": "^1.10.0", - "@liveblocks/yjs": "^1.10.0", + "@liveblocks/client": "^2.11.0", + "@liveblocks/react": "^2.11.0", + "@liveblocks/react-tiptap": "^2.11.0", + "@liveblocks/react-ui": "^2.11.0", + "@liveblocks/yjs": "^2.11.0", "@mantine/core": "^7.10.1", "@mui/icons-material": "^5.16.1", "@mui/material": "^5.16.1", diff --git a/packages/core/src/api/nodeConversions/nodeToBlock.ts b/packages/core/src/api/nodeConversions/nodeToBlock.ts index 73198c1927..47af4a35f2 100644 --- a/packages/core/src/api/nodeConversions/nodeToBlock.ts +++ b/packages/core/src/api/nodeConversions/nodeToBlock.ts @@ -130,6 +130,10 @@ export function contentNodeToInlineContent< } else { const config = styleSchema[mark.type.name]; if (!config) { + if (mark.type.name === "liveblocksCommentMark") { + // TODO + continue; + } throw new Error(`style ${mark.type.name} not found in styleSchema`); } if (config.propSchema === "boolean") { diff --git a/packages/core/src/editor/BlockNoteEditor.ts b/packages/core/src/editor/BlockNoteEditor.ts index d316528aef..b1301871a0 100644 --- a/packages/core/src/editor/BlockNoteEditor.ts +++ b/packages/core/src/editor/BlockNoteEditor.ts @@ -419,7 +419,15 @@ export class BlockNoteEditor< this.resolveFileUrl = newOptions.resolveFileUrl || (async (url) => url); this.headless = newOptions._headless; - if (newOptions.collaboration && newOptions.initialContent) { + const collaborationEnabled = + !!extensions.find((ext) => ext.name === "Collaboration") || + !!newOptions._tiptapOptions?.extensions?.find( + (ext) => ext.name === "liveblocksExtension" + ); + + debugger; + + if (collaborationEnabled && newOptions.initialContent) { // eslint-disable-next-line no-console console.warn( "When using Collaboration, initialContent might cause conflicts, because changes should come from the collaboration provider" @@ -428,7 +436,7 @@ export class BlockNoteEditor< const initialContent = newOptions.initialContent || - (options.collaboration + (collaborationEnabled ? [ { type: "paragraph", @@ -500,8 +508,11 @@ export class BlockNoteEditor< * * @warning Not needed to call manually when using React, use BlockNoteView to take care of mounting */ - public mount = (parentElement?: HTMLElement | null) => { - this._tiptapEditor.mount(parentElement); + public mount = ( + parentElement?: HTMLElement | null, + contentComponent?: any + ) => { + this._tiptapEditor.mount(parentElement, contentComponent); }; public get prosemirrorView() { diff --git a/packages/core/src/editor/BlockNoteExtensions.ts b/packages/core/src/editor/BlockNoteExtensions.ts index 3c7bd94720..35ecadd843 100644 --- a/packages/core/src/editor/BlockNoteExtensions.ts +++ b/packages/core/src/editor/BlockNoteExtensions.ts @@ -91,7 +91,9 @@ export const getBlockNoteExtensions = < }, }), ...Object.values(opts.styleSpecs).map((styleSpec) => { - return styleSpec.implementation.mark; + return styleSpec.implementation.mark.configure({ + editor: opts.editor as any, + }); }), TextColorExtension, diff --git a/packages/core/src/editor/BlockNoteTipTapEditor.ts b/packages/core/src/editor/BlockNoteTipTapEditor.ts index f4505d8153..fa49f7d491 100644 --- a/packages/core/src/editor/BlockNoteTipTapEditor.ts +++ b/packages/core/src/editor/BlockNoteTipTapEditor.ts @@ -150,40 +150,41 @@ export class BlockNoteTipTapEditor extends TiptapEditor { /** * Replace the default `createView` method with a custom one - which we call on mount */ - private createViewAlternative() { + private createViewAlternative(contentComponent?: any) { this._creating = true; // Without queueMicrotask, custom IC / styles will give a React FlushSync error - queueMicrotask(() => { - if (!this._creating) { - return; + // queueMicrotask(() => { + if (!this._creating) { + return; + } + (this as any).contentComponent = contentComponent; + this.view = new EditorView( + { mount: this.options.element as any }, // use mount option so that we reuse the existing element instead of creating a new one + { + ...this.options.editorProps, + // @ts-ignore + dispatchTransaction: this.dispatchTransaction.bind(this), + state: this.state, } - this.view = new EditorView( - { mount: this.options.element as any }, // use mount option so that we reuse the existing element instead of creating a new one - { - ...this.options.editorProps, - // @ts-ignore - dispatchTransaction: this.dispatchTransaction.bind(this), - state: this.state, - } - ); + ); - // `editor.view` is not yet available at this time. - // Therefore we will add all plugins and node views directly afterwards. - const newState = this.state.reconfigure({ - plugins: this.extensionManager.plugins, - }); + // `editor.view` is not yet available at this time. + // Therefore we will add all plugins and node views directly afterwards. + const newState = this.state.reconfigure({ + plugins: this.extensionManager.plugins, + }); - this.view.updateState(newState); + this.view.updateState(newState); - this.createNodeViews(); + this.createNodeViews(); - // emit the created event, call here manually because we blocked the default call in the constructor - // (https://github.com/ueberdosis/tiptap/blob/45bac803283446795ad1b03f43d3746fa54a68ff/packages/core/src/Editor.ts#L117) - this.commands.focus(this.options.autofocus); - this.emit("create", { editor: this }); - this.isInitialized = true; - this._creating = false; - }); + // emit the created event, call here manually because we blocked the default call in the constructor + // (https://github.com/ueberdosis/tiptap/blob/45bac803283446795ad1b03f43d3746fa54a68ff/packages/core/src/Editor.ts#L117) + this.commands.focus(this.options.autofocus); + this.emit("create", { editor: this }); + this.isInitialized = true; + this._creating = false; + // }); } /** @@ -191,7 +192,7 @@ export class BlockNoteTipTapEditor extends TiptapEditor { * * @param element DOM element to mount to, ur null / undefined to destroy */ - public mount = (element?: HTMLElement | null) => { + public mount = (element?: HTMLElement | null, contentComponent?: any) => { if (!element) { this.destroy(); // cancel pending microtask @@ -199,7 +200,7 @@ export class BlockNoteTipTapEditor extends TiptapEditor { } else { this.options.element = element; // @ts-ignore - this.createViewAlternative(); + this.createViewAlternative(contentComponent); } }; } diff --git a/packages/core/src/extensions/UniqueID/UniqueID.ts b/packages/core/src/extensions/UniqueID/UniqueID.ts index c2077f094f..1310b82875 100644 --- a/packages/core/src/extensions/UniqueID/UniqueID.ts +++ b/packages/core/src/extensions/UniqueID/UniqueID.ts @@ -53,6 +53,7 @@ const UniqueID = Extension.create({ setIdAttribute: false, generateID: () => { // Use mock ID if tests are running. + debugger; if (typeof window !== "undefined" && (window as any).__TEST_OPTIONS) { const testOptions = (window as any).__TEST_OPTIONS; if (testOptions.mockID === undefined) { diff --git a/packages/react/src/editor/BlockNoteView.tsx b/packages/react/src/editor/BlockNoteView.tsx index 24733e2379..22922aec3a 100644 --- a/packages/react/src/editor/BlockNoteView.tsx +++ b/packages/react/src/editor/BlockNoteView.tsx @@ -24,7 +24,7 @@ import { BlockNoteDefaultUI, BlockNoteDefaultUIProps, } from "./BlockNoteDefaultUI.js"; -import { EditorContent } from "./EditorContent.js"; +import { Portals, getInstance } from "./EditorContent.js"; import { ElementRenderer } from "./ElementRenderer.js"; import "./styles.css"; @@ -151,11 +151,23 @@ function BlockNoteViewComponent< [editor] ); + const portalManager = useMemo(() => { + return getInstance(); + }, []); + + const mount = useCallback( + (element: HTMLElement | null) => { + editor.mount(element, portalManager); + }, + [editor, portalManager] + ); + return ( {!editor.headless && ( - + <> +
{renderChildren}
-
+ + {/* */} + )}
); diff --git a/packages/react/src/editor/EditorContent.tsx b/packages/react/src/editor/EditorContent.tsx index 1bd6c9accb..15dbc4be31 100644 --- a/packages/react/src/editor/EditorContent.tsx +++ b/packages/react/src/editor/EditorContent.tsx @@ -1,67 +1,66 @@ -import { BlockNoteEditor } from "@blocknote/core"; import { ReactRenderer } from "@tiptap/react"; -import { useEffect, useState } from "react"; +import { useSyncExternalStore } from "react"; import { createPortal } from "react-dom"; -const Portals: React.FC<{ renderers: Record }> = ({ - renderers, -}) => { - return ( - <> - {Object.entries(renderers).map(([key, renderer]) => { - return createPortal(renderer.reactElement, renderer.element, key); - })} - - ); -}; +export function getInstance() { + const subscribers = new Set<() => void>(); + let renderers: Record = {}; -/** - * Replacement of https://github.com/ueberdosis/tiptap/blob/6676c7e034a46117afdde560a1b25fe75411a21d/packages/react/src/EditorContent.tsx - * that only takes care of the Portals. - * - * Original implementation is messy, and we use a "mount" system in BlockNoteTiptapEditor.tsx that makes this cleaner - */ -export function EditorContent(props: { - editor: BlockNoteEditor; - children: any; -}) { - const [renderers, setRenderers] = useState>({}); - - useEffect(() => { - props.editor._tiptapEditor.contentComponent = { - /** - * Used by TipTap - */ - setRenderer(id: string, renderer: ReactRenderer) { - setRenderers((renderers) => ({ ...renderers, [id]: renderer })); - }, + return { + /** + * Subscribe to the editor instance's changes. + */ + subscribe(callback: () => void) { + subscribers.add(callback); + return () => { + subscribers.delete(callback); + }; + }, + getSnapshot() { + return renderers; + }, + getServerSnapshot() { + return renderers; + }, + /** + * Adds a new NodeView Renderer to the editor. + */ + setRenderer(id: string, renderer: ReactRenderer) { + renderers = { + ...renderers, + [id]: createPortal(renderer.reactElement, renderer.element, id), + }; - /** - * Used by TipTap - */ - removeRenderer(id: string) { - setRenderers((renderers) => { - const nextRenderers = { ...renderers }; + subscribers.forEach((subscriber) => subscriber()); + }, + /** + * Removes a NodeView Renderer from the editor. + */ + removeRenderer(id: string) { + const nextRenderers = { ...renderers }; - delete nextRenderers[id]; + delete nextRenderers[id]; + renderers = nextRenderers; + subscribers.forEach((subscriber) => subscriber()); + }, + }; +} - return nextRenderers; - }); - }, - }; - // Without queueMicrotask, custom IC / styles will give a React FlushSync error - queueMicrotask(() => { - props.editor._tiptapEditor.createNodeViews(); - }); - return () => { - props.editor._tiptapEditor.contentComponent = null; - }; - }, [props.editor._tiptapEditor]); +type ContentComponent = ReturnType; - return ( - <> - - {props.children} - +/** + * This component renders all of the editor's node views. + */ +export const Portals: React.FC<{ contentComponent: ContentComponent }> = ({ + contentComponent, +}) => { + // For performance reasons, we render the node view portals on state changes only + const renderers = useSyncExternalStore( + contentComponent.subscribe, + contentComponent.getSnapshot, + contentComponent.getServerSnapshot ); -} + + // This allows us to directly render the portals without any additional wrapper + return <>{Object.values(renderers)}; +}; diff --git a/packages/react/src/schema/@util/ReactRenderUtil.ts b/packages/react/src/schema/@util/ReactRenderUtil.ts index 81c4e8bae9..e81752d94e 100644 --- a/packages/react/src/schema/@util/ReactRenderUtil.ts +++ b/packages/react/src/schema/@util/ReactRenderUtil.ts @@ -25,6 +25,7 @@ export function renderToDOMSpec( // We also use this if _tiptapEditor or _tiptapEditor.contentComponent is undefined, use a temporary root. // This is actually a fallback / temporary fix, as normally this shouldn't happen (see #755). TODO: find cause root = createRoot(div); + throw new Error("No editor provided"); flushSync(() => { root!.render(fc((el) => (contentDOM = el || undefined))); }); diff --git a/packages/react/src/schema/ReactStyleSpec.tsx b/packages/react/src/schema/ReactStyleSpec.tsx index 72843d2866..23b5ce18da 100644 --- a/packages/react/src/schema/ReactStyleSpec.tsx +++ b/packages/react/src/schema/ReactStyleSpec.tsx @@ -43,9 +43,10 @@ export function createReactStyleSpec( } const Content = styleImplementation.render; + const renderResult = renderToDOMSpec( (refCB) => , - undefined + this.options.editor ); return addStyleAttributes( diff --git a/playground/package.json b/playground/package.json index cc8bc56dcf..455cba73b9 100644 --- a/playground/package.json +++ b/playground/package.json @@ -21,8 +21,11 @@ "@blocknote/xl-multi-column": "^0.19.0", "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.5", - "@liveblocks/client": "^1.10.0", - "@liveblocks/yjs": "^1.10.0", + "@liveblocks/client": "^2.11.0", + "@liveblocks/react": "^2.11.0", + "@liveblocks/react-tiptap": "^2.11.0", + "@liveblocks/react-ui": "^2.11.0", + "@liveblocks/yjs": "^2.11.0", "@mantine/core": "^7.10.1", "@mui/icons-material": "^5.16.1", "@mui/material": "^5.16.1", From c65925353b1f625f24b5f3f1a5b1842489f31314 Mon Sep 17 00:00:00 2001 From: yousefed Date: Mon, 18 Nov 2024 11:27:15 +0100 Subject: [PATCH 002/144] misc fixes --- examples/01-basic/01-minimal/App.tsx | 18 ++++++----- examples/01-basic/01-minimal/Editor.tsx | 28 ++--------------- examples/01-basic/01-minimal/Room.tsx | 30 ++++++++++++++++++- examples/01-basic/01-minimal/globals.css | 4 +++ package-lock.json | 3 ++ packages/core/src/editor/BlockNoteEditor.ts | 14 +++++---- .../core/src/extensions/UniqueID/UniqueID.ts | 1 - playground/package.json | 3 ++ playground/vite.config.ts | 29 ++++++++++++++++++ 9 files changed, 89 insertions(+), 41 deletions(-) diff --git a/examples/01-basic/01-minimal/App.tsx b/examples/01-basic/01-minimal/App.tsx index f35ac2be6f..aa59e53ca4 100644 --- a/examples/01-basic/01-minimal/App.tsx +++ b/examples/01-basic/01-minimal/App.tsx @@ -1,7 +1,9 @@ import "@blocknote/core/fonts/inter.css"; import "@blocknote/mantine/style.css"; -import "@liveblocks/react-tiptap/styles.css"; -import "@liveblocks/react-ui/styles.css"; +import "@liveblocks/react-ui/../styles.css"; + +import "@liveblocks/react-tiptap/../styles.css"; + import { Editor } from "./Editor.jsx"; import { Room } from "./Room.jsx"; import "./globals.css"; @@ -19,15 +21,15 @@ export default function App() { * * TODO: * - blocking: DOM updates - * - blocking: Extension API * - fix mount issue * - - (domelement) - * - - position out of range + * - - make with with queueMicrotask * - versioning * - automatic comment button? - * - animation performance * - Even simpler API? - * - side menu visibility when composing - * - hide composing box when clicking outside - * - users / mentions / history / notifications + * - History + * - mentions: + * -- paste handler + * -- "change" API to trigger code from document changes + * -- hook up blocknote mentions, create default mention API that adds inline content + menu */ diff --git a/examples/01-basic/01-minimal/Editor.tsx b/examples/01-basic/01-minimal/Editor.tsx index 5df71a2389..a35fe28ae1 100644 --- a/examples/01-basic/01-minimal/Editor.tsx +++ b/examples/01-basic/01-minimal/Editor.tsx @@ -11,6 +11,7 @@ import { useCreateBlockNote, } from "@blocknote/react"; import { useLiveblocksExtension } from "@liveblocks/react-tiptap"; +import { Threads } from "./Threads"; export function CustomFormattingToolbar(props: { editor: BlockNoteEditor; @@ -24,7 +25,6 @@ export function CustomFormattingToolbar(props: { { - debugger; props.editor?._tiptapEditor.chain().focus().addPendingComment().run(); }}> Comment @@ -37,38 +37,16 @@ export function Editor() { const liveblocks = useLiveblocksExtension(); const editor = useCreateBlockNote({ - _tiptapOptions: { - extensions: [liveblocks], - }, + _extensions: { liveblocksExtension: liveblocks }, disableExtensions: ["history"], }); - // const [x, setX] = useState(); - - // useEffect(() => { - // const interval = setInterval(() => { - // setX(Math.random()); - // }, 1000); - // return () => clearInterval(interval); - // }, []); - return ( - {/* {editor.prosemirrorView && editor.domElement && ( */} -
- HELLO - {/* - */} -
- {/* )} */} + } />
); } - -function Test(props: { editor: Editor }) { - debugger; - return
TEST
; -} diff --git a/examples/01-basic/01-minimal/Room.tsx b/examples/01-basic/01-minimal/Room.tsx index 679e4626e8..e25db82357 100644 --- a/examples/01-basic/01-minimal/Room.tsx +++ b/examples/01-basic/01-minimal/Room.tsx @@ -7,12 +7,40 @@ import { } from "@liveblocks/react/suspense"; import { ReactNode } from "react"; +const users = [ + { + id: "1", + name: "John Doe", + avatar: "https://liveblocks.io/avatars/avatar-1.png", + }, + { + id: "2", + name: "Alice Smith", + avatar: "https://liveblocks.io/avatars/avatar-2.png", + }, + { + id: "3", + name: "Bob Johnson", + avatar: "https://liveblocks.io/avatars/avatar-3.png", + }, +]; + export function Room({ children }: { children: ReactNode }) { return ( + } + resolveMentionSuggestions={async (args) => { + return users + .filter((user) => + user.name.toLowerCase().startsWith(args.text.toLowerCase()) + ) + .map((user) => user.id); + }} + resolveUsers={async (args) => { + return args.userIds.map((id) => users.find((user) => user.id === id)); + }}> Loading…
}> {children} diff --git a/examples/01-basic/01-minimal/globals.css b/examples/01-basic/01-minimal/globals.css index 82cdc90418..66ba5d06a7 100644 --- a/examples/01-basic/01-minimal/globals.css +++ b/examples/01-basic/01-minimal/globals.css @@ -21,3 +21,7 @@ display: none; } } + +.lb-portal { + z-index: 3000; +} diff --git a/package-lock.json b/package-lock.json index 1b3dfa1a0e..cac2dbc2f4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32443,6 +32443,9 @@ "@mantine/core": "^7.10.1", "@mui/icons-material": "^5.16.1", "@mui/material": "^5.16.1", + "@tiptap/core": "^2.7.1", + "@tiptap/react": "^2.7.1", + "@tiptap/suggestion": "^2.7.1", "@uppy/core": "^3.13.1", "@uppy/dashboard": "^3.9.1", "@uppy/drag-drop": "^3.1.1", diff --git a/packages/core/src/editor/BlockNoteEditor.ts b/packages/core/src/editor/BlockNoteEditor.ts index b0a4edd05a..b9460d91cf 100644 --- a/packages/core/src/editor/BlockNoteEditor.ts +++ b/packages/core/src/editor/BlockNoteEditor.ts @@ -434,12 +434,8 @@ export class BlockNoteEditor< this.headless = newOptions._headless; const collaborationEnabled = - !!extensions.find((ext) => ext.name === "Collaboration") || - !!newOptions._tiptapOptions?.extensions?.find( - (ext) => ext.name === "liveblocksExtension" - ); - - debugger; + "Collaboration" in this.extensions || + "liveblocksExtension" in this.extensions; if (collaborationEnabled && newOptions.initialContent) { // eslint-disable-next-line no-console @@ -482,6 +478,12 @@ export class BlockNoteEditor< return ext; } + if (!ext.plugin) { + throw new Error( + "Extension should either be a TipTap extension or a ProseMirror plugin in a plugin property" + ); + } + // "blocknote" extensions (prosemirror plugins) return Extension.create({ name: key, diff --git a/packages/core/src/extensions/UniqueID/UniqueID.ts b/packages/core/src/extensions/UniqueID/UniqueID.ts index 1310b82875..c2077f094f 100644 --- a/packages/core/src/extensions/UniqueID/UniqueID.ts +++ b/packages/core/src/extensions/UniqueID/UniqueID.ts @@ -53,7 +53,6 @@ const UniqueID = Extension.create({ setIdAttribute: false, generateID: () => { // Use mock ID if tests are running. - debugger; if (typeof window !== "undefined" && (window as any).__TEST_OPTIONS) { const testOptions = (window as any).__TEST_OPTIONS; if (testOptions.mockID === undefined) { diff --git a/playground/package.json b/playground/package.json index 0886c7a691..1ab71afed7 100644 --- a/playground/package.json +++ b/playground/package.json @@ -21,6 +21,9 @@ "@blocknote/xl-multi-column": "^0.19.1", "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.5", + "@tiptap/suggestion": "^2.7.1", + "@tiptap/core": "^2.7.1", + "@tiptap/react": "^2.7.1", "@liveblocks/client": "^2.11.0", "@liveblocks/react": "^2.11.0", "@liveblocks/react-tiptap": "^2.11.0", diff --git a/playground/vite.config.ts b/playground/vite.config.ts index 9a4362f0b5..55afac6e3c 100644 --- a/playground/vite.config.ts +++ b/playground/vite.config.ts @@ -63,6 +63,35 @@ export default defineConfig((conf) => ({ __dirname, "../packages/xl-multi-column/src/" ), + "@liveblocks/core": path.resolve( + __dirname, + "../../liveblocks/packages/liveblocks-core/src/" + ), + "@liveblocks/react": path.resolve( + __dirname, + "../../liveblocks/packages/liveblocks-react/src/" + ), + "@liveblocks/react-tiptap": path.resolve( + __dirname, + "../../liveblocks/packages/liveblocks-react-tiptap/src/" + ), + "@liveblocks/react-ui": path.resolve( + __dirname, + "../../liveblocks/packages/liveblocks-react-ui/src/" + ), + "@liveblocks/client": path.resolve( + __dirname, + "../../liveblocks/packages/liveblocks-client/src/" + ), + "@liveblocks/yjs": path.resolve( + __dirname, + "../../liveblocks/packages/liveblocks-yjs/src/" + ), + "@tiptap/suggestion": path.resolve( + __dirname, + "../node_modules/@tiptap/suggestion/" + ), + yjs: path.resolve(__dirname, "../node_modules/yjs/"), }, }, })); From a03d33f43078014efe5dc59b86d5885225a1ea26 Mon Sep 17 00:00:00 2001 From: yousefed Date: Tue, 19 Nov 2024 14:57:20 +0100 Subject: [PATCH 003/144] misc --- .../02-liveblocks/.bnexample.json | 10 ++-- .../07-collaboration/02-liveblocks/App.tsx | 49 ++++++------------ .../07-collaboration/02-liveblocks/Editor.tsx | 32 ++++++++++++ .../07-collaboration/02-liveblocks/Room.tsx | 51 +++++++++++++++++++ .../02-liveblocks/Threads.tsx | 29 +++++++++++ .../02-liveblocks/globals.css | 27 ++++++++++ .../src/api/nodeConversions/nodeToBlock.ts | 11 ++-- packages/core/src/editor/BlockNoteEditor.ts | 7 ++- .../core/src/editor/BlockNoteExtensions.ts | 2 +- packages/core/src/i18n/locales/en.ts | 3 ++ .../DefaultButtons/AddCommentButton.tsx | 41 +++++++++++++++ .../FormattingToolbar/FormattingToolbar.tsx | 2 + playground/package.json | 1 + playground/vite.config.ts | 14 +++++ 14 files changed, 233 insertions(+), 46 deletions(-) create mode 100644 examples/07-collaboration/02-liveblocks/Editor.tsx create mode 100644 examples/07-collaboration/02-liveblocks/Room.tsx create mode 100644 examples/07-collaboration/02-liveblocks/Threads.tsx create mode 100644 examples/07-collaboration/02-liveblocks/globals.css create mode 100644 packages/react/src/components/FormattingToolbar/DefaultButtons/AddCommentButton.tsx diff --git a/examples/07-collaboration/02-liveblocks/.bnexample.json b/examples/07-collaboration/02-liveblocks/.bnexample.json index 9498aff1dd..441b6f9f0b 100644 --- a/examples/07-collaboration/02-liveblocks/.bnexample.json +++ b/examples/07-collaboration/02-liveblocks/.bnexample.json @@ -2,11 +2,11 @@ "playground": true, "docs": true, "author": "yousefed", - "tags": ["Advanced", "Saving/Loading", "Collaboration"], + "tags": ["Advanced", "Liveblocks", "Collaboration"], "dependencies": { - "@liveblocks/client": "^1.10.0", - "@liveblocks/react": "^1.10.0", - "@liveblocks/yjs": "^1.10.0", - "yjs": "^13.6.15" + "@liveblocks/client": "^2.11.0", + "@liveblocks/react": "^2.11.0", + "@liveblocks/react-ui": "^2.11.0", + "@liveblocks/react-blocknote": "^2.11.0" } } diff --git a/examples/07-collaboration/02-liveblocks/App.tsx b/examples/07-collaboration/02-liveblocks/App.tsx index 854b6b1091..8db1114620 100644 --- a/examples/07-collaboration/02-liveblocks/App.tsx +++ b/examples/07-collaboration/02-liveblocks/App.tsx @@ -1,41 +1,22 @@ import "@blocknote/core/fonts/inter.css"; -import { useCreateBlockNote } from "@blocknote/react"; -import { BlockNoteView } from "@blocknote/mantine"; import "@blocknote/mantine/style.css"; -import { createClient } from "@liveblocks/client"; -import LiveblocksProvider from "@liveblocks/yjs"; -import * as Y from "yjs"; -// Sets up Liveblocks client. -const client = createClient({ - publicApiKey: - "pk_dev_lJAS4XHx3l1e0x_Gh9VMtrvo8PEB1vrNarC2YRtAOp4t6i9_QAcSX2U953GS6v7B", -}); -// Enters a multiplayer room. -// Use a unique name as a "room" for your application. -const { room } = client.enterRoom("your-project-name", { - initialPresence: {}, -}); +// .. in imports are temporary for development (vite setup) -// Sets up Yjs document and Liveblocks Yjs provider. -const doc = new Y.Doc(); -const provider = new LiveblocksProvider(room, doc); +// Need to be imported before @liveblocks/react-blocknote styles +import "@liveblocks/react-ui/styles.css"; +// Need to be imported after @liveblocks/react-ui styles +import "@liveblocks/react-blocknote/styles.css"; -export default function App() { - const editor = useCreateBlockNote({ - collaboration: { - // The Yjs Provider responsible for transporting updates: - provider, - // Where to store BlockNote data in the Y.Doc: - fragment: doc.getXmlFragment("document-store"), - // Information (name and color) for this user: - user: { - name: "My Username", - color: "#ff0000", - }, - }, - }); +import { Editor } from "./Editor.jsx"; +import { Room } from "./Room.jsx"; +import "./globals.css"; - // Renders the editor instance. - return ; +export default function App() { + // Renders the editor instance using a React component. + return ( + + + + ); } diff --git a/examples/07-collaboration/02-liveblocks/Editor.tsx b/examples/07-collaboration/02-liveblocks/Editor.tsx new file mode 100644 index 0000000000..179a8f528b --- /dev/null +++ b/examples/07-collaboration/02-liveblocks/Editor.tsx @@ -0,0 +1,32 @@ +"use client"; + +import { BlockNoteSchema } from "@blocknote/core"; +import { BlockNoteView } from "@blocknote/mantine"; +import { useCreateBlockNote } from "@blocknote/react"; +import { + useLiveblocksExtension, + withLiveblocks, +} from "@liveblocks/react-blocknote"; +import { Threads } from "./Threads"; + +export function Editor() { + const liveblocks = useLiveblocksExtension(); + + const schema = withLiveblocks(BlockNoteSchema.create()); + + const editor = useCreateBlockNote({ + schema, + _extensions: { liveblocksExtension: liveblocks }, + disableExtensions: ["history"], + }); + + return ( + { + console.log(editor.document); + }} + editor={editor}> + + + ); +} diff --git a/examples/07-collaboration/02-liveblocks/Room.tsx b/examples/07-collaboration/02-liveblocks/Room.tsx new file mode 100644 index 0000000000..e25db82357 --- /dev/null +++ b/examples/07-collaboration/02-liveblocks/Room.tsx @@ -0,0 +1,51 @@ +"use client"; + +import { + ClientSideSuspense, + LiveblocksProvider, + RoomProvider, +} from "@liveblocks/react/suspense"; +import { ReactNode } from "react"; + +const users = [ + { + id: "1", + name: "John Doe", + avatar: "https://liveblocks.io/avatars/avatar-1.png", + }, + { + id: "2", + name: "Alice Smith", + avatar: "https://liveblocks.io/avatars/avatar-2.png", + }, + { + id: "3", + name: "Bob Johnson", + avatar: "https://liveblocks.io/avatars/avatar-3.png", + }, +]; + +export function Room({ children }: { children: ReactNode }) { + return ( + { + return users + .filter((user) => + user.name.toLowerCase().startsWith(args.text.toLowerCase()) + ) + .map((user) => user.id); + }} + resolveUsers={async (args) => { + return args.userIds.map((id) => users.find((user) => user.id === id)); + }}> + + Loading…}> + {children} + + + + ); +} diff --git a/examples/07-collaboration/02-liveblocks/Threads.tsx b/examples/07-collaboration/02-liveblocks/Threads.tsx new file mode 100644 index 0000000000..d2b7a5f40d --- /dev/null +++ b/examples/07-collaboration/02-liveblocks/Threads.tsx @@ -0,0 +1,29 @@ +import { BlockNoteEditor } from "@blocknote/core"; +import { + AnchoredThreads, + FloatingComposer, + FloatingThreads, +} from "@liveblocks/react-blocknote"; +import { useThreads } from "@liveblocks/react/suspense"; + +export function Threads({ + editor, +}: { + editor: BlockNoteEditor; +}) { + const { threads } = useThreads({ query: { resolved: false } }); + + return ( + <> +
+ +
+ + + + ); +} diff --git a/examples/07-collaboration/02-liveblocks/globals.css b/examples/07-collaboration/02-liveblocks/globals.css new file mode 100644 index 0000000000..66ba5d06a7 --- /dev/null +++ b/examples/07-collaboration/02-liveblocks/globals.css @@ -0,0 +1,27 @@ +/* For mobile */ +.floating-threads { + display: none; +} + +/* For desktop */ +.anchored-threads { + display: block; + max-width: 300px; + width: 100%; + position: absolute; + right: 12px; +} + +@media (max-width: 640px) { + .floating-threads { + display: block; + } + + .anchored-threads { + display: none; + } +} + +.lb-portal { + z-index: 3000; +} diff --git a/packages/core/src/api/nodeConversions/nodeToBlock.ts b/packages/core/src/api/nodeConversions/nodeToBlock.ts index 47af4a35f2..14b56a3de1 100644 --- a/packages/core/src/api/nodeConversions/nodeToBlock.ts +++ b/packages/core/src/api/nodeConversions/nodeToBlock.ts @@ -104,11 +104,12 @@ export function contentNodeToInlineContent< return; } - if ( - node.type.name !== "link" && - node.type.name !== "text" && - inlineContentSchema[node.type.name] - ) { + if (node.type.name !== "link" && node.type.name !== "text") { + if (!inlineContentSchema[node.type.name]) { + // @eslint-disable-next-line no-console + console.warn("unrecognized inline content type", node.type.name); + return; + } if (currentContent) { content.push(currentContent); currentContent = undefined; diff --git a/packages/core/src/editor/BlockNoteEditor.ts b/packages/core/src/editor/BlockNoteEditor.ts index b9460d91cf..6931619887 100644 --- a/packages/core/src/editor/BlockNoteEditor.ts +++ b/packages/core/src/editor/BlockNoteEditor.ts @@ -901,7 +901,12 @@ export class BlockNoteEditor< for (const mark of marks) { const config = this.schema.styleSchema[mark.type.name]; if (!config) { - if (mark.type.name !== "link") { + if ( + // In BlockNote, links are represented as inline content instead of "styles" + mark.type.name !== "link" && + // We don't expose comments in the blocknote schema, as we consider them content "outside" of the document + mark.type.name !== "liveblocksCommentMark" + ) { // eslint-disable-next-line no-console console.warn("mark not found in styleschema", mark.type.name); } diff --git a/packages/core/src/editor/BlockNoteExtensions.ts b/packages/core/src/editor/BlockNoteExtensions.ts index 8e2078b7a4..ef620b0651 100644 --- a/packages/core/src/editor/BlockNoteExtensions.ts +++ b/packages/core/src/editor/BlockNoteExtensions.ts @@ -116,7 +116,7 @@ export const getBlockNoteExtensions = < ret["nodeSelectionKeyboard"] = new NodeSelectionKeyboardPlugin(); const disableExtensions: string[] = opts.disableExtensions || []; - for (const ext of Object.keys(disableExtensions)) { + for (const ext of disableExtensions) { delete ret[ext]; } diff --git a/packages/core/src/i18n/locales/en.ts b/packages/core/src/i18n/locales/en.ts index e23d0e2638..6365542b4e 100644 --- a/packages/core/src/i18n/locales/en.ts +++ b/packages/core/src/i18n/locales/en.ts @@ -269,6 +269,9 @@ export const en = { align_justify: { tooltip: "Justify text", }, + comment: { + tooltip: "Add comment", + }, }, file_panel: { upload: { diff --git a/packages/react/src/components/FormattingToolbar/DefaultButtons/AddCommentButton.tsx b/packages/react/src/components/FormattingToolbar/DefaultButtons/AddCommentButton.tsx new file mode 100644 index 0000000000..50c8a6911e --- /dev/null +++ b/packages/react/src/components/FormattingToolbar/DefaultButtons/AddCommentButton.tsx @@ -0,0 +1,41 @@ +import { BlockSchema, InlineContentSchema, StyleSchema } from "@blocknote/core"; +import { useCallback } from "react"; +import { RiChat3Line } from "react-icons/ri"; + +import { useComponentsContext } from "../../../editor/ComponentsContext.js"; +import { useBlockNoteEditor } from "../../../hooks/useBlockNoteEditor.js"; +import { useDictionary } from "../../../i18n/dictionary.js"; + +export const AddCommentButton = () => { + const dict = useDictionary(); + const Components = useComponentsContext()!; + + const editor = useBlockNoteEditor< + BlockSchema, + InlineContentSchema, + StyleSchema + >(); + + const onClick = useCallback(() => { + (editor._tiptapEditor as any).chain().focus().addPendingComment().run(); + }, [editor]); + + if ( + // We manually check if a comment extension (like liveblocks) is installed + // By adding default support for this, the user doesn't need to customize the formatting toolbar + !(editor._tiptapEditor.commands as any)["addPendingComment"] || + !editor.isEditable + ) { + return null; + } + + return ( + } + onClick={onClick} + /> + ); +}; diff --git a/packages/react/src/components/FormattingToolbar/FormattingToolbar.tsx b/packages/react/src/components/FormattingToolbar/FormattingToolbar.tsx index e6b77b6050..dac1f182fa 100644 --- a/packages/react/src/components/FormattingToolbar/FormattingToolbar.tsx +++ b/packages/react/src/components/FormattingToolbar/FormattingToolbar.tsx @@ -17,6 +17,7 @@ import { BlockTypeSelectItem, } from "./DefaultSelects/BlockTypeSelect.js"; +import { AddCommentButton } from "./DefaultButtons/AddCommentButton.js"; import { FileDownloadButton } from "./DefaultButtons/FileDownloadButton.js"; import { FilePreviewButton } from "./DefaultButtons/FilePreviewButton.js"; import { TextAlignButton } from "./DefaultButtons/TextAlignButton.js"; @@ -46,6 +47,7 @@ export const getFormattingToolbarItems = ( , , , + , ]; // TODO: props.blockTypeSelectItems should only be available if no children diff --git a/playground/package.json b/playground/package.json index 1ab71afed7..fcd4002194 100644 --- a/playground/package.json +++ b/playground/package.json @@ -25,6 +25,7 @@ "@tiptap/core": "^2.7.1", "@tiptap/react": "^2.7.1", "@liveblocks/client": "^2.11.0", + "@liveblocks/react-blocknote": "^2.11.0", "@liveblocks/react": "^2.11.0", "@liveblocks/react-tiptap": "^2.11.0", "@liveblocks/react-ui": "^2.11.0", diff --git a/playground/vite.config.ts b/playground/vite.config.ts index 55afac6e3c..1ab3829cfe 100644 --- a/playground/vite.config.ts +++ b/playground/vite.config.ts @@ -63,6 +63,7 @@ export default defineConfig((conf) => ({ __dirname, "../packages/xl-multi-column/src/" ), + "@liveblocks/core": path.resolve( __dirname, "../../liveblocks/packages/liveblocks-core/src/" @@ -75,10 +76,23 @@ export default defineConfig((conf) => ({ __dirname, "../../liveblocks/packages/liveblocks-react-tiptap/src/" ), + "@liveblocks/react-blocknote/styles.css": path.resolve( + __dirname, + "../../liveblocks/packages/liveblocks-react-blocknote/styles.css" + ), + "@liveblocks/react-blocknote": path.resolve( + __dirname, + "../../liveblocks/packages/liveblocks-react-blocknote/src/" + ), + "@liveblocks/react-ui/styles.css": path.resolve( + __dirname, + "../../liveblocks/packages/liveblocks-react-ui/styles.css" + ), "@liveblocks/react-ui": path.resolve( __dirname, "../../liveblocks/packages/liveblocks-react-ui/src/" ), + "@liveblocks/client": path.resolve( __dirname, "../../liveblocks/packages/liveblocks-client/src/" From 85521dffcad3baea55eff2e8abe644ae7fa06dd0 Mon Sep 17 00:00:00 2001 From: yousefed Date: Tue, 19 Nov 2024 15:01:31 +0100 Subject: [PATCH 004/144] revert minimal --- examples/01-basic/01-minimal/App.tsx | 35 +++------------- examples/01-basic/01-minimal/Editor.tsx | 52 ------------------------ examples/01-basic/01-minimal/Room.tsx | 51 ----------------------- examples/01-basic/01-minimal/Threads.tsx | 25 ------------ examples/01-basic/01-minimal/globals.css | 27 ------------ 5 files changed, 6 insertions(+), 184 deletions(-) delete mode 100644 examples/01-basic/01-minimal/Editor.tsx delete mode 100644 examples/01-basic/01-minimal/Room.tsx delete mode 100644 examples/01-basic/01-minimal/Threads.tsx delete mode 100644 examples/01-basic/01-minimal/globals.css diff --git a/examples/01-basic/01-minimal/App.tsx b/examples/01-basic/01-minimal/App.tsx index aa59e53ca4..a3b92bafd2 100644 --- a/examples/01-basic/01-minimal/App.tsx +++ b/examples/01-basic/01-minimal/App.tsx @@ -1,35 +1,12 @@ import "@blocknote/core/fonts/inter.css"; +import { BlockNoteView } from "@blocknote/mantine"; import "@blocknote/mantine/style.css"; -import "@liveblocks/react-ui/../styles.css"; - -import "@liveblocks/react-tiptap/../styles.css"; - -import { Editor } from "./Editor.jsx"; -import { Room } from "./Room.jsx"; -import "./globals.css"; +import { useCreateBlockNote } from "@blocknote/react"; export default function App() { + // Creates a new editor instance. + const editor = useCreateBlockNote(); + // Renders the editor instance using a React component. - return ( - - - - ); + return ; } - -/** - * - * TODO: - * - blocking: DOM updates - * - fix mount issue - * - - (domelement) - * - - make with with queueMicrotask - * - versioning - * - automatic comment button? - * - Even simpler API? - * - History - * - mentions: - * -- paste handler - * -- "change" API to trigger code from document changes - * -- hook up blocknote mentions, create default mention API that adds inline content + menu - */ diff --git a/examples/01-basic/01-minimal/Editor.tsx b/examples/01-basic/01-minimal/Editor.tsx deleted file mode 100644 index a35fe28ae1..0000000000 --- a/examples/01-basic/01-minimal/Editor.tsx +++ /dev/null @@ -1,52 +0,0 @@ -"use client"; - -import { BlockNoteEditor } from "@blocknote/core"; -import { BlockNoteView } from "@blocknote/mantine"; -import { - FormattingToolbar, - FormattingToolbarController, - blockTypeSelectItems, - getFormattingToolbarItems, - useComponentsContext, - useCreateBlockNote, -} from "@blocknote/react"; -import { useLiveblocksExtension } from "@liveblocks/react-tiptap"; -import { Threads } from "./Threads"; - -export function CustomFormattingToolbar(props: { - editor: BlockNoteEditor; -}) { - const ctx = useComponentsContext()!; - return ( - - {...getFormattingToolbarItems( - blockTypeSelectItems(props.editor.dictionary) - )} - { - props.editor?._tiptapEditor.chain().focus().addPendingComment().run(); - }}> - Comment - - - ); -} - -export function Editor() { - const liveblocks = useLiveblocksExtension(); - - const editor = useCreateBlockNote({ - _extensions: { liveblocksExtension: liveblocks }, - disableExtensions: ["history"], - }); - - return ( - - - } - /> - - ); -} diff --git a/examples/01-basic/01-minimal/Room.tsx b/examples/01-basic/01-minimal/Room.tsx deleted file mode 100644 index e25db82357..0000000000 --- a/examples/01-basic/01-minimal/Room.tsx +++ /dev/null @@ -1,51 +0,0 @@ -"use client"; - -import { - ClientSideSuspense, - LiveblocksProvider, - RoomProvider, -} from "@liveblocks/react/suspense"; -import { ReactNode } from "react"; - -const users = [ - { - id: "1", - name: "John Doe", - avatar: "https://liveblocks.io/avatars/avatar-1.png", - }, - { - id: "2", - name: "Alice Smith", - avatar: "https://liveblocks.io/avatars/avatar-2.png", - }, - { - id: "3", - name: "Bob Johnson", - avatar: "https://liveblocks.io/avatars/avatar-3.png", - }, -]; - -export function Room({ children }: { children: ReactNode }) { - return ( - { - return users - .filter((user) => - user.name.toLowerCase().startsWith(args.text.toLowerCase()) - ) - .map((user) => user.id); - }} - resolveUsers={async (args) => { - return args.userIds.map((id) => users.find((user) => user.id === id)); - }}> - - Loading…}> - {children} - - - - ); -} diff --git a/examples/01-basic/01-minimal/Threads.tsx b/examples/01-basic/01-minimal/Threads.tsx deleted file mode 100644 index 968fe84482..0000000000 --- a/examples/01-basic/01-minimal/Threads.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { - AnchoredThreads, - FloatingComposer, - FloatingThreads, -} from "@liveblocks/react-tiptap"; -import { useThreads } from "@liveblocks/react/suspense"; -import { Editor } from "@tiptap/react"; - -export function Threads({ editor }: { editor: Editor | null }) { - const { threads } = useThreads({ query: { resolved: false } }); - - return ( - <> -
- -
- - - - ); -} diff --git a/examples/01-basic/01-minimal/globals.css b/examples/01-basic/01-minimal/globals.css deleted file mode 100644 index 66ba5d06a7..0000000000 --- a/examples/01-basic/01-minimal/globals.css +++ /dev/null @@ -1,27 +0,0 @@ -/* For mobile */ -.floating-threads { - display: none; -} - -/* For desktop */ -.anchored-threads { - display: block; - max-width: 300px; - width: 100%; - position: absolute; - right: 12px; -} - -@media (max-width: 640px) { - .floating-threads { - display: block; - } - - .anchored-threads { - display: none; - } -} - -.lb-portal { - z-index: 3000; -} From e43741aab364851ccd5d71886013df52df82e0cb Mon Sep 17 00:00:00 2001 From: yousefed Date: Wed, 20 Nov 2024 05:25:53 +0100 Subject: [PATCH 005/144] simplify setup --- .../07-collaboration/02-liveblocks/Editor.tsx | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/examples/07-collaboration/02-liveblocks/Editor.tsx b/examples/07-collaboration/02-liveblocks/Editor.tsx index 179a8f528b..3f6cb1175d 100644 --- a/examples/07-collaboration/02-liveblocks/Editor.tsx +++ b/examples/07-collaboration/02-liveblocks/Editor.tsx @@ -1,24 +1,10 @@ "use client"; -import { BlockNoteSchema } from "@blocknote/core"; import { BlockNoteView } from "@blocknote/mantine"; -import { useCreateBlockNote } from "@blocknote/react"; -import { - useLiveblocksExtension, - withLiveblocks, -} from "@liveblocks/react-blocknote"; +import { useCreateBlockNoteWithLiveblocks } from "@liveblocks/react-blocknote"; import { Threads } from "./Threads"; - export function Editor() { - const liveblocks = useLiveblocksExtension(); - - const schema = withLiveblocks(BlockNoteSchema.create()); - - const editor = useCreateBlockNote({ - schema, - _extensions: { liveblocksExtension: liveblocks }, - disableExtensions: ["history"], - }); + const editor = useCreateBlockNoteWithLiveblocks({}); return ( Date: Wed, 20 Nov 2024 05:36:48 +0100 Subject: [PATCH 006/144] update config --- playground/package.json | 1 - playground/vite.config.ts | 6 ++++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/playground/package.json b/playground/package.json index fcd4002194..994af336be 100644 --- a/playground/package.json +++ b/playground/package.json @@ -27,7 +27,6 @@ "@liveblocks/client": "^2.11.0", "@liveblocks/react-blocknote": "^2.11.0", "@liveblocks/react": "^2.11.0", - "@liveblocks/react-tiptap": "^2.11.0", "@liveblocks/react-ui": "^2.11.0", "@liveblocks/yjs": "^2.11.0", "@mantine/core": "^7.10.1", diff --git a/playground/vite.config.ts b/playground/vite.config.ts index 1ab3829cfe..0b8c7da4a7 100644 --- a/playground/vite.config.ts +++ b/playground/vite.config.ts @@ -63,7 +63,9 @@ export default defineConfig((conf) => ({ __dirname, "../packages/xl-multi-column/src/" ), - + /* + This can be used when developing against a local version of liveblocks: + "@liveblocks/core": path.resolve( __dirname, "../../liveblocks/packages/liveblocks-core/src/" @@ -105,7 +107,7 @@ export default defineConfig((conf) => ({ __dirname, "../node_modules/@tiptap/suggestion/" ), - yjs: path.resolve(__dirname, "../node_modules/yjs/"), + yjs: path.resolve(__dirname, "../node_modules/yjs/"),*/ }, }, })); From d747238ac556c38cc264ff4f44cc081d7ee2b2a3 Mon Sep 17 00:00:00 2001 From: yousefed Date: Wed, 20 Nov 2024 05:37:21 +0100 Subject: [PATCH 007/144] fix --- examples/07-collaboration/02-liveblocks/Editor.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/examples/07-collaboration/02-liveblocks/Editor.tsx b/examples/07-collaboration/02-liveblocks/Editor.tsx index 3f6cb1175d..0d5a87a488 100644 --- a/examples/07-collaboration/02-liveblocks/Editor.tsx +++ b/examples/07-collaboration/02-liveblocks/Editor.tsx @@ -7,11 +7,7 @@ export function Editor() { const editor = useCreateBlockNoteWithLiveblocks({}); return ( - { - console.log(editor.document); - }} - editor={editor}> + ); From 6ce69ea52e6390ff4e6fd5fc73ded36679a982fb Mon Sep 17 00:00:00 2001 From: yousefed Date: Wed, 20 Nov 2024 05:55:49 +0100 Subject: [PATCH 008/144] fix --- examples/01-basic/02-block-objects/App.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/01-basic/02-block-objects/App.tsx b/examples/01-basic/02-block-objects/App.tsx index cecd134b97..b33a9561e7 100644 --- a/examples/01-basic/02-block-objects/App.tsx +++ b/examples/01-basic/02-block-objects/App.tsx @@ -38,7 +38,6 @@ export default function App() {
BlockNote Editor:
{ // Saves the document JSON to state. From a3028c112e8891aa5c15e59a602fd62d3d201c24 Mon Sep 17 00:00:00 2001 From: yousefed Date: Sat, 14 Dec 2024 11:54:18 +0100 Subject: [PATCH 009/144] markview --- .../02-liveblocks/package.json | 8 +- packages/core/package.json | 2 +- .../core/src/editor/BlockNoteTipTapEditor.ts | 16 +++ packages/react/src/editor/ElementRenderer.tsx | 1 + .../react/src/schema/@util/CoreMarkView.ts | 106 ++++++++++++++++++ .../src/schema/@util/CoreMarkViewOptions.ts | 32 ++++++ .../src/schema/@util/ReactMarkViewOptions.ts | 16 +++ .../schema/@util/ReactMarkViewRenderer.tsx | 72 ++++++++++++ .../react/src/schema/@util/markViewContext.ts | 33 ++++++ packages/react/src/schema/ReactStyleSpec.tsx | 16 +++ packages/xl-multi-column/package.json | 2 +- playground/package.json | 1 - playground/src/examples.gen.tsx | 10 +- 13 files changed, 303 insertions(+), 12 deletions(-) create mode 100644 packages/react/src/schema/@util/CoreMarkView.ts create mode 100644 packages/react/src/schema/@util/CoreMarkViewOptions.ts create mode 100644 packages/react/src/schema/@util/ReactMarkViewOptions.ts create mode 100644 packages/react/src/schema/@util/ReactMarkViewRenderer.tsx create mode 100644 packages/react/src/schema/@util/markViewContext.ts diff --git a/examples/07-collaboration/02-liveblocks/package.json b/examples/07-collaboration/02-liveblocks/package.json index bafb616c66..fbbffced48 100644 --- a/examples/07-collaboration/02-liveblocks/package.json +++ b/examples/07-collaboration/02-liveblocks/package.json @@ -18,10 +18,10 @@ "@blocknote/shadcn": "latest", "react": "^18.3.1", "react-dom": "^18.3.1", - "@liveblocks/client": "^1.10.0", - "@liveblocks/react": "^1.10.0", - "@liveblocks/yjs": "^1.10.0", - "yjs": "^13.6.15" + "@liveblocks/client": "^2.11.0", + "@liveblocks/react": "^2.11.0", + "@liveblocks/react-ui": "^2.11.0", + "@liveblocks/react-blocknote": "^2.11.0" }, "devDependencies": { "@types/react": "^18.0.25", diff --git a/packages/core/package.json b/packages/core/package.json index 7751216969..21e8793ac5 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -85,7 +85,7 @@ "prosemirror-state": "^1.4.3", "prosemirror-tables": "^1.6.1", "prosemirror-transform": "^1.9.0", - "prosemirror-view": "^1.33.7", + "prosemirror-view": "^1.37.1", "rehype-format": "^5.0.0", "rehype-parse": "^8.0.4", "rehype-remark": "^9.1.2", diff --git a/packages/core/src/editor/BlockNoteTipTapEditor.ts b/packages/core/src/editor/BlockNoteTipTapEditor.ts index fa49f7d491..634760bf59 100644 --- a/packages/core/src/editor/BlockNoteTipTapEditor.ts +++ b/packages/core/src/editor/BlockNoteTipTapEditor.ts @@ -158,6 +158,21 @@ export class BlockNoteTipTapEditor extends TiptapEditor { return; } (this as any).contentComponent = contentComponent; + + const markViews: any = {}; + this.extensionManager.extensions.forEach((extension) => { + if (extension.type === "mark" && extension.config.addMarkView) { + markViews[extension.name] = extension.config.addMarkView; + } + }); + + // if (Object.keys(markViews).length > 0) { + // debugger; + // this.view.setProps({ + // markViews, + // }); + // } + this.view = new EditorView( { mount: this.options.element as any }, // use mount option so that we reuse the existing element instead of creating a new one { @@ -165,6 +180,7 @@ export class BlockNoteTipTapEditor extends TiptapEditor { // @ts-ignore dispatchTransaction: this.dispatchTransaction.bind(this), state: this.state, + markViews, } ); diff --git a/packages/react/src/editor/ElementRenderer.tsx b/packages/react/src/editor/ElementRenderer.tsx index aedcc55ed5..73dee1d3d7 100644 --- a/packages/react/src/editor/ElementRenderer.tsx +++ b/packages/react/src/editor/ElementRenderer.tsx @@ -17,6 +17,7 @@ export const ElementRenderer = forwardRef< ref, () => { return (node: React.ReactNode, container: HTMLElement) => { + debugger; flushSync(() => { setSingleRenderData({ node, container }); }); diff --git a/packages/react/src/schema/@util/CoreMarkView.ts b/packages/react/src/schema/@util/CoreMarkView.ts new file mode 100644 index 0000000000..84c4facde9 --- /dev/null +++ b/packages/react/src/schema/@util/CoreMarkView.ts @@ -0,0 +1,106 @@ +import type { Mark } from "prosemirror-model"; +import type { + EditorView, + MarkView, + ViewMutationRecord, +} from "prosemirror-view"; + +import type { BlockNoteEditor } from "@blocknote/core"; +import type { + CoreMarkViewSpec, + CoreMarkViewUserOptions, + MarkViewDOMSpec, +} from "./CoreMarkViewOptions"; + +export class CoreMarkView implements MarkView { + dom: HTMLElement; + contentDOM: HTMLElement | undefined; + mark: Mark; + view: EditorView; + inline: boolean; + options: CoreMarkViewUserOptions; + editor: BlockNoteEditor; + + #createElement(as?: MarkViewDOMSpec) { + const { inline, mark } = this; + return as == null + ? document.createElement(inline ? "span" : "div") + : as instanceof HTMLElement + ? as + : as instanceof Function + ? as(mark) + : document.createElement(as); + } + + createDOM(as?: MarkViewDOMSpec) { + return this.#createElement(as); + } + + createContentDOM(as?: MarkViewDOMSpec) { + return this.#createElement(as); + } + + constructor({ + mark, + view, + inline, + options, + editor, // BlockNote specific + }: CoreMarkViewSpec) { + this.mark = mark; + this.view = view; + this.inline = inline; + this.options = options; + this.editor = editor; + + this.dom = this.createDOM(options.as); + this.contentDOM = options.contentAs + ? this.createContentDOM(options.contentAs) + : undefined; + this.dom.setAttribute("data-mark-view-root", "true"); + if (this.contentDOM) { + this.contentDOM.setAttribute("data-mark-view-content", "true"); + this.contentDOM.style.whiteSpace = "inherit"; + } + } + + get component() { + return this.options.component; + } + + shouldIgnoreMutation: (mutation: ViewMutationRecord) => boolean = ( + mutation + ) => { + if (!this.dom || !this.contentDOM) return true; + + if (mutation.type === "selection") return false; + + if (this.contentDOM === mutation.target && mutation.type === "attributes") + return true; + + if (this.contentDOM.contains(mutation.target)) return false; + + return true; + }; + + ignoreMutation: (mutation: ViewMutationRecord) => boolean = (mutation) => { + if (!this.dom || !this.contentDOM) return true; + + let result; + + const userIgnoreMutation = this.options.ignoreMutation; + + if (userIgnoreMutation) result = userIgnoreMutation(mutation); + + if (typeof result !== "boolean") + result = this.shouldIgnoreMutation(mutation); + + return result; + }; + + public destroy() { + this.options.destroy?.(); + this.dom.remove(); + this.contentDOM?.remove(); + } +} diff --git a/packages/react/src/schema/@util/CoreMarkViewOptions.ts b/packages/react/src/schema/@util/CoreMarkViewOptions.ts new file mode 100644 index 0000000000..dca5fa4909 --- /dev/null +++ b/packages/react/src/schema/@util/CoreMarkViewOptions.ts @@ -0,0 +1,32 @@ +import { BlockNoteEditor } from "@blocknote/core"; +import type { Mark } from "prosemirror-model"; +import type { EditorView, ViewMutationRecord } from "prosemirror-view"; + +export type MarkViewDOMSpec = + | string + | HTMLElement + | ((mark: Mark) => HTMLElement); + +export interface CoreMarkViewUserOptions { + // DOM + as?: MarkViewDOMSpec; + contentAs?: MarkViewDOMSpec; + + // Component + component: Component; + + // Overrides + ignoreMutation?: (mutation: ViewMutationRecord) => boolean | void; + destroy?: () => void; +} + +export interface CoreMarkViewSpec { + mark: Mark; + view: EditorView; + inline: boolean; + + options: CoreMarkViewUserOptions; + + // BlockNote specific + editor: BlockNoteEditor; +} diff --git a/packages/react/src/schema/@util/ReactMarkViewOptions.ts b/packages/react/src/schema/@util/ReactMarkViewOptions.ts new file mode 100644 index 0000000000..d61350cb56 --- /dev/null +++ b/packages/react/src/schema/@util/ReactMarkViewOptions.ts @@ -0,0 +1,16 @@ +import type { FC } from "react"; +import type { + CoreMarkViewSpec, + CoreMarkViewUserOptions, +} from "./CoreMarkViewOptions.js"; + +// export type ReactMarkViewComponent = ComponentType>; + +export type ReactMarkViewComponent = + | FC<{ contentRef: (el: HTMLElement | null) => void }> + | FC<{ contentRef: (el: HTMLElement | null) => void; value: string }>; + +export type ReactMarkViewSpec = CoreMarkViewSpec; + +export type ReactMarkViewUserOptions = + CoreMarkViewUserOptions; diff --git a/packages/react/src/schema/@util/ReactMarkViewRenderer.tsx b/packages/react/src/schema/@util/ReactMarkViewRenderer.tsx new file mode 100644 index 0000000000..3113cbe76c --- /dev/null +++ b/packages/react/src/schema/@util/ReactMarkViewRenderer.tsx @@ -0,0 +1,72 @@ +// import { nanoid } from "nanoid"; +import { CoreMarkView } from "./CoreMarkView.js"; + +// import type { ReactRenderer } from "../ReactRenderer"; +import type { MarkViewContext } from "./markViewContext.js"; +import type { ReactMarkViewComponent } from "./ReactMarkViewOptions.js"; + +export class ReactMarkView extends CoreMarkView { + // implements ReactRenderer + // key: string = nanoid(); + id = Math.floor(Math.random() * 0xffffffff).toString(); + + context: MarkViewContext = { + contentRef: (element) => { + if (element && this.contentDOM && element.firstChild !== this.contentDOM) + element.appendChild(this.contentDOM); + }, + view: this.view, + + mark: this.mark, + }; + + updateContext = () => { + Object.assign(this.context, { + mark: this.mark, + }); + }; + + // render = () => { + // const UserComponent = this.component; + + // return createPortal( + // + // + // , + // this.dom, + // this.key + // ); + // }; + + render = () => { + this.editor._tiptapEditor.contentComponent.setRenderer( + this.id, + this.renderer() + ); + }; + + destroy = () => { + super.destroy(); + this.editor._tiptapEditor.contentComponent.removeRenderer(this.id); + }; + + renderer = () => { + const UserComponent = this.component; + + const props: any = {}; + + if (this.mark.attrs.stringValue) { + props.value = this.mark.attrs.stringValue; + } + debugger; + + return { + reactElement: ( + // + + // + ), + element: this.dom, + }; + }; +} diff --git a/packages/react/src/schema/@util/markViewContext.ts b/packages/react/src/schema/@util/markViewContext.ts new file mode 100644 index 0000000000..ce5254b4e5 --- /dev/null +++ b/packages/react/src/schema/@util/markViewContext.ts @@ -0,0 +1,33 @@ +import type { Mark } from "prosemirror-model"; +import type { EditorView, MarkViewConstructor } from "prosemirror-view"; +import { createContext, useContext } from "react"; +import type { ReactMarkViewUserOptions } from "./ReactMarkViewOptions.js"; + +export type MarkViewContentRef = (node: HTMLElement | null) => void; + +export interface MarkViewContext { + // won't change + contentRef: MarkViewContentRef; + view: EditorView; + mark: Mark; +} + +export const markViewContext = createContext({ + contentRef: () => { + // nothing to do + }, + view: null as never, + mark: null as never, +}); + +export const useMarkViewContext = () => useContext(markViewContext); + +export const createMarkViewContext = createContext< + (options: ReactMarkViewUserOptions) => MarkViewConstructor +>((_options) => { + throw new Error( + "No ProsemirrorAdapterProvider detected, maybe you need to wrap the component with the Editor with ProsemirrorAdapterProvider?" + ); +}); + +export const useMarkViewFactory = () => useContext(createMarkViewContext); diff --git a/packages/react/src/schema/ReactStyleSpec.tsx b/packages/react/src/schema/ReactStyleSpec.tsx index 23b5ce18da..c179ce0219 100644 --- a/packages/react/src/schema/ReactStyleSpec.tsx +++ b/packages/react/src/schema/ReactStyleSpec.tsx @@ -7,6 +7,7 @@ import { } from "@blocknote/core"; import { Mark } from "@tiptap/react"; import { FC } from "react"; +import { ReactMarkView } from "./@util/ReactMarkViewRenderer.js"; import { renderToDOMSpec } from "./@util/ReactRenderUtil.js"; // this file is mostly analogoues to `customBlocks.ts`, but for React blocks @@ -58,6 +59,21 @@ export function createReactStyleSpec( }, }); + let x = mark; + (mark as any).config.addMarkView = (mark: any, view: any) => { + const markView = new ReactMarkView({ + editor: x.child?.options.editor, + inline: true, + mark, + options: { + component: styleImplementation.render, + contentAs: "span", + }, + view, + }); + markView.render(); + return markView; + }; return createInternalStyleSpec(styleConfig, { mark, }); diff --git a/packages/xl-multi-column/package.json b/packages/xl-multi-column/package.json index 4680115f49..e587526ac8 100644 --- a/packages/xl-multi-column/package.json +++ b/packages/xl-multi-column/package.json @@ -52,7 +52,7 @@ "prosemirror-state": "^1.4.3", "prosemirror-tables": "^1.3.7", "prosemirror-transform": "^1.9.0", - "prosemirror-view": "^1.33.7", + "prosemirror-view": "^1.37.1", "react-icons": "^5.2.1" }, "devDependencies": { diff --git a/playground/package.json b/playground/package.json index 994af336be..124fa080a8 100644 --- a/playground/package.json +++ b/playground/package.json @@ -25,7 +25,6 @@ "@tiptap/core": "^2.7.1", "@tiptap/react": "^2.7.1", "@liveblocks/client": "^2.11.0", - "@liveblocks/react-blocknote": "^2.11.0", "@liveblocks/react": "^2.11.0", "@liveblocks/react-ui": "^2.11.0", "@liveblocks/yjs": "^2.11.0", diff --git a/playground/src/examples.gen.tsx b/playground/src/examples.gen.tsx index 9a4d3c9b4b..e98143297a 100644 --- a/playground/src/examples.gen.tsx +++ b/playground/src/examples.gen.tsx @@ -1045,14 +1045,14 @@ "author": "yousefed", "tags": [ "Advanced", - "Saving/Loading", + "Liveblocks", "Collaboration" ], "dependencies": { - "@liveblocks/client": "^1.10.0", - "@liveblocks/react": "^1.10.0", - "@liveblocks/yjs": "^1.10.0", - "yjs": "^13.6.15" + "@liveblocks/client": "^2.11.0", + "@liveblocks/react": "^2.11.0", + "@liveblocks/react-ui": "^2.11.0", + "@liveblocks/react-blocknote": "^2.11.0" } as any }, "title": "Collaborative Editing with Liveblocks", From 15c7520f1301d684fb859ee7ae90caa08a6b0535 Mon Sep 17 00:00:00 2001 From: yousefed Date: Sat, 14 Dec 2024 15:37:19 +0100 Subject: [PATCH 010/144] cleanup --- .../core/src/editor/BlockNoteTipTapEditor.ts | 21 +++---------------- .../react/src/schema/@util/ReactRenderUtil.ts | 16 ++++++++------ 2 files changed, 13 insertions(+), 24 deletions(-) diff --git a/packages/core/src/editor/BlockNoteTipTapEditor.ts b/packages/core/src/editor/BlockNoteTipTapEditor.ts index 634760bf59..0a8d7db01f 100644 --- a/packages/core/src/editor/BlockNoteTipTapEditor.ts +++ b/packages/core/src/editor/BlockNoteTipTapEditor.ts @@ -24,7 +24,7 @@ export type BlockNoteTipTapEditorOptions = Partial< // @ts-ignore export class BlockNoteTipTapEditor extends TiptapEditor { private _state: EditorState; - private _creating = false; + public static create = ( options: BlockNoteTipTapEditorOptions, styleSchema: StyleSchema @@ -151,28 +151,17 @@ export class BlockNoteTipTapEditor extends TiptapEditor { * Replace the default `createView` method with a custom one - which we call on mount */ private createViewAlternative(contentComponent?: any) { - this._creating = true; - // Without queueMicrotask, custom IC / styles will give a React FlushSync error - // queueMicrotask(() => { - if (!this._creating) { - return; - } (this as any).contentComponent = contentComponent; const markViews: any = {}; this.extensionManager.extensions.forEach((extension) => { if (extension.type === "mark" && extension.config.addMarkView) { + // Note: migrate to using `addMarkView` from tiptap as soon as this lands + // (currently tiptap doesn't support markviews) markViews[extension.name] = extension.config.addMarkView; } }); - // if (Object.keys(markViews).length > 0) { - // debugger; - // this.view.setProps({ - // markViews, - // }); - // } - this.view = new EditorView( { mount: this.options.element as any }, // use mount option so that we reuse the existing element instead of creating a new one { @@ -199,8 +188,6 @@ export class BlockNoteTipTapEditor extends TiptapEditor { this.commands.focus(this.options.autofocus); this.emit("create", { editor: this }); this.isInitialized = true; - this._creating = false; - // }); } /** @@ -211,8 +198,6 @@ export class BlockNoteTipTapEditor extends TiptapEditor { public mount = (element?: HTMLElement | null, contentComponent?: any) => { if (!element) { this.destroy(); - // cancel pending microtask - this._creating = false; } else { this.options.element = element; // @ts-ignore diff --git a/packages/react/src/schema/@util/ReactRenderUtil.ts b/packages/react/src/schema/@util/ReactRenderUtil.ts index e81752d94e..2f2aaf6c9d 100644 --- a/packages/react/src/schema/@util/ReactRenderUtil.ts +++ b/packages/react/src/schema/@util/ReactRenderUtil.ts @@ -19,13 +19,17 @@ export function renderToDOMSpec( div ); } else { - // If no editor is provided, use a temporary root. This is currently only used for Styles (see ReactStyleSpec). - // In this case, react context etc. won't be available inside `fc` - - // We also use this if _tiptapEditor or _tiptapEditor.contentComponent is undefined, use a temporary root. - // This is actually a fallback / temporary fix, as normally this shouldn't happen (see #755). TODO: find cause + // If no editor is provided, use a temporary root. + // This is currently only used for when we use ServerBlockNoteEditor (@blocknote/server-util) + // and without using `withReactContext` + + if (!editor?.headless) { + throw new Error( + "elementRenderer not available, expected headless editor" + ); + } root = createRoot(div); - throw new Error("No editor provided"); + flushSync(() => { root!.render(fc((el) => (contentDOM = el || undefined))); }); From 1b44296e93152353bbfea9be3991472507c8c6d7 Mon Sep 17 00:00:00 2001 From: yousefed Date: Fri, 10 Jan 2025 10:30:57 +0100 Subject: [PATCH 011/144] wip --- examples/01-basic/01-minimal/App.tsx | 2 +- .../02-liveblocks/tsconfig.json | 14 +- package-lock.json | 357 ++++++++---------- .../src/api/nodeConversions/nodeToBlock.ts | 5 +- packages/core/src/editor/Block.css | 10 +- packages/core/src/editor/BlockNoteEditor.ts | 49 ++- .../core/src/editor/BlockNoteExtensions.ts | 6 + packages/core/src/editor/editor.css | 4 + .../src/extensions/Comments/CommentMark.ts | 45 +++ .../src/extensions/Comments/CommentsPlugin.ts | 336 +++++++++++++++++ packages/mantine/src/comments/Composer.tsx | 39 ++ packages/mantine/src/index.tsx | 5 +- .../src/components/Comments/Composer.tsx | 55 +++ .../Comments/FloatingComposerController.tsx | 76 ++++ .../Comments/FloatingThreadsController.tsx | 76 ++++ .../DefaultButtons/AddCommentButton.tsx | 5 +- .../react/src/editor/BlockNoteDefaultUI.tsx | 5 + .../react/src/editor/ComponentsContext.tsx | 8 + playground/package.json | 9 +- playground/src/style.css | 4 + playground/tsconfig.json | 2 + 21 files changed, 881 insertions(+), 231 deletions(-) create mode 100644 packages/core/src/extensions/Comments/CommentMark.ts create mode 100644 packages/core/src/extensions/Comments/CommentsPlugin.ts create mode 100644 packages/mantine/src/comments/Composer.tsx create mode 100644 packages/react/src/components/Comments/Composer.tsx create mode 100644 packages/react/src/components/Comments/FloatingComposerController.tsx create mode 100644 packages/react/src/components/Comments/FloatingThreadsController.tsx diff --git a/examples/01-basic/01-minimal/App.tsx b/examples/01-basic/01-minimal/App.tsx index a3b92bafd2..d4fd6f2e12 100644 --- a/examples/01-basic/01-minimal/App.tsx +++ b/examples/01-basic/01-minimal/App.tsx @@ -5,7 +5,7 @@ import { useCreateBlockNote } from "@blocknote/react"; export default function App() { // Creates a new editor instance. - const editor = useCreateBlockNote(); + const editor = useCreateBlockNote({}); // Renders the editor instance using a React component. return ; diff --git a/examples/07-collaboration/02-liveblocks/tsconfig.json b/examples/07-collaboration/02-liveblocks/tsconfig.json index 1bd8ab3c57..4a76cf4c7d 100644 --- a/examples/07-collaboration/02-liveblocks/tsconfig.json +++ b/examples/07-collaboration/02-liveblocks/tsconfig.json @@ -3,11 +3,7 @@ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], + "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, "skipLibCheck": true, "esModuleInterop": false, @@ -22,10 +18,8 @@ "jsx": "react-jsx", "composite": true }, - "include": [ - "." - ], - "__ADD_FOR_LOCAL_DEV_references": [ + "include": ["."], + "references": [ { "path": "../../../packages/core/" }, @@ -33,4 +27,4 @@ "path": "../../../packages/react/" } ] -} \ No newline at end of file +} diff --git a/package-lock.json b/package-lock.json index a1ed9a79e0..6da0a2ab6c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,146 @@ "typescript": "^5.3.3" } }, + "../liveblocks/packages/liveblocks-client": { + "name": "@liveblocks/client", + "version": "2.15.2", + "license": "Apache-2.0", + "dependencies": { + "@liveblocks/core": "2.15.2" + }, + "devDependencies": { + "@liveblocks/eslint-config": "*", + "@liveblocks/jest-config": "*", + "@types/ws": "^8.5.10", + "dotenv": "^16.4.5", + "msw": "^0.39.1", + "ws": "^8.17.1" + } + }, + "../liveblocks/packages/liveblocks-react": { + "name": "@liveblocks/react", + "version": "2.15.2", + "license": "Apache-2.0", + "dependencies": { + "@liveblocks/client": "2.15.2", + "@liveblocks/core": "2.15.2" + }, + "devDependencies": { + "@liveblocks/eslint-config": "*", + "@liveblocks/jest-config": "*", + "@liveblocks/query-parser": "^0.0.4", + "@testing-library/jest-dom": "6.4.6", + "@testing-library/react": "14.1.2", + "date-fns": "^3.6.0", + "eslint-plugin-react-hooks": "^4.6.2", + "itertools": "^2.3.2", + "msw": "1.3.2", + "react-error-boundary": "^4.0.13" + }, + "peerDependencies": { + "react": "^18 || ^19 || ^19.0.0-rc" + } + }, + "../liveblocks/packages/liveblocks-react-blocknote": { + "version": "2.15.2", + "license": "Apache-2.0", + "dependencies": { + "@liveblocks/client": "2.15.2", + "@liveblocks/core": "2.15.2", + "@liveblocks/react": "2.15.2", + "@liveblocks/react-tiptap": "2.15.2", + "@liveblocks/react-ui": "2.15.2", + "@liveblocks/yjs": "2.15.2", + "@tiptap/core": "^2.7.2" + }, + "devDependencies": { + "@liveblocks/eslint-config": "*", + "@liveblocks/jest-config": "*", + "@rollup/plugin-node-resolve": "^15.2.3", + "@rollup/plugin-replace": "^5.0.5", + "@rollup/plugin-typescript": "^11.1.2", + "@testing-library/jest-dom": "^5.16.5", + "@types/use-sync-external-store": "^0.0.6", + "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react-hooks": "^4.6.0", + "msw": "^0.27.1", + "rollup": "^3.28.0", + "rollup-plugin-dts": "^5.3.1", + "rollup-plugin-esbuild": "^5.0.0", + "rollup-plugin-preserve-directives": "^0.2.0", + "stylelint": "^15.10.2", + "stylelint-config-standard": "^34.0.0", + "stylelint-order": "^6.0.3", + "stylelint-plugin-logical-css": "^0.13.2" + }, + "peerDependencies": { + "@blocknote/core": "^0.19.1", + "@blocknote/react": "^0.19.1", + "@tiptap/core": "^2.7.2", + "react": "^16.14.0 || ^17 || ^18", + "react-dom": "^16.14.0 || ^17 || ^18" + } + }, + "../liveblocks/packages/liveblocks-react-ui": { + "name": "@liveblocks/react-ui", + "version": "2.15.2", + "license": "Apache-2.0", + "dependencies": { + "@floating-ui/react-dom": "^2.1.2", + "@liveblocks/client": "2.15.2", + "@liveblocks/core": "2.15.2", + "@liveblocks/react": "2.15.2", + "@radix-ui/react-dropdown-menu": "^2.1.2", + "@radix-ui/react-popover": "^1.1.2", + "@radix-ui/react-slot": "^1.1.0", + "@radix-ui/react-toggle": "^1.1.0", + "@radix-ui/react-tooltip": "^1.1.3", + "react-virtuoso": "^4.12.0", + "slate": "^0.110.2", + "slate-history": "^0.110.3", + "slate-hyperscript": "^0.100.0", + "slate-react": "^0.110.3" + }, + "devDependencies": { + "@liveblocks/eslint-config": "*", + "@liveblocks/jest-config": "*", + "@liveblocks/rollup-config": "*", + "@testing-library/jest-dom": "^5.16.5", + "@testing-library/react": "^13.1.1", + "emojibase": "^15.3.0", + "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react-hooks": "^4.6.0", + "msw": "^0.27.1", + "rollup": "3.28.0", + "stylelint": "^15.10.2", + "stylelint-config-standard": "^34.0.0", + "stylelint-order": "^6.0.3", + "stylelint-plugin-logical-css": "^0.13.2" + }, + "peerDependencies": { + "react": "^18 || ^19 || ^19.0.0-rc" + } + }, + "../liveblocks/packages/liveblocks-yjs": { + "name": "@liveblocks/yjs", + "version": "2.15.2", + "license": "Apache-2.0", + "dependencies": { + "@liveblocks/client": "2.15.2", + "@liveblocks/core": "2.15.2", + "js-base64": "^3.7.7", + "y-indexeddb": "^9.0.12" + }, + "devDependencies": { + "@liveblocks/eslint-config": "*", + "@liveblocks/jest-config": "*", + "@testing-library/jest-dom": "^6.4.6", + "msw": "^0.47.4" + }, + "peerDependencies": { + "yjs": "^13.6.1" + } + }, "docs": { "version": "0.22.0", "dependencies": { @@ -4703,11 +4843,6 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@juggle/resize-observer": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.4.0.tgz", - "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==" - }, "node_modules/@lerna/add": { "version": "5.6.2", "resolved": "https://registry.npmjs.org/@lerna/add/-/add-5.6.2.tgz", @@ -5773,99 +5908,24 @@ } }, "node_modules/@liveblocks/client": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/@liveblocks/client/-/client-2.11.0.tgz", - "integrity": "sha512-ZRayUwwOzucYn4QqOTz0vcQSZlqGaP8TBROzE9Ye/PwfACNasyfa3roZqfizhBlYlYQQ/8qQlvGl2n3LPf8Byg==", - "dependencies": { - "@liveblocks/core": "2.11.0" - } - }, - "node_modules/@liveblocks/core": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/@liveblocks/core/-/core-2.11.0.tgz", - "integrity": "sha512-2WQlJvJ1NVJ/CpKhDNJJHXrh2Yzz6eXchqn64JqTHk5TLGbx1s4kaTahEXaAOXmu2abnJWnv06v1mhv3YXYg+A==" + "resolved": "../liveblocks/packages/liveblocks-client", + "link": true }, "node_modules/@liveblocks/react": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/@liveblocks/react/-/react-2.11.0.tgz", - "integrity": "sha512-uAPEh9ZS03ANTnbbU5PTiFVusI05H7EqOH4aDVaKDrogkKi70RYbCek4PKY8TpK2vLhmutBxyVd3tp2uBwvTFQ==", - "dependencies": { - "@liveblocks/client": "2.11.0", - "@liveblocks/core": "2.11.0", - "use-sync-external-store": "^1.2.2" - }, - "peerDependencies": { - "react": "^16.14.0 || ^17 || ^18 || ^19 || ^19.0.0-rc" - } - }, - "node_modules/@liveblocks/react-ui": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/@liveblocks/react-ui/-/react-ui-2.11.0.tgz", - "integrity": "sha512-aBrgLWclSoV+3tOXxf45pL6Uaf8mRvHTmda79kwyCB/8yjqoeqNe2PThcELL8WWyaZsl7XU6Bgjpat3zY5rRHQ==", - "dependencies": { - "@floating-ui/react-dom": "^2.1.2", - "@liveblocks/client": "2.11.0", - "@liveblocks/core": "2.11.0", - "@liveblocks/react": "2.11.0", - "@radix-ui/react-dropdown-menu": "^2.1.2", - "@radix-ui/react-popover": "^1.1.2", - "@radix-ui/react-slot": "^1.1.0", - "@radix-ui/react-toggle": "^1.1.0", - "@radix-ui/react-tooltip": "^1.1.3", - "react-virtuoso": "^4.12.0", - "slate": "^0.110.2", - "slate-history": "^0.110.3", - "slate-hyperscript": "^0.100.0", - "slate-react": "^0.110.3", - "use-sync-external-store": "^1.2.2" - }, - "peerDependencies": { - "react": "^16.14.0 || ^17 || ^18 || ^19 || ^19.0.0-rc" - } + "resolved": "../liveblocks/packages/liveblocks-react", + "link": true }, - "node_modules/@liveblocks/react-ui/node_modules/@radix-ui/react-compose-refs": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz", - "integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } + "node_modules/@liveblocks/react-blocknote": { + "resolved": "../liveblocks/packages/liveblocks-react-blocknote", + "link": true }, - "node_modules/@liveblocks/react-ui/node_modules/@radix-ui/react-slot": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", - "integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } + "node_modules/@liveblocks/react-ui": { + "resolved": "../liveblocks/packages/liveblocks-react-ui", + "link": true }, "node_modules/@liveblocks/yjs": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/@liveblocks/yjs/-/yjs-2.11.0.tgz", - "integrity": "sha512-o9RfjrVUMW4htHvHGwXxptZxnjC+evEvEkBqS2uPhOBBtOSMe15rWqzhoHqRXngkMUs8BIZ0EOkM5a0qsFIF0w==", - "dependencies": { - "@liveblocks/client": "2.11.0", - "@liveblocks/core": "2.11.0", - "js-base64": "^3.7.7" - }, - "peerDependencies": { - "yjs": "^13.6.1" - } + "resolved": "../liveblocks/packages/liveblocks-yjs", + "link": true }, "node_modules/@mantine/core": { "version": "7.10.1", @@ -15685,18 +15745,6 @@ "node": ">=8" } }, - "node_modules/direction": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/direction/-/direction-1.0.4.tgz", - "integrity": "sha512-GYqKi1aH7PJXxdhTeZBFrg8vUBeKXi+cNprXsC1kpJcbcVnV9wBsrOu1cQEdG0WeQwlfHiy3XvnKfIrJ2R0NzQ==", - "bin": { - "direction": "cli.js" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/dlv": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", @@ -19186,15 +19234,6 @@ "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" }, - "node_modules/immer": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz", - "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/immer" - } - }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -19724,11 +19763,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/is-hotkey": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/is-hotkey/-/is-hotkey-0.2.0.tgz", - "integrity": "sha512-UknnZK4RakDmTgz4PI1wIph5yxSs/mvChWs9ifnlXsKuXgWmOkY/hAE0H/k2MIqH0RlRye0i1oC07MCRSD28Mw==" - }, "node_modules/is-inside-container": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", @@ -20291,11 +20325,6 @@ "url": "https://github.com/sponsors/panva" } }, - "node_modules/js-base64": { - "version": "3.7.7", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.7.tgz", - "integrity": "sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==" - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -26616,18 +26645,6 @@ "react-dom": ">=16.6.0" } }, - "node_modules/react-virtuoso": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/react-virtuoso/-/react-virtuoso-4.12.0.tgz", - "integrity": "sha512-oHrKlU7xHsrnBQ89ecZoMPAK0tHnI9s1hsFW3KKg5ZGeZ5SWvbGhg/QFJFY4XETAzoCUeu+Xaxn1OUb/PGtPlA==", - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "react": ">=16 || >=17 || >= 18", - "react-dom": ">=16 || >=17 || >= 18" - } - }, "node_modules/read": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", @@ -28270,57 +28287,6 @@ "node": ">=8" } }, - "node_modules/slate": { - "version": "0.110.2", - "resolved": "https://registry.npmjs.org/slate/-/slate-0.110.2.tgz", - "integrity": "sha512-4xGULnyMCiEQ0Ml7JAC1A6HVE6MNpPJU7Eq4cXh1LxlrR0dFXC3XC+rNfQtUJ7chHoPkws57x7DDiWiZAt+PBA==", - "dependencies": { - "immer": "^10.0.3", - "is-plain-object": "^5.0.0", - "tiny-warning": "^1.0.3" - } - }, - "node_modules/slate-history": { - "version": "0.110.3", - "resolved": "https://registry.npmjs.org/slate-history/-/slate-history-0.110.3.tgz", - "integrity": "sha512-sgdff4Usdflmw5ZUbhDkxFwCBQ2qlDKMMkF93w66KdV48vHOgN2BmLrf+2H8SdX8PYIpP/cTB0w8qWC2GwhDVA==", - "dependencies": { - "is-plain-object": "^5.0.0" - }, - "peerDependencies": { - "slate": ">=0.65.3" - } - }, - "node_modules/slate-hyperscript": { - "version": "0.100.0", - "resolved": "https://registry.npmjs.org/slate-hyperscript/-/slate-hyperscript-0.100.0.tgz", - "integrity": "sha512-fb2KdAYg6RkrQGlqaIi4wdqz3oa0S4zKNBJlbnJbNOwa23+9FLD6oPVx9zUGqCSIpy+HIpOeqXrg0Kzwh/Ii4A==", - "dependencies": { - "is-plain-object": "^5.0.0" - }, - "peerDependencies": { - "slate": ">=0.65.3" - } - }, - "node_modules/slate-react": { - "version": "0.110.3", - "resolved": "https://registry.npmjs.org/slate-react/-/slate-react-0.110.3.tgz", - "integrity": "sha512-AS8PPjwmsFS3Lq0MOEegLVlFoxhyos68G6zz2nW4sh3WeTXV7pX0exnwtY1a/docn+J3LGQO11aZXTenPXA/kg==", - "dependencies": { - "@juggle/resize-observer": "^3.4.0", - "direction": "^1.0.4", - "is-hotkey": "^0.2.0", - "is-plain-object": "^5.0.0", - "lodash": "^4.17.21", - "scroll-into-view-if-needed": "^3.1.0", - "tiny-invariant": "1.3.1" - }, - "peerDependencies": { - "react": ">=18.2.0", - "react-dom": ">=18.2.0", - "slate": ">=0.99.0" - } - }, "node_modules/smart-buffer": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", @@ -29172,16 +29138,6 @@ "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==" }, - "node_modules/tiny-invariant": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.1.tgz", - "integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==" - }, - "node_modules/tiny-warning": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", - "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" - }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -32444,10 +32400,11 @@ "@blocknote/xl-multi-column": "^0.22.0", "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.5", - "@liveblocks/client": "^2.11.0", - "@liveblocks/react": "^2.11.0", - "@liveblocks/react-ui": "^2.11.0", - "@liveblocks/yjs": "^2.11.0", + "@liveblocks/client": "file:../../liveblocks/packages/liveblocks-client", + "@liveblocks/react": "file:../../liveblocks/packages/liveblocks-react", + "@liveblocks/react-blocknote": "file:../../liveblocks/packages/liveblocks-react-blocknote", + "@liveblocks/react-ui": "file:../../liveblocks/packages/liveblocks-react-ui", + "@liveblocks/yjs": "file:../../liveblocks/packages/liveblocks-yjs", "@mantine/core": "^7.10.1", "@mui/icons-material": "^5.16.1", "@mui/material": "^5.16.1", diff --git a/packages/core/src/api/nodeConversions/nodeToBlock.ts b/packages/core/src/api/nodeConversions/nodeToBlock.ts index 4d73393dde..455499b01d 100644 --- a/packages/core/src/api/nodeConversions/nodeToBlock.ts +++ b/packages/core/src/api/nodeConversions/nodeToBlock.ts @@ -131,7 +131,10 @@ export function contentNodeToInlineContent< } else { const config = styleSchema[mark.type.name]; if (!config) { - if (mark.type.name === "liveblocksCommentMark") { + if ( + mark.type.spec.group?.includes("blocknoteIgnore") || + mark.type.name === "liveblocksCommentMark" + ) { // TODO continue; } diff --git a/packages/core/src/editor/Block.css b/packages/core/src/editor/Block.css index 54849c2b6c..ace7353fce 100644 --- a/packages/core/src/editor/Block.css +++ b/packages/core/src/editor/Block.css @@ -336,7 +336,7 @@ NESTED BLOCKS .bn-editor[contenteditable="true"] [data-file-block] .bn-add-file-button:hover, [data-file-block] .bn-file-name-with-icon:hover, -.ProseMirror-selectednode .bn-file-name-with-icon{ +.ProseMirror-selectednode .bn-file-name-with-icon { background-color: rgb(225, 225, 225); } @@ -523,3 +523,11 @@ NESTED BLOCKS .bn-block-column:last-child { padding-right: 0; } + +.bn-thread-mark { + background: rgba(255, 200, 0, 0.15); +} + +.bn-thread-mark-selected { + background: rgba(255, 200, 0, 0.25); +} diff --git a/packages/core/src/editor/BlockNoteEditor.ts b/packages/core/src/editor/BlockNoteEditor.ts index 0231b47735..9b19014c81 100644 --- a/packages/core/src/editor/BlockNoteEditor.ts +++ b/packages/core/src/editor/BlockNoteEditor.ts @@ -3,18 +3,14 @@ import { EditorOptions, Extension, getSchema, + isNodeSelection, Mark, + posToDOMRect, Node as TipTapNode, } from "@tiptap/core"; import { Node, Schema } from "prosemirror-model"; // import "./blocknote.css"; import * as Y from "yjs"; -import { - getBlock, - getNextBlock, - getParentBlock, - getPrevBlock, -} from "../api/blockManipulation/getBlock/getBlock.js"; import { insertBlocks } from "../api/blockManipulation/commands/insertBlocks/insertBlocks.js"; import { moveBlocksDown, @@ -29,15 +25,21 @@ import { import { removeBlocks } from "../api/blockManipulation/commands/removeBlocks/removeBlocks.js"; import { replaceBlocks } from "../api/blockManipulation/commands/replaceBlocks/replaceBlocks.js"; import { updateBlock } from "../api/blockManipulation/commands/updateBlock/updateBlock.js"; -import { insertContentAt } from "../api/blockManipulation/insertContentAt.js"; import { - getTextCursorPosition, - setTextCursorPosition, -} from "../api/blockManipulation/selections/textCursorPosition/textCursorPosition.js"; + getBlock, + getNextBlock, + getParentBlock, + getPrevBlock, +} from "../api/blockManipulation/getBlock/getBlock.js"; +import { insertContentAt } from "../api/blockManipulation/insertContentAt.js"; import { getSelection, setSelection, } from "../api/blockManipulation/selections/selection.js"; +import { + getTextCursorPosition, + setTextCursorPosition, +} from "../api/blockManipulation/selections/textCursorPosition/textCursorPosition.js"; import { createExternalHTMLExporter } from "../api/exporters/html/externalHTMLExporter.js"; import { blocksToMarkdown } from "../api/exporters/markdown/markdownExporter.js"; import { HTMLToBlocks } from "../api/parsers/html/parseHTML.js"; @@ -89,11 +91,12 @@ import { en } from "../i18n/locales/index.js"; import { Plugin, Transaction } from "@tiptap/pm/state"; import { dropCursor } from "prosemirror-dropcursor"; +import { EditorView } from "prosemirror-view"; import { createInternalHTMLSerializer } from "../api/exporters/html/internalHTMLSerializer.js"; import { inlineContentToNodes } from "../api/nodeConversions/blockToNode.js"; import { nodeToBlock } from "../api/nodeConversions/nodeToBlock.js"; +import { CommentsPlugin } from "../extensions/Comments/CommentsPlugin.js"; import "../style.css"; -import { EditorView } from "prosemirror-view"; export type BlockNoteExtension = | AnyExtension @@ -329,6 +332,7 @@ export class BlockNoteEditor< ISchema, SSchema >; + public readonly comments?: CommentsPlugin; /** * The `uploadFile` method is what the editor uses when files need to be uploaded (for example when selecting an image to upload). @@ -441,6 +445,7 @@ export class BlockNoteEditor< this.suggestionMenus = this.extensions["suggestionMenus"] as any; this.filePanel = this.extensions["filePanel"] as any; this.tableHandles = this.extensions["tableHandles"] as any; + this.comments = this.extensions["comments"] as any; if (newOptions.uploadFile) { const uploadFile = newOptions.uploadFile; @@ -1206,6 +1211,28 @@ export class BlockNoteEditor< }; } + public getSelectionBoundingBox() { + if (!this.prosemirrorView) { + return undefined; + } + const state = this.prosemirrorView?.state; + const { selection } = state; + + // support for CellSelections + const { ranges } = selection; + const from = Math.min(...ranges.map((range) => range.$from.pos)); + const to = Math.max(...ranges.map((range) => range.$to.pos)); + + if (isNodeSelection(selection)) { + const node = this.prosemirrorView.nodeDOM(from) as HTMLElement; + if (node) { + return node.getBoundingClientRect(); + } + } + + return posToDOMRect(this.prosemirrorView, from, to); + } + public openSuggestionMenu( triggerCharacter: string, pluginState?: { diff --git a/packages/core/src/editor/BlockNoteExtensions.ts b/packages/core/src/editor/BlockNoteExtensions.ts index b6ce68a7c7..33052cb241 100644 --- a/packages/core/src/editor/BlockNoteExtensions.ts +++ b/packages/core/src/editor/BlockNoteExtensions.ts @@ -15,6 +15,8 @@ import { createDropFileExtension } from "../api/clipboard/fromClipboard/fileDrop import { createPasteFromClipboardExtension } from "../api/clipboard/fromClipboard/pasteExtension.js"; import { createCopyToClipboardExtension } from "../api/clipboard/toClipboard/copyExtension.js"; import { BackgroundColorExtension } from "../extensions/BackgroundColor/BackgroundColorExtension.js"; +import { CommentMark } from "../extensions/Comments/CommentMark.js"; +import { CommentsPlugin } from "../extensions/Comments/CommentsPlugin.js"; import { FilePanelProsemirrorPlugin } from "../extensions/FilePanel/FilePanelPlugin.js"; import { FormattingToolbarProsemirrorPlugin } from "../extensions/FormattingToolbar/FormattingToolbarPlugin.js"; import { KeyboardShortcutsExtension } from "../extensions/KeyboardShortcuts/KeyboardShortcutsExtension.js"; @@ -120,6 +122,9 @@ export const getBlockNoteExtensions = < ret["nodeSelectionKeyboard"] = new NodeSelectionKeyboardPlugin(); + // TODO + ret["comments"] = new CommentsPlugin(opts.editor, CommentMark.name); + const disableExtensions: string[] = opts.disableExtensions || []; for (const ext of disableExtensions) { delete ret[ext]; @@ -240,6 +245,7 @@ const getTipTapExtensions = < ...(opts.trailingBlock === undefined || opts.trailingBlock ? [TrailingNode] : []), + CommentMark, ]; if (opts.collaboration) { diff --git a/packages/core/src/editor/editor.css b/packages/core/src/editor/editor.css index ba9dffb39d..5f3d97a6df 100644 --- a/packages/core/src/editor/editor.css +++ b/packages/core/src/editor/editor.css @@ -10,6 +10,10 @@ --N40: #dfe1e6; /* Light neutral used for subtle borders and text on dark background */ } +.bn-comment-composer .bn-editor { + padding: 0; +} + /* bn-root should be applied to all top-level elements diff --git a/packages/core/src/extensions/Comments/CommentMark.ts b/packages/core/src/extensions/Comments/CommentMark.ts new file mode 100644 index 0000000000..f7c6f7faa8 --- /dev/null +++ b/packages/core/src/extensions/Comments/CommentMark.ts @@ -0,0 +1,45 @@ +import { Mark, mergeAttributes } from "@tiptap/core"; + +export const CommentMark = Mark.create({ + name: "comment", + excludes: "", + inclusive: false, + keepOnSplit: true, + group: "blocknoteIgnore", // ignore in blocknote json + + addAttributes() { + // Return an object with attribute configuration + return { + // TODO: check if needed + orphan: { + parseHTML: (element) => !!element.getAttribute("data-orphan"), + renderHTML: (attributes) => { + return (attributes as { orphan: boolean }).orphan + ? { + "data-orphan": "true", + } + : {}; + }, + default: false, + }, + threadId: { + parseHTML: (element) => element.getAttribute("data-lb-thread-id"), + renderHTML: (attributes) => { + return { + "data-lb-thread-id": (attributes as { threadId: string }).threadId, + }; + }, + default: "", + }, + }; + }, + + renderHTML({ HTMLAttributes }: { HTMLAttributes: Record }) { + return [ + "span", + mergeAttributes(HTMLAttributes, { + class: "bn-thread-mark", + }), + ]; + }, +}); diff --git a/packages/core/src/extensions/Comments/CommentsPlugin.ts b/packages/core/src/extensions/Comments/CommentsPlugin.ts new file mode 100644 index 0000000000..0a01f421fa --- /dev/null +++ b/packages/core/src/extensions/Comments/CommentsPlugin.ts @@ -0,0 +1,336 @@ +import { Node } from "prosemirror-model"; +import { Plugin, PluginKey } from "prosemirror-state"; +import { Decoration, DecorationSet } from "prosemirror-view"; +import { v4 } from "uuid"; +import * as Y from "yjs"; +import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; +import { EventEmitter } from "../../util/EventEmitter.js"; +const PLUGIN_KEY = new PluginKey(`blocknote-comments`); + +enum CommentsPluginActions { + SET_SELECTED_THREAD_ID = "SET_SELECTED_THREAD_ID", +} + +type CommentsPluginAction = { + name: CommentsPluginActions; + data: string | null; +}; + +type CommentsPluginState = { + threadPositions: Map; + selectedThreadId: string | null; + // selectedThreadPos: number | null; + decorations: DecorationSet; +}; + +function updateState( + doc: Node, + selectedThreadId: string | null, + markType: string +) { + const threadPositions = new Map(); + const decorations: Decoration[] = []; + // find all thread marks and store their position + create decoration for selected thread + doc.descendants((node, pos) => { + node.marks.forEach((mark) => { + if (mark.type.name === markType) { + const thisThreadId = (mark.attrs as { threadId: string | undefined }) + .threadId; + if (!thisThreadId) { + return; + } + const from = pos; + const to = from + node.nodeSize; + + // FloatingThreads component uses "to" as the position, so always store the largest "to" found + // AnchoredThreads component uses "from" as the position, so always store the smallest "from" found + const currentPosition = threadPositions.get(thisThreadId) ?? { + from: Infinity, + to: 0, + }; + threadPositions.set(thisThreadId, { + from: Math.min(from, currentPosition.from), + to: Math.max(to, currentPosition.to), + }); + + if (selectedThreadId === thisThreadId) { + decorations.push( + Decoration.inline(from, to, { + class: "bn-thread-mark-selected", + }) + ); + } + } + }); + }); + return { + decorations: DecorationSet.create(doc, decorations), + selectedThreadId, + threadPositions, + selectedThreadPos: + selectedThreadId !== null + ? threadPositions.get(selectedThreadId)?.to ?? null + : null, + }; +} + +export class CommentsPlugin extends EventEmitter { + public readonly plugin: Plugin; + private provider: CommentProvider; + private pendingComment = false; + + constructor( + private readonly editor: BlockNoteEditor, + private readonly markType: string + ) { + super(); + + const doc = new Y.Doc(); + this.provider = new YjsCommentProvider( + editor, + "blablauserid", + doc.getMap("threads") + ); + + // TODO + setTimeout(() => { + editor.onSelectionChange(() => { + // TODO: filter out yjs transactions + if (this.pendingComment) { + this.pendingComment = false; + this.emit("update", { + pendingComment: this.pendingComment, + }); + } + }); + }, 600); + + this.plugin = new Plugin({ + key: PLUGIN_KEY, + state: { + init() { + return { + threadPositions: new Map(), + selectedThreadId: null, + decorations: DecorationSet.empty, + } satisfies CommentsPluginState; + }, + apply(tr, state) { + const action = tr.getMeta(PLUGIN_KEY) as CommentsPluginAction; + if (!tr.docChanged && !action) { + return state; + } + + if (!action) { + // Doc changed, but no action, just update rects + return updateState(tr.doc, state.selectedThreadId, markType); + } + // handle actions, possibly support more actions + if ( + action.name === CommentsPluginActions.SET_SELECTED_THREAD_ID && + state.selectedThreadId !== action.data + ) { + return updateState(tr.doc, action.data, markType); + } + + return state; + }, + }, + props: { + decorations(state) { + return PLUGIN_KEY.getState(state)?.decorations ?? DecorationSet.empty; + }, + handleClick: (view, pos, event) => { + if (event.button !== 0) { + return; + } + + const selectThread = (threadId: string | null) => { + view.dispatch( + view.state.tr.setMeta(PLUGIN_KEY, { + name: CommentsPluginActions.SET_SELECTED_THREAD_ID, + data: threadId, + }) + ); + }; + + const node = view.state.doc.nodeAt(pos); + if (!node) { + selectThread(null); + return; + } + const commentMark = node.marks.find( + (mark) => mark.type.name === markType + ); + // don't allow selecting orphaned threads + if (commentMark?.attrs.orphan) { + selectThread(null); + return; + } + const threadId = commentMark?.attrs.threadId as string | undefined; + selectThread(threadId ?? null); + }, + }, + }); + } + + public onUpdate(callback: (state: { pendingComment: boolean }) => void) { + return this.on("update", callback); + } + + public addPendingComment() { + this.pendingComment = true; + this.emit("update", { + pendingComment: this.pendingComment, + }); + } + + public async createThread(body: CommentBody) { + const thread = await this.provider.createThread({ + initialComment: { + body, + }, + }); + this.editor._tiptapEditor.commands.setMark(this.markType, { + threadId: thread.id, + }); + } +} + +type CommentBody = any; + +type CommentReaction = { + emoji: string; + createdAt: Date; + users: { + id: string; + }[]; +}; + +type Comment = { + type: "comment"; + id: string; + userId: string; + createdAt: Date; + updatedAt: Date; + reactions: CommentReaction[]; + // attachments: CommentAttachment[]; + metadata: any; + body: CommentBody; +}; + +type Thread = { + type: "thread"; + id: string; + createdAt: Date; + updatedAt: Date; + comments: Comment[]; + resolved: boolean; + resolvedUpdatedAt?: Date; + metadata: any; +}; + +export abstract class CommentProvider { + abstract createThread(options: { + initialComment: { + body: CommentBody; + metadata?: any; + }; + metadata?: any; + }): Promise; +} + +export class YjsCommentProvider extends CommentProvider { + constructor( + private readonly editor: BlockNoteEditor, + private readonly userId: string, + private readonly threadsYMap: Y.Map + ) { + super(); + } + + private commentToYMap(comment: Comment) { + const yMap = new Y.Map(); + yMap.set("id", comment.id); + yMap.set("userId", comment.userId); + yMap.set("createdAt", comment.createdAt.toISOString()); + yMap.set("updatedAt", comment.updatedAt.toISOString()); + if (comment.reactions.length > 0) { + throw new Error("Reactions should be empty in commentToYMap"); + } + yMap.set("reactions", new Y.Array()); + yMap.set("metadata", comment.metadata); + yMap.set("body", comment.body); + return yMap; + } + + private threadToYMap(thread: Thread) { + const yMap = new Y.Map(); + yMap.set("id", thread.id); + yMap.set("createdAt", thread.createdAt.toISOString()); + yMap.set("updatedAt", thread.updatedAt.toISOString()); + const commentsArray = new Y.Array>(); + + commentsArray.push( + thread.comments.map((comment) => this.commentToYMap(comment)) + ); + + yMap.set("comments", commentsArray); + yMap.set("resolved", thread.resolved); + yMap.set("resolvedUpdatedAt", thread.resolvedUpdatedAt?.toISOString()); + yMap.set("metadata", thread.metadata); + return yMap; + } + + public async createThread(options: { + initialComment: { + body: CommentBody; + metadata?: any; + }; + metadata?: any; + }) { + const date = new Date(); + + const comment: Comment = { + type: "comment", + id: v4(), + userId: this.userId, + createdAt: date, + updatedAt: date, + reactions: [], + metadata: options.metadata, + body: options.initialComment.body, + }; + + const thread: Thread = { + type: "thread", + id: v4(), + createdAt: date, + updatedAt: date, + comments: [comment], + resolved: false, + metadata: options.metadata, + }; + + this.threadsYMap.set(thread.id, this.threadToYMap(thread)); + + return thread; + } +} + +export class LiveblocksCommentProvider { + constructor(private readonly editor: BlockNoteEditor) {} + + public async createThread() { + const x = useCreateThread(); + return x; + } +} + +export class TiptapCommentProvider { + constructor(private readonly editor: BlockNoteEditor) {} + + public async createThread() { + this.editor._tiptapEditor.commands.setMark(this.markType, { threadId: id }); + } +} diff --git a/packages/mantine/src/comments/Composer.tsx b/packages/mantine/src/comments/Composer.tsx new file mode 100644 index 0000000000..5b61ff4d5e --- /dev/null +++ b/packages/mantine/src/comments/Composer.tsx @@ -0,0 +1,39 @@ +import { assertEmpty } from "@blocknote/core"; +import { ComponentProps } from "@blocknote/react"; +import { Button, Flex, Paper } from "@mantine/core"; +import { forwardRef } from "react"; +import { BlockNoteView } from "../index.js"; + +export const Composer = forwardRef< + HTMLDivElement, + ComponentProps["Comments"]["Composer"] +>((props, ref) => { + const { className, editor, onSubmit, ...rest } = props; + + assertEmpty(rest, false); + + return ( + + + {/* TODO: extract / change to icon? */} + + + + + ); +}); diff --git a/packages/mantine/src/index.tsx b/packages/mantine/src/index.tsx index 136e122806..7c93c5b8d7 100644 --- a/packages/mantine/src/index.tsx +++ b/packages/mantine/src/index.tsx @@ -20,6 +20,7 @@ import { removeBlockNoteCSSVariables, Theme, } from "./BlockNoteTheme.js"; +import { Composer } from "./comments/Composer.js"; import { TextInput } from "./form/TextInput.js"; import { Menu, @@ -52,7 +53,6 @@ import { TableHandle } from "./tableHandle/TableHandle.js"; import { Toolbar } from "./toolbar/Toolbar.js"; import { ToolbarButton } from "./toolbar/ToolbarButton.js"; import { ToolbarSelect } from "./toolbar/ToolbarSelect.js"; - export * from "./BlockNoteTheme.js"; export * from "./defaultThemes.js"; @@ -113,6 +113,9 @@ export const components: Components = { Content: PopoverContent, }, }, + Comments: { + Composer, + }, }; const mantineTheme = { diff --git a/packages/react/src/components/Comments/Composer.tsx b/packages/react/src/components/Comments/Composer.tsx new file mode 100644 index 0000000000..8c60fae364 --- /dev/null +++ b/packages/react/src/components/Comments/Composer.tsx @@ -0,0 +1,55 @@ +import { BlockNoteSchema, defaultBlockSpecs } from "@blocknote/core"; + +import { useCreateBlockNote } from "@blocknote/react"; +import { + ComponentProps, + useComponentsContext, +} from "../../editor/ComponentsContext.js"; +import { useBlockNoteEditor } from "../../hooks/useBlockNoteEditor.js"; +import { useDictionary } from "../../i18n/dictionary.js"; + +type PanelProps = ComponentProps["FilePanel"]["Root"]; + +// TODO: disable props on paragraph +const schema = BlockNoteSchema.create({ + blockSpecs: { + paragraph: defaultBlockSpecs.paragraph, + }, +}); + +/** + * By default, the FilePanel component will render with default tabs. However, + * you can override the tabs to render by passing the `tabs` prop. You can use + * the default tab panels in the `DefaultTabPanels` directory or make your own + * using the `FilePanelPanel` component. + */ +export const Composer = () => { + const dict = useDictionary(); + const editor = useBlockNoteEditor(); + + const commentEditor = useCreateBlockNote({ + trailingBlock: false, + dictionary: { + ...dict, + placeholders: { + ...dict.placeholders, + default: "Write a comment...", // TODO: only for empty doc + }, + }, + schema, + }); + + const components = useComponentsContext()!; + + return ( + { + editor.comments!.createThread({ + body: editor.document, + }); + }} + /> + ); +}; diff --git a/packages/react/src/components/Comments/FloatingComposerController.tsx b/packages/react/src/components/Comments/FloatingComposerController.tsx new file mode 100644 index 0000000000..40807ee537 --- /dev/null +++ b/packages/react/src/components/Comments/FloatingComposerController.tsx @@ -0,0 +1,76 @@ +import { + BlockSchema, + DefaultBlockSchema, + DefaultInlineContentSchema, + DefaultStyleSchema, + InlineContentSchema, + StyleSchema, +} from "@blocknote/core"; +import { UseFloatingOptions, flip, offset } from "@floating-ui/react"; +import { FC, useMemo } from "react"; + +import { useBlockNoteEditor } from "../../hooks/useBlockNoteEditor.js"; +import { useUIElementPositioning } from "../../hooks/useUIElementPositioning.js"; +import { useUIPluginState } from "../../hooks/useUIPluginState.js"; +import { Composer } from "./Composer.js"; + +export const FloatingComposerController = < + B extends BlockSchema = DefaultBlockSchema, + I extends InlineContentSchema = DefaultInlineContentSchema, + S extends StyleSchema = DefaultStyleSchema +>(props: { + filePanel?: FC; + floatingOptions?: Partial; +}) => { + const editor = useBlockNoteEditor(); + + if (!editor.comments) { + throw new Error( + "FloatingComposerController can only be used when BlockNote editor has enabled comments" + ); + } + + const state = useUIPluginState( + editor.comments.onUpdate.bind(editor.comments) + ); + + const referencePos = useMemo(() => { + if (!state?.pendingComment) { + return null; + } + + // TODO: update referencepos when doc changes (remote updates) + return editor.getSelectionBoundingBox(); + }, [editor, state?.pendingComment]); + + // TODO: review + const { isMounted, ref, style, getFloatingProps } = useUIElementPositioning( + state?.pendingComment || false, + referencePos || null, + 5000, + { + placement: "bottom", + middleware: [offset(10), flip()], + onOpenChange: (open) => { + if (!open) { + editor.filePanel!.closeMenu(); + editor.focus(); + } + }, + ...props.floatingOptions, + } + ); + + if (!isMounted || !state) { + return null; + } + + const Component = props.filePanel || Composer; + + return ( +
+ {/*
hello
*/} + +
+ ); +}; diff --git a/packages/react/src/components/Comments/FloatingThreadsController.tsx b/packages/react/src/components/Comments/FloatingThreadsController.tsx new file mode 100644 index 0000000000..aa6c72db1c --- /dev/null +++ b/packages/react/src/components/Comments/FloatingThreadsController.tsx @@ -0,0 +1,76 @@ +import { + BlockSchema, + DefaultBlockSchema, + DefaultInlineContentSchema, + DefaultStyleSchema, + InlineContentSchema, + StyleSchema, +} from "@blocknote/core"; +import { UseFloatingOptions, flip, offset } from "@floating-ui/react"; +import { FC, useMemo } from "react"; + +import { useBlockNoteEditor } from "../../hooks/useBlockNoteEditor.js"; +import { useUIElementPositioning } from "../../hooks/useUIElementPositioning.js"; +import { useUIPluginState } from "../../hooks/useUIPluginState.js"; +import { Composer } from "./Composer.js"; + +export const FloatingThreadController = < + B extends BlockSchema = DefaultBlockSchema, + I extends InlineContentSchema = DefaultInlineContentSchema, + S extends StyleSchema = DefaultStyleSchema +>(props: { + filePanel?: FC; + floatingOptions?: Partial; +}) => { + const editor = useBlockNoteEditor(); + + if (!editor.comments) { + throw new Error( + "FloatingComposerController can only be used when BlockNote editor has enabled comments" + ); + } + + const state = useUIPluginState( + editor.comments.onUpdate.bind(editor.comments) + ); + + const referencePos = useMemo(() => { + if (!state?.pendingComment) { + return null; + } + + // TODO: update referencepos when doc changes (remote updates) + return editor.getSelectionBoundingBox(); + }, [editor, state?.pendingComment]); + + // TODO: review + const { isMounted, ref, style, getFloatingProps } = useUIElementPositioning( + state?.pendingComment || false, + referencePos || null, + 5000, + { + placement: "bottom", + middleware: [offset(10), flip()], + onOpenChange: (open) => { + if (!open) { + editor.filePanel!.closeMenu(); + editor.focus(); + } + }, + ...props.floatingOptions, + } + ); + + if (!isMounted || !state) { + return null; + } + + const Component = props.filePanel || Composer; + + return ( +
+ {/*
hello
*/} + +
+ ); +}; diff --git a/packages/react/src/components/FormattingToolbar/DefaultButtons/AddCommentButton.tsx b/packages/react/src/components/FormattingToolbar/DefaultButtons/AddCommentButton.tsx index 50c8a6911e..c37cfcf0d4 100644 --- a/packages/react/src/components/FormattingToolbar/DefaultButtons/AddCommentButton.tsx +++ b/packages/react/src/components/FormattingToolbar/DefaultButtons/AddCommentButton.tsx @@ -17,13 +17,14 @@ export const AddCommentButton = () => { >(); const onClick = useCallback(() => { - (editor._tiptapEditor as any).chain().focus().addPendingComment().run(); + editor.comments?.addPendingComment(); + editor.formattingToolbar.closeMenu(); }, [editor]); if ( // We manually check if a comment extension (like liveblocks) is installed // By adding default support for this, the user doesn't need to customize the formatting toolbar - !(editor._tiptapEditor.commands as any)["addPendingComment"] || + !editor.comments || !editor.isEditable ) { return null; diff --git a/packages/react/src/editor/BlockNoteDefaultUI.tsx b/packages/react/src/editor/BlockNoteDefaultUI.tsx index 757dac7989..88cf55c01f 100644 --- a/packages/react/src/editor/BlockNoteDefaultUI.tsx +++ b/packages/react/src/editor/BlockNoteDefaultUI.tsx @@ -1,3 +1,4 @@ +import { FloatingComposerController } from "../components/Comments/FloatingComposerController.js"; import { FilePanelController } from "../components/FilePanel/FilePanelController.js"; import { FormattingToolbarController } from "../components/FormattingToolbar/FormattingToolbarController.js"; import { LinkToolbarController } from "../components/LinkToolbar/LinkToolbarController.js"; @@ -15,6 +16,7 @@ export type BlockNoteDefaultUIProps = { filePanel?: boolean; tableHandles?: boolean; emojiPicker?: boolean; + comments?: boolean; }; export function BlockNoteDefaultUI(props: BlockNoteDefaultUIProps) { @@ -45,6 +47,9 @@ export function BlockNoteDefaultUI(props: BlockNoteDefaultUIProps) { {editor.tableHandles && props.tableHandles !== false && ( )} + {editor.comments && props.comments !== false && ( + + )} ); } diff --git a/packages/react/src/editor/ComponentsContext.tsx b/packages/react/src/editor/ComponentsContext.tsx index 123264a199..af1e758bb5 100644 --- a/packages/react/src/editor/ComponentsContext.tsx +++ b/packages/react/src/editor/ComponentsContext.tsx @@ -9,6 +9,7 @@ import { useContext, } from "react"; +import { BlockNoteEditor } from "@blocknote/core"; import { DefaultReactGridSuggestionItem } from "../components/SuggestionMenu/GridSuggestionMenu/types.js"; import { DefaultReactSuggestionItem } from "../components/SuggestionMenu/types.js"; @@ -258,6 +259,13 @@ export type ComponentProps = { }; }; }; + Comments: { + Composer: { + className?: string; + editor: BlockNoteEditor; + onSubmit: () => void; + }; + }; }; export type Components = { diff --git a/playground/package.json b/playground/package.json index ca5a6c881f..d1e04bc77f 100644 --- a/playground/package.json +++ b/playground/package.json @@ -24,10 +24,11 @@ "@tiptap/suggestion": "^2.7.1", "@tiptap/core": "^2.7.1", "@tiptap/react": "^2.7.1", - "@liveblocks/client": "^2.11.0", - "@liveblocks/react": "^2.11.0", - "@liveblocks/react-ui": "^2.11.0", - "@liveblocks/yjs": "^2.11.0", + "@liveblocks/client": "file:../../liveblocks/packages/liveblocks-client", + "@liveblocks/react": "file:../../liveblocks/packages/liveblocks-react", + "@liveblocks/react-blocknote": "file:../../liveblocks/packages/liveblocks-react-blocknote", + "@liveblocks/react-ui": "file:../../liveblocks/packages/liveblocks-react-ui", + "@liveblocks/yjs": "file:../../liveblocks/packages/liveblocks-yjs", "@mantine/core": "^7.10.1", "@mui/icons-material": "^5.16.1", "@mui/material": "^5.16.1", diff --git a/playground/src/style.css b/playground/src/style.css index 7e716f2b47..43b27bc082 100644 --- a/playground/src/style.css +++ b/playground/src/style.css @@ -10,6 +10,10 @@ body { max-width: 731px; } +.bn-comment-composer .bn-container { + padding-top: 0; +} + .mantine-AppShell-navbar { background-color: #f7f7f5; } diff --git a/playground/tsconfig.json b/playground/tsconfig.json index 6ca41017b8..04c99061c5 100644 --- a/playground/tsconfig.json +++ b/playground/tsconfig.json @@ -23,6 +23,8 @@ { "path": "./tsconfig.node.json" }, { "path": "../packages/core/" }, { "path": "../packages/react/" }, + { "path": "../packages/ariakit/" }, + { "path": "../packages/mantine/" }, { "path": "../packages/shadcn/" }, { "path": "../packages/xl-pdf-exporter/" }, { "path": "../packages/xl-docx-exporter/" }, From 23275a07eef92c71f7437b377785448b05a24514 Mon Sep 17 00:00:00 2001 From: yousefed Date: Mon, 13 Jan 2025 17:16:31 +0100 Subject: [PATCH 012/144] wip --- .../src/extensions/Comments/CommentMark.ts | 4 +- .../src/extensions/Comments/CommentsPlugin.ts | 167 ++--- .../core/src/extensions/Comments/types.ts | 32 + packages/core/src/index.ts | 4 +- packages/core/src/util/browser.ts | 2 +- packages/mantine/src/BlockNoteView.tsx | 100 +++ packages/mantine/src/comments/Card.tsx | 46 ++ packages/mantine/src/comments/Comment.tsx | 25 + packages/mantine/src/comments/Composer.tsx | 2 +- packages/mantine/src/components.tsx | 104 +++ packages/mantine/src/index.tsx | 196 +----- .../react/src/components/Comments/Comment.tsx | 599 ++++++++++++++++++ .../src/components/Comments/Composer.tsx | 30 +- ...oller.tsx => FloatingThreadController.tsx} | 66 +- .../react/src/components/Comments/Thread.tsx | 280 ++++++++ .../react/src/components/Comments/schema.ts | 8 + .../react/src/editor/BlockNoteDefaultUI.tsx | 6 +- .../react/src/editor/ComponentsContext.tsx | 13 + .../src/hooks/useUIElementPositioning.ts | 3 + 19 files changed, 1361 insertions(+), 326 deletions(-) create mode 100644 packages/core/src/extensions/Comments/types.ts create mode 100644 packages/mantine/src/BlockNoteView.tsx create mode 100644 packages/mantine/src/comments/Card.tsx create mode 100644 packages/mantine/src/comments/Comment.tsx create mode 100644 packages/mantine/src/components.tsx create mode 100644 packages/react/src/components/Comments/Comment.tsx rename packages/react/src/components/Comments/{FloatingThreadsController.tsx => FloatingThreadController.tsx} (52%) create mode 100644 packages/react/src/components/Comments/Thread.tsx create mode 100644 packages/react/src/components/Comments/schema.ts diff --git a/packages/core/src/extensions/Comments/CommentMark.ts b/packages/core/src/extensions/Comments/CommentMark.ts index f7c6f7faa8..3b4f2e8fee 100644 --- a/packages/core/src/extensions/Comments/CommentMark.ts +++ b/packages/core/src/extensions/Comments/CommentMark.ts @@ -23,10 +23,10 @@ export const CommentMark = Mark.create({ default: false, }, threadId: { - parseHTML: (element) => element.getAttribute("data-lb-thread-id"), + parseHTML: (element) => element.getAttribute("data-bn-thread-id"), renderHTML: (attributes) => { return { - "data-lb-thread-id": (attributes as { threadId: string }).threadId, + "data-bn-thread-id": (attributes as { threadId: string }).threadId, }; }, default: "", diff --git a/packages/core/src/extensions/Comments/CommentsPlugin.ts b/packages/core/src/extensions/Comments/CommentsPlugin.ts index 0a01f421fa..0b0bb40c5c 100644 --- a/packages/core/src/extensions/Comments/CommentsPlugin.ts +++ b/packages/core/src/extensions/Comments/CommentsPlugin.ts @@ -5,6 +5,7 @@ import { v4 } from "uuid"; import * as Y from "yjs"; import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; import { EventEmitter } from "../../util/EventEmitter.js"; +import { CommentBody, CommentData, ThreadData } from "./types.js"; const PLUGIN_KEY = new PluginKey(`blocknote-comments`); enum CommentsPluginActions { @@ -18,14 +19,14 @@ type CommentsPluginAction = { type CommentsPluginState = { threadPositions: Map; - selectedThreadId: string | null; + // selectedThreadId: string | null; // selectedThreadPos: number | null; decorations: DecorationSet; }; function updateState( doc: Node, - selectedThreadId: string | null, + selectedThreadId: string | undefined, markType: string ) { const threadPositions = new Map(); @@ -65,19 +66,22 @@ function updateState( }); return { decorations: DecorationSet.create(doc, decorations), - selectedThreadId, threadPositions, - selectedThreadPos: - selectedThreadId !== null - ? threadPositions.get(selectedThreadId)?.to ?? null - : null, }; } export class CommentsPlugin extends EventEmitter { public readonly plugin: Plugin; - private provider: CommentProvider; + public readonly store: ThreadStore; private pendingComment = false; + private selectedThreadId: string | undefined; + + private emitStateUpdate() { + this.emit("update", { + selectedThreadId: this.selectedThreadId, + pendingComment: this.pendingComment, + }); + } constructor( private readonly editor: BlockNoteEditor, @@ -86,7 +90,7 @@ export class CommentsPlugin extends EventEmitter { super(); const doc = new Y.Doc(); - this.provider = new YjsCommentProvider( + this.store = new YjsThreadStore( editor, "blablauserid", doc.getMap("threads") @@ -98,20 +102,19 @@ export class CommentsPlugin extends EventEmitter { // TODO: filter out yjs transactions if (this.pendingComment) { this.pendingComment = false; - this.emit("update", { - pendingComment: this.pendingComment, - }); + this.emitStateUpdate(); } }); }, 600); + const self = this; + this.plugin = new Plugin({ key: PLUGIN_KEY, state: { init() { return { threadPositions: new Map(), - selectedThreadId: null, decorations: DecorationSet.empty, } satisfies CommentsPluginState; }, @@ -121,19 +124,8 @@ export class CommentsPlugin extends EventEmitter { return state; } - if (!action) { - // Doc changed, but no action, just update rects - return updateState(tr.doc, state.selectedThreadId, markType); - } - // handle actions, possibly support more actions - if ( - action.name === CommentsPluginActions.SET_SELECTED_THREAD_ID && - state.selectedThreadId !== action.data - ) { - return updateState(tr.doc, action.data, markType); - } - - return state; + // Doc changed, but no action, just update rects + return updateState(tr.doc, self.selectedThreadId, markType); }, }, props: { @@ -145,18 +137,19 @@ export class CommentsPlugin extends EventEmitter { return; } - const selectThread = (threadId: string | null) => { + const selectThread = (threadId: string | undefined) => { + self.selectedThreadId = threadId; + self.emitStateUpdate(); view.dispatch( view.state.tr.setMeta(PLUGIN_KEY, { name: CommentsPluginActions.SET_SELECTED_THREAD_ID, - data: threadId, }) ); }; const node = view.state.doc.nodeAt(pos); if (!node) { - selectThread(null); + selectThread(undefined); return; } const commentMark = node.marks.find( @@ -164,83 +157,57 @@ export class CommentsPlugin extends EventEmitter { ); // don't allow selecting orphaned threads if (commentMark?.attrs.orphan) { - selectThread(null); + selectThread(undefined); return; } const threadId = commentMark?.attrs.threadId as string | undefined; - selectThread(threadId ?? null); + selectThread(threadId); }, }, }); } - public onUpdate(callback: (state: { pendingComment: boolean }) => void) { + public onUpdate( + callback: (state: { + pendingComment: boolean; + selectedThreadId: string | undefined; + }) => void + ) { return this.on("update", callback); } public addPendingComment() { this.pendingComment = true; - this.emit("update", { - pendingComment: this.pendingComment, - }); + this.emitStateUpdate(); } - public async createThread(body: CommentBody) { - const thread = await this.provider.createThread({ - initialComment: { - body, - }, - }); + public async createThread(options: { + initialComment: { + body: CommentBody; + metadata?: any; + }; + metadata?: any; + }) { + const thread = await this.store.createThread(options); this.editor._tiptapEditor.commands.setMark(this.markType, { threadId: thread.id, }); } } -type CommentBody = any; - -type CommentReaction = { - emoji: string; - createdAt: Date; - users: { - id: string; - }[]; -}; - -type Comment = { - type: "comment"; - id: string; - userId: string; - createdAt: Date; - updatedAt: Date; - reactions: CommentReaction[]; - // attachments: CommentAttachment[]; - metadata: any; - body: CommentBody; -}; - -type Thread = { - type: "thread"; - id: string; - createdAt: Date; - updatedAt: Date; - comments: Comment[]; - resolved: boolean; - resolvedUpdatedAt?: Date; - metadata: any; -}; - -export abstract class CommentProvider { +export abstract class ThreadStore { abstract createThread(options: { initialComment: { body: CommentBody; metadata?: any; }; metadata?: any; - }): Promise; + }): Promise; + + abstract getThread(threadId: string): ThreadData; } -export class YjsCommentProvider extends CommentProvider { +export class YjsThreadStore extends ThreadStore { constructor( private readonly editor: BlockNoteEditor, private readonly userId: string, @@ -249,7 +216,7 @@ export class YjsCommentProvider extends CommentProvider { super(); } - private commentToYMap(comment: Comment) { + private commentToYMap(comment: CommentData) { const yMap = new Y.Map(); yMap.set("id", comment.id); yMap.set("userId", comment.userId); @@ -264,7 +231,7 @@ export class YjsCommentProvider extends CommentProvider { return yMap; } - private threadToYMap(thread: Thread) { + private threadToYMap(thread: ThreadData) { const yMap = new Y.Map(); yMap.set("id", thread.id); yMap.set("createdAt", thread.createdAt.toISOString()); @@ -282,6 +249,40 @@ export class YjsCommentProvider extends CommentProvider { return yMap; } + private yMapToComment(yMap: Y.Map): CommentData { + return { + type: "comment", + id: yMap.get("id"), + userId: yMap.get("userId"), + createdAt: new Date(yMap.get("createdAt")), + updatedAt: new Date(yMap.get("updatedAt")), + reactions: [], + metadata: yMap.get("metadata"), + body: yMap.get("body"), + }; + } + + private yMapToThread(yMap: Y.Map): ThreadData { + return { + type: "thread", + id: yMap.get("id"), + createdAt: new Date(yMap.get("createdAt")), + updatedAt: new Date(yMap.get("updatedAt")), + comments: ((yMap.get("comments") as Y.Array>) || []).map( + (comment) => this.yMapToComment(comment) + ), + resolved: yMap.get("resolved"), + resolvedUpdatedAt: yMap.get("resolvedUpdatedAt"), + metadata: yMap.get("metadata"), + }; + } + + // TODO: async / reactive interface? + public getThread(threadId: string) { + const thread = this.yMapToThread(this.threadsYMap.get(threadId)); + return thread; + } + public async createThread(options: { initialComment: { body: CommentBody; @@ -291,7 +292,7 @@ export class YjsCommentProvider extends CommentProvider { }) { const date = new Date(); - const comment: Comment = { + const comment: CommentData = { type: "comment", id: v4(), userId: this.userId, @@ -302,7 +303,7 @@ export class YjsCommentProvider extends CommentProvider { body: options.initialComment.body, }; - const thread: Thread = { + const thread: ThreadData = { type: "thread", id: v4(), createdAt: date, @@ -318,7 +319,7 @@ export class YjsCommentProvider extends CommentProvider { } } -export class LiveblocksCommentProvider { +export class LiveblocksThreadStore { constructor(private readonly editor: BlockNoteEditor) {} public async createThread() { @@ -327,7 +328,7 @@ export class LiveblocksCommentProvider { } } -export class TiptapCommentProvider { +export class TiptapThreadStore { constructor(private readonly editor: BlockNoteEditor) {} public async createThread() { diff --git a/packages/core/src/extensions/Comments/types.ts b/packages/core/src/extensions/Comments/types.ts new file mode 100644 index 0000000000..db7fc2bede --- /dev/null +++ b/packages/core/src/extensions/Comments/types.ts @@ -0,0 +1,32 @@ +export type CommentBody = any; + +export type CommentReactionData = { + emoji: string; + createdAt: Date; + users: { + id: string; + }[]; +}; + +export type CommentData = { + type: "comment"; + id: string; + userId: string; + createdAt: Date; + updatedAt: Date; + reactions: CommentReactionData[]; + // attachments: CommentAttachment[]; + metadata: any; + body: CommentBody; +}; + +export type ThreadData = { + type: "thread"; + id: string; + createdAt: Date; + updatedAt: Date; + comments: CommentData[]; + resolved: boolean; + resolvedUpdatedAt?: Date; + metadata: any; +}; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 7b0115ae47..aa12a962d8 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,4 +1,5 @@ import * as locales from "./i18n/locales/index.js"; +export * from "./api/blockManipulation/commands/updateBlock/updateBlock.js"; export * from "./api/exporters/html/externalHTMLExporter.js"; export * from "./api/exporters/html/internalHTMLSerializer.js"; export * from "./api/getBlockInfoFromPos.js"; @@ -53,7 +54,6 @@ export * from "./util/string.js"; export * from "./util/typescript.js"; export { UnreachableCaseError, assertEmpty } from "./util/typescript.js"; export { locales }; -export * from "./api/blockManipulation/commands/updateBlock/updateBlock.js"; // for testing from react (TODO: move): export * from "./api/nodeConversions/blockToNode.js"; @@ -65,3 +65,5 @@ export * from "./extensions/UniqueID/UniqueID.js"; export * from "./api/exporters/markdown/markdownExporter.js"; export * from "./api/parsers/html/parseHTML.js"; export * from "./api/parsers/markdown/parseMarkdown.js"; + +export * from "./extensions/Comments/types.js"; diff --git a/packages/core/src/util/browser.ts b/packages/core/src/util/browser.ts index 9ecdf3250d..2a0a2905bd 100644 --- a/packages/core/src/util/browser.ts +++ b/packages/core/src/util/browser.ts @@ -12,7 +12,7 @@ export function formatKeyboardShortcut(shortcut: string, ctrlText = "Ctrl") { } } -export function mergeCSSClasses(...classes: string[]) { +export function mergeCSSClasses(...classes: (string | false | undefined)[]) { return classes.filter((c) => c).join(" "); } diff --git a/packages/mantine/src/BlockNoteView.tsx b/packages/mantine/src/BlockNoteView.tsx new file mode 100644 index 0000000000..f9403d6a10 --- /dev/null +++ b/packages/mantine/src/BlockNoteView.tsx @@ -0,0 +1,100 @@ +import { + BlockSchema, + InlineContentSchema, + mergeCSSClasses, + StyleSchema, +} from "@blocknote/core"; +import { + BlockNoteViewProps, + BlockNoteViewRaw, + ComponentsContext, + useBlockNoteContext, + usePrefersColorScheme, +} from "@blocknote/react"; +import { MantineProvider } from "@mantine/core"; +import { useCallback } from "react"; + +import { + applyBlockNoteCSSVariablesFromTheme, + removeBlockNoteCSSVariables, + Theme, +} from "./BlockNoteTheme.js"; +import { components } from "./components.js"; +import "./style.css"; +export * from "./BlockNoteTheme.js"; +export * from "./defaultThemes.js"; + +const mantineTheme = { + // Removes button press effect + activeClassName: "", +}; + +export const BlockNoteView = < + BSchema extends BlockSchema, + ISchema extends InlineContentSchema, + SSchema extends StyleSchema +>( + props: Omit, "theme"> & { + theme?: + | "light" + | "dark" + | Theme + | { + light: Theme; + dark: Theme; + }; + } +) => { + const { className, theme, ...rest } = props; + + const existingContext = useBlockNoteContext(); + const systemColorScheme = usePrefersColorScheme(); + const defaultColorScheme = + existingContext?.colorSchemePreference || systemColorScheme; + + const ref = useCallback( + (node: HTMLDivElement | null) => { + if (!node) { + // todo: clean variables? + return; + } + + removeBlockNoteCSSVariables(node); + + if (typeof theme === "object") { + if ("light" in theme && "dark" in theme) { + applyBlockNoteCSSVariablesFromTheme( + theme[defaultColorScheme === "dark" ? "dark" : "light"], + node + ); + return; + } + + applyBlockNoteCSSVariablesFromTheme(theme, node); + return; + } + }, + [defaultColorScheme, theme] + ); + + return ( + + {/* `cssVariablesSelector` scopes Mantine CSS variables to only the editor, */} + {/* as proposed here: https://github.com/orgs/mantinedev/discussions/5685 */} + undefined}> + + + + ); +}; diff --git a/packages/mantine/src/comments/Card.tsx b/packages/mantine/src/comments/Card.tsx new file mode 100644 index 0000000000..2e50ea092d --- /dev/null +++ b/packages/mantine/src/comments/Card.tsx @@ -0,0 +1,46 @@ +import { assertEmpty } from "@blocknote/core"; +import { ComponentProps } from "@blocknote/react"; +import { Card as MantineCard } from "@mantine/core"; +import { forwardRef } from "react"; + +export const Card = forwardRef< + HTMLDivElement, + ComponentProps["Comments"]["Card"] +>((props, ref) => { + const { className, children, ...rest } = props; + + assertEmpty(rest, false); + + return ( + + {children} + + ); +}); + +export const CardSection = forwardRef< + HTMLDivElement, + ComponentProps["Comments"]["CardSection"] +>((props, ref) => { + const { className, children, ...rest } = props; + + assertEmpty(rest, false); + + return ( + + {children} + + ); +}); diff --git a/packages/mantine/src/comments/Comment.tsx b/packages/mantine/src/comments/Comment.tsx new file mode 100644 index 0000000000..f00947a035 --- /dev/null +++ b/packages/mantine/src/comments/Comment.tsx @@ -0,0 +1,25 @@ +import { assertEmpty } from "@blocknote/core"; +import { ComponentProps } from "@blocknote/react"; +import { forwardRef } from "react"; +import { BlockNoteView } from "../BlockNoteView.js"; + +export const Comment = forwardRef< + HTMLDivElement, + ComponentProps["Comments"]["Comment"] +>((props, ref) => { + const { className, editor, editable, ...rest } = props; + + assertEmpty(rest, false); + + return ( + + ); +}); diff --git a/packages/mantine/src/comments/Composer.tsx b/packages/mantine/src/comments/Composer.tsx index 5b61ff4d5e..648016ca33 100644 --- a/packages/mantine/src/comments/Composer.tsx +++ b/packages/mantine/src/comments/Composer.tsx @@ -2,7 +2,7 @@ import { assertEmpty } from "@blocknote/core"; import { ComponentProps } from "@blocknote/react"; import { Button, Flex, Paper } from "@mantine/core"; import { forwardRef } from "react"; -import { BlockNoteView } from "../index.js"; +import { BlockNoteView } from "../BlockNoteView.js"; export const Composer = forwardRef< HTMLDivElement, diff --git a/packages/mantine/src/components.tsx b/packages/mantine/src/components.tsx new file mode 100644 index 0000000000..f8645f19bc --- /dev/null +++ b/packages/mantine/src/components.tsx @@ -0,0 +1,104 @@ +import { Components } from "@blocknote/react"; + +import { Card, CardSection } from "./comments/Card.js"; +import { Comment } from "./comments/Comment.js"; +import { Composer } from "./comments/Composer.js"; +import { TextInput } from "./form/TextInput.js"; +import { + Menu, + MenuDivider, + MenuDropdown, + MenuItem, + MenuLabel, + MenuTrigger, +} from "./menu/Menu.js"; +import { Panel } from "./panel/Panel.js"; +import { PanelButton } from "./panel/PanelButton.js"; +import { PanelFileInput } from "./panel/PanelFileInput.js"; +import { PanelTab } from "./panel/PanelTab.js"; +import { PanelTextInput } from "./panel/PanelTextInput.js"; +import { Popover, PopoverContent, PopoverTrigger } from "./popover/Popover.js"; +import { SideMenu } from "./sideMenu/SideMenu.js"; +import { SideMenuButton } from "./sideMenu/SideMenuButton.js"; +import "./style.css"; +import { SuggestionMenu } from "./suggestionMenu/SuggestionMenu.js"; +import { SuggestionMenuEmptyItem } from "./suggestionMenu/SuggestionMenuEmptyItem.js"; +import { SuggestionMenuItem } from "./suggestionMenu/SuggestionMenuItem.js"; +import { SuggestionMenuLabel } from "./suggestionMenu/SuggestionMenuLabel.js"; +import { SuggestionMenuLoader } from "./suggestionMenu/SuggestionMenuLoader.js"; +import { GridSuggestionMenu } from "./suggestionMenu/gridSuggestionMenu/GridSuggestionMenu.js"; +import { GridSuggestionMenuEmptyItem } from "./suggestionMenu/gridSuggestionMenu/GridSuggestionMenuEmptyItem.js"; +import { GridSuggestionMenuItem } from "./suggestionMenu/gridSuggestionMenu/GridSuggestionMenuItem.js"; +import { GridSuggestionMenuLoader } from "./suggestionMenu/gridSuggestionMenu/GridSuggestionMenuLoader.js"; +import { ExtendButton } from "./tableHandle/ExtendButton.js"; +import { TableHandle } from "./tableHandle/TableHandle.js"; +import { Toolbar } from "./toolbar/Toolbar.js"; +import { ToolbarButton } from "./toolbar/ToolbarButton.js"; +import { ToolbarSelect } from "./toolbar/ToolbarSelect.js"; +export * from "./BlockNoteTheme.js"; +export * from "./defaultThemes.js"; + +export const components: Components = { + FormattingToolbar: { + Root: Toolbar, + Button: ToolbarButton, + Select: ToolbarSelect, + }, + FilePanel: { + Root: Panel, + Button: PanelButton, + FileInput: PanelFileInput, + TabPanel: PanelTab, + TextInput: PanelTextInput, + }, + GridSuggestionMenu: { + Root: GridSuggestionMenu, + Item: GridSuggestionMenuItem, + EmptyItem: GridSuggestionMenuEmptyItem, + Loader: GridSuggestionMenuLoader, + }, + LinkToolbar: { + Root: Toolbar, + Button: ToolbarButton, + }, + SideMenu: { + Root: SideMenu, + Button: SideMenuButton, + }, + SuggestionMenu: { + Root: SuggestionMenu, + Item: SuggestionMenuItem, + EmptyItem: SuggestionMenuEmptyItem, + Label: SuggestionMenuLabel, + Loader: SuggestionMenuLoader, + }, + TableHandle: { + Root: TableHandle, + ExtendButton: ExtendButton, + }, + Generic: { + Form: { + Root: (props) =>
{props.children}
, + TextInput: TextInput, + }, + Menu: { + Root: Menu, + Trigger: MenuTrigger, + Dropdown: MenuDropdown, + Divider: MenuDivider, + Label: MenuLabel, + Item: MenuItem, + }, + Popover: { + Root: Popover, + Trigger: PopoverTrigger, + Content: PopoverContent, + }, + }, + Comments: { + Comment, + Composer, + Card, + CardSection, + }, +}; diff --git a/packages/mantine/src/index.tsx b/packages/mantine/src/index.tsx index 7c93c5b8d7..f3f6a4bfc8 100644 --- a/packages/mantine/src/index.tsx +++ b/packages/mantine/src/index.tsx @@ -1,194 +1,2 @@ -import { - BlockSchema, - InlineContentSchema, - mergeCSSClasses, - StyleSchema, -} from "@blocknote/core"; -import { - BlockNoteViewProps, - BlockNoteViewRaw, - Components, - ComponentsContext, - useBlockNoteContext, - usePrefersColorScheme, -} from "@blocknote/react"; -import { MantineProvider } from "@mantine/core"; -import { useCallback } from "react"; - -import { - applyBlockNoteCSSVariablesFromTheme, - removeBlockNoteCSSVariables, - Theme, -} from "./BlockNoteTheme.js"; -import { Composer } from "./comments/Composer.js"; -import { TextInput } from "./form/TextInput.js"; -import { - Menu, - MenuDivider, - MenuDropdown, - MenuItem, - MenuLabel, - MenuTrigger, -} from "./menu/Menu.js"; -import { Panel } from "./panel/Panel.js"; -import { PanelButton } from "./panel/PanelButton.js"; -import { PanelFileInput } from "./panel/PanelFileInput.js"; -import { PanelTab } from "./panel/PanelTab.js"; -import { PanelTextInput } from "./panel/PanelTextInput.js"; -import { Popover, PopoverContent, PopoverTrigger } from "./popover/Popover.js"; -import { SideMenu } from "./sideMenu/SideMenu.js"; -import { SideMenuButton } from "./sideMenu/SideMenuButton.js"; -import "./style.css"; -import { GridSuggestionMenu } from "./suggestionMenu/gridSuggestionMenu/GridSuggestionMenu.js"; -import { GridSuggestionMenuEmptyItem } from "./suggestionMenu/gridSuggestionMenu/GridSuggestionMenuEmptyItem.js"; -import { GridSuggestionMenuItem } from "./suggestionMenu/gridSuggestionMenu/GridSuggestionMenuItem.js"; -import { GridSuggestionMenuLoader } from "./suggestionMenu/gridSuggestionMenu/GridSuggestionMenuLoader.js"; -import { SuggestionMenu } from "./suggestionMenu/SuggestionMenu.js"; -import { SuggestionMenuEmptyItem } from "./suggestionMenu/SuggestionMenuEmptyItem.js"; -import { SuggestionMenuItem } from "./suggestionMenu/SuggestionMenuItem.js"; -import { SuggestionMenuLabel } from "./suggestionMenu/SuggestionMenuLabel.js"; -import { SuggestionMenuLoader } from "./suggestionMenu/SuggestionMenuLoader.js"; -import { ExtendButton } from "./tableHandle/ExtendButton.js"; -import { TableHandle } from "./tableHandle/TableHandle.js"; -import { Toolbar } from "./toolbar/Toolbar.js"; -import { ToolbarButton } from "./toolbar/ToolbarButton.js"; -import { ToolbarSelect } from "./toolbar/ToolbarSelect.js"; -export * from "./BlockNoteTheme.js"; -export * from "./defaultThemes.js"; - -export const components: Components = { - FormattingToolbar: { - Root: Toolbar, - Button: ToolbarButton, - Select: ToolbarSelect, - }, - FilePanel: { - Root: Panel, - Button: PanelButton, - FileInput: PanelFileInput, - TabPanel: PanelTab, - TextInput: PanelTextInput, - }, - GridSuggestionMenu: { - Root: GridSuggestionMenu, - Item: GridSuggestionMenuItem, - EmptyItem: GridSuggestionMenuEmptyItem, - Loader: GridSuggestionMenuLoader, - }, - LinkToolbar: { - Root: Toolbar, - Button: ToolbarButton, - }, - SideMenu: { - Root: SideMenu, - Button: SideMenuButton, - }, - SuggestionMenu: { - Root: SuggestionMenu, - Item: SuggestionMenuItem, - EmptyItem: SuggestionMenuEmptyItem, - Label: SuggestionMenuLabel, - Loader: SuggestionMenuLoader, - }, - TableHandle: { - Root: TableHandle, - ExtendButton: ExtendButton, - }, - Generic: { - Form: { - Root: (props) =>
{props.children}
, - TextInput: TextInput, - }, - Menu: { - Root: Menu, - Trigger: MenuTrigger, - Dropdown: MenuDropdown, - Divider: MenuDivider, - Label: MenuLabel, - Item: MenuItem, - }, - Popover: { - Root: Popover, - Trigger: PopoverTrigger, - Content: PopoverContent, - }, - }, - Comments: { - Composer, - }, -}; - -const mantineTheme = { - // Removes button press effect - activeClassName: "", -}; - -export const BlockNoteView = < - BSchema extends BlockSchema, - ISchema extends InlineContentSchema, - SSchema extends StyleSchema ->( - props: Omit, "theme"> & { - theme?: - | "light" - | "dark" - | Theme - | { - light: Theme; - dark: Theme; - }; - } -) => { - const { className, theme, ...rest } = props; - - const existingContext = useBlockNoteContext(); - const systemColorScheme = usePrefersColorScheme(); - const defaultColorScheme = - existingContext?.colorSchemePreference || systemColorScheme; - - const ref = useCallback( - (node: HTMLDivElement | null) => { - if (!node) { - // todo: clean variables? - return; - } - - removeBlockNoteCSSVariables(node); - - if (typeof theme === "object") { - if ("light" in theme && "dark" in theme) { - applyBlockNoteCSSVariablesFromTheme( - theme[defaultColorScheme === "dark" ? "dark" : "light"], - node - ); - return; - } - - applyBlockNoteCSSVariablesFromTheme(theme, node); - return; - } - }, - [defaultColorScheme, theme] - ); - - return ( - - {/* `cssVariablesSelector` scopes Mantine CSS variables to only the editor, */} - {/* as proposed here: https://github.com/orgs/mantinedev/discussions/5685 */} - undefined}> - - - - ); -}; +export * from "./BlockNoteView.js"; +export * from "./components.js"; diff --git a/packages/react/src/components/Comments/Comment.tsx b/packages/react/src/components/Comments/Comment.tsx new file mode 100644 index 0000000000..6fedaec6a6 --- /dev/null +++ b/packages/react/src/components/Comments/Comment.tsx @@ -0,0 +1,599 @@ +"use client"; + +import { CommentData, mergeCSSClasses } from "@blocknote/core"; +import type { + ComponentPropsWithoutRef, + FormEvent, + MouseEvent, + ReactNode, + SyntheticEvent, +} from "react"; +import { forwardRef, useCallback, useEffect, useRef, useState } from "react"; +import { useComponentsContext } from "../../editor/ComponentsContext.js"; +import { useCreateBlockNote } from "../../hooks/useCreateBlockNote.js"; +import { useDictionary } from "../../i18n/dictionary.js"; +import { mergeRefs } from "../../util/mergeRefs.js"; +import { schema } from "./schema.js"; + +/** + * Liveblocks, but changed: + * - removed attachments + * - removed read status + */ +const REACTIONS_TRUNCATE = 5; + +export interface CommentProps extends ComponentPropsWithoutRef<"div"> { + /** + * The comment to display. + */ + comment: CommentData; + + /** + * How to show or hide the actions. + */ + showActions?: boolean | "hover"; + + /** + * Whether to show the comment if it was deleted. If set to `false`, it will render deleted comments as `null`. + */ + showDeleted?: boolean; + + /** + * Whether to show reactions. + */ + showReactions?: boolean; + + /** + * Whether to show the composer's formatting controls when editing the comment. + */ + // showComposerFormattingControls?: ComposerProps["showFormattingControls"]; + + /** + * Whether to indent the comment's content. + */ + indentContent?: boolean; + + /** + * The event handler called when the comment is edited. + */ + onCommentEdit?: (comment: CommentData) => void; + + /** + * The event handler called when the comment is deleted. + */ + onCommentDelete?: (comment: CommentData) => void; + + /** + * The event handler called when clicking on the author. + */ + onAuthorClick?: (userId: string, event: MouseEvent) => void; + + /** + * The event handler called when clicking on a mention. + */ + onMentionClick?: (userId: string, event: MouseEvent) => void; + + /** + * Override the component's strings. + */ + // overrides?: Partial; + + /** + * @internal + */ + autoMarkReadThreadId?: string; + + /** + * @internal + */ + additionalActions?: ReactNode; + + /** + * @internal + */ + additionalActionsClassName?: string; +} + +// interface CommentReactionButtonProps +// extends ComponentPropsWithoutRef { +// reaction: CommentReactionData; +// // overrides?: Partial; +// } + +// interface CommentReactionProps extends ComponentPropsWithoutRef<"button"> { +// comment: CommentData; +// reaction: CommentReactionData; +// // overrides?: Partial; +// } + +// type CommentNonInteractiveReactionProps = Omit; + +// const CommentReactionButton = forwardRef< +// HTMLButtonElement, +// CommentReactionButtonProps +// >(({ reaction, overrides, className, ...props }, forwardedRef) => { +// const $ = useOverrides(overrides); +// return ( +// +// ); +// }); + +// export const CommentReaction = forwardRef< +// HTMLButtonElement, +// CommentReactionProps +// >(({ comment, reaction, overrides, disabled, ...props }, forwardedRef) => { +// const addReaction = useAddRoomCommentReaction(comment.roomId); +// const removeReaction = useRemoveRoomCommentReaction(comment.roomId); +// const currentId = useCurrentUserId(); +// const isActive = useMemo(() => { +// return reaction.users.some((users) => users.id === currentId); +// }, [currentId, reaction]); +// const $ = useOverrides(overrides); +// const tooltipContent = useMemo( +// () => ( +// +// {$.COMMENT_REACTION_LIST( +// ( +// +// ))} +// formatRemaining={$.LIST_REMAINING_USERS} +// truncate={REACTIONS_TRUNCATE} +// locale={$.locale} +// />, +// reaction.emoji, +// reaction.users.length +// )} +// +// ), +// [$, reaction] +// ); + +// const stopPropagation = useCallback((event: SyntheticEvent) => { +// event.stopPropagation(); +// }, []); + +// const handlePressedChange = useCallback( +// (isPressed: boolean) => { +// if (isPressed) { +// addReaction({ +// threadId: comment.threadId, +// commentId: comment.id, +// emoji: reaction.emoji, +// }); +// } else { +// removeReaction({ +// threadId: comment.threadId, +// commentId: comment.id, +// emoji: reaction.emoji, +// }); +// } +// }, +// [addReaction, comment.threadId, comment.id, reaction.emoji, removeReaction] +// ); + +// return ( +// +// +// +// +// +// ); +// }); + +// export const CommentNonInteractiveReaction = forwardRef< +// HTMLButtonElement, +// CommentNonInteractiveReactionProps +// >(({ reaction, overrides, ...props }, forwardedRef) => { +// const currentId = useCurrentUserId(); +// const isActive = useMemo(() => { +// return reaction.users.some((users) => users.id === currentId); +// }, [currentId, reaction]); + +// return ( +// +// ); +// }); + +/** + * Displays a single comment. + * + * @example + * <> + * {thread.comments.map((comment) => ( + * + * ))} + * + */ +export const Comment = forwardRef( + ( + { + comment, + indentContent = true, + showDeleted, + showActions = "hover", + showReactions = true, + // showComposerFormattingControls = true, + onAuthorClick, + onMentionClick, + onCommentEdit, + onCommentDelete, + // overrides, + className, + additionalActions, + additionalActionsClassName, + autoMarkReadThreadId, + ...props + }, + forwardedRef + ) => { + const dict = useDictionary(); + + const commentEditor = useCreateBlockNote({ + initialContent: comment.body, + trailingBlock: false, + dictionary: { + ...dict, + placeholders: { + ...dict.placeholders, + default: "Edit comment...", // TODO: only for empty doc + }, + }, + schema, + }); + + const ctx = useComponentsContext()!; + + const ref = useRef(null); + const mergedRefs = mergeRefs([forwardedRef, ref]); + // const currentUserId = useCurrentUserId(); + // const deleteComment = useDeleteRoomComment(comment.roomId); + // const editComment = useEditRoomComment(comment.roomId); + // const addReaction = useAddRoomCommentReaction(comment.roomId); + // const removeReaction = useRemoveRoomCommentReaction(comment.roomId); + // const $ = useOverrides(overrides); + const [isEditing, setEditing] = useState(false); + const [isTarget, setTarget] = useState(false); + const [isMoreActionOpen, setMoreActionOpen] = useState(false); + const [isReactionActionOpen, setReactionActionOpen] = useState(false); + + const stopPropagation = useCallback((event: SyntheticEvent) => { + event.stopPropagation(); + }, []); + + const handleEdit = useCallback(() => { + setEditing(true); + }, []); + + const handleEditCancel = useCallback( + (event: MouseEvent) => { + event.stopPropagation(); + setEditing(false); + }, + [] + ); + + const handleEditSubmit = useCallback( + ( + { body, attachments }: ComposerSubmitComment, + event: FormEvent + ) => { + // TODO: Add a way to preventDefault from within this callback, to override the default behavior (e.g. showing a confirmation dialog) + onCommentEdit?.(comment); + + // event.preventDefault(); + // setEditing(false); + // editComment({ + // commentId: comment.id, + // threadId: comment.threadId, + // body, + // attachments, + // }); + }, + [comment, onCommentEdit] + ); + + const handleDelete = useCallback(() => { + // TODO: Add a way to preventDefault from within this callback, to override the default behavior (e.g. showing a confirmation dialog) + onCommentDelete?.(comment); + + // deleteComment({ + // commentId: comment.id, + // threadId: comment.threadId, + // }); + }, [comment, onCommentDelete]); + + // const handleAuthorClick = useCallback( + // (event: MouseEvent) => { + // onAuthorClick?.(comment.userId, event); + // }, + // [comment.userId, onAuthorClick] + // ); + + // const handleReactionSelect = useCallback( + // (emoji: string) => { + // const reactionIndex = comment.reactions.findIndex( + // (reaction) => reaction.emoji === emoji + // ); + + // if ( + // reactionIndex >= 0 && + // currentUserId && + // comment.reactions[reactionIndex]?.users.some( + // (user) => user.id === currentUserId + // ) + // ) { + // removeReaction({ + // threadId: comment.threadId, + // commentId: comment.id, + // emoji, + // }); + // } else { + // addReaction({ + // threadId: comment.threadId, + // commentId: comment.id, + // emoji, + // }); + // } + // }, + // [ + // addReaction, + // comment.id, + // comment.reactions, + // comment.threadId, + // removeReaction, + // currentUserId, + // ] + // ); + + useEffect(() => { + const isWindowDefined = typeof window !== "undefined"; + if (!isWindowDefined) { + return; + } + + const hash = window.location.hash; + const commentId = hash.slice(1); + + if (commentId === comment.id) { + setTarget(true); + } + }, []); // eslint-disable-line react-hooks/exhaustive-deps + + if (!showDeleted && !comment.body) { + return null; + } + + return ( +
+
+ {/*
+ + + + + + {comment.editedAt && comment.body && ( + <> + {" "} + + {$.COMMENT_EDITED} + + + )} + + +
*/} + {showActions && !isEditing && ( +
+ {additionalActions ?? null} + {/* {showReactions && ( + + + + + + + + )} */} + {/* {comment.userId === currentUserId && ( + + + + {$.COMMENT_EDIT} + + + + {$.COMMENT_DELETE} + + + }> + + + + + + + )} */} +
+ )} +
+
+ {isEditing ? ( + + ) : // + // + // + // + // }> + // + // + // + // + // + // } + // overrides={{ + // COMPOSER_PLACEHOLDER: $.COMMENT_EDIT_COMPOSER_PLACEHOLDER, + // }} + // roomId={comment.roomId} + // /> + comment.body ? ( + <> + + {/* ( + onMentionClick?.(userId, event)} + /> + ), + Link: CommentLink, + }} + /> */} + {showReactions && comment.reactions.length > 0 && ( +
+ {/* {comment.reactions.map((reaction) => ( + + ))} */} + {/* + + + + + + */} +
+ )} + + ) : ( +
+ {/*

{$.COMMENT_DELETED}

*/} +
+ )} +
+
+ ); + } +); diff --git a/packages/react/src/components/Comments/Composer.tsx b/packages/react/src/components/Comments/Composer.tsx index 8c60fae364..1457adf793 100644 --- a/packages/react/src/components/Comments/Composer.tsx +++ b/packages/react/src/components/Comments/Composer.tsx @@ -1,28 +1,8 @@ -import { BlockNoteSchema, defaultBlockSpecs } from "@blocknote/core"; - -import { useCreateBlockNote } from "@blocknote/react"; -import { - ComponentProps, - useComponentsContext, -} from "../../editor/ComponentsContext.js"; +import { useComponentsContext } from "../../editor/ComponentsContext.js"; import { useBlockNoteEditor } from "../../hooks/useBlockNoteEditor.js"; +import { useCreateBlockNote } from "../../hooks/useCreateBlockNote.js"; import { useDictionary } from "../../i18n/dictionary.js"; - -type PanelProps = ComponentProps["FilePanel"]["Root"]; - -// TODO: disable props on paragraph -const schema = BlockNoteSchema.create({ - blockSpecs: { - paragraph: defaultBlockSpecs.paragraph, - }, -}); - -/** - * By default, the FilePanel component will render with default tabs. However, - * you can override the tabs to render by passing the `tabs` prop. You can use - * the default tab panels in the `DefaultTabPanels` directory or make your own - * using the `FilePanelPanel` component. - */ +import { schema } from "./schema.js"; export const Composer = () => { const dict = useDictionary(); const editor = useBlockNoteEditor(); @@ -47,7 +27,9 @@ export const Composer = () => { editor={commentEditor} onSubmit={() => { editor.comments!.createThread({ - body: editor.document, + initialComment: { + body: commentEditor.document, + }, }); }} /> diff --git a/packages/react/src/components/Comments/FloatingThreadsController.tsx b/packages/react/src/components/Comments/FloatingThreadController.tsx similarity index 52% rename from packages/react/src/components/Comments/FloatingThreadsController.tsx rename to packages/react/src/components/Comments/FloatingThreadController.tsx index aa6c72db1c..17e7fcc5bb 100644 --- a/packages/react/src/components/Comments/FloatingThreadsController.tsx +++ b/packages/react/src/components/Comments/FloatingThreadController.tsx @@ -7,13 +7,21 @@ import { StyleSchema, } from "@blocknote/core"; import { UseFloatingOptions, flip, offset } from "@floating-ui/react"; -import { FC, useMemo } from "react"; +import { FC, useCallback, useEffect, useLayoutEffect } from "react"; import { useBlockNoteEditor } from "../../hooks/useBlockNoteEditor.js"; import { useUIElementPositioning } from "../../hooks/useUIElementPositioning.js"; import { useUIPluginState } from "../../hooks/useUIPluginState.js"; -import { Composer } from "./Composer.js"; +import { Thread } from "./Thread.js"; +/** + * This component is pretty close to the LiveBlocks FloatingThreads one. + * We have a bit of a different approach to communicating data to / from the plugin + */ + +/** + * TODO: docs + */ export const FloatingThreadController = < B extends BlockSchema = DefaultBlockSchema, I extends InlineContentSchema = DefaultInlineContentSchema, @@ -34,21 +42,9 @@ export const FloatingThreadController = < editor.comments.onUpdate.bind(editor.comments) ); - const referencePos = useMemo(() => { - if (!state?.pendingComment) { - return null; - } - - // TODO: update referencepos when doc changes (remote updates) - return editor.getSelectionBoundingBox(); - }, [editor, state?.pendingComment]); - // TODO: review - const { isMounted, ref, style, getFloatingProps } = useUIElementPositioning( - state?.pendingComment || false, - referencePos || null, - 5000, - { + const { isMounted, ref, style, getFloatingProps, setReference } = + useUIElementPositioning(!!state?.selectedThreadId, null, 5000, { placement: "bottom", middleware: [offset(10), flip()], onOpenChange: (open) => { @@ -58,19 +54,51 @@ export const FloatingThreadController = < } }, ...props.floatingOptions, + }); + + // TODO: could also use thread position from the state. prefer this? + const updateRef = useCallback(() => { + if (!state?.selectedThreadId) { + return; } - ); + + const el = editor.domElement?.querySelector( + `[data-bn-thread-id="${state?.selectedThreadId}"]` + ); + if (el) { + setReference(el); + } + }, [setReference, editor, state?.selectedThreadId]); + + // Remote cursor updates and other edits can cause the ref to break + useEffect(() => { + if (!state?.selectedThreadId) { + return; + } + + return editor.onChange(() => { + updateRef(); + }); + }, [editor, updateRef, state?.selectedThreadId]); + + useLayoutEffect(updateRef, [updateRef]); if (!isMounted || !state) { return null; } - const Component = props.filePanel || Composer; + if (!state.selectedThreadId) { + return null; // TODO + } + + const Component = props.filePanel || Thread; + + const thread = editor.comments.store.getThread(state.selectedThreadId); return (
{/*
hello
*/} - +
); }; diff --git a/packages/react/src/components/Comments/Thread.tsx b/packages/react/src/components/Comments/Thread.tsx new file mode 100644 index 0000000000..b17ffba6a1 --- /dev/null +++ b/packages/react/src/components/Comments/Thread.tsx @@ -0,0 +1,280 @@ +"use client"; + +import { ThreadData, mergeCSSClasses } from "@blocknote/core"; +import type { + ComponentPropsWithoutRef, + ForwardedRef, + SyntheticEvent, +} from "react"; +import { forwardRef, useCallback, useMemo } from "react"; +import { useComponentsContext } from "../../editor/ComponentsContext.js"; +import { Comment, CommentProps } from "./Comment.js"; + +export interface ThreadProps extends ComponentPropsWithoutRef<"div"> { + /** + * The thread to display. + */ + thread: ThreadData; + + /** + * How to show or hide the composer to reply to the thread. + */ + showComposer?: boolean | "collapsed"; + + /** + * Whether to show the action to resolve the thread. + */ + showResolveAction?: boolean; + + /** + * How to show or hide the actions. + */ + showActions?: CommentProps["showActions"]; + + /** + * Whether to show reactions. + */ + showReactions?: CommentProps["showReactions"]; + + /** + * Whether to show the composer's formatting controls. + */ + // showComposerFormattingControls?: ComposerProps["showFormattingControls"]; + + /** + * Whether to indent the comments' content. + */ + indentCommentContent?: CommentProps["indentContent"]; + + /** + * Whether to show deleted comments. + */ + showDeletedComments?: CommentProps["showDeleted"]; + + /** + * Whether to show attachments. + */ + showAttachments?: boolean; + + /** + * The event handler called when changing the resolved status. + */ + onResolvedChange?: (resolved: boolean) => void; + + /** + * The event handler called when a comment is edited. + */ + onCommentEdit?: CommentProps["onCommentEdit"]; + + /** + * The event handler called when a comment is deleted. + */ + onCommentDelete?: CommentProps["onCommentDelete"]; + + /** + * The event handler called when the thread is deleted. + * A thread is deleted when all its comments are deleted. + */ + onThreadDelete?: (thread: ThreadData) => void; + + /** + * The event handler called when clicking on a comment's author. + */ + onAuthorClick?: CommentProps["onAuthorClick"]; + + /** + * The event handler called when clicking on a mention. + */ + onMentionClick?: CommentProps["onMentionClick"]; + + /** + * The event handler called when clicking on a comment's attachment. + */ + // onAttachmentClick?: CommentProps["onAttachmentClick"]; + + /** + * The event handler called when the composer is submitted. + */ + // onComposerSubmit?: ComposerProps["onComposerSubmit"]; + + /** + * Override the component's strings. + */ + // overrides?: Partial< + // GlobalOverrides & ThreadOverrides & CommentOverrides & ComposerOverrides + // >; +} + +/** + * Displays a thread of comments, with a composer to reply + * to it. + * + * @example + * <> + * {threads.map((thread) => ( + * + * ))} + * + */ +export const Thread = forwardRef( + ( + { + thread, + indentCommentContent = true, + showActions = "hover", + showDeletedComments, + showResolveAction = true, + showReactions = true, + showComposer = "collapsed", + showAttachments = true, + // showComposerFormattingControls = true, + onResolvedChange, + onCommentEdit, + onCommentDelete, + onThreadDelete, + onAuthorClick, + onMentionClick, + // onAttachmentClick, + // onComposerSubmit, + // overrides, + className, + ...props + }: ThreadProps, + forwardedRef: ForwardedRef + ) => { + // const markThreadAsResolved = useMarkRoomThreadAsResolved(thread.roomId); + // const markThreadAsUnresolved = useMarkRoomThreadAsUnresolved(thread.roomId); + + const ctx = useComponentsContext()!; + const firstCommentIndex = useMemo(() => { + return showDeletedComments + ? 0 + : thread.comments.findIndex((comment) => comment.body); + }, [showDeletedComments, thread.comments]); + + const stopPropagation = useCallback((event: SyntheticEvent) => { + event.stopPropagation(); + }, []); + + // const handleResolvedChange = useCallback( + // (resolved: boolean) => { + // onResolvedChange?.(resolved); + + // if (resolved) { + // markThreadAsResolved(thread.id); + // } else { + // markThreadAsUnresolved(thread.id); + // } + // }, + // [ + // markThreadAsResolved, + // markThreadAsUnresolved, + // onResolvedChange, + // thread.id, + // ] + // ); + + // TODO: thread deletion + + // const handleCommentDelete = useCallback( + // (comment: Comment) => { + // onCommentDelete?.(comment); + + // const filteredComments = thread.comments.filter( + // (comment) => comment.body + // ); + + // if (filteredComments.length <= 1) { + // onThreadDelete?.(thread); + // } + // }, + // [onCommentDelete, onThreadDelete, thread] + // ); + + // TODO: extract component + return ( + + + {thread.comments.map((comment, index) => { + const isFirstComment = index === firstCommentIndex; + + return ( + + // + // + // + // + // ) : null + // } + /> + ); + })} + + + {/* {showComposer && ( + + )} */} + + + ); + } +) as (props: ThreadProps & RefAttributes) => JSX.Element; diff --git a/packages/react/src/components/Comments/schema.ts b/packages/react/src/components/Comments/schema.ts new file mode 100644 index 0000000000..c9078380d7 --- /dev/null +++ b/packages/react/src/components/Comments/schema.ts @@ -0,0 +1,8 @@ +import { BlockNoteSchema, defaultBlockSpecs } from "@blocknote/core"; + +// TODO: disable props on paragraph +export const schema = BlockNoteSchema.create({ + blockSpecs: { + paragraph: defaultBlockSpecs.paragraph, + }, +}); diff --git a/packages/react/src/editor/BlockNoteDefaultUI.tsx b/packages/react/src/editor/BlockNoteDefaultUI.tsx index 88cf55c01f..606389acab 100644 --- a/packages/react/src/editor/BlockNoteDefaultUI.tsx +++ b/packages/react/src/editor/BlockNoteDefaultUI.tsx @@ -1,4 +1,5 @@ import { FloatingComposerController } from "../components/Comments/FloatingComposerController.js"; +import { FloatingThreadController } from "../components/Comments/FloatingThreadController.js"; import { FilePanelController } from "../components/FilePanel/FilePanelController.js"; import { FormattingToolbarController } from "../components/FormattingToolbar/FormattingToolbarController.js"; import { LinkToolbarController } from "../components/LinkToolbar/LinkToolbarController.js"; @@ -48,7 +49,10 @@ export function BlockNoteDefaultUI(props: BlockNoteDefaultUIProps) { )} {editor.comments && props.comments !== false && ( - + <> + + + )} ); diff --git a/packages/react/src/editor/ComponentsContext.tsx b/packages/react/src/editor/ComponentsContext.tsx index af1e758bb5..3218c58073 100644 --- a/packages/react/src/editor/ComponentsContext.tsx +++ b/packages/react/src/editor/ComponentsContext.tsx @@ -260,11 +260,24 @@ export type ComponentProps = { }; }; Comments: { + Card: { + className?: string; + children?: ReactNode; + }; + CardSection: { + className?: string; + children?: ReactNode; + }; Composer: { className?: string; editor: BlockNoteEditor; onSubmit: () => void; }; + Comment: { + className?: string; + editable: boolean; + editor: BlockNoteEditor; + }; }; }; diff --git a/packages/react/src/hooks/useUIElementPositioning.ts b/packages/react/src/hooks/useUIElementPositioning.ts index 328e481eb9..1e9de1b10d 100644 --- a/packages/react/src/hooks/useUIElementPositioning.ts +++ b/packages/react/src/hooks/useUIElementPositioning.ts @@ -15,6 +15,7 @@ export function useUIElementPositioning( ) { const { refs, update, context, floatingStyles } = useFloating({ open: show, + strategy: "fixed", ...options, }); const { isMounted, styles } = useTransitionStyles(context); @@ -43,6 +44,7 @@ export function useUIElementPositioning( return { isMounted, ref: refs.setFloating, + setReference: refs.setReference, style: { display: "flex", ...styles, @@ -56,6 +58,7 @@ export function useUIElementPositioning( floatingStyles, isMounted, refs.setFloating, + refs.setReference, styles, zIndex, getFloatingProps, From 721a4e94b1834a1ef24cc4aae45077c14e136a27 Mon Sep 17 00:00:00 2001 From: yousefed Date: Tue, 14 Jan 2025 17:42:38 +0100 Subject: [PATCH 013/144] wip --- packages/mantine/src/comments/Comment.tsx | 68 +++- packages/mantine/src/comments/Editor.tsx | 25 ++ packages/mantine/src/components.tsx | 6 + .../react/src/components/Comments/Comment.tsx | 337 +++++++++--------- .../react/src/components/Comments/Thread.tsx | 23 +- .../react/src/editor/ComponentsContext.tsx | 76 ++-- 6 files changed, 306 insertions(+), 229 deletions(-) create mode 100644 packages/mantine/src/comments/Editor.tsx diff --git a/packages/mantine/src/comments/Comment.tsx b/packages/mantine/src/comments/Comment.tsx index f00947a035..0f382408b0 100644 --- a/packages/mantine/src/comments/Comment.tsx +++ b/packages/mantine/src/comments/Comment.tsx @@ -1,25 +1,71 @@ import { assertEmpty } from "@blocknote/core"; import { ComponentProps } from "@blocknote/react"; +import { Avatar, Group, Skeleton, Text } from "@mantine/core"; import { forwardRef } from "react"; -import { BlockNoteView } from "../BlockNoteView.js"; + +const AuthorInfo = forwardRef< + HTMLDivElement, + ComponentProps["Comments"]["Comment"] +>((props, ref) => { + const { className, authorInfo, timeString, actions, children, ...rest } = + props; + + assertEmpty(rest, false); + + if (authorInfo === "loading") { + return ( + + +
+ +
+
+ ); + } + + return ( + + + + + {authorInfo.username} + + {timeString} + + + + ); +}); export const Comment = forwardRef< HTMLDivElement, ComponentProps["Comments"]["Comment"] >((props, ref) => { - const { className, editor, editable, ...rest } = props; + const { className, authorInfo, timeString, actions, children, ...rest } = + props; assertEmpty(rest, false); return ( - + <> +
+ {actions} +
+ + {children} + ); }); diff --git a/packages/mantine/src/comments/Editor.tsx b/packages/mantine/src/comments/Editor.tsx new file mode 100644 index 0000000000..9975c608e0 --- /dev/null +++ b/packages/mantine/src/comments/Editor.tsx @@ -0,0 +1,25 @@ +import { assertEmpty } from "@blocknote/core"; +import { ComponentProps } from "@blocknote/react"; +import { forwardRef } from "react"; +import { BlockNoteView } from "../BlockNoteView.js"; + +export const Editor = forwardRef< + HTMLDivElement, + ComponentProps["Comments"]["Editor"] +>((props, ref) => { + const { className, editor, editable, ...rest } = props; + + assertEmpty(rest, false); + + return ( + + ); +}); diff --git a/packages/mantine/src/components.tsx b/packages/mantine/src/components.tsx index f8645f19bc..7e736d5a3f 100644 --- a/packages/mantine/src/components.tsx +++ b/packages/mantine/src/components.tsx @@ -3,6 +3,7 @@ import { Components } from "@blocknote/react"; import { Card, CardSection } from "./comments/Card.js"; import { Comment } from "./comments/Comment.js"; import { Composer } from "./comments/Composer.js"; +import { Editor } from "./comments/Editor.js"; import { TextInput } from "./form/TextInput.js"; import { Menu, @@ -94,9 +95,14 @@ export const components: Components = { Trigger: PopoverTrigger, Content: PopoverContent, }, + Toolbar: { + Root: Toolbar, + Button: ToolbarButton, + }, }, Comments: { Comment, + Editor, Composer, Card, CardSection, diff --git a/packages/react/src/components/Comments/Comment.tsx b/packages/react/src/components/Comments/Comment.tsx index 6fedaec6a6..b99973440a 100644 --- a/packages/react/src/components/Comments/Comment.tsx +++ b/packages/react/src/components/Comments/Comment.tsx @@ -274,7 +274,7 @@ export const Comment = forwardRef( schema, }); - const ctx = useComponentsContext()!; + const Components = useComponentsContext()!; const ref = useRef(null); const mergedRefs = mergeRefs([forwardedRef, ref]); @@ -396,174 +396,156 @@ export const Comment = forwardRef( return null; } + let actions: ReactNode | undefined = undefined; + + if (showActions && !isEditing) { + actions = ( + + {additionalActions ?? null} + {/* {showReactions && ( + + + + + + + + )} */} + + R1 + + + R2 + + + + + ... + + + + + Edit comment + + + Delete comment + + + + {/* {comment.userId === currentUserId && ( + + + + {$.COMMENT_EDIT} + + + + {$.COMMENT_DELETE} + + + }> + + + + + + + )} */} + + ); + } + + const timeString = + comment.createdAt.toLocaleDateString(undefined, { + month: "short", + day: "numeric", + }) + (comment.updatedAt !== comment.createdAt ? " (edited)" : ""); // TODO: needs editedAt? + return ( -
-
- {/*
- + {isEditing ? ( + + ) : // + // + // + // + // }> + // + // + // + // + // + // } + // overrides={{ + // COMPOSER_PLACEHOLDER: $.COMMENT_EDIT_COMPOSER_PLACEHOLDER, + // }} + // roomId={comment.roomId} + // /> + comment.body ? ( + <> + - - - - - {comment.editedAt && comment.body && ( - <> - {" "} - - {$.COMMENT_EDITED} - - - )} - - -
*/} - {showActions && !isEditing && ( -
- {additionalActions ?? null} - {/* {showReactions && ( - - - - - - - - )} */} - {/* {comment.userId === currentUserId && ( - - - - {$.COMMENT_EDIT} - - - - {$.COMMENT_DELETE} - - - }> - - - - - - - )} */} -
- )} -
-
- {isEditing ? ( - - ) : // - // - // - // - // }> - // - // - // - // - // - // } - // overrides={{ - // COMPOSER_PLACEHOLDER: $.COMMENT_EDIT_COMPOSER_PLACEHOLDER, - // }} - // roomId={comment.roomId} - // /> - comment.body ? ( - <> - - {/* ( - onMentionClick?.(userId, event)} - /> - ), - Link: CommentLink, - }} - /> */} - {showReactions && comment.reactions.length > 0 && ( -
- {/* {comment.reactions.map((reaction) => ( + {showReactions && comment.reactions.length > 0 && ( +
+ {/* {comment.reactions.map((reaction) => ( ( overrides={overrides} /> ))} */} - {/* + {/*
- )} - - ) : ( -
- {/*

{$.COMMENT_DELETED}

*/} -
- )} -
-
+
+ )} + + ) : ( +
+ {/*

{$.COMMENT_DELETED}

*/} +
+ )} + ); } ); diff --git a/packages/react/src/components/Comments/Thread.tsx b/packages/react/src/components/Comments/Thread.tsx index b17ffba6a1..23a293882d 100644 --- a/packages/react/src/components/Comments/Thread.tsx +++ b/packages/react/src/components/Comments/Thread.tsx @@ -1,14 +1,13 @@ "use client"; import { ThreadData, mergeCSSClasses } from "@blocknote/core"; -import type { - ComponentPropsWithoutRef, - ForwardedRef, - SyntheticEvent, -} from "react"; +import type { ComponentPropsWithoutRef, ForwardedRef } from "react"; import { forwardRef, useCallback, useMemo } from "react"; import { useComponentsContext } from "../../editor/ComponentsContext.js"; +import { useCreateBlockNote } from "../../hooks/useCreateBlockNote.js"; +import { useDictionary } from "../../i18n/dictionary.js"; import { Comment, CommentProps } from "./Comment.js"; +import { schema } from "./schema.js"; export interface ThreadProps extends ComponentPropsWithoutRef<"div"> { /** @@ -146,6 +145,19 @@ export const Thread = forwardRef( // const markThreadAsUnresolved = useMarkRoomThreadAsUnresolved(thread.roomId); const ctx = useComponentsContext()!; + const dict = useDictionary(); + const newCommentEditor = useCreateBlockNote({ + trailingBlock: false, + dictionary: { + ...dict, + placeholders: { + ...dict.placeholders, + default: "Add comment...", // TODO: only for empty doc + }, + }, + schema, + }); + const firstCommentIndex = useMemo(() => { return showDeletedComments ? 0 @@ -273,6 +285,7 @@ export const Thread = forwardRef( roomId={thread.roomId} /> )} */} + ); diff --git a/packages/react/src/editor/ComponentsContext.tsx b/packages/react/src/editor/ComponentsContext.tsx index 3218c58073..d127e0c939 100644 --- a/packages/react/src/editor/ComponentsContext.tsx +++ b/packages/react/src/editor/ComponentsContext.tsx @@ -13,24 +13,29 @@ import { BlockNoteEditor } from "@blocknote/core"; import { DefaultReactGridSuggestionItem } from "../components/SuggestionMenu/GridSuggestionMenu/types.js"; import { DefaultReactSuggestionItem } from "../components/SuggestionMenu/types.js"; +type ToolbarRootType = { + className?: string; + children?: ReactNode; + onMouseEnter?: () => void; + onMouseLeave?: () => void; +}; + +type ToolbarButtonType = { + className?: string; + mainTooltip: string; + secondaryTooltip?: string; + icon?: ReactNode; + onClick?: (e: MouseEvent) => void; + isSelected?: boolean; + isDisabled?: boolean; +} & ( + | { children: ReactNode; label?: string } + | { children?: undefined; label: string } +); export type ComponentProps = { FormattingToolbar: { - Root: { - className?: string; - children?: ReactNode; - }; - Button: { - className?: string; - mainTooltip: string; - secondaryTooltip?: string; - icon?: ReactNode; - onClick?: (e: MouseEvent) => void; - isSelected?: boolean; - isDisabled?: boolean; - } & ( - | { children: ReactNode; label?: string } - | { children?: undefined; label: string } - ); + Root: ToolbarRootType; + Button: ToolbarButtonType; Select: { className?: string; items: { @@ -82,24 +87,8 @@ export type ComponentProps = { }; }; LinkToolbar: { - Root: { - className?: string; - children?: ReactNode; - onMouseEnter?: () => void; - onMouseLeave?: () => void; - }; - Button: { - className?: string; - mainTooltip: string; - secondaryTooltip?: string; - icon?: ReactNode; - onClick?: (e: MouseEvent) => void; - isSelected?: boolean; - isDisabled?: boolean; - } & ( - | { children: ReactNode; label?: string } - | { children?: undefined; label: string } - ); + Root: ToolbarRootType; + Button: ToolbarButtonType; }; SideMenu: { Root: { @@ -258,6 +247,10 @@ export type ComponentProps = { children?: ReactNode; }; }; + Toolbar: { + Root: ToolbarRootType; + Button: ToolbarButtonType; + }; }; Comments: { Card: { @@ -268,16 +261,29 @@ export type ComponentProps = { className?: string; children?: ReactNode; }; + // TODO: same as editor? Composer: { className?: string; editor: BlockNoteEditor; onSubmit: () => void; }; - Comment: { + Editor: { className?: string; editable: boolean; editor: BlockNoteEditor; }; + Comment: { + className?: string; + children?: ReactNode; + authorInfo: + | "loading" + | { + username: string; + avatarUrl?: string; + }; + timeString: string; + actions?: ReactNode; + }; }; }; From e824c428f18f55edd833e46e842ef1d34e3cd3b9 Mon Sep 17 00:00:00 2001 From: yousefed Date: Wed, 15 Jan 2025 14:16:38 +0100 Subject: [PATCH 014/144] wip --- packages/core/src/editor/editor.css | 7 +- .../Placeholder/PlaceholderPlugin.ts | 7 +- packages/mantine/src/comments/Editor.tsx | 5 +- packages/mantine/src/toolbar/Toolbar.tsx | 19 ++- .../mantine/src/toolbar/ToolbarButton.tsx | 5 +- .../react/src/components/Comments/Comment.tsx | 124 ++++++------------ .../src/components/Comments/CommentEditor.tsx | 66 ++++++++++ .../src/components/Comments/Composer.tsx | 47 ++++--- .../Comments/FloatingComposerController.tsx | 2 +- .../Comments/FloatingThreadController.tsx | 4 +- .../react/src/components/Comments/Thread.tsx | 54 ++++---- .../react/src/editor/ComponentsContext.tsx | 4 + 12 files changed, 206 insertions(+), 138 deletions(-) create mode 100644 packages/react/src/components/Comments/CommentEditor.tsx diff --git a/packages/core/src/editor/editor.css b/packages/core/src/editor/editor.css index 5f3d97a6df..248c5313e7 100644 --- a/packages/core/src/editor/editor.css +++ b/packages/core/src/editor/editor.css @@ -10,7 +10,12 @@ --N40: #dfe1e6; /* Light neutral used for subtle borders and text on dark background */ } -.bn-comment-composer .bn-editor { +.bn-comment-editor { + width: 100%; + padding: 0; +} + +.bn-comment-editor .bn-editor { padding: 0; } diff --git a/packages/core/src/extensions/Placeholder/PlaceholderPlugin.ts b/packages/core/src/extensions/Placeholder/PlaceholderPlugin.ts index 440457eb29..db1681e89d 100644 --- a/packages/core/src/extensions/Placeholder/PlaceholderPlugin.ts +++ b/packages/core/src/extensions/Placeholder/PlaceholderPlugin.ts @@ -1,5 +1,6 @@ import { Plugin, PluginKey } from "prosemirror-state"; import { Decoration, DecorationSet } from "prosemirror-view"; +import { v4 } from "uuid"; import type { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; const PLUGIN_KEY = new PluginKey(`blocknote-placeholder`); @@ -12,7 +13,9 @@ export class PlaceholderPlugin { ) { this.plugin = new Plugin({ key: PLUGIN_KEY, - view: () => { + view: (view) => { + const uniqueEditorSelector = `placeholder-selector-${v4()}`; + view.dom.classList.add(uniqueEditorSelector); const styleEl = document.createElement("style"); const nonce = editor._tiptapEditor.options.injectNonce; if (nonce) { @@ -27,7 +30,7 @@ export class PlaceholderPlugin { const styleSheet = styleEl.sheet!; const getBaseSelector = (additionalSelectors = "") => - `.bn-block-content${additionalSelectors} .bn-inline-content:has(> .ProseMirror-trailingBreak:only-child):before`; + `.${uniqueEditorSelector} .bn-block-content${additionalSelectors} .bn-inline-content:has(> .ProseMirror-trailingBreak:only-child):before`; const getSelector = ( blockType: string | "default", diff --git a/packages/mantine/src/comments/Editor.tsx b/packages/mantine/src/comments/Editor.tsx index 9975c608e0..af5a88f4c2 100644 --- a/packages/mantine/src/comments/Editor.tsx +++ b/packages/mantine/src/comments/Editor.tsx @@ -7,12 +7,13 @@ export const Editor = forwardRef< HTMLDivElement, ComponentProps["Comments"]["Editor"] >((props, ref) => { - const { className, editor, editable, ...rest } = props; + const { className, onFocus, onBlur, editor, editable, ...rest } = props; assertEmpty(rest, false); return ( ); }); diff --git a/packages/mantine/src/toolbar/Toolbar.tsx b/packages/mantine/src/toolbar/Toolbar.tsx index 9ad042e850..47dd1cb6e4 100644 --- a/packages/mantine/src/toolbar/Toolbar.tsx +++ b/packages/mantine/src/toolbar/Toolbar.tsx @@ -1,4 +1,4 @@ -import { Group as MantineGroup } from "@mantine/core"; +import { Flex } from "@mantine/core"; import { assertEmpty } from "@blocknote/core"; import { ComponentProps } from "@blocknote/react"; @@ -10,7 +10,14 @@ type ToolbarProps = ComponentProps["FormattingToolbar"]["Root"] & export const Toolbar = forwardRef( (props, ref) => { - const { className, children, onMouseEnter, onMouseLeave, ...rest } = props; + const { + className, + children, + onMouseEnter, + onMouseLeave, + variant, + ...rest + } = props; assertEmpty(rest); @@ -22,15 +29,17 @@ export const Toolbar = forwardRef( const combinedRef = mergeRefs(ref, focusRef, trapRef); return ( - + onMouseLeave={onMouseLeave} + justify={variant === "action-toolbar" ? "flex-end" : undefined} + gap={variant === "action-toolbar" ? "xs" : undefined}> {children} - + ); } ); diff --git a/packages/mantine/src/toolbar/ToolbarButton.tsx b/packages/mantine/src/toolbar/ToolbarButton.tsx index bd22f04110..4f2fd9e5fc 100644 --- a/packages/mantine/src/toolbar/ToolbarButton.tsx +++ b/packages/mantine/src/toolbar/ToolbarButton.tsx @@ -40,6 +40,7 @@ export const ToolbarButton = forwardRef( isDisabled, onClick, label, + variant, ...rest } = props; @@ -75,7 +76,7 @@ export const ToolbarButton = forwardRef( mainTooltip.slice(0, 1).toLowerCase() + mainTooltip.replace(/\s+/g, "").slice(1) } - size={"xs"} + size={variant === "compact" ? "compact-xs" : "xs"} disabled={isDisabled || false} ref={ref} {...rest}> @@ -99,7 +100,7 @@ export const ToolbarButton = forwardRef( mainTooltip.slice(0, 1).toLowerCase() + mainTooltip.replace(/\s+/g, "").slice(1) } - size={30} + size={variant === "compact" ? 20 : 30} disabled={isDisabled || false} ref={ref} {...rest}> diff --git a/packages/react/src/components/Comments/Comment.tsx b/packages/react/src/components/Comments/Comment.tsx index b99973440a..967b21033e 100644 --- a/packages/react/src/components/Comments/Comment.tsx +++ b/packages/react/src/components/Comments/Comment.tsx @@ -3,7 +3,6 @@ import { CommentData, mergeCSSClasses } from "@blocknote/core"; import type { ComponentPropsWithoutRef, - FormEvent, MouseEvent, ReactNode, SyntheticEvent, @@ -13,6 +12,7 @@ import { useComponentsContext } from "../../editor/ComponentsContext.js"; import { useCreateBlockNote } from "../../hooks/useCreateBlockNote.js"; import { useDictionary } from "../../i18n/dictionary.js"; import { mergeRefs } from "../../util/mergeRefs.js"; +import { CommentEditor } from "./CommentEditor.js"; import { schema } from "./schema.js"; /** @@ -298,18 +298,15 @@ export const Comment = forwardRef( }, []); const handleEditCancel = useCallback( - (event: MouseEvent) => { + (event: MouseEvent) => { event.stopPropagation(); setEditing(false); }, - [] + [setEditing] ); const handleEditSubmit = useCallback( - ( - { body, attachments }: ComposerSubmitComment, - event: FormEvent - ) => { + (_event: MouseEvent) => { // TODO: Add a way to preventDefault from within this callback, to override the default behavior (e.g. showing a confirmation dialog) onCommentEdit?.(comment); @@ -423,15 +420,21 @@ export const Comment = forwardRef( )} */} - + R1 - + R2 - + ... @@ -444,40 +447,6 @@ export const Comment = forwardRef( - {/* {comment.userId === currentUserId && ( - - - - {$.COMMENT_EDIT} - - - - {$.COMMENT_DELETE} - - - }> - - - - - - - )} */} ); } @@ -498,51 +467,38 @@ export const Comment = forwardRef( timeString={timeString} actions={actions}> {isEditing ? ( - - ) : // - // - // - // - // }> - // - // - // - // - // - // } - // overrides={{ - // COMPOSER_PLACEHOLDER: $.COMMENT_EDIT_COMPOSER_PLACEHOLDER, - // }} - // roomId={comment.roomId} - // /> - comment.body ? ( + <> + ( + + + X + + + Save + + + )} + /> + + ) : comment.body ? ( <> + {showReactions && comment.reactions.length > 0 && (
{/* {comment.reactions.map((reaction) => ( diff --git a/packages/react/src/components/Comments/CommentEditor.tsx b/packages/react/src/components/Comments/CommentEditor.tsx new file mode 100644 index 0000000000..2fa3501379 --- /dev/null +++ b/packages/react/src/components/Comments/CommentEditor.tsx @@ -0,0 +1,66 @@ +import { BlockNoteEditor } from "@blocknote/core"; +import { FC, useCallback, useState } from "react"; +import { useComponentsContext } from "../../editor/ComponentsContext.js"; +import { useEditorChange } from "../../hooks/useEditorChange.js"; +import { schema } from "./schema.js"; + +function isDocumentEmpty( + editor: BlockNoteEditor< + typeof schema.blockSchema, + typeof schema.inlineContentSchema, + typeof schema.styleSchema + > +) { + return ( + editor.document.length === 0 || + (editor.document.length === 1 && + editor.document[0].type === "paragraph" && + editor.document[0].content.length === 0) + ); +} + +export const CommentEditor = (props: { + editable: boolean; + placeholder?: string; + actions?: FC<{ + isFocused: boolean; + isEmpty: boolean; + }>; + editor: BlockNoteEditor< + typeof schema.blockSchema, + typeof schema.inlineContentSchema, + typeof schema.styleSchema + >; +}) => { + const [isFocused, setIsFocused] = useState(false); + const [isEmpty, setIsEmpty] = useState(isDocumentEmpty(props.editor)); + + const components = useComponentsContext()!; + + useEditorChange(() => { + setIsEmpty(isDocumentEmpty(props.editor)); + }, props.editor); + + const onFocus = useCallback(() => { + setIsFocused(true); + }, []); + + const onBlur = useCallback(() => { + setIsFocused(false); + }, []); + + return ( + <> + + {props.actions && ( + + )} + + ); +}; diff --git a/packages/react/src/components/Comments/Composer.tsx b/packages/react/src/components/Comments/Composer.tsx index 1457adf793..d7d55bc3b7 100644 --- a/packages/react/src/components/Comments/Composer.tsx +++ b/packages/react/src/components/Comments/Composer.tsx @@ -2,12 +2,15 @@ import { useComponentsContext } from "../../editor/ComponentsContext.js"; import { useBlockNoteEditor } from "../../hooks/useBlockNoteEditor.js"; import { useCreateBlockNote } from "../../hooks/useCreateBlockNote.js"; import { useDictionary } from "../../i18n/dictionary.js"; +import { CommentEditor } from "./CommentEditor.js"; import { schema } from "./schema.js"; -export const Composer = () => { - const dict = useDictionary(); + +export function Composer() { const editor = useBlockNoteEditor(); + const Components = useComponentsContext()!; + const dict = useDictionary(); - const commentEditor = useCreateBlockNote({ + const newCommentEditor = useCreateBlockNote({ trailingBlock: false, dictionary: { ...dict, @@ -19,19 +22,29 @@ export const Composer = () => { schema, }); - const components = useComponentsContext()!; - return ( - { - editor.comments!.createThread({ - initialComment: { - body: commentEditor.document, - }, - }); - }} - /> + + ( + + { + editor.comments!.createThread({ + initialComment: { + body: newCommentEditor.document, + }, + }); + }}> + Save + + + )} + /> + ); -}; +} diff --git a/packages/react/src/components/Comments/FloatingComposerController.tsx b/packages/react/src/components/Comments/FloatingComposerController.tsx index 40807ee537..72a6b4c905 100644 --- a/packages/react/src/components/Comments/FloatingComposerController.tsx +++ b/packages/react/src/components/Comments/FloatingComposerController.tsx @@ -53,6 +53,7 @@ export const FloatingComposerController = < middleware: [offset(10), flip()], onOpenChange: (open) => { if (!open) { + // TODO editor.filePanel!.closeMenu(); editor.focus(); } @@ -69,7 +70,6 @@ export const FloatingComposerController = < return (
- {/*
hello
*/}
); diff --git a/packages/react/src/components/Comments/FloatingThreadController.tsx b/packages/react/src/components/Comments/FloatingThreadController.tsx index 17e7fcc5bb..65cfa5a180 100644 --- a/packages/react/src/components/Comments/FloatingThreadController.tsx +++ b/packages/react/src/components/Comments/FloatingThreadController.tsx @@ -49,8 +49,8 @@ export const FloatingThreadController = < middleware: [offset(10), flip()], onOpenChange: (open) => { if (!open) { - editor.filePanel!.closeMenu(); - editor.focus(); + // editor.filePanel!.closeMenu(); + // editor.focus(); } }, ...props.floatingOptions, diff --git a/packages/react/src/components/Comments/Thread.tsx b/packages/react/src/components/Comments/Thread.tsx index 23a293882d..09d61c2aad 100644 --- a/packages/react/src/components/Comments/Thread.tsx +++ b/packages/react/src/components/Comments/Thread.tsx @@ -7,6 +7,7 @@ import { useComponentsContext } from "../../editor/ComponentsContext.js"; import { useCreateBlockNote } from "../../hooks/useCreateBlockNote.js"; import { useDictionary } from "../../i18n/dictionary.js"; import { Comment, CommentProps } from "./Comment.js"; +import { CommentEditor } from "./CommentEditor.js"; import { schema } from "./schema.js"; export interface ThreadProps extends ComponentPropsWithoutRef<"div"> { @@ -144,8 +145,9 @@ export const Thread = forwardRef( // const markThreadAsResolved = useMarkRoomThreadAsResolved(thread.roomId); // const markThreadAsUnresolved = useMarkRoomThreadAsUnresolved(thread.roomId); - const ctx = useComponentsContext()!; + const Components = useComponentsContext()!; const dict = useDictionary(); + const newCommentEditor = useCreateBlockNote({ trailingBlock: false, dictionary: { @@ -205,16 +207,16 @@ export const Thread = forwardRef( // TODO: extract component return ( - - + {thread.comments.map((comment, index) => { const isFirstComment = index === firstCommentIndex; @@ -268,26 +270,32 @@ export const Thread = forwardRef( /> ); })} - - - {/* {showComposer && ( - + + { + if (!isFocused && isEmpty) { + return null; + } + + return ( + + + Save + + + ); + }} /> - )} */} - - - + + ); } ) as (props: ThreadProps & RefAttributes) => JSX.Element; diff --git a/packages/react/src/editor/ComponentsContext.tsx b/packages/react/src/editor/ComponentsContext.tsx index d127e0c939..8fd6a00e3d 100644 --- a/packages/react/src/editor/ComponentsContext.tsx +++ b/packages/react/src/editor/ComponentsContext.tsx @@ -18,6 +18,7 @@ type ToolbarRootType = { children?: ReactNode; onMouseEnter?: () => void; onMouseLeave?: () => void; + variant?: "default" | "action-toolbar"; }; type ToolbarButtonType = { @@ -28,6 +29,7 @@ type ToolbarButtonType = { onClick?: (e: MouseEvent) => void; isSelected?: boolean; isDisabled?: boolean; + variant?: "default" | "compact"; } & ( | { children: ReactNode; label?: string } | { children?: undefined; label: string } @@ -271,6 +273,8 @@ export type ComponentProps = { className?: string; editable: boolean; editor: BlockNoteEditor; + onFocus?: () => void; + onBlur?: () => void; }; Comment: { className?: string; From f2d4bb802e8a85b67e11a6b4604dadee79cdbfef Mon Sep 17 00:00:00 2001 From: yousefed Date: Thu, 16 Jan 2025 09:50:18 +0100 Subject: [PATCH 015/144] misc --- .../src/extensions/Comments/CommentsPlugin.ts | 153 +----- .../Comments/store/LiveBlocksThreadStore.ts | 8 + .../extensions/Comments/store/ThreadStore.ts | 59 +++ .../Comments/store/TipTapThreadStore.ts | 7 + .../Comments/store/YjsThreadStore.ts | 339 ++++++++++++ .../core/src/extensions/Comments/types.ts | 13 +- packages/mantine/src/comments/Comment.tsx | 51 +- packages/mantine/src/comments/Composer.tsx | 39 -- packages/mantine/src/components.tsx | 3 +- .../react/src/components/Comments/Comment.tsx | 498 +++++++----------- .../src/components/Comments/CommentEditor.tsx | 4 +- .../{Composer.tsx => FloatingComposer.tsx} | 7 +- .../Comments/FloatingComposerController.tsx | 10 +- .../Comments/FloatingThreadController.tsx | 6 +- .../react/src/components/Comments/Thread.tsx | 364 ++++++------- .../src/components/Comments/useThreadStore.ts | 28 + .../DefaultButtons/AddCommentButton.tsx | 2 +- .../react/src/editor/ComponentsContext.tsx | 7 +- 18 files changed, 900 insertions(+), 698 deletions(-) create mode 100644 packages/core/src/extensions/Comments/store/LiveBlocksThreadStore.ts create mode 100644 packages/core/src/extensions/Comments/store/ThreadStore.ts create mode 100644 packages/core/src/extensions/Comments/store/TipTapThreadStore.ts create mode 100644 packages/core/src/extensions/Comments/store/YjsThreadStore.ts delete mode 100644 packages/mantine/src/comments/Composer.tsx rename packages/react/src/components/Comments/{Composer.tsx => FloatingComposer.tsx} (88%) create mode 100644 packages/react/src/components/Comments/useThreadStore.ts diff --git a/packages/core/src/extensions/Comments/CommentsPlugin.ts b/packages/core/src/extensions/Comments/CommentsPlugin.ts index 0b0bb40c5c..ffec2d8700 100644 --- a/packages/core/src/extensions/Comments/CommentsPlugin.ts +++ b/packages/core/src/extensions/Comments/CommentsPlugin.ts @@ -1,11 +1,12 @@ import { Node } from "prosemirror-model"; import { Plugin, PluginKey } from "prosemirror-state"; import { Decoration, DecorationSet } from "prosemirror-view"; -import { v4 } from "uuid"; import * as Y from "yjs"; import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; import { EventEmitter } from "../../util/EventEmitter.js"; -import { CommentBody, CommentData, ThreadData } from "./types.js"; +import { ThreadStore } from "./store/ThreadStore.js"; +import { YjsThreadStore } from "./store/YjsThreadStore.js"; +import { CommentBody } from "./types.js"; const PLUGIN_KEY = new PluginKey(`blocknote-comments`); enum CommentsPluginActions { @@ -176,11 +177,16 @@ export class CommentsPlugin extends EventEmitter { return this.on("update", callback); } - public addPendingComment() { + public startPendingComment() { this.pendingComment = true; this.emitStateUpdate(); } + public stopPendingComment() { + this.pendingComment = false; + this.emitStateUpdate(); + } + public async createThread(options: { initialComment: { body: CommentBody; @@ -194,144 +200,3 @@ export class CommentsPlugin extends EventEmitter { }); } } - -export abstract class ThreadStore { - abstract createThread(options: { - initialComment: { - body: CommentBody; - metadata?: any; - }; - metadata?: any; - }): Promise; - - abstract getThread(threadId: string): ThreadData; -} - -export class YjsThreadStore extends ThreadStore { - constructor( - private readonly editor: BlockNoteEditor, - private readonly userId: string, - private readonly threadsYMap: Y.Map - ) { - super(); - } - - private commentToYMap(comment: CommentData) { - const yMap = new Y.Map(); - yMap.set("id", comment.id); - yMap.set("userId", comment.userId); - yMap.set("createdAt", comment.createdAt.toISOString()); - yMap.set("updatedAt", comment.updatedAt.toISOString()); - if (comment.reactions.length > 0) { - throw new Error("Reactions should be empty in commentToYMap"); - } - yMap.set("reactions", new Y.Array()); - yMap.set("metadata", comment.metadata); - yMap.set("body", comment.body); - return yMap; - } - - private threadToYMap(thread: ThreadData) { - const yMap = new Y.Map(); - yMap.set("id", thread.id); - yMap.set("createdAt", thread.createdAt.toISOString()); - yMap.set("updatedAt", thread.updatedAt.toISOString()); - const commentsArray = new Y.Array>(); - - commentsArray.push( - thread.comments.map((comment) => this.commentToYMap(comment)) - ); - - yMap.set("comments", commentsArray); - yMap.set("resolved", thread.resolved); - yMap.set("resolvedUpdatedAt", thread.resolvedUpdatedAt?.toISOString()); - yMap.set("metadata", thread.metadata); - return yMap; - } - - private yMapToComment(yMap: Y.Map): CommentData { - return { - type: "comment", - id: yMap.get("id"), - userId: yMap.get("userId"), - createdAt: new Date(yMap.get("createdAt")), - updatedAt: new Date(yMap.get("updatedAt")), - reactions: [], - metadata: yMap.get("metadata"), - body: yMap.get("body"), - }; - } - - private yMapToThread(yMap: Y.Map): ThreadData { - return { - type: "thread", - id: yMap.get("id"), - createdAt: new Date(yMap.get("createdAt")), - updatedAt: new Date(yMap.get("updatedAt")), - comments: ((yMap.get("comments") as Y.Array>) || []).map( - (comment) => this.yMapToComment(comment) - ), - resolved: yMap.get("resolved"), - resolvedUpdatedAt: yMap.get("resolvedUpdatedAt"), - metadata: yMap.get("metadata"), - }; - } - - // TODO: async / reactive interface? - public getThread(threadId: string) { - const thread = this.yMapToThread(this.threadsYMap.get(threadId)); - return thread; - } - - public async createThread(options: { - initialComment: { - body: CommentBody; - metadata?: any; - }; - metadata?: any; - }) { - const date = new Date(); - - const comment: CommentData = { - type: "comment", - id: v4(), - userId: this.userId, - createdAt: date, - updatedAt: date, - reactions: [], - metadata: options.metadata, - body: options.initialComment.body, - }; - - const thread: ThreadData = { - type: "thread", - id: v4(), - createdAt: date, - updatedAt: date, - comments: [comment], - resolved: false, - metadata: options.metadata, - }; - - this.threadsYMap.set(thread.id, this.threadToYMap(thread)); - - return thread; - } -} - -export class LiveblocksThreadStore { - constructor(private readonly editor: BlockNoteEditor) {} - - public async createThread() { - const x = useCreateThread(); - return x; - } -} - -export class TiptapThreadStore { - constructor(private readonly editor: BlockNoteEditor) {} - - public async createThread() { - this.editor._tiptapEditor.commands.setMark(this.markType, { threadId: id }); - } -} diff --git a/packages/core/src/extensions/Comments/store/LiveBlocksThreadStore.ts b/packages/core/src/extensions/Comments/store/LiveBlocksThreadStore.ts new file mode 100644 index 0000000000..d7b28a527a --- /dev/null +++ b/packages/core/src/extensions/Comments/store/LiveBlocksThreadStore.ts @@ -0,0 +1,8 @@ +export class LiveblocksThreadStore { + constructor(private readonly editor: BlockNoteEditor) {} + + public async createThread() { + const x = useCreateThread(); + return x; + } +} diff --git a/packages/core/src/extensions/Comments/store/ThreadStore.ts b/packages/core/src/extensions/Comments/store/ThreadStore.ts new file mode 100644 index 0000000000..c3454d624a --- /dev/null +++ b/packages/core/src/extensions/Comments/store/ThreadStore.ts @@ -0,0 +1,59 @@ +import { CommentBody, CommentData, ThreadData } from "../types.js"; + +export abstract class ThreadStore { + abstract createThread(options: { + initialComment: { + body: CommentBody; + metadata?: any; + }; + metadata?: any; + }): Promise; + + abstract addComment(options: { + comment: { + body: CommentBody; + metadata?: any; + }; + threadId: string; + }): Promise; + + abstract updateComment(options: { + comment: { + body: CommentBody; + metadata?: any; + }; + threadId: string; + commentId: string; + }): Promise; + + abstract deleteComment(options: { + threadId: string; + commentId: string; + }): Promise; + + abstract deleteThread(options: { threadId: string }): Promise; + + abstract resolveThread(options: { threadId: string }): Promise; + + abstract unresolveThread(options: { threadId: string }): Promise; + + abstract addReaction(options: { + threadId: string; + commentId: string; + // reaction: string; TODO + }): Promise; + + abstract deleteReaction(options: { + threadId: string; + commentId: string; + reactionId: string; + }): Promise; + + abstract getThread(threadId: string): ThreadData; + + abstract getThreads(): Map; + + abstract subscribe( + cb: (threads: Map) => void + ): () => void; +} diff --git a/packages/core/src/extensions/Comments/store/TipTapThreadStore.ts b/packages/core/src/extensions/Comments/store/TipTapThreadStore.ts new file mode 100644 index 0000000000..8d068a37c5 --- /dev/null +++ b/packages/core/src/extensions/Comments/store/TipTapThreadStore.ts @@ -0,0 +1,7 @@ +export class TiptapThreadStore { + constructor(private readonly editor: BlockNoteEditor) {} + + public async createThread() { + this.editor._tiptapEditor.commands.setMark(this.markType, { threadId: id }); + } +} diff --git a/packages/core/src/extensions/Comments/store/YjsThreadStore.ts b/packages/core/src/extensions/Comments/store/YjsThreadStore.ts new file mode 100644 index 0000000000..fb3b262f17 --- /dev/null +++ b/packages/core/src/extensions/Comments/store/YjsThreadStore.ts @@ -0,0 +1,339 @@ +import { v4 } from "uuid"; +import * as Y from "yjs"; +import { BlockNoteEditor } from "../../../editor/BlockNoteEditor.js"; +import { CommentBody, CommentData, ThreadData } from "../types.js"; +import { ThreadStore } from "./ThreadStore.js"; + +// type YjsType = { +// [K in keyof T]: T[K] extends Date ? string : T[K]; // TODO: dates as string? +// }; + +// type YjsTypeConvertArrays = { +// [K in keyof T]: T[K] extends Array +// ? Y.Array> +// : YjsType; +// }; + +export class YjsThreadStore extends ThreadStore { + constructor( + private readonly editor: BlockNoteEditor, + private readonly userId: string, + private readonly threadsYMap: Y.Map + ) { + super(); + } + + private transact = ( + fn: (options: T) => R + ): ((options: T) => Promise) => { + return async (options: T) => { + return this.threadsYMap.doc!.transact(() => { + return fn(options); + }); + }; + }; + + public createThread = this.transact( + (options: { + initialComment: { + body: CommentBody; + metadata?: any; + }; + metadata?: any; + }) => { + const date = new Date(); + + const comment: CommentData = { + type: "comment", + id: v4(), + userId: this.userId, + createdAt: date, + updatedAt: date, + reactions: [], + metadata: options.metadata, + body: options.initialComment.body, + }; + + const thread: ThreadData = { + type: "thread", + id: v4(), + createdAt: date, + updatedAt: date, + comments: [comment], + resolved: false, + metadata: options.metadata, + }; + + this.threadsYMap.set(thread.id, threadToYMap(thread)); + + return thread; + } + ); + + public addComment = this.transact( + (options: { + comment: { + body: CommentBody; + metadata?: any; + }; + threadId: string; + }) => { + const yThread = this.threadsYMap.get(options.threadId); + if (!yThread) { + throw new Error("Thread not found"); + } + + const date = new Date(); + const comment: CommentData = { + type: "comment", + id: v4(), + userId: this.userId, + createdAt: date, + updatedAt: date, + deletedAt: undefined, + reactions: [], + metadata: options.comment.metadata, + body: options.comment.body, + }; + + (yThread.get("comments") as Y.Array>).push([ + commentToYMap(comment), + ]); + + yThread.set("updatedAt", new Date().getTime()); + return comment; + } + ); + + public updateComment = this.transact( + (options: { + comment: { + body: CommentBody; + metadata?: any; + }; + threadId: string; + commentId: string; + }) => { + const yThread = this.threadsYMap.get(options.threadId); + if (!yThread) { + throw new Error("Thread not found"); + } + + const yCommentIndex = yArrayFindIndex( + yThread.get("comments"), + (comment) => comment.get("id") === options.commentId + ); + + if (yCommentIndex === -1) { + throw new Error("Comment not found"); + } + + const yComment = yThread.get("comments").get(yCommentIndex); + yComment.set("body", options.comment.body); + yComment.set("updatedAt", new Date().getTime()); + yComment.set("metadata", options.comment.metadata); + } + ); + + public deleteComment = this.transact( + (options: { + threadId: string; + commentId: string; + softDelete?: boolean; + }) => { + const yThread = this.threadsYMap.get(options.threadId); + if (!yThread) { + throw new Error("Thread not found"); + } + + const yCommentIndex = yArrayFindIndex( + yThread.get("comments"), + (comment) => comment.get("id") === options.commentId + ); + + if (yCommentIndex === -1) { + throw new Error("Comment not found"); + } + + const yComment = yThread.get("comments").get(yCommentIndex); + + if (yComment.get("deletedAt")) { + throw new Error("Comment already deleted"); + } + + if (options.softDelete) { + yComment.set("deletedAt", new Date().getTime()); + yComment.set("body", undefined); + } else { + yThread.get("comments").delete(yCommentIndex); + } + + if ( + (yThread.get("comments") as Y.Array) + .toArray() + .every((comment) => comment.get("deletedAt")) + ) { + // all comments deleted + if (options.softDelete) { + yThread.set("deletedAt", new Date().getTime()); + } else { + this.threadsYMap.delete(options.threadId); + } + } + + yThread.set("updatedAt", new Date().getTime()); + } + ); + + public deleteThread = this.transact((options: { threadId: string }) => { + this.threadsYMap.delete(options.threadId); + }); + + public resolveThread = this.transact((options: { threadId: string }) => { + const yThread = this.threadsYMap.get(options.threadId); + if (!yThread) { + throw new Error("Thread not found"); + } + + yThread.set("resolved", true); + yThread.set("resolvedUpdatedAt", new Date().getTime()); + }); + + public unresolveThread = this.transact((options: { threadId: string }) => { + const yThread = this.threadsYMap.get(options.threadId); + if (!yThread) { + throw new Error("Thread not found"); + } + + yThread.set("resolved", false); + yThread.set("resolvedUpdatedAt", new Date().getTime()); + }); + + public addReaction = this.transact( + (options: { + threadId: string; + commentId: string; + // reaction: string; TODO + }) => { + throw new Error("Not implemented"); + } + ); + + public deleteReaction = this.transact( + (options: { threadId: string; commentId: string; reactionId: string }) => { + throw new Error("Not implemented"); + } + ); + + // TODO: async / reactive interface? + public getThread(threadId: string) { + const yThread = this.threadsYMap.get(threadId); + if (!yThread) { + throw new Error("Thread not found"); + } + const thread = yMapToThread(yThread); + return thread; + } + + public getThreads(): Map { + const threadMap = new Map(); + this.threadsYMap.forEach((yThread, id) => { + threadMap.set(id, yMapToThread(yThread)); + }); + return threadMap; + } + + public subscribe(cb: (threads: Map) => void) { + const observer = () => { + cb(this.getThreads()); + }; + + this.threadsYMap.observeDeep(observer); + + return () => { + this.threadsYMap.unobserveDeep(observer); + }; + } +} + +// HELPERS + +function commentToYMap(comment: CommentData) { + const yMap = new Y.Map(); + yMap.set("id", comment.id); + yMap.set("userId", comment.userId); + yMap.set("createdAt", comment.createdAt.getTime()); + yMap.set("updatedAt", comment.updatedAt.getTime()); + if (comment.deletedAt) { + yMap.set("deletedAt", comment.deletedAt.getTime()); + yMap.set("body", undefined); + } else { + yMap.set("body", comment.body); + } + if (comment.reactions.length > 0) { + throw new Error("Reactions should be empty in commentToYMap"); + } + yMap.set("reactions", new Y.Array()); + yMap.set("metadata", comment.metadata); + + return yMap; +} + +function threadToYMap(thread: ThreadData) { + const yMap = new Y.Map(); + yMap.set("id", thread.id); + yMap.set("createdAt", thread.createdAt.getTime()); + yMap.set("updatedAt", thread.updatedAt.getTime()); + const commentsArray = new Y.Array>(); + + commentsArray.push(thread.comments.map((comment) => commentToYMap(comment))); + + yMap.set("comments", commentsArray); + yMap.set("resolved", thread.resolved); + yMap.set("resolvedUpdatedAt", thread.resolvedUpdatedAt?.getTime()); + yMap.set("metadata", thread.metadata); + return yMap; +} + +function yMapToComment(yMap: Y.Map): CommentData { + return { + type: "comment", + id: yMap.get("id"), + userId: yMap.get("userId"), + createdAt: new Date(yMap.get("createdAt")), + updatedAt: new Date(yMap.get("updatedAt")), + deletedAt: yMap.get("deletedAt") + ? new Date(yMap.get("deletedAt")) + : undefined, + reactions: [], + metadata: yMap.get("metadata"), + body: yMap.get("body"), + }; +} + +function yMapToThread(yMap: Y.Map): ThreadData { + return { + type: "thread", + id: yMap.get("id"), + createdAt: new Date(yMap.get("createdAt")), + updatedAt: new Date(yMap.get("updatedAt")), + comments: ((yMap.get("comments") as Y.Array>) || []).map( + (comment) => yMapToComment(comment) + ), + resolved: yMap.get("resolved"), + resolvedUpdatedAt: yMap.get("resolvedUpdatedAt"), + metadata: yMap.get("metadata"), + }; +} + +function yArrayFindIndex( + yArray: Y.Array, + predicate: (item: any) => boolean +) { + for (let i = 0; i < yArray.length; i++) { + if (predicate(yArray.get(i))) { + return i; + } + } + return -1; +} diff --git a/packages/core/src/extensions/Comments/types.ts b/packages/core/src/extensions/Comments/types.ts index db7fc2bede..a928f9aec2 100644 --- a/packages/core/src/extensions/Comments/types.ts +++ b/packages/core/src/extensions/Comments/types.ts @@ -17,8 +17,16 @@ export type CommentData = { reactions: CommentReactionData[]; // attachments: CommentAttachment[]; metadata: any; - body: CommentBody; -}; +} & ( + | { + deletedAt: Date; + body: undefined; + } + | { + deletedAt?: never; + body: CommentBody; + } +); export type ThreadData = { type: "thread"; @@ -29,4 +37,5 @@ export type ThreadData = { resolved: boolean; resolvedUpdatedAt?: Date; metadata: any; + deletedAt?: Date; }; diff --git a/packages/mantine/src/comments/Comment.tsx b/packages/mantine/src/comments/Comment.tsx index 0f382408b0..9167c09360 100644 --- a/packages/mantine/src/comments/Comment.tsx +++ b/packages/mantine/src/comments/Comment.tsx @@ -1,14 +1,14 @@ import { assertEmpty } from "@blocknote/core"; -import { ComponentProps } from "@blocknote/react"; +import { ComponentProps, mergeRefs } from "@blocknote/react"; import { Avatar, Group, Skeleton, Text } from "@mantine/core"; +import { useHover } from "@mantine/hooks"; import { forwardRef } from "react"; const AuthorInfo = forwardRef< HTMLDivElement, - ComponentProps["Comments"]["Comment"] + Pick >((props, ref) => { - const { className, authorInfo, timeString, actions, children, ...rest } = - props; + const { authorInfo, timeString, ...rest } = props; assertEmpty(rest, false); @@ -48,24 +48,41 @@ export const Comment = forwardRef< HTMLDivElement, ComponentProps["Comments"]["Comment"] >((props, ref) => { - const { className, authorInfo, timeString, actions, children, ...rest } = - props; + const { + className, + showActions, + authorInfo, + timeString, + actions, + children, + ...rest + } = props; + const { hovered, ref: hoverRef } = useHover(); + const mergedRef = mergeRefs([ref, hoverRef]); assertEmpty(rest, false); + const doShowActions = + actions && + (showActions === true || + showActions === undefined || + (showActions === "hover" && hovered)); + return ( - <> -
- {actions} -
+ + {doShowActions ? ( + + {actions} + + ) : null} {children} - + ); }); diff --git a/packages/mantine/src/comments/Composer.tsx b/packages/mantine/src/comments/Composer.tsx deleted file mode 100644 index 648016ca33..0000000000 --- a/packages/mantine/src/comments/Composer.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { assertEmpty } from "@blocknote/core"; -import { ComponentProps } from "@blocknote/react"; -import { Button, Flex, Paper } from "@mantine/core"; -import { forwardRef } from "react"; -import { BlockNoteView } from "../BlockNoteView.js"; - -export const Composer = forwardRef< - HTMLDivElement, - ComponentProps["Comments"]["Composer"] ->((props, ref) => { - const { className, editor, onSubmit, ...rest } = props; - - assertEmpty(rest, false); - - return ( - - - {/* TODO: extract / change to icon? */} - - - - - ); -}); diff --git a/packages/mantine/src/components.tsx b/packages/mantine/src/components.tsx index 7e736d5a3f..b892d2b33b 100644 --- a/packages/mantine/src/components.tsx +++ b/packages/mantine/src/components.tsx @@ -2,7 +2,7 @@ import { Components } from "@blocknote/react"; import { Card, CardSection } from "./comments/Card.js"; import { Comment } from "./comments/Comment.js"; -import { Composer } from "./comments/Composer.js"; + import { Editor } from "./comments/Editor.js"; import { TextInput } from "./form/TextInput.js"; import { @@ -103,7 +103,6 @@ export const components: Components = { Comments: { Comment, Editor, - Composer, Card, CardSection, }, diff --git a/packages/react/src/components/Comments/Comment.tsx b/packages/react/src/components/Comments/Comment.tsx index 967b21033e..07398ce82a 100644 --- a/packages/react/src/components/Comments/Comment.tsx +++ b/packages/react/src/components/Comments/Comment.tsx @@ -1,17 +1,12 @@ "use client"; import { CommentData, mergeCSSClasses } from "@blocknote/core"; -import type { - ComponentPropsWithoutRef, - MouseEvent, - ReactNode, - SyntheticEvent, -} from "react"; -import { forwardRef, useCallback, useEffect, useRef, useState } from "react"; +import type { ComponentPropsWithoutRef, MouseEvent, ReactNode } from "react"; +import { useCallback, useEffect, useState } from "react"; import { useComponentsContext } from "../../editor/ComponentsContext.js"; +import { useBlockNoteEditor } from "../../hooks/useBlockNoteEditor.js"; import { useCreateBlockNote } from "../../hooks/useCreateBlockNote.js"; import { useDictionary } from "../../i18n/dictionary.js"; -import { mergeRefs } from "../../util/mergeRefs.js"; import { CommentEditor } from "./CommentEditor.js"; import { schema } from "./schema.js"; @@ -19,6 +14,7 @@ import { schema } from "./schema.js"; * Liveblocks, but changed: * - removed attachments * - removed read status + * ... */ const REACTIONS_TRUNCATE = 5; @@ -28,6 +24,11 @@ export interface CommentProps extends ComponentPropsWithoutRef<"div"> { */ comment: CommentData; + /** + * The thread id. + */ + threadId: string; + /** * How to show or hide the actions. */ @@ -227,41 +228,29 @@ export interface CommentProps extends ComponentPropsWithoutRef<"div"> { // ); // }); -/** - * Displays a single comment. - * - * @example - * <> - * {thread.comments.map((comment) => ( - * - * ))} - * - */ -export const Comment = forwardRef( - ( +export const Comment = ({ + comment, + threadId, + indentContent = true, + showDeleted, + showActions = "hover", + showReactions = true, + // showComposerFormattingControls = true, + onAuthorClick, + onMentionClick, + onCommentEdit, + onCommentDelete, + // overrides, + className, + additionalActions, + additionalActionsClassName, + autoMarkReadThreadId, + ...props +}: CommentProps) => { + const dict = useDictionary(); + + const commentEditor = useCreateBlockNote( { - comment, - indentContent = true, - showDeleted, - showActions = "hover", - showReactions = true, - // showComposerFormattingControls = true, - onAuthorClick, - onMentionClick, - onCommentEdit, - onCommentDelete, - // overrides, - className, - additionalActions, - additionalActionsClassName, - autoMarkReadThreadId, - ...props - }, - forwardedRef - ) => { - const dict = useDictionary(); - - const commentEditor = useCreateBlockNote({ initialContent: comment.body, trailingBlock: false, dictionary: { @@ -272,265 +261,186 @@ export const Comment = forwardRef( }, }, schema, + }, + [comment.body] + ); + + const Components = useComponentsContext()!; + + // const currentUserId = useCurrentUserId(); + // const deleteComment = useDeleteRoomComment(comment.roomId); + // const editComment = useEditRoomComment(com ment.roomId); + // const addReaction = useAddRoomCommentReaction(comment.roomId); + // const removeReaction = useRemoveRoomCommentReaction(comment.roomId); + // const $ = useOverrides(overrides); + const [isEditing, setEditing] = useState(false); + const [isTarget, setTarget] = useState(false); + const [isMoreActionOpen, setMoreActionOpen] = useState(false); + const [isReactionActionOpen, setReactionActionOpen] = useState(false); + + const editor = useBlockNoteEditor(); + + const handleEdit = useCallback(() => { + setEditing(true); + }, []); + + const onEditCancel = useCallback(() => { + commentEditor.replaceBlocks(commentEditor.document, comment.body); + setEditing(false); + }, [commentEditor, comment.body]); + + const onEditSubmit = useCallback( + async (_event: MouseEvent) => { + await editor.comments!.store.updateComment({ + commentId: comment.id, + comment: { + body: commentEditor.document, + }, + threadId: threadId, + }); + + setEditing(false); + }, + [comment, threadId, commentEditor, editor.comments] + ); + + const onDelete = useCallback(() => { + editor.comments!.store.deleteComment({ + commentId: comment.id, + threadId: threadId, }); + }, [comment, threadId, editor.comments]); - const Components = useComponentsContext()!; - - const ref = useRef(null); - const mergedRefs = mergeRefs([forwardedRef, ref]); - // const currentUserId = useCurrentUserId(); - // const deleteComment = useDeleteRoomComment(comment.roomId); - // const editComment = useEditRoomComment(comment.roomId); - // const addReaction = useAddRoomCommentReaction(comment.roomId); - // const removeReaction = useRemoveRoomCommentReaction(comment.roomId); - // const $ = useOverrides(overrides); - const [isEditing, setEditing] = useState(false); - const [isTarget, setTarget] = useState(false); - const [isMoreActionOpen, setMoreActionOpen] = useState(false); - const [isReactionActionOpen, setReactionActionOpen] = useState(false); - - const stopPropagation = useCallback((event: SyntheticEvent) => { - event.stopPropagation(); - }, []); - - const handleEdit = useCallback(() => { - setEditing(true); - }, []); - - const handleEditCancel = useCallback( - (event: MouseEvent) => { - event.stopPropagation(); - setEditing(false); - }, - [setEditing] - ); + const onReactionSelect = useCallback(() => { + console.log("reaction select"); + }, []); - const handleEditSubmit = useCallback( - (_event: MouseEvent) => { - // TODO: Add a way to preventDefault from within this callback, to override the default behavior (e.g. showing a confirmation dialog) - onCommentEdit?.(comment); - - // event.preventDefault(); - // setEditing(false); - // editComment({ - // commentId: comment.id, - // threadId: comment.threadId, - // body, - // attachments, - // }); - }, - [comment, onCommentEdit] - ); + const onResolve = useCallback(() => { + console.log("resolve"); + }, []); - const handleDelete = useCallback(() => { - // TODO: Add a way to preventDefault from within this callback, to override the default behavior (e.g. showing a confirmation dialog) - onCommentDelete?.(comment); - - // deleteComment({ - // commentId: comment.id, - // threadId: comment.threadId, - // }); - }, [comment, onCommentDelete]); - - // const handleAuthorClick = useCallback( - // (event: MouseEvent) => { - // onAuthorClick?.(comment.userId, event); - // }, - // [comment.userId, onAuthorClick] - // ); - - // const handleReactionSelect = useCallback( - // (emoji: string) => { - // const reactionIndex = comment.reactions.findIndex( - // (reaction) => reaction.emoji === emoji - // ); - - // if ( - // reactionIndex >= 0 && - // currentUserId && - // comment.reactions[reactionIndex]?.users.some( - // (user) => user.id === currentUserId - // ) - // ) { - // removeReaction({ - // threadId: comment.threadId, - // commentId: comment.id, - // emoji, - // }); - // } else { - // addReaction({ - // threadId: comment.threadId, - // commentId: comment.id, - // emoji, - // }); - // } - // }, - // [ - // addReaction, - // comment.id, - // comment.reactions, - // comment.threadId, - // removeReaction, - // currentUserId, - // ] - // ); - - useEffect(() => { - const isWindowDefined = typeof window !== "undefined"; - if (!isWindowDefined) { - return; - } - - const hash = window.location.hash; - const commentId = hash.slice(1); - - if (commentId === comment.id) { - setTarget(true); - } - }, []); // eslint-disable-line react-hooks/exhaustive-deps - - if (!showDeleted && !comment.body) { - return null; + useEffect(() => { + const isWindowDefined = typeof window !== "undefined"; + if (!isWindowDefined) { + return; } - let actions: ReactNode | undefined = undefined; - - if (showActions && !isEditing) { - actions = ( - - {additionalActions ?? null} - {/* {showReactions && ( - - - - - - - - )} */} - - R1 - - - R2 - - - - - ... - - - - - Edit comment - - - Delete comment - - - - - ); + const hash = window.location.hash; + const commentId = hash.slice(1); + + if (commentId === comment.id) { + setTarget(true); } + }, []); // eslint-disable-line react-hooks/exhaustive-deps - const timeString = - comment.createdAt.toLocaleDateString(undefined, { - month: "short", - day: "numeric", - }) + (comment.updatedAt !== comment.createdAt ? " (edited)" : ""); // TODO: needs editedAt? - - return ( - - {isEditing ? ( - <> - ( - - - X - - - Save - - - )} - /> - - ) : comment.body ? ( - <> - - - {showReactions && comment.reactions.length > 0 && ( -
- {/* {comment.reactions.map((reaction) => ( - - ))} */} - {/* - - - - - - */} -
- )} - - ) : ( -
- {/*

{$.COMMENT_DELETED}

*/} -
- )} -
+ if (!showDeleted && !comment.body) { + return null; + } + + let actions: ReactNode | undefined = undefined; + + if (showActions && !isEditing) { + actions = ( + + {additionalActions ?? null} + + R1 + + + R2 + + + + + ... + + + + + Edit comment + + + Delete comment + + + + ); } -); + + const timeString = + comment.createdAt.toLocaleDateString(undefined, { + month: "short", + day: "numeric", + }) + + (comment.updatedAt.getTime() !== comment.createdAt.getTime() + ? " (edited)" + : ""); // TODO: needs editedAt? + + return ( + + {isEditing ? ( + <> + ( + + + X + + + Save + + + )} + /> + + ) : comment.body ? ( + <> + + + {showReactions && comment.reactions.length > 0 && ( +
+ )} + + ) : ( + // Soft deletes + // TODO, test +
+

Deleted

+
+ )} +
+ ); +}; diff --git a/packages/react/src/components/Comments/CommentEditor.tsx b/packages/react/src/components/Comments/CommentEditor.tsx index 2fa3501379..43841c4a16 100644 --- a/packages/react/src/components/Comments/CommentEditor.tsx +++ b/packages/react/src/components/Comments/CommentEditor.tsx @@ -59,7 +59,9 @@ export const CommentEditor = (props: { editable={props.editable} /> {props.actions && ( - +
+ +
)} ); diff --git a/packages/react/src/components/Comments/Composer.tsx b/packages/react/src/components/Comments/FloatingComposer.tsx similarity index 88% rename from packages/react/src/components/Comments/Composer.tsx rename to packages/react/src/components/Comments/FloatingComposer.tsx index d7d55bc3b7..214b4d740c 100644 --- a/packages/react/src/components/Comments/Composer.tsx +++ b/packages/react/src/components/Comments/FloatingComposer.tsx @@ -5,7 +5,7 @@ import { useDictionary } from "../../i18n/dictionary.js"; import { CommentEditor } from "./CommentEditor.js"; import { schema } from "./schema.js"; -export function Composer() { +export function FloatingComposer() { const editor = useBlockNoteEditor(); const Components = useComponentsContext()!; const dict = useDictionary(); @@ -33,12 +33,13 @@ export function Composer() { mainTooltip="Save" variant="compact" isDisabled={isEmpty} - onClick={() => { - editor.comments!.createThread({ + onClick={async () => { + await editor.comments!.createThread({ initialComment: { body: newCommentEditor.document, }, }); + editor.comments!.stopPendingComment(); }}> Save diff --git a/packages/react/src/components/Comments/FloatingComposerController.tsx b/packages/react/src/components/Comments/FloatingComposerController.tsx index 72a6b4c905..75722dfe9e 100644 --- a/packages/react/src/components/Comments/FloatingComposerController.tsx +++ b/packages/react/src/components/Comments/FloatingComposerController.tsx @@ -7,19 +7,19 @@ import { StyleSchema, } from "@blocknote/core"; import { UseFloatingOptions, flip, offset } from "@floating-ui/react"; -import { FC, useMemo } from "react"; +import { ComponentProps, FC, useMemo } from "react"; import { useBlockNoteEditor } from "../../hooks/useBlockNoteEditor.js"; import { useUIElementPositioning } from "../../hooks/useUIElementPositioning.js"; import { useUIPluginState } from "../../hooks/useUIPluginState.js"; -import { Composer } from "./Composer.js"; +import { FloatingComposer } from "./FloatingComposer.js"; export const FloatingComposerController = < B extends BlockSchema = DefaultBlockSchema, I extends InlineContentSchema = DefaultInlineContentSchema, S extends StyleSchema = DefaultStyleSchema >(props: { - filePanel?: FC; + floatingComposer?: FC>; floatingOptions?: Partial; }) => { const editor = useBlockNoteEditor(); @@ -54,7 +54,7 @@ export const FloatingComposerController = < onOpenChange: (open) => { if (!open) { // TODO - editor.filePanel!.closeMenu(); + editor.comments!.stopPendingComment(); editor.focus(); } }, @@ -66,7 +66,7 @@ export const FloatingComposerController = < return null; } - const Component = props.filePanel || Composer; + const Component = props.floatingComposer || FloatingComposer; return (
diff --git a/packages/react/src/components/Comments/FloatingThreadController.tsx b/packages/react/src/components/Comments/FloatingThreadController.tsx index 65cfa5a180..93bdf1ee3e 100644 --- a/packages/react/src/components/Comments/FloatingThreadController.tsx +++ b/packages/react/src/components/Comments/FloatingThreadController.tsx @@ -27,7 +27,7 @@ export const FloatingThreadController = < I extends InlineContentSchema = DefaultInlineContentSchema, S extends StyleSchema = DefaultStyleSchema >(props: { - filePanel?: FC; + filePanel?: FC; // TODO floatingOptions?: Partial; }) => { const editor = useBlockNoteEditor(); @@ -93,12 +93,10 @@ export const FloatingThreadController = < const Component = props.filePanel || Thread; - const thread = editor.comments.store.getThread(state.selectedThreadId); - return (
{/*
hello
*/} - +
); }; diff --git a/packages/react/src/components/Comments/Thread.tsx b/packages/react/src/components/Comments/Thread.tsx index 09d61c2aad..69ea985a57 100644 --- a/packages/react/src/components/Comments/Thread.tsx +++ b/packages/react/src/components/Comments/Thread.tsx @@ -1,20 +1,22 @@ "use client"; import { ThreadData, mergeCSSClasses } from "@blocknote/core"; -import type { ComponentPropsWithoutRef, ForwardedRef } from "react"; -import { forwardRef, useCallback, useMemo } from "react"; +import type { ComponentPropsWithoutRef } from "react"; +import { useCallback, useMemo } from "react"; import { useComponentsContext } from "../../editor/ComponentsContext.js"; +import { useBlockNoteEditor } from "../../hooks/useBlockNoteEditor.js"; import { useCreateBlockNote } from "../../hooks/useCreateBlockNote.js"; import { useDictionary } from "../../i18n/dictionary.js"; import { Comment, CommentProps } from "./Comment.js"; import { CommentEditor } from "./CommentEditor.js"; import { schema } from "./schema.js"; +import { useThreadStore } from "./useThreadStore.js"; export interface ThreadProps extends ComponentPropsWithoutRef<"div"> { /** * The thread to display. */ - thread: ThreadData; + threadId: string; /** * How to show or hide the composer to reply to the thread. @@ -116,186 +118,188 @@ export interface ThreadProps extends ComponentPropsWithoutRef<"div"> { * ))} * */ -export const Thread = forwardRef( - ( - { - thread, - indentCommentContent = true, - showActions = "hover", - showDeletedComments, - showResolveAction = true, - showReactions = true, - showComposer = "collapsed", - showAttachments = true, - // showComposerFormattingControls = true, - onResolvedChange, - onCommentEdit, - onCommentDelete, - onThreadDelete, - onAuthorClick, - onMentionClick, - // onAttachmentClick, - // onComposerSubmit, - // overrides, - className, - ...props - }: ThreadProps, - forwardedRef: ForwardedRef - ) => { - // const markThreadAsResolved = useMarkRoomThreadAsResolved(thread.roomId); - // const markThreadAsUnresolved = useMarkRoomThreadAsUnresolved(thread.roomId); - - const Components = useComponentsContext()!; - const dict = useDictionary(); - - const newCommentEditor = useCreateBlockNote({ - trailingBlock: false, - dictionary: { - ...dict, - placeholders: { - ...dict.placeholders, - default: "Add comment...", // TODO: only for empty doc - }, +export const Thread = ({ + threadId, + indentCommentContent = true, + showActions = "hover", + showDeletedComments, + showResolveAction = true, + showReactions = true, + showComposer = "collapsed", + showAttachments = true, + // showComposerFormattingControls = true, + onResolvedChange, + onCommentEdit, + onCommentDelete, + onThreadDelete, + onAuthorClick, + onMentionClick, + // onAttachmentClick, + // onComposerSubmit, + // overrides, + className, + ...props +}: ThreadProps) => { + // const markThreadAsResolved = useMarkRoomThreadAsResolved(thread.roomId); + // const markThreadAsUnresolved = useMarkRoomThreadAsUnresolved(thread.roomId); + const editor = useBlockNoteEditor(); + const Components = useComponentsContext()!; + const dict = useDictionary(); + + const threadMap = useThreadStore(editor); + const thread = threadMap.get(threadId); + + if (!thread) { + throw new Error("Thread not found"); + } + + const newCommentEditor = useCreateBlockNote({ + trailingBlock: false, + dictionary: { + ...dict, + placeholders: { + ...dict.placeholders, + default: "Add comment...", // TODO: only for empty doc }, - schema, + }, + schema, + }); + + const firstCommentIndex = useMemo(() => { + return showDeletedComments + ? 0 + : thread.comments.findIndex((comment) => comment.body); + }, [showDeletedComments, thread.comments]); + + // const handleResolvedChange = useCallback( + // (resolved: boolean) => { + // onResolvedChange?.(resolved); + + // if (resolved) { + // markThreadAsResolved(thread.id); + // } else { + // markThreadAsUnresolved(thread.id); + // } + // }, + // [ + // markThreadAsResolved, + // markThreadAsUnresolved, + // onResolvedChange, + // thread.id, + // ] + // ); + + // TODO: thread deletion + + // const handleCommentDelete = useCallback( + // (comment: Comment) => { + // onCommentDelete?.(comment); + + // const filteredComments = thread.comments.filter( + // (comment) => comment.body + // ); + + // if (filteredComments.length <= 1) { + // onThreadDelete?.(thread); + // } + // }, + // [onCommentDelete, onThreadDelete, thread] + // ); + + const onNewCommentSave = useCallback(async () => { + await editor.comments!.store.addComment({ + comment: { + body: newCommentEditor.document, + }, + threadId: thread.id, }); - const firstCommentIndex = useMemo(() => { - return showDeletedComments - ? 0 - : thread.comments.findIndex((comment) => comment.body); - }, [showDeletedComments, thread.comments]); - - const stopPropagation = useCallback((event: SyntheticEvent) => { - event.stopPropagation(); - }, []); - - // const handleResolvedChange = useCallback( - // (resolved: boolean) => { - // onResolvedChange?.(resolved); - - // if (resolved) { - // markThreadAsResolved(thread.id); - // } else { - // markThreadAsUnresolved(thread.id); - // } - // }, - // [ - // markThreadAsResolved, - // markThreadAsUnresolved, - // onResolvedChange, - // thread.id, - // ] - // ); - - // TODO: thread deletion - - // const handleCommentDelete = useCallback( - // (comment: Comment) => { - // onCommentDelete?.(comment); - - // const filteredComments = thread.comments.filter( - // (comment) => comment.body - // ); - - // if (filteredComments.length <= 1) { - // onThreadDelete?.(thread); - // } - // }, - // [onCommentDelete, onThreadDelete, thread] - // ); - - // TODO: extract component - return ( - - - {thread.comments.map((comment, index) => { - const isFirstComment = index === firstCommentIndex; + // reset editor + newCommentEditor.removeBlocks(newCommentEditor.document); + }, [editor.comments, newCommentEditor, thread.id]); + + // TODO: extract component + return ( + + + {thread.comments.map((comment, index) => { + const isFirstComment = index === firstCommentIndex; + + return ( + + // + // + // + // + // ) : null + // } + /> + ); + })} + + + { + if (!isFocused && isEmpty) { + return null; + } return ( - - // - // - // - // - // ) : null - // } - /> + + + Save + + ); - })} - - - { - if (!isFocused && isEmpty) { - return null; - } - - return ( - - - Save - - - ); - }} - /> - - - ); - } -) as (props: ThreadProps & RefAttributes) => JSX.Element; + }} + /> + + + ); +}; diff --git a/packages/react/src/components/Comments/useThreadStore.ts b/packages/react/src/components/Comments/useThreadStore.ts new file mode 100644 index 0000000000..1c37a25345 --- /dev/null +++ b/packages/react/src/components/Comments/useThreadStore.ts @@ -0,0 +1,28 @@ +import { BlockNoteEditor, ThreadData } from "@blocknote/core"; +import { useCallback, useRef, useSyncExternalStore } from "react"; + +export function useThreadStore(editor: BlockNoteEditor) { + const store = editor.comments!.store; + + // this ref works around this error: + // https://react.dev/reference/react/useSyncExternalStore#im-getting-an-error-the-result-of-getsnapshot-should-be-cached + // however, might not be a good practice to work around it this way + const threadsRef = useRef>(); + + if (!threadsRef.current) { + threadsRef.current = store.getThreads(); + } + + const subscribe = useCallback( + (cb: () => void) => { + return store.subscribe((threads) => { + // update ref when changed + threadsRef.current = threads; + cb(); + }); + }, + [store] + ); + + return useSyncExternalStore(subscribe, () => threadsRef.current!); +} diff --git a/packages/react/src/components/FormattingToolbar/DefaultButtons/AddCommentButton.tsx b/packages/react/src/components/FormattingToolbar/DefaultButtons/AddCommentButton.tsx index c37cfcf0d4..bd00a988f7 100644 --- a/packages/react/src/components/FormattingToolbar/DefaultButtons/AddCommentButton.tsx +++ b/packages/react/src/components/FormattingToolbar/DefaultButtons/AddCommentButton.tsx @@ -17,7 +17,7 @@ export const AddCommentButton = () => { >(); const onClick = useCallback(() => { - editor.comments?.addPendingComment(); + editor.comments?.startPendingComment(); editor.formattingToolbar.closeMenu(); }, [editor]); diff --git a/packages/react/src/editor/ComponentsContext.tsx b/packages/react/src/editor/ComponentsContext.tsx index 8fd6a00e3d..ffe5b56152 100644 --- a/packages/react/src/editor/ComponentsContext.tsx +++ b/packages/react/src/editor/ComponentsContext.tsx @@ -263,12 +263,6 @@ export type ComponentProps = { className?: string; children?: ReactNode; }; - // TODO: same as editor? - Composer: { - className?: string; - editor: BlockNoteEditor; - onSubmit: () => void; - }; Editor: { className?: string; editable: boolean; @@ -287,6 +281,7 @@ export type ComponentProps = { }; timeString: string; actions?: ReactNode; + showActions?: boolean | "hover"; }; }; }; From 434eafa228042a07334f086465c1759596aa503a Mon Sep 17 00:00:00 2001 From: yousefed Date: Thu, 16 Jan 2025 14:50:38 +0100 Subject: [PATCH 016/144] add threadstore tests --- .../Comments/store/YjsThreadStore.test.ts | 282 ++++++++++++++++++ .../Comments/store/YjsThreadStore.ts | 2 +- 2 files changed, 283 insertions(+), 1 deletion(-) create mode 100644 packages/core/src/extensions/Comments/store/YjsThreadStore.test.ts diff --git a/packages/core/src/extensions/Comments/store/YjsThreadStore.test.ts b/packages/core/src/extensions/Comments/store/YjsThreadStore.test.ts new file mode 100644 index 0000000000..d3f939e57d --- /dev/null +++ b/packages/core/src/extensions/Comments/store/YjsThreadStore.test.ts @@ -0,0 +1,282 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; +import * as Y from "yjs"; +import { BlockNoteEditor } from "../../../editor/BlockNoteEditor.js"; +import { CommentBody } from "../types.js"; +import { YjsThreadStore } from "./YjsThreadStore.js"; + +// Mock UUID to generate sequential IDs +let mockUuidCounter = 0; +vi.mock("uuid", () => ({ + v4: () => `mocked-uuid-${++mockUuidCounter}`, +})); + +describe("YjsThreadStore", () => { + let store: YjsThreadStore; + let doc: Y.Doc; + let threadsYMap: Y.Map; + let editor: BlockNoteEditor; + + beforeEach(() => { + // Reset mocks and create fresh instances + vi.clearAllMocks(); + mockUuidCounter = 0; + doc = new Y.Doc(); + threadsYMap = doc.getMap("threads"); + editor = {} as BlockNoteEditor; + store = new YjsThreadStore(editor, "test-user", threadsYMap); + }); + + describe("createThread", () => { + it("creates a thread with initial comment", async () => { + const initialComment = { + body: "Test comment" as CommentBody, + metadata: { extra: "metadatacomment" }, + }; + + const thread = await store.createThread({ + initialComment, + metadata: { extra: "metadatathread" }, + }); + + expect(thread).toMatchObject({ + type: "thread", + id: "mocked-uuid-2", + resolved: false, + metadata: { extra: "metadatathread" }, + comments: [ + { + type: "comment", + id: "mocked-uuid-1", + userId: "test-user", + body: "Test comment", + metadata: { extra: "metadatacomment" }, + reactions: [], + }, + ], + }); + }); + }); + + describe("addComment", () => { + it("adds a comment to existing thread", async () => { + // First create a thread + const thread = await store.createThread({ + initialComment: { + body: "Initial comment" as CommentBody, + }, + }); + + // Add new comment + const comment = await store.addComment({ + threadId: thread.id, + comment: { + body: "New comment" as CommentBody, + metadata: { test: "metadata" }, + }, + }); + + expect(comment).toMatchObject({ + type: "comment", + id: "mocked-uuid-3", + userId: "test-user", + body: "New comment", + metadata: { test: "metadata" }, + reactions: [], + }); + + // Verify thread has both comments + const updatedThread = store.getThread(thread.id); + expect(updatedThread.comments).toHaveLength(2); + }); + + it("throws error for non-existent thread", async () => { + await expect( + store.addComment({ + threadId: "non-existent", + comment: { + body: "Test comment" as CommentBody, + }, + }) + ).rejects.toThrow("Thread not found"); + }); + }); + + describe("updateComment", () => { + it("updates existing comment", async () => { + const thread = await store.createThread({ + initialComment: { + body: "Initial comment" as CommentBody, + }, + }); + + await store.updateComment({ + threadId: thread.id, + commentId: thread.comments[0].id, + comment: { + body: "Updated comment" as CommentBody, + metadata: { updatedMetadata: true }, + }, + }); + + const updatedThread = store.getThread(thread.id); + expect(updatedThread.comments[0]).toMatchObject({ + body: "Updated comment", + metadata: { updatedMetadata: true }, + }); + }); + }); + + describe("deleteComment", () => { + it("soft deletes a comment", async () => { + const thread = await store.createThread({ + initialComment: { + body: "Test comment" as CommentBody, + }, + }); + + await store.deleteComment({ + threadId: thread.id, + commentId: thread.comments[0].id, + softDelete: true, + }); + + const updatedThread = store.getThread(thread.id); + expect(updatedThread.comments[0].deletedAt).toBeDefined(); + expect(updatedThread.comments[0].body).toBeUndefined(); + }); + + it("hard deletes a comment (deletes thread)", async () => { + const thread = await store.createThread({ + initialComment: { + body: "Test comment" as CommentBody, + }, + }); + + await store.deleteComment({ + threadId: thread.id, + commentId: thread.comments[0].id, + softDelete: false, + }); + + // Thread should be deleted since it was the only comment + expect(() => store.getThread(thread.id)).toThrow("Thread not found"); + }); + }); + + describe("resolveThread", () => { + it("resolves a thread", async () => { + const thread = await store.createThread({ + initialComment: { + body: "Test comment" as CommentBody, + }, + }); + + await store.resolveThread({ threadId: thread.id }); + + const updatedThread = store.getThread(thread.id); + expect(updatedThread.resolved).toBe(true); + expect(updatedThread.resolvedUpdatedAt).toBeDefined(); + }); + }); + + describe("unresolveThread", () => { + it("unresolves a thread", async () => { + const thread = await store.createThread({ + initialComment: { + body: "Test comment" as CommentBody, + }, + }); + + await store.resolveThread({ threadId: thread.id }); + await store.unresolveThread({ threadId: thread.id }); + + const updatedThread = store.getThread(thread.id); + expect(updatedThread.resolved).toBe(false); + expect(updatedThread.resolvedUpdatedAt).toBeDefined(); + }); + }); + + describe("getThreads", () => { + it("returns all threads", async () => { + await store.createThread({ + initialComment: { + body: "Thread 1" as CommentBody, + }, + }); + + await store.createThread({ + initialComment: { + body: "Thread 2" as CommentBody, + }, + }); + + const threads = store.getThreads(); + expect(threads.size).toBe(2); + }); + }); + + describe("deleteThread", () => { + it("deletes an entire thread", async () => { + const thread = await store.createThread({ + initialComment: { + body: "Test comment" as CommentBody, + }, + }); + + await store.deleteThread({ threadId: thread.id }); + + // Verify thread is deleted + expect(() => store.getThread(thread.id)).toThrow("Thread not found"); + }); + }); + + describe("reactions", () => { + it("throws not implemented error when adding reaction", async () => { + const thread = await store.createThread({ + initialComment: { + body: "Test comment" as CommentBody, + }, + }); + + await expect( + store.addReaction({ + threadId: thread.id, + commentId: thread.comments[0].id, + }) + ).rejects.toThrow("Not implemented"); + }); + + it("throws not implemented error when deleting reaction", async () => { + const thread = await store.createThread({ + initialComment: { + body: "Test comment" as CommentBody, + }, + }); + + await expect( + store.deleteReaction({ + threadId: thread.id, + commentId: thread.comments[0].id, + reactionId: "some-reaction", + }) + ).rejects.toThrow("Not implemented"); + }); + }); + + describe("subscribe", () => { + it("calls callback when threads change", async () => { + const callback = vi.fn(); + const unsubscribe = store.subscribe(callback); + + await store.createThread({ + initialComment: { + body: "Test comment" as CommentBody, + }, + }); + + expect(callback).toHaveBeenCalled(); + + unsubscribe(); + }); + }); +}); diff --git a/packages/core/src/extensions/Comments/store/YjsThreadStore.ts b/packages/core/src/extensions/Comments/store/YjsThreadStore.ts index fb3b262f17..fb9c7f9048 100644 --- a/packages/core/src/extensions/Comments/store/YjsThreadStore.ts +++ b/packages/core/src/extensions/Comments/store/YjsThreadStore.ts @@ -50,7 +50,7 @@ export class YjsThreadStore extends ThreadStore { createdAt: date, updatedAt: date, reactions: [], - metadata: options.metadata, + metadata: options.initialComment.metadata, body: options.initialComment.body, }; From 9d35f72b7d0214b781b985ecad5f2ec2f24e12e3 Mon Sep 17 00:00:00 2001 From: yousefed Date: Thu, 16 Jan 2025 15:02:08 +0100 Subject: [PATCH 017/144] document recommended auth rules --- .../extensions/Comments/store/ThreadStore.ts | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/packages/core/src/extensions/Comments/store/ThreadStore.ts b/packages/core/src/extensions/Comments/store/ThreadStore.ts index c3454d624a..79c8d4f497 100644 --- a/packages/core/src/extensions/Comments/store/ThreadStore.ts +++ b/packages/core/src/extensions/Comments/store/ThreadStore.ts @@ -1,6 +1,24 @@ import { CommentBody, CommentData, ThreadData } from "../types.js"; +/** + * ThreadStore is an abstract class that defines the interface + * to read / add / update / delete threads and comments. + * + * The methods are annotated with the recommended auth pattern + * (but of course this could be different in your app): + * - View-only users should not be able to see any comments + * - Comment-only users and editors can: + * - - create new comments / replies / reactions + * - - edit / delete their own comments / reactions + * - - resolve / unresolve threads + * - Editors can also delete any comment or thread + */ export abstract class ThreadStore { + /** + * Creates a new thread with an initial comment. + * + * Auth: should be possible by anyone with comment access + */ abstract createThread(options: { initialComment: { body: CommentBody; @@ -9,6 +27,11 @@ export abstract class ThreadStore { metadata?: any; }): Promise; + /** + * Adds a comment to a thread. + * + * Auth: should be possible by anyone with comment access + */ abstract addComment(options: { comment: { body: CommentBody; @@ -17,6 +40,11 @@ export abstract class ThreadStore { threadId: string; }): Promise; + /** + * Updates a comment in a thread. + * + * Auth: should only be possible by the comment author + */ abstract updateComment(options: { comment: { body: CommentBody; @@ -26,23 +54,53 @@ export abstract class ThreadStore { commentId: string; }): Promise; + /** + * Deletes a comment from a thread. + * + * Auth: should be possible by the comment author OR an editor of the document + */ abstract deleteComment(options: { threadId: string; commentId: string; }): Promise; + /** + * Deletes a thread. + * + * Auth: should only be possible by an editor of the document + */ abstract deleteThread(options: { threadId: string }): Promise; + /** + * Marks a thread as resolved. + * + * Auth: should be possible by anyone with comment access + */ abstract resolveThread(options: { threadId: string }): Promise; + /** + * Marks a thread as unresolved. + * + * Auth: should be possible by anyone with comment access + */ abstract unresolveThread(options: { threadId: string }): Promise; + /** + * Adds a reaction to a comment. + * + * Auth: should be possible by anyone with comment access + */ abstract addReaction(options: { threadId: string; commentId: string; // reaction: string; TODO }): Promise; + /** + * Deletes a reaction from a comment. + * + * Auth: should be possible by the reaction author + */ abstract deleteReaction(options: { threadId: string; commentId: string; From 58ed7c00ea27dd906bd0917ea214e91a886ed80c Mon Sep 17 00:00:00 2001 From: yousefed Date: Thu, 16 Jan 2025 16:05:44 +0100 Subject: [PATCH 018/144] resolve --- packages/core/src/editor/Block.css | 4 +- .../src/extensions/Comments/CommentsPlugin.ts | 63 +++++++++- .../react/src/components/Comments/Comment.tsx | 117 ++++++------------ .../react/src/components/Comments/Thread.tsx | 114 +---------------- 4 files changed, 109 insertions(+), 189 deletions(-) diff --git a/packages/core/src/editor/Block.css b/packages/core/src/editor/Block.css index ace7353fce..e699fd478a 100644 --- a/packages/core/src/editor/Block.css +++ b/packages/core/src/editor/Block.css @@ -524,10 +524,10 @@ NESTED BLOCKS padding-right: 0; } -.bn-thread-mark { +.bn-thread-mark:not([data-orphan="true"]) { background: rgba(255, 200, 0, 0.15); } -.bn-thread-mark-selected { +.bn-thread-mark:not([data-orphan="true"]) .bn-thread-mark-selected { background: rgba(255, 200, 0, 0.25); } diff --git a/packages/core/src/extensions/Comments/CommentsPlugin.ts b/packages/core/src/extensions/Comments/CommentsPlugin.ts index ffec2d8700..92457e5b4d 100644 --- a/packages/core/src/extensions/Comments/CommentsPlugin.ts +++ b/packages/core/src/extensions/Comments/CommentsPlugin.ts @@ -6,7 +6,7 @@ import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; import { EventEmitter } from "../../util/EventEmitter.js"; import { ThreadStore } from "./store/ThreadStore.js"; import { YjsThreadStore } from "./store/YjsThreadStore.js"; -import { CommentBody } from "./types.js"; +import { CommentBody, ThreadData } from "./types.js"; const PLUGIN_KEY = new PluginKey(`blocknote-comments`); enum CommentsPluginActions { @@ -84,6 +84,59 @@ export class CommentsPlugin extends EventEmitter { }); } + /** + * when a thread is resolved or deleted, we need to update the marks to reflect the new state + */ + private updateMarksFromThreads = (threads: Map) => { + const doc = new Y.Doc(); + const threadMap = doc.getMap("threads"); + threads.forEach((thread) => { + threadMap.set(thread.id, thread); + }); + + const ttEditor = this.editor._tiptapEditor; + if (!ttEditor) { + // TODO: better lifecycle management + return; + } + + ttEditor.state.doc.descendants((node, pos) => { + node.marks.forEach((mark) => { + if (mark.type.name === this.markType) { + const markType = mark.type; + const markThreadId = mark.attrs.threadId; + const thread = threads.get(markThreadId); + const isOrphan = !thread || thread.resolved || thread.deletedAt; + + if (isOrphan !== mark.attrs.orphan) { + const { tr } = ttEditor.state; + const trimmedFrom = Math.max(pos, 0); + const trimmedTo = Math.min( + pos + node.nodeSize, + ttEditor.state.doc.content.size - 1 + ); + tr.removeMark(trimmedFrom, trimmedTo, markType); + tr.addMark( + trimmedFrom, + trimmedTo, + markType.create({ + ...mark.attrs, + orphan: isOrphan, + }) + ); + ttEditor.dispatch(tr); + + if (isOrphan && this.selectedThreadId === markThreadId) { + // unselect + this.selectedThreadId = undefined; + this.emitStateUpdate(); + } + } + } + }); + }); + }; + constructor( private readonly editor: BlockNoteEditor, private readonly markType: string @@ -97,7 +150,13 @@ export class CommentsPlugin extends EventEmitter { doc.getMap("threads") ); - // TODO + // TODO: unsubscribe + this.store.subscribe(this.updateMarksFromThreads); + + // initial + this.updateMarksFromThreads(this.store.getThreads()); + + // TODO: remove settimeout setTimeout(() => { editor.onSelectionChange(() => { // TODO: filter out yjs transactions diff --git a/packages/react/src/components/Comments/Comment.tsx b/packages/react/src/components/Comments/Comment.tsx index 07398ce82a..e06c137e83 100644 --- a/packages/react/src/components/Comments/Comment.tsx +++ b/packages/react/src/components/Comments/Comment.tsx @@ -1,6 +1,6 @@ "use client"; -import { CommentData, mergeCSSClasses } from "@blocknote/core"; +import { CommentData, ThreadData, mergeCSSClasses } from "@blocknote/core"; import type { ComponentPropsWithoutRef, MouseEvent, ReactNode } from "react"; import { useCallback, useEffect, useState } from "react"; import { useComponentsContext } from "../../editor/ComponentsContext.js"; @@ -16,7 +16,7 @@ import { schema } from "./schema.js"; * - removed read status * ... */ -const REACTIONS_TRUNCATE = 5; +// const REACTIONS_TRUNCATE = 5; export interface CommentProps extends ComponentPropsWithoutRef<"div"> { /** @@ -27,13 +27,18 @@ export interface CommentProps extends ComponentPropsWithoutRef<"div"> { /** * The thread id. */ - threadId: string; + thread: ThreadData; /** * How to show or hide the actions. */ showActions?: boolean | "hover"; + /** + * Whether to show the resolve action. + */ + showResolveAction?: boolean; + /** * Whether to show the comment if it was deleted. If set to `false`, it will render deleted comments as `null`. */ @@ -44,55 +49,10 @@ export interface CommentProps extends ComponentPropsWithoutRef<"div"> { */ showReactions?: boolean; - /** - * Whether to show the composer's formatting controls when editing the comment. - */ - // showComposerFormattingControls?: ComposerProps["showFormattingControls"]; - - /** - * Whether to indent the comment's content. - */ - indentContent?: boolean; - - /** - * The event handler called when the comment is edited. - */ - onCommentEdit?: (comment: CommentData) => void; - - /** - * The event handler called when the comment is deleted. - */ - onCommentDelete?: (comment: CommentData) => void; - - /** - * The event handler called when clicking on the author. - */ - onAuthorClick?: (userId: string, event: MouseEvent) => void; - - /** - * The event handler called when clicking on a mention. - */ - onMentionClick?: (userId: string, event: MouseEvent) => void; - - /** - * Override the component's strings. - */ - // overrides?: Partial; - - /** - * @internal - */ - autoMarkReadThreadId?: string; - /** * @internal */ additionalActions?: ReactNode; - - /** - * @internal - */ - additionalActionsClassName?: string; } // interface CommentReactionButtonProps @@ -230,22 +190,13 @@ export interface CommentProps extends ComponentPropsWithoutRef<"div"> { export const Comment = ({ comment, - threadId, - indentContent = true, + thread, showDeleted, showActions = "hover", showReactions = true, - // showComposerFormattingControls = true, - onAuthorClick, - onMentionClick, - onCommentEdit, - onCommentDelete, - // overrides, + showResolveAction = false, className, additionalActions, - additionalActionsClassName, - autoMarkReadThreadId, - ...props }: CommentProps) => { const dict = useDictionary(); @@ -296,28 +247,36 @@ export const Comment = ({ comment: { body: commentEditor.document, }, - threadId: threadId, + threadId: thread.id, }); setEditing(false); }, - [comment, threadId, commentEditor, editor.comments] + [comment, thread.id, commentEditor, editor.comments] ); const onDelete = useCallback(() => { editor.comments!.store.deleteComment({ commentId: comment.id, - threadId: threadId, + threadId: thread.id, }); - }, [comment, threadId, editor.comments]); + }, [comment, thread.id, editor.comments]); const onReactionSelect = useCallback(() => { console.log("reaction select"); }, []); const onResolve = useCallback(() => { - console.log("resolve"); - }, []); + editor.comments!.store.resolveThread({ + threadId: thread.id, + }); + }, [thread.id, editor.comments]); + + const onReopen = useCallback(() => { + editor.comments!.store.unresolveThread({ + threadId: thread.id, + }); + }, [thread.id, editor.comments]); useEffect(() => { const isWindowDefined = typeof window !== "undefined"; @@ -342,11 +301,7 @@ export const Comment = ({ if (showActions && !isEditing) { actions = ( + className={mergeCSSClasses("bn-comment-actions", "bn-toolbar")}> {additionalActions ?? null} R1 - - R2 - + {showResolveAction && + (thread.resolved ? ( + + R2 + + ) : ( + + R2 + + ))} { */ showReactions?: CommentProps["showReactions"]; - /** - * Whether to show the composer's formatting controls. - */ - // showComposerFormattingControls?: ComposerProps["showFormattingControls"]; - - /** - * Whether to indent the comments' content. - */ - indentCommentContent?: CommentProps["indentContent"]; - /** * Whether to show deleted comments. */ @@ -57,54 +47,6 @@ export interface ThreadProps extends ComponentPropsWithoutRef<"div"> { * Whether to show attachments. */ showAttachments?: boolean; - - /** - * The event handler called when changing the resolved status. - */ - onResolvedChange?: (resolved: boolean) => void; - - /** - * The event handler called when a comment is edited. - */ - onCommentEdit?: CommentProps["onCommentEdit"]; - - /** - * The event handler called when a comment is deleted. - */ - onCommentDelete?: CommentProps["onCommentDelete"]; - - /** - * The event handler called when the thread is deleted. - * A thread is deleted when all its comments are deleted. - */ - onThreadDelete?: (thread: ThreadData) => void; - - /** - * The event handler called when clicking on a comment's author. - */ - onAuthorClick?: CommentProps["onAuthorClick"]; - - /** - * The event handler called when clicking on a mention. - */ - onMentionClick?: CommentProps["onMentionClick"]; - - /** - * The event handler called when clicking on a comment's attachment. - */ - // onAttachmentClick?: CommentProps["onAttachmentClick"]; - - /** - * The event handler called when the composer is submitted. - */ - // onComposerSubmit?: ComposerProps["onComposerSubmit"]; - - /** - * Override the component's strings. - */ - // overrides?: Partial< - // GlobalOverrides & ThreadOverrides & CommentOverrides & ComposerOverrides - // >; } /** @@ -120,23 +62,10 @@ export interface ThreadProps extends ComponentPropsWithoutRef<"div"> { */ export const Thread = ({ threadId, - indentCommentContent = true, showActions = "hover", showDeletedComments, showResolveAction = true, showReactions = true, - showComposer = "collapsed", - showAttachments = true, - // showComposerFormattingControls = true, - onResolvedChange, - onCommentEdit, - onCommentDelete, - onThreadDelete, - onAuthorClick, - onMentionClick, - // onAttachmentClick, - // onComposerSubmit, - // overrides, className, ...props }: ThreadProps) => { @@ -222,55 +151,22 @@ export const Thread = ({ return ( {thread.comments.map((comment, index) => { const isFirstComment = index === firstCommentIndex; - + const hasRightToResolve = true; // TODO + const showResolveAction = isFirstComment && hasRightToResolve; return ( - // - // - // - // - // ) : null - // } + showResolveAction={showResolveAction} /> ); })} From b761e1ed3d5b9bc65820cfe119aa9295f5ce8504 Mon Sep 17 00:00:00 2001 From: yousefed Date: Thu, 16 Jan 2025 20:43:31 +0100 Subject: [PATCH 019/144] basic userstore impl --- .../src/extensions/Comments/CommentsPlugin.ts | 23 +++++-- .../LiveBlocksThreadStore.ts | 0 .../{store => threadstore}/ThreadStore.ts | 0 .../TipTapThreadStore.ts | 0 .../YjsThreadStore.test.ts | 0 .../{store => threadstore}/YjsThreadStore.ts | 0 .../core/src/extensions/Comments/types.ts | 10 ++- .../Comments/userstore/UserStore.ts | 48 ++++++++++++++ .../react/src/components/Comments/Comment.tsx | 9 ++- .../react/src/components/Comments/Thread.tsx | 62 ++++--------------- .../react/src/components/Comments/useUsers.ts | 52 ++++++++++++++++ .../react/src/editor/ComponentsContext.tsx | 9 +-- 12 files changed, 143 insertions(+), 70 deletions(-) rename packages/core/src/extensions/Comments/{store => threadstore}/LiveBlocksThreadStore.ts (100%) rename packages/core/src/extensions/Comments/{store => threadstore}/ThreadStore.ts (100%) rename packages/core/src/extensions/Comments/{store => threadstore}/TipTapThreadStore.ts (100%) rename packages/core/src/extensions/Comments/{store => threadstore}/YjsThreadStore.test.ts (100%) rename packages/core/src/extensions/Comments/{store => threadstore}/YjsThreadStore.ts (100%) create mode 100644 packages/core/src/extensions/Comments/userstore/UserStore.ts create mode 100644 packages/react/src/components/Comments/useUsers.ts diff --git a/packages/core/src/extensions/Comments/CommentsPlugin.ts b/packages/core/src/extensions/Comments/CommentsPlugin.ts index 92457e5b4d..732dbada26 100644 --- a/packages/core/src/extensions/Comments/CommentsPlugin.ts +++ b/packages/core/src/extensions/Comments/CommentsPlugin.ts @@ -4,9 +4,10 @@ import { Decoration, DecorationSet } from "prosemirror-view"; import * as Y from "yjs"; import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; import { EventEmitter } from "../../util/EventEmitter.js"; -import { ThreadStore } from "./store/ThreadStore.js"; -import { YjsThreadStore } from "./store/YjsThreadStore.js"; -import { CommentBody, ThreadData } from "./types.js"; +import { ThreadStore } from "./threadstore/ThreadStore.js"; +import { YjsThreadStore } from "./threadstore/YjsThreadStore.js"; +import { CommentBody, ThreadData, User } from "./types.js"; +import { UserStore } from "./userstore/UserStore.js"; const PLUGIN_KEY = new PluginKey(`blocknote-comments`); enum CommentsPluginActions { @@ -139,7 +140,21 @@ export class CommentsPlugin extends EventEmitter { constructor( private readonly editor: BlockNoteEditor, - private readonly markType: string + private readonly markType: string, + public readonly userStore = new UserStore(async (userIds) => { + // fake slow network request + await new Promise((resolve) => setTimeout(resolve, 1000)); + + // random username + const names = ["John Doe", "Jane Doe", "John Smith", "Jane Smith"]; + const username = names[Math.floor(Math.random() * names.length)]; + + return userIds.map((id) => ({ + id, + username, + avatarUrl: `https://placehold.co/100x100?text=${username}`, + })); + }) ) { super(); diff --git a/packages/core/src/extensions/Comments/store/LiveBlocksThreadStore.ts b/packages/core/src/extensions/Comments/threadstore/LiveBlocksThreadStore.ts similarity index 100% rename from packages/core/src/extensions/Comments/store/LiveBlocksThreadStore.ts rename to packages/core/src/extensions/Comments/threadstore/LiveBlocksThreadStore.ts diff --git a/packages/core/src/extensions/Comments/store/ThreadStore.ts b/packages/core/src/extensions/Comments/threadstore/ThreadStore.ts similarity index 100% rename from packages/core/src/extensions/Comments/store/ThreadStore.ts rename to packages/core/src/extensions/Comments/threadstore/ThreadStore.ts diff --git a/packages/core/src/extensions/Comments/store/TipTapThreadStore.ts b/packages/core/src/extensions/Comments/threadstore/TipTapThreadStore.ts similarity index 100% rename from packages/core/src/extensions/Comments/store/TipTapThreadStore.ts rename to packages/core/src/extensions/Comments/threadstore/TipTapThreadStore.ts diff --git a/packages/core/src/extensions/Comments/store/YjsThreadStore.test.ts b/packages/core/src/extensions/Comments/threadstore/YjsThreadStore.test.ts similarity index 100% rename from packages/core/src/extensions/Comments/store/YjsThreadStore.test.ts rename to packages/core/src/extensions/Comments/threadstore/YjsThreadStore.test.ts diff --git a/packages/core/src/extensions/Comments/store/YjsThreadStore.ts b/packages/core/src/extensions/Comments/threadstore/YjsThreadStore.ts similarity index 100% rename from packages/core/src/extensions/Comments/store/YjsThreadStore.ts rename to packages/core/src/extensions/Comments/threadstore/YjsThreadStore.ts diff --git a/packages/core/src/extensions/Comments/types.ts b/packages/core/src/extensions/Comments/types.ts index a928f9aec2..c50f769ec3 100644 --- a/packages/core/src/extensions/Comments/types.ts +++ b/packages/core/src/extensions/Comments/types.ts @@ -3,9 +3,7 @@ export type CommentBody = any; export type CommentReactionData = { emoji: string; createdAt: Date; - users: { - id: string; - }[]; + usersIds: string[]; }; export type CommentData = { @@ -39,3 +37,9 @@ export type ThreadData = { metadata: any; deletedAt?: Date; }; + +export type User = { + id: string; + username: string; + avatarUrl: string; +}; diff --git a/packages/core/src/extensions/Comments/userstore/UserStore.ts b/packages/core/src/extensions/Comments/userstore/UserStore.ts new file mode 100644 index 0000000000..4cad7326e0 --- /dev/null +++ b/packages/core/src/extensions/Comments/userstore/UserStore.ts @@ -0,0 +1,48 @@ +import { EventEmitter } from "../../../util/EventEmitter.js"; +import { User } from "../types.js"; +export class UserStore extends EventEmitter { + private userCache: Map = new Map(); + + // avoid duplicate loads + private loadingUsers = new Set(); + + public constructor( + private readonly resolveUsers: (userIds: string[]) => Promise + ) { + super(); + } + + public async loadUsers(userIds: string[]) { + const missingUsers = userIds.filter( + (id) => !this.userCache.has(id) && !this.loadingUsers.has(id) + ); + + if (missingUsers.length === 0) { + return; + } + + for (const id of missingUsers) { + this.loadingUsers.add(id); + } + + try { + const users = await this.resolveUsers(missingUsers); + for (const user of users) { + this.userCache.set(user.id, user); + } + this.emit("update", this.userCache); + } finally { + for (const id of missingUsers) { + this.loadingUsers.delete(id); + } + } + } + + public getUser(userId: string): U | undefined { + return this.userCache.get(userId); + } + + public subscribe(cb: (users: Map) => void): () => void { + return this.on("update", cb); + } +} diff --git a/packages/react/src/components/Comments/Comment.tsx b/packages/react/src/components/Comments/Comment.tsx index e06c137e83..90537faa74 100644 --- a/packages/react/src/components/Comments/Comment.tsx +++ b/packages/react/src/components/Comments/Comment.tsx @@ -9,6 +9,7 @@ import { useCreateBlockNote } from "../../hooks/useCreateBlockNote.js"; import { useDictionary } from "../../i18n/dictionary.js"; import { CommentEditor } from "./CommentEditor.js"; import { schema } from "./schema.js"; +import { useUser } from "./useUsers.js"; /** * Liveblocks, but changed: @@ -292,6 +293,8 @@ export const Comment = ({ } }, []); // eslint-disable-line react-hooks/exhaustive-deps + const user = useUser(editor, comment.userId); + if (!showDeleted && !comment.body) { return null; } @@ -357,11 +360,7 @@ export const Comment = ({ return ( diff --git a/packages/react/src/components/Comments/Thread.tsx b/packages/react/src/components/Comments/Thread.tsx index 48502c1504..0bd33c6619 100644 --- a/packages/react/src/components/Comments/Thread.tsx +++ b/packages/react/src/components/Comments/Thread.tsx @@ -11,6 +11,7 @@ import { Comment, CommentProps } from "./Comment.js"; import { CommentEditor } from "./CommentEditor.js"; import { schema } from "./schema.js"; import { useThreadStore } from "./useThreadStore.js"; +import { useUsers } from "./useUsers.js"; export interface ThreadProps extends ComponentPropsWithoutRef<"div"> { /** @@ -42,24 +43,8 @@ export interface ThreadProps extends ComponentPropsWithoutRef<"div"> { * Whether to show deleted comments. */ showDeletedComments?: CommentProps["showDeleted"]; - - /** - * Whether to show attachments. - */ - showAttachments?: boolean; } -/** - * Displays a thread of comments, with a composer to reply - * to it. - * - * @example - * <> - * {threads.map((thread) => ( - * - * ))} - * - */ export const Thread = ({ threadId, showActions = "hover", @@ -82,6 +67,16 @@ export const Thread = ({ throw new Error("Thread not found"); } + const userIds = useMemo(() => { + return thread.comments.flatMap((c) => [ + c.userId, + ...c.reactions.flatMap((r) => r.usersIds), + ]); + }, [thread.comments]); + + // load all user data + useUsers(editor, userIds); + const newCommentEditor = useCreateBlockNote({ trailingBlock: false, dictionary: { @@ -100,41 +95,6 @@ export const Thread = ({ : thread.comments.findIndex((comment) => comment.body); }, [showDeletedComments, thread.comments]); - // const handleResolvedChange = useCallback( - // (resolved: boolean) => { - // onResolvedChange?.(resolved); - - // if (resolved) { - // markThreadAsResolved(thread.id); - // } else { - // markThreadAsUnresolved(thread.id); - // } - // }, - // [ - // markThreadAsResolved, - // markThreadAsUnresolved, - // onResolvedChange, - // thread.id, - // ] - // ); - - // TODO: thread deletion - - // const handleCommentDelete = useCallback( - // (comment: Comment) => { - // onCommentDelete?.(comment); - - // const filteredComments = thread.comments.filter( - // (comment) => comment.body - // ); - - // if (filteredComments.length <= 1) { - // onThreadDelete?.(thread); - // } - // }, - // [onCommentDelete, onThreadDelete, thread] - // ); - const onNewCommentSave = useCallback(async () => { await editor.comments!.store.addComment({ comment: { diff --git a/packages/react/src/components/Comments/useUsers.ts b/packages/react/src/components/Comments/useUsers.ts new file mode 100644 index 0000000000..62279d9810 --- /dev/null +++ b/packages/react/src/components/Comments/useUsers.ts @@ -0,0 +1,52 @@ +import { BlockNoteEditor, User } from "@blocknote/core"; +import { useCallback, useRef, useSyncExternalStore } from "react"; + +export function useUser( + editor: BlockNoteEditor, + userId: string +) { + return useUsers(editor, [userId]).get(userId); +} + +export function useUsers( + editor: BlockNoteEditor, + userIds: string[] +) { + const store = editor.comments!.userStore; + + // this ref works around this error: + // https://react.dev/reference/react/useSyncExternalStore#im-getting-an-error-the-result-of-getsnapshot-should-be-cached + // however, might not be a good practice to work around it this way + const usersRef = useRef>(); + + const getSnapshot = useCallback(() => { + const map = new Map(); + for (const id of userIds) { + const user = store.getUser(id); + if (user) { + map.set(id, user); + } + } + return map; + }, [store, userIds]); + + if (!usersRef.current) { + usersRef.current = getSnapshot(); + } + + // note: this is inefficient as it will trigger a re-render even if other users (not in userIds) are updated + const subscribe = useCallback( + (cb: () => void) => { + const ret = store.subscribe((_users) => { + // update ref when changed + usersRef.current = getSnapshot(); + cb(); + }); + store.loadUsers(userIds); + return ret; + }, + [store, getSnapshot, userIds] + ); + + return useSyncExternalStore(subscribe, () => usersRef.current!); +} diff --git a/packages/react/src/editor/ComponentsContext.tsx b/packages/react/src/editor/ComponentsContext.tsx index ffe5b56152..2ebdbce383 100644 --- a/packages/react/src/editor/ComponentsContext.tsx +++ b/packages/react/src/editor/ComponentsContext.tsx @@ -9,7 +9,7 @@ import { useContext, } from "react"; -import { BlockNoteEditor } from "@blocknote/core"; +import { BlockNoteEditor, User } from "@blocknote/core"; import { DefaultReactGridSuggestionItem } from "../components/SuggestionMenu/GridSuggestionMenu/types.js"; import { DefaultReactSuggestionItem } from "../components/SuggestionMenu/types.js"; @@ -273,12 +273,7 @@ export type ComponentProps = { Comment: { className?: string; children?: ReactNode; - authorInfo: - | "loading" - | { - username: string; - avatarUrl?: string; - }; + authorInfo: "loading" | User; timeString: string; actions?: ReactNode; showActions?: boolean | "hover"; From 5f5214790944c871d8d0bd6b2a30e77b3a38b999 Mon Sep 17 00:00:00 2001 From: yousefed Date: Thu, 16 Jan 2025 22:42:04 +0100 Subject: [PATCH 020/144] user auth --- .../src/extensions/Comments/CommentsPlugin.ts | 4 +- .../threadstore/DefaultThreadStoreAuth.ts | 74 +++++++++++++++++++ .../Comments/threadstore/ThreadStore.ts | 30 ++------ .../Comments/threadstore/ThreadStoreAuth.ts | 14 ++++ .../Comments/threadstore/YjsThreadStore.ts | 49 +++++++++--- .../react/src/components/Comments/Comment.tsx | 67 ++++++++++------- .../react/src/components/Comments/Thread.tsx | 60 +++++++-------- 7 files changed, 208 insertions(+), 90 deletions(-) create mode 100644 packages/core/src/extensions/Comments/threadstore/DefaultThreadStoreAuth.ts create mode 100644 packages/core/src/extensions/Comments/threadstore/ThreadStoreAuth.ts diff --git a/packages/core/src/extensions/Comments/CommentsPlugin.ts b/packages/core/src/extensions/Comments/CommentsPlugin.ts index 732dbada26..ed71492dab 100644 --- a/packages/core/src/extensions/Comments/CommentsPlugin.ts +++ b/packages/core/src/extensions/Comments/CommentsPlugin.ts @@ -4,6 +4,7 @@ import { Decoration, DecorationSet } from "prosemirror-view"; import * as Y from "yjs"; import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; import { EventEmitter } from "../../util/EventEmitter.js"; +import { DefaultThreadStoreAuth } from "./threadstore/DefaultThreadStoreAuth.js"; import { ThreadStore } from "./threadstore/ThreadStore.js"; import { YjsThreadStore } from "./threadstore/YjsThreadStore.js"; import { CommentBody, ThreadData, User } from "./types.js"; @@ -162,7 +163,8 @@ export class CommentsPlugin extends EventEmitter { this.store = new YjsThreadStore( editor, "blablauserid", - doc.getMap("threads") + doc.getMap("threads"), + new DefaultThreadStoreAuth("blablauserid", "comment") ); // TODO: unsubscribe diff --git a/packages/core/src/extensions/Comments/threadstore/DefaultThreadStoreAuth.ts b/packages/core/src/extensions/Comments/threadstore/DefaultThreadStoreAuth.ts new file mode 100644 index 0000000000..4faf240116 --- /dev/null +++ b/packages/core/src/extensions/Comments/threadstore/DefaultThreadStoreAuth.ts @@ -0,0 +1,74 @@ +import { CommentData, ThreadData } from "../types.js"; +import { ThreadStoreAuth } from "./ThreadStoreAuth.js"; + +/* + * The methods are annotated with the recommended auth pattern + * (but of course this could be different in your app): + * - View-only users should not be able to see any comments + * - Comment-only users and editors can: + * - - create new comments / replies / reactions + * - - edit / delete their own comments / reactions + * - - resolve / unresolve threads + * - Editors can also delete any comment or thread + */ +export class DefaultThreadStoreAuth extends ThreadStoreAuth { + constructor( + private readonly userId: string, + private readonly role: "comment" | "editor" + ) { + super(); + } + + /** + * Auth: should be possible by anyone with comment access + */ + canCreateThread(): boolean { + return true; + } + + /** + * Auth: should be possible by anyone with comment access + */ + canAddComment(_thread: ThreadData): boolean { + return true; + } + + /** + * Auth: should only be possible by the comment author + */ + canUpdateComment(comment: CommentData): boolean { + return comment.userId === this.userId; + } + + /** + * Auth: should be possible by the comment author OR an editor of the document + */ + canDeleteComment(comment: CommentData): boolean { + return comment.userId === this.userId || this.role === "editor"; + } + + /** + * Auth: should only be possible by an editor of the document + */ + canDeleteThread(_thread: ThreadData): boolean { + return this.role === "editor"; + } + + /** + * Auth: should be possible by anyone with comment access + */ + canResolveThread(_thread: ThreadData): boolean { + return true; + } + + /** + * Auth: should be possible by anyone with comment access + */ + canUnresolveThread(_thread: ThreadData): boolean { + return true; + } + + // TODO: reactions + // abstract canAddReaction(comment: CommentData): boolean; + // abstract canDeleteReaction(comment: CommentData): boolean; +} diff --git a/packages/core/src/extensions/Comments/threadstore/ThreadStore.ts b/packages/core/src/extensions/Comments/threadstore/ThreadStore.ts index 79c8d4f497..936288d463 100644 --- a/packages/core/src/extensions/Comments/threadstore/ThreadStore.ts +++ b/packages/core/src/extensions/Comments/threadstore/ThreadStore.ts @@ -1,23 +1,19 @@ import { CommentBody, CommentData, ThreadData } from "../types.js"; +import { ThreadStoreAuth } from "./ThreadStoreAuth.js"; /** * ThreadStore is an abstract class that defines the interface * to read / add / update / delete threads and comments. - * - * The methods are annotated with the recommended auth pattern - * (but of course this could be different in your app): - * - View-only users should not be able to see any comments - * - Comment-only users and editors can: - * - - create new comments / replies / reactions - * - - edit / delete their own comments / reactions - * - - resolve / unresolve threads - * - Editors can also delete any comment or thread */ export abstract class ThreadStore { + public readonly auth: ThreadStoreAuth; + + constructor(auth: ThreadStoreAuth) { + this.auth = auth; + } + /** * Creates a new thread with an initial comment. - * - * Auth: should be possible by anyone with comment access */ abstract createThread(options: { initialComment: { @@ -29,8 +25,6 @@ export abstract class ThreadStore { /** * Adds a comment to a thread. - * - * Auth: should be possible by anyone with comment access */ abstract addComment(options: { comment: { @@ -42,8 +36,6 @@ export abstract class ThreadStore { /** * Updates a comment in a thread. - * - * Auth: should only be possible by the comment author */ abstract updateComment(options: { comment: { @@ -56,8 +48,6 @@ export abstract class ThreadStore { /** * Deletes a comment from a thread. - * - * Auth: should be possible by the comment author OR an editor of the document */ abstract deleteComment(options: { threadId: string; @@ -66,22 +56,16 @@ export abstract class ThreadStore { /** * Deletes a thread. - * - * Auth: should only be possible by an editor of the document */ abstract deleteThread(options: { threadId: string }): Promise; /** * Marks a thread as resolved. - * - * Auth: should be possible by anyone with comment access */ abstract resolveThread(options: { threadId: string }): Promise; /** * Marks a thread as unresolved. - * - * Auth: should be possible by anyone with comment access */ abstract unresolveThread(options: { threadId: string }): Promise; diff --git a/packages/core/src/extensions/Comments/threadstore/ThreadStoreAuth.ts b/packages/core/src/extensions/Comments/threadstore/ThreadStoreAuth.ts new file mode 100644 index 0000000000..57031c015c --- /dev/null +++ b/packages/core/src/extensions/Comments/threadstore/ThreadStoreAuth.ts @@ -0,0 +1,14 @@ +import { CommentData, ThreadData } from "../types.js"; + +export abstract class ThreadStoreAuth { + abstract canCreateThread(): boolean; + abstract canAddComment(thread: ThreadData): boolean; + abstract canUpdateComment(comment: CommentData): boolean; + abstract canDeleteComment(comment: CommentData): boolean; + abstract canDeleteThread(thread: ThreadData): boolean; + abstract canResolveThread(thread: ThreadData): boolean; + abstract canUnresolveThread(thread: ThreadData): boolean; + // TODO: reactions + // abstract canAddReaction(comment: CommentData): boolean; + // abstract canDeleteReaction(comment: CommentData): boolean; +} diff --git a/packages/core/src/extensions/Comments/threadstore/YjsThreadStore.ts b/packages/core/src/extensions/Comments/threadstore/YjsThreadStore.ts index fb9c7f9048..91e90d4837 100644 --- a/packages/core/src/extensions/Comments/threadstore/YjsThreadStore.ts +++ b/packages/core/src/extensions/Comments/threadstore/YjsThreadStore.ts @@ -3,24 +3,16 @@ import * as Y from "yjs"; import { BlockNoteEditor } from "../../../editor/BlockNoteEditor.js"; import { CommentBody, CommentData, ThreadData } from "../types.js"; import { ThreadStore } from "./ThreadStore.js"; - -// type YjsType = { -// [K in keyof T]: T[K] extends Date ? string : T[K]; // TODO: dates as string? -// }; - -// type YjsTypeConvertArrays = { -// [K in keyof T]: T[K] extends Array -// ? Y.Array> -// : YjsType; -// }; +import { ThreadStoreAuth } from "./ThreadStoreAuth.js"; export class YjsThreadStore extends ThreadStore { constructor( private readonly editor: BlockNoteEditor, private readonly userId: string, - private readonly threadsYMap: Y.Map + private readonly threadsYMap: Y.Map, + auth: ThreadStoreAuth ) { - super(); + super(auth); } private transact = ( @@ -41,6 +33,10 @@ export class YjsThreadStore extends ThreadStore { }; metadata?: any; }) => { + if (!this.auth.canCreateThread()) { + throw new Error("Not authorized"); + } + const date = new Date(); const comment: CommentData = { @@ -83,6 +79,10 @@ export class YjsThreadStore extends ThreadStore { throw new Error("Thread not found"); } + if (!this.auth.canAddComment(yMapToThread(yThread))) { + throw new Error("Not authorized"); + } + const date = new Date(); const comment: CommentData = { type: "comment", @@ -129,6 +129,11 @@ export class YjsThreadStore extends ThreadStore { } const yComment = yThread.get("comments").get(yCommentIndex); + + if (!this.auth.canUpdateComment(yMapToComment(yComment))) { + throw new Error("Not authorized"); + } + yComment.set("body", options.comment.body); yComment.set("updatedAt", new Date().getTime()); yComment.set("metadata", options.comment.metadata); @@ -157,6 +162,10 @@ export class YjsThreadStore extends ThreadStore { const yComment = yThread.get("comments").get(yCommentIndex); + if (!this.auth.canDeleteComment(yMapToComment(yComment))) { + throw new Error("Not authorized"); + } + if (yComment.get("deletedAt")) { throw new Error("Comment already deleted"); } @@ -186,6 +195,14 @@ export class YjsThreadStore extends ThreadStore { ); public deleteThread = this.transact((options: { threadId: string }) => { + if ( + !this.auth.canDeleteThread( + yMapToThread(this.threadsYMap.get(options.threadId)) + ) + ) { + throw new Error("Not authorized"); + } + this.threadsYMap.delete(options.threadId); }); @@ -195,6 +212,10 @@ export class YjsThreadStore extends ThreadStore { throw new Error("Thread not found"); } + if (!this.auth.canResolveThread(yMapToThread(yThread))) { + throw new Error("Not authorized"); + } + yThread.set("resolved", true); yThread.set("resolvedUpdatedAt", new Date().getTime()); }); @@ -205,6 +226,10 @@ export class YjsThreadStore extends ThreadStore { throw new Error("Thread not found"); } + if (!this.auth.canUnresolveThread(yMapToThread(yThread))) { + throw new Error("Not authorized"); + } + yThread.set("resolved", false); yThread.set("resolvedUpdatedAt", new Date().getTime()); }); diff --git a/packages/react/src/components/Comments/Comment.tsx b/packages/react/src/components/Comments/Comment.tsx index 90537faa74..3023b20954 100644 --- a/packages/react/src/components/Comments/Comment.tsx +++ b/packages/react/src/components/Comments/Comment.tsx @@ -300,19 +300,30 @@ export const Comment = ({ } let actions: ReactNode | undefined = undefined; + const canAddReaction = true; //editor.comments!.store.auth.canAddReaction(comment); + const canDeleteComment = + editor.comments!.store.auth.canDeleteComment(comment); + const canEditComment = editor.comments!.store.auth.canUpdateComment(comment); + + const showResolveOrReopen = + showResolveAction && + (thread.resolved + ? editor.comments!.store.auth.canUnresolveThread(thread) + : editor.comments!.store.auth.canResolveThread(thread)); if (showActions && !isEditing) { actions = ( - {additionalActions ?? null} - - R1 - - {showResolveAction && + {canAddReaction && ( + + R1 + + )} + {showResolveOrReopen && (thread.resolved ? ( ))} - - - - ... - - - - - Edit comment - - - Delete comment - - - + {(canDeleteComment || canEditComment) && ( + + + + ... + + + + {canEditComment && ( + + Edit comment + + )} + {canDeleteComment && ( + + Delete comment + + )} + + + )} ); } diff --git a/packages/react/src/components/Comments/Thread.tsx b/packages/react/src/components/Comments/Thread.tsx index 0bd33c6619..590b0648cd 100644 --- a/packages/react/src/components/Comments/Thread.tsx +++ b/packages/react/src/components/Comments/Thread.tsx @@ -107,7 +107,8 @@ export const Thread = ({ newCommentEditor.removeBlocks(newCommentEditor.document); }, [editor.comments, newCommentEditor, thread.id]); - // TODO: extract component + const showComposer = editor.comments!.store.auth.canAddComment(thread); + return ( {thread.comments.map((comment, index) => { const isFirstComment = index === firstCommentIndex; - const hasRightToResolve = true; // TODO - const showResolveAction = isFirstComment && hasRightToResolve; + return ( ); })} - - { - if (!isFocused && isEmpty) { - return null; - } - - return ( - - - Save - - - ); - }} - /> - + {showComposer && ( + + { + if (!isFocused && isEmpty) { + return null; + } + + return ( + + + Save + + + ); + }} + /> + + )} ); }; From 060708d9cfae06cbd074e34fb5f00df4c1a0d9e5 Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Fri, 24 Jan 2025 12:22:42 +0100 Subject: [PATCH 021/144] Big comments UX WIP --- .../Comments/threadstore/ThreadStore.ts | 2 +- .../Comments/threadstore/YjsThreadStore.ts | 116 ++++++++++++-- packages/mantine/src/Badge/Badge.tsx | 40 +++++ packages/mantine/src/comments/Card.tsx | 16 +- packages/mantine/src/comments/Editor.tsx | 11 +- packages/mantine/src/components.tsx | 6 +- packages/mantine/src/style.css | 76 +++++++++ packages/mantine/src/toolbar/Toolbar.tsx | 3 +- .../react/src/components/Comments/Comment.tsx | 148 +++++++++++++++--- .../src/components/Comments/CommentEditor.tsx | 2 +- .../components/Comments/FloatingComposer.tsx | 12 +- .../react/src/components/Comments/Thread.tsx | 5 +- packages/react/src/editor/BlockNoteView.tsx | 8 +- .../react/src/editor/ComponentsContext.tsx | 13 ++ .../src/hooks/useUIElementPositioning.ts | 1 - 15 files changed, 394 insertions(+), 65 deletions(-) create mode 100644 packages/mantine/src/Badge/Badge.tsx diff --git a/packages/core/src/extensions/Comments/threadstore/ThreadStore.ts b/packages/core/src/extensions/Comments/threadstore/ThreadStore.ts index 936288d463..01c2b4c75d 100644 --- a/packages/core/src/extensions/Comments/threadstore/ThreadStore.ts +++ b/packages/core/src/extensions/Comments/threadstore/ThreadStore.ts @@ -77,7 +77,7 @@ export abstract class ThreadStore { abstract addReaction(options: { threadId: string; commentId: string; - // reaction: string; TODO + reaction: string; }): Promise; /** diff --git a/packages/core/src/extensions/Comments/threadstore/YjsThreadStore.ts b/packages/core/src/extensions/Comments/threadstore/YjsThreadStore.ts index 91e90d4837..aad1354c87 100644 --- a/packages/core/src/extensions/Comments/threadstore/YjsThreadStore.ts +++ b/packages/core/src/extensions/Comments/threadstore/YjsThreadStore.ts @@ -1,7 +1,12 @@ import { v4 } from "uuid"; import * as Y from "yjs"; import { BlockNoteEditor } from "../../../editor/BlockNoteEditor.js"; -import { CommentBody, CommentData, ThreadData } from "../types.js"; +import { + CommentBody, + CommentData, + CommentReactionData, + ThreadData, +} from "../types.js"; import { ThreadStore } from "./ThreadStore.js"; import { ThreadStoreAuth } from "./ThreadStoreAuth.js"; @@ -234,19 +239,76 @@ export class YjsThreadStore extends ThreadStore { yThread.set("resolvedUpdatedAt", new Date().getTime()); }); - public addReaction = this.transact( - (options: { - threadId: string; - commentId: string; - // reaction: string; TODO - }) => { - throw new Error("Not implemented"); - } - ); + public toggleReaction = this.transact( + (options: { threadId: string; commentId: string; reaction: string }) => { + const yThread = this.threadsYMap.get(options.threadId); + if (!yThread) { + throw new Error("Thread not found"); + } + + const yCommentIndex = yArrayFindIndex( + yThread.get("comments"), + (comment) => comment.get("id") === options.commentId + ); + if (yCommentIndex === -1) { + throw new Error("Comment not found"); + } + + const yComment = yThread.get("comments").get(yCommentIndex); + if (!this.auth.canUpdateComment(yMapToComment(yComment))) { + throw new Error("Not authorized"); + } + + const yReactionIndex = yArrayFindIndex( + yComment.get("reactions"), + (reaction) => reaction.get("emoji") === options.reaction + ); - public deleteReaction = this.transact( - (options: { threadId: string; commentId: string; reactionId: string }) => { - throw new Error("Not implemented"); + if (yReactionIndex !== -1) { + const yReaction = yComment.get("reactions").get(yReactionIndex); + + const yUserIdIndex = yArrayFindIndex( + yReaction.get("usersIds"), + (userId) => userId === this.userId + ); + if (yUserIdIndex !== -1) { + // This user already reacted with this emoji, so it should be toggled + // off. + + // Reaction exists and contains user ID, so the user ID should be + // removed from the list. If the list is now empty, remove the reaction + // altogether. + yReaction.get("usersIds").delete(yUserIdIndex); + + if (yReaction.get("usersIds").length === 0) { + yComment.get("reactions").delete(yReactionIndex); + yComment.set("updatedAt", new Date().getTime()); + } + + return; + } + // Other users have reacted with this emoji, but this user has not, so + // it should be toggled on. + + // Reaction exists but does not contain user ID, so the user ID should + // be added to the list. + yReaction.get("usersIds").push([this.userId]); + + return; + } + // No one has reacted with this emoji, so it should again be toggled on. + + // Reaction does not exist, and so a new one should be created and this + // user should be added to the list. + const date = new Date(); + const reaction: CommentReactionData = { + emoji: options.reaction, + createdAt: date, + usersIds: [this.userId, "fesfes"], + }; + + yComment.get("reactions").push([reactionToYMap(reaction)]); + yComment.set("updatedAt", date.getTime()); } ); @@ -283,6 +345,22 @@ export class YjsThreadStore extends ThreadStore { // HELPERS +function reactionToYMap(reaction: CommentReactionData) { + const yMap = new Y.Map(); + yMap.set("emoji", reaction.emoji); + yMap.set("createdAt", reaction.createdAt.getTime()); + if (reaction.usersIds.length === 0) { + throw new Error("Need at least one user ID in reactionToYMap"); + } + const usersIdsArray = new Y.Array(); + + usersIdsArray.push([...reaction.usersIds]); + + yMap.set("usersIds", usersIdsArray); + + return yMap; +} + function commentToYMap(comment: CommentData) { const yMap = new Y.Map(); yMap.set("id", comment.id); @@ -320,6 +398,14 @@ function threadToYMap(thread: ThreadData) { return yMap; } +function yMapToReaction(yMap: Y.Map): CommentReactionData { + return { + emoji: yMap.get("emoji"), + createdAt: yMap.get("createdAt"), + usersIds: yMap.get("usersIds").toArray(), + }; +} + function yMapToComment(yMap: Y.Map): CommentData { return { type: "comment", @@ -330,7 +416,9 @@ function yMapToComment(yMap: Y.Map): CommentData { deletedAt: yMap.get("deletedAt") ? new Date(yMap.get("deletedAt")) : undefined, - reactions: [], + reactions: yMap + .get("reactions") + .map((reaction: Y.Map) => yMapToReaction(reaction)), metadata: yMap.get("metadata"), body: yMap.get("body"), }; diff --git a/packages/mantine/src/Badge/Badge.tsx b/packages/mantine/src/Badge/Badge.tsx new file mode 100644 index 0000000000..9d3a213c74 --- /dev/null +++ b/packages/mantine/src/Badge/Badge.tsx @@ -0,0 +1,40 @@ +import { forwardRef } from "react"; +import { ComponentProps } from "@blocknote/react"; +import { Chip, Group } from "@mantine/core"; +import { assertEmpty } from "@blocknote/core"; + +export const Badge = forwardRef< + HTMLInputElement, + ComponentProps["Generic"]["Badge"]["Root"] +>((props, ref) => { + const { className, text, icon, isSelected, onClick, ...rest } = props; + + assertEmpty(rest); + + return ( + + {icon} + {text} + + ); +}); + +export const BadgeGroup = forwardRef< + HTMLDivElement, + ComponentProps["Generic"]["Badge"]["Group"] +>((props, ref) => { + const { className, children, ...rest } = props; + + assertEmpty(rest); + + return ( + + {children} + + ); +}); diff --git a/packages/mantine/src/comments/Card.tsx b/packages/mantine/src/comments/Card.tsx index 2e50ea092d..38865105ae 100644 --- a/packages/mantine/src/comments/Card.tsx +++ b/packages/mantine/src/comments/Card.tsx @@ -12,14 +12,7 @@ export const Card = forwardRef< assertEmpty(rest, false); return ( - + {children} ); @@ -34,12 +27,7 @@ export const CardSection = forwardRef< assertEmpty(rest, false); return ( - + {children} ); diff --git a/packages/mantine/src/comments/Editor.tsx b/packages/mantine/src/comments/Editor.tsx index af5a88f4c2..6bd4805b8f 100644 --- a/packages/mantine/src/comments/Editor.tsx +++ b/packages/mantine/src/comments/Editor.tsx @@ -1,6 +1,6 @@ import { assertEmpty } from "@blocknote/core"; import { ComponentProps } from "@blocknote/react"; -import { forwardRef } from "react"; +import { forwardRef, useEffect } from "react"; import { BlockNoteView } from "../BlockNoteView.js"; export const Editor = forwardRef< @@ -11,8 +11,17 @@ export const Editor = forwardRef< assertEmpty(rest, false); + // When we click the edit button on a comment, we also want to focus the + // comment editor + useEffect(() => { + if (editable) { + editor.focus(); + } + }, [editable, editor]); + return (
{props.children}
, TextInput: TextInput, diff --git a/packages/mantine/src/style.css b/packages/mantine/src/style.css index 9fe02136fa..495f845bb9 100644 --- a/packages/mantine/src/style.css +++ b/packages/mantine/src/style.css @@ -564,3 +564,79 @@ display: flex; justify-content: space-between; } + +/* TODO: Clean up */ +.bn-mantine .bn-thread { + background-color: var(--bn-colors-menu-background); + border: var(--bn-border); + border-radius: var(--bn-border-radius-medium); + box-shadow: var(--bn-shadow-medium); + color: var(--bn-colors-menu-text); + overflow: visible; +} + +.bn-mantine .bn-thread .bn-grid-suggestion-menu { + gap: 0; + max-height: 300px; + padding: 6px; +} + +.bn-mantine .bn-thread .bn-grid-suggestion-menu-item { + border-radius: var(--bn-border-radius-small); + font-size: 20px; + height: 32px; + margin: 0; + width: 32px; +} + +.bn-mantine .bn-thread-comments { + border-bottom: var(--bn-border); +} + +.bn-mantine .bn-comment-actions-wrapper { + width: 100%; + display: flex; + align-items: flex-end; + justify-content: flex-end; +} + +.bn-mantine .bn-action-toolbar { + background-color: var(--bn-colors-menu-background); + border: var(--bn-border); + border-radius: var(--bn-border-radius-medium); + gap: 0; + padding: 2px; +} + +.bn-action-toolbar .mantine-Button-root, +.bn-action-toolbar .mantine-ActionIcon-root { + background-color: var(--bn-colors-menu-background); + border: none; + border-radius: var(--bn-border-radius-small); + color: var(--bn-colors-menu-text); +} + +.bn-action-toolbar .mantine-Button-root:hover, +.bn-action-toolbar .mantine-ActionIcon-root:hover { + background-color: var(--bn-colors-hovered-background); + border: none; + color: var(--bn-colors-hovered-text); +} + +.bn-action-toolbar .mantine-Button-root[data-selected], +.bn-action-toolbar .mantine-ActionIcon-root[data-selected] { + background-color: var(--bn-colors-selected-background); + border: none; + color: var(--bn-colors-selected-text); +} + +.bn-action-toolbar .mantine-Button-root[data-disabled], +.bn-action-toolbar .mantine-ActionIcon-root[data-disabled] { + background-color: var(--bn-colors-disabled-background); + border: none; + color: var(--bn-colors-disabled-text); +} + +.bn-mantine .bn-action-toolbar .mantine-Menu-itemLabel { + font-size: 12px; +} \ No newline at end of file diff --git a/packages/mantine/src/toolbar/Toolbar.tsx b/packages/mantine/src/toolbar/Toolbar.tsx index 47dd1cb6e4..60e12a6dbe 100644 --- a/packages/mantine/src/toolbar/Toolbar.tsx +++ b/packages/mantine/src/toolbar/Toolbar.tsx @@ -36,8 +36,7 @@ export const Toolbar = forwardRef( // TODO: aria-label onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} - justify={variant === "action-toolbar" ? "flex-end" : undefined} - gap={variant === "action-toolbar" ? "xs" : undefined}> + gap={variant === "action-toolbar" ? 2 : undefined}> {children} ); diff --git a/packages/react/src/components/Comments/Comment.tsx b/packages/react/src/components/Comments/Comment.tsx index 3023b20954..4dda62d608 100644 --- a/packages/react/src/components/Comments/Comment.tsx +++ b/packages/react/src/components/Comments/Comment.tsx @@ -1,12 +1,35 @@ "use client"; -import { CommentData, ThreadData, mergeCSSClasses } from "@blocknote/core"; -import type { ComponentPropsWithoutRef, MouseEvent, ReactNode } from "react"; -import { useCallback, useEffect, useState } from "react"; +import { + CommentData, + ThreadData, + mergeCSSClasses, + getDefaultEmojiPickerItems, + DefaultGridSuggestionItem, +} from "@blocknote/core"; +import { + ComponentPropsWithoutRef, + MouseEvent, + ReactNode, + useCallback, + useEffect, + useState, + useRef, +} from "react"; +import { + RiArrowGoBackFill, + RiCheckFill, + RiDeleteBinFill, + RiEditFill, + RiEmotionFill, + RiMoreFill, +} from "react-icons/ri"; + import { useComponentsContext } from "../../editor/ComponentsContext.js"; import { useBlockNoteEditor } from "../../hooks/useBlockNoteEditor.js"; import { useCreateBlockNote } from "../../hooks/useCreateBlockNote.js"; import { useDictionary } from "../../i18n/dictionary.js"; +import { GridSuggestionMenu } from "../SuggestionMenu/GridSuggestionMenu/GridSuggestionMenu.js"; import { CommentEditor } from "./CommentEditor.js"; import { schema } from "./schema.js"; import { useUser } from "./useUsers.js"; @@ -263,9 +286,16 @@ export const Comment = ({ }); }, [comment, thread.id, editor.comments]); - const onReactionSelect = useCallback(() => { - console.log("reaction select"); - }, []); + const onReactionSelect = useCallback( + (emoji: string) => { + editor.comments?.store.toggleReaction({ + threadId: thread.id, + commentId: comment.id, + reaction: emoji, + }); + }, + [comment.id, editor.comments?.store, thread.id] + ); const onResolve = useCallback(() => { editor.comments!.store.resolveThread({ @@ -295,6 +325,16 @@ export const Comment = ({ const user = useUser(editor, comment.userId); + // TODO: Change emoji picker implementation to premade component? + const emojis = useRef(undefined); + useEffect(() => { + const getEmojis = async () => { + emojis.current = await getDefaultEmojiPickerItems(editor, ""); + }; + + getEmojis(); + }, [editor]); + if (!showDeleted && !comment.body) { return null; } @@ -314,14 +354,32 @@ export const Comment = ({ if (showActions && !isEditing) { actions = ( + className={mergeCSSClasses("bn-action-toolbar", "bn-comment-actions")}> {canAddReaction && ( - - R1 - + + + + + + + + {/* TODO: Change emoji picker implementation to premade component? */} + ({ + ...item, + icon: <>{item.id}, + })) || [] + } + loadingState={"loaded"} + selectedIndex={-1} + columns={6} + onItemClick={(item) => onReactionSelect(item.id)} + /> + + )} {showResolveOrReopen && (thread.resolved ? ( @@ -329,14 +387,14 @@ export const Comment = ({ mainTooltip="Re-open" variant="compact" onClick={onReopen}> - R2 +
) : ( - R2 + ))} {(canDeleteComment || canEditComment) && ( @@ -345,17 +403,21 @@ export const Comment = ({ - ... +
{canEditComment && ( - + } + onClick={handleEdit}> Edit comment )} {canDeleteComment && ( - + } + onClick={onDelete}> Delete comment )} @@ -389,13 +451,10 @@ export const Comment = ({ actions={({ isEmpty }) => ( - - X - + className={mergeCSSClasses( + "bn-action-toolbar", + "bn-comment-actions" + )}> Save + + Cancel + )} /> ) : comment.body ? ( <> - + 0 + ? () => ( + + {comment.reactions.map((reaction) => ( + + onReactionSelect(reaction.emoji) + }> + ))} + + ) + : undefined + } + /> {showReactions && comment.reactions.length > 0 && (
diff --git a/packages/react/src/components/Comments/CommentEditor.tsx b/packages/react/src/components/Comments/CommentEditor.tsx index 43841c4a16..e869cbae41 100644 --- a/packages/react/src/components/Comments/CommentEditor.tsx +++ b/packages/react/src/components/Comments/CommentEditor.tsx @@ -59,7 +59,7 @@ export const CommentEditor = (props: { editable={props.editable} /> {props.actions && ( -
+
)} diff --git a/packages/react/src/components/Comments/FloatingComposer.tsx b/packages/react/src/components/Comments/FloatingComposer.tsx index 214b4d740c..aa449d1d42 100644 --- a/packages/react/src/components/Comments/FloatingComposer.tsx +++ b/packages/react/src/components/Comments/FloatingComposer.tsx @@ -1,3 +1,5 @@ +import { mergeCSSClasses } from "@blocknote/core"; + import { useComponentsContext } from "../../editor/ComponentsContext.js"; import { useBlockNoteEditor } from "../../hooks/useBlockNoteEditor.js"; import { useCreateBlockNote } from "../../hooks/useCreateBlockNote.js"; @@ -23,13 +25,19 @@ export function FloatingComposer() { }); return ( - + ( - + + className={mergeCSSClasses( + "bn-action-toolbar", + "bn-comment-actions" + )}> { editor.mount(element, portalManager); + + // Since we mount the editor ourselves, we also have to manually + // autofocus it on mount. + if (rest.autoFocus) { + element?.focus(); + } }, - [editor, portalManager] + [editor, portalManager, rest.autoFocus] ); return ( diff --git a/packages/react/src/editor/ComponentsContext.tsx b/packages/react/src/editor/ComponentsContext.tsx index 2ebdbce383..f8b5842c2b 100644 --- a/packages/react/src/editor/ComponentsContext.tsx +++ b/packages/react/src/editor/ComponentsContext.tsx @@ -184,6 +184,19 @@ export type ComponentProps = { }; // TODO: We should try to make everything as generic as we can Generic: { + Badge: { + Root: { + className?: string; + text: string; + icon?: ReactNode; + isSelected?: boolean; + onClick?: () => void; + }; + Group: { + className?: string; + children: ReactNode; + }; + }; Form: { Root: { children?: ReactNode; diff --git a/packages/react/src/hooks/useUIElementPositioning.ts b/packages/react/src/hooks/useUIElementPositioning.ts index 1e9de1b10d..926447b149 100644 --- a/packages/react/src/hooks/useUIElementPositioning.ts +++ b/packages/react/src/hooks/useUIElementPositioning.ts @@ -15,7 +15,6 @@ export function useUIElementPositioning( ) { const { refs, update, context, floatingStyles } = useFloating({ open: show, - strategy: "fixed", ...options, }); const { isMounted, styles } = useTransitionStyles(context); From a5e07c09066be25592183bbf3c8a81da54e352fa Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Wed, 29 Jan 2025 00:48:24 +0100 Subject: [PATCH 022/144] Updated reactions UX --- package-lock.json | 11 ++ .../Comments/threadstore/YjsThreadStore.ts | 2 +- packages/mantine/src/Badge/Badge.tsx | 40 ----- packages/mantine/src/badge/Badge.tsx | 76 ++++++++ packages/mantine/src/components.tsx | 2 +- packages/mantine/src/style.css | 40 ++++- .../mantine/src/toolbar/ToolbarButton.tsx | 8 +- packages/react/package.json | 3 + .../react/src/components/Comments/Comment.tsx | 165 +++++++++--------- .../react/src/editor/ComponentsContext.tsx | 2 + 10 files changed, 219 insertions(+), 130 deletions(-) delete mode 100644 packages/mantine/src/Badge/Badge.tsx create mode 100644 packages/mantine/src/badge/Badge.tsx diff --git a/package-lock.json b/package-lock.json index 1a862c1d96..566a3b1140 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3648,6 +3648,15 @@ "resolved": "https://registry.npmjs.org/@emoji-mart/data/-/data-1.2.1.tgz", "integrity": "sha512-no2pQMWiBy6gpBEiqGeU77/bFejDqUTRY7KX+0+iur13op3bqUsXdnwoZs6Xb1zbv0gAj5VvS1PWoUUckSr5Dw==" }, + "node_modules/@emoji-mart/react": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@emoji-mart/react/-/react-1.1.1.tgz", + "integrity": "sha512-NMlFNeWgv1//uPsvLxvGQoIerPuVdXwK/EUek8OOkJ6wVOWPUizRBJU0hDqWZCOROVpfBgCemaC3m6jDOXi03g==", + "peerDependencies": { + "emoji-mart": "^5.2", + "react": "^16.8 || ^17 || ^18" + } + }, "node_modules/@emotion/babel-plugin": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.12.0.tgz", @@ -31907,6 +31916,8 @@ "license": "MPL-2.0", "dependencies": { "@blocknote/core": "^0.23.1", + "@emoji-mart/data": "^1.2.1", + "@emoji-mart/react": "^1.1.1", "@floating-ui/react": "^0.26.4", "@tiptap/core": "^2.7.1", "@tiptap/react": "^2.7.1", diff --git a/packages/core/src/extensions/Comments/threadstore/YjsThreadStore.ts b/packages/core/src/extensions/Comments/threadstore/YjsThreadStore.ts index aad1354c87..aea14335f3 100644 --- a/packages/core/src/extensions/Comments/threadstore/YjsThreadStore.ts +++ b/packages/core/src/extensions/Comments/threadstore/YjsThreadStore.ts @@ -304,7 +304,7 @@ export class YjsThreadStore extends ThreadStore { const reaction: CommentReactionData = { emoji: options.reaction, createdAt: date, - usersIds: [this.userId, "fesfes"], + usersIds: [this.userId], }; yComment.get("reactions").push([reactionToYMap(reaction)]); diff --git a/packages/mantine/src/Badge/Badge.tsx b/packages/mantine/src/Badge/Badge.tsx deleted file mode 100644 index 9d3a213c74..0000000000 --- a/packages/mantine/src/Badge/Badge.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { forwardRef } from "react"; -import { ComponentProps } from "@blocknote/react"; -import { Chip, Group } from "@mantine/core"; -import { assertEmpty } from "@blocknote/core"; - -export const Badge = forwardRef< - HTMLInputElement, - ComponentProps["Generic"]["Badge"]["Root"] ->((props, ref) => { - const { className, text, icon, isSelected, onClick, ...rest } = props; - - assertEmpty(rest); - - return ( - - {icon} - {text} - - ); -}); - -export const BadgeGroup = forwardRef< - HTMLDivElement, - ComponentProps["Generic"]["Badge"]["Group"] ->((props, ref) => { - const { className, children, ...rest } = props; - - assertEmpty(rest); - - return ( - - {children} - - ); -}); diff --git a/packages/mantine/src/badge/Badge.tsx b/packages/mantine/src/badge/Badge.tsx new file mode 100644 index 0000000000..213a05316f --- /dev/null +++ b/packages/mantine/src/badge/Badge.tsx @@ -0,0 +1,76 @@ +import { forwardRef } from "react"; +import { ComponentProps } from "@blocknote/react"; +import { + Chip as MantineChip, + Group as MantineGroup, + Tooltip as MantineTooltip, +} from "@mantine/core"; +import { assertEmpty } from "@blocknote/core"; + +import { TooltipContent } from "../toolbar/ToolbarButton.js"; + +export const Badge = forwardRef< + HTMLInputElement, + ComponentProps["Generic"]["Badge"]["Root"] +>((props, ref) => { + const { + className, + text, + icon, + isSelected, + mainTooltip, + secondaryTooltip, + onClick, + ...rest + } = props; + + // false, because rest props can be added by mantine when chip is used as a trigger + // assertEmpty in this case is only used at typescript level, not runtime level + assertEmpty(rest, false); + + const badge = ( + + {icon} + {text} + + ); + + if (!mainTooltip) { + return badge; + } + + return ( + + } + multiline> + {badge} + + ); +}); + +export const BadgeGroup = forwardRef< + HTMLDivElement, + ComponentProps["Generic"]["Badge"]["Group"] +>((props, ref) => { + const { className, children, ...rest } = props; + + assertEmpty(rest); + + return ( + + {children} + + ); +}); diff --git a/packages/mantine/src/components.tsx b/packages/mantine/src/components.tsx index 74a5b93b35..f39e4b89c3 100644 --- a/packages/mantine/src/components.tsx +++ b/packages/mantine/src/components.tsx @@ -1,6 +1,6 @@ import { Components } from "@blocknote/react"; -import { Badge, BadgeGroup } from "./Badge/Badge.js"; +import { Badge, BadgeGroup } from "./badge/Badge.js"; import { Card, CardSection } from "./comments/Card.js"; import { Comment } from "./comments/Comment.js"; import { Editor } from "./comments/Editor.js"; diff --git a/packages/mantine/src/style.css b/packages/mantine/src/style.css index 495f845bb9..5692c4dbea 100644 --- a/packages/mantine/src/style.css +++ b/packages/mantine/src/style.css @@ -549,6 +549,7 @@ color: var(--bn-colors-tooltip-text); padding: 4px 10px; text-align: center; + white-space: pre-wrap; } /* Additional menu styles */ @@ -596,11 +597,48 @@ .bn-mantine .bn-comment-actions-wrapper { width: 100%; display: flex; - align-items: flex-end; justify-content: flex-end; } +.bn-mantine .bn-badge-group { + display: flex; + gap: 4px; + justify-content: flex-start; + width: 100%; +} + +.bn-mantine .bn-badge { + flex-grow: 0; +} + +.bn-mantine .bn-badge .mantine-Chip-label { + + padding: 0 8px; +} + +.bn-mantine .bn-badge .mantine-Chip-label:not([data-checked="true"]) { + background-color: var(--bn-colors-menu-background); + border: var(--bn-border); + color: var(--bn-colors-menu-text); +} + +.bn-mantine .bn-badge .mantine-Chip-label > span:not(.mantine-Chip-iconWrapper) { + display: inline-flex; + gap: 4px; +} + +.bn-mantine .bn-badge .mantine-Chip-label > span:not(.mantine-Chip-iconWrapper) > span { + align-items: center; + display: inline-flex; + justify-content: center; +} + +.bn-mantine .bn-badge .mantine-Chip-iconWrapper { + display: none; +} + .bn-mantine .bn-action-toolbar { + align-self: flex-end; background-color: var(--bn-colors-menu-background); border: var(--bn-border); border-radius: var(--bn-border-radius-medium); diff --git a/packages/mantine/src/toolbar/ToolbarButton.tsx b/packages/mantine/src/toolbar/ToolbarButton.tsx index 4f2fd9e5fc..29dea8ee48 100644 --- a/packages/mantine/src/toolbar/ToolbarButton.tsx +++ b/packages/mantine/src/toolbar/ToolbarButton.tsx @@ -15,9 +15,13 @@ export const TooltipContent = (props: { secondaryTooltip?: string; }) => ( - {props.mainTooltip} + + {props.mainTooltip} + {props.secondaryTooltip && ( - {props.secondaryTooltip} + + {props.secondaryTooltip} + )} ); diff --git a/packages/react/package.json b/packages/react/package.json index d7be35c21c..0fb8a7eaba 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -53,6 +53,8 @@ }, "dependencies": { "@blocknote/core": "^0.23.1", + "@emoji-mart/data": "^1.2.1", + "@emoji-mart/react": "^1.1.1", "@floating-ui/react": "^0.26.4", "@tiptap/core": "^2.7.1", "@tiptap/react": "^2.7.1", @@ -60,6 +62,7 @@ "react-icons": "^5.2.1" }, "devDependencies": { + "@types/emoji-mart": "^3.0.14", "@types/lodash.foreach": "^4.5.9", "@types/lodash.groupby": "^4.6.9", "@types/lodash.merge": "^4.6.9", diff --git a/packages/react/src/components/Comments/Comment.tsx b/packages/react/src/components/Comments/Comment.tsx index 4dda62d608..0b2cb1c863 100644 --- a/packages/react/src/components/Comments/Comment.tsx +++ b/packages/react/src/components/Comments/Comment.tsx @@ -1,12 +1,9 @@ "use client"; -import { - CommentData, - ThreadData, - mergeCSSClasses, - getDefaultEmojiPickerItems, - DefaultGridSuggestionItem, -} from "@blocknote/core"; +import { CommentData, ThreadData, mergeCSSClasses } from "@blocknote/core"; +import data from "@emoji-mart/data"; +import Picker from "@emoji-mart/react"; +import type { EmojiData } from "emoji-mart"; import { ComponentPropsWithoutRef, MouseEvent, @@ -14,7 +11,6 @@ import { useCallback, useEffect, useState, - useRef, } from "react"; import { RiArrowGoBackFill, @@ -25,11 +21,11 @@ import { RiMoreFill, } from "react-icons/ri"; +import { useBlockNoteContext } from "../../editor/BlockNoteContext.js"; import { useComponentsContext } from "../../editor/ComponentsContext.js"; import { useBlockNoteEditor } from "../../hooks/useBlockNoteEditor.js"; import { useCreateBlockNote } from "../../hooks/useCreateBlockNote.js"; import { useDictionary } from "../../i18n/dictionary.js"; -import { GridSuggestionMenu } from "../SuggestionMenu/GridSuggestionMenu/GridSuggestionMenu.js"; import { CommentEditor } from "./CommentEditor.js"; import { schema } from "./schema.js"; import { useUser } from "./useUsers.js"; @@ -325,15 +321,7 @@ export const Comment = ({ const user = useUser(editor, comment.userId); - // TODO: Change emoji picker implementation to premade component? - const emojis = useRef(undefined); - useEffect(() => { - const getEmojis = async () => { - emojis.current = await getDefaultEmojiPickerItems(editor, ""); - }; - - getEmojis(); - }, [editor]); + const blockNoteContext = useBlockNoteContext(); if (!showDeleted && !comment.body) { return null; @@ -365,18 +353,12 @@ export const Comment = ({ - {/* TODO: Change emoji picker implementation to premade component? */} - ({ - ...item, - icon: <>{item.id}, - })) || [] + + onReactionSelect(emoji.native) } - loadingState={"loaded"} - selectedIndex={-1} - columns={6} - onItemClick={(item) => onReactionSelect(item.id)} + theme={blockNoteContext?.colorSchemePreference} /> @@ -443,73 +425,86 @@ export const Comment = ({ timeString={timeString} showActions={showActions} actions={actions}> - {isEditing ? ( + {comment.body ? ( <> ( - - - Save - - - Cancel - - - )} - /> - - ) : comment.body ? ( - <> - 0 - ? () => ( - - {comment.reactions.map((reaction) => ( + <> + {showReactions && comment.reactions.length > 0 && ( + + {comment.reactions.map((reaction) => ( + onReactionSelect(reaction.emoji)} + mainTooltip={"Reacted by"} + secondaryTooltip={`${reaction.usersIds.map( + (userId) => userId + "\n" + )}`} + /> + ))} + + } + /> + + + + onReactionSelect(emoji.native) } - onClick={() => - onReactionSelect(reaction.emoji) - }> - ))} - - ) - : undefined - } + theme={blockNoteContext?.colorSchemePreference} + /> + + + + )} + {isEditing && ( + + + Save + + + Cancel + + + )} + + )} /> - - {showReactions && comment.reactions.length > 0 && ( -
- )} ) : ( // Soft deletes diff --git a/packages/react/src/editor/ComponentsContext.tsx b/packages/react/src/editor/ComponentsContext.tsx index f8b5842c2b..6d5194ab23 100644 --- a/packages/react/src/editor/ComponentsContext.tsx +++ b/packages/react/src/editor/ComponentsContext.tsx @@ -190,6 +190,8 @@ export type ComponentProps = { text: string; icon?: ReactNode; isSelected?: boolean; + mainTooltip?: string; + secondaryTooltip?: string; onClick?: () => void; }; Group: { From b42047f9034eaa9ecff0434c61eba4f9edc9b7c8 Mon Sep 17 00:00:00 2001 From: yousefed Date: Mon, 10 Feb 2025 13:07:56 +0100 Subject: [PATCH 023/144] change reaction implementation --- package-lock.json | 1 + .../threadstore/DefaultThreadStoreAuth.ts | 11 +- .../Comments/threadstore/ThreadStoreAuth.ts | 1 + .../Comments/threadstore/YjsThreadStore.ts | 134 +++++++++--------- .../core/src/extensions/Comments/types.ts | 2 +- .../react/src/components/Comments/Comment.tsx | 6 +- .../react/src/components/Comments/Thread.tsx | 2 +- 7 files changed, 79 insertions(+), 78 deletions(-) diff --git a/package-lock.json b/package-lock.json index 566a3b1140..ee50902e86 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31925,6 +31925,7 @@ "react-icons": "^5.2.1" }, "devDependencies": { + "@types/emoji-mart": "^3.0.14", "@types/lodash.foreach": "^4.5.9", "@types/lodash.groupby": "^4.6.9", "@types/lodash.merge": "^4.6.9", diff --git a/packages/core/src/extensions/Comments/threadstore/DefaultThreadStoreAuth.ts b/packages/core/src/extensions/Comments/threadstore/DefaultThreadStoreAuth.ts index 4faf240116..7fd414bbce 100644 --- a/packages/core/src/extensions/Comments/threadstore/DefaultThreadStoreAuth.ts +++ b/packages/core/src/extensions/Comments/threadstore/DefaultThreadStoreAuth.ts @@ -33,6 +33,13 @@ export class DefaultThreadStoreAuth extends ThreadStoreAuth { return true; } + /** + * Auth: should be possible by anyone with comment access + */ + canLeaveReaction(_comment: CommentData): boolean { + return true; + } + /** * Auth: should only be possible by the comment author */ @@ -67,8 +74,4 @@ export class DefaultThreadStoreAuth extends ThreadStoreAuth { canUnresolveThread(_thread: ThreadData): boolean { return true; } - - // TODO: reactions - // abstract canAddReaction(comment: CommentData): boolean; - // abstract canDeleteReaction(comment: CommentData): boolean; } diff --git a/packages/core/src/extensions/Comments/threadstore/ThreadStoreAuth.ts b/packages/core/src/extensions/Comments/threadstore/ThreadStoreAuth.ts index 57031c015c..f0686b47ee 100644 --- a/packages/core/src/extensions/Comments/threadstore/ThreadStoreAuth.ts +++ b/packages/core/src/extensions/Comments/threadstore/ThreadStoreAuth.ts @@ -8,6 +8,7 @@ export abstract class ThreadStoreAuth { abstract canDeleteThread(thread: ThreadData): boolean; abstract canResolveThread(thread: ThreadData): boolean; abstract canUnresolveThread(thread: ThreadData): boolean; + abstract canLeaveReaction(comment: CommentData): boolean; // TODO: reactions // abstract canAddReaction(comment: CommentData): boolean; // abstract canDeleteReaction(comment: CommentData): boolean; diff --git a/packages/core/src/extensions/Comments/threadstore/YjsThreadStore.ts b/packages/core/src/extensions/Comments/threadstore/YjsThreadStore.ts index aea14335f3..bd2aa5e8df 100644 --- a/packages/core/src/extensions/Comments/threadstore/YjsThreadStore.ts +++ b/packages/core/src/extensions/Comments/threadstore/YjsThreadStore.ts @@ -240,7 +240,7 @@ export class YjsThreadStore extends ThreadStore { }); public toggleReaction = this.transact( - (options: { threadId: string; commentId: string; reaction: string }) => { + (options: { threadId: string; commentId: string; emoji: string }) => { const yThread = this.threadsYMap.get(options.threadId); if (!yThread) { throw new Error("Thread not found"); @@ -250,64 +250,33 @@ export class YjsThreadStore extends ThreadStore { yThread.get("comments"), (comment) => comment.get("id") === options.commentId ); + if (yCommentIndex === -1) { throw new Error("Comment not found"); } const yComment = yThread.get("comments").get(yCommentIndex); - if (!this.auth.canUpdateComment(yMapToComment(yComment))) { + + if (!this.auth.canLeaveReaction(yMapToComment(yComment))) { throw new Error("Not authorized"); } - const yReactionIndex = yArrayFindIndex( - yComment.get("reactions"), - (reaction) => reaction.get("emoji") === options.reaction - ); - - if (yReactionIndex !== -1) { - const yReaction = yComment.get("reactions").get(yReactionIndex); - - const yUserIdIndex = yArrayFindIndex( - yReaction.get("usersIds"), - (userId) => userId === this.userId - ); - if (yUserIdIndex !== -1) { - // This user already reacted with this emoji, so it should be toggled - // off. - - // Reaction exists and contains user ID, so the user ID should be - // removed from the list. If the list is now empty, remove the reaction - // altogether. - yReaction.get("usersIds").delete(yUserIdIndex); - - if (yReaction.get("usersIds").length === 0) { - yComment.get("reactions").delete(yReactionIndex); - yComment.set("updatedAt", new Date().getTime()); - } + const date = new Date(); - return; - } - // Other users have reacted with this emoji, but this user has not, so - // it should be toggled on. + const key = `${this.userId}-${options.emoji}`; - // Reaction exists but does not contain user ID, so the user ID should - // be added to the list. - yReaction.get("usersIds").push([this.userId]); + const reactionsByUser = yComment.get("reactionsByUser"); - return; + if (reactionsByUser.has(key)) { + reactionsByUser.delete(key); + } else { + const reaction = new Y.Map(); + reaction.set("emoji", options.emoji); + reaction.set("createdAt", date.getTime()); + reaction.set("userId", this.userId); + reactionsByUser.set(key, reaction); } - // No one has reacted with this emoji, so it should again be toggled on. - // Reaction does not exist, and so a new one should be created and this - // user should be added to the list. - const date = new Date(); - const reaction: CommentReactionData = { - emoji: options.reaction, - createdAt: date, - usersIds: [this.userId], - }; - - yComment.get("reactions").push([reactionToYMap(reaction)]); yComment.set("updatedAt", date.getTime()); } ); @@ -345,22 +314,6 @@ export class YjsThreadStore extends ThreadStore { // HELPERS -function reactionToYMap(reaction: CommentReactionData) { - const yMap = new Y.Map(); - yMap.set("emoji", reaction.emoji); - yMap.set("createdAt", reaction.createdAt.getTime()); - if (reaction.usersIds.length === 0) { - throw new Error("Need at least one user ID in reactionToYMap"); - } - const usersIdsArray = new Y.Array(); - - usersIdsArray.push([...reaction.usersIds]); - - yMap.set("usersIds", usersIdsArray); - - return yMap; -} - function commentToYMap(comment: CommentData) { const yMap = new Y.Map(); yMap.set("id", comment.id); @@ -376,7 +329,17 @@ function commentToYMap(comment: CommentData) { if (comment.reactions.length > 0) { throw new Error("Reactions should be empty in commentToYMap"); } - yMap.set("reactions", new Y.Array()); + + /** + * Reactions are stored in a map keyed by {userId-emoji}, + * this makes it easy to add / remove reactions and in a way that works local-first. + * The cost is that "reading" the reactions is a bit more complex (see yMapToReactions). + * + * Alternative could be to store it in the same format as CommentReactionData, + * but in that case we should not never delete an emoji entry, because when a user "thinks" he has deleted + * the last reaction with a specific emoji, perhaps another user (offline) has reacted with that emoji. + */ + yMap.set("reactionsByUser", new Y.Map()); yMap.set("metadata", comment.metadata); return yMap; @@ -398,14 +361,49 @@ function threadToYMap(thread: ThreadData) { return yMap; } -function yMapToReaction(yMap: Y.Map): CommentReactionData { +type SingleUserCommentReactionData = { + emoji: string; + createdAt: Date; + userId: string; +}; + +function yMapToReaction(yMap: Y.Map): SingleUserCommentReactionData { return { emoji: yMap.get("emoji"), - createdAt: yMap.get("createdAt"), - usersIds: yMap.get("usersIds").toArray(), + createdAt: new Date(yMap.get("createdAt")), + userId: yMap.get("userId"), }; } +function yMapToReactions(yMap: Y.Map): CommentReactionData[] { + const flatReactions = [...yMap.values()].map((reaction: Y.Map) => + yMapToReaction(reaction) + ); + // combine reactions by the same emoji + return flatReactions.reduce( + (acc: CommentReactionData[], reaction: SingleUserCommentReactionData) => { + const existingReaction = acc.find((r) => r.emoji === reaction.emoji); + if (existingReaction) { + existingReaction.userIds.push(reaction.userId); + existingReaction.createdAt = new Date( + Math.min( + existingReaction.createdAt.getTime(), + reaction.createdAt.getTime() + ) + ); + } else { + acc.push({ + emoji: reaction.emoji, + createdAt: reaction.createdAt, + userIds: [reaction.userId], + }); + } + return acc; + }, + [] as CommentReactionData[] + ); +} + function yMapToComment(yMap: Y.Map): CommentData { return { type: "comment", @@ -416,9 +414,7 @@ function yMapToComment(yMap: Y.Map): CommentData { deletedAt: yMap.get("deletedAt") ? new Date(yMap.get("deletedAt")) : undefined, - reactions: yMap - .get("reactions") - .map((reaction: Y.Map) => yMapToReaction(reaction)), + reactions: yMapToReactions(yMap.get("reactionsByUser")), metadata: yMap.get("metadata"), body: yMap.get("body"), }; diff --git a/packages/core/src/extensions/Comments/types.ts b/packages/core/src/extensions/Comments/types.ts index c50f769ec3..75327f9ac0 100644 --- a/packages/core/src/extensions/Comments/types.ts +++ b/packages/core/src/extensions/Comments/types.ts @@ -3,7 +3,7 @@ export type CommentBody = any; export type CommentReactionData = { emoji: string; createdAt: Date; - usersIds: string[]; + userIds: string[]; }; export type CommentData = { diff --git a/packages/react/src/components/Comments/Comment.tsx b/packages/react/src/components/Comments/Comment.tsx index 0b2cb1c863..f1c0e073e7 100644 --- a/packages/react/src/components/Comments/Comment.tsx +++ b/packages/react/src/components/Comments/Comment.tsx @@ -445,12 +445,12 @@ export const Comment = ({ "bn-badge", "bn-comment-reaction" )} - text={reaction.usersIds.length.toString()} + text={reaction.userIds.length.toString()} icon={reaction.emoji} - isSelected={user && reaction.usersIds.includes(user.id)} + isSelected={user && reaction.userIds.includes(user.id)} onClick={() => onReactionSelect(reaction.emoji)} mainTooltip={"Reacted by"} - secondaryTooltip={`${reaction.usersIds.map( + secondaryTooltip={`${reaction.userIds.map( (userId) => userId + "\n" )}`} /> diff --git a/packages/react/src/components/Comments/Thread.tsx b/packages/react/src/components/Comments/Thread.tsx index 0e41c6b59c..1d32baf0cc 100644 --- a/packages/react/src/components/Comments/Thread.tsx +++ b/packages/react/src/components/Comments/Thread.tsx @@ -70,7 +70,7 @@ export const Thread = ({ const userIds = useMemo(() => { return thread.comments.flatMap((c) => [ c.userId, - ...c.reactions.flatMap((r) => r.usersIds), + ...c.reactions.flatMap((r) => r.userIds), ]); }, [thread.comments]); From 43a1eb083603451764a2516740564733412b3ef3 Mon Sep 17 00:00:00 2001 From: yousefed Date: Mon, 10 Feb 2025 13:24:24 +0100 Subject: [PATCH 024/144] reactions improvements --- .../threadstore/DefaultThreadStoreAuth.ts | 21 ++++++--- .../Comments/threadstore/ThreadStore.ts | 4 +- .../Comments/threadstore/ThreadStoreAuth.ts | 6 +-- .../threadstore/YjsThreadStore.test.ts | 47 ++++++++++++------- .../Comments/threadstore/YjsThreadStore.ts | 44 ++++++++++++++--- 5 files changed, 86 insertions(+), 36 deletions(-) diff --git a/packages/core/src/extensions/Comments/threadstore/DefaultThreadStoreAuth.ts b/packages/core/src/extensions/Comments/threadstore/DefaultThreadStoreAuth.ts index 7fd414bbce..3069f57564 100644 --- a/packages/core/src/extensions/Comments/threadstore/DefaultThreadStoreAuth.ts +++ b/packages/core/src/extensions/Comments/threadstore/DefaultThreadStoreAuth.ts @@ -33,13 +33,6 @@ export class DefaultThreadStoreAuth extends ThreadStoreAuth { return true; } - /** - * Auth: should be possible by anyone with comment access - */ - canLeaveReaction(_comment: CommentData): boolean { - return true; - } - /** * Auth: should only be possible by the comment author */ @@ -74,4 +67,18 @@ export class DefaultThreadStoreAuth extends ThreadStoreAuth { canUnresolveThread(_thread: ThreadData): boolean { return true; } + + /** + * Auth: should be possible by anyone with comment access + */ + canAddReaction(_comment: CommentData): boolean { + return true; + } + + /** + * Auth: should be possible by anyone with comment access + */ + canDeleteReaction(_comment: CommentData): boolean { + return true; + } } diff --git a/packages/core/src/extensions/Comments/threadstore/ThreadStore.ts b/packages/core/src/extensions/Comments/threadstore/ThreadStore.ts index 01c2b4c75d..e96f96c7e0 100644 --- a/packages/core/src/extensions/Comments/threadstore/ThreadStore.ts +++ b/packages/core/src/extensions/Comments/threadstore/ThreadStore.ts @@ -77,7 +77,7 @@ export abstract class ThreadStore { abstract addReaction(options: { threadId: string; commentId: string; - reaction: string; + emoji: string; }): Promise; /** @@ -88,7 +88,7 @@ export abstract class ThreadStore { abstract deleteReaction(options: { threadId: string; commentId: string; - reactionId: string; + emoji: string; }): Promise; abstract getThread(threadId: string): ThreadData; diff --git a/packages/core/src/extensions/Comments/threadstore/ThreadStoreAuth.ts b/packages/core/src/extensions/Comments/threadstore/ThreadStoreAuth.ts index f0686b47ee..933c4b76a3 100644 --- a/packages/core/src/extensions/Comments/threadstore/ThreadStoreAuth.ts +++ b/packages/core/src/extensions/Comments/threadstore/ThreadStoreAuth.ts @@ -8,8 +8,6 @@ export abstract class ThreadStoreAuth { abstract canDeleteThread(thread: ThreadData): boolean; abstract canResolveThread(thread: ThreadData): boolean; abstract canUnresolveThread(thread: ThreadData): boolean; - abstract canLeaveReaction(comment: CommentData): boolean; - // TODO: reactions - // abstract canAddReaction(comment: CommentData): boolean; - // abstract canDeleteReaction(comment: CommentData): boolean; + abstract canAddReaction(comment: CommentData): boolean; + abstract canDeleteReaction(comment: CommentData): boolean; } diff --git a/packages/core/src/extensions/Comments/threadstore/YjsThreadStore.test.ts b/packages/core/src/extensions/Comments/threadstore/YjsThreadStore.test.ts index d3f939e57d..fd7aafe120 100644 --- a/packages/core/src/extensions/Comments/threadstore/YjsThreadStore.test.ts +++ b/packages/core/src/extensions/Comments/threadstore/YjsThreadStore.test.ts @@ -2,6 +2,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; import * as Y from "yjs"; import { BlockNoteEditor } from "../../../editor/BlockNoteEditor.js"; import { CommentBody } from "../types.js"; +import { DefaultThreadStoreAuth } from "./DefaultThreadStoreAuth.js"; import { YjsThreadStore } from "./YjsThreadStore.js"; // Mock UUID to generate sequential IDs @@ -23,7 +24,12 @@ describe("YjsThreadStore", () => { doc = new Y.Doc(); threadsYMap = doc.getMap("threads"); editor = {} as BlockNoteEditor; - store = new YjsThreadStore(editor, "test-user", threadsYMap); + store = new YjsThreadStore( + editor, + "test-user", + threadsYMap, + new DefaultThreadStoreAuth("test-user", "editor") + ); }); describe("createThread", () => { @@ -231,35 +237,44 @@ describe("YjsThreadStore", () => { }); describe("reactions", () => { - it("throws not implemented error when adding reaction", async () => { + it("adds a reaction to a comment", async () => { const thread = await store.createThread({ initialComment: { body: "Test comment" as CommentBody, }, }); - await expect( - store.addReaction({ - threadId: thread.id, - commentId: thread.comments[0].id, - }) - ).rejects.toThrow("Not implemented"); + await store.addReaction({ + threadId: thread.id, + commentId: thread.comments[0].id, + emoji: "👍", + }); + + expect(store.getThread(thread.id).comments[0].reactions).toHaveLength(1); }); - it("throws not implemented error when deleting reaction", async () => { + it("deletes a reaction from a comment", async () => { const thread = await store.createThread({ initialComment: { body: "Test comment" as CommentBody, }, }); - await expect( - store.deleteReaction({ - threadId: thread.id, - commentId: thread.comments[0].id, - reactionId: "some-reaction", - }) - ).rejects.toThrow("Not implemented"); + await store.addReaction({ + threadId: thread.id, + commentId: thread.comments[0].id, + emoji: "👍", + }); + + expect(store.getThread(thread.id).comments[0].reactions).toHaveLength(1); + + await store.deleteReaction({ + threadId: thread.id, + commentId: thread.comments[0].id, + emoji: "👍", + }); + + expect(store.getThread(thread.id).comments[0].reactions).toHaveLength(0); }); }); diff --git a/packages/core/src/extensions/Comments/threadstore/YjsThreadStore.ts b/packages/core/src/extensions/Comments/threadstore/YjsThreadStore.ts index bd2aa5e8df..3fba121971 100644 --- a/packages/core/src/extensions/Comments/threadstore/YjsThreadStore.ts +++ b/packages/core/src/extensions/Comments/threadstore/YjsThreadStore.ts @@ -239,7 +239,7 @@ export class YjsThreadStore extends ThreadStore { yThread.set("resolvedUpdatedAt", new Date().getTime()); }); - public toggleReaction = this.transact( + public addReaction = this.transact( (options: { threadId: string; commentId: string; emoji: string }) => { const yThread = this.threadsYMap.get(options.threadId); if (!yThread) { @@ -257,7 +257,7 @@ export class YjsThreadStore extends ThreadStore { const yComment = yThread.get("comments").get(yCommentIndex); - if (!this.auth.canLeaveReaction(yMapToComment(yComment))) { + if (!this.auth.canAddReaction(yMapToComment(yComment))) { throw new Error("Not authorized"); } @@ -268,7 +268,8 @@ export class YjsThreadStore extends ThreadStore { const reactionsByUser = yComment.get("reactionsByUser"); if (reactionsByUser.has(key)) { - reactionsByUser.delete(key); + // already exists + return; } else { const reaction = new Y.Map(); reaction.set("emoji", options.emoji); @@ -281,6 +282,39 @@ export class YjsThreadStore extends ThreadStore { } ); + public deleteReaction = this.transact( + (options: { threadId: string; commentId: string; emoji: string }) => { + const yThread = this.threadsYMap.get(options.threadId); + if (!yThread) { + throw new Error("Thread not found"); + } + + const yCommentIndex = yArrayFindIndex( + yThread.get("comments"), + (comment) => comment.get("id") === options.commentId + ); + + if (yCommentIndex === -1) { + throw new Error("Comment not found"); + } + + const yComment = yThread.get("comments").get(yCommentIndex); + + if (!this.auth.canDeleteReaction(yMapToComment(yComment))) { + throw new Error("Not authorized"); + } + + const date = new Date(); + + const key = `${this.userId}-${options.emoji}`; + + const reactionsByUser = yComment.get("reactionsByUser"); + + reactionsByUser.delete(key); + + yComment.set("updatedAt", date.getTime()); + } + ); // TODO: async / reactive interface? public getThread(threadId: string) { const yThread = this.threadsYMap.get(threadId); @@ -334,10 +368,6 @@ function commentToYMap(comment: CommentData) { * Reactions are stored in a map keyed by {userId-emoji}, * this makes it easy to add / remove reactions and in a way that works local-first. * The cost is that "reading" the reactions is a bit more complex (see yMapToReactions). - * - * Alternative could be to store it in the same format as CommentReactionData, - * but in that case we should not never delete an emoji entry, because when a user "thinks" he has deleted - * the last reaction with a specific emoji, perhaps another user (offline) has reacted with that emoji. */ yMap.set("reactionsByUser", new Y.Map()); yMap.set("metadata", comment.metadata); From 306c3354235c18c929e83c527f2c2165bd61a862 Mon Sep 17 00:00:00 2001 From: yousefed Date: Mon, 10 Feb 2025 17:49:21 +0100 Subject: [PATCH 025/144] small cleanup --- packages/core/src/extensions/Comments/CommentMark.ts | 5 ++++- packages/react/src/components/Comments/Comment.tsx | 4 ++-- packages/react/src/editor/ElementRenderer.tsx | 1 - packages/react/src/schema/@util/ReactMarkViewRenderer.tsx | 1 - packages/react/src/schema/ReactStyleSpec.tsx | 6 +++--- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/packages/core/src/extensions/Comments/CommentMark.ts b/packages/core/src/extensions/Comments/CommentMark.ts index 3b4f2e8fee..719f34e420 100644 --- a/packages/core/src/extensions/Comments/CommentMark.ts +++ b/packages/core/src/extensions/Comments/CommentMark.ts @@ -10,7 +10,10 @@ export const CommentMark = Mark.create({ addAttributes() { // Return an object with attribute configuration return { - // TODO: check if needed + // orphans are marks that currently don't have an active thread. It could be + // that users have resolved the thread. Resolved threads by default are not shown in the document, + // but we need to keep the mark (positioning) data so we can still "revive" it when the thread is unresolved + // or we enter a "comments" view that includes resolved threads. orphan: { parseHTML: (element) => !!element.getAttribute("data-orphan"), renderHTML: (attributes) => { diff --git a/packages/react/src/components/Comments/Comment.tsx b/packages/react/src/components/Comments/Comment.tsx index f1c0e073e7..63e55a040e 100644 --- a/packages/react/src/components/Comments/Comment.tsx +++ b/packages/react/src/components/Comments/Comment.tsx @@ -284,10 +284,10 @@ export const Comment = ({ const onReactionSelect = useCallback( (emoji: string) => { - editor.comments?.store.toggleReaction({ + editor.comments?.store.addReaction({ threadId: thread.id, commentId: comment.id, - reaction: emoji, + emoji, }); }, [comment.id, editor.comments?.store, thread.id] diff --git a/packages/react/src/editor/ElementRenderer.tsx b/packages/react/src/editor/ElementRenderer.tsx index 73dee1d3d7..aedcc55ed5 100644 --- a/packages/react/src/editor/ElementRenderer.tsx +++ b/packages/react/src/editor/ElementRenderer.tsx @@ -17,7 +17,6 @@ export const ElementRenderer = forwardRef< ref, () => { return (node: React.ReactNode, container: HTMLElement) => { - debugger; flushSync(() => { setSingleRenderData({ node, container }); }); diff --git a/packages/react/src/schema/@util/ReactMarkViewRenderer.tsx b/packages/react/src/schema/@util/ReactMarkViewRenderer.tsx index 3113cbe76c..57d7d4d3c0 100644 --- a/packages/react/src/schema/@util/ReactMarkViewRenderer.tsx +++ b/packages/react/src/schema/@util/ReactMarkViewRenderer.tsx @@ -58,7 +58,6 @@ export class ReactMarkView extends CoreMarkView { if (this.mark.attrs.stringValue) { props.value = this.mark.attrs.stringValue; } - debugger; return { reactElement: ( diff --git a/packages/react/src/schema/ReactStyleSpec.tsx b/packages/react/src/schema/ReactStyleSpec.tsx index c179ce0219..90321c1dab 100644 --- a/packages/react/src/schema/ReactStyleSpec.tsx +++ b/packages/react/src/schema/ReactStyleSpec.tsx @@ -59,10 +59,10 @@ export function createReactStyleSpec( }, }); - let x = mark; - (mark as any).config.addMarkView = (mark: any, view: any) => { + const markType = mark; + (markType as any).config.addMarkView = (mark: any, view: any) => { const markView = new ReactMarkView({ - editor: x.child?.options.editor, + editor: markType.child?.options.editor, inline: true, mark, options: { From 99d9d214fcc45e055afaf405502ede32589a197b Mon Sep 17 00:00:00 2001 From: yousefed Date: Mon, 10 Feb 2025 21:25:50 +0100 Subject: [PATCH 026/144] cleanups + mark some todos --- .../Comments/threadstore/YjsThreadStore.ts | 6 - .../react/src/components/Comments/Comment.tsx | 232 +++--------------- .../src/components/Comments/CommentEditor.tsx | 11 +- .../components/Comments/FloatingComposer.tsx | 2 + .../react/src/components/Comments/Thread.tsx | 23 +- .../{useThreadStore.ts => useThreads.ts} | 2 +- 6 files changed, 64 insertions(+), 212 deletions(-) rename packages/react/src/components/Comments/{useThreadStore.ts => useThreads.ts} (91%) diff --git a/packages/core/src/extensions/Comments/threadstore/YjsThreadStore.ts b/packages/core/src/extensions/Comments/threadstore/YjsThreadStore.ts index 3fba121971..cf0d5127f4 100644 --- a/packages/core/src/extensions/Comments/threadstore/YjsThreadStore.ts +++ b/packages/core/src/extensions/Comments/threadstore/YjsThreadStore.ts @@ -277,8 +277,6 @@ export class YjsThreadStore extends ThreadStore { reaction.set("userId", this.userId); reactionsByUser.set(key, reaction); } - - yComment.set("updatedAt", date.getTime()); } ); @@ -304,15 +302,11 @@ export class YjsThreadStore extends ThreadStore { throw new Error("Not authorized"); } - const date = new Date(); - const key = `${this.userId}-${options.emoji}`; const reactionsByUser = yComment.get("reactionsByUser"); reactionsByUser.delete(key); - - yComment.set("updatedAt", date.getTime()); } ); // TODO: async / reactive interface? diff --git a/packages/react/src/components/Comments/Comment.tsx b/packages/react/src/components/Comments/Comment.tsx index 63e55a040e..d3f66ec113 100644 --- a/packages/react/src/components/Comments/Comment.tsx +++ b/packages/react/src/components/Comments/Comment.tsx @@ -9,7 +9,6 @@ import { MouseEvent, ReactNode, useCallback, - useEffect, useState, } from "react"; import { @@ -30,14 +29,6 @@ import { CommentEditor } from "./CommentEditor.js"; import { schema } from "./schema.js"; import { useUser } from "./useUsers.js"; -/** - * Liveblocks, but changed: - * - removed attachments - * - removed read status - * ... - */ -// const REACTIONS_TRUNCATE = 5; - export interface CommentProps extends ComponentPropsWithoutRef<"div"> { /** * The comment to display. @@ -45,7 +36,7 @@ export interface CommentProps extends ComponentPropsWithoutRef<"div"> { comment: CommentData; /** - * The thread id. + * The thread the comment belongs to. */ thread: ThreadData; @@ -68,146 +59,13 @@ export interface CommentProps extends ComponentPropsWithoutRef<"div"> { * Whether to show reactions. */ showReactions?: boolean; - - /** - * @internal - */ - additionalActions?: ReactNode; } -// interface CommentReactionButtonProps -// extends ComponentPropsWithoutRef { -// reaction: CommentReactionData; -// // overrides?: Partial; -// } - -// interface CommentReactionProps extends ComponentPropsWithoutRef<"button"> { -// comment: CommentData; -// reaction: CommentReactionData; -// // overrides?: Partial; -// } - -// type CommentNonInteractiveReactionProps = Omit; - -// const CommentReactionButton = forwardRef< -// HTMLButtonElement, -// CommentReactionButtonProps -// >(({ reaction, overrides, className, ...props }, forwardedRef) => { -// const $ = useOverrides(overrides); -// return ( -// -// ); -// }); - -// export const CommentReaction = forwardRef< -// HTMLButtonElement, -// CommentReactionProps -// >(({ comment, reaction, overrides, disabled, ...props }, forwardedRef) => { -// const addReaction = useAddRoomCommentReaction(comment.roomId); -// const removeReaction = useRemoveRoomCommentReaction(comment.roomId); -// const currentId = useCurrentUserId(); -// const isActive = useMemo(() => { -// return reaction.users.some((users) => users.id === currentId); -// }, [currentId, reaction]); -// const $ = useOverrides(overrides); -// const tooltipContent = useMemo( -// () => ( -// -// {$.COMMENT_REACTION_LIST( -// ( -// -// ))} -// formatRemaining={$.LIST_REMAINING_USERS} -// truncate={REACTIONS_TRUNCATE} -// locale={$.locale} -// />, -// reaction.emoji, -// reaction.users.length -// )} -// -// ), -// [$, reaction] -// ); - -// const stopPropagation = useCallback((event: SyntheticEvent) => { -// event.stopPropagation(); -// }, []); - -// const handlePressedChange = useCallback( -// (isPressed: boolean) => { -// if (isPressed) { -// addReaction({ -// threadId: comment.threadId, -// commentId: comment.id, -// emoji: reaction.emoji, -// }); -// } else { -// removeReaction({ -// threadId: comment.threadId, -// commentId: comment.id, -// emoji: reaction.emoji, -// }); -// } -// }, -// [addReaction, comment.threadId, comment.id, reaction.emoji, removeReaction] -// ); - -// return ( -// -// -// -// -// -// ); -// }); - -// export const CommentNonInteractiveReaction = forwardRef< -// HTMLButtonElement, -// CommentNonInteractiveReactionProps -// >(({ reaction, overrides, ...props }, forwardedRef) => { -// const currentId = useCurrentUserId(); -// const isActive = useMemo(() => { -// return reaction.users.some((users) => users.id === currentId); -// }, [currentId, reaction]); - -// return ( -// -// ); -// }); - +/** + * The Comment component displays a single comment with actions, + * a reaction list and an editor when editing. + * + */ export const Comment = ({ comment, thread, @@ -216,10 +74,10 @@ export const Comment = ({ showReactions = true, showResolveAction = false, className, - additionalActions, }: CommentProps) => { const dict = useDictionary(); + // TODO: review use of sub-editor const commentEditor = useCreateBlockNote( { initialContent: comment.body, @@ -238,19 +96,12 @@ export const Comment = ({ const Components = useComponentsContext()!; - // const currentUserId = useCurrentUserId(); - // const deleteComment = useDeleteRoomComment(comment.roomId); - // const editComment = useEditRoomComment(com ment.roomId); - // const addReaction = useAddRoomCommentReaction(comment.roomId); - // const removeReaction = useRemoveRoomCommentReaction(comment.roomId); - // const $ = useOverrides(overrides); const [isEditing, setEditing] = useState(false); - const [isTarget, setTarget] = useState(false); - const [isMoreActionOpen, setMoreActionOpen] = useState(false); - const [isReactionActionOpen, setReactionActionOpen] = useState(false); const editor = useBlockNoteEditor(); + const commentStore = editor.comments!.store; + const handleEdit = useCallback(() => { setEditing(true); }, []); @@ -262,7 +113,8 @@ export const Comment = ({ const onEditSubmit = useCallback( async (_event: MouseEvent) => { - await editor.comments!.store.updateComment({ + // TODO: show error on failure? + await commentStore.updateComment({ commentId: comment.id, comment: { body: commentEditor.document, @@ -272,52 +124,42 @@ export const Comment = ({ setEditing(false); }, - [comment, thread.id, commentEditor, editor.comments] + [comment, thread.id, commentEditor, commentStore] ); - const onDelete = useCallback(() => { - editor.comments!.store.deleteComment({ + const onDelete = useCallback(async () => { + // TODO: show error on failure? + await commentStore.deleteComment({ commentId: comment.id, threadId: thread.id, }); - }, [comment, thread.id, editor.comments]); + }, [comment, thread.id, commentStore]); const onReactionSelect = useCallback( - (emoji: string) => { - editor.comments?.store.addReaction({ + async (emoji: string) => { + // TODO: show error on failure? + await commentStore.addReaction({ threadId: thread.id, commentId: comment.id, emoji, }); }, - [comment.id, editor.comments?.store, thread.id] + [comment.id, commentStore, thread.id] ); - const onResolve = useCallback(() => { - editor.comments!.store.resolveThread({ + const onResolve = useCallback(async () => { + // TODO: show error on failure? + await commentStore.resolveThread({ threadId: thread.id, }); - }, [thread.id, editor.comments]); + }, [thread.id, commentStore]); - const onReopen = useCallback(() => { - editor.comments!.store.unresolveThread({ + const onReopen = useCallback(async () => { + // TODO: show error on failure? + await commentStore.unresolveThread({ threadId: thread.id, }); - }, [thread.id, editor.comments]); - - useEffect(() => { - const isWindowDefined = typeof window !== "undefined"; - if (!isWindowDefined) { - return; - } - - const hash = window.location.hash; - const commentId = hash.slice(1); - - if (commentId === comment.id) { - setTarget(true); - } - }, []); // eslint-disable-line react-hooks/exhaustive-deps + }, [thread.id, commentStore]); const user = useUser(editor, comment.userId); @@ -328,16 +170,15 @@ export const Comment = ({ } let actions: ReactNode | undefined = undefined; - const canAddReaction = true; //editor.comments!.store.auth.canAddReaction(comment); - const canDeleteComment = - editor.comments!.store.auth.canDeleteComment(comment); - const canEditComment = editor.comments!.store.auth.canUpdateComment(comment); + const canAddReaction = commentStore.auth.canAddReaction(comment); + const canDeleteComment = commentStore.auth.canDeleteComment(comment); + const canEditComment = commentStore.auth.canUpdateComment(comment); const showResolveOrReopen = showResolveAction && (thread.resolved - ? editor.comments!.store.auth.canUnresolveThread(thread) - : editor.comments!.store.auth.canResolveThread(thread)); + ? commentStore.auth.canUnresolveThread(thread) + : commentStore.auth.canResolveThread(thread)); if (showActions && !isEditing) { actions = ( @@ -417,19 +258,20 @@ export const Comment = ({ }) + (comment.updatedAt.getTime() !== comment.createdAt.getTime() ? " (edited)" - : ""); // TODO: needs editedAt? + : ""); return ( + actions={actions} + className={className}> {comment.body ? ( <> ( <> {showReactions && comment.reactions.length > 0 && ( diff --git a/packages/react/src/components/Comments/CommentEditor.tsx b/packages/react/src/components/Comments/CommentEditor.tsx index e869cbae41..1ce75da9bc 100644 --- a/packages/react/src/components/Comments/CommentEditor.tsx +++ b/packages/react/src/components/Comments/CommentEditor.tsx @@ -19,9 +19,18 @@ function isDocumentEmpty( ); } +/** + * The CommentEditor component displays an editor for creating or editing a comment. + * Currently, we also use the non-editable version for displaying a comment. + * + * It's used: + * - to create a new comment (FloatingComposer.tsx) + * - As the last item in a Thread, to compose a reply (Thread.tsx) + * - To edit or display an existing comment (Comment.tsx) + * + */ export const CommentEditor = (props: { editable: boolean; - placeholder?: string; actions?: FC<{ isFocused: boolean; isEmpty: boolean; diff --git a/packages/react/src/components/Comments/FloatingComposer.tsx b/packages/react/src/components/Comments/FloatingComposer.tsx index aa449d1d42..f8bcaf1439 100644 --- a/packages/react/src/components/Comments/FloatingComposer.tsx +++ b/packages/react/src/components/Comments/FloatingComposer.tsx @@ -12,6 +12,7 @@ export function FloatingComposer() { const Components = useComponentsContext()!; const dict = useDictionary(); + // TODO: review use of sub-editor const newCommentEditor = useCreateBlockNote({ trailingBlock: false, dictionary: { @@ -42,6 +43,7 @@ export function FloatingComposer() { variant="compact" isDisabled={isEmpty} onClick={async () => { + // TODO: handle errors? await editor.comments!.createThread({ initialComment: { body: newCommentEditor.document, diff --git a/packages/react/src/components/Comments/Thread.tsx b/packages/react/src/components/Comments/Thread.tsx index 1d32baf0cc..931532af11 100644 --- a/packages/react/src/components/Comments/Thread.tsx +++ b/packages/react/src/components/Comments/Thread.tsx @@ -1,5 +1,3 @@ -"use client"; - import { mergeCSSClasses } from "@blocknote/core"; import type { ComponentPropsWithoutRef } from "react"; import { useCallback, useMemo } from "react"; @@ -10,7 +8,7 @@ import { useDictionary } from "../../i18n/dictionary.js"; import { Comment, CommentProps } from "./Comment.js"; import { CommentEditor } from "./CommentEditor.js"; import { schema } from "./schema.js"; -import { useThreadStore } from "./useThreadStore.js"; +import { useThreads } from "./useThreads.js"; import { useUsers } from "./useUsers.js"; export interface ThreadProps extends ComponentPropsWithoutRef<"div"> { @@ -20,9 +18,9 @@ export interface ThreadProps extends ComponentPropsWithoutRef<"div"> { threadId: string; /** - * How to show or hide the composer to reply to the thread. + * Whether to show the composer to reply to the thread. */ - showComposer?: boolean | "collapsed"; + showComposer?: boolean; /** * Whether to show the action to resolve the thread. @@ -45,22 +43,26 @@ export interface ThreadProps extends ComponentPropsWithoutRef<"div"> { showDeletedComments?: CommentProps["showDeleted"]; } +/** + * The Thread component displays a (main) comment with a list of replies (other comments). + * + * It also includes a composer to reply to the thread. + */ export const Thread = ({ threadId, showActions = "hover", showDeletedComments, showResolveAction = true, showReactions = true, + showComposer = true, className, ...props }: ThreadProps) => { - // const markThreadAsResolved = useMarkRoomThreadAsResolved(thread.roomId); - // const markThreadAsUnresolved = useMarkRoomThreadAsUnresolved(thread.roomId); const editor = useBlockNoteEditor(); const Components = useComponentsContext()!; const dict = useDictionary(); - const threadMap = useThreadStore(editor); + const threadMap = useThreads(editor); const thread = threadMap.get(threadId); if (!thread) { @@ -77,6 +79,7 @@ export const Thread = ({ // load all user data useUsers(editor, userIds); + // TODO: review use of sub-editor const newCommentEditor = useCreateBlockNote({ trailingBlock: false, dictionary: { @@ -96,6 +99,7 @@ export const Thread = ({ }, [showDeletedComments, thread.comments]); const onNewCommentSave = useCallback(async () => { + // TODO: show error on failure? await editor.comments!.store.addComment({ comment: { body: newCommentEditor.document, @@ -107,7 +111,8 @@ export const Thread = ({ newCommentEditor.removeBlocks(newCommentEditor.document); }, [editor.comments, newCommentEditor, thread.id]); - const showComposer = editor.comments!.store.auth.canAddComment(thread); + showComposer = + showComposer && editor.comments!.store.auth.canAddComment(thread); return ( ) { +export function useThreads(editor: BlockNoteEditor) { const store = editor.comments!.store; // this ref works around this error: From 4501af401efdea4a1935c0d5a3ab3a118d14d4fc Mon Sep 17 00:00:00 2001 From: yousefed Date: Tue, 11 Feb 2025 10:48:47 +0100 Subject: [PATCH 027/144] comments --- .../components/Comments/FloatingComposer.tsx | 5 +++++ .../Comments/FloatingThreadController.tsx | 21 ++++++++++--------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/packages/react/src/components/Comments/FloatingComposer.tsx b/packages/react/src/components/Comments/FloatingComposer.tsx index f8bcaf1439..a543c95bbc 100644 --- a/packages/react/src/components/Comments/FloatingComposer.tsx +++ b/packages/react/src/components/Comments/FloatingComposer.tsx @@ -7,6 +7,11 @@ import { useDictionary } from "../../i18n/dictionary.js"; import { CommentEditor } from "./CommentEditor.js"; import { schema } from "./schema.js"; +/** + * The FloatingComposer component displays a comment editor "floating" card. + * + * It's used when the user highlights a parts of the document to create a new comment / thread. + */ export function FloatingComposer() { const editor = useBlockNoteEditor(); const Components = useComponentsContext()!; diff --git a/packages/react/src/components/Comments/FloatingThreadController.tsx b/packages/react/src/components/Comments/FloatingThreadController.tsx index 93bdf1ee3e..169ffdfd60 100644 --- a/packages/react/src/components/Comments/FloatingThreadController.tsx +++ b/packages/react/src/components/Comments/FloatingThreadController.tsx @@ -7,7 +7,13 @@ import { StyleSchema, } from "@blocknote/core"; import { UseFloatingOptions, flip, offset } from "@floating-ui/react"; -import { FC, useCallback, useEffect, useLayoutEffect } from "react"; +import { + ComponentProps, + FC, + useCallback, + useEffect, + useLayoutEffect, +} from "react"; import { useBlockNoteEditor } from "../../hooks/useBlockNoteEditor.js"; import { useUIElementPositioning } from "../../hooks/useUIElementPositioning.js"; @@ -15,19 +21,15 @@ import { useUIPluginState } from "../../hooks/useUIPluginState.js"; import { Thread } from "./Thread.js"; /** - * This component is pretty close to the LiveBlocks FloatingThreads one. - * We have a bit of a different approach to communicating data to / from the plugin - */ - -/** - * TODO: docs + * This component is used to display a thread in a floating card. + * It can be used when the user clicks on a thread / comment in the document. */ export const FloatingThreadController = < B extends BlockSchema = DefaultBlockSchema, I extends InlineContentSchema = DefaultInlineContentSchema, S extends StyleSchema = DefaultStyleSchema >(props: { - filePanel?: FC; // TODO + floatingThread?: FC>; floatingOptions?: Partial; }) => { const editor = useBlockNoteEditor(); @@ -91,11 +93,10 @@ export const FloatingThreadController = < return null; // TODO } - const Component = props.filePanel || Thread; + const Component = props.floatingThread || Thread; return (
- {/*
hello
*/}
); From eadb49da4bdd05b53ad7f9bb3f95053d61feda27 Mon Sep 17 00:00:00 2001 From: yousefed Date: Tue, 11 Feb 2025 11:16:14 +0100 Subject: [PATCH 028/144] fix locales --- packages/core/src/i18n/locales/ar.ts | 3 + packages/core/src/i18n/locales/de.ts | 3 + packages/core/src/i18n/locales/es.ts | 3 + packages/core/src/i18n/locales/fr.ts | 3 + packages/core/src/i18n/locales/hr.ts | 3 + packages/core/src/i18n/locales/is.ts | 3 + packages/core/src/i18n/locales/it.ts | 630 ++++++++++++++------------- packages/core/src/i18n/locales/ja.ts | 3 + packages/core/src/i18n/locales/ko.ts | 3 + packages/core/src/i18n/locales/nl.ts | 3 + packages/core/src/i18n/locales/pl.ts | 3 + packages/core/src/i18n/locales/pt.ts | 3 + packages/core/src/i18n/locales/ru.ts | 3 + packages/core/src/i18n/locales/vi.ts | 3 + packages/core/src/i18n/locales/zh.ts | 3 + 15 files changed, 358 insertions(+), 314 deletions(-) diff --git a/packages/core/src/i18n/locales/ar.ts b/packages/core/src/i18n/locales/ar.ts index c4c2ad8a8e..26bd7e41e8 100644 --- a/packages/core/src/i18n/locales/ar.ts +++ b/packages/core/src/i18n/locales/ar.ts @@ -261,6 +261,9 @@ export const ar: Dictionary = { align_justify: { tooltip: "ضبط النص", }, + comment: { + tooltip: "إضافة ملاحظة", + }, }, file_panel: { upload: { diff --git a/packages/core/src/i18n/locales/de.ts b/packages/core/src/i18n/locales/de.ts index f4b9e5f5f0..6d9e6adadc 100644 --- a/packages/core/src/i18n/locales/de.ts +++ b/packages/core/src/i18n/locales/de.ts @@ -273,6 +273,9 @@ export const de = { align_justify: { tooltip: "Text Blocksatz", }, + comment: { + tooltip: "Kommentar hinzufügen", + }, }, file_panel: { upload: { diff --git a/packages/core/src/i18n/locales/es.ts b/packages/core/src/i18n/locales/es.ts index 0a8e11ac9f..2293186610 100644 --- a/packages/core/src/i18n/locales/es.ts +++ b/packages/core/src/i18n/locales/es.ts @@ -272,6 +272,9 @@ export const es = { align_justify: { tooltip: "Justificar texto", }, + comment: { + tooltip: "Añadir comentario", + }, }, file_panel: { upload: { diff --git a/packages/core/src/i18n/locales/fr.ts b/packages/core/src/i18n/locales/fr.ts index 9f694dadb6..2c0f204d30 100644 --- a/packages/core/src/i18n/locales/fr.ts +++ b/packages/core/src/i18n/locales/fr.ts @@ -300,6 +300,9 @@ export const fr: Dictionary = { align_justify: { tooltip: "Justifier le texte", }, + comment: { + tooltip: "Ajouter un commentaire", + }, }, file_panel: { upload: { diff --git a/packages/core/src/i18n/locales/hr.ts b/packages/core/src/i18n/locales/hr.ts index b7604e6ff8..b6a88b11a9 100644 --- a/packages/core/src/i18n/locales/hr.ts +++ b/packages/core/src/i18n/locales/hr.ts @@ -128,6 +128,9 @@ export const hr = { aliases: ["emoji", "emotikon", "emocija", "lice"], group: "Ostalo", }, + comment: { + tooltip: "Dodaj komentar", + }, }, placeholders: { default: "Unesi tekst ili upiši ‘/’ za naredbe", diff --git a/packages/core/src/i18n/locales/is.ts b/packages/core/src/i18n/locales/is.ts index 978ba481ba..f21f450445 100644 --- a/packages/core/src/i18n/locales/is.ts +++ b/packages/core/src/i18n/locales/is.ts @@ -268,6 +268,9 @@ export const is: Dictionary = { align_justify: { tooltip: "Jafna texta", }, + comment: { + tooltip: "Bæta við athugasemd", + }, }, file_panel: { upload: { diff --git a/packages/core/src/i18n/locales/it.ts b/packages/core/src/i18n/locales/it.ts index b1df6e780e..dafefb0f83 100644 --- a/packages/core/src/i18n/locales/it.ts +++ b/packages/core/src/i18n/locales/it.ts @@ -1,315 +1,317 @@ export const it = { - slash_menu: { - heading: { - title: "Intestazione 1", - subtext: "Intestazione di primo livello", - aliases: ["h", "intestazione1", "h1"], - group: "Intestazioni", - }, - heading_2: { - title: "Intestazione 2", - subtext: "Intestazione di sezione chiave", - aliases: ["h2", "intestazione2", "sottotitolo"], - group: "Intestazioni", - }, - heading_3: { - title: "Intestazione 3", - subtext: "Intestazione di sottosezione e gruppo", - aliases: ["h3", "intestazione3", "sottotitolo"], - group: "Intestazioni", - }, - numbered_list: { - title: "Elenco Numerato", - subtext: "Elenco con elementi ordinati", - aliases: ["ol", "li", "elenco", "elenconumerato", "elenco numerato"], - group: "Blocchi Base", - }, - bullet_list: { - title: "Elenco Puntato", - subtext: "Elenco con elementi non ordinati", - aliases: ["ul", "li", "elenco", "elencopuntato", "elenco puntato"], - group: "Blocchi Base", - }, - check_list: { - title: "Elenco di Controllo", - subtext: "Elenco con caselle di controllo", - aliases: [ - "ul", - "li", - "elenco", - "elencocontrollo", - "elenco controllo", - "elenco verificato", - "casella di controllo", - ], - group: "Blocchi Base", - }, - paragraph: { - title: "Paragrafo", - subtext: "Il corpo del tuo documento", - aliases: ["p", "paragrafo"], - group: "Blocchi Base", - }, - code_block: { - title: "Blocco di Codice", - subtext: "Blocco di codice con evidenziazione della sintassi", - aliases: ["code", "pre"], - group: "Blocchi Base", - }, - table: { - title: "Tabella", - subtext: "Tabella con celle modificabili", - aliases: ["tabella"], - group: "Avanzato", - }, - image: { - title: "Immagine", - subtext: "Immagine ridimensionabile con didascalia", - aliases: [ - "immagine", - "caricaImmagine", - "carica", - "img", - "foto", - "media", - "url", - ], - group: "Media", - }, - video: { - title: "Video", - subtext: "Video ridimensionabile con didascalia", - aliases: [ - "video", - "caricaVideo", - "carica", - "mp4", - "film", - "media", - "url", - ], - group: "Media", - }, - audio: { - title: "Audio", - subtext: "Audio incorporato con didascalia", - aliases: [ - "audio", - "caricaAudio", - "carica", - "mp3", - "suono", - "media", - "url", - ], - group: "Media", - }, - file: { - title: "File", - subtext: "File incorporato", - aliases: ["file", "carica", "embed", "media", "url"], - group: "Media", - }, - emoji: { - title: "Emoji", - subtext: "Cerca e inserisci un'emoji", - aliases: ["emoji", "emote", "emozione", "faccia"], - group: "Altri", - }, - }, - placeholders: { - default: "Inserisci testo o digita '/' per i comandi", - heading: "Intestazione", - bulletListItem: "Elenco", - numberedListItem: "Elenco", - checkListItem: "Elenco", - }, - file_blocks: { - image: { - add_button_text: "Aggiungi immagine", - }, - video: { - add_button_text: "Aggiungi video", - }, - audio: { - add_button_text: "Aggiungi audio", - }, - file: { - add_button_text: "Aggiungi file", - }, - }, - // from react package: - side_menu: { - add_block_label: "Aggiungi blocco", - drag_handle_label: "Apri menu blocco", - }, - drag_handle: { - delete_menuitem: "Elimina", - colors_menuitem: "Colori", - }, - table_handle: { - delete_column_menuitem: "Elimina colonna", - delete_row_menuitem: "Elimina riga", - add_left_menuitem: "Aggiungi colonna a sinistra", - add_right_menuitem: "Aggiungi colonna a destra", - add_above_menuitem: "Aggiungi riga sopra", - add_below_menuitem: "Aggiungi riga sotto", - }, - suggestion_menu: { - no_items_title: "Nessun elemento trovato", - loading: "Caricamento…", - }, - color_picker: { - text_title: "Testo", - background_title: "Sfondo", - colors: { - default: "Predefinito", - gray: "Grigio", - brown: "Marrone", - red: "Rosso", - orange: "Arancione", - yellow: "Giallo", - green: "Verde", - blue: "Blu", - purple: "Viola", - pink: "Rosa", - }, - }, - - formatting_toolbar: { - bold: { - tooltip: "Grassetto", - secondary_tooltip: "Cmd+B", - }, - italic: { - tooltip: "Corsivo", - secondary_tooltip: "Cmd+I", - }, - underline: { - tooltip: "Sottolineato", - secondary_tooltip: "Cmd+U", - }, - strike: { - tooltip: "Barrato", - secondary_tooltip: "Cmd+Shift+S", - }, - code: { - tooltip: "Codice", - secondary_tooltip: "", - }, - colors: { - tooltip: "Colori", - }, - link: { - tooltip: "Crea link", - secondary_tooltip: "Cmd+K", - }, - file_caption: { - tooltip: "Modifica didascalia", - input_placeholder: "Modifica didascalia", - }, - file_replace: { - tooltip: { - image: "Sostituisci immagine", - video: "Sostituisci video", - audio: "Sostituisci audio", - file: "Sostituisci file", - } as Record, - }, - file_rename: { - tooltip: { - image: "Rinomina immagine", - video: "Rinomina video", - audio: "Rinomina audio", - file: "Rinomina file", - } as Record, - input_placeholder: { - image: "Rinomina immagine", - video: "Rinomina video", - audio: "Rinomina audio", - file: "Rinomina file", - } as Record, - }, - file_download: { - tooltip: { - image: "Scarica immagine", - video: "Scarica video", - audio: "Scarica audio", - file: "Scarica file", - } as Record, - }, - file_delete: { - tooltip: { - image: "Elimina immagine", - video: "Elimina video", - audio: "Elimina audio", - file: "Elimina file", - } as Record, - }, - file_preview_toggle: { - tooltip: "Attiva/disattiva anteprima", - }, - nest: { - tooltip: "Annida blocco", - secondary_tooltip: "Tab", - }, - unnest: { - tooltip: "Disannida blocco", - secondary_tooltip: "Shift+Tab", - }, - align_left: { - tooltip: "Allinea testo a sinistra", - }, - align_center: { - tooltip: "Allinea testo al centro", - }, - align_right: { - tooltip: "Allinea testo a destra", - }, - align_justify: { - tooltip: "Giustifica testo", - }, - }, - file_panel: { - upload: { - title: "Carica", - file_placeholder: { - image: "Carica immagine", - video: "Carica video", - audio: "Carica audio", - file: "Carica file", - } as Record, - upload_error: "Errore: Caricamento fallito", - }, - embed: { - title: "Incorpora", - embed_button: { - image: "Incorpora immagine", - video: "Incorpora video", - audio: "Incorpora audio", - file: "Incorpora file", - } as Record, - url_placeholder: "Inserisci URL", - }, - }, - link_toolbar: { - delete: { - tooltip: "Rimuovi link", - }, - edit: { - text: "Modifica link", - tooltip: "Modifica", - }, - open: { - tooltip: "Apri in una nuova scheda", - }, - form: { - title_placeholder: "Modifica titolo", - url_placeholder: "Modifica URL", - }, - }, - generic: { - ctrl_shortcut: "Ctrl", - }, - }; - \ No newline at end of file + slash_menu: { + heading: { + title: "Intestazione 1", + subtext: "Intestazione di primo livello", + aliases: ["h", "intestazione1", "h1"], + group: "Intestazioni", + }, + heading_2: { + title: "Intestazione 2", + subtext: "Intestazione di sezione chiave", + aliases: ["h2", "intestazione2", "sottotitolo"], + group: "Intestazioni", + }, + heading_3: { + title: "Intestazione 3", + subtext: "Intestazione di sottosezione e gruppo", + aliases: ["h3", "intestazione3", "sottotitolo"], + group: "Intestazioni", + }, + numbered_list: { + title: "Elenco Numerato", + subtext: "Elenco con elementi ordinati", + aliases: ["ol", "li", "elenco", "elenconumerato", "elenco numerato"], + group: "Blocchi Base", + }, + bullet_list: { + title: "Elenco Puntato", + subtext: "Elenco con elementi non ordinati", + aliases: ["ul", "li", "elenco", "elencopuntato", "elenco puntato"], + group: "Blocchi Base", + }, + check_list: { + title: "Elenco di Controllo", + subtext: "Elenco con caselle di controllo", + aliases: [ + "ul", + "li", + "elenco", + "elencocontrollo", + "elenco controllo", + "elenco verificato", + "casella di controllo", + ], + group: "Blocchi Base", + }, + paragraph: { + title: "Paragrafo", + subtext: "Il corpo del tuo documento", + aliases: ["p", "paragrafo"], + group: "Blocchi Base", + }, + code_block: { + title: "Blocco di Codice", + subtext: "Blocco di codice con evidenziazione della sintassi", + aliases: ["code", "pre"], + group: "Blocchi Base", + }, + table: { + title: "Tabella", + subtext: "Tabella con celle modificabili", + aliases: ["tabella"], + group: "Avanzato", + }, + image: { + title: "Immagine", + subtext: "Immagine ridimensionabile con didascalia", + aliases: [ + "immagine", + "caricaImmagine", + "carica", + "img", + "foto", + "media", + "url", + ], + group: "Media", + }, + video: { + title: "Video", + subtext: "Video ridimensionabile con didascalia", + aliases: [ + "video", + "caricaVideo", + "carica", + "mp4", + "film", + "media", + "url", + ], + group: "Media", + }, + audio: { + title: "Audio", + subtext: "Audio incorporato con didascalia", + aliases: [ + "audio", + "caricaAudio", + "carica", + "mp3", + "suono", + "media", + "url", + ], + group: "Media", + }, + file: { + title: "File", + subtext: "File incorporato", + aliases: ["file", "carica", "embed", "media", "url"], + group: "Media", + }, + emoji: { + title: "Emoji", + subtext: "Cerca e inserisci un'emoji", + aliases: ["emoji", "emote", "emozione", "faccia"], + group: "Altri", + }, + }, + placeholders: { + default: "Inserisci testo o digita '/' per i comandi", + heading: "Intestazione", + bulletListItem: "Elenco", + numberedListItem: "Elenco", + checkListItem: "Elenco", + }, + file_blocks: { + image: { + add_button_text: "Aggiungi immagine", + }, + video: { + add_button_text: "Aggiungi video", + }, + audio: { + add_button_text: "Aggiungi audio", + }, + file: { + add_button_text: "Aggiungi file", + }, + }, + // from react package: + side_menu: { + add_block_label: "Aggiungi blocco", + drag_handle_label: "Apri menu blocco", + }, + drag_handle: { + delete_menuitem: "Elimina", + colors_menuitem: "Colori", + }, + table_handle: { + delete_column_menuitem: "Elimina colonna", + delete_row_menuitem: "Elimina riga", + add_left_menuitem: "Aggiungi colonna a sinistra", + add_right_menuitem: "Aggiungi colonna a destra", + add_above_menuitem: "Aggiungi riga sopra", + add_below_menuitem: "Aggiungi riga sotto", + }, + suggestion_menu: { + no_items_title: "Nessun elemento trovato", + loading: "Caricamento…", + }, + color_picker: { + text_title: "Testo", + background_title: "Sfondo", + colors: { + default: "Predefinito", + gray: "Grigio", + brown: "Marrone", + red: "Rosso", + orange: "Arancione", + yellow: "Giallo", + green: "Verde", + blue: "Blu", + purple: "Viola", + pink: "Rosa", + }, + }, + + formatting_toolbar: { + bold: { + tooltip: "Grassetto", + secondary_tooltip: "Cmd+B", + }, + italic: { + tooltip: "Corsivo", + secondary_tooltip: "Cmd+I", + }, + underline: { + tooltip: "Sottolineato", + secondary_tooltip: "Cmd+U", + }, + strike: { + tooltip: "Barrato", + secondary_tooltip: "Cmd+Shift+S", + }, + code: { + tooltip: "Codice", + secondary_tooltip: "", + }, + colors: { + tooltip: "Colori", + }, + link: { + tooltip: "Crea link", + secondary_tooltip: "Cmd+K", + }, + file_caption: { + tooltip: "Modifica didascalia", + input_placeholder: "Modifica didascalia", + }, + file_replace: { + tooltip: { + image: "Sostituisci immagine", + video: "Sostituisci video", + audio: "Sostituisci audio", + file: "Sostituisci file", + } as Record, + }, + file_rename: { + tooltip: { + image: "Rinomina immagine", + video: "Rinomina video", + audio: "Rinomina audio", + file: "Rinomina file", + } as Record, + input_placeholder: { + image: "Rinomina immagine", + video: "Rinomina video", + audio: "Rinomina audio", + file: "Rinomina file", + } as Record, + }, + file_download: { + tooltip: { + image: "Scarica immagine", + video: "Scarica video", + audio: "Scarica audio", + file: "Scarica file", + } as Record, + }, + file_delete: { + tooltip: { + image: "Elimina immagine", + video: "Elimina video", + audio: "Elimina audio", + file: "Elimina file", + } as Record, + }, + file_preview_toggle: { + tooltip: "Attiva/disattiva anteprima", + }, + nest: { + tooltip: "Annida blocco", + secondary_tooltip: "Tab", + }, + unnest: { + tooltip: "Disannida blocco", + secondary_tooltip: "Shift+Tab", + }, + align_left: { + tooltip: "Allinea testo a sinistra", + }, + align_center: { + tooltip: "Allinea testo al centro", + }, + align_right: { + tooltip: "Allinea testo a destra", + }, + align_justify: { + tooltip: "Giustifica testo", + }, + comment: { + tooltip: "Aggiungi commento", + }, + }, + file_panel: { + upload: { + title: "Carica", + file_placeholder: { + image: "Carica immagine", + video: "Carica video", + audio: "Carica audio", + file: "Carica file", + } as Record, + upload_error: "Errore: Caricamento fallito", + }, + embed: { + title: "Incorpora", + embed_button: { + image: "Incorpora immagine", + video: "Incorpora video", + audio: "Incorpora audio", + file: "Incorpora file", + } as Record, + url_placeholder: "Inserisci URL", + }, + }, + link_toolbar: { + delete: { + tooltip: "Rimuovi link", + }, + edit: { + text: "Modifica link", + tooltip: "Modifica", + }, + open: { + tooltip: "Apri in una nuova scheda", + }, + form: { + title_placeholder: "Modifica titolo", + url_placeholder: "Modifica URL", + }, + }, + generic: { + ctrl_shortcut: "Ctrl", + }, +}; diff --git a/packages/core/src/i18n/locales/ja.ts b/packages/core/src/i18n/locales/ja.ts index a4c2ff04a4..abb3b4b4d0 100644 --- a/packages/core/src/i18n/locales/ja.ts +++ b/packages/core/src/i18n/locales/ja.ts @@ -296,6 +296,9 @@ export const ja: Dictionary = { align_justify: { tooltip: "両端揃え", }, + comment: { + tooltip: "コメントを追加", + }, }, file_panel: { upload: { diff --git a/packages/core/src/i18n/locales/ko.ts b/packages/core/src/i18n/locales/ko.ts index 0bf8e24a1f..e7ba018c30 100644 --- a/packages/core/src/i18n/locales/ko.ts +++ b/packages/core/src/i18n/locales/ko.ts @@ -289,6 +289,9 @@ export const ko: Dictionary = { align_justify: { tooltip: "텍스트 양쪽 맞춤", }, + comment: { + tooltip: "댓글 추가", + }, }, file_panel: { upload: { diff --git a/packages/core/src/i18n/locales/nl.ts b/packages/core/src/i18n/locales/nl.ts index 25c3426d2f..923a21f539 100644 --- a/packages/core/src/i18n/locales/nl.ts +++ b/packages/core/src/i18n/locales/nl.ts @@ -275,6 +275,9 @@ export const nl: Dictionary = { align_justify: { tooltip: "Tekst uitvullen", }, + comment: { + tooltip: "Opmerking toevoegen", + }, }, file_panel: { upload: { diff --git a/packages/core/src/i18n/locales/pl.ts b/packages/core/src/i18n/locales/pl.ts index befa6ace7c..b8b9ad908d 100644 --- a/packages/core/src/i18n/locales/pl.ts +++ b/packages/core/src/i18n/locales/pl.ts @@ -260,6 +260,9 @@ export const pl: Dictionary = { align_justify: { tooltip: "Wyjustuj tekst", }, + comment: { + tooltip: "Dodaj komentarz", + }, }, file_panel: { upload: { diff --git a/packages/core/src/i18n/locales/pt.ts b/packages/core/src/i18n/locales/pt.ts index 6dd7dab3ca..049119de0c 100644 --- a/packages/core/src/i18n/locales/pt.ts +++ b/packages/core/src/i18n/locales/pt.ts @@ -268,6 +268,9 @@ export const pt: Dictionary = { align_justify: { tooltip: "Justificar texto", }, + comment: { + tooltip: "Adicionar comentário", + }, }, file_panel: { upload: { diff --git a/packages/core/src/i18n/locales/ru.ts b/packages/core/src/i18n/locales/ru.ts index 60309d5d90..6445104cb8 100644 --- a/packages/core/src/i18n/locales/ru.ts +++ b/packages/core/src/i18n/locales/ru.ts @@ -303,6 +303,9 @@ export const ru: Dictionary = { align_justify: { tooltip: "По середине текст", }, + comment: { + tooltip: "Добавить комментарий", + }, }, file_panel: { upload: { diff --git a/packages/core/src/i18n/locales/vi.ts b/packages/core/src/i18n/locales/vi.ts index eb1dbf31a2..c9ae069214 100644 --- a/packages/core/src/i18n/locales/vi.ts +++ b/packages/core/src/i18n/locales/vi.ts @@ -275,6 +275,9 @@ export const vi: Dictionary = { align_justify: { tooltip: "Căn đều văn bản", }, + comment: { + tooltip: "Thêm bình luận", + }, }, file_panel: { upload: { diff --git a/packages/core/src/i18n/locales/zh.ts b/packages/core/src/i18n/locales/zh.ts index cfb32c155f..e6f5f9334e 100644 --- a/packages/core/src/i18n/locales/zh.ts +++ b/packages/core/src/i18n/locales/zh.ts @@ -309,6 +309,9 @@ export const zh: Dictionary = { align_justify: { tooltip: "文本对齐", }, + comment: { + tooltip: "添加评论", + }, }, file_panel: { upload: { From 9f406518c2e6281ed27d5db65688420a16e17d64 Mon Sep 17 00:00:00 2001 From: yousefed Date: Tue, 11 Feb 2025 11:26:37 +0100 Subject: [PATCH 029/144] fix build --- .../02-liveblocks/tsconfig.json | 14 +- .../04-comments/.bnexample.json | 9 + examples/07-collaboration/04-comments/App.tsx | 12 + .../07-collaboration/04-comments/README.md | 3 + .../07-collaboration/04-comments/index.html | 14 + .../07-collaboration/04-comments/main.tsx | 11 + .../07-collaboration/04-comments/package.json | 38 ++ .../04-comments/tsconfig.json | 36 + .../04-comments/vite.config.ts | 32 + package-lock.json | 1 + .../TableBlockContent/TableBlockContent.ts | 3 +- ...dStore.ts => LiveBlocksThreadStore.ts.bak} | 0 ...hreadStore.ts => TipTapThreadStore.ts.bak} | 0 .../Comments/threadstore/YjsThreadStore.ts | 1 + packages/core/src/i18n/locales/uk.ts | 615 ++++++++++-------- packages/react/package.json | 1 + .../react/src/components/Comments/Comment.tsx | 8 +- packages/react/src/schema/ReactBlockSpec.tsx | 4 +- playground/src/examples.gen.tsx | 23 + 19 files changed, 535 insertions(+), 290 deletions(-) create mode 100644 examples/07-collaboration/04-comments/.bnexample.json create mode 100644 examples/07-collaboration/04-comments/App.tsx create mode 100644 examples/07-collaboration/04-comments/README.md create mode 100644 examples/07-collaboration/04-comments/index.html create mode 100644 examples/07-collaboration/04-comments/main.tsx create mode 100644 examples/07-collaboration/04-comments/package.json create mode 100644 examples/07-collaboration/04-comments/tsconfig.json create mode 100644 examples/07-collaboration/04-comments/vite.config.ts rename packages/core/src/extensions/Comments/threadstore/{LiveBlocksThreadStore.ts => LiveBlocksThreadStore.ts.bak} (100%) rename packages/core/src/extensions/Comments/threadstore/{TipTapThreadStore.ts => TipTapThreadStore.ts.bak} (100%) diff --git a/examples/07-collaboration/02-liveblocks/tsconfig.json b/examples/07-collaboration/02-liveblocks/tsconfig.json index 4a76cf4c7d..1bd8ab3c57 100644 --- a/examples/07-collaboration/02-liveblocks/tsconfig.json +++ b/examples/07-collaboration/02-liveblocks/tsconfig.json @@ -3,7 +3,11 @@ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, - "lib": ["DOM", "DOM.Iterable", "ESNext"], + "lib": [ + "DOM", + "DOM.Iterable", + "ESNext" + ], "allowJs": false, "skipLibCheck": true, "esModuleInterop": false, @@ -18,8 +22,10 @@ "jsx": "react-jsx", "composite": true }, - "include": ["."], - "references": [ + "include": [ + "." + ], + "__ADD_FOR_LOCAL_DEV_references": [ { "path": "../../../packages/core/" }, @@ -27,4 +33,4 @@ "path": "../../../packages/react/" } ] -} +} \ No newline at end of file diff --git a/examples/07-collaboration/04-comments/.bnexample.json b/examples/07-collaboration/04-comments/.bnexample.json new file mode 100644 index 0000000000..ec9d562b0e --- /dev/null +++ b/examples/07-collaboration/04-comments/.bnexample.json @@ -0,0 +1,9 @@ +{ + "playground": true, + "docs": true, + "author": "jakelazaroff", + "tags": ["Advanced", "Saving/Loading", "Collaboration"], + "dependencies": { + "@y-sweet/react": "^0.6.3" + } +} diff --git a/examples/07-collaboration/04-comments/App.tsx b/examples/07-collaboration/04-comments/App.tsx new file mode 100644 index 0000000000..65d9d8e5aa --- /dev/null +++ b/examples/07-collaboration/04-comments/App.tsx @@ -0,0 +1,12 @@ +"use client"; + +import { BlockNoteView } from "@blocknote/mantine"; +import { useCreateBlockNote } from "@blocknote/react"; + +import "@blocknote/mantine/style.css"; + +export default function App() { + const editor = useCreateBlockNote({}); + + return ; +} diff --git a/examples/07-collaboration/04-comments/README.md b/examples/07-collaboration/04-comments/README.md new file mode 100644 index 0000000000..fc7b54d750 --- /dev/null +++ b/examples/07-collaboration/04-comments/README.md @@ -0,0 +1,3 @@ +# Comments & Threads + +TODO diff --git a/examples/07-collaboration/04-comments/index.html b/examples/07-collaboration/04-comments/index.html new file mode 100644 index 0000000000..6acbee32f9 --- /dev/null +++ b/examples/07-collaboration/04-comments/index.html @@ -0,0 +1,14 @@ + + + + + + Comments & Threads + + +
+ + + diff --git a/examples/07-collaboration/04-comments/main.tsx b/examples/07-collaboration/04-comments/main.tsx new file mode 100644 index 0000000000..f88b490fbd --- /dev/null +++ b/examples/07-collaboration/04-comments/main.tsx @@ -0,0 +1,11 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import React from "react"; +import { createRoot } from "react-dom/client"; +import App from "./App"; + +const root = createRoot(document.getElementById("root")!); +root.render( + + + +); diff --git a/examples/07-collaboration/04-comments/package.json b/examples/07-collaboration/04-comments/package.json new file mode 100644 index 0000000000..d3d3c0a3d9 --- /dev/null +++ b/examples/07-collaboration/04-comments/package.json @@ -0,0 +1,38 @@ +{ + "name": "@blocknote/example-comments", + "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "private": true, + "version": "0.12.4", + "scripts": { + "start": "vite", + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview", + "lint": "eslint . --max-warnings 0" + }, + "dependencies": { + "@blocknote/core": "latest", + "@blocknote/react": "latest", + "@blocknote/ariakit": "latest", + "@blocknote/mantine": "latest", + "@blocknote/shadcn": "latest", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "@y-sweet/react": "^0.6.3" + }, + "devDependencies": { + "@types/react": "^18.0.25", + "@types/react-dom": "^18.0.9", + "@vitejs/plugin-react": "^4.3.1", + "eslint": "^8.10.0", + "vite": "^5.3.4" + }, + "eslintConfig": { + "extends": [ + "../../../.eslintrc.js" + ] + }, + "eslintIgnore": [ + "dist" + ] +} \ No newline at end of file diff --git a/examples/07-collaboration/04-comments/tsconfig.json b/examples/07-collaboration/04-comments/tsconfig.json new file mode 100644 index 0000000000..1bd8ab3c57 --- /dev/null +++ b/examples/07-collaboration/04-comments/tsconfig.json @@ -0,0 +1,36 @@ +{ + "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": [ + "DOM", + "DOM.Iterable", + "ESNext" + ], + "allowJs": false, + "skipLibCheck": true, + "esModuleInterop": false, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "Node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "composite": true + }, + "include": [ + "." + ], + "__ADD_FOR_LOCAL_DEV_references": [ + { + "path": "../../../packages/core/" + }, + { + "path": "../../../packages/react/" + } + ] +} \ No newline at end of file diff --git a/examples/07-collaboration/04-comments/vite.config.ts b/examples/07-collaboration/04-comments/vite.config.ts new file mode 100644 index 0000000000..f62ab20bc2 --- /dev/null +++ b/examples/07-collaboration/04-comments/vite.config.ts @@ -0,0 +1,32 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import react from "@vitejs/plugin-react"; +import * as fs from "fs"; +import * as path from "path"; +import { defineConfig } from "vite"; +// import eslintPlugin from "vite-plugin-eslint"; +// https://vitejs.dev/config/ +export default defineConfig((conf) => ({ + plugins: [react()], + optimizeDeps: {}, + build: { + sourcemap: true, + }, + resolve: { + alias: + conf.command === "build" || + !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) + ? {} + : ({ + // Comment out the lines below to load a built version of blocknote + // or, keep as is to load live from sources with live reload working + "@blocknote/core": path.resolve( + __dirname, + "../../packages/core/src/" + ), + "@blocknote/react": path.resolve( + __dirname, + "../../packages/react/src/" + ), + } as any), + }, +})); diff --git a/package-lock.json b/package-lock.json index 801888860d..b19791cb29 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31920,6 +31920,7 @@ "@emoji-mart/react": "^1.1.1", "@floating-ui/react": "^0.26.4", "@tiptap/core": "^2.7.1", + "@tiptap/pm": "^2.7.1", "@tiptap/react": "^2.7.1", "lodash.merge": "^4.6.2", "react-icons": "^5.2.1" diff --git a/packages/core/src/blocks/TableBlockContent/TableBlockContent.ts b/packages/core/src/blocks/TableBlockContent/TableBlockContent.ts index 174cf0654a..bb35506176 100644 --- a/packages/core/src/blocks/TableBlockContent/TableBlockContent.ts +++ b/packages/core/src/blocks/TableBlockContent/TableBlockContent.ts @@ -5,6 +5,7 @@ import { TableRow } from "@tiptap/extension-table-row"; import { Node as PMNode } from "prosemirror-model"; import { TableView } from "prosemirror-tables"; +import { NodeView } from "prosemirror-view"; import { createBlockSpecFromStronglyTypedTiptapNode, createStronglyTypedTiptapNode, @@ -101,7 +102,7 @@ export const TableBlockContent = createStronglyTypedTiptapNode({ return new BlockNoteTableView(node, EMPTY_CELL_WIDTH, { ...(this.options.domAttributes?.blockContent || {}), ...HTMLAttributes, - }); + }) as NodeView; // needs cast, tiptap types (wrongly) doesn't support return tableview here }; }, }); diff --git a/packages/core/src/extensions/Comments/threadstore/LiveBlocksThreadStore.ts b/packages/core/src/extensions/Comments/threadstore/LiveBlocksThreadStore.ts.bak similarity index 100% rename from packages/core/src/extensions/Comments/threadstore/LiveBlocksThreadStore.ts rename to packages/core/src/extensions/Comments/threadstore/LiveBlocksThreadStore.ts.bak diff --git a/packages/core/src/extensions/Comments/threadstore/TipTapThreadStore.ts b/packages/core/src/extensions/Comments/threadstore/TipTapThreadStore.ts.bak similarity index 100% rename from packages/core/src/extensions/Comments/threadstore/TipTapThreadStore.ts rename to packages/core/src/extensions/Comments/threadstore/TipTapThreadStore.ts.bak diff --git a/packages/core/src/extensions/Comments/threadstore/YjsThreadStore.ts b/packages/core/src/extensions/Comments/threadstore/YjsThreadStore.ts index cf0d5127f4..1f1928028c 100644 --- a/packages/core/src/extensions/Comments/threadstore/YjsThreadStore.ts +++ b/packages/core/src/extensions/Comments/threadstore/YjsThreadStore.ts @@ -12,6 +12,7 @@ import { ThreadStoreAuth } from "./ThreadStoreAuth.js"; export class YjsThreadStore extends ThreadStore { constructor( + // @ts-ignore private readonly editor: BlockNoteEditor, private readonly userId: string, private readonly threadsYMap: Y.Map, diff --git a/packages/core/src/i18n/locales/uk.ts b/packages/core/src/i18n/locales/uk.ts index 8cb144e7a1..28ee7cd93c 100644 --- a/packages/core/src/i18n/locales/uk.ts +++ b/packages/core/src/i18n/locales/uk.ts @@ -1,289 +1,348 @@ import { Dictionary } from "../dictionary.js"; export const uk: Dictionary = { - slash_menu: { - heading: { - title: "Заголовок 1", - subtext: "Заголовок найвищого рівня", - aliases: ["h", "heading1", "h1", "заголовок1"], - group: "Заголовки", - }, - heading_2: { - title: "Заголовок 2", - subtext: "Основний заголовок розділу", - aliases: ["h2", "heading2", "subheading", "заголовок2"], - group: "Заголовки", - }, - heading_3: { - title: "Заголовок 3", - subtext: "Підзаголовок і груповий заголовок", - aliases: ["h3", "heading3", "subheading", "заголовок3"], - group: "Заголовки", - }, - numbered_list: { - title: "Нумерований список", - subtext: "Список із впорядкованими елементами", - aliases: ["ol", "li", "list", "numberedlist", "numbered list", "список", "нумерований список"], - group: "Базові блоки", - }, - bullet_list: { - title: "Маркований список", - subtext: "Список із невпорядкованими елементами", - aliases: ["ul", "li", "list", "bulletlist", "bullet list", "список", "маркований список"], - group: "Базові блоки", - }, - check_list: { - title: "Чек-лист", - subtext: "Список із чекбоксами", - aliases: ["ul", "li", "list", "checklist", "check list", "checked list", "checkbox", "чекбокс", "чек-лист"], - group: "Базові блоки", - }, - paragraph: { - title: "Параграф", - subtext: "Основний текст документа", - aliases: ["p", "paragraph", "параграф"], - group: "Базові блоки", - }, - code_block: { - title: "Блок коду", - subtext: "Блок коду з підсвіткою синтаксису", - aliases: ["code", "pre", "блок коду"], - group: "Базові блоки", - }, - page_break: { - title: "Розрив сторінки", - subtext: "Роздільник сторінки", - aliases: ["page", "break", "separator", "розрив сторінки", "розділювач"], - group: "Базові блоки", - }, - table: { - title: "Таблиця", - subtext: "Таблиця з редагованими клітинками", - aliases: ["table", "таблиця"], - group: "Розширені", - }, - image: { - title: "Зображення", - subtext: "Масштабоване зображення з підписом", - aliases: ["image", "imageUpload", "upload", "img", "picture", "media", "url", "зображення", "медіа"], - group: "Медіа", - }, - video: { - title: "Відео", - subtext: "Масштабоване відео з підписом", - aliases: ["video", "videoUpload", "upload", "mp4", "film", "media", "url", "відео", "медіа"], - group: "Медіа", - }, - audio: { - title: "Аудіо", - subtext: "Вбудоване аудіо з підписом", - aliases: ["audio", "audioUpload", "upload", "mp3", "sound", "media", "url", "аудіо", "медіа"], - group: "Медіа", - }, - file: { - title: "Файл", - subtext: "Вбудований файл", - aliases: ["file", "upload", "embed", "media", "url", "файл", "медіа"], - group: "Медіа", - }, - emoji: { - title: "Емодзі", - subtext: "Пошук і вставка емодзі", - aliases: ["emoji", "emote", "emotion", "face", "смайлик", "емодзі"], - group: "Інше", - }, + slash_menu: { + heading: { + title: "Заголовок 1", + subtext: "Заголовок найвищого рівня", + aliases: ["h", "heading1", "h1", "заголовок1"], + group: "Заголовки", + }, + heading_2: { + title: "Заголовок 2", + subtext: "Основний заголовок розділу", + aliases: ["h2", "heading2", "subheading", "заголовок2"], + group: "Заголовки", + }, + heading_3: { + title: "Заголовок 3", + subtext: "Підзаголовок і груповий заголовок", + aliases: ["h3", "heading3", "subheading", "заголовок3"], + group: "Заголовки", + }, + numbered_list: { + title: "Нумерований список", + subtext: "Список із впорядкованими елементами", + aliases: [ + "ol", + "li", + "list", + "numberedlist", + "numbered list", + "список", + "нумерований список", + ], + group: "Базові блоки", + }, + bullet_list: { + title: "Маркований список", + subtext: "Список із невпорядкованими елементами", + aliases: [ + "ul", + "li", + "list", + "bulletlist", + "bullet list", + "список", + "маркований список", + ], + group: "Базові блоки", + }, + check_list: { + title: "Чек-лист", + subtext: "Список із чекбоксами", + aliases: [ + "ul", + "li", + "list", + "checklist", + "check list", + "checked list", + "checkbox", + "чекбокс", + "чек-лист", + ], + group: "Базові блоки", + }, + paragraph: { + title: "Параграф", + subtext: "Основний текст документа", + aliases: ["p", "paragraph", "параграф"], + group: "Базові блоки", + }, + code_block: { + title: "Блок коду", + subtext: "Блок коду з підсвіткою синтаксису", + aliases: ["code", "pre", "блок коду"], + group: "Базові блоки", + }, + page_break: { + title: "Розрив сторінки", + subtext: "Роздільник сторінки", + aliases: ["page", "break", "separator", "розрив сторінки", "розділювач"], + group: "Базові блоки", + }, + table: { + title: "Таблиця", + subtext: "Таблиця з редагованими клітинками", + aliases: ["table", "таблиця"], + group: "Розширені", + }, + image: { + title: "Зображення", + subtext: "Масштабоване зображення з підписом", + aliases: [ + "image", + "imageUpload", + "upload", + "img", + "picture", + "media", + "url", + "зображення", + "медіа", + ], + group: "Медіа", + }, + video: { + title: "Відео", + subtext: "Масштабоване відео з підписом", + aliases: [ + "video", + "videoUpload", + "upload", + "mp4", + "film", + "media", + "url", + "відео", + "медіа", + ], + group: "Медіа", + }, + audio: { + title: "Аудіо", + subtext: "Вбудоване аудіо з підписом", + aliases: [ + "audio", + "audioUpload", + "upload", + "mp3", + "sound", + "media", + "url", + "аудіо", + "медіа", + ], + group: "Медіа", + }, + file: { + title: "Файл", + subtext: "Вбудований файл", + aliases: ["file", "upload", "embed", "media", "url", "файл", "медіа"], + group: "Медіа", + }, + emoji: { + title: "Емодзі", + subtext: "Пошук і вставка емодзі", + aliases: ["emoji", "emote", "emotion", "face", "смайлик", "емодзі"], + group: "Інше", + }, + }, + placeholders: { + default: "Введіть текст або наберіть '/' для команд", + heading: "Заголовок", + bulletListItem: "Список", + numberedListItem: "Список", + checkListItem: "Список", + }, + file_blocks: { + image: { + add_button_text: "Додати зображення", + }, + video: { + add_button_text: "Додати відео", + }, + audio: { + add_button_text: "Додати аудіо", + }, + file: { + add_button_text: "Додати файл", + }, + }, + // from react package: + side_menu: { + add_block_label: "Додати блок", + drag_handle_label: "Відкрити меню блока", + }, + drag_handle: { + delete_menuitem: "Видалити", + colors_menuitem: "Кольори", + }, + table_handle: { + delete_column_menuitem: "Видалити стовпець", + delete_row_menuitem: "Видалити рядок", + add_left_menuitem: "Додати стовпець зліва", + add_right_menuitem: "Додати стовпець справа", + add_above_menuitem: "Додати рядок вище", + add_below_menuitem: "Додати рядок нижче", + }, + suggestion_menu: { + no_items_title: "Нічого не знайдено", + loading: "Завантаження…", + }, + color_picker: { + text_title: "Текст", + background_title: "Фон", + colors: { + default: "За замовчуванням", + gray: "Сірий", + brown: "Коричневий", + red: "Червоний", + orange: "Помаранчевий", + yellow: "Жовтий", + green: "Зелений", + blue: "Блакитний", + purple: "Фіолетовий", + pink: "Рожевий", + }, + }, + formatting_toolbar: { + bold: { + tooltip: "Жирний", + secondary_tooltip: "Mod+B", + }, + italic: { + tooltip: "Курсив", + secondary_tooltip: "Mod+I", + }, + underline: { + tooltip: "Підкреслений", + secondary_tooltip: "Mod+U", + }, + strike: { + tooltip: "Закреслений", + secondary_tooltip: "Mod+Shift+X", + }, + code: { + tooltip: "Код", + secondary_tooltip: "", + }, + colors: { + tooltip: "Кольори", + }, + link: { + tooltip: "Створити посилання", + secondary_tooltip: "Mod+K", + }, + file_caption: { + tooltip: "Редагувати підпис", + input_placeholder: "Редагувати підпис", + }, + file_replace: { + tooltip: { + image: "Замінити зображення", + video: "Замінити відео", + audio: "Замінити аудіо", + file: "Замінити файл", }, - placeholders: { - default: "Введіть текст або наберіть '/' для команд", - heading: "Заголовок", - bulletListItem: "Список", - numberedListItem: "Список", - checkListItem: "Список", + }, + file_rename: { + tooltip: { + image: "Перейменувати зображення", + video: "Перейменувати відео", + audio: "Перейменувати аудіо", + file: "Перейменувати файл", }, - file_blocks: { - image: { - add_button_text: "Додати зображення", - }, - video: { - add_button_text: "Додати відео", - }, - audio: { - add_button_text: "Додати аудіо", - }, - file: { - add_button_text: "Додати файл", - }, + input_placeholder: { + image: "Перейменувати зображення", + video: "Перейменувати відео", + audio: "Перейменувати аудіо", + file: "Перейменувати файл", }, - // from react package: - side_menu: { - add_block_label: "Додати блок", - drag_handle_label: "Відкрити меню блока", + }, + file_download: { + tooltip: { + image: "Завантажити зображення", + video: "Завантажити відео", + audio: "Завантажити аудіо", + file: "Завантажити файл", }, - drag_handle: { - delete_menuitem: "Видалити", - colors_menuitem: "Кольори", + }, + file_delete: { + tooltip: { + image: "Видалити зображення", + video: "Видалити відео", + audio: "Видалити аудіо", + file: "Видалити файл", }, - table_handle: { - delete_column_menuitem: "Видалити стовпець", - delete_row_menuitem: "Видалити рядок", - add_left_menuitem: "Додати стовпець зліва", - add_right_menuitem: "Додати стовпець справа", - add_above_menuitem: "Додати рядок вище", - add_below_menuitem: "Додати рядок нижче", + }, + file_preview_toggle: { + tooltip: "Перемкнути попередній перегляд", + }, + nest: { + tooltip: "Вкладений блок", + secondary_tooltip: "Tab", + }, + unnest: { + tooltip: "Розгрупувати блок", + secondary_tooltip: "Shift+Tab", + }, + align_left: { + tooltip: "Вирівняти за лівим краєм", + }, + align_center: { + tooltip: "Вирівняти по центру", + }, + align_right: { + tooltip: "Вирівняти за правим краєм", + }, + align_justify: { + tooltip: "Вирівняти за шириною", + }, + comment: { + tooltip: "Додати коментар", + }, + }, + file_panel: { + upload: { + title: "Завантажити", + file_placeholder: { + image: "Завантажити зображення", + video: "Завантажити відео", + audio: "Завантажити аудіо", + file: "Завантажити файл", }, - suggestion_menu: { - no_items_title: "Нічого не знайдено", - loading: "Завантаження…", - }, - color_picker: { - text_title: "Текст", - background_title: "Фон", - colors: { - default: "За замовчуванням", - gray: "Сірий", - brown: "Коричневий", - red: "Червоний", - orange: "Помаранчевий", - yellow: "Жовтий", - green: "Зелений", - blue: "Блакитний", - purple: "Фіолетовий", - pink: "Рожевий", - }, - }, - formatting_toolbar: { - bold: { - tooltip: "Жирний", - secondary_tooltip: "Mod+B", - }, - italic: { - tooltip: "Курсив", - secondary_tooltip: "Mod+I", - }, - underline: { - tooltip: "Підкреслений", - secondary_tooltip: "Mod+U", - }, - strike: { - tooltip: "Закреслений", - secondary_tooltip: "Mod+Shift+X", - }, - code: { - tooltip: "Код", - secondary_tooltip: "", - }, - colors: { - tooltip: "Кольори", - }, - link: { - tooltip: "Створити посилання", - secondary_tooltip: "Mod+K", - }, - file_caption: { - tooltip: "Редагувати підпис", - input_placeholder: "Редагувати підпис", - }, - file_replace: { - tooltip: { - image: "Замінити зображення", - video: "Замінити відео", - audio: "Замінити аудіо", - file: "Замінити файл", - }, - }, - file_rename: { - tooltip: { - image: "Перейменувати зображення", - video: "Перейменувати відео", - audio: "Перейменувати аудіо", - file: "Перейменувати файл", - }, - input_placeholder: { - image: "Перейменувати зображення", - video: "Перейменувати відео", - audio: "Перейменувати аудіо", - file: "Перейменувати файл", - }, - }, - file_download: { - tooltip: { - image: "Завантажити зображення", - video: "Завантажити відео", - audio: "Завантажити аудіо", - file: "Завантажити файл", - }, - }, - file_delete: { - tooltip: { - image: "Видалити зображення", - video: "Видалити відео", - audio: "Видалити аудіо", - file: "Видалити файл", - }, - }, - file_preview_toggle: { - tooltip: "Перемкнути попередній перегляд", - }, - nest: { - tooltip: "Вкладений блок", - secondary_tooltip: "Tab", - }, - unnest: { - tooltip: "Розгрупувати блок", - secondary_tooltip: "Shift+Tab", - }, - align_left: { - tooltip: "Вирівняти за лівим краєм", - }, - align_center: { - tooltip: "Вирівняти по центру", - }, - align_right: { - tooltip: "Вирівняти за правим краєм", - }, - align_justify: { - tooltip: "Вирівняти за шириною", - }, - }, - file_panel: { - upload: { - title: "Завантажити", - file_placeholder: { - image: "Завантажити зображення", - video: "Завантажити відео", - audio: "Завантажити аудіо", - file: "Завантажити файл", - }, - upload_error: "Помилка: не вдалося завантажити", - }, - embed: { - title: "Вставити", - embed_button: { - image: "Вставити зображення", - video: "Вставити відео", - audio: "Вставити аудіо", - file: "Вставити файл", - }, - url_placeholder: "Введіть URL", - }, - }, - link_toolbar: { - delete: { - tooltip: "Видалити посилання", - }, - edit: { - text: "Редагувати посилання", - tooltip: "Редагувати", - }, - open: { - tooltip: "Відкрити в новій вкладці", - }, - form: { - title_placeholder: "Редагувати заголовок", - url_placeholder: "Редагувати URL", - }, - }, - generic: { - ctrl_shortcut: "Ctrl", + upload_error: "Помилка: не вдалося завантажити", + }, + embed: { + title: "Вставити", + embed_button: { + image: "Вставити зображення", + video: "Вставити відео", + audio: "Вставити аудіо", + file: "Вставити файл", }, + url_placeholder: "Введіть URL", + }, + }, + link_toolbar: { + delete: { + tooltip: "Видалити посилання", + }, + edit: { + text: "Редагувати посилання", + tooltip: "Редагувати", + }, + open: { + tooltip: "Відкрити в новій вкладці", + }, + form: { + title_placeholder: "Редагувати заголовок", + url_placeholder: "Редагувати URL", + }, + }, + generic: { + ctrl_shortcut: "Ctrl", + }, }; diff --git a/packages/react/package.json b/packages/react/package.json index e95501c5d3..3877ca49c2 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -58,6 +58,7 @@ "@floating-ui/react": "^0.26.4", "@tiptap/core": "^2.7.1", "@tiptap/react": "^2.7.1", + "@tiptap/pm": "^2.7.1", "lodash.merge": "^4.6.2", "react-icons": "^5.2.1" }, diff --git a/packages/react/src/components/Comments/Comment.tsx b/packages/react/src/components/Comments/Comment.tsx index d3f66ec113..2b095a0be2 100644 --- a/packages/react/src/components/Comments/Comment.tsx +++ b/packages/react/src/components/Comments/Comment.tsx @@ -3,7 +3,7 @@ import { CommentData, ThreadData, mergeCSSClasses } from "@blocknote/core"; import data from "@emoji-mart/data"; import Picker from "@emoji-mart/react"; -import type { EmojiData } from "emoji-mart"; + import { ComponentPropsWithoutRef, MouseEvent, @@ -196,9 +196,7 @@ export const Comment = ({ - onReactionSelect(emoji.native) - } + onEmojiSelect={(emoji: any) => onReactionSelect(emoji.native)} theme={blockNoteContext?.colorSchemePreference} /> @@ -312,7 +310,7 @@ export const Comment = ({ variant={"form-popover"}> + onEmojiSelect={(emoji: any) => onReactionSelect(emoji.native) } theme={blockNoteContext?.colorSchemePreference} diff --git a/packages/react/src/schema/ReactBlockSpec.tsx b/packages/react/src/schema/ReactBlockSpec.tsx index 05673edb44..11cb684d46 100644 --- a/packages/react/src/schema/ReactBlockSpec.tsx +++ b/packages/react/src/schema/ReactBlockSpec.tsx @@ -19,8 +19,8 @@ import { StyleSchema, wrapInBlockStructure, } from "@blocknote/core"; +import { NodeView } from "@tiptap/pm/view"; import { - NodeView, NodeViewProps, NodeViewWrapper, ReactNodeViewRenderer, @@ -192,7 +192,7 @@ export function createReactBlockSpec< { className: "bn-react-node-view-renderer", } - )(props) as NodeView; + )(props) as NodeView; if (blockConfig.isSelectable === false) { applyNonSelectableBlockFix(nodeView, this.editor); diff --git a/playground/src/examples.gen.tsx b/playground/src/examples.gen.tsx index 515530851f..42de417238 100644 --- a/playground/src/examples.gen.tsx +++ b/playground/src/examples.gen.tsx @@ -1137,6 +1137,29 @@ "pathFromRoot": "examples/07-collaboration", "slug": "collaboration" } + }, + { + "projectSlug": "comments", + "fullSlug": "collaboration/comments", + "pathFromRoot": "examples/07-collaboration/04-comments", + "config": { + "playground": true, + "docs": true, + "author": "jakelazaroff", + "tags": [ + "Advanced", + "Saving/Loading", + "Collaboration" + ], + "dependencies": { + "@y-sweet/react": "^0.6.3" + } as any + }, + "title": "Comments & Threads", + "group": { + "pathFromRoot": "examples/07-collaboration", + "slug": "collaboration" + } } ] }, From 7ae2b7f8c4bb6c03377752c0a09aaa631545f076 Mon Sep 17 00:00:00 2001 From: yousefed Date: Tue, 11 Feb 2025 14:01:11 +0100 Subject: [PATCH 030/144] cleanup + sample --- .../07-collaboration/02-liveblocks/App.tsx | 27 ++-- .../{Editor.tsx => Editor.tsx.bak} | 0 .../04-comments/.bnexample.json | 3 +- examples/07-collaboration/04-comments/App.tsx | 115 +++++++++++++++++- .../04-comments/tsconfig.json | 14 +-- packages/ariakit/src/index.tsx | 2 + packages/ariakit/src/toolbar/Toolbar.tsx | 2 + .../ariakit/src/toolbar/ToolbarButton.tsx | 5 +- packages/core/src/editor/BlockNoteEditor.ts | 16 +++ .../core/src/editor/BlockNoteExtensions.ts | 17 ++- .../src/extensions/Comments/CommentsPlugin.ts | 42 ++----- ...hreadStore.ts.bak => TipTapThreadStore.ts} | 0 .../Comments/threadstore/YjsThreadStore.ts | 3 - .../core/src/extensions/Comments/types.ts | 6 - packages/core/src/index.ts | 26 ++-- packages/core/src/models/User.ts | 5 + packages/mantine/src/comments/Comment.tsx | 2 +- .../react/src/components/Comments/Comment.tsx | 36 +++--- .../components/Comments/FloatingComposer.tsx | 11 +- .../Comments/FloatingComposerController.tsx | 8 +- .../react/src/components/Comments/Thread.tsx | 13 +- .../src/components/Comments/useThreads.ts | 7 +- .../react/src/components/Comments/useUsers.ts | 7 +- .../DefaultButtons/AddCommentButton.tsx | 3 +- packages/shadcn/src/index.tsx | 2 + packages/shadcn/src/toolbar/Toolbar.tsx | 8 ++ 26 files changed, 272 insertions(+), 108 deletions(-) rename examples/07-collaboration/02-liveblocks/{Editor.tsx => Editor.tsx.bak} (100%) rename packages/core/src/extensions/Comments/threadstore/{TipTapThreadStore.ts.bak => TipTapThreadStore.ts} (100%) create mode 100644 packages/core/src/models/User.ts diff --git a/examples/07-collaboration/02-liveblocks/App.tsx b/examples/07-collaboration/02-liveblocks/App.tsx index 8db1114620..6d2d99f25c 100644 --- a/examples/07-collaboration/02-liveblocks/App.tsx +++ b/examples/07-collaboration/02-liveblocks/App.tsx @@ -1,22 +1,23 @@ -import "@blocknote/core/fonts/inter.css"; -import "@blocknote/mantine/style.css"; +// import "@blocknote/core/fonts/inter.css"; +// import "@blocknote/mantine/style.css"; -// .. in imports are temporary for development (vite setup) +// // .. in imports are temporary for development (vite setup) -// Need to be imported before @liveblocks/react-blocknote styles -import "@liveblocks/react-ui/styles.css"; -// Need to be imported after @liveblocks/react-ui styles -import "@liveblocks/react-blocknote/styles.css"; +// // Need to be imported before @liveblocks/react-blocknote styles +// import "@liveblocks/react-ui/styles.css"; +// // Need to be imported after @liveblocks/react-ui styles +// import "@liveblocks/react-blocknote/styles.css"; -import { Editor } from "./Editor.jsx"; -import { Room } from "./Room.jsx"; -import "./globals.css"; +// import { Editor } from "./Editor.jsx"; +// import { Room } from "./Room.jsx"; +// import "./globals.css"; export default function App() { // Renders the editor instance using a React component. return ( - - - +
TODO
+ // + // + // ); } diff --git a/examples/07-collaboration/02-liveblocks/Editor.tsx b/examples/07-collaboration/02-liveblocks/Editor.tsx.bak similarity index 100% rename from examples/07-collaboration/02-liveblocks/Editor.tsx rename to examples/07-collaboration/02-liveblocks/Editor.tsx.bak diff --git a/examples/07-collaboration/04-comments/.bnexample.json b/examples/07-collaboration/04-comments/.bnexample.json index ec9d562b0e..631b4fb54e 100644 --- a/examples/07-collaboration/04-comments/.bnexample.json +++ b/examples/07-collaboration/04-comments/.bnexample.json @@ -4,6 +4,7 @@ "author": "jakelazaroff", "tags": ["Advanced", "Saving/Loading", "Collaboration"], "dependencies": { - "@y-sweet/react": "^0.6.3" + "@y-sweet/react": "^0.6.3", + "@mantine/core": "^7.10.1" } } diff --git a/examples/07-collaboration/04-comments/App.tsx b/examples/07-collaboration/04-comments/App.tsx index 65d9d8e5aa..99b972c1ca 100644 --- a/examples/07-collaboration/04-comments/App.tsx +++ b/examples/07-collaboration/04-comments/App.tsx @@ -1,12 +1,121 @@ "use client"; +import { DefaultThreadStoreAuth, User, YjsThreadStore } from "@blocknote/core"; import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/mantine/style.css"; import { useCreateBlockNote } from "@blocknote/react"; +import { Select } from "@mantine/core"; +import { useYDoc, useYjsProvider, YDocProvider } from "@y-sweet/react"; +import { useMemo, useState } from "react"; -import "@blocknote/mantine/style.css"; +const colors = [ + "#958DF1", + "#F98181", + "#FBBC88", + "#FAF594", + "#70CFF8", + "#94FADB", + "#B9F18D", +]; + +const getRandomElement = (list: any[]) => + list[Math.floor(Math.random() * list.length)]; + +const getRandomColor = () => getRandomElement(colors); +type MyUserType = User & { + role: "editor" | "comment"; +}; + +const HARDCODED_USERS: MyUserType[] = [ + { + id: "1", + username: "John Doe", + avatarUrl: "https://placehold.co/100x100?text=John", + role: "editor", + }, + { + id: "2", + username: "Jane Doe", + avatarUrl: "https://placehold.co/100x100?text=Jane", + role: "comment", + }, +]; + +// The resolveUsers function fetches information about your users +// (e.g. their name, avatar, etc.). Usually, you'd fetch this from your +// own database or user management system. +// Here, we just return the hardcoded users. +async function resolveUsers(userIds: string[]) { + // fake a (slow) network request + await new Promise((resolve) => setTimeout(resolve, 1000)); + + return HARDCODED_USERS.filter((user) => userIds.includes(user.id)); +} + +// This follows the Y-Sweet demo to setup a collabotive editor +// (but of course, you also use other collaboration providers) export default function App() { - const editor = useCreateBlockNote({}); + const docId = "my-blocknote-document-with-comments"; + + return ( + + + + ); +} + +function Document() { + const [user, setUser] = useState(HARDCODED_USERS[0]); + const provider = useYjsProvider(); + + // take the Y.Doc collaborative document from Y-Sweet + const doc = useYDoc(); + + // setup the thread store which stores / and syncs thread / comment data + const threadStore = useMemo(() => { + return new YjsThreadStore( + user.id, + doc.getMap("threads"), + new DefaultThreadStoreAuth(user.id, user.role) + ); + }, [doc, user]); + + // setup the editor with comments and collaboration + const editor = useCreateBlockNote( + { + resolveUsers, + comments: { + threadStore, + }, + collaboration: { + provider, + fragment: doc.getXmlFragment("blocknote"), + user: { color: getRandomColor(), name: user.username }, + }, + }, + [user, threadStore] + ); - return ; + // TODO: make sure comment button / formatting toolbar appears for comment-only users + return ( +
+ ({ @@ -111,6 +124,9 @@ function Document() { label: user.username + " (" + user.role + ")", }))} onChange={(value) => { + if (!value) { + return; + } setUser(HARDCODED_USERS.find((user) => user.id === value)!); }} value={user.id} diff --git a/packages/core/src/extensions/Comments/threadstore/TipTapThreadStore.ts b/packages/core/src/extensions/Comments/threadstore/TipTapThreadStore.ts.bak similarity index 100% rename from packages/core/src/extensions/Comments/threadstore/TipTapThreadStore.ts rename to packages/core/src/extensions/Comments/threadstore/TipTapThreadStore.ts.bak diff --git a/packages/core/src/extensions/Comments/threadstore/YjsThreadStore.test.ts b/packages/core/src/extensions/Comments/threadstore/YjsThreadStore.test.ts index fd7aafe120..f936a87977 100644 --- a/packages/core/src/extensions/Comments/threadstore/YjsThreadStore.test.ts +++ b/packages/core/src/extensions/Comments/threadstore/YjsThreadStore.test.ts @@ -1,6 +1,5 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; import * as Y from "yjs"; -import { BlockNoteEditor } from "../../../editor/BlockNoteEditor.js"; import { CommentBody } from "../types.js"; import { DefaultThreadStoreAuth } from "./DefaultThreadStoreAuth.js"; import { YjsThreadStore } from "./YjsThreadStore.js"; @@ -15,7 +14,6 @@ describe("YjsThreadStore", () => { let store: YjsThreadStore; let doc: Y.Doc; let threadsYMap: Y.Map; - let editor: BlockNoteEditor; beforeEach(() => { // Reset mocks and create fresh instances @@ -23,9 +21,8 @@ describe("YjsThreadStore", () => { mockUuidCounter = 0; doc = new Y.Doc(); threadsYMap = doc.getMap("threads"); - editor = {} as BlockNoteEditor; + store = new YjsThreadStore( - editor, "test-user", threadsYMap, new DefaultThreadStoreAuth("test-user", "editor") diff --git a/packages/core/src/extensions/Comments/userstore/UserStore.ts b/packages/core/src/extensions/Comments/userstore/UserStore.ts index 4cad7326e0..e1f1a42fed 100644 --- a/packages/core/src/extensions/Comments/userstore/UserStore.ts +++ b/packages/core/src/extensions/Comments/userstore/UserStore.ts @@ -1,5 +1,5 @@ +import { User } from "../../../models/User.js"; import { EventEmitter } from "../../../util/EventEmitter.js"; -import { User } from "../types.js"; export class UserStore extends EventEmitter { private userCache: Map = new Map(); From e93b53b20949f22ff2b891494096dad7d4f29528 Mon Sep 17 00:00:00 2001 From: yousefed Date: Tue, 11 Feb 2025 14:09:28 +0100 Subject: [PATCH 032/144] fix lint --- packages/core/src/api/nodeConversions/nodeToBlock.ts | 2 +- packages/core/src/extensions/Comments/CommentsPlugin.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/core/src/api/nodeConversions/nodeToBlock.ts b/packages/core/src/api/nodeConversions/nodeToBlock.ts index 455499b01d..7f42115a5c 100644 --- a/packages/core/src/api/nodeConversions/nodeToBlock.ts +++ b/packages/core/src/api/nodeConversions/nodeToBlock.ts @@ -106,7 +106,7 @@ export function contentNodeToInlineContent< if (node.type.name !== "link" && node.type.name !== "text") { if (!inlineContentSchema[node.type.name]) { - // @eslint-disable-next-line no-console + // eslint-disable-next-line no-console console.warn("unrecognized inline content type", node.type.name); return; } diff --git a/packages/core/src/extensions/Comments/CommentsPlugin.ts b/packages/core/src/extensions/Comments/CommentsPlugin.ts index 3be64ca780..2d9a425e0b 100644 --- a/packages/core/src/extensions/Comments/CommentsPlugin.ts +++ b/packages/core/src/extensions/Comments/CommentsPlugin.ts @@ -168,6 +168,7 @@ export class CommentsPlugin extends EventEmitter { }); }, 600); + // eslint-disable-next-line @typescript-eslint/no-this-alias const self = this; this.plugin = new Plugin({ From be9dd4647717c607c112491159b184c7016d7534 Mon Sep 17 00:00:00 2001 From: yousefed Date: Tue, 11 Feb 2025 14:10:32 +0100 Subject: [PATCH 033/144] disable liveblocks for now --- .../07-collaboration/02-liveblocks/{Room.tsx => Room.tsx.bak} | 0 .../02-liveblocks/{Threads.tsx => Threads.tsx.bak} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename examples/07-collaboration/02-liveblocks/{Room.tsx => Room.tsx.bak} (100%) rename examples/07-collaboration/02-liveblocks/{Threads.tsx => Threads.tsx.bak} (100%) diff --git a/examples/07-collaboration/02-liveblocks/Room.tsx b/examples/07-collaboration/02-liveblocks/Room.tsx.bak similarity index 100% rename from examples/07-collaboration/02-liveblocks/Room.tsx rename to examples/07-collaboration/02-liveblocks/Room.tsx.bak diff --git a/examples/07-collaboration/02-liveblocks/Threads.tsx b/examples/07-collaboration/02-liveblocks/Threads.tsx.bak similarity index 100% rename from examples/07-collaboration/02-liveblocks/Threads.tsx rename to examples/07-collaboration/02-liveblocks/Threads.tsx.bak From 3e93d1eaf1d28c5c31db04e8f84f7b8e56cb5cfe Mon Sep 17 00:00:00 2001 From: yousefed Date: Tue, 11 Feb 2025 14:18:27 +0100 Subject: [PATCH 034/144] lint --- packages/react/src/schema/@util/CoreMarkView.ts | 2 ++ packages/react/src/schema/@util/ReactMarkViewRenderer.tsx | 2 ++ 2 files changed, 4 insertions(+) diff --git a/packages/react/src/schema/@util/CoreMarkView.ts b/packages/react/src/schema/@util/CoreMarkView.ts index 84c4facde9..92085cbc2e 100644 --- a/packages/react/src/schema/@util/CoreMarkView.ts +++ b/packages/react/src/schema/@util/CoreMarkView.ts @@ -12,6 +12,8 @@ import type { MarkViewDOMSpec, } from "./CoreMarkViewOptions"; +/* eslint-disable curly */ + export class CoreMarkView implements MarkView { dom: HTMLElement; contentDOM: HTMLElement | undefined; diff --git a/packages/react/src/schema/@util/ReactMarkViewRenderer.tsx b/packages/react/src/schema/@util/ReactMarkViewRenderer.tsx index 57d7d4d3c0..a156059f0a 100644 --- a/packages/react/src/schema/@util/ReactMarkViewRenderer.tsx +++ b/packages/react/src/schema/@util/ReactMarkViewRenderer.tsx @@ -5,6 +5,8 @@ import { CoreMarkView } from "./CoreMarkView.js"; import type { MarkViewContext } from "./markViewContext.js"; import type { ReactMarkViewComponent } from "./ReactMarkViewOptions.js"; +/* eslint-disable curly */ + export class ReactMarkView extends CoreMarkView { // implements ReactRenderer // key: string = nanoid(); From 40269a52d3905f7f678e0184eebc44d85ad3ed53 Mon Sep 17 00:00:00 2001 From: yousefed Date: Tue, 11 Feb 2025 15:06:11 +0100 Subject: [PATCH 035/144] fix linkify warning + make toggle editable comment --- packages/core/src/editor/BlockNoteExtensions.ts | 7 ++++++- packages/react/src/components/Comments/Comment.tsx | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/core/src/editor/BlockNoteExtensions.ts b/packages/core/src/editor/BlockNoteExtensions.ts index eb52fecc94..f5afe06ec6 100644 --- a/packages/core/src/editor/BlockNoteExtensions.ts +++ b/packages/core/src/editor/BlockNoteExtensions.ts @@ -145,6 +145,8 @@ export const getBlockNoteExtensions = < return ret; }; +let LINKIFY_INITIALIZED = false; + /** * Get all the Tiptap extensions BlockNote is configured with by default */ @@ -182,7 +184,8 @@ const getTipTapExtensions = < inclusive: false, }).configure({ defaultProtocol: DEFAULT_LINK_PROTOCOL, - protocols: VALID_LINK_PROTOCOLS, + // only call this once if we have multiple editors installed. Or fix https://github.com/ueberdosis/tiptap/issues/5450 + protocols: LINKIFY_INITIALIZED ? [] : VALID_LINK_PROTOCOLS, }), ...Object.values(opts.styleSpecs).map((styleSpec) => { return styleSpec.implementation.mark.configure({ @@ -260,6 +263,8 @@ const getTipTapExtensions = < CommentMark, ]; + LINKIFY_INITIALIZED = true; + if (opts.collaboration) { tiptapExtensions.push(...createCollaborationExtensions(opts.collaboration)); } else { diff --git a/packages/react/src/components/Comments/Comment.tsx b/packages/react/src/components/Comments/Comment.tsx index 16aa5b381a..bfdf94ad3c 100644 --- a/packages/react/src/components/Comments/Comment.tsx +++ b/packages/react/src/components/Comments/Comment.tsx @@ -65,6 +65,7 @@ export interface CommentProps extends ComponentPropsWithoutRef<"div"> { * The Comment component displays a single comment with actions, * a reaction list and an editor when editing. * + * It's generally used in the `Thread` component for comments that have already been created. */ export const Comment = ({ comment, @@ -273,7 +274,7 @@ export const Comment = ({ <> ( <> {showReactions && comment.reactions.length > 0 && ( From 62a8944b9393d80994e7b2be562b58372515a61a Mon Sep 17 00:00:00 2001 From: yousefed Date: Tue, 11 Feb 2025 15:19:53 +0100 Subject: [PATCH 036/144] fix content reset bug --- packages/core/src/editor/BlockNoteEditor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/editor/BlockNoteEditor.ts b/packages/core/src/editor/BlockNoteEditor.ts index 02a3511cf3..464888e762 100644 --- a/packages/core/src/editor/BlockNoteEditor.ts +++ b/packages/core/src/editor/BlockNoteEditor.ts @@ -508,7 +508,7 @@ export class BlockNoteEditor< this.headless = newOptions._headless; const collaborationEnabled = - "Collaboration" in this.extensions || + "collaboration" in this.extensions || "liveblocksExtension" in this.extensions; if (collaborationEnabled && newOptions.initialContent) { From 22fdde675a00e88c5b61a39de808e845764bd49f Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Tue, 11 Feb 2025 22:46:11 +0100 Subject: [PATCH 037/144] Implemented PR feedback --- packages/mantine/src/badge/Badge.tsx | 1 + packages/mantine/src/style.css | 96 +++++++++---------- .../react/src/components/Comments/Comment.tsx | 18 +++- 3 files changed, 58 insertions(+), 57 deletions(-) diff --git a/packages/mantine/src/badge/Badge.tsx b/packages/mantine/src/badge/Badge.tsx index 213a05316f..ba9d711443 100644 --- a/packages/mantine/src/badge/Badge.tsx +++ b/packages/mantine/src/badge/Badge.tsx @@ -34,6 +34,7 @@ export const Badge = forwardRef< checked={isSelected === true} onClick={onClick} variant={"light"} + icon={<>} ref={ref}> {icon} {text} diff --git a/packages/mantine/src/style.css b/packages/mantine/src/style.css index 5692c4dbea..852aafc7e3 100644 --- a/packages/mantine/src/style.css +++ b/packages/mantine/src/style.css @@ -566,7 +566,7 @@ justify-content: space-between; } -/* TODO: Clean up */ +/* Comment styling */ .bn-mantine .bn-thread { background-color: var(--bn-colors-menu-background); border: var(--bn-border); @@ -576,20 +576,6 @@ overflow: visible; } -.bn-mantine .bn-thread .bn-grid-suggestion-menu { - gap: 0; - max-height: 300px; - padding: 6px; -} - -.bn-mantine .bn-thread .bn-grid-suggestion-menu-item { - border-radius: var(--bn-border-radius-small); - font-size: 20px; - height: 32px; - margin: 0; - width: 32px; -} - .bn-mantine .bn-thread-comments { border-bottom: var(--bn-border); } @@ -600,43 +586,7 @@ justify-content: flex-end; } -.bn-mantine .bn-badge-group { - display: flex; - gap: 4px; - justify-content: flex-start; - width: 100%; -} - -.bn-mantine .bn-badge { - flex-grow: 0; -} - -.bn-mantine .bn-badge .mantine-Chip-label { - - padding: 0 8px; -} - -.bn-mantine .bn-badge .mantine-Chip-label:not([data-checked="true"]) { - background-color: var(--bn-colors-menu-background); - border: var(--bn-border); - color: var(--bn-colors-menu-text); -} - -.bn-mantine .bn-badge .mantine-Chip-label > span:not(.mantine-Chip-iconWrapper) { - display: inline-flex; - gap: 4px; -} - -.bn-mantine .bn-badge .mantine-Chip-label > span:not(.mantine-Chip-iconWrapper) > span { - align-items: center; - display: inline-flex; - justify-content: center; -} - -.bn-mantine .bn-badge .mantine-Chip-iconWrapper { - display: none; -} - +/* Comment action toolbar styling */ .bn-mantine .bn-action-toolbar { align-self: flex-end; background-color: var(--bn-colors-menu-background); @@ -677,4 +627,44 @@ .bn-mantine .bn-action-toolbar .mantine-Menu-itemLabel { font-size: 12px; -} \ No newline at end of file +} + +/* Badge styling */ +.bn-mantine .bn-badge-group { + display: flex; + gap: 4px; + justify-content: flex-start; + width: 100%; +} + +.bn-mantine .bn-badge { + flex-grow: 0; +} + +.bn-mantine .bn-badge .mantine-Chip-label { + padding: 0 8px; +} + +.bn-mantine .bn-badge .mantine-Chip-label:not([data-checked="true"]) { + background-color: var(--bn-colors-menu-background); + border: var(--bn-border); + color: var(--bn-colors-menu-text); +} + +.bn-mantine .bn-badge .mantine-Chip-label > span:not(.mantine-Chip-iconWrapper) { + display: inline-flex; + gap: 4px; +} + +.bn-mantine .bn-badge .mantine-Chip-label > span:not(.mantine-Chip-iconWrapper) > span { + align-items: center; + display: inline-flex; + justify-content: center; +} + +/* We need to get rid of the checked icon - you can set the icon prop to an +empty element (<>), but even so Mantine leaves extra space for the icon, so +we just don't display it in CSS instead. */ +.bn-mantine .bn-badge .mantine-Chip-iconWrapper { + display: none; +} diff --git a/packages/react/src/components/Comments/Comment.tsx b/packages/react/src/components/Comments/Comment.tsx index 0b2cb1c863..d4184372cf 100644 --- a/packages/react/src/components/Comments/Comment.tsx +++ b/packages/react/src/components/Comments/Comment.tsx @@ -1,7 +1,7 @@ "use client"; import { CommentData, ThreadData, mergeCSSClasses } from "@blocknote/core"; -import data from "@emoji-mart/data"; +import { type EmojiMartData } from "@emoji-mart/data"; import Picker from "@emoji-mart/react"; import type { EmojiData } from "emoji-mart"; import { @@ -30,6 +30,12 @@ import { CommentEditor } from "./CommentEditor.js"; import { schema } from "./schema.js"; import { useUser } from "./useUsers.js"; +let data: EmojiMartData | undefined; +async function initData() { + const fullData = await import("@emoji-mart/data"); + data = fullData.default as EmojiMartData; +} + /** * Liveblocks, but changed: * - removed attachments @@ -339,6 +345,12 @@ export const Comment = ({ ? editor.comments!.store.auth.canUnresolveThread(thread) : editor.comments!.store.auth.canResolveThread(thread)); + if (!data) { + // TODO: Is this safe? we should technically wait for this to load before + // rendering emoji picker + initData(); + } + if (showActions && !isEditing) { actions = ( onReactionSelect(reaction.emoji)} mainTooltip={"Reacted by"} - secondaryTooltip={`${reaction.usersIds.map( - (userId) => userId + "\n" - )}`} + secondaryTooltip={`${reaction.usersIds.join("\n")}`} /> ))} From c0d1bd59a80d507c64f3e859d843e75d2da2eeb9 Mon Sep 17 00:00:00 2001 From: yousefed Date: Wed, 12 Feb 2025 06:13:16 +0100 Subject: [PATCH 038/144] fix placeholder --- packages/core/src/editor/BlockNoteEditor.ts | 14 ++- .../Placeholder/PlaceholderPlugin.ts | 113 ++++++++++-------- packages/core/src/i18n/locales/en.ts | 3 +- .../src/components/Comments/CommentEditor.tsx | 19 +-- .../react/src/components/Comments/Thread.tsx | 4 +- 5 files changed, 80 insertions(+), 73 deletions(-) diff --git a/packages/core/src/editor/BlockNoteEditor.ts b/packages/core/src/editor/BlockNoteEditor.ts index 464888e762..f4fd808c33 100644 --- a/packages/core/src/editor/BlockNoteEditor.ts +++ b/packages/core/src/editor/BlockNoteEditor.ts @@ -135,7 +135,7 @@ export type BlockNoteEditorOptions< /** * @deprecated, provide placeholders via dictionary instead */ - placeholders: Record; + placeholders: Record; /** * An object containing attributes that should be added to HTML elements of the editor. @@ -1274,6 +1274,18 @@ export class BlockNoteEditor< return posToDOMRect(this.prosemirrorView, from, to); } + public get isEmpty() { + const doc = this.document; + // Note: only works for paragraphs as default blocks (but for now this is default in blocknote) + // checking prosemirror directly might be faster + return ( + doc.length === 0 || + (doc.length === 1 && + doc[0].type === "paragraph" && + (doc[0].content as any).length === 0) + ); + } + public openSuggestionMenu( triggerCharacter: string, pluginState?: { diff --git a/packages/core/src/extensions/Placeholder/PlaceholderPlugin.ts b/packages/core/src/extensions/Placeholder/PlaceholderPlugin.ts index db1681e89d..ee1b592e64 100644 --- a/packages/core/src/extensions/Placeholder/PlaceholderPlugin.ts +++ b/packages/core/src/extensions/Placeholder/PlaceholderPlugin.ts @@ -9,7 +9,7 @@ export class PlaceholderPlugin { public readonly plugin: Plugin; constructor( editor: BlockNoteEditor, - placeholders: Record + placeholders: Record ) { this.plugin = new Plugin({ key: PLUGIN_KEY, @@ -17,10 +17,12 @@ export class PlaceholderPlugin { const uniqueEditorSelector = `placeholder-selector-${v4()}`; view.dom.classList.add(uniqueEditorSelector); const styleEl = document.createElement("style"); + const nonce = editor._tiptapEditor.options.injectNonce; if (nonce) { styleEl.setAttribute("nonce", nonce); } + if (editor.prosemirrorView?.root instanceof ShadowRoot) { editor.prosemirrorView.root.append(styleEl); } else { @@ -29,54 +31,50 @@ export class PlaceholderPlugin { const styleSheet = styleEl.sheet!; - const getBaseSelector = (additionalSelectors = "") => + const getSelector = (additionalSelectors = "") => `.${uniqueEditorSelector} .bn-block-content${additionalSelectors} .bn-inline-content:has(> .ProseMirror-trailingBreak:only-child):before`; - const getSelector = ( - blockType: string | "default", - mustBeFocused = true - ) => { - const mustBeFocusedSelector = mustBeFocused - ? `[data-is-empty-and-focused]` - : ``; + try { + // FIXME: the names "default" and "emptyDocument" are hardcoded + const { + default: defaultPlaceholder, + emptyDocument: emptyPlaceholder, + ...rest + } = placeholders; - if (blockType === "default") { - return getBaseSelector(mustBeFocusedSelector); - } - - const blockTypeSelector = `[data-content-type="${blockType}"]`; - return getBaseSelector(mustBeFocusedSelector + blockTypeSelector); - }; + // add block specific placeholders + for (const [blockType, placeholder] of Object.entries(rest)) { + const blockTypeSelector = `[data-content-type="${blockType}"]`; - for (const [blockType, placeholder] of Object.entries(placeholders)) { - const mustBeFocused = blockType === "default"; - - try { styleSheet.insertRule( - `${getSelector( - blockType, - mustBeFocused - )} { content: ${JSON.stringify(placeholder)}; }` - ); - - // For some reason, the placeholders which show when the block is focused - // take priority over ones which show depending on block type, so we need - // to make sure the block specific ones are also used when the block is - // focused. - if (!mustBeFocused) { - styleSheet.insertRule( - `${getSelector(blockType, true)} { content: ${JSON.stringify( - placeholder - )}; }` - ); - } - } catch (e) { - // eslint-disable-next-line no-console - console.warn( - `Failed to insert placeholder CSS rule - this is likely due to the browser not supporting certain CSS pseudo-element selectors (:has, :only-child:, or :before)`, - e + `${getSelector(blockTypeSelector)} { content: ${JSON.stringify( + placeholder + )}; }` ); } + + const onlyBlockSelector = `[data-is-only-empty-block]`; + const mustBeFocusedSelector = `[data-is-empty-and-focused]`; + + // placeholder for when there's only one empty block + styleSheet.insertRule( + `${getSelector(onlyBlockSelector)} { content: ${JSON.stringify( + emptyPlaceholder + )}; }` + ); + + // placeholder for default blocks, only when the cursor is in the block (mustBeFocused) + styleSheet.insertRule( + `${getSelector(mustBeFocusedSelector)} { content: ${JSON.stringify( + defaultPlaceholder + )}; }` + ); + } catch (e) { + // eslint-disable-next-line no-console + console.warn( + `Failed to insert placeholder CSS rule - this is likely due to the browser not supporting certain CSS pseudo-element selectors (:has, :only-child:, or :before)`, + e + ); } return { @@ -90,7 +88,6 @@ export class PlaceholderPlugin { }; }, props: { - // TODO: maybe also add placeholder for empty document ("e.g.: start writing..") decorations: (state) => { const { doc, selection } = state; @@ -107,20 +104,32 @@ export class PlaceholderPlugin { return; } - const $pos = selection.$anchor; - const node = $pos.parent; + const decs = []; - if (node.content.size > 0) { - return null; + // decoration for when there's only one empty block + // positions are hardcoded for now + if (state.doc.content.size === 6) { + decs.push( + Decoration.node(2, 4, { + "data-is-only-empty-block": "true", + }) + ); } - const before = $pos.before(); + const $pos = selection.$anchor; + const node = $pos.parent; + + if (node.content.size === 0) { + const before = $pos.before(); - const dec = Decoration.node(before, before + node.nodeSize, { - "data-is-empty-and-focused": "true", - }); + decs.push( + Decoration.node(before, before + node.nodeSize, { + "data-is-empty-and-focused": "true", + }) + ); + } - return DecorationSet.create(doc, [dec]); + return DecorationSet.create(doc, decs); }, }, }); diff --git a/packages/core/src/i18n/locales/en.ts b/packages/core/src/i18n/locales/en.ts index 9ef4b4e800..816dd7e855 100644 --- a/packages/core/src/i18n/locales/en.ts +++ b/packages/core/src/i18n/locales/en.ts @@ -129,7 +129,8 @@ export const en = { bulletListItem: "List", numberedListItem: "List", checkListItem: "List", - }, + emptyDocument: undefined, + } as Record, file_blocks: { image: { add_button_text: "Add image", diff --git a/packages/react/src/components/Comments/CommentEditor.tsx b/packages/react/src/components/Comments/CommentEditor.tsx index 1ce75da9bc..0e7617b1ff 100644 --- a/packages/react/src/components/Comments/CommentEditor.tsx +++ b/packages/react/src/components/Comments/CommentEditor.tsx @@ -4,21 +4,6 @@ import { useComponentsContext } from "../../editor/ComponentsContext.js"; import { useEditorChange } from "../../hooks/useEditorChange.js"; import { schema } from "./schema.js"; -function isDocumentEmpty( - editor: BlockNoteEditor< - typeof schema.blockSchema, - typeof schema.inlineContentSchema, - typeof schema.styleSchema - > -) { - return ( - editor.document.length === 0 || - (editor.document.length === 1 && - editor.document[0].type === "paragraph" && - editor.document[0].content.length === 0) - ); -} - /** * The CommentEditor component displays an editor for creating or editing a comment. * Currently, we also use the non-editable version for displaying a comment. @@ -42,12 +27,12 @@ export const CommentEditor = (props: { >; }) => { const [isFocused, setIsFocused] = useState(false); - const [isEmpty, setIsEmpty] = useState(isDocumentEmpty(props.editor)); + const [isEmpty, setIsEmpty] = useState(props.editor.isEmpty); const components = useComponentsContext()!; useEditorChange(() => { - setIsEmpty(isDocumentEmpty(props.editor)); + setIsEmpty(props.editor.isEmpty); }, props.editor); const onFocus = useCallback(() => { diff --git a/packages/react/src/components/Comments/Thread.tsx b/packages/react/src/components/Comments/Thread.tsx index c1d1614896..f2945c9c76 100644 --- a/packages/react/src/components/Comments/Thread.tsx +++ b/packages/react/src/components/Comments/Thread.tsx @@ -92,8 +92,8 @@ export const Thread = ({ dictionary: { ...dict, placeholders: { - ...dict.placeholders, - default: "Add comment...", // TODO: only for empty doc + emptyDocument: "Add comment...", + default: undefined, }, }, schema, From a71fc69dcf04fbcc6ace141113d93a485d78182d Mon Sep 17 00:00:00 2001 From: yousefed Date: Wed, 12 Feb 2025 06:52:19 +0100 Subject: [PATCH 039/144] clean comment editor --- packages/mantine/src/comments/Editor.tsx | 24 ++++++++++++++--- .../react/src/components/Comments/schema.ts | 26 ++++++++++++++++--- 2 files changed, 44 insertions(+), 6 deletions(-) diff --git a/packages/mantine/src/comments/Editor.tsx b/packages/mantine/src/comments/Editor.tsx index 6bd4805b8f..857b417109 100644 --- a/packages/mantine/src/comments/Editor.tsx +++ b/packages/mantine/src/comments/Editor.tsx @@ -1,5 +1,10 @@ import { assertEmpty } from "@blocknote/core"; -import { ComponentProps } from "@blocknote/react"; +import { + ComponentProps, + FormattingToolbar, + FormattingToolbarController, + getFormattingToolbarItems, +} from "@blocknote/react"; import { forwardRef, useEffect } from "react"; import { BlockNoteView } from "../BlockNoteView.js"; @@ -28,10 +33,23 @@ export const Editor = forwardRef< slashMenu={false} tableHandles={false} filePanel={false} + formattingToolbar={false} editable={editable} ref={ref} onFocus={onFocus} - onBlur={onBlur} - /> + onBlur={onBlur}> + + ); }); + +const CustomFormattingToolbar = () => { + const items = getFormattingToolbarItems([]).filter( + (el) => el.key !== "nestBlockButton" && el.key !== "unnestBlockButton" + ); + return ( + {items} + ); +}; diff --git a/packages/react/src/components/Comments/schema.ts b/packages/react/src/components/Comments/schema.ts index c9078380d7..7389f35793 100644 --- a/packages/react/src/components/Comments/schema.ts +++ b/packages/react/src/components/Comments/schema.ts @@ -1,8 +1,28 @@ -import { BlockNoteSchema, defaultBlockSpecs } from "@blocknote/core"; +import { + BlockNoteSchema, + createBlockSpecFromStronglyTypedTiptapNode, + createStronglyTypedTiptapNode, + defaultBlockSpecs, + defaultStyleSpecs, +} from "@blocknote/core"; -// TODO: disable props on paragraph +// this is quite convoluted. we'll clean this up when we make +// it easier to extend / customize the default blocks +const paragraph = createBlockSpecFromStronglyTypedTiptapNode( + createStronglyTypedTiptapNode<"paragraph", "inline*">( + defaultBlockSpecs.paragraph.implementation.node.config as any + ), + // disable default props on paragraph (such as textalignment and colors) + {} +); + +// remove textColor, backgroundColor from styleSpecs +const { textColor, backgroundColor, ...styleSpecs } = defaultStyleSpecs; + +// the schema to use for comments export const schema = BlockNoteSchema.create({ blockSpecs: { - paragraph: defaultBlockSpecs.paragraph, + paragraph, }, + styleSpecs, }); From 4d498de8c75d88fd8f14da1e7bbbd852b7b1f877 Mon Sep 17 00:00:00 2001 From: yousefed Date: Wed, 12 Feb 2025 06:54:11 +0100 Subject: [PATCH 040/144] fix build --- packages/core/src/editor/BlockNoteEditor.ts | 5 ++++- packages/core/src/editor/BlockNoteExtensions.ts | 5 ++++- .../core/src/extensions/Placeholder/PlaceholderPlugin.ts | 5 ++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/core/src/editor/BlockNoteEditor.ts b/packages/core/src/editor/BlockNoteEditor.ts index f4fd808c33..9147e56740 100644 --- a/packages/core/src/editor/BlockNoteEditor.ts +++ b/packages/core/src/editor/BlockNoteEditor.ts @@ -135,7 +135,10 @@ export type BlockNoteEditorOptions< /** * @deprecated, provide placeholders via dictionary instead */ - placeholders: Record; + placeholders: Record< + string | "default" | "emptyDocument", + string | undefined + >; /** * An object containing attributes that should be added to HTML elements of the editor. diff --git a/packages/core/src/editor/BlockNoteExtensions.ts b/packages/core/src/editor/BlockNoteExtensions.ts index f5afe06ec6..cd04a78c81 100644 --- a/packages/core/src/editor/BlockNoteExtensions.ts +++ b/packages/core/src/editor/BlockNoteExtensions.ts @@ -72,7 +72,10 @@ type ExtensionOptions< animations: boolean; tableHandles: boolean; dropCursor: (opts: any) => Plugin; - placeholders: Record; + placeholders: Record< + string | "default" | "emptyDocument", + string | undefined + >; tabBehavior?: "prefer-navigate-ui" | "prefer-indent"; sideMenuDetection: "viewport" | "editor"; comments?: { diff --git a/packages/core/src/extensions/Placeholder/PlaceholderPlugin.ts b/packages/core/src/extensions/Placeholder/PlaceholderPlugin.ts index ee1b592e64..ebb4c27b18 100644 --- a/packages/core/src/extensions/Placeholder/PlaceholderPlugin.ts +++ b/packages/core/src/extensions/Placeholder/PlaceholderPlugin.ts @@ -9,7 +9,10 @@ export class PlaceholderPlugin { public readonly plugin: Plugin; constructor( editor: BlockNoteEditor, - placeholders: Record + placeholders: Record< + string | "default" | "emptyDocument", + string | undefined + > ) { this.plugin = new Plugin({ key: PLUGIN_KEY, From c5a6a0c20f76db658c34c5f9f7319ebc5ff52f43 Mon Sep 17 00:00:00 2001 From: yousefed Date: Wed, 12 Feb 2025 07:14:49 +0100 Subject: [PATCH 041/144] fix placeholders --- packages/react/src/components/Comments/Comment.tsx | 3 +-- packages/react/src/components/Comments/FloatingComposer.tsx | 3 +-- packages/react/src/components/Comments/Thread.tsx | 1 - 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/react/src/components/Comments/Comment.tsx b/packages/react/src/components/Comments/Comment.tsx index e5144bcbdf..6cba12355c 100644 --- a/packages/react/src/components/Comments/Comment.tsx +++ b/packages/react/src/components/Comments/Comment.tsx @@ -92,8 +92,7 @@ export const Comment = ({ dictionary: { ...dict, placeholders: { - ...dict.placeholders, - default: "Edit comment...", // TODO: only for empty doc + emptyDocument: "Edit comment...", }, }, schema, diff --git a/packages/react/src/components/Comments/FloatingComposer.tsx b/packages/react/src/components/Comments/FloatingComposer.tsx index ac63a4dd52..9f4133caf3 100644 --- a/packages/react/src/components/Comments/FloatingComposer.tsx +++ b/packages/react/src/components/Comments/FloatingComposer.tsx @@ -30,8 +30,7 @@ export function FloatingComposer() { dictionary: { ...dict, placeholders: { - ...dict.placeholders, - default: "Write a comment...", // TODO: only for empty doc + emptyDocument: "Write a comment...", }, }, schema, diff --git a/packages/react/src/components/Comments/Thread.tsx b/packages/react/src/components/Comments/Thread.tsx index f2945c9c76..44fb4a04b6 100644 --- a/packages/react/src/components/Comments/Thread.tsx +++ b/packages/react/src/components/Comments/Thread.tsx @@ -93,7 +93,6 @@ export const Thread = ({ ...dict, placeholders: { emptyDocument: "Add comment...", - default: undefined, }, }, schema, From 3f7828dced14905bde59962155ac285282b98c58 Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Thu, 13 Feb 2025 09:57:40 +0100 Subject: [PATCH 042/144] - Adjusted comment spacing - Changed emoji icon - Changed "More actions" dropdown alignment - Made toolbar button tooltips with spaces not become multi-line - Made "More actions" and emoji buttons hide tooltips when dropdown is open - Made emoji picker close when emoji is picked --- packages/mantine/src/style.css | 8 +- .../mantine/src/toolbar/ToolbarButton.tsx | 62 +++-- .../react/src/components/Comments/Comment.tsx | 215 +++++++++++------- .../react/src/editor/ComponentsContext.tsx | 11 +- 4 files changed, 195 insertions(+), 101 deletions(-) diff --git a/packages/mantine/src/style.css b/packages/mantine/src/style.css index 852aafc7e3..ba85f3602d 100644 --- a/packages/mantine/src/style.css +++ b/packages/mantine/src/style.css @@ -549,7 +549,6 @@ color: var(--bn-colors-tooltip-text); padding: 4px 10px; text-align: center; - white-space: pre-wrap; } /* Additional menu styles */ @@ -578,6 +577,13 @@ .bn-mantine .bn-thread-comments { border-bottom: var(--bn-border); + display: flex; + flex-direction: column; + gap: 1rem; +} + +.bn-mantine .bn-thread-comment { + gap: 0.25rem; } .bn-mantine .bn-comment-actions-wrapper { diff --git a/packages/mantine/src/toolbar/ToolbarButton.tsx b/packages/mantine/src/toolbar/ToolbarButton.tsx index 29dea8ee48..9ba2eb05e1 100644 --- a/packages/mantine/src/toolbar/ToolbarButton.tsx +++ b/packages/mantine/src/toolbar/ToolbarButton.tsx @@ -8,10 +8,10 @@ import { import { assertEmpty, isSafari } from "@blocknote/core"; import { ComponentProps } from "@blocknote/react"; -import { forwardRef } from "react"; +import { forwardRef, useMemo } from "react"; export const TooltipContent = (props: { - mainTooltip: string; + mainTooltip?: string; secondaryTooltip?: string; }) => ( @@ -52,17 +52,10 @@ export const ToolbarButton = forwardRef( // assertEmpty in this case is only used at typescript level, not runtime level assertEmpty(rest, false); - return ( - - }> - {/*Creates an ActionIcon instead of a Button if only an icon is provided as content.*/} - {children ? ( + const button = useMemo( + () => + // Creates an ActionIcon instead of a Button if only an icon is provided as content. + children ? ( ( aria-pressed={isSelected} data-selected={isSelected || undefined} data-test={ - mainTooltip.slice(0, 1).toLowerCase() + - mainTooltip.replace(/\s+/g, "").slice(1) + mainTooltip + ? mainTooltip.slice(0, 1).toLowerCase() + + mainTooltip.replace(/\s+/g, "").slice(1) + : undefined } size={variant === "compact" ? "compact-xs" : "xs"} disabled={isDisabled || false} @@ -101,8 +96,10 @@ export const ToolbarButton = forwardRef( aria-pressed={isSelected} data-selected={isSelected || undefined} data-test={ - mainTooltip.slice(0, 1).toLowerCase() + - mainTooltip.replace(/\s+/g, "").slice(1) + mainTooltip + ? mainTooltip.slice(0, 1).toLowerCase() + + mainTooltip.replace(/\s+/g, "").slice(1) + : undefined } size={variant === "compact" ? 20 : 30} disabled={isDisabled || false} @@ -110,7 +107,36 @@ export const ToolbarButton = forwardRef( {...rest}> {icon} - )} + ), + [ + children, + className, + icon, + isDisabled, + isSelected, + label, + mainTooltip, + onClick, + ref, + rest, + variant, + ] + ); + + if (!mainTooltip) { + return button; + } + + return ( + + }> + {button} ); } diff --git a/packages/react/src/components/Comments/Comment.tsx b/packages/react/src/components/Comments/Comment.tsx index 6cba12355c..bf6ea94c4e 100644 --- a/packages/react/src/components/Comments/Comment.tsx +++ b/packages/react/src/components/Comments/Comment.tsx @@ -16,7 +16,7 @@ import { RiCheckFill, RiDeleteBinFill, RiEditFill, - RiEmotionFill, + RiEmotionLine, RiMoreFill, } from "react-icons/ri"; @@ -103,6 +103,14 @@ export const Comment = ({ const Components = useComponentsContext()!; const [isEditing, setEditing] = useState(false); + // We need to keep a state so we can close the picker after selecting an emoji + const [showReactionsPicker, setShowReactionsPicker] = useState< + undefined | "toolbar" | "badge" + >(undefined); + + const [moreActionsTooltip, setMoreActionsTooltip] = useState< + string | undefined + >("More actions"); const editor = useBlockNoteEditor(); @@ -201,18 +209,33 @@ export const Comment = ({ {canAddReaction && ( - + - + mainTooltip={showReactionsPicker ? undefined : "Add reaction"} + variant="compact" + onClick={(e) => { + setShowReactionsPicker( + showReactionsPicker === "toolbar" ? undefined : "toolbar" + ); + // Needed as the Picker component's onClickOutside handler + // fires immediately after otherwise, preventing the popover + // from opening. + e.preventDefault(); + e.stopPropagation(); + }}> + onReactionSelect(emoji.native)} + onEmojiSelect={(emoji: EmojiData) => { + onReactionSelect(emoji.native); + setShowReactionsPicker(undefined); + }} + onClickOutside={() => setShowReactionsPicker(undefined)} theme={blockNoteContext?.colorSchemePreference} /> @@ -235,10 +258,14 @@ export const Comment = ({ ))} {(canDeleteComment || canEditComment) && ( - + + setMoreActionsTooltip(open ? undefined : "More actions") + }> @@ -286,78 +313,108 @@ export const Comment = ({ ( - <> - {showReactions && comment.reactions.length > 0 && ( - - {comment.reactions.map((reaction) => ( - onReactionSelect(reaction.emoji)} - mainTooltip={"Reacted by"} - secondaryTooltip={`${reaction.userIds.join("\n")}`} - /> - ))} - - - 0) || isEditing + ? ({ isEmpty }) => ( + <> + {showReactions && comment.reactions.length > 0 && ( + + {comment.reactions.map((reaction) => ( + onReactionSelect(reaction.emoji)} + mainTooltip={"Reacted by"} + secondaryTooltip={`${reaction.userIds.join( + "\n" + )}`} + /> + ))} + + + } + onClick={(e) => { + setShowReactionsPicker( + showReactionsPicker === "badge" + ? undefined + : "badge" + ); + // Needed as the Picker component's onClickOutside handler + // fires immediately after otherwise, preventing the popover + // from opening. + e.preventDefault(); + e.stopPropagation(); + }} + mainTooltip={ + showReactionsPicker === "badge" + ? undefined + : "Add reaction" + } + /> + + + { + onReactionSelect(emoji.native); + setShowReactionsPicker(undefined); + }} + onClickOutside={() => + setShowReactionsPicker(undefined) + } + theme={blockNoteContext?.colorSchemePreference} + /> + + + + )} + {isEditing && ( + } - /> - - - - onReactionSelect(emoji.native) - } - theme={blockNoteContext?.colorSchemePreference} - /> - - - - )} - {isEditing && ( - - - Save - - - Cancel - - - )} - - )} + "bn-action-toolbar", + "bn-comment-actions" + )}> + + Save + + + Cancel + + + )} + + ) + : undefined + } /> ) : ( diff --git a/packages/react/src/editor/ComponentsContext.tsx b/packages/react/src/editor/ComponentsContext.tsx index 6d5194ab23..899d027986 100644 --- a/packages/react/src/editor/ComponentsContext.tsx +++ b/packages/react/src/editor/ComponentsContext.tsx @@ -23,7 +23,7 @@ type ToolbarRootType = { type ToolbarButtonType = { className?: string; - mainTooltip: string; + mainTooltip?: string; secondaryTooltip?: string; icon?: ReactNode; onClick?: (e: MouseEvent) => void; @@ -192,7 +192,7 @@ export type ComponentProps = { isSelected?: boolean; mainTooltip?: string; secondaryTooltip?: string; - onClick?: () => void; + onClick?: (event: React.MouseEvent) => void; }; Group: { className?: string; @@ -219,7 +219,12 @@ export type ComponentProps = { Menu: { Root: { sub?: boolean; - position?: "top" | "right" | "bottom" | "left"; + position?: + | "top" + | "right" + | "bottom" + | "left" + | `${"top" | "right" | "bottom" | "left"}-${"start" | "end"}`; onOpenChange?: (open: boolean) => void; children?: ReactNode; }; From d2c9f340f14acfde321eba01f8d378f4c52178c5 Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Fri, 14 Feb 2025 02:43:52 +0100 Subject: [PATCH 043/144] Implemented PR feedback --- packages/mantine/src/badge/Badge.tsx | 13 +- packages/mantine/src/menu/Menu.tsx | 77 +++++----- packages/mantine/src/popover/Popover.tsx | 27 +++- .../mantine/src/toolbar/ToolbarButton.tsx | 131 ++++++++---------- .../react/src/components/Comments/Comment.tsx | 86 ++---------- .../react/src/editor/ComponentsContext.tsx | 12 +- 6 files changed, 153 insertions(+), 193 deletions(-) diff --git a/packages/mantine/src/badge/Badge.tsx b/packages/mantine/src/badge/Badge.tsx index ba9d711443..4bacafa9f1 100644 --- a/packages/mantine/src/badge/Badge.tsx +++ b/packages/mantine/src/badge/Badge.tsx @@ -1,13 +1,15 @@ -import { forwardRef } from "react"; -import { ComponentProps } from "@blocknote/react"; import { Chip as MantineChip, Group as MantineGroup, Tooltip as MantineTooltip, } from "@mantine/core"; + import { assertEmpty } from "@blocknote/core"; +import { ComponentProps } from "@blocknote/react"; +import { forwardRef, useContext } from "react"; import { TooltipContent } from "../toolbar/ToolbarButton.js"; +import { PopoverContext } from "../popover/Popover.js"; export const Badge = forwardRef< HTMLInputElement, @@ -28,6 +30,8 @@ export const Badge = forwardRef< // assertEmpty in this case is only used at typescript level, not runtime level assertEmpty(rest, false); + const { isOpened } = useContext(PopoverContext); + const badge = ( ); - if (!mainTooltip) { + if (!mainTooltip || isOpened) { return badge; } @@ -54,8 +58,7 @@ export const Badge = forwardRef< mainTooltip={mainTooltip} secondaryTooltip={secondaryTooltip} /> - } - multiline> + }> {badge} ); diff --git a/packages/mantine/src/menu/Menu.tsx b/packages/mantine/src/menu/Menu.tsx index df656b1d2e..d81eae84a5 100644 --- a/packages/mantine/src/menu/Menu.tsx +++ b/packages/mantine/src/menu/Menu.tsx @@ -16,6 +16,8 @@ import { } from "react"; import { HiChevronRight } from "react-icons/hi"; +import { PopoverContext } from "../popover/Popover.js"; + const SubMenuContext = createContext< | { onMenuMouseOver: () => void; @@ -106,32 +108,33 @@ const SubMenu = forwardRef< }, []); return ( - - - onOpenChange?.(false)} - onOpen={() => onOpenChange?.(true)} - position={position}> - {children} - - - + + + + + {children} + + + + ); }); @@ -140,19 +143,25 @@ export const Menu = (props: ComponentProps["Generic"]["Menu"]["Root"]) => { assertEmpty(rest); + const [isOpened, setIsOpened] = useState(false); + if (sub) { return ; } return ( - onOpenChange?.(false)} - onOpen={() => onOpenChange?.(true)} - position={position}> - {children} - + + { + setIsOpened(open); + onOpenChange?.(open); + }} + position={position}> + {children} + + ); }; diff --git a/packages/mantine/src/popover/Popover.tsx b/packages/mantine/src/popover/Popover.tsx index e2bef2f265..316ae94730 100644 --- a/packages/mantine/src/popover/Popover.tsx +++ b/packages/mantine/src/popover/Popover.tsx @@ -6,19 +6,36 @@ import { import { assertEmpty } from "@blocknote/core"; import { ComponentProps } from "@blocknote/react"; -import { forwardRef } from "react"; +import { createContext, forwardRef, useState } from "react"; + +export const PopoverContext = createContext<{ + isOpened: boolean; +}>({ + isOpened: false, +}); export const Popover = ( props: ComponentProps["Generic"]["Popover"]["Root"] ) => { - const { children, opened, position, ...rest } = props; + const { opened, onOpenChange, position, children, ...rest } = props; assertEmpty(rest); + const [isOpened, setIsOpened] = useState(false); + return ( - - {children} - + + { + setIsOpened(open); + onOpenChange?.(open); + }} + position={position}> + {children} + + ); }; diff --git a/packages/mantine/src/toolbar/ToolbarButton.tsx b/packages/mantine/src/toolbar/ToolbarButton.tsx index 9ba2eb05e1..49d078eee5 100644 --- a/packages/mantine/src/toolbar/ToolbarButton.tsx +++ b/packages/mantine/src/toolbar/ToolbarButton.tsx @@ -8,10 +8,12 @@ import { import { assertEmpty, isSafari } from "@blocknote/core"; import { ComponentProps } from "@blocknote/react"; -import { forwardRef, useMemo } from "react"; +import { forwardRef, useContext } from "react"; + +import { PopoverContext } from "../popover/Popover.js"; export const TooltipContent = (props: { - mainTooltip?: string; + mainTooltip: string; secondaryTooltip?: string; }) => ( @@ -52,78 +54,63 @@ export const ToolbarButton = forwardRef( // assertEmpty in this case is only used at typescript level, not runtime level assertEmpty(rest, false); - const button = useMemo( - () => - // Creates an ActionIcon instead of a Button if only an icon is provided as content. - children ? ( - { - if (isSafari()) { - (e.currentTarget as HTMLButtonElement).focus(); - } - }} - onClick={onClick} - aria-pressed={isSelected} - data-selected={isSelected || undefined} - data-test={ - mainTooltip - ? mainTooltip.slice(0, 1).toLowerCase() + - mainTooltip.replace(/\s+/g, "").slice(1) - : undefined - } - size={variant === "compact" ? "compact-xs" : "xs"} - disabled={isDisabled || false} - ref={ref} - {...rest}> - {children} - - ) : ( - { - if (isSafari()) { - (e.currentTarget as HTMLButtonElement).focus(); - } - }} - onClick={onClick} - aria-pressed={isSelected} - data-selected={isSelected || undefined} - data-test={ - mainTooltip - ? mainTooltip.slice(0, 1).toLowerCase() + - mainTooltip.replace(/\s+/g, "").slice(1) - : undefined - } - size={variant === "compact" ? 20 : 30} - disabled={isDisabled || false} - ref={ref} - {...rest}> - {icon} - - ), - [ - children, - className, - icon, - isDisabled, - isSelected, - label, - mainTooltip, - onClick, - ref, - rest, - variant, - ] + const { isOpened } = useContext(PopoverContext); + + const button = children ? ( + { + if (isSafari()) { + (e.currentTarget as HTMLButtonElement).focus(); + } + }} + onClick={onClick} + aria-pressed={isSelected} + data-selected={isSelected || undefined} + data-test={ + mainTooltip + ? mainTooltip.slice(0, 1).toLowerCase() + + mainTooltip.replace(/\s+/g, "").slice(1) + : undefined + } + size={variant === "compact" ? "compact-xs" : "xs"} + disabled={isDisabled || false} + ref={ref} + {...rest}> + {children} + + ) : ( + { + if (isSafari()) { + (e.currentTarget as HTMLButtonElement).focus(); + } + }} + onClick={onClick} + aria-pressed={isSelected} + data-selected={isSelected || undefined} + data-test={ + mainTooltip + ? mainTooltip.slice(0, 1).toLowerCase() + + mainTooltip.replace(/\s+/g, "").slice(1) + : undefined + } + size={variant === "compact" ? 20 : 30} + disabled={isDisabled || false} + ref={ref} + {...rest}> + {icon} + ); - if (!mainTooltip) { + if (!mainTooltip || isOpened) { return button; } diff --git a/packages/react/src/components/Comments/Comment.tsx b/packages/react/src/components/Comments/Comment.tsx index bf6ea94c4e..6471c5dd0a 100644 --- a/packages/react/src/components/Comments/Comment.tsx +++ b/packages/react/src/components/Comments/Comment.tsx @@ -1,9 +1,7 @@ "use client"; import { CommentData, ThreadData, mergeCSSClasses } from "@blocknote/core"; -import { type EmojiMartData } from "@emoji-mart/data"; import Picker from "@emoji-mart/react"; -import type { EmojiData } from "emoji-mart"; import { ComponentPropsWithoutRef, MouseEvent, @@ -29,12 +27,6 @@ import { CommentEditor } from "./CommentEditor.js"; import { schema } from "./schema.js"; import { useUser } from "./useUsers.js"; -let data: EmojiMartData | undefined; -async function initData() { - const fullData = await import("@emoji-mart/data"); - data = fullData.default as EmojiMartData; -} - export interface CommentProps extends ComponentPropsWithoutRef<"div"> { /** * The comment to display. @@ -103,14 +95,6 @@ export const Comment = ({ const Components = useComponentsContext()!; const [isEditing, setEditing] = useState(false); - // We need to keep a state so we can close the picker after selecting an emoji - const [showReactionsPicker, setShowReactionsPicker] = useState< - undefined | "toolbar" | "badge" - >(undefined); - - const [moreActionsTooltip, setMoreActionsTooltip] = useState< - string | undefined - >("More actions"); const editor = useBlockNoteEditor(); @@ -198,44 +182,24 @@ export const Comment = ({ ? threadStore.auth.canUnresolveThread(thread) : threadStore.auth.canResolveThread(thread)); - if (!data) { - // TODO: Is this safe? we should technically wait for this to load before - // rendering emoji picker - initData(); - } - if (showActions && !isEditing) { actions = ( {canAddReaction && ( - + { - setShowReactionsPicker( - showReactionsPicker === "toolbar" ? undefined : "toolbar" - ); - // Needed as the Picker component's onClickOutside handler - // fires immediately after otherwise, preventing the popover - // from opening. - e.preventDefault(); - e.stopPropagation(); - }}> + mainTooltip="Add reaction" + variant="compact"> { - onReactionSelect(emoji.native); - setShowReactionsPicker(undefined); - }} - onClickOutside={() => setShowReactionsPicker(undefined)} + onEmojiSelect={(emoji: { native: string }) => + onReactionSelect(emoji.native) + } theme={blockNoteContext?.colorSchemePreference} /> @@ -258,14 +222,10 @@ export const Comment = ({ ))} {(canDeleteComment || canEditComment) && ( - - setMoreActionsTooltip(open ? undefined : "More actions") - }> + @@ -342,8 +302,7 @@ export const Comment = ({ )}`} /> ))} - + } - onClick={(e) => { - setShowReactionsPicker( - showReactionsPicker === "badge" - ? undefined - : "badge" - ); - // Needed as the Picker component's onClickOutside handler - // fires immediately after otherwise, preventing the popover - // from opening. - e.preventDefault(); - e.stopPropagation(); - }} - mainTooltip={ - showReactionsPicker === "badge" - ? undefined - : "Add reaction" - } + mainTooltip="Add reaction" /> { - onReactionSelect(emoji.native); - setShowReactionsPicker(undefined); - }} - onClickOutside={() => - setShowReactionsPicker(undefined) + onEmojiSelect={(emoji: { native: string }) => + onReactionSelect(emoji.native) } theme={blockNoteContext?.colorSchemePreference} /> diff --git a/packages/react/src/editor/ComponentsContext.tsx b/packages/react/src/editor/ComponentsContext.tsx index 899d027986..106b255323 100644 --- a/packages/react/src/editor/ComponentsContext.tsx +++ b/packages/react/src/editor/ComponentsContext.tsx @@ -219,13 +219,13 @@ export type ComponentProps = { Menu: { Root: { sub?: boolean; + onOpenChange?: (open: boolean) => void; position?: | "top" | "right" | "bottom" | "left" | `${"top" | "right" | "bottom" | "left"}-${"start" | "end"}`; - onOpenChange?: (open: boolean) => void; children?: ReactNode; }; Divider: { @@ -256,9 +256,15 @@ export type ComponentProps = { }; Popover: { Root: { - children?: ReactNode; opened?: boolean; - position?: "top" | "right" | "bottom" | "left"; + onOpenChange?: (open: boolean) => void; + position?: + | "top" + | "right" + | "bottom" + | "left" + | `${"top" | "right" | "bottom" | "left"}-${"start" | "end"}`; + children?: ReactNode; }; Content: { className?: string; From 6df6886fa93d95a43774d5cdbc2f639f331f5dc5 Mon Sep 17 00:00:00 2001 From: yousefed Date: Fri, 14 Feb 2025 15:03:09 +0100 Subject: [PATCH 044/144] fix build --- examples/07-collaboration/04-comments/package.json | 3 ++- .../07-collaboration/04-comments/tsconfig.json | 14 ++++++++++---- package-lock.json | 1 + packages/ariakit/src/popover/Popover.tsx | 1 + packages/ariakit/src/toolbar/ToolbarButton.tsx | 2 ++ packages/shadcn/src/popover/popover.tsx | 1 + playground/src/examples.gen.tsx | 3 ++- 7 files changed, 19 insertions(+), 6 deletions(-) diff --git a/examples/07-collaboration/04-comments/package.json b/examples/07-collaboration/04-comments/package.json index d3d3c0a3d9..0d1e47a364 100644 --- a/examples/07-collaboration/04-comments/package.json +++ b/examples/07-collaboration/04-comments/package.json @@ -18,7 +18,8 @@ "@blocknote/shadcn": "latest", "react": "^18.3.1", "react-dom": "^18.3.1", - "@y-sweet/react": "^0.6.3" + "@y-sweet/react": "^0.6.3", + "@mantine/core": "^7.10.1" }, "devDependencies": { "@types/react": "^18.0.25", diff --git a/examples/07-collaboration/04-comments/tsconfig.json b/examples/07-collaboration/04-comments/tsconfig.json index 4a76cf4c7d..1bd8ab3c57 100644 --- a/examples/07-collaboration/04-comments/tsconfig.json +++ b/examples/07-collaboration/04-comments/tsconfig.json @@ -3,7 +3,11 @@ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, - "lib": ["DOM", "DOM.Iterable", "ESNext"], + "lib": [ + "DOM", + "DOM.Iterable", + "ESNext" + ], "allowJs": false, "skipLibCheck": true, "esModuleInterop": false, @@ -18,8 +22,10 @@ "jsx": "react-jsx", "composite": true }, - "include": ["."], - "references": [ + "include": [ + "." + ], + "__ADD_FOR_LOCAL_DEV_references": [ { "path": "../../../packages/core/" }, @@ -27,4 +33,4 @@ "path": "../../../packages/react/" } ] -} +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index b19791cb29..360aa3ea95 100644 --- a/package-lock.json +++ b/package-lock.json @@ -65,6 +65,7 @@ } }, "../liveblocks/packages/liveblocks-react-blocknote": { + "name": "@liveblocks/react-blocknote", "version": "2.15.2", "license": "Apache-2.0", "dependencies": { diff --git a/packages/ariakit/src/popover/Popover.tsx b/packages/ariakit/src/popover/Popover.tsx index 3165eeac48..0a92d59e4e 100644 --- a/packages/ariakit/src/popover/Popover.tsx +++ b/packages/ariakit/src/popover/Popover.tsx @@ -41,6 +41,7 @@ export const Popover = ( ) => { const { children, opened, position, ...rest } = props; + // @ts-ignore TODO assertEmpty(rest); return ( diff --git a/packages/ariakit/src/toolbar/ToolbarButton.tsx b/packages/ariakit/src/toolbar/ToolbarButton.tsx index 3a58e23c3c..c62021f3cf 100644 --- a/packages/ariakit/src/toolbar/ToolbarButton.tsx +++ b/packages/ariakit/src/toolbar/ToolbarButton.tsx @@ -59,7 +59,9 @@ export const ToolbarButton = forwardRef( aria-pressed={isSelected} data-selected={isSelected ? "true" : undefined} data-test={ + // @ts-ignore TODO props.mainTooltip.slice(0, 1).toLowerCase() + + // @ts-ignore TODO props.mainTooltip.replace(/\s+/g, "").slice(1) } // size={"xs"} diff --git a/packages/shadcn/src/popover/popover.tsx b/packages/shadcn/src/popover/popover.tsx index dacb3f4d39..9b44e5b665 100644 --- a/packages/shadcn/src/popover/popover.tsx +++ b/packages/shadcn/src/popover/popover.tsx @@ -15,6 +15,7 @@ export const Popover = ( ...rest } = props; + // @ts-ignore TODO assertEmpty(rest); const ShadCNComponents = useShadCNComponentsContext()!; diff --git a/playground/src/examples.gen.tsx b/playground/src/examples.gen.tsx index 42de417238..e0d3787a1c 100644 --- a/playground/src/examples.gen.tsx +++ b/playground/src/examples.gen.tsx @@ -1152,7 +1152,8 @@ "Collaboration" ], "dependencies": { - "@y-sweet/react": "^0.6.3" + "@y-sweet/react": "^0.6.3", + "@mantine/core": "^7.10.1" } as any }, "title": "Comments & Threads", From 5b7e3d4e0624a2e4f1e09e4eaac014b58ac6ff79 Mon Sep 17 00:00:00 2001 From: yousefed Date: Fri, 14 Feb 2025 15:59:35 +0100 Subject: [PATCH 045/144] implement TipTapThreadStore --- package-lock.json | 32 ++ packages/core/package.json | 3 +- .../Comments/threadstore/TipTapThreadStore.ts | 283 ++++++++++++++++++ .../threadstore/TipTapThreadStore.ts.bak | 7 - packages/core/src/index.ts | 13 +- 5 files changed, 324 insertions(+), 14 deletions(-) create mode 100644 packages/core/src/extensions/Comments/threadstore/TipTapThreadStore.ts delete mode 100644 packages/core/src/extensions/Comments/threadstore/TipTapThreadStore.ts.bak diff --git a/package-lock.json b/package-lock.json index 360aa3ea95..f079761547 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4354,6 +4354,31 @@ "react": ">= 16" } }, + "node_modules/@hocuspocus/common": { + "version": "2.15.2", + "resolved": "https://registry.npmjs.org/@hocuspocus/common/-/common-2.15.2.tgz", + "integrity": "sha512-wU1wxXNnQQMXyeL3mdSDYiQsm/r/QyJVjjQhF7sUBrLnjdsN7bA1cvfcSvJBr1ymrMSeYRmUL3UlQmEHEOaP7w==", + "dev": true, + "dependencies": { + "lib0": "^0.2.87" + } + }, + "node_modules/@hocuspocus/provider": { + "version": "2.15.2", + "resolved": "https://registry.npmjs.org/@hocuspocus/provider/-/provider-2.15.2.tgz", + "integrity": "sha512-mdBurviyaUd7bQx4vMIE39WqRJDTpfFelHOVXr7w/jA8G1E7K7lxQ9/DacSrbg+9o8s+1z1+SerZiUjaToaBJg==", + "dev": true, + "dependencies": { + "@hocuspocus/common": "^2.15.2", + "@lifeomic/attempt": "^3.0.2", + "lib0": "^0.2.87", + "ws": "^8.17.1" + }, + "peerDependencies": { + "y-protocols": "^1.0.6", + "yjs": "^13.6.8" + } + }, "node_modules/@hookform/resolvers": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.6.0.tgz", @@ -5924,6 +5949,12 @@ "node": "^14.15.0 || >=16.0.0" } }, + "node_modules/@lifeomic/attempt": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@lifeomic/attempt/-/attempt-3.1.0.tgz", + "integrity": "sha512-QZqem4QuAnAyzfz+Gj5/+SLxqwCAw2qmt7732ZXodr6VDWGeYLG6w1i/vYLa55JQM9wRuBKLmXmiZ2P0LtE5rw==", + "dev": true + }, "node_modules/@liveblocks/client": { "resolved": "../liveblocks/packages/liveblocks-client", "link": true @@ -31739,6 +31770,7 @@ "yjs": "^13.6.15" }, "devDependencies": { + "@hocuspocus/provider": "^2.15.2", "@types/emoji-mart": "^3.0.14", "@types/hast": "^3.0.0", "@types/uuid": "^8.3.4", diff --git a/packages/core/package.json b/packages/core/package.json index c804f1ae4f..498ac969f3 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -85,7 +85,7 @@ "prosemirror-state": "^1.4.3", "prosemirror-tables": "^1.6.1", "prosemirror-transform": "^1.10.2", - "prosemirror-view":"^1.37.1", + "prosemirror-view": "^1.37.1", "rehype-format": "^5.0.0", "rehype-parse": "^8.0.4", "rehype-remark": "^9.1.2", @@ -105,6 +105,7 @@ "@types/emoji-mart": "^3.0.14", "@types/hast": "^3.0.0", "@types/uuid": "^8.3.4", + "@hocuspocus/provider": "^2.15.2", "eslint": "^8.10.0", "jsdom": "^25.0.1", "prettier": "^2.7.1", diff --git a/packages/core/src/extensions/Comments/threadstore/TipTapThreadStore.ts b/packages/core/src/extensions/Comments/threadstore/TipTapThreadStore.ts new file mode 100644 index 0000000000..e4d4b3ec9e --- /dev/null +++ b/packages/core/src/extensions/Comments/threadstore/TipTapThreadStore.ts @@ -0,0 +1,283 @@ +import type { + TCollabComment, + TCollabThread, + TiptapCollabProvider, +} from "@hocuspocus/provider"; +import { + CommentBody, + CommentData, + CommentReactionData, + ThreadData, +} from "../types.js"; +import { ThreadStore } from "./ThreadStore.js"; +import { ThreadStoreAuth } from "./ThreadStoreAuth.js"; + +type ReactionAsTiptapData = { + emoji: string; + createdAt: number; + userId: string; +}; + +export class TiptapThreadStore extends ThreadStore { + constructor( + private readonly userId: string, + private readonly provider: TiptapCollabProvider, + auth: ThreadStoreAuth // TODO: use? + ) { + super(auth); + } + + /** + * Creates a new thread with an initial comment. + */ + public async createThread(options: { + initialComment: { + body: CommentBody; + metadata?: any; + }; + metadata?: any; + }): Promise { + let thread = this.provider.createThread({ + data: options.metadata, + }); + + thread = this.provider.addComment(thread.id, { + content: options.initialComment.body, + data: { + metadata: options.initialComment.metadata, + userId: this.userId, + }, + }); + + return this.tiptapThreadToThreadData(thread); + } + + /** + * Adds a comment to a thread. + */ + public async addComment(options: { + comment: { + body: CommentBody; + metadata?: any; + }; + threadId: string; + }): Promise { + const thread = this.provider.addComment(options.threadId, { + content: options.comment.body, + data: { + metadata: options.comment.metadata, + userId: this.userId, + }, + }); + + return this.tiptapCommentToCommentData( + thread.comments[thread.comments.length - 1] + ); + } + + /** + * Updates a comment in a thread. + */ + public async updateComment(options: { + comment: { + body: CommentBody; + metadata?: any; + }; + threadId: string; + commentId: string; + }) { + const comment = this.provider.getThreadComment( + options.threadId, + options.commentId, + true + ); + + if (!comment) { + throw new Error("Comment not found"); + } + + this.provider.updateComment(options.threadId, options.commentId, { + content: options.comment.body, + data: { + ...comment.data, + metadata: options.comment.metadata, + }, + }); + } + + private tiptapCommentToCommentData(comment: TCollabComment): CommentData { + const reactions: CommentReactionData[] = []; + + for (const reaction of (comment.data?.reactions || + []) as ReactionAsTiptapData[]) { + const existingReaction = reactions.find( + (r) => r.emoji === reaction.emoji + ); + if (existingReaction) { + existingReaction.userIds.push(reaction.userId); + existingReaction.createdAt = new Date( + Math.min(existingReaction.createdAt.getTime(), reaction.createdAt) + ); + } else { + reactions.push({ + emoji: reaction.emoji, + createdAt: new Date(reaction.createdAt), + userIds: [reaction.userId], + }); + } + } + + return { + type: "comment", + id: comment.id, + body: comment.content, + metadata: comment.data?.metadata, + userId: comment.data?.userId, + createdAt: new Date(comment.createdAt), + updatedAt: new Date(comment.updatedAt), + reactions, + }; + } + + private tiptapThreadToThreadData(thread: TCollabThread): ThreadData { + return { + type: "thread", + id: thread.id, + comments: thread.comments.map((comment) => + this.tiptapCommentToCommentData(comment) + ), + resolved: !!thread.resolvedAt, + metadata: thread.data?.metadata, + createdAt: new Date(thread.createdAt), + updatedAt: new Date(thread.updatedAt), + }; + } + + /** + * Deletes a comment from a thread. + */ + public async deleteComment(options: { threadId: string; commentId: string }) { + this.provider.deleteComment(options.threadId, options.commentId); + } + + /** + * Deletes a thread. + */ + public async deleteThread(options: { threadId: string }) { + this.provider.deleteThread(options.threadId); + } + + /** + * Marks a thread as resolved. + */ + public async resolveThread(options: { threadId: string }) { + this.provider.updateThread(options.threadId, { + resolvedAt: new Date().toISOString(), + }); + } + + /** + * Marks a thread as unresolved. + */ + public async unresolveThread(options: { threadId: string }) { + this.provider.updateThread(options.threadId, { + resolvedAt: null, + }); + } + + /** + * Adds a reaction to a comment. + * + * Auth: should be possible by anyone with comment access + */ + public async addReaction(options: { + threadId: string; + commentId: string; + emoji: string; + }) { + const comment = this.provider.getThreadComment( + options.threadId, + options.commentId, + true + ); + + if (!comment) { + throw new Error("Comment not found"); + } + + this.provider.updateComment(options.threadId, options.commentId, { + data: { + ...comment.data, + reactions: [ + ...((comment.data?.reactions || []) as ReactionAsTiptapData[]), + { + emoji: options.emoji, + createdAt: Date.now(), + userId: this.userId, + }, + ], + }, + }); + } + + /** + * Deletes a reaction from a comment. + * + * Auth: should be possible by the reaction author + */ + public async deleteReaction(options: { + threadId: string; + commentId: string; + emoji: string; + }) { + const comment = this.provider.getThreadComment( + options.threadId, + options.commentId, + true + ); + + if (!comment) { + throw new Error("Comment not found"); + } + + this.provider.updateComment(options.threadId, options.commentId, { + data: { + ...comment.data, + reactions: ( + (comment.data?.reactions || []) as ReactionAsTiptapData[] + ).filter( + (reaction) => + reaction.emoji !== options.emoji && reaction.userId !== this.userId + ), + }, + }); + } + + public getThread(threadId: string): ThreadData { + const thread = this.provider.getThread(threadId); + + if (!thread) { + throw new Error("Thread not found"); + } + + return this.tiptapThreadToThreadData(thread); + } + + public getThreads(): Map { + return new Map( + this.provider + .getThreads() + .map((thread) => [thread.id, this.tiptapThreadToThreadData(thread)]) + ); + } + + public subscribe(cb: (threads: Map) => void): () => void { + const newCb = () => { + cb(this.getThreads()); + }; + this.provider.watchThreads(newCb); + return () => { + this.provider.unwatchThreads(newCb); + }; + } +} diff --git a/packages/core/src/extensions/Comments/threadstore/TipTapThreadStore.ts.bak b/packages/core/src/extensions/Comments/threadstore/TipTapThreadStore.ts.bak deleted file mode 100644 index 8d068a37c5..0000000000 --- a/packages/core/src/extensions/Comments/threadstore/TipTapThreadStore.ts.bak +++ /dev/null @@ -1,7 +0,0 @@ -export class TiptapThreadStore { - constructor(private readonly editor: BlockNoteEditor) {} - - public async createThread() { - this.editor._tiptapEditor.commands.setMark(this.markType, { threadId: id }); - } -} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index e4d340d233..ea3c984cd5 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -7,10 +7,6 @@ export * from "./api/nodeUtil.js"; export * from "./api/testUtil/index.js"; export * from "./blocks/AudioBlockContent/AudioBlockContent.js"; export * from "./blocks/CodeBlockContent/CodeBlockContent.js"; -export * from "./blocks/defaultBlockHelpers.js"; -export * from "./blocks/defaultBlocks.js"; -export * from "./blocks/defaultBlockTypeGuards.js"; -export * from "./blocks/defaultProps.js"; export * from "./blocks/FileBlockContent/FileBlockContent.js"; export * from "./blocks/FileBlockContent/helpers/parse/parseEmbedElement.js"; export * from "./blocks/FileBlockContent/helpers/parse/parseFigureElement.js"; @@ -22,14 +18,18 @@ export * from "./blocks/FileBlockContent/helpers/toExternalHTML/createFigureWith export * from "./blocks/FileBlockContent/helpers/toExternalHTML/createLinkWithCaption.js"; export * from "./blocks/FileBlockContent/uploadToTmpFilesDotOrg_DEV_ONLY.js"; export * from "./blocks/ImageBlockContent/ImageBlockContent.js"; -export * from "./blocks/PageBreakBlockContent/getPageBreakSlashMenuItems.js"; export * from "./blocks/PageBreakBlockContent/PageBreakBlockContent.js"; +export * from "./blocks/PageBreakBlockContent/getPageBreakSlashMenuItems.js"; export * from "./blocks/PageBreakBlockContent/schema.js"; export { EMPTY_CELL_HEIGHT, EMPTY_CELL_WIDTH, } from "./blocks/TableBlockContent/TableExtension.js"; export * from "./blocks/VideoBlockContent/VideoBlockContent.js"; +export * from "./blocks/defaultBlockHelpers.js"; +export * from "./blocks/defaultBlockTypeGuards.js"; +export * from "./blocks/defaultBlocks.js"; +export * from "./blocks/defaultProps.js"; export * from "./editor/BlockNoteEditor.js"; export * from "./editor/BlockNoteExtensions.js"; export * from "./editor/BlockNoteSchema.js"; @@ -44,9 +44,9 @@ export * from "./extensions/LinkToolbar/protocols.js"; export * from "./extensions/SideMenu/SideMenuPlugin.js"; export * from "./extensions/SuggestionMenu/DefaultGridSuggestionItem.js"; export * from "./extensions/SuggestionMenu/DefaultSuggestionItem.js"; +export * from "./extensions/SuggestionMenu/SuggestionPlugin.js"; export * from "./extensions/SuggestionMenu/getDefaultEmojiPickerItems.js"; export * from "./extensions/SuggestionMenu/getDefaultSlashMenuItems.js"; -export * from "./extensions/SuggestionMenu/SuggestionPlugin.js"; export * from "./extensions/TableHandles/TableHandlesPlugin.js"; export * from "./i18n/dictionary.js"; export * from "./schema/index.js"; @@ -78,5 +78,6 @@ export * from "./extensions/Comments/types.js"; export * from "./extensions/Comments/threadstore/DefaultThreadStoreAuth.js"; export * from "./extensions/Comments/threadstore/ThreadStore.js"; export * from "./extensions/Comments/threadstore/ThreadStoreAuth.js"; +export * from "./extensions/Comments/threadstore/TipTapThreadStore.js"; export * from "./extensions/Comments/threadstore/YjsThreadStore.js"; export * from "./models/User.js"; From 3ef17e5728c0f13ef1d04962ee60a0785c8b3cd0 Mon Sep 17 00:00:00 2001 From: yousefed Date: Fri, 14 Feb 2025 15:59:46 +0100 Subject: [PATCH 046/144] address feedback --- .../src/extensions/Comments/CommentsPlugin.ts | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/packages/core/src/extensions/Comments/CommentsPlugin.ts b/packages/core/src/extensions/Comments/CommentsPlugin.ts index 2d9a425e0b..754db445b6 100644 --- a/packages/core/src/extensions/Comments/CommentsPlugin.ts +++ b/packages/core/src/extensions/Comments/CommentsPlugin.ts @@ -1,7 +1,6 @@ import { Node } from "prosemirror-model"; import { Plugin, PluginKey } from "prosemirror-state"; import { Decoration, DecorationSet } from "prosemirror-view"; -import * as Y from "yjs"; import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; import { User } from "../../models/User.js"; import { EventEmitter } from "../../util/EventEmitter.js"; @@ -10,13 +9,10 @@ import { CommentBody, ThreadData } from "./types.js"; import { UserStore } from "./userstore/UserStore.js"; const PLUGIN_KEY = new PluginKey(`blocknote-comments`); -enum CommentsPluginActions { - SET_SELECTED_THREAD_ID = "SET_SELECTED_THREAD_ID", -} +const SET_SELECTED_THREAD_ID = "SET_SELECTED_THREAD_ID"; type CommentsPluginAction = { - name: CommentsPluginActions; - data: string | null; + name: typeof SET_SELECTED_THREAD_ID; }; type CommentsPluginState = { @@ -90,12 +86,6 @@ export class CommentsPlugin extends EventEmitter { * when a thread is resolved or deleted, we need to update the marks to reflect the new state */ private updateMarksFromThreads = (threads: Map) => { - const doc = new Y.Doc(); - const threadMap = doc.getMap("threads"); - threads.forEach((thread) => { - threadMap.set(thread.id, thread); - }); - const ttEditor = this.editor._tiptapEditor; if (!ttEditor) { // TODO: better lifecycle management @@ -204,7 +194,7 @@ export class CommentsPlugin extends EventEmitter { self.emitStateUpdate(); view.dispatch( view.state.tr.setMeta(PLUGIN_KEY, { - name: CommentsPluginActions.SET_SELECTED_THREAD_ID, + name: SET_SELECTED_THREAD_ID, }) ); }; From db7d339c7452469dbbd803675e7143486eb3dbcd Mon Sep 17 00:00:00 2001 From: yousefed Date: Mon, 17 Feb 2025 09:08:24 +0100 Subject: [PATCH 047/144] add comment --- examples/07-collaboration/04-comments/App.tsx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/examples/07-collaboration/04-comments/App.tsx b/examples/07-collaboration/04-comments/App.tsx index 0df6a4f759..37520a5ee2 100644 --- a/examples/07-collaboration/04-comments/App.tsx +++ b/examples/07-collaboration/04-comments/App.tsx @@ -5,7 +5,7 @@ import { BlockNoteView } from "@blocknote/mantine"; import "@blocknote/mantine/style.css"; import { useCreateBlockNote } from "@blocknote/react"; import { Select } from "@mantine/core"; -import { useYDoc, useYjsProvider, YDocProvider } from "@y-sweet/react"; +import { YDocProvider, useYDoc, useYjsProvider } from "@y-sweet/react"; import { useMemo, useState } from "react"; const colors = [ @@ -88,6 +88,17 @@ function Document() { // setup the thread store which stores / and syncs thread / comment data const threadStore = useMemo(() => { + // const provider = new TiptapCollabProvider({ + // name: "test", + // baseUrl: "https://collab.yourdomain.com", + // appId: "test", + // document: doc, + // }); + // return new TiptapThreadStore( + // user.id, + // provider, + // new DefaultThreadStoreAuth(user.id, user.role) + // ); return new YjsThreadStore( user.id, doc.getMap("threads"), From c007f8490c690c0a101697ff36f174eb6267c35d Mon Sep 17 00:00:00 2001 From: yousefed Date: Mon, 17 Feb 2025 19:02:07 +0100 Subject: [PATCH 048/144] wip --- packages/core/src/extensions/Comments/CommentMark.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/core/src/extensions/Comments/CommentMark.ts b/packages/core/src/extensions/Comments/CommentMark.ts index 719f34e420..fd2158c7d5 100644 --- a/packages/core/src/extensions/Comments/CommentMark.ts +++ b/packages/core/src/extensions/Comments/CommentMark.ts @@ -45,4 +45,13 @@ export const CommentMark = Mark.create({ }), ]; }, + + extendMarkSchema(extension) { + if (extension.name === "comment") { + return { + blocknoteIgnore: true, + }; + } + return {}; + }, }); From c0c848a6167d6923e63ce37375720fcf35cfcc04 Mon Sep 17 00:00:00 2001 From: yousefed Date: Tue, 18 Feb 2025 09:13:19 +0100 Subject: [PATCH 049/144] add autofocus --- packages/core/src/editor/BlockNoteTipTapEditor.ts | 5 ++++- packages/react/src/editor/BlockNoteView.tsx | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/core/src/editor/BlockNoteTipTapEditor.ts b/packages/core/src/editor/BlockNoteTipTapEditor.ts index 458a22a4c2..2a35c35256 100644 --- a/packages/core/src/editor/BlockNoteTipTapEditor.ts +++ b/packages/core/src/editor/BlockNoteTipTapEditor.ts @@ -184,7 +184,10 @@ export class BlockNoteTipTapEditor extends TiptapEditor { // emit the created event, call here manually because we blocked the default call in the constructor // (https://github.com/ueberdosis/tiptap/blob/45bac803283446795ad1b03f43d3746fa54a68ff/packages/core/src/Editor.ts#L117) - this.commands.focus(this.options.autofocus); + this.commands.focus( + this.options.autofocus || + this.options.element.getAttribute("data-bn-autofocus") === "true" + ); this.emit("create", { editor: this }); this.isInitialized = true; } diff --git a/packages/react/src/editor/BlockNoteView.tsx b/packages/react/src/editor/BlockNoteView.tsx index a18b10e49d..0905d8331c 100644 --- a/packages/react/src/editor/BlockNoteView.tsx +++ b/packages/react/src/editor/BlockNoteView.tsx @@ -86,6 +86,7 @@ function BlockNoteViewComponent< sideMenu, filePanel, tableHandles, + autoFocus, ...rest } = props; @@ -179,6 +180,7 @@ function BlockNoteViewComponent<
From 9576cfea0c2cae7e246dd94fe1d525b0d3964881 Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Tue, 18 Feb 2025 16:25:24 +0100 Subject: [PATCH 050/144] Simplified tooltip + popover interaction --- packages/mantine/src/badge/Badge.tsx | 15 ++-- packages/mantine/src/menu/Menu.tsx | 75 ++++++++----------- packages/mantine/src/popover/Popover.tsx | 29 ++----- .../mantine/src/toolbar/ToolbarButton.tsx | 16 ++-- 4 files changed, 60 insertions(+), 75 deletions(-) diff --git a/packages/mantine/src/badge/Badge.tsx b/packages/mantine/src/badge/Badge.tsx index 4bacafa9f1..3356e663c9 100644 --- a/packages/mantine/src/badge/Badge.tsx +++ b/packages/mantine/src/badge/Badge.tsx @@ -6,10 +6,9 @@ import { import { assertEmpty } from "@blocknote/core"; import { ComponentProps } from "@blocknote/react"; -import { forwardRef, useContext } from "react"; +import { forwardRef, useState } from "react"; import { TooltipContent } from "../toolbar/ToolbarButton.js"; -import { PopoverContext } from "../popover/Popover.js"; export const Badge = forwardRef< HTMLInputElement, @@ -30,13 +29,19 @@ export const Badge = forwardRef< // assertEmpty in this case is only used at typescript level, not runtime level assertEmpty(rest, false); - const { isOpened } = useContext(PopoverContext); + const [hideTooltip, setHideTooltip] = useState(false); const badge = ( { + setHideTooltip(true); + onClick?.(event); + }} + wrapperProps={{ + onMouseLeave: () => setHideTooltip(false), + }} variant={"light"} icon={<>} ref={ref}> @@ -45,7 +50,7 @@ export const Badge = forwardRef< ); - if (!mainTooltip || isOpened) { + if (!mainTooltip || hideTooltip) { return badge; } diff --git a/packages/mantine/src/menu/Menu.tsx b/packages/mantine/src/menu/Menu.tsx index d81eae84a5..5a632cab49 100644 --- a/packages/mantine/src/menu/Menu.tsx +++ b/packages/mantine/src/menu/Menu.tsx @@ -16,8 +16,6 @@ import { } from "react"; import { HiChevronRight } from "react-icons/hi"; -import { PopoverContext } from "../popover/Popover.js"; - const SubMenuContext = createContext< | { onMenuMouseOver: () => void; @@ -108,33 +106,31 @@ const SubMenu = forwardRef< }, []); return ( - - - - - {children} - - - - + + + + {children} + + + ); }); @@ -143,25 +139,18 @@ export const Menu = (props: ComponentProps["Generic"]["Menu"]["Root"]) => { assertEmpty(rest); - const [isOpened, setIsOpened] = useState(false); - if (sub) { return ; } return ( - - { - setIsOpened(open); - onOpenChange?.(open); - }} - position={position}> - {children} - - + + {children} + ); }; diff --git a/packages/mantine/src/popover/Popover.tsx b/packages/mantine/src/popover/Popover.tsx index 316ae94730..fd92374069 100644 --- a/packages/mantine/src/popover/Popover.tsx +++ b/packages/mantine/src/popover/Popover.tsx @@ -6,13 +6,7 @@ import { import { assertEmpty } from "@blocknote/core"; import { ComponentProps } from "@blocknote/react"; -import { createContext, forwardRef, useState } from "react"; - -export const PopoverContext = createContext<{ - isOpened: boolean; -}>({ - isOpened: false, -}); +import { forwardRef } from "react"; export const Popover = ( props: ComponentProps["Generic"]["Popover"]["Root"] @@ -21,21 +15,14 @@ export const Popover = ( assertEmpty(rest); - const [isOpened, setIsOpened] = useState(false); - return ( - - { - setIsOpened(open); - onOpenChange?.(open); - }} - position={position}> - {children} - - + + {children} + ); }; diff --git a/packages/mantine/src/toolbar/ToolbarButton.tsx b/packages/mantine/src/toolbar/ToolbarButton.tsx index 49d078eee5..4740e028bf 100644 --- a/packages/mantine/src/toolbar/ToolbarButton.tsx +++ b/packages/mantine/src/toolbar/ToolbarButton.tsx @@ -8,9 +8,7 @@ import { import { assertEmpty, isSafari } from "@blocknote/core"; import { ComponentProps } from "@blocknote/react"; -import { forwardRef, useContext } from "react"; - -import { PopoverContext } from "../popover/Popover.js"; +import { forwardRef, useState } from "react"; export const TooltipContent = (props: { mainTooltip: string; @@ -54,7 +52,7 @@ export const ToolbarButton = forwardRef( // assertEmpty in this case is only used at typescript level, not runtime level assertEmpty(rest, false); - const { isOpened } = useContext(PopoverContext); + const [hideTooltip, setHideTooltip] = useState(false); const button = children ? ( ( (e.currentTarget as HTMLButtonElement).focus(); } }} - onClick={onClick} + onClick={(event) => { + setHideTooltip(true); + onClick?.(event); + }} + // Mantine Menu.Target elements block mouseleave events for some reason, + // but pointerleave events work fine. + onPointerLeave={() => setHideTooltip(false)} aria-pressed={isSelected} data-selected={isSelected || undefined} data-test={ @@ -110,7 +114,7 @@ export const ToolbarButton = forwardRef( ); - if (!mainTooltip || isOpened) { + if (!mainTooltip || hideTooltip) { return button; } From 9fa9d0753e5ec1b311b858d81381dbb11c16a6d8 Mon Sep 17 00:00:00 2001 From: yousefed Date: Wed, 19 Feb 2025 19:00:21 +0100 Subject: [PATCH 051/144] wip --- examples/07-collaboration/04-comments/App.tsx | 14 ++++- .../04-comments/tsconfig.json | 14 ++--- .../src/extensions/Comments/CommentsPlugin.ts | 18 +++--- packages/mantine/src/comments/Card.tsx | 4 +- packages/mantine/src/style.css | 14 ++++- .../components/Comments/ThreadStreamView.tsx | 62 +++++++++++++++++++ .../react/src/editor/BlockNoteDefaultUI.tsx | 3 +- .../react/src/editor/ComponentsContext.tsx | 1 + packages/react/src/index.ts | 9 ++- 9 files changed, 110 insertions(+), 29 deletions(-) create mode 100644 packages/react/src/components/Comments/ThreadStreamView.tsx diff --git a/examples/07-collaboration/04-comments/App.tsx b/examples/07-collaboration/04-comments/App.tsx index 37520a5ee2..4b10f9db77 100644 --- a/examples/07-collaboration/04-comments/App.tsx +++ b/examples/07-collaboration/04-comments/App.tsx @@ -3,7 +3,11 @@ import { DefaultThreadStoreAuth, User, YjsThreadStore } from "@blocknote/core"; import { BlockNoteView } from "@blocknote/mantine"; import "@blocknote/mantine/style.css"; -import { useCreateBlockNote } from "@blocknote/react"; +import { + BlockNoteContext, + ThreadStreamView, + useCreateBlockNote, +} from "@blocknote/react"; import { Select } from "@mantine/core"; import { YDocProvider, useYDoc, useYjsProvider } from "@y-sweet/react"; import { useMemo, useState } from "react"; @@ -142,7 +146,13 @@ function Document() { }} value={user.id} /> - + +
+ + + +
+
); } diff --git a/examples/07-collaboration/04-comments/tsconfig.json b/examples/07-collaboration/04-comments/tsconfig.json index 1bd8ab3c57..4a76cf4c7d 100644 --- a/examples/07-collaboration/04-comments/tsconfig.json +++ b/examples/07-collaboration/04-comments/tsconfig.json @@ -3,11 +3,7 @@ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], + "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, "skipLibCheck": true, "esModuleInterop": false, @@ -22,10 +18,8 @@ "jsx": "react-jsx", "composite": true }, - "include": [ - "." - ], - "__ADD_FOR_LOCAL_DEV_references": [ + "include": ["."], + "references": [ { "path": "../../../packages/core/" }, @@ -33,4 +27,4 @@ "path": "../../../packages/react/" } ] -} \ No newline at end of file +} diff --git a/packages/core/src/extensions/Comments/CommentsPlugin.ts b/packages/core/src/extensions/Comments/CommentsPlugin.ts index 754db445b6..247f98965a 100644 --- a/packages/core/src/extensions/Comments/CommentsPlugin.ts +++ b/packages/core/src/extensions/Comments/CommentsPlugin.ts @@ -190,13 +190,7 @@ export class CommentsPlugin extends EventEmitter { } const selectThread = (threadId: string | undefined) => { - self.selectedThreadId = threadId; - self.emitStateUpdate(); - view.dispatch( - view.state.tr.setMeta(PLUGIN_KEY, { - name: SET_SELECTED_THREAD_ID, - }) - ); + self.selectThread(threadId); }; const node = view.state.doc.nodeAt(pos); @@ -228,6 +222,16 @@ export class CommentsPlugin extends EventEmitter { return this.on("update", callback); } + public selectThread(threadId: string | undefined) { + this.selectedThreadId = threadId; + this.emitStateUpdate(); + this.editor.dispatch( + this.editor.prosemirrorView!.state.tr.setMeta(PLUGIN_KEY, { + name: SET_SELECTED_THREAD_ID, + }) + ); + } + public startPendingComment() { this.pendingComment = true; this.emitStateUpdate(); diff --git a/packages/mantine/src/comments/Card.tsx b/packages/mantine/src/comments/Card.tsx index 38865105ae..519d7f09c8 100644 --- a/packages/mantine/src/comments/Card.tsx +++ b/packages/mantine/src/comments/Card.tsx @@ -9,10 +9,10 @@ export const Card = forwardRef< >((props, ref) => { const { className, children, ...rest } = props; - assertEmpty(rest, false); + // assertEmpty(rest, false); return ( - + {children} ); diff --git a/packages/mantine/src/style.css b/packages/mantine/src/style.css index ba85f3602d..5b4a224b08 100644 --- a/packages/mantine/src/style.css +++ b/packages/mantine/src/style.css @@ -570,9 +570,10 @@ background-color: var(--bn-colors-menu-background); border: var(--bn-border); border-radius: var(--bn-border-radius-medium); - box-shadow: var(--bn-shadow-medium); + /* box-shadow: var(--bn-shadow-medium); */ color: var(--bn-colors-menu-text); overflow: visible; + margin-bottom: 1em; } .bn-mantine .bn-thread-comments { @@ -657,12 +658,19 @@ color: var(--bn-colors-menu-text); } -.bn-mantine .bn-badge .mantine-Chip-label > span:not(.mantine-Chip-iconWrapper) { +.bn-mantine + .bn-badge + .mantine-Chip-label + > span:not(.mantine-Chip-iconWrapper) { display: inline-flex; gap: 4px; } -.bn-mantine .bn-badge .mantine-Chip-label > span:not(.mantine-Chip-iconWrapper) > span { +.bn-mantine + .bn-badge + .mantine-Chip-label + > span:not(.mantine-Chip-iconWrapper) + > span { align-items: center; display: inline-flex; justify-content: center; diff --git a/packages/react/src/components/Comments/ThreadStreamView.tsx b/packages/react/src/components/Comments/ThreadStreamView.tsx new file mode 100644 index 0000000000..a2ba23365f --- /dev/null +++ b/packages/react/src/components/Comments/ThreadStreamView.tsx @@ -0,0 +1,62 @@ +import { useMemo } from "react"; +import { useBlockNoteEditor } from "../../hooks/useBlockNoteEditor.js"; +import { useUIPluginState } from "../../hooks/useUIPluginState.js"; +import { Thread } from "./Thread.js"; +import { useThreads } from "./useThreads.js"; + +/** + * The ThreadStreamView component can be used to display a list of comments in a sidebar. + * + * This component is similar to Google Docs "Show All Comments" sidebar (cmd+option+shift+A) + */ +export function ThreadStreamView(props: { + maxCommentsPerThread?: number; + sort?: "document-order" | "newest-first" | "oldest-first"; +}) { + const editor = useBlockNoteEditor(); + + if (!editor.comments) { + throw new Error("Comments plugin not found"); + } + + const comments = editor.comments; + + const state = useUIPluginState( + editor.comments.onUpdate.bind(editor.comments) + ); + + const selectedThreadId = state?.selectedThreadId; + + const allThreads = useThreads(editor); + + const threads = useMemo(() => { + const ret = Array.from(allThreads.values()).filter( + (thread) => !thread.resolved && !thread.deletedAt + ); + + return ret; + //.sort((a, b) => { + // return a.id.localeCompare(b.id); + //}); + }, [allThreads]); + + return ( +
+

ThreadStreamView

+ {threads.map((thread) => ( + { + comments.selectThread(thread.id); + }} + onBlur={() => { + comments.selectThread(undefined); + }} + tabIndex={0} + /> + ))} +
+ ); +} diff --git a/packages/react/src/editor/BlockNoteDefaultUI.tsx b/packages/react/src/editor/BlockNoteDefaultUI.tsx index 606389acab..9f2433575e 100644 --- a/packages/react/src/editor/BlockNoteDefaultUI.tsx +++ b/packages/react/src/editor/BlockNoteDefaultUI.tsx @@ -1,5 +1,4 @@ import { FloatingComposerController } from "../components/Comments/FloatingComposerController.js"; -import { FloatingThreadController } from "../components/Comments/FloatingThreadController.js"; import { FilePanelController } from "../components/FilePanel/FilePanelController.js"; import { FormattingToolbarController } from "../components/FormattingToolbar/FormattingToolbarController.js"; import { LinkToolbarController } from "../components/LinkToolbar/LinkToolbarController.js"; @@ -51,7 +50,7 @@ export function BlockNoteDefaultUI(props: BlockNoteDefaultUIProps) { {editor.comments && props.comments !== false && ( <> - + {/* */} )} diff --git a/packages/react/src/editor/ComponentsContext.tsx b/packages/react/src/editor/ComponentsContext.tsx index 6eb2e58f5f..142e519201 100644 --- a/packages/react/src/editor/ComponentsContext.tsx +++ b/packages/react/src/editor/ComponentsContext.tsx @@ -186,6 +186,7 @@ export type ComponentProps = { Card: { className?: string; children?: ReactNode; + onClick?: () => void; }; CardSection: { className?: string; diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index dd8215d700..68582fbb42 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -30,9 +30,9 @@ export * from "./components/FormattingToolbar/DefaultButtons/FileReplaceButton.j export * from "./components/FormattingToolbar/DefaultButtons/NestBlockButtons.js"; export * from "./components/FormattingToolbar/DefaultButtons/TextAlignButton.js"; export * from "./components/FormattingToolbar/DefaultSelects/BlockTypeSelect.js"; +export * from "./components/FormattingToolbar/ExperimentalMobileFormattingToolbarController.js"; export * from "./components/FormattingToolbar/FormattingToolbar.js"; export * from "./components/FormattingToolbar/FormattingToolbarController.js"; -export * from "./components/FormattingToolbar/ExperimentalMobileFormattingToolbarController.js"; export * from "./components/FormattingToolbar/FormattingToolbarProps.js"; export * from "./components/LinkToolbar/DefaultButtons/DeleteLinkButton.js"; @@ -74,11 +74,11 @@ export * from "./components/FilePanel/FilePanel.js"; export * from "./components/FilePanel/FilePanelController.js"; export * from "./components/FilePanel/FilePanelProps.js"; +export * from "./components/TableHandles/ExtendButton/ExtendButton.js"; +export * from "./components/TableHandles/ExtendButton/ExtendButtonProps.js"; export * from "./components/TableHandles/TableHandle.js"; export * from "./components/TableHandles/TableHandleProps.js"; export * from "./components/TableHandles/TableHandlesController.js"; -export * from "./components/TableHandles/ExtendButton/ExtendButton.js"; -export * from "./components/TableHandles/ExtendButton/ExtendButtonProps.js"; export * from "./components/TableHandles/hooks/useExtendButtonsPositioning.js"; export * from "./components/TableHandles/hooks/useTableHandlesPositioning.js"; @@ -103,3 +103,6 @@ export * from "./schema/ReactStyleSpec.js"; export * from "./util/elementOverflow.js"; export * from "./util/mergeRefs.js"; + +export * from "./components/Comments/ThreadStreamView.js"; +// TODO: other exports From 44b2dcab64d227d6763bc33333ddb574e0099d00 Mon Sep 17 00:00:00 2001 From: Matthew Lipski <50169049+matthewlipski@users.noreply.github.com> Date: Thu, 20 Feb 2025 11:27:42 +0100 Subject: [PATCH 052/144] feat: ShadCN comments (#1445) * Added ShadCN comments implementation * Removed unneeded state * Fixed menu stealing focus * Updated screenshots * Added ariakit comments implementation (#1448) --- package-lock.json | 121 ++++++++++++ packages/ariakit/src/ariakitStyles.css | 183 ++++++++++++++++++ packages/ariakit/src/badge/Badge.tsx | 75 +++++++ packages/ariakit/src/comments/Card.tsx | 39 ++++ packages/ariakit/src/comments/Comment.tsx | 84 ++++++++ packages/ariakit/src/comments/Editor.tsx | 55 ++++++ packages/ariakit/src/components.ts | 15 +- packages/ariakit/src/popover/Popover.tsx | 6 +- packages/ariakit/src/style.css | 105 +++++++++- packages/ariakit/src/toolbar/Toolbar.tsx | 9 +- .../ariakit/src/toolbar/ToolbarButton.tsx | 3 +- .../react/src/components/Comments/Comment.tsx | 71 +++---- .../src/components/Comments/EmojiPicker.tsx | 48 +++++ packages/shadcn/package.json | 1 + .../shadcn/src/ShadCNComponentsContext.tsx | 14 ++ packages/shadcn/src/badge/Badge.tsx | 81 ++++++++ packages/shadcn/src/comments/Card.tsx | 48 +++++ packages/shadcn/src/comments/Comment.tsx | 106 ++++++++++ packages/shadcn/src/comments/Editor.tsx | 57 ++++++ packages/shadcn/src/components.ts | 15 +- packages/shadcn/src/components/ui/avatar.tsx | 48 +++++ .../shadcn/src/components/ui/skeleton.tsx | 15 ++ packages/shadcn/src/menu/Menu.tsx | 4 +- packages/shadcn/src/popover/popover.tsx | 3 +- packages/shadcn/src/style.css | 5 + packages/shadcn/src/toolbar/Toolbar.tsx | 25 ++- ...shadcn-drag-handle-menu-chromium-linux.png | Bin 10644 -> 10808 bytes .../shadcn-drag-handle-menu-firefox-linux.png | Bin 25411 -> 25772 bytes 28 files changed, 1175 insertions(+), 61 deletions(-) create mode 100644 packages/ariakit/src/badge/Badge.tsx create mode 100644 packages/ariakit/src/comments/Card.tsx create mode 100644 packages/ariakit/src/comments/Comment.tsx create mode 100644 packages/ariakit/src/comments/Editor.tsx create mode 100644 packages/react/src/components/Comments/EmojiPicker.tsx create mode 100644 packages/shadcn/src/badge/Badge.tsx create mode 100644 packages/shadcn/src/comments/Card.tsx create mode 100644 packages/shadcn/src/comments/Comment.tsx create mode 100644 packages/shadcn/src/comments/Editor.tsx create mode 100644 packages/shadcn/src/components/ui/avatar.tsx create mode 100644 packages/shadcn/src/components/ui/skeleton.tsx diff --git a/package-lock.json b/package-lock.json index 62d00ecf06..a7f5bf1b57 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7658,6 +7658,126 @@ } } }, + "node_modules/@radix-ui/react-avatar": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.1.3.tgz", + "integrity": "sha512-Paen00T4P8L8gd9bNsRMw7Cbaz85oxiv+hzomsRZgFm2byltPFDtfcoqlWJ8GyZlIBWgLssJlzLCnKU0G0302g==", + "dependencies": { + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-avatar/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz", + "integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-avatar/node_modules/@radix-ui/react-context": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz", + "integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-avatar/node_modules/@radix-ui/react-primitive": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.2.tgz", + "integrity": "sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w==", + "dependencies": { + "@radix-ui/react-slot": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-avatar/node_modules/@radix-ui/react-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz", + "integrity": "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-avatar/node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz", + "integrity": "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-avatar/node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz", + "integrity": "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-collection": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.0.3.tgz", @@ -32027,6 +32147,7 @@ "@blocknote/core": "^0.24.0", "@blocknote/react": "^0.24.0", "@hookform/resolvers": "^3.6.0", + "@radix-ui/react-avatar": "^1.1.3", "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-label": "^2.0.2", "@radix-ui/react-popover": "^1.0.7", diff --git a/packages/ariakit/src/ariakitStyles.css b/packages/ariakit/src/ariakitStyles.css index c0e42e964f..82e5016755 100644 --- a/packages/ariakit/src/ariakitStyles.css +++ b/packages/ariakit/src/ariakitStyles.css @@ -1003,4 +1003,187 @@ responsible for the majority of the styling. */ .bn-ak-link:where(.dark, .dark *) { color: hsl(204 100% 64%); +} + +/* https://ariakit.org/components/hovercard */ +/*.bn-ak-button {*/ +/* --border: rgb(0 0 0/13%);*/ +/* --highlight: rgb(255 255 255/20%);*/ +/* --shadow: rgb(0 0 0/10%);*/ +/* display: flex;*/ +/* height: 2.5rem;*/ +/* user-select: none;*/ +/* align-items: center;*/ +/* justify-content: center;*/ +/* gap: 0.25rem;*/ +/* white-space: nowrap;*/ +/* border-style: none;*/ +/* background-color: white;*/ +/* padding-left: 1rem;*/ +/* padding-right: 1rem;*/ +/* font-size: 1rem;*/ +/* line-height: 1.5rem;*/ +/* color: black;*/ +/* text-decoration-line: none;*/ +/* outline-width: 2px;*/ +/* outline-offset: 2px;*/ +/* outline-color: hsl(204 100% 40%);*/ +/* box-shadow:*/ +/* inset 0 0 0 1px var(--border),*/ +/* inset 0 2px 0 var(--highlight),*/ +/* inset 0 -1px 0 var(--shadow),*/ +/* 0 1px 1px var(--shadow);*/ +/* position: absolute;*/ +/* top: 1rem;*/ +/* right: 1rem;*/ +/* border-radius: 9999px;*/ +/* font-weight: 600;*/ +/*}*/ + +/*.bn-ak-button:where(.dark, .dark *) {*/ +/* --border: rgb(255 255 255/10%);*/ +/* --highlight: rgb(255 255 255/5%);*/ +/* --shadow: rgb(0 0 0/25%);*/ +/* background-color: rgb(255 255 255 / 0.05);*/ +/* color: white;*/ +/* box-shadow:*/ +/* inset 0 0 0 1px var(--border),*/ +/* inset 0 -1px 0 1px var(--shadow),*/ +/* inset 0 1px 0 var(--highlight);*/ +/*}*/ + +/*.bn-ak-button:not(:active):hover {*/ +/* --border: rgb(0 0 0/33%);*/ +/*}*/ + +/*.bn-ak-button:where(.dark, .dark *):not(:active):hover {*/ +/* --border: rgb(255 255 255/25%);*/ +/*}*/ + +.bn-ak-primary { + --border: rgba(0, 0, 0, 0.15); + --highlight: rgba(255, 255, 255, 0.25); + --shadow: rgba(0, 0, 0, 0.15); + background-color: hsl(204 100% 40%); + color: white; +} + +.bn-ak-primary:hover { + --border: rgba(0, 0, 0, 0.35); + background-color: hsl(204 100% 35%); +} + +.bn-ak-primary:where(.dark, .dark *) { + --border: rgba(255, 255, 255, 0.25); + --highlight: rgba(255, 255, 255, 0.1); + --shadow: rgba(0, 0, 0, 0.25); + background-color: hsl(204 100% 35%); +} + +.bn-ak-primary:hover:where(.dark, .dark *) { + --border: rgba(255, 255, 255, 0.45); + background-color: hsl(204 100% 40%); +} + +.bn-ak-button[aria-disabled="true"] { + opacity: 0.5; +} + +.bn-ak-button[data-focus-visible] { + outline-style: solid; +} + +.bn-ak-button:active, +.bn-ak-button[data-active] { + padding-top: 0.125rem; + box-shadow: + inset 0 0 0 1px var(--border), + inset 0 2px 0 var(--border); +} + +@media (min-width: 640px) { + .bn-ak-button { + gap: 0.5rem; + } +} + +.bn-ak-button:active:where(.dark, .dark *), +.bn-ak-button[data-active]:where(.dark, .dark *) { + box-shadow: + inset 0 0 0 1px var(--border), + inset 0 1px 1px 1px var(--shadow); +} + +.bn-ak-flat { + box-shadow: none; +} + +.bn-ak-flat:where(.dark, .dark *) { + box-shadow: none; +} + +.bn-ak-flat:active:where(.dark, .dark *), +.bn-ak-flat[data-active]:where(.dark, .dark *) { + box-shadow: none; +} + +.bn-ak-anchor { + font-weight: 500; + color: hsl(204 100% 35%); + text-decoration-line: underline; + text-underline-offset: 0.25em; + text-decoration-skip-ink: none; +} + +.bn-ak-anchor:hover { + text-decoration-thickness: 3px; +} + +.bn-ak-anchor:where(.dark, .dark *) { + color: hsl(204 100% 64%); +} + +.bn-ak-hovercard { + position: relative; + z-index: 50; + display: flex; + width: 300px; + flex-direction: column; + gap: 0.5rem; + border-radius: 0.5rem; + border-width: 1px; + border-style: solid; + border-color: hsl(204 20% 88%); + background-color: white; + padding: 1rem; + color: black; + box-shadow: + 0 10px 15px -3px rgb(0 0 0 / 0.1), + 0 4px 6px -4px rgb(0 0 0 / 0.1); +} + +.bn-ak-hovercard:focus-visible, +.bn-ak-hovercard[data-focus-visible] { + outline: 2px solid hsl(204 100% 40%); + outline-offset: 2px; +} + +.bn-ak-hovercard:where(.dark, .dark *) { + border-color: hsl(204 4% 24%); + background-color: hsl(204 4% 16%); + color: white; + box-shadow: + 0 10px 15px -3px rgb(0 0 0 / 0.25), + 0 4px 6px -4px rgb(0 0 0 / 0.1); +} + +.bn-ak-avatar { + width: 4rem; + border-radius: 9999px; +} + +.bn-ak-username { + font-size: 1.125rem; + line-height: 1.75rem; + font-weight: 600; } \ No newline at end of file diff --git a/packages/ariakit/src/badge/Badge.tsx b/packages/ariakit/src/badge/Badge.tsx new file mode 100644 index 0000000000..0d89b8a592 --- /dev/null +++ b/packages/ariakit/src/badge/Badge.tsx @@ -0,0 +1,75 @@ +import { + Button as AriakitButton, + Group as AriakitGroup, + Tooltip as AriakitTooltip, + TooltipAnchor as AriakitTooltipAnchor, + TooltipProvider as AriakitTooltipProvider, +} from "@ariakit/react"; + +import { assertEmpty, mergeCSSClasses } from "@blocknote/core"; +import { ComponentProps } from "@blocknote/react"; +import { forwardRef } from "react"; + +export const Badge = forwardRef< + HTMLButtonElement, + ComponentProps["Generic"]["Badge"]["Root"] +>((props, ref) => { + const { + className, + text, + icon, + isSelected, + mainTooltip, + secondaryTooltip, + onClick, + ...rest + } = props; + + assertEmpty(rest, false); + + const badge = ( + onClick?.(event)} + ref={ref}> + {icon} + {text} + + ); + + if (!mainTooltip) { + return badge; + } + + return ( + + + + {mainTooltip} + {secondaryTooltip && {secondaryTooltip}} + + + ); +}); + +export const BadgeGroup = forwardRef< + HTMLDivElement, + ComponentProps["Generic"]["Badge"]["Group"] +>((props, ref) => { + const { className, children, ...rest } = props; + + assertEmpty(rest); + + return ( + + {children} + + ); +}); diff --git a/packages/ariakit/src/comments/Card.tsx b/packages/ariakit/src/comments/Card.tsx new file mode 100644 index 0000000000..0a31a72806 --- /dev/null +++ b/packages/ariakit/src/comments/Card.tsx @@ -0,0 +1,39 @@ +import { Group as AriakitGroup } from "@ariakit/react"; + +import { assertEmpty, mergeCSSClasses } from "@blocknote/core"; +import { ComponentProps } from "@blocknote/react"; +import { forwardRef } from "react"; + +export const Card = forwardRef< + HTMLDivElement, + ComponentProps["Comments"]["Card"] +>((props, ref) => { + const { className, children, ...rest } = props; + + assertEmpty(rest, false); + + return ( + + {children} + + ); +}); + +export const CardSection = forwardRef< + HTMLDivElement, + ComponentProps["Comments"]["CardSection"] +>((props, ref) => { + const { className, children, ...rest } = props; + + assertEmpty(rest, false); + + return ( + + {children} + + ); +}); diff --git a/packages/ariakit/src/comments/Comment.tsx b/packages/ariakit/src/comments/Comment.tsx new file mode 100644 index 0000000000..ad8413da48 --- /dev/null +++ b/packages/ariakit/src/comments/Comment.tsx @@ -0,0 +1,84 @@ +import { Group as AriakitGroup } from "@ariakit/react"; + +import { assertEmpty } from "@blocknote/core"; +import { ComponentProps } from "@blocknote/react"; +import { forwardRef, useState } from "react"; + +const AuthorInfo = forwardRef< + HTMLDivElement, + Pick +>((props, _ref) => { + const { authorInfo, timeString, ...rest } = props; + + assertEmpty(rest, false); + + if (authorInfo === "loading") { + return ( + +
+
+ + ); + } + + return ( + + {authorInfo.username} +
+ {authorInfo.username} + {timeString} +
+
+ ); +}); + +export const Comment = forwardRef< + HTMLDivElement, + ComponentProps["Comments"]["Comment"] +>((props, ref) => { + const { + className, + showActions, + authorInfo, + timeString, + actions, + children, + ...rest + } = props; + + assertEmpty(rest, false); + + const [hovered, setHovered] = useState(false); + + const doShowActions = + actions && + (showActions === true || + showActions === undefined || + (showActions === "hover" && hovered)); + + return ( + setHovered(true)} + onMouseLeave={() => setHovered(false)}> + {doShowActions ? ( + + {actions} + + ) : null} + + {children} + + ); +}); diff --git a/packages/ariakit/src/comments/Editor.tsx b/packages/ariakit/src/comments/Editor.tsx new file mode 100644 index 0000000000..857b417109 --- /dev/null +++ b/packages/ariakit/src/comments/Editor.tsx @@ -0,0 +1,55 @@ +import { assertEmpty } from "@blocknote/core"; +import { + ComponentProps, + FormattingToolbar, + FormattingToolbarController, + getFormattingToolbarItems, +} from "@blocknote/react"; +import { forwardRef, useEffect } from "react"; +import { BlockNoteView } from "../BlockNoteView.js"; + +export const Editor = forwardRef< + HTMLDivElement, + ComponentProps["Comments"]["Editor"] +>((props, ref) => { + const { className, onFocus, onBlur, editor, editable, ...rest } = props; + + assertEmpty(rest, false); + + // When we click the edit button on a comment, we also want to focus the + // comment editor + useEffect(() => { + if (editable) { + editor.focus(); + } + }, [editable, editor]); + + return ( + + + + ); +}); + +const CustomFormattingToolbar = () => { + const items = getFormattingToolbarItems([]).filter( + (el) => el.key !== "nestBlockButton" && el.key !== "unnestBlockButton" + ); + return ( + {items} + ); +}; diff --git a/packages/ariakit/src/components.ts b/packages/ariakit/src/components.ts index 372a235699..9819fcfb21 100644 --- a/packages/ariakit/src/components.ts +++ b/packages/ariakit/src/components.ts @@ -32,6 +32,10 @@ import { TableHandle } from "./tableHandle/TableHandle.js"; import { Toolbar } from "./toolbar/Toolbar.js"; import { ToolbarButton } from "./toolbar/ToolbarButton.js"; import { ToolbarSelect } from "./toolbar/ToolbarSelect.js"; +import { Card, CardSection } from "./comments/Card.js"; +import { Comment } from "./comments/Comment.js"; +import { Editor } from "./comments/Editor.js"; +import { Badge, BadgeGroup } from "./badge/Badge.js"; export const components: Components = { FormattingToolbar: { @@ -71,8 +75,17 @@ export const components: Components = { Root: TableHandle, ExtendButton: ExtendButton, }, - // @ts-ignore TODO + Comments: { + Comment: Comment, + Editor: Editor, + Card: Card, + CardSection: CardSection, + }, Generic: { + Badge: { + Root: Badge, + Group: BadgeGroup, + }, Toolbar: { Root: Toolbar, Button: ToolbarButton, diff --git a/packages/ariakit/src/popover/Popover.tsx b/packages/ariakit/src/popover/Popover.tsx index 0a92d59e4e..7118cbbbf7 100644 --- a/packages/ariakit/src/popover/Popover.tsx +++ b/packages/ariakit/src/popover/Popover.tsx @@ -29,7 +29,11 @@ export const PopoverContent = forwardRef< return ( {children} diff --git a/packages/ariakit/src/style.css b/packages/ariakit/src/style.css index eb05443137..af81137370 100644 --- a/packages/ariakit/src/style.css +++ b/packages/ariakit/src/style.css @@ -11,10 +11,11 @@ gap: 0.5rem; } -.bn-toolbar.bn-ak-toolbar { - overflow-x: auto; - max-width: 100vw; +.bn-ak-toolbar { + height: fit-content; + overflow: visible; } + .bn-toolbar .bn-ak-button { width: unset; } @@ -213,3 +214,101 @@ display: flex; flex-direction: column; } + +.bn-ariakit .bn-thread-comments { + display: flex; + flex-direction: column; + gap: 16px; +} + +.bn-ak-avatar { + height: 24px; + width: 24px; +} + +.bn-ak-username { + align-items: center; + display: flex; + font-size: 14px; + gap: 8px; + line-height: 1rem; +} + +.bn-ak-username > span { + font-size: 10px; +} + +.bn-ak-author-info { + align-items: center; + display: flex; + gap: 16px +} + +.bn-ariakit .bn-comment-editor .bn-editor { + background-color: transparent; +} + +.bn-ak-badge { + align-items: center; + border-radius: 12px; + display: flex; + gap: 4px; + height: fit-content; + padding: 2px 10px; + position: relative; + width: fit-content; +} + +.bn-ak-badge span { + align-items: center; + display: flex; + height: fit-content; +} + +.bn-ak-badge-group { + align-items: center; + display: flex; + gap: 4px; + flex-wrap: wrap; + width: 100%; +} + +.bn-ariakit .bn-thread-comment { + position: relative; + display: flex; + flex-direction: column; + gap: 4px; +} + +.bn-ariakit .bn-action-toolbar .bn-ak-button { + height: 1.5rem; + padding: 8px; + width: fit-content; +} + +.bn-ak-hovercard { + z-index: 0; +} + +.bn-ak-panel-popover { + background-color: transparent; + border: none; + box-shadow: none; + padding: 0; +} + +.bn-ariakit .bn-comment-actions-wrapper { + align-items: start; + display: flex; + justify-content: flex-end; +} + +.bn-ak-skeleton { + background-color: rgb(255 255 255/25%); +} + +.bn-ak-username.bn-ak-skeleton { + border-radius: 8px; + height: 16px; + width: 100px; +} \ No newline at end of file diff --git a/packages/ariakit/src/toolbar/Toolbar.tsx b/packages/ariakit/src/toolbar/Toolbar.tsx index b5def0f74b..6ce24ed560 100644 --- a/packages/ariakit/src/toolbar/Toolbar.tsx +++ b/packages/ariakit/src/toolbar/Toolbar.tsx @@ -9,7 +9,14 @@ type ToolbarProps = ComponentProps["FormattingToolbar"]["Root"] & export const Toolbar = forwardRef( (props, ref) => { - const { className, children, onMouseEnter, onMouseLeave, ...rest } = props; + const { + className, + children, + onMouseEnter, + onMouseLeave, + variant, + ...rest + } = props; // TODO // @ts-ignore diff --git a/packages/ariakit/src/toolbar/ToolbarButton.tsx b/packages/ariakit/src/toolbar/ToolbarButton.tsx index c62021f3cf..b277963db9 100644 --- a/packages/ariakit/src/toolbar/ToolbarButton.tsx +++ b/packages/ariakit/src/toolbar/ToolbarButton.tsx @@ -40,7 +40,6 @@ export const ToolbarButton = forwardRef( return ( ( } /> - + {mainTooltip} {secondaryTooltip && {secondaryTooltip}} diff --git a/packages/react/src/components/Comments/Comment.tsx b/packages/react/src/components/Comments/Comment.tsx index 6471c5dd0a..e30500e5dc 100644 --- a/packages/react/src/components/Comments/Comment.tsx +++ b/packages/react/src/components/Comments/Comment.tsx @@ -1,7 +1,6 @@ "use client"; import { CommentData, ThreadData, mergeCSSClasses } from "@blocknote/core"; -import Picker from "@emoji-mart/react"; import { ComponentPropsWithoutRef, MouseEvent, @@ -18,11 +17,11 @@ import { RiMoreFill, } from "react-icons/ri"; -import { useBlockNoteContext } from "../../editor/BlockNoteContext.js"; import { useComponentsContext } from "../../editor/ComponentsContext.js"; import { useBlockNoteEditor } from "../../hooks/useBlockNoteEditor.js"; import { useCreateBlockNote } from "../../hooks/useCreateBlockNote.js"; import { useDictionary } from "../../i18n/dictionary.js"; +import { EmojiPicker } from "./EmojiPicker.js"; import { CommentEditor } from "./CommentEditor.js"; import { schema } from "./schema.js"; import { useUser } from "./useUsers.js"; @@ -165,8 +164,6 @@ export const Comment = ({ const user = useUser(editor, comment.userId); - const blockNoteContext = useBlockNoteContext(); - if (!showDeleted && !comment.body) { return null; } @@ -185,25 +182,19 @@ export const Comment = ({ if (showActions && !isEditing) { actions = ( + className={mergeCSSClasses("bn-action-toolbar", "bn-comment-actions")} + variant={"action-toolbar"}> {canAddReaction && ( - - - - - - - - - onReactionSelect(emoji.native) - } - theme={blockNoteContext?.colorSchemePreference} - /> - - + + onReactionSelect(emoji.native) + }> + + + + )} {showResolveOrReopen && (thread.resolved ? ( @@ -302,28 +293,20 @@ export const Comment = ({ )}`} /> ))} - - - } - mainTooltip="Add reaction" - /> - - - - onReactionSelect(emoji.native) - } - theme={blockNoteContext?.colorSchemePreference} - /> - - + + onReactionSelect(emoji.native) + }> + } + mainTooltip="Add reaction" + /> + )} {isEditing && ( diff --git a/packages/react/src/components/Comments/EmojiPicker.tsx b/packages/react/src/components/Comments/EmojiPicker.tsx new file mode 100644 index 0000000000..85d09666d0 --- /dev/null +++ b/packages/react/src/components/Comments/EmojiPicker.tsx @@ -0,0 +1,48 @@ +import Picker from "@emoji-mart/react"; +import { ReactNode, useState } from "react"; + +import { useComponentsContext } from "../../editor/ComponentsContext.js"; +import { useBlockNoteContext } from "../../editor/BlockNoteContext.js"; + +export const EmojiPicker = (props: { + onEmojiSelect: (emoji: { native: string }) => void; + children: ReactNode; +}) => { + const [open, setOpen] = useState(false); + + const Components = useComponentsContext()!; + const blockNoteContext = useBlockNoteContext(); + + return ( + + +
{ + // Needed as the Picker component's onClickOutside handler + // fires immediately after otherwise, preventing the popover + // from opening. + event.preventDefault(); + event.stopPropagation(); + setOpen(!open); + }} + style={{ + display: "flex", + justifyContent: "center", + alignItems: "center", + }}> + {props.children} +
+
+ + setOpen(false)} + onEmojiSelect={(emoji: { native: string }) => { + props.onEmojiSelect(emoji); + setOpen(false); + }} + theme={blockNoteContext?.colorSchemePreference} + /> + +
+ ); +}; diff --git a/packages/shadcn/package.json b/packages/shadcn/package.json index 57d09c5ebb..044183eb7f 100644 --- a/packages/shadcn/package.json +++ b/packages/shadcn/package.json @@ -53,6 +53,7 @@ "@blocknote/core": "^0.24.0", "@blocknote/react": "^0.24.0", "@hookform/resolvers": "^3.6.0", + "@radix-ui/react-avatar": "^1.1.3", "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-label": "^2.0.2", "@radix-ui/react-popover": "^1.0.7", diff --git a/packages/shadcn/src/ShadCNComponentsContext.tsx b/packages/shadcn/src/ShadCNComponentsContext.tsx index 56a8c0d49f..29a4ea0673 100644 --- a/packages/shadcn/src/ShadCNComponentsContext.tsx +++ b/packages/shadcn/src/ShadCNComponentsContext.tsx @@ -1,3 +1,8 @@ +import { + Avatar as ShadCNAvatar, + AvatarFallback as ShadCNAvatarFallback, + AvatarImage as ShadCNAvatarImage, +} from "./components/ui/avatar.js"; import { Badge as ShadCNBadge } from "./components/ui/badge.js"; import { Button as ShadCNButton } from "./components/ui/button.js"; import { @@ -31,6 +36,7 @@ import { SelectTrigger as ShadCNSelectTrigger, SelectValue as ShadCNSelectValue, } from "./components/ui/select.js"; +import { Skeleton as ShadCNSkeleton } from "./components/ui/skeleton.js"; import { Tabs as ShadCNTabs, TabsContent as ShadCNTabsContent, @@ -48,6 +54,11 @@ import { import { createContext, useContext } from "react"; export const ShadCNDefaultComponents = { + Avatar: { + Avatar: ShadCNAvatar, + AvatarFallback: ShadCNAvatarFallback, + AvatarImage: ShadCNAvatarImage, + }, Badge: { Badge: ShadCNBadge, }, @@ -91,6 +102,9 @@ export const ShadCNDefaultComponents = { SelectTrigger: ShadCNSelectTrigger, SelectValue: ShadCNSelectValue, }, + Skeleton: { + Skeleton: ShadCNSkeleton, + }, Tabs: { Tabs: ShadCNTabs, TabsContent: ShadCNTabsContent, diff --git a/packages/shadcn/src/badge/Badge.tsx b/packages/shadcn/src/badge/Badge.tsx new file mode 100644 index 0000000000..153f3b9ebc --- /dev/null +++ b/packages/shadcn/src/badge/Badge.tsx @@ -0,0 +1,81 @@ +import { assertEmpty } from "@blocknote/core"; +import { ComponentProps } from "@blocknote/react"; +import { forwardRef } from "react"; + +import { cn } from "../lib/utils.js"; +import { useShadCNComponentsContext } from "../ShadCNComponentsContext.js"; + +export const Badge = forwardRef< + HTMLButtonElement, + ComponentProps["Generic"]["Badge"]["Root"] +>((props, ref) => { + const { + className, + text, + icon, + isSelected, + mainTooltip, + secondaryTooltip, + onClick, + ...rest + } = props; + + assertEmpty(rest, false); + + const ShadCNComponents = useShadCNComponentsContext()!; + + const badge = ( + + {icon} + {text} + + ); + + if (!mainTooltip) { + return badge; + } + + return ( + + + {badge} + + + {mainTooltip} + {secondaryTooltip && {secondaryTooltip}} + + + ); +}); + +export const BadgeGroup = forwardRef< + HTMLDivElement, + ComponentProps["Generic"]["Badge"]["Group"] +>((props, ref) => { + const { className, children, ...rest } = props; + + assertEmpty(rest); + + const ShadCNComponents = useShadCNComponentsContext()!; + + return ( + +
+ {children} +
+
+ ); +}); diff --git a/packages/shadcn/src/comments/Card.tsx b/packages/shadcn/src/comments/Card.tsx new file mode 100644 index 0000000000..21de48b9d6 --- /dev/null +++ b/packages/shadcn/src/comments/Card.tsx @@ -0,0 +1,48 @@ +import { assertEmpty } from "@blocknote/core"; +import { ComponentProps } from "@blocknote/react"; +import { forwardRef } from "react"; + +import { cn } from "../lib/utils.js"; +import { useShadCNComponentsContext } from "../ShadCNComponentsContext.js"; + +export const Card = forwardRef< + HTMLDivElement, + ComponentProps["Comments"]["Card"] +>((props, ref) => { + const { className, children, ...rest } = props; + + assertEmpty(rest); + + const ShadCNComponents = useShadCNComponentsContext()!; + + return ( + + {children} + + ); +}); + +export const CardSection = forwardRef< + HTMLDivElement, + ComponentProps["Comments"]["CardSection"] +>((props, ref) => { + const { className, children, ...rest } = props; + + assertEmpty(rest); + + return ( +
+ {children} +
+ ); +}); diff --git a/packages/shadcn/src/comments/Comment.tsx b/packages/shadcn/src/comments/Comment.tsx new file mode 100644 index 0000000000..dc734b185f --- /dev/null +++ b/packages/shadcn/src/comments/Comment.tsx @@ -0,0 +1,106 @@ +import { assertEmpty } from "@blocknote/core"; +import { ComponentProps } from "@blocknote/react"; +import { forwardRef, useState } from "react"; + +import { cn } from "../lib/utils.js"; +import { useShadCNComponentsContext } from "../ShadCNComponentsContext.js"; + +const AuthorInfo = forwardRef< + HTMLDivElement, + Pick +>((props, _ref) => { + const { authorInfo, timeString, ...rest } = props; + + assertEmpty(rest, false); + + const ShadCNComponents = useShadCNComponentsContext()!; + + if (authorInfo === "loading") { + return ( +
+ + +
+ ); + } + + return ( +
+ + + + {authorInfo.username[0]} + + + +
+ {authorInfo.username} + {timeString} +
+
+ ); +}); + +export const Comment = forwardRef< + HTMLDivElement, + ComponentProps["Comments"]["Comment"] +>((props, ref) => { + const { + className, + showActions, + authorInfo, + timeString, + actions, + children, + ...rest + } = props; + + assertEmpty(rest); + + const [hovered, setHovered] = useState(false); + + const doShowActions = + actions && + (showActions === true || + showActions === undefined || + (showActions === "hover" && hovered)); + + return ( +
setHovered(true)} + onMouseLeave={() => { + // TODO: This is getting triggered when opening the "more actions" + // popover? + setHovered(false); + }}> + {doShowActions ? ( +
+ {actions} +
+ ) : null} + + {children} +
+ ); +}); diff --git a/packages/shadcn/src/comments/Editor.tsx b/packages/shadcn/src/comments/Editor.tsx new file mode 100644 index 0000000000..052037ef36 --- /dev/null +++ b/packages/shadcn/src/comments/Editor.tsx @@ -0,0 +1,57 @@ +import { assertEmpty } from "@blocknote/core"; +import { + ComponentProps, + FormattingToolbar, + FormattingToolbarController, + getFormattingToolbarItems, +} from "@blocknote/react"; +import { forwardRef, useEffect } from "react"; + +import { BlockNoteView } from "../BlockNoteView.js"; +import { cn } from "../lib/utils.js"; + +export const Editor = forwardRef< + HTMLDivElement, + ComponentProps["Comments"]["Editor"] +>((props, ref) => { + const { className, onFocus, onBlur, editor, editable, ...rest } = props; + + assertEmpty(rest); + + // When we click the edit button on a comment, we also want to focus the + // comment editor + useEffect(() => { + if (editable) { + editor.focus(); + } + }, [editable, editor]); + + return ( + + + + ); +}); + +const CustomFormattingToolbar = () => { + const items = getFormattingToolbarItems([]).filter( + (el) => el.key !== "nestBlockButton" && el.key !== "unnestBlockButton" + ); + return ( + {items} + ); +}; diff --git a/packages/shadcn/src/components.ts b/packages/shadcn/src/components.ts index 2b7799e26e..c365118537 100644 --- a/packages/shadcn/src/components.ts +++ b/packages/shadcn/src/components.ts @@ -26,6 +26,10 @@ import { SuggestionMenuLoader } from "./suggestionMenu/SuggestionMenuLoader.js"; import { ExtendButton } from "./tableHandle/ExtendButton.js"; import { TableHandle } from "./tableHandle/TableHandle.js"; import { Toolbar, ToolbarButton, ToolbarSelect } from "./toolbar/Toolbar.js"; +import { Card, CardSection } from "./comments/Card.js"; +import { Comment } from "./comments/Comment.js"; +import { Editor } from "./comments/Editor.js"; +import { Badge, BadgeGroup } from "./badge/Badge.js"; import { PanelButton } from "./panel/PanelButton.js"; import { PanelFileInput } from "./panel/PanelFileInput.js"; @@ -71,8 +75,17 @@ export const components: Components = { Root: TableHandle, ExtendButton: ExtendButton, }, - // @ts-ignore TODO + Comments: { + Comment: Comment, + Editor: Editor, + Card: Card, + CardSection: CardSection, + }, Generic: { + Badge: { + Root: Badge, + Group: BadgeGroup, + }, Toolbar: { Root: Toolbar, Button: ToolbarButton, diff --git a/packages/shadcn/src/components/ui/avatar.tsx b/packages/shadcn/src/components/ui/avatar.tsx new file mode 100644 index 0000000000..df976cf21e --- /dev/null +++ b/packages/shadcn/src/components/ui/avatar.tsx @@ -0,0 +1,48 @@ +import * as React from "react"; +import * as AvatarPrimitive from "@radix-ui/react-avatar"; + +import { cn } from "../../lib/utils"; + +const Avatar = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +Avatar.displayName = AvatarPrimitive.Root.displayName; + +const AvatarImage = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AvatarImage.displayName = AvatarPrimitive.Image.displayName; + +const AvatarFallback = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName; + +export { Avatar, AvatarImage, AvatarFallback }; diff --git a/packages/shadcn/src/components/ui/skeleton.tsx b/packages/shadcn/src/components/ui/skeleton.tsx new file mode 100644 index 0000000000..4525c3a03d --- /dev/null +++ b/packages/shadcn/src/components/ui/skeleton.tsx @@ -0,0 +1,15 @@ +import { cn } from "../../lib/utils"; + +function Skeleton({ + className, + ...props +}: React.HTMLAttributes) { + return ( +
+ ); +} + +export { Skeleton }; diff --git a/packages/shadcn/src/menu/Menu.tsx b/packages/shadcn/src/menu/Menu.tsx index a62a4cd6ca..5e1f4ae135 100644 --- a/packages/shadcn/src/menu/Menu.tsx +++ b/packages/shadcn/src/menu/Menu.tsx @@ -56,7 +56,9 @@ export const Menu = (props: ComponentProps["Generic"]["Menu"]["Root"]) => { ); } else { return ( - + {children} ); diff --git a/packages/shadcn/src/popover/popover.tsx b/packages/shadcn/src/popover/popover.tsx index 9b44e5b665..a720d377d7 100644 --- a/packages/shadcn/src/popover/popover.tsx +++ b/packages/shadcn/src/popover/popover.tsx @@ -12,6 +12,7 @@ export const Popover = ( children, opened, position, // unused + onOpenChange, ...rest } = props; @@ -21,7 +22,7 @@ export const Popover = ( const ShadCNComponents = useShadCNComponentsContext()!; return ( - + {children} ); diff --git a/packages/shadcn/src/style.css b/packages/shadcn/src/style.css index 468753f44b..29f47fe5d5 100644 --- a/packages/shadcn/src/style.css +++ b/packages/shadcn/src/style.css @@ -173,3 +173,8 @@ overflow-x: auto; max-width: 100vw; } + +.bn-shadcn .bn-comment-actions-wrapper { + display: flex; + justify-content: flex-end; +} diff --git a/packages/shadcn/src/toolbar/Toolbar.tsx b/packages/shadcn/src/toolbar/Toolbar.tsx index 0a9f6c7d2f..e4eecda8d3 100644 --- a/packages/shadcn/src/toolbar/Toolbar.tsx +++ b/packages/shadcn/src/toolbar/Toolbar.tsx @@ -10,10 +10,15 @@ type ToolbarProps = ComponentProps["FormattingToolbar"]["Root"] & export const Toolbar = forwardRef( (props, ref) => { - const { className, children, onMouseEnter, onMouseLeave, ...rest } = props; + const { + className, + children, + onMouseEnter, + onMouseLeave, + variant, + ...rest + } = props; - // TODO - // @ts-ignore assertEmpty(rest); const ShadCNComponents = useShadCNComponentsContext()!; @@ -23,7 +28,8 @@ export const Toolbar = forwardRef(
( isDisabled, onClick, label, + variant, ...rest } = props; @@ -64,10 +71,14 @@ export const ToolbarButton = forwardRef( const trigger = isSelected === undefined ? ( ( mVcyz$=K^{w@-uj_l?_5OF4f1Dv%XFq2@``P>V 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 I^7-|@0Dko>&j0`b delta 6778 zcmXwec~n!^_Wu>fs(`OnQ6c2L`YK`=RD@KyZ{w6%hVjj=gAcWLLj{PnF<-yZ#Um{*fc>Dk>smELsB zoF7r4Irz^1=DUX$&VLHzO1pE>tAmQ@oRJd~b0-zcmaXsaGxq@iU>rV}MuXmfyJkwP z?oDvaJaVakg0|)a(Q$hK;1P$pvhSb2W6+k_4y1;5q{jz>rf&4AXjk#n9#s;lOx6r= zFB?4x01tefW5)K6b~O}YM6irpTVrIT&8?z94`ED3vbR^v~-QbZVcr1Y<$R7 zeX0&fv0I(Z^w@eAfWLgik`&I6HRtZw*)p^Xm_#|hY-NI_sLok?nWCqW?ZX;;&zcB| z*b{SPL$Nnv&w5KvIVb9FK}lKE7T%*`ApM4ElilsaTm6ocmH|&b(mjbw(>-VqcB;TL z;d;6AbxgfJrtGF?#1viY&UqABf4?k&$m2i-< z*m@O+0phfHvWUoY!V14Jc6R2t-CB0DdZ0m)X3mQ@y^b|`#9$wce#RN^_%%2Tfo8bN zn|q=X_GTiLQb$yQtG3N0vRAPK{x!L&kfUuDs@0Lm4!>w)MtG;6KyUj^Ob+M*qThrEzs~E=yZIs z*4bSjc;5bK&Z!c!kOot2r21DyddV8esrW?qQrtqE`pAZKj_Tm&+;m$9PJQDfZ+Fk_ zwJHDkmP=qu&#I`4|XK`OY_L>(cs9+`?QWUQl-V^zS15>(-}I)>Pe%0o@?d>^i-uw$^6U(=KVg} z%>wIiA5c)^owi+AL%RzrxJ+95+lP&Na5p+U>#3~w2SedqnMYp_#?L0>G++BdO6F;8 zYcm?@K;?3Q>s4;hZozC_XX8BgNz+xuPfZ$ggGB!ewS^^&sSU?5DM%+pwHE9=wE>%9}x>-ME(RR{~KN2JhKu4kt84e$bRXl5^3 zVd3m6m(9ubrP{|CNy)ty!j<>9^!4=oRKK$aS7&WpjlH3P5Z;eE#E*u+?tGh-^q^&Z>uj<3}GTr#XkKu2>2kRqJ| za}Qd}#1=A8)R}fmpYBjNtIO(psV}{`gE?#G*i^Tvz881<98*U`eB)j{Hp!&0bIU3R zBD{A0p7E2NcW&aFP(KdcLS~E_?6aaoGR8i zY2MY-VY zJq|^A;PNoygbb~#184fr%Ijr}&?rFGRSsr}uO?kkZi)Ou-BZf4o4phmQ_fs2mt zdG(f?iq<#f*V{FQJT?|_SuY$=JK0Z?LK?xR~w5PtJ6$%c-4cf4OyNYx(@})gWL(L z1|z8fiu12~a2^k+CiePn%~K9c>l6oTQ@tFk5OZbiFBF&6wx6ioak+$&RK!V9cPRVO z%yl>W@s)-D@Nhk981N}^=|WPaHEAV+`TmiZK%lsJc4c6bKZV3u5pI8<=UjwCJ;o6O zZws-P=Pd*hRa9m1t7bGHsW<36ieJOp4k_|_I^BE1j`RDLBV=We>%kH?f z!J7}{-e;w@d12En@GRsHEJo2EFTJ&%5n>-6O>;o$iw1iJ!~H@H%2boLtTt{y;`2?T zZEL~%0#Fs%$hm)3T6K8vNmA&{>HJiOT&tW$oUFa~j>V&iUy6d{af3J{|9Dz2rxX;U zyx7i}E#Oz?L&oEsC(W)}x*M^oB69`PA0uyL)^B^v+{uXyRHz|$?u*~HFReQjdCv`> zs7PBZu+3d-`*Kz~Pkq_fQ(w<$Y)$DgVcN6)5cJ}PNX{H%2*TuNx3L17-)x!Ag0M8K zG0>sZ(4bj=yFFVoBESxrqbPVJt?KvF!2hAZɚyuZ?eR`}3dX!vga>Rp=u$%7pu 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 diff --git a/tests/src/end-to-end/shadcn/shadcn.test.ts-snapshots/shadcn-drag-handle-menu-firefox-linux.png b/tests/src/end-to-end/shadcn/shadcn.test.ts-snapshots/shadcn-drag-handle-menu-firefox-linux.png index ad5b4929c10196487283aac7060996ec1212e1cb..e1dd29776cd795702825dcce0e900f9e4712918b 100644 GIT binary patch literal 25772 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 C>2)&z literal 25411 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 Date: Thu, 20 Feb 2025 13:18:23 +0100 Subject: [PATCH 053/144] change reaction auth --- .../threadstore/DefaultThreadStoreAuth.ts | 18 ++++++++++++++---- .../Comments/threadstore/ThreadStoreAuth.ts | 4 ++-- .../Comments/threadstore/YjsThreadStore.ts | 6 ++++-- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/packages/core/src/extensions/Comments/threadstore/DefaultThreadStoreAuth.ts b/packages/core/src/extensions/Comments/threadstore/DefaultThreadStoreAuth.ts index 3069f57564..7c385fdb7b 100644 --- a/packages/core/src/extensions/Comments/threadstore/DefaultThreadStoreAuth.ts +++ b/packages/core/src/extensions/Comments/threadstore/DefaultThreadStoreAuth.ts @@ -70,15 +70,25 @@ export class DefaultThreadStoreAuth extends ThreadStoreAuth { /** * Auth: should be possible by anyone with comment access + * + * Note: will also check if the user has already reacted with the same emoji. TBD: is that a nice design or should this responsibility be outside of auth? */ - canAddReaction(_comment: CommentData): boolean { - return true; + canAddReaction(comment: CommentData, emoji: string): boolean { + return !comment.reactions.some( + (reaction) => + reaction.emoji === emoji && reaction.userIds.includes(this.userId) + ); } /** * Auth: should be possible by anyone with comment access + * + * Note: will also check if the user has already reacted with the same emoji. TBD: is that a nice design or should this responsibility be outside of auth? */ - canDeleteReaction(_comment: CommentData): boolean { - return true; + canDeleteReaction(comment: CommentData, emoji: string): boolean { + return comment.reactions.some( + (reaction) => + reaction.emoji === emoji && reaction.userIds.includes(this.userId) + ); } } diff --git a/packages/core/src/extensions/Comments/threadstore/ThreadStoreAuth.ts b/packages/core/src/extensions/Comments/threadstore/ThreadStoreAuth.ts index 933c4b76a3..9131cab6f1 100644 --- a/packages/core/src/extensions/Comments/threadstore/ThreadStoreAuth.ts +++ b/packages/core/src/extensions/Comments/threadstore/ThreadStoreAuth.ts @@ -8,6 +8,6 @@ export abstract class ThreadStoreAuth { abstract canDeleteThread(thread: ThreadData): boolean; abstract canResolveThread(thread: ThreadData): boolean; abstract canUnresolveThread(thread: ThreadData): boolean; - abstract canAddReaction(comment: CommentData): boolean; - abstract canDeleteReaction(comment: CommentData): boolean; + abstract canAddReaction(comment: CommentData, emoji: string): boolean; + abstract canDeleteReaction(comment: CommentData, emoji: string): boolean; } diff --git a/packages/core/src/extensions/Comments/threadstore/YjsThreadStore.ts b/packages/core/src/extensions/Comments/threadstore/YjsThreadStore.ts index 52722e86c9..a9666328f7 100644 --- a/packages/core/src/extensions/Comments/threadstore/YjsThreadStore.ts +++ b/packages/core/src/extensions/Comments/threadstore/YjsThreadStore.ts @@ -255,7 +255,7 @@ export class YjsThreadStore extends ThreadStore { const yComment = yThread.get("comments").get(yCommentIndex); - if (!this.auth.canAddReaction(yMapToComment(yComment))) { + if (!this.auth.canAddReaction(yMapToComment(yComment), options.emoji)) { throw new Error("Not authorized"); } @@ -296,7 +296,9 @@ export class YjsThreadStore extends ThreadStore { const yComment = yThread.get("comments").get(yCommentIndex); - if (!this.auth.canDeleteReaction(yMapToComment(yComment))) { + if ( + !this.auth.canDeleteReaction(yMapToComment(yComment), options.emoji) + ) { throw new Error("Not authorized"); } From 5decc46e7bcb8df714405d1739b01223fb48e1af Mon Sep 17 00:00:00 2001 From: yousefed Date: Thu, 20 Feb 2025 13:23:08 +0100 Subject: [PATCH 054/144] make emoji optional --- .../Comments/threadstore/DefaultThreadStoreAuth.ts | 12 ++++++++++-- .../Comments/threadstore/ThreadStoreAuth.ts | 4 ++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/packages/core/src/extensions/Comments/threadstore/DefaultThreadStoreAuth.ts b/packages/core/src/extensions/Comments/threadstore/DefaultThreadStoreAuth.ts index 7c385fdb7b..f019ab1925 100644 --- a/packages/core/src/extensions/Comments/threadstore/DefaultThreadStoreAuth.ts +++ b/packages/core/src/extensions/Comments/threadstore/DefaultThreadStoreAuth.ts @@ -73,7 +73,11 @@ export class DefaultThreadStoreAuth extends ThreadStoreAuth { * * Note: will also check if the user has already reacted with the same emoji. TBD: is that a nice design or should this responsibility be outside of auth? */ - canAddReaction(comment: CommentData, emoji: string): boolean { + canAddReaction(comment: CommentData, emoji?: string): boolean { + if (!emoji) { + return true; + } + return !comment.reactions.some( (reaction) => reaction.emoji === emoji && reaction.userIds.includes(this.userId) @@ -85,7 +89,11 @@ export class DefaultThreadStoreAuth extends ThreadStoreAuth { * * Note: will also check if the user has already reacted with the same emoji. TBD: is that a nice design or should this responsibility be outside of auth? */ - canDeleteReaction(comment: CommentData, emoji: string): boolean { + canDeleteReaction(comment: CommentData, emoji?: string): boolean { + if (!emoji) { + return true; + } + return comment.reactions.some( (reaction) => reaction.emoji === emoji && reaction.userIds.includes(this.userId) diff --git a/packages/core/src/extensions/Comments/threadstore/ThreadStoreAuth.ts b/packages/core/src/extensions/Comments/threadstore/ThreadStoreAuth.ts index 9131cab6f1..452ad25b03 100644 --- a/packages/core/src/extensions/Comments/threadstore/ThreadStoreAuth.ts +++ b/packages/core/src/extensions/Comments/threadstore/ThreadStoreAuth.ts @@ -8,6 +8,6 @@ export abstract class ThreadStoreAuth { abstract canDeleteThread(thread: ThreadData): boolean; abstract canResolveThread(thread: ThreadData): boolean; abstract canUnresolveThread(thread: ThreadData): boolean; - abstract canAddReaction(comment: CommentData, emoji: string): boolean; - abstract canDeleteReaction(comment: CommentData, emoji: string): boolean; + abstract canAddReaction(comment: CommentData, emoji?: string): boolean; + abstract canDeleteReaction(comment: CommentData, emoji?: string): boolean; } From 4cb7af02bc53bee97297cf180cd35674821d036d Mon Sep 17 00:00:00 2001 From: yousefed Date: Thu, 20 Feb 2025 13:55:16 +0100 Subject: [PATCH 055/144] fix bug --- packages/core/src/editor/BlockNoteEditor.ts | 12 +++++++++++- .../core/src/extensions/Comments/CommentsPlugin.ts | 1 - 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/core/src/editor/BlockNoteEditor.ts b/packages/core/src/editor/BlockNoteEditor.ts index 5ce0c25fe1..df9dcd7001 100644 --- a/packages/core/src/editor/BlockNoteEditor.ts +++ b/packages/core/src/editor/BlockNoteEditor.ts @@ -92,6 +92,7 @@ import { en } from "../i18n/locales/index.js"; import { Plugin, Transaction } from "@tiptap/pm/state"; import { dropCursor } from "prosemirror-dropcursor"; import { EditorView } from "prosemirror-view"; +import { ySyncPluginKey } from "y-prosemirror"; import { createInternalHTMLSerializer } from "../api/exporters/html/internalHTMLSerializer.js"; import { inlineContentToNodes } from "../api/nodeConversions/blockToNode.js"; import { nodeToBlock } from "../api/nodeConversions/nodeToBlock.js"; @@ -789,6 +790,8 @@ export class BlockNoteEditor< /** * Executes a callback whenever the editor's contents change. * @param callback The callback to execute. + * + * @deprecated use `onChange` instead */ public onEditorContentChange(callback: () => void) { this._tiptapEditor.on("update", callback); @@ -797,6 +800,8 @@ export class BlockNoteEditor< /** * Executes a callback whenever the editor's selection changes. * @param callback The callback to execute. + * + * @deprecated use `onSelectionChange` instead */ public onEditorSelectionChange(callback: () => void) { this._tiptapEditor.on("selectionUpdate", callback); @@ -1244,7 +1249,12 @@ export class BlockNoteEditor< return; } - const cb = () => { + const cb = (e: { transaction: Transaction }) => { + if (e.transaction.getMeta(ySyncPluginKey)) { + // selection changed because of a yjs sync (i.e.: other user was typing) + // we don't want to trigger the callback in this case + return; + } callback(this); }; diff --git a/packages/core/src/extensions/Comments/CommentsPlugin.ts b/packages/core/src/extensions/Comments/CommentsPlugin.ts index 754db445b6..80a13211e0 100644 --- a/packages/core/src/extensions/Comments/CommentsPlugin.ts +++ b/packages/core/src/extensions/Comments/CommentsPlugin.ts @@ -150,7 +150,6 @@ export class CommentsPlugin extends EventEmitter { // TODO: remove settimeout setTimeout(() => { editor.onSelectionChange(() => { - // TODO: filter out yjs transactions if (this.pendingComment) { this.pendingComment = false; this.emitStateUpdate(); From 27a252331b2ccd0ec8e6978ebff59e25e12cce2f Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Thu, 20 Feb 2025 14:04:50 +0100 Subject: [PATCH 056/144] Extracted reaction badge WIP --- packages/ariakit/src/badge/Badge.tsx | 2 + packages/mantine/src/badge/Badge.tsx | 2 + .../react/src/components/Comments/Comment.tsx | 43 +++++++-------- .../src/components/Comments/ReactionBadge.tsx | 55 +++++++++++++++++++ .../react/src/editor/ComponentsContext.tsx | 1 + packages/shadcn/src/badge/Badge.tsx | 2 + 6 files changed, 82 insertions(+), 23 deletions(-) create mode 100644 packages/react/src/components/Comments/ReactionBadge.tsx diff --git a/packages/ariakit/src/badge/Badge.tsx b/packages/ariakit/src/badge/Badge.tsx index 0d89b8a592..92985ac251 100644 --- a/packages/ariakit/src/badge/Badge.tsx +++ b/packages/ariakit/src/badge/Badge.tsx @@ -22,6 +22,7 @@ export const Badge = forwardRef< mainTooltip, secondaryTooltip, onClick, + onMouseEnter, ...rest } = props; @@ -36,6 +37,7 @@ export const Badge = forwardRef< )} aria-selected={isSelected === true} onClick={(event) => onClick?.(event)} + onMouseEnter={onMouseEnter} ref={ref}> {icon} {text} diff --git a/packages/mantine/src/badge/Badge.tsx b/packages/mantine/src/badge/Badge.tsx index 3356e663c9..5f3d4c748e 100644 --- a/packages/mantine/src/badge/Badge.tsx +++ b/packages/mantine/src/badge/Badge.tsx @@ -22,6 +22,7 @@ export const Badge = forwardRef< mainTooltip, secondaryTooltip, onClick, + onMouseEnter, ...rest } = props; @@ -40,6 +41,7 @@ export const Badge = forwardRef< onClick?.(event); }} wrapperProps={{ + onMouseEnter, onMouseLeave: () => setHideTooltip(false), }} variant={"light"} diff --git a/packages/react/src/components/Comments/Comment.tsx b/packages/react/src/components/Comments/Comment.tsx index e30500e5dc..f7fb76add7 100644 --- a/packages/react/src/components/Comments/Comment.tsx +++ b/packages/react/src/components/Comments/Comment.tsx @@ -23,6 +23,7 @@ import { useCreateBlockNote } from "../../hooks/useCreateBlockNote.js"; import { useDictionary } from "../../i18n/dictionary.js"; import { EmojiPicker } from "./EmojiPicker.js"; import { CommentEditor } from "./CommentEditor.js"; +import { ReactionBadge } from "./ReactionBadge.js"; import { schema } from "./schema.js"; import { useUser } from "./useUsers.js"; @@ -138,14 +139,22 @@ export const Comment = ({ const onReactionSelect = useCallback( async (emoji: string) => { - // TODO: show error on failure? - await threadStore.addReaction({ - threadId: thread.id, - commentId: comment.id, - emoji, - }); + if (threadStore.auth.canAddReaction(comment, emoji)) { + // TODO: show error on failure? + await threadStore.addReaction({ + threadId: thread.id, + commentId: comment.id, + emoji, + }); + } else if (threadStore.auth.canDeleteReaction(comment, emoji)) { + await threadStore.deleteReaction({ + threadId: thread.id, + commentId: comment.id, + emoji, + }); + } }, - [comment.id, threadStore, thread.id] + [threadStore, comment, thread.id] ); const onResolve = useCallback(async () => { @@ -275,22 +284,10 @@ export const Comment = ({ "bn-comment-reactions" )}> {comment.reactions.map((reaction) => ( - onReactionSelect(reaction.emoji)} - mainTooltip={"Reacted by"} - secondaryTooltip={`${reaction.userIds.join( - "\n" - )}`} + ))} void; +}) => { + const Components = useComponentsContext()!; + + const editor = useBlockNoteEditor(); + if (!editor.comments) { + throw new Error( + "ReactionBadge must be used inside a BlockNote editor with comments enabled" + ); + } + + const reaction = props.comment.reactions.find( + (reaction) => reaction.emoji === props.emoji + ); + if (!reaction) { + throw new Error( + "Trying to render reaction badge for non-existing reaction" + ); + } + + const [userIds, setUserIds] = useState([]); + const users = useUsers(editor, userIds); + if (users.size) { + debugger; + } + + return ( + props.onReactionSelect(reaction.emoji)} + onMouseEnter={() => setUserIds(reaction.userIds)} + mainTooltip={"Reacted by"} + secondaryTooltip={`${Array.from(users.values()) + .map((user) => user.username) + .join("\n")}`} + /> + ); +}; diff --git a/packages/react/src/editor/ComponentsContext.tsx b/packages/react/src/editor/ComponentsContext.tsx index 6eb2e58f5f..42f6c3f2b7 100644 --- a/packages/react/src/editor/ComponentsContext.tsx +++ b/packages/react/src/editor/ComponentsContext.tsx @@ -218,6 +218,7 @@ export type ComponentProps = { mainTooltip?: string; secondaryTooltip?: string; onClick?: (event: React.MouseEvent) => void; + onMouseEnter?: () => void; }; Group: { className?: string; diff --git a/packages/shadcn/src/badge/Badge.tsx b/packages/shadcn/src/badge/Badge.tsx index 153f3b9ebc..368c94011a 100644 --- a/packages/shadcn/src/badge/Badge.tsx +++ b/packages/shadcn/src/badge/Badge.tsx @@ -17,6 +17,7 @@ export const Badge = forwardRef< mainTooltip, secondaryTooltip, onClick, + onMouseEnter, ...rest } = props; @@ -32,6 +33,7 @@ export const Badge = forwardRef< "bn-flex bn-items-center bn-gap-1 bn-rounded-full bn-h-7 bn-px-2.5" )} onClick={onClick} + onMouseEnter={onMouseEnter} ref={ref}> {icon} {text} From 65dfc202f2c018aa2cf4920f939841efcf5217a2 Mon Sep 17 00:00:00 2001 From: yousefed Date: Thu, 20 Feb 2025 14:37:28 +0100 Subject: [PATCH 057/144] fix useUsers --- .../src/components/Comments/ReactionBadge.tsx | 3 -- .../react/src/components/Comments/useUsers.ts | 30 +++++++++++-------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/packages/react/src/components/Comments/ReactionBadge.tsx b/packages/react/src/components/Comments/ReactionBadge.tsx index c22cdc677b..c876ad9453 100644 --- a/packages/react/src/components/Comments/ReactionBadge.tsx +++ b/packages/react/src/components/Comments/ReactionBadge.tsx @@ -30,9 +30,6 @@ export const ReactionBadge = (props: { const [userIds, setUserIds] = useState([]); const users = useUsers(editor, userIds); - if (users.size) { - debugger; - } return ( , @@ -19,12 +19,7 @@ export function useUsers( const store = comments.userStore; - // this ref works around this error: - // https://react.dev/reference/react/useSyncExternalStore#im-getting-an-error-the-result-of-getsnapshot-should-be-cached - // however, might not be a good practice to work around it this way - const usersRef = useRef>(); - - const getSnapshot = useCallback(() => { + const getUpdatedSnapshot = useCallback(() => { const map = new Map(); for (const id of userIds) { const user = store.getUser(id); @@ -35,23 +30,32 @@ export function useUsers( return map; }, [store, userIds]); - if (!usersRef.current) { - usersRef.current = getSnapshot(); - } + // this ref / memoworks around this error: + // https://react.dev/reference/react/useSyncExternalStore#im-getting-an-error-the-result-of-getsnapshot-should-be-cached + // however, might not be a good practice to work around it this way + + // We need to use a memo instead of a ref to make sure the snapshot is updated when the userIds change + const ref = useMemo(() => { + return { + current: getUpdatedSnapshot(), + }; + }, [getUpdatedSnapshot]); // note: this is inefficient as it will trigger a re-render even if other users (not in userIds) are updated const subscribe = useCallback( (cb: () => void) => { const ret = store.subscribe((_users) => { // update ref when changed - usersRef.current = getSnapshot(); + ref.current = getUpdatedSnapshot(); + + // calling cb() will make sure `useSyncExternalStore` will fetch the latest snapshot (which is ref.current) cb(); }); store.loadUsers(userIds); return ret; }, - [store, getSnapshot, userIds] + [store, getUpdatedSnapshot, userIds, ref] ); - return useSyncExternalStore(subscribe, () => usersRef.current!); + return useSyncExternalStore(subscribe, () => ref.current!); } From 2fe1052c3d393981e7b6989c7a7715eb000b306d Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Thu, 20 Feb 2025 14:53:41 +0100 Subject: [PATCH 058/144] Fixed formatting toolbar not showing up when editor is non-editable --- .../extensions/FormattingToolbar/FormattingToolbarPlugin.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/core/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts b/packages/core/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts index 0f08e8d6c5..660f4ac43f 100644 --- a/packages/core/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts +++ b/packages/core/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts @@ -25,7 +25,7 @@ export class FormattingToolbarView implements PluginView { state: EditorState; from: number; to: number; - }) => boolean = ({ state, from, to, view }) => { + }) => boolean = ({ state, from, to }) => { const { doc, selection } = state; const { empty } = selection; @@ -43,8 +43,7 @@ export class FormattingToolbarView implements PluginView { return false; } - // check view.hasFocus so that the toolbar doesn't show up when the editor is not focused or when for example a code block is focused - return !(!view.hasFocus() || empty || isEmptyTextBlock); + return !(empty || isEmptyTextBlock); }; constructor( From 165a014526de589a4d527a82b40e3e5428aab375 Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Thu, 20 Feb 2025 15:26:41 +0100 Subject: [PATCH 059/144] Fixed reaction badge tooltip line breaks and made leaving comment not hide popovers/menus in it --- packages/mantine/src/comments/Comment.tsx | 7 +++++-- packages/mantine/src/style.css | 1 + packages/mantine/src/toolbar/ToolbarButton.tsx | 11 +++++++++-- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/packages/mantine/src/comments/Comment.tsx b/packages/mantine/src/comments/Comment.tsx index e3af75a31f..72aa98929e 100644 --- a/packages/mantine/src/comments/Comment.tsx +++ b/packages/mantine/src/comments/Comment.tsx @@ -1,7 +1,7 @@ import { assertEmpty } from "@blocknote/core"; import { ComponentProps, mergeRefs } from "@blocknote/react"; import { Avatar, Group, Skeleton, Text } from "@mantine/core"; -import { useHover } from "@mantine/hooks"; +import { useFocusWithin, useHover } from "@mantine/hooks"; import { forwardRef } from "react"; const AuthorInfo = forwardRef< @@ -59,6 +59,7 @@ export const Comment = forwardRef< } = props; const { hovered, ref: hoverRef } = useHover(); + const { focused, ref: focusRef } = useFocusWithin(); const mergedRef = mergeRefs([ref, hoverRef]); assertEmpty(rest, false); @@ -66,12 +67,14 @@ export const Comment = forwardRef< actions && (showActions === true || showActions === undefined || - (showActions === "hover" && hovered)); + (showActions === "hover" && hovered) || + focused); return ( {doShowActions ? ( ( (e.currentTarget as HTMLButtonElement).focus(); } }} - onClick={onClick} + onClick={(event) => { + setHideTooltip(true); + onClick?.(event); + }} + // Mantine Menu.Target elements block mouseleave events for some reason, + // but pointerleave events work fine. + onPointerLeave={() => setHideTooltip(false)} aria-pressed={isSelected} data-selected={isSelected || undefined} data-test={ @@ -114,12 +120,13 @@ export const ToolbarButton = forwardRef( ); - if (!mainTooltip || hideTooltip) { + if (!mainTooltip) { return button; } return ( Date: Thu, 20 Feb 2025 17:44:53 +0100 Subject: [PATCH 060/144] Improved badge UX and made reactions hide when editing --- packages/mantine/src/style.css | 5 ++ .../react/src/components/Comments/Comment.tsx | 76 +++++++++---------- 2 files changed, 43 insertions(+), 38 deletions(-) diff --git a/packages/mantine/src/style.css b/packages/mantine/src/style.css index c76d274563..b1a309c0c7 100644 --- a/packages/mantine/src/style.css +++ b/packages/mantine/src/style.css @@ -658,6 +658,11 @@ color: var(--bn-colors-menu-text); } +.bn-mantine .bn-badge .mantine-Chip-label:hover { + border: 1px solid var(--mantine-primary-color-filled-hover); + color: var(--mantine-primary-color-filled-hover); +} + .bn-mantine .bn-badge .mantine-Chip-label > span:not(.mantine-Chip-iconWrapper) { display: inline-flex; gap: 4px; diff --git a/packages/react/src/components/Comments/Comment.tsx b/packages/react/src/components/Comments/Comment.tsx index f7fb76add7..ed2cd7dc36 100644 --- a/packages/react/src/components/Comments/Comment.tsx +++ b/packages/react/src/components/Comments/Comment.tsx @@ -269,15 +269,16 @@ export const Comment = ({ actions={actions} className={className}> {comment.body ? ( - <> - 0) || isEditing - ? ({ isEmpty }) => ( - <> - {showReactions && comment.reactions.length > 0 && ( + 0) || isEditing + ? ({ isEmpty }) => ( + <> + {showReactions && + comment.reactions.length > 0 && + !isEditing && ( )} - {isEditing && ( - - - Save - - - Cancel - - - )} - - ) - : undefined - } - /> - + {isEditing && ( + + + Save + + + Cancel + + + )} + + ) + : undefined + } + /> ) : ( // Soft deletes // TODO, test From 3fada1aeeb8edf29af457957c48841960485e6ce Mon Sep 17 00:00:00 2001 From: yousefed Date: Fri, 21 Feb 2025 12:00:42 +0100 Subject: [PATCH 061/144] wip --- examples/07-collaboration/04-comments/App.tsx | 19 +++++++++++++------ .../04-comments/tsconfig.json | 14 ++++++++++---- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/examples/07-collaboration/04-comments/App.tsx b/examples/07-collaboration/04-comments/App.tsx index 4b10f9db77..e5e9500faa 100644 --- a/examples/07-collaboration/04-comments/App.tsx +++ b/examples/07-collaboration/04-comments/App.tsx @@ -4,7 +4,7 @@ import { DefaultThreadStoreAuth, User, YjsThreadStore } from "@blocknote/core"; import { BlockNoteView } from "@blocknote/mantine"; import "@blocknote/mantine/style.css"; import { - BlockNoteContext, + BlockNoteViewEditor, ThreadStreamView, useCreateBlockNote, } from "@blocknote/react"; @@ -146,13 +146,20 @@ function Document() { }} value={user.id} /> - -
- + + +
+ +
+

Sidebar

- +
- +
); } diff --git a/examples/07-collaboration/04-comments/tsconfig.json b/examples/07-collaboration/04-comments/tsconfig.json index 4a76cf4c7d..1bd8ab3c57 100644 --- a/examples/07-collaboration/04-comments/tsconfig.json +++ b/examples/07-collaboration/04-comments/tsconfig.json @@ -3,7 +3,11 @@ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, - "lib": ["DOM", "DOM.Iterable", "ESNext"], + "lib": [ + "DOM", + "DOM.Iterable", + "ESNext" + ], "allowJs": false, "skipLibCheck": true, "esModuleInterop": false, @@ -18,8 +22,10 @@ "jsx": "react-jsx", "composite": true }, - "include": ["."], - "references": [ + "include": [ + "." + ], + "__ADD_FOR_LOCAL_DEV_references": [ { "path": "../../../packages/core/" }, @@ -27,4 +33,4 @@ "path": "../../../packages/react/" } ] -} +} \ No newline at end of file From 4d3d2ff29308223703960e9e00e65f27368eb14c Mon Sep 17 00:00:00 2001 From: yousefed Date: Fri, 21 Feb 2025 12:11:02 +0100 Subject: [PATCH 062/144] refactor: add renderEditor boolean to BlockNoteView --- packages/core/src/util/browser.ts | 2 +- packages/react/src/editor/BlockNoteContext.ts | 2 +- packages/react/src/editor/BlockNoteView.tsx | 186 ++++++++++++------ .../react/src/editor/BlockNoteViewContext.ts | 19 ++ 4 files changed, 149 insertions(+), 60 deletions(-) create mode 100644 packages/react/src/editor/BlockNoteViewContext.ts diff --git a/packages/core/src/util/browser.ts b/packages/core/src/util/browser.ts index 9ecdf3250d..ef626cee8c 100644 --- a/packages/core/src/util/browser.ts +++ b/packages/core/src/util/browser.ts @@ -12,7 +12,7 @@ export function formatKeyboardShortcut(shortcut: string, ctrlText = "Ctrl") { } } -export function mergeCSSClasses(...classes: string[]) { +export function mergeCSSClasses(...classes: (string | undefined)[]) { return classes.filter((c) => c).join(" "); } diff --git a/packages/react/src/editor/BlockNoteContext.ts b/packages/react/src/editor/BlockNoteContext.ts index 1a14ab4805..daabf51e26 100644 --- a/packages/react/src/editor/BlockNoteContext.ts +++ b/packages/react/src/editor/BlockNoteContext.ts @@ -10,7 +10,7 @@ import { } from "@blocknote/core"; import { createContext, useContext, useState } from "react"; -type BlockNoteContextValue< +export type BlockNoteContextValue< BSchema extends BlockSchema = DefaultBlockSchema, ISchema extends InlineContentSchema = DefaultInlineContentSchema, SSchema extends StyleSchema = DefaultStyleSchema diff --git a/packages/react/src/editor/BlockNoteView.tsx b/packages/react/src/editor/BlockNoteView.tsx index a18b10e49d..ad0b39f63f 100644 --- a/packages/react/src/editor/BlockNoteView.tsx +++ b/packages/react/src/editor/BlockNoteView.tsx @@ -18,11 +18,19 @@ import React, { import { useEditorChange } from "../hooks/useEditorChange.js"; import { useEditorSelectionChange } from "../hooks/useEditorSelectionChange.js"; import { usePrefersColorScheme } from "../hooks/usePrefersColorScheme.js"; -import { BlockNoteContext, useBlockNoteContext } from "./BlockNoteContext.js"; +import { + BlockNoteContext, + BlockNoteContextValue, + useBlockNoteContext, +} from "./BlockNoteContext.js"; import { BlockNoteDefaultUI, BlockNoteDefaultUIProps, } from "./BlockNoteDefaultUI.js"; +import { + BlockNoteViewContext, + useBlockNoteViewContext, +} from "./BlockNoteViewContext.js"; import { Portals, getContentComponent } from "./EditorContent.js"; import { ElementRenderer } from "./ElementRenderer.js"; import "./styles.css"; @@ -40,8 +48,18 @@ export type BlockNoteViewProps< theme?: "light" | "dark"; + /** + * Whether to render the editor element itself. + * When `false`, you're responsible for rendering the editor yourself using the `BlockNoteViewEditor` component. + * + * @default true + */ + renderEditor?: boolean; + /** * Locks the editor from being editable by the user if set to `false`. + * + * @default true */ editable?: boolean; /** @@ -86,9 +104,12 @@ function BlockNoteViewComponent< sideMenu, filePanel, tableHandles, + autoFocus, + renderEditor, ...rest } = props; + const doRenderEditor = renderEditor ?? true; // Used so other components (suggestion menu) can set // aria related props to the contenteditable div const [contentEditableProps, setContentEditableProps] = @@ -109,40 +130,6 @@ function BlockNoteViewComponent< editor.isEditable = editable !== false; }, [editable, editor]); - const renderChildren = useMemo(() => { - return ( - <> - {children} - - - ); - }, [ - children, - formattingToolbar, - linkToolbar, - slashMenu, - emojiPicker, - sideMenu, - filePanel, - tableHandles, - ]); - - const context = useMemo(() => { - return { - ...existingContext, - editor, - setContentEditableProps, - }; - }, [existingContext, editor]); - const setElementRenderer = useCallback( (ref: (typeof editor)["elementRenderer"]) => { editor.elementRenderer = ref; @@ -161,31 +148,57 @@ function BlockNoteViewComponent< [editor, portalManager] ); + // The BlockNoteContext makes sure the editor and some helper methods + // are always available to nesteed compoenents + const blockNoteContext: BlockNoteContextValue = useMemo(() => { + return { + ...existingContext, + editor, + setContentEditableProps, + }; + }, [existingContext, editor]); + + // We set defaultUIProps and editorProps on a different context, the BlockNoteViewContext. + // This BlockNoteViewContext is used to render the editor and the default UI. + const defaultUIProps = { + formattingToolbar, + linkToolbar, + slashMenu, + emojiPicker, + sideMenu, + filePanel, + tableHandles, + }; + + const editorProps = { + autoFocus, + className, + editorColorScheme, + mount, + contentEditableProps, + ref, + ...rest, + }; + return ( - - - {!editor.headless && ( - <> - -
+ + + {!editor.headless && ( + <> + + {doRenderEditor ? ( + {children} + ) : ( + children )} - data-color-scheme={editorColorScheme} - {...rest} - ref={ref}> -
- {renderChildren} -
- - )} + + )} +
); } @@ -200,3 +213,60 @@ export const BlockNoteViewRaw = React.forwardRef(BlockNoteViewComponent) as < ref?: React.ForwardedRef; } ) => ReturnType>; + +/** + * Renders the editor itself and the default UI elements + */ +export const BlockNoteViewEditor = (props: { children: ReactNode }) => { + const ctx = useBlockNoteViewContext()!; + return ( + + {/* Renders the UI elements such as formatting toolbar, etc, unless they have been explicitly disabled in defaultUIProps */} + + {/* Manually passed in children, such as customized UI elements / controllers */} + {props.children} + + ); +}; + +/** + * Renders the container div + contentEditable div. + */ +const EditorElement = ( + props: { + className?: string; + editorColorScheme?: string; + autoFocus?: boolean; + mount: (element: HTMLElement | null) => void; + contentEditableProps?: Record; + children: ReactNode; + } & HTMLAttributes +) => { + const { + className, + editorColorScheme, + autoFocus, + mount, + children, + contentEditableProps, + ...rest + } = props; + return ( + // The container wraps the contentEditable div and UI Elements such as sidebar, formatting toolbar, etc. +
+ {/* The actual contentEditable that Prosemirror mounts to */} +
+ {/* The UI elements such as sidebar, formatting toolbar, etc. */} + {children} +
+ ); +}; diff --git a/packages/react/src/editor/BlockNoteViewContext.ts b/packages/react/src/editor/BlockNoteViewContext.ts new file mode 100644 index 0000000000..f285ddace4 --- /dev/null +++ b/packages/react/src/editor/BlockNoteViewContext.ts @@ -0,0 +1,19 @@ +import { createContext, useContext } from "react"; +import { BlockNoteDefaultUIProps } from "./BlockNoteDefaultUI.js"; + +export type BlockNoteViewContextValue = { + editorProps: any; + defaultUIProps: BlockNoteDefaultUIProps; +}; + +export const BlockNoteViewContext = createContext< + BlockNoteViewContextValue | undefined +>(undefined); + +export function useBlockNoteViewContext(): + | BlockNoteViewContextValue + | undefined { + const context = useContext(BlockNoteViewContext) as any; + + return context; +} From e3e58679283e5b4df3aca8b316c4f92b80ea9b0d Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Fri, 21 Feb 2025 12:14:06 +0100 Subject: [PATCH 063/144] Fixed new comments sometimes not being selectable --- .../core/src/extensions/Comments/CommentsPlugin.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/packages/core/src/extensions/Comments/CommentsPlugin.ts b/packages/core/src/extensions/Comments/CommentsPlugin.ts index 80a13211e0..7331053028 100644 --- a/packages/core/src/extensions/Comments/CommentsPlugin.ts +++ b/packages/core/src/extensions/Comments/CommentsPlugin.ts @@ -98,7 +98,7 @@ export class CommentsPlugin extends EventEmitter { const markType = mark.type; const markThreadId = mark.attrs.threadId; const thread = threads.get(markThreadId); - const isOrphan = !thread || thread.resolved || thread.deletedAt; + const isOrphan = !!(!thread || thread.resolved || thread.deletedAt); if (isOrphan !== mark.attrs.orphan) { const { tr } = ttEditor.state; @@ -204,13 +204,9 @@ export class CommentsPlugin extends EventEmitter { return; } const commentMark = node.marks.find( - (mark) => mark.type.name === markType + (mark) => mark.type.name === markType && mark.attrs.orphan !== true ); - // don't allow selecting orphaned threads - if (commentMark?.attrs.orphan) { - selectThread(undefined); - return; - } + const threadId = commentMark?.attrs.threadId as string | undefined; selectThread(threadId); }, @@ -246,6 +242,7 @@ export class CommentsPlugin extends EventEmitter { }) { const thread = await this.threadStore.createThread(options); this.editor._tiptapEditor.commands.setMark(this.markType, { + orphan: false, threadId: thread.id, }); } From bcd375d501734c4028ac7c0ec57b645861f1fa48 Mon Sep 17 00:00:00 2001 From: yousefed Date: Fri, 21 Feb 2025 12:35:45 +0100 Subject: [PATCH 064/144] fix unnecessary rerender --- .../core/src/extensions/Comments/CommentsPlugin.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/core/src/extensions/Comments/CommentsPlugin.ts b/packages/core/src/extensions/Comments/CommentsPlugin.ts index 2e61701a3f..275d7d29b7 100644 --- a/packages/core/src/extensions/Comments/CommentsPlugin.ts +++ b/packages/core/src/extensions/Comments/CommentsPlugin.ts @@ -188,13 +188,9 @@ export class CommentsPlugin extends EventEmitter { return; } - const selectThread = (threadId: string | undefined) => { - self.selectThread(threadId); - }; - const node = view.state.doc.nodeAt(pos); if (!node) { - selectThread(undefined); + self.selectThread(undefined); return; } const commentMark = node.marks.find( @@ -202,11 +198,11 @@ export class CommentsPlugin extends EventEmitter { ); // don't allow selecting orphaned threads if (commentMark?.attrs.orphan) { - selectThread(undefined); + self.selectThread(undefined); return; } const threadId = commentMark?.attrs.threadId as string | undefined; - selectThread(threadId); + self.selectThread(threadId); }, }, }); @@ -222,6 +218,9 @@ export class CommentsPlugin extends EventEmitter { } public selectThread(threadId: string | undefined) { + if (this.selectedThreadId === threadId) { + return; + } this.selectedThreadId = threadId; this.emitStateUpdate(); this.editor.dispatch( From 6bdbf155c7bfde8b8d4be11169066ac136e517d7 Mon Sep 17 00:00:00 2001 From: yousefed Date: Fri, 21 Feb 2025 14:15:33 +0100 Subject: [PATCH 065/144] remove unused files --- .../react/src/schema/@util/CoreMarkView.ts | 108 ------------------ .../src/schema/@util/CoreMarkViewOptions.ts | 32 ------ .../src/schema/@util/ReactMarkViewOptions.ts | 16 --- .../schema/@util/ReactMarkViewRenderer.tsx | 73 ------------ .../react/src/schema/@util/markViewContext.ts | 33 ------ packages/react/src/schema/ReactStyleSpec.tsx | 2 +- 6 files changed, 1 insertion(+), 263 deletions(-) delete mode 100644 packages/react/src/schema/@util/CoreMarkView.ts delete mode 100644 packages/react/src/schema/@util/CoreMarkViewOptions.ts delete mode 100644 packages/react/src/schema/@util/ReactMarkViewOptions.ts delete mode 100644 packages/react/src/schema/@util/ReactMarkViewRenderer.tsx delete mode 100644 packages/react/src/schema/@util/markViewContext.ts diff --git a/packages/react/src/schema/@util/CoreMarkView.ts b/packages/react/src/schema/@util/CoreMarkView.ts deleted file mode 100644 index 92085cbc2e..0000000000 --- a/packages/react/src/schema/@util/CoreMarkView.ts +++ /dev/null @@ -1,108 +0,0 @@ -import type { Mark } from "prosemirror-model"; -import type { - EditorView, - MarkView, - ViewMutationRecord, -} from "prosemirror-view"; - -import type { BlockNoteEditor } from "@blocknote/core"; -import type { - CoreMarkViewSpec, - CoreMarkViewUserOptions, - MarkViewDOMSpec, -} from "./CoreMarkViewOptions"; - -/* eslint-disable curly */ - -export class CoreMarkView implements MarkView { - dom: HTMLElement; - contentDOM: HTMLElement | undefined; - mark: Mark; - view: EditorView; - inline: boolean; - options: CoreMarkViewUserOptions; - editor: BlockNoteEditor; - - #createElement(as?: MarkViewDOMSpec) { - const { inline, mark } = this; - return as == null - ? document.createElement(inline ? "span" : "div") - : as instanceof HTMLElement - ? as - : as instanceof Function - ? as(mark) - : document.createElement(as); - } - - createDOM(as?: MarkViewDOMSpec) { - return this.#createElement(as); - } - - createContentDOM(as?: MarkViewDOMSpec) { - return this.#createElement(as); - } - - constructor({ - mark, - view, - inline, - options, - editor, // BlockNote specific - }: CoreMarkViewSpec) { - this.mark = mark; - this.view = view; - this.inline = inline; - this.options = options; - this.editor = editor; - - this.dom = this.createDOM(options.as); - this.contentDOM = options.contentAs - ? this.createContentDOM(options.contentAs) - : undefined; - this.dom.setAttribute("data-mark-view-root", "true"); - if (this.contentDOM) { - this.contentDOM.setAttribute("data-mark-view-content", "true"); - this.contentDOM.style.whiteSpace = "inherit"; - } - } - - get component() { - return this.options.component; - } - - shouldIgnoreMutation: (mutation: ViewMutationRecord) => boolean = ( - mutation - ) => { - if (!this.dom || !this.contentDOM) return true; - - if (mutation.type === "selection") return false; - - if (this.contentDOM === mutation.target && mutation.type === "attributes") - return true; - - if (this.contentDOM.contains(mutation.target)) return false; - - return true; - }; - - ignoreMutation: (mutation: ViewMutationRecord) => boolean = (mutation) => { - if (!this.dom || !this.contentDOM) return true; - - let result; - - const userIgnoreMutation = this.options.ignoreMutation; - - if (userIgnoreMutation) result = userIgnoreMutation(mutation); - - if (typeof result !== "boolean") - result = this.shouldIgnoreMutation(mutation); - - return result; - }; - - public destroy() { - this.options.destroy?.(); - this.dom.remove(); - this.contentDOM?.remove(); - } -} diff --git a/packages/react/src/schema/@util/CoreMarkViewOptions.ts b/packages/react/src/schema/@util/CoreMarkViewOptions.ts deleted file mode 100644 index dca5fa4909..0000000000 --- a/packages/react/src/schema/@util/CoreMarkViewOptions.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { BlockNoteEditor } from "@blocknote/core"; -import type { Mark } from "prosemirror-model"; -import type { EditorView, ViewMutationRecord } from "prosemirror-view"; - -export type MarkViewDOMSpec = - | string - | HTMLElement - | ((mark: Mark) => HTMLElement); - -export interface CoreMarkViewUserOptions { - // DOM - as?: MarkViewDOMSpec; - contentAs?: MarkViewDOMSpec; - - // Component - component: Component; - - // Overrides - ignoreMutation?: (mutation: ViewMutationRecord) => boolean | void; - destroy?: () => void; -} - -export interface CoreMarkViewSpec { - mark: Mark; - view: EditorView; - inline: boolean; - - options: CoreMarkViewUserOptions; - - // BlockNote specific - editor: BlockNoteEditor; -} diff --git a/packages/react/src/schema/@util/ReactMarkViewOptions.ts b/packages/react/src/schema/@util/ReactMarkViewOptions.ts deleted file mode 100644 index d61350cb56..0000000000 --- a/packages/react/src/schema/@util/ReactMarkViewOptions.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { FC } from "react"; -import type { - CoreMarkViewSpec, - CoreMarkViewUserOptions, -} from "./CoreMarkViewOptions.js"; - -// export type ReactMarkViewComponent = ComponentType>; - -export type ReactMarkViewComponent = - | FC<{ contentRef: (el: HTMLElement | null) => void }> - | FC<{ contentRef: (el: HTMLElement | null) => void; value: string }>; - -export type ReactMarkViewSpec = CoreMarkViewSpec; - -export type ReactMarkViewUserOptions = - CoreMarkViewUserOptions; diff --git a/packages/react/src/schema/@util/ReactMarkViewRenderer.tsx b/packages/react/src/schema/@util/ReactMarkViewRenderer.tsx deleted file mode 100644 index a156059f0a..0000000000 --- a/packages/react/src/schema/@util/ReactMarkViewRenderer.tsx +++ /dev/null @@ -1,73 +0,0 @@ -// import { nanoid } from "nanoid"; -import { CoreMarkView } from "./CoreMarkView.js"; - -// import type { ReactRenderer } from "../ReactRenderer"; -import type { MarkViewContext } from "./markViewContext.js"; -import type { ReactMarkViewComponent } from "./ReactMarkViewOptions.js"; - -/* eslint-disable curly */ - -export class ReactMarkView extends CoreMarkView { - // implements ReactRenderer - // key: string = nanoid(); - id = Math.floor(Math.random() * 0xffffffff).toString(); - - context: MarkViewContext = { - contentRef: (element) => { - if (element && this.contentDOM && element.firstChild !== this.contentDOM) - element.appendChild(this.contentDOM); - }, - view: this.view, - - mark: this.mark, - }; - - updateContext = () => { - Object.assign(this.context, { - mark: this.mark, - }); - }; - - // render = () => { - // const UserComponent = this.component; - - // return createPortal( - // - // - // , - // this.dom, - // this.key - // ); - // }; - - render = () => { - this.editor._tiptapEditor.contentComponent.setRenderer( - this.id, - this.renderer() - ); - }; - - destroy = () => { - super.destroy(); - this.editor._tiptapEditor.contentComponent.removeRenderer(this.id); - }; - - renderer = () => { - const UserComponent = this.component; - - const props: any = {}; - - if (this.mark.attrs.stringValue) { - props.value = this.mark.attrs.stringValue; - } - - return { - reactElement: ( - // - - // - ), - element: this.dom, - }; - }; -} diff --git a/packages/react/src/schema/@util/markViewContext.ts b/packages/react/src/schema/@util/markViewContext.ts deleted file mode 100644 index ce5254b4e5..0000000000 --- a/packages/react/src/schema/@util/markViewContext.ts +++ /dev/null @@ -1,33 +0,0 @@ -import type { Mark } from "prosemirror-model"; -import type { EditorView, MarkViewConstructor } from "prosemirror-view"; -import { createContext, useContext } from "react"; -import type { ReactMarkViewUserOptions } from "./ReactMarkViewOptions.js"; - -export type MarkViewContentRef = (node: HTMLElement | null) => void; - -export interface MarkViewContext { - // won't change - contentRef: MarkViewContentRef; - view: EditorView; - mark: Mark; -} - -export const markViewContext = createContext({ - contentRef: () => { - // nothing to do - }, - view: null as never, - mark: null as never, -}); - -export const useMarkViewContext = () => useContext(markViewContext); - -export const createMarkViewContext = createContext< - (options: ReactMarkViewUserOptions) => MarkViewConstructor ->((_options) => { - throw new Error( - "No ProsemirrorAdapterProvider detected, maybe you need to wrap the component with the Editor with ProsemirrorAdapterProvider?" - ); -}); - -export const useMarkViewFactory = () => useContext(createMarkViewContext); diff --git a/packages/react/src/schema/ReactStyleSpec.tsx b/packages/react/src/schema/ReactStyleSpec.tsx index 61c3c4234d..43c56a3ce2 100644 --- a/packages/react/src/schema/ReactStyleSpec.tsx +++ b/packages/react/src/schema/ReactStyleSpec.tsx @@ -7,8 +7,8 @@ import { } from "@blocknote/core"; import { Mark } from "@tiptap/react"; import { FC } from "react"; -import { ReactMarkView } from "./@util/ReactMarkViewRenderer.js"; import { renderToDOMSpec } from "./@util/ReactRenderUtil.js"; +import { ReactMarkView } from "./markviews/ReactMarkViewRenderer.js"; // this file is mostly analogoues to `customBlocks.ts`, but for React blocks From ec26b3057b07c127c4522a89b460ddfe9eec037b Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Fri, 21 Feb 2025 19:20:17 +0100 Subject: [PATCH 066/144] Improved UX/UI --- examples/07-collaboration/04-comments/App.tsx | 10 +- .../07-collaboration/04-comments/style.css | 97 +++++++++++++++++++ packages/ariakit/src/comments/Card.tsx | 3 +- packages/mantine/src/comments/Card.tsx | 11 ++- .../mantine/src/toolbar/ToolbarButton.tsx | 4 + .../src/components/Comments/EmojiPicker.tsx | 6 ++ .../components/Comments/ThreadStreamView.tsx | 16 ++- packages/react/src/editor/BlockNoteView.tsx | 2 +- packages/shadcn/src/comments/Card.tsx | 3 +- playground/src/style.css | 10 ++ 10 files changed, 150 insertions(+), 12 deletions(-) create mode 100644 examples/07-collaboration/04-comments/style.css diff --git a/examples/07-collaboration/04-comments/App.tsx b/examples/07-collaboration/04-comments/App.tsx index e5e9500faa..1d4b9441e1 100644 --- a/examples/07-collaboration/04-comments/App.tsx +++ b/examples/07-collaboration/04-comments/App.tsx @@ -12,6 +12,8 @@ import { Select } from "@mantine/core"; import { YDocProvider, useYDoc, useYjsProvider } from "@y-sweet/react"; import { useMemo, useState } from "react"; +import "./style.css"; + const colors = [ "#958DF1", "#F98181", @@ -128,7 +130,7 @@ function Document() { // TODO: make sure comment button / formatting toolbar appears for comment-only users return ( -
+
+ {/* render the actual editor */}
); diff --git a/examples/07-collaboration/04-comments/userdata.ts b/examples/07-collaboration/04-comments/userdata.ts new file mode 100644 index 0000000000..0714cf6e9f --- /dev/null +++ b/examples/07-collaboration/04-comments/userdata.ts @@ -0,0 +1,47 @@ +import type { User } from "@blocknote/core"; + +const colors = [ + "#958DF1", + "#F98181", + "#FBBC88", + "#FAF594", + "#70CFF8", + "#94FADB", + "#B9F18D", +]; + +const getRandomElement = (list: any[]) => + list[Math.floor(Math.random() * list.length)]; + +export const getRandomColor = () => getRandomElement(colors); + +export type MyUserType = User & { + role: "editor" | "comment"; +}; + +export const HARDCODED_USERS: MyUserType[] = [ + { + id: "1", + username: "John Doe", + avatarUrl: "https://placehold.co/100x100?text=John", + role: "editor", + }, + { + id: "2", + username: "Jane Doe", + avatarUrl: "https://placehold.co/100x100?text=Jane", + role: "editor", + }, + { + id: "3", + username: "Bob Smith", + avatarUrl: "https://placehold.co/100x100?text=Bob", + role: "comment", + }, + { + id: "4", + username: "Betty Smith", + avatarUrl: "https://placehold.co/100x100?text=Betty", + role: "comment", + }, +]; From 4d293ef1679d45f1cb4dcb268478726faab2fdf3 Mon Sep 17 00:00:00 2001 From: yousefed Date: Sat, 22 Feb 2025 17:09:52 +0100 Subject: [PATCH 071/144] update docs --- docs/pages/docs/collaboration/comments.mdx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/pages/docs/collaboration/comments.mdx b/docs/pages/docs/collaboration/comments.mdx index 62319b6493..0d9210f114 100644 --- a/docs/pages/docs/collaboration/comments.mdx +++ b/docs/pages/docs/collaboration/comments.mdx @@ -54,7 +54,7 @@ const threadStore = new YjsThreadStore( ); ``` -Note: While this is the easiest to implement, it requires users to have write access to the Yjs document to leave comments. Also, without proper server-side validation, any user could technically modify other users' comments. +_Note: While this is the easiest to implement, it requires users to have write access to the Yjs document to leave comments. Also, without proper server-side validation, any user could technically modify other users' comments._ ### `RESTYjsThreadStore` @@ -77,6 +77,8 @@ const threadStore = new RESTYjsThreadStore( An example implementation of the REST API can be found in the [example repository](https://github.com/TypeCellOS/BlockNote-demo-nextjs-hocuspocus). +_Note: Because writes are executed via a REST API, the `RESTYjsThreadStore` is not suitable for local-first applications that should be able to add and edit comments offline._ + ### `TiptapThreadStore` The `TiptapThreadStore` integrates with Tiptap's collaboration provider for comment management. This implementation is designed specifically for use with Tiptap's collaborative editing features. @@ -107,7 +109,7 @@ The `ThreadStoreAuth` class defines the authorization rules for interacting with The `DefaultThreadStoreAuth` class provides a basic implementation of the `ThreadStoreAuth` class. It takes a user ID and a role ("comment" or "editor") and implements the rules. See the [source code](https://github.com/TypeCellOS/BlockNote/blob/main/packages/core/src/extensions/Comments/threadstore/DefaultThreadStoreAuth.ts) for more details. -_Note:_ The `ThreadStoreAuth` only used to show / hide options in the UI. To secure comment related data, you still need to implement your own server-side validation (e.g. using `RESTYjsThreadStore` and a secure REST API). +_Note: The `ThreadStoreAuth` only used to show / hide options in the UI. To secure comment related data, you still need to implement your own server-side validation (e.g. using `RESTYjsThreadStore` and a secure REST API)._ ## `resolveUsers` function From f35a66a89afc1b451907705d37be825abeeb39cd Mon Sep 17 00:00:00 2001 From: yousefed Date: Sun, 23 Feb 2025 12:43:59 +0100 Subject: [PATCH 072/144] revert liveblocks, will be separate PR --- .../02-liveblocks/.bnexample.json | 10 ++-- .../07-collaboration/02-liveblocks/App.tsx | 54 ++++++++++++------- .../02-liveblocks/Editor.tsx.bak | 14 ----- .../02-liveblocks/Room.tsx.bak | 51 ------------------ .../02-liveblocks/Threads.tsx.bak | 29 ---------- .../02-liveblocks/globals.css | 27 ---------- .../02-liveblocks/package.json | 10 ++-- playground/package.json | 10 +--- 8 files changed, 48 insertions(+), 157 deletions(-) delete mode 100644 examples/07-collaboration/02-liveblocks/Editor.tsx.bak delete mode 100644 examples/07-collaboration/02-liveblocks/Room.tsx.bak delete mode 100644 examples/07-collaboration/02-liveblocks/Threads.tsx.bak delete mode 100644 examples/07-collaboration/02-liveblocks/globals.css diff --git a/examples/07-collaboration/02-liveblocks/.bnexample.json b/examples/07-collaboration/02-liveblocks/.bnexample.json index 441b6f9f0b..9498aff1dd 100644 --- a/examples/07-collaboration/02-liveblocks/.bnexample.json +++ b/examples/07-collaboration/02-liveblocks/.bnexample.json @@ -2,11 +2,11 @@ "playground": true, "docs": true, "author": "yousefed", - "tags": ["Advanced", "Liveblocks", "Collaboration"], + "tags": ["Advanced", "Saving/Loading", "Collaboration"], "dependencies": { - "@liveblocks/client": "^2.11.0", - "@liveblocks/react": "^2.11.0", - "@liveblocks/react-ui": "^2.11.0", - "@liveblocks/react-blocknote": "^2.11.0" + "@liveblocks/client": "^1.10.0", + "@liveblocks/react": "^1.10.0", + "@liveblocks/yjs": "^1.10.0", + "yjs": "^13.6.15" } } diff --git a/examples/07-collaboration/02-liveblocks/App.tsx b/examples/07-collaboration/02-liveblocks/App.tsx index 6d2d99f25c..6a64dc8c29 100644 --- a/examples/07-collaboration/02-liveblocks/App.tsx +++ b/examples/07-collaboration/02-liveblocks/App.tsx @@ -1,23 +1,41 @@ -// import "@blocknote/core/fonts/inter.css"; -// import "@blocknote/mantine/style.css"; +import "@blocknote/core/fonts/inter.css"; +import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/mantine/style.css"; +import { useCreateBlockNote } from "@blocknote/react"; +import { createClient } from "@liveblocks/client"; +import LiveblocksProvider from "@liveblocks/yjs"; +import * as Y from "yjs"; -// // .. in imports are temporary for development (vite setup) +// Sets up Liveblocks client. +const client = createClient({ + publicApiKey: + "pk_dev_lJAS4XHx3l1e0x_Gh9VMtrvo8PEB1vrNarC2YRtAOp4t6i9_QAcSX2U953GS6v7B", +}); +// Enters a multiplayer room. +// Use a unique name as a "room" for your application. +const { room } = client.enterRoom("your-project-name", { + initialPresence: {}, +}); -// // Need to be imported before @liveblocks/react-blocknote styles -// import "@liveblocks/react-ui/styles.css"; -// // Need to be imported after @liveblocks/react-ui styles -// import "@liveblocks/react-blocknote/styles.css"; - -// import { Editor } from "./Editor.jsx"; -// import { Room } from "./Room.jsx"; -// import "./globals.css"; +// Sets up Yjs document and Liveblocks Yjs provider. +const doc = new Y.Doc(); +const provider = new LiveblocksProvider(room, doc); export default function App() { - // Renders the editor instance using a React component. - return ( -
TODO
- // - // - // - ); + const editor = useCreateBlockNote({ + collaboration: { + // The Yjs Provider responsible for transporting updates: + provider, + // Where to store BlockNote data in the Y.Doc: + fragment: doc.getXmlFragment("document-store"), + // Information (name and color) for this user: + user: { + name: "My Username", + color: "#ff0000", + }, + }, + }); + + // Renders the editor instance. + return ; } diff --git a/examples/07-collaboration/02-liveblocks/Editor.tsx.bak b/examples/07-collaboration/02-liveblocks/Editor.tsx.bak deleted file mode 100644 index 0d5a87a488..0000000000 --- a/examples/07-collaboration/02-liveblocks/Editor.tsx.bak +++ /dev/null @@ -1,14 +0,0 @@ -"use client"; - -import { BlockNoteView } from "@blocknote/mantine"; -import { useCreateBlockNoteWithLiveblocks } from "@liveblocks/react-blocknote"; -import { Threads } from "./Threads"; -export function Editor() { - const editor = useCreateBlockNoteWithLiveblocks({}); - - return ( - - - - ); -} diff --git a/examples/07-collaboration/02-liveblocks/Room.tsx.bak b/examples/07-collaboration/02-liveblocks/Room.tsx.bak deleted file mode 100644 index e25db82357..0000000000 --- a/examples/07-collaboration/02-liveblocks/Room.tsx.bak +++ /dev/null @@ -1,51 +0,0 @@ -"use client"; - -import { - ClientSideSuspense, - LiveblocksProvider, - RoomProvider, -} from "@liveblocks/react/suspense"; -import { ReactNode } from "react"; - -const users = [ - { - id: "1", - name: "John Doe", - avatar: "https://liveblocks.io/avatars/avatar-1.png", - }, - { - id: "2", - name: "Alice Smith", - avatar: "https://liveblocks.io/avatars/avatar-2.png", - }, - { - id: "3", - name: "Bob Johnson", - avatar: "https://liveblocks.io/avatars/avatar-3.png", - }, -]; - -export function Room({ children }: { children: ReactNode }) { - return ( - { - return users - .filter((user) => - user.name.toLowerCase().startsWith(args.text.toLowerCase()) - ) - .map((user) => user.id); - }} - resolveUsers={async (args) => { - return args.userIds.map((id) => users.find((user) => user.id === id)); - }}> - - Loading…
}> - {children} - - - - ); -} diff --git a/examples/07-collaboration/02-liveblocks/Threads.tsx.bak b/examples/07-collaboration/02-liveblocks/Threads.tsx.bak deleted file mode 100644 index d2b7a5f40d..0000000000 --- a/examples/07-collaboration/02-liveblocks/Threads.tsx.bak +++ /dev/null @@ -1,29 +0,0 @@ -import { BlockNoteEditor } from "@blocknote/core"; -import { - AnchoredThreads, - FloatingComposer, - FloatingThreads, -} from "@liveblocks/react-blocknote"; -import { useThreads } from "@liveblocks/react/suspense"; - -export function Threads({ - editor, -}: { - editor: BlockNoteEditor; -}) { - const { threads } = useThreads({ query: { resolved: false } }); - - return ( - <> -
- -
- - - - ); -} diff --git a/examples/07-collaboration/02-liveblocks/globals.css b/examples/07-collaboration/02-liveblocks/globals.css deleted file mode 100644 index 66ba5d06a7..0000000000 --- a/examples/07-collaboration/02-liveblocks/globals.css +++ /dev/null @@ -1,27 +0,0 @@ -/* For mobile */ -.floating-threads { - display: none; -} - -/* For desktop */ -.anchored-threads { - display: block; - max-width: 300px; - width: 100%; - position: absolute; - right: 12px; -} - -@media (max-width: 640px) { - .floating-threads { - display: block; - } - - .anchored-threads { - display: none; - } -} - -.lb-portal { - z-index: 3000; -} diff --git a/examples/07-collaboration/02-liveblocks/package.json b/examples/07-collaboration/02-liveblocks/package.json index fbbffced48..691ab0dd63 100644 --- a/examples/07-collaboration/02-liveblocks/package.json +++ b/examples/07-collaboration/02-liveblocks/package.json @@ -18,10 +18,10 @@ "@blocknote/shadcn": "latest", "react": "^18.3.1", "react-dom": "^18.3.1", - "@liveblocks/client": "^2.11.0", - "@liveblocks/react": "^2.11.0", - "@liveblocks/react-ui": "^2.11.0", - "@liveblocks/react-blocknote": "^2.11.0" + "@liveblocks/client": "^1.10.0", + "@liveblocks/react": "^1.10.0", + "@liveblocks/yjs": "^1.10.0", + "yjs": "^13.6.15" }, "devDependencies": { "@types/react": "^18.0.25", @@ -38,4 +38,4 @@ "eslintIgnore": [ "dist" ] -} \ No newline at end of file +} diff --git a/playground/package.json b/playground/package.json index dab9aa15fe..a03e18259a 100644 --- a/playground/package.json +++ b/playground/package.json @@ -21,14 +21,8 @@ "@blocknote/xl-multi-column": "^0.24.1", "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.5", - "@tiptap/suggestion": "^2.7.1", - "@tiptap/core": "^2.7.1", - "@tiptap/react": "^2.7.1", - "@liveblocks/client": "file:../../liveblocks/packages/liveblocks-client", - "@liveblocks/react": "file:../../liveblocks/packages/liveblocks-react", - "@liveblocks/react-blocknote": "file:../../liveblocks/packages/liveblocks-react-blocknote", - "@liveblocks/react-ui": "file:../../liveblocks/packages/liveblocks-react-ui", - "@liveblocks/yjs": "file:../../liveblocks/packages/liveblocks-yjs", + "@liveblocks/client": "^1.10.0", + "@liveblocks/yjs": "^1.10.0", "@mantine/core": "^7.10.1", "@mui/icons-material": "^5.16.1", "@mui/material": "^5.16.1", From 8d9d5426fd5b889559560c672bd6d5731e4b2611 Mon Sep 17 00:00:00 2001 From: yousefed Date: Sun, 23 Feb 2025 12:45:09 +0100 Subject: [PATCH 073/144] clean lockfile --- package-lock.json | 2137 +++++++-------------------------------------- 1 file changed, 323 insertions(+), 1814 deletions(-) diff --git a/package-lock.json b/package-lock.json index fcedc709dc..06332362f0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,137 +24,6 @@ "typescript": "^5.3.3" } }, - "../liveblocks/packages/liveblocks-client": { - "name": "@liveblocks/client", - "version": "2.18.2", - "license": "Apache-2.0", - "dependencies": { - "@liveblocks/core": "2.18.2" - }, - "devDependencies": { - "@liveblocks/eslint-config": "*", - "@liveblocks/jest-config": "*" - } - }, - "../liveblocks/packages/liveblocks-react": { - "name": "@liveblocks/react", - "version": "2.18.2", - "license": "Apache-2.0", - "dependencies": { - "@liveblocks/client": "2.18.2", - "@liveblocks/core": "2.18.2" - }, - "devDependencies": { - "@liveblocks/eslint-config": "*", - "@liveblocks/jest-config": "*", - "@liveblocks/query-parser": "^0.1.1", - "@testing-library/jest-dom": "6.4.6", - "@testing-library/react": "14.1.2", - "date-fns": "^3.6.0", - "eslint-plugin-react-hooks": "^4.6.2", - "itertools": "^2.3.2", - "msw": "^1.3.5", - "react-error-boundary": "^4.0.13" - }, - "peerDependencies": { - "react": "^18 || ^19 || ^19.0.0-rc" - } - }, - "../liveblocks/packages/liveblocks-react-blocknote": { - "name": "@liveblocks/react-blocknote", - "version": "2.18.2", - "license": "Apache-2.0", - "dependencies": { - "@liveblocks/client": "file:../../packages/liveblocks-client", - "@liveblocks/core": "file:../../packages/liveblocks-core", - "@liveblocks/react": "file:../../packages/liveblocks-react", - "@liveblocks/react-tiptap": "file:../../packages/liveblocks-react-tiptap", - "@liveblocks/react-ui": "file:../../packages/liveblocks-react-ui", - "@liveblocks/yjs": "file:../../packages/liveblocks-yjs", - "@tiptap/core": "^2.7.2" - }, - "devDependencies": { - "@liveblocks/eslint-config": "*", - "@liveblocks/jest-config": "*", - "@liveblocks/rollup-config": "*", - "@testing-library/jest-dom": "^5.16.5", - "eslint-plugin-react": "^7.33.2", - "eslint-plugin-react-hooks": "^4.6.0", - "msw": "^1.3.5", - "rollup": "3.28.0", - "stylelint": "^15.10.2", - "stylelint-config-standard": "^34.0.0", - "stylelint-order": "^6.0.3", - "stylelint-plugin-logical-css": "^0.13.2" - }, - "peerDependencies": { - "@blocknote/core": "^0.24.0", - "@blocknote/react": "^0.24.0", - "@tiptap/core": "^2.7.2", - "react": "^16.14.0 || ^17 || ^18", - "react-dom": "^16.14.0 || ^17 || ^18" - } - }, - "../liveblocks/packages/liveblocks-react-ui": { - "name": "@liveblocks/react-ui", - "version": "2.18.2", - "license": "Apache-2.0", - "dependencies": { - "@floating-ui/react-dom": "^2.1.2", - "@liveblocks/client": "2.18.2", - "@liveblocks/core": "2.18.2", - "@liveblocks/react": "2.18.2", - "@radix-ui/react-dropdown-menu": "^2.1.2", - "@radix-ui/react-popover": "^1.1.2", - "@radix-ui/react-slot": "^1.1.0", - "@radix-ui/react-toggle": "^1.1.0", - "@radix-ui/react-tooltip": "^1.1.3", - "react-virtuoso": "^4.12.0", - "slate": "^0.110.2", - "slate-history": "^0.110.3", - "slate-hyperscript": "^0.100.0", - "slate-react": "^0.110.3" - }, - "devDependencies": { - "@liveblocks/eslint-config": "*", - "@liveblocks/jest-config": "*", - "@liveblocks/rollup-config": "*", - "@testing-library/jest-dom": "^5.16.5", - "@testing-library/react": "^13.1.1", - "emojibase": "^15.3.0", - "eslint-plugin-react": "^7.33.2", - "eslint-plugin-react-hooks": "^4.6.0", - "msw": "^1.3.5", - "rollup": "3.28.0", - "stylelint": "^15.10.2", - "stylelint-config-standard": "^34.0.0", - "stylelint-order": "^6.0.3", - "stylelint-plugin-logical-css": "^0.13.2" - }, - "peerDependencies": { - "react": "^18 || ^19 || ^19.0.0-rc" - } - }, - "../liveblocks/packages/liveblocks-yjs": { - "name": "@liveblocks/yjs", - "version": "2.18.2", - "license": "Apache-2.0", - "dependencies": { - "@liveblocks/client": "2.18.2", - "@liveblocks/core": "2.18.2", - "js-base64": "^3.7.7", - "y-indexeddb": "^9.0.12" - }, - "devDependencies": { - "@liveblocks/eslint-config": "*", - "@liveblocks/jest-config": "*", - "@testing-library/jest-dom": "^6.4.6", - "msw": "^1.3.5" - }, - "peerDependencies": { - "yjs": "^13.6.1" - } - }, "docs": { "version": "0.24.1", "dependencies": { @@ -4298,9 +4167,9 @@ } }, "node_modules/@floating-ui/react-dom": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.2.tgz", - "integrity": "sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.0.tgz", + "integrity": "sha512-lNzj5EQmEKn5FFKc04+zasr09h/uX8RtJRNj5gUXsSQIXHVWTVh+hVAg1vOMCexkX8EgvemMvIFpQfkosnVNyA==", "dependencies": { "@floating-ui/dom": "^1.0.0" }, @@ -5946,24 +5815,30 @@ "dev": true }, "node_modules/@liveblocks/client": { - "resolved": "../liveblocks/packages/liveblocks-client", - "link": true - }, - "node_modules/@liveblocks/react": { - "resolved": "../liveblocks/packages/liveblocks-react", - "link": true - }, - "node_modules/@liveblocks/react-blocknote": { - "resolved": "../liveblocks/packages/liveblocks-react-blocknote", - "link": true + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/@liveblocks/client/-/client-1.12.0.tgz", + "integrity": "sha512-TL4sPbWBlrGF7UXLNx2RYuZi/Z51jXALMFAdaWkYE0Qz7mMwxTVSyjPVR7ZXVuqdSo0CTQ1rpPyZeqcoUDtEoQ==", + "dependencies": { + "@liveblocks/core": "1.12.0" + } }, - "node_modules/@liveblocks/react-ui": { - "resolved": "../liveblocks/packages/liveblocks-react-ui", - "link": true + "node_modules/@liveblocks/core": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/@liveblocks/core/-/core-1.12.0.tgz", + "integrity": "sha512-cPuVZwSh+EBnJL8DA999h4QDNSlOMFyxHPPHm9qVBf9Cl+NY7/Bg3cyKFiHOki6g7y8dQj8No2tpnAHWdqqalA==" }, "node_modules/@liveblocks/yjs": { - "resolved": "../liveblocks/packages/liveblocks-yjs", - "link": true + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/@liveblocks/yjs/-/yjs-1.12.0.tgz", + "integrity": "sha512-jCGUlfh8hW2Cr5jRrx/c/dBaY3u6psIIVPETDO91AvKgeVld50I7tcYGjFARWwaOYMhSD3c57iAsOjEkGIvpHA==", + "dependencies": { + "@liveblocks/client": "1.12.0", + "@liveblocks/core": "1.12.0", + "js-base64": "^3.7.5" + }, + "peerDependencies": { + "yjs": "^13.6.1" + } }, "node_modules/@mantine/core": { "version": "7.10.1", @@ -7883,95 +7758,24 @@ } }, "node_modules/@radix-ui/react-dropdown-menu": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.2.tgz", - "integrity": "sha512-GVZMR+eqK8/Kes0a36Qrv+i20bAPXSn8rCBTHx30w+3ECnR5o3xixAlqcVaYvLeyKUsm0aqyhWfmUcqufM8nYA==", - "dependencies": { - "@radix-ui/primitive": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-context": "1.1.1", - "@radix-ui/react-id": "1.1.0", - "@radix-ui/react-menu": "2.1.2", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-use-controllable-state": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/primitive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", - "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==" - }, - "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-compose-refs": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz", - "integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-context": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz", - "integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-id": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz", - "integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-primitive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", - "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.0.6.tgz", + "integrity": "sha512-i6TuFOoWmLWq+M/eCLGd/bQ2HfAX1RJgvrBQ6AQLmzfvsLdefxbWu8G9zczcPFfcSPehz9GcpF6K9QYreFV8hA==", "dependencies": { - "@radix-ui/react-slot": "1.1.0" + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-id": "1.0.1", + "@radix-ui/react-menu": "2.0.6", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-use-controllable-state": "1.0.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" }, "peerDependenciesMeta": { "@types/react": { @@ -7982,68 +7786,6 @@ } } }, - "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-slot": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", - "integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-use-callback-ref": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz", - "integrity": "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-use-controllable-state": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz", - "integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==", - "dependencies": { - "@radix-ui/react-use-callback-ref": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-use-layout-effect": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz", - "integrity": "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "node_modules/@radix-ui/react-focus-guards": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.1.tgz", @@ -8128,61 +7870,35 @@ } }, "node_modules/@radix-ui/react-menu": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.2.tgz", - "integrity": "sha512-lZ0R4qR2Al6fZ4yCCZzu/ReTFrylHFxIqy7OezIpWF4bL0o9biKo0pFIvkaew3TyZ9Fy5gYVrR5zCGZBVbO1zg==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.0.6.tgz", + "integrity": "sha512-BVkFLS+bUC8HcImkRKPSiVumA1VPOOEC5WBMiT+QAVsPzW1FJzI9KnqgGxVDPBcql5xXrHkD3JOVoXWEXD8SYA==", "dependencies": { - "@radix-ui/primitive": "1.1.0", - "@radix-ui/react-collection": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-context": "1.1.1", - "@radix-ui/react-direction": "1.1.0", - "@radix-ui/react-dismissable-layer": "1.1.1", - "@radix-ui/react-focus-guards": "1.1.1", - "@radix-ui/react-focus-scope": "1.1.0", - "@radix-ui/react-id": "1.1.0", - "@radix-ui/react-popper": "1.2.0", - "@radix-ui/react-portal": "1.1.2", - "@radix-ui/react-presence": "1.1.1", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-roving-focus": "1.1.0", - "@radix-ui/react-slot": "1.1.0", - "@radix-ui/react-use-callback-ref": "1.1.0", + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-collection": "1.0.3", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-direction": "1.0.1", + "@radix-ui/react-dismissable-layer": "1.0.5", + "@radix-ui/react-focus-guards": "1.0.1", + "@radix-ui/react-focus-scope": "1.0.4", + "@radix-ui/react-id": "1.0.1", + "@radix-ui/react-popper": "1.1.3", + "@radix-ui/react-portal": "1.0.4", + "@radix-ui/react-presence": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-roving-focus": "1.0.4", + "@radix-ui/react-slot": "1.0.2", + "@radix-ui/react-use-callback-ref": "1.0.1", "aria-hidden": "^1.1.1", - "react-remove-scroll": "2.6.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/primitive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", - "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==" - }, - "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-arrow": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.0.tgz", - "integrity": "sha512-FmlW1rCg7hBpEBwFbjHwCW6AmWLQM6g/v0Sn8XbP9NvmSZ2San1FpQeyPtufzOMSIx7Y4dzjlHoifhp+7NkZhw==", - "dependencies": { - "@radix-ui/react-primitive": "2.0.0" + "react-remove-scroll": "2.5.5" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" }, "peerDependenciesMeta": { "@types/react": { @@ -8193,52 +7909,23 @@ } } }, - "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-collection": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.0.tgz", - "integrity": "sha512-GZsZslMJEyo1VKm5L1ZJY8tGDxZNPAoUeQUIbKeJfoi7Q4kmig5AsgLMYYuyYbfjd8fBmFORAIwYAkXMnXZgZw==", + "node_modules/@radix-ui/react-menu/node_modules/react-remove-scroll": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz", + "integrity": "sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==", "dependencies": { - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-context": "1.1.0", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-slot": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "react-remove-scroll-bar": "^2.3.3", + "react-style-singleton": "^2.2.1", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.0", + "use-sidecar": "^1.1.2" }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-context": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.0.tgz", - "integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "engines": { + "node": ">=10" }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-compose-refs": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz", - "integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==", "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" }, "peerDependenciesMeta": { "@types/react": { @@ -8246,1306 +7933,33 @@ } } }, - "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-context": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz", - "integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-direction": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz", - "integrity": "sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-dismissable-layer": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.1.tgz", - "integrity": "sha512-QSxg29lfr/xcev6kSz7MAlmDnzbP1eI/Dwn3Tp1ip0KT5CUELsxkekFEMVBEoykI3oV39hKT4TKZzBNMbcTZYQ==", - "dependencies": { - "@radix-ui/primitive": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-use-callback-ref": "1.1.0", - "@radix-ui/react-use-escape-keydown": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-focus-guards": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.1.tgz", - "integrity": "sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-focus-scope": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.0.tgz", - "integrity": "sha512-200UD8zylvEyL8Bx+z76RJnASR2gRMuxlgFCPAe/Q/679a/r0eK3MBVYMb7vZODZcffZBdob1EGnky78xmVvcA==", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-use-callback-ref": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-id": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz", - "integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-popper": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.0.tgz", - "integrity": "sha512-ZnRMshKF43aBxVWPWvbj21+7TQCvhuULWJ4gNIKYpRlQt5xGRhLx66tMp8pya2UkGHTSlhpXwmjqltDYHhw7Vg==", - "dependencies": { - "@floating-ui/react-dom": "^2.0.0", - "@radix-ui/react-arrow": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-context": "1.1.0", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-use-callback-ref": "1.1.0", - "@radix-ui/react-use-layout-effect": "1.1.0", - "@radix-ui/react-use-rect": "1.1.0", - "@radix-ui/react-use-size": "1.1.0", - "@radix-ui/rect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-popper/node_modules/@radix-ui/react-context": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.0.tgz", - "integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-portal": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.2.tgz", - "integrity": "sha512-WeDYLGPxJb/5EGBoedyJbT0MpoULmwnIPMJMSldkuiMsBAv7N1cRdsTWZWht9vpPOiN3qyiGAtbK2is47/uMFg==", - "dependencies": { - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-use-layout-effect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-presence": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.1.tgz", - "integrity": "sha512-IeFXVi4YS1K0wVZzXNrbaaUvIJ3qdY+/Ih4eHFhWA9SwGR9UDX7Ck8abvL57C4cv3wwMvUE0OG69Qc3NCcTe/A==", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-use-layout-effect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-primitive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", - "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", - "dependencies": { - "@radix-ui/react-slot": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-roving-focus": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.0.tgz", - "integrity": "sha512-EA6AMGeq9AEeQDeSH0aZgG198qkfHSbvWTf1HvoDmOB5bBG/qTxjYMWUKMnYiV6J/iP/J8MEFSuB2zRU2n7ODA==", - "dependencies": { - "@radix-ui/primitive": "1.1.0", - "@radix-ui/react-collection": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-context": "1.1.0", - "@radix-ui/react-direction": "1.1.0", - "@radix-ui/react-id": "1.1.0", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-use-callback-ref": "1.1.0", - "@radix-ui/react-use-controllable-state": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-context": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.0.tgz", - "integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", - "integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-use-callback-ref": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz", - "integrity": "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-use-controllable-state": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz", - "integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==", - "dependencies": { - "@radix-ui/react-use-callback-ref": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-use-escape-keydown": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.0.tgz", - "integrity": "sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==", - "dependencies": { - "@radix-ui/react-use-callback-ref": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-use-layout-effect": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz", - "integrity": "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-use-rect": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz", - "integrity": "sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==", - "dependencies": { - "@radix-ui/rect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-use-size": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.0.tgz", - "integrity": "sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/rect": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.0.tgz", - "integrity": "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==" - }, - "node_modules/@radix-ui/react-popover": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.2.tgz", - "integrity": "sha512-u2HRUyWW+lOiA2g0Le0tMmT55FGOEWHwPFt1EPfbLly7uXQExFo5duNKqG2DzmFXIdqOeNd+TpE8baHWJCyP9w==", - "dependencies": { - "@radix-ui/primitive": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-context": "1.1.1", - "@radix-ui/react-dismissable-layer": "1.1.1", - "@radix-ui/react-focus-guards": "1.1.1", - "@radix-ui/react-focus-scope": "1.1.0", - "@radix-ui/react-id": "1.1.0", - "@radix-ui/react-popper": "1.2.0", - "@radix-ui/react-portal": "1.1.2", - "@radix-ui/react-presence": "1.1.1", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-slot": "1.1.0", - "@radix-ui/react-use-controllable-state": "1.1.0", - "aria-hidden": "^1.1.1", - "react-remove-scroll": "2.6.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/primitive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", - "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==" - }, - "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-arrow": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.0.tgz", - "integrity": "sha512-FmlW1rCg7hBpEBwFbjHwCW6AmWLQM6g/v0Sn8XbP9NvmSZ2San1FpQeyPtufzOMSIx7Y4dzjlHoifhp+7NkZhw==", - "dependencies": { - "@radix-ui/react-primitive": "2.0.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-compose-refs": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz", - "integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-context": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz", - "integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-dismissable-layer": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.1.tgz", - "integrity": "sha512-QSxg29lfr/xcev6kSz7MAlmDnzbP1eI/Dwn3Tp1ip0KT5CUELsxkekFEMVBEoykI3oV39hKT4TKZzBNMbcTZYQ==", - "dependencies": { - "@radix-ui/primitive": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-use-callback-ref": "1.1.0", - "@radix-ui/react-use-escape-keydown": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-focus-guards": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.1.tgz", - "integrity": "sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-focus-scope": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.0.tgz", - "integrity": "sha512-200UD8zylvEyL8Bx+z76RJnASR2gRMuxlgFCPAe/Q/679a/r0eK3MBVYMb7vZODZcffZBdob1EGnky78xmVvcA==", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-use-callback-ref": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-id": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz", - "integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-popper": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.0.tgz", - "integrity": "sha512-ZnRMshKF43aBxVWPWvbj21+7TQCvhuULWJ4gNIKYpRlQt5xGRhLx66tMp8pya2UkGHTSlhpXwmjqltDYHhw7Vg==", - "dependencies": { - "@floating-ui/react-dom": "^2.0.0", - "@radix-ui/react-arrow": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-context": "1.1.0", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-use-callback-ref": "1.1.0", - "@radix-ui/react-use-layout-effect": "1.1.0", - "@radix-ui/react-use-rect": "1.1.0", - "@radix-ui/react-use-size": "1.1.0", - "@radix-ui/rect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-popper/node_modules/@radix-ui/react-context": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.0.tgz", - "integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-portal": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.2.tgz", - "integrity": "sha512-WeDYLGPxJb/5EGBoedyJbT0MpoULmwnIPMJMSldkuiMsBAv7N1cRdsTWZWht9vpPOiN3qyiGAtbK2is47/uMFg==", - "dependencies": { - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-use-layout-effect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-presence": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.1.tgz", - "integrity": "sha512-IeFXVi4YS1K0wVZzXNrbaaUvIJ3qdY+/Ih4eHFhWA9SwGR9UDX7Ck8abvL57C4cv3wwMvUE0OG69Qc3NCcTe/A==", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-use-layout-effect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-primitive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", - "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", - "dependencies": { - "@radix-ui/react-slot": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-slot": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", - "integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-use-callback-ref": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz", - "integrity": "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-use-controllable-state": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz", - "integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==", - "dependencies": { - "@radix-ui/react-use-callback-ref": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-use-escape-keydown": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.0.tgz", - "integrity": "sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==", - "dependencies": { - "@radix-ui/react-use-callback-ref": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-use-layout-effect": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz", - "integrity": "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-use-rect": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz", - "integrity": "sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==", - "dependencies": { - "@radix-ui/rect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-use-size": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.0.tgz", - "integrity": "sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/rect": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.0.tgz", - "integrity": "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==" - }, - "node_modules/@radix-ui/react-popper": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.1.3.tgz", - "integrity": "sha512-cKpopj/5RHZWjrbF2846jBNacjQVwkP068DfmgrNJXpvVWrOvlAmE9xSiy5OqeE+Gi8D9fP+oDhUnPqNMY8/5w==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@floating-ui/react-dom": "^2.0.0", - "@radix-ui/react-arrow": "1.0.3", - "@radix-ui/react-compose-refs": "1.0.1", - "@radix-ui/react-context": "1.0.1", - "@radix-ui/react-primitive": "1.0.3", - "@radix-ui/react-use-callback-ref": "1.0.1", - "@radix-ui/react-use-layout-effect": "1.0.1", - "@radix-ui/react-use-rect": "1.0.1", - "@radix-ui/react-use-size": "1.0.1", - "@radix-ui/rect": "1.0.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-portal": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.0.4.tgz", - "integrity": "sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-primitive": "1.0.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-presence": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.0.1.tgz", - "integrity": "sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-compose-refs": "1.0.1", - "@radix-ui/react-use-layout-effect": "1.0.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-primitive": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.3.tgz", - "integrity": "sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-slot": "1.0.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-roving-focus": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.0.4.tgz", - "integrity": "sha512-2mUg5Mgcu001VkGy+FfzZyzbmuUWzgWkj3rvv4yu+mLw03+mTzbxZHvfcGyFp2b8EkQeMkpRQ5FiA2Vr2O6TeQ==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/primitive": "1.0.1", - "@radix-ui/react-collection": "1.0.3", - "@radix-ui/react-compose-refs": "1.0.1", - "@radix-ui/react-context": "1.0.1", - "@radix-ui/react-direction": "1.0.1", - "@radix-ui/react-id": "1.0.1", - "@radix-ui/react-primitive": "1.0.3", - "@radix-ui/react-use-callback-ref": "1.0.1", - "@radix-ui/react-use-controllable-state": "1.0.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-select": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.0.0.tgz", - "integrity": "sha512-RH5b7af4oHtkcHS7pG6Sgv5rk5Wxa7XI8W5gvB1N/yiuDGZxko1ynvOiVhFM7Cis2A8zxF9bTOUVbRDzPepe6w==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/number": "1.0.1", - "@radix-ui/primitive": "1.0.1", - "@radix-ui/react-collection": "1.0.3", - "@radix-ui/react-compose-refs": "1.0.1", - "@radix-ui/react-context": "1.0.1", - "@radix-ui/react-direction": "1.0.1", - "@radix-ui/react-dismissable-layer": "1.0.5", - "@radix-ui/react-focus-guards": "1.0.1", - "@radix-ui/react-focus-scope": "1.0.4", - "@radix-ui/react-id": "1.0.1", - "@radix-ui/react-popper": "1.1.3", - "@radix-ui/react-portal": "1.0.4", - "@radix-ui/react-primitive": "1.0.3", - "@radix-ui/react-slot": "1.0.2", - "@radix-ui/react-use-callback-ref": "1.0.1", - "@radix-ui/react-use-controllable-state": "1.0.1", - "@radix-ui/react-use-layout-effect": "1.0.1", - "@radix-ui/react-use-previous": "1.0.1", - "@radix-ui/react-visually-hidden": "1.0.3", - "aria-hidden": "^1.1.1", - "react-remove-scroll": "2.5.5" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-select/node_modules/react-remove-scroll": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz", - "integrity": "sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==", - "dependencies": { - "react-remove-scroll-bar": "^2.3.3", - "react-style-singleton": "^2.2.1", - "tslib": "^2.1.0", - "use-callback-ref": "^1.3.0", - "use-sidecar": "^1.1.2" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-slot": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz", - "integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-compose-refs": "1.0.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-tabs": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.0.4.tgz", - "integrity": "sha512-egZfYY/+wRNCflXNHx+dePvnz9FbmssDTJBtgRfDY7e8SE5oIo3Py2eCB1ckAbh1Q7cQ/6yJZThJ++sgbxibog==", + "node_modules/@radix-ui/react-popover": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.0.7.tgz", + "integrity": "sha512-shtvVnlsxT6faMnK/a7n0wptwBD23xc1Z5mdrtKLwVEfsEMXodS0r5s0/g5P0hX//EKYZS2sxUjqfzlg52ZSnQ==", "dependencies": { "@babel/runtime": "^7.13.10", "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-compose-refs": "1.0.1", "@radix-ui/react-context": "1.0.1", - "@radix-ui/react-direction": "1.0.1", + "@radix-ui/react-dismissable-layer": "1.0.5", + "@radix-ui/react-focus-guards": "1.0.1", + "@radix-ui/react-focus-scope": "1.0.4", "@radix-ui/react-id": "1.0.1", - "@radix-ui/react-presence": "1.0.1", - "@radix-ui/react-primitive": "1.0.3", - "@radix-ui/react-roving-focus": "1.0.4", - "@radix-ui/react-use-controllable-state": "1.0.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-toggle": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.0.tgz", - "integrity": "sha512-gwoxaKZ0oJ4vIgzsfESBuSgJNdc0rv12VhHgcqN0TEJmmZixXG/2XpsLK8kzNWYcnaoRIEEQc0bEi3dIvdUpjw==", - "dependencies": { - "@radix-ui/primitive": "1.1.0", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-use-controllable-state": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-toggle/node_modules/@radix-ui/primitive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", - "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==" - }, - "node_modules/@radix-ui/react-toggle/node_modules/@radix-ui/react-compose-refs": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz", - "integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-toggle/node_modules/@radix-ui/react-primitive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", - "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", - "dependencies": { - "@radix-ui/react-slot": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-toggle/node_modules/@radix-ui/react-slot": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", - "integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-toggle/node_modules/@radix-ui/react-use-callback-ref": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz", - "integrity": "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-toggle/node_modules/@radix-ui/react-use-controllable-state": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz", - "integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==", - "dependencies": { - "@radix-ui/react-use-callback-ref": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-tooltip": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.1.3.tgz", - "integrity": "sha512-Z4w1FIS0BqVFI2c1jZvb/uDVJijJjJ2ZMuPV81oVgTZ7g3BZxobplnMVvXtFWgtozdvYJ+MFWtwkM5S2HnAong==", - "dependencies": { - "@radix-ui/primitive": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-context": "1.1.1", - "@radix-ui/react-dismissable-layer": "1.1.1", - "@radix-ui/react-id": "1.1.0", - "@radix-ui/react-popper": "1.2.0", - "@radix-ui/react-portal": "1.1.2", - "@radix-ui/react-presence": "1.1.1", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-slot": "1.1.0", - "@radix-ui/react-use-controllable-state": "1.1.0", - "@radix-ui/react-visually-hidden": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/primitive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", - "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==" - }, - "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-arrow": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.0.tgz", - "integrity": "sha512-FmlW1rCg7hBpEBwFbjHwCW6AmWLQM6g/v0Sn8XbP9NvmSZ2San1FpQeyPtufzOMSIx7Y4dzjlHoifhp+7NkZhw==", - "dependencies": { - "@radix-ui/react-primitive": "2.0.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-compose-refs": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz", - "integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-context": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz", - "integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-dismissable-layer": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.1.tgz", - "integrity": "sha512-QSxg29lfr/xcev6kSz7MAlmDnzbP1eI/Dwn3Tp1ip0KT5CUELsxkekFEMVBEoykI3oV39hKT4TKZzBNMbcTZYQ==", - "dependencies": { - "@radix-ui/primitive": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-use-callback-ref": "1.1.0", - "@radix-ui/react-use-escape-keydown": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-id": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz", - "integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-popper": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.0.tgz", - "integrity": "sha512-ZnRMshKF43aBxVWPWvbj21+7TQCvhuULWJ4gNIKYpRlQt5xGRhLx66tMp8pya2UkGHTSlhpXwmjqltDYHhw7Vg==", - "dependencies": { - "@floating-ui/react-dom": "^2.0.0", - "@radix-ui/react-arrow": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-context": "1.1.0", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-use-callback-ref": "1.1.0", - "@radix-ui/react-use-layout-effect": "1.1.0", - "@radix-ui/react-use-rect": "1.1.0", - "@radix-ui/react-use-size": "1.1.0", - "@radix-ui/rect": "1.1.0" + "@radix-ui/react-popper": "1.1.3", + "@radix-ui/react-portal": "1.0.4", + "@radix-ui/react-presence": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-slot": "1.0.2", + "@radix-ui/react-use-controllable-state": "1.0.1", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.5.5" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" }, "peerDependenciesMeta": { "@types/react": { @@ -9556,13 +7970,23 @@ } } }, - "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-popper/node_modules/@radix-ui/react-context": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.0.tgz", - "integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==", + "node_modules/@radix-ui/react-popover/node_modules/react-remove-scroll": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz", + "integrity": "sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==", + "dependencies": { + "react-remove-scroll-bar": "^2.3.3", + "react-style-singleton": "^2.2.1", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.0", + "use-sidecar": "^1.1.2" + }, + "engines": { + "node": ">=10" + }, "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" }, "peerDependenciesMeta": { "@types/react": { @@ -9570,19 +7994,28 @@ } } }, - "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-portal": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.2.tgz", - "integrity": "sha512-WeDYLGPxJb/5EGBoedyJbT0MpoULmwnIPMJMSldkuiMsBAv7N1cRdsTWZWht9vpPOiN3qyiGAtbK2is47/uMFg==", + "node_modules/@radix-ui/react-popper": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.1.3.tgz", + "integrity": "sha512-cKpopj/5RHZWjrbF2846jBNacjQVwkP068DfmgrNJXpvVWrOvlAmE9xSiy5OqeE+Gi8D9fP+oDhUnPqNMY8/5w==", "dependencies": { - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-use-layout-effect": "1.1.0" + "@babel/runtime": "^7.13.10", + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.0.3", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-use-callback-ref": "1.0.1", + "@radix-ui/react-use-layout-effect": "1.0.1", + "@radix-ui/react-use-rect": "1.0.1", + "@radix-ui/react-use-size": "1.0.1", + "@radix-ui/rect": "1.0.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" }, "peerDependenciesMeta": { "@types/react": { @@ -9593,19 +8026,19 @@ } } }, - "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-presence": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.1.tgz", - "integrity": "sha512-IeFXVi4YS1K0wVZzXNrbaaUvIJ3qdY+/Ih4eHFhWA9SwGR9UDX7Ck8abvL57C4cv3wwMvUE0OG69Qc3NCcTe/A==", + "node_modules/@radix-ui/react-portal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.0.4.tgz", + "integrity": "sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q==", "dependencies": { - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-use-layout-effect": "1.1.0" + "@babel/runtime": "^7.13.10", + "@radix-ui/react-primitive": "1.0.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" }, "peerDependenciesMeta": { "@types/react": { @@ -9616,18 +8049,20 @@ } } }, - "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-primitive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", - "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", + "node_modules/@radix-ui/react-presence": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.0.1.tgz", + "integrity": "sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==", "dependencies": { - "@radix-ui/react-slot": "1.1.0" + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-use-layout-effect": "1.0.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" }, "peerDependenciesMeta": { "@types/react": { @@ -9638,64 +8073,120 @@ } } }, - "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-slot": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", - "integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==", + "node_modules/@radix-ui/react-primitive": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.3.tgz", + "integrity": "sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==", "dependencies": { - "@radix-ui/react-compose-refs": "1.1.0" + "@babel/runtime": "^7.13.10", + "@radix-ui/react-slot": "1.0.2" }, "peerDependencies": { "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" }, "peerDependenciesMeta": { "@types/react": { "optional": true + }, + "@types/react-dom": { + "optional": true } } }, - "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-use-callback-ref": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz", - "integrity": "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==", + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.0.4.tgz", + "integrity": "sha512-2mUg5Mgcu001VkGy+FfzZyzbmuUWzgWkj3rvv4yu+mLw03+mTzbxZHvfcGyFp2b8EkQeMkpRQ5FiA2Vr2O6TeQ==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-collection": "1.0.3", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-direction": "1.0.1", + "@radix-ui/react-id": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-use-callback-ref": "1.0.1", + "@radix-ui/react-use-controllable-state": "1.0.1" + }, "peerDependencies": { "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" }, "peerDependenciesMeta": { "@types/react": { "optional": true + }, + "@types/react-dom": { + "optional": true } } }, - "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-use-controllable-state": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz", - "integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==", + "node_modules/@radix-ui/react-select": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.0.0.tgz", + "integrity": "sha512-RH5b7af4oHtkcHS7pG6Sgv5rk5Wxa7XI8W5gvB1N/yiuDGZxko1ynvOiVhFM7Cis2A8zxF9bTOUVbRDzPepe6w==", "dependencies": { - "@radix-ui/react-use-callback-ref": "1.1.0" + "@babel/runtime": "^7.13.10", + "@radix-ui/number": "1.0.1", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-collection": "1.0.3", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-direction": "1.0.1", + "@radix-ui/react-dismissable-layer": "1.0.5", + "@radix-ui/react-focus-guards": "1.0.1", + "@radix-ui/react-focus-scope": "1.0.4", + "@radix-ui/react-id": "1.0.1", + "@radix-ui/react-popper": "1.1.3", + "@radix-ui/react-portal": "1.0.4", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-slot": "1.0.2", + "@radix-ui/react-use-callback-ref": "1.0.1", + "@radix-ui/react-use-controllable-state": "1.0.1", + "@radix-ui/react-use-layout-effect": "1.0.1", + "@radix-ui/react-use-previous": "1.0.1", + "@radix-ui/react-visually-hidden": "1.0.3", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.5.5" }, "peerDependencies": { "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" }, "peerDependenciesMeta": { "@types/react": { "optional": true + }, + "@types/react-dom": { + "optional": true } } }, - "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-use-escape-keydown": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.0.tgz", - "integrity": "sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==", + "node_modules/@radix-ui/react-select/node_modules/react-remove-scroll": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz", + "integrity": "sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==", "dependencies": { - "@radix-ui/react-use-callback-ref": "1.1.0" + "react-remove-scroll-bar": "^2.3.3", + "react-style-singleton": "^2.2.1", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.0", + "use-sidecar": "^1.1.2" + }, + "engines": { + "node": ">=10" }, "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" }, "peerDependenciesMeta": { "@types/react": { @@ -9703,13 +8194,17 @@ } } }, - "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-use-layout-effect": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz", - "integrity": "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==", + "node_modules/@radix-ui/react-slot": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz", + "integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.1" + }, "peerDependencies": { "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "react": "^16.8 || ^17.0 || ^18.0" }, "peerDependenciesMeta": { "@types/react": { @@ -9717,52 +8212,85 @@ } } }, - "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-use-rect": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz", - "integrity": "sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==", + "node_modules/@radix-ui/react-tabs": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.0.4.tgz", + "integrity": "sha512-egZfYY/+wRNCflXNHx+dePvnz9FbmssDTJBtgRfDY7e8SE5oIo3Py2eCB1ckAbh1Q7cQ/6yJZThJ++sgbxibog==", "dependencies": { - "@radix-ui/rect": "1.1.0" + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-direction": "1.0.1", + "@radix-ui/react-id": "1.0.1", + "@radix-ui/react-presence": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-roving-focus": "1.0.4", + "@radix-ui/react-use-controllable-state": "1.0.1" }, "peerDependencies": { "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" }, "peerDependenciesMeta": { "@types/react": { "optional": true + }, + "@types/react-dom": { + "optional": true } } }, - "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-use-size": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.0.tgz", - "integrity": "sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==", + "node_modules/@radix-ui/react-toggle": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.0.3.tgz", + "integrity": "sha512-Pkqg3+Bc98ftZGsl60CLANXQBBQ4W3mTFS9EJvNxKMZ7magklKV69/id1mlAlOFDDfHvlCms0fx8fA4CMKDJHg==", "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.0" + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-use-controllable-state": "1.0.1" }, "peerDependencies": { "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" }, "peerDependenciesMeta": { "@types/react": { "optional": true + }, + "@types/react-dom": { + "optional": true } } }, - "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-visually-hidden": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.0.tgz", - "integrity": "sha512-N8MDZqtgCgG5S3aV60INAB475osJousYpZ4cTJ2cFbMpdHS5Y6loLTH8LPtkj2QN0x93J30HT/M3qJXM0+lyeQ==", + "node_modules/@radix-ui/react-tooltip": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.0.7.tgz", + "integrity": "sha512-lPh5iKNFVQ/jav/j6ZrWq3blfDJ0OH9R6FlNUHPMqdLuQ9vwDgFsRxvl8b7Asuy5c8xmoojHUxKHQSOAvMHxyw==", "dependencies": { - "@radix-ui/react-primitive": "2.0.0" + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-dismissable-layer": "1.0.5", + "@radix-ui/react-id": "1.0.1", + "@radix-ui/react-popper": "1.1.3", + "@radix-ui/react-portal": "1.0.4", + "@radix-ui/react-presence": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-slot": "1.0.2", + "@radix-ui/react-use-controllable-state": "1.0.1", + "@radix-ui/react-visually-hidden": "1.0.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" }, "peerDependenciesMeta": { "@types/react": { @@ -9773,11 +8301,6 @@ } } }, - "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/rect": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.0.tgz", - "integrity": "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==" - }, "node_modules/@radix-ui/react-use-callback-ref": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz", @@ -11271,9 +9794,9 @@ } }, "node_modules/@tiptap/core": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.9.1.tgz", - "integrity": "sha512-tifnLL/ARzQ6/FGEJjVwj9UT3v+pENdWHdk9x6F3X0mB1y0SeCjV21wpFLYESzwNdBPAj8NMp8Behv7dBnhIfw==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.7.1.tgz", + "integrity": "sha512-/sOJ3J2OWxQrho6MWgE9xaRBln5MC4BEuevTYIGia4zrc523lX9s+h/lUeLtCPhI0+J6z9Vz+v3G/uoEqWCL+A==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -11295,9 +9818,9 @@ } }, "node_modules/@tiptap/extension-bubble-menu": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.9.1.tgz", - "integrity": "sha512-DWUF6NG08/bZDWw0jCeotSTvpkyqZTi4meJPomG9Wzs/Ol7mEwlNCsCViD999g0+IjyXFatBk4DfUq1YDDu++Q==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.7.1.tgz", + "integrity": "sha512-ZQh2Q2bAu61Z249b8eRLMKk0WU2ILvUz9JM9uxjxXaGE9L8nQbv0Pc5sZxIecOKmdX9N5Nq6mSoh/kD+klUOzA==", "dependencies": { "tippy.js": "^6.3.7" }, @@ -11323,9 +9846,9 @@ } }, "node_modules/@tiptap/extension-collaboration": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/@tiptap/extension-collaboration/-/extension-collaboration-2.9.1.tgz", - "integrity": "sha512-AaS66O4X0rx1WLnCC3LW5fLk7ZULr0t7/gj/7dNgbBtyg4NhoYc3PhSBiimLbgD+R8wFrfm1lsHb02deaqQB6w==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-collaboration/-/extension-collaboration-2.7.1.tgz", + "integrity": "sha512-u6eDKDuzJ9Fd9ZPul7wAnSzqaq+ovRaVsDRYrNDYUYT2QlbWJtI8HKE/5Mt1Ml0Xnpwn3UcLwJdo0iN4/A4IOQ==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -11337,9 +9860,9 @@ } }, "node_modules/@tiptap/extension-collaboration-cursor": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/@tiptap/extension-collaboration-cursor/-/extension-collaboration-cursor-2.9.1.tgz", - "integrity": "sha512-mbNYEdlp22UpKY0SULVWRvOm3U19V80DokC+iDIDZXGXI2nCevB4vMQVkXmiQtiz3qT9I9mIN0vSgy3w75A7WA==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-collaboration-cursor/-/extension-collaboration-cursor-2.7.1.tgz", + "integrity": "sha512-aYVuztLFTFfoh+bQMQV+f/+iTrq6L99YJF+TRW4CfnD6gsThX3fk4tbGXWVoE8Zd6SQMAjote4TZZBwGbANtdg==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -11350,9 +9873,9 @@ } }, "node_modules/@tiptap/extension-floating-menu": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-2.9.1.tgz", - "integrity": "sha512-MxZ7acNNsoNaKpetxfwi3Z11Bgrh0T2EJlCV77v9N1vWK38+st3H1WJanmLbPNtc2ocvhHJrz+DjDz3CWxQ9rQ==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-2.7.1.tgz", + "integrity": "sha512-t6nANJPDudo8lOTN2zxXUBqMK8K8ZZgGfC8j3vVbOzLCLEgffU1ZX6YfYqOWPRBua0TXLJPZnGD6nff4pxzG5g==", "dependencies": { "tippy.js": "^6.3.7" }, @@ -11529,9 +10052,9 @@ } }, "node_modules/@tiptap/pm": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.9.1.tgz", - "integrity": "sha512-mvV86fr7kEuDYEApQ2uMPCKL2uagUE0BsXiyyz3KOkY1zifyVm1fzdkscb24Qy1GmLzWAIIihA+3UHNRgYdOlQ==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.7.1.tgz", + "integrity": "sha512-gG++eBQu9SObWCmxZDv6tkwFHVmbg7phowy0F7Nihq9Um7/oae5Ag9skfiG8GG9eYdw54paEAY/MP+tE3x/smA==", "dependencies": { "prosemirror-changeset": "^2.2.1", "prosemirror-collab": "^1.3.1", @@ -11550,7 +10073,7 @@ "prosemirror-tables": "^1.4.0", "prosemirror-trailing-node": "^3.0.0", "prosemirror-transform": "^1.10.0", - "prosemirror-view": "^1.34.3" + "prosemirror-view": "^1.33.10" }, "funding": { "type": "github", @@ -11558,12 +10081,12 @@ } }, "node_modules/@tiptap/react": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/@tiptap/react/-/react-2.9.1.tgz", - "integrity": "sha512-LQJ34ZPfXtJF36SZdcn4Fiwsl2WxZ9YRJI87OLnsjJ45O+gV/PfBzz/4ap+LF8LOS0AbbGhTTjBOelPoNm+aYA==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@tiptap/react/-/react-2.7.1.tgz", + "integrity": "sha512-FlH40q0hGNkSO3uE3hvBYYcVEoWvUpolwB6J76P+WAjUl+sPnw5ARtQ4eYnDtYi+ehnVJPoRh8ifnBMXLV1nDA==", "dependencies": { - "@tiptap/extension-bubble-menu": "^2.9.1", - "@tiptap/extension-floating-menu": "^2.9.1", + "@tiptap/extension-bubble-menu": "^2.7.1", + "@tiptap/extension-floating-menu": "^2.7.1", "@types/use-sync-external-store": "^0.0.6", "fast-deep-equal": "^3", "use-sync-external-store": "^1.2.2" @@ -11579,19 +10102,6 @@ "react-dom": "^17.0.0 || ^18.0.0" } }, - "node_modules/@tiptap/suggestion": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/@tiptap/suggestion/-/suggestion-2.9.1.tgz", - "integrity": "sha512-MMxwpbtocxUsbmc8qtFY1AQYNTW5i/M4aNSv9zsKKRISaS5hMD7XVrw2eod0x0yEqZU3izLiPDZPmgr8glF+jQ==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^2.7.0", - "@tiptap/pm": "^2.7.0" - } - }, "node_modules/@tootallnate/once": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", @@ -20483,6 +18993,11 @@ "url": "https://github.com/sponsors/panva" } }, + "node_modules/js-base64": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.7.tgz", + "integrity": "sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -20902,9 +19417,9 @@ } }, "node_modules/lib0": { - "version": "0.2.98", - "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.98.tgz", - "integrity": "sha512-XteTiNO0qEXqqweWx+b21p/fBnNHUA1NwAtJNJek1oPrewEZs2uiT4gWivHKr9GqCjDPAhchz0UQO8NwU3bBNA==", + "version": "0.2.94", + "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.94.tgz", + "integrity": "sha512-hZ3p54jL4Wpu7IOg26uC7dnEWiMyNlUrb9KoG7+xYs45WkQwpVvKFndVq2+pqLYKe1u8Fp3+zAfZHVvTK34PvQ==", "dependencies": { "isomorphic.js": "^0.2.4" }, @@ -26699,9 +25214,9 @@ } }, "node_modules/react-remove-scroll": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.0.tgz", - "integrity": "sha512-I2U4JVEsQenxDAKaVa3VZ/JeJZe0/2DxPWL8Tj8yLKctQJQiZM52pn/GWFpSp8dftjM3pSAHVJZscAnC/y+ySQ==", + "version": "2.5.10", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.10.tgz", + "integrity": "sha512-m3zvBRANPBw3qxVVjEIPEQinkcwlFZ4qyomuWVpNJdv4c6MvHfXV0C3L9Jx5rr3HeBHKNRX+1jreB5QloDIJjA==", "dependencies": { "react-remove-scroll-bar": "^2.3.6", "react-style-singleton": "^2.2.1", @@ -31715,11 +30230,11 @@ } }, "node_modules/yjs": { - "version": "13.6.20", - "resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.20.tgz", - "integrity": "sha512-Z2YZI+SYqK7XdWlloI3lhMiKnCdFCVC4PchpdO+mCYwtiTwncjUbnRK9R1JmkNfdmHyDXuWN3ibJAt0wsqTbLQ==", + "version": "13.6.16", + "resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.16.tgz", + "integrity": "sha512-uEq+n/dFIecBElEdeQea8nDnltScBfuhCSyAxDw4CosveP9Ag0eW6iZi2mdpW7EgxSFT7VXK2MJl3tKaLTmhAQ==", "dependencies": { - "lib0": "^0.2.98" + "lib0": "^0.2.86" }, "engines": { "node": ">=16.0.0", @@ -32596,17 +31111,11 @@ "@blocknote/xl-multi-column": "^0.24.1", "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.5", - "@liveblocks/client": "file:../../liveblocks/packages/liveblocks-client", - "@liveblocks/react": "file:../../liveblocks/packages/liveblocks-react", - "@liveblocks/react-blocknote": "file:../../liveblocks/packages/liveblocks-react-blocknote", - "@liveblocks/react-ui": "file:../../liveblocks/packages/liveblocks-react-ui", - "@liveblocks/yjs": "file:../../liveblocks/packages/liveblocks-yjs", + "@liveblocks/client": "^1.10.0", + "@liveblocks/yjs": "^1.10.0", "@mantine/core": "^7.10.1", "@mui/icons-material": "^5.16.1", "@mui/material": "^5.16.1", - "@tiptap/core": "^2.7.1", - "@tiptap/react": "^2.7.1", - "@tiptap/suggestion": "^2.7.1", "@uppy/core": "^3.13.1", "@uppy/dashboard": "^3.9.1", "@uppy/drag-drop": "^3.1.1", From 0f1e01949c0f6884242e3efe520e39c8abc49171 Mon Sep 17 00:00:00 2001 From: yousefed Date: Sun, 23 Feb 2025 12:49:55 +0100 Subject: [PATCH 074/144] fix some todos --- packages/ariakit/src/toolbar/Toolbar.tsx | 2 -- packages/mantine/src/toolbar/ToolbarButton.tsx | 3 +++ packages/shadcn/src/popover/popover.tsx | 1 - packages/shadcn/src/toolbar/Toolbar.tsx | 6 ------ packages/xl-pdf-exporter/src/pdf/pdfExporter.tsx | 1 - 5 files changed, 3 insertions(+), 10 deletions(-) diff --git a/packages/ariakit/src/toolbar/Toolbar.tsx b/packages/ariakit/src/toolbar/Toolbar.tsx index 6ce24ed560..9bed2b250b 100644 --- a/packages/ariakit/src/toolbar/Toolbar.tsx +++ b/packages/ariakit/src/toolbar/Toolbar.tsx @@ -18,8 +18,6 @@ export const Toolbar = forwardRef( ...rest } = props; - // TODO - // @ts-ignore assertEmpty(rest); return ( diff --git a/packages/mantine/src/toolbar/ToolbarButton.tsx b/packages/mantine/src/toolbar/ToolbarButton.tsx index a772ceaab7..5f88256796 100644 --- a/packages/mantine/src/toolbar/ToolbarButton.tsx +++ b/packages/mantine/src/toolbar/ToolbarButton.tsx @@ -98,6 +98,9 @@ export const ToolbarButton = forwardRef( } }} onClick={(event) => { + // We manually hide the tooltip onclick, because the click event + // might open a popover which would then show both the tooltip and the popover + // this is similar to default behavior of shadcn / radix setHideTooltip(true); onClick?.(event); }} diff --git a/packages/shadcn/src/popover/popover.tsx b/packages/shadcn/src/popover/popover.tsx index a720d377d7..60809748ca 100644 --- a/packages/shadcn/src/popover/popover.tsx +++ b/packages/shadcn/src/popover/popover.tsx @@ -16,7 +16,6 @@ export const Popover = ( ...rest } = props; - // @ts-ignore TODO assertEmpty(rest); const ShadCNComponents = useShadCNComponentsContext()!; diff --git a/packages/shadcn/src/toolbar/Toolbar.tsx b/packages/shadcn/src/toolbar/Toolbar.tsx index e4eecda8d3..ee1582adbe 100644 --- a/packages/shadcn/src/toolbar/Toolbar.tsx +++ b/packages/shadcn/src/toolbar/Toolbar.tsx @@ -62,8 +62,6 @@ export const ToolbarButton = forwardRef( // false, because rest props can be added by shadcn when button is used as a trigger // assertEmpty in this case is only used at typescript level, not runtime level - // TODO - // @ts-ignore assertEmpty(rest, false); const ShadCNComponents = useShadCNComponentsContext()!; @@ -75,8 +73,6 @@ export const ToolbarButton = forwardRef( className, variant === "compact" ? "bn-h-6 bn-min-w-6 bn-p-0" : "" )} - // TODO - // @ts-ignore variant="ghost" size={variant === "compact" ? "sm" : "default"} disabled={isDisabled} @@ -88,8 +84,6 @@ export const ToolbarButton = forwardRef( {children} ) : ( - // TODO - // @ts-ignore Date: Sun, 23 Feb 2025 13:07:51 +0100 Subject: [PATCH 075/144] adress number of comments --- .../02-liveblocks/package.json | 2 +- .../04-comments/.bnexample.json | 4 +- .../core/src/editor/BlockNoteExtensions.ts | 2 +- .../Comments/threadstore/ThreadStore.ts | 11 +++ .../Comments/threadstore/TipTapThreadStore.ts | 6 ++ .../core/src/extensions/Comments/types.ts | 80 ++++++++++++++++++- .../Comments/userstore/UserStore.ts | 24 ++++++ playground/src/examples.gen.tsx | 14 ++-- 8 files changed, 131 insertions(+), 12 deletions(-) diff --git a/examples/07-collaboration/02-liveblocks/package.json b/examples/07-collaboration/02-liveblocks/package.json index 691ab0dd63..bafb616c66 100644 --- a/examples/07-collaboration/02-liveblocks/package.json +++ b/examples/07-collaboration/02-liveblocks/package.json @@ -38,4 +38,4 @@ "eslintIgnore": [ "dist" ] -} +} \ No newline at end of file diff --git a/examples/07-collaboration/04-comments/.bnexample.json b/examples/07-collaboration/04-comments/.bnexample.json index 631b4fb54e..da901b915b 100644 --- a/examples/07-collaboration/04-comments/.bnexample.json +++ b/examples/07-collaboration/04-comments/.bnexample.json @@ -1,8 +1,8 @@ { "playground": true, "docs": true, - "author": "jakelazaroff", - "tags": ["Advanced", "Saving/Loading", "Collaboration"], + "author": "yousefed", + "tags": ["Advanced", "Comments", "Collaboration"], "dependencies": { "@y-sweet/react": "^0.6.3", "@mantine/core": "^7.10.1" diff --git a/packages/core/src/editor/BlockNoteExtensions.ts b/packages/core/src/editor/BlockNoteExtensions.ts index cd04a78c81..af11ede459 100644 --- a/packages/core/src/editor/BlockNoteExtensions.ts +++ b/packages/core/src/editor/BlockNoteExtensions.ts @@ -263,7 +263,7 @@ const getTipTapExtensions = < ...(opts.trailingBlock === undefined || opts.trailingBlock ? [TrailingNode] : []), - CommentMark, + ...(opts.comments ? [CommentMark] : []), ]; LINKIFY_INITIALIZED = true; diff --git a/packages/core/src/extensions/Comments/threadstore/ThreadStore.ts b/packages/core/src/extensions/Comments/threadstore/ThreadStore.ts index 386f74add9..4d4fcd3438 100644 --- a/packages/core/src/extensions/Comments/threadstore/ThreadStore.ts +++ b/packages/core/src/extensions/Comments/threadstore/ThreadStore.ts @@ -113,10 +113,21 @@ export abstract class ThreadStore { emoji: string; }): Promise; + /** + * Retrieve data for a specific thread. + */ abstract getThread(threadId: string): ThreadData; + /** + * Retrieve all threads. + */ abstract getThreads(): Map; + /** + * Subscribe to changes in the thread store. + * + * @returns a function to unsubscribe from the thread store + */ abstract subscribe( cb: (threads: Map) => void ): () => void; diff --git a/packages/core/src/extensions/Comments/threadstore/TipTapThreadStore.ts b/packages/core/src/extensions/Comments/threadstore/TipTapThreadStore.ts index 4a0e02fd41..aef4b06d80 100644 --- a/packages/core/src/extensions/Comments/threadstore/TipTapThreadStore.ts +++ b/packages/core/src/extensions/Comments/threadstore/TipTapThreadStore.ts @@ -18,6 +18,12 @@ type ReactionAsTiptapData = { userId: string; }; +/** + * The `TiptapThreadStore` integrates with Tiptap's collaboration provider for comment management. + * You can pass a `TiptapCollabProvider` to the constructor which takes care of storing the comments. + * + * Under the hood, this actually works similarly to the `YjsThreadStore` implementation. (comments are stored in the Yjs document) + */ export class TiptapThreadStore extends ThreadStore { constructor( private readonly userId: string, diff --git a/packages/core/src/extensions/Comments/types.ts b/packages/core/src/extensions/Comments/types.ts index 79c197d2eb..f29d3a4ea5 100644 --- a/packages/core/src/extensions/Comments/types.ts +++ b/packages/core/src/extensions/Comments/types.ts @@ -1,39 +1,117 @@ +/** + * The body of a comment. This actually is a BlockNote document (array of blocks) + */ export type CommentBody = any; +/** + * A reaction to a comment. + */ export type CommentReactionData = { + /** + * The emoji that was reacted to the comment. + */ emoji: string; + /** + * The date the first user reacted to the comment with this emoji. + */ createdAt: Date; + /** + * The user ids of the users that have reacted to the comment with this emoji + */ userIds: string[]; }; +/** + * Information about a comment. + */ export type CommentData = { type: "comment"; + /** + * The unique identifier for the comment. + */ id: string; + /** + * The user id of the author of the comment. + */ userId: string; + /** + * The date when the comment was created. + */ createdAt: Date; + /** + * The date when the comment was last updated. + */ updatedAt: Date; + + /** + * The reactions (emoji reactions) to the comment. + */ reactions: CommentReactionData[]; - // attachments: CommentAttachment[]; + + /** + * You can use this store any additional information about the comment. + */ metadata: any; } & ( | { + /** + * The date when the comment was deleted. This applies only for "soft deletes", + * otherwise the comment is removed entirely. + */ deletedAt: Date; + /** + * The body of the comment is undefined if the comment is deleted. + */ body: undefined; } | { + /** + * In case of a non-deleted comment, this is not set + */ deletedAt?: never; + /** + * The body of the comment. + */ body: CommentBody; } ); +/** + * Information about a thread. A thread holds a list of comments. + */ export type ThreadData = { type: "thread"; + /** + * The unique identifier for the thread. + */ id: string; + /** + * The date when the thread was created. + */ createdAt: Date; + /** + * The date when the thread was last updated. + */ updatedAt: Date; + /** + * The comments in the thread. + */ comments: CommentData[]; + /** + * Whether the thread has been marked as resolved. + */ resolved: boolean; + /** + * The date when the thread was marked as resolved. + */ resolvedUpdatedAt?: Date; + /** + * You can use this store any additional information about the thread. + */ metadata: any; + /** + * The date when the thread was deleted. (or undefined if it is not deleted) + * This only applies for "soft deletes", otherwise the thread is removed entirely. + */ deletedAt?: Date; }; diff --git a/packages/core/src/extensions/Comments/userstore/UserStore.ts b/packages/core/src/extensions/Comments/userstore/UserStore.ts index e1f1a42fed..7d14960965 100644 --- a/packages/core/src/extensions/Comments/userstore/UserStore.ts +++ b/packages/core/src/extensions/Comments/userstore/UserStore.ts @@ -1,5 +1,12 @@ import { User } from "../../../models/User.js"; import { EventEmitter } from "../../../util/EventEmitter.js"; + +/** + * The `UserStore` is used to retrieve and cache information about users. + * + * It does this by calling `resolveUsers` (which is user-defined in the Editor Options) + * for users that are not yet cached. + */ export class UserStore extends EventEmitter { private userCache: Map = new Map(); @@ -12,6 +19,9 @@ export class UserStore extends EventEmitter { super(); } + /** + * Load information about users based on an array of user ids. + */ public async loadUsers(userIds: string[]) { const missingUsers = userIds.filter( (id) => !this.userCache.has(id) && !this.loadingUsers.has(id) @@ -33,15 +43,29 @@ export class UserStore extends EventEmitter { this.emit("update", this.userCache); } finally { for (const id of missingUsers) { + // delete the users from the loading set + // on a next call to `loadUsers` we will either + // return the cached user or retry loading the user if the request failed failed this.loadingUsers.delete(id); } } } + /** + * Retrieve information about a user based on their id, if cached. + * + * The user will have to be loaded via `loadUsers` first + */ public getUser(userId: string): U | undefined { return this.userCache.get(userId); } + /** + * Subscribe to changes in the user store. + * + * @param cb - The callback to call when the user store changes. + * @returns A function to unsubscribe from the user store. + */ public subscribe(cb: (users: Map) => void): () => void { return this.on("update", cb); } diff --git a/playground/src/examples.gen.tsx b/playground/src/examples.gen.tsx index e0d3787a1c..32bb88d44b 100644 --- a/playground/src/examples.gen.tsx +++ b/playground/src/examples.gen.tsx @@ -1099,14 +1099,14 @@ "author": "yousefed", "tags": [ "Advanced", - "Liveblocks", + "Saving/Loading", "Collaboration" ], "dependencies": { - "@liveblocks/client": "^2.11.0", - "@liveblocks/react": "^2.11.0", - "@liveblocks/react-ui": "^2.11.0", - "@liveblocks/react-blocknote": "^2.11.0" + "@liveblocks/client": "^1.10.0", + "@liveblocks/react": "^1.10.0", + "@liveblocks/yjs": "^1.10.0", + "yjs": "^13.6.15" } as any }, "title": "Collaborative Editing with Liveblocks", @@ -1145,10 +1145,10 @@ "config": { "playground": true, "docs": true, - "author": "jakelazaroff", + "author": "yousefed", "tags": [ "Advanced", - "Saving/Loading", + "Comments", "Collaboration" ], "dependencies": { From 11d9ee6080e0314204c506614e25a1293a3d91f8 Mon Sep 17 00:00:00 2001 From: yousefed Date: Sun, 23 Feb 2025 13:23:05 +0100 Subject: [PATCH 076/144] address more comments --- packages/ariakit/src/comments/Editor.tsx | 10 +- packages/core/src/editor/BlockNoteEditor.ts | 6 + packages/mantine/src/comments/Editor.tsx | 10 +- .../react/src/components/Comments/Comment.tsx | 140 +++++++++--------- .../src/components/Comments/CommentEditor.tsx | 10 +- packages/shadcn/src/comments/Editor.tsx | 10 +- 6 files changed, 86 insertions(+), 100 deletions(-) diff --git a/packages/ariakit/src/comments/Editor.tsx b/packages/ariakit/src/comments/Editor.tsx index 857b417109..2080107063 100644 --- a/packages/ariakit/src/comments/Editor.tsx +++ b/packages/ariakit/src/comments/Editor.tsx @@ -5,7 +5,7 @@ import { FormattingToolbarController, getFormattingToolbarItems, } from "@blocknote/react"; -import { forwardRef, useEffect } from "react"; +import { forwardRef } from "react"; import { BlockNoteView } from "../BlockNoteView.js"; export const Editor = forwardRef< @@ -16,14 +16,6 @@ export const Editor = forwardRef< assertEmpty(rest, false); - // When we click the edit button on a comment, we also want to focus the - // comment editor - useEffect(() => { - if (editable) { - editor.focus(); - } - }, [editable, editor]); - return ( { - if (editable) { - editor.focus(); - } - }, [editable, editor]); - return ( - {comment.body ? ( - 0) || isEditing - ? ({ isEmpty }) => ( - <> - {showReactions && - comment.reactions.length > 0 && - !isEditing && ( - - {comment.reactions.map((reaction) => ( - - ))} - - onReactionSelect(emoji.native) - }> - } - mainTooltip="Add reaction" - /> - - - )} - {isEditing && ( - 0) || isEditing + ? ({ isEmpty }) => ( + <> + {showReactions && + comment.reactions.length > 0 && + !isEditing && ( + - - Save - - - Cancel - - + {comment.reactions.map((reaction) => ( + + ))} + + onReactionSelect(emoji.native) + }> + } + mainTooltip="Add reaction" + /> + + )} - - ) - : undefined - } - /> - ) : ( - // Soft deletes - // TODO, test -
-

Deleted

-
- )} + {isEditing && ( + + + Save + + + Cancel + + + )} + + ) + : undefined + } + /> ); }; diff --git a/packages/react/src/components/Comments/CommentEditor.tsx b/packages/react/src/components/Comments/CommentEditor.tsx index 0e7617b1ff..1d79b334f3 100644 --- a/packages/react/src/components/Comments/CommentEditor.tsx +++ b/packages/react/src/components/Comments/CommentEditor.tsx @@ -1,5 +1,5 @@ import { BlockNoteEditor } from "@blocknote/core"; -import { FC, useCallback, useState } from "react"; +import { FC, useCallback, useEffect, useState } from "react"; import { useComponentsContext } from "../../editor/ComponentsContext.js"; import { useEditorChange } from "../../hooks/useEditorChange.js"; import { schema } from "./schema.js"; @@ -43,6 +43,14 @@ export const CommentEditor = (props: { setIsFocused(false); }, []); + // When we click the edit button on a comment, we also want to focus the + // comment editor + useEffect(() => { + if (props.editable) { + props.editor.focus(); + } + }, [props.editable, props.editor]); + return ( <> { - if (editable) { - editor.focus(); - } - }, [editable, editor]); - return ( Date: Sun, 23 Feb 2025 17:27:46 +0100 Subject: [PATCH 077/144] address some comments --- packages/react/src/components/Comments/Comment.tsx | 10 ++++------ .../react/src/components/Comments/FloatingComposer.tsx | 3 +-- .../components/Comments/FloatingComposerController.tsx | 1 - .../components/Comments/FloatingThreadController.tsx | 5 +++-- packages/react/src/components/Comments/Thread.tsx | 5 +++-- packages/react/src/components/Comments/useThreads.ts | 3 +++ packages/react/src/components/Comments/useUsers.ts | 3 +++ 7 files changed, 17 insertions(+), 13 deletions(-) diff --git a/packages/react/src/components/Comments/Comment.tsx b/packages/react/src/components/Comments/Comment.tsx index 9e176b8ec6..312b136c9c 100644 --- a/packages/react/src/components/Comments/Comment.tsx +++ b/packages/react/src/components/Comments/Comment.tsx @@ -64,6 +64,7 @@ export interface CommentProps extends ComponentPropsWithoutRef<"div"> { * a reaction list and an editor when editing. * * It's generally used in the `Thread` component for comments that have already been created. + * */ export const Comment = ({ comment, @@ -74,9 +75,11 @@ export const Comment = ({ showResolveAction = false, className, }: CommentProps) => { + // TODO: if REST API becomes popular, all interactions (click handlers) should implement a loading state and error state + // (or optimistic local updates) + const dict = useDictionary(); - // TODO: review use of sub-editor const commentEditor = useCreateBlockNote( { initialContent: comment.body, @@ -115,7 +118,6 @@ export const Comment = ({ const onEditSubmit = useCallback( async (_event: MouseEvent) => { - // TODO: show error on failure? await threadStore.updateComment({ commentId: comment.id, comment: { @@ -130,7 +132,6 @@ export const Comment = ({ ); const onDelete = useCallback(async () => { - // TODO: show error on failure? await threadStore.deleteComment({ commentId: comment.id, threadId: thread.id, @@ -140,7 +141,6 @@ export const Comment = ({ const onReactionSelect = useCallback( async (emoji: string) => { if (threadStore.auth.canAddReaction(comment, emoji)) { - // TODO: show error on failure? await threadStore.addReaction({ threadId: thread.id, commentId: comment.id, @@ -158,14 +158,12 @@ export const Comment = ({ ); const onResolve = useCallback(async () => { - // TODO: show error on failure? await threadStore.resolveThread({ threadId: thread.id, }); }, [thread.id, threadStore]); const onReopen = useCallback(async () => { - // TODO: show error on failure? await threadStore.unresolveThread({ threadId: thread.id, }); diff --git a/packages/react/src/components/Comments/FloatingComposer.tsx b/packages/react/src/components/Comments/FloatingComposer.tsx index 9f4133caf3..a54a79ed8f 100644 --- a/packages/react/src/components/Comments/FloatingComposer.tsx +++ b/packages/react/src/components/Comments/FloatingComposer.tsx @@ -24,7 +24,6 @@ export function FloatingComposer() { const Components = useComponentsContext()!; const dict = useDictionary(); - // TODO: review use of sub-editor const newCommentEditor = useCreateBlockNote({ trailingBlock: false, dictionary: { @@ -54,7 +53,7 @@ export function FloatingComposer() { variant="compact" isDisabled={isEmpty} onClick={async () => { - // TODO: handle errors? + // (later) For REST API, we should implement a loading state and error state await comments.createThread({ initialComment: { body: newCommentEditor.document, diff --git a/packages/react/src/components/Comments/FloatingComposerController.tsx b/packages/react/src/components/Comments/FloatingComposerController.tsx index 6fd49ad3a9..00ad0ced1c 100644 --- a/packages/react/src/components/Comments/FloatingComposerController.tsx +++ b/packages/react/src/components/Comments/FloatingComposerController.tsx @@ -53,7 +53,6 @@ export const FloatingComposerController = < middleware: [offset(10), shift(), flip()], onOpenChange: (open) => { if (!open) { - // TODO comments.stopPendingComment(); editor.focus(); } diff --git a/packages/react/src/components/Comments/FloatingThreadController.tsx b/packages/react/src/components/Comments/FloatingThreadController.tsx index ee8bd040b5..d1ec8c1d91 100644 --- a/packages/react/src/components/Comments/FloatingThreadController.tsx +++ b/packages/react/src/components/Comments/FloatingThreadController.tsx @@ -51,8 +51,9 @@ export const FloatingThreadController = < middleware: [offset(10), shift(), flip()], onOpenChange: (open) => { if (!open) { - // editor.filePanel!.closeMenu(); - // editor.focus(); + // TODO; + // editor.comments!.closeMenu(); + editor.focus(); } }, ...props.floatingOptions, diff --git a/packages/react/src/components/Comments/Thread.tsx b/packages/react/src/components/Comments/Thread.tsx index 44fb4a04b6..7c0f92ebfb 100644 --- a/packages/react/src/components/Comments/Thread.tsx +++ b/packages/react/src/components/Comments/Thread.tsx @@ -58,6 +58,9 @@ export const Thread = ({ className, ...props }: ThreadProps) => { + // TODO: if REST API becomes popular, all interactions (click handlers) should implement a loading state and error state + // (or optimistic local updates) + const editor = useBlockNoteEditor(); const comments = editor.comments; @@ -86,7 +89,6 @@ export const Thread = ({ // load all user data useUsers(editor, userIds); - // TODO: review use of sub-editor const newCommentEditor = useCreateBlockNote({ trailingBlock: false, dictionary: { @@ -105,7 +107,6 @@ export const Thread = ({ }, [showDeletedComments, thread.comments]); const onNewCommentSave = useCallback(async () => { - // TODO: show error on failure? await comments.threadStore.addComment({ comment: { body: newCommentEditor.document, diff --git a/packages/react/src/components/Comments/useThreads.ts b/packages/react/src/components/Comments/useThreads.ts index ae8decb9c7..d13b315002 100644 --- a/packages/react/src/components/Comments/useThreads.ts +++ b/packages/react/src/components/Comments/useThreads.ts @@ -1,6 +1,9 @@ import { BlockNoteEditor, ThreadData } from "@blocknote/core"; import { useCallback, useRef, useSyncExternalStore } from "react"; +/** + * Bridges the ThreadStore to React using useSyncExternalStore. + */ export function useThreads(editor: BlockNoteEditor) { const comments = editor.comments; if (!comments) { diff --git a/packages/react/src/components/Comments/useUsers.ts b/packages/react/src/components/Comments/useUsers.ts index ab83af2453..7901e97904 100644 --- a/packages/react/src/components/Comments/useUsers.ts +++ b/packages/react/src/components/Comments/useUsers.ts @@ -8,6 +8,9 @@ export function useUser( return useUsers(editor, [userId]).get(userId); } +/** + * Bridges the UserStore to React using useSyncExternalStore. + */ export function useUsers( editor: BlockNoteEditor, userIds: string[] From 8ec3e507664fc07fe6704ff95102baddecbf089d Mon Sep 17 00:00:00 2001 From: yousefed Date: Sun, 23 Feb 2025 17:55:20 +0100 Subject: [PATCH 078/144] clean commentsplugin --- packages/core/src/editor/BlockNoteEditor.ts | 20 ++++++- .../src/extensions/Comments/CommentsPlugin.ts | 59 ++++++++++++------- 2 files changed, 56 insertions(+), 23 deletions(-) diff --git a/packages/core/src/editor/BlockNoteEditor.ts b/packages/core/src/editor/BlockNoteEditor.ts index 6bec5cb6e6..ab7ae083c6 100644 --- a/packages/core/src/editor/BlockNoteEditor.ts +++ b/packages/core/src/editor/BlockNoteEditor.ts @@ -100,6 +100,7 @@ import { CommentsPlugin } from "../extensions/Comments/CommentsPlugin.js"; import { ThreadStore } from "../extensions/Comments/threadstore/ThreadStore.js"; import { User } from "../models/User.js"; import "../style.css"; +import { EventEmitter } from "../util/EventEmitter.js"; export type BlockNoteExtensionFactory = ( editor: BlockNoteEditor @@ -292,7 +293,9 @@ export class BlockNoteEditor< BSchema extends BlockSchema = DefaultBlockSchema, ISchema extends InlineContentSchema = DefaultInlineContentSchema, SSchema extends StyleSchema = DefaultStyleSchema -> { +> extends EventEmitter<{ + create: void; +}> { private readonly _pmSchema: Schema; /** @@ -400,6 +403,7 @@ export class BlockNoteEditor< protected constructor( protected readonly options: Partial> ) { + super(); const anyOpts = options as any; if (anyOpts.onEditorContentChange) { throw new Error( @@ -613,6 +617,7 @@ export class BlockNoteEditor< // but we still need the schema this._pmSchema = getSchema(tiptapOptions.extensions!); } + this.emit("create"); } dispatch(tr: Transaction) { @@ -1271,6 +1276,19 @@ export class BlockNoteEditor< }; } + /** + * A callback function that runs when the editor has been initialized. + * + * This can be useful for plugins to initialize themselves after the editor has been initialized. + */ + public onCreate(callback: () => void) { + this.on("create", callback); + + return () => { + this.off("create", callback); + }; + } + public getSelectionBoundingBox() { if (!this.prosemirrorView) { return undefined; diff --git a/packages/core/src/extensions/Comments/CommentsPlugin.ts b/packages/core/src/extensions/Comments/CommentsPlugin.ts index a3fc234210..b44aa68a17 100644 --- a/packages/core/src/extensions/Comments/CommentsPlugin.ts +++ b/packages/core/src/extensions/Comments/CommentsPlugin.ts @@ -8,26 +8,30 @@ import { EventEmitter } from "../../util/EventEmitter.js"; import { ThreadStore } from "./threadstore/ThreadStore.js"; import { CommentBody, ThreadData } from "./types.js"; import { UserStore } from "./userstore/UserStore.js"; -const PLUGIN_KEY = new PluginKey(`blocknote-comments`); +const PLUGIN_KEY = new PluginKey(`blocknote-comments`); const SET_SELECTED_THREAD_ID = "SET_SELECTED_THREAD_ID"; -type CommentsPluginAction = { - name: typeof SET_SELECTED_THREAD_ID; -}; - type CommentsPluginState = { + /** + * Store the positions of all threads in the document. + * this can be used later to implement a floating sidebar + */ threadPositions: Map; - // selectedThreadId: string | null; - // selectedThreadPos: number | null; + /** + * Decorations to be rendered, specifically to indicate the selected thread + */ decorations: DecorationSet; }; +/** + * Get a new state (theadPositions and decorations) from the current document state + */ function updateState( doc: Node, selectedThreadId: string | undefined, markType: string -) { +): CommentsPluginState { const threadPositions = new Map(); const decorations: Decoration[] = []; // find all thread marks and store their position + create decoration for selected thread @@ -88,10 +92,6 @@ export class CommentsPlugin extends EventEmitter { */ private updateMarksFromThreads = (threads: Map) => { const ttEditor = this.editor._tiptapEditor; - if (!ttEditor) { - // TODO: better lifecycle management - return; - } ttEditor.state.doc.descendants((node, pos) => { node.marks.forEach((mark) => { @@ -142,21 +142,19 @@ export class CommentsPlugin extends EventEmitter { } this.userStore = new UserStore(editor.resolveUsers); - // TODO: unsubscribe + // Note: Plugins are currently not destroyed when the editor is destroyed. + // We should unsubscribe from the threadStore when the editor is destroyed. this.threadStore.subscribe(this.updateMarksFromThreads); - // initial - this.updateMarksFromThreads(this.threadStore.getThreads()); - - // TODO: remove settimeout - setTimeout(() => { + editor.onCreate(() => { + this.updateMarksFromThreads(this.threadStore.getThreads()); editor.onSelectionChange(() => { if (this.pendingComment) { this.pendingComment = false; this.emitStateUpdate(); } }); - }, 600); + }); // eslint-disable-next-line @typescript-eslint/no-this-alias const self = this; @@ -168,15 +166,15 @@ export class CommentsPlugin extends EventEmitter { return { threadPositions: new Map(), decorations: DecorationSet.empty, - } satisfies CommentsPluginState; + }; }, apply(tr, state) { - const action = tr.getMeta(PLUGIN_KEY) as CommentsPluginAction; + const action = tr.getMeta(PLUGIN_KEY); if (!tr.docChanged && !action) { return state; } - // Doc changed, but no action, just update rects + // The doc changed or the selected thread changed return updateState(tr.doc, self.selectedThreadId, markType); }, }, @@ -184,6 +182,9 @@ export class CommentsPlugin extends EventEmitter { decorations(state) { return PLUGIN_KEY.getState(state)?.decorations ?? DecorationSet.empty; }, + /** + * Handle click on a thread mark and mark it as selected + */ handleClick: (view, pos, event) => { if (event.button !== 0) { return; @@ -200,10 +201,12 @@ export class CommentsPlugin extends EventEmitter { }; const node = view.state.doc.nodeAt(pos); + if (!node) { selectThread(undefined); return; } + const commentMark = node.marks.find( (mark) => mark.type.name === markType && mark.attrs.orphan !== true ); @@ -215,6 +218,9 @@ export class CommentsPlugin extends EventEmitter { }); } + /** + * Subscribe to state updates + */ public onUpdate( callback: (state: { pendingComment: boolean; @@ -224,16 +230,25 @@ export class CommentsPlugin extends EventEmitter { return this.on("update", callback); } + /** + * Start a pending comment (e.g.: when clicking the "Add comment" button) + */ public startPendingComment() { this.pendingComment = true; this.emitStateUpdate(); } + /** + * Stop a pending comment (e.g.: user closes the comment composer) + */ public stopPendingComment() { this.pendingComment = false; this.emitStateUpdate(); } + /** + * Create a thread at the current selection + */ public async createThread(options: { initialComment: { body: CommentBody; From fa1e5a709a0386ba1bd1005333ad4543e26e7a60 Mon Sep 17 00:00:00 2001 From: yousefed Date: Sun, 23 Feb 2025 18:43:33 +0100 Subject: [PATCH 079/144] ForceSelectionVisible --- packages/core/src/editor/BlockNoteEditor.ts | 12 +++++ .../core/src/editor/BlockNoteExtensions.ts | 3 ++ packages/core/src/editor/editor.css | 8 +++ .../ShowSelection/ShowSelectionPlugin.ts | 52 +++++++++++++++++++ .../Comments/FloatingComposerController.tsx | 3 ++ 5 files changed, 78 insertions(+) create mode 100644 packages/core/src/extensions/ShowSelection/ShowSelectionPlugin.ts diff --git a/packages/core/src/editor/BlockNoteEditor.ts b/packages/core/src/editor/BlockNoteEditor.ts index ab7ae083c6..3a3b41ff3f 100644 --- a/packages/core/src/editor/BlockNoteEditor.ts +++ b/packages/core/src/editor/BlockNoteEditor.ts @@ -98,6 +98,7 @@ import { inlineContentToNodes } from "../api/nodeConversions/blockToNode.js"; import { nodeToBlock } from "../api/nodeConversions/nodeToBlock.js"; import { CommentsPlugin } from "../extensions/Comments/CommentsPlugin.js"; import { ThreadStore } from "../extensions/Comments/threadstore/ThreadStore.js"; +import { ShowSelectionPlugin } from "../extensions/ShowSelection/ShowSelectionPlugin.js"; import { User } from "../models/User.js"; import "../style.css"; import { EventEmitter } from "../util/EventEmitter.js"; @@ -369,6 +370,8 @@ export class BlockNoteEditor< >; public readonly comments?: CommentsPlugin; + private readonly showSelectionPlugin: ShowSelectionPlugin; + /** * The `uploadFile` method is what the editor uses when files need to be uploaded (for example when selecting an image to upload). * This method should set when creating the editor as this is application-specific. @@ -495,6 +498,7 @@ export class BlockNoteEditor< this.filePanel = this.extensions["filePanel"] as any; this.tableHandles = this.extensions["tableHandles"] as any; this.comments = this.extensions["comments"] as any; + this.showSelectionPlugin = this.extensions["showSelection"] as any; if (newOptions.uploadFile) { const uploadFile = newOptions.uploadFile; @@ -1349,4 +1353,12 @@ export class BlockNoteEditor< }) ); } + + public get ForceSelectionVisible() { + return this.showSelectionPlugin.ForceSelectionVisible; + } + + public set ForceSelectionVisible(forceSelectionVisible: boolean) { + this.showSelectionPlugin.ForceSelectionVisible = forceSelectionVisible; + } } diff --git a/packages/core/src/editor/BlockNoteExtensions.ts b/packages/core/src/editor/BlockNoteExtensions.ts index af11ede459..899655641b 100644 --- a/packages/core/src/editor/BlockNoteExtensions.ts +++ b/packages/core/src/editor/BlockNoteExtensions.ts @@ -26,6 +26,7 @@ import { import { NodeSelectionKeyboardPlugin } from "../extensions/NodeSelectionKeyboard/NodeSelectionKeyboardPlugin.js"; import { PlaceholderPlugin } from "../extensions/Placeholder/PlaceholderPlugin.js"; import { PreviousBlockTypePlugin } from "../extensions/PreviousBlockType/PreviousBlockTypePlugin.js"; +import { ShowSelectionPlugin } from "../extensions/ShowSelection/ShowSelectionPlugin.js"; import { SideMenuProsemirrorPlugin } from "../extensions/SideMenu/SideMenuPlugin.js"; import { SuggestionMenuProseMirrorPlugin } from "../extensions/SuggestionMenu/SuggestionPlugin.js"; import { TableHandlesProsemirrorPlugin } from "../extensions/TableHandles/TableHandlesPlugin.js"; @@ -132,6 +133,8 @@ export const getBlockNoteExtensions = < ret["nodeSelectionKeyboard"] = new NodeSelectionKeyboardPlugin(); + ret["showSelection"] = new ShowSelectionPlugin(opts.editor); + if (opts.comments) { ret["comments"] = new CommentsPlugin( opts.editor, diff --git a/packages/core/src/editor/editor.css b/packages/core/src/editor/editor.css index 7f3056508f..d4263bbbcb 100644 --- a/packages/core/src/editor/editor.css +++ b/packages/core/src/editor/editor.css @@ -188,3 +188,11 @@ Tippy popups that are appended to document.body directly .prosemirror-dropcursor-vertical { transition-property: left, right; } + +/* +For the ShowSelectionPlugin +*/ +[data-show-selection] { + background-color: highlight; + padding: 2px 0; +} diff --git a/packages/core/src/extensions/ShowSelection/ShowSelectionPlugin.ts b/packages/core/src/extensions/ShowSelection/ShowSelectionPlugin.ts new file mode 100644 index 0000000000..fa4b77961e --- /dev/null +++ b/packages/core/src/extensions/ShowSelection/ShowSelectionPlugin.ts @@ -0,0 +1,52 @@ +import { Plugin, PluginKey } from "prosemirror-state"; +import { Decoration, DecorationSet } from "prosemirror-view"; +import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; + +const PLUGIN_KEY = new PluginKey(`blocknote-show-selection`); + +/** + * Plugin that shows adds a decoration around the current selection + * This can be used to highlight the current selection in the UI even when the + * text editor is not focused. + */ +export class ShowSelectionPlugin { + public readonly plugin: Plugin; + private forceSelectionVisible = false; + + public constructor(private readonly editor: BlockNoteEditor) { + this.plugin = new Plugin({ + key: PLUGIN_KEY, + props: { + decorations: (state) => { + const { doc, selection } = state; + + if (!this.forceSelectionVisible) { + return DecorationSet.empty; + } + + const dec = Decoration.inline(selection.from, selection.to, { + "data-show-selection": "true", + }); + + return DecorationSet.create(doc, [dec]); + }, + }, + }); + } + + public set ForceSelectionVisible(forceSelectionVisible: boolean) { + if (this.forceSelectionVisible === forceSelectionVisible) { + return; + } + + this.forceSelectionVisible = forceSelectionVisible; + + this.editor.prosemirrorView?.dispatch( + this.editor.prosemirrorView?.state.tr.setMeta(PLUGIN_KEY, {}) + ); + } + + public get ForceSelectionVisible() { + return this.forceSelectionVisible; + } +} diff --git a/packages/react/src/components/Comments/FloatingComposerController.tsx b/packages/react/src/components/Comments/FloatingComposerController.tsx index 00ad0ced1c..845ac82963 100644 --- a/packages/react/src/components/Comments/FloatingComposerController.tsx +++ b/packages/react/src/components/Comments/FloatingComposerController.tsx @@ -36,9 +36,12 @@ export const FloatingComposerController = < const referencePos = useMemo(() => { if (!state?.pendingComment) { + editor.ForceSelectionVisible = false; return null; } + editor.ForceSelectionVisible = true; + // TODO: update referencepos when doc changes (remote updates) return editor.getSelectionBoundingBox(); }, [editor, state?.pendingComment]); From f2f29e738be5d2ee940e66e7f07472620554a25c Mon Sep 17 00:00:00 2001 From: yousefed Date: Sun, 23 Feb 2025 19:30:39 +0100 Subject: [PATCH 080/144] fix floatingcomposercontroller --- packages/core/src/editor/BlockNoteEditor.ts | 8 +++- .../Comments/FloatingComposerController.tsx | 22 +++++----- .../hooks/useEditorSelectionBoundingBox.ts | 43 +++++++++++++++++++ .../src/hooks/useEditorSelectionChange.ts | 7 +-- 4 files changed, 64 insertions(+), 16 deletions(-) create mode 100644 packages/react/src/hooks/useEditorSelectionBoundingBox.ts diff --git a/packages/core/src/editor/BlockNoteEditor.ts b/packages/core/src/editor/BlockNoteEditor.ts index 3a3b41ff3f..f4f2b12306 100644 --- a/packages/core/src/editor/BlockNoteEditor.ts +++ b/packages/core/src/editor/BlockNoteEditor.ts @@ -1258,14 +1258,18 @@ export class BlockNoteEditor< * @returns A function to remove the callback. */ public onSelectionChange( - callback: (editor: BlockNoteEditor) => void + callback: (editor: BlockNoteEditor) => void, + includeSelectionChangedByRemote?: boolean ) { if (this.headless) { return; } const cb = (e: { transaction: Transaction }) => { - if (e.transaction.getMeta(ySyncPluginKey)) { + if ( + e.transaction.getMeta(ySyncPluginKey) && + !includeSelectionChangedByRemote + ) { // selection changed because of a yjs sync (i.e.: other user was typing) // we don't want to trigger the callback in this case return; diff --git a/packages/react/src/components/Comments/FloatingComposerController.tsx b/packages/react/src/components/Comments/FloatingComposerController.tsx index 845ac82963..07145e0342 100644 --- a/packages/react/src/components/Comments/FloatingComposerController.tsx +++ b/packages/react/src/components/Comments/FloatingComposerController.tsx @@ -7,9 +7,10 @@ import { StyleSchema, } from "@blocknote/core"; import { UseFloatingOptions, flip, offset, shift } from "@floating-ui/react"; -import { ComponentProps, FC, useMemo } from "react"; +import { ComponentProps, FC, useEffect } from "react"; import { useBlockNoteEditor } from "../../hooks/useBlockNoteEditor.js"; +import { useEditorSelectionBoundingBox } from "../../hooks/useEditorSelectionBoundingBox.js"; import { useUIElementPositioning } from "../../hooks/useUIElementPositioning.js"; import { useUIPluginState } from "../../hooks/useUIPluginState.js"; import { FloatingComposer } from "./FloatingComposer.js"; @@ -34,19 +35,14 @@ export const FloatingComposerController = < const state = useUIPluginState(comments.onUpdate.bind(comments)); - const referencePos = useMemo(() => { - if (!state?.pendingComment) { - editor.ForceSelectionVisible = false; - return null; - } - - editor.ForceSelectionVisible = true; + // the reference position is updated automatically when the selection moves, + // this can happen when the document is updated by a remote user + const referencePos = useEditorSelectionBoundingBox(state?.pendingComment); - // TODO: update referencepos when doc changes (remote updates) - return editor.getSelectionBoundingBox(); + useEffect(() => { + editor.ForceSelectionVisible = !!state?.pendingComment; }, [editor, state?.pendingComment]); - // TODO: review const { isMounted, ref, style, getFloatingProps } = useUIElementPositioning( state?.pendingComment || false, referencePos || null, @@ -68,6 +64,10 @@ export const FloatingComposerController = < return null; } + // nice to have improvements would be: + // - transition transform property so composer box animates when remote document is changed + // - fade out on close + const Component = props.floatingComposer || FloatingComposer; return ( diff --git a/packages/react/src/hooks/useEditorSelectionBoundingBox.ts b/packages/react/src/hooks/useEditorSelectionBoundingBox.ts new file mode 100644 index 0000000000..f86bfdfc38 --- /dev/null +++ b/packages/react/src/hooks/useEditorSelectionBoundingBox.ts @@ -0,0 +1,43 @@ +import type { BlockNoteEditor } from "@blocknote/core"; +import { useCallback, useEffect, useState } from "react"; +import { useBlockNoteEditor } from "./useBlockNoteEditor.js"; +import { useEditorSelectionChange } from "./useEditorSelectionChange.js"; + +export function useEditorSelectionBoundingBox( + enabled?: boolean, + editor?: BlockNoteEditor +) { + const ctxEditor = useBlockNoteEditor(); + editor = editor || ctxEditor; + + const [boundingBox, setBoundingBox] = useState(() => { + if (!enabled) { + return undefined; + } + + return editor.getSelectionBoundingBox(); + }); + + const cb = useCallback(() => { + if (!enabled) { + return; + } + + const selection = editor.getSelectionBoundingBox(); + + setBoundingBox(selection); + }, [editor, enabled]); + + useEditorSelectionChange(cb, editor, true); + + // initial value when `enabled` changes + useEffect(() => { + if (!enabled) { + setBoundingBox(undefined); + } else { + setBoundingBox(editor.getSelectionBoundingBox()); + } + }, [enabled, editor]); + + return boundingBox; +} diff --git a/packages/react/src/hooks/useEditorSelectionChange.ts b/packages/react/src/hooks/useEditorSelectionChange.ts index fb4ebb02dc..65cae28b1c 100644 --- a/packages/react/src/hooks/useEditorSelectionChange.ts +++ b/packages/react/src/hooks/useEditorSelectionChange.ts @@ -4,7 +4,8 @@ import { useBlockNoteContext } from "../editor/BlockNoteContext.js"; export function useEditorSelectionChange( callback: () => void, - editor?: BlockNoteEditor + editor?: BlockNoteEditor, + includeSelectionChangedByRemote?: boolean ) { const editorContext = useBlockNoteContext(); if (!editor) { @@ -17,6 +18,6 @@ export function useEditorSelectionChange( "'editor' is required, either from BlockNoteContext or as a function argument" ); } - return editor.onSelectionChange(callback); - }, [callback, editor]); + return editor.onSelectionChange(callback, includeSelectionChangedByRemote); + }, [callback, editor, includeSelectionChangedByRemote]); } From e97985e133591495ab158a5876c99a7299411707 Mon Sep 17 00:00:00 2001 From: yousefed Date: Sun, 23 Feb 2025 19:45:22 +0100 Subject: [PATCH 081/144] fix unit test --- .../FormattingToolbar/FormattingToolbarPlugin.ts | 10 ++++++++-- .../components/Comments/FloatingThreadController.tsx | 6 +++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/core/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts b/packages/core/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts index 660f4ac43f..08b9222e2a 100644 --- a/packages/core/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts +++ b/packages/core/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts @@ -153,15 +153,20 @@ export class FormattingToolbarView implements PluginView { const from = Math.min(...ranges.map((range) => range.$from.pos)); const to = Math.max(...ranges.map((range) => range.$to.pos)); - const shouldShow = this.shouldShow?.({ + const shouldShow = this.shouldShow({ view, state, from, to, }); + // in jsdom, Range.prototype.getClientRects is not implemented, + // this would cause `getSelectionBoundingBox` to fail + // we can just ignore jsdom for now and not show the toolbar + const jsdom = typeof Range.prototype.getClientRects === "undefined"; + // Checks if menu should be shown/updated. - if (!this.preventShow && (shouldShow || this.preventHide)) { + if (!this.preventShow && (shouldShow || this.preventHide) && !jsdom) { // Unlike other UI elements, we don't prevent the formatting toolbar from // showing when the editor is not editable. This is because some buttons, // e.g. the download file button, should still be accessible. Therefore, @@ -223,6 +228,7 @@ export class FormattingToolbarView implements PluginView { } } + debugger; return posToDOMRect(this.pmView, from, to); } } diff --git a/packages/react/src/components/Comments/FloatingThreadController.tsx b/packages/react/src/components/Comments/FloatingThreadController.tsx index d1ec8c1d91..dfb25938d1 100644 --- a/packages/react/src/components/Comments/FloatingThreadController.tsx +++ b/packages/react/src/components/Comments/FloatingThreadController.tsx @@ -91,9 +91,13 @@ export const FloatingThreadController = < } if (!state.selectedThreadId) { - return null; // TODO + return null; } + // nice to have improvements: + // - fade out on close + // - transition transform property so composer box animates when remote document is changed + const Component = props.floatingThread || Thread; return ( From a4a90b862cd79c3a55f556e95ca6bf267f526260 Mon Sep 17 00:00:00 2001 From: yousefed Date: Sun, 23 Feb 2025 19:49:41 +0100 Subject: [PATCH 082/144] close threads on esc --- .../src/extensions/Comments/CommentsPlugin.ts | 30 +++++++++++-------- .../Comments/FloatingThreadController.tsx | 8 ++--- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/packages/core/src/extensions/Comments/CommentsPlugin.ts b/packages/core/src/extensions/Comments/CommentsPlugin.ts index b44aa68a17..02614cb6ab 100644 --- a/packages/core/src/extensions/Comments/CommentsPlugin.ts +++ b/packages/core/src/extensions/Comments/CommentsPlugin.ts @@ -190,20 +190,10 @@ export class CommentsPlugin extends EventEmitter { return; } - const selectThread = (threadId: string | undefined) => { - self.selectedThreadId = threadId; - self.emitStateUpdate(); - view.dispatch( - view.state.tr.setMeta(PLUGIN_KEY, { - name: SET_SELECTED_THREAD_ID, - }) - ); - }; - const node = view.state.doc.nodeAt(pos); if (!node) { - selectThread(undefined); + self.selectThread(undefined); return; } @@ -212,7 +202,7 @@ export class CommentsPlugin extends EventEmitter { ); const threadId = commentMark?.attrs.threadId as string | undefined; - selectThread(threadId); + self.selectThread(threadId); }, }, }); @@ -230,6 +220,22 @@ export class CommentsPlugin extends EventEmitter { return this.on("update", callback); } + /** + * Set the selected thread + */ + public selectThread(threadId: string | undefined) { + if (this.selectedThreadId === threadId) { + return; + } + this.selectedThreadId = threadId; + this.emitStateUpdate(); + this.editor.dispatch( + this.editor.prosemirrorView!.state.tr.setMeta(PLUGIN_KEY, { + name: SET_SELECTED_THREAD_ID, + }) + ); + } + /** * Start a pending comment (e.g.: when clicking the "Add comment" button) */ diff --git a/packages/react/src/components/Comments/FloatingThreadController.tsx b/packages/react/src/components/Comments/FloatingThreadController.tsx index dfb25938d1..42e8959e80 100644 --- a/packages/react/src/components/Comments/FloatingThreadController.tsx +++ b/packages/react/src/components/Comments/FloatingThreadController.tsx @@ -44,22 +44,22 @@ export const FloatingThreadController = < editor.comments.onUpdate.bind(editor.comments) ); - // TODO: review const { isMounted, ref, style, getFloatingProps, setReference } = useUIElementPositioning(!!state?.selectedThreadId, null, 5000, { placement: "bottom", middleware: [offset(10), shift(), flip()], onOpenChange: (open) => { if (!open) { - // TODO; - // editor.comments!.closeMenu(); + editor.comments?.selectThread(undefined); editor.focus(); } }, ...props.floatingOptions, }); - // TODO: could also use thread position from the state. prefer this? + // Positioning with [data-bn-thread-id] attribute is a bit hacky, + // we could probably also use the thread position from the plugin state? + // for now, this works ok const updateRef = useCallback(() => { if (!state?.selectedThreadId) { return; From 7b128dc092c913bcd0b0211866302d931eda41d5 Mon Sep 17 00:00:00 2001 From: yousefed Date: Sun, 23 Feb 2025 19:56:17 +0100 Subject: [PATCH 083/144] remove debugger --- .../src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/core/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts b/packages/core/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts index 08b9222e2a..84a8f3c63d 100644 --- a/packages/core/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts +++ b/packages/core/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts @@ -228,7 +228,6 @@ export class FormattingToolbarView implements PluginView { } } - debugger; return posToDOMRect(this.pmView, from, to); } } From 9ae74bd3649863a45a9409c4cf3af9da0f9a0bfe Mon Sep 17 00:00:00 2001 From: yousefed Date: Mon, 24 Feb 2025 20:39:47 +0100 Subject: [PATCH 084/144] small fix --- docs/components/example/styles.css | 34 +++++++++++-------- .../react/src/components/Comments/Comment.tsx | 6 ++++ 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/docs/components/example/styles.css b/docs/components/example/styles.css index 55a24cbf7c..798f06ae4c 100644 --- a/docs/components/example/styles.css +++ b/docs/components/example/styles.css @@ -1,34 +1,38 @@ :focus-visible { - box-shadow: unset !important; + box-shadow: unset !important; } .demo .nextra-code-block pre { - background-color: transparent !important; - margin: 0 !important; - padding: 0 !important; + background-color: transparent !important; + margin: 0 !important; + padding: 0 !important; } .demo .nextra-code-block code > span { - padding: 0 !important; + padding: 0 !important; } -.demo .bn-container, +.demo .bn-container:not(.bn-comment-editor), .demo .bn-editor { - height: 100%; + height: 100%; +} + +.demo .bn-container:not(.bn-comment-editor) .bn-editor { + height: 100%; } .demo .bn-editor { - overflow: auto; - padding-block: 1rem; + overflow: auto; + padding-block: 1rem; } .demo-contents a { - color: revert; - text-decoration: revert; + color: revert; + text-decoration: revert; } .demo code.bn-inline-content { - font-size: 1em; - line-height: 1.5; - display: block; -} \ No newline at end of file + font-size: 1em; + line-height: 1.5; + display: block; +} diff --git a/packages/react/src/components/Comments/Comment.tsx b/packages/react/src/components/Comments/Comment.tsx index 312b136c9c..9cbf11ea63 100644 --- a/packages/react/src/components/Comments/Comment.tsx +++ b/packages/react/src/components/Comments/Comment.tsx @@ -197,6 +197,7 @@ export const Comment = ({ onReactionSelect(emoji.native) }> @@ -206,6 +207,7 @@ export const Comment = ({ {showResolveOrReopen && (thread.resolved ? ( @@ -213,6 +215,7 @@ export const Comment = ({ ) : ( @@ -223,6 +226,7 @@ export const Comment = ({ @@ -231,6 +235,7 @@ export const Comment = ({ {canEditComment && ( } onClick={handleEdit}> Edit comment @@ -238,6 +243,7 @@ export const Comment = ({ )} {canDeleteComment && ( } onClick={onDelete}> Delete comment From 953aefa2d0866f90709e018c6d0020f23dbbcff1 Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Tue, 25 Feb 2025 18:22:34 +0100 Subject: [PATCH 085/144] Fixed badge styles on docs --- packages/mantine/src/style.css | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/packages/mantine/src/style.css b/packages/mantine/src/style.css index b1a309c0c7..028d601fd7 100644 --- a/packages/mantine/src/style.css +++ b/packages/mantine/src/style.css @@ -584,9 +584,15 @@ } .bn-mantine .bn-thread-comment { + align-items: flex-start; + flex-direction: column; gap: 0.25rem; } +.bn-mantine .bn-thread-comment p { + color: var(--bn-colors-menu-text); +} + .bn-mantine .bn-comment-actions-wrapper { width: 100%; display: flex; @@ -663,12 +669,19 @@ color: var(--mantine-primary-color-filled-hover); } -.bn-mantine .bn-badge .mantine-Chip-label > span:not(.mantine-Chip-iconWrapper) { +.bn-mantine + .bn-badge + .mantine-Chip-label + > span:not(.mantine-Chip-iconWrapper) { display: inline-flex; gap: 4px; } -.bn-mantine .bn-badge .mantine-Chip-label > span:not(.mantine-Chip-iconWrapper) > span { +.bn-mantine + .bn-badge + .mantine-Chip-label + > span:not(.mantine-Chip-iconWrapper) + > span { align-items: center; display: inline-flex; justify-content: center; From 46101a0ec3ad6d00357c395a1853e5472c9cb0a6 Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Tue, 25 Feb 2025 18:27:00 +0100 Subject: [PATCH 086/144] Fixed badge click handler --- packages/mantine/src/badge/Badge.tsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/mantine/src/badge/Badge.tsx b/packages/mantine/src/badge/Badge.tsx index 5f3d4c748e..60d93a76f2 100644 --- a/packages/mantine/src/badge/Badge.tsx +++ b/packages/mantine/src/badge/Badge.tsx @@ -6,7 +6,7 @@ import { import { assertEmpty } from "@blocknote/core"; import { ComponentProps } from "@blocknote/react"; -import { forwardRef, useState } from "react"; +import { MouseEvent, forwardRef, useState } from "react"; import { TooltipContent } from "../toolbar/ToolbarButton.js"; @@ -31,18 +31,19 @@ export const Badge = forwardRef< assertEmpty(rest, false); const [hideTooltip, setHideTooltip] = useState(false); + console.log("hideTooltip", hideTooltip); const badge = ( { - setHideTooltip(true); - onClick?.(event); - }} wrapperProps={{ onMouseEnter, onMouseLeave: () => setHideTooltip(false), + onClick: (event: MouseEvent) => { + setHideTooltip(true); + onClick?.(event); + }, }} variant={"light"} icon={<>} From ab00b5d68cfd5502e8a0de4cdf6b92d471c56a72 Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Tue, 25 Feb 2025 19:38:53 +0100 Subject: [PATCH 087/144] Added remaining UX fixes from Mantine to Ariakit/ShadCN --- packages/ariakit/src/comments/Comment.tsx | 7 ++- packages/ariakit/src/style.css | 1 + packages/react/src/hooks/useFocusWithin.ts | 64 ++++++++++++++++++++++ packages/react/src/index.ts | 1 + packages/shadcn/src/badge/Badge.tsx | 4 +- packages/shadcn/src/comments/Comment.tsx | 10 +++- packages/shadcn/src/toolbar/Toolbar.tsx | 4 +- 7 files changed, 84 insertions(+), 7 deletions(-) create mode 100644 packages/react/src/hooks/useFocusWithin.ts diff --git a/packages/ariakit/src/comments/Comment.tsx b/packages/ariakit/src/comments/Comment.tsx index ad8413da48..9db7a93778 100644 --- a/packages/ariakit/src/comments/Comment.tsx +++ b/packages/ariakit/src/comments/Comment.tsx @@ -1,7 +1,7 @@ import { Group as AriakitGroup } from "@ariakit/react"; import { assertEmpty } from "@blocknote/core"; -import { ComponentProps } from "@blocknote/react"; +import { ComponentProps, useFocusWithin } from "@blocknote/react"; import { forwardRef, useState } from "react"; const AuthorInfo = forwardRef< @@ -53,12 +53,14 @@ export const Comment = forwardRef< assertEmpty(rest, false); const [hovered, setHovered] = useState(false); + const { focused, ref: focusRef } = useFocusWithin(); const doShowActions = actions && (showActions === true || showActions === undefined || - (showActions === "hover" && hovered)); + (showActions === "hover" && hovered) || + focused); return ( setHovered(false)}> {doShowActions ? ( void; + onBlur?: (event: FocusEvent) => void; +} + +function containsRelatedTarget(event: FocusEvent) { + if ( + event.currentTarget instanceof HTMLElement && + event.relatedTarget instanceof HTMLElement + ) { + return event.currentTarget.contains(event.relatedTarget); + } + + return false; +} + +export function useFocusWithin({ + onBlur, + onFocus, +}: UseFocusWithinOptions = {}): { ref: React.RefObject; focused: boolean } { + const ref = useRef(null); + const [focused, setFocused] = useState(false); + const focusedRef = useRef(false); + + const _setFocused = (value: boolean) => { + setFocused(value); + focusedRef.current = value; + }; + + const handleFocusIn = (event: FocusEvent) => { + if (!focusedRef.current) { + _setFocused(true); + onFocus?.(event); + } + }; + + const handleFocusOut = (event: FocusEvent) => { + if (focusedRef.current && !containsRelatedTarget(event)) { + _setFocused(false); + onBlur?.(event); + } + }; + + useEffect(() => { + const node = ref.current; + + if (node) { + node.addEventListener("focusin", handleFocusIn); + node.addEventListener("focusout", handleFocusOut); + + return () => { + node?.removeEventListener("focusin", handleFocusIn); + node?.removeEventListener("focusout", handleFocusOut); + }; + } + + return undefined; + }, [handleFocusIn, handleFocusOut]); + + return { ref: ref as React.RefObject, focused }; +} diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index dd8215d700..4db62489c9 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -94,6 +94,7 @@ export * from "./hooks/useEditorChange.js"; export * from "./hooks/useEditorContentOrSelectionChange.js"; export * from "./hooks/useEditorForceUpdate.js"; export * from "./hooks/useEditorSelectionChange.js"; +export * from "./hooks/useFocusWithin.js"; export * from "./hooks/usePrefersColorScheme.js"; export * from "./hooks/useSelectedBlocks.js"; diff --git a/packages/shadcn/src/badge/Badge.tsx b/packages/shadcn/src/badge/Badge.tsx index 368c94011a..82b5de5ff4 100644 --- a/packages/shadcn/src/badge/Badge.tsx +++ b/packages/shadcn/src/badge/Badge.tsx @@ -50,7 +50,9 @@ export const Badge = forwardRef< {badge} + className={ + "bn-flex bn-flex-col bn-items-center bn-whitespace-pre-wrap" + }> {mainTooltip} {secondaryTooltip && {secondaryTooltip}} diff --git a/packages/shadcn/src/comments/Comment.tsx b/packages/shadcn/src/comments/Comment.tsx index dc734b185f..9590c1d3b2 100644 --- a/packages/shadcn/src/comments/Comment.tsx +++ b/packages/shadcn/src/comments/Comment.tsx @@ -1,5 +1,5 @@ import { assertEmpty } from "@blocknote/core"; -import { ComponentProps } from "@blocknote/react"; +import { ComponentProps, useFocusWithin } from "@blocknote/react"; import { forwardRef, useState } from "react"; import { cn } from "../lib/utils.js"; @@ -77,12 +77,14 @@ export const Comment = forwardRef< assertEmpty(rest); const [hovered, setHovered] = useState(false); + const { focused, ref: focusRef } = useFocusWithin(); const doShowActions = actions && (showActions === true || showActions === undefined || - (showActions === "hover" && hovered)); + (showActions === "hover" && hovered) || + focused); return (
{doShowActions ? ( -
+
{actions}
) : null} diff --git a/packages/shadcn/src/toolbar/Toolbar.tsx b/packages/shadcn/src/toolbar/Toolbar.tsx index ee1582adbe..ba3fd592e7 100644 --- a/packages/shadcn/src/toolbar/Toolbar.tsx +++ b/packages/shadcn/src/toolbar/Toolbar.tsx @@ -110,7 +110,9 @@ export const ToolbarButton = forwardRef( {trigger} + className={ + "bn-flex bn-flex-col bn-items-center bn-whitespace-pre-wrap" + }> {mainTooltip} {secondaryTooltip && {secondaryTooltip}} From 38a670d6af51fe38c517d625469e523656e0734a Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Wed, 26 Feb 2025 17:09:01 +0100 Subject: [PATCH 088/144] Externalized strings --- packages/core/src/i18n/locales/ar.ts | 15 +++++++++++++++ packages/core/src/i18n/locales/de.ts | 15 +++++++++++++++ packages/core/src/i18n/locales/en.ts | 15 +++++++++++++++ packages/core/src/i18n/locales/es.ts | 15 +++++++++++++++ packages/core/src/i18n/locales/fr.ts | 15 +++++++++++++++ packages/core/src/i18n/locales/hr.ts | 15 +++++++++++++++ packages/core/src/i18n/locales/is.ts | 15 +++++++++++++++ packages/core/src/i18n/locales/it.ts | 15 +++++++++++++++ packages/core/src/i18n/locales/ja.ts | 15 +++++++++++++++ packages/core/src/i18n/locales/ko.ts | 15 +++++++++++++++ packages/core/src/i18n/locales/nl.ts | 15 +++++++++++++++ packages/core/src/i18n/locales/pl.ts | 15 +++++++++++++++ packages/core/src/i18n/locales/pt.ts | 15 +++++++++++++++ packages/core/src/i18n/locales/ru.ts | 15 +++++++++++++++ packages/core/src/i18n/locales/uk.ts | 15 +++++++++++++++ packages/core/src/i18n/locales/vi.ts | 15 +++++++++++++++ packages/core/src/i18n/locales/zh.ts | 15 +++++++++++++++ .../react/src/components/Comments/Comment.tsx | 14 +++++++------- .../src/components/Comments/FloatingComposer.tsx | 2 +- .../src/components/Comments/ReactionBadge.tsx | 4 +++- packages/react/src/components/Comments/Thread.tsx | 2 +- 21 files changed, 267 insertions(+), 10 deletions(-) diff --git a/packages/core/src/i18n/locales/ar.ts b/packages/core/src/i18n/locales/ar.ts index 26bd7e41e8..496afb342f 100644 --- a/packages/core/src/i18n/locales/ar.ts +++ b/packages/core/src/i18n/locales/ar.ts @@ -115,6 +115,9 @@ export const ar: Dictionary = { bulletListItem: "قائمة", numberedListItem: "قائمة", checkListItem: "قائمة", + new_comment: "اكتب تعليقًا...", + edit_comment: "تحرير التعليق...", + comment_reply: "أضف تعليقًا...", }, file_blocks: { image: { @@ -303,6 +306,18 @@ export const ar: Dictionary = { url_placeholder: "تحرير الرابط", }, }, + comments: { + actions: { + add_reaction: "أضف تفاعلًا", + resolve: "حل", + edit_comment: "تحرير التعليق", + delete_comment: "حذف التعليق", + more_actions: "المزيد من الإجراءات", + }, + reactions: { + reacted_by: "تفاعل بواسطة", + }, + }, generic: { ctrl_shortcut: "Ctrl", }, diff --git a/packages/core/src/i18n/locales/de.ts b/packages/core/src/i18n/locales/de.ts index 6d9e6adadc..1510f228c7 100644 --- a/packages/core/src/i18n/locales/de.ts +++ b/packages/core/src/i18n/locales/de.ts @@ -129,6 +129,9 @@ export const de = { bulletListItem: "Liste", numberedListItem: "Liste", checkListItem: "Liste", + new_comment: "Einen Kommentar schreiben...", + edit_comment: "Kommentar bearbeiten...", + comment_reply: "Kommentar hinzufügen...", }, file_blocks: { image: { @@ -315,6 +318,18 @@ export const de = { url_placeholder: "URL bearbeiten", }, }, + comments: { + actions: { + add_reaction: "Reaktion hinzufügen", + resolve: "Lösen", + edit_comment: "Kommentar bearbeiten", + delete_comment: "Kommentar löschen", + more_actions: "Weitere Aktionen", + }, + reactions: { + reacted_by: "Reagiert von", + }, + }, generic: { ctrl_shortcut: "Strg", }, diff --git a/packages/core/src/i18n/locales/en.ts b/packages/core/src/i18n/locales/en.ts index 816dd7e855..15f1536d73 100644 --- a/packages/core/src/i18n/locales/en.ts +++ b/packages/core/src/i18n/locales/en.ts @@ -130,6 +130,9 @@ export const en = { numberedListItem: "List", checkListItem: "List", emptyDocument: undefined, + new_comment: "Write a comment...", + edit_comment: "Edit comment...", + comment_reply: "Add comment...", } as Record, file_blocks: { image: { @@ -318,6 +321,18 @@ export const en = { url_placeholder: "Edit URL", }, }, + comments: { + actions: { + add_reaction: "Add reaction", + resolve: "Resolve", + edit_comment: "Edit comment", + delete_comment: "Delete comment", + more_actions: "More actions", + }, + reactions: { + reacted_by: "Reacted by", + }, + }, generic: { ctrl_shortcut: "Ctrl", }, diff --git a/packages/core/src/i18n/locales/es.ts b/packages/core/src/i18n/locales/es.ts index 2293186610..92690dc7b6 100644 --- a/packages/core/src/i18n/locales/es.ts +++ b/packages/core/src/i18n/locales/es.ts @@ -128,6 +128,9 @@ export const es = { bulletListItem: "Lista", numberedListItem: "Lista", checkListItem: "Lista", + new_comment: "Escribe un comentario...", + edit_comment: "Editar comentario...", + comment_reply: "Agregar comentario...", }, file_blocks: { image: { @@ -314,6 +317,18 @@ export const es = { url_placeholder: "Editar URL", }, }, + comments: { + actions: { + add_reaction: "Agregar reacción", + resolve: "Resolver", + edit_comment: "Editar comentario", + delete_comment: "Eliminar comentario", + more_actions: "Más acciones", + }, + reactions: { + reacted_by: "Reaccionado por", + }, + }, generic: { ctrl_shortcut: "Ctrl", }, diff --git a/packages/core/src/i18n/locales/fr.ts b/packages/core/src/i18n/locales/fr.ts index 61ff162992..eb3c6aa1b1 100644 --- a/packages/core/src/i18n/locales/fr.ts +++ b/packages/core/src/i18n/locales/fr.ts @@ -154,6 +154,9 @@ export const fr: Dictionary = { bulletListItem: "Liste", numberedListItem: "Liste", checkListItem: "Liste", + new_comment: "Écrire un commentaire...", + edit_comment: "Modifier le commentaire...", + comment_reply: "Ajouter un commentaire...", }, file_blocks: { image: { @@ -342,6 +345,18 @@ export const fr: Dictionary = { url_placeholder: "Modifier l'URL", }, }, + comments: { + actions: { + add_reaction: "Ajouter une réaction", + resolve: "Résoudre", + edit_comment: "Modifier le commentaire", + delete_comment: "Supprimer le commentaire", + more_actions: "Plus d'actions", + }, + reactions: { + reacted_by: "Réagi par", + }, + }, generic: { ctrl_shortcut: "Ctrl", }, diff --git a/packages/core/src/i18n/locales/hr.ts b/packages/core/src/i18n/locales/hr.ts index b8df7d3a49..d3da2e4032 100644 --- a/packages/core/src/i18n/locales/hr.ts +++ b/packages/core/src/i18n/locales/hr.ts @@ -138,6 +138,9 @@ export const hr = { bulletListItem: "Lista", numberedListItem: "Lista", checkListItem: "Lista", + new_comment: "Napišite komentar...", + edit_comment: "Uredi komentar...", + comment_reply: "Dodaj komentar...", }, file_blocks: { image: { @@ -326,6 +329,18 @@ export const hr = { url_placeholder: "Uredi URL", }, }, + comments: { + actions: { + add_reaction: "Dodaj reakciju", + resolve: "Riješi", + edit_comment: "Uredi komentar", + delete_comment: "Obriši komentar", + more_actions: "Više radnji", + }, + reactions: { + reacted_by: "Reagirao/la", + }, + }, generic: { ctrl_shortcut: "Ctrl", }, diff --git a/packages/core/src/i18n/locales/is.ts b/packages/core/src/i18n/locales/is.ts index f21f450445..022207b818 100644 --- a/packages/core/src/i18n/locales/is.ts +++ b/packages/core/src/i18n/locales/is.ts @@ -123,6 +123,9 @@ export const is: Dictionary = { bulletListItem: "Listi", numberedListItem: "Listi", checkListItem: "Listi", + new_comment: "Skrifaðu athugasemd...", + edit_comment: "Breyta athugasemd...", + comment_reply: "Bæta við athugasemd...", }, file_blocks: { image: { @@ -310,6 +313,18 @@ export const is: Dictionary = { url_placeholder: "Breyta URL", }, }, + comments: { + actions: { + add_reaction: "Bæta við viðbrögðum", + resolve: "Leysa", + edit_comment: "Breyta athugasemd", + delete_comment: "Eyða athugasemd", + more_actions: "Fleiri aðgerðir", + }, + reactions: { + reacted_by: "Brást við af", + }, + }, generic: { ctrl_shortcut: "Ctrl", }, diff --git a/packages/core/src/i18n/locales/it.ts b/packages/core/src/i18n/locales/it.ts index dafefb0f83..986ed16b9f 100644 --- a/packages/core/src/i18n/locales/it.ts +++ b/packages/core/src/i18n/locales/it.ts @@ -123,6 +123,9 @@ export const it = { bulletListItem: "Elenco", numberedListItem: "Elenco", checkListItem: "Elenco", + new_comment: "Scrivi un commento...", + edit_comment: "Modifica commento...", + comment_reply: "Aggiungi commento...", }, file_blocks: { image: { @@ -311,6 +314,18 @@ export const it = { url_placeholder: "Modifica URL", }, }, + comments: { + actions: { + add_reaction: "Aggiungi reazione", + resolve: "Risolvi", + edit_comment: "Modifica commento", + delete_comment: "Elimina commento", + more_actions: "Altre azioni", + }, + reactions: { + reacted_by: "Reagito da", + }, + }, generic: { ctrl_shortcut: "Ctrl", }, diff --git a/packages/core/src/i18n/locales/ja.ts b/packages/core/src/i18n/locales/ja.ts index abb3b4b4d0..4ed8ba1721 100644 --- a/packages/core/src/i18n/locales/ja.ts +++ b/packages/core/src/i18n/locales/ja.ts @@ -150,6 +150,9 @@ export const ja: Dictionary = { bulletListItem: "リストを追加", numberedListItem: "リストを追加", checkListItem: "リストを追加", + new_comment: "コメントを書く...", + edit_comment: "コメントを編集...", + comment_reply: "コメントを追加...", }, file_blocks: { image: { @@ -338,6 +341,18 @@ export const ja: Dictionary = { url_placeholder: "URLを編集", }, }, + comments: { + actions: { + add_reaction: "リアクションを追加", + resolve: "解決", + edit_comment: "コメントを編集", + delete_comment: "コメントを削除", + more_actions: "その他の操作", + }, + reactions: { + reacted_by: "リアクションした人", + }, + }, generic: { ctrl_shortcut: "Ctrl", }, diff --git a/packages/core/src/i18n/locales/ko.ts b/packages/core/src/i18n/locales/ko.ts index 70a0f2d0c1..463978dc78 100644 --- a/packages/core/src/i18n/locales/ko.ts +++ b/packages/core/src/i18n/locales/ko.ts @@ -143,6 +143,9 @@ export const ko: Dictionary = { bulletListItem: "목록", numberedListItem: "목록", checkListItem: "목록", + new_comment: "댓글 작성...", + edit_comment: "댓글 수정...", + comment_reply: "댓글 추가...", }, file_blocks: { image: { @@ -331,6 +334,18 @@ export const ko: Dictionary = { url_placeholder: "URL 수정", }, }, + comments: { + actions: { + add_reaction: "반응 추가", + resolve: "해결", + edit_comment: "댓글 수정", + delete_comment: "댓글 삭제", + more_actions: "더 많은 작업", + }, + reactions: { + reacted_by: "반응한 사람", + }, + }, generic: { ctrl_shortcut: "Ctrl", }, diff --git a/packages/core/src/i18n/locales/nl.ts b/packages/core/src/i18n/locales/nl.ts index 07dcf6a09e..9b890a54cf 100644 --- a/packages/core/src/i18n/locales/nl.ts +++ b/packages/core/src/i18n/locales/nl.ts @@ -130,6 +130,9 @@ export const nl: Dictionary = { bulletListItem: "Lijst", numberedListItem: "Lijst", checkListItem: "Lijst", + new_comment: "Schrijf een reactie...", + edit_comment: "Reactie bewerken...", + comment_reply: "Reactie toevoegen...", }, file_blocks: { image: { @@ -317,6 +320,18 @@ export const nl: Dictionary = { url_placeholder: "Bewerk URL", }, }, + comments: { + actions: { + add_reaction: "Reactie toevoegen", + resolve: "Oplossen", + edit_comment: "Reactie bewerken", + delete_comment: "Reactie verwijderen", + more_actions: "Meer acties", + }, + reactions: { + reacted_by: "Gereageerd door", + }, + }, generic: { ctrl_shortcut: "Ctrl", }, diff --git a/packages/core/src/i18n/locales/pl.ts b/packages/core/src/i18n/locales/pl.ts index b8b9ad908d..e776bcd479 100644 --- a/packages/core/src/i18n/locales/pl.ts +++ b/packages/core/src/i18n/locales/pl.ts @@ -115,6 +115,9 @@ export const pl: Dictionary = { bulletListItem: "Lista", numberedListItem: "Lista", checkListItem: "Lista", + new_comment: "Napisz komentarz...", + edit_comment: "Edytuj komentarz...", + comment_reply: "Dodaj komentarz...", }, file_blocks: { image: { @@ -302,6 +305,18 @@ export const pl: Dictionary = { url_placeholder: "Edytuj URL", }, }, + comments: { + actions: { + add_reaction: "Dodaj reakcję", + resolve: "Rozwiąż", + edit_comment: "Edytuj komentarz", + delete_comment: "Usuń komentarz", + more_actions: "Więcej akcji", + }, + reactions: { + reacted_by: "Zareagowali", + }, + }, generic: { ctrl_shortcut: "Ctrl", }, diff --git a/packages/core/src/i18n/locales/pt.ts b/packages/core/src/i18n/locales/pt.ts index 049119de0c..bf9fbd52db 100644 --- a/packages/core/src/i18n/locales/pt.ts +++ b/packages/core/src/i18n/locales/pt.ts @@ -122,6 +122,9 @@ export const pt: Dictionary = { bulletListItem: "Lista", numberedListItem: "Lista", checkListItem: "Lista", + new_comment: "Escreva um comentário...", + edit_comment: "Editar comentário...", + comment_reply: "Adicionar comentário...", }, file_blocks: { image: { @@ -310,6 +313,18 @@ export const pt: Dictionary = { url_placeholder: "Editar URL", }, }, + comments: { + actions: { + add_reaction: "Adicionar reação", + resolve: "Resolver", + edit_comment: "Editar comentário", + delete_comment: "Excluir comentário", + more_actions: "Mais ações", + }, + reactions: { + reacted_by: "Reagido por", + }, + }, generic: { ctrl_shortcut: "Ctrl", }, diff --git a/packages/core/src/i18n/locales/ru.ts b/packages/core/src/i18n/locales/ru.ts index 6445104cb8..809fcc6347 100644 --- a/packages/core/src/i18n/locales/ru.ts +++ b/packages/core/src/i18n/locales/ru.ts @@ -157,6 +157,9 @@ export const ru: Dictionary = { bulletListItem: "Список", numberedListItem: "Список", checkListItem: "Список", + new_comment: "Напишите комментарий...", + edit_comment: "Редактировать комментарий...", + comment_reply: "Добавить комментарий...", }, file_blocks: { image: { @@ -345,6 +348,18 @@ export const ru: Dictionary = { url_placeholder: "Изменить URL", }, }, + comments: { + actions: { + add_reaction: "Добавить реакцию", + resolve: "Решить", + edit_comment: "Редактировать комментарий", + delete_comment: "Удалить комментарий", + more_actions: "Другие действия", + }, + reactions: { + reacted_by: "Отреагировал(а)", + }, + }, generic: { ctrl_shortcut: "Ctrl", }, diff --git a/packages/core/src/i18n/locales/uk.ts b/packages/core/src/i18n/locales/uk.ts index 28ee7cd93c..afda90672b 100644 --- a/packages/core/src/i18n/locales/uk.ts +++ b/packages/core/src/i18n/locales/uk.ts @@ -155,6 +155,9 @@ export const uk: Dictionary = { bulletListItem: "Список", numberedListItem: "Список", checkListItem: "Список", + new_comment: "Напишіть коментар...", + edit_comment: "Редагувати коментар...", + comment_reply: "Додати коментар...", }, file_blocks: { image: { @@ -342,6 +345,18 @@ export const uk: Dictionary = { url_placeholder: "Редагувати URL", }, }, + comments: { + actions: { + add_reaction: "Додати реакцію", + resolve: "Вирішити", + edit_comment: "Редагувати коментар", + delete_comment: "Видалити коментар", + more_actions: "Більше дій", + }, + reactions: { + reacted_by: "Відреагував(ла)", + }, + }, generic: { ctrl_shortcut: "Ctrl", }, diff --git a/packages/core/src/i18n/locales/vi.ts b/packages/core/src/i18n/locales/vi.ts index c9ae069214..41b12d088f 100644 --- a/packages/core/src/i18n/locales/vi.ts +++ b/packages/core/src/i18n/locales/vi.ts @@ -129,6 +129,9 @@ export const vi: Dictionary = { bulletListItem: "Danh sách", numberedListItem: "Danh sách", checkListItem: "Danh sách", + new_comment: "Viết bình luận...", + edit_comment: "Chỉnh sửa bình luận...", + comment_reply: "Thêm bình luận...", }, file_blocks: { image: { @@ -317,6 +320,18 @@ export const vi: Dictionary = { url_placeholder: "Chỉnh sửa URL", }, }, + comments: { + actions: { + add_reaction: "Thêm phản ứng", + resolve: "Giải quyết", + edit_comment: "Chỉnh sửa bình luận", + delete_comment: "Xóa bình luận", + more_actions: "Thêm hành động", + }, + reactions: { + reacted_by: "Phản ứng bởi", + }, + }, generic: { ctrl_shortcut: "Ctrl", }, diff --git a/packages/core/src/i18n/locales/zh.ts b/packages/core/src/i18n/locales/zh.ts index e6f5f9334e..7ae9fa384d 100644 --- a/packages/core/src/i18n/locales/zh.ts +++ b/packages/core/src/i18n/locales/zh.ts @@ -163,6 +163,9 @@ export const zh: Dictionary = { bulletListItem: "列表", numberedListItem: "列表", checkListItem: "列表", + new_comment: "写评论...", + edit_comment: "编辑评论...", + comment_reply: "添加评论...", }, file_blocks: { image: { @@ -351,6 +354,18 @@ export const zh: Dictionary = { url_placeholder: "编辑链接地址", }, }, + comments: { + actions: { + add_reaction: "添加反应", + resolve: "解决", + edit_comment: "编辑评论", + delete_comment: "删除评论", + more_actions: "更多操作", + }, + reactions: { + reacted_by: "已回应", + }, + }, generic: { ctrl_shortcut: "Ctrl", }, diff --git a/packages/react/src/components/Comments/Comment.tsx b/packages/react/src/components/Comments/Comment.tsx index 9cbf11ea63..6098643a5d 100644 --- a/packages/react/src/components/Comments/Comment.tsx +++ b/packages/react/src/components/Comments/Comment.tsx @@ -87,7 +87,7 @@ export const Comment = ({ dictionary: { ...dict, placeholders: { - emptyDocument: "Edit comment...", + emptyDocument: dict.placeholders.edit_comment, }, }, schema, @@ -198,7 +198,7 @@ export const Comment = ({ }> @@ -216,7 +216,7 @@ export const Comment = ({ ) : ( @@ -227,7 +227,7 @@ export const Comment = ({ @@ -238,7 +238,7 @@ export const Comment = ({ key={"edit-comment"} icon={} onClick={handleEdit}> - Edit comment + {dict.comments.actions.edit_comment} )} {canDeleteComment && ( @@ -246,7 +246,7 @@ export const Comment = ({ key={"delete-comment"} icon={} onClick={onDelete}> - Delete comment + {dict.comments.actions.delete_comment} )} @@ -309,7 +309,7 @@ export const Comment = ({ )} text={"+"} icon={} - mainTooltip="Add reaction" + mainTooltip={dict.comments.actions.add_reaction} /> diff --git a/packages/react/src/components/Comments/FloatingComposer.tsx b/packages/react/src/components/Comments/FloatingComposer.tsx index a54a79ed8f..a53ecfa0cf 100644 --- a/packages/react/src/components/Comments/FloatingComposer.tsx +++ b/packages/react/src/components/Comments/FloatingComposer.tsx @@ -29,7 +29,7 @@ export function FloatingComposer() { dictionary: { ...dict, placeholders: { - emptyDocument: "Write a comment...", + emptyDocument: dict.placeholders.new_comment, }, }, schema, diff --git a/packages/react/src/components/Comments/ReactionBadge.tsx b/packages/react/src/components/Comments/ReactionBadge.tsx index c876ad9453..ae8fb65b3b 100644 --- a/packages/react/src/components/Comments/ReactionBadge.tsx +++ b/packages/react/src/components/Comments/ReactionBadge.tsx @@ -1,6 +1,7 @@ import { CommentData, mergeCSSClasses } from "@blocknote/core"; import { useState } from "react"; +import { useDictionary } from "../../i18n/dictionary.js"; import { useComponentsContext } from "../../editor/ComponentsContext.js"; import { useBlockNoteEditor } from "../../hooks/useBlockNoteEditor.js"; import { useUsers } from "./useUsers.js"; @@ -11,6 +12,7 @@ export const ReactionBadge = (props: { onReactionSelect: (emoji: string) => void; }) => { const Components = useComponentsContext()!; + const dict = useDictionary(); const editor = useBlockNoteEditor(); if (!editor.comments) { @@ -43,7 +45,7 @@ export const ReactionBadge = (props: { )} onClick={() => props.onReactionSelect(reaction.emoji)} onMouseEnter={() => setUserIds(reaction.userIds)} - mainTooltip={"Reacted by"} + mainTooltip={dict.comments.reactions.reacted_by} secondaryTooltip={`${Array.from(users.values()) .map((user) => user.username) .join("\n")}`} diff --git a/packages/react/src/components/Comments/Thread.tsx b/packages/react/src/components/Comments/Thread.tsx index 7c0f92ebfb..3b173a85cf 100644 --- a/packages/react/src/components/Comments/Thread.tsx +++ b/packages/react/src/components/Comments/Thread.tsx @@ -94,7 +94,7 @@ export const Thread = ({ dictionary: { ...dict, placeholders: { - emptyDocument: "Add comment...", + emptyDocument: dict.placeholders.comment_reply, }, }, schema, From 8d8ebec51a30a6f9589a80eb99b8e8384f425c3c Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Wed, 26 Feb 2025 17:28:16 +0100 Subject: [PATCH 089/144] Small styling fix --- packages/mantine/src/style.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/mantine/src/style.css b/packages/mantine/src/style.css index 028d601fd7..023acf1d93 100644 --- a/packages/mantine/src/style.css +++ b/packages/mantine/src/style.css @@ -589,6 +589,10 @@ gap: 0.25rem; } +.bn-mantine .bn-thread-comment > .mantine-Group-root { + gap: 12px; +} + .bn-mantine .bn-thread-comment p { color: var(--bn-colors-menu-text); } From a7a56569fd55e88782c3cd462f581edf156b3c0f Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Wed, 26 Feb 2025 17:38:59 +0100 Subject: [PATCH 090/144] Small styling fix --- packages/mantine/src/style.css | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/mantine/src/style.css b/packages/mantine/src/style.css index 023acf1d93..fa120da74f 100644 --- a/packages/mantine/src/style.css +++ b/packages/mantine/src/style.css @@ -125,9 +125,16 @@ border-radius: 0; box-shadow: none; padding: 0; +} + +.bn-mantine .mantine-Tooltip-tooltip p:last-child { white-space: pre-wrap; } +.bn-mantine .mantine-Tooltip-tooltip p:first-child { + white-space: unset; +} + /* UI element styling */ /* Select styling */ From ebfb4da8c5f94621e6790f5e8b6deec4fe972bc4 Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Wed, 26 Feb 2025 17:44:51 +0100 Subject: [PATCH 091/144] Fix lint --- packages/mantine/src/badge/Badge.tsx | 1 - packages/react/src/hooks/useFocusWithin.ts | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/mantine/src/badge/Badge.tsx b/packages/mantine/src/badge/Badge.tsx index 60d93a76f2..c43168fd94 100644 --- a/packages/mantine/src/badge/Badge.tsx +++ b/packages/mantine/src/badge/Badge.tsx @@ -31,7 +31,6 @@ export const Badge = forwardRef< assertEmpty(rest, false); const [hideTooltip, setHideTooltip] = useState(false); - console.log("hideTooltip", hideTooltip); const badge = ( ({ focusedRef.current = value; }; + // eslint-disable-next-line react-hooks/exhaustive-deps const handleFocusIn = (event: FocusEvent) => { if (!focusedRef.current) { _setFocused(true); @@ -37,6 +38,7 @@ export function useFocusWithin({ } }; + // eslint-disable-next-line react-hooks/exhaustive-deps const handleFocusOut = (event: FocusEvent) => { if (focusedRef.current && !containsRelatedTarget(event)) { _setFocused(false); From 9944ca7ab662d45398dff1bb49e569f3a4cab72e Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Wed, 26 Feb 2025 17:50:54 +0100 Subject: [PATCH 092/144] Fixed side menu regression issue --- packages/mantine/src/menu/Menu.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/mantine/src/menu/Menu.tsx b/packages/mantine/src/menu/Menu.tsx index 5a632cab49..0ac54ff949 100644 --- a/packages/mantine/src/menu/Menu.tsx +++ b/packages/mantine/src/menu/Menu.tsx @@ -146,8 +146,8 @@ export const Menu = (props: ComponentProps["Generic"]["Menu"]["Root"]) => { return ( {children} From 30003d745ef821db41e2ad4cd4564cfd3b8e9679 Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Thu, 27 Feb 2025 21:52:34 +0100 Subject: [PATCH 093/144] Implemented PR feedback --- examples/07-collaboration/01-partykit/README.md | 2 +- examples/07-collaboration/02-liveblocks/README.md | 2 +- examples/07-collaboration/03-y-sweet/README.md | 3 ++- examples/07-collaboration/04-comments/README.md | 12 +++++++++++- packages/ariakit/src/popover/Popover.tsx | 1 - packages/ariakit/src/style.css | 3 ++- packages/ariakit/src/toolbar/ToolbarButton.tsx | 11 +---------- packages/core/src/editor/BlockNoteEditor.ts | 8 ++++---- .../ShowSelection/ShowSelectionPlugin.ts | 14 +++++++------- packages/mantine/src/badge/Badge.tsx | 2 +- packages/mantine/src/popover/Popover.tsx | 8 ++------ .../Comments/FloatingComposerController.tsx | 2 +- packages/react/src/editor/ComponentsContext.tsx | 1 - packages/shadcn/src/popover/popover.tsx | 3 +-- 14 files changed, 34 insertions(+), 38 deletions(-) diff --git a/examples/07-collaboration/01-partykit/README.md b/examples/07-collaboration/01-partykit/README.md index c7eeccd037..6480c3fef5 100644 --- a/examples/07-collaboration/01-partykit/README.md +++ b/examples/07-collaboration/01-partykit/README.md @@ -6,5 +6,5 @@ In this example, we use PartyKit to let multiple users collaborate on a single B **Relevant Docs:** -- [PartyKit](/docs/advanced/real-time-collaboration#partykit) - [Editor Setup](/docs/editor-basics/setup) +- [PartyKit](/docs/collaboration/real-time-collaboration#partykit) diff --git a/examples/07-collaboration/02-liveblocks/README.md b/examples/07-collaboration/02-liveblocks/README.md index 2f0d6a6bab..62652c386e 100644 --- a/examples/07-collaboration/02-liveblocks/README.md +++ b/examples/07-collaboration/02-liveblocks/README.md @@ -6,5 +6,5 @@ In this example, we use Liveblocks to let multiple users collaborate on a single **Relevant Docs:** -- [Liveblocks](/docs/advanced/real-time-collaboration#liveblocks) - [Editor Setup](/docs/editor-basics/setup) +- [Liveblocks](/docs/collaboration/real-time-collaboration#liveblocks) diff --git a/examples/07-collaboration/03-y-sweet/README.md b/examples/07-collaboration/03-y-sweet/README.md index 1c7334cce4..97e8961a12 100644 --- a/examples/07-collaboration/03-y-sweet/README.md +++ b/examples/07-collaboration/03-y-sweet/README.md @@ -6,5 +6,6 @@ In this example, we use Y-Sweet to let multiple users collaborate on a single Bl **Relevant Docs:** -- [Y-Sweet on Jamsocket](https://docs.jamsocket.com/y-sweet/tutorials/blocknote) - [Editor Setup](/docs/editor-basics/setup) +- [Real-time collaboration](/docs/collaboration/real-time-collaboration) +- [Y-Sweet on Jamsocket](https://docs.jamsocket.com/y-sweet/tutorials/blocknote) diff --git a/examples/07-collaboration/04-comments/README.md b/examples/07-collaboration/04-comments/README.md index fc7b54d750..e94dcd5a37 100644 --- a/examples/07-collaboration/04-comments/README.md +++ b/examples/07-collaboration/04-comments/README.md @@ -1,3 +1,13 @@ # Comments & Threads -TODO +In this example, you can add comments to the document while collaborating with others. You can also pick user accounts with different permissions, as well as react to, reply to, and resolve existing comments. + +**Try it out:** Click the "Add comment" button in the [Formatting Toolbar](/docs/ui-components/formatting-toolbar) to add a comment! + +**Relevant Docs:** + +- [Editor Setup](/docs/editor-basics/setup) +- [Real-time collaboration](/docs/collaboration/real-time-collaboration) +- [Y-Sweet on Jamsocket](https://docs.jamsocket.com/y-sweet/tutorials/blocknote) +- [Comments](/docs/collaboration/comments) + diff --git a/packages/ariakit/src/popover/Popover.tsx b/packages/ariakit/src/popover/Popover.tsx index 7118cbbbf7..9f6bb28950 100644 --- a/packages/ariakit/src/popover/Popover.tsx +++ b/packages/ariakit/src/popover/Popover.tsx @@ -45,7 +45,6 @@ export const Popover = ( ) => { const { children, opened, position, ...rest } = props; - // @ts-ignore TODO assertEmpty(rest); return ( diff --git a/packages/ariakit/src/style.css b/packages/ariakit/src/style.css index beb8d6f203..fdc42499db 100644 --- a/packages/ariakit/src/style.css +++ b/packages/ariakit/src/style.css @@ -13,7 +13,8 @@ .bn-ak-toolbar { height: fit-content; - overflow: visible; + overflow: scroll; + max-width: 100vw; } .bn-toolbar .bn-ak-button { diff --git a/packages/ariakit/src/toolbar/ToolbarButton.tsx b/packages/ariakit/src/toolbar/ToolbarButton.tsx index b277963db9..5042e64f19 100644 --- a/packages/ariakit/src/toolbar/ToolbarButton.tsx +++ b/packages/ariakit/src/toolbar/ToolbarButton.tsx @@ -27,14 +27,12 @@ export const ToolbarButton = forwardRef( isDisabled, onClick, label, + variant, ...rest } = props; // false, because rest props can be added by ariakit when button is used as a trigger // assertEmpty in this case is only used at typescript level, not runtime level - - // TODO - // @ts-ignore assertEmpty(rest, false); return ( @@ -57,13 +55,6 @@ export const ToolbarButton = forwardRef( onClick={onClick} aria-pressed={isSelected} data-selected={isSelected ? "true" : undefined} - data-test={ - // @ts-ignore TODO - props.mainTooltip.slice(0, 1).toLowerCase() + - // @ts-ignore TODO - props.mainTooltip.replace(/\s+/g, "").slice(1) - } - // size={"xs"} disabled={isDisabled || false} ref={ref} {...rest}> diff --git a/packages/core/src/editor/BlockNoteEditor.ts b/packages/core/src/editor/BlockNoteEditor.ts index f4f2b12306..bb95292e47 100644 --- a/packages/core/src/editor/BlockNoteEditor.ts +++ b/packages/core/src/editor/BlockNoteEditor.ts @@ -1358,11 +1358,11 @@ export class BlockNoteEditor< ); } - public get ForceSelectionVisible() { - return this.showSelectionPlugin.ForceSelectionVisible; + public getForceSelectionVisible() { + return this.showSelectionPlugin.getEnabled(); } - public set ForceSelectionVisible(forceSelectionVisible: boolean) { - this.showSelectionPlugin.ForceSelectionVisible = forceSelectionVisible; + public setForceSelectionVisible(forceSelectionVisible: boolean) { + this.showSelectionPlugin.setEnabled(forceSelectionVisible); } } diff --git a/packages/core/src/extensions/ShowSelection/ShowSelectionPlugin.ts b/packages/core/src/extensions/ShowSelection/ShowSelectionPlugin.ts index fa4b77961e..acb8a61cc3 100644 --- a/packages/core/src/extensions/ShowSelection/ShowSelectionPlugin.ts +++ b/packages/core/src/extensions/ShowSelection/ShowSelectionPlugin.ts @@ -11,7 +11,7 @@ const PLUGIN_KEY = new PluginKey(`blocknote-show-selection`); */ export class ShowSelectionPlugin { public readonly plugin: Plugin; - private forceSelectionVisible = false; + private enabled = false; public constructor(private readonly editor: BlockNoteEditor) { this.plugin = new Plugin({ @@ -20,7 +20,7 @@ export class ShowSelectionPlugin { decorations: (state) => { const { doc, selection } = state; - if (!this.forceSelectionVisible) { + if (!this.enabled) { return DecorationSet.empty; } @@ -34,19 +34,19 @@ export class ShowSelectionPlugin { }); } - public set ForceSelectionVisible(forceSelectionVisible: boolean) { - if (this.forceSelectionVisible === forceSelectionVisible) { + public setEnabled(enabled: boolean) { + if (this.enabled === enabled) { return; } - this.forceSelectionVisible = forceSelectionVisible; + this.enabled = enabled; this.editor.prosemirrorView?.dispatch( this.editor.prosemirrorView?.state.tr.setMeta(PLUGIN_KEY, {}) ); } - public get ForceSelectionVisible() { - return this.forceSelectionVisible; + public getEnabled() { + return this.enabled; } } diff --git a/packages/mantine/src/badge/Badge.tsx b/packages/mantine/src/badge/Badge.tsx index c43168fd94..27b077c015 100644 --- a/packages/mantine/src/badge/Badge.tsx +++ b/packages/mantine/src/badge/Badge.tsx @@ -45,7 +45,7 @@ export const Badge = forwardRef< }, }} variant={"light"} - icon={<>} + icon={null} ref={ref}> {icon} {text} diff --git a/packages/mantine/src/popover/Popover.tsx b/packages/mantine/src/popover/Popover.tsx index fd92374069..cd020ca93b 100644 --- a/packages/mantine/src/popover/Popover.tsx +++ b/packages/mantine/src/popover/Popover.tsx @@ -11,16 +11,12 @@ import { forwardRef } from "react"; export const Popover = ( props: ComponentProps["Generic"]["Popover"]["Root"] ) => { - const { opened, onOpenChange, position, children, ...rest } = props; + const { opened, position, children, ...rest } = props; assertEmpty(rest); return ( - + {children} ); diff --git a/packages/react/src/components/Comments/FloatingComposerController.tsx b/packages/react/src/components/Comments/FloatingComposerController.tsx index 07145e0342..db3bd1e47a 100644 --- a/packages/react/src/components/Comments/FloatingComposerController.tsx +++ b/packages/react/src/components/Comments/FloatingComposerController.tsx @@ -40,7 +40,7 @@ export const FloatingComposerController = < const referencePos = useEditorSelectionBoundingBox(state?.pendingComment); useEffect(() => { - editor.ForceSelectionVisible = !!state?.pendingComment; + editor.setForceSelectionVisible(!!state?.pendingComment); }, [editor, state?.pendingComment]); const { isMounted, ref, style, getFloatingProps } = useUIElementPositioning( diff --git a/packages/react/src/editor/ComponentsContext.tsx b/packages/react/src/editor/ComponentsContext.tsx index 42f6c3f2b7..38ef7765fd 100644 --- a/packages/react/src/editor/ComponentsContext.tsx +++ b/packages/react/src/editor/ComponentsContext.tsx @@ -283,7 +283,6 @@ export type ComponentProps = { Popover: { Root: { opened?: boolean; - onOpenChange?: (open: boolean) => void; position?: | "top" | "right" diff --git a/packages/shadcn/src/popover/popover.tsx b/packages/shadcn/src/popover/popover.tsx index 60809748ca..dacb3f4d39 100644 --- a/packages/shadcn/src/popover/popover.tsx +++ b/packages/shadcn/src/popover/popover.tsx @@ -12,7 +12,6 @@ export const Popover = ( children, opened, position, // unused - onOpenChange, ...rest } = props; @@ -21,7 +20,7 @@ export const Popover = ( const ShadCNComponents = useShadCNComponentsContext()!; return ( - + {children} ); From f1557b1e1f22782b08e1c21a6b5177ade465b603 Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Thu, 27 Feb 2025 22:21:44 +0100 Subject: [PATCH 094/144] Implemented PR feedback --- packages/core/package.json | 9 ++++++++- packages/core/src/extensions/Comments/CommentsPlugin.ts | 1 + .../components/Comments/FloatingComposerController.tsx | 9 +++++---- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/core/package.json b/packages/core/package.json index 89e057ed58..1315274793 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -105,7 +105,6 @@ "@types/emoji-mart": "^3.0.14", "@types/hast": "^3.0.0", "@types/uuid": "^8.3.4", - "@hocuspocus/provider": "^2.15.2", "eslint": "^8.10.0", "jsdom": "^25.0.1", "prettier": "^2.7.1", @@ -116,6 +115,14 @@ "vite-plugin-eslint": "^1.8.1", "vitest": "^2.0.3" }, + "peerDependencies": { + "@hocuspocus/provider": "^2.15.2" + }, + "peerDependenciesMeta": { + "hocuspocus/provider": { + "optional": true + } + }, "eslintConfig": { "extends": [ "../../.eslintrc.js" diff --git a/packages/core/src/extensions/Comments/CommentsPlugin.ts b/packages/core/src/extensions/Comments/CommentsPlugin.ts index 02614cb6ab..368359b515 100644 --- a/packages/core/src/extensions/Comments/CommentsPlugin.ts +++ b/packages/core/src/extensions/Comments/CommentsPlugin.ts @@ -147,6 +147,7 @@ export class CommentsPlugin extends EventEmitter { this.threadStore.subscribe(this.updateMarksFromThreads); editor.onCreate(() => { + // Need to wait for TipTap editor state to be initialized this.updateMarksFromThreads(this.threadStore.getThreads()); editor.onSelectionChange(() => { if (this.pendingComment) { diff --git a/packages/react/src/components/Comments/FloatingComposerController.tsx b/packages/react/src/components/Comments/FloatingComposerController.tsx index db3bd1e47a..a89626d43b 100644 --- a/packages/react/src/components/Comments/FloatingComposerController.tsx +++ b/packages/react/src/components/Comments/FloatingComposerController.tsx @@ -32,6 +32,11 @@ export const FloatingComposerController = < } const comments = editor.comments; + useEffect(() => { + comments.onUpdate((state) => + editor.setForceSelectionVisible(state.pendingComment) + ); + }, [comments, editor]); const state = useUIPluginState(comments.onUpdate.bind(comments)); @@ -39,10 +44,6 @@ export const FloatingComposerController = < // this can happen when the document is updated by a remote user const referencePos = useEditorSelectionBoundingBox(state?.pendingComment); - useEffect(() => { - editor.setForceSelectionVisible(!!state?.pendingComment); - }, [editor, state?.pendingComment]); - const { isMounted, ref, style, getFloatingProps } = useUIElementPositioning( state?.pendingComment || false, referencePos || null, From a647ec3e152ce2301acea8a8a6cb9e2427c48b54 Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Fri, 28 Feb 2025 10:44:01 +0100 Subject: [PATCH 095/144] Updated emoji picker screenshots --- .../ariakit-emoji-picker-chromium-linux.png | Bin 62045 -> 28898 bytes .../ariakit-emoji-picker-webkit-linux.png | Bin 179610 -> 78756 bytes ...shadcn-drag-handle-menu-chromium-linux.png | Bin 10808 -> 69247 bytes .../shadcn-drag-handle-menu-webkit-linux.png | Bin 34364 -> 78812 bytes .../dark-emoji-picker-chromium-linux.png | Bin 64383 -> 29884 bytes .../dark-emoji-picker-webkit-linux.png | Bin 187687 -> 80136 bytes 6 files changed, 0 insertions(+), 0 deletions(-) diff --git a/tests/src/end-to-end/ariakit/ariakit.test.ts-snapshots/ariakit-emoji-picker-chromium-linux.png b/tests/src/end-to-end/ariakit/ariakit.test.ts-snapshots/ariakit-emoji-picker-chromium-linux.png index 3f171df8b7d4722b6585661733e9b054556c8aca..58f301a0b4dc0b16b4325fa76429de08404f07c2 100644 GIT binary patch literal 28898 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 Sz;{bMjRV$>R`h+|asLCDZ*4FD literal 62045 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�?Lz+>lAK+Nu|>Dp^mM%CF?_qBjm7s+?6xUpz7u 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 diff --git a/tests/src/end-to-end/ariakit/ariakit.test.ts-snapshots/ariakit-emoji-picker-webkit-linux.png b/tests/src/end-to-end/ariakit/ariakit.test.ts-snapshots/ariakit-emoji-picker-webkit-linux.png index 00ba26310e0526522603d1b8d1eeef0649d862e5..74a78dd958572b87d9bf68279e4067e3461a33f4 100644 GIT binary patch literal 78756 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 literal 179610 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.k7o1-Z4YE$fpL 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�!+mDTy)^7yhFScBv9Q>4({SY3=r1& z$fcDc(D1gOVU*oq%BQ$44+LSp50zoJFuZEXhqH^_x5}#^f`ZE$R~xFXn=P`GR}OwsI)Uv6Fk;c75P{cb zBthixW=@H|_bVX_+M?+7ZiuX1sCGRRJ_V7u+31(+l6Sv&)^)1 z*-vQ6$zmqd+#&`R3*3Q(L5wlp`Ui^C;D*4jLV%pvzmqh&o$h5C7^#%uboPj3O$!c8 zb`;LKv}p+M$&3G}C5}==ZI*d4{I!5g0X(YRDdye)QT4)+)L$+4z#rIN(L@p9%OD+9 z%D6jw`MFe&`vPX2q`mX5m3t|h(3re_EZg#3e_ZJM*7Cw#bxoBx}weYQ$?PFwz22L(Ey=!0H{6Z zC9TtbxQEPTG38UuAq``s3wEKO4pPGy_EP3~x|c`bBW~%ua4(sYK92^={3cfyxy+F3 zh#(sF35M82pkwp(c`{zh1N)yG3euvx7NJ~sz;??2&O~v+9DW{RW5O^^ry5>i)nNU) z{3ft!(Cr~C0u6|G>HtG!pqLm~mT^6ar74Tr$9^U?z}9ZxD90)1vgV|*2xULaZ@J!d zq9M3yunfzur9~A9G>t_gD*?SOu0UV30MshrIFMf*VkSzowdh`M3tV>4xzB;k+czqh zo58DWD2jcp(Emf5pqyqdz)iiT8ET3p&E1_%2sA|Ba48_ZJON~tRpDwd?pQaDR4EmE zDr77=dvvAcC8~oY`I0v$dbq zIg&EJMOxIu8W<@6YcT(24ZuF~L)%7vSH=f70bCceVNG-{an~QhIFhor(J$dq1YUy(nzZKI&;@`N;hNo9O~i@XkG;;7|)b^n5mpN}?4M*137`)raLEjXAx zYv3PKgHsWPy3FXprY1E?Ozj?{SNd>0eNd@*A8e!18}r;m#J6Aq+Csgw&=hR_SzO$X)+&~Ihdx%!gDn9zf>BPsPr56+@UH^ED@Q;N0>_C zRjS3FN*PAg4s-Gi)iWo3A8`e3dVI`Nnz;zmM9qF;5Bv$h05|!8-(rk+GpS^QqbMH< z)2MBIj-L$^MyB+4GU7*X&?cVPPrxB88ICdrm@h7$WFN+KC>8rFv~OJELJn9nm%=7- zY%3%Gn5B3X*mJd?LxN;V)?qU-WY=|ljzRR3Z0e;sN86JrL!E}UYwstBw*E+D*#jma zaN>0Ek2%de#$Na`dR<_pYWNM0Ib^&m;dXBc;8y(hC)O0~&3K6-K@c8Yz3b>b?sgsj zm}~nimKcOL30BH5%?ZmAK+l*2uT(Ad?NC{hV9$U%sg0JCNx4 zpF;}YkUqbb9gtZ`1`Sy}$g;-glqgG;vicyaqJK5DvUrfigDf6o@$k7o%H{{zlqDN2 z(Xx1$w}9*tL3W8CyF?((hr@rmg}aAaya9fGPDBfH&b0==r3aSkx8TFzxfQqze6BpV zNb3;rZyZS11(d`5OW6ULm1NM6#e*zs{69&Ra>1qDV{u&xQE&y=z0=k4?)JcQ{|0{I B9JBxc diff --git a/tests/src/end-to-end/shadcn/shadcn.test.ts-snapshots/shadcn-drag-handle-menu-chromium-linux.png b/tests/src/end-to-end/shadcn/shadcn.test.ts-snapshots/shadcn-drag-handle-menu-chromium-linux.png index 73e9edbfcc9b979cb6c70d2cba0debee23061932..f37d20c24417b2401298a8d30d290b4f4f30316a 100644 GIT binary patch literal 69247 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 I^7-|@0Dko>&j0`b diff --git a/tests/src/end-to-end/shadcn/shadcn.test.ts-snapshots/shadcn-drag-handle-menu-webkit-linux.png b/tests/src/end-to-end/shadcn/shadcn.test.ts-snapshots/shadcn-drag-handle-menu-webkit-linux.png index b46604784c455bd14665a75f62d9f710d017fb84..204560f8fb9fa22dda68ebe62a273d49b74cf6eb 100644 GIT binary patch literal 78812 zcmeFZd010d+b$mdLIS<$W+OT5-2n71CGpE1y0)Y&FuKW3;4}kx2Ksz`fP#WmW*I#-k!R97@ z%Hh(Rt$$bR^;fFx{hR8N<7lsViPJByq^&)_Z(~p3p-Z=G()WF|FZU~l)?fANkAN=T zU8RG18T_vs5Qy;z^go^*sQkY-|L5zV`-MirFg3-gW*%j`XcnF_aC$Yzl&06Ivt9HW zDFgV243~lRN)`^*kgJi8#o)B_Y&Z@x>d_+MX1nO(M=y85=D~U`4-bc7H@|bHYY7|D zK%lvTy&-P#9IRRdZHzg)0c$aZk&hzMm4BpzVH>c}IMaB_7Hw1gIGpp%;zS}x>oX7C zvmhkVbkmN#$2h4{uW#1A>+ZPSx-USW1n5kR)rfW3E$}VMmibvnI9ADH;Iu+G_*S<@ zA!WJfg-ygyFM>h7N^vcTn$oUy0obOAzG_jc#WY&p2B|ohdYz&fD*~-r={L>p0%$Gh zVr!{3#%h=+jPH~)x2!FzcfoC(m zvoQaQT)u(aW_v6p=FZL59qAbM;41~7e`+uo@oCq-XS86j+7Ksz>zc2@5>78YU9?Mn zH^AoIV!*aMBonnXqLw zO{Ws#`&|wEtX%yD%1IX-gdG31?l#x{JKX%@Zs6k>o~*;@lmalZD~77%$KWmgm4k2N z5@-aRPRXU|wPHL0temYS{Ur)0`ez_^bw^tWh(8jO}7HxI>xgQ6T|%Q0{1k-IOrGWYBWzbyQFFs-aPH+DR)e18e+cs zOW3Ik?>v2R(jUY$)^}`C)f6ULSm>=MV1K=Gv1r9Pw1#nJ&;iXY(L!k5EAyIE10w-q zq2tZrI2gsm0fg?*-;|TTWyxzH=#V~h7Ky`YWo-OQ_Nq1S7{7MHFed)Ij_>*3dGm0A zpx3y5y>cZO^mrU>AYa}9KKj#-hN~yoFu=+UN=m!^Hr-|KyK6nrc+JHi;`k}}@1MN8 zMn45wFS@J@aAEq42&)38C0LHI{8=YCk8LpWE)xuZ_^L`*@LTLZe4&IIMbYkd;iIq!zG{< zWFV%aQSbiwYTOS%TQ_>eYYwCz__GyZ=%CBb(1AD@{`p@WgaWNo&W5p_#86#&r<)&p ziVPMMX97HQpBUbD=1W00h#Am+#Naz=iIw2* zoos>IUn=$5p3XJ#2V)TBYgBW4Cs zev&=QLhsvXW8enlbW-2>uG3M2`0jP!>s?3taE2#=dbPCNK<w&4n^|lB zD}@BS)g5Wj!v5iH4zLpHvO${cUzU!&bv6iMcOJABROk(#N;*rwwawrgAd@R1@$cvr zEg3pGQcdla6ffP{20Tit)FoclpOD!bNEWRyQv<#>cNTu#&<$wnxr%dNVYLb%(e1uM z^X0Z z1l~%_r#Y)R6)VIREDZE}mri8@^xs8Irxz6)(+pvQ`31o1MO)05fK*TJyp!tP z^F#1upy#TgiWRe0gd74*R;)FQKD%=uz_{-4-`nglUlLtcBz9eyDbk`=%rmsjPF@MF zT3RHm^qTF9!^vsTKW#L%GI;h6ZL>AJW1s|DhLk5)Sus3oxC#7bssf+`Ps}yWAxBA?{@}2y7J|#Zw%7y#q0s3yZNVdhS3QE zy_2pI95#0be(s&6M_0bQXo#a}KP}zL0F-6<&R8WY5=0twaO58KE@0v>|1|Nx3jDuO zm_GC&P_*&wR(FvpFd&%kVLdd$8WimyH+v}G35&B)cfJC+1>b)c^K(G^ob_WfwUrb-r0glekrHy(msue&2<0koBV85=rE5@yuKD2Zch)Af(R|rX`OJ0QNXF67I40=aBK599Vo29{ zbLR}n|5+uFy}>t_CubK0WL}^j?2Q$0VwBWk_24T6{3>lSgPWR0eCsMH2c{CP>+bO2tBT$;M(GDZ5>YUkfR}I%iH;R74^ns1TMNAKBvfjJ9P}E^M{?A_}a_ zJP+!!22AwM@BSM7|K0QLF8-sJ-sU;bXL+!1MvJLsjgNDt9T8Z4FBY76!DUi2f1X&A zvWq?6s@Hv@UfASXM9NOtMff!pQ;ry5M;?1dF~u&w;To0#Ig&%T3z|+}UG3_hNuWs+ zfavj+q~Nb85ejH+jv<*`-K|+n!HMd;DUplCOHm3hW&o?9faSnV3U^0YDPvsbHR?%X zO(b&0{VHD0%Mi=>LQElvC1EN%yg_w#pvjyqz$ya`nT+}JJ?{6-s)f>37o(HwNq_X1 zrCUH}6!u18^C3r5esFy1ml>pUerSr)VMtu&(UCrHyqp_J7-$$^JMvvCIV`5Md7F6F zQQ_zzmD!-0H>5eBIg_g|VtlT(BxW;I*8jmsb|B^<@r2Kg3DIWxF z@ZrW=Dnl??HNuS0A;v)$fn$4gCTl1`jR9}1iR-jWT1utF4`wN%sKum^-CRl^u9Z3L z=mPhfYvKXrh`uT`c9Q$=`%GRYdVGk%0tx_)7V z1o9ZP9b~V}cmFzT8T=Md_5qUE$75z)KFob)=2>pgi&nPXJap@iah<7y$2(&6WEQ38 zvG3Jc1{!+5#sw?eE>{P2iZ!X%(E?=1q+n_!W6ZyQh>M^`cXs7ODgJ!e%47$_!YQiR z3C#(bSQ=~5oLMRYw|aUl_a9yCgw1sp6b^CgfvP^z>|Hh6>e(d1;*g3mLUtr8bs*g5N4M%L$fB$6(HCx2 zvP}IMIeDR#d4%5u&qy=hHE3|?iNfEPx|0CG1P;9S~JD91wnnd zkmu#SwbtulP3S~UWW}9;GkxC4I)%D=i9xb`kt$+1n22AOc3mGWj8szw*qXznXRJfM55`sNpR4hFQjaFK5-c$5RT zZow|KCCdDVO_SQmMz|vlBC3L<7fQUsnydd$qf&k^1Y2MN%t}H_2!7P&_>Hv61kSwj+?r9oad; zh@Hxqtsp5UBbmnPYs2w{dit-aA?2iG_{=GarU6$L!@t<_TyU9ACTK~JLrb4tbU}xU z{G1^&`(N89?+Jt^|3Q+oV7@hx!!KBhq9l}%du@hKUgujW?lm$WXp9M5*Zf;bOM;KY z>Q=WI-gNTh0Hv`GzxOM84Src3VWlS?jE%6!inOlUN4dUOwWJoE%D1fD=ZcN#Lrxuk z=+Hv^^hI;Vvuf<9*YnxHI8L*v{bc;;J$Ngb1dP;utkJx3)#$KSU0;G~T-bZG=6m~S zGZ%p}hKk)GmC>S$NcNt&Ta>onV01p!;IX&T$J;XUaF|*Gn^4*t3!G*Az;hE-YYrlo)=|F>cb+*Nu`bTabIH@SPSP2IO~|v?|FVM z`I6JIdqVU7fR~S*0WXRM+~N9?Ay*)&_c~4NJ80a$OYeWM+RjVv)fX7&V%*<&E}u-U z-+JNjR_`WCMIYJ?tb2Jpi^j=;?cIT+C5#g5ME&PEA1VgfM7y8OLwjev;^11V$b5;= zU#(TdVmzhIF5fug4+r(XrZR2esIn9|OE`7>LsX~4nZ|m)1J7i1W$~Mv>9z0z#cxsy{7e(EV$;v>Og z4FW$1&p;$$Coj+sOOJ31B-Ny{U98Hs1NQvUd1KDgZyd;B6eHN7NBi)1&gGYWYdI#o zmFw2RVVS(j{q!OwVhQusJpZ-AjHP#+x7(*q@dmuK@$FLG>YtS=&8}o>7QtSPRV*c| zd+G)Th^60BC*9tkyu4TvT8Gf!3D(Hg5Vf|sG=M+tr6g@gI|E+Tbe8`O!1=FjeZI!7 z=G|J`276D&r(?(A!rIH^!k9??Vb&YRenkEO#*nz)m~ld?nd4G`>4rzNG5!vgHj7xp zqzP+j_?-TN=ohUevGd{i#3474{vMoUH%ReEy$pkATS~tbrta?dz;8$k8uTwx2b~Dz zl>d{1n6P00<2^X*3IwUrOoE;|}mSiM9I5){Tl6LR;E-X1~IN7uhyZ| zc2r#EZ1UJfOG?-0`w@zrP_!{S0|EBuDJa*ydc(Q#GU)eDp6e`Hm{K_%z)u(q}!RLCw4KMr`o`1&zkr^3Y1L z*ZRBtwustF$8t&oeuz$5J*eS}ZL^9JjceJZ>#jMe;e6F7eA{gM*hW)In|FAg<{0-# zfrj2ci^Rk@thwms)6RPR4Idp+5Zk~36SsM1rSAVl=a~F5%rx7P6jMe*qN3f)!sDPR zz0Ld>n5QaUty@wSk1>zHQM+WX3khmo>%+d^M)?JzvRG=?sZLeu%l<^9bh+7B@JE4m zJ1KEmnR4jgSO0z6*L8coh(Yy7wT4BNXmy8q8BH7u&C8PruvWJ8B>U5;ri@a4=$#hV{u^TW}Xonf6O7& z9vs?e$qlC@4P78T!|X)jW!f|Jn{EvR;clPuT<&&KxTa$kJ38q-YCkrQx8_xmOwoMd z@keyBpRbCofVt6090=i)f_ajdl_9m~N}g?lr)uWNEZCw_K%njNqfL&4l}GGsrbMFq zrBm+Gc&k^Lgv^?^a76V$Jf1&{ydb@H#nh4u8Hvw^z+;H-nFOT3Y=?#>uzDsjCraj| z{*JPW1N_FgX-QCyP47{9 z6d$sv#!lF91x*s^co15YIw1UGdFu;{wGAPO*6{`XP=p?;(iui!`%yS*lY=fcdbm`? zo+yUsb5(cvz^AU-6PK<~<$r|m!zhUei0>jKS2*G50uC>|jd4lY0@`ZWm-(k5ng6S% zlyM%EIyN`aO72}9u@#&0YYenTa{-}xLu=X|yzw`TBne6}K3d!uIU z@AJpK-ih5yl_lHkCw*rMNnIWj^b{ks9YJ=3&)w$DLWfBw_*LAt&*6+eCWg=~{|}g~ z_Gm&7+HUY^qvjaasFpK~%~F>{F;Bif=thwfIfMJt3uLmZtr5X@2WVS|bqBbPMylvD zM#aeI4tfYz*liXbX{Ugp9(DxlGgxd_sPfH7<8GGj*o)u+^mQT}~!v`N1vf7$)`I3%y?RyJSTR>n$e-yjn#kTYKq1I-5L~e57 zt^Ocr8uw(Rt}H4nNaL4@w2qD$@YmIn^~P*{FRG3@t0*A9lr4Ty=G^m27ecR~ZbAiJNS4GoBTk^r6+^-AwKg)uQVDnxEtHZYE&ly|H2X*=ABUikA+iCRy-_3m4eE zGT|X#MQZ)ktse=ISEZeiClvQM0*E?l){#pc8P#VkI9{So`VG8LSwRXOsJhar2_ zb#()^*1b_`d7PP<({@$fF;tGK9b7H-@lmsn?~~53!ky-&AS_yb8&~Nt!raN=CtO6`jCRg zo||Ys$KNC9#D(7jYku)X;c5M)^@5aqtJ>=hKw8VQDU50uk+yD&Ou16+wug&qk4BN= z`jT*5%rMJmaYmgmebWu|L7!w8mAtFdjsGn_@bFF?0mnxEx~#UYzWIFUC<@*lsljA^ zD+-l6sIip}v_xuF_{pJr5t$wB+x$^ zH`Xl7puwS>k&#j#eyGtAQa*_BNA~LBcw@HJ5x-TJtov~6OevWjRb7~19U5yrc4QET zm-MuS)zw@M;QdI9W#SN+-94z*Z4mm9SN_Ratt0m?N3q%?Bqr(vWv#stRFzP(;I%GO zQ`*mnIvgdaA{?;g(6!9QU`xLcXf#0?5dJ_D%q1Tf#lt+DW|8#geB^{KL)r|fVEg;b zYP(zxpcmC$kSxSqA-h`WBdLS?#xlK@pPFNCt8SAs4` zkGlvO--zk#8#~07zM0%(=$~x4zj&**Z}c%v-|<~}*8%U|t5)eRQCMrArg?P_V(Lx!$bHB^<{Ht>$E^)x}}a*yV*cxpqWeVtXAN7emLe8m9}h7Ir0xQg=gd zv`?7vkqxKk60#y$^;P4mCDGw1AX#OVhVa9U7SQW| zF$yVD#t?R}t&Fm(SKnIXbi{h>w@+xr%3{Eqy>D1^Ax}|?a$CNIeOMjcM$E+%5LoLT z^ffmV$~8Nq8ZWErC#UI_sAd`)-V{DB6NNLS#piDosIES5KIx-FN4b>m;f_K>M>!BG zI+yVPx4-Ho`xf;m7s5s+>CoS^kbEIGgYiP}+oV${bwBbJv-PgFCwM!@PhQ@8Ec9hM zT5)=E%r+V-B@3#gGW++(0tGQ9@rqj8gpT;2s3a7SDY#bScWYJj1#XA0&aR<2}2 z+|k8XlR@;RZ6HOS(vDQaOvw&|MW9FV8J;GB;)+muEaY`6<=uYczia^Y6)Jpzjpvpv z+LmBMOvF89fO&UdN9-@E2v}e~xm>o@oW_m68eGei`)-sV?h3r= zRYqEd-SY{v(Dg?)sx%>!-_hgfNh7~yu+fFB#@|72)#QBSZGt&UeD%; zH{G_jW$BI&kAlScyAu0w78iH|tP&ZQN497<)7zhlq0D_%wl3P~(50q$DnH39iHYO~ zUIDXv+g>7uG^sNk7Pa9eK?2!O>TdnV$t@~PZt`74f#q|Vf{!$2eBR2EFLqk4l`8}E z4Nu@&LLfbs$%;>SqH;k@D}c3*;(y+={Bu{&xB}D$4(h`MHIR>ydz}XUNX#L$Z|0_i z(9Ji_W8UZ|t5;vd%KbGV&L6}_kapDOnMI^hfQ$RDMM{raG{B0 zQ8le_#j_lnSxDX^%ki)}q3|m;k1O*6F^h%|hk)_JeiNjvHCG9Aa`o;aWr`^mA#30x zMTrEv{9T@Ih@y05%w%ZI=e{WLt;dcR!l^ZmE^NQ&BVDyO$J^?W(&iiq6Db>t?aH6? zbjH_SW^?dhbMYxA(E@~k7H>0JdxOt@Su@F&qObt=r=$IB z^jPaLSPW3*RpT)O5Yyz~0XCxt3By=lBYcJ#^o~o;qHL&kI1~;H6^f(i{hCc{pe0qzyb@iSlzX4*q`XWI7zo)02QootM9rV zdi%i7Bn$njAFlOAC}zWJ_Th=loSXg6O={;@{tp5VX)=2Y1%-AVN{7&M2#?xJKAxFZ z^q8&HvyK3quZU1vIU=TcaZ`Wu8tzG@kc!E&Hg=)BF$CIx=k^=_LuiFlGK&bpj2fSV zwN3FfSaeB$%OoNPGbk96C=13+-^8S6T#<7}a&Ub(pB`a2)wx^+e5|!M>cUj77_c*Z-BYYycATvtjYZJntlG0HY~W%uuD`laTd%kSmC6FW zEc+3I8J_&h`Fi@N7w;4R69kEah0>KfMg@)qX8FEk(@_p=sWUI*`~VU5w$r>F)8Yqj z4Q2@>htg_6g^Yb_KMRZ4cl-t zuqA}o4QvW}U8#XEWE|5|nG-`cMWnGXS`$YpUOo;lCb7Mw?bO+0+&soVLyMA2CH>*X zPqz%HM6Ey$W)e{D>M(+Ye;AC=?-Y0s^<#o^7~fifB246J7V@M?ZOY-7y!Ny5P&!Nd z|0>qv4*0DaSk3IMye)2CTh7?jP^r?9%G8{_z7tdqBS}9>uxzXx&jB`}awP*)h3$nL zF3VVBTW)c70?=MmM23{(*dOHCkVsc3fO;bxQ5w_wkj2KjaJ4K5oMg@*Ncw_JXnh@S zH;M#EsU&a^0vB4PT8w4h>e7@tyb#P6!WM&|14>J&%uX*`eX)F}w#hQOToTj#4*2-D z9ri6CaY^$7&TFyagER*o=7bnnX!-cq8uxq1wi1xl!VLJC_QxU#mLU>w5EyWcM8xnr zGqayp)dd&$3y-6;K^xQBqv$8b+?1q2tC8F&LC0t-dGrLF(XPl)rqCNRks+KBZ5xvRv0`29^~c3FxY@6Y)eyh$R>W@Rta zBDX%jrsjKcUPo$|$g3s#QHzgxU2uY2aX&*$1cuFd<(-#XR@J6nI8|=FuG?}e@p69D zG}2cnbV4_69*vOC)G^r%AmJnwDS$G|*_H_?2B=5f;2NJ{y$><~?AgWw@REz9E*!h4 z#%n)1>`b^jEt_cTBNxqGO~q$9^hzEw@vGrEC#U?U`vY_}KVN`94}A8k`xY}R$t88 zou=f;bb)7huv7V!F^QTxBYEQyXni&7Q=chdq2Rgx0FP!!w9I|xYjFaBra@OGRl^ZL>5eW5S}9(w-(V!F>yvtQ{z>kp5oVAXE(p?rhD zG5x6&KPKCg!7Z3R>R@*nEStzl$?TP{Ag%s=ei=uWgG|b^+Mtcb=6sZ&SBa_TgkQnw|51*myFaJV1-sy9)7^1bS0%!P07<=pck< z5hit!Ihb*S*VVdheMtYq+nik=m)@uWOJx!!Tivcm9bjKj+~g25c(lmS7RCxX>0bd; zE8vy3KLIfzlcw|`R?-K*NFtHkfV!HsmCNLT0+u%y0Un7eBXLJxGTCE5R)xw|78B6D zF%}j&;u3xui6hq=o0`7Y-?o7&3`m_cWJ{Nx&082@`VEGOQwPLs>W1S)nx&3M13*7o zs0557$VBfgRz>`3~o|-+UtxWhX5mVL?4e@~^huImBIMQSq=J8g498XJ6x z+OQM8S-67vd%YKR5HZ?^MLEGfO!|RY#w4a5eY>v9rajf^5NA2XubTA$Q+{}WZNqh9 zVIoKI!3t-ldIvjOufP$uSR86+3cI6JT|sx)Y~ic}AvO?-qlyhs+R`#$4(a|Axted& z40?(iP8vSBxNMIKvMs+7VN_(Q*C}W&Iw?V~Sq6>+$EYw z3go-~2;;iZDBEh8trPeUItm8Zm^i1<=M7%wEeMksXDn2m%W+1*{CuSn?FbmkY+doPmj+KS%P6Vs#!)IdAq{%ocbp^Kn z_ryWm4k~P{w(Un6DMoCg#mtRZ3dkSv{DkUetlV0?Y&cTVBu11<`9iF5E4C}&s>h|a z$2a>8uz?&m-5$WIwgfrWK9?r%P6{&4-l5KWBh}?h1E~zmGa^5vH5dm#@o>$)>sjmA`~fo-J+lOs!_rFFF6M@B7! z|5O+6{R%u}zH++E<`S~^KHVksT)KoCkVhw5@R~A$S}4t>K4UBqIR=BvjdWpL{{X;4 z1~K$MHgTZbm)VMY42MBAu>V2B{RSETs{9yW9P{qj#&Y$d<3g&#;4Z1*2-=x;@yIkr z(8Cf21zQdcJ5{r90p+b`Q)O}#0aZdyHEBBqBd7~Q`tS7%vZrOTsQ1JJdeQW4W9 zISUBXUP?feX1^ExI0+zV8O6_95mpPbCH0PNJH!CKGta8vQP=PN*PSiSR(^C(h&XT0 zD2e-RGs>m~STK5bRr}J9OKJzTu-n&x3LN80^_uKgLn#P& zt}u+b2$PK>2->I<%EU=imJ@bV!Mp*hI&ooeNh$P|bu=Dm7olTCvgYgo=NDpRDyA$R zmY~&QEVSZLXTvE4vH>Sw_$PGV_My?mhyt7KtcB;>CRI*}*Drih{iM5yKV`y(eR%b-v#nN_L#BQloEv&3fU->=Cm&^YQfxges=!eSCifVX zDvO)Am)l;#AVW-92T=_!_*Q!sFr61rcafHn_|M$rz--f&mgwn|3<4GaY!drMZ z+9c7+dda-`_jvDsBQ*JNhcl&$4aK0ztr~n+3sZ909xiS?I3bup7n3S>NsnVG=L#~| z)zgVGRRG(VQJH-{Gr1v%OMX0I05t`*Pkh2FylPc2ls$8=sQsfPlA>O>ytE_)6FG_i z_=kskZKf&TTD=8MCZaTo9LYRY9zzKfu3b!%ce!$HrUkHlwP8*L^e+7A9EqftrCqGd4#`mi zRi_zsvUgDh#w2rwA$!YAdRWQs`gn;E3RN9VQ9(*2@fXj^X@vV=u}w0ovo+F=NGlp# zyB&bIynug3D58ST@E1oJDd}B@<${Hg=@#S*OhR-!QPA~A*uDOy`AXGxt`0#!--vW~8IdHR+rxxBZs;p3}Sv zkQ5Y*J`8t@K0yX*!2GlM`kJ<5*hwZUd2l32Hm!(PJFzdUX5tu8yhJW$L`!|7ta%Tz z!wv~Vjk0ykL|YH|!dyB*;l@eugwH{NuAwIF3#R z#2M@(-h^Af%%1t5aJ!Z$d-a%rRj?FFMJ!?y5?qsZpH|5aVLS*k@fXiz&5&6Tw90Rh z4^jDjjBsM{Kaw`bbRr%qJA6#P1Ip)RJ_J%e1dd7us(=}%x;4klX%N%gaq1mhyN9k! z&rfX5+(%<+c{-t^g%LkN86y(ZNqzp^PK&@2+2LVpaGuEv@uQ~4N6LUq?!g?JNLqzW z2c&_nU+97B(IdbDHQVuF__#tdo*tU-D3=^(aCdLSa=F)Mnrvi+GIf21qD;6uz(fj( znJq8{>$eIl{)FZ9!?@ohX;x=ICcnt{ zXY-K+JJ%ADtFBZQ=+FT9pfzHov07Q2f#5-BGg5n()x?*GK~1Spk?cR#0C4BD@nSSV zXf1-~ua%V9H9bCFX4`+B=bzFzU*C_*2hdlV z!(7bQMWkd6X@DJE@N6pZ;NTyN&VuN9@W$t(3G)?3n1RsL>Z# zQwnt`xK@`d(V@!BY8CmA+a^U27}O!?#?{BN_>1nQ6@U+Nir^o1NNRCF?MIrfh7WU$Io1i=2U+y7EEK~)WoPI z^;y-E>mrm%t63k`x@_HoK>^LHFUplwp(#;Rz9L}Lpz(toFtExncNh#Nv3{lWrVbc4 zURAe~G$+!ZH@hH8NcV0@W8KkL>P*>zBW_rqgVjr8*1Xr)(r+7GvXduIsGL&5RE5^O z9+V`fnSG-e?2L&t$;Z;P%D4jdjq`NX$_8Z;{^mf=E>Fm304k)TkW1SyJt964-`Rx~ zK=9M{6%BaXsn@csh$yPOeUYhrEe3X^Q_`IdYnNjN0{Bzbaqh_tKEE~)Vq-L$#`=?> z)vOXNGR>|vE*WzxCWpWn%TlmgJFlCz13UijaeUhd`s$0;Poe?}8%}jv{hrBtv~S}z z%nhYi(?5<+Le|w-*^X$J)D2eF9;V5wj!Jny@@Rbk^son|8A=`K+64tlCX7Naw+4=2 z3Ynv>wHpr8(A!IG>g+x)2LN-S#i1dV6CuznRQ+_T_ol9b@kEhpCN3mlTIpD$z(epR zWiVCAeNZ7c5WV~D4EJ80R##vZRl!LNA7|KgeVjob3W0G(_{^k&?#9Z*8D&vaCfqu> zSMZa0I~K+krXa(AtLPnn0qk6B1UnEB4q;ipwO6dIXP6i8rF_*Q2l4jz6rol5% z`&o=Ge&)J2jzQk0q~_@ki;^XBy6>Ar002<_CjR2*d3{(u0KZm0xMyDNKWot*6OUJ$pqmFb{lTm)FuFcdY z1Ba;S!fK(M_K7VC@SSznT+|$(qDdZ|jC{Y}7<5!^l+>DcFvn^t+{PhY8&GPpt$^Ia z-u5#-=UMxB!<}o~qtOlo!{pA=gX!JM5CV#I zntoVaU{g=toTX0rL0PQb!H{oqnDvKwiS1cdtL2Lvpgf8std2Nx$LYw zPgRo4`(7u9+84l{?4)CyRl+ZNcNs&I@eqC8R^W#nKCt^ht730RQ)&?ejGQgYf>k=H zQbYQW`>|}{G4a`}r=ANqz+R`&J=KRPM1Z>NeL!>4qtZioM5l#bG=U{T`bWmguTbBe zt^Jp?pLC`#i1{SkdOkX!(4*-i|BXGsz`Kw%oBlhr_hd)|VN4M<-6YYeu=^;d@9Fsx zhX-?h`NF|-z(+IZdF`#A!^u^p>-bi_Y>fyt9{{Ys^;f^_5DT9RkkiNM7)V?b3E?Yt zC>rzW6QX8eGdvnlXOBtdxGk!F=O9uRRR9>*rZ!BSTtC;kGgtAO<5M5bZ89Y$e-5!# zWcv-ivik)z)nYc>X=HlYE0)@Kftg`f{?#gY2N`<`~hd zm@_T0boO)mB9h`TDB|$pGKwwtSh>$ab2Y0;7I5w1dxNY2=ZT3$1y8%zfZFn`7G<~I z62m@eRJ&V^R1)9EH?*pB2G#2QsZ~UKfi8BOC}(K@_eF0%0uN2V*!=pI^d3g z2ckFE0En}^HH<~hJ;9VYU~p%LNzE~_g%dtLMu(f-3 zdOQ;Gs25nNj|aIK2}WA(*(GMMMe6eo{ZE#j({!!2Bw_U#qQ+ zX1{<6eQbNBxKwbzTzC|Nqida?3cr}9+`p4B%0VtJ1K`M{7>5?-vmNS4ZgIx9B6hRg zXH+6^h=oCpr^=Io<6#3VD5{w?N@fD)_E$;f2ducj@i0d_xGo6w2tVJlPkNMO2Eaj> zB+9cVHZq5!E-Xj*O8tA~haLX=m~v>uAyCAdIN_7)=K8_aV{L808++R^1|76K-$u^0 znvz8=VXBvI&6t(1w+eMC4-f~gx(1*?_^%Tft1lX*Wb*;a@4gt4WJFufhtID5+)6;E+`>uMF(dB3_4g8|QKuUJVY0@v1!O=IRV zLKFLiUl1peHSRtvwdR<#?Qqbcs(oDJ;n`%^7_iG7;!8S&tbV7dwwlX$uJfIYAQ2k? zJ3j|QGJ^QXfkzVQFp`hls;zlR`y4JEuNnhjr`%M^sjP5OW1IOXeMLv;@*=Q);HcWE zej4>PZNj>mtmi4uqvBiMHzI&z6OZ@hbHcyv=sOp1y>=bHzV~R;HNSN=ny!{is!Q|R zz>O~gs+h8@{itx^s6H;)T7__G2*Dxa^v$o}*~4DIglu%dpd`nvaL;_%A_v>j3k|TW zlSlCdmXS-qnTLX|k<8HX+-Is(3L-zo>*x%7v?SxK35^9&&yh&~5V4JOF&NLdE($0K zKf>~FU>>$557iMa8$ChIx{ zj>UzxisoC!6{e2PwT~<7&C}N2+xOWQgE2|j%za<}^B9||o^Xh{0ecOJ}l)$_7KyHI8;cdm)pfzby zq$T2)xk=&3RhDYV?!<@3717rJ8eX;(uPP-> zl#?^qQ@f6-+tSZOSlu7R{OPuyid0r zH+xOJZqnxC@Ij|bd5H8l%@1*W^O0}(jY*ZEqP|t#teR-nXhTqT&pY7)80Q&NW(9Pi z-O03|)wLNUSK5Z-aIpJ8=O^%}t|$LdHd5|y&dG|tS$Wuy?Cocm$1zCYM;#uI*T?wY z3=g^^va0`%F<+1P0F-@nP?6<#=chO00IG1fpM_2&w);Fb6T1JBLo3RgmzRcjeGpZG z89zlESU7u!`Zm1Ph$NCltp}O3o4Qc1`3&|@lo|1@qO0LP4 z5NBIVv`!!~XxYZJJ(|+l;q3g7bp+P>hf|!8l2rM2C$?#j9JG^^`a{a+rTDbTdQ#1N z%D%|L1yh%D(TbBbk7v6Za4ECx$1_PngVZ z0>l&D16@3sw7wudEv6xKQn}BbHs8AD`=Az=HEFIJ&=*-rXB&fYx4`NiE@f`y(h2t; zS-OXW&pvqT_%tbVbdq=}r@T`a@1M;`8MQt|sH=~e97a@M7COZUc9lt$qf7CZp$jok z(2-wn*vWezCzzb>In3B*Z(717s`(Z6*%GwqbkN{66Of>FSGMcbLtJOk-pV9zB)6Hg znY10B?c0igeK=?fYgNe|bhk}?Iq9yJV$oJz)GtS27L&Lq`s@!@B}Zj9H(weHqHZW; zp|(WbuTKUyIKsX)NPZ@#GHMgZEoWcIY0t2^>hg0%vf(vpPM*sL5_z+|oqj&8h$|1o zmv7kr-r~9@Z3}77+E-t88?R=reVcl=pd|6rxv8o8j;o-%mqy*K5JPSFZsjENO>YiovR$`ZGa7OeQqf_uMn zC<~^n5Od9KqO?1e^I1npzQ8|0q*=HZ=t%91*eQr3U`*O>3r}XbzQ$~MY%coab)q+v zYtQ|qmNDE}J4XJzAsYf~opSybM=%CezAYgWLSr)78EZ}|gKnm!LIw~ITd_zQ z{S#?8zg?vNpn>>i&_8pdpr2tviV8zdTCeJM{tS(-8H;=P6Xdo0GUzTZ;xg&0G|d3l z4phiy3Qqjv)mT7H#fKo{&Dj}jVuwOgUM!qz*%jY4sGD=UctwudmMbyTuW_G^bSAb^ zgPtJ+Tt^rkMblzl)#H2Y1v{ z?UI}x;G&Ty`0sQOdW5l$t*w}P<=J_B+wNgWh}DgLLUpzS^>n;tkO@dY zd?NCQ;QQtia>*`|u(5^cuivz4G2oamtP?2?Y87wb+OJsuOwQxXRbK$kuyN+64Cmp` zEXfU9Kws~F%@2cnRt@~r{Jq`_eL!JS9nC16F8K`OnhL+nDFq|ZcFMHahM80P_P-252)ro+7@G5ltE?9*vY;Bq#j zsEyV3ha#wd|4X;3pTGKOH2QUcfVDlYM9aBPwI1dkeunbfq&qt&aN0br_;{SiZ(>1Q zn4zivt%rpDml=}IA0*%4oHk-oYGaPOR)#Eb?>z^jHVwumeUCI6j&hid7aG6%P(Cxd z>I~RBLLab_rGsgjFr^71dy8I)bXse>zn^g1 zMe_##%f~e7c9v`rE&MT5X7sxAz%n~DbG92k+oy6m^3_48Js$H+xrebllECE7RtawW zOhV0hX_dDd>DX&!Z`vZ1*_U?LrIU;w5mKC9Af%;F(AUD8VHYrEN%FST%8uQL&giIT z{;H$j;FjMc3cjc0hZ5qO=1oWlR7}?(qa%X0v>hEGL;o)7=u0a5PmW;(VwRm7*{_O- zUlxxht_lwHZ2dOsK{lZ)(e*1mWCtj${o@57hktQOY zG)b_5C`}?Dgg$7100{(02MI~;qIT{5efu2U=l;2W?sJ~UA9?V{T64~^M)QvM9n-D$ z>uCl&Ujd2)yCxTD>SQ;2u*QYGuAtc~lI>NjhjY4&y(A$TeF3F2dC8ZiQrY-zsL2|- zJ$;~pXFFr{UVIoEIJ;nBrc*aN7K4^;c{T6{jl#bnnWp;ieDj$DQ9OJ0eD*Z!Lsy?q zEL*?2h8RZnT#0r}q+sTPoP(5NB`O7uz_}-o)!#Gd>>Jv6=m$}tWEH6Y`95`&E&VMi zW@{56742yW5r6u{{%Zo72&+k@Eo{>P?`%2$4g&;tHzEbmo8GWkHf%TNcm2Vs?Vjm) zADgFT3^*q`t}wl(JM^$9I*-|y*wu&CbjzN>R4t0^XUBTJf@j&y8m#f!EvaH&;LF<= zc_!lbL~Z36&$Y7g`mR)Q42I{;wS8b_O=i2AZLvy#`4?{rLO&G3j zt)5j09!+7L#GVu|uz;5_X}jLe&9}WP9UFSUzn(#|cYi&jsmgZvw$UzPIGgbZO|7=M z^-Sls_g)6?C9bmOBYZ#VVLjhIq%20dL(yq`8Fd1iLUY45=y*$pTthu;YMuApcTXog zDxxa+uCyw4uOvKO{hI%l@vyGRIS4X7Ra$A*AM7Vz_F5xzP1MPJ{Hw;?$S%0Sq(?q& zyd0iL%^X8terj^$u|CO=N6l{l9XRJgKf6riSBDtk3u+Q&F95#{!CqlSpW*i*FLr`? zp2U_zSfK!G>o?{64r-;Np5Ml)oUpg`;%y`gRa9@B=0z0q&VlVq6tt{ND#25cBO3(s zKVNB6k#&4p_CZ#448&YYxwoVRR?eO9dcXANjjS7jk99j4_h_$E601FhD%DXo!8X{N zI(-Rd*0*DP8n%lqNa-0nub;pTzn^!2P-w0^&jR{_?U7>xQ8!@>i$J_l;QgRF%C3)8 z4w@3{Lm~9zm+X0HPFdYZWfDl3LMzYJ=X&n(m()>0ZZj8snyhXI4DG)^KO$huN|I?7 zt3iKl;6Tetde!wC@0XVRY2`Fe_jRCzPeN~X)bhv^@ZPU_XcwiO5R0~7bo`a_oueO+ z%-_-wF9)sn46CC`bE3FJ?aedDgP_a-ZuU~WqhHRPF8?9^dR#SCKD~QFaBE!{in`-e&`>UZR$$)(u#b#(|gH*b-fkF_$mG_mBu2qM5}Hw_%=d|>`YUl>^A>7^IN1G zO{Qt^%w>vNgja?%+}0y^G#31f2d^6-9`qB(cGC8xGex#wlO-#bSuTtwC=(*X`PNXS zx#V67orIJIKGpqzz4a$DtnUz3SCbLsqGys?AnEIk9jm^&UA>|) zBl(YldP~2C*Z@&Mb7ml$0=g8=RRP=+cUxdo4RV$|@)REic!BkS4}vt3B+EYgzXslw zwto;l>}*Mt^@@B4%ktLLi=xM@YXqBF+P`Ko$<+@|y$f|7^mu>SMuPpu5lP4$$cBX_OD{&(XOl8|MFg2yVNz2FAd#-ea%2h z2DIK;^q&o_Htz(tq&8nHOcxIu!B$mZD zZ=yl8huZe{#Mz;cfQ?s^onMk^BLPF*_(a*yBFoecXL!wgt49Y-zMFJ4Qpn8@h@0R1 z1OW0G!niZ0RCQ1z!qURe{8)TlrA8=JH|1l4)J~T?))f;!9cadsLoaNjAW^=tRUYKV zJAA=}l^#Q&!n|d{rY>jGt6v?)R*-luYnFTOa^>-!-Vn@j(@?7%pePFrPN8kWFnyMi zsh)%~Pj^jxT|Qq*s8o{ES*pL;R=`MWh4sFBf!%`3Beg~o$ltG5ST6z3l=G?bOYYg6 zuOuLIjW{hBU+=UjHT<-z`4gO}mwd?S6U^!?L)-kL3}%W+KBCUt4SB&j+y|7v5G!O^wK-9;`9;leGci;7~U zSyjDnFk5xi(Rt0i-{uuXvztSM8!4Z6hT4C#vPMX|xokTip&=Y`@Jl`0L&y%b7R6jz zSI#m2y>^Sd3V@(FzG647UeG|jzDXus8rGH;Y@lA@MCYZM9oH=cFU6&aT5*-zXm^le z@pP*%h&osIaz+#XrBXh=^fq-pLFx$@iSS28VgXVTlyC$O|3Dgi1q?hUpN@RG^lAp+F1oP)t|L`F(}$cU?5F|GRGC&#=g)Fq!F*P9oU=7V>~3myh&6Gl9_ zvlvAq%9rPp^YuXq))33VTSUj{-n^@nLw@RyMhzJU7wr(egI>u+L!IGUutjggx)!<& z$tBzU1S|~x;Q>9nh~*4Hr1&|uL`U;%9r)Fg52xYqjzhU$ZReC)j?vSwfdD?VMf8Bn z9%>;p{FMJ2gU{D6^cIM<;S+edmi+nF*SdA<^zh^KZG@38JuR#)-;W>wh3T@cd1uBa z`*&z%5Jm6O?Ak{TmkcIizr~cr9t|&!ePn`wG5C${(4+J&L77}rB1m}p=|Z;SJIrz+ zmfH8+&M*ATY3^ZE_bp=Cae5advQa3)AMiUO>k`yKgnJR^PTwFtCQaV+tD#qx9nZNk zsQLNVmJ=<&f48)vB+&}iQND_Xgf%Vs7yit%D8&r1RJ->lZ)E=&_kPXlkN?X!#) zSkktMzSij0yuOKOiZ(0wV_J}=jF68v1x_BlOVJ6_-Y`-GhR%h+I#&-I%}Gg#dW{D> z(qXBUbTP|pL9@AriF_D|+KQ#Gi$K*lQWfwo>dH#;)`a<&ny1?|X6!uOQ5~EQS;`|v zmuI-#AbhhU>?E^bD6qR^E{B*awiY*t$2(hNl|=m581`VKjFzsC19O%>MhAJ8OO}Rm zWXGJP)XjC?@LA&#JdkrCql@P2fBV6XxGRAY&>ywI)>@BF*4mB$y8%ENKG|ahq0kfJ z{*1(U9c8)wtCmFb-WKmMb=|F-Tew}(GZkL~LyK$nTV{|%(Z_)U$JM%xWGKp=SH2il zT*)`|R~A&f#{fu_(%TzLo{&|x9QnR9dmJ;yFY>ZCMWG*Pci?3&nYWOA?k$mPmULF~ z^YRg>H&FFdL*2p|FB$wnXbvzbZ+B(}S}N$Pm2Fxk*mqGIqson%YhH)8Qa$P7Ytroj zEYj}S?e5w?bl#{mkly4yWGYH%u&h(;$I1QKmP>oS&mW>hk2|Qi=mcNHnr`86E{J;O zMP62xufqu(tXqNxj6-#;=%_ri&^<9U6;d3*R+k58VqV`4by3i|;w|XkX*^uYFR>W62~7Z!!!^AoQ>Z3(*py328M5 zs>jLLc8lOB$_jF)^}`n$WM@)n)W3kW6?kb8Zw44|{qKK+qb#FUpojMUJHtkb3k=5v zzl!)QrlPJt2u*Tz3T^cW1gp0qkE_1AjamK`45@a}#^alb6~?1BO@gbc2TFRDq{$dW9^F5YbqEzaMa8Bu+Q= z2njfY8a!#;q{PSjSiEth(Xz1Z5PD>`XJx~zeo*fcy+GFkWLaZ_2fo%mKk!!AGdug% z{=ifge$~vh13f((?=pw|mU0;qv~2AMF=U=fu!E@!bocars;3U&*zX6lLCj&9Mg}S` z#{I_rkMj@bnpLg@g)kSb7X4U)DiUAm95{FG+L0V5jq5lrGo^FbR^Ullfvb&1FTOWQ4%Ni2DOnrVI zUF5R#()`^~<;8udGuM+O3$}9lM=?Azl({ZozSToGbWjqlA! zt}bc-8H}|>;x>Z{>6+ABsK(&(>7d;6nqv3E*l3;4S%>`~_v{z9C=P9u&&B&|3!XFh zJrOgrm<-BOoW0kIbKRAf_wNYU0l5N5Csg^*U2JpCgqac6Q1Q=C#=TGIr$}AyJl~_% zD|76_j~-v91S&7Q3K8|9B?O@6A4zMO2gno5t{4hoZ_7GHlEX21f-+xCe$1-nYs0;Y zb`2c!ey+EuLQy_uIBle)jCm_}U2t>{H-2QMbAB2Q%i+NQSm9`Vq$e1Jnjl~WYT;w6V}>Ru zPMb(V4 zd$>#c7^RJFPscu>yY5zn=lHDl0_%g`5F}?xBBs>%Wsz0$^<4 z32e>($mFMnzyC%4o&Rux|HI=?##(-NId}i@z^;bB;EVrOL;d~0k&f@y5&{2sATR8@ z8QA83Jn(;){OF&y`M;<9Uoy7;zZ!OV5@QJ?Wn$1GyiA10Nf}@%pl&azHNnV2MN@W^Tk@SMuiKqM}q0S+rp%7@7K!IC!8a$ERb zEY}OjuWCM;b&}t9VsFLRw{vhazlX*eQt|kTY6$6DX6i1K{dd3qR=}B&j$=A?>rw|+ z-q+quCZPj*JB?GZza@8K0PTuw_XlCPOb`y1NH`doAkw*PmAof3;fV9Z{KJ4o{65U4 ztCr-0%{O9UVEV1lZUoSU60r{vvY9km^a+w5ihc4?%KINUk2&{BZ2CKO<%y6obm+Ui zA;&~YEvn?bkgqvXm+yhrQoXh6pEHO>>^$mb6>9a2>(aI{gbvP% zZd{#29_deaM~*MS@5tf5K)nkgk+xBf@!}ajIBB9!c1^PPWbB6Tuf7yW7Yf7EaA9FDC z=;+gu49}PA-CjxX6C=^N)S%N{?rtt>=ptz#Wa{=%fB2rojL&qsR8WhPE@Y6Fc~sdl z6)XYn3zA;Mh`Mp;^D((W9_h4Qs&BR^Ufv#~8@!0MZmZKr;Jix^i6BoQ&_+D&4GeQ; z!P@7!6f}xlabpwL8eS-|^FsQ40zKwg@Sx(N>BxorzvBKB`!Bcue>Fhm<9Ta#Jk-2C zIr_eA^h5eIKd#5&c#GfcwhRYI(nnFRr6hM)uv>@pQOS%I- zW62E*Lp}!nO^R|hicZ_Rgq6`4Yzo@Px?H>%H-00FOBsj~&%0NB)wU%OTQ-XL#$@pp z81DW;%$18KsWjsI5Jp4IQa|ugkoN;tQB!f^hu?qCH2HWohtS{(|M{DK4=dk)Et)hv z{Ibum#ZBHwu~%nikk`p>8=Bw0D=)<^kTSC=;QK(6;!lox?1p4wO*F}lRk`m z=&%fEyZR3C%jpe`6W1gk?Kkpr5Rx_;NX7@ILBwR6sYjtJMTsArSpQrjx{aaG{gI(2l0tEN0BVa+3@o zEx^YrX6?cIFAQ{Ig$J%erL%%O-z1UFo|DYKlJAvr?B9(Y&Ih)gswi4+?IY~Cn_uX{ zkEp%2=k$@Fr>xTxi`kL~s5W0#uRMBF+q^AUTvykg4$?brL+Qn;qw2Ou870wm<`7~{ z?QJBrJg&%eM-vISJBdEf6)00dov~rgkGyS2ydfHTAYs^jI?za@YIDQW{rk13&a9cx z=M(kO!tso=pK^0@$ZBHjZiI6>b$jfTeRh%Br%OShp{CUa) zPa+C0ehj~(NE^wBTV;AHvpQ%Yb|mSvR=)B~kW6W?f&_F!@ZWYTF4mGHGtWuO<0hna zY{FBuLzf4gHVa{0g;96p<}m~hdJkxl+|CiMl7?LF}YFst7h`P zg3$)c!u}l&=mj1AVervc6Zy#-_J;bE)P?BcDrZ-L#Br%N1_r$c50P~9fDo4Xq!l56 z;bFy;UVIg+N3JQ361%F`S=J?W4`uXN7YdVK`TfV2_H_RV^qQ`?C0Gx-=WNBM^2tjd zM!eRZX`lO0TE(5@tdgAC&Xk7k6QrtouJKeGaE6R=>V{M7D#sz)$%B$ZE6>2=sbJVSj4Ea2lKXeI8eK~t6PWjFy;0)3ORF9z0xE? zwtav5jaGyDT?{@c|El^_1YEO)Q9cQuU-a5o{#40ke7f$Rl8^QZ&QG!#~@n8S!x z6s~0{s`5T-K?@o)LAoh>8G3;)ZX=45S;m9prBb9^Q_;8w;+?ItMQulfV@pi2kTYU7 z5XkiG89dn$wfn^h?U>SuZGwKuz~Gx6d{qV=+lQ~30*=jH2VaGQZwL7Zr}@sYKYc$N zxS$Z9*rIml8_XGZP8nP=;+-}w;vuB0n@QA_)R`c5# zQ^cOW4Ao76hjxs*C44Yn%yw{buaUJnbedl`?(CgSvfscnTWp=4DGDB3uqPZ)5p3ka z24WJ?Gog7Ekg0Cy8jzUVW2Eq)ZerX2xFW^p)=mh)PM;f_k~1__PR<4H(R#gG@neMZ z5wKq+Zy+T6PI3C)9c4>>56Oqc$Icofvd0keD8Sl?fyQwYc_2^ABQ=_;PluI1YG2Jg z^v>Me32qdfF$WPv2~5%&4pQPTwitZ{`(-rTp{V-L{`sa_o2p`Lx1D*bAn}VUq2o&k zJ;a=Lui&dz^>c`){oC^`9>R|F02}2p3Y>R{CgP{Et#mbky)$x?9}6D#}Jy(%5Wy~k?N(E-qOY-O{((oamiPBsZE=qY37aG zn(-xlT`3+cY{Kiz_c3s0eo^Ek!U(mA0DmNllDty<^Y2#dt zG#WH7rj!P+y1w>``|RP2Zzvo>mXlU74>amRpHkJgeM6lz-H&*~nAZpX>&WhwmJsA@ zYz8hEM-C;@1^9KQL}iV*f5L2$9WV?@lZd>& z@3uVMl|idh$IU-FXawAuPnS=?ZhHHBoW-Q;tMIEx^H;B{J!B6@wziyFq7AbeSv-j|za!Feyn!Bzp1I_;wXHO-!4om-QG1a~cpA-iLbcbS zS09_@8#QYL($x>+Q05bC+EaM#=yDi?7S1G|;pn0^Xm;Qwf7JbcfxrO^(cWcoVP z+bO!ay7}jpj)7rWa8T|TCBoVNHS{--=LWg0Ew+-jsOUx@J!yL=LVS}zlXtyv9H-oy z!dJ(Sum_DT$v0=UGUr^Rez7jSy*PNKR;bkDY8PfH}AbJCK3O`FO3Sd{*;yLiSp&`H80$ zcpz6&06vVnK12!3Cg*GR2HJ(NvRH9Rnm1#gQwL_D<}U=>1T5J1olvmvKG4TsmH=n} zKv_LZTPvn8a}sy!_o7R!Dj@V_WTJ8kk7$~rcGh$4tzAu)x{!6N1qYNx9Sw^eq;P`$ zAk?#_huX38aYUhgQ0Z%oxl$C1g~_;EvCyh|=^aM-*NWLv^IJ8^h>_d+e9oV*&dV3E zoo7d@cR$W(ji{B6mjJ`PYkTuRqYsG!>it^v#r1Q&?3r3D z>(E*A7wH1spwfu2qG906a}(_bqUpdk&{*Fr?O8NuLQt>m$Z`K%3+zd-EHaK6o65+t zFI+?dVShguyfg4UX!H=SdYBq~cE+L$?~@}Qs;l*&xpQcyD=oPH2591es=c}F-y@b>jrRad#m*B9o_XBVdzXrB{w$u{xxVH51^{LK1J1ly$8qk!6-g%og z#CBK5pI&z2tsIA0w0@J?+4ATTFtig3V?D+&Uk|7EKX+sUo06L<$j?|z{*#d-%|vYI z2;7t6-(o?nBqSz+can!R<>R%roC)W{#pVNZnkpdT2~iqMT6sNXaa!`DY-$Zid3QT# zwDRsJ>vw9}k1xF@WuQ)&oqS_iH5bBz5v8V2>_iFlr7XIQRz!)~Xx0(ZP*$jY?fCk5 z@4Yi)c7d3b_$ativM|3vHedNZy7ulkWTX5TJh3a?j~WO=S z8IJ?kfS{SzT{>()I@KE@(Zk!^+7>BR_oeyNaoY|LueI*f$dIGgcpC=WttSzFxX54~ z$jkLFwa1rR;fuhx1eZOVEa7z$0<^VT3ZuhgwTIJ-(BoR@Khej_eUXD7SQ;4KIeB88 z-?;T*%tY={@EGN>?o#q`@e~qpJG>fj!B4oipi!6xC~n{Pqgszbyz`Uav0L7%%dgjd z{06@D8KL>ZrP)Bxe>#r zZv0%cK4GIY2O?_=oMIs@I3Fx;2gT%?_24~?-d#}+F7kjRfntnV&-$9kyeRp5Cz9U{g1ix!#$;_OTyyKC9nW0ZvY%o)B9rwa{b~kX{>azVRY^ zE6Zb@oZ6|A561lFx7C1k3&N>dpxn2)cDL+Ai2=qz$<(>-f?MxAdD=jjb#(lokx3pK zxbZ7LW?{Hz=F^fMr{`h9HJ_yJ{bGKd2EhOAV_J#wnZ1JhsBbOh0QE~!XZp3!CH#l~XaJ=^g^^{9nebim*qXYeeaFdcO4i=A z1F_xK^+CdX=iA?h=3nSOpYVO8=9l@FWJ}r-(eJ7p+z!8NQ>oUMiMM+<{AZtiC`Yf) zQ9gc?_=N1hCtI4$vH0W?v@fk6;O!s+aKc1s{e~&h`fEiIWW1x%O482CCrj8e>WS?U zY)v<$PWk(*84{6Ca=#*IDUUdT7|0V)rdf`ZqTIoVU(dWT0mp>+g)jKXzf}f}tQAlg zwz#${`uH|c%(8KCJX_y7xyB9FhS|Lu1Ofcz4+?Zpv^rmI3(4jyYjS1o5cd`oM#pfW~VT}P9pP_Z*>!Ytii9Fvc0#6guo zT`9?SlyLLHDnmsl(r~>Pa@VYeUlO134zouTH~W5L3(J;7sS(WgQu%cYo2#pa%WX4M zHBfPik&Pn*N$7I%=wE*<5TH(Y?F7b=fKL8$#LY;fvg&>yQ2|qFy=ZAf-iw&%pxtDy z$HdtC6Kq?!TnjCxZ9dhYxK2_Ucu~ID7Lyp+bkW?Q=6Dh%-`5=qngiqo#PlD5H1hF@ zTht0K1)#!}9(=Mg;Ci|w?d-i9lvT>CCJ^_f&Cxp(Ht}HcC(U^ko4TAA{r4=>@IiG& zL26xIvHPjqSkVid_shid2lhv^SIndHiRn8Gpvh=(oY~{org&(_pGBjdXwEk)Ke9mm zY?}zciGw`Io$~Qk%-u>^56+Cpl5vg+vCHkzBzJH5J5$V|BCwkIg-2=+fe$jIF zbYR*03n(@}CenppVON`mb7tPETYgBvSA{zR7cZcrNECri8RTunv79%(yf>%eqj)2K zVlHDbk8$XTLkd$-zF1MtGb|rtGgMoo0@zj^qs+W!?5SG)w!Ob<^$M!!j_*jQ^a80mRHVaxd zOR>(abxWR7%=1{o@fI{3i*rk`@trb3cjfn|_qXV;l0CC03Cz;bnUK(IM#L;+B&nKT z7>ofl zk-;g<7)yYKcluKHx1lz=_UxdmvhIR-W4Why2dICGwly-!HxCFHrl7?i7CP%dYRQwJ zU*0Jzi!Zy{L#P=xB2^{5FQ1~NSBWv-YUGyu%gWYv{nAI_ZTksI3%a0*Ha`pIl$6Eh=PH$aC8uXvoU4_(wciaPLoV#t` zQPE*n>#9%D3$?xTIiU;oT02g^_1Nuk;8=j-38mEWus^Bg&^w@*Gw(2)P?CP;BYR=d z#C#9)4mw4W`IS&-E1}8#u}RX&YK>Jhot^0jV$z4DU!V;LbdatUv3H=I6=}QwWYYt< z@h7hK!Ms6do8X3EZ)`MrSgrIjd%UqJCTh=>n6~stKRh<{x-B4JuBB3SL>W3{;{)a97;HdZi>B5gb1Y0o)^ zPi?L7jGD4*jhzT|1l(>$6-gmxNN(-|Las2v{UsLYK!x3r{VezmB|6=R*G$zkZ=|E_ z`&^xG8H-pe3JuOtHD?6K$D2$DSjxq|Hd#g|njtyMt(>cz(RF`K3Wih99$ zm`!`ejx3KcH_cVmYX*Wg&ED0sR<4QJyIB(y2f%^&pzl&AvQ~s|VrM1)J<+M7?@Bc( zfsgV|)~)w6RnBb4gL}YMKTr2_^cY={L#}nixTt+NX}Z(@H{!7`rHOo3XSsD~1HBKi zi^w~_*1x782XnFG6i-Ko1{>RjR=nJ=sDp|h55)Y`%we}d8L08TPZjBpe@!t$8$7PL zsJubI*du`70V&HmlL$V02RUSDk?+;O8Y&RH+1(pk*y&uD4wYI3rsZhSooiNu)W3Bg z?FLhYb~96G`)^Bd0m~KVnp{C_;qmWOd|KMY?DQj-HaZ&T;LCmMQ;L+Tab*xq{i+#H zxu#@-kGq=G`LhXmR%LdkIL40P3Yt@bAO9bcn0=t%6HkZzwkLRZE_q=oyBWI4|E1#U z-gg-7LcJ>vzsCz_Ca(m}Y8)R__i<{1UQ93mpn0?4$y+2bsx^W@3^8Z1m=gyi%je#? zJ(f*YY8nR(se&%6z~^SOnxn#2c_Ek3GmTp=DH{WplI_7YI;n4mk!&wnz482X&ESx;$fDP!9_WC}kxhN0DCd$+bN9QY_ zP_JSVwh*w&zqSi+(ba*=1xE@r_fIjqppwP1Q%;jVPg)A_x1&$5CyDk0DuSSJv7gcd zG8E{A>Gow5eABXA(XWF0$bd#}10CIEsEJWltB~1JZ%~jQIJSp4pmgEQ@_Xc1q%)ky zy^+HAX3~a&QUmw-JLqs|{EDog=yGa~EijLow#(tc3?Jqi0|CDCD@fs0;l>@6z&MeD z!@BJ??ShU6g3iy+0eIHc`3YfY)dOa?36OBwECY$m(*I~7fczq1hccrSu#t|(I(Ywg z_+zTx_mqj6XQ%YqO4mO>3(cE^YIUNIuG8Lf5_5C@@Yg#;XL{U>N@i(~>*E(j{yS3s z(0}5~1%w&CAe%TLJ0KzF)wKSOCZbrkt#X573o`%4>zjU=%^n3VO0)A9NO8?3oK?+q zW!Y}tdtetb7((NQ@mb7O^-$Y^MW5H>%h{@8pW9oXu?wSPl-!FN0j|u2hKE9zb4%&vEw3HCbpMM7sMv{2wCmO3%mn z$fNbe?Wa6UcBmPMm2Kd90cAjoz8%g#lzw!R+iW5kc15h&8P?+ZZVQ3roF?&G2ci(Y zVv}du=d@VUuhv>99CJLDGg-O*tJ%_WimG0={kwmG%KK^4o7XgtKIpwS0g>m#(Z%wAZB+K~k4IYbJT`0F`mh+HEt4l{g^ z%3q4AnKEX&Q_jH{k+Zad9ZgydK%XpZGxIllO8}hi#!=UTqONc}d!B#+|5#!KM3o=s z_Xk!)C9fdP{&zKlJzi=m7Dn6cs7!pKmNJUaVbmpFwEpGI#Y#J6Q0K)Fd}RS$+4z+U zfj+!O`NEKQjS)vVwRjWBQ0XImxm_G_XH=1~d*;`UhO<$ZIGf=nikb5xat;INgN@88 zqbJ~(3Ky_$1~V}x>F<6?{+hUIl8lzqLqu zAIs(@e>3m?A;#5S9|2NT-g`6islsOG9zZ3~!__HjPjhup=}Yl zJ2fgcEjnM>dCQ@u+3fmiAS{iZOT0b;-`b?O9ISfd4hSeps+O*(9SAHn4UX7qeOGx= z2jYE~Um;t12@(BvBVCx>w29h@?Vl|;K3JcpoS@AgdO)lL@(4@>;pB@DWuO^No_S=8 zBL?K5D0!iW8B1xB*2Nk0jO4Nk<*aox9pIH;sc|zVTLlyI&Ds|!;+qoy*FSC-s87Og zpMFgD^0fdRhEKBl;qVo|k4F6frYS911L_BqU9BpXLoe6k%Px?@imiWnX!B>@o)>}_ zMK|8m;1!W-DVaO|SkAxXC6Bf}M3RrTccH&7s~kP-GqBa*3076Uow|vtU4hZ`&T^Z* z(GsWy;~pOJ(!#SX5ulnE(#E0>A7 zBGd|U8H478w$I>}ma`v^rLUw^ha%ZgpF^C@%9$+W_F+8Gvm!t=aaG(-&?OHCs2V`EsnTKxh`}Q7f7>! zL}u8i9GD;h_H)l*ENpJBC+!vUf0*RzfD}=t?>BiXYzB<_+^)o?Nca3c`fZ`nlbs10 zQ0|S2l3pFAg(JzlXWKwK+T=G7_d=d_AM*I_zM3u4g8NW5MU#**go?J03dqx4-=}U8S$=0`9ihIyL(i zVg2&VPqHAfm9R@-j?>>&`~v_T^&3FqPfBc!5Z*8Izl(JX58U(1kwKofW^E!G$P4az ztP7aaXv?!$-paNNyg72qq#^@mxS#zwFp~XdpV+O4nGsfb-HwTLy4JZr2zp zULo11L>YiGVNQ3?>Y%di*{0VLo=;(UZ(V;z&D4K+ORUbOnNsSkI1848c1S0WN7+B9 z$$|b+0`bjO*O=hPPZc(WnD4k-ne=63HzPxMAALGAaL>&nvix}%##@ku@?9%8!Gc0I z%U>R@>!xphk9mlu_%cU4L(EeL-5*MqletFl9xlrU?@gdoLTV#)@}2Cvo`>LsH8s^E*s~N&9 zIR7jbpgVkw!Aj1MqQHH4k|S<+W9IcmbG&*aDh6A#TH!o5EYVzJx%AMfu8luB$ZF8& z2PyQC)bDH%fHWKC&Ci|`#U*cTIrna-w)|<;2XiAT3-CAL#UA_NMvLUYs;{kqmdw^f zgvdjBrGjEKl((>P<@i|DlKM0DGOZ^PF4qH{QHcNH^4EZl$W+@;&h4l+bnApeMlbEf=>*Xe_QW1?LH5j$L$9$0}o`|(sCP#*dOFk|=ij;r*+#BK4 za3RKb_H0($)FVpM_m8;^G|K6On)A##pc7637j;~eK8|Aj{cBkl(5LKiKm%Tg@DzvC^Q`##-&NHAjVw-Se}651w}1ba1piOk z=09Wy|I7RT*DRy|l{)-ah5X-N(*Jkrj)UX$^<75a4;L#B*J-w3{2!kD`QE>N8wqZ~ z3U*D^%6b>2xHQb-?)cq=gzrY@X3%KV6j(I5LR8;sr0!u{63TYZ`-8zpE^K(u)Cib{9&_-wI#pGSUtx zzCe7xJKipumTT&FbWUBCY;@tXqd^Q$AsnU z6YJemXn-(EpR~2=9(u~uciIm!A5EGtzx$cqxbCr>!y#f+Ms0kouu$1dM4@Uf@4e?ZYE(NoEIXQyJrmDDWRW7l)PDPx0 zli0cl)IBBr*hE1}(${{cKm+9~F9JJ--tn8>fB-&%iRyHAnz z@4F8i8BI95jjFpbbk@O_!*H#jO6sY572>ZUDZuD#aK!#MaGeK!Gva75x&yL}WlK4n zPwk*8yNi;qv*=DswVhr?8(ybW^Nq_mr%;O2VTb&;n&=NY=I3tI=iizmGVp>9)<#DTQ}OA(geBjVlG{^6;>>FPV+G{taN(? z#w$Eu6K92Bnzii4n4o|u1wTQx@k@wvN!;GMf#xI=$>7Tj&Nn{KkhvW#yHcbL&5%w3 z8o_A{b#~8RN;OJ~Y@0q1yzfo^JtymcTqGQ5^&O_0b%IN3Efm&3aUX52kq(@Uoj?=9 ztzMB8BMsP42>*2sYfN1-^E)F=2)iRoiQVeZLR0D9$A<`*FM!=U{4Hh_=2Oe7LrcI) z*X`SxisgLG99s$%z;(v??u#L&vYGDcO*J`34vx_;NCwIq^QGbyVk#n!?(Os5gVSGx z`!7SQ7^DwQz-YUj{~T?1y2(cqb)A(0u{$Dr>6OEKF1yO;{~Rn}0ZekSpj1*nD=tJD zjB0i}qs$Bi=an7CQPV^*z^P}+YLEh=?YHP6ZSx0*Sc8Dcb--U#$ONcKY0J4GLCiWX7!mSD`7tt?b~v%Iad-hIZk z)zb9oUg8+tt+w|q1GT&mof5ux@Du4R&|MW(yDwP|4w0+>FQcZc?EL>3MwAs_w^Kkc z?7`V}Qe$N+;;ZK?p-MW11OB0lSLpEcck!@6)=7-P1y9{jd+U~t+~l}jzjR83D?D|| z5PrU_4D3=G-p{P#WaTo20L`(9iv9_1ayeR{I(m+5Gzh70aG9NZA88!<9b9@*9lu$|eKsZhIqdMeD5^ zqdNMKY!s@O*rI(*Ou=Y2REd8MMC=1)M&&;Nh;U~d?KiY@-^Sp*t@pN|6M{8?vf7oa z@sW?pr*Y%#jpl^5Dd?rs*B{9?oq=5M=+JI}&L8FzOw#8$&tk|Nbm%OI5~0qF=YgB) zt3f}l%x459^fg;aOYQ+b4*hFJA24hUcfhkI=qYYI^Ns#zN3$p7U4Aa#sNa@|TnG(d z%+kW!_=4b$lNj_uzV>E`C~HNTk~d4$EIb@bLWd7-pv#{mfW3{>1=`a&>VlY%)r!oB z$VV|{=sE_Kx}!L z98`ATq-R>g+1K}t4ll&$!CP3p`m?Al!`_XxOV+(5ydLvTM@-eI_XUq3NY_O_SrpE$Ar?9Oe9CjN9Y`$jxO@mZhX3+(-llHG)5|FgHDqKfx3G{KhsKio#w1lfJ7t- z_7hFxF1;?Lqcsw{h4n2qYC@_E+!pkJrIkj_)0SrW6CL-W5MN-O(33doyajlTDe=Ha zWqYlxuu+vK77S7GIpl6&bpI>7b|34Tb>khg*18Ia`8|-otl{nqg?e^56f`Gf#+OTl z?0*W`DDx2C6-zn!k6E5m%CwTx2CKwre^9(rmp4((vuKFf969@hozCiurx!ZLGgZf} zdOvA9C|xprn0cuElf;u-SCu84xiU#R?GPo-*o$gj;Jr_8*jv7N@RNI42S8$H0uwuU zFC?dM3odWMWUd{LA+o$r%T}IXaROPrkHx3NyNRxb_BRWb=@E`BcQ!D;p*+L{q8~j0 z8a2Ns$y1m5WJH%{h@Ai;Y33^(MA>+DceN!zDUR94;ImVrbtXffQ6h#1Zlgw{8E_YnNSP52sb`w1O5{;b< zPdMLyA{BJvW6Ym1Z#gMFe^Ao( zgir*cZ)?$@vC+fE<@@eS&)|wU86ySzYiM{vE2;mVNJGbe^D_XA0At=61A7EM?h$aO zP3gR8o#inYOs>hwe*jBm#vZ`*=CW~ZsGR?Yy?2jG@_zrvu`E}s+1A|BOxs#()0LUC z(gZqeZ`(T9K~Phn-lVqhkUBEK8|=J8O0Jn%DYXtJh<9d69spbKNoh(4ffNNBDGDMU zxIu0NzAv};KHt5+@9q10Jbr(F9}oUW1G(?(zFybic|EV|x_~`vq6!*pnP#`vr6+!t zdiGL|)!3-z(Qfm(vu^BPG>KI_RdVb&o8zOSzG%?>Ms>%}@fN0Ft4si)FBJ{QW6M%y zZ@O8mDB1fq9HVsbFB%-82+8fF#<6pOvMkr7TWbza26!l6Hm(xy3cGr&Zn?Y|)BDN! zRz~*qvtEXQ^*z$@8{! zd_gQXR*OZ~KzE3y2$s)3OF~LBeFj-yJqd^?NX9p^N7R zur6J}({s1BCylW=TPNmEdl-L@e#}WsuzwZ);qY7Q2iI@xx|mS(;XM3&`^8$?rG{s2 zL(5*e{rrJR13Dc9JxN=BS@iVCBI7n#Q_z69DP})|)yfguN_7u9X!{JxHyBr09n>dm zh;*rmQ#gwXY*=kava+Ck2|GWEnOM;_0+?Z2@d)T7DE5pj2{y(RU(9m3{7cAb^ApFC zGg0&I4qkas{TTk?F)MntfXJp%fp{I-oP+FohbT}GsI@Vt=b;W&td+~V{Z2(+QB5c_6{-6!nB zRlXv1U%xf^t`)$g{3cdPF8OK*oS;F~v%=3L| zQjE_fg)``$CGDqVa zIe9~pw2d_JtQY*^`6J?z+nmC{(qDtKhvl|9qBuG8Zb4?tn@`xW5`P(a#&#sWY2wn8 zX!g^$rOSmk6_=_wDbH(9Jdbw6YZ-@s9Bm1?BIyYCw<#1Am6us4!j{Vb@9*hrm|cs0 z1zsQo^Frd-**?8GKc}b5pYwU<)`ggyS|jH@`X_CHzpVW=bQ{NRzM+QITO@95ZTBT@ zb+TM=ul@K>?EcZ3$e|^auV=9>WATk5$@|K-s;#2}m-T-jx9QYGkN|zL?D-!95`lpg zt(q0G>}4YIL43rEo!DZr;12G?ZL>T+z?@y@{Q9GPwMW%#FbYn5CxU7)eCd1Jc;buD ziS=&|>2a}s=)tcRnU-Wl#_7f@?^?b&S2n)DzU@UC{~c$YLh6j8{95{}r86Lmqm`?q zA=_a}{OyP# z6kQ659p(kP(I2bsb~rUNyn*6`D9CSHtoy!n z-keZk0Fv_i_?763ZvsI*`vUpj(@0nCto`(<`0a|A*JqkeI7^UU19 z50iYRdGiT;m&z9>(tJPZ+4*hnbl3=FB)@;+Abh-G84_B*yc(~y0mgiu_qDA*v8c*9 zlgnjB$0Nb*$I?Rkc8s4fwp5x)HEQQX^x37E;=2ixVH?%BP!d?o^YJ;$cwJ73c1f1w z{q=>Tk3!cDk9%vZXEcR}0s3?edFYA<+a!GXz)99MhmGr5<>0UlUc1I|lefUBS!t-VvZ;HmqsoN81`A8Wbq7I!F^uKCR+UqO*_$8U z{H^w=R%0LNRaN`hY3Jv4?Y7@G@K~OktT`)LQgt~KD;>|wS6ZXx>8C_Va}GzRm5(9a zTLz>g(f65JfygsssO-3 ziw~tH$)DA)+Hl0xMazb;@gQ-hQ%Q0ll;pbz$p+%n6>%pCeHvBQz z3@D-YRp*@Jhq6MCEuL)8q`Dj}eXXtj$fB(4OIAJwBR=ALm%J2*Jf;6Qi-i^*MsQy@ zl$SQR$TSubUd3mzcHtJI}xR-n40%`QcZJ>zF&=d>FDG+4H97^S0Mt z`{4V%f{gFu?G@UgWN6Xx%uYq@gUPD5=o1G=NbfZ|BlEMU_wd9@9-IlW&U6WMFOR54 zt;T)F)NH-7ibP&Qt%R>$E3{m8;DTd)JKbHH>fXZSq#^c@@$BAPbc5(8E;+j$|Gg>G ztUWoZRg*N*SusfrxS&IhBxS4BK{FqX87Ibf2z^a0w3sO1)9rp^p+neiZg=X?7hCEH6{Gy&vNY_oBfTc zX>;Y4-T=mB<5; zk)C^U$$f_$mC`6{x%lQH$a7;ZO)V<5;U<}J>U;$CnziwazVATa0vn^_T49RG&>o5B zXfIUyVF&IDlVr19n)WPfpRPTty*8yGGI(;UExX$pvM+9nU7K6g%$Z>(Eh(oj&nqhF zgIE!yH6Z|?X6&o3(g|;>*7yinDU@z@6(FlKS1jb;nIK zZ%4k}AalE5tQ6rX5&Am(ZZeByfEG>j78@ncc=;)FdixegjJDfiZe_!o3D|y1X;$X3 zX{Q!94bcq>q{j4J7rmR56c2Z@1Gi>f_iKEjzUISEro=T=FU72lnwC3`oH2lXF;h=G z%olslQo&{RC3Q*n4Qr35FGnoS(a9M6n(N@+Yf6|g!?9XZPz$b~)m=_$)i0;sbgt;& zsi+y^QAa0fm=$o3sQf6qyUJq4DF^IBg$A&7ZEW2o@XRro$8Um-rqP>y_j{M)c6d^O z`I(uixy`D;O;)%ZIJS7!G@RF%lIyx`?BipdslKmzchL7|PsoGaaW)tbp)SUdtlkcNuV zpK|Z#)icp@Q^F9k6-=0&Iz9KYd=~J;oNiunkWhzre|2Z_lP787CCA&W)#P)& zuTPT)mYFx^*0hnZpR7DJvsp5cHeuZF(!|gQOMZy3KNj0_V(h-hmv(MXjdi&r^pXa~ zoD1$ARz~z4wo6ZNvV}K&A#B*LI+pQ%xIcE7ojK(O4g#eTjyFZy?XO6EIIH!F8^+)D zf&@&o;DY^GQ~IQ!x&x=oO^Px;y)0qAF<{gpdj}-q;J!lmluwqE3EX!2+_Nkx4{v^#D0LJ5rRMUc4O2v&ivLID(#8cpVz@;OP-!CEa&HL5`$d{px?%wpb^P4 zSvN(hAM8ULnz+o%41%OYr@W;*K19-Fssw5Q?fl9nA#zdEa#n=9UyZBcan_~PWkDvx z(j8}eW2S9H?rE4M*+SxiUpM!yEe(F`OAS*?fBgM;HU6e5Wsiw`vFoX44S+jzhp8k% zzLkf7rr6YAfDBg`)9$9fWb?d|8l@GXArmnKqh-)ptVlE?q};SgYuT|}t8dPjS6f20 zC87^n`cj;%*}`!wQF^5n8I3bAnU^XCnQ8(vY5s3A330+?45o>OY-ldfO)sn>&cP(E zCvI|jF%xsoZ%CN?q}hehld7+0ckomC>*q||iLi>y`x=M0K2vl_N7tfF8QyX7k-NCL zH}ZL>7bp5zS}EH3#JBEj{-%tqrglra1;d%a*>e>u#av%KU#Dllx|&@AAu>sni7qdn zb0|&NujfY?TM|5bwW~wUdu>2Gsl>rofdX9(+p&`ZEx_xTj`!q2*W8 zwRQ%B>Nt>Rv>}co#7=4Wv>c>*hj4hzSYXrQvWIlrYJ*iDsue~s##!lUY-lki({k*V zXH}@pUN8-44{u6f|6`|lGgtB+2FeccXQp~KC*%?z{Oa{n-X?QBvvG0>LHTGka6L5S zLJiyFgNapf*n)~{>92+OP<`J}q(?d>y}M^r99)Q<7f3|7KkuHkHDTebn`d5r=XgP| zTOTgAaH&|k^N#UBuh7@X=Pq84WeJ^XQ^y@G*oNgBTIrE z?yr$W4udymg0aPnJjJMwh#uPh6+X;SWXlsumNo0?EQ_CDk+I=t>*Bu(ZFoQh8-t@; z_a5=C@3Wzw=6Riw?_Dlvr3GKG9Y|Q0eL1;W1hXvW8)ktBcS#pv-04ds=DqyJF_<*# z%U5?!e7GvE?QG4l5G$@|*?I!&W)1hW)4cKR;7|EnOq5(gNKi}W^)4y&8Jc_B|DzpJ zs@N#x?r18WmRq2B!bdJrDt8PnDYPXM#67|mFJIdcGjfq>UJrf<+`r9axtF zJEjI4E<@B=%PHWp{z1S$uMQGA8y}Zg9H#fQ^|92+0`x(d!(R5x)>zxV!dAjrG=4GX zZgYf#=M3!IoTue*N1+2ZoBuZSDFpHen*J#L-g%bfy>yXuZ~z{h*1SpSbbu z@^m$~EJ@$DGe)o3!K_8QDuc6))`ln~v?X&aLB0Kzir%KMINlzq<~hO+824fEAR-J2 z&)ZjDOqXL=H^-vwql_s}v4{To(n-OWn3cmNZuC53P*9=a!-3sKs&!2fq9h=ahG!s7 z^p@~Tefgu-xEoQ7jM4@H^I(ZZpC9S`m)I$v;V`9r5iu6@s(|Ypyw2P{ppeQZ>MJ?I zF~{@c#~3oF>*#E8HK4r$HTge6!W$qYJnwl&wjTFjJclfazS=MutJdN)h75K3lf&0_ z4v1l`2M31MT?7O(5^M>HUB49aVbi=tmY1W^*vYd>ZFErNwkIDP{q}@i_q=S?r~C-) zphX;vfm5PnSAs2%MDSwCXkV~xFXlEqsPZ{Os@y6ww;%B-j)AS{(|LROGGRnTH?P8A zf`-%txgNAP$v4d&2RAbo#v;~r8-}#v?t!(_Tec~eSeZ5ANeI<$|t&8d;ma^&m zq!L7FlH3CY{9BWao$T@A!W1xQ@p0%aPK?D5y2UBR?P73Hi;a~k+CCBR?3~ZTOZ;kA z%mEsb1CzM+{MGD}cE24_l1_gC?`3*C zabH2;%8|`^>=;`Cq+_pR#=rcr8$Q)or}pGr((N?E?!9RQ_f6h>QBPH~y(k!31i#@#!4rZr?vjU-54GjkGqTk(KVx6S!HC-`B+0UdGiN{bX{k_cPq z*talYvHZ6&XXq@Wkt)3vRC%BMFfd`RX?#8YU6am;7iKtfHY(illOtdjIN*98hUq>N z-#DrVtEq&L+6uKOmEKmNjjdTYX^V&-%_(M@)~k|dVMIis!ld3ZV=6c7rT#f?0dUT> zpAUPI`t`Fd35U+N(wp8Zyq_~1G;=INIj5KU!n~RAaDUF3@im{J2E3)j3WI+kPyKI;x)BeO$ zA4iQhD2N*D83XzsSMnd(aByzShWxTq$)1f-uiZX1LU{=L6=`wyG1Rrx>t0*rjIF5I z@09?V(1-|qwq$>@*1GPRi5_MSKlU~OyR|CH&Wx39dK-y*Q@X7OooA4o`-aD#+sal4XDt z{8DVHU`lQo`pi>ze?-_m?syJ*5Bx0Uwj^@Y4zU{ouv?v2>hm$U|9hbzJMGD zbf?>_jx#XwKIWmUXPtYpkd#J{@A_F(K6^&OCHvu68W09*dnZ%W=-c>FFMj^yx^d@; zRsL|9&%-SjV9PZoYe2xu{1*u`qFU{WvB->gyJgpJx&Fk(KR;6dw}m#R=aP@Ib6EaI zqK-E(vxhAQFt?8_jb#^I?UDDDqpk&BbL4>?kS;sy($H?`*GEA=za4%sry}sP0zGca zH@{kHMv)PbWw5vqr_jWGym+Jl39YP(4Q?puBF=c0(bGBg^!x!T+#4g4POn)Fwqr}D zH$ER%ON}#~j&YHwCG;`1!fnhIBfZLW1Lh@P#^PXi0r4NA{rSLAWSD+k#%0f@goo$f zi-Y1HGzdrHQ!nXleRZEqulHG8M*uVEa#(4;2X9Ah`q$(oqtxeii>PnG!CThKm7lZD zgr&OgE~!hCnr&67r!gFl#kX)yOW=x;GS)ghvesoiag^Uay`0B!J5r`?%!=xH-ANcp zyAgar5##^g<%6@jeTNeU%8A@gQn8=7RZ{+#s@reEN-rpwFF zj>V}JaRQGf`!jpMUW9kC7q6#ln);Fn8Y4y0jb3wy`D1X^ZnJagL*~QBGeRuul|6 z!n30KYF?xX-o3q-p5%K!^x3mu1mc8*>3La^RKETl8aDx;ABz#v-P#|J^5JZ*1iOHtoK}k$yU}} zs@0oJes+4Hn(bsycT_ct%z6}&X?@6oVX{|(1+U78+_wx7VH z5r%iW6ihww;0|eSW!g9?<)pLH&sIvgFe}o#Tvrnj(RH3Q>A2{i+)KOpOyjkythuS_ zyL}#d+V=L{vTOcDn8mS|-PT^JU|+sQzhW$CVPx53jT*3l+dS5#Kib$?4;rlbp>D4M za?EcR(*H`_kHOsj<`0An9Gf2~-23q-_4I*MID^A>#Zd351r1SkDKqcZh_l-(S4hrM z79QUp+%$Cm8%A*IwV&r(!Ioc__gInA`$gn8*BoQU2IHbAdlxHX>ZCGWg&>_zM1r$< z2Zj0jI399p`)TvhW32eY`xUnL-}a~~naEDOiax-AxKg_G6%hQ@aoi_>adG2|_O zT%zvJmpCP?s%3Y7xF0+Bv!r9$b@l}dK+3i`%b4!>t?y7{ioP(6)I-{qp^$rm{0WA?MhF-g1d5(~%ENKi?tnd8PJ zMnJ%{T^;&?YGZl~E_h4$dpIz0%i5%q)j;qc+{=c7G`7>1yUn zDkV}w)EcFtN)QY6U7#=T$Q-f3`OepEgx(04l_X>?m&m4n9TniRemL6{w&Nv_Af2AG zP-DyDKZq@~Am|#SfkT_yAKW=TySnfR`B-Ci3hM(*wDxr1N0%)epRoo40I;&vTs7VX zyqe5d<@=Xby}G{Y-PlcKRpeMpgG|D=CF_FmP$kI0{4uc?1y zMpCED_kZSE-~vGkpY?Cd(xA!*w1r$34%VK(a{yT^L4mdIl#`xiXE8lPLJpwuw z!Tf@Md_TLQ$BV@k0Dfw#!)zwZEv)75QOzx8Fgs9zt3 zuWLN*g5Q#+kLD}1j0CdeP@xD1!&VX*K??u-eE(K{yP)bQyJ2$a2|9T4DA}A9fO)~r z;$=+Qnd+a2uVMDU@YSi8db`kKX&uXn97*#x38G?^v0uoyqod2^`W9%e_Nd~C0=z8c zmLdmZFq)P>`Iq+Zg7i9lY#=wl1R zb7}IXi0y((-b@UC>aG+Q3{<0RiA;caqVz5KLB^MYY0zLevcjpVHxT>GG;txiM~?vV z{bU+urExx#0_|@z8#?y1QThVMUkr|Z2Q4BO3EkiU{lt;FJQ0ort&4@?4U=40-8|vA z3wk`l|I5!LRCqPz%nVVDKF6YqhnGAM@^wm|(~uSQ+xMLhrduDs!g#--y2E37E+kAv zgm4`@Qf($E^jH-2G7eL@X{RutvEZ1m|~=r1U?pA!o+#B|4<0RIIF z>aio`boQ;_4uvxOU|d;t1Zq<|9XE8Nm5rqEkW*QuZfI>(sQVezTH{+S@_BBnmnl`3 zroM;^mFzSI{Dv7DtHWo5^Zp-Ba_ZtMR!AC@siS4?J%drwI@ zAhhHlbi;-#IVG(b@vMq;-^$yhv}_tVjlilH4LgKf6^n3DNahfkW$4p;lPjfE!D%yn z(JTjn02oWM()?!RqUGdljC>hJUWPH(n9sCOslN@~j7Ve(Z6vGP-%@i75{{vtSnE++!6Eh5JdiR$ zHO%}wM#^TsG|KF284Y4CefU6eCVsz!ywcjCL$7eA9-$l`v4KM(x!TFIH>~E zp}?hQK8{JU?Dx%3ib#@`zZ5I<=flzZEGu7S)*EGCLj3UuLXnEAl9KFZM$MPx@zQQa z@s|NVp`;?DNmcF74#brOX$yk7Lft#cMsJKQ%hp3b{hWEa_%fK%Yi z!85yhC%!#S!Hj&TM`8f}&Iu$^<%f>FERAoWENJRVn4fa$e0W(fNcoIkY+)T%_g>n1DowAQWU%y@b8 z6cVKbGu5@~sg3H;EL*o;Peil{yjt`k9HrNjkkYnDbu$RegNw-JaHoR#iR^hj&i*h# zfU^fE@??Ed?xQ!u&^5H`Cpl~<}{DS{P5&r+FaxJ5>#e8=Z9pOj$_G0%y3?X_u;DNI{ zK&t5Mo{ceI>50+L2BX|770I@pG2chU!982a)vMo{+%f?Bz1MW&Fg?Jx+!2P@hxJ)LuicoW`bH}G_4Q`Ep<~tD+ZMM;NdMLpv8aghj zi4sPziE0we7xgnydIM^v$JfE(9lsy3QK|>fHH3(<3n9K=$FKn&cdb}-(@o8C+N17^PeAChoJD-c zWkd(HiAXnK>}OBeDHW=sq)65o5_t0sl|WKcz_r5;P|<}3Bg9U=(nv@Z3-SW5q!T*@ zruSjqXv73=mxsgFN_LF(*(z>`-r7;^NC$cGPp5W-Pw#X5am2V?7$fuXbhIt>7Ke?ioDHm$<3 zRxlF+71+*V6K1a=Y3_4)N0K`xQJ7EdE!NZYNJ+}F$Gffyj3V3c7IMb+1qsVZDZ+e! z%V%Wg3(>Qs*WK&388zWNJc|LfIY?K*$uetnGJLw>X>Nkgl6_ZBRPE{{(q+D7iWkai zOU-^?!Lh6wqg6n^^X>po&oJ~jh{> z&od!%o1BO+!+5BsTSpb)7-W1s$(PR`Pgv1Pvsal>x*4H&OgI2&Y3ckM=EDz^_K~3i zmxI1t*alQ%m}|QuO?tfy!R$q9$a1PTNpuI*q{@n?=f=P>ugj{FV1*%?6s!LR5q-;aK z)${lUkJ;SCFZo^!hFp|CRvI>(C8}Kc*(yf0#`m%qW@{Bg z+NUDXPpBro2JX~mA;Hi+kG-ulTK+KYhKTu@FbiPf5*@VeFHyS0r6HBGPYdY}*TmWb zEi<7SX|}5UpZ=I5@bFxuG$y;kF(GURHHSc89TRQWylo3|79RH)B&?}a`&TG3U^vFz zBkYT{%LRHN<1nHf<3fCUW6gHnTVsRNl!}U9lFKYyKWfJ0{5Ij3cYoawyFsQ7vIO5D zXG5TfSjx7Asa7bAGsc@Ka94vNNOuPKjrC5~mT>N|BT$NpYpPGv5@j6E%BFoM-xx=~ zNR#%ftTj=_HaTO5uvGut(Ht>jNU-HbxF)*+pUAS8W!Nt79z%dPDiDddC}kKy9%~g@ z=3woM(X89cX+6Z(M!tmX-_X?q_~-0k^XOk2mBRg)HR}czo<3Z+ZALRnV-sr$SF1sU zfl}07=Dxa+@0YkViDr`HL7}T8WUA`dKw?+4iq4PGBVJrT{jr}bAWJdK$ev*&SbrrS zOTA_>5M&%F525*Asf`LbB^+swo)v=N2ym_ZAnbZ{*wET(y9jwHHi#-0KRVwnO1(rv zR+9XR*iwS7(4{HJ9E@EcogVHCGlHP9V~CQ+O1uXEeFvz#31LgSo9tZoO*eXGV~AVF z9u+sPNO^`;k1I?`@YNl*r%MzpSuyyu4b|z`4gVzTwZE{zsS06iNnN*aS{Q|l|7VW+ z18XpxA^H41bU zE=@zu$8`Svv9+JHDe5ZV`=nUdXu9tFLhic#PR~w*T8PlkGtYO55|<~RbkdFZJ;E#q zUny6_N&<;alNkLP_L<#nd%LOPxz9T-^#c5*YhatBErba^Pv`5dddu2 z?avqOtECmic4nDye7y)awv4V9X4fX-({E49&0h4%hWfg-PmHf0LTbeQT&}ORewvUy zIctPe7lB4wJ@;|5%O2l7#HFrJ9(+VunzQR@ErXU8>q$(C zg?yVqK!s++lr{vd1@y4MGsoMb2&Kj_B5RVAG#3N73bC8|;+O%h_mFQtwc^+@FVC6w z>%-2rqI!DAStx{KeQvKz1uv`IRxHBm9H4l|e{!KpczT}4uo%{`CLTWYg#V(=w=Z`_Xy@g~r z$wIFLIQ#3?9S6tLs}fJm6+~s9aomfg$pL8!xmZKMJ{H%-+hZPjtu7lriJ>lJKQv^j zMMSBHw8VZ-rl@Mh-4I}1&nkxGWv(i2vS6AgcE?$+B77<)sayAbg@R%C_LmgnY(RVxHR_7H<3Nnkk9)XRGv6GR-2W*}&())hotv!6HQm=W`>}$xU9O53u+O zo`R~d0X&ta=xqCqqFAG{?h}PYl~<^RTWxVHr?j@3KbmyY#4}1eZRk!7qZ!Zu8~(-_ zRs|2}H`Q?Ggh{T(&G=%MrfhdYbe8cP3$4v?@>6cC+B;-><_48lhXsHx{E$=JBarQRI_M3tZS@@!S0=6Y()vE}0 z=86T=h4zT8DsEU}s(QLBU~|HYyNM-2nDU~+Xd~qbjKX?aJfU)nZ*6K2X-zD0A!(2* z6iFk%{_it)4-J7XB!-%or>Th%F2*u{4PC_#DW_tazb_<7kHQ4Q1g%UkXBL&t7S? zYF2Z{=)SBETT^VJ%R<;USd=tGaMn5h8hY?}6&-Y9;prR@ae027@51SZD_a#B#IxVB zPrY{DqSHB6dN=VDYOg{?XC8>E=c22^3`grbThg)AZ`KfNL7QwjSRa?^ZIM%v6-5@8 z3_nREc2IvO>Leyp8JhvgRrf@|it1@MVTvrh-|RV&2KYbOl}4YfjbcdoCxv1Agyq5-XEnr?-J9I5ajEt}M6T6+kwlK|5NG6%HFGu=xm$QWZ6XoaB`xCsH7r$#K&m_l~&O165>Q8Dt(F%H7ugPvD2b`3_+(xu@qRBB>S6G zd_z9%@RK!f1li^p5A-OB>eD36m4K@ky0vqB<|>hvKrHoaQA_QeHW2*lbO3AG~%MMw@{Pe8xDvWRNO5a;6S)o?{W}!juD$r@!=bF&TD!y*XmO0W?*L z63NYA*fKx&=V+mY)(wKf9UTh0bpSA&`K5@Lr7AU+ZRhvZtTUU63n_+|jaiW;Y*7th z!^Po1{yLGy1A43H*X&U)+QhQ2u4AcA14$&UbMMyaqwV+9@er#PrJv%=`1Jr7u7KIP zASJ-GT13V^Q*;l{Nwn3!+o9y1aMdr6bnV$vSK(yOm^H88tjS)bZ5Kr(4eNu>7(E#> zmIMFSLF|kWdGQ7^*L_$8zNbBGzy_+~`8VL!Yqb5WN-xY~YLK9nTEnW3JJnLREncm7 z5p*!FC@KaX{6#1tB9yi=6S9xx_cB*?iR6LVdiE!U+>A~cf|#VvqN*~DXjn#cj_;|! z9IR|jH{;F}8am2mnv=xdGfg&I^o46@s7 z^n&8o#~cK@o+t=Dg{cncEK849?7=?I?j5iqv5{&{kOfiEd-T*)u}7TL)~@1k4TZSm zlqGn|s~lyT+M)$nX`tNpwPV|%JMFM0U2D7SZ$>X#?#|}tWo)ld&;bqZ8d_ItF-_?d zT|DqxUH<`PxYFJj(esNrY&Ht{e3m5k9L`L4?)E1Ogto4!yPxze8;mC$p8tvS3K zBDbp)71cwnFP?Wvg^K<0YjAEfCEfTa!78#`rSDX>Sn41-ole)Mrx`XG#$(Wnlom%v z-B-1`=Y3{BiQ;%RTp8-#7d*l5Stst->nI{4tj!|$h&PsWDylhdV3Z;>b@`cOBVQ2< zwT1fN6Dl3ACgmv>u4B@$z|nfKyXzln4!|rFeOhi~xI*O1zw9=Cp<1Wy>K6Q4QtIC& z<+k>3)#^7%-TgySo-R$1`BiWmpkogzOUePPyPLuB+l_ss7r^kfLdh1f-vn6o3ts%$ zZ(oLl?dD(^8((N!I&oIhCay~pw-vN#mhLaOvccF#Krcd`jEjJC-_5Jv*0ZdrZ>?UK z)R;W9A!Q9&Q8t74u!1DDBR#%0IqvcexIkf<=*C~m$iZH*=(w~}ja8o5J{^Hrd{On- zM9nFQooB}adqg7h(5jmmN_s#gWmr%bl|59PVzS57!6E0-Pal~QjE4o>pLLenrGp(#yzs(`6;TvAJ3*N-`Z;(j}mo;v87Gj5%HVqMc; zmvQ_bRwaH@8$8h3`^oYC4*8Xd62gOrLtADltmxz8qM>m2OWu5MGMQ6dXfqqyrl#x1 zxX0>iqWC34mS`viyQlLoTCn~X(n#7IOuj8kj?k~e>YsjhQ^4+YN&s8DW;K&6C|CBq z&_l~4>r^*~&$ar0wzJTfc_~?33Acz$lAf;9UBr$oevY9o@g7rzSTeVrgk3Hw< zs!dZr1$Bb}*p%9CBCI8WmZ3TnDJK zA+|Zq_9^%oy`=$Tia|GxFyhXTvJOG{hK|~dc>c>Ur!`cOr|j<9p2~X1f`$@Hj2V|u z&x$D3X&@Q&sU?_|L4P z3V^8`0w^ta?BgKMx3%Zm#~|M(A#dzO=jov>#}-0(|Kzh;?Hrt!NKcT+6@#R{TFQlA zHb?#S@;#2SFOQ`L$~fSIQJ;A!qEFd@ns`yY$x*2w=YW$|s7(D3iScXHmo% zSn)+H$@)E_N<~6#^}9rX4P9c!qtUR@@j|0v>#vCLo5n;PV6(NZ{QhEi*RMXJv?86@x9;bJ=U?mj;{%?qE~&Q^fKi= z(424)|8hHk9WwLH>WJ9mqRC6z;W?#XD9Vj@p^{-js?z8@Kv=0sc4?YAR^%-~g|@zl z`5EOA=)z6!T05?z=f1;^tRYrLMAOn%LN^gFUxQxN7k-cma`4J$ipC@$p}oKGTdH2& zV;mVG5)M#|qE7Z`O$i{t$~|p+w1O&*v{JaYFPytE-5$m`KYs8LK#euy9IhhPT(|Kn z4X40h+#u*~z@g@6WsUBA-f`@8!-{)w(jK^vM3!(fNvZ2*V)XW9&i*laiJIG;I0s`+ zc@~cv!6^TYF_AMx*!UmvJFuwvU!^@M zV)+mKNVjmNwNnzlDS{q0g*x+X(YBHe`+=ImuVMPvRu7|l428;9wRQ)4%I<_T3#ZQ3 zHUtT-yz075a{IGmAI|#X)qG5~IRlajF?)v_Jcw8gJr5+Qe6COb>cMU3k1YRrhl~yi zqFt=~a`V!nJSkx+4IT^ng3OD6K*Ito_Ld~iOPN!YptLh-&vkS!qef@fQcw6Az!-{pv_}6(j{7cMN0xQz5z*{bFDkbF%)cGt{h?)Wm1LzKtpeh zH+8nd;VJ@0Pe$d zH=i#~dK`&w7PeNmg_oWt)ukb#s1;Hxnhb&hnnTH(T_j^1PAS8?Q`FRwrB8Mrb%GX` z(e5seKn5m%yAcC)_K$;07a9rlJ@?Dg7kZrSVi=MUOgWTLYwTQ!8&a&-%Ih8IJH95CbA17Fk$0X zgh;L~H6eDL@P-)9lc`_-@4W6y_j#AIkFY+=57|_W8(Qc_^_-CPE4Chd&J94{mIkDp2FCm( z<83nFB;(-(7|g_8ME!98=;nmj!EwGs2#`%&8i8LNcuTPu{tw; z1|7wLZrw~W`(gGS^qd#tGOWiQ7}_L~_6emHKg*z2mh7)6iIG!Frkg?HA)RilxGa!B zbgJR*8Ht=ZCkY(l{@|>D%|uRQ1HtNfl}lbD=Uc=wrq2^a zTA3>L_o#C2-$a$$PTlv^8Rp&`ryauu=d|u!F}*%c4hhxz!`Cr3FGG1Rw{34S4g+Ti#!~9wpu>~ zwWaqh;Dra-ij`;dWtOxd0?ce+6xurU*GiSM4_xk!5JmtNRarCfDWh!HjOo1jXBwx2e|g6?v9DvbWs*LXy9w?a7MiV)V1KIX%atu&x)L z>>TDm_;X%kV(!@Fnd2fb>*A(*cnJ-#fj|#a=-^9nq&%7&f$hzr+WYD&>^_rQ5|lOW z8r+RCpgy1`3cM;uk?PwaB_1mt+^tT9dFEi-I&-ZhMDk=)68n_2HkG{LEM|Z21wmrE?;uEI3pI|vuF-h(-SgWV_=XJ4 zUHh^C$i%ZF;XsknvTq#3N*r-|zW27DrEQ$H8h!(F^I%hPi^7hw<(<=W)2CV} zPqe5!05ZpdZ=CcoH*T)taIc?ytBUF^+5IpcL5l(*+jax?6ths&nhPE3q_3WmrLxt!>_=1(?M`%wza&?4?30Bpec5;b(cu#N z!_LCvOqH`NHwW}J{@wzUIdH^znU`64OCkAEdvr>>+)Zs$Z8S1lmm*3Q1bmDww^nf2dC7q zQhU=Qz3Demm`g=0srX)+{IM`np0G7`in_Uyzo$nMQ3pOoa#9%FxI?RKZm%Pw>e z_t85Csm%_1zS>k40*SNWs%W$z`Wmr>(h_ys=aLH~J^t_eO3&no2jF|NdX^X5b%%fp zi(+`gJx&gbJ*VFOS>G4jbuLOGQQ#f=tMVlU>i_=jmus=3$PH%L>a ziDR2HW_w;K|86_0$}6s?<#AORH@U#daPQ&*c;-ky1h<&CMnaTe7Ob+bv5(3f%Ya;IkKK-qkB+NR z2zJw^`hKs8res0HTu-;n1!~YES#8=;2qkYES$^h@yJs~z*8b&<$?ZP%jP-PPv!t&#mNxfHZM&4a; z+u$INgq6|67Mjw6MFkN*urHhTpoPaO!k?1{zf4?kB9HnY4f%7i-OJ%lo*mh>qL|dW z7rgZvH|)}A{x$5!YJ4gfZL6i#q&#JXRxamc#n)7Z_Q&PS<8O|8FOQ|nDTS-eLYDXS zl_8qdd)bxVv#lZ)2Z_A~Dc}(4PL&Vyx<=$}_Clhf=t99n3$HHbEi7mMsKfmiQ%8L^ z!z&J5PFb|nJe=q(Gb7W#GJR!ek>ih%OR-hY!dZ=u8$+3~TBf*Zw?d;SanydNft?d! z~RhG5rSsN93x;0%{XmaL(doL#!K z9!`%|uh;LEGp$Pmf)NiA?KEev(Oa7C`Fq#>Xlf4@Rp@Ku@k>-$|NRpw?Vn_&y2ISP z95{Sz$|C+@@ysRKH1Lc0vQ!Xbh4Fh=;*L31r@h@Qu+0skg6w3yHzHSd7Fv5h7Zh6? z?hQXW)C%YGOZx&kw-hl`A+AXvRBE2Fx7vEj`-NCq;R{=!7kB;~T^2o01+nVz2+U?Z&&Gwre zikGo?q^adMkM9ewI6sthu%Nsc6Kyys$3%-HizL8?F)lT?o@}FBZ5`$vdh02~0;T#k zof;`m39f1DkCT>iIG>Puqs2EH)6$;ns*(xUBYVA`h4u%{P|l!qR--g^fdl*d6vTl z)>CUt^>EZ{(2=3=V1`{|wV84Ox1c{fvo$dEht)a3;-??nCqCX6=Xd__0sS}IA12yoUhDGtn&D@da0ursG#4$=- z2`#@g5gKAa*A|f=8m2xTvUqdTw7n1y^SKWB>)Y8rTmg+#P7_tcd`#sV&8YOJsr4uO zm8EV`1WVYZx|{5t8yLTTz(y|@b-Hhu1|ei3nh9kAsy>8Bn5q{yYWD_nx3dUV54)gC z$_nGY=MOnQ#O~Hu6C-ml#Vx)#<#BwaCMji_8~fT%;Ni`SnKv<_%m>bt_^Hw@h7@>s zw3+Rlg$-8_E#U~>XU0UtWDamT+L9^joIWaPc8Y7yhVX*v(6Mm?4o6jR3i~?6T+Ex; z6>FRSp?*Cm4Abq4qh&&-0eYj~M1@AV0xf-M3MG1iFT0l3`Y1%+)=^Xn7h;pP1BHw2 zj5h**SeU^sJ`=6I|DHj9U0Bnv6c&MR1?N#BB=WlMVgl@~uqomUZBYkY>RGRJUq464xN^KWTI2JS{Zsb zNC1%*m+m;FseUzjJ=b%64Wq+>cj>J?L|@&Acpq}**D3Ph$$|PbIkgYRYo7%v1s6_a zzn$pR{aX3~@a31XXgW#0Ut}{125`+s@nEWeBhvNa<#sL`ZzWL91re@FiFn9B<&})6 zG;QKEFR{jGI=R4?q7iPyuJ>yp5cLI$_u0=R^cR$7PBthY7D)-bE56O~h4c@!;ZcOA z|3o|~l~@$M?XumIdS+Jxunj%ZMIa_Bdn&y}Y=~{Xn_)9u@#3=GvQkubTb#;X= zq7kpo1i#ebc(#g;H!%hAWj)pINt>p;271xzHn-}*N1#Cm>G&$}y5*^0U)~K!e2HQ1 zUw``1+900>&=OHICzAeSY>@UvHRU|9Cr~AAVOo8*@VP)*K%6|lmVn9n?pof^DJ!iW zJl++H*`PeaOk{$Yj*TkweOn6>BJl(A4bE=tKtH#(ekPpAi<@xLudMkm7EXDpL@9=_ zFqA>#Pw|H80UR$PX-P>aB-z|gN>8sM;`Mui{ZrMsymn;^=?>SZXK)#m zXTps;!);{Z8+PwM>=NJDWw07#arMveps++YP0xpwbe9Ev=A%II3h!#=KwWhAjhpdQ zU*n*rG#9YKH<>T&F3t|1d+!c~G)_`TD;KST3+l3wYQS%ziI$gVNmu_;4UuGT;uKMx zC}kiXf~nrY?02p3ZNhD2-7 z1GXmgz<#9OmtZO-*PvsgU{0>Ck4V#OR%?vXgh>4&9v*v$N1Lzw6Ok6_@=qz{d2Psz zjqqMueNwqje};UnbdXeeYR`gIV0!+Ay1DMNCw_^Q3LZU_bJtd{bGcMg)6YJCEz^<> zB%L48=Lr~2HVxo@FGz5qx1opUNx0gVcN!csXu8`sG%K`<;l+o;aXd}a&*V;^XNWP% z`H$iFA$Y9kjHIzoF)hmH3u&aE)Rm^L&nMcp7ma=@EK2JLWBX^3M+ZXj2GBzdV(8^B z)NxlnDNSvskicct-Bj;fxCm`zKJO^DRm}=}(6%&@^R7GKs*-HjMT`syY@=5m{~Or78{>S;VuxpTa#Q%BNNb!qO6;LO0a-JM4x197Vt8q zDU;pu7gpyJC8?iaZcLXmG`4;qKAje=a@9@(dY~jWe=_)xg>o{o-4>U#md6OONb6GY ziMl|YtlpKR01+{bn+Im!e2SupOg|L9mN)vKlkgl8K<)_{bZu)8^d&2RaYgPX)^Nz7 zyQ*$T!0euzL05CEHH1?AQJRk&4@Tj@ME!CM6B!D9xq6L+=6j9hpX&WsxjXo|Yg_%* zsV&YBhbk&JVdq%TX}La^@#?-!B*}QG52dG9-h6D`j`Th2AGS+$e z!a|-PgX%7CBKlJMVk)5~&oEIJ!Bl9Uuxa{XCT+3`oFZbQjIh&vO~L_>e`R7Z`Z)Y1j#|5??0G*fMxZFE3=|cU_)w+S4?B!S)lEY^AO6PewcTq$Jc`D|Bd@DL-GR9Y zZ>#~q;dHs^#Ag?@elJ@ z1_xty7AFLSrT7bgvdjqO;Ac+40`*(oiKEG|S;e-X!YR4g$Ir`n-#XwdK*~>d?Wy`e zKPJ*N^uvA;e)byi;e}f2ndKJHNHmjQAnTg#E@W=ykdmPZeK7)NQeEx6z2qGad~S{6!U58U>LWra6m~v^3$hF@k?9bjYwBC)ZjN=Q_o84D9O9jdpo8=wb$6* z5nmE-V&r)6g;0(64p&&f=sYFNleTm~tti`9NN*s2`9KyGJsBPBL8UY5w1~M_PpUlc zb*)RXTQ|Z95rn*T1?#NOF$^;Ru2w?LlHnTF^A{?}(QF*|-Q@^P7yKMn?z;SS;-Q}6 zG@ZuyuCZeT$CB$J%xBak_b)-yx?mC=!ve&IyYgg%vaRHw)n#7OjIU7Ku1^*wi|@$F zW#=^;V0^cuI6(PxvglBS7l#r;NW8EZzYY@ae_P1h&XHVLEIh@`lQxA~0zjV{$yfgl ze~B2^Q!M)-@e}e6RCB@hEu6+ji-adhs3s!xKgMbX@QcOMq2?`k(E%R5*u-%H7%#J6 zZNZw16M8y3$GtB4VY2v=amWLWvKrNPHNpEGsBz{Vuu;@4ucULE2s=aSY8hyxK7v@A zaG8>Q0jzv%;|`?ZBM4rPTo~@f;(6#9l5amOr{K#@OB>4(97hoxS@jwGv*MFIt+G|9 zi%$~7NI~XPAw6rR;UfFXNkunI8a^;C^MtS}B z$BjKS=1L!yqt@3@|9j^%oTQ8JtBj(y~@mR0X>KsZ8!0mPx z2P8kvX7gKip&`#9qJZja@y}Uas(i0P;{YQwfL0hPI^@i7F_KA#96RU+B(|bUUS;Fe05(jIP!R=^caPC59!dXb zKF%4#0%e2qjcCsV;4r4K?PBtgv3he+nGlslg%e_o(4{PdB9SMWy>s&!D~#ioqV5Ka zO_$0pj zSyh_X2lFawHXVMu6EVw!Ssu*tV3vnh7J_+~W!_qv_tWM>1i#k`!7LAEc`zjp^k)^8 zc8E*X8c6JS9?!MFcqe}6EfZkj{F;TP*wA19?zRBDcgDZ}eS^;_ynjw|IIw3m>fzIm LKl`Z6KPvs-;mzEL literal 34364 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 z z7f|_%MG~$JmPkb+tX>>tjkQAhJN&-bQ9+b>%PrwFTzOW?<1S`GC$oK)%;L+VGXFNf z3NsN7ERDey4cNS#Et*;9uB%JC`-`oW@C$TA?HASPP-kN+e05IL=1bbPz;pq6`oKQc zEWA5nVA&t#g66cu#rL|?-RmDR6GTzn@A+~QWvrUs*CK^SO1SF`2fjAC;QU8eP2#vo zcswl!?+Rp23>I=bR~+rujV@E_-7-?gYzc0Ee*ZB-6Epx~PK98k3{5eias2&wr>xFR)LM2u!}8$zg+wVx@Nvu$1e-R4AN7vc)XL@RQ`t2Tly8ZS%I z7AzeNJMY}UvsC0coBWJXPL`3jq=)kH>v)yUVjQcbPB!ZtSx$QwAFUx7aKLIy1%_qK z>;ZC~DyxjokGp+{TT!EOj`W&C#^f1g_Ii6XU79#Rl<#z9by!cxAs;@?Z!~{KBsnpj z8{p2$dy_Y&$v`>Z>IHD^D-Mt17AjvD2BveSef_d9*Nd^gZ%(x8MOR8pB>5@@5ZC=n zW+D<@NU1ENT`9-zMNwwl#YX!HRQY+t;D;EWqq-c6+pHt^24tViY8tshPGzaW7FD11 zFn_=Mz5MQmbR<<1g5Cg+88%~FWUp6B4jJRWl+J*CD`Mpk;5>oA43|E&qb-F6G0IS8 z#*)dC<{I2811m2&8Et0A9wMj@ygS!D$H531$Kp)>QRXNWDVr@fAIHJ}d5H-a0(V^*HYd|b_w-tftCL`xD`}VxY&zmYy zh7O6cgJ)V1i*S`Fd)jDtcR-o_{?*d5a$78Sog9LWUv3Z)*^ohjw-?1I)&@})C>i1z zu&C*sGt`_L#f`{vVGjCCN;J1z#p;?K^;{PFu-tYa*>46?=)D_f<6J9OfUCY)#>CcX z(XN7RbZRmJyHe)T^?-+4g*INywcNxuFfg+IbpLD|w*=wum&;uv8Y>dDeI?5-LwZ8@ zqR#uSd^juNGWveJv^9e8)1fRv1METyR<8WIp4cdhBhxUHzv$j%p=)4bHowyE=33vxvv@hg&9yH7xOa;|ttP@9)c{X&6^{La zhT4Wz|Fwd|_j9$8SGgj`o=2-Mam|tRXXbklE>R0mU}sO=7AS; zjfl@U#tvJ(OXMc~eoDrGdA0kl>qUmpw~QZHXo9^D1UGArb{kQvq{VK#g61yDk5Boo zU~16xHe~2r`|maE)oN<^oEYI-lV^|}yL=h>8q9cne<%J8v78WShv_fsp4UJ(YGDwP zo&)Y0Q5F<+snJCI+XV2MZ`N-kd<8bAWFte(rCJ#I^Q_gqIz7R1@iIb@t5UdukmrEE zMza4JSogTf_4xAvKZEtGXXn>^aPty#Z%&GgQBY&gPii#YW`Vz6l%hUNXtbnF(BR`A zf;m*8&by9Yl%R~+M?Nh3&x~|ZyYb1MuquPh)zyA#QM2_Y6O?CvlVR7LD7%X8$&2wL zKy7$OFeW>a)LCVaT z5X1YmzVrHyby;EBKT(M4&%)U?$-0tZsji}mB z#(UTSHnA4%XLZ3Hf+bAwQU)CW6H%;(A8j_dfUg*+!cC&Q_{L zNR2P}t^7DC0unj{_EBT0`G)b-0G#yWz)N)_qaf=6ElK-0C)f{T|p8`0wC0 zM_O<(GvHfkqt*6bvsl3wet$NgYNRxu?+Jz3D$Z2oPmjuyPI+tBB(<4`1;rwDU)Md~ z}B$forI|`0^}ps z{-;A|Tf=zo5BXepwpdowr})%WrDP<=>r2}1tbNPlm^a`&%Cdu1aP7~;L!pT)5_Kv; zJbbkel*%~!z_4fu|MD<+lk)IOvy1Z9jE$DpCW3CqW1Li?EG)2J+vx@b`f0vxCy^&l zpP2@0zy}!Tr*6B?2HBrYP{ad)FFdtd?sBP3xAW^4a0>~{H`JymKdyGd?2}JFE%Y$q zAx`LSN?z6^n~ANZzJ0fEC7F!BQMEOJ-vbn!9PHE=W{Lnozt8~6$O{HCPXrsIlmu3l zmiqekPJPi4G4oPZI!5>zGQ^gyv-ehRQHEH8_mjF7{s0p0s{kVUQ~Xf7&x_8CbX{BH zZ}4kQD?A6b17Qu2wZQMI%)7xd=wOQGTiI!qd`vxWmpTncZ?P*Ha8_AO_o6b{e@s6l~ScakR`hwYM2oUB-NOlh7h zMSkMjV|4nEsOCMH@Y# zg{^xP2r*oMoyzcC==5bB)ZNo&cLUC}XH^zO9e%m7%ElIPC3_IhUo)`{epL8Qmjx#)pq&4-ker z?1>A|g6n3wC-qVjDsRcdHjnoekmDY8k9<+;IAfUEu8HdO&EoF}qZ>>V(O?Rhp1(U~ zD7I__cDg%$m;U{CL7SNo-umMG z8-}pm@)~%$;0&blfWB!q1#v!81W$)2Fo2^Jr=y@Y3|xd%2wKgRR9Qr zJ!0Cr4urB~+n?S5@F(4O0CWBvhqz(ebpi|)xC^>$`*<(EOMd%T80^YFJD2=1#*W2y z9A?WGcH(6RF1Ar&2QB6Q;NwoR*vXZDd|@Y7?&QiXU)af&JGpWj6?Sr^{2zS$f0QfN z>9aiykjxhNo{s8enmon;J9FYc-eh{`zf^D+L0iF}U~GD z*wJbJZ9DkCCyRy{OI?7bb$o^HF14%w;ucRhaxie~7x3@Dxc}4XSM|?-X#rFKtt@|z zoeOrXwBs5(@$d%^c2HvnDtGe3P8Qvv4*&VDAne4$PCV?y!%jT>%aL}9rk$c`r)b(K zn*IfZ9jWF2TYbrEu(k*W+gk+vKLWOm{C|Oo<>&tUM!TmwJ6bzCT01>cJ3UkX=8^v^ b+Maf0VmvHKGKYDfX{p>+SIoI(`uBeW3)PnY diff --git a/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-emoji-picker-chromium-linux.png b/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-emoji-picker-chromium-linux.png index 80e11160e8ccab67fccd4dc404d2e315ca23e3f9..ed01a1dc076362e8e6085191f371ac32c459bd4d 100644 GIT binary patch literal 29884 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 Ae&5Z@QA1nYk>a_7-1uR4^Rt z+Ex7RV584GIvZNUuK_^?sJDLZ93m}-fD ztZHZZ=S-u5;;8W2bSX*EiicLtJlO&$;dREv#w3E!ggzEX=Vy4@os0$jIeWGVTt12v z492dZkrmU^OaAsnqqRkeDt*j^9?2*o8?eHJVB3bIcpHlQSSn|=Tb}`qj35R5vFcse zN^XP5wj?V2GCp2D2a-4f-}u6pA|UTX0_QLRsWJ^D1c@_$_|j_?$LhCcQ{~>dnVFs^ zrBRq#EN5vY_nE2un3RK8S`Jxngy~pbi8*Dslb#1vD%OoJh7lcRE{;J=GrvlzeH5Am zC=?6&ZXoQo)FO1P9Jr@biQbwF9_KeseT3s%f!Fj*$V~#DKVY2oHG(8QF!)aTr1`*N zB^uf>1>6fQm0+;SlDC4jawb`^LDe1_5Ubb^92{ylGaite(Dh*wALZ7KN<8t!Ynhon zPQ7uQp(S`JS-zpc&PM^G<8*+RGaZ}oe-RKWpL_>wHEw2Rc2e;IvClO6tsrWUG!vrR zff)kf@NZ^tToG!d3_^W`Q;(O9OO8xqL0taFB}SkV2Q+${=y_{_lRemRLb2fOTztL3 zjGv>eKwqtdUo$vHrs)m?fL0;gzZrqRp1c*pIWU1;KYi@-H@htZcDoltFhs-6q{@Mf zGouKBuKP>K%nBK4hX@5TW71qZC69S_$Zc-iDPa(}Np%eYtn3?#<9$^f?g3=yQO zAf@U2m2tSSR r^X>5S75Oty_?aw$necyN1o4s{wRwetgss96$4KLh$%a literal 64383 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 akJu=UX997O=gI(qgA`;{Wh$ghL;nj;H8RZr diff --git a/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-emoji-picker-webkit-linux.png b/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-emoji-picker-webkit-linux.png index be28839102db136172499da91c7e015cb900f55b..dad4607cfe90a8a8a678485a65cf0f584e77b9a0 100644 GIT binary patch literal 80136 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�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 z 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/|sniZmA1S~o_T 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