chalknotes 0.0.26 → 0.0.27

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chalknotes",
3
- "version": "0.0.26",
3
+ "version": "0.0.27",
4
4
  "description": "A tool that simplifies blogs.",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -27,24 +27,7 @@ const getPostBySlug = async (slug) => {
27
27
  })
28
28
  let content = ""
29
29
  for (const block of response.results) {
30
- switch (block.type) {
31
- case "paragraph":
32
- const textHTML = block.paragraph.rich_text.map(text => text.plain_text).join("");
33
- content += `<p class="mb-4">${textHTML}</p>`
34
- break;
35
- case "heading_1":
36
- const headingHTML = block.heading_1.rich_text.map(text => text.plain_text).join("");
37
- content += `<h1 class="text-2xl font-bold mb-4">${headingHTML}</h1>`
38
- break;
39
- case "heading_2":
40
- const heading2HTML = block.heading_2.rich_text.map(text => text.plain_text).join("");
41
- content += `<h2 class="text-xl font-bold mb-4">${heading2HTML}</h2>`
42
- break;
43
- case "heading_3":
44
- const heading3HTML = block.heading_3.rich_text.map(text => text.plain_text).join("");
45
- content += `<h3 class="text-lg font-bold mb-4">${heading3HTML}</h3>`
46
- break;
47
- }
30
+ content += processBlock(block);
48
31
  }
49
32
  return {
50
33
  title,
@@ -62,29 +45,222 @@ const getPostBySlug = async (slug) => {
62
45
  }
63
46
  }
64
47
 
