@questpie/admin 3.5.2 → 3.5.4

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 (195) hide show
  1. package/README.md +8 -0
  2. package/dist/client/blocks/block-renderer.d.mts +2 -2
  3. package/dist/client/builder/index.d.mts +1 -1
  4. package/dist/client/builder/types/collection-types.d.mts +89 -5
  5. package/dist/client/builder/types/common.d.mts +5 -0
  6. package/dist/client/builder/types/field-types.d.mts +41 -1
  7. package/dist/client/builder/view/view.d.mts +3 -2
  8. package/dist/client/components/actions/action-dialog.mjs +5 -0
  9. package/dist/client/components/admin-link.d.mts +2 -2
  10. package/dist/client/components/fields/boolean-field.mjs +2 -1
  11. package/dist/client/components/fields/date-field.mjs +2 -1
  12. package/dist/client/components/fields/datetime-field.mjs +2 -1
  13. package/dist/client/components/fields/email-field.mjs +2 -1
  14. package/dist/client/components/fields/field-utils.d.mts +11 -0
  15. package/dist/client/components/fields/field-utils.mjs +3 -1
  16. package/dist/client/components/fields/field-wrapper.mjs +3 -3
  17. package/dist/client/components/fields/number-field.mjs +2 -1
  18. package/dist/client/components/fields/object-field.mjs +2 -1
  19. package/dist/client/components/fields/relation/displays/types.mjs +3 -3
  20. package/dist/client/components/fields/rich-text-editor/bubble-menu.mjs +7 -0
  21. package/dist/client/components/fields/rich-text-editor/extensions.mjs +19 -2
  22. package/dist/client/components/fields/rich-text-editor/image-popover.mjs +6 -2
  23. package/dist/client/components/fields/rich-text-editor/image-upload.mjs +2 -1
  24. package/dist/client/components/fields/rich-text-editor/index.d.mts +5 -3
  25. package/dist/client/components/fields/rich-text-editor/index.mjs +38 -76
  26. package/dist/client/components/fields/rich-text-editor/slash-commands.mjs +30 -7
  27. package/dist/client/components/fields/rich-text-editor/toolbar.mjs +1 -312
  28. package/dist/client/components/fields/rich-text-editor/types.d.mts +4 -0
  29. package/dist/client/components/fields/rich-text-editor/types.mjs +1 -1
  30. package/dist/client/components/fields/rich-text-editor/utils.mjs +6 -12
  31. package/dist/client/components/fields/select-field.mjs +2 -1
  32. package/dist/client/components/fields/text-field.mjs +2 -1
  33. package/dist/client/components/fields/textarea-field.mjs +2 -1
  34. package/dist/client/components/fields/time-field.mjs +2 -1
  35. package/dist/client/components/filter-builder/filter-builder-sheet.mjs +75 -22
  36. package/dist/client/components/layout/field-layout-renderer.mjs +4 -4
  37. package/dist/client/components/media/media-grid.mjs +2 -1
  38. package/dist/client/components/primitives/asset-preview.mjs +4 -2
  39. package/dist/client/components/primitives/dropzone.d.mts +100 -0
  40. package/dist/client/components/primitives/field-select-control.mjs +2 -1
  41. package/dist/client/components/ui/button.d.mts +23 -0
  42. package/dist/client/components/ui/button.mjs +2 -2
  43. package/dist/client/components/ui/dropdown-menu.d.mts +49 -0
  44. package/dist/client/components/ui/dropdown-menu.mjs +7 -19
  45. package/dist/client/components/ui/popover.mjs +1 -1
  46. package/dist/client/components/ui/search-input.d.mts +56 -0
  47. package/dist/client/components/ui/select.mjs +2 -2
  48. package/dist/client/components/ui/sheet.d.mts +40 -0
  49. package/dist/client/components/ui/table.d.mts +49 -0
  50. package/dist/client/components/ui/table.mjs +15 -1
  51. package/dist/client/components/ui/tooltip.d.mts +21 -0
  52. package/dist/client/contexts/focus-context.d.mts +2 -2
  53. package/dist/client/hooks/query-access.d.mts +9 -0
  54. package/dist/client/hooks/query-access.mjs +20 -0
  55. package/dist/client/hooks/typed-hooks.d.mts +4 -2
  56. package/dist/client/hooks/typed-hooks.mjs +30 -29
  57. package/dist/client/hooks/use-admin-config.mjs +20 -1
  58. package/dist/client/hooks/use-autosave.mjs +91 -0
  59. package/dist/client/hooks/use-collection.mjs +65 -23
  60. package/dist/client/hooks/use-reactive-fields.d.mts +1 -0
  61. package/dist/client/hooks/use-reactive-fields.mjs +16 -1
  62. package/dist/client/hooks/use-server-actions.mjs +12 -1
  63. package/dist/client/hooks/use-upload.d.mts +40 -0
  64. package/dist/client/hooks/use-upload.mjs +4 -2
  65. package/dist/client/hooks/use-view-state.mjs +15 -7
  66. package/dist/client/i18n/hooks.d.mts +20 -0
  67. package/dist/client/lib/utils.d.mts +6 -0
  68. package/dist/client/lib/view-filter-utils.mjs +30 -0
  69. package/dist/client/preview/block-scope-context.d.mts +2 -2
  70. package/dist/client/preview/preview-banner.d.mts +2 -2
  71. package/dist/client/preview/preview-field.d.mts +4 -4
  72. package/dist/client/runtime/provider.mjs +22 -3
  73. package/dist/client/scope/picker.d.mts +2 -2
  74. package/dist/client/scope/provider.d.mts +2 -2
  75. package/dist/client/styles/base.css +75 -79
  76. package/dist/client/utils/asset-url.mjs +27 -0
  77. package/dist/client/utils/build-field-definitions-from-schema.mjs +1 -0
  78. package/dist/client/views/auth/accept-invite-form.d.mts +2 -2
  79. package/dist/client/views/auth/auth-layout.d.mts +3 -3
  80. package/dist/client/views/auth/forgot-password-form.d.mts +2 -2
  81. package/dist/client/views/auth/login-form.d.mts +2 -2
  82. package/dist/client/views/auth/reset-password-form.d.mts +2 -2
  83. package/dist/client/views/auth/setup-form.d.mts +2 -2
  84. package/dist/client/views/collection/auto-form-fields.mjs +7 -6
  85. package/dist/client/views/collection/cells/primitive-cells.mjs +9 -6
  86. package/dist/client/views/collection/cells/shared/asset-thumbnail.d.mts +7 -0
  87. package/dist/client/views/collection/cells/shared/asset-thumbnail.mjs +3 -2
  88. package/dist/client/views/collection/cells/shared/cell-helpers.mjs +3 -2
  89. package/dist/client/views/collection/cells/upload-cells.mjs +2 -1
  90. package/dist/client/views/collection/columns/build-columns.mjs +3 -1
  91. package/dist/client/views/collection/document-view.d.mts +30 -0
  92. package/dist/client/views/collection/document-view.mjs +377 -0
  93. package/dist/client/views/collection/field-context.mjs +3 -2
  94. package/dist/client/views/collection/field-renderer.mjs +13 -5
  95. package/dist/client/views/collection/form-view.mjs +221 -282
  96. package/dist/client/views/collection/list-view.mjs +592 -190
  97. package/dist/client/views/collection/outline.mjs +44 -19
  98. package/dist/client/views/collection/quick-filter-bar.mjs +45 -0
  99. package/dist/client/views/collection/table-view.mjs +61 -17
  100. package/dist/client/views/globals/global-form-view.mjs +12 -9
  101. package/dist/client/views/layout/admin-layout-provider.mjs +4 -3
  102. package/dist/client/views/layout/admin-layout.mjs +108 -21
  103. package/dist/client/views/layout/admin-router.mjs +19 -3
  104. package/dist/client/views/layout/admin-sidebar.mjs +70 -20
  105. package/dist/client/views/layout/admin-theme.mjs +5 -4
  106. package/dist/client/views/layout/admin-view-layout.d.mts +36 -0
  107. package/dist/client/views/pages/accept-invite-page.d.mts +2 -2
  108. package/dist/client/views/pages/dashboard-page.d.mts +2 -2
  109. package/dist/client/views/pages/forgot-password-page.d.mts +2 -2
  110. package/dist/client/views/pages/invite-page.d.mts +2 -2
  111. package/dist/client/views/pages/login-page.d.mts +2 -2
  112. package/dist/client/views/pages/reset-password-page.d.mts +2 -2
  113. package/dist/client/views/pages/setup-page.d.mts +2 -2
  114. package/dist/client.d.mts +17 -2
  115. package/dist/client.mjs +17 -2
  116. package/dist/components/rich-text/rich-text-renderer.d.mts +5 -5
  117. package/dist/components/rich-text/rich-text-renderer.mjs +5 -2
  118. package/dist/factories.d.mts +4 -2
  119. package/dist/factories.mjs +2 -2
  120. package/dist/index.d.mts +17 -3
  121. package/dist/index.mjs +17 -2
  122. package/dist/modules/admin.d.mts +1 -1
  123. package/dist/server/adapters/index.d.mts +2 -0
  124. package/dist/server/adapters/nextjs.d.mts +1 -0
  125. package/dist/server/augmentation/actions.d.mts +9 -3
  126. package/dist/server/augmentation/dashboard.d.mts +11 -11
  127. package/dist/server/augmentation/form-layout.d.mts +16 -6
  128. package/dist/server/augmentation/index.d.mts +7 -0
  129. package/dist/server/augmentation/sidebar.d.mts +8 -8
  130. package/dist/server/augmentation/views.d.mts +4 -1
  131. package/dist/server/auth-helpers.d.mts +1 -0
  132. package/dist/server/codegen/admin-client-template.mjs +7 -6
  133. package/dist/server/fields/blocks.mjs +4 -1
  134. package/dist/server/fields/index.d.mts +1 -1
  135. package/dist/server/fields/reactive-runtime.mjs +3 -0
  136. package/dist/server/fields/rich-text.d.mts +16 -17
  137. package/dist/server/fields/rich-text.mjs +18 -7
  138. package/dist/server/i18n/messages/cs.mjs +2 -0
  139. package/dist/server/i18n/messages/de.mjs +2 -0
  140. package/dist/server/i18n/messages/en.mjs +4 -0
  141. package/dist/server/i18n/messages/es.mjs +2 -0
  142. package/dist/server/i18n/messages/fr.mjs +2 -0
  143. package/dist/server/i18n/messages/pl.mjs +2 -0
  144. package/dist/server/i18n/messages/pt.mjs +2 -0
  145. package/dist/server/i18n/messages/sk.mjs +2 -0
  146. package/dist/server/modules/admin/.generated/module.d.mts +1 -1
  147. package/dist/server/modules/admin/auth-helpers.mjs +7 -1
  148. package/dist/server/modules/admin/block/block-builder.d.mts +0 -8
  149. package/dist/server/modules/admin/block/introspection.d.mts +2 -2
  150. package/dist/server/modules/admin/block/introspection.mjs +28 -4
  151. package/dist/server/modules/admin/block/prefetch.d.mts +11 -0
  152. package/dist/server/modules/admin/block/prefetch.mjs +108 -27
  153. package/dist/server/modules/admin/client/.generated/module.d.mts +68 -67
  154. package/dist/server/modules/admin/client/.generated/module.mjs +2 -0
  155. package/dist/server/modules/admin/client/views/collection-document.d.mts +6 -0
  156. package/dist/server/modules/admin/client/views/collection-document.mjs +10 -0
  157. package/dist/server/modules/admin/collections/account.d.mts +53 -52
  158. package/dist/server/modules/admin/collections/admin-locks.d.mts +57 -56
  159. package/dist/server/modules/admin/collections/admin-preferences.d.mts +38 -37
  160. package/dist/server/modules/admin/collections/admin-saved-views.d.mts +50 -49
  161. package/dist/server/modules/admin/collections/apikey.d.mts +76 -67
  162. package/dist/server/modules/admin/collections/assets.d.mts +37 -36
  163. package/dist/server/modules/admin/collections/session.d.mts +42 -41
  164. package/dist/server/modules/admin/collections/user.d.mts +57 -56
  165. package/dist/server/modules/admin/collections/verification.d.mts +34 -33
  166. package/dist/server/modules/admin/dto/admin-config.dto.mjs +34 -4
  167. package/dist/server/modules/admin/factories.mjs +4 -34
  168. package/dist/server/modules/admin/index.d.mts +3 -3
  169. package/dist/server/modules/admin/routes/admin-config.d.mts +4 -2
  170. package/dist/server/modules/admin/routes/admin-config.mjs +56 -24
  171. package/dist/server/modules/admin/routes/execute-action.d.mts +9 -9
  172. package/dist/server/modules/admin/routes/execute-action.mjs +35 -9
  173. package/dist/server/modules/admin/routes/locales.mjs +1 -1
  174. package/dist/server/modules/admin/routes/preview.d.mts +11 -11
  175. package/dist/server/modules/admin/routes/preview.mjs +6 -5
  176. package/dist/server/modules/admin/routes/reactive.d.mts +9 -9
  177. package/dist/server/modules/admin/routes/reactive.mjs +2 -2
  178. package/dist/server/modules/admin/routes/route-helpers.d.mts +11 -7
  179. package/dist/server/modules/admin/routes/route-helpers.mjs +1 -1
  180. package/dist/server/modules/admin/routes/setup.d.mts +7 -7
  181. package/dist/server/modules/admin/routes/translations.d.mts +4 -4
  182. package/dist/server/modules/admin/routes/widget-data.d.mts +5 -5
  183. package/dist/server/modules/admin/routes/widget-data.mjs +12 -4
  184. package/dist/server/modules/admin-preferences/collections/saved-views.d.mts +45 -45
  185. package/dist/server/modules/audit/.generated/module.d.mts +6 -6
  186. package/dist/server/modules/audit/collections/audit-log.d.mts +81 -80
  187. package/dist/server/modules/audit/jobs/audit-cleanup.d.mts +2 -2
  188. package/dist/server/plugin.mjs +10 -5
  189. package/dist/server/proxy-factories.d.mts +8 -1
  190. package/dist/server/proxy-factories.mjs +33 -1
  191. package/dist/server.d.mts +3 -1
  192. package/dist/shared/types/index.d.mts +1 -0
  193. package/dist/shared/types/saved-views.types.d.mts +14 -7
  194. package/dist/shared.d.mts +3 -2
  195. package/package.json +5 -4
