noblog 0.2.2 → 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 -932
  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 -962
  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
package/dist/generate.cjs DELETED
@@ -1,962 +0,0 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- var path = require('path');
5
- var fs = require('fs/promises');
6
- var fetch = require('node-fetch');
7
- var markdownTable = require('markdown-table');
8
- require('dotenv/config');
9
- var retry = require('async-retry');
10
- var client$1 = require('@notionhq/client');
11
- var fs$1 = require('fs');
12
- var url = require('url');
13
-
14
- var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
15
- function _interopNamespaceDefault(e) {
16
- var n = Object.create(null);
17
- if (e) {
18
- Object.keys(e).forEach(function (k) {
19
- if (k !== 'default') {
20
- var d = Object.getOwnPropertyDescriptor(e, k);
21
- Object.defineProperty(n, k, d.get ? d : {
22
- enumerable: true,
23
- get: function () { return e[k]; }
24
- });
25
- }
26
- });
27
- }
28
- n.default = e;
29
- return Object.freeze(n);
30
- }
31
-
32
- var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path);
33
- var fs__namespace = /*#__PURE__*/_interopNamespaceDefault(fs);
34
-
35
- const suffixInlineCode = "`";
36
- const prefixInlineCode = "`";
37
- const suffixBold = "**";
38
- const prefixBold = "**";
39
- const suffixItalic = "_";
40
- const prefixItalic = "_";
41
- const suffixStrikethrough = "~~";
42
- const prefixStrikethrough = "~~";
43
- const suffixUnderline = "<u>";
44
- const prefixUnderline = "</u>";
45
- function GetPrefixSuffix(type) {
46
- switch (type) {
47
- case "code":
48
- return [prefixInlineCode, suffixInlineCode];
49
- case "bold":
50
- return [prefixBold, suffixBold];
51
- case "italic":
52
- return [prefixItalic, suffixItalic];
53
- case "strikethrough":
54
- return [prefixStrikethrough, suffixStrikethrough];
55
- case "underline":
56
- return [prefixUnderline, suffixUnderline];
57
- default:
58
- return ["", ""];
59
- }
60
- }
61
- const inlineEquation = (text) => {
62
- return `$${text}$`;
63
- };
64
- const link = (text, href) => {
65
- return `[${text}](${href})`;
66
- };
67
- const codeBlock = (text, language) => {
68
- if (language === "plain text")
69
- language = "text";
70
- return `\`\`\`${language}
71
- ${text}
72
- \`\`\``;
73
- };
74
- const equation = (text) => {
75
- return `$$
76
- ${text}
77
- $$`;
78
- };
79
- const heading1 = (text) => {
80
- return `# ${text}`;
81
- };
82
- const heading2 = (text) => {
83
- return `## ${text}`;
84
- };
85
- const heading3 = (text) => {
86
- return `### ${text}`;
87
- };
88
- const quote = (text) => {
89
- // the replace is done to handle multiple lines
90
- return `> ${text.replace(/\n/g, " \n> ")}`;
91
- };
92
- const callout = (text, icon) => {
93
- let emoji;
94
- if (icon?.type === "emoji") {
95
- emoji = icon.emoji;
96
- }
97
- // the replace is done to handle multiple lines
98
- return `> ${emoji ? emoji + " " : ""}${text.replace(/\n/g, " \n> ")}`;
99
- };
100
- const bullet = (text, count) => {
101
- const renderText = text.trim();
102
- return count ? `${count}. ${renderText}` : `- ${renderText}`;
103
- };
104
- const todo = (text, checked) => {
105
- return checked ? `- [x] ${text}` : `- [ ] ${text}`;
106
- };
107
- const image = async (alt, href, convertToBase64 = false) => {
108
- // In case the user does not want to convert the images to Base64
109
- // or the image is already base64
110
- if (!convertToBase64 || href.startsWith("data:")) {
111
- if (href.startsWith("data:")) {
112
- // Extract base64 data, i.e. the string after 'data:mime/type;base64,'
113
- const base64 = href.split(",").pop();
114
- // This overrides incorrect data: string format to png
115
- // so that browsers can correctly render the data
116
- return `![${alt}](data:image/png;base64,${base64})`;
117
- }
118
- return `![${alt}](${href})`;
119
- }
120
- else {
121
- // Otherwise, download the image and convert it to base64
122
- const res = await fetch(href);
123
- const buf = await res.arrayBuffer();
124
- const base64 = Buffer.from(buf).toString("base64");
125
- return `![${alt}](data:image/png;base64,${base64})`;
126
- }
127
- };
128
- const addTabSpace = (text, n = 0) => {
129
- const tab = " ";
130
- for (let i = 0; i < n; i++) {
131
- if (text.includes("\n")) {
132
- const multiLineText = text.split(/(?:^|\n)/).join(`\n${tab}`);
133
- text = tab + multiLineText;
134
- }
135
- else
136
- text = tab + text;
137
- }
138
- return text;
139
- };
140
- const divider = () => {
141
- return "---";
142
- };
143
- const toggle = (summary, children) => {
144
- if (!summary)
145
- return children || "";
146
- return `<details>
147
- <summary>${summary}</summary>
148
- ${children || ""}
149
- </details>\n\n`;
150
- };
151
- const table = (cells) => {
152
- return markdownTable.markdownTable(cells);
153
- };
154
-
155
- const NOTION_API_SECRET = process.env.NOTION_API_SECRET ?? "";
156
- const NOBLOG_DATABASE_ID = process.env.NOBLOG_DATABASE_ID ?? "";
157
- const client = new client$1.Client({
158
- auth: NOTION_API_SECRET,
159
- });
160
- const numberOfRetry = 2;
161
- async function ListPages() {
162
- const respDatabase = (await client.databases.retrieve({
163
- database_id: NOBLOG_DATABASE_ID,
164
- }));
165
- const reqDataSources = respDatabase.data_sources.map((source) => ({
166
- data_source_id: source.id,
167
- filter: {
168
- and: [
169
- {
170
- property: "publish",
171
- checkbox: { equals: true },
172
- },
173
- ],
174
- },
175
- sorts: [
176
- {
177
- property: "date",
178
- direction: "descending",
179
- },
180
- ],
181
- page_size: 100,
182
- }));
183
- let results = [];
184
- for (const req of reqDataSources) {
185
- let params = req;
186
- while (true) {
187
- const resp = await retry(async (bail) => {
188
- try {
189
- return client.dataSources.query(params);
190
- }
191
- catch (error) {
192
- if (client$1.isNotionClientError(error)) {
193
- bail(error);
194
- }
195
- throw error;
196
- }
197
- }, { retries: numberOfRetry });
198
- results = results.concat(resp.results);
199
- if (!resp.has_more) {
200
- break;
201
- }
202
- params.start_cursor = resp.next_cursor;
203
- }
204
- }
205
- return results;
206
- }
207
- async function GetPage(page_id) {
208
- const result = await retry(async (bail) => {
209
- try {
210
- return (await client.pages.retrieve({
211
- page_id: page_id,
212
- }));
213
- }
214
- catch (error) {
215
- if (client$1.isNotionClientError(error)) {
216
- bail(error);
217
- }
218
- throw error;
219
- }
220
- }, { retries: numberOfRetry });
221
- return result;
222
- }
223
- async function GetBlockChildren(block_id, totalPage) {
224
- const results = [];
225
- let pageCount = 0;
226
- let start_cursor = undefined;
227
- do {
228
- const response = (await client.blocks.children.list({
229
- start_cursor: start_cursor,
230
- block_id: block_id,
231
- }));
232
- results.push(...response.results);
233
- start_cursor = response?.next_cursor;
234
- pageCount += 1;
235
- } while (start_cursor != null &&
236
- (totalPage == null || pageCount < totalPage));
237
- ModifyNumberedListObject(results);
238
- return results;
239
- }
240
- function ModifyNumberedListObject(blocks) {
241
- let numberedListIndex = 0;
242
- for (const block of blocks) {
243
- if ("type" in block && block.type === "numbered_list_item") {
244
- // add numbers
245
- // @ts-ignore
246
- block.numbered_list_item.number = ++numberedListIndex;
247
- }
248
- else {
249
- numberedListIndex = 0;
250
- }
251
- }
252
- }
253
-
254
- const BLOCK_TO_DO = "to_do";
255
- const BLOCK_TOGGLE = "toggle";
256
- const BLOCK_CALLOUT = "callout";
257
- const BLOCK_CHILD_PAGE = "child_page";
258
- const BLOCK_UNSUPPORTED = "supported";
259
- const BLOCK_SYNCED_BLOCK = "synced_block";
260
- const BLOCK_NUMBERED_LIST_ITEM = "numbered_list_item";
261
- const BLOCK_BULLETED_LIST_ITEM = "bulleted_list_item";
262
- const BLOCK_IMAGE = "image";
263
- const BLOCK_DIVIDER = "divider";
264
- const BLOCK_EQUATION = "equation";
265
- const BLOCK_PDF = "pdf";
266
- const BLOCK_FILE = "file";
267
- const BLOCK_VIDEO = "video";
268
- const BLOCK_EMBED = "embed";
269
- const BLOCK_BOOKMARK = "bookmark";
270
- const BLOCK_LINK_PREVIEW = "link_preview";
271
- const BLOCK_LINK_TO_PAGE = "link_to_page";
272
- const BLOCK_CHILD_DATABASE = "child_database";
273
- const BLOCK_TABLE = "table";
274
- const BLOCK_CODE = "code";
275
- const BLOCK_HEADING_1 = "heading_1";
276
- const BLOCK_HEADING_2 = "heading_2";
277
- const BLOCK_HEADING_3 = "heading_3";
278
- const BLOCK_QUOTE = "quote";
279
- const BLOCK_PARAGRAPH = "paragraph";
280
- const LINEWRAP_NULL = "";
281
- const LINEWRAP_NEWLINE = "\n";
282
-
283
- async function SaveJelly(fname, dir, jelly, dry_run = false) {
284
- if (jelly === undefined)
285
- return `failed: ${fname} has empty jelly: `;
286
- try {
287
- if (dry_run) {
288
- console.log(jelly.content);
289
- return `success: ${fname} saved to ${dir}`;
290
- }
291
- await fs__namespace.mkdir(dir, { recursive: true });
292
- const filePath = path__namespace.join(dir, fname);
293
- await fs__namespace.writeFile(filePath, jelly.content, "utf-8");
294
- return `success: ${fname} saved to ${filePath}`;
295
- }
296
- catch (error) {
297
- if (error instanceof Error) {
298
- return `failed: ${fname} - ${error.message}`;
299
- }
300
- else {
301
- return "failed: an unknown error occurred";
302
- }
303
- }
304
- }
305
- class Noblog {
306
- Posts = [];
307
- CurrPage = "";
308
- SavePath = "./src/pages/posts/";
309
- ChildPath = "nob_children/";
310
- LayoutPath = "../../layouts/MarkdownPostLayout.astro";
311
- MdCollection = {};
312
- CustomTransformers = {};
313
- constructor(posts, save_path, child_path, layout_path) {
314
- this.Posts = posts;
315
- if (child_path != undefined) {
316
- this.ChildPath = child_path;
317
- }
318
- if (save_path != undefined) {
319
- this.SavePath = save_path;
320
- }
321
- if (layout_path != undefined) {
322
- this.LayoutPath = layout_path;
323
- }
324
- this.CustomTransformers[BLOCK_CHILD_PAGE] = async (block) => {
325
- const page = block;
326
- if (!page?.id) {
327
- return false;
328
- }
329
- // if (curr_page.id in this.Post) and (page.id in this.Post) ||
330
- // (curr_page.id not in this.Post) and (page.id not in this.Post)
331
- // * path should be "../pageid"
332
- // if (curr_page.id in this.Post) and (page.id not in this.Post)
333
- // * path should be "../save_path/pageid"
334
- // if (curr_page.id not in this.Post) and (page.id in this.Post)
335
- // * path should be "../../pageid"
336
- let sub_path;
337
- const parent_surface = this.Posts.filter((post) => post.id === this.CurrPage).length > 0;
338
- const child_surface = this.Posts.filter((post) => post.id === page.id).length > 0;
339
- if (parent_surface === child_surface) {
340
- sub_path = "";
341
- }
342
- else if (parent_surface) {
343
- sub_path = this.ChildPath;
344
- }
345
- else {
346
- sub_path = "..";
347
- }
348
- const full_path = path__namespace.join("../", sub_path, page.id);
349
- return `(」゚ロ゚)」 [${page.child_page.title}](${full_path})`;
350
- };
351
- }
352
- // Collect converts all pages in database to markdown files
353
- async Collect(recursive = true) {
354
- const save_dir = this.SavePath;
355
- const sub_dir = path__namespace.join(save_dir, this.ChildPath);
356
- for (const page of this.Posts) {
357
- await this.FromPageId(page.id, recursive);
358
- }
359
- try {
360
- for (const page_id of Object.keys(this.MdCollection)) {
361
- let fpath = sub_dir;
362
- if (this.Posts.find((page) => page.id === page_id))
363
- fpath = save_dir;
364
- await SaveJelly(page_id + ".md", fpath, this.MdCollection[page_id]);
365
- }
366
- console.log(`success: finish dump all files to "${save_dir}"`);
367
- }
368
- catch (error) {
369
- if (error instanceof Error) {
370
- console.log(`failed: consider run verbose with v=true - ${error.message}`);
371
- }
372
- else {
373
- console.log("failed: an unknown error occurred");
374
- }
375
- }
376
- }
377
- // FromBlocks converts blocks fetched from notion api to
378
- // rendered chunk (which is called jelly). Content in jelly
379
- // will be collected to form the final markdown. If recursive
380
- // is true, it will also fetch child pages exists in the given
381
- // blocks.
382
- async FromBlocks(blocks, recursive) {
383
- const jelly = { content: "", children: [] };
384
- for (const block of blocks) {
385
- const subjelly = await this.FromBlocks(block.children, recursive);
386
- let line_wrap = LINEWRAP_NULL;
387
- if (block.type !== BLOCK_TO_DO &&
388
- block.type !== BLOCK_BULLETED_LIST_ITEM &&
389
- block.type !== BLOCK_NUMBERED_LIST_ITEM) {
390
- line_wrap = LINEWRAP_NEWLINE;
391
- }
392
- switch (block.type) {
393
- case BLOCK_TOGGLE:
394
- jelly.content += toggle(block.parent, subjelly.content);
395
- jelly.children = [...jelly.children, ...subjelly.children];
396
- continue;
397
- default:
398
- jelly.content +=
399
- line_wrap + `${addTabSpace(block.parent, 0)}\n` + line_wrap;
400
- }
401
- if (block.type === BLOCK_CHILD_PAGE) {
402
- jelly.children = [...jelly.children, block.blockId];
403
- if (recursive)
404
- await this.FromPageId(block.blockId, recursive);
405
- }
406
- else {
407
- jelly.content += subjelly.content;
408
- jelly.children = [...jelly.children, ...subjelly.children];
409
- }
410
- }
411
- return jelly;
412
- }
413
- // FromPageid converts a page id to rendered chunk (which is
414
- // called jelly). Content in jelly will be collected to form
415
- // the final markdown. If recursive is true, it will also
416
- // fetch child pages exists in the given page.
417
- async FromPageId(page_id, recursive = true) {
418
- // preserve the curr page info
419
- const prev_page = this.CurrPage;
420
- this.CurrPage = page_id;
421
- if (this.MdCollection.hasOwnProperty(page_id)) {
422
- return this.MdCollection[page_id];
423
- }
424
- const blocks = await this.PageToMarkdown(page_id);
425
- const jelly = await this.FromBlocks(blocks, recursive);
426
- const meta = await GetPage(page_id);
427
- const astro_meta = await this.AssembleAstroFrontmatter(meta);
428
- jelly.content = astro_meta + jelly.content;
429
- this.MdCollection[page_id] = jelly;
430
- // restore the prev page info
431
- this.CurrPage = prev_page;
432
- return jelly;
433
- }
434
- async PageToMarkdown(id, totalPage) {
435
- if (!client) {
436
- throw new Error("notion client is not provided");
437
- }
438
- const blocks = await GetBlockChildren(id, totalPage);
439
- const parsedData = await this.BlocksToMarkdown(blocks);
440
- return parsedData;
441
- }
442
- async BlocksToMarkdown(blocks, totalPage = null, mdBlocks = []) {
443
- if (!client) {
444
- throw new Error("notion client is not provided");
445
- }
446
- if (!blocks)
447
- return mdBlocks;
448
- for (const block of blocks) {
449
- // @ts-ignore
450
- if (block.type === BLOCK_UNSUPPORTED) {
451
- continue;
452
- }
453
- if (!("has_children" in block) || !block.has_children) {
454
- mdBlocks.push({
455
- // @ts-ignore
456
- type: block.type,
457
- blockId: block.id,
458
- children: [],
459
- parent: await this.BlockToMarkdown(block),
460
- });
461
- continue;
462
- }
463
- const block_id = block.type == BLOCK_SYNCED_BLOCK &&
464
- block.synced_block?.synced_from?.block_id
465
- ? block.synced_block.synced_from.block_id
466
- : block.id;
467
- // Get children of this block.
468
- const child_blocks = await GetBlockChildren(block_id, totalPage);
469
- // Push this block to mdBlocks.
470
- mdBlocks.push({
471
- type: block.type,
472
- blockId: block.id,
473
- parent: await this.BlockToMarkdown(block),
474
- children: [],
475
- });
476
- if (block.type === BLOCK_CALLOUT) {
477
- continue;
478
- }
479
- if (!(block.type in this.CustomTransformers)) {
480
- continue;
481
- }
482
- // Recursively call BlocksToMarkdown to collect children. check for custom transformer before parsing child
483
- if (mdBlocks !== undefined) {
484
- await this.BlocksToMarkdown(child_blocks, totalPage, mdBlocks[mdBlocks.length - 1]?.children);
485
- }
486
- }
487
- return mdBlocks;
488
- }
489
- async BlockToMarkdown(block) {
490
- if (typeof block !== "object" || !("type" in block))
491
- return "";
492
- let renderedData = "";
493
- const { type } = block;
494
- if (type in this.CustomTransformers && !!this.CustomTransformers[type]) {
495
- const transformer = this.CustomTransformers[type];
496
- const customTransformerValue = await transformer(block);
497
- if (typeof customTransformerValue === "string")
498
- return customTransformerValue;
499
- }
500
- switch (type) {
501
- case BLOCK_IMAGE:
502
- const blockContent = block.image;
503
- let image_title = "image";
504
- const image_caption_plain = blockContent.caption
505
- .map((item) => item.plain_text)
506
- .join("");
507
- let link$1 = "";
508
- const image_type = blockContent.type;
509
- if (image_type === "external") {
510
- link$1 = blockContent.external.url;
511
- }
512
- if (image_type === "file") {
513
- link$1 = blockContent.file.url;
514
- }
515
- // image caption with high priority
516
- if (image_caption_plain.trim().length > 0) {
517
- image_title = image_caption_plain;
518
- }
519
- else if (image_type === "file" || image_type === "external") {
520
- const matches = link$1.match(/[^\/\\&\?]+\.\w{3,4}(?=([\?&].*$|$))/);
521
- image_title = matches ? matches[0] : image_title;
522
- }
523
- return await image(image_title, link$1, false);
524
- case BLOCK_DIVIDER:
525
- return divider();
526
- case BLOCK_EQUATION:
527
- return equation(block.equation.expression);
528
- case BLOCK_PDF:
529
- case BLOCK_FILE:
530
- case BLOCK_VIDEO:
531
- {
532
- let blockContent;
533
- let title = type;
534
- if (type === BLOCK_PDF)
535
- blockContent = block.pdf;
536
- if (type === BLOCK_FILE)
537
- blockContent = block.file;
538
- if (type === BLOCK_VIDEO)
539
- blockContent = block.video;
540
- const caption = blockContent?.caption
541
- .map((item) => item.plain_text)
542
- .join("");
543
- if (blockContent) {
544
- const file_type = blockContent.type;
545
- let link$1 = "";
546
- if (file_type === "file")
547
- link$1 = blockContent.file.url;
548
- if (file_type === "external")
549
- link$1 = blockContent.external.url;
550
- if (caption && caption.trim().length > 0) {
551
- title = caption;
552
- }
553
- else if (link$1) {
554
- const matches = link$1.match(/[^\/\\&\?]+\.\w{3,4}(?=([\?&].*$|$))/);
555
- title = matches ? matches[0] : type;
556
- }
557
- return link(title, link$1);
558
- }
559
- }
560
- break;
561
- case BLOCK_EMBED:
562
- case BLOCK_BOOKMARK:
563
- case BLOCK_LINK_PREVIEW:
564
- case BLOCK_LINK_TO_PAGE:
565
- {
566
- let blockContent;
567
- const title = type;
568
- if (type === BLOCK_EMBED)
569
- blockContent = block.embed;
570
- if (type === BLOCK_BOOKMARK)
571
- blockContent = block.bookmark;
572
- if (type === BLOCK_LINK_PREVIEW)
573
- blockContent = block.link_preview;
574
- if (type === BLOCK_LINK_TO_PAGE &&
575
- block.link_to_page.type === "page_id") {
576
- blockContent = {
577
- url: `https://www.notion.so/${block.link_to_page.page_id}`,
578
- };
579
- }
580
- if (blockContent)
581
- return link(title, blockContent.url);
582
- }
583
- break;
584
- case BLOCK_CHILD_PAGE: {
585
- const pageTitle = block.child_page.title;
586
- return heading2(pageTitle);
587
- }
588
- case BLOCK_CHILD_DATABASE: {
589
- const pageTitle = block.child_database.title || `child_database`;
590
- return heading2(pageTitle);
591
- }
592
- case BLOCK_TABLE: {
593
- const { id, has_children } = block;
594
- const tableArr = [];
595
- if (has_children) {
596
- const tableRows = await GetBlockChildren(id, 100);
597
- const rowsPromise = tableRows?.map(async (row) => {
598
- const { type } = row;
599
- const cells = row[type]["cells"];
600
- /**
601
- * this is more like a hack since matching the type text was
602
- * difficult. So converting each cell to paragraph type to
603
- * reuse the BlockToMarkdown function
604
- */
605
- const cellStringPromise = cells.map(async (cell) => await this.BlockToMarkdown({
606
- type: BLOCK_PARAGRAPH,
607
- paragraph: { rich_text: cell },
608
- }));
609
- const cellStringArr = await Promise.all(cellStringPromise);
610
- tableArr.push(cellStringArr);
611
- });
612
- await Promise.all(rowsPromise || []);
613
- }
614
- return table(tableArr);
615
- }
616
- // Rest of the types:
617
- // "paragraph" "heading_1" "heading_2" "heading_3"
618
- // "bulleted_list_item" "numbered_list_item"
619
- // "quote" "to_do" "template" "synced_block"
620
- // "child_page" "child_database" "code" "callout" "breadcrumb"
621
- // "table_of_contents" "link_to_page" "audio" "unsupported"
622
- default: {
623
- // @ts-ignore
624
- const blockContent = block[type].text || block[type].rich_text || [];
625
- const bucket = [];
626
- const code_range = [];
627
- const bold_range = [];
628
- const italic_range = [];
629
- const underline_range = [];
630
- const strikethrough_range = [];
631
- const push_or_merge = (arr, idx) => {
632
- if (arr.length == 0) {
633
- return;
634
- }
635
- // @ts-ignore
636
- if (arr[arr.length - 1][1] === idx) {
637
- // @ts-ignore
638
- arr[arr.length - 1][1] += 1;
639
- }
640
- else {
641
- arr.push([idx, idx + 1]);
642
- }
643
- };
644
- for (let i = 0; i < blockContent.length; i++) {
645
- const content = blockContent[i];
646
- if (content.type === "equation") {
647
- bucket.push(inlineEquation(content.equation.expression));
648
- continue;
649
- }
650
- let plain_text = content.plain_text;
651
- if (content["href"])
652
- plain_text = link(plain_text, content["href"]);
653
- bucket.push(plain_text);
654
- const annotations = content.annotations;
655
- if (annotations.code)
656
- push_or_merge(code_range, i);
657
- if (annotations.bold)
658
- push_or_merge(bold_range, i);
659
- if (annotations.italic)
660
- push_or_merge(italic_range, i);
661
- if (annotations.underline)
662
- push_or_merge(underline_range, i);
663
- if (annotations.strikethrough)
664
- push_or_merge(strikethrough_range, i);
665
- }
666
- const annoed_bucket = [];
667
- for (const range of code_range)
668
- annoed_bucket.push({ range: range, type: "code" });
669
- for (const range of bold_range)
670
- annoed_bucket.push({ range: range, type: "bold" });
671
- for (const range of italic_range)
672
- annoed_bucket.push({ range: range, type: "italic" });
673
- for (const range of underline_range)
674
- annoed_bucket.push({ range: range, type: "underline" });
675
- for (const range of strikethrough_range)
676
- annoed_bucket.push({ range: range, type: "strikethrough" });
677
- annoed_bucket.sort(
678
- // @ts-ignore
679
- (a, b) => a.range[1] - a.range[0] - (b.range[1] - b.range[0]));
680
- for (const annoed of annoed_bucket) {
681
- const type = annoed.type;
682
- const [from, to] = annoed.range;
683
- let panno, sanno = GetPrefixSuffix(type);
684
- // @ts-ignore
685
- bucket[from] = panno + bucket[from];
686
- // @ts-ignore
687
- bucket[to - 1] = bucket[to - 1] + sanno;
688
- }
689
- bucket.forEach((x) => (renderedData += x));
690
- }
691
- }
692
- switch (type) {
693
- case BLOCK_CODE:
694
- renderedData = codeBlock(renderedData, block[type].language);
695
- break;
696
- case BLOCK_HEADING_1:
697
- renderedData = heading1(renderedData);
698
- break;
699
- case BLOCK_HEADING_2:
700
- renderedData = heading2(renderedData);
701
- break;
702
- case BLOCK_HEADING_3:
703
- renderedData = heading3(renderedData);
704
- break;
705
- case BLOCK_QUOTE:
706
- renderedData = quote(renderedData);
707
- break;
708
- case BLOCK_CALLOUT:
709
- const { id, has_children } = block;
710
- let callout_string = "";
711
- if (!has_children) {
712
- return callout(renderedData, block.callout.icon);
713
- }
714
- const callout_children_object = await GetBlockChildren(id, 100);
715
- const callout_children = await this.BlocksToMarkdown(callout_children_object);
716
- callout_string += `${renderedData}\n`;
717
- callout_string += (await this.FromBlocks(callout_children, true))
718
- .content;
719
- renderedData = callout(callout_string.trim(), block.callout.icon);
720
- break;
721
- case BLOCK_BULLETED_LIST_ITEM:
722
- renderedData = bullet(renderedData);
723
- break;
724
- case BLOCK_NUMBERED_LIST_ITEM:
725
- renderedData = bullet(renderedData, block.numbered_list_item.number);
726
- break;
727
- case BLOCK_TO_DO:
728
- renderedData = todo(renderedData, block.to_do.checked);
729
- break;
730
- }
731
- return renderedData;
732
- }
733
- async AssembleAstroFrontmatter(page) {
734
- let frontmatter = "";
735
- // Default layout
736
- const ischild = this.Posts.filter((post) => post.id === page.id).length > 0 ? "" : "../";
737
- frontmatter += "layout: " + ischild + this.LayoutPath + "\n";
738
- let title = "";
739
- let description = "";
740
- let tags = [];
741
- let pubDate = "";
742
- let archived = "";
743
- Object.entries(page.properties).forEach(([key, property]) => {
744
- switch (property.type) {
745
- case "title": // Get title
746
- title = property.title
747
- ?.map((title) => title.plain_text)
748
- .reduce((a, b) => a + b, "");
749
- break;
750
- case "multi_select": // Get tags
751
- tags = property.multi_select?.map((tag) => tag.name);
752
- break;
753
- case "date": // Get date
754
- pubDate =
755
- property.date?.start ?? new Date().toISOString().split("T")[0];
756
- break;
757
- default: // Customized properties from the template given in the Github repo
758
- switch (key) {
759
- case "archived": // Get archived
760
- archived =
761
- property.type === "checkbox" && property.checkbox
762
- ? "true"
763
- : "false";
764
- break;
765
- case "description": // Get description
766
- description =
767
- property.type === "rich_text"
768
- ? property.rich_text
769
- ?.map((text) => text.plain_text)
770
- .reduce((a, b) => a + b, "")
771
- : "";
772
- break;
773
- }
774
- }
775
- });
776
- frontmatter += "title: " + JSON.stringify(title) + "\n";
777
- frontmatter += "description: " + JSON.stringify(description) + "\n";
778
- frontmatter += "tags: " + JSON.stringify(tags) + "\n";
779
- frontmatter += "pubDate: " + JSON.stringify(pubDate) + "\n";
780
- frontmatter += "archived: " + JSON.stringify(archived) + "\n";
781
- return "---\n" + frontmatter + "---\n";
782
- }
783
- }
784
-
785
- function getVersion() {
786
- try {
787
- const __filename = url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('generate.cjs', document.baseURI).href)));
788
- const __dirname = path.dirname(__filename);
789
- const packagePath = path.join(__dirname, "../../package.json");
790
- const packageJson = JSON.parse(fs$1.readFileSync(packagePath, "utf-8"));
791
- return packageJson.version;
792
- }
793
- catch {
794
- return "unknown";
795
- }
796
- }
797
- function help() {
798
- console.log(`
799
- noblog - Generate static blog content from Notion.so
800
-
801
- Usage: noblog [command] [options] [SAVE_PATH] [SUB_PATH]
802
-
803
- Options:
804
- -v, --verbose Print more messages for debugging
805
- -h, --help Show help message
806
- --version Show version information
807
- --layout PATH Custom layout path (default: ../../layouts/MarkdownPostLayout.astro)
808
-
809
- Commands:
810
- help Show this help message
811
- version Show version information
812
- generate Generate blog content (default command)
813
-
814
- Options:
815
- -v, --verbose Print more messages for debugging
816
- -h, --help Show help message
817
- --version Show version information
818
-
819
- Arguments:
820
- SAVE_PATH Directory to save main posts (default: ./src/pages/posts/)
821
- SUB_PATH Subdirectory for nested content (default: nob_children/)
822
-
823
- Layout Options:
824
- --layout PATH Custom layout path relative to save directory (default: ../../layouts/MarkdownPostLayout.astro)
825
-
826
- Examples:
827
- noblog help
828
- noblog version
829
- noblog generate
830
- noblog -v
831
- noblog src/pages/posts/ nob_children/
832
- noblog --layout ../../layouts/BlogLayout.astro
833
- noblog src/posts/ children/ --layout ../../../layouts/CustomLayout.astro -v
834
-
835
- Environment Variables:
836
- NOTION_API_SECRET Your Notion API secret
837
- NOBLOG_DATABASE_ID Your Notion database ID
838
- `);
839
- }
840
- function parseArgs(argv) {
841
- const options = {
842
- verbose: false,
843
- help: false,
844
- version: false,
845
- savePath: "./src/pages/posts/",
846
- subPath: "nob_children/",
847
- layoutPath: "../../layouts/MarkdownPostLayout.astro",
848
- };
849
- const positionalArgs = [];
850
- for (let i = 0; i < argv.length; i++) {
851
- const arg = argv[i];
852
- switch (arg) {
853
- case "-v":
854
- case "--verbose":
855
- options.verbose = true;
856
- break;
857
- case "-h":
858
- case "--help":
859
- case "help":
860
- options.help = true;
861
- break;
862
- case "--version":
863
- case "version":
864
- options.version = true;
865
- break;
866
- case "--layout":
867
- if (i + 1 < argv.length && !argv[i + 1].startsWith("-")) {
868
- options.layoutPath = argv[++i];
869
- }
870
- else {
871
- console.error("Error: --layout requires a path argument");
872
- help();
873
- process.exit(1);
874
- }
875
- break;
876
- case "generate":
877
- // Explicit generate command, skip it
878
- break;
879
- default:
880
- if (arg.startsWith("-")) {
881
- console.error(`Unknown option: ${arg}`);
882
- help();
883
- process.exit(1);
884
- }
885
- positionalArgs.push(arg);
886
- break;
887
- }
888
- }
889
- // Handle positional arguments
890
- if (positionalArgs.length > 0) {
891
- options.savePath = positionalArgs[0];
892
- }
893
- if (positionalArgs.length > 1) {
894
- options.subPath = positionalArgs[1];
895
- }
896
- if (positionalArgs.length > 2) {
897
- console.error("Error: Too many arguments.");
898
- help();
899
- process.exit(1);
900
- }
901
- return options;
902
- }
903
- async function generateBlog(options) {
904
- try {
905
- if (options.verbose) {
906
- console.log("Fetching posts from Notion...");
907
- }
908
- const pages = await ListPages();
909
- if (options.verbose) {
910
- console.log(`Found ${pages.length} pages`);
911
- console.log(`Save path: ${options.savePath}`);
912
- console.log(`Sub path: ${options.subPath}`);
913
- console.log(`Layout path: ${options.layoutPath}`);
914
- }
915
- const nob = new Noblog(pages, options.savePath, options.subPath, options.layoutPath);
916
- await nob.Collect(true);
917
- console.log(`✅ Successfully generated blog content`);
918
- }
919
- catch (error) {
920
- console.error(`❌ Error generating blog content:`);
921
- if (error instanceof Error) {
922
- console.error(` ${error.message}`);
923
- if (options.verbose) {
924
- console.error(error.stack);
925
- }
926
- }
927
- else {
928
- console.error(` An unknown error occurred`);
929
- }
930
- process.exit(1);
931
- }
932
- }
933
- async function main() {
934
- const argv = process.argv.slice(2);
935
- if (argv.length === 0) {
936
- // Default behavior: generate with default options
937
- await generateBlog({
938
- verbose: false,
939
- help: false,
940
- version: false,
941
- savePath: "./src/pages/posts/",
942
- subPath: "nob_children/",
943
- layoutPath: "../../layouts/MarkdownPostLayout.astro",
944
- });
945
- return;
946
- }
947
- const options = parseArgs(argv);
948
- if (options.help) {
949
- help();
950
- return;
951
- }
952
- if (options.version) {
953
- console.log(`noblog v${getVersion()}`);
954
- return;
955
- }
956
- await generateBlog(options);
957
- }
958
- main().catch((error) => {
959
- console.error(`❌ Unexpected error:`, error);
960
- process.exit(1);
961
- });
962
- //# sourceMappingURL=generate.cjs.map