65
- module.exports = {
66
- getPostBySlug
48
+ /**
49
+ * Process individual Notion blocks and convert to HTML
50
+ * @param {Object} block - Notion block object
51
+ * @returns {string} HTML string
52
+ */
53
+ function processBlock(block) {
54
+ switch (block.type) {
55
+ case "paragraph":
56
+ return processRichText(block.paragraph.rich_text, "p", "mb-4");
57
+
58
+ case "heading_1":
59
+ return processRichText(block.heading_1.rich_text, "h1", "text-3xl font-bold mb-6");
60
+
61
+ case "heading_2":
62
+ return processRichText(block.heading_2.rich_text, "h2", "text-2xl font-bold mb-5");
63
+
64
+ case "heading_3":
65
+ return processRichText(block.heading_3.rich_text, "h3", "text-xl font-bold mb-4");
66
+
67
+ case "bulleted_list_item":
68
+ return processRichText(block.bulleted_list_item.rich_text, "li", "mb-2");
69
+
70
+ case "numbered_list_item":
71
+ return processRichText(block.numbered_list_item.rich_text, "li", "mb-2");
72
+
73
+ case "quote":
74
+ return processRichText(block.quote.rich_text, "blockquote", "border-l-4 border-gray-300 pl-4 italic text-gray-600 mb-4");
75
+
76
+ case "code":
77
+ const codeContent = block.code.rich_text.map(text => text.plain_text).join("");
78
+ const language = block.code.language || 'text';
79
+ return `<pre class="bg-gray-100 p-4 rounded-lg overflow-x-auto mb-4"><code class="language-${language}">${escapeHtml(codeContent)}</code></pre>`;
80
+
81
+ case "image":
82
+ return processImage(block.image);
83
+
84
+ case "divider":
85
+ return '<hr class="my-8 border-gray-300" />';
86
+
87
+ case "callout":
88
+ return processCallout(block.callout);
89
+
90
+ case "toggle":
91
+ return processToggle(block.toggle);
92
+
93
+ case "table_of_contents":
94
+ return '<div class="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-4"><p class="text-blue-800 font-medium">📋 Table of Contents</p><p class="text-blue-600 text-sm">(Generated automatically)</p></div>';
95
+
96
+ case "bookmark":
97
+ return processBookmark(block.bookmark);
98
+
99
+ case "equation":
100
+ return `<div class="bg-gray-50 p-4 rounded-lg mb-4 text-center"><p class="text-gray-600">📐 Mathematical equation</p><p class="font-mono text-sm">${block.equation.expression}</p></div>`;
101
+
102
+ default:
103
+ // For unsupported blocks, try to extract plain text
104
+ if (block[block.type]?.rich_text) {
105
+ return processRichText(block[block.type].rich_text, "p", "mb-4 text-gray-500");
106
+ }
107
+ return "";
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Process image block with size, alignment, and alt text
113
+ * @param {Object} image - Notion image block
114
+ * @returns {string} HTML string
115
+ */
116
+ function processImage(image) {
117
+ const imageUrl = image.type === 'external' ? image.external.url : image.file.url;
118
+ const caption = image.caption?.map(text => text.plain_text).join("") || "";
119
+ const altText = caption || "Image";
120
+
121
+ // Get image size and alignment from Notion properties
122
+ let sizeClass = "w-full"; // Default full width
123
+ let alignmentClass = "text-center"; // Default center alignment
124
+
125
+ // You can extend this based on Notion's image properties
126
+ // For now, we'll use responsive sizing
127
+ const responsiveClasses = "max-w-full h-auto rounded-lg shadow-sm";
128
+
129
+ return `
130
+ <figure class="my-8 ${alignmentClass}">
131
+ <img
132
+ src="${imageUrl}"
133
+ alt="${escapeHtml(altText)}"
134
+ class="${sizeClass} ${responsiveClasses}"
135
+ loading="lazy"
136
+ />
137
+ ${caption ? `<figcaption class="text-center text-gray-600 mt-2 text-sm">${escapeHtml(caption)}</figcaption>` : ''}
138
+ </figure>
139
+ `.trim();
140
+ }
141
+
142
+ /**
143
+ * Process callout block
144
+ * @param {Object} callout - Notion callout block
145
+ * @returns {string} HTML string
146
+ */
147
+ function processCallout(callout) {
148
+ const content = processRichText(callout.rich_text, "div", "");
149
+ const icon = callout.icon?.emoji || "💡";
150
+ const bgColor = callout.color || "blue";
151
+
152
+ const colorClasses = {
153
+ blue: "bg-blue-50 border-blue-200 text-blue-800",
154
+ gray: "bg-gray-50 border-gray-200 text-gray-800",
155
+ yellow: "bg-yellow-50 border-yellow-200 text-yellow-800",
156
+ red: "bg-red-50 border-red-200 text-red-800",
157
+ green: "bg-green-50 border-green-200 text-green-800",
158
+ purple: "bg-purple-50 border-purple-200 text-purple-800",
159
+ pink: "bg-pink-50 border-pink-200 text-pink-800"
160
+ };
161
+
162
+ const colorClass = colorClasses[bgColor] || colorClasses.blue;
163
+
164
+ return `
165
+ <div class="${colorClass} border-l-4 p-4 my-4 rounded-r-lg">
166
+ <div class="flex items-start">
167
+ <span class="mr-3 text-lg">${icon}</span>
168
+ <div class="flex-1">${content}</div>
169
+ </div>
170
+ </div>
171
+ `.trim();
67
172
  }
68
- // const slugify = (title) =>
69
- // title
70
- // .toLowerCase()
71
- // .replace(/[^a-z0-9]+/g, "-")
72
- // .replace(/(^-|-$)/g, "");
73
173
 
74
- // for (const page of response.results) {
75
- // const titleProperty = page.properties["Name"];
76
- // const title = titleProperty?.title?.[0]?.plain_text;
174
+ /**
175
+ * Process toggle block
176
+ * @param {Object} toggle - Notion toggle block
177
+ * @returns {string} HTML string
178
+ */
179
+ function processToggle(toggle) {
180
+ const content = processRichText(toggle.rich_text, "div", "");
181
+ return `
182
+ <details class="my-4">
183
+ <summary class="cursor-pointer font-medium text-gray-700 hover:text-gray-900">
184
+ ${content}
185
+ </summary>
186
+ <div class="mt-2 pl-4 border-l-2 border-gray-200">
187
+ <!-- Toggle content would go here if Notion API provided it -->
188
+ <p class="text-gray-600 text-sm">Toggle content not available in current API</p>
189
+ </div>
190
+ </details>
191
+ `.trim();
192
+ }
193
+
194
+ /**
195
+ * Process bookmark block
196
+ * @param {Object} bookmark - Notion bookmark block
197
+ * @returns {string} HTML string
198
+ */
199
+ function processBookmark(bookmark) {
200
+ const url = bookmark.url;
201
+ const title = bookmark.caption?.[0]?.plain_text || "Bookmark";
202
+
203
+ return `
204
+ <div class="my-4">
205
+ <a href="${url}" target="_blank" rel="noopener noreferrer" class="block border border-gray-200 rounded-lg p-4 hover:border-gray-300 transition-colors">
206
+ <div class="flex items-center">
207
+ <div class="flex-1">
208
+ <p class="font-medium text-gray-900">${escapeHtml(title)}</p>
209
+ <p class="text-sm text-gray-500 truncate">${url}</p>
210
+ </div>
211
+ <svg class="w-5 h-5 text-gray-400 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
212
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"></path>
213
+ </svg>
214
+ </div>
215
+ </a>
216
+ </div>
217
+ `.trim();
218
+ }
77
219
 
78
- // const pageSlug = slugify(title);
220
+ /**
221
+ * Process rich text and apply formatting
222
+ * @param {Array} richText - Array of rich text objects
223
+ * @param {string} tag - HTML tag to wrap content
224
+ * @param {string} className - CSS classes
225
+ * @returns {string} HTML string
226
+ */
227
+ function processRichText(richText, tag, className) {
228
+ if (!richText || richText.length === 0) return "";
229
+
230
+ const content = richText.map(text => {
231
+ let result = text.plain_text;
232
+
233
+ // Apply annotations
234
+ if (text.annotations.bold) result = `<strong>${result}</strong>`;
235
+ if (text.annotations.italic) result = `<em>${result}</em>`;
236
+ if (text.annotations.strikethrough) result = `<del>${result}</del>`;
237
+ if (text.annotations.code) result = `<code class="bg-gray-100 px-1 py-0.5 rounded text-sm font-mono">${result}</code>`;
238
+
239
+ // Apply links
240
+ if (text.href) result = `<a href="${text.href}" class="text-blue-600 hover:text-blue-800 underline" target="_blank" rel="noopener noreferrer">${result}</a>`;
241
+
242
+ return result;
243
+ }).join("");
244
+
245
+ return `<${tag} class="${className}">${content}</${tag}>`;
246
+ }
79
247
 
80
- // if (pageSlug === slug) {
81
- // // FOUND IT
82
- // return {
83
- // title,
84
- // slug: pageSlug,
85
- // notionPageId: page.id,
86
- // };
87
- // }
88
- // }
248
+ /**
249
+ * Escape HTML special characters
250
+ * @param {string} text - Text to escape
251
+ * @returns {string} Escaped text
252
+ */
253
+ function escapeHtml(text) {
254
+ const map = {
255
+ '&': '&amp;',
256
+ '<': '&lt;',
257
+ '>': '&gt;',
258
+ '"': '&quot;',
259
+ "'": '&#039;'
260
+ };
261
+ return text.replace(/[&<>"']/g, m => map[m]);
262
+ }
89
263
 
90
- // throw new Error(`No post found with slug "${slug}"`);
264
+ module.exports = {
265
+ getPostBySlug
266
+ }