📝 Docs: 商店插件可用性筛选 & 更新排序 (#3334)

This commit is contained in:
StarHeart
2025-02-26 23:05:06 +08:00
committed by GitHub
parent db857b11fa
commit 6cff660af0
42 changed files with 2320 additions and 2374 deletions

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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}
/>

View File

@@ -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} />

View File

@@ -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>
</>
);
}

View File

@@ -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;
}