📝 Docs: 升级新版 NonePress 主题 (#2375)

This commit is contained in:
Ju4tCode
2023-09-27 16:00:26 +08:00
committed by GitHub
parent 7754f6da1d
commit 842c6ff4c6
234 changed files with 8759 additions and 5887 deletions

View File

@ -0,0 +1,175 @@
import React, { useCallback, useEffect, useState } from "react";
import Translate from "@docusaurus/Translate";
import { usePagination } from "react-use-pagination";
import Admonition from "@theme/Admonition";
import Paginate from "@/components/Paginate";
import ResourceCard from "@/components/Resource/Card";
import Searcher from "@/components/Searcher";
import StoreToolbar, { type Action } from "@/components/Store/Toolbar";
import { authorFilter, tagFilter } from "@/libs/filter";
import { useSearchControl } from "@/libs/search";
import { fetchRegistryData, loadFailedTitle } from "@/libs/store";
import { useToolbar } from "@/libs/toolbar";
import type { Adapter } from "@/types/adapter";
export default function AdapterPage(): JSX.Element {
const [adapters, setAdapters] = useState<Adapter[] | null>(null);
const adapterCount = adapters?.length ?? 0;
const loading = adapters === null;
const [error, setError] = useState<Error | null>(null);
const {
filteredResources: filteredAdapters,
searcherTags,
addFilter,
onSearchQueryChange,
onSearchQuerySubmit,
onSearchBackspace,
onSearchClear,
onSearchTagClick,
} = useSearchControl<Adapter>(adapters ?? []);
const filteredAdapterCount = filteredAdapters.length;
const {
startIndex,
endIndex,
totalPages,
currentPage,
setNextPage,
setPreviousPage,
setPage,
previousEnabled,
nextEnabled,
} = usePagination({
totalItems: filteredAdapters.length,
initialPageSize: 12,
});
const currentAdapters = filteredAdapters.slice(startIndex, endIndex + 1);
// load adapters asynchronously
useEffect(() => {
fetchRegistryData("adapter")
.then(setAdapters)
.catch((e) => {
setError(e);
console.error(e);
});
}, []);
const { filters: filterTools } = useToolbar({
resources: adapters ?? [],
addFilter,
});
const actionTool: Action = {
label: "发布适配器",
icon: ["fas", "plus"],
onClick: () => {
// TODO: open adapter release modal
window.open(
"https://github.com/nonebot/nonebot2/issues/new?template=adapter_publish.yml"
);
},
};
const onCardClick = useCallback((adapter: Adapter) => {
// TODO: open adapter modal
console.log(adapter, "clicked");
}, []);
const onCardTagClick = useCallback(
(tag: string) => {
addFilter(tagFilter(tag));
},
[addFilter]
);
const onCardAuthorClick = useCallback(
(author: string) => {
addFilter(authorFilter(author));
},
[addFilter]
);
return (
<>
<p className="store-description">
{adapterCount === filteredAdapterCount ? (
<Translate
id="pages.store.adapter.info"
description="Adapters info of the adapter store page"
values={{ adapterCount }}
>
{"当前共有 {adapterCount} 个适配器"}
</Translate>
) : (
<Translate
id="pages.store.adapter.searchInfo"
description="Adapters search info of the adapter store page"
values={{
adapterCount,
filteredAdapterCount,
}}
>
{"当前共有 {filteredAdapterCount} / {adapterCount} 个插件"}
</Translate>
)}
</p>
<Searcher
className="store-searcher not-prose"
onChange={onSearchQueryChange}
onSubmit={onSearchQuerySubmit}
onBackspace={onSearchBackspace}
onClear={onSearchClear}
onTagClick={onSearchTagClick}
tags={searcherTags}
disabled={loading}
/>
<StoreToolbar
className="not-prose"
filters={filterTools}
action={actionTool}
/>
{error ? (
<Admonition type="caution" title={loadFailedTitle}>
{error.message}
</Admonition>
) : loading ? (
<p className="store-loading-container">
<span className="loading loading-dots loading-lg store-loading"></span>
</p>
) : (
<div className="store-container">
{currentAdapters.map((adapter, index) => (
<ResourceCard
key={index}
className="not-prose"
resource={adapter}
onClick={() => onCardClick(adapter)}
onTagClick={onCardTagClick}
onAuthorClick={() => onCardAuthorClick(adapter.author)}
/>
))}
</div>
)}
<Paginate
className="not-prose"
totalPages={totalPages}
currentPage={currentPage}
setNextPage={setNextPage}
setPreviousPage={setPreviousPage}
setPage={setPage}
nextEnabled={nextEnabled}
previousEnabled={previousEnabled}
/>
</>
);
}

View File

@ -0,0 +1,169 @@
import React, { useCallback, useEffect, useState } from "react";
import Translate from "@docusaurus/Translate";
import { usePagination } from "react-use-pagination";
import Admonition from "@theme/Admonition";
import Paginate from "@/components/Paginate";
import ResourceCard from "@/components/Resource/Card";
import Searcher from "@/components/Searcher";
import StoreToolbar, { type Action } from "@/components/Store/Toolbar";
import { authorFilter, tagFilter } from "@/libs/filter";
import { useSearchControl } from "@/libs/search";
import { fetchRegistryData, loadFailedTitle } from "@/libs/store";
import { useToolbar } from "@/libs/toolbar";
import type { Bot } from "@/types/bot";
export default function PluginPage(): JSX.Element {
const [bots, setBots] = useState<Bot[] | null>(null);
const botCount = bots?.length ?? 0;
const loading = bots === null;
const [error, setError] = useState<Error | null>(null);
const {
filteredResources: filteredBots,
searcherTags,
addFilter,
onSearchQueryChange,
onSearchQuerySubmit,
onSearchBackspace,
onSearchClear,
onSearchTagClick,
} = useSearchControl<Bot>(bots ?? []);
const filteredBotCount = filteredBots.length;
const {
startIndex,
endIndex,
totalPages,
currentPage,
setNextPage,
setPreviousPage,
setPage,
previousEnabled,
nextEnabled,
} = usePagination({
totalItems: filteredBots.length,
initialPageSize: 12,
});
const currentBots = filteredBots.slice(startIndex, endIndex + 1);
// load bots asynchronously
useEffect(() => {
fetchRegistryData("bot")
.then(setBots)
.catch((e) => {
setError(e);
console.error(e);
});
}, []);
const { filters: filterTools } = useToolbar({
resources: bots ?? [],
addFilter,
});
const actionTool: Action = {
label: "发布机器人",
icon: ["fas", "plus"],
onClick: () => {
// TODO: open bot release modal
window.open(
"https://github.com/nonebot/nonebot2/issues/new?template=bot_publish.yml"
);
},
};
const onCardTagClick = useCallback(
(tag: string) => {
addFilter(tagFilter(tag));
},
[addFilter]
);
const onAuthorClick = useCallback(
(author: string) => {
addFilter(authorFilter(author));
},
[addFilter]
);
return (
<>
<p className="store-description">
{botCount === filteredBotCount ? (
<Translate
id="pages.store.bot.info"
description="Bots info of the bot store page"
values={{ botCount }}
>
{"当前共有 {botCount} 个机器人"}
</Translate>
) : (
<Translate
id="pages.store.bot.searchInfo"
description="Bots search info of the bot store page"
values={{
botCount,
filteredBotCount,
}}
>
{"当前共有 {filteredBotCount} / {botCount} 个机器人"}
</Translate>
)}
</p>
<Searcher
className="store-searcher not-prose"
onChange={onSearchQueryChange}
onSubmit={onSearchQuerySubmit}
onBackspace={onSearchBackspace}
onClear={onSearchClear}
onTagClick={onSearchTagClick}
tags={searcherTags}
disabled={loading}
/>
<StoreToolbar
className="not-prose"
filters={filterTools}
action={actionTool}
/>
{error ? (
<Admonition type="caution" title={loadFailedTitle}>
{error.message}
</Admonition>
) : loading ? (
<p className="store-loading-container">
<span className="loading loading-dots loading-lg store-loading"></span>
</p>
) : (
<div className="store-container">
{currentBots.map((bot, index) => (
<ResourceCard
key={index}
className="not-prose"
resource={bot}
onTagClick={onCardTagClick}
onAuthorClick={() => onAuthorClick(bot.author)}
/>
))}
</div>
)}
<Paginate
className="not-prose"
totalPages={totalPages}
currentPage={currentPage}
setNextPage={setNextPage}
setPreviousPage={setPreviousPage}
setPage={setPage}
nextEnabled={nextEnabled}
previousEnabled={previousEnabled}
/>
</>
);
}

View File

@ -0,0 +1,151 @@
import React, { useCallback, useEffect, useState } from "react";
import Translate from "@docusaurus/Translate";
import { usePagination } from "react-use-pagination";
import Admonition from "@theme/Admonition";
import Paginate from "@/components/Paginate";
import ResourceCard from "@/components/Resource/Card";
import Searcher from "@/components/Searcher";
import { authorFilter, tagFilter } from "@/libs/filter";
import { useSearchControl } from "@/libs/search";
import { fetchRegistryData, loadFailedTitle } from "@/libs/store";
import type { Driver } from "@/types/driver";
export default function DriverPage(): JSX.Element {
const [drivers, setDrivers] = useState<Driver[] | null>(null);
const driverCount = drivers?.length ?? 0;
const loading = drivers === null;
const [error, setError] = useState<Error | null>(null);
const {
filteredResources: filteredDrivers,
searcherTags,
addFilter,
onSearchQueryChange,
onSearchQuerySubmit,
onSearchBackspace,
onSearchClear,
onSearchTagClick,
} = useSearchControl<Driver>(drivers ?? []);
const filteredDriverCount = filteredDrivers.length;
const {
startIndex,
endIndex,
totalPages,
currentPage,
setNextPage,
setPreviousPage,
setPage,
previousEnabled,
nextEnabled,
} = usePagination({
totalItems: filteredDrivers.length,
initialPageSize: 12,
});
const currentDrivers = filteredDrivers.slice(startIndex, endIndex + 1);
// load drivers asynchronously
useEffect(() => {
fetchRegistryData("driver")
.then(setDrivers)
.catch((e) => {
setError(e);
console.error(e);
});
}, []);
const onCardClick = useCallback((driver: Driver) => {
// TODO: open driver modal
console.log(driver, "clicked");
}, []);
const onCardTagClick = useCallback(
(tag: string) => {
addFilter(tagFilter(tag));
},
[addFilter]
);
const onAuthorClick = useCallback(
(author: string) => {
addFilter(authorFilter(author));
},
[addFilter]
);
return (
<>
<p className="store-description">
{driverCount === filteredDriverCount ? (
<Translate
id="pages.store.driver.info"
description="Drivers info of the driver store page"
values={{ driverCount }}
>
{"当前共有 {driverCount} 个插件"}
</Translate>
) : (
<Translate
id="pages.store.driver.searchInfo"
description="Drivers search info of the driver store page"
values={{
driverCount,
filteredDriverCount,
}}
>
{"当前共有 {filteredDriverCount} / {driverCount} 个插件"}
</Translate>
)}
</p>
<Searcher
className="store-searcher not-prose"
onChange={onSearchQueryChange}
onSubmit={onSearchQuerySubmit}
onBackspace={onSearchBackspace}
onClear={onSearchClear}
onTagClick={onSearchTagClick}
tags={searcherTags}
disabled={loading}
/>
{error ? (
<Admonition type="caution" title={loadFailedTitle}>
{error.message}
</Admonition>
) : loading ? (
<p className="store-loading-container">
<span className="loading loading-dots loading-lg store-loading"></span>
</p>
) : (
<div className="store-container">
{currentDrivers.map((driver, index) => (
<ResourceCard
key={index}
className="not-prose"
resource={driver}
onClick={() => onCardClick(driver)}
onTagClick={onCardTagClick}
onAuthorClick={() => onAuthorClick(driver.author)}
/>
))}
</div>
)}
<Paginate
className="not-prose"
totalPages={totalPages}
currentPage={currentPage}
setNextPage={setNextPage}
setPreviousPage={setPreviousPage}
setPage={setPage}
nextEnabled={nextEnabled}
previousEnabled={previousEnabled}
/>
</>
);
}

View File

@ -0,0 +1,172 @@
import React, { useCallback, useEffect, useState } from "react";
import Translate from "@docusaurus/Translate";
import { usePagination } from "react-use-pagination";
import Admonition from "@theme/Admonition";
import Paginate from "@/components/Paginate";
import ResourceCard from "@/components/Resource/Card";
import Searcher from "@/components/Searcher";
import StoreToolbar, { type Action } from "@/components/Store/Toolbar";
import { authorFilter, tagFilter } from "@/libs/filter";
import { useSearchControl } from "@/libs/search";
import { fetchRegistryData, loadFailedTitle } from "@/libs/store";
import { useToolbar } from "@/libs/toolbar";
import type { Plugin } from "@/types/plugin";
export default function PluginPage(): JSX.Element {
const [plugins, setPlugins] = useState<Plugin[] | null>(null);
const pluginCount = plugins?.length ?? 0;
const loading = plugins === null;
const [error, setError] = useState<Error | null>(null);
const {
filteredResources: filteredPlugins,
searcherTags,
addFilter,
onSearchQueryChange,
onSearchQuerySubmit,
onSearchBackspace,
onSearchClear,
onSearchTagClick,
} = useSearchControl<Plugin>(plugins ?? []);
const filteredPluginCount = filteredPlugins.length;
const {
startIndex,
endIndex,
totalPages,
currentPage,
setNextPage,
setPreviousPage,
setPage,
previousEnabled,
nextEnabled,
} = usePagination({
totalItems: filteredPlugins.length,
initialPageSize: 12,
});
const currentPlugins = filteredPlugins.slice(startIndex, endIndex + 1);
// load plugins asynchronously
useEffect(() => {
fetchRegistryData("plugin")
.then(setPlugins)
.catch((e) => {
setError(e);
console.error(e);
});
}, []);
const { filters: filterTools } = useToolbar({
resources: plugins ?? [],
addFilter,
});
const actionTool: Action = {
label: "发布插件",
icon: ["fas", "plus"],
onClick: () => {
// TODO: open plugin release modal
window.open(
"https://github.com/nonebot/nonebot2/issues/new?template=plugin_publish.yml"
);
},
};
const onCardClick = useCallback((plugin: Plugin) => {
// TODO: open plugin modal
console.log(plugin, "clicked");
}, []);
const onCardTagClick = useCallback(
(tag: string) => {
addFilter(tagFilter(tag));
},
[addFilter]
);
const onCardAuthorClick = useCallback(
(author: string) => {
addFilter(authorFilter(author));
},
[addFilter]
);
return (
<>
<p className="store-description">
{pluginCount === filteredPluginCount ? (
<Translate
id="pages.store.plugin.info"
description="Plugins info of the plugin store page"
values={{ pluginCount }}
>
{"当前共有 {pluginCount} 个插件"}
</Translate>
) : (
<Translate
id="pages.store.plugin.searchInfo"
description="Plugins search info of the plugin store page"
values={{ pluginCount, filteredPluginCount: filteredPluginCount }}
>
{"当前共有 {filteredPluginCount} / {pluginCount} 个插件"}
</Translate>
)}
</p>
<Searcher
className="store-searcher not-prose"
onChange={onSearchQueryChange}
onSubmit={onSearchQuerySubmit}
onBackspace={onSearchBackspace}
onClear={onSearchClear}
onTagClick={onSearchTagClick}
tags={searcherTags}
disabled={loading}
/>
<StoreToolbar
className="not-prose"
filters={filterTools}
action={actionTool}
/>
{error ? (
<Admonition type="caution" title={loadFailedTitle}>
{error.message}
</Admonition>
) : loading ? (
<p className="store-loading-container">
<span className="loading loading-dots loading-lg store-loading"></span>
</p>
) : (
<div className="store-container">
{currentPlugins.map((plugin, index) => (
<ResourceCard
key={index}
className="not-prose"
resource={plugin}
onClick={() => onCardClick(plugin)}
onTagClick={onCardTagClick}
onAuthorClick={() => onCardAuthorClick(plugin.author)}
/>
))}
</div>
)}
<Paginate
className="not-prose"
totalPages={totalPages}
currentPage={currentPage}
setNextPage={setNextPage}
setPreviousPage={setPreviousPage}
setPage={setPage}
nextEnabled={nextEnabled}
previousEnabled={previousEnabled}
/>
</>
);
}

View File

@ -0,0 +1,50 @@
import React from "react";
import { PageMetadata } from "@docusaurus/theme-common";
import { useDocsVersionCandidates } from "@docusaurus/theme-common/internal";
import { useVersionedSidebar } from "@nullbot/docusaurus-plugin-getsidebar/client";
import { SidebarContentFiller } from "@nullbot/docusaurus-theme-nonepress/contexts";
import BackToTopButton from "@theme/BackToTopButton";
import Layout from "@theme/Layout";
import Page from "@theme/Page";
import "./styles.css";
const SIDEBAR_ID = "ecosystem";
type Props = {
title: string;
children: React.ReactNode;
};
function StorePage({ title, children }: Props): JSX.Element {
const sidebarItems = useVersionedSidebar(
useDocsVersionCandidates()[0].name,
SIDEBAR_ID
)!;
return (
<Page hideTableOfContents reduceContentWidth={false}>
<SidebarContentFiller items={sidebarItems} />
<article className="prose max-w-full">
<h1 className="store-title">{title}</h1>
{children}
</article>
</Page>
);
}
export default function StoreLayout({ title, ...props }: Props): JSX.Element {
return (
<>
<PageMetadata title={title} />
<Layout>
<BackToTopButton />
<StorePage title={title} {...props} />
</Layout>
</>
);
}

View File

@ -0,0 +1,128 @@
import React, { useState } from "react";
import clsx from "clsx";
import type { IconProp } from "@fortawesome/fontawesome-svg-core";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
export type Filter = {
label: string;
icon: IconProp;
choices?: string[];
onSubmit: (query: string) => void;
};
export type Action = {
label: string;
icon: IconProp;
onClick: () => void;
};
export type Props = {
filters?: Filter[];
action?: Action;
className?: string;
};
function ToolbarFilter({
label,
icon,
choices,
onSubmit,
}: Filter): JSX.Element {
const [query, setQuery] = useState<string>("");
const filteredChoices = choices
?.filter((choice) => choice.toLowerCase().includes(query.toLowerCase()))
?.slice(0, 5);
const handleQuerySubmit = () => {
if (filteredChoices && filteredChoices.length > 0) {
onSubmit(filteredChoices[0]);
} else if (choices === null) {
onSubmit(query);
}
};
const onQueryKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter") {
handleQuerySubmit();
e.preventDefault();
}
};
const onChoiceKeyDown = (e: React.KeyboardEvent<HTMLLIElement>) => {
if (e.key === "Enter") {
onSubmit(e.currentTarget.innerText);
e.preventDefault();
}
};
return (
<div className="dropdown">
<label
className="btn btn-sm btn-outline btn-primary no-animation"
tabIndex={0}
>
<FontAwesomeIcon icon={icon} />
{label}
</label>
<div className="dropdown-content store-toolbar-dropdown">
<input
type="text"
placeholder="搜索"
value={query}
onChange={(e) => setQuery(e.target.value)}
onKeyDown={onQueryKeyDown}
className="input input-sm input-bordered w-full"
/>
{filteredChoices && (
<ul className="menu menu-sm">
{filteredChoices.map((choice, index) => (
<li
key={index}
onClick={() => onSubmit(choice)}
onKeyDown={onChoiceKeyDown}
>
<a tabIndex={0}>{choice}</a>
</li>
))}
</ul>
)}
</div>
</div>
);
}
export default function StoreToolbar({
filters,
action,
className,
}: Props): JSX.Element | null {
if (!(filters && filters.length > 0) && !action) {
return null;
}
return (
<div className={clsx("store-toolbar", className)}>
{filters && filters.length > 0 && (
<div className="store-toolbar-filters">
{filters.map((filter, index) => (
<ToolbarFilter key={index} {...filter} />
))}
</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>
);
}

View File

@ -0,0 +1,38 @@
.store {
&-title {
@apply text-center;
}
&-description {
@apply text-center;
}
&-searcher {
@apply max-w-2xl mx-auto my-4;
}
&-toolbar {
@apply flex items-center justify-center my-4;
&-filters {
@apply flex grow gap-2;
}
&-dropdown {
@apply w-36 z-10 m-0 p-2;
@apply rounded-md bg-base-100 shadow-lg border border-base-200;
}
}
&-loading {
@apply text-primary;
&-container {
@apply text-center;
}
}
&-container {
@apply grid grid-cols-[repeat(auto-fill,minmax(300px,1fr))] gap-6 mt-4 mb-8;
}
}