adminforth 2.17.0-next.4 → 2.17.0-next.41

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 (108) hide show
  1. package/commands/createApp/templates/adminuser.ts.hbs +2 -1
  2. package/commands/createApp/templates/index.ts.hbs +3 -2
  3. package/commands/createCustomComponent/main.js +0 -3
  4. package/commands/createCustomComponent/templates/customCrud/afterBreadcrumbs.vue.hbs +4 -2
  5. package/commands/createCustomComponent/templates/customCrud/beforeActionButtons.vue.hbs +3 -2
  6. package/commands/createCustomComponent/templates/customCrud/beforeBreadcrumbs.vue.hbs +4 -2
  7. package/commands/createCustomComponent/templates/customCrud/bottom.vue.hbs +4 -2
  8. package/commands/createCustomComponent/templates/customCrud/threeDotsDropdownItems.vue.hbs +4 -2
  9. package/commands/createPlugin/templates/index.ts.hbs +4 -0
  10. package/dist/auth.d.ts +2 -2
  11. package/dist/auth.d.ts.map +1 -1
  12. package/dist/auth.js +17 -10
  13. package/dist/auth.js.map +1 -1
  14. package/dist/basePlugin.d.ts +1 -0
  15. package/dist/basePlugin.d.ts.map +1 -1
  16. package/dist/basePlugin.js +6 -2
  17. package/dist/basePlugin.js.map +1 -1
  18. package/dist/dataConnectors/baseConnector.d.ts +1 -0
  19. package/dist/dataConnectors/baseConnector.d.ts.map +1 -1
  20. package/dist/dataConnectors/baseConnector.js +100 -14
  21. package/dist/dataConnectors/baseConnector.js.map +1 -1
  22. package/dist/dataConnectors/clickhouse.d.ts +2 -0
  23. package/dist/dataConnectors/clickhouse.d.ts.map +1 -1
  24. package/dist/dataConnectors/clickhouse.js +15 -4
  25. package/dist/dataConnectors/clickhouse.js.map +1 -1
  26. package/dist/dataConnectors/mongo.d.ts +8 -1
  27. package/dist/dataConnectors/mongo.d.ts.map +1 -1
  28. package/dist/dataConnectors/mongo.js +72 -28
  29. package/dist/dataConnectors/mongo.js.map +1 -1
  30. package/dist/dataConnectors/mysql.d.ts +2 -0
  31. package/dist/dataConnectors/mysql.d.ts.map +1 -1
  32. package/dist/dataConnectors/mysql.js +22 -23
  33. package/dist/dataConnectors/mysql.js.map +1 -1
  34. package/dist/dataConnectors/postgres.d.ts +2 -0
  35. package/dist/dataConnectors/postgres.d.ts.map +1 -1
  36. package/dist/dataConnectors/postgres.js +23 -26
  37. package/dist/dataConnectors/postgres.js.map +1 -1
  38. package/dist/dataConnectors/sqlite.d.ts +2 -0
  39. package/dist/dataConnectors/sqlite.d.ts.map +1 -1
  40. package/dist/dataConnectors/sqlite.js +19 -19
  41. package/dist/dataConnectors/sqlite.js.map +1 -1
  42. package/dist/index.d.ts +10 -4
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +60 -54
  45. package/dist/index.js.map +1 -1
  46. package/dist/modules/codeInjector.d.ts.map +1 -1
  47. package/dist/modules/codeInjector.js +45 -63
  48. package/dist/modules/codeInjector.js.map +1 -1
  49. package/dist/modules/configValidator.d.ts.map +1 -1
  50. package/dist/modules/configValidator.js +12 -8
  51. package/dist/modules/configValidator.js.map +1 -1
  52. package/dist/modules/logger.d.ts +5 -0
  53. package/dist/modules/logger.d.ts.map +1 -0
  54. package/dist/modules/logger.js +16 -0
  55. package/dist/modules/logger.js.map +1 -0
  56. package/dist/modules/restApi.d.ts.map +1 -1
  57. package/dist/modules/restApi.js +21 -23
  58. package/dist/modules/restApi.js.map +1 -1
  59. package/dist/modules/socketBroker.d.ts.map +1 -1
  60. package/dist/modules/socketBroker.js +6 -5
  61. package/dist/modules/socketBroker.js.map +1 -1
  62. package/dist/modules/styles.js +1 -1
  63. package/dist/servers/express.d.ts.map +1 -1
  64. package/dist/servers/express.js +11 -11
  65. package/dist/servers/express.js.map +1 -1
  66. package/dist/spa/src/App.vue +6 -3
  67. package/dist/spa/src/adminforth.ts +60 -1
  68. package/dist/spa/src/afcl/DatePicker.vue +0 -1
  69. package/dist/spa/src/afcl/Dropzone.vue +6 -4
  70. package/dist/spa/src/afcl/Tooltip.vue +38 -4
  71. package/dist/spa/src/components/ColumnValueInput.vue +14 -1
  72. package/dist/spa/src/components/CustomDateRangePicker.vue +0 -2
  73. package/dist/spa/src/components/CustomRangePicker.vue +9 -6
  74. package/dist/spa/src/components/Filters.vue +4 -4
  75. package/dist/spa/src/components/ListActionsThreeDots.vue +235 -0
  76. package/dist/spa/src/components/ResourceForm.vue +4 -4
  77. package/dist/spa/src/components/ResourceListTable.vue +30 -16
  78. package/dist/spa/src/components/ResourceListTableVirtual.vue +34 -18
  79. package/dist/spa/src/components/Sidebar.vue +4 -2
  80. package/dist/spa/src/components/ThreeDotsMenu.vue +6 -6
  81. package/dist/spa/src/composables/useFrontendApi.ts +8 -4
  82. package/dist/spa/src/renderers/CompactField.vue +3 -2
  83. package/dist/spa/src/renderers/CompactUUID.vue +3 -2
  84. package/dist/spa/src/renderers/RichText.vue +15 -0
  85. package/dist/spa/src/stores/core.ts +3 -2
  86. package/dist/spa/src/stores/filters.ts +1 -1
  87. package/dist/spa/src/types/Back.ts +34 -11
  88. package/dist/spa/src/types/Common.ts +7 -14
  89. package/dist/spa/src/types/FrontendAPI.ts +25 -10
  90. package/dist/spa/src/utils.ts +4 -3
  91. package/dist/spa/src/views/CreateView.vue +23 -31
  92. package/dist/spa/src/views/EditView.vue +27 -31
  93. package/dist/spa/src/views/ListView.vue +20 -10
  94. package/dist/spa/src/views/SettingsView.vue +3 -2
  95. package/dist/spa/src/views/ShowView.vue +7 -6
  96. package/dist/types/Back.d.ts +27 -5
  97. package/dist/types/Back.d.ts.map +1 -1
  98. package/dist/types/Back.js +6 -0
  99. package/dist/types/Back.js.map +1 -1
  100. package/dist/types/Common.d.ts +8 -15
  101. package/dist/types/Common.d.ts.map +1 -1
  102. package/dist/types/Common.js +2 -0
  103. package/dist/types/Common.js.map +1 -1
  104. package/dist/types/FrontendAPI.d.ts +32 -10
  105. package/dist/types/FrontendAPI.d.ts.map +1 -1
  106. package/dist/types/FrontendAPI.js.map +1 -1
  107. package/package.json +4 -1
  108. package/commands/createCustomComponent/templates/customCrud/saveButton.vue.hbs +0 -28
