@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.
- package/dist/components/admin/site-health/seo-metrics.config.json +38 -18
- package/dist/components/admin/site-health/site-health-accessibility.js +2 -94
- package/dist/components/admin/site-health/site-health-axe-core.js +2 -2
- package/dist/components/admin/site-health/site-health-cloudwatch.js +2 -2
- package/dist/components/admin/site-health/site-health-dependency-vulnerabilities.js +1 -1
- package/dist/components/admin/site-health/site-health-github.js +3 -3
- package/dist/components/admin/site-health/site-health-google-analytics.js +1 -1
- package/dist/components/admin/site-health/site-health-google-search-console.js +1 -1
- package/dist/components/admin/site-health/site-health-on-site-seo.integration.js +275 -3
- package/dist/components/admin/site-health/site-health-on-site-seo.js +5 -56
- package/dist/components/admin/site-health/site-health-overview.js +1 -9
- package/dist/components/admin/site-health/site-health-performance.js +2 -158
- package/dist/components/admin/site-health/site-health-security.js +2 -139
- package/dist/components/admin/site-health/site-health-seo.js +2 -94
- package/dist/components/admin/site-health/site-health-utils.js +170 -0
- package/dist/components/admin/site-health/site-health.css +33 -3
- package/dist/components/cms/smartimage.js +28 -8
- package/dist/components/general/table.js +2 -2
- package/dist/components/sitebuilder/config/ConfigBuilder.js +12 -3
- package/dist/index.adminclient.js +1 -0
- package/dist/index.adminserver.js +1 -0
- package/dist/types/components/admin/site-health/site-health-accessibility.d.ts.map +1 -1
- package/dist/types/components/admin/site-health/site-health-on-site-seo.d.ts.map +1 -1
- package/dist/types/components/admin/site-health/site-health-on-site-seo.integration.d.ts.map +1 -1
- package/dist/types/components/admin/site-health/site-health-overview.d.ts.map +1 -1
- package/dist/types/components/admin/site-health/site-health-performance.d.ts.map +1 -1
- package/dist/types/components/admin/site-health/site-health-security.d.ts.map +1 -1
- package/dist/types/components/admin/site-health/site-health-seo.d.ts.map +1 -1
- package/dist/types/components/admin/site-health/site-health-utils.d.ts +17 -0
- package/dist/types/components/admin/site-health/site-health-utils.d.ts.map +1 -0
- package/dist/types/components/cms/smartimage.d.ts.map +1 -1
- package/dist/types/components/sitebuilder/config/ConfigBuilder.d.ts.map +1 -1
- package/dist/types/index.adminclient.d.ts +1 -0
- package/dist/types/index.adminserver.d.ts +1 -0
- package/dist/types/tests/test-seo-logic.d.ts +2 -0
- package/dist/types/tests/test-seo-logic.d.ts.map +1 -0
- 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
|
-
//
|
|
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 {
|
|
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
|
-
|
|
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: [
|
|
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 {
|
|
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 {
|
|
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: "
|
|
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
|
]
|