@elementor/editor-audits 4.3.4 → 4.3.5

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 (52) hide show
  1. package/dist/index.d.mts +2 -0
  2. package/dist/index.d.ts +2 -0
  3. package/dist/index.js +789 -1098
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +731 -1040
  6. package/dist/index.mjs.map +1 -1
  7. package/package.json +17 -17
  8. package/src/audits/accessibility-statement.ts +2 -2
  9. package/src/audits/cookie-policy.ts +1 -0
  10. package/src/audits/images-alt-text.ts +6 -4
  11. package/src/audits/images-inefficient-format.ts +8 -9
  12. package/src/audits/images-too-large.ts +8 -9
  13. package/src/audits/nested-boxed-containers.ts +5 -0
  14. package/src/components/audit-feedback.tsx +4 -1
  15. package/src/components/audit-panel.tsx +13 -13
  16. package/src/components/audit-toggle-icon.tsx +22 -0
  17. package/src/components/fix-violation-with-angie.tsx +18 -7
  18. package/src/components/pages/category-page.tsx +27 -3
  19. package/src/components/pages/overview-page.tsx +24 -29
  20. package/src/components/report-shell.tsx +10 -91
  21. package/src/components/rescan-button.tsx +38 -0
  22. package/src/components/severity-icons.tsx +5 -1
  23. package/src/components/severity-summary-cards.tsx +52 -0
  24. package/src/components/violation-cta-button.tsx +10 -2
  25. package/src/components/violation-icons.tsx +2 -2
  26. package/src/components/violation-row.tsx +118 -69
  27. package/src/hooks/use-audit-report.ts +5 -4
  28. package/src/hooks/use-audit-toggle-props.ts +2 -2
  29. package/src/hooks/use-single-expanded-card.ts +11 -0
  30. package/src/init.ts +2 -0
  31. package/src/register-audits.ts +0 -2
  32. package/src/register-promotions.ts +0 -23
  33. package/src/runner.ts +6 -2
  34. package/src/store/selectors.ts +4 -0
  35. package/src/store/slice.ts +10 -1
  36. package/src/sync-report-staleness.ts +14 -0
  37. package/src/types.ts +2 -0
  38. package/src/utils/all-image-sources.ts +17 -0
  39. package/src/utils/alt-text-image-sources.ts +24 -0
  40. package/src/utils/atomic-image-sources.ts +8 -4
  41. package/src/utils/audit-status-summary.ts +0 -57
  42. package/src/utils/image-like-sources.ts +0 -12
  43. package/src/utils/page-attachments.ts +3 -11
  44. package/src/audits/too-many-widgets.ts +0 -53
  45. package/src/components/audit-statuses-section.tsx +0 -65
  46. package/src/components/count-summary-circle.tsx +0 -66
  47. package/src/components/pages/all-audits-page.tsx +0 -65
  48. package/src/components/pages/issues-page.tsx +0 -65
  49. package/src/components/score-bar.tsx +0 -56
  50. package/src/components/score-circle.tsx +0 -60
  51. package/src/components/severity-filter-chips.tsx +0 -57
  52. package/src/utils/score-thresholds.ts +0 -23
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elementor/editor-audits",
3
- "version": "4.3.4",
3
+ "version": "4.3.5",
4
4
  "private": false,
5
5
  "author": "Elementor Team",
6
6
  "homepage": "https://elementor.com/",
@@ -39,22 +39,22 @@
39
39
  "dev": "tsup --config=../../tsup.dev.ts"
40
40
  },
