chalknotes 0.0.25 β†’ 0.0.26

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 CHANGED
@@ -9,6 +9,9 @@
9
9
  - πŸ“„ Fetch blog posts from Notion
10
10
  - πŸͺ„ Auto-generate routes for App Router or Page Router
11
11
  - βš™οΈ Helpers for `getStaticProps` / `getStaticPaths`
12
+ - 🎨 Clean, responsive themes (light & dark mode)
13
+ - πŸ”§ Interactive configuration setup
14
+ - πŸ“ Customizable route paths
12
15
  - 🧠 Minimal setup – just run `chalknotes`
13
16
 
14
17
  ---
@@ -25,28 +28,64 @@ npm install chalknotes
25
28
 
26
29
  ## πŸ§™β€β™‚οΈ Quick Start
27
30
 
28
- ```bash
29
- npx chalknotes
30
- ```
31
+ 1. **Set up environment variables**
32
+ ```bash
33
+ # Create .env file
34
+ NOTION_TOKEN=secret_...
35
+ NOTION_DATABASE_ID=xxxxxxxxxxxxxxxxxxxxxx
36
+ ```
37
+
38
+ 2. **Run the CLI**
39
+ ```bash
40
+ npx chalknotes
41
+ ```
31
42
 
32
- - Automatically detects if you're using **App Router** or **Page Router**
33
- - Generates blog routes at `/blog/[slug]`
34
- - You’re done βœ…
43
+ 3. **That's it!** βœ…
44
+ - Automatically detects if you're using **App Router** or **Page Router**
45
+ - Creates `blog.config.js` with default configuration (if needed)
46
+ - Generates blog routes with clean, responsive templates
47
+ - Supports light and dark themes
35
48
 
36
49
  ---
37
50
 
38
- ## πŸ”§ Setup
51
+ ## πŸ”§ Configuration
52
+
53
+ The CLI creates a `blog.config.js` file in your project root. Customize it to match your needs:
54
+
55
+ ```javascript
56
+ module.exports = {
57
+ // Notion Configuration
58
+ notionToken: process.env.NOTION_TOKEN,
59
+ notionDatabaseId: process.env.NOTION_DATABASE_ID,
60
+
61
+ // Blog Configuration
62
+ routeBasePath: '/blog', // Default: '/blog'
63
+ theme: 'default', // Options: 'default' (light) or 'dark'
64
+ plugins: [],
65
+ };
66
+ ```
39
67
 
40
- Make sure your `.env` file contains:
68
+ ### Configuration Options
41
69
 
42
- ```env
43
- NOTION_TOKEN=secret_...
44
- NOTION_DATABASE_ID=xxxxxxxxxxxxxxxxxxxxxx
45
- ```
70
+ - **`routeBasePath`**: Customize your blog route (e.g., `/posts`, `/articles`)
71
+ - **`theme`**: Choose between `'default'` (light mode) or `'dark'` (dark mode)
72
+ - **`plugins`**: Array for future plugin support
73
+
74
+ ---
75
+
76
+ ## 🎨 Themes
77
+
78
+ ### Default Theme (Light Mode)
79
+ - Clean white cards with subtle shadows
80
+ - Light gray background
81
+ - Dark text for optimal readability
82
+ - Responsive design with Tailwind CSS
46
83
 
47
- Your Notion database should have:
48
- - A **"Name"** title property
49
- - A **"Published"** checkbox property
84
+ ### Dark Theme
85
+ - Dark background with gray cards
86
+ - White text with proper contrast
87
+ - Inverted typography for dark mode
88
+ - Same responsive layout
50
89
 
51
90
  ---
52
91
 
@@ -57,7 +96,7 @@ Your Notion database should have:
57
96
  Creates:
58
97
 
