@nitrogenbuilder/connector-payload 0.1.33 → 0.1.35

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.
Files changed (39) hide show
  1. package/dist/collections/NitrogenComponentCatalog.d.ts +2 -0
  2. package/dist/collections/NitrogenComponentCatalog.js +44 -0
  3. package/dist/collections/NitrogenComponentUsage.d.ts +2 -0
  4. package/dist/collections/NitrogenComponentUsage.js +81 -0
  5. package/dist/components/LockedJsonField.d.ts +2 -9
  6. package/dist/components/LockedJsonField.js +7 -6
  7. package/dist/components/LockedJsonFieldRuntime.d.ts +3 -7
  8. package/dist/components/LockedJsonFieldRuntime.js +21 -22
  9. package/dist/components/NitrogenComponentInventoryClient.d.ts +13 -0
  10. package/dist/components/NitrogenComponentInventoryClient.js +309 -0
  11. package/dist/components/NitrogenComponentInventoryField.d.ts +3 -0
  12. package/dist/components/NitrogenComponentInventoryField.js +26 -0
  13. package/dist/components/NitrogenComponentInventoryNavLink.d.ts +1 -0
  14. package/dist/components/NitrogenComponentInventoryNavLink.js +75 -0
  15. package/dist/components/NitrogenComponentInventoryReindexButton.d.ts +1 -0
  16. package/dist/components/NitrogenComponentInventoryReindexButton.js +91 -0
  17. package/dist/components/NitrogenComponentInventoryView.d.ts +2 -0
  18. package/dist/components/NitrogenComponentInventoryView.js +28 -0
  19. package/dist/components/NitrogenDataViewer.d.ts +2 -2
  20. package/dist/components/NitrogenDataViewer.js +7 -6
  21. package/dist/components/NitrogenDataViewerRuntime.d.ts +6 -2
  22. package/dist/components/NitrogenDataViewerRuntime.js +24 -40
  23. package/dist/components/NitrogenNavGroup.d.ts +1 -0
  24. package/dist/components/NitrogenNavGroup.js +74 -0
  25. package/dist/editor/NitrogenEditorPage.d.ts +1 -2
  26. package/dist/endpoints/collection-endpoints.js +50 -5
  27. package/dist/endpoints/component-inventory.d.ts +6 -0
  28. package/dist/endpoints/component-inventory.js +20 -0
  29. package/dist/frontend/NitrogenWrapper.d.ts +1 -2
  30. package/dist/globals/NitrogenComponents.d.ts +2 -0
  31. package/dist/globals/NitrogenComponents.js +22 -0
  32. package/dist/index.d.ts +12 -2
  33. package/dist/index.js +94 -3
  34. package/dist/inventory/indexing.d.ts +31 -0
  35. package/dist/inventory/indexing.js +230 -0
  36. package/dist/inventory/manifest.d.ts +3 -0
  37. package/dist/inventory/manifest.js +22 -0
  38. package/dist/types.d.ts +35 -1
  39. package/package.json +7 -2
