@preprio/toolkit 0.5.0-rc.5 → 0.5.0-rc.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 +133 -26
- package/dist/astro/index.cjs +126 -30
- package/dist/astro/index.js +130 -30
- package/dist/{chunk-3X55JL52.js → chunk-HVS5QLWG.js} +1 -1
- package/dist/{chunk-TG6N3C22.js → chunk-IPMB32AZ.js} +1 -1
- package/dist/{chunk-CMUFTCG3.js → chunk-JUXP5KRX.js} +1 -1
- package/dist/{chunk-74KY5EAM.js → chunk-KD3GK6TO.js} +2 -0
- package/dist/{chunk-SBLZANL5.js → chunk-SUAFASBB.js} +11 -3
- package/dist/codegen/index.cjs +82 -0
- package/dist/codegen/index.d.cts +70 -0
- package/dist/codegen/index.d.ts +70 -0
- package/dist/codegen/index.js +64 -0
- package/dist/index.cjs +133 -28
- package/dist/index.d.cts +14 -2
- package/dist/index.d.ts +14 -2
- package/dist/index.js +108 -6
- package/dist/{live-preview-H4643QYF.js → live-preview-DLXGXRE5.js} +2 -2
- package/dist/nextjs/components.cjs +11 -3
- package/dist/nextjs/components.js +2 -2
- package/dist/nextjs/index.cjs +124 -28
- package/dist/nextjs/index.js +128 -28
- package/dist/nextjs/live-action.js +3 -3
- package/dist/nextjs/live.js +1 -1
- package/dist/nuxt/chunk-BWPGO25E.js +340 -0
- package/dist/nuxt/index.cjs +1 -1
- package/dist/nuxt/index.js +1 -1
- package/dist/nuxt/{live-preview-O2ZTJNW4.js → live-preview-2RCU35W5.js} +6 -337
- package/dist/nuxt/live.cjs +1007 -911
- package/dist/nuxt/live.js +109 -5
- package/dist/react/components.cjs +11 -3
- package/dist/react/components.js +11 -3
- package/dist/sveltekit/chunk-BWPGO25E.js +340 -0
- package/dist/sveltekit/index.cjs +1 -1
- package/dist/sveltekit/index.js +1 -1
- package/dist/sveltekit/{live-preview-O2ZTJNW4.js → live-preview-2RCU35W5.js} +6 -337
- package/dist/sveltekit/live.cjs +1008 -909
- package/dist/sveltekit/live.js +118 -11
- package/package.json +11 -1
package/README.md
CHANGED
|
@@ -127,6 +127,7 @@ Per-framework peer dependencies — all optional; installing without them is fin
|
|
|
127
127
|
| `@preprio/toolkit/astro` | none (`.astro` components compile in your own pipeline) |
|
|
128
128
|
| `@preprio/toolkit/sveltekit` | `svelte` (only for the `.svelte` components) |
|
|
129
129
|
| `@preprio/toolkit/sveltekit/live` | none (returns a plain Svelte store) |
|
|
130
|
+
| `@preprio/toolkit/codegen` | `graphql` >= 16 (build time, from your Codegen setup) |
|
|
130
131
|
| `@preprio/toolkit/nuxt` | `vue` (only for the `.vue` components) |
|
|
131
132
|
| `@preprio/toolkit/nuxt/live` | `vue` >= 3.3 |
|
|
132
133
|
|
|
@@ -621,23 +622,22 @@ const location = useLocation();
|
|
|
621
622
|
|
|
622
623
|
### Tracking pageviews
|
|
623
624
|
|
|
624
|
-
|
|
625
|
+
The pixel tracks pageviews by itself, client-side route changes included. It counts a view for the Prepr content item named by a `prepr:id` meta tag in `<head>`, and watches that tag: when a route change points it at another item, the pixel records a new view. Render the tag on every page that shows a Prepr item:
|
|
625
626
|
|
|
626
627
|
```tsx
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
useEffect(() => {
|
|
635
|
-
trackEvent('pageload');
|
|
636
|
-
}, [pathname]);
|
|
628
|
+
function Page({ page }: { page: { _id: string } }) {
|
|
629
|
+
return (
|
|
630
|
+
<>
|
|
631
|
+
<meta property="prepr:id" content={page._id} />
|
|
632
|
+
{/* … */}
|
|
633
|
+
</>
|
|
634
|
+
);
|
|
637
635
|
}
|
|
638
636
|
```
|
|
639
637
|
|
|
640
|
-
|
|
638
|
+
React 19 hoists a `<meta>` rendered anywhere in the tree into `<head>`. On React 18, set it with your head manager (`react-helmet-async` or similar) instead.
|
|
639
|
+
|
|
640
|
+
A page without the tag records no view. Calling `trackEvent('pageload')` yourself does not help: the pixel takes one `pageload` per page load and ignores the rest. Use `trackEvent` for your own events, such as `trackEvent('add_to_cart', { productId })`.
|
|
641
641
|
|
|
642
642
|
The `id` is your access token, and in a client-rendered app it is public — bundled into JS and visible in devtools. Use a tracking-only token; do not derive it from a browser-exposed GraphQL URL, which would also expose content read access.
|
|
643
643
|
|
|
@@ -729,22 +729,129 @@ exactly these.
|
|
|
729
729
|
|
|
730
730
|
Live updates and click-to-edit find content by id, never by position, so
|
|
731
731
|
they stay correct while an editor adds, removes or reorders blocks before
|
|
732
|
-
saving
|
|
732
|
+
saving. Every model, component and dynamic content entry that should
|
|
733
|
+
live-update needs `_id` (and ideally `__typename`) in the query result; items
|
|
734
|
+
without `_id` stay static.
|
|
735
|
+
|
|
736
|
+
There are three ways to get them there. Pick the first that fits your setup.
|
|
737
|
+
|
|
738
|
+
#### Option 1: GraphQL Codegen (recommended)
|
|
739
|
+
|
|
740
|
+
Add the `preprIds` document transform to your Codegen output. `_id` is
|
|
741
|
+
added wherever your schema has one: on models and components, and inside
|
|
742
|
+
each member of a dynamic content field. Collections like `Pages` and unions
|
|
743
|
+
themselves never get an `_id`, so the queries stay valid. Your `.graphql`
|
|
744
|
+
files stay as they are; the generated documents and types include the field,
|
|
745
|
+
at no runtime cost. Adding a field never breaks code that compiles today, so
|
|
746
|
+
this is safe to turn on in an existing project.
|
|
747
|
+
|
|
748
|
+
```ts
|
|
749
|
+
// codegen.ts
|
|
750
|
+
import type { CodegenConfig } from '@graphql-codegen/cli';
|
|
751
|
+
import { preprIds } from '@preprio/toolkit/codegen';
|
|
752
|
+
|
|
753
|
+
const config: CodegenConfig = {
|
|
754
|
+
schema: process.env.PREPR_GRAPHQL_URL,
|
|
755
|
+
documents: ['src/queries/**/*.graphql'],
|
|
756
|
+
generates: {
|
|
757
|
+
'src/gql/': {
|
|
758
|
+
preset: 'client',
|
|
759
|
+
documentTransforms: [preprIds],
|
|
760
|
+
},
|
|
761
|
+
},
|
|
762
|
+
};
|
|
763
|
+
|
|
764
|
+
export default config;
|
|
765
|
+
```
|
|
766
|
+
|
|
767
|
+
Run Codegen again after adding it. Needs `graphql` 16 or later, which a
|
|
768
|
+
Codegen setup already has.
|
|
769
|
+
|
|
770
|
+
To add `__typename` everywhere too, use `createPreprIds({ typename: true })`
|
|
771
|
+
instead. It is off by default because a selected `__typename` changes the
|
|
772
|
+
generated types: each member of a union is typed on its own, so code that
|
|
773
|
+
reads a union's fields without checking `__typename` first stops compiling.
|
|
774
|
+
Live preview only needs it on dynamic content entries, where you select it
|
|
775
|
+
already to render them.
|
|
776
|
+
|
|
777
|
+
#### Option 2: at runtime, without Codegen
|
|
778
|
+
|
|
779
|
+
`addPreprIds(document, schema)` does the same to a parsed query. It needs
|
|
780
|
+
your schema, which Prepr serves through introspection on the same GraphQL
|
|
781
|
+
URL. Load it once on the server, and transform each query once:
|
|
782
|
+
|
|
783
|
+
```ts
|
|
784
|
+
import { addPreprIds } from '@preprio/toolkit/codegen';
|
|
785
|
+
import {
|
|
786
|
+
buildClientSchema,
|
|
787
|
+
getIntrospectionQuery,
|
|
788
|
+
parse,
|
|
789
|
+
print,
|
|
790
|
+
type GraphQLSchema,
|
|
791
|
+
} from 'graphql';
|
|
792
|
+
|
|
793
|
+
const url = process.env.PREPR_GRAPHQL_URL!;
|
|
794
|
+
|
|
795
|
+
let schema: Promise<GraphQLSchema> | undefined;
|
|
796
|
+
function preprSchema(): Promise<GraphQLSchema> {
|
|
797
|
+
schema ??= fetch(url, {
|
|
798
|
+
method: 'POST',
|
|
799
|
+
headers: { 'Content-Type': 'application/json' },
|
|
800
|
+
body: JSON.stringify({ query: getIntrospectionQuery() }),
|
|
801
|
+
})
|
|
802
|
+
.then((response) => response.json())
|
|
803
|
+
.then(({ data }) => buildClientSchema(data));
|
|
804
|
+
return schema;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
const prepared = new Map<string, string>();
|
|
808
|
+
export async function withPreprIds(query: string): Promise<string> {
|
|
809
|
+
let result = prepared.get(query);
|
|
810
|
+
if (!result) {
|
|
811
|
+
result = print(addPreprIds(parse(query), await preprSchema()));
|
|
812
|
+
prepared.set(query, result);
|
|
813
|
+
}
|
|
814
|
+
return result;
|
|
815
|
+
}
|
|
816
|
+
```
|
|
817
|
+
|
|
818
|
+
Then send `await withPreprIds(query)` instead of `query` in your fetch
|
|
819
|
+
helper. With a client that takes a `DocumentNode`, pass
|
|
820
|
+
`addPreprIds(document, schema)` instead. Keep this on the server: the
|
|
821
|
+
GraphQL URL carries your access token.
|
|
822
|
+
|
|
823
|
+
#### Option 3: by hand
|
|
824
|
+
|
|
825
|
+
Select `_id` (and `__typename`) on every model and component level, and on
|
|
826
|
+
every entry type in dynamic content fields (`Text`, `Quote`, …). `Assets`
|
|
827
|
+
entries have no `_id` of their own: select `items { _id }` instead.
|
|
828
|
+
|
|
829
|
+
The rest is about rendering:
|
|
733
830
|
|
|
734
|
-
- Select `_id` (and ideally `__typename`) on every model and component level
|
|
735
|
-
that should live-update. Items without `_id` stay static.
|
|
736
|
-
- In dynamic content fields, select `_id` on every entry type (`Text`,
|
|
737
|
-
`Quote`, …). `Assets` entries have no `_id` of their own: select
|
|
738
|
-
`items { _id }` instead.
|
|
739
831
|
- A `Text` entry's content updates whether the query selects `body`,
|
|
740
832
|
`html` or `text` (the latter with tags stripped).
|
|
741
|
-
-
|
|
742
|
-
`
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
on the
|
|
833
|
+
- **Dynamic content entries are found automatically.** In preview,
|
|
834
|
+
`normalizeQuery`, `normalizedQuery` and `useNormalizedQuery` add each
|
|
835
|
+
entry's `_id` (and, for `Text` entries, the field path) to the hidden edit
|
|
836
|
+
link of every text inside it. Click-to-edit and the editor's "jump to
|
|
837
|
+
field" then find the entry by id, also after an editor reorders entries.
|
|
838
|
+
Render a `Text` entry's HTML straight into one wrapper element, so its
|
|
839
|
+
edit link lands on that wrapper and the whole entry is outlined.
|
|
840
|
+
- Prepr images are click-to-edit inside the editor without any markup: the
|
|
841
|
+
toolkit matches image URLs (also through image optimizers like
|
|
842
|
+
`/_next/image`) to the open item's assets.
|
|
843
|
+
- **Set the attributes yourself only where that does not reach:**
|
|
844
|
+
- Data you fetch in the browser without those helpers: run it through
|
|
845
|
+
`annotateEditLinks(result, query)` from `@preprio/toolkit`, or mark the
|
|
846
|
+
element as below.
|
|
847
|
+
- An entry with no text and no Prepr image (an embed, a video URL): wrap
|
|
848
|
+
it in an element with `data-prepr-edit-target="<field>.<index>"` and
|
|
849
|
+
`data-prepr-entry-id="<entry _id>"` (for `Assets`, the first asset's
|
|
850
|
+
`_id`). Attributes you set always win over the automatic ones.
|
|
851
|
+
- Other values without a hidden edit link (video and other URLs): make
|
|
852
|
+
them click-to-edit with
|
|
853
|
+
`previewAttrs({ id, field }, { enabled: preview })` on the element that
|
|
854
|
+
renders them.
|
|
748
855
|
- Where the page renders a linked item whose content comes from items nested
|
|
749
856
|
inside it (for example a personalization or a shared section referenced
|
|
750
857
|
from a stack), wrap it in an element with
|
|
@@ -1105,7 +1212,7 @@ createPreprPreview({ props, options: { locale: 'nl' } });
|
|
|
1105
1212
|
|
|
1106
1213
|
- **Check `enabled`**: every live preview entry point needs your preview flag, and the middleware must run with `preview: true` for that page.
|
|
1107
1214
|
- **Check the preview URL in Prepr**: it must point at a deployment that uses the GraphQL Preview token. A production build with preview off shows saved content only.
|
|
1108
|
-
- **Check `_id`**: an item or component whose query does not select `_id` stays static.
|
|
1215
|
+
- **Check `_id`**: an item or component whose query does not select `_id` stays static. Add the `preprIds` Codegen transform, or see [Preparing your queries](#preparing-your-queries).
|
|
1109
1216
|
- **Check the frame**: a `Refused to frame` error in the browser console means your `Content-Security-Policy` `frame-ancestors` does not list your Prepr environment.
|
|
1110
1217
|
- **Next.js only shows changes on save**: `normalizeQuery` with `PreprRefresh` needs Next.js 16 or later.
|
|
1111
1218
|
|
package/dist/astro/index.cjs
CHANGED
|
@@ -31,7 +31,7 @@ __export(astro_exports, {
|
|
|
31
31
|
module.exports = __toCommonJS(astro_exports);
|
|
32
32
|
|
|
33
33
|
// src/version.ts
|
|
34
|
-
var VERSION = "0.5.0-rc.
|
|
34
|
+
var VERSION = "0.5.0-rc.7";
|
|
35
35
|
|
|
36
36
|
// src/core/features.ts
|
|
37
37
|
function isEnabled(config) {
|
|
@@ -430,14 +430,14 @@ function indexSnapshot(data) {
|
|
|
430
430
|
const index = /* @__PURE__ */ new Map();
|
|
431
431
|
const visited = /* @__PURE__ */ new WeakSet();
|
|
432
432
|
const path = [];
|
|
433
|
-
function
|
|
433
|
+
function walk2(node) {
|
|
434
434
|
if (node === null || typeof node !== "object") return;
|
|
435
435
|
if (visited.has(node)) return;
|
|
436
436
|
visited.add(node);
|
|
437
437
|
if (Array.isArray(node)) {
|
|
438
438
|
for (let i = 0; i < node.length; i++) {
|
|
439
439
|
path.push(i);
|
|
440
|
-
|
|
440
|
+
walk2(node[i]);
|
|
441
441
|
path.pop();
|
|
442
442
|
}
|
|
443
443
|
return;
|
|
@@ -455,11 +455,11 @@ function indexSnapshot(data) {
|
|
|
455
455
|
for (const key in record) {
|
|
456
456
|
if (!Object.prototype.hasOwnProperty.call(record, key)) continue;
|
|
457
457
|
path.push(key);
|
|
458
|
-
|
|
458
|
+
walk2(record[key]);
|
|
459
459
|
path.pop();
|
|
460
460
|
}
|
|
461
461
|
}
|
|
462
|
-
|
|
462
|
+
walk2(data);
|
|
463
463
|
return index;
|
|
464
464
|
}
|
|
465
465
|
|
|
@@ -916,12 +916,12 @@ function resolveFields(selections, typename, doc, options) {
|
|
|
916
916
|
}
|
|
917
917
|
};
|
|
918
918
|
const applies = (on, list) => conditionMatches(on, typename) || typename !== void 0 && typeConditions(list, doc).includes(typename);
|
|
919
|
-
const
|
|
919
|
+
const walk2 = (list, matched) => {
|
|
920
920
|
for (const s of list) {
|
|
921
921
|
if (s.kind === "field") {
|
|
922
922
|
add(matched ? exact : loose, s);
|
|
923
923
|
} else if (s.kind === "inline") {
|
|
924
|
-
|
|
924
|
+
walk2(s.selections, matched && applies(s.on, s.selections));
|
|
925
925
|
} else {
|
|
926
926
|
const fragment = doc.fragments.get(s.name);
|
|
927
927
|
if (!fragment) continue;
|
|
@@ -929,11 +929,11 @@ function resolveFields(selections, typename, doc, options) {
|
|
|
929
929
|
if (walkedMatched.has(fragment)) continue;
|
|
930
930
|
if (!asMatched && walkedLoose.has(fragment)) continue;
|
|
931
931
|
(asMatched ? walkedMatched : walkedLoose).add(fragment);
|
|
932
|
-
|
|
932
|
+
walk2(fragment.selections, asMatched);
|
|
933
933
|
}
|
|
934
934
|
}
|
|
935
935
|
};
|
|
936
|
-
|
|
936
|
+
walk2(selections, true);
|
|
937
937
|
if (!options.strict) {
|
|
938
938
|
for (const [key, field] of loose) {
|
|
939
939
|
if (!exact.has(key)) exact.set(key, field);
|
|
@@ -950,21 +950,21 @@ function typeConditions(selections, doc) {
|
|
|
950
950
|
if (hit) return hit;
|
|
951
951
|
const names = /* @__PURE__ */ new Set();
|
|
952
952
|
const seen = /* @__PURE__ */ new Set();
|
|
953
|
-
const
|
|
953
|
+
const walk2 = (list) => {
|
|
954
954
|
for (const s of list) {
|
|
955
955
|
if (s.kind === "inline") {
|
|
956
956
|
if (s.on) names.add(s.on);
|
|
957
|
-
|
|
957
|
+
walk2(s.selections);
|
|
958
958
|
} else if (s.kind === "spread") {
|
|
959
959
|
const fragment = doc.fragments.get(s.name);
|
|
960
960
|
if (!fragment || seen.has(fragment)) continue;
|
|
961
961
|
seen.add(fragment);
|
|
962
962
|
names.add(fragment.on);
|
|
963
|
-
|
|
963
|
+
walk2(fragment.selections);
|
|
964
964
|
}
|
|
965
965
|
}
|
|
966
966
|
};
|
|
967
|
-
|
|
967
|
+
walk2(selections);
|
|
968
968
|
const conditions = [...names];
|
|
969
969
|
conditionsCache.set(selections, conditions);
|
|
970
970
|
return conditions;
|
|
@@ -1259,7 +1259,7 @@ function resolveTargets(root, doc, ownerPath, segments) {
|
|
|
1259
1259
|
let current = root;
|
|
1260
1260
|
const out = [];
|
|
1261
1261
|
const plain = /* @__PURE__ */ new Set();
|
|
1262
|
-
const
|
|
1262
|
+
const walk2 = (node, sel, i, path) => {
|
|
1263
1263
|
if (i === segments.length) {
|
|
1264
1264
|
out.push(path);
|
|
1265
1265
|
return;
|
|
@@ -1269,10 +1269,10 @@ function resolveTargets(root, doc, ownerPath, segments) {
|
|
|
1269
1269
|
if (INDEX_SEGMENT.test(segment)) {
|
|
1270
1270
|
const position = Number(segment);
|
|
1271
1271
|
if (position < node.length) {
|
|
1272
|
-
|
|
1272
|
+
walk2(node[position], sel, i + 1, [...path, position]);
|
|
1273
1273
|
}
|
|
1274
1274
|
} else if (segment === "items") {
|
|
1275
|
-
|
|
1275
|
+
walk2(node, sel, i + 1, path);
|
|
1276
1276
|
}
|
|
1277
1277
|
return;
|
|
1278
1278
|
}
|
|
@@ -1305,11 +1305,11 @@ function resolveTargets(root, doc, ownerPath, segments) {
|
|
|
1305
1305
|
current = writeAtPath(current, fieldPath, value);
|
|
1306
1306
|
}
|
|
1307
1307
|
}
|
|
1308
|
-
|
|
1308
|
+
walk2(value, field.selections, i + 1, fieldPath);
|
|
1309
1309
|
}
|
|
1310
|
-
if (!matched && segment === "items")
|
|
1310
|
+
if (!matched && segment === "items") walk2(node, sel, i + 1, path);
|
|
1311
1311
|
};
|
|
1312
|
-
|
|
1312
|
+
walk2(owner, selections, 0, ownerPath);
|
|
1313
1313
|
return { root: current, targets: out, plain };
|
|
1314
1314
|
}
|
|
1315
1315
|
var NON_ALPHANUMERIC = /[^a-z0-9]/gi;
|
|
@@ -1432,11 +1432,11 @@ function listKeysIn(result) {
|
|
|
1432
1432
|
if (!byType) {
|
|
1433
1433
|
byType = /* @__PURE__ */ new Map();
|
|
1434
1434
|
const seen = /* @__PURE__ */ new WeakSet();
|
|
1435
|
-
const
|
|
1435
|
+
const walk2 = (node) => {
|
|
1436
1436
|
if (node === null || typeof node !== "object" || seen.has(node)) return;
|
|
1437
1437
|
seen.add(node);
|
|
1438
1438
|
if (Array.isArray(node)) {
|
|
1439
|
-
node.forEach(
|
|
1439
|
+
node.forEach(walk2);
|
|
1440
1440
|
return;
|
|
1441
1441
|
}
|
|
1442
1442
|
const record = node;
|
|
@@ -1447,10 +1447,10 @@ function listKeysIn(result) {
|
|
|
1447
1447
|
if (!keys) byType.set(type, keys = /* @__PURE__ */ new Set());
|
|
1448
1448
|
keys.add(key);
|
|
1449
1449
|
}
|
|
1450
|
-
|
|
1450
|
+
walk2(value);
|
|
1451
1451
|
}
|
|
1452
1452
|
};
|
|
1453
|
-
|
|
1453
|
+
walk2(result);
|
|
1454
1454
|
listKeysCache.set(result, byType);
|
|
1455
1455
|
}
|
|
1456
1456
|
return byType.get(typename) ?? NO_KEYS;
|
|
@@ -1492,7 +1492,7 @@ function findEditLink(root) {
|
|
|
1492
1492
|
if (linkCache.has(root)) return linkCache.get(root) ?? null;
|
|
1493
1493
|
let found = null;
|
|
1494
1494
|
const seen = /* @__PURE__ */ new WeakSet();
|
|
1495
|
-
const
|
|
1495
|
+
const walk2 = (node) => {
|
|
1496
1496
|
if (found) return;
|
|
1497
1497
|
if (typeof node === "string") {
|
|
1498
1498
|
if (!(0, import_stega2.vercelStegaSplit)(node).encoded) return;
|
|
@@ -1507,9 +1507,9 @@ function findEditLink(root) {
|
|
|
1507
1507
|
}
|
|
1508
1508
|
if (node === null || typeof node !== "object" || seen.has(node)) return;
|
|
1509
1509
|
seen.add(node);
|
|
1510
|
-
for (const value of Object.values(node))
|
|
1510
|
+
for (const value of Object.values(node)) walk2(value);
|
|
1511
1511
|
};
|
|
1512
|
-
|
|
1512
|
+
walk2(root);
|
|
1513
1513
|
linkCache.set(root, found);
|
|
1514
1514
|
return found;
|
|
1515
1515
|
}
|
|
@@ -1535,6 +1535,101 @@ async function readLiveLogFromRequest(request) {
|
|
|
1535
1535
|
}
|
|
1536
1536
|
}
|
|
1537
1537
|
|
|
1538
|
+
// src/core/live-preview/edit-links.ts
|
|
1539
|
+
var import_stega3 = require("@vercel/stega");
|
|
1540
|
+
var ENTRY_PARAM = "entry";
|
|
1541
|
+
var TOP_LEVEL_ENTRY = /^[A-Za-z0-9_]+\.\d+$/;
|
|
1542
|
+
function entryIdOf(element) {
|
|
1543
|
+
if (!isRecord(element)) return void 0;
|
|
1544
|
+
if (typeof element._id === "string") return element._id;
|
|
1545
|
+
if (element.__typename !== "Assets" || !Array.isArray(element.items)) {
|
|
1546
|
+
return void 0;
|
|
1547
|
+
}
|
|
1548
|
+
const first = element.items[0];
|
|
1549
|
+
return isRecord(first) && typeof first._id === "string" ? first._id : void 0;
|
|
1550
|
+
}
|
|
1551
|
+
function isRecord(value) {
|
|
1552
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1553
|
+
}
|
|
1554
|
+
function annotate(value, ctx) {
|
|
1555
|
+
if (!ctx.entryId) return value;
|
|
1556
|
+
const { cleaned, encoded } = (0, import_stega3.vercelStegaSplit)(value);
|
|
1557
|
+
if (!encoded) return value;
|
|
1558
|
+
let payload;
|
|
1559
|
+
try {
|
|
1560
|
+
payload = (0, import_stega3.vercelStegaDecode)(encoded);
|
|
1561
|
+
} catch {
|
|
1562
|
+
return value;
|
|
1563
|
+
}
|
|
1564
|
+
if (typeof payload?.href !== "string") return value;
|
|
1565
|
+
let url;
|
|
1566
|
+
try {
|
|
1567
|
+
url = new URL(payload.href);
|
|
1568
|
+
} catch {
|
|
1569
|
+
return value;
|
|
1570
|
+
}
|
|
1571
|
+
if (url.searchParams.has(ENTRY_PARAM)) return value;
|
|
1572
|
+
url.searchParams.set(ENTRY_PARAM, ctx.entryId);
|
|
1573
|
+
const itemId = decodeURIComponent(url.pathname.split("/").pop() ?? "");
|
|
1574
|
+
if (!url.searchParams.has("field") && ctx.entryField && TOP_LEVEL_ENTRY.test(ctx.entryField) && itemId === ctx.entryOwnerId) {
|
|
1575
|
+
url.searchParams.set("field", ctx.entryField);
|
|
1576
|
+
}
|
|
1577
|
+
return (0, import_stega3.vercelStegaCombine)(
|
|
1578
|
+
cleaned,
|
|
1579
|
+
{ ...payload, href: url.toString() },
|
|
1580
|
+
false
|
|
1581
|
+
);
|
|
1582
|
+
}
|
|
1583
|
+
function walk(value, selections, ctx) {
|
|
1584
|
+
if (typeof value === "string") return annotate(value, ctx);
|
|
1585
|
+
if (Array.isArray(value)) {
|
|
1586
|
+
let changed2 = false;
|
|
1587
|
+
const next2 = value.map((element, index) => {
|
|
1588
|
+
const path = [...ctx.path, String(index)];
|
|
1589
|
+
const entryId = ctx.inAssets ? void 0 : entryIdOf(element);
|
|
1590
|
+
const entry = entryId ? {
|
|
1591
|
+
entryId,
|
|
1592
|
+
entryField: ctx.ownerId ? path.join(".") : void 0,
|
|
1593
|
+
entryOwnerId: ctx.ownerId,
|
|
1594
|
+
inAssets: isRecord(element) && element._id === void 0
|
|
1595
|
+
} : {};
|
|
1596
|
+
const out = walk(element, selections, { ...ctx, ...entry, path });
|
|
1597
|
+
if (out !== element) changed2 = true;
|
|
1598
|
+
return out;
|
|
1599
|
+
});
|
|
1600
|
+
return changed2 ? next2 : value;
|
|
1601
|
+
}
|
|
1602
|
+
if (!isRecord(value)) return value;
|
|
1603
|
+
const id = typeof value._id === "string" ? value._id : void 0;
|
|
1604
|
+
const typename = typeof value.__typename === "string" ? value.__typename : void 0;
|
|
1605
|
+
const fields = ctx.doc && selections ? collectFields(selections, typename, ctx.doc) : null;
|
|
1606
|
+
let changed = false;
|
|
1607
|
+
const next = {};
|
|
1608
|
+
for (const [key, child] of Object.entries(value)) {
|
|
1609
|
+
const field = fields?.get(key);
|
|
1610
|
+
const name = field?.name ?? key;
|
|
1611
|
+
const out = walk(child, field?.selections ?? null, {
|
|
1612
|
+
...ctx,
|
|
1613
|
+
ownerId: id ?? ctx.ownerId,
|
|
1614
|
+
path: id ? [name] : [...ctx.path, name]
|
|
1615
|
+
});
|
|
1616
|
+
if (out !== child) changed = true;
|
|
1617
|
+
next[key] = out;
|
|
1618
|
+
}
|
|
1619
|
+
return changed ? next : value;
|
|
1620
|
+
}
|
|
1621
|
+
function annotateEditLinks(result, query, operationName) {
|
|
1622
|
+
let doc = null;
|
|
1623
|
+
if (query !== void 0) {
|
|
1624
|
+
try {
|
|
1625
|
+
doc = readQueryDocument(query, operationName);
|
|
1626
|
+
} catch {
|
|
1627
|
+
doc = null;
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
return walk(result, doc?.operation ?? null, { doc, path: [] });
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1538
1633
|
// src/astro/index.ts
|
|
1539
1634
|
var LIVE_LOG = /* @__PURE__ */ Symbol.for("@preprio/toolkit/astro-live-log");
|
|
1540
1635
|
async function onPreprRequest(context, next, options) {
|
|
@@ -1575,10 +1670,11 @@ async function getToolbarProps(headers, token, features) {
|
|
|
1575
1670
|
return getToolbarPropsFromHeaders(headers, token, features);
|
|
1576
1671
|
}
|
|
1577
1672
|
function normalizeQuery2(query, result, options = {}) {
|
|
1578
|
-
if (!options.enabled
|
|
1579
|
-
const
|
|
1580
|
-
|
|
1581
|
-
|
|
1673
|
+
if (!options.enabled) return result;
|
|
1674
|
+
const annotated = annotateEditLinks(result, query, options.operationName);
|
|
1675
|
+
const log = options.locals?.[LIVE_LOG];
|
|
1676
|
+
if (!log?.length) return annotated;
|
|
1677
|
+
return normalizeQuery(query, annotated, {
|
|
1582
1678
|
log,
|
|
1583
1679
|
operationName: options.operationName
|
|
1584
1680
|
});
|