@nitrogenbuilder/connector-payload 0.1.12 → 0.1.14
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/dist/components/NitrogenDataViewer.d.ts +3 -0
- package/dist/components/NitrogenDataViewer.js +347 -0
- package/dist/endpoints/batch.d.ts +2 -0
- package/dist/endpoints/batch.js +76 -0
- package/dist/endpoints/collection-endpoints.js +40 -24
- package/dist/endpoints/helpers.d.ts +9 -0
- package/dist/endpoints/helpers.js +24 -0
- package/dist/globals/NitrogenSettings.js +3 -0
- package/dist/index.js +2 -0
- package/package.json +9 -5
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { useState, useCallback } from 'react';
|
|
4
|
+
import { Collapsible, useField } from '@payloadcms/ui';
|
|
5
|
+
function updateAtPath(root, path, newValue) {
|
|
6
|
+
if (path.length === 0)
|
|
7
|
+
return newValue;
|
|
8
|
+
const [head, ...rest] = path;
|
|
9
|
+
if (Array.isArray(root)) {
|
|
10
|
+
const copy = [...root];
|
|
11
|
+
copy[head] = updateAtPath(copy[head], rest, newValue);
|
|
12
|
+
return copy;
|
|
13
|
+
}
|
|
14
|
+
if (typeof root === 'object' && root !== null) {
|
|
15
|
+
const obj = root;
|
|
16
|
+
return { ...obj, [head]: updateAtPath(obj[head], rest, newValue) };
|
|
17
|
+
}
|
|
18
|
+
return root;
|
|
19
|
+
}
|
|
20
|
+
function deleteAtPath(root, path) {
|
|
21
|
+
if (path.length === 0)
|
|
22
|
+
return root;
|
|
23
|
+
if (path.length === 1) {
|
|
24
|
+
const [head] = path;
|
|
25
|
+
if (Array.isArray(root))
|
|
26
|
+
return root.filter((_, i) => i !== head);
|
|
27
|
+
if (typeof root === 'object' && root !== null) {
|
|
28
|
+
const { [head]: _removed, ...rest } = root;
|
|
29
|
+
return rest;
|
|
30
|
+
}
|
|
31
|
+
return root;
|
|
32
|
+
}
|
|
33
|
+
const [head, ...rest] = path;
|
|
34
|
+
if (Array.isArray(root)) {
|
|
35
|
+
const copy = [...root];
|
|
36
|
+
copy[head] = deleteAtPath(copy[head], rest);
|
|
37
|
+
return copy;
|
|
38
|
+
}
|
|
39
|
+
if (typeof root === 'object' && root !== null) {
|
|
40
|
+
const obj = root;
|
|
41
|
+
return { ...obj, [head]: deleteAtPath(obj[head], rest) };
|
|
42
|
+
}
|
|
43
|
+
return root;
|
|
44
|
+
}
|
|
45
|
+
function renameKeyAtPath(root, path, newKey) {
|
|
46
|
+
if (path.length === 0)
|
|
47
|
+
return root;
|
|
48
|
+
const parentPath = path.slice(0, -1);
|
|
49
|
+
const oldKey = path[path.length - 1];
|
|
50
|
+
function navigate(node, remaining) {
|
|
51
|
+
if (remaining.length === 0) {
|
|
52
|
+
if (typeof node === 'object' && node !== null && !Array.isArray(node)) {
|
|
53
|
+
const obj = node;
|
|
54
|
+
const result = {};
|
|
55
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
56
|
+
result[k === oldKey ? newKey : k] = v;
|
|
57
|
+
}
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
60
|
+
return node;
|
|
61
|
+
}
|
|
62
|
+
const [head, ...rest] = remaining;
|
|
63
|
+
if (Array.isArray(node)) {
|
|
64
|
+
const copy = [...node];
|
|
65
|
+
copy[head] = navigate(copy[head], rest);
|
|
66
|
+
return copy;
|
|
67
|
+
}
|
|
68
|
+
if (typeof node === 'object' && node !== null) {
|
|
69
|
+
const obj = node;
|
|
70
|
+
return { ...obj, [head]: navigate(obj[head], rest) };
|
|
71
|
+
}
|
|
72
|
+
return node;
|
|
73
|
+
}
|
|
74
|
+
return navigate(root, parentPath);
|
|
75
|
+
}
|
|
76
|
+
function addChildAtPath(root, path) {
|
|
77
|
+
function getAt(node, p) {
|
|
78
|
+
if (p.length === 0)
|
|
79
|
+
return node;
|
|
80
|
+
const [head, ...rest] = p;
|
|
81
|
+
if (Array.isArray(node))
|
|
82
|
+
return getAt(node[head], rest);
|
|
83
|
+
if (typeof node === 'object' && node !== null)
|
|
84
|
+
return getAt(node[head], rest);
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
const target = getAt(root, path);
|
|
88
|
+
if (Array.isArray(target)) {
|
|
89
|
+
return updateAtPath(root, path, [...target, '']);
|
|
90
|
+
}
|
|
91
|
+
if (typeof target === 'object' && target !== null) {
|
|
92
|
+
const obj = target;
|
|
93
|
+
let key = 'newKey';
|
|
94
|
+
let i = 1;
|
|
95
|
+
while (key in obj)
|
|
96
|
+
key = `newKey${i++}`;
|
|
97
|
+
return updateAtPath(root, path, { ...obj, [key]: '' });
|
|
98
|
+
}
|
|
99
|
+
return root;
|
|
100
|
+
}
|
|
101
|
+
// ── Inline text input for editing values and keys ─────────────────────
|
|
102
|
+
function InlineEdit({ display, onCommit, colorClass, }) {
|
|
103
|
+
const [draft, setDraft] = useState(display);
|
|
104
|
+
const commit = () => onCommit(draft);
|
|
105
|
+
return (_jsx("input", { autoFocus: true, value: draft, onChange: (e) => setDraft(e.target.value), onBlur: commit, onKeyDown: (e) => {
|
|
106
|
+
if (e.key === 'Enter') {
|
|
107
|
+
e.preventDefault();
|
|
108
|
+
commit();
|
|
109
|
+
}
|
|
110
|
+
if (e.key === 'Escape') {
|
|
111
|
+
onCommit(display);
|
|
112
|
+
} // cancel
|
|
113
|
+
}, onClick: (e) => e.stopPropagation(), className: colorClass, style: {
|
|
114
|
+
background: 'transparent',
|
|
115
|
+
border: 'none',
|
|
116
|
+
borderBottom: '1px solid currentColor',
|
|
117
|
+
color: 'inherit',
|
|
118
|
+
font: 'inherit',
|
|
119
|
+
padding: '0 2px',
|
|
120
|
+
outline: 'none',
|
|
121
|
+
minWidth: '4ch',
|
|
122
|
+
width: `${Math.max(draft.length + 2, 4)}ch`,
|
|
123
|
+
maxWidth: '500px',
|
|
124
|
+
} }));
|
|
125
|
+
}
|
|
126
|
+
// ── Small action button (delete / add) ────────────────────────────────
|
|
127
|
+
function ActionBtn({ label, color, onClick, }) {
|
|
128
|
+
return (_jsx("button", { type: "button", onClick: onClick, style: {
|
|
129
|
+
marginLeft: 6,
|
|
130
|
+
padding: '0 5px',
|
|
131
|
+
fontSize: '10px',
|
|
132
|
+
lineHeight: '16px',
|
|
133
|
+
background: color,
|
|
134
|
+
color: '#fff',
|
|
135
|
+
border: 'none',
|
|
136
|
+
borderRadius: '3px',
|
|
137
|
+
cursor: 'pointer',
|
|
138
|
+
flexShrink: 0,
|
|
139
|
+
opacity: 0.85,
|
|
140
|
+
}, children: label }));
|
|
141
|
+
}
|
|
142
|
+
function JsonNode({ k, value, depth, forceOpen, path, unlocked, callbacks, }) {
|
|
143
|
+
const defaultOpen = forceOpen !== null ? forceOpen : depth < 2;
|
|
144
|
+
const [open, setOpen] = useState(defaultOpen);
|
|
145
|
+
const [editingValue, setEditingValue] = useState(false);
|
|
146
|
+
const [editingKey, setEditingKey] = useState(false);
|
|
147
|
+
const { onUpdate, onDelete, onRenameKey, onAddChild } = callbacks;
|
|
148
|
+
const del = unlocked ? (_jsx(ActionBtn, { label: "\u00D7", color: "#ef4444", onClick: (e) => { e.stopPropagation(); onDelete(path); } })) : null;
|
|
149
|
+
// Key display — clicking it opens inline edit when unlocked
|
|
150
|
+
const keyEl = k === undefined ? null : (_jsxs(_Fragment, { children: [_jsxs("span", { className: "njv-key", children: ["\"", unlocked && editingKey ? (_jsx(InlineEdit, { display: k, colorClass: "njv-key", onCommit: (newKey) => {
|
|
151
|
+
setEditingKey(false);
|
|
152
|
+
if (newKey !== k)
|
|
153
|
+
onRenameKey(path, newKey);
|
|
154
|
+
} })) : (_jsx("span", { onClick: unlocked ? (e) => { e.stopPropagation(); setEditingKey(true); } : undefined, style: unlocked ? { cursor: 'text', borderBottom: '1px dotted currentColor' } : undefined, children: k })), "\""] }), _jsx("span", { className: "njv-brace", children: ": " })] }));
|
|
155
|
+
// ── null ──
|
|
156
|
+
if (value === null) {
|
|
157
|
+
return (_jsxs("div", { className: "njv-line", style: { alignItems: 'center' }, children: [_jsxs("span", { className: "njv-code", style: { paddingLeft: depth * 20 }, children: [keyEl, _jsx("span", { className: "njv-null", title: unlocked ? 'Click to convert to empty string' : undefined, onClick: unlocked ? (e) => { e.stopPropagation(); onUpdate(path, ''); } : undefined, style: unlocked ? { cursor: 'pointer', borderBottom: '1px dotted currentColor' } : undefined, children: "null" })] }), del] }));
|
|
158
|
+
}
|
|
159
|
+
// ── string ──
|
|
160
|
+
if (typeof value === 'string') {
|
|
161
|
+
return (_jsxs("div", { className: "njv-line", style: { alignItems: 'center' }, children: [_jsxs("span", { className: "njv-code", style: { paddingLeft: depth * 20 }, children: [keyEl, _jsxs("span", { className: "njv-string", children: ["\"", unlocked && editingValue ? (_jsx(InlineEdit, { display: value, colorClass: "njv-string", onCommit: (v) => { setEditingValue(false); onUpdate(path, v); } })) : (_jsx("span", { onClick: unlocked ? (e) => { e.stopPropagation(); setEditingValue(true); } : undefined, style: unlocked ? { cursor: 'text', borderBottom: '1px dotted currentColor' } : undefined, children: value.length > 120 ? value.slice(0, 120) + '…' : value })), "\""] })] }), del] }));
|
|
162
|
+
}
|
|
163
|
+
// ── number ──
|
|
164
|
+
if (typeof value === 'number') {
|
|
165
|
+
return (_jsxs("div", { className: "njv-line", style: { alignItems: 'center' }, children: [_jsxs("span", { className: "njv-code", style: { paddingLeft: depth * 20 }, children: [keyEl, _jsx("span", { className: "njv-number", children: unlocked && editingValue ? (_jsx(InlineEdit, { display: String(value), colorClass: "njv-number", onCommit: (v) => {
|
|
166
|
+
setEditingValue(false);
|
|
167
|
+
const n = Number(v);
|
|
168
|
+
onUpdate(path, isNaN(n) ? v : n);
|
|
169
|
+
} })) : (_jsx("span", { onClick: unlocked ? (e) => { e.stopPropagation(); setEditingValue(true); } : undefined, style: unlocked ? { cursor: 'text', borderBottom: '1px dotted currentColor' } : undefined, children: value })) })] }), del] }));
|
|
170
|
+
}
|
|
171
|
+
// ── boolean — click to toggle ──
|
|
172
|
+
if (typeof value === 'boolean') {
|
|
173
|
+
return (_jsxs("div", { className: "njv-line", style: { alignItems: 'center' }, children: [_jsxs("span", { className: "njv-code", style: { paddingLeft: depth * 20 }, children: [keyEl, _jsx("span", { className: "njv-boolean", onClick: unlocked ? (e) => { e.stopPropagation(); onUpdate(path, !value); } : undefined, title: unlocked ? 'Click to toggle' : undefined, style: unlocked ? { cursor: 'pointer', borderBottom: '1px dotted currentColor' } : undefined, children: String(value) })] }), del] }));
|
|
174
|
+
}
|
|
175
|
+
// ── array ──
|
|
176
|
+
if (Array.isArray(value)) {
|
|
177
|
+
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "njv-line", style: { alignItems: 'center' }, children: [_jsxs("span", { className: "njv-code", style: { paddingLeft: depth * 20, cursor: 'pointer', userSelect: 'none' }, onClick: () => setOpen((o) => !o), children: [_jsx("span", { className: "njv-arrow", children: open ? '▼' : '▶' }), keyEl, _jsx("span", { className: "njv-brace", children: "[" }), !open && (_jsxs(_Fragment, { children: [_jsxs("span", { className: "njv-summary", children: [" ", value.length, " items "] }), _jsx("span", { className: "njv-brace", children: "]" })] }))] }), del] }), open && (_jsxs(_Fragment, { children: [value.map((item, i) => (_jsx(JsonNode, { value: item, depth: depth + 1, forceOpen: forceOpen, path: [...path, i], unlocked: unlocked, callbacks: callbacks }, i))), _jsxs("div", { className: "njv-line", style: { alignItems: 'center' }, children: [_jsx("span", { className: "njv-code", style: { paddingLeft: (depth + 1) * 20 }, children: _jsx("span", { className: "njv-brace", children: "]" }) }), unlocked && (_jsx(ActionBtn, { label: "+ item", color: "#22c55e", onClick: (e) => { e.stopPropagation(); onAddChild(path); } }))] })] }))] }));
|
|
178
|
+
}
|
|
179
|
+
// ── object ──
|
|
180
|
+
if (typeof value === 'object') {
|
|
181
|
+
const entries = Object.entries(value);
|
|
182
|
+
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "njv-line", style: { alignItems: 'center' }, children: [_jsxs("span", { className: "njv-code", style: { paddingLeft: depth * 20, cursor: 'pointer', userSelect: 'none' }, onClick: () => setOpen((o) => !o), children: [_jsx("span", { className: "njv-arrow", children: open ? '▼' : '▶' }), keyEl, _jsx("span", { className: "njv-brace", children: '{' }), !open && (_jsxs(_Fragment, { children: [_jsxs("span", { className: "njv-summary", children: [" ", entries.length, " keys "] }), _jsx("span", { className: "njv-brace", children: '}' })] }))] }), del] }), open && (_jsxs(_Fragment, { children: [entries.map(([ek, ev]) => (_jsx(JsonNode, { k: ek, value: ev, depth: depth + 1, forceOpen: forceOpen, path: [...path, ek], unlocked: unlocked, callbacks: callbacks }, ek))), _jsxs("div", { className: "njv-line", style: { alignItems: 'center' }, children: [_jsx("span", { className: "njv-code", style: { paddingLeft: (depth + 1) * 20 }, children: _jsx("span", { className: "njv-brace", children: '}' }) }), unlocked && (_jsx(ActionBtn, { label: "+ key", color: "#22c55e", onClick: (e) => { e.stopPropagation(); onAddChild(path); } }))] })] }))] }));
|
|
183
|
+
}
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
// ── Injected styles ───────────────────────────────────────────────────
|
|
187
|
+
const stylesId = 'nitrogen-json-viewer-styles';
|
|
188
|
+
function ensureStyles() {
|
|
189
|
+
if (typeof document === 'undefined')
|
|
190
|
+
return;
|
|
191
|
+
if (document.getElementById(stylesId))
|
|
192
|
+
return;
|
|
193
|
+
const style = document.createElement('style');
|
|
194
|
+
style.id = stylesId;
|
|
195
|
+
style.textContent = `
|
|
196
|
+
.njv-root {
|
|
197
|
+
--njv-bg: var(--theme-elevation-50, #f8f8f8);
|
|
198
|
+
--njv-border: var(--theme-elevation-150, #ddd);
|
|
199
|
+
--njv-toolbar-bg: var(--theme-elevation-100, #eee);
|
|
200
|
+
--njv-gutter-bg: var(--theme-elevation-50, #f0f0f0);
|
|
201
|
+
--njv-gutter-color: var(--theme-elevation-400, #999);
|
|
202
|
+
--njv-gutter-border: var(--theme-elevation-150, #ddd);
|
|
203
|
+
--njv-key: #0451a5;
|
|
204
|
+
--njv-string: #a31515;
|
|
205
|
+
--njv-number: #098658;
|
|
206
|
+
--njv-boolean: #0000ff;
|
|
207
|
+
--njv-null: #0000ff;
|
|
208
|
+
--njv-brace: var(--theme-elevation-800, #333);
|
|
209
|
+
--njv-arrow: var(--theme-elevation-500, #888);
|
|
210
|
+
--njv-summary: var(--theme-elevation-400, #888);
|
|
211
|
+
|
|
212
|
+
.collapsible__content { padding: 0; }
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
[data-theme="dark"] .njv-root {
|
|
216
|
+
--njv-key: #9cdcfe;
|
|
217
|
+
--njv-string: #ce9178;
|
|
218
|
+
--njv-number: #b5cea8;
|
|
219
|
+
--njv-boolean: #569cd6;
|
|
220
|
+
--njv-null: #569cd6;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
@media (prefers-color-scheme: dark) {
|
|
224
|
+
.njv-root:not([data-theme="light"] .njv-root) {
|
|
225
|
+
--njv-key: #9cdcfe;
|
|
226
|
+
--njv-string: #ce9178;
|
|
227
|
+
--njv-number: #b5cea8;
|
|
228
|
+
--njv-boolean: #569cd6;
|
|
229
|
+
--njv-null: #569cd6;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.njv-key { color: var(--njv-key); }
|
|
234
|
+
.njv-string { color: var(--njv-string); word-break: break-all; white-space: normal; }
|
|
235
|
+
.njv-number { color: var(--njv-number); }
|
|
236
|
+
.njv-boolean { color: var(--njv-boolean); }
|
|
237
|
+
.njv-null { color: var(--njv-null); font-style: italic; }
|
|
238
|
+
.njv-brace { color: var(--njv-brace); }
|
|
239
|
+
.njv-arrow {
|
|
240
|
+
color: var(--njv-arrow);
|
|
241
|
+
display: inline-block;
|
|
242
|
+
width: 14px;
|
|
243
|
+
font-size: 9px;
|
|
244
|
+
margin-right: 4px;
|
|
245
|
+
text-align: center;
|
|
246
|
+
}
|
|
247
|
+
.njv-summary { color: var(--njv-summary); font-style: italic; }
|
|
248
|
+
|
|
249
|
+
.njv-body { counter-reset: njv-line; }
|
|
250
|
+
|
|
251
|
+
.njv-line {
|
|
252
|
+
display: flex;
|
|
253
|
+
align-items: baseline;
|
|
254
|
+
min-height: 24px;
|
|
255
|
+
line-height: 24px;
|
|
256
|
+
counter-increment: njv-line;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.njv-line::before {
|
|
260
|
+
content: counter(njv-line);
|
|
261
|
+
flex-shrink: 0;
|
|
262
|
+
width: 44px;
|
|
263
|
+
text-align: right;
|
|
264
|
+
padding-right: 12px;
|
|
265
|
+
color: var(--njv-gutter-color);
|
|
266
|
+
background: var(--njv-gutter-bg);
|
|
267
|
+
border-right: 1px solid var(--njv-gutter-border);
|
|
268
|
+
user-select: none;
|
|
269
|
+
position: sticky;
|
|
270
|
+
left: 0;
|
|
271
|
+
z-index: 1;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.njv-code {
|
|
275
|
+
padding-left: 12px;
|
|
276
|
+
white-space: nowrap;
|
|
277
|
+
flex: 1;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
.njv-toolbar {
|
|
281
|
+
display: flex;
|
|
282
|
+
gap: 8px 12px;
|
|
283
|
+
padding: 0 12px;
|
|
284
|
+
border-bottom: 1px solid var(--njv-border);
|
|
285
|
+
background: var(--njv-toolbar-bg);
|
|
286
|
+
button { margin-block: 12px; }
|
|
287
|
+
}
|
|
288
|
+
`;
|
|
289
|
+
document.head.appendChild(style);
|
|
290
|
+
}
|
|
291
|
+
// ── Main component ────────────────────────────────────────────────────
|
|
292
|
+
const NitrogenDataViewer = ({ path, field }) => {
|
|
293
|
+
const { value, setValue } = useField({ path });
|
|
294
|
+
const [copied, setCopied] = useState(false);
|
|
295
|
+
const [forceOpen, setForceOpen] = useState(null);
|
|
296
|
+
const [treeKey, setTreeKey] = useState(0);
|
|
297
|
+
const [unlocked, setUnlocked] = useState(false);
|
|
298
|
+
ensureStyles();
|
|
299
|
+
let parsed = [];
|
|
300
|
+
try {
|
|
301
|
+
parsed = typeof value === 'string' ? JSON.parse(value) : value;
|
|
302
|
+
}
|
|
303
|
+
catch {
|
|
304
|
+
parsed = [];
|
|
305
|
+
}
|
|
306
|
+
const jsonString = value != null ? JSON.stringify(value, null, 2) : '[]';
|
|
307
|
+
const moduleCount = Array.isArray(parsed) ? parsed.length : null;
|
|
308
|
+
const handleCopy = useCallback(() => {
|
|
309
|
+
navigator.clipboard.writeText(jsonString).then(() => {
|
|
310
|
+
setCopied(true);
|
|
311
|
+
setTimeout(() => setCopied(false), 2000);
|
|
312
|
+
});
|
|
313
|
+
}, [jsonString]);
|
|
314
|
+
const expandAll = useCallback(() => {
|
|
315
|
+
setForceOpen(true);
|
|
316
|
+
setTreeKey((k) => k + 1);
|
|
317
|
+
}, []);
|
|
318
|
+
const collapseAll = useCallback(() => {
|
|
319
|
+
setForceOpen(false);
|
|
320
|
+
setTreeKey((k) => k + 1);
|
|
321
|
+
}, []);
|
|
322
|
+
const callbacks = {
|
|
323
|
+
onUpdate: (p, v) => setValue(updateAtPath(parsed, p, v)),
|
|
324
|
+
onDelete: (p) => setValue(deleteAtPath(parsed, p)),
|
|
325
|
+
onRenameKey: (p, newKey) => setValue(renameKeyAtPath(parsed, p, newKey)),
|
|
326
|
+
onAddChild: (p) => setValue(addChildAtPath(parsed, p)),
|
|
327
|
+
};
|
|
328
|
+
const header = (_jsxs("span", { children: [typeof field.label === 'string' ? field.label : field.name, moduleCount !== null && (_jsxs("span", { style: { opacity: 0.5, fontWeight: 400, marginLeft: 8 }, children: ["(", moduleCount, " item", moduleCount !== 1 ? 's' : '', ")"] }))] }));
|
|
329
|
+
return (_jsxs("div", { className: "njv-root field-type", style: { marginBottom: '1.5rem' }, children: [_jsx(Collapsible, { header: header, initCollapsed: true, children: _jsxs("div", { style: { border: '1px solid var(--njv-border)', borderRadius: '0 0 4px 4px', overflow: 'hidden' }, children: [_jsxs("div", { className: "njv-toolbar", children: [_jsx("button", { type: "button", className: "btn btn--size-small btn--style-secondary", onClick: handleCopy, children: copied ? '✓ Copied' : 'Copy' }), _jsx("button", { type: "button", className: "btn btn--size-small btn--style-secondary", onClick: expandAll, children: "Expand All" }), _jsx("button", { type: "button", className: "btn btn--size-small btn--style-secondary", onClick: collapseAll, children: "Collapse All" }), _jsx("button", { type: "button", onClick: () => setUnlocked((u) => !u), style: {
|
|
330
|
+
marginBlock: '12px',
|
|
331
|
+
marginLeft: 'auto',
|
|
332
|
+
padding: '4px 12px',
|
|
333
|
+
fontSize: '12px',
|
|
334
|
+
background: unlocked ? '#ef4444' : '#6b7280',
|
|
335
|
+
color: '#fff',
|
|
336
|
+
border: 'none',
|
|
337
|
+
borderRadius: '4px',
|
|
338
|
+
cursor: 'pointer',
|
|
339
|
+
}, children: unlocked ? 'Lock' : 'Unlock Edit' })] }), _jsx("div", { className: "njv-body", style: {
|
|
340
|
+
maxHeight: '50vh',
|
|
341
|
+
overflow: 'auto',
|
|
342
|
+
fontFamily: 'var(--font-mono, "SF Mono", Menlo, Consolas, monospace)',
|
|
343
|
+
fontSize: 'var(--font-body-size, 13px)',
|
|
344
|
+
backgroundColor: 'var(--njv-bg)',
|
|
345
|
+
}, children: _jsx(JsonNode, { value: parsed ?? [], depth: 0, forceOpen: forceOpen, path: [], unlocked: unlocked, callbacks: callbacks }, treeKey) })] }) }), typeof field.admin?.description === 'string' && (_jsx("p", { style: { fontSize: '12px', color: '#888', marginTop: '4px' }, children: field.admin.description }))] }));
|
|
346
|
+
};
|
|
347
|
+
export default NitrogenDataViewer;
|
|
@@ -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 =
|
|
71
|
-
collection,
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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(
|
|
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
|
|
131
|
-
collection,
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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(
|
|
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
|
|
153
|
-
collection,
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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(
|
|
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 });
|
|
@@ -38,6 +38,9 @@ export const NitrogenSettings = {
|
|
|
38
38
|
type: 'json',
|
|
39
39
|
admin: {
|
|
40
40
|
description: 'Full Nitrogen configuration JSON (colors, variables, CSS injection, URL maps, etc.)',
|
|
41
|
+
components: {
|
|
42
|
+
Field: '@nitrogenbuilder/connector-payload/components/NitrogenDataViewer',
|
|
43
|
+
},
|
|
41
44
|
},
|
|
42
45
|
},
|
|
43
46
|
],
|
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.
|
|
3
|
+
"version": "0.1.14",
|
|
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",
|
|
@@ -24,6 +24,11 @@
|
|
|
24
24
|
"files": [
|
|
25
25
|
"dist"
|
|
26
26
|
],
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "tsc",
|
|
29
|
+
"typecheck": "tsc --noEmit",
|
|
30
|
+
"prepublishOnly": "pnpm build"
|
|
31
|
+
},
|
|
27
32
|
"peerDependencies": {
|
|
28
33
|
"payload": "^3.0.0",
|
|
29
34
|
"next": "^15.0.0",
|
|
@@ -41,8 +46,7 @@
|
|
|
41
46
|
"@nitrogenbuilder/client-core": "link:../monogen/packages/client-core",
|
|
42
47
|
"@nitrogenbuilder/types": "link:../monogen/packages/types"
|
|
43
48
|
},
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"typecheck": "tsc --noEmit"
|
|
49
|
+
"pnpm": {
|
|
50
|
+
"onlyBuiltDependencies": ["esbuild", "sharp"]
|
|
47
51
|
}
|
|
48
|
-
}
|
|
52
|
+
}
|