@preprio/toolkit 0.6.0-alpha.5 → 0.6.0-alpha.7
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 +138 -52
- package/dist/astro/{chunk-CD4LXNR3.js → chunk-3EXK2LHI.js} +1 -1
- package/dist/astro/graphql.cjs +58 -10
- package/dist/astro/graphql.d.cts +33 -38
- package/dist/astro/graphql.d.ts +33 -38
- package/dist/astro/graphql.js +59 -11
- package/dist/astro/index.cjs +1 -1
- package/dist/astro/index.js +1 -1
- package/dist/{chunk-5O7RGYHX.js → chunk-3QGWCYTL.js} +1 -1
- package/dist/{chunk-ZWKHACNA.js → chunk-PFJO4TYL.js} +52 -5
- package/dist/{chunk-HPWTLDLR.js → chunk-U7TRFVK7.js} +1 -1
- package/dist/{chunk-OH2PPC76.js → chunk-VQIPTUN4.js} +1 -1
- package/dist/graphql/codegen.cjs +51 -4
- package/dist/graphql/codegen.js +3 -3
- package/dist/graphql.cjs +89 -10
- package/dist/graphql.d.cts +69 -39
- package/dist/graphql.d.ts +69 -39
- package/dist/graphql.js +38 -9
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3 -3
- package/dist/nextjs/{chunk-L4L6X3JJ.js → chunk-CZK7SMBD.js} +58 -53
- package/dist/nextjs/components.cjs +175 -242
- package/dist/nextjs/components.d.cts +25 -21
- package/dist/nextjs/components.d.ts +25 -21
- package/dist/nextjs/components.js +167 -236
- package/dist/nextjs/graphql.cjs +374 -510
- package/dist/nextjs/graphql.d.cts +131 -135
- package/dist/nextjs/graphql.d.ts +131 -135
- package/dist/nextjs/graphql.js +309 -457
- package/dist/nextjs/index.cjs +114 -54
- package/dist/nextjs/index.d.cts +57 -33
- package/dist/nextjs/index.d.ts +57 -33
- package/dist/nextjs/index.js +108 -5
- package/dist/nuxt/{chunk-PJZMLKJ2.js → chunk-JL33ENCM.js} +1 -1
- package/dist/nuxt/graphql.cjs +58 -10
- package/dist/nuxt/graphql.d.cts +33 -38
- package/dist/nuxt/graphql.d.ts +33 -38
- package/dist/nuxt/graphql.js +59 -11
- package/dist/nuxt/index.cjs +1 -1
- package/dist/nuxt/index.js +1 -1
- package/dist/react/components.cjs +1 -1
- package/dist/react/components.js +1 -1
- package/dist/sveltekit/{chunk-SKBW7RBD.js → chunk-S7S276V7.js} +1 -1
- package/dist/sveltekit/graphql.cjs +58 -10
- package/dist/sveltekit/graphql.d.cts +33 -38
- package/dist/sveltekit/graphql.d.ts +33 -38
- package/dist/sveltekit/graphql.js +59 -11
- package/dist/sveltekit/index.cjs +1 -1
- package/dist/sveltekit/index.js +1 -1
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ReactNode, ReactElement } from 'react';
|
|
2
2
|
import { k as PreprToolbarComponentProps } from '../types-BYH9XqmN.cjs';
|
|
3
3
|
import { P as PreprPixelConfig } from '../pixel-DwW7cXJm.cjs';
|
|
4
4
|
|
|
@@ -23,24 +23,30 @@ declare function PreprTrackingPixel({ id, config, }: PreprTrackingPixelProps): n
|
|
|
23
23
|
*/
|
|
24
24
|
declare function PreprToolbar({ options, ...props }: PreprToolbarComponentProps): null;
|
|
25
25
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
26
|
+
* Decides every client-side navigation to another page: when the visitor
|
|
27
|
+
* presses a same-site link, it asks `createPreprProxy` which variants they
|
|
28
|
+
* get on that page, and holds the click until the answer is in. The new page
|
|
29
|
+
* then paints with those variants, never with the default variant first.
|
|
30
|
+
* Pressing starts the request, so the wait mostly overlaps the click itself.
|
|
31
|
+
*
|
|
32
|
+
* Mount it once in the root layout, so links on pages without a
|
|
33
|
+
* `PreprStack` are covered too. Clicks with a modifier key, links to other
|
|
34
|
+
* sites and links that open a new tab or download are left alone.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* <PreprPersonalizationSwitch />
|
|
38
|
+
*/
|
|
39
|
+
declare function PreprPersonalizationSwitch(): null;
|
|
40
|
+
/**
|
|
41
|
+
* Applies this page view's decision to the variant wrappers of the
|
|
42
|
+
* `PreprStack` it is rendered by. A click's decision is in before the page
|
|
43
|
+
* paints; Back and Forward show the page as this tab last showed it. On a
|
|
44
|
+
* page load, and any other view without a decision (a navigation started
|
|
45
|
+
* from code, or Back to a page this tab never showed), its personalized
|
|
46
|
+
* sections stay hidden until the decision request answers. Rendered by
|
|
30
47
|
* `PreprStack`; you do not need it yourself.
|
|
31
48
|
*/
|
|
32
|
-
declare function
|
|
33
|
-
interface PreprPersonalizationSwitchProps {
|
|
34
|
-
/**
|
|
35
|
-
* Keep segment decisions fresh in the background. Default `true`. Leave it
|
|
36
|
-
* on for the instance in your root layout; `PreprStack` renders its own
|
|
37
|
-
* with `false`.
|
|
38
|
-
*/
|
|
39
|
-
refresh?: boolean;
|
|
40
|
-
/** Same as `createPreprProxy`'s `freshFor`. Default 10 000 ms. */
|
|
41
|
-
freshFor?: number;
|
|
42
|
-
}
|
|
43
|
-
declare function PreprPersonalizationSwitch({ refresh, freshFor, }: PreprPersonalizationSwitchProps): ReactElement | null;
|
|
49
|
+
declare function PreprDecisionApplier(): null;
|
|
44
50
|
/**
|
|
45
51
|
* Wraps one rendered variant and, once hydrated, puts the variant keys on the
|
|
46
52
|
* elements inside, where Prepr's tracking pixel counts impressions and the
|
|
@@ -55,11 +61,9 @@ declare function PreprPersonalizationSwitch({ refresh, freshFor, }: PreprPersona
|
|
|
55
61
|
* carries a variant key is left alone. Rendered by `PreprStack` with
|
|
56
62
|
* `trackVariants`; you do not need it yourself.
|
|
57
63
|
*/
|
|
58
|
-
declare function PreprVariantTracking({ variantKeys,
|
|
64
|
+
declare function PreprVariantTracking({ variantKeys, children, }: {
|
|
59
65
|
variantKeys: string[];
|
|
60
|
-
/** The A/B bucket wrapper this also serves as; its CSS decides `display`. */
|
|
61
|
-
ab?: string;
|
|
62
66
|
children?: ReactNode;
|
|
63
67
|
}): ReactElement;
|
|
64
68
|
|
|
65
|
-
export {
|
|
69
|
+
export { PreprDecisionApplier, PreprPersonalizationSwitch, PreprToolbar, PreprTrackingPixel, type PreprTrackingPixelProps, PreprVariantTracking };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ReactNode, ReactElement } from 'react';
|
|
2
2
|
import { k as PreprToolbarComponentProps } from '../types-BYH9XqmN.js';
|
|
3
3
|
import { P as PreprPixelConfig } from '../pixel-DwW7cXJm.js';
|
|
4
4
|
|
|
@@ -23,24 +23,30 @@ declare function PreprTrackingPixel({ id, config, }: PreprTrackingPixelProps): n
|
|
|
23
23
|
*/
|
|
24
24
|
declare function PreprToolbar({ options, ...props }: PreprToolbarComponentProps): null;
|
|
25
25
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
26
|
+
* Decides every client-side navigation to another page: when the visitor
|
|
27
|
+
* presses a same-site link, it asks `createPreprProxy` which variants they
|
|
28
|
+
* get on that page, and holds the click until the answer is in. The new page
|
|
29
|
+
* then paints with those variants, never with the default variant first.
|
|
30
|
+
* Pressing starts the request, so the wait mostly overlaps the click itself.
|
|
31
|
+
*
|
|
32
|
+
* Mount it once in the root layout, so links on pages without a
|
|
33
|
+
* `PreprStack` are covered too. Clicks with a modifier key, links to other
|
|
34
|
+
* sites and links that open a new tab or download are left alone.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* <PreprPersonalizationSwitch />
|
|
38
|
+
*/
|
|
39
|
+
declare function PreprPersonalizationSwitch(): null;
|
|
40
|
+
/**
|
|
41
|
+
* Applies this page view's decision to the variant wrappers of the
|
|
42
|
+
* `PreprStack` it is rendered by. A click's decision is in before the page
|
|
43
|
+
* paints; Back and Forward show the page as this tab last showed it. On a
|
|
44
|
+
* page load, and any other view without a decision (a navigation started
|
|
45
|
+
* from code, or Back to a page this tab never showed), its personalized
|
|
46
|
+
* sections stay hidden until the decision request answers. Rendered by
|
|
30
47
|
* `PreprStack`; you do not need it yourself.
|
|
31
48
|
*/
|
|
32
|
-
declare function
|
|
33
|
-
interface PreprPersonalizationSwitchProps {
|
|
34
|
-
/**
|
|
35
|
-
* Keep segment decisions fresh in the background. Default `true`. Leave it
|
|
36
|
-
* on for the instance in your root layout; `PreprStack` renders its own
|
|
37
|
-
* with `false`.
|
|
38
|
-
*/
|
|
39
|
-
refresh?: boolean;
|
|
40
|
-
/** Same as `createPreprProxy`'s `freshFor`. Default 10 000 ms. */
|
|
41
|
-
freshFor?: number;
|
|
42
|
-
}
|
|
43
|
-
declare function PreprPersonalizationSwitch({ refresh, freshFor, }: PreprPersonalizationSwitchProps): ReactElement | null;
|
|
49
|
+
declare function PreprDecisionApplier(): null;
|
|
44
50
|
/**
|
|
45
51
|
* Wraps one rendered variant and, once hydrated, puts the variant keys on the
|
|
46
52
|
* elements inside, where Prepr's tracking pixel counts impressions and the
|
|
@@ -55,11 +61,9 @@ declare function PreprPersonalizationSwitch({ refresh, freshFor, }: PreprPersona
|
|
|
55
61
|
* carries a variant key is left alone. Rendered by `PreprStack` with
|
|
56
62
|
* `trackVariants`; you do not need it yourself.
|
|
57
63
|
*/
|
|
58
|
-
declare function PreprVariantTracking({ variantKeys,
|
|
64
|
+
declare function PreprVariantTracking({ variantKeys, children, }: {
|
|
59
65
|
variantKeys: string[];
|
|
60
|
-
/** The A/B bucket wrapper this also serves as; its CSS decides `display`. */
|
|
61
|
-
ab?: string;
|
|
62
66
|
children?: ReactNode;
|
|
63
67
|
}): ReactElement;
|
|
64
68
|
|
|
65
|
-
export {
|
|
69
|
+
export { PreprDecisionApplier, PreprPersonalizationSwitch, PreprToolbar, PreprTrackingPixel, type PreprTrackingPixelProps, PreprVariantTracking };
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
import {
|
|
3
3
|
createPreprPreview,
|
|
4
4
|
loadTrackingPixel
|
|
5
|
-
} from "../chunk-
|
|
5
|
+
} from "../chunk-U7TRFVK7.js";
|
|
6
6
|
import "../chunk-S5RWVMDJ.js";
|
|
7
|
-
import "../chunk-
|
|
7
|
+
import "../chunk-VQIPTUN4.js";
|
|
8
8
|
|
|
9
9
|
// src/nextjs/components.ts
|
|
10
10
|
import { usePathname, useRouter } from "next/navigation";
|
|
@@ -12,9 +12,7 @@ import {
|
|
|
12
12
|
createElement,
|
|
13
13
|
useEffect as useEffect2,
|
|
14
14
|
useLayoutEffect,
|
|
15
|
-
useRef
|
|
16
|
-
useState,
|
|
17
|
-
Suspense
|
|
15
|
+
useRef
|
|
18
16
|
} from "react";
|
|
19
17
|
|
|
20
18
|
// src/react/hydration.ts
|
|
@@ -36,197 +34,35 @@ function isReactHydrated(text) {
|
|
|
36
34
|
return true;
|
|
37
35
|
}
|
|
38
36
|
|
|
39
|
-
// src/
|
|
40
|
-
|
|
41
|
-
var PREPR_REFRESH_HEADER = "Prepr-Refresh";
|
|
42
|
-
var REFRESH_MAX_PATHS = 5;
|
|
43
|
-
var SAFE_PATH = /^\/(?![/\\])[\w\-./%]{0,200}$/;
|
|
44
|
-
function safePath(path) {
|
|
45
|
-
return SAFE_PATH.test(path) ? path : void 0;
|
|
46
|
-
}
|
|
47
|
-
function isFresh(at, freshFor, now = Date.now()) {
|
|
48
|
-
const age = now - at;
|
|
49
|
-
return at > 0 && age >= 0 && age < freshFor;
|
|
50
|
-
}
|
|
51
|
-
function cleanEntries(entries) {
|
|
52
|
-
const out = [];
|
|
53
|
-
for (const entry of entries) {
|
|
54
|
-
if (out.length === REFRESH_MAX_PATHS) break;
|
|
55
|
-
if (safePath(entry.path) && !out.some((e) => e.path === entry.path)) {
|
|
56
|
-
out.push(entry);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
return out;
|
|
60
|
-
}
|
|
61
|
-
function parseRefresh(raw) {
|
|
62
|
-
return cleanEntries(
|
|
63
|
-
(raw ?? "").split("|").map((pair) => {
|
|
64
|
-
const split = pair.lastIndexOf("~");
|
|
65
|
-
const at = Number(split < 0 ? "" : pair.slice(split + 1));
|
|
66
|
-
return {
|
|
67
|
-
path: split < 0 ? pair : pair.slice(0, split),
|
|
68
|
-
at: Number.isFinite(at) && at > 0 ? Math.floor(at) : 0
|
|
69
|
-
};
|
|
70
|
-
})
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// src/core/refresh-scheduler.ts
|
|
75
|
-
var VIEW_DELAYS_MS = [3e3, 1e4];
|
|
76
|
-
var INTENT_MIN_AGE_MS = 2e3;
|
|
77
|
-
var INTENT_EVENTS = ["pointerover", "focusin", "touchstart"];
|
|
78
|
-
var LISTENER = { capture: true, passive: true };
|
|
79
|
-
var COOKIE_PATTERN = new RegExp(`(?:^|; )${PREPR_REFRESH_COOKIE}=([^;]*)`);
|
|
80
|
-
var UNSUPPORTED_KEY = "prepr_refresh_unsupported";
|
|
81
|
-
function readEntries() {
|
|
82
|
-
const match = document.cookie.match(COOKIE_PATTERN);
|
|
83
|
-
try {
|
|
84
|
-
return parseRefresh(match ? decodeURIComponent(match[1]) : void 0);
|
|
85
|
-
} catch {
|
|
86
|
-
return [];
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
function unsupported() {
|
|
90
|
-
try {
|
|
91
|
-
return sessionStorage.getItem(UNSUPPORTED_KEY) === "1";
|
|
92
|
-
} catch {
|
|
93
|
-
return false;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
function createPreprRefreshScheduler(options = {}) {
|
|
97
|
-
const freshFor = options.freshFor ?? 1e4;
|
|
98
|
-
let timers = [];
|
|
99
|
-
let inFlight = false;
|
|
100
|
-
let stopped = unsupported();
|
|
101
|
-
function refresh() {
|
|
102
|
-
if (inFlight || stopped) return;
|
|
103
|
-
const paths = readEntries().map((entry) => entry.path);
|
|
104
|
-
if (paths.length === 0) return;
|
|
105
|
-
inFlight = true;
|
|
106
|
-
const controller = new AbortController();
|
|
107
|
-
fetch(location.pathname, {
|
|
108
|
-
headers: { [PREPR_REFRESH_HEADER]: paths.join("|") },
|
|
109
|
-
cache: "no-store",
|
|
110
|
-
credentials: "same-origin",
|
|
111
|
-
signal: controller.signal
|
|
112
|
-
}).then((response) => {
|
|
113
|
-
if (response.status === 204) return;
|
|
114
|
-
stopped = true;
|
|
115
|
-
controller.abort();
|
|
116
|
-
try {
|
|
117
|
-
sessionStorage.setItem(UNSUPPORTED_KEY, "1");
|
|
118
|
-
} catch {
|
|
119
|
-
}
|
|
120
|
-
}).catch(() => void 0).finally(() => {
|
|
121
|
-
inFlight = false;
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
function onIntent(event) {
|
|
125
|
-
const anchor = event.target?.closest?.("a[href]");
|
|
126
|
-
if (!(anchor instanceof HTMLAnchorElement)) return;
|
|
127
|
-
let url;
|
|
128
|
-
try {
|
|
129
|
-
url = new URL(anchor.href, location.href);
|
|
130
|
-
} catch {
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
if (url.origin !== location.origin) return;
|
|
134
|
-
const entry = readEntries().find((e) => e.path === url.pathname);
|
|
135
|
-
if (entry && !isFresh(entry.at, INTENT_MIN_AGE_MS)) refresh();
|
|
136
|
-
}
|
|
137
|
-
function onVisibility() {
|
|
138
|
-
if (document.visibilityState === "visible" && readEntries().some((entry) => !isFresh(entry.at, freshFor))) {
|
|
139
|
-
refresh();
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
for (const type of INTENT_EVENTS) {
|
|
143
|
-
document.addEventListener(type, onIntent, LISTENER);
|
|
144
|
-
}
|
|
145
|
-
document.addEventListener("visibilitychange", onVisibility, {
|
|
146
|
-
passive: true
|
|
147
|
-
});
|
|
148
|
-
return {
|
|
149
|
-
view() {
|
|
150
|
-
timers.forEach(clearTimeout);
|
|
151
|
-
timers = VIEW_DELAYS_MS.map((delay) => setTimeout(refresh, delay));
|
|
152
|
-
},
|
|
153
|
-
stop() {
|
|
154
|
-
timers.forEach(clearTimeout);
|
|
155
|
-
timers = [];
|
|
156
|
-
for (const type of INTENT_EVENTS) {
|
|
157
|
-
document.removeEventListener(type, onIntent, LISTENER);
|
|
158
|
-
}
|
|
159
|
-
document.removeEventListener("visibilitychange", onVisibility);
|
|
160
|
-
}
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// src/nextjs/ab-switch.ts
|
|
165
|
-
function applyPreprAbBucket() {
|
|
166
|
-
const match = document.cookie.match(/(?:^|; )prepr_ab_bucket=([AB])(?:;|$)/);
|
|
167
|
-
const bucket = match ? match[1] : "A";
|
|
37
|
+
// src/nextjs/decision-switch.ts
|
|
38
|
+
function applyPreprDecisions(decision) {
|
|
168
39
|
const state = window;
|
|
169
|
-
|
|
170
|
-
state.
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
if (
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
if (viewState.__preprDecisionsView === current) return;
|
|
191
|
-
viewState.__preprDecisionsView = current;
|
|
192
|
-
const match = document.cookie.match(/(?:^|; )prepr_decisions=([^;]*)/);
|
|
193
|
-
let raw;
|
|
194
|
-
try {
|
|
195
|
-
raw = match ? decodeURIComponent(match[1]) : "";
|
|
196
|
-
} catch {
|
|
197
|
-
raw = "";
|
|
198
|
-
}
|
|
199
|
-
const state = window;
|
|
200
|
-
if (state.__preprDecisions === raw) return;
|
|
201
|
-
state.__preprDecisions = raw;
|
|
202
|
-
const id = /^[\w-]+$/;
|
|
203
|
-
let css = "";
|
|
204
|
-
raw.split("~").forEach((pair) => {
|
|
205
|
-
const at = pair.indexOf(":");
|
|
206
|
-
const group = pair.slice(0, at);
|
|
207
|
-
if (at < 1 || !id.test(group)) return;
|
|
208
|
-
const selector = '[data-prepr-group="' + group + '"]';
|
|
209
|
-
css += selector + "{display:none!important}";
|
|
210
|
-
pair.slice(at + 1).split("|").forEach((variant) => {
|
|
211
|
-
if (id.test(variant)) {
|
|
212
|
-
css += selector + '[data-prepr-variant="' + variant + '"]{display:contents!important}';
|
|
213
|
-
}
|
|
40
|
+
const key = location.pathname + location.search;
|
|
41
|
+
if (decision !== void 0) state.__preprDecision = { key, value: decision };
|
|
42
|
+
const current = state.__preprDecision;
|
|
43
|
+
if (!current || current.key !== key) return false;
|
|
44
|
+
document.querySelectorAll("[data-prepr-decision]").forEach((marker) => {
|
|
45
|
+
marker.setAttribute("data-prepr-decided", "");
|
|
46
|
+
});
|
|
47
|
+
const defaults = current.value === "*";
|
|
48
|
+
const chosen = {};
|
|
49
|
+
current.value.split("~").forEach((pair) => {
|
|
50
|
+
const colon = pair.indexOf(":");
|
|
51
|
+
if (colon > 0)
|
|
52
|
+
chosen[pair.slice(0, colon)] = pair.slice(colon + 1).split("|");
|
|
53
|
+
});
|
|
54
|
+
document.querySelectorAll("[data-prepr-v]").forEach((element) => {
|
|
55
|
+
const nested = element.hasAttribute("data-prepr-nested");
|
|
56
|
+
const show = defaults ? element.hasAttribute("data-prepr-default") : (element.getAttribute("data-prepr-v") || "").split(" ").every((token) => {
|
|
57
|
+
const colon = token.indexOf(":");
|
|
58
|
+
const variant = token.slice(colon + 1);
|
|
59
|
+
const variants = chosen[token.slice(0, colon)];
|
|
60
|
+
return !!variants && (nested ? variants[0] === variant : variants.indexOf(variant) >= 0);
|
|
214
61
|
});
|
|
62
|
+
if (show) element.setAttribute("data-prepr-show", "");
|
|
63
|
+
else element.removeAttribute("data-prepr-show");
|
|
215
64
|
});
|
|
216
|
-
|
|
217
|
-
if (!state.__preprDecisionSheet) {
|
|
218
|
-
const sheet = new CSSStyleSheet();
|
|
219
|
-
document.adoptedStyleSheets = document.adoptedStyleSheets.concat(sheet);
|
|
220
|
-
state.__preprDecisionSheet = sheet;
|
|
221
|
-
}
|
|
222
|
-
state.__preprDecisionSheet.replaceSync(css);
|
|
223
|
-
} catch {
|
|
224
|
-
if (!state.__preprDecisionSheet) {
|
|
225
|
-
state.__preprDecisionSheet = document.createElement("style");
|
|
226
|
-
document.head.appendChild(state.__preprDecisionSheet);
|
|
227
|
-
}
|
|
228
|
-
state.__preprDecisionSheet.textContent = css;
|
|
229
|
-
}
|
|
65
|
+
return true;
|
|
230
66
|
}
|
|
231
67
|
|
|
232
68
|
// src/react/components.ts
|
|
@@ -264,62 +100,152 @@ function PreprToolbar({
|
|
|
264
100
|
}, []);
|
|
265
101
|
return null;
|
|
266
102
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
103
|
+
var DECIDE_HEADER = "Prepr-Decide";
|
|
104
|
+
var DECIDE_TIMEOUT_MS = 6e3;
|
|
105
|
+
function requestDecision(key) {
|
|
106
|
+
const pending = { key, value: null };
|
|
107
|
+
pending.value = fetch(key, {
|
|
108
|
+
headers: { [DECIDE_HEADER]: "1" },
|
|
109
|
+
cache: "no-store",
|
|
110
|
+
credentials: "same-origin",
|
|
111
|
+
signal: AbortSignal.timeout(DECIDE_TIMEOUT_MS)
|
|
112
|
+
}).then((response) => response.ok ? response.text() : "*").catch(() => "*").then((value) => {
|
|
113
|
+
pending.result = value;
|
|
114
|
+
try {
|
|
115
|
+
sessionStorage.setItem(
|
|
116
|
+
"prepr_decision",
|
|
117
|
+
JSON.stringify({ key, value, at: Date.now() })
|
|
118
|
+
);
|
|
119
|
+
} catch {
|
|
120
|
+
}
|
|
121
|
+
return value;
|
|
270
122
|
});
|
|
271
|
-
return
|
|
123
|
+
return pending;
|
|
272
124
|
}
|
|
273
|
-
var
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
const
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
125
|
+
var pressed = null;
|
|
126
|
+
var replaying = null;
|
|
127
|
+
var shown = /* @__PURE__ */ new Map();
|
|
128
|
+
var historyStep = false;
|
|
129
|
+
var onPopState = () => {
|
|
130
|
+
historyStep = true;
|
|
131
|
+
};
|
|
132
|
+
function linkTarget(event) {
|
|
133
|
+
const anchor = event.target?.closest?.("a[href]");
|
|
134
|
+
if (!(anchor instanceof HTMLAnchorElement)) return null;
|
|
135
|
+
if (anchor.target && anchor.target !== "_self" || anchor.hasAttribute("download")) {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
const url = new URL(anchor.href, location.href);
|
|
139
|
+
const key = url.pathname + url.search;
|
|
140
|
+
if (url.origin !== location.origin || key === location.pathname + location.search) {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
return { anchor, key };
|
|
144
|
+
}
|
|
145
|
+
function onPointerDown(event) {
|
|
146
|
+
if (event.button !== 0) return;
|
|
147
|
+
const target = linkTarget(event);
|
|
148
|
+
if (!target || pressed?.key === target.key && pressed.result === void 0) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
pressed = requestDecision(target.key);
|
|
152
|
+
}
|
|
153
|
+
function onClick(event) {
|
|
154
|
+
const target = linkTarget(event);
|
|
155
|
+
if (!target) return;
|
|
156
|
+
if (replaying === target.anchor) {
|
|
157
|
+
replaying = null;
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
const pending = pressed && pressed.key === target.key ? pressed : requestDecision(target.key);
|
|
164
|
+
pressed = null;
|
|
165
|
+
const record = (value) => {
|
|
166
|
+
window.__preprDecision = { key: target.key, value };
|
|
167
|
+
};
|
|
168
|
+
if (pending.result !== void 0) {
|
|
169
|
+
record(pending.result);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
event.preventDefault();
|
|
173
|
+
event.stopImmediatePropagation();
|
|
174
|
+
void pending.value.then((value) => {
|
|
175
|
+
record(value);
|
|
176
|
+
replaying = target.anchor;
|
|
177
|
+
target.anchor.click();
|
|
287
178
|
});
|
|
179
|
+
}
|
|
180
|
+
var listeners = 0;
|
|
181
|
+
function useDecisionRequests() {
|
|
288
182
|
useEffect2(() => {
|
|
289
|
-
if (
|
|
290
|
-
|
|
291
|
-
|
|
183
|
+
if (listeners++ === 0) {
|
|
184
|
+
window.addEventListener("pointerdown", onPointerDown, true);
|
|
185
|
+
window.addEventListener("click", onClick, true);
|
|
186
|
+
window.addEventListener("popstate", onPopState);
|
|
187
|
+
}
|
|
292
188
|
return () => {
|
|
293
|
-
|
|
294
|
-
|
|
189
|
+
if (--listeners === 0) {
|
|
190
|
+
window.removeEventListener("pointerdown", onPointerDown, true);
|
|
191
|
+
window.removeEventListener("click", onClick, true);
|
|
192
|
+
window.removeEventListener("popstate", onPopState);
|
|
193
|
+
}
|
|
295
194
|
};
|
|
296
|
-
}, [
|
|
297
|
-
return scheduler ? createElement(
|
|
298
|
-
Suspense,
|
|
299
|
-
{ fallback: null },
|
|
300
|
-
createElement(ViewCounter, { scheduler })
|
|
301
|
-
) : null;
|
|
195
|
+
}, []);
|
|
302
196
|
}
|
|
303
|
-
function
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
197
|
+
function PreprPersonalizationSwitch() {
|
|
198
|
+
useDecisionRequests();
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
function PreprDecisionApplier() {
|
|
202
|
+
useDecisionRequests();
|
|
203
|
+
useLayoutEffect(() => {
|
|
204
|
+
const key = location.pathname + location.search;
|
|
205
|
+
const remember = () => {
|
|
206
|
+
const state = window.__preprDecision;
|
|
207
|
+
if (state) shown.set(key, state.value);
|
|
208
|
+
};
|
|
209
|
+
const previous = historyStep ? shown.get(key) : void 0;
|
|
210
|
+
historyStep = false;
|
|
211
|
+
if (applyPreprDecisions(previous)) {
|
|
212
|
+
remember();
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
let live = true;
|
|
216
|
+
const started = window.__preprPending;
|
|
217
|
+
const value = started?.key === key ? started.value : requestDecision(key).value;
|
|
218
|
+
void value.then((value2) => {
|
|
219
|
+
if (live && location.pathname + location.search === key) {
|
|
220
|
+
applyPreprDecisions(value2);
|
|
221
|
+
remember();
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
return () => {
|
|
225
|
+
live = false;
|
|
226
|
+
};
|
|
227
|
+
});
|
|
310
228
|
return null;
|
|
311
229
|
}
|
|
312
230
|
var VARIANT_KEY_ATTRIBUTE = "data-prepr-variant-key";
|
|
313
231
|
function PreprVariantTracking({
|
|
314
232
|
variantKeys,
|
|
315
|
-
ab,
|
|
316
233
|
children
|
|
317
234
|
}) {
|
|
318
235
|
const ref = useRef(null);
|
|
319
236
|
const keys = variantKeys.join(" ");
|
|
320
237
|
useLayoutEffect(() => {
|
|
321
238
|
let tagged = [];
|
|
322
|
-
const
|
|
239
|
+
const wrapper = ref.current?.closest("[data-prepr-v]");
|
|
240
|
+
const shown2 = () => !wrapper || wrapper.hasAttribute("data-prepr-show");
|
|
241
|
+
const untag = () => {
|
|
242
|
+
for (const [element] of tagged)
|
|
243
|
+
element.removeAttribute(VARIANT_KEY_ATTRIBUTE);
|
|
244
|
+
tagged = [];
|
|
245
|
+
};
|
|
246
|
+
const tag = () => {
|
|
247
|
+
if (!shown2()) return untag();
|
|
248
|
+
if (tagged.length > 0) return;
|
|
323
249
|
const elements = [...ref.current?.children ?? []];
|
|
324
250
|
const list = keys.split(" ");
|
|
325
251
|
const pairs = elements.length === list.length ? elements.map((element, i) => [element, list[i]]) : elements.slice(0, 1).map((element) => [element, list[0]]);
|
|
@@ -328,22 +254,27 @@ function PreprVariantTracking({
|
|
|
328
254
|
element.setAttribute(VARIANT_KEY_ATTRIBUTE, key);
|
|
329
255
|
return true;
|
|
330
256
|
});
|
|
257
|
+
};
|
|
258
|
+
const timer = setTimeout(tag);
|
|
259
|
+
const observer = wrapper && new MutationObserver(tag);
|
|
260
|
+
observer?.observe(wrapper, {
|
|
261
|
+
attributes: true,
|
|
262
|
+
attributeFilter: ["data-prepr-show"]
|
|
331
263
|
});
|
|
332
264
|
return () => {
|
|
333
265
|
clearTimeout(timer);
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
}
|
|
266
|
+
observer?.disconnect();
|
|
267
|
+
untag();
|
|
337
268
|
};
|
|
338
269
|
}, [keys]);
|
|
339
270
|
return createElement(
|
|
340
271
|
"div",
|
|
341
|
-
|
|
272
|
+
{ ref, style: { display: "contents" } },
|
|
342
273
|
children
|
|
343
274
|
);
|
|
344
275
|
}
|
|
345
276
|
export {
|
|
346
|
-
|
|
277
|
+
PreprDecisionApplier,
|
|
347
278
|
PreprPersonalizationSwitch,
|
|
348
279
|
PreprToolbar,
|
|
349
280
|
PreprTrackingPixel,
|