@nitrogenbuilder/connector-payload 0.1.27 → 0.1.29

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.
@@ -3,5 +3,8 @@
3
3
  *
4
4
  * The plugin registers collections at init time via `registerCollection()`.
5
5
  */
6
- export declare function registerCollection(alias: string, collectionSlug: string): void;
6
+ export declare function registerCollection(alias: string, collectionSlug: string, routePattern?: string): void;
7
7
  export declare function resolveCollection(postType: string): string;
8
+ export declare function resolveCollectionRoutePattern(postType: string): string | undefined;
9
+ export declare function buildRelativePermalink(postType: string, slug: string): string;
10
+ export declare function buildPermalink(postType: string, slug: string, frontendUrl?: string): string;
@@ -4,9 +4,40 @@
4
4
  * The plugin registers collections at init time via `registerCollection()`.
5
5
  */
6
6
  const registeredCollections = new Map();
7
- export function registerCollection(alias, collectionSlug) {
8
- registeredCollections.set(alias, collectionSlug);
7
+ function normalizeRoutePattern(routePattern) {
8
+ if (!routePattern)
9
+ return undefined;
10
+ return routePattern.startsWith('/') ? routePattern : `/${routePattern}`;
11
+ }
12
+ function buildRelativePath(routePattern, slug) {
13
+ if (routePattern) {
14
+ if (routePattern.includes('[...slug]')) {
15
+ return routePattern.replace('[...slug]', slug);
16
+ }
17
+ if (routePattern.includes('[slug]')) {
18
+ return routePattern.replace('[slug]', slug);
19
+ }
20
+ return routePattern.endsWith('/') ? `${routePattern}${slug}` : routePattern;
21
+ }
22
+ return `/${slug}`;
23
+ }
24
+ export function registerCollection(alias, collectionSlug, routePattern) {
25
+ registeredCollections.set(alias, {
26
+ collectionSlug,
27
+ routePattern: normalizeRoutePattern(routePattern),
28
+ });
9
29
  }
10
30
  export function resolveCollection(postType) {
11
- return registeredCollections.get(postType) || postType;
31
+ return registeredCollections.get(postType)?.collectionSlug || postType;
32
+ }
33
+ export function resolveCollectionRoutePattern(postType) {
34
+ return registeredCollections.get(postType)?.routePattern;
35
+ }
36
+ export function buildRelativePermalink(postType, slug) {
37
+ return buildRelativePath(resolveCollectionRoutePattern(postType), slug);
38
+ }
39
+ export function buildPermalink(postType, slug, frontendUrl) {
40
+ const relativePermalink = buildRelativePermalink(postType, slug);
41
+ const baseUrl = (frontendUrl || '').replace(/\/$/, '');
42
+ return baseUrl ? `${baseUrl}${relativePermalink}` : relativePermalink;
12
43
  }
@@ -70,7 +70,11 @@ export const NitrogenEditButtonRuntime = ({ collection }) => {
70
70
  return null;
71
71
  const editorBase = instanceUrl ?? '';
72
72
  const isDevelopmentMode = isLocalAdminHost();
73
- const editHref = `${editorBase}/nitrogen-editor?token=${encodeURIComponent(token)}&collection=${encodeURIComponent(collection)}&pageId=${id}&authorId=${encodeURIComponent(authorId)}${isDevelopmentMode ? '&development=true' : ''}`;
73
+ const encodedAuthorId = encodeURIComponent(authorId);
74
+ const developmentFlags = isDevelopmentMode
75
+ ? '&development=true&editorDevelopment=true'
76
+ : '';
77
+ const editHref = `${editorBase}/nitrogen-editor?token=${encodeURIComponent(token)}&collection=${encodeURIComponent(collection)}&pageId=${id}&authorId=${encodedAuthorId}&author=${encodedAuthorId}${developmentFlags}`;
74
78
  const viewHref = collection === 'nitrogen-templates'
75
79
  ? `${frontendUrl}/nitrogen-templates/${slug}`
76
80
  : `${frontendUrl}/${slug}`;
@@ -3,6 +3,7 @@ interface NitrogenEditorSearchParams {
3
3
  pageId?: string;
4
4
  collection?: string;
5
5
  development?: string;
6
+ editorDevelopment?: string;
6
7
  }
7
8
  /**
8
9
  * Creates a standalone editor page that loads the Nitrogen builder.
@@ -73,8 +73,10 @@ export function createNitrogenEditorPage(config) {
73
73
  const settings = (await payload.findGlobal({
74
74
  slug: "nitrogen-settings",
75
75
  }));
76
- const isDevelopment = params.development === "true";
77
- const siteUrl = getSiteUrl(settings, isDevelopment);
76
+ // `development=true` selects the preview target (`developmentUrl`) without
77
+ // requiring the editor shell itself to come from a local Vite server.
78
+ const isPreviewDevelopment = params.development === "true";
79
+ const siteUrl = getSiteUrl(settings, isPreviewDevelopment);
78
80
  const nitrogenConfig = settings.nitrogenConfig || {};
79
81
  if (!params.pageId || !params.collection) {
80
82
  return redirect("/admin");
@@ -84,7 +86,7 @@ export function createNitrogenEditorPage(config) {
84
86
  licenseKey: settings.licenseKey || "",
85
87
  provider: {
86
88
  type: "payload",
87
- apiUrl: getNitrogenApiUrl(isDevelopment),
89
+ apiUrl: getNitrogenApiUrl(isPreviewDevelopment),
88
90
  collection: params.collection,
89
91
  },
90
92
  siteUrl: siteUrl || "",
@@ -93,7 +95,10 @@ export function createNitrogenEditorPage(config) {
93
95
  cssInjection: nitrogenConfig.cssInjection || "",
94
96
  };
95
97
  const editId = params.pageId;
96
- const isEditorDev = isDevelopment && !!settings.instanceUrl;
98
+ // Keep editor HMR opt-in separate from preview-target selection so a
99
+ // deployed Payload instance can still preview against a local frontend.
100
+ const isEditorDevelopment = params.editorDevelopment === "true";
101
+ const isEditorDev = isEditorDevelopment && !!settings.instanceUrl;
97
102
  const editorOrigin = settings.instanceUrl?.replace(/\/$/, "") || "";
98
103
  const editorAssetsBase = isEditorDev
99
104
  ? editorOrigin
@@ -103,8 +108,8 @@ export function createNitrogenEditorPage(config) {
103
108
  __html: [
104
109
  `window.nitrogenConfig = ${JSON.stringify(builderConfig)};`,
105
110
  `window.nitrogenEditId = "${editId}";`,
106
- // Inject authorId into the URL so the editor can read it from window.location.search
107
- `(function(){var u=new URL(window.location.href);if(!u.searchParams.has("authorId")){u.searchParams.set("authorId",${JSON.stringify(String(user.id))});window.history.replaceState(null,"",u.toString())}})();`,
111
+ // Keep both legacy and current author params present for editor builds.
112
+ `(function(){var u=new URL(window.location.href),a=${JSON.stringify(String(user.id))},changed=false;if(!u.searchParams.has("authorId")){u.searchParams.set("authorId",a);changed=true}if(!u.searchParams.has("author")){u.searchParams.set("author",a);changed=true}if(changed){window.history.replaceState(null,"",u.toString())}})();`,
108
113
  ].join(""),
109
114
  } }), _jsxs("div", { id: "root", children: [_jsx("div", { style: {
110
115
  position: "fixed",
@@ -1,5 +1,5 @@
1
1
  import { getNitrogenSettings } from './helpers';
2
- import { resolveCollection } from '../collection-registry';
2
+ import { buildPermalink, buildRelativePermalink, resolveCollection, } from '../collection-registry';
3
3
  export const allEndpoints = [
4
4
  // GET /api/nitrogen/v1/all — List all items with filtering
5
5
  {
@@ -34,8 +34,8 @@ export const allEndpoints = [
34
34
  id: doc.id,
35
35
  title: String(doc.title || ''),
36
36
  slug: String(doc.slug || ''),
37
- permalink: `${settings.frontendUrl || ''}/${String(doc.slug || '')}`,
38
- relative_permalink: `/${String(doc.slug || '')}`,
37
+ permalink: buildPermalink(postType, String(doc.slug || ''), settings.frontendUrl),
38
+ relative_permalink: buildRelativePermalink(postType, String(doc.slug || '')),
39
39
  type: collection,
40
40
  }));
41
41
  return Response.json(items);
@@ -1,4 +1,4 @@
1
- import { resolveCollection } from "../collection-registry";
1
+ import { buildPermalink, buildRelativePermalink, resolveCollection, } from "../collection-registry";
2
2
  import { getNitrogenSettings, buildDynamicData, buildPageResponse, } from "./helpers";
3
3
  function toArray(value) {
4
4
  if (Array.isArray(value)) {
@@ -189,18 +189,19 @@ export const batchEndpoints = [
189
189
  else if (params.embed) {
190
190
  data = result.docs.map((doc) => {
191
191
  const dynamicData = buildDynamicData(doc, settings);
192
- return buildPageResponse(doc, settings, dynamicData);
192
+ return buildPageResponse(doc, settings, dynamicData, collectionSlug);
193
193
  });
194
194
  }
195
195
  else {
196
196
  data = result.docs.map((doc) => {
197
197
  const d = doc;
198
+ const slug = String(d.slug || "");
198
199
  return {
199
200
  id: d.id,
200
201
  title: String(d.title || ""),
201
- slug: String(d.slug || ""),
202
- permalink: `${settings.frontendUrl || ""}/${String(d.slug || "")}`,
203
- relative_permalink: `/${String(d.slug || "")}`,
202
+ slug,
203
+ permalink: buildPermalink(endpoint, slug, settings.frontendUrl),
204
+ relative_permalink: buildRelativePermalink(endpoint, slug),
204
205
  };
205
206
  });
206
207
  }
@@ -28,7 +28,7 @@ export function createCollectionEndpoints(collectionSlug, endpointPrefix) {
28
28
  limit: 0,
29
29
  depth: 1,
30
30
  });
31
- const items = result.docs.map((doc) => buildListItemResponse(doc, settings));
31
+ const items = result.docs.map((doc) => buildListItemResponse(doc, settings, collectionSlug));
32
32
  return Response.json(items);
33
33
  },
34
34
  },
@@ -76,7 +76,7 @@ export function createCollectionEndpoints(collectionSlug, endpointPrefix) {
76
76
  ]);
77
77
  const dynamicData = buildDynamicData(doc, settings);
78
78
  return Response.json({
79
- ...buildPageResponse(doc, settings, dynamicData),
79
+ ...buildPageResponse(doc, settings, dynamicData, collectionSlug),
80
80
  template: pageTemplate,
81
81
  headerTemplate,
82
82
  footerTemplate,
@@ -146,7 +146,7 @@ export function createCollectionEndpoints(collectionSlug, endpointPrefix) {
146
146
  const doc = result.docs[0];
147
147
  const dynamicData = buildDynamicData(doc, settings);
148
148
  return Response.json({
149
- ...buildPageResponse(doc, settings, dynamicData),
149
+ ...buildPageResponse(doc, settings, dynamicData, collectionSlug),
150
150
  template: pageTemplate,
151
151
  headerTemplate,
152
152
  footerTemplate,
@@ -173,7 +173,7 @@ export function createCollectionEndpoints(collectionSlug, endpointPrefix) {
173
173
  const doc = result.docs[0];
174
174
  const dynamicData = buildDynamicData(doc, settings);
175
175
  return Response.json({
176
- ...buildPageResponse(doc, settings, dynamicData),
176
+ ...buildPageResponse(doc, settings, dynamicData, collectionSlug),
177
177
  template: pageTemplate,
178
178
  headerTemplate,
179
179
  footerTemplate,
@@ -6,7 +6,7 @@ export interface TemplateRef {
6
6
  }
7
7
  export declare function formatDate(date: string | Date): string;
8
8
  export declare function buildDynamicData(doc: NitrogenPageDoc | NitrogenTemplateDoc, globalSettings?: NitrogenSettingsGlobal): JsonObject;
9
- export declare function buildPageResponse(doc: NitrogenPageDoc | NitrogenTemplateDoc, settings: NitrogenSettingsGlobal, dynamicData: JsonObject): {
9
+ export declare function buildPageResponse(doc: NitrogenPageDoc | NitrogenTemplateDoc, settings: NitrogenSettingsGlobal, dynamicData: JsonObject, collectionSlug?: string): {
10
10
  id: string | number;
11
11
  token: string;
12
12
  title: string;
@@ -29,7 +29,7 @@ export declare function buildPageResponse(doc: NitrogenPageDoc | NitrogenTemplat
29
29
  wysiwygColors: {};
30
30
  };
31
31
  };
32
- export declare function buildListItemResponse(doc: NitrogenPageDoc | NitrogenTemplateDoc, settings: NitrogenSettingsGlobal): {
32
+ export declare function buildListItemResponse(doc: NitrogenPageDoc | NitrogenTemplateDoc, settings: NitrogenSettingsGlobal, collectionSlug?: string): {
33
33
  id: string | number;
34
34
  title: string;
35
35
  slug: string;
@@ -1,3 +1,4 @@
1
+ import { buildPermalink, buildRelativePermalink } from '../collection-registry';
1
2
  export function formatDate(date) {
2
3
  const d = new Date(date);
3
4
  const pad = (n) => String(n).padStart(2, '0');
@@ -49,17 +50,18 @@ export function buildDynamicData(doc, globalSettings) {
49
50
  }
50
51
  return dynamicData;
51
52
  }
52
- export function buildPageResponse(doc, settings, dynamicData) {
53
+ export function buildPageResponse(doc, settings, dynamicData, collectionSlug = 'pages') {
53
54
  const pageSettings = 'pageSettings' in doc ? doc.pageSettings : undefined;
54
55
  const templateSettings = 'templateSettings' in doc ? doc.templateSettings : undefined;
56
+ const slug = String(doc.slug || '');
55
57
  return {
56
58
  id: doc.id,
57
59
  token: settings.licenseKey || '',
58
60
  title: doc.title,
59
61
  author: doc.author && typeof doc.author === 'object' ? doc.author.id : (doc.author || ''),
60
- slug: doc.slug,
61
- permalink: `${settings.frontendUrl || ''}/${doc.slug}`,
62
- relative_permalink: `/${doc.slug}`,
62
+ slug,
63
+ permalink: buildPermalink(collectionSlug, slug, settings.frontendUrl),
64
+ relative_permalink: buildRelativePermalink(collectionSlug, slug),
63
65
  content: doc.nitrogenData ? JSON.stringify(doc.nitrogenData) : '[]',
64
66
  dynamic_data: dynamicData,
65
67
  settings: pageSettings || templateSettings || {
@@ -72,15 +74,16 @@ export function buildPageResponse(doc, settings, dynamicData) {
72
74
  },
73
75
  };
74
76
  }
75
- export function buildListItemResponse(doc, settings) {
77
+ export function buildListItemResponse(doc, settings, collectionSlug = 'pages') {
76
78
  const pageSettings = 'pageSettings' in doc ? doc.pageSettings : undefined;
77
79
  const templateSettings = 'templateSettings' in doc ? doc.templateSettings : undefined;
80
+ const slug = String(doc.slug || '');
78
81
  return {
79
82
  id: doc.id,
80
83
  title: doc.title,
81
- slug: doc.slug,
82
- permalink: `${settings.frontendUrl || ''}/${doc.slug}`,
83
- relative_permalink: `/${doc.slug}`,
84
+ slug,
85
+ permalink: buildPermalink(collectionSlug, slug, settings.frontendUrl),
86
+ relative_permalink: buildRelativePermalink(collectionSlug, slug),
84
87
  content: doc.nitrogenData ? JSON.stringify(doc.nitrogenData) : '[]',
85
88
  settings: pageSettings || templateSettings || {},
86
89
  nitrogen_settings: {
@@ -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
  export const templatesEndpoints = [
3
3
  // GET /api/nitrogen/v1/nitrogen-templates/slug/:slug — Get a single template by slug
4
4
  {
@@ -8,12 +8,16 @@ export const templatesEndpoints = [
8
8
  const { payload, routeParams } = req;
9
9
  const slug = routeParams?.slug;
10
10
  try {
11
- const result = await payload.find({
12
- collection: 'nitrogen-templates',
13
- where: { slug: { equals: slug } },
14
- limit: 1,
15
- depth: 1,
16
- });
11
+ const [result, headerTemplate, footerTemplate] = await Promise.all([
12
+ payload.find({
13
+ collection: 'nitrogen-templates',
14
+ where: { slug: { equals: slug } },
15
+ limit: 1,
16
+ depth: 1,
17
+ }),
18
+ getTemplateForType(payload, 'header'),
19
+ getTemplateForType(payload, 'footer'),
20
+ ]);
17
21
  const doc = result.docs[0];
18
22
  if (!doc) {
19
23
  return Response.json({ error: 'Not found' }, { status: 404 });
@@ -46,6 +50,12 @@ export const templatesEndpoints = [
46
50
  permalink: frontendBase ? `${frontendBase}${relativePermalink}` : relativePermalink,
47
51
  relative_permalink: relativePermalink,
48
52
  postType: associatedCollection || '',
53
+ headerTemplate: associatedCollection !== 'header' && associatedCollection !== 'footer'
54
+ ? headerTemplate
55
+ : null,
56
+ footerTemplate: associatedCollection !== 'header' && associatedCollection !== 'footer'
57
+ ? footerTemplate
58
+ : null,
49
59
  });
50
60
  }
51
61
  catch {
@@ -91,11 +101,15 @@ export const templatesEndpoints = [
91
101
  const { payload, routeParams } = req;
92
102
  const id = routeParams?.id;
93
103
  try {
94
- const doc = await payload.findByID({
95
- collection: 'nitrogen-templates',
96
- id,
97
- depth: 1,
98
- });
104
+ const [doc, headerTemplate, footerTemplate] = await Promise.all([
105
+ payload.findByID({
106
+ collection: 'nitrogen-templates',
107
+ id,
108
+ depth: 1,
109
+ }),
110
+ getTemplateForType(payload, 'header'),
111
+ getTemplateForType(payload, 'footer'),
112
+ ]);
99
113
  const settings = await getNitrogenSettings(payload);
100
114
  // Build dynamic data from the first document of the associated collection
101
115
  // so the template preview has real data to bind to
@@ -126,6 +140,12 @@ export const templatesEndpoints = [
126
140
  permalink: frontendBase ? `${frontendBase}${relativePermalink}` : relativePermalink,
127
141
  relative_permalink: relativePermalink,
128
142
  postType: associatedCollection || '',
143
+ headerTemplate: associatedCollection !== 'header' && associatedCollection !== 'footer'
144
+ ? headerTemplate
145
+ : null,
146
+ footerTemplate: associatedCollection !== 'header' && associatedCollection !== 'footer'
147
+ ? footerTemplate
148
+ : null,
129
149
  });
130
150
  }
131
151
  catch {
package/dist/index.d.ts CHANGED
@@ -10,6 +10,14 @@ export interface NitrogenConnectorPluginOptions {
10
10
  * Example: `collections: ['pages', 'blog-posts']`
11
11
  */
12
12
  collections?: string[];
13
+ /**
14
+ * Optional frontend route patterns for collections whose public URLs do not
15
+ * match the raw collection slug.
16
+ *
17
+ * Example:
18
+ * `collectionRoutes: { posts: '/blog/[slug]', patent: '/patents/[slug]' }`
19
+ */
20
+ collectionRoutes?: Record<string, string>;
13
21
  }
14
22
  export declare const nitrogenConnectorPlugin: (options?: NitrogenConnectorPluginOptions) => Plugin;
15
23
  export { NitrogenTemplates } from "./collections/NitrogenTemplates";
package/dist/index.js CHANGED
@@ -53,11 +53,12 @@ export const nitrogenConnectorPlugin = (options = {}) => (incomingConfig) => {
53
53
  ...batchEndpoints,
54
54
  ];
55
55
  // Register templates collection
56
- registerCollection("nitrogen-templates", "nitrogen-templates");
57
- registerCollection("nitrogen_template", "nitrogen-templates");
56
+ registerCollection("nitrogen-templates", "nitrogen-templates", "/nitrogen-templates/[slug]");
57
+ registerCollection("nitrogen_template", "nitrogen-templates", "/nitrogen-templates/[slug]");
58
58
  // Process collections
59
59
  const additionalCollections = options.collections || [];
60
60
  for (const slug of additionalCollections) {
61
+ const routePattern = options.collectionRoutes?.[slug];
61
62
  // Find the existing collection in the config
62
63
  const existingIndex = (config.collections || []).findIndex((col) => col.slug === slug);
63
64
  if (existingIndex === -1) {
@@ -125,7 +126,7 @@ export const nitrogenConnectorPlugin = (options = {}) => (incomingConfig) => {
125
126
  const collectionEndpoints = createCollectionEndpoints(slug, slug);
126
127
  config.endpoints = [...(config.endpoints || []), ...collectionEndpoints];
127
128
  // Register in the collection registry for the `all` endpoint
128
- registerCollection(slug, slug);
129
+ registerCollection(slug, slug, routePattern);
129
130
  }
130
131
  return config;
131
132
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitrogenbuilder/connector-payload",
3
- "version": "0.1.27",
3
+ "version": "0.1.29",
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",