├── .eslintrc.json ├── .gitignore ├── LICENSE ├── README.md ├── app ├── [code] │ └── page.tsx ├── favicon.ico ├── fonts │ ├── GeistMonoVF.woff │ └── GeistVF.woff ├── globals.css ├── layout.tsx ├── lib │ └── websocketServer.ts ├── page.tsx └── v1 │ └── compiler │ └── page.tsx ├── components ├── Navbar.tsx └── background-beams-with-collision.tsx ├── lib └── utils.ts ├── next.config.mjs ├── package-lock.json ├── package.json ├── pnpm-lock.yaml ├── postcss.config.mjs ├── tailwind.config.ts └── tsconfig.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["next/core-web-vitals", "next/typescript"] 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | .env 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 KRISH SONI 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Disclaimer 2 | 3 | This project is not affiliated with or endorsed by [CodeNow.com](https://www.codenow.com). 4 | 5 | 6 | **CodeNow** is a code-sharing platform with an integrated compiler. 7 | 8 | This is a side project created by [Krish Soni](https://krishsoni.co). 9 | -------------------------------------------------------------------------------- /app/[code]/page.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | import { useState, useEffect, useRef } from "react" 3 | import type React from "react" 4 | 5 | import { Copy, Share2, Code, Terminal } from "lucide-react" 6 | import { useSearchParams } from "next/navigation" 7 | import { io } from "socket.io-client" 8 | import debounce from "lodash.debounce" 9 | import LZString from "lz-string" 10 | import { motion, AnimatePresence } from "framer-motion" 11 | import SyntaxHighlighter from "react-syntax-highlighter" 12 | import { atomOneDark } from "react-syntax-highlighter/dist/esm/styles/hljs" 13 | import { nanoid } from "nanoid" 14 | import { Navbar } from "@/components/Navbar" 15 | 16 | const socket = io(process.env.NEXT_PUBLIC_SOCKET_URL) 17 | 18 | const ShareCodePage: React.FC = () => { 19 | const searchParams = useSearchParams() 20 | const initialCode = LZString.decompressFromEncodedURIComponent(searchParams.get("code") || "") || "" 21 | const [sharedCode, setSharedCode] = useState(initialCode) 22 | const [toastMessage, setToastMessage] = useState(null) 23 | const [cursorPosition, setCursorPosition] = useState({ line: 1, column: 1 }) 24 | const textareaRef = useRef(null) 25 | 26 | useEffect(() => { 27 | const shortId = window.location.pathname.substring(1) 28 | if (shortId) { 29 | const fetchCode = async () => { 30 | try { 31 | const response = await fetch(`${process.env.NEXT_PUBLIC_SERVER_URL}/api/getCode/${shortId}`) 32 | if (response.ok) { 33 | const data = await response.json() 34 | if (data.code) { 35 | const decompressedCode = LZString.decompressFromEncodedURIComponent(data.code) 36 | setSharedCode(decompressedCode) 37 | } 38 | } else { 39 | console.error("Code not found for the given short ID.") 40 | showToast("Share your code now.") 41 | } 42 | } catch (error) { 43 | console.error("Error fetching code:", error) 44 | showToast("Failed to fetch code.") 45 | } 46 | } 47 | 48 | fetchCode() 49 | } 50 | 51 | const handleCodeUpdate = (newCode: string) => setSharedCode(newCode) 52 | const handleMessage = (message: string) => console.log(message) 53 | 54 | socket.on("codeUpdate", handleCodeUpdate) 55 | socket.on("message", handleMessage) 56 | 57 | return () => { 58 | socket.off("codeUpdate", handleCodeUpdate) 59 | socket.off("message", handleMessage) 60 | } 61 | }, []) 62 | 63 | const handleCursorUpdate = () => { 64 | if (!textareaRef.current) return 65 | 66 | const textarea = textareaRef.current 67 | const cursorIndex = textarea.selectionStart 68 | const textBeforeCursor = sharedCode.substring(0, cursorIndex) 69 | const lines = textBeforeCursor.split("\n") 70 | const currentLine = lines.length 71 | const currentColumn = lines[lines.length - 1].length + 1 72 | 73 | setCursorPosition({ line: currentLine, column: currentColumn }) 74 | } 75 | 76 | const showToast = (message: string) => { 77 | setToastMessage(message) 78 | setTimeout(() => setToastMessage(null), 3000) 79 | } 80 | 81 | const handleCopy = () => { 82 | navigator.clipboard.writeText(sharedCode) 83 | showToast("The code has been copied to your clipboard.") 84 | } 85 | 86 | const handleShare = async () => { 87 | try { 88 | const compressedCode = LZString.compressToEncodedURIComponent(sharedCode) 89 | 90 | const shortId = nanoid(8) 91 | 92 | await fetch(`${process.env.NEXT_PUBLIC_SERVER_URL}/api/saveCode`, { 93 | method: "POST", 94 | headers: { "Content-Type": "application/json" }, 95 | body: JSON.stringify({ id: shortId, code: compressedCode }), 96 | }) 97 | 98 | const shortUrl = `${window.location.origin}/${shortId}` 99 | 100 | await navigator.clipboard.writeText(shortUrl) 101 | showToast("Share link copied to clipboard!") 102 | } catch (err) { 103 | console.error("Error:", err) 104 | showToast("Failed to create share link.") 105 | } 106 | } 107 | 108 | const debouncedEmitCodeChange = useRef( 109 | debounce((newCode: string) => { 110 | socket.emit("codeChange", { newCode, url: window.location.href }) 111 | localStorage.setItem("sharedCode", newCode) 112 | }, 500), 113 | ).current 114 | 115 | const handleTextareaChange = (e: React.ChangeEvent) => { 116 | const newCode = e.target.value 117 | setSharedCode(newCode) 118 | debouncedEmitCodeChange(newCode) 119 | 120 | const textarea = e.target 121 | const cursorIndex = textarea.selectionStart 122 | const textBeforeCursor = newCode.substring(0, cursorIndex) 123 | const lines = textBeforeCursor.split("\n") 124 | const currentLine = lines.length 125 | const currentColumn = lines[lines.length - 1].length + 1 126 | 127 | setCursorPosition({ line: currentLine, column: currentColumn }) 128 | } 129 | 130 | const lines = sharedCode.split("\n") 131 | return ( 132 |
133 | 134 |
135 |
136 |
137 | 143 |

144 | Shared Code 145 |

146 |
147 | 148 | 149 | Line: {cursorPosition.line}, Column: {cursorPosition.column} 150 | 151 |
152 |
153 | 154 | 160 |
161 |
162 | 169 | 176 |
177 |
178 | 179 | Real-time collaboration 180 |
181 |
182 | 183 |
184 |
185 | {lines.map((_, index) => ( 186 |
187 | {index + 1} 188 |
189 | ))} 190 |
191 |
192 | 202 | {sharedCode} 203 | 204 |