@pixelated-tech/components 3.5.12 → 3.5.14

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.
Files changed (37) hide show
  1. package/dist/components/admin/site-health/seo-metrics.config.json +38 -18
  2. package/dist/components/admin/site-health/site-health-accessibility.js +2 -94
  3. package/dist/components/admin/site-health/site-health-axe-core.js +2 -2
  4. package/dist/components/admin/site-health/site-health-cloudwatch.js +2 -2
  5. package/dist/components/admin/site-health/site-health-dependency-vulnerabilities.js +1 -1
  6. package/dist/components/admin/site-health/site-health-github.js +3 -3
  7. package/dist/components/admin/site-health/site-health-google-analytics.js +1 -1
  8. package/dist/components/admin/site-health/site-health-google-search-console.js +1 -1
  9. package/dist/components/admin/site-health/site-health-on-site-seo.integration.js +275 -3
  10. package/dist/components/admin/site-health/site-health-on-site-seo.js +5 -56
  11. package/dist/components/admin/site-health/site-health-overview.js +1 -9
  12. package/dist/components/admin/site-health/site-health-performance.js +2 -158
  13. package/dist/components/admin/site-health/site-health-security.js +2 -139
  14. package/dist/components/admin/site-health/site-health-seo.js +2 -94
  15. package/dist/components/admin/site-health/site-health-utils.js +170 -0
  16. package/dist/components/admin/site-health/site-health.css +33 -3
  17. package/dist/components/cms/smartimage.js +28 -8
  18. package/dist/components/general/table.js +2 -2
  19. package/dist/components/sitebuilder/config/ConfigBuilder.js +12 -3
  20. package/dist/index.adminclient.js +1 -0
  21. package/dist/index.adminserver.js +1 -0
  22. package/dist/types/components/admin/site-health/site-health-accessibility.d.ts.map +1 -1
  23. package/dist/types/components/admin/site-health/site-health-on-site-seo.d.ts.map +1 -1
  24. package/dist/types/components/admin/site-health/site-health-on-site-seo.integration.d.ts.map +1 -1
  25. package/dist/types/components/admin/site-health/site-health-overview.d.ts.map +1 -1
  26. package/dist/types/components/admin/site-health/site-health-performance.d.ts.map +1 -1
  27. package/dist/types/components/admin/site-health/site-health-security.d.ts.map +1 -1
  28. package/dist/types/components/admin/site-health/site-health-seo.d.ts.map +1 -1
  29. package/dist/types/components/admin/site-health/site-health-utils.d.ts +17 -0
  30. package/dist/types/components/admin/site-health/site-health-utils.d.ts.map +1 -0
  31. package/dist/types/components/cms/smartimage.d.ts.map +1 -1
  32. package/dist/types/components/sitebuilder/config/ConfigBuilder.d.ts.map +1 -1
  33. package/dist/types/index.adminclient.d.ts +1 -0
  34. package/dist/types/index.adminserver.d.ts +1 -0
  35. package/dist/types/tests/test-seo-logic.d.ts +2 -0
  36. package/dist/types/tests/test-seo-logic.d.ts.map +1 -0
  37. package/package.json +4 -4
@@ -24,7 +24,12 @@ const dataCollectors = {
24
24
  collectInternationalSEOData,
25
25
  collectFacetedNavigationData,
26
26
  collectBrowserCachingData,
27
- collectGzipCompressionData
27
+ collectGzipCompressionData,
28
+ collectSchemaBlogPostingData,
29
+ collectSchemaFAQData,
30
+ collectSchemaLocalBusinessData,
31
+ collectSchemaServicesData,
32
+ collectSchemaWebsiteData
28
33
  };
