adminforth 2.17.0-next.3 → 2.17.0-next.31

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 (87) hide show
  1. package/commands/createCustomComponent/main.js +0 -3
  2. package/commands/createCustomComponent/templates/customCrud/afterBreadcrumbs.vue.hbs +4 -2
  3. package/commands/createCustomComponent/templates/customCrud/beforeActionButtons.vue.hbs +3 -2
  4. package/commands/createCustomComponent/templates/customCrud/beforeBreadcrumbs.vue.hbs +4 -2
  5. package/commands/createCustomComponent/templates/customCrud/bottom.vue.hbs +4 -2
  6. package/commands/createCustomComponent/templates/customCrud/threeDotsDropdownItems.vue.hbs +4 -2
  7. package/commands/createPlugin/templates/index.ts.hbs +4 -0
  8. package/dist/auth.d.ts +1 -1
  9. package/dist/auth.d.ts.map +1 -1
  10. package/dist/auth.js +1 -2
  11. package/dist/auth.js.map +1 -1
  12. package/dist/basePlugin.d.ts +1 -0
  13. package/dist/basePlugin.d.ts.map +1 -1
  14. package/dist/basePlugin.js +3 -0
  15. package/dist/basePlugin.js.map +1 -1
  16. package/dist/dataConnectors/baseConnector.d.ts +1 -0
  17. package/dist/dataConnectors/baseConnector.d.ts.map +1 -1
  18. package/dist/dataConnectors/baseConnector.js +92 -6
  19. package/dist/dataConnectors/baseConnector.js.map +1 -1
  20. package/dist/dataConnectors/clickhouse.d.ts +2 -0
  21. package/dist/dataConnectors/clickhouse.d.ts.map +1 -1
  22. package/dist/dataConnectors/clickhouse.js +11 -1
  23. package/dist/dataConnectors/clickhouse.js.map +1 -1
  24. package/dist/dataConnectors/mongo.d.ts +8 -1
  25. package/dist/dataConnectors/mongo.d.ts.map +1 -1
  26. package/dist/dataConnectors/mongo.js +67 -24
  27. package/dist/dataConnectors/mongo.js.map +1 -1
  28. package/dist/dataConnectors/mysql.d.ts +2 -0
  29. package/dist/dataConnectors/mysql.d.ts.map +1 -1
  30. package/dist/dataConnectors/mysql.js +12 -2
  31. package/dist/dataConnectors/mysql.js.map +1 -1
  32. package/dist/dataConnectors/postgres.d.ts +2 -0
  33. package/dist/dataConnectors/postgres.d.ts.map +1 -1
  34. package/dist/dataConnectors/postgres.js +12 -2
  35. package/dist/dataConnectors/postgres.js.map +1 -1
  36. package/dist/dataConnectors/sqlite.d.ts +2 -0
  37. package/dist/dataConnectors/sqlite.d.ts.map +1 -1
  38. package/dist/dataConnectors/sqlite.js +12 -2
  39. package/dist/dataConnectors/sqlite.js.map +1 -1
  40. package/dist/modules/configValidator.d.ts.map +1 -1
  41. package/dist/modules/configValidator.js +9 -7
  42. package/dist/modules/configValidator.js.map +1 -1
  43. package/dist/modules/restApi.d.ts.map +1 -1
  44. package/dist/modules/restApi.js +3 -3
  45. package/dist/modules/restApi.js.map +1 -1
  46. package/dist/spa/src/App.vue +6 -3
  47. package/dist/spa/src/adminforth.ts +60 -1
  48. package/dist/spa/src/afcl/Dropzone.vue +6 -4
  49. package/dist/spa/src/afcl/Tooltip.vue +38 -4
  50. package/dist/spa/src/components/ColumnValueInput.vue +14 -1
  51. package/dist/spa/src/components/CustomRangePicker.vue +9 -4
  52. package/dist/spa/src/components/Filters.vue +4 -4
  53. package/dist/spa/src/components/ListActionsThreeDots.vue +235 -0
  54. package/dist/spa/src/components/ResourceForm.vue +5 -5
  55. package/dist/spa/src/components/ResourceListTable.vue +30 -16
  56. package/dist/spa/src/components/ResourceListTableVirtual.vue +34 -18
  57. package/dist/spa/src/components/ShowTable.vue +2 -2
  58. package/dist/spa/src/components/Sidebar.vue +4 -2
  59. package/dist/spa/src/components/ThreeDotsMenu.vue +6 -6
  60. package/dist/spa/src/composables/useFrontendApi.ts +8 -4
  61. package/dist/spa/src/renderers/CompactField.vue +3 -2
  62. package/dist/spa/src/renderers/CompactUUID.vue +3 -2
  63. package/dist/spa/src/renderers/RichText.vue +15 -0
  64. package/dist/spa/src/stores/core.ts +3 -2
  65. package/dist/spa/src/stores/filters.ts +1 -1
  66. package/dist/spa/src/types/Back.ts +22 -5
  67. package/dist/spa/src/types/Common.ts +3 -10
  68. package/dist/spa/src/types/FrontendAPI.ts +25 -10
  69. package/dist/spa/src/utils.ts +8 -3
  70. package/dist/spa/src/views/CreateView.vue +24 -32
  71. package/dist/spa/src/views/EditView.vue +27 -31
  72. package/dist/spa/src/views/ListView.vue +20 -10
  73. package/dist/spa/src/views/SettingsView.vue +3 -2
  74. package/dist/spa/src/views/ShowView.vue +7 -6
  75. package/dist/types/Back.d.ts +15 -4
  76. package/dist/types/Back.d.ts.map +1 -1
  77. package/dist/types/Back.js +6 -0
  78. package/dist/types/Back.js.map +1 -1
  79. package/dist/types/Common.d.ts +4 -11
  80. package/dist/types/Common.d.ts.map +1 -1
  81. package/dist/types/Common.js +2 -0
  82. package/dist/types/Common.js.map +1 -1
  83. package/dist/types/FrontendAPI.d.ts +32 -10
  84. package/dist/types/FrontendAPI.d.ts.map +1 -1
  85. package/dist/types/FrontendAPI.js.map +1 -1
  86. package/package.json +1 -1
  87. package/commands/createCustomComponent/templates/customCrud/saveButton.vue.hbs +0 -28
