Skip to content

Commit f3f77a5

Browse files
authored
Merge pull request #539 from devforth/check-captcha-in-live-demo
Check captcha in live demo
2 parents 0337b34 + 4301d71 commit f3f77a5

5 files changed

Lines changed: 160 additions & 1 deletion

File tree

adminforth/documentation/docs/tutorial/09-Advanced/01-plugin-development.md

Lines changed: 147 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -597,4 +597,150 @@ handler: async (a) => {
597597
const resp = await this.options.adapter.complete(content, ['.'], this.options.expert?.maxTokens || 50);
598598
...
599599
}
600-
```
600+
```
601+
## Providing a frontend API
602+
If a plugin needs to provide an external frontend API so user code or other plugins can interact with it, you can create a .ts file or a global Pinia store (if you want to have some global variables or use plugin modals) and then re-use it in your frontend logic. For example:
603+
604+
1) Create new `./custom/useChatGptApi.ts` and `./custom/customModal` files
605+
```text
606+
af-plugin-chatgpt/
607+
├── custom
608+
│ └── tsconfig.json
609+
| └── completionInput.vue
610+
//diff-add
611+
| └── useChatGptApi.ts
612+
//diff-add
613+
| └── customModal.vue
614+
├── index.ts
615+
├── package.json
616+
├── tsconfig.json
617+
└── types.ts
618+
```
619+
620+
2) Write your custom logic:
621+
622+
```ts title="./custom/useChatGptApi.ts"
623+
import { ref } from 'vue';
624+
import { callAdminForthApi } from '@/utils';
625+
import { defineStore } from 'pinia'
626+
627+
export const useChatGptApi = defineStore('chatGptApi', () => {
628+
const isCustomModalOpened = ref(false);
629+
const customModalData = ref({})
630+
631+
function openCustomModal(modalData) {
632+
customModalData.value = modalData;
633+
isCustomModalOpened.value = true;
634+
}
635+
636+
function closeCustomModal() {
637+
isCustomModalOpened.value = false;
638+
}
639+
640+
async function doComplete(pluginInstanceId, record, columnName, textBeforeCursor) {
641+
const res = await callAdminForthApi({
642+
path: `/plugin/${pluginInstanceId}/doComplete`,
643+
method: 'POST',
644+
body: {
645+
record: { ...record, [columnName]: textBeforeCursor },
646+
},
647+
});
648+
649+
return res.completion;
650+
}
651+
652+
return {
653+
isCustomModalOpened,
654+
customModalData,
655+
openCustomModal,
656+
closeCustomModal,
657+
doComplete
658+
}
659+
```
660+
661+
3) If you want your plugin to have its own modal API, then first of all you'll need to inject this component somewhere so you can open it:
662+
663+
```ts title="./index.ts"
664+
665+
async modifyResourceConfig(adminforth: IAdminForth, resourceConfig: AdminForthResource) {
666+
667+
...
668+
669+
// Global API injection: exposes openCustomModal(...) to open chatGptModal from anywhere
670+
(adminforth.config.customization.globalInjections.header).push({
671+
file: this.componentPath('customModal.vue'),
672+
meta: {
673+
pluginInstanceId: this.pluginInstanceId,
674+
}
675+
});
676+
677+
...
678+
679+
}
680+
681+
```
682+
683+
684+
and:
685+
686+
```html title="customModal.vue"
687+
<template>
688+
<!-- Hidden component that registers a global function to open job info modal -->
689+
<Modal
690+
ref="dialogRef"
691+
removeFromDomOnClose
692+
class="p-4"
693+
:beforeCloseFunction="() => { chatGpt.closeCustomModal(); }"
694+
>
695+
<div>
696+
***Modal content***
697+
{{ chatGpt.customModalData }}
698+
</div
699+
</Modal>
700+
</template>
701+
702+
<script setup lang="ts">
703+
import { ref, onMounted, onBeforeUnmount, watch } from 'vue';
704+
import { Modal } from '@/afcl';
705+
import { useChatGptApi } from './useChatGptApi';
706+
707+
const chatGpt = useChatGptApi();
708+
const dialogRef = ref<any>(null);
709+
710+
const props = defineProps<{
711+
meta: {
712+
pluginInstanceId: string;
713+
}
714+
}>();
715+
716+
watch(() => chatGpt.isCustomModalOpened, (newVal) => {
717+
if (newVal) {
718+
dialogRef.value?.open?.();
719+
} else {
720+
dialogRef.value?.close?.();
721+
}
722+
});
723+
</script>
724+
725+
726+
```
727+
728+
4) Re-use this api in your component:
729+
730+
```html
731+
<template>
732+
...
733+
</template>
734+
735+
<script setup>
736+
//diff-add
737+
import { useChatGptApi } from '@/custom/plugins/ChatGptPlugin/useChatGptApi';
738+
...
739+
</script>
740+
```
741+
742+
### How to get the plugin file path from a custom component
743+
744+
`ChatGptPlugin` is the class name that is defined inside `index.ts`.
745+
So if we want to access plugin files from a custom component, we should use the path:
746+
>`@/custom/plugins/*Plugin class name*/*File name*`

