diff --git a/components/Tabs.js b/components/Tabs.js index 66012477..39308d29 100644 --- a/components/Tabs.js +++ b/components/Tabs.js @@ -5,7 +5,7 @@ import React, { useState } from 'react' * @param {*} param0 * @returns */ -const Tabs = ({ children }) => { +const Tabs = ({ className, children }) => { if (!children) { return <> } @@ -18,7 +18,7 @@ const Tabs = ({ children }) => { }) if (count === 1) { - return
+ return
{children}
} @@ -29,7 +29,7 @@ const Tabs = ({ children }) => { setCurrentTab(i) } - return
+ return
    {children.map((item, index) => { return
  • { onClick={() => { tabClickHandle(index) }}> - {item?.key} + {item?.key || 'undefined_key'}
  • })}
diff --git a/themes/Medium/LayoutBase.js b/themes/Medium/LayoutBase.js index 5772a5e4..3cd0f7bb 100644 --- a/themes/Medium/LayoutBase.js +++ b/themes/Medium/LayoutBase.js @@ -2,9 +2,14 @@ import CommonHead from '@/components/CommonHead' import React from 'react' import Footer from './components/Footer' import InfoCard from './components/InfoCard' -import LogoBar from './components/LogoBar' import RevolverMaps from './components/RevolverMaps' import CONFIG_MEDIUM from './config_medium' +import Tabs from '@/components/Tabs' +import TopNavBar from './components/TopNavBar' +import SearchInput from './components/SearchInput' +import BottomMenuBar from './components/BottomMenuBar' +import { useGlobal } from '@/lib/global' +import JumpToTopButton from './components/JumpToTopButton' /** * 基础布局 采用左右两侧布局,移动端使用顶部导航栏 @@ -14,24 +19,40 @@ import CONFIG_MEDIUM from './config_medium' */ const LayoutBase = props => { const { children, meta, showInfoCard = true, slotRight } = props + const { locale } = useGlobal() return ( -
- -
- -
- {showInfoCard && } -
-
+
+ +
+ + {/* 桌面端左侧菜单 */} + {/* */} + +
+ {/* 移动端顶部菜单 */} + +
{children} +
-
- { CONFIG_MEDIUM.WIDGET_REVOLVER_MAPS === 'true' && } - { slotRight } + + {/* 桌面端右侧 */} +
+ + {slotRight} +
+ + {showInfoCard && } + {CONFIG_MEDIUM.WIDGET_REVOLVER_MAPS === 'true' && } +
+
-
+ + {/* 移动端底部 */} + +
) } diff --git a/themes/Medium/LayoutIndex.js b/themes/Medium/LayoutIndex.js index 3d336bd7..884b825a 100644 --- a/themes/Medium/LayoutIndex.js +++ b/themes/Medium/LayoutIndex.js @@ -2,7 +2,6 @@ import BlogPostListPage from './components/BlogPostListPage' import LayoutBase from './LayoutBase' export const LayoutIndex = (props) => { - // const { posts, tags, meta, categories, postCount, latestPosts } = props return diff --git a/themes/Medium/LayoutPage.js b/themes/Medium/LayoutPage.js index 0c6640c8..cec493ce 100644 --- a/themes/Medium/LayoutPage.js +++ b/themes/Medium/LayoutPage.js @@ -2,8 +2,7 @@ import LayoutBase from './LayoutBase' import BlogPostListPage from './components/BlogPostListPage' export const LayoutPage = (props) => { - const { page, posts, postCount } = props return - + } diff --git a/themes/Medium/LayoutSearch.js b/themes/Medium/LayoutSearch.js index c7469a60..cd66e5e8 100644 --- a/themes/Medium/LayoutSearch.js +++ b/themes/Medium/LayoutSearch.js @@ -1,31 +1,24 @@ -import { useRouter } from 'next/router' import LayoutBase from './LayoutBase' +import BlogPostListPage from './components/BlogPostListPage' +import SearchInput from './components/SearchInput' +import { useGlobal } from '@/lib/global' +import TagGroups from './components/TagGroups' +import CategoryGroup from './components/CategoryGroup' export const LayoutSearch = (props) => { - const { posts } = props - let filteredPosts - const searchKey = getSearchKey() - if (searchKey) { - filteredPosts = posts.filter(post => { - const tagContent = post.tags ? post.tags.join(' ') : '' - const searchContent = post.title + post.summary + tagContent - return searchContent.toLowerCase().includes(searchKey.toLowerCase()) - }) - } else { - filteredPosts = posts - } - - console.log(filteredPosts) - + const { locale } = useGlobal() return - Search {searchKey} + +
+
+ {locale.NAV.SEARCH} +
+ + + + +
+ +
} - -function getSearchKey () { - const router = useRouter() - if (router.query && router.query.s) { - return router.query.s - } - return null -} diff --git a/themes/Medium/LayoutSlug.js b/themes/Medium/LayoutSlug.js index 905d8ed8..015e6d29 100644 --- a/themes/Medium/LayoutSlug.js +++ b/themes/Medium/LayoutSlug.js @@ -14,7 +14,7 @@ import { useGlobal } from '@/lib/global' import formatDate from '@/lib/formatDate' import Link from 'next/link' import mediumZoom from 'medium-zoom' -import { useEffect, useRef } from 'react' +import React, { useEffect, useRef } from 'react' import ArticleAround from './components/ArticleAround' import Catalog from './components/Catalog' @@ -56,22 +56,26 @@ export const LayoutSlug = (props) => { } }) - return }> -

