adminforth 1.3.56-next.9 → 1.3.57-next.0

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 (48) hide show
  1. package/dist/auth.d.ts.map +1 -1
  2. package/dist/auth.js +8 -4
  3. package/dist/auth.js.map +1 -1
  4. package/dist/basePlugin.d.ts +5 -0
  5. package/dist/basePlugin.d.ts.map +1 -1
  6. package/dist/basePlugin.js +6 -1
  7. package/dist/basePlugin.js.map +1 -1
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +10 -0
  10. package/dist/index.js.map +1 -1
  11. package/dist/modules/codeInjector.d.ts.map +1 -1
  12. package/dist/modules/codeInjector.js +3 -1
  13. package/dist/modules/codeInjector.js.map +1 -1
  14. package/dist/modules/configValidator.d.ts.map +1 -1
  15. package/dist/modules/configValidator.js +15 -2
  16. package/dist/modules/configValidator.js.map +1 -1
  17. package/dist/modules/styles.js +2 -2
  18. package/dist/modules/styles.js.map +1 -1
  19. package/dist/servers/express.js +2 -1
  20. package/dist/servers/express.js.map +1 -1
  21. package/dist/spa/index.html +2 -2
  22. package/dist/spa/package-lock.json +16 -0
  23. package/dist/spa/package.json +1 -0
  24. package/dist/spa/src/App.vue +13 -31
  25. package/dist/spa/src/components/AfTooltip.vue +43 -0
  26. package/dist/spa/src/components/BreadcrumbsWithButtons.vue +3 -4
  27. package/dist/spa/src/components/CustomDatePicker.vue +2 -2
  28. package/dist/spa/src/components/Dropdown.vue +2 -2
  29. package/dist/spa/src/components/GroupsTable.vue +170 -0
  30. package/dist/spa/src/components/ResourceForm.vue +70 -156
  31. package/dist/spa/src/components/ResourceListTable.vue +123 -124
  32. package/dist/spa/src/renderers/CompactField.vue +45 -0
  33. package/dist/spa/src/renderers/CompactUUID.vue +12 -15
  34. package/dist/spa/src/renderers/CountryFlag.vue +16 -21
  35. package/dist/spa/src/renderers/HumanNumber.vue +11 -16
  36. package/dist/spa/src/renderers/RelativeTime.vue +41 -0
  37. package/dist/spa/src/renderers/URL.vue +18 -0
  38. package/dist/spa/src/stores/user.ts +0 -5
  39. package/dist/spa/src/types/AdminForthConfig.ts +25 -11
  40. package/dist/spa/src/views/CreateView.vue +1 -1
  41. package/dist/spa/src/views/EditView.vue +1 -1
  42. package/dist/spa/src/views/ListView.vue +3 -8
  43. package/dist/spa/src/views/ResourceParent.vue +36 -2
  44. package/dist/spa/src/views/ShowView.vue +16 -15
  45. package/dist/types/AdminForthConfig.d.ts +24 -11
  46. package/dist/types/AdminForthConfig.d.ts.map +1 -1
  47. package/dist/types/AdminForthConfig.js.map +1 -1
  48. package/package.json +1 -1
@@ -1,22 +1,20 @@
1
1
  <template>
2
- <span class="flex items-center"
3
- :data-tooltip-target="visualValue && `tooltip-${id}`"
4
- data-tooltip-placement="top"
5
- >
6
- {{ visualValue }} <IconFileCopyAltSolid @click.stop="copyToCB" class="w-5 h-5 text-lightPrimary dark:text-darkPrimary" v-if="visualValue"/>
7
-
8
- <div :id="`tooltip-${id}`" role="tooltip" v-if="visualValue"
9
- class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-white transition-opacity duration-300 bg-gray-900 rounded-lg shadow-sm opacity-0 tooltip dark:bg-gray-700">
2
+ <AfTooltip>
3
+ <span class="flex items-center">
4
+ {{ visualValue }} <IconFileCopyAltSolid @click.stop="copyToCB" class="w-5 h-5 text-lightPrimary dark:text-darkPrimary" v-if="visualValue"/>
5
+ </span>
6
+ <template #tooltip v-if="visualValue">
10
7
  {{ props.record[props.column.name] }}