package/README.md CHANGED
@@ -65,6 +65,14 @@ export default adminConfig({
65
65
 
66
66
  Collections, globals, routes, and jobs are auto-discovered via file convention. Codegen produces a `.generated/index.ts` with the fully-typed `App` and runtime `app` instance.
67
67
 
68
+ ### Admin Auth Contract
69
+
70
+ `@questpie/admin` expects the app to use `adminModule`'s starter Better Auth model, or an equivalent auth setup that exposes `session.user.role` on the active session.
71
+
72
+ Access to the admin panel and admin RPC routes is granted only when `session.user.role === "admin"`. This includes admin config, content locale callbacks, preview URL/token generation, actions, widgets, and reactive field handlers. Authenticated users with `role: "user"` or a missing role are not admin users. The built-in setup route uses the same contract when checking for and creating the first admin user with `role = "admin"`.
73
+
74
+ If you extend the Better Auth `user` collection, merge the starter user collection instead of replacing it from scratch. Custom user collections must preserve the role field and session role propagation used by the admin guard.
75
+
68
76
  ### Collection Admin Config
69
77
 
70
78
  Admin metadata, list views, and form views are defined on the collection itself:
@@ -1,6 +1,6 @@
1
1
  import { BlockContent } from "./types.mjs";
2
2
  import * as React from "react";
3
- import * as react_jsx_runtime25 from "react/jsx-runtime";
3
+ import * as react_jsx_runtime54 from "react/jsx-runtime";
4
4
 
5
5
  //#region src/client/blocks/block-renderer.d.ts
6
6
 
@@ -50,6 +50,6 @@ declare function BlockRenderer({
50
50
  onBlockClick,
51
51
  onBlockInsert,
52
52
  className
53
- }: BlockRendererProps): react_jsx_runtime25.JSX.Element | null;
53
+ }: BlockRendererProps): react_jsx_runtime54.JSX.Element | null;
54
54
  //#endregion
