noblog 0.0.10 → 0.0.12

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,13 +1,79 @@
1
1
  #!/usr/bin/env node
2
2
  import * as path from 'path';
3
3
  import * as fs from 'fs/promises';
4
- import { NotionToMarkdown } from 'notion-to-md';
5
- import 'node-fetch';
6
- import 'markdown-table';
4
+ import fetch from 'node-fetch';
5
+ import { markdownTable } from 'markdown-table';
7
6
  import 'dotenv/config';
8
7
  import retry from 'async-retry';
9
8
  import { Client, APIResponseError } from '@notionhq/client';
10
9
 
10
+ const inlineEquation = (text) => {
11
+ return `$${text}$`;
12
+ };
13
+ const link = (text, href) => {
14
+ return `[${text}](${href})`;
15
+ };
16
+ const codeBlock = (text, language) => {
17
+ if (language === "plain text")
18
+ language = "text";
19
+ return `\`\`\`${language}
20
+ ${text}
21
+ \`\`\``;
22
+ };
23
+ const equation = (text) => {
24
+ return `$$
25
+ ${text}
26
+ $$`;
27
+ };
28
+ const heading1 = (text) => {
29
+ return `# ${text}`;
30
+ };
31
+ const heading2 = (text) => {
32
+ return `## ${text}`;
33
+ };
34
+ const heading3 = (text) => {
35
+ return `### ${text}`;
36
+ };
37
+ const quote = (text) => {
38
+ // the replace is done to handle multiple lines
39
+ return `> ${text.replace(/\n/g, " \n> ")}`;
40
+ };
41
+ const callout = (text, icon) => {
42
+ let emoji;
43
+ if (icon?.type === "emoji") {
44
+ emoji = icon.emoji;
45
+ }
46
+ // the replace is done to handle multiple lines
47
+ return `> ${emoji ? emoji + " " : ""}${text.replace(/\n/g, " \n> ")}`;
48
+ };
49
+ const bullet = (text, count) => {
50
+ let renderText = text.trim();
51
+ return count ? `${count}. ${renderText}` : `- ${renderText}`;
52
+ };
53
+ const todo = (text, checked) => {
54
+ return checked ? `- [x] ${text}` : `- [ ] ${text}`;
55
+ };
56
+ const image = async (alt, href, convertToBase64 = false) => {
57
+ // In case the user does not want to convert the images to Base64
58
+ // or the image is already base64
59
+ if (!convertToBase64 || href.startsWith("data:")) {
60
+ if (href.startsWith("data:")) {
61
+ // Extract base64 data, i.e. the string after 'data:mime/type;base64,'
62
+ const base64 = href.split(",").pop();
63
+ // This overrides incorrect data: string format to png
64
+ // so that browsers can correctly render the data
65
+ return `![${alt}](data:image/png;base64,${base64})`;
66
+ }
67
+ return `![${alt}](${href})`;
68
+ }
69
+ else {
70
+ // Otherwise, download the image and convert it to base64
71
+ const res = await fetch(href);
72
+ const buf = await res.arrayBuffer();
73
+ const base64 = Buffer.from(buf).toString("base64");
74
+ return `![${alt}](data:image/png;base64,${base64})`;
75
+ }
76
+ };
11
77
  const addTabSpace = (text, n = 0) => {
12
78
  const tab = " ";
13
79
  for (let i = 0; i < n; i++) {
@@ -20,6 +86,9 @@ const addTabSpace = (text, n = 0) => {
20
86
  }
21
87
  return text;
22
88
  };
89
+ const divider = () => {
90
+ return "---";
91
+ };
23
92
  const toggle = (summary, children) => {
24
93
  if (!summary)
25
94
  return children || "";
@@ -28,9 +97,12 @@ const toggle = (summary, children) => {
28
97
  ${children || ""}
29
98
  </details>\n\n`;
30
99
  };
100
+ const table = (cells) => {
101
+ return markdownTable(cells);
102
+ };
31
103
 
32
- const NOTION_API_SECRET = process.env.NOTION_API_SECRET ?? '';
33
- const NOBLOG_DATABASE_ID = process.env.NOBLOG_DATABASE_ID ?? '';
104
+ const NOTION_API_SECRET = process.env.NOTION_API_SECRET ?? "";
105
+ const NOBLOG_DATABASE_ID = process.env.NOBLOG_DATABASE_ID ?? "";
34
106
  const client = new Client({
35
107
  auth: NOTION_API_SECRET,
36
108
  });
@@ -39,15 +111,19 @@ async function GetAllPosts() {
39
111
  const params = {
40
112
  database_id: NOBLOG_DATABASE_ID,
41
113
  filter: {
42
- and: [{
43
- property: 'publish',
44
- checkbox: { equals: true, },
45
- }]
114
+ and: [
115
+ {
116
+ property: "publish",
117
+ checkbox: { equals: true },
118
+ },
119
+ ],
46
120
  },
47
- sorts: [{
48
- property: 'date',
49
- direction: 'descending',
50
- }],
121
+ sorts: [
122
+ {
123
+ property: "date",
124
+ direction: "descending",
125
+ },
126
+ ],
51
127
  page_size: 100,
52
128
  };
53
129
  let results = [];
@@ -64,12 +140,12 @@ async function GetAllPosts() {
64
140
  }
65
141
  throw error;
66
142
  }
67
- }, { retries: numberOfRetry, });
143
+ }, { retries: numberOfRetry });
68
144
  results = results.concat(res.results);
69
145
  if (!res.has_more) {
70
146
  break;
71
147
  }
72
- params['start_cursor'] = res.next_cursor;
148
+ params["start_cursor"] = res.next_cursor;
73
149
  }
74
150
  return results;
75
151
  }
@@ -86,18 +162,48 @@ async function GetPageMeta(page_id) {
86
162
  }
87
163
  throw error;
88
164
  }
89
- }, { retries: numberOfRetry, });
165
+ }, { retries: numberOfRetry });
166
+ return result;
167
+ }
168
+ async function GetBlockChildren(block_id, totalPage) {
169
+ let result = [];
170
+ let pageCount = 0;
171
+ let start_cursor = undefined;
172
+ do {
173
+ const response = (await client.blocks.children.list({
174
+ start_cursor: start_cursor,
175
+ block_id: block_id,
176
+ }));
177
+ result.push(...response.results);
178
+ start_cursor = response?.next_cursor;
179
+ pageCount += 1;
180
+ } while (start_cursor != null &&
181
+ (totalPage == null || pageCount < totalPage));
182
+ ModifyNumberedListObject(result);
90
183
  return result;
91
184
  }
185
+ function ModifyNumberedListObject(blocks) {
186
+ let numberedListIndex = 0;
187
+ for (const block of blocks) {
188
+ if ("type" in block && block.type === "numbered_list_item") {
189
+ // add numbers
190
+ // @ts-ignore
191
+ block.numbered_list_item.number = ++numberedListIndex;
192
+ }
193
+ else {
194
+ numberedListIndex = 0;
195
+ }
196
+ }
197
+ }
92
198
 
93
- class Noblog extends NotionToMarkdown {
199
+ class Noblog {
94
200
  Posts = [];
95
201
  CurrPage = "";
96
202
  SavePath = "./src/pages/posts/";
97
203
  ChildPath = "nob_children/";
98
204
  MdCollection = {};
205
+ CustomTransformers = {};
99
206
  constructor(posts, save_path, child_path) {
100
- super({ notionClient: client });
101
207
  this.Posts = posts;
102
208
  if (child_path != undefined) {
103
209
  this.ChildPath = child_path;
@@ -105,21 +211,21 @@ class Noblog extends NotionToMarkdown {
105
211
  if (save_path != undefined) {
106
212
  this.SavePath = save_path;
107
213
  }
108
- this.setCustomTransformer("child_page", async (block) => {
214
+ this.CustomTransformers["child_page"] = async (block) => {
109
215
  const page = block;
110
216
  if (!page?.id) {
111
217
  return false;
112
218
  }
113
219
  let sub_path;
114
- // if (curr_page.id in this.Post) and (page.id in this.Post)
115
- // if (curr_page.id not in this.Post) and (page.id not in this.Post)
220
+ // if (curr_page.id in this.Post) and (page.id in this.Post) ||
221
+ // (curr_page.id not in this.Post) and (page.id not in this.Post)
116
222
  // * path should be "../pageid"
117
223
  // if (curr_page.id in this.Post) and (page.id not in this.Post)
118
224
  // * path should be "../save_path/pageid"
119
225
  // if (curr_page.id not in this.Post) and (page.id in this.Post)
120
226
  // * path should be "../../pageid"
121
- const parent_surface = (this.Posts.filter(post => post.id === this.CurrPage).length > 0);
122
- const child_surface = (this.Posts.filter(post => post.id === page.id).length > 0);
227
+ const parent_surface = this.Posts.filter((post) => post.id === this.CurrPage).length > 0;
228
+ const child_surface = this.Posts.filter((post) => post.id === page.id).length > 0;
123
229
  if (parent_surface === child_surface) {
124
230
  sub_path = "";
125
231
  }
@@ -131,7 +237,7 @@ class Noblog extends NotionToMarkdown {
131
237
  }
132
238
  const full_path = path.join("../", sub_path, page.id);
133
239
  return `&[${page.child_page.title}](${full_path})`;
134
- });
240
+ };
135
241
  }
136
242
  async Collect(recur = true) {
137
243
  const save_dir = this.SavePath;
@@ -142,7 +248,7 @@ class Noblog extends NotionToMarkdown {
142
248
  try {
143
249
  for (const pageid of Object.keys(this.MdCollection)) {
144
250
  let fpath = sub_dir;
145
- if (this.Posts.find(page => page.id === pageid))
251
+ if (this.Posts.find((page) => page.id === pageid))
146
252
  fpath = save_dir;
147
253
  await this.SaveJelly(pageid + ".md", fpath, this.MdCollection[pageid]);
148
254
  }
@@ -153,7 +259,7 @@ class Noblog extends NotionToMarkdown {
153
259
  console.log(`failed: consider run verbose with v=true - ${error.message}`);
154
260
  }
155
261
  else {
156
- console.log('failed: an unknown error occurred');
262
+ console.log("failed: an unknown error occurred");
157
263
  }
158
264
  }
159
265
  }
@@ -172,7 +278,8 @@ class Noblog extends NotionToMarkdown {
172
278
  block.type !== "numbered_list_item") {
173
279
  line_wrap = "\n";
174
280
  }
175
- jelly.content += (line_wrap + `${addTabSpace(block.parent, 0)}\n` + line_wrap);
281
+ jelly.content +=
282
+ line_wrap + `${addTabSpace(block.parent, 0)}\n` + line_wrap;
176
283
  if (block.type === "child_page") {
177
284
  jelly.children = [...jelly.children, block.blockId];
178
285
  if (recursive)
@@ -192,23 +299,326 @@ class Noblog extends NotionToMarkdown {
192
299
  if (this.MdCollection.hasOwnProperty(page_id)) {
193
300
  return this.MdCollection[page_id];
194
301
  }
195
- const blocks = await this.pageToMarkdown(page_id);
302
+ const blocks = await this.PageToMarkdown(page_id);
196
303
  const jelly = await this.FromBlocks(blocks, recursive);
197
304
  const meta = await GetPageMeta(page_id);
198
305
  const astro_meta = await this.AssembleAstroFrontmatter(meta);
199
306
  jelly.content = astro_meta + jelly.content;
200
307
  this.MdCollection[page_id] = jelly;
201
- // restore the prev page info
308
+ // restore the prev page info
202
309
  this.CurrPage = prev_page;
203
310
  return jelly;
204
311
  }
312
+ async PageToMarkdown(id, totalPage) {
313
+ if (!client) {
314
+ throw new Error("notion client is not provided, for more details check out https://github.com/souvikinator/notion-to-md");
315
+ }
316
+ const blocks = await GetBlockChildren(id, totalPage);
317
+ const parsedData = await this.BlocksToMarkdown(blocks);
318
+ return parsedData;
319
+ }
320
+ async BlocksToMarkdown(blocks, totalPage, mdBlocks) {
321
+ if (!client) {
322
+ throw new Error("notion client is not provided, for more details check out https://github.com/souvikinator/notion-to-md");
323
+ }
324
+ if (!blocks)
325
+ return mdBlocks;
326
+ for (let i = 0; i < blocks.length; i++) {
327
+ let block = blocks[i];
328
+ if (
329
+ // @ts-ignore
330
+ block.type === "unsupported" ||
331
+ // @ts-ignore
332
+ (block.type === "child_page" && !this.config.parseChildPages)) {
333
+ continue;
334
+ }
335
+ if ("has_children" in block && block.has_children) {
336
+ const block_id = block.type == "synced_block" &&
337
+ block.synced_block?.synced_from?.block_id
338
+ ? block.synced_block.synced_from.block_id
339
+ : block.id;
340
+ // Get children of this block.
341
+ let child_blocks = await GetBlockChildren(block_id, totalPage);
342
+ // Recursively call BlocksToMarkdown to get children of this block.
343
+ // check for custom transformer before parsing child
344
+ if (!(block.type in this.CustomTransformers) &&
345
+ !this.CustomTransformers[block.type]) {
346
+ let l = mdBlocks.length;
347
+ if (mdBlocks !== undefined) {
348
+ await this.BlocksToMarkdown(child_blocks, totalPage, mdBlocks[l - 1]?.children);
349
+ }
350
+ }
351
+ // Push this block to mdBlocks.
352
+ mdBlocks.push({
353
+ type: block.type,
354
+ blockId: block.id,
355
+ parent: await this.BlockToMarkdown(block),
356
+ children: [],
357
+ });
358
+ continue;
359
+ }
360
+ let tmp = await this.BlockToMarkdown(block);
361
+ mdBlocks.push({
362
+ // @ts-ignore
363
+ type: block.type,
364
+ blockId: block.id,
365
+ parent: tmp,
366
+ children: [],
367
+ });
368
+ }
369
+ return mdBlocks;
370
+ }
371
+ async BlockToMarkdown(block) {
372
+ if (typeof block !== "object" || !("type" in block))
373
+ return "";
374
+ let parsedData = "";
375
+ const { type } = block;
376
+ if (type in this.CustomTransformers && !!this.CustomTransformers[type]) {
377
+ const transformer = this.CustomTransformers[type];
378
+ const customTransformerValue = transformer(block);
379
+ if (typeof customTransformerValue === "string")
380
+ return customTransformerValue;
381
+ }
382
+ switch (type) {
383
+ case "image":
384
+ let blockContent = block.image;
385
+ let image_title = "image";
386
+ const image_caption_plain = blockContent.caption
387
+ .map((item) => item.plain_text)
388
+ .join("");
389
+ const image_type = blockContent.type;
390
+ let link$1 = "";
391
+ if (image_type === "external") {
392
+ link$1 = blockContent.external.url;
393
+ }
394
+ if (image_type === "file") {
395
+ link$1 = blockContent.file.url;
396
+ }
397
+ // image caption with high priority
398
+ if (image_caption_plain.trim().length > 0) {
399
+ image_title = image_caption_plain;
400
+ }
401
+ else if (image_type === "file" || image_type === "external") {
402
+ const matches = link$1.match(/[^\/\\&\?]+\.\w{3,4}(?=([\?&].*$|$))/);
403
+ image_title = matches ? matches[0] : image_title;
404
+ }
405
+ return await image(image_title, link$1, false);
406
+ case "divider":
407
+ return divider();
408
+ case "equation":
409
+ return equation(block.equation.expression);
410
+ case "video":
411
+ case "file":
412
+ case "pdf":
413
+ {
414
+ let blockContent;
415
+ let title = type;
416
+ if (type === "video")
417
+ blockContent = block.video;
418
+ if (type === "file")
419
+ blockContent = block.file;
420
+ if (type === "pdf")
421
+ blockContent = block.pdf;
422
+ const caption = blockContent?.caption
423
+ .map((item) => item.plain_text)
424
+ .join("");
425
+ if (blockContent) {
426
+ const file_type = blockContent.type;
427
+ let link$1 = "";
428
+ if (file_type === "external")
429
+ link$1 = blockContent.external.url;
430
+ if (file_type === "file")
431
+ link$1 = blockContent.file.url;
432
+ if (caption && caption.trim().length > 0) {
433
+ title = caption;
434
+ }
435
+ else if (link$1) {
436
+ const matches = link$1.match(/[^\/\\&\?]+\.\w{3,4}(?=([\?&].*$|$))/);
437
+ title = matches ? matches[0] : type;
438
+ }
439
+ return link(title, link$1);
440
+ }
441
+ }
442
+ break;
443
+ case "bookmark":
444
+ case "embed":
445
+ case "link_preview":
446
+ case "link_to_page":
447
+ {
448
+ let blockContent;
449
+ let title = type;
450
+ if (type === "bookmark")
451
+ blockContent = block.bookmark;
452
+ if (type === "embed")
453
+ blockContent = block.embed;
454
+ if (type === "link_preview")
455
+ blockContent = block.link_preview;
456
+ if (type === "link_to_page" &&
457
+ block.link_to_page.type === "page_id") {
458
+ blockContent = {
459
+ url: `https://www.notion.so/${block.link_to_page.page_id}`,
460
+ };
461
+ }
462
+ if (blockContent)
463
+ return link(title, blockContent.url);
464
+ }
465
+ break;
466
+ case "child_page": {
467
+ let pageTitle = block.child_page.title;
468
+ return heading2(pageTitle);
469
+ }
470
+ case "child_database": {
471
+ let pageTitle = block.child_database.title || `child_database`;
472
+ return heading2(pageTitle);
473
+ }
474
+ case "table": {
475
+ const { id, has_children } = block;
476
+ let tableArr = [];
477
+ if (has_children) {
478
+ const tableRows = await GetBlockChildren(id, 100);
479
+ let rowsPromise = tableRows?.map(async (row) => {
480
+ const { type } = row;
481
+ const cells = row[type]["cells"];
482
+ /**
483
+ * this is more like a hack since matching the type text was
484
+ * difficult. So converting each cell to paragraph type to
485
+ * reuse the BlockToMarkdown function
486
+ */
487
+ let cellStringPromise = cells.map(async (cell) => await this.BlockToMarkdown({
488
+ type: "paragraph",
489
+ paragraph: { rich_text: cell },
490
+ }));
491
+ const cellStringArr = await Promise.all(cellStringPromise);
492
+ tableArr.push(cellStringArr);
493
+ });
494
+ await Promise.all(rowsPromise || []);
495
+ }
496
+ return table(tableArr);
497
+ }
498
+ // Rest of the types:
499
+ // "paragraph" "heading_1" "heading_2" "heading_3"
500
+ // "bulleted_list_item" "numbered_list_item"
501
+ // "quote" "to_do" "template" "synced_block"
502
+ // "child_page" "child_database" "code" "callout" "breadcrumb"
503
+ // "table_of_contents" "link_to_page" "audio" "unsupported"
504
+ default: {
505
+ // @ts-ignore
506
+ let blockContent = block[type].text || block[type].rich_text || [];
507
+ let bucket = [];
508
+ let code_range = [];
509
+ let bold_range = [];
510
+ let italic_range = [];
511
+ let underline_range = [];
512
+ let strikethrough_range = [];
513
+ const push_or_merge = (arr, idx) => {
514
+ if (arr.length == 0) {
515
+ return;
516
+ }
517
+ // @ts-ignore
518
+ if (idx === arr[arr.length - 1][1]) {
519
+ // @ts-ignore
520
+ arr[arr.length - 1][1] += 1;
521
+ }
522
+ else {
523
+ arr.push([idx, idx + 1]);
524
+ }
525
+ };
526
+ for (let i = 0; i < blockContent.length; i++) {
527
+ let content = blockContent[i];
528
+ if (content.type === "equation") {
529
+ bucket.push(inlineEquation(content.equation.expression));
530
+ continue;
531
+ }
532
+ let plain_text = content.plain_text;
533
+ if (content["href"])
534
+ plain_text = link(plain_text, content["href"]);
535
+ bucket.push(plain_text);
536
+ const annotations = content.annotations;
537
+ if (annotations.code)
538
+ push_or_merge(code_range, i);
539
+ if (annotations.bold)
540
+ push_or_merge(bold_range, i);
541
+ if (annotations.italic)
542
+ push_or_merge(italic_range, i);
543
+ if (annotations.underline)
544
+ push_or_merge(underline_range, i);
545
+ if (annotations.strikethrough)
546
+ push_or_merge(strikethrough_range, i);
547
+ }
548
+ let annoed_bucket = [];
549
+ for (let range of code_range)
550
+ annoed_bucket.push({ range: range, type: "code" });
551
+ for (let range of bold_range)
552
+ annoed_bucket.push({ range: range, type: "bold" });
553
+ for (let range of italic_range)
554
+ annoed_bucket.push({ range: range, type: "italic" });
555
+ for (let range of underline_range)
556
+ annoed_bucket.push({ range: range, type: "underline" });
557
+ for (let range of strikethrough_range)
558
+ annoed_bucket.push({ range: range, type: "strikethrough" });
559
+ annoed_bucket.sort(
560
+ // @ts-ignore
561
+ (a, b) => a.range[1] - a.range[0] - (b.range[1] - b.range[0]));
562
+ for (let annoed of annoed_bucket) {
563
+ annoed.type;
564
+ let [from, to] = annoed.range;
565
+ // @ts-ignore
566
+ bucket[from] = annotation + bucket[from];
567
+ // @ts-ignore
568
+ bucket[to - 1] = bucket[to - 1] + annotation;
569
+ }
570
+ bucket.forEach((x) => (parsedData += x));
571
+ }
572
+ }
573
+ switch (type) {
574
+ case "code":
575
+ parsedData = codeBlock(parsedData, block[type].language);
576
+ break;
577
+ case "heading_1":
578
+ parsedData = heading1(parsedData);
579
+ break;
580
+ case "heading_2":
581
+ parsedData = heading2(parsedData);
582
+ break;
583
+ case "heading_3":
584
+ parsedData = heading3(parsedData);
585
+ break;
586
+ case "quote":
587
+ parsedData = quote(parsedData);
588
+ break;
589
+ case "callout":
590
+ const { id, has_children } = block;
591
+ let callout_string = "";
592
+ if (!has_children) {
593
+ return callout(parsedData, block[type].icon);
594
+ }
595
+ const callout_children_object = await GetBlockChildren(id, 100);
596
+ const callout_children = await this.BlocksToMarkdown(callout_children_object);
597
+ callout_string += `${parsedData}\n`;
598
+ callout_children.map((child) => {
599
+ callout_string += `${child.parent}\n\n`;
600
+ });
601
+ parsedData = callout(callout_string.trim(), block[type].icon);
602
+ break;
603
+ case "bulleted_list_item":
604
+ parsedData = bullet(parsedData);
605
+ break;
606
+ case "numbered_list_item":
607
+ parsedData = bullet(parsedData, block.numbered_list_item.number);
608
+ break;
609
+ case "to_do":
610
+ parsedData = todo(parsedData, block.to_do.checked);
611
+ break;
612
+ }
613
+ return parsedData;
614
+ }
205
615
  async SaveJelly(fname, dir, jelly) {
206
616
  if (jelly === undefined)
207
617
  return `failed: ${fname} has empty jelly: `;
208
618
  try {
209
619
  await fs.mkdir(dir, { recursive: true });
210
620
  const filePath = path.join(dir, fname);
211
- await fs.writeFile(filePath, jelly.content, 'utf-8');
621
+ await fs.writeFile(filePath, jelly.content, "utf-8");
212
622
  return `success: ${fname} saved to ${filePath}`;
213
623
  }
214
624
  catch (error) {
@@ -216,25 +626,40 @@ class Noblog extends NotionToMarkdown {
216
626
  return `failed: ${fname} - ${error.message}`;
217
627
  }
218
628
  else {
219
- return 'failed: an unknown error occurred';
629
+ return "failed: an unknown error occurred";
220
630
  }
221
631
  }
222
632
  }
