@elementor/editor-audits 4.3.3 → 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 +11 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.js +1103 -1146
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1075 -1108
- package/dist/index.mjs.map +1 -1
- package/package.json +17 -15
- package/src/api/page-context-client.ts +8 -5
- package/src/audits/accessibility-statement.ts +39 -0
- package/src/audits/accessibility-widget.ts +41 -0
- package/src/audits/cookie-policy.ts +1 -0
- package/src/audits/google-consent-mode.ts +4 -7
- package/src/audits/images-alt-text.ts +6 -4
- package/src/audits/images-inefficient-format.ts +66 -0
- package/src/audits/images-too-large.ts +23 -10
- 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 +11 -2
- package/src/components/violation-icons.tsx +2 -2
- package/src/components/violation-row.tsx +120 -62
- 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 +6 -2
- package/src/runner.ts +15 -7
- 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 +11 -1
- package/src/utils/all-image-sources.ts +17 -0
- package/src/utils/alt-text-image-sources.ts +24 -0
- package/src/utils/atomic-background-image-sources.ts +56 -0
- package/src/utils/atomic-image-sources.ts +66 -0
- package/src/utils/audit-status-summary.ts +0 -57
- package/src/utils/background-image-sources.ts +32 -0
- package/src/utils/image-alt.ts +3 -1
- package/src/utils/image-like-sources.ts +5 -14
- package/src/utils/image-size-key.ts +8 -0
- package/src/utils/page-attachments.ts +7 -6
- package/src/utils/scan-accessibility-widgets.ts +7 -0
- 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,20 +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-
|
|
49
|
-
"@elementor/
|
|
50
|
-
"@elementor/
|
|
51
|
-
"@elementor/
|
|
52
|
-
"@elementor/
|
|
53
|
-
"@elementor/
|
|
54
|
-
"@elementor/
|
|
55
|
-
"@elementor/
|
|
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"
|
|
56
58
|
},
|
|
57
59
|
"peerDependencies": {
|
|
58
60
|
"@wordpress/i18n": "^5.13.0",
|
|
@@ -4,13 +4,16 @@ import { type PageContextResponse } from '../types';
|
|
|
4
4
|
import { isNonceInvalidError, refreshAuditsNonce } from '../utils/session-expiration';
|
|
5
5
|
import { getWindowConfig } from '../utils/window-config';
|
|
6
6
|
|
|
7
|
-
export async function fetchPageContext(
|
|
8
|
-
|
|
7
|
+
export async function fetchPageContext(
|
|
8
|
+
documentId: number,
|
|
9
|
+
imageSizeRequests: string[]
|
|
10
|
+
): Promise< PageContextResponse > {
|
|
11
|
+
return requestPageContext( documentId, imageSizeRequests, true );
|
|
9
12
|
}
|
|
10
13
|
|
|
11
14
|
async function requestPageContext(
|
|
12
15
|
documentId: number,
|
|
13
|
-
|
|
16
|
+
imageSizeRequests: string[],
|
|
14
17
|
allowNonceRetry: boolean
|
|
15
18
|
): Promise< PageContextResponse > {
|
|
16
19
|
const { restNamespace, nonce } = getWindowConfig();
|
|
@@ -20,7 +23,7 @@ async function requestPageContext(
|
|
|
20
23
|
const response = await httpService().get< PageContextResponse >( url, {
|
|
21
24
|
params: {
|
|
22
25
|
document_id: documentId,
|
|
23
|
-
|
|
26
|
+
image_size_requests: imageSizeRequests,
|
|
24
27
|
},
|
|
25
28
|
headers: { 'X-WP-Nonce': nonce },
|
|
26
29
|
} );
|
|
@@ -33,6 +36,6 @@ async function requestPageContext(
|
|
|
33
36
|
|
|
34
37
|
await refreshAuditsNonce();
|
|
35
38
|
|
|
36
|
-
return requestPageContext( documentId,
|
|
39
|
+
return requestPageContext( documentId, imageSizeRequests, false );
|
|
37
40
|
}
|
|
38
41
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { __ } from '@wordpress/i18n';
|
|
2
|
+
|
|
3
|
+
import { type Audit } from '../types';
|
|
4
|
+
|
|
5
|
+
export const audit: Audit = {
|
|
6
|
+
id: 'audits/accessibility-statement',
|
|
7
|
+
title: __( 'Create an accessibility statement', 'elementor' ),
|
|
8
|
+
description: __(
|
|
9
|
+
'An accessibility statement showcases your efforts to create an inclusive online space, highlighting helpful features and a commitment to accessibility.',
|
|
10
|
+
'elementor'
|
|
11
|
+
),
|
|
12
|
+
fixHint: __( 'Use Ally to create and publish an accessibility statement for your site.', 'elementor' ),
|
|
13
|
+
categories: [ 'accessibility' ],
|
|
14
|
+
severity: 'info',
|
|
15
|
+
weight: 1,
|
|
16
|
+
evaluate: ( ctx ) => {
|
|
17
|
+
const isReady = ctx.pageContext.ally_plugin_active;
|
|
18
|
+
|
|
19
|
+
if ( isReady && ctx.pageContext.ally_accessibility_statement_created ) {
|
|
20
|
+
return { status: 'pass' };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
status: 'fail',
|
|
25
|
+
violations: [
|
|
26
|
+
{
|
|
27
|
+
auditId: audit.id,
|
|
28
|
+
label: __( 'No accessibility statement has been created for this site.', 'elementor' ),
|
|
29
|
+
externalUrl: isReady
|
|
30
|
+
? ctx.pageContext.ally_accessibility_statement_url
|
|
31
|
+
: ctx.pageContext.ally_plugin_url,
|
|
32
|
+
ctaLabel: __( 'Create', 'elementor' ),
|
|
33
|
+
secondaryCtaLabel: __( 'Learn more', 'elementor' ),
|
|
34
|
+
secondaryCtaUrl: 'https://elementor.com/help/start-using-ally/#statement',
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
};
|
|
38
|
+
},
|
|
39
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { __ } from '@wordpress/i18n';
|
|
2
|
+
|
|
3
|
+
import { type Audit } from '../types';
|
|
4
|
+
import { hasAccessibilityWidgetScript } from '../utils/scan-accessibility-widgets';
|
|
5
|
+
|
|
6
|
+
export const audit: Audit = {
|
|
7
|
+
id: 'audits/accessibility-widget',
|
|
8
|
+
title: __( 'Add an accessibility widget', 'elementor' ),
|
|
9
|
+
description: __( 'Make your site more inclusive by adding an accessibility widget.', 'elementor' ),
|
|
10
|
+
fixHint: __( 'Activate Ally’s accessibility widget to give visitors on-page accessibility tools.', 'elementor' ),
|
|
11
|
+
categories: [ 'accessibility' ],
|
|
12
|
+
severity: 'info',
|
|
13
|
+
weight: 1,
|
|
14
|
+
evaluate: ( ctx ) => {
|
|
15
|
+
if ( ! ctx.pageContext.frontend_url ) {
|
|
16
|
+
return { status: 'skipped', reason: __( 'Page is not published.', 'elementor' ) };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if ( ! ctx.renderedHtml ) {
|
|
20
|
+
return { status: 'skipped', reason: __( 'Could not fetch the published page.', 'elementor' ) };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if ( hasAccessibilityWidgetScript( ctx.renderedHtml ) ) {
|
|
24
|
+
return { status: 'pass' };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const isReady = ctx.pageContext.ally_plugin_active;
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
status: 'fail',
|
|
31
|
+
violations: [
|
|
32
|
+
{
|
|
33
|
+
auditId: audit.id,
|
|
34
|
+
label: __( 'No accessibility widget is active on this site.', 'elementor' ),
|
|
35
|
+
externalUrl: isReady ? ctx.pageContext.ally_widget_settings_url : ctx.pageContext.ally_plugin_url,
|
|
36
|
+
ctaLabel: __( 'Activate', 'elementor' ),
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
};
|
|
40
|
+
},
|
|
41
|
+
};
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { __ } from '@wordpress/i18n';
|
|
2
2
|
|
|
3
3
|
import { type Audit } from '../types';
|
|
4
|
-
import { fetchRenderedHtml } from '../utils/fetch-rendered-html';
|
|
5
4
|
import { hasConsentDefaultCall, hasGoogleTracking } from '../utils/scan-consent-signals';
|
|
6
5
|
|
|
7
6
|
export const audit: Audit = {
|
|
@@ -18,22 +17,20 @@ export const audit: Audit = {
|
|
|
18
17
|
categories: [ 'compliance' ],
|
|
19
18
|
severity: 'warning',
|
|
20
19
|
weight: 1,
|
|
21
|
-
evaluate:
|
|
20
|
+
evaluate: ( ctx ) => {
|
|
22
21
|
if ( ! ctx.pageContext.frontend_url ) {
|
|
23
22
|
return { status: 'skipped', reason: __( 'Page is not published.', 'elementor' ) };
|
|
24
23
|
}
|
|
25
24
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
if ( ! html ) {
|
|
25
|
+
if ( ! ctx.renderedHtml ) {
|
|
29
26
|
return { status: 'skipped', reason: __( 'Could not fetch the published page.', 'elementor' ) };
|
|
30
27
|
}
|
|
31
28
|
|
|
32
|
-
if ( ! hasGoogleTracking(
|
|
29
|
+
if ( ! hasGoogleTracking( ctx.renderedHtml ) ) {
|
|
33
30
|
return { status: 'skipped', reason: __( 'No Google tracking product detected.', 'elementor' ) };
|
|
34
31
|
}
|
|
35
32
|
|
|
36
|
-
if ( hasConsentDefaultCall(
|
|
33
|
+
if ( hasConsentDefaultCall( ctx.renderedHtml ) ) {
|
|
37
34
|
return { status: 'pass' };
|
|
38
35
|
}
|
|
39
36
|
|
|
@@ -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
|
} );
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { __ } from '@wordpress/i18n';
|
|
2
|
+
|
|
3
|
+
import { type Audit, type AuditViolation, type ElementSnapshotNode } from '../types';
|
|
4
|
+
import { walkAllImageSources } from '../utils/all-image-sources';
|
|
5
|
+
import { type ImageLikeMedia } from '../utils/image-like-sources';
|
|
6
|
+
import { buildImageSizeKey } from '../utils/image-size-key';
|
|
7
|
+
|
|
8
|
+
const LEGACY_RASTER_MIME_TYPES = new Set( [ 'image/jpeg', 'image/png', 'image/gif', 'image/bmp', 'image/tiff' ] );
|
|
9
|
+
|
|
10
|
+
export const audit: Audit = {
|
|
11
|
+
id: 'audits/images-inefficient-format',
|
|
12
|
+
title: __( 'Inefficient image format', 'elementor' ),
|
|
13
|
+
description: __( "Image isn't served in a modern, efficient format like WebP or AVIF.", 'elementor' ),
|
|
14
|
+
fixHint: __( 'Convert the image to a modern format like WebP or AVIF, or enable image optimization.', 'elementor' ),
|
|
15
|
+
categories: [ 'performance' ],
|
|
16
|
+
severity: 'info',
|
|
17
|
+
weight: 1,
|
|
18
|
+
evaluate: ( ctx ) => {
|
|
19
|
+
const isReady = ctx.pageContext.image_optimization_plugin_active;
|
|
20
|
+
const offendingElementIds = new Set< string >();
|
|
21
|
+
let hasAnyImage = false;
|
|
22
|
+
let inefficientImageFormatCount = 0;
|
|
23
|
+
|
|
24
|
+
const evaluateSource = ( node: ElementSnapshotNode, media: ImageLikeMedia ) => {
|
|
25
|
+
if ( media.id || media.url ) {
|
|
26
|
+
hasAnyImage = true;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const id = media.id;
|
|
30
|
+
|
|
31
|
+
if ( ! id ) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const mime = ctx.pageContext.image_sizes[ buildImageSizeKey( { id, size: media.size } ) ]?.mime;
|
|
36
|
+
|
|
37
|
+
if ( ! mime || ! LEGACY_RASTER_MIME_TYPES.has( mime ) ) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
inefficientImageFormatCount++;
|
|
42
|
+
offendingElementIds.add( node.id );
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
walkAllImageSources( ctx.elements.tree, ( { node, media } ) => evaluateSource( node, media ) );
|
|
46
|
+
|
|
47
|
+
if ( ! hasAnyImage ) {
|
|
48
|
+
return { status: 'skipped', reason: __( 'No images', 'elementor' ) };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const violations: AuditViolation[] = Array.from( offendingElementIds ).map( ( elementId ) => ( {
|
|
52
|
+
auditId: audit.id,
|
|
53
|
+
elementId,
|
|
54
|
+
targetHint: 'element-settings' as const,
|
|
55
|
+
label: __( "Image isn't served in a modern format like WebP or AVIF.", 'elementor' ),
|
|
56
|
+
externalUrl: isReady
|
|
57
|
+
? ctx.pageContext.image_optimization_settings_url
|
|
58
|
+
: ctx.pageContext.image_optimization_plugin_url,
|
|
59
|
+
ctaLabel: __( 'Optimize all', 'elementor' ),
|
|
60
|
+
} ) );
|
|
61
|
+
|
|
62
|
+
return violations.length === 0
|
|
63
|
+
? { status: 'pass' }
|
|
64
|
+
: { status: 'fail', violations, metadata: { inefficientImageFormatCount } };
|
|
65
|
+
},
|
|
66
|
+
};
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { __, sprintf } from '@wordpress/i18n';
|
|
2
2
|
|
|
3
|
-
import { type Audit, type AuditViolation } from '../types';
|
|
4
|
-
import {
|
|
3
|
+
import { type Audit, type AuditViolation, type ElementSnapshotNode } from '../types';
|
|
4
|
+
import { walkAllImageSources } from '../utils/all-image-sources';
|
|
5
|
+
import { type ImageLikeMedia } from '../utils/image-like-sources';
|
|
6
|
+
import { buildImageSizeKey } from '../utils/image-size-key';
|
|
5
7
|
|
|
6
8
|
const SIZE_THRESHOLD_BYTES = 500 * 1024;
|
|
7
9
|
const BYTES_PER_KB = 1024;
|
|
@@ -15,21 +17,23 @@ export const audit: Audit = {
|
|
|
15
17
|
severity: 'warning',
|
|
16
18
|
weight: 7,
|
|
17
19
|
evaluate: ( ctx ) => {
|
|
18
|
-
|
|
19
|
-
return { status: 'skipped', reason: __( 'No images', 'elementor' ) };
|
|
20
|
-
}
|
|
21
|
-
|
|
20
|
+
const isReady = ctx.pageContext.image_optimization_plugin_active;
|
|
22
21
|
const widgetMaxKb = new Map< string, number >();
|
|
23
22
|
let oversizedImageCount = 0;
|
|
23
|
+
let hasAnyImage = false;
|
|
24
|
+
|
|
25
|
+
const evaluateSource = ( node: ElementSnapshotNode, media: ImageLikeMedia ) => {
|
|
26
|
+
if ( media.id || media.url ) {
|
|
27
|
+
hasAnyImage = true;
|
|
28
|
+
}
|
|
24
29
|
|
|
25
|
-
walkImageLikeSources( ctx.elements.tree, ( { node, media } ) => {
|
|
26
30
|
const id = media.id;
|
|
27
31
|
|
|
28
32
|
if ( ! id ) {
|
|
29
33
|
return;
|
|
30
34
|
}
|
|
31
35
|
|
|
32
|
-
const size = ctx.pageContext.image_sizes[ id ];
|
|
36
|
+
const size = ctx.pageContext.image_sizes[ buildImageSizeKey( { id, size: media.size } ) ];
|
|
33
37
|
|
|
34
38
|
if ( ! size || size.filesize_bytes <= SIZE_THRESHOLD_BYTES ) {
|
|
35
39
|
return;
|
|
@@ -40,7 +44,13 @@ export const audit: Audit = {
|
|
|
40
44
|
const kb = Math.round( size.filesize_bytes / BYTES_PER_KB );
|
|
41
45
|
const currentMax = widgetMaxKb.get( node.id ) ?? 0;
|
|
42
46
|
widgetMaxKb.set( node.id, Math.max( currentMax, kb ) );
|
|
43
|
-
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
walkAllImageSources( ctx.elements.tree, ( { node, media } ) => evaluateSource( node, media ) );
|
|
50
|
+
|
|
51
|
+
if ( ! hasAnyImage ) {
|
|
52
|
+
return { status: 'skipped', reason: __( 'No images', 'elementor' ) };
|
|
53
|
+
}
|
|
44
54
|
|
|
45
55
|
const violations: AuditViolation[] = Array.from( widgetMaxKb.entries() ).map( ( [ elementId, kb ] ) => ( {
|
|
46
56
|
auditId: audit.id,
|
|
@@ -51,7 +61,10 @@ export const audit: Audit = {
|
|
|
51
61
|
__( 'Image is %d KB (over 500 KB).', 'elementor' ),
|
|
52
62
|
kb
|
|
53
63
|
),
|
|
54
|
-
externalUrl:
|
|
64
|
+
externalUrl: isReady
|
|
65
|
+
? ctx.pageContext.image_optimization_settings_url
|
|
66
|
+
: ctx.pageContext.image_optimization_plugin_url,
|
|
67
|
+
ctaLabel: __( 'Optimize all', 'elementor' ),
|
|
55
68
|
} ) );
|
|
56
69
|
|
|
57
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>
|