mirror of
https://github.com/nonebot/nonebot2.git
synced 2025-01-19 01:18:19 +08:00
🚧 add handler di example
This commit is contained in:
parent
ec35f292bd
commit
78d0ec847e
@ -1,228 +0,0 @@
|
||||
<template>
|
||||
<v-card flat class="adapters">
|
||||
<v-row class="justify-center">
|
||||
<v-col cols="12" sm="6">
|
||||
<v-text-field
|
||||
v-model="filterText"
|
||||
dense
|
||||
rounded
|
||||
outlined
|
||||
clearable
|
||||
hide-details
|
||||
label="搜索适配器"
|
||||
>
|
||||
<template v-slot:prepend-inner>
|
||||
<div class="v-input__icon v-input__icon--prepend-inner">
|
||||
<v-icon small>fa-filter</v-icon>
|
||||
</div>
|
||||
</template>
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6">
|
||||
<v-dialog v-model="dialog" max-width="600px">
|
||||
<template v-slot:activator="{ on, attrs }">
|
||||
<v-btn dark block color="primary" v-bind="attrs" v-on="on"
|
||||
>发布适配器
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
<span class="headline">适配器信息</span>
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-form ref="newAdapterForm" v-model="valid" lazy-validation>
|
||||
<v-container>
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<v-text-field
|
||||
v-model="newAdapter.name"
|
||||
:rules="rules"
|
||||
label="协议名称"
|
||||
required
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<v-text-field
|
||||
v-model="newAdapter.desc"
|
||||
:rules="rules"
|
||||
label="协议介绍"
|
||||
required
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<v-text-field
|
||||
v-model="newAdapter.link"
|
||||
:rules="rules"
|
||||
label="PyPI 项目名"
|
||||
required
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<v-text-field
|
||||
v-model="newAdapter.id"
|
||||
:rules="rules"
|
||||
label="协议 import 包名"
|
||||
required
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<v-text-field
|
||||
v-model="newAdapter.repo"
|
||||
:rules="rules"
|
||||
label="仓库/主页链接"
|
||||
required
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="blue darken-1" text @click="dialog = false">
|
||||
关闭
|
||||
</v-btn>
|
||||
<v-btn
|
||||
:disabled="!valid"
|
||||
color="blue darken-1"
|
||||
text
|
||||
@click="publishAdapter"
|
||||
>
|
||||
发布
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<v-pagination
|
||||
v-model="page"
|
||||
:length="pageNum"
|
||||
prev-icon="fa-caret-left"
|
||||
next-icon="fa-caret-right"
|
||||
></v-pagination
|
||||
></v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col
|
||||
cols="12"
|
||||
sm="6"
|
||||
v-for="(adapter, index) in displayAdapters"
|
||||
:key="index"
|
||||
>
|
||||
<PublishCard
|
||||
:name="adapter.name"
|
||||
:desc="adapter.desc"
|
||||
:id="adapter.id"
|
||||
:author="adapter.author"
|
||||
:link="adapter.repo"
|
||||
></PublishCard>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<v-pagination
|
||||
v-model="page"
|
||||
:length="pageNum"
|
||||
prev-icon="fa-caret-left"
|
||||
next-icon="fa-caret-right"
|
||||
></v-pagination>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PublishCard from "./PublishCard.vue";
|
||||
import adapters from "../public/adapters.json";
|
||||
|
||||
export default {
|
||||
name: "Adapters",
|
||||
components: {
|
||||
PublishCard,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
adapters: adapters,
|
||||
filterText: "",
|
||||
page: 1,
|
||||
dialog: false,
|
||||
valid: false,
|
||||
rules: [(v) => !!v || "This field is required"],
|
||||
newAdapter: {
|
||||
name: null,
|
||||
desc: null,
|
||||
id: null,
|
||||
link: null,
|
||||
repo: null,
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
pageNum() {
|
||||
return Math.ceil(this.filteredAdapters.length / 10);
|
||||
},
|
||||
filteredAdapters() {
|
||||
return this.adapters.filter((adapter) => {
|
||||
return (
|
||||
adapter.id.indexOf(this.filterText || "") != -1 ||
|
||||
adapter.name.indexOf(this.filterText || "") != -1 ||
|
||||
adapter.desc.indexOf(this.filterText || "") != -1 ||
|
||||
adapter.author.indexOf(this.filterText || "") != -1
|
||||
);
|
||||
});
|
||||
},
|
||||
displayAdapters() {
|
||||
return this.filteredAdapters.slice((this.page - 1) * 10, this.page * 10);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
publishAdapter() {
|
||||
if (!this.$refs.newAdapterForm.validate()) {
|
||||
return;
|
||||
}
|
||||
this.dialog = false;
|
||||
const title = encodeURIComponent(
|
||||
`Adapter: ${this.newAdapter.name}`
|
||||
).replace(/%2B/gi, "+");
|
||||
const body = encodeURIComponent(
|
||||
`
|
||||
**协议名称:**
|
||||
|
||||
${this.newAdapter.name}
|
||||
|
||||
**协议功能:**
|
||||
|
||||
${this.newAdapter.desc}
|
||||
|
||||
**PyPI 项目名:**
|
||||
|
||||
${this.newAdapter.link}
|
||||
|
||||
**协议 import 包名:**
|
||||
|
||||
${this.newAdapter.id}
|
||||
|
||||
**协议项目仓库/主页链接:**
|
||||
|
||||
${this.newAdapter.repo}
|
||||
|
||||
<!-- DO NOT EDIT ! -->
|
||||
<!--
|
||||
- id: ${this.newAdapter.id}
|
||||
- link: ${this.newAdapter.link}
|
||||
- name: ${this.newAdapter.name}
|
||||
- desc: ${this.newAdapter.desc}
|
||||
- repo: ${this.newAdapter.repo}
|
||||
-->
|
||||
`.trim()
|
||||
).replace(/%2B/gi, "+");
|
||||
window.open(
|
||||
`https://github.com/nonebot/nonebot2/issues/new?title=${title}&body=${body}&labels=Adapter`
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
@ -1,194 +0,0 @@
|
||||
<template>
|
||||
<v-card flat class="bots">
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6">
|
||||
<v-text-field
|
||||
v-model="filterText"
|
||||
dense
|
||||
rounded
|
||||
outlined
|
||||
clearable
|
||||
hide-details
|
||||
label="搜索机器人"
|
||||
>
|
||||
<template v-slot:prepend-inner>
|
||||
<div class="v-input__icon v-input__icon--prepend-inner">
|
||||
<v-icon small>fa-filter</v-icon>
|
||||
</div>
|
||||
</template>
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6">
|
||||
<v-dialog v-model="dialog" max-width="600px">
|
||||
<template v-slot:activator="{ on, attrs }">
|
||||
<v-btn dark block color="primary" v-bind="attrs" v-on="on"
|
||||
>发布机器人
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
<span class="headline">机器人信息</span>
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-form ref="newBotForm" v-model="valid" lazy-validation>
|
||||
<v-container>
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<v-text-field
|
||||
v-model="newBot.name"
|
||||
:rules="rules"
|
||||
label="机器人名称"
|
||||
required
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<v-text-field
|
||||
v-model="newBot.desc"
|
||||
:rules="rules"
|
||||
label="机器人介绍"
|
||||
required
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<v-text-field
|
||||
v-model="newBot.repo"
|
||||
:rules="rules"
|
||||
label="仓库/主页链接"
|
||||
required
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="blue darken-1" text @click="dialog = false">
|
||||
关闭
|
||||
</v-btn>
|
||||
<v-btn
|
||||
:disabled="!valid"
|
||||
color="blue darken-1"
|
||||
text
|
||||
@click="publishBot"
|
||||
>
|
||||
发布
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<v-pagination
|
||||
v-model="page"
|
||||
:length="pageNum"
|
||||
prev-icon="fa-caret-left"
|
||||
next-icon="fa-caret-right"
|
||||
></v-pagination>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" v-for="(bot, index) in displayBots" :key="index">
|
||||
<PublishCard
|
||||
:name="bot.name"
|
||||
:desc="bot.desc"
|
||||
:author="bot.author"
|
||||
:link="bot.repo"
|
||||
></PublishCard>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<v-pagination
|
||||
v-model="page"
|
||||
:length="pageNum"
|
||||
prev-icon="fa-caret-left"
|
||||
next-icon="fa-caret-right"
|
||||
></v-pagination>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PublishCard from "./PublishCard.vue";
|
||||
import bots from "../public/bots.json";
|
||||
|
||||
export default {
|
||||
name: "Bots",
|
||||
components: {
|
||||
PublishCard,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
bots: bots,
|
||||
filterText: "",
|
||||
page: 1,
|
||||
dialog: false,
|
||||
valid: false,
|
||||
rules: [(v) => !!v || "This field is required"],
|
||||
newBot: {
|
||||
name: null,
|
||||
desc: null,
|
||||
repo: null,
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
pageNum() {
|
||||
return Math.ceil(this.filteredBots.length / 10);
|
||||
},
|
||||
filteredBots() {
|
||||
return this.bots.filter((bot) => {
|
||||
return (
|
||||
bot.name.indexOf(this.filterText || "") != -1 ||
|
||||
bot.desc.indexOf(this.filterText || "") != -1 ||
|
||||
bot.author.indexOf(this.filterText || "") != -1
|
||||
);
|
||||
});
|
||||
},
|
||||
displayBots() {
|
||||
return this.filteredBots.slice((this.page - 1) * 10, this.page * 10);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
publishBot() {
|
||||
if (!this.$refs.newBotForm.validate()) {
|
||||
return;
|
||||
}
|
||||
this.dialog = false;
|
||||
const title = encodeURIComponent(`Bot: ${this.newBot.name}`).replace(
|
||||
/%2B/gi,
|
||||
"+"
|
||||
);
|
||||
const body = encodeURIComponent(
|
||||
`
|
||||
**机器人名称:**
|
||||
|
||||
${this.newBot.name}
|
||||
|
||||
**机器人功能:**
|
||||
|
||||
${this.newBot.desc}
|
||||
|
||||
**机器人项目仓库/主页链接:**
|
||||
|
||||
${this.newBot.repo}
|
||||
|
||||
<!-- DO NOT EDIT ! -->
|
||||
<!--
|
||||
- name: ${this.newBot.name}
|
||||
- desc: ${this.newBot.desc}
|
||||
- repo: ${this.newBot.repo}
|
||||
-->
|
||||
`.trim()
|
||||
).replace(/%2B/gi, "+");
|
||||
window.open(
|
||||
`https://github.com/nonebot/nonebot2/issues/new?title=${title}&body=${body}&labels=Bot`
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
@ -1,219 +0,0 @@
|
||||
<template>
|
||||
<div class="qq-chat">
|
||||
<v-app>
|
||||
<v-main>
|
||||
<v-card class="elevation-6">
|
||||
<v-toolbar color="primary" dark dense flat>
|
||||
<v-row no-gutters>
|
||||
<v-col>
|
||||
<v-row no-gutters justify="space-between">
|
||||
<v-col cols="auto">
|
||||
<v-icon small>fa-chevron-left</v-icon>
|
||||
</v-col>
|
||||
<v-col cols="auto">
|
||||
<h3>🔥</h3>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-col>
|
||||
<v-col cols="auto">
|
||||
<h3 class="white--text">NoneBot</h3>
|
||||
</v-col>
|
||||
<v-col class="text-right">
|
||||
<v-icon small>fa-user</v-icon>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-toolbar>
|
||||
<v-container fluid ref="chat" class="chat chat-bg">
|
||||
<template v-for="(item, index) in messages">
|
||||
<v-row
|
||||
v-if="item.position === 'right'"
|
||||
justify="end"
|
||||
:key="index"
|
||||
class="message wow animate__fadeInRight"
|
||||
data-wow-duration="0.7s"
|
||||
>
|
||||
<div
|
||||
class="message-box"
|
||||
v-html="
|
||||
item.msg.replace(/\n/g, '<br/>').replace(/ /g, ' ')
|
||||
"
|
||||
></div>
|
||||
<v-avatar color="blue lighten-2" size="36">
|
||||
<v-icon small>fa-user</v-icon>
|
||||
</v-avatar>
|
||||
</v-row>
|
||||
<v-row
|
||||
v-else-if="item.position === 'left'"
|
||||
justify="start"
|
||||
:key="index"
|
||||
class="message wow animate__fadeInLeft"
|
||||
data-wow-duration="0.7s"
|
||||
>
|
||||
<v-avatar color="transparent" size="36">
|
||||
<v-img src="/logo.png"></v-img>
|
||||
</v-avatar>
|
||||
<div
|
||||
class="message-box"
|
||||
v-html="
|
||||
item.msg.replace(/\n/g, '<br/>').replace(/ /g, ' ')
|
||||
"
|
||||
></div>
|
||||
</v-row>
|
||||
<v-row
|
||||
v-else
|
||||
justify="center"
|
||||
:key="index"
|
||||
class="notify mt-1 wow animate__fadeIn"
|
||||
data-wow-duration="0.7s"
|
||||
>
|
||||
<div class="notify-box">
|
||||
<span style="display: inline; white-space: nowrap">
|
||||
<v-icon x-small color="blue" left>fa-info-circle</v-icon>
|
||||
</span>
|
||||
<span
|
||||
v-html="
|
||||
item.msg.replace(/\n/g, '<br/>').replace(/ /g, ' ')
|
||||
"
|
||||
></span>
|
||||
</div>
|
||||
</v-row>
|
||||
</template>
|
||||
</v-container>
|
||||
<v-container fluid class="chat-bg py-0">
|
||||
<v-row dense class="mx-0">
|
||||
<v-col>
|
||||
<v-text-field
|
||||
dense
|
||||
solo
|
||||
hide-details
|
||||
height="28px"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="auto">
|
||||
<v-btn
|
||||
style="font-size: 0.8rem"
|
||||
color="primary"
|
||||
small
|
||||
rounded
|
||||
depressed
|
||||
>发送</v-btn
|
||||
>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row class="text-center" no-gutters>
|
||||
<v-col class="pa-1" cols="2">
|
||||
<v-icon small>fa-microphone</v-icon>
|
||||
</v-col>
|
||||
<v-col class="pa-1" cols="2">
|
||||
<v-icon small>fa-image</v-icon>
|
||||
</v-col>
|
||||
<v-col class="pa-1" cols="2">
|
||||
<v-icon small>fa-camera</v-icon>
|
||||
</v-col>
|
||||
<v-col class="pa-1" cols="2">
|
||||
<v-icon small>fa-wallet</v-icon>
|
||||
</v-col>
|
||||
<v-col class="pa-1" cols="2">
|
||||
<v-icon small>fa-smile-wink</v-icon>
|
||||
</v-col>
|
||||
<v-col class="pa-1" cols="2">
|
||||
<v-icon small>fa-plus-circle</v-icon>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</v-card>
|
||||
</v-main>
|
||||
</v-app>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { WOW } from "wowjs";
|
||||
import "animate.css/animate.min.css";
|
||||
|
||||
export default {
|
||||
name: "Messenger",
|
||||
props: {
|
||||
messages: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
initWOW: function() {
|
||||
new WOW({
|
||||
noxClass: "wow",
|
||||
animateClass: "animate__animated",
|
||||
offset: 0,
|
||||
mobile: true,
|
||||
live: true,
|
||||
}).init();
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.initWOW();
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.wow {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.chat {
|
||||
min-height: 150px;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
.chat-bg {
|
||||
background-color: #f3f6f9;
|
||||
}
|
||||
|
||||
.message {
|
||||
position: relative;
|
||||
margin: 0;
|
||||
}
|
||||
.message .message-box {
|
||||
position: relative;
|
||||
width: fit-content;
|
||||
max-width: 55%;
|
||||
border-radius: 0.5rem;
|
||||
padding: 0.6rem 0.8rem;
|
||||
margin: 0.4rem 0.8rem;
|
||||
background-color: #fff;
|
||||
}
|
||||
.message .message-box::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: 100%;
|
||||
top: 0;
|
||||
width: 8px;
|
||||
height: 12px;
|
||||
color: #fff;
|
||||
border: 0 solid transparent;
|
||||
border-bottom: 7px solid;
|
||||
border-radius: 0 0 0 8px;
|
||||
}
|
||||
.message.justify-end .message-box::after {
|
||||
left: 100%;
|
||||
right: auto;
|
||||
border-radius: 0 0 8px 0;
|
||||
}
|
||||
|
||||
.notify {
|
||||
position: relative;
|
||||
}
|
||||
.notify .notify-box {
|
||||
max-width: 70%;
|
||||
background: #e0e0e0;
|
||||
border-radius: 10px;
|
||||
padding: 5px 12px;
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.v-application--wrap {
|
||||
min-height: 0 !important;
|
||||
}
|
||||
</style>
|
@ -1,230 +0,0 @@
|
||||
<template>
|
||||
<v-card flat class="plugins">
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6">
|
||||
<v-text-field
|
||||
v-model="filterText"
|
||||
dense
|
||||
rounded
|
||||
outlined
|
||||
clearable
|
||||
hide-details
|
||||
label="搜索插件"
|
||||
>
|
||||
<template v-slot:prepend-inner>
|
||||
<div class="v-input__icon v-input__icon--prepend-inner">
|
||||
<v-icon small>fa-filter</v-icon>
|
||||
</div>
|
||||
</template>
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6">
|
||||
<v-dialog v-model="dialog" max-width="600px">
|
||||
<template v-slot:activator="{ on, attrs }">
|
||||
<v-btn dark block color="primary" v-bind="attrs" v-on="on"
|
||||
>发布插件
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
<span class="headline">插件信息</span>
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-form ref="newPluginForm" v-model="valid" lazy-validation>
|
||||
<v-container>
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<v-text-field
|
||||
v-model="newPlugin.name"
|
||||
:rules="rules"
|
||||
label="插件名称"
|
||||
required
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<v-text-field
|
||||
v-model="newPlugin.desc"
|
||||
:rules="rules"
|
||||
label="插件介绍"
|
||||
required
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<v-text-field
|
||||
v-model="newPlugin.link"
|
||||
:rules="rules"
|
||||
label="PyPI 项目名"
|
||||
required
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<v-text-field
|
||||
v-model="newPlugin.id"
|
||||
:rules="rules"
|
||||
label="插件 import 包名"
|
||||
required
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<v-text-field
|
||||
v-model="newPlugin.repo"
|
||||
:rules="rules"
|
||||
label="仓库/主页链接"
|
||||
required
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="blue darken-1" text @click="dialog = false">
|
||||
关闭
|
||||
</v-btn>
|
||||
<v-btn
|
||||
:disabled="!valid"
|
||||
color="blue darken-1"
|
||||
text
|
||||
@click="publishPlugin"
|
||||
>
|
||||
发布
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<v-pagination
|
||||
v-model="page"
|
||||
:length="pageNum"
|
||||
prev-icon="fa-caret-left"
|
||||
next-icon="fa-caret-right"
|
||||
></v-pagination>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col
|
||||
cols="12"
|
||||
sm="6"
|
||||
v-for="(plugin, index) in displayPlugins"
|
||||
:key="index"
|
||||
>
|
||||
<PublishCard
|
||||
:name="plugin.name"
|
||||
:desc="plugin.desc"
|
||||
:id="plugin.id"
|
||||
:author="plugin.author"
|
||||
:link="plugin.repo"
|
||||
text="点此复制安装命令"
|
||||
:command="`nb plugin install ${plugin.id}`"
|
||||
></PublishCard>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<v-pagination
|
||||
v-model="page"
|
||||
:length="pageNum"
|
||||
prev-icon="fa-caret-left"
|
||||
next-icon="fa-caret-right"
|
||||
></v-pagination>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PublishCard from "./PublishCard.vue";
|
||||
import plugins from "../public/plugins.json";
|
||||
|
||||
export default {
|
||||
name: "Plugins",
|
||||
components: {
|
||||
PublishCard,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
plugins: plugins,
|
||||
filterText: "",
|
||||
page: 1,
|
||||
dialog: false,
|
||||
valid: false,
|
||||
rules: [(v) => !!v || "This field is required"],
|
||||
newPlugin: {
|
||||
name: null,
|
||||
desc: null,
|
||||
id: null,
|
||||
link: null,
|
||||
repo: null,
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
pageNum() {
|
||||
return Math.ceil(this.filteredPlugins.length / 10);
|
||||
},
|
||||
filteredPlugins() {
|
||||
return this.plugins.filter((plugin) => {
|
||||
return (
|
||||
plugin.id.indexOf(this.filterText || "") != -1 ||
|
||||
plugin.name.indexOf(this.filterText || "") != -1 ||
|
||||
plugin.desc.indexOf(this.filterText || "") != -1 ||
|
||||
plugin.author.indexOf(this.filterText || "") != -1
|
||||
);
|
||||
});
|
||||
},
|
||||
displayPlugins() {
|
||||
return this.filteredPlugins.slice((this.page - 1) * 10, this.page * 10);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
publishPlugin() {
|
||||
if (!this.$refs.newPluginForm.validate()) {
|
||||
return;
|
||||
}
|
||||
this.dialog = false;
|
||||
const title = encodeURIComponent(
|
||||
`Plugin: ${this.newPlugin.name}`
|
||||
).replace(/%2B/gi, "+");
|
||||
const body = encodeURIComponent(
|
||||
`
|
||||
**插件名称:**
|
||||
|
||||
${this.newPlugin.name}
|
||||
|
||||
**插件功能:**
|
||||
|
||||
${this.newPlugin.desc}
|
||||
|
||||
**PyPI 项目名:**
|
||||
|
||||
${this.newPlugin.link}
|
||||
|
||||
**插件 import 包名:**
|
||||
|
||||
${this.newPlugin.id}
|
||||
|
||||
**插件项目仓库/主页链接:**
|
||||
|
||||
${this.newPlugin.repo}
|
||||
|
||||
<!-- DO NOT EDIT ! -->
|
||||
<!--
|
||||
- id: ${this.newPlugin.id}
|
||||
- link: ${this.newPlugin.link}
|
||||
- name: ${this.newPlugin.name}
|
||||
- desc: ${this.newPlugin.desc}
|
||||
- repo: ${this.newPlugin.repo}
|
||||
-->
|
||||
`.trim()
|
||||
).replace(/%2B/gi, "+");
|
||||
window.open(
|
||||
`https://github.com/nonebot/nonebot2/issues/new?title=${title}&body=${body}&labels=Plugin`
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
@ -1,84 +0,0 @@
|
||||
<template>
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
{{ name }}
|
||||
<v-spacer></v-spacer>
|
||||
<a
|
||||
class="repo-link"
|
||||
v-if="repoLink(link)"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
:title="link"
|
||||
:href="repoLink(link)"
|
||||
>
|
||||
<v-icon>fab fa-github</v-icon>
|
||||
</a>
|
||||
</v-card-title>
|
||||
<v-card-text>{{ desc }}</v-card-text>
|
||||
<v-card-text>
|
||||
<template v-if="id">
|
||||
<v-icon x-small>fa-fingerprint</v-icon>
|
||||
{{ id }}
|
||||
</template>
|
||||
<v-icon x-small class="ml-2">fa-user</v-icon>
|
||||
{{ author }}
|
||||
</v-card-text>
|
||||
<v-card-actions v-if="showCommand">
|
||||
<v-btn block depressed class="btn-copy" @click="copyCommand()">
|
||||
{{ text }}
|
||||
<v-icon right small>fa-copy</v-icon>
|
||||
</v-btn>
|
||||
<v-snackbar v-model="snackbar">复制成功!</v-snackbar>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import copy from "copy-to-clipboard";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
name: String,
|
||||
desc: String,
|
||||
id: String,
|
||||
author: String,
|
||||
link: String,
|
||||
text: String,
|
||||
command: String,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
snackbar: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
showCommand() {
|
||||
return this.text && this.command;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
repoLink(repo) {
|
||||
if (repo) {
|
||||
return /^https?:/.test(repo) ? repo : `https://github.com/${repo}`;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
copyCommand() {
|
||||
copy(this.command, {
|
||||
format: "text/plain",
|
||||
});
|
||||
this.snackbar = true;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.repo-link {
|
||||
text-decoration: none !important;
|
||||
display: inline-block;
|
||||
}
|
||||
.repo-link:hover i {
|
||||
color: #ea5252;
|
||||
}
|
||||
</style>
|
@ -1,70 +0,0 @@
|
||||
<template>
|
||||
<div class="store">
|
||||
<v-app>
|
||||
<v-main>
|
||||
<v-card>
|
||||
<v-toolbar dense flat>
|
||||
<v-tabs v-model="tab" centered>
|
||||
<v-tab v-for="(name, index) in tabs" :key="index">{{
|
||||
name
|
||||
}}</v-tab>
|
||||
</v-tabs>
|
||||
</v-toolbar>
|
||||
<v-tabs-items class="sub-item pt-1" v-model="tab">
|
||||
<v-tab-item>
|
||||
<Adapter></Adapter>
|
||||
</v-tab-item>
|
||||
<v-tab-item>
|
||||
<Plugin></Plugin>
|
||||
</v-tab-item>
|
||||
<v-tab-item>
|
||||
<Bot></Bot>
|
||||
</v-tab-item>
|
||||
</v-tabs-items>
|
||||
</v-card>
|
||||
</v-main>
|
||||
</v-app>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Adapter from "./Adapter.vue";
|
||||
import Plugin from "./Plugin.vue";
|
||||
import Bot from "./Bot.vue";
|
||||
|
||||
export default {
|
||||
name: "Store",
|
||||
components: {
|
||||
Adapter,
|
||||
Plugin,
|
||||
Bot,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tab: 1,
|
||||
tabs: {
|
||||
0: "协议",
|
||||
1: "插件",
|
||||
2: "机器人",
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {},
|
||||
methods: {},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.v-application--wrap {
|
||||
min-height: 0 !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style scoped>
|
||||
.store {
|
||||
margin: 0 -20px;
|
||||
}
|
||||
.sub-item {
|
||||
padding: 0 10px;
|
||||
}
|
||||
</style>
|
@ -183,19 +183,20 @@ class BotParam(Param):
|
||||
) -> Optional["BotParam"]:
|
||||
if param.default == param.empty:
|
||||
if generic_check_issubclass(param.annotation, Bot):
|
||||
dependent.pre_checkers.append(
|
||||
_BotChecker(
|
||||
Required,
|
||||
field=ModelField(
|
||||
name="",
|
||||
type_=param.annotation,
|
||||
class_validators=None,
|
||||
model_config=CustomConfig,
|
||||
default=None,
|
||||
required=True,
|
||||
),
|
||||
if param.annotation is not Bot:
|
||||
dependent.pre_checkers.append(
|
||||
_BotChecker(
|
||||
Required,
|
||||
field=ModelField(
|
||||
name="",
|
||||
type_=param.annotation,
|
||||
class_validators=None,
|
||||
model_config=CustomConfig,
|
||||
default=None,
|
||||
required=True,
|
||||
),
|
||||
)
|
||||
)
|
||||
)
|
||||
return cls(Required)
|
||||
elif param.annotation == param.empty and name == "bot":
|
||||
return cls(Required)
|
||||
@ -223,19 +224,20 @@ class EventParam(Param):
|
||||
) -> Optional["EventParam"]:
|
||||
if param.default == param.empty:
|
||||
if generic_check_issubclass(param.annotation, Event):
|
||||
dependent.pre_checkers.append(
|
||||
_EventChecker(
|
||||
Required,
|
||||
field=ModelField(
|
||||
name="",
|
||||
type_=param.annotation,
|
||||
class_validators=None,
|
||||
model_config=CustomConfig,
|
||||
default=None,
|
||||
required=True,
|
||||
),
|
||||
if param.annotation is not Event:
|
||||
dependent.pre_checkers.append(
|
||||
_EventChecker(
|
||||
Required,
|
||||
field=ModelField(
|
||||
name="",
|
||||
type_=param.annotation,
|
||||
class_validators=None,
|
||||
model_config=CustomConfig,
|
||||
default=None,
|
||||
required=True,
|
||||
),
|
||||
)
|
||||
)
|
||||
)
|
||||
return cls(Required)
|
||||
elif param.annotation == param.empty and name == "event":
|
||||
return cls(Required)
|
||||
|
@ -15,3 +15,252 @@ options:
|
||||
## 添加一个处理依赖
|
||||
|
||||
## 事件处理流程
|
||||
|
||||
## 获取上下文信息
|
||||
|
||||
### Bot
|
||||
|
||||
```python {7-9}
|
||||
from typing import Union
|
||||
|
||||
from nonebot.adapters import Bot
|
||||
from nonebot.adapters.ding import Bot as DingBot
|
||||
from nonebot.adapters.onebot.v11 import Bot as OneBotV11Bot
|
||||
|
||||
async def _(foo: Bot): ...
|
||||
async def _(foo: Union[DingBot, OneBotV11Bot]): ...
|
||||
async def _(bot): ... # 兼容性处理
|
||||
```
|
||||
|
||||
### Event
|
||||
|
||||
```python {6-8}
|
||||
from typing import Union
|
||||
|
||||
from nonebot.adapters import Event
|
||||
from nonebot.adapters.onebot.v11 import PrivateMessageEvent, GroupMessageEvent
|
||||
|
||||
async def _(foo: Event): ...
|
||||
async def _(foo: Union[PrivateMessageEvent, GroupMessageEvent]): ...
|
||||
async def _(event): ... # 兼容性处理
|
||||
```
|
||||
|
||||
### EventType
|
||||
|
||||
```python {3}
|
||||
from nonebot.params import EventType
|
||||
|
||||
async def _(foo: str = EventType()): ...
|
||||
```
|
||||
|
||||
### EventMessage
|
||||
|
||||
```python {4}
|
||||
from nonebot.adapters import Message
|
||||
from nonebot.params import EventMessage
|
||||
|
||||
async def _(foo: str = EventMessage()): ...
|
||||
```
|
||||
|
||||
### EventPlainText
|
||||
|
||||
```python {3}
|
||||
from nonebot.params import EventPlainText
|
||||
|
||||
async def _(foo: str = EventPlainText()): ...
|
||||
```
|
||||
|
||||
### EventToMe
|
||||
|
||||
```python {3}
|
||||
from nonebot.params import EventToMe
|
||||
|
||||
async def _(foo: bool = EventToMe()): ...
|
||||
```
|
||||
|
||||
### State
|
||||
|
||||
```python {4}
|
||||
from nonebot.params import State
|
||||
from nonebot.typing import T_State
|
||||
|
||||
async def _(foo: T_State = State()): ...
|
||||
```
|
||||
|
||||
### Command
|
||||
|
||||
```python {7}
|
||||
from nonebot import on_command
|
||||
from nonebot.params import Command
|
||||
|
||||
matcher = on_command("cmd")
|
||||
|
||||
@matcher.handle()
|
||||
async def _(foo: Tuple[str, ...] = Command()): ...
|
||||
```
|
||||
|
||||
### CommandArg
|
||||
|
||||
```python {8}
|
||||
from nonebot import on_command
|
||||
from nonebot.adapters import Message
|
||||
from nonebot.params import CommandArg
|
||||
|
||||
matcher = on_command("cmd")
|
||||
|
||||
@matcher.handle()
|
||||
async def _(foo: Message = CommandArg()): ...
|
||||
```
|
||||
|
||||
### ShellCommandArgs
|
||||
|
||||
```python {7}
|
||||
from nonebot import on_command
|
||||
from nonebot.params import ShellCommandArgs
|
||||
|
||||
matcher = on_shell_command("cmd", parser)
|
||||
|
||||
@matcher.handle()
|
||||
async def _(foo: Dict[str, Any] = ShellCommandArgs()): ...
|
||||
```
|
||||
|
||||
### ShellCommandArgv
|
||||
|
||||
```python {7}
|
||||
from nonebot import on_command
|
||||
from nonebot.params import ShellCommandArgs
|
||||
|
||||
matcher = on_shell_command("cmd")
|
||||
|
||||
@matcher.handle()
|
||||
async def _(foo: List[str] = ShellCommandArgv()): ...
|
||||
```
|
||||
|
||||
### RegexMatched
|
||||
|
||||
```python {7}
|
||||
from nonebot import on_regex
|
||||
from nonebot.params import RegexMatched
|
||||
|
||||
matcher = on_regex("regex")
|
||||
|
||||
@matcher.handle()
|
||||
async def _(foo: str = RegexMatched()): ...
|
||||
```
|
||||
|
||||
### RegexGroup
|
||||
|
||||
```python {7}
|
||||
from nonebot import on_regex
|
||||
from nonebot.params import RegexGroup
|
||||
|
||||
matcher = on_regex("regex")
|
||||
|
||||
@matcher.handle()
|
||||
async def _(foo: Tuple[Any, ...] = RegexGroup()): ...
|
||||
```
|
||||
|
||||
### RegexDict
|
||||
|
||||
```python {7}
|
||||
from nonebot import on_regex
|
||||
from nonebot.params import RegexDict
|
||||
|
||||
matcher = on_regex("regex")
|
||||
|
||||
@matcher.handle()
|
||||
async def _(foo: Dict[str, Any] = RegexDict()): ...
|
||||
```
|
||||
|
||||
### Matcher
|
||||
|
||||
```python {7}
|
||||
from nonebot import on_message
|
||||
from nonebot.matcher import Matcher
|
||||
|
||||
foo = on_message()
|
||||
|
||||
@foo.handle()
|
||||
async def _(matcher: Matcher): ...
|
||||
```
|
||||
|
||||
### Received
|
||||
|
||||
```python {8}
|
||||
from nonebot import on_message
|
||||
from nonebot.adapters import Event
|
||||
from nonebot.params import Received
|
||||
|
||||
matcher = on_message()
|
||||
|
||||
@matcher.receive("id")
|
||||
async def _(foo: Event = Received("id")): ...
|
||||
```
|
||||
|
||||
### LastReceived
|
||||
|
||||
```python {8}
|
||||
from nonebot import on_message
|
||||
from nonebot.adapters import Event
|
||||
from nonebot.params import LastReceived
|
||||
|
||||
matcher = on_message()
|
||||
|
||||
@matcher.receive("any")
|
||||
async def _(foo: Event = LastReceived()): ...
|
||||
```
|
||||
|
||||
### Arg
|
||||
|
||||
```python {8-9}
|
||||
from nonebot.params import Arg
|
||||
from nonebot import on_message
|
||||
from nonebot.adapters import Message
|
||||
|
||||
matcher = on_message()
|
||||
|
||||
@matcher.got("key")
|
||||
async def _(key: Message = Arg()): ...
|
||||
async def _(foo: Message = Arg("key")): ...
|
||||
```
|
||||
|
||||
### ArgStr
|
||||
|
||||
```python {7-8}
|
||||
from nonebot import on_message
|
||||
from nonebot.params import ArgStr
|
||||
|
||||
matcher = on_message()
|
||||
|
||||
@matcher.got("key")
|
||||
async def _(key: str = ArgStr()): ...
|
||||
async def _(foo: str = ArgStr("key")): ...
|
||||
```
|
||||
|
||||
### ArgPlainText
|
||||
|
||||
```python {7-8}
|
||||
from nonebot import on_message
|
||||
from nonebot.params import ArgPlainText
|
||||
|
||||
matcher = on_message()
|
||||
|
||||
@matcher.got("key")
|
||||
async def _(key: str = ArgPlainText()): ...
|
||||
async def _(foo: str = ArgPlainText("key")): ...
|
||||
```
|
||||
|
||||
### Exception
|
||||
|
||||
```python {4}
|
||||
from nonebot.message import run_postprocessor
|
||||
|
||||
@run_postprocessor
|
||||
async def _(e: Exception): ...
|
||||
```
|
||||
|
||||
### Default
|
||||
|
||||
```python {1}
|
||||
async def _(foo="bar"): ...
|
||||
```
|
||||
|
@ -1,12 +1,20 @@
|
||||
import React from "react";
|
||||
import clsx from "clsx";
|
||||
import React, { useRef, useState } from "react";
|
||||
import { ChromePicker } from "react-color";
|
||||
import { usePagination } from "react-use-pagination";
|
||||
|
||||
import adapters from "../../static/adapters.json";
|
||||
import { useFilteredObjs } from "../libs/store";
|
||||
import { Tag, useFilteredObjs } from "../libs/store";
|
||||
import Card from "./Card";
|
||||
import Modal from "./Modal";
|
||||
import ModalAction from "./ModalAction";
|
||||
import ModalContent from "./ModalContent";
|
||||
import ModalTitle from "./ModalTitle";
|
||||
import Paginate from "./Paginate";
|
||||
import TagComponent from "./Tag";
|
||||
|
||||
export default function Adapter(): JSX.Element {
|
||||
const [modalOpen, setModalOpen] = useState<boolean>(false);
|
||||
const {
|
||||
filter,
|
||||
setFilter,
|
||||
@ -20,6 +28,93 @@ export default function Adapter(): JSX.Element {
|
||||
const { startIndex, endIndex } = props;
|
||||
const currentAdapters = filteredAdapters.slice(startIndex, endIndex + 1);
|
||||
|
||||
const [form, setForm] = useState<{
|
||||
name: string;
|
||||
desc: string;
|
||||
projectLink: string;
|
||||
moduleName: string;
|
||||
homepage: string;
|
||||
}>({ name: "", desc: "", projectLink: "", moduleName: "", homepage: "" });
|
||||
|
||||
const ref = useRef<HTMLInputElement>(null);
|
||||
const [tags, setTags] = useState<Tag[]>([]);
|
||||
const [label, setLabel] = useState<string>("");
|
||||
const [color, setColor] = useState<string>("#ea5252");
|
||||
|
||||
const onSubmit = () => {
|
||||
setModalOpen(false);
|
||||
const title = encodeURIComponent(`Adapter: ${form.name}`).replace(
|
||||
/%2B/gi,
|
||||
"+"
|
||||
);
|
||||
const body = encodeURIComponent(
|
||||
`
|
||||
**协议名称:**
|
||||
|
||||
${form.name}
|
||||
|
||||
**协议功能:**
|
||||
|
||||
${form.desc}
|
||||
|
||||
**PyPI 项目名:**
|
||||
|
||||
${form.projectLink}
|
||||
|
||||
**协议 import 包名:**
|
||||
|
||||
${form.moduleName}
|
||||
|
||||
**协议项目仓库/主页链接:**
|
||||
|
||||
${form.homepage}
|
||||
|
||||
**标签:**
|
||||
|
||||
${JSON.stringify(tags)}
|
||||
`.trim()
|
||||
).replace(/%2B/gi, "+");
|
||||
window.open(
|
||||
`https://github.com/nonebot/nonebot2/issues/new?title=${title}&body=${body}&labels=Adapter`
|
||||
);
|
||||
};
|
||||
const onChange = (event) => {
|
||||
const target = event.target;
|
||||
const value = target.type === "checkbox" ? target.checked : target.value;
|
||||
const name = target.name;
|
||||
|
||||
setForm({
|
||||
...form,
|
||||
[name]: value,
|
||||
});
|
||||
event.preventDefault();
|
||||
};
|
||||
const onChangeLabel = (event) => {
|
||||
setLabel(event.target.value);
|
||||
};
|
||||
const onChangeColor = (color) => {
|
||||
setColor(color.hex);
|
||||
};
|
||||
const validateTag = () => {
|
||||
return label.length >= 1 && label.length <= 10;
|
||||
};
|
||||
const newTag = () => {
|
||||
if (tags.length >= 3) {
|
||||
return;
|
||||
}
|
||||
if (validateTag()) {
|
||||
const tag = { label, color };
|
||||
setTags([...tags, tag]);
|
||||
}
|
||||
};
|
||||
const delTag = (index: number) => {
|
||||
setTags(tags.filter((_, i) => i !== index));
|
||||
};
|
||||
const insertTagType = (text: string) => {
|
||||
setLabel(text + label);
|
||||
ref.current.value = text + label;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 mt-4 px-4">
|
||||
@ -29,7 +124,10 @@ export default function Adapter(): JSX.Element {
|
||||
placeholder="搜索适配器"
|
||||
onChange={(event) => setFilter(event.target.value)}
|
||||
/>
|
||||
<button className="w-full rounded-lg bg-hero text-white">
|
||||
<button
|
||||
className="w-full rounded-lg bg-hero text-white"
|
||||
onClick={() => setModalOpen(true)}
|
||||
>
|
||||
发布适配器
|
||||
</button>
|
||||
</div>
|
||||
@ -44,6 +142,129 @@ export default function Adapter(): JSX.Element {
|
||||
<div className="grid grid-cols-1 p-4">
|
||||
<Paginate {...props} />
|
||||
</div>
|
||||
<Modal active={modalOpen} setActive={setModalOpen}>
|
||||
<ModalTitle title={"适配器信息"} />
|
||||
<ModalContent>
|
||||
<form onSubmit={onSubmit}>
|
||||
<div className="grid grid-cols-1 gap-4 p-4">
|
||||
<label className="flex flex-wrap">
|
||||
<span className="mr-2">适配器名称:</span>
|
||||
<input
|
||||
type="text"
|
||||
name="name"
|
||||
maxLength={20}
|
||||
className="px-2 flex-grow rounded bg-light-nonepress-200 dark:bg-dark-nonepress-200"
|
||||
onChange={onChange}
|
||||
/>
|
||||
</label>
|
||||
<label className="flex flex-wrap">
|
||||
<span className="mr-2">适配器介绍:</span>
|
||||
<input
|
||||
type="text"
|
||||
name="desc"
|
||||
className="px-2 flex-grow rounded bg-light-nonepress-200 dark:bg-dark-nonepress-200"
|
||||
onChange={onChange}
|
||||
/>
|
||||
</label>
|
||||
<label className="flex flex-wrap">
|
||||
<span className="mr-2">PyPI 项目名:</span>
|
||||
<input
|
||||
type="text"
|
||||
name="projectLink"
|
||||
className="px-2 flex-grow rounded bg-light-nonepress-200 dark:bg-dark-nonepress-200"
|
||||
onChange={onChange}
|
||||
/>
|
||||
</label>
|
||||
<label className="flex flex-wrap">
|
||||
<span className="mr-2">import 包名:</span>
|
||||
<input
|
||||
type="text"
|
||||
name="moduleName"
|
||||
className="px-2 flex-grow rounded bg-light-nonepress-200 dark:bg-dark-nonepress-200"
|
||||
onChange={onChange}
|
||||
/>
|
||||
</label>
|
||||
<label className="flex flex-wrap">
|
||||
<span className="mr-2">仓库/主页:</span>
|
||||
<input
|
||||
type="text"
|
||||
name="homepage"
|
||||
className="px-2 flex-grow rounded bg-light-nonepress-200 dark:bg-dark-nonepress-200"
|
||||
onChange={onChange}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
<div className="px-4">
|
||||
<label className="flex flex-wrap">
|
||||
<span className="mr-2">标签:</span>
|
||||
{tags.map((tag, index) => (
|
||||
<TagComponent
|
||||
key={index}
|
||||
{...tag}
|
||||
className="cursor-pointer"
|
||||
onClick={() => delTag(index)}
|
||||
/>
|
||||
))}
|
||||
</label>
|
||||
</div>
|
||||
<div className="px-4 pt-4">
|
||||
<input
|
||||
ref={ref}
|
||||
type="text"
|
||||
className="px-2 flex-grow rounded bg-light-nonepress-200 dark:bg-dark-nonepress-200"
|
||||
onChange={onChangeLabel}
|
||||
/>
|
||||
<ChromePicker
|
||||
className="mt-2"
|
||||
color={color}
|
||||
disableAlpha={true}
|
||||
onChangeComplete={onChangeColor}
|
||||
/>
|
||||
<div className="flex flex-wrap mt-2 items-center">
|
||||
<span className="mr-2">Type:</span>
|
||||
<button
|
||||
className="px-2 h-9 min-w-[64px] rounded text-hero hover:bg-hero hover:bg-opacity-[.08]"
|
||||
onClick={() => insertTagType("a:")}
|
||||
>
|
||||
Adapter
|
||||
</button>
|
||||
<button
|
||||
className="px-2 h-9 min-w-[64px] rounded text-hero hover:bg-hero hover:bg-opacity-[.08]"
|
||||
onClick={() => insertTagType("t:")}
|
||||
>
|
||||
Topic
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex mt-2">
|
||||
<TagComponent label={label} color={color} />
|
||||
<button
|
||||
className={clsx(
|
||||
"px-2 h-9 min-w-[64px] rounded text-hero hover:bg-hero hover:bg-opacity-[.08]",
|
||||
{ "pointer-events-none opacity-60": !validateTag() }
|
||||
)}
|
||||
onClick={newTag}
|
||||
>
|
||||
添加标签
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</ModalContent>
|
||||
<ModalAction>
|
||||
<button
|
||||
className="px-2 h-9 min-w-[64px] rounded text-hero hover:bg-hero hover:bg-opacity-[.08]"
|
||||
onClick={() => setModalOpen(false)}
|
||||
>
|
||||
关闭
|
||||
</button>
|
||||
<button
|
||||
className="ml-2 px-2 h-9 min-w-[64px] rounded text-hero hover:bg-hero hover:bg-opacity-[.08]"
|
||||
onClick={onSubmit}
|
||||
>
|
||||
发布
|
||||
</button>
|
||||
</ModalAction>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -1,12 +1,20 @@
|
||||
import React from "react";
|
||||
import clsx from "clsx";
|
||||
import React, { useRef, useState } from "react";
|
||||
import { ChromePicker } from "react-color";
|
||||
import { usePagination } from "react-use-pagination";
|
||||
|
||||
import bots from "../../static/bots.json";
|
||||
import { useFilteredObjs } from "../libs/store";
|
||||
import { Tag, useFilteredObjs } from "../libs/store";
|
||||
import Card from "./Card";
|
||||
import Modal from "./Modal";
|
||||
import ModalAction from "./ModalAction";
|
||||
import ModalContent from "./ModalContent";
|
||||
import ModalTitle from "./ModalTitle";
|
||||
import Paginate from "./Paginate";
|
||||
import TagComponent from "./Tag";
|
||||
|
||||
export default function Adapter(): JSX.Element {
|
||||
const [modalOpen, setModalOpen] = useState<boolean>(false);
|
||||
const {
|
||||
filter,
|
||||
setFilter,
|
||||
@ -20,6 +28,80 @@ export default function Adapter(): JSX.Element {
|
||||
const { startIndex, endIndex } = props;
|
||||
const currentBots = filteredBots.slice(startIndex, endIndex + 1);
|
||||
|
||||
const [form, setForm] = useState<{
|
||||
name: string;
|
||||
desc: string;
|
||||
homepage: string;
|
||||
}>({ name: "", desc: "", homepage: "" });
|
||||
|
||||
const ref = useRef<HTMLInputElement>(null);
|
||||
const [tags, setTags] = useState<Tag[]>([]);
|
||||
const [label, setLabel] = useState<string>("");
|
||||
const [color, setColor] = useState<string>("#ea5252");
|
||||
|
||||
const onSubmit = () => {
|
||||
setModalOpen(false);
|
||||
const title = encodeURIComponent(`Bot: ${form.name}`).replace(/%2B/gi, "+");
|
||||
const body = encodeURIComponent(
|
||||
`
|
||||
**机器人名称:**
|
||||
|
||||
${form.name}
|
||||
|
||||
**机器人功能:**
|
||||
|
||||
${form.desc}
|
||||
|
||||
**机器人项目仓库/主页链接:**
|
||||
|
||||
${form.homepage}
|
||||
|
||||
**标签:**
|
||||
|
||||
${JSON.stringify(tags)}
|
||||
`.trim()
|
||||
).replace(/%2B/gi, "+");
|
||||
window.open(
|
||||
`https://github.com/nonebot/nonebot2/issues/new?title=${title}&body=${body}&labels=Bot`
|
||||
);
|
||||
};
|
||||
const onChange = (event) => {
|
||||
const target = event.target;
|
||||
const value = target.type === "checkbox" ? target.checked : target.value;
|
||||
const name = target.name;
|
||||
|
||||
setForm({
|
||||
...form,
|
||||
[name]: value,
|
||||
});
|
||||
event.preventDefault();
|
||||
};
|
||||
const onChangeLabel = (event) => {
|
||||
setLabel(event.target.value);
|
||||
};
|
||||
const onChangeColor = (color) => {
|
||||
setColor(color.hex);
|
||||
};
|
||||
const validateTag = () => {
|
||||
return label.length >= 1 && label.length <= 10;
|
||||
};
|
||||
const newTag = () => {
|
||||
if (tags.length >= 3) {
|
||||
return;
|
||||
}
|
||||
if (validateTag()) {
|
||||
const tag = { label, color };
|
||||
setTags([...tags, tag]);
|
||||
}
|
||||
};
|
||||
const delTag = (index: number) => {
|
||||
setTags(tags.filter((_, i) => i !== index));
|
||||
};
|
||||
const insertTagType = (text: string) => {
|
||||
setLabel(text + label);
|
||||
ref.current.value = text + label;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 mt-4 px-4">
|
||||
@ -29,7 +111,10 @@ export default function Adapter(): JSX.Element {
|
||||
placeholder="搜索机器人"
|
||||
onChange={(event) => setFilter(event.target.value)}
|
||||
/>
|
||||
<button className="w-full rounded-lg bg-hero text-white">
|
||||
<button
|
||||
className="w-full rounded-lg bg-hero text-white"
|
||||
onClick={() => setModalOpen(true)}
|
||||
>
|
||||
发布机器人
|
||||
</button>
|
||||
</div>
|
||||
@ -44,6 +129,111 @@ export default function Adapter(): JSX.Element {
|
||||
<div className="grid grid-cols-1 p-4">
|
||||
<Paginate {...props} />
|
||||
</div>
|
||||
<Modal active={modalOpen} setActive={setModalOpen}>
|
||||
<ModalTitle title={"机器人信息"} />
|
||||
<ModalContent>
|
||||
<form onSubmit={onSubmit}>
|
||||
<div className="grid grid-cols-1 gap-4 p-4">
|
||||
<label className="flex flex-wrap">
|
||||
<span className="mr-2">机器人名称:</span>
|
||||
<input
|
||||
type="text"
|
||||
name="name"
|
||||
maxLength={20}
|
||||
className="px-2 flex-grow rounded bg-light-nonepress-200 dark:bg-dark-nonepress-200"
|
||||
onChange={onChange}
|
||||
/>
|
||||
</label>
|
||||
<label className="flex flex-wrap">
|
||||
<span className="mr-2">机器人介绍:</span>
|
||||
<input
|
||||
type="text"
|
||||
name="desc"
|
||||
className="px-2 flex-grow rounded bg-light-nonepress-200 dark:bg-dark-nonepress-200"
|
||||
onChange={onChange}
|
||||
/>
|
||||
</label>
|
||||
<label className="flex flex-wrap">
|
||||
<span className="mr-2">仓库/主页:</span>
|
||||
<input
|
||||
type="text"
|
||||
name="homepage"
|
||||
className="px-2 flex-grow rounded bg-light-nonepress-200 dark:bg-dark-nonepress-200"
|
||||
onChange={onChange}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
<div className="px-4">
|
||||
<label className="flex flex-wrap">
|
||||
<span className="mr-2">标签:</span>
|
||||
{tags.map((tag, index) => (
|
||||
<TagComponent
|
||||
key={index}
|
||||
{...tag}
|
||||
className="cursor-pointer"
|
||||
onClick={() => delTag(index)}
|
||||
/>
|
||||
))}
|
||||
</label>
|
||||
</div>
|
||||
<div className="px-4 pt-4">
|
||||
<input
|
||||
ref={ref}
|
||||
type="text"
|
||||
className="px-2 flex-grow rounded bg-light-nonepress-200 dark:bg-dark-nonepress-200"
|
||||
onChange={onChangeLabel}
|
||||
/>
|
||||
<ChromePicker
|
||||
className="mt-2"
|
||||
color={color}
|
||||
disableAlpha={true}
|
||||
onChangeComplete={onChangeColor}
|
||||
/>
|
||||
<div className="flex flex-wrap mt-2 items-center">
|
||||
<span className="mr-2">Type:</span>
|
||||
<button
|
||||
className="px-2 h-9 min-w-[64px] rounded text-hero hover:bg-hero hover:bg-opacity-[.08]"
|
||||
onClick={() => insertTagType("a:")}
|
||||
>
|
||||
Adapter
|
||||
</button>
|
||||
<button
|
||||
className="px-2 h-9 min-w-[64px] rounded text-hero hover:bg-hero hover:bg-opacity-[.08]"
|
||||
onClick={() => insertTagType("t:")}
|
||||
>
|
||||
Topic
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex mt-2">
|
||||
<TagComponent label={label} color={color} />
|
||||
<button
|
||||
className={clsx(
|
||||
"px-2 h-9 min-w-[64px] rounded text-hero hover:bg-hero hover:bg-opacity-[.08]",
|
||||
{ "pointer-events-none opacity-60": !validateTag() }
|
||||
)}
|
||||
onClick={newTag}
|
||||
>
|
||||
添加标签
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</ModalContent>
|
||||
<ModalAction>
|
||||
<button
|
||||
className="px-2 h-9 min-w-[64px] rounded text-hero hover:bg-hero hover:bg-opacity-[.08]"
|
||||
onClick={() => setModalOpen(false)}
|
||||
>
|
||||
关闭
|
||||
</button>
|
||||
<button
|
||||
className="ml-2 px-2 h-9 min-w-[64px] rounded text-hero hover:bg-hero hover:bg-opacity-[.08]"
|
||||
onClick={onSubmit}
|
||||
>
|
||||
发布
|
||||
</button>
|
||||
</ModalAction>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -94,5 +94,13 @@
|
||||
"homepage": "https://github.com/ssttkkl/PixivBot",
|
||||
"tags": [],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"name": "SeaBot_QQ",
|
||||
"desc": "一个能够获取新闻资讯并推送至QQ的群聊机器人。",
|
||||
"author": "B1ue1nWh1te",
|
||||
"homepage": "https://github.com/B1ue1nWh1te/SeaBot_QQ",
|
||||
"tags": [],
|
||||
"is_official": false
|
||||
}
|
||||
]
|
||||
|
@ -688,5 +688,85 @@
|
||||
"homepage": "https://github.com/yzyyz1387/nonebot_plugin_heisi",
|
||||
"tags": [],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_picsbank",
|
||||
"project_link": "nonebot-plugin-picsbank",
|
||||
"name": "picsbank",
|
||||
"desc": "匹配图片进行回答",
|
||||
"author": "Diaosi1111",
|
||||
"homepage": "https://github.com/Diaosi1111/nonebot_plugin_picsbank",
|
||||
"tags": [],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_tvseries",
|
||||
"project_link": "nonebot-plugin-tvseries",
|
||||
"name": "剧集更新列表",
|
||||
"desc": "获取聚集更新",
|
||||
"author": "kexue-z",
|
||||
"homepage": "https://github.com/kexue-z/nonebot-plugin-tvseries",
|
||||
"tags": [],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_lolmatch",
|
||||
"project_link": "nonebot-plugin-lolmatch",
|
||||
"name": "lol比赛信息",
|
||||
"desc": "简单的lol比赛信息插件,订阅后会定时推送当日比赛结果",
|
||||
"author": "Diaosi1111",
|
||||
"homepage": "https://github.com/Diaosi1111/nonebot_plugin_lolmatch",
|
||||
"tags": [],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "OlivOS",
|
||||
"project_link": "olivos.nb2",
|
||||
"name": "OlivOS.nb2",
|
||||
"desc": "在 NoneBot2 中加载 OlivOS 插件",
|
||||
"author": "j1g5awi",
|
||||
"homepage": "https://github.com/nonepkg/OlivOS.nb2",
|
||||
"tags": [],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_htmlrender",
|
||||
"project_link": "nonebot-plugin-htmlrender",
|
||||
"name": "通过浏览器来生成图片",
|
||||
"desc": "通过playwright加一点点前端知识来简单的生成图片",
|
||||
"author": "kexue-z",
|
||||
"homepage": "https://github.com/kexue-z/nonebot-plugin-htmlrender",
|
||||
"tags": [],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_admin",
|
||||
"project_link": "nonebot-plugin-admin",
|
||||
"name": "简易群管",
|
||||
"desc": "简易群管 踢 禁 改",
|
||||
"author": "yzyyz1387",
|
||||
"homepage": "https://github.com/yzyyz1387/nonebot_plugin_admin",
|
||||
"tags": [],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_logo",
|
||||
"project_link": "nonebot-plugin-logo",
|
||||
"name": "nonebot-plugin-logo",
|
||||
"desc": "PornHub、Youtube 等风格logo生成",
|
||||
"author": "MeetWq",
|
||||
"homepage": "https://github.com/MeetWq/nonebot-plugin-logo",
|
||||
"tags": [],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_memes",
|
||||
"project_link": "nonebot-plugin-memes",
|
||||
"name": "Memes generator",
|
||||
"desc": "表情包制作",
|
||||
"author": "MeetWq",
|
||||
"homepage": "https://github.com/MeetWq/nonebot-plugin-memes",
|
||||
"tags": [],
|
||||
"is_official": false
|
||||
}
|
||||
]
|
||||
|
Loading…
Reference in New Issue
Block a user