@@ -0,0 +1,75 @@
1
+ 'use client';
2
+ import { usePathname } from 'next/navigation';
3
+ import { useEffect } from 'react';
4
+ const groupSelector = '#nav-group-Nitrogen .nav-group__content';
5
+ export default function NitrogenComponentInventoryNavLink() {
6
+ const pathname = usePathname();
7
+ const isActive = pathname === '/admin/nitrogen/components' || pathname?.startsWith('/admin/nitrogen/components/');
8
+ useEffect(() => {
9
+ const linkId = 'nitrogen-components-nav-link';
10
+ const renderLink = () => {
11
+ const target = document.querySelector(groupSelector);
12
+ if (!target) {
13
+ return;
14
+ }
15
+ const existing = document.getElementById(linkId);
16
+ if (existing) {
17
+ existing.remove();
18
+ }
19
+ const link = document.createElement('a');
20
+ link.id = linkId;
21
+ link.href = '/admin/nitrogen/components';
22
+ link.textContent = 'Nitrogen Components';
23
+ link.style.alignItems = 'center';
24
+ link.style.color = 'inherit';
25
+ link.style.display = 'flex';
26
+ link.style.fontSize = '18px';
27
+ link.style.fontWeight = isActive ? '700' : '500';
28
+ link.style.gap = '12px';
29
+ link.style.minHeight = '40px';
30
+ link.style.opacity = isActive ? '1' : '0.92';
31
+ link.style.padding = '6px 0';
32
+ link.style.textDecoration = 'none';
33
+ const icon = document.createElement('span');
34
+ icon.ariaHidden = 'true';
35
+ icon.style.color = '#6b7280';
36
+ icon.style.display = 'inline-flex';
37
+ icon.style.fontSize = '16px';
38
+ icon.style.justifyContent = 'center';
39
+ icon.style.width = '20px';
40
+ icon.innerHTML = `
41
+ <svg fill="none" height="20" viewBox="0 0 20 20" width="20" xmlns="http://www.w3.org/2000/svg">
42
+ <path
43
+ d="M4 5.5C4 4.67157 4.67157 4 5.5 4H14.5C15.3284 4 16 4.67157 16 5.5V14.5C16 15.3284 15.3284 16 14.5 16H5.5C4.67157 16 4 15.3284 4 14.5V5.5Z"
44
+ stroke="currentColor"
45
+ stroke-width="1.5"
46
+ />
47
+ <path d="M7 8H13" stroke="currentColor" stroke-linecap="round" stroke-width="1.5" />
48
+ <path d="M7 10.5H13" stroke="currentColor" stroke-linecap="round" stroke-width="1.5" />
49
+ <path d="M7 13H10.5" stroke="currentColor" stroke-linecap="round" stroke-width="1.5" />
50
+ </svg>
51
+ `;
52
+ const label = document.createElement('span');
53
+ label.textContent = 'Nitrogen Components';
54
+ link.textContent = '';
55
+ link.append(icon, label);
56
+ if (isActive) {
57
+ link.setAttribute('aria-current', 'page');
58
+ }
59
+ target.append(link);
60
+ };
61
+ renderLink();
62
+ const observer = new MutationObserver(() => {
63
+ renderLink();
64
+ });
65
+ observer.observe(document.body, {
66
+ childList: true,
67
+ subtree: true,
68
+ });
69
+ return () => {
70
+ observer.disconnect();
71
+ document.getElementById(linkId)?.remove();
72
+ };
73
+ }, [isActive]);
74
+ return null;
75
+ }
@@ -0,0 +1 @@
1
+ export default function NitrogenComponentInventoryReindexButton(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,91 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useState } from 'react';
4
+ const statusStorageKey = 'nitrogen-component-inventory:last-reindex-status';
5
+ export default function NitrogenComponentInventoryReindexButton() {
6
+ const [loading, setLoading] = useState(false);
7
+ const [status, setStatus] = useState(null);
8
+ useEffect(() => {
9
+ const rawValue = window.sessionStorage.getItem(statusStorageKey);
10
+ if (!rawValue) {
11
+ return;
12
+ }
13
+ try {
14
+ setStatus(JSON.parse(rawValue));
15
+ }
16
+ catch {
17
+ window.sessionStorage.removeItem(statusStorageKey);
18
+ }
19
+ }, []);
20
+ function persistStatus(nextStatus) {
21
+ setStatus(nextStatus);
22
+ window.sessionStorage.setItem(statusStorageKey, JSON.stringify(nextStatus));
23
+ }
24
+ async function handleClick() {
25
+ try {
26
+ setLoading(true);
27
+ const response = await fetch('/api/nitrogen/v1/component-inventory/reindex', {
28
+ method: 'POST',
29
+ });
30
+ const data = await response.json();
31
+ if (!response.ok) {
32
+ persistStatus({
33
+ kind: 'error',
34
+ message: data?.error || 'Failed to reindex component inventory.',
35
+ timestamp: new Date().toISOString(),
36
+ });
37
+ return;
38
+ }
39
+ persistStatus({
40
+ kind: 'success',
41
+ message: `Reindexed ${data.documentCount} documents and ${data.usageCount} component usages.`,
42
+ timestamp: new Date().toISOString(),
43
+ });
44
+ window.location.reload();
45
+ }
46
+ catch (error) {
47
+ console.error(error);
48
+ persistStatus({
49
+ kind: 'error',
50
+ message: 'Failed to reindex component inventory.',
51
+ timestamp: new Date().toISOString(),
52
+ });
53
+ }
54
+ finally {
55
+ setLoading(false);
56
+ }
57
+ }
58
+ function clearStatus() {
59
+ setStatus(null);
60
+ window.sessionStorage.removeItem(statusStorageKey);
61
+ }
62
+ return (_jsxs("div", { style: { display: 'grid', gap: 12 }, children: [_jsx("div", { style: { display: 'flex', gap: 12, alignItems: 'center', flexWrap: 'wrap' }, children: _jsx("button", { type: "button", onClick: handleClick, disabled: loading, style: {
63
+ border: 'none',
64
+ borderRadius: 6,
65
+ background: '#171717',
66
+ color: '#fff',
67
+ cursor: loading ? 'wait' : 'pointer',
68
+ fontSize: 14,
69
+ fontWeight: 600,
70
+ padding: '10px 14px',
71
+ }, children: loading ? 'Reindexing...' : 'Reindex Components' }) }), status ? (_jsxs("div", { style: {
72
+ alignItems: 'start',
73
+ background: status.kind === 'success' ? '#ecfdf5' : '#fef2f2',
74
+ border: `1px solid ${status.kind === 'success' ? '#a7f3d0' : '#fecaca'}`,
75
+ borderRadius: 10,
76
+ color: status.kind === 'success' ? '#065f46' : '#991b1b',
77
+ display: 'grid',
78
+ gap: 6,
79
+ gridTemplateColumns: '1fr auto',
80
+ padding: '12px 14px',
81
+ }, children: [_jsxs("div", { children: [_jsx("div", { style: { fontSize: 13, fontWeight: 700 }, children: status.kind === 'success' ? 'Last Reindex Completed' : 'Reindex Failed' }), _jsx("div", { style: { fontSize: 13, marginTop: 2 }, children: status.message }), _jsx("div", { style: { fontSize: 12, marginTop: 4, opacity: 0.75 }, children: new Date(status.timestamp).toLocaleString() })] }), _jsx("button", { type: "button", onClick: clearStatus, style: {
82
+ appearance: 'none',
83
+ background: 'transparent',
84
+ border: 'none',
85
+ color: 'inherit',
86
+ cursor: 'pointer',
87
+ fontSize: 12,
88
+ fontWeight: 700,
89
+ padding: 0,
90
+ }, children: "Dismiss" })] })) : null] }));
91
+ }
@@ -0,0 +1,2 @@
1
+ import type { AdminViewServerProps } from 'payload';
2
+ export default function NitrogenComponentInventoryView({ clientConfig, initPageResult, i18n, locale, params, searchParams, viewActions, viewType, }: AdminViewServerProps): Promise<import("react/jsx-runtime").JSX.Element>;
@@ -0,0 +1,28 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // @ts-expect-error The consuming Payload app provides @payloadcms/next at runtime.
3
+ import { DefaultTemplate } from '@payloadcms/next/templates';
4
+ import { findAllDocs, NITROGEN_COMPONENT_CATALOG_COLLECTION, NITROGEN_COMPONENT_USAGE_COLLECTION, } from '../inventory/indexing';
5
+ import NitrogenComponentInventoryClient from './NitrogenComponentInventoryClient';
6
+ import NitrogenComponentInventoryReindexButton from './NitrogenComponentInventoryReindexButton';
7
+ export default async function NitrogenComponentInventoryView({ clientConfig, initPageResult, i18n, locale, params, searchParams, viewActions, viewType, }) {
8
+ const payload = initPageResult.req.payload;
9
+ const [catalogDocs, usageDocs] = await Promise.all([
10
+ findAllDocs(payload, NITROGEN_COMPONENT_CATALOG_COLLECTION),
11
+ findAllDocs(payload, NITROGEN_COMPONENT_USAGE_COLLECTION),
12
+ ]);
13
+ const catalogByName = new Map(catalogDocs.map((doc) => [doc.componentName, doc.definition]));
14
+ const usageByName = new Map();
15
+ for (const usageDoc of usageDocs) {
16
+ const docs = usageByName.get(usageDoc.componentName) || [];
17
+ docs.push(usageDoc);
18
+ usageByName.set(usageDoc.componentName, docs);
19
+ }
20
+ const componentNames = Array.from(new Set([...catalogByName.keys(), ...usageByName.keys()])).sort((a, b) => a.localeCompare(b));
21
+ const components = componentNames.map((componentName) => ({
22
+ componentName,
23
+ definition: catalogByName.get(componentName),
24
+ isUnknown: !catalogByName.get(componentName),
25
+ usages: usageByName.get(componentName) || [],
26
+ }));
27
+ return (_jsx(DefaultTemplate, { i18n: i18n, locale: locale, params: params, payload: payload, req: initPageResult.req, searchParams: searchParams, user: initPageResult.req.user, viewActions: viewActions, viewType: viewType, visibleEntities: initPageResult.visibleEntities, children: _jsxs("div", { style: { padding: 24 }, children: [_jsxs("div", { style: { display: 'grid', gap: 12, marginBottom: 24 }, children: [_jsx("h1", { style: { margin: 0, fontSize: 28 }, children: "Nitrogen Components" }), _jsx("p", { style: { margin: 0, color: '#4b5563', maxWidth: 920 }, children: "Inventory view for the registered Nitrogen component manifest and all indexed usages across Nitrogen-enabled documents and templates." }), _jsx(NitrogenComponentInventoryReindexButton, {})] }), _jsx(NitrogenComponentInventoryClient, { adminRoute: clientConfig.routes.admin, components: components })] }) }));
28
+ }
@@ -1,3 +1,3 @@
1
- import type { JSONFieldClientComponent } from 'payload';
2
- declare const NitrogenDataViewer: JSONFieldClientComponent;
1
+ import type { JSONFieldServerComponent } from 'payload';
2
+ declare const NitrogenDataViewer: JSONFieldServerComponent;
3
3
  export default NitrogenDataViewer;