11
- <div class="tooltip-arrow" data-popper-arrow></div>
12
- </div>
13
- </span>
8
+ </template>
9
+ </AfTooltip>
10
+
14
11
  </template>
15
12
 
16
13
  <script setup>
17
- import { computed, ref, onMounted } from 'vue';
14
+ import { computed, ref, onMounted, nextTick } from 'vue';
18
15
  import { IconFileCopyAltSolid } from '@iconify-prerendered/vue-flowbite';
19
- import { initFlowbite } from 'flowbite';
16
+ import AfTooltip from '@/components/AfTooltip.vue';
17
+
20
18
 
21
19
  const visualValue = computed(() => {
22
20
  // if lenght is more then 8, show only first 4 and last 4 characters, ... in the middle
@@ -41,8 +39,7 @@ function copyToCB() {
41
39
 
42
40
  onMounted(async () => {
43
41
  id.value = Math.random().toString(36).substring(7);
44
- await new Promise(resolve => setTimeout(resolve, 0));
45
- initFlowbite();
42
+ await nextTick();
46
43
  });
47
44
 
48
45
  </script>
@@ -1,31 +1,25 @@
1
1
  <template>
2
- <span class="flex items-center">
3
- <span
4
- :class="{[`fi-${countryIsoLow}`]: true, 'flag-icon': countryName}"
5
- :data-tooltip-target="`tooltip-${id}`"
6
- ></span>
7
-
8
- <span v-if="meta.showCountryName" class="ms-2">{{ countryName }}</span>
9
-
10
- <div
11
- v-if="!meta.showCountryName && countryName"
12
- :id="`tooltip-${id}`" role="tooltip"
13
- class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-white transition-opacity duration-300 bg-gray-900 rounded-lg shadow-sm opacity-0 tooltip dark:bg-gray-700"
14
- >
15
- {{ countryName }}
16
- <div class="tooltip-arrow" data-popper-arrow></div>
17
- </div>
18
- </span>
19
-
2
+ <AfTooltip>
3
+ <span class="flex items-center">
4
+ <span
5
+ :class="{[`fi-${countryIsoLow}`]: true, 'flag-icon': countryName}"
6
+ ></span>
7
+ <span v-if="meta.showCountryName" class="ms-2">{{ countryName }}</span>
8
+ </span>
9
+
10
+ <template v-if="shouldShowTooltip" #tooltip>
11
+ {{ countryName }}
12
+ </template>
13
+ </AfTooltip>
20
14
  </template>
21
15
 
22
16
  <script setup>
23
17
 
24
18
  import { computed, ref, onMounted } from 'vue';
25
- import { initFlowbite } from 'flowbite';
26
19
  import 'flag-icons/css/flag-icons.min.css';
27
20
  import isoCountries from 'i18n-iso-countries';
28
21
  import enLocal from 'i18n-iso-countries/langs/en.json';
22
+ import AfTooltip from '@/components/AfTooltip.vue';
29
23
 
30
24
  isoCountries.registerLocale(enLocal);
31
25
 
@@ -33,11 +27,12 @@ const props = defineProps(['column', 'record', 'meta', 'resource', 'adminUser'])
33
27
 
34
28
  const id = ref();
35
29
 
30
+ const shouldShowTooltip = computed(() => {
31
+ return !props.meta.showCountryName && countryName.value;
32
+ });
36
33
 
37
34
  onMounted(async () => {
38
35
  id.value = Math.random().toString(36).substring(7);
39
- await new Promise(resolve => setTimeout(resolve, 0));
40
- initFlowbite();
41
36
  });
42
37
 
43
38
  const countryIsoLow = computed(() => {
@@ -1,19 +1,15 @@
1
1
  <template>
2
- <span class="flex items-center"
3
- :data-tooltip-target="formattedValue && `tooltip-${id}`"
4
- data-tooltip-placement="left">
2
+ <AfTooltip class="flex items-center" >
5
3
  {{ formattedValue }}
6
- <div :id="`tooltip-${id}`" role="tooltip" v-if="formattedValue"
7
- class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-white transition-opacity duration-300 bg-gray-900 rounded-lg shadow-sm opacity-0 tooltip dark:bg-gray-700">
4
+ <template #tooltip v-if="formattedValue">
8
5
  {{ formattedTooltipValue }}
9
- <div class="tooltip-arrow" data-popper-arrow></div>
10
- </div>
11
- </span>
6
+ </template>
7
+ </AfTooltip>
12
8
  </template>
13
9
 
14
10
  <script setup>
15
- import { computed, ref, onMounted } from 'vue';
16
- import { initFlowbite } from 'flowbite';
11
+ import { computed, ref, onMounted, nextTick } from 'vue';
12
+ import AfTooltip from '@/components/AfTooltip.vue';
17
13
 
18
14
  const props = defineProps(['column', 'record']);
19
15
  const id = ref();
@@ -50,13 +46,12 @@
50
46
  return new Intl.NumberFormat(locale).format(num);
51
47
  }
52
48
 
53
- function formatNumberUsingIntl(num, locale) {
54
- return new Intl.NumberFormat(locale).format(num);
55
- }
49
+ function formatNumberUsingIntl(num, locale) {
50
+ return new Intl.NumberFormat(locale).format(num);
51
+ }
56
52
 
57
53
  onMounted(async () => {
58
54
  id.value = Math.random().toString(36).substring(7);
59
- await new Promise(resolve => setTimeout(resolve, 0));
60
- initFlowbite();
55
+ await nextTick();
61
56
  });
62
- </script>
57
+ </script>
@@ -0,0 +1,41 @@
1
+ <template>
2
+ <AfTooltip class="flex items-center">
3
+ {{ relativeTime }}
4
+ <template #tooltip v-if="relativeTime">
5
+ {{ fullTime }}
6
+ </template>
7
+ </AfTooltip>
8
+ </template>
9
+
10
+ <script setup>
11
+ import { computed, ref, onMounted } from 'vue';
12
+ import AfTooltip from '@/components/AfTooltip.vue';
13
+ import en from 'javascript-time-ago/locale/en';
14
+ import TimeAgo from 'javascript-time-ago';
15
+ import dayjs from 'dayjs';
16
+
17
+ const id = ref();
18
+
19
+ TimeAgo.addLocale(en);
20
+
21
+ const props = defineProps(['column', 'record']);
22
+
23
+ const userLocale = ref(navigator.language || 'en-US');
24
+ const timeAgoFormatter = new TimeAgo(userLocale.value);
25
+ const relativeTime = computed(() => {
26
+ const value = props.record[props.column.name];
27
+ const date = new Date(value);
28
+ return timeAgoFormatter.format(date);
29
+ });
30
+
31
+ const fullTime = computed(() => {
32
+ const value = props.record[props.column.name];
33
+ const date = dayjs(new Date(value));
34
+ return date.format('DD MMM HH:mm');
35
+ });
36
+
37
+ onMounted(async () => {
38
+ id.value = Math.random().toString(36).substring(7);
39
+ });
40
+
41
+ </script>
@@ -0,0 +1,18 @@
1
+ <template>
2
+ <a v-if='record[column.name]' :href="record[column.name]"
3
+ class="text-blue-500 hover:underline overflow-ellipsis overflow-hidden whitespace-nowrap inline-block max-w-full"
4
+ >{{ record[column.name] }}</a>
5
+ </template>
6
+
7
+ <script setup>
8
+
9
+ defineProps({
10
+ record: Object,
11
+ resource: Object,
12
+ adminUser: Object,
13
+ meta: Object,
14
+ column: Object
15
+ });
16
+
17
+
18
+ </script>
@@ -1,8 +1,6 @@
1
1
  import { ref } from 'vue';
2
2
  import { defineStore } from 'pinia';
3
3
  import { callAdminForthApi } from '@/utils';
4
- import { initFlowbite } from 'flowbite'
5
- import { useRouter } from 'vue-router';
6
4
  import { useCoreStore } from './core';
7
5
  import router from '@/router';
8
6
 
@@ -27,9 +25,6 @@ export const useUserStore = defineStore('user', () => {
27
25
  await router.push('/');
28
26
  await router.isReady();
29
27
  await coreStore.fetchMenuAndResource();
30
- setTimeout(() => {
31
- initFlowbite();
32
- });
33
28
  }
34
29
 
35
30
  async function logout() {
@@ -916,13 +916,13 @@ export type AdminForthResource = {
916
916
  hooks?: {
917
917
  show?: {
918
918
  /**
919
- * Typical usecases:
919
+ * Typical use-cases:
920
920
  * - request additional data from database before returning to frontend for soft-join
921
921
  */
922
922
  beforeDatasourceRequest?: BeforeDataSourceRequestFunction | Array<BeforeDataSourceRequestFunction>,
923
923
 
924
924
  /**
925
- * Typical usecases:
925
+ * Typical use-cases:
926
926
  * - Transform value for some field for record returned from database before returning to frontend (minimize, sanitize, etc)
927
927
  * - If some-why you can't use `backendOnly` you can cleanup sensitive fields here
928
928
  * - Attach additional data to record before returning to frontend
@@ -931,21 +931,22 @@ export type AdminForthResource = {
931
931
  },
932
932
  list?: {
933
933
  /**
934
- * Typical usecases:
934
+ * Typical use-cases:
935
935
  * - add additional filters in addition to what user selected before fetching data from database.
936
936
  * - same as hooks.show.beforeDatasourceRequest
937
937
  */
938
938
  beforeDatasourceRequest?: BeforeDataSourceRequestFunction | Array<BeforeDataSourceRequestFunction>,
939
939
 
940
940
  /**
941
- * Typical usecases:
942
- * - Same as hooks.show.afterDatasourceResponse but applied for each record in list
941
+ * Typical use-cases:
942
+ * - Same as hooks.show.afterDatasourceResponse but applied for all records returned from database for
943
+ * showing in list view, e.g. add new field to each record in list view
943
944
  */
944
945
  afterDatasourceResponse?: AfterDataSourceResponseFunction | Array<AfterDataSourceResponseFunction>,
945
946
  },
946
947
  create?: {
947
948
  /**
948
- * Typical usecases:
949
+ * Typical use-cases:
949
950
  * - Validate record before saving to database and interrupt execution if validation failed (`allowedActions.create` should be preferred in most cases)
950
951
  * - fill-in adminUser as creator of record
951
952
  * - Attach additional data to record before saving to database (mostly fillOnCreate should be used instead)
@@ -953,32 +954,32 @@ export type AdminForthResource = {
953
954
  beforeSave?: BeforeSaveFunction | Array<BeforeSaveFunction>,
954
955
 
955
956
  /**
956
- * Typical usecases:
957
+ * Typical use-cases:
957
958
  * - Initiate some trigger after record saved to database (e.g sync to another datasource)
958
959
  */
959
960
  afterSave?: AfterSaveFunction | Array<AfterSaveFunction>,
960
961
  },
961
962
  edit?: {
962
963
  /**
963
- * Typical usecases:
964
+ * Typical use-cases:
964
965
  * - Same as hooks.create.beforeSave but for edit page
965
966
  */
966
967
  beforeSave?: BeforeSaveFunction | Array<BeforeSaveFunction>,
967
968
 
968
969
  /**
969
- * Typical usecases:
970
+ * Typical use-cases:
970
971
  * - Same as hooks.create.afterSave but for edit page
971
972
  */
972
973
  afterSave?: AfterSaveFunction | Array<AfterSaveFunction>,
973
974
  },
974
975
  delete?: {
975
976
  /**
976
- * Typical usecases:
977
+ * Typical use-cases:
977
978
  * - Validate that record can be deleted and interrupt execution if validation failed (`allowedActions.delete` should be preferred in most cases)
978
979
  */
979
980
  beforeSave?: BeforeSaveFunction | Array<BeforeSaveFunction>,
980
981
  /**
981
- * Typical usecases:
982
+ * Typical use-cases:
982
983
  * - Initiate some trigger after record deleted from database (e.g sync to another datasource)
983
984
  */
984
985
  afterSave?: BeforeSaveFunction | Array<BeforeSaveFunction>,
@@ -1043,6 +1044,14 @@ export type AdminForthResource = {
1043
1044
  */
1044
1045
  allowedActions?: AllowedActions,
1045
1046
 
1047
+ /**
1048
+ * Allows to make groups of columns in create/edit resource pages.
1049
+ */
1050
+ createEditGroups?: {
1051
+ groupName: string;
1052
+ columns: string[];
1053
+ }[];
1054
+
1046
1055
  /**
1047
1056
  * Page size for list view
1048
1057
  */
@@ -1293,6 +1302,11 @@ export type AdminForthConfig = {
1293
1302
  */
1294
1303
  brandName?: string,
1295
1304
 
1305
+ /**
1306
+ * Slug which will be used on tech side e.g. to store cookies separately.
1307
+ * Created automatically from brandName if not set.
1308
+ */
1309
+ _brandNameSlug?: string,
1296
1310
 
1297
1311
  /**
1298
1312
  * Whether to show brand name in sidebar
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div class="relative">
2
+ <div class="relative w-full">
3
3
 
4
4
  <component
5
5
  v-for="c in coreStore?.resourceOptions?.pageInjections?.create?.beforeBreadcrumbs || []"
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div class="relative">
2
+ <div class="relative w-full">
3
3
  <component
4
4
  v-for="c in coreStore?.resourceOptions?.pageInjections?.edit?.beforeBreadcrumbs || []"
5
5
  :is="getCustomComponent(c)"
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div class="relative">
2
+ <div class="relative flex flex-col max-w-full w-full">
3
3
  <Teleport to="body">
4
4
  <Filters
5
5
  :columns="coreStore.resource?.columns"
@@ -116,11 +116,10 @@ import ResourceListTable from '@/components/ResourceListTable.vue';
116
116
  import { useCoreStore } from '@/stores/core';
117
117
  import { useFiltersStore } from '@/stores/filters';
118
118
  import { callAdminForthApi, currentQuery, getIcon, setQuery } from '@/utils';
119
- import { computed, onMounted, ref, watch } from 'vue';
119
+ import { computed, onMounted, ref, watch, nextTick } from 'vue';
120
120
  import { useRoute } from 'vue-router';
121
121
  import { showErrorTost } from '@/composables/useFrontendApi'
122
122
  import { getCustomComponent, initThreeDotsDropdown } from '@/utils';
123
- import { initFlowbite } from 'flowbite';
124
123
  import ThreeDotsMenu from '@/components/ThreeDotsMenu.vue';
125
124
 
126
125
 
@@ -183,9 +182,7 @@ async function getList() {
183
182
  return row;
184
183
  });
185
184
  totalRows.value = data.total;
186
- await new Promise(resolve => setTimeout(resolve, 0));
187
- initFlowbite(); // for tooltips in table
188
-
185
+ await nextTick();
189
186
  }
190
187
 
191
188
  async function startBulkAction(actionId) {
@@ -246,8 +243,6 @@ async function init() {
246
243
  resourceId: route.params.resourceId
247
244
  });
248
245
 
249
- initFlowbite();
250
-
251
246
  // !!! clear filters should be in same tick with sort assignment so that watch can catch it as one change
252
247
 
253
248
  // try to init filters from query params
@@ -1,5 +1,7 @@
1
1
  <template>
2
- <div :key="`${$route?.params.resourceId}---${$route?.params.primaryKey}`" class="p-4">
2
+ <div :key="`${$route?.params.resourceId}---${$route?.params.primaryKey}`" class="p-4 flex"
3
+ :class="limitHeightToPage ? 'h-[calc(100vh-3.5rem)]': undefined"
4
+ >
3
5
  <RouterView/>
4
6
  </div>
5
7
  </template>
@@ -9,9 +11,41 @@
9
11
 
10
12
  <script setup>
11
13
 
12
-
13
14
  import { useCoreStore } from '@/stores/core';
15
+ import { computed } from 'vue';
16
+ import { useRoute } from 'vue-router';
14
17
 
18
+ const route = useRoute()
15
19
  const coreStore = useCoreStore()
16
20
 
21
+ const limitHeightToPage = computed(() => {
22
+ if (route.name !== 'resource-list' ) {
23
+ return false;
24
+ }
25
+ // for mobile phones disable shrinking table at all because of the issues with HEADER and general UX
26
+ // use navigator.userAgent to detect mobile phones
27
+ if (navigator.userAgent.match(/Android/i) || navigator.userAgent.match(/iPhone|iPad|iPod/i)) {
28
+ return false;
29
+ }
30
+
31
+ if (!coreStore.resource?.options?.pageInjections?.list) {
32
+ return true;
33
+ }
34
+ const listPageInjects = coreStore.resource.options.pageInjections.list;
35
+ console.log('asdcoreStore.resource', JSON.stringify(listPageInjects, null, 2))
36
+
37
+ for (const pi of [listPageInjects.beforeBreadcrumbs, listPageInjects.afterBreadcrumbs, listPageInjects.bottom]) {
38
+ if (pi) {
39
+ for (const piItem of pi) {
40
+ if (!piItem.meta?.thinEnoughToShrinkTable) {
41
+ return false;
42
+ }
43
+ }
44
+ }
45
+ }
46
+
47
+ return true;
48
+
49
+ })
50
+
17
51
  </script>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div class="relative">
2
+ <div class="relative w-full">
3
3
  <component
4
4
  v-if="!loading"
5
5
  v-for="c in coreStore?.resourceOptions?.pageInjections?.show?.beforeBreadcrumbs || []"
@@ -57,22 +57,23 @@
57
57
  </div>
58
58
  <div
59
59
  v-else-if="coreStore.record"
60
- class="relative overflow-x-auto rounded-default shadow-resourseFormShadow "
60
+ class="relative w-full overflow-x-auto rounded-default shadow-resourseFormShadow"
61
61
  >
62
- <table class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400 rounded-default">
63
- <thead class="text-xs text-gray-700 uppercase bg-lightFormHeading dark:bg-gray-700 dark:text-gray-400">
64
- <tr>
65
- <th scope="col" class="px-6 py-3">
66
- Field
67
- </th>
68
- <th scope="col" class="px-6 py-3 w-5/6">
69
- Value
70
- </th>
71
- </tr>
62
+ <table class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400 table-fixed">
63
+ <!-- table fixed layour used to prevent double scroll in inline list plugins -->
64
+ <thead class="text-xs text-gray-700 uppercase dark:text-gray-400 bg-lightFormHeading dark:bg-gray-700 block md:table-row-group ">
65
+ <tr>
66
+ <th scope="col" class="px-6 py-3 hidden md:w-52 md:table-cell">
67
+ Field
68
+ </th>
69
+ <th scope="col" class="px-6 py-3 hidden md:table-cell">
70
+ Value
71
+ </th>
72
+ </tr>
72
73
  </thead>
73
74
  <tbody>
74
75
  <tr v-for="column,i in coreStore.resource?.columns.filter(c => c.showIn.includes('show'))" :key="column.name"
75
- class="bg-lightForm bg-darkForm odd:dark:bg-gray-900 even:dark:bg-gray-800 dark:border-gray-700"
76
+ class="bg-lightForm bg-darkForm odd:dark:bg-gray-900 even:dark:bg-gray-800 dark:border-gray-700 block md:table-row"
76
77
  :class="{ 'border-b': i !== coreStore.resource.columns.filter(c => c.showIn.includes('show')).length - 1 }"
77
78
  >
78
79
  <component
@@ -84,10 +85,10 @@
84
85
  :record="coreStore.record"
85
86
  />
86
87
  <template v-else>
87
- <td class="px-6 py-4 whitespace-nowrap "> <!--align-top-->
88
+ <td class="px-6 py-4 relative block md:table-cell font-bold md:font-normal pb-0 md:pb-4"> <!--align-top-->
88
89
  {{ column.label }}
89
90
  </td>
90
- <td class="px-6 py-4 whitespace-nowrap whitespace-pre-wrap" :data-af-column="column.name">
91
+ <td class="px-6 py-4 whitespace-pre-wrap" :data-af-column="column.name">
91
92
  <component
92
93
  v-if="column?.components?.show"
93
94
  :is="getCustomComponent(column?.components?.show)"
@@ -883,12 +883,12 @@ export type AdminForthResource = {
883
883
  hooks?: {
884
884
  show?: {
885
885
  /**
886
- * Typical usecases:
886
+ * Typical use-cases:
887
887
  * - request additional data from database before returning to frontend for soft-join
888
888
  */
889
889
  beforeDatasourceRequest?: BeforeDataSourceRequestFunction | Array<BeforeDataSourceRequestFunction>;
890
890
  /**
891
- * Typical usecases:
891
+ * Typical use-cases:
892
892
  * - Transform value for some field for record returned from database before returning to frontend (minimize, sanitize, etc)
893
893
  * - If some-why you can't use `backendOnly` you can cleanup sensitive fields here
894
894
  * - Attach additional data to record before returning to frontend
@@ -897,51 +897,52 @@ export type AdminForthResource = {
897
897
  };
898
898
  list?: {
899
899
  /**
900
- * Typical usecases:
900
+ * Typical use-cases:
901
901
  * - add additional filters in addition to what user selected before fetching data from database.
902
902
  * - same as hooks.show.beforeDatasourceRequest
903
903
  */
904
904
  beforeDatasourceRequest?: BeforeDataSourceRequestFunction | Array<BeforeDataSourceRequestFunction>;
905
905
  /**
906
- * Typical usecases:
907
- * - Same as hooks.show.afterDatasourceResponse but applied for each record in list
906
+ * Typical use-cases:
907
+ * - Same as hooks.show.afterDatasourceResponse but applied for all records returned from database for
908
+ * showing in list view, e.g. add new field to each record in list view
908
909
  */
909
910
  afterDatasourceResponse?: AfterDataSourceResponseFunction | Array<AfterDataSourceResponseFunction>;
910
911
  };
911
912
  create?: {
912
913
  /**
913
- * Typical usecases:
914
+ * Typical use-cases:
914
915
  * - Validate record before saving to database and interrupt execution if validation failed (`allowedActions.create` should be preferred in most cases)
915
916
  * - fill-in adminUser as creator of record
916
917
  * - Attach additional data to record before saving to database (mostly fillOnCreate should be used instead)
917
918
  */
918
919
  beforeSave?: BeforeSaveFunction | Array<BeforeSaveFunction>;
919
920
  /**
920
- * Typical usecases:
921
+ * Typical use-cases:
921
922
  * - Initiate some trigger after record saved to database (e.g sync to another datasource)
922
923
  */
923
924
  afterSave?: AfterSaveFunction | Array<AfterSaveFunction>;
924
925
  };
925
926
  edit?: {
926
927
  /**
927
- * Typical usecases:
928
+ * Typical use-cases:
928
929
  * - Same as hooks.create.beforeSave but for edit page
929
930
  */
930
931
  beforeSave?: BeforeSaveFunction | Array<BeforeSaveFunction>;
931
932
  /**
932
- * Typical usecases:
933
+ * Typical use-cases:
933
934
  * - Same as hooks.create.afterSave but for edit page
934
935
  */
935
936
  afterSave?: AfterSaveFunction | Array<AfterSaveFunction>;
936
937
  };
937
938
  delete?: {
938
939
  /**
939
- * Typical usecases:
940
+ * Typical use-cases:
940
941
  * - Validate that record can be deleted and interrupt execution if validation failed (`allowedActions.delete` should be preferred in most cases)
941
942
  */
942
943
  beforeSave?: BeforeSaveFunction | Array<BeforeSaveFunction>;
943
944
  /**
944
- * Typical usecases:
945
+ * Typical use-cases:
945
946
  * - Initiate some trigger after record deleted from database (e.g sync to another datasource)
946
947
  */
947
948
  afterSave?: BeforeSaveFunction | Array<BeforeSaveFunction>;
@@ -999,6 +1000,13 @@ export type AdminForthResource = {
999
1000
  *
1000
1001
  */
1001
1002
  allowedActions?: AllowedActions;
1003
+ /**
1004
+ * Allows to make groups of columns in create/edit resource pages.
1005
+ */
1006
+ createEditGroups?: {
1007
+ groupName: string;
1008
+ columns: string[];
1009
+ }[];
1002
1010
  /**
1003
1011
  * Page size for list view
1004
1012
  */
@@ -1221,6 +1229,11 @@ export type AdminForthConfig = {
1221
1229
  * Your app name
1222
1230
  */
1223
1231
  brandName?: string;
1232
+ /**
1233
+ * Slug which will be used on tech side e.g. to store cookies separately.
1234
+ * Created automatically from brandName if not set.
1235
+ */
1236
+ _brandNameSlug?: string;
1224
1237
  /**
1225
1238
  * Whether to show brand name in sidebar
1226
1239
  * default is true