@preprio/toolkit 0.5.0-rc.3 → 0.5.0-rc.5

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.
Files changed (39) hide show
  1. package/README.md +226 -151
  2. package/dist/astro/index.cjs +27 -7
  3. package/dist/astro/index.js +27 -7
  4. package/dist/{chunk-SXUDBGLZ.js → chunk-3X55JL52.js} +1 -1
  5. package/dist/{chunk-42Z2VW7G.js → chunk-74KY5EAM.js} +67 -9
  6. package/dist/{chunk-MDAV2FLM.js → chunk-CMUFTCG3.js} +1 -1
  7. package/dist/{chunk-PP3O7RB4.js → chunk-SBLZANL5.js} +1 -1
  8. package/dist/{chunk-6WY3MWCX.js → chunk-TG6N3C22.js} +1 -1
  9. package/dist/index.cjs +68 -10
  10. package/dist/index.d.cts +1 -1
  11. package/dist/index.d.ts +1 -1
  12. package/dist/index.js +5 -5
  13. package/dist/{live-preview-EADA3ZL4.js → live-preview-H4643QYF.js} +2 -2
  14. package/dist/nextjs/components.cjs +1 -1
  15. package/dist/nextjs/components.js +2 -2
  16. package/dist/nextjs/index.cjs +27 -7
  17. package/dist/nextjs/index.d.cts +2 -0
  18. package/dist/nextjs/index.d.ts +2 -0
  19. package/dist/nextjs/index.js +27 -7
  20. package/dist/nextjs/live-action.js +3 -3
  21. package/dist/nextjs/live.cjs +73 -12
  22. package/dist/nextjs/live.d.cts +3 -1
  23. package/dist/nextjs/live.d.ts +3 -1
  24. package/dist/nextjs/live.js +5 -2
  25. package/dist/nuxt/index.cjs +1 -1
  26. package/dist/nuxt/index.js +1 -1
  27. package/dist/nuxt/{live-preview-PTEC3GPC.js → live-preview-O2ZTJNW4.js} +67 -9
  28. package/dist/nuxt/live.cjs +69 -11
  29. package/dist/nuxt/live.js +1 -1
  30. package/dist/react/components.cjs +1 -1
  31. package/dist/react/components.js +1 -1
  32. package/dist/sveltekit/index.cjs +1 -1
  33. package/dist/sveltekit/index.js +1 -1
  34. package/dist/sveltekit/live-preview-O2ZTJNW4.js +1520 -0
  35. package/dist/sveltekit/live.cjs +1731 -0
  36. package/dist/sveltekit/live.d.cts +59 -0
  37. package/dist/sveltekit/live.d.ts +59 -0
  38. package/dist/sveltekit/live.js +97 -0
  39. package/package.json +6 -1
@@ -311,6 +311,8 @@ interface LivePreviewConfig {
311
311
  interface PreprRefreshProps {
312
312
  /** Pass your preview flag; never `true` on a production deployment. */
313
313
  enabled?: boolean;
314
+ /** Exact editor origins, replacing the `*.prepr.io` default. */
315
+ allowedEditorOrigins?: string[];
314
316
  /** Your preview token uses GraphQL strict mode; see the toolbar option. */
315
317
  strictMode?: boolean;
316
318
  }
@@ -2,7 +2,7 @@
2
2
  import { NextResponse } from "next/server";
3
3
 
4
4
  // src/version.ts
5
- var VERSION = "0.5.0-rc.3";
5
+ var VERSION = "0.5.0-rc.5";
6
6
 
7
7
  // src/core/constants.ts
8
8
  var COOKIE_PREVIEW_MODE = "Prepr-Preview-Mode";
@@ -281,6 +281,20 @@ var UPPERCASE_BOUNDARY = /(.)(?=[A-Z])/g;
281
281
  function graphqlFieldName(apiId) {
282
282
  return apiId.replace(UPPERCASE_BOUNDARY, "$1_").toLowerCase();
283
283
  }
284
+ var TEXT_BODY_FIELDS = [
285
+ { name: "body", plain: false },
286
+ { name: "html", plain: false },
287
+ { name: "text", plain: true }
288
+ ];
289
+ var TAG_PATTERN = /<[^>]+>/g;
290
+ function plainTextFromHtml(html) {
291
+ return html.replace(TAG_PATTERN, "");
292
+ }
293
+ function isTextEntry(record) {
294
+ const typename = record.__typename;
295
+ if (typeof typename === "string") return typename === "Text";
296
+ return "html" in record || "text" in record && "format" in record;
297
+ }
284
298
 
285
299
  // src/core/live-preview/index-snapshot.ts
286
300
  var debug = createScopedLogger("live-preview");
@@ -418,7 +432,6 @@ function listKeysOf(entry) {
418
432
  }
419
433
  return keys;
420
434
  }
