@plutocms/pluto 0.10.0 → 0.12.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.
Files changed (39) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/FEATURES.md +17 -0
  3. package/README.md +30 -0
  4. package/app/assets/css/admin-theme.css +207 -0
  5. package/app/assets/css/pluto-layer.css +40 -0
  6. package/app/components/ColorModeButton.vue +4 -2
  7. package/app/components/Pluto/Content/PlutoContentForm.vue +325 -50
  8. package/app/components/Pluto/Content/PlutoContentList.vue +192 -108
  9. package/app/components/Pluto/Dashboard/PlutoDashboardContentActivity.vue +114 -0
  10. package/app/components/Pluto/Dashboard/PlutoDashboardContentOverview.vue +103 -0
  11. package/app/components/Pluto/Dashboard/PlutoDashboardQuickActions.vue +70 -0
  12. package/app/components/Pluto/Navbar/PlutoNavbarAdmin.vue +6 -1
  13. package/app/components/Pluto/View/PlutoViewAdminPanel.vue +119 -0
  14. package/app/components/Pluto/View/PlutoViewAside.vue +79 -0
  15. package/app/components/Pluto/View/PlutoViewToolbar.vue +64 -0
  16. package/app/composables/pluto-admin-theme.ts +14 -0
  17. package/app/composables/pluto-command-palette.ts +59 -0
  18. package/app/composables/pluto-content-actions.ts +17 -0
  19. package/app/composables/pluto-content-activity.ts +37 -0
  20. package/app/composables/pluto-content-stats.ts +78 -0
  21. package/app/composables/pluto-extension.ts +1 -0
  22. package/app/composables/pluto-registry.ts +1 -0
  23. package/app/composables/pluto-view.ts +9 -0
  24. package/app/layouts/admin.vue +59 -62
  25. package/app/layouts/auth.vue +4 -2
  26. package/app/pages/admin/home.vue +34 -7
  27. package/app/plugins/pluto-core-registrations.ts +34 -0
  28. package/modules/pluto-css/css-entries.ts +182 -0
  29. package/modules/pluto-css/index.ts +86 -0
  30. package/nuxt.config.ts +3 -1
  31. package/package.json +23 -22
  32. package/scripts/generate-admin-theme.cjs +96 -0
  33. package/server/api/_pluto/content/[type]/index.get.ts +4 -1
  34. package/server/api/_pluto/content-activity/[type].get.ts +15 -0
  35. package/server/utils/pluto-content-handlers.ts +65 -1
  36. package/shared/types/content.ts +37 -0
  37. package/shared/types/registry.ts +30 -0
  38. package/shared/utils/content-map.ts +15 -0
  39. package/app/assets/css/tailwind.css +0 -11
@@ -110,6 +110,8 @@ export interface PlutoContentTypeCapabilities {
110
110
  read?: string
111
111
  write?: string
112
112
  delete?: string
113
+ /** Capability required to publish. Falls back to `write` when unset. */
114
+ publish?: string
113
115
  }
114
116
 
