noblog 0.1.1 → 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/README.md +169 -48
- package/dist/generate.cjs +404 -184
- package/dist/generate.cjs.map +1 -1
- package/dist/generate.js +405 -185
- package/dist/generate.js.map +1 -1
- package/dist/index.d.ts +16 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/lib/{nomd → noblog}/format.d.ts +1 -16
- package/dist/lib/noblog/format.d.ts.map +1 -0
- package/dist/lib/noblog/noblog.d.ts +31 -0
- package/dist/lib/noblog/noblog.d.ts.map +1 -0
- package/dist/lib/noblog/utils.d.ts +36 -0
- package/dist/lib/noblog/utils.d.ts.map +1 -0
- package/dist/lib/notion/client.d.ts +4 -12
- package/dist/lib/notion/client.d.ts.map +1 -1
- package/package.json +11 -11
- package/dist/lib/nomd/format.d.ts.map +0 -1
- package/dist/lib/nomd/nomd.d.ts +0 -32
- package/dist/lib/nomd/nomd.d.ts.map +0 -1
- package/dist/lib/nomd/utils.d.ts +0 -6
- package/dist/lib/nomd/utils.d.ts.map +0 -1
- package/dist/lib/notion/object.d.ts +0 -148
- package/dist/lib/notion/object.d.ts.map +0 -1
- package/dist/lib/notion/request.d.ts +0 -36
- package/dist/lib/notion/request.d.ts.map +0 -1
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,
|
|
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
|
|
137
|
-
const
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
|
|
175
|
+
}, { retries: numberOfRetry });
|
|
176
|
+
results = results.concat(resp.results);
|
|
177
|
+
if (!resp.has_more) {
|
|
178
|
+
break;
|
|
168
179
|
}
|
|
169
|
-
|
|
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
|
|
185
|
+
async function GetPage(page_id) {
|
|
179
186
|
const result = await retry(async (bail) => {
|
|
180
187
|
try {
|
|
181
|
-
return (await client.pages.retrieve({
|
|
188
|
+
return (await client.pages.retrieve({
|
|
189
|
+
page_id: page_id,
|
|
190
|
+
}));
|
|
182
191
|
}
|
|
183
192
|
catch (error) {
|
|
184
|
-
if (error
|
|
185
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
209
|
-
return
|
|
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,7 +296,10 @@ class Noblog {
|
|
|
237
296
|
if (save_path != undefined) {
|
|
238
297
|
this.SavePath = save_path;
|
|
239
298
|
}
|
|
240
|
-
|
|
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;
|
|
@@ -265,18 +327,19 @@ class Noblog {
|
|
|
265
327
|
return `(」゚ロ゚)」 [${page.child_page.title}](${full_path})`;
|
|
266
328
|
};
|
|
267
329
|
}
|
|
268
|
-
|
|
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.
|
|
335
|
+
await this.FromPageId(page.id, recursive);
|
|
273
336
|
}
|
|
274
337
|
try {
|
|
275
|
-
for (const
|
|
338
|
+
for (const page_id of Object.keys(this.MdCollection)) {
|
|
276
339
|
let fpath = sub_dir;
|
|
277
|
-
if (this.Posts.find((page) => page.id ===
|
|
340
|
+
if (this.Posts.find((page) => page.id === page_id))
|
|
278
341
|
fpath = save_dir;
|
|
279
|
-
await
|
|
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
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
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
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
|
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,59 +419,55 @@ 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
|
|
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 (
|
|
355
|
-
const block = blocks[i];
|
|
356
|
-
if (
|
|
426
|
+
for (const block of blocks) {
|
|
357
427
|
// @ts-ignore
|
|
358
|
-
block.type ===
|
|
428
|
+
if (block.type === BLOCK_UNSUPPORTED) {
|
|
359
429
|
continue;
|
|
360
430
|
}
|
|
361
|
-
if ("has_children" in block
|
|
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
|
-
// Push this block to mdBlocks.
|
|
431
|
+
if (!("has_children" in block) || !block.has_children) {
|
|
369
432
|
mdBlocks.push({
|
|
433
|
+
// @ts-ignore
|
|
370
434
|
type: block.type,
|
|
371
435
|
blockId: block.id,
|
|
372
|
-
parent: await this.BlockToMarkdown(block),
|
|
373
436
|
children: [],
|
|
437
|
+
parent: await this.BlockToMarkdown(block),
|
|
374
438
|
});
|
|
375
|
-
// Recursively call BlocksToMarkdown to get children of this block.
|
|
376
|
-
// check for custom transformer before parsing child
|
|
377
|
-
if (!(block.type in this.CustomTransformers) &&
|
|
378
|
-
!this.CustomTransformers[block.type]) {
|
|
379
|
-
const l = mdBlocks.length;
|
|
380
|
-
if (mdBlocks !== undefined) {
|
|
381
|
-
await this.BlocksToMarkdown(child_blocks, totalPage, mdBlocks[l - 1]?.children);
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
439
|
continue;
|
|
385
440
|
}
|
|
386
|
-
const
|
|
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:
|
|
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
|
|
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];
|
|
@@ -406,7 +476,7 @@ class Noblog {
|
|
|
406
476
|
return customTransformerValue;
|
|
407
477
|
}
|
|
408
478
|
switch (type) {
|
|
409
|
-
case
|
|
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
|
|
502
|
+
case BLOCK_DIVIDER:
|
|
433
503
|
return divider();
|
|
434
|
-
case
|
|
504
|
+
case BLOCK_EQUATION:
|
|
435
505
|
return equation(block.equation.expression);
|
|
436
|
-
case
|
|
437
|
-
case
|
|
438
|
-
case
|
|
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 ===
|
|
512
|
+
if (type === BLOCK_PDF)
|
|
443
513
|
blockContent = block.pdf;
|
|
444
|
-
if (type ===
|
|
514
|
+
if (type === BLOCK_FILE)
|
|
445
515
|
blockContent = block.file;
|
|
446
|
-
if (type ===
|
|
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
|
|
470
|
-
case
|
|
471
|
-
case
|
|
472
|
-
case
|
|
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 ===
|
|
546
|
+
if (type === BLOCK_EMBED)
|
|
477
547
|
blockContent = block.embed;
|
|
478
|
-
if (type ===
|
|
548
|
+
if (type === BLOCK_BOOKMARK)
|
|
479
549
|
blockContent = block.bookmark;
|
|
480
|
-
if (type ===
|
|
550
|
+
if (type === BLOCK_LINK_PREVIEW)
|
|
481
551
|
blockContent = block.link_preview;
|
|
482
|
-
if (type ===
|
|
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
|
|
562
|
+
case BLOCK_CHILD_PAGE: {
|
|
493
563
|
const pageTitle = block.child_page.title;
|
|
494
564
|
return heading2(pageTitle);
|
|
495
565
|
}
|
|
496
|
-
case
|
|
566
|
+
case BLOCK_CHILD_DATABASE: {
|
|
497
567
|
const pageTitle = block.child_database.title || `child_database`;
|
|
498
568
|
return heading2(pageTitle);
|
|
499
569
|
}
|
|
500
|
-
case
|
|
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:
|
|
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) => (
|
|
667
|
+
bucket.forEach((x) => (renderedData += x));
|
|
598
668
|
}
|
|
599
669
|
}
|
|
600
670
|
switch (type) {
|
|
601
|
-
case
|
|
602
|
-
|
|
671
|
+
case BLOCK_CODE:
|
|
672
|
+
renderedData = codeBlock(renderedData, block[type].language);
|
|
603
673
|
break;
|
|
604
|
-
case
|
|
605
|
-
|
|
674
|
+
case BLOCK_HEADING_1:
|
|
675
|
+
renderedData = heading1(renderedData);
|
|
606
676
|
break;
|
|
607
|
-
case
|
|
608
|
-
|
|
677
|
+
case BLOCK_HEADING_2:
|
|
678
|
+
renderedData = heading2(renderedData);
|
|
609
679
|
break;
|
|
610
|
-
case
|
|
611
|
-
|
|
680
|
+
case BLOCK_HEADING_3:
|
|
681
|
+
renderedData = heading3(renderedData);
|
|
612
682
|
break;
|
|
613
|
-
case
|
|
614
|
-
|
|
683
|
+
case BLOCK_QUOTE:
|
|
684
|
+
renderedData = quote(renderedData);
|
|
615
685
|
break;
|
|
616
|
-
case
|
|
686
|
+
case BLOCK_CALLOUT:
|
|
617
687
|
const { id, has_children } = block;
|
|
618
688
|
let callout_string = "";
|
|
619
689
|
if (!has_children) {
|
|
620
|
-
return callout(
|
|
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 += `${
|
|
625
|
-
|
|
626
|
-
|
|
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
|
|
631
|
-
|
|
699
|
+
case BLOCK_BULLETED_LIST_ITEM:
|
|
700
|
+
renderedData = bullet(renderedData);
|
|
632
701
|
break;
|
|
633
|
-
case
|
|
634
|
-
|
|
702
|
+
case BLOCK_NUMBERED_LIST_ITEM:
|
|
703
|
+
renderedData = bullet(renderedData, block.numbered_list_item.number);
|
|
635
704
|
break;
|
|
636
|
-
case
|
|
637
|
-
|
|
705
|
+
case BLOCK_TO_DO:
|
|
706
|
+
renderedData = todo(renderedData, block.to_do.checked);
|
|
638
707
|
break;
|
|
639
708
|
}
|
|
640
|
-
return
|
|
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
|
-
//
|
|
713
|
+
// Default layout
|
|
663
714
|
const ischild = this.Posts.filter((post) => post.id === page.id).length > 0 ? "" : "../";
|
|
664
|
-
frontmatter +=
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
"
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
"
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
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(
|
|
696
|
-
|
|
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
|
-
|
|
699
|
-
|
|
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
|
-
|
|
703
|
-
|
|
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
|
|
708
|
-
|
|
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
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
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
|