55
55
  export { BlockRenderer, BlockRendererProps };
@@ -4,7 +4,7 @@ import { AnyWidgetConfig, WidgetAction, WidgetCardVariant, WidgetComponentProps,
4
4
  import { DashboardAction, DashboardConfig, DashboardLayoutItem, DashboardSection, DashboardTabConfig, DashboardTabs } from "./types/ui-config.mjs";
5
5
  import { AdminState } from "./admin-types.mjs";
6
6
  import { Admin, AppAdmin, InferAdminCMS } from "./admin.mjs";
7
- import { ComponentRegistry, FieldComponentProps, FieldLayoutItem, FormSidebarConfig, FormViewConfig, SectionLayout, TabConfig, TabsLayout } from "./types/field-types.mjs";
7
+ import { ComponentRegistry, DocumentViewConfig, FieldComponentProps, FieldLayoutItem, FormSidebarConfig, FormViewConfig, SectionLayout, TabConfig, TabsLayout } from "./types/field-types.mjs";
8
8
  import { QuestpieApp, QuestpieClient } from "questpie/client";
9
9
  import { CollectionInfer } from "questpie";
10
10
 
@@ -1,8 +1,10 @@
1
- import "../../i18n/types.mjs";
2
- import { MaybeLazyComponent } from "./common.mjs";
1
+ import { I18nText } from "../../i18n/types.mjs";
2
+ import { IconComponent, MaybeLazyComponent } from "./common.mjs";
3
+ import { ComponentReference } from "../../../server/augmentation/common.mjs";
4
+ import { FilterRule, QuickFilterConfig } from "../../../shared/types/saved-views.types.mjs";
3
5
  import "../../../server/augmentation.mjs";
4
- import "../field/field.mjs";
5
- import "../admin.mjs";
6
+ import { FieldDefinition } from "../field/field.mjs";
7
+ import { Admin } from "../admin.mjs";
6
8
  import { ActionsConfig } from "./action-types.mjs";
7
9
 
8
10
  //#region src/client/builder/types/collection-types.d.ts
@@ -124,6 +126,14 @@ interface ListViewConfig<TFieldNames extends string = string> {
124
126
  field: TFieldNames;
125
127
  direction: "asc" | "desc";
126
128
  };
129
+ /**
130
+ * Initial filters used when the user has no saved view state.
131
+ */
132
+ defaultFilters?: FilterRule[];
133
+ /**
134
+ * Header-level quick filter presets.
135
+ */
136
+ quickFilters?: QuickFilterConfig[];
127
137
  /**
128
138
  * Enables reorder mode for this list.
129
139
  * Requires a numeric field named `order` on the collection.
@@ -193,5 +203,79 @@ interface ListViewConfig<TFieldNames extends string = string> {
193
203
  */
194
204
  actions?: ActionsConfig;
195
205
  }
