gatsby-source-notion-churnotion 1.1.32 → 1.1.36
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 +28 -6
- package/dist/api/getPages.js +91 -95
- package/dist/createSchemaCustomization.js +62 -15
- package/dist/onPostBootstrap.d.ts +1 -1
- package/dist/onPostBootstrap.js +119 -37
- package/package.json +1 -1
package/README.md
CHANGED
@@ -6,6 +6,33 @@ This plugin recursively collects categories from a single Notion database, which
|
|
6
6
|
|
7
7
|
If you're considering Notion as your CMS for Gatsby, this plugin could be a great choice as it supports recursive category collection.
|
8
8
|
|
9
|
+
## What's New in v1.1.35
|
10
|
+
|
11
|
+
- **Major Overhaul of Node Relationship Handling**:
|
12
|
+
- Completely rewrote the Book-Post relationship mechanism
|
13
|
+
- Added custom GraphQL resolver for childrenChurnotion field
|
14
|
+
- Improved recursive database traversal logic
|
15
|
+
- Optimized batch processing with smaller batches (20 pages at a time)
|
16
|
+
- Added detailed logging for better debuggability
|
17
|
+
- Simplified code structure with direct database page processing
|
18
|
+
|
19
|
+
## What's New in v1.1.34
|
20
|
+
|
21
|
+
- **Fixed GraphQL Query Error**:
|
22
|
+
- Fixed "Cannot return null for non-nullable field Churnotion.rawText" error
|
23
|
+
- Changed rawText field to be nullable in schema definition
|
24
|
+
- Added fallback empty string for rawText in Post node creation
|
25
|
+
- Improved error handling for description field
|
26
|
+
|
27
|
+
## What's New in v1.1.33
|
28
|
+
|
29
|
+
- **Fixed childrenChurnotion Field Issues**:
|
30
|
+
- Completely redesigned how Book-Post relationships are handled
|
31
|
+
- Added explicit node fields for childrenChurnotion
|
32
|
+
- Implemented robust node relationship creation in onPostBootstrap
|
33
|
+
- Fixed schema definition by using Fields type
|
34
|
+
- Resolved persistent "Field childrenChurnotion is not defined" error
|
35
|
+
|
9
36
|
## What's New in v1.1.32
|
10
37
|
|
11
38
|
- **Fixed Gatsby Schema Relationship Bug**:
|
@@ -110,9 +137,4 @@ query MyQuery {
|
|
110
137
|
}
|
111
138
|
}
|
112
139
|
}
|
113
|
-
```
|
114
|
-
|
115
|
-
This will return results in MDX format, as shown below:
|
116
|
-
|
117
|
-
|
118
|
-

|
140
|
+
```
|
package/dist/api/getPages.js
CHANGED
@@ -37,12 +37,12 @@ const getPages = async ({ databaseId, reporter, getCache, actions, createNode, c
|
|
37
37
|
const result = await notionService.queryDatabase(databaseId);
|
38
38
|
hasMore = false; // Notion API가 페이지네이션을 완전히 지원하지 않으므로 일단 한 번만 처리
|
39
39
|
if (result?.results?.length) {
|
40
|
-
reporter.info(`[SUCCESS]
|
40
|
+
reporter.info(`[SUCCESS] Database ${databaseId} has ${result.results.length} pages`);
|
41
41
|
// 페이지 ID 목록 수집
|
42
42
|
const pageIds = result.results.map((page) => page.id);
|
43
|
-
// 페이지 블록들을 병렬로 가져오기 - 최대
|
44
|
-
for (let i = 0; i < pageIds.length; i +=
|
45
|
-
const batch = pageIds.slice(i, i +
|
43
|
+
// 페이지 블록들을 병렬로 가져오기 - 최대 20개씩 배치 처리
|
44
|
+
for (let i = 0; i < pageIds.length; i += 20) {
|
45
|
+
const batch = pageIds.slice(i, i + 20);
|
46
46
|
reporter.info(`[BATCH] Processing pages ${i + 1} to ${i + batch.length} of ${pageIds.length}`);
|
47
47
|
const batchBlocks = await notionService.getMultiplePagesBlocks(batch);
|
48
48
|
// 페이지 데이터와 블록 결합
|
@@ -59,106 +59,100 @@ const getPages = async ({ databaseId, reporter, getCache, actions, createNode, c
|
|
59
59
|
}
|
60
60
|
}
|
61
61
|
}
|
62
|
-
|
63
|
-
|
62
|
+
reporter.info(`[PROCESS] Processing ${pagesToProcess.length} pages from database ${databaseId}`);
|
63
|
+
// 모든 페이지 처리
|
64
|
+
for (const { page, blocks } of pagesToProcess) {
|
64
65
|
try {
|
65
|
-
//
|
66
|
-
|
67
|
-
|
66
|
+
// 첫 번째 블록이 child_database인지 확인
|
67
|
+
if (blocks?.[0]?.type === `child_database`) {
|
68
|
+
// 카테고리 처리
|
69
|
+
const categoryJsonData = blocks[0];
|
70
|
+
const title = categoryJsonData.child_database?.title || `Unnamed Category`;
|
71
|
+
const slug = (0, slugify_1.slugify)(title) || `no-title-${categoryJsonData.id}`;
|
72
|
+
if (!title) {
|
73
|
+
reporter.warn(`[WARNING] Category without a title detected: ${categoryJsonData.id}`);
|
74
|
+
}
|
75
|
+
const nodeId = createNodeId(`${categoryJsonData.id}-category`);
|
76
|
+
const categoryUrl = `${parentCategoryUrl}/${slug}`;
|
77
|
+
const categoryNode = {
|
78
|
+
id: nodeId,
|
79
|
+
category_name: title,
|
80
|
+
parent: parentCategoryId,
|
81
|
+
slug: slug,
|
82
|
+
children: [],
|
83
|
+
internal: {
|
84
|
+
type: constants_1.NODE_TYPE.Category,
|
85
|
+
contentDigest: crypto_1.default
|
86
|
+
.createHash(`md5`)
|
87
|
+
.update(JSON.stringify(categoryJsonData))
|
88
|
+
.digest(`hex`),
|
89
|
+
},
|
90
|
+
url: `${constants_1.COMMON_URI}/${constants_1.CATEGORY_URI}${categoryUrl}`,
|
91
|
+
books: [],
|
92
|
+
};
|
93
|
+
await createNode(categoryNode);
|
94
|
+
// Book 관계 처리
|
95
|
+
const bookRelations = page.properties?.book?.relation || null;
|
96
|
+
if (bookRelations) {
|
97
|
+
bookRelations.forEach((relation) => {
|
98
|
+
const bookId = relation.id;
|
99
|
+
const bookNodeId = createNodeId(`${bookId}-book`);
|
100
|
+
const bookNode = getNode(bookNodeId);
|
101
|
+
if (bookNode) {
|
102
|
+
createParentChildLink({
|
103
|
+
parent: categoryNode,
|
104
|
+
child: bookNode,
|
105
|
+
});
|
106
|
+
const updatedBookNode = {
|
107
|
+
...bookNode,
|
108
|
+
book_category: categoryNode.id,
|
109
|
+
internal: {
|
110
|
+
type: bookNode.internal.type,
|
111
|
+
contentDigest: crypto_1.default
|
112
|
+
.createHash(`md5`)
|
113
|
+
.update(JSON.stringify(bookNode))
|
114
|
+
.digest(`hex`),
|
115
|
+
},
|
116
|
+
};
|
117
|
+
createNode(updatedBookNode);
|
118
|
+
reporter.info(`[SUCCESS] Linked Category-Book: ${categoryNode.category_name} -> child: ${bookNode.book_name}`);
|
119
|
+
}
|
120
|
+
});
|
121
|
+
}
|
122
|
+
// 부모-자식 카테고리 관계 설정
|
123
|
+
if (parentCategoryId && categoryNode) {
|
124
|
+
const parentNode = getNode(parentCategoryId);
|
125
|
+
if (parentNode) {
|
126
|
+
createParentChildLink({
|
127
|
+
parent: parentNode,
|
128
|
+
child: categoryNode,
|
129
|
+
});
|
130
|
+
reporter.info(`[SUCCESS] Linked parent: ${parentNode.category_name} -> child: ${categoryNode.category_name}`);
|
131
|
+
}
|
132
|
+
else {
|
133
|
+
reporter.warn(`[WARNING] Parent node not found for ID: ${parentCategoryId}`);
|
134
|
+
}
|
135
|
+
}
|
136
|
+
const newCategoryPath = [...categoryPath, categoryNode];
|
137
|
+
// 해당 데이터베이스의 하위 페이지들을 처리
|
138
|
+
// 여기서 재귀적으로 자식 데이터베이스 처리
|
139
|
+
await processDatabase(categoryJsonData.id, nodeId, newCategoryPath, categoryUrl);
|
140
|
+
}
|
141
|
+
else {
|
142
|
+
// 일반 포스트 처리
|
143
|
+
await (0, timeLimit_1.timeLimit)(processPost(page, blocks, parentCategoryId, categoryPath, tagMap, parentCategoryUrl), 30000, // 30초 제한
|
144
|
+
`Processing post ${page.id} timed out after 30 seconds`);
|
145
|
+
}
|
68
146
|
}
|
69
147
|
catch (error) {
|
70
148
|
reporter.warn(`[WARNING] Error processing page ${page.id}: ${error}`);
|
71
149
|
}
|
72
|
-
}
|
150
|
+
}
|
73
151
|
}
|
74
152
|
catch (error) {
|
75
153
|
reporter.error(`[ERROR] Processing database ${databaseId} failed: ${error}`);
|
76
154
|
}
|
77
155
|
};
|
78
|
-
/**
|
79
|
-
* 페이지 데이터 처리 메서드
|
80
|
-
*/
|
81
|
-
const processPageData = async (page, pageBlocks, parentCategoryId, categoryPath, tagMap, parentCategoryUrl) => {
|
82
|
-
// 첫 번째 블록이 child_database인지 확인
|
83
|
-
if (pageBlocks?.[0]?.type === `child_database`) {
|
84
|
-
await processCategory(page, pageBlocks, parentCategoryId, categoryPath, tagMap, parentCategoryUrl);
|
85
|
-
}
|
86
|
-
else {
|
87
|
-
await processPost(page, pageBlocks, parentCategoryId, categoryPath, tagMap, parentCategoryUrl);
|
88
|
-
}
|
89
|
-
};
|
90
|
-
/**
|
91
|
-
* 카테고리 처리 메서드
|
92
|
-
*/
|
93
|
-
const processCategory = async (page, pageBlocks, parentCategoryId, categoryPath, tagMap, parentCategoryUrl) => {
|
94
|
-
const categoryJsonData = pageBlocks[0];
|
95
|
-
const title = categoryJsonData.child_database?.title || `Unnamed Category`;
|
96
|
-
const slug = (0, slugify_1.slugify)(title) || `no-title-${categoryJsonData.id}`;
|
97
|
-
if (!title) {
|
98
|
-
reporter.warn(`[WARNING] Category without a title detected: ${categoryJsonData.id}`);
|
99
|
-
}
|
100
|
-
const nodeId = createNodeId(`${categoryJsonData.id}-category`);
|
101
|
-
const categoryUrl = `${parentCategoryUrl}/${slug}`;
|
102
|
-
const categoryNode = {
|
103
|
-
id: nodeId,
|
104
|
-
category_name: title,
|
105
|
-
parent: parentCategoryId,
|
106
|
-
slug: slug,
|
107
|
-
children: [],
|
108
|
-
internal: {
|
109
|
-
type: constants_1.NODE_TYPE.Category,
|
110
|
-
contentDigest: crypto_1.default
|
111
|
-
.createHash(`md5`)
|
112
|
-
.update(JSON.stringify(categoryJsonData))
|
113
|
-
.digest(`hex`),
|
114
|
-
},
|
115
|
-
url: `${constants_1.COMMON_URI}/${constants_1.CATEGORY_URI}${categoryUrl}`,
|
116
|
-
books: [],
|
117
|
-
};
|
118
|
-
await createNode(categoryNode);
|
119
|
-
const bookRelations = page.properties?.book?.relation || null;
|
120
|
-
if (bookRelations) {
|
121
|
-
bookRelations.forEach((relation) => {
|
122
|
-
const bookId = relation.id;
|
123
|
-
const bookNodeId = createNodeId(`${bookId}-book`);
|
124
|
-
const bookNode = getNode(bookNodeId);
|
125
|
-
if (bookNode) {
|
126
|
-
createParentChildLink({
|
127
|
-
parent: categoryNode,
|
128
|
-
child: bookNode,
|
129
|
-
});
|
130
|
-
const updatedBookNode = {
|
131
|
-
...bookNode,
|
132
|
-
book_category: categoryNode.id,
|
133
|
-
internal: {
|
134
|
-
type: bookNode.internal.type,
|
135
|
-
contentDigest: crypto_1.default
|
136
|
-
.createHash(`md5`)
|
137
|
-
.update(JSON.stringify(bookNode))
|
138
|
-
.digest(`hex`),
|
139
|
-
},
|
140
|
-
};
|
141
|
-
createNode(updatedBookNode);
|
142
|
-
reporter.info(`[SUCCESS] Linked Category-Book: ${categoryNode.category_name} -> child: ${bookNode.book_name}`);
|
143
|
-
}
|
144
|
-
});
|
145
|
-
}
|
146
|
-
if (parentCategoryId && categoryNode) {
|
147
|
-
const parentNode = getNode(parentCategoryId);
|
148
|
-
if (parentNode) {
|
149
|
-
createParentChildLink({
|
150
|
-
parent: parentNode,
|
151
|
-
child: categoryNode,
|
152
|
-
});
|
153
|
-
reporter.info(`[SUCCESS] Linked parent: ${parentNode.category_name} -> child: ${categoryNode.category_name}`);
|
154
|
-
}
|
155
|
-
else {
|
156
|
-
reporter.warn(`[WARNING] Parent node not found for ID: ${parentCategoryId}`);
|
157
|
-
}
|
158
|
-
}
|
159
|
-
const newCategoryPath = [...categoryPath, categoryNode];
|
160
|
-
await processDatabase(categoryJsonData.id, nodeId, newCategoryPath, categoryUrl);
|
161
|
-
};
|
162
156
|
/**
|
163
157
|
* 포스트 처리 메서드
|
164
158
|
*/
|
@@ -224,7 +218,7 @@ const getPages = async ({ databaseId, reporter, getCache, actions, createNode, c
|
|
224
218
|
update_date: (0, formatDate_1.useFormatDate)(page.last_edited_time),
|
225
219
|
version: page.properties?.version?.number || null,
|
226
220
|
description: page.properties?.description?.rich_text?.[0]?.plain_text ||
|
227
|
-
rawText.substring(0, 400),
|
221
|
+
(rawText ? rawText.substring(0, 400) : ""),
|
228
222
|
slug: slug,
|
229
223
|
category_list: categoryPath,
|
230
224
|
children: [],
|
@@ -240,8 +234,10 @@ const getPages = async ({ databaseId, reporter, getCache, actions, createNode, c
|
|
240
234
|
url: `${constants_1.COMMON_URI}/${constants_1.POST_URI}/${slug}`,
|
241
235
|
thumbnail: imageNode,
|
242
236
|
parent: parentCategoryId,
|
237
|
+
rawText: rawText || "",
|
243
238
|
};
|
244
239
|
await createNode(postNode);
|
240
|
+
reporter.info(`[SUCCESS] Created post node: ${title} (${nodeId})`);
|
245
241
|
if (parentCategoryId) {
|
246
242
|
const parentNode = getNode(parentCategoryId);
|
247
243
|
if (parentNode) {
|
@@ -2,9 +2,64 @@
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
3
|
exports.createSchemaCustomization = void 0;
|
4
4
|
const constants_1 = require("./constants");
|
5
|
-
const createSchemaCustomization = ({ actions }) => {
|
5
|
+
const createSchemaCustomization = ({ actions, schema }) => {
|
6
6
|
const { createTypes } = actions;
|
7
|
-
createTypes(
|
7
|
+
createTypes([
|
8
|
+
schema.buildObjectType({
|
9
|
+
name: constants_1.NODE_TYPE.Book,
|
10
|
+
interfaces: ["Node"],
|
11
|
+
fields: {
|
12
|
+
id: "ID!",
|
13
|
+
book_name: "String!",
|
14
|
+
create_date: {
|
15
|
+
type: "Date!",
|
16
|
+
extensions: {
|
17
|
+
dateformat: {},
|
18
|
+
},
|
19
|
+
},
|
20
|
+
update_date: {
|
21
|
+
type: "Date!",
|
22
|
+
extensions: {
|
23
|
+
dateformat: {},
|
24
|
+
},
|
25
|
+
},
|
26
|
+
children: {
|
27
|
+
type: `[${constants_1.NODE_TYPE.Post}]`,
|
28
|
+
extensions: {
|
29
|
+
link: { by: "book", from: "id" },
|
30
|
+
},
|
31
|
+
},
|
32
|
+
childrenChurnotion: {
|
33
|
+
type: `[${constants_1.NODE_TYPE.Post}]`,
|
34
|
+
resolve: (source, args, context) => {
|
35
|
+
return context.nodeModel.runQuery({
|
36
|
+
query: {
|
37
|
+
filter: {
|
38
|
+
book: { eq: source.id },
|
39
|
+
},
|
40
|
+
},
|
41
|
+
type: constants_1.NODE_TYPE.Post,
|
42
|
+
firstOnly: false,
|
43
|
+
});
|
44
|
+
},
|
45
|
+
},
|
46
|
+
url: "String!",
|
47
|
+
book_category: {
|
48
|
+
type: constants_1.NODE_TYPE.Category,
|
49
|
+
extensions: {
|
50
|
+
link: { by: "id", from: "book_category" },
|
51
|
+
},
|
52
|
+
},
|
53
|
+
book_image: {
|
54
|
+
type: "File",
|
55
|
+
extensions: {
|
56
|
+
link: { by: "id", from: "book_image" },
|
57
|
+
},
|
58
|
+
},
|
59
|
+
description: "String!",
|
60
|
+
},
|
61
|
+
}),
|
62
|
+
`
|
8
63
|
type ${constants_1.NODE_TYPE.Post} implements Node {
|
9
64
|
id: ID!
|
10
65
|
category: ${constants_1.NODE_TYPE.Category}! @link(by: "id", from: "category")
|
@@ -22,7 +77,7 @@ const createSchemaCustomization = ({ actions }) => {
|
|
22
77
|
category_list: [${constants_1.NODE_TYPE.Category}]
|
23
78
|
url: String!
|
24
79
|
thumbnail: File @link(by: "id", from: "thumbnail")
|
25
|
-
rawText: String
|
80
|
+
rawText: String
|
26
81
|
}
|
27
82
|
|
28
83
|
type ${constants_1.NODE_TYPE.Tag} implements Node {
|
@@ -46,17 +101,8 @@ const createSchemaCustomization = ({ actions }) => {
|
|
46
101
|
childrenNBook: [${constants_1.NODE_TYPE.Book}] @link(by: "book_category", from: "id")
|
47
102
|
}
|
48
103
|
|
49
|
-
type
|
50
|
-
|
51
|
-
book_name: String!
|
52
|
-
create_date: Date! @dateformat
|
53
|
-
update_date: Date! @dateformat
|
54
|
-
children: [${constants_1.NODE_TYPE.Post}] @link(by: "book", from: "id")
|
55
|
-
childrenChurnotion: [${constants_1.NODE_TYPE.Post}] @link(by: "book", from: "id")
|
56
|
-
url: String!
|
57
|
-
book_category: ${constants_1.NODE_TYPE.Category} @link(by: "id", from: "book_category")
|
58
|
-
book_image: File @link(by: "id", from: "book_image")
|
59
|
-
description: String!
|
104
|
+
type Fields {
|
105
|
+
childrenChurnotion: [${constants_1.NODE_TYPE.Post}] @link(by: "id")
|
60
106
|
}
|
61
107
|
|
62
108
|
type ${constants_1.NODE_TYPE.Metadata} implements Node {
|
@@ -70,6 +116,7 @@ const createSchemaCustomization = ({ actions }) => {
|
|
70
116
|
type ${constants_1.NODE_TYPE.RelatedPost} implements Node {
|
71
117
|
posts: [${constants_1.NODE_TYPE.Post}] @link(by: "id")
|
72
118
|
}
|
73
|
-
|
119
|
+
`,
|
120
|
+
]);
|
74
121
|
};
|
75
122
|
exports.createSchemaCustomization = createSchemaCustomization;
|
@@ -1,2 +1,2 @@
|
|
1
1
|
import { GatsbyNode } from "gatsby";
|
2
|
-
export declare const onPostBootstrap: GatsbyNode[
|
2
|
+
export declare const onPostBootstrap: GatsbyNode["onPostBootstrap"];
|
package/dist/onPostBootstrap.js
CHANGED
@@ -65,59 +65,141 @@ const getSpaceSeparatedDoc = (doc) => {
|
|
65
65
|
const tokens = getTokens(doc);
|
66
66
|
return tokens.join(" ");
|
67
67
|
};
|
68
|
-
const onPostBootstrap = async ({
|
69
|
-
const
|
70
|
-
|
71
|
-
|
72
|
-
|
68
|
+
const onPostBootstrap = async ({ getNodesByType, actions, reporter, createNodeId, cache, }) => {
|
69
|
+
const { createNodeField, createNode } = actions;
|
70
|
+
reporter.info(`Creating explicit relationships between nodes...`);
|
71
|
+
// 1. Book과 Post 간의 관계 설정
|
72
|
+
// Get all Book and Post nodes
|
73
|
+
const books = getNodesByType(constants_1.NODE_TYPE.Book);
|
74
|
+
const posts = getNodesByType(constants_1.NODE_TYPE.Post);
|
75
|
+
// Create a map of book ID to related posts
|
76
|
+
const bookPostMap = new Map();
|
77
|
+
// Populate the map
|
78
|
+
posts.forEach((post) => {
|
79
|
+
if (post.book) {
|
80
|
+
if (!bookPostMap.has(post.book)) {
|
81
|
+
bookPostMap.set(post.book, []);
|
82
|
+
}
|
83
|
+
bookPostMap.get(post.book).push(post.id);
|
84
|
+
}
|
85
|
+
});
|
86
|
+
// Create explicit fields for each book
|
87
|
+
books.forEach((book) => {
|
88
|
+
const relatedPostIds = bookPostMap.get(book.id) || [];
|
89
|
+
// Add childrenChurnotion field explicitly
|
90
|
+
createNodeField({
|
91
|
+
node: book,
|
92
|
+
name: "childrenChurnotion",
|
93
|
+
value: relatedPostIds,
|
94
|
+
});
|
95
|
+
reporter.info(`Added ${relatedPostIds.length} posts to book: ${book.book_name}`);
|
96
|
+
});
|
97
|
+
reporter.info(`Book-Post relationship creation completed`);
|
98
|
+
// 2. 관련 포스트 기능 구현
|
99
|
+
reporter.info(`Creating related posts...`);
|
100
|
+
// 유효한 텍스트가 있는 포스트 필터링
|
101
|
+
const docsWithText = posts
|
102
|
+
.filter((post) => post.rawText && post.rawText.trim() !== "")
|
103
|
+
.map((post) => ({
|
104
|
+
id: post.id,
|
105
|
+
text: post.rawText || "",
|
106
|
+
}));
|
107
|
+
if (docsWithText.length === 0) {
|
108
|
+
reporter.warn(`No posts with valid text content found for related posts calculation`);
|
109
|
+
// 빈 관련 포스트 노드라도 생성
|
110
|
+
posts.forEach((post) => {
|
111
|
+
const digest = `${post.id} - ${constants_1.NODE_TYPE.RelatedPost}`;
|
112
|
+
createNode({
|
113
|
+
id: createNodeId(digest),
|
114
|
+
parent: post.id,
|
115
|
+
internal: {
|
116
|
+
type: constants_1.NODE_TYPE.RelatedPost,
|
117
|
+
contentDigest: digest,
|
118
|
+
},
|
119
|
+
posts: [],
|
120
|
+
});
|
121
|
+
});
|
122
|
+
return;
|
123
|
+
}
|
124
|
+
reporter.info(`Processing ${docsWithText.length} posts for related content`);
|
125
|
+
// TF-IDF 계산 준비
|
73
126
|
const tfidf = new natural_1.TfIdf();
|
74
|
-
//
|
75
|
-
|
76
|
-
if (doc.text)
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
127
|
+
// 텍스트 전처리 및 TF-IDF 문서 추가
|
128
|
+
for (const doc of docsWithText) {
|
129
|
+
if (!doc.text)
|
130
|
+
continue;
|
131
|
+
const cacheKey = `${md5(doc.text)}-doc`;
|
132
|
+
let processedText;
|
133
|
+
try {
|
134
|
+
// 캐시에서 전처리된 문서 가져오기 시도
|
135
|
+
const cachedText = await cache.get(cacheKey);
|
136
|
+
if (cachedText) {
|
137
|
+
processedText = cachedText;
|
81
138
|
}
|
82
139
|
else {
|
83
|
-
|
84
|
-
|
85
|
-
|
140
|
+
// 텍스트 전처리
|
141
|
+
const cleanedText = await getTextFromRawText(doc.text);
|
142
|
+
processedText = getSpaceSeparatedDoc(cleanedText);
|
143
|
+
await cache.set(cacheKey, processedText);
|
86
144
|
}
|
145
|
+
// TF-IDF에 문서 추가
|
146
|
+
tfidf.addDocument(processedText);
|
147
|
+
}
|
148
|
+
catch (err) {
|
149
|
+
reporter.warn(`Error processing text for post ${doc.id}: ${err}`);
|
150
|
+
tfidf.addDocument(""); // 오류 방지를 위해 빈 문서 추가
|
151
|
+
}
|
152
|
+
}
|
153
|
+
const docTerms = Array.from({ length: docsWithText.length }, (_, i) => {
|
154
|
+
try {
|
155
|
+
return tfidf.listTerms(i)
|
156
|
+
.map((x) => ({ ...x, tfidf: x.tf * x.idf }))
|
157
|
+
.sort((x, y) => y.tfidf - x.tfidf);
|
158
|
+
}
|
159
|
+
catch (err) {
|
160
|
+
reporter.warn(`Error listing terms for document index ${i}: ${err}`);
|
161
|
+
return [];
|
87
162
|
}
|
88
163
|
});
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
all_keywords.add(x.term);
|
98
|
-
tfidf_map_for_each_doc[i].set(x.term, x.tfidf);
|
164
|
+
// 모든 키워드 수집 및 TF-IDF 맵 생성
|
165
|
+
const allKeywords = new Set();
|
166
|
+
const tfidfMapForEachDoc = [];
|
167
|
+
docTerms.forEach((terms, i) => {
|
168
|
+
tfidfMapForEachDoc[i] = new Map();
|
169
|
+
terms.slice(0, 30).forEach((term) => {
|
170
|
+
allKeywords.add(term.term);
|
171
|
+
tfidfMapForEachDoc[i].set(term.term, term.tfidf);
|
99
172
|
});
|
100
173
|
});
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
.map((x) => tfidf_map_for_each_doc[i].get(x))
|
107
|
-
.map((x) => (x === undefined ? 0 : x)));
|
174
|
+
// 각 문서의 BOW 벡터 생성
|
175
|
+
const bowVectors = new Map();
|
176
|
+
docsWithText.forEach((doc, i) => {
|
177
|
+
const vector = Array.from(allKeywords).map((keyword) => tfidfMapForEachDoc[i].get(keyword) || 0);
|
178
|
+
bowVectors.set(doc.id, vector);
|
108
179
|
});
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
180
|
+
// 각 포스트에 대해 관련 포스트 노드 생성
|
181
|
+
posts.forEach((post) => {
|
182
|
+
let relatedNodeIds = [];
|
183
|
+
if (bowVectors.has(post.id)) {
|
184
|
+
try {
|
185
|
+
relatedNodeIds = getRelatedPosts(post.id, bowVectors);
|
186
|
+
}
|
187
|
+
catch (err) {
|
188
|
+
reporter.warn(`Error getting related posts for ${post.id}: ${err}`);
|
189
|
+
}
|
190
|
+
}
|
191
|
+
const digest = `${post.id} - ${constants_1.NODE_TYPE.RelatedPost}`;
|
192
|
+
createNode({
|
113
193
|
id: createNodeId(digest),
|
114
|
-
parent:
|
194
|
+
parent: post.id,
|
115
195
|
internal: {
|
116
196
|
type: constants_1.NODE_TYPE.RelatedPost,
|
117
197
|
contentDigest: digest,
|
118
198
|
},
|
119
199
|
posts: relatedNodeIds,
|
120
200
|
});
|
201
|
+
reporter.info(`Created related posts node for: ${post.title} with ${relatedNodeIds.length} related posts`);
|
121
202
|
});
|
203
|
+
reporter.info(`Related posts creation completed`);
|
122
204
|
};
|
123
205
|
exports.onPostBootstrap = onPostBootstrap;
|
package/package.json
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"name": "gatsby-source-notion-churnotion",
|
3
3
|
"description": "Gatsby plugin that can connect with One Notion Database RECURSIVELY using official API",
|
4
|
-
"version": "1.1.
|
4
|
+
"version": "1.1.36",
|
5
5
|
"skipLibCheck": true,
|
6
6
|
"license": "0BSD",
|
7
7
|
"main": "./dist/gatsby-node.js",
|