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.
@@ -26,98 +26,174 @@ const formatTableOfContents = (tableOfContents, reporter) => {
26
26
  entry.contextTitle = entry.title;
27
27
  }
28
28
  }
29
- // H1 섹션별 명령어 매핑 & 실제 자식 항목 분석
30
- const h1Sections = new Map(); // H1 해시 -> H1 항목
31
- const h1Children = new Map(); // H1 해시 -> H2 자식 해시 세트
32
- const h2Children = new Map(); // H2 해시 -> H3 자식 해시 세트
33
- // 1단계: 모든 H1 항목 수집
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
- if (entry.level === 1) {
36
- h1Sections.set(entry.hash, entry);
37
- h1Children.set(entry.hash, new Set());
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
- // 2단계: 정당한 H2 항목 식별 (H1의 직접 자식)
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.level === 2 &&
43
- entry.parentHash &&
44
- h1Sections.has(entry.parentHash)) {
45
- // H1의 자식으로 추가
46
- h1Children.get(entry.parentHash).add(entry.hash);
47
- h2Children.set(entry.hash, new Set());
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
- // 3단계: 정당한 H3 항목 식별 (H2의 직접 자식)
51
- for (const entry of sortedToc) {
52
- if (entry.level === 3 && entry.parentHash) {
53
- // 부모가 H2인 경우
54
- if (h2Children.has(entry.parentHash)) {
55
- h2Children.get(entry.parentHash).add(entry.hash);
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
- // 4단계: H1 섹션 내에서 일반적인 명령어 이름이 실제 내용을 가지는지 확인
60
- const commandSections = new Map(); // H1 해시 -> (명령어 이름 -> 유효성)
61
- const duplicateCommandNames = new Set();
62
- // 모든 섹션에서 명령어 이름 수집
63
- for (const entry of sortedToc) {
64
- if (entry.level === 2) {
65
- // 일반적인 명령어 이름인지 확인
66
- const isCommonCommand = /^(cp|mv|rm|find|grep|curl|wget|ls|chmod|chown|ps|sed|awk|cat|service|file|locate|stat)$/.test(entry.title.toLowerCase());
67
- if (isCommonCommand) {
68
- duplicateCommandNames.add(entry.title.toLowerCase());
69
- // 이 명령어가 실제 H3 자식을 가지는지 확인
70
- const hasChildren = !!(entry.parentHash &&
71
- h2Children.has(entry.hash) &&
72
- h2Children.get(entry.hash).size > 0);
73
- // 섹션별 명령어 유효성 추적
74
- if (entry.parentHash) {
75
- if (!commandSections.has(entry.parentHash)) {
76
- commandSections.set(entry.parentHash, new Map());
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
- // 5단계: 유효하지 않은 명령어 항목 필터링
86
- const invalidEntries = new Set();
87
- // Squid 프록시 서버와 같은 특정 섹션의 특별 처리
88
- const squidSectionEntry = sortedToc.find((entry) => entry.level === 1 && entry.title.includes("Squid 프록시 서버"));
89
- if (squidSectionEntry) {
90
- const squidSectionHash = squidSectionEntry.hash;
91
- const squidCommandStatus = commandSections.get(squidSectionHash);
92
- // Squid 섹션의 모든 H2 항목 검토
93
- for (const entry of sortedToc) {
94
- if (entry.level === 2 && entry.parentHash === squidSectionHash) {
95
- // 일반적인 명령어 이름이면서 실제 내용(H3 자식)이 없는 항목은 제거 대상
96
- const isCommonCommand = /^(cp|mv|rm|find|grep|curl|wget|ls|chmod|chown|ps|sed|awk|cat|service|file|locate|stat)$/.test(entry.title.toLowerCase());
97
- // Squid 섹션 특성에 맞지 않는 일반 명령어 식별
98
- if (isCommonCommand) {
99
- // 이 명령어가 Squid 섹션에서 정당한 내용을 가지는지 확인
100
- const hasValidChildren = !!(entry.hash &&
101
- h2Children.has(entry.hash) &&
102
- h2Children.get(entry.hash).size > 0);
103
- // 내용 없는 명령어는 제거
104
- if (!hasValidChildren) {
105
- invalidEntries.add(entry.hash);
106
- // 이 명령어의 모든 자식도 제거 (혹시 있을 경우)
107
- for (const childEntry of sortedToc) {
108
- if (childEntry.parentHash === entry.hash) {
109
- invalidEntries.add(childEntry.hash);
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 from special sections`);
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 lastH1Hash = "";
9
- let lastH2Hash = "";
10
- let lastH1Title = "";
11
- let lastH2Title = "";
12
- let currentCommandSection = ""; // 현재 명령어 섹션 추적
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
- lastH1Hash = "";
15
- lastH2Hash = "";
16
- lastH1Title = "";
17
- lastH2Title = "";
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
- let contextualId = plainText;
27
- let parentHash = "";
28
- let level = 0;
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 = `link-${plainText
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 displayTitle = plainText;
85
- let contextTitle = displayTitle;
86
- // 서브헤딩에 컨텍스트 추가 (H3)
87
- if (level === 3) {
88
- // H2가 없으면 H1을 컨텍스트로 사용
89
- const contextSectionName = lastH2Hash
90
- ? currentCommandSection
91
- : lastH1Title;
92
- contextTitle = `${plainText} (${contextSectionName})`;
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
- contextTitle = `${plainText} (${lastH1Title})`;
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
- // 중복 체크 - 동일한 hash가 이미 존재하는지 확인
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: displayTitle,
93
+ title: plainText,
105
94
  contextTitle,
106
95
  parentHash,
107
96
  level,
@@ -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
  /**
@@ -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
- h1Entries.set(entry.hash, entry);
51
- childEntries.set(entry.hash, new Set());
47
+ uniqueMap.set(entry.hash, entry);
52
48
  }
53
49
  }
54
- // 2. 항목을 처리하여 해시 기반으로 적절한 부모-자식 관계 검증
50
+ // Then collect child entries (H2, H3) that have valid parents
55
51
  for (const entry of validatedToc) {
56
- // H1은 이미 처리됨
57
- if (entry.level === 1) {
58
- uniqueMap.set(entry.hash, entry);
59
- continue;
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 (childEntries.has(entry.parentHash)) {
68
- childEntries.get(entry.parentHash).add(entry.hash);
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/invalid TOC entries in ${processTime}ms.`);
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
- // First level sort - by level, then by approximate order
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
- // 1. 섹션별 매핑 구성
162
- const h1Map = new Map();
163
- const h1Children = new Map();
164
- // 2. 모든 H1 항목 수집
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
- h1Map.set(entry.hash, entry);
168
- h1Children.set(entry.hash, new Set());
116
+ h1Entries.set(entry.hash, entry);
169
117
  }
170
118
  }
171
- // H1 없으면 원본 반환
172
- if (h1Map.size === 0) {
119
+ // No H1 entries? Return original
120
+ if (h1Entries.size === 0) {
173
121
  return tocEntries;
174
122
  }
175
- // 3. 항목을 올바른 부모에 연결
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
- // 부모가 H1인 경우
179
- if (h1Map.has(entry.parentHash)) {
180
- h1Children.get(entry.parentHash).add(entry.hash);
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
- // 4. H1 섹션 내에서 중복된 H2/H3 타이틀 발견 (같은 섹션에 동일 타이틀 중복)
185
- const suspiciousEntries = new Set();
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
- if (entry.level === 2) {
208
- const title = entry.title.toLowerCase();
209
- if (!titleToSections.has(title)) {
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 (const [title, sections] of titleToSections.entries()) {
220
- if (sections.size > 1) {
221
- reporter.info(`Title "${title}" appears across ${sections.size} different sections.`);
222
- // 일반적인 명령어 이름인지 확인 (cp, mv, rm 등)
223
- const isCommonCommand = /^(cp|mv|rm|find|grep|curl|wget|ls|chmod|chown|ps|sed|awk|cat|service|file|locate|stat)$/.test(title);
224
- if (isCommonCommand) {
225
- reporter.info(`"${title}" is a common command name that appears in multiple sections.`);
226
- // 섹션의 항목 분석
227
- for (const entry of tocEntries) {
228
- if (entry.title.toLowerCase() === title && entry.level === 2) {
229
- // 이 H2 항목이 이 섹션의 내용과 일치하는지 확인할 방법이 필요
230
- // 예: 이 H2와 연결된 H3 항목이 정당한지 검사
231
- let hasValidChildren = false;
232
- for (const child of tocEntries) {
233
- if (child.level === 3 && child.parentHash === entry.hash) {
234
- hasValidChildren = true;
235
- break;
236
- }
237
- }
238
- // 자식이 없는 중복된 명령어는 다른 섹션에서 복사된 것일 가능성이 높음
239
- if (!hasValidChildren && sections.size > 1) {
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
- // 6. 의심스러운 항목 수 보고
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
- // 7. 의심스러운 항목 제거된 결과 반환
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.7",
4
+ "version": "1.2.9",
5
5
  "skipLibCheck": true,
6
6
  "license": "0BSD",
7
7
  "main": "./dist/gatsby-node.js",