gatsby-source-notion-churnotion 1.2.3 → 1.2.5
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/createSchemaCustomization.js +113 -42
- package/dist/util/processor.js +20 -1
- package/dist/util/resolvers.d.ts +6 -0
- package/dist/util/resolvers.js +137 -0
- package/dist/util/tableOfContent.d.ts +1 -0
- package/dist/util/tableOfContent.js +63 -5
- package/dist/util/tocHelper.d.ts +11 -1
- package/dist/util/tocHelper.js +88 -7
- package/package.json +1 -1
@@ -2,6 +2,7 @@
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
3
|
exports.createSchemaCustomization = void 0;
|
4
4
|
const constants_1 = require("./constants");
|
5
|
+
const resolvers_1 = require("./util/resolvers");
|
5
6
|
const createSchemaCustomization = ({ actions, schema }) => {
|
6
7
|
const { createTypes } = actions;
|
7
8
|
createTypes([
|
@@ -119,48 +120,118 @@ const createSchemaCustomization = ({ actions, schema }) => {
|
|
119
120
|
},
|
120
121
|
},
|
121
122
|
}),
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
123
|
+
schema.buildObjectType({
|
124
|
+
name: constants_1.NODE_TYPE.Post,
|
125
|
+
interfaces: ["Node"],
|
126
|
+
fields: {
|
127
|
+
id: "ID!",
|
128
|
+
category: {
|
129
|
+
type: `${constants_1.NODE_TYPE.Category}!`,
|
130
|
+
extensions: {
|
131
|
+
link: { by: "id", from: "category" },
|
132
|
+
},
|
133
|
+
},
|
134
|
+
tags: {
|
135
|
+
type: `[${constants_1.NODE_TYPE.Tag}]`,
|
136
|
+
extensions: {
|
137
|
+
link: { by: "id" },
|
138
|
+
},
|
139
|
+
},
|
140
|
+
book: {
|
141
|
+
type: constants_1.NODE_TYPE.Book,
|
142
|
+
extensions: {
|
143
|
+
link: { by: "id" },
|
144
|
+
},
|
145
|
+
},
|
146
|
+
book_index: "Int",
|
147
|
+
title: "String",
|
148
|
+
content: "[JSON]",
|
149
|
+
create_date: {
|
150
|
+
type: "Date!",
|
151
|
+
extensions: {
|
152
|
+
dateformat: {},
|
153
|
+
},
|
154
|
+
},
|
155
|
+
update_date: {
|
156
|
+
type: "Date!",
|
157
|
+
extensions: {
|
158
|
+
dateformat: {},
|
159
|
+
},
|
160
|
+
},
|
161
|
+
version: "Int",
|
162
|
+
description: "String",
|
163
|
+
slug: "String",
|
164
|
+
tableOfContents: "[TocEntry]",
|
165
|
+
formattedTableOfContents: {
|
166
|
+
type: "[TocEntry]",
|
167
|
+
resolve: (source, args, context) => {
|
168
|
+
if (!source.tableOfContents)
|
169
|
+
return [];
|
170
|
+
return (0, resolvers_1.formatTableOfContents)(source.tableOfContents, context.reporter);
|
171
|
+
},
|
172
|
+
},
|
173
|
+
category_list: `[${constants_1.NODE_TYPE.Category}]`,
|
174
|
+
url: "String!",
|
175
|
+
thumbnail: {
|
176
|
+
type: "File",
|
177
|
+
extensions: {
|
178
|
+
link: { by: "id", from: "thumbnail" },
|
179
|
+
},
|
180
|
+
},
|
181
|
+
rawText: "String",
|
182
|
+
},
|
183
|
+
}),
|
184
|
+
schema.buildObjectType({
|
185
|
+
name: constants_1.NODE_TYPE.Tag,
|
186
|
+
interfaces: ["Node"],
|
187
|
+
fields: {
|
188
|
+
id: "ID!",
|
189
|
+
tag_name: "String!",
|
190
|
+
slug: "String!",
|
191
|
+
color: "String!",
|
192
|
+
children: {
|
193
|
+
type: `[${constants_1.NODE_TYPE.Post}]`,
|
194
|
+
extensions: {
|
195
|
+
link: { by: "id", from: "tags" },
|
196
|
+
},
|
197
|
+
},
|
198
|
+
url: "String!",
|
199
|
+
},
|
200
|
+
}),
|
201
|
+
schema.buildObjectType({
|
202
|
+
name: constants_1.NODE_TYPE.Metadata,
|
203
|
+
interfaces: ["Node"],
|
204
|
+
fields: {
|
205
|
+
id: "ID!",
|
206
|
+
title: "String",
|
207
|
+
description: "String",
|
208
|
+
image: "String",
|
209
|
+
url: "String",
|
210
|
+
},
|
211
|
+
}),
|
212
|
+
schema.buildObjectType({
|
213
|
+
name: constants_1.NODE_TYPE.RelatedPost,
|
214
|
+
interfaces: ["Node"],
|
215
|
+
fields: {
|
216
|
+
posts: {
|
217
|
+
type: `[${constants_1.NODE_TYPE.Post}]`,
|
218
|
+
extensions: {
|
219
|
+
link: { by: "id" },
|
220
|
+
},
|
221
|
+
},
|
222
|
+
},
|
223
|
+
}),
|
224
|
+
schema.buildObjectType({
|
225
|
+
name: "TocEntry",
|
226
|
+
fields: {
|
227
|
+
type: "String!",
|
228
|
+
hash: "String!",
|
229
|
+
title: "String!",
|
230
|
+
contextTitle: "String",
|
231
|
+
parentHash: "String",
|
232
|
+
level: "Int",
|
233
|
+
},
|
234
|
+
}),
|
164
235
|
]);
|
165
236
|
};
|
166
237
|
exports.createSchemaCustomization = createSchemaCustomization;
|
package/dist/util/processor.js
CHANGED
@@ -4,6 +4,7 @@ exports.processor = void 0;
|
|
4
4
|
const metadataProcessor_1 = require("./metadataProcessor");
|
5
5
|
const blocks_1 = require("./blocks");
|
6
6
|
const tocHelper_1 = require("./tocHelper");
|
7
|
+
const tableOfContent_1 = require("./tableOfContent");
|
7
8
|
const processor = async (blocks, actions, getCache, createNodeId, reporter, cache) => {
|
8
9
|
const { thumbnail, tableOfContents, updatedBlocks, rawText } = await processBlocksForContent(blocks, actions, getCache, createNodeId, reporter, cache);
|
9
10
|
await (0, metadataProcessor_1.processMetadata)(blocks, actions, createNodeId, reporter, cache);
|
@@ -18,6 +19,8 @@ const processBlocksForContent = async (blocks, actions, getCache, createNodeId,
|
|
18
19
|
reporter,
|
19
20
|
cache,
|
20
21
|
};
|
22
|
+
// 목차 컨텍스트 초기화
|
23
|
+
(0, tableOfContent_1.resetTocContext)();
|
21
24
|
// 블록 프로세서 레지스트리 생성
|
22
25
|
const processorRegistry = new blocks_1.BlockProcessorRegistry(context);
|
23
26
|
const tableOfContents = [];
|
@@ -26,6 +29,11 @@ const processBlocksForContent = async (blocks, actions, getCache, createNodeId,
|
|
26
29
|
const updatedBlocks = [];
|
27
30
|
// 첫 번째 이미지 블록을 찾아 썸네일로 사용
|
28
31
|
let firstImageIndex = blocks.findIndex((block) => block.type === "image");
|
32
|
+
// 헤딩 타입 블록을 먼저 처리하여 구조를 생성
|
33
|
+
const headingBlocks = blocks.filter((block) => ["heading_1", "heading_2", "heading_3"].includes(block.type));
|
34
|
+
if (headingBlocks.length > 0) {
|
35
|
+
reporter.info(`Found ${headingBlocks.length} heading blocks for TOC generation`);
|
36
|
+
}
|
29
37
|
// 블록 처리
|
30
38
|
const processResults = await Promise.all(blocks.map(async (block, index) => {
|
31
39
|
const result = await processorRegistry.processBlock(block);
|
@@ -43,8 +51,19 @@ const processBlocksForContent = async (blocks, actions, getCache, createNodeId,
|
|
43
51
|
}
|
44
52
|
return result;
|
45
53
|
}));
|
54
|
+
// 목차 로깅 및 디버깅
|
55
|
+
const h1Count = tableOfContents.filter((item) => item.type === "heading_1").length;
|
56
|
+
const h2Count = tableOfContents.filter((item) => item.type === "heading_2").length;
|
57
|
+
const h3Count = tableOfContents.filter((item) => item.type === "heading_3").length;
|
58
|
+
reporter.info(`TOC entries before optimization: ${tableOfContents.length} total, ${h1Count} h1, ${h2Count} h2, ${h3Count} h3`);
|
46
59
|
// 목차 최적화
|
47
|
-
const processedToc = (0, tocHelper_1.optimizeTocArray)(tableOfContents, reporter
|
60
|
+
const processedToc = (0, tocHelper_1.optimizeTocArray)(tableOfContents, reporter, {
|
61
|
+
maxSize: 2000,
|
62
|
+
warnThreshold: 500,
|
63
|
+
removeDuplicates: true,
|
64
|
+
enhanceDuplicates: true,
|
65
|
+
buildHierarchy: true,
|
66
|
+
});
|
48
67
|
// 업데이트된 블록 적용
|
49
68
|
processResults.forEach((result, index) => {
|
50
69
|
if (result.updatedBlock) {
|
@@ -0,0 +1,137 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.formatTableOfContents = void 0;
|
4
|
+
/**
|
5
|
+
* 목차 구조를 계층적으로 변환하는 함수
|
6
|
+
*/
|
7
|
+
const formatTableOfContents = (tableOfContents, reporter) => {
|
8
|
+
if (!tableOfContents || tableOfContents.length === 0) {
|
9
|
+
return [];
|
10
|
+
}
|
11
|
+
try {
|
12
|
+
// Deep clone to avoid modifying original
|
13
|
+
const tocClone = JSON.parse(JSON.stringify(tableOfContents));
|
14
|
+
// Ensure TOC entries are sorted by level first, then by appearance order
|
15
|
+
const sortedToc = tocClone.sort((a, b) => {
|
16
|
+
// Level 기준 정렬
|
17
|
+
if ((a.level || 0) !== (b.level || 0)) {
|
18
|
+
return (a.level || 0) - (b.level || 0);
|
19
|
+
}
|
20
|
+
// Original order is preserved
|
21
|
+
return 0;
|
22
|
+
});
|
23
|
+
// Add contextTitle where missing
|
24
|
+
for (const entry of sortedToc) {
|
25
|
+
if (!entry.contextTitle) {
|
26
|
+
entry.contextTitle = entry.title;
|
27
|
+
}
|
28
|
+
}
|
29
|
+
// 1. Find all main headings (h1)
|
30
|
+
const mainHeadings = sortedToc.filter((entry) => entry.level === 1);
|
31
|
+
// 2. Find all command headings (h2) with their parent h1
|
32
|
+
const commandHeadings = sortedToc.filter((entry) => entry.level === 2);
|
33
|
+
// Group h2 by parent h1 for easier traversal
|
34
|
+
const h1ToH2Map = new Map();
|
35
|
+
for (const h1 of mainHeadings) {
|
36
|
+
h1ToH2Map.set(h1.hash, []);
|
37
|
+
}
|
38
|
+
for (const h2 of commandHeadings) {
|
39
|
+
if (h2.parentHash) {
|
40
|
+
const h2List = h1ToH2Map.get(h2.parentHash) || [];
|
41
|
+
h2List.push(h2);
|
42
|
+
h1ToH2Map.set(h2.parentHash, h2List);
|
43
|
+
}
|
44
|
+
}
|
45
|
+
// 3. Find all subheadings (h3) with their parent h2
|
46
|
+
const subheadings = sortedToc.filter((entry) => entry.level === 3);
|
47
|
+
// Group h3 by parent h2 for easier traversal
|
48
|
+
const h2ToH3Map = new Map();
|
49
|
+
for (const h2 of commandHeadings) {
|
50
|
+
h2ToH3Map.set(h2.hash, []);
|
51
|
+
}
|
52
|
+
for (const h3 of subheadings) {
|
53
|
+
if (h3.parentHash) {
|
54
|
+
const h3List = h2ToH3Map.get(h3.parentHash) || [];
|
55
|
+
h3List.push(h3);
|
56
|
+
h2ToH3Map.set(h3.parentHash, h3List);
|
57
|
+
}
|
58
|
+
}
|
59
|
+
// Find all unique h3 titles that appear anywhere
|
60
|
+
const allH3Titles = new Set();
|
61
|
+
for (const h3 of subheadings) {
|
62
|
+
allH3Titles.add(h3.title);
|
63
|
+
}
|
64
|
+
// Common expected subheadings for commands
|
65
|
+
const commonSubheadings = [
|
66
|
+
"요약",
|
67
|
+
"개요",
|
68
|
+
"설명",
|
69
|
+
"옵션",
|
70
|
+
"문법",
|
71
|
+
"주요 명령어",
|
72
|
+
"주요 속성 설명",
|
73
|
+
"설치",
|
74
|
+
"기본 명령",
|
75
|
+
];
|
76
|
+
// Add common subheadings to the set
|
77
|
+
for (const title of commonSubheadings) {
|
78
|
+
allH3Titles.add(title);
|
79
|
+
}
|
80
|
+
reporter.info(`Found ${allH3Titles.size} unique h3 titles across sections`);
|
81
|
+
// For each command, ensure all common subheadings exist
|
82
|
+
const finalToc = [...sortedToc];
|
83
|
+
let addedEntries = 0;
|
84
|
+
// Create a map of known title combinations to avoid duplicates
|
85
|
+
const knownCombinations = new Set();
|
86
|
+
// Add all existing combinations to the known set
|
87
|
+
for (const entry of subheadings) {
|
88
|
+
if (entry.parentHash) {
|
89
|
+
const parentCommand = commandHeadings.find((h2) => h2.hash === entry.parentHash);
|
90
|
+
if (parentCommand) {
|
91
|
+
const combo = `${parentCommand.title}:${entry.title}`;
|
92
|
+
knownCombinations.add(combo);
|
93
|
+
}
|
94
|
+
}
|
95
|
+
}
|
96
|
+
// Check which commands are missing which subheadings
|
97
|
+
for (const h2 of commandHeadings) {
|
98
|
+
const h3Entries = h2ToH3Map.get(h2.hash) || [];
|
99
|
+
const existingTitles = new Set(h3Entries.map((entry) => entry.title));
|
100
|
+
// For each expected subheading, check if it exists
|
101
|
+
for (const title of commonSubheadings) {
|
102
|
+
const combo = `${h2.title}:${title}`;
|
103
|
+
// Skip if we already have this combination
|
104
|
+
if (knownCombinations.has(combo) || existingTitles.has(title)) {
|
105
|
+
continue;
|
106
|
+
}
|
107
|
+
// We don't have this subheading for this command - but don't add synthetic entries
|
108
|
+
// in this implementation as we want to show only what's actually in the document
|
109
|
+
}
|
110
|
+
}
|
111
|
+
// If we added entries, re-sort the TOC
|
112
|
+
if (addedEntries > 0) {
|
113
|
+
reporter.info(`Added ${addedEntries} missing subheadings to TOC`);
|
114
|
+
finalToc.sort((a, b) => {
|
115
|
+
if ((a.level || 0) !== (b.level || 0)) {
|
116
|
+
return (a.level || 0) - (b.level || 0);
|
117
|
+
}
|
118
|
+
return 0;
|
119
|
+
});
|
120
|
+
}
|
121
|
+
// Always make sure all h3 entries have context titles that include their parent command
|
122
|
+
for (const entry of finalToc) {
|
123
|
+
if (entry.level === 3 && entry.parentHash) {
|
124
|
+
const parentCommand = commandHeadings.find((h2) => h2.hash === entry.parentHash);
|
125
|
+
if (parentCommand) {
|
126
|
+
entry.contextTitle = `${entry.title} (${parentCommand.title})`;
|
127
|
+
}
|
128
|
+
}
|
129
|
+
}
|
130
|
+
return finalToc;
|
131
|
+
}
|
132
|
+
catch (error) {
|
133
|
+
reporter.error(`Error formatting table of contents: ${error}`);
|
134
|
+
return tableOfContents; // Return original on error
|
135
|
+
}
|
136
|
+
};
|
137
|
+
exports.formatTableOfContents = formatTableOfContents;
|
@@ -1,17 +1,58 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
exports.processTableOfContents = void 0;
|
3
|
+
exports.processTableOfContents = exports.resetTocContext = void 0;
|
4
4
|
const crypto_1 = require("crypto");
|
5
5
|
// Hash 생성 시 사용할 캐시
|
6
6
|
const hashCache = new Map();
|
7
|
+
// 헤딩 레벨 트래킹을 위한 변수들
|
8
|
+
let lastH1Hash = "";
|
9
|
+
let lastH2Hash = "";
|
10
|
+
let lastH1Title = "";
|
11
|
+
let lastH2Title = "";
|
12
|
+
let currentCommandSection = ""; // 현재 명령어 섹션 추적
|
13
|
+
const resetTocContext = () => {
|
14
|
+
lastH1Hash = "";
|
15
|
+
lastH2Hash = "";
|
16
|
+
lastH1Title = "";
|
17
|
+
lastH2Title = "";
|
18
|
+
currentCommandSection = "";
|
19
|
+
};
|
20
|
+
exports.resetTocContext = resetTocContext;
|
7
21
|
const processTableOfContents = async (block, tableOfContents) => {
|
8
22
|
if (["heading_1", "heading_2", "heading_3"].includes(block.type) &&
|
9
23
|
block[block.type]?.rich_text?.length > 0) {
|
10
24
|
const plainText = block[block.type]?.rich_text?.[0]?.plain_text || "";
|
25
|
+
// 부모 컨텍스트 추가
|
26
|
+
let contextualId = plainText;
|
27
|
+
let parentHash = "";
|
28
|
+
let level = 0;
|
29
|
+
// 상위 헤딩에 따라 컨텍스트 저장
|
30
|
+
if (block.type === "heading_1") {
|
31
|
+
lastH1Title = plainText;
|
32
|
+
lastH2Title = "";
|
33
|
+
currentCommandSection = ""; // 새 섹션 시작
|
34
|
+
contextualId = `h1-${plainText}`;
|
35
|
+
level = 1;
|
36
|
+
// heading_1은 최상위 레벨이므로 부모가 없음
|
37
|
+
}
|
38
|
+
else if (block.type === "heading_2") {
|
39
|
+
lastH2Title = plainText;
|
40
|
+
currentCommandSection = plainText; // 현재 명령어 설정 (예: useradd, usermod 등)
|
41
|
+
contextualId = `h2-${lastH1Title}-${plainText}`;
|
42
|
+
parentHash = lastH1Hash; // heading_2의 부모는 가장 최근의 heading_1
|
43
|
+
level = 2;
|
44
|
+
}
|
45
|
+
else if (block.type === "heading_3") {
|
46
|
+
// 서브섹션 제목에 명령어 컨텍스트 추가 (예: 요약 -> useradd 요약)
|
47
|
+
const fullContextId = `h3-${lastH1Title}-${currentCommandSection}-${plainText}`;
|
48
|
+
contextualId = fullContextId;
|
49
|
+
parentHash = lastH2Hash || lastH1Hash; // heading_3의 부모는 가장 최근의 heading_2, 없으면 heading_1
|
50
|
+
level = 3;
|
51
|
+
}
|
11
52
|
// 이미 처리된 플레인텍스트인지 확인
|
12
53
|
let hash;
|
13
|
-
if (hashCache.has(
|
14
|
-
hash = hashCache.get(
|
54
|
+
if (hashCache.has(contextualId)) {
|
55
|
+
hash = hashCache.get(contextualId);
|
15
56
|
}
|
16
57
|
else {
|
17
58
|
hash = `link-${plainText
|
@@ -19,16 +60,33 @@ const processTableOfContents = async (block, tableOfContents) => {
|
|
19
60
|
.trim()
|
20
61
|
.replace(/\s+/g, "-")
|
21
62
|
.toLowerCase()}-${(0, crypto_1.randomUUID)().substring(0, 4)}`;
|
22
|
-
hashCache.set(
|
63
|
+
hashCache.set(contextualId, hash);
|
64
|
+
}
|
65
|
+
// 현재 해시 저장
|
66
|
+
if (block.type === "heading_1") {
|
67
|
+
lastH1Hash = hash;
|
68
|
+
}
|
69
|
+
else if (block.type === "heading_2") {
|
70
|
+
lastH2Hash = hash;
|
23
71
|
}
|
24
72
|
block.hash = hash;
|
73
|
+
// 항상 복사본을 사용하여 중복을 방지
|
74
|
+
const displayTitle = plainText;
|
75
|
+
let contextTitle = displayTitle;
|
76
|
+
// 서브헤딩에 컨텍스트 추가
|
77
|
+
if (level === 3 && currentCommandSection) {
|
78
|
+
contextTitle = `${plainText} (${currentCommandSection})`;
|
79
|
+
}
|
25
80
|
// 중복 체크 - 동일한 hash가 이미 존재하는지 확인
|
26
81
|
const existingTocIndex = tableOfContents.findIndex((toc) => toc.hash === hash);
|
27
82
|
if (existingTocIndex === -1) {
|
28
83
|
tableOfContents.push({
|
29
84
|
type: block.type,
|
30
85
|
hash,
|
31
|
-
title:
|
86
|
+
title: displayTitle,
|
87
|
+
contextTitle,
|
88
|
+
parentHash,
|
89
|
+
level,
|
32
90
|
});
|
33
91
|
}
|
34
92
|
}
|
package/dist/util/tocHelper.d.ts
CHANGED
@@ -6,13 +6,23 @@ export interface TocEntry {
|
|
6
6
|
type: string;
|
7
7
|
hash: string;
|
8
8
|
title: string;
|
9
|
+
contextTitle?: string;
|
10
|
+
parentHash?: string;
|
11
|
+
level?: number;
|
12
|
+
children?: TocEntry[];
|
9
13
|
}
|
10
14
|
/**
|
11
15
|
* Utility function to efficiently handle large table of contents arrays
|
12
|
-
* by removing duplicates and optionally limiting size
|
16
|
+
* by removing duplicates and optionally limiting size while preserving hierarchy
|
13
17
|
*/
|
14
18
|
export declare const optimizeTocArray: (tocEntries: TocEntry[], reporter: Reporter, options?: {
|
15
19
|
maxSize?: number;
|
16
20
|
warnThreshold?: number;
|
17
21
|
removeDuplicates?: boolean;
|
22
|
+
enhanceDuplicates?: boolean;
|
23
|
+
buildHierarchy?: boolean;
|
18
24
|
}) => TocEntry[];
|
25
|
+
/**
|
26
|
+
* Enhances TOC entries with contextual titles to disambiguate duplicate titles
|
27
|
+
*/
|
28
|
+
export declare const enrichDuplicateTitles: (tocEntries: TocEntry[], reporter: Reporter) => void;
|
package/dist/util/tocHelper.js
CHANGED
@@ -1,14 +1,16 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
exports.optimizeTocArray = void 0;
|
3
|
+
exports.enrichDuplicateTitles = exports.optimizeTocArray = void 0;
|
4
4
|
/**
|
5
5
|
* Utility function to efficiently handle large table of contents arrays
|
6
|
-
* by removing duplicates and optionally limiting size
|
6
|
+
* by removing duplicates and optionally limiting size while preserving hierarchy
|
7
7
|
*/
|
8
8
|
const optimizeTocArray = (tocEntries, reporter, options = {}) => {
|
9
|
-
const { maxSize =
|
9
|
+
const { maxSize = 2000, // Maximum entries to include
|
10
10
|
warnThreshold = 300, // Threshold to issue warning
|
11
11
|
removeDuplicates = true, // Whether to remove duplicates
|
12
|
+
enhanceDuplicates = true, // Whether to enhance duplicate titles with context
|
13
|
+
buildHierarchy = true, // Whether to build the hierarchy tree
|
12
14
|
} = options;
|
13
15
|
if (!tocEntries || tocEntries.length === 0) {
|
14
16
|
return [];
|
@@ -17,27 +19,106 @@ const optimizeTocArray = (tocEntries, reporter, options = {}) => {
|
|
17
19
|
if (tocEntries.length > warnThreshold) {
|
18
20
|
reporter.warn(`Large table of contents detected (${tocEntries.length} items). This might affect performance.`);
|
19
21
|
}
|
22
|
+
// Pre-process all entries to ensure proper levels
|
23
|
+
const enrichedTocEntries = tocEntries.map((entry) => {
|
24
|
+
if (!entry.level) {
|
25
|
+
const level = entry.type === "heading_1"
|
26
|
+
? 1
|
27
|
+
: entry.type === "heading_2"
|
28
|
+
? 2
|
29
|
+
: entry.type === "heading_3"
|
30
|
+
? 3
|
31
|
+
: 0;
|
32
|
+
return { ...entry, level };
|
33
|
+
}
|
34
|
+
return entry;
|
35
|
+
});
|
20
36
|
// Remove duplicates if requested
|
21
|
-
let processedToc =
|
37
|
+
let processedToc = enrichedTocEntries;
|
22
38
|
if (removeDuplicates) {
|
23
39
|
const startTime = Date.now();
|
24
40
|
const uniqueMap = new Map();
|
25
|
-
// Use
|
26
|
-
for (const entry of
|
41
|
+
// Use the hash to ensure uniqueness, but preserve context
|
42
|
+
for (const entry of enrichedTocEntries) {
|
27
43
|
uniqueMap.set(entry.hash, entry);
|
28
44
|
}
|
29
45
|
processedToc = Array.from(uniqueMap.values());
|
30
|
-
const removedCount =
|
46
|
+
const removedCount = enrichedTocEntries.length - processedToc.length;
|
31
47
|
const processTime = Date.now() - startTime;
|
32
48
|
if (removedCount > 0) {
|
33
49
|
reporter.info(`Removed ${removedCount} duplicate TOC entries in ${processTime}ms.`);
|
34
50
|
}
|
35
51
|
}
|
52
|
+
// Add context to duplicate title entries for better display
|
53
|
+
if (enhanceDuplicates) {
|
54
|
+
(0, exports.enrichDuplicateTitles)(processedToc, reporter);
|
55
|
+
}
|
36
56
|
// Limit size if necessary
|
37
57
|
if (processedToc.length > maxSize) {
|
38
58
|
reporter.warn(`Table of contents exceeds maximum size (${processedToc.length} > ${maxSize}). Truncating.`);
|
39
59
|
processedToc = processedToc.slice(0, maxSize);
|
40
60
|
}
|
61
|
+
// Build the hierarchical structure if requested
|
62
|
+
if (buildHierarchy) {
|
63
|
+
// First level sort - by level, then by approximate order
|
64
|
+
processedToc.sort((a, b) => {
|
65
|
+
if ((a.level || 0) !== (b.level || 0)) {
|
66
|
+
return (a.level || 0) - (b.level || 0);
|
67
|
+
}
|
68
|
+
return 0; // Keep original order for same level
|
69
|
+
});
|
70
|
+
// 확인을 위한 로깅
|
71
|
+
const h1Count = processedToc.filter((item) => item.level === 1).length;
|
72
|
+
const h2Count = processedToc.filter((item) => item.level === 2).length;
|
73
|
+
const h3Count = processedToc.filter((item) => item.level === 3).length;
|
74
|
+
reporter.info(`TOC structure: ${h1Count} h1, ${h2Count} h2, ${h3Count} h3 headings`);
|
75
|
+
}
|
41
76
|
return processedToc;
|
42
77
|
};
|
43
78
|
exports.optimizeTocArray = optimizeTocArray;
|
79
|
+
/**
|
80
|
+
* Enhances TOC entries with contextual titles to disambiguate duplicate titles
|
81
|
+
*/
|
82
|
+
const enrichDuplicateTitles = (tocEntries, reporter) => {
|
83
|
+
if (!tocEntries || tocEntries.length === 0)
|
84
|
+
return;
|
85
|
+
// Find duplicate titles
|
86
|
+
const titleCounts = new Map();
|
87
|
+
const duplicateTitles = new Set();
|
88
|
+
// Count occurrences of each title
|
89
|
+
for (const entry of tocEntries) {
|
90
|
+
const count = (titleCounts.get(entry.title) || 0) + 1;
|
91
|
+
titleCounts.set(entry.title, count);
|
92
|
+
if (count > 1) {
|
93
|
+
duplicateTitles.add(entry.title);
|
94
|
+
}
|
95
|
+
}
|
96
|
+
if (duplicateTitles.size === 0)
|
97
|
+
return;
|
98
|
+
reporter.info(`Found ${duplicateTitles.size} duplicate title(s) in TOC. Adding context...`);
|
99
|
+
// Build parent map for fast lookups
|
100
|
+
const entryMap = new Map();
|
101
|
+
for (const entry of tocEntries) {
|
102
|
+
entryMap.set(entry.hash, entry);
|
103
|
+
}
|
104
|
+
// Process each entry to ensure all duplicate titles have context
|
105
|
+
for (const entry of tocEntries) {
|
106
|
+
if (duplicateTitles.has(entry.title) && !entry.contextTitle) {
|
107
|
+
let context = "";
|
108
|
+
let parentEntry;
|
109
|
+
// Find parent context
|
110
|
+
if (entry.parentHash && entryMap.has(entry.parentHash)) {
|
111
|
+
parentEntry = entryMap.get(entry.parentHash);
|
112
|
+
context = parentEntry?.title || "";
|
113
|
+
}
|
114
|
+
// Add context to title
|
115
|
+
if (context) {
|
116
|
+
entry.contextTitle = `${entry.title} (${context})`;
|
117
|
+
}
|
118
|
+
else {
|
119
|
+
entry.contextTitle = entry.title;
|
120
|
+
}
|
121
|
+
}
|
122
|
+
}
|
123
|
+
};
|
124
|
+
exports.enrichDuplicateTitles = enrichDuplicateTitles;
|
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.2.
|
4
|
+
"version": "1.2.5",
|
5
5
|
"skipLibCheck": true,
|
6
6
|
"license": "0BSD",
|
7
7
|
"main": "./dist/gatsby-node.js",
|