@ytspar/devbar 0.0.1 → 1.0.0-canary.2b99e1e
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 +21 -0
- package/README.md +173 -28
- package/dist/GlobalDevBar.d.ts +318 -0
- package/dist/GlobalDevBar.js +3281 -0
- package/dist/accessibility.d.ts +84 -0
- package/dist/accessibility.js +155 -0
- package/dist/constants.d.ts +301 -0
- package/dist/constants.js +641 -0
- package/dist/debug.d.ts +39 -0
- package/dist/debug.js +92 -0
- package/dist/earlyConsoleCapture.d.ts +34 -0
- package/dist/earlyConsoleCapture.js +77 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +25 -0
- package/dist/lazy/index.d.ts +6 -0
- package/dist/lazy/index.js +6 -0
- package/dist/lazy/lazyHtml2Canvas.d.ts +29 -0
- package/dist/lazy/lazyHtml2Canvas.js +37 -0
- package/dist/network.d.ts +92 -0
- package/dist/network.js +176 -0
- package/dist/outline.d.ts +14 -0
- package/dist/outline.js +248 -0
- package/dist/presets.d.ts +57 -0
- package/dist/presets.js +133 -0
- package/dist/schema.d.ts +14 -0
- package/dist/schema.js +114 -0
- package/dist/settings.d.ts +150 -0
- package/dist/settings.js +292 -0
- package/dist/storage.d.ts +83 -0
- package/dist/storage.js +182 -0
- package/dist/types.d.ts +75 -0
- package/dist/types.js +8 -0
- package/dist/ui/buttons.d.ts +21 -0
- package/dist/ui/buttons.js +55 -0
- package/dist/ui/icons.d.ts +13 -0
- package/dist/ui/icons.js +25 -0
- package/dist/ui/index.d.ts +8 -0
- package/dist/ui/index.js +8 -0
- package/dist/ui/modals.d.ts +44 -0
- package/dist/ui/modals.js +190 -0
- package/dist/utils.d.ts +11 -0
- package/dist/utils.js +13 -0
- package/package.json +58 -6
package/dist/outline.js
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Document Outline Extraction
|
|
3
|
+
*
|
|
4
|
+
* Functions for extracting and formatting the semantic document outline.
|
|
5
|
+
*/
|
|
6
|
+
// ============================================================================
|
|
7
|
+
// Semantic Element Sets
|
|
8
|
+
// ============================================================================
|
|
9
|
+
const semanticElements = new Set([
|
|
10
|
+
'article',
|
|
11
|
+
'aside',
|
|
12
|
+
'nav',
|
|
13
|
+
'section',
|
|
14
|
+
'main',
|
|
15
|
+
'body',
|
|
16
|
+
'header',
|
|
17
|
+
'footer',
|
|
18
|
+
'figure',
|
|
19
|
+
'figcaption',
|
|
20
|
+
'details',
|
|
21
|
+
'summary',
|
|
22
|
+
'dialog',
|
|
23
|
+
'address',
|
|
24
|
+
'hgroup',
|
|
25
|
+
'form',
|
|
26
|
+
'fieldset',
|
|
27
|
+
'legend',
|
|
28
|
+
'ul',
|
|
29
|
+
'ol',
|
|
30
|
+
'dl',
|
|
31
|
+
'menu',
|
|
32
|
+
'table',
|
|
33
|
+
'thead',
|
|
34
|
+
'tbody',
|
|
35
|
+
'tfoot',
|
|
36
|
+
'caption',
|
|
37
|
+
'h1',
|
|
38
|
+
'h2',
|
|
39
|
+
'h3',
|
|
40
|
+
'h4',
|
|
41
|
+
'h5',
|
|
42
|
+
'h6',
|
|
43
|
+
]);
|
|
44
|
+
const headingElements = new Set(['h1', 'h2', 'h3', 'h4', 'h5', 'h6']);
|
|
45
|
+
// ============================================================================
|
|
46
|
+
// Helper Functions
|
|
47
|
+
// ============================================================================
|
|
48
|
+
/**
|
|
49
|
+
* Get the semantic category for an element tag
|
|
50
|
+
*/
|
|
51
|
+
function getSemanticCategory(tag) {
|
|
52
|
+
if (headingElements.has(tag))
|
|
53
|
+
return 'heading';
|
|
54
|
+
if (['article', 'section', 'aside', 'nav'].includes(tag))
|
|
55
|
+
return 'sectioning';
|
|
56
|
+
if (['main', 'header', 'footer'].includes(tag))
|
|
57
|
+
return 'landmark';
|
|
58
|
+
if (['figure', 'figcaption', 'details', 'summary'].includes(tag))
|
|
59
|
+
return 'grouping';
|
|
60
|
+
if (['form', 'fieldset', 'legend'].includes(tag))
|
|
61
|
+
return 'form';
|
|
62
|
+
if (['table', 'thead', 'tbody', 'tfoot', 'caption'].includes(tag))
|
|
63
|
+
return 'table';
|
|
64
|
+
if (['ul', 'ol', 'dl', 'menu'].includes(tag))
|
|
65
|
+
return 'list';
|
|
66
|
+
return 'other';
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Get trimmed text content from an element with optional max length
|
|
70
|
+
*/
|
|
71
|
+
function getTextContent(el, maxLen) {
|
|
72
|
+
return el?.textContent?.trim().slice(0, maxLen) || '';
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Mapping of tag names to their child element selector for text extraction
|
|
76
|
+
*/
|
|
77
|
+
const childTextSelectors = {
|
|
78
|
+
figure: 'figcaption',
|
|
79
|
+
details: 'summary',
|
|
80
|
+
fieldset: 'legend',
|
|
81
|
+
table: 'caption',
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* Get descriptive text for an element
|
|
85
|
+
*/
|
|
86
|
+
function getElementText(el, tagName) {
|
|
87
|
+
// Check ARIA attributes first
|
|
88
|
+
const ariaLabel = el.getAttribute('aria-label');
|
|
89
|
+
if (ariaLabel)
|
|
90
|
+
return ariaLabel;
|
|
91
|
+
const labelledBy = el.getAttribute('aria-labelledby');
|
|
92
|
+
if (labelledBy) {
|
|
93
|
+
const labelEl = document.getElementById(labelledBy);
|
|
94
|
+
if (labelEl)
|
|
95
|
+
return getTextContent(labelEl, 80);
|
|
96
|
+
}
|
|
97
|
+
// Headings: use direct text content
|
|
98
|
+
if (headingElements.has(tagName)) {
|
|
99
|
+
return getTextContent(el, 100);
|
|
100
|
+
}
|
|
101
|
+
// Elements with child selectors (figure, details, fieldset, table)
|
|
102
|
+
const childSelector = childTextSelectors[tagName];
|
|
103
|
+
if (childSelector) {
|
|
104
|
+
const childEl = el.querySelector(childSelector);
|
|
105
|
+
if (childEl)
|
|
106
|
+
return getTextContent(childEl, 80);
|
|
107
|
+
}
|
|
108
|
+
// Form: use name or id attribute
|
|
109
|
+
if (tagName === 'form') {
|
|
110
|
+
const name = el.getAttribute('name') || el.getAttribute('id');
|
|
111
|
+
if (name)
|
|
112
|
+
return name;
|
|
113
|
+
}
|
|
114
|
+
// Nav: try heading first, then first link
|
|
115
|
+
if (tagName === 'nav') {
|
|
116
|
+
const heading = el.querySelector('h1, h2, h3, h4, h5, h6');
|
|
117
|
+
if (heading)
|
|
118
|
+
return getTextContent(heading, 50);
|
|
119
|
+
const firstLink = el.querySelector('a');
|
|
120
|
+
if (firstLink)
|
|
121
|
+
return `Navigation (${getTextContent(firstLink, 30)}...)`;
|
|
122
|
+
}
|
|
123
|
+
// Sectioning elements: try direct child heading, then class name
|
|
124
|
+
if (['section', 'article', 'aside'].includes(tagName)) {
|
|
125
|
+
const heading = el.querySelector(':scope > h1, :scope > h2, :scope > h3, :scope > h4, :scope > h5, :scope > h6');
|
|
126
|
+
if (heading)
|
|
127
|
+
return getTextContent(heading, 80);
|
|
128
|
+
const className = el.className?.toString().split(' ')[0];
|
|
129
|
+
if (className && className.length < 30)
|
|
130
|
+
return className;
|
|
131
|
+
}
|
|
132
|
+
// Lists: count items
|
|
133
|
+
if (['ul', 'ol'].includes(tagName)) {
|
|
134
|
+
return `${el.querySelectorAll(':scope > li').length} items`;
|
|
135
|
+
}
|
|
136
|
+
if (tagName === 'dl') {
|
|
137
|
+
return `${el.querySelectorAll(':scope > dt').length} terms`;
|
|
138
|
+
}
|
|
139
|
+
// Fallback to role attribute
|
|
140
|
+
const role = el.getAttribute('role');
|
|
141
|
+
if (role)
|
|
142
|
+
return `[role="${role}"]`;
|
|
143
|
+
return '';
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Check if an element is visible
|
|
147
|
+
*/
|
|
148
|
+
function isVisible(el) {
|
|
149
|
+
const style = window.getComputedStyle(el);
|
|
150
|
+
return style.display !== 'none' && style.visibility !== 'hidden';
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Recursively extract outline nodes from an element
|
|
154
|
+
*/
|
|
155
|
+
function extractFromElement(root) {
|
|
156
|
+
const nodes = [];
|
|
157
|
+
for (const child of Array.from(root.children)) {
|
|
158
|
+
const tagName = child.tagName.toLowerCase();
|
|
159
|
+
if (!isVisible(child))
|
|
160
|
+
continue;
|
|
161
|
+
if (child.getAttribute('data-devbar'))
|
|
162
|
+
continue;
|
|
163
|
+
if (semanticElements.has(tagName)) {
|
|
164
|
+
const text = getElementText(child, tagName);
|
|
165
|
+
const isHeading = headingElements.has(tagName);
|
|
166
|
+
const isLandmark = [
|
|
167
|
+
'main',
|
|
168
|
+
'nav',
|
|
169
|
+
'header',
|
|
170
|
+
'footer',
|
|
171
|
+
'article',
|
|
172
|
+
'section',
|
|
173
|
+
'aside',
|
|
174
|
+
].includes(tagName);
|
|
175
|
+
const hasText = text.length > 0;
|
|
176
|
+
if (isHeading || isLandmark || hasText) {
|
|
177
|
+
const level = isHeading ? parseInt(tagName[1], 10) : 0;
|
|
178
|
+
const node = {
|
|
179
|
+
tagName,
|
|
180
|
+
level,
|
|
181
|
+
text: text || `<${tagName}>`,
|
|
182
|
+
id: child.id || undefined,
|
|
183
|
+
children: [],
|
|
184
|
+
category: getSemanticCategory(tagName),
|
|
185
|
+
};
|
|
186
|
+
if (!isHeading) {
|
|
187
|
+
node.children = extractFromElement(child);
|
|
188
|
+
}
|
|
189
|
+
nodes.push(node);
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
const childNodes = extractFromElement(child);
|
|
193
|
+
nodes.push(...childNodes);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
const childNodes = extractFromElement(child);
|
|
198
|
+
nodes.push(...childNodes);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return nodes;
|
|
202
|
+
}
|
|
203
|
+
// ============================================================================
|
|
204
|
+
// Public API
|
|
205
|
+
// ============================================================================
|
|
206
|
+
/**
|
|
207
|
+
* Extract the document outline from the page
|
|
208
|
+
*/
|
|
209
|
+
export function extractDocumentOutline() {
|
|
210
|
+
const body = document.body;
|
|
211
|
+
if (!body)
|
|
212
|
+
return [];
|
|
213
|
+
return extractFromElement(body);
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Convert an outline to markdown format
|
|
217
|
+
*/
|
|
218
|
+
export function outlineToMarkdown(outline, indent = 0) {
|
|
219
|
+
let md = '';
|
|
220
|
+
if (indent === 0) {
|
|
221
|
+
md += '# Document Outline\n\n';
|
|
222
|
+
md += '**Semantic Categories:**\n';
|
|
223
|
+
md += '- `heading` - h1-h6 elements\n';
|
|
224
|
+
md += '- `sectioning` - article, section, aside, nav\n';
|
|
225
|
+
md += '- `landmark` - main, header, footer\n';
|
|
226
|
+
md += '- `grouping` - figure, details, summary\n';
|
|
227
|
+
md += '- `form` - form, fieldset\n';
|
|
228
|
+
md += '- `table` - table elements\n';
|
|
229
|
+
md += '- `list` - ul, ol, dl\n\n';
|
|
230
|
+
md += '---\n\n';
|
|
231
|
+
}
|
|
232
|
+
for (const node of outline) {
|
|
233
|
+
const prefix = ' '.repeat(indent);
|
|
234
|
+
const tagLabel = `\`<${node.tagName}>\``;
|
|
235
|
+
const anchor = node.id ? ` \`#${node.id}\`` : '';
|
|
236
|
+
const category = node.category ? ` [${node.category}]` : '';
|
|
237
|
+
if (node.category === 'heading' && indent === 0) {
|
|
238
|
+
md += `${'#'.repeat(node.level)} ${tagLabel} ${node.text}${anchor}\n\n`;
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
md += `${prefix}- ${tagLabel}${category} ${node.text}${anchor}\n`;
|
|
242
|
+
}
|
|
243
|
+
if (node.children.length > 0) {
|
|
244
|
+
md += outlineToMarkdown(node.children, indent + 1);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return md;
|
|
248
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DevBar Configuration Presets
|
|
3
|
+
*
|
|
4
|
+
* Pre-configured options for common DevBar use cases.
|
|
5
|
+
*/
|
|
6
|
+
import { type GlobalDevBar } from './GlobalDevBar.js';
|
|
7
|
+
import type { GlobalDevBarOptions } from './types.js';
|
|
8
|
+
/**
|
|
9
|
+
* Minimal preset - shows only essential features
|
|
10
|
+
* Good for production debugging with minimal visual footprint
|
|
11
|
+
*/
|
|
12
|
+
export declare const PRESET_MINIMAL: GlobalDevBarOptions;
|
|
13
|
+
/**
|
|
14
|
+
* Full preset - shows all features
|
|
15
|
+
* Good for comprehensive development monitoring
|
|
16
|
+
*/
|
|
17
|
+
export declare const PRESET_FULL: GlobalDevBarOptions;
|
|
18
|
+
/**
|
|
19
|
+
* Performance preset - focuses on Core Web Vitals
|
|
20
|
+
* Good for performance optimization work
|
|
21
|
+
*/
|
|
22
|
+
export declare const PRESET_PERFORMANCE: GlobalDevBarOptions;
|
|
23
|
+
/**
|
|
24
|
+
* Responsive preset - focuses on responsive design
|
|
25
|
+
* Good for layout and breakpoint work
|
|
26
|
+
*/
|
|
27
|
+
export declare const PRESET_RESPONSIVE: GlobalDevBarOptions;
|
|
28
|
+
/**
|
|
29
|
+
* Debug preset - full features with debug logging
|
|
30
|
+
* Good for troubleshooting DevBar itself
|
|
31
|
+
*/
|
|
32
|
+
export declare const PRESET_DEBUG: GlobalDevBarOptions;
|
|
33
|
+
/**
|
|
34
|
+
* Initialize DevBar with minimal preset
|
|
35
|
+
* @param options Additional options to merge with preset
|
|
36
|
+
*/
|
|
37
|
+
export declare function initMinimal(options?: Partial<GlobalDevBarOptions>): GlobalDevBar;
|
|
38
|
+
/**
|
|
39
|
+
* Initialize DevBar with full preset
|
|
40
|
+
* @param options Additional options to merge with preset
|
|
41
|
+
*/
|
|
42
|
+
export declare function initFull(options?: Partial<GlobalDevBarOptions>): GlobalDevBar;
|
|
43
|
+
/**
|
|
44
|
+
* Initialize DevBar with performance preset
|
|
45
|
+
* @param options Additional options to merge with preset
|
|
46
|
+
*/
|
|
47
|
+
export declare function initPerformance(options?: Partial<GlobalDevBarOptions>): GlobalDevBar;
|
|
48
|
+
/**
|
|
49
|
+
* Initialize DevBar with responsive preset
|
|
50
|
+
* @param options Additional options to merge with preset
|
|
51
|
+
*/
|
|
52
|
+
export declare function initResponsive(options?: Partial<GlobalDevBarOptions>): GlobalDevBar;
|
|
53
|
+
/**
|
|
54
|
+
* Initialize DevBar with debug preset
|
|
55
|
+
* @param options Additional options to merge with preset
|
|
56
|
+
*/
|
|
57
|
+
export declare function initDebug(options?: Partial<GlobalDevBarOptions>): GlobalDevBar;
|
package/dist/presets.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DevBar Configuration Presets
|
|
3
|
+
*
|
|
4
|
+
* Pre-configured options for common DevBar use cases.
|
|
5
|
+
*/
|
|
6
|
+
import { initGlobalDevBar } from './GlobalDevBar.js';
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// Preset Configurations
|
|
9
|
+
// ============================================================================
|
|
10
|
+
/**
|
|
11
|
+
* Minimal preset - shows only essential features
|
|
12
|
+
* Good for production debugging with minimal visual footprint
|
|
13
|
+
*/
|
|
14
|
+
export const PRESET_MINIMAL = {
|
|
15
|
+
showMetrics: {
|
|
16
|
+
breakpoint: false,
|
|
17
|
+
fcp: false,
|
|
18
|
+
lcp: false,
|
|
19
|
+
cls: false,
|
|
20
|
+
inp: false,
|
|
21
|
+
pageSize: false,
|
|
22
|
+
},
|
|
23
|
+
showScreenshot: false,
|
|
24
|
+
showConsoleBadges: true,
|
|
25
|
+
showTooltips: false,
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Full preset - shows all features
|
|
29
|
+
* Good for comprehensive development monitoring
|
|
30
|
+
*/
|
|
31
|
+
export const PRESET_FULL = {
|
|
32
|
+
showMetrics: {
|
|
33
|
+
breakpoint: true,
|
|
34
|
+
fcp: true,
|
|
35
|
+
lcp: true,
|
|
36
|
+
cls: true,
|
|
37
|
+
inp: true,
|
|
38
|
+
pageSize: true,
|
|
39
|
+
},
|
|
40
|
+
showScreenshot: true,
|
|
41
|
+
showConsoleBadges: true,
|
|
42
|
+
showTooltips: true,
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Performance preset - focuses on Core Web Vitals
|
|
46
|
+
* Good for performance optimization work
|
|
47
|
+
*/
|
|
48
|
+
export const PRESET_PERFORMANCE = {
|
|
49
|
+
showMetrics: {
|
|
50
|
+
breakpoint: false,
|
|
51
|
+
fcp: true,
|
|
52
|
+
lcp: true,
|
|
53
|
+
cls: true,
|
|
54
|
+
inp: true,
|
|
55
|
+
pageSize: true,
|
|
56
|
+
},
|
|
57
|
+
showScreenshot: false,
|
|
58
|
+
showConsoleBadges: false,
|
|
59
|
+
showTooltips: true,
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Responsive preset - focuses on responsive design
|
|
63
|
+
* Good for layout and breakpoint work
|
|
64
|
+
*/
|
|
65
|
+
export const PRESET_RESPONSIVE = {
|
|
66
|
+
showMetrics: {
|
|
67
|
+
breakpoint: true,
|
|
68
|
+
fcp: false,
|
|
69
|
+
lcp: false,
|
|
70
|
+
cls: false,
|
|
71
|
+
inp: false,
|
|
72
|
+
pageSize: false,
|
|
73
|
+
},
|
|
74
|
+
showScreenshot: true,
|
|
75
|
+
showConsoleBadges: false,
|
|
76
|
+
showTooltips: true,
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* Debug preset - full features with debug logging
|
|
80
|
+
* Good for troubleshooting DevBar itself
|
|
81
|
+
*/
|
|
82
|
+
export const PRESET_DEBUG = {
|
|
83
|
+
showMetrics: {
|
|
84
|
+
breakpoint: true,
|
|
85
|
+
fcp: true,
|
|
86
|
+
lcp: true,
|
|
87
|
+
cls: true,
|
|
88
|
+
inp: true,
|
|
89
|
+
pageSize: true,
|
|
90
|
+
},
|
|
91
|
+
showScreenshot: true,
|
|
92
|
+
showConsoleBadges: true,
|
|
93
|
+
showTooltips: true,
|
|
94
|
+
debug: true,
|
|
95
|
+
};
|
|
96
|
+
// ============================================================================
|
|
97
|
+
// Convenience Initialization Functions
|
|
98
|
+
// ============================================================================
|
|
99
|
+
/**
|
|
100
|
+
* Initialize DevBar with minimal preset
|
|
101
|
+
* @param options Additional options to merge with preset
|
|
102
|
+
*/
|
|
103
|
+
export function initMinimal(options) {
|
|
104
|
+
return initGlobalDevBar({ ...PRESET_MINIMAL, ...options });
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Initialize DevBar with full preset
|
|
108
|
+
* @param options Additional options to merge with preset
|
|
109
|
+
*/
|
|
110
|
+
export function initFull(options) {
|
|
111
|
+
return initGlobalDevBar({ ...PRESET_FULL, ...options });
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Initialize DevBar with performance preset
|
|
115
|
+
* @param options Additional options to merge with preset
|
|
116
|
+
*/
|
|
117
|
+
export function initPerformance(options) {
|
|
118
|
+
return initGlobalDevBar({ ...PRESET_PERFORMANCE, ...options });
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Initialize DevBar with responsive preset
|
|
122
|
+
* @param options Additional options to merge with preset
|
|
123
|
+
*/
|
|
124
|
+
export function initResponsive(options) {
|
|
125
|
+
return initGlobalDevBar({ ...PRESET_RESPONSIVE, ...options });
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Initialize DevBar with debug preset
|
|
129
|
+
* @param options Additional options to merge with preset
|
|
130
|
+
*/
|
|
131
|
+
export function initDebug(options) {
|
|
132
|
+
return initGlobalDevBar({ ...PRESET_DEBUG, ...options });
|
|
133
|
+
}
|
package/dist/schema.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Page Schema Extraction
|
|
3
|
+
*
|
|
4
|
+
* Functions for extracting and formatting structured data from pages.
|
|
5
|
+
*/
|
|
6
|
+
import type { PageSchema } from './types.js';
|
|
7
|
+
/**
|
|
8
|
+
* Extract structured data (JSON-LD, meta tags, Open Graph, etc.) from the page
|
|
9
|
+
*/
|
|
10
|
+
export declare function extractPageSchema(): PageSchema;
|
|
11
|
+
/**
|
|
12
|
+
* Convert a page schema to markdown format
|
|
13
|
+
*/
|
|
14
|
+
export declare function schemaToMarkdown(schema: PageSchema): string;
|
package/dist/schema.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Page Schema Extraction
|
|
3
|
+
*
|
|
4
|
+
* Functions for extracting and formatting structured data from pages.
|
|
5
|
+
*/
|
|
6
|
+
// ============================================================================
|
|
7
|
+
// Public API
|
|
8
|
+
// ============================================================================
|
|
9
|
+
/**
|
|
10
|
+
* Extract structured data (JSON-LD, meta tags, Open Graph, etc.) from the page
|
|
11
|
+
*/
|
|
12
|
+
export function extractPageSchema() {
|
|
13
|
+
const schema = {
|
|
14
|
+
jsonLd: [],
|
|
15
|
+
metaTags: {},
|
|
16
|
+
openGraph: {},
|
|
17
|
+
twitter: {},
|
|
18
|
+
microdata: [],
|
|
19
|
+
};
|
|
20
|
+
// Extract JSON-LD
|
|
21
|
+
const jsonLdScripts = document.querySelectorAll('script[type="application/ld+json"]');
|
|
22
|
+
jsonLdScripts.forEach((script) => {
|
|
23
|
+
try {
|
|
24
|
+
const data = JSON.parse(script.textContent || '');
|
|
25
|
+
schema.jsonLd.push(data);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
// Invalid JSON-LD, skip
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
// Extract meta tags
|
|
32
|
+
const metaTags = document.querySelectorAll('meta[name], meta[property]');
|
|
33
|
+
metaTags.forEach((meta) => {
|
|
34
|
+
const name = meta.getAttribute('name') || meta.getAttribute('property') || '';
|
|
35
|
+
const content = meta.getAttribute('content') || '';
|
|
36
|
+
if (name.startsWith('og:')) {
|
|
37
|
+
schema.openGraph[name.replace('og:', '')] = content;
|
|
38
|
+
}
|
|
39
|
+
else if (name.startsWith('twitter:')) {
|
|
40
|
+
schema.twitter[name.replace('twitter:', '')] = content;
|
|
41
|
+
}
|
|
42
|
+
else if (name) {
|
|
43
|
+
schema.metaTags[name] = content;
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
// Extract microdata
|
|
47
|
+
const microdataItems = document.querySelectorAll('[itemscope]');
|
|
48
|
+
microdataItems.forEach((item) => {
|
|
49
|
+
const itemType = item.getAttribute('itemtype');
|
|
50
|
+
const props = {};
|
|
51
|
+
item.querySelectorAll('[itemprop]').forEach((prop) => {
|
|
52
|
+
const propName = prop.getAttribute('itemprop') || '';
|
|
53
|
+
const propValue = prop.getAttribute('content') ||
|
|
54
|
+
prop.getAttribute('href') ||
|
|
55
|
+
prop.textContent?.trim().slice(0, 200) ||
|
|
56
|
+
'';
|
|
57
|
+
if (propName)
|
|
58
|
+
props[propName] = propValue;
|
|
59
|
+
});
|
|
60
|
+
if (itemType || Object.keys(props).length > 0) {
|
|
61
|
+
schema.microdata.push({ type: itemType, properties: props });
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
return schema;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Convert a page schema to markdown format
|
|
68
|
+
*/
|
|
69
|
+
export function schemaToMarkdown(schema) {
|
|
70
|
+
let md = '';
|
|
71
|
+
if (schema.jsonLd.length > 0) {
|
|
72
|
+
md += '## JSON-LD\n\n';
|
|
73
|
+
schema.jsonLd.forEach((item, i) => {
|
|
74
|
+
md += `### Schema ${i + 1}\n\n`;
|
|
75
|
+
md += `\`\`\`json\n${JSON.stringify(item, null, 2)}\n\`\`\`\n\n`;
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
if (Object.keys(schema.openGraph).length > 0) {
|
|
79
|
+
md += '## Open Graph\n\n';
|
|
80
|
+
for (const [key, value] of Object.entries(schema.openGraph)) {
|
|
81
|
+
md += `- **${key}**: ${value}\n`;
|
|
82
|
+
}
|
|
83
|
+
md += '\n';
|
|
84
|
+
}
|
|
85
|
+
if (Object.keys(schema.twitter).length > 0) {
|
|
86
|
+
md += '## Twitter Cards\n\n';
|
|
87
|
+
for (const [key, value] of Object.entries(schema.twitter)) {
|
|
88
|
+
md += `- **${key}**: ${value}\n`;
|
|
89
|
+
}
|
|
90
|
+
md += '\n';
|
|
91
|
+
}
|
|
92
|
+
if (Object.keys(schema.metaTags).length > 0) {
|
|
93
|
+
md += '## Meta Tags\n\n';
|
|
94
|
+
for (const [key, value] of Object.entries(schema.metaTags)) {
|
|
95
|
+
md += `- **${key}**: ${value}\n`;
|
|
96
|
+
}
|
|
97
|
+
md += '\n';
|
|
98
|
+
}
|
|
99
|
+
if (schema.microdata.length > 0) {
|
|
100
|
+
md += '## Microdata\n\n';
|
|
101
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
102
|
+
schema.microdata.forEach((item, i) => {
|
|
103
|
+
md += `### Item ${i + 1}${item.type ? ` (${item.type})` : ''}\n\n`;
|
|
104
|
+
for (const [key, value] of Object.entries(item.properties || {})) {
|
|
105
|
+
md += `- **${key}**: ${value}\n`;
|
|
106
|
+
}
|
|
107
|
+
md += '\n';
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
if (!md) {
|
|
111
|
+
md = '_No structured data found on this page_\n';
|
|
112
|
+
}
|
|
113
|
+
return md;
|
|
114
|
+
}
|