206
+ /**
207
+ * Preview configuration for live preview in form view
208
+ */
209
+ interface PreviewConfig {
210
+ /**
211
+ * URL builder function that returns preview URL for current form values
212
+ * @param values - Current form values
213
+ * @param locale - Current content locale
214
+ * @returns Preview URL string
215
+ */
216
+ url: (values: Record<string, unknown>, locale: string) => string;
217
+ /**
218
+ * Whether preview is enabled (default: true)
219
+ */
220
+ enabled?: boolean;
221
+ /**
222
+ * Position of the preview panel (default: "right")
223
+ */
224
+ position?: "right" | "bottom";
225
+ /**
226
+ * Default width/height percentage of preview panel (default: 50)
227
+ */
228
+ defaultWidth?: number;
229
+ /**
230
+ * Minimum width/height percentage (default: 30)
231
+ */
232
+ minWidth?: number;
233
+ /**
234
+ * Maximum width/height percentage (default: 70)
235
+ */
236
+ maxWidth?: number;
237
+ }
238
+ /**
239
+ * Autosave configuration for form view
240
+ */
241
+ interface AutoSaveConfig {
242
+ /**
243
+ * Whether autosave is enabled
244
+ * @default false
245
+ */
246
+ enabled?: boolean;
247
+ /**
248
+ * Debounce delay in milliseconds before autosave triggers
249
+ * @default 500 (0.5s as specified)
250
+ */
251
+ debounce?: number;
252
+ /**
253
+ * Show autosave status indicator in form header
254
+ * @default true
255
+ */
256
+ indicator?: boolean;
257
+ /**
258
+ * Warn user before navigating away with unsaved changes
259
+ * @default true
260
+ */
261
+ preventNavigation?: boolean;
262
+ }
263
+ /**
264
+ * Collection builder state - internal state during building
265
+ */
266
+ interface CollectionBuilderState<TAdminApp extends Admin<any> = Admin<any>> {
267
+ readonly name: string;
268
+ readonly "~adminApp": TAdminApp;
269
+ /** Display label - supports inline translations */
270
+ readonly label?: I18nText;
271
+ /** Description - supports inline translations */
272
+ readonly description?: I18nText;
273
+ readonly icon?: IconComponent | ComponentReference;
274
+ readonly fields?: Record<string, FieldDefinition>;
275
+ readonly list?: any;
276
+ readonly form?: any;
277
+ readonly preview?: PreviewConfig;
278
+ readonly autoSave?: AutoSaveConfig;
279
+ }
196
280
  //#endregion
197
- export { ListViewConfig };
281
+ export { CollectionBuilderState, ListViewConfig };
@@ -39,6 +39,11 @@ type BaseFieldProps = {
39
39
  required?: boolean;
40
40
  localized?: boolean;
41
41
  locale?: string;
42
+ /**
43
+ * Render the control WITHOUT its own label/description. Used by compact
44
+ * layouts (e.g. Notion-style property rows) that supply the label themselves.
45
+ */
46
+ hideLabel?: boolean;
42
47
  control?: any;
43
48
  className?: string;
44
49
  };
@@ -578,6 +578,46 @@ interface FormViewActionsConfig<TItem = any> {
578
578
  /** Actions shown in dropdown menu (...) */
579
579
  secondary?: ActionDefinition<TItem>[];
580
580
  }
581
+ /**
582
+ * Document view configuration — a Notion-style page with a dominant rich-text
583
+ * body and inline-editable property rows.
584
+ *
585
+ * No hardcoded field names: the consumer declares which field is the body, the
586
+ * (optional) title field, and which fields appear as properties. Defaults are
587
+ * data-driven (`properties: "auto"` derives from the schema).
588
+ *
589
+ * @example
590
+ * ```ts
591
+ * .form(({ v, f }) => v.collectionDocument({
592
+ * document: {
593
+ * body: f.body,
594
+ * title: f.name,
595
+ * properties: [f.scopeType, f.path, f.createdAt],
596
+ * save: "autosave",
597
+ * },
598
+ * }))
599
+ * ```
600
+ */
601
+ interface DocumentViewConfig {
602
+ document: {
603
+ /** The dominant long-form field, rendered with the rich-text editor. */
604
+ body: string;
605
+ /**
606
+ * Property fields shown as inline-editable rows.
607
+ * - `"auto"` (default): all schema fields except body/title, in schema order.
608
+ * - `string[]`: an explicit, ordered list.
609
+ */
610
+ properties?: "auto" | string[];
611
+ /**
612
+ * Save behavior.
613
+ * - `"autosave"` (default): debounced autosave with a "Saved"/"Saving" indicator.
614
+ * - `"manual"`: a Save affordance with dirty tracking.
615
+ */
616
+ save?: "autosave" | "manual";
617
+ /** Optional page-title field; falls back to the record id/path. */
618
+ title?: string;
619
+ };
620
+ }
581
621
  /**
582
622
  * Registry for custom field and widget components
583
623
  */