223
633
  async AssembleAstroFrontmatter(page) {
224
634
  let frontmatter = "";
225
635
  // default layout
226
- const ischild = this.Posts.filter(post => post.id === page.id).length > 0 ? "" : "../";
227
- frontmatter += "layout: " + ischild + "../../layouts/MarkdownPostLayout.astro" + "\n";
636
+ const ischild = this.Posts.filter((post) => post.id === page.id).length > 0 ? "" : "../";
637
+ frontmatter +=
638
+ "layout: " + ischild + "../../layouts/MarkdownPostLayout.astro" + "\n";
228
639
  // get title
229
- frontmatter += "title: " + JSON.stringify(page.properties.title?.title?.[0]?.plain_text ?? "") + "\n";
640
+ frontmatter +=
641
+ "title: " +
642
+ JSON.stringify(page.properties.title?.title?.[0]?.plain_text ?? "") +
643
+ "\n";
230
644
  // get tags
231
- frontmatter += "tags: " + JSON.stringify((page.properties.tags?.multi_select ?? []).map(tag => tag.name)) + "\n";
645
+ frontmatter +=
646
+ "tags: " +
647
+ JSON.stringify((page.properties.tags?.multi_select ?? []).map((tag) => tag.name)) +
648
+ "\n";
232
649
  // get date
233
- frontmatter += "pubDate: " + (page.properties.date?.date?.start ?? new Date().toISOString().split("T")[0]) + "\n";
650
+ frontmatter +=
651
+ "pubDate: " +
652
+ (page.properties.date?.date?.start ??
653
+ new Date().toISOString().split("T")[0]) +
654
+ "\n";
234
655
  // get archived
235
- frontmatter += "archived: " + (page.properties.archived ? "true" : "false") + "\n";
656
+ frontmatter +=
657
+ "archived: " + (page.properties.archived ? "true" : "false") + "\n";
236
658
  // get description
237
- frontmatter += "description: " + JSON.stringify(page.properties.description?.rich_text?.[0]?.plain_text ?? "") + "\n";
659
+ frontmatter +=
660
+ "description: " +
661
+ JSON.stringify(page.properties.description?.rich_text?.[0]?.plain_text ?? "") +
662
+ "\n";
238
663
  return "---\n" + frontmatter + "---\n";
239
664
  }
240
665
  }