mirror of
https://github.com/nonebot/nonebot2.git
synced 2025-10-07 11:16:43 +00:00
📝 Docs: 商店插件可用性筛选 & 更新排序 (#3334)
This commit is contained in:
@@ -18,7 +18,7 @@ import { fetchRegistryData, loadFailedTitle } from "@/libs/store";
|
||||
import { useToolbar } from "@/libs/toolbar";
|
||||
import type { Adapter } from "@/types/adapter";
|
||||
|
||||
export default function AdapterPage(): JSX.Element {
|
||||
export default function AdapterPage(): React.ReactNode {
|
||||
const [adapters, setAdapters] = useState<Adapter[] | null>(null);
|
||||
const adapterCount = adapters?.length ?? 0;
|
||||
const loading = adapters === null;
|
||||
|
@@ -17,7 +17,7 @@ import { fetchRegistryData, loadFailedTitle } from "@/libs/store";
|
||||
import { useToolbar } from "@/libs/toolbar";
|
||||
import type { Bot } from "@/types/bot";
|
||||
|
||||
export default function PluginPage(): JSX.Element {
|
||||
export default function PluginPage(): React.ReactNode {
|
||||
const [bots, setBots] = useState<Bot[] | null>(null);
|
||||
const botCount = bots?.length ?? 0;
|
||||
const loading = bots === null;
|
||||
|
@@ -15,7 +15,7 @@ import { useSearchControl } from "@/libs/search";
|
||||
import { fetchRegistryData, loadFailedTitle } from "@/libs/store";
|
||||
import type { Driver } from "@/types/driver";
|
||||
|
||||
export default function DriverPage(): JSX.Element {
|
||||
export default function DriverPage(): React.ReactNode {
|
||||
const [drivers, setDrivers] = useState<Driver[] | null>(null);
|
||||
const driverCount = drivers?.length ?? 0;
|
||||
const loading = drivers === null;
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
|
||||
import Translate from "@docusaurus/Translate";
|
||||
import Translate, { translate } from "@docusaurus/Translate";
|
||||
import { usePagination } from "react-use-pagination";
|
||||
|
||||
import Admonition from "@theme/Admonition";
|
||||
@@ -11,14 +11,18 @@ import Paginate from "@/components/Paginate";
|
||||
import ResourceCard from "@/components/Resource/Card";
|
||||
import ResourceDetailCard from "@/components/Resource/DetailCard";
|
||||
import Searcher from "@/components/Searcher";
|
||||
import StoreToolbar, { type Action } from "@/components/Store/Toolbar";
|
||||
import StoreToolbar, {
|
||||
type Action,
|
||||
type Sorter,
|
||||
} from "@/components/Store/Toolbar";
|
||||
import { authorFilter, tagFilter } from "@/libs/filter";
|
||||
import { useSearchControl } from "@/libs/search";
|
||||
import { SortMode } from "@/libs/sorter";
|
||||
import { fetchRegistryData, loadFailedTitle } from "@/libs/store";
|
||||
import { useToolbar } from "@/libs/toolbar";
|
||||
import type { Plugin } from "@/types/plugin";
|
||||
|
||||
export default function PluginPage(): JSX.Element {
|
||||
export default function PluginPage(): React.ReactNode {
|
||||
const [plugins, setPlugins] = useState<Plugin[] | null>(null);
|
||||
const pluginCount = plugins?.length ?? 0;
|
||||
const loading = plugins === null;
|
||||
@@ -27,6 +31,38 @@ export default function PluginPage(): JSX.Element {
|
||||
const [isOpenModal, setIsOpenModal] = useState<boolean>(false);
|
||||
const [isOpenCardModal, setIsOpenCardModal] = useState<boolean>(false);
|
||||
const [clickedPlugin, setClickedPlugin] = useState<Plugin | null>(null);
|
||||
const [sortMode, setSortMode] = useState<SortMode>(SortMode.Default);
|
||||
|
||||
const sorterTool: Sorter = {
|
||||
label:
|
||||
sortMode === SortMode.Default
|
||||
? translate({
|
||||
id: "pages.store.sorter.label.default",
|
||||
description: "The label of default sorter",
|
||||
message: "默认顺序",
|
||||
})
|
||||
: translate({
|
||||
id: "pages.store.sorter.label.updateDesc",
|
||||
description: "The label of updateDesc sorter",
|
||||
message: "更新时间倒序",
|
||||
}),
|
||||
icon: ["fas", "sort-amount-down"],
|
||||
active: sortMode === SortMode.UpdateDesc,
|
||||
onClick: () => {
|
||||
setSortMode(
|
||||
sortMode === SortMode.Default ? SortMode.UpdateDesc : SortMode.Default
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
const getSortedPlugins = (plugins: Plugin[]): Plugin[] => {
|
||||
if (sortMode === SortMode.UpdateDesc) {
|
||||
return [...plugins].sort(
|
||||
(a, b) => new Date(b.time).getTime() - new Date(a.time).getTime()
|
||||
);
|
||||
}
|
||||
return plugins;
|
||||
};
|
||||
|
||||
const {
|
||||
filteredResources: filteredPlugins,
|
||||
@@ -37,7 +73,7 @@ export default function PluginPage(): JSX.Element {
|
||||
onSearchBackspace,
|
||||
onSearchClear,
|
||||
onSearchTagClick,
|
||||
} = useSearchControl<Plugin>(plugins ?? []);
|
||||
} = useSearchControl<Plugin>(getSortedPlugins(plugins ?? []));
|
||||
const filteredPluginCount = filteredPlugins.length;
|
||||
|
||||
const {
|
||||
@@ -134,6 +170,7 @@ export default function PluginPage(): JSX.Element {
|
||||
<StoreToolbar
|
||||
className="not-prose"
|
||||
filters={filterTools}
|
||||
sorter={sorterTool}
|
||||
action={actionTool}
|
||||
/>
|
||||
|
||||
|
@@ -18,7 +18,7 @@ type Props = {
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
function StorePage({ title, children }: Props): JSX.Element {
|
||||
function StorePage({ title, children }: Props): React.ReactNode {
|
||||
const sidebarItems = useVersionedSidebar(
|
||||
useDocsVersionCandidates()[0].name,
|
||||
SIDEBAR_ID
|
||||
@@ -35,7 +35,10 @@ function StorePage({ title, children }: Props): JSX.Element {
|
||||
);
|
||||
}
|
||||
|
||||
export default function StoreLayout({ title, ...props }: Props): JSX.Element {
|
||||
export default function StoreLayout({
|
||||
title,
|
||||
...props
|
||||
}: Props): React.ReactNode {
|
||||
return (
|
||||
<>
|
||||
<PageMetadata title={title} />
|
||||
|
@@ -12,6 +12,13 @@ export type Filter = {
|
||||
onSubmit: (query: string) => void;
|
||||
};
|
||||
|
||||
export type Sorter = {
|
||||
label: string;
|
||||
icon: IconProp;
|
||||
active: boolean;
|
||||
onClick: () => void;
|
||||
};
|
||||
|
||||
export type Action = {
|
||||
label: string;
|
||||
icon: IconProp;
|
||||
@@ -20,6 +27,7 @@ export type Action = {
|
||||
|
||||
export type Props = {
|
||||
filters?: Filter[];
|
||||
sorter?: Sorter;
|
||||
action?: Action;
|
||||
className?: string;
|
||||
};
|
||||
@@ -29,7 +37,7 @@ function ToolbarFilter({
|
||||
icon,
|
||||
choices,
|
||||
onSubmit,
|
||||
}: Filter): JSX.Element {
|
||||
}: Filter): React.ReactNode {
|
||||
const [query, setQuery] = useState<string>("");
|
||||
|
||||
const filteredChoices = choices
|
||||
@@ -96,33 +104,65 @@ function ToolbarFilter({
|
||||
|
||||
export default function StoreToolbar({
|
||||
filters,
|
||||
sorter,
|
||||
action,
|
||||
className,
|
||||
}: Props): JSX.Element | null {
|
||||
}: Props): React.ReactNode | null {
|
||||
if (!(filters && filters.length > 0) && !action) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={clsx("store-toolbar", className)}>
|
||||
{filters && filters.length > 0 && (
|
||||
<>
|
||||
<div className={clsx("store-toolbar", className)}>
|
||||
<div className="store-toolbar-filters">
|
||||
{filters.map((filter, index) => (
|
||||
{filters?.map((filter, index) => (
|
||||
<ToolbarFilter key={index} {...filter} />
|
||||
))}
|
||||
{sorter && (
|
||||
<div className="store-toolbar-sorter store-toolbar-sorter-desktop">
|
||||
<button
|
||||
className={clsx(
|
||||
"btn btn-sm btn-primary no-animation mr-2",
|
||||
!sorter.active && "btn-outline"
|
||||
)}
|
||||
onClick={sorter.onClick}
|
||||
>
|
||||
<FontAwesomeIcon icon={sorter.icon} />
|
||||
{sorter.label}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{action && (
|
||||
<div className="store-toolbar-action">
|
||||
<button
|
||||
className="btn btn-sm btn-primary no-animation"
|
||||
onClick={action.onClick}
|
||||
>
|
||||
<FontAwesomeIcon icon={action.icon} />
|
||||
{action.label}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{action && (
|
||||
<div className="store-toolbar-action">
|
||||
<button
|
||||
className="btn btn-sm btn-primary no-animation"
|
||||
onClick={action.onClick}
|
||||
>
|
||||
<FontAwesomeIcon icon={action.icon} />
|
||||
{action.label}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className={clsx("store-toolbar store-toolbar-second", className)}>
|
||||
{sorter && (
|
||||
<div className="store-toolbar-sorter">
|
||||
<button
|
||||
className={clsx(
|
||||
"btn btn-sm btn-primary no-animation mr-2",
|
||||
!sorter.active && "btn-outline"
|
||||
)}
|
||||
onClick={sorter.onClick}
|
||||
>
|
||||
<FontAwesomeIcon icon={sorter.icon} />
|
||||
{sorter.label}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@@ -14,6 +14,18 @@
|
||||
&-toolbar {
|
||||
@apply flex items-center justify-center my-4;
|
||||
|
||||
&-second {
|
||||
@apply lg:hidden;
|
||||
}
|
||||
|
||||
&-sorter {
|
||||
@apply max-lg:flex-1;
|
||||
|
||||
&-desktop {
|
||||
@apply max-lg:hidden;
|
||||
}
|
||||
}
|
||||
|
||||
&-filters {
|
||||
@apply flex grow gap-2;
|
||||
}
|
||||
|
Reference in New Issue
Block a user