59
98
  ```js
60
- // pages/blog/[slug].js
99
+ // pages/blog/[slug].js (or custom route)
61
100
  import { getStaticPropsForPost, getStaticPathsForPosts } from 'chalknotes';
62
101
 
63
102
  export const getStaticProps = getStaticPropsForPost;
@@ -65,10 +104,19 @@ export const getStaticPaths = getStaticPathsForPosts;
65
104
 
66
105
  export default function BlogPost({ post }) {
67
106
  return (
68
- <article>
69
- <h1>{post.title}</h1>
70
- <div dangerouslySetInnerHTML={{ __html: post.content }} />
71
- </article>
107
+ <div className="min-h-screen bg-gray-50">
108
+ <main className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
109
+ <article className="bg-white rounded-lg shadow-sm border border-gray-200 p-8">
110
+ <h1 className="text-4xl font-bold text-gray-900 mb-6 leading-tight">
111
+ {post.title}
112
+ </h1>
113
+ <div
114
+ className="prose prose-lg max-w-none text-gray-700 leading-relaxed"
115
+ dangerouslySetInnerHTML={{ __html: post.content }}
116
+ />
117
+ </article>
118
+ </main>
119
+ </div>
72
120
  );
73
121
  }
74
122
  ```
@@ -80,17 +128,26 @@ export default function BlogPost({ post }) {
80
128
  Creates:
81
129
 
82
130
  ```jsx
83
- // app/blog/[slug]/page.jsx
131
+ // app/blog/[slug]/page.jsx (or custom route)
84
132
  import { getPostBySlug } from 'chalknotes';
85
133
 
86
134
  export default async function BlogPost({ params }) {
87
135
  const post = await getPostBySlug(params.slug);
88
136
 
89
137
  return (
90
- <article>
91
- <h1>{post.title}</h1>
92
- <div dangerouslySetInnerHTML={{ __html: post.content }} />
93
- </article>
138
+ <div className="min-h-screen bg-gray-50">
139
+ <main className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
140
+ <article className="bg-white rounded-lg shadow-sm border border-gray-200 p-8">
141
+ <h1 className="text-4xl font-bold text-gray-900 mb-6 leading-tight">
142
+ {post.title}
143
+ </h1>
144
+ <div
145
+ className="prose prose-lg max-w-none text-gray-700 leading-relaxed"
146
+ dangerouslySetInnerHTML={{ __html: post.content }}
147
+ />
148
+ </article>
149
+ </main>
150
+ </div>
94
151
  );
95
152
  }
96
153
  ```
@@ -134,12 +191,32 @@ For use with `getStaticPaths` in Page Router.
134
191
 
135
192
  ---
136
193
 
194
+ ## 🎨 Styling
195
+
196
+ The generated templates use Tailwind CSS with:
197
+ - Clean, minimal design
198
+ - Responsive layout
199
+ - Typography optimized for readability
200
+ - Proper spacing and hierarchy
201
+ - Light and dark mode support
202
+
203
+ Make sure you have Tailwind CSS installed in your project:
204
+
205
+ ```bash
206
+ npm install -D tailwindcss @tailwindcss/typography
207
+ ```
208
+
209
+ ---
210
+
137
211
  ## πŸ“… Roadmap
138
212
 
139
- - [ ] Rich block support (images, lists, toggles, etc)
213
+ - [ ] Plugin system for custom components
214
+ - [ ] More Notion block support (images, lists, code blocks)
140
215
  - [ ] RSS feed support
141
216
  - [ ] MDX or Markdown output option
142
- - [ ] Custom blog.config.js support
217
+ - [ ] Custom theme templates
218
+ - [ ] Search functionality
219
+ - [ ] Categories and tags support
143
220
 
144
221
  ---
145
222
 
package/bin/cli.js CHANGED
@@ -1,20 +1,95 @@
1
1
  #!/usr/bin/env node
2
2
  const fs = require("fs");
3
3
  const path = require("path");
4
+ const dotenv = require("dotenv");
5
+ const readline = require("readline");
6
+ dotenv.config();
4
7
 
5
- console.log("Initializing ChalkNotes...");
8
+ console.log("πŸš€ Initializing ChalkNotes...");
9
+
10
+ // Check environment variables
11
+ if (!process.env.NOTION_TOKEN) {
12
+ console.error("❌ NOTION_TOKEN is not set in .env file");
13
+ console.log("πŸ’‘ Please create a .env file with your NOTION_TOKEN");
14
+ process.exit(1);
15
+ }
16
+
17
+ if (!process.env.NOTION_DATABASE_ID) {
18
+ console.error("❌ NOTION_DATABASE_ID is not set in .env file");
19
+ console.log("πŸ’‘ Please create a .env file with your NOTION_DATABASE_ID");
20
+ process.exit(1);
21
+ }
22
+
23
+ console.log("βœ… Environment variables are set");
6
24
 
7
25
  const configPath = path.join(process.cwd(), 'blog.config.js');
8
- const pageRouter = path.join(process.cwd(), '/pages')
9
- const appRouter = path.join(process.cwd(), '/app')
10
26
 
27
+ // Check if blog.config.js exists
28
+ if (!fs.existsSync(configPath)) {
29
+ console.log("\n❌ blog.config.js not found");
30
+ console.log("This file is required to configure your blog settings.");
31
+
32
+ const rl = readline.createInterface({
33
+ input: process.stdin,
34
+ output: process.stdout
35
+ });
36
+
37
+ rl.question("Would you like to create a default blog.config.js? (y/n): ", (answer) => {
38
+ if (answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes') {
39
+ console.log("πŸ“ Creating blog.config.js with default configuration...");
40
+
41
+ const configTemplate = `module.exports = {
42
+ notionToken: process.env.NOTION_TOKEN,
43
+ notionDatabaseId: process.env.NOTION_DATABASE_ID,
44
+ routeBasePath: '/blog',
45
+ theme: 'default',
46
+ plugins: [],
47
+ };`.trim();
48
+
49
+ fs.writeFileSync(configPath, configTemplate);
50
+ console.log("βœ… Created blog.config.js with default configuration");
51
+ console.log("\nπŸ’‘ Now you can re-run 'npx chalknotes' to scaffold your blog pages!");
52
+ } else {
53
+ console.log("❌ Please create a blog.config.js file and try again.");
54
+ }
55
+ rl.close();
56
+ });
57
+ return;
58
+ }
11
59
 
12
- if (fs.existsSync(pageRouter)) {
13
- console.log("βœ… Page router found");
14
- const slugDir = path.join(pageRouter, "blog", "[slug].js")
15
- const dirPath = path.dirname(slugDir);
60
+ // Load configuration
61
+ let config;
62
+ try {
63
+ config = require(configPath);
64
+ } catch (error) {
65
+ console.error("❌ Error loading blog.config.js:", error.message);
66
+ process.exit(1);
67
+ }
16
68
 
17
- const pageRouterTemplate = `
69
+ // Set defaults for missing config values
70
+ config.routeBasePath = config.routeBasePath || '/blog';
71
+ config.theme = config.theme || 'default';
72
+
73
+ console.log("βœ… Configuration loaded successfully");
74
+ console.log(`πŸ“ Route base path: ${config.routeBasePath}`);
75
+ console.log(`🎨 Theme: ${config.theme}`);
76
+
77
+ // Ask to proceed with scaffolding
78
+ console.log("\nπŸ”¨ Ready to scaffold your blog page?");
79
+ console.log("This will create a clean, responsive blog template using Tailwind CSS.");
80
+ console.log("Press Enter to continue or Ctrl+C to cancel...");
81
+
82
+ // Create blog page templates based on theme and route
83
+ const pageRouter = path.join(process.cwd(), '/pages')
84
+ const appRouter = path.join(process.cwd(), '/app')
85
+
86
+ // Generate templates based on theme
87
+ function getTemplates(theme, routeBasePath) {
88
+ const routePath = routeBasePath.replace(/^\//, ''); // Remove leading slash
89
+
90
+ if (theme === 'dark') {
91
+ return {
92
+ pageRouter: `
18
93
  import { getStaticPropsForPost, getStaticPathsForPosts } from 'chalknotes';
19
94
 
20
95
  export const getStaticProps = getStaticPropsForPost;
@@ -22,53 +97,129 @@ export const getStaticPaths = getStaticPathsForPosts;
22
97
 
23
98
  export default function BlogPost({ post }) {
24
99
  return (
25
- <article className="max-w-4xl mx-auto px-4 py-8">
26
- <h1 className="text-3xl font-bold mb-4">{post.title}</h1>
27
- <div className="text-lg text-gray-700" dangerouslySetInnerHTML={{ __html: post.content }} />
28
- </article>
100
+ <div className="min-h-screen bg-gray-900">
101
+ <main className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
102
+ <article className="bg-gray-800 rounded-lg shadow-lg border border-gray-700 p-8">
103
+ <h1 className="text-4xl font-bold text-white mb-6 leading-tight">
104
+ {post.title}
105
+ </h1>
106
+ <div
107
+ className="prose prose-lg prose-invert max-w-none text-gray-300 leading-relaxed"
108
+ dangerouslySetInnerHTML={{ __html: post.content }}
109
+ />
110
+ </article>
111
+ </main>
112
+ </div>
29
113
  );
30
- }
31
- `.trim();
114
+ }`.trim(),
115
+ appRouter: `
116
+ import { getPostBySlug } from 'chalknotes';
32
117
 
33
- fs.mkdirSync(dirPath, { recursive: true });
34
- fs.writeFileSync(slugDir, pageRouterTemplate)
35
- } else if (fs.existsSync(appRouter)) {
36
- console.log("βœ… App router found");
37
- const slugDir = path.join(appRouter, 'blog', "[slug]", "page.jsx")
38
- const dirPath = path.dirname(slugDir);
118
+ export default async function BlogPost({ params }) {
119
+ const post = await getPostBySlug(params.slug);
39
120
 
40
- const appRouterTemplate = `
121
+ return (
122
+ <div className="min-h-screen bg-gray-900">
123
+ <main className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
124
+ <article className="bg-gray-800 rounded-lg shadow-lg border border-gray-700 p-8">
125
+ <h1 className="text-4xl font-bold text-white mb-6 leading-tight">
126
+ {post.title}
127
+ </h1>
128
+ <div
129
+ className="prose prose-lg prose-invert max-w-none text-gray-300 leading-relaxed"
130
+ dangerouslySetInnerHTML={{ __html: post.content }}
131
+ />
132
+ </article>
133
+ </main>
134
+ </div>
135
+ );
136
+ }`.trim()
137
+ };
138
+ } else {
139
+ // Default theme (light mode)
140
+ return {
141
+ pageRouter: `
142
+ import { getStaticPropsForPost, getStaticPathsForPosts } from 'chalknotes';
143
+
144
+ export const getStaticProps = getStaticPropsForPost;
145
+ export const getStaticPaths = getStaticPathsForPosts;
146
+
147
+ export default function BlogPost({ post }) {
148
+ return (
149
+ <div className="min-h-screen bg-gray-50">
150
+ <main className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
151
+ <article className="bg-white rounded-lg shadow-sm border border-gray-200 p-8">
152
+ <h1 className="text-4xl font-bold text-gray-900 mb-6 leading-tight">
153
+ {post.title}
154
+ </h1>
155
+ <div
156
+ className="prose prose-lg max-w-none text-gray-700 leading-relaxed"
157
+ dangerouslySetInnerHTML={{ __html: post.content }}
158
+ />
159
+ </article>
160
+ </main>
161
+ </div>
162
+ );
163
+ }`.trim(),
164
+ appRouter: `
41
165
  import { getPostBySlug } from 'chalknotes';
42
166
 
43
167
  export default async function BlogPost({ params }) {
44
168
  const post = await getPostBySlug(params.slug);
45
169
 
46
170
  return (
47
- <article className="max-w-4xl mx-auto px-4 py-8">
48
- <h1 className="text-3xl font-bold mb-4">{post.title}</h1>
49
- <div className="text-lg text-gray-700" dangerouslySetInnerHTML={{ __html: post.content }} />
50
- </article>
171
+ <div className="min-h-screen bg-gray-50">
172
+ <main className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
173
+ <article className="bg-white rounded-lg shadow-sm border border-gray-200 p-8">
174
+ <h1 className="text-4xl font-bold text-gray-900 mb-6 leading-tight">
175
+ {post.title}
176
+ </h1>
177
+ <div
178
+ className="prose prose-lg max-w-none text-gray-700 leading-relaxed"
179
+ dangerouslySetInnerHTML={{ __html: post.content }}
180
+ />
181
+ </article>
182
+ </main>
183
+ </div>
51
184
  );
185
+ }`.trim()
186
+ };
187
+ }
52
188
  }
53
- `.trim();
54
189
 
190
+ // Create blog page templates
191
+ if (fs.existsSync(pageRouter)) {
192
+ console.log("βœ… Page router found");
193
+ const routePath = config.routeBasePath.replace(/^\//, ''); // Remove leading slash
194
+ const slugDir = path.join(pageRouter, routePath, "[slug].js")
195
+ const dirPath = path.dirname(slugDir);
196
+
197
+ const templates = getTemplates(config.theme, config.routeBasePath);
198
+
199
+ fs.mkdirSync(dirPath, { recursive: true });
200
+ fs.writeFileSync(slugDir, templates.pageRouter);
201
+ console.log(`βœ… Created pages/${routePath}/[slug].js`);
202
+
203
+ } else if (fs.existsSync(appRouter)) {
204
+ console.log("βœ… App router found");
205
+ const routePath = config.routeBasePath.replace(/^\//, ''); // Remove leading slash
206
+ const slugDir = path.join(appRouter, routePath, "[slug]", "page.jsx")
207
+ const dirPath = path.dirname(slugDir);
208
+
209
+ const templates = getTemplates(config.theme, config.routeBasePath);
210
+
55
211
  fs.mkdirSync(dirPath, { recursive: true });
56
- fs.writeFileSync(slugDir, appRouterTemplate)
212
+ fs.writeFileSync(slugDir, templates.appRouter);
213
+ console.log(`βœ… Created app/${routePath}/[slug]/page.jsx`);
214
+
57
215
  } else {
58
- console.log("❌ Page router and app router not found");
216
+ console.log("❌ Neither pages/ nor app/ directory found");
217
+ console.log("πŸ’‘ Please make sure you're running this in a Next.js project");
218
+ process.exit(1);
59
219
  }
60
220
 
61
- // if (!fs.existsSync(configPath)) {
62
- // const configTemplate = `
63
- // module.exports = {
64
- // notionDatabaseId: '',
65
- // notionToken: process.env.NOTION_TOKEN,
66
- // routeBasePath: '/blog',
67
- // plugins: [],
68
- // };
69
- // `;
70
- // fs.writeFileSync(configPath, configTemplate.trim());
71
- // console.log("βœ… Created blog.config.js");
72
- // } else {
73
- // console.log("⚠️ blog.config.js already exists");
74
- // }
221
+ console.log("\nπŸŽ‰ Blog page scaffolded successfully!");
222
+ console.log(`\nπŸ“ Next steps:`);
223
+ console.log("1. Add Tailwind CSS to your project if not already installed");
224
+ console.log("2. Start your development server");
225
+ console.log(`3. Visit ${config.routeBasePath}/[your-post-slug] to see your blog`);
package/blog.config.js ADDED
@@ -0,0 +1,7 @@
1
+ module.exports = {
2
+ notionToken: process.env.NOTION_TOKEN,
3
+ notionDatabaseId: process.env.NOTION_DATABASE_ID,
4
+ routeBasePath: '/blog',
5
+ theme: 'default',
6
+ plugins: [],
7
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chalknotes",
3
- "version": "0.0.25",
3
+ "version": "0.0.26",
4
4
  "description": "A tool that simplifies blogs.",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -12,6 +12,9 @@ const getPostBySlug = async (slug) => {
12
12
  }
13
13
  }
14
14
  })
15
+ if (response.results.length === 0) {
16
+ throw new Error("No posts found");
17
+ }
15
18
 
16
19
  for (const page of response.results) {
17
20
  const titleProperty = page.properties["Name"];