diff --git a/package.json b/package.json index f7db9e6..a91d6b8 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "scripts": { "build": "father build", "ci": "npm run lint && npm run type-check", - "dev": "father dev", + "dev": "concurrently -n dumi,example -c blue,yellow \"father dev\" \"npm run docs:dev\"", "docs:build": "cd example && npm run build", "docs:dev": "cd example && npm run dev", "doctor": "father doctor", @@ -69,8 +69,8 @@ ] }, "dependencies": { - "@floating-ui/react": "^0.26.17", - "ahooks": "^3.8.0", + "@floating-ui/react": "^0.26.28", + "ahooks": "^3.8.1", "chalk": "^4.1.2", "fast-deep-equal": "^3.1.3", "history": "^5.3.0", @@ -78,42 +78,46 @@ "polished": "^4.3.1", "rc-footer": "^0.6.8", "react-layout-kit": "^1.9.0", + "swr": "^2.2.5", "use-merge-value": "^1.2.0", - "zustand": "^4.5.2" + "zustand": "^4.5.5", + "zustand-utils": "^1.3.2" }, "devDependencies": { - "@commitlint/cli": "^19.3.0", - "@lobehub/lint": "^1.23.4", + "@commitlint/cli": "^19.6.0", + "@lobehub/lint": "^1.24.4", "@types/lodash-es": "^4.17.12", - "@types/node": "^20.14.2", - "@types/react": "^18.3.3", - "@types/react-dom": "^18.3.0", + "@types/node": "^20.17.6", + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", "@vitest/coverage-v8": "^1.6.0", "clean-pkg-json": "^1.2.0", - "commitlint": "^19.3.0", - "dumi-assets-types": "^2.3.0", - "eslint": "^8.57.0", - "father": "4.3.1", + "commitlint": "^19.6.0", + "concurrently": "^9.1.0", + "dumi-assets-types": "^2.4.14", + "eslint": "^8.57.1", + "father": "^4.5.1", "father-plugin-dumi-theme": "1.0.0-rc.1", - "husky": "^9.0.11", + "husky": "^9.1.7", "jsdom": "^22.1.0", "leva": "^0.9.35", "lint-staged": "^13.3.0", - "prettier": "^3.3.2", + "prettier": "^3.3.3", "react": "^18.3.1", "react-dom": "^18.3.1", "remark": "^14.0.3", "remark-cli": "^11.0.0", "semantic-release": "^21.1.2", "stylelint": "^15.11.0", - "typescript": "^5.4.5", + "typescript": "^5.6.3", "vitest": "^1.6.0" }, "peerDependencies": { "@giscus/react": ">=3", "@lobehub/ui": ">=1", "antd": ">=5", - "dumi": "~2.2", + "antd-style": ">=3", + "dumi": ">=2", "lucide-react": ">=0.292", "react": ">=18", "react-dom": ">=18", diff --git a/src/components/ApiHeader/index.tsx b/src/components/ApiHeader/index.tsx index 6bc446e..dfa2723 100644 --- a/src/components/ApiHeader/index.tsx +++ b/src/components/ApiHeader/index.tsx @@ -3,7 +3,7 @@ import { Divider, Space, Typography } from 'antd'; import { useResponsive } from 'antd-style'; import { Edit3, Github } from 'lucide-react'; import { type ReactNode, memo } from 'react'; -import { Flexbox } from 'react-layout-kit'; +import { Flexbox, FlexboxProps } from 'react-layout-kit'; import { ApiHeaderConfig } from '@/types'; @@ -72,7 +72,7 @@ export interface ServiceItem { url: string; } -export const ApiHeader = memo( +export const ApiHeader = memo( ({ title, type, @@ -83,6 +83,7 @@ export const ApiHeader = memo( sourceUrl, docUrl, serviceList = [], + ...rest }) => { const { styles } = useStyles(); const { mobile } = useResponsive(); @@ -106,7 +107,7 @@ export const ApiHeader = memo( : `import { ${componentName} } from '${pkg}';`; return ( - + {title} {description && {description}} {!isDoc && ( diff --git a/src/components/StoreUpdater/index.tsx b/src/components/StoreUpdater/index.tsx index d973fbc..2259b02 100644 --- a/src/components/StoreUpdater/index.tsx +++ b/src/components/StoreUpdater/index.tsx @@ -1,30 +1,23 @@ import { useDebounceEffect } from 'ahooks'; -import { - useLocale, - useLocation, - useNavData, - useRouteMeta, - useSidebarData, - useSiteData, - useTabMeta, -} from 'dumi'; +import { useLocale, useLocation, useNavData, useSidebarData, useSiteData, useTabMeta } from 'dumi'; import isEqual from 'fast-deep-equal'; -import React, { type DependencyList, type EffectCallback, useEffect } from 'react'; +import React, { memo, useEffect } from 'react'; -import { SiteStore, useSiteStore } from '@/store/useSiteStore'; +import { SiteStore, useStoreApi } from '../../store/useSiteStore'; +import { useRouteMeta } from './useRouteMeta'; const isBrowser = typeof window !== 'undefined'; const SSRInit: Record = {}; -const useReact18xUpdater = (effect: EffectCallback, deps?: DependencyList) => { +const useReact18xUpdater = (effect: React.EffectCallback, deps?: React.DependencyList) => { useEffect(() => { (React as any).startTransition(() => { effect(); }); }, deps); }; -const useLegacyUpdater = (effect: EffectCallback, deps?: DependencyList) => { +const useLegacyUpdater = (effect: React.EffectCallback, deps?: React.DependencyList) => { useDebounceEffect( () => { effect(); @@ -41,9 +34,10 @@ const useSyncState = ( value: SiteStore[T], updateMethod?: (key: T, value: SiteStore[T]) => void, ) => { + const storeApi = useStoreApi(); const updater = updateMethod ? updateMethod - : (key: T, value: SiteStore[T]) => useSiteStore.setState({ [key]: value }); + : (key: T, value: SiteStore[T]) => storeApi.setState({ [key]: value }); // 如果是 Node 环境,直接更新一次 store // 但是为了避免多次更新 store,所以加一个标记 @@ -57,45 +51,51 @@ const useSyncState = ( }, [value]); }; -const homeNav = { +const displayLangHomeNavMap: Record = { + 'en-US': 'Home', + 'zh-CN': '首页', +}; + +const getHomeNav = (id: string) => ({ activePath: '/', link: '/', - title: 'Home', -}; + title: displayLangHomeNavMap[id], +}); -export const StoreUpdater = () => { - const siteData: any = useSiteData(); +export const StoreUpdater = memo(() => { + const siteData = useSiteData(); const sidebar = useSidebarData(); - const routeMeta = useRouteMeta(); const tabMeta = useTabMeta(); const navData = useNavData(); const location = useLocation(); const locale = useLocale(); + const storeApi = useStoreApi(); + + useRouteMeta(); useSyncState('siteData', siteData, () => { // eslint-disable-next-line @typescript-eslint/no-unused-vars const { setLoading, ...data } = siteData; const { // eslint-disable-next-line @typescript-eslint/no-unused-vars - siteData: { setLoading: _, ...previousData }, - } = useSiteStore.getState(); + siteData: { setLoading: _, ...prevData }, + } = storeApi.getState(); - if (isEqual(data, previousData)) return; + if (isEqual(data, prevData)) return; - useSiteStore.setState({ siteData }); + storeApi.setState({ siteData }); }); useSyncState('sidebar', sidebar); - useSyncState('routeMeta', routeMeta); useSyncState('location', location); useSyncState('tabMeta', tabMeta); useSyncState('locale', locale); useSyncState('navData', navData, () => { - const data = siteData.themeConfig.hideHomeNav ? navData : [homeNav, ...navData]; + const data = siteData.themeConfig.hideHomeNav ? navData : [getHomeNav(locale.id), ...navData]; - useSiteStore.setState({ navData: data }); + storeApi.setState({ navData: data }); }); - return false; -}; + return null; +}); diff --git a/src/components/StoreUpdater/useRouteMeta.ts b/src/components/StoreUpdater/useRouteMeta.ts new file mode 100644 index 0000000..846d503 --- /dev/null +++ b/src/components/StoreUpdater/useRouteMeta.ts @@ -0,0 +1,33 @@ +import { getRouteMetaById, useMatchedRoute } from 'dumi'; +import type { IRouteMeta } from 'dumi/dist/client/theme-api/types'; +import useSWR from 'swr'; + +import { useStoreApi } from '../../store/useSiteStore'; + +const EMPTY_META = { + frontmatter: {}, + texts: [], + toc: [], +} as any; + +// https://github.com/umijs/dumi/pull/2165 +export const useRouteMeta = () => { + const storeApi = useStoreApi(); + const matched = useMatchedRoute(); + + const merge = (meta: IRouteMeta = EMPTY_META) => { + if (matched.meta) { + for (const key of Object.keys(matched.meta)) { + (meta as any)[key] ??= (matched.meta as any)[key]; + } + } + return meta; + }; + + useSWR(matched.id, getRouteMetaById, { + fallback: EMPTY_META, + onSuccess: (meta) => { + storeApi.setState({ routeMeta: merge(meta) }); + }, + }); +}; diff --git a/src/layouts/DocLayout/DocumentLayout.tsx b/src/layouts/DocLayout/DocumentLayout.tsx index 391a0e2..0a6c4a3 100644 --- a/src/layouts/DocLayout/DocumentLayout.tsx +++ b/src/layouts/DocLayout/DocumentLayout.tsx @@ -3,7 +3,6 @@ import { useResponsive, useTheme } from 'antd-style'; import { Helmet, useIntl, useLocation } from 'dumi'; import isEqual from 'fast-deep-equal'; import { memo, useCallback, useEffect } from 'react'; -import { shallow } from 'zustand/shallow'; import Changelog from '@/pages/Changelog'; import Docs from '@/pages/Docs'; @@ -39,7 +38,7 @@ const DocumentLayout = memo(() => { page: page, siteTitle: siteTitleSel(s), }; - }, shallow); + }); const fm = useSiteStore((s) => s.routeMeta.frontmatter, isEqual); diff --git a/src/layouts/DocLayout/ThemeProvider.tsx b/src/layouts/DocLayout/ThemeProvider.tsx new file mode 100644 index 0000000..9b1bef2 --- /dev/null +++ b/src/layouts/DocLayout/ThemeProvider.tsx @@ -0,0 +1,22 @@ +import { ThemeProvider } from '@lobehub/ui'; +import isEqual from 'fast-deep-equal'; +import { PropsWithChildren, memo } from 'react'; + +import GlobalStyle from '@/layouts/DocLayout/GlobalStyle'; +import { siteSelectors, useSiteStore, useThemeStore } from '@/store'; +import customToken from '@/styles/customToken'; + +export default memo(({ children }) => { + const themeMode = useThemeStore((st) => st.themeMode); + const userToken = useSiteStore(siteSelectors.token, isEqual); + + return ( + Object.assign({}, customToken(themeToken), userToken)} + themeMode={themeMode} + > + + {children} + + ); +}); diff --git a/src/layouts/DocLayout/index.tsx b/src/layouts/DocLayout/index.tsx index 3ef3a90..7796f43 100644 --- a/src/layouts/DocLayout/index.tsx +++ b/src/layouts/DocLayout/index.tsx @@ -1,32 +1,46 @@ -import { ThemeProvider } from '@lobehub/ui'; -import { memo } from 'react'; -import { shallow } from 'zustand/shallow'; +import { + useLocale, + useLocation, + useNavData, + useRouteMeta, + useSidebarData, + useSiteData, + useTabMeta, +} from 'dumi'; +import { memo, useMemo } from 'react'; import Favicons from '@/components/Favicons'; import { StoreUpdater } from '@/components/StoreUpdater'; -import GlobalStyle from '@/layouts/DocLayout/GlobalStyle'; -import { siteSelectors, useSiteStore, useThemeStore } from '@/store'; -import customToken from '@/styles/customToken'; +import { Provider, createStore } from '@/store'; import DocumentLayout from './DocumentLayout'; +import ThemeProvider from './ThemeProvider'; -const App = memo(() => { - const themeMode = useThemeStore((st) => st.themeMode, shallow); - const userToken = useSiteStore(siteSelectors.token); - +const App = memo(({ initState }: any) => { return ( - <> + createStore(initState)}> - Object.assign({}, customToken(themeToken), userToken)} - themeMode={themeMode} - > - + - + ); }); -export default App; +export default memo(() => { + const siteData = useSiteData(); + const sidebar = useSidebarData(); + const routeMeta = useRouteMeta(); + const tabMeta = useTabMeta(); + const navData = useNavData(); + const location = useLocation(); + const locale = useLocale(); + + const initState = useMemo( + () => ({ locale, location, navData, routeMeta, sidebar, siteData, tabMeta }), + [], + ); + + return ; +}); diff --git a/src/pages/Changelog/index.tsx b/src/pages/Changelog/index.tsx index 2b6cbdf..ad9d9de 100644 --- a/src/pages/Changelog/index.tsx +++ b/src/pages/Changelog/index.tsx @@ -3,7 +3,6 @@ import { useOutlet } from 'dumi'; import isEqual from 'fast-deep-equal'; import { memo, useEffect } from 'react'; import { Center } from 'react-layout-kit'; -import { shallow } from 'zustand/shallow'; import { ApiHeader } from '@/components/ApiHeader'; import { useStyles } from '@/pages/Docs/styles'; @@ -13,12 +12,9 @@ import { githubSel, useSiteStore } from '@/store'; const Changelog = memo(() => { const outlet = useOutlet(); const { mobile } = useResponsive(); - const { repoBase } = useSiteStore( - (s) => ({ - repoBase: githubSel(s), - }), - shallow, - ); + const { repoBase } = useSiteStore((s) => ({ + repoBase: githubSel(s), + })); const { fm } = useSiteStore( (s) => ({ @@ -35,25 +31,16 @@ const Changelog = memo(() => { }, []); return ( - <> -
-
-
- -
- {outlet} -
- +
+ + {outlet} +
); }); diff --git a/src/pages/Docs/index.tsx b/src/pages/Docs/index.tsx index 342fccb..b71c66c 100644 --- a/src/pages/Docs/index.tsx +++ b/src/pages/Docs/index.tsx @@ -3,7 +3,6 @@ import { useResponsive } from 'antd-style'; import { useOutlet } from 'dumi'; import { memo, useCallback, useEffect } from 'react'; import { Center } from 'react-layout-kit'; -import { shallow } from 'zustand/shallow'; import ApiHeader from '@/slots/ApiHeader'; import Content from '@/slots/Content'; @@ -14,10 +13,10 @@ import { useStyles } from './styles'; const Documents = memo(() => { const outlet = useOutlet(); const { mobile } = useResponsive(); - const { isApiPage, giscus } = useSiteStore( - (st) => ({ giscus: giscusSel(st), isApiPage: isApiPageSel(st) }), - shallow, - ); + const { isApiPage, giscus } = useSiteStore((st) => ({ + giscus: giscusSel(st), + isApiPage: isApiPageSel(st), + })); const { styles } = useStyles(); useEffect(() => { @@ -42,20 +41,13 @@ const Documents = memo(() => { [giscus, location.pathname], ); return ( - <> -
-
- {isApiPage ? ( -
- -
- ) : undefined} - - {outlet} - {giscus && } - -
- +
+ {isApiPage && } + + {outlet} + {giscus && } + +
); }); diff --git a/src/pages/Docs/styles.ts b/src/pages/Docs/styles.ts index db57b91..294e19c 100644 --- a/src/pages/Docs/styles.ts +++ b/src/pages/Docs/styles.ts @@ -1,24 +1,6 @@ import { createStyles } from 'antd-style'; -export const useStyles = createStyles(({ cx, css, token, stylish }) => ({ - background: cx( - stylish.gradientAnimation, - css` - pointer-events: none; - - position: absolute; - z-index: 0; - top: -100px; - right: -20vw; - transform: rotate(4deg); - - width: 60vw; - height: 200px; - - opacity: 0.2; - filter: blur(100px); - `, - ), +export const useStyles = createStyles(({ css, token }) => ({ changelog: css` .markdown { font-size: 16px; @@ -43,6 +25,13 @@ export const useStyles = createStyles(({ cx, css, token, stylish }) => ({ summary > kbd { margin-left: 6px; + padding: unset; + + font-size: inherit; + line-height: inherit; + + background: unset; + border: unset; } a[href='/changelog#readme-top'] { diff --git a/src/plugin/index.ts b/src/plugin/index.ts index 934c6c6..8dd286f 100644 --- a/src/plugin/index.ts +++ b/src/plugin/index.ts @@ -40,7 +40,7 @@ const SSRPlugin = (api: IApi) => { .filter((f) => !f.path.includes(':')) .map((file) => { - const antdCache = (global as any).__ANTD_CACHE__; + const antdCache = (global as any).__LOBE_CACHE__; const styles = extractStaticStyle(file.content, { antdCache }); diff --git a/src/slots/ApiHeader/index.tsx b/src/slots/ApiHeader/index.tsx index cc8cf8d..8336792 100644 --- a/src/slots/ApiHeader/index.tsx +++ b/src/slots/ApiHeader/index.tsx @@ -1,5 +1,6 @@ import isEqual from 'fast-deep-equal'; import { memo, useMemo } from 'react'; +import { FlexboxProps } from 'react-layout-kit'; import { ApiHeader as Header } from '@/components/ApiHeader'; import { apiHeaderSel, useSiteStore } from '@/store'; @@ -10,7 +11,7 @@ import NpmFilled from './NpmFilled'; import PackagePhobia from './PackagePhobia'; import Unpkg from './Unpkg'; -const ApiHeader = memo(() => { +const ApiHeader = memo(({ ...rest }) => { const props = useSiteStore(apiHeaderSel, isEqual); const { pkg } = props; @@ -51,7 +52,7 @@ const ApiHeader = memo(() => { ]; }, [pkg]); - return
; + return
; }); export default ApiHeader; diff --git a/src/slots/Content/index.tsx b/src/slots/Content/index.tsx index 42ce0bb..86a12a3 100644 --- a/src/slots/Content/index.tsx +++ b/src/slots/Content/index.tsx @@ -14,7 +14,7 @@ import { useStyles } from './style'; const Content = memo(({ children, ...props }) => { const loading = useSiteStore((s) => s.siteData.loading); const { docStyle } = useSiteStore(themeConfig, isEqual); - const { styles, cx } = useStyles(docStyle === 'pure'); + const { styles } = useStyles(docStyle === 'pure'); const { mobile } = useResponsive(); useEffect(() => { @@ -23,9 +23,10 @@ const Content = memo(({ children, ...props }) => { return ( -
+
{ url: 'https://github.com/lobehub/lobe-ui', }, { - description: 'Awesome lint configs', + description: 'AI / LLM Icon Collection', openExternal: true, - title: 'Lobe Lint', - url: 'https://github.com/lobehub/lobe-chat', + title: 'Lobe Icon', + url: 'https://github.com/lobehub/lobe-icons', + }, + { + description: 'Modern Charts', + openExternal: true, + title: 'Lobe Charts', + url: 'https://github.com/lobehub/lobe-charts', }, { - description: 'Lobe Dumi Theme', + description: 'TTS/STT Library', openExternal: true, - title: 'Designed for Dumi 2', - url: 'https://github.com/lobehub/lobe-flow', + title: 'Lobe TTS', + url: 'https://github.com/lobehub/lobe-tts', }, ], title: 'Resources', @@ -69,31 +75,25 @@ export const getColumns = ({ github }: GetColumnParameters) => { const more: FooterColumn = { items: [ { - description: 'OpenAI Chat Bot', + description: 'AI/LLM Chat Framework', openExternal: true, - title: '🤖 Lobe Chat', + title: '🤯 Lobe Chat', url: 'https://github.com/lobehub/lobe-chat', }, { - description: 'Stable Diffusion Extension', - openExternal: true, - title: '🤯 Lobe Theme', - url: 'https://github.com/lobehub/sd-webui-lobe-theme', - }, - { - description: 'Gen intelligently', + description: 'Virtual Idols for EveryOne', openExternal: true, - title: '📝 Readme Generator', - url: 'https://ui.lobehub.com', + title: '🧸 Lobe Vidol', + url: 'https://github.com/lobehub/lobe-vidol', }, { - description: 'AI Commit CLI', + description: 'Stable Diffusion Extension', openExternal: true, - title: '💌 Lobe Commit', - url: 'https://github.com/lobehub/lobe-commit', + title: '🅰️ Lobe Theme', + url: 'https://github.com/lobehub/sd-webui-lobe-theme', }, { - description: 'AI i18n CLI', + description: 'AI i18next CLI', openExternal: true, title: '🌐 Lobe i18n', url: 'https://github.com/lobehub/lobe-commit', diff --git a/src/slots/Footer/index.tsx b/src/slots/Footer/index.tsx index 71aa09a..1503285 100644 --- a/src/slots/Footer/index.tsx +++ b/src/slots/Footer/index.tsx @@ -4,7 +4,6 @@ import { useResponsive } from 'antd-style'; import isEqual from 'fast-deep-equal'; import { memo } from 'react'; import { Center, Flexbox } from 'react-layout-kit'; -import { shallow } from 'zustand/shallow'; import { githubSel, useSiteStore } from '@/store'; @@ -14,7 +13,7 @@ import { useStyles } from './style'; const Footer = memo(() => { const { themeConfig, pkg } = useSiteStore((s) => s.siteData, isEqual); const { footerConfig, footer } = themeConfig; - const githubUrl = useSiteStore(githubSel, shallow); + const githubUrl = useSiteStore(githubSel); const { styles, theme } = useStyles(); const { mobile } = useResponsive(); diff --git a/src/slots/Header/Burger.tsx b/src/slots/Header/Burger.tsx index b785d49..c6a8753 100644 --- a/src/slots/Header/Burger.tsx +++ b/src/slots/Header/Burger.tsx @@ -3,7 +3,6 @@ import { Link } from 'dumi'; import isEqual from 'fast-deep-equal'; import { uniq } from 'lodash-es'; import { memo, useMemo, useState } from 'react'; -import { shallow } from 'zustand/shallow'; import { activePathSel, useSiteStore } from '@/store'; @@ -12,13 +11,10 @@ const Burger = memo(() => { const nav = useSiteStore((s) => s.navData, isEqual); const sidebar = useSiteStore((s) => s.sidebar, isEqual); - const { pathname, activePath } = useSiteStore( - (s) => ({ - activePath: activePathSel(s), - pathname: s.location.pathname, - }), - shallow, - ); + const { pathname, activePath } = useSiteStore((s) => ({ + activePath: activePathSel(s), + pathname: s.location.pathname, + })); const items: BurgerProps['items'] = useMemo(() => { const sidebarItems = sidebar?.map((group) => { diff --git a/src/slots/Loading/index.tsx b/src/slots/Loading/index.tsx new file mode 100644 index 0000000..b4aa13f --- /dev/null +++ b/src/slots/Loading/index.tsx @@ -0,0 +1,18 @@ +import { Icon } from '@lobehub/ui'; +import { Typography } from 'antd'; +import { LoaderCircle } from 'lucide-react'; +import { memo } from 'react'; +import { Center, Flexbox } from 'react-layout-kit'; + +export default memo(() => { + return ( +
+ +
+ +
+ Loading... +
+
+ ); +}); diff --git a/src/slots/Navbar/index.tsx b/src/slots/Navbar/index.tsx index c227901..2a54dbe 100644 --- a/src/slots/Navbar/index.tsx +++ b/src/slots/Navbar/index.tsx @@ -3,7 +3,6 @@ import { createStyles } from 'antd-style'; import { Link, history } from 'dumi'; import NavbarExtra from 'dumi/theme-default/slots/NavbarExtra'; import { memo } from 'react'; -import { shallow } from 'zustand/shallow'; import { activePathSel, useSiteStore } from '@/store'; @@ -25,9 +24,9 @@ const useStyles = createStyles(({ css, stylish, token, responsive, prefixCls }) const Navbar = memo(() => { const { styles } = useStyles(); - const nav = useSiteStore((s) => s.navData, shallow); + const regLink = /^(\w+:)\/\/|^(mailto|tel):/; + const nav = useSiteStore((s) => s.navData); const activePath = useSiteStore(activePathSel); - return ( <> { className={styles.tabs} items={nav.map((item) => ({ key: String(item.activePath! || item.link), - label: /^(\w+:)\/\/|^(mailto|tel):/.test(item.link || '') ? ( - + label: regLink.test(item.link || '') ? ( + {item.title} ) : ( - + {item.title} ), }))} onChange={(path) => { - const url = nav.find((index) => index.activePath === path || index.link === path)?.link; - - if (!url) return; - + const url = nav.find((i) => i.activePath === path || i.link === path)?.link; + if (!url || regLink.test(url)) return; history.push(url); }} /> diff --git a/src/slots/SearchBar/index.tsx b/src/slots/SearchBar/index.tsx index 8742e47..874be2e 100644 --- a/src/slots/SearchBar/index.tsx +++ b/src/slots/SearchBar/index.tsx @@ -22,6 +22,7 @@ const SearchBar = memo(() => { onChange={(e: any) => setKeywords(e.target.value)} onFocus={() => setFocusing(true)} spotlight + type={'block'} /> {keywords.trim() && focusing && (result.length > 0 || !loading) && ( diff --git a/src/store/index.ts b/src/store/index.ts index bb8ecb7..53f0049 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -1,11 +1,22 @@ -import { apiHeaderSel, flattenSidebarSel, tokenSel } from './selectors'; +import { apiHeaderSel, flattenSidebarSel, logoSel, tokenSel } from './selectors'; +export { initialState } from './initialState'; export * from './selectors'; export * from './useSiteStore'; export * from './useThemeStore'; +/** + * @title 数据选择器 + */ export const siteSelectors = { + /** + * @title API 头部选择器 + */ apiHeader: apiHeaderSel, + /** + * @title 扁平化侧边栏选择器 + */ flattenSidebar: flattenSidebarSel, + logo: logoSel, token: tokenSel, }; diff --git a/src/store/selectors/hero.ts b/src/store/selectors/hero.ts index ee882f4..46f421b 100644 --- a/src/store/selectors/hero.ts +++ b/src/store/selectors/hero.ts @@ -2,9 +2,14 @@ import { type FeatureItem } from '@lobehub/ui'; import { SiteStore } from '../useSiteStore'; -export const isHeroPageSel = (s: SiteStore) => Boolean(s.routeMeta.frontmatter.hero); +export const isHeroPageSel = (s: SiteStore) => s.location.pathname === '/'; -const localeValueSel = (s: SiteStore, value: any) => { +// 是否展示首页的自定义内容 +export const showHeroPageCustomContent = (s: SiteStore) => + !!s.routeMeta.frontmatter.hero?.showCustomContent || + !!s.siteData.themeConfig.hero?.showCustomContent; + +export const localeValueSel = (s: SiteStore, value: any) => { if (!value) return; if (value[s.locale.id]) return value[s.locale.id]; @@ -12,28 +17,53 @@ 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 3493857..7280f59 100644 --- a/src/store/selectors/siteBasicInfo.ts +++ b/src/store/selectors/siteBasicInfo.ts @@ -7,3 +7,15 @@ export const githubSel = (s: SiteStore) => s.siteData.themeConfig.socialLinks?.g export const discordSel = (s: SiteStore) => s.siteData.themeConfig.socialLinks?.discord || ''; export const giscusSel = (s: SiteStore) => s.siteData.themeConfig.giscus; + +export const logoSel = (s: SiteStore): string => { + const logo = s.siteData.themeConfig.logo; + + if (!logo) return logo || ''; + + // 如果是 url 地址,则什么也不处理 + if (logo.startsWith('http')) return logo; + + // TODO: 如果是相对路径,则拼接上 base + return logo; +}; diff --git a/src/store/useSiteStore.ts b/src/store/useSiteStore.ts index 027fda9..0bbc95a 100644 --- a/src/store/useSiteStore.ts +++ b/src/store/useSiteStore.ts @@ -1,10 +1,44 @@ -import { create } from 'zustand'; +import type { ISiteContext } from 'dumi/dist/client/theme-api/context'; +import { + ILocale, + INavItem, + IRouteMeta, + ISidebarGroup, + IThemeConfig, +} from 'dumi/dist/client/theme-api/types'; +import type { Location } from 'history'; +import { StoreApi } from 'zustand'; +import { createContext } from 'zustand-utils'; import { devtools } from 'zustand/middleware'; +import { shallow } from 'zustand/shallow'; +import { createWithEqualityFn } from 'zustand/traditional'; -import { SiteStore, initialState } from './initialState'; +export type NavData = (INavItem & { children?: INavItem[] | undefined })[]; -export const useSiteStore = create()( - devtools(() => initialState, { name: 'dumi-theme-lobehub' }), -); +export type ISiteData = ISiteContext & { + themeConfig: IThemeConfig & { + socialLinks?: { + discord?: string; + }; + }; +}; -export * from './initialState'; +export interface SiteStore { + locale: ILocale; + location: Location; + navData: NavData; + routeMeta: IRouteMeta; + sidebar?: ISidebarGroup[]; + siteData: ISiteData; + tabMeta?: NonNullable[0]['meta']; +} + +export const createStore = (initState: SiteStore) => + createWithEqualityFn()( + devtools(() => initState, { name: 'lobe-docs' }), + shallow, + ); + +const { useStore, useStoreApi, Provider } = createContext>(); + +export { Provider, useStore as useSiteStore, useStoreApi }; diff --git a/src/store/useThemeStore.ts b/src/store/useThemeStore.ts index 8e58ad1..135fa55 100644 --- a/src/store/useThemeStore.ts +++ b/src/store/useThemeStore.ts @@ -1,15 +1,17 @@ import type { ThemeMode } from 'antd-style'; -import { create } from 'zustand'; import { persist } from 'zustand/middleware'; +import { shallow } from 'zustand/shallow'; +import { createWithEqualityFn } from 'zustand/traditional'; -export interface ThemeStore { +interface Store { themeMode: ThemeMode; } -export const useThemeStore = create()( +export const useThemeStore = createWithEqualityFn()( persist( () => ({ themeMode: 'auto' as ThemeMode, }), - { name: 'ANTD_STYLE_DOC_STORE' }, + { name: 'LOBE_DOC_STORE' }, ), + shallow, );