@preprio/toolkit 0.5.0-rc.2 → 0.5.0-rc.4
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 +209 -10
- package/dist/astro/index.cjs +1194 -1
- package/dist/astro/index.d.cts +47 -1
- package/dist/astro/index.d.ts +47 -1
- package/dist/astro/index.js +1197 -1
- package/dist/chunk-46ISDRHT.js +26 -0
- package/dist/chunk-74KY5EAM.js +1219 -0
- package/dist/chunk-AHBN6FQP.js +184 -0
- package/dist/{chunk-FLPNN3CV.js → chunk-PDNCNTDB.js} +1370 -623
- package/dist/chunk-S3VT4UWM.js +75 -0
- package/dist/chunk-TG6N3C22.js +246 -0
- package/dist/chunk-URXSDJOU.js +356 -0
- package/dist/index.cjs +3166 -392
- package/dist/index.d.cts +238 -4
- package/dist/index.d.ts +238 -4
- package/dist/index.js +189 -149
- package/dist/live-preview-H4643QYF.js +18 -0
- package/dist/nextjs/components.cjs +1522 -359
- package/dist/nextjs/components.d.cts +1 -1
- package/dist/nextjs/components.d.ts +1 -1
- package/dist/nextjs/components.js +4 -1
- package/dist/nextjs/index.cjs +1085 -6
- package/dist/nextjs/index.d.cts +73 -1
- package/dist/nextjs/index.d.ts +73 -1
- package/dist/nextjs/index.js +1079 -7
- package/dist/nextjs/live-action.cjs +285 -0
- package/dist/nextjs/live-action.d.cts +12 -0
- package/dist/nextjs/live-action.d.ts +12 -0
- package/dist/nextjs/live-action.js +68 -0
- package/dist/nextjs/live.cjs +1896 -0
- package/dist/nextjs/live.d.cts +52 -0
- package/dist/nextjs/live.d.ts +52 -0
- package/dist/nextjs/live.js +265 -0
- package/dist/nuxt/index.cjs +1 -1
- package/dist/nuxt/index.js +1 -1
- package/dist/nuxt/live-preview-O2ZTJNW4.js +1520 -0
- package/dist/nuxt/live.cjs +1720 -0
- package/dist/nuxt/live.d.cts +43 -0
- package/dist/nuxt/live.d.ts +43 -0
- package/dist/nuxt/live.js +94 -0
- package/dist/{pixel-B3oQNbZ3.d.cts → pixel-CX422dCO.d.cts} +10 -1
- package/dist/{pixel-B3oQNbZ3.d.ts → pixel-CX422dCO.d.ts} +10 -1
- package/dist/react/components.cjs +1522 -359
- package/dist/react/components.d.cts +9 -0
- package/dist/react/components.d.ts +9 -0
- package/dist/react/components.js +1522 -359
- package/dist/sveltekit/index.cjs +1 -1
- package/dist/sveltekit/index.js +1 -1
- package/package.json +12 -1
- package/src/astro/PreprToolbar.astro +7 -1
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/** Executes a registered query. `query` is opaque — string or DocumentNode. */
|
|
2
|
+
interface LiveFetcher {
|
|
3
|
+
(query: unknown, variables?: Record<string, unknown>): Promise<unknown>;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
interface LivePreviewConfig {
|
|
7
|
+
/**
|
|
8
|
+
* Pass your preview flag; never `true` on a production deployment.
|
|
9
|
+
* Defaults to false: nothing starts and `useLive` is a passthrough.
|
|
10
|
+
*/
|
|
11
|
+
enabled?: boolean;
|
|
12
|
+
/** Runs registered queries against Prepr GraphQL in preview mode. */
|
|
13
|
+
fetcher?: LiveFetcher;
|
|
14
|
+
/** Exact editor origins, replacing the `*.prepr.io` default. */
|
|
15
|
+
allowedEditorOrigins?: string[];
|
|
16
|
+
/** Your preview token uses GraphQL strict mode; see the toolbar option. */
|
|
17
|
+
strictMode?: boolean;
|
|
18
|
+
}
|
|
19
|
+
interface PreprRefreshProps {
|
|
20
|
+
/** Pass your preview flag; never `true` on a production deployment. */
|
|
21
|
+
enabled?: boolean;
|
|
22
|
+
/** Your preview token uses GraphQL strict mode; see the toolbar option. */
|
|
23
|
+
strictMode?: boolean;
|
|
24
|
+
}
|
|
25
|
+
interface UseLiveOptions {
|
|
26
|
+
/** Query (string or DocumentNode) passed as-is to the fetcher. */
|
|
27
|
+
query?: unknown;
|
|
28
|
+
variables?: Record<string, unknown>;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Turn live preview on for `useLive`. Call once on the client with your
|
|
32
|
+
* preview flag as `enabled`. Without a fetcher, saves do not refetch. Not
|
|
33
|
+
* needed when `PreprRefresh` is enabled.
|
|
34
|
+
*/
|
|
35
|
+
declare function initLivePreview(config?: LivePreviewConfig): void;
|
|
36
|
+
/**
|
|
37
|
+
* Register a query result for live preview. Field changes re-render
|
|
38
|
+
* instantly; saves refetch `{ query, variables }` when given. A passthrough
|
|
39
|
+
* until live preview is enabled and outside the editor iframe. The data must
|
|
40
|
+
* select `_id` on every level that should update.
|
|
41
|
+
*/
|
|
42
|
+
declare function useLive<T>(data: T, opts?: UseLiveOptions): T;
|
|
43
|
+
/**
|
|
44
|
+
* Keeps server components in step with the Prepr editor: unsaved changes
|
|
45
|
+
* re-render the route, where `normalizeQuery` applies them; a save refreshes
|
|
46
|
+
* it. Mount once in the root layout with your preview flag as `enabled`.
|
|
47
|
+
* Use this or `useLive` with a `query`, not both: a save would refetch and
|
|
48
|
+
* refresh.
|
|
49
|
+
*/
|
|
50
|
+
declare function PreprRefresh({ enabled, strictMode: strict, }: PreprRefreshProps): null;
|
|
51
|
+
|
|
52
|
+
export { type LivePreviewConfig, PreprRefresh, type PreprRefreshProps, type UseLiveOptions, initLivePreview, useLive };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/** Executes a registered query. `query` is opaque — string or DocumentNode. */
|
|
2
|
+
interface LiveFetcher {
|
|
3
|
+
(query: unknown, variables?: Record<string, unknown>): Promise<unknown>;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
interface LivePreviewConfig {
|
|
7
|
+
/**
|
|
8
|
+
* Pass your preview flag; never `true` on a production deployment.
|
|
9
|
+
* Defaults to false: nothing starts and `useLive` is a passthrough.
|
|
10
|
+
*/
|
|
11
|
+
enabled?: boolean;
|
|
12
|
+
/** Runs registered queries against Prepr GraphQL in preview mode. */
|
|
13
|
+
fetcher?: LiveFetcher;
|
|
14
|
+
/** Exact editor origins, replacing the `*.prepr.io` default. */
|
|
15
|
+
allowedEditorOrigins?: string[];
|
|
16
|
+
/** Your preview token uses GraphQL strict mode; see the toolbar option. */
|
|
17
|
+
strictMode?: boolean;
|
|
18
|
+
}
|
|
19
|
+
interface PreprRefreshProps {
|
|
20
|
+
/** Pass your preview flag; never `true` on a production deployment. */
|
|
21
|
+
enabled?: boolean;
|
|
22
|
+
/** Your preview token uses GraphQL strict mode; see the toolbar option. */
|
|
23
|
+
strictMode?: boolean;
|
|
24
|
+
}
|
|
25
|
+
interface UseLiveOptions {
|
|
26
|
+
/** Query (string or DocumentNode) passed as-is to the fetcher. */
|
|
27
|
+
query?: unknown;
|
|
28
|
+
variables?: Record<string, unknown>;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Turn live preview on for `useLive`. Call once on the client with your
|
|
32
|
+
* preview flag as `enabled`. Without a fetcher, saves do not refetch. Not
|
|
33
|
+
* needed when `PreprRefresh` is enabled.
|
|
34
|
+
*/
|
|
35
|
+
declare function initLivePreview(config?: LivePreviewConfig): void;
|
|
36
|
+
/**
|
|
37
|
+
* Register a query result for live preview. Field changes re-render
|
|
38
|
+
* instantly; saves refetch `{ query, variables }` when given. A passthrough
|
|
39
|
+
* until live preview is enabled and outside the editor iframe. The data must
|
|
40
|
+
* select `_id` on every level that should update.
|
|
41
|
+
*/
|
|
42
|
+
declare function useLive<T>(data: T, opts?: UseLiveOptions): T;
|
|
43
|
+
/**
|
|
44
|
+
* Keeps server components in step with the Prepr editor: unsaved changes
|
|
45
|
+
* re-render the route, where `normalizeQuery` applies them; a save refreshes
|
|
46
|
+
* it. Mount once in the root layout with your preview flag as `enabled`.
|
|
47
|
+
* Use this or `useLive` with a `query`, not both: a save would refetch and
|
|
48
|
+
* refresh.
|
|
49
|
+
*/
|
|
50
|
+
declare function PreprRefresh({ enabled, strictMode: strict, }: PreprRefreshProps): null;
|
|
51
|
+
|
|
52
|
+
export { type LivePreviewConfig, PreprRefresh, type PreprRefreshProps, type UseLiveOptions, initLivePreview, useLive };
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import {
|
|
3
|
+
createScopedLogger
|
|
4
|
+
} from "../chunk-S3VT4UWM.js";
|
|
5
|
+
|
|
6
|
+
// src/nextjs/live.ts
|
|
7
|
+
import { useRouter } from "next/navigation";
|
|
8
|
+
import { useEffect, useState } from "react";
|
|
9
|
+
|
|
10
|
+
// src/core/live-preview/test-reset.ts
|
|
11
|
+
var resets = /* @__PURE__ */ new Set();
|
|
12
|
+
function onTestReset(reset) {
|
|
13
|
+
resets.add(reset);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// src/nextjs/live.ts
|
|
17
|
+
import { syncPreprLiveLog } from "./live-action.js";
|
|
18
|
+
var debug = createScopedLogger("live-preview:next");
|
|
19
|
+
var LOG_SYNC_DEBOUNCE_MS = 150;
|
|
20
|
+
var starting = null;
|
|
21
|
+
var store = null;
|
|
22
|
+
var bridge = null;
|
|
23
|
+
var allowedEditorOrigins;
|
|
24
|
+
var strictMode = false;
|
|
25
|
+
var startListeners = /* @__PURE__ */ new Set();
|
|
26
|
+
function isFramed() {
|
|
27
|
+
return typeof window !== "undefined" && window.self !== window.top;
|
|
28
|
+
}
|
|
29
|
+
function startLive() {
|
|
30
|
+
starting ?? (starting = import("../live-preview-H4643QYF.js").then(
|
|
31
|
+
({ createLiveBridge, createLiveStore }) => {
|
|
32
|
+
const skipped = new Set(quarantined());
|
|
33
|
+
const started = createLiveStore({
|
|
34
|
+
accept: (patch) => !skipped.has(patchKey(patch))
|
|
35
|
+
});
|
|
36
|
+
watchClientRenders(started);
|
|
37
|
+
bridge = createLiveBridge(started, {
|
|
38
|
+
allowedEditorOrigins: () => allowedEditorOrigins,
|
|
39
|
+
strictMode: () => strictMode
|
|
40
|
+
});
|
|
41
|
+
bridge.start();
|
|
42
|
+
store = started;
|
|
43
|
+
startListeners.forEach((listener) => listener(started));
|
|
44
|
+
startListeners.clear();
|
|
45
|
+
return started;
|
|
46
|
+
}
|
|
47
|
+
));
|
|
48
|
+
return starting;
|
|
49
|
+
}
|
|
50
|
+
function whenLive(listener) {
|
|
51
|
+
if (store) {
|
|
52
|
+
listener(store);
|
|
53
|
+
return () => {
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
startListeners.add(listener);
|
|
57
|
+
return () => startListeners.delete(listener);
|
|
58
|
+
}
|
|
59
|
+
function initLivePreview(config = {}) {
|
|
60
|
+
if (!config.enabled || !isFramed()) return;
|
|
61
|
+
if (config.strictMode) strictMode = true;
|
|
62
|
+
if (config.allowedEditorOrigins) {
|
|
63
|
+
allowedEditorOrigins = [...config.allowedEditorOrigins];
|
|
64
|
+
}
|
|
65
|
+
const { fetcher } = config;
|
|
66
|
+
void startLive().then((started) => {
|
|
67
|
+
if (fetcher) started.setFetcher(fetcher);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
function useLive(data, opts) {
|
|
71
|
+
const [live, setLive] = useState(data);
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
if (!isFramed()) return;
|
|
74
|
+
let unregister = null;
|
|
75
|
+
const stopWaiting = whenLive((started) => {
|
|
76
|
+
const registration = started.register(data, opts);
|
|
77
|
+
setLive(registration.getSnapshot());
|
|
78
|
+
const unsubscribe = registration.subscribe(
|
|
79
|
+
() => setLive(registration.getSnapshot())
|
|
80
|
+
);
|
|
81
|
+
unregister = () => {
|
|
82
|
+
unsubscribe();
|
|
83
|
+
registration.unregister();
|
|
84
|
+
};
|
|
85
|
+
});
|
|
86
|
+
return () => {
|
|
87
|
+
stopWaiting();
|
|
88
|
+
unregister?.();
|
|
89
|
+
};
|
|
90
|
+
}, [data]);
|
|
91
|
+
return isFramed() ? live : data;
|
|
92
|
+
}
|
|
93
|
+
function PreprRefresh({
|
|
94
|
+
enabled = false,
|
|
95
|
+
strictMode: strict = false
|
|
96
|
+
}) {
|
|
97
|
+
const router = useRouter();
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
if (!enabled || !isFramed()) return;
|
|
100
|
+
if (strict) strictMode = true;
|
|
101
|
+
const sync = createLogSync();
|
|
102
|
+
let unsubscribe = null;
|
|
103
|
+
let active = true;
|
|
104
|
+
void startLive().then((started) => {
|
|
105
|
+
if (!active) return;
|
|
106
|
+
const offLog = started.onLogChange((log) => {
|
|
107
|
+
if (log.length) sync.schedule(log);
|
|
108
|
+
else sync.reset();
|
|
109
|
+
});
|
|
110
|
+
const offInvalidate = started.onInvalidate(() => router.refresh());
|
|
111
|
+
unsubscribe = () => {
|
|
112
|
+
offLog();
|
|
113
|
+
offInvalidate();
|
|
114
|
+
};
|
|
115
|
+
});
|
|
116
|
+
return () => {
|
|
117
|
+
active = false;
|
|
118
|
+
unsubscribe?.();
|
|
119
|
+
sync.cancel();
|
|
120
|
+
};
|
|
121
|
+
}, [enabled, strict, router]);
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
var STEGA_CHARS = /[\u200b-\u200d\u2060-\u2064\ufeff]/;
|
|
125
|
+
var RENDER_SETTLE_MS = 1e3;
|
|
126
|
+
var RENDER_POLL_MS = 100;
|
|
127
|
+
function showsPreprContent() {
|
|
128
|
+
return document.querySelector("[data-prepr-encoded]") !== null || // Visible text only: inline scripts carry the route's data, edit links
|
|
129
|
+
// included, even when the route rendered its error page.
|
|
130
|
+
STEGA_CHARS.test(document.body?.innerText ?? "");
|
|
131
|
+
}
|
|
132
|
+
function watchUntilSettled(onBreak) {
|
|
133
|
+
const interval = setInterval(() => {
|
|
134
|
+
if (showsPreprContent()) return;
|
|
135
|
+
clearInterval(interval);
|
|
136
|
+
onBreak();
|
|
137
|
+
}, RENDER_POLL_MS);
|
|
138
|
+
return () => clearInterval(interval);
|
|
139
|
+
}
|
|
140
|
+
var QUARANTINE_KEY = "__prepr_live_quarantine";
|
|
141
|
+
var MAX_QUARANTINED = 50;
|
|
142
|
+
function quarantined() {
|
|
143
|
+
try {
|
|
144
|
+
const stored = JSON.parse(
|
|
145
|
+
sessionStorage.getItem(QUARANTINE_KEY) ?? "[]"
|
|
146
|
+
);
|
|
147
|
+
return Array.isArray(stored) ? stored.filter((key) => typeof key === "string") : [];
|
|
148
|
+
} catch {
|
|
149
|
+
return [];
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function setQuarantined(keys) {
|
|
153
|
+
try {
|
|
154
|
+
if (keys.length) {
|
|
155
|
+
sessionStorage.setItem(
|
|
156
|
+
QUARANTINE_KEY,
|
|
157
|
+
JSON.stringify(keys.slice(-MAX_QUARANTINED))
|
|
158
|
+
);
|
|
159
|
+
} else {
|
|
160
|
+
sessionStorage.removeItem(QUARANTINE_KEY);
|
|
161
|
+
}
|
|
162
|
+
} catch {
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
var patchKey = (patch) => JSON.stringify(patch);
|
|
166
|
+
var CLIENT_RENDER_MS = 300;
|
|
167
|
+
var recovering = false;
|
|
168
|
+
function recoverFrom(log, lastGood) {
|
|
169
|
+
if (recovering) return;
|
|
170
|
+
recovering = true;
|
|
171
|
+
debug.warn("a live change broke the page; reloading without it");
|
|
172
|
+
const good = new Set(lastGood.map(patchKey));
|
|
173
|
+
const broken = log.map(patchKey).filter((key) => !good.has(key));
|
|
174
|
+
setQuarantined([...quarantined(), ...broken]);
|
|
175
|
+
window.location.reload();
|
|
176
|
+
}
|
|
177
|
+
function watchClientRenders(watched) {
|
|
178
|
+
let timer = null;
|
|
179
|
+
let lastGood = [];
|
|
180
|
+
watched.onLogChange((log) => {
|
|
181
|
+
if (timer) clearTimeout(timer);
|
|
182
|
+
timer = null;
|
|
183
|
+
if (!log.length) {
|
|
184
|
+
lastGood = [];
|
|
185
|
+
setQuarantined([]);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
if (!showsPreprContent()) return;
|
|
189
|
+
timer = setTimeout(() => {
|
|
190
|
+
timer = null;
|
|
191
|
+
if (showsPreprContent()) lastGood = log;
|
|
192
|
+
else recoverFrom(log, lastGood);
|
|
193
|
+
}, CLIENT_RENDER_MS);
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
function createLogSync() {
|
|
197
|
+
let timer = null;
|
|
198
|
+
let inFlight = false;
|
|
199
|
+
let pending = null;
|
|
200
|
+
let cancelled = false;
|
|
201
|
+
let lastGood = [];
|
|
202
|
+
const send = async (incoming) => {
|
|
203
|
+
const skipped = new Set(quarantined());
|
|
204
|
+
const log = skipped.size ? incoming.filter((patch) => !skipped.has(patchKey(patch))) : incoming;
|
|
205
|
+
if (!log.length) return;
|
|
206
|
+
inFlight = true;
|
|
207
|
+
const stopWatching = showsPreprContent() ? watchUntilSettled(() => recoverFrom(log, lastGood)) : () => {
|
|
208
|
+
};
|
|
209
|
+
let failed = false;
|
|
210
|
+
try {
|
|
211
|
+
await syncPreprLiveLog(log);
|
|
212
|
+
} catch (error) {
|
|
213
|
+
failed = true;
|
|
214
|
+
debug.log("sending unsaved changes failed", error);
|
|
215
|
+
}
|
|
216
|
+
await new Promise((resolve) => setTimeout(resolve, RENDER_SETTLE_MS));
|
|
217
|
+
stopWatching();
|
|
218
|
+
inFlight = false;
|
|
219
|
+
if (recovering) return;
|
|
220
|
+
if (!failed) lastGood = log;
|
|
221
|
+
if (pending && !cancelled) {
|
|
222
|
+
const next = pending;
|
|
223
|
+
pending = null;
|
|
224
|
+
void send(next);
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
return {
|
|
228
|
+
schedule(log) {
|
|
229
|
+
cancelled = false;
|
|
230
|
+
if (timer) clearTimeout(timer);
|
|
231
|
+
timer = setTimeout(() => {
|
|
232
|
+
timer = null;
|
|
233
|
+
if (inFlight) pending = log;
|
|
234
|
+
else void send(log);
|
|
235
|
+
}, LOG_SYNC_DEBOUNCE_MS);
|
|
236
|
+
},
|
|
237
|
+
cancel() {
|
|
238
|
+
cancelled = true;
|
|
239
|
+
pending = null;
|
|
240
|
+
if (timer) clearTimeout(timer);
|
|
241
|
+
timer = null;
|
|
242
|
+
},
|
|
243
|
+
/** After a save: the stored content is what renders now. */
|
|
244
|
+
reset() {
|
|
245
|
+
this.cancel();
|
|
246
|
+
lastGood = [];
|
|
247
|
+
setQuarantined([]);
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
onTestReset(() => {
|
|
252
|
+
bridge?.stop();
|
|
253
|
+
bridge = null;
|
|
254
|
+
store = null;
|
|
255
|
+
starting = null;
|
|
256
|
+
recovering = false;
|
|
257
|
+
startListeners.clear();
|
|
258
|
+
allowedEditorOrigins = void 0;
|
|
259
|
+
strictMode = false;
|
|
260
|
+
});
|
|
261
|
+
export {
|
|
262
|
+
PreprRefresh,
|
|
263
|
+
initLivePreview,
|
|
264
|
+
useLive
|
|
265
|
+
};
|
package/dist/nuxt/index.cjs
CHANGED
package/dist/nuxt/index.js
CHANGED