gatsby-source-notion-churnotion 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- package/README.md +3 -0
- package/dist/api/connector.d.ts +9 -0
- package/dist/api/connector.js +28 -0
- package/dist/api/getPage.d.ts +2 -0
- package/dist/api/getPage.js +143 -0
- package/dist/constants.d.ts +9 -0
- package/dist/constants.js +13 -0
- package/dist/createSchemaCustomization.d.ts +2 -0
- package/dist/createSchemaCustomization.js +43 -0
- package/dist/gatsby-node.d.ts +4 -0
- package/dist/gatsby-node.js +9 -0
- package/dist/on-plugin-init.d.ts +2 -0
- package/dist/on-plugin-init.js +7 -0
- package/dist/source-nodes.d.ts +2 -0
- package/dist/source-nodes.js +31 -0
- package/dist/util/fetchData.d.ts +18 -0
- package/dist/util/fetchData.js +80 -0
- package/dist/util/imageProcessor.d.ts +3 -0
- package/dist/util/imageProcessor.js +47 -0
- package/dist/util/slugify.d.ts +1 -0
- package/dist/util/slugify.js +7 -0
- package/dist/util/timeLimit.d.ts +5 -0
- package/dist/util/timeLimit.js +11 -0
- package/gatsby-node.js +1 -0
- package/package.json +53 -0
package/README.md
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
|
+
};
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
+
exports.instance = exports.n2m = void 0;
|
7
|
+
const client_1 = require("@notionhq/client");
|
8
|
+
const axios_1 = __importDefault(require("axios"));
|
9
|
+
const notion_to_md_1 = require("notion-to-md");
|
10
|
+
const constants_1 = require("../constants");
|
11
|
+
const notion = new client_1.Client({
|
12
|
+
auth: process.env.GATSBY_INTEGRATION_TOKEN,
|
13
|
+
});
|
14
|
+
/**
|
15
|
+
* Page to Markdown Instance
|
16
|
+
*/
|
17
|
+
exports.n2m = new notion_to_md_1.NotionToMarkdown({ notionClient: notion });
|
18
|
+
/**
|
19
|
+
* Notion API Instance
|
20
|
+
*/
|
21
|
+
exports.instance = axios_1.default.create({
|
22
|
+
baseURL: `https://api.notion.com/v1/`,
|
23
|
+
headers: {
|
24
|
+
"Content-Type": `application/json`,
|
25
|
+
"Notion-Version": constants_1.NOTION_API_VERSION,
|
26
|
+
Authorization: `Bearer ${process.env.GATSBY_INTEGRATION_TOKEN}`,
|
27
|
+
},
|
28
|
+
});
|
@@ -0,0 +1,143 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
|
+
};
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
+
exports.getPages = void 0;
|
7
|
+
const crypto_1 = __importDefault(require("crypto"));
|
8
|
+
const constants_1 = require("../constants");
|
9
|
+
const fetchData_1 = require("../util/fetchData");
|
10
|
+
const imageProcessor_1 = require("../util/imageProcessor");
|
11
|
+
const slugify_1 = require("../util/slugify");
|
12
|
+
const connector_1 = require("./connector");
|
13
|
+
const getPages = async ({ databaseId, reporter, getCache, actions, createNode, createNodeId, createParentChildLink, getNode, }) => {
|
14
|
+
let hasMore = true;
|
15
|
+
/**
|
16
|
+
* 데이터베이스 내에 페이지들을 읽어서 재귀적으로 추가하는 서브 메서드드
|
17
|
+
* @param databaseId 데이터베이스 아이디
|
18
|
+
* @param parentCategoryId 부모 데이터베이스 아이디
|
19
|
+
*/
|
20
|
+
const processDatabase = async (databaseId, parentCategoryId = null) => {
|
21
|
+
try {
|
22
|
+
while (hasMore) {
|
23
|
+
const databaseUrl = `databases/${databaseId}/query`;
|
24
|
+
const body = {};
|
25
|
+
const result = await (0, fetchData_1.fetchPostWithRetry)(databaseUrl, body);
|
26
|
+
if (result?.results?.length) {
|
27
|
+
reporter.info(`[SUCCESS] total pages > ${result.results.length}`);
|
28
|
+
}
|
29
|
+
for (const page of result.results) {
|
30
|
+
reporter.info(`[CHECK!!!] page: ${page.id}`);
|
31
|
+
const pageUrl = `blocks/${page.id}/children?page_size=100`;
|
32
|
+
// 페이지 데이터
|
33
|
+
const pageData = await (0, fetchData_1.fetchGetWithRetry)(pageUrl);
|
34
|
+
if (pageData.results[0].type === `child_database`) {
|
35
|
+
const categoryJsonData = pageData.results[0];
|
36
|
+
const title = categoryJsonData.child_database?.title || `Unnamed Category`;
|
37
|
+
const slug = (0, slugify_1.slugify)(title);
|
38
|
+
if (!title) {
|
39
|
+
reporter.warn(`[WARNING] Category without a title detected: ${categoryJsonData.id}`);
|
40
|
+
}
|
41
|
+
const nodeId = createNodeId(`${categoryJsonData.id}-category`);
|
42
|
+
const categoryNode = {
|
43
|
+
id: nodeId,
|
44
|
+
category_name: title,
|
45
|
+
parent_id: parentCategoryId,
|
46
|
+
slug: slug || `no-title-${categoryJsonData.id}`,
|
47
|
+
children: [],
|
48
|
+
internal: {
|
49
|
+
type: constants_1.NODE_TYPE.Category,
|
50
|
+
contentDigest: crypto_1.default
|
51
|
+
.createHash(`md5`)
|
52
|
+
.update(JSON.stringify(categoryJsonData))
|
53
|
+
.digest(`hex`),
|
54
|
+
},
|
55
|
+
};
|
56
|
+
createNode(categoryNode);
|
57
|
+
if (parentCategoryId && categoryNode) {
|
58
|
+
const parentNode = getNode(parentCategoryId); // Gatsby에서 노드를 검색
|
59
|
+
if (parentNode) {
|
60
|
+
createParentChildLink({
|
61
|
+
parent: parentNode,
|
62
|
+
child: categoryNode,
|
63
|
+
});
|
64
|
+
reporter.info(`[SUCCESS] Linked parent: ${parentNode.category_name} -> child: ${categoryNode.category_name}`);
|
65
|
+
}
|
66
|
+
else {
|
67
|
+
reporter.warn(`[WARNING] Parent node not found for ID: ${parentCategoryId}`);
|
68
|
+
}
|
69
|
+
}
|
70
|
+
await processDatabase(categoryJsonData.id, nodeId);
|
71
|
+
}
|
72
|
+
else {
|
73
|
+
// 페이지인 경우
|
74
|
+
const title = page.properties?.[`이름`]?.title?.[0]?.plain_text || `Unnamed`;
|
75
|
+
const slug = (0, slugify_1.slugify)(title);
|
76
|
+
if (!title) {
|
77
|
+
reporter.warn(`[WARNING] Category without a title detected: ${page.id}`);
|
78
|
+
}
|
79
|
+
const nodeId = createNodeId(`${page.id}-page`);
|
80
|
+
// Tag 노드 만들기
|
81
|
+
if (page.properties.tags && page.properties.tags.multi_select) {
|
82
|
+
page.properties.tags.multi_select.map((tagData) => createNode({
|
83
|
+
id: createNodeId(`${tagData.id}-tag`),
|
84
|
+
tag_name: tagData.name,
|
85
|
+
color: tagData.color,
|
86
|
+
internal: {
|
87
|
+
type: constants_1.NODE_TYPE.Tag,
|
88
|
+
contentDigest: crypto_1.default
|
89
|
+
.createHash(`md5`)
|
90
|
+
.update(JSON.stringify(tagData.id))
|
91
|
+
.digest(`hex`),
|
92
|
+
},
|
93
|
+
}));
|
94
|
+
}
|
95
|
+
const bookId = page.properties?.book?.relation?.[0]?.id || null;
|
96
|
+
const markdownContent = await connector_1.n2m.pageToMarkdown(page.id);
|
97
|
+
await (0, imageProcessor_1.processBlocks)(markdownContent, actions, getCache, createNodeId, reporter);
|
98
|
+
const postNode = {
|
99
|
+
id: nodeId,
|
100
|
+
category_id: parentCategoryId,
|
101
|
+
book_id: bookId,
|
102
|
+
title: title,
|
103
|
+
content: markdownContent,
|
104
|
+
create_date: page.created_time,
|
105
|
+
update_date: page.last_edited_time,
|
106
|
+
version: page.properties?.version?.rich_text?.[0]?.plain_text || null,
|
107
|
+
description: null,
|
108
|
+
slug: slug || `no-title-${nodeId}`,
|
109
|
+
children: [],
|
110
|
+
internal: {
|
111
|
+
type: constants_1.NODE_TYPE.Post,
|
112
|
+
contentDigest: crypto_1.default
|
113
|
+
.createHash(`md5`)
|
114
|
+
.update(JSON.stringify(nodeId))
|
115
|
+
.digest(`hex`),
|
116
|
+
},
|
117
|
+
};
|
118
|
+
await createNode(postNode);
|
119
|
+
if (parentCategoryId && postNode) {
|
120
|
+
const parentNode = getNode(parentCategoryId); // Gatsby에서 노드를 검색
|
121
|
+
if (parentNode) {
|
122
|
+
createParentChildLink({
|
123
|
+
parent: parentNode,
|
124
|
+
child: postNode,
|
125
|
+
});
|
126
|
+
reporter.info(`[SUCCESS] Linked parent: ${parentNode.category_name} -> child: ${postNode.title}`);
|
127
|
+
}
|
128
|
+
else {
|
129
|
+
reporter.warn(`[WARNING] Parent node not found for ID: ${parentCategoryId}`);
|
130
|
+
}
|
131
|
+
}
|
132
|
+
}
|
133
|
+
}
|
134
|
+
hasMore = result.has_more;
|
135
|
+
}
|
136
|
+
}
|
137
|
+
catch (error) {
|
138
|
+
reporter.error(`[ERROR] fetching page`);
|
139
|
+
}
|
140
|
+
};
|
141
|
+
await processDatabase(databaseId);
|
142
|
+
};
|
143
|
+
exports.getPages = getPages;
|
@@ -0,0 +1,9 @@
|
|
1
|
+
export declare const NOTION_API_VERSION = "2022-06-28";
|
2
|
+
export declare const NOTION_LIMIT_ERROR = 429;
|
3
|
+
export declare const PLUGIN_NAME = "gatsby-source-notion-churnotion";
|
4
|
+
export declare const NODE_TYPE: {
|
5
|
+
Post: string;
|
6
|
+
Category: string;
|
7
|
+
Tag: string;
|
8
|
+
Book: string;
|
9
|
+
};
|
@@ -0,0 +1,13 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.NODE_TYPE = exports.PLUGIN_NAME = exports.NOTION_LIMIT_ERROR = exports.NOTION_API_VERSION = void 0;
|
4
|
+
exports.NOTION_API_VERSION = `2022-06-28`;
|
5
|
+
exports.NOTION_LIMIT_ERROR = 429;
|
6
|
+
exports.PLUGIN_NAME = `gatsby-source-notion-churnotion`;
|
7
|
+
//////////////////////////////////////////////////
|
8
|
+
exports.NODE_TYPE = {
|
9
|
+
Post: `Churnotion`,
|
10
|
+
Category: `NCategory`,
|
11
|
+
Tag: `NTag`,
|
12
|
+
Book: `NBook`,
|
13
|
+
};
|
@@ -0,0 +1,43 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.createSchemaCustomization = void 0;
|
4
|
+
const constants_1 = require("./constants");
|
5
|
+
const createSchemaCustomization = ({ actions }) => {
|
6
|
+
const { createTypes } = actions;
|
7
|
+
createTypes(`
|
8
|
+
type ${constants_1.NODE_TYPE.Post} implements Node {
|
9
|
+
id: ID!
|
10
|
+
category_id: ${constants_1.NODE_TYPE.Category} @link(by: "id")
|
11
|
+
tags: ${constants_1.NODE_TYPE.Tag} @link(by: "id")
|
12
|
+
book_id: ${constants_1.NODE_TYPE.Book} @link(by: "id")
|
13
|
+
title: String
|
14
|
+
content: [JSON]
|
15
|
+
create_date: Date! @dateformat
|
16
|
+
update_date: Date! @dateformat
|
17
|
+
version: Int
|
18
|
+
description: String
|
19
|
+
slug: String
|
20
|
+
}
|
21
|
+
|
22
|
+
type ${constants_1.NODE_TYPE.Tag} implements Node {
|
23
|
+
id: ID!
|
24
|
+
tag_name: String!
|
25
|
+
color: String!
|
26
|
+
}
|
27
|
+
|
28
|
+
type ${constants_1.NODE_TYPE.Category} implements Node {
|
29
|
+
id: ID!
|
30
|
+
parent_id: ${constants_1.NODE_TYPE.Category} @link(by: "id")
|
31
|
+
category_name: String!
|
32
|
+
slug: String!
|
33
|
+
}
|
34
|
+
|
35
|
+
type ${constants_1.NODE_TYPE.Book} implements Node {
|
36
|
+
id: ID!
|
37
|
+
book_name: String!
|
38
|
+
create_date: Date! @dateformat
|
39
|
+
update_date: Date! @dateformat
|
40
|
+
}
|
41
|
+
`);
|
42
|
+
};
|
43
|
+
exports.createSchemaCustomization = createSchemaCustomization;
|
@@ -0,0 +1,9 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.createSchemaCustomization = exports.sourceNodes = exports.onPluginInit = void 0;
|
4
|
+
var on_plugin_init_1 = require("./on-plugin-init");
|
5
|
+
Object.defineProperty(exports, "onPluginInit", { enumerable: true, get: function () { return on_plugin_init_1.onPluginInit; } });
|
6
|
+
var source_nodes_1 = require("./source-nodes");
|
7
|
+
Object.defineProperty(exports, "sourceNodes", { enumerable: true, get: function () { return source_nodes_1.sourceNodes; } });
|
8
|
+
var createSchemaCustomization_1 = require("./createSchemaCustomization");
|
9
|
+
Object.defineProperty(exports, "createSchemaCustomization", { enumerable: true, get: function () { return createSchemaCustomization_1.createSchemaCustomization; } });
|
@@ -0,0 +1,31 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.sourceNodes = void 0;
|
4
|
+
const getPage_1 = require("./api/getPage");
|
5
|
+
const sourceNodes = async (gatsbyApi, options) => {
|
6
|
+
const { actions, reporter, createNodeId, getNode, getCache } = gatsbyApi;
|
7
|
+
const { createNode, createParentChildLink } = actions;
|
8
|
+
const { token, databaseId } = options;
|
9
|
+
if (!token || !databaseId) {
|
10
|
+
reporter.error(`[ERROR] Missing Notion API token or database ID.`);
|
11
|
+
return;
|
12
|
+
}
|
13
|
+
reporter.info(`[INFO] Fetching pages from Notion database...`);
|
14
|
+
try {
|
15
|
+
await (0, getPage_1.getPages)({
|
16
|
+
token,
|
17
|
+
databaseId,
|
18
|
+
reporter,
|
19
|
+
getCache,
|
20
|
+
actions,
|
21
|
+
createNode,
|
22
|
+
createNodeId,
|
23
|
+
createParentChildLink,
|
24
|
+
getNode,
|
25
|
+
});
|
26
|
+
}
|
27
|
+
catch (e) {
|
28
|
+
reporter.error(`[ERROR] ${e}`);
|
29
|
+
}
|
30
|
+
};
|
31
|
+
exports.sourceNodes = sourceNodes;
|
@@ -0,0 +1,18 @@
|
|
1
|
+
/**
|
2
|
+
* POST request with retry logic using axios instance
|
3
|
+
* @param {string} url - API endpoint URL
|
4
|
+
* @param {any} body - Request body
|
5
|
+
* @param {number} tryCount - Current retry count
|
6
|
+
* @param {number} maxRetries - Maximum retry attempts
|
7
|
+
* @returns {Promise<any>}
|
8
|
+
*/
|
9
|
+
export declare const fetchPostWithRetry: (url: string, body: any, tryCount?: number, maxRetries?: number) => Promise<any>;
|
10
|
+
/**
|
11
|
+
* General GET/PUT/DELETE request with retry logic using axios instance
|
12
|
+
* @param {string} url - API endpoint URL
|
13
|
+
* @param {any} options - Axios request options
|
14
|
+
* @param {number} tryCount - Current retry count
|
15
|
+
* @param {number} maxRetries - Maximum retry attempts
|
16
|
+
* @returns {Promise<any>}
|
17
|
+
*/
|
18
|
+
export declare const fetchGetWithRetry: (url: string, options?: any, tryCount?: number, maxRetries?: number) => Promise<any>;
|
@@ -0,0 +1,80 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.fetchGetWithRetry = exports.fetchPostWithRetry = void 0;
|
4
|
+
const connector_1 = require("../api/connector");
|
5
|
+
const constants_1 = require("../constants");
|
6
|
+
const timeLimit_1 = require("./timeLimit");
|
7
|
+
/**
|
8
|
+
* POST request with retry logic using axios instance
|
9
|
+
* @param {string} url - API endpoint URL
|
10
|
+
* @param {any} body - Request body
|
11
|
+
* @param {number} tryCount - Current retry count
|
12
|
+
* @param {number} maxRetries - Maximum retry attempts
|
13
|
+
* @returns {Promise<any>}
|
14
|
+
*/
|
15
|
+
const fetchPostWithRetry = async (url, body, tryCount = 0, maxRetries = 5) => {
|
16
|
+
try {
|
17
|
+
const response = await connector_1.instance.post(url, body);
|
18
|
+
// Return data if the request was successful
|
19
|
+
return response.data;
|
20
|
+
}
|
21
|
+
catch (error) {
|
22
|
+
const status = error.response?.status;
|
23
|
+
const message = error.response?.data?.message || `Unknown error`;
|
24
|
+
// Handle rate limit errors with exponential backoff
|
25
|
+
if (status === constants_1.NOTION_LIMIT_ERROR && tryCount < maxRetries) {
|
26
|
+
const delay = Math.pow(2, tryCount) * 1000; // Exponential backoff: 1s, 2s, 4s...
|
27
|
+
console.log(`[${constants_1.PLUGIN_NAME}] Rate limit hit. Retrying in ${delay / 1000}s...`);
|
28
|
+
await (0, timeLimit_1.sleep)(delay);
|
29
|
+
return (0, exports.fetchPostWithRetry)(url, body, tryCount + 1, maxRetries);
|
30
|
+
}
|
31
|
+
// Handle unexpected errors with retry
|
32
|
+
if (tryCount < maxRetries) {
|
33
|
+
const delay = Math.pow(2, tryCount) * 1000; // Exponential backoff
|
34
|
+
console.log(`[${constants_1.PLUGIN_NAME}] Unexpected error. Retrying in ${delay / 1000}s...`);
|
35
|
+
await (0, timeLimit_1.sleep)(delay);
|
36
|
+
return (0, exports.fetchPostWithRetry)(url, body, tryCount + 1, maxRetries);
|
37
|
+
}
|
38
|
+
// Log and throw the error if all retries fail
|
39
|
+
console.error(`[${constants_1.PLUGIN_NAME}] Failed after ${tryCount} retries:`, message);
|
40
|
+
throw error;
|
41
|
+
}
|
42
|
+
};
|
43
|
+
exports.fetchPostWithRetry = fetchPostWithRetry;
|
44
|
+
/**
|
45
|
+
* General GET/PUT/DELETE request with retry logic using axios instance
|
46
|
+
* @param {string} url - API endpoint URL
|
47
|
+
* @param {any} options - Axios request options
|
48
|
+
* @param {number} tryCount - Current retry count
|
49
|
+
* @param {number} maxRetries - Maximum retry attempts
|
50
|
+
* @returns {Promise<any>}
|
51
|
+
*/
|
52
|
+
const fetchGetWithRetry = async (url, options = {}, tryCount = 0, maxRetries = 5) => {
|
53
|
+
try {
|
54
|
+
const response = await connector_1.instance.get(url);
|
55
|
+
// Return data if the request was successful
|
56
|
+
return response.data;
|
57
|
+
}
|
58
|
+
catch (error) {
|
59
|
+
const status = error.response?.status;
|
60
|
+
const message = error.response?.data?.message || `Unknown error`;
|
61
|
+
// Handle rate limit errors with exponential backoff
|
62
|
+
if (status === constants_1.NOTION_LIMIT_ERROR && tryCount < maxRetries) {
|
63
|
+
const delay = Math.pow(2, tryCount) * 1000; // Exponential backoff: 1s, 2s, 4s...
|
64
|
+
console.log(`[${constants_1.PLUGIN_NAME}] Rate limit hit. Retrying in ${delay / 1000}s...`);
|
65
|
+
await (0, timeLimit_1.sleep)(delay);
|
66
|
+
return (0, exports.fetchGetWithRetry)(url, options, tryCount + 1, maxRetries);
|
67
|
+
}
|
68
|
+
// Handle unexpected errors with retry
|
69
|
+
if (tryCount < maxRetries) {
|
70
|
+
const delay = Math.pow(2, tryCount) * 1000; // Exponential backoff
|
71
|
+
console.log(`[${constants_1.PLUGIN_NAME}] Unexpected error. Retrying in ${delay / 1000}s...`);
|
72
|
+
await (0, timeLimit_1.sleep)(delay);
|
73
|
+
return (0, exports.fetchGetWithRetry)(url, options, tryCount + 1, maxRetries);
|
74
|
+
}
|
75
|
+
// Log and throw the error if all retries fail
|
76
|
+
console.error(`[${constants_1.PLUGIN_NAME}] Failed after ${tryCount} retries:`, message);
|
77
|
+
throw error;
|
78
|
+
}
|
79
|
+
};
|
80
|
+
exports.fetchGetWithRetry = fetchGetWithRetry;
|
@@ -0,0 +1,3 @@
|
|
1
|
+
import { Actions, GatsbyCache, Reporter } from "gatsby";
|
2
|
+
import { MdBlock } from "notion-to-md/build/types";
|
3
|
+
export declare const processBlocks: (blocks: MdBlock[], actions: Actions, getCache: (this: void, id: string) => GatsbyCache, createNodeId: (this: void, input: string) => string, reporter: Reporter) => Promise<void>;
|
@@ -0,0 +1,47 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
|
+
};
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
+
exports.processBlocks = void 0;
|
7
|
+
const gatsby_source_filesystem_1 = require("gatsby-source-filesystem");
|
8
|
+
const path_1 = __importDefault(require("path"));
|
9
|
+
const crypto_1 = __importDefault(require("crypto"));
|
10
|
+
const processBlocks = async (blocks, actions, getCache, createNodeId, reporter) => {
|
11
|
+
const { createNode } = actions;
|
12
|
+
for (const block of blocks) {
|
13
|
+
// 이미지 블록 처리
|
14
|
+
if (block.type === `image` &&
|
15
|
+
typeof block.parent === `string` &&
|
16
|
+
block.parent.includes(`http`)) {
|
17
|
+
const match = block.parent.match(/\((https?:\/\/.*\.(?:png|jpg|jpeg|gif|webp))\)/);
|
18
|
+
if (match) {
|
19
|
+
const imageUrl = match[1]; // 이미지 URL 추출
|
20
|
+
try {
|
21
|
+
// 해싱된 파일명 생성
|
22
|
+
const fileExtension = path_1.default.extname(imageUrl); // 확장자 추출
|
23
|
+
const hashedFileName = crypto_1.default.createHash(`md5`).update(imageUrl).digest(`hex`) +
|
24
|
+
fileExtension;
|
25
|
+
// 파일 생성
|
26
|
+
const fileNode = await (0, gatsby_source_filesystem_1.createRemoteFileNode)({
|
27
|
+
url: imageUrl,
|
28
|
+
parentNodeId: block.blockId, // 블록 ID를 부모로 설정
|
29
|
+
getCache,
|
30
|
+
createNode,
|
31
|
+
createNodeId,
|
32
|
+
name: hashedFileName,
|
33
|
+
});
|
34
|
+
if (fileNode) {
|
35
|
+
const relativePath = `/static/images/${hashedFileName}`; // 해싱된 파일명을 사용
|
36
|
+
block.parent = `![${hashedFileName}](${relativePath})`; // Markdown 형식으로 업데이트
|
37
|
+
reporter.info(`[SUCCESS] Updated block with new hashed image path: ${block.parent}`);
|
38
|
+
}
|
39
|
+
}
|
40
|
+
catch (error) {
|
41
|
+
reporter.warn(`[WARNING] Failed to download image: ${imageUrl}`);
|
42
|
+
}
|
43
|
+
}
|
44
|
+
}
|
45
|
+
}
|
46
|
+
};
|
47
|
+
exports.processBlocks = processBlocks;
|
@@ -0,0 +1 @@
|
|
1
|
+
export declare const slugify: (text: string) => string;
|
@@ -0,0 +1,11 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.sleep = void 0;
|
4
|
+
/**
|
5
|
+
* Sleep for a given number of milliseconds
|
6
|
+
* @param {number} ms - Milliseconds to sleep
|
7
|
+
*/
|
8
|
+
const sleep = (ms) => {
|
9
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
10
|
+
};
|
11
|
+
exports.sleep = sleep;
|
package/gatsby-node.js
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
module.exports = require(`./dist/gatsby-node`);
|
package/package.json
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
{
|
2
|
+
"name": "gatsby-source-notion-churnotion",
|
3
|
+
"description": "Gatsby plugin that can connect with One Notion Database RECURSIVELY using official API",
|
4
|
+
"version": "1.0.0",
|
5
|
+
"skipLibCheck": true,
|
6
|
+
"license": "0BSD",
|
7
|
+
"main": "./dist/gatsby-node.js",
|
8
|
+
"files": [
|
9
|
+
"./dist/*",
|
10
|
+
"gatsby-node.js"
|
11
|
+
],
|
12
|
+
"scripts": {
|
13
|
+
"clean": "del-cli dist",
|
14
|
+
"build": "tsc",
|
15
|
+
"develop": "tsc --watch",
|
16
|
+
"test": "jest",
|
17
|
+
"prepare": "npm run clean && npm run build"
|
18
|
+
},
|
19
|
+
"keywords": [
|
20
|
+
"gatsby",
|
21
|
+
"gatsby-plugin",
|
22
|
+
"notion",
|
23
|
+
"gatsby-notion",
|
24
|
+
"recursive"
|
25
|
+
],
|
26
|
+
"author": "Churnobyl <tjdcjfals3@naver.com> (https://github.com/Churnobyl)",
|
27
|
+
"homepage": "https://github.com/Churnobyl/gatsby-source-notion-churnotion",
|
28
|
+
"repository": {
|
29
|
+
"type": "git",
|
30
|
+
"url": "git+https://github.com/Churnobyl/gatsby-source-notion-churnotion"
|
31
|
+
},
|
32
|
+
"engines": {
|
33
|
+
"node": ">=18.0.0"
|
34
|
+
},
|
35
|
+
"dependencies": {
|
36
|
+
"@notionhq/client": "^2.2.15",
|
37
|
+
"@types/node": "^22.10.2",
|
38
|
+
"axios": "^1.7.9",
|
39
|
+
"gatsby-source-filesystem": "^5.14.0",
|
40
|
+
"gatsby-transformer-json": "^5.14.0",
|
41
|
+
"node-fetch": "^3.3.2",
|
42
|
+
"notion-to-md": "^3.1.1",
|
43
|
+
"typescript": "^5.7.2"
|
44
|
+
},
|
45
|
+
"devDependencies": {
|
46
|
+
"gatsby": "^5.14.0"
|
47
|
+
},
|
48
|
+
"peerDependencies": {
|
49
|
+
"gatsby": "^5.14.0",
|
50
|
+
"react": "^18.0.0",
|
51
|
+
"react-dom": "^18.0.0"
|
52
|
+
}
|
53
|
+
}
|