41
41
  "dependencies": {
42
- "@elementor/editor": "4.3.4",
43
- "@elementor/editor-mcp": "4.3.4",
44
- "@elementor/editor-app-bar": "4.3.4",
45
- "@elementor/editor-elements": "4.3.4",
46
- "@elementor/editor-floating-panels": "4.3.4",
47
- "@elementor/editor-notifications": "4.3.4",
48
- "@elementor/editor-props": "4.3.4",
49
- "@elementor/editor-styles": "4.3.4",
50
- "@elementor/editor-v1-adapters": "4.3.4",
51
- "@elementor/events": "4.3.4",
52
- "@elementor/http-client": "4.3.4",
53
- "@elementor/icons": "~1.77.0",
54
- "@elementor/locations": "4.3.4",
55
- "@elementor/session": "4.3.4",
56
- "@elementor/store": "4.3.4",
57
- "@elementor/ui": "1.37.5"
42
+ "@elementor/editor": "4.3.5",
43
+ "@elementor/editor-mcp": "4.3.5",
44
+ "@elementor/editor-app-bar": "4.3.5",
45
+ "@elementor/editor-elements": "4.3.5",
46
+ "@elementor/editor-floating-panels": "4.3.5",
47
+ "@elementor/editor-notifications": "4.3.5",
48
+ "@elementor/editor-props": "4.3.5",
49
+ "@elementor/editor-styles": "4.3.5",
50
+ "@elementor/editor-v1-adapters": "4.3.5",
51
+ "@elementor/events": "4.3.5",
52
+ "@elementor/http-client": "4.3.5",
53
+ "@elementor/icons": "~1.78.1",
54
+ "@elementor/locations": "4.3.5",
55
+ "@elementor/session": "4.3.5",
56
+ "@elementor/store": "4.3.5",
57
+ "@elementor/ui": "1.41.1"
58
58
  },
59
59
  "peerDependencies": {
60
60
  "@wordpress/i18n": "^5.13.0",
@@ -29,9 +29,9 @@ export const audit: Audit = {
29
29
  externalUrl: isReady
30
30
  ? ctx.pageContext.ally_accessibility_statement_url
31
31
  : ctx.pageContext.ally_plugin_url,
32
- ctaLabel: isReady ? __( 'Create', 'elementor' ) : undefined,
32
+ ctaLabel: __( 'Create', 'elementor' ),
33
33
  secondaryCtaLabel: __( 'Learn more', 'elementor' ),
34
- secondaryCtaUrl: 'https://go.elementor.com/acc-plg-learn-more',
34
+ secondaryCtaUrl: 'https://elementor.com/help/start-using-ally/#statement',
35
35
  },
36
36
  ],
37
37
  };
@@ -25,6 +25,7 @@ export const audit: Audit = {
25
25
  auditId: audit.id,
26
26
  label: __( 'Cookiez plugin is not installed or active.', 'elementor' ),
27
27
  externalUrl: ctx.pageContext.cookiez_plugin_url,
28
+ ctaLabel: __( 'Set up cookie consent', 'elementor' ),
28
29
  },
29
30
  ],
30
31
  };
@@ -1,8 +1,8 @@
1
1
  import { __ } from '@wordpress/i18n';
2
2
 
3
3
  import { type Audit, type AuditViolation } from '../types';
4
+ import { hasAltTextImages, walkAltTextImageSources } from '../utils/alt-text-image-sources';
4
5
  import { hasMeaningfulAlt, isImageSourcePresent } from '../utils/image-alt';
5
- import { hasPageImages, walkImageLikeSources } from '../utils/image-like-sources';
6
6
 