421
- var TAG_PATTERN = /<[^>]+>/g;
422
435
  function buildText(entry, template) {
423
436
  const body = typeof entry.body === "string" ? entry.body : "";
424
437
  const full = {
@@ -426,7 +439,7 @@ function buildText(entry, template) {
426
439
  _id: entry._id,
427
440
  body,
428
441
  html: body,
429
- text: body.replace(TAG_PATTERN, ""),
442
+ text: plainTextFromHtml(body),
430
443
  format: entry.format ?? null
431
444
  };
432
445
  if (!template) return full;
@@ -886,11 +899,13 @@ function applyPatch(root, index, patch, doc, result) {
886
899
  const segments = patch.field.split(".");
887
900
  if (segments.some((s) => !s || FORBIDDEN_SEGMENTS2.has(s))) return root;
888
901
  const targets = [];
902
+ const plain = /* @__PURE__ */ new Set();
889
903
  for (const ownerPath of ownerPaths) {
890
904
  if (doc) {
891
905
  const resolved = resolveTargets(root, doc, ownerPath, segments);
892
906
  root = resolved.root;
893
907
  targets.push(...resolved.targets);
908
+ resolved.plain.forEach((target) => plain.add(target));
894
909
  } else {
895
910
  targets.push([...ownerPath, ...segments.map(toPathSegment)]);
896
911
  }
@@ -904,7 +919,8 @@ function applyPatch(root, index, patch, doc, result) {
904
919
  }
905
920
  if (patch.type === "field") {
906
921
  for (const target of targets) {
907
- let value = keepEditLink(readAtPath(root, target), patch.value);
922
+ const pushed = plain.has(target) && typeof patch.value === "string" ? plainTextFromHtml(patch.value) : patch.value;
923
+ let value = keepEditLink(readAtPath(root, target), pushed);
908
924
  if (patch.edit && typeof value === "string" && !vercelStegaSplit2(value).encoded) {
909
925
  value = editLinksFor(result, patch.edit.item)?.encode(
910
926
  value,
@@ -968,9 +984,10 @@ function resolveTargets(root, doc, ownerPath, segments) {
968
984
  owner = owner[step];
969
985
  }
970
986
  const selections = selectionsAt(root, doc, ownerPath);
971
- if (!selections) return { root, targets: [] };
987
+ if (!selections) return { root, targets: [], plain: /* @__PURE__ */ new Set() };
972
988
  let current = root;
973
989
  const out = [];
990
+ const plain = /* @__PURE__ */ new Set();
974
991
  const walk = (node, sel, i, path) => {
975
992
  if (i === segments.length) {
976
993
  out.push(path);
@@ -991,12 +1008,15 @@ function resolveTargets(root, doc, ownerPath, segments) {
991
1008
  if (node === null || typeof node !== "object") return;
992
1009
  const record = node;
993
1010
  const wanted = graphqlFieldName(segment);
1011
+ const textBody = i === segments.length - 1 && segment === "body" && isTextEntry(record) ? TEXT_BODY_FIELDS : null;
994
1012
  let matched = false;
995
1013
  for (const field of collectFields(sel, typenameOf(node), doc).values()) {
996
- if (field.name !== segment && field.name !== wanted) continue;
1014
+ const alias = textBody?.find((f) => f.name === field.name);
1015
+ if (field.name !== segment && field.name !== wanted && !alias) continue;
997
1016
  if (!Object.prototype.hasOwnProperty.call(record, field.key)) continue;
998
1017
  matched = true;
999
1018
  const fieldPath = [...path, field.key];
1019
+ if (alias?.plain) plain.add(fieldPath);
1000
1020
  let value = record[field.key];
1001
1021
  const next = segments[i + 1];
1002
1022
  if (value === null && field.selections.length && next !== void 0 && next !== "items" && !INDEX_SEGMENT.test(next)) {
@@ -1019,7 +1039,7 @@ function resolveTargets(root, doc, ownerPath, segments) {
1019
1039
  if (!matched && segment === "items") walk(node, sel, i + 1, path);
1020
1040
  };
1021
1041
  walk(owner, selections, 0, ownerPath);
1022
- return { root: current, targets: out };
1042
+ return { root: current, targets: out, plain };
1023
1043
  }
1024
1044
  var NON_ALPHANUMERIC = /[^a-z0-9]/gi;
1025
1045
  var typeKey = (name) => name.replace(NON_ALPHANUMERIC, "").toLowerCase();
@@ -1,11 +1,11 @@
1
1
  "use server";
2
2
  import {
3
3
  isPreviewFromHeaders
4
- } from "../chunk-SXUDBGLZ.js";
5
- import "../chunk-MDAV2FLM.js";
4
+ } from "../chunk-3X55JL52.js";
5
+ import "../chunk-CMUFTCG3.js";
6
6
  import {
7
7
  parsePatchLog
8
- } from "../chunk-42Z2VW7G.js";
8
+ } from "../chunk-74KY5EAM.js";
9
9
  import "../chunk-S3VT4UWM.js";
10
10
 
11
11
  // src/nextjs/live-action.ts
@@ -149,11 +149,25 @@ function fieldKeyIn(record, apiId) {
149
149
  const name = graphqlFieldName(apiId);
150
150
  return Object.prototype.hasOwnProperty.call(record, name) ? name : void 0;
151
151
  }
152
- var UPPERCASE_BOUNDARY;
152
+ function plainTextFromHtml(html) {
153
+ return html.replace(TAG_PATTERN, "");
154
+ }
155
+ function isTextEntry(record) {
156
+ const typename = record.__typename;
157
+ if (typeof typename === "string") return typename === "Text";
158
+ return "html" in record || "text" in record && "format" in record;
159
+ }
160
+ var UPPERCASE_BOUNDARY, TEXT_BODY_FIELDS, TAG_PATTERN;
153
161
  var init_field_name = __esm({
154
162
  "src/core/live-preview/field-name.ts"() {
155
163
  "use strict";
156
164
  UPPERCASE_BOUNDARY = /(.)(?=[A-Z])/g;
165
+ TEXT_BODY_FIELDS = [
166
+ { name: "body", plain: false },
167
+ { name: "html", plain: false },
168
+ { name: "text", plain: true }
169
+ ];
170
+ TAG_PATTERN = /<[^>]+>/g;
157
171
  }
158
172
  });
159
173
 
@@ -213,13 +227,51 @@ function patchSnapshot(snapshot, index, itemId, field, value) {
213
227
  if (!paths?.length) return snapshot;
214
228
  let root = snapshot;
215
229
  let changed = false;
230
+ const markChanged = () => {
231
+ changed = true;
232
+ };
216
233
  for (const path of paths) {
217
- root = patchAtPath(root, path, field, value, () => {
218
- changed = true;
219
- });
234
+ for (const [target, targetValue] of expandTextBody(
235
+ root,
236
+ path,
237
+ field,
238
+ value
239
+ )) {
240
+ root = patchAtPath(root, path, target, targetValue, markChanged);
241
+ }
220
242
  }
221
243
  return changed ? root : snapshot;
222
244
  }
245
+ function expandTextBody(root, path, field, value) {
246
+ const segments = field.split(".");
247
+ if (segments[segments.length - 1] !== "body" || typeof value !== "string") {
248
+ return [[field, value]];
249
+ }
250
+ let node = root;
251
+ for (const step of path) {
252
+ node = node[step];
253
+ }
254
+ for (const segment of segments.slice(0, -1)) {
255
+ if (node === null || typeof node !== "object") return [[field, value]];
256
+ const record2 = node;
257
+ node = INDEX_SEGMENT.test(segment) ? record2[Number(segment)] : record2[fieldKeyIn(record2, segment) ?? segment];
258
+ }
259
+ if (node === null || typeof node !== "object" || Array.isArray(node)) {
260
+ return [[field, value]];
261
+ }
262
+ const record = node;
263
+ if (!isTextEntry(record)) return [[field, value]];
264
+ const prefix = segments.slice(0, -1);
265
+ const writes = [];
266
+ for (const { name, plain } of TEXT_BODY_FIELDS) {
267
+ if (!(name in record)) continue;
268
+ writes.push([
269
+ [...prefix, name].join("."),
270
+ plain ? plainTextFromHtml(value) : value
271
+ ]);
272
+ }
273
+ return writes.length ? writes : [[field, value]];
274
+ }
223
275
  function patchAtPath(root, path, field, value, markChanged) {
224
276
  const segments = field.split(".");
225
277
  if (segments.some((s) => FORBIDDEN_SEGMENTS.has(s))) return root;
@@ -291,7 +343,7 @@ function buildText(entry, template) {
291
343
  _id: entry._id,
292
344
  body,
293
345
  html: body,
294
- text: body.replace(TAG_PATTERN, ""),
346
+ text: plainTextFromHtml(body),
295
347
  format: entry.format ?? null
296
348
  };
297
349
  if (!template) return full;
@@ -369,7 +421,7 @@ function keepEditLink(current, value) {
369
421
  if (!encoded || (0, import_stega.vercelStegaSplit)(value).encoded) return value;
370
422
  return value + encoded;
371
423
  }
372
- var import_stega, INDEX_SEGMENT, FORBIDDEN_SEGMENTS, TAG_PATTERN;
424
+ var import_stega, INDEX_SEGMENT, FORBIDDEN_SEGMENTS;
373
425
  var init_patch = __esm({
374
426
  "src/core/live-preview/patch.ts"() {
375
427
  "use strict";
@@ -377,7 +429,6 @@ var init_patch = __esm({
377
429
  init_field_name();
378
430
  INDEX_SEGMENT = /^\d+$/;
379
431
  FORBIDDEN_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
380
- TAG_PATTERN = /<[^>]+>/g;
381
432
  }
382
433
  });
383
434
 
@@ -1193,11 +1244,13 @@ function applyPatch(root, index, patch, doc, result) {
1193
1244
  const segments = patch.field.split(".");
1194
1245
  if (segments.some((s) => !s || FORBIDDEN_SEGMENTS2.has(s))) return root;
1195
1246
  const targets = [];
1247
+ const plain = /* @__PURE__ */ new Set();
1196
1248
  for (const ownerPath of ownerPaths) {
1197
1249
  if (doc) {
1198
1250
  const resolved = resolveTargets(root, doc, ownerPath, segments);
1199
1251
  root = resolved.root;
1200
1252
  targets.push(...resolved.targets);
1253
+ resolved.plain.forEach((target) => plain.add(target));
1201
1254
  } else {
1202
1255
  targets.push([...ownerPath, ...segments.map(toPathSegment)]);
1203
1256
  }
@@ -1211,7 +1264,8 @@ function applyPatch(root, index, patch, doc, result) {
1211
1264
  }
1212
1265
  if (patch.type === "field") {
1213
1266
  for (const target of targets) {
1214
- let value = keepEditLink(readAtPath(root, target), patch.value);
1267
+ const pushed = plain.has(target) && typeof patch.value === "string" ? plainTextFromHtml(patch.value) : patch.value;
1268
+ let value = keepEditLink(readAtPath(root, target), pushed);
1215
1269
  if (patch.edit && typeof value === "string" && !(0, import_stega2.vercelStegaSplit)(value).encoded) {
1216
1270
  value = editLinksFor(result, patch.edit.item)?.encode(
1217
1271
  value,
@@ -1275,9 +1329,10 @@ function resolveTargets(root, doc, ownerPath, segments) {
1275
1329
  owner = owner[step];
1276
1330
  }
1277
1331
  const selections = selectionsAt(root, doc, ownerPath);
1278
- if (!selections) return { root, targets: [] };
1332
+ if (!selections) return { root, targets: [], plain: /* @__PURE__ */ new Set() };
1279
1333
  let current = root;
1280
1334
  const out = [];
1335
+ const plain = /* @__PURE__ */ new Set();
1281
1336
  const walk = (node, sel, i, path) => {
1282
1337
  if (i === segments.length) {
1283
1338
  out.push(path);
@@ -1298,12 +1353,15 @@ function resolveTargets(root, doc, ownerPath, segments) {
1298
1353
  if (node === null || typeof node !== "object") return;
1299
1354
  const record = node;
1300
1355
  const wanted = graphqlFieldName(segment);
1356
+ const textBody = i === segments.length - 1 && segment === "body" && isTextEntry(record) ? TEXT_BODY_FIELDS : null;
1301
1357
  let matched = false;
1302
1358
  for (const field of collectFields(sel, typenameOf(node), doc).values()) {
1303
- if (field.name !== segment && field.name !== wanted) continue;
1359
+ const alias = textBody?.find((f) => f.name === field.name);
1360
+ if (field.name !== segment && field.name !== wanted && !alias) continue;
1304
1361
  if (!Object.prototype.hasOwnProperty.call(record, field.key)) continue;
1305
1362
  matched = true;
1306
1363
  const fieldPath = [...path, field.key];
1364
+ if (alias?.plain) plain.add(fieldPath);
1307
1365
  let value = record[field.key];
1308
1366
  const next = segments[i + 1];
1309
1367
  if (value === null && field.selections.length && next !== void 0 && next !== "items" && !INDEX_SEGMENT2.test(next)) {
@@ -1326,7 +1384,7 @@ function resolveTargets(root, doc, ownerPath, segments) {
1326
1384
  if (!matched && segment === "items") walk(node, sel, i + 1, path);
1327
1385
  };
1328
1386
  walk(owner, selections, 0, ownerPath);
1329
- return { root: current, targets: out };
1387
+ return { root: current, targets: out, plain };
1330
1388
  }
1331
1389
  function resolveTypename(doc, selections, typename, typeName) {
1332
1390
  const known = typeConditions(selections, doc);
@@ -1664,12 +1722,15 @@ function useLive(data, opts) {
1664
1722
  }
1665
1723
  function PreprRefresh({
1666
1724
  enabled = false,
1725
+ allowedEditorOrigins: origins,
1667
1726
  strictMode: strict = false
1668
1727
  }) {
1669
1728
  const router = (0, import_navigation.useRouter)();
1729
+ const originsKey = origins?.join(" ");
1670
1730
  (0, import_react.useEffect)(() => {
1671
1731
  if (!enabled || !isFramed()) return;
1672
1732
  if (strict) strictMode = true;
1733
+ if (originsKey) allowedEditorOrigins = originsKey.split(" ");
1673
1734
  const sync = createLogSync();
1674
1735
  let unsubscribe = null;
1675
1736
  let active = true;
@@ -1690,7 +1751,7 @@ function PreprRefresh({
1690
1751
  unsubscribe?.();
1691
1752
  sync.cancel();
1692
1753
  };
1693
- }, [enabled, strict, router]);
1754
+ }, [enabled, originsKey, strict, router]);
1694
1755
  return null;
1695
1756
  }
1696
1757
  var STEGA_CHARS = /[\u200b-\u200d\u2060-\u2064\ufeff]/;
@@ -19,6 +19,8 @@ interface LivePreviewConfig {
19
19
  interface PreprRefreshProps {
20
20
  /** Pass your preview flag; never `true` on a production deployment. */
21
21
  enabled?: boolean;
22
+ /** Exact editor origins, replacing the `*.prepr.io` default. */
23
+ allowedEditorOrigins?: string[];
22
24
  /** Your preview token uses GraphQL strict mode; see the toolbar option. */
23
25
  strictMode?: boolean;
24
26
  }
@@ -47,6 +49,6 @@ declare function useLive<T>(data: T, opts?: UseLiveOptions): T;
47
49
  * Use this or `useLive` with a `query`, not both: a save would refetch and
48
50
  * refresh.
49
51
  */
50
- declare function PreprRefresh({ enabled, strictMode: strict, }: PreprRefreshProps): null;
52
+ declare function PreprRefresh({ enabled, allowedEditorOrigins: origins, strictMode: strict, }: PreprRefreshProps): null;
51
53
 
52
54
  export { type LivePreviewConfig, PreprRefresh, type PreprRefreshProps, type UseLiveOptions, initLivePreview, useLive };
@@ -19,6 +19,8 @@ interface LivePreviewConfig {
19
19
  interface PreprRefreshProps {
20
20
  /** Pass your preview flag; never `true` on a production deployment. */
21
21
  enabled?: boolean;
22
+ /** Exact editor origins, replacing the `*.prepr.io` default. */
23
+ allowedEditorOrigins?: string[];
22
24
  /** Your preview token uses GraphQL strict mode; see the toolbar option. */
23
25
  strictMode?: boolean;
24
26
  }
@@ -47,6 +49,6 @@ declare function useLive<T>(data: T, opts?: UseLiveOptions): T;
47
49
  * Use this or `useLive` with a `query`, not both: a save would refetch and
48
50
  * refresh.
49
51
  */
50
- declare function PreprRefresh({ enabled, strictMode: strict, }: PreprRefreshProps): null;
52
+ declare function PreprRefresh({ enabled, allowedEditorOrigins: origins, strictMode: strict, }: PreprRefreshProps): null;
51
53
 
52
54
  export { type LivePreviewConfig, PreprRefresh, type PreprRefreshProps, type UseLiveOptions, initLivePreview, useLive };
@@ -27,7 +27,7 @@ function isFramed() {
27
27
  return typeof window !== "undefined" && window.self !== window.top;
28
28
  }
29
29
  function startLive() {
30
- starting ?? (starting = import("../live-preview-EADA3ZL4.js").then(
30
+ starting ?? (starting = import("../live-preview-H4643QYF.js").then(
31
31
  ({ createLiveBridge, createLiveStore }) => {
32
32
  const skipped = new Set(quarantined());
33
33
  const started = createLiveStore({
@@ -92,12 +92,15 @@ function useLive(data, opts) {
92
92
  }
93
93
  function PreprRefresh({
94
94
  enabled = false,
95
+ allowedEditorOrigins: origins,
95
96
  strictMode: strict = false
96
97
  }) {
97
98
  const router = useRouter();
99
+ const originsKey = origins?.join(" ");
98
100
  useEffect(() => {
99
101
  if (!enabled || !isFramed()) return;
100
102
  if (strict) strictMode = true;
103
+ if (originsKey) allowedEditorOrigins = originsKey.split(" ");
101
104
  const sync = createLogSync();
102
105
  let unsubscribe = null;
103
106
  let active = true;
@@ -118,7 +121,7 @@ function PreprRefresh({
118
121
  unsubscribe?.();
119
122
  sync.cancel();
120
123
  };
121
- }, [enabled, strict, router]);
124
+ }, [enabled, originsKey, strict, router]);
122
125
  return null;
123
126
  }
124
127
  var STEGA_CHARS = /[\u200b-\u200d\u2060-\u2064\ufeff]/;
@@ -35,7 +35,7 @@ __export(nuxt_exports, {
35
35
  module.exports = __toCommonJS(nuxt_exports);
36
36
 
37
37
  // src/version.ts
38
- var VERSION = "0.5.0-rc.3";
38
+ var VERSION = "0.5.0-rc.5";
39
39
 
40
40
  // src/core/constants.ts
41
41
  var COOKIE_PREVIEW_MODE = "Prepr-Preview-Mode";
@@ -1,5 +1,5 @@
1
1
  // src/version.ts
2
- var VERSION = "0.5.0-rc.3";
2
+ var VERSION = "0.5.0-rc.5";
3
3
 
4
4
  // src/core/constants.ts
5
5
  var COOKIE_PREVIEW_MODE = "Prepr-Preview-Mode";
@@ -112,6 +112,20 @@ function fieldKeyIn(record, apiId) {
112
112
  const name = graphqlFieldName(apiId);
113
113
  return Object.prototype.hasOwnProperty.call(record, name) ? name : void 0;
114
114
  }
115
+ var TEXT_BODY_FIELDS = [
116
+ { name: "body", plain: false },
117
+ { name: "html", plain: false },
118
+ { name: "text", plain: true }
119
+ ];
120
+ var TAG_PATTERN = /<[^>]+>/g;
121
+ function plainTextFromHtml(html) {
122
+ return html.replace(TAG_PATTERN, "");
123
+ }
124
+ function isTextEntry(record) {
125
+ const typename = record.__typename;
126
+ if (typeof typename === "string") return typename === "Text";
127
+ return "html" in record || "text" in record && "format" in record;
128
+ }
115
129
 
116
130
  // src/core/live-preview/patch.ts
117
131
  var INDEX_SEGMENT = /^\d+$/;
@@ -171,13 +185,51 @@ function patchSnapshot(snapshot, index, itemId, field, value) {
171
185
  if (!paths?.length) return snapshot;
172
186
  let root = snapshot;
173
187
  let changed = false;
188
+ const markChanged = () => {
189
+ changed = true;
190
+ };
174
191
  for (const path of paths) {
175
- root = patchAtPath(root, path, field, value, () => {
176
- changed = true;
177
- });
192
+ for (const [target, targetValue] of expandTextBody(
193
+ root,
194
+ path,
195
+ field,
196
+ value
197
+ )) {
198
+ root = patchAtPath(root, path, target, targetValue, markChanged);
199
+ }
178
200
  }
179
201
  return changed ? root : snapshot;
180
202
  }
203
+ function expandTextBody(root, path, field, value) {
204
+ const segments = field.split(".");
205
+ if (segments[segments.length - 1] !== "body" || typeof value !== "string") {
206
+ return [[field, value]];
207
+ }
208
+ let node = root;
209
+ for (const step of path) {
210
+ node = node[step];
211
+ }
212
+ for (const segment of segments.slice(0, -1)) {
213
+ if (node === null || typeof node !== "object") return [[field, value]];
214
+ const record2 = node;
215
+ node = INDEX_SEGMENT.test(segment) ? record2[Number(segment)] : record2[fieldKeyIn(record2, segment) ?? segment];
216
+ }
217
+ if (node === null || typeof node !== "object" || Array.isArray(node)) {
218
+ return [[field, value]];
219
+ }
220
+ const record = node;
221
+ if (!isTextEntry(record)) return [[field, value]];
222
+ const prefix = segments.slice(0, -1);
223
+ const writes = [];
224
+ for (const { name, plain } of TEXT_BODY_FIELDS) {
225
+ if (!(name in record)) continue;
226
+ writes.push([
227
+ [...prefix, name].join("."),
228
+ plain ? plainTextFromHtml(value) : value
229
+ ]);
230
+ }
231
+ return writes.length ? writes : [[field, value]];
232
+ }
181
233
  function patchAtPath(root, path, field, value, markChanged) {
182
234
  const segments = field.split(".");
183
235
  if (segments.some((s) => FORBIDDEN_SEGMENTS.has(s))) return root;
@@ -242,7 +294,6 @@ function listKeysOf(entry) {
242
294
  }
243
295
  return keys;
244
296
  }
245
- var TAG_PATTERN = /<[^>]+>/g;
246
297
  function buildText(entry, template) {
247
298
  const body = typeof entry.body === "string" ? entry.body : "";
248
299
  const full = {
@@ -250,7 +301,7 @@ function buildText(entry, template) {
250
301
  _id: entry._id,
251
302
  body,
252
303
  html: body,
253
- text: body.replace(TAG_PATTERN, ""),
304
+ text: plainTextFromHtml(body),
254
305
  format: entry.format ?? null
255
306
  };
256
307
  if (!template) return full;
@@ -1117,11 +1168,13 @@ function applyPatch(root, index, patch, doc, result) {
1117
1168
  const segments = patch.field.split(".");
1118
1169
  if (segments.some((s) => !s || FORBIDDEN_SEGMENTS2.has(s))) return root;
1119
1170
  const targets = [];
1171
+ const plain = /* @__PURE__ */ new Set();
1120
1172
  for (const ownerPath of ownerPaths) {
1121
1173
  if (doc) {
1122
1174
  const resolved = resolveTargets(root, doc, ownerPath, segments);
1123
1175
  root = resolved.root;
1124
1176
  targets.push(...resolved.targets);
1177
+ resolved.plain.forEach((target) => plain.add(target));
1125
1178
  } else {
1126
1179
  targets.push([...ownerPath, ...segments.map(toPathSegment)]);
1127
1180
  }
@@ -1135,7 +1188,8 @@ function applyPatch(root, index, patch, doc, result) {
1135
1188
  }
1136
1189
  if (patch.type === "field") {
1137
1190
  for (const target of targets) {
1138
- let value = keepEditLink(readAtPath(root, target), patch.value);
1191
+ const pushed = plain.has(target) && typeof patch.value === "string" ? plainTextFromHtml(patch.value) : patch.value;
1192
+ let value = keepEditLink(readAtPath(root, target), pushed);
1139
1193
  if (patch.edit && typeof value === "string" && !vercelStegaSplit2(value).encoded) {
1140
1194
  value = editLinksFor(result, patch.edit.item)?.encode(
1141
1195
  value,
@@ -1199,9 +1253,10 @@ function resolveTargets(root, doc, ownerPath, segments) {
1199
1253
  owner = owner[step];
1200
1254
  }
1201
1255
  const selections = selectionsAt(root, doc, ownerPath);
1202
- if (!selections) return { root, targets: [] };
1256
+ if (!selections) return { root, targets: [], plain: /* @__PURE__ */ new Set() };
1203
1257
  let current = root;
1204
1258
  const out = [];
1259
+ const plain = /* @__PURE__ */ new Set();
1205
1260
  const walk = (node, sel, i, path) => {
1206
1261
  if (i === segments.length) {
1207
1262
  out.push(path);
@@ -1222,12 +1277,15 @@ function resolveTargets(root, doc, ownerPath, segments) {
1222
1277
  if (node === null || typeof node !== "object") return;
1223
1278
  const record = node;
1224
1279
  const wanted = graphqlFieldName(segment);
1280
+ const textBody = i === segments.length - 1 && segment === "body" && isTextEntry(record) ? TEXT_BODY_FIELDS : null;
1225
1281
  let matched = false;
1226
1282
  for (const field of collectFields(sel, typenameOf(node), doc).values()) {
1227
- if (field.name !== segment && field.name !== wanted) continue;
1283
+ const alias = textBody?.find((f) => f.name === field.name);
1284
+ if (field.name !== segment && field.name !== wanted && !alias) continue;
1228
1285
  if (!Object.prototype.hasOwnProperty.call(record, field.key)) continue;
1229
1286
  matched = true;
1230
1287
  const fieldPath = [...path, field.key];
1288
+ if (alias?.plain) plain.add(fieldPath);
1231
1289
  let value = record[field.key];
1232
1290
  const next = segments[i + 1];
1233
1291
  if (value === null && field.selections.length && next !== void 0 && next !== "items" && !INDEX_SEGMENT2.test(next)) {
@@ -1250,7 +1308,7 @@ function resolveTargets(root, doc, ownerPath, segments) {
1250
1308
  if (!matched && segment === "items") walk(node, sel, i + 1, path);
1251
1309
  };
1252
1310
  walk(owner, selections, 0, ownerPath);
1253
- return { root: current, targets: out };
1311
+ return { root: current, targets: out, plain };
1254
1312
  }
1255
1313
  var NON_ALPHANUMERIC = /[^a-z0-9]/gi;
1256
1314
  var typeKey = (name) => name.replace(NON_ALPHANUMERIC, "").toLowerCase();