{post?.title}

- -
- {BLOG.AUTHOR} -
{BLOG.AUTHOR}
-
{date}
-
- + const slotRight = post?.toc &&
+ + return +

{post?.title}

+
+ + <> + {BLOG.AUTHOR} +
{BLOG.AUTHOR}
+ + +
{date}
+
{/* Notion文章主体 */}
{post.blockMap && ( diff --git a/themes/Medium/components/BlogPostListPage.js b/themes/Medium/components/BlogPostListPage.js index fe91b9b0..0c6b56b8 100644 --- a/themes/Medium/components/BlogPostListPage.js +++ b/themes/Medium/components/BlogPostListPage.js @@ -2,6 +2,7 @@ import BlogPostCard from './BlogPostCard' import BLOG from '@/blog.config' import BlogPostListEmpty from './BlogPostListEmpty' import PaginationSimple from './PaginationSimple' +import { useRouter } from 'next/router' /** * 文章列表分页表格 @@ -12,7 +13,17 @@ import PaginationSimple from './PaginationSimple' * @constructor */ const BlogPostListPage = ({ page = 1, posts = [], postCount }) => { - const totalPage = Math.ceil(postCount / BLOG.POSTS_PER_PAGE) + let filteredPosts = Object.assign(posts) + const searchKey = getSearchKey() + if (searchKey) { + filteredPosts = posts.filter(post => { + const tagContent = post.tags ? post.tags.join(' ') : '' + const searchContent = post.title + post.summary + tagContent + return searchContent.toLowerCase().includes(searchKey.toLowerCase()) + }) + } + const filteredPostsCount = filteredPosts.length + const totalPage = Math.ceil(filteredPostsCount / BLOG.POSTS_PER_PAGE) if (!posts || posts.length === 0) { return @@ -20,7 +31,7 @@ const BlogPostListPage = ({ page = 1, posts = [], postCount }) => { return (
{/* 文章列表 */} - {posts.map(post => ( + {filteredPosts.map(post => ( ))} @@ -29,4 +40,12 @@ const BlogPostListPage = ({ page = 1, posts = [], postCount }) => { } } +function getSearchKey () { + const router = useRouter() + if (router.query && router.query.s) { + return router.query.s + } + return null +} + export default BlogPostListPage diff --git a/themes/Medium/components/BottomMenuBar.js b/themes/Medium/components/BottomMenuBar.js new file mode 100644 index 00000000..6e8e0c71 --- /dev/null +++ b/themes/Medium/components/BottomMenuBar.js @@ -0,0 +1,27 @@ +import { faHome, faSearch } from '@fortawesome/free-solid-svg-icons' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import Link from 'next/link' +import React from 'react' +import JumpToTopButton from '@/themes/Medium/components/JumpToTopButton' + +export default function BottomMenuBar ({ className }) { + return ( +
+
+ +
+ +
+ + +
+ +
+ +
+ +
+
+
+ ) +} diff --git a/themes/Medium/components/Catalog.js b/themes/Medium/components/Catalog.js index d82bee28..9ae655a1 100644 --- a/themes/Medium/components/Catalog.js +++ b/themes/Medium/components/Catalog.js @@ -2,8 +2,6 @@ import React from 'react' import throttle from 'lodash.throttle' import { uuidToId } from 'notion-utils' import Progress from './Progress' -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' -import { faStream } from '@fortawesome/free-solid-svg-icons' // import { cs } from 'react-notion-x' /** @@ -55,7 +53,6 @@ const Catalog = ({ toc }) => { }, throttleMs)) return
-
目录
diff --git a/themes/Medium/components/CategoryGroup.js b/themes/Medium/components/CategoryGroup.js new file mode 100644 index 00000000..6a1d040b --- /dev/null +++ b/themes/Medium/components/CategoryGroup.js @@ -0,0 +1,30 @@ +import { faFolder, faFolderOpen, faTag, faTh } from '@fortawesome/free-solid-svg-icons' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import Link from 'next/link' +import React from 'react' + +const CategoryGroup = ({ currentCategory, categories }) => { + if (!categories) { + return <> + } + return
+
分类
+
+ {Object.keys(categories).map(category => { + const selected = currentCategory === category + return + +
{category}({categories[category]}) +
+
+ + })} +
+
+} + +export default CategoryGroup diff --git a/themes/Medium/components/InfoCard.js b/themes/Medium/components/InfoCard.js index 47d300f6..65e1e3ba 100644 --- a/themes/Medium/components/InfoCard.js +++ b/themes/Medium/components/InfoCard.js @@ -5,9 +5,9 @@ import React from 'react' import SocialButton from './SocialButton' const InfoCard = () => { - return <> -
-
{ Router.push('/about') }}> + return
+
+
{ Router.push('/about') }}> {BLOG.AUTHOR} { className='rounded-full' />
-
{BLOG.AUTHOR}
-
{BLOG.BIO}
+
{BLOG.AUTHOR}
+
{BLOG.BIO}
- +
} export default InfoCard diff --git a/themes/Medium/components/JumpToTopButton.js b/themes/Medium/components/JumpToTopButton.js new file mode 100644 index 00000000..7126309c --- /dev/null +++ b/themes/Medium/components/JumpToTopButton.js @@ -0,0 +1,28 @@ +import { useGlobal } from '@/lib/global' +import { faArrowUp } from '@fortawesome/free-solid-svg-icons' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import React from 'react' +import CONFIG_MEDIUM from '../config_medium' + +/** + * 跳转到网页顶部 + * 当屏幕下滑500像素后会出现该控件 + * @param targetRef 关联高度的目标html标签 + * @param showPercent 是否显示百分比 + * @returns {JSX.Element} + * @constructor + */ +const JumpToTopButton = ({ showPercent = false, percent }) => { + if (!CONFIG_MEDIUM.WIDGET_TO_TOP) { + return <> + } + const { locale } = useGlobal() + return (
window.scrollTo({ top: 0, behavior: 'smooth' })} > +
+ +
+ {showPercent && (
{percent}%
)} +
) +} + +export default JumpToTopButton diff --git a/themes/Medium/components/LeftMenuBar.js b/themes/Medium/components/LeftMenuBar.js new file mode 100644 index 00000000..680855c2 --- /dev/null +++ b/themes/Medium/components/LeftMenuBar.js @@ -0,0 +1,16 @@ +import Link from 'next/link' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { faHome } from '@fortawesome/free-solid-svg-icons' +import React from 'react' + +export default function LeftMenuBar () { + return
+
+ +
+ +
+ +
+
+} diff --git a/themes/Medium/components/LogoBar.js b/themes/Medium/components/LogoBar.js index 75a23aec..f8836744 100644 --- a/themes/Medium/components/LogoBar.js +++ b/themes/Medium/components/LogoBar.js @@ -1,25 +1,26 @@ import BLOG from '@/blog.config' -import { useGlobal } from '@/lib/global' import { faEnvelope } from '@fortawesome/free-solid-svg-icons' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import Link from 'next/link' export default function LogoBar () { - const { locale } = useGlobal() + // const { locale } = useGlobal() return
-
- - {BLOG.TITLE} - - - {locale.NAV.ABOUT} - - {BLOG.CONTACT_EMAIL && -
- -
- } +
+
+ + {BLOG.TITLE} + + {/* */} + {/* {locale.NAV.ABOUT} */} + {/* */} +
+ {BLOG.CONTACT_EMAIL && +
+
+ }
+
} diff --git a/themes/Medium/components/RevolverMaps.js b/themes/Medium/components/RevolverMaps.js index 57d2cccc..d839d85d 100644 --- a/themes/Medium/components/RevolverMaps.js +++ b/themes/Medium/components/RevolverMaps.js @@ -8,7 +8,7 @@ export default function RevolverMaps () { changeLoad(true) } }) - return
+ return
} function initRevolverMaps () { diff --git a/themes/Medium/components/SearchInput.js b/themes/Medium/components/SearchInput.js new file mode 100644 index 00000000..53dc6637 --- /dev/null +++ b/themes/Medium/components/SearchInput.js @@ -0,0 +1,77 @@ +import { useRouter } from 'next/router' +import { useImperativeHandle, useRef, useState } from 'react' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { faSearch, faSpinner, faTimes } from '@fortawesome/free-solid-svg-icons' + +const SearchInput = ({ currentTag, currentSearch, cRef, className }) => { + const [searchKey, setSearchKey] = useState(currentSearch || getSearchKey() || '') + const [onLoading, setLoadingState] = useState(false) + const router = useRouter() + const searchInputRef = useRef() + useImperativeHandle(cRef, () => { + return { + focus: () => { + searchInputRef?.current?.focus() + } + } + }) + + const handleSearch = (key) => { + if (key && key !== '') { + setLoadingState(true) + router.push({ pathname: '/search', query: { s: key } }).then(r => { + setLoadingState(false) + }) + } else { + router.push({ pathname: '/' }).then(r => { + }) + } + } + const handleKeyUp = (e) => { + if (e.keyCode === 13) { // 回车 + handleSearch(searchInputRef.current.value) + } else if (e.keyCode === 27) { // ESC + cleanSearch() + } + } + const cleanSearch = () => { + searchInputRef.current.value = '' + setSearchKey('') + } + + const updateSearchKey = (val) => { + setSearchKey(val) + } + + return
+ updateSearchKey(e.target.value)} + defaultValue={searchKey} + /> + +
{ handleSearch(searchKey) }}> + +
+ + {(searchKey && searchKey.length && +
+ +
+ )} +
+} + +function getSearchKey () { + const router = useRouter() + if (router.query && router.query.s) { + return router.query.s + } + return null +} + +export default SearchInput diff --git a/themes/Medium/components/SocialButton.js b/themes/Medium/components/SocialButton.js index c50f5984..71f43da2 100644 --- a/themes/Medium/components/SocialButton.js +++ b/themes/Medium/components/SocialButton.js @@ -10,7 +10,7 @@ import React from 'react' * @constructor */ const SocialButton = () => { - return
+ return
{BLOG.CONTACT_GITHUB && diff --git a/themes/Medium/components/TagGroups.js b/themes/Medium/components/TagGroups.js new file mode 100644 index 00000000..17da94b4 --- /dev/null +++ b/themes/Medium/components/TagGroups.js @@ -0,0 +1,29 @@ +import { faTag } from '@fortawesome/free-solid-svg-icons' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import TagItemMini from './TagItemMini' + +/** + * 标签组 + * @param tags + * @param currentTag + * @returns {JSX.Element} + * @constructor + */ +const TagGroups = ({ tags, currentTag }) => { + if (!tags) return <> + return ( +
+
标签
+
+ { + tags.map(tag => { + const selected = tag.name === currentTag + return + }) + } +
+
+ ) +} + +export default TagGroups diff --git a/themes/Medium/components/TagItemMini.js b/themes/Medium/components/TagItemMini.js index 9fec9e35..fb7e3013 100644 --- a/themes/Medium/components/TagItemMini.js +++ b/themes/Medium/components/TagItemMini.js @@ -5,7 +5,7 @@ import Link from 'next/link' const TagItemMini = ({ tag, selected = false }) => { return
diff --git a/themes/Medium/components/TopNavBar.js b/themes/Medium/components/TopNavBar.js new file mode 100644 index 00000000..b08dc88b --- /dev/null +++ b/themes/Medium/components/TopNavBar.js @@ -0,0 +1,9 @@ +import LogoBar from '@/themes/Medium/components/LogoBar' + +export default function TopNavBar ({ className }) { + return
+
+ +
+
+} diff --git a/themes/Medium/config_medium.js b/themes/Medium/config_medium.js index 4d13d756..e61b1982 100644 --- a/themes/Medium/config_medium.js +++ b/themes/Medium/config_medium.js @@ -11,6 +11,7 @@ const CONFIG_MEDIUM = { MENU_SEARCH: true, // 显示搜索 // Widget - WIDGET_REVOLVER_MAPS: process.env.NEXT_PUBLIC_WIDGET_REVOLVER_MAPS || 'false' // 地图插件 + WIDGET_REVOLVER_MAPS: process.env.NEXT_PUBLIC_WIDGET_REVOLVER_MAPS || 'false', // 地图插件 + WIDGET_TO_TOP: true // 跳回顶部 } export default CONFIG_MEDIUM