@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.
- package/dist/index.d.mts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +789 -1098
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +731 -1040
- package/dist/index.mjs.map +1 -1
- package/package.json +17 -17
- package/src/audits/accessibility-statement.ts +2 -2
- package/src/audits/cookie-policy.ts +1 -0
- package/src/audits/images-alt-text.ts +6 -4
- package/src/audits/images-inefficient-format.ts +8 -9
- package/src/audits/images-too-large.ts +8 -9
- package/src/audits/nested-boxed-containers.ts +5 -0
- package/src/components/audit-feedback.tsx +4 -1
- package/src/components/audit-panel.tsx +13 -13
- package/src/components/audit-toggle-icon.tsx +22 -0
- package/src/components/fix-violation-with-angie.tsx +18 -7
- package/src/components/pages/category-page.tsx +27 -3
- package/src/components/pages/overview-page.tsx +24 -29
- package/src/components/report-shell.tsx +10 -91
- package/src/components/rescan-button.tsx +38 -0
- package/src/components/severity-icons.tsx +5 -1
- package/src/components/severity-summary-cards.tsx +52 -0
- package/src/components/violation-cta-button.tsx +10 -2
- package/src/components/violation-icons.tsx +2 -2
- package/src/components/violation-row.tsx +118 -69
- package/src/hooks/use-audit-report.ts +5 -4
- package/src/hooks/use-audit-toggle-props.ts +2 -2
- package/src/hooks/use-single-expanded-card.ts +11 -0
- package/src/init.ts +2 -0
- package/src/register-audits.ts +0 -2
- package/src/register-promotions.ts +0 -23
- package/src/runner.ts +6 -2
- package/src/store/selectors.ts +4 -0
- package/src/store/slice.ts +10 -1
- package/src/sync-report-staleness.ts +14 -0
- package/src/types.ts +2 -0
- package/src/utils/all-image-sources.ts +17 -0
- package/src/utils/alt-text-image-sources.ts +24 -0
- package/src/utils/atomic-image-sources.ts +8 -4
- package/src/utils/audit-status-summary.ts +0 -57
- package/src/utils/image-like-sources.ts +0 -12
- package/src/utils/page-attachments.ts +3 -11
- package/src/audits/too-many-widgets.ts +0 -53
- package/src/components/audit-statuses-section.tsx +0 -65
- package/src/components/count-summary-circle.tsx +0 -66
- package/src/components/pages/all-audits-page.tsx +0 -65
- package/src/components/pages/issues-page.tsx +0 -65
- package/src/components/score-bar.tsx +0 -56
- package/src/components/score-circle.tsx +0 -60
- package/src/components/severity-filter-chips.tsx +0 -57
- 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.
|
|
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.
|
|
43
|
-
"@elementor/editor-mcp": "4.3.
|
|
44
|
-
"@elementor/editor-app-bar": "4.3.
|
|
45
|
-
"@elementor/editor-elements": "4.3.
|
|
46
|
-
"@elementor/editor-floating-panels": "4.3.
|
|
47
|
-
"@elementor/editor-notifications": "4.3.
|
|
48
|
-
"@elementor/editor-props": "4.3.
|
|
49
|
-
"@elementor/editor-styles": "4.3.
|
|
50
|
-
"@elementor/editor-v1-adapters": "4.3.
|
|
51
|
-
"@elementor/events": "4.3.
|
|
52
|
-
"@elementor/http-client": "4.3.
|
|
53
|
-
"@elementor/icons": "~1.
|
|
54
|
-
"@elementor/locations": "4.3.
|
|
55
|
-
"@elementor/session": "4.3.
|
|
56
|
-
"@elementor/store": "4.3.
|
|
57
|
-
"@elementor/ui": "1.
|
|
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:
|
|
32
|
+
ctaLabel: __( 'Create', 'elementor' ),
|
|
33
33
|
secondaryCtaLabel: __( 'Learn more', 'elementor' ),
|
|
34
|
-
secondaryCtaUrl: 'https://
|
|
34
|
+
secondaryCtaUrl: 'https://elementor.com/help/start-using-ally/#statement',
|
|
35
35
|
},
|
|
36
36
|
],
|
|
37
37
|
};
|
|
@@ -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 ( !
|
|
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
|
-
|
|
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 {
|
|
5
|
-
import {
|
|
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
|
-
|
|
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:
|
|
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 {
|
|
5
|
-
import {
|
|
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
|
-
|
|
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:
|
|
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 {
|
|
2
|
+
import { getHostDocumentId } from '@elementor/editor-elements';
|
|
3
3
|
import { FloatingPanelBody, FloatingPanelFooter, FloatingPanelHeader } from '@elementor/editor-floating-panels';
|
|
4
|
-
import { Box,
|
|
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
|
|
18
|
-
const onRun = () => run(
|
|
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=
|
|
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
|
-
<
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
<RescanButton
|
|
53
|
+
hasScanned={ Boolean( lastScanLabel ) }
|
|
54
|
+
isStale={ isStale }
|
|
55
|
+
disabled={ status === 'loading' || hostDocumentId === 0 }
|
|
53
56
|
onClick={ onRun }
|
|
54
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
<
|
|
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
|
-
|
|
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
|
|
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
|
|
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,
|
|
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 {
|
|
8
|
+
import { countSeverities } from '../../utils/severity-counts';
|
|
9
|
+
import IssuesCategoryRow from '../issues-category-row';
|
|
9
10
|
import Promotions from '../promotions';
|
|
10
|
-
import
|
|
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
|
-
|
|
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
|
|
24
|
-
<
|
|
25
|
-
|
|
26
|
-
<
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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={ {
|
|
70
|
-
<
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
}
|