├── .gitignore ├── .npmrc ├── README.md ├── app.config.ts ├── app.vue ├── assets ├── file-extensions.json ├── github-dark.json └── languages.js ├── components └── App │ ├── Footer │ └── index.vue │ ├── Navbar │ └── index.vue │ └── Sidebar │ └── index.vue ├── composables └── editor.js ├── drizzle.config.js ├── modules └── drizzle-studio.ts ├── nuxt.config.ts ├── package.json ├── pages ├── [id].vue └── index.vue ├── plugins └── vuemonaco.client.js ├── pnpm-lock.yaml ├── public ├── favicon.ico ├── logo.png ├── logo.svg └── social-card.png ├── server ├── api │ └── codebin │ │ ├── [id].get.ts │ │ └── index.post.ts ├── database │ ├── migrations │ │ ├── 0000_cold_tiger_shark.sql │ │ └── meta │ │ │ ├── 0000_snapshot.json │ │ │ └── _journal.json │ └── schema.ts ├── tsconfig.json └── utils │ └── db.js ├── tailwind.config.js └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Nuxt dev/build outputs 2 | .output 3 | .data 4 | .nuxt 5 | .nitro 6 | .cache 7 | dist 8 | 9 | # Node dependencies 10 | node_modules 11 | 12 | # Logs 13 | logs 14 | *.log 15 | 16 | # Misc 17 | .DS_Store 18 | .fleet 19 | .idea 20 | .vscode/ 21 | 22 | # Local env files 23 | .env 24 | .env.* 25 | !.env.example 26 | 27 | .wrangler/state/v3 28 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Codeshare.app 2 | 3 | [Codeshare.app](https://codeshare.app) A mininal code bin sharing app, I wanted a simple but powerful for devs pastebin so, I created this small tool using monaco editor, I've made is as frictionless as possible, kept the UI similar to VS Code. 4 | 5 | ## Pending tasks 6 | 7 | 1. Keyboard shortcuts 8 | 2. Diff editor 9 | 3. Refactor (lot of code repeated, didn't think it through) - make common and reuse components and composables 10 | 4. Sharable diff editor 11 | 5. File icons (feels like a lot of work) 12 | 6. Password protected bins (simple password based middleware) 13 | 14 | ## Stack 15 | 16 | - [Nuxt 3](https://nuxt.com) 17 | - [Tailwindcss](https://tailwindcss.com) 18 | - [Nuxt UI](https://ui.nuxt.com) 19 | - [Turso Database](https://turso.tech) 20 | - [Monaco editor](https://microsoft.github.io/monaco-editor/) 21 | 22 | ## ENV Vars 23 | 24 | TURSO_DB_TOKEN - Database token 25 | 26 | TURSO_DB_URL - Database url 27 | 28 | ## Local setup 29 | 30 | Make sure to install the dependencies: 31 | 32 | ```bash 33 | # npm 34 | npm install 35 | 36 | # pnpm 37 | pnpm install 38 | 39 | # yarn 40 | yarn install 41 | 42 | # bun 43 | bun install 44 | ``` 45 | 46 | ## Development Server 47 | 48 | Start the development server on `http://localhost:3000`: 49 | 50 | ```bash 51 | # npm 52 | npm run dev 53 | 54 | # pnpm 55 | pnpm run dev 56 | 57 | # yarn 58 | yarn dev 59 | 60 | # bun 61 | bun run dev 62 | ``` 63 | 64 | ## Production 65 | 66 | Build the application for production: 67 | 68 | ```bash 69 | # npm 70 | npm run build 71 | 72 | # pnpm 73 | pnpm run build 74 | 75 | # yarn 76 | yarn build 77 | 78 | # bun 79 | bun run build 80 | ``` 81 | 82 | Locally preview production build: 83 | 84 | ```bash 85 | # npm 86 | npm run preview 87 | 88 | # pnpm 89 | pnpm run preview 90 | 91 | # yarn 92 | yarn preview 93 | 94 | # bun 95 | bun run preview 96 | ``` 97 | 98 | 99 | ## What's tracked in the app? 100 | - Visitor count 101 | - Snippets created count -------------------------------------------------------------------------------- /app.config.ts: -------------------------------------------------------------------------------- 1 | export default defineAppConfig({ 2 | ui: { 3 | primary: "green", 4 | gray: "cool", 5 | formGroup: { 6 | help: "text-xs mt-1 text-gray-500 dark:text-gray-400", 7 | error: "text-xs mt-1 text-red-500 dark:text-red-400", 8 | label: { 9 | base: "text-sm block font-medium text-gray-500 dark:text-gray-200", 10 | }, 11 | }, 12 | button: { 13 | rounded: 14 | "rounded-lg transition-transform active:scale-x-[0.98] active:scale-y-[0.99]", 15 | default: { loadingIcon: "i-lucide-loader-2" }, 16 | }, 17 | modal: { 18 | overlay: { 19 | background: "bg-[rgba(0,8,47,.275)] saturate-50", 20 | }, 21 | padding: "p-0", 22 | rounded: "rounded-t-2xl sm:rounded-xl", 23 | transition: { 24 | enterFrom: "opacity-0 translate-y-full sm:translate-y-0 sm:scale-x-95", 25 | leaveFrom: "opacity-100 translate-y-0 sm:scale-x-100", 26 | }, 27 | }, 28 | }, 29 | }); 30 | -------------------------------------------------------------------------------- /app.vue: -------------------------------------------------------------------------------- 1 | 6 | 25 | 35 | -------------------------------------------------------------------------------- /assets/file-extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "plaintext": ".txt", 3 | "abap": ".abap", 4 | "apex": ".cls", 5 | "azcli": ".azcli", 6 | "bat": ".bat", 7 | "bicep": ".bicep", 8 | "cameligo": ".cameligo", 9 | "clojure": ".clj", 10 | "coffeescript": ".coffee", 11 | "c": ".c", 12 | "cpp": ".cpp", 13 | "csharp": ".cs", 14 | "csp": ".csp", 15 | "css": ".css", 16 | "cypher": ".cyp", 17 | "dart": ".dart", 18 | "dockerfile": ".dockerfile", 19 | "ecl": ".ecl", 20 | "elixir": ".ex", 21 | "flow9": ".flow9", 22 | "freemarker2": ".ftl", 23 | "fsharp": ".fs", 24 | "go": ".go", 25 | "graphql": ".gql", 26 | "handlebars": ".hbs", 27 | "hcl": ".hcl", 28 | "html": ".html", 29 | "ini": ".ini", 30 | "java": ".java", 31 | "javascript": ".js", 32 | "json": ".json", 33 | "julia": ".jl", 34 | "kotlin": ".kt", 35 | "less": ".less", 36 | "lexon": ".lexon", 37 | "liquid": ".liquid", 38 | "lua": ".lua", 39 | "m3": ".m3", 40 | "markdown": ".md", 41 | "mdx": ".mdx", 42 | "mips": ".mips", 43 | "msdax": ".msdax", 44 | "mysql": ".sql", 45 | "objective-c": ".m", 46 | "pascal": ".pas", 47 | "pascaligo": ".pascaligo", 48 | "perl": ".pl", 49 | "pgsql": ".pgsql", 50 | "php": ".php", 51 | "pla": ".pla", 52 | "postiats": ".dats", 53 | "powerquery": ".pq", 54 | "powershell": ".ps1", 55 | "proto": ".proto", 56 | "pug": ".pug", 57 | "python": ".py", 58 | "qsharp": ".qs", 59 | "r": ".r", 60 | "razor": ".cshtml", 61 | "redis": ".redis", 62 | "redshift": ".sql", 63 | "restructuredtext": ".rst", 64 | "ruby": ".rb", 65 | "rust": ".rs", 66 | "sb": ".sb", 67 | "scala": ".scala", 68 | "scheme": ".scm", 69 | "scss": ".scss", 70 | "shell": ".sh", 71 | "sol": ".sol", 72 | "aes": ".aes", 73 | "sparql": ".rq", 74 | "sql": ".sql", 75 | "st": ".st", 76 | "swift": ".swift", 77 | "systemverilog": ".sv", 78 | "verilog": ".v", 79 | "tcl": ".tcl", 80 | "twig": ".twig", 81 | "typescript": ".ts", 82 | "vb": ".vb", 83 | "wgsl": ".wgsl", 84 | "xml": ".xml", 85 | "yaml": ".yaml", 86 | "ada": ".ada", 87 | "cobol": ".cob", 88 | "d": ".d", 89 | "erlang": ".erl", 90 | "fortran": ".f", 91 | "groovy": ".groovy", 92 | "haskell": ".hs", 93 | "ocaml": ".ml", 94 | "octave": ".m", 95 | "racket": ".rkt", 96 | "sbcl": ".sbcl" 97 | } 98 | -------------------------------------------------------------------------------- /assets/github-dark.json: -------------------------------------------------------------------------------- 1 | { 2 | "base": "vs-dark", 3 | "inherit": true, 4 | "rules": [ 5 | { 6 | "background": "111827", 7 | "token": "" 8 | }, 9 | { 10 | "foreground": "959da5", 11 | "token": "comment" 12 | }, 13 | { 14 | "foreground": "959da5", 15 | "token": "punctuation.definition.comment" 16 | }, 17 | { 18 | "foreground": "959da5", 19 | "token": "string.comment" 20 | }, 21 | { 22 | "foreground": "c8e1ff", 23 | "token": "constant" 24 | }, 25 | { 26 | "foreground": "c8e1ff", 27 | "token": "entity.name.constant" 28 | }, 29 | { 30 | "foreground": "c8e1ff", 31 | "token": "variable.other.constant" 32 | }, 33 | { 34 | "foreground": "c8e1ff", 35 | "token": "variable.language" 36 | }, 37 | { 38 | "foreground": "b392f0", 39 | "token": "entity" 40 | }, 41 | { 42 | "foreground": "b392f0", 43 | "token": "entity.name" 44 | }, 45 | { 46 | "foreground": "f6f8fa", 47 | "token": "variable.parameter.function" 48 | }, 49 | { 50 | "foreground": "7bcc72", 51 | "token": "entity.name.tag" 52 | }, 53 | { 54 | "foreground": "ea4a5a", 55 | "token": "keyword" 56 | }, 57 | { 58 | "foreground": "ea4a5a", 59 | "token": "storage" 60 | }, 61 | { 62 | "foreground": "ea4a5a", 63 | "token": "storage.type" 64 | }, 65 | { 66 | "foreground": "f6f8fa", 67 | "token": "storage.modifier.package" 68 | }, 69 | { 70 | "foreground": "f6f8fa", 71 | "token": "storage.modifier.import" 72 | }, 73 | { 74 | "foreground": "f6f8fa", 75 | "token": "storage.type.java" 76 | }, 77 | { 78 | "foreground": "79b8ff", 79 | "token": "string" 80 | }, 81 | { 82 | "foreground": "79b8ff", 83 | "token": "punctuation.definition.string" 84 | }, 85 | { 86 | "foreground": "79b8ff", 87 | "token": "string punctuation.section.embedded source" 88 | }, 89 | { 90 | "foreground": "c8e1ff", 91 | "token": "support" 92 | }, 93 | { 94 | "foreground": "c8e1ff", 95 | "token": "meta.property-name" 96 | }, 97 | { 98 | "foreground": "fb8532", 99 | "token": "variable" 100 | }, 101 | { 102 | "foreground": "f6f8fa", 103 | "token": "variable.other" 104 | }, 105 | { 106 | "foreground": "d73a49", 107 | "fontStyle": "bold italic underline", 108 | "token": "invalid.broken" 109 | }, 110 | { 111 | "foreground": "d73a49", 112 | "fontStyle": "bold italic underline", 113 | "token": "invalid.deprecated" 114 | }, 115 | { 116 | "foreground": "fafbfc", 117 | "background": "d73a49", 118 | "fontStyle": "italic underline", 119 | "token": "invalid.illegal" 120 | }, 121 | { 122 | "foreground": "fafbfc", 123 | "background": "d73a49", 124 | "fontStyle": "italic underline", 125 | "token": "carriage-return" 126 | }, 127 | { 128 | "foreground": "d73a49", 129 | "fontStyle": "bold italic underline", 130 | "token": "invalid.unimplemented" 131 | }, 132 | { 133 | "foreground": "d73a49", 134 | "token": "message.error" 135 | }, 136 | { 137 | "foreground": "f6f8fa", 138 | "token": "string source" 139 | }, 140 | { 141 | "foreground": "c8e1ff", 142 | "token": "string variable" 143 | }, 144 | { 145 | "foreground": "79b8ff", 146 | "token": "source.regexp" 147 | }, 148 | { 149 | "foreground": "79b8ff", 150 | "token": "string.regexp" 151 | }, 152 | { 153 | "foreground": "79b8ff", 154 | "token": "string.regexp.character-class" 155 | }, 156 | { 157 | "foreground": "79b8ff", 158 | "token": "string.regexp constant.character.escape" 159 | }, 160 | { 161 | "foreground": "79b8ff", 162 | "token": "string.regexp source.ruby.embedded" 163 | }, 164 | { 165 | "foreground": "79b8ff", 166 | "token": "string.regexp string.regexp.arbitrary-repitition" 167 | }, 168 | { 169 | "foreground": "7bcc72", 170 | "fontStyle": "bold", 171 | "token": "string.regexp constant.character.escape" 172 | }, 173 | { 174 | "foreground": "c8e1ff", 175 | "token": "support.constant" 176 | }, 177 | { 178 | "foreground": "c8e1ff", 179 | "token": "support.variable" 180 | }, 181 | { 182 | "foreground": "c8e1ff", 183 | "token": "meta.module-reference" 184 | }, 185 | { 186 | "foreground": "fb8532", 187 | "token": "markup.list" 188 | }, 189 | { 190 | "foreground": "0366d6", 191 | "fontStyle": "bold", 192 | "token": "markup.heading" 193 | }, 194 | { 195 | "foreground": "0366d6", 196 | "fontStyle": "bold", 197 | "token": "markup.heading entity.name" 198 | }, 199 | { 200 | "foreground": "c8e1ff", 201 | "token": "markup.quote" 202 | }, 203 | { 204 | "foreground": "f6f8fa", 205 | "fontStyle": "italic", 206 | "token": "markup.italic" 207 | }, 208 | { 209 | "foreground": "f6f8fa", 210 | "fontStyle": "bold", 211 | "token": "markup.bold" 212 | }, 213 | { 214 | "foreground": "c8e1ff", 215 | "token": "markup.raw" 216 | }, 217 | { 218 | "foreground": "b31d28", 219 | "background": "ffeef0", 220 | "token": "markup.deleted" 221 | }, 222 | { 223 | "foreground": "b31d28", 224 | "background": "ffeef0", 225 | "token": "meta.diff.header.from-file" 226 | }, 227 | { 228 | "foreground": "b31d28", 229 | "background": "ffeef0", 230 | "token": "punctuation.definition.deleted" 231 | }, 232 | { 233 | "foreground": "176f2c", 234 | "background": "f0fff4", 235 | "token": "markup.inserted" 236 | }, 237 | { 238 | "foreground": "176f2c", 239 | "background": "f0fff4", 240 | "token": "meta.diff.header.to-file" 241 | }, 242 | { 243 | "foreground": "176f2c", 244 | "background": "f0fff4", 245 | "token": "punctuation.definition.inserted" 246 | }, 247 | { 248 | "foreground": "b08800", 249 | "background": "fffdef", 250 | "token": "markup.changed" 251 | }, 252 | { 253 | "foreground": "b08800", 254 | "background": "fffdef", 255 | "token": "punctuation.definition.changed" 256 | }, 257 | { 258 | "foreground": "2f363d", 259 | "background": "959da5", 260 | "token": "markup.ignored" 261 | }, 262 | { 263 | "foreground": "2f363d", 264 | "background": "959da5", 265 | "token": "markup.untracked" 266 | }, 267 | { 268 | "foreground": "b392f0", 269 | "fontStyle": "bold", 270 | "token": "meta.diff.range" 271 | }, 272 | { 273 | "foreground": "c8e1ff", 274 | "token": "meta.diff.header" 275 | }, 276 | { 277 | "foreground": "0366d6", 278 | "fontStyle": "bold", 279 | "token": "meta.separator" 280 | }, 281 | { 282 | "foreground": "0366d6", 283 | "token": "meta.output" 284 | }, 285 | { 286 | "foreground": "ffeef0", 287 | "token": "brackethighlighter.tag" 288 | }, 289 | { 290 | "foreground": "ffeef0", 291 | "token": "brackethighlighter.curly" 292 | }, 293 | { 294 | "foreground": "ffeef0", 295 | "token": "brackethighlighter.round" 296 | }, 297 | { 298 | "foreground": "ffeef0", 299 | "token": "brackethighlighter.square" 300 | }, 301 | { 302 | "foreground": "ffeef0", 303 | "token": "brackethighlighter.angle" 304 | }, 305 | { 306 | "foreground": "ffeef0", 307 | "token": "brackethighlighter.quote" 308 | }, 309 | { 310 | "foreground": "d73a49", 311 | "token": "brackethighlighter.unmatched" 312 | }, 313 | { 314 | "foreground": "d73a49", 315 | "token": "sublimelinter.mark.error" 316 | }, 317 | { 318 | "foreground": "fb8532", 319 | "token": "sublimelinter.mark.warning" 320 | }, 321 | { 322 | "foreground": "6a737d", 323 | "token": "sublimelinter.gutter-mark" 324 | }, 325 | { 326 | "foreground": "79b8ff", 327 | "fontStyle": "underline", 328 | "token": "constant.other.reference.link" 329 | }, 330 | { 331 | "foreground": "79b8ff", 332 | "fontStyle": "underline", 333 | "token": "string.other.link" 334 | } 335 | ], 336 | "colors": { 337 | "editor.foreground": "#f6f8fa", 338 | "editor.background": "#111827", 339 | "editor.selectionBackground": "#134E4A", 340 | "editor.inactiveSelectionBackground": "#1F2937", 341 | "editor.lineHighlightBackground": "#1F2937", 342 | "editorCursor.foreground": "#ffffff", 343 | "editorWhitespace.foreground": "#6a737d", 344 | "editorIndentGuide.background": "#6a737d", 345 | "editorIndentGuide.activeBackground": "#f6f8fa", 346 | "editor.selectionHighlightBorder": "#1F2937" 347 | } 348 | } -------------------------------------------------------------------------------- /assets/languages.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | "plaintext", 3 | "abap", 4 | "apex", 5 | "azcli", 6 | "bat", 7 | "bicep", 8 | "cameligo", 9 | "clojure", 10 | "coffeescript", 11 | "c", 12 | "cpp", 13 | "csharp", 14 | "csp", 15 | "css", 16 | "cypher", 17 | "dart", 18 | "dockerfile", 19 | "ecl", 20 | "elixir", 21 | "flow9", 22 | "freemarker2", 23 | "fsharp", 24 | "go", 25 | "graphql", 26 | "handlebars", 27 | "hcl", 28 | "html", 29 | "ini", 30 | "java", 31 | "javascript", 32 | "json", 33 | "julia", 34 | "kotlin", 35 | "less", 36 | "lexon", 37 | "liquid", 38 | "lua", 39 | "m3", 40 | "markdown", 41 | "mdx", 42 | "mips", 43 | "msdax", 44 | "mysql", 45 | "objective-c", 46 | "pascal", 47 | "pascaligo", 48 | "perl", 49 | "pgsql", 50 | "php", 51 | "pla", 52 | "postiats", 53 | "powerquery", 54 | "powershell", 55 | "proto", 56 | "pug", 57 | "python", 58 | "qsharp", 59 | "r", 60 | "razor", 61 | "redis", 62 | "redshift", 63 | "restructuredtext", 64 | "ruby", 65 | "rust", 66 | "sb", 67 | "scala", 68 | "scheme", 69 | "scss", 70 | "shell", 71 | "sol", 72 | "aes", 73 | "sparql", 74 | "sql", 75 | "st", 76 | "swift", 77 | "systemverilog", 78 | "verilog", 79 | "tcl", 80 | "twig", 81 | "typescript", 82 | "vb", 83 | "wgsl", 84 | "xml", 85 | "yaml", 86 | "ada", 87 | "cobol", 88 | "d", 89 | "dart", 90 | "elixir", 91 | "erlang", 92 | "fortran", 93 | "groovy", 94 | "haskell", 95 | "julia", 96 | "ocaml", 97 | "octave", 98 | "racket", 99 | "sbcl", 100 | "scala", 101 | ]; 102 | -------------------------------------------------------------------------------- /components/App/Footer/index.vue: -------------------------------------------------------------------------------- 1 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /components/App/Navbar/index.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /components/App/Sidebar/index.vue: -------------------------------------------------------------------------------- 1 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /composables/editor.js: -------------------------------------------------------------------------------- 1 | import githubTheme from "@/assets/github-dark.json"; 2 | import languages from "@/assets/languages"; 3 | import fileExtensions from "@/assets/file-extensions"; 4 | 5 | export const useEditor = () => { 6 | const { copy } = useClipboard(); 7 | const MONACO_EDITOR_OPTIONS = ref({ 8 | automaticLayout: true, 9 | formatOnPaste: true, 10 | foldingHighlight: true, 11 | autoClosingQuotes: "always", 12 | autoClosingBrackets: "always", 13 | autoIndent: true, 14 | autoSurround: "languageDefined", 15 | scrollBeyondLastLine: false, 16 | fontLigatures: true, 17 | fontFamily: "Cascadia Code, Fira Code, monospace", 18 | minimap: { 19 | enabled: false, 20 | }, 21 | stickyScroll: { 22 | enabled: true, 23 | }, 24 | inlineSuggest: { 25 | enabled: true, 26 | showToolbar: "always", 27 | }, 28 | }); 29 | const snippet = useState("snippet", () => { 30 | return { 31 | title: "untitled.txt", 32 | body: "", 33 | language: "plaintext", 34 | }; 35 | }); 36 | 37 | const confirmationModal = ref(false); 38 | const loading = ref(false); 39 | const toast = useToast(); 40 | const selectedLanguage = ref("plaintext"); 41 | const lineCount = ref(0); 42 | const editorRef = shallowRef(); 43 | 44 | watchEffect(() => { 45 | const language = snippet.value.language; 46 | if (fileExtensions[language]) { 47 | const originalFilename = snippet.value.title.split('.')[0]; 48 | snippet.value.title = `${originalFilename}${fileExtensions[language]}`; 49 | } 50 | }); 51 | 52 | const handleMount = (editor, monaco) => { 53 | editorRef.value = editor; 54 | monaco.editor.defineTheme("github-dark", githubTheme); 55 | monaco.editor.setTheme("github-dark"); 56 | lineCount.value = editorRef.value?.getModel()?.getLineCount() || 0; 57 | }; 58 | 59 | const wordCount = computed(() => { 60 | const text = snippet.value.body; 61 | const words = text.match(/\w+/g); 62 | return words ? words.length : 0; 63 | }); 64 | 65 | const letterCount = computed(() => { 66 | const text = snippet.value.body; 67 | return text.length; 68 | }); 69 | 70 | const onChange = () => { 71 | if (!editorRef.value) return; 72 | lineCount.value = editorRef.value?.getModel()?.getLineCount() || 0; 73 | }; 74 | 75 | function formatCode() { 76 | editorRef.value?.getAction("editor.action.formatDocument").run(); 77 | } 78 | 79 | function toggleMinimap() { 80 | MONACO_EDITOR_OPTIONS.value.minimap.enabled = 81 | !MONACO_EDITOR_OPTIONS.value.minimap.enabled; 82 | } 83 | 84 | const publishSnippet = async () => { 85 | if (!snippet.value.body) return; 86 | confirmationModal.value = true; 87 | }; 88 | 89 | const copySnippet = () => { 90 | copy(snippet.value.body); 91 | toast.add({ 92 | title: "Success", 93 | description: "Snippet copied to clipboard.", 94 | color: "green", 95 | icon: "i-heroicons-check-circle", 96 | }); 97 | }; 98 | 99 | const confirmPublish = async () => { 100 | try { 101 | loading.value = true; 102 | 103 | const data = await $fetch("/api/codebin", { 104 | method: "POST", 105 | body: snippet.value, 106 | headers: { 107 | "Content-Type": "application/json", 108 | }, 109 | }); 110 | toast.add({ 111 | title: "Success", 112 | description: "Snippet published successfully, URL copied to clipboard.", 113 | color: "green", 114 | icon: "i-heroicons-check-circle", 115 | }); 116 | copy(`${window.location.origin}/${data.uid}`); 117 | confirmationModal.value = false; 118 | navigateTo(`/${data.uid}`); 119 | } catch (error) { 120 | loading.value = false; 121 | console.log(error.statusMessage); 122 | toast.add({ 123 | title: "Error", 124 | description: error.statusMessage || error.message, 125 | color: "red", 126 | icon: "i-heroicons-x-circle", 127 | }); 128 | } 129 | }; 130 | 131 | const downloadSnippet = () => { 132 | const blob = new Blob([snippet.value.body], { 133 | type: "text/plain;charset=utf-8", 134 | }); 135 | saveAs(blob, snippet.value.title || "untitled.txt"); 136 | }; 137 | 138 | function saveAs(blob, fileName) { 139 | const link = document.createElement("a"); 140 | link.href = window.URL.createObjectURL(blob); 141 | link.download = fileName; 142 | link.click(); 143 | link.remove(); 144 | } 145 | 146 | return { 147 | MONACO_EDITOR_OPTIONS, 148 | selectedLanguage, 149 | languages, 150 | lineCount, 151 | wordCount, 152 | letterCount, 153 | editorRef, 154 | snippet, 155 | loading, 156 | confirmationModal, 157 | toggleMinimap, 158 | handleMount, 159 | onChange, 160 | formatCode, 161 | publishSnippet, 162 | confirmPublish, 163 | copySnippet, 164 | downloadSnippet, 165 | }; 166 | }; 167 | -------------------------------------------------------------------------------- /drizzle.config.js: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | const { TURSO_DB_URL, TURSO_DB_TOKEN } = process.env; 3 | 4 | const turso = { 5 | driver: "turso", 6 | dbCredentials: { 7 | url: TURSO_DB_URL, 8 | authToken: TURSO_DB_TOKEN, 9 | }, 10 | }; 11 | 12 | export default { 13 | schema: "./server/database/schema.ts", 14 | out: "./server/database/migrations", 15 | ...turso, 16 | }; 17 | -------------------------------------------------------------------------------- /modules/drizzle-studio.ts: -------------------------------------------------------------------------------- 1 | import { defineNuxtModule } from "nuxt/kit"; 2 | import { addCustomTab, startSubprocess } from "@nuxt/devtools-kit"; 3 | 4 | export default defineNuxtModule({ 5 | meta: { 6 | name: "drizzle-studio", 7 | }, 8 | setup(_options, nuxt) { 9 | if (!nuxt.options.dev) return; 10 | startSubprocess( 11 | { 12 | command: "npx", 13 | args: ["drizzle-kit", "studio"], 14 | }, 15 | { 16 | id: "nuxt-drizzle-kit--studio", 17 | name: "Drizzle Studio", 18 | } 19 | ); 20 | 21 | addCustomTab({ 22 | name: "dizzle-studio", 23 | title: "Drizzle Studio", 24 | icon: "simple-icons:drizzle", 25 | view: { 26 | type: "iframe", 27 | src: "https://local.drizzle.studio", 28 | }, 29 | }); 30 | }, 31 | }); 32 | -------------------------------------------------------------------------------- /nuxt.config.ts: -------------------------------------------------------------------------------- 1 | // https://nuxt.com/docs/api/configuration/nuxt-config 2 | 3 | export default defineNuxtConfig({ 4 | devtools: { enabled: true }, 5 | modules: [ 6 | "@nuxt/ui", 7 | "nuxt-icon", 8 | "@nuxtjs/google-fonts", 9 | "@nuxtjs/fontaine", 10 | "@vueuse/nuxt", 11 | "nuxt-rate-limit" 12 | ], 13 | ui: { 14 | icons: ["heroicons", "lucide"], 15 | }, 16 | colorMode: { 17 | preference: "dark", 18 | }, 19 | nuxtRateLimit: { 20 | routes: { 21 | '/api/*': { 22 | maxRequests: 5, 23 | intervalSeconds: 120, 24 | }, 25 | }, 26 | }, 27 | app: { 28 | head: { 29 | script: [ 30 | { 31 | src: "https://eu.umami.is/script.js", 32 | "data-website-id": "2bd6347d-b9b2-4bd7-b846-a7b0a7f24598", 33 | async: true, 34 | }, 35 | ], 36 | }, 37 | }, 38 | googleFonts: { 39 | display: "swap", 40 | families: { 41 | Inter: [400, 500, 600, 700, 800, 900], 42 | }, 43 | }, 44 | }); 45 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nuxt-app", 3 | "private": true, 4 | "type": "module", 5 | "scripts": { 6 | "build": "nuxt build", 7 | "dev": "nuxt dev", 8 | "generate": "nuxt generate", 9 | "preview": "nuxt preview", 10 | "postinstall": "nuxt prepare", 11 | "db:migrate": "drizzle-kit generate:sqlite", 12 | "db:deploy": "drizzle-kit push:sqlite" 13 | }, 14 | "devDependencies": { 15 | "@nuxtjs/fontaine": "^0.4.1", 16 | "@nuxtjs/google-fonts": "^3.1.3", 17 | "@vueuse/core": "^10.7.2", 18 | "@vueuse/nuxt": "^10.7.2", 19 | "drizzle-kit": "^0.20.13", 20 | "nitro-cloudflare-dev": "^0.0.7", 21 | "nuxt": "^3.9.1", 22 | "nuxt-icon": "^0.6.8", 23 | "nuxt-rate-limit": "^1.1.0", 24 | "vue": "^3.4.13", 25 | "vue-router": "^4.2.5", 26 | "wrangler": "^3.24.0" 27 | }, 28 | "dependencies": { 29 | "@guolao/vue-monaco-editor": "^1.4.1", 30 | "@iconify-json/lucide": "^1.1.154", 31 | "@libsql/client": "0.4.0-pre.7", 32 | "@nuxt/ui": "^2.12.0", 33 | "drizzle-orm": "^0.29.3", 34 | "h3-zod": "^0.5.3", 35 | "nanoid": "^5.0.4", 36 | "zod": "^3.22.4" 37 | } 38 | } -------------------------------------------------------------------------------- /pages/[id].vue: -------------------------------------------------------------------------------- 1 | 109 | 110 | 151 | 152 | 157 | -------------------------------------------------------------------------------- /pages/index.vue: -------------------------------------------------------------------------------- 1 | 109 | 110 | 142 | 143 | 148 | -------------------------------------------------------------------------------- /plugins/vuemonaco.client.js: -------------------------------------------------------------------------------- 1 | import { install as VueMonacoEditorPlugin } from '@guolao/vue-monaco-editor' 2 | 3 | export default defineNuxtPlugin((nuxtApp) => { 4 | nuxtApp.vueApp.use(VueMonacoEditorPlugin, { 5 | paths: { 6 | vs: 'https://cdn.jsdelivr.net/npm/monaco-editor@0.43.0/min/vs' 7 | }, 8 | }) 9 | }) 10 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fayazara/codeshare/1fe742f5305ea6ede5a5d6dfce180a58f3fd1d4a/public/favicon.ico -------------------------------------------------------------------------------- /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fayazara/codeshare/1fe742f5305ea6ede5a5d6dfce180a58f3fd1d4a/public/logo.png -------------------------------------------------------------------------------- /public/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /public/social-card.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fayazara/codeshare/1fe742f5305ea6ede5a5d6dfce180a58f3fd1d4a/public/social-card.png -------------------------------------------------------------------------------- /server/api/codebin/[id].get.ts: -------------------------------------------------------------------------------- 1 | import { z, zh } from "h3-zod"; 2 | import { eq } from "drizzle-orm"; 3 | 4 | export default eventHandler(async (event) => { 5 | try { 6 | const { id } = await zh.useValidatedParams(event, { id: z.string() }); 7 | const snippet = await useDB() 8 | .select() 9 | .from(tables.snippets) 10 | .where(eq(tables.snippets.uid, id)) 11 | .get(); 12 | return snippet; 13 | } catch (error) { 14 | return error; 15 | } 16 | }); 17 | -------------------------------------------------------------------------------- /server/api/codebin/index.post.ts: -------------------------------------------------------------------------------- 1 | import { useValidatedBody, z } from "h3-zod"; 2 | import { nanoid } from "nanoid"; 3 | 4 | export default eventHandler(async (event) => { 5 | const { title, body, language } = await useValidatedBody(event, { 6 | title: z.string().min(1).max(50), 7 | body: z.string().min(1), 8 | language: z.string().min(1).max(50), 9 | }); 10 | const uid = nanoid(10); 11 | const snippet = await useDB() 12 | .insert(tables.snippets) 13 | .values({ 14 | title, 15 | body, 16 | uid, 17 | language, 18 | createdAt: new Date(), 19 | }) 20 | .returning() 21 | .get(); 22 | 23 | return snippet; 24 | }); 25 | -------------------------------------------------------------------------------- /server/database/migrations/0000_cold_tiger_shark.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS `snippets` ( 2 | `id` integer PRIMARY KEY NOT NULL, 3 | `title` text NOT NULL, 4 | `body` text NOT NULL, 5 | `uid` text NOT NULL, 6 | `language` text DEFAULT 'plaintext' NOT NULL, 7 | `created_at` integer NOT NULL 8 | ); 9 | -------------------------------------------------------------------------------- /server/database/migrations/meta/0000_snapshot.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "5", 3 | "dialect": "sqlite", 4 | "id": "9be7329b-d484-4d05-a2bc-28f7b82d7cfc", 5 | "prevId": "00000000-0000-0000-0000-000000000000", 6 | "tables": { 7 | "snippets": { 8 | "name": "snippets", 9 | "columns": { 10 | "id": { 11 | "name": "id", 12 | "type": "integer", 13 | "primaryKey": true, 14 | "notNull": true, 15 | "autoincrement": false 16 | }, 17 | "title": { 18 | "name": "title", 19 | "type": "text", 20 | "primaryKey": false, 21 | "notNull": true, 22 | "autoincrement": false 23 | }, 24 | "body": { 25 | "name": "body", 26 | "type": "text", 27 | "primaryKey": false, 28 | "notNull": true, 29 | "autoincrement": false 30 | }, 31 | "uid": { 32 | "name": "uid", 33 | "type": "text", 34 | "primaryKey": false, 35 | "notNull": true, 36 | "autoincrement": false 37 | }, 38 | "language": { 39 | "name": "language", 40 | "type": "text", 41 | "primaryKey": false, 42 | "notNull": true, 43 | "autoincrement": false, 44 | "default": "'plaintext'" 45 | }, 46 | "created_at": { 47 | "name": "created_at", 48 | "type": "integer", 49 | "primaryKey": false, 50 | "notNull": true, 51 | "autoincrement": false 52 | } 53 | }, 54 | "indexes": {}, 55 | "foreignKeys": {}, 56 | "compositePrimaryKeys": {}, 57 | "uniqueConstraints": {} 58 | } 59 | }, 60 | "enums": {}, 61 | "_meta": { 62 | "schemas": {}, 63 | "tables": {}, 64 | "columns": {} 65 | } 66 | } -------------------------------------------------------------------------------- /server/database/migrations/meta/_journal.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "5", 3 | "dialect": "sqlite", 4 | "entries": [ 5 | { 6 | "idx": 0, 7 | "version": "5", 8 | "when": 1705410260827, 9 | "tag": "0000_cold_tiger_shark", 10 | "breakpoints": true 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /server/database/schema.ts: -------------------------------------------------------------------------------- 1 | import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core"; 2 | 3 | export const snippets = sqliteTable("snippets", { 4 | id: integer("id").primaryKey(), 5 | title: text("title").notNull(), 6 | body: text("body").notNull(), 7 | uid: text("uid").notNull(), 8 | language: text("language").default("plaintext").notNull(), 9 | createdAt: integer("created_at", { mode: "timestamp" }).notNull(), 10 | }); 11 | -------------------------------------------------------------------------------- /server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../.nuxt/tsconfig.server.json" 3 | } 4 | -------------------------------------------------------------------------------- /server/utils/db.js: -------------------------------------------------------------------------------- 1 | import { createClient as createLibSQLClient } from "@libsql/client/http"; 2 | import { drizzle as drizzleLibSQL } from "drizzle-orm/libsql"; 3 | 4 | let _db = null; 5 | 6 | export * as tables from '~/server/database/schema' 7 | 8 | export const useDB = () => { 9 | if (!_db) { 10 | if (process.env.TURSO_DB_URL && process.env.TURSO_DB_TOKEN) { 11 | _db = drizzleLibSQL( 12 | createLibSQLClient({ 13 | url: process.env.TURSO_DB_URL, 14 | authToken: process.env.TURSO_DB_TOKEN, 15 | }) 16 | ); 17 | console.log("Database initiated:", process.env.TURSO_DB_URL); 18 | } else { 19 | throw new Error("No database configured for production or development"); 20 | } 21 | } 22 | return _db; 23 | }; 24 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | "./components/**/*.{js,vue,ts}", 5 | "./layouts/**/*.vue", 6 | "./pages/**/*.vue", 7 | "./plugins/**/*.{js,ts}", 8 | "./nuxt.config.{js,ts}", 9 | "./app.vue", 10 | ], 11 | theme: { 12 | fontFamily: { 13 | sans: [ 14 | "Inter", 15 | "Avenir Next", 16 | "Roboto", 17 | "-apple-system", 18 | "BlinkMacSystemFont", 19 | '"Segoe UI"', 20 | "Ubuntu", 21 | '"Helvetica Neue"', 22 | "Arial", 23 | '"Noto Sans"', 24 | "sans-serif", 25 | '"Apple Color Emoji"', 26 | '"Segoe UI Emoji"', 27 | '"Segoe UI Symbol"', 28 | '"Noto Color Emoji"', 29 | ], 30 | mono: [ 31 | "Cascadia Code", 32 | "ui-monospace", 33 | "SFMono-Regular", 34 | "Menlo", 35 | "Monaco", 36 | "Consolas", 37 | "Liberation Mono", 38 | "Courier New", 39 | "monospace", 40 | ], 41 | }, 42 | }, 43 | plugins: [], 44 | }; -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // https://nuxt.com/docs/guide/concepts/typescript 3 | "extends": "./.nuxt/tsconfig.json" 4 | } 5 | --------------------------------------------------------------------------------