@nitrogenbuilder/connector-payload 0.1.42 → 0.1.44

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,8 +3,14 @@
3
3
  *
4
4
  * The plugin registers collections at init time via `registerCollection()`.
5
5
  */
6
+ type RegisteredCollection = {
7
+ collectionSlug: string;
8
+ routePattern?: string;
9
+ };
6
10
  export declare function registerCollection(alias: string, collectionSlug: string, routePattern?: string): void;
7
11
  export declare function resolveCollection(postType: string): string;
8
12
  export declare function resolveCollectionRoutePattern(postType: string): string | undefined;
13
+ export declare function getRegisteredCollections(): RegisteredCollection[];
9
14
  export declare function buildRelativePermalink(postType: string, slug: string): string;
10
15
  export declare function buildPermalink(postType: string, slug: string, frontendUrl?: string): string;
16
+ export {};
@@ -33,6 +33,16 @@ export function resolveCollection(postType) {
33
33
  export function resolveCollectionRoutePattern(postType) {
34
34
  return registeredCollections.get(postType)?.routePattern;
35
35
  }
36
+ export function getRegisteredCollections() {
37
+ const collectionsBySlug = new Map();
38
+ for (const collection of registeredCollections.values()) {
39
+ const existing = collectionsBySlug.get(collection.collectionSlug);
40
+ if (!existing || (!existing.routePattern && collection.routePattern)) {
41
+ collectionsBySlug.set(collection.collectionSlug, collection);
42
+ }
43
+ }
44
+ return Array.from(collectionsBySlug.values());
45
+ }
36
46
  export function buildRelativePermalink(postType, slug) {
37
47
  return buildRelativePath(resolveCollectionRoutePattern(postType), slug);
38
48
  }
@@ -38,6 +38,23 @@ function normalizeRoutePattern(routePattern) {
38
38
  function trimSlashes(value) {
39
39
  return value.replace(/^\/+|\/+$/g, '');
40
40
  }
41
+ function normalizeRelativePath(value) {
42
+ const trimmed = value.trim();
43
+ if (!trimmed || trimmed === '/home')
44
+ return '/';
45
+ return trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
46
+ }
47
+ function getBreadcrumbRelativePath(data) {
48
+ if (!data || typeof data !== 'object')
49
+ return undefined;
50
+ const breadcrumbs = data.breadcrumbs;
51
+ const lastUrl = Array.isArray(breadcrumbs)
52
+ ? breadcrumbs.at(-1)?.url
53
+ : undefined;
54
+ return typeof lastUrl === 'string' && lastUrl.trim()
55
+ ? normalizeRelativePath(lastUrl)
56
+ : undefined;
57
+ }
41
58
  function buildRelativePath(slug, routePattern, templateSlug) {
42
59
  const normalizedRoutePattern = normalizeRoutePattern(routePattern);
43
60
  if (normalizedRoutePattern) {
@@ -60,6 +77,7 @@ export const NitrogenEditButtonRuntime = ({ collection, routePattern }) => {
60
77
  const [id, setId] = useState(undefined);
61
78
  const [token, setToken] = useState(undefined);
62
79
  const [slug, setSlug] = useState(undefined);
80
+ const [documentRelativePath, setDocumentRelativePath] = useState(undefined);
63
81
  const [frontendUrl, setFrontendUrl] = useState(undefined);
64
82
  const [instanceUrl, setInstanceUrl] = useState(undefined);
65
83
  const [authorId, setAuthorId] = useState(undefined);
@@ -73,6 +91,7 @@ export const NitrogenEditButtonRuntime = ({ collection, routePattern }) => {
73
91
  fetch(`/api/${collection}/${docId}`)
74
92
  .then((res) => res.json())
75
93
  .then((data) => {
94
+ setDocumentRelativePath(getBreadcrumbRelativePath(data));
76
95
  if (data.slug) {
77
96
  setSlug(data.slug);
78
97
  }
@@ -142,7 +161,8 @@ export const NitrogenEditButtonRuntime = ({ collection, routePattern }) => {
142
161
  const editHref = `${editorBase}/nitrogen-editor?token=${encodeURIComponent(token)}&collection=${encodeURIComponent(collection)}&pageId=${id}&authorId=${encodedAuthorId}&author=${encodedAuthorId}${developmentFlags}`;
143
162
  const viewRelativePath = collection === 'nitrogen-templates'
144
163
  ? `/nitrogen-templates/${trimSlashes(slug || '')}`
145
- : buildRelativePath(slug || '', routePattern, templateSlug ?? undefined);
164
+ : documentRelativePath ||
165
+ buildRelativePath(slug || '', routePattern, templateSlug ?? undefined);
146
166
  const viewHref = `${frontendUrl}${viewRelativePath}`;
147
167
  const editButtonVars = isDevelopmentMode
148
168
  ? nitrogenEditDevButtonVars
@@ -24,6 +24,23 @@ function normalizeRoutePattern(routePattern) {
24
24
  function trimSlashes(value) {
25
25
  return value.replace(/^\/+|\/+$/g, '');
26
26
  }
27
+ function normalizeRelativePath(value) {
28
+ const trimmed = value.trim();
29
+ if (!trimmed || trimmed === '/home')
30
+ return '/';
31
+ return trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
32
+ }
33
+ function getBreadcrumbRelativePath(data) {
34
+ if (!data || typeof data !== 'object')
35
+ return undefined;
36
+ const breadcrumbs = data.breadcrumbs;
37
+ const lastUrl = Array.isArray(breadcrumbs)
38
+ ? breadcrumbs.at(-1)?.url
39
+ : undefined;
40
+ return typeof lastUrl === 'string' && lastUrl.trim()
41
+ ? normalizeRelativePath(lastUrl)
42
+ : undefined;
43
+ }
27
44
  function buildRelativePath(slug, routePattern, templateSlug) {
28
45
  const normalizedRoutePattern = normalizeRoutePattern(routePattern);
29
46
  if (normalizedRoutePattern) {
@@ -44,6 +61,7 @@ function buildRelativePath(slug, routePattern, templateSlug) {
44
61
  }
45
62
  export const NitrogenViewButtonRuntime = ({ collection, routePattern }) => {
46
63
  const [slug, setSlug] = useState(undefined);
64
+ const [documentRelativePath, setDocumentRelativePath] = useState(undefined);
47
65
  const [siteUrl, setSiteUrl] = useState(undefined);
48
66
  const [templateSlug, setTemplateSlug] = useState(undefined);
49
67
  useEffect(() => {
@@ -55,6 +73,7 @@ export const NitrogenViewButtonRuntime = ({ collection, routePattern }) => {
55
73
  fetch(`/api/${collection}/${docId}`)
56
74
  .then((res) => res.json())
57
75
  .then((data) => {
76
+ setDocumentRelativePath(getBreadcrumbRelativePath(data));
58
77
  if (data.slug) {
59
78
  setSlug(data.slug);
60
79
  }
@@ -105,7 +124,8 @@ export const NitrogenViewButtonRuntime = ({ collection, routePattern }) => {
105
124
  }
106
125
  const relativePath = collection === 'nitrogen-templates'
107
126
  ? `/nitrogen-templates/${trimSlashes(slug)}`
108
- : buildRelativePath(slug, routePattern, templateSlug ?? undefined);
127
+ : documentRelativePath ||
128
+ buildRelativePath(slug, routePattern, templateSlug ?? undefined);
109
129
  const href = `${siteUrl}${relativePath}`;
110
130
  return (_jsx(Button, { buttonStyle: "primary", el: "anchor", margin: false, newTab: true, url: href, extraButtonProps: {
111
131
  style: nitrogenViewButtonVars,
@@ -1,5 +1,5 @@
1
- import { getNitrogenSettings } from './helpers.js';
2
- import { buildPermalink, buildRelativePermalink, resolveCollection, } from '../collection-registry.js';
1
+ import { getDocumentPermalink, getDocumentRelativePermalink, getNitrogenSettings, } from './helpers.js';
2
+ import { resolveCollection } from '../collection-registry.js';
3
3
  export const allEndpoints = [
4
4
  // GET /api/nitrogen/v1/all — List all items with filtering
5
5
  {
@@ -30,14 +30,17 @@ export const allEndpoints = [
30
30
  limit: perPage,
31
31
  depth: 0,
32
32
  });
33
- const items = result.docs.map((doc) => ({
34
- id: doc.id,
35
- title: String(doc.title || ''),
36
- slug: String(doc.slug || ''),
37
- permalink: buildPermalink(postType, String(doc.slug || ''), settings.frontendUrl),
38
- relative_permalink: buildRelativePermalink(postType, String(doc.slug || '')),
39
- type: collection,
40
- }));
33
+ const items = result.docs.map((doc) => {
34
+ const document = doc;
35
+ return {
36
+ id: document.id,
37
+ title: String(document.title || ''),
38
+ slug: String(document.slug || ''),
39
+ permalink: getDocumentPermalink(document, settings, postType),
40
+ relative_permalink: getDocumentRelativePermalink(document, postType),
41
+ type: collection,
42
+ };
43
+ });
41
44
  return Response.json(items);
42
45
  }
43
46
  catch {
@@ -1,5 +1,5 @@
1
- import { buildPermalink, buildRelativePermalink, resolveCollection, } from "../collection-registry.js";
2
- import { getNitrogenSettings, buildDynamicData, buildPageResponse, } from "./helpers.js";
1
+ import { resolveCollection } from "../collection-registry.js";
2
+ import { getNitrogenSettings, buildDynamicData, buildPageResponse, getDocumentPermalink, getDocumentRelativePermalink, } from "./helpers.js";
3
3
  function toArray(value) {
4
4
  if (Array.isArray(value)) {
5
5
  return value.map((item) => String(item).trim()).filter(Boolean);
@@ -286,8 +286,8 @@ export const batchEndpoints = [
286
286
  id: d.id,
287
287
  title: String(d.title || ""),
288
288
  slug,
289
- permalink: buildPermalink(endpoint, slug, settings.frontendUrl),
290
- relative_permalink: buildRelativePermalink(endpoint, slug),
289
+ permalink: getDocumentPermalink(d, settings, endpoint),
290
+ relative_permalink: getDocumentRelativePermalink(d, endpoint),
291
291
  };
292
292
  });
293
293
  }
@@ -1,4 +1,4 @@
1
- import { getNitrogenSettings, buildDynamicData, buildPageResponse, buildListItemResponse, getTemplateForType, requireAuth, } from './helpers.js';
1
+ import { getNitrogenSettings, buildDynamicData, buildResolvedPageResponse, buildResolvedListItemResponse, getTemplateForType, requireAuth, } from './helpers.js';
2
2
  /**
3
3
  * Creates a complete set of CRUD endpoints for a given Payload collection.
4
4
  *
@@ -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, collectionSlug));
31
+ const items = await Promise.all(result.docs.map((doc) => buildResolvedListItemResponse(payload, doc, settings, collectionSlug)));
32
32
  return Response.json(items);
33
33
  },
34
34
  },
@@ -75,8 +75,9 @@ export function createCollectionEndpoints(collectionSlug, endpointPrefix) {
75
75
  getTemplateForType(payload, collectionSlug),
76
76
  ]);
77
77
  const dynamicData = buildDynamicData(doc, settings);
78
+ const response = await buildResolvedPageResponse(payload, doc, settings, dynamicData, collectionSlug);
78
79
  return Response.json({
79
- ...buildPageResponse(doc, settings, dynamicData, collectionSlug),
80
+ ...response,
80
81
  template: pageTemplate,
81
82
  headerTemplate,
82
83
  footerTemplate,
@@ -190,8 +191,9 @@ export function createCollectionEndpoints(collectionSlug, endpointPrefix) {
190
191
  }
191
192
  const doc = result.docs[0];
192
193
  const dynamicData = buildDynamicData(doc, settings);
194
+ const response = await buildResolvedPageResponse(payload, doc, settings, dynamicData, collectionSlug);
193
195
  return Response.json({
194
- ...buildPageResponse(doc, settings, dynamicData, collectionSlug),
196
+ ...response,
195
197
  template: pageTemplate,
196
198
  headerTemplate,
197
199
  footerTemplate,
@@ -217,8 +219,9 @@ export function createCollectionEndpoints(collectionSlug, endpointPrefix) {
217
219
  }
218
220
  const doc = result.docs[0];
219
221
  const dynamicData = buildDynamicData(doc, settings);
222
+ const response = await buildResolvedPageResponse(payload, doc, settings, dynamicData, collectionSlug);
220
223
  return Response.json({
221
- ...buildPageResponse(doc, settings, dynamicData, collectionSlug),
224
+ ...response,
222
225
  template: pageTemplate,
223
226
  headerTemplate,
224
227
  footerTemplate,
@@ -0,0 +1,3 @@
1
+ import type { Payload } from 'payload';
2
+ import type { NitrogenSettingsGlobal } from '../types.js';
3
+ export declare function resolveCtrlLinksInNitrogenData<T>(payload: Payload, value: T, settings: NitrogenSettingsGlobal): Promise<T>;
@@ -0,0 +1,228 @@
1
+ import { buildRelativePermalink, getRegisteredCollections, } from '../collection-registry.js';
2
+ function isRecord(value) {
3
+ return Boolean(value && typeof value === 'object' && !Array.isArray(value));
4
+ }
5
+ function getString(value) {
6
+ return typeof value === 'string' ? value : '';
7
+ }
8
+ function getId(value) {
9
+ if (typeof value === 'string' || typeof value === 'number')
10
+ return value;
11
+ return null;
12
+ }
13
+ function cloneJson(value) {
14
+ if (value === undefined)
15
+ return value;
16
+ return JSON.parse(JSON.stringify(value));
17
+ }
18
+ function normalizeRelativePath(value) {
19
+ const trimmed = value.trim();
20
+ if (!trimmed || trimmed === '/home')
21
+ return '/';
22
+ try {
23
+ const parsed = new URL(trimmed);
24
+ return normalizeRelativePath(`${parsed.pathname}${parsed.search}${parsed.hash}`);
25
+ }
26
+ catch {
27
+ // Treat as already relative.
28
+ }
29
+ const [path = '', suffix = ''] = trimmed.split(/([?#].*)/, 2);
30
+ const normalizedPath = path.startsWith('/') ? path : `/${path}`;
31
+ const withoutTrailingSlash = normalizedPath.length > 1 ? normalizedPath.replace(/\/+$/, '') : normalizedPath;
32
+ return `${withoutTrailingSlash}${suffix}`;
33
+ }
34
+ function getPathname(value) {
35
+ const normalized = normalizeRelativePath(value);
36
+ return normalized.split(/[?#]/)[0] || '/';
37
+ }
38
+ function matchRoutePattern(pathname, routePattern) {
39
+ if (!routePattern)
40
+ return 1;
41
+ const pattern = getPathname(routePattern);
42
+ const placeholderMatch = pattern.match(/\[(?:\.\.\.)?slug\]/);
43
+ if (placeholderMatch) {
44
+ const base = pattern.slice(0, placeholderMatch.index).replace(/\/+$/, '');
45
+ if (!base) {
46
+ return pathname === '/' ? 0 : 20;
47
+ }
48
+ return pathname.startsWith(`${base}/`) ? 100 + base.length : 0;
49
+ }
50
+ if (pathname === pattern)
51
+ return 90 + pattern.length;
52
+ return pathname.startsWith(`${pattern}/`) ? 50 + pattern.length : 0;
53
+ }
54
+ function getCollectionCandidates(link, internalRef) {
55
+ const explicitCollection = getString(internalRef.collection) ||
56
+ getString(internalRef.collectionSlug) ||
57
+ getString(link.collection) ||
58
+ getString(link.collectionSlug);
59
+ const savedPath = getString(internalRef.relativePermalink) ||
60
+ getString(link.url) ||
61
+ getString(internalRef.slug);
62
+ const pathname = getPathname(savedPath);
63
+ const candidates = new Map();
64
+ if (explicitCollection) {
65
+ candidates.set(explicitCollection, Number.MAX_SAFE_INTEGER);
66
+ }
67
+ for (const collection of getRegisteredCollections()) {
68
+ const score = matchRoutePattern(pathname, collection.routePattern);
69
+ if (score <= 0)
70
+ continue;
71
+ candidates.set(collection.collectionSlug, Math.max(candidates.get(collection.collectionSlug) || 0, score));
72
+ }
73
+ if (!candidates.has('pages')) {
74
+ candidates.set('pages', 1);
75
+ }
76
+ return Array.from(candidates.entries())
77
+ .sort((a, b) => b[1] - a[1])
78
+ .map(([collection]) => collection);
79
+ }
80
+ function getDocumentRelativePermalink(doc, collectionSlug) {
81
+ const breadcrumbs = doc.breadcrumbs;
82
+ const lastUrl = Array.isArray(breadcrumbs)
83
+ ? breadcrumbs.at(-1)?.url
84
+ : undefined;
85
+ if (typeof lastUrl === 'string' && lastUrl.trim()) {
86
+ return normalizeRelativePath(lastUrl);
87
+ }
88
+ return buildRelativePermalink(collectionSlug, String(doc.slug || ''));
89
+ }
90
+ function getDocumentPermalink(doc, settings, collectionSlug) {
91
+ const relativePermalink = getDocumentRelativePermalink(doc, collectionSlug);
92
+ const baseUrl = (settings.frontendUrl || '').replace(/\/$/, '');
93
+ return baseUrl ? `${baseUrl}${relativePermalink}` : relativePermalink;
94
+ }
95
+ function getPayloadBaseUrl(settings) {
96
+ return (process.env.NEXT_PUBLIC_PAYLOAD_API_URL ||
97
+ process.env.NEXT_PUBLIC_SERVER_URL ||
98
+ settings.nitrogenEditorUrl ||
99
+ settings.instanceUrl ||
100
+ '').replace(/\/$/, '');
101
+ }
102
+ function resolveMediaUrl(url, settings) {
103
+ if (!url || /^https?:\/\//i.test(url))
104
+ return url;
105
+ const baseUrl = getPayloadBaseUrl(settings);
106
+ const relativeUrl = url.startsWith('/') ? url : `/${url}`;
107
+ return baseUrl ? `${baseUrl}${relativeUrl}` : relativeUrl;
108
+ }
109
+ async function findInternalTarget(context, link, internalRef) {
110
+ const entryId = getId(internalRef.entryId);
111
+ if (entryId === null)
112
+ return null;
113
+ const candidates = getCollectionCandidates(link, internalRef);
114
+ for (const collection of candidates) {
115
+ try {
116
+ const doc = (await context.payload.findByID({
117
+ collection: collection,
118
+ id: entryId,
119
+ depth: 1,
120
+ disableErrors: true,
121
+ }));
122
+ if (!doc)
123
+ continue;
124
+ return {
125
+ slug: String(doc.slug || internalRef.slug || ''),
126
+ relativePermalink: getDocumentRelativePermalink(doc, collection),
127
+ url: getDocumentPermalink(doc, context.settings, collection),
128
+ };
129
+ }
130
+ catch {
131
+ // Try the next candidate collection.
132
+ }
133
+ }
134
+ return null;
135
+ }
136
+ async function resolveInternalLink(context, link, internalRef) {
137
+ const entryId = getId(internalRef.entryId);
138
+ if (entryId === null)
139
+ return null;
140
+ const key = [
141
+ entryId,
142
+ getString(internalRef.relativePermalink),
143
+ getString(link.url),
144
+ getString(internalRef.collection),
145
+ getString(link.collection),
146
+ ].join(':');
147
+ if (!context.internalLinks.has(key)) {
148
+ context.internalLinks.set(key, findInternalTarget(context, link, internalRef));
149
+ }
150
+ return context.internalLinks.get(key);
151
+ }
152
+ async function findMediaTarget(context, mediaRef) {
153
+ const mediaId = getId(mediaRef.mediaId);
154
+ if (mediaId === null)
155
+ return null;
156
+ try {
157
+ const doc = (await context.payload.findByID({
158
+ collection: 'media',
159
+ id: mediaId,
160
+ depth: 0,
161
+ disableErrors: true,
162
+ }));
163
+ const url = doc?.url || '';
164
+ if (!doc || !url)
165
+ return null;
166
+ return {
167
+ filename: doc.filename || getString(mediaRef.filename),
168
+ mimeType: doc.mimeType || getString(mediaRef.mimeType),
169
+ url: resolveMediaUrl(url, context.settings),
170
+ };
171
+ }
172
+ catch {
173
+ return null;
174
+ }
175
+ }
176
+ async function resolveMediaLink(context, mediaRef) {
177
+ const mediaId = getId(mediaRef.mediaId);
178
+ if (mediaId === null)
179
+ return null;
180
+ const key = String(mediaId);
181
+ if (!context.mediaLinks.has(key)) {
182
+ context.mediaLinks.set(key, findMediaTarget(context, mediaRef));
183
+ }
184
+ return context.mediaLinks.get(key);
185
+ }
186
+ async function resolveLinkRecord(context, link) {
187
+ if (link.type === 'internal' && isRecord(link.internalRef)) {
188
+ const resolved = await resolveInternalLink(context, link, link.internalRef);
189
+ if (!resolved)
190
+ return;
191
+ link.url = resolved.url;
192
+ link.internalRef.slug = resolved.slug;
193
+ link.internalRef.relativePermalink = resolved.relativePermalink;
194
+ return;
195
+ }
196
+ if (link.type === 'media' && isRecord(link.mediaRef)) {
197
+ const resolved = await resolveMediaLink(context, link.mediaRef);
198
+ if (!resolved)
199
+ return;
200
+ link.url = resolved.url;
201
+ link.mediaRef.url = resolved.url;
202
+ link.mediaRef.filename = resolved.filename;
203
+ link.mediaRef.mimeType = resolved.mimeType;
204
+ }
205
+ }
206
+ async function walk(context, value) {
207
+ if (Array.isArray(value)) {
208
+ await Promise.all(value.map((item) => walk(context, item)));
209
+ return;
210
+ }
211
+ if (!isRecord(value))
212
+ return;
213
+ await resolveLinkRecord(context, value);
214
+ await Promise.all(Object.values(value).map((item) => walk(context, item)));
215
+ }
216
+ export async function resolveCtrlLinksInNitrogenData(payload, value, settings) {
217
+ if (!value)
218
+ return value;
219
+ const cloned = cloneJson(value);
220
+ const context = {
221
+ internalLinks: new Map(),
222
+ mediaLinks: new Map(),
223
+ payload,
224
+ settings,
225
+ };
226
+ await walk(context, cloned);
227
+ return cloned;
228
+ }
@@ -1,5 +1,7 @@
1
1
  import type { Payload, PayloadRequest } from 'payload';
2
2
  import type { JsonObject, NitrogenPageDoc, NitrogenTemplateDoc, NitrogenSettingsGlobal, MediaDoc } from '../types.js';
3
+ export declare function getDocumentRelativePermalink(doc: NitrogenPageDoc | NitrogenTemplateDoc | Record<string, unknown>, collectionSlug?: string): string;
4
+ export declare function getDocumentPermalink(doc: NitrogenPageDoc | NitrogenTemplateDoc | Record<string, unknown>, settings: NitrogenSettingsGlobal, collectionSlug?: string): string;
3
5
  export interface TemplateRef {
4
6
  ID: string | number;
5
7
  content: string;
@@ -29,6 +31,29 @@ export declare function buildPageResponse(doc: NitrogenPageDoc | NitrogenTemplat
29
31
  wysiwygColors: {};
30
32
  };
31
33
  };
34
+ export declare function buildResolvedPageResponse(payload: Payload, doc: NitrogenPageDoc | NitrogenTemplateDoc, settings: NitrogenSettingsGlobal, dynamicData: JsonObject, collectionSlug?: string): Promise<{
35
+ id: string | number;
36
+ token: string;
37
+ title: string;
38
+ author: string | number;
39
+ slug: string;
40
+ permalink: string;
41
+ relative_permalink: string;
42
+ content: string;
43
+ dynamic_data: JsonObject;
44
+ settings: JsonObject;
45
+ nitrogen_settings: {
46
+ variables?: Record<string, {
47
+ type?: string;
48
+ value?: string | boolean;
49
+ }> | Array<{
50
+ name: string;
51
+ value?: string;
52
+ }>;
53
+ urlMaps: never[];
54
+ wysiwygColors: {};
55
+ };
56
+ }>;
32
57
  export declare function buildListItemResponse(doc: NitrogenPageDoc | NitrogenTemplateDoc, settings: NitrogenSettingsGlobal, collectionSlug?: string): {
33
58
  id: string | number;
34
59
  title: string;
@@ -49,6 +74,26 @@ export declare function buildListItemResponse(doc: NitrogenPageDoc | NitrogenTem
49
74
  wysiwygColors: {};
50
75
  };
51
76
  };
77
+ export declare function buildResolvedListItemResponse(payload: Payload, doc: NitrogenPageDoc | NitrogenTemplateDoc, settings: NitrogenSettingsGlobal, collectionSlug?: string): Promise<{
78
+ id: string | number;
79
+ title: string;
80
+ slug: string;
81
+ permalink: string;
82
+ relative_permalink: string;
83
+ content: string;
84
+ settings: JsonObject;
85
+ nitrogen_settings: {
86
+ variables?: Record<string, {
87
+ type?: string;
88
+ value?: string | boolean;
89
+ }> | Array<{
90
+ name: string;
91
+ value?: string;
92
+ }>;
93
+ urlMaps: never[];
94
+ wysiwygColors: {};
95
+ };
96
+ }>;
52
97
  export declare function buildMediaItemResponse(doc: MediaDoc): {
53
98
  id: string | number;
54
99
  filename: string;
@@ -1,4 +1,26 @@
1
- import { buildPermalink, buildRelativePermalink } from '../collection-registry.js';
1
+ import { buildRelativePermalink } from '../collection-registry.js';
2
+ import { resolveCtrlLinksInNitrogenData } from './ctrlLinkResolver.js';
3
+ function normalizeRelativePath(value) {
4
+ const trimmed = value.trim();
5
+ if (!trimmed || trimmed === '/home')
6
+ return '/';
7
+ return trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
8
+ }
9
+ export function getDocumentRelativePermalink(doc, collectionSlug = 'pages') {
10
+ const breadcrumbs = doc.breadcrumbs;
11
+ const lastUrl = Array.isArray(breadcrumbs)
12
+ ? breadcrumbs.at(-1)?.url
13
+ : undefined;
14
+ if (typeof lastUrl === 'string' && lastUrl.trim()) {
15
+ return normalizeRelativePath(lastUrl);
16
+ }
17
+ return buildRelativePermalink(collectionSlug, String(doc.slug || ''));
18
+ }
19
+ export function getDocumentPermalink(doc, settings, collectionSlug = 'pages') {
20
+ const relativePermalink = getDocumentRelativePermalink(doc, collectionSlug);
21
+ const baseUrl = (settings.frontendUrl || '').replace(/\/$/, '');
22
+ return baseUrl ? `${baseUrl}${relativePermalink}` : relativePermalink;
23
+ }
2
24
  export function formatDate(date) {
3
25
  const d = new Date(date);
4
26
  const pad = (n) => String(n).padStart(2, '0');
@@ -60,8 +82,8 @@ export function buildPageResponse(doc, settings, dynamicData, collectionSlug = '
60
82
  title: doc.title,
61
83
  author: doc.author && typeof doc.author === 'object' ? doc.author.id : (doc.author || ''),
62
84
  slug,
63
- permalink: buildPermalink(collectionSlug, slug, settings.frontendUrl),
64
- relative_permalink: buildRelativePermalink(collectionSlug, slug),
85
+ permalink: getDocumentPermalink(doc, settings, collectionSlug),
86
+ relative_permalink: getDocumentRelativePermalink(doc, collectionSlug),
65
87
  content: doc.nitrogenData ? JSON.stringify(doc.nitrogenData) : '[]',
66
88
  dynamic_data: dynamicData,
67
89
  settings: pageSettings || templateSettings || {
@@ -74,6 +96,13 @@ export function buildPageResponse(doc, settings, dynamicData, collectionSlug = '
74
96
  },
75
97
  };
76
98
  }
99
+ export async function buildResolvedPageResponse(payload, doc, settings, dynamicData, collectionSlug = 'pages') {
100
+ const response = buildPageResponse(doc, settings, dynamicData, collectionSlug);
101
+ if (doc.nitrogenData) {
102
+ response.content = JSON.stringify(await resolveCtrlLinksInNitrogenData(payload, doc.nitrogenData, settings));
103
+ }
104
+ return response;
105
+ }
77
106
  export function buildListItemResponse(doc, settings, collectionSlug = 'pages') {
78
107
  const pageSettings = 'pageSettings' in doc ? doc.pageSettings : undefined;
79
108
  const templateSettings = 'templateSettings' in doc ? doc.templateSettings : undefined;
@@ -82,8 +111,8 @@ export function buildListItemResponse(doc, settings, collectionSlug = 'pages') {
82
111
  id: doc.id,
83
112
  title: doc.title,
84
113
  slug,
85
- permalink: buildPermalink(collectionSlug, slug, settings.frontendUrl),
86
- relative_permalink: buildRelativePermalink(collectionSlug, slug),
114
+ permalink: getDocumentPermalink(doc, settings, collectionSlug),
115
+ relative_permalink: getDocumentRelativePermalink(doc, collectionSlug),
87
116
  content: doc.nitrogenData ? JSON.stringify(doc.nitrogenData) : '[]',
88
117
  settings: pageSettings || templateSettings || {},
89
118
  nitrogen_settings: {
@@ -93,6 +122,13 @@ export function buildListItemResponse(doc, settings, collectionSlug = 'pages') {
93
122
  },
94
123
  };
95
124
  }
125
+ export async function buildResolvedListItemResponse(payload, doc, settings, collectionSlug = 'pages') {
126
+ const response = buildListItemResponse(doc, settings, collectionSlug);
127
+ if (doc.nitrogenData) {
128
+ response.content = JSON.stringify(await resolveCtrlLinksInNitrogenData(payload, doc.nitrogenData, settings));
129
+ }
130
+ return response;
131
+ }
96
132
  export function buildMediaItemResponse(doc) {
97
133
  const filename = doc.filename || '';
98
134
  const ext = filename.includes('.') ? `.${filename.split('.').pop()}` : '';
@@ -176,9 +212,13 @@ export async function getTemplateForType(payload, type) {
176
212
  const doc = result.docs[0];
177
213
  if (!doc)
178
214
  return null;
215
+ const settings = await getNitrogenSettings(payload);
216
+ const resolvedNitrogenData = doc.nitrogenData
217
+ ? await resolveCtrlLinksInNitrogenData(payload, doc.nitrogenData, settings)
218
+ : null;
179
219
  return {
180
220
  ID: doc.id,
181
- content: doc.nitrogenData ? JSON.stringify(doc.nitrogenData) : '[]',
221
+ content: resolvedNitrogenData ? JSON.stringify(resolvedNitrogenData) : '[]',
182
222
  };
183
223
  }
184
224
  catch {
@@ -1,5 +1,4 @@
1
- import { getNitrogenSettings, buildDynamicData, buildPageResponse, buildListItemResponse, getTemplateForType, requireAuth, } from './helpers.js';
2
- import { buildPermalink, buildRelativePermalink } from '../collection-registry.js';
1
+ import { getNitrogenSettings, buildDynamicData, buildResolvedPageResponse, buildResolvedListItemResponse, getDocumentPermalink, getDocumentRelativePermalink, getTemplateForType, requireAuth, } from './helpers.js';
3
2
  function isContentTemplateCollection(collection) {
4
3
  return !!collection && collection !== 'header' && collection !== 'footer';
5
4
  }
@@ -11,10 +10,11 @@ function getTemplatePreviewTarget(doc, settings, associatedCollection, associate
11
10
  ? associatedCollection
12
11
  : 'nitrogen-templates';
13
12
  const previewSlug = String(associatedDoc?.slug || doc.slug || '');
13
+ const previewDoc = associatedDoc || doc;
14
14
  return {
15
15
  slug: previewSlug,
16
- permalink: buildPermalink(previewCollection, previewSlug, settings.frontendUrl),
17
- relative_permalink: buildRelativePermalink(previewCollection, previewSlug),
16
+ permalink: getDocumentPermalink(previewDoc, settings, previewCollection),
17
+ relative_permalink: getDocumentRelativePermalink(previewDoc, previewCollection),
18
18
  };
19
19
  }
20
20
  export const templatesEndpoints = [
@@ -60,7 +60,7 @@ export const templatesEndpoints = [
60
60
  // Collection may not exist — use template's own data
61
61
  }
62
62
  }
63
- const response = buildPageResponse(doc, settings, dynamicData);
63
+ const response = await buildResolvedPageResponse(payload, doc, settings, dynamicData);
64
64
  const previewTarget = getTemplatePreviewTarget(doc, settings, associatedCollection, associatedDoc);
65
65
  return Response.json({
66
66
  ...response,
@@ -99,11 +99,14 @@ export const templatesEndpoints = [
99
99
  limit: 0,
100
100
  depth: 1,
101
101
  });
102
- const items = result.docs.map((doc) => ({
103
- ...buildListItemResponse(doc, settings),
104
- permalink: `${(settings.frontendUrl || '').replace(/\/$/, '')}/nitrogen-templates/${doc.slug}`,
105
- relative_permalink: `/nitrogen-templates/${doc.slug}`,
106
- postType: doc.associatedCollection || '',
102
+ const items = await Promise.all(result.docs.map(async (doc) => {
103
+ const response = await buildResolvedListItemResponse(payload, doc, settings);
104
+ return {
105
+ ...response,
106
+ permalink: `${(settings.frontendUrl || '').replace(/\/$/, '')}/nitrogen-templates/${doc.slug}`,
107
+ relative_permalink: `/nitrogen-templates/${doc.slug}`,
108
+ postType: doc.associatedCollection || '',
109
+ };
107
110
  }));
108
111
  return Response.json(items);
109
112
  },
@@ -147,7 +150,7 @@ export const templatesEndpoints = [
147
150
  // Collection may not exist — use template's own data
148
151
  }
149
152
  }
150
- const response = buildPageResponse(doc, settings, dynamicData);
153
+ const response = await buildResolvedPageResponse(payload, doc, settings, dynamicData);
151
154
  const previewTarget = getTemplatePreviewTarget(doc, settings, associatedCollection, associatedDoc);
152
155
  return Response.json({
153
156
  ...response,
package/dist/types.d.ts CHANGED
@@ -11,6 +11,11 @@ interface NitrogenDocBase {
11
11
  id: string | number;
12
12
  title: string;
13
13
  slug: string;
14
+ breadcrumbs?: Array<{
15
+ url?: string | null;
16
+ label?: string | null;
17
+ doc?: string | number | null;
18
+ }>;
14
19
  status?: 'draft' | 'published';
15
20
  author?: string | number | {
16
21
  id: string | number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitrogenbuilder/connector-payload",
3
- "version": "0.1.42",
3
+ "version": "0.1.44",
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",