diff --git a/public/bg_image.jpg b/public/bg_image.jpg old mode 100755 new mode 100644 index cfbe96f5..d602ff36 Binary files a/public/bg_image.jpg and b/public/bg_image.jpg differ diff --git a/styles/globals.css b/styles/globals.css index 70eec009..cd9aab7b 100644 --- a/styles/globals.css +++ b/styles/globals.css @@ -188,4 +188,8 @@ nav { width: 100%; background-image: linear-gradient(-180deg,rgba(255,255,255,0) 0%,#fff 70%); padding-bottom: 34px; +} + +.shadow-text{ + text-shadow: 0.1em 0.1em 0.2em black; } \ No newline at end of file diff --git a/themes/Empty/LayoutSlug.js b/themes/Empty/LayoutSlug.js index cb5844df..4def0283 100644 --- a/themes/Empty/LayoutSlug.js +++ b/themes/Empty/LayoutSlug.js @@ -1,3 +1,4 @@ +import BLOG from '@/blog.config' import 'prismjs' import 'prismjs/components/prism-bash' import 'prismjs/components/prism-javascript' @@ -5,6 +6,7 @@ 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 LayoutBase from './LayoutBase' const mapPageUrl = id => { return 'https://www.notion.so/' + id.replace(/-/g, '') @@ -12,7 +14,14 @@ const mapPageUrl = id => { export const LayoutSlug = (props) => { const { post } = props - return
+ const meta = { + title: `${post.title} | ${BLOG.TITLE}`, + description: post.summary, + type: 'article', + tags: post.tags + } + + return

Slug - {post?.title}

{/* Notion文章主体 */} @@ -32,5 +41,5 @@ export const LayoutSlug = (props) => {

-
+ } diff --git a/themes/Hexo/LayoutBase.js b/themes/Hexo/LayoutBase.js index 87e54f0a..8975f09f 100644 --- a/themes/Hexo/LayoutBase.js +++ b/themes/Hexo/LayoutBase.js @@ -1,41 +1,34 @@ import CommonHead from '@/components/CommonHead' +import Footer from './components/Footer' +import SideRight from './components/SideRight' + /** * 基础布局 采用左右两侧布局,移动端使用顶部导航栏 - * @param children - * @param layout - * @param tags - * @param meta - * @param post - * @param currentSearch - * @param currentCategory - * @param currentTag - * @param categories + * @param props * @returns {JSX.Element} * @constructor */ -const LayoutBase = ({ - children, - headerSlot, - tags, - meta, - post, - postCount, - sideBarSlot, - floatSlot, - rightAreaSlot, - currentSearch, - currentCategory, - currentTag, - categories -}) => { - return (<> +const LayoutBase = (props) => { + const { children, headerSlot, meta } = props + + return (
+ {headerSlot} -
- {children} + +
+ +
+ {children} + +
+
- ) + +
) } export default LayoutBase diff --git a/themes/Hexo/LayoutIndex.js b/themes/Hexo/LayoutIndex.js index 83cf86e0..d7a7c3af 100644 --- a/themes/Hexo/LayoutIndex.js +++ b/themes/Hexo/LayoutIndex.js @@ -1,9 +1,10 @@ +import BlogPostListPage from './components/BlogPostListPage' import Header from './components/Header' import CONFIG_HEXO from './config_hexo' import LayoutBase from './LayoutBase' export const LayoutIndex = (props) => { return }> - + } diff --git a/themes/Hexo/LayoutSlug.js b/themes/Hexo/LayoutSlug.js index 9335af67..c4d5ce7e 100644 --- a/themes/Hexo/LayoutSlug.js +++ b/themes/Hexo/LayoutSlug.js @@ -1,21 +1,84 @@ +import BLOG from '@/blog.config' +import formatDate from '@/lib/formatDate' +import { useGlobal } from '@/lib/global' +import { faFolderOpen } from '@fortawesome/free-solid-svg-icons' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import Link from 'next/link' import 'prismjs' import 'prismjs/components/prism-bash' +import 'prismjs/components/prism-java' import 'prismjs/components/prism-javascript' import 'prismjs/components/prism-markup' import 'prismjs/components/prism-python' import 'prismjs/components/prism-typescript' +import CONFIG_NEXT from '../NEXT/config_next' +import ArticleDetail from './components/ArticleDetail' +import Card from './components/Card' +import LayoutBase from './LayoutBase' -export const LayoutSlug = ({ - post, - tags, - prev, - next, - recommendPosts, - categories, - postCount, - latestPosts -}) => { - return
- Slug -
+export const LayoutSlug = props => { + const { post } = props + const meta = { + title: `${post.title} | ${BLOG.TITLE}`, + description: post.summary, + type: 'article', + tags: post.tags + } + + const { locale } = useGlobal() + const date = formatDate( + post?.date?.start_date || post.createdTime, + locale.LOCALE + ) + + const headerSlot = ( +
+ +
+
+ {/* 文章Title */} +
+ {post.title} +
+ +
+
+ + + + {post.category} + + + | + + {post.type[0] !== 'Page' && (<> + + + {date} + + + )} + +
+ | + 次访问 +
+
+
+
+
+
+ ) + + return ( + + + + + + ) } diff --git a/themes/Hexo/components/ArticleDetail.js b/themes/Hexo/components/ArticleDetail.js new file mode 100644 index 00000000..a30585b9 --- /dev/null +++ b/themes/Hexo/components/ArticleDetail.js @@ -0,0 +1,98 @@ +import Comment from '@/components/Comment' +import mediumZoom from 'medium-zoom' +import 'prismjs' +import 'prismjs/components/prism-bash' +import 'prismjs/components/prism-javascript' +import 'prismjs/components/prism-markup' +import 'prismjs/components/prism-python' +import 'prismjs/components/prism-typescript' +import { useEffect, useRef } from 'react' +import { Code, Collection, CollectionRow, Equation, NotionRenderer } from 'react-notion-x' + +/** + * + * @param {*} param0 + * @returns + */ +export default function ArticleDetail ({ post, recommendPosts, prev, next }) { + 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(() => { + // 将所有container下的所有图片添加medium-zoom + const container = document?.getElementById('container') + const imgList = container?.getElementsByTagName('img') + if (imgList && zoomRef.current) { + for (let i = 0; i < imgList.length; i++) { + (zoomRef.current).attach(imgList[i]) + } + } + }) + + return (
+
+ + {/* Notion文章主体 */} +
+ {post.blockMap && ( + + )} +
+ +
+ {/* 文章内嵌广告 */} + +
+ +
+ + {/* 评论互动 */} +
+
发表评论
+ +
+
) +} + +const mapPageUrl = id => { + return 'https://www.notion.so/' + id.replace(/-/g, '') +} + +function getMediumZoomMargin () { + const width = window.innerWidth + + if (width < 500) { + return 8 + } else if (width < 800) { + return 20 + } else if (width < 1280) { + return 30 + } else if (width < 1600) { + return 40 + } else if (width < 1920) { + return 48 + } else { + return 72 + } +} diff --git a/themes/Hexo/components/BlogPostCard.js b/themes/Hexo/components/BlogPostCard.js new file mode 100644 index 00000000..2c98753a --- /dev/null +++ b/themes/Hexo/components/BlogPostCard.js @@ -0,0 +1,83 @@ +import BLOG from '@/blog.config' +import { faFolder } from '@fortawesome/free-solid-svg-icons' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import Image from 'next/image' +import Link from 'next/link' +import React from 'react' +import { Code, Collection, CollectionRow, Equation, NotionRenderer } from 'react-notion-x' +import TagItemMini from './TagItemMini' +import CONFIG_HEXO from '../config_hexo' + +const BlogPostCard = ({ post, showSummary }) => { + const showPreview = CONFIG_HEXO.POST_LIST_PREVIEW && post.blockMap + return ( +
+
+ +
+ + + {post.title} + + + +
+
+ + {post.date.start_date} + +
+
+ + {(!showPreview || showSummary) &&

+ {post.summary} +

} + + {showPreview && post?.blockMap &&
+ +
} + +
+ + + + {post.category} + + +
+
{post.tagItems.map(tag => ())}
+
+
+ +
+ + {CONFIG_HEXO.POST_LIST_COVER && post?.page_cover && ( + + + {/* eslint-disable-next-line @next/next/no-img-element */} + {/* {post.title} */} + {post.title} + + + )} +
+
+ + ) +} + +const mapPageUrl = id => { + return 'https://www.notion.so/' + id.replace(/-/g, '') +} + +export default BlogPostCard diff --git a/themes/Hexo/components/BlogPostListEmpty.js b/themes/Hexo/components/BlogPostListEmpty.js new file mode 100644 index 00000000..b1d87253 --- /dev/null +++ b/themes/Hexo/components/BlogPostListEmpty.js @@ -0,0 +1,12 @@ + +/** + * 空白博客 列表 + * @returns {JSX.Element} + * @constructor + */ +const BlogPostListEmpty = ({ currentSearch }) => { + return
+

没有找到文章 {(currentSearch &&

{currentSearch}
)}

+
+} +export default BlogPostListEmpty diff --git a/themes/Hexo/components/BlogPostListPage.js b/themes/Hexo/components/BlogPostListPage.js new file mode 100644 index 00000000..f10811af --- /dev/null +++ b/themes/Hexo/components/BlogPostListPage.js @@ -0,0 +1,34 @@ +import BlogPostCard from './BlogPostCard' +import PaginationNumber from './PaginationNumber' +import BLOG from '@/blog.config' +import BlogPostListEmpty from './BlogPostListEmpty' + +/** + * 文章列表分页表格 + * @param page 当前页 + * @param posts 所有文章 + * @param tags 所有标签 + * @returns {JSX.Element} + * @constructor + */ +const BlogPostListPage = ({ page = 1, posts = [], postCount }) => { + const totalPage = Math.ceil(postCount / BLOG.POSTS_PER_PAGE) + + if (!posts || posts.length === 0) { + return + } else { + return ( +
+ {/* 文章列表 */} +
+ {posts.map(post => ( + + ))} +
+ +
+ ) + } +} + +export default BlogPostListPage diff --git a/themes/Hexo/components/Card.js b/themes/Hexo/components/Card.js new file mode 100644 index 00000000..61feccfd --- /dev/null +++ b/themes/Hexo/components/Card.js @@ -0,0 +1,9 @@ +const Card = ({ children, headerSlot, className }) => { + return
+ <>{headerSlot} +
+ {children} +
+
+} +export default Card diff --git a/themes/Hexo/components/CategoryGroup.js b/themes/Hexo/components/CategoryGroup.js new file mode 100644 index 00000000..bc07e00a --- /dev/null +++ b/themes/Hexo/components/CategoryGroup.js @@ -0,0 +1,24 @@ +import { faFolder, faFolderOpen } 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 }) => { + return <> +
+ {Object.keys(categories).map(category => { + const selected = currentCategory === category + return + +
{category}({categories[category]})
+
+ + })} +
+ +} + +export default CategoryGroup diff --git a/themes/Hexo/components/Footer.js b/themes/Hexo/components/Footer.js new file mode 100644 index 00000000..57ad7797 --- /dev/null +++ b/themes/Hexo/components/Footer.js @@ -0,0 +1,30 @@ +import { faCopyright, faEye, faShieldAlt, faUsers, faHeart } from '@fortawesome/free-solid-svg-icons' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import React from 'react' +import BLOG from '@/blog.config' + +const Footer = ({ title }) => { + const d = new Date() + const currentYear = d.getFullYear() + const startYear = BLOG.SINCE && BLOG.SINCE !== currentYear && BLOG.SINCE + '-' + return ( + + ) +} + +export default Footer diff --git a/themes/Hexo/components/Header.js b/themes/Hexo/components/Header.js index f75f5d36..c55aac64 100644 --- a/themes/Hexo/components/Header.js +++ b/themes/Hexo/components/Header.js @@ -77,7 +77,7 @@ export default function Header () { setTimeout(() => { if (window) { const wrapperElement = document.getElementById('wrapper') - wrapperTop = wrapperElement.offsetTop + wrapperTop = wrapperElement?.offsetTop } }, 500) } @@ -103,7 +103,7 @@ export default function Header () { }} >
-
+
{ diff --git a/themes/Hexo/components/LatestPostsGroup.js b/themes/Hexo/components/LatestPostsGroup.js new file mode 100644 index 00000000..78782e69 --- /dev/null +++ b/themes/Hexo/components/LatestPostsGroup.js @@ -0,0 +1,42 @@ +import BLOG from '@/blog.config' +import { useGlobal } from '@/lib/global' +import { faArchive, faFileAlt } from '@fortawesome/free-solid-svg-icons' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import Link from 'next/link' +import { useRouter } from 'next/router' + +/** + * 最新文章列表 + * @param posts 所有文章数据 + * @param sliceCount 截取展示的数量 默认6 + * @constructor + */ +const LatestPostsGroup = ({ posts }) => { + if (!posts) { + return <> + } + // 获取当前路径 + const currentPath = useRouter().asPath + const { locale } = useGlobal() + + return <> +
+
{locale.COMMON.LATEST_POSTS}
+
+ {posts.map(post => { + const selected = currentPath === `${BLOG.PATH}/article/${post.slug}` + return ( + + +
+ +
{post.title}
+
+
+ + ) + })} + +} +export default LatestPostsGroup diff --git a/themes/Hexo/components/MenuButtonGroup.js b/themes/Hexo/components/MenuButtonGroup.js new file mode 100644 index 00000000..53528653 --- /dev/null +++ b/themes/Hexo/components/MenuButtonGroup.js @@ -0,0 +1,41 @@ +import React from 'react' +import Link from 'next/link' +import { useRouter } from 'next/router' +import { useGlobal } from '@/lib/global' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { faArchive, faHome, faTag, faTh, faUser } from '@fortawesome/free-solid-svg-icons' +import CONFIG_HEXO from '../config_hexo' + +const MenuButtonGroup = ({ postCount }) => { + const { locale } = useGlobal() + const router = useRouter() + const archiveSlot =
{postCount}
+ + const links = [ + { id: 0, icon: faHome, name: locale.NAV.INDEX, to: '/' || '/', show: true }, + { id: 1, icon: faTh, name: locale.COMMON.CATEGORY, to: '/category', show: CONFIG_HEXO.MENU_CATEGORY }, + { id: 2, icon: faTag, name: locale.COMMON.TAGS, to: '/tag', show: CONFIG_HEXO.MENU_TAG }, + { id: 3, icon: faArchive, name: locale.NAV.ARCHIVE, to: '/archive', slot: archiveSlot, show: CONFIG_HEXO.MENU_ARCHIVE }, + { id: 4, icon: faUser, name: locale.NAV.ABOUT, to: '/about', show: CONFIG_HEXO.MENU_ABOUT } + ] + return +} +export default MenuButtonGroup diff --git a/themes/Hexo/components/PaginationNumber.js b/themes/Hexo/components/PaginationNumber.js new file mode 100644 index 00000000..bec6f399 --- /dev/null +++ b/themes/Hexo/components/PaginationNumber.js @@ -0,0 +1,94 @@ +import BLOG from '@/blog.config' +import Link from 'next/link' +import { useRouter } from 'next/router' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { faAngleLeft, faAngleRight } from '@fortawesome/free-solid-svg-icons' + +/** + * 数字翻页插件 + * @param page 当前页码 + * @param showNext 是否有下一页 + * @returns {JSX.Element} + * @constructor + */ +const PaginationNumber = ({ page, totalPage }) => { + const router = useRouter() + const currentPage = +page + const showNext = page !== totalPage + const pages = generatePages(page, currentPage, totalPage) + + return ( +
+ + {/* 上一页 */} + + + + + {pages} + + {/* 下一页 */} + + + +
+ ) +} + +function getPageElement (page, currentPage) { + return + + {page} + + +} +function generatePages (page, currentPage, totalPage) { + const pages = [] + const groupCount = 7 // 最多显示页签数 + if (totalPage <= groupCount) { + for (let i = 1; i <= totalPage; i++) { + pages.push(getPageElement(i, page)) + } + } else { + pages.push(getPageElement(1, page)) + const dynamicGroupCount = groupCount - 2 + let startPage = currentPage - 2 + if (startPage <= 1) { + startPage = 2 + } + if (startPage + dynamicGroupCount > totalPage) { + startPage = totalPage - dynamicGroupCount + } + if (startPage > 2) { + pages.push(
...
) + } + + for (let i = 0; i < dynamicGroupCount; i++) { + if (startPage + i < totalPage) { + pages.push(getPageElement(startPage + i, page)) + } + } + + if (startPage + dynamicGroupCount < totalPage) { + pages.push(
...
) + } + + pages.push(getPageElement(totalPage, page)) + } + return pages +} +export default PaginationNumber diff --git a/themes/Hexo/components/SearchInput.js b/themes/Hexo/components/SearchInput.js new file mode 100644 index 00000000..57e16633 --- /dev/null +++ b/themes/Hexo/components/SearchInput.js @@ -0,0 +1,68 @@ +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 }) => { + const [searchKey, setSearchKey] = useState(currentSearch || '') + 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 && +
+ +
+ )} +
+} + +export default SearchInput diff --git a/themes/Hexo/components/SideRight.js b/themes/Hexo/components/SideRight.js new file mode 100644 index 00000000..adac7e09 --- /dev/null +++ b/themes/Hexo/components/SideRight.js @@ -0,0 +1,64 @@ +import Router from 'next/router' +import Image from 'next/image' +import BLOG from '@/blog.config' +import Card from './Card' +import MenuButtonGroup from './MenuButtonGroup' +import SearchInput from './SearchInput' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { faChartArea, faTh } from '@fortawesome/free-solid-svg-icons' +import CategoryGroup from './CategoryGroup' +import LatestPostsGroup from './LatestPostsGroup' +import TagGroups from './TagGroups' +import SocialButton from './SocialButton' + +export default function SideRight (props) { + const { postCount, currentCategory, categories, latestPosts, tags, currentTag } = props + return
+ +
{ Router.push('/') }}> + {BLOG.AUTHOR} +
+
{BLOG.TITLE}
+ +
+ + + + + +
+ 统计 +
+
+
+
文章数:
{postCount}
+
+
+
访问量:
+
+
+
访客数:
+
+
+ + +
+ 分类 +
+ +
+ + + + + + +
+} diff --git a/themes/Hexo/components/SocialButton.js b/themes/Hexo/components/SocialButton.js new file mode 100644 index 00000000..4a4c6a2e --- /dev/null +++ b/themes/Hexo/components/SocialButton.js @@ -0,0 +1,36 @@ +import BLOG from '@/blog.config' +import { faGithub, faTelegram, faTwitter, faWeibo } from '@fortawesome/free-brands-svg-icons' +import { faEnvelope, faRss } from '@fortawesome/free-solid-svg-icons' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import React from 'react' + +/** + * 社交联系方式按钮组 + * @returns {JSX.Element} + * @constructor + */ +const SocialButton = () => { + return
+
+ {BLOG.CONTACT_GITHUB && + + } + {BLOG.CONTACT_TWITTER && + + } + {BLOG.CONTACT_TELEGRAM && + + } + {BLOG.CONTACT_WEIBO && + + } + {BLOG.CONTACT_EMAIL && + + } + + + +
+
+} +export default SocialButton diff --git a/themes/Hexo/components/TagGroups.js b/themes/Hexo/components/TagGroups.js new file mode 100644 index 00000000..9f87776f --- /dev/null +++ b/themes/Hexo/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/Hexo/components/TagItemMini.js b/themes/Hexo/components/TagItemMini.js new file mode 100644 index 00000000..e687413f --- /dev/null +++ b/themes/Hexo/components/TagItemMini.js @@ -0,0 +1,17 @@ +import { faTag } from '@fortawesome/free-solid-svg-icons' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import Link from 'next/link' + +const TagItemMini = ({ tag, selected = false }) => { + return + +
{selected && } {tag.name + (tag.count ? `(${tag.count})` : '')}
+
+ +} + +export default TagItemMini diff --git a/themes/Hexo/config_hexo.js b/themes/Hexo/config_hexo.js index 6da54488..4581a256 100644 --- a/themes/Hexo/config_hexo.js +++ b/themes/Hexo/config_hexo.js @@ -1,6 +1,15 @@ const CONFIG_HEXO = { HOME_BANNER_ENABLE: true, HOME_BANNER_GREETINGS: ['Hi,我是一个程序员', 'Hi,我是一个打工人', 'Hi,我是一个干饭人', '欢迎来到我的博客🎉'], // 首页大图标语文字 - HOME_BANNER_IMAGE: './bg_image.jpg' // see /public/bg_image.jpg + HOME_BANNER_IMAGE: './bg_image.jpg', // see /public/bg_image.jpg + + // 菜单 + MENU_ABOUT: false, // 显示关于 + MENU_CATEGORY: true, // 显示分类 + MENU_TAG: true, // 显示标签 + MENU_ARCHIVE: true, // 显示归档 + MENU_SEARCH: true, // 显示搜索 + + POST_LIST_COVER: true // 文章封面 } export default CONFIG_HEXO diff --git a/themes/index.js b/themes/index.js index a519f091..4f34ef8d 100644 --- a/themes/index.js +++ b/themes/index.js @@ -2,7 +2,7 @@ * 修改 from 后面的路径,实现主题切换 */ -// export * from './NEXT' // 切换主题 +// export * from './Empty' // 空主题 +// export * from './NEXT' // export * from './Fukasawa' -// export * from './Empty' -export * from './Hexo' +export * from './Hexo' //