@elementor/editor-audits 4.4.0-1122 → 4.4.0-1124

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elementor/editor-audits",
3
- "version": "4.4.0-1122",
3
+ "version": "4.4.0-1124",
4
4
  "private": false,
5
5
  "author": "Elementor Team",
6
6
  "homepage": "https://elementor.com/",
@@ -39,21 +39,21 @@
39
39
  "dev": "tsup --config=../../tsup.dev.ts"
40
40
  },
41
41
  "dependencies": {
42
- "@elementor/editor": "4.4.0-1122",
43
- "@elementor/editor-mcp": "4.4.0-1122",
44
- "@elementor/editor-app-bar": "4.4.0-1122",
45
- "@elementor/editor-elements": "4.4.0-1122",
46
- "@elementor/editor-floating-panels": "4.4.0-1122",
47
- "@elementor/editor-notifications": "4.4.0-1122",
48
- "@elementor/editor-props": "4.4.0-1122",
49
- "@elementor/editor-styles": "4.4.0-1122",
50
- "@elementor/editor-v1-adapters": "4.4.0-1122",
51
- "@elementor/events": "4.4.0-1122",
52
- "@elementor/http-client": "4.4.0-1122",
42
+ "@elementor/editor": "4.4.0-1124",
43
+ "@elementor/editor-mcp": "4.4.0-1124",
44
+ "@elementor/editor-app-bar": "4.4.0-1124",
45
+ "@elementor/editor-elements": "4.4.0-1124",
46
+ "@elementor/editor-floating-panels": "4.4.0-1124",
47
+ "@elementor/editor-notifications": "4.4.0-1124",
48
+ "@elementor/editor-props": "4.4.0-1124",
49
+ "@elementor/editor-styles": "4.4.0-1124",
50
+ "@elementor/editor-v1-adapters": "4.4.0-1124",
51
+ "@elementor/events": "4.4.0-1124",
52
+ "@elementor/http-client": "4.4.0-1124",
53
53
  "@elementor/icons": "~1.77.0",
54
- "@elementor/locations": "4.4.0-1122",
55
- "@elementor/session": "4.4.0-1122",
56
- "@elementor/store": "4.4.0-1122",
54
+ "@elementor/locations": "4.4.0-1124",
55
+ "@elementor/session": "4.4.0-1124",
56
+ "@elementor/store": "4.4.0-1124",
57
57
  "@elementor/ui": "1.37.5"
58
58
  },
59
59
  "peerDependencies": {
@@ -17,6 +17,7 @@ export const audit: Audit = {
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;
@@ -35,7 +36,7 @@ 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,
39
40
  ctaLabel: __( 'Fix with Ally', 'elementor' ),
40
41
  } );
41
42
  }
@@ -18,6 +18,7 @@ export const audit: Audit = {
18
18
  severity: 'info',
19
19
  weight: 1,
20
20
  evaluate: ( ctx ) => {
21
+ const isReady = ctx.pageContext.image_optimization_plugin_active;
21
22
  const offendingElementIds = new Set< string >();
22
23
  let hasAnyImage = false;
23
24
  let inefficientImageFormatCount = 0;
@@ -57,7 +58,9 @@ export const audit: Audit = {
57
58
  elementId,
58
59
  targetHint: 'element-settings' as const,
59
60
  label: __( "Image isn't served in a modern format like WebP or AVIF.", 'elementor' ),
60
- externalUrl: ctx.pageContext.image_optimization_plugin_url,
61
+ externalUrl: isReady
62
+ ? ctx.pageContext.image_optimization_settings_url
63
+ : ctx.pageContext.image_optimization_plugin_url,
61
64
  ctaLabel: __( 'Optimize all', 'elementor' ),
62
65
  } ) );
63
66
 
@@ -19,6 +19,7 @@ export const audit: Audit = {
19
19
  severity: 'warning',
20
20
  weight: 7,
21
21
  evaluate: ( ctx ) => {
22
+ const isReady = ctx.pageContext.image_optimization_plugin_active;
22
23
  const widgetMaxKb = new Map< string, number >();
23
24
  let oversizedImageCount = 0;
24
25
  let hasAnyImage = false;
@@ -65,7 +66,9 @@ export const audit: Audit = {
65
66
  __( 'Image is %d KB (over 500 KB).', 'elementor' ),
66
67
  kb
67
68
  ),
68
- externalUrl: ctx.pageContext.image_optimization_plugin_url,
69
+ externalUrl: isReady
70
+ ? ctx.pageContext.image_optimization_settings_url
71
+ : ctx.pageContext.image_optimization_plugin_url,
69
72
  ctaLabel: __( 'Optimize all', 'elementor' ),
70
73
  } ) );
71
74
 
@@ -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,7 +1,7 @@
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';
@@ -9,9 +9,10 @@ import { ANGIE_FIX_ENTRY_POINT, CREATE_WIDGET_EVENT } from '../constants';
9
9
  type Props = {
10
10
  prompt: string;
11
11
  panelZIndex: number;
12
+ variant?: 'icon' | 'button';
12
13
  };
