@next-md-blog/core 1.0.0
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/README.md +242 -0
- package/dist/components/BlogPostSEO.d.ts +28 -0
- package/dist/components/BlogPostSEO.d.ts.map +1 -0
- package/dist/components/BlogPostSEO.js +22 -0
- package/dist/components/MarkdownContent.d.ts +48 -0
- package/dist/components/MarkdownContent.d.ts.map +1 -0
- package/dist/components/MarkdownContent.js +44 -0
- package/dist/components/OgImage.d.ts +35 -0
- package/dist/components/OgImage.d.ts.map +1 -0
- package/dist/components/OgImage.js +52 -0
- package/dist/components/markdown/a.d.ts +6 -0
- package/dist/components/markdown/a.d.ts.map +1 -0
- package/dist/components/markdown/a.js +7 -0
- package/dist/components/markdown/blockquote.d.ts +6 -0
- package/dist/components/markdown/blockquote.d.ts.map +1 -0
- package/dist/components/markdown/blockquote.js +7 -0
- package/dist/components/markdown/code.d.ts +6 -0
- package/dist/components/markdown/code.d.ts.map +1 -0
- package/dist/components/markdown/code.js +7 -0
- package/dist/components/markdown/defaults.d.ts +12 -0
- package/dist/components/markdown/defaults.d.ts.map +1 -0
- package/dist/components/markdown/defaults.js +59 -0
- package/dist/components/markdown/em.d.ts +6 -0
- package/dist/components/markdown/em.d.ts.map +1 -0
- package/dist/components/markdown/em.js +7 -0
- package/dist/components/markdown/h1.d.ts +6 -0
- package/dist/components/markdown/h1.d.ts.map +1 -0
- package/dist/components/markdown/h1.js +7 -0
- package/dist/components/markdown/h2.d.ts +6 -0
- package/dist/components/markdown/h2.d.ts.map +1 -0
- package/dist/components/markdown/h2.js +7 -0
- package/dist/components/markdown/h3.d.ts +6 -0
- package/dist/components/markdown/h3.d.ts.map +1 -0
- package/dist/components/markdown/h3.js +7 -0
- package/dist/components/markdown/h4.d.ts +6 -0
- package/dist/components/markdown/h4.d.ts.map +1 -0
- package/dist/components/markdown/h4.js +7 -0
- package/dist/components/markdown/h5.d.ts +6 -0
- package/dist/components/markdown/h5.d.ts.map +1 -0
- package/dist/components/markdown/h5.js +7 -0
- package/dist/components/markdown/h6.d.ts +6 -0
- package/dist/components/markdown/h6.d.ts.map +1 -0
- package/dist/components/markdown/h6.js +7 -0
- package/dist/components/markdown/hr.d.ts +6 -0
- package/dist/components/markdown/hr.d.ts.map +1 -0
- package/dist/components/markdown/hr.js +7 -0
- package/dist/components/markdown/img.d.ts +6 -0
- package/dist/components/markdown/img.d.ts.map +1 -0
- package/dist/components/markdown/img.js +9 -0
- package/dist/components/markdown/index.d.ts +29 -0
- package/dist/components/markdown/index.d.ts.map +1 -0
- package/dist/components/markdown/index.js +28 -0
- package/dist/components/markdown/li.d.ts +6 -0
- package/dist/components/markdown/li.d.ts.map +1 -0
- package/dist/components/markdown/li.js +7 -0
- package/dist/components/markdown/ol.d.ts +6 -0
- package/dist/components/markdown/ol.d.ts.map +1 -0
- package/dist/components/markdown/ol.js +7 -0
- package/dist/components/markdown/p.d.ts +6 -0
- package/dist/components/markdown/p.d.ts.map +1 -0
- package/dist/components/markdown/p.js +7 -0
- package/dist/components/markdown/pre.d.ts +6 -0
- package/dist/components/markdown/pre.d.ts.map +1 -0
- package/dist/components/markdown/pre.js +7 -0
- package/dist/components/markdown/strong.d.ts +6 -0
- package/dist/components/markdown/strong.d.ts.map +1 -0
- package/dist/components/markdown/strong.js +7 -0
- package/dist/components/markdown/table.d.ts +6 -0
- package/dist/components/markdown/table.d.ts.map +1 -0
- package/dist/components/markdown/table.js +7 -0
- package/dist/components/markdown/tbody.d.ts +6 -0
- package/dist/components/markdown/tbody.d.ts.map +1 -0
- package/dist/components/markdown/tbody.js +7 -0
- package/dist/components/markdown/td.d.ts +6 -0
- package/dist/components/markdown/td.d.ts.map +1 -0
- package/dist/components/markdown/td.js +7 -0
- package/dist/components/markdown/th.d.ts +6 -0
- package/dist/components/markdown/th.d.ts.map +1 -0
- package/dist/components/markdown/th.js +7 -0
- package/dist/components/markdown/thead.d.ts +6 -0
- package/dist/components/markdown/thead.d.ts.map +1 -0
- package/dist/components/markdown/thead.js +7 -0
- package/dist/components/markdown/tr.d.ts +6 -0
- package/dist/components/markdown/tr.d.ts.map +1 -0
- package/dist/components/markdown/tr.js +7 -0
- package/dist/components/markdown/ul.d.ts +6 -0
- package/dist/components/markdown/ul.d.ts.map +1 -0
- package/dist/components/markdown/ul.js +7 -0
- package/dist/components/markdown/utils.d.ts +6 -0
- package/dist/components/markdown/utils.d.ts.map +1 -0
- package/dist/components/markdown/utils.js +7 -0
- package/dist/core/config.d.ts +36 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +63 -0
- package/dist/core/constants.d.ts +36 -0
- package/dist/core/constants.d.ts.map +1 -0
- package/dist/core/constants.js +44 -0
- package/dist/core/errors.d.ts +48 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +57 -0
- package/dist/core/file-utils.d.ts +22 -0
- package/dist/core/file-utils.d.ts.map +1 -0
- package/dist/core/file-utils.js +180 -0
- package/dist/core/seo-feeds.d.ts +16 -0
- package/dist/core/seo-feeds.d.ts.map +1 -0
- package/dist/core/seo-feeds.js +73 -0
- package/dist/core/seo-metadata.d.ts +17 -0
- package/dist/core/seo-metadata.d.ts.map +1 -0
- package/dist/core/seo-metadata.js +197 -0
- package/dist/core/seo-schema.d.ts +20 -0
- package/dist/core/seo-schema.d.ts.map +1 -0
- package/dist/core/seo-schema.js +131 -0
- package/dist/core/seo-utils.d.ts +66 -0
- package/dist/core/seo-utils.d.ts.map +1 -0
- package/dist/core/seo-utils.js +135 -0
- package/dist/core/seo.d.ts +11 -0
- package/dist/core/seo.d.ts.map +1 -0
- package/dist/core/seo.js +12 -0
- package/dist/core/type-guards.d.ts +58 -0
- package/dist/core/type-guards.d.ts.map +1 -0
- package/dist/core/type-guards.js +83 -0
- package/dist/core/types.d.ts +116 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +1 -0
- package/dist/core/utils.d.ts +49 -0
- package/dist/core/utils.d.ts.map +1 -0
- package/dist/core/utils.js +175 -0
- package/dist/core/validation.d.ts +22 -0
- package/dist/core/validation.d.ts.map +1 -0
- package/dist/core/validation.js +50 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/package.json +80 -0
package/README.md
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
# @florianamette/next-md-blog
|
|
2
|
+
|
|
3
|
+
A powerful React library for parsing and displaying markdown and MDX blog posts in Next.js projects. Write your blog posts in markdown or MDX format and easily display them with dynamic routes, complete SEO optimization, and beautiful Open Graph images.
|
|
4
|
+
|
|
5
|
+
## ✨ Features
|
|
6
|
+
|
|
7
|
+
- 📝 **Markdown & MDX Support** - Full markdown and MDX support with GitHub Flavored Markdown (GFM) support
|
|
8
|
+
- 🎨 **Server Components** - Built for Next.js App Router with server-side rendering
|
|
9
|
+
- 🔍 **SEO Optimized** - Automatic metadata generation with Open Graph and Twitter Cards
|
|
10
|
+
- 🖼️ **OG Images** - Built-in OG image generation component
|
|
11
|
+
- ⚡ **Type Safe** - Full TypeScript support with comprehensive types
|
|
12
|
+
- 🎯 **Flexible** - Works with both App Router and Pages Router
|
|
13
|
+
- 🛡️ **Robust** - Input validation, error handling, and clean code principles
|
|
14
|
+
|
|
15
|
+
## 📦 Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install @florianamette/next-md-blog
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## 🚀 Quick Start
|
|
22
|
+
|
|
23
|
+
### 1. Initialize with CLI (Recommended)
|
|
24
|
+
|
|
25
|
+
The easiest way to get started is using the CLI:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npx @florianamette/next-md-blog-cli
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
This will automatically:
|
|
32
|
+
- ✅ Create blog routes and pages
|
|
33
|
+
- ✅ Install required dependencies
|
|
34
|
+
- ✅ Configure Tailwind CSS typography
|
|
35
|
+
- ✅ Set up SEO configuration
|
|
36
|
+
|
|
37
|
+
### 2. Manual Setup
|
|
38
|
+
|
|
39
|
+
If you prefer manual setup:
|
|
40
|
+
|
|
41
|
+
1. **Install the package:**
|
|
42
|
+
```bash
|
|
43
|
+
npm install @florianamette/next-md-blog @tailwindcss/typography
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
2. **Create a config file:**
|
|
47
|
+
```tsx
|
|
48
|
+
// next-md-blog.config.ts
|
|
49
|
+
import { createConfig } from '@florianamette/next-md-blog';
|
|
50
|
+
|
|
51
|
+
export default createConfig({
|
|
52
|
+
siteName: 'My Blog',
|
|
53
|
+
siteUrl: process.env.NEXT_PUBLIC_SITE_URL || 'https://example.com',
|
|
54
|
+
defaultAuthor: 'Your Name',
|
|
55
|
+
twitterHandle: '@yourhandle',
|
|
56
|
+
defaultLang: 'en',
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
3. **Create blog routes:**
|
|
61
|
+
```tsx
|
|
62
|
+
// app/blog/[slug]/page.tsx
|
|
63
|
+
import { getBlogPost, getAllBlogPosts, generateBlogPostMetadata } from '@florianamette/next-md-blog';
|
|
64
|
+
import { MarkdownContent } from '@florianamette/next-md-blog';
|
|
65
|
+
import { notFound } from 'next/navigation';
|
|
66
|
+
import type { Metadata } from 'next';
|
|
67
|
+
import blogConfig from '@/next-md-blog.config';
|
|
68
|
+
|
|
69
|
+
export async function generateStaticParams() {
|
|
70
|
+
const posts = await getAllBlogPosts({ config: blogConfig });
|
|
71
|
+
return posts.map((post) => ({
|
|
72
|
+
slug: post.slug,
|
|
73
|
+
}));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }): Promise<Metadata> {
|
|
77
|
+
const { slug } = await params;
|
|
78
|
+
const post = await getBlogPost(slug, { config: blogConfig });
|
|
79
|
+
|
|
80
|
+
if (!post) {
|
|
81
|
+
return { title: 'Post Not Found' };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return generateBlogPostMetadata(post, blogConfig);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export default async function BlogPost({ params }: { params: Promise<{ slug: string }> }) {
|
|
88
|
+
const { slug } = await params;
|
|
89
|
+
const post = await getBlogPost(slug, { config: blogConfig });
|
|
90
|
+
|
|
91
|
+
if (!post) {
|
|
92
|
+
notFound();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<div className="prose prose-lg dark:prose-invert max-w-none">
|
|
97
|
+
<MarkdownContent content={post.content} />
|
|
98
|
+
</div>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## 📚 API Reference
|
|
104
|
+
|
|
105
|
+
### Components
|
|
106
|
+
|
|
107
|
+
#### `MarkdownContent`
|
|
108
|
+
|
|
109
|
+
A React Server Component that renders markdown content as HTML.
|
|
110
|
+
|
|
111
|
+
**Props:**
|
|
112
|
+
- `content` (string, required): The markdown content to render
|
|
113
|
+
- `className` (string, optional): CSS class name for the container
|
|
114
|
+
- `components` (MarkdownComponents, optional): Custom components to override default markdown rendering
|
|
115
|
+
- `remarkPlugins` (any[], optional): Custom remark plugins to extend markdown parsing
|
|
116
|
+
- `rehypePlugins` (any[], optional): Custom rehype plugins to extend HTML processing
|
|
117
|
+
|
|
118
|
+
**Example:**
|
|
119
|
+
```tsx
|
|
120
|
+
<MarkdownContent content="# Hello World" className="prose prose-lg" />
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
#### `BlogPostSEO`
|
|
124
|
+
|
|
125
|
+
A component that generates JSON-LD structured data for SEO.
|
|
126
|
+
|
|
127
|
+
**Props:**
|
|
128
|
+
- `post` (BlogPost, required): The blog post object
|
|
129
|
+
- `config` (SEOConfig, required): SEO configuration
|
|
130
|
+
|
|
131
|
+
**Example:**
|
|
132
|
+
```tsx
|
|
133
|
+
<BlogPostSEO post={post} config={blogConfig} />
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
#### `OgImage`
|
|
137
|
+
|
|
138
|
+
A component for generating Open Graph images (used with `@vercel/og`).
|
|
139
|
+
|
|
140
|
+
**Props:**
|
|
141
|
+
- `title` (string, required): Title text
|
|
142
|
+
- `description` (string, optional): Description/subtitle text
|
|
143
|
+
- `siteName` (string, optional): Site name
|
|
144
|
+
- `backgroundColor` (string, optional): Background color (default: `#1a1a1a`)
|
|
145
|
+
- `textColor` (string, optional): Text color (default: `#ffffff`)
|
|
146
|
+
|
|
147
|
+
### Functions
|
|
148
|
+
|
|
149
|
+
#### `getBlogPost(slug: string, options?: GetBlogPostOptions): Promise<BlogPost | null>`
|
|
150
|
+
|
|
151
|
+
Retrieves a single blog post by its slug.
|
|
152
|
+
|
|
153
|
+
**Parameters:**
|
|
154
|
+
- `slug` (string): The slug of the blog post (filename without .md or .mdx extension)
|
|
155
|
+
- `options` (optional): Configuration object
|
|
156
|
+
- `postsDir` (string, optional): Custom path to posts directory
|
|
157
|
+
- `config` (Config, optional): Blog configuration
|
|
158
|
+
|
|
159
|
+
**Returns:**
|
|
160
|
+
- `Promise<BlogPost | null>`: The blog post object or null if not found
|
|
161
|
+
|
|
162
|
+
#### `getAllBlogPosts(options?: GetBlogPostOptions): Promise<BlogPostMetadata[]>`
|
|
163
|
+
|
|
164
|
+
Retrieves all blog posts from the posts folder.
|
|
165
|
+
|
|
166
|
+
**Parameters:**
|
|
167
|
+
- `options` (optional): Configuration object
|
|
168
|
+
- `postsDir` (string, optional): Custom path to posts directory
|
|
169
|
+
- `config` (Config, optional): Blog configuration
|
|
170
|
+
|
|
171
|
+
**Returns:**
|
|
172
|
+
- `Promise<BlogPostMetadata[]>`: Array of blog post metadata, sorted by date (newest first)
|
|
173
|
+
|
|
174
|
+
#### `generateBlogPostMetadata(post: BlogPost, config?: SEOConfig): Metadata`
|
|
175
|
+
|
|
176
|
+
Generates comprehensive SEO metadata for a blog post.
|
|
177
|
+
|
|
178
|
+
**Parameters:**
|
|
179
|
+
- `post` (BlogPost): The blog post object
|
|
180
|
+
- `config` (SEOConfig, optional): SEO configuration
|
|
181
|
+
|
|
182
|
+
**Returns:**
|
|
183
|
+
- `Metadata`: Next.js metadata object
|
|
184
|
+
|
|
185
|
+
#### `generateBlogListMetadata(posts: BlogPostMetadata[], config?: SEOConfig): Metadata`
|
|
186
|
+
|
|
187
|
+
Generates SEO metadata for the blog listing page.
|
|
188
|
+
|
|
189
|
+
### Types
|
|
190
|
+
|
|
191
|
+
#### `BlogPost`
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
interface BlogPost {
|
|
195
|
+
slug: string;
|
|
196
|
+
content: string;
|
|
197
|
+
frontmatter: BlogPostFrontmatter;
|
|
198
|
+
authors: Author[];
|
|
199
|
+
readingTime: number;
|
|
200
|
+
wordCount: number;
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
#### `BlogPostFrontmatter`
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
interface BlogPostFrontmatter {
|
|
208
|
+
title?: string;
|
|
209
|
+
date?: string;
|
|
210
|
+
description?: string;
|
|
211
|
+
author?: string;
|
|
212
|
+
tags?: string[];
|
|
213
|
+
ogImage?: string;
|
|
214
|
+
image?: string;
|
|
215
|
+
[key: string]: unknown;
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## 🎨 Styling
|
|
220
|
+
|
|
221
|
+
The library is designed to work with Tailwind CSS v4 and the `@tailwindcss/typography` plugin. Use the `prose` classes for beautiful markdown styling:
|
|
222
|
+
|
|
223
|
+
```tsx
|
|
224
|
+
<div className="prose prose-lg dark:prose-invert max-w-none">
|
|
225
|
+
<MarkdownContent content={post.content} />
|
|
226
|
+
</div>
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## 📖 Documentation
|
|
230
|
+
|
|
231
|
+
For complete documentation, examples, and advanced usage, visit the [main repository](https://github.com/florianamette/next-mdx-blog).
|
|
232
|
+
|
|
233
|
+
## 📝 License
|
|
234
|
+
|
|
235
|
+
MIT
|
|
236
|
+
|
|
237
|
+
## 🔗 Links
|
|
238
|
+
|
|
239
|
+
- [GitHub Repository](https://github.com/florianamette/next-mdx-blog)
|
|
240
|
+
- [CLI Package](https://www.npmjs.com/package/@florianamette/next-md-blog-cli)
|
|
241
|
+
- [Issues](https://github.com/florianamette/next-mdx-blog/issues)
|
|
242
|
+
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { BlogPost, Config } from '../core/types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Props for the BlogPostSEO component
|
|
4
|
+
*/
|
|
5
|
+
export interface BlogPostSEOProps {
|
|
6
|
+
/** The blog post */
|
|
7
|
+
post: BlogPost;
|
|
8
|
+
/** Configuration (optional - will load from next-md-blog.config.ts if not provided) */
|
|
9
|
+
config?: Config;
|
|
10
|
+
/** Custom breadcrumb items (optional) */
|
|
11
|
+
breadcrumbs?: Array<{
|
|
12
|
+
name: string;
|
|
13
|
+
url: string;
|
|
14
|
+
}>;
|
|
15
|
+
/** Whether to include breadcrumbs schema (default: true) */
|
|
16
|
+
includeBreadcrumbs?: boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Component that generates and injects JSON-LD structured data for a blog post
|
|
20
|
+
* Handles both article schema and breadcrumbs schema automatically
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```tsx
|
|
24
|
+
* <BlogPostSEO post={post} />
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export declare function BlogPostSEO({ post, config, breadcrumbs, includeBreadcrumbs, }: BlogPostSEOProps): import("react/jsx-runtime").JSX.Element;
|
|
28
|
+
//# sourceMappingURL=BlogPostSEO.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BlogPostSEO.d.ts","sourceRoot":"","sources":["../../src/components/BlogPostSEO.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAIzD;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,oBAAoB;IACpB,IAAI,EAAE,QAAQ,CAAC;IACf,uFAAuF;IACvF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yCAAyC;IACzC,WAAW,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnD,4DAA4D;IAC5D,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,EAC1B,IAAI,EACJ,MAAM,EACN,WAAW,EACX,kBAAyB,GAC1B,EAAE,gBAAgB,2CA2BlB"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { generateBlogPostSchema, generateBreadcrumbsSchema } from '../core/seo.js';
|
|
3
|
+
import { getConfig } from '../core/config.js';
|
|
4
|
+
/**
|
|
5
|
+
* Component that generates and injects JSON-LD structured data for a blog post
|
|
6
|
+
* Handles both article schema and breadcrumbs schema automatically
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```tsx
|
|
10
|
+
* <BlogPostSEO post={post} />
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export function BlogPostSEO({ post, config, breadcrumbs, includeBreadcrumbs = true, }) {
|
|
14
|
+
const blogConfig = config || getConfig();
|
|
15
|
+
// Generate article schema
|
|
16
|
+
const articleSchema = generateBlogPostSchema(post, blogConfig);
|
|
17
|
+
// Generate breadcrumbs schema if enabled
|
|
18
|
+
const breadcrumbsSchema = includeBreadcrumbs
|
|
19
|
+
? generateBreadcrumbsSchema(post, blogConfig, breadcrumbs)
|
|
20
|
+
: null;
|
|
21
|
+
return (_jsxs(_Fragment, { children: [_jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: JSON.stringify(articleSchema) } }), breadcrumbsSchema && (_jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: JSON.stringify(breadcrumbsSchema) } }))] }));
|
|
22
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { Components } from 'react-markdown';
|
|
2
|
+
/**
|
|
3
|
+
* Component types that can be overridden
|
|
4
|
+
*/
|
|
5
|
+
export interface MarkdownComponents extends Partial<Components> {
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Props for the MarkdownContent component
|
|
9
|
+
*/
|
|
10
|
+
export interface MarkdownContentProps {
|
|
11
|
+
/** The markdown content to render */
|
|
12
|
+
content: string;
|
|
13
|
+
/** Optional CSS class name for the container */
|
|
14
|
+
className?: string;
|
|
15
|
+
/** Optional custom components to override default markdown rendering */
|
|
16
|
+
components?: MarkdownComponents;
|
|
17
|
+
/** Optional remark plugins */
|
|
18
|
+
remarkPlugins?: any[];
|
|
19
|
+
/** Optional rehype plugins */
|
|
20
|
+
rehypePlugins?: any[];
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* React Server Component that renders markdown content as React elements
|
|
24
|
+
* Uses react-markdown under the hood with support for custom components.
|
|
25
|
+
*
|
|
26
|
+
* If custom components are provided in the components/markdown folder, they will be used.
|
|
27
|
+
* Otherwise, Tailwind's prose classes will handle the styling.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```tsx
|
|
31
|
+
* // Using default components from components/markdown folder
|
|
32
|
+
* <MarkdownContent
|
|
33
|
+
* content="# Hello World"
|
|
34
|
+
* className="prose prose-lg"
|
|
35
|
+
* />
|
|
36
|
+
*
|
|
37
|
+
* // Overriding specific components
|
|
38
|
+
* <MarkdownContent
|
|
39
|
+
* content="# Hello World"
|
|
40
|
+
* className="prose prose-lg"
|
|
41
|
+
* components={{
|
|
42
|
+
* h1: ({ children, ...props }) => <h1 className="text-4xl font-bold" {...props}>{children}</h1>,
|
|
43
|
+
* }}
|
|
44
|
+
* />
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export declare function MarkdownContent({ content, className, components, remarkPlugins, rehypePlugins, }: MarkdownContentProps): import("react/jsx-runtime").JSX.Element;
|
|
48
|
+
//# sourceMappingURL=MarkdownContent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MarkdownContent.d.ts","sourceRoot":"","sources":["../../src/components/MarkdownContent.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAGjD;;GAEG;AACH,MAAM,WAAW,kBAAmB,SAAQ,OAAO,CAAC,UAAU,CAAC;CAAG;AAElE;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,qCAAqC;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,gDAAgD;IAChD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wEAAwE;IACxE,UAAU,CAAC,EAAE,kBAAkB,CAAC;IAChC,8BAA8B;IAC9B,aAAa,CAAC,EAAE,GAAG,EAAE,CAAC;IACtB,8BAA8B;IAC9B,aAAa,CAAC,EAAE,GAAG,EAAE,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,eAAe,CAAC,EAC9B,OAAO,EACP,SAAc,EACd,UAAU,EACV,aAAkB,EAClB,aAAkB,GACnB,EAAE,oBAAoB,2CA0BtB"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import ReactMarkdown from 'react-markdown';
|
|
3
|
+
import remarkGfm from 'remark-gfm';
|
|
4
|
+
import remarkEmoji from 'remark-emoji';
|
|
5
|
+
import { defaultMarkdownComponents } from './markdown/defaults.js';
|
|
6
|
+
/**
|
|
7
|
+
* React Server Component that renders markdown content as React elements
|
|
8
|
+
* Uses react-markdown under the hood with support for custom components.
|
|
9
|
+
*
|
|
10
|
+
* If custom components are provided in the components/markdown folder, they will be used.
|
|
11
|
+
* Otherwise, Tailwind's prose classes will handle the styling.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```tsx
|
|
15
|
+
* // Using default components from components/markdown folder
|
|
16
|
+
* <MarkdownContent
|
|
17
|
+
* content="# Hello World"
|
|
18
|
+
* className="prose prose-lg"
|
|
19
|
+
* />
|
|
20
|
+
*
|
|
21
|
+
* // Overriding specific components
|
|
22
|
+
* <MarkdownContent
|
|
23
|
+
* content="# Hello World"
|
|
24
|
+
* className="prose prose-lg"
|
|
25
|
+
* components={{
|
|
26
|
+
* h1: ({ children, ...props }) => <h1 className="text-4xl font-bold" {...props}>{children}</h1>,
|
|
27
|
+
* }}
|
|
28
|
+
* />
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export function MarkdownContent({ content, className = '', components, remarkPlugins = [], rehypePlugins = [], }) {
|
|
32
|
+
if (!content || typeof content !== 'string') {
|
|
33
|
+
throw new Error('Invalid content: must be a non-empty string');
|
|
34
|
+
}
|
|
35
|
+
// Merge default components with user-provided components
|
|
36
|
+
// User components override defaults
|
|
37
|
+
const mergedComponents = {
|
|
38
|
+
...defaultMarkdownComponents,
|
|
39
|
+
...components,
|
|
40
|
+
};
|
|
41
|
+
// Default remark plugins
|
|
42
|
+
const defaultRemarkPlugins = [remarkGfm, remarkEmoji, ...remarkPlugins];
|
|
43
|
+
return (_jsx("article", { className: className ? `prose prose-lg max-w-none dark:prose-invert ${className}` : 'prose prose-lg max-w-none dark:prose-invert', children: _jsx(ReactMarkdown, { remarkPlugins: defaultRemarkPlugins, rehypePlugins: rehypePlugins, components: mergedComponents, children: content }) }));
|
|
44
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Props for the OgImage component
|
|
4
|
+
*/
|
|
5
|
+
export interface OgImageProps {
|
|
6
|
+
/** Title text */
|
|
7
|
+
title: string;
|
|
8
|
+
/** Description/subtitle text */
|
|
9
|
+
description?: string;
|
|
10
|
+
/** Site name */
|
|
11
|
+
siteName?: string;
|
|
12
|
+
/** Background color */
|
|
13
|
+
backgroundColor?: string;
|
|
14
|
+
/** Text color */
|
|
15
|
+
textColor?: string;
|
|
16
|
+
/** Width of the image */
|
|
17
|
+
width?: number;
|
|
18
|
+
/** Height of the image */
|
|
19
|
+
height?: number;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Default OG Image component for generating Open Graph images
|
|
23
|
+
* This component can be used with @vercel/og or similar libraries
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```tsx
|
|
27
|
+
* <OgImage
|
|
28
|
+
* title="My Blog Post"
|
|
29
|
+
* description="A great blog post"
|
|
30
|
+
* siteName="My Blog"
|
|
31
|
+
* />
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export declare function OgImage({ title, description, siteName, backgroundColor, textColor, width, height, }: OgImageProps): React.JSX.Element;
|
|
35
|
+
//# sourceMappingURL=OgImage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"OgImage.d.ts","sourceRoot":"","sources":["../../src/components/OgImage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,iBAAiB;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,gCAAgC;IAChC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,uBAAuB;IACvB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iBAAiB;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yBAAyB;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0BAA0B;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,OAAO,CAAC,EACtB,KAAK,EACL,WAAW,EACX,QAAoB,EACpB,eAA2B,EAC3B,SAAqB,EACrB,KAAY,EACZ,MAAY,GACb,EAAE,YAAY,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAiElC"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Default OG Image component for generating Open Graph images
|
|
4
|
+
* This component can be used with @vercel/og or similar libraries
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```tsx
|
|
8
|
+
* <OgImage
|
|
9
|
+
* title="My Blog Post"
|
|
10
|
+
* description="A great blog post"
|
|
11
|
+
* siteName="My Blog"
|
|
12
|
+
* />
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export function OgImage({ title, description, siteName = 'My Blog', backgroundColor = '#1a1a1a', textColor = '#ffffff', width = 1200, height = 630, }) {
|
|
16
|
+
return (_jsx("div", { style: {
|
|
17
|
+
width,
|
|
18
|
+
height,
|
|
19
|
+
backgroundColor,
|
|
20
|
+
color: textColor,
|
|
21
|
+
display: 'flex',
|
|
22
|
+
flexDirection: 'column',
|
|
23
|
+
justifyContent: 'center',
|
|
24
|
+
alignItems: 'center',
|
|
25
|
+
padding: '80px',
|
|
26
|
+
fontFamily: 'system-ui, -apple-system, sans-serif',
|
|
27
|
+
}, children: _jsxs("div", { style: {
|
|
28
|
+
display: 'flex',
|
|
29
|
+
flexDirection: 'column',
|
|
30
|
+
alignItems: 'center',
|
|
31
|
+
textAlign: 'center',
|
|
32
|
+
maxWidth: '1000px',
|
|
33
|
+
}, children: [siteName && (_jsx("div", { style: {
|
|
34
|
+
fontSize: '24px',
|
|
35
|
+
opacity: 0.8,
|
|
36
|
+
marginBottom: '20px',
|
|
37
|
+
fontWeight: 500,
|
|
38
|
+
}, children: siteName })), _jsx("h1", { style: {
|
|
39
|
+
fontSize: '64px',
|
|
40
|
+
fontWeight: 700,
|
|
41
|
+
margin: 0,
|
|
42
|
+
marginBottom: description ? '24px' : 0,
|
|
43
|
+
lineHeight: 1.2,
|
|
44
|
+
wordWrap: 'break-word',
|
|
45
|
+
}, children: title }), description && (_jsx("p", { style: {
|
|
46
|
+
fontSize: '32px',
|
|
47
|
+
opacity: 0.9,
|
|
48
|
+
margin: 0,
|
|
49
|
+
lineHeight: 1.4,
|
|
50
|
+
wordWrap: 'break-word',
|
|
51
|
+
}, children: description }))] }) }));
|
|
52
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"a.d.ts","sourceRoot":"","sources":["../../../src/components/markdown/a.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,WAAW,MAAO,SAAQ,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC;CAAG;AAEhF,QAAA,MAAM,CAAC,kFAIN,CAAC;AAGF,eAAe,CAAC,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface BlockquoteProps extends React.HTMLAttributes<HTMLQuoteElement> {
|
|
3
|
+
}
|
|
4
|
+
declare const Blockquote: React.ForwardRefExoticComponent<BlockquoteProps & React.RefAttributes<HTMLQuoteElement>>;
|
|
5
|
+
export default Blockquote;
|
|
6
|
+
//# sourceMappingURL=blockquote.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blockquote.d.ts","sourceRoot":"","sources":["../../../src/components/markdown/blockquote.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,WAAW,eAAgB,SAAQ,KAAK,CAAC,cAAc,CAAC,gBAAgB,CAAC;CAAG;AAElF,QAAA,MAAM,UAAU,0FAIf,CAAC;AAGF,eAAe,UAAU,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
const Blockquote = React.forwardRef(({ className, ...props }, ref) => {
|
|
4
|
+
return _jsx("blockquote", { ref: ref, className: className, ...props });
|
|
5
|
+
});
|
|
6
|
+
Blockquote.displayName = 'Blockquote';
|
|
7
|
+
export default Blockquote;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"code.d.ts","sourceRoot":"","sources":["../../../src/components/markdown/code.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,WAAW,SAAU,SAAQ,KAAK,CAAC,cAAc,CAAC,WAAW,CAAC;CAAG;AAEvE,QAAA,MAAM,IAAI,+EAIT,CAAC;AAGF,eAAe,IAAI,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
const Code = React.forwardRef(({ className, ...props }, ref) => {
|
|
4
|
+
return _jsx("code", { ref: ref, className: className, ...props });
|
|
5
|
+
});
|
|
6
|
+
Code.displayName = 'Code';
|
|
7
|
+
export default Code;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default markdown components
|
|
3
|
+
* These are used when no custom components are provided
|
|
4
|
+
*/
|
|
5
|
+
import type { Components } from 'react-markdown';
|
|
6
|
+
/**
|
|
7
|
+
* Default markdown components that will be used if no custom components are provided
|
|
8
|
+
* These components from the components/markdown folder will be used.
|
|
9
|
+
* If a component is not provided here, Tailwind's prose classes will handle the styling.
|
|
10
|
+
*/
|
|
11
|
+
export declare const defaultMarkdownComponents: Partial<Components>;
|
|
12
|
+
//# sourceMappingURL=defaults.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../../../src/components/markdown/defaults.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AA0BjD;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,EAAE,OAAO,CAAC,UAAU,CAyBzD,CAAC"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default markdown components
|
|
3
|
+
* These are used when no custom components are provided
|
|
4
|
+
*/
|
|
5
|
+
import H1 from './h1.js';
|
|
6
|
+
import H2 from './h2.js';
|
|
7
|
+
import H3 from './h3.js';
|
|
8
|
+
import H4 from './h4.js';
|
|
9
|
+
import H5 from './h5.js';
|
|
10
|
+
import H6 from './h6.js';
|
|
11
|
+
import P from './p.js';
|
|
12
|
+
import A from './a.js';
|
|
13
|
+
import Img from './img.js';
|
|
14
|
+
import Code from './code.js';
|
|
15
|
+
import Pre from './pre.js';
|
|
16
|
+
import Blockquote from './blockquote.js';
|
|
17
|
+
import Ul from './ul.js';
|
|
18
|
+
import Ol from './ol.js';
|
|
19
|
+
import Li from './li.js';
|
|
20
|
+
import Table from './table.js';
|
|
21
|
+
import Thead from './thead.js';
|
|
22
|
+
import Tbody from './tbody.js';
|
|
23
|
+
import Tr from './tr.js';
|
|
24
|
+
import Th from './th.js';
|
|
25
|
+
import Td from './td.js';
|
|
26
|
+
import Hr from './hr.js';
|
|
27
|
+
import Strong from './strong.js';
|
|
28
|
+
import Em from './em.js';
|
|
29
|
+
/**
|
|
30
|
+
* Default markdown components that will be used if no custom components are provided
|
|
31
|
+
* These components from the components/markdown folder will be used.
|
|
32
|
+
* If a component is not provided here, Tailwind's prose classes will handle the styling.
|
|
33
|
+
*/
|
|
34
|
+
export const defaultMarkdownComponents = {
|
|
35
|
+
h1: H1,
|
|
36
|
+
h2: H2,
|
|
37
|
+
h3: H3,
|
|
38
|
+
h4: H4,
|
|
39
|
+
h5: H5,
|
|
40
|
+
h6: H6,
|
|
41
|
+
p: P,
|
|
42
|
+
a: A,
|
|
43
|
+
img: Img,
|
|
44
|
+
code: Code,
|
|
45
|
+
pre: Pre,
|
|
46
|
+
blockquote: Blockquote,
|
|
47
|
+
ul: Ul,
|
|
48
|
+
ol: Ol,
|
|
49
|
+
li: Li,
|
|
50
|
+
table: Table,
|
|
51
|
+
thead: Thead,
|
|
52
|
+
tbody: Tbody,
|
|
53
|
+
tr: Tr,
|
|
54
|
+
th: Th,
|
|
55
|
+
td: Td,
|
|
56
|
+
hr: Hr,
|
|
57
|
+
strong: Strong,
|
|
58
|
+
em: Em,
|
|
59
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"em.d.ts","sourceRoot":"","sources":["../../../src/components/markdown/em.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,WAAW,OAAQ,SAAQ,KAAK,CAAC,cAAc,CAAC,WAAW,CAAC;CAAG;AAErE,QAAA,MAAM,EAAE,6EAIP,CAAC;AAGF,eAAe,EAAE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"h1.d.ts","sourceRoot":"","sources":["../../../src/components/markdown/h1.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,WAAW,OAAQ,SAAQ,KAAK,CAAC,cAAc,CAAC,kBAAkB,CAAC;CAAG;AAE5E,QAAA,MAAM,EAAE,oFAIP,CAAC;AAGF,eAAe,EAAE,CAAC"}
|