gatsby-source-notion-churnotion 1.2.7 → 1.2.8

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,129 @@ 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 항목 수집
34
- for (const entry of sortedToc) {
35
- if (entry.level === 1) {
36
- h1Sections.set(entry.hash, entry);
37
- h1Children.set(entry.hash, new Set());
38
- }
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());
39
36
  }
40
- // 2단계: 정당한 H2 항목 식별 (H1의 직접 자식)
37
+ // First pass: Collect all headings by hash
41
38
  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());
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);
48
43
  }
49
44
  }
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);
56
- }
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());
57
50
  }
58
- }
59
- // 4단계: 각 H1 섹션 내에서 일반적인 명령어 이름이 실제 내용을 가지는지 확인
60
- const commandSections = new Map(); // H1 해시 -> (명령어 이름 -> 유효성)
61
- const duplicateCommandNames = new Set();
62
- // 모든 섹션에서 명령어 이름 수집
51
+ invalidHashesCollectionByReason.get(reason).add(hash);
52
+ };
53
+ // Build parent-child relationship map
63
54
  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());
77
- }
78
- commandSections
79
- .get(entry.parentHash)
80
- .set(entry.title.toLowerCase(), hasChildren);
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}`);
81
80
  }
82
81
  }
83
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");
86
+ }
84
87
  }
85
- // 5단계: 유효하지 않은 명령어 항목 필터링
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
86
91
  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
- }
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);
99
+ }
100
+ }
101
+ }
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
+ // Get direct children of this server section
110
+ const directChildren = childrenByParent.get(serverSection.hash) || new Set();
111
+ // Check each child for common command pattern matching
112
+ for (const childHash of directChildren) {
113
+ const child = headingsByHash.get(childHash);
114
+ if (child && commonCommandPattern.test(child.title)) {
115
+ // Check if this child has valid descendants or content
116
+ const childHasChildren = childrenByParent.has(childHash) &&
117
+ childrenByParent.get(childHash).size > 0;
118
+ // Special check for service section headings - common commands shouldn't be here
119
+ // if they don't have meaningful content
120
+ if (!childHasChildren) {
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);
111
126
  }
112
127
  }
113
128
  }
114
129
  }
115
130
  }
116
131
  }
117
- // 유효하지 않은 항목 제외한 결과 생성
132
+ // Ensure the correct context titles reflect the parent relationship
133
+ for (const entry of sortedToc) {
134
+ if (entry.level === 2 && entry.parentHash) {
135
+ const parent = headingsByHash.get(entry.parentHash);
136
+ if (parent) {
137
+ entry.contextTitle = `${entry.title} (${parent.title})`;
138
+ }
139
+ }
140
+ else if (entry.level === 3 && entry.parentHash) {
141
+ // For H3, prefer its direct parent, whether H2 or H1
142
+ const parent = headingsByHash.get(entry.parentHash);
143
+ if (parent) {
144
+ entry.contextTitle = `${entry.title} (${parent.title})`;
145
+ }
146
+ }
147
+ }
148
+ // Create a filtered TOC without invalid entries
118
149
  const validToc = sortedToc.filter((entry) => !invalidEntries.has(entry.hash));
119
150
  if (invalidEntries.size > 0) {
120
- reporter.info(`Removed ${invalidEntries.size} invalid TOC entries from special sections`);
151
+ reporter.info(`Removed ${invalidEntries.size} invalid TOC entries`);
121
152
  }
122
153
  return validToc;
123
154
  }
@@ -4,104 +4,84 @@ 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 = ""; // 현재 명령어 섹션 추적
7
+ // 계층별 컨텍스트 저장
8
+ const headingStack = [];
9
+ let lastHeadingByLevel = [null, null, null, null]; // 인덱스 1부터 3까지 사용
13
10
  const resetTocContext = () => {
14
- lastH1Hash = "";
15
- lastH2Hash = "";
16
- lastH1Title = "";
17
- lastH2Title = "";
18
- currentCommandSection = "";
11
+ headingStack.length = 0;
12
+ lastHeadingByLevel = [null, null, null, null];
13
+ hashCache.clear();
19
14
  };
20
15
  exports.resetTocContext = resetTocContext;
16
+ // 필요한 수준의 상위 헤딩 찾기
17
+ const findParentHeading = (currentLevel) => {
18
+ // 자신보다 낮은 레벨의 가장 가까운 헤딩 찾기 (1 < 2 < 3)
19
+ for (let level = currentLevel - 1; level > 0; level--) {
20
+ if (lastHeadingByLevel[level]) {
21
+ return lastHeadingByLevel[level];
22
+ }
23
+ }
24
+ return null;
25
+ };
26
+ const generateHash = (text) => {
27
+ return `link-${text
28
+ .replace(/[^a-zA-Z0-9가-힣\s-_]/g, "")
29
+ .trim()
30
+ .replace(/\s+/g, "-")
31
+ .toLowerCase()}-${(0, crypto_1.randomUUID)().substring(0, 4)}`;
32
+ };
21
33
  const processTableOfContents = async (block, tableOfContents) => {
22
34
  if (["heading_1", "heading_2", "heading_3"].includes(block.type) &&
23
35
  block[block.type]?.rich_text?.length > 0) {
24
36
  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
- // 이미 처리된 플레인텍스트인지 확인
37
+ // 헤딩 레벨 추출 (heading_1 -> 1, heading_2 -> 2, heading_3 -> 3)
38
+ const level = parseInt(block.type.split("_")[1]);
39
+ // 현재 헤딩의 컨텍스트 ID 생성
40
+ const contextualId = `h${level}-${plainText}`;
41
+ // 해시 생성 또는 캐시에서 가져오기
63
42
  let hash;
64
43
  if (hashCache.has(contextualId)) {
65
44
  hash = hashCache.get(contextualId);
66
45
  }
67
46
  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)}`;
