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