Skip to content

Commit da9817e

Browse files
authored
Merge pull request #679 from devforth/next
Next
2 parents 00d52c2 + e3e43f3 commit da9817e

20 files changed

Lines changed: 235 additions & 54 deletions

File tree

adminforth/documentation/docs/tutorial/03-Customization/13-standardPagesTuning.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,26 @@ And `edit` action will be available as quick action:
542542
543543

544544

545+
## Show
546+
547+
### Next record button
548+
549+
By default, when a user opens a record from the list view, a **Next** button appears on the show page. It allows navigating through records one by one, respecting the current filters and sorting applied in the list. When the user reaches the last record on the current page, AdminForth automatically fetches the next page and continues navigation seamlessly.
550+
551+
To disable the Next button for a resource, set `showNextButton` to `false`:
552+
553+
```typescript title="./resources/apartments.ts"
554+
export default {
555+
resourceId: 'aparts',
556+
options: {
557+
//diff-add
558+
showNextButton: false,
559+
}
560+
}
561+
```
562+
563+
> ☝️ The Next button is only shown when the user navigates to the show page from the list view. Opening a record directly via URL will not display the button.
564+
545565
## Creating
546566

547567
### Fill with default values

adminforth/documentation/docs/tutorial/06-Adapters/02-oauth2-adapters.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ Adds support for Twitch authentication, useful for streaming or creator-oriented
5959
## Clerk OAuth Adapter
6060

6161
```bash
62-
pnpm i @adminforth/clerk-oauth-adapter
62+
pnpm i @adminforth/oauth-adapter-clerk
6363
```
6464