@@ -1,17 +1,21 @@
1
- import adminforth from '@/adminforth';
1
+ import { useAdminforth } from '@/adminforth';
2
+
2
3
 
3
4
  export function showSuccesTost(message: string) {
4
- adminforth.alert({ message, variant: 'success' });
5
+ const { alert } = useAdminforth();
6
+ alert({ message, variant: 'success' });
5
7
  return message;
6
8
  }
7
9
 
8
10
  export function showWarningTost(message: string) {
9
- adminforth.alert({ message, variant: 'warning' });
11
+ const { alert } = useAdminforth();
12
+ alert({ message, variant: 'warning' });
10
13
  return message;
11
14
  }
12
15
 
13
16
  export function showErrorTost(message: string, timeout?: number) {
14
- adminforth.alert({ message, variant: 'danger', timeout: timeout || 30});
17
+ const { alert } = useAdminforth();
18
+ alert({ message, variant: 'danger', timeout: timeout || 30});
15
19
  return message;
16
20
  }
17
21
 
@@ -15,10 +15,11 @@
15
15
  import { computed, ref, onMounted, nextTick } from 'vue';
16
16
  import { IconFileCopyAltSolid } from '@iconify-prerendered/vue-flowbite';
17
17
  import Tooltip from '@/afcl/Tooltip.vue';
18
- import adminforth from '@/adminforth';
18
+ import { useAdminforth } from '@/adminforth';
19
19
  import { useI18n } from 'vue-i18n';
20
20
 
21
21
  const { t } = useI18n();
