From d17d5b811e4524898d14f3147ec89e021c857182 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=80=8F=E6=98=B1?= Date: Mon, 10 Jul 2023 14:01:50 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(config):=20add=20`giscus`=20an?= =?UTF-8?q?d=20`discord`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .husky/commit-msg | 0 .husky/pre-commit | 0 README.md | 29 ++++++++ example/.dumirc.ts | 10 ++- package.json | 3 +- src/components/StoreUpdater/index.tsx | 2 +- src/pages/Docs/index.tsx | 32 ++++---- src/plugin/index.ts | 5 -- src/slots/Footer/index.tsx | 13 ++-- src/slots/Header/DiscordButton.tsx | 37 +++++++++ src/slots/Header/index.tsx | 6 +- src/slots/PreviewerActions/index.tsx | 4 +- src/store/index.ts | 9 --- src/store/selectors/apiHeader.ts | 8 -- src/store/selectors/hero.ts | 25 ------- src/store/selectors/siteBasicInfo.ts | 11 +-- src/store/useSiteStore.ts | 5 +- src/store/useThemeStore.ts | 4 +- src/types/config.ts | 103 +++++--------------------- src/types/hero.ts | 2 +- 20 files changed, 135 insertions(+), 173 deletions(-) mode change 100644 => 100755 .husky/commit-msg mode change 100644 => 100755 .husky/pre-commit create mode 100644 src/slots/Header/DiscordButton.tsx diff --git a/.husky/commit-msg b/.husky/commit-msg old mode 100644 new mode 100755 diff --git a/.husky/pre-commit b/.husky/pre-commit old mode 100644 new mode 100755 diff --git a/README.md b/README.md index 55a2194..6223b4d 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,35 @@ $ pnpm install $ pnpm start ``` +Theme Config: + +```ts +interface SiteThemeConfig { + actions: HeroProps['actions']; + apiHeader?: ApiHeaderConfig | false; + description?: string; + features: FeaturesProps['items']; + footer?: string | false; + footerConfig?: FooterConfig; + giscus?: { + category: string; + categoryId: string; + repo: `${string}/${string}`; + repoId: string; + }; + hero?: HeroConfig | Record; + hideHomeNav?: boolean; + logo?: string; + name?: string; + siteToken?: SiteConfigToken; + socialLinks?: { + discord?: `https://discord.gg/${string}`; + github?: string; + }; + title?: string; +} +``` +
[![][back-to-top]](#readme-top) diff --git a/example/.dumirc.ts b/example/.dumirc.ts index d1069e4..dbcff62 100644 --- a/example/.dumirc.ts +++ b/example/.dumirc.ts @@ -27,7 +27,6 @@ const themeConfig = { sourceUrl: `{github}/tree/master/src/{atomId}/index.tsx`, }, description: 'Lobe UI is an open-source UI component library for building chatbot web apps', - features: [ { description: @@ -49,8 +48,15 @@ const themeConfig = { }, ], footer: 'Made with 🤯 by LobeHub', + giscus: { + category: 'Q&A', + categoryId: 'DIC_kwDOJloKoM4CXsCu', + repo: 'lobehub/lobe-ui', + repoId: 'R_kgDOJloKoA', + }, name: 'DUMI', socialLinks: { + discord: 'https://discord.gg/AYFPHvv2jT', github: homepage, }, title: 'Dumi Theme LobeHub', @@ -58,7 +64,7 @@ const themeConfig = { export default defineConfig({ alias: { - '@': resolve(__dirname, 'src'), + '@': resolve(__dirname, '../src'), 'dumi-theme-lobehub': resolve(__dirname, '../src'), }, codeSplitting: { diff --git a/package.json b/package.json index a411743..fbdd05f 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "prepublishOnly": "npm run build", "prettier": "prettier -c --write --no-error-on-unmatched-pattern \"**/**\"", "release": "semantic-release", - "start": "concurrently \"npm run dev\" \"npm run dev:docs\"", + "start": "npm run dev", "test": "vitest --passWithNoTests", "test:coverage": "vitest run --coverage --passWithNoTests", "test:update": "vitest -u", @@ -92,7 +92,6 @@ "@vitest/coverage-v8": "latest", "clean-pkg-json": "^1", "commitlint": "^17", - "concurrently": "^8", "dumi": "^2", "dumi-assets-types": "^1", "eslint": "^8", diff --git a/src/components/StoreUpdater/index.tsx b/src/components/StoreUpdater/index.tsx index 33bd7b8..d973fbc 100644 --- a/src/components/StoreUpdater/index.tsx +++ b/src/components/StoreUpdater/index.tsx @@ -64,7 +64,7 @@ const homeNav = { }; export const StoreUpdater = () => { - const siteData = useSiteData(); + const siteData: any = useSiteData(); const sidebar = useSidebarData(); const routeMeta = useRouteMeta(); const tabMeta = useTabMeta(); diff --git a/src/pages/Docs/index.tsx b/src/pages/Docs/index.tsx index d3346a9..511e888 100644 --- a/src/pages/Docs/index.tsx +++ b/src/pages/Docs/index.tsx @@ -7,28 +7,32 @@ import { shallow } from 'zustand/shallow'; import ApiHeader from '@/slots/ApiHeader'; import Content from '@/slots/Content'; -import { isApiPageSel, useSiteStore } from '@/store'; +import { giscusSel, isApiPageSel, useSiteStore } from '@/store'; import { useStyles } from './styles'; const Documents = memo(() => { const outlet = useOutlet(); const { mobile } = useResponsive(); - const isApiPage = useSiteStore(isApiPageSel, shallow); + const { isApiPage, giscus } = useSiteStore( + (st) => ({ giscus: giscusSel(st), isApiPage: isApiPageSel(st) }), + shallow, + ); const { styles } = useStyles(); const Comment = useCallback( - () => ( - - ), - [location.pathname], + () => + giscus && ( + + ), + [giscus, location.pathname], ); return ( <> @@ -41,7 +45,7 @@ const Documents = memo(() => { ) : undefined} {outlet} - + {giscus && } diff --git a/src/plugin/index.ts b/src/plugin/index.ts index 4728cd0..934c6c6 100644 --- a/src/plugin/index.ts +++ b/src/plugin/index.ts @@ -6,15 +6,11 @@ import { join } from 'node:path'; import { getHash } from './utils'; -/* - * SSR 抽取样式 - */ const SSRPlugin = (api: IApi) => { api.describe({ key: '@', }); - // 如果没有开启 SSR,则啥也不做 if (!api.userConfig.ssr) return; api.logger.info('detect ssr config, when building html will extract css.'); @@ -46,7 +42,6 @@ const SSRPlugin = (api: IApi) => { .map((file) => { const antdCache = (global as any).__ANTD_CACHE__; - // 提取 antd-style 样式到独立 css 文件 const styles = extractStaticStyle(file.content, { antdCache }); for (const result of styles) { diff --git a/src/slots/Footer/index.tsx b/src/slots/Footer/index.tsx index 81f85ec..ea7c24e 100644 --- a/src/slots/Footer/index.tsx +++ b/src/slots/Footer/index.tsx @@ -7,7 +7,7 @@ import { Center, Flexbox } from 'react-layout-kit'; import { shallow } from 'zustand/shallow'; import { githubSel, useSiteStore } from '@/store'; -import { IFooter } from '@/types'; +import { FooterConfig } from '@/types'; import { getColumns } from './columns'; import { useStyles } from './style'; @@ -20,12 +20,11 @@ const Footer = memo(() => { if (!themeConfig.footer) return; - const footer = themeConfig.footerConfig as IFooter; + const footer = themeConfig.footerConfig as FooterConfig; - const columns = - footer?.columns === false - ? undefined - : getColumns({ github: githubUrl || (pkg as any).homepage }); + const columns = footer?.columns + ? undefined + : getColumns({ github: githubUrl || (pkg as any).homepage }); const bottomFooter = footer?.bottom || themeConfig.footer; @@ -50,7 +49,7 @@ const Footer = memo(() => { } columns={columns} contentMaxWidth={theme.contentMaxWidth} - theme={footer?.theme || (theme.appearance as FooterProps['theme'])} + theme={theme.appearance as FooterProps['theme']} /> ); }); diff --git a/src/slots/Header/DiscordButton.tsx b/src/slots/Header/DiscordButton.tsx new file mode 100644 index 0000000..6ff8823 --- /dev/null +++ b/src/slots/Header/DiscordButton.tsx @@ -0,0 +1,37 @@ +import { ActionIcon } from '@lobehub/ui'; +import { createStyles } from 'antd-style'; +import { type LucideIcon, createLucideIcon } from 'lucide-react'; +import { memo } from 'react'; + +import { discordSel, useSiteStore } from '@/store'; + +const Discord: LucideIcon = createLucideIcon('Discord', [ + [ + 'path', + { + d: 'M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z', + key: '18tl5t', + }, + ], +]); + +const useStyles = createStyles( + ({ css }) => css` + svg { + overflow: visible !important; + } + `, +); + +const DiscordButton = memo(() => { + const inviteUrl = useSiteStore(discordSel); + const { styles } = useStyles(); + + return inviteUrl ? ( + + + + ) : undefined; +}); + +export default DiscordButton; diff --git a/src/slots/Header/index.tsx b/src/slots/Header/index.tsx index abc31e3..43e5904 100644 --- a/src/slots/Header/index.tsx +++ b/src/slots/Header/index.tsx @@ -8,6 +8,7 @@ import SearchBar from '@/slots/SearchBar'; import { useSiteStore } from '@/store/useSiteStore'; import Burger from './Burger'; +import DiscordButton from './DiscordButton'; import GithubButton from './GithubButton'; import LangSwitch from './LangSwitch'; import ThemeSwitch from './ThemeSwitch'; @@ -25,10 +26,9 @@ const Header = memo(() => { ) : ( <> - {' '} - - + + ) diff --git a/src/slots/PreviewerActions/index.tsx b/src/slots/PreviewerActions/index.tsx index e14f845..493f956 100644 --- a/src/slots/PreviewerActions/index.tsx +++ b/src/slots/PreviewerActions/index.tsx @@ -8,7 +8,7 @@ import { useStyles } from './style'; const SIZE = { blockSize: 24, fontSize: 16, strokeWidth: 2 }; -export interface IPreviewerActionsProps extends IPreviewerProps { +export interface PreviewerActionsProps extends IPreviewerProps { demoContainer: HTMLDivElement | HTMLIFrameElement; /** * disabled actions @@ -18,7 +18,7 @@ export interface IPreviewerActionsProps extends IPreviewerProps { forceShowCode?: boolean; } -const PreviewerActions: FC = (props) => { +const PreviewerActions: FC = (props) => { const intl = useIntl(); const files = Object.entries(props.asset.dependencies).filter(([, { type }]) => type === 'FILE'); const [activeKey, setActiveKey] = useState(0); diff --git a/src/store/index.ts b/src/store/index.ts index 39e0a08..bb8ecb7 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -4,17 +4,8 @@ export * from './selectors'; export * from './useSiteStore'; export * from './useThemeStore'; -/** - * @title 数据选择器 - */ export const siteSelectors = { - /** - * @title API 头部选择器 - */ apiHeader: apiHeaderSel, - /** - * @title 扁平化侧边栏选择器 - */ flattenSidebar: flattenSidebarSel, token: tokenSel, }; diff --git a/src/store/selectors/apiHeader.ts b/src/store/selectors/apiHeader.ts index 5e31c04..a490194 100644 --- a/src/store/selectors/apiHeader.ts +++ b/src/store/selectors/apiHeader.ts @@ -28,7 +28,6 @@ export const apiHeaderSel = (s: SiteStore): ApiHeaderProps => { const fm = s.routeMeta.frontmatter; const localeId = s.locale.id; - // 统一的路径匹配替换方法 const replaceUrl = (rawString: string) => { return rawString .replace('{github}', REPO_BASE) @@ -43,17 +42,10 @@ export const apiHeaderSel = (s: SiteStore): ApiHeaderProps => { docUrl: documentUrlMatch, } = (s.siteData.themeConfig.apiHeader || {}) as ApiHeaderConfig; - // 1. 兜底默认使用文档的 apiHeader.pkg - // 2. 如果 themeConfig 里配置了 pkg, 则使用配置的 pkg - // 3. 兜底使用 package.json 中的 name const displayPackage = fm.apiHeader?.pkg || package_; - // 1. 默认使用文档的 fm.atomId - // 2. 兜底到文档 title const componentName = fm.atomId || fm.title; - // 1. 优先选择使用文档 apiHeader.defaultImport - // 2. 默认使用 false const defaultImport = fm.apiHeader?.defaultImport || false; const sourceUrl = diff --git a/src/store/selectors/hero.ts b/src/store/selectors/hero.ts index b0fd3d6..ee882f4 100644 --- a/src/store/selectors/hero.ts +++ b/src/store/selectors/hero.ts @@ -12,53 +12,28 @@ const localeValueSel = (s: SiteStore, value: any) => { return value; }; -/** - * Hero Title 选择器 - * 选择逻辑:优先使用 hero 配置的 title, 再兜底到 themeConfig 中的 name - */ export const heroTitleSel = (s: SiteStore) => s.routeMeta.frontmatter.hero?.title || - // 从 hero 的 title 中选择 localeValueSel(s, s.siteData.themeConfig.hero)?.title || - // @deprecated 1.0 正式版本移除 - // 从 hero 的 title 中选择 localeValueSel(s, s.siteData.themeConfig.title) || s.siteData.themeConfig.name; -/** - * Hero description 选择器 - * 选择逻辑:优先使用 hero 配置的 description, 再兜底到 themeConfig 中的 name - */ export const heroDescSel = (s: SiteStore) => s.routeMeta.frontmatter.hero?.description || - // 从 hero 的 description 中选择 localeValueSel(s, s.siteData.themeConfig.hero)?.description || - // @deprecated 1.0 正式版本移除 - // 从 hero 的 description 中选择 localeValueSel(s, s.siteData.themeConfig.description); -/** - * Hero Action 选择器 - * 选择逻辑:优先使用 hero 配置的 actions, 再兜底到 themeConfig 中的 actions - */ export const heroActionsSel = (s: SiteStore) => s.routeMeta.frontmatter.hero?.actions || - // 从 hero 的 actions 中选择 localeValueSel(s, s.siteData.themeConfig.hero)?.actions || - // @deprecated 1.0 正式版本移除 localeValueSel(s, s.siteData.themeConfig.actions); -/** - * Features 选择器 - */ export const featuresSel = (s: SiteStore): FeatureItem[] => { if (!isHeroPageSel(s)) return []; return ( localeValueSel(s, s.siteData.themeConfig.hero)?.features || - // @deprecated 1.0 正式版本移除 localeValueSel(s, s.siteData.themeConfig.features) || - // 在themeConfig 没有配置的话,尝试兜底到 frontmatter 中的配置 s.routeMeta.frontmatter.features || [] ); diff --git a/src/store/selectors/siteBasicInfo.ts b/src/store/selectors/siteBasicInfo.ts index 5c8f2f3..4e28967 100644 --- a/src/store/selectors/siteBasicInfo.ts +++ b/src/store/selectors/siteBasicInfo.ts @@ -1,11 +1,8 @@ import { SiteStore } from '../useSiteStore'; -/** - * 站点标题选择器 - */ export const siteTitleSel = (s: SiteStore) => s.siteData.themeConfig.title; -export const githubSel = (s: SiteStore) => - // 优先取 socialLinks 里的 github - // TODO: 后面的 github 在 1.0 里废弃 - s.siteData.themeConfig.socialLinks?.github || s.siteData.themeConfig.github; +export const githubSel = (s: SiteStore) => s.siteData.themeConfig.socialLinks?.github || ''; +export const discordSel = (s: SiteStore) => s.siteData.themeConfig.socialLinks?.discord || ''; + +export const giscusSel = (s: SiteStore) => s.siteData.themeConfig.giscus; diff --git a/src/store/useSiteStore.ts b/src/store/useSiteStore.ts index 0f13b0b..9fdff11 100644 --- a/src/store/useSiteStore.ts +++ b/src/store/useSiteStore.ts @@ -6,7 +6,6 @@ import { IPreviewerProps, IRouteMeta, ISidebarGroup, - IThemeConfig, } from 'dumi/dist/client/theme-api/types'; import { PICKED_PKG_FIELDS } from 'dumi/dist/constants'; import type { Location } from 'history'; @@ -14,6 +13,8 @@ import { ComponentType } from 'react'; import { create } from 'zustand'; import { devtools } from 'zustand/middleware'; +import { SiteThemeConfig } from '@/types'; + export type NavData = (INavItem & { children?: INavItem[] | undefined })[]; export interface ISiteData { @@ -32,7 +33,7 @@ export interface ISiteData { locales: ILocalesConfig; pkg: Partial>; setLoading: (status: boolean) => void; - themeConfig: IThemeConfig; + themeConfig: SiteThemeConfig; } export interface SiteStore { diff --git a/src/store/useThemeStore.ts b/src/store/useThemeStore.ts index 7f0b282..8e58ad1 100644 --- a/src/store/useThemeStore.ts +++ b/src/store/useThemeStore.ts @@ -2,10 +2,10 @@ import type { ThemeMode } from 'antd-style'; import { create } from 'zustand'; import { persist } from 'zustand/middleware'; -interface Store { +export interface ThemeStore { themeMode: ThemeMode; } -export const useThemeStore = create()( +export const useThemeStore = create()( persist( () => ({ themeMode: 'auto' as ThemeMode, diff --git a/src/types/config.ts b/src/types/config.ts index 5816a92..858898d 100644 --- a/src/types/config.ts +++ b/src/types/config.ts @@ -1,107 +1,44 @@ -import { FooterColumn } from 'rc-footer/es/column'; +import type { FeaturesProps, FooterProps, HeroProps } from '@lobehub/ui'; -import { SiteCustomToken } from '../styles/customToken'; -import { IHero } from './hero'; +import type { SiteCustomToken } from '@/styles/customToken'; + +import type { HeroConfig } from './hero'; export interface ApiHeaderConfig { - /** - * @title 文档链接 - * @description 点击 ApiHeader 组件的文档链接跳转的地址 - */ docUrl?: string | false; - /** - * @title 匹配路由 - * @description ApiHeader 组件的匹配路由 - * @default ["/api", "/components"] - */ match?: string[]; - /** - * @title 组件库包名 - * @description 可以从 package.json 中引入名称 - */ pkg?: string; - /** - * @title 源代码链接 - * @description 点击 ApiHeader 组件的源代码链接跳转的地址 - */ sourceUrl?: string | false; } -/** - * @title 页面底部 Footer 组件的配置 - */ -export interface IFooter { - /** - * @title 底部内容 - */ - bottom?: string; - /** - * @title 列配置 - */ - columns?: FooterColumn[] | false; - /** - * @title 主题 - * @enum ['dark', 'light'] - * @enumNames ['黑色', '白色'] - */ - theme?: 'dark' | 'light'; +export interface FooterConfig { + bottom?: FooterProps['bottom']; + columns?: FooterProps['columns']; } -/** - * 网站主题配置 - */ export interface SiteThemeConfig { - /** - * API 文档页头部配置 - * @type ApiHeaderConfig | false - */ + actions: HeroProps['actions']; apiHeader?: ApiHeaderConfig | false; - /** - * 网站页脚 - * @type string | false - */ + description?: string; + features: FeaturesProps['items']; footer?: string | false; - /** - * 网站页脚配置 - * @type IFooter - */ - footerConfig?: IFooter; - /** - * 导航栏 Github 图标链接,如不配置该字段,则不展示。 - */ - github?: string; - /** - * 网站首页头部 - * @type IHero | Record - */ - hero?: IHero | Record; - /** - * 是否隐藏首页的 nav tab,配置为 `false` 则不展示首页的 tab - */ + footerConfig?: FooterConfig; + giscus?: { + category: string; + categoryId: string; + repo: `${string}/${string}`; + repoId: string; + }; + hero?: HeroConfig | Record; hideHomeNav?: boolean; - /** - * 网站 logo 图片链接 - */ logo?: string; - /** - * 网站名称 - */ name?: string; - - /** - * 网站主题 Token 配置 - */ siteToken?: SiteConfigToken; socialLinks?: { - facebook?: string; + discord?: `https://discord.gg/${string}`; github?: string; - gitlab?: string; - linkedin?: string; - twitter?: string; - weibo?: string; - yueque?: string; - zhihu?: string; }; + title?: string; } export type SiteConfigToken = Partial< diff --git a/src/types/hero.ts b/src/types/hero.ts index ab9fd77..678e1fc 100644 --- a/src/types/hero.ts +++ b/src/types/hero.ts @@ -2,7 +2,7 @@ import { type FeatureItem, type HeroAction } from '@lobehub/ui'; export type ImageContainerType = 'light' | 'primary' | 'soon'; -export interface IHero { +export interface HeroConfig { actions: HeroAction[]; description?: string; features?: FeatureItem[];