47
+ hash = generateHash(plainText);
73
48
  hashCache.set(contextualId, hash);
74
49
  }
75
- // 현재 해시 저장
76
- if (block.type === "heading_1") {
77
- lastH1Hash = hash;
78
- }
79
- else if (block.type === "heading_2") {
80
- lastH2Hash = hash;
81
- }
82
50
  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})`;
51
+ // 계층 구조 관리 - 현재 레벨보다 높은 모든 하위 헤딩 컨텍스트 삭제
52
+ // 예: H1이 나오면 H2, H3 컨텍스트 초기화
53
+ for (let i = level + 1; i < lastHeadingByLevel.length; i++) {
54
+ lastHeadingByLevel[i] = null;
55
+ }
56
+ // 부모 헤딩 찾기
57
+ const parentHeading = findParentHeading(level);
58
+ const parentHash = parentHeading ? parentHeading.hash : "";
59
+ // 부모 헤딩 스택에 자식으로 등록
60
+ if (parentHeading) {
61
+ parentHeading.children.push(hash);
93
62
  }
94
- // H2 항목도 컨텍스트 추가
95
- else if (level === 2) {
96
- contextTitle = `${plainText} (${lastH1Title})`;
63
+ // 컨텍스트 제목 생성
64
+ let contextTitle = plainText;
65
+ // 부모 컨텍스트 추가
66
+ if (level > 1 && parentHeading) {
67
+ contextTitle = `${plainText} (${parentHeading.title})`;
97
68
  }
69
+ // 자신을 현재 레벨의 마지막 헤딩으로 등록
70
+ const currentHeading = {
71
+ hash,
72
+ title: plainText,
73
+ level,
74
+ children: [],
75
+ };
76
+ lastHeadingByLevel[level] = currentHeading;
98
77
  // 중복 체크 - 동일한 hash가 이미 존재하는지 확인
99
78
  const existingTocIndex = tableOfContents.findIndex((toc) => toc.hash === hash);
100
79
  if (existingTocIndex === -1) {
80
+ // TOC 항목 추가
101
81
  tableOfContents.push({
102
82
  type: block.type,
103
83
  hash,
104
- title: displayTitle,
84
+ title: plainText,
105
85
  contextTitle,
106
86
  parentHash,
107
87
  level,
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.8",
5
5
  "skipLibCheck": true,
6
6
  "license": "0BSD",
7
7
  "main": "./dist/gatsby-node.js",