create-next-mdx-blog-app 2.0.0 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +17 -6
- package/README.md +74 -8
- package/mdx.d.ts +5 -0
- package/next.config.ts +1 -1
- package/package.json +26 -26
- package/src/app/globals.css +47 -0
- package/src/app/layout.tsx +6 -0
- package/src/components/ArticleAuthorBio.tsx +7 -7
- package/src/components/ArticleHeader.tsx +18 -16
- package/src/components/customMDXComponents/CodeBlock.tsx +64 -19
- package/src/components/customMDXComponents/GistCodeBlock.tsx +19 -0
- package/src/components/customMDXComponents/GistCopyButton.tsx +57 -0
- package/src/components/customMDXComponents/GitHubGist.tsx +84 -44
- package/src/components/customMDXComponents/MDXImage.tsx +23 -21
- package/src/markdown/ArticleContent.mdx +2 -3
- package/src/utils/constants/GitHubGistConstants.ts +77 -0
- package/src/utils/constants/index.ts +1 -0
- package/tsconfig.json +2 -1
package/.env.example
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
|
-
# Supabase — required for dynamic blog post fetching
|
|
2
|
-
SUPABASE_URL=
|
|
3
|
-
SUPABASE_ANON_KEY=
|
|
4
|
-
|
|
5
|
-
# Anthropic — required for the Blog Assistant chat feature
|
|
6
|
-
ANTHROPIC_API_KEY=
|
|
1
|
+
# Supabase — required for dynamic blog post fetching
|
|
2
|
+
SUPABASE_URL=
|
|
3
|
+
SUPABASE_ANON_KEY=
|
|
4
|
+
|
|
5
|
+
# Anthropic — required for the Blog Assistant chat feature
|
|
6
|
+
ANTHROPIC_API_KEY=
|
|
7
|
+
|
|
8
|
+
# GitHub — optional but recommended for the <GitHubGist> component
|
|
9
|
+
# Without a token, the GitHub API allows 60 requests/hr per IP.
|
|
10
|
+
# With a token (read-only gist scope), the limit rises to 5,000 requests/hr.
|
|
11
|
+
GITHUB_TOKEN=
|
|
12
|
+
|
|
13
|
+
# GitHub username used to construct mdxgists.net URLs (defaults to CodingAbdullah)
|
|
14
|
+
GITHUB_USERNAME=
|
|
15
|
+
|
|
16
|
+
# Base URL for mdxgists.net links (defaults to https://mdxgists.net)
|
|
17
|
+
GIST_BASE_URL=
|
package/README.md
CHANGED
|
@@ -59,7 +59,7 @@ npx create-next-mdx-blog-app .
|
|
|
59
59
|
### Package Information
|
|
60
60
|
|
|
61
61
|
- **Package Name**: `create-next-mdx-blog-app`
|
|
62
|
-
- **Version**: `2.
|
|
62
|
+
- **Version**: `2.1.1`
|
|
63
63
|
- **License**: MIT
|
|
64
64
|
- **Homepage**: [https://www.npmjs.com/package/create-next-mdx-blog-app](https://www.npmjs.com/package/create-next-mdx-blog-app/)
|
|
65
65
|
|
|
@@ -83,7 +83,7 @@ npx create-next-mdx-blog-app .
|
|
|
83
83
|
- **@ai-sdk/anthropic**: Anthropic provider for the Vercel AI SDK, used to connect to Claude models.
|
|
84
84
|
- **@ai-sdk/react**: React hooks (`useChat`) for building streaming AI chat interfaces.
|
|
85
85
|
- **zod**: TypeScript-first schema validation library used to define and validate AI tool parameters.
|
|
86
|
-
- **sonner**: Lightweight toast notification library used for user feedback
|
|
86
|
+
- **sonner**: Lightweight toast notification library used for user feedback across the Code Sandbox, the `CodeBlock` copy button, and the GitHub Gist copy button.
|
|
87
87
|
|
|
88
88
|
## 🌐 Static/Dynamic Rendering with MDX
|
|
89
89
|
This project utilizes MDX for both static and dynamic rendering of blog posts. The two MDX files included in the project serve as examples of how to structure your content.
|
|
@@ -111,19 +111,56 @@ The project includes a custom `CodeBlock` component for syntax highlighting code
|
|
|
111
111
|
|
|
112
112
|
Default theme is set to the `vscDarkPlus` theme. Feel free to modify the theme and even add your own syntax highlighting library if you so choose.
|
|
113
113
|
|
|
114
|
-
|
|
114
|
+
A **copy button** is rendered in the top-right corner of every code block. Clicking it writes the code to the clipboard and triggers a sonner toast notification confirming success or failure.
|
|
115
115
|
|
|
116
|
+
You can read more about the library used in this project <a href="https://react-syntax-highlighter.github.io/react-syntax-highlighter/demo/">here.</a>
|
|
116
117
|
|
|
117
118
|
### GitHub Gists
|
|
118
|
-
|
|
119
|
+
GitHub Gists can be embedded directly in MDX files using the `<GitHubGist>` custom component. The component fetches gist content live from the **GitHub REST API** at render time and caches the result for one hour via Next.js ISR (`next: { revalidate: 3600 }`).
|
|
120
|
+
|
|
121
|
+
```mdx
|
|
122
|
+
<GitHubGist id="your-gist-id" figCaptionText="Caption shown below the gist" />
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
#### How it works
|
|
126
|
+
1. `GitHubGist` calls `https://api.github.com/gists/<id>` to retrieve metadata (file name, detected language).
|
|
127
|
+
2. It then fetches the raw file content from the `raw_url` returned by the API.
|
|
128
|
+
3. The language is mapped to a Prism-compatible token using the built-in `GITHUB_GIST_LANGUAGE_MAP` constant (70+ languages supported — see `src/utils/constants/GitHubGistConstants.ts`).
|
|
129
|
+
4. The code is rendered via `GistCodeBlock` (syntax-highlighted with `react-syntax-highlighter`) inside a scrollable container with a custom green scrollbar (`scrollbar-gist`).
|
|
130
|
+
|
|
131
|
+
#### mdxgists.net
|
|
132
|
+
Each rendered gist includes a header bar with:
|
|
133
|
+
- The detected **language badge**
|
|
134
|
+
- A **Copy** button that writes the raw content to clipboard with a sonner toast confirmation
|
|
135
|
+
- A **mdxgists.net** link that opens the gist on [mdxgists.net](https://mdxgists.net) — a companion site for browsing and sharing MDX-ready gists
|
|
136
|
+
|
|
137
|
+
The mdxgists.net URL is constructed as `<GIST_BASE_URL>/<GITHUB_USERNAME>/<gist-id>` using values from `GitHubGistConstants.ts`. Both values can be overridden via environment variables (see below).
|
|
138
|
+
|
|
139
|
+
#### Components
|
|
140
|
+
- `src/components/customMDXComponents/GitHubGist.tsx` — async Server Component; fetches from GitHub API, resolves language, builds the mdxgists.net URL, renders the full gist UI
|
|
141
|
+
- `src/components/customMDXComponents/GistCodeBlock.tsx` — syntax-highlighted code display using `react-syntax-highlighter`
|
|
142
|
+
- `src/components/customMDXComponents/GistCopyButton.tsx` — `"use client"` component; copy-to-clipboard with sonner toast + mdxgists.net external link
|
|
143
|
+
|
|
144
|
+
#### Language Detection
|
|
145
|
+
The `GITHUB_GIST_LANGUAGE_MAP` in `src/utils/constants/GitHubGistConstants.ts` maps GitHub's reported language names to Prism-compatible identifiers. Over 70 languages are supported out of the box including TypeScript, Python, Rust, Go, Solidity, YAML, SQL, and more. If a language is not in the map, the component falls back to `language.toLowerCase()`. If no language is detected, it falls back to `text`.
|
|
146
|
+
|
|
147
|
+
#### GitHub OAuth Token (optional but recommended)
|
|
148
|
+
Unauthenticated requests to the GitHub API are rate-limited to **60 requests per hour** per IP. For production use, set a `GITHUB_TOKEN` environment variable to increase this to **5,000 requests per hour**:
|
|
119
149
|
|
|
120
|
-
|
|
150
|
+
```bash
|
|
151
|
+
# .env.local
|
|
152
|
+
GITHUB_TOKEN=ghp_your_personal_access_token
|
|
153
|
+
```
|
|
121
154
|
|
|
122
|
-
|
|
155
|
+
To generate a token: GitHub → Settings → Developer settings → Personal access tokens → Generate new token. The token only needs the **read-only** `gist` scope (or no scopes at all for public gists).
|
|
156
|
+
|
|
157
|
+
> Only **public** gists are supported by default. The component can be modified to support private gists by adding the appropriate scopes to the token.
|
|
123
158
|
|
|
124
159
|
### MDX Images
|
|
125
160
|
The project comes with its own `MDXImage` component that utilizes the Next.js built-in `Image` component as well as the built-in `figure` and `figcaption` elements to integrate imaging and captions seamlessly.
|
|
126
161
|
|
|
162
|
+
Images are displayed inside a styled container with a **green glow border** and a **hover scale effect** consistent with the rest of the matrix-green design system.
|
|
163
|
+
|
|
127
164
|
## 🖥️ Code Sandbox
|
|
128
165
|
The project includes an interactive in-browser code execution environment powered by **Sandpack** (<b>route</b>: `/code-sandbox`).
|
|
129
166
|
|
|
@@ -187,12 +224,18 @@ cp .env.example .env.local
|
|
|
187
224
|
| `SUPABASE_URL` | Dynamic blog post fetching (Supabase client) |
|
|
188
225
|
| `SUPABASE_ANON_KEY` | Dynamic blog post fetching (Supabase client) |
|
|
189
226
|
| `ANTHROPIC_API_KEY` | Blog Assistant chatbot (`/api/chat` edge route) |
|
|
227
|
+
| `GITHUB_TOKEN` | GitHub API authentication for `<GitHubGist>` — optional but raises rate limit from 60 to 5,000 req/hr |
|
|
228
|
+
| `GITHUB_USERNAME` | Your GitHub username, used to construct the mdxgists.net URL (defaults to `CodingAbdullah`) |
|
|
229
|
+
| `GIST_BASE_URL` | Base URL for mdxgists.net links (defaults to `https://mdxgists.net`) |
|
|
190
230
|
|
|
191
231
|
```
|
|
192
232
|
# .env.local
|
|
193
233
|
SUPABASE_URL=
|
|
194
234
|
SUPABASE_ANON_KEY=
|
|
195
235
|
ANTHROPIC_API_KEY=
|
|
236
|
+
GITHUB_TOKEN=
|
|
237
|
+
GITHUB_USERNAME=
|
|
238
|
+
GIST_BASE_URL=
|
|
196
239
|
```
|
|
197
240
|
|
|
198
241
|
## 🌩️ AWS
|
|
@@ -210,7 +253,7 @@ docker build -t mdx-medium-blog .
|
|
|
210
253
|
``
|
|
211
254
|
|
|
212
255
|
``
|
|
213
|
-
docker run -e SUPABASE_URL=your_supabase_url \ -e SUPABASE_ANON_KEY=your_supabase_anon_key \ -e ANTHROPIC_API_KEY=your_anthropic_api_key \ -p 3000:3000 mdx-medium-blog
|
|
256
|
+
docker run -e SUPABASE_URL=your_supabase_url \ -e SUPABASE_ANON_KEY=your_supabase_anon_key \ -e ANTHROPIC_API_KEY=your_anthropic_api_key \ -e GITHUB_TOKEN=your_github_token \ -e GITHUB_USERNAME=your_github_username \ -e GIST_BASE_URL=https://mdxgists.net \ -p 3000:3000 mdx-medium-blog
|
|
214
257
|
``
|
|
215
258
|
|
|
216
259
|
## 🔄 CRUD Operations and Supabase Actions
|
|
@@ -227,7 +270,7 @@ The `/scripts` folder contains various scripts to help set up the project and da
|
|
|
227
270
|
- **Bash Shell Script**: Script for setting up project on Linux, Mac, etc.
|
|
228
271
|
|
|
229
272
|
### GitHub Gist Fetcher
|
|
230
|
-
|
|
273
|
+
> **Note:** This script is no longer required for rendering gists in MDX. The `<GitHubGist>` component now fetches content live from the GitHub API at render time. This script is retained for offline inspection or local caching workflows.
|
|
231
274
|
|
|
232
275
|
**Location**: `/scripts/github-gist-fetcher/fetch-github-gist.ts`
|
|
233
276
|
|
|
@@ -270,6 +313,29 @@ The article slug is the name of the markdown file located in the `/src/markdown`
|
|
|
270
313
|
|
|
271
314
|
The update statement takes in an additional parameter which is also the same thing (file name minus the `.mdx` extension located in the `/src/markdown` directory).
|
|
272
315
|
|
|
316
|
+
## 📱 Mobile Responsiveness
|
|
317
|
+
All components in the starter kit are built mobile-first using Tailwind CSS responsive prefixes (`sm:`, `md:`, `lg:`).
|
|
318
|
+
|
|
319
|
+
Key responsive behaviours:
|
|
320
|
+
|
|
321
|
+
| Component | Mobile behaviour |
|
|
322
|
+
|---|---|
|
|
323
|
+
| `ArticleHeader` | Title scales from `text-2xl` → `text-4xl`; author row stacks vertically on small screens |
|
|
324
|
+
| `ArticleAuthorBio` | Avatar and text stack vertically; button goes full-width; text is center-aligned on mobile |
|
|
325
|
+
| `GitHubGist` | Header wraps via `flex-wrap`; code area scrolls horizontally with a styled green scrollbar |
|
|
326
|
+
| `CodeBlock` | Copy button is absolutely positioned and does not interfere with code flow |
|
|
327
|
+
| `MDXImage` | Image container scales inline; hover effect applies on capable devices |
|
|
328
|
+
| Chat & Code Sandbox | Responsive padding and height calculations across all breakpoints |
|
|
329
|
+
|
|
330
|
+
The root layout exports a `viewport` configuration (via Next.js `Viewport` type) ensuring correct scaling on all mobile browsers:
|
|
331
|
+
|
|
332
|
+
```ts
|
|
333
|
+
export const viewport: Viewport = {
|
|
334
|
+
width: 'device-width',
|
|
335
|
+
initialScale: 1,
|
|
336
|
+
};
|
|
337
|
+
```
|
|
338
|
+
|
|
273
339
|
## 📊 Analytics
|
|
274
340
|
Integrated in this setup project is Vercel Analytics (`@vercel/analytics`) to track user interactions and performance metrics of the blog.
|
|
275
341
|
|
package/mdx.d.ts
ADDED
package/next.config.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-next-mdx-blog-app",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"bin": {
|
|
5
5
|
"create-next-mdx-blog-app": "./bin/create.js"
|
|
6
6
|
},
|
|
@@ -29,46 +29,46 @@
|
|
|
29
29
|
"lint": "next lint"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@ai-sdk/anthropic": "^3.0.
|
|
33
|
-
"@ai-sdk/react": "^3.0.
|
|
34
|
-
"@codesandbox/sandpack-react": "^2.
|
|
32
|
+
"@ai-sdk/anthropic": "^3.0.64",
|
|
33
|
+
"@ai-sdk/react": "^3.0.143",
|
|
34
|
+
"@codesandbox/sandpack-react": "^2.20.0",
|
|
35
35
|
"@mdx-js/loader": "^3.1.1",
|
|
36
36
|
"@mdx-js/react": "^3.1.1",
|
|
37
|
-
"@next/mdx": "^16.
|
|
38
|
-
"@radix-ui/react-avatar": "^1.1.
|
|
39
|
-
"@radix-ui/react-slot": "^1.2.
|
|
40
|
-
"@supabase/supabase-js": "^2.
|
|
37
|
+
"@next/mdx": "^16.2.1",
|
|
38
|
+
"@radix-ui/react-avatar": "^1.1.11",
|
|
39
|
+
"@radix-ui/react-slot": "^1.2.4",
|
|
40
|
+
"@supabase/supabase-js": "^2.101.0",
|
|
41
41
|
"@types/mdx": "^2.0.13",
|
|
42
42
|
"@types/react-syntax-highlighter": "^15.5.13",
|
|
43
|
-
"@vercel/analytics": "^
|
|
44
|
-
"ai": "^6.0.
|
|
43
|
+
"@vercel/analytics": "^2.0.1",
|
|
44
|
+
"ai": "^6.0.141",
|
|
45
45
|
"class-variance-authority": "^0.7.1",
|
|
46
46
|
"clsx": "^2.1.1",
|
|
47
47
|
"degit": "^2.8.4",
|
|
48
|
-
"execa": "^9.6.
|
|
48
|
+
"execa": "^9.6.1",
|
|
49
49
|
"gray-matter": "^4.0.3",
|
|
50
|
-
"inquirer": "^
|
|
51
|
-
"lucide-react": "^
|
|
52
|
-
"next": "^16.
|
|
50
|
+
"inquirer": "^13.3.2",
|
|
51
|
+
"lucide-react": "^1.7.0",
|
|
52
|
+
"next": "^16.2.1",
|
|
53
53
|
"next-mdx-remote": "^6.0.0",
|
|
54
|
-
"react": "^19.2.
|
|
55
|
-
"react-dom": "^19.2.
|
|
56
|
-
"react-syntax-highlighter": "^
|
|
57
|
-
"sonner": "^
|
|
58
|
-
"tailwind-merge": "^3.
|
|
59
|
-
"tsx": "^4.
|
|
60
|
-
"zod": "^3.
|
|
54
|
+
"react": "^19.2.4",
|
|
55
|
+
"react-dom": "^19.2.4",
|
|
56
|
+
"react-syntax-highlighter": "^16.1.1",
|
|
57
|
+
"sonner": "^2.0.7",
|
|
58
|
+
"tailwind-merge": "^3.5.0",
|
|
59
|
+
"tsx": "^4.21.0",
|
|
60
|
+
"zod": "^4.3.6"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
63
|
"@eslint/eslintrc": "^3",
|
|
64
64
|
"@tailwindcss/postcss": "^4",
|
|
65
|
-
"@types/node": "^
|
|
65
|
+
"@types/node": "^25.5.0",
|
|
66
66
|
"@types/react": "^19",
|
|
67
67
|
"@types/react-dom": "^19",
|
|
68
|
-
"eslint": "^
|
|
69
|
-
"eslint-config-next": "
|
|
68
|
+
"eslint": "^10",
|
|
69
|
+
"eslint-config-next": "16.2.1",
|
|
70
70
|
"tailwindcss": "^4",
|
|
71
|
-
"tw-animate-css": "^1.
|
|
72
|
-
"typescript": "
|
|
71
|
+
"tw-animate-css": "^1.4.0",
|
|
72
|
+
"typescript": "6.0.2"
|
|
73
73
|
}
|
|
74
74
|
}
|
package/src/app/globals.css
CHANGED
|
@@ -174,4 +174,51 @@
|
|
|
174
174
|
.matrix-scrollbar::-webkit-scrollbar-thumb:hover {
|
|
175
175
|
background: rgba(0, 200, 0, 0.5);
|
|
176
176
|
}
|
|
177
|
+
|
|
178
|
+
/* Enhanced scrollbar for GitHub gist component */
|
|
179
|
+
.scrollbar-gist {
|
|
180
|
+
scrollbar-width: thin;
|
|
181
|
+
scrollbar-color: rgba(34, 197, 94, 0.7) rgba(15, 23, 42, 0.8);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.scrollbar-gist::-webkit-scrollbar {
|
|
185
|
+
width: 10px;
|
|
186
|
+
height: 10px;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.scrollbar-gist::-webkit-scrollbar-track {
|
|
190
|
+
background: rgba(15, 23, 42, 0.9);
|
|
191
|
+
border-radius: 6px;
|
|
192
|
+
border: 1px solid rgba(34, 197, 94, 0.2);
|
|
193
|
+
box-shadow: inset 0 0 4px rgba(0, 0, 0, 0.5);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.scrollbar-gist::-webkit-scrollbar-thumb {
|
|
197
|
+
background: linear-gradient(135deg,
|
|
198
|
+
rgba(34, 197, 94, 0.9) 0%,
|
|
199
|
+
rgba(22, 163, 74, 0.8) 30%,
|
|
200
|
+
rgba(21, 128, 61, 0.7) 70%,
|
|
201
|
+
rgba(34, 197, 94, 0.9) 100%);
|
|
202
|
+
border-radius: 6px;
|
|
203
|
+
border: 1px solid rgba(34, 197, 94, 0.4);
|
|
204
|
+
box-shadow: 0 0 8px rgba(34, 197, 94, 0.5),
|
|
205
|
+
inset 0 1px 0 rgba(34, 197, 94, 0.3);
|
|
206
|
+
transition: all 0.3s ease;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.scrollbar-gist::-webkit-scrollbar-thumb:hover {
|
|
210
|
+
background: linear-gradient(135deg,
|
|
211
|
+
rgba(34, 197, 94, 1) 0%,
|
|
212
|
+
rgba(22, 163, 74, 0.9) 30%,
|
|
213
|
+
rgba(21, 128, 61, 0.8) 70%,
|
|
214
|
+
rgba(34, 197, 94, 1) 100%);
|
|
215
|
+
box-shadow: 0 0 12px rgba(34, 197, 94, 0.7),
|
|
216
|
+
inset 0 1px 0 rgba(34, 197, 94, 0.4);
|
|
217
|
+
transform: scale(1.05);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.scrollbar-gist::-webkit-scrollbar-corner {
|
|
221
|
+
background: rgba(15, 23, 42, 0.9);
|
|
222
|
+
border: 1px solid rgba(34, 197, 94, 0.2);
|
|
223
|
+
}
|
|
177
224
|
}
|
package/src/app/layout.tsx
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import "@/app/globals.css";
|
|
2
2
|
import { Analytics } from "@vercel/analytics/next";
|
|
3
3
|
import { Toaster } from "sonner";
|
|
4
|
+
import type { Viewport } from "next";
|
|
5
|
+
|
|
6
|
+
export const viewport: Viewport = {
|
|
7
|
+
width: "device-width",
|
|
8
|
+
initialScale: 1,
|
|
9
|
+
};
|
|
4
10
|
|
|
5
11
|
// Root Layout for the simple Next.js application
|
|
6
12
|
export default function RootLayout({
|
|
@@ -7,16 +7,16 @@ import type { ArticleAuthorInfoType } from "@/utils/types";
|
|
|
7
7
|
// Article Author Bio Section component
|
|
8
8
|
export default function ArticleAuthorBio({ authorInformation }: { authorInformation: ArticleAuthorInfoType }) {
|
|
9
9
|
return (
|
|
10
|
-
<div className="glass-card p-6 mb-12">
|
|
11
|
-
<div className="flex items-start">
|
|
12
|
-
<Avatar className="
|
|
10
|
+
<div className="glass-card p-4 sm:p-6 mb-8 sm:mb-12">
|
|
11
|
+
<div className="flex flex-col sm:flex-row items-center sm:items-start space-y-4 sm:space-y-0">
|
|
12
|
+
<Avatar className="mr-0 sm:mr-6 mb-4 sm:mb-0 border-2 border-green-500/30">
|
|
13
13
|
<Image src={authorInformation.authorProfileImageURL} alt="No Name" width={64} height={64} className="rounded-full object-cover" />
|
|
14
14
|
</Avatar>
|
|
15
|
-
<div>
|
|
16
|
-
<h3 className="text-xl font-semibold mb-2 matrix-glow text-green-300">{authorInformation.authorName}</h3>
|
|
17
|
-
<p className="text-green-200/80 mb-3">{authorInformation.authorDescription}</p>
|
|
15
|
+
<div className="text-center sm:text-left w-full">
|
|
16
|
+
<h3 className="text-lg sm:text-xl font-semibold mb-2 matrix-glow text-green-300">{authorInformation.authorName}</h3>
|
|
17
|
+
<p className="text-green-200/80 mb-3 text-sm sm:text-base">{authorInformation.authorDescription}</p>
|
|
18
18
|
<Link href="/">
|
|
19
|
-
<Button className="bg-green-600 hover:bg-green-700 text-white border-none">
|
|
19
|
+
<Button className="bg-green-600 hover:bg-green-700 text-white border-none w-full sm:w-auto text-sm sm:text-base">
|
|
20
20
|
Go Back To Home Page
|
|
21
21
|
</Button>
|
|
22
22
|
</Link>
|
|
@@ -11,26 +11,28 @@ export default function ArticleHeader({ articleHeaderInformation } : { articleHe
|
|
|
11
11
|
<Badge className="mb-3 bg-green-900/60 text-green-100 border border-green-500/50">
|
|
12
12
|
{ articleHeaderInformation.articleTags[0] }
|
|
13
13
|
</Badge>
|
|
14
|
-
<h1 className="text-3xl md:text-
|
|
14
|
+
<h1 className="text-2xl sm:text-3xl md:text-4xl font-bold mb-4 sm:mb-6 matrix-glow leading-tight text-green-300">
|
|
15
15
|
{ articleHeaderInformation.articleTitle }
|
|
16
16
|
</h1>
|
|
17
|
-
<p className="text-xl text-green-200/80 mb-6 leading-relaxed">
|
|
17
|
+
<p className="text-base sm:text-xl text-green-200/80 mb-4 sm:mb-6 leading-relaxed">
|
|
18
18
|
{ articleHeaderInformation.articleDescription }
|
|
19
|
-
</p>
|
|
19
|
+
</p>
|
|
20
20
|
{/* Author info and publish date */}
|
|
21
|
-
<div className="flex items-center mb-6">
|
|
22
|
-
<
|
|
23
|
-
<
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
<
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
21
|
+
<div className="flex flex-col sm:flex-row sm:items-center mb-4 sm:mb-6 space-y-2 sm:space-y-0">
|
|
22
|
+
<div className="flex items-center">
|
|
23
|
+
<Avatar className="h-10 w-10 sm:h-12 sm:w-12 mr-3 sm:mr-4 border-2 border-green-500/30">
|
|
24
|
+
<Image src={ articleHeaderInformation.articleAuthorInfo.authorProfileImageURL } alt="No Name Exists" width={48} height={48} className="rounded-full object-cover" />
|
|
25
|
+
</Avatar>
|
|
26
|
+
<div>
|
|
27
|
+
<p className="font-medium text-green-300 text-sm sm:text-base">{ articleHeaderInformation.articleAuthorInfo.authorName }</p>
|
|
28
|
+
<p className="text-xs sm:text-sm text-green-400/70">
|
|
29
|
+
Published on {new Date(Date.now()).toLocaleDateString('en-US', {
|
|
30
|
+
year: 'numeric',
|
|
31
|
+
month: 'long',
|
|
32
|
+
day: 'numeric'
|
|
33
|
+
})} • { articleHeaderInformation.articleReadingTime }
|
|
34
|
+
</p>
|
|
35
|
+
</div>
|
|
34
36
|
</div>
|
|
35
37
|
</div>
|
|
36
38
|
{/* Cover image */}
|
|
@@ -1,19 +1,64 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
1
|
+
"use client";
|
|
2
|
+
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
|
3
|
+
import { vscDarkPlus } from 'react-syntax-highlighter/dist/cjs/styles/prism';
|
|
4
|
+
import { toast } from 'sonner';
|
|
5
|
+
|
|
6
|
+
// Custom code block component for handling code in MDX files
|
|
7
|
+
// Visual Studio Code Dark Plus theme with copy functionality
|
|
8
|
+
const CodeBlock = ({ className = '', children }: { className?: string; children: string }) => {
|
|
9
|
+
const match = /language-(\w+)/.exec(className || '');
|
|
10
|
+
const language = match?.[1] || 'text';
|
|
11
|
+
const code = String(children).trim();
|
|
12
|
+
|
|
13
|
+
// Copy content and receive a toast message based on action
|
|
14
|
+
const copyToClipboard = async () => {
|
|
15
|
+
try {
|
|
16
|
+
await navigator.clipboard.writeText(code);
|
|
17
|
+
toast.success('Code copied!', {
|
|
18
|
+
style: {
|
|
19
|
+
background: '#0d1117',
|
|
20
|
+
border: '1px solid #22c55e',
|
|
21
|
+
color: 'white'
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
toast.error('Failed to copy code', {
|
|
27
|
+
style: {
|
|
28
|
+
background: '#0d1117',
|
|
29
|
+
border: '1px solid #22c55e',
|
|
30
|
+
color: 'white'
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<div className="relative my-6 group">
|
|
38
|
+
<div className="absolute top-3 right-3 z-10">
|
|
39
|
+
<button
|
|
40
|
+
onClick={copyToClipboard}
|
|
41
|
+
className="flex items-center gap-1 text-xs text-green-400/70 hover:text-green-300 transition-colors duration-200 bg-black/60 hover:bg-black/80 px-2 py-1 rounded border border-green-500/20 backdrop-blur-sm"
|
|
42
|
+
>
|
|
43
|
+
<svg className="w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
|
|
44
|
+
<path d="M8 3a1 1 0 011-1h2a1 1 0 110 2H9a1 1 0 01-1-1z" />
|
|
45
|
+
<path d="M6 3a2 2 0 00-2 2v11a2 2 0 002 2h8a2 2 0 002-2V5a2 2 0 00-2-2 3 3 0 01-3 3H9a3 3 0 01-3-3z" />
|
|
46
|
+
</svg>
|
|
47
|
+
Copy
|
|
48
|
+
</button>
|
|
49
|
+
</div>
|
|
50
|
+
<SyntaxHighlighter
|
|
51
|
+
language={language}
|
|
52
|
+
style={vscDarkPlus}
|
|
53
|
+
className="rounded-lg"
|
|
54
|
+
customStyle={{
|
|
55
|
+
paddingTop: '2rem',
|
|
56
|
+
}}
|
|
57
|
+
>
|
|
58
|
+
{code}
|
|
59
|
+
</SyntaxHighlighter>
|
|
60
|
+
</div>
|
|
61
|
+
);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export default CodeBlock;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
|
2
|
+
import { vscDarkPlus } from 'react-syntax-highlighter/dist/cjs/styles/prism';
|
|
3
|
+
|
|
4
|
+
interface GistCodeBlockProps {
|
|
5
|
+
content: string;
|
|
6
|
+
language: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export default function GistCodeBlock({ content, language }: GistCodeBlockProps) {
|
|
10
|
+
return (
|
|
11
|
+
<SyntaxHighlighter
|
|
12
|
+
language={language}
|
|
13
|
+
style={vscDarkPlus}
|
|
14
|
+
customStyle={{ margin: 0, background: 'transparent', fontSize: '0.875rem' }}
|
|
15
|
+
>
|
|
16
|
+
{content}
|
|
17
|
+
</SyntaxHighlighter>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { toast } from 'sonner';
|
|
3
|
+
|
|
4
|
+
interface GistCopyButtonProps {
|
|
5
|
+
content: string;
|
|
6
|
+
mdxGHGistURL: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export default function GistCopyButton({ content, mdxGHGistURL }: GistCopyButtonProps) {
|
|
10
|
+
const copyToClipboard = async () => {
|
|
11
|
+
try {
|
|
12
|
+
await navigator.clipboard.writeText(content);
|
|
13
|
+
toast.success('GitHub Gist copied!', {
|
|
14
|
+
style: {
|
|
15
|
+
background: '#0d1117',
|
|
16
|
+
border: '1px solid #22c55e',
|
|
17
|
+
color: 'white'
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
toast.error('Failed to copy GitHub Gist', {
|
|
23
|
+
style: {
|
|
24
|
+
background: '#0d1117',
|
|
25
|
+
border: '1px solid #22c55e',
|
|
26
|
+
color: 'white'
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<div className="flex items-center gap-2 shrink-0">
|
|
34
|
+
<button
|
|
35
|
+
onClick={copyToClipboard}
|
|
36
|
+
className="flex items-center gap-1 text-xs text-green-400/70 hover:text-green-300 transition-colors duration-200 bg-green-500/10 hover:bg-green-500/20 px-2 py-1 rounded border border-green-500/20"
|
|
37
|
+
>
|
|
38
|
+
<svg className="w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
|
|
39
|
+
<path d="M8 3a1 1 0 011-1h2a1 1 0 110 2H9a1 1 0 01-1-1z" />
|
|
40
|
+
<path d="M6 3a2 2 0 00-2 2v11a2 2 0 002 2h8a2 2 0 002-2V5a2 2 0 00-2-2 3 3 0 01-3 3H9a3 3 0 01-3-3z" />
|
|
41
|
+
</svg>
|
|
42
|
+
Copy
|
|
43
|
+
</button>
|
|
44
|
+
<a
|
|
45
|
+
href={mdxGHGistURL}
|
|
46
|
+
target="_blank"
|
|
47
|
+
rel="noopener noreferrer"
|
|
48
|
+
className="flex items-center gap-1 text-xs text-green-400/70 hover:text-green-300 transition-colors duration-200 bg-green-500/10 hover:bg-green-500/20 px-2 py-1 rounded border border-green-500/20"
|
|
49
|
+
>
|
|
50
|
+
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
51
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
|
|
52
|
+
</svg>
|
|
53
|
+
mdxgists.net
|
|
54
|
+
</a>
|
|
55
|
+
</div>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
@@ -1,44 +1,84 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const
|
|
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
|
-
|
|
1
|
+
import type GitHubGistType from '@/utils/types/GitHubGistType';
|
|
2
|
+
import GistCopyButton from './GistCopyButton';
|
|
3
|
+
import GistCodeBlock from './GistCodeBlock';
|
|
4
|
+
import { GITHUB_USERNAME, GITHUB_GIST_LANGUAGE_MAP, GIST_BASE_URL } from '@/utils/constants';
|
|
5
|
+
|
|
6
|
+
export default async function GitHubGist({ id, figCaptionText }: GitHubGistType) {
|
|
7
|
+
try {
|
|
8
|
+
const headers: HeadersInit = {
|
|
9
|
+
'Accept': 'application/vnd.github.v3+json',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
if (process.env.GITHUB_TOKEN) {
|
|
13
|
+
headers['Authorization'] = `Bearer ${process.env.GITHUB_TOKEN}`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const response = await fetch(`https://api.github.com/gists/${id}`, {
|
|
17
|
+
headers,
|
|
18
|
+
next: { revalidate: 3600 },
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
if (!response.ok) {
|
|
22
|
+
return (
|
|
23
|
+
<div className="text-red-400 bg-red-950/30 border border-red-500/30 p-4 rounded-lg font-mono text-sm">
|
|
24
|
+
Could not load GitHub Gist ({response.status})
|
|
25
|
+
</div>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const data = await response.json();
|
|
30
|
+
const firstFileKey = Object.keys(data.files)[0];
|
|
31
|
+
const firstFile = data.files[firstFileKey];
|
|
32
|
+
|
|
33
|
+
const rawResponse = await fetch(firstFile.raw_url, {
|
|
34
|
+
headers: process.env.GITHUB_TOKEN
|
|
35
|
+
? { 'Authorization': `Bearer ${process.env.GITHUB_TOKEN}` }
|
|
36
|
+
: {},
|
|
37
|
+
next: { revalidate: 3600 },
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const content = await rawResponse.text();
|
|
41
|
+
const language: string | null = firstFile.language;
|
|
42
|
+
const prismLanguage = language
|
|
43
|
+
? (GITHUB_GIST_LANGUAGE_MAP[language] ?? language.toLowerCase())
|
|
44
|
+
: 'text';
|
|
45
|
+
const mdxGHGistURL = `${GIST_BASE_URL}/${GITHUB_USERNAME}/${id}`;
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<figure className="my-6">
|
|
49
|
+
<div className="relative bg-gray-900/30 rounded-lg border-2 border-green-500 shadow-[0_0_15px_rgba(34,197,94,0.3)] overflow-hidden">
|
|
50
|
+
{/* Header */}
|
|
51
|
+
<div className="flex flex-wrap items-center justify-between gap-2 px-4 py-2 bg-gray-800/50 border-b border-green-500/30">
|
|
52
|
+
<div className="flex items-center gap-2">
|
|
53
|
+
<svg className="w-4 h-4 text-green-400" fill="currentColor" viewBox="0 0 20 20">
|
|
54
|
+
<path fillRule="evenodd" d="M12.316 3.051a1 1 0 01.633 1.265l-4 12a1 1 0 11-1.898-.632l4-12a1 1 0 011.265-.633zM5.707 6.293a1 1 0 010 1.414L3.414 10l2.293 2.293a1 1 0 11-1.414 1.414l-3-3a1 1 0 010-1.414l3-3a1 1 0 011.414 0zm8.586 0a1 1 0 011.414 0l3 3a1 1 0 010 1.414l-3 3a1 1 0 11-1.414-1.414L16.586 10l-2.293-2.293a1 1 0 010-1.414z" clipRule="evenodd" />
|
|
55
|
+
</svg>
|
|
56
|
+
<span className="text-sm text-green-200 font-medium">GitHub Gist</span>
|
|
57
|
+
{language && (
|
|
58
|
+
<span className="text-xs text-green-600 font-mono border border-green-500/20 px-1.5 py-0.5 rounded">
|
|
59
|
+
{language}
|
|
60
|
+
</span>
|
|
61
|
+
)}
|
|
62
|
+
</div>
|
|
63
|
+
<GistCopyButton content={content} mdxGHGistURL={mdxGHGistURL} />
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
{/* Code */}
|
|
67
|
+
<div className="scrollbar-gist overflow-auto max-h-96">
|
|
68
|
+
<GistCodeBlock content={content} language={prismLanguage} />
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
<figcaption className="mt-3 mb-4 leading-relaxed text-green-200/90 text-sm font-medium text-center">
|
|
72
|
+
{figCaptionText}
|
|
73
|
+
</figcaption>
|
|
74
|
+
</figure>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
return (
|
|
79
|
+
<div className="text-red-400 bg-red-950/30 border border-red-500/30 p-4 rounded-lg font-mono text-sm">
|
|
80
|
+
Could not load GitHub Gist: {err instanceof Error ? err.message : String(err)}
|
|
81
|
+
</div>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -1,21 +1,23 @@
|
|
|
1
|
-
import Image from "next/image";
|
|
2
|
-
import type { MDXImageType } from "@/utils/types";
|
|
3
|
-
|
|
4
|
-
// MDXImage custom component
|
|
5
|
-
// Utilizes the built-in Next.js Image component as well as the figcaption element
|
|
6
|
-
export default function MDXImage(imageProperties: MDXImageType) {
|
|
7
|
-
return (
|
|
8
|
-
<figure className='text-center'>
|
|
9
|
-
<
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
1
|
+
import Image from "next/image";
|
|
2
|
+
import type { MDXImageType } from "@/utils/types";
|
|
3
|
+
|
|
4
|
+
// MDXImage custom component
|
|
5
|
+
// Utilizes the built-in Next.js Image component as well as the figcaption element
|
|
6
|
+
export default function MDXImage(imageProperties: MDXImageType) {
|
|
7
|
+
return (
|
|
8
|
+
<figure className='text-center my-6'>
|
|
9
|
+
<div className="relative inline-block p-4 bg-gray-900/30 rounded-lg border-2 border-green-500 shadow-[0_0_15px_rgba(34,197,94,0.3)] transition-all duration-300 hover:shadow-[0_0_25px_rgba(34,197,94,0.5)] hover:scale-[1.02]">
|
|
10
|
+
<Image
|
|
11
|
+
className='mx-auto rounded-md transition-transform duration-300'
|
|
12
|
+
src={imageProperties.src}
|
|
13
|
+
height={imageProperties.height}
|
|
14
|
+
width={imageProperties.width}
|
|
15
|
+
alt={imageProperties.alt}
|
|
16
|
+
/>
|
|
17
|
+
</div>
|
|
18
|
+
<figcaption className="mt-3 mb-4 leading-relaxed text-green-200/90 text-sm font-medium">
|
|
19
|
+
{imageProperties.figcaption}
|
|
20
|
+
</figcaption>
|
|
21
|
+
</figure>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
|
|
93
93
|
> "The Virtual DOM is useful not because it's faster, but because it allows React to do declarative programming while maintaining good performance." - Dan Abramov, React core team
|
|
94
94
|
|
|
95
|
-
|
|
95
|
+
<MDXImage src="https://mdx-blog-bucket.s3.us-east-2.amazonaws.com/blog-article-image.PNG" height="50" width="50" alt="No Image Found" figcaption="This is an image" />
|
|
96
96
|
|
|
97
97
|
## When to Worry About Re-renders
|
|
98
98
|
|
|
@@ -104,5 +104,4 @@
|
|
|
104
104
|
|
|
105
105
|
## Conclusion
|
|
106
106
|
|
|
107
|
-
The Virtual DOM is a powerful abstraction that allows for a simpler mental model when building UI components. It enables React's declarative programming style while maintaining good performance through efficient updates to the DOM.
|
|
108
|
-
`
|
|
107
|
+
The Virtual DOM is a powerful abstraction that allows for a simpler mental model when building UI components. It enables React's declarative programming style while maintaining good performance through efficient updates to the DOM.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
export const GITHUB_USERNAME = process.env.GITHUB_USERNAME ?? "CodingAbdullah";
|
|
2
|
+
export const GIST_BASE_URL = process.env.GIST_BASE_URL ?? "https://mdxgists.net";
|
|
3
|
+
|
|
4
|
+
export const GITHUB_GIST_LANGUAGE_MAP: Record<string, string> = {
|
|
5
|
+
'Assembly': 'asm',
|
|
6
|
+
'Bash': 'bash',
|
|
7
|
+
'C': 'c',
|
|
8
|
+
'C#': 'csharp',
|
|
9
|
+
'C++': 'cpp',
|
|
10
|
+
'Cairo': 'cairo',
|
|
11
|
+
'Clojure': 'clojure',
|
|
12
|
+
'Crystal': 'crystal',
|
|
13
|
+
'CSS': 'css',
|
|
14
|
+
'Dart': 'dart',
|
|
15
|
+
'Diff': 'diff',
|
|
16
|
+
'Dockerfile': 'dockerfile',
|
|
17
|
+
'Elixir': 'elixir',
|
|
18
|
+
'Erlang': 'erlang',
|
|
19
|
+
'F#': 'fsharp',
|
|
20
|
+
'GLSL': 'glsl',
|
|
21
|
+
'Go': 'go',
|
|
22
|
+
'GraphQL': 'graphql',
|
|
23
|
+
'GraphQL Schema': 'graphql',
|
|
24
|
+
'Groovy': 'groovy',
|
|
25
|
+
'Haml': 'haml',
|
|
26
|
+
'Handlebars': 'handlebars',
|
|
27
|
+
'Haskell': 'haskell',
|
|
28
|
+
'HCL': 'hcl',
|
|
29
|
+
'HTML': 'html',
|
|
30
|
+
'INI': 'ini',
|
|
31
|
+
'Java': 'java',
|
|
32
|
+
'JavaScript': 'javascript',
|
|
33
|
+
'JSON': 'json',
|
|
34
|
+
'JSX': 'jsx',
|
|
35
|
+
'Julia': 'julia',
|
|
36
|
+
'Kotlin': 'kotlin',
|
|
37
|
+
'Less': 'less',
|
|
38
|
+
'Liquid': 'liquid',
|
|
39
|
+
'Lua': 'lua',
|
|
40
|
+
'Makefile': 'makefile',
|
|
41
|
+
'Markdown': 'markdown',
|
|
42
|
+
'MATLAB': 'matlab',
|
|
43
|
+
'Maven POM': 'xml',
|
|
44
|
+
'MDX': 'mdx',
|
|
45
|
+
'Nginx': 'nginx',
|
|
46
|
+
'Nim': 'nim',
|
|
47
|
+
'OCaml': 'ocaml',
|
|
48
|
+
'Perl': 'perl',
|
|
49
|
+
'PHP': 'php',
|
|
50
|
+
'PowerShell': 'powershell',
|
|
51
|
+
'Protocol Buffer': 'protobuf',
|
|
52
|
+
'Pug': 'pug',
|
|
53
|
+
'Python': 'python',
|
|
54
|
+
'R': 'r',
|
|
55
|
+
'Ruby': 'ruby',
|
|
56
|
+
'Rust': 'rust',
|
|
57
|
+
'Sass': 'sass',
|
|
58
|
+
'Scala': 'scala',
|
|
59
|
+
'SCSS': 'scss',
|
|
60
|
+
'Shell': 'bash',
|
|
61
|
+
'Solidity': 'solidity',
|
|
62
|
+
'SPARQL': 'sparql',
|
|
63
|
+
'SQL': 'sql',
|
|
64
|
+
'Svelte': 'svelte',
|
|
65
|
+
'Swift': 'swift',
|
|
66
|
+
'Terraform': 'terraform',
|
|
67
|
+
'TOML': 'toml',
|
|
68
|
+
'TSX': 'tsx',
|
|
69
|
+
'TypeScript': 'typescript',
|
|
70
|
+
'V': 'v',
|
|
71
|
+
'Vue': 'vue',
|
|
72
|
+
'Vyper': 'vyper',
|
|
73
|
+
'WebAssembly': 'wasm',
|
|
74
|
+
'XML': 'xml',
|
|
75
|
+
'YAML': 'yaml',
|
|
76
|
+
'Zig': 'zig',
|
|
77
|
+
};
|