adminforth 2.26.0-next.29 → 2.26.0-next.30

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.
@@ -34,6 +34,7 @@
34
34
  "@tailwindcss/typography": "^0.5.19",
35
35
  "@tsconfig/node20": "^20.1.4",
36
36
  "@types/node": "^20.12.5",
37
+ "@types/sanitize-html": "^2.16.1",
37
38
  "@vitejs/plugin-vue": "^5.0.4",
38
39
  "@vue/eslint-config-typescript": "^13.0.0",
39
40
  "@vue/tsconfig": "^0.5.1",
@@ -1642,6 +1643,49 @@
1642
1643
  "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==",
1643
1644
  "license": "MIT"
1644
1645
  },
1646
+ "node_modules/@types/sanitize-html": {
1647
+ "version": "2.16.1",
1648
+ "resolved": "https://registry.npmjs.org/@types/sanitize-html/-/sanitize-html-2.16.1.tgz",
1649
+ "integrity": "sha512-n9wjs8bCOTyN/ynwD8s/nTcTreIHB1vf31vhLMGqUPNHaweKC4/fAl4Dj+hUlCTKYgm4P3k83fmiFfzkZ6sgMA==",
1650
+ "dev": true,
1651
+ "license": "MIT",
1652
+ "dependencies": {
1653
+ "htmlparser2": "^10.1"
1654
+ }
1655
+ },
1656
+ "node_modules/@types/sanitize-html/node_modules/entities": {
1657
+ "version": "7.0.1",
1658
+ "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
1659
+ "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
1660
+ "dev": true,
1661
+ "license": "BSD-2-Clause",
1662
+ "engines": {
1663
+ "node": ">=0.12"
1664
+ },
1665
+ "funding": {
1666
+ "url": "https://github.com/fb55/entities?sponsor=1"
1667
+ }
1668
+ },
1669
+ "node_modules/@types/sanitize-html/node_modules/htmlparser2": {
1670
+ "version": "10.1.0",
1671
+ "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz",
1672
+ "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==",
1673
+ "dev": true,
1674
+ "funding": [
1675
+ "https://github.com/fb55/htmlparser2?sponsor=1",
1676
+ {
1677
+ "type": "github",
1678
+ "url": "https://github.com/sponsors/fb55"
1679
+ }
1680
+ ],
1681
+ "license": "MIT",
1682
+ "dependencies": {
1683
+ "domelementtype": "^2.3.0",
1684
+ "domhandler": "^5.0.3",
1685
+ "domutils": "^3.2.2",
1686
+ "entities": "^7.0.1"
1687
+ }
1688
+ },
1645
1689
  "node_modules/@types/web-bluetooth": {
1646
1690
  "version": "0.0.20",
1647
1691
  "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz",
@@ -39,6 +39,7 @@
39
39
  "@tailwindcss/typography": "^0.5.19",
40
40
  "@tsconfig/node20": "^20.1.4",
41
41
  "@types/node": "^20.12.5",
42
+ "@types/sanitize-html": "^2.16.1",
42
43
  "@vitejs/plugin-vue": "^5.0.4",
43
44
  "@vue/eslint-config-typescript": "^13.0.0",
44
45
  "@vue/tsconfig": "^0.5.1",
@@ -81,6 +81,9 @@ importers:
81
81
  '@types/node':
82
82
  specifier: ^20.12.5
83
83
  version: 20.19.37
84
+ '@types/sanitize-html':
85
+ specifier: ^2.16.1
86
+ version: 2.16.1
84
87
  '@vitejs/plugin-vue':
85
88
  specifier: ^5.0.4
86
89
  version: 5.2.4(vite@5.4.21(@types/node@20.19.37)(sass@1.97.3))(vue@3.5.29(typescript@5.4.5))
@@ -677,6 +680,9 @@ packages:
677
680
  '@types/resolve@1.20.2':
678
681
  resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==}
679
682
 
683
+ '@types/sanitize-html@2.16.1':
684
+ resolution: {integrity: sha512-n9wjs8bCOTyN/ynwD8s/nTcTreIHB1vf31vhLMGqUPNHaweKC4/fAl4Dj+hUlCTKYgm4P3k83fmiFfzkZ6sgMA==}
685
+
680
686
  '@types/web-bluetooth@0.0.14':
681
687
  resolution: {integrity: sha512-5d2RhCard1nQUC3aHcq/gHzWYO6K0WJmAbjO7mQJgCQKtZpgXxv1rOM6O/dBDhDYYVutk1sciOgNSe+5YyfM8A==}
682
688
 
@@ -1273,6 +1279,9 @@ packages:
1273
1279
  hookable@5.5.3:
1274
1280
  resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==}
1275
1281
 
1282
+ htmlparser2@10.1.0:
1283
+ resolution: {integrity: sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==}
1284
+
1276
1285
  htmlparser2@8.0.2:
1277
1286
  resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==}
1278
1287
 
@@ -2265,6 +2274,10 @@ snapshots:
2265
2274
 
2266
2275
  '@types/resolve@1.20.2': {}
2267
2276
 
2277
+ '@types/sanitize-html@2.16.1':
2278
+ dependencies:
2279
+ htmlparser2: 10.1.0
2280
+
2268
2281
  '@types/web-bluetooth@0.0.14': {}
2269
2282
 
2270
2283
  '@types/web-bluetooth@0.0.20': {}
@@ -2972,6 +2985,13 @@ snapshots:
2972
2985
 
2973
2986
  hookable@5.5.3: {}
2974
2987
 
2988
+ htmlparser2@10.1.0:
2989
+ dependencies:
2990
+ domelementtype: 2.3.0
2991
+ domhandler: 5.0.3
2992
+ domutils: 3.2.2
2993
+ entities: 7.0.1
2994
+
2975
2995
  htmlparser2@8.0.2:
2976
2996
  dependencies:
2977
2997
  domelementtype: 2.3.0
@@ -19,26 +19,13 @@ class FrontendAPI implements FrontendAPIInterface {
19
19
  public modalStore:any
20
20
  public filtersStore:any
21
21
  public coreStore:any
22
- private saveInterceptors: Record<string, Array<(ctx: { action: 'create'|'edit'; values: any; resource: any; resourceId: string; }) => Promise<{ ok: boolean; error?: string | null; extra?: object; }>>> = {};
23
-
24
- public list: {
25
- refresh(): Promise<{ error? : string }>;
26
- silentRefresh(): Promise<{ error? : string }>;
27
- silentRefreshRow(pk: any): Promise<{ error? : string }>;
28
- closeThreeDotsDropdown(): Promise<{ error? : string }>;
29
- closeUserMenuDropdown: () => void;
30
- setFilter: (filter: FilterParams) => void;
31
- updateFilter: (filter: FilterParams) => void;
32
- clearFilters: () => void;
33
- }
22
+ private saveInterceptors: Record<string, Array<Parameters<FrontendAPIInterface['registerSaveInterceptor']>[0]>> = {};
34
23
 
35
- public menu: {
36
- refreshMenuBadges: () => void;
37
- }
24
+ public list: FrontendAPIInterface['list'];
38
25
 
39
- public show: {
40
- refresh(): void;
41
- }
26
+ public menu: FrontendAPIInterface['menu'];
27
+
28
+ public show: FrontendAPIInterface['show'];
42
29
 
43
30
  closeUserMenuDropdown(): void {
44
31
  console.log('closeUserMenuDropdown')
@@ -70,9 +57,6 @@ class FrontendAPI implements FrontendAPIInterface {
70
57
  console.log('closeThreeDotsDropdown')
71
58
  return { error: 'Not implemented' }
72
59
  },
73
- closeUserMenuDropdown: () => {
74
- console.log('closeUserMenuDropdown')
75
- },
76
60
  setFilter: this.setListFilter.bind(this),
77
61
  updateFilter: this.updateListFilter.bind(this),
78
62
  clearFilters: this.clearListFilters.bind(this),
@@ -83,11 +67,15 @@ class FrontendAPI implements FrontendAPIInterface {
83
67
  console.log('show.refresh')
84
68
  }
85
69
  }
70
+
71
+ this.closeUserMenuDropdown = () => {
72
+ console.log('closeUserMenuDropdown')
73
+ };
86
74
  }
87
75
 