22
+ const { alert } = useAdminforth();
22
23
  const visualValue = computed(() => {
23
24
  // if lenght is more then 8, show only first 4 and last 4 characters, ... in the middle
24
25
  const val = props.record[props.column.name];
@@ -34,7 +35,7 @@ const id = ref();
34
35
 
35
36
  function copyToCB() {
36
37
  navigator.clipboard.writeText(props.record[props.column.name]);
37
- adminforth.alert({
38
+ alert({
38
39
  message: t('ID copied to clipboard'),
39
40
  variant: 'success',
40
41
  })
@@ -15,10 +15,11 @@
15
15
  import { computed, ref, onMounted, nextTick } from 'vue';
16
16
  import { IconFileCopyAltSolid } from '@iconify-prerendered/vue-flowbite';
17
17
  import Tooltip from '@/afcl/Tooltip.vue';
18
- import adminforth from '@/adminforth';
18
+ import { useAdminforth } from '@/adminforth';
19
19
  import { useI18n } from 'vue-i18n';
20
20
 
21
21
  const { t } = useI18n();
22
+ const { alert } = useAdminforth();
22
23
  const visualValue = computed(() => {
23
24
  // if lenght is more then 8, show only first 4 and last 4 characters, ... in the middle
24
25
  const val = props.record[props.column.name];
@@ -34,7 +35,7 @@ const id = ref();
34
35
 
35
36
  function copyToCB() {
36
37
  navigator.clipboard.writeText(props.record[props.column.name]);
37
- adminforth.alert({
38
+ alert({
38
39
  message: t('ID copied to clipboard'),
39
40
  variant: 'success',
40
41
  })
@@ -22,4 +22,19 @@ const htmlContent = protectAgainstXSS(props.record[props.column.name])
22
22
  /* You can add default styles here if needed */
23
23
  word-break: break-word;
24
24
  }
25
+ .rich-text :deep(table) {
26
+ border-collapse: collapse;
27
+ border: 1px solid #ddd;
28
+ }
29
+
30
+ .rich-text :deep(table th),
31
+ .rich-text :deep(table td) {
32
+ border: 1px solid #ddd;
33
+ padding: 8px;
34
+ }
35
+
36
+ .rich-text :deep(table th) {
37
+ background-color: #f5f5f5;
38
+ font-weight: 600;
39
+ }
25
40
  </style>
@@ -2,12 +2,13 @@ import { ref, computed } from 'vue'
2
2
  import { defineStore } from 'pinia'
3
3
  import { callAdminForthApi } from '@/utils';
4
4
  import websocket from '@/websocket';
5
- import adminforth from '@/adminforth';
5
+ import { useAdminforth } from '@/adminforth';
6
6
 
7
7
  import type { AdminForthResourceCommon, AdminForthResourceColumnCommon, GetBaseConfigResponse, ResourceVeryShort, AdminUser, UserData, AdminForthConfigMenuItem, AdminForthConfigForFrontend } from '@/types/Common';
8
8
  import type { Ref } from 'vue'
9
9
 
10
10
  export const useCoreStore = defineStore('core', () => {
11
+ const { alert } = useAdminforth();
11
12
  const resourceById: Ref<Record<string, ResourceVeryShort>> = ref({});
12
13
  const theme: Ref<'light'| 'dark'> = ref(window.localStorage.getItem('af__theme') as ('light'|'dark') || 'light');
13
14
 
@@ -157,7 +158,7 @@ export const useCoreStore = defineStore('core', () => {
157
158
  });
158
159
 
159
160
  if (respData.error) {
160
- adminforth.alert({
161
+ alert({
161
162
  message: respData.error,
162
163
  variant: 'danger',
163
164
  timeout: 30,
@@ -15,7 +15,7 @@ export const useFiltersStore = defineStore('filters', () => {
15
15
  }
16
16
  const setFilter = (filter: any) => {
17
17
  const index = filters.value.findIndex(f => f.field === filter.field);
18
- if (filters.value[index]) {
18
+ if (filters.value[index] && filters.value[index].operator === filter.value.operator) {
19
19
  filters.value[index] = filter;
20
20
  return;
21
21
  }
@@ -1,7 +1,7 @@
1
1
  import type { Express, Request } from 'express';
2
2
  import type { Writable } from 'stream';
3
3
 
4
- import { ActionCheckSource, AdminForthFilterOperators, AdminForthSortDirections, AllowedActionsEnum,
4
+ import { ActionCheckSource, AdminForthFilterOperators, AdminForthSortDirections, AllowedActionsEnum, AdminForthResourcePages,
5
5
  type AdminForthComponentDeclaration,
6
6
  type AdminForthResourceCommon,
7
7
  type AdminUser, type AllowedActionsResolved,
@@ -12,7 +12,6 @@ import { ActionCheckSource, AdminForthFilterOperators, AdminForthSortDirections,
12
12
  type AdminForthComponentDeclarationFull,
13
13
  type AdminForthConfigMenuItem,
14
14
  type AnnouncementBadgeResponse,
15
- AdminForthResourcePages,
16
15
  type AdminForthResourceColumnInputCommon,
17
16
  } from './Common.js';
18
17
 
@@ -129,7 +128,7 @@ export interface IAdminForthSingleFilter {
129
128
  operator?: AdminForthFilterOperators.EQ | AdminForthFilterOperators.NE
130
129
  | AdminForthFilterOperators.GT | AdminForthFilterOperators.LT | AdminForthFilterOperators.GTE
131
130
  | AdminForthFilterOperators.LTE | AdminForthFilterOperators.LIKE | AdminForthFilterOperators.ILIKE
132
- | AdminForthFilterOperators.IN | AdminForthFilterOperators.NIN;
131
+ | AdminForthFilterOperators.IN | AdminForthFilterOperators.NIN | AdminForthFilterOperators.IS_EMPTY | AdminForthFilterOperators.IS_NOT_EMPTY;
133
132
  value?: any;
134
133
  rightField?: string;
135
134
  insecureRawSQL?: string;
@@ -307,7 +306,7 @@ export interface IAdminForthAuth {
307
306
 
308
307
  removeCustomCookie({response, name}: {response: any, name: string}): void;
309
308
 
310
- setCustomCookie({response, payload}: {response: any, payload: {name: string, value: string, expiry: number, expirySeconds: number, httpOnly: boolean}}): void;
309
+ setCustomCookie({response, payload}: {response: any, payload: {name: string, value: string, expiry?: number, expirySeconds: number, httpOnly: boolean}}): void;
311
310
 
312
311
  getCustomCookie({cookies, name}: {cookies: {key: string, value: string}[], name: string}): string | null;
313
312
 
@@ -469,6 +468,13 @@ export interface IAdminForthPlugin {
469
468
  instanceUniqueRepresentation(pluginOptions: any) : string;
470
469
 
471
470
 
471
+ /**
472
+ * If this method returns true, AdminForth will allow only one instance of plugin per whole app
473
+ * (only for case when we are creating copy of resource and activating plugins)
474
+ * If false, multiple instances of plugin can be installed on different resources.
475
+ */
476
+ shouldHaveSingleInstancePerWholeApp?(): boolean;
477
+
472
478
  /**
473
479
  * Optional method which will be called after AdminForth discovers all resources and their columns.
474
480
  * Can be used to validate types of columns, check if some columns are missing, etc.
@@ -848,6 +854,7 @@ export interface AdminForthActionInput {
848
854
  name: string;
849
855
  showIn?: {
850
856
  list?: boolean,
857
+ listThreeDotsMenu?: boolean,
851
858
  showButton?: boolean,
852
859
  showThreeDotsMenu?: boolean,
853
860
  };
@@ -861,6 +868,7 @@ export interface AdminForthActionInput {
861
868
  resource: AdminForthResource;
862
869
  recordId: string;
863
870
  adminUser: AdminUser;
871
+ response: IAdminForthHttpResponse;
864
872
  extra?: HttpExtra;
865
873
  tr: Function;
866
874
  }) => Promise<{
@@ -1102,7 +1110,7 @@ export interface AdminForthInputConfig {
1102
1110
  /**
1103
1111
  * Add custom page to the settings page
1104
1112
  */
1105
- userMenuSettingsPages: {
1113
+ userMenuSettingsPages?: {
1106
1114
  icon?: string,
1107
1115
  pageLabel: string,
1108
1116
  slug?: string,
@@ -1282,6 +1290,14 @@ export class Filters {
1282
1290
  subFilters,
1283
1291
  };
1284
1292
  }
1293
+
1294
+ static IS_EMPTY(field: string): IAdminForthSingleFilter {
1295
+ return { field, operator: AdminForthFilterOperators.IS_EMPTY, value: null };
1296
+ }
1297
+
1298
+ static IS_NOT_EMPTY(field: string): IAdminForthSingleFilter {
1299
+ return { field, operator: AdminForthFilterOperators.IS_NOT_EMPTY, value: null };
1300
+ }
1285
1301
  }
1286
1302
 
1287
1303
  export type FDataSort = (field: string, direction: AdminForthSortDirections) => IAdminForthSort;
@@ -1358,6 +1374,7 @@ export type AllowedActions = {
1358
1374
  */
1359
1375
  export interface ResourceOptionsInput extends Omit<NonNullable<AdminForthResourceInputCommon['options']>, 'allowedActions' | 'bulkActions'> {
1360
1376
 
1377
+ baseActionsAsQuickIcons?: ('show' | 'edit' | 'delete')[],
1361
1378
  /**
1362
1379
  * Custom bulk actions list. Bulk actions available in list view when user selects multiple records by
1363
1380
  * using checkboxes.
@@ -28,6 +28,8 @@ export enum AdminForthFilterOperators {
28
28
  NIN = 'nin',
29
29
  AND = 'and',
30
30
  OR = 'or',
31
+ IS_EMPTY = 'isEmpty',
32
+ IS_NOT_EMPTY = 'isNotEmpty',
31
33
  };
32
34
 
33
35
  export type FilterParams = {
@@ -503,6 +505,7 @@ export interface AdminForthResourceInputCommon {
503
505
  bottom?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
504
506
  threeDotsDropdownItems?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
505
507
  customActionIcons?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
508
+ customActionIconsThreeDotsMenuItems?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
506
509
  tableBodyStart?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
507
510
  tableRowReplace?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
508
511
  },
@@ -529,11 +532,6 @@ export interface AdminForthResourceInputCommon {
529
532
  afterBreadcrumbs?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
530
533
  bottom?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
531
534
  threeDotsDropdownItems?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
532
- /**
533
- * Custom Save button component for Edit page.
534
- * Accepts props: [record, resource, adminUser, meta, saving, validating, isValid, disabled, saveRecord]
535
- */
536
- saveButton?: AdminForthComponentDeclaration,
537
535
  },
538
536
 
539
537
  /**
@@ -546,11 +544,6 @@ export interface AdminForthResourceInputCommon {
546
544
  afterBreadcrumbs?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
547
545
  bottom?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
548
546
  threeDotsDropdownItems?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
549
- /**
550
- * Custom Save button component for Create page.
551
- * Accepts props: [record, resource, adminUser, meta, saving, validating, isValid, disabled, saveRecord]
552
- */
553
- saveButton?: AdminForthComponentDeclaration,
554
547
  },
555
548
  }
556
549
  },
@@ -10,9 +10,10 @@ export interface FrontendAPIInterface {
10
10
  * Example:
11
11
  *
12
12
  * ```ts
13
- * import adminforth from '@/adminforth'
13
+ *import { useAdminforth } from '@/adminforth';
14
14
  *
15
- * const isConfirmed = await adminforth.confirm({message: 'Are you sure?', yes: 'Yes', no: 'No'})
15
+ * const { confirm } = useAdminforth();
16
+ * const isConfirmed = await confirm({message: 'Are you sure?', yes: 'Yes', no: 'No'})
16
17
  * if (isConfirmed) {
17
18
  * your code...
18
19
  * }
@@ -31,9 +32,10 @@ export interface FrontendAPIInterface {
31
32
  * Example:
32
33
  *
33
34
  * ```ts
34
- * import adminforth from '@/adminforth'
35
+ * import { useAdminforth } from '@/adminforth';
36
+ * const { alert } = useAdminforth();
35
37
  *
36
- * adminforth.alert({message: 'Hello', variant: 'success'})
38
+ * alert({message: 'Hello', variant: 'success'})
37
39
  * ```
38
40
  *
39
41
  * @param params - The parameters of the alert
@@ -76,13 +78,14 @@ export interface FrontendAPIInterface {
76
78
  * Example:
77
79
  *
78
80
  * ```ts
79
- * import adminforth from '@/adminforth'
81
+ * import { useAdminforth } from '@/adminforth';
80
82
  *
83
+ * const { list } = useAdminforth();
81
84
  * // Regular filter (will show in badge if column.showIn.filter !== false)
82
- * adminforth.list.setFilter({field: 'name', operator: 'ilike', value: 'john'})
85
+ * list.setFilter({field: 'name', operator: 'ilike', value: 'john'})
83
86
  *
84
87
  * // Hidden filter (won't show in badge if column.showIn.filter === false)
85
- * adminforth.list.setFilter({field: 'internal_status', operator: 'eq', value: 'active'})
88
+ * list.setFilter({field: 'internal_status', operator: 'eq', value: 'active'})
86
89
  * ```
87
90
  *
88
91
  * Please note that you can set/update filter even for fields which have showIn.filter=false in resource configuration.
@@ -102,9 +105,9 @@ export interface FrontendAPIInterface {
102
105
  * Example:
103
106
  *
104
107
  * ```ts
105
- * import adminforth from '@/adminforth';
106
- *
107
- * adminforth.list.updateFilter({field: 'name', operator: 'ilike', value: 'john'})
108
+ * import { useAdminforth } from '@/adminforth';
109
+ * const { list } = useAdminforth();
110
+ * list.updateFilter({field: 'name', operator: 'ilike', value: 'john'})
108
111
  * ```
109
112
  *
110
113
  * @param filter - The filter to update
@@ -136,6 +139,18 @@ export interface FrontendAPIInterface {
136
139
  * Close the user menu dropdown
137
140
  */
138
141
  closeUserMenuDropdown(): void;
142
+
143
+ /**
144
+ * Run save interceptors for a specific resource or all resources if no resourceId is provided
145
+ */
146
+ runSaveInterceptors(params: { action: 'create'|'edit'; values: any; resource: any; resourceId: string; }): Promise<{ ok: boolean; error?: string | null; extra?: object; }>;
147
+
148
+ /**
149
+ * Clear save interceptors for a specific resource or all resources if no resourceId is provided
150
+ *
151
+ * @param resourceId - The resource ID to clear interceptors for
152
+ */
153
+ clearSaveInterceptors(resourceId?: string): void;
139
154
  }
140
155
 
141
156
  export type ConfirmParams = {
@@ -446,9 +446,14 @@ export function createSearchInputHandlers(
446
446
  }, {} as Record<string, (searchTerm: string) => void>);
447
447
  }
448
448
 
449
- export function checkShowIf(c: AdminForthResourceColumnInputCommon, record: Record<string, any>) {
449
+ export function checkShowIf(c: AdminForthResourceColumnInputCommon, record: Record<string, any>, allColumns: AdminForthResourceColumnInputCommon[]) {
450
450
  if (!c.showIf) return true;
451
-
451
+ const recordCopy = { ...record };
452
+ for (const col of allColumns) {
453
+ if (!recordCopy[col.name]) {
454
+ recordCopy[col.name] = null;
455
+ }
456
+ }
452
457
  const evaluatePredicate = (predicate: Predicate): boolean => {
453
458
  const results: boolean[] = [];
454
459
 
@@ -463,7 +468,7 @@ export function checkShowIf(c: AdminForthResourceColumnInputCommon, record: Reco
463
468
  const fieldEntries = Object.entries(predicate).filter(([key]) => !key.startsWith('$'));
464
469
  if (fieldEntries.length > 0) {
465
470
  const fieldResult = fieldEntries.every(([field, condition]) => {
466
- const recordValue = record[field];
471
+ const recordValue = recordCopy[field];
467
472
 
468
473
  if (condition === undefined) {
469
474
  return true;
@@ -17,25 +17,7 @@
17
17
  >
18
18
  {{ $t('Cancel') }}
19
19
  </button>
20
-
21
- <!-- Custom Save Button injection -->
22
- <component
23
- v-if="createSaveButtonInjection"
24
- :is="getCustomComponent(createSaveButtonInjection)"
25
- :meta="createSaveButtonInjection.meta"
26
- :record="record"
27
- :resource="coreStore.resource"
28
- :adminUser="coreStore.adminUser"
29
- :saving="saving"
30
- :validating="validating"
31
- :isValid="isValid"
32
- :disabled="saving || (validating && !isValid)"
33
- :saveRecord="saveRecord"
34
- />
35
-
36
- <!-- Default Save Button fallback -->
37
20
  <button
38
- v-else
39
21
  @click="() => saveRecord()"
40
22
  class="af-save-button flex items-center py-1 px-3 text-sm font-medium rounded-default text-lightCreateViewSaveButtonText focus:outline-none bg-lightCreateViewButtonBackground rounded border border-lightCreateViewButtonBorder hover:bg-lightCreateViewButtonBackgroundHover hover:text-lightCreateViewSaveButtonTextHover focus:z-10 focus:ring-4 focus:ring-lightCreateViewButtonFocusRing dark:focus:ring-darkCreateViewButtonFocusRing dark:bg-darkCreateViewButtonBackground dark:text-darkCreateViewSaveButtonText dark:border-darkCreateViewButtonBorder dark:hover:text-darkCreateViewSaveButtonTextHover dark:hover:bg-darkCreateViewButtonBackgroundHover disabled:opacity-50 gap-1"
41
23
  :disabled="saving || (validating && !isValid)"
@@ -99,12 +81,12 @@ import SingleSkeletLoader from '@/components/SingleSkeletLoader.vue';
99
81
  import { useCoreStore } from '@/stores/core';
100
82
  import { callAdminForthApi, getCustomComponent,checkAcessByAllowedActions, initThreeDotsDropdown, checkShowIf } from '@/utils';
101
83
  import { IconFloppyDiskSolid } from '@iconify-prerendered/vue-flowbite';
102
- import { onMounted, ref, watch, nextTick } from 'vue';
84
+ import { onMounted, onBeforeMount, ref, watch, nextTick } from 'vue';
103
85
  import { useRoute, useRouter } from 'vue-router';
104
86
  import { computed } from 'vue';
105
87
  import { showErrorTost } from '@/composables/useFrontendApi';
106
88
  import ThreeDotsMenu from '@/components/ThreeDotsMenu.vue';
107
- import adminforth from '@/adminforth';
89
+ import { useAdminforth } from '@/adminforth';
108
90
  import { useI18n } from 'vue-i18n';
109
91
  import { type AdminForthComponentDeclarationFull } from '@/types/Common.js';
110
92
  import type { AdminForthResourceColumn } from '@/types/Back';
@@ -121,18 +103,12 @@ const router = useRouter();
121
103
  const record = ref({});
122
104
 
123
105
  const coreStore = useCoreStore();
106
+ const { clearSaveInterceptors, runSaveInterceptors, alert } = useAdminforth();
124
107
 
125
108
  const { t } = useI18n();
126
109
 
127
110
  const resourceFormRef = ref<InstanceType<typeof ResourceForm> | null>(null);
128
111
 
129
- const createSaveButtonInjection = computed<AdminForthComponentDeclarationFull | null>(() => {
130
- const raw: any = coreStore.resourceOptions?.pageInjections?.create?.saveButton as any;
131
- if (!raw) return null;
132
- const item = Array.isArray(raw) ? raw[0] : raw;
133
- return item as AdminForthComponentDeclarationFull;
134
- });
135
-
136
112
  const initialValues = ref({});
137
113
 
138
114
  const readonlyColumns = ref([]);
@@ -142,6 +118,10 @@ async function onUpdateRecord(newRecord: any) {
142
118
  record.value = newRecord;
143
119
  }
144
120
 
121
+ onBeforeMount(() => {
122
+ clearSaveInterceptors(route.params.resourceId as string);
123
+ });
124
+
145
125
  onMounted(async () => {
146
126
  loading.value = true;
147
127
  await coreStore.fetchResourceFull({
@@ -182,7 +162,7 @@ onMounted(async () => {
182
162
  initThreeDotsDropdown();
183
163
  });
184
164
 
185
- async function saveRecord(opts?: { confirmationResult?: any }) {
165
+ async function saveRecord() {
186
166
  if (!isValid.value) {
187
167
  validating.value = true;
188
168
  await nextTick();
@@ -192,8 +172,20 @@ async function saveRecord(opts?: { confirmationResult?: any }) {
192
172
  validating.value = false;
193
173
  }
194
174
  const requiredColumns = coreStore.resource?.columns.filter(c => c.required?.create === true) || [];
195
- const requiredColumnsToSkip = requiredColumns.filter(c => checkShowIf(c, record.value) === false);
175
+ const requiredColumnsToSkip = requiredColumns.filter(c => checkShowIf(c, record.value, coreStore.resource?.columns || []) === false);
196
176
  saving.value = true;
177
+ const interceptorsResult = await runSaveInterceptors({
178
+ action: 'create',
179
+ values: record.value,
180
+ resource: coreStore.resource,
181
+ resourceId: route.params.resourceId as string,
182
+ });
183
+ if (!interceptorsResult.ok) {
184
+ saving.value = false;
185
+ if (interceptorsResult.error) showErrorTost(interceptorsResult.error);
186
+ return;
187
+ }
188
+ const interceptorConfirmationResult = (interceptorsResult.extra as Record<string, any>)?.confirmationResult;
197
189
  const response = await callAdminForthApi({
198
190
  method: 'POST',
199
191
  path: `/create_record`,
@@ -202,7 +194,7 @@ async function saveRecord(opts?: { confirmationResult?: any }) {
202
194
  record: record.value,
203
195
  requiredColumnsToSkip,
204
196
  meta: {
205
- ...(opts?.confirmationResult ? { confirmationResult: opts.confirmationResult } : {}),
197
+ ...(interceptorConfirmationResult ? { confirmationResult: interceptorConfirmationResult } : {}),
206
198
  },
207
199
  },
208
200
  });
@@ -220,7 +212,7 @@ async function saveRecord(opts?: { confirmationResult?: any }) {
220
212
  primaryKey: response.newRecordId
221
213
  }
222
214
  });
223
- adminforth.alert({
215
+ alert({
224
216
  message: t('Record created successfully!'),
225
217
  variant: 'success'
226
218
  });
@@ -236,7 +228,7 @@ function scrollToInvalidField() {
236
228
  }
237
229
  }
238
230
  const errorMessage = t('Failed to save. Please fix errors for the following fields:') + '<ul class="mt-2 list-disc list-inside">' + columnsWithErrors.map(c => `<li><strong>${c.column.label || c.column.name}</strong>: ${c.error}</li>`).join('') + '</ul>';
239
- adminforth.alert({
231
+ alert({
240
232
  messageHtml: errorMessage,
241
233
  variant: 'danger'
242
234
  });
@@ -16,25 +16,7 @@
16
16
  >
17
17
  {{ $t('Cancel') }}
18
18
  </button>
19
-
20
- <!-- Custom Save Button injection -->
21
- <component
22
- v-if="editSaveButtonInjection"
23
- :is="getCustomComponent(editSaveButtonInjection)"
24
- :meta="editSaveButtonInjection.meta"
25
- :record="editableRecord"
26
- :resource="coreStore.resource"
27
- :adminUser="coreStore.adminUser"
28
- :saving="saving"
29
- :validating="validating"
30
- :isValid="isValid"
31
- :disabled="saving || (validating && !isValid)"
32
- :saveRecord="saveRecord"
33
- />
34
-
35
- <!-- Default Save Button fallback -->
36
19
  <button
37
- v-else
38
20
  @click="() => saveRecord()"
39
21
  class="flex items-center py-1 px-3 text-sm font-medium rounded-default text-lightEditViewSaveButtonText focus:outline-none bg-lightEditViewButtonBackground rounded border border-lightEditViewButtonBorder hover:bg-lightEditViewButtonBackgroundHover hover:text-lightEditViewSaveButtonTextHover focus:z-10 focus:ring-4 focus:ring-lightEditViewButtonFocusRing dark:focus:ring-darkEditViewButtonFocusRing dark:bg-darkEditViewButtonBackground dark:text-darkEditViewSaveButtonText dark:border-darkEditViewButtonBorder dark:hover:text-darkEditViewSaveButtonTextHover dark:hover:bg-darkEditViewButtonBackgroundHover disabled:opacity-50 gap-1"
40
22
  :disabled="saving || (validating && !isValid)"
@@ -94,17 +76,18 @@ import SingleSkeletLoader from '@/components/SingleSkeletLoader.vue';
94
76
  import { useCoreStore } from '@/stores/core';
95
77
  import { callAdminForthApi, getCustomComponent,checkAcessByAllowedActions, initThreeDotsDropdown } from '@/utils';
96
78
  import { IconFloppyDiskSolid } from '@iconify-prerendered/vue-flowbite';
97
- import { computed, onMounted, ref, type Ref, nextTick } from 'vue';
79
+ import { computed, onMounted, onBeforeMount, ref, type Ref, nextTick, watch } from 'vue';
98
80
  import { useRoute, useRouter } from 'vue-router';
99
81
  import { showErrorTost } from '@/composables/useFrontendApi';
100
82
  import ThreeDotsMenu from '@/components/ThreeDotsMenu.vue';
101
- import adminforth from '@/adminforth';
83
+ import { useAdminforth } from '@/adminforth';
102
84
  import { useI18n } from 'vue-i18n';
103
85
  import { type AdminForthComponentDeclarationFull } from '@/types/Common.js';
104
86
  import type { AdminForthResourceColumn } from '@/types/Back';
105
87
 
106
88
  const { t } = useI18n();
107
89
  const coreStore = useCoreStore();
90
+ const { clearSaveInterceptors, runSaveInterceptors, alert } = useAdminforth();
108
91
 
109
92
  const isValid = ref(false);
110
93
  const validating = ref(false);
@@ -118,14 +101,11 @@ const saving = ref(false);
118
101
 
119
102
  const record: Ref<Record<string, any>> = ref({});
120
103
 
121
- const resourceFormRef = ref<InstanceType<typeof ResourceForm> | null>(null);
104
+ watch(record, (newVal) => {
105
+ console.log('Record updated:', newVal);
106
+ }, { deep: true });
122
107
 
123
- const editSaveButtonInjection = computed<AdminForthComponentDeclarationFull | null>(() => {
124
- const raw: any = coreStore.resourceOptions?.pageInjections?.edit?.saveButton as any;
125
- if (!raw) return null;
126
- const item = Array.isArray(raw) ? raw[0] : raw;
127
- return item as AdminForthComponentDeclarationFull;
128
- });
108
+ const resourceFormRef = ref<InstanceType<typeof ResourceForm> | null>(null);
129
109
 
130
110
  async function onUpdateRecord(newRecord: Record<string, any>) {
131
111
  record.value = newRecord;
@@ -148,6 +128,10 @@ const editableRecord = computed(() => {
148
128
  return newRecord;
149
129
  })
150
130
 
131
+ onBeforeMount(() => {
132
+ clearSaveInterceptors(route.params.resourceId as string);
133
+ });
134
+
151
135
  onMounted(async () => {
152
136
  loading.value = true;
153
137
 
@@ -169,7 +153,7 @@ onMounted(async () => {
169
153
  loading.value = false;
170
154
  });
171
155
 
172
- async function saveRecord(opts?: { confirmationResult?: any }) {
156
+ async function saveRecord() {
173
157
  if (!isValid.value) {
174
158
  validating.value = true;
175
159
  await nextTick();
@@ -180,6 +164,18 @@ async function saveRecord(opts?: { confirmationResult?: any }) {
180
164
  }
181
165
 
182
166
  saving.value = true;
167
+ const interceptorsResult = await runSaveInterceptors({
168
+ action: 'edit',
169
+ values: record.value,
170
+ resource: coreStore.resource,
171
+ resourceId: route.params.resourceId as string,
172
+ });
173
+ if (!interceptorsResult.ok) {
174
+ saving.value = false;
175
+ if (interceptorsResult.error) showErrorTost(interceptorsResult.error);
176
+ return;
177
+ }
178
+ const interceptorConfirmationResult = interceptorsResult.extra?.confirmationResult;
183
179
  const updates: Record<string, any> = {};
184
180
  for (const key in record.value) {
185
181
  let columnIsUpdated = false;
@@ -212,14 +208,14 @@ async function saveRecord(opts?: { confirmationResult?: any }) {
212
208
  recordId: route.params.primaryKey,
213
209
  record: updates,
214
210
  meta: {
215
- ...(opts?.confirmationResult ? { confirmationResult: opts.confirmationResult } : {}),
211
+ ...(interceptorConfirmationResult ? { confirmationResult: interceptorConfirmationResult } : {}),
216
212
  },
217
213
  },
218
214
  });
219
215
  if (resp.error && resp.error !== 'Operation aborted by hook') {
220
216
  showErrorTost(resp.error);
221
217
  } else {
222
- adminforth.alert({
218
+ alert({
223
219
  message: t('Record updated successfully'),
224
220
  variant: 'success',
225
221
  timeout: 400000
@@ -238,7 +234,7 @@ function scrollToInvalidField() {
238
234
  }
239
235
  }
240
236
  const errorMessage = t('Failed to save. Please fix errors for the following fields:') + '<ul class="mt-2 list-disc list-inside">' + columnsWithErrors.map(c => `<li><strong>${c.column.label || c.column.name}</strong>: ${c.error}</li>`).join('') + '</ul>';
241
- adminforth.alert({
237
+ alert({
242
238
  messageHtml: errorMessage,
243
239
  variant: 'danger'
244
240
  });