payload-nested-docs-page-tree 1.5.0 → 1.6.0
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 +148 -11
- package/dist/components/PageTreeListView.client.js +9 -4
- package/dist/components/PageTreeListView.client.js.map +1 -1
- package/dist/components/PageTreeListView.module.css +55 -8
- package/dist/components/PageTreeListView.server.js +30 -14
- package/dist/components/PageTreeListView.server.js.map +1 -1
- package/dist/components/PageTreeLocaleStatusBadges.d.ts +7 -0
- package/dist/components/PageTreeLocaleStatusBadges.js +79 -0
- package/dist/components/PageTreeLocaleStatusBadges.js.map +1 -0
- package/dist/components/PageTreeStatusBadge.d.ts +2 -1
- package/dist/components/PageTreeStatusBadge.js +37 -7
- package/dist/components/PageTreeStatusBadge.js.map +1 -1
- package/dist/components/PageTreeTitleCell.js +12 -10
- package/dist/components/PageTreeTitleCell.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +8 -4
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +49 -8
- package/dist/types.js.map +1 -1
- package/dist/utilities/badgeConfig.js +3 -0
- package/dist/utilities/badgeConfig.js.map +1 -1
- package/dist/utilities/badgeLinks.d.ts +1 -1
- package/dist/utilities/badgeLinks.js +13 -3
- package/dist/utilities/badgeLinks.js.map +1 -1
- package/dist/utilities/localeStatus.d.ts +11 -0
- package/dist/utilities/localeStatus.js +83 -0
- package/dist/utilities/localeStatus.js.map +1 -0
- package/dist/utilities/pageTree.js +1 -1
- package/dist/utilities/pageTree.js.map +1 -1
- package/dist/utilities/pageTreeConfig.d.ts +1 -1
- package/dist/utilities/pageTreeConfig.js +2 -0
- package/dist/utilities/pageTreeConfig.js.map +1 -1
- package/dist/utilities/status.d.ts +2 -2
- package/dist/utilities/status.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,6 +5,7 @@ Page management tools for Payload admin, built on [`@payloadcms/plugin-nested-do
|
|
|
5
5
|
- **Page tree UI** with hierarchy and page URL paths.
|
|
6
6
|
- **Intuitive drag and drop** to reorder siblings or move pages between parents.
|
|
7
7
|
- **Status badges** for published pages, drafts, and drafts with unpublished changes.
|
|
8
|
+
- **Localized status badges** to see each language's publication status and open the page editor in that language with one click.
|
|
8
9
|
- **Live and preview links** from badges, with separate destinations for changed pages.
|
|
9
10
|
- **Customizable badges** with label and color overrides for light and dark themes.
|
|
10
11
|
- **Homepage icon** to identify the root `home` page.
|
|
@@ -14,7 +15,7 @@ Page management tools for Payload admin, built on [`@payloadcms/plugin-nested-do
|
|
|
14
15
|
|
|
15
16
|
**Coming soon:** custom badges for any cell.
|
|
16
17
|
|
|
17
|
-
[Page tree](#page-tree-ui) · [Drag and drop](#drag-and-drop) · [Badges](#badges) · [Live and preview links](#live-and-preview-links) · [Homepage icon](#homepage-icon) · [Diagnostics](#diagnostics) · [Full setup](#setup) · [Configuration](#configuration)
|
|
18
|
+
[Page tree](#page-tree-ui) · [Drag and drop](#drag-and-drop) · [Badges](#badges) · [Localized status](#locale-status-badges) · [Live and preview links](#live-and-preview-links) · [Homepage icon](#homepage-icon) · [Diagnostics](#diagnostics) · [Full setup](#setup) · [Configuration](#configuration)
|
|
18
19
|
|
|
19
20
|
## Page tree UI
|
|
20
21
|
|
|
@@ -107,6 +108,81 @@ See [the playground deploy hook](dev/lib/rebuild.ts) for a complete example. The
|
|
|
107
108
|
|
|
108
109
|
Override any labels or colors with `badges`. Unspecified values use Payload defaults; custom colors adapt to light and dark themes.
|
|
109
110
|
|
|
111
|
+
### Locale status badges
|
|
112
|
+
|
|
113
|
+

|
|
114
|
+
|
|
115
|
+
See each page's publication status across languages directly in the page tree. Each language has its own color-coded badge showing whether the page is published, a draft, or has unpublished changes. Click a language badge to open that page's editor in the selected language, in the same tab.
|
|
116
|
+
|
|
117
|
+
The active language also shows its status label, such as **FR · Published** or **FR · Changed**. Other languages show their codes, such as **EN** and **DE**, with their own status colors. Badges line up across rows so editors can quickly scan for translations that need attention.
|
|
118
|
+
|
|
119
|
+
Set `badges.locales: true` to show one badge for each available locale:
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
nestedDocsPageTreePlugin({
|
|
123
|
+
collections: ['pages'],
|
|
124
|
+
badges: {
|
|
125
|
+
locales: true,
|
|
126
|
+
// Existing colors and labels apply independently to each status.
|
|
127
|
+
labels: { changed: 'Unpublished edits' },
|
|
128
|
+
},
|
|
129
|
+
})
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
For an editor using French, the badges appear side by side: **EN** | **FR · Unpublished edits** | **DE**.
|
|
133
|
+
|
|
134
|
+
Locale codes display in uppercase and follow the order in Payload's localization configuration, respecting `filterAvailableLocales`. Existing badge label and color overrides apply to each locale's status.
|
|
135
|
+
|
|
136
|
+
Badge links support keyboard navigation, include the language and status in their accessible names, and respect your configured admin route. Hidden badges preserve their aligned slots and cannot be clicked. Locale badges link to the editor independently of `badgesLinks`, which controls live and preview links on single status badges.
|
|
137
|
+
|
|
138
|
+
This requires Payload's native localized status, configured separately:
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
// Main Payload config
|
|
142
|
+
experimental: { localizeStatus: true },
|
|
143
|
+
localization: {
|
|
144
|
+
defaultLocale: 'en',
|
|
145
|
+
locales: ['en', 'fr', 'de'],
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
// Target collection
|
|
149
|
+
versions: {
|
|
150
|
+
drafts: { localizeStatus: true },
|
|
151
|
+
},
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Localized status is experimental in the supported Payload versions. Follow [Payload's status-localization and migration guidance](https://payloadcms.com/docs/configuration/localization#status-localization) before enabling it for existing data. The plugin does not enable it or migrate your database. Collections without localized status, or with `badges.locales` omitted, retain the existing single badge.
|
|
155
|
+
|
|
156
|
+
Statuses are read from the latest draft and current document, with locale fallback disabled:
|
|
157
|
+
|
|
158
|
+
| Latest locale status | Current locale status | Badge |
|
|
159
|
+
| --- | --- | --- |
|
|
160
|
+
| Published | Any | Published |
|
|
161
|
+
| Draft | Published | Unpublished edits |
|
|
162
|
+
| Draft | Draft or missing | Draft |
|
|
163
|
+
| Missing or unsupported | Any | Unknown (neutral) |
|
|
164
|
+
|
|
165
|
+
The plugin batches the two status reads across the tree result, respects read permissions, and does not fetch content separately for every badge.
|
|
166
|
+
|
|
167
|
+
#### Custom appearance
|
|
168
|
+
|
|
169
|
+
The plugin keeps the status calculation and layout reusable. Use these styling hooks to apply a project-specific appearance, such as seasonal colors and status icons:
|
|
170
|
+
|
|
171
|
+
- `.pages-hierarchy-locale-statuses`: the badge group.
|
|
172
|
+
- `.pages-hierarchy-locale-status-badge`: a badge, with `data-locale`, `data-status`, and `data-active`.
|
|
173
|
+
- `.pages-hierarchy-locale-status-badge__code`, `__separator`, and `__label`: the visible pieces.
|
|
174
|
+
|
|
175
|
+
For example, local admin CSS can assign a season color:
|
|
176
|
+
|
|
177
|
+
```css
|
|
178
|
+
.pages-hierarchy-locale-statuses .pages-hierarchy-locale-status-badge[data-locale='summer'] {
|
|
179
|
+
background: #fff0a8;
|
|
180
|
+
color: #594600;
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
A reskin can hide `__code` and `__separator` and add a status icon using the badge's `::before` and `data-status`, keeping the active status label and accessible name. The default plugin supplies no icons.
|
|
185
|
+
|
|
110
186
|
### Live and preview links
|
|
111
187
|
|
|
112
188
|
|
|
@@ -114,25 +190,50 @@ https://github.com/user-attachments/assets/e5fdb350-4740-4d80-9811-8c23deaf8701
|
|
|
114
190
|
|
|
115
191
|
Enable `badgesLinks` as shown in [Setup](#setup).
|
|
116
192
|
|
|
117
|
-
Published badges open live; draft-only badges open preview.
|
|
193
|
+
Published badges open live with a right-side link icon; draft-only badges open preview with a right-side eye icon. Because drafts with a published version has two (live and draft) states, choose:
|
|
118
194
|
|
|
119
195
|
| Value | Behavior |
|
|
120
196
|
| --- | --- |
|
|
121
|
-
| `'live'` |
|
|
122
|
-
| `'preview'` |
|
|
123
|
-
| `'both'` (default) | Body opens live;
|
|
197
|
+
| `'live'` | Whole badge, including the right-side link icon, opens live |
|
|
198
|
+
| `'preview'` | Whole badge, including the right-side eye icon, opens preview |
|
|
199
|
+
| `'both'` (default) | Body with link icon opens live; eye icon after a separator opens preview |
|
|
124
200
|
|
|
125
|
-
- **Live:**
|
|
201
|
+
- **Live:** A string `liveURL` is the base URL for the published document's last breadcrumb path. Alternatively, pass a sync or async callback returning the final URL for tenant domains or localized routes. Omit `liveURL` for preview links only.
|
|
126
202
|
- **Preview:** Uses the collection's [`admin.preview`](https://payloadcms.com/docs/admin/preview) callback. Your frontend must serve draft content.
|
|
127
203
|
|
|
128
|
-
All links open in new tabs. Omit `badgesLinks` to keep badges unlinked, even with preview configured.
|
|
204
|
+
All live and preview links open in new tabs. Unlinked badges have no icon. Omit `badgesLinks` to keep single status badges unlinked, even with preview configured. Locale badges still link to the editor.
|
|
205
|
+
|
|
206
|
+
`badgesLinks.showIcons` defaults to `true`: live links use Payload's chain-link `LinkIcon`, and preview links use `EyeIcon`. Set it to `false` to hide inline badge icons; in `'both'` mode, the separate preview action still shows `ExternalLinkIcon` after the separator.
|
|
207
|
+
|
|
208
|
+
For example, with a `tenant` relationship on pages and a `domain` field on tenants:
|
|
209
|
+
|
|
210
|
+
```ts
|
|
211
|
+
badgesLinks: {
|
|
212
|
+
liveURL: async ({ doc, locale, path, req }) => {
|
|
213
|
+
if (!path || (typeof doc.tenant !== 'string' && typeof doc.tenant !== 'number')) {
|
|
214
|
+
return null
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const tenant = await req.payload.findByID({
|
|
218
|
+
collection: 'tenants',
|
|
219
|
+
id: doc.tenant,
|
|
220
|
+
overrideAccess: false,
|
|
221
|
+
req,
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
return new URL(`${locale ? `/${locale}` : ''}${path}`, `https://${tenant.domain}`).href
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
The callback receives `{ collectionSlug, doc, locale, path, req }`. `doc` is the readable published document at depth `0`, so relationships contain IDs. `locale` is the selected admin locale, or `undefined` without localization. `path` is the last published breadcrumb URL from `breadcrumbsFieldSlug`, or `undefined` when unavailable. The callback can build its own route without breadcrumbs. It runs only when a live destination is needed and a readable published document exists, and stays on the server. The `PageTreeLiveURL` type is exported for standalone callbacks.
|
|
129
230
|
|
|
130
231
|
<details>
|
|
131
232
|
<summary>URL resolution and unavailable links</summary>
|
|
132
233
|
|
|
133
|
-
|
|
234
|
+
String live links use `breadcrumbsFieldSlug` and the published path, unaffected by draft slug or parent changes. Callback results are used as final URLs without appending the breadcrumb path. Relative callback URLs resolve against the CMS request URL; return an absolute URL for a separate frontend. Only HTTP and HTTPS links are accepted. Preview receives the latest saved draft, locale, request, and user token. Unsaved changes and `admin.livePreview.url` are not used.
|
|
134
235
|
|
|
135
|
-
Missing breadcrumbs, missing or invalid URLs, or a failing preview callback disable only that link. In `'both'` mode, no live URL leaves the body as text; no preview URL hides the icon. Single-link modes never switch destinations. The selected mode is independent of the badge label.
|
|
236
|
+
Missing breadcrumbs for string live URLs, missing or invalid URLs, or a failing live or preview callback disable only that link. Return `null`, `undefined`, or an empty string from the live callback to leave the live destination unlinked. In `'both'` mode, no live URL leaves the body as text; no preview URL hides the icon. Single-link modes never switch destinations. The selected mode is independent of the badge label.
|
|
136
237
|
|
|
137
238
|
</details>
|
|
138
239
|
|
|
@@ -208,6 +309,7 @@ export const plugins = [
|
|
|
208
309
|
},
|
|
209
310
|
|
|
210
311
|
badgesLinks: { // Omit to disable links; preview uses collection admin.preview.
|
|
312
|
+
showIcons: true, // Default; false hides inline icons and keeps the split preview action.
|
|
211
313
|
draftHasPublishedVersion: 'both', // 'live' | 'preview' | 'both'
|
|
212
314
|
liveURL: 'https://www.example.com', // Base URL for the published breadcrumb path.
|
|
213
315
|
},
|
|
@@ -218,7 +320,7 @@ export const plugins = [
|
|
|
218
320
|
]
|
|
219
321
|
```
|
|
220
322
|
|
|
221
|
-
Only `collections` is required in the page-tree config. Omit `badges` for default styling and `badgesLinks` to disable links. Preview links require the collection's `admin.preview` callback.
|
|
323
|
+
Only `collections` is required in the page-tree config. Omit `badges` for default styling and `badgesLinks` to disable live and preview links. Preview links require the collection's `admin.preview` callback.
|
|
222
324
|
|
|
223
325
|
Each target collection needs parent, breadcrumbs, and `admin.useAsTitle` fields stored at the document's top level. Presentational tabs, rows, collapsibles, and unnamed groups are supported; fields inside named tabs or groups are not.
|
|
224
326
|
|
|
@@ -238,7 +340,7 @@ pnpm exec payload generate:importmap
|
|
|
238
340
|
| `defaultLimit` | `100` | List page size |
|
|
239
341
|
| `hideBreadcrumbs` | `true` | Hide the read-only breadcrumbs field |
|
|
240
342
|
| `homeIndicator` | `{ collections: ['pages'] }` | Collections showing a home icon; `false` disables it |
|
|
241
|
-
| `badges` | Payload defaults | Status
|
|
343
|
+
| `badges` | Payload defaults | Status labels, colors, and optional locale badges |
|
|
242
344
|
| `badgesLinks` | Disabled | Live and preview links |
|
|
243
345
|
| `publishOnMove` | `false` | Publish moves for pages without pending edits |
|
|
244
346
|
| `diagnostics` | `false` | Structured operation logs |
|
|
@@ -258,6 +360,18 @@ pnpm dev
|
|
|
258
360
|
|
|
259
361
|
Open [localhost:3000/admin](http://localhost:3000/admin). The playground creates `admin@email.com` / `password` on startup. Use **seed the database** on the dashboard to add sample pages.
|
|
260
362
|
|
|
363
|
+
The Seed button populates three collections: **Pages** and **Tabbed Pages** use shared publication status; **Localized Pages** uses independent English/French/German status (`en`, `fr`, `de`, in that order). All three use the same 30-page tree, titles, slugs, and ordering in every language. Localized Pages includes mixed published, changed, and draft statuses for testing. Restart the playground after changing locale configuration, then click **seed the database** to populate all three languages. **Reseeding replaces all content and version history in these three playground collections**, removing stale pages and restoring the shared demo hierarchy. Users are preserved.
|
|
364
|
+
|
|
365
|
+
For a fresh in-memory playground database on Windows:
|
|
366
|
+
|
|
367
|
+
```powershell
|
|
368
|
+
$env:PAYLOAD_TEST_DATABASE = 'true'
|
|
369
|
+
pnpm.cmd dev
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
Without that flag, normal development uses `DATABASE_URL` from `dev/.env`. The two status modes can coexist in either database setup; no migration is needed for the new Localized Pages collection.
|
|
373
|
+
|
|
374
|
+
|
|
261
375
|
```bash
|
|
262
376
|
pnpm generate:types
|
|
263
377
|
pnpm generate:importmap
|
|
@@ -275,3 +389,26 @@ pnpm add /path/payload-nested-docs-page-tree-*.tgz
|
|
|
275
389
|
```
|
|
276
390
|
|
|
277
391
|
</details>
|
|
392
|
+
|
|
393
|
+
### Locale badge visibility
|
|
394
|
+
|
|
395
|
+
With localized status badges enabled, the optional top-level
|
|
396
|
+
`localeBadgeVisibility: ({ doc, locale, req }) => boolean` callback decides which
|
|
397
|
+
badges are visible. It runs only on the server, against the latest draft with all
|
|
398
|
+
locale values and the current user's read access. Returning false hides the badge
|
|
399
|
+
from both sight and assistive technology while preserving its aligned slot,
|
|
400
|
+
including when that locale is selected. Publication status is unchanged.
|
|
401
|
+
|
|
402
|
+
Without a callback, all locale badges remain visible. When configured, the existing
|
|
403
|
+
batched draft query reads the document content needed by the callback; no per-row
|
|
404
|
+
queries are added. The callback and document content are not passed to the client.
|
|
405
|
+
Project-specific inheritance or detachment rules belong in the consuming app.
|
|
406
|
+
|
|
407
|
+
The optional top-level
|
|
408
|
+
`localeBadgeStatus: ({ doc, publishedDoc, locale, req, status }) => status`
|
|
409
|
+
callback overrides the displayed status. It receives the latest draft, the current
|
|
410
|
+
document (when readable), and the default computed status. Both documents contain
|
|
411
|
+
all locales. Check the current document's locale status before treating its content
|
|
412
|
+
as published. This callback loads content in the same two access-controlled batched
|
|
413
|
+
queries; no document content or callbacks are passed to the client. It never writes
|
|
414
|
+
publication state. Without it, the plugin's normal status behavior is unchanged.
|
|
@@ -10,9 +10,9 @@ import { buildInsertDropTargets, getDropTargetParentDoc } from '../utilities/dro
|
|
|
10
10
|
import { CANCEL_DRAG_MESSAGE, getDropValidation } from '../utilities/moveValidation.js';
|
|
11
11
|
import { buildDocSlugPath, buildPageTreeDocs, buildProspectiveDocSlugPath, getDocDisplayLabel, getVisibleTreeDocs } from '../utilities/pageTree.js';
|
|
12
12
|
import { pageTreeCollisionDetectionStrategy } from '../utilities/pageTreeCollision.js';
|
|
13
|
-
import { PageTreeStatusBadge } from './PageTreeStatusBadge.js';
|
|
14
13
|
import { PageTreeProvider, PageTreeRowDndProvider } from './PageTreeContext.js';
|
|
15
14
|
import styles from './PageTreeListView.module.css';
|
|
15
|
+
import { PageTreeStatusBadge } from './PageTreeStatusBadge.js';
|
|
16
16
|
import { PageTreeTitleCell } from './PageTreeTitleCell.js';
|
|
17
17
|
const SILENT_MOVE_MESSAGES = new Set([
|
|
18
18
|
CANCEL_DRAG_MESSAGE
|
|
@@ -194,7 +194,8 @@ function sliceColumnState(columnState, docs, renderedCellIndexByDocID) {
|
|
|
194
194
|
function getSelectableRowData(doc) {
|
|
195
195
|
const record = doc;
|
|
196
196
|
return {
|
|
197
|
-
|
|
197
|
+
// Payload's selection Map preserves numeric IDs despite SelectRow's string-only type.
|
|
198
|
+
id: doc.id ?? doc.__pageTreeID,
|
|
198
199
|
_isLocked: Boolean(record._isLocked),
|
|
199
200
|
_userEditing: record._userEditing
|
|
200
201
|
};
|
|
@@ -388,10 +389,11 @@ function getDropTargetValidation(args) {
|
|
|
388
389
|
};
|
|
389
390
|
}
|
|
390
391
|
function renderStatusBadge(args) {
|
|
391
|
-
const { badgeConfig, badgesLinks, doc, index } = args;
|
|
392
|
+
const { badgeConfig, badgesLinks, collectionSlug, doc, index } = args;
|
|
392
393
|
return /*#__PURE__*/ _jsx(PageTreeStatusBadge, {
|
|
393
394
|
badgeConfig: badgeConfig,
|
|
394
395
|
badgesLinks: badgesLinks,
|
|
396
|
+
collectionSlug: collectionSlug,
|
|
395
397
|
doc: doc
|
|
396
398
|
}, doc.__pageTreeID ?? index);
|
|
397
399
|
}
|
|
@@ -425,7 +427,7 @@ function ParentMoveToggle({ collectionSlug, enabled, onToggle }) {
|
|
|
425
427
|
return toggle;
|
|
426
428
|
}
|
|
427
429
|
function buildTableColumns(args) {
|
|
428
|
-
const { badgeConfig, badgesLinks, columnState, docs, enableRowSelections, homeIndicatorEnabled, orderableFieldName, parentFieldSlug, t, useAsTitle } = args;
|
|
430
|
+
const { badgeConfig, badgesLinks, collectionSlug, columnState, docs, enableRowSelections, homeIndicatorEnabled, orderableFieldName, parentFieldSlug, t, useAsTitle } = args;
|
|
429
431
|
const columnsToUse = columnState.map((column)=>{
|
|
430
432
|
if (column.accessor === useAsTitle) {
|
|
431
433
|
return {
|
|
@@ -444,6 +446,7 @@ function buildTableColumns(args) {
|
|
|
444
446
|
renderedCells: docs.map((doc, index)=>renderStatusBadge({
|
|
445
447
|
badgeConfig,
|
|
446
448
|
badgesLinks,
|
|
449
|
+
collectionSlug,
|
|
447
450
|
doc,
|
|
448
451
|
index,
|
|
449
452
|
t
|
|
@@ -892,6 +895,7 @@ export default function PageTreeListViewClient({ allDocs, badgeConfig, badgesLin
|
|
|
892
895
|
const tableColumns = React.useMemo(()=>buildTableColumns({
|
|
893
896
|
badgeConfig,
|
|
894
897
|
badgesLinks,
|
|
898
|
+
collectionSlug: props.collectionSlug,
|
|
895
899
|
columnState: paginatedColumnState,
|
|
896
900
|
docs: displayedPaginatedDocs,
|
|
897
901
|
enableRowSelections: props.enableRowSelections,
|
|
@@ -908,6 +912,7 @@ export default function PageTreeListViewClient({ allDocs, badgeConfig, badgesLin
|
|
|
908
912
|
homeIndicatorEnabled,
|
|
909
913
|
orderableFieldName,
|
|
910
914
|
parentFieldSlug,
|
|
915
|
+
props.collectionSlug,
|
|
911
916
|
props.enableRowSelections,
|
|
912
917
|
i18n.t,
|
|
913
918
|
useAsTitle
|