gatsby-source-notion-churnotion 1.2.7 → 1.2.9
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/util/resolvers.js +148 -72
- package/dist/util/tableOfContent.js +65 -76
- package/dist/util/tocHelper.d.ts +1 -1
- package/dist/util/tocHelper.js +63 -139
- package/package.json +1 -1
package/dist/util/resolvers.js
CHANGED
@@ -26,98 +26,174 @@ const formatTableOfContents = (tableOfContents, reporter) => {
|
|
26
26
|
entry.contextTitle = entry.title;
|
27
27
|
}
|
28
28
|
}
|
29
|
-
//
|
30
|
-
const
|
31
|
-
const
|
32
|
-
const
|
33
|
-
//
|
29
|
+
// Build the heading hierarchy based on level and parentHash
|
30
|
+
const headingsByHash = new Map();
|
31
|
+
const childrenByParent = new Map();
|
32
|
+
const validHeadingsByLevel = new Map();
|
33
|
+
// Initialize valid headings collections for each level
|
34
|
+
for (let level = 1; level <= 3; level++) {
|
35
|
+
validHeadingsByLevel.set(level, new Set());
|
36
|
+
}
|
37
|
+
// First pass: Collect all headings by hash
|
34
38
|
for (const entry of sortedToc) {
|
35
|
-
|
36
|
-
|
37
|
-
|
39
|
+
headingsByHash.set(entry.hash, entry);
|
40
|
+
// Store valid headings by level
|
41
|
+
if (entry.level && entry.level >= 1 && entry.level <= 3) {
|
42
|
+
validHeadingsByLevel.get(entry.level).add(entry.hash);
|
38
43
|
}
|
39
44
|
}
|
40
|
-
//
|
45
|
+
// Second pass: Validate parent-child relationships
|
46
|
+
const invalidHashesCollectionByReason = new Map();
|
47
|
+
const addInvalidHash = (hash, reason) => {
|
48
|
+
if (!invalidHashesCollectionByReason.has(reason)) {
|
49
|
+
invalidHashesCollectionByReason.set(reason, new Set());
|
50
|
+
}
|
51
|
+
invalidHashesCollectionByReason.get(reason).add(hash);
|
52
|
+
};
|
53
|
+
// Build parent-child relationship map
|
41
54
|
for (const entry of sortedToc) {
|
42
|
-
if (entry.
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
55
|
+
if (entry.parentHash) {
|
56
|
+
// Check if parent exists
|
57
|
+
const parentExists = headingsByHash.has(entry.parentHash);
|
58
|
+
if (parentExists) {
|
59
|
+
// Add this entry as a child of its parent
|
60
|
+
if (!childrenByParent.has(entry.parentHash)) {
|
61
|
+
childrenByParent.set(entry.parentHash, new Set());
|
62
|
+
}
|
63
|
+
childrenByParent.get(entry.parentHash).add(entry.hash);
|
64
|
+
}
|
65
|
+
else {
|
66
|
+
// Invalid parent reference
|
67
|
+
addInvalidHash(entry.hash, "orphaned");
|
68
|
+
}
|
69
|
+
// Validate level relationships
|
70
|
+
const parent = headingsByHash.get(entry.parentHash);
|
71
|
+
if (parent && entry.level && parent.level) {
|
72
|
+
if (entry.level <= parent.level) {
|
73
|
+
// Invalid level hierarchy (e.g. H2 with H2 or H3 parent)
|
74
|
+
addInvalidHash(entry.hash, "invalid_level");
|
75
|
+
}
|
76
|
+
if (entry.level > parent.level + 1) {
|
77
|
+
// Skip levels (e.g. H1 -> H3 directly)
|
78
|
+
// This is actually valid in the new implementation, but we want to note it
|
79
|
+
reporter.info(`Found H${entry.level} heading "${entry.title}" directly under H${parent.level}`);
|
80
|
+
}
|
81
|
+
}
|
82
|
+
}
|
83
|
+
else if (entry.level && entry.level > 1) {
|
84
|
+
// No parent hash but not a top-level heading
|
85
|
+
addInvalidHash(entry.hash, "missing_parent");
|
48
86
|
}
|
49
87
|
}
|
50
|
-
//
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
88
|
+
// Identify common command names for special handling
|
89
|
+
const commonCommandPattern = /^(cp|mv|rm|find|locate|grep|curl|wget|ls|chmod|chown|ps|sed|awk|cat|service|file|stat)$/i;
|
90
|
+
// Third pass: Identify structurally invalid entries based on context
|
91
|
+
const invalidEntries = new Set();
|
92
|
+
// Process invalid entries from previous validations
|
93
|
+
for (const [reason, hashes] of invalidHashesCollectionByReason.entries()) {
|
94
|
+
if (reason === "orphaned" ||
|
95
|
+
reason === "invalid_level" ||
|
96
|
+
reason === "missing_parent") {
|
97
|
+
for (const hash of hashes) {
|
98
|
+
invalidEntries.add(hash);
|
56
99
|
}
|
57
100
|
}
|
58
101
|
}
|
59
|
-
//
|
60
|
-
const
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
102
|
+
// Find server/service sections
|
103
|
+
const serverSections = sortedToc.filter((entry) => entry.level === 1 &&
|
104
|
+
(entry.title.includes("서버") ||
|
105
|
+
entry.title.includes("Server") ||
|
106
|
+
entry.title.includes("프록시")));
|
107
|
+
// Process special handling for server sections
|
108
|
+
for (const serverSection of serverSections) {
|
109
|
+
// 스퀴드 프록시 서버 섹션인지 특별히 확인
|
110
|
+
const isSquidProxySection = serverSection.title.includes("Squid") ||
|
111
|
+
serverSection.title.includes("프록시") ||
|
112
|
+
serverSection.title.includes("Proxy");
|
113
|
+
// Get direct children of this server section
|
114
|
+
const directChildren = childrenByParent.get(serverSection.hash) || new Set();
|
115
|
+
// Check each child for common command pattern matching
|
116
|
+
for (const childHash of directChildren) {
|
117
|
+
const child = headingsByHash.get(childHash);
|
118
|
+
if (child && commonCommandPattern.test(child.title)) {
|
119
|
+
// 스퀴드 프록시 섹션의 명령어는 무조건 제거 (컨텐츠 유무와 상관없이)
|
120
|
+
if (isSquidProxySection) {
|
121
|
+
invalidEntries.add(childHash);
|
122
|
+
// Also, mark children as invalid if there are any
|
123
|
+
if (childrenByParent.has(childHash)) {
|
124
|
+
for (const grandChildHash of childrenByParent.get(childHash)) {
|
125
|
+
invalidEntries.add(grandChildHash);
|
126
|
+
}
|
127
|
+
}
|
128
|
+
continue;
|
129
|
+
}
|
130
|
+
// 다른 서버 섹션은 기존 로직대로 처리
|
131
|
+
const childHasChildren = childrenByParent.has(childHash) &&
|
132
|
+
childrenByParent.get(childHash).size > 0;
|
133
|
+
// Special check for service section headings
|
134
|
+
if (!childHasChildren) {
|
135
|
+
invalidEntries.add(childHash);
|
136
|
+
// Also, mark children as invalid if there are any
|
137
|
+
if (childrenByParent.has(childHash)) {
|
138
|
+
for (const grandChildHash of childrenByParent.get(childHash)) {
|
139
|
+
invalidEntries.add(grandChildHash);
|
140
|
+
}
|
77
141
|
}
|
78
|
-
commandSections
|
79
|
-
.get(entry.parentHash)
|
80
|
-
.set(entry.title.toLowerCase(), hasChildren);
|
81
142
|
}
|
82
143
|
}
|
83
144
|
}
|
84
145
|
}
|
85
|
-
//
|
86
|
-
const
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
146
|
+
// Ensure the correct context titles reflect the parent relationship
|
147
|
+
for (const entry of sortedToc) {
|
148
|
+
if (entry.level === 2 && entry.parentHash) {
|
149
|
+
const parent = headingsByHash.get(entry.parentHash);
|
150
|
+
if (parent) {
|
151
|
+
entry.contextTitle = `${entry.title} (${parent.title})`;
|
152
|
+
}
|
153
|
+
}
|
154
|
+
else if (entry.level === 3 && entry.parentHash) {
|
155
|
+
// For H3, prefer its direct parent, whether H2 or H1
|
156
|
+
const parent = headingsByHash.get(entry.parentHash);
|
157
|
+
if (parent) {
|
158
|
+
entry.contextTitle = `${entry.title} (${parent.title})`;
|
159
|
+
}
|
160
|
+
}
|
161
|
+
}
|
162
|
+
// 최종 필터링 - 강제 제거 대상
|
163
|
+
const forceRemoveMap = new Map(); // 부모 해시 -> 제거할 자식 해시 세트
|
164
|
+
// 스퀴드 프록시 서버 섹션 찾기
|
165
|
+
const squidSection = sortedToc.find((entry) => entry.level === 1 &&
|
166
|
+
(entry.title.includes("Squid") ||
|
167
|
+
entry.title.includes("프록시") ||
|
168
|
+
entry.title.includes("Proxy")));
|
169
|
+
if (squidSection) {
|
170
|
+
// 스퀴드 섹션 내 명령어 찾기
|
171
|
+
const squidChildEntries = sortedToc.filter((entry) => entry.level === 2 && entry.parentHash === squidSection.hash);
|
172
|
+
// 일반 명령어 패턴
|
173
|
+
const finalCommandPattern = /^(cp|mv|rm|find|locate|grep|curl|wget|ls|chmod|chown|ps|sed|awk|cat|service|file|stat)$/i;
|
174
|
+
// 스퀴드 섹션 내 명령어 중 일반 명령어인 경우
|
175
|
+
for (const child of squidChildEntries) {
|
176
|
+
if (finalCommandPattern.test(child.title)) {
|
177
|
+
// 제거 대상 목록에 추가
|
178
|
+
if (!forceRemoveMap.has(squidSection.hash)) {
|
179
|
+
forceRemoveMap.set(squidSection.hash, new Set());
|
180
|
+
}
|
181
|
+
forceRemoveMap.get(squidSection.hash).add(child.hash);
|
182
|
+
// 해당 명령어의 자식들도 제거 대상에 추가
|
183
|
+
const childChildren = sortedToc.filter((entry) => entry.parentHash === child.hash);
|
184
|
+
for (const grandChild of childChildren) {
|
185
|
+
invalidEntries.add(grandChild.hash);
|
113
186
|
}
|
187
|
+
// 명령어 자체도 제거 대상에 추가
|
188
|
+
invalidEntries.add(child.hash);
|
114
189
|
}
|
115
190
|
}
|
191
|
+
reporter.info(`Forcibly removed ${invalidEntries.size} entries from Squid Proxy section`);
|
116
192
|
}
|
117
|
-
//
|
193
|
+
// Create a filtered TOC without invalid entries
|
118
194
|
const validToc = sortedToc.filter((entry) => !invalidEntries.has(entry.hash));
|
119
195
|
if (invalidEntries.size > 0) {
|
120
|
-
reporter.info(`Removed ${invalidEntries.size} invalid TOC entries
|
196
|
+
reporter.info(`Removed ${invalidEntries.size} invalid TOC entries`);
|
121
197
|
}
|
122
198
|
return validToc;
|
123
199
|
}
|
@@ -2,106 +2,95 @@
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
3
|
exports.processTableOfContents = exports.resetTocContext = void 0;
|
4
4
|
const crypto_1 = require("crypto");
|
5
|
-
// Hash
|
5
|
+
// Hash cache for generation
|
6
6
|
const hashCache = new Map();
|
7
|
-
//
|
8
|
-
let
|
9
|
-
let
|
10
|
-
let
|
11
|
-
|
12
|
-
|
7
|
+
// Track current context by level
|
8
|
+
let currentH1 = null;
|
9
|
+
let currentH2 = null;
|
10
|
+
let currentH3 = null;
|
11
|
+
// Generate a hash for a heading
|
12
|
+
const generateHash = (text) => {
|
13
|
+
return `link-${text
|
14
|
+
.replace(/[^a-zA-Z0-9가-힣\s-_]/g, "")
|
15
|
+
.trim()
|
16
|
+
.replace(/\s+/g, "-")
|
17
|
+
.toLowerCase()}-${(0, crypto_1.randomUUID)().substring(0, 4)}`;
|
18
|
+
};
|
19
|
+
// Reset context between pages/documents
|
13
20
|
const resetTocContext = () => {
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
currentCommandSection = "";
|
21
|
+
currentH1 = null;
|
22
|
+
currentH2 = null;
|
23
|
+
currentH3 = null;
|
24
|
+
hashCache.clear();
|
19
25
|
};
|
20
26
|
exports.resetTocContext = resetTocContext;
|
21
27
|
const processTableOfContents = async (block, tableOfContents) => {
|
22
28
|
if (["heading_1", "heading_2", "heading_3"].includes(block.type) &&
|
23
29
|
block[block.type]?.rich_text?.length > 0) {
|
24
30
|
const plainText = block[block.type]?.rich_text?.[0]?.plain_text || "";
|
25
|
-
//
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
// 상위 헤딩에 따라 컨텍스트 저장
|
30
|
-
if (block.type === "heading_1") {
|
31
|
-
lastH1Title = plainText;
|
32
|
-
lastH2Title = "";
|
33
|
-
lastH2Hash = ""; // H1을 만나면 H2 해시도 초기화
|
34
|
-
currentCommandSection = ""; // 새 섹션 시작
|
35
|
-
contextualId = `h1-${plainText}`;
|
36
|
-
level = 1;
|
37
|
-
// heading_1은 최상위 레벨이므로 부모가 없음
|
38
|
-
}
|
39
|
-
else if (block.type === "heading_2") {
|
40
|
-
// H2는 반드시 H1 아래에 속함
|
41
|
-
lastH2Title = plainText;
|
42
|
-
currentCommandSection = plainText; // 현재 명령어 설정 (예: useradd, usermod 등)
|
43
|
-
contextualId = `h2-${lastH1Title}-${plainText}`;
|
44
|
-
parentHash = lastH1Hash; // heading_2의 부모는 가장 최근의 heading_1
|
45
|
-
level = 2;
|
46
|
-
}
|
47
|
-
else if (block.type === "heading_3") {
|
48
|
-
// H3 처리를 개선
|
49
|
-
const parentSectionInfo = lastH2Hash
|
50
|
-
? currentCommandSection
|
51
|
-
: lastH1Title;
|
52
|
-
const fullContextId = `h3-${lastH1Title}-${parentSectionInfo}-${plainText}`;
|
53
|
-
contextualId = fullContextId;
|
54
|
-
// 현재 H2가 없다면 바로 H1을 부모로 설정
|
55
|
-
parentHash = lastH2Hash || lastH1Hash;
|
56
|
-
// H2가 없는 경우 현재 H1 제목을 컨텍스트로 사용
|
57
|
-
if (!lastH2Hash) {
|
58
|
-
currentCommandSection = lastH1Title;
|
59
|
-
}
|
60
|
-
level = 3;
|
61
|
-
}
|
62
|
-
// 이미 처리된 플레인텍스트인지 확인
|
31
|
+
// Extract heading level (1, 2, or 3)
|
32
|
+
const level = parseInt(block.type.split("_")[1]);
|
33
|
+
// Generate or retrieve hash from cache
|
34
|
+
const contextualId = `h${level}-${plainText}`;
|
63
35
|
let hash;
|
64
36
|
if (hashCache.has(contextualId)) {
|
65
37
|
hash = hashCache.get(contextualId);
|
66
38
|
}
|
67
39
|
else {
|
68
|
-
hash =
|
69
|
-
.replace(/[^a-zA-Z0-9가-힣\s-_]/g, "")
|
70
|
-
.trim()
|
71
|
-
.replace(/\s+/g, "-")
|
72
|
-
.toLowerCase()}-${(0, crypto_1.randomUUID)().substring(0, 4)}`;
|
40
|
+
hash = generateHash(plainText);
|
73
41
|
hashCache.set(contextualId, hash);
|
74
42
|
}
|
75
|
-
//
|
76
|
-
if (block.type === "heading_1") {
|
77
|
-
lastH1Hash = hash;
|
78
|
-
}
|
79
|
-
else if (block.type === "heading_2") {
|
80
|
-
lastH2Hash = hash;
|
81
|
-
}
|
43
|
+
// Set hash on block for reference
|
82
44
|
block.hash = hash;
|
83
|
-
//
|
84
|
-
const
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
45
|
+
// Create new heading context
|
46
|
+
const newHeading = {
|
47
|
+
hash,
|
48
|
+
title: plainText,
|
49
|
+
level,
|
50
|
+
children: [],
|
51
|
+
};
|
52
|
+
// Manage hierarchy based on heading level
|
53
|
+
let parentHash = "";
|
54
|
+
let contextTitle = plainText;
|
55
|
+
if (level === 1) {
|
56
|
+
// New H1 starts a completely new context
|
57
|
+
currentH1 = newHeading;
|
58
|
+
currentH2 = null;
|
59
|
+
currentH3 = null;
|
60
|
+
parentHash = "";
|
93
61
|
}
|
94
|
-
// H2 항목도 컨텍스트 추가
|
95
62
|
else if (level === 2) {
|
96
|
-
|
63
|
+
// H2 is a child of the current H1
|
64
|
+
if (currentH1) {
|
65
|
+
parentHash = currentH1.hash;
|
66
|
+
currentH1.children.push(hash);
|
67
|
+
contextTitle = `${plainText} (${currentH1.title})`;
|
68
|
+
}
|
69
|
+
currentH2 = newHeading;
|
70
|
+
currentH3 = null;
|
71
|
+
}
|
72
|
+
else if (level === 3) {
|
73
|
+
// H3 is a child of the current H2, or directly of H1 if no H2 exists
|
74
|
+
if (currentH2) {
|
75
|
+
parentHash = currentH2.hash;
|
76
|
+
currentH2.children.push(hash);
|
77
|
+
contextTitle = `${plainText} (${currentH2.title})`;
|
78
|
+
}
|
79
|
+
else if (currentH1) {
|
80
|
+
parentHash = currentH1.hash;
|
81
|
+
currentH1.children.push(hash);
|
82
|
+
contextTitle = `${plainText} (${currentH1.title})`;
|
83
|
+
}
|
84
|
+
currentH3 = newHeading;
|
97
85
|
}
|
98
|
-
//
|
86
|
+
// Check for duplicates before adding to TOC
|
99
87
|
const existingTocIndex = tableOfContents.findIndex((toc) => toc.hash === hash);
|
100
88
|
if (existingTocIndex === -1) {
|
89
|
+
// Add new TOC entry
|
101
90
|
tableOfContents.push({
|
102
91
|
type: block.type,
|
103
92
|
hash,
|
104
|
-
title:
|
93
|
+
title: plainText,
|
105
94
|
contextTitle,
|
106
95
|
parentHash,
|
107
96
|
level,
|
package/dist/util/tocHelper.d.ts
CHANGED
@@ -23,7 +23,7 @@ export declare const optimizeTocArray: (tocEntries: TocEntry[], reporter: Report
|
|
23
23
|
buildHierarchy?: boolean;
|
24
24
|
}) => TocEntry[];
|
25
25
|
/**
|
26
|
-
* TOC
|
26
|
+
* Validates TOC structure and ensures proper parent-child relationships
|
27
27
|
*/
|
28
28
|
export declare const validateTocStructure: (tocEntries: TocEntry[], reporter: Reporter) => TocEntry[];
|
29
29
|
/**
|
package/dist/util/tocHelper.js
CHANGED
@@ -33,95 +33,42 @@ const optimizeTocArray = (tocEntries, reporter, options = {}) => {
|
|
33
33
|
}
|
34
34
|
return entry;
|
35
35
|
});
|
36
|
-
//
|
36
|
+
// Validate structure and build hierarchy
|
37
37
|
const validatedToc = (0, exports.validateTocStructure)(enrichedTocEntries, reporter);
|
38
38
|
// Remove duplicates if requested
|
39
39
|
let processedToc = validatedToc;
|
40
40
|
if (removeDuplicates) {
|
41
41
|
const startTime = Date.now();
|
42
|
-
//
|
42
|
+
// Map to track unique entries by hash
|
43
43
|
const uniqueMap = new Map();
|
44
|
-
//
|
45
|
-
const h1Entries = new Map(); // 최상위 항목
|
46
|
-
const childEntries = new Map(); // 상위 해시 -> 하위 해시 세트
|
47
|
-
// 1. 먼저 모든 H1 항목을 수집하고 맵에 저장
|
44
|
+
// First collect H1 entries (top-level sections)
|
48
45
|
for (const entry of validatedToc) {
|
49
46
|
if (entry.level === 1) {
|
50
|
-
|
51
|
-
childEntries.set(entry.hash, new Set());
|
47
|
+
uniqueMap.set(entry.hash, entry);
|
52
48
|
}
|
53
49
|
}
|
54
|
-
//
|
50
|
+
// Then collect child entries (H2, H3) that have valid parents
|
55
51
|
for (const entry of validatedToc) {
|
56
|
-
|
57
|
-
|
58
|
-
uniqueMap.
|
59
|
-
|
60
|
-
}
|
61
|
-
// H2, H3 항목 처리
|
62
|
-
if (entry.parentHash) {
|
63
|
-
// 부모 해시가 있는 경우 부모가 실제로 존재하는지 확인
|
64
|
-
const parentExists = uniqueMap.has(entry.parentHash) || h1Entries.has(entry.parentHash);
|
52
|
+
if (entry.level !== 1 && entry.parentHash) {
|
53
|
+
// Only add entries with valid parents to ensure proper hierarchy
|
54
|
+
const parentExists = uniqueMap.has(entry.parentHash) ||
|
55
|
+
validatedToc.some((e) => e.hash === entry.parentHash);
|
65
56
|
if (parentExists) {
|
66
|
-
//
|
67
|
-
if (
|
68
|
-
|
57
|
+
// Add to unique map if not already there
|
58
|
+
if (!uniqueMap.has(entry.hash)) {
|
59
|
+
uniqueMap.set(entry.hash, entry);
|
69
60
|
}
|
70
|
-
else {
|
71
|
-
childEntries.set(entry.parentHash, new Set([entry.hash]));
|
72
|
-
}
|
73
|
-
// 항목을 유효한 것으로 추가
|
74
|
-
uniqueMap.set(entry.hash, entry);
|
75
61
|
}
|
76
62
|
else {
|
77
|
-
|
78
|
-
reporter.warn(`Skipping orphaned TOC entry: ${entry.title} (parent hash ${entry.parentHash} not found)`);
|
79
|
-
}
|
80
|
-
}
|
81
|
-
else if (entry.level === 2) {
|
82
|
-
// H2 항목이지만 부모 해시가 없는 경우 (비정상)
|
83
|
-
reporter.warn(`Skipping H2 entry without parent: ${entry.title}`);
|
84
|
-
}
|
85
|
-
else {
|
86
|
-
// 부모 해시가 없지만 유효할 수 있는 항목
|
87
|
-
uniqueMap.set(entry.hash, entry);
|
88
|
-
}
|
89
|
-
}
|
90
|
-
// 내부 일관성 검사: 동일한 제목의 항목이 여러 섹션에 있는 경우
|
91
|
-
const titleToSections = new Map();
|
92
|
-
for (const entry of uniqueMap.values()) {
|
93
|
-
if (entry.level === 2) {
|
94
|
-
const title = entry.title.toLowerCase();
|
95
|
-
if (!titleToSections.has(title)) {
|
96
|
-
titleToSections.set(title, new Set());
|
97
|
-
}
|
98
|
-
// 이 제목이 속한 부모 H1 추적
|
99
|
-
if (entry.parentHash) {
|
100
|
-
titleToSections.get(title).add(entry.parentHash);
|
63
|
+
reporter.warn(`Skipping orphaned TOC entry: ${entry.title}`);
|
101
64
|
}
|
102
65
|
}
|
103
66
|
}
|
104
|
-
// 여러 섹션에 동일한 제목의 H2가 있는지 확인하고, 영향받는 H2의 contextTitle을 더 강화
|
105
|
-
for (const [title, sections] of titleToSections.entries()) {
|
106
|
-
if (sections.size > 1) {
|
107
|
-
reporter.info(`Title "${title}" appears across ${sections.size} different sections. Enhancing context.`);
|
108
|
-
// 해당 제목의 모든 항목에 대해 상위 항목의 제목을 컨텍스트에 추가
|
109
|
-
for (const entry of uniqueMap.values()) {
|
110
|
-
if (entry.title.toLowerCase() === title && entry.parentHash) {
|
111
|
-
const parentEntry = h1Entries.get(entry.parentHash);
|
112
|
-
if (parentEntry) {
|
113
|
-
entry.contextTitle = `${entry.title} (${parentEntry.title})`;
|
114
|
-
}
|
115
|
-
}
|
116
|
-
}
|
117
|
-
}
|
118
|
-
}
|
119
|
-
// 최종 결과를 배열로 변환
|
120
67
|
processedToc = Array.from(uniqueMap.values());
|
121
68
|
const removedCount = validatedToc.length - processedToc.length;
|
122
69
|
const processTime = Date.now() - startTime;
|
123
70
|
if (removedCount > 0) {
|
124
|
-
reporter.info(`Removed ${removedCount} duplicate
|
71
|
+
reporter.info(`Removed ${removedCount} duplicate TOC entries in ${processTime}ms.`);
|
125
72
|
}
|
126
73
|
}
|
127
74
|
// Add context to duplicate title entries for better display
|
@@ -135,14 +82,14 @@ const optimizeTocArray = (tocEntries, reporter, options = {}) => {
|
|
135
82
|
}
|
136
83
|
// Build the hierarchical structure if requested
|
137
84
|
if (buildHierarchy) {
|
138
|
-
//
|
85
|
+
// Sort by level for proper hierarchy building
|
139
86
|
processedToc.sort((a, b) => {
|
140
87
|
if ((a.level || 0) !== (b.level || 0)) {
|
141
88
|
return (a.level || 0) - (b.level || 0);
|
142
89
|
}
|
143
90
|
return 0; // Keep original order for same level
|
144
91
|
});
|
145
|
-
//
|
92
|
+
// Log the TOC structure
|
146
93
|
const h1Count = processedToc.filter((item) => item.level === 1).length;
|
147
94
|
const h2Count = processedToc.filter((item) => item.level === 2).length;
|
148
95
|
const h3Count = processedToc.filter((item) => item.level === 3).length;
|
@@ -152,103 +99,80 @@ const optimizeTocArray = (tocEntries, reporter, options = {}) => {
|
|
152
99
|
};
|
153
100
|
exports.optimizeTocArray = optimizeTocArray;
|
154
101
|
/**
|
155
|
-
* TOC
|
102
|
+
* Validates TOC structure and ensures proper parent-child relationships
|
156
103
|
*/
|
157
104
|
const validateTocStructure = (tocEntries, reporter) => {
|
158
105
|
if (!tocEntries || tocEntries.length === 0) {
|
159
106
|
return [];
|
160
107
|
}
|
161
|
-
//
|
162
|
-
const
|
163
|
-
const
|
164
|
-
//
|
108
|
+
// Create maps for efficient lookups
|
109
|
+
const entryMap = new Map();
|
110
|
+
const h1Entries = new Map();
|
111
|
+
// First pass: collect entries by hash
|
165
112
|
for (const entry of tocEntries) {
|
113
|
+
entryMap.set(entry.hash, entry);
|
114
|
+
// Collect H1 entries separately
|
166
115
|
if (entry.level === 1) {
|
167
|
-
|
168
|
-
h1Children.set(entry.hash, new Set());
|
116
|
+
h1Entries.set(entry.hash, entry);
|
169
117
|
}
|
170
118
|
}
|
171
|
-
// H1
|
172
|
-
if (
|
119
|
+
// No H1 entries? Return original
|
120
|
+
if (h1Entries.size === 0) {
|
173
121
|
return tocEntries;
|
174
122
|
}
|
175
|
-
//
|
123
|
+
// Track suspicious entries that might be duplicates
|
124
|
+
const suspiciousEntries = new Set();
|
125
|
+
// Second pass: check parent-child relationships
|
176
126
|
for (const entry of tocEntries) {
|
177
127
|
if (entry.level !== 1 && entry.parentHash) {
|
178
|
-
//
|
179
|
-
if (
|
180
|
-
|
128
|
+
// Check if parent exists
|
129
|
+
if (!entryMap.has(entry.parentHash)) {
|
130
|
+
suspiciousEntries.add(entry.hash);
|
131
|
+
reporter.warn(`Entry ${entry.title} has invalid parent reference`);
|
181
132
|
}
|
182
133
|
}
|
183
134
|
}
|
184
|
-
//
|
185
|
-
const
|
186
|
-
const sectionTitles = new Map();
|
187
|
-
for (const [h1Hash, childHashes] of h1Children.entries()) {
|
188
|
-
const titleCounts = new Map();
|
189
|
-
sectionTitles.set(h1Hash, titleCounts);
|
190
|
-
// 이 H1에 속한 모든 자식 항목 검사
|
191
|
-
for (const entry of tocEntries) {
|
192
|
-
if ((entry.level === 2 || entry.level === 3) &&
|
193
|
-
entry.parentHash === h1Hash) {
|
194
|
-
const title = entry.title.toLowerCase();
|
195
|
-
const count = (titleCounts.get(title) || 0) + 1;
|
196
|
-
titleCounts.set(title, count);
|
197
|
-
// 이 섹션에 이 제목이 이미 있으면 중복으로 표시
|
198
|
-
if (count > 1) {
|
199
|
-
suspiciousEntries.add(entry.hash);
|
200
|
-
}
|
201
|
-
}
|
202
|
-
}
|
203
|
-
}
|
204
|
-
// 5. 다른 섹션에 속할 것으로 보이는 항목 식별 (예: 14. Squid 프록시 서버의 잘못된 자식)
|
205
|
-
const titleToSections = new Map();
|
135
|
+
// Analyze entries with same title but different parents
|
136
|
+
const titleToEntries = new Map();
|
206
137
|
for (const entry of tocEntries) {
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
titleToSections.set(title, new Set());
|
211
|
-
}
|
212
|
-
// 이 제목이 어떤 H1 섹션에 속하는지 기록
|
213
|
-
if (entry.parentHash) {
|
214
|
-
titleToSections.get(title).add(entry.parentHash);
|
215
|
-
}
|
138
|
+
const title = entry.title.toLowerCase();
|
139
|
+
if (!titleToEntries.has(title)) {
|
140
|
+
titleToEntries.set(title, []);
|
216
141
|
}
|
217
|
-
|
218
|
-
|
219
|
-
for
|
220
|
-
|
221
|
-
|
222
|
-
//
|
223
|
-
const
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
suspiciousEntries.add(entry.hash);
|
142
|
+
titleToEntries.get(title).push(entry);
|
143
|
+
}
|
144
|
+
// Check for duplicate titles and mark suspicious if needed
|
145
|
+
for (const [title, entries] of titleToEntries.entries()) {
|
146
|
+
if (entries.length > 1) {
|
147
|
+
// Group entries by parent hash
|
148
|
+
const entriesByParent = new Map();
|
149
|
+
for (const entry of entries) {
|
150
|
+
const parentKey = entry.parentHash || "none";
|
151
|
+
if (!entriesByParent.has(parentKey)) {
|
152
|
+
entriesByParent.set(parentKey, []);
|
153
|
+
}
|
154
|
+
entriesByParent.get(parentKey).push(entry);
|
155
|
+
}
|
156
|
+
// If same title appears under multiple parents
|
157
|
+
if (entriesByParent.size > 1) {
|
158
|
+
reporter.info(`Title "${title}" appears under ${entriesByParent.size} different parents`);
|
159
|
+
// For each parent, keep only the first occurrence
|
160
|
+
for (const [parentHash, parentEntries] of entriesByParent.entries()) {
|
161
|
+
if (parentEntries.length > 1) {
|
162
|
+
// Keep first entry, mark others as suspicious
|
163
|
+
for (let i = 1; i < parentEntries.length; i++) {
|
164
|
+
suspiciousEntries.add(parentEntries[i].hash);
|
241
165
|
}
|
242
166
|
}
|
243
167
|
}
|
244
168
|
}
|
245
169
|
}
|
246
170
|
}
|
247
|
-
//
|
171
|
+
// Report suspicious entries
|
248
172
|
if (suspiciousEntries.size > 0) {
|
249
173
|
reporter.info(`Found ${suspiciousEntries.size} suspicious TOC entries that might be duplicates`);
|
250
174
|
}
|
251
|
-
//
|
175
|
+
// Return filtered TOC with suspicious entries removed
|
252
176
|
return tocEntries.filter((entry) => !suspiciousEntries.has(entry.hash));
|
253
177
|
};
|
254
178
|
exports.validateTocStructure = validateTocStructure;
|
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.9",
|
5
5
|
"skipLibCheck": true,
|
6
6
|
"license": "0BSD",
|
7
7
|
"main": "./dist/gatsby-node.js",
|