115
117
  export interface PlutoContentStatus {
@@ -209,6 +211,17 @@ export interface PlutoContentType {
209
211
  editPath?: (id: string | number) => string
210
212
  navOrder?: number
211
213
  i18n?: PlutoContentI18n
214
+ /** Layout options for the generic content form (a later wave). */
215
+ form?: {
216
+ /** 'default' = heading + container. 'focus' = minimal chrome for a writing surface. */
217
+ layout?: 'default' | 'focus'
218
+ /** true = always show. false = never. 'auto' (default) = show when any field has region 'side'. */
219
+ aside?: boolean | 'auto'
220
+ /** Aside starts closed. Default false, or true when layout is 'focus'. */
221
+ asideCollapsed?: boolean
222
+ /** Drop the max-width container in the main region. Default false, or true when layout is 'focus'. */
223
+ width?: 'container' | 'full'
224
+ }
212
225
  /**
213
226
  * Server-only lifecycle hooks, run by the generic write handlers
214
227
  * (`server/utils/pluto-content-handlers.ts`) after the adapter call
@@ -276,4 +289,28 @@ export interface PlutoContentAdapter {
276
289
  /** Throws when `event`'s caller does not hold `capability`. Optional — see the fail-open rule in the content-model skill. */
277
290
  authorize?: (event: H3Event, capability: string) => Promise<void>
278
291
  describe?: (ctx: PlutoContentContext) => Promise<string[] | null>
292
+ /**
293
+ * Counts the items created from `range.from` (inclusive) to `range.to`
294
+ * (exclusive), both ISO timestamps. Optional. Return `null` when the
295
+ * content type has no created timestamp. The dashboard activity chart
296
+ * calls it once per week; without it, the chart leaves the type out.
297
+ */
298
+ countCreated?: (ctx: PlutoContentContext, range: {
299
+ from: string
300
+ to: string
301
+ }) => Promise<number | null>
302
+ }
303
+
304
+ /** One week of the content activity series. */
305
+ export interface PlutoContentActivityPoint {
306
+ /** Start of the week, an ISO date (YYYY-MM-DD), Monday, UTC. */
307
+ start: string
308
+ count: number
309
+ }
310
+
311
+ export interface PlutoContentActivity {
312
+ /** `false` when the adapter, or this content type, cannot count by creation date. */
313
+ supported: boolean
314
+ interval: 'week'
315
+ points: PlutoContentActivityPoint[]
279
316
  }
@@ -113,6 +113,34 @@ export interface PlutoMediaAdapterEntry extends PlutoEntry {
113
113
  adapter: PlutoMediaAdapter
114
114
  }
115
115
 
116
+ /** State passed to a `PlutoContentAction`'s `show` test and `onSelect` handler. */
117
+ export interface PlutoContentActionContext {
118
+ type: PlutoContentType
119
+ item: Record<string, unknown>
120
+ editing: boolean
121
+ saving: boolean
122
+ save: () => Promise<void>
123
+ remove: () => Promise<void>
124
+ }
125
+
126
+ /**
127
+ * A button a layer adds to the generic content form (a later wave). Matched
128
+ * against a content type the same way `PlutoContentFieldWidget` matches a
129
+ * field: by exact name, or by `'*'` for every content type.
130
+ */
131
+ export interface PlutoContentAction extends PlutoEntry {
132
+ /** Content type `name`, or '*' for every content type. */
133
+ contentType: string | '*'
134
+ label: string
135
+ icon?: string
136
+ color?: string
137
+ variant?: string
138
+ /** Where the action renders. Default 'toolbar'. */
139
+ placement?: 'toolbar' | 'aside'
140
+ show?: (ctx: PlutoContentActionContext) => boolean
141
+ onSelect: (ctx: PlutoContentActionContext) => void | Promise<void>
142
+ }
143
+
116
144
  export interface PlutoExtension {
117
145
  /** Layer name. Use the package's $meta.name, e.g. 'supabase-blog'. */
118
146
  id: string
@@ -127,6 +155,7 @@ export interface PlutoExtension {
127
155
  permissionsDriver?: PlutoPermissionsDriver
128
156
  contentTypes?: PlutoContentTypeEntry[]
129
157
  contentFieldWidgets?: PlutoContentFieldWidget[]
158
+ contentActions?: PlutoContentAction[]
130
159
  mediaAdapters?: PlutoMediaAdapterEntry[]
131
160
  }
132
161
 
@@ -154,5 +183,6 @@ export interface PlutoRegistry {
154
183
  permissionsDrivers: PlutoRegistryBucket<PlutoPermissionsDriver>
155
184
  contentTypes: PlutoRegistryBucket<PlutoContentTypeEntry>
156
185
  contentFieldWidgets: PlutoRegistryBucket<PlutoContentFieldWidget>
186
+ contentActions: PlutoRegistryBucket<PlutoContentAction>
157
187
  mediaAdapters: PlutoRegistryBucket<PlutoMediaAdapterEntry>
158
188
  }
@@ -43,6 +43,14 @@ export function mapFieldsToColumns(
43
43
  * `mapFieldsToColumns` — it has no column to read back from `row` in the
44
44
  * first place, so this only guards against a stray column that happens to
45
45
  * share the field's name.
46
+ *
47
+ * When `type.status` is set, this also maps the status column
48
+ * (`type.status.column`, default `'status'`) to `item.status`, unless a
49
+ * declared field already set `item.status` above. This mirrors the write
50
+ * side: `@plutocms/supabase`'s `content-adapter.ts` already writes the
51
+ * status column for a content type with workflow enabled, even when no
52
+ * field named `status` is declared. Without this, a generic list/form
53
+ * read back a row with no `status` key at all.
46
54
  */
47
55
  export function mapColumnsToFields(
48
56
  type: PlutoContentType,
@@ -61,5 +69,12 @@ export function mapColumnsToFields(
61
69
  }
62
70
  }
63
71
 
72
+ if (type.status) {
73
+ const statusColumn = type.status.column ?? 'status'
74
+ if (!Object.hasOwn(item, 'status') && Object.hasOwn(row, statusColumn)) {
75
+ item.status = row[statusColumn]
76
+ }
77
+ }
78
+
64
79
  return item as PlutoContentItem
65
80
  }
@@ -1,11 +0,0 @@
1
- @import 'tailwindcss';
2
- @import '@nuxt/ui';
3
-
4
- @source '../..';
5
-
6
- @theme {
7
- --color-admin-content: var(--color-zinc-800);
8
- --color-admin-sidebar: var(--color-zinc-900);
9
-
10
- --font-outfit: 'Outfit', sans-serif;
11
- }