From c713f741e87b1b64e71946a6b3ead9d61e9afba2 Mon Sep 17 00:00:00 2001 From: Priyanshu Date: Thu, 15 Aug 2024 23:39:49 +0530 Subject: [PATCH 1/8] Tiptap collaboration extension config --- app/components/Card/Card.tsx | 9 +- app/globals.css | 26 + app/writer/[id]/components/Header/Header.tsx | 12 +- app/writer/[id]/editor/editorConfig.ts | 31 +- app/writer/[id]/page.tsx | 30 +- helpers/getRandomColor.ts | 29 + package-lock.json | 653 ++++++++++++++++++- package.json | 7 +- yarn.lock | 349 +++++++++- 9 files changed, 1105 insertions(+), 41 deletions(-) create mode 100644 helpers/getRandomColor.ts diff --git a/app/components/Card/Card.tsx b/app/components/Card/Card.tsx index 8ebf59f..cc081e2 100644 --- a/app/components/Card/Card.tsx +++ b/app/components/Card/Card.tsx @@ -30,10 +30,17 @@ type DocCardPropType = { } }[] } -export default function DocCard({ docId, thumbnail, title, updatedAt, users }: DocCardPropType) { +export default function DocCard({ + docId, + thumbnail, + title, + updatedAt, + users +}: DocCardPropType) { const router = useRouter(); const session = useClientSession(); + localStorage.setItem('name', session.name as string); const inputRef = useRef(null); diff --git a/app/globals.css b/app/globals.css index 980b125..d407514 100644 --- a/app/globals.css +++ b/app/globals.css @@ -97,3 +97,29 @@ margin-left: 0.25em; } +/* Give a remote user a caret */ +.collaboration-cursor__caret { + border-left: 1px solid #0d0d0d; + border-right: 1px solid #0d0d0d; + margin-left: -1px; + margin-right: -1px; + pointer-events: none; + position: relative; + word-break: normal; +} + +/* Render the username above the caret */ +.collaboration-cursor__label { + border-radius: 3px 3px 3px 0; + color: #0d0d0d; + font-size: 12px; + font-style: normal; + font-weight: 600; + left: -1px; + line-height: normal; + padding: 0.1rem 0.3rem; + position: absolute; + top: -1.4em; + user-select: none; + white-space: nowrap; +} diff --git a/app/writer/[id]/components/Header/Header.tsx b/app/writer/[id]/components/Header/Header.tsx index 6d2a6b5..ae58f48 100644 --- a/app/writer/[id]/components/Header/Header.tsx +++ b/app/writer/[id]/components/Header/Header.tsx @@ -56,25 +56,25 @@ export default function Header({ :
} - - + Format diff --git a/app/writer/[id]/editor/editorConfig.ts b/app/writer/[id]/editor/editorConfig.ts index 3fa182c..14e001d 100644 --- a/app/writer/[id]/editor/editorConfig.ts +++ b/app/writer/[id]/editor/editorConfig.ts @@ -1,27 +1,48 @@ import { Color } from '@tiptap/extension-color' +import { WebrtcProvider } from 'y-webrtc' +import Collaboration from '@tiptap/extension-collaboration' import StarterKit from '@tiptap/starter-kit' import Highlight from '@tiptap/extension-highlight' import Underline from '@tiptap/extension-underline' -import Heading from '@tiptap/extension-heading' import TextAlign from '@tiptap/extension-text-align' import FontFamily from '@tiptap/extension-font-family' import TextStyle from '@tiptap/extension-text-style' +import * as Y from 'yjs' import { cn } from '@/lib/utils' +import getServerSession from '@/lib/customHooks/getServerSession' + +export const ydoc = new Y.Doc(); + +const room = `room.${new Date() + .getFullYear() + .toString() + .slice(-2)}${new Date().getMonth() + 1}${new Date().getDate()}` + +export const provider = new WebrtcProvider(room, ydoc); + +export const getSession = async () => { + return await getServerSession(); +} export const extensions = [ - StarterKit, + StarterKit.configure({ + history: false, + heading: { + levels: [1, 2, 3, 4, 5, 6] + } + }), Color.configure({ types: [TextStyle.name] }), Highlight.configure({ multicolor: true }), Underline, TextStyle, FontFamily, - Heading.configure({ - levels: [1, 2, 3, 4, 5, 6], - }), TextAlign.configure({ types: ['heading', 'paragraph'], }), + Collaboration.configure({ + document: ydoc, + }), ] export const props = { diff --git a/app/writer/[id]/page.tsx b/app/writer/[id]/page.tsx index cd35f09..adb1244 100644 --- a/app/writer/[id]/page.tsx +++ b/app/writer/[id]/page.tsx @@ -13,15 +13,21 @@ import { EditorContent } from '@tiptap/react' import { useEditor } from "@tiptap/react" import { debounce } from 'lodash' import html2canvas from 'html2canvas' +import CollaborationCursor from '@tiptap/extension-collaboration-cursor' import { ScrollArea } from "@/components/ui/scroll-area" +import { getRandomColor } from '@/helpers/getRandomColor' import { FormatOptions, InsertOptions } from "./components/options" import Header from "./components/Header/Header" import Tabs from "./components/Tabs" import Loading from './components/EditorLoading' -import { extensions, props } from './editor/editorConfig' +import { + provider, + extensions, + props +} from './editor/editorConfig' import { GetDocDetails, UpdateDocData, UpdateThumbnail } from './actions' import { toast } from 'sonner' import useClientSession from '@/lib/customHooks/useClientSession' @@ -31,8 +37,8 @@ export default function Dashboard() { const session = useClientSession(); + const [option, setOption] = useState(0); const [isSaving, setIsSaving] = useState(false); - const [isDrawerOpen, setIsDrawerOpen] = useState(false); const [docData, setDocData] = useState(undefined); const editorRef = useRef(null); @@ -101,7 +107,23 @@ export default function Dashboard() { ); const editor = useEditor({ - extensions: extensions, + onCreate: ({ editor: currentEditor }) => { + provider.on('synced', () => { + if (currentEditor.isEmpty) { + currentEditor.commands.setContent("") + } + }) + }, + extensions: [ + ...extensions, + CollaborationCursor.configure({ + provider, + user: { + name: localStorage.getItem('name'), + color: getRandomColor + } + }), + ], editorProps: props, content: "", onUpdate({ editor }) { @@ -110,7 +132,6 @@ export default function Dashboard() { }); useEffect(() => { - console.log(docData); if (editor && docData) { editor.commands.setContent(docData); } @@ -120,7 +141,6 @@ export default function Dashboard() { , ] - const [option, setOption] = useState(0) return (
diff --git a/helpers/getRandomColor.ts b/helpers/getRandomColor.ts new file mode 100644 index 0000000..ed1c7c2 --- /dev/null +++ b/helpers/getRandomColor.ts @@ -0,0 +1,29 @@ +const colors = [ + '#958DF1', + '#F98181', + '#FBBC88', + '#FAF594', + '#70CFF8', + '#94FADB', + '#B9F18D', + '#C3E2C2', + '#EAECCC', + '#AFC8AD', + '#EEC759', + '#9BB8CD', + '#FF90BC', + '#FFC0D9', + '#DC8686', + '#7ED7C1', + '#F3EEEA', + '#89B9AD', + '#D0BFFF', + '#FFF8C9', + '#CBFFA9', + '#9BABB8', + '#E3F4F4', +] + +export const getRandomColor = () => { + return colors[Math.floor(Math.random() * colors.length)]; +} diff --git a/package-lock.json b/package-lock.json index e9ce747..c74fdd1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,10 +21,11 @@ "@radix-ui/react-tooltip": "^1.1.2", "@tanstack/react-query": "^5.50.1", "@tanstack/react-query-devtools": "^5.50.1", + "@tiptap/extension-collaboration": "^2.6.2", + "@tiptap/extension-collaboration-cursor": "^2.6.2", "@tiptap/extension-color": "^2.4.0", "@tiptap/extension-document": "^2.4.0", "@tiptap/extension-font-family": "^2.4.0", - "@tiptap/extension-heading": "^2.4.0", "@tiptap/extension-highlight": "^2.4.0", "@tiptap/extension-list-item": "^2.4.0", "@tiptap/extension-text-align": "^2.4.0", @@ -55,6 +56,10 @@ "tailwind-merge": "^2.3.0", "tailwindcss-animate": "^1.0.7", "vaul": "^0.9.1", + "y-prosemirror": "^1.2.12", + "y-webrtc": "^10.3.0", + "y-websocket": "^2.0.4", + "yjs": "^13.6.18", "zod": "^3.23.8" }, "devDependencies": { @@ -1977,15 +1982,15 @@ } }, "node_modules/@tiptap/core": { - "version": "2.5.8", - "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.5.8.tgz", - "integrity": "sha512-lkWCKyoAoMTxM137MoEsorG7tZ5MZU6O3wMRuZ0P9fcTRY5vd1NWncWuPzuGSJIpL20gwBQOsS6PaQSfR3xjlA==", + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.6.3.tgz", + "integrity": "sha512-QTFJRblUjQ0vBcL6IrEPo5gJti0FNx9ylACE1KVe45gxsOWqxbonLrDvlRQ8xNtvvDIgU6UK3CNCvNUXguU+wQ==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/pm": "^2.5.8" + "@tiptap/pm": "^2.6.3" } }, "node_modules/@tiptap/extension-blockquote": { @@ -2071,6 +2076,33 @@ "@tiptap/pm": "^2.0.0" } }, + "node_modules/@tiptap/extension-collaboration": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-collaboration/-/extension-collaboration-2.6.2.tgz", + "integrity": "sha512-QRE656MrppPEPb1q/0UO2ACRm51cv1bNLN5p802Lsp10jvD4fJUhI78LRH9h5cKkOEdUOGMh6QiYZg63kGLEcA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.6.2", + "@tiptap/pm": "^2.6.2", + "y-prosemirror": "^1.2.11" + } + }, + "node_modules/@tiptap/extension-collaboration-cursor": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-collaboration-cursor/-/extension-collaboration-cursor-2.6.2.tgz", + "integrity": "sha512-DkqVZx+bhdTh9+lTjo9qvUZ1a4wKYYIXEhM97lBe7dvkyg0BTGTnLpWb6EtFdVtoBuvSNaLs26w8z6r+GJ/j5w==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.6.2", + "y-prosemirror": "^1.2.11" + } + }, "node_modules/@tiptap/extension-color": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/@tiptap/extension-color/-/extension-color-2.4.0.tgz", @@ -2358,9 +2390,9 @@ } }, "node_modules/@tiptap/pm": { - "version": "2.5.8", - "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.5.8.tgz", - "integrity": "sha512-CVhHaTG4QNHSkvuh6HHsUR4hE+nbUnk7z+VMUedaqPU8tNqkTwWGCMbiyTc+PCsz0T9Mni7vvBR+EXgEQ3+w4g==", + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.6.3.tgz", + "integrity": "sha512-PxLYNtDzpvDFuW26Bln8umJdJa5lUrkELUbk2QemgxDmkNzlQQIa/n/2YVbW2VeFL9jcLkFbwnpPumyJTlJx4g==", "dependencies": { "prosemirror-changeset": "^2.2.1", "prosemirror-collab": "^1.3.1", @@ -2737,6 +2769,22 @@ "dev": true, "license": "ISC" }, + "node_modules/abstract-leveldown": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-6.2.3.tgz", + "integrity": "sha512-BsLm5vFMRUrrLeCcRc+G0t2qOaTzpoJQLOubq2XM72eNpjF5UdU5o/5NvlNhx95XHcAvcl8OMXr4mlg/fRgUXQ==", + "optional": true, + "dependencies": { + "buffer": "^5.5.0", + "immediate": "^3.2.3", + "level-concat-iterator": "~2.0.0", + "level-supports": "~1.0.0", + "xtend": "~4.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/acorn": { "version": "8.12.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", @@ -3042,6 +3090,12 @@ "dev": true, "license": "MIT" }, + "node_modules/async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", + "optional": true + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -3109,6 +3163,25 @@ "node": ">= 0.6.0" } }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/bcryptjs": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", @@ -3148,6 +3221,30 @@ "node": ">=8" } }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "optional": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, "node_modules/busboy": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", @@ -3546,6 +3643,19 @@ "dev": true, "license": "MIT" }, + "node_modules/deferred-leveldown": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/deferred-leveldown/-/deferred-leveldown-5.3.0.tgz", + "integrity": "sha512-a59VOT+oDy7vtAbLRCZwWgxu2BaCfd5Hk7wxJd48ei7I+nsg8Orlb9CLG0PMZienk9BSUKgeAqkO2+Lw+1+Ukw==", + "optional": true, + "dependencies": { + "abstract-leveldown": "~6.2.1", + "inherits": "^2.0.3" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -3655,6 +3765,21 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "license": "MIT" }, + "node_modules/encoding-down": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/encoding-down/-/encoding-down-6.3.0.tgz", + "integrity": "sha512-QKrV0iKR6MZVJV08QY0wp1e7vF6QbhnbQhb07bwpEyuz4uZiZgPlEGdkCROuFkUwdxlFaiPIhjyarH1ee/3vhw==", + "optional": true, + "dependencies": { + "abstract-leveldown": "^6.2.1", + "inherits": "^2.0.3", + "level-codec": "^9.0.0", + "level-errors": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/enhanced-resolve": { "version": "5.17.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.0.tgz", @@ -3681,6 +3806,23 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/err-code": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-3.0.1.tgz", + "integrity": "sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA==" + }, + "node_modules/errno": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "optional": true, + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, "node_modules/es-abstract": { "version": "1.23.3", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", @@ -4538,6 +4680,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-browser-rtc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-browser-rtc/-/get-browser-rtc-1.1.0.tgz", + "integrity": "sha512-MghbMJ61EJrRsDe7w1Bvqt3ZsBuqhce5nrn/XAwgwOXhcsz53/ltdxOse1h/8eKXj5slzxdsz56g5rzOFSGwfQ==" + }, "node_modules/get-intrinsic": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", @@ -4826,6 +4973,25 @@ "node": ">=8.0.0" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/ignore": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", @@ -4836,6 +5002,12 @@ "node": ">= 4" } }, + "node_modules/immediate": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.3.0.tgz", + "integrity": "sha512-HR7EVodfFUdQCTIeySw+WDRFJlPcLOJbXfwwZ7Oom6tjsvZ3bOkCDJHehQC3nxJrv7+f9XecwazynjU8e4Vw3Q==", + "optional": true + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -4878,7 +5050,6 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, "license": "ISC" }, "node_modules/internal-slot": { @@ -5332,6 +5503,15 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "license": "ISC" }, + "node_modules/isomorphic.js": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.2.5.tgz", + "integrity": "sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==", + "funding": { + "type": "GitHub Sponsors ❤", + "url": "https://github.com/sponsors/dmonad" + } + }, "node_modules/iterator.prototype": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.2.tgz", @@ -5500,6 +5680,139 @@ "node": ">=0.10" } }, + "node_modules/level": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/level/-/level-6.0.1.tgz", + "integrity": "sha512-psRSqJZCsC/irNhfHzrVZbmPYXDcEYhA5TVNwr+V92jF44rbf86hqGp8fiT702FyiArScYIlPSBTDUASCVNSpw==", + "optional": true, + "dependencies": { + "level-js": "^5.0.0", + "level-packager": "^5.1.0", + "leveldown": "^5.4.0" + }, + "engines": { + "node": ">=8.6.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/level" + } + }, + "node_modules/level-codec": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/level-codec/-/level-codec-9.0.2.tgz", + "integrity": "sha512-UyIwNb1lJBChJnGfjmO0OR+ezh2iVu1Kas3nvBS/BzGnx79dv6g7unpKIDNPMhfdTEGoc7mC8uAu51XEtX+FHQ==", + "optional": true, + "dependencies": { + "buffer": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/level-concat-iterator": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/level-concat-iterator/-/level-concat-iterator-2.0.1.tgz", + "integrity": "sha512-OTKKOqeav2QWcERMJR7IS9CUo1sHnke2C0gkSmcR7QuEtFNLLzHQAvnMw8ykvEcv0Qtkg0p7FOwP1v9e5Smdcw==", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/level-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/level-errors/-/level-errors-2.0.1.tgz", + "integrity": "sha512-UVprBJXite4gPS+3VznfgDSU8PTRuVX0NXwoWW50KLxd2yw4Y1t2JUR5In1itQnudZqRMT9DlAM3Q//9NCjCFw==", + "optional": true, + "dependencies": { + "errno": "~0.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/level-iterator-stream": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/level-iterator-stream/-/level-iterator-stream-4.0.2.tgz", + "integrity": "sha512-ZSthfEqzGSOMWoUGhTXdX9jv26d32XJuHz/5YnuHZzH6wldfWMOVwI9TBtKcya4BKTyTt3XVA0A3cF3q5CY30Q==", + "optional": true, + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "^3.4.0", + "xtend": "^4.0.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/level-js": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/level-js/-/level-js-5.0.2.tgz", + "integrity": "sha512-SnBIDo2pdO5VXh02ZmtAyPP6/+6YTJg2ibLtl9C34pWvmtMEmRTWpra+qO/hifkUtBTOtfx6S9vLDjBsBK4gRg==", + "optional": true, + "dependencies": { + "abstract-leveldown": "~6.2.3", + "buffer": "^5.5.0", + "inherits": "^2.0.3", + "ltgt": "^2.1.2" + } + }, + "node_modules/level-packager": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/level-packager/-/level-packager-5.1.1.tgz", + "integrity": "sha512-HMwMaQPlTC1IlcwT3+swhqf/NUO+ZhXVz6TY1zZIIZlIR0YSn8GtAAWmIvKjNY16ZkEg/JcpAuQskxsXqC0yOQ==", + "optional": true, + "dependencies": { + "encoding-down": "^6.3.0", + "levelup": "^4.3.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/level-supports": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/level-supports/-/level-supports-1.0.1.tgz", + "integrity": "sha512-rXM7GYnW8gsl1vedTJIbzOrRv85c/2uCMpiiCzO2fndd06U/kUXEEU9evYn4zFggBOg36IsBW8LzqIpETwwQzg==", + "optional": true, + "dependencies": { + "xtend": "^4.0.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/leveldown": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/leveldown/-/leveldown-5.6.0.tgz", + "integrity": "sha512-iB8O/7Db9lPaITU1aA2txU/cBEXAt4vWwKQRrrWuS6XDgbP4QZGj9BL2aNbwb002atoQ/lIotJkfyzz+ygQnUQ==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "abstract-leveldown": "~6.2.1", + "napi-macros": "~2.0.0", + "node-gyp-build": "~4.1.0" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/levelup": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/levelup/-/levelup-4.4.0.tgz", + "integrity": "sha512-94++VFO3qN95cM/d6eBXvd894oJE0w3cInq9USsyQzzoJxmiYzPAocNcuGCPGGjoXqDVJcr3C1jzt1TSjyaiLQ==", + "optional": true, + "dependencies": { + "deferred-leveldown": "~5.3.0", + "level-errors": "~2.0.0", + "level-iterator-stream": "~4.0.0", + "level-supports": "~1.0.0", + "xtend": "~4.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -5514,6 +5827,26 @@ "node": ">= 0.8.0" } }, + "node_modules/lib0": { + "version": "0.2.97", + "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.97.tgz", + "integrity": "sha512-Q4d1ekgvufi9FiHkkL46AhecfNjznSL9MRNoJRQ76gBHS9OqU2ArfQK0FvBpuxgWeJeNI0LVgAYMIpsGeX4gYg==", + "dependencies": { + "isomorphic.js": "^0.2.4" + }, + "bin": { + "0ecdsa-generate-keypair": "bin/0ecdsa-generate-keypair.js", + "0gentesthtml": "bin/gentesthtml.js", + "0serve": "bin/0serve.js" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "type": "GitHub Sponsors ❤", + "url": "https://github.com/sponsors/dmonad" + } + }, "node_modules/lilconfig": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", @@ -5566,6 +5899,11 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" + }, "node_modules/lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", @@ -5601,6 +5939,12 @@ "node": "14 || >=16.14" } }, + "node_modules/ltgt": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ltgt/-/ltgt-2.2.1.tgz", + "integrity": "sha512-AI2r85+4MquTw9ZYqabu4nMwy9Oftlfa/e/52t9IjtfG+mGBbTNdAoZ3RQKLHR6r0wQnwZnPIEh/Ya6XTWAKNA==", + "optional": true + }, "node_modules/lucide-react": { "version": "0.400.0", "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.400.0.tgz", @@ -5754,6 +6098,12 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/napi-macros": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-macros/-/napi-macros-2.0.0.tgz", + "integrity": "sha512-A0xLykHtARfueITVDernsAWdtIMbOJgKgcluwENp3AlsKN/PloyO10HtmoqnFAQAcxPkgZN7wdfPfEd0zNGxbg==", + "optional": true + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -5892,6 +6242,17 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/node-gyp-build": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.1.1.tgz", + "integrity": "sha512-dSq1xmcPDKPZ2EED2S6zw/b9NKsqzXRE6dVr8TVQnI3FJOTteUMuqF3Qqs6LZg+mLGYJWqQzMbIjMtJqTv87nQ==", + "optional": true, + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -6716,6 +7077,12 @@ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "license": "MIT" }, + "node_modules/prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", + "optional": true + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -6755,6 +7122,14 @@ ], "license": "MIT" }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, "node_modules/react": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", @@ -6892,6 +7267,19 @@ "pify": "^2.3.0" } }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -7083,6 +7471,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/safe-regex-test": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", @@ -7247,6 +7654,57 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/simple-peer": { + "version": "9.11.1", + "resolved": "https://registry.npmjs.org/simple-peer/-/simple-peer-9.11.1.tgz", + "integrity": "sha512-D1SaWpOW8afq1CZGWB8xTfrT3FekjQmPValrqncJMX7QFl8YwhrPTZvMCANLtgBwwdS+7zURyqxDDEmY558tTw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "buffer": "^6.0.3", + "debug": "^4.3.2", + "err-code": "^3.0.1", + "get-browser-rtc": "^1.1.0", + "queue-microtask": "^1.2.3", + "randombytes": "^2.1.0", + "readable-stream": "^3.6.0" + } + }, + "node_modules/simple-peer/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "node_modules/simple-swizzle": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", @@ -7305,6 +7763,14 @@ "node": ">=10.0.0" } }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", @@ -8202,6 +8668,159 @@ "dev": true, "license": "ISC" }, + "node_modules/ws": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", + "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==", + "optional": true, + "dependencies": { + "async-limiter": "~1.0.0" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "optional": true, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y-leveldb": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/y-leveldb/-/y-leveldb-0.1.2.tgz", + "integrity": "sha512-6ulEn5AXfXJYi89rXPEg2mMHAyyw8+ZfeMMdOtBbV8FJpQ1NOrcgi6DTAcXof0dap84NjHPT2+9d0rb6cFsjEg==", + "optional": true, + "dependencies": { + "level": "^6.0.1", + "lib0": "^0.2.31" + }, + "funding": { + "type": "GitHub Sponsors ❤", + "url": "https://github.com/sponsors/dmonad" + }, + "peerDependencies": { + "yjs": "^13.0.0" + } + }, + "node_modules/y-prosemirror": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/y-prosemirror/-/y-prosemirror-1.2.12.tgz", + "integrity": "sha512-UMnUIR5ppVn30n2kzeeBQEaesWGe4fsbnlch1HnNa3/snJMoOn7M7dieuS+o1OQwKs1dqx2eT3gFfGF2aOaQdw==", + "dependencies": { + "lib0": "^0.2.42" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=8.0.0" + }, + "funding": { + "type": "GitHub Sponsors ❤", + "url": "https://github.com/sponsors/dmonad" + }, + "peerDependencies": { + "prosemirror-model": "^1.7.1", + "prosemirror-state": "^1.2.3", + "prosemirror-view": "^1.9.10", + "y-protocols": "^1.0.1", + "yjs": "^13.5.38" + } + }, + "node_modules/y-protocols": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/y-protocols/-/y-protocols-1.0.6.tgz", + "integrity": "sha512-vHRF2L6iT3rwj1jub/K5tYcTT/mEYDUppgNPXwp8fmLpui9f7Yeq3OEtTLVF012j39QnV+KEQpNqoN7CWU7Y9Q==", + "dependencies": { + "lib0": "^0.2.85" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=8.0.0" + }, + "funding": { + "type": "GitHub Sponsors ❤", + "url": "https://github.com/sponsors/dmonad" + }, + "peerDependencies": { + "yjs": "^13.0.0" + } + }, + "node_modules/y-webrtc": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/y-webrtc/-/y-webrtc-10.3.0.tgz", + "integrity": "sha512-KalJr7dCgUgyVFxoG3CQYbpS0O2qybegD0vI4bYnYHI0MOwoVbucED3RZ5f2o1a5HZb1qEssUKS0H/Upc6p1lA==", + "dependencies": { + "lib0": "^0.2.42", + "simple-peer": "^9.11.0", + "y-protocols": "^1.0.6" + }, + "bin": { + "y-webrtc-signaling": "bin/server.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "GitHub Sponsors ❤", + "url": "https://github.com/sponsors/dmonad" + }, + "optionalDependencies": { + "ws": "^8.14.2" + }, + "peerDependencies": { + "yjs": "^13.6.8" + } + }, + "node_modules/y-webrtc/node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "optional": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/y-websocket": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/y-websocket/-/y-websocket-2.0.4.tgz", + "integrity": "sha512-UbrkOU4GPNFFTDlJYAxAmzZhia8EPxHkngZ6qjrxgIYCN3gI2l+zzLzA9p4LQJ0IswzpioeIgmzekWe7HoBBjg==", + "dependencies": { + "lib0": "^0.2.52", + "lodash.debounce": "^4.0.8", + "y-protocols": "^1.0.5" + }, + "bin": { + "y-websocket": "bin/server.cjs", + "y-websocket-server": "bin/server.cjs" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=8.0.0" + }, + "funding": { + "type": "GitHub Sponsors ❤", + "url": "https://github.com/sponsors/dmonad" + }, + "optionalDependencies": { + "ws": "^6.2.1", + "y-leveldb": "^0.1.0" + }, + "peerDependencies": { + "yjs": "^13.5.6" + } + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -8219,6 +8838,22 @@ "node": ">= 14" } }, + "node_modules/yjs": { + "version": "13.6.18", + "resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.18.tgz", + "integrity": "sha512-GBTjO4QCmv2HFKFkYIJl7U77hIB1o22vSCSQD1Ge8ZxWbIbn8AltI4gyXbtL+g5/GJep67HCMq3Y5AmNwDSyEg==", + "dependencies": { + "lib0": "^0.2.86" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=8.0.0" + }, + "funding": { + "type": "GitHub Sponsors ❤", + "url": "https://github.com/sponsors/dmonad" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index e4f237d..4bd89d9 100644 --- a/package.json +++ b/package.json @@ -26,10 +26,11 @@ "@radix-ui/react-tooltip": "^1.1.2", "@tanstack/react-query": "^5.50.1", "@tanstack/react-query-devtools": "^5.50.1", + "@tiptap/extension-collaboration": "^2.6.2", + "@tiptap/extension-collaboration-cursor": "^2.6.2", "@tiptap/extension-color": "^2.4.0", "@tiptap/extension-document": "^2.4.0", "@tiptap/extension-font-family": "^2.4.0", - "@tiptap/extension-heading": "^2.4.0", "@tiptap/extension-highlight": "^2.4.0", "@tiptap/extension-list-item": "^2.4.0", "@tiptap/extension-text-align": "^2.4.0", @@ -60,6 +61,10 @@ "tailwind-merge": "^2.3.0", "tailwindcss-animate": "^1.0.7", "vaul": "^0.9.1", + "y-prosemirror": "^1.2.12", + "y-webrtc": "^10.3.0", + "y-websocket": "^2.0.4", + "yjs": "^13.6.18", "zod": "^3.23.8" }, "devDependencies": { diff --git a/yarn.lock b/yarn.lock index 37dda13..6753aa3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -665,10 +665,10 @@ dependencies: "@tanstack/query-core" "5.50.1" -"@tiptap/core@^2.0.0", "@tiptap/core@^2.4.0", "@tiptap/core@^2.5.8": - version "2.5.8" - resolved "https://registry.npmjs.org/@tiptap/core/-/core-2.5.8.tgz" - integrity sha512-lkWCKyoAoMTxM137MoEsorG7tZ5MZU6O3wMRuZ0P9fcTRY5vd1NWncWuPzuGSJIpL20gwBQOsS6PaQSfR3xjlA== +"@tiptap/core@^2.0.0", "@tiptap/core@^2.4.0", "@tiptap/core@^2.5.8", "@tiptap/core@^2.6.2": + version "2.6.3" + resolved "https://registry.npmjs.org/@tiptap/core/-/core-2.6.3.tgz" + integrity sha512-QTFJRblUjQ0vBcL6IrEPo5gJti0FNx9ylACE1KVe45gxsOWqxbonLrDvlRQ8xNtvvDIgU6UK3CNCvNUXguU+wQ== "@tiptap/extension-blockquote@^2.4.0": version "2.4.0" @@ -702,6 +702,16 @@ resolved "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-2.4.0.tgz" integrity sha512-wjhBukuiyJMq4cTcK3RBTzUPV24k5n1eEPlpmzku6ThwwkMdwynnMGMAmSF3fErh3AOyOUPoTTjgMYN2d10SJA== +"@tiptap/extension-collaboration-cursor@^2.6.2": + version "2.6.2" + resolved "https://registry.npmjs.org/@tiptap/extension-collaboration-cursor/-/extension-collaboration-cursor-2.6.2.tgz" + integrity sha512-DkqVZx+bhdTh9+lTjo9qvUZ1a4wKYYIXEhM97lBe7dvkyg0BTGTnLpWb6EtFdVtoBuvSNaLs26w8z6r+GJ/j5w== + +"@tiptap/extension-collaboration@^2.6.2": + version "2.6.2" + resolved "https://registry.npmjs.org/@tiptap/extension-collaboration/-/extension-collaboration-2.6.2.tgz" + integrity sha512-QRE656MrppPEPb1q/0UO2ACRm51cv1bNLN5p802Lsp10jvD4fJUhI78LRH9h5cKkOEdUOGMh6QiYZg63kGLEcA== + "@tiptap/extension-color@^2.4.0": version "2.4.0" resolved "https://registry.npmjs.org/@tiptap/extension-color/-/extension-color-2.4.0.tgz" @@ -811,10 +821,10 @@ dependencies: zeed-dom "^0.10.9" -"@tiptap/pm@^2.0.0", "@tiptap/pm@^2.4.0", "@tiptap/pm@^2.5.8": - version "2.5.8" - resolved "https://registry.npmjs.org/@tiptap/pm/-/pm-2.5.8.tgz" - integrity sha512-CVhHaTG4QNHSkvuh6HHsUR4hE+nbUnk7z+VMUedaqPU8tNqkTwWGCMbiyTc+PCsz0T9Mni7vvBR+EXgEQ3+w4g== +"@tiptap/pm@^2.0.0", "@tiptap/pm@^2.4.0", "@tiptap/pm@^2.5.8", "@tiptap/pm@^2.6.2", "@tiptap/pm@^2.6.3": + version "2.6.3" + resolved "https://registry.npmjs.org/@tiptap/pm/-/pm-2.6.3.tgz" + integrity sha512-PxLYNtDzpvDFuW26Bln8umJdJa5lUrkELUbk2QemgxDmkNzlQQIa/n/2YVbW2VeFL9jcLkFbwnpPumyJTlJx4g== dependencies: prosemirror-changeset "^2.2.1" prosemirror-collab "^1.3.1" @@ -1001,6 +1011,17 @@ resolved "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz" integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== +abstract-leveldown@^6.2.1, abstract-leveldown@~6.2.1, abstract-leveldown@~6.2.3: + version "6.2.3" + resolved "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-6.2.3.tgz" + integrity sha512-BsLm5vFMRUrrLeCcRc+G0t2qOaTzpoJQLOubq2XM72eNpjF5UdU5o/5NvlNhx95XHcAvcl8OMXr4mlg/fRgUXQ== + dependencies: + buffer "^5.5.0" + immediate "^3.2.3" + level-concat-iterator "~2.0.0" + level-supports "~1.0.0" + xtend "~4.0.0" + acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz" @@ -1189,6 +1210,11 @@ ast-types-flow@^0.0.8: resolved "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz" integrity sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ== +async-limiter@~1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz" + integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== + asynckit@^0.4.0: version "0.4.0" resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" @@ -1232,6 +1258,11 @@ base64-arraybuffer@^1.0.2: resolved "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz" integrity sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ== +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + bcryptjs@^2.4.3: version "2.4.3" resolved "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz" @@ -1264,6 +1295,22 @@ braces@^3.0.3, braces@~3.0.2: dependencies: fill-range "^7.1.1" +buffer@^5.5.0, buffer@^5.6.0: + version "5.7.1" + resolved "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + +buffer@^6.0.3: + version "6.0.3" + resolved "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + busboy@1.6.0: version "1.6.0" resolved "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz" @@ -1508,6 +1555,14 @@ deep-is@^0.1.3: resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== +deferred-leveldown@~5.3.0: + version "5.3.0" + resolved "https://registry.npmjs.org/deferred-leveldown/-/deferred-leveldown-5.3.0.tgz" + integrity sha512-a59VOT+oDy7vtAbLRCZwWgxu2BaCfd5Hk7wxJd48ei7I+nsg8Orlb9CLG0PMZienk9BSUKgeAqkO2+Lw+1+Ukw== + dependencies: + abstract-leveldown "~6.2.1" + inherits "^2.0.3" + define-data-property@^1.0.1, define-data-property@^1.1.4: version "1.1.4" resolved "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz" @@ -1587,6 +1642,16 @@ emoji-regex@^9.2.2: resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz" integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== +encoding-down@^6.3.0: + version "6.3.0" + resolved "https://registry.npmjs.org/encoding-down/-/encoding-down-6.3.0.tgz" + integrity sha512-QKrV0iKR6MZVJV08QY0wp1e7vF6QbhnbQhb07bwpEyuz4uZiZgPlEGdkCROuFkUwdxlFaiPIhjyarH1ee/3vhw== + dependencies: + abstract-leveldown "^6.2.1" + inherits "^2.0.3" + level-codec "^9.0.0" + level-errors "^2.0.0" + enhanced-resolve@^5.12.0: version "5.17.0" resolved "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.0.tgz" @@ -1600,6 +1665,18 @@ entities@^4.4.0: resolved "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz" integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== +err-code@^3.0.1: + version "3.0.1" + resolved "https://registry.npmjs.org/err-code/-/err-code-3.0.1.tgz" + integrity sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA== + +errno@~0.1.1: + version "0.1.8" + resolved "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz" + integrity sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A== + dependencies: + prr "~1.0.1" + es-abstract@^1.17.5, es-abstract@^1.22.1, es-abstract@^1.22.3, es-abstract@^1.23.0, es-abstract@^1.23.1, es-abstract@^1.23.2, es-abstract@^1.23.3: version "1.23.3" resolved "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz" @@ -2067,6 +2144,11 @@ functions-have-names@^1.2.3: resolved "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz" integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== +get-browser-rtc@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/get-browser-rtc/-/get-browser-rtc-1.1.0.tgz" + integrity sha512-MghbMJ61EJrRsDe7w1Bvqt3ZsBuqhce5nrn/XAwgwOXhcsz53/ltdxOse1h/8eKXj5slzxdsz56g5rzOFSGwfQ== + get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.2, get-intrinsic@^1.2.3, get-intrinsic@^1.2.4: version "1.2.4" resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz" @@ -2229,11 +2311,21 @@ html2canvas@^1.4.1: css-line-break "^2.1.0" text-segmentation "^1.0.3" +ieee754@^1.1.13, ieee754@^1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + ignore@^5.2.0: version "5.3.1" resolved "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz" integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== +immediate@^3.2.3: + version "3.3.0" + resolved "https://registry.npmjs.org/immediate/-/immediate-3.3.0.tgz" + integrity sha512-HR7EVodfFUdQCTIeySw+WDRFJlPcLOJbXfwwZ7Oom6tjsvZ3bOkCDJHehQC3nxJrv7+f9XecwazynjU8e4Vw3Q== + import-fresh@^3.2.1: version "3.3.0" resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz" @@ -2255,7 +2347,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2: +inherits@^2.0.3, inherits@^2.0.4, inherits@2: version "2.0.4" resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -2481,6 +2573,11 @@ isexe@^2.0.0: resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== +isomorphic.js@^0.2.4: + version "0.2.5" + resolved "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.2.5.tgz" + integrity sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw== + iterator.prototype@^1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.2.tgz" @@ -2600,6 +2697,88 @@ language-tags@^1.0.9: dependencies: language-subtag-registry "^0.3.20" +level-codec@^9.0.0: + version "9.0.2" + resolved "https://registry.npmjs.org/level-codec/-/level-codec-9.0.2.tgz" + integrity sha512-UyIwNb1lJBChJnGfjmO0OR+ezh2iVu1Kas3nvBS/BzGnx79dv6g7unpKIDNPMhfdTEGoc7mC8uAu51XEtX+FHQ== + dependencies: + buffer "^5.6.0" + +level-concat-iterator@~2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/level-concat-iterator/-/level-concat-iterator-2.0.1.tgz" + integrity sha512-OTKKOqeav2QWcERMJR7IS9CUo1sHnke2C0gkSmcR7QuEtFNLLzHQAvnMw8ykvEcv0Qtkg0p7FOwP1v9e5Smdcw== + +level-errors@^2.0.0, level-errors@~2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/level-errors/-/level-errors-2.0.1.tgz" + integrity sha512-UVprBJXite4gPS+3VznfgDSU8PTRuVX0NXwoWW50KLxd2yw4Y1t2JUR5In1itQnudZqRMT9DlAM3Q//9NCjCFw== + dependencies: + errno "~0.1.1" + +level-iterator-stream@~4.0.0: + version "4.0.2" + resolved "https://registry.npmjs.org/level-iterator-stream/-/level-iterator-stream-4.0.2.tgz" + integrity sha512-ZSthfEqzGSOMWoUGhTXdX9jv26d32XJuHz/5YnuHZzH6wldfWMOVwI9TBtKcya4BKTyTt3XVA0A3cF3q5CY30Q== + dependencies: + inherits "^2.0.4" + readable-stream "^3.4.0" + xtend "^4.0.2" + +level-js@^5.0.0: + version "5.0.2" + resolved "https://registry.npmjs.org/level-js/-/level-js-5.0.2.tgz" + integrity sha512-SnBIDo2pdO5VXh02ZmtAyPP6/+6YTJg2ibLtl9C34pWvmtMEmRTWpra+qO/hifkUtBTOtfx6S9vLDjBsBK4gRg== + dependencies: + abstract-leveldown "~6.2.3" + buffer "^5.5.0" + inherits "^2.0.3" + ltgt "^2.1.2" + +level-packager@^5.1.0: + version "5.1.1" + resolved "https://registry.npmjs.org/level-packager/-/level-packager-5.1.1.tgz" + integrity sha512-HMwMaQPlTC1IlcwT3+swhqf/NUO+ZhXVz6TY1zZIIZlIR0YSn8GtAAWmIvKjNY16ZkEg/JcpAuQskxsXqC0yOQ== + dependencies: + encoding-down "^6.3.0" + levelup "^4.3.2" + +level-supports@~1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/level-supports/-/level-supports-1.0.1.tgz" + integrity sha512-rXM7GYnW8gsl1vedTJIbzOrRv85c/2uCMpiiCzO2fndd06U/kUXEEU9evYn4zFggBOg36IsBW8LzqIpETwwQzg== + dependencies: + xtend "^4.0.2" + +level@^6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/level/-/level-6.0.1.tgz" + integrity sha512-psRSqJZCsC/irNhfHzrVZbmPYXDcEYhA5TVNwr+V92jF44rbf86hqGp8fiT702FyiArScYIlPSBTDUASCVNSpw== + dependencies: + level-js "^5.0.0" + level-packager "^5.1.0" + leveldown "^5.4.0" + +leveldown@^5.4.0: + version "5.6.0" + resolved "https://registry.npmjs.org/leveldown/-/leveldown-5.6.0.tgz" + integrity sha512-iB8O/7Db9lPaITU1aA2txU/cBEXAt4vWwKQRrrWuS6XDgbP4QZGj9BL2aNbwb002atoQ/lIotJkfyzz+ygQnUQ== + dependencies: + abstract-leveldown "~6.2.1" + napi-macros "~2.0.0" + node-gyp-build "~4.1.0" + +levelup@^4.3.2: + version "4.4.0" + resolved "https://registry.npmjs.org/levelup/-/levelup-4.4.0.tgz" + integrity sha512-94++VFO3qN95cM/d6eBXvd894oJE0w3cInq9USsyQzzoJxmiYzPAocNcuGCPGGjoXqDVJcr3C1jzt1TSjyaiLQ== + dependencies: + deferred-leveldown "~5.3.0" + level-errors "~2.0.0" + level-iterator-stream "~4.0.0" + level-supports "~1.0.0" + xtend "~4.0.0" + levn@^0.4.1: version "0.4.1" resolved "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz" @@ -2608,6 +2787,13 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" +lib0@^0.2.31, lib0@^0.2.42, lib0@^0.2.52, lib0@^0.2.85, lib0@^0.2.86: + version "0.2.97" + resolved "https://registry.npmjs.org/lib0/-/lib0-0.2.97.tgz" + integrity sha512-Q4d1ekgvufi9FiHkkL46AhecfNjznSL9MRNoJRQ76gBHS9OqU2ArfQK0FvBpuxgWeJeNI0LVgAYMIpsGeX4gYg== + dependencies: + isomorphic.js "^0.2.4" + lilconfig@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz" @@ -2642,6 +2828,11 @@ lodash.castarray@^4.4.0: resolved "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz" integrity sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q== +lodash.debounce@^4.0.8: + version "4.0.8" + resolved "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz" + integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== + lodash.isplainobject@^4.0.6: version "4.0.6" resolved "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz" @@ -2676,6 +2867,11 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" +ltgt@^2.1.2: + version "2.2.1" + resolved "https://registry.npmjs.org/ltgt/-/ltgt-2.2.1.tgz" + integrity sha512-AI2r85+4MquTw9ZYqabu4nMwy9Oftlfa/e/52t9IjtfG+mGBbTNdAoZ3RQKLHR6r0wQnwZnPIEh/Ya6XTWAKNA== + lucide-react@^0.400.0: version "0.400.0" resolved "https://registry.npmjs.org/lucide-react/-/lucide-react-0.400.0.tgz" @@ -2780,6 +2976,11 @@ nanoid@^3.3.6, nanoid@^3.3.7: resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz" integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== +napi-macros@~2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/napi-macros/-/napi-macros-2.0.0.tgz" + integrity sha512-A0xLykHtARfueITVDernsAWdtIMbOJgKgcluwENp3AlsKN/PloyO10HtmoqnFAQAcxPkgZN7wdfPfEd0zNGxbg== + natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz" @@ -2828,6 +3029,11 @@ next-themes@^0.3.0: "@next/swc-win32-ia32-msvc" "14.2.4" "@next/swc-win32-x64-msvc" "14.2.4" +node-gyp-build@~4.1.0: + version "4.1.1" + resolved "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.1.1.tgz" + integrity sha512-dSq1xmcPDKPZ2EED2S6zw/b9NKsqzXRE6dVr8TVQnI3FJOTteUMuqF3Qqs6LZg+mLGYJWqQzMbIjMtJqTv87nQ== + normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz" @@ -3244,7 +3450,7 @@ prosemirror-menu@^1.2.4: prosemirror-history "^1.0.0" prosemirror-state "^1.0.0" -prosemirror-model@^1.0.0, prosemirror-model@^1.19.0, prosemirror-model@^1.20.0, prosemirror-model@^1.21.0, prosemirror-model@^1.22.1, prosemirror-model@^1.22.2, prosemirror-model@^1.8.1: +prosemirror-model@^1.0.0, prosemirror-model@^1.19.0, prosemirror-model@^1.20.0, prosemirror-model@^1.21.0, prosemirror-model@^1.22.1, prosemirror-model@^1.22.2, prosemirror-model@^1.7.1, prosemirror-model@^1.8.1: version "1.22.2" resolved "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.22.2.tgz" integrity sha512-I4lS7HHIW47D0Xv/gWmi4iUWcQIDYaJKd8Hk4+lcSps+553FlQrhmxtItpEvTr75iAruhzVShVp6WUwsT6Boww== @@ -3267,7 +3473,7 @@ prosemirror-schema-list@^1.4.1: prosemirror-state "^1.0.0" prosemirror-transform "^1.7.3" -prosemirror-state@^1.0.0, prosemirror-state@^1.2.2, prosemirror-state@^1.3.1, prosemirror-state@^1.4.2, prosemirror-state@^1.4.3: +prosemirror-state@^1.0.0, prosemirror-state@^1.2.2, prosemirror-state@^1.2.3, prosemirror-state@^1.3.1, prosemirror-state@^1.4.2, prosemirror-state@^1.4.3: version "1.4.3" resolved "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.3.tgz" integrity sha512-goFKORVbvPuAQaXhpbemJFRKJ2aixr+AZMGiquiqKxaucC6hlpHNZHWgz5R7dS4roHiwq9vDctE//CZ++o0W1Q== @@ -3302,7 +3508,7 @@ prosemirror-transform@^1.0.0, prosemirror-transform@^1.1.0, prosemirror-transfor dependencies: prosemirror-model "^1.21.0" -prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.13.3, prosemirror-view@^1.27.0, prosemirror-view@^1.31.0, prosemirror-view@^1.33.8, prosemirror-view@^1.33.9: +prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.13.3, prosemirror-view@^1.27.0, prosemirror-view@^1.31.0, prosemirror-view@^1.33.8, prosemirror-view@^1.33.9, prosemirror-view@^1.9.10: version "1.33.9" resolved "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.33.9.tgz" integrity sha512-xV1A0Vz9cIcEnwmMhKKFAOkfIp8XmJRnaZoPqNXrPS7EK5n11Ov8V76KhR0RsfQd/SIzmWY+bg+M44A2Lx/Nnw== @@ -3316,6 +3522,11 @@ proxy-from-env@^1.1.0: resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz" integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== +prr@~1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz" + integrity sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw== + punycode.js@^2.3.1: version "2.3.1" resolved "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz" @@ -3326,11 +3537,18 @@ punycode@^2.1.0: resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz" integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== -queue-microtask@^1.2.2: +queue-microtask@^1.2.2, queue-microtask@^1.2.3: version "1.2.3" resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== +randombytes@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + react-colorful@^5.6.1: version "5.6.1" resolved "https://registry.npmjs.org/react-colorful/-/react-colorful-5.6.1.tgz" @@ -3396,6 +3614,15 @@ read-cache@^1.0.0: dependencies: pify "^2.3.0" +readable-stream@^3.4.0, readable-stream@^3.6.0: + version "3.6.2" + resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + readdirp@~3.6.0: version "3.6.0" resolved "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz" @@ -3493,6 +3720,11 @@ safe-array-concat@^1.1.2: has-symbols "^1.0.3" isarray "^2.0.5" +safe-buffer@^5.1.0, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + safe-regex-test@^1.0.3: version "1.0.3" resolved "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz" @@ -3597,6 +3829,19 @@ signal-exit@^4.0.1: resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz" integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== +simple-peer@^9.11.0: + version "9.11.1" + resolved "https://registry.npmjs.org/simple-peer/-/simple-peer-9.11.1.tgz" + integrity sha512-D1SaWpOW8afq1CZGWB8xTfrT3FekjQmPValrqncJMX7QFl8YwhrPTZvMCANLtgBwwdS+7zURyqxDDEmY558tTw== + dependencies: + buffer "^6.0.3" + debug "^4.3.2" + err-code "^3.0.1" + get-browser-rtc "^1.1.0" + queue-microtask "^1.2.3" + randombytes "^2.1.0" + readable-stream "^3.6.0" + simple-swizzle@^0.2.2: version "0.2.2" resolved "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz" @@ -3631,6 +3876,13 @@ streamsearch@^1.1.0: resolved "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz" integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + "string-width-cjs@npm:string-width@^4.2.0": version "4.2.3" resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" @@ -3993,7 +4245,7 @@ use-sidecar@^1.1.2: detect-node-es "^1.1.0" tslib "^2.0.0" -util-deprecate@^1.0.2: +util-deprecate@^1.0.1, util-deprecate@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== @@ -4107,6 +4359,68 @@ wrappy@1: resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== +ws@^6.2.1: + version "6.2.3" + resolved "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz" + integrity sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA== + dependencies: + async-limiter "~1.0.0" + +ws@^8.14.2: + version "8.18.0" + resolved "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz" + integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== + +xtend@^4.0.2, xtend@~4.0.0: + version "4.0.2" + resolved "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + +y-leveldb@^0.1.0: + version "0.1.2" + resolved "https://registry.npmjs.org/y-leveldb/-/y-leveldb-0.1.2.tgz" + integrity sha512-6ulEn5AXfXJYi89rXPEg2mMHAyyw8+ZfeMMdOtBbV8FJpQ1NOrcgi6DTAcXof0dap84NjHPT2+9d0rb6cFsjEg== + dependencies: + level "^6.0.1" + lib0 "^0.2.31" + +y-prosemirror@^1.2.11, y-prosemirror@^1.2.12: + version "1.2.12" + resolved "https://registry.npmjs.org/y-prosemirror/-/y-prosemirror-1.2.12.tgz" + integrity sha512-UMnUIR5ppVn30n2kzeeBQEaesWGe4fsbnlch1HnNa3/snJMoOn7M7dieuS+o1OQwKs1dqx2eT3gFfGF2aOaQdw== + dependencies: + lib0 "^0.2.42" + +y-protocols@^1.0.1, y-protocols@^1.0.5, y-protocols@^1.0.6: + version "1.0.6" + resolved "https://registry.npmjs.org/y-protocols/-/y-protocols-1.0.6.tgz" + integrity sha512-vHRF2L6iT3rwj1jub/K5tYcTT/mEYDUppgNPXwp8fmLpui9f7Yeq3OEtTLVF012j39QnV+KEQpNqoN7CWU7Y9Q== + dependencies: + lib0 "^0.2.85" + +y-webrtc@^10.3.0: + version "10.3.0" + resolved "https://registry.npmjs.org/y-webrtc/-/y-webrtc-10.3.0.tgz" + integrity sha512-KalJr7dCgUgyVFxoG3CQYbpS0O2qybegD0vI4bYnYHI0MOwoVbucED3RZ5f2o1a5HZb1qEssUKS0H/Upc6p1lA== + dependencies: + lib0 "^0.2.42" + simple-peer "^9.11.0" + y-protocols "^1.0.6" + optionalDependencies: + ws "^8.14.2" + +y-websocket@^2.0.4: + version "2.0.4" + resolved "https://registry.npmjs.org/y-websocket/-/y-websocket-2.0.4.tgz" + integrity sha512-UbrkOU4GPNFFTDlJYAxAmzZhia8EPxHkngZ6qjrxgIYCN3gI2l+zzLzA9p4LQJ0IswzpioeIgmzekWe7HoBBjg== + dependencies: + lib0 "^0.2.52" + lodash.debounce "^4.0.8" + y-protocols "^1.0.5" + optionalDependencies: + ws "^6.2.1" + y-leveldb "^0.1.0" + yallist@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz" @@ -4117,6 +4431,13 @@ yaml@^2.3.4: resolved "https://registry.npmjs.org/yaml/-/yaml-2.4.5.tgz" integrity sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg== +yjs@^13.0.0, yjs@^13.5.38, yjs@^13.5.6, yjs@^13.6.18, yjs@^13.6.8: + version "13.6.18" + resolved "https://registry.npmjs.org/yjs/-/yjs-13.6.18.tgz" + integrity sha512-GBTjO4QCmv2HFKFkYIJl7U77hIB1o22vSCSQD1Ge8ZxWbIbn8AltI4gyXbtL+g5/GJep67HCMq3Y5AmNwDSyEg== + dependencies: + lib0 "^0.2.86" + yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz" From bd19817b73aaaad88c1fac4269cf05e73d3352c2 Mon Sep 17 00:00:00 2001 From: Priyanshu Date: Thu, 15 Aug 2024 23:40:11 +0530 Subject: [PATCH 2/8] Sharing feature --- app/actions.ts | 10 +++- app/components/Card/components/Options.tsx | 22 +++++++-- app/writer/[id]/actions.ts | 49 +++++++++++++++---- .../options/format/ColorHighlight.tsx | 2 +- 4 files changed, 66 insertions(+), 17 deletions(-) diff --git a/app/actions.ts b/app/actions.ts index 740d153..992b987 100644 --- a/app/actions.ts +++ b/app/actions.ts @@ -6,7 +6,15 @@ export const GetAllDocs = async (userId: string) => { try { const response = await prisma.document.findMany( { - where: { userId }, + where: { + users: { + some: { + user: { + id: userId + } + } + } + }, select: { id: true, thumbnail: true, diff --git a/app/components/Card/components/Options.tsx b/app/components/Card/components/Options.tsx index d486543..85bd936 100644 --- a/app/components/Card/components/Options.tsx +++ b/app/components/Card/components/Options.tsx @@ -26,6 +26,7 @@ import { Button } from "@/components/ui/button" import { DeleteDocument } from "../actions" import useClientSession from "@/lib/customHooks/useClientSession" import LoaderButton from "@/components/LoaderButton" +import { Jua } from "next/font/google" type CardOptionsPropType = { docId: string, @@ -40,12 +41,12 @@ export default function CardOptions({ docId, inputRef }: CardOptionsPropType) { const docOptions = [ { icon: Type, color: "#60b7c3", title: "Rename", onClick: () => renameDocument() }, { icon: FilePenLine, color: "#48acf9", title: "Edit", onClick: () => editDocument() }, - { icon: Share2, color: "#48f983", title: "Share", onClick: () => console.log("edit") }, + { icon: Share2, color: "#48f983", title: "Share", onClick: () => shareDocument() }, { icon: Trash2, color: "#f94848", title: "Delete", onClick: () => deleteDocument() }, ] const [isOptionsOpen, setIsOptionsOpen] = useState(false); - const [isOpen, setIsOpen] = useState(false); + const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); const [isDeleting, setIsDeleting] = useState(false); const renameDocument = () => { @@ -58,9 +59,20 @@ export default function CardOptions({ docId, inputRef }: CardOptionsPropType) { router.push(`/writer/${docId}`) } + const shareDocument = () => { + navigator.clipboard.writeText(`${process.env.NEXT_PUBLIC_APP_URL}/writer/${docId}`).then(() => { + toast.success('Share link copied to clipboard'); + }).catch(e => { + console.log(e) + toast.error(e); + }).finally(() => { + setIsOptionsOpen(false); + }); + } + const deleteDocument = async () => { setIsOptionsOpen(false); - setIsOpen(true); + setIsDeleteModalOpen(true); } const confirmDeleteDocument = async () => { @@ -102,7 +114,7 @@ export default function CardOptions({ docId, inputRef }: CardOptionsPropType) { - + Delete Document? @@ -111,7 +123,7 @@ export default function CardOptions({ docId, inputRef }: CardOptionsPropType) {
- + { +export const GetDocDetails = async (id: any) => { + const session = await getServerSession(); + if (!session.id) return { + success: false, + error: "User is not logged in", + } + try { - const doc = await prisma.document.findFirst({ where: { id, userId } }) + const doc = await prisma.document.update({ + where: { id }, + data: { + users: { + upsert: { + where: { + userId_documentId: { + userId: session.id, + documentId: id, + }, + }, + update: {}, + create: { + user: { + connect: { + id: session.id + } + } + } + }, + }, + } + }) + if (!doc) return { success: false, error: "Document does not exist", @@ -26,14 +56,13 @@ export const UpdateDocData = async (id: any, userId: string, data: string) => { error: "Document does not exist", } - await prisma.document.update( - { - where: { id, userId }, - data: { - data: data, - updatedAt: new Date(), - } - }) + await prisma.document.update({ + where: { id, userId }, + data: { + data: data, + updatedAt: new Date(), + } + }) return { success: true, data: "Saved" } } catch (e) { diff --git a/app/writer/[id]/components/options/format/ColorHighlight.tsx b/app/writer/[id]/components/options/format/ColorHighlight.tsx index 2a1c3b1..695ad42 100644 --- a/app/writer/[id]/components/options/format/ColorHighlight.tsx +++ b/app/writer/[id]/components/options/format/ColorHighlight.tsx @@ -1,6 +1,6 @@ 'use client' -import { useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { Editor } from "@tiptap/react"; import { Baseline, From 5bdfcb46539666d366fb274c2f9e87cdbd67a344 Mon Sep 17 00:00:00 2001 From: Priyanshu Date: Fri, 16 Aug 2024 21:46:43 +0530 Subject: [PATCH 3/8] peer2peer connection b/w shared document --- app/(auth)/actions.ts | 18 +-- app/writer/[id]/editor/editorConfig.ts | 13 ++- app/writer/[id]/page.tsx | 14 +-- helpers/generateJWT.ts | 15 +++ package-lock.json | 145 ++++++++++++++++++++++++- package.json | 3 + yarn.lock | 115 +++++++++++++++++++- 7 files changed, 288 insertions(+), 35 deletions(-) create mode 100644 helpers/generateJWT.ts diff --git a/app/(auth)/actions.ts b/app/(auth)/actions.ts index f2b1993..2e3c950 100644 --- a/app/(auth)/actions.ts +++ b/app/(auth)/actions.ts @@ -2,26 +2,12 @@ import { cookies } from "next/headers" import { z } from 'zod'; -import { JWTPayload, SignJWT, importJWK } from 'jose'; // @ts-ignore import bcrypt from 'bcryptjs'; import prisma from "@/prisma/prismaClient"; import { signinSchema, signupSchema } from './zodSchema'; - -const generateJWT = async (payload: JWTPayload) => { - const secret = process.env.JWT_SECRET || 'secret'; - - const jwk = await importJWK({ k: secret, alg: 'HS256', kty: 'oct' }); - - const jwt = await new SignJWT(payload) - .setProtectedHeader({ alg: 'HS256' }) - .setIssuedAt() - .setExpirationTime('365d') - .sign(jwk); - - return jwt; -}; +import { generateJWT } from "@/helpers/generateJWT"; export const SignupAction = async (data: z.infer) => { try { @@ -51,7 +37,7 @@ export const SignupAction = async (data: z.infer) => { await prisma.user.upsert({ where: { email: data.email }, update: { - password: hashedPassword + password: hashedPassword }, create: { name: data.name, diff --git a/app/writer/[id]/editor/editorConfig.ts b/app/writer/[id]/editor/editorConfig.ts index 14e001d..b699cc8 100644 --- a/app/writer/[id]/editor/editorConfig.ts +++ b/app/writer/[id]/editor/editorConfig.ts @@ -10,7 +10,6 @@ import TextStyle from '@tiptap/extension-text-style' import * as Y from 'yjs' import { cn } from '@/lib/utils' -import getServerSession from '@/lib/customHooks/getServerSession' export const ydoc = new Y.Doc(); @@ -19,11 +18,13 @@ const room = `room.${new Date() .toString() .slice(-2)}${new Date().getMonth() + 1}${new Date().getDate()}` -export const provider = new WebrtcProvider(room, ydoc); - -export const getSession = async () => { - return await getServerSession(); -} +export const provider = new WebrtcProvider( + room, + ydoc, + { + signaling: [process.env.NEXT_PUBLIC_SIGNALLING_URL as string] + } +); export const extensions = [ StarterKit.configure({ diff --git a/app/writer/[id]/page.tsx b/app/writer/[id]/page.tsx index adb1244..81f96d6 100644 --- a/app/writer/[id]/page.tsx +++ b/app/writer/[id]/page.tsx @@ -46,7 +46,7 @@ export default function Dashboard() { const { data } = useQuery({ queryKey: ["doc-details", params.id], queryFn: async () => { - const response = await GetDocDetails(params.id, session.id!); + const response = await GetDocDetails(params.id); if (response.success) { if (response.data?.data) { setDocData(JSON.parse(response.data?.data)); @@ -127,15 +127,15 @@ export default function Dashboard() { editorProps: props, content: "", onUpdate({ editor }) { - debouncedSaveDoc(editor); + // debouncedSaveDoc(editor); }, }); - useEffect(() => { - if (editor && docData) { - editor.commands.setContent(docData); - } - }, [docData, editor]); + // useEffect(() => { + // if (editor && docData) { + // editor.commands.setContent(docData); + // } + // }, [docData, editor]); const Options = [ , diff --git a/helpers/generateJWT.ts b/helpers/generateJWT.ts new file mode 100644 index 0000000..69d3955 --- /dev/null +++ b/helpers/generateJWT.ts @@ -0,0 +1,15 @@ +import { JWTPayload, SignJWT, importJWK } from 'jose'; + +export const generateJWT = async (payload: JWTPayload) => { + const secret = process.env.JWT_SECRET || 'secret'; + + const jwk = await importJWK({ k: secret, alg: 'HS256', kty: 'oct' }); + + const jwt = await new SignJWT(payload) + .setProtectedHeader({ alg: 'HS256' }) + .setIssuedAt() + .setExpirationTime('365d') + .sign(jwk); + + return jwt; +}; diff --git a/package-lock.json b/package-lock.json index c74fdd1..afad8bc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0", "dependencies": { "@auth0/nextjs-auth0": "^3.5.0", + "@hocuspocus/provider": "^2.13.5", "@prisma/client": "^5.16.2", "@radix-ui/react-avatar": "^1.1.0", "@radix-ui/react-dialog": "^1.1.1", @@ -35,12 +36,14 @@ "@tiptap/pm": "^2.4.0", "@tiptap/react": "^2.4.0", "@tiptap/starter-kit": "^2.4.0", + "@types/jsonwebtoken": "^9.0.6", "axios": "^1.7.2", "bcryptjs": "^2.4.3", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "html2canvas": "^1.4.1", "jose": "^5.6.3", + "jsonwebtoken": "^9.0.2", "jwt-decode": "^4.0.0", "lodash": "^4.17.21", "lucide-react": "^0.400.0", @@ -251,6 +254,49 @@ "@hapi/hoek": "^9.0.0" } }, + "node_modules/@hocuspocus/common": { + "version": "2.13.5", + "resolved": "https://registry.npmjs.org/@hocuspocus/common/-/common-2.13.5.tgz", + "integrity": "sha512-8D9FzhZFlt0WsgXw5yT2zwSxi6z9d4V2vUz6co2vo3Cj+Y2bvGZsdDiTvU/MerGcCLME5k/w6PwLPojLYH/4pg==", + "dependencies": { + "lib0": "^0.2.87" + } + }, + "node_modules/@hocuspocus/provider": { + "version": "2.13.5", + "resolved": "https://registry.npmjs.org/@hocuspocus/provider/-/provider-2.13.5.tgz", + "integrity": "sha512-G3S0OiFSYkmbOwnbhV7FyJs4OBqB/+1YT9c44Ujux1RKowGm5H8+0p3FUHfXwd/3v9V0jE+E1FnFKoGonJSQwA==", + "dependencies": { + "@hocuspocus/common": "^2.13.5", + "@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/@hocuspocus/provider/node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", @@ -810,6 +856,11 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "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==" + }, "node_modules/@next/env": { "version": "14.2.4", "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.4.tgz", @@ -2476,6 +2527,14 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.6.tgz", + "integrity": "sha512-/5hndP5dCjloafCXns6SZyESp3Ldq7YjH3zwzwczYnjxIT0Fqzk5ROSYVGfFyczIue7IUEj8hkvLbPoLQ18vQw==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/lodash": { "version": "4.17.6", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.6.tgz", @@ -2486,7 +2545,6 @@ "version": "20.14.9", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.9.tgz", "integrity": "sha512-06OCtnTXtWOZBJlRApleWndH4JsRVs1pDCc8dLSQp+7PpUpX3ePdHyeNSFTeSe7FtKyQkrlPvHwJOW3SLd8Oyg==", - "dev": true, "license": "MIT", "dependencies": { "undici-types": "~5.26.4" @@ -3245,6 +3303,11 @@ "ieee754": "^1.1.13" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, "node_modules/busboy": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", @@ -3759,6 +3822,14 @@ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "license": "MIT" }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -5626,6 +5697,27 @@ "json5": "lib/cli.js" } }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", @@ -5642,6 +5734,25 @@ "node": ">=4.0" } }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/jwt-decode": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", @@ -5904,13 +6015,37 @@ "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + }, "node_modules/lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", - "dev": true, "license": "MIT" }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -5918,6 +6053,11 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -8363,7 +8503,6 @@ "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true, "license": "MIT" }, "node_modules/uri-js": { diff --git a/package.json b/package.json index 4bd89d9..c2ed219 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ }, "dependencies": { "@auth0/nextjs-auth0": "^3.5.0", + "@hocuspocus/provider": "^2.13.5", "@prisma/client": "^5.16.2", "@radix-ui/react-avatar": "^1.1.0", "@radix-ui/react-dialog": "^1.1.1", @@ -40,12 +41,14 @@ "@tiptap/pm": "^2.4.0", "@tiptap/react": "^2.4.0", "@tiptap/starter-kit": "^2.4.0", + "@types/jsonwebtoken": "^9.0.6", "axios": "^1.7.2", "bcryptjs": "^2.4.3", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "html2canvas": "^1.4.1", "jose": "^5.6.3", + "jsonwebtoken": "^9.0.2", "jwt-decode": "^4.0.0", "lodash": "^4.17.21", "lucide-react": "^0.400.0", diff --git a/yarn.lock b/yarn.lock index 6753aa3..0a5a982 100644 --- a/yarn.lock +++ b/yarn.lock @@ -100,6 +100,23 @@ dependencies: "@hapi/hoek" "^9.0.0" +"@hocuspocus/common@^2.13.5": + version "2.13.5" + resolved "https://registry.npmjs.org/@hocuspocus/common/-/common-2.13.5.tgz" + integrity sha512-8D9FzhZFlt0WsgXw5yT2zwSxi6z9d4V2vUz6co2vo3Cj+Y2bvGZsdDiTvU/MerGcCLME5k/w6PwLPojLYH/4pg== + dependencies: + lib0 "^0.2.87" + +"@hocuspocus/provider@^2.13.5": + version "2.13.5" + resolved "https://registry.npmjs.org/@hocuspocus/provider/-/provider-2.13.5.tgz" + integrity sha512-G3S0OiFSYkmbOwnbhV7FyJs4OBqB/+1YT9c44Ujux1RKowGm5H8+0p3FUHfXwd/3v9V0jE+E1FnFKoGonJSQwA== + dependencies: + "@hocuspocus/common" "^2.13.5" + "@lifeomic/attempt" "^3.0.2" + lib0 "^0.2.87" + ws "^8.17.1" + "@humanwhocodes/config-array@^0.11.14": version "0.11.14" resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz" @@ -163,6 +180,11 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" +"@lifeomic/attempt@^3.0.2": + version "3.1.0" + resolved "https://registry.npmjs.org/@lifeomic/attempt/-/attempt-3.1.0.tgz" + integrity sha512-QZqem4QuAnAyzfz+Gj5/+SLxqwCAw2qmt7732ZXodr6VDWGeYLG6w1i/vYLa55JQM9wRuBKLmXmiZ2P0LtE5rw== + "@next/env@14.2.4": version "14.2.4" resolved "https://registry.npmjs.org/@next/env/-/env-14.2.4.tgz" @@ -883,12 +905,19 @@ resolved "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz" integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== +"@types/jsonwebtoken@^9.0.6": + version "9.0.6" + resolved "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.6.tgz" + integrity sha512-/5hndP5dCjloafCXns6SZyESp3Ldq7YjH3zwzwczYnjxIT0Fqzk5ROSYVGfFyczIue7IUEj8hkvLbPoLQ18vQw== + dependencies: + "@types/node" "*" + "@types/lodash@^4.17.6": version "4.17.6" resolved "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.6.tgz" integrity sha512-OpXEVoCKSS3lQqjx9GGGOapBeuW5eUboYHRlHP9urXPX25IKZ6AnP5ZRxtVf63iieUbsHxLn8NQ5Nlftc6yzAA== -"@types/node@^20": +"@types/node@*", "@types/node@^20": version "20.14.9" resolved "https://registry.npmjs.org/@types/node/-/node-20.14.9.tgz" integrity sha512-06OCtnTXtWOZBJlRApleWndH4JsRVs1pDCc8dLSQp+7PpUpX3ePdHyeNSFTeSe7FtKyQkrlPvHwJOW3SLd8Oyg== @@ -1295,6 +1324,11 @@ braces@^3.0.3, braces@~3.0.2: dependencies: fill-range "^7.1.1" +buffer-equal-constant-time@1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz" + integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA== + buffer@^5.5.0, buffer@^5.6.0: version "5.7.1" resolved "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz" @@ -1632,6 +1666,13 @@ eastasianwidth@^0.2.0: resolved "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz" integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== +ecdsa-sig-formatter@1.0.11: + version "1.0.11" + resolved "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz" + integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== + dependencies: + safe-buffer "^5.0.1" + emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz" @@ -2663,6 +2704,22 @@ json5@^1.0.2: dependencies: minimist "^1.2.0" +jsonwebtoken@^9.0.2: + version "9.0.2" + resolved "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz" + integrity sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ== + dependencies: + jws "^3.2.2" + lodash.includes "^4.3.0" + lodash.isboolean "^3.0.3" + lodash.isinteger "^4.0.4" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.once "^4.0.0" + ms "^2.1.1" + semver "^7.5.4" + "jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.3.5: version "3.3.5" resolved "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz" @@ -2673,6 +2730,23 @@ json5@^1.0.2: object.assign "^4.1.4" object.values "^1.1.6" +jwa@^1.4.1: + version "1.4.1" + resolved "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz" + integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== + dependencies: + buffer-equal-constant-time "1.0.1" + ecdsa-sig-formatter "1.0.11" + safe-buffer "^5.0.1" + +jws@^3.2.2: + version "3.2.2" + resolved "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz" + integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== + dependencies: + jwa "^1.4.1" + safe-buffer "^5.0.1" + jwt-decode@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz" @@ -2787,7 +2861,7 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" -lib0@^0.2.31, lib0@^0.2.42, lib0@^0.2.52, lib0@^0.2.85, lib0@^0.2.86: +lib0@^0.2.31, lib0@^0.2.42, lib0@^0.2.52, lib0@^0.2.85, lib0@^0.2.86, lib0@^0.2.87: version "0.2.97" resolved "https://registry.npmjs.org/lib0/-/lib0-0.2.97.tgz" integrity sha512-Q4d1ekgvufi9FiHkkL46AhecfNjznSL9MRNoJRQ76gBHS9OqU2ArfQK0FvBpuxgWeJeNI0LVgAYMIpsGeX4gYg== @@ -2833,16 +2907,46 @@ lodash.debounce@^4.0.8: resolved "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz" integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== +lodash.includes@^4.3.0: + version "4.3.0" + resolved "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz" + integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w== + +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz" + integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg== + +lodash.isinteger@^4.0.4: + version "4.0.4" + resolved "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz" + integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA== + +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz" + integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw== + lodash.isplainobject@^4.0.6: version "4.0.6" resolved "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz" integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz" + integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw== + lodash.merge@^4.6.2: version "4.6.2" resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== +lodash.once@^4.0.0: + version "4.1.1" + resolved "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz" + integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg== + lodash@^4.17.21: version "4.17.21" resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" @@ -3720,7 +3824,7 @@ safe-array-concat@^1.1.2: has-symbols "^1.0.3" isarray "^2.0.5" -safe-buffer@^5.1.0, safe-buffer@~5.2.0: +safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -4371,6 +4475,11 @@ ws@^8.14.2: resolved "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz" integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== +ws@^8.17.1: + version "8.18.0" + resolved "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz" + integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== + xtend@^4.0.2, xtend@~4.0.0: version "4.0.2" resolved "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz" From 540aa4acb13c6550377dde4e82ce1be520d47234 Mon Sep 17 00:00:00 2001 From: Priyanshu Date: Sat, 17 Aug 2024 10:21:57 +0530 Subject: [PATCH 4/8] Avatar group --- app/components/Card/Card.tsx | 27 +++++------------------ components/AvatarList.tsx | 42 ++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 22 deletions(-) create mode 100644 components/AvatarList.tsx diff --git a/app/components/Card/Card.tsx b/app/components/Card/Card.tsx index cc081e2..bb58468 100644 --- a/app/components/Card/Card.tsx +++ b/app/components/Card/Card.tsx @@ -2,20 +2,20 @@ import { useRef, useState } from "react" import { useRouter } from "next/navigation" +import { CircleCheck } from "lucide-react" import { Card, CardContent, CardFooter, } from "@/components/ui/card" -import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" import { Input } from "@/components/ui/input" -import prettifyDate from '@/helpers/prettifyDates' -import CardOptions from "./components/Options" import { RenameDocument } from "./actions" +import AvatarList from '@/components/AvatarList' +import CardOptions from "./components/Options" +import prettifyDate from '@/helpers/prettifyDates' import useClientSession from "@/lib/customHooks/useClientSession" -import { CircleCheck } from "lucide-react" type DocCardPropType = { docId: string; @@ -46,13 +46,6 @@ export default function DocCard({ const [name, setName] = useState(title) - const getInitials = (name: string) => { - let initials = name.split(" "); - - if (initials.length > 2) return initials[0][0] + initials[1][0]; - return initials[0][0]; - } - return (
- {users.map((e, index) => { - return ( - - { - e.user.picture - ? - : {getInitials(e.user.name)} - } - - ) - })} +

{prettifyDate(String(updatedAt), { year: "numeric", diff --git a/components/AvatarList.tsx b/components/AvatarList.tsx new file mode 100644 index 0000000..f0a4a26 --- /dev/null +++ b/components/AvatarList.tsx @@ -0,0 +1,42 @@ +import { + Avatar, + AvatarFallback, + AvatarImage +} from "@/components/ui/avatar" + +type AvatarListPropType = { + users: { + user: + { + name: string, + picture: string | null + } + }[] +} + +const getInitials = (name: string) => { + let initials = name.split(" "); + + if (initials.length > 2) return initials[0][0] + initials[1][0]; + return initials[0][0]; +} + +export default function AvatarList({ users }: AvatarListPropType) { + return ( +

+ { + users.map((e, index) => { + return ( + + { + e.user.picture + ? + : {getInitials(e.user.name)} + } + + ) + }) + } +
+ ) +} From 3ec542f5ccd679e9789655e4fe08db84a97c08b0 Mon Sep 17 00:00:00 2001 From: Priyanshu Date: Sun, 18 Aug 2024 13:59:47 +0530 Subject: [PATCH 5/8] Using y-websocket instead of y-webrtc for peer2peer connection --- .../{LogInForm.tsx => SignInForm.tsx} | 0 app/(auth)/signin/page.tsx | 4 +- .../{SignInForm.tsx => SignUpForm.tsx} | 0 app/(auth)/signup/page.tsx | 4 +- app/actions.ts | 1 + app/components/Card/Card.tsx | 7 +- app/writer/[id]/editor/editorConfig.ts | 10 +- app/writer/[id]/page.tsx | 14 +- components/AvatarList.tsx | 7 +- package.json | 14 +- prisma/prismaClient.ts | 12 +- socket-server/callback.js | 77 +++++ socket-server/server.js | 33 ++ socket-server/utils.js | 307 ++++++++++++++++++ 14 files changed, 458 insertions(+), 32 deletions(-) rename app/(auth)/signin/components/{LogInForm.tsx => SignInForm.tsx} (100%) rename app/(auth)/signup/components/{SignInForm.tsx => SignUpForm.tsx} (100%) create mode 100644 socket-server/callback.js create mode 100644 socket-server/server.js create mode 100644 socket-server/utils.js diff --git a/app/(auth)/signin/components/LogInForm.tsx b/app/(auth)/signin/components/SignInForm.tsx similarity index 100% rename from app/(auth)/signin/components/LogInForm.tsx rename to app/(auth)/signin/components/SignInForm.tsx diff --git a/app/(auth)/signin/page.tsx b/app/(auth)/signin/page.tsx index cc0cc73..15dde56 100644 --- a/app/(auth)/signin/page.tsx +++ b/app/(auth)/signin/page.tsx @@ -2,7 +2,7 @@ import Link from "next/link" -import LogInForm from "./components/LogInForm" +import SignInForm from "./components/SignInForm" import GoogleAuthButton from "./components/GoogleAuthButton" export default function Login() { @@ -16,7 +16,7 @@ export default function Login() {

- +

OR

diff --git a/app/(auth)/signup/components/SignInForm.tsx b/app/(auth)/signup/components/SignUpForm.tsx similarity index 100% rename from app/(auth)/signup/components/SignInForm.tsx rename to app/(auth)/signup/components/SignUpForm.tsx diff --git a/app/(auth)/signup/page.tsx b/app/(auth)/signup/page.tsx index bbbc8e5..81d240b 100644 --- a/app/(auth)/signup/page.tsx +++ b/app/(auth)/signup/page.tsx @@ -1,6 +1,6 @@ import Link from "next/link" -import SignInForm from "./components/SignInForm" +import SignUpForm from "./components/SignUpForm" import GoogleAuthButton from "./components/GoogleAuthButton" export default function Signup() { @@ -13,7 +13,7 @@ export default function Signup() {

- +

OR

diff --git a/app/actions.ts b/app/actions.ts index 992b987..9ddbb82 100644 --- a/app/actions.ts +++ b/app/actions.ts @@ -20,6 +20,7 @@ export const GetAllDocs = async (userId: string) => { thumbnail: true, name: true, updatedAt: true, + createdBy: true, users: { select: { user: { diff --git a/app/components/Card/Card.tsx b/app/components/Card/Card.tsx index bb58468..65c20bd 100644 --- a/app/components/Card/Card.tsx +++ b/app/components/Card/Card.tsx @@ -10,6 +10,7 @@ import { CardFooter, } from "@/components/ui/card" import { Input } from "@/components/ui/input" +import type { User } from "@prisma/client" import { RenameDocument } from "./actions" import AvatarList from '@/components/AvatarList' @@ -23,11 +24,7 @@ type DocCardPropType = { title: string; updatedAt: Date users: { - user: - { - name: string, - picture: string | null - } + user: Pick }[] } export default function DocCard({ diff --git a/app/writer/[id]/editor/editorConfig.ts b/app/writer/[id]/editor/editorConfig.ts index b699cc8..2aa9e4a 100644 --- a/app/writer/[id]/editor/editorConfig.ts +++ b/app/writer/[id]/editor/editorConfig.ts @@ -1,5 +1,5 @@ import { Color } from '@tiptap/extension-color' -import { WebrtcProvider } from 'y-webrtc' +import { WebsocketProvider } from 'y-websocket' import Collaboration from '@tiptap/extension-collaboration' import StarterKit from '@tiptap/starter-kit' import Highlight from '@tiptap/extension-highlight' @@ -18,12 +18,10 @@ const room = `room.${new Date() .toString() .slice(-2)}${new Date().getMonth() + 1}${new Date().getDate()}` -export const provider = new WebrtcProvider( +export const provider = new WebsocketProvider( + process.env.NEXT_PUBLIC_SIGNALLING_URL as string, room, - ydoc, - { - signaling: [process.env.NEXT_PUBLIC_SIGNALLING_URL as string] - } + ydoc ); export const extensions = [ diff --git a/app/writer/[id]/page.tsx b/app/writer/[id]/page.tsx index 81f96d6..0518bf2 100644 --- a/app/writer/[id]/page.tsx +++ b/app/writer/[id]/page.tsx @@ -120,22 +120,22 @@ export default function Dashboard() { provider, user: { name: localStorage.getItem('name'), - color: getRandomColor + color: getRandomColor() } }), ], editorProps: props, content: "", onUpdate({ editor }) { - // debouncedSaveDoc(editor); + debouncedSaveDoc(editor); }, }); - // useEffect(() => { - // if (editor && docData) { - // editor.commands.setContent(docData); - // } - // }, [docData, editor]); + useEffect(() => { + if (editor && docData) { + editor.commands.setContent(docData); + } + }, [docData, editor]); const Options = [ , diff --git a/components/AvatarList.tsx b/components/AvatarList.tsx index f0a4a26..3192c33 100644 --- a/components/AvatarList.tsx +++ b/components/AvatarList.tsx @@ -3,14 +3,11 @@ import { AvatarFallback, AvatarImage } from "@/components/ui/avatar" +import type { User } from "@prisma/client" type AvatarListPropType = { users: { - user: - { - name: string, - picture: string | null - } + user: Pick }[] } diff --git a/package.json b/package.json index c2ed219..7edd71b 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,9 @@ "db:migrate": "npx prisma migrate", "db:generate": "npx prisma generate", "db:run": "npm run db:migrate && npm run db:generate", - "install:prod": "npm i && npm run db:run" - }, + "start:socket-server": "node ./socket-server/server.js", + "install:prod": "npm i && npm run db:run && start:socket-server" + }, "dependencies": { "@auth0/nextjs-auth0": "^3.5.0", "@hocuspocus/provider": "^2.13.5", @@ -64,11 +65,16 @@ "tailwind-merge": "^2.3.0", "tailwindcss-animate": "^1.0.7", "vaul": "^0.9.1", + "yjs": "^13.6.18", "y-prosemirror": "^1.2.12", "y-webrtc": "^10.3.0", "y-websocket": "^2.0.4", - "yjs": "^13.6.18", - "zod": "^3.23.8" + "zod": "^3.23.8", + "lib0": "^0.2.97", + "lodash": "^4.17.21", + "lodash.debounce": "^4.0.8", + "ws": "^8.18.0", + "y-protocols": "^1.0.6" }, "devDependencies": { "@tailwindcss/typography": "^0.5.13", diff --git a/prisma/prismaClient.ts b/prisma/prismaClient.ts index 3098b92..08a8751 100644 --- a/prisma/prismaClient.ts +++ b/prisma/prismaClient.ts @@ -1,5 +1,15 @@ import { PrismaClient } from '@prisma/client' -let prisma = new PrismaClient() +const prismaClientSingleton = () => { + return new PrismaClient() +} + +declare const globalThis: { + prismaGlobal: ReturnType; +} & typeof global; + +const prisma = globalThis.prismaGlobal ?? prismaClientSingleton() export default prisma + +if (process.env.NODE_ENV !== 'production') globalThis.prismaGlobal = prisma diff --git a/socket-server/callback.js b/socket-server/callback.js new file mode 100644 index 0000000..b41c9a5 --- /dev/null +++ b/socket-server/callback.js @@ -0,0 +1,77 @@ +const http = require('http') +const number = require('lib0/number') + +const CALLBACK_URL = process.env.CALLBACK_URL ? new URL(process.env.CALLBACK_URL) : null +const CALLBACK_TIMEOUT = number.parseInt(process.env.CALLBACK_TIMEOUT || '5000') +const CALLBACK_OBJECTS = process.env.CALLBACK_OBJECTS ? JSON.parse(process.env.CALLBACK_OBJECTS) : {} + +exports.isCallbackSet = !!CALLBACK_URL + +/** + * @param {Uint8Array} update + * @param {any} origin + * @param {import('./utils.cjs').WSSharedDoc} doc + */ +exports.callbackHandler = (update, origin, doc) => { + const room = doc.name + const dataToSend = { + room, + data: {} + } + const sharedObjectList = Object.keys(CALLBACK_OBJECTS) + sharedObjectList.forEach(sharedObjectName => { + const sharedObjectType = CALLBACK_OBJECTS[sharedObjectName] + dataToSend.data[sharedObjectName] = { + type: sharedObjectType, + content: getContent(sharedObjectName, sharedObjectType, doc).toJSON() + } + }) + CALLBACK_URL && callbackRequest(CALLBACK_URL, CALLBACK_TIMEOUT, dataToSend) +} + +/** + * @param {URL} url + * @param {number} timeout + * @param {Object} data + */ +const callbackRequest = (url, timeout, data) => { + data = JSON.stringify(data) + const options = { + hostname: url.hostname, + port: url.port, + path: url.pathname, + timeout, + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Content-Length': Buffer.byteLength(data) + } + } + const req = http.request(options) + req.on('timeout', () => { + console.warn('Callback request timed out.') + req.abort() + }) + req.on('error', (e) => { + console.error('Callback request error.', e) + req.abort() + }) + req.write(data) + req.end() +} + +/** + * @param {string} objName + * @param {string} objType + * @param {import('./utils.cjs').WSSharedDoc} doc + */ +const getContent = (objName, objType, doc) => { + switch (objType) { + case 'Array': return doc.getArray(objName) + case 'Map': return doc.getMap(objName) + case 'Text': return doc.getText(objName) + case 'XmlFragment': return doc.getXmlFragment(objName) + case 'XmlElement': return doc.getXmlElement(objName) + default : return {} + } +} diff --git a/socket-server/server.js b/socket-server/server.js new file mode 100644 index 0000000..99c0428 --- /dev/null +++ b/socket-server/server.js @@ -0,0 +1,33 @@ +/* + * Copied from docx/node_modules/y-websocket/bin + */ + +const WebSocket = require('ws') +const http = require('http') +const number = require('lib0/number') +const wss = new WebSocket.Server({ noServer: true }) +const setupWSConnection = require('./utils.js').setupWSConnection + +const host = process.env.HOST || 'localhost' +const port = number.parseInt(process.env.PORT || '1234') + +const server = http.createServer((_request, response) => { + response.writeHead(200, { 'Content-Type': 'text/plain' }) + response.end('okay') +}) + +wss.on('connection', setupWSConnection) + +server.on('upgrade', (request, socket, head) => { + // You may check auth of request here.. + // Call `wss.HandleUpgrade` *after* you checked whether the client has access + // (e.g. by checking cookies, or url parameters). + // See https://github.com/websockets/ws#client-authentication + wss.handleUpgrade(request, socket, head, /** @param {any} ws */ ws => { + wss.emit('connection', ws, request) + }) +}) + +server.listen(port, host, () => { + console.log(`running at '${host}' on port ${port}`) +}) diff --git a/socket-server/utils.js b/socket-server/utils.js new file mode 100644 index 0000000..bfdf8e4 --- /dev/null +++ b/socket-server/utils.js @@ -0,0 +1,307 @@ +const Y = require('yjs') +const syncProtocol = require('y-protocols/sync') +const awarenessProtocol = require('y-protocols/awareness') + +const encoding = require('lib0/encoding') +const decoding = require('lib0/decoding') +const map = require('lib0/map') + +const debounce = require('lodash.debounce') + +const callbackHandler = require('./callback.js').callbackHandler +const isCallbackSet = require('./callback.js').isCallbackSet + +const CALLBACK_DEBOUNCE_WAIT = parseInt(process.env.CALLBACK_DEBOUNCE_WAIT || '2000') +const CALLBACK_DEBOUNCE_MAXWAIT = parseInt(process.env.CALLBACK_DEBOUNCE_MAXWAIT || '10000') + +const wsReadyStateConnecting = 0 +const wsReadyStateOpen = 1 +const wsReadyStateClosing = 2 // eslint-disable-line +const wsReadyStateClosed = 3 // eslint-disable-line + +// disable gc when using snapshots! +const gcEnabled = process.env.GC !== 'false' && process.env.GC !== '0' +const persistenceDir = process.env.YPERSISTENCE +/** + * @type {{bindState: function(string,WSSharedDoc):void, writeState:function(string,WSSharedDoc):Promise, provider: any}|null} + */ +let persistence = null +if (typeof persistenceDir === 'string') { + console.info('Persisting documents to "' + persistenceDir + '"') + // @ts-ignore + const LeveldbPersistence = require('y-leveldb').LeveldbPersistence + const ldb = new LeveldbPersistence(persistenceDir) + persistence = { + provider: ldb, + bindState: async (docName, ydoc) => { + const persistedYdoc = await ldb.getYDoc(docName) + const newUpdates = Y.encodeStateAsUpdate(ydoc) + ldb.storeUpdate(docName, newUpdates) + Y.applyUpdate(ydoc, Y.encodeStateAsUpdate(persistedYdoc)) + ydoc.on('update', update => { + ldb.storeUpdate(docName, update) + }) + }, + writeState: async (_docName, _ydoc) => { } + } +} + +/** + * @param {{bindState: function(string,WSSharedDoc):void, + * writeState:function(string,WSSharedDoc):Promise,provider:any}|null} persistence_ + */ +exports.setPersistence = persistence_ => { + persistence = persistence_ +} + +/** + * @return {null|{bindState: function(string,WSSharedDoc):void, + * writeState:function(string,WSSharedDoc):Promise}|null} used persistence layer + */ +exports.getPersistence = () => persistence + +/** + * @type {Map} + */ +const docs = new Map() +// exporting docs so that others can use it +exports.docs = docs + +const messageSync = 0 +const messageAwareness = 1 +// const messageAuth = 2 + +/** + * @param {Uint8Array} update + * @param {any} _origin + * @param {WSSharedDoc} doc + * @param {any} _tr + */ +const updateHandler = (update, _origin, doc, _tr) => { + const encoder = encoding.createEncoder() + encoding.writeVarUint(encoder, messageSync) + syncProtocol.writeUpdate(encoder, update) + const message = encoding.toUint8Array(encoder) + doc.conns.forEach((_, conn) => send(doc, conn, message)) +} + +/** + * @type {(ydoc: Y.Doc) => Promise} + */ +let contentInitializor = _ydoc => Promise.resolve() + +/** + * This function is called once every time a Yjs document is created. You can + * use it to pull data from an external source or initialize content. + * + * @param {(ydoc: Y.Doc) => Promise} f + */ +exports.setContentInitializor = (f) => { + contentInitializor = f +} + +class WSSharedDoc extends Y.Doc { + /** + * @param {string} name + */ + constructor(name) { + super({ gc: gcEnabled }) + this.name = name + /** + * Maps from conn to set of controlled user ids. Delete all user ids from awareness when this conn is closed + * @type {Map>} + */ + this.conns = new Map() + /** + * @type {awarenessProtocol.Awareness} + */ + this.awareness = new awarenessProtocol.Awareness(this) + this.awareness.setLocalState(null) + /** + * @param {{ added: Array, updated: Array, removed: Array }} changes + * @param {Object | null} conn Origin is the connection that made the change + */ + const awarenessChangeHandler = ({ added, updated, removed }, conn) => { + const changedClients = added.concat(updated, removed) + if (conn !== null) { + const connControlledIDs = /** @type {Set} */ (this.conns.get(conn)) + if (connControlledIDs !== undefined) { + added.forEach(clientID => { connControlledIDs.add(clientID) }) + removed.forEach(clientID => { connControlledIDs.delete(clientID) }) + } + } + // broadcast awareness update + const encoder = encoding.createEncoder() + encoding.writeVarUint(encoder, messageAwareness) + encoding.writeVarUint8Array(encoder, awarenessProtocol.encodeAwarenessUpdate(this.awareness, changedClients)) + const buff = encoding.toUint8Array(encoder) + this.conns.forEach((_, c) => { + send(this, c, buff) + }) + } + this.awareness.on('update', awarenessChangeHandler) + this.on('update', /** @type {any} */(updateHandler)) + if (isCallbackSet) { + this.on('update', /** @type {any} */(debounce( + callbackHandler, + CALLBACK_DEBOUNCE_WAIT, + { maxWait: CALLBACK_DEBOUNCE_MAXWAIT } + ))) + } + this.whenInitialized = contentInitializor(this) + } +} + +exports.WSSharedDoc = WSSharedDoc + +/** + * Gets a Y.Doc by name, whether in memory or on disk + * + * @param {string} docname - the name of the Y.Doc to find or create + * @param {boolean} gc - whether to allow gc on the doc (applies only when created) + * @return {WSSharedDoc} + */ +const getYDoc = (docname, gc = true) => map.setIfUndefined(docs, docname, () => { + const doc = new WSSharedDoc(docname) + doc.gc = gc + if (persistence !== null) { + persistence.bindState(docname, doc) + } + docs.set(docname, doc) + return doc +}) + +exports.getYDoc = getYDoc + +/** + * @param {any} conn + * @param {WSSharedDoc} doc + * @param {Uint8Array} message + */ +const messageListener = (conn, doc, message) => { + try { + const encoder = encoding.createEncoder() + const decoder = decoding.createDecoder(message) + const messageType = decoding.readVarUint(decoder) + switch (messageType) { + case messageSync: + encoding.writeVarUint(encoder, messageSync) + syncProtocol.readSyncMessage(decoder, encoder, doc, conn) + + // If the `encoder` only contains the type of reply message and no + // message, there is no need to send the message. When `encoder` only + // contains the type of reply, its length is 1. + if (encoding.length(encoder) > 1) { + send(doc, conn, encoding.toUint8Array(encoder)) + } + break + case messageAwareness: { + awarenessProtocol.applyAwarenessUpdate(doc.awareness, decoding.readVarUint8Array(decoder), conn) + break + } + } + } catch (err) { + console.error(err) + // @ts-ignore + doc.emit('error', [err]) + } +} + +/** + * @param {WSSharedDoc} doc + * @param {any} conn + */ +const closeConn = (doc, conn) => { + if (doc.conns.has(conn)) { + /** + * @type {Set} + */ + // @ts-ignore + const controlledIds = doc.conns.get(conn) + doc.conns.delete(conn) + awarenessProtocol.removeAwarenessStates(doc.awareness, Array.from(controlledIds), null) + if (doc.conns.size === 0 && persistence !== null) { + // if persisted, we store state and destroy ydocument + persistence.writeState(doc.name, doc).then(() => { + doc.destroy() + }) + docs.delete(doc.name) + } + } + conn.close() +} + +/** + * @param {WSSharedDoc} doc + * @param {import('ws').WebSocket} conn + * @param {Uint8Array} m + */ +const send = (doc, conn, m) => { + if (conn.readyState !== wsReadyStateConnecting && conn.readyState !== wsReadyStateOpen) { + closeConn(doc, conn) + } + try { + conn.send(m, {}, err => { err != null && closeConn(doc, conn) }) + } catch (e) { + closeConn(doc, conn) + } +} + +const pingTimeout = 30000 + +/** + * @param {import('ws').WebSocket} conn + * @param {import('http').IncomingMessage} req + * @param {any} opts + */ +exports.setupWSConnection = (conn, req, { docName = (req.url || '').slice(1).split('?')[0], gc = true } = {}) => { + conn.binaryType = 'arraybuffer' + // get doc, initialize if it does not exist yet + const doc = getYDoc(docName, gc) + doc.conns.set(conn, new Set()) + // listen and reply to events + conn.on('message', /** @param {ArrayBuffer} message */ message => messageListener(conn, doc, new Uint8Array(message))) + + // Check if connection is still alive + let pongReceived = true + const pingInterval = setInterval(() => { + if (!pongReceived) { + if (doc.conns.has(conn)) { + closeConn(doc, conn) + } + clearInterval(pingInterval) + } else if (doc.conns.has(conn)) { + pongReceived = false + try { + conn.ping() + } catch (e) { + closeConn(doc, conn) + clearInterval(pingInterval) + } + } + }, pingTimeout) + conn.on('close', () => { + closeConn(doc, conn) + clearInterval(pingInterval) + }) + conn.on('pong', () => { + pongReceived = true + }) + // put the following in a variables in a block so the interval handlers don't keep in in + // scope + { + // send sync step 1 + const encoder = encoding.createEncoder() + encoding.writeVarUint(encoder, messageSync) + syncProtocol.writeSyncStep1(encoder, doc) + send(doc, conn, encoding.toUint8Array(encoder)) + const awarenessStates = doc.awareness.getStates() + if (awarenessStates.size > 0) { + const encoder = encoding.createEncoder() + encoding.writeVarUint(encoder, messageAwareness) + encoding.writeVarUint8Array(encoder, awarenessProtocol.encodeAwarenessUpdate(doc.awareness, Array.from(awarenessStates.keys()))) + send(doc, conn, encoding.toUint8Array(encoder)) + } + } +} + From 8f98caa79bf5ad6ca3b95cf93e1673346d841d40 Mon Sep 17 00:00:00 2001 From: Priyanshu Date: Sun, 18 Aug 2024 14:37:53 +0530 Subject: [PATCH 6/8] Fixed deployment issue --- package-lock.json | 77 ++++++++++++++++++----------------------------- package.json | 4 +-- yarn.lock | 9 ++---- 3 files changed, 34 insertions(+), 56 deletions(-) diff --git a/package-lock.json b/package-lock.json index afad8bc..f545614 100644 --- a/package-lock.json +++ b/package-lock.json @@ -45,7 +45,9 @@ "jose": "^5.6.3", "jsonwebtoken": "^9.0.2", "jwt-decode": "^4.0.0", + "lib0": "^0.2.97", "lodash": "^4.17.21", + "lodash.debounce": "^4.0.8", "lucide-react": "^0.400.0", "next": "14.2.4", "next-auth": "^4.24.7", @@ -59,7 +61,9 @@ "tailwind-merge": "^2.3.0", "tailwindcss-animate": "^1.0.7", "vaul": "^0.9.1", + "ws": "^8.18.0", "y-prosemirror": "^1.2.12", + "y-protocols": "^1.0.6", "y-webrtc": "^10.3.0", "y-websocket": "^2.0.4", "yjs": "^13.6.18", @@ -277,26 +281,6 @@ "yjs": "^13.6.8" } }, - "node_modules/@hocuspocus/provider/node_modules/ws": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", - "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", @@ -8808,12 +8792,23 @@ "license": "ISC" }, "node_modules/ws": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", - "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==", - "optional": true, - "dependencies": { - "async-limiter": "~1.0.0" + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, "node_modules/xtend": { @@ -8910,27 +8905,6 @@ "yjs": "^13.6.8" } }, - "node_modules/y-webrtc/node_modules/ws": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", - "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", - "optional": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/y-websocket": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/y-websocket/-/y-websocket-2.0.4.tgz", @@ -8960,6 +8934,15 @@ "yjs": "^13.5.6" } }, + "node_modules/y-websocket/node_modules/ws": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", + "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==", + "optional": true, + "dependencies": { + "async-limiter": "~1.0.0" + } + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", diff --git a/package.json b/package.json index 7edd71b..fc9e38f 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,8 @@ "db:migrate": "npx prisma migrate", "db:generate": "npx prisma generate", "db:run": "npm run db:migrate && npm run db:generate", - "start:socket-server": "node ./socket-server/server.js", - "install:prod": "npm i && npm run db:run && start:socket-server" + "start:socket-server": "HOST=localhost node ./socket-server/server.js", + "install:prod": "npm i && npm run db:run && npm run start:socket-server" }, "dependencies": { "@auth0/nextjs-auth0": "^3.5.0", diff --git a/yarn.lock b/yarn.lock index 0a5a982..a4766dc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2861,7 +2861,7 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" -lib0@^0.2.31, lib0@^0.2.42, lib0@^0.2.52, lib0@^0.2.85, lib0@^0.2.86, lib0@^0.2.87: +lib0@^0.2.31, lib0@^0.2.42, lib0@^0.2.52, lib0@^0.2.85, lib0@^0.2.86, lib0@^0.2.87, lib0@^0.2.97: version "0.2.97" resolved "https://registry.npmjs.org/lib0/-/lib0-0.2.97.tgz" integrity sha512-Q4d1ekgvufi9FiHkkL46AhecfNjznSL9MRNoJRQ76gBHS9OqU2ArfQK0FvBpuxgWeJeNI0LVgAYMIpsGeX4gYg== @@ -4470,12 +4470,7 @@ ws@^6.2.1: dependencies: async-limiter "~1.0.0" -ws@^8.14.2: - version "8.18.0" - resolved "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz" - integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== - -ws@^8.17.1: +ws@^8.14.2, ws@^8.17.1, ws@^8.18.0: version "8.18.0" resolved "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz" integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== From 21a145439575105db110380f9cd0377ddab9d6f3 Mon Sep 17 00:00:00 2001 From: Priyanshu Date: Sun, 18 Aug 2024 19:35:09 +0530 Subject: [PATCH 7/8] Using hosted websocket server --- app/writer/[id]/editor/editorConfig.ts | 2 +- socket-server/callback.js | 77 ------- socket-server/server.js | 33 --- socket-server/utils.js | 307 ------------------------- 4 files changed, 1 insertion(+), 418 deletions(-) delete mode 100644 socket-server/callback.js delete mode 100644 socket-server/server.js delete mode 100644 socket-server/utils.js diff --git a/app/writer/[id]/editor/editorConfig.ts b/app/writer/[id]/editor/editorConfig.ts index 2aa9e4a..ebc4dfd 100644 --- a/app/writer/[id]/editor/editorConfig.ts +++ b/app/writer/[id]/editor/editorConfig.ts @@ -19,7 +19,7 @@ const room = `room.${new Date() .slice(-2)}${new Date().getMonth() + 1}${new Date().getDate()}` export const provider = new WebsocketProvider( - process.env.NEXT_PUBLIC_SIGNALLING_URL as string, + process.env.NEXT_PUBLIC_WEBSOCKET_URL as string, room, ydoc ); diff --git a/socket-server/callback.js b/socket-server/callback.js deleted file mode 100644 index b41c9a5..0000000 --- a/socket-server/callback.js +++ /dev/null @@ -1,77 +0,0 @@ -const http = require('http') -const number = require('lib0/number') - -const CALLBACK_URL = process.env.CALLBACK_URL ? new URL(process.env.CALLBACK_URL) : null -const CALLBACK_TIMEOUT = number.parseInt(process.env.CALLBACK_TIMEOUT || '5000') -const CALLBACK_OBJECTS = process.env.CALLBACK_OBJECTS ? JSON.parse(process.env.CALLBACK_OBJECTS) : {} - -exports.isCallbackSet = !!CALLBACK_URL - -/** - * @param {Uint8Array} update - * @param {any} origin - * @param {import('./utils.cjs').WSSharedDoc} doc - */ -exports.callbackHandler = (update, origin, doc) => { - const room = doc.name - const dataToSend = { - room, - data: {} - } - const sharedObjectList = Object.keys(CALLBACK_OBJECTS) - sharedObjectList.forEach(sharedObjectName => { - const sharedObjectType = CALLBACK_OBJECTS[sharedObjectName] - dataToSend.data[sharedObjectName] = { - type: sharedObjectType, - content: getContent(sharedObjectName, sharedObjectType, doc).toJSON() - } - }) - CALLBACK_URL && callbackRequest(CALLBACK_URL, CALLBACK_TIMEOUT, dataToSend) -} - -/** - * @param {URL} url - * @param {number} timeout - * @param {Object} data - */ -const callbackRequest = (url, timeout, data) => { - data = JSON.stringify(data) - const options = { - hostname: url.hostname, - port: url.port, - path: url.pathname, - timeout, - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Content-Length': Buffer.byteLength(data) - } - } - const req = http.request(options) - req.on('timeout', () => { - console.warn('Callback request timed out.') - req.abort() - }) - req.on('error', (e) => { - console.error('Callback request error.', e) - req.abort() - }) - req.write(data) - req.end() -} - -/** - * @param {string} objName - * @param {string} objType - * @param {import('./utils.cjs').WSSharedDoc} doc - */ -const getContent = (objName, objType, doc) => { - switch (objType) { - case 'Array': return doc.getArray(objName) - case 'Map': return doc.getMap(objName) - case 'Text': return doc.getText(objName) - case 'XmlFragment': return doc.getXmlFragment(objName) - case 'XmlElement': return doc.getXmlElement(objName) - default : return {} - } -} diff --git a/socket-server/server.js b/socket-server/server.js deleted file mode 100644 index 99c0428..0000000 --- a/socket-server/server.js +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copied from docx/node_modules/y-websocket/bin - */ - -const WebSocket = require('ws') -const http = require('http') -const number = require('lib0/number') -const wss = new WebSocket.Server({ noServer: true }) -const setupWSConnection = require('./utils.js').setupWSConnection - -const host = process.env.HOST || 'localhost' -const port = number.parseInt(process.env.PORT || '1234') - -const server = http.createServer((_request, response) => { - response.writeHead(200, { 'Content-Type': 'text/plain' }) - response.end('okay') -}) - -wss.on('connection', setupWSConnection) - -server.on('upgrade', (request, socket, head) => { - // You may check auth of request here.. - // Call `wss.HandleUpgrade` *after* you checked whether the client has access - // (e.g. by checking cookies, or url parameters). - // See https://github.com/websockets/ws#client-authentication - wss.handleUpgrade(request, socket, head, /** @param {any} ws */ ws => { - wss.emit('connection', ws, request) - }) -}) - -server.listen(port, host, () => { - console.log(`running at '${host}' on port ${port}`) -}) diff --git a/socket-server/utils.js b/socket-server/utils.js deleted file mode 100644 index bfdf8e4..0000000 --- a/socket-server/utils.js +++ /dev/null @@ -1,307 +0,0 @@ -const Y = require('yjs') -const syncProtocol = require('y-protocols/sync') -const awarenessProtocol = require('y-protocols/awareness') - -const encoding = require('lib0/encoding') -const decoding = require('lib0/decoding') -const map = require('lib0/map') - -const debounce = require('lodash.debounce') - -const callbackHandler = require('./callback.js').callbackHandler -const isCallbackSet = require('./callback.js').isCallbackSet - -const CALLBACK_DEBOUNCE_WAIT = parseInt(process.env.CALLBACK_DEBOUNCE_WAIT || '2000') -const CALLBACK_DEBOUNCE_MAXWAIT = parseInt(process.env.CALLBACK_DEBOUNCE_MAXWAIT || '10000') - -const wsReadyStateConnecting = 0 -const wsReadyStateOpen = 1 -const wsReadyStateClosing = 2 // eslint-disable-line -const wsReadyStateClosed = 3 // eslint-disable-line - -// disable gc when using snapshots! -const gcEnabled = process.env.GC !== 'false' && process.env.GC !== '0' -const persistenceDir = process.env.YPERSISTENCE -/** - * @type {{bindState: function(string,WSSharedDoc):void, writeState:function(string,WSSharedDoc):Promise, provider: any}|null} - */ -let persistence = null -if (typeof persistenceDir === 'string') { - console.info('Persisting documents to "' + persistenceDir + '"') - // @ts-ignore - const LeveldbPersistence = require('y-leveldb').LeveldbPersistence - const ldb = new LeveldbPersistence(persistenceDir) - persistence = { - provider: ldb, - bindState: async (docName, ydoc) => { - const persistedYdoc = await ldb.getYDoc(docName) - const newUpdates = Y.encodeStateAsUpdate(ydoc) - ldb.storeUpdate(docName, newUpdates) - Y.applyUpdate(ydoc, Y.encodeStateAsUpdate(persistedYdoc)) - ydoc.on('update', update => { - ldb.storeUpdate(docName, update) - }) - }, - writeState: async (_docName, _ydoc) => { } - } -} - -/** - * @param {{bindState: function(string,WSSharedDoc):void, - * writeState:function(string,WSSharedDoc):Promise,provider:any}|null} persistence_ - */ -exports.setPersistence = persistence_ => { - persistence = persistence_ -} - -/** - * @return {null|{bindState: function(string,WSSharedDoc):void, - * writeState:function(string,WSSharedDoc):Promise}|null} used persistence layer - */ -exports.getPersistence = () => persistence - -/** - * @type {Map} - */ -const docs = new Map() -// exporting docs so that others can use it -exports.docs = docs - -const messageSync = 0 -const messageAwareness = 1 -// const messageAuth = 2 - -/** - * @param {Uint8Array} update - * @param {any} _origin - * @param {WSSharedDoc} doc - * @param {any} _tr - */ -const updateHandler = (update, _origin, doc, _tr) => { - const encoder = encoding.createEncoder() - encoding.writeVarUint(encoder, messageSync) - syncProtocol.writeUpdate(encoder, update) - const message = encoding.toUint8Array(encoder) - doc.conns.forEach((_, conn) => send(doc, conn, message)) -} - -/** - * @type {(ydoc: Y.Doc) => Promise} - */ -let contentInitializor = _ydoc => Promise.resolve() - -/** - * This function is called once every time a Yjs document is created. You can - * use it to pull data from an external source or initialize content. - * - * @param {(ydoc: Y.Doc) => Promise} f - */ -exports.setContentInitializor = (f) => { - contentInitializor = f -} - -class WSSharedDoc extends Y.Doc { - /** - * @param {string} name - */ - constructor(name) { - super({ gc: gcEnabled }) - this.name = name - /** - * Maps from conn to set of controlled user ids. Delete all user ids from awareness when this conn is closed - * @type {Map>} - */ - this.conns = new Map() - /** - * @type {awarenessProtocol.Awareness} - */ - this.awareness = new awarenessProtocol.Awareness(this) - this.awareness.setLocalState(null) - /** - * @param {{ added: Array, updated: Array, removed: Array }} changes - * @param {Object | null} conn Origin is the connection that made the change - */ - const awarenessChangeHandler = ({ added, updated, removed }, conn) => { - const changedClients = added.concat(updated, removed) - if (conn !== null) { - const connControlledIDs = /** @type {Set} */ (this.conns.get(conn)) - if (connControlledIDs !== undefined) { - added.forEach(clientID => { connControlledIDs.add(clientID) }) - removed.forEach(clientID => { connControlledIDs.delete(clientID) }) - } - } - // broadcast awareness update - const encoder = encoding.createEncoder() - encoding.writeVarUint(encoder, messageAwareness) - encoding.writeVarUint8Array(encoder, awarenessProtocol.encodeAwarenessUpdate(this.awareness, changedClients)) - const buff = encoding.toUint8Array(encoder) - this.conns.forEach((_, c) => { - send(this, c, buff) - }) - } - this.awareness.on('update', awarenessChangeHandler) - this.on('update', /** @type {any} */(updateHandler)) - if (isCallbackSet) { - this.on('update', /** @type {any} */(debounce( - callbackHandler, - CALLBACK_DEBOUNCE_WAIT, - { maxWait: CALLBACK_DEBOUNCE_MAXWAIT } - ))) - } - this.whenInitialized = contentInitializor(this) - } -} - -exports.WSSharedDoc = WSSharedDoc - -/** - * Gets a Y.Doc by name, whether in memory or on disk - * - * @param {string} docname - the name of the Y.Doc to find or create - * @param {boolean} gc - whether to allow gc on the doc (applies only when created) - * @return {WSSharedDoc} - */ -const getYDoc = (docname, gc = true) => map.setIfUndefined(docs, docname, () => { - const doc = new WSSharedDoc(docname) - doc.gc = gc - if (persistence !== null) { - persistence.bindState(docname, doc) - } - docs.set(docname, doc) - return doc -}) - -exports.getYDoc = getYDoc - -/** - * @param {any} conn - * @param {WSSharedDoc} doc - * @param {Uint8Array} message - */ -const messageListener = (conn, doc, message) => { - try { - const encoder = encoding.createEncoder() - const decoder = decoding.createDecoder(message) - const messageType = decoding.readVarUint(decoder) - switch (messageType) { - case messageSync: - encoding.writeVarUint(encoder, messageSync) - syncProtocol.readSyncMessage(decoder, encoder, doc, conn) - - // If the `encoder` only contains the type of reply message and no - // message, there is no need to send the message. When `encoder` only - // contains the type of reply, its length is 1. - if (encoding.length(encoder) > 1) { - send(doc, conn, encoding.toUint8Array(encoder)) - } - break - case messageAwareness: { - awarenessProtocol.applyAwarenessUpdate(doc.awareness, decoding.readVarUint8Array(decoder), conn) - break - } - } - } catch (err) { - console.error(err) - // @ts-ignore - doc.emit('error', [err]) - } -} - -/** - * @param {WSSharedDoc} doc - * @param {any} conn - */ -const closeConn = (doc, conn) => { - if (doc.conns.has(conn)) { - /** - * @type {Set} - */ - // @ts-ignore - const controlledIds = doc.conns.get(conn) - doc.conns.delete(conn) - awarenessProtocol.removeAwarenessStates(doc.awareness, Array.from(controlledIds), null) - if (doc.conns.size === 0 && persistence !== null) { - // if persisted, we store state and destroy ydocument - persistence.writeState(doc.name, doc).then(() => { - doc.destroy() - }) - docs.delete(doc.name) - } - } - conn.close() -} - -/** - * @param {WSSharedDoc} doc - * @param {import('ws').WebSocket} conn - * @param {Uint8Array} m - */ -const send = (doc, conn, m) => { - if (conn.readyState !== wsReadyStateConnecting && conn.readyState !== wsReadyStateOpen) { - closeConn(doc, conn) - } - try { - conn.send(m, {}, err => { err != null && closeConn(doc, conn) }) - } catch (e) { - closeConn(doc, conn) - } -} - -const pingTimeout = 30000 - -/** - * @param {import('ws').WebSocket} conn - * @param {import('http').IncomingMessage} req - * @param {any} opts - */ -exports.setupWSConnection = (conn, req, { docName = (req.url || '').slice(1).split('?')[0], gc = true } = {}) => { - conn.binaryType = 'arraybuffer' - // get doc, initialize if it does not exist yet - const doc = getYDoc(docName, gc) - doc.conns.set(conn, new Set()) - // listen and reply to events - conn.on('message', /** @param {ArrayBuffer} message */ message => messageListener(conn, doc, new Uint8Array(message))) - - // Check if connection is still alive - let pongReceived = true - const pingInterval = setInterval(() => { - if (!pongReceived) { - if (doc.conns.has(conn)) { - closeConn(doc, conn) - } - clearInterval(pingInterval) - } else if (doc.conns.has(conn)) { - pongReceived = false - try { - conn.ping() - } catch (e) { - closeConn(doc, conn) - clearInterval(pingInterval) - } - } - }, pingTimeout) - conn.on('close', () => { - closeConn(doc, conn) - clearInterval(pingInterval) - }) - conn.on('pong', () => { - pongReceived = true - }) - // put the following in a variables in a block so the interval handlers don't keep in in - // scope - { - // send sync step 1 - const encoder = encoding.createEncoder() - encoding.writeVarUint(encoder, messageSync) - syncProtocol.writeSyncStep1(encoder, doc) - send(doc, conn, encoding.toUint8Array(encoder)) - const awarenessStates = doc.awareness.getStates() - if (awarenessStates.size > 0) { - const encoder = encoding.createEncoder() - encoding.writeVarUint(encoder, messageAwareness) - encoding.writeVarUint8Array(encoder, awarenessProtocol.encodeAwarenessUpdate(doc.awareness, Array.from(awarenessStates.keys()))) - send(doc, conn, encoding.toUint8Array(encoder)) - } - } -} - From 53344c56f8e13d36892761459c77d037bfaa01aa Mon Sep 17 00:00:00 2001 From: Priyanshu Date: Sun, 18 Aug 2024 19:36:12 +0530 Subject: [PATCH 8/8] Removed redundant scripts --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index fc9e38f..cc6d44e 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,7 @@ "db:migrate": "npx prisma migrate", "db:generate": "npx prisma generate", "db:run": "npm run db:migrate && npm run db:generate", - "start:socket-server": "HOST=localhost node ./socket-server/server.js", - "install:prod": "npm i && npm run db:run && npm run start:socket-server" + "install:prod": "npm i && npm run db:run" }, "dependencies": { "@auth0/nextjs-auth0": "^3.5.0",