88
76
  registerSaveInterceptor(
89
- handler: (ctx: { action: 'create'|'edit'; values: any; resource: any; }) => Promise<{ ok: boolean; error?: string | null; extra?: object; }>,
90
- ): void {
77
+ handler: Parameters<FrontendAPIInterface['registerSaveInterceptor']>[0]
78
+ ): ReturnType<FrontendAPIInterface['registerSaveInterceptor']> {
91
79
  const rid = router.currentRoute.value?.params?.resourceId as string;
92
80
  if (!rid) {
93
81
  return;
@@ -98,7 +86,7 @@ class FrontendAPI implements FrontendAPIInterface {
98
86
  this.saveInterceptors[rid].push(handler);
99
87
  }
100
88
 
101
- async runSaveInterceptors(params: { action: 'create'|'edit'; values: any; resource: any; resourceId: string; }): Promise<{ ok: boolean; error?: string | null; extra?: object; }> {
89
+ async runSaveInterceptors(params: Parameters<FrontendAPIInterface['runSaveInterceptors']>[0]): ReturnType<FrontendAPIInterface['runSaveInterceptors']> {
102
90
  const list = this.saveInterceptors[params.resourceId] || [];
103
91
  const aggregatedExtra: Record<string, any> = {};
104
92
  for (const fn of list) {
@@ -120,7 +108,7 @@ class FrontendAPI implements FrontendAPIInterface {
120
108
  return { ok: true, extra: aggregatedExtra };
121
109
  }
122
110
 
123
- clearSaveInterceptors(resourceId?: string): void {
111
+ clearSaveInterceptors(resourceId?: Parameters<FrontendAPIInterface['clearSaveInterceptors']>[0]): ReturnType<FrontendAPIInterface['clearSaveInterceptors']> {
124
112
  if (resourceId) {
125
113
  delete this.saveInterceptors[resourceId];
126
114
  } else {
@@ -128,7 +116,7 @@ class FrontendAPI implements FrontendAPIInterface {
128
116
  }
129
117
  }
130
118
 
131
- confirm(params: ConfirmParams): Promise<boolean> {
119
+ confirm(params: Parameters<FrontendAPIInterface['confirm']>[0]): ReturnType<FrontendAPIInterface['confirm']> {
132
120
  return new Promise((resolve, reject) => {
133
121
  this.modalStore.setModalContent({
134
122
  content: params.message,
@@ -142,7 +130,7 @@ class FrontendAPI implements FrontendAPIInterface {
142
130
  })
143
131
  }
144
132
 
145
- alert(params: AlertParams): void | Promise<string> | string {
133
+ alert(params: Parameters<FrontendAPIInterface['alert']>[0]): ReturnType<FrontendAPIInterface['alert']> {
146
134
  const toats = {
147
135
  message: params.message,
148
136
  messageHtml: params.messageHtml,
@@ -162,14 +150,14 @@ class FrontendAPI implements FrontendAPIInterface {
162
150
  }
163
151
  }
164
152
 
165
- listFilterValidation(filter: FilterParams): boolean {
153
+ listFilterValidation(filter: Parameters<FrontendAPIInterface['list']['setFilter']>[0]): boolean {
166
154
  if(router.currentRoute.value.meta.type !== 'list'){
167
155
  throw new Error(`Cannot use ${this.setListFilter.name} filter on a list page`)
168
156
  }
169
157
  return true
170
158
  }
171
159
 
172
- setListFilter(filter: FilterParams): void {
160
+ setListFilter(filter: Parameters<FrontendAPIInterface['list']['setFilter']>[0]): ReturnType<FrontendAPIInterface['list']['setFilter']> {
173
161
  if(this.listFilterValidation(filter)){
174
162
  const existingFilterIndex = this.filtersStore.filters.findIndex((f: any) => {
175
163
  return f.field === filter.field && f.operator === filter.operator
@@ -43,7 +43,7 @@ const isIos = coreStore.isIos;
43
43
 
44
44
  const props = defineProps<{
45
45
  type: string,
46
- fullWidth: boolean,
46
+ fullWidth?: boolean,
47
47
  modelValue: string,
48
48
  suffix?: string,
49
49
  prefix?: string,
@@ -119,10 +119,12 @@ import { ref, computed, onMounted, onUnmounted, watch, nextTick,type PropType, t
119
119
  import { IconCaretDownSolid } from '@iconify-prerendered/vue-flowbite';
120
120
  import { useElementSize } from '@vueuse/core'
121
121
 
122
+ type ISingleSelectModelValue = string | number;
123
+
122
124
  const props = defineProps({
123
125
  options: Array,
124
126
  modelValue: {
125
- type: Array as PropType<(string | number)[] | (string | number)>,
127
+ type: Array as PropType<(ISingleSelectModelValue)[] | ISingleSelectModelValue>,
126
128
  default: () => [],
127
129
  },
128
130
  multiple: {
@@ -201,7 +203,7 @@ function updateFromProps() {
201
203
  selectedItems.value = [];
202
204
  }
203
205
  } else {
204
- selectedItems.value = props.options?.filter((item: any) => props.modelValue?.includes(item.value)) || [];
206
+ selectedItems.value = props.options?.filter((item: any) => (props.modelValue as (ISingleSelectModelValue)[])?.includes(item.value)) || [];
205
207
  }
206
208
  }
207
209
  }
@@ -364,7 +364,7 @@ function sortArrayData(data:any[], sortField?:string, dir:'asc'|'desc'='asc') {
364
364
  });
365
365
  }
366
366
 
367
- function tableRowClick(row) {
367
+ function tableRowClick(row: any) {
368
368
  emit("clickTableRow", row)
369
369
  }
370
370
  </script>
@@ -16,11 +16,11 @@
16
16
  :unmasked="unmasked"
17
17
  :deletable="!column.editReadonly"
18
18
  @update:modelValue="setCurrentValue(column.name, $event, arrayItemIndex)"
19
- @update:recordFieldValue="({ fieldName, fieldValue }: { fieldName: string; fieldValue: any }) => setCurrentValue(fieldName, fieldValue)"
19
+ @update:recordFieldValue="recordFieldValueUpdate"
20
20
  @update:unmasked="$emit('update:unmasked', column.name)"
21
21
  @update:inValidity="$emit('update:inValidity', { name: column.name, value: $event })"
22
22
  @update:emptiness="$emit('update:emptiness', { name: column.name, value: $event })"
23
- @delete="setCurrentValue(column.name, currentValues[column.name].filter((_: any, index: any) => index !== arrayItemIndex))"
23
+ @delete="deleteHandler(arrayItemIndex)"
24
24
  />
25
25
  </div>
26
26
  <div class="flex items-center">
@@ -48,7 +48,7 @@
48
48
  :columnOptions="columnOptions"
49
49
  :unmasked="unmasked"
50
50
  @update:modelValue="setCurrentValue(column.name, $event)"
51
- @update:recordFieldValue="({ fieldName, fieldValue }: { fieldName: string; fieldValue: any }) => setCurrentValue(fieldName, fieldValue)"
51
+ @update:recordFieldValue="recordFieldValueUpdate"
52
52
  @update:unmasked="$emit('update:unmasked', column.name)"
53
53
  @update:inValidity="$emit('update:inValidity', { name: column.name, value: $event })"
54
54
  @update:emptiness="$emit('update:emptiness', { name: column.name, value: $event })"
@@ -80,4 +80,12 @@
80
80
  await nextTick();
81
81
  arrayItemRefs.value[arrayItemRefs.value.length - 1].focus();
82
82
  }
83
+
84
+ function recordFieldValueUpdate({ fieldName, fieldValue }: { fieldName: string; fieldValue: any }) {
85
+ props.setCurrentValue(fieldName, fieldValue);
86
+ }
87
+
88
+ function deleteHandler(arrayItemIndex: number | string) {
89
+ props.setCurrentValue(props.column.name, props.currentValues[props.column.name].filter((_: any, index: any) => index !== arrayItemIndex));
90
+ }
83
91
  </script>
@@ -57,14 +57,14 @@
57
57
  {{ $t('Delete item') }}
58
58
  </button>
59
59
  </template>
60
- <div v-for="action in (resourceOptions.actions ?? []).filter(a => a.showIn?.listThreeDotsMenu)" :key="action.id" >
60
+ <div v-for="action in (resourceOptions.actions ?? []).filter((a: AdminForthActionInput) => a.showIn?.listThreeDotsMenu)" :key="action.id" >
61
61
  <button class="flex text-nowrap p-1 hover:bg-gray-100 dark:hover:bg-gray-800 w-full text-left px-4 py-2 text-sm text-gray-700 dark:text-gray-300" @click="() => { startCustomAction(action.id, record); showMenu = false; }">
62
62
  <component
63
63
  :is="action.customComponent ? getCustomComponent(action.customComponent) : CallActionWrapper"
64
64
  :meta="action.customComponent?.meta"
65
65
  :row="record"
66
- :resource="resource"
67
- :adminUser="adminUser"
66
+ :resource="coreStore.resource"
67
+ :adminUser="coreStore.adminUser"
68
68
  @callAction="(payload? : Object) => startCustomAction(action.id, record, payload)"
69
69
  >
70
70
  <component
@@ -79,8 +79,8 @@
79
79
  <template v-if="customActionIconsThreeDotsMenuItems">
80
80
  <component
81
81
  v-for="c in customActionIconsThreeDotsMenuItems"
82
- :is="getCustomComponent(c)"
83
- :meta="c.meta"
82
+ :is="getCustomComponent(formatComponent(c))"
83
+ :meta="formatComponent(c).meta"
84
84
  :resource="coreStore.resource"
85
85
  :adminUser="coreStore.adminUser"
86
86
  :record="record"
@@ -100,9 +100,10 @@ import {
100
100
  IconDotsHorizontalOutline
101
101
  } from '@iconify-prerendered/vue-flowbite';
102
102
  import { onMounted, onBeforeUnmount, ref, nextTick, watch } from 'vue';
103
- import { getIcon, getCustomComponent } from '@/utils';
103
+ import { getIcon, getCustomComponent, formatComponent } from '@/utils';
104
104
  import { useCoreStore } from '@/stores/core';
105
105
  import CallActionWrapper from '@/components/CallActionWrapper.vue'
106
+ import { type AdminForthActionInput, type AdminForthComponentDeclaration, type AdminForthComponentDeclarationFull } from '@/types/Common';
106
107
 
107
108
  const coreStore = useCoreStore();
108
109
  const showMenu = ref(false);
@@ -113,11 +114,11 @@ const menuStyles = ref<Record<string, string>>({});
113
114
  const props = defineProps<{
114
115
  resourceOptions: any;
115
116
  record: any;
116
- customActionIconsThreeDotsMenuItems: any[];
117
+ customActionIconsThreeDotsMenuItems: AdminForthComponentDeclaration[];
117
118
  resourceId: string;
118
119
  deleteRecord: (record: any) => void;
119
120
  updateRecords: () => void;
120
- startCustomAction: (actionId: string, record: any) => void;
121
+ startCustomAction: (actionId: string, row: any, extraData?: Record<string, any>) => void;
121
122
  }>();
122
123
 
123
124
  onMounted(() => {
@@ -103,12 +103,12 @@
103
103
 
104
104
  <component
105
105
  v-for="(row, rowI) in rowsToRender"
106
- :is="tableRowReplaceInjection ? getCustomComponent(tableRowReplaceInjection) : 'tr'"
106
+ :is="tableRowReplaceInjection ? getCustomComponent(formatComponent(tableRowReplaceInjection)) : 'tr'"
107
107
  :key="`row_${row._primaryKeyValue}`"
108
108
  :record="row"
109
109
  :resource="resource"
110
110
  :adminUser="coreStore.adminUser"
111
- :meta="tableRowReplaceInjection ? tableRowReplaceInjection.meta : undefined"
111
+ :meta="tableRowReplaceInjection ? formatComponent(tableRowReplaceInjection).meta : undefined"
112
112
  @click="onClick($event, row)"
113
113
  ref="rowRefs"
114
114
  class="list-table-body-row bg-lightListTable dark:bg-darkListTable border-lightListBorder dark:border-gray-700 hover:bg-lightListTableRowHover dark:hover:bg-darkListTableRowHover"
@@ -203,17 +203,17 @@
203
203
  :key="action.id"
204
204
  >
205
205
  <component
206
- :is="action.customComponent ? getCustomComponent(action.customComponent) : CallActionWrapper"
207
- :meta="action.customComponent?.meta"
206
+ v-if="action.customComponent"
207
+ :is="action.customComponent ? getCustomComponent(formatComponent(action.customComponent)) : CallActionWrapper"
208
+ :meta="formatComponent(action.customComponent).meta"
208
209
  :row="row"
209
210
  :resource="resource"
210
- :adminUser="adminUser"
211
- @callAction="(payload? : Object) => startCustomAction(action.id, row, payload)"
211
+ :adminUser="coreStore.adminUser"
212
+ @callAction="(payload? : Object) => startCustomAction(action.id as string | number, row, payload)"
212
213
  >
213
214
  <button
214
215
  type="button"
215
216
  class="border border-gray-300 dark:border-gray-700 dark:border-opacity-0 border-opacity-0 hover:border-opacity-100 dark:hover:border-opacity-100 rounded-md hover:bg-gray-100 dark:hover:bg-gray-800 cursor-pointer"
216
- :disabled="rowActionLoadingStates?.[action.id]"
217
217
  >
218
218
  <component
219
219
  v-if="action.icon"
@@ -236,7 +236,7 @@
236
236
  :deleteRecord="deleteRecord"
237
237
  :resourceId="resource.resourceId"
238
238
  :startCustomAction="startCustomAction"
239
- :customActionIconsThreeDotsMenuItems="customActionIconsThreeDotsMenuItems"
239
+ :customActionIconsThreeDotsMenuItems="customActionIconsThreeDotsMenuItems ?? []"
240
240
  />
241
241
  </div>
242
242
 
@@ -347,7 +347,7 @@ import { computed, onMounted, ref, watch, useTemplateRef, nextTick, type Ref } f
347
347
  import { callAdminForthApi } from '@/utils';
348
348
  import { useI18n } from 'vue-i18n';
349
349
  import ValueRenderer from '@/components/ValueRenderer.vue';
350
- import { getCustomComponent } from '@/utils';
350
+ import { getCustomComponent, formatComponent } from '@/utils';
351
351
  import { useCoreStore } from '@/stores/core';
352
352
  import { showSuccesTost, showErrorTost } from '@/composables/useFrontendApi';
353
353
  import SkeleteLoader from '@/components/SkeleteLoader.vue';
@@ -360,7 +360,7 @@ import {
360
360
  } from '@iconify-prerendered/vue-flowbite';
361
361
  import router from '@/router';
362
362
  import { Tooltip } from '@/afcl';
363
- import type { AdminForthResourceCommon, AdminForthResourceColumnCommon, AdminForthComponentDeclarationFull } from '@/types/Common';
363
+ import type { AdminForthResourceCommon, AdminForthResourceColumnCommon, AdminForthComponentDeclarationFull, AdminForthComponentDeclaration } from '@/types/Common';
364
364
  import { useAdminforth } from '@/adminforth';
365
365
  import Checkbox from '@/afcl/Checkbox.vue';
366
366
  import ListActionsThreeDots from '@/components/ListActionsThreeDots.vue';
@@ -383,8 +383,8 @@ const props = defineProps<{
383
383
  containerHeight?: number,
384
384
  itemHeight?: number,
385
385
  bufferSize?: number,
386
- customActionIconsThreeDotsMenuItems?: any[]
387
- tableRowReplaceInjection?: AdminForthComponentDeclarationFull,
386
+ customActionIconsThreeDotsMenuItems?: AdminForthComponentDeclaration[]
387
+ tableRowReplaceInjection?: AdminForthComponentDeclaration,
388
388
  isVirtualScrollEnabled: boolean
389
389
  }>();
390
390
 
@@ -414,7 +414,7 @@ const sort: Ref<Array<{field: string, direction: string}>> = ref([]);
414
414
  const showListActionsThreeDots = computed(() => {
415
415
  return props.resource?.options?.actions?.some(a => a.showIn?.listThreeDotsMenu) // show if any action is set to show in three dots menu
416
416
  || (props.customActionIconsThreeDotsMenuItems && props.customActionIconsThreeDotsMenuItems.length > 0) // or if there are custom action icons for three dots menu
417
- || !props.resource?.options.baseActionsAsQuickIcons // or if there is no baseActionsAsQuickIcons
417
+ || !props.resource?.options?.baseActionsAsQuickIcons // or if there is no baseActionsAsQuickIcons
418
418
  || (props.resource?.options.baseActionsAsQuickIcons && props.resource?.options.baseActionsAsQuickIcons.length < 3) // if there all 3 base actions are shown as quick icons - hide three dots icon
419
419
  })
420
420
 
@@ -609,7 +609,7 @@ async function deleteRecord(row: any) {
609
609
 
610
610
  const actionLoadingStates = ref<Record<string | number, boolean>>({});
611
611
 
612
- async function startCustomAction(actionId: string, row: any, extraData: Record<string, any> = {}) {
612
+ async function startCustomAction(actionId: string | number, row: any, extraData: Record<string, any> = {}) {
613
613
 
614
614
  actionLoadingStates.value[actionId] = true;
615
615
 
@@ -52,7 +52,7 @@
52
52
  </div>
53
53
  </div>
54
54
 
55
- <div v-if="coreStore.config.defaultUserExists && !isLocalhost" class="p-4 mb-4 text-white rounded-lg bg-red-700/80 fill-white text-sm">
55
+ <div v-if="coreStore?.config?.defaultUserExists && !isLocalhost" class="p-4 mb-4 text-white rounded-lg bg-red-700/80 fill-white text-sm">
56
56
  <IconExclamationCircleOutline class="inline-block align-text-bottom mr-0,5 w-5 h-5" />
57
57
  Default user <strong>"adminforth"</strong> detected. Delete it and create your own account.
58
58
  </div>
@@ -1,5 +1,5 @@
1
1
  <template >
2
- <div class="relative" v-if="threeDotsDropdownItems?.length || customActions?.length || (bulkActions?.some((action: AdminForthBulkActionCommon) => action.showInThreeDotsDropdown))">
2
+ <div class="relative" v-if="threeDotsDropdownItems?.length || customActions?.length || (bulkActions?.some((action: AdminForthBulkActionFront) => action.showInThreeDotsDropdown))">
3
3
  <button
4
4
  ref="buttonTriggerRef"
5
5
  @click="toggleDropdownVisibility"
@@ -46,8 +46,9 @@
46
46
  <li v-for="action in customActions" :key="action.id">
47
47
  <div class="wrapper">
48
48
  <component
49
- :is="(action.customComponent && getCustomComponent(action.customComponent)) || CallActionWrapper"
50
- :meta="action.customComponent?.meta"
49
+ v-if="action.customComponent"
50
+ :is="(action.customComponent && getCustomComponent(formatComponent(action.customComponent))) || CallActionWrapper"
51
+ :meta="formatComponent(action.customComponent).meta"
51
52
  @callAction="(payload? : Object) => handleActionClick(action, payload)"
52
53
  >
53
54
  <a @click.prevent class="block px-4 py-2 hover:text-lightThreeDotsMenuBodyTextHover hover:bg-lightThreeDotsMenuBodyBackgroundHover dark:hover:bg-darkThreeDotsMenuBodyBackgroundHover dark:hover:text-darkThreeDotsMenuBodyTextHover">
@@ -88,14 +89,14 @@
88
89
 
89
90
 
90
91
  <script setup lang="ts">
91
- import { getCustomComponent, getIcon } from '@/utils';
92
+ import { getCustomComponent, getIcon, formatComponent } from '@/utils';
92
93
  import { useCoreStore } from '@/stores/core';
93
94
  import { useAdminforth } from '@/adminforth';
94
95
  import { callAdminForthApi } from '@/utils';
95
96
  import { useRoute, useRouter } from 'vue-router';
96
97
  import CallActionWrapper from '@/components/CallActionWrapper.vue'
97
98
  import { ref, type ComponentPublicInstance, onMounted, onUnmounted } from 'vue';
98
- import type { AdminForthBulkActionCommon, AdminForthComponentDeclarationFull } from '@/types/Common';
99
+ import type { AdminForthActionFront, AdminForthBulkActionFront, AdminForthComponentDeclarationFull } from '@/types/Common';
99
100
  import type { AdminForthActionInput } from '@/types/Back';
100
101
 
101
102
  const { list, alert} = useAdminforth();
@@ -109,8 +110,8 @@ const buttonTriggerRef = ref<HTMLElement | null>(null);
109
110
 
110
111
  const props = defineProps({
111
112
  threeDotsDropdownItems: Array<AdminForthComponentDeclarationFull>,
112
- customActions: Array<AdminForthActionInput>,
113
- bulkActions: Array<AdminForthBulkActionCommon>,
113
+ customActions: Array<AdminForthActionFront>,
114
+ bulkActions: Array<AdminForthBulkActionFront>,
114
115
  checkboxes: Array,
115
116
  updateList: {
116
117
  type: Function,
@@ -51,6 +51,6 @@ export function initI18n(app: ReturnType<typeof createApp>) {
51
51
  },
52
52
  });
53
53
  app.use(i18n);
54
- i18nInstance = i18n
54
+ i18nInstance = i18n as typeof i18nInstance
55
55
  return i18n
56
56
  }
@@ -4,9 +4,11 @@ import { callAdminForthApi } from '@/utils';
4
4
  import websocket from '@/websocket';
5
5
  import { useAdminforth } from '@/adminforth';
6
6
 
7
- import type { AdminForthResourceCommon, AdminForthResourceColumnCommon, GetBaseConfigResponse, ResourceVeryShort, AdminUser, UserData, AdminForthConfigMenuItem, AdminForthConfigForFrontend } from '@/types/Common';
7
+ import type { AdminForthResourceCommon, AdminForthResourceColumnCommon, GetBaseConfigResponse, ResourceVeryShort, AdminUser, UserData, AdminForthConfigMenuItem, AdminForthConfigForFrontend, AdminForthResourceFrontend } from '@/types/Common';
8
8
  import type { Ref } from 'vue'
9
9
 
10
+
11
+
10
12
  export const useCoreStore = defineStore('core', () => {
11
13
  const { alert } = useAdminforth();
12
14
  const resourceById: Ref<Record<string, ResourceVeryShort>> = ref({});
@@ -15,7 +17,7 @@ export const useCoreStore = defineStore('core', () => {
15
17
  const menu: Ref<AdminForthConfigMenuItem[]> = ref([]);
16
18
  const config: Ref<AdminForthConfigForFrontend | null> = ref(null);
17
19
  const record: Ref<any | null> = ref({});
18
- const resource: Ref<AdminForthResourceCommon | null> = ref(null);
20
+ const resource: Ref<AdminForthResourceFrontend | null> = ref(null);
19
21
  const userData: Ref<UserData | null> = ref(null);
20
22
  const isResourceFetching = ref(false);
21
23
  const isInternetError = ref(false);
@@ -1641,7 +1641,7 @@ export interface AdminForthConfigCustomization extends Omit<AdminForthInputConfi
1641
1641
 
1642
1642
  loginPageInjections: {
1643
1643
  underInputs: Array<AdminForthComponentDeclarationFull>,
1644
- underLoginButton?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
1644
+ underLoginButton: Array<AdminForthComponentDeclarationFull>,
1645
1645
  panelHeader: Array<AdminForthComponentDeclarationFull>,
1646
1646
  },
1647
1647
 
@@ -1829,7 +1829,7 @@ export type AllowedActions = {
1829
1829
  /**
1830
1830
  * General options for resource.
1831
1831
  */
1832
- export interface ResourceOptionsInput extends Omit<NonNullable<AdminForthResourceInputCommon['options']>, 'allowedActions' | 'bulkActions'> {
1832
+ export interface ResourceOptionsInput extends Omit<NonNullable<AdminForthResourceInputCommon['options']>, 'allowedActions' | 'bulkActions' | 'actions'> {
1833
1833
 
1834
1834
  /**
1835
1835
  * Custom bulk actions list. Bulk actions available in list view when user selects multiple records by
@@ -314,6 +314,25 @@ export type FieldGroup = {
314
314
  noTitle?: boolean;
315
315
  };
316
316
 
317
+ export interface AdminForthActionFront extends Omit<AdminForthActionInput, 'id'> {
318
+ id: string;
319
+ }
320
+
321
+ export interface AdminForthBulkActionFront extends Omit<AdminForthBulkActionCommon, 'id'> {
322
+ id: string,
323
+ }
324
+
325
+ type AdminforthOptionsCommon = NonNullable<AdminForthResourceCommon['options']>;
326
+
327
+ export interface AdminForthOptionsForFrontend extends Omit<AdminforthOptionsCommon, 'actions' | 'bulkActions'> {
328
+ actions?: AdminForthActionFront[],
329
+ bulkActions?: AdminForthBulkActionFront[],
330
+ }
331
+
332
+ export interface AdminForthResourceFrontend extends Omit<AdminForthResourceCommon, 'options'> {
333
+ options: AdminForthOptionsForFrontend;
334
+ }
335
+
317
336
  /**
318
337
  * Resource describes one table or collection in database.
319
338
  * AdminForth generates set of pages for 'list', 'show', 'edit', 'create', 'filter' operations for each resource.
@@ -361,17 +380,17 @@ export interface AdminForthResourceInputCommon {
361
380
  recordLabel?: (item: any) => string,
362
381
 
363
382
 
364
- /**
365
- * If true, user will not see warning about unsaved changes when tries to leave edit or create page with unsaved changes.
366
- * default is false
367
- */
368
- dontShowWarningAboutUnsavedChanges?: boolean,
369
383
 
370
384
  /**
371
385
  * General options for resource.
372
386
  */
373
387
  options?: {
374
388
 
389
+ /**
390
+ * If true, user will not see warning about unsaved changes when tries to leave edit or create page with unsaved changes.
391
+ * default is false
392
+ */
393
+ dontShowWarningAboutUnsavedChanges?: boolean,
375
394
 
376
395
  /**
377
396
  * Show quick action icons for base actions (show, edit, delete) in list view.
@@ -1172,6 +1191,7 @@ export interface AdminForthConfigForFrontend {
1172
1191
  loginPageInjections: {
1173
1192
  underInputs: Array<AdminForthComponentDeclaration>,
1174
1193
  panelHeader: Array<AdminForthComponentDeclaration>,
1194
+ underLoginButton: Array<AdminForthComponentDeclaration>,
1175
1195
  },
1176
1196
  rememberMeDuration: string,
1177
1197
  showBrandNameInSidebar: boolean,
@@ -144,7 +144,7 @@ export interface FrontendAPIInterface {
144
144
  /**
145
145
  * Run save interceptors for a specific resource or all resources if no resourceId is provided
146
146
  */
147
- runSaveInterceptors(params: { action: 'create'|'edit'; values: any; resource: any; resourceId: string; }): Promise<{ ok: boolean; error?: string | null; extra?: object; }>;
147
+ runSaveInterceptors(params: { action: 'create'|'edit'; values: any; resource: any; resourceId: string; }): Promise<{ ok: boolean; error?: string | null; extra?: any; }>;
148
148
 
149
149
  /**
150
150
  * Clear save interceptors for a specific resource or all resources if no resourceId is provided
@@ -152,6 +152,11 @@ export interface FrontendAPIInterface {
152
152
  * @param resourceId - The resource ID to clear interceptors for
153
153
  */
154
154
  clearSaveInterceptors(resourceId?: string): void;
155
+
156
+ /**
157
+ * Register a save interceptor for a specific resource
158
+ */
159
+ registerSaveInterceptor(handler: (ctx: { action: 'create'|'edit'; values: any; resource: any; }) => Promise<{ ok: boolean; error?: string | null; extra?: any; }>): void;
155
160
  }
156
161
 
157
162
  export type ConfirmParams = {