@rakun-kit/manager-react 1.7.0 → 1.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/README.md +448 -445
  2. package/dist/cjs/components/media/previews/Previews.js +66 -2
  3. package/dist/cjs/components/media/previews/Previews.js.map +1 -1
  4. package/dist/cjs/components/media/previews/dialogs/ExpandedPreviewDialog.js +12 -4
  5. package/dist/cjs/components/media/previews/dialogs/ExpandedPreviewDialog.js.map +1 -1
  6. package/dist/cjs/components/media/previews/hooks/useMediaPreviewRenderer.js +1 -1
  7. package/dist/cjs/components/media/previews/hooks/useMediaPreviewRenderer.js.map +1 -1
  8. package/dist/cjs/i18n/catalog.js +11 -0
  9. package/dist/cjs/i18n/catalog.js.map +1 -1
  10. package/dist/cjs/lib/media.js.map +1 -1
  11. package/dist/cjs/router/dashboard/[contentType]/[edit]/ContentTypeEdit.js +17 -4
  12. package/dist/cjs/router/dashboard/[contentType]/[edit]/ContentTypeEdit.js.map +1 -1
  13. package/dist/cjs/router/dashboard/[contentType]/[edit]/_components/EditToolbar.js +1 -1
  14. package/dist/cjs/router/dashboard/[contentType]/[edit]/_components/EditToolbar.js.map +1 -1
  15. package/dist/cjs/router/dashboard/[contentType]/[edit]/_context/EditPageContext.js +2 -2
  16. package/dist/cjs/router/dashboard/[contentType]/[edit]/_context/EditPageContext.js.map +1 -1
  17. package/dist/cjs/router/dashboard/[contentType]/[edit]/_fields/ListField/ListUI.js +1 -1
  18. package/dist/cjs/router/dashboard/[contentType]/[edit]/_fields/ListField/ListUI.js.map +1 -1
  19. package/dist/cjs/router/dashboard/[contentType]/[edit]/_fields/ListField/SimpleListUI.js +1 -4
  20. package/dist/cjs/router/dashboard/[contentType]/[edit]/_fields/ListField/SimpleListUI.js.map +1 -1
  21. package/dist/cjs/router/dashboard/[contentType]/[edit]/_fields/ListField/simpleListItems.js +3 -1
  22. package/dist/cjs/router/dashboard/[contentType]/[edit]/_fields/ListField/simpleListItems.js.map +1 -1
  23. package/dist/cjs/router/dashboard/[contentType]/[edit]/_fields/RelationField/ExistingRelation.js +3 -9
  24. package/dist/cjs/router/dashboard/[contentType]/[edit]/_fields/RelationField/ExistingRelation.js.map +1 -1
  25. package/dist/cjs/router/dashboard/[contentType]/[edit]/_hooks/useContentPreview.js +5 -4
  26. package/dist/cjs/router/dashboard/[contentType]/[edit]/_hooks/useContentPreview.js.map +1 -1
  27. package/dist/cjs/router/dashboard/[contentType]/[edit]/_hooks/useEditFormController.js +8 -1
  28. package/dist/cjs/router/dashboard/[contentType]/[edit]/_hooks/useEditFormController.js.map +1 -1
  29. package/dist/cjs/router/dashboard/[contentType]/[edit]/edit.js +1 -1
  30. package/dist/cjs/router/dashboard/[contentType]/[edit]/edit.js.map +1 -1
  31. package/dist/docs/index.md +429 -420
  32. package/dist/esm/components/media/previews/Previews.d.ts.map +1 -1
  33. package/dist/esm/components/media/previews/Previews.js +66 -2
  34. package/dist/esm/components/media/previews/Previews.js.map +1 -1
  35. package/dist/esm/components/media/previews/dialogs/ExpandedPreviewDialog.d.ts +5 -1
  36. package/dist/esm/components/media/previews/dialogs/ExpandedPreviewDialog.d.ts.map +1 -1
  37. package/dist/esm/components/media/previews/dialogs/ExpandedPreviewDialog.js +12 -4
  38. package/dist/esm/components/media/previews/dialogs/ExpandedPreviewDialog.js.map +1 -1
  39. package/dist/esm/components/media/previews/hooks/useMediaPreviewRenderer.d.ts.map +1 -1
  40. package/dist/esm/components/media/previews/hooks/useMediaPreviewRenderer.js +1 -1
  41. package/dist/esm/components/media/previews/hooks/useMediaPreviewRenderer.js.map +1 -1
  42. package/dist/esm/i18n/catalog.d.ts +33 -0
  43. package/dist/esm/i18n/catalog.d.ts.map +1 -1
  44. package/dist/esm/i18n/catalog.js +11 -0
  45. package/dist/esm/i18n/catalog.js.map +1 -1
  46. package/dist/esm/lib/media.d.ts +2 -0
  47. package/dist/esm/lib/media.d.ts.map +1 -1
  48. package/dist/esm/lib/media.js.map +1 -1
  49. package/dist/esm/router/dashboard/[contentType]/[edit]/ContentTypeEdit.d.ts.map +1 -1
  50. package/dist/esm/router/dashboard/[contentType]/[edit]/ContentTypeEdit.js +17 -4
  51. package/dist/esm/router/dashboard/[contentType]/[edit]/ContentTypeEdit.js.map +1 -1
  52. package/dist/esm/router/dashboard/[contentType]/[edit]/_components/EditToolbar.js +1 -1
  53. package/dist/esm/router/dashboard/[contentType]/[edit]/_components/EditToolbar.js.map +1 -1
  54. package/dist/esm/router/dashboard/[contentType]/[edit]/_context/EditPageContext.js +2 -2
  55. package/dist/esm/router/dashboard/[contentType]/[edit]/_context/EditPageContext.js.map +1 -1
  56. package/dist/esm/router/dashboard/[contentType]/[edit]/_fields/ListField/ListUI.js +1 -1
  57. package/dist/esm/router/dashboard/[contentType]/[edit]/_fields/ListField/ListUI.js.map +1 -1
  58. package/dist/esm/router/dashboard/[contentType]/[edit]/_fields/ListField/SimpleListUI.d.ts.map +1 -1
  59. package/dist/esm/router/dashboard/[contentType]/[edit]/_fields/ListField/SimpleListUI.js +2 -5
  60. package/dist/esm/router/dashboard/[contentType]/[edit]/_fields/ListField/SimpleListUI.js.map +1 -1
  61. package/dist/esm/router/dashboard/[contentType]/[edit]/_fields/ListField/simpleListItems.d.ts +1 -0
  62. package/dist/esm/router/dashboard/[contentType]/[edit]/_fields/ListField/simpleListItems.d.ts.map +1 -1
  63. package/dist/esm/router/dashboard/[contentType]/[edit]/_fields/ListField/simpleListItems.js +1 -0
  64. package/dist/esm/router/dashboard/[contentType]/[edit]/_fields/ListField/simpleListItems.js.map +1 -1
  65. package/dist/esm/router/dashboard/[contentType]/[edit]/_fields/RelationField/ExistingRelation.d.ts.map +1 -1
  66. package/dist/esm/router/dashboard/[contentType]/[edit]/_fields/RelationField/ExistingRelation.js +3 -9
  67. package/dist/esm/router/dashboard/[contentType]/[edit]/_fields/RelationField/ExistingRelation.js.map +1 -1
  68. package/dist/esm/router/dashboard/[contentType]/[edit]/_hooks/useContentPreview.d.ts +7 -2
  69. package/dist/esm/router/dashboard/[contentType]/[edit]/_hooks/useContentPreview.d.ts.map +1 -1
  70. package/dist/esm/router/dashboard/[contentType]/[edit]/_hooks/useContentPreview.js +5 -4
  71. package/dist/esm/router/dashboard/[contentType]/[edit]/_hooks/useContentPreview.js.map +1 -1
  72. package/dist/esm/router/dashboard/[contentType]/[edit]/_hooks/useEditFormController.d.ts +2 -1
  73. package/dist/esm/router/dashboard/[contentType]/[edit]/_hooks/useEditFormController.d.ts.map +1 -1
  74. package/dist/esm/router/dashboard/[contentType]/[edit]/_hooks/useEditFormController.js +8 -1
  75. package/dist/esm/router/dashboard/[contentType]/[edit]/_hooks/useEditFormController.js.map +1 -1
  76. package/dist/esm/router/dashboard/[contentType]/[edit]/_hooks/useRouteLayoutData.d.ts.map +1 -1
  77. package/dist/esm/router/dashboard/[contentType]/[edit]/edit.js +1 -1
  78. package/dist/esm/router/dashboard/[contentType]/[edit]/edit.js.map +1 -1
  79. package/dist/styles.css +20 -2
  80. package/dist/tsconfig.build.tsbuildinfo +1 -1
  81. package/package.json +1 -1