7
7
  export const audit: Audit = {
8
8
  id: 'audits/images-alt-text',
@@ -13,15 +13,16 @@ export const audit: Audit = {
13
13
  severity: 'error',
14
14
  weight: 10,
15
15
  evaluate: ( ctx ) => {
16
- if ( ! hasPageImages( ctx.elements.tree ) ) {
16
+ if ( ! hasAltTextImages( ctx.elements.tree ) ) {
17
17
  return { status: 'skipped', reason: __( 'No images', 'elementor' ) };
18
18
  }
19
19
 
20
+ const isReady = ctx.pageContext.ally_plugin_active;
20
21
  const violations: AuditViolation[] = [];
21
22
  const failedWidgetIds = new Set< string >();
22
23
  let missingAltImageCount = 0;
23
24
 
24
- walkImageLikeSources( ctx.elements.tree, ( { node, media } ) => {
25
+ walkAltTextImageSources( ctx.elements.tree, ( { node, media } ) => {
25
26
  if ( ! isImageSourcePresent( media ) || hasMeaningfulAlt( media, ctx.pageContext ) ) {
26
27
  return;
27
28
  }
@@ -35,7 +36,8 @@ export const audit: Audit = {
35
36
  elementId: node.id,
36
37
  targetHint: 'element-settings',
37
38
  label: __( 'Image is missing alt text.', 'elementor' ),
38
- externalUrl: ctx.pageContext.ally_plugin_url,
39
+ externalUrl: isReady ? ctx.pageContext.ally_scan_url : ctx.pageContext.ally_plugin_url,
40
+ ctaLabel: __( 'Fix with Ally', 'elementor' ),
39
41
  } );
40
42
  }
41
43
  } );
@@ -1,10 +1,8 @@
1
1
  import { __ } from '@wordpress/i18n';
2
2
 
3
3
  import { type Audit, type AuditViolation, type ElementSnapshotNode } from '../types';
4
- import { walkAtomicBackgroundImageSources } from '../utils/atomic-background-image-sources';
5
- import { walkAtomicImageSources } from '../utils/atomic-image-sources';
6
- import { walkBackgroundImageSources } from '../utils/background-image-sources';
7
- import { type ImageLikeMedia, walkImageLikeSources } from '../utils/image-like-sources';
4
+ import { walkAllImageSources } from '../utils/all-image-sources';
5
+ import { type ImageLikeMedia } from '../utils/image-like-sources';
8
6
  import { buildImageSizeKey } from '../utils/image-size-key';
9
7
 
10
8
  const LEGACY_RASTER_MIME_TYPES = new Set( [ 'image/jpeg', 'image/png', 'image/gif', 'image/bmp', 'image/tiff' ] );
@@ -18,6 +16,7 @@ export const audit: Audit = {
18
16
  severity: 'info',
19
17
  weight: 1,
20
18
  evaluate: ( ctx ) => {
19
+ const isReady = ctx.pageContext.image_optimization_plugin_active;
21
20
  const offendingElementIds = new Set< string >();
22
21
  let hasAnyImage = false;
23
22
  let inefficientImageFormatCount = 0;
@@ -43,10 +42,7 @@ export const audit: Audit = {
43
42
  offendingElementIds.add( node.id );
44
43
  };
45
44
 
46
- walkImageLikeSources( ctx.elements.tree, ( { node, media } ) => evaluateSource( node, media ) );
47
- walkBackgroundImageSources( ctx.elements.tree, ( { node, media } ) => evaluateSource( node, media ) );
48
- walkAtomicImageSources( ctx.elements.tree, ( { node, media } ) => evaluateSource( node, media ) );
49
- walkAtomicBackgroundImageSources( ctx.elements.tree, ( { node, media } ) => evaluateSource( node, media ) );
45
+ walkAllImageSources( ctx.elements.tree, ( { node, media } ) => evaluateSource( node, media ) );
50
46
 
51
47
  if ( ! hasAnyImage ) {
52
48
  return { status: 'skipped', reason: __( 'No images', 'elementor' ) };
@@ -57,7 +53,10 @@ export const audit: Audit = {
57
53
  elementId,
58
54
  targetHint: 'element-settings' as const,
59
55
  label: __( "Image isn't served in a modern format like WebP or AVIF.", 'elementor' ),
60
- externalUrl: ctx.pageContext.image_optimization_plugin_url,
56
+ externalUrl: isReady
57
+ ? ctx.pageContext.image_optimization_settings_url
58
+ : ctx.pageContext.image_optimization_plugin_url,
59
+ ctaLabel: __( 'Optimize all', 'elementor' ),
61
60
  } ) );
62
61
 
63
62
  return violations.length === 0
@@ -1,10 +1,8 @@
1
1
  import { __, sprintf } from '@wordpress/i18n';
2
2
 
3
3
  import { type Audit, type AuditViolation, type ElementSnapshotNode } from '../types';
4
- import { walkAtomicBackgroundImageSources } from '../utils/atomic-background-image-sources';
5
- import { walkAtomicImageSources } from '../utils/atomic-image-sources';
6
- import { walkBackgroundImageSources } from '../utils/background-image-sources';
7
- import { type ImageLikeMedia, walkImageLikeSources } from '../utils/image-like-sources';
4
+ import { walkAllImageSources } from '../utils/all-image-sources';
5
+ import { type ImageLikeMedia } from '../utils/image-like-sources';
8
6
  import { buildImageSizeKey } from '../utils/image-size-key';
9
7
 
10
8
  const SIZE_THRESHOLD_BYTES = 500 * 1024;
@@ -19,6 +17,7 @@ export const audit: Audit = {
19
17
  severity: 'warning',
20
18
  weight: 7,
21
19
  evaluate: ( ctx ) => {
20
+ const isReady = ctx.pageContext.image_optimization_plugin_active;
22
21
  const widgetMaxKb = new Map< string, number >();
23
22
  let oversizedImageCount = 0;
24
23
  let hasAnyImage = false;
@@ -47,10 +46,7 @@ export const audit: Audit = {
47
46
  widgetMaxKb.set( node.id, Math.max( currentMax, kb ) );
48
47
  };
49
48
 
50
- walkImageLikeSources( ctx.elements.tree, ( { node, media } ) => evaluateSource( node, media ) );
51
- walkBackgroundImageSources( ctx.elements.tree, ( { node, media } ) => evaluateSource( node, media ) );
52
- walkAtomicImageSources( ctx.elements.tree, ( { node, media } ) => evaluateSource( node, media ) );
53
- walkAtomicBackgroundImageSources( ctx.elements.tree, ( { node, media } ) => evaluateSource( node, media ) );
49
+ walkAllImageSources( ctx.elements.tree, ( { node, media } ) => evaluateSource( node, media ) );
54
50
 
55
51
  if ( ! hasAnyImage ) {
56
52
  return { status: 'skipped', reason: __( 'No images', 'elementor' ) };
@@ -65,7 +61,10 @@ export const audit: Audit = {
65
61
  __( 'Image is %d KB (over 500 KB).', 'elementor' ),
66
62
  kb
67
63
  ),
68
- externalUrl: ctx.pageContext.image_optimization_plugin_url,
64
+ externalUrl: isReady
65
+ ? ctx.pageContext.image_optimization_settings_url
66
+ : ctx.pageContext.image_optimization_plugin_url,
67
+ ctaLabel: __( 'Optimize all', 'elementor' ),
69
68
  } ) );
70
69
 
71
70
  return violations.length === 0
@@ -35,6 +35,11 @@ export const audit: Audit = {
35
35
  elementId: node.id,
36
36
  targetHint: 'element-settings',
37
37
  label: __( 'Nested boxed container.', 'elementor' ),
38
+ angieFix: true,
39
+ angiePrompt: __(
40
+ 'Find containers that have nested containers. Containers can be deep-nested. Update the nested container styling so that when a parent container already uses a boxed setting, any inner container inside it is set to Full Width. Keep the first parent container boxed, remove all boxed styling from the nested inner containers, and preserve the inner container’s content, spacing, alignment, and hierarchy.',
41
+ 'elementor'
42
+ ),
38
43
  } );
39
44
  }
40
45
  } );
@@ -1,5 +1,6 @@
1
1
  import * as React from 'react';
2
2
  import { useState } from 'react';
3
+ import { useFloatingPanelZIndex } from '@elementor/editor-floating-panels';
3
4
  import { notify } from '@elementor/editor-notifications';
4
5
  import { isExperimentActive } from '@elementor/editor-v1-adapters';
5
6
  import { useMixpanel } from '@elementor/events';
@@ -21,6 +22,7 @@ import {
21
22
  import { __ } from '@wordpress/i18n';
22
23
 
23
24
  import {
25
+ AUDIT_PANEL_ID,
24
26
  FEEDBACK_CANCELLED_EVENT,
25
27
  FEEDBACK_CLICKED_EVENT,
26
28
  FEEDBACK_CLOSED_EVENT,
@@ -58,6 +60,7 @@ export default function AuditFeedback() {
58
60
  const [ feedbackText, setFeedbackText ] = useState( '' );
59
61
  const [ isSubmitting, setIsSubmitting ] = useState( false );
60
62
  const { dispatchEvent: trackEvent = ( ...args: unknown[] ) => void args } = useMixpanel();
63
+ const panelZIndex = useFloatingPanelZIndex( AUDIT_PANEL_ID );
61
64
 
62
65
  if ( ! isExperimentActive( FEEDBACK_EXPERIMENT_NAME ) ) {
63
66
  return null;
@@ -117,7 +120,7 @@ export default function AuditFeedback() {
117
120
 
118
121
  return (
119
122
  <>
120
- <Tooltip title={ triggerLabel } placement="top">
123
+ <Tooltip title={ triggerLabel } placement="top" PopperProps={ { sx: { zIndex: panelZIndex } } }>
121
124
  <IconButton size="small" aria-label={ triggerLabel } onClick={ handleOpen }>
122
125
  <MessageLinesIcon fontSize="small" />
123
126
  </IconButton>
@@ -1,27 +1,29 @@
1
1
  import * as React from 'react';
2
- import { getCurrentDocumentId } from '@elementor/editor-elements';
2
+ import { getHostDocumentId } from '@elementor/editor-elements';
3
3
  import { FloatingPanelBody, FloatingPanelFooter, FloatingPanelHeader } from '@elementor/editor-floating-panels';
4
- import { Box, Button, Typography } from '@elementor/ui';
4
+ import { Box, Typography } from '@elementor/ui';
5
5
  import { __ } from '@wordpress/i18n';
6
6
 
7
+ import { AUDIT_PANEL_ID } from '../constants';
7
8
  import { useAuditReport } from '../hooks/use-audit-report';
8
9
  import AuditFeedback from './audit-feedback';
9
10
  import ErrorPage from './pages/error-page';
10
11
  import LoadingPage from './pages/loading-page';
11
12
  import WelcomePage from './pages/welcome-page';
12
13
  import ReportShell from './report-shell';
14
+ import RescanButton from './rescan-button';
13
15
 
14
16
  export default function AuditPanel() {
15
- const { status, report, error, run } = useAuditReport();
17
+ const { status, report, error, isStale, run } = useAuditReport();
16
18
 
17
- const currentDocumentId = getCurrentDocumentId() ?? 0;
18
- const onRun = () => run( currentDocumentId );
19
+ const hostDocumentId = getHostDocumentId() ?? 0;
20
+ const onRun = () => run( hostDocumentId );
19
21
  const lastScanLabel = report ? new Date( report.runAt ).toLocaleTimeString() : null;
20
22
 
21
23
  return (
22
24
  <>
23
25
  <FloatingPanelHeader
24
- panelId="audit-panel"
26
+ panelId={ AUDIT_PANEL_ID }
25
27
  title={ __( 'Page Audit', 'elementor' ) }
26
28
  badge={ __( 'Beta', 'elementor' ) }
27
29
  titleVariant="subtitle2"
@@ -47,14 +49,12 @@ export default function AuditPanel() {
47
49
  <Box sx={ { flex: 1 } } />
48
50
  ) }
49
51
  { lastScanLabel && <AuditFeedback /> }
50
- <Button
51
- variant="contained"
52
- size="small"
52
+ <RescanButton
53
+ hasScanned={ Boolean( lastScanLabel ) }
54
+ isStale={ isStale }
55
+ disabled={ status === 'loading' || hostDocumentId === 0 }
53
56
  onClick={ onRun }
54
- disabled={ status === 'loading' || currentDocumentId === 0 }
55
- >
56
- { lastScanLabel ? __( 'Re-scan', 'elementor' ) : __( 'Run page audit', 'elementor' ) }
57
- </Button>
57
+ />
58
58
  </FloatingPanelFooter>
59
59
  </>
60
60
  );
@@ -0,0 +1,22 @@
1
+ import * as React from 'react';
2
+ import { ShieldCheckIcon } from '@elementor/icons';
3
+ import { __useSelector as useSelector } from '@elementor/store';
4
+ import { Badge } from '@elementor/ui';
5
+
6
+ import { type GlobalState, selectIsStale } from '../store';
7
+
8
+ export default function AuditToggleIcon() {
9
+ const isStale = useSelector( ( state: GlobalState ) => selectIsStale( state ) );
10
+
11
+ return (
12
+ <Badge
13
+ variant="dot"
14
+ color="warning"
15
+ invisible={ ! isStale }
16
+ overlap="circular"
17
+ slotProps={ { badge: { 'data-testid': 'audit-stale-indicator' } } }
18
+ >
19
+ <ShieldCheckIcon />
20
+ </Badge>
21
+ );
22
+ }
@@ -1,16 +1,18 @@
1
1
  import * as React from 'react';
2
2
  import { isAngieAvailable, sendPromptToAngie } from '@elementor/editor-mcp';
3
3
  import { AngieIcon } from '@elementor/icons';
4
- import { IconButton, Tooltip } from '@elementor/ui';
4
+ import { Button, IconButton, Tooltip } from '@elementor/ui';
5
5
  import { __ } from '@wordpress/i18n';
6
6
 
7
7
  import { ANGIE_FIX_ENTRY_POINT, CREATE_WIDGET_EVENT } from '../constants';
8
8
 
9
9
  type Props = {
10
10
  prompt: string;
11
+ panelZIndex: number;
12
+ variant?: 'icon' | 'button';
11
13
  };
12
14
 
13
- export default function FixViolationWithAngie( { prompt }: Props ) {
15
+ export default function FixViolationWithAngie( { prompt, panelZIndex, variant = 'icon' }: Props ) {
14
16
  const label = __( 'Fix with Angie', 'elementor' );
15
17
  const href = `#angie-prompt=${ encodeURIComponent( prompt ) }`;
16
18
 
@@ -33,16 +35,25 @@ export default function FixViolationWithAngie( { prompt }: Props ) {
33
35
  );
34
36
  };
35
37
 
36
- return (
37
- <Tooltip title={ label }>
38
- <IconButton
39
- className="violation-hover-icon"
38
+ if ( variant === 'button' ) {
39
+ return (
40
+ <Button
40
41
  component="a"
41
42
  href={ href }
42
43
  size="small"
43
- aria-label={ label }
44
+ color="secondary"
45
+ variant="outlined"
46
+ startIcon={ <AngieIcon fontSize="tiny" aria-hidden={ true } /> }
44
47
  onClick={ handleClick }
45
48
  >
49
+ { label }
50
+ </Button>
51
+ );
52
+ }
53
+
54
+ return (
55
+ <Tooltip title={ label } placement="top" PopperProps={ { sx: { zIndex: panelZIndex } } }>
56
+ <IconButton component="a" href={ href } size="small" aria-label={ label } onClick={ handleClick }>
46
57
  <AngieIcon fontSize="tiny" />
47
58
  </IconButton>
48
59
  </Tooltip>
@@ -3,6 +3,7 @@ import { Box } from '@elementor/ui';
3
3
  import { __ } from '@wordpress/i18n';
4
4
 
5
5
  import { CATEGORY_LABELS } from '../../constants';
6
+ import { useSingleExpandedCard } from '../../hooks/use-single-expanded-card';
6
7
  import { type AuditCategory, type PageAuditReport } from '../../types';
7
8
  import { partitionAuditResults } from '../../utils/audit-status-summary';
8
9
  import { CATEGORY_ICONS } from '../category-icons';
@@ -18,7 +19,8 @@ type Props = {
18
19
 
19
20
  export default function CategoryPage( { category, report, onBack }: Props ) {
20
21
  const Icon = CATEGORY_ICONS[ category ];
21
- const { failed, passed, totalViolations } = partitionAuditResults( report, { category } );
22
+ const { failed, passed, skipped, totalViolations } = partitionAuditResults( report, { category } );
23
+ const { expandedId, toggle } = useSingleExpandedCard();
22
24
 
23
25
  return (
24
26
  <>
@@ -36,12 +38,34 @@ export default function CategoryPage( { category, report, onBack }: Props ) {
36
38
  defaultExpanded
37
39
  >
38
40
  { failed.map( ( r ) => (
39
- <ViolationRow key={ r.audit.id } audit={ r.audit } violations={ r.result.violations } />
41
+ <ViolationRow
42
+ key={ r.audit.id }
43
+ audit={ r.audit }
44
+ violations={ r.result.violations }
45
+ expanded={ expandedId === r.audit.id }
46
+ onToggleExpand={ () => toggle( r.audit.id ) }
47
+ />
40
48
  ) ) }
41
49
  </StatusSection>
42
50
  <StatusSection label={ __( 'Passed audits', 'elementor' ) } count={ passed.length } color="success">
43
51
  { passed.map( ( r ) => (
44
- <ViolationRow key={ r.audit.id } audit={ r.audit } />
52
+ <ViolationRow
53
+ key={ r.audit.id }
54
+ audit={ r.audit }
55
+ expanded={ expandedId === r.audit.id }
56
+ onToggleExpand={ () => toggle( r.audit.id ) }
57
+ />
58
+ ) ) }
59
+ </StatusSection>
60
+ <StatusSection label={ __( 'Skipped audits', 'elementor' ) } count={ skipped.length } color="default">
61
+ { skipped.map( ( r ) => (
62
+ <ViolationRow
63
+ key={ r.audit.id }
64
+ audit={ r.audit }
65
+ skipReason={ r.result.reason }
66
+ expanded={ expandedId === r.audit.id }
67
+ onToggleExpand={ () => toggle( r.audit.id ) }
68
+ />
45
69
  ) ) }
46
70
  </StatusSection>
47
71
  </Box>
@@ -1,14 +1,14 @@
1
1
  import * as React from 'react';
2
- import { Box, Chip, Typography } from '@elementor/ui';
2
+ import { Box, Typography } from '@elementor/ui';
3
3
  import { __ } from '@wordpress/i18n';
4
4
 
5
5
  import { ALL_CATEGORIES, CATEGORY_LABELS } from '../../constants';
6
6
  import { type AuditCategory, type PageAuditReport } from '../../types';
7
7
  import { getPopulatedCategories } from '../../utils/audit-status-summary';
8
- import { getScoreTier } from '../../utils/score-thresholds';
8
+ import { countSeverities } from '../../utils/severity-counts';
9
+ import IssuesCategoryRow from '../issues-category-row';
9
10
  import Promotions from '../promotions';
10
- import ScoreBar from '../score-bar';
11
- import ScoreCircle from '../score-circle';
11
+ import SeveritySummaryCards from '../severity-summary-cards';
12
12
 
13
13
  type Props = {
14
14
  onCategoryClick: ( category: AuditCategory ) => void;
@@ -17,35 +17,30 @@ type Props = {
17
17
 
18
18
  export default function OverviewPage( { onCategoryClick, report }: Props ) {
19
19
  const populatedCategories = getPopulatedCategories( report.categories, ALL_CATEGORIES );
20
- const overallScore = getScoreTier( report.overall );
20
+
21
+ const categoryRows = populatedCategories
22
+ .map( ( category ) => ( { category, counts: countSeverities( report, category ) } ) )
23
+ .filter( ( { counts } ) => Object.values( counts ).some( ( count ) => count > 0 ) );
21
24
 
22
25
  return (
23
- <Box sx={ { display: 'flex', flexDirection: 'column', gap: 4, p: 2 } }>
24
- <Box sx={ { display: 'flex', alignItems: 'center', gap: 2 } }>
25
- <ScoreCircle color={ overallScore.color } score={ report.overall } />
26
- <Box sx={ { display: 'flex', flexDirection: 'column', gap: 0.5 } }>
27
- <Chip
28
- label={ overallScore.label }
29
- color={ overallScore.color }
30
- variant="standard"
31
- size="small"
32
- sx={ { fontWeight: 600, alignSelf: 'flex-start' } }
33
- />
34
- <Typography variant="body2" color="text.secondary">
35
- { __( 'Overall score', 'elementor' ) }
36
- </Typography>
26
+ <Box display="flex" flexDirection="column" gap={ 3 } p={ 2 }>
27
+ <SeveritySummaryCards report={ report } />
28
+ <Box display="flex" flexDirection="column" gap={ 1 }>
29
+ <Typography variant="subtitle1" component="h2">
30
+ { __( 'All issues', 'elementor' ) }
31
+ </Typography>
32
+ <Box display="flex" flexDirection="column" gap={ 1 }>
33
+ { categoryRows.map( ( { category, counts } ) => (
34
+ <IssuesCategoryRow
35
+ key={ category }
36
+ category={ category }
37
+ label={ CATEGORY_LABELS[ category ] }
38
+ counts={ counts }
39
+ onClick={ () => onCategoryClick( category ) }
40
+ />
41
+ ) ) }
37
42
  </Box>
38
43
  </Box>
39
- <Box sx={ { display: 'flex', flexDirection: 'column', gap: 2 } }>
40
- { populatedCategories.map( ( category ) => (
41
- <ScoreBar
42
- key={ category }
43
- label={ CATEGORY_LABELS[ category ] }
44
- score={ report.categories[ category ].score }
45
- onClick={ () => onCategoryClick( category ) }
46
- />
47
- ) ) }
48
- </Box>
49
44
  <Promotions report={ report } />
50
45
  </Box>
51
46
  );
@@ -1,114 +1,33 @@
1
1
  import * as React from 'react';
2
2
  import { useState } from 'react';
3
- import { Box, Divider, Tab, Tabs } from '@elementor/ui';
4
- import { __ } from '@wordpress/i18n';
3
+ import { Box } from '@elementor/ui';
5
4
 
6
5
  import { type AuditCategory, type PageAuditReport } from '../types';
7
- import { type AuditStatusGroup } from '../utils/audit-status-summary';
8
- import AllAuditsPage from './pages/all-audits-page';
9
6
  import CategoryPage from './pages/category-page';
10
- import IssuesPage from './pages/issues-page';
11
7
  import OverviewPage from './pages/overview-page';
12
8
 
13
- type TabId = 'overview' | 'issues';
14
-
15
- type ActivePage =
16
- | 'overview'
17
- | 'issues'
18
- | { backTo: TabId; category: AuditCategory }
19
- | { backTo: TabId; expand?: AuditStatusGroup; page: 'all-audits' };
9
+ type ActivePage = 'overview' | { category: AuditCategory };
20
10
 
21
11
  type Props = {
22
12
  report: PageAuditReport;
23
13
  };
24
14
 
25
- function activeTab( page: ActivePage ): TabId {
26
- if ( page === 'overview' ) {
27
- return 'overview';
28
- }
29
-
30
- if ( page === 'issues' ) {
31
- return 'issues';
32
- }
33
-
34
- return page.backTo;
35
- }
36
-
37
- function isAllAuditsPage( page: ActivePage ): page is { backTo: TabId; expand?: AuditStatusGroup; page: 'all-audits' } {
38
- return typeof page === 'object' && 'page' in page && page.page === 'all-audits';
39
- }
40
-
41
- function isCategoryPage( page: ActivePage ): page is { backTo: TabId; category: AuditCategory } {
15
+ function isCategoryPage( page: ActivePage ): page is { category: AuditCategory } {
42
16
  return typeof page === 'object' && 'category' in page;
43
17
  }
44
18
 
45
19
  export default function ReportShell( { report }: Props ) {
46
20
  const [ activePage, setActivePage ] = useState< ActivePage >( 'overview' );
47
21
 
48
- const currentTab = activeTab( activePage );
49
-
50
- const handleTabChange = ( _: React.SyntheticEvent, value: TabId ) => {
51
- setActivePage( value );
52
- };
53
-
54
- const openCategory = ( category: AuditCategory, backTo: TabId ) => {
55
- setActivePage( { category, backTo } );
56
- };
57
-
58
- const openAllAudits = ( expand?: AuditStatusGroup, backTo: TabId = 'issues' ) => {
59
- setActivePage( { page: 'all-audits', expand, backTo } );
60
- };
61
-
62
- const backFromSubPage = () => {
63
- if ( isAllAuditsPage( activePage ) || isCategoryPage( activePage ) ) {
64
- setActivePage( activePage.backTo );
65
- }
66
- };
22
+ const openCategory = ( category: AuditCategory ) => setActivePage( { category } );
23
+ const backToOverview = () => setActivePage( 'overview' );
67
24
 
68
25
  return (
69
- <Box sx={ { display: 'flex', flexDirection: 'column', flex: 1, minHeight: 0 } }>
70
- <Box sx={ { flexShrink: 0 } }>
71
- <Tabs
72
- aria-label={ __( 'Audit navigation', 'elementor' ) }
73
- value={ currentTab }
74
- onChange={ handleTabChange }
75
- textColor="secondary"
76
- indicatorColor="secondary"
77
- size="small"
78
- centered={ true }
79
- variant="fullWidth"
80
- >
81
- <Tab value="overview" label={ __( 'Overview', 'elementor' ) } />
82
- <Tab value="issues" label={ __( 'Issues', 'elementor' ) } />
83
- </Tabs>
84
- <Divider />
85
- </Box>
86
- <Box sx={ { flex: 1, minHeight: 0, overflowY: 'auto' } }>
87
- { activePage === 'overview' && (
88
- <OverviewPage
89
- report={ report }
90
- onCategoryClick={ ( category ) => openCategory( category, 'overview' ) }
91
- />
92
- ) }
93
- { activePage === 'issues' && (
94
- <IssuesPage
95
- report={ report }
96
- onCategoryClick={ ( category ) => openCategory( category, 'issues' ) }
97
- onAllAuditsClick={ () => openAllAudits() }
98
- onStatusClick={ ( status ) => openAllAudits( status ) }
99
- />
100
- ) }
101
- { isAllAuditsPage( activePage ) && (
102
- <AllAuditsPage
103
- report={ report }
104
- initialExpandedStatus={ activePage.expand }
105
- onBack={ backFromSubPage }
106
- />
107
- ) }
108
- { isCategoryPage( activePage ) && (
109
- <CategoryPage category={ activePage.category } report={ report } onBack={ backFromSubPage } />
110
- ) }
111
- </Box>
26
+ <Box sx={ { flex: 1, minHeight: 0, overflowY: 'auto' } }>
27
+ { activePage === 'overview' && <OverviewPage report={ report } onCategoryClick={ openCategory } /> }
28
+ { isCategoryPage( activePage ) && (
29
+ <CategoryPage category={ activePage.category } report={ report } onBack={ backToOverview } />
30
+ ) }
112
31
  </Box>
113
32
  );
114
33
  }