6565
Enables sign-in via [Clerk](https://clerk.com/) — a hosted authentication platform with built-in user management, MFA, and social logins.

adminforth/documentation/docs/tutorial/09-Plugins/11-oauth.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -481,7 +481,7 @@ plugins: [
481481
Install Adapter:
482482

483483
```bash
484-
pnpm install @adminforth/clerk-oauth-adapter --save
484+
pnpm install @adminforth/oauth-adapter-clerk --save
485485
```
486486

487487
1. Go to the [Clerk Dashboard](https://dashboard.clerk.com) and open your application.
@@ -502,7 +502,7 @@ CLERK_DOMAIN=https://your-app.clerk.accounts.dev
502502
Add the adapter to your plugin configuration:
503503

504504
```typescript title="./resources/adminuser.ts"
505-
import AdminForthAdapterClerkOauth2 from '@adminforth/clerk-oauth-adapter';
505+
import AdminForthAdapterClerkOauth2 from '@adminforth/oauth-adapter-clerk';
506506

507507
// ... existing resource configuration ...
508508
plugins: [

adminforth/index.ts

Lines changed: 4 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import CodeInjector from './modules/codeInjector.js';
33
import ExpressServer from './servers/express.js';
44
import OpenApiRegistry from './servers/openapi.js';
55
// import FastifyServer from './servers/fastify.js';
6-
import { ADMINFORTH_VERSION, listify, suggestIfTypo, RateLimiter, RAMLock, getClientIp, isProbablyUUIDColumn, convertPeriodToSeconds, hookResponseError, md5hash } from './modules/utils.js';
6+
import { ADMINFORTH_VERSION, listify, suggestIfTypo, RateLimiter, RAMLock, getClientIp, isProbablyUUIDColumn, convertPeriodToSeconds, hookResponseError, md5hash, applyRegexValidation } from './modules/utils.js';
77
import {
88
type AdminForthConfig,
99
type IAdminForth,
@@ -35,7 +35,7 @@ import {
3535

3636
import AdminForthPlugin from './basePlugin.js';
3737
import ConfigValidator from './modules/configValidator.js';
38-
import AdminForthRestAPI, { interpretResource } from './modules/restApi.js';
38+
import AdminForthRestAPI, { interpretResource, rejectApiRawFilters } from './modules/restApi.js';
3939
import OperationalResource from './modules/operationalResource.js';
4040
import SocketBroker from './modules/socketBroker.js';
4141
import { afLogger } from './modules/logger.js';
@@ -50,7 +50,7 @@ export * from './types/adapters/index.js';
5050
export * from './modules/filtersTools.js';
5151
export * from './modules/requestContext.js';
5252
export * from './modules/utils.js';
53-
export { interpretResource };
53+
export { interpretResource, rejectApiRawFilters };
5454
export { AdminForthPlugin };
5555
export { suggestIfTypo, RateLimiter, RAMLock, getClientIp, convertPeriodToSeconds };
5656
export { default as AdminForthBaseConnector } from './dataConnectors/baseConnector.js';
@@ -69,33 +69,7 @@ class AdminForth implements IAdminForth {
6969
},
7070

7171
applyRegexValidation(value, validation) {
72-
if (validation?.length) {
73-
const validationArray = validation;
74-
for (let i = 0; i < validationArray.length; i++) {
75-
if (validationArray[i].regExp) {
76-
let flags = '';
77-
if (validationArray[i].caseSensitive) {
78-
flags += 'i';
79-
}
80-
if (validationArray[i].multiline) {
81-
flags += 'm';
82-
}
83-
if (validationArray[i].global) {
84-
flags += 'g';
85-
}
86-
87-
const regExp = new RegExp(validationArray[i].regExp, flags);
88-
if (value === undefined || value === null) {
89-
value = '';
90-
}
91-
let valueS = `${value}`;
92-
93-
if (!regExp.test(valueS)) {
94-
return validationArray[i].message;
95-
}
96-
}
97-
}
98-
}
72+
return applyRegexValidation(value, validation);
9973
},
10074

10175
PASSWORD_VALIDATORS: {

adminforth/modules/codeInjector.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1240,7 +1240,6 @@ class CodeInjector implements ICodeInjector {
12401240
// parse port from message " ➜ Local: http://localhost:xyz/"
12411241
const s = stripAnsiCodes(data.toString());
12421242

1243-
process.env.HEAVY_DEBUG && console.log(`🪲 devServer stdout ➜ (port detect): ${s}`);
12441243
const portMatch = s.match(/.+?http:\/\/.+?:(\d+).+?/m);
12451244
if (portMatch) {
12461245
this.devServerPort = parseInt(portMatch[1]);

adminforth/modules/restApi.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ function hasApiRawFilter(filters: any): boolean {
259259
return Array.isArray(filters.subFilters) && filters.subFilters.some(hasApiRawFilter);
260260
}
261261

262-
function rejectApiRawFilters(filters: any): { error: string } | undefined {
262+
export function rejectApiRawFilters(filters: any): { error: string } | undefined {
263263
if (hasApiRawFilter(filters)) {
264264
return { error: 'insecureRawSQL and insecureRawNoSQL filters are not allowed in API requests' };
265265
}
@@ -341,6 +341,7 @@ const getResourceDataResponseSchema: AnySchemaObject = createErrorOrSuccessSchem
341341
items: genericObjectSchema,
342342
},
343343
total: { type: 'number' },
344+
recordIds: { type: 'array', items: {} },
344345
options: genericObjectSchema,
345346
},
346347
additionalProperties: true,
@@ -1636,6 +1637,11 @@ export default class AdminForthRestAPI implements IAdminForthRestAPI {
16361637
}
16371638
}
16381639

1640+
if (source === 'list') {
1641+
const pkField = resource.columns.find((col) => col.primaryKey).name;
1642+
(data as any).recordIds = data.data.map((item) => item[pkField]);
1643+
}
1644+
16391645
return data;
16401646
},
16411647
});

adminforth/modules/utils.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -592,4 +592,34 @@ export function checkIfFieldIsInsideResourceColumns(fieldName: string, resource:
592592
}
593593
}
594594
return false;
595+
}
596+
597+
export function applyRegexValidation(value, validation) {
598+
if (validation?.length) {
599+
const validationArray = validation;
600+
for (let i = 0; i < validationArray.length; i++) {
601+
if (validationArray[i].regExp) {
602+
let flags = '';
603+
if (validationArray[i].caseSensitive) {
604+
flags += 'i';
605+
}
606+
if (validationArray[i].multiline) {
607+
flags += 'm';
608+
}
609+
if (validationArray[i].global) {
610+
flags += 'g';
611+
}
612+
613+
const regExp = new RegExp(validationArray[i].regExp, flags);
614+
if (value === undefined || value === null) {
615+
value = '';
616+
}
617+
let valueS = `${value}`;
618+
619+
if (!regExp.test(valueS)) {
620+
return validationArray[i].message;
621+
}
622+
}
623+
}
624+
}
595625
}

adminforth/spa/src/components/CustomDateRangePicker.vue

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,10 @@ async function initDatepickers() {
208208
'flowbite-datepicker/Datepicker'
209209
);
210210
211+
if (!datepickerStartEl.value || !datepickerEndEl.value) {
212+
return;
213+
}
214+
211215
const LS_LANG_KEY = `afLanguage`;
212216
datepickerStartObject.value = new Datepicker(datepickerStartEl.value, {format: 'dd M yyyy', language: localStorage.getItem(LS_LANG_KEY)});
213217
datepickerEndObject.value = new Datepicker(datepickerEndEl.value, {format: 'dd M yyyy', language: localStorage.getItem(LS_LANG_KEY)});
@@ -225,8 +229,8 @@ function removeChangeDateListener() {
225229
}
226230
227231
function destroyDatepickerElement() {
228-
datepickerStartObject.value.destroy();
229-
datepickerEndObject.value.destroy();
232+
datepickerStartObject.value?.destroy?.();
233+
datepickerEndObject.value?.destroy?.();
230234
}
231235
232236
function setStartDate(event) {

adminforth/spa/src/components/ResourceListTable.vue

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -422,7 +422,8 @@ const props = withDefaults(defineProps<{
422422
bufferSize?: number,
423423
customActionIconsThreeDotsMenuItems?: AdminForthComponentDeclaration[]
424424
tableRowReplaceInjection?: AdminForthComponentDeclaration,
425-
isVirtualScrollEnabled: boolean
425+
isVirtualScrollEnabled: boolean,
426+
filters?: any[]
426427
}>(), {
427428
sort: () => []
428429
});
@@ -615,6 +616,12 @@ async function onClick(e: any, row: any) {
615616
// user asked to nothing on click
616617
return;
617618
}
619+
coreStore.listRecordIds = props.rows?.map(r => r._primaryKeyValue) ?? [];
620+
coreStore.listResourceId = props.resource?.resourceId ?? null;
621+
coreStore.listSort = props.sort;
622+
coreStore.listPage = page.value;
623+
coreStore.listPageSize = props.pageSize;
624+
coreStore.listFilters = props.filters ?? [];
618625
if (e.ctrlKey || e.metaKey || row._clickUrl?.includes('target=_blank')) {
619626
620627
if (row._clickUrl) {

adminforth/spa/src/renderers/SensitiveBlurCell.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<template>
22
<div class="inline-flex items-center gap-1">
33
<div
4-
class="overflow-hidden max-w-[200px] max-h-[20px] rounded-default"
4+
class="overflow-hidden max-h-[20px] rounded-default"
55
:title="show ? $t('Click to hide') : $t('Click to show')"
66
@click="toggle"
77
>

0 commit comments

Comments
 (0)