@rogatio/cli 6.9.0 → 6.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/editor/index.css +119 -31
- package/dist/editor/index.js +572 -218
- package/dist/node/index.js +95 -70
- package/package.json +1 -1
package/dist/editor/index.js
CHANGED
|
@@ -1742,6 +1742,31 @@ var HTTP_METHODS2 = [
|
|
|
1742
1742
|
"CONNECT",
|
|
1743
1743
|
"TRACE"
|
|
1744
1744
|
];
|
|
1745
|
+
var RESOURCE_TYPE_LABELS = {
|
|
1746
|
+
main_frame: "Page",
|
|
1747
|
+
sub_frame: "Frame",
|
|
1748
|
+
stylesheet: "Stylesheet",
|
|
1749
|
+
script: "Script",
|
|
1750
|
+
image: "Image",
|
|
1751
|
+
font: "Font",
|
|
1752
|
+
object: "Plugin",
|
|
1753
|
+
media: "Media",
|
|
1754
|
+
xmlhttprequest: "Fetch",
|
|
1755
|
+
ping: "Ping",
|
|
1756
|
+
csp_report: "CSP report",
|
|
1757
|
+
websocket: "WebSocket",
|
|
1758
|
+
webtransport: "WebTransport",
|
|
1759
|
+
webbundle: "Web Bundle",
|
|
1760
|
+
other: "Other"
|
|
1761
|
+
};
|
|
1762
|
+
var TEST_CASE_LIMIT = 256;
|
|
1763
|
+
var RULE_TYPE_LABELS = {
|
|
1764
|
+
redirect: "Redirect",
|
|
1765
|
+
query: "Query parameters",
|
|
1766
|
+
header: "Header",
|
|
1767
|
+
"response-body": "Response body",
|
|
1768
|
+
"request-body": "Request body"
|
|
1769
|
+
};
|
|
1745
1770
|
var COMMON_RULE_FIELDS = /* @__PURE__ */ new Set([
|
|
1746
1771
|
"id",
|
|
1747
1772
|
"name",
|
|
@@ -1759,6 +1784,30 @@ var FORBIDDEN_EXTENSION_FIELDS = /* @__PURE__ */ new Set([
|
|
|
1759
1784
|
var MAX_SNAPSHOT_ARRAY_LENGTH = 4096;
|
|
1760
1785
|
var F2_MAX_URL_REGEX_LENGTH2 = 2048;
|
|
1761
1786
|
var editorInstanceCount = 0;
|
|
1787
|
+
function normalizeProjectActions(value) {
|
|
1788
|
+
if (!Array.isArray(value)) return [];
|
|
1789
|
+
const actions = [];
|
|
1790
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
1791
|
+
if (!Object.hasOwn(value, index)) continue;
|
|
1792
|
+
const item = value[index];
|
|
1793
|
+
if (!isRecord4(item)) continue;
|
|
1794
|
+
const command = item.command;
|
|
1795
|
+
const label = item.label;
|
|
1796
|
+
if (typeof command !== "string" || command.length === 0) continue;
|
|
1797
|
+
if (typeof label !== "string" || label.length === 0) continue;
|
|
1798
|
+
if (item.tone === "danger") {
|
|
1799
|
+
actions.push({ command, label, tone: "danger" });
|
|
1800
|
+
} else {
|
|
1801
|
+
actions.push({ command, label });
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
return actions;
|
|
1805
|
+
}
|
|
1806
|
+
function ruleCountLabel(count) {
|
|
1807
|
+
if (count === 0) return "No rules";
|
|
1808
|
+
if (count === 1) return "1 rule";
|
|
1809
|
+
return `${count} rules`;
|
|
1810
|
+
}
|
|
1762
1811
|
function isRecord4(value) {
|
|
1763
1812
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1764
1813
|
}
|
|
@@ -2033,6 +2082,80 @@ function displayName(value, fallback) {
|
|
|
2033
2082
|
const text = safeText(value, fallback);
|
|
2034
2083
|
return text.length > 0 ? text : fallback;
|
|
2035
2084
|
}
|
|
2085
|
+
function checkingCopy(method, resourceType) {
|
|
2086
|
+
if (method === "GET" && resourceType === "main_frame") {
|
|
2087
|
+
return "Checking these as page loads (GET).";
|
|
2088
|
+
}
|
|
2089
|
+
if (method === "" && resourceType === "") {
|
|
2090
|
+
return "The method and the resource type are not tested.";
|
|
2091
|
+
}
|
|
2092
|
+
const resourceLabel = resourceType === "" ? "" : RESOURCE_TYPE_LABELS[resourceType];
|
|
2093
|
+
if (method === "") {
|
|
2094
|
+
const subject = resourceType === "main_frame" ? "page loads" : `${resourceLabel} requests`;
|
|
2095
|
+
return `Checking these as ${subject}. The method is not tested.`;
|
|
2096
|
+
}
|
|
2097
|
+
if (resourceType === "") {
|
|
2098
|
+
return `Checking these as ${method} requests. The resource type is not tested.`;
|
|
2099
|
+
}
|
|
2100
|
+
if (resourceType === "main_frame") {
|
|
2101
|
+
return `Checking these as page loads (${method}).`;
|
|
2102
|
+
}
|
|
2103
|
+
return `Checking these as ${resourceLabel} requests (${method}).`;
|
|
2104
|
+
}
|
|
2105
|
+
function exactUrlFromSource(source) {
|
|
2106
|
+
if (source?.key !== "url" || source.operator !== "regex") {
|
|
2107
|
+
return null;
|
|
2108
|
+
}
|
|
2109
|
+
if (typeof source.value !== "string") return null;
|
|
2110
|
+
const value = source.value;
|
|
2111
|
+
if (!value.startsWith("^") || !value.endsWith("$") || value.length < 2) {
|
|
2112
|
+
return null;
|
|
2113
|
+
}
|
|
2114
|
+
let decoded = "";
|
|
2115
|
+
for (let index = 1; index < value.length - 1; index += 1) {
|
|
2116
|
+
const char = value[index];
|
|
2117
|
+
if (char === "\\") {
|
|
2118
|
+
index += 1;
|
|
2119
|
+
const escaped = value[index];
|
|
2120
|
+
if (escaped === void 0 || index >= value.length - 1) return null;
|
|
2121
|
+
decoded += escaped;
|
|
2122
|
+
continue;
|
|
2123
|
+
}
|
|
2124
|
+
if ("^$\\.*+?()[]{}|".includes(char)) return null;
|
|
2125
|
+
decoded += char;
|
|
2126
|
+
}
|
|
2127
|
+
const converted = urlToExactRegex(decoded);
|
|
2128
|
+
if (!converted.ok || converted.source !== value) return null;
|
|
2129
|
+
return decoded;
|
|
2130
|
+
}
|
|
2131
|
+
function isDryRunResult(value) {
|
|
2132
|
+
if (!isRecord4(value)) return false;
|
|
2133
|
+
const summary = value.summary;
|
|
2134
|
+
return Array.isArray(value.results) && Array.isArray(value.errors) && isRecord4(summary) && typeof summary.matchedUrlCount === "number" && typeof summary.urlCount === "number";
|
|
2135
|
+
}
|
|
2136
|
+
function previewSentence(preview) {
|
|
2137
|
+
if (!preview || preview.summary.length === 0) return null;
|
|
2138
|
+
switch (preview.kind) {
|
|
2139
|
+
case "redirect":
|
|
2140
|
+
return `The browser would go to ${preview.summary}.`;
|
|
2141
|
+
case "query":
|
|
2142
|
+
return `The query would be ${preview.summary}.`;
|
|
2143
|
+
case "header":
|
|
2144
|
+
return `The header would be ${preview.summary}.`;
|
|
2145
|
+
case "request-body":
|
|
2146
|
+
return `The request body would be ${preview.summary}.`;
|
|
2147
|
+
case "response-body":
|
|
2148
|
+
return `The response body would be ${preview.summary}.`;
|
|
2149
|
+
default:
|
|
2150
|
+
return null;
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2153
|
+
function missReason(rule) {
|
|
2154
|
+
if (rule.source.state === "unmatched") return "URL pattern";
|
|
2155
|
+
if (rule.method.state === "unmatched") return "method";
|
|
2156
|
+
if (rule.resourceType.state === "unmatched") return "resource type";
|
|
2157
|
+
return "URL pattern";
|
|
2158
|
+
}
|
|
2036
2159
|
function isHTMLElement(value) {
|
|
2037
2160
|
return value !== null && typeof value === "object" && value.nodeType === 1 && typeof value.appendChild === "function";
|
|
2038
2161
|
}
|
|
@@ -2163,6 +2286,8 @@ var EditorControllerImpl = class {
|
|
|
2163
2286
|
extensionControls = /* @__PURE__ */ new Map();
|
|
2164
2287
|
extensionCleanups = [];
|
|
2165
2288
|
confirmation;
|
|
2289
|
+
groupPickerOpen = false;
|
|
2290
|
+
projectActions;
|
|
2166
2291
|
focusRequest;
|
|
2167
2292
|
saving = false;
|
|
2168
2293
|
destroyed = false;
|
|
@@ -2171,9 +2296,8 @@ var EditorControllerImpl = class {
|
|
|
2171
2296
|
controlNumber = 0;
|
|
2172
2297
|
previousFocus;
|
|
2173
2298
|
testUrls = "";
|
|
2174
|
-
testMethod = "";
|
|
2175
|
-
testResourceType = "";
|
|
2176
|
-
testMaxCases = "256";
|
|
2299
|
+
testMethod = "GET";
|
|
2300
|
+
testResourceType = "main_frame";
|
|
2177
2301
|
testResult = void 0;
|
|
2178
2302
|
testRunning = false;
|
|
2179
2303
|
testRequestId = 0;
|
|
@@ -2193,6 +2317,7 @@ var EditorControllerImpl = class {
|
|
|
2193
2317
|
this.root = options.root;
|
|
2194
2318
|
this.document = options.root.ownerDocument;
|
|
2195
2319
|
this.options = options;
|
|
2320
|
+
this.projectActions = normalizeProjectActions(options.projectActions);
|
|
2196
2321
|
this.extensions = extensions;
|
|
2197
2322
|
this.instanceId = `rogatio-editor-${++editorInstanceCount}`;
|
|
2198
2323
|
this.draft = initial;
|
|
@@ -2310,6 +2435,10 @@ var EditorControllerImpl = class {
|
|
|
2310
2435
|
if (this.destroyed) return;
|
|
2311
2436
|
const target = event.target;
|
|
2312
2437
|
if (!(target instanceof Element)) return;
|
|
2438
|
+
if (this.groupPickerOpen && target instanceof HTMLElement && target.dataset.groupPicker !== void 0 && event.target === target) {
|
|
2439
|
+
this.closeGroupPicker();
|
|
2440
|
+
return;
|
|
2441
|
+
}
|
|
2313
2442
|
const element = target.closest(
|
|
2314
2443
|
"[data-command], [data-route], [data-search-result], [data-error-path]"
|
|
2315
2444
|
);
|
|
@@ -2349,10 +2478,6 @@ var EditorControllerImpl = class {
|
|
|
2349
2478
|
this.testUrls = target.value;
|
|
2350
2479
|
return;
|
|
2351
2480
|
}
|
|
2352
|
-
if (target.dataset.testMaxCases !== void 0) {
|
|
2353
|
-
this.testMaxCases = target.value;
|
|
2354
|
-
return;
|
|
2355
|
-
}
|
|
2356
2481
|
const path = target.dataset.path;
|
|
2357
2482
|
if (!path || target.type === "checkbox" || this.extensionControls.has(path))
|
|
2358
2483
|
return;
|
|
@@ -2366,12 +2491,6 @@ var EditorControllerImpl = class {
|
|
|
2366
2491
|
if (!(target instanceof HTMLInputElement || target instanceof HTMLSelectElement)) {
|
|
2367
2492
|
return;
|
|
2368
2493
|
}
|
|
2369
|
-
if (target.dataset.mobileRoute !== void 0 && target instanceof HTMLSelectElement) {
|
|
2370
|
-
const selectedOption = target.options[target.selectedIndex];
|
|
2371
|
-
const groupId = selectedOption?.dataset.groupId;
|
|
2372
|
-
this.navigate(target.value, groupId);
|
|
2373
|
-
return;
|
|
2374
|
-
}
|
|
2375
2494
|
if (target instanceof HTMLInputElement && target.dataset.resourcePath !== void 0) {
|
|
2376
2495
|
this.updateResourceTypes(
|
|
2377
2496
|
target.dataset.resourcePath,
|
|
@@ -2387,10 +2506,12 @@ var EditorControllerImpl = class {
|
|
|
2387
2506
|
}
|
|
2388
2507
|
if (target instanceof HTMLSelectElement && target.dataset.testMethod !== void 0) {
|
|
2389
2508
|
this.testMethod = target.value || "";
|
|
2509
|
+
this.render();
|
|
2390
2510
|
return;
|
|
2391
2511
|
}
|
|
2392
2512
|
if (target instanceof HTMLSelectElement && target.dataset.testResourceType !== void 0) {
|
|
2393
2513
|
this.testResourceType = target.value || "";
|
|
2514
|
+
this.render();
|
|
2394
2515
|
return;
|
|
2395
2516
|
}
|
|
2396
2517
|
const path = target.dataset.path;
|
|
@@ -2425,6 +2546,11 @@ var EditorControllerImpl = class {
|
|
|
2425
2546
|
this.render();
|
|
2426
2547
|
return;
|
|
2427
2548
|
}
|
|
2549
|
+
if (event.key === "Escape" && this.groupPickerOpen) {
|
|
2550
|
+
event.preventDefault();
|
|
2551
|
+
this.closeGroupPicker();
|
|
2552
|
+
return;
|
|
2553
|
+
}
|
|
2428
2554
|
const target = event.target;
|
|
2429
2555
|
const inRenameInput = target instanceof HTMLElement && target.dataset.renameInput !== void 0;
|
|
2430
2556
|
if (!inRenameInput) return;
|
|
@@ -2571,6 +2697,25 @@ var EditorControllerImpl = class {
|
|
|
2571
2697
|
void this.runTest();
|
|
2572
2698
|
return;
|
|
2573
2699
|
}
|
|
2700
|
+
if (command === "test:open-rule") {
|
|
2701
|
+
this.navigateToRule(element.dataset.groupId, element.dataset.ruleId);
|
|
2702
|
+
return;
|
|
2703
|
+
}
|
|
2704
|
+
if (command === "test:try-url") {
|
|
2705
|
+
const url = element.dataset.url;
|
|
2706
|
+
if (!url) return;
|
|
2707
|
+
this.testUrls = url;
|
|
2708
|
+
this.statusMessage = "";
|
|
2709
|
+
this.render();
|
|
2710
|
+
return;
|
|
2711
|
+
}
|
|
2712
|
+
if (command === "open-group-picker") {
|
|
2713
|
+
this.openGroupPicker();
|
|
2714
|
+
return;
|
|
2715
|
+
}
|
|
2716
|
+
if (command === "close-group-picker") {
|
|
2717
|
+
this.closeGroupPicker();
|
|
2718
|
+
}
|
|
2574
2719
|
}
|
|
2575
2720
|
collectDiagnostics(value) {
|
|
2576
2721
|
let hostDiagnostics;
|
|
@@ -3282,65 +3427,111 @@ var EditorControllerImpl = class {
|
|
|
3282
3427
|
this.focusRequest = path;
|
|
3283
3428
|
this.render();
|
|
3284
3429
|
}
|
|
3430
|
+
ruleCount() {
|
|
3431
|
+
let count = 0;
|
|
3432
|
+
for (const group of this.draft.groups) count += group.rules.length;
|
|
3433
|
+
return count;
|
|
3434
|
+
}
|
|
3435
|
+
testLines() {
|
|
3436
|
+
const entries = [];
|
|
3437
|
+
const rawLines = this.testUrls.split(/\r?\n/);
|
|
3438
|
+
for (let index = 0; index < rawLines.length; index += 1) {
|
|
3439
|
+
const url = rawLines[index]?.trim() ?? "";
|
|
3440
|
+
if (url.length > 0) entries.push({ url, line: index + 1 });
|
|
3441
|
+
}
|
|
3442
|
+
return entries;
|
|
3443
|
+
}
|
|
3444
|
+
showDryRunDiagnostics(diagnostics) {
|
|
3445
|
+
this.testResult = void 0;
|
|
3446
|
+
this.testRunning = false;
|
|
3447
|
+
this.errors = [...diagnostics];
|
|
3448
|
+
const count = diagnostics.length;
|
|
3449
|
+
this.statusMessage = count === 0 ? "The project could not be tested." : `${count} validation error${count === 1 ? "" : "s"} found.`;
|
|
3450
|
+
const first = diagnostics[0];
|
|
3451
|
+
if (first) {
|
|
3452
|
+
this.navigateToPath(first.path);
|
|
3453
|
+
return;
|
|
3454
|
+
}
|
|
3455
|
+
this.render();
|
|
3456
|
+
}
|
|
3285
3457
|
async runTest() {
|
|
3286
3458
|
if (!this.options.dryRun) {
|
|
3287
3459
|
this.statusMessage = "Dry-run is not configured for this editor instance.";
|
|
3288
3460
|
this.render();
|
|
3289
3461
|
return;
|
|
3290
3462
|
}
|
|
3291
|
-
|
|
3292
|
-
|
|
3463
|
+
if (this.ruleCount() === 0) {
|
|
3464
|
+
this.testResult = void 0;
|
|
3465
|
+
this.testRunning = false;
|
|
3466
|
+
this.statusMessage = "This project has no rules to test.";
|
|
3467
|
+
this.render();
|
|
3468
|
+
return;
|
|
3469
|
+
}
|
|
3470
|
+
const lines = this.testLines();
|
|
3471
|
+
if (lines.length === 0) {
|
|
3293
3472
|
this.statusMessage = "Please enter at least one URL to test.";
|
|
3294
3473
|
this.render();
|
|
3295
3474
|
return;
|
|
3296
3475
|
}
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
const
|
|
3476
|
+
if (lines.length > TEST_CASE_LIMIT) {
|
|
3477
|
+
this.testResult = void 0;
|
|
3478
|
+
this.testRunning = false;
|
|
3479
|
+
this.statusMessage = "Only 256 URLs can be checked at once.";
|
|
3480
|
+
this.render();
|
|
3481
|
+
return;
|
|
3482
|
+
}
|
|
3483
|
+
const cases = lines.map((entry) => {
|
|
3484
|
+
const testCase = { url: entry.url };
|
|
3485
|
+
if (this.testMethod !== "") testCase.method = this.testMethod;
|
|
3486
|
+
if (this.testResourceType !== "") {
|
|
3487
|
+
testCase.resourceType = this.testResourceType;
|
|
3488
|
+
}
|
|
3489
|
+
return testCase;
|
|
3490
|
+
});
|
|
3305
3491
|
const requestId = ++this.testRequestId;
|
|
3306
3492
|
this.testRunning = true;
|
|
3307
|
-
this.statusMessage = "
|
|
3493
|
+
this.statusMessage = "Checking URLs...";
|
|
3308
3494
|
this.render();
|
|
3309
3495
|
try {
|
|
3310
|
-
const
|
|
3311
|
-
this.getDraft(),
|
|
3312
|
-
cases,
|
|
3313
|
-
maxCases === void 0 ? void 0 : { maxCases }
|
|
3314
|
-
);
|
|
3496
|
+
const outcome = await this.options.dryRun(this.getDraft(), cases);
|
|
3315
3497
|
if (requestId !== this.testRequestId) return;
|
|
3316
|
-
this.testResult = result;
|
|
3317
3498
|
this.testRunning = false;
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3499
|
+
if (isDryRunResult(outcome)) {
|
|
3500
|
+
this.testResult = outcome;
|
|
3501
|
+
const checked = outcome.summary.urlCount;
|
|
3502
|
+
this.statusMessage = outcome.errors.length > 0 ? `Checked ${checked} URL${checked === 1 ? "" : "s"}. Some lines are not http(s) URLs.` : `Checked ${checked} URL${checked === 1 ? "" : "s"}.`;
|
|
3503
|
+
this.render();
|
|
3504
|
+
return;
|
|
3505
|
+
}
|
|
3506
|
+
const diagnostics = normalizeDiagnostics(
|
|
3507
|
+
isRecord4(outcome) ? outcome.diagnostics : void 0
|
|
3508
|
+
);
|
|
3509
|
+
this.showDryRunDiagnostics(diagnostics);
|
|
3321
3510
|
} catch (e) {
|
|
3322
3511
|
if (requestId !== this.testRequestId) return;
|
|
3512
|
+
this.testResult = void 0;
|
|
3323
3513
|
this.testRunning = false;
|
|
3324
3514
|
const message = e instanceof Error ? e.message : "Test failed";
|
|
3325
3515
|
this.statusMessage = `Test error: ${message}`;
|
|
3516
|
+
this.render();
|
|
3326
3517
|
}
|
|
3327
|
-
this.render();
|
|
3328
3518
|
}
|
|
3329
3519
|
renderTestResults(result) {
|
|
3330
3520
|
const resultsSection = this.host.querySelector(
|
|
3331
3521
|
"[data-test-results]"
|
|
3332
3522
|
);
|
|
3333
|
-
if (!resultsSection) return;
|
|
3523
|
+
if (!resultsSection || !isDryRunResult(result)) return;
|
|
3334
3524
|
resultsSection.replaceChildren();
|
|
3335
3525
|
const heading = this.document.createElement("h3");
|
|
3336
3526
|
heading.textContent = "Results";
|
|
3337
3527
|
resultsSection.append(heading);
|
|
3528
|
+
const lines = this.testLines();
|
|
3338
3529
|
if (result.errors.length > 0) {
|
|
3339
3530
|
const errorsList = this.document.createElement("ul");
|
|
3340
3531
|
errorsList.dataset.testErrors = "true";
|
|
3341
3532
|
for (const err of result.errors) {
|
|
3342
3533
|
const item = this.document.createElement("li");
|
|
3343
|
-
item.textContent =
|
|
3534
|
+
item.textContent = this.testErrorText(err, lines);
|
|
3344
3535
|
errorsList.append(item);
|
|
3345
3536
|
}
|
|
3346
3537
|
resultsSection.append(errorsList);
|
|
@@ -3355,75 +3546,138 @@ var EditorControllerImpl = class {
|
|
|
3355
3546
|
for (const urlResult of result.results) {
|
|
3356
3547
|
const card = this.document.createElement("article");
|
|
3357
3548
|
card.dataset.testResultCard = "true";
|
|
3358
|
-
const
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
urlBadge.dataset.variant = matched ? "matched" : "unmatched";
|
|
3367
|
-
urlBadge.textContent = matched ? "MATCHED" : "NO MATCH";
|
|
3368
|
-
urlHeader.append(urlText, urlBadge);
|
|
3369
|
-
card.append(urlHeader);
|
|
3370
|
-
for (const rule of urlResult.rules) {
|
|
3371
|
-
const ruleDiv = this.document.createElement("div");
|
|
3372
|
-
ruleDiv.dataset.testRule = "true";
|
|
3373
|
-
const ruleHeader = this.document.createElement("div");
|
|
3374
|
-
ruleHeader.dataset.testRuleHeader = "true";
|
|
3375
|
-
const ruleName = this.document.createElement("strong");
|
|
3376
|
-
ruleName.textContent = this.testRuleLabel(rule.groupId, rule.ruleId);
|
|
3377
|
-
const ruleBadge = this.document.createElement("span");
|
|
3378
|
-
ruleBadge.dataset.testBadge = "true";
|
|
3379
|
-
ruleBadge.dataset.variant = rule.matched ? "matched" : "unmatched";
|
|
3380
|
-
ruleBadge.textContent = rule.matched ? "\u2713 MATCHED" : "\u2717 NOT MATCHED";
|
|
3381
|
-
ruleHeader.append(ruleName, ruleBadge);
|
|
3382
|
-
ruleDiv.append(ruleHeader);
|
|
3383
|
-
const dims = [
|
|
3384
|
-
{ label: "source", dim: rule.source },
|
|
3385
|
-
{ label: "method", dim: rule.method },
|
|
3386
|
-
{ label: "resourceType", dim: rule.resourceType }
|
|
3387
|
-
];
|
|
3388
|
-
for (const { label, dim } of dims) {
|
|
3389
|
-
const dimDiv = this.document.createElement("div");
|
|
3390
|
-
dimDiv.dataset.testDimension = "true";
|
|
3391
|
-
const badge = this.document.createElement("span");
|
|
3392
|
-
badge.dataset.testBadge = "true";
|
|
3393
|
-
badge.dataset.variant = dim.state === "matched" ? "matched" : dim.state === "unmatched" ? "unmatched" : "na";
|
|
3394
|
-
badge.textContent = dim.state.toUpperCase();
|
|
3395
|
-
dimDiv.append(
|
|
3396
|
-
badge,
|
|
3397
|
-
this.document.createTextNode(`${label}: ${dim.detail}`)
|
|
3398
|
-
);
|
|
3399
|
-
ruleDiv.append(dimDiv);
|
|
3400
|
-
}
|
|
3401
|
-
if (rule.actionPreview) {
|
|
3402
|
-
const apDiv = this.document.createElement("div");
|
|
3403
|
-
apDiv.dataset.testActionPreview = "true";
|
|
3404
|
-
apDiv.textContent = `Action preview: ${rule.actionPreview.kind} - ${rule.actionPreview.summary}`;
|
|
3405
|
-
ruleDiv.append(apDiv);
|
|
3406
|
-
}
|
|
3407
|
-
card.append(ruleDiv);
|
|
3549
|
+
const matches = urlResult.rules.filter((rule) => rule.matched);
|
|
3550
|
+
const misses = urlResult.rules.filter((rule) => !rule.matched);
|
|
3551
|
+
card.dataset.matched = matches.length > 0 ? "true" : "false";
|
|
3552
|
+
for (const rule of matches) {
|
|
3553
|
+
card.append(this.renderTestOutcome(urlResult.url, rule));
|
|
3554
|
+
}
|
|
3555
|
+
if (misses.length > 0) {
|
|
3556
|
+
card.append(this.renderTestMisses(misses));
|
|
3408
3557
|
}
|
|
3409
3558
|
resultsSection.append(card);
|
|
3410
3559
|
}
|
|
3411
3560
|
}
|
|
3561
|
+
testErrorText(err, lines) {
|
|
3562
|
+
if (err.code === "dryrun.batch-limit") {
|
|
3563
|
+
return "Only 256 URLs can be checked at once.";
|
|
3564
|
+
}
|
|
3565
|
+
const entry = err.index === void 0 ? void 0 : lines[err.index];
|
|
3566
|
+
if (err.code === "dryrun.invalid-url" && entry) {
|
|
3567
|
+
return `Line ${entry.line} is not an http(s) URL: ${entry.url}`;
|
|
3568
|
+
}
|
|
3569
|
+
if (entry) {
|
|
3570
|
+
return `Line ${entry.line} could not be checked: ${entry.url}`;
|
|
3571
|
+
}
|
|
3572
|
+
return err.message;
|
|
3573
|
+
}
|
|
3574
|
+
renderTestOutcome(url, rule) {
|
|
3575
|
+
const names = this.testRuleNames(rule.groupId, rule.ruleId);
|
|
3576
|
+
const typeLabel = this.testRuleTypeLabel(rule.groupId, rule.ruleId);
|
|
3577
|
+
const outcome = this.document.createElement("p");
|
|
3578
|
+
outcome.dataset.testOutcome = "true";
|
|
3579
|
+
const lead = this.document.createElement("span");
|
|
3580
|
+
lead.textContent = names.groupKnown ? `${url} matches ${typeLabel} in ${names.groupName} / ` : `${url} matches ${typeLabel} in `;
|
|
3581
|
+
const ruleButton = this.createCommandButton(
|
|
3582
|
+
names.ruleLabel,
|
|
3583
|
+
"test:open-rule",
|
|
3584
|
+
false,
|
|
3585
|
+
{ groupId: rule.groupId, ruleId: rule.ruleId }
|
|
3586
|
+
);
|
|
3587
|
+
ruleButton.dataset.testRuleLink = "true";
|
|
3588
|
+
ruleButton.removeAttribute("data-btn");
|
|
3589
|
+
const tailParts = ["."];
|
|
3590
|
+
const preview = previewSentence(rule.actionPreview);
|
|
3591
|
+
if (preview) tailParts.push(` ${preview}`);
|
|
3592
|
+
if (rule.method.state === "not-applicable") {
|
|
3593
|
+
tailParts.push(" The method was not tested.");
|
|
3594
|
+
}
|
|
3595
|
+
if (rule.resourceType.state === "not-applicable") {
|
|
3596
|
+
tailParts.push(" The resource type was not tested.");
|
|
3597
|
+
}
|
|
3598
|
+
if (this.groupIsOffInBrowser(rule.groupId)) {
|
|
3599
|
+
tailParts.push(
|
|
3600
|
+
" This group is off in Chrome, so the browser will not apply this rule."
|
|
3601
|
+
);
|
|
3602
|
+
}
|
|
3603
|
+
const tail = this.document.createElement("span");
|
|
3604
|
+
tail.textContent = tailParts.join("");
|
|
3605
|
+
outcome.append(lead, ruleButton, tail);
|
|
3606
|
+
return outcome;
|
|
3607
|
+
}
|
|
3608
|
+
renderTestMisses(misses) {
|
|
3609
|
+
const details = this.document.createElement("details");
|
|
3610
|
+
details.dataset.testMisses = "true";
|
|
3611
|
+
const summary = this.document.createElement("summary");
|
|
3612
|
+
summary.textContent = misses.length === 1 ? "1 rule did not match" : `${misses.length} rules did not match`;
|
|
3613
|
+
const list = this.document.createElement("ul");
|
|
3614
|
+
for (const rule of misses) {
|
|
3615
|
+
const names = this.testRuleNames(rule.groupId, rule.ruleId);
|
|
3616
|
+
const item = this.document.createElement("li");
|
|
3617
|
+
const ruleButton = this.createCommandButton(
|
|
3618
|
+
names.ruleLabel,
|
|
3619
|
+
"test:open-rule",
|
|
3620
|
+
false,
|
|
3621
|
+
{ groupId: rule.groupId, ruleId: rule.ruleId }
|
|
3622
|
+
);
|
|
3623
|
+
ruleButton.dataset.testRuleLink = "true";
|
|
3624
|
+
ruleButton.removeAttribute("data-btn");
|
|
3625
|
+
const reason = this.document.createElement("span");
|
|
3626
|
+
reason.dataset.testMissReason = "true";
|
|
3627
|
+
reason.textContent = missReason(rule);
|
|
3628
|
+
item.append(ruleButton, this.document.createTextNode(" \u2014 "), reason);
|
|
3629
|
+
list.append(item);
|
|
3630
|
+
}
|
|
3631
|
+
details.append(summary, list);
|
|
3632
|
+
return details;
|
|
3633
|
+
}
|
|
3412
3634
|
/**
|
|
3413
3635
|
* A test-result row is identified by the names the user actually sees. The dry
|
|
3414
3636
|
* result carries ids only, and the draft the editor handed over is the source
|
|
3415
|
-
* of truth for both
|
|
3416
|
-
* draft falls back to its id rather than rendering a blank row.
|
|
3637
|
+
* of truth for both. A rule that is no longer in the draft falls back to its id.
|
|
3417
3638
|
*/
|
|
3418
|
-
|
|
3639
|
+
testRuleNames(groupId, ruleId) {
|
|
3419
3640
|
const group = this.groupById(groupId);
|
|
3420
|
-
if (!group)
|
|
3641
|
+
if (!group) {
|
|
3642
|
+
return {
|
|
3643
|
+
groupKnown: false,
|
|
3644
|
+
ruleKnown: false,
|
|
3645
|
+
groupName: groupId,
|
|
3646
|
+
ruleLabel: `${groupId}/${ruleId}`
|
|
3647
|
+
};
|
|
3648
|
+
}
|
|
3421
3649
|
const groupName = displayName(group.name, "Unnamed group");
|
|
3422
3650
|
const rule = this.ruleById(groupId, ruleId);
|
|
3423
|
-
if (!rule)
|
|
3424
|
-
|
|
3651
|
+
if (!rule) {
|
|
3652
|
+
return {
|
|
3653
|
+
groupKnown: true,
|
|
3654
|
+
ruleKnown: false,
|
|
3655
|
+
groupName,
|
|
3656
|
+
ruleLabel: ruleId
|
|
3657
|
+
};
|
|
3658
|
+
}
|
|
3659
|
+
return {
|
|
3660
|
+
groupKnown: true,
|
|
3661
|
+
ruleKnown: true,
|
|
3662
|
+
groupName,
|
|
3663
|
+
ruleLabel: displayName(rule.name, "Unnamed rule")
|
|
3664
|
+
};
|
|
3665
|
+
}
|
|
3666
|
+
testRuleTypeLabel(groupId, ruleId) {
|
|
3667
|
+
const rule = this.ruleById(groupId, ruleId);
|
|
3668
|
+
const type = rule && typeof rule.type === "string" ? rule.type : "";
|
|
3669
|
+
return RULE_TYPE_LABELS[type] ?? "the rule";
|
|
3670
|
+
}
|
|
3671
|
+
groupIsOffInBrowser(groupId) {
|
|
3672
|
+
const enablement = this.options.groupEnablement;
|
|
3673
|
+
if (!enablement) return false;
|
|
3674
|
+
const group = this.groupById(groupId);
|
|
3675
|
+
const saved = group ? this.savedGroupId(group) : void 0;
|
|
3676
|
+
const id = saved && saved.length > 0 ? saved : groupId;
|
|
3677
|
+
return enablement.isEnabled(id) !== true;
|
|
3425
3678
|
}
|
|
3426
3679
|
navigate(route, groupId) {
|
|
3680
|
+
const returnFocusToGroups = this.groupPickerOpen;
|
|
3427
3681
|
if (route === "project") {
|
|
3428
3682
|
this.route = { kind: "project" };
|
|
3429
3683
|
} else if (route === "test") {
|
|
@@ -3433,6 +3687,8 @@ var EditorControllerImpl = class {
|
|
|
3433
3687
|
} else {
|
|
3434
3688
|
return;
|
|
3435
3689
|
}
|
|
3690
|
+
this.groupPickerOpen = false;
|
|
3691
|
+
if (returnFocusToGroups) this.focusRequest = "route:groups";
|
|
3436
3692
|
this.closeRenameOnLeave();
|
|
3437
3693
|
this.testRequestId += 1;
|
|
3438
3694
|
this.statusMessage = "";
|
|
@@ -3458,6 +3714,7 @@ var EditorControllerImpl = class {
|
|
|
3458
3714
|
}
|
|
3459
3715
|
}
|
|
3460
3716
|
navigateToGroup(groupId) {
|
|
3717
|
+
this.groupPickerOpen = false;
|
|
3461
3718
|
const groupIds = new Set(
|
|
3462
3719
|
this.draft.groups.map((group) => typeof group.id === "string" ? group.id : "").filter((id) => id.length > 0)
|
|
3463
3720
|
);
|
|
@@ -3530,6 +3787,7 @@ var EditorControllerImpl = class {
|
|
|
3530
3787
|
this.revealRuleCard(resolvedGroup, resolvedRule);
|
|
3531
3788
|
}
|
|
3532
3789
|
navigateToSearchResult(path) {
|
|
3790
|
+
this.groupPickerOpen = false;
|
|
3533
3791
|
const segments = decodePointer(path);
|
|
3534
3792
|
if (segments?.[0] !== "groups") {
|
|
3535
3793
|
this.route = { kind: "project" };
|
|
@@ -3621,7 +3879,9 @@ var EditorControllerImpl = class {
|
|
|
3621
3879
|
this.renderSummary();
|
|
3622
3880
|
this.renderMigrationNotices();
|
|
3623
3881
|
this.renderSearchResults();
|
|
3882
|
+
if (this.confirmation) this.groupPickerOpen = false;
|
|
3624
3883
|
this.renderConfirmation();
|
|
3884
|
+
this.renderGroupPicker();
|
|
3625
3885
|
this.restoreFocus();
|
|
3626
3886
|
this.focusRequest = void 0;
|
|
3627
3887
|
}
|
|
@@ -3634,39 +3894,7 @@ var EditorControllerImpl = class {
|
|
|
3634
3894
|
dirty.dataset.dirtyState = "true";
|
|
3635
3895
|
dirty.textContent = this.isDirty() ? "Unsaved changes" : "All changes saved";
|
|
3636
3896
|
titleBlock.append(title, dirty);
|
|
3637
|
-
|
|
3638
|
-
mobileNav.dataset.mobileRouteNav = "true";
|
|
3639
|
-
mobileNav.textContent = "Project section";
|
|
3640
|
-
const select = this.document.createElement("select");
|
|
3641
|
-
select.dataset.mobileRoute = "true";
|
|
3642
|
-
select.dataset.editorKey = "mobile-route";
|
|
3643
|
-
const projectOption = this.document.createElement("option");
|
|
3644
|
-
projectOption.value = "project";
|
|
3645
|
-
projectOption.textContent = "Project";
|
|
3646
|
-
select.append(projectOption);
|
|
3647
|
-
for (const group of this.draft.groups) {
|
|
3648
|
-
const option = this.document.createElement("option");
|
|
3649
|
-
option.value = "group";
|
|
3650
|
-
option.dataset.groupId = safeText(group.id);
|
|
3651
|
-
option.textContent = displayName(group.name, "Unnamed group");
|
|
3652
|
-
select.append(option);
|
|
3653
|
-
}
|
|
3654
|
-
const testOption = this.document.createElement("option");
|
|
3655
|
-
testOption.value = "test";
|
|
3656
|
-
testOption.textContent = "Test console";
|
|
3657
|
-
select.append(testOption);
|
|
3658
|
-
if (this.route.kind === "project") {
|
|
3659
|
-
select.value = "project";
|
|
3660
|
-
} else if (this.route.kind === "test") {
|
|
3661
|
-
select.value = "test";
|
|
3662
|
-
} else {
|
|
3663
|
-
const groupId = this.route.groupId;
|
|
3664
|
-
const options = Array.from(select.options);
|
|
3665
|
-
const option = options.find((value) => value.dataset.groupId === groupId);
|
|
3666
|
-
if (option) select.value = "group";
|
|
3667
|
-
}
|
|
3668
|
-
mobileNav.append(select);
|
|
3669
|
-
this.header.append(titleBlock, mobileNav);
|
|
3897
|
+
this.header.append(titleBlock);
|
|
3670
3898
|
this.status.textContent = this.statusMessage;
|
|
3671
3899
|
this.status.setAttribute("aria-busy", this.saving ? "true" : "false");
|
|
3672
3900
|
}
|
|
@@ -3676,30 +3904,34 @@ var EditorControllerImpl = class {
|
|
|
3676
3904
|
heading.dataset.editorVisuallyHidden = "true";
|
|
3677
3905
|
heading.textContent = "Project sections";
|
|
3678
3906
|
this.rail.append(heading);
|
|
3679
|
-
const
|
|
3907
|
+
const list = this.document.createElement("ol");
|
|
3908
|
+
list.dataset.routeBreadcrumb = "true";
|
|
3909
|
+
const projectItem = this.document.createElement("li");
|
|
3910
|
+
const project = this.createButton(
|
|
3911
|
+
displayName(this.draft.name, "Project"),
|
|
3912
|
+
"route:project"
|
|
3913
|
+
);
|
|
3680
3914
|
project.dataset.route = "project";
|
|
3681
3915
|
project.dataset.editorKey = "route:project";
|
|
3682
|
-
if (this.route.kind === "project")
|
|
3916
|
+
if (this.route.kind === "project") {
|
|
3683
3917
|
project.setAttribute("aria-current", "page");
|
|
3684
|
-
this.rail.append(project);
|
|
3685
|
-
for (const group of this.draft.groups) {
|
|
3686
|
-
const groupId = safeText(group.id);
|
|
3687
|
-
const name = displayName(group.name, "Unnamed group");
|
|
3688
|
-
const button = this.createButton(name, `route:group:${groupId}`);
|
|
3689
|
-
button.dataset.route = "group";
|
|
3690
|
-
button.dataset.groupId = groupId;
|
|
3691
|
-
button.dataset.editorKey = `route:group:${groupId}`;
|
|
3692
|
-
if (this.route.kind === "group" && this.route.groupId === groupId) {
|
|
3693
|
-
button.setAttribute("aria-current", "page");
|
|
3694
|
-
}
|
|
3695
|
-
this.rail.append(button);
|
|
3696
3918
|
}
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3919
|
+
projectItem.append(project);
|
|
3920
|
+
const groupItem = this.document.createElement("li");
|
|
3921
|
+
const openGroup = this.route.kind === "group" ? this.groupById(this.route.groupId) : void 0;
|
|
3922
|
+
const groups = this.createButton(
|
|
3923
|
+
openGroup ? displayName(openGroup.name, "Unnamed group") : "Groups",
|
|
3924
|
+
"route:groups"
|
|
3925
|
+
);
|
|
3926
|
+
groups.dataset.command = "open-group-picker";
|
|
3927
|
+
groups.dataset.editorKey = "route:groups";
|
|
3928
|
+
if (openGroup) {
|
|
3929
|
+
groups.dataset.groupId = safeText(openGroup.id);
|
|
3930
|
+
groups.setAttribute("aria-current", "page");
|
|
3931
|
+
}
|
|
3932
|
+
groupItem.append(groups);
|
|
3933
|
+
list.append(projectItem, groupItem);
|
|
3934
|
+
this.rail.append(list);
|
|
3703
3935
|
const searchWrap = this.document.createElement("div");
|
|
3704
3936
|
searchWrap.dataset.searchWrap = "true";
|
|
3705
3937
|
const searchLabel = this.document.createElement("label");
|
|
@@ -3731,18 +3963,18 @@ var EditorControllerImpl = class {
|
|
|
3731
3963
|
this.createCommandButton("AI Assist", "ai-assist", this.saving)
|
|
3732
3964
|
);
|
|
3733
3965
|
}
|
|
3734
|
-
|
|
3966
|
+
const testBtn = this.createButton("Test console", "route:test");
|
|
3967
|
+
testBtn.dataset.route = "test";
|
|
3968
|
+
testBtn.dataset.editorKey = "route:test";
|
|
3969
|
+
testBtn.dataset.btn = "secondary";
|
|
3970
|
+
testBtn.disabled = this.saving;
|
|
3735
3971
|
if (this.route.kind === "test") {
|
|
3736
|
-
|
|
3737
|
-
this.createCommandButton(
|
|
3738
|
-
"Run test",
|
|
3739
|
-
"test:run",
|
|
3740
|
-
this.saving || this.testRunning,
|
|
3741
|
-
{},
|
|
3742
|
-
"primary"
|
|
3743
|
-
)
|
|
3744
|
-
);
|
|
3972
|
+
testBtn.setAttribute("aria-current", "page");
|
|
3745
3973
|
}
|
|
3974
|
+
tools.append(
|
|
3975
|
+
testBtn,
|
|
3976
|
+
this.createCommandButton("Validate", "validate", this.saving)
|
|
3977
|
+
);
|
|
3746
3978
|
const commit = this.document.createElement("div");
|
|
3747
3979
|
commit.dataset.actionCluster = "commit";
|
|
3748
3980
|
commit.append(
|
|
@@ -3890,19 +4122,6 @@ var EditorControllerImpl = class {
|
|
|
3890
4122
|
this.createGroupActionClusters(groupId, groupName)
|
|
3891
4123
|
);
|
|
3892
4124
|
}
|
|
3893
|
-
} else {
|
|
3894
|
-
const run = this.document.createElement("div");
|
|
3895
|
-
run.dataset.actionCluster = "safe";
|
|
3896
|
-
run.append(
|
|
3897
|
-
this.createCommandButton(
|
|
3898
|
-
"Run test",
|
|
3899
|
-
"test:run",
|
|
3900
|
-
this.saving || this.testRunning,
|
|
3901
|
-
{},
|
|
3902
|
-
"primary"
|
|
3903
|
-
)
|
|
3904
|
-
);
|
|
3905
|
-
this.appendDockRow(dock, "Test", run);
|
|
3906
4125
|
}
|
|
3907
4126
|
this.appendDockRow(dock, "Project", this.createProjectActionRow());
|
|
3908
4127
|
this.form.append(dock);
|
|
@@ -3932,6 +4151,7 @@ var EditorControllerImpl = class {
|
|
|
3932
4151
|
description
|
|
3933
4152
|
);
|
|
3934
4153
|
fields.append(fieldGrid);
|
|
4154
|
+
this.renderProjectActions(fields);
|
|
3935
4155
|
this.form.append(fields);
|
|
3936
4156
|
const groups = this.document.createElement("section");
|
|
3937
4157
|
groups.dataset.groupListSection = "true";
|
|
@@ -4055,7 +4275,7 @@ var EditorControllerImpl = class {
|
|
|
4055
4275
|
this.form.append(heading);
|
|
4056
4276
|
const description = this.document.createElement("p");
|
|
4057
4277
|
description.dataset.testDescription = "true";
|
|
4058
|
-
description.textContent = "
|
|
4278
|
+
description.textContent = "Check whether these URLs match your rules. Nothing is contacted, and nothing is saved.";
|
|
4059
4279
|
this.form.append(description);
|
|
4060
4280
|
const panel = this.document.createElement("div");
|
|
4061
4281
|
panel.dataset.testPanel = "true";
|
|
@@ -4072,91 +4292,128 @@ var EditorControllerImpl = class {
|
|
|
4072
4292
|
urlsTextarea.placeholder = "https://example.com/page\nhttps://example.com/script.js\nhttps://other.com/";
|
|
4073
4293
|
urlsTextarea.value = this.testUrls;
|
|
4074
4294
|
urlsTextarea.dataset.testUrls = "true";
|
|
4295
|
+
urlsTextarea.dataset.editorKey = "test-urls";
|
|
4075
4296
|
urlsLabel.append(urlsTextarea);
|
|
4076
4297
|
urlsField.append(urlsLabel);
|
|
4077
4298
|
urlsFieldset.append(urlsField);
|
|
4299
|
+
const samples = this.renderSampleUrlButtons();
|
|
4300
|
+
if (samples) urlsFieldset.append(samples);
|
|
4078
4301
|
panel.append(urlsFieldset);
|
|
4079
|
-
const
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4302
|
+
const checking = this.document.createElement("p");
|
|
4303
|
+
checking.dataset.testChecking = "true";
|
|
4304
|
+
checking.textContent = checkingCopy(this.testMethod, this.testResourceType);
|
|
4305
|
+
panel.append(checking);
|
|
4083
4306
|
const defaultsGrid = this.document.createElement("div");
|
|
4084
4307
|
defaultsGrid.dataset.testDefaults = "true";
|
|
4085
4308
|
const methodField = this.document.createElement("div");
|
|
4086
4309
|
methodField.dataset.editorField = "true";
|
|
4087
4310
|
const methodLabel = this.document.createElement("label");
|
|
4088
|
-
methodLabel.textContent = "
|
|
4311
|
+
methodLabel.textContent = "Method";
|
|
4089
4312
|
const methodSelect = this.document.createElement("select");
|
|
4090
4313
|
methodSelect.dataset.testMethod = "true";
|
|
4314
|
+
methodSelect.dataset.editorKey = "test-method";
|
|
4315
|
+
for (const method of HTTP_METHODS2) {
|
|
4316
|
+
const option = this.document.createElement("option");
|
|
4317
|
+
option.value = method;
|
|
4318
|
+
option.textContent = method;
|
|
4319
|
+
methodSelect.append(option);
|
|
4320
|
+
}
|
|
4321
|
+
const methodAny = this.document.createElement("option");
|
|
4322
|
+
methodAny.value = "";
|
|
4323
|
+
methodAny.textContent = "Any method";
|
|
4324
|
+
methodSelect.append(methodAny);
|
|
4091
4325
|
methodSelect.value = this.testMethod;
|
|
4092
|
-
const methodEmpty = this.document.createElement("option");
|
|
4093
|
-
methodEmpty.value = "";
|
|
4094
|
-
methodEmpty.textContent = "(none \u2014 not-applicable)";
|
|
4095
|
-
methodSelect.append(methodEmpty);
|
|
4096
|
-
for (const m of HTTP_METHODS2) {
|
|
4097
|
-
const opt = this.document.createElement("option");
|
|
4098
|
-
opt.value = m;
|
|
4099
|
-
opt.textContent = m;
|
|
4100
|
-
methodSelect.append(opt);
|
|
4101
|
-
}
|
|
4102
4326
|
methodLabel.append(methodSelect);
|
|
4103
4327
|
methodField.append(methodLabel);
|
|
4104
4328
|
const rtField = this.document.createElement("div");
|
|
4105
4329
|
rtField.dataset.editorField = "true";
|
|
4106
4330
|
const rtLabel = this.document.createElement("label");
|
|
4107
|
-
rtLabel.textContent = "
|
|
4331
|
+
rtLabel.textContent = "Resource type";
|
|
4108
4332
|
const rtSelect = this.document.createElement("select");
|
|
4109
4333
|
rtSelect.dataset.testResourceType = "true";
|
|
4334
|
+
rtSelect.dataset.editorKey = "test-resource-type";
|
|
4335
|
+
for (const resourceType of RESOURCE_TYPES2) {
|
|
4336
|
+
const option = this.document.createElement("option");
|
|
4337
|
+
option.value = resourceType;
|
|
4338
|
+
option.textContent = RESOURCE_TYPE_LABELS[resourceType];
|
|
4339
|
+
rtSelect.append(option);
|
|
4340
|
+
}
|
|
4341
|
+
const rtAny = this.document.createElement("option");
|
|
4342
|
+
rtAny.value = "";
|
|
4343
|
+
rtAny.textContent = "Any resource type";
|
|
4344
|
+
rtSelect.append(rtAny);
|
|
4110
4345
|
rtSelect.value = this.testResourceType;
|
|
4111
|
-
const rtEmpty = this.document.createElement("option");
|
|
4112
|
-
rtEmpty.value = "";
|
|
4113
|
-
rtEmpty.textContent = "(none \u2014 not-applicable)";
|
|
4114
|
-
rtSelect.append(rtEmpty);
|
|
4115
|
-
for (const rt of RESOURCE_TYPES2) {
|
|
4116
|
-
const opt = this.document.createElement("option");
|
|
4117
|
-
opt.value = rt;
|
|
4118
|
-
opt.textContent = rt;
|
|
4119
|
-
rtSelect.append(opt);
|
|
4120
|
-
}
|
|
4121
4346
|
rtLabel.append(rtSelect);
|
|
4122
4347
|
rtField.append(rtLabel);
|
|
4123
4348
|
defaultsGrid.append(methodField, rtField);
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
maxCasesInput.min = "1";
|
|
4133
|
-
maxCasesInput.max = "10000";
|
|
4134
|
-
maxCasesInput.value = this.testMaxCases;
|
|
4135
|
-
maxCasesInput.style.width = "8rem";
|
|
4136
|
-
maxCasesInput.dataset.testMaxCases = "true";
|
|
4137
|
-
maxCasesLabel.append(maxCasesInput);
|
|
4138
|
-
maxCasesField.append(maxCasesLabel);
|
|
4139
|
-
panel.append(maxCasesField);
|
|
4140
|
-
const runBtn = this.createButton("Run test", "test:run");
|
|
4141
|
-
runBtn.type = "button";
|
|
4349
|
+
panel.append(defaultsGrid);
|
|
4350
|
+
const runBtn = this.createCommandButton(
|
|
4351
|
+
"Run test",
|
|
4352
|
+
"test:run",
|
|
4353
|
+
this.saving || this.testRunning,
|
|
4354
|
+
{},
|
|
4355
|
+
"primary"
|
|
4356
|
+
);
|
|
4142
4357
|
runBtn.dataset.testRun = "true";
|
|
4143
|
-
runBtn.dataset.command = "test:run";
|
|
4144
|
-
runBtn.dataset.btn = "primary";
|
|
4145
|
-
runBtn.disabled = this.saving || this.testRunning;
|
|
4146
4358
|
panel.append(runBtn);
|
|
4147
4359
|
this.form.append(panel);
|
|
4148
4360
|
const resultsSection = this.document.createElement("section");
|
|
4149
4361
|
resultsSection.dataset.testResults = "true";
|
|
4150
4362
|
this.form.append(resultsSection);
|
|
4151
|
-
if (this.
|
|
4363
|
+
if (this.ruleCount() === 0) {
|
|
4364
|
+
const empty = this.document.createElement("p");
|
|
4365
|
+
empty.dataset.testEmpty = "true";
|
|
4366
|
+
empty.textContent = "This project has no rules to test.";
|
|
4367
|
+
resultsSection.append(empty);
|
|
4368
|
+
} else if (this.testResult) {
|
|
4152
4369
|
this.renderTestResults(this.testResult);
|
|
4153
4370
|
} else if (this.testRunning) {
|
|
4154
4371
|
const pending = this.document.createElement("p");
|
|
4155
4372
|
pending.dataset.testPending = "true";
|
|
4156
|
-
pending.textContent = "
|
|
4373
|
+
pending.textContent = "Checking URLs...";
|
|
4157
4374
|
resultsSection.append(pending);
|
|
4375
|
+
} else if (this.statusMessage === "Only 256 URLs can be checked at once.") {
|
|
4376
|
+
const limit = this.document.createElement("p");
|
|
4377
|
+
limit.dataset.testLimit = "true";
|
|
4378
|
+
limit.textContent = this.statusMessage;
|
|
4379
|
+
resultsSection.append(limit);
|
|
4158
4380
|
}
|
|
4159
4381
|
}
|
|
4382
|
+
renderSampleUrlButtons() {
|
|
4383
|
+
const samples = [];
|
|
4384
|
+
for (const group of this.draft.groups) {
|
|
4385
|
+
const groupId = safeText(group.id);
|
|
4386
|
+
for (const rule of group.rules) {
|
|
4387
|
+
const url = exactUrlFromSource(rule.source);
|
|
4388
|
+
if (!url) continue;
|
|
4389
|
+
samples.push({
|
|
4390
|
+
groupId,
|
|
4391
|
+
ruleId: safeText(rule.id),
|
|
4392
|
+
url,
|
|
4393
|
+
name: displayName(rule.name, "Unnamed rule")
|
|
4394
|
+
});
|
|
4395
|
+
}
|
|
4396
|
+
}
|
|
4397
|
+
if (samples.length === 0) return void 0;
|
|
4398
|
+
const row = this.document.createElement("div");
|
|
4399
|
+
row.dataset.testSamples = "true";
|
|
4400
|
+
for (const sample of samples) {
|
|
4401
|
+
const button = this.createCommandButton(
|
|
4402
|
+
"Try a URL from this rule",
|
|
4403
|
+
"test:try-url",
|
|
4404
|
+
false,
|
|
4405
|
+
{ groupId: sample.groupId, ruleId: sample.ruleId, url: sample.url }
|
|
4406
|
+
);
|
|
4407
|
+
if (samples.length > 1) {
|
|
4408
|
+
button.setAttribute(
|
|
4409
|
+
"aria-label",
|
|
4410
|
+
`Try a URL from this rule, ${sample.name}`
|
|
4411
|
+
);
|
|
4412
|
+
}
|
|
4413
|
+
row.append(button);
|
|
4414
|
+
}
|
|
4415
|
+
return row;
|
|
4416
|
+
}
|
|
4160
4417
|
renderRule(group, rule, groupIndex, ruleIndex) {
|
|
4161
4418
|
const groupId = safeText(group.id);
|
|
4162
4419
|
const ruleId = safeText(rule.id);
|
|
@@ -5056,6 +5313,103 @@ var EditorControllerImpl = class {
|
|
|
5056
5313
|
}
|
|
5057
5314
|
return results;
|
|
5058
5315
|
}
|
|
5316
|
+
renderProjectActions(fields) {
|
|
5317
|
+
if (this.projectActions.length === 0) return;
|
|
5318
|
+
const group = this.document.createElement("div");
|
|
5319
|
+
group.dataset.projectActions = "true";
|
|
5320
|
+
group.setAttribute("role", "group");
|
|
5321
|
+
group.setAttribute("aria-label", "Project settings");
|
|
5322
|
+
for (const action of this.projectActions) {
|
|
5323
|
+
group.append(
|
|
5324
|
+
this.createCommandButton(
|
|
5325
|
+
action.label,
|
|
5326
|
+
action.command,
|
|
5327
|
+
false,
|
|
5328
|
+
{},
|
|
5329
|
+
action.tone === "danger" ? "danger" : "secondary"
|
|
5330
|
+
)
|
|
5331
|
+
);
|
|
5332
|
+
}
|
|
5333
|
+
fields.append(group);
|
|
5334
|
+
}
|
|
5335
|
+
openGroupPicker() {
|
|
5336
|
+
this.groupPickerOpen = true;
|
|
5337
|
+
this.focusRequest = this.draft.groups.length > 0 ? "group-picker-first" : "group-picker-close";
|
|
5338
|
+
this.render();
|
|
5339
|
+
}
|
|
5340
|
+
closeGroupPicker() {
|
|
5341
|
+
this.groupPickerOpen = false;
|
|
5342
|
+
this.focusRequest = "route:groups";
|
|
5343
|
+
this.render();
|
|
5344
|
+
}
|
|
5345
|
+
renderGroupPicker() {
|
|
5346
|
+
const existing = this.host.querySelector("[data-group-picker]");
|
|
5347
|
+
existing?.remove();
|
|
5348
|
+
if (!this.groupPickerOpen) return;
|
|
5349
|
+
const overlay = this.document.createElement("div");
|
|
5350
|
+
overlay.dataset.groupPicker = "true";
|
|
5351
|
+
overlay.setAttribute("role", "presentation");
|
|
5352
|
+
const dialog = this.document.createElement("section");
|
|
5353
|
+
dialog.dataset.editorDialog = "true";
|
|
5354
|
+
dialog.setAttribute("role", "dialog");
|
|
5355
|
+
dialog.setAttribute("aria-modal", "true");
|
|
5356
|
+
const title = this.document.createElement("h2");
|
|
5357
|
+
title.id = `${this.instanceId}-group-picker-title`;
|
|
5358
|
+
title.textContent = "Groups";
|
|
5359
|
+
dialog.setAttribute("aria-labelledby", title.id);
|
|
5360
|
+
dialog.append(title);
|
|
5361
|
+
if (this.draft.groups.length === 0) {
|
|
5362
|
+
const empty = this.document.createElement("p");
|
|
5363
|
+
empty.textContent = "No groups yet.";
|
|
5364
|
+
dialog.append(empty);
|
|
5365
|
+
} else {
|
|
5366
|
+
const list = this.document.createElement("ul");
|
|
5367
|
+
list.dataset.groupPickerList = "true";
|
|
5368
|
+
for (let index = 0; index < this.draft.groups.length; index += 1) {
|
|
5369
|
+
const group = this.draft.groups[index];
|
|
5370
|
+
if (!group) continue;
|
|
5371
|
+
const groupId = safeText(group.id);
|
|
5372
|
+
const name = displayName(group.name, "Unnamed group");
|
|
5373
|
+
const item = this.document.createElement("li");
|
|
5374
|
+
const button = this.document.createElement("button");
|
|
5375
|
+
button.type = "button";
|
|
5376
|
+
button.dataset.route = "group";
|
|
5377
|
+
button.dataset.groupId = groupId;
|
|
5378
|
+
button.dataset.btn = "secondary";
|
|
5379
|
+
button.dataset.editorKey = index === 0 ? "group-picker-first" : `group-picker:${groupId}`;
|
|
5380
|
+
if (this.route.kind === "group" && this.route.groupId === groupId) {
|
|
5381
|
+
button.setAttribute("aria-current", "true");
|
|
5382
|
+
}
|
|
5383
|
+
const nameEl = this.document.createElement("span");
|
|
5384
|
+
nameEl.dataset.groupPickerName = "true";
|
|
5385
|
+
nameEl.textContent = name;
|
|
5386
|
+
const rulesEl = this.document.createElement("span");
|
|
5387
|
+
rulesEl.dataset.groupPickerRules = "true";
|
|
5388
|
+
rulesEl.textContent = ruleCountLabel(group.rules.length);
|
|
5389
|
+
button.append(nameEl, rulesEl);
|
|
5390
|
+
const savedId = this.savedGroupId(group);
|
|
5391
|
+
if (this.options.groupEnablement && savedId) {
|
|
5392
|
+
const stateEl = this.document.createElement("span");
|
|
5393
|
+
stateEl.dataset.groupPickerState = "true";
|
|
5394
|
+
stateEl.textContent = this.options.groupEnablement.isEnabled(savedId) ? "Enabled" : "Disabled";
|
|
5395
|
+
button.append(stateEl);
|
|
5396
|
+
}
|
|
5397
|
+
item.append(button);
|
|
5398
|
+
list.append(item);
|
|
5399
|
+
}
|
|
5400
|
+
dialog.append(list);
|
|
5401
|
+
}
|
|
5402
|
+
const actions = this.document.createElement("div");
|
|
5403
|
+
actions.dataset.editorDialogActions = "true";
|
|
5404
|
+
const close = this.createButton("Close", "group-picker-close");
|
|
5405
|
+
close.dataset.command = "close-group-picker";
|
|
5406
|
+
close.dataset.editorKey = "group-picker-close";
|
|
5407
|
+
close.dataset.btn = "secondary";
|
|
5408
|
+
actions.append(close);
|
|
5409
|
+
dialog.append(actions);
|
|
5410
|
+
overlay.append(dialog);
|
|
5411
|
+
this.host.append(overlay);
|
|
5412
|
+
}
|
|
5059
5413
|
renderConfirmation() {
|
|
5060
5414
|
const existing = this.host.querySelector("[data-editor-confirmation]");
|
|
5061
5415
|
existing?.remove();
|