noblog 0.0.13 → 0.2.0-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/generate.js CHANGED
@@ -1,11 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import * as path from 'path';
3
+ import { dirname, join } from 'path';
3
4
  import * as fs from 'fs/promises';
4
5
  import fetch from 'node-fetch';
5
6
  import { markdownTable } from 'markdown-table';
6
7
  import 'dotenv/config';
7
8
  import retry from 'async-retry';
8
- import { Client, APIResponseError } from '@notionhq/client';
9
+ import { Client, isNotionClientError } from '@notionhq/client';
10
+ import { readFileSync } from 'fs';
11
+ import { fileURLToPath } from 'url';
9
12
 
10
13
  const suffixInlineCode = "`";
11
14
  const prefixInlineCode = "`";
@@ -133,9 +136,12 @@ const client = new Client({
133
136
  auth: NOTION_API_SECRET,
134
137
  });
135
138
  const numberOfRetry = 2;
136
- async function GetAllPosts() {
137
- const params = {
139
+ async function ListPages() {
140
+ const respDatabase = (await client.databases.retrieve({
138
141
  database_id: NOBLOG_DATABASE_ID,
142
+ }));
143
+ const reqDataSources = respDatabase.data_sources.map((source) => ({
144
+ data_source_id: source.id,
139
145
  filter: {
140
146
  and: [
141
147
  {
@@ -151,40 +157,41 @@ async function GetAllPosts() {
151
157
  },
152
158
  ],
153
159
  page_size: 100,
154
- };
160
+ }));
155
161
  let results = [];
156
- while (true) {
157
- const res = await retry(async (bail) => {
158
- try {
159
- return (await client.databases.query(params));
160
- }
161
- catch (error) {
162
- if (error instanceof APIResponseError) {
163
- if (error.status && error.status >= 400 && error.status < 500) {
162
+ for (const req of reqDataSources) {
163
+ let params = req;
164
+ while (true) {
165
+ const resp = await retry(async (bail) => {
166
+ try {
167
+ return client.dataSources.query(params);
168
+ }
169
+ catch (error) {
170
+ if (isNotionClientError(error)) {
164
171
  bail(error);
165
172
  }
173
+ throw error;
166
174
  }
167
- throw error;
175
+ }, { retries: numberOfRetry });
176
+ results = results.concat(resp.results);
177
+ if (!resp.has_more) {
178
+ break;
168
179
  }
169
- }, { retries: numberOfRetry });
170
- results = results.concat(res.results);
171
- if (!res.has_more) {
172
- break;
180
+ params.start_cursor = resp.next_cursor;
173
181
  }
174
- params["start_cursor"] = res.next_cursor;
175
182
  }
176
183
  return results;
177
184
  }
178
- async function GetPageMeta(page_id) {
185
+ async function GetPage(page_id) {
179
186
  const result = await retry(async (bail) => {
180
187
  try {
181
- return (await client.pages.retrieve({ page_id: page_id }));
188
+ return (await client.pages.retrieve({
189
+ page_id: page_id,
190
+ }));
182
191
  }
183
192
  catch (error) {
184
- if (error instanceof APIResponseError) {
185
- if (error.status && error.status >= 400 && error.status < 500) {
186
- bail(error);
187
- }
193
+ if (isNotionClientError(error)) {
194
+ bail(error);
188
195
  }
189
196
  throw error;
190
197
  }
@@ -192,7 +199,7 @@ async function GetPageMeta(page_id) {
192
199
  return result;
193
200
  }
194
201
  async function GetBlockChildren(block_id, totalPage) {
195
- const result = [];
202
+ const results = [];
196
203
  let pageCount = 0;
197
204
  let start_cursor = undefined;
198
205
  do {
@@ -200,13 +207,13 @@ async function GetBlockChildren(block_id, totalPage) {
200
207
  start_cursor: start_cursor,
201
208
  block_id: block_id,
202
209
  }));
203
- result.push(...response.results);
210
+ results.push(...response.results);
204
211
  start_cursor = response?.next_cursor;
205
212
  pageCount += 1;
206
213
  } while (start_cursor != null &&
207
214
  (totalPage == null || pageCount < totalPage));
208
- ModifyNumberedListObject(result);
209
- return result;
215
+ ModifyNumberedListObject(results);
216
+ return results;
210
217
  }
211
218
  function ModifyNumberedListObject(blocks) {
212
219
  let numberedListIndex = 0;
@@ -222,14 +229,66 @@ function ModifyNumberedListObject(blocks) {
222
229
  }
223
230
  }
224
231
 
232
+ const BLOCK_TO_DO = "to_do";
233
+ const BLOCK_TOGGLE = "toggle";
234
+ const BLOCK_CALLOUT = "callout";
235
+ const BLOCK_CHILD_PAGE = "child_page";
236
+ const BLOCK_UNSUPPORTED = "supported";
237
+ const BLOCK_SYNCED_BLOCK = "synced_block";
238
+ const BLOCK_NUMBERED_LIST_ITEM = "numbered_list_item";
239
+ const BLOCK_BULLETED_LIST_ITEM = "bulleted_list_item";
240
+ const BLOCK_IMAGE = "image";
241
+ const BLOCK_DIVIDER = "divider";
242
+ const BLOCK_EQUATION = "equation";
243
+ const BLOCK_PDF = "pdf";
244
+ const BLOCK_FILE = "file";
245
+ const BLOCK_VIDEO = "video";
246
+ const BLOCK_EMBED = "embed";
247
+ const BLOCK_BOOKMARK = "bookmark";
248
+ const BLOCK_LINK_PREVIEW = "link_preview";
249
+ const BLOCK_LINK_TO_PAGE = "link_to_page";
250
+ const BLOCK_CHILD_DATABASE = "child_database";
251
+ const BLOCK_TABLE = "table";
252
+ const BLOCK_CODE = "code";
253
+ const BLOCK_HEADING_1 = "heading_1";
254
+ const BLOCK_HEADING_2 = "heading_2";
255
+ const BLOCK_HEADING_3 = "heading_3";
256
+ const BLOCK_QUOTE = "quote";
257
+ const BLOCK_PARAGRAPH = "paragraph";
258
+ const LINEWRAP_NULL = "";
259
+ const LINEWRAP_NEWLINE = "\n";
260
+
261
+ async function SaveJelly(fname, dir, jelly, dry_run = false) {
262
+ if (jelly === undefined)
263
+ return `failed: ${fname} has empty jelly: `;
264
+ try {
265
+ if (dry_run) {
266
+ console.log(jelly.content);
267
+ return `success: ${fname} saved to ${dir}`;
268
+ }
269
+ await fs.mkdir(dir, { recursive: true });
270
+ const filePath = path.join(dir, fname);
271
+ await fs.writeFile(filePath, jelly.content, "utf-8");
272
+ return `success: ${fname} saved to ${filePath}`;
273
+ }
274
+ catch (error) {
275
+ if (error instanceof Error) {
276
+ return `failed: ${fname} - ${error.message}`;
277
+ }
278
+ else {
279
+ return "failed: an unknown error occurred";
280
+ }
281
+ }
282
+ }
225
283
  class Noblog {
226
284
  Posts = [];
227
285
  CurrPage = "";
228
286
  SavePath = "./src/pages/posts/";
229
287
  ChildPath = "nob_children/";
288
+ LayoutPath = "../../layouts/MarkdownPostLayout.astro";
230
289
  MdCollection = {};
231
290
  CustomTransformers = {};
232
- constructor(posts, save_path, child_path) {
291
+ constructor(posts, save_path, child_path, layout_path) {
233
292
  this.Posts = posts;
234
293
  if (child_path != undefined) {
235
294
  this.ChildPath = child_path;
@@ -237,12 +296,14 @@ class Noblog {
237
296
  if (save_path != undefined) {
238
297
  this.SavePath = save_path;
239
298
  }
240
- this.CustomTransformers["child_page"] = async (block) => {
299
+ if (layout_path != undefined) {
300
+ this.LayoutPath = layout_path;
301
+ }
302
+ this.CustomTransformers[BLOCK_CHILD_PAGE] = async (block) => {
241
303
  const page = block;
242
304
  if (!page?.id) {
243
305
  return false;
244
306
  }
245
- let sub_path;
246
307
  // if (curr_page.id in this.Post) and (page.id in this.Post) ||
247
308
  // (curr_page.id not in this.Post) and (page.id not in this.Post)
248
309
  // * path should be "../pageid"
@@ -250,6 +311,7 @@ class Noblog {
250
311
  // * path should be "../save_path/pageid"
251
312
  // if (curr_page.id not in this.Post) and (page.id in this.Post)
252
313
  // * path should be "../../pageid"
314
+ let sub_path;
253
315
  const parent_surface = this.Posts.filter((post) => post.id === this.CurrPage).length > 0;
254
316
  const child_surface = this.Posts.filter((post) => post.id === page.id).length > 0;
255
317
  if (parent_surface === child_surface) {
@@ -262,21 +324,22 @@ class Noblog {
262
324
  sub_path = "..";
263
325
  }
264
326
  const full_path = path.join("../", sub_path, page.id);
265
- return `&[${page.child_page.title}](${full_path})`;
327
+ return `(」゚ロ゚)」 [${page.child_page.title}](${full_path})`;
266
328
  };
267
329
  }
268
- async Collect(recur = true) {
330
+ // Collect converts all pages in database to markdown files
331
+ async Collect(recursive = true) {
269
332
  const save_dir = this.SavePath;
270
333
  const sub_dir = path.join(save_dir, this.ChildPath);
271
334
  for (const page of this.Posts) {
272
- await this.FromPageid(page.id, recur);
335
+ await this.FromPageId(page.id, recursive);
273
336
  }
274
337
  try {
275
- for (const pageid of Object.keys(this.MdCollection)) {
338
+ for (const page_id of Object.keys(this.MdCollection)) {
276
339
  let fpath = sub_dir;
277
- if (this.Posts.find((page) => page.id === pageid))
340
+ if (this.Posts.find((page) => page.id === page_id))
278
341
  fpath = save_dir;
279
- await this.SaveJelly(pageid + ".md", fpath, this.MdCollection[pageid]);
342
+ await SaveJelly(page_id + ".md", fpath, this.MdCollection[page_id]);
280
343
  }
281
344
  console.log(`success: finish dump all files to "${save_dir}"`);
282
345
  }
@@ -289,27 +352,34 @@ class Noblog {
289
352
  }
290
353
  }
291
354
  }
355
+ // FromBlocks converts blocks fetched from notion api to
356
+ // rendered chunk (which is called jelly). Content in jelly
357
+ // will be collected to form the final markdown. If recursive
358
+ // is true, it will also fetch child pages exists in the given
359
+ // blocks.
292
360
  async FromBlocks(blocks, recursive) {
293
361
  const jelly = { content: "", children: [] };
294
362
  for (const block of blocks) {
295
363
  const subjelly = await this.FromBlocks(block.children, recursive);
296
- if (block.type === "toggle") {
297
- jelly.content += toggle(block.parent, subjelly.content);
298
- jelly.children = [...jelly.children, ...subjelly.children];
299
- continue;
364
+ let line_wrap = LINEWRAP_NULL;
365
+ if (block.type !== BLOCK_TO_DO &&
366
+ block.type !== BLOCK_BULLETED_LIST_ITEM &&
367
+ block.type !== BLOCK_NUMBERED_LIST_ITEM) {
368
+ line_wrap = LINEWRAP_NEWLINE;
300
369
  }
301
- let line_wrap = "";
302
- if (block.type !== "to_do" &&
303
- block.type !== "bulleted_list_item" &&
304
- block.type !== "numbered_list_item") {
305
- line_wrap = "\n";
370
+ switch (block.type) {
371
+ case BLOCK_TOGGLE:
372
+ jelly.content += toggle(block.parent, subjelly.content);
373
+ jelly.children = [...jelly.children, ...subjelly.children];
374
+ continue;
375
+ default:
376
+ jelly.content +=
377
+ line_wrap + `${addTabSpace(block.parent, 0)}\n` + line_wrap;
306
378
  }
307
- jelly.content +=
308
- line_wrap + `${addTabSpace(block.parent, 0)}\n` + line_wrap;
309
- if (block.type === "child_page") {
379
+ if (block.type === BLOCK_CHILD_PAGE) {
310
380
  jelly.children = [...jelly.children, block.blockId];
311
381
  if (recursive)
312
- await this.FromPageid(block.blockId, recursive);
382
+ await this.FromPageId(block.blockId, recursive);
313
383
  }
314
384
  else {
315
385
  jelly.content += subjelly.content;
@@ -318,7 +388,11 @@ class Noblog {
318
388
  }
319
389
  return jelly;
320
390
  }
321
- async FromPageid(page_id, recursive = true) {
391
+ // FromPageid converts a page id to rendered chunk (which is
392
+ // called jelly). Content in jelly will be collected to form
393
+ // the final markdown. If recursive is true, it will also
394
+ // fetch child pages exists in the given page.
395
+ async FromPageId(page_id, recursive = true) {
322
396
  // preserve the curr page info
323
397
  const prev_page = this.CurrPage;
324
398
  this.CurrPage = page_id;
@@ -327,7 +401,7 @@ class Noblog {
327
401
  }
328
402
  const blocks = await this.PageToMarkdown(page_id);
329
403
  const jelly = await this.FromBlocks(blocks, recursive);
330
- const meta = await GetPageMeta(page_id);
404
+ const meta = await GetPage(page_id);
331
405
  const astro_meta = await this.AssembleAstroFrontmatter(meta);
332
406
  jelly.content = astro_meta + jelly.content;
333
407
  this.MdCollection[page_id] = jelly;
@@ -337,7 +411,7 @@ class Noblog {
337
411
  }
338
412
  async PageToMarkdown(id, totalPage) {
339
413
  if (!client) {
340
- throw new Error("notion client is not provided, for more details check out https://github.com/souvikinator/notion-to-md");
414
+ throw new Error("notion client is not provided");
341
415
  }
342
416
  const blocks = await GetBlockChildren(id, totalPage);
343
417
  const parsedData = await this.BlocksToMarkdown(blocks);
@@ -345,68 +419,64 @@ class Noblog {
345
419
  }
346
420
  async BlocksToMarkdown(blocks, totalPage = null, mdBlocks = []) {
347
421
  if (!client) {
348
- throw new Error("notion client is not provided, for more details check out https://github.com/souvikinator/notion-to-md");
422
+ throw new Error("notion client is not provided");
349
423
  }
350
- if (!mdBlocks)
351
- mdBlocks = [];
352
424
  if (!blocks)
353
425
  return mdBlocks;
354
- for (let i = 0; i < blocks.length; i++) {
355
- const block = blocks[i];
356
- if (
426
+ for (const block of blocks) {
357
427
  // @ts-ignore
358
- block.type === "unsupported") {
428
+ if (block.type === BLOCK_UNSUPPORTED) {
359
429
  continue;
360
430
  }
361
- if ("has_children" in block && block.has_children) {
362
- const block_id = block.type == "synced_block" &&
363
- block.synced_block?.synced_from?.block_id
364
- ? block.synced_block.synced_from.block_id
365
- : block.id;
366
- // Get children of this block.
367
- const child_blocks = await GetBlockChildren(block_id, totalPage);
368
- // Recursively call BlocksToMarkdown to get children of this block.
369
- // check for custom transformer before parsing child
370
- if (!(block.type in this.CustomTransformers) &&
371
- !this.CustomTransformers[block.type]) {
372
- const l = mdBlocks.length;
373
- if (mdBlocks !== undefined) {
374
- await this.BlocksToMarkdown(child_blocks, totalPage, mdBlocks[l - 1]?.children);
375
- }
376
- }
377
- // Push this block to mdBlocks.
431
+ if (!("has_children" in block) || !block.has_children) {
378
432
  mdBlocks.push({
433
+ // @ts-ignore
379
434
  type: block.type,
380
435
  blockId: block.id,
381
- parent: await this.BlockToMarkdown(block),
382
436
  children: [],
437
+ parent: await this.BlockToMarkdown(block),
383
438
  });
384
439
  continue;
385
440
  }
386
- const tmp = await this.BlockToMarkdown(block);
441
+ const block_id = block.type == BLOCK_SYNCED_BLOCK &&
442
+ block.synced_block?.synced_from?.block_id
443
+ ? block.synced_block.synced_from.block_id
444
+ : block.id;
445
+ // Get children of this block.
446
+ const child_blocks = await GetBlockChildren(block_id, totalPage);
447
+ // Push this block to mdBlocks.
387
448
  mdBlocks.push({
388
- // @ts-ignore
389
449
  type: block.type,
390
450
  blockId: block.id,
391
- parent: tmp,
451
+ parent: await this.BlockToMarkdown(block),
392
452
  children: [],
393
453
  });
454
+ if (block.type === BLOCK_CALLOUT) {
455
+ continue;
456
+ }
457
+ if (!(block.type in this.CustomTransformers)) {
458
+ continue;
459
+ }
460
+ // Recursively call BlocksToMarkdown to collect children. check for custom transformer before parsing child
461
+ if (mdBlocks !== undefined) {
462
+ await this.BlocksToMarkdown(child_blocks, totalPage, mdBlocks[mdBlocks.length - 1]?.children);
463
+ }
394
464
  }
395
465
  return mdBlocks;
396
466
  }
397
467
  async BlockToMarkdown(block) {
398
468
  if (typeof block !== "object" || !("type" in block))
399
469
  return "";
400
- let parsedData = "";
470
+ let renderedData = "";
401
471
  const { type } = block;
402
472
  if (type in this.CustomTransformers && !!this.CustomTransformers[type]) {
403
473
  const transformer = this.CustomTransformers[type];
404
- const customTransformerValue = transformer(block);
474
+ const customTransformerValue = await transformer(block);
405
475
  if (typeof customTransformerValue === "string")
406
476
  return customTransformerValue;
407
477
  }
408
478
  switch (type) {
409
- case "image":
479
+ case BLOCK_IMAGE:
410
480
  const blockContent = block.image;
411
481
  let image_title = "image";
412
482
  const image_caption_plain = blockContent.caption
@@ -429,21 +499,21 @@ class Noblog {
429
499
  image_title = matches ? matches[0] : image_title;
430
500
  }
431
501
  return await image(image_title, link$1, false);
432
- case "divider":
502
+ case BLOCK_DIVIDER:
433
503
  return divider();
434
- case "equation":
504
+ case BLOCK_EQUATION:
435
505
  return equation(block.equation.expression);
436
- case "pdf":
437
- case "file":
438
- case "video":
506
+ case BLOCK_PDF:
507
+ case BLOCK_FILE:
508
+ case BLOCK_VIDEO:
439
509
  {
440
510
  let blockContent;
441
511
  let title = type;
442
- if (type === "pdf")
512
+ if (type === BLOCK_PDF)
443
513
  blockContent = block.pdf;
444
- if (type === "file")
514
+ if (type === BLOCK_FILE)
445
515
  blockContent = block.file;
446
- if (type === "video")
516
+ if (type === BLOCK_VIDEO)
447
517
  blockContent = block.video;
448
518
  const caption = blockContent?.caption
449
519
  .map((item) => item.plain_text)
@@ -466,20 +536,20 @@ class Noblog {
466
536
  }
467
537
  }
468
538
  break;
469
- case "embed":
470
- case "bookmark":
471
- case "link_preview":
472
- case "link_to_page":
539
+ case BLOCK_EMBED:
540
+ case BLOCK_BOOKMARK:
541
+ case BLOCK_LINK_PREVIEW:
542
+ case BLOCK_LINK_TO_PAGE:
473
543
  {
474
544
  let blockContent;
475
545
  const title = type;
476
- if (type === "embed")
546
+ if (type === BLOCK_EMBED)
477
547
  blockContent = block.embed;
478
- if (type === "bookmark")
548
+ if (type === BLOCK_BOOKMARK)
479
549
  blockContent = block.bookmark;
480
- if (type === "link_preview")
550
+ if (type === BLOCK_LINK_PREVIEW)
481
551
  blockContent = block.link_preview;
482
- if (type === "link_to_page" &&
552
+ if (type === BLOCK_LINK_TO_PAGE &&
483
553
  block.link_to_page.type === "page_id") {
484
554
  blockContent = {
485
555
  url: `https://www.notion.so/${block.link_to_page.page_id}`,
@@ -489,15 +559,15 @@ class Noblog {
489
559
  return link(title, blockContent.url);
490
560
  }
491
561
  break;
492
- case "child_page": {
562
+ case BLOCK_CHILD_PAGE: {
493
563
  const pageTitle = block.child_page.title;
494
564
  return heading2(pageTitle);
495
565
  }
496
- case "child_database": {
566
+ case BLOCK_CHILD_DATABASE: {
497
567
  const pageTitle = block.child_database.title || `child_database`;
498
568
  return heading2(pageTitle);
499
569
  }
500
- case "table": {
570
+ case BLOCK_TABLE: {
501
571
  const { id, has_children } = block;
502
572
  const tableArr = [];
503
573
  if (has_children) {
@@ -511,7 +581,7 @@ class Noblog {
511
581
  * reuse the BlockToMarkdown function
512
582
  */
513
583
  const cellStringPromise = cells.map(async (cell) => await this.BlockToMarkdown({
514
- type: "paragraph",
584
+ type: BLOCK_PARAGRAPH,
515
585
  paragraph: { rich_text: cell },
516
586
  }));
517
587
  const cellStringArr = await Promise.all(cellStringPromise);
@@ -594,125 +664,275 @@ class Noblog {
594
664
  // @ts-ignore
595
665
  bucket[to - 1] = bucket[to - 1] + sanno;
596
666
  }
597
- bucket.forEach((x) => (parsedData += x));
667
+ bucket.forEach((x) => (renderedData += x));
598
668
  }
599
669
  }
600
670
  switch (type) {
601
- case "code":
602
- parsedData = codeBlock(parsedData, block[type].language);
671
+ case BLOCK_CODE:
672
+ renderedData = codeBlock(renderedData, block[type].language);
603
673
  break;
604
- case "heading_1":
605
- parsedData = heading1(parsedData);
674
+ case BLOCK_HEADING_1:
675
+ renderedData = heading1(renderedData);
606
676
  break;
607
- case "heading_2":
608
- parsedData = heading2(parsedData);
677
+ case BLOCK_HEADING_2:
678
+ renderedData = heading2(renderedData);
609
679
  break;
610
- case "heading_3":
611
- parsedData = heading3(parsedData);
680
+ case BLOCK_HEADING_3:
681
+ renderedData = heading3(renderedData);
612
682
  break;
613
- case "quote":
614
- parsedData = quote(parsedData);
683
+ case BLOCK_QUOTE:
684
+ renderedData = quote(renderedData);
615
685
  break;
616
- case "callout":
686
+ case BLOCK_CALLOUT:
617
687
  const { id, has_children } = block;
618
688
  let callout_string = "";
619
689
  if (!has_children) {
620
- return callout(parsedData, block[type].icon);
690
+ return callout(renderedData, block.callout.icon);
621
691
  }
622
692
  const callout_children_object = await GetBlockChildren(id, 100);
623
693
  const callout_children = await this.BlocksToMarkdown(callout_children_object);
624
- callout_string += `${parsedData}\n`;
625
- callout_children.map((child) => {
626
- callout_string += `${child.parent}\n\n`;
627
- });
628
- parsedData = callout(callout_string.trim(), block[type].icon);
694
+ callout_string += `${renderedData}\n`;
695
+ callout_string += (await this.FromBlocks(callout_children, true))
696
+ .content;
697
+ renderedData = callout(callout_string.trim(), block.callout.icon);
629
698
  break;
630
- case "bulleted_list_item":
631
- parsedData = bullet(parsedData);
699
+ case BLOCK_BULLETED_LIST_ITEM:
700
+ renderedData = bullet(renderedData);
632
701
  break;
633
- case "numbered_list_item":
634
- parsedData = bullet(parsedData, block.numbered_list_item.number);
702
+ case BLOCK_NUMBERED_LIST_ITEM:
703
+ renderedData = bullet(renderedData, block.numbered_list_item.number);
635
704
  break;
636
- case "to_do":
637
- parsedData = todo(parsedData, block.to_do.checked);
705
+ case BLOCK_TO_DO:
706
+ renderedData = todo(renderedData, block.to_do.checked);
638
707
  break;
639
708
  }
640
- return parsedData;
641
- }
642
- async SaveJelly(fname, dir, jelly) {
643
- if (jelly === undefined)
644
- return `failed: ${fname} has empty jelly: `;
645
- try {
646
- await fs.mkdir(dir, { recursive: true });
647
- const filePath = path.join(dir, fname);
648
- await fs.writeFile(filePath, jelly.content, "utf-8");
649
- return `success: ${fname} saved to ${filePath}`;
650
- }
651
- catch (error) {
652
- if (error instanceof Error) {
653
- return `failed: ${fname} - ${error.message}`;
654
- }
655
- else {
656
- return "failed: an unknown error occurred";
657
- }
658
- }
709
+ return renderedData;
659
710
  }
660
711
  async AssembleAstroFrontmatter(page) {
661
712
  let frontmatter = "";
662
- // default layout
713
+ // Default layout
663
714
  const ischild = this.Posts.filter((post) => post.id === page.id).length > 0 ? "" : "../";
664
- frontmatter +=
665
- "layout: " + ischild + "../../layouts/MarkdownPostLayout.astro" + "\n";
666
- // get title
667
- frontmatter +=
668
- "title: " +
669
- JSON.stringify(page.properties.title?.title?.[0]?.plain_text ?? "") +
670
- "\n";
671
- // get tags
672
- frontmatter +=
673
- "tags: " +
674
- JSON.stringify((page.properties.tags?.multi_select ?? []).map((tag) => tag.name)) +
675
- "\n";
676
- // get date
677
- frontmatter +=
678
- "pubDate: " +
679
- (page.properties.date?.date?.start ??
680
- new Date().toISOString().split("T")[0]) +
681
- "\n";
682
- // get archived
683
- frontmatter +=
684
- "archived: " + (page.properties.archived ? "true" : "false") + "\n";
685
- // get description
686
- frontmatter +=
687
- "description: " +
688
- JSON.stringify(page.properties.description?.rich_text?.[0]?.plain_text ?? "") +
689
- "\n";
715
+ frontmatter += "layout: " + ischild + this.LayoutPath + "\n";
716
+ Object.entries(page.properties).forEach(([key, property]) => {
717
+ switch (property.type) {
718
+ case "title": // Get title
719
+ frontmatter +=
720
+ "title: " +
721
+ JSON.stringify(property.title
722
+ ?.map((title) => title.plain_text)
723
+ .reduce((a, b) => a + b)) +
724
+ "\n";
725
+ break;
726
+ case "multi_select": // Get tags
727
+ frontmatter +=
728
+ "tags: " +
729
+ JSON.stringify(property.multi_select.map((tag) => tag.name)) +
730
+ "\n";
731
+ break;
732
+ case "date": // Get date
733
+ frontmatter +=
734
+ "pubDate: " +
735
+ (property.date?.start ?? new Date().toISOString().split("T")[0]) +
736
+ "\n";
737
+ break;
738
+ default: // Customized properties from the template given in the Github repo
739
+ switch (key) {
740
+ case "archived": // Get archived
741
+ if (property.type === "checkbox")
742
+ frontmatter +=
743
+ "archived: " + property.checkbox
744
+ ? "true"
745
+ : "false" + "\n";
746
+ break;
747
+ case "description": // Get description
748
+ if (property.type === "rich_text")
749
+ frontmatter +=
750
+ "description: " +
751
+ JSON.stringify(property.rich_text?.[0]?.plain_text ?? "") +
752
+ "\n";
753
+ break;
754
+ }
755
+ }
756
+ });
690
757
  return "---\n" + frontmatter + "---\n";
691
758
  }
692
759
  }
693
760
 
761
+ function getVersion() {
762
+ try {
763
+ const __filename = fileURLToPath(import.meta.url);
764
+ const __dirname = dirname(__filename);
765
+ const packagePath = join(__dirname, "../../package.json");
766
+ const packageJson = JSON.parse(readFileSync(packagePath, "utf-8"));
767
+ return packageJson.version;
768
+ }
769
+ catch {
770
+ return "unknown";
771
+ }
772
+ }
694
773
  function help() {
695
- console.log(`\n
696
- Usage: noblog [SAVE_PATH] [SUB_PATH]
774
+ console.log(`
775
+ noblog - Generate static blog content from Notion.so
776
+
777
+ Usage: noblog [command] [options] [SAVE_PATH] [SUB_PATH]
778
+
779
+ Options:
780
+ -v, --verbose Print more messages for debugging
781
+ -h, --help Show help message
782
+ --version Show version information
783
+ --layout PATH Custom layout path (default: ../../layouts/MarkdownPostLayout.astro)
784
+
785
+ Commands:
786
+ help Show this help message
787
+ version Show version information
788
+ generate Generate blog content (default command)
789
+
790
+ Options:
791
+ -v, --verbose Print more messages for debugging
792
+ -h, --help Show help message
793
+ --version Show version information
794
+
795
+ Arguments:
796
+ SAVE_PATH Directory to save main posts (default: ./src/pages/posts/)
797
+ SUB_PATH Subdirectory for nested content (default: nob_children/)
697
798
 
698
- Example:
699
- noblog
799
+ Layout Options:
800
+ --layout PATH Custom layout path relative to save directory (default: ../../layouts/MarkdownPostLayout.astro)
801
+
802
+ Examples:
803
+ noblog help
804
+ noblog version
805
+ noblog generate
806
+ noblog -v
700
807
  noblog src/pages/posts/ nob_children/
808
+ noblog --layout ../../layouts/BlogLayout.astro
809
+ noblog src/posts/ children/ --layout ../../../layouts/CustomLayout.astro -v
701
810
 
702
- Available <option>:
703
- -v Print more messages for debugging.\n`);
811
+ Environment Variables:
812
+ NOTION_API_SECRET Your Notion API secret
813
+ NOBLOG_DATABASE_ID Your Notion database ID
814
+ `);
815
+ }
816
+ function parseArgs(argv) {
817
+ const options = {
818
+ verbose: false,
819
+ help: false,
820
+ version: false,
821
+ savePath: "./src/pages/posts/",
822
+ subPath: "nob_children/",
823
+ layoutPath: "../../layouts/MarkdownPostLayout.astro",
824
+ };
825
+ const positionalArgs = [];
826
+ for (let i = 0; i < argv.length; i++) {
827
+ const arg = argv[i];
828
+ switch (arg) {
829
+ case "-v":
830
+ case "--verbose":
831
+ options.verbose = true;
832
+ break;
833
+ case "-h":
834
+ case "--help":
835
+ case "help":
836
+ options.help = true;
837
+ break;
838
+ case "--version":
839
+ case "version":
840
+ options.version = true;
841
+ break;
842
+ case "--layout":
843
+ if (i + 1 < argv.length && !argv[i + 1].startsWith("-")) {
844
+ options.layoutPath = argv[++i];
845
+ }
846
+ else {
847
+ console.error("Error: --layout requires a path argument");
848
+ help();
849
+ process.exit(1);
850
+ }
851
+ break;
852
+ case "generate":
853
+ // Explicit generate command, skip it
854
+ break;
855
+ default:
856
+ if (arg.startsWith("-")) {
857
+ console.error(`Unknown option: ${arg}`);
858
+ help();
859
+ process.exit(1);
860
+ }
861
+ positionalArgs.push(arg);
862
+ break;
863
+ }
864
+ }
865
+ // Handle positional arguments
866
+ if (positionalArgs.length > 0) {
867
+ options.savePath = positionalArgs[0];
868
+ }
869
+ if (positionalArgs.length > 1) {
870
+ options.subPath = positionalArgs[1];
871
+ }
872
+ if (positionalArgs.length > 2) {
873
+ console.error("Error: Too many arguments.");
874
+ help();
875
+ process.exit(1);
876
+ }
877
+ return options;
878
+ }
879
+ async function generateBlog(options) {
880
+ try {
881
+ if (options.verbose) {
882
+ console.log("Fetching posts from Notion...");
883
+ }
884
+ const pages = await ListPages();
885
+ if (options.verbose) {
886
+ console.log(`Found ${pages.length} pages`);
887
+ console.log(`Save path: ${options.savePath}`);
888
+ console.log(`Sub path: ${options.subPath}`);
889
+ console.log(`Layout path: ${options.layoutPath}`);
890
+ }
891
+ const nob = new Noblog(pages, options.savePath, options.subPath, options.layoutPath);
892
+ await nob.Collect(true);
893
+ console.log(`✅ Successfully generated blog content`);
894
+ }
895
+ catch (error) {
896
+ console.error(`❌ Error generating blog content:`);
897
+ if (error instanceof Error) {
898
+ console.error(` ${error.message}`);
899
+ if (options.verbose) {
900
+ console.error(error.stack);
901
+ }
902
+ }
903
+ else {
904
+ console.error(` An unknown error occurred`);
905
+ }
906
+ process.exit(1);
907
+ }
704
908
  }
705
909
  async function main() {
706
910
  const argv = process.argv.slice(2);
707
- if (argv.length > 2) {
708
- console.error("Error: invallid arg.");
911
+ if (argv.length === 0) {
912
+ // Default behavior: generate with default options
913
+ await generateBlog({
914
+ verbose: false,
915
+ help: false,
916
+ version: false,
917
+ savePath: "./src/pages/posts/",
918
+ subPath: "nob_children/",
919
+ layoutPath: "../../layouts/MarkdownPostLayout.astro",
920
+ });
921
+ return;
922
+ }
923
+ const options = parseArgs(argv);
924
+ if (options.help) {
709
925
  help();
926
+ return;
710
927
  }
711
- const save_dir = argv[0] ?? "./src/pages/posts/";
712
- const sub_dir = argv[1] ?? "nob_children/";
713
- const pages = await GetAllPosts();
714
- const nob = new Noblog(pages, save_dir, sub_dir);
715
- nob.Collect(true);
928
+ if (options.version) {
929
+ console.log(`noblog v${getVersion()}`);
930
+ return;
931
+ }
932
+ await generateBlog(options);
716
933
  }
717
- main();
934
+ main().catch((error) => {
935
+ console.error(`❌ Unexpected error:`, error);
936
+ process.exit(1);
937
+ });
718
938
  //# sourceMappingURL=generate.js.map