@@ -596,4 +636,4 @@ interface ComponentRegistry {
596
636
  custom?: Record<string, MaybeLazyComponent<any>>;
597
637
  }
598
638
  //#endregion
599
- export { ComponentRegistry, FieldComponentProps, FieldLayoutItem, FormSidebarConfig, FormViewConfig, SectionLayout, TabConfig, TabsLayout };
639
+ export { ComponentRegistry, DocumentViewConfig, FieldComponentProps, FieldLayoutItem, FormSidebarConfig, FormViewConfig, SectionLayout, TabConfig, TabsLayout };
@@ -3,9 +3,10 @@ import { MaybeLazyComponent } from "../types/common.mjs";
3
3
  //#region src/client/builder/view/view.d.ts
4
4
 
5
5
  /**
6
- * View kind discriminant — "list" for collection list pages, "form" for edit/create pages.
6
+ * View kind discriminant — "list" for collection list pages, "form" for edit/create pages,
7
+ * "document" for Notion-style document pages (dominant rich-text body + property rows).
7
8
  */
8
- type ViewKind = "list" | "form";
9
+ type ViewKind = "list" | "form" | "document";
9
10
  /**
10
11
  * View definition — a registry entry mapping a view name to its component.
11
12
  *
@@ -92,6 +92,11 @@ function FormDialogContent({ action, ctx, onClose }) {
92
92
  return message || t("toast.actionSuccess");
93
93
  },
94
94
  error: (error) => {
95
+ const validationError = error;
96
+ if (validationError.fieldErrors) for (const [field, message] of Object.entries(validationError.fieldErrors)) form.setError(field, {
97
+ type: "server",
98
+ message
99
+ });
95
100
  return error instanceof Error ? error.message : t("toast.actionFailed");
96
101
  },
97
102
  finally: () => {
@@ -1,6 +1,6 @@
1
1
  import { CollectionNames, GlobalNames } from "../builder/index.mjs";
2
2
  import * as React from "react";
3
- import * as react_jsx_runtime14 from "react/jsx-runtime";
3
+ import * as react_jsx_runtime48 from "react/jsx-runtime";
4
4
  import { QuestpieApp } from "questpie/client";
5
5
 
6
6
  //#region src/client/components/admin-link.d.ts
@@ -61,6 +61,6 @@ declare function AdminLink<TApp extends QuestpieApp>({
61
61
  children,
62
62
  onClick,
63
63
  ...rest
64
- }: AdminLinkProps<TApp>): react_jsx_runtime14.JSX.Element;
64
+ }: AdminLinkProps<TApp>): react_jsx_runtime48.JSX.Element;
65
65
  //#endregion
66
66
  export { AdminLink };
@@ -11,7 +11,7 @@ import { Controller } from "react-hook-form";
11
11
  * Unified boolean field component.
12
12
  * Renders as checkbox (default) or switch based on `displayAs` prop.
13
13
  */
