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 && (
+
+
+
+
+
+ )}
+
+
+
+ )
+}
+
+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}>
+
+
+}
+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 <>
+
+ >
+}
+
+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
+}
+
+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'