veryfront 0.0.36 → 0.0.38
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/dist/ai/components.js +34 -23
- package/dist/ai/components.js.map +2 -2
- package/dist/ai/index.js +1 -1
- package/dist/ai/index.js.map +1 -1
- package/dist/cli.js +131 -88
- package/dist/components.js +1 -1
- package/dist/components.js.map +1 -1
- package/dist/config.js +1 -1
- package/dist/config.js.map +1 -1
- package/dist/data.js +1 -1
- package/dist/data.js.map +1 -1
- package/dist/index.js +1 -7
- package/dist/index.js.map +2 -2
- package/dist/templates/ai/ai/agents/assistant.ts +20 -0
- package/dist/templates/ai/ai/prompts/assistant.ts +14 -0
- package/dist/templates/ai/ai/tools/get-weather.ts +29 -0
- package/dist/templates/ai/app/api/chat/route.ts +37 -0
- package/dist/templates/ai/app/layout.tsx +18 -0
- package/dist/templates/ai/app/page.tsx +28 -0
- package/dist/templates/ai/tsconfig.json +18 -0
- package/dist/templates/ai/veryfront.config.ts +9 -0
- package/dist/templates/app/_env.example +16 -0
- package/dist/templates/app/app/api/auth/login/route.ts +53 -0
- package/dist/templates/app/app/api/auth/logout/route.ts +27 -0
- package/dist/templates/app/app/api/auth/me/route.ts +34 -0
- package/dist/templates/app/app/api/auth/register/route.ts +42 -0
- package/dist/templates/app/app/api/stats/route.ts +21 -0
- package/dist/templates/app/app/api/users/route.ts +42 -0
- package/dist/templates/app/app/dashboard/page.tsx +29 -0
- package/dist/templates/app/app/layout.tsx +45 -0
- package/dist/templates/app/app/login/page.tsx +222 -0
- package/dist/templates/app/app/page.tsx +15 -0
- package/dist/templates/app/components/AuthProvider.tsx +51 -0
- package/dist/templates/app/components/DashboardLayout.tsx +142 -0
- package/dist/templates/app/components/FeatureGrid.tsx +98 -0
- package/dist/templates/app/components/Header.tsx +58 -0
- package/dist/templates/app/components/HeroSection.tsx +49 -0
- package/dist/templates/app/components/RecentActivity.tsx +98 -0
- package/dist/templates/app/components/StatsGrid.tsx +126 -0
- package/dist/templates/app/components/Toaster.tsx +113 -0
- package/dist/templates/app/lib/auth-client.ts +38 -0
- package/dist/templates/app/lib/auth.ts +49 -0
- package/dist/templates/app/lib/stats.ts +34 -0
- package/dist/templates/app/lib/users.ts +86 -0
- package/dist/templates/app/middleware/auth.ts +34 -0
- package/dist/templates/app/public/robots.txt +4 -0
- package/dist/templates/app/veryfront.config.js +74 -0
- package/dist/templates/blog/app/about/page.mdx +14 -0
- package/dist/templates/blog/app/archive/page.tsx +42 -0
- package/dist/templates/blog/app/blog/[slug]/page.tsx +47 -0
- package/dist/templates/blog/app/layout.tsx +54 -0
- package/dist/templates/blog/app/page.tsx +13 -0
- package/dist/templates/blog/components/BlogPostList.tsx +53 -0
- package/dist/templates/blog/components/MDXContent.tsx +26 -0
- package/dist/templates/blog/content/posts/hello-world.mdx +29 -0
- package/dist/templates/blog/content/posts/markdown-showcase.mdx +105 -0
- package/dist/templates/blog/lib/posts.ts +76 -0
- package/dist/templates/blog/lib/utils.ts +14 -0
- package/dist/templates/blog/public/robots.txt +4 -0
- package/dist/templates/blog/styles/globals.css +21 -0
- package/dist/templates/blog/veryfront.config.js +39 -0
- package/dist/templates/docs/app/docs/api/page.mdx +102 -0
- package/dist/templates/docs/app/docs/core-concepts/page.mdx +82 -0
- package/dist/templates/docs/app/docs/getting-started/page.mdx +67 -0
- package/dist/templates/docs/app/layout.tsx +41 -0
- package/dist/templates/docs/app/page.mdx +51 -0
- package/dist/templates/docs/components/CodeBlock.tsx +44 -0
- package/dist/templates/docs/components/Header.tsx +49 -0
- package/dist/templates/docs/components/Sidebar.tsx +68 -0
- package/dist/templates/docs/public/robots.txt +4 -0
- package/dist/templates/docs/styles/globals.css +48 -0
- package/dist/templates/docs/veryfront.config.js +47 -0
- package/dist/templates/minimal/app/about/page.mdx +18 -0
- package/dist/templates/minimal/app/layout.tsx +20 -0
- package/dist/templates/minimal/app/page.tsx +26 -0
- package/dist/templates/minimal/veryfront.config.js +29 -0
- package/package.json +1 -1
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { getPosts } from "../../lib/posts.ts";
|
|
2
|
+
import { formatDate } from "../../lib/utils.ts";
|
|
3
|
+
|
|
4
|
+
export default async function Archive() {
|
|
5
|
+
const posts = await getPosts();
|
|
6
|
+
const postsByYear = posts.reduce((acc, post) => {
|
|
7
|
+
const year = new Date(post.date).getFullYear();
|
|
8
|
+
if (!acc[year]) acc[year] = [];
|
|
9
|
+
acc[year].push(post);
|
|
10
|
+
return acc;
|
|
11
|
+
}, {} as Record<number, typeof posts>);
|
|
12
|
+
|
|
13
|
+
const years = Object.keys(postsByYear)
|
|
14
|
+
.map(Number)
|
|
15
|
+
.sort((a, b) => b - a);
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<div>
|
|
19
|
+
<h1 className="text-4xl font-bold mb-8">Archive</h1>
|
|
20
|
+
{years.map(year => (
|
|
21
|
+
<div key={year} className="mb-8">
|
|
22
|
+
<h2 className="text-2xl font-semibold mb-4">{year}</h2>
|
|
23
|
+
<ul className="space-y-2">
|
|
24
|
+
{postsByYear[year]?.map(post => (
|
|
25
|
+
<li key={post.slug}>
|
|
26
|
+
<a
|
|
27
|
+
href={`/blog/${post.slug}`}
|
|
28
|
+
className="text-blue-600 hover:underline"
|
|
29
|
+
>
|
|
30
|
+
{post.title}
|
|
31
|
+
</a>
|
|
32
|
+
<span className="text-gray-600 ml-2">
|
|
33
|
+
{formatDate(post.date)}
|
|
34
|
+
</span>
|
|
35
|
+
</li>
|
|
36
|
+
))}
|
|
37
|
+
</ul>
|
|
38
|
+
</div>
|
|
39
|
+
))}
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { MDX } from "veryfront/mdx";
|
|
2
|
+
import { getPost, getPosts } from "../../../lib/posts.ts";
|
|
3
|
+
import { formatDate } from "../../../lib/utils.ts";
|
|
4
|
+
|
|
5
|
+
export default async function BlogPost({
|
|
6
|
+
params
|
|
7
|
+
}: {
|
|
8
|
+
params: { slug: string }
|
|
9
|
+
}) {
|
|
10
|
+
const post = await getPost(params.slug);
|
|
11
|
+
|
|
12
|
+
if (!post) {
|
|
13
|
+
return <div>Post not found</div>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<article className="prose lg:prose-lg mx-auto">
|
|
18
|
+
<header className="mb-8">
|
|
19
|
+
<h1 className="mb-2">{post.title}</h1>
|
|
20
|
+
<div className="text-gray-600">
|
|
21
|
+
<time>{formatDate(post.date)}</time>
|
|
22
|
+
{post.author && <span> · By {post.author}</span>}
|
|
23
|
+
</div>
|
|
24
|
+
{post.tags && (
|
|
25
|
+
<div className="flex gap-2 mt-4">
|
|
26
|
+
{post.tags.map(tag => (
|
|
27
|
+
<span
|
|
28
|
+
key={tag}
|
|
29
|
+
className="px-2 py-1 bg-blue-100 text-blue-700 rounded text-sm"
|
|
30
|
+
>
|
|
31
|
+
{tag}
|
|
32
|
+
</span>
|
|
33
|
+
))}
|
|
34
|
+
</div>
|
|
35
|
+
)}
|
|
36
|
+
</header>
|
|
37
|
+
<MDX source={post.content} />
|
|
38
|
+
</article>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function generateStaticParams() {
|
|
43
|
+
const posts = await getPosts();
|
|
44
|
+
return posts.map(post => ({
|
|
45
|
+
slug: post.slug,
|
|
46
|
+
}));
|
|
47
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
export const metadata = {
|
|
4
|
+
title: "My Blog",
|
|
5
|
+
description: "A blog built with Veryfront",
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export default function RootLayout({
|
|
9
|
+
children
|
|
10
|
+
}: {
|
|
11
|
+
children: React.ReactNode
|
|
12
|
+
}) {
|
|
13
|
+
return (
|
|
14
|
+
<html lang="en">
|
|
15
|
+
<head>
|
|
16
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
17
|
+
<link
|
|
18
|
+
rel="stylesheet"
|
|
19
|
+
href="https://cdn.jsdelivr.net/npm/tailwindcss@3/dist/tailwind.min.css"
|
|
20
|
+
/>
|
|
21
|
+
</head>
|
|
22
|
+
<body className="bg-gray-50">
|
|
23
|
+
<header className="bg-white shadow-sm">
|
|
24
|
+
<nav className="max-w-4xl mx-auto px-4 py-4">
|
|
25
|
+
<div className="flex justify-between items-center">
|
|
26
|
+
<a href="/" className="text-xl font-bold text-gray-900">
|
|
27
|
+
My Blog
|
|
28
|
+
</a>
|
|
29
|
+
<div className="flex gap-6">
|
|
30
|
+
<a href="/" className="text-gray-600 hover:text-gray-900">
|
|
31
|
+
Home
|
|
32
|
+
</a>
|
|
33
|
+
<a href="/about" className="text-gray-600 hover:text-gray-900">
|
|
34
|
+
About
|
|
35
|
+
</a>
|
|
36
|
+
<a href="/archive" className="text-gray-600 hover:text-gray-900">
|
|
37
|
+
Archive
|
|
38
|
+
</a>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
</nav>
|
|
42
|
+
</header>
|
|
43
|
+
<main className="max-w-4xl mx-auto px-4 py-8">
|
|
44
|
+
{children}
|
|
45
|
+
</main>
|
|
46
|
+
<footer className="bg-gray-100 mt-16">
|
|
47
|
+
<div className="max-w-4xl mx-auto px-4 py-8 text-center text-gray-600">
|
|
48
|
+
© 2024 My Blog. Built with Veryfront.
|
|
49
|
+
</div>
|
|
50
|
+
</footer>
|
|
51
|
+
</body>
|
|
52
|
+
</html>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { BlogPostList } from "../components/BlogPostList.tsx";
|
|
2
|
+
import { getPosts } from "../lib/posts.ts";
|
|
3
|
+
|
|
4
|
+
export default async function HomePage() {
|
|
5
|
+
const posts = await getPosts();
|
|
6
|
+
|
|
7
|
+
return (
|
|
8
|
+
<div>
|
|
9
|
+
<h1 className="text-4xl font-bold mb-8">Latest Posts</h1>
|
|
10
|
+
<BlogPostList posts={posts} />
|
|
11
|
+
</div>
|
|
12
|
+
);
|
|
13
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { formatDate } from "../lib/utils.ts";
|
|
3
|
+
|
|
4
|
+
interface Post {
|
|
5
|
+
slug: string;
|
|
6
|
+
title: string;
|
|
7
|
+
date: string;
|
|
8
|
+
excerpt?: string;
|
|
9
|
+
tags?: string[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function BlogPostList({ posts }: { posts: Post[] }) {
|
|
13
|
+
return (
|
|
14
|
+
<div className="space-y-8">
|
|
15
|
+
{posts.map(post => (
|
|
16
|
+
<article key={post.slug} className="border-b pb-8">
|
|
17
|
+
<h2 className="text-2xl font-semibold mb-2">
|
|
18
|
+
<a
|
|
19
|
+
href={`/blog/${post.slug}`}
|
|
20
|
+
className="text-gray-900 hover:text-blue-600"
|
|
21
|
+
>
|
|
22
|
+
{post.title}
|
|
23
|
+
</a>
|
|
24
|
+
</h2>
|
|
25
|
+
<div className="text-gray-600 text-sm mb-2">
|
|
26
|
+
{formatDate(post.date)}
|
|
27
|
+
</div>
|
|
28
|
+
{post.excerpt && (
|
|
29
|
+
<p className="text-gray-700 mb-4">{post.excerpt}</p>
|
|
30
|
+
)}
|
|
31
|
+
{post.tags && (
|
|
32
|
+
<div className="flex gap-2">
|
|
33
|
+
{post.tags.map(tag => (
|
|
34
|
+
<span
|
|
35
|
+
key={tag}
|
|
36
|
+
className="px-2 py-1 bg-gray-100 text-gray-600 rounded text-sm"
|
|
37
|
+
>
|
|
38
|
+
{tag}
|
|
39
|
+
</span>
|
|
40
|
+
))}
|
|
41
|
+
</div>
|
|
42
|
+
)}
|
|
43
|
+
<a
|
|
44
|
+
href={`/blog/${post.slug}`}
|
|
45
|
+
className="text-blue-600 hover:underline mt-2 inline-block"
|
|
46
|
+
>
|
|
47
|
+
Read more →
|
|
48
|
+
</a>
|
|
49
|
+
</article>
|
|
50
|
+
))}
|
|
51
|
+
</div>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { MDXProvider } from "@mdx-js/react";
|
|
5
|
+
|
|
6
|
+
const components = {
|
|
7
|
+
// Add custom components here
|
|
8
|
+
pre: ({ children, ...props }: React.ComponentProps<'pre'>) => (
|
|
9
|
+
<pre {...props} className="bg-gray-100 p-4 rounded-lg overflow-x-auto">
|
|
10
|
+
{children}
|
|
11
|
+
</pre>
|
|
12
|
+
),
|
|
13
|
+
code: ({ children, ...props }: React.ComponentProps<'code'>) => (
|
|
14
|
+
<code {...props} className="bg-gray-100 px-1 py-0.5 rounded text-sm">
|
|
15
|
+
{children}
|
|
16
|
+
</code>
|
|
17
|
+
),
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export function MDXContent({ content }: { content: React.ReactNode }) {
|
|
21
|
+
return (
|
|
22
|
+
<MDXProvider components={components}>
|
|
23
|
+
{content}
|
|
24
|
+
</MDXProvider>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Hello World
|
|
3
|
+
date: 2024-01-01
|
|
4
|
+
author: Your Name
|
|
5
|
+
tags: [intro, meta]
|
|
6
|
+
excerpt: Welcome to my new blog built with Veryfront!
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
Welcome to my new blog! This is my first post built with Veryfront.
|
|
10
|
+
|
|
11
|
+
## Why Veryfront?
|
|
12
|
+
|
|
13
|
+
I chose Veryfront because:
|
|
14
|
+
|
|
15
|
+
1. **Deno-first** - No node_modules, secure by default
|
|
16
|
+
2. **Great MDX support** - Write content with React components
|
|
17
|
+
3. **Fast** - Built on modern web standards
|
|
18
|
+
4. **Simple** - Easy to understand and customize
|
|
19
|
+
|
|
20
|
+
## What's Next?
|
|
21
|
+
|
|
22
|
+
I'll be writing about:
|
|
23
|
+
|
|
24
|
+
- Web development tips and tricks
|
|
25
|
+
- My experiences with Deno and modern JavaScript
|
|
26
|
+
- Building applications with React Server Components
|
|
27
|
+
- And much more!
|
|
28
|
+
|
|
29
|
+
Stay tuned for more posts!
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Markdown Showcase
|
|
3
|
+
date: 2024-01-02
|
|
4
|
+
author: Your Name
|
|
5
|
+
tags: [markdown, demo]
|
|
6
|
+
excerpt: A demonstration of all the Markdown features supported in Veryfront
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
This post showcases all the Markdown and MDX features available in Veryfront.
|
|
10
|
+
|
|
11
|
+
## Headers
|
|
12
|
+
|
|
13
|
+
### H3 Header
|
|
14
|
+
#### H4 Header
|
|
15
|
+
##### H5 Header
|
|
16
|
+
###### H6 Header
|
|
17
|
+
|
|
18
|
+
## Emphasis
|
|
19
|
+
|
|
20
|
+
*This text is italicized*
|
|
21
|
+
**This text is bold**
|
|
22
|
+
***This text is bold and italicized***
|
|
23
|
+
~~This text is struck through~~
|
|
24
|
+
|
|
25
|
+
## Lists
|
|
26
|
+
|
|
27
|
+
### Unordered List
|
|
28
|
+
- First item
|
|
29
|
+
- Second item
|
|
30
|
+
- Nested item
|
|
31
|
+
- Another nested item
|
|
32
|
+
- Third item
|
|
33
|
+
|
|
34
|
+
### Ordered List
|
|
35
|
+
1. First step
|
|
36
|
+
2. Second step
|
|
37
|
+
1. Sub-step A
|
|
38
|
+
2. Sub-step B
|
|
39
|
+
3. Third step
|
|
40
|
+
|
|
41
|
+
## Links and Images
|
|
42
|
+
|
|
43
|
+
[Visit Veryfront](https://github.com/veryfront/veryfront)
|
|
44
|
+
|
|
45
|
+

|
|
46
|
+
|
|
47
|
+
## Code
|
|
48
|
+
|
|
49
|
+
Inline code: `const greeting = "Hello, World!"`
|
|
50
|
+
|
|
51
|
+
```javascript
|
|
52
|
+
// Code block with syntax highlighting
|
|
53
|
+
function fibonacci(n) {
|
|
54
|
+
if (n <= 1) return n;
|
|
55
|
+
return fibonacci(n - 1) + fibonacci(n - 2);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
console.log(fibonacci(10)); // 55
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
// React component
|
|
63
|
+
export function Counter() {
|
|
64
|
+
const [count, setCount] = useState(0);
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<button onClick={() => setCount(count + 1)}>
|
|
68
|
+
Count: {count}
|
|
69
|
+
</button>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Blockquotes
|
|
75
|
+
|
|
76
|
+
> "The best way to predict the future is to invent it."
|
|
77
|
+
> — Alan Kay
|
|
78
|
+
|
|
79
|
+
## Tables
|
|
80
|
+
|
|
81
|
+
| Feature | Supported | Notes |
|
|
82
|
+
|---------|-----------|--------|
|
|
83
|
+
| MDX | ✅ | Full support |
|
|
84
|
+
| RSC | ✅ | React Server Components |
|
|
85
|
+
| HMR | ✅ | Hot Module Replacement |
|
|
86
|
+
| TypeScript | ✅ | Built-in support |
|
|
87
|
+
|
|
88
|
+
## Task Lists
|
|
89
|
+
|
|
90
|
+
- [x] Set up Veryfront
|
|
91
|
+
- [x] Create first blog post
|
|
92
|
+
- [ ] Customize theme
|
|
93
|
+
- [ ] Add comments system
|
|
94
|
+
|
|
95
|
+
## Horizontal Rule
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## MDX Components
|
|
100
|
+
|
|
101
|
+
You can also embed React components directly in your markdown:
|
|
102
|
+
|
|
103
|
+
<button className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">
|
|
104
|
+
Click me!
|
|
105
|
+
</button>
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { parse as parseYaml } from "yaml";
|
|
2
|
+
import { join } from "veryfront/platform/path";
|
|
3
|
+
import { cwd, createFileSystem } from "veryfront/platform";
|
|
4
|
+
|
|
5
|
+
interface PostMeta {
|
|
6
|
+
title: string;
|
|
7
|
+
date: string;
|
|
8
|
+
author?: string;
|
|
9
|
+
tags?: string[];
|
|
10
|
+
excerpt?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface Post extends PostMeta {
|
|
14
|
+
slug: string;
|
|
15
|
+
content: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const fs = createFileSystem();
|
|
19
|
+
|
|
20
|
+
function getPostsDir(): string {
|
|
21
|
+
return join(cwd(), "content", "posts");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function extractFrontmatter(content: string): { attrs: PostMeta; body: string } {
|
|
25
|
+
const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
26
|
+
if (!match) {
|
|
27
|
+
return { attrs: { title: "Untitled", date: new Date().toISOString() }, body: content };
|
|
28
|
+
}
|
|
29
|
+
const attrs = parseYaml(match[1]) as PostMeta;
|
|
30
|
+
const body = match[2].trim();
|
|
31
|
+
return { attrs, body };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function getPosts(): Promise<Post[]> {
|
|
35
|
+
const posts: Post[] = [];
|
|
36
|
+
const postsDir = getPostsDir();
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
for await (const entry of fs.readDir(postsDir)) {
|
|
40
|
+
if (entry.isFile && entry.name.endsWith(".mdx")) {
|
|
41
|
+
const slug = entry.name.replace(/\.mdx$/, "");
|
|
42
|
+
const content = await fs.readTextFile(join(postsDir, entry.name));
|
|
43
|
+
const { attrs, body } = extractFrontmatter(content);
|
|
44
|
+
|
|
45
|
+
posts.push({
|
|
46
|
+
slug,
|
|
47
|
+
content: body,
|
|
48
|
+
...attrs,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.error("Error reading posts:", error);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Sort by date, newest first
|
|
57
|
+
return posts.sort((a, b) =>
|
|
58
|
+
new Date(b.date).getTime() - new Date(a.date).getTime()
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export async function getPost(slug: string): Promise<Post | null> {
|
|
63
|
+
const postsDir = getPostsDir();
|
|
64
|
+
try {
|
|
65
|
+
const content = await fs.readTextFile(join(postsDir, `${slug}.mdx`));
|
|
66
|
+
const { attrs, body } = extractFrontmatter(content);
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
slug,
|
|
70
|
+
content: body,
|
|
71
|
+
...attrs,
|
|
72
|
+
};
|
|
73
|
+
} catch {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function formatDate(date: string): string {
|
|
2
|
+
return new Date(date).toLocaleDateString("en-US", {
|
|
3
|
+
year: "numeric",
|
|
4
|
+
month: "long",
|
|
5
|
+
day: "numeric",
|
|
6
|
+
});
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function slugify(text: string): string {
|
|
10
|
+
return text
|
|
11
|
+
.toLowerCase()
|
|
12
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
13
|
+
.replace(/^-+|-+$/g, "");
|
|
14
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
@tailwind base;
|
|
2
|
+
@tailwind components;
|
|
3
|
+
@tailwind utilities;
|
|
4
|
+
|
|
5
|
+
/* Custom styles for blog */
|
|
6
|
+
.prose pre {
|
|
7
|
+
background-color: #1e293b;
|
|
8
|
+
color: #e2e8f0;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.prose code {
|
|
12
|
+
background-color: #f3f4f6;
|
|
13
|
+
padding: 0.125rem 0.25rem;
|
|
14
|
+
border-radius: 0.25rem;
|
|
15
|
+
font-size: 0.875em;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.prose pre code {
|
|
19
|
+
background-color: transparent;
|
|
20
|
+
padding: 0;
|
|
21
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
title: "My Blog",
|
|
3
|
+
description: "A blog built with Veryfront",
|
|
4
|
+
author: "Your Name",
|
|
5
|
+
|
|
6
|
+
// Blog configuration
|
|
7
|
+
blog: {
|
|
8
|
+
postsPerPage: 10,
|
|
9
|
+
rss: true,
|
|
10
|
+
categories: ["Tech", "Life", "Code"],
|
|
11
|
+
},
|
|
12
|
+
|
|
13
|
+
// Theme
|
|
14
|
+
theme: {
|
|
15
|
+
colors: {
|
|
16
|
+
primary: "#3B82F6",
|
|
17
|
+
secondary: "#10B981",
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
// Development
|
|
22
|
+
dev: {
|
|
23
|
+
port: 3002,
|
|
24
|
+
open: true,
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
// Import map
|
|
28
|
+
resolve: {
|
|
29
|
+
importMap: {
|
|
30
|
+
imports: {
|
|
31
|
+
"react": "https://esm.sh/react@19.1.1",
|
|
32
|
+
"react/jsx-runtime": "https://esm.sh/react@19.1.1/jsx-runtime",
|
|
33
|
+
"react-dom": "https://esm.sh/react-dom@19.1.1",
|
|
34
|
+
"react-dom/client": "https://esm.sh/react-dom@19.1.1/client",
|
|
35
|
+
"date-fns": "https://esm.sh/date-fns@3.0.0",
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# API Reference
|
|
2
|
+
|
|
3
|
+
Complete API documentation for all available functions and components.
|
|
4
|
+
|
|
5
|
+
## Core API
|
|
6
|
+
|
|
7
|
+
### `createApp()`
|
|
8
|
+
|
|
9
|
+
Creates a new application instance.
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
import { createApp } from '@example/core';
|
|
13
|
+
|
|
14
|
+
const app = createApp({
|
|
15
|
+
name: 'My App',
|
|
16
|
+
version: '1.0.0',
|
|
17
|
+
});
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**Parameters:**
|
|
21
|
+
- `config` - Application configuration object
|
|
22
|
+
- `name` (string) - Application name
|
|
23
|
+
- `version` (string) - Application version
|
|
24
|
+
- `plugins?` (Plugin[]) - Optional plugins
|
|
25
|
+
|
|
26
|
+
**Returns:** `Application` instance
|
|
27
|
+
|
|
28
|
+
### `defineRoute()`
|
|
29
|
+
|
|
30
|
+
Defines a new route handler.
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
export const route = defineRoute({
|
|
34
|
+
path: '/api/users/:id',
|
|
35
|
+
method: 'GET',
|
|
36
|
+
handler: async (req, params) => {
|
|
37
|
+
const user = await getUser(params.id);
|
|
38
|
+
return Response.json(user);
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Components
|
|
44
|
+
|
|
45
|
+
### `<Layout>`
|
|
46
|
+
|
|
47
|
+
Base layout component for pages.
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
import { Layout } from '@example/ui';
|
|
51
|
+
|
|
52
|
+
export default function Page() {
|
|
53
|
+
return (
|
|
54
|
+
<Layout title="My Page">
|
|
55
|
+
<h1>Content</h1>
|
|
56
|
+
</Layout>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Props:**
|
|
62
|
+
- `title` (string) - Page title
|
|
63
|
+
- `children` (ReactNode) - Page content
|
|
64
|
+
- `sidebar?` (boolean) - Show sidebar
|
|
65
|
+
|
|
66
|
+
### `<Button>`
|
|
67
|
+
|
|
68
|
+
Styled button component.
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
<Button
|
|
72
|
+
variant="primary"
|
|
73
|
+
size="lg"
|
|
74
|
+
onClick={handleClick}
|
|
75
|
+
>
|
|
76
|
+
Click me
|
|
77
|
+
</Button>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**Props:**
|
|
81
|
+
- `variant` - "primary" | "secondary" | "danger"
|
|
82
|
+
- `size` - "sm" | "md" | "lg"
|
|
83
|
+
- `disabled?` (boolean)
|
|
84
|
+
- `onClick?` (function)
|
|
85
|
+
|
|
86
|
+
## Hooks
|
|
87
|
+
|
|
88
|
+
### `useData()`
|
|
89
|
+
|
|
90
|
+
Fetches and caches data.
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
const { data, error, loading } = useData('/api/users');
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### `useAuth()`
|
|
97
|
+
|
|
98
|
+
Authentication hook.
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
const { user, login, logout } = useAuth();
|
|
102
|
+
```
|