@cosmocoder/mcp-web-docs 1.0.0
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/LICENSE +22 -0
- package/README.md +368 -0
- package/build/__mocks__/embeddings.d.ts +17 -0
- package/build/__mocks__/embeddings.js +66 -0
- package/build/__mocks__/embeddings.js.map +1 -0
- package/build/config.d.ts +44 -0
- package/build/config.js +158 -0
- package/build/config.js.map +1 -0
- package/build/config.test.d.ts +1 -0
- package/build/config.test.js +165 -0
- package/build/config.test.js.map +1 -0
- package/build/crawler/auth.d.ts +128 -0
- package/build/crawler/auth.js +546 -0
- package/build/crawler/auth.js.map +1 -0
- package/build/crawler/auth.test.d.ts +1 -0
- package/build/crawler/auth.test.js +174 -0
- package/build/crawler/auth.test.js.map +1 -0
- package/build/crawler/base.d.ts +24 -0
- package/build/crawler/base.js +149 -0
- package/build/crawler/base.js.map +1 -0
- package/build/crawler/base.test.d.ts +1 -0
- package/build/crawler/base.test.js +234 -0
- package/build/crawler/base.test.js.map +1 -0
- package/build/crawler/browser-config.d.ts +2 -0
- package/build/crawler/browser-config.js +29 -0
- package/build/crawler/browser-config.js.map +1 -0
- package/build/crawler/browser-config.test.d.ts +1 -0
- package/build/crawler/browser-config.test.js +56 -0
- package/build/crawler/browser-config.test.js.map +1 -0
- package/build/crawler/cheerio.d.ts +11 -0
- package/build/crawler/cheerio.js +134 -0
- package/build/crawler/cheerio.js.map +1 -0
- package/build/crawler/chromium.d.ts +21 -0
- package/build/crawler/chromium.js +596 -0
- package/build/crawler/chromium.js.map +1 -0
- package/build/crawler/content-extractor-types.d.ts +25 -0
- package/build/crawler/content-extractor-types.js +2 -0
- package/build/crawler/content-extractor-types.js.map +1 -0
- package/build/crawler/content-extractors.d.ts +9 -0
- package/build/crawler/content-extractors.js +9 -0
- package/build/crawler/content-extractors.js.map +1 -0
- package/build/crawler/content-utils.d.ts +2 -0
- package/build/crawler/content-utils.js +22 -0
- package/build/crawler/content-utils.js.map +1 -0
- package/build/crawler/content-utils.test.d.ts +1 -0
- package/build/crawler/content-utils.test.js +99 -0
- package/build/crawler/content-utils.test.js.map +1 -0
- package/build/crawler/crawlee-crawler.d.ts +63 -0
- package/build/crawler/crawlee-crawler.js +342 -0
- package/build/crawler/crawlee-crawler.js.map +1 -0
- package/build/crawler/crawlee-crawler.test.d.ts +1 -0
- package/build/crawler/crawlee-crawler.test.js +280 -0
- package/build/crawler/crawlee-crawler.test.js.map +1 -0
- package/build/crawler/default-extractor.d.ts +4 -0
- package/build/crawler/default-extractor.js +26 -0
- package/build/crawler/default-extractor.js.map +1 -0
- package/build/crawler/default-extractor.test.d.ts +1 -0
- package/build/crawler/default-extractor.test.js +200 -0
- package/build/crawler/default-extractor.test.js.map +1 -0
- package/build/crawler/default.d.ts +11 -0
- package/build/crawler/default.js +138 -0
- package/build/crawler/default.js.map +1 -0
- package/build/crawler/docs-crawler.d.ts +26 -0
- package/build/crawler/docs-crawler.js +97 -0
- package/build/crawler/docs-crawler.js.map +1 -0
- package/build/crawler/docs-crawler.test.d.ts +1 -0
- package/build/crawler/docs-crawler.test.js +185 -0
- package/build/crawler/docs-crawler.test.js.map +1 -0
- package/build/crawler/factory.d.ts +6 -0
- package/build/crawler/factory.js +83 -0
- package/build/crawler/factory.js.map +1 -0
- package/build/crawler/github-pages-extractor.d.ts +4 -0
- package/build/crawler/github-pages-extractor.js +33 -0
- package/build/crawler/github-pages-extractor.js.map +1 -0
- package/build/crawler/github-pages-extractor.test.d.ts +1 -0
- package/build/crawler/github-pages-extractor.test.js +184 -0
- package/build/crawler/github-pages-extractor.test.js.map +1 -0
- package/build/crawler/github.d.ts +20 -0
- package/build/crawler/github.js +181 -0
- package/build/crawler/github.js.map +1 -0
- package/build/crawler/github.test.d.ts +1 -0
- package/build/crawler/github.test.js +326 -0
- package/build/crawler/github.test.js.map +1 -0
- package/build/crawler/puppeteer.d.ts +16 -0
- package/build/crawler/puppeteer.js +191 -0
- package/build/crawler/puppeteer.js.map +1 -0
- package/build/crawler/queue-manager.d.ts +43 -0
- package/build/crawler/queue-manager.js +169 -0
- package/build/crawler/queue-manager.js.map +1 -0
- package/build/crawler/queue-manager.test.d.ts +1 -0
- package/build/crawler/queue-manager.test.js +509 -0
- package/build/crawler/queue-manager.test.js.map +1 -0
- package/build/crawler/site-rules.d.ts +11 -0
- package/build/crawler/site-rules.js +104 -0
- package/build/crawler/site-rules.js.map +1 -0
- package/build/crawler/site-rules.test.d.ts +1 -0
- package/build/crawler/site-rules.test.js +139 -0
- package/build/crawler/site-rules.test.js.map +1 -0
- package/build/crawler/storybook-extractor.d.ts +34 -0
- package/build/crawler/storybook-extractor.js +767 -0
- package/build/crawler/storybook-extractor.js.map +1 -0
- package/build/crawler/storybook-extractor.test.d.ts +1 -0
- package/build/crawler/storybook-extractor.test.js +491 -0
- package/build/crawler/storybook-extractor.test.js.map +1 -0
- package/build/embeddings/fastembed.d.ts +25 -0
- package/build/embeddings/fastembed.js +188 -0
- package/build/embeddings/fastembed.js.map +1 -0
- package/build/embeddings/fastembed.test.d.ts +1 -0
- package/build/embeddings/fastembed.test.js +307 -0
- package/build/embeddings/fastembed.test.js.map +1 -0
- package/build/embeddings/openai.d.ts +8 -0
- package/build/embeddings/openai.js +56 -0
- package/build/embeddings/openai.js.map +1 -0
- package/build/embeddings/types.d.ts +4 -0
- package/build/embeddings/types.js +2 -0
- package/build/embeddings/types.js.map +1 -0
- package/build/index.d.ts +2 -0
- package/build/index.js +1007 -0
- package/build/index.js.map +1 -0
- package/build/index.test.d.ts +1 -0
- package/build/index.test.js +364 -0
- package/build/index.test.js.map +1 -0
- package/build/indexing/queue-manager.d.ts +36 -0
- package/build/indexing/queue-manager.js +86 -0
- package/build/indexing/queue-manager.js.map +1 -0
- package/build/indexing/queue-manager.test.d.ts +1 -0
- package/build/indexing/queue-manager.test.js +257 -0
- package/build/indexing/queue-manager.test.js.map +1 -0
- package/build/indexing/status.d.ts +39 -0
- package/build/indexing/status.js +207 -0
- package/build/indexing/status.js.map +1 -0
- package/build/indexing/status.test.d.ts +1 -0
- package/build/indexing/status.test.js +246 -0
- package/build/indexing/status.test.js.map +1 -0
- package/build/processor/content.d.ts +16 -0
- package/build/processor/content.js +286 -0
- package/build/processor/content.js.map +1 -0
- package/build/processor/content.test.d.ts +1 -0
- package/build/processor/content.test.js +369 -0
- package/build/processor/content.test.js.map +1 -0
- package/build/processor/markdown.d.ts +11 -0
- package/build/processor/markdown.js +256 -0
- package/build/processor/markdown.js.map +1 -0
- package/build/processor/markdown.test.d.ts +1 -0
- package/build/processor/markdown.test.js +312 -0
- package/build/processor/markdown.test.js.map +1 -0
- package/build/processor/metadata-parser.d.ts +37 -0
- package/build/processor/metadata-parser.js +245 -0
- package/build/processor/metadata-parser.js.map +1 -0
- package/build/processor/metadata-parser.test.d.ts +1 -0
- package/build/processor/metadata-parser.test.js +357 -0
- package/build/processor/metadata-parser.test.js.map +1 -0
- package/build/processor/processor.d.ts +8 -0
- package/build/processor/processor.js +190 -0
- package/build/processor/processor.js.map +1 -0
- package/build/processor/processor.test.d.ts +1 -0
- package/build/processor/processor.test.js +357 -0
- package/build/processor/processor.test.js.map +1 -0
- package/build/rag/cache.d.ts +10 -0
- package/build/rag/cache.js +10 -0
- package/build/rag/cache.js.map +1 -0
- package/build/rag/code-generator.d.ts +11 -0
- package/build/rag/code-generator.js +30 -0
- package/build/rag/code-generator.js.map +1 -0
- package/build/rag/context-assembler.d.ts +23 -0
- package/build/rag/context-assembler.js +113 -0
- package/build/rag/context-assembler.js.map +1 -0
- package/build/rag/docs-search.d.ts +55 -0
- package/build/rag/docs-search.js +380 -0
- package/build/rag/docs-search.js.map +1 -0
- package/build/rag/pipeline.d.ts +26 -0
- package/build/rag/pipeline.js +91 -0
- package/build/rag/pipeline.js.map +1 -0
- package/build/rag/query-processor.d.ts +14 -0
- package/build/rag/query-processor.js +57 -0
- package/build/rag/query-processor.js.map +1 -0
- package/build/rag/reranker.d.ts +55 -0
- package/build/rag/reranker.js +210 -0
- package/build/rag/reranker.js.map +1 -0
- package/build/rag/response-generator.d.ts +20 -0
- package/build/rag/response-generator.js +101 -0
- package/build/rag/response-generator.js.map +1 -0
- package/build/rag/retriever.d.ts +19 -0
- package/build/rag/retriever.js +111 -0
- package/build/rag/retriever.js.map +1 -0
- package/build/rag/validator.d.ts +22 -0
- package/build/rag/validator.js +128 -0
- package/build/rag/validator.js.map +1 -0
- package/build/rag/version-manager.d.ts +23 -0
- package/build/rag/version-manager.js +98 -0
- package/build/rag/version-manager.js.map +1 -0
- package/build/setupTests.d.ts +4 -0
- package/build/setupTests.js +50 -0
- package/build/setupTests.js.map +1 -0
- package/build/storage/storage.d.ts +38 -0
- package/build/storage/storage.js +700 -0
- package/build/storage/storage.js.map +1 -0
- package/build/storage/storage.test.d.ts +1 -0
- package/build/storage/storage.test.js +338 -0
- package/build/storage/storage.test.js.map +1 -0
- package/build/types/rag.d.ts +27 -0
- package/build/types/rag.js +2 -0
- package/build/types/rag.js.map +1 -0
- package/build/types.d.ts +120 -0
- package/build/types.js +2 -0
- package/build/types.js.map +1 -0
- package/build/util/content-utils.d.ts +31 -0
- package/build/util/content-utils.js +120 -0
- package/build/util/content-utils.js.map +1 -0
- package/build/util/content.d.ts +1 -0
- package/build/util/content.js +16 -0
- package/build/util/content.js.map +1 -0
- package/build/util/docs.d.ts +1 -0
- package/build/util/docs.js +26 -0
- package/build/util/docs.js.map +1 -0
- package/build/util/docs.test.d.ts +1 -0
- package/build/util/docs.test.js +49 -0
- package/build/util/docs.test.js.map +1 -0
- package/build/util/favicon.d.ts +6 -0
- package/build/util/favicon.js +88 -0
- package/build/util/favicon.js.map +1 -0
- package/build/util/favicon.test.d.ts +1 -0
- package/build/util/favicon.test.js +140 -0
- package/build/util/favicon.test.js.map +1 -0
- package/build/util/logger.d.ts +17 -0
- package/build/util/logger.js +72 -0
- package/build/util/logger.js.map +1 -0
- package/build/util/logger.test.d.ts +1 -0
- package/build/util/logger.test.js +46 -0
- package/build/util/logger.test.js.map +1 -0
- package/build/util/security.d.ts +312 -0
- package/build/util/security.js +719 -0
- package/build/util/security.js.map +1 -0
- package/build/util/security.test.d.ts +1 -0
- package/build/util/security.test.js +524 -0
- package/build/util/security.test.js.map +1 -0
- package/build/util/site-detector.d.ts +22 -0
- package/build/util/site-detector.js +42 -0
- package/build/util/site-detector.js.map +1 -0
- package/package.json +112 -0
|
@@ -0,0 +1,767 @@
|
|
|
1
|
+
// NOTE: This class is serialized and runs in the browser via Playwright's evaluate()
|
|
2
|
+
// Do NOT import Node.js modules here - use console.error for logging
|
|
3
|
+
export class StorybookExtractor {
|
|
4
|
+
addContentToSections(content, sections, addedSections) {
|
|
5
|
+
const trimmed = content.trim();
|
|
6
|
+
if (trimmed && !addedSections.has(trimmed)) {
|
|
7
|
+
sections.push(trimmed);
|
|
8
|
+
addedSections.add(trimmed);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
isElementVisible(element) {
|
|
12
|
+
const style = window.getComputedStyle(element);
|
|
13
|
+
return style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0';
|
|
14
|
+
}
|
|
15
|
+
formatCodeBlock(code, language) {
|
|
16
|
+
// Clean up the code
|
|
17
|
+
const cleanCode = code
|
|
18
|
+
.replace(/^\s+|\s+$/g, '') // Trim whitespace
|
|
19
|
+
.replace(/\t/g, ' ') // Convert tabs to spaces
|
|
20
|
+
.replace(/\n{3,}/g, '\n\n') // Reduce multiple blank lines
|
|
21
|
+
.replace(/\u00A0/g, ' '); // Replace non-breaking spaces
|
|
22
|
+
return `\`\`\`${language}\n${cleanCode}\n\`\`\``;
|
|
23
|
+
}
|
|
24
|
+
extractLinks(element) {
|
|
25
|
+
let content = element.innerHTML;
|
|
26
|
+
// Handle links
|
|
27
|
+
const links = element.querySelectorAll('a');
|
|
28
|
+
links.forEach((link) => {
|
|
29
|
+
const text = link.textContent?.trim();
|
|
30
|
+
const href = link.getAttribute('href');
|
|
31
|
+
if (text && href) {
|
|
32
|
+
const linkHtml = link.outerHTML;
|
|
33
|
+
content = content.replace(linkHtml, `[${text}](${href})`);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
// Handle inline code elements
|
|
37
|
+
const codeElements = element.querySelectorAll('code, [class*="code"], [class*="inline-code"], [class*="monospace"]');
|
|
38
|
+
codeElements.forEach((code) => {
|
|
39
|
+
const text = code.textContent?.trim();
|
|
40
|
+
if (text) {
|
|
41
|
+
const codeHtml = code.outerHTML;
|
|
42
|
+
content = content.replace(codeHtml, `\`${text}\``);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
const div = element.ownerDocument.createElement('div');
|
|
46
|
+
div.innerHTML = content;
|
|
47
|
+
return div.textContent?.trim() || '';
|
|
48
|
+
}
|
|
49
|
+
async processSection(section, sections, addedSections) {
|
|
50
|
+
try {
|
|
51
|
+
const heading = section.querySelector('h2, h3, h4') || section.closest('section')?.querySelector('h2, h3, h4');
|
|
52
|
+
if (heading) {
|
|
53
|
+
const title = heading.textContent?.trim();
|
|
54
|
+
if (title && !addedSections.has(`## ${title}`)) {
|
|
55
|
+
this.addContentToSections(`## ${title}`, sections, addedSections);
|
|
56
|
+
this.addContentToSections('', sections, addedSections);
|
|
57
|
+
let current = heading.nextElementSibling;
|
|
58
|
+
while (current && !current.matches('h2, h3, h4')) {
|
|
59
|
+
await this.processSectionContent(current, sections, addedSections);
|
|
60
|
+
current = current.nextElementSibling;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const remainingContent = Array.from(section.children).filter((el) => !el.matches('h2, h3, h4') && (!heading || !heading.contains(el)));
|
|
65
|
+
for (const content of remainingContent) {
|
|
66
|
+
await this.processSectionContent(content, sections, addedSections);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
console.error('Error processing section:', error);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
async processSectionContent(element, sections, addedSections) {
|
|
74
|
+
if (!element)
|
|
75
|
+
return;
|
|
76
|
+
try {
|
|
77
|
+
if (!this.isElementVisible(element))
|
|
78
|
+
return;
|
|
79
|
+
if (element.matches('p, div[class*="description"], [class*="markdown"], [class*="text"], [class*="content"], [class*="docblock-text"]')) {
|
|
80
|
+
const text = this.extractLinks(element);
|
|
81
|
+
if (text) {
|
|
82
|
+
this.addContentToSections(text, sections, addedSections);
|
|
83
|
+
this.addContentToSections('', sections, addedSections);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (element.matches('pre.prismjs')) {
|
|
87
|
+
const code = element.textContent?.trim() || '';
|
|
88
|
+
if (code) {
|
|
89
|
+
const language = element.className.match(/language-(\w+)/)?.[1] || 'typescript';
|
|
90
|
+
const formattedCode = this.formatCodeBlock(code, language);
|
|
91
|
+
this.addContentToSections(formattedCode, sections, addedSections);
|
|
92
|
+
this.addContentToSections('', sections, addedSections);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (element.matches('table')) {
|
|
96
|
+
await this.processTableContent(element, sections, addedSections);
|
|
97
|
+
}
|
|
98
|
+
if (element.matches('div')) {
|
|
99
|
+
const name = element.textContent?.trim();
|
|
100
|
+
const nextElement = element.nextElementSibling;
|
|
101
|
+
if (name && nextElement) {
|
|
102
|
+
const hexValue = nextElement.textContent?.trim();
|
|
103
|
+
if (hexValue && hexValue.startsWith('#')) {
|
|
104
|
+
this.addContentToSections(`${name}: ${hexValue}`, sections, addedSections);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const button = element.querySelector('button');
|
|
109
|
+
if (button?.textContent?.trim() === 'Expand') {
|
|
110
|
+
try {
|
|
111
|
+
button.click();
|
|
112
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
console.error('Error clicking expand button:', error);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const codeButtons = Array.from(element.querySelectorAll('button')).filter((button) => {
|
|
119
|
+
const text = button.textContent?.toLowerCase() || '';
|
|
120
|
+
return text.includes('show code');
|
|
121
|
+
});
|
|
122
|
+
for (const button of codeButtons) {
|
|
123
|
+
try {
|
|
124
|
+
if (!this.isElementVisible(button))
|
|
125
|
+
continue;
|
|
126
|
+
button.click();
|
|
127
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
128
|
+
const codeBlocks = element.querySelectorAll('pre.prismjs');
|
|
129
|
+
for (const block of codeBlocks) {
|
|
130
|
+
if (!this.isElementVisible(block))
|
|
131
|
+
continue;
|
|
132
|
+
const code = block.textContent?.trim() || '';
|
|
133
|
+
if (code) {
|
|
134
|
+
const language = block.className.match(/language-(\w+)/)?.[1] || 'typescript';
|
|
135
|
+
const formattedCode = this.formatCodeBlock(code, language);
|
|
136
|
+
this.addContentToSections(formattedCode, sections, addedSections);
|
|
137
|
+
this.addContentToSections('', sections, addedSections);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
button.click();
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
console.error('Error handling code button:', error);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const children = Array.from(element.children).filter((el) => {
|
|
147
|
+
if (el.matches('h1, h2, h3, h4'))
|
|
148
|
+
return false;
|
|
149
|
+
if (el.matches('script, style, iframe'))
|
|
150
|
+
return false;
|
|
151
|
+
if (element.matches('pre, code') && el.matches('pre, code'))
|
|
152
|
+
return false;
|
|
153
|
+
return true;
|
|
154
|
+
});
|
|
155
|
+
for (const child of children) {
|
|
156
|
+
await this.processSectionContent(child, sections, addedSections);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
console.error('Error processing section content:', error);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
async processPropsTable(table, sections, addedSections, componentName) {
|
|
164
|
+
// First, expand all "Show more" buttons in the table to reveal all type values
|
|
165
|
+
await this.expandAllTypeValues(table);
|
|
166
|
+
// Check if there's already a heading before this table that includes a component name
|
|
167
|
+
// This handles cases like "TableHead Props", "TableBody Props" etc.
|
|
168
|
+
let propsHeading = '## Props';
|
|
169
|
+
// Look for the closest preceding heading
|
|
170
|
+
const closestHeading = this.findClosestPrecedingHeading(table);
|
|
171
|
+
if (closestHeading) {
|
|
172
|
+
const headingText = closestHeading.trim();
|
|
173
|
+
// If the heading already contains "Props" with a component name, use it as-is
|
|
174
|
+
if (/\w+\s+Props$/i.test(headingText)) {
|
|
175
|
+
propsHeading = `## ${headingText}`;
|
|
176
|
+
}
|
|
177
|
+
else if (/^Props$/i.test(headingText) && componentName) {
|
|
178
|
+
// If it's just "Props", prepend the component name
|
|
179
|
+
propsHeading = `## ${componentName} Props`;
|
|
180
|
+
}
|
|
181
|
+
else if (componentName) {
|
|
182
|
+
// Heading doesn't contain Props pattern, use component name
|
|
183
|
+
propsHeading = `## ${componentName} Props`;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
else if (componentName) {
|
|
187
|
+
// No heading found, use component name
|
|
188
|
+
propsHeading = `## ${componentName} Props`;
|
|
189
|
+
}
|
|
190
|
+
this.addContentToSections(propsHeading, sections, addedSections);
|
|
191
|
+
this.addContentToSections('', sections, addedSections);
|
|
192
|
+
this.addContentToSections('| Name | Type | Default |', sections, addedSections);
|
|
193
|
+
this.addContentToSections('|------|------|---------|', sections, addedSections);
|
|
194
|
+
const rows = Array.from(table.querySelectorAll('tr, tbody > div[role="row"]'));
|
|
195
|
+
for (const row of rows) {
|
|
196
|
+
// Skip header rows
|
|
197
|
+
if (row.querySelector('th, [role="columnheader"]'))
|
|
198
|
+
continue;
|
|
199
|
+
const cells = row.querySelectorAll('td, [role="cell"]');
|
|
200
|
+
if (cells.length === 0)
|
|
201
|
+
continue;
|
|
202
|
+
let name = '';
|
|
203
|
+
let type = '';
|
|
204
|
+
let defaultValue = '-';
|
|
205
|
+
let required = false;
|
|
206
|
+
// Cell 0: Name
|
|
207
|
+
if (cells[0]) {
|
|
208
|
+
const nameCell = cells[0];
|
|
209
|
+
name = nameCell.textContent?.trim().split('\n')[0] || '';
|
|
210
|
+
required = name.includes('*') || nameCell.querySelector('[class*="required"]') !== null;
|
|
211
|
+
name = name.replace(/\*$/, '').trim();
|
|
212
|
+
}
|
|
213
|
+
// Cell 1: Type (in Storybook 7+, this is the Description column but contains type)
|
|
214
|
+
if (cells[1]) {
|
|
215
|
+
type = this.extractTypeFromCell(cells[1]);
|
|
216
|
+
}
|
|
217
|
+
// Cell 2: Default value
|
|
218
|
+
if (cells[2]) {
|
|
219
|
+
const defaultCell = cells[2];
|
|
220
|
+
defaultValue = defaultCell.textContent?.trim() || '-';
|
|
221
|
+
}
|
|
222
|
+
// If only 2 cells, second might be default
|
|
223
|
+
if (cells.length === 2 && !type) {
|
|
224
|
+
defaultValue = cells[1]?.textContent?.trim() || '-';
|
|
225
|
+
type = '-';
|
|
226
|
+
}
|
|
227
|
+
// Clean default value
|
|
228
|
+
const formattedDefault = defaultValue
|
|
229
|
+
.replace(/\|/g, '\\|')
|
|
230
|
+
.replace(/^"([^"]+)"$/, '`"$1"`')
|
|
231
|
+
.replace(/^'([^']+)'$/, "`'$1'`");
|
|
232
|
+
const formattedName = required ? `${name}*` : name;
|
|
233
|
+
if (name && name !== 'Name') {
|
|
234
|
+
this.addContentToSections(`| ${formattedName} | ${type || '-'} | ${formattedDefault} |`, sections, addedSections);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
this.addContentToSections('', sections, addedSections);
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Find the closest heading element before the given element
|
|
241
|
+
* This helps detect if a Props table already has a component-prefixed heading
|
|
242
|
+
*/
|
|
243
|
+
findClosestPrecedingHeading(element) {
|
|
244
|
+
// Check previous siblings
|
|
245
|
+
let sibling = element.previousElementSibling;
|
|
246
|
+
let depth = 0;
|
|
247
|
+
const maxDepth = 5; // Don't look too far back
|
|
248
|
+
while (sibling && depth < maxDepth) {
|
|
249
|
+
// Check if this sibling is a heading
|
|
250
|
+
if (sibling.matches('h1, h2, h3, h4, h5, h6')) {
|
|
251
|
+
return sibling.textContent?.trim() || null;
|
|
252
|
+
}
|
|
253
|
+
// Check if sibling contains a heading
|
|
254
|
+
const heading = sibling.querySelector('h1, h2, h3, h4, h5, h6');
|
|
255
|
+
if (heading) {
|
|
256
|
+
return heading.textContent?.trim() || null;
|
|
257
|
+
}
|
|
258
|
+
sibling = sibling.previousElementSibling;
|
|
259
|
+
depth++;
|
|
260
|
+
}
|
|
261
|
+
// Check parent's previous siblings
|
|
262
|
+
let parent = element.parentElement;
|
|
263
|
+
depth = 0;
|
|
264
|
+
while (parent && depth < 3) {
|
|
265
|
+
sibling = parent.previousElementSibling;
|
|
266
|
+
while (sibling && depth < maxDepth) {
|
|
267
|
+
if (sibling.matches('h1, h2, h3, h4, h5, h6')) {
|
|
268
|
+
return sibling.textContent?.trim() || null;
|
|
269
|
+
}
|
|
270
|
+
const heading = sibling.querySelector('h1, h2, h3, h4, h5, h6');
|
|
271
|
+
if (heading) {
|
|
272
|
+
return heading.textContent?.trim() || null;
|
|
273
|
+
}
|
|
274
|
+
sibling = sibling.previousElementSibling;
|
|
275
|
+
depth++;
|
|
276
|
+
}
|
|
277
|
+
parent = parent.parentElement;
|
|
278
|
+
}
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Click all "Show more" buttons in a props table to expand type values
|
|
283
|
+
*/
|
|
284
|
+
async expandAllTypeValues(table) {
|
|
285
|
+
try {
|
|
286
|
+
// Find all "Show X more" buttons and click them
|
|
287
|
+
const showMoreButtons = table.querySelectorAll('button');
|
|
288
|
+
for (const button of showMoreButtons) {
|
|
289
|
+
const text = button.textContent?.toLowerCase() || '';
|
|
290
|
+
if (text.includes('show') && (text.includes('more') || /\d+/.test(text))) {
|
|
291
|
+
try {
|
|
292
|
+
button.click();
|
|
293
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
294
|
+
}
|
|
295
|
+
catch {
|
|
296
|
+
// Ignore click errors
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
// Wait for expansion to complete
|
|
301
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
302
|
+
}
|
|
303
|
+
catch (error) {
|
|
304
|
+
console.error('[StorybookExtractor] Error expanding type values:', error);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Extract type values from a table cell, handling Storybook's various DOM structures
|
|
309
|
+
*/
|
|
310
|
+
extractTypeFromCell(cell) {
|
|
311
|
+
const typeValues = [];
|
|
312
|
+
const seenValues = new Set();
|
|
313
|
+
// Helper to add a value if it's valid and not seen
|
|
314
|
+
const addValue = (val) => {
|
|
315
|
+
if (!val)
|
|
316
|
+
return;
|
|
317
|
+
const cleaned = val
|
|
318
|
+
.trim()
|
|
319
|
+
.replace(/^["']|["']$/g, '') // Remove surrounding quotes
|
|
320
|
+
.replace(/^\||\|$/g, '') // Remove leading/trailing pipes
|
|
321
|
+
.trim();
|
|
322
|
+
// Skip invalid values
|
|
323
|
+
if (!cleaned ||
|
|
324
|
+
cleaned.length < 1 ||
|
|
325
|
+
cleaned === '-' ||
|
|
326
|
+
/^show/i.test(cleaned) ||
|
|
327
|
+
/more\.\.\.?$/i.test(cleaned) ||
|
|
328
|
+
/^less\.\.\.?$/i.test(cleaned) ||
|
|
329
|
+
/^deprecated/i.test(cleaned) ||
|
|
330
|
+
seenValues.has(cleaned.toLowerCase())) {
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
seenValues.add(cleaned.toLowerCase());
|
|
334
|
+
typeValues.push(cleaned);
|
|
335
|
+
};
|
|
336
|
+
// Strategy 1: Look for specific type value containers (Storybook 7+)
|
|
337
|
+
// These are usually spans/divs with specific classes containing individual values
|
|
338
|
+
const typeContainers = cell.querySelectorAll('[class*="argType"] span, ' +
|
|
339
|
+
'[class*="type-"] span, ' +
|
|
340
|
+
'[class*="union"] > span, ' +
|
|
341
|
+
'.css-in3yi3, ' + // Common Storybook class for type values
|
|
342
|
+
'span[title]' // Spans with title attributes often contain type info
|
|
343
|
+
);
|
|
344
|
+
if (typeContainers.length > 0) {
|
|
345
|
+
for (const container of typeContainers) {
|
|
346
|
+
// Get the direct text, not nested button text
|
|
347
|
+
const hasButton = container.querySelector('button');
|
|
348
|
+
if (hasButton)
|
|
349
|
+
continue;
|
|
350
|
+
const text = container.textContent?.trim();
|
|
351
|
+
addValue(text);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
// Strategy 2: If we have quoted strings in the cell, extract them
|
|
355
|
+
const cellText = cell.textContent || '';
|
|
356
|
+
const quotedMatches = cellText.match(/"([^"]+)"|'([^']+)'/g);
|
|
357
|
+
if (quotedMatches) {
|
|
358
|
+
for (const match of quotedMatches) {
|
|
359
|
+
addValue(match.replace(/["']/g, ''));
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
// Strategy 3: Look for literal type spans (often have specific styling)
|
|
363
|
+
const literalSpans = cell.querySelectorAll('span');
|
|
364
|
+
for (const span of literalSpans) {
|
|
365
|
+
// Only consider leaf spans (no child elements except text)
|
|
366
|
+
if (span.children.length === 0) {
|
|
367
|
+
const text = span.textContent?.trim();
|
|
368
|
+
// Check if it looks like a type value (quoted, or specific format)
|
|
369
|
+
if (text &&
|
|
370
|
+
(text.startsWith('"') ||
|
|
371
|
+
text.startsWith("'") ||
|
|
372
|
+
/^[a-z]+$/i.test(text) || // Simple word like "boolean", "string"
|
|
373
|
+
/^[A-Z][a-zA-Z<>[\]]+$/.test(text))) {
|
|
374
|
+
// Type like "Ref<HTMLElement>"
|
|
375
|
+
addValue(text);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
// Strategy 4: Check for code elements
|
|
380
|
+
const codeElements = cell.querySelectorAll('code');
|
|
381
|
+
for (const code of codeElements) {
|
|
382
|
+
addValue(code.textContent);
|
|
383
|
+
}
|
|
384
|
+
// Strategy 5: Fallback - parse the text content intelligently
|
|
385
|
+
if (typeValues.length === 0) {
|
|
386
|
+
// Clean up the full text
|
|
387
|
+
const fullText = cellText
|
|
388
|
+
.replace(/Show \d+ more\.\.\.?/gi, '')
|
|
389
|
+
.replace(/Show less\.\.\.?/gi, '')
|
|
390
|
+
.replace(/Deprecated:[^|]*/gi, '')
|
|
391
|
+
.trim();
|
|
392
|
+
// Check if it's a simple type (boolean, string, number, any, etc.)
|
|
393
|
+
if (/^(boolean|string|number|any|never|void|null|undefined|object|function)$/i.test(fullText)) {
|
|
394
|
+
return fullText.toLowerCase();
|
|
395
|
+
}
|
|
396
|
+
// Check if it looks like a React type
|
|
397
|
+
if (/^(Ref|React\.|HTMLElement|JSX\.)/i.test(fullText)) {
|
|
398
|
+
return fullText;
|
|
399
|
+
}
|
|
400
|
+
// Try to split by common separators if text has multiple values
|
|
401
|
+
if (fullText.includes('|') || fullText.includes(' or ')) {
|
|
402
|
+
const parts = fullText.split(/\s*\|\s*|\s+or\s+/);
|
|
403
|
+
for (const part of parts) {
|
|
404
|
+
addValue(part);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
else {
|
|
408
|
+
// Last resort: just use the text as-is
|
|
409
|
+
return fullText || '-';
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
// Format the output
|
|
413
|
+
if (typeValues.length === 0) {
|
|
414
|
+
return '-';
|
|
415
|
+
}
|
|
416
|
+
// Join with escaped pipe separators (\ |) so they don't break markdown tables
|
|
417
|
+
return typeValues.join(' \\| ');
|
|
418
|
+
}
|
|
419
|
+
async waitForSidebar(document) {
|
|
420
|
+
const maxAttempts = 20;
|
|
421
|
+
const delay = 250;
|
|
422
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
423
|
+
const sidebar = document.querySelector('[class*="sidebar"]');
|
|
424
|
+
if (sidebar) {
|
|
425
|
+
// Wait for sidebar content to load
|
|
426
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
async expandSidebarSections(document) {
|
|
433
|
+
try {
|
|
434
|
+
// Wait for sidebar to be ready
|
|
435
|
+
await this.waitForSidebar(document);
|
|
436
|
+
// Find all sidebar-subheading-action buttons (the "Show/Hide" buttons)
|
|
437
|
+
const sidebarButtons = document.querySelectorAll('button.sidebar-subheading-action');
|
|
438
|
+
console.error(`[StorybookExtractor] Found ${sidebarButtons.length} sidebar buttons to expand`);
|
|
439
|
+
// First pass: Click all buttons to show all sections
|
|
440
|
+
for (const button of sidebarButtons) {
|
|
441
|
+
if (this.isElementVisible(button)) {
|
|
442
|
+
button.click();
|
|
443
|
+
// Wait for content to update
|
|
444
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
// Wait for any new buttons that might appear
|
|
448
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
449
|
+
// Second pass: Click any new buttons that appeared
|
|
450
|
+
const newButtons = document.querySelectorAll('button.sidebar-subheading-action');
|
|
451
|
+
console.error(`[StorybookExtractor] Found ${newButtons.length} total sidebar buttons after expansion`);
|
|
452
|
+
for (const button of newButtons) {
|
|
453
|
+
if (this.isElementVisible(button)) {
|
|
454
|
+
button.click();
|
|
455
|
+
// Wait for content to update
|
|
456
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
// Final wait to ensure all sections have expanded
|
|
460
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
461
|
+
}
|
|
462
|
+
catch (error) {
|
|
463
|
+
console.error('[StorybookExtractor] Error expanding sidebar sections:', error);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
async waitForStorybookContent(document) {
|
|
467
|
+
const maxAttempts = 10;
|
|
468
|
+
const delay = 500;
|
|
469
|
+
// First, expand all sidebar sections
|
|
470
|
+
await this.expandSidebarSections(document);
|
|
471
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
472
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
473
|
+
const mainArea = document.querySelector('.sbdocs-content, #docs-root');
|
|
474
|
+
if (!mainArea)
|
|
475
|
+
continue;
|
|
476
|
+
const hasContent = mainArea.querySelector('h1') &&
|
|
477
|
+
(mainArea.querySelector('p') ||
|
|
478
|
+
mainArea.querySelector('table') ||
|
|
479
|
+
mainArea.querySelector('ul, ol') ||
|
|
480
|
+
mainArea.querySelector('[class*="docblock"]'));
|
|
481
|
+
if (hasContent) {
|
|
482
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
483
|
+
return mainArea;
|
|
484
|
+
}
|
|
485
|
+
const loadingIndicator = document.querySelector('[class*="loading"], [class*="pending"]');
|
|
486
|
+
if (!loadingIndicator) {
|
|
487
|
+
console.warn('No loading indicator found but content is missing');
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
return null;
|
|
491
|
+
}
|
|
492
|
+
async processTableContent(table, sections, addedSections) {
|
|
493
|
+
try {
|
|
494
|
+
const headers = Array.from(table.querySelectorAll('th')).map((th) => th.textContent?.trim() || '');
|
|
495
|
+
if (headers.length === 0)
|
|
496
|
+
return;
|
|
497
|
+
this.addContentToSections(`| ${headers.join(' | ')} |`, sections, addedSections);
|
|
498
|
+
this.addContentToSections(`| ${headers.map(() => '---').join(' | ')} |`, sections, addedSections);
|
|
499
|
+
const rows = table.querySelectorAll('tr');
|
|
500
|
+
for (const row of rows) {
|
|
501
|
+
const cells = Array.from(row.querySelectorAll('td')).map((td) => {
|
|
502
|
+
const text = this.extractLinks(td);
|
|
503
|
+
return text.replace(/\|/g, '\\|');
|
|
504
|
+
});
|
|
505
|
+
if (cells.length > 0) {
|
|
506
|
+
this.addContentToSections(`| ${cells.join(' | ')} |`, sections, addedSections);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
this.addContentToSections('', sections, addedSections);
|
|
510
|
+
}
|
|
511
|
+
catch (error) {
|
|
512
|
+
console.error('Error processing table:', error);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
async processListContent(list, sections, addedSections) {
|
|
516
|
+
try {
|
|
517
|
+
const items = list.querySelectorAll('li');
|
|
518
|
+
for (const item of items) {
|
|
519
|
+
const text = this.extractLinks(item);
|
|
520
|
+
if (text) {
|
|
521
|
+
const prefix = list.tagName.toLowerCase() === 'ol' ? '1. ' : '- ';
|
|
522
|
+
this.addContentToSections(`${prefix}${text}`, sections, addedSections);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
this.addContentToSections('', sections, addedSections);
|
|
526
|
+
}
|
|
527
|
+
catch (error) {
|
|
528
|
+
console.error('Error processing list:', error);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Extract type annotations from inline code examples and prop documentation
|
|
533
|
+
*/
|
|
534
|
+
async extractTypeAnnotations(mainArea, sections, addedSections) {
|
|
535
|
+
try {
|
|
536
|
+
// Look for type definitions in code blocks
|
|
537
|
+
const codeBlocks = mainArea.querySelectorAll('pre code, .prismjs, [class*="highlight"]');
|
|
538
|
+
for (const block of codeBlocks) {
|
|
539
|
+
const code = block.textContent || '';
|
|
540
|
+
// Extract TypeScript interface/type definitions
|
|
541
|
+
const interfaceMatch = code.match(/interface\s+(\w+Props?)\s*\{([^}]+)\}/);
|
|
542
|
+
if (interfaceMatch) {
|
|
543
|
+
const [, name, body] = interfaceMatch;
|
|
544
|
+
if (!addedSections.has(`## ${name} Type`)) {
|
|
545
|
+
this.addContentToSections(`## ${name} Type`, sections, addedSections);
|
|
546
|
+
this.addContentToSections('```typescript', sections, addedSections);
|
|
547
|
+
this.addContentToSections(`interface ${name} {${body}}`, sections, addedSections);
|
|
548
|
+
this.addContentToSections('```', sections, addedSections);
|
|
549
|
+
this.addContentToSections('', sections, addedSections);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
// Extract type alias definitions
|
|
553
|
+
const typeMatch = code.match(/type\s+(\w+)\s*=\s*([^;]+);/);
|
|
554
|
+
if (typeMatch) {
|
|
555
|
+
const [, name, definition] = typeMatch;
|
|
556
|
+
if (!addedSections.has(`Type: ${name}`)) {
|
|
557
|
+
this.addContentToSections(`**Type ${name}:** \`${definition.trim()}\``, sections, addedSections);
|
|
558
|
+
this.addContentToSections('', sections, addedSections);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
// Look for prop annotations in inline code (e.g., buttonStyle="primary")
|
|
563
|
+
const inlineCodes = mainArea.querySelectorAll('code:not(pre code)');
|
|
564
|
+
const propAnnotations = new Map();
|
|
565
|
+
for (const code of inlineCodes) {
|
|
566
|
+
const text = code.textContent || '';
|
|
567
|
+
// Match prop="value" or prop='value' patterns
|
|
568
|
+
const propMatch = text.match(/(\w+)=["']([^"']+)["']/);
|
|
569
|
+
if (propMatch) {
|
|
570
|
+
const [, propName, value] = propMatch;
|
|
571
|
+
if (!propAnnotations.has(propName)) {
|
|
572
|
+
propAnnotations.set(propName, new Set());
|
|
573
|
+
}
|
|
574
|
+
propAnnotations.get(propName)?.add(value);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
// If we found prop annotations, add them as discovered values
|
|
578
|
+
if (propAnnotations.size > 0) {
|
|
579
|
+
let hasAddedHeader = false;
|
|
580
|
+
for (const [propName, values] of propAnnotations) {
|
|
581
|
+
if (values.size > 1) {
|
|
582
|
+
if (!hasAddedHeader) {
|
|
583
|
+
this.addContentToSections('## Discovered Prop Values', sections, addedSections);
|
|
584
|
+
hasAddedHeader = true;
|
|
585
|
+
}
|
|
586
|
+
const valueList = Array.from(values)
|
|
587
|
+
.map((v) => `"${v}"`)
|
|
588
|
+
.join(' | ');
|
|
589
|
+
this.addContentToSections(`- **${propName}**: ${valueList}`, sections, addedSections);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
if (hasAddedHeader) {
|
|
593
|
+
this.addContentToSections('', sections, addedSections);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
catch (error) {
|
|
598
|
+
console.error('[StorybookExtractor] Error extracting type annotations:', error);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
async waitForStorybookAPI() {
|
|
602
|
+
return new Promise((resolve) => {
|
|
603
|
+
const win = window;
|
|
604
|
+
if (typeof win.__STORYBOOK_CLIENT_API__ !== 'undefined') {
|
|
605
|
+
const checkReady = () => {
|
|
606
|
+
const api = win.__STORYBOOK_CLIENT_API__;
|
|
607
|
+
if (api?.storyStore?.ready) {
|
|
608
|
+
resolve();
|
|
609
|
+
}
|
|
610
|
+
else {
|
|
611
|
+
setTimeout(checkReady, 100);
|
|
612
|
+
}
|
|
613
|
+
};
|
|
614
|
+
checkReady();
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
if (document.querySelector('#storybook-root, .sbdocs, [data-nodetype="root"]') !== null ||
|
|
618
|
+
document.querySelector('meta[name="storybook-version"]') !== null ||
|
|
619
|
+
document.baseURI?.includes('path=/docs/') ||
|
|
620
|
+
document.baseURI?.includes('path=/story/')) {
|
|
621
|
+
resolve();
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
resolve();
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
async extractContent(document) {
|
|
628
|
+
const emptyResult = {
|
|
629
|
+
content: '',
|
|
630
|
+
metadata: { type: 'overview' },
|
|
631
|
+
};
|
|
632
|
+
try {
|
|
633
|
+
await this.waitForStorybookAPI();
|
|
634
|
+
const mainArea = await this.waitForStorybookContent(document);
|
|
635
|
+
if (!mainArea) {
|
|
636
|
+
console.error('Failed to find Storybook content area');
|
|
637
|
+
return emptyResult;
|
|
638
|
+
}
|
|
639
|
+
const sections = [];
|
|
640
|
+
const addedSections = new Set();
|
|
641
|
+
let mainTitle = '';
|
|
642
|
+
let mainDescription = '';
|
|
643
|
+
const title = mainArea.querySelector('h1')?.textContent?.trim();
|
|
644
|
+
if (title) {
|
|
645
|
+
mainTitle = title;
|
|
646
|
+
this.addContentToSections(`# ${title}`, sections, addedSections);
|
|
647
|
+
this.addContentToSections('', sections, addedSections);
|
|
648
|
+
}
|
|
649
|
+
const description = mainArea.querySelector('h1 + p');
|
|
650
|
+
if (description) {
|
|
651
|
+
mainDescription = this.extractLinks(description);
|
|
652
|
+
this.addContentToSections(mainDescription, sections, addedSections);
|
|
653
|
+
this.addContentToSections('', sections, addedSections);
|
|
654
|
+
}
|
|
655
|
+
const headings = mainArea.querySelectorAll('h1, h2, h3, h4');
|
|
656
|
+
for (const heading of headings) {
|
|
657
|
+
const title = heading.textContent?.trim();
|
|
658
|
+
if (title && !addedSections.has(`## ${title}`)) {
|
|
659
|
+
// Skip "Props" section - it will be handled by processPropsTable
|
|
660
|
+
const isPropsSection = /^props$/i.test(title);
|
|
661
|
+
if (isPropsSection)
|
|
662
|
+
continue;
|
|
663
|
+
const level = heading.tagName === 'H1' ? '#' : '##';
|
|
664
|
+
this.addContentToSections(`${level} ${title}`, sections, addedSections);
|
|
665
|
+
this.addContentToSections('', sections, addedSections);
|
|
666
|
+
let current = heading.nextElementSibling;
|
|
667
|
+
while (current && !current.matches('h1, h2, h3, h4')) {
|
|
668
|
+
if (this.isElementVisible(current)) {
|
|
669
|
+
// Skip props tables - they will be handled separately
|
|
670
|
+
const isPropsTable = current.matches('.docblock-argstable, [class*="ArgTable"], [class*="argtable"]');
|
|
671
|
+
if (current.matches('table') && !isPropsTable) {
|
|
672
|
+
await this.processTableContent(current, sections, addedSections);
|
|
673
|
+
}
|
|
674
|
+
else if (current.matches('ul, ol')) {
|
|
675
|
+
await this.processListContent(current, sections, addedSections);
|
|
676
|
+
}
|
|
677
|
+
else if (!isPropsTable) {
|
|
678
|
+
await this.processSectionContent(current, sections, addedSections);
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
current = current.nextElementSibling;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
// Find props/args table with multiple selectors for different Storybook versions
|
|
686
|
+
const propsTableSelectors = [
|
|
687
|
+
'.docblock-argstable', // Storybook 6.x
|
|
688
|
+
'.docblock-argtable', // Alternative naming
|
|
689
|
+
'table.docblock-table', // Storybook 7.x
|
|
690
|
+
'[class*="ArgTable"]', // React-based ArgTable
|
|
691
|
+
'[class*="argtable"]', // Case variations
|
|
692
|
+
'table[class*="props"]', // Generic props table
|
|
693
|
+
'.sb-argstable', // Another Storybook variant
|
|
694
|
+
'[data-testid="args-table"]', // Test ID selector
|
|
695
|
+
'.docs-story + table', // Table after story
|
|
696
|
+
'section:has(h2:contains("Props")) table', // Section with Props heading
|
|
697
|
+
];
|
|
698
|
+
let propsTable = null;
|
|
699
|
+
for (const selector of propsTableSelectors) {
|
|
700
|
+
try {
|
|
701
|
+
propsTable = mainArea.querySelector(selector);
|
|
702
|
+
if (propsTable)
|
|
703
|
+
break;
|
|
704
|
+
}
|
|
705
|
+
catch {
|
|
706
|
+
// Some selectors may not be valid in all browsers
|
|
707
|
+
continue;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
// Also try to find ArgTypes component which renders the props
|
|
711
|
+
if (!propsTable) {
|
|
712
|
+
const argTypesSection = mainArea.querySelector('[class*="ArgTypes"], [class*="argtypes"]');
|
|
713
|
+
if (argTypesSection) {
|
|
714
|
+
propsTable = argTypesSection.querySelector('table');
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
if (propsTable) {
|
|
718
|
+
await this.processPropsTable(propsTable, sections, addedSections, mainTitle);
|
|
719
|
+
}
|
|
720
|
+
// Also extract any inline type annotations from code examples
|
|
721
|
+
await this.extractTypeAnnotations(mainArea, sections, addedSections);
|
|
722
|
+
// Process iframes - Storybook loads docs content in iframes
|
|
723
|
+
const iframes = mainArea.querySelectorAll('iframe');
|
|
724
|
+
for (const iframe of iframes) {
|
|
725
|
+
try {
|
|
726
|
+
const iframeDoc = iframe.contentDocument;
|
|
727
|
+
if (iframeDoc) {
|
|
728
|
+
// Look for props table in iframe
|
|
729
|
+
const iframePropsTable = iframeDoc.querySelector('.docblock-argstable, .docblock-argtable, table.docblock-table, [class*="ArgTable"]');
|
|
730
|
+
if (iframePropsTable && !propsTable) {
|
|
731
|
+
await this.processPropsTable(iframePropsTable, sections, addedSections, mainTitle);
|
|
732
|
+
}
|
|
733
|
+
// Process any other content in the iframe
|
|
734
|
+
const iframeContent = iframeDoc.querySelector('.sbdocs-content, #docs-root, body');
|
|
735
|
+
if (iframeContent) {
|
|
736
|
+
await this.processSection(iframeContent, sections, addedSections);
|
|
737
|
+
// Also try to extract type annotations from iframe
|
|
738
|
+
await this.extractTypeAnnotations(iframeContent, sections, addedSections);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
catch (error) {
|
|
743
|
+
// Cross-origin iframe - expected for external content
|
|
744
|
+
console.error('Error processing iframe (may be cross-origin):', error);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
return {
|
|
748
|
+
content: sections.join('\n\n'),
|
|
749
|
+
metadata: {
|
|
750
|
+
type: 'overview',
|
|
751
|
+
pattern: {
|
|
752
|
+
name: mainTitle,
|
|
753
|
+
type: 'component',
|
|
754
|
+
description: mainDescription,
|
|
755
|
+
usageContexts: [],
|
|
756
|
+
relatedPatterns: [],
|
|
757
|
+
},
|
|
758
|
+
},
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
catch (error) {
|
|
762
|
+
console.error('Error extracting Storybook content:', error);
|
|
763
|
+
return emptyResult;
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
//# sourceMappingURL=storybook-extractor.js.map
|