mirror of
https://github.com/nonebot/nonebot2.git
synced 2025-08-01 18:50:00 +00:00
📝 Docs: 添加商店表单支持 (#2460)
Co-authored-by: Ju4tCode <42488585+yanyongyu@users.noreply.github.com>
This commit is contained in:
157
website/src/components/Resource/DetailCard/index.tsx
Normal file
157
website/src/components/Resource/DetailCard/index.tsx
Normal file
@ -0,0 +1,157 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
// @ts-expect-error: we need to make package have type: module
|
||||
import copy from "copy-text-to-clipboard";
|
||||
|
||||
import { PyPIData } from "./types";
|
||||
|
||||
import Tag from "@/components/Resource/Tag";
|
||||
import type { Resource } from "@/libs/store";
|
||||
|
||||
import "./styles.css";
|
||||
|
||||
export type Props = {
|
||||
resource: Resource;
|
||||
};
|
||||
|
||||
export default function ResourceDetailCard({ resource }: Props) {
|
||||
const [pypiData, setPypiData] = useState<PyPIData | null>(null);
|
||||
const [copied, setCopied] = useState<boolean>(false);
|
||||
|
||||
const authorLink = `https://github.com/${resource.author}`;
|
||||
const authorAvatar = `${authorLink}.png?size=100`;
|
||||
|
||||
const getProjectLink = (resource: Resource) => {
|
||||
switch (resource.resourceType) {
|
||||
case "plugin":
|
||||
case "adapter":
|
||||
case "driver":
|
||||
return resource.project_link;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const getModuleName = (resource: Resource) => {
|
||||
switch (resource.resourceType) {
|
||||
case "plugin":
|
||||
case "adapter":
|
||||
return resource.module_name;
|
||||
case "driver":
|
||||
return resource.module_name.replace(/~/, "nonebot.drivers.");
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const fetchPypiProject = (projectName: string) =>
|
||||
fetch(`https://pypi.org/pypi/${projectName}/json`)
|
||||
.then((response) => response.json())
|
||||
.then((data) => setPypiData(data));
|
||||
|
||||
const copyCommand = (resource: Resource) => {
|
||||
const projectLink = getProjectLink(resource);
|
||||
if (projectLink) {
|
||||
copy(`nb ${resource.resourceType} install ${projectLink}`);
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const fetchingTasks: Promise<void>[] = [];
|
||||
if (resource.resourceType === "bot" || resource.resourceType === "driver")
|
||||
return;
|
||||
|
||||
if (resource.project_link)
|
||||
fetchingTasks.push(fetchPypiProject(resource.project_link));
|
||||
|
||||
Promise.all(fetchingTasks);
|
||||
}, [resource]);
|
||||
|
||||
const projectLink = getProjectLink(resource) || "无";
|
||||
const moduleName = getModuleName(resource) || "无";
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="detail-card-header">
|
||||
<img
|
||||
src={authorAvatar}
|
||||
className="detail-card-avatar"
|
||||
decoding="async"
|
||||
></img>
|
||||
<div className="detail-card-title">
|
||||
<span className="detail-card-title-main">{resource.name}</span>
|
||||
<span className="detail-card-title-sub">{resource.author}</span>
|
||||
</div>
|
||||
<button
|
||||
className="detail-card-copy-button detail-card-copy-button-desktop"
|
||||
onClick={() => copyCommand(resource)}
|
||||
>
|
||||
{copied ? "复制成功" : "复制安装命令"}
|
||||
</button>
|
||||
</div>
|
||||
<div className="detail-card-body">
|
||||
<div className="detail-card-body-left">
|
||||
<span className="h-full">{resource.desc}</span>
|
||||
<div className="resource-card-footer-tags mb-4">
|
||||
{resource.tags.map((tag, index) => (
|
||||
<Tag className="align-bottom" key={index} {...tag} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="detail-card-body-divider" />
|
||||
<div className="detail-card-body-right">
|
||||
<div className="detail-card-meta-item">
|
||||
<span>
|
||||
<FontAwesomeIcon fixedWidth icon={["fab", "python"]} />{" "}
|
||||
{(pypiData && pypiData.info.requires_python) || "无"}
|
||||
</span>
|
||||
</div>
|
||||
<div className="detail-card-meta-item">
|
||||
<FontAwesomeIcon fixedWidth icon={["fas", "file-zipper"]} />{" "}
|
||||
{(pypiData &&
|
||||
pypiData.releases[pypiData.info.version] &&
|
||||
`${
|
||||
pypiData.releases[pypiData.info.version].reduce(
|
||||
(acc, curr) => acc + curr.size,
|
||||
0
|
||||
) / 1000
|
||||
}K`) ||
|
||||
"无"}
|
||||
</div>
|
||||
<div className="detail-card-meta-item">
|
||||
<span>
|
||||
<FontAwesomeIcon
|
||||
className="fa-fw"
|
||||
icon={["fas", "scale-balanced"]}
|
||||
/>{" "}
|
||||
{(pypiData && pypiData.info.license) || "无"}
|
||||
</span>
|
||||
</div>
|
||||
<div className="detail-card-meta-item">
|
||||
<FontAwesomeIcon fixedWidth icon={["fas", "tag"]} />{" "}
|
||||
{(pypiData && pypiData.info.version) || "无"}
|
||||
</div>
|
||||
|
||||
<div className="detail-card-meta-item">
|
||||
<FontAwesomeIcon fixedWidth icon={["fas", "fingerprint"]} />{" "}
|
||||
<span>{moduleName}</span>
|
||||
</div>
|
||||
|
||||
<div className="detail-card-meta-item">
|
||||
<FontAwesomeIcon fixedWidth icon={["fas", "cubes"]} />{" "}
|
||||
<span>{projectLink}</span>
|
||||
</div>
|
||||
<button
|
||||
className="detail-card-copy-button detail-card-copy-button-mobile w-full"
|
||||
onClick={() => copyCommand(resource)}
|
||||
>
|
||||
{copied ? "复制成功" : "复制安装命令"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
53
website/src/components/Resource/DetailCard/styles.css
Normal file
53
website/src/components/Resource/DetailCard/styles.css
Normal file
@ -0,0 +1,53 @@
|
||||
.detail-card {
|
||||
&-header {
|
||||
@apply flex items-center align-middle;
|
||||
}
|
||||
|
||||
&-avatar {
|
||||
@apply mr-3 w-12 h-12 rounded-full;
|
||||
}
|
||||
|
||||
&-title {
|
||||
@apply inline-flex flex-col h-12 justify-start;
|
||||
|
||||
&-main {
|
||||
@apply font-bold;
|
||||
}
|
||||
|
||||
&-sub {
|
||||
@apply text-sm;
|
||||
}
|
||||
}
|
||||
|
||||
&-copy-button {
|
||||
@apply ml-auto btn btn-sm;
|
||||
|
||||
&-mobile {
|
||||
@apply lg:hidden;
|
||||
}
|
||||
|
||||
&-desktop {
|
||||
@apply max-lg:hidden;
|
||||
}
|
||||
}
|
||||
|
||||
&-body {
|
||||
@apply flex flex-col w-full lg:flex-row;
|
||||
|
||||
&-left {
|
||||
@apply flex flex-col min-h-[150px] lg:basis-3/4;
|
||||
}
|
||||
|
||||
&-divider {
|
||||
@apply divider lg:divider-horizontal;
|
||||
}
|
||||
|
||||
&-right {
|
||||
@apply flex flex-col justify-start gap-y-2 lg:basis-1/4;
|
||||
}
|
||||
}
|
||||
|
||||
&-meta-item {
|
||||
@apply text-sm whitespace-nowrap;
|
||||
}
|
||||
}
|
64
website/src/components/Resource/DetailCard/types.ts
Normal file
64
website/src/components/Resource/DetailCard/types.ts
Normal file
@ -0,0 +1,64 @@
|
||||
export type Downloads = {
|
||||
last_day: number;
|
||||
last_month: number;
|
||||
last_week: number;
|
||||
};
|
||||
|
||||
export type Info = {
|
||||
author: string;
|
||||
author_email: string;
|
||||
bugtrack_url: null;
|
||||
classifiers: string[];
|
||||
description: string;
|
||||
description_content_type: string;
|
||||
docs_url: null;
|
||||
download_url: string;
|
||||
downloads: Downloads;
|
||||
home_page: string;
|
||||
keywords: string;
|
||||
license: string;
|
||||
maintainer: string;
|
||||
maintainer_email: string;
|
||||
name: string;
|
||||
package_url: string;
|
||||
platform: null;
|
||||
project_url: string;
|
||||
release_url: string;
|
||||
requires_dist: string[];
|
||||
requires_python: string;
|
||||
summary: string;
|
||||
version: string;
|
||||
yanked: boolean;
|
||||
yanked_reason: null;
|
||||
};
|
||||
|
||||
export interface Digests {
|
||||
blake2b_256: string;
|
||||
md5: string;
|
||||
sha256: string;
|
||||
}
|
||||
|
||||
export type Releases = {
|
||||
comment_text: string;
|
||||
digests: Digests;
|
||||
downloads: number;
|
||||
filename: string;
|
||||
has_sig: boolean;
|
||||
md5_digest: string;
|
||||
packagetype: string;
|
||||
python_version: string;
|
||||
requires_python: string;
|
||||
size: number;
|
||||
upload_time: Date;
|
||||
upload_time_iso_8601: Date;
|
||||
url: string;
|
||||
yanked: boolean;
|
||||
yanked_reason: null;
|
||||
};
|
||||
export type PyPIData = {
|
||||
info: Info;
|
||||
last_serial: number;
|
||||
releases: { [key: string]: Releases[] };
|
||||
urls: URL[];
|
||||
vulnerabilities: unknown[];
|
||||
};
|
Reference in New Issue
Block a user