13
14
 
14
- export default function FixViolationWithAngie( { prompt, panelZIndex }: Props ) {
15
+ export default function FixViolationWithAngie( { prompt, panelZIndex, variant = 'icon' }: Props ) {
15
16
  const label = __( 'Fix with Angie', 'elementor' );
16
17
  const href = `#angie-prompt=${ encodeURIComponent( prompt ) }`;
17
18
 
@@ -34,6 +35,22 @@ export default function FixViolationWithAngie( { prompt, panelZIndex }: Props )
34
35
  );
35
36
  };
36
37
 
38
+ if ( variant === 'button' ) {
39
+ return (
40
+ <Button
41
+ component="a"
42
+ href={ href }
43
+ size="small"
44
+ color="secondary"
45
+ variant="outlined"
46
+ startIcon={ <AngieIcon fontSize="tiny" aria-hidden={ true } /> }
47
+ onClick={ handleClick }
48
+ >
49
+ { label }
50
+ </Button>
51
+ );
52
+ }
53
+
37
54
  return (
38
55
  <Tooltip title={ label } placement="top" PopperProps={ { sx: { zIndex: panelZIndex } } }>
39
56
  <IconButton component="a" href={ href } size="small" aria-label={ label } onClick={ handleClick }>
@@ -20,7 +20,11 @@ type Props = {
20
20
  severity: AuditSeverity;
21
21
  };
22
22
 
23
+ const INFO_ICON_VERTICAL_NUDGE = 'translateY(-2px)';
24
+
23
25
  export default function SeverityIcon( { severity }: Props ) {
24
26
  const { Icon, color } = SEVERITY_CONFIG[ severity ];
25
- return <Icon fontSize="small" color={ color } />;
27
+ const sx = 'info' === severity ? { transform: INFO_ICON_VERTICAL_NUDGE } : undefined;
28
+
29
+ return <Icon fontSize="small" color={ color } sx={ sx } />;
26
30
  }
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import { getElementIcon, getElementTitle } from '@elementor/editor-elements';
3
3
  import { useFloatingPanelZIndex } from '@elementor/editor-floating-panels';
4
- import { CheckIcon, ChevronDownIcon, HelpIcon, InfoCircleIcon, WandIcon } from '@elementor/icons';
4
+ import { CheckIcon, ChevronDownIcon, HelpIcon, InfoCircleIcon } from '@elementor/icons';
5
5
  import { Alert, Box, Collapse, IconButton, Tooltip, Typography } from '@elementor/ui';
6
6
  import { __ } from '@wordpress/i18n';
7
7
 
@@ -51,35 +51,67 @@ function StatusIndicator( { audit, violations }: Pick< Props, 'audit' | 'violati
51
51
  return <CheckIcon fontSize="small" color="success" />;
52
52
  }
53
53
 
54
- function GuidanceAction( { violation }: { violation: AuditViolation } ) {
54
+ function GuidanceAction( {
55
+ violation,
56
+ sharedAngiePrompt,
57
+ panelZIndex,
58
+ }: {
59
+ violation: AuditViolation;
60
+ sharedAngiePrompt?: string;
61
+ panelZIndex: number;
62
+ } ) {
55
63
  const hasPrimaryCta = !! ( violation.ctaLabel && violation.externalUrl );
56
64
 
57
- if ( ! hasPrimaryCta ) {
65
+ if ( ! hasPrimaryCta && ! sharedAngiePrompt ) {
58
66
  return null;
59
67
  }
60
68
 
61
69
  return (
62
70
  <Box display="flex" alignItems="center" gap={ 1 } mt={ 1 }>
63
- <ViolationCtaButton
64
- ctaLabel={ violation.ctaLabel as string }
65
- externalUrl={ violation.externalUrl as string }
66
- withIcon
67
- />
68
- { violation.secondaryCtaLabel && violation.secondaryCtaUrl && (
71
+ { hasPrimaryCta && (
72
+ <ViolationCtaButton
73
+ ctaLabel={ violation.ctaLabel as string }
74
+ externalUrl={ violation.externalUrl as string }
75
+ withIcon
76
+ />
77
+ ) }
78
+ { hasPrimaryCta && violation.secondaryCtaLabel && violation.secondaryCtaUrl && (
69
79
  <ViolationCtaButton
70
80
  ctaLabel={ violation.secondaryCtaLabel }
71
81
  externalUrl={ violation.secondaryCtaUrl }
72
82
  variant="text"
73
83
  />
74
84
  ) }
85
+ { sharedAngiePrompt && (
86
+ <FixViolationWithAngie prompt={ sharedAngiePrompt } panelZIndex={ panelZIndex } variant="button" />
87
+ ) }
75
88
  </Box>
76
89
  );
77
90
  }
78
91
 
92
+ function getSharedAngiePrompt( violations?: AuditViolation[] ): string | undefined {
93
+ if ( ! violations || violations.length < 2 ) {
94
+ return undefined;
95
+ }
96
+
97
+ const [ first, ...rest ] = violations;
98
+
99
+ if ( ! first.angieFix || ! first.angiePrompt ) {
100
+ return undefined;
101
+ }
102
+
103
+ const allShareTheSameFix = rest.every(
104
+ ( violation ) => violation.angieFix && violation.angiePrompt === first.angiePrompt
105
+ );
106
+
107
+ return allShareTheSameFix ? first.angiePrompt : undefined;
108
+ }
109
+
79
110
  export default function ViolationRow( { audit, expanded, onToggleExpand, skipReason, violations }: Props ) {
80
111
  const primaryViolation = violations?.[ 0 ];
81
112
  const isGuidanceFocusable = !! primaryViolation;
82
113
  const panelZIndex = useFloatingPanelZIndex( AUDIT_PANEL_ID );
114
+ const sharedAngiePrompt = getSharedAngiePrompt( violations );
83
115
 
84
116
  const handleGuidanceClick = () => {
85
117
  if ( isGuidanceFocusable && primaryViolation ) {
@@ -136,7 +168,13 @@ export default function ViolationRow( { audit, expanded, onToggleExpand, skipRea
136
168
  <Typography variant="caption" component="p" color="text.secondary">
137
169
  { audit.description }
138
170
  </Typography>
139
- { primaryViolation && <GuidanceAction violation={ primaryViolation } /> }
171
+ { primaryViolation && (
172
+ <GuidanceAction
173
+ violation={ primaryViolation }
174
+ sharedAngiePrompt={ sharedAngiePrompt }
175
+ panelZIndex={ panelZIndex }
176
+ />
177
+ ) }
140
178
  </Alert>
141
179
  </Tooltip>
142
180
  </Box>
@@ -145,9 +183,11 @@ export default function ViolationRow( { audit, expanded, onToggleExpand, skipRea
145
183
  { violations.map( ( violation, idx ) => {
146
184
  const widgetIcon = violation.elementId ? getElementIcon( violation.elementId ) : null;
147
185
  const elementTitle = violation.elementId ? getElementTitle( violation.elementId ) : null;
148
- const rowLabel = elementTitle
186
+ const displayLabel = elementTitle ?? violation.label;
187
+ const angiePromptContext = elementTitle
149
188
  ? `${ elementTitle } - ${ violation.label }`
150
189
  : violation.label;
190
+ const showsOwnFixAction = !! violation.angieFix && ! sharedAngiePrompt;
151
191
 
152
192
  return (
153
193
  <Box
@@ -165,24 +205,33 @@ export default function ViolationRow( { audit, expanded, onToggleExpand, skipRea
165
205
  borderRadius: 1,
166
206
  cursor: 'pointer',
167
207
  '&:hover': { bgcolor: 'action.hover' },
208
+ '&:hover .violation-row-fix-action, &:focus-visible .violation-row-fix-action':
209
+ {
210
+ opacity: 1,
211
+ },
168
212
  } }
169
213
  >
170
214
  <ViolationIcon violation={ violation } widgetIcon={ widgetIcon } />
171
215
  <Box sx={ { flex: 1 } }>
172
- <Typography variant="caption">{ rowLabel }</Typography>
216
+ <Typography variant="caption">{ displayLabel }</Typography>
173
217
  { violation.detail && (
174
218
  <Typography variant="caption" color="text.secondary">
175
219
  { violation.detail }
176
220
  </Typography>
177
221
  ) }
178
222
  </Box>
179
- { violation.angieFix ? (
180
- <FixViolationWithAngie
181
- prompt={ violation.angiePrompt ?? buildAngiePrompt( rowLabel ) }
182
- panelZIndex={ panelZIndex }
183
- />
184
- ) : (
185
- <WandIcon fontSize="tiny" aria-hidden={ true } />
223
+ { showsOwnFixAction && (
224
+ <Box
225
+ className="violation-row-fix-action"
226
+ sx={ { opacity: 0, transition: 'opacity .15s' } }
227
+ >
228
+ <FixViolationWithAngie
229
+ prompt={
230
+ violation.angiePrompt ?? buildAngiePrompt( angiePromptContext )
231
+ }
232
+ panelZIndex={ panelZIndex }
233
+ />
234
+ </Box>
186
235
  ) }
187
236
  </Box>
188
237
  );
package/src/types.ts CHANGED
@@ -72,6 +72,7 @@ export type PageContextResponse = {
72
72
  ally_accessibility_statement_created: boolean;
73
73
  ally_accessibility_statement_url: string;
74
74
  ally_widget_settings_url: string;
75
+ ally_scan_url: string;
75
76
  cookiez_plugin_active: boolean;
76
77
  cookiez_plugin_url: string;
77
78
  cookiez_plugin_installed: boolean;
@@ -80,6 +81,7 @@ export type PageContextResponse = {
80
81
  cookiez_consent_mode_settings_url: string;
81
82
  image_optimization_plugin_active: boolean;
82
83
  image_optimization_plugin_url: string;
84
+ image_optimization_settings_url: string;
83
85
  frontend_url: string | null;
84
86
  site_identity: {
85
87
  site_name_set: boolean;