diff --git a/components/Tabs.js b/components/Tabs.js index 66012477..6f7497ba 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
  • { - const { children, meta, showInfoCard = true } = props + const { children, meta, showInfoCard = true, slotRight, slotTop } = props + const { locale } = useGlobal() return ( -
    - -
    - -
    - {showInfoCard && } -
    -
    +
    + +
    + {/* 桌面端左侧菜单 */} + {/* */} + +
    + {/* 移动端顶部菜单 */} + +
    + {slotTop} {children} +
    -
    - { CONFIG_MEDIUM.WIDGET_REVOLVER_MAPS === 'true' && } + + {/* 桌面端右侧 */} +
    + + {slotRight} +
    + + {showInfoCard && } + {CONFIG_MEDIUM.WIDGET_REVOLVER_MAPS === 'true' && } +
    +
    -
    + + {/* 移动端底部 */} +
    +
    ) } diff --git a/themes/Medium/LayoutCategory.js b/themes/Medium/LayoutCategory.js index 3a4bb0e9..34d76d23 100644 --- a/themes/Medium/LayoutCategory.js +++ b/themes/Medium/LayoutCategory.js @@ -1,8 +1,13 @@ import LayoutBase from './LayoutBase' +import BlogPostListScroll from './components/BlogPostListScroll' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { faTh } from '@fortawesome/free-solid-svg-icons' export const LayoutCategory = (props) => { const { category } = props - return - Category - {category} + const slotTop =
    分类:
    {category}
    + + return + } 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..4fcbfee6 100644 --- a/themes/Medium/LayoutSearch.js +++ b/themes/Medium/LayoutSearch.js @@ -1,31 +1,25 @@ -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' +import BlogPostListScroll from './components/BlogPostListScroll' 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 cb6ef8c6..8c5a441d 100644 --- a/themes/Medium/LayoutSlug.js +++ b/themes/Medium/LayoutSlug.js @@ -6,7 +6,13 @@ import 'prismjs/components/prism-javascript' import 'prismjs/components/prism-markup' import 'prismjs/components/prism-python' import 'prismjs/components/prism-typescript' -import { Code, Collection, CollectionRow, Equation, NotionRenderer } from 'react-notion-x' +import { + Code, + Collection, + CollectionRow, + Equation, + NotionRenderer +} from 'react-notion-x' import LayoutBase from './LayoutBase' import Comment from '@/components/Comment' import Image from 'next/image' @@ -14,14 +20,20 @@ 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' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { faEye } from '@fortawesome/free-solid-svg-icons' +import CategoryItem from './components/CategoryItem' +import TagItemMini from './components/TagItemMini' +import CONFIG_MEDIUM from './config_medium' const mapPageUrl = id => { return 'https://www.notion.so/' + id.replace(/-/g, '') } -export const LayoutSlug = (props) => { +export const LayoutSlug = props => { const { post, prev, next } = props const meta = { title: `${post.title} | ${BLOG.TITLE}`, @@ -35,13 +47,18 @@ export const LayoutSlug = (props) => { post.toc = getPageTableOfContents(post, post.blockMap) } const { locale } = useGlobal() - const date = formatDate(post?.date?.start_date || post.createdTime, locale.LOCALE) + const date = formatDate( + post?.date?.start_date || post.createdTime, + locale.LOCALE + ) - const zoom = typeof window !== 'undefined' && mediumZoom({ - container: '.notion-viewport', - background: 'rgba(0, 0, 0, 0.2)', - margin: getMediumZoomMargin() - }) + const zoom = + typeof window !== 'undefined' && + mediumZoom({ + container: '.notion-viewport', + background: 'rgba(0, 0, 0, 0.2)', + margin: getMediumZoomMargin() + }) const zoomRef = useRef(zoom ? zoom.clone() : null) useEffect(() => { @@ -50,57 +67,88 @@ export const LayoutSlug = (props) => { const imgList = container?.getElementsByTagName('img') if (imgList && zoomRef.current) { for (let i = 0; i < imgList.length; i++) { - (zoomRef.current).attach(imgList[i]) + zoomRef.current.attach(imgList[i]) } } }) - return -

    {post?.title}

    - -
    - {BLOG.AUTHOR} -
    {BLOG.AUTHOR}
    -
    {date}
    -
    - - {/* Notion文章主体 */} -
    - {post.blockMap && ( - - )} -
    -
    - {/* 文章内嵌广告 */} - -
    -
    - - + const slotRight = post?.toc && post?.toc?.length > 3 && ( +
    +
    - + ) + + return ( + +

    {post?.title}

    +
    + + <> + {BLOG.AUTHOR} +
    + {BLOG.AUTHOR} +
    + + +
    {date}
    +
    + +   + +
    +
    + {/* Notion文章主体 */} +
    + {post.blockMap && ( + + )} +
    + +
    + {/* 文章内嵌广告 */} + +
    +
    +
    + { CONFIG_MEDIUM.POST_DETAIL_CATEGORY && } +
    + { CONFIG_MEDIUM.POST_DETAIL_TAG && post?.tagItems?.map(tag => )} +
    +
    + + +
    +
    + ) } function getMediumZoomMargin () { diff --git a/themes/Medium/LayoutTag.js b/themes/Medium/LayoutTag.js index 5cfacb20..042daf1c 100644 --- a/themes/Medium/LayoutTag.js +++ b/themes/Medium/LayoutTag.js @@ -1,8 +1,13 @@ import LayoutBase from './LayoutBase' +import BlogPostListScroll from './components/BlogPostListScroll' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { faTag } from '@fortawesome/free-solid-svg-icons' export const LayoutTag = (props) => { const { tag } = props - return - Tag - {tag} + const slotTop =
    标签:
    {tag}
    + + return + } diff --git a/themes/Medium/components/BlogPostCard.js b/themes/Medium/components/BlogPostCard.js index 76ab1933..f2c93a7c 100644 --- a/themes/Medium/components/BlogPostCard.js +++ b/themes/Medium/components/BlogPostCard.js @@ -4,8 +4,10 @@ import { faAngleRight } from '@fortawesome/free-solid-svg-icons' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import Link from 'next/link' import React from 'react' -import { Code, Collection, CollectionRow, Equation, NotionRenderer } from 'react-notion-x' +import { Code, Collection, Equation, NotionRenderer } from 'react-notion-x' import CONFIG_MEDIUM from '../config_medium' +import CategoryItem from './CategoryItem' +import TagItemMini from './TagItemMini' const BlogPostCard = ({ post, showSummary }) => { const showPreview = CONFIG_MEDIUM.POST_LIST_PREVIEW && post.blockMap @@ -20,8 +22,14 @@ const BlogPostCard = ({ post, showSummary }) => { -
    - {post.date.start_date} +
    +
    {post.date.start_date}
    + { CONFIG_MEDIUM.POST_LIST_CATEGORY && } + { CONFIG_MEDIUM.POST_LIST_TAG && post?.tagItems?.map(tag => )} +
    + +
    +
    {(!showPreview || showSummary) &&

    @@ -36,7 +44,6 @@ const BlogPostCard = ({ post, showSummary }) => { components={{ equation: Equation, code: Code, - collectionRow: CollectionRow, collection: Collection }} /> diff --git a/themes/Medium/components/BlogPostListPage.js b/themes/Medium/components/BlogPostListPage.js index fe91b9b0..bac8ab88 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' /** * 文章列表分页表格 diff --git a/themes/Medium/components/BlogPostListScroll.js b/themes/Medium/components/BlogPostListScroll.js new file mode 100644 index 00000000..1b2334cc --- /dev/null +++ b/themes/Medium/components/BlogPostListScroll.js @@ -0,0 +1,106 @@ +import BLOG from '@/blog.config' +import BlogPostCard from './BlogPostCard' +import BlogPostListEmpty from './BlogPostListEmpty' +import { useGlobal } from '@/lib/global' +import throttle from 'lodash.throttle' +import React, { useCallback, useEffect, useRef, useState } from 'react' +import { useRouter } from 'next/router' + +/** + * 博客列表滚动分页 + * @param posts 所有文章 + * @param tags 所有标签 + * @returns {JSX.Element} + * @constructor + */ +const BlogPostListScroll = ({ posts = [], currentSearch }) => { + const postsPerPage = BLOG.POSTS_PER_PAGE + const [page, updatePage] = useState(1) + 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 postsToShow = getPostByPage(page, filteredPosts, postsPerPage) + + let hasMore = false + if (filteredPosts) { + const totalCount = filteredPosts.length + hasMore = page * postsPerPage < totalCount + } + + const handleGetMore = () => { + if (!hasMore) return + updatePage(page + 1) + } + + // 监听滚动自动分页加载 + const scrollTrigger = useCallback(throttle(() => { + const scrollS = window.scrollY + window.outerHeight + const clientHeight = targetRef ? (targetRef.current ? (targetRef.current.clientHeight) : 0) : 0 + if (scrollS > clientHeight + 100) { + handleGetMore() + } + }, 500)) + + // 监听滚动 + useEffect(() => { + window.addEventListener('scroll', scrollTrigger) + return () => { + window.removeEventListener('scroll', scrollTrigger) + } + }) + + const targetRef = useRef(null) + const { locale } = useGlobal() + + if (!postsToShow || postsToShow.length === 0) { + return + } else { + return

    + + {/* 文章列表 */} +
    + {postsToShow.map(post => ( + + ))} +
    + +
    +
    { + handleGetMore() + }} + className='w-full my-4 py-4 text-center cursor-pointer glassmorphism shadow-xl rounded-xl dark:text-gray-200' + > {hasMore ? locale.COMMON.MORE : `${locale.COMMON.NO_MORE} 😰`}
    +
    +
    + } +} + +/** + * 获取从第1页到指定页码的文章 + * @param page 第几页 + * @param totalPosts 所有文章 + * @param postsPerPage 每页文章数量 + * @returns {*} + */ +const getPostByPage = function (page, totalPosts, postsPerPage) { + return totalPosts.slice( + 0, + postsPerPage * page + ) +} + +function getSearchKey () { + const router = useRouter() + if (router.query && router.query.s) { + return router.query.s + } + return null +} + +export default BlogPostListScroll diff --git a/themes/Medium/components/BottomMenuBar.js b/themes/Medium/components/BottomMenuBar.js new file mode 100644 index 00000000..f81af654 --- /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 new file mode 100644 index 00000000..b1dc71ba --- /dev/null +++ b/themes/Medium/components/Catalog.js @@ -0,0 +1,85 @@ +import React from 'react' +import throttle from 'lodash.throttle' +import { uuidToId } from 'notion-utils' +import Progress from './Progress' +import JumpToTopButton from './JumpToTopButton' +/** + * 目录导航组件 + * @param toc + * @returns {JSX.Element} + * @constructor + */ +const Catalog = ({ toc }) => { + // 无目录就直接返回空 + if (!toc || toc.length < 1) { + return <> + } + // 监听滚动事件 + React.useEffect(() => { + window.addEventListener('scroll', actionSectionScrollSpy) + actionSectionScrollSpy() + return () => { + window.removeEventListener('scroll', actionSectionScrollSpy) + } + }, []) + + // 同步选中目录事件 + const [activeSection, setActiveSection] = React.useState(null) + const throttleMs = 100 + const actionSectionScrollSpy = React.useCallback(throttle(() => { + const sections = document.getElementsByClassName('notion-h') + let prevBBox = null + let currentSectionId = activeSection + for (let i = 0; i < sections.length; ++i) { + const section = sections[i] + if (!section || !(section instanceof Element)) continue + if (!currentSectionId) { + currentSectionId = section.getAttribute('data-id') + } + const bbox = section.getBoundingClientRect() + const prevHeight = prevBBox ? bbox.top - prevBBox.bottom : 0 + const offset = Math.max(150, prevHeight / 4) + // GetBoundingClientRect returns values relative to viewport + if (bbox.top - offset < 0) { + currentSectionId = section.getAttribute('data-id') + prevBBox = bbox + continue + } + // No need to continue loop, if last element has been detected + break + } + setActiveSection(currentSectionId) + }, throttleMs)) + + return
    +
    + +
    + + +
    +} + +export default Catalog diff --git a/themes/Medium/components/CategoryGroup.js b/themes/Medium/components/CategoryGroup.js new file mode 100644 index 00000000..276577c1 --- /dev/null +++ b/themes/Medium/components/CategoryGroup.js @@ -0,0 +1,22 @@ +import { faTh } from '@fortawesome/free-solid-svg-icons' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import React from 'react' +import CategoryItem from './CategoryItem' + +const CategoryGroup = ({ currentCategory, categories }) => { + if (!categories) { + return <> + } + return
    +
    分类
    +
    + {Object.keys(categories).map(category => { + const selected = currentCategory === category + const categoryCount = +categories[category] + return + })} +
    +
    +} + +export default CategoryGroup diff --git a/themes/Medium/components/CategoryItem.js b/themes/Medium/components/CategoryItem.js new file mode 100644 index 00000000..5af5cee7 --- /dev/null +++ b/themes/Medium/components/CategoryItem.js @@ -0,0 +1,16 @@ +import Link from 'next/link' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { faFolderOpen, faFolder } from '@fortawesome/free-solid-svg-icons' + +export default function CategoryItem ({ selected, category, categoryCount }) { + return + +
    {category} {categoryCount && (categoryCount)} +
    +
    + +} 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..6eab4596 --- /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, className }) => { + 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..c4fb5b1d 100644 --- a/themes/Medium/components/LogoBar.js +++ b/themes/Medium/components/LogoBar.js @@ -1,25 +1,14 @@ 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() - return
    -
    - - {BLOG.TITLE} - - - {locale.NAV.ABOUT} - - {BLOG.CONTACT_EMAIL && -
    - -
    - } -
    +
    +
    + + {BLOG.TITLE} + +
    +
    } diff --git a/themes/Medium/components/Progress.js b/themes/Medium/components/Progress.js new file mode 100644 index 00000000..4c4adb9f --- /dev/null +++ b/themes/Medium/components/Progress.js @@ -0,0 +1,43 @@ +import React, { useEffect, useState } from 'react' + +/** + * 顶部页面阅读进度条 + * @returns {JSX.Element} + * @constructor + */ +const Progress = ({ targetRef, showPercent = true }) => { + const currentRef = targetRef?.current || targetRef + const [percent, changePercent] = useState(0) + const scrollListener = () => { + const target = currentRef || document.getElementById('container') + if (target) { + const clientHeight = target.clientHeight + const scrollY = window.pageYOffset + const fullHeight = clientHeight - window.outerHeight + let per = parseFloat(((scrollY / fullHeight) * 100).toFixed(0)) + if (per > 100) per = 100 + if (per < 0) per = 0 + changePercent(per) + } + } + + useEffect(() => { + document.addEventListener('scroll', scrollListener) + return () => document.removeEventListener('scroll', scrollListener) + }, [percent]) + + return ( +
    +
    + {showPercent && ( +
    {percent}%
    + )} +
    +
    + ) +} + +export default Progress 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..54b31158 100644 --- a/themes/Medium/components/SocialButton.js +++ b/themes/Medium/components/SocialButton.js @@ -10,8 +10,7 @@ import React from 'react' * @constructor */ const SocialButton = () => { - return
    -
    + return
    {BLOG.CONTACT_GITHUB && } @@ -31,6 +30,5 @@ const SocialButton = () => {
    -
    } export default SocialButton 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..23d6ac52 100644 --- a/themes/Medium/config_medium.js +++ b/themes/Medium/config_medium.js @@ -1,7 +1,12 @@ const CONFIG_MEDIUM = { POST_LIST_COVER: true, // 文章列表显示图片封面 - POST_LIST_PREVIEW: true, // 显示文章预览 + POST_LIST_PREVIEW: true, // 列表显示文章预览 + POST_LIST_CATEGORY: true, // 列表显示文章分类 + POST_LIST_TAG: true, // 列表显示文章标签 + + POST_DETAIL_CATEGORY: true, // 文章显示分类 + POST_DETAIL_TAG: true, // 文章显示标签 // 菜单 MENU_ABOUT: true, // 显示关于 @@ -11,6 +16,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