├── .env.example ├── .github ├── ISSUE_TEMPLATE │ ├── bugs.yml │ ├── features.yml │ └── other.yml ├── pull_request_template.md └── workflows │ ├── precommit.yml │ └── snippet.yml ├── .gitignore ├── .husky └── pre-commit ├── .vercelignore ├── .vscode └── settings.json ├── API.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── app ├── api │ ├── [language] │ │ ├── [category] │ │ │ ├── [name] │ │ │ │ ├── raw │ │ │ │ │ └── route.ts │ │ │ │ └── route.ts │ │ │ └── route.ts │ │ └── route.ts │ ├── languages │ │ └── route.ts │ └── search │ │ └── route.ts ├── extensions │ └── page.tsx ├── globals.css ├── highlight.css ├── layout.tsx ├── not-found.tsx ├── page.tsx ├── sitemap.ts └── snippets │ ├── [language] │ ├── [category] │ │ ├── [name] │ │ │ ├── page.tsx │ │ │ └── snippet.tsx │ │ └── page.tsx │ └── page.tsx │ ├── layout.tsx │ └── page.tsx ├── assets ├── banner-page-cut.png └── vscode.png ├── biome.json ├── components.json ├── components ├── badge.tsx ├── code-preview.tsx ├── code-tabs.tsx ├── copy-button.tsx ├── extendui │ └── command.tsx ├── footer.tsx ├── grouped-snippets │ ├── content.tsx │ └── list.tsx ├── icons │ ├── github.tsx │ ├── identicons.tsx │ └── language.tsx ├── keyword.tsx ├── landing │ ├── features.tsx │ └── hero.tsx ├── logo.tsx ├── mdx-provider.tsx ├── navbar.tsx ├── not-found.tsx ├── search │ ├── button.tsx │ ├── dialog.tsx │ └── provider.tsx ├── theme │ ├── provider.tsx │ └── toggle.tsx └── ui │ ├── breadcrumb.tsx │ ├── button.tsx │ ├── dialog.tsx │ ├── drawer.tsx │ └── popover.tsx ├── context └── search.ts ├── hooks └── use-search.ts ├── lib ├── extensions.ts ├── languages.ts ├── search.ts ├── snippets.ts ├── types.ts └── utils.ts ├── mdx-components.tsx ├── middleware.ts ├── next.config.mjs ├── package.json ├── pnpm-lock.yaml ├── postcss.config.mjs ├── public ├── banner.png ├── favicon-dark.png ├── favicon-light.png └── robots.txt ├── react-to-string.d.ts ├── scripts ├── cli │ ├── commands │ │ ├── list.ts │ │ ├── new.prompts.ts │ │ └── new.ts │ └── index.ts └── validate-snippets.ts ├── snippets ├── gdscript │ └── dictionary │ │ └── random-element.mdx ├── javascript │ ├── array │ │ ├── chunk.mdx │ │ ├── intersection.mdx │ │ ├── partition.mdx │ │ ├── random-element.mdx │ │ └── shuffle.mdx │ ├── color │ │ ├── random-hex.mdx │ │ ├── rgb-to-hex.mdx │ │ └── shade.mdx │ ├── date │ │ ├── get-time-difference.mdx │ │ └── is-leap-year.mdx │ ├── math │ │ ├── anagram.mdx │ │ ├── fibonacci.mdx │ │ ├── lerp.mdx │ │ └── palindrome.mdx │ ├── number │ │ ├── currency-formatter.mdx │ │ ├── file-size-formatter.mdx │ │ ├── ordinal-formatter.mdx │ │ ├── percentage-formatter.mdx │ │ ├── random-number.mdx │ │ ├── roman-numeral-formatter.mdx │ │ └── scientific-notation-formatter.mdx │ ├── string │ │ ├── acronym.mdx │ │ ├── capitalize-words.mdx │ │ ├── get-link-extension.mdx │ │ ├── is-palindrome.mdx │ │ ├── pan-card-validate-regex.mdx │ │ ├── to-camel-case.mdx │ │ ├── to-kebab-case.mdx │ │ ├── to-pascal-case.mdx │ │ ├── to-path-case.mdx │ │ ├── to-snake-case.mdx │ │ └── truncate.mdx │ └── web-storage │ │ ├── add-item-to-indexeddb.mdx │ │ ├── add-item-to-localstorage.mdx │ │ ├── check-if-item-exists-in-indexeddb.mdx │ │ ├── check-if-item-exists-in-localstorage.mdx │ │ ├── delete-item-from-indexeddb.mdx │ │ ├── delete-item-from-localstorage.mdx │ │ ├── retrieve-item-from-indexeddb.mdx │ │ └── retrieve-item-from-localstorage.mdx ├── nix │ └── fetchers │ │ └── pkgs-functions.mdx ├── python │ ├── array │ │ ├── filter.mdx │ │ ├── find-duplicates.mdx │ │ └── flatten-nested.mdx │ ├── dictionary │ │ └── sort-by-value.mdx │ ├── math │ │ ├── base-converter.mdx │ │ ├── factorial.mdx │ │ ├── fibonacci.mdx │ │ └── is-prime.mdx │ └── numpy │ │ └── generate-array.mdx ├── react-native │ └── async-storage │ │ └── hook.mdx ├── react │ └── hook │ │ ├── use-click-outside.mdx │ │ ├── use-debounced-value.mdx │ │ ├── use-dimensions.mdx │ │ ├── use-fetch.mdx │ │ ├── use-form.mdx │ │ ├── use-intersection-observer.mdx │ │ ├── use-is-mounted.mdx │ │ ├── use-local-storage.mdx │ │ ├── use-timeout.mdx │ │ └── use-viewport-size.mdx ├── regex │ ├── hash │ │ ├── md5-hash.mdx │ │ └── sha-256-hash.mdx │ ├── network │ │ ├── domain-name.mdx │ │ ├── ipv4.mdx │ │ ├── ipv6.mdx │ │ ├── localhost.mdx │ │ ├── mac.mdx │ │ └── url.mdx │ ├── other │ │ ├── email.mdx │ │ ├── hex.mdx │ │ ├── html-tag.mdx │ │ └── password.mdx │ └── unique-identifier │ │ └── uuid.mdx ├── rust │ └── math │ │ ├── factorial.mdx │ │ └── gcd.mdx └── typescript │ ├── async │ ├── delay.mdx │ └── retry-promise.mdx │ └── utility │ └── debounce.mdx ├── tailwind.config.ts └── tsconfig.json /.env.example: -------------------------------------------------------------------------------- 1 | KV_URL= 2 | KV_REST_API_READ_ONLY_TOKEN= 3 | KV_REST_API_URL= 4 | KV_REST_API_TOKEN= -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bugs.yml: -------------------------------------------------------------------------------- 1 | name: "Bug Report" 2 | description: "Report an issue in the codebase" 3 | title: "[bug] - " 4 | labels: ["bug"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: "# Bug Report" 9 | - type: textarea 10 | id: description 11 | attributes: 12 | label: "What bug did you find?" 13 | description: "Explain the issue in detail. Attach screenshots as necessary." 14 | validations: 15 | required: true 16 | - type: textarea 17 | id: steps 18 | attributes: 19 | label: "Steps to Reproduce" 20 | description: "Provide a clear step-by-step guide to reproduce the issue." 21 | validations: 22 | required: true 23 | - type: input 24 | id: environment 25 | attributes: 26 | label: "Environment Details" 27 | description: "Mention the browser/OS, device, or relevant context." 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/features.yml: -------------------------------------------------------------------------------- 1 | name: "Feature Request" 2 | description: "Suggest a new feature or enhancement" 3 | title: "[feature] - " 4 | labels: ["enhancement", "feature"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: "# Feature Request" 9 | - type: textarea 10 | id: feature 11 | attributes: 12 | label: "What feature would you like to see?" 13 | description: "Describe the feature or enhancement you'd like to add. Be as detailed as possible." 14 | validations: 15 | required: true 16 | - type: textarea 17 | id: reason 18 | attributes: 19 | label: "Why is this feature useful?" 20 | description: "Explain how this feature will benefit users or improve the project." 21 | validations: 22 | required: true 23 | - type: textarea 24 | id: solution 25 | attributes: 26 | label: "Proposed Solution" 27 | description: "Share any ideas or approaches for implementing this feature." 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/other.yml: -------------------------------------------------------------------------------- 1 | name: "Other" 2 | description: "File a miscellaneous issue or request" 3 | title: "[other] - " 4 | labels: ["miscellaneous"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: "# Other Issue" 9 | - type: textarea 10 | id: description 11 | attributes: 12 | label: "What is this issue about?" 13 | description: "Describe the issue, question, or request in detail." 14 | validations: 15 | required: true 16 | - type: textarea 17 | id: additional_info 18 | attributes: 19 | label: "Additional Information" 20 | description: "Provide any extra details, context, or examples related to your issue." 21 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ### What type of change does this PR introduce? 2 | 3 | (Please check all that apply) 4 | 5 | - [ ] 🐛 **Bug Fix**: Fixes a bug or issue. 6 | - [ ] ✨ **New Feature**: Adds a new feature or functionality. 7 | - [ ] 📚 **Snippet Related**: Contributes a code snippet. 8 | - [ ] ⚙️ **Other** (please describe): 9 | 10 | ### Summary of Changes 11 | 12 | Provide a brief description of what this pull request does. Be clear and concise about the purpose of the changes. 13 | 14 | ### Checklist 15 | 16 | Before submitting your pull request, make sure you’ve done the following: 17 | 18 | - [ ] My code follows the project’s guidelines and formatting rules. 19 | - [ ] I’ve thoroughly tested my changes. 20 | - [ ] (for snippets) All folder and file names use the correct casing (e.g., lowercase for languages, kebab-case for categories/files). 21 | - [ ] (for snippets) My changes do not duplicate existing functionality or content. 22 | - [ ] I’ve provided a detailed summary of the changes and their purpose. 23 | 24 | ### Additional Information 25 | 26 | Use this section to add any extra context or information that reviewers might need. Links, screenshots, or anything else relevant can go here. 27 | -------------------------------------------------------------------------------- /.github/workflows/precommit.yml: -------------------------------------------------------------------------------- 1 | name: Pre-Commit Checker 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | precommit: 13 | runs-on: ubuntu-latest 14 | strategy: 15 | matrix: 16 | node-version: [22] 17 | 18 | steps: 19 | - uses: actions/checkout@v4 20 | 21 | - name: Install pnpm 22 | uses: pnpm/action-setup@v4 23 | with: 24 | version: 9 25 | 26 | - name: Use node.js ${{ matrix.node-version }} 27 | uses: actions/setup-node@v4 28 | with: 29 | node-version: ${{ matrix.node-version }} 30 | cache: "pnpm" 31 | 32 | - name: Install dependencies 33 | run: | 34 | pnpm install 35 | 36 | - name: Run pre-commit checks 37 | run: | 38 | pnpm run format-and-lint 39 | pnpm run validate-snippets 40 | -------------------------------------------------------------------------------- /.github/workflows/snippet.yml: -------------------------------------------------------------------------------- 1 | name: Validate Snippets 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - "snippets/**" 7 | push: 8 | paths: 9 | - "snippets/**" 10 | 11 | jobs: 12 | validate-snippets: 13 | runs-on: ubuntu-latest 14 | name: Validate Snippets 15 | strategy: 16 | matrix: 17 | node-version: [22] 18 | steps: 19 | - uses: actions/checkout@v4 20 | 21 | - name: Install pnpm 22 | uses: pnpm/action-setup@v4 23 | with: 24 | version: 9 25 | 26 | - name: Use node.js ${{ matrix.node-version }} 27 | uses: actions/setup-node@v4 28 | with: 29 | node-version: ${{ matrix.node-version }} 30 | cache: "pnpm" 31 | 32 | - name: Install dependencies 33 | run: pnpm install 34 | 35 | - name: Run validation script 36 | run: pnpm validate-snippets 37 | -------------------------------------------------------------------------------- /.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 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | 38 | # webstorm files/folders 39 | .idea -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | pnpm format-and-lint:fix 2 | pnpm validate-snippets 3 | -------------------------------------------------------------------------------- /.vercelignore: -------------------------------------------------------------------------------- 1 | .github 2 | .vscode 3 | .env.example 4 | API.md 5 | CODE_OF_CONDUCT.md 6 | CONTRIBUTING.md 7 | LICENSE.md 8 | README.md 9 | scripts/cli/ 10 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules\\typescript\\lib", 3 | "files.eol": "\n" 4 | } 5 | -------------------------------------------------------------------------------- /API.md: -------------------------------------------------------------------------------- 1 | # API Documentation 2 | 3 | Here's everything you need to know about using the SnipNest API. 4 | 5 | ## 🚦 Rate Limit 6 | 7 | You can make up to **100 requests per minute**. If you go over that, you'll get a 429 error. 8 | 9 | ## 🔗 Available Endpoints 10 | 11 | ### `GET` [/api/languages](https://snipnest.dev/api/languages) 12 | 13 | Gives you a list of all programming languages we support, as an array of strings. 14 | 15 | ```json 16 | ["string"] 17 | ``` 18 | 19 | ### `GET` [/api/[language]](https://snipnest.dev/api/[language]) 20 | 21 | Shows you all categories for a specific language, as an array of strings. 22 | 23 | ```json 24 | ["string"] 25 | ``` 26 | 27 | ### `GET` [/api/[language]/[category]](https://snipnest.dev/api/[language]/[category]) 28 | 29 | Lists all snippets in a category. 30 | 31 | ```json 32 | [ 33 | { 34 | "language": "string", 35 | "category": "string", 36 | "name": "string", 37 | "metadata": { 38 | "name": "string", 39 | "description": "string", 40 | "keywords": ["string"], 41 | "contributors": ["string"] 42 | } 43 | } 44 | ] 45 | ``` 46 | 47 | ### `GET` [/api/[language]/[category]/[name]](https://snipnest.dev/api/[language]/[category]/[name]) 48 | 49 | Returns a single snippet with all its details. 50 | 51 | ```json 52 | { 53 | "language": "string", 54 | "category": "string", 55 | "name": "string", 56 | "metadata": { 57 | "name": "string", 58 | "description": "string", 59 | "keywords": ["string"], 60 | "contributors": ["string"] 61 | } 62 | } 63 | ``` 64 | 65 | ### `GET` [/api/[language]/[category]/[name]/raw](https://snipnest.dev/api/[language]/[category]/[name]/raw) 66 | 67 | Just the code, nothing else. 68 | 69 | ```text 70 | function delay(ms: number): Promise { 71 | ... 72 | ``` 73 | 74 | ### `GET` [/api/search?query=[query]](https://snipnest.dev/api/search?query=[query]) 75 | 76 | Find snippets across the whole site. Works with names, descriptions, and keywords. 77 | 78 | ```json 79 | [ 80 | { 81 | "language": "string", 82 | "category": "string", 83 | "name": "string", 84 | "metadata": { 85 | "name": "string", 86 | "description": "string", 87 | "keywords": ["string"], 88 | "contributors": ["string"] 89 | } 90 | } 91 | ] 92 | ``` 93 | 94 | ## 🚨 Errors 95 | 96 | Every endpoint which has an error will return an object like this: 97 | 98 | ```json 99 | { 100 | "status": 404, 101 | "message": "Snippet not found." 102 | } 103 | ``` 104 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 🌟 2 | 3 | Welcome to SnipNest! To make sure everyone has a great experience, we have a few ground rules. 4 | 5 | ## 🙌 Be Respectful 6 | 7 | Treat everyone with kindness and respect. Harassment, hate speech, or any behavior that makes someone uncomfortable is not okay. 8 | 9 | ## 🤝 Be Helpful 10 | 11 | If someone’s stuck or has a question, help them out if you can! 12 | 13 | ## 👥 Keep It Friendly 14 | 15 | Debates and discussions are great, but keep them constructive. Avoid personal attacks or negative remarks about someone’s work. 16 | 17 | ## 🚫 No Spamming 18 | 19 | Let’s keep our space clean and relevant. Avoid posting spam, unrelated content, or promotional material without permission. 20 | 21 | ## 🎉 Have Fun! 22 | 23 | SnipNest is a place to learn, share, and celebrate coding together. Let’s make it a positive and welcoming community for everyone. 24 | 25 | --- 26 | 27 | By participating in SnipNest, you agree to follow this Code of Conduct. 28 | 29 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 itsbrunodev 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 | [![SnipNest Landing Page](./assets/banner-page-cut.png)](https://snipnest.dev) 2 | 3 | # SnipNest 🪺 4 | 5 | ### Code Smarter, Not Harder. 6 | 7 | Need code that works? Grab a snippet. Made something cool? Share it back. Simple as that. 8 | 9 | [![License](https://badgen.net/github/license/itsbrunodev/snipnest?color=green&label=License)](LICENSE) 10 | [![Stars](https://badgen.net/github/stars/itsbrunodev/snipnest?color=orange&label=Stars)](https://github.com/itsbrunodev/snipnest/stargazers) 11 | [![Issues](https://badgen.net/github/open-issues/itsbrunodev/snipnest?label=Open+Issues)](https://github.com/itsbrunodev/snipnest/issues) 12 | 13 | ## 🚀 What's Inside 14 | 15 | - **Working Snippets**: Code that's tested and ready to use 16 | - **Many Languages**: Pick your favorite - JavaScript, Python, and more 17 | - **Built Together**: Made by developers, for developers 18 | 19 | ## 🎯 Get Started 20 | 21 | Head over to [snipnest.dev](https://snipnest.dev) and start exploring. 22 | 23 | ```sh 24 | # Clone the repository 25 | git clone https://github.com/itsbrunodev/snipnest.git 26 | 27 | # Install dependencies 28 | pnpm install 29 | 30 | # Start development server 31 | pnpm dev 32 | ``` 33 | 34 | ## 📦 Extensions 35 | 36 | Integration with your favorite tools to make SnipNest available when you need it most. 37 | 38 | - [SnipNest for Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=itsbrunodev.snipnest) ([repository](https://github.com/itsbrunodev/snipnest-vscode)) 39 | - Is your favorite extension missing? [Open an issue](https://github.com/itsbrunodev/snipnest/issues/new?assignees=&labels=enhancement%2Cfeature&projects=&template=features.yml&title=%5Bfeature%5D+-+) 40 | 41 | ## 💻 Local Setup 42 | 43 | ### Prerequisites 44 | 45 | - Node.js 18+ 46 | - pnpm (recommended) or npm/yarn 47 | 48 | ### Environment Variables 49 | 50 | For rate limiting in production, you'll need Upstash Redis: 51 | 52 | 1. Copy `.env.example` to `.env.local` 53 | 2. Add your Upstash details ([Get them here](https://upstash.com/docs/redis/overall/getstarted)) 54 | 55 | > Note: Don't worry about rate limiting on localhost or in development mode - it's off by default. 56 | 57 | ### Going Live 58 | 59 | ```bash 60 | pnpm build 61 | pnpm start 62 | ``` 63 | 64 | ## 🤝 Want to Help? 65 | 66 | Check out our [Contributing Guidelines](./CONTRIBUTING.md). You can: 67 | 68 | - Add snippets 69 | - Fix bugs 70 | - Add new features 71 | 72 | ## 📚 API Docs 73 | 74 | Want to build something with our API? [Check the docs](./API.md) 75 | 76 | ## 🌟 Need Help? 77 | 78 | - [Found a bug?](https://github.com/itsbrunodev/snipnest/issues) 79 | - [Have an idea?](https://github.com/itsbrunodev/snipnest/issues) 80 | - [Discussions](https://github.com/itsbrunodev/snipnest/discussions) 81 | 82 | ## 📜 License 83 | 84 | SnipNest is under the [MIT license](./LICENSE). 85 | -------------------------------------------------------------------------------- /app/api/[language]/[category]/[name]/raw/route.ts: -------------------------------------------------------------------------------- 1 | import { getSnippetContent } from "@/lib/snippets"; 2 | import { handleApiError } from "@/lib/utils"; 3 | 4 | import type { SnippetParams } from "@/app/snippets/[language]/[category]/[name]/page"; 5 | 6 | export const dynamic = "force-static"; 7 | 8 | export async function GET(_: unknown, { params }: SnippetParams) { 9 | try { 10 | const { language, category, name } = await params; 11 | 12 | const snippet = await getSnippetContent(language, category, name); 13 | 14 | return new Response(snippet, { 15 | status: 200, 16 | }); 17 | } catch (error) { 18 | return handleApiError(error); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/api/[language]/[category]/[name]/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from "next/server"; 2 | 3 | import { getSnippet } from "@/lib/snippets"; 4 | import { handleApiError } from "@/lib/utils"; 5 | 6 | import type { SnippetParams } from "@/app/snippets/[language]/[category]/[name]/page"; 7 | 8 | export const dynamic = "force-static"; 9 | 10 | export async function GET(_: unknown, { params }: SnippetParams) { 11 | try { 12 | const { language, category, name } = await params; 13 | 14 | const snippet = await getSnippet(language, category, name); 15 | 16 | return NextResponse.json(snippet, { 17 | status: 200, 18 | }); 19 | } catch (error) { 20 | return handleApiError(error); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/api/[language]/[category]/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from "next/server"; 2 | 3 | import { getGroupedSnippets } from "@/lib/snippets"; 4 | import { handleApiError } from "@/lib/utils"; 5 | 6 | import type { SnippetParams } from "@/app/snippets/[language]/[category]/[name]/page"; 7 | 8 | export const dynamic = "force-static"; 9 | 10 | export async function GET(_: unknown, { params }: SnippetParams) { 11 | try { 12 | const { language, category } = await params; 13 | 14 | const groupedSnippets = await getGroupedSnippets(); 15 | 16 | const snippetsOfCategory = groupedSnippets[language][category]; 17 | 18 | return NextResponse.json(snippetsOfCategory, { 19 | status: 200, 20 | }); 21 | } catch (error) { 22 | return handleApiError(error); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/api/[language]/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from "next/server"; 2 | 3 | import { getLanguageCategories } from "@/lib/snippets"; 4 | import { handleApiError } from "@/lib/utils"; 5 | 6 | import type { SnippetParams } from "@/app/snippets/[language]/[category]/[name]/page"; 7 | 8 | export const dynamic = "force-static"; 9 | 10 | export async function GET(_: unknown, { params }: SnippetParams) { 11 | try { 12 | const { language } = await params; 13 | 14 | const languageCategories = await getLanguageCategories(language); 15 | 16 | return NextResponse.json(languageCategories, { 17 | status: 200, 18 | }); 19 | } catch (error) { 20 | return handleApiError(error); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/api/languages/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from "next/server"; 2 | 3 | import { handleApiError } from "@/lib/utils"; 4 | import { LANGUAGES } from "@/lib/languages"; 5 | 6 | export const dynamic = "force-static"; 7 | 8 | export async function GET() { 9 | try { 10 | return NextResponse.json( 11 | LANGUAGES.flatMap((x) => x.value), 12 | { 13 | status: 200, 14 | } 15 | ); 16 | } catch (error) { 17 | return handleApiError(error); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/api/search/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse, type NextRequest } from "next/server"; 2 | 3 | import { getGroupedSnippets } from "@/lib/snippets"; 4 | import { getSnippetList, handleApiError } from "@/lib/utils"; 5 | import { search } from "@/lib/search"; 6 | 7 | export async function GET(request: NextRequest) { 8 | try { 9 | const searchParams = request.nextUrl.searchParams; 10 | const query = searchParams.get("query"); 11 | 12 | if (!query) { 13 | throw new Error("Search query is required. (?query=...)", { 14 | cause: { 15 | status: 400, 16 | }, 17 | }); 18 | } 19 | 20 | const groupedSnippets = await getGroupedSnippets(); 21 | const snippetList = getSnippetList(groupedSnippets); 22 | 23 | const results = search(query, snippetList).map((result) => result.obj); 24 | 25 | return NextResponse.json(results, { 26 | status: 200, 27 | }); 28 | } catch (error) { 29 | return handleApiError(error); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/extensions/page.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import Image from "next/image"; 3 | import Link from "next/link"; 4 | 5 | import { Button } from "@/components/ui/button"; 6 | 7 | import { EXTENSIONS } from "@/lib/extensions"; 8 | 9 | export const metadata: Metadata = { 10 | title: "Extensions - SnipNest", 11 | description: 12 | "Integration with your favorite tools to make SnipNest available when you need it most.", 13 | }; 14 | 15 | export default function ExtensionsPage() { 16 | return ( 17 |
18 |
19 |

Extensions

20 |

21 | Integration with your favorite tools to make SnipNest available when 22 | you need it most. 23 |

24 |
25 |
26 | {Object.entries(EXTENSIONS).map(([key, extension]) => ( 27 | 33 |
34 | {extension.name} 42 |

43 | {extension.name} 44 |

45 |
46 | 52 | 53 | ))} 54 |

55 | Is your favorite tool missing? Help us by{" "} 56 | 61 | submitting an issue 62 | 63 | . 64 |

65 |
66 |
67 | ); 68 | } 69 | -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | body[data-scroll-locked] { 6 | min-width: 100%; 7 | } 8 | 9 | @layer utilities { 10 | .text-balance { 11 | text-wrap: balance; 12 | } 13 | } 14 | 15 | @layer base { 16 | :root { 17 | --background: 0 0% 97.65%; 18 | --foreground: 0 0% 12.55%; 19 | --muted: 0 0% 93.73%; 20 | --muted-foreground: 0 0% 39.22%; 21 | --popover: 0 0% 98.82%; 22 | --popover-foreground: 0 0% 12.55%; 23 | --card: 0 0% 98.82%; 24 | --card-foreground: 0 0% 12.55%; 25 | --border: 0 0% 84.71%; 26 | --input: 0 0% 94.12%; 27 | --primary: 22.93 92.59% 52.35%; 28 | --primary-foreground: 44 0% 100%; 29 | --secondary: 34.05 100% 85.49%; 30 | --secondary-foreground: 16.27 50.43% 22.94%; 31 | --accent: 0 0% 90.98%; 32 | --accent-foreground: 0 0% 12.55%; 33 | --destructive: 10.16 77.87% 53.92%; 34 | --destructive-foreground: 0 0% 100%; 35 | --ring: 24.8 79.79% 63.14%; 36 | --chart-1: 22.93 92.59% 52.35%; 37 | --chart-2: 34.05 100% 85.49%; 38 | --chart-3: 0 0% 90.98%; 39 | --chart-4: 34.05 100% 88.49%; 40 | --chart-5: 22.93 95.59% 52.35%; 41 | --radius: 0.5rem; 42 | } 43 | 44 | .dark { 45 | --background: 0 0% 6.67%; 46 | --foreground: 0 0% 93.33%; 47 | --muted: 0 0% 13.33%; 48 | --muted-foreground: 0 0% 70.59%; 49 | --popover: 0 0% 9.8%; 50 | --popover-foreground: 0 0% 93.33%; 51 | --card: 0 0% 9.8%; 52 | --card-foreground: 0 0% 93.33%; 53 | --border: 44 14% 11%; 54 | --input: 0 0% 28.24%; 55 | --primary: 23 93% 39%; 56 | --primary-foreground: 29.51 0% 100%; 57 | --secondary: 28.5 64.52% 12.16%; 58 | --secondary-foreground: 29.51 100% 88.04%; 59 | --accent: 0 0% 16.47%; 60 | --accent-foreground: 0 0% 93.33%; 61 | --destructive: 10.16 77.87% 53.92%; 62 | --destructive-foreground: 0 0% 100%; 63 | --ring: 23.11 59.8% 40%; 64 | --chart-1: 23 93% 39%; 65 | --chart-2: 28.5 64.52% 12.16%; 66 | --chart-3: 0 0% 16.47%; 67 | --chart-4: 28.5 64.52% 15.16%; 68 | --chart-5: 22.93 95.59% 52.35%; 69 | } 70 | } 71 | 72 | @layer base { 73 | * { 74 | @apply border-border; 75 | scroll-padding-top: 112px; 76 | } 77 | 78 | body { 79 | @apply bg-background text-foreground; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /app/highlight.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | code span[data-highlighted-line] { 7 | @apply bg-zinc-200/60 dark:bg-zinc-800; 8 | } 9 | 10 | code span mark { 11 | @apply rounded-md bg-zinc-200/60 px-1 py-0.5 dark:bg-zinc-800; 12 | } 13 | 14 | span[data-rehype-pretty-code-figure] { 15 | @apply whitespace-nowrap rounded-md border border-zinc-200 bg-zinc-100 px-1 py-0.5 dark:border-zinc-800 dark:bg-zinc-900; 16 | } 17 | 18 | span[data-rehype-pretty-code-figure] code { 19 | @apply before:content-[""] after:content-[""]; 20 | } 21 | 22 | figure[data-rehype-pretty-code-figure] { 23 | @apply !my-0 shadow-sm dark:shadow-none rounded-md; 24 | } 25 | } 26 | 27 | code[data-theme*=" "], 28 | code[data-theme*=" "] span { 29 | color: var(--shiki-light) !important; 30 | } 31 | 32 | html.dark code[data-theme*=" "], 33 | html.dark code[data-theme*=" "] span { 34 | color: var(--shiki-dark) !important; 35 | } 36 | 37 | pre { 38 | overflow-x: auto !important; 39 | } 40 | 41 | pre [data-line] { 42 | padding-left: 1rem; 43 | padding-right: 1rem; 44 | } 45 | 46 | code[data-line-numbers] { 47 | counter-reset: line; 48 | } 49 | 50 | code[data-line-numbers] > [data-line]::before { 51 | counter-increment: line; 52 | content: counter(line); 53 | display: inline-block; 54 | width: 0.75rem; 55 | margin-right: 1rem; 56 | text-align: right; 57 | color: gray; 58 | } 59 | 60 | code[data-line-numbers-max-digits="2"] > [data-line]::before { 61 | width: 1.25rem; 62 | } 63 | 64 | code[data-line-numbers-max-digits="3"] > [data-line]::before { 65 | width: 1.75rem; 66 | } 67 | 68 | code[data-line-numbers-max-digits="4"] > [data-line]::before { 69 | width: 2.25rem; 70 | } 71 | 72 | figure[data-rehype-pretty-code-figure] { 73 | margin-top: 0.5rem; 74 | margin-bottom: 0.5rem; 75 | } 76 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata, Viewport } from "next"; 2 | import { Inter, Fira_Code } from "next/font/google"; 3 | import Script from "next/script"; 4 | 5 | const sansFont = Inter({ 6 | subsets: ["latin"], 7 | display: "swap", 8 | }); 9 | 10 | const monoFont = Fira_Code({ 11 | subsets: ["latin"], 12 | display: "swap", 13 | variable: "--font-mono", 14 | }); 15 | 16 | import { getGroupedSnippets } from "@/lib/snippets"; 17 | import { cn, getSnippetList } from "@/lib/utils"; 18 | 19 | import { ThemeProvider } from "@/components/theme/provider"; 20 | 21 | import { Navbar } from "@/components/navbar"; 22 | import { Footer } from "@/components/footer"; 23 | 24 | import { SearchProvider } from "@/components/search/provider"; 25 | import { SearchDialog } from "@/components/search/dialog"; 26 | 27 | import "./globals.css"; 28 | import "./highlight.css"; 29 | 30 | export const metadata: Metadata = { 31 | metadataBase: new URL("https://snipnest.dev"), 32 | title: "SnipNest - Open-source code snippet collection", 33 | description: 34 | "Need code that works? Grab a snippet. Made something cool? Share it back. Simple as that.", 35 | alternates: { 36 | canonical: "https://snipnest.dev", 37 | }, 38 | icons: { 39 | icon: [ 40 | { 41 | media: "(prefers-color-scheme: light)", 42 | url: "/favicon-light.png", 43 | href: "/favicon-light.png", 44 | }, 45 | { 46 | media: "(prefers-color-scheme: dark)", 47 | url: "/favicon-dark.png", 48 | href: "/favicon-dark.png", 49 | }, 50 | ], 51 | }, 52 | openGraph: { 53 | tags: [ 54 | "snippets", 55 | "code", 56 | "community", 57 | "share", 58 | "tool", 59 | "useful", 60 | "development", 61 | ], 62 | images: [ 63 | { 64 | url: "/banner.png", 65 | href: "/banner.png", 66 | }, 67 | ], 68 | }, 69 | }; 70 | 71 | export const viewport: Viewport = { 72 | themeColor: "#f66b15", 73 | }; 74 | 75 | export default async function RootLayout({ 76 | children, 77 | }: Readonly<{ 78 | children: React.ReactNode; 79 | }>) { 80 | const groupedSnippets = await getGroupedSnippets(); 81 | const snippetList = getSnippetList(groupedSnippets); 82 | 83 | return ( 84 | 89 | 96 | 97 | 98 |
102 | 103 |
104 | {children} 105 | 106 |
107 |
108 |
109 |
110 |
111 |