@@ -1,7 +1,8 @@
1
- 'use client';
2
- import dynamic from 'next/dynamic';
3
- const NitrogenDataViewer = dynamic(() => import('./NitrogenDataViewerRuntime'), {
4
- loading: () => null,
5
- ssr: false,
6
- });
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import NitrogenDataViewerRuntime from './NitrogenDataViewerRuntime';
3
+ const NitrogenDataViewer = ({ field, value }) => {
4
+ const label = typeof field.label === 'string' ? field.label : field.name;
5
+ const description = typeof field.admin?.description === 'string' ? field.admin.description : undefined;
6
+ return (_jsx(NitrogenDataViewerRuntime, { description: description, label: label, value: value }));
7
+ };
7
8
  export default NitrogenDataViewer;
@@ -1,3 +1,7 @@
1
- import type { JSONFieldClientComponent } from 'payload';
2
- declare const NitrogenDataViewerRuntime: JSONFieldClientComponent;
1
+ import React from 'react';
2
+ declare const NitrogenDataViewerRuntime: React.FC<{
3
+ description?: string;
4
+ label: string;
5
+ value?: unknown;
6
+ }>;
3
7
  export default NitrogenDataViewerRuntime;
@@ -1,7 +1,6 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { useState, useCallback } from 'react';
4
- import { Collapsible, useField } from '@payloadcms/ui';
5
4
  function updateAtPath(root, path, newValue) {
6
5
  if (path.length === 0)
7
6
  return newValue;
@@ -281,13 +280,11 @@ function ensureStyles() {
281
280
  `;
282
281
  document.head.appendChild(style);
283
282
  }
284
- const NitrogenDataViewerRuntime = ({ path, field }) => {
285
- const { value, setValue } = useField({ path });
283
+ const NitrogenDataViewerRuntime = ({ description, label, value }) => {
286
284
  const [copied, setCopied] = useState(false);
287
- const [pasted, setPasted] = useState(false);
285
+ const [collapsed, setCollapsed] = useState(true);
288
286
  const [forceOpen, setForceOpen] = useState(null);
289
287
  const [treeKey, setTreeKey] = useState(0);
290
- const [unlocked, setUnlocked] = useState(false);
291
288
  ensureStyles();
292
289
  let parsed = [];
293
290
  try {
@@ -304,19 +301,6 @@ const NitrogenDataViewerRuntime = ({ path, field }) => {
304
301
  setTimeout(() => setCopied(false), 2000);
305
302
  });
306
303
  }, [jsonString]);
307
- const handlePaste = useCallback(() => {
308
- navigator.clipboard.readText().then((text) => {
309
- try {
310
- const parsedValue = JSON.parse(text);
311
- setValue(parsedValue);
312
- setPasted(true);
313
- setTimeout(() => setPasted(false), 2000);
314
- }
315
- catch {
316
- // ignore invalid JSON
317
- }
318
- });
319
- }, [setValue]);
320
304
  const expandAll = useCallback(() => {
321
305
  setForceOpen(true);
322
306
  setTreeKey((k) => k + 1);
@@ -326,28 +310,28 @@ const NitrogenDataViewerRuntime = ({ path, field }) => {
326
310
  setTreeKey((k) => k + 1);
327
311
  }, []);
328
312
  const callbacks = {
329
- onUpdate: (p, v) => setValue(updateAtPath(parsed, p, v)),
330
- onDelete: (p) => setValue(deleteAtPath(parsed, p)),
331
- onRenameKey: (p, newKey) => setValue(renameKeyAtPath(parsed, p, newKey)),
332
- onAddChild: (p) => setValue(addChildAtPath(parsed, p)),
313
+ onUpdate: () => undefined,
314
+ onDelete: () => undefined,
315
+ onRenameKey: () => undefined,
316
+ onAddChild: () => undefined,
333
317
  };
334
- 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' : '', ")"] }))] }));
335
- 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' }), unlocked && (_jsx("button", { type: "button", className: "btn btn--size-small btn--style-secondary", onClick: handlePaste, children: pasted ? '✓ Pasted' : 'Paste' })), _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: {
336
- marginBlock: '12px',
337
- marginLeft: 'auto',
338
- padding: '4px 12px',
339
- fontSize: '12px',
340
- background: unlocked ? '#ef4444' : '#6b7280',
341
- color: '#fff',
342
- border: 'none',
343
- borderRadius: '4px',
344
- cursor: 'pointer',
345
- }, children: unlocked ? 'Lock' : 'Unlock Edit' })] }), _jsx("div", { className: "njv-body", style: {
346
- maxHeight: '50vh',
347
- overflow: 'auto',
348
- fontFamily: 'var(--font-mono, "SF Mono", Menlo, Consolas, monospace)',
349
- fontSize: 'var(--font-body-size, 13px)',
350
- backgroundColor: 'var(--njv-bg)',
351
- }, 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 }))] }));
318
+ const header = (_jsxs("span", { children: [label, moduleCount !== null && (_jsxs("span", { style: { opacity: 0.5, fontWeight: 400, marginLeft: 8 }, children: ["(", moduleCount, " item", moduleCount !== 1 ? 's' : '', ")"] }))] }));
319
+ return (_jsxs("div", { className: "njv-root field-type", style: { marginBottom: '1.5rem' }, children: [_jsxs("div", { style: { border: '1px solid var(--njv-border)', borderRadius: '4px', overflow: 'hidden' }, children: [_jsxs("button", { type: "button", onClick: () => setCollapsed((current) => !current), style: {
320
+ alignItems: 'center',
321
+ background: 'var(--theme-elevation-50, #f8f8f8)',
322
+ border: 'none',
323
+ borderBottom: collapsed ? 'none' : '1px solid var(--njv-border)',
324
+ cursor: 'pointer',
325
+ display: 'flex',
326
+ justifyContent: 'space-between',
327
+ padding: '12px 16px',
328
+ width: '100%',
329
+ }, children: [header, _jsx("span", { style: { fontSize: 12, opacity: 0.65 }, children: collapsed ? 'Show' : 'Hide' })] }), !collapsed && (_jsxs("div", { 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("div", { className: "njv-body", style: {
330
+ maxHeight: '50vh',
331
+ overflow: 'auto',
332
+ fontFamily: 'var(--font-mono, "SF Mono", Menlo, Consolas, monospace)',
333
+ fontSize: 'var(--font-body-size, 13px)',
334
+ backgroundColor: 'var(--njv-bg)',
335
+ }, children: _jsx(JsonNode, { value: parsed ?? [], depth: 0, forceOpen: forceOpen, path: [], unlocked: false, callbacks: callbacks }, treeKey) })] }))] }), typeof description === 'string' && (_jsx("p", { style: { fontSize: '12px', color: '#888', marginTop: '4px' }, children: description }))] }));
352
336
  };
353
337
  export default NitrogenDataViewerRuntime;
@@ -0,0 +1 @@
1
+ export default function NitrogenNavGroup(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,74 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { usePathname } from 'next/navigation';
4
+ import { useEffect, useMemo, useState } from 'react';
5
+ const items = [
6
+ {
7
+ href: '/admin/collections/nitrogen-templates',
8
+ label: 'Templates',
9
+ },
10
+ {
11
+ href: '/admin/globals/nitrogen-settings',
12
+ label: 'Settings',
13
+ },
14
+ {
15
+ href: '/admin/nitrogen/components',
16
+ label: 'Components',
17
+ },
18
+ ];
19
+ export default function NitrogenNavGroup() {
20
+ const pathname = usePathname();
21
+ const hasActiveChild = useMemo(() => items.some((item) => pathname === item.href || pathname?.startsWith(`${item.href}/`)), [pathname]);
22
+ const [collapsed, setCollapsed] = useState(!hasActiveChild);
23
+ useEffect(() => {
24
+ if (hasActiveChild) {
25
+ setCollapsed(false);
26
+ }
27
+ }, [hasActiveChild]);
28
+ useEffect(() => {
29
+ const styleId = 'nitrogen-custom-nav-group-style';
30
+ if (document.getElementById(styleId)) {
31
+ return;
32
+ }
33
+ const style = document.createElement('style');
34
+ style.id = styleId;
35
+ style.textContent = `
36
+ #nav-group-Nitrogen {
37
+ display: none;
38
+ }
39
+ `;
40
+ document.head.appendChild(style);
41
+ return () => {
42
+ style.remove();
43
+ };
44
+ }, []);
45
+ return (_jsxs("div", { style: { marginTop: 12 }, children: [_jsxs("button", { type: "button", onClick: () => setCollapsed((current) => !current), style: {
46
+ alignItems: 'center',
47
+ background: 'transparent',
48
+ border: 'none',
49
+ color: 'inherit',
50
+ cursor: 'pointer',
51
+ display: 'flex',
52
+ font: 'inherit',
53
+ justifyContent: 'space-between',
54
+ padding: '6px 0',
55
+ width: '100%',
56
+ }, children: [_jsx("span", { style: { fontSize: 13, fontWeight: 700, letterSpacing: '0.01em' }, children: "Nitrogen" }), _jsx("span", { "aria-hidden": "true", style: {
57
+ display: 'inline-block',
58
+ fontSize: 12,
59
+ opacity: 0.65,
60
+ transform: collapsed ? 'rotate(-90deg)' : 'rotate(0deg)',
61
+ transition: 'transform 160ms ease',
62
+ }, children: "\u25BE" })] }), !collapsed && (_jsx("div", { style: { display: 'grid', gap: 2, paddingBottom: 4, paddingLeft: 12 }, children: items.map((item) => {
63
+ const isActive = pathname === item.href || pathname?.startsWith(`${item.href}/`);
64
+ return (_jsx("a", { "aria-current": isActive ? 'page' : undefined, href: item.href, style: {
65
+ borderLeft: isActive ? '2px solid currentColor' : '2px solid transparent',
66
+ display: 'block',
67
+ fontSize: 13,
68
+ fontWeight: isActive ? 700 : 500,
69
+ opacity: isActive ? 1 : 0.82,
70
+ padding: '6px 0 6px 10px',
71
+ textDecoration: 'none',
72
+ }, children: item.label }, item.href));
73
+ }) }))] }));
74
+ }
@@ -1,4 +1,3 @@
1
- import type { SanitizedConfig } from "payload";
2
1
  interface NitrogenEditorSearchParams {
3
2
  pageId?: string;
4
3
  collection?: string;
@@ -16,7 +15,7 @@ interface NitrogenEditorSearchParams {
16
15
  *
17
16
  * Accessible at `/nitrogen-editor?pageId=xxx`.
18
17
  */
19
- export declare function createNitrogenEditorPage(config: Promise<SanitizedConfig>): ({ searchParams, }: {
18
+ export declare function createNitrogenEditorPage(config: Promise<any>): ({ searchParams, }: {
20
19
  searchParams: Promise<NitrogenEditorSearchParams>;
21
20
  }) => Promise<import("react/jsx-runtime").JSX.Element>;
22
21
  export {};
@@ -103,7 +103,33 @@ export function createCollectionEndpoints(collectionSlug, endpointPrefix) {
103
103
  if (!data?.title) {
104
104
  return Response.json({ error: 'Title is required' }, { status: 400 });
105
105
  }
106
+ let existingDoc = null;
107
+ try {
108
+ existingDoc = (await payload.findByID({
109
+ collection,
110
+ id,
111
+ depth: 0,
112
+ disableErrors: true,
113
+ }));
114
+ }
115
+ catch {
116
+ existingDoc = null;
117
+ }
106
118
  const updateData = { title: data.title };
119
+ if (existingDoc) {
120
+ if ('slug' in existingDoc && existingDoc.slug !== undefined) {
121
+ updateData.slug = existingDoc.slug;
122
+ }
123
+ if ('generateSlug' in existingDoc) {
124
+ updateData.generateSlug = false;
125
+ }
126
+ if ('parent' in existingDoc) {
127
+ updateData.parent = existingDoc.parent;
128
+ }
129
+ if ('breadcrumbs' in existingDoc) {
130
+ updateData.breadcrumbs = [];
131
+ }
132
+ }
107
133
  if (data.author) {
108
134
  updateData.author = data.author;
109
135
  }
@@ -114,11 +140,30 @@ export function createCollectionEndpoints(collectionSlug, endpointPrefix) {
114
140
  if (data.settings !== undefined) {
115
141
  updateData.pageSettings = data.settings;
116
142
  }
117
- await payload.update({
118
- collection,
119
- id,
120
- data: updateData,
121
- });
143
+ try {
144
+ await payload.update({
145
+ collection,
146
+ id,
147
+ data: updateData,
148
+ depth: 0,
149
+ overrideAccess: true,
150
+ req,
151
+ });
152
+ }
153
+ catch (error) {
154
+ payload.logger.error({
155
+ msg: 'Nitrogen page update failed',
156
+ collection,
157
+ id,
158
+ error,
159
+ });
160
+ return Response.json({
161
+ error: error instanceof Error ? error.message : 'Failed to update page',
162
+ details: error && typeof error === 'object' && 'data' in error
163
+ ? error.data
164
+ : undefined,
165
+ }, { status: 400 });
166
+ }
122
167
  return Response.json({ success: true });
123
168
  },
124
169
  },
@@ -0,0 +1,6 @@
1
+ import type { Endpoint } from 'payload';
2
+ import type { NitrogenComponentManifestInput } from '../inventory/manifest';
3
+ export declare function createComponentInventoryEndpoints(args: {
4
+ collections: string[];
5
+ componentManifest?: NitrogenComponentManifestInput;
6
+ }): Endpoint[];
@@ -0,0 +1,20 @@
1
+ import { requireAuth } from './helpers';
2
+ import { reindexAllComponentInventory } from '../inventory/indexing';
3
+ export function createComponentInventoryEndpoints(args) {
4
+ return [
5
+ {
6
+ path: '/nitrogen/v1/component-inventory/reindex',
7
+ method: 'post',
8
+ handler: async (req) => {
9
+ const authError = requireAuth(req);
10
+ if (authError)
11
+ return authError;
12
+ const result = await reindexAllComponentInventory(req.payload, args.collections, args.componentManifest);
13
+ return Response.json({
14
+ success: true,
15
+ ...result,
16
+ });
17
+ },
18
+ },
19
+ ];
20
+ }
@@ -1,5 +1,4 @@
1
1
  import React from 'react';
2
- import type { SanitizedConfig } from 'payload';
3
2
  import type { BuilderModule } from '@nitrogenbuilder/types';
4
3
  export interface NitrogenWrapperProps {
5
4
  /**
@@ -21,7 +20,7 @@ export interface NitrogenWrapperProps {
21
20
  /**
22
21
  * The Payload config promise. Import from '@payload-config' in your app and pass it here.
23
22
  */
24
- config: Promise<SanitizedConfig>;
23
+ config: Promise<any>;
25
24
  /**
26
25
  * Regular page content to render when not in Nitrogen mode.
27
26
  */
@@ -0,0 +1,2 @@
1
+ import type { GlobalConfig } from 'payload';
2
+ export declare const NitrogenComponents: GlobalConfig;
@@ -0,0 +1,22 @@
1
+ export const NitrogenComponents = {
2
+ slug: 'nitrogen-components',
3
+ label: 'Nitrogen Components',
4
+ admin: {
5
+ group: 'Nitrogen',
6
+ },
7
+ access: {
8
+ read: ({ req }) => !!req.user,
9
+ update: ({ req }) => !!req.user,
10
+ },
11
+ fields: [
12
+ {
13
+ name: 'inventory',
14
+ type: 'ui',
15
+ admin: {
16
+ components: {
17
+ Field: '@nitrogenbuilder/connector-payload/components/NitrogenComponentInventoryField',
18
+ },
19
+ },
20
+ },
21
+ ],
22
+ };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Plugin } from "payload";
1
+ import type { NitrogenComponentManifestInput } from './inventory/manifest';
2
2
  export interface NitrogenConnectorPluginOptions {
3
3
  /** Disable the plugin without removing it from config */
4
4
  disabled?: boolean;
@@ -18,8 +18,18 @@ export interface NitrogenConnectorPluginOptions {
18
18
  * `collectionRoutes: { posts: '/blog/[slug]', patent: '/patents/[slug]' }`
19
19
  */
20
20
  collectionRoutes?: Record<string, string>;
21
+ /**
22
+ * A static manifest or async loader describing the project's registered
23
+ * Nitrogen components and their prop schemas.
24
+ */
25
+ componentManifest?: NitrogenComponentManifestInput;
26
+ /**
27
+ * Collections whose saved `nitrogenData` should be indexed for the component
28
+ * inventory. These do not need to be editor-enabled.
29
+ */
30
+ indexCollections?: string[];
21
31
  }
22
- export declare const nitrogenConnectorPlugin: (options?: NitrogenConnectorPluginOptions) => Plugin;
32
+ export declare const nitrogenConnectorPlugin: (options?: NitrogenConnectorPluginOptions) => any;
23
33
  export { NitrogenTemplates } from "./collections/NitrogenTemplates";
24
34
  export { NitrogenSettings } from "./globals/NitrogenSettings";
25
35
  export { NitrogenEditButton } from "./components/NitrogenEditButton";