@veiag/payload-cmdk 1.0.5 → 1.1.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 +256 -10
- package/dist/components/CommandMenuContext.js +126 -29
- package/dist/components/CommandMenuContext.js.map +1 -1
- package/dist/exports/client.d.ts +1 -0
- package/dist/exports/client.js +1 -0
- package/dist/exports/client.js.map +1 -1
- package/dist/types.d.ts +88 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/index.js +20 -3
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/registry.d.ts +18 -0
- package/dist/utils/registry.js +45 -0
- package/dist/utils/registry.js.map +1 -0
- package/package.json +1 -1
- package/dist/endpoints/customEndpointHandler.d.ts +0 -2
- package/dist/endpoints/customEndpointHandler.js +0 -7
- package/dist/endpoints/customEndpointHandler.js.map +0 -1
package/README.md
CHANGED
|
@@ -304,30 +304,268 @@ Add custom menu items and groups to the command menu.
|
|
|
304
304
|
```typescript
|
|
305
305
|
{
|
|
306
306
|
type: 'item',
|
|
307
|
-
slug: 'unique-slug',
|
|
308
|
-
label: 'Item Label',
|
|
309
|
-
icon: 'LucideIconName',
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
method?: 'GET' | 'POST' | 'PUT' | 'DELETE', // For API actions
|
|
314
|
-
body?: { key: 'value' } // For API actions
|
|
315
|
-
}
|
|
307
|
+
slug: 'unique-slug', // Must be unique across all items
|
|
308
|
+
label: 'Item Label', // Can be a localized object — see Localization below
|
|
309
|
+
icon: 'LucideIconName', // Optional, from lucide.dev/icons
|
|
310
|
+
collectionSlugs: ['posts'], // Optional — only show on these collection pages
|
|
311
|
+
collectionContext: ['list'], // Optional — 'list', 'document', or both
|
|
312
|
+
action: { ... } // See Action Types below
|
|
316
313
|
}
|
|
317
314
|
```
|
|
318
315
|
|
|
319
316
|
#### Custom Menu Group
|
|
320
317
|
|
|
318
|
+
Groups are rendered with a heading and can contain multiple items. Groups with identical titles are automatically merged.
|
|
319
|
+
|
|
321
320
|
```typescript
|
|
322
321
|
{
|
|
323
322
|
type: 'group',
|
|
324
|
-
title: 'Group Title',
|
|
323
|
+
title: 'Group Title', // Can be a localized object
|
|
324
|
+
collectionSlugs: ['posts'], // Optional — only show on these collection pages
|
|
325
|
+
collectionContext: ['list'], // Optional
|
|
325
326
|
items: [
|
|
326
327
|
// Array of CustomMenuItem
|
|
327
328
|
]
|
|
328
329
|
}
|
|
329
330
|
```
|
|
330
331
|
|
|
332
|
+
---
|
|
333
|
+
|
|
334
|
+
#### Action Types
|
|
335
|
+
|
|
336
|
+
Every item requires an `action` that determines what happens when the item is selected.
|
|
337
|
+
|
|
338
|
+
##### `link` — Navigate to a URL
|
|
339
|
+
|
|
340
|
+
Navigates to a URL. Supports both internal paths and external URLs.
|
|
341
|
+
|
|
342
|
+
```typescript
|
|
343
|
+
action: {
|
|
344
|
+
type: 'link',
|
|
345
|
+
href: '/admin/collections/posts', // or 'https://your-site.com'
|
|
346
|
+
}
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
##### `api` — Call an API endpoint
|
|
350
|
+
|
|
351
|
+
Makes an HTTP request when the item is selected. The menu closes after the request completes (or fails).
|
|
352
|
+
|
|
353
|
+
```typescript
|
|
354
|
+
action: {
|
|
355
|
+
type: 'api',
|
|
356
|
+
href: '/api/cache/clear',
|
|
357
|
+
method: 'POST', // 'GET' | 'POST' | 'PUT' | 'DELETE' — default: 'GET'
|
|
358
|
+
body: { scope: 'all' }, // Optional JSON body
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
##### `function` — Call a client-side handler
|
|
363
|
+
|
|
364
|
+
Because the plugin config is serialized across the Next.js server→client boundary, functions cannot live directly in `payload.config.ts`. Instead, you reference a handler by a **string key** in the config and register the actual function on the client.
|
|
365
|
+
|
|
366
|
+
**Step 1 — reference the key in your config:**
|
|
367
|
+
|
|
368
|
+
```typescript
|
|
369
|
+
// payload.config.ts
|
|
370
|
+
customItems: [
|
|
371
|
+
{
|
|
372
|
+
type: 'item',
|
|
373
|
+
slug: 'save-document',
|
|
374
|
+
label: 'Save Document',
|
|
375
|
+
icon: 'Save',
|
|
376
|
+
collectionContext: ['document'],
|
|
377
|
+
action: {
|
|
378
|
+
type: 'function',
|
|
379
|
+
key: 'save-current-doc', // Must match the key used in registerCommandMenuAction
|
|
380
|
+
},
|
|
381
|
+
},
|
|
382
|
+
]
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
**Step 2 — register the handler in a client component:**
|
|
386
|
+
|
|
387
|
+
```typescript
|
|
388
|
+
// e.g. app/(payload)/layout.tsx or a custom admin component
|
|
389
|
+
'use client'
|
|
390
|
+
import { registerCommandMenuAction, unregisterCommandMenuAction } from '@veiag/payload-cmdk/client'
|
|
391
|
+
import { useEffect } from 'react'
|
|
392
|
+
|
|
393
|
+
export default function AdminLayout({ children }) {
|
|
394
|
+
useEffect(() => {
|
|
395
|
+
registerCommandMenuAction('save-current-doc', () => {
|
|
396
|
+
// Submit the currently active form (Payload's save button)
|
|
397
|
+
document.querySelector<HTMLButtonElement>('button[type="submit"]')?.click()
|
|
398
|
+
})
|
|
399
|
+
|
|
400
|
+
// Clean up when the component unmounts
|
|
401
|
+
return () => unregisterCommandMenuAction('save-current-doc')
|
|
402
|
+
}, [])
|
|
403
|
+
|
|
404
|
+
return <>{children}</>
|
|
405
|
+
}
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
The handler can be synchronous or return a `Promise`. The menu always closes after it resolves (or throws).
|
|
409
|
+
|
|
410
|
+
**Full example with multiple function actions:**
|
|
411
|
+
|
|
412
|
+
```typescript
|
|
413
|
+
// payload.config.ts
|
|
414
|
+
customItems: [
|
|
415
|
+
{
|
|
416
|
+
type: 'group',
|
|
417
|
+
title: 'Document Actions',
|
|
418
|
+
collectionContext: ['document'],
|
|
419
|
+
items: [
|
|
420
|
+
{
|
|
421
|
+
type: 'item',
|
|
422
|
+
slug: 'save-document',
|
|
423
|
+
label: 'Save Document',
|
|
424
|
+
icon: 'Save',
|
|
425
|
+
action: { type: 'function', key: 'save-current-doc' },
|
|
426
|
+
},
|
|
427
|
+
{
|
|
428
|
+
type: 'item',
|
|
429
|
+
slug: 'copy-doc-link',
|
|
430
|
+
label: 'Copy Document Link',
|
|
431
|
+
icon: 'Link',
|
|
432
|
+
action: { type: 'function', key: 'copy-doc-link' },
|
|
433
|
+
},
|
|
434
|
+
],
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
type: 'item',
|
|
438
|
+
slug: 'import-posts',
|
|
439
|
+
label: 'Import Posts',
|
|
440
|
+
icon: 'Upload',
|
|
441
|
+
collectionSlugs: ['posts'],
|
|
442
|
+
collectionContext: ['list'],
|
|
443
|
+
action: { type: 'function', key: 'import-posts' },
|
|
444
|
+
},
|
|
445
|
+
]
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
```typescript
|
|
449
|
+
// Client component / layout
|
|
450
|
+
'use client'
|
|
451
|
+
import { registerCommandMenuAction, unregisterCommandMenuAction } from '@veiag/payload-cmdk/client'
|
|
452
|
+
import { useEffect } from 'react'
|
|
453
|
+
|
|
454
|
+
export default function AdminLayout({ children }) {
|
|
455
|
+
useEffect(() => {
|
|
456
|
+
registerCommandMenuAction('save-current-doc', () => {
|
|
457
|
+
document.querySelector<HTMLButtonElement>('button[type="submit"]')?.click()
|
|
458
|
+
})
|
|
459
|
+
|
|
460
|
+
registerCommandMenuAction('copy-doc-link', () => {
|
|
461
|
+
navigator.clipboard.writeText(window.location.href)
|
|
462
|
+
})
|
|
463
|
+
|
|
464
|
+
registerCommandMenuAction('import-posts', async () => {
|
|
465
|
+
await fetch('/api/posts/import', { method: 'POST' })
|
|
466
|
+
})
|
|
467
|
+
|
|
468
|
+
return () => {
|
|
469
|
+
unregisterCommandMenuAction('save-current-doc')
|
|
470
|
+
unregisterCommandMenuAction('copy-doc-link')
|
|
471
|
+
unregisterCommandMenuAction('import-posts')
|
|
472
|
+
}
|
|
473
|
+
}, [])
|
|
474
|
+
|
|
475
|
+
return <>{children}</>
|
|
476
|
+
}
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
> **Note:** If a key is used in the config but no handler has been registered, the plugin logs a warning to the console and the menu still closes normally.
|
|
480
|
+
|
|
481
|
+
---
|
|
482
|
+
|
|
483
|
+
#### `collectionSlugs` — Restrict to specific collections
|
|
484
|
+
|
|
485
|
+
Show an item or group only when the user is viewing a particular collection.
|
|
486
|
+
|
|
487
|
+
```typescript
|
|
488
|
+
{
|
|
489
|
+
type: 'item',
|
|
490
|
+
slug: 'export-posts',
|
|
491
|
+
label: 'Export Posts',
|
|
492
|
+
icon: 'Download',
|
|
493
|
+
collectionSlugs: ['posts'], // only visible on /admin/collections/posts/*
|
|
494
|
+
action: { type: 'function', key: 'export-posts' },
|
|
495
|
+
}
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
Omit `collectionSlugs` (or pass an empty array) to show the item on all pages, including non-collection pages.
|
|
499
|
+
|
|
500
|
+
---
|
|
501
|
+
|
|
502
|
+
#### `collectionContext` — Restrict to list or document pages
|
|
503
|
+
|
|
504
|
+
Use `collectionContext` on any item or group to control exactly where inside a collection it appears.
|
|
505
|
+
|
|
506
|
+
- **Type:** `('document' | 'list')[]`
|
|
507
|
+
- **Default:** not set (appears on all pages)
|
|
508
|
+
|
|
509
|
+
| Value | Where it appears |
|
|
510
|
+
|-------|-----------------|
|
|
511
|
+
| `'list'` | Collection list page — e.g. `/admin/collections/posts` |
|
|
512
|
+
| `'document'` | Document edit or create page — e.g. `/admin/collections/posts/abc123` or `/admin/collections/posts/create` |
|
|
513
|
+
|
|
514
|
+
Pass both values, or omit the field entirely, to show the item on all collection pages.
|
|
515
|
+
|
|
516
|
+
`collectionContext` can be combined with `collectionSlugs` to target a specific collection **and** a specific page context, or used alone to apply to all collections.
|
|
517
|
+
|
|
518
|
+
**Examples:**
|
|
519
|
+
|
|
520
|
+
```typescript
|
|
521
|
+
customItems: [
|
|
522
|
+
// Only on the posts list page
|
|
523
|
+
{
|
|
524
|
+
type: 'item',
|
|
525
|
+
slug: 'import-posts',
|
|
526
|
+
label: 'Import Posts',
|
|
527
|
+
icon: 'Upload',
|
|
528
|
+
collectionSlugs: ['posts'],
|
|
529
|
+
collectionContext: ['list'],
|
|
530
|
+
action: { type: 'function', key: 'import-posts' },
|
|
531
|
+
},
|
|
532
|
+
|
|
533
|
+
// Only when editing or creating a post
|
|
534
|
+
{
|
|
535
|
+
type: 'item',
|
|
536
|
+
slug: 'save-post',
|
|
537
|
+
label: 'Save Post',
|
|
538
|
+
icon: 'Save',
|
|
539
|
+
collectionSlugs: ['posts'],
|
|
540
|
+
collectionContext: ['document'],
|
|
541
|
+
action: { type: 'function', key: 'save-current-doc' },
|
|
542
|
+
},
|
|
543
|
+
|
|
544
|
+
// On any document edit/create page across all collections
|
|
545
|
+
{
|
|
546
|
+
type: 'item',
|
|
547
|
+
slug: 'copy-link',
|
|
548
|
+
label: 'Copy Document Link',
|
|
549
|
+
icon: 'Link',
|
|
550
|
+
collectionContext: ['document'],
|
|
551
|
+
action: { type: 'function', key: 'copy-doc-link' },
|
|
552
|
+
},
|
|
553
|
+
|
|
554
|
+
// On both list and document pages (same as omitting collectionContext)
|
|
555
|
+
{
|
|
556
|
+
type: 'item',
|
|
557
|
+
slug: 'help',
|
|
558
|
+
label: 'Open Help',
|
|
559
|
+
icon: 'HelpCircle',
|
|
560
|
+
collectionSlugs: ['posts'],
|
|
561
|
+
collectionContext: ['list', 'document'],
|
|
562
|
+
action: { type: 'link', href: '/docs' },
|
|
563
|
+
},
|
|
564
|
+
]
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
---
|
|
568
|
+
|
|
331
569
|
**Example with localization:**
|
|
332
570
|
|
|
333
571
|
```typescript
|
|
@@ -602,3 +840,11 @@ MIT © [VeiaG](https://github.com/VeiaG)
|
|
|
602
840
|
- [react-hotkeys-hook Documentation](https://react-hotkeys-hook.vercel.app/docs/intro)
|
|
603
841
|
|
|
604
842
|
# More plugins and payload resources at [PayloadCMS Extensions](https://payload.veiag.dev/)
|
|
843
|
+
|
|
844
|
+
<a href="https://www.star-history.com/#VeiaG/payload-cmdk&type=date&legend=top-left">
|
|
845
|
+
<picture>
|
|
846
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=VeiaG/payload-cmdk&type=date&theme=dark&legend=top-left" />
|
|
847
|
+
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=VeiaG/payload-cmdk&type=date&legend=top-left" />
|
|
848
|
+
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=VeiaG/payload-cmdk&type=date&legend=top-left" />
|
|
849
|
+
</picture>
|
|
850
|
+
</a>
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import './modal.scss';
|
|
4
|
-
import { Modal, useConfig, useModal, useTranslation } from '@payloadcms/ui';
|
|
4
|
+
import { Modal, useConfig, useModal, useRouteTransition, useTranslation } from '@payloadcms/ui';
|
|
5
5
|
import { ArrowBigUp, ChevronLeft, Command as CommandIcon, Option } from 'lucide-react';
|
|
6
|
-
import { useRouter } from 'next/navigation';
|
|
6
|
+
import { usePathname, useRouter } from 'next/navigation';
|
|
7
|
+
import { formatAdminURL } from 'payload/shared';
|
|
7
8
|
import { createContext, Fragment, memo, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
|
8
9
|
import { useHotkeys } from 'react-hotkeys-hook';
|
|
9
10
|
import { createDefaultGroups } from '../utils/index';
|
|
11
|
+
import { getCommandMenuAction } from '../utils/registry';
|
|
10
12
|
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut } from './cmdk/index';
|
|
11
13
|
import { LucideIconDynamic } from './LucideIcon';
|
|
12
14
|
const MODAL_SLUG = 'command-menu';
|
|
@@ -36,7 +38,74 @@ const CommandMenuComponent = ({ pluginConfig })=>{
|
|
|
36
38
|
const commandListRef = useRef(null);
|
|
37
39
|
const { closeMenu, currentPage, groups, items, setPage } = useCommandMenu();
|
|
38
40
|
const router = useRouter();
|
|
41
|
+
const pathname = usePathname();
|
|
42
|
+
const { startRouteTransition } = useRouteTransition();
|
|
39
43
|
const { t } = useTranslation();
|
|
44
|
+
const { config } = useConfig();
|
|
45
|
+
// Detect which collection the user is currently viewing (if any) and whether
|
|
46
|
+
// they are on the list page or a specific document/create page.
|
|
47
|
+
// Use the configured admin route instead of hardcoding '/admin' so that
|
|
48
|
+
// projects with a custom adminRoute still match correctly.
|
|
49
|
+
const { currentCollectionSlug, isDocumentContext } = useMemo(()=>{
|
|
50
|
+
if (!pathname) {
|
|
51
|
+
return {
|
|
52
|
+
currentCollectionSlug: null,
|
|
53
|
+
isDocumentContext: false
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const adminRoute = config.routes?.admin ?? '/admin';
|
|
57
|
+
// Escape any regex special characters in the admin route path
|
|
58
|
+
const escapedRoute = adminRoute.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
59
|
+
// Group 1 = collection slug, Group 2 (optional) = document ID or 'create'
|
|
60
|
+
const match = pathname.match(new RegExp(`${escapedRoute}/collections/([^/]+)(?:/([^/]+))?`));
|
|
61
|
+
return {
|
|
62
|
+
currentCollectionSlug: match?.[1] ?? null,
|
|
63
|
+
isDocumentContext: match?.[2] != null
|
|
64
|
+
};
|
|
65
|
+
}, [
|
|
66
|
+
pathname,
|
|
67
|
+
config.routes?.admin
|
|
68
|
+
]);
|
|
69
|
+
/** Returns true if an item/group should be visible given the current route. */ const isEntryVisible = useCallback((entry)=>{
|
|
70
|
+
// --- collectionSlugs filter ---
|
|
71
|
+
if (entry.collectionSlugs && entry.collectionSlugs.length > 0) {
|
|
72
|
+
if (currentCollectionSlug === null || !entry.collectionSlugs.includes(currentCollectionSlug)) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// --- collectionContext filter ---
|
|
77
|
+
if (entry.collectionContext && entry.collectionContext.length > 0) {
|
|
78
|
+
// context filter only makes sense on a collection page
|
|
79
|
+
if (currentCollectionSlug === null) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
const currentContext = isDocumentContext ? 'document' : 'list';
|
|
83
|
+
if (!entry.collectionContext.includes(currentContext)) {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return true;
|
|
88
|
+
}, [
|
|
89
|
+
currentCollectionSlug,
|
|
90
|
+
isDocumentContext
|
|
91
|
+
]);
|
|
92
|
+
// Filter groups to only those visible on the current page
|
|
93
|
+
const visibleGroups = useMemo(()=>{
|
|
94
|
+
return groups.filter((group)=>isEntryVisible(group)).map((group)=>({
|
|
95
|
+
...group,
|
|
96
|
+
items: group.items.filter((item)=>isEntryVisible(item))
|
|
97
|
+
})).filter((group)=>group.items.length > 0);
|
|
98
|
+
}, [
|
|
99
|
+
groups,
|
|
100
|
+
isEntryVisible
|
|
101
|
+
]);
|
|
102
|
+
// Filter stray items to only those visible on the current page
|
|
103
|
+
const visibleItems = useMemo(()=>{
|
|
104
|
+
return items.filter((item)=>isEntryVisible(item));
|
|
105
|
+
}, [
|
|
106
|
+
items,
|
|
107
|
+
isEntryVisible
|
|
108
|
+
]);
|
|
40
109
|
useEffect(()=>{
|
|
41
110
|
setIsMac(/Mac|iPhone|iPod|iPad/i.test(navigator.platform));
|
|
42
111
|
}, []);
|
|
@@ -79,6 +148,7 @@ const CommandMenuComponent = ({ pluginConfig })=>{
|
|
|
79
148
|
children: elements
|
|
80
149
|
});
|
|
81
150
|
};
|
|
151
|
+
const adminRoute = config.routes?.admin ?? '/admin';
|
|
82
152
|
// Debounced search for submenu
|
|
83
153
|
useEffect(()=>{
|
|
84
154
|
if (currentPage === 'main') {
|
|
@@ -99,7 +169,10 @@ const CommandMenuComponent = ({ pluginConfig })=>{
|
|
|
99
169
|
type: 'custom',
|
|
100
170
|
action: {
|
|
101
171
|
type: 'link',
|
|
102
|
-
href:
|
|
172
|
+
href: formatAdminURL({
|
|
173
|
+
adminRoute,
|
|
174
|
+
path: `/collections/${currentPage.slug}/${doc.id}`
|
|
175
|
+
})
|
|
103
176
|
},
|
|
104
177
|
icon: pluginConfig?.submenu?.icons?.[currentPage.slug] ?? undefined,
|
|
105
178
|
label: doc[currentPage.useAsTitle] || doc.id
|
|
@@ -117,7 +190,8 @@ const CommandMenuComponent = ({ pluginConfig })=>{
|
|
|
117
190
|
}, [
|
|
118
191
|
search,
|
|
119
192
|
currentPage,
|
|
120
|
-
pluginConfig?.submenu?.icons
|
|
193
|
+
pluginConfig?.submenu?.icons,
|
|
194
|
+
adminRoute
|
|
121
195
|
]);
|
|
122
196
|
const handleBack = useCallback(()=>{
|
|
123
197
|
setPage('main');
|
|
@@ -127,30 +201,53 @@ const CommandMenuComponent = ({ pluginConfig })=>{
|
|
|
127
201
|
setPage
|
|
128
202
|
]);
|
|
129
203
|
const executeItemAction = useCallback(async (item)=>{
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
204
|
+
try {
|
|
205
|
+
// Execute the item's action
|
|
206
|
+
switch(item.action.type){
|
|
207
|
+
case 'api':
|
|
208
|
+
{
|
|
209
|
+
const response = await fetch(item.action.href, {
|
|
210
|
+
body: item.action.body ? JSON.stringify(item.action.body) : undefined,
|
|
211
|
+
headers: {
|
|
212
|
+
'Content-Type': 'application/json'
|
|
213
|
+
},
|
|
214
|
+
method: item.action.method || 'GET'
|
|
215
|
+
});
|
|
216
|
+
if (!response.ok) {
|
|
217
|
+
throw new Error(`[payload-cmdk] API action failed: ${response.status} ${response.statusText}`);
|
|
218
|
+
}
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
case 'function':
|
|
222
|
+
{
|
|
223
|
+
const handler = getCommandMenuAction(item.action.key);
|
|
224
|
+
if (handler) {
|
|
225
|
+
await handler();
|
|
226
|
+
} else {
|
|
227
|
+
//eslint-disable-next-line no-console
|
|
228
|
+
console.warn(`[payload-cmdk] No handler registered for function action key "${item.action.key}". ` + `Call registerCommandMenuAction("${item.action.key}", fn) on the client.`);
|
|
229
|
+
}
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
case 'link':
|
|
233
|
+
{
|
|
234
|
+
const href = item.action.href;
|
|
235
|
+
startRouteTransition(()=>router.push(href));
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
default:
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
} finally{
|
|
242
|
+
closeMenu();
|
|
243
|
+
setSearch('');
|
|
244
|
+
setPage('main');
|
|
146
245
|
}
|
|
147
|
-
closeMenu();
|
|
148
|
-
setSearch('');
|
|
149
|
-
setPage('main');
|
|
150
246
|
}, [
|
|
151
247
|
router,
|
|
152
248
|
closeMenu,
|
|
153
|
-
setPage
|
|
249
|
+
setPage,
|
|
250
|
+
startRouteTransition
|
|
154
251
|
]);
|
|
155
252
|
const openSubmenu = useCallback((item)=>{
|
|
156
253
|
setPage({
|
|
@@ -194,7 +291,7 @@ const CommandMenuComponent = ({ pluginConfig })=>{
|
|
|
194
291
|
e.preventDefault();
|
|
195
292
|
e.stopPropagation();
|
|
196
293
|
// Find the item in groups
|
|
197
|
-
const item =
|
|
294
|
+
const item = visibleGroups.flatMap((g)=>g.items).find((i)=>i.slug === itemSlug);
|
|
198
295
|
if (item) {
|
|
199
296
|
openSubmenu(item);
|
|
200
297
|
}
|
|
@@ -211,7 +308,7 @@ const CommandMenuComponent = ({ pluginConfig })=>{
|
|
|
211
308
|
submenuEnabled,
|
|
212
309
|
submenuShortcut,
|
|
213
310
|
openSubmenu,
|
|
214
|
-
|
|
311
|
+
visibleGroups
|
|
215
312
|
]);
|
|
216
313
|
const placeholder = currentPage === 'main' ? t('cmdkPlugin:search') : t('general:searchBy', {
|
|
217
314
|
label: currentPage.useAsTitleLabel
|
|
@@ -293,7 +390,7 @@ const CommandMenuComponent = ({ pluginConfig })=>{
|
|
|
293
390
|
/*#__PURE__*/ _jsx(CommandEmpty, {
|
|
294
391
|
children: isLoadingSubmenu ? t('cmdkPlugin:loading') : t('cmdkPlugin:noResults')
|
|
295
392
|
}),
|
|
296
|
-
currentPage === 'main' &&
|
|
393
|
+
currentPage === 'main' && visibleGroups.map((group, index)=>{
|
|
297
394
|
if (group.items.length === 0) {
|
|
298
395
|
return null;
|
|
299
396
|
}
|
|
@@ -304,7 +401,7 @@ const CommandMenuComponent = ({ pluginConfig })=>{
|
|
|
304
401
|
if (group.title === 'Globals') {
|
|
305
402
|
titleName = t('general:globals');
|
|
306
403
|
}
|
|
307
|
-
const isRenderSeparator = !(index ===
|
|
404
|
+
const isRenderSeparator = !(index === visibleGroups.length - 1 && visibleItems.length === 0);
|
|
308
405
|
return /*#__PURE__*/ _jsxs(Fragment, {
|
|
309
406
|
children: [
|
|
310
407
|
/*#__PURE__*/ _jsx(CommandGroup, {
|
|
@@ -340,7 +437,7 @@ const CommandMenuComponent = ({ pluginConfig })=>{
|
|
|
340
437
|
]
|
|
341
438
|
}, group.title);
|
|
342
439
|
}),
|
|
343
|
-
currentPage === 'main' &&
|
|
440
|
+
currentPage === 'main' && visibleItems?.map((item)=>{
|
|
344
441
|
const isDynamicIcon = typeof item.icon === 'string';
|
|
345
442
|
const IconComponent = isDynamicIcon ? null : item.icon;
|
|
346
443
|
return /*#__PURE__*/ _jsxs(CommandItem, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/CommandMenuContext.tsx"],"sourcesContent":["'use client'\nimport type { LucideIcon } from 'lucide-react'\nimport type { CustomTranslationsKeys, CustomTranslationsObject } from 'src/translations/index'\n\nimport './modal.scss'\n\nimport type {\n CommandMenuContextProps,\n CommandMenuGroup,\n CommandMenuItem,\n CommandMenuPage,\n GenericCollectionDocument,\n IconName,\n PluginCommandMenuConfig,\n} from 'src/types'\n\nimport { Modal, useConfig, useModal, useTranslation } from '@payloadcms/ui'\nimport { ArrowBigUp, ChevronLeft, Command as CommandIcon, Option } from 'lucide-react'\nimport { useRouter } from 'next/navigation'\nimport {\n createContext,\n Fragment,\n memo,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react'\nimport { useHotkeys } from 'react-hotkeys-hook'\n\nimport { createDefaultGroups } from '../utils/index'\nimport {\n Command,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n CommandSeparator,\n CommandShortcut,\n} from './cmdk/index'\nimport { LucideIconDynamic } from './LucideIcon'\n\nconst MODAL_SLUG = 'command-menu'\n\ninterface CommandMenuContextType {\n closeMenu: () => void\n currentPage: CommandMenuPage\n groups: CommandMenuGroup[]\n isOpen: boolean\n items: CommandMenuItem[]\n openMenu: () => void\n setPage: (page: CommandMenuPage) => void\n toggleMenu: () => void\n}\n\nconst CommandMenuContext = createContext<CommandMenuContextType>({\n closeMenu: () => {},\n currentPage: 'main',\n groups: [],\n isOpen: false,\n items: [],\n openMenu: () => {},\n setPage: () => {},\n toggleMenu: () => {},\n})\n\nexport const useCommandMenu = () => {\n const context = useContext(CommandMenuContext)\n return context\n}\n\nconst ITEM_SELECTOR = `[cmdk-item=\"\"]`\n\nfunction getSelectedElement(containerRef: React.RefObject<HTMLElement | null>) {\n return containerRef.current?.querySelector(`${ITEM_SELECTOR}[aria-selected=\"true\"]`)\n}\n\nconst CommandMenuComponent: React.FC<{\n pluginConfig: PluginCommandMenuConfig\n}> = ({ pluginConfig }) => {\n const [search, setSearch] = useState('')\n const [submenuItems, setSubmenuItems] = useState<CommandMenuItem[]>([])\n const [isLoadingSubmenu, setIsLoadingSubmenu] = useState(false)\n const [isMac, setIsMac] = useState(false)\n\n const commandListRef = useRef<HTMLDivElement>(null)\n\n const { closeMenu, currentPage, groups, items, setPage } = useCommandMenu()\n const router = useRouter()\n const { t } = useTranslation<CustomTranslationsObject, CustomTranslationsKeys>()\n\n useEffect(() => {\n setIsMac(/Mac|iPhone|iPod|iPad/i.test(navigator.platform))\n }, [])\n\n const submenuEnabled = pluginConfig?.submenu?.enabled !== false\n const submenuShortcut = pluginConfig?.submenu?.shortcut || 'shift+enter'\n const blurBg = pluginConfig?.blurBg !== false\n\n const formatShortcutKey = (key: string): React.ReactNode => {\n // Handle compound shortcuts like \"Shift + Enter\"\n const parts = key.split('+').map((part) => part.trim())\n const elements = parts.map((part, index) => {\n const lowerPart = part.toLowerCase()\n let content: React.ReactNode\n\n if (lowerPart === 'ctrl' || lowerPart === 'cmd') {\n content = isMac ? <CommandIcon size={12} /> : 'Ctrl'\n } else if (lowerPart === 'meta') {\n content = isMac ? <CommandIcon size={12} /> : 'Ctrl'\n } else if (lowerPart === 'shift') {\n content = isMac ? <ArrowBigUp size={12} /> : 'Shift'\n } else if (lowerPart === 'alt') {\n content = isMac ? <Option size={12} /> : 'Alt'\n } else {\n content = part\n }\n\n return (\n <Fragment key={index}>\n {content}\n {!isMac && index < parts.length - 1 && ' + '}\n </Fragment>\n )\n })\n\n return <>{elements}</>\n }\n\n // Debounced search for submenu\n useEffect(() => {\n if (currentPage === 'main') {\n return\n }\n\n const fetchDocuments = async () => {\n if (currentPage.type !== 'collection-search') {\n return\n }\n\n setIsLoadingSubmenu(true)\n try {\n const searchParam = search\n ? `&where[${currentPage.useAsTitle}][like]=${encodeURIComponent(search)}`\n : ''\n const response = await fetch(\n `/api/${currentPage.slug}?limit=10${searchParam}&select[${currentPage.useAsTitle}]=true`,\n )\n const data = await response.json()\n\n if (data.docs && Array.isArray(data.docs)) {\n const docs: CommandMenuItem[] = data.docs.map((doc: GenericCollectionDocument) => ({\n slug: `${currentPage.slug}-${doc.id}`,\n type: 'custom' as const,\n action: {\n type: 'link',\n href: `/admin/collections/${currentPage.slug}/${doc.id}`,\n },\n icon: pluginConfig?.submenu?.icons?.[currentPage.slug] ?? undefined,\n label: doc[currentPage.useAsTitle] || doc.id,\n }))\n setSubmenuItems(docs)\n }\n } catch {\n setSubmenuItems([])\n } finally {\n setIsLoadingSubmenu(false)\n }\n }\n\n const timeoutId = setTimeout(fetchDocuments, 300)\n return () => clearTimeout(timeoutId)\n }, [search, currentPage, pluginConfig?.submenu?.icons])\n\n const handleBack = useCallback(() => {\n setPage('main')\n setSearch('')\n setSubmenuItems([])\n }, [setPage])\n\n const executeItemAction = useCallback(\n async (item: CommandMenuItem) => {\n // Execute the item's action\n switch (item.action.type) {\n case 'api':\n await fetch(item.action.href, {\n body: item.action.body ? JSON.stringify(item.action.body) : undefined,\n headers: {\n 'Content-Type': 'application/json',\n },\n method: item.action.method || 'GET',\n })\n break\n case 'link':\n router.push(item.action.href)\n break\n default:\n break\n }\n closeMenu()\n setSearch('')\n setPage('main')\n },\n [router, closeMenu, setPage],\n )\n\n const openSubmenu = useCallback(\n (item: CommandMenuItem) => {\n setPage({\n slug: item.slug,\n type: 'collection-search',\n label: item.label,\n useAsTitle: item.useAsTitle || 'id',\n useAsTitleLabel: item.useAsTitleLabel || item.useAsTitle || 'id',\n })\n setSearch('')\n //set isLoadingSubmenu to true to show loading state while fetching\n setIsLoadingSubmenu(true)\n setSubmenuItems([])\n },\n [setPage],\n )\n\n const handleSelect = useCallback(\n async (item: CommandMenuItem) => {\n await executeItemAction(item)\n },\n [executeItemAction],\n )\n\n // Handle keyboard events for navigation and back\n useEffect(() => {\n const handleKeyDown = (e: KeyboardEvent) => {\n // ESC key for back navigation in submenu\n if (e.key === 'Escape' && currentPage !== 'main') {\n e.preventDefault()\n e.stopPropagation()\n handleBack()\n return\n }\n\n // Enter/Shift+Enter handling for collection submenu\n if (e.key === 'Enter' && currentPage === 'main') {\n const selectedElement = getSelectedElement(commandListRef)\n const itemType = selectedElement?.getAttribute('data-item-type')\n const itemSlug = selectedElement?.getAttribute('data-value')\n\n if (submenuEnabled && itemType === 'collection' && itemSlug) {\n const isShiftPressed = e.shiftKey\n const shouldOpenSubmenu =\n (submenuShortcut === 'shift+enter' && isShiftPressed) ||\n (submenuShortcut === 'enter' && !isShiftPressed)\n\n if (shouldOpenSubmenu) {\n e.preventDefault()\n e.stopPropagation()\n\n // Find the item in groups\n const item = groups.flatMap((g) => g.items).find((i) => i.slug === itemSlug)\n if (item) {\n openSubmenu(item)\n }\n return\n }\n }\n }\n }\n\n document.addEventListener('keydown', handleKeyDown, true)\n return () => document.removeEventListener('keydown', handleKeyDown, true)\n }, [currentPage, handleBack, submenuEnabled, submenuShortcut, openSubmenu, groups])\n\n const placeholder =\n currentPage === 'main'\n ? t('cmdkPlugin:search')\n : t('general:searchBy', {\n label: currentPage.useAsTitleLabel,\n })\n\n const shouldDisableFilter = currentPage !== 'main'\n\n // Static footer shortcuts based on current page\n const footerShortcuts =\n currentPage === 'main' && submenuEnabled && submenuShortcut === 'shift+enter'\n ? [\n { key: 'Enter', label: t('cmdkPlugin:navigate') },\n { key: 'Shift + Enter', label: t('cmdkPlugin:searchInCollection') },\n ]\n : currentPage === 'main' && submenuEnabled && submenuShortcut === 'enter'\n ? [\n { key: 'Enter', label: t('cmdkPlugin:searchInCollection') },\n { key: 'Shift + Enter', label: t('cmdkPlugin:navigate') },\n ]\n : currentPage === 'main'\n ? [{ key: 'Enter', label: t('cmdkPlugin:navigate') }]\n : [{ key: 'Enter', label: t('cmdkPlugin:open') }]\n\n const handleBackdropClick = (e: React.MouseEvent<HTMLDivElement>) => {\n // Close modal only if clicking the backdrop (not the command itself)\n if (e.target === e.currentTarget) {\n closeMenu()\n }\n }\n // console.log('Rendering CommandMenuComponent', { currentPage, groups, items, submenuItems })\n return (\n <Modal slug={MODAL_SLUG}>\n {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}\n <div\n className={`command-modal ${blurBg ? 'command-modal--blur' : ''}`}\n onClick={handleBackdropClick}\n >\n <Command label=\"Command Menu\" shouldFilter={!shouldDisableFilter}>\n {/* Header for submenu navigation */}\n {currentPage !== 'main' && (\n <div className=\"command__header\">\n <button className=\"command__back-button\" onClick={handleBack} type=\"button\">\n <ChevronLeft size={16} />\n </button>\n <span className=\"command__header-label\">\n {t('cmdkPlugin:searchIn', { label: currentPage.label })}\n </span>\n </div>\n )}\n\n <CommandInput onValueChange={setSearch} placeholder={placeholder} value={search} />\n <CommandList ref={commandListRef}>\n <CommandEmpty>\n {isLoadingSubmenu ? t('cmdkPlugin:loading') : t('cmdkPlugin:noResults')}\n </CommandEmpty>\n\n {/* Main page view */}\n {currentPage === 'main' &&\n groups.map((group, index) => {\n if (group.items.length === 0) {\n return null\n }\n\n let titleName = group.title\n if (group.title === 'Collections') {\n titleName = t('general:collections')\n }\n if (group.title === 'Globals') {\n titleName = t('general:globals')\n }\n\n const isRenderSeparator = !(index === groups.length - 1 && items.length === 0)\n return (\n <Fragment key={group.title}>\n <CommandGroup heading={titleName}>\n {group.items.map((item) => {\n const isDynamicIcon = typeof item.icon === 'string'\n const IconComponent = isDynamicIcon ? null : (item.icon as LucideIcon)\n return (\n <CommandItem\n data-action-type={item.action.type}\n data-item-type={item.type}\n key={item.slug}\n keywords={[titleName, item.label]}\n onSelect={() => handleSelect(item)}\n value={item.slug}\n >\n {isDynamicIcon ? (\n <LucideIconDynamic\n className=\"command__item-icon\"\n name={item.icon as IconName}\n />\n ) : (\n IconComponent && <IconComponent className=\"command__item-icon\" />\n )}\n {item.label}\n {submenuEnabled && item.type === 'collection' && (\n <CommandShortcut>›</CommandShortcut>\n )}\n </CommandItem>\n )\n })}\n </CommandGroup>\n {isRenderSeparator && <CommandSeparator />}\n </Fragment>\n )\n })}\n\n {/* Stray items on main page */}\n {currentPage === 'main' &&\n items?.map((item) => {\n const isDynamicIcon = typeof item.icon === 'string'\n const IconComponent = isDynamicIcon ? null : (item.icon as LucideIcon)\n return (\n <CommandItem\n data-action-type={item.action.type}\n data-item-type={item.type}\n key={item.slug}\n keywords={[item.label]}\n onSelect={() => handleSelect(item)}\n value={item.slug}\n >\n {isDynamicIcon ? (\n <LucideIconDynamic\n className=\"command__item-icon\"\n name={item.icon as IconName}\n />\n ) : (\n IconComponent && <IconComponent className=\"command__item-icon\" />\n )}\n {item.label}\n </CommandItem>\n )\n })}\n\n {/* Submenu page view */}\n {currentPage !== 'main' &&\n submenuItems.map((item) => {\n const isDynamicIcon = typeof item.icon === 'string'\n const IconComponent = isDynamicIcon ? null : (item.icon as LucideIcon)\n return (\n <CommandItem\n data-action-type={item.action.type}\n data-item-type={item.type}\n key={item.slug}\n onSelect={() => handleSelect(item)}\n value={item.slug}\n >\n {isDynamicIcon ? (\n <LucideIconDynamic\n className=\"command__item-icon\"\n name={item.icon as IconName}\n />\n ) : (\n IconComponent && <IconComponent className=\"command__item-icon\" />\n )}\n {item.label}\n </CommandItem>\n )\n })}\n </CommandList>\n\n {/* Footer with static shortcuts */}\n {footerShortcuts && footerShortcuts.length > 0 && (\n <div className=\"command__footer\">\n {footerShortcuts.map((shortcut, index) => (\n <span key={index}>\n <kbd>{formatShortcutKey(shortcut.key)}</kbd> {shortcut.label}\n </span>\n ))}\n </div>\n )}\n </Command>\n </div>\n </Modal>\n )\n}\n\nconst MemoizedCommandMenuComponent = memo(CommandMenuComponent)\n\nexport const CommandMenuProvider: React.FC<CommandMenuContextProps> = ({\n children,\n pluginConfig,\n}) => {\n const { closeModal, isModalOpen, openModal, toggleModal } = useModal()\n const [currentPage, setCurrentPage] = useState<CommandMenuPage>('main')\n\n useHotkeys(\n pluginConfig.shortcut || ['meta+k', 'ctrl+k'],\n (event) => {\n event.preventDefault()\n event.stopPropagation()\n toggleModal(MODAL_SLUG)\n },\n [toggleModal],\n )\n const { config } = useConfig()\n const { i18n } = useTranslation()\n const currentLang = i18n.language\n const { groups, items } = useMemo(() => {\n return createDefaultGroups(config, currentLang, pluginConfig)\n }, [config, currentLang, pluginConfig])\n\n return (\n <CommandMenuContext.Provider\n value={{\n closeMenu: () => closeModal(MODAL_SLUG),\n currentPage,\n groups,\n isOpen: isModalOpen(MODAL_SLUG),\n items,\n openMenu: () => openModal(MODAL_SLUG),\n setPage: setCurrentPage,\n toggleMenu: () => toggleModal(MODAL_SLUG),\n }}\n >\n {children}\n <MemoizedCommandMenuComponent pluginConfig={pluginConfig} />\n </CommandMenuContext.Provider>\n )\n}\n"],"names":["Modal","useConfig","useModal","useTranslation","ArrowBigUp","ChevronLeft","Command","CommandIcon","Option","useRouter","createContext","Fragment","memo","useCallback","useContext","useEffect","useMemo","useRef","useState","useHotkeys","createDefaultGroups","CommandEmpty","CommandGroup","CommandInput","CommandItem","CommandList","CommandSeparator","CommandShortcut","LucideIconDynamic","MODAL_SLUG","CommandMenuContext","closeMenu","currentPage","groups","isOpen","items","openMenu","setPage","toggleMenu","useCommandMenu","context","ITEM_SELECTOR","getSelectedElement","containerRef","current","querySelector","CommandMenuComponent","pluginConfig","search","setSearch","submenuItems","setSubmenuItems","isLoadingSubmenu","setIsLoadingSubmenu","isMac","setIsMac","commandListRef","router","t","test","navigator","platform","submenuEnabled","submenu","enabled","submenuShortcut","shortcut","blurBg","formatShortcutKey","key","parts","split","map","part","trim","elements","index","lowerPart","toLowerCase","content","size","length","fetchDocuments","type","searchParam","useAsTitle","encodeURIComponent","response","fetch","slug","data","json","docs","Array","isArray","doc","id","action","href","icon","icons","undefined","label","timeoutId","setTimeout","clearTimeout","handleBack","executeItemAction","item","body","JSON","stringify","headers","method","push","openSubmenu","useAsTitleLabel","handleSelect","handleKeyDown","e","preventDefault","stopPropagation","selectedElement","itemType","getAttribute","itemSlug","isShiftPressed","shiftKey","shouldOpenSubmenu","flatMap","g","find","i","document","addEventListener","removeEventListener","placeholder","shouldDisableFilter","footerShortcuts","handleBackdropClick","target","currentTarget","div","className","onClick","shouldFilter","button","span","onValueChange","value","ref","group","titleName","title","isRenderSeparator","heading","isDynamicIcon","IconComponent","data-action-type","data-item-type","keywords","onSelect","name","kbd","MemoizedCommandMenuComponent","CommandMenuProvider","children","closeModal","isModalOpen","openModal","toggleModal","setCurrentPage","event","config","i18n","currentLang","language","Provider"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/components/CommandMenuContext.tsx"],"sourcesContent":["'use client'\nimport type { LucideIcon } from 'lucide-react'\nimport type { CustomTranslationsKeys, CustomTranslationsObject } from 'src/translations/index'\n\nimport './modal.scss'\n\nimport type {\n CollectionContext,\n CommandMenuContextProps,\n CommandMenuGroup,\n CommandMenuItem,\n CommandMenuPage,\n GenericCollectionDocument,\n IconName,\n PluginCommandMenuConfig,\n} from 'src/types'\n\nimport { Modal, useConfig, useModal, useRouteTransition, useTranslation } from '@payloadcms/ui'\nimport { ArrowBigUp, ChevronLeft, Command as CommandIcon, Option } from 'lucide-react'\nimport { usePathname, useRouter } from 'next/navigation'\nimport { formatAdminURL } from 'payload/shared'\nimport {\n createContext,\n Fragment,\n memo,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react'\nimport { useHotkeys } from 'react-hotkeys-hook'\n\nimport { createDefaultGroups } from '../utils/index'\nimport { getCommandMenuAction } from '../utils/registry'\nimport {\n Command,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n CommandSeparator,\n CommandShortcut,\n} from './cmdk/index'\nimport { LucideIconDynamic } from './LucideIcon'\n\nconst MODAL_SLUG = 'command-menu'\n\ninterface CommandMenuContextType {\n closeMenu: () => void\n currentPage: CommandMenuPage\n groups: CommandMenuGroup[]\n isOpen: boolean\n items: CommandMenuItem[]\n openMenu: () => void\n setPage: (page: CommandMenuPage) => void\n toggleMenu: () => void\n}\n\nconst CommandMenuContext = createContext<CommandMenuContextType>({\n closeMenu: () => {},\n currentPage: 'main',\n groups: [],\n isOpen: false,\n items: [],\n openMenu: () => {},\n setPage: () => {},\n toggleMenu: () => {},\n})\n\nexport const useCommandMenu = () => {\n const context = useContext(CommandMenuContext)\n return context\n}\n\nconst ITEM_SELECTOR = `[cmdk-item=\"\"]`\n\nfunction getSelectedElement(containerRef: React.RefObject<HTMLElement | null>) {\n return containerRef.current?.querySelector(`${ITEM_SELECTOR}[aria-selected=\"true\"]`)\n}\n\nconst CommandMenuComponent: React.FC<{\n pluginConfig: PluginCommandMenuConfig\n}> = ({ pluginConfig }) => {\n const [search, setSearch] = useState('')\n const [submenuItems, setSubmenuItems] = useState<CommandMenuItem[]>([])\n const [isLoadingSubmenu, setIsLoadingSubmenu] = useState(false)\n const [isMac, setIsMac] = useState(false)\n\n const commandListRef = useRef<HTMLDivElement>(null)\n\n const { closeMenu, currentPage, groups, items, setPage } = useCommandMenu()\n const router = useRouter()\n const pathname = usePathname()\n const { startRouteTransition } = useRouteTransition()\n const { t } = useTranslation<CustomTranslationsObject, CustomTranslationsKeys>()\n const { config } = useConfig()\n\n // Detect which collection the user is currently viewing (if any) and whether\n // they are on the list page or a specific document/create page.\n // Use the configured admin route instead of hardcoding '/admin' so that\n // projects with a custom adminRoute still match correctly.\n const { currentCollectionSlug, isDocumentContext } = useMemo(() => {\n if (!pathname) {\n return { currentCollectionSlug: null, isDocumentContext: false }\n }\n const adminRoute = config.routes?.admin ?? '/admin'\n // Escape any regex special characters in the admin route path\n const escapedRoute = adminRoute.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n // Group 1 = collection slug, Group 2 (optional) = document ID or 'create'\n const match = pathname.match(new RegExp(`${escapedRoute}/collections/([^/]+)(?:/([^/]+))?`))\n return {\n currentCollectionSlug: match?.[1] ?? null,\n isDocumentContext: match?.[2] != null,\n }\n }, [pathname, config.routes?.admin])\n\n /** Returns true if an item/group should be visible given the current route. */\n const isEntryVisible = useCallback(\n (entry: {\n collectionContext?: CollectionContext\n collectionSlugs?: readonly string[] | string[]\n }): boolean => {\n // --- collectionSlugs filter ---\n if (entry.collectionSlugs && entry.collectionSlugs.length > 0) {\n if (\n currentCollectionSlug === null ||\n !(entry.collectionSlugs as string[]).includes(currentCollectionSlug)\n ) {\n return false\n }\n }\n // --- collectionContext filter ---\n if (entry.collectionContext && entry.collectionContext.length > 0) {\n // context filter only makes sense on a collection page\n if (currentCollectionSlug === null) {\n return false\n }\n const currentContext = isDocumentContext ? 'document' : 'list'\n if (!entry.collectionContext.includes(currentContext)) {\n return false\n }\n }\n return true\n },\n [currentCollectionSlug, isDocumentContext],\n )\n\n // Filter groups to only those visible on the current page\n const visibleGroups = useMemo(() => {\n return groups\n .filter((group) => isEntryVisible(group))\n .map((group) => ({\n ...group,\n items: group.items.filter((item) => isEntryVisible(item)),\n }))\n .filter((group) => group.items.length > 0)\n }, [groups, isEntryVisible])\n\n // Filter stray items to only those visible on the current page\n const visibleItems = useMemo(() => {\n return items.filter((item) => isEntryVisible(item))\n }, [items, isEntryVisible])\n\n useEffect(() => {\n setIsMac(/Mac|iPhone|iPod|iPad/i.test(navigator.platform))\n }, [])\n\n const submenuEnabled = pluginConfig?.submenu?.enabled !== false\n const submenuShortcut = pluginConfig?.submenu?.shortcut || 'shift+enter'\n const blurBg = pluginConfig?.blurBg !== false\n\n const formatShortcutKey = (key: string): React.ReactNode => {\n // Handle compound shortcuts like \"Shift + Enter\"\n const parts = key.split('+').map((part) => part.trim())\n const elements = parts.map((part, index) => {\n const lowerPart = part.toLowerCase()\n let content: React.ReactNode\n\n if (lowerPart === 'ctrl' || lowerPart === 'cmd') {\n content = isMac ? <CommandIcon size={12} /> : 'Ctrl'\n } else if (lowerPart === 'meta') {\n content = isMac ? <CommandIcon size={12} /> : 'Ctrl'\n } else if (lowerPart === 'shift') {\n content = isMac ? <ArrowBigUp size={12} /> : 'Shift'\n } else if (lowerPart === 'alt') {\n content = isMac ? <Option size={12} /> : 'Alt'\n } else {\n content = part\n }\n\n return (\n <Fragment key={index}>\n {content}\n {!isMac && index < parts.length - 1 && ' + '}\n </Fragment>\n )\n })\n\n return <>{elements}</>\n }\n\n const adminRoute = config.routes?.admin ?? '/admin'\n // Debounced search for submenu\n useEffect(() => {\n if (currentPage === 'main') {\n return\n }\n\n const fetchDocuments = async () => {\n if (currentPage.type !== 'collection-search') {\n return\n }\n\n setIsLoadingSubmenu(true)\n try {\n const searchParam = search\n ? `&where[${currentPage.useAsTitle}][like]=${encodeURIComponent(search)}`\n : ''\n const response = await fetch(\n `/api/${currentPage.slug}?limit=10${searchParam}&select[${currentPage.useAsTitle}]=true`,\n )\n const data = await response.json()\n\n if (data.docs && Array.isArray(data.docs)) {\n const docs: CommandMenuItem[] = data.docs.map((doc: GenericCollectionDocument) => ({\n slug: `${currentPage.slug}-${doc.id}`,\n type: 'custom' as const,\n action: {\n type: 'link',\n href: formatAdminURL({\n adminRoute,\n path: `/collections/${currentPage.slug}/${doc.id}`,\n }),\n },\n icon: pluginConfig?.submenu?.icons?.[currentPage.slug] ?? undefined,\n label: doc[currentPage.useAsTitle] || doc.id,\n }))\n setSubmenuItems(docs)\n }\n } catch {\n setSubmenuItems([])\n } finally {\n setIsLoadingSubmenu(false)\n }\n }\n\n const timeoutId = setTimeout(fetchDocuments, 300)\n return () => clearTimeout(timeoutId)\n }, [search, currentPage, pluginConfig?.submenu?.icons, adminRoute])\n\n const handleBack = useCallback(() => {\n setPage('main')\n setSearch('')\n setSubmenuItems([])\n }, [setPage])\n\n const executeItemAction = useCallback(\n async (item: CommandMenuItem) => {\n try {\n // Execute the item's action\n switch (item.action.type) {\n case 'api': {\n const response = await fetch(item.action.href, {\n body: item.action.body ? JSON.stringify(item.action.body) : undefined,\n headers: {\n 'Content-Type': 'application/json',\n },\n method: item.action.method || 'GET',\n })\n if (!response.ok) {\n throw new Error(\n `[payload-cmdk] API action failed: ${response.status} ${response.statusText}`,\n )\n }\n break\n }\n case 'function': {\n const handler = getCommandMenuAction(item.action.key)\n if (handler) {\n await handler()\n } else {\n //eslint-disable-next-line no-console\n console.warn(\n `[payload-cmdk] No handler registered for function action key \"${item.action.key}\". ` +\n `Call registerCommandMenuAction(\"${item.action.key}\", fn) on the client.`,\n )\n }\n break\n }\n case 'link': {\n const href = item.action.href\n startRouteTransition(() => router.push(href))\n break\n }\n default:\n break\n }\n } finally {\n closeMenu()\n setSearch('')\n setPage('main')\n }\n },\n [router, closeMenu, setPage, startRouteTransition],\n )\n\n const openSubmenu = useCallback(\n (item: CommandMenuItem) => {\n setPage({\n slug: item.slug,\n type: 'collection-search',\n label: item.label,\n useAsTitle: item.useAsTitle || 'id',\n useAsTitleLabel: item.useAsTitleLabel || item.useAsTitle || 'id',\n })\n setSearch('')\n //set isLoadingSubmenu to true to show loading state while fetching\n setIsLoadingSubmenu(true)\n setSubmenuItems([])\n },\n [setPage],\n )\n\n const handleSelect = useCallback(\n async (item: CommandMenuItem) => {\n await executeItemAction(item)\n },\n [executeItemAction],\n )\n\n // Handle keyboard events for navigation and back\n useEffect(() => {\n const handleKeyDown = (e: KeyboardEvent) => {\n // ESC key for back navigation in submenu\n if (e.key === 'Escape' && currentPage !== 'main') {\n e.preventDefault()\n e.stopPropagation()\n handleBack()\n return\n }\n\n // Enter/Shift+Enter handling for collection submenu\n if (e.key === 'Enter' && currentPage === 'main') {\n const selectedElement = getSelectedElement(commandListRef)\n const itemType = selectedElement?.getAttribute('data-item-type')\n const itemSlug = selectedElement?.getAttribute('data-value')\n\n if (submenuEnabled && itemType === 'collection' && itemSlug) {\n const isShiftPressed = e.shiftKey\n const shouldOpenSubmenu =\n (submenuShortcut === 'shift+enter' && isShiftPressed) ||\n (submenuShortcut === 'enter' && !isShiftPressed)\n\n if (shouldOpenSubmenu) {\n e.preventDefault()\n e.stopPropagation()\n\n // Find the item in groups\n const item = visibleGroups.flatMap((g) => g.items).find((i) => i.slug === itemSlug)\n if (item) {\n openSubmenu(item)\n }\n return\n }\n }\n }\n }\n\n document.addEventListener('keydown', handleKeyDown, true)\n return () => document.removeEventListener('keydown', handleKeyDown, true)\n }, [currentPage, handleBack, submenuEnabled, submenuShortcut, openSubmenu, visibleGroups])\n\n const placeholder =\n currentPage === 'main'\n ? t('cmdkPlugin:search')\n : t('general:searchBy', {\n label: currentPage.useAsTitleLabel,\n })\n\n const shouldDisableFilter = currentPage !== 'main'\n\n // Static footer shortcuts based on current page\n const footerShortcuts =\n currentPage === 'main' && submenuEnabled && submenuShortcut === 'shift+enter'\n ? [\n { key: 'Enter', label: t('cmdkPlugin:navigate') },\n { key: 'Shift + Enter', label: t('cmdkPlugin:searchInCollection') },\n ]\n : currentPage === 'main' && submenuEnabled && submenuShortcut === 'enter'\n ? [\n { key: 'Enter', label: t('cmdkPlugin:searchInCollection') },\n { key: 'Shift + Enter', label: t('cmdkPlugin:navigate') },\n ]\n : currentPage === 'main'\n ? [{ key: 'Enter', label: t('cmdkPlugin:navigate') }]\n : [{ key: 'Enter', label: t('cmdkPlugin:open') }]\n\n const handleBackdropClick = (e: React.MouseEvent<HTMLDivElement>) => {\n // Close modal only if clicking the backdrop (not the command itself)\n if (e.target === e.currentTarget) {\n closeMenu()\n }\n }\n // console.log('Rendering CommandMenuComponent', { currentPage, groups, items, submenuItems })\n return (\n <Modal slug={MODAL_SLUG}>\n {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}\n <div\n className={`command-modal ${blurBg ? 'command-modal--blur' : ''}`}\n onClick={handleBackdropClick}\n >\n <Command label=\"Command Menu\" shouldFilter={!shouldDisableFilter}>\n {/* Header for submenu navigation */}\n {currentPage !== 'main' && (\n <div className=\"command__header\">\n <button className=\"command__back-button\" onClick={handleBack} type=\"button\">\n <ChevronLeft size={16} />\n </button>\n <span className=\"command__header-label\">\n {t('cmdkPlugin:searchIn', { label: currentPage.label })}\n </span>\n </div>\n )}\n\n <CommandInput onValueChange={setSearch} placeholder={placeholder} value={search} />\n <CommandList ref={commandListRef}>\n <CommandEmpty>\n {isLoadingSubmenu ? t('cmdkPlugin:loading') : t('cmdkPlugin:noResults')}\n </CommandEmpty>\n\n {/* Main page view */}\n {currentPage === 'main' &&\n visibleGroups.map((group, index) => {\n if (group.items.length === 0) {\n return null\n }\n\n let titleName = group.title\n if (group.title === 'Collections') {\n titleName = t('general:collections')\n }\n if (group.title === 'Globals') {\n titleName = t('general:globals')\n }\n\n const isRenderSeparator = !(\n index === visibleGroups.length - 1 && visibleItems.length === 0\n )\n return (\n <Fragment key={group.title}>\n <CommandGroup heading={titleName}>\n {group.items.map((item) => {\n const isDynamicIcon = typeof item.icon === 'string'\n const IconComponent = isDynamicIcon ? null : (item.icon as LucideIcon)\n return (\n <CommandItem\n data-action-type={item.action.type}\n data-item-type={item.type}\n key={item.slug}\n keywords={[titleName, item.label]}\n onSelect={() => handleSelect(item)}\n value={item.slug}\n >\n {isDynamicIcon ? (\n <LucideIconDynamic\n className=\"command__item-icon\"\n name={item.icon as IconName}\n />\n ) : (\n IconComponent && <IconComponent className=\"command__item-icon\" />\n )}\n {item.label}\n {submenuEnabled && item.type === 'collection' && (\n <CommandShortcut>›</CommandShortcut>\n )}\n </CommandItem>\n )\n })}\n </CommandGroup>\n {isRenderSeparator && <CommandSeparator />}\n </Fragment>\n )\n })}\n\n {/* Stray items on main page */}\n {currentPage === 'main' &&\n visibleItems?.map((item) => {\n const isDynamicIcon = typeof item.icon === 'string'\n const IconComponent = isDynamicIcon ? null : (item.icon as LucideIcon)\n return (\n <CommandItem\n data-action-type={item.action.type}\n data-item-type={item.type}\n key={item.slug}\n keywords={[item.label]}\n onSelect={() => handleSelect(item)}\n value={item.slug}\n >\n {isDynamicIcon ? (\n <LucideIconDynamic\n className=\"command__item-icon\"\n name={item.icon as IconName}\n />\n ) : (\n IconComponent && <IconComponent className=\"command__item-icon\" />\n )}\n {item.label}\n </CommandItem>\n )\n })}\n\n {/* Submenu page view */}\n {currentPage !== 'main' &&\n submenuItems.map((item) => {\n const isDynamicIcon = typeof item.icon === 'string'\n const IconComponent = isDynamicIcon ? null : (item.icon as LucideIcon)\n return (\n <CommandItem\n data-action-type={item.action.type}\n data-item-type={item.type}\n key={item.slug}\n onSelect={() => handleSelect(item)}\n value={item.slug}\n >\n {isDynamicIcon ? (\n <LucideIconDynamic\n className=\"command__item-icon\"\n name={item.icon as IconName}\n />\n ) : (\n IconComponent && <IconComponent className=\"command__item-icon\" />\n )}\n {item.label}\n </CommandItem>\n )\n })}\n </CommandList>\n\n {/* Footer with static shortcuts */}\n {footerShortcuts && footerShortcuts.length > 0 && (\n <div className=\"command__footer\">\n {footerShortcuts.map((shortcut, index) => (\n <span key={index}>\n <kbd>{formatShortcutKey(shortcut.key)}</kbd> {shortcut.label}\n </span>\n ))}\n </div>\n )}\n </Command>\n </div>\n </Modal>\n )\n}\n\nconst MemoizedCommandMenuComponent = memo(CommandMenuComponent)\n\nexport const CommandMenuProvider: React.FC<CommandMenuContextProps> = ({\n children,\n pluginConfig,\n}) => {\n const { closeModal, isModalOpen, openModal, toggleModal } = useModal()\n const [currentPage, setCurrentPage] = useState<CommandMenuPage>('main')\n\n useHotkeys(\n pluginConfig.shortcut || ['meta+k', 'ctrl+k'],\n (event) => {\n event.preventDefault()\n event.stopPropagation()\n toggleModal(MODAL_SLUG)\n },\n [toggleModal],\n )\n const { config } = useConfig()\n const { i18n } = useTranslation()\n const currentLang = i18n.language\n const { groups, items } = useMemo(() => {\n return createDefaultGroups(config, currentLang, pluginConfig)\n }, [config, currentLang, pluginConfig])\n\n return (\n <CommandMenuContext.Provider\n value={{\n closeMenu: () => closeModal(MODAL_SLUG),\n currentPage,\n groups,\n isOpen: isModalOpen(MODAL_SLUG),\n items,\n openMenu: () => openModal(MODAL_SLUG),\n setPage: setCurrentPage,\n toggleMenu: () => toggleModal(MODAL_SLUG),\n }}\n >\n {children}\n <MemoizedCommandMenuComponent pluginConfig={pluginConfig} />\n </CommandMenuContext.Provider>\n )\n}\n"],"names":["Modal","useConfig","useModal","useRouteTransition","useTranslation","ArrowBigUp","ChevronLeft","Command","CommandIcon","Option","usePathname","useRouter","formatAdminURL","createContext","Fragment","memo","useCallback","useContext","useEffect","useMemo","useRef","useState","useHotkeys","createDefaultGroups","getCommandMenuAction","CommandEmpty","CommandGroup","CommandInput","CommandItem","CommandList","CommandSeparator","CommandShortcut","LucideIconDynamic","MODAL_SLUG","CommandMenuContext","closeMenu","currentPage","groups","isOpen","items","openMenu","setPage","toggleMenu","useCommandMenu","context","ITEM_SELECTOR","getSelectedElement","containerRef","current","querySelector","CommandMenuComponent","pluginConfig","search","setSearch","submenuItems","setSubmenuItems","isLoadingSubmenu","setIsLoadingSubmenu","isMac","setIsMac","commandListRef","router","pathname","startRouteTransition","t","config","currentCollectionSlug","isDocumentContext","adminRoute","routes","admin","escapedRoute","replace","match","RegExp","isEntryVisible","entry","collectionSlugs","length","includes","collectionContext","currentContext","visibleGroups","filter","group","map","item","visibleItems","test","navigator","platform","submenuEnabled","submenu","enabled","submenuShortcut","shortcut","blurBg","formatShortcutKey","key","parts","split","part","trim","elements","index","lowerPart","toLowerCase","content","size","fetchDocuments","type","searchParam","useAsTitle","encodeURIComponent","response","fetch","slug","data","json","docs","Array","isArray","doc","id","action","href","path","icon","icons","undefined","label","timeoutId","setTimeout","clearTimeout","handleBack","executeItemAction","body","JSON","stringify","headers","method","ok","Error","status","statusText","handler","console","warn","push","openSubmenu","useAsTitleLabel","handleSelect","handleKeyDown","e","preventDefault","stopPropagation","selectedElement","itemType","getAttribute","itemSlug","isShiftPressed","shiftKey","shouldOpenSubmenu","flatMap","g","find","i","document","addEventListener","removeEventListener","placeholder","shouldDisableFilter","footerShortcuts","handleBackdropClick","target","currentTarget","div","className","onClick","shouldFilter","button","span","onValueChange","value","ref","titleName","title","isRenderSeparator","heading","isDynamicIcon","IconComponent","data-action-type","data-item-type","keywords","onSelect","name","kbd","MemoizedCommandMenuComponent","CommandMenuProvider","children","closeModal","isModalOpen","openModal","toggleModal","setCurrentPage","event","i18n","currentLang","language","Provider"],"mappings":"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"}
|
package/dist/exports/client.d.ts
CHANGED
package/dist/exports/client.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { CommandMenuProvider, useCommandMenu } from '../components/CommandMenuContext';
|
|
2
2
|
export { default as SearchButton } from '../components/SearchButton';
|
|
3
|
+
export { registerCommandMenuAction, unregisterCommandMenuAction } from '../utils/registry';
|
|
3
4
|
|
|
4
5
|
//# sourceMappingURL=client.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/exports/client.ts"],"sourcesContent":["export { CommandMenuProvider, useCommandMenu } from '../components/CommandMenuContext'\nexport { default as SearchButton } from '../components/SearchButton'\n"],"names":["CommandMenuProvider","useCommandMenu","default","SearchButton"],"mappings":"AAAA,SAASA,mBAAmB,EAAEC,cAAc,QAAQ,mCAAkC;AACtF,SAASC,WAAWC,YAAY,QAAQ,6BAA4B"}
|
|
1
|
+
{"version":3,"sources":["../../src/exports/client.ts"],"sourcesContent":["export { CommandMenuProvider, useCommandMenu } from '../components/CommandMenuContext'\nexport { default as SearchButton } from '../components/SearchButton'\nexport {\n registerCommandMenuAction,\n unregisterCommandMenuAction,\n} from '../utils/registry'\n"],"names":["CommandMenuProvider","useCommandMenu","default","SearchButton","registerCommandMenuAction","unregisterCommandMenuAction"],"mappings":"AAAA,SAASA,mBAAmB,EAAEC,cAAc,QAAQ,mCAAkC;AACtF,SAASC,WAAWC,YAAY,QAAQ,6BAA4B;AACpE,SACEC,yBAAyB,EACzBC,2BAA2B,QACtB,oBAAmB"}
|
package/dist/types.d.ts
CHANGED
|
@@ -5,12 +5,46 @@ export type LocalizedString = {
|
|
|
5
5
|
[locale: string]: string;
|
|
6
6
|
} | string;
|
|
7
7
|
export type InternalIcon = IconName | LucideIcon;
|
|
8
|
+
/**
|
|
9
|
+
* Controls which context within a collection a custom item/group is visible in.
|
|
10
|
+
* Pass an array with one or both values:
|
|
11
|
+
*
|
|
12
|
+
* - `'list'` — collection list page (`/admin/collections/{slug}`)
|
|
13
|
+
* - `'document'` — document edit/create page (`/admin/collections/{slug}/{id|create}`)
|
|
14
|
+
*
|
|
15
|
+
* Omit the field (or pass both values) to show in all contexts.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* collectionContext: ['list'] // list page only
|
|
19
|
+
* collectionContext: ['document'] // document page only
|
|
20
|
+
* collectionContext: ['list', 'document'] // both (same as omitting)
|
|
21
|
+
*/
|
|
22
|
+
export type CollectionContext = ('document' | 'list')[];
|
|
8
23
|
/**
|
|
9
24
|
* Custom menu item, for configuration.
|
|
10
25
|
* Will be mapped to CommandMenuItem internally.
|
|
11
26
|
*/
|
|
12
27
|
export type CustomMenuItem = {
|
|
13
28
|
action: CommandMenuAction;
|
|
29
|
+
/**
|
|
30
|
+
* Restrict visibility to specific contexts within a collection page.
|
|
31
|
+
* Pass an array with one or both values — omit to show in all contexts.
|
|
32
|
+
*
|
|
33
|
+
* - `'list'` — collection list page (`/admin/collections/{slug}`)
|
|
34
|
+
* - `'document'` — document edit/create page (`/admin/collections/{slug}/{id|create}`)
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* collectionContext: ['list'] // list page only
|
|
38
|
+
* collectionContext: ['document'] // document page only
|
|
39
|
+
* collectionContext: ['list', 'document'] // both (same as omitting)
|
|
40
|
+
*/
|
|
41
|
+
collectionContext?: CollectionContext;
|
|
42
|
+
/**
|
|
43
|
+
* Restrict this item to only appear when the user is on one of these collection pages.
|
|
44
|
+
* Matches against the current route (e.g. `/admin/collections/{slug}`).
|
|
45
|
+
* If omitted or empty, the item appears on all pages.
|
|
46
|
+
*/
|
|
47
|
+
collectionSlugs?: CollectionSlug[];
|
|
14
48
|
icon?: IconName;
|
|
15
49
|
label: LocalizedString;
|
|
16
50
|
slug: string;
|
|
@@ -23,6 +57,25 @@ export type CustomMenuItem = {
|
|
|
23
57
|
* Groups will be merged if they have the same title.
|
|
24
58
|
*/
|
|
25
59
|
export type CustomMenuGroup = {
|
|
60
|
+
/**
|
|
61
|
+
* Restrict visibility to specific contexts within a collection page.
|
|
62
|
+
* Pass an array with one or both values — omit to show in all contexts.
|
|
63
|
+
*
|
|
64
|
+
* - `'list'` — collection list page (`/admin/collections/{slug}`)
|
|
65
|
+
* - `'document'` — document edit/create page (`/admin/collections/{slug}/{id|create}`)
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* collectionContext: ['list'] // list page only
|
|
69
|
+
* collectionContext: ['document'] // document page only
|
|
70
|
+
* collectionContext: ['list', 'document'] // both (same as omitting)
|
|
71
|
+
*/
|
|
72
|
+
collectionContext?: CollectionContext;
|
|
73
|
+
/**
|
|
74
|
+
* Restrict this group to only appear when the user is on one of these collection pages.
|
|
75
|
+
* Matches against the current route (e.g. `/admin/collections/{slug}`).
|
|
76
|
+
* If omitted or empty, the group appears on all pages.
|
|
77
|
+
*/
|
|
78
|
+
collectionSlugs?: CollectionSlug[];
|
|
26
79
|
items: CustomMenuItem[];
|
|
27
80
|
title: LocalizedString;
|
|
28
81
|
type: 'group';
|
|
@@ -148,12 +201,39 @@ export interface CommandMenuActionAPICall {
|
|
|
148
201
|
method?: 'DELETE' | 'GET' | 'POST' | 'PUT';
|
|
149
202
|
type: 'api';
|
|
150
203
|
}
|
|
151
|
-
|
|
204
|
+
/**
|
|
205
|
+
* Calls a function registered via `registerCommandMenuAction(key, fn)` on the client.
|
|
206
|
+
* Since the plugin config is serialized across the server→client boundary, functions
|
|
207
|
+
* cannot be passed directly — use a string key to reference a registered handler instead.
|
|
208
|
+
*
|
|
209
|
+
* @example
|
|
210
|
+
* // In payload.config.ts:
|
|
211
|
+
* action: { type: 'function', key: 'save-current-doc' }
|
|
212
|
+
*
|
|
213
|
+
* // In your client-side code:
|
|
214
|
+
* import { registerCommandMenuAction } from '@veiag/payload-cmdk/client'
|
|
215
|
+
* registerCommandMenuAction('save-current-doc', () => { ... })
|
|
216
|
+
*/
|
|
217
|
+
export interface CommandMenuActionFunction {
|
|
218
|
+
/**
|
|
219
|
+
* Key used to look up the registered handler via `registerCommandMenuAction`.
|
|
220
|
+
*/
|
|
221
|
+
key: string;
|
|
222
|
+
type: 'function';
|
|
223
|
+
}
|
|
224
|
+
export type CommandMenuAction = CommandMenuActionAPICall | CommandMenuActionFunction | CommandMenuActionLink;
|
|
152
225
|
export interface CommandMenuItem {
|
|
153
226
|
/**
|
|
154
227
|
* Action to perform when the command menu item is selected.
|
|
155
228
|
*/
|
|
156
229
|
action: CommandMenuAction;
|
|
230
|
+
/** Populated from `CustomMenuItem.collectionContext`. */
|
|
231
|
+
collectionContext?: CollectionContext;
|
|
232
|
+
/**
|
|
233
|
+
* Restrict this item to only appear when the user is on one of these collection pages.
|
|
234
|
+
* Populated from `CustomMenuItem.collectionSlugs`.
|
|
235
|
+
*/
|
|
236
|
+
collectionSlugs?: CollectionSlug[];
|
|
157
237
|
icon?: InternalIcon;
|
|
158
238
|
label: string;
|
|
159
239
|
slug: string;
|
|
@@ -177,6 +257,13 @@ export interface CommandMenuItem {
|
|
|
177
257
|
useAsTitleLabel?: string;
|
|
178
258
|
}
|
|
179
259
|
export interface CommandMenuGroup {
|
|
260
|
+
/** Populated from `CustomMenuGroup.collectionContext`. */
|
|
261
|
+
collectionContext?: CollectionContext;
|
|
262
|
+
/**
|
|
263
|
+
* Restrict this group to only appear when the user is on one of these collection pages.
|
|
264
|
+
* Populated from `CustomMenuGroup.collectionSlugs`.
|
|
265
|
+
*/
|
|
266
|
+
collectionSlugs?: CollectionSlug[];
|
|
180
267
|
items: CommandMenuItem[];
|
|
181
268
|
title: string;
|
|
182
269
|
}
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/types.ts"],"sourcesContent":["import type { icons, LucideIcon } from 'lucide-react'\nimport type { CollectionSlug, GlobalSlug } from 'payload'\n\nexport type IconName = keyof typeof icons\n\nexport type LocalizedString = { [locale: string]: string } | string\n\nexport type InternalIcon = IconName | LucideIcon\n\n/**\n * Custom menu item, for configuration.\n * Will be mapped to CommandMenuItem internally.\n */\nexport type CustomMenuItem = {\n action: CommandMenuAction\n icon?: IconName\n label: LocalizedString\n slug: string\n type: 'item'\n}\n\n/**\n * Custom menu group, for configuration.\n * Will be mapped to CommandMenuGroup internally.\n *\n * Groups will be merged if they have the same title.\n */\nexport type CustomMenuGroup = {\n items: CustomMenuItem[]\n title: LocalizedString\n type: 'group'\n}\n/**\n * Full serializable config for the plugin.\n */\nexport type PluginCommandMenuConfig = {\n /**\n * Enable backdrop blur effect\n * @default true\n */\n blurBg?: boolean\n /**\n * Custom items or groups to add to the command menu.\n */\n customItems?: (CustomMenuGroup | CustomMenuItem)[]\n /**\n * Disable the plugin functionality\n * @default false\n */\n disabled?: boolean\n /**\n * Custom icons for collections and globals.\n * Key is the collection slug, value is the icon name from lucide-react.\n * Collections default icon - Files,\n * Globals default icon - Globe.\n */\n icons?: {\n /**\n * Custom icons for collections.\n * @default <Files/>\n */\n collections?: Partial<Record<CollectionSlug, IconName>>\n /**\n * Custom icons for globals.\n * @default <Globe/>\n */\n globals?: Partial<Record<GlobalSlug, IconName>>\n }\n /**\n * Configuration for the search button in the admin navigation.\n * Set to false to disable the search button.\n * @default { position: 'actions' }\n */\n searchButton?:\n | {\n /**\n * Position of the search button in the admin navigation.\n * @default 'actions'\n */\n position?: 'actions' | 'nav'\n }\n | false\n /**\n * Keyboard shortcut to open the command menu.\n * Can be a single shortcut string or an array of shortcuts for cross-platform support.\n * @default ['meta+k', 'ctrl+k']\n * @example 'mod+k' or ['meta+k', 'ctrl+k']\n *\n * More details here - https://react-hotkeys-hook.vercel.app/docs/intro\n */\n shortcut?: string | string[]\n /**\n * Specify which collections slugs remove from the command menu.\n * @default ['payload-migrations','payload-preferences','payload-locked-documents']\n *\n * You can also provide an object with `ignoreList` and `replaceDefaults` properties.\n * `replaceDefaults` allows you to completely replace the default slugs to ignore instead of appending to them.\n */\n slugsToIgnore?:\n | {\n /**\n * List of collection/global slugs to ignore in the command menu.\n */\n ignoreList: CollectionSlug[]\n /**\n * Whether to replace the default slugs to ignore instead of appending to them.\n */\n replaceDefaults?: boolean\n }\n | CollectionSlug[]\n /**\n * Configure submenu behavior for collections.\n * When enabled, users can search within a collection's documents.\n * @default { enabled: true, shortcut: 'shift+enter' }\n */\n submenu?: {\n /**\n * Enable or disable submenu functionality.\n * @default true\n */\n enabled?: boolean\n /**\n * Custom icons for collection submenus.\n * Key is the collection slug, value is the icon name from lucide-react.\n *\n * @default null\n */\n icons?: Partial<Record<CollectionSlug, IconName>>\n /**\n * Keyboard shortcut to open collection submenu.\n * - 'shift+enter': Shift+Enter opens submenu, Enter navigates to collection list\n * - 'enter': Enter opens submenu, Shift+Enter navigates to collection list\n * @default 'shift+enter'\n */\n shortcut?: 'enter' | 'shift+enter'\n }\n}\n\nexport interface CommandMenuContextProps {\n children: React.ReactNode\n pluginConfig: PluginCommandMenuConfig\n}\n\nexport interface CommandMenuActionLink {\n href: string\n type: 'link'\n}\n\nexport interface CommandMenuActionAPICall {\n body?: {\n [key: string]: unknown\n }\n href: string\n /**\n * HTTP method to use for the API call.\n * @default 'GET'\n */\n method?: 'DELETE' | 'GET' | 'POST' | 'PUT'\n type: 'api'\n}\n\nexport type CommandMenuAction = CommandMenuActionAPICall | CommandMenuActionLink\n\nexport interface CommandMenuItem {\n /**\n * Action to perform when the command menu item is selected.\n */\n action: CommandMenuAction\n icon?: InternalIcon\n label: string\n slug: string\n /**\n * Type of the command menu item. Used for grouping and icons.\n * @default 'custom'\n */\n type: 'collection' | 'custom' | 'global'\n\n /**\n * Field name used as title for collection documents.\n * Only applicable for collection type items.\n * Defaults to 'id' if not specified.\n */\n useAsTitle?: string\n /**\n * Label for the field used as title for collection documents.\n * Only applicable for collection type items.\n *\n * Used in submenu search placeholder.\n */\n useAsTitleLabel?: string\n}\n\nexport interface CommandMenuGroup {\n items: CommandMenuItem[]\n title: string\n}\n\n/**\n * Page state for command menu navigation.\n * - 'main': Default view showing all collections/globals/custom items\n * - CollectionSearchPage: Submenu view for searching within a specific collection\n */\nexport type CommandMenuPage =\n | 'main'\n | {\n /**\n * Collection label for display\n */\n label: string\n /**\n * Collection slug\n */\n slug: string\n /**\n * Page type identifier\n */\n type: 'collection-search'\n /**\n * Field name to use as document title\n */\n useAsTitle: string\n /**\n * Label for the field used as title\n */\n useAsTitleLabel: string\n }\n\n/**\n * Generic document type for collections, with dynamic keys.\n * We assume values are either string or number for simplicity, useAsTitle is making sure of that.\n */\nexport type GenericCollectionDocument = {\n [key: string]: number | string\n id: string\n}\n"],"names":[],"mappings":"AAmOA;;;CAGC,GACD,WAGC"}
|
|
1
|
+
{"version":3,"sources":["../src/types.ts"],"sourcesContent":["import type { icons, LucideIcon } from 'lucide-react'\nimport type { CollectionSlug, GlobalSlug } from 'payload'\n\nexport type IconName = keyof typeof icons\n\nexport type LocalizedString = { [locale: string]: string } | string\n\nexport type InternalIcon = IconName | LucideIcon\n\n/**\n * Controls which context within a collection a custom item/group is visible in.\n * Pass an array with one or both values:\n *\n * - `'list'` — collection list page (`/admin/collections/{slug}`)\n * - `'document'` — document edit/create page (`/admin/collections/{slug}/{id|create}`)\n *\n * Omit the field (or pass both values) to show in all contexts.\n *\n * @example\n * collectionContext: ['list'] // list page only\n * collectionContext: ['document'] // document page only\n * collectionContext: ['list', 'document'] // both (same as omitting)\n */\nexport type CollectionContext = ('document' | 'list')[]\n\n/**\n * Custom menu item, for configuration.\n * Will be mapped to CommandMenuItem internally.\n */\nexport type CustomMenuItem = {\n action: CommandMenuAction\n /**\n * Restrict visibility to specific contexts within a collection page.\n * Pass an array with one or both values — omit to show in all contexts.\n *\n * - `'list'` — collection list page (`/admin/collections/{slug}`)\n * - `'document'` — document edit/create page (`/admin/collections/{slug}/{id|create}`)\n *\n * @example\n * collectionContext: ['list'] // list page only\n * collectionContext: ['document'] // document page only\n * collectionContext: ['list', 'document'] // both (same as omitting)\n */\n collectionContext?: CollectionContext\n /**\n * Restrict this item to only appear when the user is on one of these collection pages.\n * Matches against the current route (e.g. `/admin/collections/{slug}`).\n * If omitted or empty, the item appears on all pages.\n */\n collectionSlugs?: CollectionSlug[]\n icon?: IconName\n label: LocalizedString\n slug: string\n type: 'item'\n}\n\n/**\n * Custom menu group, for configuration.\n * Will be mapped to CommandMenuGroup internally.\n *\n * Groups will be merged if they have the same title.\n */\nexport type CustomMenuGroup = {\n /**\n * Restrict visibility to specific contexts within a collection page.\n * Pass an array with one or both values — omit to show in all contexts.\n *\n * - `'list'` — collection list page (`/admin/collections/{slug}`)\n * - `'document'` — document edit/create page (`/admin/collections/{slug}/{id|create}`)\n *\n * @example\n * collectionContext: ['list'] // list page only\n * collectionContext: ['document'] // document page only\n * collectionContext: ['list', 'document'] // both (same as omitting)\n */\n collectionContext?: CollectionContext\n /**\n * Restrict this group to only appear when the user is on one of these collection pages.\n * Matches against the current route (e.g. `/admin/collections/{slug}`).\n * If omitted or empty, the group appears on all pages.\n */\n collectionSlugs?: CollectionSlug[]\n items: CustomMenuItem[]\n title: LocalizedString\n type: 'group'\n}\n/**\n * Full serializable config for the plugin.\n */\nexport type PluginCommandMenuConfig = {\n /**\n * Enable backdrop blur effect\n * @default true\n */\n blurBg?: boolean\n /**\n * Custom items or groups to add to the command menu.\n */\n customItems?: (CustomMenuGroup | CustomMenuItem)[]\n /**\n * Disable the plugin functionality\n * @default false\n */\n disabled?: boolean\n /**\n * Custom icons for collections and globals.\n * Key is the collection slug, value is the icon name from lucide-react.\n * Collections default icon - Files,\n * Globals default icon - Globe.\n */\n icons?: {\n /**\n * Custom icons for collections.\n * @default <Files/>\n */\n collections?: Partial<Record<CollectionSlug, IconName>>\n /**\n * Custom icons for globals.\n * @default <Globe/>\n */\n globals?: Partial<Record<GlobalSlug, IconName>>\n }\n /**\n * Configuration for the search button in the admin navigation.\n * Set to false to disable the search button.\n * @default { position: 'actions' }\n */\n searchButton?:\n | {\n /**\n * Position of the search button in the admin navigation.\n * @default 'actions'\n */\n position?: 'actions' | 'nav'\n }\n | false\n /**\n * Keyboard shortcut to open the command menu.\n * Can be a single shortcut string or an array of shortcuts for cross-platform support.\n * @default ['meta+k', 'ctrl+k']\n * @example 'mod+k' or ['meta+k', 'ctrl+k']\n *\n * More details here - https://react-hotkeys-hook.vercel.app/docs/intro\n */\n shortcut?: string | string[]\n /**\n * Specify which collections slugs remove from the command menu.\n * @default ['payload-migrations','payload-preferences','payload-locked-documents']\n *\n * You can also provide an object with `ignoreList` and `replaceDefaults` properties.\n * `replaceDefaults` allows you to completely replace the default slugs to ignore instead of appending to them.\n */\n slugsToIgnore?:\n | {\n /**\n * List of collection/global slugs to ignore in the command menu.\n */\n ignoreList: CollectionSlug[]\n /**\n * Whether to replace the default slugs to ignore instead of appending to them.\n */\n replaceDefaults?: boolean\n }\n | CollectionSlug[]\n /**\n * Configure submenu behavior for collections.\n * When enabled, users can search within a collection's documents.\n * @default { enabled: true, shortcut: 'shift+enter' }\n */\n submenu?: {\n /**\n * Enable or disable submenu functionality.\n * @default true\n */\n enabled?: boolean\n /**\n * Custom icons for collection submenus.\n * Key is the collection slug, value is the icon name from lucide-react.\n *\n * @default null\n */\n icons?: Partial<Record<CollectionSlug, IconName>>\n /**\n * Keyboard shortcut to open collection submenu.\n * - 'shift+enter': Shift+Enter opens submenu, Enter navigates to collection list\n * - 'enter': Enter opens submenu, Shift+Enter navigates to collection list\n * @default 'shift+enter'\n */\n shortcut?: 'enter' | 'shift+enter'\n }\n}\n\nexport interface CommandMenuContextProps {\n children: React.ReactNode\n pluginConfig: PluginCommandMenuConfig\n}\n\nexport interface CommandMenuActionLink {\n href: string\n type: 'link'\n}\n\nexport interface CommandMenuActionAPICall {\n body?: {\n [key: string]: unknown\n }\n href: string\n /**\n * HTTP method to use for the API call.\n * @default 'GET'\n */\n method?: 'DELETE' | 'GET' | 'POST' | 'PUT'\n type: 'api'\n}\n\n/**\n * Calls a function registered via `registerCommandMenuAction(key, fn)` on the client.\n * Since the plugin config is serialized across the server→client boundary, functions\n * cannot be passed directly — use a string key to reference a registered handler instead.\n *\n * @example\n * // In payload.config.ts:\n * action: { type: 'function', key: 'save-current-doc' }\n *\n * // In your client-side code:\n * import { registerCommandMenuAction } from '@veiag/payload-cmdk/client'\n * registerCommandMenuAction('save-current-doc', () => { ... })\n */\nexport interface CommandMenuActionFunction {\n /**\n * Key used to look up the registered handler via `registerCommandMenuAction`.\n */\n key: string\n type: 'function'\n}\n\nexport type CommandMenuAction =\n | CommandMenuActionAPICall\n | CommandMenuActionFunction\n | CommandMenuActionLink\n\nexport interface CommandMenuItem {\n /**\n * Action to perform when the command menu item is selected.\n */\n action: CommandMenuAction\n /** Populated from `CustomMenuItem.collectionContext`. */\n collectionContext?: CollectionContext\n /**\n * Restrict this item to only appear when the user is on one of these collection pages.\n * Populated from `CustomMenuItem.collectionSlugs`.\n */\n collectionSlugs?: CollectionSlug[]\n icon?: InternalIcon\n label: string\n slug: string\n /**\n * Type of the command menu item. Used for grouping and icons.\n * @default 'custom'\n */\n type: 'collection' | 'custom' | 'global'\n\n /**\n * Field name used as title for collection documents.\n * Only applicable for collection type items.\n * Defaults to 'id' if not specified.\n */\n useAsTitle?: string\n /**\n * Label for the field used as title for collection documents.\n * Only applicable for collection type items.\n *\n * Used in submenu search placeholder.\n */\n useAsTitleLabel?: string\n}\n\nexport interface CommandMenuGroup {\n /** Populated from `CustomMenuGroup.collectionContext`. */\n collectionContext?: CollectionContext\n /**\n * Restrict this group to only appear when the user is on one of these collection pages.\n * Populated from `CustomMenuGroup.collectionSlugs`.\n */\n collectionSlugs?: CollectionSlug[]\n items: CommandMenuItem[]\n title: string\n}\n\n/**\n * Page state for command menu navigation.\n * - 'main': Default view showing all collections/globals/custom items\n * - CollectionSearchPage: Submenu view for searching within a specific collection\n */\nexport type CommandMenuPage =\n | 'main'\n | {\n /**\n * Collection label for display\n */\n label: string\n /**\n * Collection slug\n */\n slug: string\n /**\n * Page type identifier\n */\n type: 'collection-search'\n /**\n * Field name to use as document title\n */\n useAsTitle: string\n /**\n * Label for the field used as title\n */\n useAsTitleLabel: string\n }\n\n/**\n * Generic document type for collections, with dynamic keys.\n * We assume values are either string or number for simplicity, useAsTitle is making sure of that.\n */\nexport type GenericCollectionDocument = {\n [key: string]: number | string\n id: string\n}\n"],"names":[],"mappings":"AA+TA;;;CAGC,GACD,WAGC"}
|
package/dist/utils/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Files, Globe } from 'lucide-react';
|
|
2
|
+
import { formatAdminURL } from 'payload/shared';
|
|
2
3
|
export const convertSlugToTitle = (slug)=>{
|
|
3
4
|
return slug.replace(/-/g, ' ').replace(/\b\w/g, (char)=>char.toUpperCase());
|
|
4
5
|
};
|
|
@@ -39,12 +40,16 @@ export const convertConfigItem = (item, currentLang)=>{
|
|
|
39
40
|
slug: item.slug,
|
|
40
41
|
type: 'custom',
|
|
41
42
|
action: item.action,
|
|
43
|
+
collectionContext: item.collectionContext,
|
|
44
|
+
collectionSlugs: item.collectionSlugs,
|
|
42
45
|
icon: item.icon,
|
|
43
46
|
label: extractLocalizedValue(item.label, currentLang, item.slug)
|
|
44
47
|
};
|
|
45
48
|
};
|
|
46
49
|
export const convertConfigGroup = (group, currentLang)=>{
|
|
47
50
|
return {
|
|
51
|
+
collectionContext: group.collectionContext,
|
|
52
|
+
collectionSlugs: group.collectionSlugs,
|
|
48
53
|
items: group.items.map((item)=>convertConfigItem(item, currentLang)),
|
|
49
54
|
title: extractLocalizedValue(group.title, currentLang)
|
|
50
55
|
};
|
|
@@ -55,7 +60,10 @@ export const convertConfigGroup = (group, currentLang)=>{
|
|
|
55
60
|
*/ const DEFAULT_SLUGS_TO_IGNORE = [
|
|
56
61
|
'payload-migrations',
|
|
57
62
|
'payload-preferences',
|
|
58
|
-
'payload-locked-documents'
|
|
63
|
+
'payload-locked-documents',
|
|
64
|
+
'payload-folders',
|
|
65
|
+
'payload-kvs',
|
|
66
|
+
'payload-jobs'
|
|
59
67
|
];
|
|
60
68
|
export const createDefaultGroups = (config, currentLang, pluginConfig)=>{
|
|
61
69
|
const groups = [];
|
|
@@ -65,6 +73,7 @@ export const createDefaultGroups = (config, currentLang, pluginConfig)=>{
|
|
|
65
73
|
let slugsToIgnore = [
|
|
66
74
|
...DEFAULT_SLUGS_TO_IGNORE
|
|
67
75
|
];
|
|
76
|
+
const adminRoute = config.routes?.admin ?? '/admin';
|
|
68
77
|
//Handle slugs to ignore from plugin config
|
|
69
78
|
if (pluginConfig?.slugsToIgnore) {
|
|
70
79
|
if (Array.isArray(pluginConfig.slugsToIgnore)) {
|
|
@@ -113,7 +122,10 @@ export const createDefaultGroups = (config, currentLang, pluginConfig)=>{
|
|
|
113
122
|
type: 'collection',
|
|
114
123
|
action: {
|
|
115
124
|
type: 'link',
|
|
116
|
-
href:
|
|
125
|
+
href: formatAdminURL({
|
|
126
|
+
adminRoute,
|
|
127
|
+
path: `/collections/${collection.slug}`
|
|
128
|
+
})
|
|
117
129
|
},
|
|
118
130
|
//Either custom icon from plugin config, or default Files icon
|
|
119
131
|
icon: pluginConfig?.icons?.collections?.[collection.slug] || Files,
|
|
@@ -146,7 +158,10 @@ export const createDefaultGroups = (config, currentLang, pluginConfig)=>{
|
|
|
146
158
|
type: 'global',
|
|
147
159
|
action: {
|
|
148
160
|
type: 'link',
|
|
149
|
-
href:
|
|
161
|
+
href: formatAdminURL({
|
|
162
|
+
adminRoute,
|
|
163
|
+
path: `/globals/${global.slug}`
|
|
164
|
+
})
|
|
150
165
|
},
|
|
151
166
|
//Either custom icon from plugin config, or default Globe icon
|
|
152
167
|
icon: pluginConfig?.icons?.globals?.[global.slug] || Globe,
|
|
@@ -165,6 +180,8 @@ export const createDefaultGroups = (config, currentLang, pluginConfig)=>{
|
|
|
165
180
|
if (!avaibleGroups.has(convertedGroup.title)) {
|
|
166
181
|
avaibleGroups.add(convertedGroup.title);
|
|
167
182
|
groups.push({
|
|
183
|
+
collectionContext: convertedGroup.collectionContext,
|
|
184
|
+
collectionSlugs: convertedGroup.collectionSlugs,
|
|
168
185
|
items: [],
|
|
169
186
|
title: convertedGroup.title
|
|
170
187
|
});
|
package/dist/utils/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utils/index.ts"],"sourcesContent":["import type { ClientConfig, LabelFunction, TextFieldClient } from 'payload'\nimport type {\n CommandMenuGroup,\n CommandMenuItem,\n CustomMenuGroup,\n CustomMenuItem,\n LocalizedString,\n PluginCommandMenuConfig,\n} from 'src/types'\n\nimport { Files, Globe } from 'lucide-react'\n\nexport const convertSlugToTitle = (slug: string): string => {\n return slug.replace(/-/g, ' ').replace(/\\b\\w/g, (char) => char.toUpperCase())\n}\n\nexport const extractLocalizedValue = (\n value: LocalizedString,\n locale: string,\n slug?: string,\n): string => {\n if (typeof value === 'string') {\n return value\n }\n return value[locale] || convertSlugToTitle(slug || '')\n}\n\nexport const extractLocalizedCollectionName = (\n collection: {\n labels?: { plural?: LocalizedString; singular?: LocalizedString }\n slug: string\n },\n locale: string,\n): string => {\n //Get plural name if exists, otherwise singular, otherwise slug\n if (collection.labels?.plural) {\n return extractLocalizedValue(collection.labels.plural, locale, collection.slug)\n }\n if (collection.labels?.singular) {\n return extractLocalizedValue(collection.labels.singular, locale, collection.slug)\n }\n return '' //Generally should not happen\n}\n\nexport const extractLocalizedGlobalName = (\n global: {\n label?: LabelFunction | LocalizedString\n slug: string\n },\n locale: string,\n): string => {\n if (global.label) {\n return extractLocalizedValue(\n //Ignore label functions, they are not serializable\n typeof global.label === 'function' ? {} : global.label,\n locale,\n global.slug,\n )\n }\n return '' //Generally should not happen\n}\n\nexport const extractLocalizedGroupName = (\n object: {\n admin?: { group?: false | LocalizedString }\n },\n locale: string,\n): null | string => {\n if (object.admin?.group) {\n //Try to extract group name, with fallback to null (no group)\n return extractLocalizedValue(object.admin.group, locale)?.trim() || null\n }\n return null\n}\n\nexport const convertConfigItem = (item: CustomMenuItem, currentLang: string): CommandMenuItem => {\n return {\n slug: item.slug,\n type: 'custom',\n action: item.action,\n icon: item.icon,\n label: extractLocalizedValue(item.label, currentLang, item.slug),\n }\n}\n\nexport const convertConfigGroup = (\n group: CustomMenuGroup,\n currentLang: string,\n): CommandMenuGroup => {\n return {\n items: group.items.map((item) => convertConfigItem(item, currentLang)), // Will be merged with existing items if group exists\n title: extractLocalizedValue(group.title, currentLang),\n }\n}\n/**\n * Set of collection/globals slugs to ignore in the command menu.\n * This is useful to avoid showing certain collections/globals in the command menu.\n */\nconst DEFAULT_SLUGS_TO_IGNORE: string[] = [\n 'payload-migrations',\n 'payload-preferences',\n 'payload-locked-documents',\n]\nexport const createDefaultGroups = (\n config: ClientConfig,\n currentLang: string,\n pluginConfig: PluginCommandMenuConfig,\n): {\n groups: CommandMenuGroup[]\n /**\n * Stray items that don't belong to any group.\n * Only with custom items.\n */\n items: CommandMenuItem[]\n} => {\n const groups: CommandMenuGroup[] = []\n const items: CommandMenuItem[] = []\n const avaibleGroups = new Set<string>() //To avoid duplicates\n let slugsToIgnore = [...DEFAULT_SLUGS_TO_IGNORE]\n\n //Handle slugs to ignore from plugin config\n if (pluginConfig?.slugsToIgnore) {\n if (Array.isArray(pluginConfig.slugsToIgnore)) {\n slugsToIgnore.push(...pluginConfig.slugsToIgnore)\n } else {\n //Object with ignoreList and replaceDefaults\n if (pluginConfig.slugsToIgnore.replaceDefaults) {\n //Replace defaults\n slugsToIgnore = [] //Reset\n }\n slugsToIgnore.push(...pluginConfig.slugsToIgnore.ignoreList)\n }\n }\n\n if (config.collections) {\n config.collections.forEach((collection) => {\n if (slugsToIgnore.includes(collection.slug)) {\n return\n }\n\n const groupName = extractLocalizedGroupName(collection, currentLang) || 'Collections'\n // console.log(collection.slug, 'groupName:', groupName, 'Object', collection.admin)\n if (!avaibleGroups.has(groupName)) {\n avaibleGroups.add(groupName)\n groups.push({\n items: [],\n title: groupName,\n })\n }\n const group = groups.find((g) => g.title === groupName)\n if (group) {\n const useAsTitleField = collection.admin?.useAsTitle || 'id'\n let useAsTitleFieldLabel: TextFieldClient['label'] | undefined = undefined\n let useAsTitleLabel: string | undefined = undefined\n //Only extract useAsTitle label if submenu is enabled\n if (pluginConfig?.submenu?.enabled !== false) {\n useAsTitleFieldLabel = (\n collection?.fields?.find((field) => {\n if ('name' in field === false) {\n return null\n }\n return field.name === useAsTitleField\n }) as null | TextFieldClient\n )?.label\n //Extract label for useAsTitle field\n useAsTitleLabel = extractLocalizedValue(\n typeof useAsTitleFieldLabel === 'function' ? {} : useAsTitleFieldLabel || {},\n currentLang,\n useAsTitleField,\n )\n }\n group.items.push({\n slug: collection.slug,\n type: 'collection',\n action: {\n type: 'link',\n href: `/admin/collections/${collection.slug}`,\n },\n //Either custom icon from plugin config, or default Files icon\n icon: pluginConfig?.icons?.collections?.[collection.slug] || Files,\n label: extractLocalizedCollectionName(collection, currentLang),\n useAsTitle: useAsTitleField,\n useAsTitleLabel: useAsTitleLabel || useAsTitleField,\n })\n }\n })\n }\n //Globals\n if (config.globals) {\n config.globals.forEach((global) => {\n if (slugsToIgnore.includes(global.slug)) {\n return\n }\n //Same logic as collections\n const groupName = extractLocalizedGroupName(global, currentLang) || 'Globals'\n if (!avaibleGroups.has(groupName)) {\n avaibleGroups.add(groupName)\n groups.push({\n items: [],\n title: groupName,\n })\n }\n const group = groups.find((g) => g.title === groupName)\n if (group) {\n group.items.push({\n slug: global.slug,\n type: 'global',\n action: {\n type: 'link',\n href: `/admin/globals/${global.slug}`,\n },\n //Either custom icon from plugin config, or default Globe icon\n icon: pluginConfig?.icons?.globals?.[global.slug] || Globe,\n label: extractLocalizedGlobalName(global, currentLang),\n })\n }\n })\n }\n //Now custom items/groups from plugin config\n if (pluginConfig?.customItems) {\n //We are not using slugsToIgnore for custom items, as they are user-defined\n pluginConfig.customItems.forEach((value) => {\n if (value.type === 'group') {\n const convertedGroup = convertConfigGroup(value, currentLang)\n //Check if group already exists using our set\n if (!avaibleGroups.has(convertedGroup.title)) {\n avaibleGroups.add(convertedGroup.title)\n groups.push({\n items: [], //Don't add items yet, will do below\n title: convertedGroup.title,\n })\n }\n const group = groups.find((g) => g.title === convertedGroup.title)\n if (group) {\n //Append items to existing group, or if it was empty - add items\n group.items.push(...convertedGroup.items)\n }\n }\n if (value.type === 'item') {\n //Stray item, add to items array\n const convertedItem = convertConfigItem(value, currentLang)\n items.push(convertedItem)\n }\n })\n }\n return { groups, items }\n}\n"],"names":["Files","Globe","convertSlugToTitle","slug","replace","char","toUpperCase","extractLocalizedValue","value","locale","extractLocalizedCollectionName","collection","labels","plural","singular","extractLocalizedGlobalName","global","label","extractLocalizedGroupName","object","admin","group","trim","convertConfigItem","item","currentLang","type","action","icon","convertConfigGroup","items","map","title","DEFAULT_SLUGS_TO_IGNORE","createDefaultGroups","config","pluginConfig","groups","avaibleGroups","Set","slugsToIgnore","Array","isArray","push","replaceDefaults","ignoreList","collections","forEach","includes","groupName","has","add","find","g","useAsTitleField","useAsTitle","useAsTitleFieldLabel","undefined","useAsTitleLabel","submenu","enabled","fields","field","name","href","icons","globals","customItems","convertedGroup","convertedItem"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/utils/index.ts"],"sourcesContent":["import type { ClientConfig, LabelFunction, TextFieldClient } from 'payload'\nimport type {\n CommandMenuGroup,\n CommandMenuItem,\n CustomMenuGroup,\n CustomMenuItem,\n LocalizedString,\n PluginCommandMenuConfig,\n} from 'src/types'\n\nimport { Files, Globe } from 'lucide-react'\nimport { formatAdminURL } from 'payload/shared'\n\nexport const convertSlugToTitle = (slug: string): string => {\n return slug.replace(/-/g, ' ').replace(/\\b\\w/g, (char) => char.toUpperCase())\n}\n\nexport const extractLocalizedValue = (\n value: LocalizedString,\n locale: string,\n slug?: string,\n): string => {\n if (typeof value === 'string') {\n return value\n }\n return value[locale] || convertSlugToTitle(slug || '')\n}\n\nexport const extractLocalizedCollectionName = (\n collection: {\n labels?: { plural?: LocalizedString; singular?: LocalizedString }\n slug: string\n },\n locale: string,\n): string => {\n //Get plural name if exists, otherwise singular, otherwise slug\n if (collection.labels?.plural) {\n return extractLocalizedValue(collection.labels.plural, locale, collection.slug)\n }\n if (collection.labels?.singular) {\n return extractLocalizedValue(collection.labels.singular, locale, collection.slug)\n }\n return '' //Generally should not happen\n}\n\nexport const extractLocalizedGlobalName = (\n global: {\n label?: LabelFunction | LocalizedString\n slug: string\n },\n locale: string,\n): string => {\n if (global.label) {\n return extractLocalizedValue(\n //Ignore label functions, they are not serializable\n typeof global.label === 'function' ? {} : global.label,\n locale,\n global.slug,\n )\n }\n return '' //Generally should not happen\n}\n\nexport const extractLocalizedGroupName = (\n object: {\n admin?: { group?: false | LocalizedString }\n },\n locale: string,\n): null | string => {\n if (object.admin?.group) {\n //Try to extract group name, with fallback to null (no group)\n return extractLocalizedValue(object.admin.group, locale)?.trim() || null\n }\n return null\n}\n\nexport const convertConfigItem = (item: CustomMenuItem, currentLang: string): CommandMenuItem => {\n return {\n slug: item.slug,\n type: 'custom',\n action: item.action,\n collectionContext: item.collectionContext,\n collectionSlugs: item.collectionSlugs,\n icon: item.icon,\n label: extractLocalizedValue(item.label, currentLang, item.slug),\n }\n}\n\nexport const convertConfigGroup = (\n group: CustomMenuGroup,\n currentLang: string,\n): CommandMenuGroup => {\n return {\n collectionContext: group.collectionContext,\n collectionSlugs: group.collectionSlugs,\n items: group.items.map((item) => convertConfigItem(item, currentLang)), // Will be merged with existing items if group exists\n title: extractLocalizedValue(group.title, currentLang),\n }\n}\n/**\n * Set of collection/globals slugs to ignore in the command menu.\n * This is useful to avoid showing certain collections/globals in the command menu.\n */\nconst DEFAULT_SLUGS_TO_IGNORE: string[] = [\n 'payload-migrations',\n 'payload-preferences',\n 'payload-locked-documents',\n 'payload-folders',\n 'payload-kvs',\n 'payload-jobs',\n]\nexport const createDefaultGroups = (\n config: ClientConfig,\n currentLang: string,\n pluginConfig: PluginCommandMenuConfig,\n): {\n groups: CommandMenuGroup[]\n /**\n * Stray items that don't belong to any group.\n * Only with custom items.\n */\n items: CommandMenuItem[]\n} => {\n const groups: CommandMenuGroup[] = []\n const items: CommandMenuItem[] = []\n const avaibleGroups = new Set<string>() //To avoid duplicates\n let slugsToIgnore = [...DEFAULT_SLUGS_TO_IGNORE]\n\n const adminRoute = config.routes?.admin ?? '/admin'\n\n //Handle slugs to ignore from plugin config\n if (pluginConfig?.slugsToIgnore) {\n if (Array.isArray(pluginConfig.slugsToIgnore)) {\n slugsToIgnore.push(...pluginConfig.slugsToIgnore)\n } else {\n //Object with ignoreList and replaceDefaults\n if (pluginConfig.slugsToIgnore.replaceDefaults) {\n //Replace defaults\n slugsToIgnore = [] //Reset\n }\n slugsToIgnore.push(...pluginConfig.slugsToIgnore.ignoreList)\n }\n }\n\n if (config.collections) {\n config.collections.forEach((collection) => {\n if (slugsToIgnore.includes(collection.slug)) {\n return\n }\n\n const groupName = extractLocalizedGroupName(collection, currentLang) || 'Collections'\n // console.log(collection.slug, 'groupName:', groupName, 'Object', collection.admin)\n if (!avaibleGroups.has(groupName)) {\n avaibleGroups.add(groupName)\n groups.push({\n items: [],\n title: groupName,\n })\n }\n const group = groups.find((g) => g.title === groupName)\n if (group) {\n const useAsTitleField = collection.admin?.useAsTitle || 'id'\n let useAsTitleFieldLabel: TextFieldClient['label'] | undefined = undefined\n let useAsTitleLabel: string | undefined = undefined\n //Only extract useAsTitle label if submenu is enabled\n if (pluginConfig?.submenu?.enabled !== false) {\n useAsTitleFieldLabel = (\n collection?.fields?.find((field) => {\n if ('name' in field === false) {\n return null\n }\n return field.name === useAsTitleField\n }) as null | TextFieldClient\n )?.label\n //Extract label for useAsTitle field\n useAsTitleLabel = extractLocalizedValue(\n typeof useAsTitleFieldLabel === 'function' ? {} : useAsTitleFieldLabel || {},\n currentLang,\n useAsTitleField,\n )\n }\n group.items.push({\n slug: collection.slug,\n type: 'collection',\n action: {\n type: 'link',\n href: formatAdminURL({ adminRoute, path: `/collections/${collection.slug}` }),\n },\n //Either custom icon from plugin config, or default Files icon\n icon: pluginConfig?.icons?.collections?.[collection.slug] || Files,\n label: extractLocalizedCollectionName(collection, currentLang),\n useAsTitle: useAsTitleField,\n useAsTitleLabel: useAsTitleLabel || useAsTitleField,\n })\n }\n })\n }\n //Globals\n if (config.globals) {\n config.globals.forEach((global) => {\n if (slugsToIgnore.includes(global.slug)) {\n return\n }\n //Same logic as collections\n const groupName = extractLocalizedGroupName(global, currentLang) || 'Globals'\n if (!avaibleGroups.has(groupName)) {\n avaibleGroups.add(groupName)\n groups.push({\n items: [],\n title: groupName,\n })\n }\n const group = groups.find((g) => g.title === groupName)\n if (group) {\n group.items.push({\n slug: global.slug,\n type: 'global',\n action: {\n type: 'link',\n href: formatAdminURL({ adminRoute, path: `/globals/${global.slug}` }),\n },\n //Either custom icon from plugin config, or default Globe icon\n icon: pluginConfig?.icons?.globals?.[global.slug] || Globe,\n label: extractLocalizedGlobalName(global, currentLang),\n })\n }\n })\n }\n //Now custom items/groups from plugin config\n if (pluginConfig?.customItems) {\n //We are not using slugsToIgnore for custom items, as they are user-defined\n pluginConfig.customItems.forEach((value) => {\n if (value.type === 'group') {\n const convertedGroup = convertConfigGroup(value, currentLang)\n //Check if group already exists using our set\n if (!avaibleGroups.has(convertedGroup.title)) {\n avaibleGroups.add(convertedGroup.title)\n groups.push({\n collectionContext: convertedGroup.collectionContext,\n collectionSlugs: convertedGroup.collectionSlugs,\n items: [], //Don't add items yet, will do below\n title: convertedGroup.title,\n })\n }\n const group = groups.find((g) => g.title === convertedGroup.title)\n if (group) {\n //Append items to existing group, or if it was empty - add items\n group.items.push(...convertedGroup.items)\n }\n }\n if (value.type === 'item') {\n //Stray item, add to items array\n const convertedItem = convertConfigItem(value, currentLang)\n items.push(convertedItem)\n }\n })\n }\n return { groups, items }\n}\n"],"names":["Files","Globe","formatAdminURL","convertSlugToTitle","slug","replace","char","toUpperCase","extractLocalizedValue","value","locale","extractLocalizedCollectionName","collection","labels","plural","singular","extractLocalizedGlobalName","global","label","extractLocalizedGroupName","object","admin","group","trim","convertConfigItem","item","currentLang","type","action","collectionContext","collectionSlugs","icon","convertConfigGroup","items","map","title","DEFAULT_SLUGS_TO_IGNORE","createDefaultGroups","config","pluginConfig","groups","avaibleGroups","Set","slugsToIgnore","adminRoute","routes","Array","isArray","push","replaceDefaults","ignoreList","collections","forEach","includes","groupName","has","add","find","g","useAsTitleField","useAsTitle","useAsTitleFieldLabel","undefined","useAsTitleLabel","submenu","enabled","fields","field","name","href","path","icons","globals","customItems","convertedGroup","convertedItem"],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handler type for command menu function actions.
|
|
3
|
+
*/
|
|
4
|
+
export type CommandMenuActionHandler = () => Promise<void> | void;
|
|
5
|
+
/**
|
|
6
|
+
* Register a handler function under the given key.
|
|
7
|
+
* Calling this with the same key again will overwrite the previous handler.
|
|
8
|
+
*/
|
|
9
|
+
export declare const registerCommandMenuAction: (key: string, handler: CommandMenuActionHandler) => void;
|
|
10
|
+
/**
|
|
11
|
+
* Remove a previously registered handler.
|
|
12
|
+
*/
|
|
13
|
+
export declare const unregisterCommandMenuAction: (key: string) => void;
|
|
14
|
+
/**
|
|
15
|
+
* Look up a registered handler by key. Returns `undefined` if not found.
|
|
16
|
+
* Used internally by the command menu to execute function actions.
|
|
17
|
+
*/
|
|
18
|
+
export declare const getCommandMenuAction: (key: string) => CommandMenuActionHandler | undefined;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
/**
|
|
3
|
+
* Client-side registry for function-based command menu actions.
|
|
4
|
+
*
|
|
5
|
+
* Because the plugin config is serialized across the Next.js server→client
|
|
6
|
+
* boundary, JavaScript functions cannot be embedded directly in the config.
|
|
7
|
+
* Instead, register handlers here by a string key and reference that key in
|
|
8
|
+
* `action: { type: 'function', key: '...' }`.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* // payload.config.ts
|
|
12
|
+
* customItems: [{
|
|
13
|
+
* type: 'item',
|
|
14
|
+
* slug: 'save-doc',
|
|
15
|
+
* label: 'Save Document',
|
|
16
|
+
* icon: 'Save',
|
|
17
|
+
* collectionSlugs: ['posts'],
|
|
18
|
+
* action: { type: 'function', key: 'save-current-doc' },
|
|
19
|
+
* }]
|
|
20
|
+
*
|
|
21
|
+
* // Your client-side component / layout
|
|
22
|
+
* import { registerCommandMenuAction } from '@veiag/payload-cmdk/client'
|
|
23
|
+
* registerCommandMenuAction('save-current-doc', () => {
|
|
24
|
+
* document.querySelector('form')?.requestSubmit()
|
|
25
|
+
* })
|
|
26
|
+
*/ const registry = new Map();
|
|
27
|
+
/**
|
|
28
|
+
* Register a handler function under the given key.
|
|
29
|
+
* Calling this with the same key again will overwrite the previous handler.
|
|
30
|
+
*/ export const registerCommandMenuAction = (key, handler)=>{
|
|
31
|
+
registry.set(key, handler);
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Remove a previously registered handler.
|
|
35
|
+
*/ export const unregisterCommandMenuAction = (key)=>{
|
|
36
|
+
registry.delete(key);
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Look up a registered handler by key. Returns `undefined` if not found.
|
|
40
|
+
* Used internally by the command menu to execute function actions.
|
|
41
|
+
*/ export const getCommandMenuAction = (key)=>{
|
|
42
|
+
return registry.get(key);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utils/registry.ts"],"sourcesContent":["'use client'\n\n/**\n * Handler type for command menu function actions.\n */\nexport type CommandMenuActionHandler = () => Promise<void> | void\n\n/**\n * Client-side registry for function-based command menu actions.\n *\n * Because the plugin config is serialized across the Next.js server→client\n * boundary, JavaScript functions cannot be embedded directly in the config.\n * Instead, register handlers here by a string key and reference that key in\n * `action: { type: 'function', key: '...' }`.\n *\n * @example\n * // payload.config.ts\n * customItems: [{\n * type: 'item',\n * slug: 'save-doc',\n * label: 'Save Document',\n * icon: 'Save',\n * collectionSlugs: ['posts'],\n * action: { type: 'function', key: 'save-current-doc' },\n * }]\n *\n * // Your client-side component / layout\n * import { registerCommandMenuAction } from '@veiag/payload-cmdk/client'\n * registerCommandMenuAction('save-current-doc', () => {\n * document.querySelector('form')?.requestSubmit()\n * })\n */\nconst registry = new Map<string, CommandMenuActionHandler>()\n\n/**\n * Register a handler function under the given key.\n * Calling this with the same key again will overwrite the previous handler.\n */\nexport const registerCommandMenuAction = (key: string, handler: CommandMenuActionHandler): void => {\n registry.set(key, handler)\n}\n\n/**\n * Remove a previously registered handler.\n */\nexport const unregisterCommandMenuAction = (key: string): void => {\n registry.delete(key)\n}\n\n/**\n * Look up a registered handler by key. Returns `undefined` if not found.\n * Used internally by the command menu to execute function actions.\n */\nexport const getCommandMenuAction = (key: string): CommandMenuActionHandler | undefined => {\n return registry.get(key)\n}\n"],"names":["registry","Map","registerCommandMenuAction","key","handler","set","unregisterCommandMenuAction","delete","getCommandMenuAction","get"],"mappings":"AAAA;AAOA;;;;;;;;;;;;;;;;;;;;;;;;CAwBC,GACD,MAAMA,WAAW,IAAIC;AAErB;;;CAGC,GACD,OAAO,MAAMC,4BAA4B,CAACC,KAAaC;IACrDJ,SAASK,GAAG,CAACF,KAAKC;AACpB,EAAC;AAED;;CAEC,GACD,OAAO,MAAME,8BAA8B,CAACH;IAC1CH,SAASO,MAAM,CAACJ;AAClB,EAAC;AAED;;;CAGC,GACD,OAAO,MAAMK,uBAAuB,CAACL;IACnC,OAAOH,SAASS,GAAG,CAACN;AACtB,EAAC"}
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/endpoints/customEndpointHandler.ts"],"sourcesContent":["import type { PayloadHandler } from 'payload'\n\nexport const customEndpointHandler: PayloadHandler = () => {\n return Response.json({ message: 'Hello from custom endpoint' })\n}\n"],"names":["customEndpointHandler","Response","json","message"],"mappings":"AAEA,OAAO,MAAMA,wBAAwC;IACnD,OAAOC,SAASC,IAAI,CAAC;QAAEC,SAAS;IAA6B;AAC/D,EAAC"}
|