adminforth 1.2.75 → 1.2.77

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.
@@ -132,7 +132,7 @@ export default class AdminForthRestAPI {
132
132
  method: 'GET',
133
133
  path: '/get_base_config',
134
134
  handler: (_k) => __awaiter(this, [_k], void 0, function* ({ input, adminUser, cookies }) {
135
- var _l, _m, _o;
135
+ var _l, _m, _o, _p, _q;
136
136
  let username = '';
137
137
  let userFullName = '';
138
138
  if (adminUser.isRoot) {
@@ -180,6 +180,7 @@ export default class AdminForthRestAPI {
180
180
  }
181
181
  newMenu.push(newMenuItem);
182
182
  }
183
+ const announcementBadge = (_o = (_m = this.adminforth.config.customization).announcementBadge) === null || _o === void 0 ? void 0 : _o.call(_m, adminUser);
183
184
  return {
184
185
  user: userData,
185
186
  resources: this.adminforth.config.resources.map((res) => ({
@@ -194,8 +195,9 @@ export default class AdminForthRestAPI {
194
195
  deleteConfirmation: this.adminforth.config.deleteConfirmation,
195
196
  auth: this.adminforth.config.auth,
196
197
  usernameField: this.adminforth.config.auth.usernameField,
197
- title: (_m = this.adminforth.config.customization) === null || _m === void 0 ? void 0 : _m.title,
198
- emptyFieldPlaceholder: (_o = this.adminforth.config.customization) === null || _o === void 0 ? void 0 : _o.emptyFieldPlaceholder,
198
+ title: (_p = this.adminforth.config.customization) === null || _p === void 0 ? void 0 : _p.title,
199
+ emptyFieldPlaceholder: (_q = this.adminforth.config.customization) === null || _q === void 0 ? void 0 : _q.emptyFieldPlaceholder,
200
+ announcementBadge,
199
201
  },
200
202
  adminUser,
201
203
  version: ADMINFORTH_VERSION,
@@ -234,7 +236,7 @@ export default class AdminForthRestAPI {
234
236
  server.endpoint({
235
237
  method: 'POST',
236
238
  path: '/get_resource',
237
- handler: (_p) => __awaiter(this, [_p], void 0, function* ({ body, adminUser }) {
239
+ handler: (_r) => __awaiter(this, [_r], void 0, function* ({ body, adminUser }) {
238
240
  const { resourceId } = body;
239
241
  if (!this.adminforth.statuses.dbDiscover) {
240
242
  return { error: 'Database discovery not started' };
@@ -265,8 +267,8 @@ export default class AdminForthRestAPI {
265
267
  server.endpoint({
266
268
  method: 'POST',
267
269
  path: '/get_resource_data',
268
- handler: (_q) => __awaiter(this, [_q], void 0, function* ({ body, adminUser }) {
269
- var _r, _s, _t, _u;
270
+ handler: (_s) => __awaiter(this, [_s], void 0, function* ({ body, adminUser }) {
271
+ var _t, _u, _v, _w;
270
272
  const { resourceId, source } = body;
271
273
  if (['show', 'list'].includes(source) === false) {
272
274
  return { error: 'Invalid source, should be list or show' };
@@ -286,7 +288,7 @@ export default class AdminForthRestAPI {
286
288
  if (!allowed) {
287
289
  return { error };
288
290
  }
289
- for (const hook of listify((_s = (_r = resource.hooks) === null || _r === void 0 ? void 0 : _r[source]) === null || _s === void 0 ? void 0 : _s.beforeDatasourceRequest)) {
291
+ for (const hook of listify((_u = (_t = resource.hooks) === null || _t === void 0 ? void 0 : _t[source]) === null || _u === void 0 ? void 0 : _u.beforeDatasourceRequest)) {
290
292
  const resp = yield hook({ resource, query: body, adminUser });
291
293
  if (!resp || (!resp.ok && !resp.error)) {
292
294
  throw new Error(`Hook must return object with {ok: true} or { error: 'Error' } `);
@@ -365,7 +367,7 @@ export default class AdminForthRestAPI {
365
367
  item._label = resource.recordLabel(item);
366
368
  });
367
369
  // only after adminforth made all post processing, give user ability to edit it
368
- for (const hook of listify((_u = (_t = resource.hooks) === null || _t === void 0 ? void 0 : _t[source]) === null || _u === void 0 ? void 0 : _u.afterDatasourceResponse)) {
370
+ for (const hook of listify((_w = (_v = resource.hooks) === null || _v === void 0 ? void 0 : _v[source]) === null || _w === void 0 ? void 0 : _w.afterDatasourceResponse)) {
369
371
  const resp = yield hook({ resource, response: data.data, adminUser });
370
372
  if (!resp || (!resp.ok && !resp.error)) {
371
373
  throw new Error(`Hook must return object with {ok: true} or { error: 'Error' } `);
@@ -380,8 +382,8 @@ export default class AdminForthRestAPI {
380
382
  server.endpoint({
381
383
  method: 'POST',
382
384
  path: '/get_resource_foreign_data',
383
- handler: (_v) => __awaiter(this, [_v], void 0, function* ({ body, adminUser }) {
384
- var _w, _x, _y, _z;
385
+ handler: (_x) => __awaiter(this, [_x], void 0, function* ({ body, adminUser }) {
386
+ var _y, _z, _0, _1;
385
387
  const { resourceId, column } = body;
386
388
  if (!this.adminforth.statuses.dbDiscover) {
387
389
  return { error: 'Database discovery not started' };
@@ -402,7 +404,7 @@ export default class AdminForthRestAPI {
402
404
  }
403
405
  const targetResourceId = columnConfig.foreignResource.resourceId;
404
406
  const targetResource = this.adminforth.config.resources.find((res) => res.resourceId == targetResourceId);
405
- for (const hook of listify((_x = (_w = columnConfig.foreignResource.hooks) === null || _w === void 0 ? void 0 : _w.dropdownList) === null || _x === void 0 ? void 0 : _x.beforeDatasourceRequest)) {
407
+ for (const hook of listify((_z = (_y = columnConfig.foreignResource.hooks) === null || _y === void 0 ? void 0 : _y.dropdownList) === null || _z === void 0 ? void 0 : _z.beforeDatasourceRequest)) {
406
408
  const resp = yield hook({ query: body, adminUser, resource: targetResource });
407
409
  if (!resp || (!resp.ok && !resp.error)) {
408
410
  throw new Error(`Hook must return object with {ok: true} or { error: 'Error' } `);
@@ -431,7 +433,7 @@ export default class AdminForthRestAPI {
431
433
  const response = {
432
434
  items
433
435
  };
434
- for (const hook of listify((_z = (_y = columnConfig.foreignResource.hooks) === null || _y === void 0 ? void 0 : _y.dropdownList) === null || _z === void 0 ? void 0 : _z.afterDatasourceResponse)) {
436
+ for (const hook of listify((_1 = (_0 = columnConfig.foreignResource.hooks) === null || _0 === void 0 ? void 0 : _0.dropdownList) === null || _1 === void 0 ? void 0 : _1.afterDatasourceResponse)) {
435
437
  const resp = yield hook({ response, adminUser, resource: targetResource });
436
438
  if (!resp || (!resp.ok && !resp.error)) {
437
439
  throw new Error(`Hook must return object with {ok: true} or { error: 'Error' } `);
@@ -446,7 +448,7 @@ export default class AdminForthRestAPI {
446
448
  server.endpoint({
447
449
  method: 'POST',
448
450
  path: '/get_min_max_for_columns',
449
- handler: (_0) => __awaiter(this, [_0], void 0, function* ({ body }) {
451
+ handler: (_2) => __awaiter(this, [_2], void 0, function* ({ body }) {
450
452
  const { resourceId } = body;
451
453
  if (!this.adminforth.statuses.dbDiscover) {
452
454
  return { error: 'Database discovery not started' };
@@ -475,7 +477,7 @@ export default class AdminForthRestAPI {
475
477
  server.endpoint({
476
478
  method: 'POST',
477
479
  path: '/create_record',
478
- handler: (_1) => __awaiter(this, [_1], void 0, function* ({ body, adminUser }) {
480
+ handler: (_3) => __awaiter(this, [_3], void 0, function* ({ body, adminUser }) {
479
481
  const resource = this.adminforth.config.resources.find((res) => res.resourceId == body['resourceId']);
480
482
  if (!resource) {
481
483
  return { error: `Resource '${body['resourceId']}' not found` };
@@ -499,8 +501,8 @@ export default class AdminForthRestAPI {
499
501
  server.endpoint({
500
502
  method: 'POST',
501
503
  path: '/update_record',
502
- handler: (_2) => __awaiter(this, [_2], void 0, function* ({ body, adminUser }) {
503
- var _3, _4, _5, _6;
504
+ handler: (_4) => __awaiter(this, [_4], void 0, function* ({ body, adminUser }) {
505
+ var _5, _6, _7, _8;
504
506
  const resource = this.adminforth.config.resources.find((res) => res.resourceId == body['resourceId']);
505
507
  if (!resource) {
506
508
  return { error: `Resource '${body['resourceId']}' not found` };
@@ -519,7 +521,7 @@ export default class AdminForthRestAPI {
519
521
  return { error };
520
522
  }
521
523
  // execute hook if needed
522
- for (const hook of listify((_4 = (_3 = resource.hooks) === null || _3 === void 0 ? void 0 : _3.edit) === null || _4 === void 0 ? void 0 : _4.beforeSave)) {
524
+ for (const hook of listify((_6 = (_5 = resource.hooks) === null || _5 === void 0 ? void 0 : _5.edit) === null || _6 === void 0 ? void 0 : _6.beforeSave)) {
523
525
  const resp = yield hook({ resource, record, adminUser });
524
526
  if (!resp || (!resp.ok && !resp.error)) {
525
527
  throw new Error(`Hook beforeSave must return object with {ok: true} or { error: 'Error' } `);
@@ -546,7 +548,7 @@ export default class AdminForthRestAPI {
546
548
  yield connector.updateRecord({ resource, recordId, newValues });
547
549
  }
548
550
  // execute hook if needed
549
- for (const hook of listify((_6 = (_5 = resource.hooks) === null || _5 === void 0 ? void 0 : _5.edit) === null || _6 === void 0 ? void 0 : _6.afterSave)) {
551
+ for (const hook of listify((_8 = (_7 = resource.hooks) === null || _7 === void 0 ? void 0 : _7.edit) === null || _8 === void 0 ? void 0 : _8.afterSave)) {
550
552
  const resp = yield hook({ resource, record, adminUser, oldRecord });
551
553
  if (!resp || (!resp.ok && !resp.error)) {
552
554
  throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
@@ -563,8 +565,8 @@ export default class AdminForthRestAPI {
563
565
  server.endpoint({
564
566
  method: 'POST',
565
567
  path: '/delete_record',
566
- handler: (_7) => __awaiter(this, [_7], void 0, function* ({ body, adminUser }) {
567
- var _8, _9, _10, _11;
568
+ handler: (_9) => __awaiter(this, [_9], void 0, function* ({ body, adminUser }) {
569
+ var _10, _11, _12, _13;
568
570
  const resource = this.adminforth.config.resources.find((res) => res.resourceId == body['resourceId']);
569
571
  const record = yield this.adminforth.connectors[resource.dataSource].getRecordByPrimaryKey(resource, body['primaryKey']);
570
572
  if (!resource) {
@@ -582,7 +584,7 @@ export default class AdminForthRestAPI {
582
584
  return { error };
583
585
  }
584
586
  // execute hook if needed
585
- for (const hook of listify((_9 = (_8 = resource.hooks) === null || _8 === void 0 ? void 0 : _8.delete) === null || _9 === void 0 ? void 0 : _9.beforeSave)) {
587
+ for (const hook of listify((_11 = (_10 = resource.hooks) === null || _10 === void 0 ? void 0 : _10.delete) === null || _11 === void 0 ? void 0 : _11.beforeSave)) {
586
588
  const resp = yield hook({ resource, record, adminUser });
587
589
  if (!resp || (!resp.ok && !resp.error)) {
588
590
  throw new Error(`Hook beforeSave must return object with {ok: true} or { error: 'Error' } `);
@@ -594,7 +596,7 @@ export default class AdminForthRestAPI {
594
596
  const connector = this.adminforth.connectors[resource.dataSource];
595
597
  yield connector.deleteRecord({ resource, recordId: body['primaryKey'] });
596
598
  // execute hook if needed
597
- for (const hook of listify((_11 = (_10 = resource.hooks) === null || _10 === void 0 ? void 0 : _10.delete) === null || _11 === void 0 ? void 0 : _11.afterSave)) {
599
+ for (const hook of listify((_13 = (_12 = resource.hooks) === null || _12 === void 0 ? void 0 : _12.delete) === null || _13 === void 0 ? void 0 : _13.afterSave)) {
598
600
  const resp = yield hook({ resource, record, adminUser });
599
601
  if (!resp || (!resp.ok && !resp.error)) {
600
602
  throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
@@ -611,7 +613,7 @@ export default class AdminForthRestAPI {
611
613
  server.endpoint({
612
614
  method: 'POST',
613
615
  path: '/start_bulk_action',
614
- handler: (_12) => __awaiter(this, [_12], void 0, function* ({ body, adminUser }) {
616
+ handler: (_14) => __awaiter(this, [_14], void 0, function* ({ body, adminUser }) {
615
617
  const { resourceId, actionId, recordIds } = body;
616
618
  const resource = this.adminforth.config.resources.find((res) => res.resourceId == resourceId);
617
619
  if (!resource) {
@@ -207,6 +207,8 @@ export default class AdminForthRestAPI {
207
207
  newMenu.push(newMenuItem)
208
208
  }
209
209
 
210
+ const announcementBadge = this.adminforth.config.customization.announcementBadge?.(adminUser);
211
+
210
212
  return {
211
213
  user: userData,
212
214
  resources: this.adminforth.config.resources.map((res) => ({
@@ -223,6 +225,7 @@ export default class AdminForthRestAPI {
223
225
  usernameField: this.adminforth.config.auth.usernameField,
224
226
  title: this.adminforth.config.customization?.title,
225
227
  emptyFieldPlaceholder: this.adminforth.config.customization?.emptyFieldPlaceholder,
228
+ announcementBadge,
226
229
  },
227
230
  adminUser,
228
231
  version: ADMINFORTH_VERSION,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adminforth",
3
- "version": "1.2.75",
3
+ "version": "1.2.77",
4
4
  "description": "OpenSource Vue3 powered forth-generation admin panel",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -11,7 +11,7 @@
11
11
  "rollout": "tsc && npm version patch && npm publish && npm run rollout-doc",
12
12
  "rollout-doc": "cd documentation && npm run build && npm run deploy",
13
13
  "docs": "typedoc",
14
- "postinstall": "cd spa/src && ln -sf ../../types types"
14
+ "postinstall": "cd ./spa/src/ && test ! -d ./types && ln -sf ../../types ./types || echo 'types linked'"
15
15
  },
16
16
  "author": "devforth.io",
17
17
  "license": "ISC",
package/spa/src/App.vue CHANGED
@@ -8,7 +8,7 @@
8
8
  <div class="flex items-center justify-between">
9
9
  <div class="flex items-center justify-start rtl:justify-end">
10
10
  <button @click="sideBarOpen = !sideBarOpen"
11
- type="button" class="inline-flex items-center p-2 text-sm rounded-lg sm:hidden hover:bg-lightSidebarItemHover focus:outline-none focus:ring-2 focus:ring-lightSidebarDevider dark:text-darkSidebarIcons dark:hover:bg-darkSidebarHover dark:focus:ring-lightSidebarDevider">
11
+ type="button" class="inline-flex items-center p-2 text-sm rounded-lg sm:hidden hover:bg-lightSidebarItemHover focus:outline-none focus:ring-2 focus:ring-lightSidebarDevider dark:text-darkSidebarIcons dark:hover:bg-darkSidebarHover dark:focus:ring-lightSidebarDevider">
12
12
  <span class="sr-only">Open sidebar</span>
13
13
  <svg class="w-6 h-6" aria-hidden="true" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
14
14
  <path clip-rule="evenodd" fill-rule="evenodd" d="M2 4.75A.75.75 0 012.75 4h14.5a.75.75 0 010 1.5H2.75A.75.75 0 012 4.75zm0 10.5a.75.75 0 01.75-.75h7.5a.75.75 0 010 1.5h-7.5a.75.75 0 01-.75-.75zM2 10a.75.75 0 01.75-.75h14.5a.75.75 0 010 1.5H2.75A.75.75 0 012 10z"></path>
@@ -73,38 +73,66 @@
73
73
  </span>
74
74
  </div>
75
75
 
76
- <ul class="space-y-2 font-medium">
77
- <template v-for="(item, i) in coreStore.menu" :key="`menu-${i}`">
78
- <div v-if="item.type === 'divider'" class="border-t border-lightSidebarDevider dark:border-darkSidebarDevider"></div>
79
- <div v-else-if="item.type === 'gap'" class="flex items-center justify-center h-8"></div>
80
- <div v-else-if="item.type === 'heading'" class="flex items-center justify-left pl-2 h-8 text-lightSidebarHeading dark:text-darkSidebarHeading
81
- ">{{ item.label }}</div>
82
- <li v-else-if="item.children" class=" ">
83
- <button @click="clickOnMenuItem(i)" type="button" class="flex items-center w-full p-2 text-base text-lightSidebarText rounded-default transition duration-75 group hover:bg-lightSidebarItemHover hover:text-lightSidebarTextHover dark:text-darkSidebarText dark:hover:bg-darkSidebarHover dark:hover:text-darkSidebarTextHover"
84
- :aria-controls="`dropdown-example${i}`"
85
- :data-collapse-toggle="`dropdown-example${i}`"
86
- >
87
- <component v-if="item.icon" :is="getIcon(item.icon)" class="w-5 h-5 text-lightSidebarIcons group-hover:text-lightSidebarIconsHover transition duration-75 dark:group-hover:text-darkSidebarIconsHover dark:text-darkSidebarIcons" ></component>
88
-
89
- <span class="flex-1 ms-3 text-left rtl:text-right whitespace-nowrap">{{ item.label }}</span>
90
- <svg :class="{'rotate-180': opened.includes(i) }" class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 6">
91
- <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 4 4 4-4"/>
92
- </svg>
93
- </button>
76
+ <ul class="space-y-2 font-medium">
77
+ <template v-for="(item, i) in coreStore.menu" :key="`menu-${i}`">
78
+ <div v-if="item.type === 'divider'" class="border-t border-lightSidebarDevider dark:border-darkSidebarDevider"></div>
79
+ <div v-else-if="item.type === 'gap'" class="flex items-center justify-center h-8"></div>
80
+ <div v-else-if="item.type === 'heading'" class="flex items-center justify-left pl-2 h-8 text-lightSidebarHeading dark:text-darkSidebarHeading
81
+ ">{{ item.label }}</div>
82
+ <li v-else-if="item.children" class=" ">
83
+ <button @click="clickOnMenuItem(i)" type="button" class="flex items-center w-full p-2 text-base text-lightSidebarText rounded-default transition duration-75 group hover:bg-lightSidebarItemHover hover:text-lightSidebarTextHover dark:text-darkSidebarText dark:hover:bg-darkSidebarHover dark:hover:text-darkSidebarTextHover"
84
+ :aria-controls="`dropdown-example${i}`"
85
+ :data-collapse-toggle="`dropdown-example${i}`"
86
+ >
87
+ <component v-if="item.icon" :is="getIcon(item.icon)" class="w-5 h-5 text-lightSidebarIcons group-hover:text-lightSidebarIconsHover transition duration-75 dark:group-hover:text-darkSidebarIconsHover dark:text-darkSidebarIcons" ></component>
88
+
89
+ <span class="flex-1 ms-3 text-left rtl:text-right whitespace-nowrap">{{ item.label }}</span>
90
+ <svg :class="{'rotate-180': opened.includes(i) }" class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 6">
91
+ <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 4 4 4-4"/>
92
+ </svg>
93
+ </button>
94
94
 
95
- <ul :id="`dropdown-example${i}`" role="none" class="pt-1 space-y-1" :class="{ 'hidden': !opened.includes(i) }">
96
- <template v-for="(child, j) in item.children" :key="`menu-${i}-${j}`">
97
- <li>
98
- <MenuLink :item="child" isChild="true" />
99
- </li>
100
- </template>
101
- </ul>
102
- </li>
103
- <li v-else>
104
- <MenuLink :item="item" />
105
- </li>
106
- </template>
107
- </ul>
95
+ <ul :id="`dropdown-example${i}`" role="none" class="pt-1 space-y-1" :class="{ 'hidden': !opened.includes(i) }">
96
+ <template v-for="(child, j) in item.children" :key="`menu-${i}-${j}`">
97
+ <li>
98
+ <MenuLink :item="child" isChild="true" />
99
+ </li>
100
+ </template>
101
+ </ul>
102
+ </li>
103
+ <li v-else>
104
+ <MenuLink :item="item" />
105
+ </li>
106
+ </template>
107
+ </ul>
108
+
109
+
110
+ <div id="dropdown-cta" class="p-4 mt-6 rounded-lg bg-blue-50 dark:bg-blue-900" role="alert"
111
+ v-if="ctaBadge"
112
+ >
113
+ <div class="flex items-center mb-3">
114
+ <span class="bg-blue-200 text-blue-800 text-sm font-semibold me-2 px-2.5 py-0.5 rounded dark:bg-blue-200 dark:text-blue-800"
115
+ v-if="ctaBadge.title"
116
+ >
117
+ {{ctaBadge.title}}
118
+ </span>
119
+ <button type="button"
120
+ class="ms-auto -mx-1.5 -my-1.5 bg-blue-50 inline-flex justify-center items-center w-6 h-6 text-blue-900 rounded-lg focus:ring-2 focus:ring-blue-400 p-1 hover:bg-blue-200 dark:bg-blue-900 dark:text-blue-400 dark:hover:bg-blue-800"
121
+ data-dismiss-target="#dropdown-cta" aria-label="Close"
122
+ v-if="ctaBadge.closable" @click="closeCTA"
123
+ >
124
+ <span class="sr-only">Close</span>
125
+ <svg class="w-2.5 h-2.5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
126
+ <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/>
127
+ </svg>
128
+ </button>
129
+ </div>
130
+ <p class="mb-3 text-sm fill-blue-800 dark:fill-blue-400 text-blue-800 dark:text-blue-400" v-if="ctaBadge.html" v-html="ctaBadge.html"></p>
131
+ <p class="text-sm text-blue-800 dark:text-blue-400" v-else>
132
+ {{ ctaBadge.text }}
133
+ </p>
134
+ <!-- <a class="text-sm text-blue-800 underline font-medium hover:text-blue-900 dark:text-blue-400 dark:hover:text-blue-300" href="#">Turn new navigation off</a> -->
135
+ </div>
108
136
  </div>
109
137
  </aside>
110
138
 
@@ -311,4 +339,18 @@ onBeforeMount(()=>{
311
339
  document.documentElement.classList.toggle('dark', theme.value === 'dark');
312
340
  })
313
341
 
342
+
343
+ const ctaBadge = computed(() => {
344
+ const badge = coreStore.config?.announcementBadge;
345
+ const hash = badge.closable ? JSON.stringify(badge).split('').reduce((a,b)=>{a=((a<<5)-a)+b.charCodeAt(0);return a&a},0) : '';
346
+ if (badge.closable && window.localStorage.getItem(`ctaBadge-${hash}`)) {
347
+ return null;
348
+ }
349
+ return {...badge, hash};
350
+ });
351
+
352
+ function closeCTA() {
353
+ const hash = ctaBadge.value.hash;
354
+ window.localStorage.setItem(`ctaBadge-${hash}`, '1');
355
+ }
314
356
  </script>
@@ -1,6 +1,6 @@
1
- import { createRouter, createWebHistory } from 'vue-router'
2
- import ResourceParent from '@/views/ResourceParent.vue'
3
- import { useUserStore } from '@/stores/user'
1
+ import { createRouter, createWebHistory } from 'vue-router';
2
+ import ResourceParent from '@/views/ResourceParent.vue';
3
+ import { useFiltersStore } from '@/stores/filters';
4
4
  /* IMPORTANT:ADMINFORTH ROUTES IMPORTS */
5
5
 
6
6
  const router = createRouter({
@@ -51,13 +51,17 @@ const router = createRouter({
51
51
 
52
52
  },
53
53
  ]
54
- },
54
+ },
55
55
  /* IMPORTANT:ADMINFORTH ROUTES */
56
56
  ]
57
57
  })
58
58
 
59
-
60
-
61
-
59
+ router.beforeEach((to, from, next) => {
60
+ const filterStore = useFiltersStore();
61
+ if (to.path.startsWith('/resource/')) {
62
+ filterStore.clearFilters();
63
+ }
64
+ next()
65
+ })
62
66
 
63
67
  export default router
@@ -1,9 +1,8 @@
1
- import { ref } from 'vue'
2
- import { defineStore } from 'pinia'
3
- import { callAdminForthApi } from '@/utils';
1
+ import { ref, type Ref } from 'vue';
2
+ import { defineStore } from 'pinia';
4
3
 
5
4
  export const useFiltersStore = defineStore('filters', () => {
6
- const filters = ref([]);
5
+ const filters: Ref<any[]> = ref([]);
7
6
  const setFilter = (filter: any) => {
8
7
  filters.value = [...filters.value, filter];
9
8
  }
@@ -16,7 +15,5 @@ export const useFiltersStore = defineStore('filters', () => {
16
15
  const clearFilters = () => {
17
16
  filters.value = [];
18
17
  }
19
- return {setFilter, getFilters,clearFilters, filters,setFilters}
20
- })
21
-
22
-
18
+ return {setFilter, getFilters, clearFilters, filters, setFilters}
19
+ })
@@ -798,7 +798,9 @@ export type AdminForthResource = {
798
798
  */
799
799
  columns: Array<AdminForthResourceColumn>,
800
800
 
801
-
801
+ /**
802
+ * Internal array of columns which are not virtual. You should not edit it.
803
+ */
802
804
  dataSourceColumns?: Array<AdminForthResourceColumn>, // TODO, mark as private
803
805
 
804
806
  /**
@@ -818,25 +820,79 @@ export type AdminForthResource = {
818
820
  *
819
821
  */
820
822
  plugins?: Array<IAdminForthPlugin>,
823
+
824
+ /**
825
+ * Hooks allow you to change the data on different stages of resource lifecycle.
826
+ * Hooks are functions which will be called on backend side (only backend side).
827
+ */
821
828
  hooks?: {
822
829
  show?: {
830
+ /**
831
+ * Typical usecases:
832
+ * - request additional data from database before returning to frontend for soft-join
833
+ */
823
834
  beforeDatasourceRequest?: BeforeDataSourceRequestFunction | Array<BeforeDataSourceRequestFunction>,
835
+
836
+ /**
837
+ * Typical usecases:
838
+ * - Transform value for some field for record returned from database before returning to frontend (minimize, sanitize, etc)
839
+ * - If some-why you can't use `backendOnly` you can cleanup sensitive fields here
840
+ * - Attach additional data to record before returning to frontend
841
+ */
824
842
  afterDatasourceResponse?: AfterDataSourceResponseFunction | Array<AfterDataSourceResponseFunction>,
825
843
  },
826
844
  list?: {
845
+ /**
846
+ * Typical usecases:
847
+ * - add additional filters in addition to what user selected before fetching data from database.
848
+ * - same as hooks.show.beforeDatasourceRequest
849
+ */
827
850
  beforeDatasourceRequest?: BeforeDataSourceRequestFunction | Array<BeforeDataSourceRequestFunction>,
851
+
852
+ /**
853
+ * Typical usecases:
854
+ * - Same as hooks.show.afterDatasourceResponse but applied for each record in list
855
+ */
828
856
  afterDatasourceResponse?: AfterDataSourceResponseFunction | Array<AfterDataSourceResponseFunction>,
829
857
  },
830
858
  create?: {
859
+ /**
860
+ * Typical usecases:
861
+ * - Validate record before saving to database and interrupt execution if validation failed (`allowedActions.create` should be preferred in most cases)
862
+ * - fill-in adminUser as creator of record
863
+ * - Attach additional data to record before saving to database (mostly fillOnCreate should be used instead)
864
+ */
831
865
  beforeSave?: BeforeSaveFunction | Array<BeforeSaveFunction>,
866
+
867
+ /**
868
+ * Typical usecases:
869
+ * - Initiate some trigger after record saved to database (e.g sync to another datasource)
870
+ */
832
871
  afterSave?: AfterSaveFunction | Array<AfterSaveFunction>,
833
872
  },
834
873
  edit?: {
874
+ /**
875
+ * Typical usecases:
876
+ * - Same as hooks.create.beforeSave but for edit page
877
+ */
835
878
  beforeSave?: BeforeSaveFunction | Array<BeforeSaveFunction>,
879
+
880
+ /**
881
+ * Typical usecases:
882
+ * - Same as hooks.create.afterSave but for edit page
883
+ */
836
884
  afterSave?: AfterSaveFunction | Array<AfterSaveFunction>,
837
885
  },
838
886
  delete?: {
887
+ /**
888
+ * Typical usecases:
889
+ * - Validate that record can be deleted and interrupt execution if validation failed (`allowedActions.delete` should be preferred in most cases)
890
+ */
839
891
  beforeSave?: BeforeSaveFunction | Array<BeforeSaveFunction>,
892
+ /**
893
+ * Typical usecases:
894
+ * - Initiate some trigger after record deleted from database (e.g sync to another datasource)
895
+ */
840
896
  afterSave?: BeforeSaveFunction | Array<BeforeSaveFunction>,
841
897
  },
842
898
  },
@@ -1050,7 +1106,6 @@ export type AdminForthConfig = {
1050
1106
  * Function or functions which will be called before user try to login.
1051
1107
  * Each function will resive User object as an argument
1052
1108
  */
1053
-
1054
1109
  beforeLoginConfirmation?: BeforeLoginConfirmationFunction | Array<BeforeLoginConfirmationFunction>,
1055
1110
 
1056
1111
  /**
@@ -1205,14 +1260,21 @@ export type AdminForthConfig = {
1205
1260
  * Object to redefine default styles for AdminForth components. Use this file as reference for all possible adjustments https://github.com/devforth/adminforth/blob/main/adminforth/modules/styles.ts
1206
1261
  */
1207
1262
  styles?: Object,
1263
+
1208
1264
  /**
1209
1265
  * Description of custom pages which will let register custom pages for custom routes in AdminForth.
1210
1266
  */
1211
-
1212
1267
  customPages?: Array<{
1213
1268
  path: string,
1214
1269
  component: AdminForthComponentDeclaration,
1215
1270
  }>,
1271
+
1272
+ /**
1273
+ * Function to return custom badge in side bar for users. Can return text or html
1274
+ * If function is not passed or returns null, badge will not be shown.
1275
+ * Execution is done on admin app load.
1276
+ */
1277
+ announcementBadge?: (user: AdminUser) => { text?: string, html?: string, closable?: boolean, title?: string } | null,
1216
1278
  }
1217
1279
 
1218
1280
  /**
@@ -1224,7 +1286,8 @@ export type AdminForthConfig = {
1224
1286
  *
1225
1287
  */
1226
1288
  baseUrl?: string,
1227
-
1289
+
1290
+
1228
1291
  deleteConfirmation?: boolean,
1229
1292
 
1230
1293