adminforth/spa/src/afcl/Select.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,7 @@ onMounted(() => {
287287
// Add scroll listeners if teleportToBody is true
288288
if (props.teleportToBody) {
289289
window.addEventListener('scroll', handleScroll, true);
290+
window.addEventListener('resize', handleScroll);
290291
}
291292
});
292293
@@ -349,6 +350,7 @@ onUnmounted(() => {
349350
// Remove scroll listeners if teleportToBody is true
350351
if (props.teleportToBody) {
351352
window.removeEventListener('scroll', handleScroll, true);
353+
window.removeEventListener('resize', handleScroll);
352354
}
353355
if (searchDebounceHandle) {
354356
clearTimeout(searchDebounceHandle);

dev-demo/Taskfile.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ vars:
5252
- "adminforth-completion-adapter-google-gemini"
5353
- "adminforth-key-value-adapter-redis"
5454
- "adminforth-key-value-adapter-leveldb"
55+
- "adminforth-image-generation-adapter-nano-banana"
5556

5657

5758
tasks:

live-demo/app/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
"@adminforth/image-generation-adapter-openai": "^1.0.7",
2828
"@adminforth/image-vision-adapter-openai": "^1.0.1",
2929
"@adminforth/import-export": "^1.4.6",
30+
"@adminforth/login-captcha": "^1.1.7",
31+
"@adminforth/login-captcha-adapter-cloudflare": "^1.0.16",
3032
"@adminforth/rich-editor": "^1.6.14",
3133
"@adminforth/storage-adapter-amazon-s3": "^1.0.11",
3234
"@adminforth/text-complete": "^1.8.9",

live-demo/app/resources/users.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import AdminForth, { AdminForthDataTypes, AdminForthResourceColumn } from 'adminforth';
22
import ForeignInlineListPlugin from '@adminforth/foreign-inline-list';
33
import { randomUUID } from 'crypto';
4+
import CaptchaAdapterCloudflare from '../../../adapters/adminforth-login-captcha-adapter-cloudflare/index.js';
5+
import CaptchaPlugin from '../../../plugins/adminforth-login-captcha/index.js';
46

57
const blockDemoUsers = async ({ record, adminUser, resource }) => {
68
if (adminUser.dbUser && adminUser.dbUser.role !== 'superadmin') {
@@ -26,6 +28,12 @@ export default {
2628
new ForeignInlineListPlugin({
2729
foreignResourceId: 'audit_logs',
2830
}),
31+
new CaptchaPlugin({
32+
captchaAdapter: new CaptchaAdapterCloudflare({
33+
siteKey: `${process.env.CLOUDFLARE_SITE_KEY}`, // Replace with your site key
34+
secretKey: `${process.env.CLOUDFLARE_SECRET_KEY}`, // Replace with your secret key
35+
}),
36+
}),
2937
],
3038
columns: [
3139
{

0 commit comments

Comments
 (0)