diff --git a/blog.config.js b/blog.config.js index 79803ce0..80bce8f6 100644 --- a/blog.config.js +++ b/blog.config.js @@ -7,6 +7,7 @@ const BLOG = { link: 'https://tangly1024.com', // 网站地址 keywords: ['Notion', '写作', '博客'], // 网站关键词 lang: 'zh-CN', // ['zh-CN','en-US'] default lang => see /lib/lang.js for more. + beian: '闽ICP备20010331号', // 备案号 notionPageId: process.env.NOTION_PAGE_ID || 'bee1fccfa3bd47a1a7be83cc71372d83', // Important page_id!!! notionAccessToken: process.env.NOTION_ACCESS_TOKEN || '', // Useful if you prefer not to make your database public appearance: 'auto', // ['light', 'dark', 'auto'], diff --git a/components/CommonHead.js b/components/CommonHead.js index 784795a0..9f9a3c2c 100644 --- a/components/CommonHead.js +++ b/components/CommonHead.js @@ -36,7 +36,7 @@ const CommonHead = ({ meta }) => { - {meta.type === 'article' && ( + {meta?.type === 'article' && ( <> { + return 404 +} diff --git a/themes/Fukasawa/LayoutArchive.js b/themes/Fukasawa/LayoutArchive.js new file mode 100644 index 00000000..5f4b05df --- /dev/null +++ b/themes/Fukasawa/LayoutArchive.js @@ -0,0 +1,7 @@ +import LayoutBase from './LayoutBase' + +export const LayoutArchive = (props) => { + return + Archive Page + +} diff --git a/themes/Fukasawa/LayoutBase.js b/themes/Fukasawa/LayoutBase.js new file mode 100644 index 00000000..9250b713 --- /dev/null +++ b/themes/Fukasawa/LayoutBase.js @@ -0,0 +1,48 @@ +import CommonHead from '@/components/CommonHead' +import AsideLeft from './components/AsideLeft' + +/** + * 基础布局 采用左右两侧布局,移动端使用顶部导航栏 + * @param children + * @param layout + * @param tags + * @param meta + * @param post + * @param currentSearch + * @param currentCategory + * @param currentTag + * @param categories + * @returns {JSX.Element} + * @constructor + */ +const LayoutBase = ({ + children, + headerSlot, + tags, + meta, + post, + postCount, + sideBarSlot, + floatSlot, + rightAreaSlot, + currentSearch, + currentCategory, + currentTag, + categories +}) => { + return (<> + +
+ +
+
+
{headerSlot}
+
{children}
+
+
+
+ + ) +} + +export default LayoutBase diff --git a/themes/Fukasawa/LayoutCategory.js b/themes/Fukasawa/LayoutCategory.js new file mode 100644 index 00000000..3b810ce9 --- /dev/null +++ b/themes/Fukasawa/LayoutCategory.js @@ -0,0 +1,7 @@ +import LayoutBase from './LayoutBase' + +export const LayoutCategory = (props) => { + return + Category + +} diff --git a/themes/Fukasawa/LayoutCategoryIndex.js b/themes/Fukasawa/LayoutCategoryIndex.js new file mode 100644 index 00000000..9a3168a8 --- /dev/null +++ b/themes/Fukasawa/LayoutCategoryIndex.js @@ -0,0 +1,7 @@ +import LayoutBase from './LayoutBase' + +export const LayoutCategoryIndex = (props) => { + return + Category + +} diff --git a/themes/Fukasawa/LayoutIndex.js b/themes/Fukasawa/LayoutIndex.js new file mode 100644 index 00000000..5a1209c5 --- /dev/null +++ b/themes/Fukasawa/LayoutIndex.js @@ -0,0 +1,10 @@ +import BlogListPage from './components/BlogListPage' +import LayoutBase from './LayoutBase' + +export const LayoutIndex = (props) => { + return + + + + +} diff --git a/themes/Fukasawa/LayoutPage.js b/themes/Fukasawa/LayoutPage.js new file mode 100644 index 00000000..0cf417fe --- /dev/null +++ b/themes/Fukasawa/LayoutPage.js @@ -0,0 +1,10 @@ +import BlogListPage from './components/BlogListPage' +import LayoutBase from './LayoutBase' + +export const LayoutPage = (props) => { + return + + + + +} diff --git a/themes/Fukasawa/LayoutSearch.js b/themes/Fukasawa/LayoutSearch.js new file mode 100644 index 00000000..1d77bf69 --- /dev/null +++ b/themes/Fukasawa/LayoutSearch.js @@ -0,0 +1,30 @@ +import { useRouter } from 'next/router' +import LayoutBase from './LayoutBase' + +export const LayoutSearch = (props) => { + let filteredPosts + const searchKey = getSearchKey() + if (searchKey) { + filteredPosts = props.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 = props.posts + } + + console.log(filteredPosts) + + return + Search {searchKey} + +} + +function getSearchKey () { + const router = useRouter() + if (router.query && router.query.s) { + return router.query.s + } + return null +} diff --git a/themes/Fukasawa/LayoutSlug.js b/themes/Fukasawa/LayoutSlug.js new file mode 100644 index 00000000..d582caac --- /dev/null +++ b/themes/Fukasawa/LayoutSlug.js @@ -0,0 +1,13 @@ +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 LayoutBase from './LayoutBase' + +export const LayoutSlug = (props) => { + return + 首页 + +} diff --git a/themes/Fukasawa/LayoutTag.js b/themes/Fukasawa/LayoutTag.js new file mode 100644 index 00000000..53167b65 --- /dev/null +++ b/themes/Fukasawa/LayoutTag.js @@ -0,0 +1,7 @@ +import LayoutBase from './LayoutBase' + +export const LayoutTag = (props) => { + return + Tag - {props.tag} + +} diff --git a/themes/Fukasawa/LayoutTagIndex.js b/themes/Fukasawa/LayoutTagIndex.js new file mode 100644 index 00000000..48ad8d2d --- /dev/null +++ b/themes/Fukasawa/LayoutTagIndex.js @@ -0,0 +1,7 @@ +import LayoutBase from './LayoutBase' + +export const LayoutTagIndex = (props) => { + return + Tag - {props.tag} + +} diff --git a/themes/Fukasawa/components/AsideLeft.js b/themes/Fukasawa/components/AsideLeft.js new file mode 100644 index 00000000..8c8d13dc --- /dev/null +++ b/themes/Fukasawa/components/AsideLeft.js @@ -0,0 +1,47 @@ +import BLOG from '@/blog.config' +import Logo from './Logo' +import GroupCategory from './GroupCategory' +import GroupMenu from './GroupMenu' +import GroupTag from './GroupTag' +import SearchInput from './SearchInput' +import SiteInfo from './SiteInfo' + +function AsideLeft ({ tags, currentTag, categories, currentCategory }) { + return
+ + + +
+
+ +
+ +
+
+ +
+ +
+
+ { BLOG.description } +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+} + +export default AsideLeft diff --git a/themes/Fukasawa/components/BlogCard.js b/themes/Fukasawa/components/BlogCard.js new file mode 100644 index 00000000..9d80617e --- /dev/null +++ b/themes/Fukasawa/components/BlogCard.js @@ -0,0 +1,39 @@ +import BLOG from '@/blog.config' +import Image from 'next/image' +import Link from 'next/link' +import React from 'react' +import CONFIG_FUKA from '../config_fuka' +import Card from './Card' + +const BlogCard = ({ post, showSummary }) => { + const showPreview = CONFIG_FUKA.POST_LIST_PREVIEW && post.blockMap + return ( + +
+ +
+ + + {post.title} + + + + {(!showPreview || showSummary) &&

+ {post.summary} +

} +
+ + {CONFIG_FUKA.POST_LIST_COVER && post?.page_cover && ( + +
+ {post.title} +
+ + )} +
+
+ + ) +} + +export default BlogCard diff --git a/themes/Fukasawa/components/BlogListEmpty.js b/themes/Fukasawa/components/BlogListEmpty.js new file mode 100644 index 00000000..41b42e73 --- /dev/null +++ b/themes/Fukasawa/components/BlogListEmpty.js @@ -0,0 +1,12 @@ + +/** + * 空白博客 列表 + * @returns {JSX.Element} + * @constructor + */ +const BlogListEmpty = ({ currentSearch }) => { + return
+

没有找到文章 {(currentSearch &&

{currentSearch}
)}

+
+} +export default BlogListEmpty diff --git a/themes/Fukasawa/components/BlogListPage.js b/themes/Fukasawa/components/BlogListPage.js new file mode 100644 index 00000000..fa7a23d3 --- /dev/null +++ b/themes/Fukasawa/components/BlogListPage.js @@ -0,0 +1,36 @@ +import BlogCard from './BlogCard' +import PaginationNumber from './PaginationNumber' +import BLOG from '@/blog.config' +import BlogPostListEmpty from './BlogListEmpty' + +/** + * 文章列表分页表格 + * @param page 当前页 + * @param posts 所有文章 + * @param tags 所有标签 + * @returns {JSX.Element} + * @constructor + */ +const BlogListPage = ({ page = 1, posts = [], postCount }) => { + const totalPage = Math.ceil(postCount / BLOG.postsPerPage) + + if (!posts || posts.length === 0) { + return + } else { + return ( +
+ {/* 文章列表 */} +
+ {posts.map(post => ( +
+ +
+ ))} +
+ +
+ ) + } +} + +export default BlogListPage diff --git a/themes/Fukasawa/components/Card.js b/themes/Fukasawa/components/Card.js new file mode 100644 index 00000000..d24c046e --- /dev/null +++ b/themes/Fukasawa/components/Card.js @@ -0,0 +1,9 @@ +const Card = ({ children, headerSlot, className }) => { + return
+ <>{headerSlot} +
+ {children} +
+
+} +export default Card diff --git a/themes/Fukasawa/components/GroupCategory.js b/themes/Fukasawa/components/GroupCategory.js new file mode 100644 index 00000000..42bc28cb --- /dev/null +++ b/themes/Fukasawa/components/GroupCategory.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' + +function GroupCategory ({ currentCategory, categories }) { + return <> +
+ {Object.keys(categories).map(category => { + const selected = currentCategory === category + return + + {category}({categories[category]}) + + + })} +
+ +} + +export default GroupCategory diff --git a/themes/Fukasawa/components/GroupMenu.js b/themes/Fukasawa/components/GroupMenu.js new file mode 100644 index 00000000..4b97583d --- /dev/null +++ b/themes/Fukasawa/components/GroupMenu.js @@ -0,0 +1,38 @@ +import React from 'react' +import Link from 'next/link' +import { useRouter } from 'next/router' +import { useGlobal } from '@/lib/global' +import CONFIG_FUKA from '../config_fuka' + +function GroupMenu () { + const { locale } = useGlobal() + const router = useRouter() + + const links = [ + { id: 0, name: locale.NAV.INDEX, to: '/' || '/', show: true }, + { id: 1, name: locale.COMMON.CATEGORY, to: '/category', show: CONFIG_FUKA.MENU_CATEGORY }, + { id: 2, name: locale.COMMON.TAGS, to: '/tag', show: CONFIG_FUKA.MENU_TAG }, + { id: 3, name: locale.NAV.ARCHIVE, to: '/archive', show: CONFIG_FUKA.MENU_ARCHIVE }, + { id: 4, name: locale.NAV.ABOUT, to: '/about', show: CONFIG_FUKA.MENU_ABOUT } + ] + return +} + +export default GroupMenu diff --git a/themes/Fukasawa/components/GroupTag.js b/themes/Fukasawa/components/GroupTag.js new file mode 100644 index 00000000..16bf01a3 --- /dev/null +++ b/themes/Fukasawa/components/GroupTag.js @@ -0,0 +1,24 @@ +import TagItemMini from './TagItemMini' + +/** + * 标签组 + * @param tags + * @param currentTag + * @returns {JSX.Element} + * @constructor + */ +function GroupTag ({ tags, currentTag }) { + if (!tags) return <> + return ( +
+ { + tags.map(tag => { + const selected = tag.name === currentTag + return + }) + } +
+ ) +} + +export default GroupTag diff --git a/themes/Fukasawa/components/Logo.js b/themes/Fukasawa/components/Logo.js new file mode 100644 index 00000000..7c63ee8e --- /dev/null +++ b/themes/Fukasawa/components/Logo.js @@ -0,0 +1,12 @@ +import BLOG from '@/blog.config' +import Link from 'next/link' + +function Logo () { + return
+ + {BLOG.title} + +
+} + +export default Logo diff --git a/themes/Fukasawa/components/PaginationNumber.js b/themes/Fukasawa/components/PaginationNumber.js new file mode 100644 index 00000000..36de638b --- /dev/null +++ b/themes/Fukasawa/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/Fukasawa/components/SearchInput.js b/themes/Fukasawa/components/SearchInput.js new file mode 100644 index 00000000..a91cd46a --- /dev/null +++ b/themes/Fukasawa/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/Fukasawa/components/SiteInfo.js b/themes/Fukasawa/components/SiteInfo.js new file mode 100644 index 00000000..ac19d6c7 --- /dev/null +++ b/themes/Fukasawa/components/SiteInfo.js @@ -0,0 +1,26 @@ +import BLOG from '@/blog.config' +import { faEye, faShieldAlt, faUsers } from '@fortawesome/free-solid-svg-icons' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' + +function SiteInfo ({ title }) { + const d = new Date() + const currentYear = d.getFullYear() + const startYear = BLOG.since && BLOG.since !== currentYear && BLOG.since + '-' + return ( + + ) +} +export default SiteInfo diff --git a/themes/Fukasawa/components/TagItemMini.js b/themes/Fukasawa/components/TagItemMini.js new file mode 100644 index 00000000..9fec9e35 --- /dev/null +++ b/themes/Fukasawa/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/Fukasawa/config_fuka.js b/themes/Fukasawa/config_fuka.js new file mode 100644 index 00000000..b34851e8 --- /dev/null +++ b/themes/Fukasawa/config_fuka.js @@ -0,0 +1,12 @@ +const FUKA_CONFIG = { + + POST_LIST_COVER: true, // 文章列表显示图片封面 + + // 菜单 + MENU_ABOUT: true, // 显示关于 + MENU_CATEGORY: true, // 显示分类 + MENU_TAG: true, // 显示标签 + MENU_ARCHIVE: true, // 显示归档 + MENU_SEARCH: true // 显示搜索 +} +export default FUKA_CONFIG diff --git a/themes/Fukasawa/index.js b/themes/Fukasawa/index.js new file mode 100644 index 00000000..aabed077 --- /dev/null +++ b/themes/Fukasawa/index.js @@ -0,0 +1,10 @@ +export { LayoutIndex } from './LayoutIndex' +export { LayoutSearch } from './LayoutSearch' +export { LayoutArchive } from './LayoutArchive' +export { LayoutSlug } from './LayoutSlug' +export { Layout404 } from './Layout404' +export { LayoutCategory } from './LayoutCategory' +export { LayoutCategoryIndex } from './LayoutCategoryIndex' +export { LayoutPage } from './LayoutPage' +export { LayoutTag } from './LayoutTag' +export { LayoutTagIndex } from './LayoutTagIndex' diff --git a/themes/index.js b/themes/index.js index 9adbd8cd..de2f713d 100644 --- a/themes/index.js +++ b/themes/index.js @@ -2,5 +2,6 @@ * 直接将./NEXT 替换成对应的主题路径 */ -export * from './NEXT' // 切换主题 -// export * from './Empty' // 切换主题 +// export * from './NEXT' // 切换主题 +export * from './Fukasawa' +// export * from './Empty'