From be4917ce8001c02fa055c86255b6fae69c48c5bf Mon Sep 17 00:00:00 2001 From: Leo dev Date: Mon, 22 Dec 2025 12:45:48 +0100 Subject: [PATCH 1/5] Improved protocol --- package-lock.json | 110 +++++++---- package.json | 1 + src/app/chat/[id]/page.tsx | 19 +- src/components/channel/ChannelView.tsx | 12 +- src/components/channel/MessageBox.tsx | 240 ++++++++++++++--------- src/components/ui/context-menu.tsx | 252 +++++++++++++++++++++++++ src/hooks/use-app.ts | 1 + src/types/protocol.ts | 9 + 8 files changed, 491 insertions(+), 153 deletions(-) create mode 100644 src/components/ui/context-menu.tsx create mode 100644 src/types/protocol.ts diff --git a/package-lock.json b/package-lock.json index 0d2a329..6a01c88 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@lexical/rich-text": "^0.34.0", "@radix-ui/react-avatar": "^1.1.10", "@radix-ui/react-checkbox": "^1.3.3", + "@radix-ui/react-context-menu": "^2.2.16", "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.16", "@radix-ui/react-label": "^2.1.7", @@ -38,7 +39,7 @@ "js-cookie": "^3.0.5", "lexical": "^0.34.0", "lucide-react": "^0.541.0", - "next": "15.5.0", + "next": "15.5.7", "next-themes": "^0.4.6", "pg": "^8.16.3", "react": "19.1.0", @@ -883,15 +884,15 @@ } }, "node_modules/@next/env": { - "version": "15.5.0", - "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.0.tgz", - "integrity": "sha512-sDaprBAfzCQiOgo2pO+LhnV0Wt2wBgartjrr+dpcTORYVnnXD0gwhHhiiyIih9hQbq+JnbqH4odgcFWhqCGidw==", + "version": "15.5.7", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.7.tgz", + "integrity": "sha512-4h6Y2NyEkIEN7Z8YxkA27pq6zTkS09bUSYC0xjd0NpwFxjnIKeZEeH591o5WECSmjpUhLn3H2QLJcDye3Uzcvg==", "license": "MIT" }, "node_modules/@next/swc-darwin-arm64": { - "version": "15.5.0", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.0.tgz", - "integrity": "sha512-v7Jj9iqC6enxIRBIScD/o0lH7QKvSxq2LM8UTyqJi+S2w2QzhMYjven4vgu/RzgsdtdbpkyCxBTzHl/gN5rTRg==", + "version": "15.5.7", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.7.tgz", + "integrity": "sha512-IZwtxCEpI91HVU/rAUOOobWSZv4P2DeTtNaCdHqLcTJU4wdNXgAySvKa/qJCgR5m6KI8UsKDXtO2B31jcaw1Yw==", "cpu": [ "arm64" ], @@ -905,9 +906,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "15.5.0", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.0.tgz", - "integrity": "sha512-s2Nk6ec+pmYmAb/utawuURy7uvyYKDk+TRE5aqLRsdnj3AhwC9IKUBmhfnLmY/+P+DnwqpeXEFIKe9tlG0p6CA==", + "version": "15.5.7", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.7.tgz", + "integrity": "sha512-UP6CaDBcqaCBuiq/gfCEJw7sPEoX1aIjZHnBWN9v9qYHQdMKvCKcAVs4OX1vIjeE+tC5EIuwDTVIoXpUes29lg==", "cpu": [ "x64" ], @@ -921,9 +922,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "15.5.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.0.tgz", - "integrity": "sha512-mGlPJMZReU4yP5fSHjOxiTYvZmwPSWn/eF/dcg21pwfmiUCKS1amFvf1F1RkLHPIMPfocxLViNWFvkvDB14Isg==", + "version": "15.5.7", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.7.tgz", + "integrity": "sha512-NCslw3GrNIw7OgmRBxHtdWFQYhexoUCq+0oS2ccjyYLtcn1SzGzeM54jpTFonIMUjNbHmpKpziXnpxhSWLcmBA==", "cpu": [ "arm64" ], @@ -937,9 +938,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "15.5.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.0.tgz", - "integrity": "sha512-biWqIOE17OW/6S34t1X8K/3vb1+svp5ji5QQT/IKR+VfM3B7GvlCwmz5XtlEan2ukOUf9tj2vJJBffaGH4fGRw==", + "version": "15.5.7", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.7.tgz", + "integrity": "sha512-nfymt+SE5cvtTrG9u1wdoxBr9bVB7mtKTcj0ltRn6gkP/2Nu1zM5ei8rwP9qKQP0Y//umK+TtkKgNtfboBxRrw==", "cpu": [ "arm64" ], @@ -953,9 +954,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "15.5.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.0.tgz", - "integrity": "sha512-zPisT+obYypM/l6EZ0yRkK3LEuoZqHaSoYKj+5jiD9ESHwdr6QhnabnNxYkdy34uCigNlWIaCbjFmQ8FY5AlxA==", + "version": "15.5.7", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.7.tgz", + "integrity": "sha512-hvXcZvCaaEbCZcVzcY7E1uXN9xWZfFvkNHwbe/n4OkRhFWrs1J1QV+4U1BN06tXLdaS4DazEGXwgqnu/VMcmqw==", "cpu": [ "x64" ], @@ -969,9 +970,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "15.5.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.0.tgz", - "integrity": "sha512-+t3+7GoU9IYmk+N+FHKBNFdahaReoAktdOpXHFIPOU1ixxtdge26NgQEEkJkCw2dHT9UwwK5zw4mAsURw4E8jA==", + "version": "15.5.7", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.7.tgz", + "integrity": "sha512-4IUO539b8FmF0odY6/SqANJdgwn1xs1GkPO5doZugwZ3ETF6JUdckk7RGmsfSf7ws8Qb2YB5It33mvNL/0acqA==", "cpu": [ "x64" ], @@ -985,9 +986,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "15.5.0", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.0.tgz", - "integrity": "sha512-d8MrXKh0A+c9DLiy1BUFwtg3Hu90Lucj3k6iKTUdPOv42Ve2UiIG8HYi3UAb8kFVluXxEfdpCoPPCSODk5fDcw==", + "version": "15.5.7", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.7.tgz", + "integrity": "sha512-CpJVTkYI3ZajQkC5vajM7/ApKJUOlm6uP4BknM3XKvJ7VXAvCqSjSLmM0LKdYzn6nBJVSjdclx8nYJSa3xlTgQ==", "cpu": [ "arm64" ], @@ -1001,9 +1002,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "15.5.0", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.0.tgz", - "integrity": "sha512-Fe1tGHxOWEyQjmygWkkXSwhFcTJuimrNu52JEuwItrKJVV4iRjbWp9I7zZjwqtiNnQmxoEvoisn8wueFLrNpvQ==", + "version": "15.5.7", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.7.tgz", + "integrity": "sha512-gMzgBX164I6DN+9/PGA+9dQiwmTkE4TloBNx8Kv9UiGARsr9Nba7IpcBRA1iTV9vwlYnrE3Uy6I7Aj6qLjQuqw==", "cpu": [ "x64" ], @@ -1164,6 +1165,34 @@ } } }, + "node_modules/@radix-ui/react-context-menu": { + "version": "2.2.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context-menu/-/react-context-menu-2.2.16.tgz", + "integrity": "sha512-O8morBEW+HsVG28gYDZPTrT9UUovQUlJue5YO836tiTJhuIWBm/zQHc7j388sHWtdH/xUZurK9olD2+pcqx5ww==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-menu": "2.1.16", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-dialog": { "version": "1.1.15", "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz", @@ -4458,12 +4487,13 @@ } }, "node_modules/next": { - "version": "15.5.0", - "resolved": "https://registry.npmjs.org/next/-/next-15.5.0.tgz", - "integrity": "sha512-N1lp9Hatw3a9XLt0307lGB4uTKsXDhyOKQo7uYMzX4i0nF/c27grcGXkLdb7VcT8QPYLBa8ouIyEoUQJ2OyeNQ==", + "version": "15.5.7", + "resolved": "https://registry.npmjs.org/next/-/next-15.5.7.tgz", + "integrity": "sha512-+t2/0jIJ48kUpGKkdlhgkv+zPTEOoXyr60qXe68eB/pl3CMJaLeIGjzp5D6Oqt25hCBiBTt8wEeeAzfJvUKnPQ==", + "deprecated": "This version has a security vulnerability. Please upgrade to a patched version. See https://nextjs.org/blog/security-update-2025-12-11 for more details.", "license": "MIT", "dependencies": { - "@next/env": "15.5.0", + "@next/env": "15.5.7", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", @@ -4476,14 +4506,14 @@ "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "15.5.0", - "@next/swc-darwin-x64": "15.5.0", - "@next/swc-linux-arm64-gnu": "15.5.0", - "@next/swc-linux-arm64-musl": "15.5.0", - "@next/swc-linux-x64-gnu": "15.5.0", - "@next/swc-linux-x64-musl": "15.5.0", - "@next/swc-win32-arm64-msvc": "15.5.0", - "@next/swc-win32-x64-msvc": "15.5.0", + "@next/swc-darwin-arm64": "15.5.7", + "@next/swc-darwin-x64": "15.5.7", + "@next/swc-linux-arm64-gnu": "15.5.7", + "@next/swc-linux-arm64-musl": "15.5.7", + "@next/swc-linux-x64-gnu": "15.5.7", + "@next/swc-linux-x64-musl": "15.5.7", + "@next/swc-win32-arm64-msvc": "15.5.7", + "@next/swc-win32-x64-msvc": "15.5.7", "sharp": "^0.34.3" }, "peerDependencies": { diff --git a/package.json b/package.json index 107b8ec..8909f37 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "@lexical/rich-text": "^0.34.0", "@radix-ui/react-avatar": "^1.1.10", "@radix-ui/react-checkbox": "^1.3.3", + "@radix-ui/react-context-menu": "^2.2.16", "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.16", "@radix-ui/react-label": "^2.1.7", diff --git a/src/app/chat/[id]/page.tsx b/src/app/chat/[id]/page.tsx index d62c919..c682a3b 100644 --- a/src/app/chat/[id]/page.tsx +++ b/src/app/chat/[id]/page.tsx @@ -42,16 +42,15 @@ export default function DMs() { (m.channel_id === id && m.from === app.profile?.id) || (m.from === id && m.channel_id === app.profile?.id) )} - sendMessage={(message) => { - targetNode.current?.send( - JSON.stringify({ - type: "send_message", - params: { - channel_id: id, - contents: message, - }, - }) - ); + sendRequest={(req) => { + switch (req.type) { + case "send_message": + targetNode.current?.send(JSON.stringify(req)); + break; + default: + app.node.current?.send(JSON.stringify(req)); + break; + } }} /> diff --git a/src/components/channel/ChannelView.tsx b/src/components/channel/ChannelView.tsx index d77542e..68d563c 100644 --- a/src/components/channel/ChannelView.tsx +++ b/src/components/channel/ChannelView.tsx @@ -30,17 +30,7 @@ export default function ChannelView({ (m) => m.channel_id === app.currentChannel?.id ) || [] } - sendMessage={(message) => { - serverRef.current?.send( - JSON.stringify({ - type: "send_message", - params: { - channel_id: app.currentChannel?.id, - contents: message, - }, - }) - ); - }} + sendRequest={(req) => serverRef.current?.send(JSON.stringify(req))} /> ); diff --git a/src/components/channel/MessageBox.tsx b/src/components/channel/MessageBox.tsx index 73d7f61..3ff01be 100644 --- a/src/components/channel/MessageBox.tsx +++ b/src/components/channel/MessageBox.tsx @@ -19,112 +19,151 @@ import { UserProfile } from "@/hooks/get-user"; import ProfilePicture from "../ProfilePicture"; import App from "@/types/app"; import { Textarea } from "../ui/textarea"; +import { + ContextMenu, + ContextMenuCheckboxItem, + ContextMenuContent, + ContextMenuItem, + ContextMenuLabel, + ContextMenuRadioGroup, + ContextMenuRadioItem, + ContextMenuSeparator, + ContextMenuShortcut, + ContextMenuSub, + ContextMenuSubContent, + ContextMenuSubTrigger, + ContextMenuTrigger, +} from "@/components/ui/context-menu"; +import { ClientMessage } from "@/types/protocol"; -function MessageContainer({ message, app }: { message: Message; app: App }) { +function MessageContainer({ + message, + app, + sendRequest, +}: { + message: Message; + app: App; + sendRequest: (content: ClientMessage) => void; +}) { const [profile, setProfile] = useState(); app.getUserById(message.from).then(setProfile); return ( -
-
- {/*
*/} - -
- - {profile?.display_name || message.from} + + +
+
+ {/*
*/} + +
+ + {profile?.display_name || message.from} -

- {isToday(message.timestamp * 1000) - ? format(message.timestamp * 1000, "p") - : isYesterday(message.timestamp * 1000) - ? "Yesterday, " + format(message.timestamp * 1000, "p") - : format(message.timestamp * 1000, "PP, p")} -

-
+

+ {isToday(message.timestamp * 1000) + ? format(message.timestamp * 1000, "p") + : isYesterday(message.timestamp * 1000) + ? "Yesterday, " + format(message.timestamp * 1000, "p") + : format(message.timestamp * 1000, "PP, p")} +

+ -
- {message.contents.split("\n\n").map((line, index) => ( -
- {index > 0 &&
} - ( -

- ), - h2: ({ node, ...props }) => ( -

- ), - h3: ({ node, ...props }) => ( -

- ), - h4: ({ node, ...props }) => ( -

- ), - h5: ({ node, ...props }) => ( -

- ), - ul: ({ node, ...props }) => ( -
-
-
+ + + Copy message + {}} + disabled={message.from !== app.profile?.id} + > + Delete + + + ); } export default function MessageBox({ app, - sendMessage, + sendRequest, channelName, channelIcon, messages, }: { app: App; - sendMessage: (m: string) => void; + sendRequest: (req: ClientMessage) => void; channelName?: string; channelIcon?: string; messages: Message[]; @@ -132,6 +171,21 @@ export default function MessageBox({ const [text, setText] = useState(""); const inputRef = useRef(null); + const sendMessage = () => { + if (!app.currentChannel) { + return; + } + + sendRequest({ + type: "send_message", + params: { + channel_id: app.currentChannel?.id, + contents: text.trim(), + }, + }); + setText(""); + }; + return (
{channelName && ( @@ -156,7 +210,12 @@ export default function MessageBox({ )}
{messages.toReversed().map((msg) => ( - + ))}
@@ -142,6 +180,16 @@ function MessageContainer({ Copy message + { + setEditText(message.contents); + setIsEditing(true); + }} + disabled={message.from !== app.profile?.id} + > + Edit + ; + onEnter: () => void; +}) { + const inputRef = useRef(null); + + useEffect(() => { + inputRef.current?.focus(); + }, [inputRef]); + + return text.includes("\n") ? ( +