@@ -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
 
@@ -360,16 +359,16 @@ export interface IAdminForth {
360
359
  tr(msg: string, category: string, lang: string, params: any, pluralizationNumber?: number): Promise<string>;
361
360
 
362
361
  createResourceRecord(
363
- params: { resource: AdminForthResource, record: any, adminUser: AdminUser, extra?: HttpExtra }
362
+ params: { resource: AdminForthResource, record: any, response: IAdminForthHttpResponse, adminUser: AdminUser, extra?: HttpExtra }
364
363
  ): Promise<{ error?: string, createdRecord?: any, newRecordId?: any }>;
365
364
 
366
365
  updateResourceRecord(
367
- params: { resource: AdminForthResource, recordId: any, record: any, oldRecord: any, adminUser: AdminUser, extra?: HttpExtra, updates?: never }
368
- | { resource: AdminForthResource, recordId: any, record?: never, oldRecord: any, adminUser: AdminUser, extra?: HttpExtra, updates: any }
366
+ params: { resource: AdminForthResource, recordId: any, record: any, oldRecord: any, adminUser: AdminUser, response: IAdminForthHttpResponse, extra?: HttpExtra, updates?: never }
367
+ | { resource: AdminForthResource, recordId: any, record?: never, oldRecord: any, adminUser: AdminUser, response: IAdminForthHttpResponse, extra?: HttpExtra, updates: any }
369
368
  ): Promise<{ error?: string }>;
370
369
 
371
370
  deleteResourceRecord(
372
- params: { resource: AdminForthResource, recordId: string, adminUser: AdminUser, record: any, extra?: HttpExtra }
371
+ params: { resource: AdminForthResource, recordId: string, adminUser: AdminUser, record: any, response: IAdminForthHttpResponse, extra?: HttpExtra }
373
372
  ): Promise<{ error?: string }>;
374
373
 
375
374
  auth: IAdminForthAuth;
@@ -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.
@@ -539,6 +545,7 @@ export type BeforeDeleteSaveFunction = (params: {
539
545
  adminUser: AdminUser,
540
546
  record: any,
541
547
  adminforth: IAdminForth,
548
+ response: IAdminForthHttpResponse,
542
549
  extra?: HttpExtra,
543
550
  }) => Promise<{ok: boolean, error?: string}>;
544
551
 
@@ -551,6 +558,7 @@ export type BeforeEditSaveFunction = (params: {
551
558
  record: any, // legacy, 'updates' should be used instead
552
559
  oldRecord: any,
553
560
  adminforth: IAdminForth,
561
+ response: IAdminForthHttpResponse,
554
562
  extra?: HttpExtra,
555
563
  }) => Promise<{ok: boolean, error?: string | null}>;
556
564
 
@@ -561,6 +569,7 @@ export type BeforeCreateSaveFunction = (params: {
561
569
  adminUser: AdminUser,
562
570
  record: any,
563
571
  adminforth: IAdminForth,
572
+ response: IAdminForthHttpResponse,
564
573
  extra?: HttpExtra,
565
574
  }) => Promise<{ok: boolean, error?: string | null, newRecordId?: string}>;
566
575
 
@@ -571,6 +580,7 @@ export type AfterCreateSaveFunction = (params: {
571
580
  record: any,
572
581
  adminforth: IAdminForth,
573
582
  recordWithVirtualColumns?: any,
583
+ response: IAdminForthHttpResponse,
574
584
  extra?: HttpExtra,
575
585
  }) => Promise<{ok: boolean, error?: string}>;
576
586
 
@@ -584,6 +594,7 @@ export type AfterDeleteSaveFunction = (params: {
584
594
  adminUser: AdminUser,
585
595
  record: any,
586
596
  adminforth: IAdminForth,
597
+ response: IAdminForthHttpResponse,
587
598
  extra?: HttpExtra,
588
599
  }) => Promise<{ok: boolean, error?: string}>;
589
600
 
@@ -596,6 +607,7 @@ export type AfterEditSaveFunction = (params: {
596
607
  record: any, // legacy, 'updates' should be used instead
597
608
  oldRecord: any,
598
609
  adminforth: IAdminForth,
610
+ response: IAdminForthHttpResponse,
599
611
  extra?: HttpExtra,
600
612
  }) => Promise<{ok: boolean, error?: string}>;
601
613
 
@@ -848,6 +860,7 @@ export interface AdminForthActionInput {
848
860
  name: string;
849
861
  showIn?: {
850
862
  list?: boolean,
863
+ listThreeDotsMenu?: boolean,
851
864
  showButton?: boolean,
852
865
  showThreeDotsMenu?: boolean,
853
866
  };
@@ -861,6 +874,7 @@ export interface AdminForthActionInput {
861
874
  resource: AdminForthResource;
862
875
  recordId: string;
863
876
  adminUser: AdminUser;
877
+ response: IAdminForthHttpResponse;
864
878
  extra?: HttpExtra;
865
879
  tr: Function;
866
880
  }) => Promise<{
@@ -1102,7 +1116,7 @@ export interface AdminForthInputConfig {
1102
1116
  /**
1103
1117
  * Add custom page to the settings page
1104
1118
  */
1105
- userMenuSettingsPages: {
1119
+ userMenuSettingsPages?: {
1106
1120
  icon?: string,
1107
1121
  pageLabel: string,
1108
1122
  slug?: string,
@@ -1282,6 +1296,14 @@ export class Filters {
1282
1296
  subFilters,
1283
1297
  };
1284
1298
  }
1299
+
1300
+ static IS_EMPTY(field: string): IAdminForthSingleFilter {
1301
+ return { field, operator: AdminForthFilterOperators.IS_EMPTY, value: null };
1302
+ }
1303
+
1304
+ static IS_NOT_EMPTY(field: string): IAdminForthSingleFilter {
1305
+ return { field, operator: AdminForthFilterOperators.IS_NOT_EMPTY, value: null };
1306
+ }
1285
1307
  }
1286
1308
 
1287
1309
  export type FDataSort = (field: string, direction: AdminForthSortDirections) => IAdminForthSort;
@@ -1358,6 +1380,7 @@ export type AllowedActions = {
1358
1380
  */
1359
1381
  export interface ResourceOptionsInput extends Omit<NonNullable<AdminForthResourceInputCommon['options']>, 'allowedActions' | 'bulkActions'> {
1360
1382
 
1383
+ baseActionsAsQuickIcons?: ('show' | 'edit' | 'delete')[],
1361
1384
  /**
1362
1385
  * Custom bulk actions list. Bulk actions available in list view when user selects multiple records by
1363
1386
  * using checkboxes.
@@ -1526,8 +1549,8 @@ export interface AdminForthBulkAction extends AdminForthBulkActionCommon {
1526
1549
  * Callback which will be called on backend when user clicks on action button.
1527
1550
  * It should return Promise which will be resolved when action is done.
1528
1551
  */
1529
- action: ({ resource, selectedIds, adminUser, tr }: {
1530
- resource: AdminForthResource, selectedIds: Array<any>, adminUser: AdminUser, tr: (key: string, category?: string, params?: any) => string
1552
+ action: ({ resource, selectedIds, adminUser, response, tr }: {
1553
+ resource: AdminForthResource, selectedIds: Array<any>, adminUser: AdminUser, response: IAdminForthHttpResponse, tr: (key: string, category?: string, params?: any) => string
1531
1554
  }) => Promise<{ ok: boolean, error?: string, successMessage?: string }>,
1532
1555
 
1533
1556
  /**
@@ -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 = {
@@ -294,7 +296,7 @@ export interface AdminForthComponentDeclarationFull {
294
296
  [key: string]: any,
295
297
  }
296
298
  }
297
- import { type AdminForthActionInput } from './Back.js'
299
+ import { type AdminForthActionInput, type AdminForthResource } from './Back.js'
298
300
  export { type AdminForthActionInput } from './Back.js'
299
301
 
300
302
  export type AdminForthComponentDeclaration = AdminForthComponentDeclarationFull | string;
@@ -445,7 +447,7 @@ export interface AdminForthResourceInputCommon {
445
447
  * If you wish to open page in new tab, add `target=_blank` get param to returned URL, example:
446
448
  *
447
449
  * ```ts
448
- * listTableClickUrl: async (record, adminUser) => {
450
+ * listTableClickUrl: async (record, adminUser, resource) => {
449
451
  * return `https://google.com/search?q=${record.name}&target=_blank`;
450
452
  * }
451
453
  * ```
@@ -455,7 +457,7 @@ export interface AdminForthResourceInputCommon {
455
457
  * Example:
456
458
  *
457
459
  * ```ts
458
- * listTableClickUrl: async (record, adminUser) => {
460
+ * listTableClickUrl: async (record, adminUser, resource) => {
459
461
  * return null;
460
462
  * }
461
463
  * ```
@@ -464,7 +466,7 @@ export interface AdminForthResourceInputCommon {
464
466
  * @param adminUser - user who clicked
465
467
  * @returns
466
468
  */
467
- listTableClickUrl?: (record: any, adminUser: AdminUser) => Promise<string | null>,
469
+ listTableClickUrl?: (record: any, adminUser: AdminUser, resource: AdminForthResource) => Promise<string | null>,
468
470
 
469
471
  /**
470
472
  * Whether to refresh existing list rows automatically every N seconds.
@@ -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 = {
@@ -448,9 +448,10 @@ export function createSearchInputHandlers(
448
448
 
449
449
  export function checkShowIf(c: AdminForthResourceColumnInputCommon, record: Record<string, any>, allColumns: AdminForthResourceColumnInputCommon[]) {
450
450
  if (!c.showIf) return true;
451
+ const recordCopy = { ...record };
451
452
  for (const col of allColumns) {
452
- if (!record[col.name]) {
453
- record[col.name] = null;
453
+ if (!recordCopy[col.name]) {
454
+ recordCopy[col.name] = null;
454
455
  }
455
456
  }
456
457
  const evaluatePredicate = (predicate: Predicate): boolean => {
@@ -467,7 +468,7 @@ export function checkShowIf(c: AdminForthResourceColumnInputCommon, record: Reco
467
468
  const fieldEntries = Object.entries(predicate).filter(([key]) => !key.startsWith('$'));
468
469
  if (fieldEntries.length > 0) {
469
470
  const fieldResult = fieldEntries.every(([field, condition]) => {
470
- const recordValue = record[field];
471
+ const recordValue = recordCopy[field];
471
472
 
472
473
  if (condition === undefined) {
473
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();
@@ -194,6 +174,18 @@ async function saveRecord(opts?: { confirmationResult?: any }) {
194
174
  const requiredColumns = coreStore.resource?.columns.filter(c => c.required?.create === true) || [];
195
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
  });