@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
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,2801 @@
|
|
|
1
|
+
// src/init.ts
|
|
2
|
+
import { registerFloatingPanel } from "@elementor/editor-floating-panels";
|
|
3
|
+
import { __registerSlice as registerSlice } from "@elementor/store";
|
|
4
|
+
|
|
5
|
+
// src/editor-app-bar.tsx
|
|
6
|
+
import { utilitiesMenu } from "@elementor/editor-app-bar";
|
|
7
|
+
|
|
8
|
+
// src/hooks/use-audit-toggle-props.ts
|
|
9
|
+
import { ShieldCheckIcon as ShieldCheckIcon2 } from "@elementor/icons";
|
|
10
|
+
import { __ as __22 } from "@wordpress/i18n";
|
|
11
|
+
|
|
12
|
+
// src/editor-panel.tsx
|
|
13
|
+
import { createFloatingPanel } from "@elementor/editor-floating-panels";
|
|
14
|
+
|
|
15
|
+
// src/components/audit-panel.tsx
|
|
16
|
+
import * as React22 from "react";
|
|
17
|
+
import { getCurrentDocumentId as getCurrentDocumentId2 } from "@elementor/editor-elements";
|
|
18
|
+
import { FloatingPanelBody, FloatingPanelFooter, FloatingPanelHeader } from "@elementor/editor-floating-panels";
|
|
19
|
+
import { Box as Box19, Button as Button4, Typography as Typography15 } from "@elementor/ui";
|
|
20
|
+
import { __ as __21 } from "@wordpress/i18n";
|
|
21
|
+
|
|
22
|
+
// src/hooks/use-audit-report.ts
|
|
23
|
+
import { useEffect } from "react";
|
|
24
|
+
import { getCurrentDocumentId } from "@elementor/editor-elements";
|
|
25
|
+
import { __useDispatch as useDispatch, __useSelector as useSelector } from "@elementor/store";
|
|
26
|
+
|
|
27
|
+
// src/runner.ts
|
|
28
|
+
import { getElements } from "@elementor/editor-elements";
|
|
29
|
+
|
|
30
|
+
// src/api/page-context-client.ts
|
|
31
|
+
import { httpService } from "@elementor/http-client";
|
|
32
|
+
|
|
33
|
+
// src/utils/window-config.ts
|
|
34
|
+
function getWindowConfig() {
|
|
35
|
+
const config = window.elementorAudits;
|
|
36
|
+
if (!config) {
|
|
37
|
+
return { restNamespace: "elementor/v1", nonce: "" };
|
|
38
|
+
}
|
|
39
|
+
return config;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// src/api/page-context-client.ts
|
|
43
|
+
async function fetchPageContext(documentId, attachmentIds) {
|
|
44
|
+
const { restNamespace, nonce } = getWindowConfig();
|
|
45
|
+
const url = `${restNamespace}/audits/page-context`;
|
|
46
|
+
const response = await httpService().get(url, {
|
|
47
|
+
params: {
|
|
48
|
+
document_id: documentId,
|
|
49
|
+
attachment_ids: attachmentIds
|
|
50
|
+
},
|
|
51
|
+
headers: { "X-WP-Nonce": nonce }
|
|
52
|
+
});
|
|
53
|
+
return response.data;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/registry.ts
|
|
57
|
+
var registry = /* @__PURE__ */ new Map();
|
|
58
|
+
function registerAudit(audit21) {
|
|
59
|
+
registry.set(audit21.id, audit21);
|
|
60
|
+
}
|
|
61
|
+
function getRegisteredAudits() {
|
|
62
|
+
return Array.from(registry.values());
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// src/constants.ts
|
|
66
|
+
import { __ } from "@wordpress/i18n";
|
|
67
|
+
var AUDIT_PANEL_ID = "audit-panel";
|
|
68
|
+
var REPORT_STORAGE_KEY_PREFIX = "elementor/audits/report";
|
|
69
|
+
var CREATE_WIDGET_EVENT = "elementor/editor/create-widget";
|
|
70
|
+
var ANGIE_FIX_ENTRY_POINT = "audit_violation";
|
|
71
|
+
var FEEDBACK_EXPERIMENT_NAME = "in_editor_feedback";
|
|
72
|
+
var FEEDBACK_ENTRY_POINT = "audit_panel";
|
|
73
|
+
var FEEDBACK_CLICKED_EVENT = "audit_feedback_clicked";
|
|
74
|
+
var FEEDBACK_SENT_EVENT = "audit_feedback_sent";
|
|
75
|
+
var FEEDBACK_CANCELLED_EVENT = "audit_feedback_cancelled";
|
|
76
|
+
var FEEDBACK_CLOSED_EVENT = "audit_feedback_closed";
|
|
77
|
+
var ALL_CATEGORIES = [
|
|
78
|
+
"best-practices",
|
|
79
|
+
"seo",
|
|
80
|
+
"accessibility",
|
|
81
|
+
"performance",
|
|
82
|
+
"compliance"
|
|
83
|
+
];
|
|
84
|
+
var CATEGORY_LABELS = {
|
|
85
|
+
"best-practices": __("Best Practices", "elementor"),
|
|
86
|
+
seo: __("SEO", "elementor"),
|
|
87
|
+
accessibility: __("Accessibility", "elementor"),
|
|
88
|
+
performance: __("Performance", "elementor"),
|
|
89
|
+
compliance: __("Compliance", "elementor")
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// src/utils/audit-status-summary.ts
|
|
93
|
+
import { __ as __2 } from "@wordpress/i18n";
|
|
94
|
+
|
|
95
|
+
// src/utils/sort-failed-audits.ts
|
|
96
|
+
var SEVERITY_SORTING_RANK = {
|
|
97
|
+
error: 0,
|
|
98
|
+
warning: 1,
|
|
99
|
+
info: 2
|
|
100
|
+
};
|
|
101
|
+
function sortFailedAuditResults(results) {
|
|
102
|
+
return [...results].sort((a, b) => {
|
|
103
|
+
const rankDiff = SEVERITY_SORTING_RANK[a.audit.severity] - SEVERITY_SORTING_RANK[b.audit.severity];
|
|
104
|
+
if (rankDiff !== 0) {
|
|
105
|
+
return rankDiff;
|
|
106
|
+
}
|
|
107
|
+
return a.audit.title.localeCompare(b.audit.title);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// src/utils/audit-status-summary.ts
|
|
112
|
+
function partitionAuditResults(report, options = {}) {
|
|
113
|
+
const { category, sortFailed = true } = options;
|
|
114
|
+
const failed = [];
|
|
115
|
+
const passed = [];
|
|
116
|
+
const skipped = [];
|
|
117
|
+
let totalViolations = 0;
|
|
118
|
+
for (const run of report.auditResults) {
|
|
119
|
+
if (category && !run.audit.categories.includes(category)) {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
switch (run.result.status) {
|
|
123
|
+
case "fail":
|
|
124
|
+
failed.push({ ...run, result: run.result });
|
|
125
|
+
totalViolations += run.result.violations.length;
|
|
126
|
+
break;
|
|
127
|
+
case "pass":
|
|
128
|
+
passed.push({ ...run, result: run.result });
|
|
129
|
+
break;
|
|
130
|
+
case "skipped":
|
|
131
|
+
skipped.push({ ...run, result: run.result });
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
failed: sortFailed ? sortFailedAuditResults(failed) : failed,
|
|
137
|
+
passed,
|
|
138
|
+
skipped,
|
|
139
|
+
totalViolations
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
function auditStatusDisplayCounts(report) {
|
|
143
|
+
let pass = 0;
|
|
144
|
+
let skipped = 0;
|
|
145
|
+
let totalViolations = 0;
|
|
146
|
+
for (const { result } of report.auditResults) {
|
|
147
|
+
switch (result.status) {
|
|
148
|
+
case "fail":
|
|
149
|
+
totalViolations += result.violations.length;
|
|
150
|
+
break;
|
|
151
|
+
case "pass":
|
|
152
|
+
pass++;
|
|
153
|
+
break;
|
|
154
|
+
case "skipped":
|
|
155
|
+
skipped++;
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return {
|
|
160
|
+
fail: totalViolations,
|
|
161
|
+
pass,
|
|
162
|
+
skipped
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
function auditStatusColor(status) {
|
|
166
|
+
switch (status) {
|
|
167
|
+
case "fail":
|
|
168
|
+
return "error";
|
|
169
|
+
case "pass":
|
|
170
|
+
return "success";
|
|
171
|
+
case "skipped":
|
|
172
|
+
return "default";
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
function auditStatusLabel(status) {
|
|
176
|
+
switch (status) {
|
|
177
|
+
case "fail":
|
|
178
|
+
return __2("Failed audits", "elementor");
|
|
179
|
+
case "pass":
|
|
180
|
+
return __2("Passed audits", "elementor");
|
|
181
|
+
case "skipped":
|
|
182
|
+
return __2("Skipped audits", "elementor");
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
function getPopulatedCategories(categoryTotals, categories) {
|
|
186
|
+
return categories.filter((category) => categoryTotals[category].total > 0);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// src/utils/compute-report.ts
|
|
190
|
+
function computeReport(documentId, results) {
|
|
191
|
+
const accumulators = Object.fromEntries(
|
|
192
|
+
ALL_CATEGORIES.map((c) => [c, { totalWeight: 0, passedWeight: 0, total: 0, failed: 0 }])
|
|
193
|
+
);
|
|
194
|
+
for (const { audit: audit21, result } of results) {
|
|
195
|
+
if (result.status === "skipped") {
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
for (const category of audit21.categories) {
|
|
199
|
+
const acc = accumulators[category];
|
|
200
|
+
acc.total++;
|
|
201
|
+
acc.totalWeight += audit21.weight;
|
|
202
|
+
if (result.status === "pass") {
|
|
203
|
+
acc.passedWeight += audit21.weight;
|
|
204
|
+
} else {
|
|
205
|
+
acc.failed++;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
const categories = Object.fromEntries(
|
|
210
|
+
ALL_CATEGORIES.map((c) => {
|
|
211
|
+
const acc = accumulators[c];
|
|
212
|
+
const score = acc.totalWeight === 0 ? 0 : Math.round(acc.passedWeight / acc.totalWeight * 100);
|
|
213
|
+
return [c, { score, failed: acc.failed, total: acc.total }];
|
|
214
|
+
})
|
|
215
|
+
);
|
|
216
|
+
const populated = getPopulatedCategories(categories, ALL_CATEGORIES);
|
|
217
|
+
const overall = populated.length === 0 ? 0 : Math.round(populated.reduce((sum, c) => sum + categories[c].score, 0) / populated.length);
|
|
218
|
+
return {
|
|
219
|
+
documentId,
|
|
220
|
+
runAt: Date.now(),
|
|
221
|
+
overall,
|
|
222
|
+
categories,
|
|
223
|
+
auditResults: results
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// src/utils/walk.ts
|
|
228
|
+
function walkElements(tree, visit) {
|
|
229
|
+
const parents = [];
|
|
230
|
+
recurse(tree, parents, visit);
|
|
231
|
+
}
|
|
232
|
+
function recurse(nodes, parents, visit) {
|
|
233
|
+
for (const node of nodes) {
|
|
234
|
+
visit(node, parents);
|
|
235
|
+
parents.push(node);
|
|
236
|
+
recurse(node.elements, parents, visit);
|
|
237
|
+
parents.pop();
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// src/utils/image-like-sources.ts
|
|
242
|
+
var IMAGE_LIKE_WIDGETS = /* @__PURE__ */ new Set(["image", "image-box", "image-carousel", "gallery", "image-gallery"]);
|
|
243
|
+
function hasPageImages(tree) {
|
|
244
|
+
let found = false;
|
|
245
|
+
walkImageLikeSources(tree, ({ media }) => {
|
|
246
|
+
if (media.id || media.url) {
|
|
247
|
+
found = true;
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
return found;
|
|
251
|
+
}
|
|
252
|
+
function walkImageLikeSources(tree, visit) {
|
|
253
|
+
walkElements(tree, (node) => {
|
|
254
|
+
if (node.elType !== "widget") {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
if (!IMAGE_LIKE_WIDGETS.has(node.widgetType ?? "")) {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
for (const media of collectMediaFromNode(node)) {
|
|
261
|
+
visit({ node, media });
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
function collectMediaFromNode(node) {
|
|
266
|
+
const sources = [];
|
|
267
|
+
const image = node.settings.image;
|
|
268
|
+
if (image?.id || image?.url) {
|
|
269
|
+
sources.push(image);
|
|
270
|
+
}
|
|
271
|
+
const gallery = node.settings.carousel ?? node.settings.gallery ?? node.settings.wp_gallery;
|
|
272
|
+
if (Array.isArray(gallery)) {
|
|
273
|
+
for (const item of gallery) {
|
|
274
|
+
if (item?.id || item?.url) {
|
|
275
|
+
sources.push(item);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return sources;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// src/utils/page-attachments.ts
|
|
283
|
+
function extractAttachmentIds(tree) {
|
|
284
|
+
const ids = /* @__PURE__ */ new Set();
|
|
285
|
+
walkImageLikeSources(tree, ({ media }) => {
|
|
286
|
+
if (media.id) {
|
|
287
|
+
ids.add(media.id);
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
return Array.from(ids).sort((a, b) => a - b);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// src/utils/read-kit-snapshot.ts
|
|
294
|
+
function hasNonEmptyGlobalValue(item) {
|
|
295
|
+
return item.value.length > 0;
|
|
296
|
+
}
|
|
297
|
+
async function readKitSnapshot(kitId) {
|
|
298
|
+
const [colorsFromApi, fontsFromApi] = await Promise.all([
|
|
299
|
+
readGlobalsFromApi("globals/colors", mapApiColor, hasNonEmptyGlobalValue),
|
|
300
|
+
readGlobalsFromApi("globals/typography", mapApiTypography, hasNonEmptyGlobalValue)
|
|
301
|
+
]);
|
|
302
|
+
const documentSettings = colorsFromApi.length === 0 || fontsFromApi.length === 0 ? getKitDocumentSettings(kitId) : null;
|
|
303
|
+
const colors = colorsFromApi.length > 0 ? colorsFromApi : readColorsFromKitDocumentSettings(documentSettings);
|
|
304
|
+
const fonts = fontsFromApi.length > 0 ? fontsFromApi : readFontsFromKitDocumentSettings(documentSettings);
|
|
305
|
+
return { id: kitId, globals: { colors, fonts } };
|
|
306
|
+
}
|
|
307
|
+
async function readGlobalsFromApi(command, mapItem, hasValue) {
|
|
308
|
+
const $e = window.$e;
|
|
309
|
+
if (!$e?.data?.get) {
|
|
310
|
+
return [];
|
|
311
|
+
}
|
|
312
|
+
try {
|
|
313
|
+
const result = await $e.data.get(command);
|
|
314
|
+
const data = result?.data ?? {};
|
|
315
|
+
return Object.values(data).map((item) => mapItem(item)).filter(hasValue);
|
|
316
|
+
} catch {
|
|
317
|
+
return [];
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
function mapApiColor(item) {
|
|
321
|
+
return {
|
|
322
|
+
id: item.id,
|
|
323
|
+
value: item.value ?? "",
|
|
324
|
+
title: item.title ?? item.id
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
function mapApiTypography(item) {
|
|
328
|
+
return {
|
|
329
|
+
id: item.id,
|
|
330
|
+
value: item.value?.typography_font_family ?? "",
|
|
331
|
+
title: item.title ?? item.id
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
function readColorsFromKitDocumentSettings(settings) {
|
|
335
|
+
if (!settings) {
|
|
336
|
+
return [];
|
|
337
|
+
}
|
|
338
|
+
const rows = [...settings.system_colors ?? [], ...settings.custom_colors ?? []];
|
|
339
|
+
return rows.map((row) => ({
|
|
340
|
+
id: row._id,
|
|
341
|
+
value: row.color,
|
|
342
|
+
title: row.title ?? row._id
|
|
343
|
+
})).filter(hasNonEmptyGlobalValue);
|
|
344
|
+
}
|
|
345
|
+
function readFontsFromKitDocumentSettings(settings) {
|
|
346
|
+
if (!settings) {
|
|
347
|
+
return [];
|
|
348
|
+
}
|
|
349
|
+
const rows = [...settings.system_typography ?? [], ...settings.custom_typography ?? []];
|
|
350
|
+
return rows.map((row) => ({
|
|
351
|
+
id: row._id,
|
|
352
|
+
value: row.typography_font_family ?? "",
|
|
353
|
+
title: row.title ?? row._id
|
|
354
|
+
})).filter(hasNonEmptyGlobalValue);
|
|
355
|
+
}
|
|
356
|
+
function getKitDocumentSettings(kitId) {
|
|
357
|
+
const document = window.elementor?.documents?.get?.(kitId);
|
|
358
|
+
return document?.config?.settings?.settings ?? null;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// src/utils/v1-snapshot.ts
|
|
362
|
+
function buildSnapshotTree(elements) {
|
|
363
|
+
if (elements.length === 0) {
|
|
364
|
+
return [];
|
|
365
|
+
}
|
|
366
|
+
const root = elements[0];
|
|
367
|
+
const children = root.children ?? [];
|
|
368
|
+
return children.map(toSnapshot);
|
|
369
|
+
}
|
|
370
|
+
function toSnapshot(element) {
|
|
371
|
+
const model = element.model;
|
|
372
|
+
const settings = element.settings?.toJSON?.() ?? {};
|
|
373
|
+
return {
|
|
374
|
+
id: element.id,
|
|
375
|
+
elType: model.get("elType") ?? "",
|
|
376
|
+
widgetType: model.get("widgetType"),
|
|
377
|
+
settings,
|
|
378
|
+
elements: (element.children ?? []).map(toSnapshot)
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// src/runner.ts
|
|
383
|
+
async function runPageAudit(documentId) {
|
|
384
|
+
const tree = buildSnapshotTree(getElements());
|
|
385
|
+
const attachmentIds = extractAttachmentIds(tree);
|
|
386
|
+
const pageContext = await fetchPageContext(documentId, attachmentIds);
|
|
387
|
+
const elements = { documentId, tree };
|
|
388
|
+
const kit = await readKitSnapshot(pageContext.kit_id);
|
|
389
|
+
const ctx = { documentId, elements, pageContext, kit };
|
|
390
|
+
const registered = getRegisteredAudits();
|
|
391
|
+
const auditResults = await Promise.all(
|
|
392
|
+
registered.map(async (audit21) => {
|
|
393
|
+
const { evaluate: _evaluate, ...meta } = audit21;
|
|
394
|
+
try {
|
|
395
|
+
const result = await audit21.evaluate(ctx);
|
|
396
|
+
return { audit: meta, result };
|
|
397
|
+
} catch (error) {
|
|
398
|
+
const reason = error instanceof Error ? error.message : "unknown-error";
|
|
399
|
+
return { audit: meta, result: { status: "skipped", reason } };
|
|
400
|
+
}
|
|
401
|
+
})
|
|
402
|
+
);
|
|
403
|
+
return computeReport(documentId, auditResults);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// src/store/slice.ts
|
|
407
|
+
import { __createSlice } from "@elementor/store";
|
|
408
|
+
var initialState = { status: "idle", report: null, error: null };
|
|
409
|
+
var slice = __createSlice({
|
|
410
|
+
name: "audits",
|
|
411
|
+
initialState,
|
|
412
|
+
reducers: {
|
|
413
|
+
runStarted(state) {
|
|
414
|
+
state.status = "loading";
|
|
415
|
+
state.error = null;
|
|
416
|
+
},
|
|
417
|
+
runSucceeded(state, action) {
|
|
418
|
+
state.status = "ready";
|
|
419
|
+
state.report = action.payload;
|
|
420
|
+
},
|
|
421
|
+
runFailed(state, action) {
|
|
422
|
+
state.status = "error";
|
|
423
|
+
state.error = action.payload;
|
|
424
|
+
},
|
|
425
|
+
reportRestored(state, action) {
|
|
426
|
+
state.status = "ready";
|
|
427
|
+
state.report = action.payload;
|
|
428
|
+
state.error = null;
|
|
429
|
+
},
|
|
430
|
+
reportCleared(state) {
|
|
431
|
+
state.status = "idle";
|
|
432
|
+
state.report = null;
|
|
433
|
+
state.error = null;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
// src/store/selectors.ts
|
|
439
|
+
function selectStatus(state) {
|
|
440
|
+
return state.audits.status;
|
|
441
|
+
}
|
|
442
|
+
function selectReport(state) {
|
|
443
|
+
return state.audits.report;
|
|
444
|
+
}
|
|
445
|
+
function selectError(state) {
|
|
446
|
+
return state.audits.error;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// src/utils/report-storage.ts
|
|
450
|
+
import { getSessionStorageItem, setSessionStorageItem } from "@elementor/session";
|
|
451
|
+
function getStorageKey(documentId) {
|
|
452
|
+
return `${REPORT_STORAGE_KEY_PREFIX}/${documentId}`;
|
|
453
|
+
}
|
|
454
|
+
function getPersistedReport(documentId) {
|
|
455
|
+
return getSessionStorageItem(getStorageKey(documentId)) ?? null;
|
|
456
|
+
}
|
|
457
|
+
function persistReport(documentId, report) {
|
|
458
|
+
setSessionStorageItem(getStorageKey(documentId), report);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// src/hooks/use-audit-report.ts
|
|
462
|
+
function useAuditReport() {
|
|
463
|
+
const status = useSelector((state) => selectStatus(state));
|
|
464
|
+
const report = useSelector((state) => selectReport(state));
|
|
465
|
+
const error = useSelector((state) => selectError(state));
|
|
466
|
+
const dispatch = useDispatch();
|
|
467
|
+
const documentId = getCurrentDocumentId() ?? 0;
|
|
468
|
+
useEffect(() => {
|
|
469
|
+
if (!documentId || report?.documentId === documentId) {
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
const persisted = getPersistedReport(documentId);
|
|
473
|
+
if (persisted) {
|
|
474
|
+
dispatch(slice.actions.reportRestored(persisted));
|
|
475
|
+
} else if (report) {
|
|
476
|
+
dispatch(slice.actions.reportCleared());
|
|
477
|
+
}
|
|
478
|
+
}, [documentId, report, dispatch]);
|
|
479
|
+
const run = async (documentIdToRun) => {
|
|
480
|
+
dispatch(slice.actions.runStarted());
|
|
481
|
+
try {
|
|
482
|
+
const nextReport = await runPageAudit(documentIdToRun);
|
|
483
|
+
dispatch(slice.actions.runSucceeded(nextReport));
|
|
484
|
+
persistReport(documentIdToRun, nextReport);
|
|
485
|
+
} catch (e) {
|
|
486
|
+
dispatch(slice.actions.runFailed(e instanceof Error ? e.message : "Unknown error"));
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
return { status, report, error, run };
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// src/components/audit-feedback.tsx
|
|
493
|
+
import * as React from "react";
|
|
494
|
+
import { useState } from "react";
|
|
495
|
+
import { notify } from "@elementor/editor-notifications";
|
|
496
|
+
import { isExperimentActive } from "@elementor/editor-v1-adapters";
|
|
497
|
+
import { useMixpanel } from "@elementor/events";
|
|
498
|
+
import { httpService as httpService2 } from "@elementor/http-client";
|
|
499
|
+
import { MessageLinesIcon } from "@elementor/icons";
|
|
500
|
+
import {
|
|
501
|
+
Button,
|
|
502
|
+
Dialog,
|
|
503
|
+
DialogActions,
|
|
504
|
+
DialogContent,
|
|
505
|
+
DialogHeader,
|
|
506
|
+
DialogTitle,
|
|
507
|
+
IconButton,
|
|
508
|
+
Stack,
|
|
509
|
+
TextField,
|
|
510
|
+
Tooltip,
|
|
511
|
+
Typography
|
|
512
|
+
} from "@elementor/ui";
|
|
513
|
+
import { __ as __3 } from "@wordpress/i18n";
|
|
514
|
+
var FEEDBACK_SUBJECT = "Page Audit Tool";
|
|
515
|
+
var FEEDBACK_SUBJECT_LABEL = __3("Page Audit Tool", "elementor");
|
|
516
|
+
var SUCCESS_NOTIFICATION_ID = "audit-feedback-success";
|
|
517
|
+
var ERROR_NOTIFICATION_ID = "audit-feedback-error";
|
|
518
|
+
var DIALOG_TITLE_ID = "audit-feedback-dialog-title";
|
|
519
|
+
var DIALOG_DESCRIPTION_ID = "audit-feedback-dialog-description";
|
|
520
|
+
var isUserConnected = () => Boolean(window.elementorCommon?.config?.library_connect?.is_connected || window.elementorPro?.config?.isActive);
|
|
521
|
+
var notifySuccess = () => notify({
|
|
522
|
+
id: SUCCESS_NOTIFICATION_ID,
|
|
523
|
+
type: "success",
|
|
524
|
+
message: __3("Feedback sent. Thanks for helping us out.", "elementor")
|
|
525
|
+
});
|
|
526
|
+
var notifyError = () => notify({
|
|
527
|
+
id: ERROR_NOTIFICATION_ID,
|
|
528
|
+
type: "error",
|
|
529
|
+
message: __3("Something went wrong. Please try sending your feedback again.", "elementor")
|
|
530
|
+
});
|
|
531
|
+
function AuditFeedback() {
|
|
532
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
533
|
+
const [feedbackText, setFeedbackText] = useState("");
|
|
534
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
535
|
+
const { dispatchEvent: trackEvent = (...args) => void args } = useMixpanel();
|
|
536
|
+
if (!isExperimentActive(FEEDBACK_EXPERIMENT_NAME)) {
|
|
537
|
+
return null;
|
|
538
|
+
}
|
|
539
|
+
const connected = isUserConnected();
|
|
540
|
+
const connectUrl = window.elementor?.config?.user?.top_bar?.connect_url;
|
|
541
|
+
if (!connected && !connectUrl) {
|
|
542
|
+
return null;
|
|
543
|
+
}
|
|
544
|
+
const triggerLabel = __3("Give feedback", "elementor");
|
|
545
|
+
const handleOpen = () => {
|
|
546
|
+
if (!connected) {
|
|
547
|
+
window.open(connectUrl, "_blank", "noopener");
|
|
548
|
+
trackEvent(FEEDBACK_CLICKED_EVENT, { entry_point: FEEDBACK_ENTRY_POINT, connected: false });
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
setIsOpen(true);
|
|
552
|
+
trackEvent(FEEDBACK_CLICKED_EVENT, { entry_point: FEEDBACK_ENTRY_POINT, connected: true });
|
|
553
|
+
};
|
|
554
|
+
const dismiss = (eventName) => {
|
|
555
|
+
setIsOpen(false);
|
|
556
|
+
setFeedbackText("");
|
|
557
|
+
trackEvent(eventName, { entry_point: FEEDBACK_ENTRY_POINT });
|
|
558
|
+
};
|
|
559
|
+
const handleCancel = () => dismiss(FEEDBACK_CANCELLED_EVENT);
|
|
560
|
+
const handleClose = () => dismiss(FEEDBACK_CLOSED_EVENT);
|
|
561
|
+
const handleSubmit = () => {
|
|
562
|
+
setIsSubmitting(true);
|
|
563
|
+
httpService2().post("elementor/v1/feedback/submit", {
|
|
564
|
+
subject: FEEDBACK_SUBJECT,
|
|
565
|
+
description: feedbackText.trim()
|
|
566
|
+
}).then((response) => {
|
|
567
|
+
if (!response.data.success) {
|
|
568
|
+
notifyError();
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
setIsOpen(false);
|
|
572
|
+
setFeedbackText("");
|
|
573
|
+
notifySuccess();
|
|
574
|
+
trackEvent(FEEDBACK_SENT_EVENT, { entry_point: FEEDBACK_ENTRY_POINT });
|
|
575
|
+
}).catch(notifyError).finally(() => setIsSubmitting(false));
|
|
576
|
+
};
|
|
577
|
+
return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Tooltip, { title: triggerLabel, placement: "top" }, /* @__PURE__ */ React.createElement(IconButton, { size: "small", "aria-label": triggerLabel, onClick: handleOpen }, /* @__PURE__ */ React.createElement(MessageLinesIcon, { fontSize: "small" }))), /* @__PURE__ */ React.createElement(
|
|
578
|
+
Dialog,
|
|
579
|
+
{
|
|
580
|
+
open: isOpen,
|
|
581
|
+
onClose: handleClose,
|
|
582
|
+
"aria-labelledby": DIALOG_TITLE_ID,
|
|
583
|
+
"aria-describedby": DIALOG_DESCRIPTION_ID,
|
|
584
|
+
PaperProps: { sx: { borderRadius: 1 } }
|
|
585
|
+
},
|
|
586
|
+
/* @__PURE__ */ React.createElement(DialogHeader, { logo: false, onClose: handleClose }, /* @__PURE__ */ React.createElement(DialogTitle, { id: DIALOG_TITLE_ID }, __3("Let us know what you think", "elementor"))),
|
|
587
|
+
/* @__PURE__ */ React.createElement(DialogContent, { dividers: true }, /* @__PURE__ */ React.createElement(Stack, { direction: "column", gap: 2 }, /* @__PURE__ */ React.createElement(TextField, { value: FEEDBACK_SUBJECT_LABEL, disabled: true, fullWidth: true, size: "small" }), /* @__PURE__ */ React.createElement(
|
|
588
|
+
TextField,
|
|
589
|
+
{
|
|
590
|
+
placeholder: __3(
|
|
591
|
+
"Tell us what you had in mind, what we can improve or fix to make this feature better.",
|
|
592
|
+
"elementor"
|
|
593
|
+
),
|
|
594
|
+
fullWidth: true,
|
|
595
|
+
multiline: true,
|
|
596
|
+
rows: 4,
|
|
597
|
+
disabled: isSubmitting,
|
|
598
|
+
onChange: (event) => setFeedbackText(event.target.value),
|
|
599
|
+
value: feedbackText
|
|
600
|
+
}
|
|
601
|
+
), /* @__PURE__ */ React.createElement(Typography, { id: DIALOG_DESCRIPTION_ID, variant: "caption", color: "text.secondary" }, __3(
|
|
602
|
+
"We appreciate your feedback! While we review all submissions, we can't guarantee that every suggestion will result in a change or update.",
|
|
603
|
+
"elementor"
|
|
604
|
+
)))),
|
|
605
|
+
/* @__PURE__ */ React.createElement(DialogActions, null, /* @__PURE__ */ React.createElement(Button, { variant: "text", color: "secondary", onClick: handleCancel }, __3("Cancel", "elementor")), /* @__PURE__ */ React.createElement(
|
|
606
|
+
Button,
|
|
607
|
+
{
|
|
608
|
+
variant: "contained",
|
|
609
|
+
color: "primary",
|
|
610
|
+
disabled: isSubmitting || feedbackText.trim().length === 0,
|
|
611
|
+
onClick: handleSubmit
|
|
612
|
+
},
|
|
613
|
+
__3("Send", "elementor")
|
|
614
|
+
))
|
|
615
|
+
));
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// src/components/pages/error-page.tsx
|
|
619
|
+
import * as React2 from "react";
|
|
620
|
+
import { Box, Button as Button2, Typography as Typography2 } from "@elementor/ui";
|
|
621
|
+
import { __ as __4 } from "@wordpress/i18n";
|
|
622
|
+
function ErrorPage({ message, onRetry }) {
|
|
623
|
+
return /* @__PURE__ */ React2.createElement(Box, { sx: { display: "flex", flexDirection: "column", alignItems: "center", gap: 1.5, p: 4 } }, /* @__PURE__ */ React2.createElement(Typography2, { variant: "body2", color: "error", textAlign: "center" }, message), /* @__PURE__ */ React2.createElement(Button2, { variant: "contained", size: "small", onClick: onRetry }, __4("Try again", "elementor")));
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// src/components/pages/loading-page.tsx
|
|
627
|
+
import * as React3 from "react";
|
|
628
|
+
import { Box as Box2, CircularProgress, Typography as Typography3 } from "@elementor/ui";
|
|
629
|
+
import { __ as __5 } from "@wordpress/i18n";
|
|
630
|
+
function LoadingPage() {
|
|
631
|
+
return /* @__PURE__ */ React3.createElement(Box2, { sx: { display: "flex", flexDirection: "column", alignItems: "center", gap: 1.5, p: 4 } }, /* @__PURE__ */ React3.createElement(CircularProgress, { size: 32 }), /* @__PURE__ */ React3.createElement(Typography3, { variant: "body2" }, __5("Audit in progress.", "elementor")));
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// src/components/pages/welcome-page.tsx
|
|
635
|
+
import * as React4 from "react";
|
|
636
|
+
import { Box as Box3, Typography as Typography4 } from "@elementor/ui";
|
|
637
|
+
import { __ as __6 } from "@wordpress/i18n";
|
|
638
|
+
var INFORMATION_IMAGE_PATH = "images/information.svg";
|
|
639
|
+
function WelcomePage() {
|
|
640
|
+
const assetsUrl = window.elementorCommon?.config?.urls?.assets ?? "";
|
|
641
|
+
return /* @__PURE__ */ React4.createElement(
|
|
642
|
+
Box3,
|
|
643
|
+
{
|
|
644
|
+
sx: {
|
|
645
|
+
display: "flex",
|
|
646
|
+
flexDirection: "column",
|
|
647
|
+
alignItems: "center",
|
|
648
|
+
justifyContent: "center",
|
|
649
|
+
gap: 1,
|
|
650
|
+
p: 3
|
|
651
|
+
}
|
|
652
|
+
},
|
|
653
|
+
/* @__PURE__ */ React4.createElement(
|
|
654
|
+
"img",
|
|
655
|
+
{
|
|
656
|
+
className: "elementor-nerd-box-icon",
|
|
657
|
+
src: `${assetsUrl}${INFORMATION_IMAGE_PATH}`,
|
|
658
|
+
loading: "lazy",
|
|
659
|
+
alt: __6("Elementor", "elementor")
|
|
660
|
+
}
|
|
661
|
+
),
|
|
662
|
+
/* @__PURE__ */ React4.createElement(Typography4, { variant: "subtitle1", component: "h3", sx: { marginBlockStart: 2 } }, __6("Audit your page!", "elementor")),
|
|
663
|
+
/* @__PURE__ */ React4.createElement(Typography4, { variant: "caption", textAlign: "center" }, __6(
|
|
664
|
+
'Check SEO, accessibility, performance, and best practices. Click "Run page audit" to begin.',
|
|
665
|
+
"elementor"
|
|
666
|
+
))
|
|
667
|
+
);
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// src/components/report-shell.tsx
|
|
671
|
+
import * as React21 from "react";
|
|
672
|
+
import { useState as useState4 } from "react";
|
|
673
|
+
import { Box as Box18, Divider as Divider2, Tab, Tabs } from "@elementor/ui";
|
|
674
|
+
import { __ as __20 } from "@wordpress/i18n";
|
|
675
|
+
|
|
676
|
+
// src/components/pages/all-audits-page.tsx
|
|
677
|
+
import * as React11 from "react";
|
|
678
|
+
import { Box as Box8 } from "@elementor/ui";
|
|
679
|
+
import { __ as __12 } from "@wordpress/i18n";
|
|
680
|
+
|
|
681
|
+
// src/components/status-section.tsx
|
|
682
|
+
import * as React5 from "react";
|
|
683
|
+
import { useState as useState2 } from "react";
|
|
684
|
+
import { ChevronDownIcon } from "@elementor/icons";
|
|
685
|
+
import { Box as Box4, Chip, Collapse, IconButton as IconButton2 } from "@elementor/ui";
|
|
686
|
+
import { __ as __7 } from "@wordpress/i18n";
|
|
687
|
+
function StatusSection({ label, count, color = "default", defaultExpanded = false, children }) {
|
|
688
|
+
const [expanded, setExpanded] = useState2(defaultExpanded);
|
|
689
|
+
if (count === 0) {
|
|
690
|
+
return null;
|
|
691
|
+
}
|
|
692
|
+
return /* @__PURE__ */ React5.createElement(Box4, { sx: { paddingBlock: 1 } }, /* @__PURE__ */ React5.createElement(
|
|
693
|
+
Box4,
|
|
694
|
+
{
|
|
695
|
+
sx: { display: "flex", alignItems: "center", gap: 0.5, cursor: "pointer" },
|
|
696
|
+
onClick: () => setExpanded((v) => !v)
|
|
697
|
+
},
|
|
698
|
+
/* @__PURE__ */ React5.createElement(
|
|
699
|
+
IconButton2,
|
|
700
|
+
{
|
|
701
|
+
size: "small",
|
|
702
|
+
"aria-label": expanded ? __7("Collapse", "elementor") : __7("Expand", "elementor")
|
|
703
|
+
},
|
|
704
|
+
/* @__PURE__ */ React5.createElement(
|
|
705
|
+
ChevronDownIcon,
|
|
706
|
+
{
|
|
707
|
+
fontSize: "small",
|
|
708
|
+
sx: {
|
|
709
|
+
transform: expanded ? "rotate(180deg)" : void 0,
|
|
710
|
+
transition: "transform .2s"
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
)
|
|
714
|
+
),
|
|
715
|
+
/* @__PURE__ */ React5.createElement(Chip, { label: `${label} (${count})`, size: "small", color, variant: "standard" })
|
|
716
|
+
), /* @__PURE__ */ React5.createElement(Collapse, { in: expanded }, /* @__PURE__ */ React5.createElement(Box4, { sx: { paddingInlineStart: 4.25, paddingBlock: 2 } }, children)));
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
// src/components/subpage-header.tsx
|
|
720
|
+
import * as React6 from "react";
|
|
721
|
+
import { ArrowLeftIcon } from "@elementor/icons";
|
|
722
|
+
import { Box as Box5, IconButton as IconButton3, Rotate, Typography as Typography5, useTheme } from "@elementor/ui";
|
|
723
|
+
import { __ as __8 } from "@wordpress/i18n";
|
|
724
|
+
function SubpageHeader({ title, onBack, backLabel, icon }) {
|
|
725
|
+
const isRtl = "rtl" === useTheme().direction;
|
|
726
|
+
return /* @__PURE__ */ React6.createElement(Box5, { sx: { display: "flex", alignItems: "center", gap: 0.5, p: 1 } }, /* @__PURE__ */ React6.createElement(IconButton3, { size: "small", onClick: onBack, "aria-label": backLabel ?? __8("Back", "elementor") }, /* @__PURE__ */ React6.createElement(Rotate, { in: isRtl }, /* @__PURE__ */ React6.createElement(ArrowLeftIcon, { fontSize: "small" }))), icon, /* @__PURE__ */ React6.createElement(Typography5, { variant: "subtitle2", fontWeight: "bold" }, title));
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
// src/components/violation-row.tsx
|
|
730
|
+
import * as React10 from "react";
|
|
731
|
+
import { useState as useState3 } from "react";
|
|
732
|
+
import { getElementIcon, getElementTitle } from "@elementor/editor-elements";
|
|
733
|
+
import { AlertCircleIcon as AlertCircleIcon2, BulbIcon, CheckIcon, ChevronDownIcon as ChevronDownIcon2, EyeIcon, HelpIcon } from "@elementor/icons";
|
|
734
|
+
import { Alert, AlertTitle, Box as Box7, Collapse as Collapse2, IconButton as IconButton5, Tooltip as Tooltip3, Typography as Typography6 } from "@elementor/ui";
|
|
735
|
+
import { __ as __11 } from "@wordpress/i18n";
|
|
736
|
+
|
|
737
|
+
// src/hooks/focus-violation.ts
|
|
738
|
+
import { getContainer, selectElement } from "@elementor/editor-elements";
|
|
739
|
+
import { __privateOpenRoute as openRoute, __privateRunCommand as runCommand } from "@elementor/editor-v1-adapters";
|
|
740
|
+
var SCROLL_TO_VIEW_DELAY_MS = 200;
|
|
741
|
+
function focusViolation(violation) {
|
|
742
|
+
if (violation.elementId) {
|
|
743
|
+
const container = getContainer(violation.elementId);
|
|
744
|
+
if (container) {
|
|
745
|
+
selectElement(violation.elementId);
|
|
746
|
+
const domElement = container.view?.getDomElement?.();
|
|
747
|
+
if (domElement) {
|
|
748
|
+
window.elementor?.helpers?.scrollToView?.(domElement, SCROLL_TO_VIEW_DELAY_MS);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
if (violation.targetHint === "element-settings") {
|
|
752
|
+
runCommand("panel/editor/open");
|
|
753
|
+
}
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
if (violation.externalUrl) {
|
|
757
|
+
window.open(violation.externalUrl, "_blank");
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
if (violation.targetHint === "page-settings") {
|
|
761
|
+
openRoute("panel/page-settings/settings");
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
if (violation.targetHint === "site-settings") {
|
|
765
|
+
runCommand("panel/global/open");
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
if (violation.targetHint === "site-identity-settings") {
|
|
769
|
+
void runCommand("panel/global/open").then(() => {
|
|
770
|
+
openRoute("panel/global/settings-site-identity");
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// src/utils/build-angie-prompt.ts
|
|
776
|
+
import { __ as __9, sprintf } from "@wordpress/i18n";
|
|
777
|
+
function buildAngiePrompt(violationText) {
|
|
778
|
+
return sprintf(
|
|
779
|
+
/* translators: %s: audit violation description. */
|
|
780
|
+
__9("Help me fix: %s", "elementor"),
|
|
781
|
+
violationText
|
|
782
|
+
);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
// src/utils/keyboard-click.ts
|
|
786
|
+
function onKeyboardClick(callback) {
|
|
787
|
+
return (event) => {
|
|
788
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
789
|
+
event.preventDefault();
|
|
790
|
+
callback();
|
|
791
|
+
}
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// src/components/fix-violation-with-angie.tsx
|
|
796
|
+
import * as React7 from "react";
|
|
797
|
+
import { isAngieAvailable, sendPromptToAngie } from "@elementor/editor-mcp";
|
|
798
|
+
import { AngieIcon } from "@elementor/icons";
|
|
799
|
+
import { IconButton as IconButton4, Tooltip as Tooltip2 } from "@elementor/ui";
|
|
800
|
+
import { __ as __10 } from "@wordpress/i18n";
|
|
801
|
+
function FixViolationWithAngie({ prompt }) {
|
|
802
|
+
const label = __10("Fix with Angie", "elementor");
|
|
803
|
+
const href = `#angie-prompt=${encodeURIComponent(prompt)}`;
|
|
804
|
+
const handleClick = (event) => {
|
|
805
|
+
event.stopPropagation();
|
|
806
|
+
event.preventDefault();
|
|
807
|
+
if (isAngieAvailable()) {
|
|
808
|
+
sendPromptToAngie(prompt);
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
window.dispatchEvent(
|
|
812
|
+
new CustomEvent(CREATE_WIDGET_EVENT, {
|
|
813
|
+
detail: {
|
|
814
|
+
entry_point: ANGIE_FIX_ENTRY_POINT,
|
|
815
|
+
prompt
|
|
816
|
+
}
|
|
817
|
+
})
|
|
818
|
+
);
|
|
819
|
+
};
|
|
820
|
+
return /* @__PURE__ */ React7.createElement(Tooltip2, { title: label }, /* @__PURE__ */ React7.createElement(
|
|
821
|
+
IconButton4,
|
|
822
|
+
{
|
|
823
|
+
className: "violation-hover-icon",
|
|
824
|
+
component: "a",
|
|
825
|
+
href,
|
|
826
|
+
size: "small",
|
|
827
|
+
"aria-label": label,
|
|
828
|
+
onClick: handleClick
|
|
829
|
+
},
|
|
830
|
+
/* @__PURE__ */ React7.createElement(AngieIcon, { fontSize: "tiny" })
|
|
831
|
+
));
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
// src/components/severity-icons.tsx
|
|
835
|
+
import * as React8 from "react";
|
|
836
|
+
import { AlertCircleIcon, AlertTriangleFilledIcon, InfoCircleIcon } from "@elementor/icons";
|
|
837
|
+
var SEVERITY_CONFIG = {
|
|
838
|
+
error: { Icon: AlertTriangleFilledIcon, color: "error" },
|
|
839
|
+
warning: { Icon: AlertCircleIcon, color: "warning" },
|
|
840
|
+
info: { Icon: InfoCircleIcon, color: "info" }
|
|
841
|
+
};
|
|
842
|
+
function SeverityIcon({ severity }) {
|
|
843
|
+
const { Icon, color } = SEVERITY_CONFIG[severity];
|
|
844
|
+
return /* @__PURE__ */ React8.createElement(Icon, { fontSize: "small", color });
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
// src/components/violation-icons.tsx
|
|
848
|
+
import * as React9 from "react";
|
|
849
|
+
import { FileSettingsIcon, SettingsIcon, ShieldCheckIcon } from "@elementor/icons";
|
|
850
|
+
import { Box as Box6 } from "@elementor/ui";
|
|
851
|
+
function ViolationIcon({ violation, widgetIcon }) {
|
|
852
|
+
if (widgetIcon) {
|
|
853
|
+
return /* @__PURE__ */ React9.createElement(
|
|
854
|
+
Box6,
|
|
855
|
+
{
|
|
856
|
+
component: "i",
|
|
857
|
+
className: widgetIcon,
|
|
858
|
+
"aria-hidden": true,
|
|
859
|
+
sx: { fontSize: "inherit", width: "1em", textAlign: "center" }
|
|
860
|
+
}
|
|
861
|
+
);
|
|
862
|
+
}
|
|
863
|
+
if (violation.targetHint === "page-settings") {
|
|
864
|
+
return /* @__PURE__ */ React9.createElement(FileSettingsIcon, { fontSize: "inherit", "aria-hidden": true });
|
|
865
|
+
}
|
|
866
|
+
if (violation.targetHint === "site-settings" || violation.targetHint === "site-identity-settings") {
|
|
867
|
+
return /* @__PURE__ */ React9.createElement(SettingsIcon, { fontSize: "inherit", "aria-hidden": true });
|
|
868
|
+
}
|
|
869
|
+
return /* @__PURE__ */ React9.createElement(ShieldCheckIcon, { fontSize: "inherit", "aria-hidden": true });
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
// src/components/violation-row.tsx
|
|
873
|
+
function SkipReasonTooltip({ reason }) {
|
|
874
|
+
return /* @__PURE__ */ React10.createElement(Tooltip3, { title: reason, placement: "top" }, /* @__PURE__ */ React10.createElement(Box7, { "aria-label": reason, component: "span", sx: { display: "inline-flex", alignItems: "center" } }, /* @__PURE__ */ React10.createElement(HelpIcon, { fontSize: "small", color: "action" })));
|
|
875
|
+
}
|
|
876
|
+
function StatusIndicator({ audit: audit21, violations }) {
|
|
877
|
+
if (violations) {
|
|
878
|
+
return /* @__PURE__ */ React10.createElement(React10.Fragment, null, /* @__PURE__ */ React10.createElement(Typography6, { variant: "caption", color: "text.secondary", fontWeight: "bold" }, violations.length), /* @__PURE__ */ React10.createElement(SeverityIcon, { severity: audit21.severity }));
|
|
879
|
+
}
|
|
880
|
+
return /* @__PURE__ */ React10.createElement(CheckIcon, { fontSize: "small", color: "success" });
|
|
881
|
+
}
|
|
882
|
+
function ViolationRow({ audit: audit21, skipReason, violations }) {
|
|
883
|
+
const [expanded, setExpanded] = useState3(false);
|
|
884
|
+
const toggleExpanded = () => setExpanded((value) => !value);
|
|
885
|
+
return /* @__PURE__ */ React10.createElement(Box7, { sx: { borderBottom: 1, borderColor: "divider", paddingBlock: 0.5 } }, /* @__PURE__ */ React10.createElement(Box7, { sx: { display: "flex", alignItems: "center", gap: 0.5 } }, /* @__PURE__ */ React10.createElement(
|
|
886
|
+
Box7,
|
|
887
|
+
{
|
|
888
|
+
sx: {
|
|
889
|
+
alignItems: "center",
|
|
890
|
+
cursor: "pointer",
|
|
891
|
+
display: "flex",
|
|
892
|
+
flex: 1,
|
|
893
|
+
gap: 0.5,
|
|
894
|
+
minWidth: 0
|
|
895
|
+
},
|
|
896
|
+
onClick: toggleExpanded
|
|
897
|
+
},
|
|
898
|
+
/* @__PURE__ */ React10.createElement(Typography6, { variant: "body2", sx: { flex: 1 } }, audit21.title),
|
|
899
|
+
!skipReason && /* @__PURE__ */ React10.createElement(StatusIndicator, { audit: audit21, violations })
|
|
900
|
+
), skipReason && /* @__PURE__ */ React10.createElement(SkipReasonTooltip, { reason: skipReason }), /* @__PURE__ */ React10.createElement(
|
|
901
|
+
IconButton5,
|
|
902
|
+
{
|
|
903
|
+
size: "small",
|
|
904
|
+
"aria-label": expanded ? __11("Collapse", "elementor") : __11("Expand", "elementor"),
|
|
905
|
+
onClick: toggleExpanded
|
|
906
|
+
},
|
|
907
|
+
/* @__PURE__ */ React10.createElement(
|
|
908
|
+
ChevronDownIcon2,
|
|
909
|
+
{
|
|
910
|
+
fontSize: "small",
|
|
911
|
+
sx: {
|
|
912
|
+
transform: expanded ? "rotate(180deg)" : void 0,
|
|
913
|
+
transition: "transform .2s"
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
)
|
|
917
|
+
)), /* @__PURE__ */ React10.createElement(Collapse2, { in: expanded }, /* @__PURE__ */ React10.createElement(Box7, { sx: { display: "flex", flexDirection: "column", gap: 1, paddingBlock: 1 } }, /* @__PURE__ */ React10.createElement(
|
|
918
|
+
Alert,
|
|
919
|
+
{
|
|
920
|
+
severity: "secondary",
|
|
921
|
+
sx: { p: 1 },
|
|
922
|
+
icon: /* @__PURE__ */ React10.createElement(AlertCircleIcon2, { fontSize: "small", color: "secondary", "aria-hidden": true })
|
|
923
|
+
},
|
|
924
|
+
/* @__PURE__ */ React10.createElement(AlertTitle, null, /* @__PURE__ */ React10.createElement(Typography6, { variant: "caption", component: "p", color: "text.primary", fontWeight: "bold" }, __11("What's the issue", "elementor"))),
|
|
925
|
+
/* @__PURE__ */ React10.createElement(Typography6, { variant: "caption", component: "p", color: "text.secondary" }, audit21.description)
|
|
926
|
+
), /* @__PURE__ */ React10.createElement(
|
|
927
|
+
Alert,
|
|
928
|
+
{
|
|
929
|
+
severity: "info",
|
|
930
|
+
sx: { p: 1 },
|
|
931
|
+
icon: /* @__PURE__ */ React10.createElement(BulbIcon, { fontSize: "small", color: "info", "aria-hidden": true })
|
|
932
|
+
},
|
|
933
|
+
/* @__PURE__ */ React10.createElement(AlertTitle, null, /* @__PURE__ */ React10.createElement(Typography6, { variant: "caption", component: "p", color: "text.primary", fontWeight: "bold" }, __11("How to resolve", "elementor"))),
|
|
934
|
+
/* @__PURE__ */ React10.createElement(Typography6, { variant: "caption", component: "p", color: "text.secondary" }, audit21.fixHint)
|
|
935
|
+
)), violations && violations.length > 0 && /* @__PURE__ */ React10.createElement(Box7, { role: "list", sx: { paddingBlockEnd: 1, paddingInlineStart: 2 } }, violations.map((violation, idx) => {
|
|
936
|
+
const widgetIcon = violation.elementId ? getElementIcon(violation.elementId) : null;
|
|
937
|
+
const elementTitle = violation.elementId ? getElementTitle(violation.elementId) : null;
|
|
938
|
+
const rowLabel = elementTitle ? `${elementTitle} - ${violation.label}` : violation.label;
|
|
939
|
+
return /* @__PURE__ */ React10.createElement(
|
|
940
|
+
Box7,
|
|
941
|
+
{
|
|
942
|
+
key: idx,
|
|
943
|
+
role: "button",
|
|
944
|
+
tabIndex: 0,
|
|
945
|
+
onClick: () => focusViolation(violation),
|
|
946
|
+
onKeyDown: onKeyboardClick(() => focusViolation(violation)),
|
|
947
|
+
sx: {
|
|
948
|
+
display: "flex",
|
|
949
|
+
alignItems: "center",
|
|
950
|
+
gap: 1,
|
|
951
|
+
paddingBlock: 0.5,
|
|
952
|
+
paddingInline: 2,
|
|
953
|
+
borderRadius: 1,
|
|
954
|
+
cursor: "pointer",
|
|
955
|
+
"& .violation-hover-icon": { opacity: 0, transition: "opacity .15s" },
|
|
956
|
+
"&:hover": { bgcolor: "action.hover" },
|
|
957
|
+
"&:hover .violation-hover-icon, &:focus-visible .violation-hover-icon": {
|
|
958
|
+
opacity: 1
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
},
|
|
962
|
+
/* @__PURE__ */ React10.createElement(ViolationIcon, { violation, widgetIcon }),
|
|
963
|
+
/* @__PURE__ */ React10.createElement(Box7, { sx: { flex: 1 } }, /* @__PURE__ */ React10.createElement(Typography6, { variant: "caption" }, rowLabel), violation.detail && /* @__PURE__ */ React10.createElement(Typography6, { variant: "caption", color: "text.secondary" }, violation.detail)),
|
|
964
|
+
violation.angieFix && /* @__PURE__ */ React10.createElement(FixViolationWithAngie, { prompt: buildAngiePrompt(rowLabel) }),
|
|
965
|
+
/* @__PURE__ */ React10.createElement(EyeIcon, { className: "violation-hover-icon", fontSize: "tiny", "aria-hidden": true })
|
|
966
|
+
);
|
|
967
|
+
}))));
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
// src/components/pages/all-audits-page.tsx
|
|
971
|
+
function AllAuditsPage({ initialExpandedStatus, onBack, report }) {
|
|
972
|
+
const { failed, passed, skipped, totalViolations } = partitionAuditResults(report);
|
|
973
|
+
const expandFail = !initialExpandedStatus || initialExpandedStatus === "fail";
|
|
974
|
+
const expandPass = initialExpandedStatus === "pass";
|
|
975
|
+
const expandSkipped = initialExpandedStatus === "skipped";
|
|
976
|
+
return /* @__PURE__ */ React11.createElement(Box8, { key: initialExpandedStatus ?? "default" }, /* @__PURE__ */ React11.createElement(SubpageHeader, { title: __12("All audits", "elementor"), onBack }), /* @__PURE__ */ React11.createElement(Box8, { sx: { p: 1 } }, /* @__PURE__ */ React11.createElement(
|
|
977
|
+
StatusSection,
|
|
978
|
+
{
|
|
979
|
+
label: auditStatusLabel("fail"),
|
|
980
|
+
count: totalViolations,
|
|
981
|
+
color: auditStatusColor("fail"),
|
|
982
|
+
defaultExpanded: expandFail
|
|
983
|
+
},
|
|
984
|
+
failed.map((r) => /* @__PURE__ */ React11.createElement(ViolationRow, { key: r.audit.id, audit: r.audit, violations: r.result.violations }))
|
|
985
|
+
), /* @__PURE__ */ React11.createElement(
|
|
986
|
+
StatusSection,
|
|
987
|
+
{
|
|
988
|
+
label: auditStatusLabel("pass"),
|
|
989
|
+
count: passed.length,
|
|
990
|
+
color: auditStatusColor("pass"),
|
|
991
|
+
defaultExpanded: expandPass
|
|
992
|
+
},
|
|
993
|
+
passed.map((r) => /* @__PURE__ */ React11.createElement(ViolationRow, { key: r.audit.id, audit: r.audit }))
|
|
994
|
+
), /* @__PURE__ */ React11.createElement(
|
|
995
|
+
StatusSection,
|
|
996
|
+
{
|
|
997
|
+
label: auditStatusLabel("skipped"),
|
|
998
|
+
count: skipped.length,
|
|
999
|
+
color: auditStatusColor("skipped"),
|
|
1000
|
+
defaultExpanded: expandSkipped
|
|
1001
|
+
},
|
|
1002
|
+
skipped.map((r) => /* @__PURE__ */ React11.createElement(ViolationRow, { key: r.audit.id, audit: r.audit, skipReason: r.result.reason }))
|
|
1003
|
+
)));
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
// src/components/pages/category-page.tsx
|
|
1007
|
+
import * as React12 from "react";
|
|
1008
|
+
import { Box as Box9 } from "@elementor/ui";
|
|
1009
|
+
import { __ as __13 } from "@wordpress/i18n";
|
|
1010
|
+
|
|
1011
|
+
// src/components/category-icons.ts
|
|
1012
|
+
import {
|
|
1013
|
+
ElementorAccessibilityIcon,
|
|
1014
|
+
FilterIcon,
|
|
1015
|
+
HeartHandShakeIcon,
|
|
1016
|
+
Settings2Icon,
|
|
1017
|
+
ShieldHalfFilledIcon
|
|
1018
|
+
} from "@elementor/icons";
|
|
1019
|
+
var CATEGORY_ICONS = {
|
|
1020
|
+
"best-practices": Settings2Icon,
|
|
1021
|
+
seo: FilterIcon,
|
|
1022
|
+
accessibility: ElementorAccessibilityIcon,
|
|
1023
|
+
performance: ShieldHalfFilledIcon,
|
|
1024
|
+
compliance: HeartHandShakeIcon
|
|
1025
|
+
};
|
|
1026
|
+
|
|
1027
|
+
// src/components/pages/category-page.tsx
|
|
1028
|
+
function CategoryPage({ category, report, onBack }) {
|
|
1029
|
+
const Icon = CATEGORY_ICONS[category];
|
|
1030
|
+
const { failed, passed, totalViolations } = partitionAuditResults(report, { category });
|
|
1031
|
+
return /* @__PURE__ */ React12.createElement(React12.Fragment, null, /* @__PURE__ */ React12.createElement(
|
|
1032
|
+
SubpageHeader,
|
|
1033
|
+
{
|
|
1034
|
+
title: CATEGORY_LABELS[category],
|
|
1035
|
+
onBack,
|
|
1036
|
+
backLabel: __13("Back to all issues", "elementor"),
|
|
1037
|
+
icon: /* @__PURE__ */ React12.createElement(Icon, { fontSize: "small", color: "action" })
|
|
1038
|
+
}
|
|
1039
|
+
), /* @__PURE__ */ React12.createElement(Box9, { sx: { p: 1 } }, /* @__PURE__ */ React12.createElement(
|
|
1040
|
+
StatusSection,
|
|
1041
|
+
{
|
|
1042
|
+
label: __13("Failed audits", "elementor"),
|
|
1043
|
+
count: totalViolations,
|
|
1044
|
+
color: "error",
|
|
1045
|
+
defaultExpanded: true
|
|
1046
|
+
},
|
|
1047
|
+
failed.map((r) => /* @__PURE__ */ React12.createElement(ViolationRow, { key: r.audit.id, audit: r.audit, violations: r.result.violations }))
|
|
1048
|
+
), /* @__PURE__ */ React12.createElement(StatusSection, { label: __13("Passed audits", "elementor"), count: passed.length, color: "success" }, passed.map((r) => /* @__PURE__ */ React12.createElement(ViolationRow, { key: r.audit.id, audit: r.audit })))));
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
// src/components/pages/issues-page.tsx
|
|
1052
|
+
import * as React16 from "react";
|
|
1053
|
+
import { Box as Box13, Link, Typography as Typography10 } from "@elementor/ui";
|
|
1054
|
+
import { __ as __17 } from "@wordpress/i18n";
|
|
1055
|
+
|
|
1056
|
+
// src/utils/severity-counts.ts
|
|
1057
|
+
import { __ as __14, sprintf as sprintf2 } from "@wordpress/i18n";
|
|
1058
|
+
var ALL_SEVERITIES = ["error", "warning", "info"];
|
|
1059
|
+
function countSeverities(report, category) {
|
|
1060
|
+
const counts = { error: 0, warning: 0, info: 0 };
|
|
1061
|
+
for (const { audit: audit21, result } of report.auditResults) {
|
|
1062
|
+
if (result.status !== "fail") {
|
|
1063
|
+
continue;
|
|
1064
|
+
}
|
|
1065
|
+
if (category && !audit21.categories.includes(category)) {
|
|
1066
|
+
continue;
|
|
1067
|
+
}
|
|
1068
|
+
counts[audit21.severity] += result.violations.length;
|
|
1069
|
+
}
|
|
1070
|
+
return counts;
|
|
1071
|
+
}
|
|
1072
|
+
function severityPluralLabel(severity) {
|
|
1073
|
+
switch (severity) {
|
|
1074
|
+
case "error":
|
|
1075
|
+
return __14("Errors", "elementor");
|
|
1076
|
+
case "warning":
|
|
1077
|
+
return __14("Warnings", "elementor");
|
|
1078
|
+
case "info":
|
|
1079
|
+
return __14("Info", "elementor");
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
function severityRemainingCountLabel(severity, count) {
|
|
1083
|
+
switch (severity) {
|
|
1084
|
+
case "error":
|
|
1085
|
+
return sprintf2(
|
|
1086
|
+
/* translators: %d: number of remaining error violations. */
|
|
1087
|
+
__14("%d errors", "elementor"),
|
|
1088
|
+
count
|
|
1089
|
+
);
|
|
1090
|
+
case "warning":
|
|
1091
|
+
return sprintf2(
|
|
1092
|
+
/* translators: %d: number of remaining warning violations. */
|
|
1093
|
+
__14("%d warnings", "elementor"),
|
|
1094
|
+
count
|
|
1095
|
+
);
|
|
1096
|
+
case "info":
|
|
1097
|
+
return sprintf2(
|
|
1098
|
+
/* translators: %d: number of remaining info violations. */
|
|
1099
|
+
__14("%d info", "elementor"),
|
|
1100
|
+
count
|
|
1101
|
+
);
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
// src/components/issues-category-row.tsx
|
|
1106
|
+
import * as React13 from "react";
|
|
1107
|
+
import { ChevronRightIcon } from "@elementor/icons";
|
|
1108
|
+
import { Box as Box10, Rotate as Rotate2, Typography as Typography7, useTheme as useTheme2 } from "@elementor/ui";
|
|
1109
|
+
function IssuesCategoryRow({ category, label, counts, onClick }) {
|
|
1110
|
+
const isRtl = "rtl" === useTheme2().direction;
|
|
1111
|
+
const Icon = CATEGORY_ICONS[category];
|
|
1112
|
+
return /* @__PURE__ */ React13.createElement(
|
|
1113
|
+
Box10,
|
|
1114
|
+
{
|
|
1115
|
+
role: "button",
|
|
1116
|
+
tabIndex: 0,
|
|
1117
|
+
onClick,
|
|
1118
|
+
onKeyDown: onKeyboardClick(onClick),
|
|
1119
|
+
sx: {
|
|
1120
|
+
display: "flex",
|
|
1121
|
+
alignItems: "center",
|
|
1122
|
+
gap: 1,
|
|
1123
|
+
px: 2,
|
|
1124
|
+
py: 1.5,
|
|
1125
|
+
cursor: "pointer",
|
|
1126
|
+
borderRadius: 1,
|
|
1127
|
+
border: 1,
|
|
1128
|
+
borderColor: "divider",
|
|
1129
|
+
"&:hover": { bgcolor: "action.hover" },
|
|
1130
|
+
outline: "none",
|
|
1131
|
+
"&:focus-visible": { outline: "2px solid", outlineColor: "primary.main" }
|
|
1132
|
+
}
|
|
1133
|
+
},
|
|
1134
|
+
/* @__PURE__ */ React13.createElement(Icon, { fontSize: "small", color: "action" }),
|
|
1135
|
+
/* @__PURE__ */ React13.createElement(Typography7, { variant: "body2", fontWeight: "bold", sx: { flex: 1 } }, label),
|
|
1136
|
+
/* @__PURE__ */ React13.createElement(Box10, { sx: { display: "flex", alignItems: "center", gap: 0.5 } }, ALL_SEVERITIES.filter((s) => counts[s] > 0).map((severity) => /* @__PURE__ */ React13.createElement(Box10, { key: severity, sx: { display: "flex", alignItems: "center", gap: 0.25 } }, /* @__PURE__ */ React13.createElement(SeverityIcon, { severity }), /* @__PURE__ */ React13.createElement(Typography7, { variant: "caption", color: "text.primary", fontWeight: "bold" }, counts[severity])))),
|
|
1137
|
+
/* @__PURE__ */ React13.createElement(Rotate2, { in: isRtl }, /* @__PURE__ */ React13.createElement(ChevronRightIcon, { fontSize: "small", color: "action" }))
|
|
1138
|
+
);
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
// src/components/promotions.tsx
|
|
1142
|
+
import * as React15 from "react";
|
|
1143
|
+
import { Box as Box12, Typography as Typography9 } from "@elementor/ui";
|
|
1144
|
+
import { __ as __16 } from "@wordpress/i18n";
|
|
1145
|
+
|
|
1146
|
+
// src/register-promotions.ts
|
|
1147
|
+
import { ElementorCookieIcon, PhotoIcon, ShieldHalfFilledIcon as ShieldHalfFilledIcon2 } from "@elementor/icons";
|
|
1148
|
+
import { __ as __15, _n, sprintf as sprintf3 } from "@wordpress/i18n";
|
|
1149
|
+
function firstFailExternalUrl(run) {
|
|
1150
|
+
return run.result.status === "fail" ? run.result.violations[0]?.externalUrl : void 0;
|
|
1151
|
+
}
|
|
1152
|
+
var PROMOTIONS = [
|
|
1153
|
+
{
|
|
1154
|
+
auditId: "audits/images-alt-text",
|
|
1155
|
+
icon: PhotoIcon,
|
|
1156
|
+
ctaLabel: __15("Fix with Ally", "elementor"),
|
|
1157
|
+
formatSubtitle: (run) => {
|
|
1158
|
+
if (run.result.status !== "fail") {
|
|
1159
|
+
return null;
|
|
1160
|
+
}
|
|
1161
|
+
const count = run.result.metadata?.missingAltImageCount ?? 0;
|
|
1162
|
+
if (count === 0) {
|
|
1163
|
+
return null;
|
|
1164
|
+
}
|
|
1165
|
+
return sprintf3(
|
|
1166
|
+
/* translators: %d: number of images missing alt text. */
|
|
1167
|
+
_n("%d image", "%d images", count, "elementor"),
|
|
1168
|
+
count
|
|
1169
|
+
);
|
|
1170
|
+
},
|
|
1171
|
+
getCtaUrl: firstFailExternalUrl
|
|
1172
|
+
},
|
|
1173
|
+
{
|
|
1174
|
+
auditId: "audits/cookie-policy",
|
|
1175
|
+
icon: ElementorCookieIcon,
|
|
1176
|
+
ctaLabel: __15("Fix with Cookiez", "elementor"),
|
|
1177
|
+
formatSubtitle: (run) => run.result.status === "fail" ? __15("Generate cookie policy", "elementor") : null,
|
|
1178
|
+
getCtaUrl: firstFailExternalUrl
|
|
1179
|
+
},
|
|
1180
|
+
{
|
|
1181
|
+
auditId: "audits/images-too-large",
|
|
1182
|
+
icon: ShieldHalfFilledIcon2,
|
|
1183
|
+
ctaLabel: __15("Optimize all", "elementor"),
|
|
1184
|
+
formatSubtitle: (run) => {
|
|
1185
|
+
if (run.result.status !== "fail") {
|
|
1186
|
+
return null;
|
|
1187
|
+
}
|
|
1188
|
+
const count = run.result.metadata?.oversizedImageCount ?? 0;
|
|
1189
|
+
if (count === 0) {
|
|
1190
|
+
return null;
|
|
1191
|
+
}
|
|
1192
|
+
return sprintf3(
|
|
1193
|
+
/* translators: %d: number of oversized images. */
|
|
1194
|
+
_n("%d image", "%d images", count, "elementor"),
|
|
1195
|
+
count
|
|
1196
|
+
);
|
|
1197
|
+
},
|
|
1198
|
+
getCtaUrl: firstFailExternalUrl
|
|
1199
|
+
}
|
|
1200
|
+
];
|
|
1201
|
+
|
|
1202
|
+
// src/utils/find-audit-run.ts
|
|
1203
|
+
function findAuditRun(report, auditId) {
|
|
1204
|
+
return report.auditResults.find((run) => run.audit.id === auditId);
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
// src/components/promotion-card.tsx
|
|
1208
|
+
import * as React14 from "react";
|
|
1209
|
+
import { Box as Box11, Button as Button3, Typography as Typography8 } from "@elementor/ui";
|
|
1210
|
+
function PromotionCard({ ctaDisabled, ctaLabel, icon: Icon, onCtaClick, subtitle, title }) {
|
|
1211
|
+
return /* @__PURE__ */ React14.createElement(
|
|
1212
|
+
Box11,
|
|
1213
|
+
{
|
|
1214
|
+
sx: {
|
|
1215
|
+
alignItems: "center",
|
|
1216
|
+
border: 1,
|
|
1217
|
+
borderColor: "divider",
|
|
1218
|
+
borderRadius: 1,
|
|
1219
|
+
display: "flex",
|
|
1220
|
+
gap: 1,
|
|
1221
|
+
px: 2,
|
|
1222
|
+
py: 1.5
|
|
1223
|
+
}
|
|
1224
|
+
},
|
|
1225
|
+
/* @__PURE__ */ React14.createElement(Icon, { fontSize: "small", color: "action" }),
|
|
1226
|
+
/* @__PURE__ */ React14.createElement(Box11, { sx: { display: "flex", flex: 1, flexDirection: "column", gap: 0.25, minWidth: 0 } }, /* @__PURE__ */ React14.createElement(Typography8, { variant: "body2", fontWeight: "bold" }, title), /* @__PURE__ */ React14.createElement(Typography8, { variant: "caption", color: "text.secondary" }, subtitle)),
|
|
1227
|
+
/* @__PURE__ */ React14.createElement(Button3, { variant: "outlined", color: "secondary", size: "small", disabled: ctaDisabled, onClick: onCtaClick }, ctaLabel)
|
|
1228
|
+
);
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
// src/components/promotions.tsx
|
|
1232
|
+
function Promotions({ report }) {
|
|
1233
|
+
const cards = PROMOTIONS.flatMap((config) => {
|
|
1234
|
+
const run = findAuditRun(report, config.auditId);
|
|
1235
|
+
if (!run || run.result.status !== "fail") {
|
|
1236
|
+
return [];
|
|
1237
|
+
}
|
|
1238
|
+
const subtitle = config.formatSubtitle(run);
|
|
1239
|
+
if (!subtitle) {
|
|
1240
|
+
return [];
|
|
1241
|
+
}
|
|
1242
|
+
const ctaUrl = config.getCtaUrl(run);
|
|
1243
|
+
return [
|
|
1244
|
+
{
|
|
1245
|
+
config,
|
|
1246
|
+
ctaUrl,
|
|
1247
|
+
key: config.auditId,
|
|
1248
|
+
run,
|
|
1249
|
+
subtitle
|
|
1250
|
+
}
|
|
1251
|
+
];
|
|
1252
|
+
});
|
|
1253
|
+
if (cards.length === 0) {
|
|
1254
|
+
return null;
|
|
1255
|
+
}
|
|
1256
|
+
return /* @__PURE__ */ React15.createElement(React15.Fragment, null, /* @__PURE__ */ React15.createElement(Typography9, { variant: "subtitle1", fontWeight: "bold" }, __16("Quick wins", "elementor")), /* @__PURE__ */ React15.createElement(Box12, { sx: { display: "flex", flexDirection: "column", gap: 1 } }, cards.map(({ config, ctaUrl, key, run, subtitle }) => /* @__PURE__ */ React15.createElement(
|
|
1257
|
+
PromotionCard,
|
|
1258
|
+
{
|
|
1259
|
+
key,
|
|
1260
|
+
icon: config.icon,
|
|
1261
|
+
title: run.audit.title,
|
|
1262
|
+
subtitle,
|
|
1263
|
+
ctaLabel: config.ctaLabel,
|
|
1264
|
+
ctaDisabled: !ctaUrl,
|
|
1265
|
+
onCtaClick: () => {
|
|
1266
|
+
if (ctaUrl) {
|
|
1267
|
+
window.open(ctaUrl, "_blank");
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
))));
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
// src/components/pages/issues-page.tsx
|
|
1275
|
+
function IssuesPage({ report, onCategoryClick, onAllAuditsClick }) {
|
|
1276
|
+
const populatedCategories = getPopulatedCategories(report.categories, ALL_CATEGORIES);
|
|
1277
|
+
return /* @__PURE__ */ React16.createElement(Box13, { sx: { display: "flex", flexDirection: "column", gap: 4, p: 2 } }, /* @__PURE__ */ React16.createElement(
|
|
1278
|
+
Link,
|
|
1279
|
+
{
|
|
1280
|
+
component: "button",
|
|
1281
|
+
underline: "none",
|
|
1282
|
+
color: "inherit",
|
|
1283
|
+
onClick: onAllAuditsClick,
|
|
1284
|
+
sx: { textAlign: "start" }
|
|
1285
|
+
},
|
|
1286
|
+
/* @__PURE__ */ React16.createElement(Typography10, { variant: "subtitle1", component: "h2" }, __17("All issues", "elementor"))
|
|
1287
|
+
), /* @__PURE__ */ React16.createElement(Box13, { sx: { display: "flex", flexDirection: "column", gap: 1 } }, populatedCategories.map((category) => /* @__PURE__ */ React16.createElement(
|
|
1288
|
+
IssuesCategoryRow,
|
|
1289
|
+
{
|
|
1290
|
+
key: category,
|
|
1291
|
+
category,
|
|
1292
|
+
label: CATEGORY_LABELS[category],
|
|
1293
|
+
counts: countSeverities(report, category),
|
|
1294
|
+
onClick: () => onCategoryClick(category)
|
|
1295
|
+
}
|
|
1296
|
+
))), /* @__PURE__ */ React16.createElement(Promotions, { report }));
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
// src/components/pages/overview-page.tsx
|
|
1300
|
+
import * as React20 from "react";
|
|
1301
|
+
import { Box as Box17, Chip as Chip3, Divider, Typography as Typography14 } from "@elementor/ui";
|
|
1302
|
+
import { __ as __19, sprintf as sprintf4 } from "@wordpress/i18n";
|
|
1303
|
+
|
|
1304
|
+
// src/utils/score-thresholds.ts
|
|
1305
|
+
import { __ as __18 } from "@wordpress/i18n";
|
|
1306
|
+
var GOOD_THRESHOLD = 90;
|
|
1307
|
+
var OK_THRESHOLD = 50;
|
|
1308
|
+
function getScoreTier(score) {
|
|
1309
|
+
if (score >= GOOD_THRESHOLD) {
|
|
1310
|
+
return { color: "success", label: __18("Good", "elementor") };
|
|
1311
|
+
}
|
|
1312
|
+
if (score >= OK_THRESHOLD) {
|
|
1313
|
+
return { color: "warning", label: __18("Needs work", "elementor") };
|
|
1314
|
+
}
|
|
1315
|
+
return { color: "error", label: __18("At risk", "elementor") };
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
// src/components/count-summary-circle.tsx
|
|
1319
|
+
import * as React17 from "react";
|
|
1320
|
+
import { Box as Box14, Chip as Chip2, Typography as Typography11 } from "@elementor/ui";
|
|
1321
|
+
var COUNT_SUMMARY_CIRCLE_SIZE = 64;
|
|
1322
|
+
var COUNT_SUMMARY_BORDER_WIDTH = 4;
|
|
1323
|
+
function chipBorderColor(theme, color) {
|
|
1324
|
+
if (color === "default" || !color) {
|
|
1325
|
+
return theme.palette.text.secondary;
|
|
1326
|
+
}
|
|
1327
|
+
return theme.palette[color].main;
|
|
1328
|
+
}
|
|
1329
|
+
function CountSummaryCircle({ ariaLabel, color, count, label, onClick }) {
|
|
1330
|
+
return /* @__PURE__ */ React17.createElement(
|
|
1331
|
+
Box14,
|
|
1332
|
+
{
|
|
1333
|
+
"aria-label": ariaLabel,
|
|
1334
|
+
component: "button",
|
|
1335
|
+
onClick,
|
|
1336
|
+
type: "button",
|
|
1337
|
+
sx: {
|
|
1338
|
+
alignItems: "center",
|
|
1339
|
+
background: "none",
|
|
1340
|
+
border: "none",
|
|
1341
|
+
cursor: "pointer",
|
|
1342
|
+
display: "flex",
|
|
1343
|
+
flex: 1,
|
|
1344
|
+
flexDirection: "column",
|
|
1345
|
+
gap: 0.5,
|
|
1346
|
+
padding: 0
|
|
1347
|
+
}
|
|
1348
|
+
},
|
|
1349
|
+
/* @__PURE__ */ React17.createElement(
|
|
1350
|
+
Chip2,
|
|
1351
|
+
{
|
|
1352
|
+
color,
|
|
1353
|
+
label: count,
|
|
1354
|
+
size: "small",
|
|
1355
|
+
variant: "standard",
|
|
1356
|
+
sx: (theme) => ({
|
|
1357
|
+
border: `${COUNT_SUMMARY_BORDER_WIDTH}px solid ${chipBorderColor(theme, color)}`,
|
|
1358
|
+
borderRadius: "50%",
|
|
1359
|
+
height: COUNT_SUMMARY_CIRCLE_SIZE,
|
|
1360
|
+
maxWidth: COUNT_SUMMARY_CIRCLE_SIZE,
|
|
1361
|
+
width: COUNT_SUMMARY_CIRCLE_SIZE,
|
|
1362
|
+
"& .MuiChip-label": {
|
|
1363
|
+
fontSize: theme.typography.body2.fontSize,
|
|
1364
|
+
fontWeight: 700,
|
|
1365
|
+
lineHeight: 1,
|
|
1366
|
+
padding: 0
|
|
1367
|
+
}
|
|
1368
|
+
})
|
|
1369
|
+
}
|
|
1370
|
+
),
|
|
1371
|
+
/* @__PURE__ */ React17.createElement(Typography11, { color: "text.secondary", sx: { textAlign: "center" }, variant: "caption" }, label)
|
|
1372
|
+
);
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
// src/components/score-bar.tsx
|
|
1376
|
+
import * as React18 from "react";
|
|
1377
|
+
import { ChevronRightIcon as ChevronRightIcon2 } from "@elementor/icons";
|
|
1378
|
+
import { Box as Box15, LinearProgress, Rotate as Rotate3, Typography as Typography12, useTheme as useTheme3 } from "@elementor/ui";
|
|
1379
|
+
function ScoreBar({ label, score, onClick }) {
|
|
1380
|
+
const isRtl = "rtl" === useTheme3().direction;
|
|
1381
|
+
return /* @__PURE__ */ React18.createElement(
|
|
1382
|
+
Box15,
|
|
1383
|
+
{
|
|
1384
|
+
role: onClick ? "button" : void 0,
|
|
1385
|
+
tabIndex: onClick ? 0 : void 0,
|
|
1386
|
+
onClick,
|
|
1387
|
+
onKeyDown: onClick ? onKeyboardClick(onClick) : void 0,
|
|
1388
|
+
sx: {
|
|
1389
|
+
display: "flex",
|
|
1390
|
+
alignItems: "center",
|
|
1391
|
+
gap: 1,
|
|
1392
|
+
cursor: onClick ? "pointer" : "default",
|
|
1393
|
+
outline: "none",
|
|
1394
|
+
"&:focus-visible": { outline: "2px solid", outlineColor: "primary.main", borderRadius: 1 },
|
|
1395
|
+
py: 0.5
|
|
1396
|
+
}
|
|
1397
|
+
},
|
|
1398
|
+
/* @__PURE__ */ React18.createElement(Typography12, { variant: "body2", sx: { minWidth: 96 } }, label),
|
|
1399
|
+
/* @__PURE__ */ React18.createElement(
|
|
1400
|
+
LinearProgress,
|
|
1401
|
+
{
|
|
1402
|
+
variant: "determinate",
|
|
1403
|
+
value: score,
|
|
1404
|
+
color: getScoreTier(score).color,
|
|
1405
|
+
sx: { flex: 1, height: 6, borderRadius: 4, bgcolor: "action.disabledBackground" }
|
|
1406
|
+
}
|
|
1407
|
+
),
|
|
1408
|
+
/* @__PURE__ */ React18.createElement(
|
|
1409
|
+
Typography12,
|
|
1410
|
+
{
|
|
1411
|
+
variant: "body2",
|
|
1412
|
+
color: "text.primary",
|
|
1413
|
+
sx: { minWidth: 24, textAlign: "right", fontWeight: 900 }
|
|
1414
|
+
},
|
|
1415
|
+
score
|
|
1416
|
+
),
|
|
1417
|
+
onClick && /* @__PURE__ */ React18.createElement(Rotate3, { in: isRtl }, /* @__PURE__ */ React18.createElement(ChevronRightIcon2, { fontSize: "small", color: "action" }))
|
|
1418
|
+
);
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
// src/components/score-circle.tsx
|
|
1422
|
+
import * as React19 from "react";
|
|
1423
|
+
import { Box as Box16, CircularProgress as CircularProgress2, Typography as Typography13 } from "@elementor/ui";
|
|
1424
|
+
var SCORE_CIRCLE_SIZE = 88;
|
|
1425
|
+
var SCORE_CIRCLE_THICKNESS = 3;
|
|
1426
|
+
function ScoreCircle({ color, score }) {
|
|
1427
|
+
const progressColor = color ?? getScoreTier(score).color;
|
|
1428
|
+
return /* @__PURE__ */ React19.createElement(
|
|
1429
|
+
Box16,
|
|
1430
|
+
{
|
|
1431
|
+
role: "progressbar",
|
|
1432
|
+
"aria-valuenow": score,
|
|
1433
|
+
"aria-valuemin": 0,
|
|
1434
|
+
"aria-valuemax": 100,
|
|
1435
|
+
sx: { position: "relative", display: "inline-flex" }
|
|
1436
|
+
},
|
|
1437
|
+
/* @__PURE__ */ React19.createElement(
|
|
1438
|
+
CircularProgress2,
|
|
1439
|
+
{
|
|
1440
|
+
variant: "determinate",
|
|
1441
|
+
value: 100,
|
|
1442
|
+
size: SCORE_CIRCLE_SIZE,
|
|
1443
|
+
thickness: SCORE_CIRCLE_THICKNESS,
|
|
1444
|
+
sx: { color: "action.disabledBackground" }
|
|
1445
|
+
}
|
|
1446
|
+
),
|
|
1447
|
+
/* @__PURE__ */ React19.createElement(
|
|
1448
|
+
CircularProgress2,
|
|
1449
|
+
{
|
|
1450
|
+
variant: "determinate",
|
|
1451
|
+
value: score,
|
|
1452
|
+
size: SCORE_CIRCLE_SIZE,
|
|
1453
|
+
thickness: SCORE_CIRCLE_THICKNESS,
|
|
1454
|
+
color: progressColor,
|
|
1455
|
+
sx: { position: "absolute", left: 0 }
|
|
1456
|
+
}
|
|
1457
|
+
),
|
|
1458
|
+
/* @__PURE__ */ React19.createElement(
|
|
1459
|
+
Box16,
|
|
1460
|
+
{
|
|
1461
|
+
sx: {
|
|
1462
|
+
position: "absolute",
|
|
1463
|
+
inset: 0,
|
|
1464
|
+
display: "flex",
|
|
1465
|
+
alignItems: "center",
|
|
1466
|
+
justifyContent: "center"
|
|
1467
|
+
}
|
|
1468
|
+
},
|
|
1469
|
+
/* @__PURE__ */ React19.createElement(
|
|
1470
|
+
Typography13,
|
|
1471
|
+
{
|
|
1472
|
+
variant: "h4",
|
|
1473
|
+
component: "span",
|
|
1474
|
+
color: "text.primary",
|
|
1475
|
+
sx: { fontWeight: 700, lineHeight: 1 }
|
|
1476
|
+
},
|
|
1477
|
+
score
|
|
1478
|
+
)
|
|
1479
|
+
)
|
|
1480
|
+
);
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
// src/components/pages/overview-page.tsx
|
|
1484
|
+
var STATUS_ARIA_LABELS = {
|
|
1485
|
+
pass: (count) => sprintf4(
|
|
1486
|
+
/* translators: %d: number of passed audits. */
|
|
1487
|
+
__19("%d passed audits, view all", "elementor"),
|
|
1488
|
+
count
|
|
1489
|
+
),
|
|
1490
|
+
fail: (count) => sprintf4(
|
|
1491
|
+
/* translators: %d: number of failed audit violations. */
|
|
1492
|
+
__19("%d failed audits, view all", "elementor"),
|
|
1493
|
+
count
|
|
1494
|
+
),
|
|
1495
|
+
skipped: (count) => sprintf4(
|
|
1496
|
+
/* translators: %d: number of skipped audits. */
|
|
1497
|
+
__19("%d skipped audits, view all", "elementor"),
|
|
1498
|
+
count
|
|
1499
|
+
)
|
|
1500
|
+
};
|
|
1501
|
+
var STATUS_GROUPS = ["fail", "pass", "skipped"];
|
|
1502
|
+
function OverviewPage({ onCategoryClick, onStatusClick, report }) {
|
|
1503
|
+
const populatedCategories = getPopulatedCategories(report.categories, ALL_CATEGORIES);
|
|
1504
|
+
const severityCounts = countSeverities(report);
|
|
1505
|
+
const statusCounts = auditStatusDisplayCounts(report);
|
|
1506
|
+
const overallScore = getScoreTier(report.overall);
|
|
1507
|
+
return /* @__PURE__ */ React20.createElement(Box17, { sx: { display: "flex", flexDirection: "column", gap: 4, p: 2 } }, /* @__PURE__ */ React20.createElement(Box17, { sx: { display: "flex", alignItems: "center", gap: 2 } }, /* @__PURE__ */ React20.createElement(ScoreCircle, { color: overallScore.color, score: report.overall }), /* @__PURE__ */ React20.createElement(Box17, { sx: { display: "flex", flexDirection: "column", gap: 0.5 } }, /* @__PURE__ */ React20.createElement(
|
|
1508
|
+
Chip3,
|
|
1509
|
+
{
|
|
1510
|
+
label: overallScore.label,
|
|
1511
|
+
color: overallScore.color,
|
|
1512
|
+
variant: "standard",
|
|
1513
|
+
size: "small",
|
|
1514
|
+
sx: { fontWeight: 600, alignSelf: "flex-start" }
|
|
1515
|
+
}
|
|
1516
|
+
), /* @__PURE__ */ React20.createElement(Typography14, { variant: "body2", color: "text.secondary" }, __19("Overall score", "elementor")))), /* @__PURE__ */ React20.createElement(Box17, { sx: { display: "flex", flexDirection: "column", gap: 2 } }, populatedCategories.map((category) => /* @__PURE__ */ React20.createElement(
|
|
1517
|
+
ScoreBar,
|
|
1518
|
+
{
|
|
1519
|
+
key: category,
|
|
1520
|
+
label: CATEGORY_LABELS[category],
|
|
1521
|
+
score: report.categories[category].score,
|
|
1522
|
+
onClick: () => onCategoryClick(category)
|
|
1523
|
+
}
|
|
1524
|
+
))), /* @__PURE__ */ React20.createElement(Divider, null), /* @__PURE__ */ React20.createElement(Typography14, { variant: "subtitle1", fontWeight: "bold" }, __19("Audit statuses", "elementor")), /* @__PURE__ */ React20.createElement(Box17, { sx: { display: "flex", justifyContent: "space-around", gap: 2 } }, STATUS_GROUPS.map((status) => /* @__PURE__ */ React20.createElement(
|
|
1525
|
+
CountSummaryCircle,
|
|
1526
|
+
{
|
|
1527
|
+
key: status,
|
|
1528
|
+
ariaLabel: STATUS_ARIA_LABELS[status](statusCounts[status]),
|
|
1529
|
+
color: auditStatusColor(status),
|
|
1530
|
+
count: statusCounts[status],
|
|
1531
|
+
label: auditStatusLabel(status),
|
|
1532
|
+
onClick: () => onStatusClick(status)
|
|
1533
|
+
}
|
|
1534
|
+
))), /* @__PURE__ */ React20.createElement(Divider, null), /* @__PURE__ */ React20.createElement(Typography14, { variant: "subtitle1", fontWeight: "bold" }, __19("Remaining issues", "elementor")), /* @__PURE__ */ React20.createElement(
|
|
1535
|
+
Box17,
|
|
1536
|
+
{
|
|
1537
|
+
component: "ul",
|
|
1538
|
+
sx: {
|
|
1539
|
+
display: "flex",
|
|
1540
|
+
flexDirection: "column",
|
|
1541
|
+
gap: 1,
|
|
1542
|
+
listStyle: "none",
|
|
1543
|
+
m: 0,
|
|
1544
|
+
p: 0
|
|
1545
|
+
}
|
|
1546
|
+
},
|
|
1547
|
+
ALL_SEVERITIES.map((severity) => {
|
|
1548
|
+
const count = severityCounts[severity];
|
|
1549
|
+
return /* @__PURE__ */ React20.createElement(
|
|
1550
|
+
Box17,
|
|
1551
|
+
{
|
|
1552
|
+
"aria-label": severityRemainingCountLabel(severity, count),
|
|
1553
|
+
component: "li",
|
|
1554
|
+
key: severity,
|
|
1555
|
+
sx: { alignItems: "center", display: "flex", gap: 0.5 }
|
|
1556
|
+
},
|
|
1557
|
+
/* @__PURE__ */ React20.createElement(SeverityIcon, { severity }),
|
|
1558
|
+
/* @__PURE__ */ React20.createElement(Typography14, { variant: "body2", fontWeight: "bold" }, count),
|
|
1559
|
+
/* @__PURE__ */ React20.createElement(Typography14, { variant: "body2" }, severityPluralLabel(severity))
|
|
1560
|
+
);
|
|
1561
|
+
})
|
|
1562
|
+
));
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
// src/components/report-shell.tsx
|
|
1566
|
+
function activeTab(page) {
|
|
1567
|
+
if (page === "overview") {
|
|
1568
|
+
return "overview";
|
|
1569
|
+
}
|
|
1570
|
+
if (page === "issues") {
|
|
1571
|
+
return "issues";
|
|
1572
|
+
}
|
|
1573
|
+
return page.backTo;
|
|
1574
|
+
}
|
|
1575
|
+
function isAllAuditsPage(page) {
|
|
1576
|
+
return typeof page === "object" && "page" in page && page.page === "all-audits";
|
|
1577
|
+
}
|
|
1578
|
+
function isCategoryPage(page) {
|
|
1579
|
+
return typeof page === "object" && "category" in page;
|
|
1580
|
+
}
|
|
1581
|
+
function ReportShell({ report }) {
|
|
1582
|
+
const [activePage, setActivePage] = useState4("overview");
|
|
1583
|
+
const currentTab = activeTab(activePage);
|
|
1584
|
+
const handleTabChange = (_, value) => {
|
|
1585
|
+
setActivePage(value);
|
|
1586
|
+
};
|
|
1587
|
+
const openCategory = (category, backTo) => {
|
|
1588
|
+
setActivePage({ category, backTo });
|
|
1589
|
+
};
|
|
1590
|
+
const openAllAudits = (expand, backTo = "issues") => {
|
|
1591
|
+
setActivePage({ page: "all-audits", expand, backTo });
|
|
1592
|
+
};
|
|
1593
|
+
const backFromSubPage = () => {
|
|
1594
|
+
if (isAllAuditsPage(activePage) || isCategoryPage(activePage)) {
|
|
1595
|
+
setActivePage(activePage.backTo);
|
|
1596
|
+
}
|
|
1597
|
+
};
|
|
1598
|
+
return /* @__PURE__ */ React21.createElement(Box18, null, /* @__PURE__ */ React21.createElement(
|
|
1599
|
+
Tabs,
|
|
1600
|
+
{
|
|
1601
|
+
"aria-label": __20("Audit navigation", "elementor"),
|
|
1602
|
+
value: currentTab,
|
|
1603
|
+
onChange: handleTabChange,
|
|
1604
|
+
textColor: "secondary",
|
|
1605
|
+
indicatorColor: "secondary",
|
|
1606
|
+
size: "small",
|
|
1607
|
+
centered: true,
|
|
1608
|
+
variant: "fullWidth"
|
|
1609
|
+
},
|
|
1610
|
+
/* @__PURE__ */ React21.createElement(Tab, { value: "overview", label: __20("Overview", "elementor") }),
|
|
1611
|
+
/* @__PURE__ */ React21.createElement(Tab, { value: "issues", label: __20("Issues", "elementor") })
|
|
1612
|
+
), /* @__PURE__ */ React21.createElement(Divider2, null), activePage === "overview" && /* @__PURE__ */ React21.createElement(
|
|
1613
|
+
OverviewPage,
|
|
1614
|
+
{
|
|
1615
|
+
report,
|
|
1616
|
+
onCategoryClick: (category) => openCategory(category, "overview"),
|
|
1617
|
+
onStatusClick: (status) => openAllAudits(status, "overview")
|
|
1618
|
+
}
|
|
1619
|
+
), activePage === "issues" && /* @__PURE__ */ React21.createElement(
|
|
1620
|
+
IssuesPage,
|
|
1621
|
+
{
|
|
1622
|
+
report,
|
|
1623
|
+
onCategoryClick: (category) => openCategory(category, "issues"),
|
|
1624
|
+
onAllAuditsClick: () => openAllAudits()
|
|
1625
|
+
}
|
|
1626
|
+
), isAllAuditsPage(activePage) && /* @__PURE__ */ React21.createElement(
|
|
1627
|
+
AllAuditsPage,
|
|
1628
|
+
{
|
|
1629
|
+
report,
|
|
1630
|
+
initialExpandedStatus: activePage.expand,
|
|
1631
|
+
onBack: backFromSubPage
|
|
1632
|
+
}
|
|
1633
|
+
), isCategoryPage(activePage) && /* @__PURE__ */ React21.createElement(CategoryPage, { category: activePage.category, report, onBack: backFromSubPage }));
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1636
|
+
// src/components/audit-panel.tsx
|
|
1637
|
+
function AuditPanel() {
|
|
1638
|
+
const { status, report, error, run } = useAuditReport();
|
|
1639
|
+
const currentDocumentId = getCurrentDocumentId2() ?? 0;
|
|
1640
|
+
const onRun = () => run(currentDocumentId);
|
|
1641
|
+
const lastScanLabel = report ? new Date(report.runAt).toLocaleTimeString() : null;
|
|
1642
|
+
return /* @__PURE__ */ React22.createElement(React22.Fragment, null, /* @__PURE__ */ React22.createElement(
|
|
1643
|
+
FloatingPanelHeader,
|
|
1644
|
+
{
|
|
1645
|
+
panelId: "audit-panel",
|
|
1646
|
+
title: __21("Page Audit", "elementor"),
|
|
1647
|
+
badge: __21("Beta", "elementor"),
|
|
1648
|
+
titleVariant: "subtitle2"
|
|
1649
|
+
}
|
|
1650
|
+
), /* @__PURE__ */ React22.createElement(FloatingPanelBody, null, status === "idle" && /* @__PURE__ */ React22.createElement(WelcomePage, null), status === "loading" && /* @__PURE__ */ React22.createElement(LoadingPage, null), status === "error" && /* @__PURE__ */ React22.createElement(ErrorPage, { message: error ?? "", onRetry: onRun }), status === "ready" && report && /* @__PURE__ */ React22.createElement(ReportShell, { report })), /* @__PURE__ */ React22.createElement(FloatingPanelFooter, null, lastScanLabel ? /* @__PURE__ */ React22.createElement(Typography15, { variant: "caption", sx: { flex: 1 } }, __21("Last scan:", "elementor"), " ", lastScanLabel) : /* @__PURE__ */ React22.createElement(Box19, { sx: { flex: 1 } }), lastScanLabel && /* @__PURE__ */ React22.createElement(AuditFeedback, null), /* @__PURE__ */ React22.createElement(
|
|
1651
|
+
Button4,
|
|
1652
|
+
{
|
|
1653
|
+
variant: "contained",
|
|
1654
|
+
size: "small",
|
|
1655
|
+
onClick: onRun,
|
|
1656
|
+
disabled: status === "loading" || currentDocumentId === 0
|
|
1657
|
+
},
|
|
1658
|
+
lastScanLabel ? __21("Re-scan", "elementor") : __21("Run page audit", "elementor")
|
|
1659
|
+
)));
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
// src/editor-panel.tsx
|
|
1663
|
+
var auditPanel = createFloatingPanel({
|
|
1664
|
+
id: AUDIT_PANEL_ID,
|
|
1665
|
+
title: "Audit",
|
|
1666
|
+
icon: () => null,
|
|
1667
|
+
component: AuditPanel,
|
|
1668
|
+
isDraggable: true,
|
|
1669
|
+
isResizable: true,
|
|
1670
|
+
defaults: {
|
|
1671
|
+
width: 360,
|
|
1672
|
+
height: 600,
|
|
1673
|
+
minWidth: 280,
|
|
1674
|
+
minHeight: 400
|
|
1675
|
+
}
|
|
1676
|
+
});
|
|
1677
|
+
|
|
1678
|
+
// src/hooks/use-audit-toggle-props.ts
|
|
1679
|
+
function useAuditToggleProps() {
|
|
1680
|
+
const { isOpen } = auditPanel.useFloatingPanelStatus();
|
|
1681
|
+
const { toggle } = auditPanel.useFloatingPanelActions();
|
|
1682
|
+
return {
|
|
1683
|
+
title: __22("Audit Page", "elementor"),
|
|
1684
|
+
icon: ShieldCheckIcon2,
|
|
1685
|
+
selected: isOpen,
|
|
1686
|
+
onClick: () => toggle()
|
|
1687
|
+
};
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1690
|
+
// src/editor-app-bar.tsx
|
|
1691
|
+
var AUDIT_TOGGLE_PRIORITY = 24;
|
|
1692
|
+
function registerAppBarAuditsToggle() {
|
|
1693
|
+
utilitiesMenu.registerToggleAction({
|
|
1694
|
+
id: "toggle-audit-panel",
|
|
1695
|
+
priority: AUDIT_TOGGLE_PRIORITY,
|
|
1696
|
+
useProps: useAuditToggleProps
|
|
1697
|
+
});
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
// src/audits/accessibility-policy.ts
|
|
1701
|
+
import { __ as __23 } from "@wordpress/i18n";
|
|
1702
|
+
var audit = {
|
|
1703
|
+
id: "audits/accessibility-policy",
|
|
1704
|
+
title: __23("Accessibility policy", "elementor"),
|
|
1705
|
+
description: __23(
|
|
1706
|
+
"An accessibility policy demonstrates your commitment to digital inclusion and may be legally required in some regions.",
|
|
1707
|
+
"elementor"
|
|
1708
|
+
),
|
|
1709
|
+
fixHint: __23("Install the Ally plugin to add an accessibility policy to your website.", "elementor"),
|
|
1710
|
+
categories: ["compliance"],
|
|
1711
|
+
severity: "info",
|
|
1712
|
+
weight: 1,
|
|
1713
|
+
evaluate: (ctx) => {
|
|
1714
|
+
if (ctx.pageContext.ally_plugin_active) {
|
|
1715
|
+
return { status: "pass" };
|
|
1716
|
+
}
|
|
1717
|
+
return {
|
|
1718
|
+
status: "fail",
|
|
1719
|
+
violations: [
|
|
1720
|
+
{
|
|
1721
|
+
auditId: audit.id,
|
|
1722
|
+
label: __23("Ally plugin is not installed or active.", "elementor"),
|
|
1723
|
+
externalUrl: ctx.pageContext.ally_plugin_url
|
|
1724
|
+
}
|
|
1725
|
+
]
|
|
1726
|
+
};
|
|
1727
|
+
}
|
|
1728
|
+
};
|
|
1729
|
+
|
|
1730
|
+
// src/audits/cookie-policy.ts
|
|
1731
|
+
import { __ as __24 } from "@wordpress/i18n";
|
|
1732
|
+
var audit2 = {
|
|
1733
|
+
id: "audits/cookie-policy",
|
|
1734
|
+
title: __24("Cookie policy", "elementor"),
|
|
1735
|
+
description: __24(
|
|
1736
|
+
"A cookie policy is required by privacy regulations such as GDPR (EU) or CCPA (US) to inform visitors about tracking and data collection.",
|
|
1737
|
+
"elementor"
|
|
1738
|
+
),
|
|
1739
|
+
fixHint: __24("Install the Cookiez plugin to add a cookie policy to your website.", "elementor"),
|
|
1740
|
+
categories: ["compliance"],
|
|
1741
|
+
severity: "info",
|
|
1742
|
+
weight: 1,
|
|
1743
|
+
evaluate: (ctx) => {
|
|
1744
|
+
if (ctx.pageContext.cookiez_plugin_active) {
|
|
1745
|
+
return { status: "pass" };
|
|
1746
|
+
}
|
|
1747
|
+
return {
|
|
1748
|
+
status: "fail",
|
|
1749
|
+
violations: [
|
|
1750
|
+
{
|
|
1751
|
+
auditId: audit2.id,
|
|
1752
|
+
label: __24("Cookiez plugin is not installed or active.", "elementor"),
|
|
1753
|
+
externalUrl: ctx.pageContext.cookiez_plugin_url
|
|
1754
|
+
}
|
|
1755
|
+
]
|
|
1756
|
+
};
|
|
1757
|
+
}
|
|
1758
|
+
};
|
|
1759
|
+
|
|
1760
|
+
// src/audits/deep-nesting.ts
|
|
1761
|
+
import { __ as __25 } from "@wordpress/i18n";
|
|
1762
|
+
var MAX_NESTING_DEPTH = 6;
|
|
1763
|
+
var audit3 = {
|
|
1764
|
+
id: "audits/deep-nesting",
|
|
1765
|
+
title: __25("Deep container nesting", "elementor"),
|
|
1766
|
+
description: __25(
|
|
1767
|
+
"Container nesting exceeds the recommended depth. Deep DOM trees hurt rendering performance and readability.",
|
|
1768
|
+
"elementor"
|
|
1769
|
+
),
|
|
1770
|
+
fixHint: __25("Flatten the layout by removing unnecessary wrapper containers.", "elementor"),
|
|
1771
|
+
categories: ["performance", "best-practices"],
|
|
1772
|
+
severity: "warning",
|
|
1773
|
+
weight: 5,
|
|
1774
|
+
evaluate: (ctx) => {
|
|
1775
|
+
if (ctx.elements.tree.length === 0) {
|
|
1776
|
+
return { status: "skipped", reason: __25("No elements", "elementor") };
|
|
1777
|
+
}
|
|
1778
|
+
const violations = [];
|
|
1779
|
+
walkElements(ctx.elements.tree, (node, parents) => {
|
|
1780
|
+
if (node.elType !== "container") {
|
|
1781
|
+
return;
|
|
1782
|
+
}
|
|
1783
|
+
const depth = parents.length + 1;
|
|
1784
|
+
if (depth > MAX_NESTING_DEPTH) {
|
|
1785
|
+
violations.push({
|
|
1786
|
+
auditId: audit3.id,
|
|
1787
|
+
elementId: node.id,
|
|
1788
|
+
targetHint: "element-settings",
|
|
1789
|
+
label: __25("Container nesting is too deep.", "elementor")
|
|
1790
|
+
});
|
|
1791
|
+
}
|
|
1792
|
+
});
|
|
1793
|
+
return violations.length === 0 ? { status: "pass" } : { status: "fail", violations };
|
|
1794
|
+
}
|
|
1795
|
+
};
|
|
1796
|
+
|
|
1797
|
+
// src/audits/default-design-system.ts
|
|
1798
|
+
import { __ as __26 } from "@wordpress/i18n";
|
|
1799
|
+
var audit4 = {
|
|
1800
|
+
id: "audits/default-design-system",
|
|
1801
|
+
title: __26("Default website kit", "elementor"),
|
|
1802
|
+
description: __26(
|
|
1803
|
+
"Your website is using the default design system colors and fonts. Custom branding makes the website feel uniquely yours.",
|
|
1804
|
+
"elementor"
|
|
1805
|
+
),
|
|
1806
|
+
fixHint: __26("Open Site Settings and customize your kit (colors, fonts, layout).", "elementor"),
|
|
1807
|
+
categories: ["best-practices"],
|
|
1808
|
+
severity: "info",
|
|
1809
|
+
weight: 1,
|
|
1810
|
+
evaluate: (ctx) => {
|
|
1811
|
+
if (!ctx.pageContext.kit_is_default_unchanged) {
|
|
1812
|
+
return { status: "pass" };
|
|
1813
|
+
}
|
|
1814
|
+
return {
|
|
1815
|
+
status: "fail",
|
|
1816
|
+
violations: [
|
|
1817
|
+
{
|
|
1818
|
+
auditId: audit4.id,
|
|
1819
|
+
label: __26("Kit appears unchanged from default.", "elementor"),
|
|
1820
|
+
targetHint: "site-settings"
|
|
1821
|
+
}
|
|
1822
|
+
]
|
|
1823
|
+
};
|
|
1824
|
+
}
|
|
1825
|
+
};
|
|
1826
|
+
|
|
1827
|
+
// src/audits/deprecated-widgets.ts
|
|
1828
|
+
import { getWidgetsCache } from "@elementor/editor-elements";
|
|
1829
|
+
import { __ as __27 } from "@wordpress/i18n";
|
|
1830
|
+
var DEPRECATION_CONTROL_NAME = "deprecation_message";
|
|
1831
|
+
var audit5 = {
|
|
1832
|
+
id: "audits/deprecated-widgets",
|
|
1833
|
+
title: __27("Deprecated widgets", "elementor"),
|
|
1834
|
+
description: __27(
|
|
1835
|
+
"Deprecated widgets should not be used. For better capabilities use the recommended replacement.",
|
|
1836
|
+
"elementor"
|
|
1837
|
+
),
|
|
1838
|
+
fixHint: __27("Replace each deprecated widget with the recommended new widget shown in its panel.", "elementor"),
|
|
1839
|
+
categories: ["best-practices", "performance"],
|
|
1840
|
+
severity: "warning",
|
|
1841
|
+
weight: 7,
|
|
1842
|
+
evaluate: (ctx) => {
|
|
1843
|
+
const widgetsCache = getWidgetsCache();
|
|
1844
|
+
if (!widgetsCache) {
|
|
1845
|
+
return { status: "skipped", reason: "widgets-cache-unavailable" };
|
|
1846
|
+
}
|
|
1847
|
+
const violations = [];
|
|
1848
|
+
walkElements(ctx.elements.tree, (node) => {
|
|
1849
|
+
if (node.elType !== "widget") {
|
|
1850
|
+
return;
|
|
1851
|
+
}
|
|
1852
|
+
const config = widgetsCache[node.widgetType ?? ""];
|
|
1853
|
+
if (!config?.controls) {
|
|
1854
|
+
return;
|
|
1855
|
+
}
|
|
1856
|
+
const controls = config.controls;
|
|
1857
|
+
const deprecationControl = controls[DEPRECATION_CONTROL_NAME];
|
|
1858
|
+
if (!deprecationControl || typeof deprecationControl !== "object") {
|
|
1859
|
+
return;
|
|
1860
|
+
}
|
|
1861
|
+
violations.push({
|
|
1862
|
+
auditId: audit5.id,
|
|
1863
|
+
elementId: node.id,
|
|
1864
|
+
targetHint: "element-settings",
|
|
1865
|
+
label: __27("Using deprecated widget.", "elementor")
|
|
1866
|
+
});
|
|
1867
|
+
});
|
|
1868
|
+
return violations.length === 0 ? { status: "pass" } : { status: "fail", violations };
|
|
1869
|
+
}
|
|
1870
|
+
};
|
|
1871
|
+
|
|
1872
|
+
// src/audits/heading-structure.ts
|
|
1873
|
+
import { __ as __28 } from "@wordpress/i18n";
|
|
1874
|
+
var HEADING_LEVELS = ["h1", "h2", "h3", "h4", "h5", "h6"];
|
|
1875
|
+
var HEADING_CONTROLS_REGISTRY = {
|
|
1876
|
+
// Core
|
|
1877
|
+
heading: [{ control: "header_size", requiresNonEmpty: "title" }],
|
|
1878
|
+
"e-heading": [{ control: "tag", requiresNonEmpty: "title" }],
|
|
1879
|
+
"icon-box": [{ control: "title_size", requiresNonEmpty: "title_text" }],
|
|
1880
|
+
"image-box": [{ control: "title_size", requiresNonEmpty: "title_text" }],
|
|
1881
|
+
counter: [{ control: "title_tag", requiresNonEmpty: "title" }],
|
|
1882
|
+
progress: [{ control: "title_tag", when: { title_display: "yes" }, requiresNonEmpty: "title" }],
|
|
1883
|
+
accordion: [{ control: "title_html_tag", repeat: { items: "tabs", textField: "tab_title" } }],
|
|
1884
|
+
toggle: [{ control: "title_html_tag", repeat: { items: "tabs", textField: "tab_title" } }],
|
|
1885
|
+
divider: [{ control: "html_tag", when: { look: "line_text" }, requiresNonEmpty: "text" }],
|
|
1886
|
+
"nested-accordion": [{ control: "title_tag", repeat: { items: "items", textField: "item_title" } }],
|
|
1887
|
+
"link-in-bio": [
|
|
1888
|
+
{ control: "bio_heading_tag", requiresNonEmpty: "bio_heading" },
|
|
1889
|
+
{ control: "bio_title_tag", requiresNonEmpty: "bio_title" },
|
|
1890
|
+
{ control: "bio_about_tag", requiresNonEmpty: "bio_about" }
|
|
1891
|
+
],
|
|
1892
|
+
// Pro — static / repeater
|
|
1893
|
+
"animated-headline": [
|
|
1894
|
+
{
|
|
1895
|
+
control: "tag",
|
|
1896
|
+
requiresAnyNonEmpty: ["before_text", "highlighted_text", "rotating_text", "after_text"]
|
|
1897
|
+
}
|
|
1898
|
+
],
|
|
1899
|
+
"author-box": [{ control: "author_name_tag", requiresNonEmpty: "author_name" }],
|
|
1900
|
+
"call-to-action": [
|
|
1901
|
+
{ control: "title_tag", requiresNonEmpty: "title" },
|
|
1902
|
+
{ control: "description_tag", requiresNonEmpty: "description" }
|
|
1903
|
+
],
|
|
1904
|
+
"flip-box": [
|
|
1905
|
+
{ control: "title_tag", requiresNonEmpty: "title_text_a" },
|
|
1906
|
+
{ control: "description_tag", requiresNonEmpty: "description_text_a" },
|
|
1907
|
+
{ control: "title_tag", requiresNonEmpty: "title_text_b" },
|
|
1908
|
+
{ control: "description_tag", requiresNonEmpty: "description_text_b" }
|
|
1909
|
+
],
|
|
1910
|
+
"price-list": [
|
|
1911
|
+
{ control: "title_tag", repeat: { items: "price_list", textField: "title" } },
|
|
1912
|
+
{ control: "description_tag", repeat: { items: "price_list", textField: "item_description" } }
|
|
1913
|
+
],
|
|
1914
|
+
"price-table": [{ control: "heading_tag", requiresNonEmpty: "heading" }],
|
|
1915
|
+
slides: [
|
|
1916
|
+
{ control: "slides_title_tag", repeat: { items: "slides", textField: "heading" } },
|
|
1917
|
+
{ control: "slides_description_tag", repeat: { items: "slides", textField: "description" } }
|
|
1918
|
+
],
|
|
1919
|
+
"table-of-contents": [{ control: "html_tag", requiresNonEmpty: "title" }],
|
|
1920
|
+
"video-playlist": [
|
|
1921
|
+
{ control: "playlist_title_tag", requiresNonEmpty: "playlist_title" },
|
|
1922
|
+
{
|
|
1923
|
+
control: "section_html_tag",
|
|
1924
|
+
repeat: { items: "tabs", tagSource: "item", itemWhen: { type: "section" }, textField: "title" }
|
|
1925
|
+
},
|
|
1926
|
+
{
|
|
1927
|
+
control: "video_html_tag",
|
|
1928
|
+
repeat: {
|
|
1929
|
+
items: "tabs",
|
|
1930
|
+
tagSource: "item",
|
|
1931
|
+
itemWhen: { type: ["youtube", "vimeo", "hosted"] },
|
|
1932
|
+
textField: "title"
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
],
|
|
1936
|
+
sitemap: [{ control: "sitemap_title_tag", repeat: { items: "sitemap_items", textField: "sitemap_title" } }],
|
|
1937
|
+
"loop-grid": [
|
|
1938
|
+
{
|
|
1939
|
+
control: "nothing_found_message_html_tag",
|
|
1940
|
+
when: { enable_nothing_found_message: "yes" },
|
|
1941
|
+
requiresNonEmpty: "nothing_found_message_text"
|
|
1942
|
+
}
|
|
1943
|
+
],
|
|
1944
|
+
search: [
|
|
1945
|
+
{
|
|
1946
|
+
control: "nothing_found_message_html_tag",
|
|
1947
|
+
when: { enable_nothing_found_message: "yes" },
|
|
1948
|
+
requiresNonEmpty: "nothing_found_message_text"
|
|
1949
|
+
}
|
|
1950
|
+
],
|
|
1951
|
+
// Pro — theme / Woo
|
|
1952
|
+
posts: [{ control: "title_tag", when: { show_title: "yes" } }],
|
|
1953
|
+
"archive-posts": [{ control: "title_tag", when: { show_title: "yes" } }],
|
|
1954
|
+
portfolio: [{ control: "title_tag", when: { show_title: "yes" } }],
|
|
1955
|
+
"theme-post-title": [{ control: "header_size" }],
|
|
1956
|
+
"theme-page-title": [{ control: "header_size" }],
|
|
1957
|
+
"theme-archive-title": [{ control: "header_size" }],
|
|
1958
|
+
"theme-site-title": [{ control: "header_size" }],
|
|
1959
|
+
"woocommerce-product-title": [{ control: "header_size" }]
|
|
1960
|
+
};
|
|
1961
|
+
function isNonEmptyString(value) {
|
|
1962
|
+
return typeof value === "string" && value.trim() !== "";
|
|
1963
|
+
}
|
|
1964
|
+
function matchesWhen(settings, when) {
|
|
1965
|
+
if (!when) {
|
|
1966
|
+
return true;
|
|
1967
|
+
}
|
|
1968
|
+
return Object.entries(when).every(([key, expected]) => {
|
|
1969
|
+
const actual = settings[key];
|
|
1970
|
+
if (Array.isArray(expected)) {
|
|
1971
|
+
return expected.includes(String(actual));
|
|
1972
|
+
}
|
|
1973
|
+
return String(actual) === expected;
|
|
1974
|
+
});
|
|
1975
|
+
}
|
|
1976
|
+
function hasRequiredText(settings, key) {
|
|
1977
|
+
if (!key) {
|
|
1978
|
+
return true;
|
|
1979
|
+
}
|
|
1980
|
+
return isNonEmptyString(settings[key]);
|
|
1981
|
+
}
|
|
1982
|
+
function hasAnyRequiredText(settings, keys) {
|
|
1983
|
+
if (!keys?.length) {
|
|
1984
|
+
return true;
|
|
1985
|
+
}
|
|
1986
|
+
return keys.some((key) => isNonEmptyString(settings[key]));
|
|
1987
|
+
}
|
|
1988
|
+
function parseHeadingLevel(raw) {
|
|
1989
|
+
if (typeof raw !== "string") {
|
|
1990
|
+
return null;
|
|
1991
|
+
}
|
|
1992
|
+
const idx = HEADING_LEVELS.indexOf(raw.toLowerCase());
|
|
1993
|
+
return idx >= 0 ? idx + 1 : null;
|
|
1994
|
+
}
|
|
1995
|
+
function getRepeaterRows(settings, key) {
|
|
1996
|
+
const rows = settings[key];
|
|
1997
|
+
return Array.isArray(rows) ? rows : [];
|
|
1998
|
+
}
|
|
1999
|
+
function pushHeading(headings, elementId, raw) {
|
|
2000
|
+
const level = parseHeadingLevel(raw);
|
|
2001
|
+
if (level !== null) {
|
|
2002
|
+
headings.push({ elementId, level });
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
2005
|
+
function extractHeadingsFromWidget(elementId, widgetType, settings) {
|
|
2006
|
+
const descriptors = HEADING_CONTROLS_REGISTRY[widgetType];
|
|
2007
|
+
if (!descriptors) {
|
|
2008
|
+
return [];
|
|
2009
|
+
}
|
|
2010
|
+
const headings = [];
|
|
2011
|
+
for (const descriptor of descriptors) {
|
|
2012
|
+
if (!matchesWhen(settings, descriptor.when)) {
|
|
2013
|
+
continue;
|
|
2014
|
+
}
|
|
2015
|
+
if (!hasRequiredText(settings, descriptor.requiresNonEmpty)) {
|
|
2016
|
+
continue;
|
|
2017
|
+
}
|
|
2018
|
+
if (!hasAnyRequiredText(settings, descriptor.requiresAnyNonEmpty)) {
|
|
2019
|
+
continue;
|
|
2020
|
+
}
|
|
2021
|
+
if (descriptor.repeat) {
|
|
2022
|
+
const { items, textField, itemWhen, tagSource = "widget" } = descriptor.repeat;
|
|
2023
|
+
for (const row of getRepeaterRows(settings, items)) {
|
|
2024
|
+
if (!matchesWhen(row, itemWhen)) {
|
|
2025
|
+
continue;
|
|
2026
|
+
}
|
|
2027
|
+
if (textField && !hasRequiredText(row, textField)) {
|
|
2028
|
+
continue;
|
|
2029
|
+
}
|
|
2030
|
+
const raw = tagSource === "item" ? row[descriptor.control] : settings[descriptor.control];
|
|
2031
|
+
pushHeading(headings, elementId, raw);
|
|
2032
|
+
}
|
|
2033
|
+
} else {
|
|
2034
|
+
pushHeading(headings, elementId, settings[descriptor.control]);
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
2037
|
+
return headings;
|
|
2038
|
+
}
|
|
2039
|
+
function extractPageHeadings(tree) {
|
|
2040
|
+
const headings = [];
|
|
2041
|
+
walkElements(tree, (node) => {
|
|
2042
|
+
if (node.elType !== "widget" || !node.widgetType) {
|
|
2043
|
+
return;
|
|
2044
|
+
}
|
|
2045
|
+
headings.push(...extractHeadingsFromWidget(node.id, node.widgetType, node.settings));
|
|
2046
|
+
});
|
|
2047
|
+
return headings;
|
|
2048
|
+
}
|
|
2049
|
+
var audit6 = {
|
|
2050
|
+
id: "audits/heading-structure",
|
|
2051
|
+
title: __28("Heading structure", "elementor"),
|
|
2052
|
+
description: __28("Pages should have exactly one H1 and a non-skipping heading order.", "elementor"),
|
|
2053
|
+
fixHint: __28("Ensure your page has one H1 and that heading levels do not skip (no H2 \u2192 H4).", "elementor"),
|
|
2054
|
+
categories: ["seo", "accessibility"],
|
|
2055
|
+
severity: "error",
|
|
2056
|
+
weight: 10,
|
|
2057
|
+
evaluate: (ctx) => {
|
|
2058
|
+
if (ctx.elements.tree.length === 0) {
|
|
2059
|
+
return { status: "skipped", reason: __28("No elements", "elementor") };
|
|
2060
|
+
}
|
|
2061
|
+
const headings = extractPageHeadings(ctx.elements.tree);
|
|
2062
|
+
const violations = [];
|
|
2063
|
+
if (headings.length === 0) {
|
|
2064
|
+
violations.push({
|
|
2065
|
+
auditId: audit6.id,
|
|
2066
|
+
label: __28("No headings found on the page.", "elementor")
|
|
2067
|
+
});
|
|
2068
|
+
} else {
|
|
2069
|
+
const h1Count = headings.filter((h) => h.level === 1).length;
|
|
2070
|
+
if (h1Count === 0) {
|
|
2071
|
+
violations.push({ auditId: audit6.id, label: __28("No H1 on the page.", "elementor") });
|
|
2072
|
+
}
|
|
2073
|
+
if (h1Count > 1) {
|
|
2074
|
+
headings.filter((h) => h.level === 1).slice(1).forEach(
|
|
2075
|
+
(h) => violations.push({
|
|
2076
|
+
auditId: audit6.id,
|
|
2077
|
+
elementId: h.elementId,
|
|
2078
|
+
targetHint: "element-settings",
|
|
2079
|
+
label: __28("More than one H1 on the page.", "elementor")
|
|
2080
|
+
})
|
|
2081
|
+
);
|
|
2082
|
+
}
|
|
2083
|
+
for (let i = 1; i < headings.length; i++) {
|
|
2084
|
+
if (headings[i].level - headings[i - 1].level > 1) {
|
|
2085
|
+
violations.push({
|
|
2086
|
+
auditId: audit6.id,
|
|
2087
|
+
elementId: headings[i].elementId,
|
|
2088
|
+
targetHint: "element-settings",
|
|
2089
|
+
label: __28("Heading level skipped.", "elementor")
|
|
2090
|
+
});
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
return violations.length === 0 ? { status: "pass" } : { status: "fail", violations };
|
|
2095
|
+
}
|
|
2096
|
+
};
|
|
2097
|
+
|
|
2098
|
+
// src/audits/hidden-elements.ts
|
|
2099
|
+
import { __ as __29 } from "@wordpress/i18n";
|
|
2100
|
+
function isHiddenOnAllDevices(settings) {
|
|
2101
|
+
return Boolean(settings.hide_desktop) && Boolean(settings.hide_tablet) && Boolean(settings.hide_mobile);
|
|
2102
|
+
}
|
|
2103
|
+
var audit7 = {
|
|
2104
|
+
id: "audits/hidden-elements",
|
|
2105
|
+
title: __29("Hidden elements", "elementor"),
|
|
2106
|
+
description: __29(
|
|
2107
|
+
"Elements hidden on all devices simultaneously are effectively dead code in the page tree.",
|
|
2108
|
+
"elementor"
|
|
2109
|
+
),
|
|
2110
|
+
fixHint: __29("Remove the element or make it visible on at least one device breakpoint.", "elementor"),
|
|
2111
|
+
categories: ["best-practices", "performance"],
|
|
2112
|
+
severity: "info",
|
|
2113
|
+
weight: 3,
|
|
2114
|
+
evaluate: (ctx) => {
|
|
2115
|
+
if (ctx.elements.tree.length === 0) {
|
|
2116
|
+
return { status: "skipped", reason: __29("No elements", "elementor") };
|
|
2117
|
+
}
|
|
2118
|
+
const violations = [];
|
|
2119
|
+
walkElements(ctx.elements.tree, (node) => {
|
|
2120
|
+
if (isHiddenOnAllDevices(node.settings)) {
|
|
2121
|
+
violations.push({
|
|
2122
|
+
auditId: audit7.id,
|
|
2123
|
+
elementId: node.id,
|
|
2124
|
+
targetHint: "element-settings",
|
|
2125
|
+
label: __29("Element is hidden on all devices.", "elementor")
|
|
2126
|
+
});
|
|
2127
|
+
}
|
|
2128
|
+
});
|
|
2129
|
+
return violations.length === 0 ? { status: "pass" } : { status: "fail", violations };
|
|
2130
|
+
}
|
|
2131
|
+
};
|
|
2132
|
+
|
|
2133
|
+
// src/audits/images-alt-text.ts
|
|
2134
|
+
import { __ as __30 } from "@wordpress/i18n";
|
|
2135
|
+
|
|
2136
|
+
// src/utils/image-alt.ts
|
|
2137
|
+
function hasMeaningfulAlt(media, pageContext) {
|
|
2138
|
+
if (!media.id && !media.url) {
|
|
2139
|
+
return true;
|
|
2140
|
+
}
|
|
2141
|
+
if (media.id) {
|
|
2142
|
+
const alt = pageContext.image_sizes[media.id]?.alt ?? "";
|
|
2143
|
+
return alt.trim().length > 0;
|
|
2144
|
+
}
|
|
2145
|
+
return (media.alt ?? "").trim().length > 0;
|
|
2146
|
+
}
|
|
2147
|
+
function isImageSourcePresent(media) {
|
|
2148
|
+
return Boolean(media.id || media.url);
|
|
2149
|
+
}
|
|
2150
|
+
|
|
2151
|
+
// src/audits/images-alt-text.ts
|
|
2152
|
+
var audit8 = {
|
|
2153
|
+
id: "audits/images-alt-text",
|
|
2154
|
+
title: __30("Images alt text", "elementor"),
|
|
2155
|
+
description: __30("Every image needs a meaningful alt attribute for screen readers and image-search.", "elementor"),
|
|
2156
|
+
fixHint: __30("Open the image's settings and add an Alt Text describing the image.", "elementor"),
|
|
2157
|
+
categories: ["seo", "accessibility"],
|
|
2158
|
+
severity: "error",
|
|
2159
|
+
weight: 10,
|
|
2160
|
+
evaluate: (ctx) => {
|
|
2161
|
+
if (!hasPageImages(ctx.elements.tree)) {
|
|
2162
|
+
return { status: "skipped", reason: __30("No images", "elementor") };
|
|
2163
|
+
}
|
|
2164
|
+
const violations = [];
|
|
2165
|
+
const failedWidgetIds = /* @__PURE__ */ new Set();
|
|
2166
|
+
let missingAltImageCount = 0;
|
|
2167
|
+
walkImageLikeSources(ctx.elements.tree, ({ node, media }) => {
|
|
2168
|
+
if (!isImageSourcePresent(media) || hasMeaningfulAlt(media, ctx.pageContext)) {
|
|
2169
|
+
return;
|
|
2170
|
+
}
|
|
2171
|
+
missingAltImageCount++;
|
|
2172
|
+
if (!failedWidgetIds.has(node.id)) {
|
|
2173
|
+
failedWidgetIds.add(node.id);
|
|
2174
|
+
violations.push({
|
|
2175
|
+
auditId: audit8.id,
|
|
2176
|
+
elementId: node.id,
|
|
2177
|
+
targetHint: "element-settings",
|
|
2178
|
+
label: __30("Image is missing alt text.", "elementor"),
|
|
2179
|
+
externalUrl: ctx.pageContext.ally_plugin_url
|
|
2180
|
+
});
|
|
2181
|
+
}
|
|
2182
|
+
});
|
|
2183
|
+
return violations.length === 0 ? { status: "pass" } : { status: "fail", violations, metadata: { missingAltImageCount } };
|
|
2184
|
+
}
|
|
2185
|
+
};
|
|
2186
|
+
|
|
2187
|
+
// src/audits/images-too-large.ts
|
|
2188
|
+
import { __ as __31, sprintf as sprintf5 } from "@wordpress/i18n";
|
|
2189
|
+
var SIZE_THRESHOLD_BYTES = 500 * 1024;
|
|
2190
|
+
var BYTES_PER_KB = 1024;
|
|
2191
|
+
var audit9 = {
|
|
2192
|
+
id: "audits/images-too-large",
|
|
2193
|
+
title: __31("Oversized images", "elementor"),
|
|
2194
|
+
description: __31("Large image files slow down the page.", "elementor"),
|
|
2195
|
+
fixHint: __31("Replace the image with a smaller version or enable image optimization.", "elementor"),
|
|
2196
|
+
categories: ["performance"],
|
|
2197
|
+
severity: "warning",
|
|
2198
|
+
weight: 7,
|
|
2199
|
+
evaluate: (ctx) => {
|
|
2200
|
+
if (!hasPageImages(ctx.elements.tree)) {
|
|
2201
|
+
return { status: "skipped", reason: __31("No images", "elementor") };
|
|
2202
|
+
}
|
|
2203
|
+
const widgetMaxKb = /* @__PURE__ */ new Map();
|
|
2204
|
+
let oversizedImageCount = 0;
|
|
2205
|
+
walkImageLikeSources(ctx.elements.tree, ({ node, media }) => {
|
|
2206
|
+
const id = media.id;
|
|
2207
|
+
if (!id) {
|
|
2208
|
+
return;
|
|
2209
|
+
}
|
|
2210
|
+
const size = ctx.pageContext.image_sizes[id];
|
|
2211
|
+
if (!size || size.filesize_bytes <= SIZE_THRESHOLD_BYTES) {
|
|
2212
|
+
return;
|
|
2213
|
+
}
|
|
2214
|
+
oversizedImageCount++;
|
|
2215
|
+
const kb = Math.round(size.filesize_bytes / BYTES_PER_KB);
|
|
2216
|
+
const currentMax = widgetMaxKb.get(node.id) ?? 0;
|
|
2217
|
+
widgetMaxKb.set(node.id, Math.max(currentMax, kb));
|
|
2218
|
+
});
|
|
2219
|
+
const violations = Array.from(widgetMaxKb.entries()).map(([elementId, kb]) => ({
|
|
2220
|
+
auditId: audit9.id,
|
|
2221
|
+
elementId,
|
|
2222
|
+
targetHint: "element-settings",
|
|
2223
|
+
label: sprintf5(
|
|
2224
|
+
/* translators: %d is the image file size in kilobytes. */
|
|
2225
|
+
__31("Image is %d KB (over 500 KB).", "elementor"),
|
|
2226
|
+
kb
|
|
2227
|
+
),
|
|
2228
|
+
externalUrl: ctx.pageContext.image_optimization_plugin_url
|
|
2229
|
+
}));
|
|
2230
|
+
return violations.length === 0 ? { status: "pass" } : { status: "fail", violations, metadata: { oversizedImageCount } };
|
|
2231
|
+
}
|
|
2232
|
+
};
|
|
2233
|
+
|
|
2234
|
+
// src/audits/nested-boxed-containers.ts
|
|
2235
|
+
import { __ as __32 } from "@wordpress/i18n";
|
|
2236
|
+
function isBoxed(settings) {
|
|
2237
|
+
return settings.content_width === "boxed";
|
|
2238
|
+
}
|
|
2239
|
+
var audit10 = {
|
|
2240
|
+
id: "audits/nested-boxed-containers",
|
|
2241
|
+
title: __32("Boxed container nested inside a boxed parent", "elementor"),
|
|
2242
|
+
description: __32("An inner container does not need to be boxed when its parent already is.", "elementor"),
|
|
2243
|
+
fixHint: __32("Change the inner container's content width to Full Width.", "elementor"),
|
|
2244
|
+
categories: ["best-practices", "performance"],
|
|
2245
|
+
severity: "info",
|
|
2246
|
+
weight: 5,
|
|
2247
|
+
evaluate: (ctx) => {
|
|
2248
|
+
if (ctx.elements.tree.length === 0) {
|
|
2249
|
+
return { status: "skipped", reason: __32("No elements", "elementor") };
|
|
2250
|
+
}
|
|
2251
|
+
const violations = [];
|
|
2252
|
+
walkElements(ctx.elements.tree, (node, parents) => {
|
|
2253
|
+
if (node.elType !== "container" || !isBoxed(node.settings)) {
|
|
2254
|
+
return;
|
|
2255
|
+
}
|
|
2256
|
+
const nearestContainerAncestor = [...parents].reverse().find((p) => p.elType === "container");
|
|
2257
|
+
if (nearestContainerAncestor && isBoxed(nearestContainerAncestor.settings)) {
|
|
2258
|
+
violations.push({
|
|
2259
|
+
auditId: audit10.id,
|
|
2260
|
+
elementId: node.id,
|
|
2261
|
+
targetHint: "element-settings",
|
|
2262
|
+
label: __32("Nested boxed container.", "elementor")
|
|
2263
|
+
});
|
|
2264
|
+
}
|
|
2265
|
+
});
|
|
2266
|
+
return violations.length === 0 ? { status: "pass" } : { status: "fail", violations };
|
|
2267
|
+
}
|
|
2268
|
+
};
|
|
2269
|
+
|
|
2270
|
+
// src/audits/page-excerpt.ts
|
|
2271
|
+
import { __ as __33 } from "@wordpress/i18n";
|
|
2272
|
+
var audit11 = {
|
|
2273
|
+
id: "audits/page-excerpt",
|
|
2274
|
+
title: __33("Page excerpt", "elementor"),
|
|
2275
|
+
description: __33("A descriptive excerpt helps search engines and previews summarize the page.", "elementor"),
|
|
2276
|
+
fixHint: __33("Open Page Settings and write a short excerpt.", "elementor"),
|
|
2277
|
+
categories: ["seo"],
|
|
2278
|
+
severity: "warning",
|
|
2279
|
+
weight: 5,
|
|
2280
|
+
evaluate: (ctx) => {
|
|
2281
|
+
if (ctx.pageContext.post_excerpt) {
|
|
2282
|
+
return { status: "pass" };
|
|
2283
|
+
}
|
|
2284
|
+
return {
|
|
2285
|
+
status: "fail",
|
|
2286
|
+
violations: [
|
|
2287
|
+
{
|
|
2288
|
+
auditId: audit11.id,
|
|
2289
|
+
label: __33("Page has no excerpt.", "elementor"),
|
|
2290
|
+
targetHint: "page-settings",
|
|
2291
|
+
angieFix: true
|
|
2292
|
+
}
|
|
2293
|
+
]
|
|
2294
|
+
};
|
|
2295
|
+
}
|
|
2296
|
+
};
|
|
2297
|
+
|
|
2298
|
+
// src/audits/page-featured-image.ts
|
|
2299
|
+
import { __ as __34 } from "@wordpress/i18n";
|
|
2300
|
+
var audit12 = {
|
|
2301
|
+
id: "audits/page-featured-image",
|
|
2302
|
+
title: __34("Page featured image", "elementor"),
|
|
2303
|
+
description: __34("Featured images are used by social shares and many themes for hero visuals.", "elementor"),
|
|
2304
|
+
fixHint: __34("Open Page Settings and set a featured image.", "elementor"),
|
|
2305
|
+
categories: ["seo"],
|
|
2306
|
+
severity: "info",
|
|
2307
|
+
weight: 5,
|
|
2308
|
+
evaluate: (ctx) => {
|
|
2309
|
+
if (ctx.pageContext.featured_image_id) {
|
|
2310
|
+
return { status: "pass" };
|
|
2311
|
+
}
|
|
2312
|
+
return {
|
|
2313
|
+
status: "fail",
|
|
2314
|
+
violations: [
|
|
2315
|
+
{
|
|
2316
|
+
auditId: audit12.id,
|
|
2317
|
+
label: __34("No featured image set.", "elementor"),
|
|
2318
|
+
targetHint: "page-settings"
|
|
2319
|
+
}
|
|
2320
|
+
]
|
|
2321
|
+
};
|
|
2322
|
+
}
|
|
2323
|
+
};
|
|
2324
|
+
|
|
2325
|
+
// src/audits/page-title.ts
|
|
2326
|
+
import { __ as __35 } from "@wordpress/i18n";
|
|
2327
|
+
var MAX_TITLE_LENGTH = 60;
|
|
2328
|
+
var audit13 = {
|
|
2329
|
+
id: "audits/page-title",
|
|
2330
|
+
title: __35("Page title", "elementor"),
|
|
2331
|
+
description: __35("Pages need a clear title for SEO and screen-reader navigation.", "elementor"),
|
|
2332
|
+
fixHint: __35("Open Page Settings and add a title.", "elementor"),
|
|
2333
|
+
categories: ["seo"],
|
|
2334
|
+
severity: "error",
|
|
2335
|
+
weight: 10,
|
|
2336
|
+
evaluate: (ctx) => {
|
|
2337
|
+
const title = ctx.pageContext.post_title;
|
|
2338
|
+
if (!title) {
|
|
2339
|
+
return {
|
|
2340
|
+
status: "fail",
|
|
2341
|
+
violations: [
|
|
2342
|
+
{
|
|
2343
|
+
auditId: audit13.id,
|
|
2344
|
+
label: __35("Page has no title.", "elementor"),
|
|
2345
|
+
targetHint: "page-settings",
|
|
2346
|
+
angieFix: true
|
|
2347
|
+
}
|
|
2348
|
+
]
|
|
2349
|
+
};
|
|
2350
|
+
}
|
|
2351
|
+
if (title.length > MAX_TITLE_LENGTH) {
|
|
2352
|
+
return {
|
|
2353
|
+
status: "fail",
|
|
2354
|
+
violations: [
|
|
2355
|
+
{
|
|
2356
|
+
auditId: audit13.id,
|
|
2357
|
+
label: __35("Page title is too long.", "elementor"),
|
|
2358
|
+
targetHint: "page-settings",
|
|
2359
|
+
angieFix: true
|
|
2360
|
+
}
|
|
2361
|
+
]
|
|
2362
|
+
};
|
|
2363
|
+
}
|
|
2364
|
+
return { status: "pass" };
|
|
2365
|
+
}
|
|
2366
|
+
};
|
|
2367
|
+
|
|
2368
|
+
// src/audits/prefer-global-colors.ts
|
|
2369
|
+
import { __ as __36, sprintf as sprintf6 } from "@wordpress/i18n";
|
|
2370
|
+
|
|
2371
|
+
// src/utils/collect-hardcoded-colors.ts
|
|
2372
|
+
var RESERVED_SETTING_KEYS = /* @__PURE__ */ new Set(["__globals__", "__dynamic__"]);
|
|
2373
|
+
var REPEATER_ROW_ID_KEY = "_id";
|
|
2374
|
+
var HEX_COLOR_PATTERN = /^#[0-9a-f]{3,8}$/i;
|
|
2375
|
+
var RGB_COLOR_PATTERN = /^rgba?\(/i;
|
|
2376
|
+
function isGlobalColorReference(value) {
|
|
2377
|
+
return value.startsWith("globals/");
|
|
2378
|
+
}
|
|
2379
|
+
function isHardcodedColorCandidate(value) {
|
|
2380
|
+
const trimmed = value.trim();
|
|
2381
|
+
return HEX_COLOR_PATTERN.test(trimmed) || RGB_COLOR_PATTERN.test(trimmed);
|
|
2382
|
+
}
|
|
2383
|
+
function formatPath(prefix, key) {
|
|
2384
|
+
return prefix ? `${prefix}.${key}` : key;
|
|
2385
|
+
}
|
|
2386
|
+
function formatArrayPath(prefix, index) {
|
|
2387
|
+
return `${prefix}[${index}]`;
|
|
2388
|
+
}
|
|
2389
|
+
function collectHardcodedColors(settings, pathPrefix = "") {
|
|
2390
|
+
const linkedGlobals = settings.__globals__ ?? {};
|
|
2391
|
+
const results = [];
|
|
2392
|
+
for (const [key, value] of Object.entries(settings)) {
|
|
2393
|
+
if (RESERVED_SETTING_KEYS.has(key) || key === REPEATER_ROW_ID_KEY) {
|
|
2394
|
+
continue;
|
|
2395
|
+
}
|
|
2396
|
+
if (linkedGlobals[key]) {
|
|
2397
|
+
continue;
|
|
2398
|
+
}
|
|
2399
|
+
if (typeof value === "string") {
|
|
2400
|
+
if (!isGlobalColorReference(value) && isHardcodedColorCandidate(value)) {
|
|
2401
|
+
results.push({ path: formatPath(pathPrefix, key), value });
|
|
2402
|
+
}
|
|
2403
|
+
continue;
|
|
2404
|
+
}
|
|
2405
|
+
if (Array.isArray(value)) {
|
|
2406
|
+
const arrayPath = formatPath(pathPrefix, key);
|
|
2407
|
+
value.forEach((item, index) => {
|
|
2408
|
+
if (item && typeof item === "object" && !Array.isArray(item)) {
|
|
2409
|
+
results.push(
|
|
2410
|
+
...collectHardcodedColors(
|
|
2411
|
+
item,
|
|
2412
|
+
formatArrayPath(arrayPath, index)
|
|
2413
|
+
)
|
|
2414
|
+
);
|
|
2415
|
+
}
|
|
2416
|
+
});
|
|
2417
|
+
continue;
|
|
2418
|
+
}
|
|
2419
|
+
if (value && typeof value === "object") {
|
|
2420
|
+
results.push(
|
|
2421
|
+
...collectHardcodedColors(value, formatPath(pathPrefix, key))
|
|
2422
|
+
);
|
|
2423
|
+
}
|
|
2424
|
+
}
|
|
2425
|
+
return results;
|
|
2426
|
+
}
|
|
2427
|
+
|
|
2428
|
+
// src/utils/match-global-by-value.ts
|
|
2429
|
+
function findMatchingGlobalByValue(value, globals) {
|
|
2430
|
+
const trimmed = value.trim();
|
|
2431
|
+
return globals.find((global) => global.value === trimmed) ?? null;
|
|
2432
|
+
}
|
|
2433
|
+
|
|
2434
|
+
// src/audits/prefer-global-colors.ts
|
|
2435
|
+
var audit14 = {
|
|
2436
|
+
id: "audits/prefer-global-colors",
|
|
2437
|
+
title: __36("Prefer global colors", "elementor"),
|
|
2438
|
+
description: __36("Global colors make the design consistent and easy to update site-wide.", "elementor"),
|
|
2439
|
+
fixHint: __36("Replace the hard-coded value with the matching global color.", "elementor"),
|
|
2440
|
+
categories: ["best-practices"],
|
|
2441
|
+
severity: "info",
|
|
2442
|
+
weight: 3,
|
|
2443
|
+
evaluate: (ctx) => {
|
|
2444
|
+
if (ctx.elements.tree.length === 0) {
|
|
2445
|
+
return { status: "skipped", reason: __36("No elements", "elementor") };
|
|
2446
|
+
}
|
|
2447
|
+
if (ctx.kit.globals.colors.length === 0) {
|
|
2448
|
+
return { status: "skipped", reason: __36("No global colors", "elementor") };
|
|
2449
|
+
}
|
|
2450
|
+
const violations = [];
|
|
2451
|
+
walkElements(ctx.elements.tree, (node) => {
|
|
2452
|
+
for (const { value } of collectHardcodedColors(node.settings)) {
|
|
2453
|
+
const global = findMatchingGlobalByValue(value, ctx.kit.globals.colors);
|
|
2454
|
+
if (!global) {
|
|
2455
|
+
continue;
|
|
2456
|
+
}
|
|
2457
|
+
violations.push({
|
|
2458
|
+
auditId: audit14.id,
|
|
2459
|
+
elementId: node.id,
|
|
2460
|
+
targetHint: "element-settings",
|
|
2461
|
+
angieFix: true,
|
|
2462
|
+
label: sprintf6(
|
|
2463
|
+
/* translators: 1: hard-coded color value, 2: global color title. */
|
|
2464
|
+
__36('replace "%1$s" with "%2$s" global color', "elementor"),
|
|
2465
|
+
value,
|
|
2466
|
+
global.title
|
|
2467
|
+
)
|
|
2468
|
+
});
|
|
2469
|
+
}
|
|
2470
|
+
});
|
|
2471
|
+
return violations.length === 0 ? { status: "pass" } : { status: "fail", violations };
|
|
2472
|
+
}
|
|
2473
|
+
};
|
|
2474
|
+
|
|
2475
|
+
// src/audits/prefer-global-fonts.ts
|
|
2476
|
+
import { __ as __37, sprintf as sprintf7 } from "@wordpress/i18n";
|
|
2477
|
+
|
|
2478
|
+
// src/utils/collect-hardcoded-fonts.ts
|
|
2479
|
+
var RESERVED_SETTING_KEYS2 = /* @__PURE__ */ new Set(["__globals__", "__dynamic__"]);
|
|
2480
|
+
var REPEATER_ROW_ID_KEY2 = "_id";
|
|
2481
|
+
var FONT_FAMILY_KEY_PATTERN = /font_family/;
|
|
2482
|
+
function isGlobalFontReference(value) {
|
|
2483
|
+
return value.startsWith("globals/");
|
|
2484
|
+
}
|
|
2485
|
+
function isHardcodedFontCandidate(key, value) {
|
|
2486
|
+
return FONT_FAMILY_KEY_PATTERN.test(key) && value.trim().length > 0;
|
|
2487
|
+
}
|
|
2488
|
+
function formatPath2(prefix, key) {
|
|
2489
|
+
return prefix ? `${prefix}.${key}` : key;
|
|
2490
|
+
}
|
|
2491
|
+
function formatArrayPath2(prefix, index) {
|
|
2492
|
+
return `${prefix}[${index}]`;
|
|
2493
|
+
}
|
|
2494
|
+
function collectHardcodedFonts(settings, pathPrefix = "") {
|
|
2495
|
+
const linkedGlobals = settings.__globals__ ?? {};
|
|
2496
|
+
const results = [];
|
|
2497
|
+
for (const [key, value] of Object.entries(settings)) {
|
|
2498
|
+
if (RESERVED_SETTING_KEYS2.has(key) || key === REPEATER_ROW_ID_KEY2) {
|
|
2499
|
+
continue;
|
|
2500
|
+
}
|
|
2501
|
+
if (linkedGlobals[key]) {
|
|
2502
|
+
continue;
|
|
2503
|
+
}
|
|
2504
|
+
if (typeof value === "string") {
|
|
2505
|
+
if (!isGlobalFontReference(value) && isHardcodedFontCandidate(key, value)) {
|
|
2506
|
+
results.push({ path: formatPath2(pathPrefix, key), value });
|
|
2507
|
+
}
|
|
2508
|
+
continue;
|
|
2509
|
+
}
|
|
2510
|
+
if (Array.isArray(value)) {
|
|
2511
|
+
const arrayPath = formatPath2(pathPrefix, key);
|
|
2512
|
+
value.forEach((item, index) => {
|
|
2513
|
+
if (item && typeof item === "object" && !Array.isArray(item)) {
|
|
2514
|
+
results.push(
|
|
2515
|
+
...collectHardcodedFonts(
|
|
2516
|
+
item,
|
|
2517
|
+
formatArrayPath2(arrayPath, index)
|
|
2518
|
+
)
|
|
2519
|
+
);
|
|
2520
|
+
}
|
|
2521
|
+
});
|
|
2522
|
+
continue;
|
|
2523
|
+
}
|
|
2524
|
+
if (value && typeof value === "object") {
|
|
2525
|
+
results.push(
|
|
2526
|
+
...collectHardcodedFonts(value, formatPath2(pathPrefix, key))
|
|
2527
|
+
);
|
|
2528
|
+
}
|
|
2529
|
+
}
|
|
2530
|
+
return results;
|
|
2531
|
+
}
|
|
2532
|
+
|
|
2533
|
+
// src/audits/prefer-global-fonts.ts
|
|
2534
|
+
var audit15 = {
|
|
2535
|
+
id: "audits/prefer-global-fonts",
|
|
2536
|
+
title: __37("Prefer global fonts", "elementor"),
|
|
2537
|
+
description: __37("Global fonts make the design consistent and easy to update site-wide.", "elementor"),
|
|
2538
|
+
fixHint: __37("Replace the hard-coded value with the matching global font.", "elementor"),
|
|
2539
|
+
categories: ["best-practices", "performance"],
|
|
2540
|
+
severity: "info",
|
|
2541
|
+
weight: 3,
|
|
2542
|
+
evaluate: (ctx) => {
|
|
2543
|
+
if (ctx.elements.tree.length === 0) {
|
|
2544
|
+
return { status: "skipped", reason: __37("No elements", "elementor") };
|
|
2545
|
+
}
|
|
2546
|
+
if (ctx.kit.globals.fonts.length === 0) {
|
|
2547
|
+
return { status: "skipped", reason: __37("No global fonts", "elementor") };
|
|
2548
|
+
}
|
|
2549
|
+
const violations = [];
|
|
2550
|
+
walkElements(ctx.elements.tree, (node) => {
|
|
2551
|
+
for (const { value } of collectHardcodedFonts(node.settings)) {
|
|
2552
|
+
const global = findMatchingGlobalByValue(value, ctx.kit.globals.fonts);
|
|
2553
|
+
if (!global) {
|
|
2554
|
+
continue;
|
|
2555
|
+
}
|
|
2556
|
+
violations.push({
|
|
2557
|
+
auditId: audit15.id,
|
|
2558
|
+
elementId: node.id,
|
|
2559
|
+
targetHint: "element-settings",
|
|
2560
|
+
label: sprintf7(
|
|
2561
|
+
/* translators: %s: global font title. */
|
|
2562
|
+
__37('replace hardcoded typography with "%s" global font', "elementor"),
|
|
2563
|
+
global.title
|
|
2564
|
+
)
|
|
2565
|
+
});
|
|
2566
|
+
}
|
|
2567
|
+
});
|
|
2568
|
+
return violations.length === 0 ? { status: "pass" } : { status: "fail", violations };
|
|
2569
|
+
}
|
|
2570
|
+
};
|
|
2571
|
+
|
|
2572
|
+
// src/audits/privacy-policy.ts
|
|
2573
|
+
import { __ as __38 } from "@wordpress/i18n";
|
|
2574
|
+
var audit16 = {
|
|
2575
|
+
id: "audits/privacy-policy",
|
|
2576
|
+
title: __38("Privacy policy", "elementor"),
|
|
2577
|
+
description: __38(
|
|
2578
|
+
"A privacy policy page is required by privacy regulations such as GDPR (EU) and CCPA (US).",
|
|
2579
|
+
"elementor"
|
|
2580
|
+
),
|
|
2581
|
+
fixHint: __38(
|
|
2582
|
+
"Go to Settings > Privacy in the WordPress admin and assign a published privacy policy page.",
|
|
2583
|
+
"elementor"
|
|
2584
|
+
),
|
|
2585
|
+
categories: ["compliance"],
|
|
2586
|
+
severity: "info",
|
|
2587
|
+
weight: 1,
|
|
2588
|
+
evaluate: (ctx) => {
|
|
2589
|
+
if (ctx.pageContext.privacy_policy_url) {
|
|
2590
|
+
return { status: "pass" };
|
|
2591
|
+
}
|
|
2592
|
+
return {
|
|
2593
|
+
status: "fail",
|
|
2594
|
+
violations: [
|
|
2595
|
+
{
|
|
2596
|
+
auditId: audit16.id,
|
|
2597
|
+
label: __38("No privacy policy page is set.", "elementor"),
|
|
2598
|
+
externalUrl: ctx.pageContext.privacy_settings_url
|
|
2599
|
+
}
|
|
2600
|
+
]
|
|
2601
|
+
};
|
|
2602
|
+
}
|
|
2603
|
+
};
|
|
2604
|
+
|
|
2605
|
+
// src/audits/robots-noindex.ts
|
|
2606
|
+
import { __ as __39 } from "@wordpress/i18n";
|
|
2607
|
+
var audit17 = {
|
|
2608
|
+
id: "audits/robots-noindex",
|
|
2609
|
+
title: __39("Search engine visibility", "elementor"),
|
|
2610
|
+
description: __39(
|
|
2611
|
+
"When search engine visibility is checked in WordPress Settings -> Reading -> Search Engine Visibility, search engines are discouraged from indexing the website.",
|
|
2612
|
+
"elementor"
|
|
2613
|
+
),
|
|
2614
|
+
fixHint: __39(
|
|
2615
|
+
'Go to Settings \u2192 Reading in the WordPress admin and uncheck "Discourage search engines from indexing this site" when the website should be indexed.',
|
|
2616
|
+
"elementor"
|
|
2617
|
+
),
|
|
2618
|
+
categories: ["seo"],
|
|
2619
|
+
severity: "error",
|
|
2620
|
+
weight: 7,
|
|
2621
|
+
evaluate: (ctx) => {
|
|
2622
|
+
if (!ctx.pageContext.is_noindex) {
|
|
2623
|
+
return { status: "pass" };
|
|
2624
|
+
}
|
|
2625
|
+
return {
|
|
2626
|
+
status: "fail",
|
|
2627
|
+
violations: [
|
|
2628
|
+
{
|
|
2629
|
+
auditId: audit17.id,
|
|
2630
|
+
label: __39("Search engines are discouraged from indexing this website.", "elementor"),
|
|
2631
|
+
externalUrl: ctx.pageContext.reading_settings_url
|
|
2632
|
+
}
|
|
2633
|
+
]
|
|
2634
|
+
};
|
|
2635
|
+
}
|
|
2636
|
+
};
|
|
2637
|
+
|
|
2638
|
+
// src/audits/sections-and-columns.ts
|
|
2639
|
+
import { __ as __40 } from "@wordpress/i18n";
|
|
2640
|
+
var audit18 = {
|
|
2641
|
+
id: "audits/sections-and-columns",
|
|
2642
|
+
title: __40("Sections and columns", "elementor"),
|
|
2643
|
+
description: __40(
|
|
2644
|
+
"Sections and columns are legacy elements. Containers render fewer DOM nodes and are more flexible.",
|
|
2645
|
+
"elementor"
|
|
2646
|
+
),
|
|
2647
|
+
fixHint: __40("Use the Container Converter to replace each section/column with a container.", "elementor"),
|
|
2648
|
+
categories: ["best-practices", "performance"],
|
|
2649
|
+
severity: "warning",
|
|
2650
|
+
weight: 7,
|
|
2651
|
+
evaluate: (ctx) => {
|
|
2652
|
+
if (ctx.elements.tree.length === 0) {
|
|
2653
|
+
return { status: "skipped", reason: __40("No elements", "elementor") };
|
|
2654
|
+
}
|
|
2655
|
+
const violations = [];
|
|
2656
|
+
walkElements(ctx.elements.tree, (node) => {
|
|
2657
|
+
if (node.elType === "section" || node.elType === "column") {
|
|
2658
|
+
violations.push({
|
|
2659
|
+
auditId: audit18.id,
|
|
2660
|
+
elementId: node.id,
|
|
2661
|
+
label: node.elType === "section" ? __40("Section element", "elementor") : __40("Column element", "elementor")
|
|
2662
|
+
});
|
|
2663
|
+
}
|
|
2664
|
+
});
|
|
2665
|
+
return violations.length === 0 ? { status: "pass" } : { status: "fail", violations };
|
|
2666
|
+
}
|
|
2667
|
+
};
|
|
2668
|
+
|
|
2669
|
+
// src/audits/site-identity.ts
|
|
2670
|
+
import { __ as __41 } from "@wordpress/i18n";
|
|
2671
|
+
var audit19 = {
|
|
2672
|
+
id: "audits/site-identity",
|
|
2673
|
+
title: __41("Site identity", "elementor"),
|
|
2674
|
+
description: __41(
|
|
2675
|
+
"Site name, description, logo, and favicon establish your brand and appear in search results and browser tabs.",
|
|
2676
|
+
"elementor"
|
|
2677
|
+
),
|
|
2678
|
+
fixHint: __41("Open Site Settings \u2192 Site Identity and complete all the missing fields.", "elementor"),
|
|
2679
|
+
categories: ["best-practices", "seo"],
|
|
2680
|
+
severity: "info",
|
|
2681
|
+
weight: 7,
|
|
2682
|
+
evaluate: (ctx) => {
|
|
2683
|
+
const { site_identity: identity } = ctx.pageContext;
|
|
2684
|
+
const violations = [];
|
|
2685
|
+
if (!identity.site_name_set) {
|
|
2686
|
+
violations.push({
|
|
2687
|
+
auditId: audit19.id,
|
|
2688
|
+
label: __41("Site name is missing or still uses the default.", "elementor"),
|
|
2689
|
+
targetHint: "site-identity-settings"
|
|
2690
|
+
});
|
|
2691
|
+
}
|
|
2692
|
+
if (!identity.site_description_set) {
|
|
2693
|
+
violations.push({
|
|
2694
|
+
auditId: audit19.id,
|
|
2695
|
+
label: __41("Site description is missing or still uses the default.", "elementor"),
|
|
2696
|
+
targetHint: "site-identity-settings",
|
|
2697
|
+
angieFix: true
|
|
2698
|
+
});
|
|
2699
|
+
}
|
|
2700
|
+
if (!identity.site_logo_set) {
|
|
2701
|
+
violations.push({
|
|
2702
|
+
auditId: audit19.id,
|
|
2703
|
+
label: __41("Site logo is not set.", "elementor"),
|
|
2704
|
+
targetHint: "site-identity-settings"
|
|
2705
|
+
});
|
|
2706
|
+
}
|
|
2707
|
+
if (!identity.site_favicon_set) {
|
|
2708
|
+
violations.push({
|
|
2709
|
+
auditId: audit19.id,
|
|
2710
|
+
label: __41("Site favicon is not set.", "elementor"),
|
|
2711
|
+
targetHint: "site-identity-settings"
|
|
2712
|
+
});
|
|
2713
|
+
}
|
|
2714
|
+
if (violations.length === 0) {
|
|
2715
|
+
return { status: "pass" };
|
|
2716
|
+
}
|
|
2717
|
+
return {
|
|
2718
|
+
status: "fail",
|
|
2719
|
+
violations
|
|
2720
|
+
};
|
|
2721
|
+
}
|
|
2722
|
+
};
|
|
2723
|
+
|
|
2724
|
+
// src/audits/too-many-widgets.ts
|
|
2725
|
+
import { __ as __42 } from "@wordpress/i18n";
|
|
2726
|
+
var WIDGET_COUNT_THRESHOLD = 100;
|
|
2727
|
+
var audit20 = {
|
|
2728
|
+
id: "audits/too-many-widgets",
|
|
2729
|
+
title: __42("Too many widgets", "elementor"),
|
|
2730
|
+
description: __42("Excessive DOM size caused by too many widgets degrades rendering performance.", "elementor"),
|
|
2731
|
+
fixHint: __42("Reduce the number of widgets on the page by removing or combining elements.", "elementor"),
|
|
2732
|
+
categories: ["best-practices", "performance"],
|
|
2733
|
+
severity: "warning",
|
|
2734
|
+
weight: 5,
|
|
2735
|
+
evaluate: (ctx) => {
|
|
2736
|
+
if (ctx.elements.tree.length === 0) {
|
|
2737
|
+
return { status: "skipped", reason: __42("No elements", "elementor") };
|
|
2738
|
+
}
|
|
2739
|
+
let widgetCount = 0;
|
|
2740
|
+
walkElements(ctx.elements.tree, (node) => {
|
|
2741
|
+
if (node.elType === "widget") {
|
|
2742
|
+
widgetCount++;
|
|
2743
|
+
}
|
|
2744
|
+
});
|
|
2745
|
+
if (widgetCount <= WIDGET_COUNT_THRESHOLD) {
|
|
2746
|
+
return { status: "pass" };
|
|
2747
|
+
}
|
|
2748
|
+
return {
|
|
2749
|
+
status: "fail",
|
|
2750
|
+
violations: [
|
|
2751
|
+
{
|
|
2752
|
+
auditId: audit20.id,
|
|
2753
|
+
label: __42("Page has too many widgets.", "elementor")
|
|
2754
|
+
}
|
|
2755
|
+
]
|
|
2756
|
+
};
|
|
2757
|
+
}
|
|
2758
|
+
};
|
|
2759
|
+
|
|
2760
|
+
// src/register-audits.ts
|
|
2761
|
+
var AUDITS = [
|
|
2762
|
+
audit13,
|
|
2763
|
+
audit11,
|
|
2764
|
+
audit12,
|
|
2765
|
+
audit7,
|
|
2766
|
+
audit5,
|
|
2767
|
+
audit20,
|
|
2768
|
+
audit18,
|
|
2769
|
+
audit3,
|
|
2770
|
+
audit10,
|
|
2771
|
+
audit4,
|
|
2772
|
+
audit19,
|
|
2773
|
+
audit14,
|
|
2774
|
+
audit15,
|
|
2775
|
+
audit6,
|
|
2776
|
+
audit8,
|
|
2777
|
+
audit9,
|
|
2778
|
+
audit17,
|
|
2779
|
+
audit16,
|
|
2780
|
+
audit,
|
|
2781
|
+
audit2
|
|
2782
|
+
];
|
|
2783
|
+
function registerAllAudits() {
|
|
2784
|
+
for (const audit21 of AUDITS) {
|
|
2785
|
+
registerAudit(audit21);
|
|
2786
|
+
}
|
|
2787
|
+
}
|
|
2788
|
+
|
|
2789
|
+
// src/init.ts
|
|
2790
|
+
function init() {
|
|
2791
|
+
registerSlice(slice);
|
|
2792
|
+
registerAllAudits();
|
|
2793
|
+
registerFloatingPanel(auditPanel.panel);
|
|
2794
|
+
registerAppBarAuditsToggle();
|
|
2795
|
+
}
|
|
2796
|
+
export {
|
|
2797
|
+
init,
|
|
2798
|
+
registerAudit,
|
|
2799
|
+
runPageAudit
|
|
2800
|
+
};
|
|
2801
|
+
//# sourceMappingURL=index.mjs.map
|