noblog 0.2.3 → 0.3.0-beta.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.
Files changed (43) hide show
  1. package/README.md +277 -121
  2. package/dist/client-CkOIgSvW.js +549 -0
  3. package/dist/client-CkOIgSvW.js.map +1 -0
  4. package/dist/config-check.impl-BQvIrgNe.js +14 -0
  5. package/dist/config-check.impl-BQvIrgNe.js.map +1 -0
  6. package/dist/config-csbquMhF.js +21 -0
  7. package/dist/config-csbquMhF.js.map +1 -0
  8. package/dist/config-file-BF0G2iJq.js +27 -0
  9. package/dist/config-file-BF0G2iJq.js.map +1 -0
  10. package/dist/defaults-D9BbNYsd.js +8 -0
  11. package/dist/defaults-D9BbNYsd.js.map +1 -0
  12. package/dist/errors-DgJ15MsB.js +72 -0
  13. package/dist/errors-DgJ15MsB.js.map +1 -0
  14. package/dist/frontmatter-fields-BkXq38CO.js +71 -0
  15. package/dist/frontmatter-fields-BkXq38CO.js.map +1 -0
  16. package/dist/generate.d.ts +1 -0
  17. package/dist/generate.impl-BKu-Ix4a.js +33 -0
  18. package/dist/generate.impl-BKu-Ix4a.js.map +1 -0
  19. package/dist/generate.js +197 -940
  20. package/dist/generate.js.map +1 -1
  21. package/dist/index.d.ts +209 -18
  22. package/dist/index.js +5 -0
  23. package/package.json +32 -17
  24. package/schema/config.json +173 -0
  25. package/skills/noblog-config/SKILL.md +40 -0
  26. package/skills/noblog-config/assets/nobloger.yaml +30 -0
  27. package/skills/noblog-config/assets/thought-lite.yaml +47 -0
  28. package/skills/noblog-config/references/config-schema.md +108 -0
  29. package/skills/noblog-config/references/notion-property-types.md +42 -0
  30. package/skills/noblog-config/references/theme-examples.md +39 -0
  31. package/dist/cmds/generate.d.ts +0 -3
  32. package/dist/cmds/generate.d.ts.map +0 -1
  33. package/dist/generate.cjs +0 -970
  34. package/dist/generate.cjs.map +0 -1
  35. package/dist/index.d.ts.map +0 -1
  36. package/dist/lib/noblog/format.d.ts +0 -24
  37. package/dist/lib/noblog/format.d.ts.map +0 -1
  38. package/dist/lib/noblog/noblog.d.ts +0 -31
  39. package/dist/lib/noblog/noblog.d.ts.map +0 -1
  40. package/dist/lib/noblog/utils.d.ts +0 -36
  41. package/dist/lib/noblog/utils.d.ts.map +0 -1
  42. package/dist/lib/notion/client.d.ts +0 -8
  43. package/dist/lib/notion/client.d.ts.map +0 -1