29
34
  /**
30
35
  * Registry of scoring functions
@@ -38,7 +43,12 @@ const scorers = {
38
43
  calculateInternationalSEOData,
39
44
  calculateFacetedNavigationScore,
40
45
  calculateBrowserCachingScore,
41
- calculateGzipCompressionScore
46
+ calculateGzipCompressionScore,
47
+ calculateSchemaBlogPostingScore,
48
+ calculateSchemaFAQScore,
49
+ calculateSchemaLocalBusinessScore,
50
+ calculateSchemaServicesScore,
51
+ calculateSchemaWebsiteScore
42
52
  };
43
53
  /**
44
54
  * Data collection functions
@@ -512,6 +522,124 @@ async function collectGzipCompressionData(url) {
512
522
  };
513
523
  }
514
524
  }
525
+ /**
526
+ * Schema Detection Data Collectors
527
+ */
528
+ function collectSchemaBlogPostingData(html) {
529
+ const hasBlogPostingSchema = /"@type"\s*:\s*"BlogPosting"/i.test(html) ||
530
+ /"@type":\s*"BlogPosting"/i.test(html) ||
531
+ /itemtype="https?:\/\/schema\.org\/BlogPosting"/i.test(html);
532
+ return {
533
+ present: hasBlogPostingSchema
534
+ };
535
+ }
536
+ function collectSchemaFAQData(html) {
537
+ const hasFAQSchema = /"@type"\s*:\s*"FAQPage"/i.test(html) ||
538
+ /"@type":\s*"FAQPage"/i.test(html) ||
539
+ /itemtype="https?:\/\/schema\.org\/FAQPage"/i.test(html);
540
+ return {
541
+ present: hasFAQSchema
542
+ };
543
+ }
544
+ function collectSchemaLocalBusinessData(html) {
545
+ const hasLocalBusinessSchema = /"@type"\s*:\s*"LocalBusiness"/i.test(html) ||
546
+ /"@type":\s*"LocalBusiness"/i.test(html) ||
547
+ /itemtype="https?:\/\/schema\.org\/LocalBusiness"/i.test(html);
548
+ return {
549
+ present: hasLocalBusinessSchema
550
+ };
551
+ }
552
+ function collectSchemaServicesData(html) {
553
+ const hasServicesSchema = /"@type"\s*:\s*"Service"/i.test(html) ||
554
+ /"@type":\s*"Service"/i.test(html) ||
555
+ /itemtype="https?:\/\/schema\.org\/Service"/i.test(html);
556
+ return {
557
+ present: hasServicesSchema
558
+ };
559
+ }
560
+ function collectSchemaWebsiteData(html) {
561
+ const hasWebsiteSchema = /"@type"\s*:\s*"WebSite"/i.test(html) ||
562
+ /"@type":\s*"WebSite"/i.test(html) ||
563
+ /itemtype="https?:\/\/schema\.org\/WebSite"/i.test(html);
564
+ return {
565
+ present: hasWebsiteSchema
566
+ };
567
+ }
568
+ /**
569
+ * Detect if a specific page is a blog page
570
+ */
571
+ function detectBlogPage(html, url) {
572
+ // Check URL patterns for blog pages (most reliable)
573
+ const blogUrlPatterns = /\/(blog|news|articles|posts|journal)(\/|$)/i;
574
+ const hasBlogUrl = blogUrlPatterns.test(url);
575
+ // Check for blog schema on this page (very reliable)
576
+ const hasBlogPostingSchema = /"@type"\s*:\s*"BlogPosting"/i.test(html) ||
577
+ /"@type":\s*"BlogPosting"/i.test(html) ||
578
+ /itemtype="https?:\/\/schema\.org\/BlogPosting"/i.test(html);
579
+ // Check for blog-specific headings (h1 or h2)
580
+ const hasBlogHeading = /<(h1|h2)[^>]*>.*?\b(blog|news|articles|journal|posts)\b.*?<\/\1>/i.test(html);
581
+ // Check for article structure with a date that isn't just a copyright year
582
+ const hasArticle = /<article[^>]*>/i.test(html);
583
+ const hasSpecificDate = /<time[^>]*datetime=["']\d{4}-\d{2}-\d{2}/i.test(html) ||
584
+ /\b(January|February|March|April|May|June|July|August|September|October|November|December)\s+\d{1,2},?\s+\d{4}\b/i.test(html);
585
+ return hasBlogUrl || hasBlogPostingSchema || (hasArticle && hasSpecificDate) || (hasBlogHeading && hasArticle);
586
+ }
587
+ /**
588
+ * Detect if a specific page is an FAQ page
589
+ */
590
+ function detectFAQPage(html, url) {
591
+ // Check URL patterns for FAQ pages
592
+ const faqUrlPatterns = /\/(faq|faqs|frequently-asked-questions)(\/|$)/i;
593
+ const hasFAQUrl = faqUrlPatterns.test(url);
594
+ // Check for FAQ schema on this page
595
+ const hasFAQSchema = /"@type"\s*:\s*"FAQPage"/i.test(html) ||
596
+ /"@type":\s*"FAQPage"/i.test(html) ||
597
+ /itemtype="https?:\/\/schema\.org\/FAQPage"/i.test(html);
598
+ // Check for FAQ-specific headings
599
+ const hasFAQHeading = /<(h1|h2)[^>]*>.*?\b(faq|faqs|frequently asked questions)\b.*?<\/\1>/i.test(html);
600
+ // Check for multiple question/answer structures (dt/dd or details/summary)
601
+ const dtCount = (html.match(/<dt[^>]*>/gi) || []).length;
602
+ const ddCount = (html.match(/<dd[^>]*>/gi) || []).length;
603
+ const detailsCount = (html.match(/<details[^>]*>/gi) || []).length;
604
+ const hasFAQStructure = (dtCount >= 3 && ddCount >= 3) || detailsCount >= 3;
605
+ return hasFAQUrl || hasFAQSchema || (hasFAQHeading && hasFAQStructure);
606
+ }
607
+ /**
608
+ * Detect if a specific page is a LocalBusiness page (usually homepage)
609
+ */
610
+ function detectLocalBusinessPage(html, url) {
611
+ const urlObj = new URL(url);
612
+ const isHomepage = urlObj.pathname === '/' || urlObj.pathname === '';
613
+ // Check for LocalBusiness schema
614
+ const hasLocalBusinessSchema = /"@type"\s*:\s*"LocalBusiness"/i.test(html) ||
615
+ /"@type":\s*"LocalBusiness"/i.test(html) ||
616
+ /itemtype="https?:\/\/schema\.org\/LocalBusiness"/i.test(html);
617
+ return isHomepage || hasLocalBusinessSchema;
618
+ }
619
+ /**
620
+ * Detect if a specific page is a Service page
621
+ */
622
+ function detectServicePage(html, url) {
623
+ const serviceUrlPatterns = /\/services\/|\/service\/|\/capabilities\/|\/what-we-do\//i;
624
+ const hasServiceUrl = serviceUrlPatterns.test(url);
625
+ // Check for Service schema
626
+ const hasServiceSchema = /"@type"\s*:\s*"Service"/i.test(html) ||
627
+ /"@type":\s*"Service"/i.test(html) ||
628
+ /itemtype="https?:\/\/schema\.org\/Service"/i.test(html);
629
+ return hasServiceUrl || hasServiceSchema;
630
+ }
631
+ /**
632
+ * Detect if a specific page is a WebSite page (usually homepage)
633
+ */
634
+ function detectWebsitePage(html, url) {
635
+ const urlObj = new URL(url);
636
+ const isHomepage = urlObj.pathname === '/' || urlObj.pathname === '';
637
+ // Check for WebSite schema
638
+ const hasWebsiteSchema = /"@type"\s*:\s*"WebSite"/i.test(html) ||
639
+ /"@type":\s*"WebSite"/i.test(html) ||
640
+ /itemtype="https?:\/\/schema\.org\/WebSite"/i.test(html);
641
+ return isHomepage || hasWebsiteSchema;
642
+ }
515
643
  /**
516
644
  * Gzip Compression Scorer
517
645
  */
@@ -544,6 +672,49 @@ function calculateGzipCompressionScore(data) {
544
672
  }
545
673
  };
