@nitrogenbuilder/connector-payload 0.1.11 → 0.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -1,39 +1,39 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @nitrogenbuilder/connector-payload
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Payload CMS 3.x plugin for visual page building with Nitrogen.
|
|
4
4
|
|
|
5
|
-
##
|
|
6
|
-
|
|
7
|
-
This project uses the `nitrogen-connector-payload` plugin for visual page building with Nitrogen. Here's how to install it in a fresh Payload CMS project:
|
|
8
|
-
|
|
9
|
-
### 1. Install Dependencies
|
|
5
|
+
## Install
|
|
10
6
|
|
|
11
7
|
```bash
|
|
12
|
-
pnpm add
|
|
13
|
-
pnpm add @nitrogenbuilder/client-react @nitrogenbuilder/client-core @nitrogenbuilder/types
|
|
8
|
+
pnpm add @nitrogenbuilder/connector-payload
|
|
14
9
|
```
|
|
15
10
|
|
|
16
|
-
|
|
11
|
+
## Setup
|
|
17
12
|
|
|
18
|
-
|
|
13
|
+
### 1. Add the plugin to your Payload config
|
|
19
14
|
|
|
20
15
|
```ts
|
|
21
|
-
import { nitrogenConnectorPlugin } from
|
|
22
|
-
|
|
23
|
-
export
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
]
|
|
16
|
+
import { nitrogenConnectorPlugin } from '@nitrogenbuilder/connector-payload'
|
|
17
|
+
|
|
18
|
+
export default buildConfig({
|
|
19
|
+
plugins: [
|
|
20
|
+
nitrogenConnectorPlugin({
|
|
21
|
+
collections: ['pages', 'posts'], // collections to enable Nitrogen editing on
|
|
22
|
+
}),
|
|
23
|
+
],
|
|
24
|
+
})
|
|
29
25
|
```
|
|
30
26
|
|
|
31
|
-
|
|
27
|
+
The plugin automatically:
|
|
28
|
+
- Injects `nitrogenData` and `pageSettings` JSON fields into each collection
|
|
29
|
+
- Adds an "Edit in Nitrogen" button to each document in the Payload admin
|
|
30
|
+
- Registers the `NitrogenTemplates` collection and `NitrogenSettings` global
|
|
31
|
+
- Creates all required API endpoints under `/api/nitrogen/v1/`
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
### 2. Wrap your page routes
|
|
34
34
|
|
|
35
35
|
```tsx
|
|
36
|
-
import { NitrogenWrapper } from '
|
|
36
|
+
import { NitrogenWrapper } from '@nitrogenbuilder/connector-payload/frontend'
|
|
37
37
|
|
|
38
38
|
export default async function Page({ params, searchParams }) {
|
|
39
39
|
const page = await queryPage(...)
|
|
@@ -41,7 +41,6 @@ export default async function Page({ params, searchParams }) {
|
|
|
41
41
|
|
|
42
42
|
return (
|
|
43
43
|
<NitrogenWrapper page={page} searchParams={search} collection="pages">
|
|
44
|
-
{/* Your regular page content */}
|
|
45
44
|
<RenderHero {...page.hero} />
|
|
46
45
|
<RenderBlocks blocks={page.layout} />
|
|
47
46
|
</NitrogenWrapper>
|
|
@@ -49,53 +48,66 @@ export default async function Page({ params, searchParams }) {
|
|
|
49
48
|
}
|
|
50
49
|
```
|
|
51
50
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
- Detects when the Nitrogen editor is requesting the page (via `?nitrogen-builder` query param)
|
|
55
|
-
- Checks if the page has Nitrogen data
|
|
56
|
-
- Renders the Nitrogen visual builder when needed, otherwise renders your regular content
|
|
51
|
+
`NitrogenWrapper` detects when the Nitrogen editor is loading the page (via `?nitrogen-builder` query param) and renders the visual builder. Otherwise it renders your normal content.
|
|
57
52
|
|
|
58
|
-
###
|
|
53
|
+
### 3. Register components
|
|
59
54
|
|
|
60
|
-
|
|
55
|
+
Create a client component that registers your Nitrogen components:
|
|
61
56
|
|
|
62
57
|
```tsx
|
|
63
58
|
// src/components/NitrogenComponents.tsx
|
|
64
59
|
"use client";
|
|
65
60
|
|
|
66
|
-
import { nitrogen } from
|
|
67
|
-
import type {
|
|
68
|
-
|
|
69
|
-
ComponentSettingsToProps,
|
|
70
|
-
} from "nitrogen-connector-payload/frontend";
|
|
71
|
-
import MyComponent from "./MyComponent";
|
|
61
|
+
import { nitrogen } from '@nitrogenbuilder/connector-payload'
|
|
62
|
+
import type { ComponentSettings, ComponentSettingsToProps } from '@nitrogenbuilder/connector-payload'
|
|
63
|
+
import MyComponent from './MyComponent'
|
|
72
64
|
|
|
73
65
|
const myComponentSettings = {
|
|
74
66
|
categories: {
|
|
75
67
|
content: {
|
|
76
|
-
label:
|
|
68
|
+
label: 'Content',
|
|
77
69
|
groups: {
|
|
78
70
|
content: {
|
|
79
|
-
label:
|
|
71
|
+
label: 'Content',
|
|
80
72
|
props: {
|
|
81
|
-
title: { type:
|
|
73
|
+
title: { type: 'string', default: 'Hello' },
|
|
82
74
|
},
|
|
83
75
|
},
|
|
84
76
|
},
|
|
85
77
|
},
|
|
86
78
|
},
|
|
87
|
-
} as const satisfies ComponentSettings
|
|
79
|
+
} as const satisfies ComponentSettings
|
|
88
80
|
|
|
89
|
-
|
|
90
|
-
nitrogen.registerModule("my-component", MyComponent, myComponentSettings);
|
|
81
|
+
nitrogen.registerModule('my-component', MyComponent, myComponentSettings)
|
|
91
82
|
|
|
92
|
-
export {}
|
|
83
|
+
export {}
|
|
93
84
|
```
|
|
94
85
|
|
|
95
86
|
Then import it in your page route:
|
|
96
87
|
|
|
97
88
|
```tsx
|
|
98
|
-
import
|
|
89
|
+
import '@/components/NitrogenComponents'
|
|
99
90
|
```
|
|
100
91
|
|
|
101
|
-
|
|
92
|
+
## Plugin Options
|
|
93
|
+
|
|
94
|
+
| Option | Type | Description |
|
|
95
|
+
|--------|------|-------------|
|
|
96
|
+
| `collections` | `string[]` | Slugs of existing Payload collections to enable Nitrogen editing on |
|
|
97
|
+
| `disabled` | `boolean` | Disable the plugin without removing it from config |
|
|
98
|
+
|
|
99
|
+
## Exports
|
|
100
|
+
|
|
101
|
+
| Export | Description |
|
|
102
|
+
|--------|-------------|
|
|
103
|
+
| `nitrogenConnectorPlugin` | The Payload plugin |
|
|
104
|
+
| `NitrogenWrapper` | Frontend wrapper component (from `/frontend`) |
|
|
105
|
+
| `NitrogenPageClient` | Low-level client component (from `/frontend`) |
|
|
106
|
+
| `nitrogen` | Re-export of `@nitrogenbuilder/client-core` |
|
|
107
|
+
| `ComponentSettings` | Type for component settings definitions |
|
|
108
|
+
| `ComponentSettingsToProps` | Type helper to derive props from settings |
|
|
109
|
+
| `NitrogenEditButton` | Admin UI button component |
|
|
110
|
+
| `NitrogenViewButton` | Admin UI view button component |
|
|
111
|
+
| `createCollectionEndpoints` | Factory for generating collection API endpoints |
|
|
112
|
+
| `buildDynamicData` | Helper for building dynamic data in page routes |
|
|
113
|
+
| `getNitrogenSettings` | Helper for fetching Nitrogen global settings |
|
|
@@ -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;
|
|
@@ -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,7 +6,6 @@ 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 { collectionPickerEndpoints } from "./endpoints/collection-picker";
|
|
10
9
|
import { registerCollection } from "./collection-registry";
|
|
11
10
|
/** Fields required by Nitrogen that will be injected into collections if missing */
|
|
12
11
|
const nitrogenRequiredFields = [
|
|
@@ -48,7 +47,6 @@ export const nitrogenConnectorPlugin = (options = {}) => (incomingConfig) => {
|
|
|
48
47
|
...nitrogenSettingsEndpoints,
|
|
49
48
|
...allEndpoints,
|
|
50
49
|
...menuEndpoints,
|
|
51
|
-
...collectionPickerEndpoints,
|
|
52
50
|
];
|
|
53
51
|
// Register templates collection
|
|
54
52
|
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.13",
|
|
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
|
+
}
|