14
- function BooleanField({ name, label, description, required, disabled, localized, locale, control, className, displayAs = "checkbox" }) {
14
+ function BooleanField({ name, label, description, required, disabled, localized, locale, hideLabel, control, className, displayAs = "checkbox" }) {
15
15
  return /* @__PURE__ */ jsx(Controller, {
16
16
  name,
17
17
  control: useResolvedControl(control),
@@ -23,6 +23,7 @@ function BooleanField({ name, label, description, required, disabled, localized,
23
23
  disabled,
24
24
  localized,
25
25
  locale,
26
+ hideLabel,
26
27
  error: fieldState.error?.message,
27
28
  children: displayAs === "switch" ? /* @__PURE__ */ jsx(ToggleInput, {
28
29
  id: name,
@@ -11,7 +11,7 @@ function parseDateFieldValue(value) {
11
11
  const date = value instanceof Date ? value : new Date(String(value));
12
12
  return Number.isNaN(date.getTime()) ? null : date;
13
13
  }
14
- function DateField({ name, label, description, placeholder, required, disabled, localized, locale, control, className, minDate, maxDate, format }) {
14
+ function DateField({ name, label, description, placeholder, required, disabled, localized, locale, hideLabel, control, className, minDate, maxDate, format }) {
15
15
  return /* @__PURE__ */ jsx(Controller, {
16
16
  name,
17
17
  control: useResolvedControl(control),
@@ -25,6 +25,7 @@ function DateField({ name, label, description, placeholder, required, disabled,
25
25
  disabled,
26
26
  localized,
27
27
  locale,
28
+ hideLabel,
28
29
  error: fieldState.error?.message,
29
30
  children: /* @__PURE__ */ jsx(DateInput, {
30
31
  id: name,
@@ -11,7 +11,7 @@ function parseDateTimeFieldValue(value) {
11
11
  const date = value instanceof Date ? value : new Date(String(value));
12
12
  return Number.isNaN(date.getTime()) ? null : date;
13
13
  }
14
- function DatetimeField({ name, label, description, placeholder, required, disabled, localized, locale, control, className, minDate, maxDate, format, precision }) {
14
+ function DatetimeField({ name, label, description, placeholder, required, disabled, localized, locale, hideLabel, control, className, minDate, maxDate, format, precision }) {
15
15
  return /* @__PURE__ */ jsx(Controller, {
16
16
  name,
17
17
  control: useResolvedControl(control),
@@ -25,6 +25,7 @@ function DatetimeField({ name, label, description, placeholder, required, disabl
25
25
  disabled,
26
26
  localized,
27
27
  locale,
28
+ hideLabel,
28
29
  error: fieldState.error?.message,
29
30
  children: /* @__PURE__ */ jsx(DateTimeInput, {
30
31
  id: name,
@@ -6,7 +6,7 @@ import { jsx } from "react/jsx-runtime";
6
6
  import { Controller } from "react-hook-form";
7
7
 
8
8
  //#region src/client/components/fields/email-field.tsx
9
- function EmailField({ name, label, description, placeholder, required, disabled, localized, locale, control, className }) {
9
+ function EmailField({ name, label, description, placeholder, required, disabled, localized, locale, hideLabel, control, className }) {
10
10
  return /* @__PURE__ */ jsx(Controller, {
11
11
  name,
12
12
  control: useResolvedControl(control),
@@ -18,6 +18,7 @@ function EmailField({ name, label, description, placeholder, required, disabled,
18
18
  disabled,
19
19
  localized,
20
20
  locale,
21
+ hideLabel,
21
22
  error: fieldState.error?.message,
22
23
  children: /* @__PURE__ */ jsx(TextInput, {
23
24
  id: name,
@@ -0,0 +1,11 @@
1
+ import "react-hook-form";
2
+
3
+ //#region src/client/components/fields/field-utils.d.ts
4
+
5
+ /**
6
+ * Sanitize filename for safe storage.
7
+ * Removes special characters, replaces spaces with hyphens.
8
+ */
9
+ declare function sanitizeFilename(filename: string): string;
10
+ //#endregion
11
+ export { sanitizeFilename };
@@ -12,7 +12,9 @@ function useResolvedControl(control) {
12
12
  function sanitizeFilename(filename) {
13
13
  const lastDot = filename.lastIndexOf(".");
14
14
  const ext = lastDot > 0 ? filename.slice(lastDot) : "";
15
- return ((lastDot > 0 ? filename.slice(0, lastDot) : filename).normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/\s+/g, "-").replace(/[^a-zA-Z0-9._-]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "").toLowerCase() || "file") + ext.toLowerCase();
15
+ const sanitized = (lastDot > 0 ? filename.slice(0, lastDot) : filename).normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/\s+/g, "-").replace(/[^a-zA-Z0-9._-]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "").toLowerCase();
16
+ const safeExt = ext.toLowerCase().replace(/[^a-z0-9.]/g, "");
17
+ return (sanitized || "file") + safeExt;
16
18
  }
17
19
  /**
18
20
  * Extract columns from collection list config.
@@ -36,10 +36,10 @@ function FieldLocaleIndicator({ localized, locale }) {
36
36
  showFlag: false
37
37
  });
38
38
  }
39
- function FieldWrapper({ name, label, description, labelAccessory, required, disabled, readOnly, error, localized, locale, children, fieldPath }) {
39
+ function FieldWrapper({ name, label, description, labelAccessory, required, disabled, readOnly, error, localized, locale, hideLabel, children, fieldPath }) {
40
40
  const resolveText = useResolveText();
41
- const resolvedLabel = label ? resolveText(label) : void 0;
42
- const resolvedDescription = description ? resolveText(description) : void 0;
41
+ const resolvedLabel = hideLabel || !label ? void 0 : resolveText(label);
42
+ const resolvedDescription = hideLabel || !description ? void 0 : resolveText(description);
43
43
  return /* @__PURE__ */ jsx(Field, {
44
44
  "data-disabled": disabled,
45
45
  "data-readonly": !disabled && readOnly,
@@ -6,7 +6,7 @@ import { jsx } from "react/jsx-runtime";
6
6
  import { Controller } from "react-hook-form";
7
7
 
8
8
  //#region src/client/components/fields/number-field.tsx
9
- function NumberField({ name, label, description, placeholder, required, disabled, localized, locale, control, className, min, max, step, showButtons }) {
9
+ function NumberField({ name, label, description, placeholder, required, disabled, localized, locale, hideLabel, control, className, min, max, step, showButtons }) {
10
10
  return /* @__PURE__ */ jsx(Controller, {
11
11
  name,
12
12
  control: useResolvedControl(control),
@@ -18,6 +18,7 @@ function NumberField({ name, label, description, placeholder, required, disabled
18
18
  disabled,
19
19
  localized,
20
20
  locale,
21
+ hideLabel,
21
22
  error: fieldState.error?.message,
22
23
  children: /* @__PURE__ */ jsx(NumberInput, {
23
24
  id: name,
@@ -26,7 +26,8 @@ function ObjectFieldPanel({ name, label, description, required, disabled, locali
26
26
  type: "button",
27
27
  variant: "ghost",
28
28
  onClick: onToggle,
29
- className: "h-auto min-h-10 w-full justify-between rounded-none px-3 py-2 text-left",
29
+ className: "hover:bg-surface-low aria-expanded:bg-surface-low h-auto min-h-12 w-full justify-between rounded-none px-4 py-3 text-left",
30
+ "aria-expanded": !isCollapsed,
30
31
  disabled,
31
32
  children: [/* @__PURE__ */ jsxs("span", {
32
33
  className: "flex min-w-0 items-center gap-2",
@@ -1,4 +1,5 @@
1
1
  import { formatLabel } from "../../../../lib/utils.mjs";
2
+ import { resolveAssetUrl } from "../../../../utils/asset-url.mjs";
2
3
  import { DefaultCell } from "../../../../views/collection/cells/primitive-cells.mjs";
3
4
 
4
5
  //#region src/client/components/fields/relation/displays/types.ts
@@ -31,9 +32,8 @@ function formatCellValue(value) {
31
32
  function getImageUrl(item, imageField) {
32
33
  if (!imageField) return null;
33
34
  const imageValue = item[imageField];
34
- if (typeof imageValue === "string") return imageValue;
35
- if (imageValue?.url) return imageValue.url;
36
- if (imageValue?.key) return imageValue.key;
35
+ if (typeof imageValue === "string") return resolveAssetUrl(imageValue) ?? null;
36
+ if (imageValue?.url) return resolveAssetUrl(imageValue.url) ?? null;
37
37
  return null;
38
38
  }
39
39
  /**
@@ -283,6 +283,13 @@ function RichTextBubbleMenu({ editor, features, disabled, linkOpen, onImageClick
283
283
  shortcut: "⌘U",
284
284
  onClick: () => editor.chain().focus().toggleUnderline().run()
285
285
  }),
286
+ features.strike && /* @__PURE__ */ jsx(ToolbarButton, {
287
+ icon: EDITOR_ICONS.strikethrough,
288
+ active: editor.isActive("strike"),
289
+ disabled: !isEditable,
290
+ title: t("editor.strikethrough"),
291
+ onClick: () => editor.chain().focus().toggleStrike().run()
292
+ }),
286
293
  features.code && /* @__PURE__ */ jsx(ToolbarButton, {
287
294
  icon: EDITOR_ICONS.code,
288
295
  active: editor.isActive("code"),
@@ -10,6 +10,7 @@ import TableRow from "@tiptap/extension-table-row";
10
10
  import TextAlign from "@tiptap/extension-text-align";
11
11
  import Underline from "@tiptap/extension-underline";
12
12
  import StarterKit from "@tiptap/starter-kit";
13
+ import { Markdown } from "tiptap-markdown";
13
14
 
14
15
  //#region src/client/components/fields/rich-text-editor/extensions.tsx
15
16
  let codeBlockExtension = null;
@@ -54,12 +55,13 @@ const defaultLabels = {
54
55
  * (i.e. codeBlock disabled, or lowlight already cached). Only returns a
55
56
  * `Promise` on the first build that includes codeBlock.
56
57
  */
57
- function buildExtensions({ features, labels, placeholder, maxCharacters, customExtensions }) {
58
+ function buildExtensions({ features, labels, placeholder, maxCharacters, outputMode, customExtensions }) {
58
59
  const base = buildBaseExtensions({
59
60
  features,
60
61
  labels,
61
62
  placeholder,
62
63
  maxCharacters,
64
+ outputMode,
63
65
  customExtensions
64
66
  });
65
67
  if (!features.codeBlock) return base;
@@ -67,7 +69,7 @@ function buildExtensions({ features, labels, placeholder, maxCharacters, customE
67
69
  if (!(codeBlock instanceof Promise)) return [...base, codeBlock];
68
70
  return codeBlock.then((ext) => [...base, ext]);
69
71
  }
70
- function buildBaseExtensions({ features, labels = defaultLabels, placeholder, maxCharacters, customExtensions }) {
72
+ function buildBaseExtensions({ features, labels = defaultLabels, placeholder, maxCharacters, outputMode, customExtensions }) {
71
73
  const starterKitConfig = { codeBlock: false };
72
74
  if (!features.bold) starterKitConfig.bold = false;
73
75
  if (!features.italic) starterKitConfig.italic = false;
@@ -97,18 +99,21 @@ function buildBaseExtensions({ features, labels = defaultLabels, placeholder, ma
97
99
  title: labels.heading(1),
98
100
  description: labels.heading1Description,
99
101
  icon: "ph:text-h-one",
102
+ group: "Headings",
100
103
  keywords: ["h1"],
101
104
  command: (cmdEditor) => cmdEditor.chain().focus().toggleHeading({ level: 1 }).run()
102
105
  }, {
103
106
  title: labels.heading(2),
104
107
  description: labels.heading2Description,
105
108
  icon: "ph:text-h-two",
109
+ group: "Headings",
106
110
  keywords: ["h2"],
107
111
  command: (cmdEditor) => cmdEditor.chain().focus().toggleHeading({ level: 2 }).run()
108
112
  }, {
109
113
  title: labels.heading(3),
110
114
  description: labels.heading3Description,
111
115
  icon: "ph:text-h-three",
116
+ group: "Headings",
112
117
  keywords: ["h3"],
113
118
  command: (cmdEditor) => cmdEditor.chain().focus().toggleHeading({ level: 3 }).run()
114
119
  });
@@ -116,6 +121,7 @@ function buildBaseExtensions({ features, labels = defaultLabels, placeholder, ma
116
121
  title: labels.paragraph,
117
122
  description: labels.paragraphDescription,
118
123
  icon: "ph:text-align-left",
124
+ group: "Text",
119
125
  keywords: ["text"],
120
126
  command: (cmdEditor) => cmdEditor.chain().focus().setParagraph().run()
121
127
  });
@@ -123,6 +129,7 @@ function buildBaseExtensions({ features, labels = defaultLabels, placeholder, ma
123
129
  title: labels.bulletList,
124
130
  description: labels.bulletListDescription,
125
131
  icon: "ph:list-bullets",
132
+ group: "Lists",
126
133
  keywords: ["list", "ul"],
127
134
  command: (cmdEditor) => cmdEditor.chain().focus().toggleBulletList().run()
128
135
  });
@@ -130,6 +137,7 @@ function buildBaseExtensions({ features, labels = defaultLabels, placeholder, ma
130
137
  title: labels.orderedList,
131
138
  description: labels.orderedListDescription,
132
139
  icon: "ph:list-numbers",
140
+ group: "Lists",
133
141
  keywords: ["list", "ol"],
134
142
  command: (cmdEditor) => cmdEditor.chain().focus().toggleOrderedList().run()
135
143
  });
@@ -137,6 +145,7 @@ function buildBaseExtensions({ features, labels = defaultLabels, placeholder, ma
137
145
  title: labels.quote,
138
146
  description: labels.quoteDescription,
139
147
  icon: "ph:quotes",
148
+ group: "Blocks",
140
149
  keywords: ["blockquote"],
141
150
  command: (cmdEditor) => cmdEditor.chain().focus().toggleBlockquote().run()
142
151
  });
@@ -144,6 +153,7 @@ function buildBaseExtensions({ features, labels = defaultLabels, placeholder, ma
144
153
  title: labels.codeBlock,
145
154
  description: labels.codeBlockDescription,
146
155
  icon: "ph:code-block",
156
+ group: "Blocks",
147
157
  keywords: ["code"],
148
158
  command: (cmdEditor) => cmdEditor.chain().focus().toggleCodeBlock().run()
149
159
  });
@@ -151,6 +161,7 @@ function buildBaseExtensions({ features, labels = defaultLabels, placeholder, ma
151
161
  title: labels.divider,
152
162
  description: labels.dividerDescription,
153
163
  icon: "ph:minus",
164
+ group: "Blocks",
154
165
  keywords: ["hr"],
155
166
  command: (cmdEditor) => cmdEditor.chain().focus().setHorizontalRule().run()
156
167
  });
@@ -158,6 +169,7 @@ function buildBaseExtensions({ features, labels = defaultLabels, placeholder, ma
158
169
  title: labels.table,
159
170
  description: labels.tableDescription,
160
171
  icon: "ph:table",
172
+ group: "Blocks",
161
173
  keywords: ["grid"],
162
174
  command: (cmdEditor) => cmdEditor.chain().focus().insertTable({
163
175
  rows: 3,
@@ -167,6 +179,11 @@ function buildBaseExtensions({ features, labels = defaultLabels, placeholder, ma
167
179
  });
168
180
  return commands;
169
181
  }));
182
+ if (outputMode === "markdown") extensions.push(Markdown.configure({
183
+ html: true,
184
+ transformCopiedText: true,
185
+ transformPastedText: true
186
+ }));
170
187
  if (customExtensions?.length) extensions.push(...customExtensions);
171
188
  return extensions;
172
189
  }
@@ -1,6 +1,7 @@
1
1
  import { useTranslation } from "../../../i18n/hooks.mjs";
2
2
  import { Button } from "../../ui/button.mjs";
3
3
  import { Input } from "../../ui/input.mjs";
4
+ import { resolveAssetUrl } from "../../../utils/asset-url.mjs";
4
5
  import { Popover, PopoverContent, PopoverHeader, PopoverTitle, PopoverTrigger } from "../../ui/popover.mjs";
5
6
  import { MediaPickerDialog } from "../../media/media-picker-dialog.mjs";
6
7
  import { useRichTextImageUpload } from "./image-upload.mjs";
@@ -76,7 +77,7 @@ function ImagePopover({ editor, open, onOpenChange, disabled, onImageUpload, ima
76
77
  return;
77
78
  }
78
79
  editor.chain().focus().setImage({
79
- src: asset.url,
80
+ src: resolveAssetUrl(asset.url) ?? asset.url,
80
81
  alt: imageAlt || asset.alt || void 0
81
82
  }).run();
82
83
  setImageUrl("");
@@ -95,7 +96,10 @@ function ImagePopover({ editor, open, onOpenChange, disabled, onImageUpload, ima
95
96
  return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(Popover, {
96
97
  open,
97
98
  onOpenChange,
98
- children: [/* @__PURE__ */ jsx(PopoverTrigger, { render: /* @__PURE__ */ jsx("div", { className: "sr-only" }) }), /* @__PURE__ */ jsxs(PopoverContent, {
99
+ children: [/* @__PURE__ */ jsx(PopoverTrigger, {
100
+ nativeButton: false,
101
+ render: /* @__PURE__ */ jsx("div", { className: "sr-only" })
102
+ }), /* @__PURE__ */ jsxs(PopoverContent, {
99
103
  className: "w-80",
100
104
  children: [/* @__PURE__ */ jsx(PopoverHeader, { children: /* @__PURE__ */ jsx(PopoverTitle, { children: t("editor.image") }) }), /* @__PURE__ */ jsxs("div", {
101
105
  className: "space-y-3",
@@ -1,5 +1,6 @@
1
1
  import { useTranslation } from "../../../i18n/hooks.mjs";
2
2
  import { sanitizeFilename } from "../field-utils.mjs";
3
+ import { resolveAssetUrl } from "../../../utils/asset-url.mjs";
3
4
  import { useUploadCollection } from "../../../hooks/use-upload-collection.mjs";
4
5
  import { useUpload } from "../../../hooks/use-upload.mjs";
5
6
  import * as React from "react";
@@ -35,7 +36,7 @@ function useRichTextImageUpload({ imageCollection, onImageUpload }) {
35
36
  const sanitizedName = sanitizeFilename(file.name);
36
37
  const uploadedAsset = await upload(sanitizedName === file.name ? file : new File([file], sanitizedName, { type: file.type }), { to: collection });
37
38
  if (!uploadedAsset?.url) throw new Error(t("upload.error"));
38
- return uploadedAsset.url;
39
+ return resolveAssetUrl(uploadedAsset.url) ?? uploadedAsset.url;
39
40
  }, [
40
41
  collection,
41
42
  collections,
@@ -1,5 +1,5 @@
1
1
  import { RichTextEditorProps } from "./types.mjs";
2
- import * as react_jsx_runtime0 from "react/jsx-runtime";
2
+ import * as react_jsx_runtime33 from "react/jsx-runtime";
3
3
 
4
4
  //#region src/client/components/fields/rich-text-editor/index.d.ts
5
5
 
@@ -24,6 +24,7 @@ declare function RichTextEditor({
24
24
  error,
25
25
  localized,
26
26
  locale,
27
+ hideLabel,
27
28
  extensions,
28
29
  preset,
29
30
  features,
@@ -32,7 +33,8 @@ declare function RichTextEditor({
32
33
  enableImages,
33
34
  onImageUpload,
34
35
  imageCollection,
35
- enableMediaLibrary
36
- }: RichTextEditorProps): react_jsx_runtime0.JSX.Element;
36
+ enableMediaLibrary,
37
+ outputMode
38
+ }: RichTextEditorProps): react_jsx_runtime33.JSX.Element;
37
39
  //#endregion
38
40
  export { RichTextEditor };