@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
@@ -1,5 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { field, fieldType, isNotNull, isNull, jsonb, sql } from "questpie";
3
+ import { text } from "questpie/drizzle-pg-core";
3
4
 
4
5
  //#region src/server/fields/rich-text.ts
5
6
  /**
@@ -85,6 +86,13 @@ function getRichTextOperators() {
85
86
  }
86
87
  };
87
88
  }
89
+ const markdownOperators = {
90
+ contains: (col, value) => sql`${col} ILIKE ${"%" + value + "%"}`,
91
+ isEmpty: (col) => sql`(${col} IS NULL OR length(trim(${col})) = 0)`,
92
+ isNotEmpty: (col) => sql`(${col} IS NOT NULL AND length(trim(${col})) > 0)`,
93
+ isNull: (col) => isNull(col),
94
+ isNotNull: (col) => isNotNull(col)
95
+ };
88
96
  /**
89
97
  * Create a rich text field.
90
98
  *
@@ -97,11 +105,13 @@ function getRichTextOperators() {
97
105
  * Rich text field runtime state factory.
98
106
  * Shared between the legacy `richText()` function and the new `richTextFieldType`.
99
107
  */
100
- function createRichTextState() {
108
+ function createRichTextState(options) {
109
+ const mode = options?.mode ?? "json";
110
+ const isMarkdown = mode === "markdown";
101
111
  return {
102
112
  type: "richText",
103
- columnFactory: (name) => jsonb(name),
104
- schemaFactory: () => {
113
+ columnFactory: isMarkdown ? (name) => text(name) : (name) => jsonb(name),
114
+ schemaFactory: isMarkdown ? () => z.string() : () => {
105
115
  const nodeSchema = z.lazy(() => z.object({
106
116
  type: z.string(),
107
117
  attrs: z.record(z.string(), z.any()).optional(),
@@ -117,7 +127,7 @@ function createRichTextState() {
117
127
  content: z.array(nodeSchema).optional()
118
128
  });
119
129
  },
120
- operatorSet: {
130
+ operatorSet: isMarkdown ? markdownOperators : {
121
131
  jsonbCast: null,
122
132
  column: getRichTextOperators().column
123
133
  },
@@ -129,7 +139,8 @@ function createRichTextState() {
129
139
  localized: state.localized ?? false,
130
140
  readOnly: state.input === false ? true : void 0,
131
141
  writeOnly: state.output === false ? true : void 0,
132
- meta: state.extensions?.admin
142
+ meta: state.extensions?.admin,
143
+ outputMode: mode
133
144
  }),
134
145
  notNull: false,
135
146
  hasDefault: false,
@@ -140,8 +151,8 @@ function createRichTextState() {
140
151
  isArray: false
141
152
  };
142
153
  }
143
- function richText() {
144
- return field(createRichTextState());
154
+ function richText(options) {
155
+ return field(createRichTextState(options));
145
156
  }
146
157
  /**
147
158
  * Rich text field type definition (v3 API).
@@ -719,6 +719,8 @@ var cs_default = {
719
719
  "viewOptions.groupByDescription": "Seskupit aktuální stránku podle nakonfigurovaného pole.",
720
720
  "viewOptions.noGrouping": "Bez seskupení",
721
721
  "viewOptions.sort": "Řazení",
722
+ "viewOptions.noSort": "Bez řazení",
723
+ "viewOptions.sortDescription": "Vyberte pole a směr řazení pro tento pohled.",
722
724
  "version.history": "Historie verzí",
723
725
  "version.historyDescription": "Procházejte předchozí verze a v případě potřeby některou obnovte.",
724
726
  "version.globalHistoryDescription": "Procházejte předchozí verze globálních nastavení a v případě potřeby některou obnovte.",
@@ -717,6 +717,8 @@ var de_default = {
717
717
  "viewOptions.groupByDescription": "Die aktuelle Seite nach einem konfigurierten Feld gruppieren.",
718
718
  "viewOptions.noGrouping": "Keine Gruppierung",
719
719
  "viewOptions.sort": "Sortierung",
720
+ "viewOptions.noSort": "Keine Sortierung",
721
+ "viewOptions.sortDescription": "Wählen Sie Feld und Richtung für diese Ansicht.",
720
722
  "version.history": "Versionsverlauf",
721
723
  "version.historyDescription": "Durchsuchen Sie frühere Versionen und stellen Sie bei Bedarf eine wieder her.",
722
724
  "version.globalHistoryDescription": "Durchsuchen Sie frühere globale Versionen und stellen Sie bei Bedarf eine wieder her.",
@@ -370,6 +370,8 @@ var en_default = {
370
370
  "table.noItemsInCollection": "No items found in this collection",
371
371
  "table.emptyDescription": "Records will appear here once they are created.",
372
372
  "table.pagination": "Pagination",
373
+ "table.items": "items",
374
+ "table.loadMore": "Load more",
373
375
  "table.editing": "Editing",
374
376
  "upload.dropzone": "Drop files here or click to upload",
375
377
  "upload.browse": "Browse files",
@@ -636,6 +638,8 @@ var en_default = {
636
638
  "viewOptions.groupByDescription": "Group the current page by a configured field.",
637
639
  "viewOptions.noGrouping": "No grouping",
638
640
  "viewOptions.sort": "Sort",
641
+ "viewOptions.noSort": "No sorting",
642
+ "viewOptions.sortDescription": "Choose the field and direction used by this view.",
639
643
  "version.history": "Version history",
640
644
  "version.historyDescription": "Browse previous versions and restore one if needed.",
641
645
  "version.globalHistoryDescription": "Browse previous global versions and restore one if needed.",
@@ -862,6 +862,8 @@ var es_default = {
862
862
  "viewOptions.showDeleted": "Mostrar eliminados",
863
863
  "viewOptions.showDeletedDescription": "Incluir registros eliminados de forma reversible en esta vista.",
864
864
  "viewOptions.sort": "Ordenar",
865
+ "viewOptions.noSort": "Sin ordenación",
866
+ "viewOptions.sortDescription": "Elige el campo y la dirección para esta vista.",
865
867
  "widget.chart.emptyDescription": "Configure una fuente de datos para mostrar un gráfico.",
866
868
  "widget.chart.emptyTitle": "No hay datos de gráfico",
867
869
  "widget.progress.emptyDescription": "Configure datos de progreso para mostrar este widget.",
@@ -862,6 +862,8 @@ var fr_default = {
862
862
  "viewOptions.showDeleted": "Afficher les supprimés",
863
863
  "viewOptions.showDeletedDescription": "Inclure les enregistrements supprimés de façon réversible dans cette vue.",
864
864
  "viewOptions.sort": "Trier",
865
+ "viewOptions.noSort": "Aucun tri",
866
+ "viewOptions.sortDescription": "Choisissez le champ et le sens utilisés par cette vue.",
865
867
  "widget.chart.emptyDescription": "Configurez une source de données pour afficher un graphique.",
866
868
  "widget.chart.emptyTitle": "Aucune donnée de graphique",
867
869
  "widget.progress.emptyDescription": "Configurez des données de progression pour afficher ce widget.",
@@ -866,6 +866,8 @@ var pl_default = {
866
866
  "viewOptions.showDeleted": "Pokaż usunięte",
867
867
  "viewOptions.showDeletedDescription": "Uwzględnij w tym widoku rekordy usunięte miękko.",
868
868
  "viewOptions.sort": "Sortuj",
869
+ "viewOptions.noSort": "Bez sortowania",
870
+ "viewOptions.sortDescription": "Wybierz pole i kierunek używane w tym widoku.",
869
871
  "widget.chart.emptyDescription": "Skonfiguruj źródło danych, aby wyświetlić wykres.",
870
872
  "widget.chart.emptyTitle": "Brak danych wykresu",
871
873
  "widget.progress.emptyDescription": "Skonfiguruj dane postępu, aby wyświetlić ten widget.",
@@ -862,6 +862,8 @@ var pt_default = {
862
862
  "viewOptions.showDeleted": "Mostrar excluídos",
863
863
  "viewOptions.showDeletedDescription": "Incluir registros excluídos de forma reversível nesta visualização.",
864
864
  "viewOptions.sort": "Ordenar",
865
+ "viewOptions.noSort": "Sem ordenação",
866
+ "viewOptions.sortDescription": "Escolha o campo e a direção usados nesta visualização.",
865
867
  "widget.chart.emptyDescription": "Configure uma fonte de dados para exibir um gráfico.",
866
868
  "widget.chart.emptyTitle": "Nenhum dado de gráfico",
867
869
  "widget.progress.emptyDescription": "Configure dados de progresso para exibir este widget.",
@@ -602,6 +602,8 @@ var sk_default = {
602
602
  "viewOptions.groupByDescription": "Zoskupí aktuálnu stránku podľa nakonfigurovaného poľa.",
603
603
  "viewOptions.noGrouping": "Bez zoskupenia",
604
604
  "viewOptions.sort": "Zoradenie",
605
+ "viewOptions.noSort": "Bez zoradenia",
606
+ "viewOptions.sortDescription": "Vyberte pole a smer zoradenia pre tento pohľad.",
605
607
  "viewOptions.saveCurrentConfig": "Uložiť aktuálnu konfiguráciu",
606
608
  "viewOptions.viewNamePlaceholder": "Názov zobrazenia...",
607
609
  "viewOptions.saveDescription": "Uloží aktuálne stĺpce, filtre, zoskupenie a zoradenie.",
@@ -14,8 +14,8 @@ import { adminAssetsCollection } from "../collections/assets.mjs";
14
14
  import { _default as _default$11 } from "../collections/session.mjs";
15
15
  import { adminUserCollection } from "../collections/user.mjs";
16
16
  import { _default as _default$12 } from "../collections/verification.mjs";
17
- import { adminConfigFunctions } from "../routes/admin-config.mjs";
18
17
  import { getApp } from "../routes/route-helpers.mjs";
18
+ import { adminConfigFunctions } from "../routes/admin-config.mjs";
19
19
  import { actionFunctions } from "../routes/execute-action.mjs";
20
20
  import { translateAdminMessage } from "../routes/i18n-helpers.mjs";
21
21
  import { localeFunctions } from "../routes/locales.mjs";
@@ -1,5 +1,11 @@
1
1
  //#region src/server/modules/admin/auth-helpers.ts
2
2
  /**
3
+ * Check whether a route context belongs to an admin user.
4
+ */
5
+ function hasAdminRole(ctx) {
6
+ return ctx.session?.user?.role === "admin";
7
+ }
8
+ /**
3
9
  * Check if user is authenticated with required role on the server.
4
10
  * Returns a redirect Response if not authenticated, null if authenticated.
5
11
  *
@@ -104,4 +110,4 @@ async function isAdminUser({ request, app, requiredRole = "admin" }) {
104
110
  }
105
111
 
106
112
  //#endregion
107
- export { getAdminSession, isAdminUser, requireAdminAuth };
113
+ export { getAdminSession, hasAdminRole, isAdminUser, requireAdminAuth };
@@ -27,14 +27,6 @@ type AdminBlockFields = BuiltinFields & typeof adminFields;
27
27
  * ```
28
28
  */
29
29
  type BlockPrefetchContext = AppContext & {
30
- /** App instance — populated at runtime by extractAppServices */
31
- app: unknown;
32
- /** Database handle — populated at runtime by extractAppServices */
33
- db: unknown;
34
- /** Collection APIs — populated at runtime by extractAppServices */
35
- collections: Record<string, any>;
36
- /** Global APIs — populated at runtime by extractAppServices */
37
- globals: Record<string, any>;
38
30
  /** Block instance ID */
39
31
  blockId: string;
40
32
  /** Block type name */
@@ -1,4 +1,4 @@
1
- import { ComponentReference } from "../../../augmentation/common.mjs";
1
+ import { ComponentReference as ComponentReference$1 } from "../../../augmentation/common.mjs";
2
2
  import { BlockCategoryConfig, FieldLayoutItem } from "../../../augmentation/form-layout.mjs";
3
3
  import "../../../augmentation.mjs";
4
4
  import { AnyBlockDefinition } from "./block-builder.mjs";
@@ -21,7 +21,7 @@ interface BlockSchema {
21
21
  /** Description */
22
22
  description?: I18nText;
23
23
  /** Icon reference */
24
- icon?: ComponentReference;
24
+ icon?: ComponentReference$1;
25
25
  /** Category for grouping in block picker */
26
26
  category?: BlockCategoryConfig;
27
27
  /** Order within category in block picker */
@@ -1,3 +1,4 @@
1
+ import { serializeFormLayoutProps, serializeReactivePropsRecord } from "../../../fields/reactive-runtime.mjs";
1
2
  import { isI18nLocaleMap } from "questpie/shared";
2
3
 
3
4
  //#region src/server/modules/admin/block/introspection.ts
@@ -11,6 +12,29 @@ function normalizeBlockDef(blockDef) {
11
12
  if (typeof blockDef?.build === "function" && !blockDef.getFieldMetadata) return blockDef.build();
12
13
  return blockDef;
13
14
  }
15
+ function serializeBlockAdmin(admin) {
16
+ if (!admin) return void 0;
17
+ return {
18
+ label: admin.label,
19
+ description: admin.description,
20
+ icon: admin.icon,
21
+ category: admin.category,
22
+ order: admin.order,
23
+ hidden: admin.hidden
24
+ };
25
+ }
26
+ function serializeBlockFields(fields) {
27
+ const serialized = {};
28
+ for (const [name, field] of Object.entries(fields)) {
29
+ const metadata = { ...field.metadata };
30
+ if (metadata.meta && typeof metadata.meta === "object") metadata.meta = serializeReactivePropsRecord(metadata.meta);
31
+ serialized[name] = {
32
+ ...field,
33
+ metadata
34
+ };
35
+ }
36
+ return serialized;
37
+ }
14
38
  /**
15
39
  * Introspect a single block definition.
16
40
  * Handles both server BlockBuilder definitions and client BlockDefinition objects
@@ -21,12 +45,12 @@ function introspectBlock(blockDef) {
21
45
  const state = resolved.state;
22
46
  return {
23
47
  name: state.name ?? resolved.name,
24
- admin: state.admin,
48
+ admin: serializeBlockAdmin(state.admin),
25
49
  allowChildren: state.allowChildren,
26
50
  maxChildren: state.maxChildren,
27
- hasPrefetch: typeof state.prefetch === "function",
28
- fields: resolved.getFieldMetadata(),
29
- form: state.form
51
+ hasPrefetch: typeof state.prefetch === "function" || Boolean(state.prefetchWith) || typeof state._prefetchLoader === "function",
52
+ fields: serializeBlockFields(resolved.getFieldMetadata()),
53
+ form: state.form ? { fields: serializeFormLayoutProps(state.form.fields) } : void 0
30
54
  };
31
55
  }
32
56
  /**
@@ -12,8 +12,14 @@ interface BlocksPrefetchContext {
12
12
  app: any;
13
13
  /** Database client */
14
14
  db: unknown;
15
+ /** Current session */
16
+ session?: unknown | null;
15
17
  /** Current locale */
16
18
  locale?: string;
19
+ /** Current CRUD access mode */
20
+ accessMode?: string;
21
+ /** Current workflow stage */
22
+ stage?: string;
17
23
  /** Collections accessor */
18
24
  collections?: unknown;
19
25
  /** Globals accessor */
@@ -76,7 +82,12 @@ declare function createBlocksPrefetchHook(): (ctx: {
76
82
  data: Record<string, unknown>;
77
83
  app: any;
78
84
  db: unknown;
85
+ session?: unknown | null;
79
86
  locale?: string;
87
+ accessMode?: string;
88
+ stage?: string;
89
+ collections?: unknown;
90
+ globals?: unknown;
80
91
  }) => Promise<void>;
81
92
  //#endregion
82
93
  export { BlocksPrefetchContext, createBlocksPrefetchHook, processBlocksDocument, processDocumentBlocksPrefetch };
@@ -1,4 +1,77 @@
1
+ import { runWithContext, tryGetContext } from "questpie";
2
+
1
3
  //#region src/server/modules/admin/block/prefetch.ts
4
+ /**
5
+ * Server CRUD Block Prefetch Utility
6
+ *
7
+ * Handles data fetching for blocks during the afterRead hook.
8
+ * The fetched data is attached to `_data[blockId]` in the response.
9
+ *
10
+ * Two mechanisms populate `_data`:
11
+ *
12
+ * 1. **Declared field expansion** (`.prefetch({ with: [...] })`): Explicitly
13
+ * declared relation/upload fields are batch-fetched and expanded to full records.
14
+ * Only the fields listed in `with` are expanded — nothing is implicit.
15
+ *
16
+ * 2. **Custom prefetch function** (`.prefetch(fn)`): For computed data that can't
17
+ * be auto-expanded, define a prefetch function that returns arbitrary data.
18
+ *
19
+ * 3. **Expand + loader** (`.prefetch({ with: [...], loader })`): Expand fields
20
+ * first, then pass the expanded data to a loader for additional processing.
21
+ *
22
+ * @example
23
+ * ```ts
24
+ * // Shape 1: Pure function
25
+ * block("featuredPosts").prefetch(async ({ values, ctx }) => {
26
+ * return { posts: await fetchPosts(values.count) };
27
+ * })
28
+ *
29
+ * // Shape 2: Expand specific fields
30
+ * block("hero").prefetch({ with: ['backgroundImage'] })
31
+ *
32
+ * // Shape 3: Expand + custom loader
33
+ * block("hero").prefetch({
34
+ * with: ['backgroundImage'],
35
+ * loader: async ({ expanded, ctx }) => ({
36
+ * analytics: await getStats(),
37
+ * }),
38
+ * })
39
+ * ```
40
+ */
41
+ function resolvePrefetchContext(ctx) {
42
+ const stored = tryGetContext();
43
+ const app = ctx.app ?? stored?.app;
44
+ return {
45
+ ...ctx,
46
+ app,
47
+ db: ctx.db ?? stored?.db,
48
+ session: ctx.session === null ? null : ctx.session ?? stored?.session,
49
+ locale: ctx.locale ?? stored?.locale,
50
+ accessMode: ctx.accessMode ?? stored?.accessMode,
51
+ stage: ctx.stage ?? stored?.stage,
52
+ collections: ctx.collections ?? app?.collections,
53
+ globals: ctx.globals ?? app?.globals
54
+ };
55
+ }
56
+ function toRuntimeContext(ctx) {
57
+ return {
58
+ app: ctx.app,
59
+ db: ctx.db,
60
+ session: ctx.session,
61
+ locale: ctx.locale,
62
+ accessMode: ctx.accessMode,
63
+ stage: ctx.stage
64
+ };
65
+ }
66
+ function toCrudContext(ctx) {
67
+ return {
68
+ db: ctx.db,
69
+ session: ctx.session,
70
+ locale: ctx.locale,
71
+ accessMode: ctx.accessMode,
72
+ stage: ctx.stage
73
+ };
74
+ }
2
75
  function normalizeBlockDefinitions(blockDefinitions) {
3
76
  const normalized = {};
4
77
  for (const [key, blockDef] of Object.entries(blockDefinitions)) {
@@ -62,23 +135,23 @@ async function expandDeclaredFields(allNodes, values, blockDefinitions, ctx) {
62
135
  if (expansionsByCollection.size === 0) return {};
63
136
  const fetchedByGroup = /* @__PURE__ */ new Map();
64
137
  const fetchPromises = [...expansionsByCollection.entries()].map(async ([groupKey, { ids, nestedWith }]) => {
65
- const collection = groupKey.includes(":") ? groupKey.slice(0, groupKey.indexOf(":")) : groupKey;
138
+ const collection$1 = groupKey.includes(":") ? groupKey.slice(0, groupKey.indexOf(":")) : groupKey;
66
139
  try {
67
- const collectionApi = ctx.app?.collections?.[collection];
140
+ const collectionApi = ctx.collections?.[collection$1] ?? ctx.app?.collections?.[collection$1];
68
141
  if (!collectionApi?.find) {
69
- console.warn(`[prefetch] Collection "${collection}" not found on app.collections, skipping`);
142
+ console.warn(`[prefetch] Collection "${collection$1}" not found on app.collections, skipping`);
70
143
  return;
71
144
  }
72
145
  const result = await collectionApi.find({
73
146
  where: { id: { in: [...ids] } },
74
147
  limit: ids.size,
75
148
  ...nestedWith ? { with: nestedWith } : {}
76
- });
149
+ }, toCrudContext(ctx));
77
150
  const recordMap = /* @__PURE__ */ new Map();
78
151
  for (const doc of result?.docs || []) if (doc && typeof doc === "object" && "id" in doc) recordMap.set(doc.id, doc);
79
152
  fetchedByGroup.set(groupKey, recordMap);
80
153
  } catch (error) {
81
- console.error(`[prefetch] Failed to fetch from "${collection}":`, error);
154
+ console.error(`[prefetch] Failed to fetch from "${collection$1}":`, error);
82
155
  }
83
156
  });
84
157
  await Promise.all(fetchPromises);
@@ -115,27 +188,30 @@ async function expandDeclaredFields(allNodes, values, blockDefinitions, ctx) {
115
188
  */
116
189
  async function processBlocksDocument(blocks, blockDefinitions, ctx) {
117
190
  if (!blocks || !blocks._tree || !blocks._values) return blocks;
118
- const allNodes = [];
119
- const collectNodes = (nodes) => {
120
- for (const node of nodes) {
121
- allNodes.push(node);
122
- if (node.children.length > 0) collectNodes(node.children);
123
- }
124
- };
125
- collectNodes(blocks._tree);
126
- const normalizedBlockDefinitions = normalizeBlockDefinitions(blockDefinitions);
127
- const expandedData = await expandDeclaredFields(allNodes, blocks._values, normalizedBlockDefinitions, ctx);
128
- const prefetchedData = await executePrefetchFunctions(allNodes, blocks._values, normalizedBlockDefinitions, ctx, expandedData);
129
- const mergedData = {};
130
- const allBlockIds = new Set([...Object.keys(expandedData), ...Object.keys(prefetchedData)]);
131
- for (const blockId of allBlockIds) mergedData[blockId] = {
132
- ...expandedData[blockId] || {},
133
- ...prefetchedData[blockId] || {}
134
- };
135
- return {
136
- ...blocks,
137
- _data: mergedData
138
- };
191
+ const resolvedCtx = resolvePrefetchContext(ctx);
192
+ return runWithContext(toRuntimeContext(resolvedCtx), async () => {
193
+ const allNodes = [];
194
+ const collectNodes = (nodes) => {
195
+ for (const node of nodes) {
196
+ allNodes.push(node);
197
+ if (node.children.length > 0) collectNodes(node.children);
198
+ }
199
+ };
200
+ collectNodes(blocks._tree);
201
+ const normalizedBlockDefinitions = normalizeBlockDefinitions(blockDefinitions);
202
+ const expandedData = await expandDeclaredFields(allNodes, blocks._values, normalizedBlockDefinitions, resolvedCtx);
203
+ const prefetchedData = await executePrefetchFunctions(allNodes, blocks._values, normalizedBlockDefinitions, resolvedCtx, expandedData);
204
+ const mergedData = {};
205
+ const allBlockIds = new Set([...Object.keys(expandedData), ...Object.keys(prefetchedData)]);
206
+ for (const blockId of allBlockIds) mergedData[blockId] = {
207
+ ...expandedData[blockId] || {},
208
+ ...prefetchedData[blockId] || {}
209
+ };
210
+ return {
211
+ ...blocks,
212
+ _data: mergedData
213
+ };
214
+ });
139
215
  }
140
216
  /**
141
217
  * Execute prefetch functions and loaders for blocks.
@@ -234,7 +310,12 @@ function createBlocksPrefetchHook() {
234
310
  for (const [key, value] of Object.entries(ctx.data)) if (isBlocksDocument(value)) ctx.data[key] = await processBlocksDocument(value, blocks, {
235
311
  app: ctx.app,
236
312
  db: ctx.db,
237
- locale: ctx.locale
313
+ session: ctx.session,
314
+ locale: ctx.locale,
315
+ accessMode: ctx.accessMode,
316
+ stage: ctx.stage,
317
+ collections: ctx.collections ?? ctx.app?.collections,
318
+ globals: ctx.globals ?? ctx.app?.globals
238
319
  });
239
320
  };
240
321
  }
@@ -1,84 +1,85 @@
1
- import { _default } from "../views/collection-form.mjs";
2
- import { _default as _default$1 } from "../views/collection-table.mjs";
3
- import { _default as _default$2 } from "../views/global-form.mjs";
4
- import { _default as _default$3 } from "../views/list-view.mjs";
1
+ import { _default } from "../views/collection-document.mjs";
2
+ import { _default as _default$1 } from "../views/collection-form.mjs";
3
+ import { _default as _default$2 } from "../views/collection-table.mjs";
4
+ import { _default as _default$3 } from "../views/global-form.mjs";
5
+ import { _default as _default$4 } from "../views/list-view.mjs";
5
6
  import { Badge, IconifyIcon } from "../../../../../client/components/component-renderer.mjs";
6
7
  import "../components/badge.mjs";
7
8
  import "../components/icon.mjs";
8
- import { _default as _default$4 } from "../fields/array.mjs";
9
- import { _default as _default$5 } from "../fields/assetPreview.mjs";
10
- import { _default as _default$6 } from "../fields/blocks.mjs";
11
- import { _default as _default$7 } from "../fields/boolean.mjs";
12
- import { _default as _default$8 } from "../fields/date.mjs";
13
- import { _default as _default$9 } from "../fields/datetime.mjs";
14
- import { _default as _default$10 } from "../fields/email.mjs";
15
- import { _default as _default$11 } from "../fields/json.mjs";
16
- import { _default as _default$12 } from "../fields/number.mjs";
17
- import { _default as _default$13 } from "../fields/object.mjs";
18
- import { _default as _default$14 } from "../fields/relation.mjs";
19
- import { _default as _default$15 } from "../fields/richText.mjs";
20
- import { _default as _default$16 } from "../fields/select.mjs";
21
- import { _default as _default$17 } from "../fields/text.mjs";
22
- import { _default as _default$18 } from "../fields/textarea.mjs";
23
- import { _default as _default$19 } from "../fields/time.mjs";
24
- import { _default as _default$20 } from "../fields/upload.mjs";
25
- import { _default as _default$21 } from "../fields/url.mjs";
26
- import { _default as _default$22 } from "../pages/dashboard.mjs";
27
- import { _default as _default$23 } from "../pages/forgotPassword.mjs";
28
- import { _default as _default$24 } from "../pages/login.mjs";
29
- import { _default as _default$25 } from "../pages/resetPassword.mjs";
30
- import { _default as _default$26 } from "../pages/setup.mjs";
31
- import { _default as _default$27 } from "../widgets/chart.mjs";
32
- import { _default as _default$28 } from "../widgets/progress.mjs";
33
- import { _default as _default$29 } from "../widgets/quickActions.mjs";
34
- import { _default as _default$30 } from "../widgets/recentItems.mjs";
35
- import { _default as _default$31 } from "../widgets/stats.mjs";
36
- import { _default as _default$32 } from "../widgets/table.mjs";
37
- import { _default as _default$33 } from "../widgets/timeline.mjs";
38
- import { _default as _default$34 } from "../widgets/value.mjs";
9
+ import { _default as _default$5 } from "../fields/array.mjs";
10
+ import { _default as _default$6 } from "../fields/assetPreview.mjs";
11
+ import { _default as _default$7 } from "../fields/blocks.mjs";
12
+ import { _default as _default$8 } from "../fields/boolean.mjs";
13
+ import { _default as _default$9 } from "../fields/date.mjs";
14
+ import { _default as _default$10 } from "../fields/datetime.mjs";
15
+ import { _default as _default$11 } from "../fields/email.mjs";
16
+ import { _default as _default$12 } from "../fields/json.mjs";
17
+ import { _default as _default$13 } from "../fields/number.mjs";
18
+ import { _default as _default$14 } from "../fields/object.mjs";
19
+ import { _default as _default$15 } from "../fields/relation.mjs";
20
+ import { _default as _default$16 } from "../fields/richText.mjs";
21
+ import { _default as _default$17 } from "../fields/select.mjs";
22
+ import { _default as _default$18 } from "../fields/text.mjs";
23
+ import { _default as _default$19 } from "../fields/textarea.mjs";
24
+ import { _default as _default$20 } from "../fields/time.mjs";
25
+ import { _default as _default$21 } from "../fields/upload.mjs";
26
+ import { _default as _default$22 } from "../fields/url.mjs";
27
+ import { _default as _default$23 } from "../pages/dashboard.mjs";
28
+ import { _default as _default$24 } from "../pages/forgotPassword.mjs";
29
+ import { _default as _default$25 } from "../pages/login.mjs";
30
+ import { _default as _default$26 } from "../pages/resetPassword.mjs";
31
+ import { _default as _default$27 } from "../pages/setup.mjs";
32
+ import { _default as _default$28 } from "../widgets/chart.mjs";
33
+ import { _default as _default$29 } from "../widgets/progress.mjs";
34
+ import { _default as _default$30 } from "../widgets/quickActions.mjs";
35
+ import { _default as _default$31 } from "../widgets/recentItems.mjs";
36
+ import { _default as _default$32 } from "../widgets/stats.mjs";
37
+ import { _default as _default$33 } from "../widgets/table.mjs";
38
+ import { _default as _default$34 } from "../widgets/timeline.mjs";
39
+ import { _default as _default$35 } from "../widgets/value.mjs";
39
40
 
40
41
  //#region src/server/modules/admin/client/.generated/module.d.ts
41
- type AdminViews = { [K in typeof _default.name]: typeof _default } & { [K in typeof _default$1.name]: typeof _default$1 } & { [K in typeof _default$2.name]: typeof _default$2 } & { [K in typeof _default$3.name]: typeof _default$3 };
42
+ type AdminViews = { [K in typeof _default.name]: typeof _default } & { [K in typeof _default$1.name]: typeof _default$1 } & { [K in typeof _default$2.name]: typeof _default$2 } & { [K in typeof _default$3.name]: typeof _default$3 } & { [K in typeof _default$4.name]: typeof _default$4 };
42
43
  interface AdminComponents {
43
44
  badge: typeof Badge;
44
45
  icon: typeof IconifyIcon;
45
46
  }
46
47
  interface AdminFields {
47
- array: typeof _default$4;
48
- assetPreview: typeof _default$5;
49
- blocks: typeof _default$6;
50
- boolean: typeof _default$7;
51
- date: typeof _default$8;
52
- datetime: typeof _default$9;
53
- email: typeof _default$10;
54
- json: typeof _default$11;
55
- number: typeof _default$12;
56
- object: typeof _default$13;
57
- relation: typeof _default$14;
58
- richText: typeof _default$15;
59
- select: typeof _default$16;
60
- text: typeof _default$17;
61
- textarea: typeof _default$18;
62
- time: typeof _default$19;
63
- upload: typeof _default$20;
64
- url: typeof _default$21;
48
+ array: typeof _default$5;
49
+ assetPreview: typeof _default$6;
50
+ blocks: typeof _default$7;
51
+ boolean: typeof _default$8;
52
+ date: typeof _default$9;
53
+ datetime: typeof _default$10;
54
+ email: typeof _default$11;
55
+ json: typeof _default$12;
56
+ number: typeof _default$13;
57
+ object: typeof _default$14;
58
+ relation: typeof _default$15;
59
+ richText: typeof _default$16;
60
+ select: typeof _default$17;
61
+ text: typeof _default$18;
62
+ textarea: typeof _default$19;
63
+ time: typeof _default$20;
64
+ upload: typeof _default$21;
65
+ url: typeof _default$22;
65
66
  }
66
67
  interface AdminPages {
67
- dashboard: typeof _default$22;
68
- forgotPassword: typeof _default$23;
69
- login: typeof _default$24;
70
- resetPassword: typeof _default$25;
71
- setup: typeof _default$26;
68
+ dashboard: typeof _default$23;
69
+ forgotPassword: typeof _default$24;
70
+ login: typeof _default$25;
71
+ resetPassword: typeof _default$26;
72
+ setup: typeof _default$27;
72
73
  }
73
74
  interface AdminWidgets {
74
- chart: typeof _default$27;
75
- progress: typeof _default$28;
76
- quickActions: typeof _default$29;
77
- recentItems: typeof _default$30;
78
- stats: typeof _default$31;
79
- table: typeof _default$32;
80
- timeline: typeof _default$33;
81
- value: typeof _default$34;
75
+ chart: typeof _default$28;
76
+ progress: typeof _default$29;
77
+ quickActions: typeof _default$30;
78
+ recentItems: typeof _default$31;
79
+ stats: typeof _default$32;
80
+ table: typeof _default$33;
81
+ timeline: typeof _default$34;
82
+ value: typeof _default$35;
82
83
  }
83
84
  declare const _module: {
84
85
  name: "questpie-admin";
@@ -1,3 +1,4 @@
1
+ import collection_document_default from "../views/collection-document.mjs";
1
2
  import collection_form_default from "../views/collection-form.mjs";
2
3
  import collection_table_default from "../views/collection-table.mjs";
3
4
  import global_form_default from "../views/global-form.mjs";
@@ -39,6 +40,7 @@ import value_default from "../widgets/value.mjs";
39
40
  const _module = {
40
41
  name: "questpie-admin",
41
42
  views: {
43
+ [collection_document_default.name]: collection_document_default,
42
44
  [collection_form_default.name]: collection_form_default,
43
45
  [collection_table_default.name]: collection_table_default,
44
46
  [global_form_default.name]: global_form_default,
@@ -0,0 +1,6 @@
1
+ import { ViewDefinition } from "../../../../../client/builder/view/view.mjs";
2
+
3
+ //#region src/server/modules/admin/client/views/collection-document.d.ts
4
+ declare const _default: ViewDefinition<"collection-document", "document">;
5
+ //#endregion
6
+ export { _default };