@nitrogenbuilder/connector-payload 0.1.13 → 0.1.15

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.
@@ -7,6 +7,7 @@ export const NitrogenEditButton = ({ collection }) => {
7
7
  const [hasDevelopmentUrl, setHasDevelopmentUrl] = useState(false);
8
8
  const [slug, setSlug] = useState(undefined);
9
9
  const [frontendUrl, setFrontendUrl] = useState(undefined);
10
+ const [instanceUrl, setInstanceUrl] = useState(undefined);
10
11
  useEffect(() => {
11
12
  const segments = window.location.pathname.split("/").filter(Boolean);
12
13
  const docId = segments.length >= 4 ? segments[3] : undefined;
@@ -34,6 +35,9 @@ export const NitrogenEditButton = ({ collection }) => {
34
35
  if (data.developmentUrl) {
35
36
  setHasDevelopmentUrl(true);
36
37
  }
38
+ if (data.instanceUrl) {
39
+ setInstanceUrl(data.instanceUrl.replace(/\/$/, ""));
40
+ }
37
41
  const url = data.frontendUrl;
38
42
  if (url) {
39
43
  setFrontendUrl(url.replace(/\/$/, ""));
@@ -46,7 +50,8 @@ export const NitrogenEditButton = ({ collection }) => {
46
50
  if (!id || !token)
47
51
  return null;
48
52
  const param = collection === "nitrogen-templates" ? "templateId" : "pageId";
49
- const baseHref = `/nitrogen-editor?token=${encodeURIComponent(token)}&collection=${encodeURIComponent(collection)}&${param}=${id}`;
53
+ const editorBase = instanceUrl ?? "";
54
+ const baseHref = `${editorBase}/nitrogen-editor?token=${encodeURIComponent(token)}&collection=${encodeURIComponent(collection)}&${param}=${id}`;
50
55
  const buttonStyle = {
51
56
  display: "inline-flex",
52
57
  alignItems: "center",
@@ -0,0 +1,2 @@
1
+ import type { Endpoint } from 'payload';
2
+ export declare const batchEndpoints: Endpoint[];
@@ -0,0 +1,76 @@
1
+ import { resolveCollection } from '../collection-registry';
2
+ import { getNitrogenSettings, buildDynamicData, buildPageResponse } from './helpers';
3
+ export const batchEndpoints = [
4
+ // POST /api/nitrogen/v1/batch-data — Batch fetch multiple collections
5
+ {
6
+ path: '/nitrogen/v1/batch-data',
7
+ method: 'post',
8
+ handler: async (req) => {
9
+ const { payload } = req;
10
+ const body = await req.json?.();
11
+ const requests = body?.requests || [];
12
+ if (!requests.length) {
13
+ return Response.json({});
14
+ }
15
+ const settings = await getNitrogenSettings(payload);
16
+ const results = {};
17
+ await Promise.all(requests.map(async (batchReq) => {
18
+ const { key, endpoint, params = {} } = batchReq;
19
+ try {
20
+ const collectionSlug = resolveCollection(endpoint);
21
+ const limit = params.posts_per_page ?? 10;
22
+ const page = params.paged ?? 1;
23
+ const statusParam = params.post_status ?? 'publish';
24
+ const statuses = statusParam.split(',').map((s) => s.trim());
25
+ const orderby = params.orderby || 'createdAt';
26
+ const sort = params.order === 'asc' ? orderby : `-${orderby}`;
27
+ const where = {};
28
+ if (!statuses.includes('any')) {
29
+ where.status = { in: statuses };
30
+ }
31
+ const result = await payload.find({
32
+ collection: collectionSlug,
33
+ where,
34
+ page,
35
+ limit,
36
+ sort,
37
+ depth: params.embed ? 1 : 0,
38
+ });
39
+ let data;
40
+ if (params.embed) {
41
+ data = result.docs.map((doc) => {
42
+ const dynamicData = buildDynamicData(doc, settings);
43
+ return buildPageResponse(doc, settings, dynamicData);
44
+ });
45
+ }
46
+ else {
47
+ data = result.docs.map((doc) => {
48
+ const d = doc;
49
+ return {
50
+ id: d.id,
51
+ title: String(d.title || ''),
52
+ slug: String(d.slug || ''),
53
+ permalink: `${settings.frontendUrl || ''}/${String(d.slug || '')}`,
54
+ relative_permalink: `/${String(d.slug || '')}`,
55
+ };
56
+ });
57
+ }
58
+ results[key] = {
59
+ data,
60
+ total: result.totalDocs,
61
+ totalPages: result.totalPages,
62
+ };
63
+ }
64
+ catch (e) {
65
+ results[key] = {
66
+ data: [],
67
+ total: 0,
68
+ totalPages: 0,
69
+ error: e instanceof Error ? e.message : 'Unknown error',
70
+ };
71
+ }
72
+ }));
73
+ return Response.json(results);
74
+ },
75
+ },
76
+ ];
@@ -1,4 +1,4 @@
1
- import { getNitrogenSettings, buildDynamicData, buildPageResponse, buildListItemResponse, requireAuth, } from './helpers';
1
+ import { getNitrogenSettings, buildDynamicData, buildPageResponse, buildListItemResponse, getTemplateForType, requireAuth, } from './helpers';
2
2
  /**
3
3
  * Creates a complete set of CRUD endpoints for a given Payload collection.
4
4
  *
@@ -67,14 +67,20 @@ export function createCollectionEndpoints(collectionSlug, endpointPrefix) {
67
67
  const { payload, routeParams } = req;
68
68
  const id = routeParams?.id;
69
69
  try {
70
- const doc = (await payload.findByID({
71
- collection,
72
- id,
73
- depth: 1,
74
- }));
75
- const settings = await getNitrogenSettings(payload);
70
+ const [doc, settings, headerTemplate, footerTemplate, pageTemplate] = await Promise.all([
71
+ payload.findByID({ collection, id, depth: 1 }),
72
+ getNitrogenSettings(payload),
73
+ getTemplateForType(payload, 'header'),
74
+ getTemplateForType(payload, 'footer'),
75
+ getTemplateForType(payload, collectionSlug),
76
+ ]);
76
77
  const dynamicData = buildDynamicData(doc, settings);
77
- return Response.json(buildPageResponse(doc, settings, dynamicData));
78
+ return Response.json({
79
+ ...buildPageResponse(doc, settings, dynamicData),
80
+ template: pageTemplate,
81
+ headerTemplate,
82
+ footerTemplate,
83
+ });
78
84
  }
79
85
  catch {
80
86
  return Response.json({ error: 'Not found' }, { status: 404 });
@@ -127,19 +133,24 @@ export function createCollectionEndpoints(collectionSlug, endpointPrefix) {
127
133
  if (!slug) {
128
134
  return Response.json({ error: 'Slug is required' }, { status: 400 });
129
135
  }
130
- const result = await payload.find({
131
- collection,
132
- where: { slug: { equals: slug } },
133
- limit: 1,
134
- depth: 1,
135
- });
136
+ const [result, settings, headerTemplate, footerTemplate, pageTemplate] = await Promise.all([
137
+ payload.find({ collection, where: { slug: { equals: slug } }, limit: 1, depth: 1 }),
138
+ getNitrogenSettings(payload),
139
+ getTemplateForType(payload, 'header'),
140
+ getTemplateForType(payload, 'footer'),
141
+ getTemplateForType(payload, collectionSlug),
142
+ ]);
136
143
  if (!result.docs.length) {
137
144
  return Response.json({ error: 'Not found' }, { status: 404 });
138
145
  }
139
146
  const doc = result.docs[0];
140
- const settings = await getNitrogenSettings(payload);
141
147
  const dynamicData = buildDynamicData(doc, settings);
142
- return Response.json(buildPageResponse(doc, settings, dynamicData));
148
+ return Response.json({
149
+ ...buildPageResponse(doc, settings, dynamicData),
150
+ template: pageTemplate,
151
+ headerTemplate,
152
+ footerTemplate,
153
+ });
143
154
  },
144
155
  },
145
156
  // GET /api/nitrogen/v1/{prefix}/slug/:slug — Get item by slug
@@ -149,19 +160,24 @@ export function createCollectionEndpoints(collectionSlug, endpointPrefix) {
149
160
  handler: async (req) => {
150
161
  const { payload, routeParams } = req;
151
162
  const slug = routeParams?.slug;
152
- const result = await payload.find({
153
- collection,
154
- where: { slug: { equals: slug } },
155
- limit: 1,
156
- depth: 1,
157
- });
163
+ const [result, settings, headerTemplate, footerTemplate, pageTemplate] = await Promise.all([
164
+ payload.find({ collection, where: { slug: { equals: slug } }, limit: 1, depth: 1 }),
165
+ getNitrogenSettings(payload),
166
+ getTemplateForType(payload, 'header'),
167
+ getTemplateForType(payload, 'footer'),
168
+ getTemplateForType(payload, collectionSlug),
169
+ ]);
158
170
  if (!result.docs.length) {
159
171
  return Response.json({ error: 'Not found' }, { status: 404 });
160
172
  }
161
173
  const doc = result.docs[0];
162
- const settings = await getNitrogenSettings(payload);
163
174
  const dynamicData = buildDynamicData(doc, settings);
164
- return Response.json(buildPageResponse(doc, settings, dynamicData));
175
+ return Response.json({
176
+ ...buildPageResponse(doc, settings, dynamicData),
177
+ template: pageTemplate,
178
+ headerTemplate,
179
+ footerTemplate,
180
+ });
165
181
  },
166
182
  },
167
183
  ];
@@ -1,5 +1,9 @@
1
1
  import type { Payload, PayloadRequest } from 'payload';
2
2
  import type { JsonObject, NitrogenPageDoc, NitrogenTemplateDoc, NitrogenSettingsGlobal, MediaDoc } from '../types';
3
+ export interface TemplateRef {
4
+ ID: string | number;
5
+ content: string;
6
+ }
3
7
  export declare function formatDate(date: string | Date): string;
4
8
  export declare function buildDynamicData(doc: NitrogenPageDoc | NitrogenTemplateDoc, globalSettings?: NitrogenSettingsGlobal): JsonObject;
5
9
  export declare function buildPageResponse(doc: NitrogenPageDoc | NitrogenTemplateDoc, settings: NitrogenSettingsGlobal, dynamicData: JsonObject): {
@@ -67,4 +71,9 @@ export declare function buildMediaItemResponse(doc: MediaDoc): {
67
71
  size: number;
68
72
  };
69
73
  export declare function getNitrogenSettings(payload: Payload): Promise<NitrogenSettingsGlobal>;
74
+ /**
75
+ * Looks up a nitrogen-template by its associatedCollection type (e.g. 'header', 'footer', or a collection slug).
76
+ * Returns a { ID, content } ref or null if no template exists for that type.
77
+ */
78
+ export declare function getTemplateForType(payload: Payload, type: string): Promise<TemplateRef | null>;
70
79
  export declare function requireAuth(req: PayloadRequest): Response | null;
@@ -104,6 +104,30 @@ export function buildMediaItemResponse(doc) {
104
104
  export async function getNitrogenSettings(payload) {
105
105
  return payload.findGlobal({ slug: 'nitrogen-settings' });
106
106
  }
107
+ /**
108
+ * Looks up a nitrogen-template by its associatedCollection type (e.g. 'header', 'footer', or a collection slug).
109
+ * Returns a { ID, content } ref or null if no template exists for that type.
110
+ */
111
+ export async function getTemplateForType(payload, type) {
112
+ try {
113
+ const result = await payload.find({
114
+ collection: 'nitrogen-templates',
115
+ where: { associatedCollection: { equals: type } },
116
+ limit: 1,
117
+ depth: 0,
118
+ });
119
+ const doc = result.docs[0];
120
+ if (!doc)
121
+ return null;
122
+ return {
123
+ ID: doc.id,
124
+ content: doc.nitrogenData ? JSON.stringify(doc.nitrogenData) : '[]',
125
+ };
126
+ }
127
+ catch {
128
+ return null;
129
+ }
130
+ }
107
131
  export function requireAuth(req) {
108
132
  if (!req.user) {
109
133
  return Response.json({ error: 'Unauthorized' }, { status: 401 });
package/dist/index.js CHANGED
@@ -6,6 +6,7 @@ import { nitrogenSettingsEndpoints } from "./endpoints/nitrogen-settings";
6
6
  import { allEndpoints } from "./endpoints/all";
7
7
  import { menuEndpoints } from "./endpoints/menu";
8
8
  import { createCollectionEndpoints } from "./endpoints/collection-endpoints";
9
+ import { batchEndpoints } from "./endpoints/batch";
9
10
  import { registerCollection } from "./collection-registry";
10
11
  /** Fields required by Nitrogen that will be injected into collections if missing */
11
12
  const nitrogenRequiredFields = [
@@ -47,6 +48,7 @@ export const nitrogenConnectorPlugin = (options = {}) => (incomingConfig) => {
47
48
  ...nitrogenSettingsEndpoints,
48
49
  ...allEndpoints,
49
50
  ...menuEndpoints,
51
+ ...batchEndpoints,
50
52
  ];
51
53
  // Register templates collection
52
54
  registerCollection("nitrogen-templates", "nitrogen-templates");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitrogenbuilder/connector-payload",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
4
4
  "description": "Nitrogen page builder connector plugin for Payload CMS 3.x",
5
5
  "author": "Leonardo Dentzien <leo@torchmedia.ca>",
6
6
  "type": "module",
@@ -1,2 +0,0 @@
1
- import type { Endpoint } from 'payload';
2
- export declare const collectionPickerEndpoints: Endpoint[];
@@ -1,52 +0,0 @@
1
- export const collectionPickerEndpoints = [
2
- // GET /api/nitrogen/v1/collection/:slug
3
- // Generic read-only endpoint for fetching any collection's documents for use
4
- // in the nitrogen editor's CtrlRelationship control.
5
- {
6
- path: '/nitrogen/v1/collection/:slug',
7
- method: 'get',
8
- handler: async (req) => {
9
- const { payload, routeParams } = req;
10
- const collectionSlug = routeParams?.slug;
11
- if (!collectionSlug) {
12
- return Response.json({ error: 'Collection slug is required' }, { status: 400 });
13
- }
14
- const url = new URL(req.url || '', 'http://localhost');
15
- const search = url.searchParams.get('search') || '';
16
- const page = parseInt(url.searchParams.get('page') || '1', 10);
17
- const limit = parseInt(url.searchParams.get('limit') || '50', 10);
18
- const depth = parseInt(url.searchParams.get('depth') || '1', 10);
19
- try {
20
- const where = {};
21
- if (search) {
22
- where['or'] = [
23
- { title: { contains: search } },
24
- { name: { contains: search } },
25
- ];
26
- }
27
- const result = await payload.find({
28
- collection: collectionSlug,
29
- where,
30
- page,
31
- limit,
32
- depth,
33
- overrideAccess: false,
34
- req,
35
- });
36
- return Response.json({
37
- docs: result.docs,
38
- totalDocs: result.totalDocs,
39
- totalPages: result.totalPages,
40
- page: result.page,
41
- });
42
- }
43
- catch (err) {
44
- const message = err instanceof Error ? err.message : String(err);
45
- if (message.includes('not found') || message.includes('Unknown collection')) {
46
- return Response.json({ error: `Collection "${collectionSlug}" not found` }, { status: 404 });
47
- }
48
- return Response.json({ error: message }, { status: 500 });
49
- }
50
- },
51
- },
52
- ];