@@ -0,0 +1,549 @@
1
+ import { t as getErrorCode } from "./errors-DgJ15MsB.js";
2
+ import { t as DEFAULT_LAYOUT_PATH } from "./defaults-D9BbNYsd.js";
3
+ import { stringify } from "yaml";
4
+ import { markdownTable } from "markdown-table";
5
+ import { randomUUID } from "node:crypto";
6
+ import { lstat, mkdir, readlink, rename, rm, symlink, writeFile } from "node:fs/promises";
7
+ import { dirname, join, relative, resolve, sep } from "node:path";
8
+ import { Client, isFullBlock, isFullDatabase, isFullPage, isNotionClientError } from "@notionhq/client";
9
+ //#region src/lib/noblog/metadata.ts
10
+ const DEFAULT_PROPERTIES = {
11
+ description: "description",
12
+ tags: "tags",
13
+ publishedAt: "date",
14
+ archived: "archived"
15
+ };
16
+ function extractPageMetadata(page, properties = {}) {
17
+ const propertyNames = new Map(Object.entries(properties));
18
+ for (const [key, name] of Object.entries(DEFAULT_PROPERTIES)) if (!propertyNames.has(key)) propertyNames.set(key, name);
19
+ const values = {};
20
+ if (!propertyNames.has("title")) values["title"] = normalizeProperty(Object.values(page.properties).findLast((property) => property.type === "title"));
21
+ for (const [key, name] of propertyNames) values[key] = normalizeProperty(page.properties[name]);
22
+ return {
23
+ title: asString(values["title"]) ?? "",
24
+ description: asString(values["description"]),
25
+ tags: asStringArray(values["tags"]) ?? [],
26
+ publishedAt: asString(values["publishedAt"]),
27
+ archived: asBoolean(values["archived"]),
28
+ values
29
+ };
30
+ }
31
+ function normalizeProperty(property) {
32
+ if (!property) return void 0;
33
+ switch (property.type) {
34
+ case "title": return plainText(property.title);
35
+ case "rich_text": return plainText(property.rich_text);
36
+ case "multi_select": return property.multi_select.map(({ name }) => name);
37
+ case "select": return property.select?.name;
38
+ case "status": return property.status?.name;
39
+ case "checkbox": return property.checkbox;
40
+ case "number": return property.number ?? void 0;
41
+ case "date": return property.date?.start;
42
+ case "url": return property.url ?? void 0;
43
+ case "email": return property.email ?? void 0;
44
+ case "phone_number": return property.phone_number ?? void 0;
45
+ case "people": return property.people.map(entityName);
46
+ case "relation": return property.relation.map(({ id }) => id);
47
+ case "files": return property.files.map((file) => "file" in file ? file.file.url : file.external.url);
48
+ case "formula": return normalizeFormula(property.formula);
49
+ case "created_time": return property.created_time;
50
+ case "last_edited_time": return property.last_edited_time;
51
+ case "created_by": return entityName(property.created_by);
52
+ case "last_edited_by": return entityName(property.last_edited_by);
53
+ case "unique_id": return property.unique_id.prefix ? `${property.unique_id.prefix}-${property.unique_id.number}` : String(property.unique_id.number);
54
+ default: return;
55
+ }
56
+ }
57
+ function normalizeFormula(formula) {
58
+ switch (formula.type) {
59
+ case "string": return formula.string ?? void 0;
60
+ case "number": return formula.number ?? void 0;
61
+ case "boolean": return formula.boolean ?? void 0;
62
+ case "date": return formula.date?.start;
63
+ }
64
+ }
65
+ function plainText(richText) {
66
+ return richText.map((item) => item.plain_text).join("");
67
+ }
68
+ function entityName(entity) {
69
+ return entity.name ?? entity.id;
70
+ }
71
+ function asString(value) {
72
+ return typeof value === "string" ? value : void 0;
73
+ }
74
+ function asBoolean(value) {
75
+ return typeof value === "boolean" ? value : void 0;
76
+ }
77
+ function asStringArray(value) {
78
+ if (typeof value === "string") return [value];
79
+ return Array.isArray(value) ? value : void 0;
80
+ }
81
+ //#endregion
82
+ //#region src/lib/noblog/frontmatter.ts
83
+ function renderFrontmatter(page, options = {}) {
84
+ const { layoutPath = DEFAULT_LAYOUT_PATH } = options;
85
+ if (options.frontmatter) {
86
+ const metadata = extractPageMetadata(page, options.propertyMap);
87
+ return serializeFrontmatter(options.frontmatter({
88
+ page,
89
+ metadata,
90
+ layoutPath,
91
+ isDatabasePage: options.isDatabasePage ?? true
92
+ }));
93
+ }
94
+ let title = "";
95
+ let description = "";
96
+ let tags = [];
97
+ let pubDate = "";
98
+ let archived = "";
99
+ for (const [name, property] of Object.entries(page.properties)) {
100
+ switch (property.type) {
101
+ case "title":
102
+ title = property.title.map((item) => item.plain_text).join("");
103
+ break;
104
+ case "multi_select":
105
+ if (name === "tags") tags = property.multi_select.map((tag) => tag.name);
106
+ break;
107
+ case "date":
108
+ if (name === "date") pubDate = property.date?.start ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
109
+ break;
110
+ }
111
+ if (name === "archived" && property.type === "checkbox") archived = property.checkbox ? "true" : "false";
112
+ if (name === "description" && property.type === "rich_text") description = property.rich_text.map((item) => item.plain_text).join("");
113
+ }
114
+ return `---
115
+ layout: ${layoutPath}\ntitle: ${JSON.stringify(title)}\ndescription: ${JSON.stringify(description)}\ntags: ${JSON.stringify(tags)}\npubDate: ${JSON.stringify(pubDate)}\narchived: ${JSON.stringify(archived)}\n---
116
+ `;
117
+ }
118
+ function serializeFrontmatter(values) {
119
+ if (values === null || typeof values !== "object" || Array.isArray(values) || Object.getPrototypeOf(values) !== Object.prototype && Object.getPrototypeOf(values) !== null) throw new Error("Frontmatter adapter must return a plain object");
120
+ const entries = Object.entries(values).filter(([, value]) => value !== void 0);
121
+ if (entries.length === 0) return "---\n---\n";
122
+ return `---\n${stringify(Object.fromEntries(entries), {
123
+ aliasDuplicateObjects: false,
124
+ schema: "yaml-1.1"
125
+ })}---\n`;
126
+ }
127
+ //#endregion
128
+ //#region src/lib/noblog/markdown.ts
129
+ const inlineCode = (text) => `\`${text}\``;
130
+ const inlineEquation = (text) => `$${text}$`;
131
+ const bold = (text) => `**${text}**`;
132
+ const italic = (text) => `_${text}_`;
133
+ const strikethrough = (text) => `~~${text}~~`;
134
+ const underline = (text) => `<u>${text}</u>`;
135
+ const link = (text, href) => `[${text}](${href})`;
136
+ function codeBlock(text, language = "") {
137
+ return `\`\`\`${language === "plain text" ? "text" : language}\n${text}\n\`\`\``;
138
+ }
139
+ const equation = (text) => `$$\n${text}\n$$`;
140
+ const heading1 = (text) => `# ${text}`;
141
+ const heading2 = (text) => `## ${text}`;
142
+ const heading3 = (text) => `### ${text}`;
143
+ const quote = (text) => `> ${text.replace(/\n/g, " \n> ")}`;
144
+ function callout(text, icon) {
145
+ return `> ${icon?.type === "emoji" ? `${icon.emoji} ` : ""}${text.replace(/\n/g, " \n> ")}`;
146
+ }
147
+ function bullet(text, count) {
148
+ const rendered = text.trim();
149
+ return count === void 0 ? `- ${rendered}` : `${count}. ${rendered}`;
150
+ }
151
+ const todo = (text, checked) => checked ? `- [x] ${text}` : `- [ ] ${text}`;
152
+ async function image(alt, href, convertToBase64 = false) {
153
+ if (!convertToBase64 || href.startsWith("data:")) {
154
+ if (href.startsWith("data:")) return `![${alt}](data:image/png;base64,${href.split(",").pop()})`;
155
+ return `![${alt}](${href})`;
156
+ }
157
+ const response = await fetch(href);
158
+ if (!response.ok) throw new Error(`Failed to download image: ${response.status} ${response.statusText}`);
159
+ const buffer = await response.arrayBuffer();
160
+ return `![${alt}](data:image/png;base64,${Buffer.from(buffer).toString("base64")})`;
161
+ }
162
+ const divider = () => "---";
163
+ function toggle(summary, children) {
164
+ if (!summary) return children ?? "";
165
+ return `<details>\n<summary>${summary}</summary>\n${children ?? ""}\n</details>\n\n`;
166
+ }
167
+ function table(cells, hasColumnHeader = true) {
168
+ if (hasColumnHeader || cells.length === 0) return markdownTable(cells);
169
+ const width = Math.max(...cells.map((row) => row.length));
170
+ if (width === 0) return "";
171
+ return markdownTable([Array.from({ length: width }, () => ""), ...cells]);
172
+ }
173
+ //#endregion
174
+ //#region src/lib/noblog/rich-text.ts
175
+ const ANNOTATION_FORMATS = [
176
+ {
177
+ name: "strikethrough",
178
+ open: "~~",
179
+ close: "~~"
180
+ },
181
+ {
182
+ name: "underline",
183
+ open: "<u>",
184
+ close: "</u>"
185
+ },
186
+ {
187
+ name: "italic",
188
+ open: "_",
189
+ close: "_"
190
+ },
191
+ {
192
+ name: "bold",
193
+ open: "**",
194
+ close: "**"
195
+ },
196
+ {
197
+ name: "code",
198
+ open: "`",
199
+ close: "`"
200
+ }
201
+ ];
202
+ function renderPlainText(richText) {
203
+ return richText.map((item) => item.plain_text).join("");
204
+ }
205
+ function renderRichText(richText) {
206
+ let rendered = "";
207
+ let active = [];
208
+ for (const [index, item] of richText.entries()) {
209
+ const desired = getAnnotationFormats(richText, index, item);
210
+ let shared = 0;
211
+ while (shared < active.length && shared < desired.length && active[shared]?.name === desired[shared]?.name) shared++;
212
+ for (const format of active.slice(shared).toReversed()) rendered += format.close;
213
+ for (const format of desired.slice(shared)) rendered += format.open;
214
+ rendered += renderContent(item);
215
+ active = desired;
216
+ }
217
+ for (const format of active.toReversed()) rendered += format.close;
218
+ return rendered;
219
+ }
220
+ function getAnnotationFormats(richText, index, item) {
221
+ if (item.type === "equation") return [];
222
+ return ANNOTATION_FORMATS.filter((format) => item.annotations[format.name]).map((format) => ({
223
+ format,
224
+ runEnd: getAnnotationRunEnd(richText, index, format.name)
225
+ })).sort((left, right) => right.runEnd - left.runEnd).map(({ format }) => format);
226
+ }
227
+ function getAnnotationRunEnd(richText, start, name) {
228
+ let end = start;
229
+ for (const [offset, next] of richText.slice(start + 1).entries()) {
230
+ if (next.type === "equation" || !next.annotations[name]) break;
231
+ end = start + offset + 1;
232
+ }
233
+ return end;
234
+ }
235
+ function renderContent(item) {
236
+ if (item.type === "equation") return inlineEquation(item.equation.expression);
237
+ return item.href ? link(item.plain_text, item.href) : item.plain_text;
238
+ }
239
+ //#endregion
240
+ //#region src/lib/noblog/blocks.ts
241
+ async function loadBlockTree(blockId, reader, ancestors = /* @__PURE__ */ new Set()) {
242
+ if (ancestors.has(blockId)) return [];
243
+ const blocks = await reader.getBlockChildren(blockId);
244
+ const childAncestors = new Set(ancestors).add(blockId);
245
+ const nodes = [];
246
+ for (const block of blocks) {
247
+ if (block.type === "unsupported") continue;
248
+ const childSourceId = getChildSourceId(block);
249
+ const children = childSourceId && !childAncestors.has(childSourceId) ? await loadBlockTree(childSourceId, reader, childAncestors) : [];
250
+ nodes.push({
251
+ block,
252
+ children
253
+ });
254
+ }
255
+ return nodes;
256
+ }
257
+ function getChildSourceId(block) {
258
+ if (block.type === "child_page" || block.type === "child_database") return;
259
+ if (block.type === "synced_block") return block.synced_block.synced_from?.block_id ?? (block.has_children ? block.id : void 0);
260
+ return block.has_children ? block.id : void 0;
261
+ }
262
+ async function renderBlockTree(nodes, context) {
263
+ const result = {
264
+ content: "",
265
+ childPageIds: []
266
+ };
267
+ let numberedListIndex = 0;
268
+ for (const node of nodes) {
269
+ const { block } = node;
270
+ numberedListIndex = block.type === "numbered_list_item" ? numberedListIndex + 1 : 0;
271
+ const children = block.type === "table" ? {
272
+ content: "",
273
+ childPageIds: []
274
+ } : await renderBlockTree(node.children, context);
275
+ const content = await renderBlockNode(node, children.content, numberedListIndex || void 0, context);
276
+ if (block.type === "toggle") result.content += content;
277
+ else {
278
+ const lineWrap = isListBlock(block) ? "" : "\n";
279
+ result.content += `${lineWrap}${content}\n${lineWrap}`;
280
+ if (!containsOwnChildren(block)) result.content += children.content;
281
+ }
282
+ if (block.type === "child_page") result.childPageIds.push(block.id);
283
+ else result.childPageIds.push(...children.childPageIds);
284
+ }
285
+ return result;
286
+ }
287
+ function isListBlock(block) {
288
+ return block.type === "to_do" || block.type === "bulleted_list_item" || block.type === "numbered_list_item";
289
+ }
290
+ function containsOwnChildren(block) {
291
+ return block.type === "child_page" || block.type === "callout" || block.type === "bulleted_list_item" || block.type === "numbered_list_item" || block.type === "table";
292
+ }
293
+ function transformBlock(type, block, context) {
294
+ const transformer = context.transformers?.[type];
295
+ return transformer?.(block, context);
296
+ }
297
+ async function renderBlockNode(node, children, listNumber, context) {
298
+ const { block } = node;
299
+ const content = await transformBlock(block.type, block, context) ?? await renderDefaultBlock(node);
300
+ switch (block.type) {
301
+ case "toggle": return toggle(content, children);
302
+ case "callout": return callout(children ? `${content}\n${children}`.trim() : content, block.callout.icon);
303
+ case "bulleted_list_item": return bullet(withIndentedChildren(content, children, 2));
304
+ case "numbered_list_item": return bullet(withIndentedChildren(content, children, `${listNumber ?? 1}. `.length), listNumber);
305
+ default: return content;
306
+ }
307
+ }
308
+ function withIndentedChildren(content, children, markerWidth) {
309
+ const nested = children.trim();
310
+ if (!nested) return content;
311
+ const indentation = " ".repeat(markerWidth);
312
+ return `${content}\n${nested.split("\n").map((line) => line ? `${indentation}${line}` : "").join("\n")}`;
313
+ }
314
+ async function renderDefaultBlock(node) {
315
+ const { block } = node;
316
+ switch (block.type) {
317
+ case "image": {
318
+ const url = getFileUrl(block.image);
319
+ return image(renderPlainText(block.image.caption).trim() || getFileName(url) || "image", url);
320
+ }
321
+ case "divider": return divider();
322
+ case "equation": return equation(block.equation.expression);
323
+ case "pdf": return renderFileLink(block.pdf, block.type);
324
+ case "file": return renderFileLink(block.file, block.type);
325
+ case "video": return renderFileLink(block.video, block.type);
326
+ case "audio": return renderFileLink(block.audio, block.type);
327
+ case "embed": return link(block.type, block.embed.url);
328
+ case "bookmark": return link(block.type, block.bookmark.url);
329
+ case "link_preview": return link(block.type, block.link_preview.url);
330
+ case "link_to_page": {
331
+ const target = block.link_to_page;
332
+ if (target.type !== "page_id" && target.type !== "database_id") return "";
333
+ const id = target.type === "page_id" ? target.page_id : target.database_id;
334
+ return link(block.type, `https://www.notion.so/${id}`);
335
+ }
336
+ case "child_page": return renderChildPageLink(block);
337
+ case "child_database": return heading2(block.child_database.title || "child_database");
338
+ case "table": return renderTable(node.children, block.table.has_column_header);
339
+ case "code": return codeBlock(renderRichText(block.code.rich_text), block.code.language);
340
+ case "heading_1": return heading1(renderRichText(block.heading_1.rich_text));
341
+ case "heading_2": return heading2(renderRichText(block.heading_2.rich_text));
342
+ case "heading_3": return heading3(renderRichText(block.heading_3.rich_text));
343
+ case "quote": return quote(renderRichText(block.quote.rich_text));
344
+ case "to_do": return todo(renderRichText(block.to_do.rich_text), block.to_do.checked);
345
+ case "paragraph": return renderRichText(block.paragraph.rich_text);
346
+ case "toggle": return renderRichText(block.toggle.rich_text);
347
+ case "callout": return renderRichText(block.callout.rich_text);
348
+ case "bulleted_list_item": return renderRichText(block.bulleted_list_item.rich_text);
349
+ case "numbered_list_item": return renderRichText(block.numbered_list_item.rich_text);
350
+ case "template": return renderRichText(block.template.rich_text);
351
+ default: return "";
352
+ }
353
+ }
354
+ function getFileUrl(content) {
355
+ if (content.type === "external") return content.external.url;
356
+ return content.file.url;
357
+ }
358
+ function getFileName(url) {
359
+ return url.match(/[^\/\\&?]+\.\w{3,4}(?=([?&].*$|$))/)?.[0];
360
+ }
361
+ function renderFileLink(content, fallbackTitle) {
362
+ const url = getFileUrl(content);
363
+ const title = renderPlainText(content.caption).trim() || getFileName(url) || fallbackTitle;
364
+ return link(title, url);
365
+ }
366
+ function renderChildPageLink(block) {
367
+ return `(」゚ロ゚)」 ${link(block.child_page.title, `./${block.id}.md`)}`;
368
+ }
369
+ function renderTable(nodes, hasColumnHeader) {
370
+ return table(nodes.flatMap(({ block }) => {
371
+ if (block.type !== "table_row") return [];
372
+ return [block.table_row.cells.map((cell) => renderRichText(cell))];
373
+ }), hasColumnHeader);
374
+ }
375
+ //#endregion
376
+ //#region src/lib/noblog/pages.ts
377
+ async function renderPage(pageId, reader, options = {}) {
378
+ return collectPage(pageId, createContext([pageId], reader, options));
379
+ }
380
+ async function renderPages(posts, reader, options = {}) {
381
+ const context = createContext(posts.map((post) => post.id), reader, options);
382
+ for (const post of posts) await collectPage(post.id, context, post);
383
+ return Promise.all([...context.renderedPages.values()]);
384
+ }
385
+ function createContext(databasePageIds, reader, options) {
386
+ return {
387
+ reader,
388
+ databasePageIds: new Set(databasePageIds),
389
+ layoutPath: options.layoutPath ?? "../../layouts/MarkdownPostLayout.astro",
390
+ propertyMap: options.propertyMap,
391
+ frontmatter: options.frontmatter,
392
+ recursive: options.recursive ?? true,
393
+ transformers: options.transformers,
394
+ renderedPages: /* @__PURE__ */ new Map()
395
+ };
396
+ }
397
+ async function collectPage(pageId, context, post) {
398
+ const cached = context.renderedPages.get(pageId);
399
+ if (cached) return cached;
400
+ const rendering = renderSinglePage(pageId, context, post);
401
+ context.renderedPages.set(pageId, rendering);
402
+ const rendered = await rendering;
403
+ if (context.recursive) for (const childPageId of rendered.childPageIds) await collectPage(childPageId, context);
404
+ return rendered;
405
+ }
406
+ async function renderSinglePage(pageId, context, post) {
407
+ const [page, blockTree] = await Promise.all([post ? Promise.resolve(post) : context.reader.getPage(pageId), loadBlockTree(pageId, context.reader)]);
408
+ const rendered = await renderBlockTree(blockTree, {
409
+ pageId,
410
+ ...context.transformers !== void 0 ? { transformers: context.transformers } : {}
411
+ });
412
+ return {
413
+ id: pageId,
414
+ content: renderFrontmatter(page, {
415
+ layoutPath: context.layoutPath,
416
+ ...context.propertyMap !== void 0 ? { propertyMap: context.propertyMap } : {},
417
+ ...context.frontmatter !== void 0 ? { frontmatter: context.frontmatter } : {},
418
+ isDatabasePage: context.databasePageIds.has(pageId)
419
+ }) + rendered.content,
420
+ childPageIds: [...new Set(rendered.childPageIds)],
421
+ isDatabasePage: context.databasePageIds.has(pageId)
422
+ };
423
+ }
424
+ //#endregion
425
+ //#region src/lib/noblog/writer.ts
426
+ async function writePages(pages, options = {}) {
427
+ const savePath = options.savePath ?? "./src/pages/posts/";
428
+ const storePath = options.storePath ?? ".noblog/pages/";
429
+ const writtenPaths = [];
430
+ await mkdir(storePath, { recursive: true });
431
+ for (const page of pages) {
432
+ const storedPath = join(storePath, `${page.id}.md`);
433
+ const storedStats = await getStats(storedPath);
434
+ if (storedStats && !storedStats.isFile()) throw new Error(`Page store path is not a regular file: ${storedPath}`);
435
+ await writeFile(storedPath, page.content, "utf8");
436
+ if (!page.isDatabasePage) continue;
437
+ const viewPath = join(savePath, `${page.id}.md`);
438
+ await mkdir(savePath, { recursive: true });
439
+ await linkStoredPage(storedPath, viewPath);
440
+ writtenPaths.push(viewPath);
441
+ }
442
+ return writtenPaths;
443
+ }
444
+ function resolveStoreLayoutPath(options = {}) {
445
+ const savePath = options.savePath ?? "./src/pages/posts/";
446
+ const storePath = options.storePath ?? ".noblog/pages/";
447
+ const layoutTarget = resolve(savePath, options.layoutPath ?? "../../layouts/MarkdownPostLayout.astro");
448
+ return relative(resolve(storePath), layoutTarget).split(sep).join("/");
449
+ }
450
+ async function linkStoredPage(storedPath, viewPath) {
451
+ if (resolve(storedPath) === resolve(viewPath)) return;
452
+ const viewStats = await getStats(viewPath);
453
+ if (viewStats && !viewStats.isFile() && !viewStats.isSymbolicLink()) throw new Error(`Refusing to replace non-file output: ${viewPath}`);
454
+ const target = relative(dirname(viewPath), storedPath);
455
+ if (viewStats?.isSymbolicLink() && await readlink(viewPath) === target) return;
456
+ const temporaryPath = `${viewPath}.noblog-${randomUUID()}.tmp`;
457
+ try {
458
+ await symlink(target, temporaryPath, "file");
459
+ await rename(temporaryPath, viewPath);
460
+ } catch (error) {
461
+ await rm(temporaryPath, { force: true }).catch(() => {});
462
+ throw error;
463
+ }
464
+ }
465
+ async function getStats(path) {
466
+ try {
467
+ return await lstat(path);
468
+ } catch (error) {
469
+ if (getErrorCode(error) === "ENOENT") return void 0;
470
+ throw error;
471
+ }
472
+ }
473
+ //#endregion
474
+ //#region src/lib/notion/client.ts
475
+ function createNotionSource(config) {
476
+ const client = new Client({ auth: config.notionApiSecret });
477
+ return {
478
+ listPages: () => listPages(client, config.databaseId),
479
+ getPage: (pageId) => getPage(client, pageId),
480
+ getBlockChildren: (blockId) => getBlockChildren(client, blockId)
481
+ };
482
+ }
483
+ async function listPages(client, databaseId) {
484
+ const database = await withRetry(() => client.databases.retrieve({ database_id: databaseId }));
485
+ if (!isFullDatabase(database)) throw new Error(`Notion database ${databaseId} is not a full database`);
486
+ const pages = [];
487
+ for (const source of database.data_sources) {
488
+ let startCursor;
489
+ do {
490
+ const response = await withRetry(() => client.dataSources.query({
491
+ data_source_id: source.id,
492
+ filter: {
493
+ property: "publish",
494
+ checkbox: { equals: true }
495
+ },
496
+ sorts: [{
497
+ property: "date",
498
+ direction: "descending"
499
+ }],
500
+ page_size: 100,
501
+ ...startCursor !== void 0 ? { start_cursor: startCursor } : {}
502
+ }));
503
+ pages.push(...response.results.filter(isFullPage));
504
+ startCursor = response.next_cursor ?? void 0;
505
+ if (!response.has_more) break;
506
+ } while (startCursor);
507
+ }
508
+ return pages;
509
+ }
510
+ async function getPage(client, pageId) {
511
+ const page = await withRetry(() => client.pages.retrieve({ page_id: pageId }));
512
+ if (!isFullPage(page)) throw new Error(`Notion page ${pageId} is not a full page`);
513
+ return page;
514
+ }
515
+ async function getBlockChildren(client, blockId) {
516
+ const blocks = [];
517
+ let startCursor;
518
+ do {
519
+ const response = await withRetry(() => client.blocks.children.list({
520
+ block_id: blockId,
521
+ page_size: 100,
522
+ ...startCursor !== void 0 ? { start_cursor: startCursor } : {}
523
+ }));
524
+ blocks.push(...response.results.filter(isFullBlock));
525
+ startCursor = response.next_cursor ?? void 0;
526
+ } while (startCursor);
527
+ return blocks;
528
+ }
529
+ const MAX_ATTEMPTS = 3;
530
+ const INITIAL_RETRY_DELAY_MS = 500;
531
+ async function withRetry(operation) {
532
+ for (let attempt = 0;; attempt++) try {
533
+ return await operation();
534
+ } catch (error) {
535
+ if (attempt === MAX_ATTEMPTS - 1 || !isRetryable(error)) throw error;
536
+ await delay(INITIAL_RETRY_DELAY_MS * 2 ** attempt);
537
+ }
538
+ }
539
+ function isRetryable(error) {
540
+ if (!isNotionClientError(error) || !("status" in error)) return true;
541
+ return error.status === 408 || error.status === 429 || error.status >= 500;
542
+ }
543
+ function delay(milliseconds) {
544
+ return new Promise((resolve) => setTimeout(resolve, milliseconds));
545
+ }
546
+ //#endregion
547
+ export { strikethrough as C, underline as D, toggle as E, renderFrontmatter as O, quote as S, todo as T, image as _, renderPages as a, italic as b, bold as c, codeBlock as d, divider as f, heading3 as g, heading2 as h, renderPage as i, extractPageMetadata as k, bullet as l, heading1 as m, resolveStoreLayoutPath as n, renderPlainText as o, equation as p, writePages as r, renderRichText as s, createNotionSource as t, callout as u, inlineCode as v, table as w, link as x, inlineEquation as y };
548
+
549
+ //# sourceMappingURL=client-CkOIgSvW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-CkOIgSvW.js","names":["md.toggle","md.callout","md.bullet","md.image","md.divider","md.equation","md.link","md.heading2","md.codeBlock","md.heading1","md.heading3","md.quote","md.todo","md.table"],"sources":["../src/lib/noblog/metadata.ts","../src/lib/noblog/frontmatter.ts","../src/lib/noblog/markdown.ts","../src/lib/noblog/rich-text.ts","../src/lib/noblog/blocks.ts","../src/lib/noblog/pages.ts","../src/lib/noblog/writer.ts","../src/lib/notion/client.ts"],"sourcesContent":["import type { PageObjectResponse } from \"@notionhq/client\";\nimport type { NotionPropertyMap } from \"./config.js\";\n\ntype NotionProperty = PageObjectResponse[\"properties\"][string];\n\nexport type MetadataValue =\n | string\n | number\n | boolean\n | readonly string[]\n | undefined;\n\nexport interface PageMetadata {\n readonly title: string;\n readonly description?: string | undefined;\n readonly tags: readonly string[];\n readonly publishedAt?: string | undefined;\n readonly archived?: boolean | undefined;\n readonly values: Readonly<Record<string, MetadataValue>>;\n}\n\nconst DEFAULT_PROPERTIES = {\n description: \"description\",\n tags: \"tags\",\n publishedAt: \"date\",\n archived: \"archived\",\n} as const;\n\nexport function extractPageMetadata(\n page: PageObjectResponse,\n properties: NotionPropertyMap = {},\n): PageMetadata {\n const propertyNames = new Map<string, string>(Object.entries(properties));\n for (const [key, name] of Object.entries(DEFAULT_PROPERTIES)) {\n if (!propertyNames.has(key)) propertyNames.set(key, name);\n }\n\n const values: Record<string, MetadataValue> = {};\n if (!propertyNames.has(\"title\")) {\n const titleProperty = Object.values(page.properties).findLast(\n (property) => property.type === \"title\",\n );\n values[\"title\"] = normalizeProperty(titleProperty);\n }\n for (const [key, name] of propertyNames) {\n values[key] = normalizeProperty(page.properties[name]);\n }\n\n return {\n title: asString(values[\"title\"]) ?? \"\",\n description: asString(values[\"description\"]),\n tags: asStringArray(values[\"tags\"]) ?? [],\n publishedAt: asString(values[\"publishedAt\"]),\n archived: asBoolean(values[\"archived\"]),\n values,\n };\n}\n\nfunction normalizeProperty(property?: NotionProperty): MetadataValue {\n if (!property) return undefined;\n\n switch (property.type) {\n case \"title\":\n return plainText(property.title);\n case \"rich_text\":\n return plainText(property.rich_text);\n case \"multi_select\":\n return property.multi_select.map(({ name }) => name);\n case \"select\":\n return property.select?.name;\n case \"status\":\n return property.status?.name;\n case \"checkbox\":\n return property.checkbox;\n case \"number\":\n return property.number ?? undefined;\n case \"date\":\n return property.date?.start;\n case \"url\":\n return property.url ?? undefined;\n case \"email\":\n return property.email ?? undefined;\n case \"phone_number\":\n return property.phone_number ?? undefined;\n case \"people\":\n return property.people.map(entityName);\n case \"relation\":\n return property.relation.map(({ id }) => id);\n case \"files\":\n return property.files.map((file) =>\n \"file\" in file ? file.file.url : file.external.url,\n );\n case \"formula\":\n return normalizeFormula(property.formula);\n case \"created_time\":\n return property.created_time;\n case \"last_edited_time\":\n return property.last_edited_time;\n case \"created_by\":\n return entityName(property.created_by);\n case \"last_edited_by\":\n return entityName(property.last_edited_by);\n case \"unique_id\":\n return property.unique_id.prefix\n ? `${property.unique_id.prefix}-${property.unique_id.number}`\n : String(property.unique_id.number);\n default:\n return undefined;\n }\n}\n\nfunction normalizeFormula(\n formula: Extract<NotionProperty, { type: \"formula\" }>[\"formula\"],\n): MetadataValue {\n switch (formula.type) {\n case \"string\":\n return formula.string ?? undefined;\n case \"number\":\n return formula.number ?? undefined;\n case \"boolean\":\n return formula.boolean ?? undefined;\n case \"date\":\n return formula.date?.start;\n }\n}\n\nfunction plainText(\n richText: Extract<NotionProperty, { type: \"title\" }>[\"title\"],\n): string {\n return richText.map((item) => item.plain_text).join(\"\");\n}\n\nfunction entityName(entity: {\n readonly id: string;\n readonly name?: string | null;\n}): string {\n return entity.name ?? entity.id;\n}\n\nfunction asString(value: MetadataValue): string | undefined {\n return typeof value === \"string\" ? value : undefined;\n}\n\nfunction asBoolean(value: MetadataValue): boolean | undefined {\n return typeof value === \"boolean\" ? value : undefined;\n}\n\nfunction asStringArray(value: MetadataValue): readonly string[] | undefined {\n if (typeof value === \"string\") return [value];\n return Array.isArray(value) ? value : undefined;\n}\n","import type { PageObjectResponse } from \"@notionhq/client\";\nimport { stringify } from \"yaml\";\nimport type { NotionPropertyMap } from \"./config.js\";\nimport { DEFAULT_LAYOUT_PATH } from \"./defaults.js\";\nimport { extractPageMetadata } from \"./metadata.js\";\nimport type { FrontmatterAdapter, FrontmatterRecord } from \"./types.js\";\n\nexport interface FrontmatterOptions {\n readonly layoutPath?: string;\n readonly propertyMap?: NotionPropertyMap;\n readonly frontmatter?: FrontmatterAdapter;\n readonly isDatabasePage?: boolean;\n}\n\nexport function renderFrontmatter(\n page: PageObjectResponse,\n options: FrontmatterOptions = {},\n): string {\n const { layoutPath = DEFAULT_LAYOUT_PATH } = options;\n if (options.frontmatter) {\n const metadata = extractPageMetadata(page, options.propertyMap);\n const values = options.frontmatter({\n page,\n metadata,\n layoutPath,\n isDatabasePage: options.isDatabasePage ?? true,\n });\n return serializeFrontmatter(values);\n }\n\n let title = \"\";\n let description = \"\";\n let tags: string[] = [];\n let pubDate = \"\";\n let archived = \"\";\n\n for (const [name, property] of Object.entries(page.properties)) {\n switch (property.type) {\n case \"title\":\n title = property.title.map((item) => item.plain_text).join(\"\");\n break;\n case \"multi_select\":\n if (name === \"tags\") {\n tags = property.multi_select.map((tag) => tag.name);\n }\n break;\n case \"date\":\n if (name === \"date\") {\n pubDate =\n property.date?.start ?? new Date().toISOString().slice(0, 10);\n }\n break;\n }\n\n if (name === \"archived\" && property.type === \"checkbox\") {\n archived = property.checkbox ? \"true\" : \"false\";\n }\n if (name === \"description\" && property.type === \"rich_text\") {\n description = property.rich_text\n .map((item) => item.plain_text)\n .join(\"\");\n }\n }\n\n return (\n \"---\\n\" +\n `layout: ${layoutPath}\\n` +\n `title: ${JSON.stringify(title)}\\n` +\n `description: ${JSON.stringify(description)}\\n` +\n `tags: ${JSON.stringify(tags)}\\n` +\n `pubDate: ${JSON.stringify(pubDate)}\\n` +\n `archived: ${JSON.stringify(archived)}\\n` +\n \"---\\n\"\n );\n}\n\nfunction serializeFrontmatter(values: FrontmatterRecord): string {\n if (\n values === null ||\n typeof values !== \"object\" ||\n Array.isArray(values) ||\n (Object.getPrototypeOf(values) !== Object.prototype &&\n Object.getPrototypeOf(values) !== null)\n ) {\n throw new Error(\"Frontmatter adapter must return a plain object\");\n }\n\n const entries = Object.entries(values).filter(\n ([, value]) => value !== undefined,\n );\n if (entries.length === 0) return \"---\\n---\\n\";\n\n const yaml = stringify(Object.fromEntries(entries), {\n aliasDuplicateObjects: false,\n schema: \"yaml-1.1\",\n });\n return `---\\n${yaml}---\\n`;\n}\n","import { markdownTable } from \"markdown-table\";\nimport type { CalloutIcon } from \"./types.js\";\n\nexport const inlineCode = (text: string) => `\\`${text}\\``;\nexport const inlineEquation = (text: string) => `$${text}$`;\nexport const bold = (text: string) => `**${text}**`;\nexport const italic = (text: string) => `_${text}_`;\nexport const strikethrough = (text: string) => `~~${text}~~`;\nexport const underline = (text: string) => `<u>${text}</u>`;\nexport const link = (text: string, href: string) => `[${text}](${href})`;\n\nexport function codeBlock(text: string, language = \"\"): string {\n const normalizedLanguage = language === \"plain text\" ? \"text\" : language;\n return `\\`\\`\\`${normalizedLanguage}\\n${text}\\n\\`\\`\\``;\n}\n\nexport const equation = (text: string) => `$$\\n${text}\\n$$`;\nexport const heading1 = (text: string) => `# ${text}`;\nexport const heading2 = (text: string) => `## ${text}`;\nexport const heading3 = (text: string) => `### ${text}`;\nexport const quote = (text: string) => `> ${text.replace(/\\n/g, \" \\n> \")}`;\n\nexport function callout(text: string, icon?: CalloutIcon): string {\n const emoji = icon?.type === \"emoji\" ? `${icon.emoji} ` : \"\";\n return `> ${emoji}${text.replace(/\\n/g, \" \\n> \")}`;\n}\n\nexport function bullet(text: string, count?: number): string {\n const rendered = text.trim();\n return count === undefined ? `- ${rendered}` : `${count}. ${rendered}`;\n}\n\nexport const todo = (text: string, checked: boolean) =>\n checked ? `- [x] ${text}` : `- [ ] ${text}`;\n\nexport async function image(\n alt: string,\n href: string,\n convertToBase64 = false,\n): Promise<string> {\n if (!convertToBase64 || href.startsWith(\"data:\")) {\n if (href.startsWith(\"data:\")) {\n const base64 = href.split(\",\").pop();\n return `![${alt}](data:image/png;base64,${base64})`;\n }\n return `![${alt}](${href})`;\n }\n\n const response = await fetch(href);\n if (!response.ok) {\n throw new Error(\n `Failed to download image: ${response.status} ${response.statusText}`,\n );\n }\n const buffer = await response.arrayBuffer();\n return `![${alt}](data:image/png;base64,${Buffer.from(buffer).toString(\"base64\")})`;\n}\n\nexport const divider = () => \"---\";\n\nexport function toggle(summary?: string, children?: string): string {\n if (!summary) return children ?? \"\";\n return `<details>\\n<summary>${summary}</summary>\\n${children ?? \"\"}\\n</details>\\n\\n`;\n}\n\nexport function table(cells: string[][], hasColumnHeader = true): string {\n if (hasColumnHeader || cells.length === 0) return markdownTable(cells);\n\n const width = Math.max(...cells.map((row) => row.length));\n if (width === 0) return \"\";\n const emptyHeader = Array.from({ length: width }, () => \"\");\n return markdownTable([emptyHeader, ...cells]);\n}\n","import type { RichTextItemResponse } from \"@notionhq/client\";\nimport { inlineEquation, link } from \"./markdown.js\";\n\ntype AnnotationName = Exclude<\n keyof RichTextItemResponse[\"annotations\"],\n \"color\"\n>;\n\ninterface AnnotationFormat {\n readonly name: AnnotationName;\n readonly open: string;\n readonly close: string;\n}\n\n// Ordered from the outermost marker to the innermost marker. Stable sorting\n// keeps this order when annotations end on the same rich-text segment.\nconst ANNOTATION_FORMATS: readonly AnnotationFormat[] = [\n { name: \"strikethrough\", open: \"~~\", close: \"~~\" },\n { name: \"underline\", open: \"<u>\", close: \"</u>\" },\n { name: \"italic\", open: \"_\", close: \"_\" },\n { name: \"bold\", open: \"**\", close: \"**\" },\n { name: \"code\", open: \"`\", close: \"`\" },\n];\n\nexport function renderPlainText(\n richText: readonly RichTextItemResponse[],\n): string {\n return richText.map((item) => item.plain_text).join(\"\");\n}\n\nexport function renderRichText(\n richText: readonly RichTextItemResponse[],\n): string {\n let rendered = \"\";\n let active: AnnotationFormat[] = [];\n\n for (const [index, item] of richText.entries()) {\n const desired = getAnnotationFormats(richText, index, item);\n let shared = 0;\n while (\n shared < active.length &&\n shared < desired.length &&\n active[shared]?.name === desired[shared]?.name\n ) {\n shared++;\n }\n\n for (const format of active.slice(shared).toReversed()) {\n rendered += format.close;\n }\n for (const format of desired.slice(shared)) {\n rendered += format.open;\n }\n\n rendered += renderContent(item);\n active = desired;\n }\n\n for (const format of active.toReversed()) {\n rendered += format.close;\n }\n return rendered;\n}\n\nfunction getAnnotationFormats(\n richText: readonly RichTextItemResponse[],\n index: number,\n item: RichTextItemResponse,\n): AnnotationFormat[] {\n if (item.type === \"equation\") return [];\n\n return ANNOTATION_FORMATS.filter((format) => item.annotations[format.name])\n .map((format) => ({\n format,\n runEnd: getAnnotationRunEnd(richText, index, format.name),\n }))\n .sort((left, right) => right.runEnd - left.runEnd)\n .map(({ format }) => format);\n}\n\nfunction getAnnotationRunEnd(\n richText: readonly RichTextItemResponse[],\n start: number,\n name: AnnotationName,\n): number {\n let end = start;\n for (const [offset, next] of richText.slice(start + 1).entries()) {\n if (next.type === \"equation\" || !next.annotations[name]) break;\n end = start + offset + 1;\n }\n return end;\n}\n\nfunction renderContent(item: RichTextItemResponse): string {\n if (item.type === \"equation\") {\n return inlineEquation(item.equation.expression);\n }\n return item.href ? link(item.plain_text, item.href) : item.plain_text;\n}\n","import type { BlockObjectResponse } from \"@notionhq/client\";\nimport * as md from \"./markdown.js\";\nimport { renderPlainText, renderRichText } from \"./rich-text.js\";\nimport type {\n BlockTransformerContext,\n BlockTransformers,\n NotionReader,\n} from \"./types.js\";\n\nexport interface BlockNode {\n readonly block: BlockObjectResponse;\n readonly children: BlockNode[];\n}\n\nexport interface BlockRenderContext extends BlockTransformerContext {\n readonly transformers?: BlockTransformers;\n}\n\nexport interface RenderedBlocks {\n content: string;\n childPageIds: string[];\n}\n\nexport async function loadBlockTree(\n blockId: string,\n reader: NotionReader,\n ancestors: ReadonlySet<string> = new Set(),\n): Promise<BlockNode[]> {\n if (ancestors.has(blockId)) return [];\n\n const blocks = await reader.getBlockChildren(blockId);\n const childAncestors = new Set(ancestors).add(blockId);\n const nodes: BlockNode[] = [];\n\n for (const block of blocks) {\n if (block.type === \"unsupported\") continue;\n\n const childSourceId = getChildSourceId(block);\n const children =\n childSourceId && !childAncestors.has(childSourceId)\n ? await loadBlockTree(childSourceId, reader, childAncestors)\n : [];\n nodes.push({ block, children });\n }\n\n return nodes;\n}\n\nfunction getChildSourceId(block: BlockObjectResponse): string | undefined {\n if (block.type === \"child_page\" || block.type === \"child_database\") {\n return undefined;\n }\n if (block.type === \"synced_block\") {\n return (\n block.synced_block.synced_from?.block_id ??\n (block.has_children ? block.id : undefined)\n );\n }\n return block.has_children ? block.id : undefined;\n}\n\nexport async function renderBlockTree(\n nodes: readonly BlockNode[],\n context: BlockRenderContext,\n): Promise<RenderedBlocks> {\n const result: RenderedBlocks = { content: \"\", childPageIds: [] };\n let numberedListIndex = 0;\n\n for (const node of nodes) {\n const { block } = node;\n numberedListIndex =\n block.type === \"numbered_list_item\" ? numberedListIndex + 1 : 0;\n\n const children =\n block.type === \"table\"\n ? { content: \"\", childPageIds: [] }\n : await renderBlockTree(node.children, context);\n const content = await renderBlockNode(\n node,\n children.content,\n numberedListIndex || undefined,\n context,\n );\n\n if (block.type === \"toggle\") {\n result.content += content;\n } else {\n const lineWrap = isListBlock(block) ? \"\" : \"\\n\";\n result.content += `${lineWrap}${content}\\n${lineWrap}`;\n if (!containsOwnChildren(block)) {\n result.content += children.content;\n }\n }\n\n if (block.type === \"child_page\") {\n result.childPageIds.push(block.id);\n } else {\n result.childPageIds.push(...children.childPageIds);\n }\n }\n\n return result;\n}\n\nfunction isListBlock(block: BlockObjectResponse): boolean {\n return (\n block.type === \"to_do\" ||\n block.type === \"bulleted_list_item\" ||\n block.type === \"numbered_list_item\"\n );\n}\n\nfunction containsOwnChildren(block: BlockObjectResponse): boolean {\n return (\n block.type === \"child_page\" ||\n block.type === \"callout\" ||\n block.type === \"bulleted_list_item\" ||\n block.type === \"numbered_list_item\" ||\n block.type === \"table\"\n );\n}\n\nfunction transformBlock<K extends BlockObjectResponse[\"type\"]>(\n type: K,\n block: Extract<BlockObjectResponse, { type: NoInfer<K> }>,\n context: BlockRenderContext,\n): string | undefined | Promise<string | undefined> {\n const transformer = context.transformers?.[type];\n return transformer?.(block, context);\n}\n\nasync function renderBlockNode(\n node: BlockNode,\n children: string,\n listNumber: number | undefined,\n context: BlockRenderContext,\n): Promise<string> {\n const { block } = node;\n const transformed = await transformBlock(block.type, block, context);\n const content = transformed ?? (await renderDefaultBlock(node));\n\n switch (block.type) {\n case \"toggle\":\n return md.toggle(content, children);\n case \"callout\":\n return md.callout(\n children ? `${content}\\n${children}`.trim() : content,\n block.callout.icon,\n );\n case \"bulleted_list_item\":\n return md.bullet(withIndentedChildren(content, children, 2));\n case \"numbered_list_item\":\n return md.bullet(\n withIndentedChildren(\n content,\n children,\n `${listNumber ?? 1}. `.length,\n ),\n listNumber,\n );\n default:\n return content;\n }\n}\n\nfunction withIndentedChildren(\n content: string,\n children: string,\n markerWidth: number,\n): string {\n const nested = children.trim();\n if (!nested) return content;\n\n const indentation = \" \".repeat(markerWidth);\n const indented = nested\n .split(\"\\n\")\n .map((line) => (line ? `${indentation}${line}` : \"\"))\n .join(\"\\n\");\n return `${content}\\n${indented}`;\n}\n\nasync function renderDefaultBlock(node: BlockNode): Promise<string> {\n const { block } = node;\n\n switch (block.type) {\n case \"image\": {\n const url = getFileUrl(block.image);\n const caption = renderPlainText(block.image.caption);\n const alt = caption.trim() || getFileName(url) || \"image\";\n return md.image(alt, url);\n }\n case \"divider\":\n return md.divider();\n case \"equation\":\n return md.equation(block.equation.expression);\n case \"pdf\":\n return renderFileLink(block.pdf, block.type);\n case \"file\":\n return renderFileLink(block.file, block.type);\n case \"video\":\n return renderFileLink(block.video, block.type);\n case \"audio\":\n return renderFileLink(block.audio, block.type);\n case \"embed\":\n return md.link(block.type, block.embed.url);\n case \"bookmark\":\n return md.link(block.type, block.bookmark.url);\n case \"link_preview\":\n return md.link(block.type, block.link_preview.url);\n case \"link_to_page\": {\n const target = block.link_to_page;\n if (target.type !== \"page_id\" && target.type !== \"database_id\") {\n return \"\";\n }\n const id = target.type === \"page_id\" ? target.page_id : target.database_id;\n return md.link(block.type, `https://www.notion.so/${id}`);\n }\n case \"child_page\":\n return renderChildPageLink(block);\n case \"child_database\":\n return md.heading2(block.child_database.title || \"child_database\");\n case \"table\":\n return renderTable(node.children, block.table.has_column_header);\n case \"code\":\n return md.codeBlock(\n renderRichText(block.code.rich_text),\n block.code.language,\n );\n case \"heading_1\":\n return md.heading1(renderRichText(block.heading_1.rich_text));\n case \"heading_2\":\n return md.heading2(renderRichText(block.heading_2.rich_text));\n case \"heading_3\":\n return md.heading3(renderRichText(block.heading_3.rich_text));\n case \"quote\":\n return md.quote(renderRichText(block.quote.rich_text));\n case \"to_do\":\n return md.todo(\n renderRichText(block.to_do.rich_text),\n block.to_do.checked,\n );\n case \"paragraph\":\n return renderRichText(block.paragraph.rich_text);\n case \"toggle\":\n return renderRichText(block.toggle.rich_text);\n case \"callout\":\n return renderRichText(block.callout.rich_text);\n case \"bulleted_list_item\":\n return renderRichText(block.bulleted_list_item.rich_text);\n case \"numbered_list_item\":\n return renderRichText(block.numbered_list_item.rich_text);\n case \"template\":\n return renderRichText(block.template.rich_text);\n default:\n return \"\";\n }\n}\n\ntype FileContent =\n | Extract<BlockObjectResponse, { type: \"image\" }>[\"image\"]\n | Extract<BlockObjectResponse, { type: \"pdf\" }>[\"pdf\"]\n | Extract<BlockObjectResponse, { type: \"file\" }>[\"file\"]\n | Extract<BlockObjectResponse, { type: \"video\" }>[\"video\"]\n | Extract<BlockObjectResponse, { type: \"audio\" }>[\"audio\"];\n\nfunction getFileUrl(content: FileContent): string {\n if (content.type === \"external\") return content.external.url;\n return content.file.url;\n}\n\nfunction getFileName(url: string): string | undefined {\n return url.match(/[^\\/\\\\&?]+\\.\\w{3,4}(?=([?&].*$|$))/)?.[0];\n}\n\nfunction renderFileLink(content: FileContent, fallbackTitle: string): string {\n const url = getFileUrl(content);\n const caption = renderPlainText(content.caption);\n const title = caption.trim() || getFileName(url) || fallbackTitle;\n return md.link(title, url);\n}\n\nfunction renderChildPageLink(\n block: Extract<BlockObjectResponse, { type: \"child_page\" }>,\n): string {\n return `(」゚ロ゚)」 ${md.link(block.child_page.title, `./${block.id}.md`)}`;\n}\n\nfunction renderTable(\n nodes: readonly BlockNode[],\n hasColumnHeader: boolean,\n): string {\n const rows = nodes.flatMap(({ block }) => {\n if (block.type !== \"table_row\") return [];\n return [\n block.table_row.cells.map((cell) => renderRichText(cell)),\n ];\n });\n return md.table(rows, hasColumnHeader);\n}\n","import type { PageObjectResponse } from \"@notionhq/client\";\nimport { loadBlockTree, renderBlockTree } from \"./blocks.js\";\nimport { DEFAULT_LAYOUT_PATH } from \"./defaults.js\";\nimport { renderFrontmatter } from \"./frontmatter.js\";\nimport type { NotionPropertyMap } from \"./config.js\";\nimport type {\n BlockTransformers,\n FrontmatterAdapter,\n NotionReader,\n RenderedPage,\n RenderOptions,\n} from \"./types.js\";\n\ninterface PageRenderContext {\n readonly reader: NotionReader;\n readonly databasePageIds: ReadonlySet<string>;\n readonly layoutPath: string;\n readonly propertyMap: NotionPropertyMap | undefined;\n readonly frontmatter: FrontmatterAdapter | undefined;\n readonly recursive: boolean;\n readonly transformers: BlockTransformers | undefined;\n readonly renderedPages: Map<string, Promise<RenderedPage>>;\n}\n\nexport async function renderPage(\n pageId: string,\n reader: NotionReader,\n options: RenderOptions = {},\n): Promise<RenderedPage> {\n const context = createContext([pageId], reader, options);\n return collectPage(pageId, context);\n}\n\nexport async function renderPages(\n posts: readonly PageObjectResponse[],\n reader: NotionReader,\n options: RenderOptions = {},\n): Promise<RenderedPage[]> {\n const context = createContext(\n posts.map((post) => post.id),\n reader,\n options,\n );\n\n for (const post of posts) {\n await collectPage(post.id, context, post);\n }\n\n return Promise.all([...context.renderedPages.values()]);\n}\n\nfunction createContext(\n databasePageIds: readonly string[],\n reader: NotionReader,\n options: RenderOptions,\n): PageRenderContext {\n return {\n reader,\n databasePageIds: new Set(databasePageIds),\n layoutPath: options.layoutPath ?? DEFAULT_LAYOUT_PATH,\n propertyMap: options.propertyMap,\n frontmatter: options.frontmatter,\n recursive: options.recursive ?? true,\n transformers: options.transformers,\n renderedPages: new Map(),\n };\n}\n\nasync function collectPage(\n pageId: string,\n context: PageRenderContext,\n post?: PageObjectResponse,\n): Promise<RenderedPage> {\n const cached = context.renderedPages.get(pageId);\n if (cached) return cached;\n\n const rendering = renderSinglePage(pageId, context, post);\n context.renderedPages.set(pageId, rendering);\n const rendered = await rendering;\n\n if (context.recursive) {\n for (const childPageId of rendered.childPageIds) {\n await collectPage(childPageId, context);\n }\n }\n\n return rendered;\n}\n\nasync function renderSinglePage(\n pageId: string,\n context: PageRenderContext,\n post?: PageObjectResponse,\n): Promise<RenderedPage> {\n const [page, blockTree] = await Promise.all([\n post ? Promise.resolve(post) : context.reader.getPage(pageId),\n loadBlockTree(pageId, context.reader),\n ]);\n const rendered = await renderBlockTree(blockTree, {\n pageId,\n ...(context.transformers !== undefined\n ? { transformers: context.transformers }\n : {}),\n });\n const frontmatter = renderFrontmatter(page, {\n layoutPath: context.layoutPath,\n ...(context.propertyMap !== undefined\n ? { propertyMap: context.propertyMap }\n : {}),\n ...(context.frontmatter !== undefined\n ? { frontmatter: context.frontmatter }\n : {}),\n isDatabasePage: context.databasePageIds.has(pageId),\n });\n\n return {\n id: pageId,\n content: frontmatter + rendered.content,\n childPageIds: [...new Set(rendered.childPageIds)],\n isDatabasePage: context.databasePageIds.has(pageId),\n };\n}\n","import { randomUUID } from \"node:crypto\";\nimport {\n lstat,\n mkdir,\n readlink,\n rename,\n rm,\n symlink,\n writeFile,\n} from \"node:fs/promises\";\nimport { dirname, join, relative, resolve, sep } from \"node:path\";\nimport { getErrorCode } from \"../errors.js\";\nimport {\n DEFAULT_LAYOUT_PATH,\n DEFAULT_SAVE_PATH,\n DEFAULT_STORE_PATH,\n} from \"./defaults.js\";\nimport type {\n RenderedPage,\n StoreLayoutOptions,\n WritePagesOptions,\n} from \"./types.js\";\n\nexport async function writePages(\n pages: readonly RenderedPage[],\n options: WritePagesOptions = {},\n): Promise<string[]> {\n const savePath = options.savePath ?? DEFAULT_SAVE_PATH;\n const storePath = options.storePath ?? DEFAULT_STORE_PATH;\n const writtenPaths: string[] = [];\n await mkdir(storePath, { recursive: true });\n\n for (const page of pages) {\n const storedPath = join(storePath, `${page.id}.md`);\n const storedStats = await getStats(storedPath);\n if (storedStats && !storedStats.isFile()) {\n throw new Error(`Page store path is not a regular file: ${storedPath}`);\n }\n await writeFile(storedPath, page.content, \"utf8\");\n\n if (!page.isDatabasePage) continue;\n\n const viewPath = join(savePath, `${page.id}.md`);\n await mkdir(savePath, { recursive: true });\n await linkStoredPage(storedPath, viewPath);\n writtenPaths.push(viewPath);\n }\n\n return writtenPaths;\n}\n\nexport function resolveStoreLayoutPath(\n options: StoreLayoutOptions = {},\n): string {\n const savePath = options.savePath ?? DEFAULT_SAVE_PATH;\n const storePath = options.storePath ?? DEFAULT_STORE_PATH;\n const layoutPath = options.layoutPath ?? DEFAULT_LAYOUT_PATH;\n const layoutTarget = resolve(savePath, layoutPath);\n return relative(resolve(storePath), layoutTarget).split(sep).join(\"/\");\n}\n\nasync function linkStoredPage(\n storedPath: string,\n viewPath: string,\n): Promise<void> {\n if (resolve(storedPath) === resolve(viewPath)) return;\n\n const viewStats = await getStats(viewPath);\n if (viewStats && !viewStats.isFile() && !viewStats.isSymbolicLink()) {\n throw new Error(`Refusing to replace non-file output: ${viewPath}`);\n }\n\n const target = relative(dirname(viewPath), storedPath);\n if (viewStats?.isSymbolicLink() && (await readlink(viewPath)) === target) {\n return;\n }\n\n const temporaryPath = `${viewPath}.noblog-${randomUUID()}.tmp`;\n try {\n await symlink(target, temporaryPath, \"file\");\n await rename(temporaryPath, viewPath);\n } catch (error) {\n await rm(temporaryPath, { force: true }).catch(() => {});\n throw error;\n }\n}\n\nasync function getStats(path: string) {\n try {\n return await lstat(path);\n } catch (error: unknown) {\n if (getErrorCode(error) === \"ENOENT\") return undefined;\n throw error;\n }\n}\n","import {\n Client,\n isFullBlock,\n isFullDatabase,\n isFullPage,\n isNotionClientError,\n} from \"@notionhq/client\";\nimport type {\n BlockObjectResponse,\n PageObjectResponse,\n} from \"@notionhq/client\";\nimport type { NotionReader } from \"../noblog/types.js\";\n\nexport interface NotionSourceConfig {\n readonly notionApiSecret: string;\n readonly databaseId: string;\n}\n\nexport interface NotionSource extends NotionReader {\n listPages(): Promise<PageObjectResponse[]>;\n}\n\nexport function createNotionSource(\n config: NotionSourceConfig,\n): NotionSource {\n const client = new Client({ auth: config.notionApiSecret });\n return {\n listPages: () => listPages(client, config.databaseId),\n getPage: (pageId) => getPage(client, pageId),\n getBlockChildren: (blockId) => getBlockChildren(client, blockId),\n };\n}\n\nasync function listPages(\n client: Client,\n databaseId: string,\n): Promise<PageObjectResponse[]> {\n const database = await withRetry(() =>\n client.databases.retrieve({ database_id: databaseId }),\n );\n if (!isFullDatabase(database)) {\n throw new Error(`Notion database ${databaseId} is not a full database`);\n }\n\n const pages: PageObjectResponse[] = [];\n for (const source of database.data_sources) {\n let startCursor: string | undefined;\n do {\n const response = await withRetry(() =>\n client.dataSources.query({\n data_source_id: source.id,\n filter: {\n property: \"publish\",\n checkbox: { equals: true },\n },\n sorts: [{ property: \"date\", direction: \"descending\" }],\n page_size: 100,\n ...(startCursor !== undefined\n ? { start_cursor: startCursor }\n : {}),\n }),\n );\n pages.push(...response.results.filter(isFullPage));\n startCursor = response.next_cursor ?? undefined;\n if (!response.has_more) break;\n } while (startCursor);\n }\n\n return pages;\n}\n\nasync function getPage(\n client: Client,\n pageId: string,\n): Promise<PageObjectResponse> {\n const page = await withRetry(() => client.pages.retrieve({ page_id: pageId }));\n if (!isFullPage(page)) {\n throw new Error(`Notion page ${pageId} is not a full page`);\n }\n return page;\n}\n\nasync function getBlockChildren(\n client: Client,\n blockId: string,\n): Promise<BlockObjectResponse[]> {\n const blocks: BlockObjectResponse[] = [];\n let startCursor: string | undefined;\n\n do {\n const response = await withRetry(() =>\n client.blocks.children.list({\n block_id: blockId,\n page_size: 100,\n ...(startCursor !== undefined ? { start_cursor: startCursor } : {}),\n }),\n );\n blocks.push(...response.results.filter(isFullBlock));\n startCursor = response.next_cursor ?? undefined;\n } while (startCursor);\n\n return blocks;\n}\n\nconst MAX_ATTEMPTS = 3;\nconst INITIAL_RETRY_DELAY_MS = 500;\n\nasync function withRetry<T>(operation: () => Promise<T>): Promise<T> {\n for (let attempt = 0; ; attempt++) {\n try {\n return await operation();\n } catch (error: unknown) {\n if (attempt === MAX_ATTEMPTS - 1 || !isRetryable(error)) throw error;\n await delay(INITIAL_RETRY_DELAY_MS * 2 ** attempt);\n }\n }\n}\n\nfunction isRetryable(error: unknown): boolean {\n if (!isNotionClientError(error) || !(\"status\" in error)) return true;\n return error.status === 408 || error.status === 429 || error.status >= 500;\n}\n\nfunction delay(milliseconds: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, milliseconds));\n}\n"],"mappings":"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"}