@usefillo/dom 0.16.1 → 0.19.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/README.md +1 -1
- package/dist/index.d.ts +13 -1
- package/dist/index.js +206 -21
- package/dist/standalone.global.js +1 -1
- package/dist/styles.css +67 -40
- package/dist/styles.unlayered.css +67 -40
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -65,7 +65,7 @@ Or drop it in with a single script tag — the bundle exposes a global `Fillo`:
|
|
|
65
65
|
</script>
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
-
The default stylesheet
|
|
68
|
+
The default stylesheet inherits the host page's CSS `color-scheme` and font. If your theme switch only toggles a class or data attribute, pass its resolved mode as `theme: { colorScheme: "light" | "dark" }`. Use `"auto"` only for a deliberately system-driven page. A fixed hex `background` automatically selects a readable palette unless you explicitly choose one.
|
|
69
69
|
|
|
70
70
|
Every rendered part carries `data-*` state attributes (`data-selected`, `data-invalid`, …) for utility CSS, and the default stylesheet is cascade-layered so your own styles win. On Tailwind v3 or reset-heavy sites import `@usefillo/dom/styles.unlayered.css` instead. Styling contract: [fillo.so/docs/styling](https://fillo.so/docs/styling).
|
|
71
71
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Field, FieldValue, FormSchema, FilloClient, ResponseData, FormStatus as FormStatus$1, FormPage, Block, FilloRespondent, CodeForm, FormTheme, ChallengeConfig, FieldKind, FilloError, FormControllerOptions, FormController } from '@usefillo/core';
|
|
1
|
+
import { Field, FieldValue, FormSchema, FilloClient, ResponseData, FormStatus as FormStatus$1, FormPage, Block, FilloRespondent, CodeForm, FormTheme, ChallengeConfig, ChallengeTheme, FieldKind, FilloError, FormControllerOptions, FormController } from '@usefillo/core';
|
|
2
2
|
export { CodeForm, Field, FieldKind, FieldValue, FileValue, FilloClient, FilloClientOptions, FilloError, FormController, FormControllerOptions, FormControllerState, FormSchema, FormTheme, ProvisionWorkspaceResult, ResponseData, createClient, defineForm, provisionWorkspace } from '@usefillo/core';
|
|
3
3
|
|
|
4
4
|
type FormStatus = FormStatus$1 | "loading" | "closed";
|
|
@@ -59,6 +59,18 @@ interface RenderFormBaseOptions {
|
|
|
59
59
|
* hosted page does this). Wins over anything the server delivers.
|
|
60
60
|
*/
|
|
61
61
|
challenge?: ChallengeConfig;
|
|
62
|
+
/**
|
|
63
|
+
* Theme for the human-verification widget. Defaults to "auto" (the
|
|
64
|
+
* visitor's OS preference); apps with their own theme switch pass
|
|
65
|
+
* "light"/"dark" so the widget matches the surrounding form.
|
|
66
|
+
*/
|
|
67
|
+
challengeTheme?: ChallengeTheme;
|
|
68
|
+
/**
|
|
69
|
+
* Bridge-mode visibility for the human check. "interaction-only" (default)
|
|
70
|
+
* keeps it invisible unless Cloudflare needs the visitor to act; "always"
|
|
71
|
+
* shows the classic widget box the whole time.
|
|
72
|
+
*/
|
|
73
|
+
challengeAppearance?: "always" | "interaction-only";
|
|
62
74
|
className?: string;
|
|
63
75
|
components?: Partial<Record<FieldKind, FieldRenderer>>;
|
|
64
76
|
customComponents?: Record<string, FieldRenderer>;
|
package/dist/index.js
CHANGED
|
@@ -1662,7 +1662,10 @@ function repeatingGroup(context) {
|
|
|
1662
1662
|
removeBtn.toggleAttribute("disabled", !canRemove);
|
|
1663
1663
|
if (!canRemove) {
|
|
1664
1664
|
removeBtn.setAttribute("aria-disabled", "true");
|
|
1665
|
-
removeBtn.setAttribute(
|
|
1665
|
+
removeBtn.setAttribute(
|
|
1666
|
+
"title",
|
|
1667
|
+
`At least ${floor} ${floor === 1 ? "entry" : "entries"} required`
|
|
1668
|
+
);
|
|
1666
1669
|
}
|
|
1667
1670
|
removeBtn.addEventListener("click", () => {
|
|
1668
1671
|
if (count <= floor) return;
|
|
@@ -1758,6 +1761,8 @@ function defaultFieldRenderer(context) {
|
|
|
1758
1761
|
}
|
|
1759
1762
|
var TURNSTILE_SCRIPT_SRC = "https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit";
|
|
1760
1763
|
var TURNSTILE_SCRIPT_ID = "fillo-turnstile-script";
|
|
1764
|
+
var BRIDGE_READY_TIMEOUT_MS = 2e4;
|
|
1765
|
+
var CHALLENGE_CDATA_RE = /^[A-Za-z0-9_-]{1,255}$/;
|
|
1761
1766
|
var turnstileScriptPromise = null;
|
|
1762
1767
|
function turnstileGlobal() {
|
|
1763
1768
|
return globalThis.turnstile;
|
|
@@ -1883,6 +1888,15 @@ var DomFormController = class {
|
|
|
1883
1888
|
// Invalidated on teardown so a stale load/render callback can't land after
|
|
1884
1889
|
// a later attempt replaced the container (or double-render into it).
|
|
1885
1890
|
turnstileAttempt = 0;
|
|
1891
|
+
// Bridge mode (challenge.bridgeUrl): the Fillo-hosted iframe that runs the
|
|
1892
|
+
// widget on Fillo's OWN hostname (so the check works on any embedding
|
|
1893
|
+
// domain) plus its postMessage wiring. Mutually exclusive with the direct
|
|
1894
|
+
// Cloudflare-script fields above; teardownTurnstileWidget() clears both.
|
|
1895
|
+
challengeFrame = null;
|
|
1896
|
+
challengeFrameSrc = null;
|
|
1897
|
+
challengeFrameOrigin = null;
|
|
1898
|
+
challengeMessageHandler = null;
|
|
1899
|
+
challengeWatchdog = null;
|
|
1886
1900
|
// Clear-then-set (one frame apart) so an identical repeat still re-fires —
|
|
1887
1901
|
// a same-tick ""→text write can collapse into a no-op net change for AT.
|
|
1888
1902
|
announce = (text) => {
|
|
@@ -1958,6 +1972,8 @@ var DomFormController = class {
|
|
|
1958
1972
|
* substitutes for the explicit uploadsAvailable control state. */
|
|
1959
1973
|
syncWarningCode;
|
|
1960
1974
|
syncWarningUrl;
|
|
1975
|
+
/** Dashboard form overview containing the Publish action — dev chrome only. */
|
|
1976
|
+
syncFormUrl;
|
|
1961
1977
|
/** Server-authoritative ability to start a new file upload. */
|
|
1962
1978
|
uploadsAvailable;
|
|
1963
1979
|
/** Server-authoritative per-file ceiling for the active storage lane. */
|
|
@@ -1983,6 +1999,7 @@ var DomFormController = class {
|
|
|
1983
1999
|
const result = await syncCodeForm(client, codeForm, { bypassCache: true });
|
|
1984
2000
|
this.syncWarningCode = result.warningCode;
|
|
1985
2001
|
this.syncWarningUrl = result.warningUrl;
|
|
2002
|
+
this.syncFormUrl = result.manageUrl;
|
|
1986
2003
|
this.uploadsAvailable = result.uploadsAvailable;
|
|
1987
2004
|
this.uploadFileSizeLimitMb = result.uploadFileSizeLimitMb;
|
|
1988
2005
|
if (result.status === "draft") {
|
|
@@ -2083,6 +2100,7 @@ var DomFormController = class {
|
|
|
2083
2100
|
this.draftNotice = result.status === "draft";
|
|
2084
2101
|
this.syncWarningCode = result.warningCode;
|
|
2085
2102
|
this.syncWarningUrl = result.warningUrl;
|
|
2103
|
+
this.syncFormUrl = result.manageUrl;
|
|
2086
2104
|
this.uploadsAvailable = result.uploadsAvailable;
|
|
2087
2105
|
this.uploadFileSizeLimitMb = result.uploadFileSizeLimitMb;
|
|
2088
2106
|
this.challenge = this.options.challenge ?? result.challenge;
|
|
@@ -2413,7 +2431,7 @@ var DomFormController = class {
|
|
|
2413
2431
|
} catch (error) {
|
|
2414
2432
|
if (this.destroyed || controller.signal.aborted) return;
|
|
2415
2433
|
const uploadError = toFilloError(error);
|
|
2416
|
-
const message = uploadError.status !== void 0 && uploadError.status >= 500 ? DEFAULT_FIELD_STRINGS.uploadUnavailable :
|
|
2434
|
+
const message = uploadError.status === 0 || uploadError.status !== void 0 && uploadError.status >= 500 ? DEFAULT_FIELD_STRINGS.uploadUnavailable : DEFAULT_FIELD_STRINGS.uploadFailed;
|
|
2417
2435
|
const row = this.inFlight.get(key);
|
|
2418
2436
|
if (row) row.error = message;
|
|
2419
2437
|
this.announceAlert(message);
|
|
@@ -2503,6 +2521,142 @@ var DomFormController = class {
|
|
|
2503
2521
|
}
|
|
2504
2522
|
this.overlays.clear();
|
|
2505
2523
|
}
|
|
2524
|
+
/** Route the challenge to bridge or direct mode per the server's config.
|
|
2525
|
+
* Bridge (bridgeUrl present — every current server) works on any embedding
|
|
2526
|
+
* domain; direct is the pre-bridge fallback for older self-hosted servers.
|
|
2527
|
+
* Message contract mirrors @usefillo/react's turnstile module; keep both
|
|
2528
|
+
* in sync with apps/web src/app/embed/challenge/route.ts. */
|
|
2529
|
+
ensureChallengeWidget(challenge) {
|
|
2530
|
+
if (challenge.bridgeUrl) this.ensureChallengeBridge(challenge.bridgeUrl);
|
|
2531
|
+
else this.ensureTurnstileWidget(challenge.siteKey);
|
|
2532
|
+
}
|
|
2533
|
+
/** Idempotent per src: mounts the Fillo-hosted bridge iframe into the
|
|
2534
|
+
* persistent container and wires its postMessage token flow. */
|
|
2535
|
+
ensureChallengeBridge(bridgeUrl) {
|
|
2536
|
+
if (typeof window === "undefined") return;
|
|
2537
|
+
let url;
|
|
2538
|
+
try {
|
|
2539
|
+
url = new URL(bridgeUrl);
|
|
2540
|
+
} catch {
|
|
2541
|
+
this.handleChallengeError();
|
|
2542
|
+
return;
|
|
2543
|
+
}
|
|
2544
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") {
|
|
2545
|
+
this.handleChallengeError();
|
|
2546
|
+
return;
|
|
2547
|
+
}
|
|
2548
|
+
url.searchParams.set("origin", window.location.origin);
|
|
2549
|
+
url.searchParams.set("theme", this.options.challengeTheme ?? "auto");
|
|
2550
|
+
if (this.formId && CHALLENGE_CDATA_RE.test(this.formId)) {
|
|
2551
|
+
url.searchParams.set("cdata", this.formId);
|
|
2552
|
+
}
|
|
2553
|
+
url.searchParams.set(
|
|
2554
|
+
"appearance",
|
|
2555
|
+
(this.options.challengeAppearance ?? "interaction-only") === "interaction-only" ? "interaction-only" : "always"
|
|
2556
|
+
);
|
|
2557
|
+
const src = url.toString();
|
|
2558
|
+
if (this.challengeFrame && this.challengeFrameSrc === src) return;
|
|
2559
|
+
this.teardownTurnstileWidget();
|
|
2560
|
+
const attempt = ++this.turnstileAttempt;
|
|
2561
|
+
const frame = el("iframe", {
|
|
2562
|
+
className: "fillo-turnstile-frame",
|
|
2563
|
+
attrs: {
|
|
2564
|
+
src,
|
|
2565
|
+
title: "Human verification",
|
|
2566
|
+
// Cloudflare's normal widget footprint; attributes so host CSS wins.
|
|
2567
|
+
width: "300",
|
|
2568
|
+
height: "65",
|
|
2569
|
+
referrerpolicy: "no-referrer"
|
|
2570
|
+
}
|
|
2571
|
+
});
|
|
2572
|
+
this.challengeFrame = frame;
|
|
2573
|
+
this.challengeFrameSrc = src;
|
|
2574
|
+
this.challengeFrameOrigin = url.origin;
|
|
2575
|
+
this.turnstileContainer.appendChild(frame);
|
|
2576
|
+
this.setChallengeFrameVisible(
|
|
2577
|
+
(this.options.challengeAppearance ?? "interaction-only") !== "interaction-only"
|
|
2578
|
+
);
|
|
2579
|
+
const onMessage = (event) => {
|
|
2580
|
+
if (attempt !== this.turnstileAttempt || this.destroyed) return;
|
|
2581
|
+
if (event.origin !== this.challengeFrameOrigin) return;
|
|
2582
|
+
if (!this.challengeFrame || event.source !== this.challengeFrame.contentWindow) return;
|
|
2583
|
+
const data = event.data;
|
|
2584
|
+
switch (data?.type) {
|
|
2585
|
+
case "fillo:challenge:ready":
|
|
2586
|
+
this.settleChallengeWatchdog();
|
|
2587
|
+
break;
|
|
2588
|
+
case "fillo:challenge:token":
|
|
2589
|
+
this.settleChallengeWatchdog();
|
|
2590
|
+
if (typeof data.token === "string" && data.token.length > 0) {
|
|
2591
|
+
this.handleChallengeToken(data.token);
|
|
2592
|
+
}
|
|
2593
|
+
if ((this.options.challengeAppearance ?? "interaction-only") === "interaction-only") {
|
|
2594
|
+
this.setChallengeFrameVisible(false);
|
|
2595
|
+
}
|
|
2596
|
+
break;
|
|
2597
|
+
case "fillo:challenge:interactive":
|
|
2598
|
+
this.setChallengeFrameVisible(true);
|
|
2599
|
+
break;
|
|
2600
|
+
case "fillo:challenge:interactive-done":
|
|
2601
|
+
if ((this.options.challengeAppearance ?? "interaction-only") === "interaction-only") {
|
|
2602
|
+
this.setChallengeFrameVisible(false);
|
|
2603
|
+
}
|
|
2604
|
+
break;
|
|
2605
|
+
case "fillo:challenge:expired":
|
|
2606
|
+
this.handleChallengeToken(void 0);
|
|
2607
|
+
break;
|
|
2608
|
+
case "fillo:challenge:error":
|
|
2609
|
+
this.settleChallengeWatchdog();
|
|
2610
|
+
this.handleChallengeToken(void 0);
|
|
2611
|
+
this.handleChallengeError();
|
|
2612
|
+
break;
|
|
2613
|
+
}
|
|
2614
|
+
};
|
|
2615
|
+
this.challengeMessageHandler = onMessage;
|
|
2616
|
+
window.addEventListener("message", onMessage);
|
|
2617
|
+
this.challengeWatchdog = window.setTimeout(() => {
|
|
2618
|
+
if (attempt !== this.turnstileAttempt || this.destroyed) return;
|
|
2619
|
+
this.handleChallengeToken(void 0);
|
|
2620
|
+
this.handleChallengeError();
|
|
2621
|
+
}, BRIDGE_READY_TIMEOUT_MS);
|
|
2622
|
+
}
|
|
2623
|
+
/** Collapse/expand the bridge frame (interaction-only mode). The frame stays
|
|
2624
|
+
* mounted either way — height 0 keeps the invisible check running. */
|
|
2625
|
+
setChallengeFrameVisible(visible) {
|
|
2626
|
+
const frame = this.challengeFrame;
|
|
2627
|
+
if (!frame) return;
|
|
2628
|
+
frame.setAttribute("height", visible ? "65" : "0");
|
|
2629
|
+
if (visible) {
|
|
2630
|
+
frame.removeAttribute("aria-hidden");
|
|
2631
|
+
frame.removeAttribute("tabindex");
|
|
2632
|
+
} else {
|
|
2633
|
+
frame.setAttribute("aria-hidden", "true");
|
|
2634
|
+
frame.setAttribute("tabindex", "-1");
|
|
2635
|
+
}
|
|
2636
|
+
this.turnstileContainer.setAttribute(
|
|
2637
|
+
"data-fillo-challenge-visible",
|
|
2638
|
+
visible ? "true" : "false"
|
|
2639
|
+
);
|
|
2640
|
+
}
|
|
2641
|
+
settleChallengeWatchdog() {
|
|
2642
|
+
if (this.challengeWatchdog !== null && typeof window !== "undefined") {
|
|
2643
|
+
window.clearTimeout(this.challengeWatchdog);
|
|
2644
|
+
}
|
|
2645
|
+
this.challengeWatchdog = null;
|
|
2646
|
+
}
|
|
2647
|
+
/** Unwire and remove the bridge frame (listener, watchdog, iframe). Safe to
|
|
2648
|
+
* call when nothing is mounted. */
|
|
2649
|
+
teardownChallengeBridge() {
|
|
2650
|
+
if (this.challengeMessageHandler && typeof window !== "undefined") {
|
|
2651
|
+
window.removeEventListener("message", this.challengeMessageHandler);
|
|
2652
|
+
}
|
|
2653
|
+
this.challengeMessageHandler = null;
|
|
2654
|
+
this.settleChallengeWatchdog();
|
|
2655
|
+
this.challengeFrame?.remove();
|
|
2656
|
+
this.challengeFrame = null;
|
|
2657
|
+
this.challengeFrameSrc = null;
|
|
2658
|
+
this.challengeFrameOrigin = null;
|
|
2659
|
+
}
|
|
2506
2660
|
/** Idempotent: at most one load+render per armed container (guarded by
|
|
2507
2661
|
* widget id / in-flight / already-failed); re-arms on a changed site key
|
|
2508
2662
|
* (mirrors react's effect, keyed on siteKey). */
|
|
@@ -2558,7 +2712,14 @@ var DomFormController = class {
|
|
|
2558
2712
|
* expiry, or timeout) — not a teardown, the widget stays mounted. */
|
|
2559
2713
|
resetChallengeWidget() {
|
|
2560
2714
|
this.challengeToken = void 0;
|
|
2561
|
-
if (this.
|
|
2715
|
+
if (this.challengeFrame && this.challengeFrameOrigin) {
|
|
2716
|
+
this.challengeFrame.contentWindow?.postMessage(
|
|
2717
|
+
{ type: "fillo:challenge:reset" },
|
|
2718
|
+
this.challengeFrameOrigin
|
|
2719
|
+
);
|
|
2720
|
+
} else if (this.turnstileWidgetId) {
|
|
2721
|
+
turnstileGlobal()?.reset(this.turnstileWidgetId);
|
|
2722
|
+
}
|
|
2562
2723
|
this.queueRender();
|
|
2563
2724
|
}
|
|
2564
2725
|
/** Fully remove any active/pending widget (mirrors react's unmount effect):
|
|
@@ -2566,8 +2727,11 @@ var DomFormController = class {
|
|
|
2566
2727
|
* fresh container replaces the old one for a pristine next mount. Safe to
|
|
2567
2728
|
* call when nothing is active. */
|
|
2568
2729
|
teardownTurnstileWidget() {
|
|
2569
|
-
if (this.turnstileWidgetId === null && !this.turnstileLoadInFlight)
|
|
2730
|
+
if (this.turnstileWidgetId === null && !this.turnstileLoadInFlight && this.challengeFrame === null) {
|
|
2731
|
+
return;
|
|
2732
|
+
}
|
|
2570
2733
|
this.turnstileAttempt++;
|
|
2734
|
+
this.teardownChallengeBridge();
|
|
2571
2735
|
const api = turnstileGlobal();
|
|
2572
2736
|
if (this.turnstileWidgetId && api) {
|
|
2573
2737
|
try {
|
|
@@ -2835,6 +2999,7 @@ var DomFormController = class {
|
|
|
2835
2999
|
staged: this.stagedNotice,
|
|
2836
3000
|
draft: this.draftNotice,
|
|
2837
3001
|
warningUrl: this.syncWarningUrl,
|
|
3002
|
+
formUrl: this.syncFormUrl,
|
|
2838
3003
|
noClient: !this.options.client && !this.options.renderOnly
|
|
2839
3004
|
})
|
|
2840
3005
|
);
|
|
@@ -3039,8 +3204,8 @@ var DomFormController = class {
|
|
|
3039
3204
|
);
|
|
3040
3205
|
}
|
|
3041
3206
|
content.appendChild(slot);
|
|
3042
|
-
this.
|
|
3043
|
-
} else if (this.turnstileWidgetId !== null || this.turnstileLoadInFlight) {
|
|
3207
|
+
this.ensureChallengeWidget(this.challenge);
|
|
3208
|
+
} else if (this.turnstileWidgetId !== null || this.turnstileLoadInFlight || this.challengeFrame !== null) {
|
|
3044
3209
|
this.teardownTurnstileWidget();
|
|
3045
3210
|
}
|
|
3046
3211
|
const submitError = api.status === "error" ? "This form can't submit right now. Please try again in a moment." : this.engine?.getState().submitError;
|
|
@@ -3214,25 +3379,45 @@ function devSyncErrorEl(error) {
|
|
|
3214
3379
|
attrs: { role: "alert" }
|
|
3215
3380
|
});
|
|
3216
3381
|
}
|
|
3217
|
-
function devStagedEl() {
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3382
|
+
function devStagedEl(formUrl) {
|
|
3383
|
+
const node = el("div", { className: "fillo-devwarning", attrs: { role: "alert" } });
|
|
3384
|
+
node.append(
|
|
3385
|
+
"Code changes are staged, not live. This page shows your draft, while respondents still get the live version. "
|
|
3386
|
+
);
|
|
3387
|
+
const publishUrl = safeHttpUrl(formUrl);
|
|
3388
|
+
if (publishUrl) {
|
|
3389
|
+
node.appendChild(
|
|
3390
|
+
el("a", {
|
|
3391
|
+
text: "Review and publish in Fillo",
|
|
3392
|
+
attrs: { href: publishUrl, target: "_blank", rel: "noopener noreferrer" }
|
|
3393
|
+
})
|
|
3394
|
+
);
|
|
3395
|
+
node.append(".");
|
|
3396
|
+
} else {
|
|
3397
|
+
node.append("Review and publish the changes in Fillo.");
|
|
3398
|
+
}
|
|
3399
|
+
return node;
|
|
3223
3400
|
}
|
|
3224
|
-
function devDraftEl(warningUrl) {
|
|
3401
|
+
function devDraftEl(warningUrl, formUrl) {
|
|
3225
3402
|
const node = el("div", { className: "fillo-devwarning", attrs: { role: "alert" } });
|
|
3226
3403
|
node.append(
|
|
3227
|
-
"Draft form \u2014
|
|
3404
|
+
"Draft form preview \u2014 the form renders for local testing, but Fillo will reject the submission and save no response until you publish it. "
|
|
3228
3405
|
);
|
|
3229
|
-
const
|
|
3230
|
-
|
|
3231
|
-
|
|
3406
|
+
const storageUrl = safeHttpUrl(warningUrl);
|
|
3407
|
+
const publishUrl = safeHttpUrl(formUrl);
|
|
3408
|
+
if (storageUrl) {
|
|
3409
|
+
node.appendChild(
|
|
3410
|
+
el("a", {
|
|
3411
|
+
text: "Connect storage to publish",
|
|
3412
|
+
attrs: { href: storageUrl, target: "_blank", rel: "noopener noreferrer" }
|
|
3413
|
+
})
|
|
3414
|
+
);
|
|
3415
|
+
node.append(". ");
|
|
3416
|
+
} else if (publishUrl) {
|
|
3232
3417
|
node.appendChild(
|
|
3233
3418
|
el("a", {
|
|
3234
|
-
text:
|
|
3235
|
-
attrs: { href:
|
|
3419
|
+
text: "Open in Fillo to publish",
|
|
3420
|
+
attrs: { href: publishUrl, target: "_blank", rel: "noopener noreferrer" }
|
|
3236
3421
|
})
|
|
3237
3422
|
);
|
|
3238
3423
|
node.append(". ");
|
|
@@ -3260,8 +3445,8 @@ function devChromeEls(state) {
|
|
|
3260
3445
|
if (state.preview) nodes.push(previewBadgeEl());
|
|
3261
3446
|
if (state.devNotices === false) return nodes;
|
|
3262
3447
|
if (state.syncError) nodes.push(devSyncErrorEl(state.syncError));
|
|
3263
|
-
else if (state.staged) nodes.push(devStagedEl());
|
|
3264
|
-
else if (state.draft) nodes.push(devDraftEl(state.warningUrl));
|
|
3448
|
+
else if (state.staged) nodes.push(devStagedEl(state.formUrl));
|
|
3449
|
+
else if (state.draft) nodes.push(devDraftEl(state.warningUrl, state.formUrl));
|
|
3265
3450
|
else if (state.noClient) nodes.push(devNoClientEl());
|
|
3266
3451
|
return nodes;
|
|
3267
3452
|
}
|