diff --git a/frontend/app/(app)/code/[id]/page.tsx b/frontend/app/(app)/code/[id]/page.tsx
index 765c4b8..79948fb 100644
--- a/frontend/app/(app)/code/[id]/page.tsx
+++ b/frontend/app/(app)/code/[id]/page.tsx
@@ -1,4 +1,5 @@
import Navbar from "@/components/editor/navbar"
+import { Room } from "@/components/editor/room"
import { Sandbox, User, UsersToSandboxes } from "@/lib/types"
import { currentUser } from "@clerk/nextjs"
import dynamic from "next/dynamic"
@@ -48,10 +49,12 @@ export default async function CodePage({ params }: { params: { id: string } }) {
return (
)
}
diff --git a/frontend/app/(app)/layout.tsx b/frontend/app/(app)/layout.tsx
index a191f40..44e4a39 100644
--- a/frontend/app/(app)/layout.tsx
+++ b/frontend/app/(app)/layout.tsx
@@ -1,3 +1,4 @@
+import { User } from "@/lib/types"
import { currentUser } from "@clerk/nextjs"
import { redirect } from "next/navigation"
@@ -13,9 +14,9 @@ export default async function AppAuthLayout({
}
const dbUser = await fetch(`http://localhost:8787/api/user?id=${user.id}`)
- const dbUserJSON = await dbUser.json()
+ const dbUserJSON = (await dbUser.json()) as User
- if (!dbUserJSON?.id) {
+ if (!dbUserJSON.id) {
const res = await fetch("http://localhost:8787/api/user", {
method: "POST",
headers: {
diff --git a/frontend/app/api/lb-auth/route.ts b/frontend/app/api/lb-auth/route.ts
new file mode 100644
index 0000000..05bdb6a
--- /dev/null
+++ b/frontend/app/api/lb-auth/route.ts
@@ -0,0 +1,43 @@
+import { User } from "@/lib/types"
+import { currentUser } from "@clerk/nextjs"
+import { Liveblocks } from "@liveblocks/node"
+import { NextRequest } from "next/server"
+
+const API_KEY = process.env.LIVEBLOCKS_SECRET_KEY!
+
+const liveblocks = new Liveblocks({
+ secret: API_KEY!,
+})
+
+export async function POST(request: NextRequest) {
+ const clerkUser = await currentUser()
+
+ if (!clerkUser) {
+ return new Response("Unauthorized", { status: 401 })
+ }
+
+ const res = await fetch(`http://localhost:8787/api/user?id=${clerkUser.id}`)
+ const user = (await res.json()) as User
+
+ // Create a session for the current user
+ // userInfo is made available in Liveblocks presence hooks, e.g. useOthers
+ const session = liveblocks.prepareSession(user.id, {
+ userInfo: {
+ id: user.id,
+ name: user.name,
+ email: user.email,
+ },
+ })
+
+ // Give the user access to the room
+ user.sandbox.forEach((sandbox) => {
+ session.allow(`${sandbox.id}`, session.FULL_ACCESS)
+ })
+ user.usersToSandboxes.forEach((userToSandbox) => {
+ session.allow(`${userToSandbox.sandboxId}`, session.FULL_ACCESS)
+ })
+
+ // Authorize the user and return the result
+ const { body, status } = await session.authorize()
+ return new Response(body, { status })
+}
diff --git a/frontend/components/editor/index.tsx b/frontend/components/editor/index.tsx
index 5e089e3..85d0eb0 100644
--- a/frontend/components/editor/index.tsx
+++ b/frontend/components/editor/index.tsx
@@ -1,9 +1,17 @@
"use client"
-import Editor, { BeforeMount, OnMount } from "@monaco-editor/react"
-import monaco from "monaco-editor"
import { useEffect, useRef, useState } from "react"
-// import theme from "./theme.json"
+import monaco from "monaco-editor"
+import Editor, { BeforeMount, OnMount } from "@monaco-editor/react"
+import { io } from "socket.io-client"
+import { toast } from "sonner"
+import { useClerk } from "@clerk/nextjs"
+
+import * as Y from "yjs"
+import LiveblocksProvider from "@liveblocks/yjs"
+import { MonacoBinding } from "y-monaco"
+import { Awareness } from "y-protocols/awareness"
+import { useRoom } from "@/liveblocks.config"
import {
ResizableHandle,
@@ -22,17 +30,12 @@ import {
} from "lucide-react"
import Tab from "../ui/tab"
import Sidebar from "./sidebar"
-import { useClerk } from "@clerk/nextjs"
-import { TFile, TFileData, TFolder, TTab } from "./sidebar/types"
-
-import { io } from "socket.io-client"
-import { processFileType, validateName } from "@/lib/utils"
-import { toast } from "sonner"
import EditorTerminal from "./terminal"
import { Button } from "../ui/button"
-import { User } from "@/lib/types"
-import { Input } from "../ui/input"
import GenerateInput from "./generate"
+import { TFile, TFileData, TFolder, TTab } from "./sidebar/types"
+import { User } from "@/lib/types"
+import { processFileType, validateName } from "@/lib/utils"
export default function CodeEditor({
userData,
@@ -62,6 +65,7 @@ export default function CodeEditor({
const [terminals, setTerminals] = useState([])
const clerk = useClerk()
+ const room = useRoom()
const editorRef = useRef(null)
const editorContainerRef = useRef(null)
@@ -230,7 +234,7 @@ export default function CodeEditor({
if (e.key === "s" && (e.metaKey || e.ctrlKey)) {
e.preventDefault()
- const activeTab = tabs.find((t) => t.id === activeId)
+ // const activeTab = tabs.find((t) => t.id === activeId)
// console.log("saving:", activeTab?.name, editorRef.current?.getValue())
setTabs((prev) =>
@@ -258,6 +262,27 @@ export default function CodeEditor({
}
})
+ useEffect(() => {
+ if (!editorRef.current) return
+
+ const yDoc = new Y.Doc()
+ const yText = yDoc.getText("monaco")
+ const yProvider: any = new LiveblocksProvider(room, yDoc)
+
+ const binding = new MonacoBinding(
+ yText,
+ editorRef.current.getModel() as monaco.editor.ITextModel,
+ new Set([editorRef.current]),
+ yProvider.awareness as Awareness
+ )
+
+ return () => {
+ yDoc.destroy()
+ yProvider.destroy()
+ binding.destroy()
+ }
+ }, [editorRef, room])
+
// connection/disconnection effect + resizeobserver
useEffect(() => {
socket.connect()
diff --git a/frontend/components/editor/room.tsx b/frontend/components/editor/room.tsx
new file mode 100644
index 0000000..13ae1bd
--- /dev/null
+++ b/frontend/components/editor/room.tsx
@@ -0,0 +1,22 @@
+"use client"
+
+import { RoomProvider } from "@/liveblocks.config"
+import { useSearchParams } from "next/navigation"
+import { ClientSideSuspense } from "@liveblocks/react"
+
+export function Room({ children }: { children: React.ReactNode }) {
+ // const roomId = useExampleRoomId("liveblocks:examples:nextjs-yjs-monaco");
+
+ return (
+
+ Loading!!!!}>
+ {() => children}
+
+
+ )
+}
diff --git a/frontend/liveblocks.config.ts b/frontend/liveblocks.config.ts
new file mode 100644
index 0000000..c5115b8
--- /dev/null
+++ b/frontend/liveblocks.config.ts
@@ -0,0 +1,161 @@
+import { createClient } from "@liveblocks/client"
+import { createRoomContext, createLiveblocksContext } from "@liveblocks/react"
+import YLiveblocksProvider from "@liveblocks/yjs"
+
+const client = createClient({
+ // publicApiKey: process.env.NEXT_PUBLIC_LIVEBLOCKS_PUBLIC_KEY!,
+ authEndpoint: "/api/lb-auth",
+ // throttle: 100,
+ async resolveUsers({ userIds }) {
+ // Used only for Comments and Notifications. Return a list of user information
+ // retrieved from `userIds`. This info is used in comments, mentions etc.
+
+ // const usersData = await __fetchUsersFromDB__(userIds);
+ //
+ // return usersData.map((userData) => ({
+ // name: userData.name,
+ // avatar: userData.avatar.src,
+ // }));
+
+ return []
+ },
+ async resolveMentionSuggestions({ text }) {
+ // Used only for Comments. Return a list of userIds that match `text`.
+ // These userIds are used to create a mention list when typing in the
+ // composer.
+ //
+ // For example when you type "@jo", `text` will be `"jo"`, and
+ // you should to return an array with John and Joanna's userIds:
+ // ["john@example.com", "joanna@example.com"]
+
+ // const users = await getUsers({ search: text });
+ // return users.map((user) => user.id);
+
+ return []
+ },
+ async resolveRoomsInfo({ roomIds }) {
+ // Used only for Comments and Notifications. Return a list of room information
+ // retrieved from `roomIds`.
+
+ // const roomsData = await __fetchRoomsFromDB__(roomIds);
+ //
+ // return roomsData.map((roomData) => ({
+ // name: roomData.name,
+ // url: roomData.url,
+ // }));
+
+ return []
+ },
+})
+
+// Presence represents the properties that exist on every user in the Room
+// and that will automatically be kept in sync. Accessible through the
+// `user.presence` property. Must be JSON-serializable.
+type Presence = {
+ // cursor: { x: number, y: number } | null,
+ // ...
+}
+
+// Optionally, Storage represents the shared document that persists in the
+// Room, even after all users leave. Fields under Storage typically are
+// LiveList, LiveMap, LiveObject instances, for which updates are
+// automatically persisted and synced to all connected clients.
+type Storage = {
+ // author: LiveObject<{ firstName: string, lastName: string }>,
+ // ...
+}
+
+// Optionally, UserMeta represents static/readonly metadata on each user, as
+// provided by your own custom auth back end (if used). Useful for data that
+// will not change during a session, like a user's name or avatar.
+type UserMeta = {
+ // id?: string, // Accessible through `user.id`
+ // info?: Json, // Accessible through `user.info`
+}
+
+// Optionally, the type of custom events broadcast and listened to in this
+// room. Use a union for multiple events. Must be JSON-serializable.
+type RoomEvent = {
+ // type: "NOTIFICATION",
+ // ...
+}
+
+// Optionally, when using Comments, ThreadMetadata represents metadata on
+// each thread. Can only contain booleans, strings, and numbers.
+export type ThreadMetadata = {
+ // resolved: boolean;
+ // quote: string;
+ // time: number;
+}
+
+// Room-level hooks, use inside `RoomProvider`
+export const {
+ suspense: {
+ RoomProvider,
+ useRoom,
+ useMyPresence,
+ useUpdateMyPresence,
+ useSelf,
+ useOthers,
+ useOthersMapped,
+ useOthersListener,
+ useOthersConnectionIds,
+ useOther,
+ useBroadcastEvent,
+ useEventListener,
+ useErrorListener,
+ useStorage,
+ useObject,
+ useMap,
+ useList,
+ useBatch,
+ useHistory,
+ useUndo,
+ useRedo,
+ useCanUndo,
+ useCanRedo,
+ useMutation,
+ useStatus,
+ useLostConnectionListener,
+ useThreads,
+ useCreateThread,
+ useEditThreadMetadata,
+ useCreateComment,
+ useEditComment,
+ useDeleteComment,
+ useAddReaction,
+ useRemoveReaction,
+ useThreadSubscription,
+ useMarkThreadAsRead,
+ useRoomNotificationSettings,
+ useUpdateRoomNotificationSettings,
+
+ // These hooks can be exported from either context
+ // useUser,
+ // useRoomInfo
+ },
+} = createRoomContext(
+ client
+)
+
+// Project-level hooks, use inside `LiveblocksProvider`
+export const {
+ suspense: {
+ LiveblocksProvider,
+ useMarkInboxNotificationAsRead,
+ useMarkAllInboxNotificationsAsRead,
+ useInboxNotifications,
+ useUnreadInboxNotificationsCount,
+
+ // These hooks can be exported from either context
+ useUser,
+ useRoomInfo,
+ },
+} = createLiveblocksContext(client)
+
+export type TypedLiveblocksProvider = YLiveblocksProvider<
+ Presence,
+ Storage,
+ UserMeta,
+ RoomEvent
+>
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 5403b1f..ac33b95 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -11,6 +11,10 @@
"@clerk/nextjs": "^4.29.12",
"@clerk/themes": "^1.7.12",
"@hookform/resolvers": "^3.3.4",
+ "@liveblocks/client": "^1.12.0",
+ "@liveblocks/node": "^1.12.0",
+ "@liveblocks/react": "^1.12.0",
+ "@liveblocks/yjs": "^1.12.0",
"@monaco-editor/react": "^4.6.0",
"@radix-ui/react-alert-dialog": "^1.0.5",
"@radix-ui/react-context-menu": "^2.1.5",
@@ -38,6 +42,9 @@
"tailwind-merge": "^2.2.2",
"tailwindcss-animate": "^1.0.7",
"vscode-icons-js": "^11.6.1",
+ "y-monaco": "^0.1.5",
+ "y-protocols": "^1.0.6",
+ "yjs": "^13.6.15",
"zod": "^3.23.3"
},
"devDependencies": {
@@ -340,6 +347,57 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
+ "node_modules/@liveblocks/client": {
+ "version": "1.12.0",
+ "resolved": "https://registry.npmjs.org/@liveblocks/client/-/client-1.12.0.tgz",
+ "integrity": "sha512-TL4sPbWBlrGF7UXLNx2RYuZi/Z51jXALMFAdaWkYE0Qz7mMwxTVSyjPVR7ZXVuqdSo0CTQ1rpPyZeqcoUDtEoQ==",
+ "dependencies": {
+ "@liveblocks/core": "1.12.0"
+ }
+ },
+ "node_modules/@liveblocks/core": {
+ "version": "1.12.0",
+ "resolved": "https://registry.npmjs.org/@liveblocks/core/-/core-1.12.0.tgz",
+ "integrity": "sha512-cPuVZwSh+EBnJL8DA999h4QDNSlOMFyxHPPHm9qVBf9Cl+NY7/Bg3cyKFiHOki6g7y8dQj8No2tpnAHWdqqalA=="
+ },
+ "node_modules/@liveblocks/node": {
+ "version": "1.12.0",
+ "resolved": "https://registry.npmjs.org/@liveblocks/node/-/node-1.12.0.tgz",
+ "integrity": "sha512-xq9EusU2ntZ2vkIQT6TrqPWv4N+txMkzn2FgNllJxt+VdJhhvbt1UkAIokywIZSElFC6gt1VUNMRFM+a7nSJUw==",
+ "dependencies": {
+ "@liveblocks/core": "1.12.0",
+ "@stablelib/base64": "^1.0.1",
+ "fast-sha256": "^1.3.0",
+ "node-fetch": "^2.6.1"
+ }
+ },
+ "node_modules/@liveblocks/react": {
+ "version": "1.12.0",
+ "resolved": "https://registry.npmjs.org/@liveblocks/react/-/react-1.12.0.tgz",
+ "integrity": "sha512-bxI420FRqUqEAHIyC6jTT40JiMDaum6+7Ag6osme2N0YsshOKZx4/z3uCD3LPdOQAzhaHiv9ZmVYwRFp3z9RfA==",
+ "dependencies": {
+ "@liveblocks/client": "1.12.0",
+ "@liveblocks/core": "1.12.0",
+ "nanoid": "^3",
+ "use-sync-external-store": "^1.2.0"
+ },
+ "peerDependencies": {
+ "react": "^16.14.0 || ^17 || ^18"
+ }
+ },
+ "node_modules/@liveblocks/yjs": {
+ "version": "1.12.0",
+ "resolved": "https://registry.npmjs.org/@liveblocks/yjs/-/yjs-1.12.0.tgz",
+ "integrity": "sha512-jCGUlfh8hW2Cr5jRrx/c/dBaY3u6psIIVPETDO91AvKgeVld50I7tcYGjFARWwaOYMhSD3c57iAsOjEkGIvpHA==",
+ "dependencies": {
+ "@liveblocks/client": "1.12.0",
+ "@liveblocks/core": "1.12.0",
+ "js-base64": "^3.7.5"
+ },
+ "peerDependencies": {
+ "yjs": "^13.6.1"
+ }
+ },
"node_modules/@monaco-editor/loader": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.4.0.tgz",
@@ -1329,6 +1387,11 @@
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
"integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA=="
},
+ "node_modules/@stablelib/base64": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@stablelib/base64/-/base64-1.0.1.tgz",
+ "integrity": "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ=="
+ },
"node_modules/@swc/helpers": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.2.tgz",
@@ -2008,6 +2071,11 @@
"resolved": "https://registry.npmjs.org/fast-plist/-/fast-plist-0.1.3.tgz",
"integrity": "sha512-d9cEfo/WcOezgPLAC/8t8wGb6YOD6JTCPMw2QcG2nAdFmyY+9rTUizCTaGjIZAloWENTEUMAPpkUAIJJJ0i96A=="
},
+ "node_modules/fast-sha256": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/fast-sha256/-/fast-sha256-1.3.0.tgz",
+ "integrity": "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ=="
+ },
"node_modules/fastq": {
"version": "1.17.1",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
@@ -2228,6 +2296,15 @@
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
},
+ "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/jackspeak": {
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz",
@@ -2253,6 +2330,11 @@
"jiti": "bin/jiti.js"
}
},
+ "node_modules/js-base64": {
+ "version": "3.7.7",
+ "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.7.tgz",
+ "integrity": "sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw=="
+ },
"node_modules/js-cookie": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.1.tgz",
@@ -2266,6 +2348,26 @@
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
+ "node_modules/lib0": {
+ "version": "0.2.93",
+ "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.93.tgz",
+ "integrity": "sha512-M5IKsiFJYulS+8Eal8f+zAqf5ckm1vffW0fFDxfgxJ+uiVopvDdd3PxJmz0GsVi3YNO7QCFSq0nAsiDmNhLj9Q==",
+ "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",
@@ -2522,6 +2624,25 @@
"tslib": "^2.0.3"
}
},
+ "node_modules/node-fetch": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
+ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
+ "dependencies": {
+ "whatwg-url": "^5.0.0"
+ },
+ "engines": {
+ "node": "4.x || >=6.0.0"
+ },
+ "peerDependencies": {
+ "encoding": "^0.1.0"
+ },
+ "peerDependenciesMeta": {
+ "encoding": {
+ "optional": true
+ }
+ }
+ },
"node_modules/node-fetch-native": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.0.1.tgz",
@@ -3376,6 +3497,11 @@
"to-no-case": "^1.0.0"
}
},
+ "node_modules/tr46": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
+ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
+ },
"node_modules/ts-interface-checker": {
"version": "0.1.13",
"resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
@@ -3519,6 +3645,20 @@
"tslib": "^2.6.2"
}
},
+ "node_modules/webidl-conversions": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
+ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
+ },
+ "node_modules/whatwg-url": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
+ "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
+ "dependencies": {
+ "tr46": "~0.0.3",
+ "webidl-conversions": "^3.0.0"
+ }
+ },
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -3645,6 +3785,41 @@
"node": ">=0.4.0"
}
},
+ "node_modules/y-monaco": {
+ "version": "0.1.5",
+ "resolved": "https://registry.npmjs.org/y-monaco/-/y-monaco-0.1.5.tgz",
+ "integrity": "sha512-zyTCMXtewIG+jP9JikJ5NN3/jLCqpkDY3titPIGBrmVeZsJr6qVeFVP3hPfmWOPHGlGZ39KCIzXrC7mFZRNFIw==",
+ "dependencies": {
+ "lib0": "^0.2.43"
+ },
+ "engines": {
+ "node": ">=12.0.0",
+ "npm": ">=6.0.0"
+ },
+ "peerDependencies": {
+ "monaco-editor": ">=0.20.0",
+ "yjs": "^13.3.1"
+ }
+ },
+ "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/yaml": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.1.tgz",
@@ -3656,6 +3831,22 @@
"node": ">= 14"
}
},
+ "node_modules/yjs": {
+ "version": "13.6.15",
+ "resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.15.tgz",
+ "integrity": "sha512-moFv4uNYhp8BFxIk3AkpoAnnjts7gwdpiG8RtyFiKbMtxKCS0zVZ5wPaaGpwC3V2N/K8TK8MwtSI3+WO9CHWjQ==",
+ "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/zod": {
"version": "3.23.3",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.23.3.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index 6f50c1e..e201b66 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -12,6 +12,10 @@
"@clerk/nextjs": "^4.29.12",
"@clerk/themes": "^1.7.12",
"@hookform/resolvers": "^3.3.4",
+ "@liveblocks/client": "^1.12.0",
+ "@liveblocks/node": "^1.12.0",
+ "@liveblocks/react": "^1.12.0",
+ "@liveblocks/yjs": "^1.12.0",
"@monaco-editor/react": "^4.6.0",
"@radix-ui/react-alert-dialog": "^1.0.5",
"@radix-ui/react-context-menu": "^2.1.5",
@@ -39,6 +43,9 @@
"tailwind-merge": "^2.2.2",
"tailwindcss-animate": "^1.0.7",
"vscode-icons-js": "^11.6.1",
+ "y-monaco": "^0.1.5",
+ "y-protocols": "^1.0.6",
+ "yjs": "^13.6.15",
"zod": "^3.23.3"
},
"devDependencies": {