package/README.md CHANGED
@@ -1,445 +1,448 @@
1
- # @rakun-kit/manager-react
2
-
3
- React manager UI and client utilities for Rakun.
4
-
5
- Most apps use this package through framework adapters such as
6
- `@rakun-kit/next/manager`, but the runtime components and clients are also
7
- published for custom integrations.
8
-
9
- ## Browser App
10
-
11
- Render the manager with a manager client and navigation implementation:
12
-
13
- ```tsx
14
- import { ManagerBrowserApp, createHttpManagerClient } from '@rakun-kit/manager-react'
15
- import '@rakun-kit/manager-react/styles.css'
16
-
17
- const client = createHttpManagerClient({
18
- baseUrl: '/api/rakun',
19
- })
20
-
21
- export function ManagerPage() {
22
- return (
23
- <ManagerBrowserApp client={client} pathname={window.location.pathname} basePath="/backend" />
24
- )
25
- }
26
- ```
27
-
28
- `ManagerBrowserApp` creates browser path navigation by default. Use
29
- `ManagerRuntimeApp` when you need to provide custom navigation.
30
-
31
- Built-in screens are split by manager route and loaded on first visit. Lucide
32
- names configured for content-type and module-picker icons remain unrestricted;
33
- the manager accepts kebab-case or PascalCase names and loads the selected icon
34
- on demand. Hosts can use `preloadManagerPath` when their router wants to
35
- prefetch a built-in route chunk.
36
-
37
- ## Realtime synchronization
38
-
39
- `ManagerBrowserApp` and `ManagerRuntimeApp` read the realtime transport from
40
- the core `manager.uiLocales` bootstrap response. They automatically use
41
- polling or SSE according to the configured Rakun platform. Pass the API base
42
- URL when a relative realtime endpoint must be resolved:
43
-
44
- ```tsx
45
- <ManagerBrowserApp
46
- client={client}
47
- realtimeBaseUrl="/api/rakun"
48
- pathname={window.location.pathname}
49
- />
50
- ```
51
-
52
- The default `sseRealtime()` endpoint resolves to `<realtimeBaseUrl>/realtime`,
53
- so core bootstrap configuration does not repeat the API base path.
54
-
55
- Event transports subscribe to invalidation topics published by core. Content
56
- collaboration, locale variants, versions, comments, and notifications refresh
57
- only after a matching event; their periodic POST requests remain only as the
58
- polling fallback. The SSE provider multiplexes all active topics over one
59
- `EventSource`, avoiding per-origin browser connection limits when several
60
- manager sessions are open. Custom hosts can also pass a `RealtimeProvider`
61
- directly to `ManagerProvider` or `ManagerAppProvider`.
62
-
63
- ## Clients
64
-
65
- HTTP client:
66
-
67
- ```ts
68
- import { createHttpManagerClient } from '@rakun-kit/manager-react/client/http'
69
-
70
- const client = createHttpManagerClient({
71
- baseUrl: '/api/rakun',
72
- })
73
-
74
- const contentTypes = await client.request('manager.contentTypes')
75
- ```
76
-
77
- tRPC client adapter:
78
-
79
- ```ts
80
- import { createTrpcManagerClient } from '@rakun-kit/manager-react/client/trpc'
81
-
82
- const managerClient = createTrpcManagerClient(trpcProxyClient)
83
- ```
84
-
85
- Custom client:
86
-
87
- ```ts
88
- import { createManagerClient } from '@rakun-kit/manager-react/client/request'
89
-
90
- const client = createManagerClient(async (name, input, options) => {
91
- // call your transport here
92
- })
93
- ```
94
-
95
- ## Navigation
96
-
97
- Use `createPathManagerNavigation` for router integrations:
98
-
99
- ```ts
100
- import { createPathManagerNavigation } from '@rakun-kit/manager-react/state/navigation'
101
-
102
- const navigation = createPathManagerNavigation({
103
- basePath: '/backend',
104
- push: (href) => router.push(href),
105
- replace: (href) => router.replace(href),
106
- })
107
- ```
108
-
109
- Hosts that own the router can pass a `linkComponent` to `ManagerBrowserApp` and
110
- use `preloadManagerPath` from the router package to warm built-in route chunks.
111
-
112
- ## Link fields
113
-
114
- The `f.link()` editor shows a title alongside its destination. Users can paste
115
- or type a direct URL, choose the home page, or browse configured page-route
116
- types and select one of their entries. Internal selections automatically use
117
- the item label when the title is empty. Direct links are stored as
118
- `{ href, title }`; internal links as `{ routeId, contentTypeId, title }` so core
119
- can resolve localized paths. Persisted legacy URL strings are normalized to
120
- `{ href, title: '' }` when loaded and remain editable.
121
-
122
- Compound arrays such as `f.array(f.link())` are reorderable from their drag
123
- handles in the Info editor, without losing unsaved values inside each item.
124
-
125
- `f.menu()` provides a navigation-tree editor intended especially for headers.
126
- Every item uses the same internal/direct destination picker as `f.link()` and
127
- can contain child items. Drag vertically to reorder and horizontally to nest or
128
- outdent; the level buttons provide the same operations for keyboard users.
129
- Removing an item also removes its descendant subtree.
130
-
131
- ## Media uploads
132
-
133
- Original file names may contain accents, non-Latin scripts, or emoji. The
134
- manager transports those names through ASCII-safe upload headers and core
135
- restores them before saving media metadata.
136
-
137
- The optimization popover separates Image and Video settings through its Format
138
- selector. Images retain their normal format and responsive-image controls;
139
- videos have their own quality and are converted to both MP4 and WebM.
140
-
141
- With optimization enabled in the media toolbar, an image or video's context
142
- menu offers `Reimport`. It regenerates the configured outputs while retaining
143
- the existing media record and all relations to it. Multi-select supports the
144
- same reimport action from the selection toolbar. Optimization is on by default,
145
- and generated image previews are stored as inline `data:image/...` LQIP values
146
- on the media record.
147
-
148
- An image's context menu also offers `Replace image`. The selected file replaces
149
- the stored image and generated variants while retaining the existing media ID,
150
- name, title, alt text, folder, status, and every relation to it. Rakun also
151
- revalidates directly referencing pages when route revalidation is configured.
152
- The image editor can crop to a rectangle or circle, rotate, and flip the saved
153
- copy horizontally or vertically.
154
-
155
- ## Dynamic data mappings
156
-
157
- Fields configured with core `.help(text)` show a compact help icon beside their
158
- label. Activating it opens the guidance in a popover, including for inline
159
- modules and nested content types. Translation keys are resolved with the active
160
- manager locale, while unregistered strings are displayed directly.
161
-
162
- The list mapping editor supports recursive `blocks` targets. Select
163
- `Nested list` for a mapped list property to configure its own source, query,
164
- filters, item type, and field mapping. `Current document` query values inside
165
- that nested editor resolve against the parent source item.
166
-
167
- Arrays of links and arrays of relations, including `relation().multiple()`, can
168
- choose `Direct field` to bind the whole array or `Nested list` to map every
169
- source item. Link items show `title` and `href`; relation items show the fields
170
- of the related content type. Unlike `blocks`, these homogeneous arrays do not
171
- add a `{ name, value }` wrapper to resolved items.
172
-
173
- When an array or `.multiple()` relation, file, select, or content-reference field
174
- declares core `.min(count)` or `.max(count)` limits, its editor shows the current
175
- count and configured range. It reports values outside that range and prevents
176
- confirming or adding beyond the maximum.
177
-
178
- A target link appears as separate `<field>.title` and `<field>.href` mapping
179
- rows, so its label and destination can come from different source fields.
180
-
181
- SEO field mappings can use compatible fields from the full current document.
182
- New documents also honor core string-field `.seo('<seoField>')` configuration
183
- as an editable initial dynamic data binding.
184
-
185
- ## llms.txt settings
186
-
187
- Settings → AI content manages the optional, site-level `llms.txt` Markdown guide served to
188
- AI tools. Editors can enable publishing, override the SEO-derived title and
189
- summary, add guidance, and order sections containing internal pages or external
190
- URLs. Marking a section as Secondary content places it under `## Optional`
191
- while preserving its title as a subsection in the generated document. For content types with publication states, the internal
192
- page picker only offers published documents. Nothing is added automatically
193
- from the sitemap.
194
-
195
- Authenticated manager layouts place a Visit site action at the top-right of the
196
- logo block. It opens SEO `siteUrl` in a new tab, falling back to `/` on the
197
- manager host until that setting is configured. A published document with a
198
- resolved page route also gets a contextual View page button in its edit toolbar;
199
- the URL follows the selected manager language and locale variant.
200
-
201
- Routeable content splits its SEO tab into Metadata and Analysis. Generate
202
- report renders the current unsaved form through the configured preview and
203
- reports metadata lengths, heading structure, missing image alt attributes,
204
- indexability, social metadata, rendered JSON-LD blocks, and Google and sharing
205
- previews. JSON-LD results identify invalid JSON and missing `@context` or
206
- `@type`. For saved documents, the report is stored with its score and date when
207
- the editor can create SEO reports. Previous reports for the active language and
208
- route can be reopened directly from the document's Analysis view. A protective
209
- `noindex` on the temporary preview URL does not affect the content's
210
- indexability result.
211
-
212
- The secondary sidebar also includes a site-wide SEO screen above the media
213
- library. It audits stored metadata for published routeable documents in the
214
- active language, treats global default titles and descriptions as valid
215
- fallbacks on the home page and warns when another page relies on them, highlights
216
- duplicates and incomplete custom Open Graph data,
217
- and stores every run so its score can be followed over time. Access and report
218
- creation reuse the `SeoSettings` read and own permissions. Evolution snapshots
219
- and recent page reports are fetched and displayed in bounded, paginated groups.
220
-
221
- ## Collaborative content editing
222
-
223
- Existing content documents use a Yjs working document keyed by content type and
224
- document `_id`; each draft therefore has its own room. Edits synchronize through
225
- the manager HTTP client and remain shared unsaved changes until Save. Save
226
- flushes pending CRDT updates, then core materializes and validates the
227
- server-side working state before updating MongoDB and revalidating. Public pages
228
- continue to use the previous saved snapshot while editors type.
229
-
230
- The edit header shows collaborative presence per browser tab. Two tabs for the
231
- same account therefore appear independently. After two visible avatars, the
232
- group collapses to `+N`; its tooltip lists all viewers and their focused field,
233
- and fields focused in another tab are highlighted in the form. Presence is
234
- ephemeral and does not affect the saved working document. With SSE it follows
235
- the authenticated stream lifecycle and server heartbeat, without a periodic
236
- collaboration sync request. The polling fallback refreshes it during polling.
237
-
238
- The manager persists each user's collaborative rooms in IndexedDB. Previously
239
- opened content and Template fields load from the local Yjs cache first, remain
240
- editable during a temporary connection loss, and synchronize when the server
241
- is reachable again. The toolbar distinguishes this locally stored offline state
242
- from a snapshot committed by Save.
243
-
244
- The shared Template editor has its own Yjs room keyed by content type, so it is
245
- collaborative across editors even when they opened different documents. Its
246
- snapshot is persisted only by Save and remains separate from every document
247
- room.
248
-
249
- The edit toolbar offers a confirmed Discard changes action that restores the
250
- last saved shared state, including an unsaved shared Template when applicable.
251
- Its save menu can also save the current form as a named draft variant copy or
252
- create an independent draft copy.
253
-
254
- Create-form content becomes collaborative after its first save. Route-layout
255
- overrides, Settings, literals, users, comments, reviews, and other
256
- administrative forms are intentionally not CRDT-backed. Content translation
257
- updates the shared working state and also waits for Save.
258
-
259
- Variant lifecycle and locale assignments stay transactional, but their manager
260
- queries refresh while editors are open so changes made by another user appear
261
- without a page reload.
262
-
263
- ## Styles
264
-
265
- Import the package stylesheet once:
266
-
267
- ```ts
268
- import '@rakun-kit/manager-react/styles.css'
269
- ```
270
-
271
- ## Manager languages
272
-
273
- English and the locale runtime are built into this package. Extra translations
274
- ship from a separate package:
275
-
276
- ```sh
277
- bun add @rakun-kit/manager-locales
278
- # or: npm install @rakun-kit/manager-locales
279
- ```
280
-
281
- Import only the locale needed by the server bootstrap:
282
-
283
- ```ts
284
- import { esManagerLocalePack } from '@rakun-kit/manager-locales/es'
285
-
286
- rakunBootstrap({
287
- // ...
288
- managerLanguages: [esManagerLocalePack],
289
- })
290
- ```
291
-
292
- Each language has an independent subpath, so application bundlers only include
293
- the locales explicitly imported by the host.
294
-
295
- Content-type titles and categories can use project-defined message keys without
296
- adding them to the built-in `ManagerMessageKey` union. Add an English partial
297
- pack and extend installed locales through `managerLanguages`:
298
-
299
- ```ts
300
- import { extendManagerLanguagePack } from '@rakun-kit/core/contracts'
301
- import { esManagerLocalePack } from '@rakun-kit/manager-locales/es'
302
-
303
- const managerLanguages = [
304
- {
305
- code: 'en',
306
- name: 'English',
307
- messages: {
308
- 'field.title': 'Title',
309
- 'layoutModule.header': 'Header',
310
- 'project.contentTypes.article.menu': 'Articles',
311
- },
312
- },
313
- extendManagerLanguagePack(esManagerLocalePack, {
314
- 'field.title': 'Título',
315
- 'layoutModule.header': 'Cabecera',
316
- 'project.contentTypes.article.menu': 'Artículos',
317
- }),
318
- ]
319
- ```
320
-
321
- Then set `menu.title` or `menu.category` to that arbitrary key. Project keys are
322
- resolved at runtime but remain outside the static manager catalog.
323
-
324
- Field labels use the dynamic `field.<fieldName>` namespace. A content-type field
325
- named `title`, for example, automatically resolves `field.title` in edit forms
326
- and list columns without adding it to `ManagerMessageKey`.
327
-
328
- Layout module labels follow `layoutModule.<layoutKey>`. For example, the
329
- `header` slot automatically resolves `layoutModule.header` in its tab and
330
- configuration panel.
331
-
332
- ## Event Logs
333
-
334
- Settings → Logs displays the persistent Rakun event stream through the native
335
- `manager.logs.list` operation. The screen supports filters, cursor pagination,
336
- and structured event details. Both the settings card and the operation require
337
- the `system.eventLog.read` permission, which can be assigned from Settings →
338
- User Roles.
339
-
340
- ## Manager Plugins
341
-
342
- Manager plugins run inside the normal providers and can add dashboard routes,
343
- sidebar items, and custom field editors:
344
-
345
- ```tsx
346
- import {
347
- defineRakunManagerPlugin,
348
- ManagerBrowserApp,
349
- } from '@rakun-kit/manager-react'
350
-
351
- const analyticsManagerPlugin = defineRakunManagerPlugin({
352
- id: '@acme/rakun-analytics',
353
- routes: [{
354
- id: 'dashboard',
355
- path: '/analytics',
356
- component: AnalyticsScreen,
357
- permissions: ['plugin.analytics.view'],
358
- }],
359
- sidebar: [{
360
- id: 'analytics',
361
- title: 'Analytics',
362
- routeId: 'dashboard',
363
- position: 'primary',
364
- group: 'Plugins',
365
- }],
366
- fieldEditors: {
367
- '@acme/rakun-analytics.query': QueryEditor,
368
- },
369
- })
370
-
371
- <ManagerBrowserApp plugins={[analyticsManagerPlugin]} {...props} />
372
- ```
373
-
374
- Use `ManagerFieldEditorProps`, `ManagerFieldEditorRef`, and
375
- `useManagerFieldValue` from `@rakun-kit/manager-react/plugins` when implementing
376
- field editors. Next.js applications should import plugin objects inside a
377
- `'use client'` wrapper around `RakunManagerClientPage`; `RakunManagerPage` accepts
378
- that wrapper through `managerComponent`.
379
-
380
- ### Extending the RichText editor
381
-
382
- Manager plugins can register Lexical nodes and React plugins for every
383
- `RichText` field, including fields rendered inside relations, blocks, and
384
- modules:
385
-
386
- ```tsx
387
- 'use client'
388
-
389
- import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
390
- import {
391
- defineRakunManagerPlugin,
392
- type ManagerRichTextPluginProps,
393
- } from '@rakun-kit/manager-react/plugins'
394
- import { MentionNode } from './MentionNode'
395
-
396
- const MentionsPlugin = ({ id }: ManagerRichTextPluginProps) => {
397
- const [editor] = useLexicalComposerContext()
398
-
399
- // Register commands and listeners on `editor` here.
400
- return null
401
- }
402
-
403
- export const mentionsManagerPlugin = defineRakunManagerPlugin({
404
- id: '@acme/rakun-mentions',
405
- richText: {
406
- nodes: [MentionNode],
407
- plugins: [
408
- {
409
- id: '@acme/rakun-mentions.plugin',
410
- component: MentionsPlugin,
411
- placement: 'editor',
412
- },
413
- ],
414
- },
415
- })
416
- ```
417
-
418
- Lexical plugin components receive `ManagerRichTextPluginProps` (the field id,
419
- configuration, current content type, and related field metadata) and render
420
- inside `LexicalComposer`, so they can use the normal Lexical React hooks.
421
- Supported placements are `toolbar`, `block-format`, `editor` (the default),
422
- `actions-start`, and `actions-end`. `block-format` components render inside the
423
- manager's block selector and can use `ManagerRichTextBlockFormatItem` from
424
- `@rakun-kit/manager-react/rich-text`. Contributions keep declaration order
425
- unless an explicit `order` is provided. Node types and plugin ids must be unique;
426
- conflicts report both plugin owners. Node replacements use Lexical's standard
427
- `LexicalNodeReplacement` configuration.
428
-
429
- ## Exports
430
-
431
- - `@rakun-kit/manager-react`: manager app, providers, clients, navigation, router, layout, media, and state helpers.
432
- - `@rakun-kit/manager-react/client/http`: HTTP manager client.
433
- - `@rakun-kit/manager-react/client/request`: transport-agnostic manager client.
434
- - `@rakun-kit/manager-react/client/trpc`: tRPC proxy client adapter.
435
- - `@rakun-kit/manager-react/app/runtime-app`: `ManagerRuntimeApp`, `ManagerBrowserApp`.
436
- - `@rakun-kit/manager-react/state/navigation`: navigation helpers and provider.
437
- - `@rakun-kit/manager-react/link`: link component provider.
438
- - `@rakun-kit/manager-react/i18n`: locale runtime, catalog, and locale types.
439
- - `@rakun-kit/manager-react/styles.css`: bundled manager styles.
440
-
441
- ## Build
442
-
443
- ```sh
444
- bun run build --workspace @rakun-kit/manager-react
445
- ```
1
+ # @rakun-kit/manager-react
2
+
3
+ React manager UI and client utilities for Rakun.
4
+
5
+ Most apps use this package through framework adapters such as
6
+ `@rakun-kit/next/manager`, but the runtime components and clients are also
7
+ published for custom integrations.
8
+
9
+ ## Browser App
10
+
11
+ Render the manager with a manager client and navigation implementation:
12
+
13
+ ```tsx
14
+ import { ManagerBrowserApp, createHttpManagerClient } from '@rakun-kit/manager-react'
15
+ import '@rakun-kit/manager-react/styles.css'
16
+
17
+ const client = createHttpManagerClient({
18
+ baseUrl: '/api/rakun',
19
+ })
20
+
21
+ export function ManagerPage() {
22
+ return (
23
+ <ManagerBrowserApp client={client} pathname={window.location.pathname} basePath="/backend" />
24
+ )
25
+ }
26
+ ```
27
+
28
+ `ManagerBrowserApp` creates browser path navigation by default. Use
29
+ `ManagerRuntimeApp` when you need to provide custom navigation.
30
+
31
+ Built-in screens are split by manager route and loaded on first visit. Lucide
32
+ names configured for content-type and module-picker icons remain unrestricted;
33
+ the manager accepts kebab-case or PascalCase names and loads the selected icon
34
+ on demand. Hosts can use `preloadManagerPath` when their router wants to
35
+ prefetch a built-in route chunk.
36
+
37
+ ## Realtime synchronization
38
+
39
+ `ManagerBrowserApp` and `ManagerRuntimeApp` read the realtime transport from
40
+ the core `manager.uiLocales` bootstrap response. They automatically use
41
+ polling or SSE according to the configured Rakun platform. Pass the API base
42
+ URL when a relative realtime endpoint must be resolved:
43
+
44
+ ```tsx
45
+ <ManagerBrowserApp
46
+ client={client}
47
+ realtimeBaseUrl="/api/rakun"
48
+ pathname={window.location.pathname}
49
+ />
50
+ ```
51
+
52
+ The default `sseRealtime()` endpoint resolves to `<realtimeBaseUrl>/realtime`,
53
+ so core bootstrap configuration does not repeat the API base path.
54
+
55
+ Event transports subscribe to invalidation topics published by core. Content
56
+ collaboration, locale variants, versions, comments, and notifications refresh
57
+ only after a matching event; their periodic POST requests remain only as the
58
+ polling fallback. The SSE provider multiplexes all active topics over one
59
+ `EventSource`, avoiding per-origin browser connection limits when several
60
+ manager sessions are open. Custom hosts can also pass a `RealtimeProvider`
61
+ directly to `ManagerProvider` or `ManagerAppProvider`.
62
+
63
+ ## Clients
64
+
65
+ HTTP client:
66
+
67
+ ```ts
68
+ import { createHttpManagerClient } from '@rakun-kit/manager-react/client/http'
69
+
70
+ const client = createHttpManagerClient({
71
+ baseUrl: '/api/rakun',
72
+ })
73
+
74
+ const contentTypes = await client.request('manager.contentTypes')
75
+ ```
76
+
77
+ tRPC client adapter:
78
+
79
+ ```ts
80
+ import { createTrpcManagerClient } from '@rakun-kit/manager-react/client/trpc'
81
+
82
+ const managerClient = createTrpcManagerClient(trpcProxyClient)
83
+ ```
84
+
85
+ Custom client:
86
+
87
+ ```ts
88
+ import { createManagerClient } from '@rakun-kit/manager-react/client/request'
89
+
90
+ const client = createManagerClient(async (name, input, options) => {
91
+ // call your transport here
92
+ })
93
+ ```
94
+
95
+ ## Navigation
96
+
97
+ Use `createPathManagerNavigation` for router integrations:
98
+
99
+ ```ts
100
+ import { createPathManagerNavigation } from '@rakun-kit/manager-react/state/navigation'
101
+
102
+ const navigation = createPathManagerNavigation({
103
+ basePath: '/backend',
104
+ push: (href) => router.push(href),
105
+ replace: (href) => router.replace(href),
106
+ })
107
+ ```
108
+
109
+ Hosts that own the router can pass a `linkComponent` to `ManagerBrowserApp` and
110
+ use `preloadManagerPath` from the router package to warm built-in route chunks.
111
+
112
+ ## Link fields
113
+
114
+ The `f.link()` editor shows a title alongside its destination. Users can paste
115
+ or type a direct URL, choose the home page, or browse configured page-route
116
+ types and select one of their entries. Internal selections automatically use
117
+ the item label when the title is empty. Direct links are stored as
118
+ `{ href, title }`; internal links as `{ routeId, contentTypeId, title }` so core
119
+ can resolve localized paths. Persisted legacy URL strings are normalized to
120
+ `{ href, title: '' }` when loaded and remain editable.
121
+
122
+ Compound arrays such as `f.array(f.link())` are reorderable from their drag
123
+ handles in the Info editor, without losing unsaved values inside each item.
124
+
125
+ `f.menu()` provides a navigation-tree editor intended especially for headers.
126
+ Every item uses the same internal/direct destination picker as `f.link()` and
127
+ can contain child items. Drag vertically to reorder and horizontally to nest or
128
+ outdent; the level buttons provide the same operations for keyboard users.
129
+ Removing an item also removes its descendant subtree.
130
+
131
+ ## Media uploads
132
+
133
+ Original file names may contain accents, non-Latin scripts, or emoji. The
134
+ manager transports those names through ASCII-safe upload headers and core
135
+ restores them before saving media metadata.
136
+
137
+ The optimization popover separates Image and Video settings through its Format
138
+ selector. Images retain their normal format and responsive-image controls;
139
+ videos have their own quality and are converted to both MP4 and WebM.
140
+
141
+ With optimization enabled in the media toolbar, an image or video's context
142
+ menu offers `Reimport`. It regenerates the configured outputs while retaining
143
+ the existing media record and all relations to it. Multi-select supports the
144
+ same reimport action from the selection toolbar. Optimization is on by default,
145
+ and generated image previews are stored as inline `data:image/...` LQIP values
146
+ on the media record.
147
+
148
+ An image's context menu also offers `Replace image`. The selected file replaces
149
+ the stored image and generated variants while retaining the existing media ID,
150
+ name, title, alt text, folder, status, and every relation to it. Rakun also
151
+ revalidates directly referencing pages when route revalidation is configured.
152
+ The image editor can crop to a rectangle or circle, rotate, and flip the saved
153
+ copy horizontally or vertically.
154
+ Open a video's details to choose a poster from the media gallery or generate
155
+ one from a chosen second. The generated JPEG is added to the video's folder;
156
+ the linked poster can be changed or removed later.
157
+
158
+ ## Dynamic data mappings
159
+
160
+ Fields configured with core `.help(text)` show a compact help icon beside their
161
+ label. Activating it opens the guidance in a popover, including for inline
162
+ modules and nested content types. Translation keys are resolved with the active
163
+ manager locale, while unregistered strings are displayed directly.
164
+
165
+ The list mapping editor supports recursive `blocks` targets. Select
166
+ `Nested list` for a mapped list property to configure its own source, query,
167
+ filters, item type, and field mapping. `Current document` query values inside
168
+ that nested editor resolve against the parent source item.
169
+
170
+ Arrays of links and arrays of relations, including `relation().multiple()`, can
171
+ choose `Direct field` to bind the whole array or `Nested list` to map every
172
+ source item. Link items show `title` and `href`; relation items show the fields
173
+ of the related content type. Unlike `blocks`, these homogeneous arrays do not
174
+ add a `{ name, value }` wrapper to resolved items.
175
+
176
+ When an array or `.multiple()` relation, file, select, or content-reference field
177
+ declares core `.min(count)` or `.max(count)` limits, its editor shows the current
178
+ count and configured range. It reports values outside that range and prevents
179
+ confirming or adding beyond the maximum.
180
+
181
+ A target link appears as separate `<field>.title` and `<field>.href` mapping
182
+ rows, so its label and destination can come from different source fields.
183
+
184
+ SEO field mappings can use compatible fields from the full current document.
185
+ New documents also honor core string-field `.seo('<seoField>')` configuration
186
+ as an editable initial dynamic data binding.
187
+
188
+ ## llms.txt settings
189
+
190
+ Settings → AI content manages the optional, site-level `llms.txt` Markdown guide served to
191
+ AI tools. Editors can enable publishing, override the SEO-derived title and
192
+ summary, add guidance, and order sections containing internal pages or external
193
+ URLs. Marking a section as Secondary content places it under `## Optional`
194
+ while preserving its title as a subsection in the generated document. For content types with publication states, the internal
195
+ page picker only offers published documents. Nothing is added automatically
196
+ from the sitemap.
197
+
198
+ Authenticated manager layouts place a Visit site action at the top-right of the
199
+ logo block. It opens SEO `siteUrl` in a new tab, falling back to `/` on the
200
+ manager host until that setting is configured. A published document with a
201
+ resolved page route also gets a contextual View page button in its edit toolbar;
202
+ the URL follows the selected manager language and locale variant.
203
+
204
+ Routeable content splits its SEO tab into Metadata and Analysis. Generate
205
+ report renders the current unsaved form through the configured preview and
206
+ reports metadata lengths, heading structure, missing image alt attributes,
207
+ indexability, social metadata, rendered JSON-LD blocks, and Google and sharing
208
+ previews. JSON-LD results identify invalid JSON and missing `@context` or
209
+ `@type`. For saved documents, the report is stored with its score and date when
210
+ the editor can create SEO reports. Previous reports for the active language and
211
+ route can be reopened directly from the document's Analysis view. A protective
212
+ `noindex` on the temporary preview URL does not affect the content's
213
+ indexability result.
214
+
215
+ The secondary sidebar also includes a site-wide SEO screen above the media
216
+ library. It audits stored metadata for published routeable documents in the
217
+ active language, treats global default titles and descriptions as valid
218
+ fallbacks on the home page and warns when another page relies on them, highlights
219
+ duplicates and incomplete custom Open Graph data,
220
+ and stores every run so its score can be followed over time. Access and report
221
+ creation reuse the `SeoSettings` read and own permissions. Evolution snapshots
222
+ and recent page reports are fetched and displayed in bounded, paginated groups.
223
+
224
+ ## Collaborative content editing
225
+
226
+ Existing content documents use a Yjs working document keyed by content type and
227
+ document `_id`; each draft therefore has its own room. Edits synchronize through
228
+ the manager HTTP client and remain shared unsaved changes until Save. Save
229
+ flushes pending CRDT updates, then core materializes and validates the
230
+ server-side working state before updating MongoDB and revalidating. Public pages
231
+ continue to use the previous saved snapshot while editors type.
232
+
233
+ The edit header shows collaborative presence per browser tab. Two tabs for the
234
+ same account therefore appear independently. After two visible avatars, the
235
+ group collapses to `+N`; its tooltip lists all viewers and their focused field,
236
+ and fields focused in another tab are highlighted in the form. Presence is
237
+ ephemeral and does not affect the saved working document. With SSE it follows
238
+ the authenticated stream lifecycle and server heartbeat, without a periodic
239
+ collaboration sync request. The polling fallback refreshes it during polling.
240
+
241
+ The manager persists each user's collaborative rooms in IndexedDB. Previously
242
+ opened content and Template fields load from the local Yjs cache first, remain
243
+ editable during a temporary connection loss, and synchronize when the server
244
+ is reachable again. The toolbar distinguishes this locally stored offline state
245
+ from a snapshot committed by Save.
246
+
247
+ The shared Template editor has its own Yjs room keyed by content type, so it is
248
+ collaborative across editors even when they opened different documents. Its
249
+ snapshot is persisted only by Save and remains separate from every document
250
+ room.
251
+
252
+ The edit toolbar offers a confirmed Discard changes action that restores the
253
+ last saved shared state, including an unsaved shared Template when applicable.
254
+ Its save menu can also save the current form as a named draft variant copy or
255
+ create an independent draft copy.
256
+
257
+ Create-form content becomes collaborative after its first save. Route-layout
258
+ overrides, Settings, literals, users, comments, reviews, and other
259
+ administrative forms are intentionally not CRDT-backed. Content translation
260
+ updates the shared working state and also waits for Save.
261
+
262
+ Variant lifecycle and locale assignments stay transactional, but their manager
263
+ queries refresh while editors are open so changes made by another user appear
264
+ without a page reload.
265
+
266
+ ## Styles
267
+
268
+ Import the package stylesheet once:
269
+
270
+ ```ts
271
+ import '@rakun-kit/manager-react/styles.css'
272
+ ```
273
+
274
+ ## Manager languages
275
+
276
+ English and the locale runtime are built into this package. Extra translations
277
+ ship from a separate package:
278
+
279
+ ```sh
280
+ bun add @rakun-kit/manager-locales
281
+ # or: npm install @rakun-kit/manager-locales
282
+ ```
283
+
284
+ Import only the locale needed by the server bootstrap:
285
+
286
+ ```ts
287
+ import { esManagerLocalePack } from '@rakun-kit/manager-locales/es'
288
+
289
+ rakunBootstrap({
290
+ // ...
291
+ managerLanguages: [esManagerLocalePack],
292
+ })
293
+ ```
294
+
295
+ Each language has an independent subpath, so application bundlers only include
296
+ the locales explicitly imported by the host.
297
+
298
+ Content-type titles and categories can use project-defined message keys without
299
+ adding them to the built-in `ManagerMessageKey` union. Add an English partial
300
+ pack and extend installed locales through `managerLanguages`:
301
+
302
+ ```ts
303
+ import { extendManagerLanguagePack } from '@rakun-kit/core/contracts'
304
+ import { esManagerLocalePack } from '@rakun-kit/manager-locales/es'
305
+
306
+ const managerLanguages = [
307
+ {
308
+ code: 'en',
309
+ name: 'English',
310
+ messages: {
311
+ 'field.title': 'Title',
312
+ 'layoutModule.header': 'Header',
313
+ 'project.contentTypes.article.menu': 'Articles',
314
+ },
315
+ },
316
+ extendManagerLanguagePack(esManagerLocalePack, {
317
+ 'field.title': 'Título',
318
+ 'layoutModule.header': 'Cabecera',
319
+ 'project.contentTypes.article.menu': 'Artículos',
320
+ }),
321
+ ]
322
+ ```
323
+
324
+ Then set `menu.title` or `menu.category` to that arbitrary key. Project keys are
325
+ resolved at runtime but remain outside the static manager catalog.
326
+
327
+ Field labels use the dynamic `field.<fieldName>` namespace. A content-type field
328
+ named `title`, for example, automatically resolves `field.title` in edit forms
329
+ and list columns without adding it to `ManagerMessageKey`.
330
+
331
+ Layout module labels follow `layoutModule.<layoutKey>`. For example, the
332
+ `header` slot automatically resolves `layoutModule.header` in its tab and
333
+ configuration panel.
334
+
335
+ ## Event Logs
336
+
337
+ Settings → Logs displays the persistent Rakun event stream through the native
338
+ `manager.logs.list` operation. The screen supports filters, cursor pagination,
339
+ and structured event details. Both the settings card and the operation require
340
+ the `system.eventLog.read` permission, which can be assigned from Settings →
341
+ User Roles.
342
+
343
+ ## Manager Plugins
344
+
345
+ Manager plugins run inside the normal providers and can add dashboard routes,
346
+ sidebar items, and custom field editors:
347
+
348
+ ```tsx
349
+ import {
350
+ defineRakunManagerPlugin,
351
+ ManagerBrowserApp,
352
+ } from '@rakun-kit/manager-react'
353
+
354
+ const analyticsManagerPlugin = defineRakunManagerPlugin({
355
+ id: '@acme/rakun-analytics',
356
+ routes: [{
357
+ id: 'dashboard',
358
+ path: '/analytics',
359
+ component: AnalyticsScreen,
360
+ permissions: ['plugin.analytics.view'],
361
+ }],
362
+ sidebar: [{
363
+ id: 'analytics',
364
+ title: 'Analytics',
365
+ routeId: 'dashboard',
366
+ position: 'primary',
367
+ group: 'Plugins',
368
+ }],
369
+ fieldEditors: {
370
+ '@acme/rakun-analytics.query': QueryEditor,
371
+ },
372
+ })
373
+
374
+ <ManagerBrowserApp plugins={[analyticsManagerPlugin]} {...props} />
375
+ ```
376
+
377
+ Use `ManagerFieldEditorProps`, `ManagerFieldEditorRef`, and
378
+ `useManagerFieldValue` from `@rakun-kit/manager-react/plugins` when implementing
379
+ field editors. Next.js applications should import plugin objects inside a
380
+ `'use client'` wrapper around `RakunManagerClientPage`; `RakunManagerPage` accepts
381
+ that wrapper through `managerComponent`.
382
+
383
+ ### Extending the RichText editor
384
+
385
+ Manager plugins can register Lexical nodes and React plugins for every
386
+ `RichText` field, including fields rendered inside relations, blocks, and
387
+ modules:
388
+
389
+ ```tsx
390
+ 'use client'
391
+
392
+ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
393
+ import {
394
+ defineRakunManagerPlugin,
395
+ type ManagerRichTextPluginProps,
396
+ } from '@rakun-kit/manager-react/plugins'
397
+ import { MentionNode } from './MentionNode'
398
+
399
+ const MentionsPlugin = ({ id }: ManagerRichTextPluginProps) => {
400
+ const [editor] = useLexicalComposerContext()
401
+
402
+ // Register commands and listeners on `editor` here.
403
+ return null
404
+ }
405
+
406
+ export const mentionsManagerPlugin = defineRakunManagerPlugin({
407
+ id: '@acme/rakun-mentions',
408
+ richText: {
409
+ nodes: [MentionNode],
410
+ plugins: [
411
+ {
412
+ id: '@acme/rakun-mentions.plugin',
413
+ component: MentionsPlugin,
414
+ placement: 'editor',
415
+ },
416
+ ],
417
+ },
418
+ })
419
+ ```
420
+
421
+ Lexical plugin components receive `ManagerRichTextPluginProps` (the field id,
422
+ configuration, current content type, and related field metadata) and render
423
+ inside `LexicalComposer`, so they can use the normal Lexical React hooks.
424
+ Supported placements are `toolbar`, `block-format`, `editor` (the default),
425
+ `actions-start`, and `actions-end`. `block-format` components render inside the
426
+ manager's block selector and can use `ManagerRichTextBlockFormatItem` from
427
+ `@rakun-kit/manager-react/rich-text`. Contributions keep declaration order
428
+ unless an explicit `order` is provided. Node types and plugin ids must be unique;
429
+ conflicts report both plugin owners. Node replacements use Lexical's standard
430
+ `LexicalNodeReplacement` configuration.
431
+
432
+ ## Exports
433
+
434
+ - `@rakun-kit/manager-react`: manager app, providers, clients, navigation, router, layout, media, and state helpers.
435
+ - `@rakun-kit/manager-react/client/http`: HTTP manager client.
436
+ - `@rakun-kit/manager-react/client/request`: transport-agnostic manager client.
437
+ - `@rakun-kit/manager-react/client/trpc`: tRPC proxy client adapter.
438
+ - `@rakun-kit/manager-react/app/runtime-app`: `ManagerRuntimeApp`, `ManagerBrowserApp`.
439
+ - `@rakun-kit/manager-react/state/navigation`: navigation helpers and provider.
440
+ - `@rakun-kit/manager-react/link`: link component provider.
441
+ - `@rakun-kit/manager-react/i18n`: locale runtime, catalog, and locale types.
442
+ - `@rakun-kit/manager-react/styles.css`: bundled manager styles.
443
+
444
+ ## Build
445
+
446
+ ```sh
447
+ bun run build --workspace @rakun-kit/manager-react
448
+ ```