546
674
  }
675
+ /**
676
+ * Schema Detection Scorers
677
+ */
678
+ function calculateSchemaBlogPostingScore(data) {
679
+ const score = data.present ? 1 : 0;
680
+ const displayValue = data.present ? 'BlogPosting schema found' : 'BlogPosting schema not found';
681
+ return {
682
+ score,
683
+ displayValue
684
+ };
685
+ }
686
+ function calculateSchemaFAQScore(data) {
687
+ const score = data.present ? 1 : 0;
688
+ const displayValue = data.present ? 'FAQ schema found' : 'FAQ schema not found';
689
+ return {
690
+ score,
691
+ displayValue
692
+ };
693
+ }
694
+ function calculateSchemaLocalBusinessScore(data) {
695
+ const score = data.present ? 1 : 0;
696
+ const displayValue = data.present ? 'LocalBusiness schema found' : 'LocalBusiness schema not found';
697
+ return {
698
+ score,
699
+ displayValue
700
+ };
701
+ }
702
+ function calculateSchemaServicesScore(data) {
703
+ const score = data.present ? 1 : 0;
704
+ const displayValue = data.present ? 'Service schema found' : 'Service schema not found';
705
+ return {
706
+ score,
707
+ displayValue
708
+ };
709
+ }
710
+ function calculateSchemaWebsiteScore(data) {
711
+ const score = data.present ? 1 : 0;
712
+ const displayValue = data.present ? 'WebSite schema found' : 'WebSite schema not found';
713
+ return {
714
+ score,
715
+ displayValue
716
+ };
717
+ }
547
718
  /**
548
719
  * Crawl the site to discover internal pages
549
720
  */
