@elementor/editor-audits 4.3.0-1036
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/CHANGELOG.md +5 -0
- package/dist/index.d.mts +122 -0
- package/dist/index.d.ts +122 -0
- package/dist/index.js +2822 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2801 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +65 -0
- package/src/api/page-context-client.ts +19 -0
- package/src/audits/accessibility-policy.ts +32 -0
- package/src/audits/cookie-policy.ts +32 -0
- package/src/audits/deep-nesting.ts +45 -0
- package/src/audits/default-design-system.ts +32 -0
- package/src/audits/deprecated-widgets.ts +57 -0
- package/src/audits/heading-structure.ts +294 -0
- package/src/audits/hidden-elements.ts +41 -0
- package/src/audits/images-alt-text.ts +47 -0
- package/src/audits/images-too-large.ts +61 -0
- package/src/audits/nested-boxed-containers.ts +44 -0
- package/src/audits/page-excerpt.ts +30 -0
- package/src/audits/page-featured-image.ts +29 -0
- package/src/audits/page-title.ts +48 -0
- package/src/audits/prefer-global-colors.ts +52 -0
- package/src/audits/prefer-global-fonts.ts +50 -0
- package/src/audits/privacy-policy.ts +35 -0
- package/src/audits/robots-noindex.ts +35 -0
- package/src/audits/sections-and-columns.ts +39 -0
- package/src/audits/site-identity.ts +62 -0
- package/src/audits/too-many-widgets.ts +43 -0
- package/src/components/audit-feedback.tsx +178 -0
- package/src/components/audit-panel.tsx +55 -0
- package/src/components/category-icons.ts +17 -0
- package/src/components/count-summary-circle.tsx +66 -0
- package/src/components/fix-violation-with-angie.tsx +50 -0
- package/src/components/issues-category-row.tsx +62 -0
- package/src/components/pages/all-audits-page.tsx +65 -0
- package/src/components/pages/category-page.tsx +50 -0
- package/src/components/pages/error-page.tsx +21 -0
- package/src/components/pages/issues-page.tsx +48 -0
- package/src/components/pages/loading-page.tsx +12 -0
- package/src/components/pages/overview-page.tsx +140 -0
- package/src/components/pages/welcome-page.tsx +48 -0
- package/src/components/promotion-card.tsx +43 -0
- package/src/components/promotions.tsx +69 -0
- package/src/components/report-shell.tsx +110 -0
- package/src/components/score-bar.tsx +56 -0
- package/src/components/score-circle.tsx +60 -0
- package/src/components/severity-icons.tsx +26 -0
- package/src/components/status-section.tsx +47 -0
- package/src/components/subpage-header.tsx +29 -0
- package/src/components/violation-icons.tsx +33 -0
- package/src/components/violation-row.tsx +169 -0
- package/src/constants.ts +35 -0
- package/src/editor-app-bar.tsx +13 -0
- package/src/editor-panel.tsx +19 -0
- package/src/hooks/focus-violation.ts +53 -0
- package/src/hooks/use-audit-report.ts +43 -0
- package/src/hooks/use-audit-toggle-props.ts +17 -0
- package/src/index.ts +14 -0
- package/src/init.ts +14 -0
- package/src/register-audits.ts +56 -0
- package/src/register-promotions.ts +75 -0
- package/src/registry.ts +19 -0
- package/src/runner.ts +44 -0
- package/src/store/index.ts +2 -0
- package/src/store/selectors.ts +15 -0
- package/src/store/slice.ts +42 -0
- package/src/types.ts +112 -0
- package/src/utils/audit-status-summary.ts +112 -0
- package/src/utils/build-angie-prompt.ts +9 -0
- package/src/utils/collect-hardcoded-colors.ts +74 -0
- package/src/utils/collect-hardcoded-fonts.ts +70 -0
- package/src/utils/compute-report.ts +51 -0
- package/src/utils/find-audit-run.ts +5 -0
- package/src/utils/image-alt.ts +19 -0
- package/src/utils/image-like-sources.ts +70 -0
- package/src/utils/keyboard-click.ts +10 -0
- package/src/utils/match-global-by-value.ts +5 -0
- package/src/utils/page-attachments.ts +14 -0
- package/src/utils/read-kit-snapshot.ts +149 -0
- package/src/utils/report-storage.ts +16 -0
- package/src/utils/score-thresholds.ts +23 -0
- package/src/utils/severity-counts.ts +59 -0
- package/src/utils/sort-failed-audits.ts +19 -0
- package/src/utils/v1-snapshot.ts +27 -0
- package/src/utils/walk.ts +17 -0
- package/src/utils/window-config.ts +20 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Box, Chip, type ChipProps, type Theme, Typography } from '@elementor/ui';
|
|
3
|
+
|
|
4
|
+
type Props = {
|
|
5
|
+
ariaLabel: string;
|
|
6
|
+
color: ChipProps[ 'color' ];
|
|
7
|
+
count: number;
|
|
8
|
+
label: string;
|
|
9
|
+
onClick: () => void;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const COUNT_SUMMARY_CIRCLE_SIZE = 64;
|
|
13
|
+
const COUNT_SUMMARY_BORDER_WIDTH = 4;
|
|
14
|
+
|
|
15
|
+
function chipBorderColor( theme: Theme, color: ChipProps[ 'color' ] ): string {
|
|
16
|
+
if ( color === 'default' || ! color ) {
|
|
17
|
+
return theme.palette.text.secondary;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return theme.palette[ color ].main;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default function CountSummaryCircle( { ariaLabel, color, count, label, onClick }: Props ) {
|
|
24
|
+
return (
|
|
25
|
+
<Box
|
|
26
|
+
aria-label={ ariaLabel }
|
|
27
|
+
component="button"
|
|
28
|
+
onClick={ onClick }
|
|
29
|
+
type="button"
|
|
30
|
+
sx={ {
|
|
31
|
+
alignItems: 'center',
|
|
32
|
+
background: 'none',
|
|
33
|
+
border: 'none',
|
|
34
|
+
cursor: 'pointer',
|
|
35
|
+
display: 'flex',
|
|
36
|
+
flex: 1,
|
|
37
|
+
flexDirection: 'column',
|
|
38
|
+
gap: 0.5,
|
|
39
|
+
padding: 0,
|
|
40
|
+
} }
|
|
41
|
+
>
|
|
42
|
+
<Chip
|
|
43
|
+
color={ color }
|
|
44
|
+
label={ count }
|
|
45
|
+
size="small"
|
|
46
|
+
variant="standard"
|
|
47
|
+
sx={ ( theme: Theme ) => ( {
|
|
48
|
+
border: `${ COUNT_SUMMARY_BORDER_WIDTH }px solid ${ chipBorderColor( theme, color ) }`,
|
|
49
|
+
borderRadius: '50%',
|
|
50
|
+
height: COUNT_SUMMARY_CIRCLE_SIZE,
|
|
51
|
+
maxWidth: COUNT_SUMMARY_CIRCLE_SIZE,
|
|
52
|
+
width: COUNT_SUMMARY_CIRCLE_SIZE,
|
|
53
|
+
'& .MuiChip-label': {
|
|
54
|
+
fontSize: theme.typography.body2.fontSize,
|
|
55
|
+
fontWeight: 700,
|
|
56
|
+
lineHeight: 1,
|
|
57
|
+
padding: 0,
|
|
58
|
+
},
|
|
59
|
+
} ) }
|
|
60
|
+
/>
|
|
61
|
+
<Typography color="text.secondary" sx={ { textAlign: 'center' } } variant="caption">
|
|
62
|
+
{ label }
|
|
63
|
+
</Typography>
|
|
64
|
+
</Box>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { isAngieAvailable, sendPromptToAngie } from '@elementor/editor-mcp';
|
|
3
|
+
import { AngieIcon } from '@elementor/icons';
|
|
4
|
+
import { IconButton, Tooltip } from '@elementor/ui';
|
|
5
|
+
import { __ } from '@wordpress/i18n';
|
|
6
|
+
|
|
7
|
+
import { ANGIE_FIX_ENTRY_POINT, CREATE_WIDGET_EVENT } from '../constants';
|
|
8
|
+
|
|
9
|
+
type Props = {
|
|
10
|
+
prompt: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export default function FixViolationWithAngie( { prompt }: Props ) {
|
|
14
|
+
const label = __( 'Fix with Angie', 'elementor' );
|
|
15
|
+
const href = `#angie-prompt=${ encodeURIComponent( prompt ) }`;
|
|
16
|
+
|
|
17
|
+
const handleClick = ( event: React.MouseEvent< HTMLAnchorElement > ) => {
|
|
18
|
+
event.stopPropagation();
|
|
19
|
+
event.preventDefault();
|
|
20
|
+
|
|
21
|
+
if ( isAngieAvailable() ) {
|
|
22
|
+
sendPromptToAngie( prompt );
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
window.dispatchEvent(
|
|
27
|
+
new CustomEvent( CREATE_WIDGET_EVENT, {
|
|
28
|
+
detail: {
|
|
29
|
+
entry_point: ANGIE_FIX_ENTRY_POINT,
|
|
30
|
+
prompt,
|
|
31
|
+
},
|
|
32
|
+
} )
|
|
33
|
+
);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<Tooltip title={ label }>
|
|
38
|
+
<IconButton
|
|
39
|
+
className="violation-hover-icon"
|
|
40
|
+
component="a"
|
|
41
|
+
href={ href }
|
|
42
|
+
size="small"
|
|
43
|
+
aria-label={ label }
|
|
44
|
+
onClick={ handleClick }
|
|
45
|
+
>
|
|
46
|
+
<AngieIcon fontSize="tiny" />
|
|
47
|
+
</IconButton>
|
|
48
|
+
</Tooltip>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { ChevronRightIcon } from '@elementor/icons';
|
|
3
|
+
import { Box, Rotate, Typography, useTheme } from '@elementor/ui';
|
|
4
|
+
|
|
5
|
+
import { type AuditCategory } from '../types';
|
|
6
|
+
import { onKeyboardClick } from '../utils/keyboard-click';
|
|
7
|
+
import { ALL_SEVERITIES, type SeverityCounts } from '../utils/severity-counts';
|
|
8
|
+
import { CATEGORY_ICONS } from './category-icons';
|
|
9
|
+
import SeverityIcon from './severity-icons';
|
|
10
|
+
|
|
11
|
+
type Props = {
|
|
12
|
+
category: AuditCategory;
|
|
13
|
+
label: string;
|
|
14
|
+
counts: SeverityCounts;
|
|
15
|
+
onClick: () => void;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export default function IssuesCategoryRow( { category, label, counts, onClick }: Props ) {
|
|
19
|
+
const isRtl = 'rtl' === useTheme().direction;
|
|
20
|
+
const Icon = CATEGORY_ICONS[ category ];
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<Box
|
|
24
|
+
role="button"
|
|
25
|
+
tabIndex={ 0 }
|
|
26
|
+
onClick={ onClick }
|
|
27
|
+
onKeyDown={ onKeyboardClick( onClick ) }
|
|
28
|
+
sx={ {
|
|
29
|
+
display: 'flex',
|
|
30
|
+
alignItems: 'center',
|
|
31
|
+
gap: 1,
|
|
32
|
+
px: 2,
|
|
33
|
+
py: 1.5,
|
|
34
|
+
cursor: 'pointer',
|
|
35
|
+
borderRadius: 1,
|
|
36
|
+
border: 1,
|
|
37
|
+
borderColor: 'divider',
|
|
38
|
+
'&:hover': { bgcolor: 'action.hover' },
|
|
39
|
+
outline: 'none',
|
|
40
|
+
'&:focus-visible': { outline: '2px solid', outlineColor: 'primary.main' },
|
|
41
|
+
} }
|
|
42
|
+
>
|
|
43
|
+
<Icon fontSize="small" color="action" />
|
|
44
|
+
<Typography variant="body2" fontWeight="bold" sx={ { flex: 1 } }>
|
|
45
|
+
{ label }
|
|
46
|
+
</Typography>
|
|
47
|
+
<Box sx={ { display: 'flex', alignItems: 'center', gap: 0.5 } }>
|
|
48
|
+
{ ALL_SEVERITIES.filter( ( s ) => counts[ s ] > 0 ).map( ( severity ) => (
|
|
49
|
+
<Box key={ severity } sx={ { display: 'flex', alignItems: 'center', gap: 0.25 } }>
|
|
50
|
+
<SeverityIcon severity={ severity } />
|
|
51
|
+
<Typography variant="caption" color="text.primary" fontWeight="bold">
|
|
52
|
+
{ counts[ severity ] }
|
|
53
|
+
</Typography>
|
|
54
|
+
</Box>
|
|
55
|
+
) ) }
|
|
56
|
+
</Box>
|
|
57
|
+
<Rotate in={ isRtl }>
|
|
58
|
+
<ChevronRightIcon fontSize="small" color="action" />
|
|
59
|
+
</Rotate>
|
|
60
|
+
</Box>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Box } from '@elementor/ui';
|
|
3
|
+
import { __ } from '@wordpress/i18n';
|
|
4
|
+
|
|
5
|
+
import { type PageAuditReport } from '../../types';
|
|
6
|
+
import {
|
|
7
|
+
auditStatusColor,
|
|
8
|
+
type AuditStatusGroup,
|
|
9
|
+
auditStatusLabel,
|
|
10
|
+
partitionAuditResults,
|
|
11
|
+
} from '../../utils/audit-status-summary';
|
|
12
|
+
import StatusSection from '../status-section';
|
|
13
|
+
import SubpageHeader from '../subpage-header';
|
|
14
|
+
import ViolationRow from '../violation-row';
|
|
15
|
+
|
|
16
|
+
type Props = {
|
|
17
|
+
initialExpandedStatus?: AuditStatusGroup;
|
|
18
|
+
onBack: () => void;
|
|
19
|
+
report: PageAuditReport;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export default function AllAuditsPage( { initialExpandedStatus, onBack, report }: Props ) {
|
|
23
|
+
const { failed, passed, skipped, totalViolations } = partitionAuditResults( report );
|
|
24
|
+
const expandFail = ! initialExpandedStatus || initialExpandedStatus === 'fail';
|
|
25
|
+
const expandPass = initialExpandedStatus === 'pass';
|
|
26
|
+
const expandSkipped = initialExpandedStatus === 'skipped';
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<Box key={ initialExpandedStatus ?? 'default' }>
|
|
30
|
+
<SubpageHeader title={ __( 'All audits', 'elementor' ) } onBack={ onBack } />
|
|
31
|
+
<Box sx={ { p: 1 } }>
|
|
32
|
+
<StatusSection
|
|
33
|
+
label={ auditStatusLabel( 'fail' ) }
|
|
34
|
+
count={ totalViolations }
|
|
35
|
+
color={ auditStatusColor( 'fail' ) }
|
|
36
|
+
defaultExpanded={ expandFail }
|
|
37
|
+
>
|
|
38
|
+
{ failed.map( ( r ) => (
|
|
39
|
+
<ViolationRow key={ r.audit.id } audit={ r.audit } violations={ r.result.violations } />
|
|
40
|
+
) ) }
|
|
41
|
+
</StatusSection>
|
|
42
|
+
<StatusSection
|
|
43
|
+
label={ auditStatusLabel( 'pass' ) }
|
|
44
|
+
count={ passed.length }
|
|
45
|
+
color={ auditStatusColor( 'pass' ) }
|
|
46
|
+
defaultExpanded={ expandPass }
|
|
47
|
+
>
|
|
48
|
+
{ passed.map( ( r ) => (
|
|
49
|
+
<ViolationRow key={ r.audit.id } audit={ r.audit } />
|
|
50
|
+
) ) }
|
|
51
|
+
</StatusSection>
|
|
52
|
+
<StatusSection
|
|
53
|
+
label={ auditStatusLabel( 'skipped' ) }
|
|
54
|
+
count={ skipped.length }
|
|
55
|
+
color={ auditStatusColor( 'skipped' ) }
|
|
56
|
+
defaultExpanded={ expandSkipped }
|
|
57
|
+
>
|
|
58
|
+
{ skipped.map( ( r ) => (
|
|
59
|
+
<ViolationRow key={ r.audit.id } audit={ r.audit } skipReason={ r.result.reason } />
|
|
60
|
+
) ) }
|
|
61
|
+
</StatusSection>
|
|
62
|
+
</Box>
|
|
63
|
+
</Box>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Box } from '@elementor/ui';
|
|
3
|
+
import { __ } from '@wordpress/i18n';
|
|
4
|
+
|
|
5
|
+
import { CATEGORY_LABELS } from '../../constants';
|
|
6
|
+
import { type AuditCategory, type PageAuditReport } from '../../types';
|
|
7
|
+
import { partitionAuditResults } from '../../utils/audit-status-summary';
|
|
8
|
+
import { CATEGORY_ICONS } from '../category-icons';
|
|
9
|
+
import StatusSection from '../status-section';
|
|
10
|
+
import SubpageHeader from '../subpage-header';
|
|
11
|
+
import ViolationRow from '../violation-row';
|
|
12
|
+
|
|
13
|
+
type Props = {
|
|
14
|
+
category: AuditCategory;
|
|
15
|
+
report: PageAuditReport;
|
|
16
|
+
onBack: () => void;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default function CategoryPage( { category, report, onBack }: Props ) {
|
|
20
|
+
const Icon = CATEGORY_ICONS[ category ];
|
|
21
|
+
const { failed, passed, totalViolations } = partitionAuditResults( report, { category } );
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<>
|
|
25
|
+
<SubpageHeader
|
|
26
|
+
title={ CATEGORY_LABELS[ category ] }
|
|
27
|
+
onBack={ onBack }
|
|
28
|
+
backLabel={ __( 'Back to all issues', 'elementor' ) }
|
|
29
|
+
icon={ <Icon fontSize="small" color="action" /> }
|
|
30
|
+
/>
|
|
31
|
+
<Box sx={ { p: 1 } }>
|
|
32
|
+
<StatusSection
|
|
33
|
+
label={ __( 'Failed audits', 'elementor' ) }
|
|
34
|
+
count={ totalViolations }
|
|
35
|
+
color="error"
|
|
36
|
+
defaultExpanded
|
|
37
|
+
>
|
|
38
|
+
{ failed.map( ( r ) => (
|
|
39
|
+
<ViolationRow key={ r.audit.id } audit={ r.audit } violations={ r.result.violations } />
|
|
40
|
+
) ) }
|
|
41
|
+
</StatusSection>
|
|
42
|
+
<StatusSection label={ __( 'Passed audits', 'elementor' ) } count={ passed.length } color="success">
|
|
43
|
+
{ passed.map( ( r ) => (
|
|
44
|
+
<ViolationRow key={ r.audit.id } audit={ r.audit } />
|
|
45
|
+
) ) }
|
|
46
|
+
</StatusSection>
|
|
47
|
+
</Box>
|
|
48
|
+
</>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Box, Button, Typography } from '@elementor/ui';
|
|
3
|
+
import { __ } from '@wordpress/i18n';
|
|
4
|
+
|
|
5
|
+
type Props = {
|
|
6
|
+
message: string;
|
|
7
|
+
onRetry: () => void;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export default function ErrorPage( { message, onRetry }: Props ) {
|
|
11
|
+
return (
|
|
12
|
+
<Box sx={ { display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 1.5, p: 4 } }>
|
|
13
|
+
<Typography variant="body2" color="error" textAlign="center">
|
|
14
|
+
{ message }
|
|
15
|
+
</Typography>
|
|
16
|
+
<Button variant="contained" size="small" onClick={ onRetry }>
|
|
17
|
+
{ __( 'Try again', 'elementor' ) }
|
|
18
|
+
</Button>
|
|
19
|
+
</Box>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Box, Link, Typography } from '@elementor/ui';
|
|
3
|
+
import { __ } from '@wordpress/i18n';
|
|
4
|
+
|
|
5
|
+
import { ALL_CATEGORIES, CATEGORY_LABELS } from '../../constants';
|
|
6
|
+
import { type AuditCategory, type PageAuditReport } from '../../types';
|
|
7
|
+
import { getPopulatedCategories } from '../../utils/audit-status-summary';
|
|
8
|
+
import { countSeverities } from '../../utils/severity-counts';
|
|
9
|
+
import IssuesCategoryRow from '../issues-category-row';
|
|
10
|
+
import Promotions from '../promotions';
|
|
11
|
+
|
|
12
|
+
type Props = {
|
|
13
|
+
report: PageAuditReport;
|
|
14
|
+
onCategoryClick: ( category: AuditCategory ) => void;
|
|
15
|
+
onAllAuditsClick: () => void;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export default function IssuesPage( { report, onCategoryClick, onAllAuditsClick }: Props ) {
|
|
19
|
+
const populatedCategories = getPopulatedCategories( report.categories, ALL_CATEGORIES );
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<Box sx={ { display: 'flex', flexDirection: 'column', gap: 4, p: 2 } }>
|
|
23
|
+
<Link
|
|
24
|
+
component="button"
|
|
25
|
+
underline="none"
|
|
26
|
+
color="inherit"
|
|
27
|
+
onClick={ onAllAuditsClick }
|
|
28
|
+
sx={ { textAlign: 'start' } }
|
|
29
|
+
>
|
|
30
|
+
<Typography variant="subtitle1" component="h2">
|
|
31
|
+
{ __( 'All issues', 'elementor' ) }
|
|
32
|
+
</Typography>
|
|
33
|
+
</Link>
|
|
34
|
+
<Box sx={ { display: 'flex', flexDirection: 'column', gap: 1 } }>
|
|
35
|
+
{ populatedCategories.map( ( category ) => (
|
|
36
|
+
<IssuesCategoryRow
|
|
37
|
+
key={ category }
|
|
38
|
+
category={ category }
|
|
39
|
+
label={ CATEGORY_LABELS[ category ] }
|
|
40
|
+
counts={ countSeverities( report, category ) }
|
|
41
|
+
onClick={ () => onCategoryClick( category ) }
|
|
42
|
+
/>
|
|
43
|
+
) ) }
|
|
44
|
+
</Box>
|
|
45
|
+
<Promotions report={ report } />
|
|
46
|
+
</Box>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Box, CircularProgress, Typography } from '@elementor/ui';
|
|
3
|
+
import { __ } from '@wordpress/i18n';
|
|
4
|
+
|
|
5
|
+
export default function LoadingPage() {
|
|
6
|
+
return (
|
|
7
|
+
<Box sx={ { display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 1.5, p: 4 } }>
|
|
8
|
+
<CircularProgress size={ 32 } />
|
|
9
|
+
<Typography variant="body2">{ __( 'Audit in progress.', 'elementor' ) }</Typography>
|
|
10
|
+
</Box>
|
|
11
|
+
);
|
|
12
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Box, Chip, Divider, Typography } from '@elementor/ui';
|
|
3
|
+
import { __, sprintf } from '@wordpress/i18n';
|
|
4
|
+
|
|
5
|
+
import { ALL_CATEGORIES, CATEGORY_LABELS } from '../../constants';
|
|
6
|
+
import { type AuditCategory, type PageAuditReport } from '../../types';
|
|
7
|
+
import {
|
|
8
|
+
auditStatusColor,
|
|
9
|
+
auditStatusDisplayCounts,
|
|
10
|
+
type AuditStatusGroup,
|
|
11
|
+
auditStatusLabel,
|
|
12
|
+
getPopulatedCategories,
|
|
13
|
+
} from '../../utils/audit-status-summary';
|
|
14
|
+
import { getScoreTier } from '../../utils/score-thresholds';
|
|
15
|
+
import {
|
|
16
|
+
ALL_SEVERITIES,
|
|
17
|
+
countSeverities,
|
|
18
|
+
severityPluralLabel,
|
|
19
|
+
severityRemainingCountLabel,
|
|
20
|
+
} from '../../utils/severity-counts';
|
|
21
|
+
import CountSummaryCircle from '../count-summary-circle';
|
|
22
|
+
import ScoreBar from '../score-bar';
|
|
23
|
+
import ScoreCircle from '../score-circle';
|
|
24
|
+
import SeverityIcon from '../severity-icons';
|
|
25
|
+
|
|
26
|
+
type Props = {
|
|
27
|
+
onCategoryClick: ( category: AuditCategory ) => void;
|
|
28
|
+
onStatusClick: ( status: AuditStatusGroup ) => void;
|
|
29
|
+
report: PageAuditReport;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const STATUS_ARIA_LABELS: Record< AuditStatusGroup, ( count: number ) => string > = {
|
|
33
|
+
pass: ( count ) =>
|
|
34
|
+
sprintf(
|
|
35
|
+
/* translators: %d: number of passed audits. */
|
|
36
|
+
__( '%d passed audits, view all', 'elementor' ),
|
|
37
|
+
count
|
|
38
|
+
),
|
|
39
|
+
fail: ( count ) =>
|
|
40
|
+
sprintf(
|
|
41
|
+
/* translators: %d: number of failed audit violations. */
|
|
42
|
+
__( '%d failed audits, view all', 'elementor' ),
|
|
43
|
+
count
|
|
44
|
+
),
|
|
45
|
+
skipped: ( count ) =>
|
|
46
|
+
sprintf(
|
|
47
|
+
/* translators: %d: number of skipped audits. */
|
|
48
|
+
__( '%d skipped audits, view all', 'elementor' ),
|
|
49
|
+
count
|
|
50
|
+
),
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const STATUS_GROUPS = [ 'fail', 'pass', 'skipped' ] as const;
|
|
54
|
+
|
|
55
|
+
export default function OverviewPage( { onCategoryClick, onStatusClick, report }: Props ) {
|
|
56
|
+
const populatedCategories = getPopulatedCategories( report.categories, ALL_CATEGORIES );
|
|
57
|
+
const severityCounts = countSeverities( report );
|
|
58
|
+
const statusCounts = auditStatusDisplayCounts( report );
|
|
59
|
+
const overallScore = getScoreTier( report.overall );
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<Box sx={ { display: 'flex', flexDirection: 'column', gap: 4, p: 2 } }>
|
|
63
|
+
<Box sx={ { display: 'flex', alignItems: 'center', gap: 2 } }>
|
|
64
|
+
<ScoreCircle color={ overallScore.color } score={ report.overall } />
|
|
65
|
+
<Box sx={ { display: 'flex', flexDirection: 'column', gap: 0.5 } }>
|
|
66
|
+
<Chip
|
|
67
|
+
label={ overallScore.label }
|
|
68
|
+
color={ overallScore.color }
|
|
69
|
+
variant="standard"
|
|
70
|
+
size="small"
|
|
71
|
+
sx={ { fontWeight: 600, alignSelf: 'flex-start' } }
|
|
72
|
+
/>
|
|
73
|
+
<Typography variant="body2" color="text.secondary">
|
|
74
|
+
{ __( 'Overall score', 'elementor' ) }
|
|
75
|
+
</Typography>
|
|
76
|
+
</Box>
|
|
77
|
+
</Box>
|
|
78
|
+
<Box sx={ { display: 'flex', flexDirection: 'column', gap: 2 } }>
|
|
79
|
+
{ populatedCategories.map( ( category ) => (
|
|
80
|
+
<ScoreBar
|
|
81
|
+
key={ category }
|
|
82
|
+
label={ CATEGORY_LABELS[ category ] }
|
|
83
|
+
score={ report.categories[ category ].score }
|
|
84
|
+
onClick={ () => onCategoryClick( category ) }
|
|
85
|
+
/>
|
|
86
|
+
) ) }
|
|
87
|
+
</Box>
|
|
88
|
+
<Divider />
|
|
89
|
+
<Typography variant="subtitle1" fontWeight="bold">
|
|
90
|
+
{ __( 'Audit statuses', 'elementor' ) }
|
|
91
|
+
</Typography>
|
|
92
|
+
<Box sx={ { display: 'flex', justifyContent: 'space-around', gap: 2 } }>
|
|
93
|
+
{ STATUS_GROUPS.map( ( status ) => (
|
|
94
|
+
<CountSummaryCircle
|
|
95
|
+
key={ status }
|
|
96
|
+
ariaLabel={ STATUS_ARIA_LABELS[ status ]( statusCounts[ status ] ) }
|
|
97
|
+
color={ auditStatusColor( status ) }
|
|
98
|
+
count={ statusCounts[ status ] }
|
|
99
|
+
label={ auditStatusLabel( status ) }
|
|
100
|
+
onClick={ () => onStatusClick( status ) }
|
|
101
|
+
/>
|
|
102
|
+
) ) }
|
|
103
|
+
</Box>
|
|
104
|
+
<Divider />
|
|
105
|
+
<Typography variant="subtitle1" fontWeight="bold">
|
|
106
|
+
{ __( 'Remaining issues', 'elementor' ) }
|
|
107
|
+
</Typography>
|
|
108
|
+
<Box
|
|
109
|
+
component="ul"
|
|
110
|
+
sx={ {
|
|
111
|
+
display: 'flex',
|
|
112
|
+
flexDirection: 'column',
|
|
113
|
+
gap: 1,
|
|
114
|
+
listStyle: 'none',
|
|
115
|
+
m: 0,
|
|
116
|
+
p: 0,
|
|
117
|
+
} }
|
|
118
|
+
>
|
|
119
|
+
{ ALL_SEVERITIES.map( ( severity ) => {
|
|
120
|
+
const count = severityCounts[ severity ];
|
|
121
|
+
|
|
122
|
+
return (
|
|
123
|
+
<Box
|
|
124
|
+
aria-label={ severityRemainingCountLabel( severity, count ) }
|
|
125
|
+
component="li"
|
|
126
|
+
key={ severity }
|
|
127
|
+
sx={ { alignItems: 'center', display: 'flex', gap: 0.5 } }
|
|
128
|
+
>
|
|
129
|
+
<SeverityIcon severity={ severity } />
|
|
130
|
+
<Typography variant="body2" fontWeight="bold">
|
|
131
|
+
{ count }
|
|
132
|
+
</Typography>
|
|
133
|
+
<Typography variant="body2">{ severityPluralLabel( severity ) }</Typography>
|
|
134
|
+
</Box>
|
|
135
|
+
);
|
|
136
|
+
} ) }
|
|
137
|
+
</Box>
|
|
138
|
+
</Box>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Box, Typography } from '@elementor/ui';
|
|
3
|
+
import { __ } from '@wordpress/i18n';
|
|
4
|
+
|
|
5
|
+
const INFORMATION_IMAGE_PATH = 'images/information.svg';
|
|
6
|
+
|
|
7
|
+
type ElementorCommonWindow = Window & {
|
|
8
|
+
elementorCommon?: {
|
|
9
|
+
config?: {
|
|
10
|
+
urls?: {
|
|
11
|
+
assets?: string;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export default function WelcomePage() {
|
|
18
|
+
const assetsUrl = ( window as ElementorCommonWindow ).elementorCommon?.config?.urls?.assets ?? '';
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<Box
|
|
22
|
+
sx={ {
|
|
23
|
+
display: 'flex',
|
|
24
|
+
flexDirection: 'column',
|
|
25
|
+
alignItems: 'center',
|
|
26
|
+
justifyContent: 'center',
|
|
27
|
+
gap: 1,
|
|
28
|
+
p: 3,
|
|
29
|
+
} }
|
|
30
|
+
>
|
|
31
|
+
<img
|
|
32
|
+
className="elementor-nerd-box-icon"
|
|
33
|
+
src={ `${ assetsUrl }${ INFORMATION_IMAGE_PATH }` }
|
|
34
|
+
loading="lazy"
|
|
35
|
+
alt={ __( 'Elementor', 'elementor' ) }
|
|
36
|
+
/>
|
|
37
|
+
<Typography variant="subtitle1" component="h3" sx={ { marginBlockStart: 2 } }>
|
|
38
|
+
{ __( 'Audit your page!', 'elementor' ) }
|
|
39
|
+
</Typography>
|
|
40
|
+
<Typography variant="caption" textAlign="center">
|
|
41
|
+
{ __(
|
|
42
|
+
'Check SEO, accessibility, performance, and best practices. Click "Run page audit" to begin.',
|
|
43
|
+
'elementor'
|
|
44
|
+
) }
|
|
45
|
+
</Typography>
|
|
46
|
+
</Box>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Box, Button, Typography } from '@elementor/ui';
|
|
3
|
+
|
|
4
|
+
import { type PromotionIcon } from '../register-promotions';
|
|
5
|
+
|
|
6
|
+
type Props = {
|
|
7
|
+
ctaDisabled: boolean;
|
|
8
|
+
ctaLabel: string;
|
|
9
|
+
icon: PromotionIcon;
|
|
10
|
+
onCtaClick: () => void;
|
|
11
|
+
subtitle: string;
|
|
12
|
+
title: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export default function PromotionCard( { ctaDisabled, ctaLabel, icon: Icon, onCtaClick, subtitle, title }: Props ) {
|
|
16
|
+
return (
|
|
17
|
+
<Box
|
|
18
|
+
sx={ {
|
|
19
|
+
alignItems: 'center',
|
|
20
|
+
border: 1,
|
|
21
|
+
borderColor: 'divider',
|
|
22
|
+
borderRadius: 1,
|
|
23
|
+
display: 'flex',
|
|
24
|
+
gap: 1,
|
|
25
|
+
px: 2,
|
|
26
|
+
py: 1.5,
|
|
27
|
+
} }
|
|
28
|
+
>
|
|
29
|
+
<Icon fontSize="small" color="action" />
|
|
30
|
+
<Box sx={ { display: 'flex', flex: 1, flexDirection: 'column', gap: 0.25, minWidth: 0 } }>
|
|
31
|
+
<Typography variant="body2" fontWeight="bold">
|
|
32
|
+
{ title }
|
|
33
|
+
</Typography>
|
|
34
|
+
<Typography variant="caption" color="text.secondary">
|
|
35
|
+
{ subtitle }
|
|
36
|
+
</Typography>
|
|
37
|
+
</Box>
|
|
38
|
+
<Button variant="outlined" color="secondary" size="small" disabled={ ctaDisabled } onClick={ onCtaClick }>
|
|
39
|
+
{ ctaLabel }
|
|
40
|
+
</Button>
|
|
41
|
+
</Box>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Box, Typography } from '@elementor/ui';
|
|
3
|
+
import { __ } from '@wordpress/i18n';
|
|
4
|
+
|
|
5
|
+
import { PROMOTIONS } from '../register-promotions';
|
|
6
|
+
import { type PageAuditReport } from '../types';
|
|
7
|
+
import { findAuditRun } from '../utils/find-audit-run';
|
|
8
|
+
import PromotionCard from './promotion-card';
|
|
9
|
+
|
|
10
|
+
type Props = {
|
|
11
|
+
report: PageAuditReport;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export default function Promotions( { report }: Props ) {
|
|
15
|
+
const cards = PROMOTIONS.flatMap( ( config ) => {
|
|
16
|
+
const run = findAuditRun( report, config.auditId );
|
|
17
|
+
|
|
18
|
+
if ( ! run || run.result.status !== 'fail' ) {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const subtitle = config.formatSubtitle( run );
|
|
23
|
+
|
|
24
|
+
if ( ! subtitle ) {
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const ctaUrl = config.getCtaUrl( run );
|
|
29
|
+
|
|
30
|
+
return [
|
|
31
|
+
{
|
|
32
|
+
config,
|
|
33
|
+
ctaUrl,
|
|
34
|
+
key: config.auditId,
|
|
35
|
+
run,
|
|
36
|
+
subtitle,
|
|
37
|
+
},
|
|
38
|
+
];
|
|
39
|
+
} );
|
|
40
|
+
|
|
41
|
+
if ( cards.length === 0 ) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<>
|
|
47
|
+
<Typography variant="subtitle1" fontWeight="bold">
|
|
48
|
+
{ __( 'Quick wins', 'elementor' ) }
|
|
49
|
+
</Typography>
|
|
50
|
+
<Box sx={ { display: 'flex', flexDirection: 'column', gap: 1 } }>
|
|
51
|
+
{ cards.map( ( { config, ctaUrl, key, run, subtitle } ) => (
|
|
52
|
+
<PromotionCard
|
|
53
|
+
key={ key }
|
|
54
|
+
icon={ config.icon }
|
|
55
|
+
title={ run.audit.title }
|
|
56
|
+
subtitle={ subtitle }
|
|
57
|
+
ctaLabel={ config.ctaLabel }
|
|
58
|
+
ctaDisabled={ ! ctaUrl }
|
|
59
|
+
onCtaClick={ () => {
|
|
60
|
+
if ( ctaUrl ) {
|
|
61
|
+
window.open( ctaUrl, '_blank' );
|
|
62
|
+
}
|
|
63
|
+
} }
|
|
64
|
+
/>
|
|
65
|
+
) ) }
|
|
66
|
+
</Box>
|
|
67
|
+
</>
|
|
68
|
+
);
|
|
69
|
+
}
|