@@ -695,6 +866,12 @@ async function analyzeSinglePage(url) {
695
866
  const html = await page.content();
696
867
  // Don't close the page here - let it be reused or closed by caller
697
868
  // await page.close();
869
+ // Detect page types
870
+ const isBlogPage = detectBlogPage(html, url);
871
+ const isFAQPage = detectFAQPage(html, url);
872
+ const isLocalBusinessPage = detectLocalBusinessPage(html, url);
873
+ const isServicePage = detectServicePage(html, url);
874
+ const isWebsitePage = detectWebsitePage(html, url);
698
875
  const audits = [];
699
876
  // Process on-page metrics from configuration
700
877
  const config = seoMetricsConfig;
@@ -703,7 +880,27 @@ async function analyzeSinglePage(url) {
703
880
  let score = 0;
704
881
  let displayValue = '';
705
882
  let details = undefined;
706
- // Use data collector and scorer if available
883
+ // Special handling for schema metrics - only check on relevant page types
884
+ if (metric.id === 'schema-blogposting') {
885
+ if (!isBlogPage)
886
+ continue;
887
+ }
888
+ else if (metric.id === 'schema-faq') {
889
+ if (!isFAQPage)
890
+ continue;
891
+ }
892
+ else if (metric.id === 'schema-localbusiness') {
893
+ if (!isLocalBusinessPage)
894
+ continue;
895
+ }
896
+ else if (metric.id === 'schema-services') {
897
+ if (!isServicePage)
898
+ continue;
899
+ }
900
+ else if (metric.id === 'schema-website') {
901
+ if (!isWebsitePage)
902
+ continue;
903
+ }
707
904
  if (metric.dataCollector && metric.scorer) {
708
905
  const collector = dataCollectors[metric.dataCollector];
709
906
  const scorer = scorers[metric.scorer];
@@ -979,9 +1176,29 @@ export async function performOnSiteSEOAnalysis(baseUrl) {
979
1176
  }
980
1177
  // Analyze each page
981
1178
  const pagesAnalyzed = [];
1179
+ const schemaResults = {
1180
+ 'schema-blogposting': [],
1181
+ 'schema-faq': [],
1182
+ 'schema-localbusiness': [],
1183
+ 'schema-services': [],
1184
+ 'schema-website': []
1185
+ };
982
1186
  for (const pageUrl of pagesToAnalyze) {
983
1187
  try {
984
1188
  const pageAnalysis = await analyzeSinglePage(pageUrl);
1189
+ // Extract and remove schema audits from page-level audits to avoid duplication
1190
+ const schemaIds = Object.keys(schemaResults);
1191
+ pageAnalysis.audits = pageAnalysis.audits.filter(audit => {
1192
+ if (schemaIds.includes(audit.id)) {
1193
+ schemaResults[audit.id].push({
1194
+ url: pageAnalysis.url,
1195
+ title: pageAnalysis.title || '',
1196
+ score: audit.score || 0
1197
+ });
1198
+ return false; // Remove from page-level audits
1199
+ }
1200
+ return true;
1201
+ });
985
1202
  pagesAnalyzed.push(pageAnalysis);
986
1203
  }
987
1204
  catch (error) {
@@ -990,6 +1207,61 @@ export async function performOnSiteSEOAnalysis(baseUrl) {
990
1207
  }
991
1208
  // Perform site-wide audits
992
1209
  const onSiteAudits = await performSiteWideAudits(baseUrl);
1210
+ // Handle Schema Metrics with specific aggregation logic
1211
+ const schemaConfigs = [
1212
+ { id: 'schema-blogposting', title: 'BlogPosting Schema', optional: true, failMsg: 'BlogPosting schema not found' },
1213
+ { id: 'schema-faq', title: 'FAQ Schema', optional: false, failMsg: 'FAQ schema not found', emptyMsg: 'No FAQ pages detected - sites should have FAQ content' },
1214
+ { id: 'schema-localbusiness', title: 'LocalBusiness Schema', optional: false, failMsg: 'LocalBusiness schema not found' },
1215
+ { id: 'schema-services', title: 'Service Schema', optional: false, failMsg: 'Service schema not found' },
1216
+ { id: 'schema-website', title: 'WebSite Schema', optional: false, failMsg: 'WebSite schema not found' }
1217
+ ];
1218
+ for (const config of schemaConfigs) {
1219
+ const results = schemaResults[config.id];
1220
+ if (results.length === 0) {
1221
+ if (config.optional) {
1222
+ // Optional and none found -> N/A (white dot)
1223
+ onSiteAudits.push({
1224
+ id: config.id,
1225
+ title: config.title,
1226
+ score: null,
1227
+ scoreDisplayMode: 'binary',
1228
+ displayValue: `No ${config.title.split(' ')[0]} pages detected`,
1229
+ category: 'on-site'
1230
+ });
1231
+ }
1232
+ else {
1233
+ // Required but none found -> 0% (red dot)
1234
+ onSiteAudits.push({
1235
+ id: config.id,
1236
+ title: config.title,
1237
+ score: 0,
1238
+ scoreDisplayMode: 'binary',
1239
+ displayValue: config.emptyMsg || `No ${config.title.split(' ')[0]} pages detected`,
1240
+ category: 'on-site'
1241
+ });
1242
+ }
1243
+ }
1244
+ else {
1245
+ const passCount = results.filter(r => r.score === 1).length;
1246
+ const totalCount = results.length;
1247
+ const score = passCount / totalCount;
1248
+ const failedPages = results.filter(r => r.score === 0).map(r => ({
1249
+ page: r.title || r.url,
1250
+ url: r.url,
1251
+ score: 0,
1252
+ displayValue: config.failMsg
1253
+ }));
1254
+ onSiteAudits.push({
1255
+ id: config.id,
1256
+ title: config.title,
1257
+ score: score,
1258
+ scoreDisplayMode: 'binary',
1259
+ displayValue: `${passCount}/${totalCount} pages pass`,
1260
+ category: 'on-site',
1261
+ details: score < 1 ? { items: failedPages } : undefined
1262
+ });
1263
+ }
1264
+ }
993
1265
  // Calculate overall score (simplified - average of all page scores)
994
1266
  let totalScore = 0;
995
1267
  let totalAudits = 0;
@@ -2,7 +2,7 @@
2
2
  import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import PropTypes from 'prop-types';
4
4
  import { SiteHealthTemplate } from './site-health-template';
5
- import { getScoreIndicator } from './site-health-indicators';
5
+ import { formatAuditItem, getAuditScoreIcon } from './site-health-utils';
6
6
  /**
7
7
  * Restructure audits by type with all pages and their individual results
8
8
  */
@@ -53,8 +53,8 @@ function restructureAuditsByType(pagesAnalyzed) {
53
53
  scoreDisplayMode: audit.scoreDisplayMode,
54
54
  displayValue: `${passCount}/${totalCount} pages pass`,
55
55
  category: audit.category,
56
- details: allPageResults.length > 0 ? {
57
- items: allPageResults
56
+ details: (overallScore !== null && overallScore < 1 && allPageResults.length > 0) ? {
57
+ items: allPageResults.filter(r => r.score !== 1) // Only show failed pages in details
58
58
  } : undefined
59
59
  };
60
60
  restructuredAudits.push(restructuredAudit);
@@ -104,12 +104,6 @@ export function SiteHealthOnSiteSEO({ siteName }) {
104
104
  }
105
105
  // Process data to aggregate audits by type across all pages
106
106
  const aggregatedOnPageAudits = restructureAuditsByType(data.pagesAnalyzed);
107
- const getScoreColor = (score) => {
108
- return getScoreIndicator(score).color;
109
- };
110
- const getAuditScoreIcon = (score) => {
111
- return getScoreIndicator(score).icon;
112
- };
113
107
  const formatPageIssue = (item) => {
114
108
  // Handle page-specific results from restructured data
115
109
  if (item.page && typeof item.page === 'string') {
@@ -153,55 +147,10 @@ export function SiteHealthOnSiteSEO({ siteName }) {
153
147
  // Fallback to original formatting
154
148
  return formatAuditItem(item);
155
149
  };
156
- const formatAuditItem = (item) => {
157
- // Handle specific SEO element formatting based on the user's list
158
- // Title Tags
159
- if (item.title && typeof item.title === 'string') {
160
- return `Title: "${item.title}" (${item.length || 'unknown'} chars)`;
161
- }
162
- // Meta tags
163
- if (item.name && typeof item.name === 'string' && item.content) {
164
- return `${item.name}: ${String(item.content)}`;
165
- }
166
- // Headings
167
- if (item.tag && typeof item.tag === 'string' && item.tag.match(/^h[1-6]$/i)) {
168
- return `${item.tag}: "${item.text || ''}"`;
169
- }
170
- // Links and URLs
171
- if (item.url && typeof item.url === 'string') {
172
- if (item.issue && typeof item.issue === 'string') {
173
- return `${item.url} - ${item.issue}`;
174
- }
175
- return item.url;
176
- }
177
- // Images with alt tags
178
- if (item.src && typeof item.src === 'string') {
179
- return `Image: ${item.src} ${item.alt ? '(has alt)' : '(missing alt)'}`;
180
- }
181
- // Robots.txt directives
182
- if (item.directive && typeof item.directive === 'string') {
183
- return `${item.directive}: ${item.value || ''}`;
184
- }
185
- // Sitemap entries
186
- if (item.loc && typeof item.loc === 'string') {
187
- return item.loc;
188
- }
189
- // Default formatting
190
- return Object.entries(item)
191
- .map(([key, value]) => `${key}: ${String(value)}`)
192
- .join(', ');
193
- };
194
- return (_jsxs(_Fragment, { children: [_jsx("h4", { className: "health-site-name", children: data.site.replace('-', ' ') }), _jsxs("p", { className: "health-site-url", children: ["URL: ", data.url] }), data.overallScore !== null && (_jsx("div", { className: "health-score-container", children: _jsxs("div", { className: "health-score-item", children: [_jsx("div", { className: "health-score-label", children: "On-Site SEO Score" }), _jsxs("div", { className: "health-score-value", style: { color: getScoreColor(data.overallScore) }, children: [Math.round((data.overallScore || 0) * 100), "%"] }), _jsx("div", { className: "health-score-bar", children: _jsx("div", { className: "health-score-fill", style: {
195
- width: `${(data.overallScore || 0) * 100}%`,
196
- backgroundColor: getScoreColor(data.overallScore)
197
- } }) })] }) })), aggregatedOnPageAudits.length > 0 && (_jsxs("div", { children: [_jsx("h5", { style: { fontSize: '1rem', fontWeight: '600', marginBottom: '1rem' }, children: "On-Page SEO Audits" }), _jsx("div", { className: "space-y-2", children: aggregatedOnPageAudits
198
- .filter(audit => audit.scoreDisplayMode !== 'notApplicable')
199
- .map((audit) => (_jsxs("div", { className: "health-audit-item", children: [_jsx("span", { className: "health-audit-icon", children: getAuditScoreIcon(audit.score) }), _jsxs("div", { className: "health-audit-content", children: [_jsxs("span", { className: "health-audit-title", children: ["(", Math.round((audit.score || 0) * 100), "%) ", audit.title] }), audit.displayValue && audit.score !== 1 && (_jsx("p", { className: "health-audit-description", children: audit.displayValue })), audit.details && audit.details.items && Array.isArray(audit.details.items) && audit.details.items.length > 0 && audit.score !== 1 && (_jsx("div", { className: "health-audit-details", children: _jsx("div", { style: { fontSize: '0.75rem', color: '#6b7280', marginTop: '0.25rem' }, children: audit.details.items
200
- .filter((item) => item.score !== 1)
201
- .map((item, idx) => (_jsx("div", { style: { marginBottom: '0.125rem' }, children: formatPageIssue(item) }, idx))) }) }))] })] }, audit.id))) })] })), data.onSiteAudits.length > 0 && (_jsxs("div", { style: { marginTop: data.pagesAnalyzed.length > 0 ? '2rem' : '0' }, children: [_jsx("h5", { style: { fontSize: '1rem', fontWeight: '600', marginBottom: '1rem' }, children: "On-Site SEO Audits" }), _jsx("div", { className: "space-y-2", children: data.onSiteAudits
150
+ return (_jsxs(_Fragment, { children: [data.onSiteAudits.length > 0 && (_jsxs("div", { style: { marginTop: data.pagesAnalyzed.length > 0 ? '2rem' : '0' }, children: [_jsx("h5", { style: { fontSize: '1rem', fontWeight: '600', marginBottom: '1rem' }, children: "On-Site SEO Audits" }), _jsx("div", { className: "health-audit-list", children: data.onSiteAudits
202
151
  .filter(audit => audit.scoreDisplayMode !== 'notApplicable')
203
152
  .sort((a, b) => (b.score || 0) - (a.score || 0))
204
- .map((audit) => (_jsxs("div", { className: "health-audit-item", children: [_jsx("span", { className: "health-audit-icon", children: getAuditScoreIcon(audit.score) }), _jsxs("div", { className: "health-audit-content", children: [_jsxs("span", { className: "health-audit-title", children: ["(", Math.round((audit.score || 0) * 100), "%) ", audit.title] }), audit.displayValue && audit.score !== 1 && (_jsx("p", { className: "health-audit-description", children: audit.displayValue })), audit.details && audit.details.items && Array.isArray(audit.details.items) && audit.details.items.length > 0 && audit.score !== 1 && (_jsx("div", { className: "health-audit-details", children: _jsx("div", { style: { fontSize: '0.75rem', color: '#6b7280', marginTop: '0.25rem' }, children: audit.details.items
153
+ .map((audit) => (_jsxs("div", { className: "health-audit-item", children: [_jsx("span", { className: "health-audit-icon", children: getAuditScoreIcon(audit.score) }), _jsxs("div", { className: "health-audit-content", children: [_jsxs("span", { className: "health-audit-title", children: [audit.score === null ? '(N/A)' : `(${Math.round((audit.score || 0) * 100)}%)`, " ", audit.title] }), audit.displayValue && audit.score !== 1 && (_jsx("p", { className: "health-audit-description", children: audit.displayValue })), audit.details && audit.details.items && Array.isArray(audit.details.items) && audit.details.items.length > 0 && audit.score !== 1 && (_jsx("div", { className: "health-audit-details", children: _jsx("div", { style: { fontSize: '0.75rem', color: '#6b7280', marginTop: '0.25rem' }, children: audit.details.items
205
154
  .filter((item) => item.score !== 1)
206
155
  .map((item, idx) => (_jsx("div", { style: { marginBottom: '0.125rem' }, children: formatAuditItem(item) }, idx))) }) }))] })] }, audit.id))) })] })), _jsxs("p", { className: "health-timestamp", children: ["Last checked: ", new Date(data.timestamp).toLocaleString()] })] }));
207
156
  } }));
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
3
3
  import { useCallback } from 'react';
4
4
  import PropTypes from 'prop-types';
5
5
  import { SiteHealthTemplate } from './site-health-template';
6
- import { getScoreIndicator } from './site-health-indicators';
6
+ import { formatScore, getScoreColor } from './site-health-utils';
7
7
  SiteHealthOverview.propTypes = {
8
8
  siteName: PropTypes.string.isRequired,
9
9
  };
@@ -25,14 +25,6 @@ export function SiteHealthOverview({ siteName }) {
25
25
  return (_jsxs("p", { style: { color: '#ef4444', fontSize: '0.875rem' }, children: ["Error: ", siteData.error] }));
26
26
  }
27
27
  // Helper functions
28
- const getScoreColor = (score) => {
29
- return getScoreIndicator(score).color;
30
- };
31
- const formatScore = (score) => {
32
- if (score === null)
33
- return 'N/A';
34
- return `${Math.round(score * 100)}%`;
35
- };
36
28
  const getStatusColor = (status) => {
37
29
  switch (status) {
38
30
  case 'good': return '#10b981'; // green
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
3
3
  import { useCallback } from 'react';
4
4
  import PropTypes from 'prop-types';
5
5
  import { SiteHealthTemplate } from './site-health-template';
6
- import { getScoreIndicator } from './site-health-indicators';
6
+ import { formatAuditItem, getAuditScoreIcon, getScoreColor, formatScore } from './site-health-utils';
7
7
  SiteHealthPerformance.propTypes = {
8
8
  siteName: PropTypes.string.isRequired,
9
9
  };
@@ -24,166 +24,10 @@ export function SiteHealthPerformance({ siteName }) {
24
24
  if (siteData.status === 'error') {
25
25
  return (_jsxs("p", { style: { color: '#ef4444', fontSize: '0.875rem' }, children: ["Error: ", siteData.error] }));
26
26
  }
27
- const getAuditScoreIcon = (score) => {
28
- return getScoreIndicator(score).icon;
29
- };
30
- const getScoreColor = (score) => {
31
- return getScoreIndicator(score).color;
32
- };
33
- const formatScore = (score) => {
34
- if (score === null)
35
- return 'N/A';
36
- return `${Math.round(score * 100)}%`;
37
- };
38
- // Helper function to display audit item details
39
- const formatAuditItem = (item, auditTitle) => {
40
- // Handle URLs
41
- if (item.url && typeof item.url === 'string') {
42
- return item.url;
43
- }
44
- // Handle sources (like JavaScript files)
45
- if (item.source && typeof item.source === 'string') {
46
- return item.source;
47
- }
48
- // Handle text descriptions
49
- if (item.text && typeof item.text === 'string') {
50
- return item.text;
51
- }
52
- // Handle entities (like "Google Tag Manager")
53
- if (item.entity && typeof item.entity === 'string') {
54
- return item.entity;
55
- }
56
- // Handle nodes with selectors
57
- if (item.node && typeof item.node === 'object' && 'selector' in item.node) {
58
- return `Element: ${item.node.selector}`;
59
- }
60
- // Handle nodes with snippets
61
- if (item.node && typeof item.node === 'object' && 'snippet' in item.node) {
62
- const snippet = item.node.snippet;
63
- return `Element: ${snippet.length > 50 ? snippet.substring(0, 50) + '...' : snippet}`;
64
- }
65
- // Handle origins (like domains)
66
- if (item.origin && typeof item.origin === 'string') {
67
- return item.origin;
68
- }
69
- // Handle labels
70
- if (item.label && typeof item.label === 'string') {
71
- return item.label;
72
- }
73
- // Handle numeric values with units
74
- if (item.value && typeof item.value === 'object' && 'type' in item.value && item.value.type === 'numeric') {
75
- const value = item.value;
76
- return `${value.value}${item.unit || ''}`;
77
- }
78
- // Handle statistics
79
- if (item.statistic && typeof item.statistic === 'string' && item.value) {
80
- if (typeof item.value === 'object' && 'type' in item.value && item.value.type === 'numeric') {
81
- const value = item.value;
82
- return `${item.statistic}: ${value.value}`;
83
- }
84
- return item.statistic;
85
- }
86
- // Handle timing data with audit context
87
- if (typeof item === 'number') {
88
- let context = '';
89
- if (auditTitle) {
90
- if (auditTitle.toLowerCase().includes('server') || auditTitle.toLowerCase().includes('backend')) {
91
- context = ' server response';
92
- }
93
- else if (auditTitle.toLowerCase().includes('network') || auditTitle.toLowerCase().includes('request')) {
94
- context = ' network request';
95
- }
96
- else if (auditTitle.toLowerCase().includes('render') || auditTitle.toLowerCase().includes('blocking')) {
97
- context = ' render blocking';
98
- }
99
- else if (auditTitle.toLowerCase().includes('javascript') || auditTitle.toLowerCase().includes('js')) {
100
- context = ' JavaScript';
101
- }
102
- else if (auditTitle.toLowerCase().includes('image') || auditTitle.toLowerCase().includes('media')) {
103
- context = ' media resource';
104
- }
105
- }
106
- return `${item.toFixed(2)}ms${context}`;
107
- }
108
- if (item.value && typeof item.value === 'number') {
109
- const unit = item.unit || 'ms';
110
- let context = '';
111
- if (auditTitle && unit === 'ms') {
112
- if (auditTitle.toLowerCase().includes('server')) {
113
- context = ' server time';
114
- }
115
- else if (auditTitle.toLowerCase().includes('network')) {
116
- context = ' network time';
117
- }
118
- }
119
- return `${item.value.toFixed(2)}${unit}${context}`;
120
- }
121
- // Handle timing data with more context
122
- if (item.duration && typeof item.duration === 'number') {
123
- const duration = item.duration;
124
- let context = '';
125
- if (item.url && typeof item.url === 'string') {
126
- context = ` for ${item.url}`;
127
- }
128
- else if (item.source && typeof item.source === 'string') {
129
- context = ` for ${item.source}`;
130
- }
131
- else if (item.name && typeof item.name === 'string') {
132
- context = ` for ${item.name}`;
133
- }
134
- else if (item.path && typeof item.path === 'string') {
135
- context = ` for ${item.path}`;
136
- }
137
- else if (item.request && typeof item.request === 'string') {
138
- context = ` for ${item.request}`;
139
- }
140
- return `${duration.toFixed(2)}ms${context}`;
141
- }
142
- // Handle response times
143
- if (item.responseTime && typeof item.responseTime === 'number') {
144
- const url = (item.url && typeof item.url === 'string') ? ` (${item.url})` : '';
145
- return `${item.responseTime.toFixed(2)}ms response time${url}`;
146
- }
147
- // Handle start/end times
148
- if ((item.startTime || item.endTime) && typeof (item.startTime || item.endTime) === 'number') {
149
- const start = item.startTime && typeof item.startTime === 'number' ? item.startTime.toFixed(2) : '?';
150
- const end = item.endTime && typeof item.endTime === 'number' ? item.endTime.toFixed(2) : '?';
151
- const url = (item.url && typeof item.url === 'string') ? ` for ${item.url}` : '';
152
- return `${start}ms - ${end}ms${url}`;
153
- }
154
- // Handle transfer size with timing
155
- if (item.transferSize && typeof item.transferSize === 'number' && item.duration && typeof item.duration === 'number') {
156
- const size = (item.transferSize / 1024).toFixed(1);
157
- const time = item.duration.toFixed(2);
158
- const url = (item.url && typeof item.url === 'string') ? ` (${item.url})` : '';
159
- return `${size} KB in ${time}ms${url}`;
160
- }
161
- // Handle main thread time
162
- if (item.mainThreadTime && typeof item.mainThreadTime === 'number') {
163
- return `${item.mainThreadTime.toFixed(1)}ms`;
164
- }
165
- // For other objects, try to find a meaningful display
166
- if (item.group && typeof item.group === 'string') {
167
- return item.group;
168
- }
169
- if (item.type && typeof item.type === 'string') {
170
- return item.type;
171
- }
172
- // If we can't find anything meaningful, provide a generic description
173
- // This handles raw timing data that might be from various performance metrics
174
- if (typeof item === 'number') {
175
- return `${item.toFixed(2)}ms`;
176
- }
177
- if (item.value && typeof item.value === 'number') {
178
- const unit = item.unit || 'ms';
179
- return `${item.value.toFixed(2)}${unit}`;
180
- }
181
- return 'Performance metric data available';
182
- };
183
27
  return (_jsxs(_Fragment, { children: [_jsx("h4", { className: "health-site-name", children: siteData.site.replace('-', ' ') }), _jsxs("p", { className: "health-site-url", children: ["URL: ", siteData.url] }), _jsx("div", { style: { marginBottom: '1.5rem' }, children: _jsxs("div", { className: "health-score-item", style: { width: '100%' }, children: [_jsx("div", { className: "health-score-label", children: "Performance Score" }), _jsx("div", { className: "health-score-value", style: { color: getScoreColor(siteData.scores.performance) }, children: formatScore(siteData.scores.performance) }), _jsx("div", { className: "health-score-bar", children: _jsx("div", { className: "health-score-fill", style: {
184
28
  width: siteData.scores.performance !== null ? `${siteData.scores.performance * 100}%` : '0%',
185
29
  backgroundColor: siteData.scores.performance !== null ? getScoreColor(siteData.scores.performance) : '#6b7280'
186
- } }) })] }) }), ((siteData.categories.performance?.audits?.length > 0) || (siteData.categories.pwa?.audits?.length > 0)) && (_jsxs("div", { children: [_jsx("h5", { style: { fontSize: '1rem', fontWeight: '600', marginBottom: '1rem' }, children: "Performance Opportunities" }), _jsx("div", { className: "space-y-2", children: [
30
+ } }) })] }) }), ((siteData.categories.performance?.audits?.length > 0) || (siteData.categories.pwa?.audits?.length > 0)) && (_jsxs("div", { children: [_jsx("h5", { style: { fontSize: '1rem', fontWeight: '600', marginBottom: '1rem' }, children: "Performance Opportunities" }), _jsx("div", { className: "health-audit-list", children: [
187
31
  ...(siteData.categories.performance?.audits || []),
188
32
  ...(siteData.categories.pwa?.audits || [])
189
33
  ]