-
+
{locale.COMMON.SUBMIT}
diff --git a/themes/nobelium/components/SearchInput.js b/themes/nobelium/components/SearchInput.js
index 6a95ba01..fdb129b9 100644
--- a/themes/nobelium/components/SearchInput.js
+++ b/themes/nobelium/components/SearchInput.js
@@ -62,7 +62,7 @@ const SearchInput = ({ currentTag, currentSearch, cRef }) => {
ref={searchInputRef}
type='text'
placeholder={currentTag ? `${locale.SEARCH.TAGS} #${currentTag}` : `${locale.SEARCH.ARTICLES}`}
- className={'w-full text-sm pl-4 transition focus:shadow-lg font-light leading-10 text-black bg-gray-100 dark:bg-gray-900 dark:text-white'}
+ className={'outline-none w-full text-sm pl-4 transition focus:shadow-lg font-light leading-10 text-black bg-gray-100 dark:bg-gray-900 dark:text-white'}
onKeyUp={handleKeyUp}
onCompositionStart={lockSearchInput}
onCompositionUpdate={lockSearchInput}
diff --git a/themes/simple/Layout404.js b/themes/simple/Layout404.js
new file mode 100644
index 00000000..5f92f0cc
--- /dev/null
+++ b/themes/simple/Layout404.js
@@ -0,0 +1,7 @@
+import LayoutBase from './LayoutBase'
+
+export const Layout404 = (props) => {
+ return
+ 404 Not found.
+
+}
diff --git a/themes/simple/LayoutArchive.js b/themes/simple/LayoutArchive.js
new file mode 100644
index 00000000..4ba92d8a
--- /dev/null
+++ b/themes/simple/LayoutArchive.js
@@ -0,0 +1,45 @@
+import BLOG from '@/blog.config'
+import Link from 'next/link'
+import LayoutBase from './LayoutBase'
+
+export const LayoutArchive = props => {
+ const { archivePosts } = props
+
+ return (
+
+
+ {Object.keys(archivePosts).map(archiveTitle => (
+
+
+ {archiveTitle}
+
+
+
+
+ ))}
+
+
+ )
+}
diff --git a/themes/simple/LayoutBase.js b/themes/simple/LayoutBase.js
new file mode 100644
index 00000000..88e17d84
--- /dev/null
+++ b/themes/simple/LayoutBase.js
@@ -0,0 +1,61 @@
+import CommonHead from '@/components/CommonHead'
+import React from 'react'
+import { Header } from './components/Header'
+import { NavBar } from './components/NavBar'
+import { Footer } from './components/Footer'
+// import { Title } from './components/Title'
+import { SideBar } from './components/SideBar'
+import JumpToTopButton from './components/JumpToTopButton'
+import BLOG from '@/blog.config'
+import { TopBar } from './components/TopBar'
+import CONFIG_SIMPLE from './config_simple'
+import { isBrowser, loadExternalResource } from '@/lib/utils'
+/**
+ * 基础布局 采用左右两侧布局,移动端使用顶部导航栏
+
+ * @returns {JSX.Element}
+ * @constructor
+ */
+const LayoutBase = props => {
+ const { children, meta } = props
+ if (isBrowser()) {
+ loadExternalResource('/css/theme-simple.css', 'css')
+ }
+ return (
+
+
+
+ {CONFIG_SIMPLE.TOP_BAR &&
}
+
+ {/* 顶部LOGO */}
+
+
+ {/* 导航栏 */}
+
+
+ {/* 主体 */}
+
+
+
+
+
+
+
+
+
+ )
+}
+
+export default LayoutBase
diff --git a/themes/simple/LayoutCategory.js b/themes/simple/LayoutCategory.js
new file mode 100644
index 00000000..6d819185
--- /dev/null
+++ b/themes/simple/LayoutCategory.js
@@ -0,0 +1,10 @@
+import BLOG from '@/blog.config'
+import { BlogListPage } from './components/BlogListPage'
+import { BlogListScroll } from './components/BlogListScroll'
+import LayoutBase from './LayoutBase'
+
+export const LayoutCategory = props => {
+ return
+ {BLOG.POST_LIST_STYLE === 'page' ? : }
+
+}
diff --git a/themes/simple/LayoutCategoryIndex.js b/themes/simple/LayoutCategoryIndex.js
new file mode 100644
index 00000000..a4c19033
--- /dev/null
+++ b/themes/simple/LayoutCategoryIndex.js
@@ -0,0 +1,26 @@
+import Link from 'next/link'
+import LayoutBase from './LayoutBase'
+
+export const LayoutCategoryIndex = props => {
+ const { categoryOptions } = props
+ return (
+
+
+ {categoryOptions?.map(category => {
+ return (
+
+
+ {category.name}({category.count})
+
+
+ )
+ })}
+
+
+ )
+}
diff --git a/themes/simple/LayoutIndex.js b/themes/simple/LayoutIndex.js
new file mode 100644
index 00000000..94eaac2f
--- /dev/null
+++ b/themes/simple/LayoutIndex.js
@@ -0,0 +1,13 @@
+
+import BLOG from '@/blog.config'
+import { BlogListPage } from './components/BlogListPage'
+import { BlogListScroll } from './components/BlogListScroll'
+import LayoutBase from './LayoutBase'
+
+export const LayoutIndex = props => {
+ return (
+
+ {BLOG.POST_LIST_STYLE === 'page' ? : }
+
+ )
+}
diff --git a/themes/simple/LayoutPage.js b/themes/simple/LayoutPage.js
new file mode 100644
index 00000000..15355018
--- /dev/null
+++ b/themes/simple/LayoutPage.js
@@ -0,0 +1,10 @@
+import { BlogListPage } from './components/BlogListPage'
+import LayoutBase from './LayoutBase'
+
+export const LayoutPage = props => {
+ return (
+
+
+
+ )
+}
diff --git a/themes/simple/LayoutSearch.js b/themes/simple/LayoutSearch.js
new file mode 100644
index 00000000..1ba8fef7
--- /dev/null
+++ b/themes/simple/LayoutSearch.js
@@ -0,0 +1,50 @@
+import BLOG from '@/blog.config'
+import { BlogListPage } from './components/BlogListPage'
+import { BlogListScroll } from './components/BlogListScroll'
+import { useRouter } from 'next/router'
+import { useEffect } from 'react'
+import Mark from 'mark.js'
+import LayoutBase from './LayoutBase'
+import { isBrowser } from '@/lib/utils'
+
+export const LayoutSearch = props => {
+ const { keyword } = props
+ const router = useRouter()
+
+ useEffect(() => {
+ setTimeout(() => {
+ const container = isBrowser() && document.getElementById('container')
+ if (container && container.innerHTML) {
+ const re = new RegExp(keyword, 'gim')
+ const instance = new Mark(container)
+ instance.markRegExp(re, {
+ element: 'span',
+ className: 'text-red-500 border-b border-dashed'
+ })
+ }
+ }, 100)
+ }, [router.events])
+
+ useEffect(() => {
+ setTimeout(() => {
+ if (keyword) {
+ const targets = document.getElementsByClassName('replace')
+ for (const container of targets) {
+ if (container && container.innerHTML) {
+ const re = new RegExp(`${keyword}`, 'gim')
+ container.innerHTML = container.innerHTML.replace(
+ re,
+ `
${keyword}`
+ )
+ }
+ }
+ }
+ }, 100)
+ }, [])
+
+ return
+
+ {BLOG.POST_LIST_STYLE === 'page' ? : }
+
+
+}
diff --git a/themes/simple/LayoutSlug.js b/themes/simple/LayoutSlug.js
new file mode 100644
index 00000000..b16892b7
--- /dev/null
+++ b/themes/simple/LayoutSlug.js
@@ -0,0 +1,30 @@
+import LayoutBase from './LayoutBase'
+import { ArticleLock } from './components/ArticleLock'
+import NotionPage from '@/components/NotionPage'
+import { ArticleInfo } from './components/ArticleInfo'
+import Comment from '@/components/Comment'
+
+export const LayoutSlug = props => {
+ const { post, lock, validPassword } = props
+
+ if (!post) {
+ return
+ }
+
+ return (
+
+
+ {lock && }
+
+ {!lock &&
+
+ {post && <>
+
+
+
+ >}
+ }
+
+
+ )
+}
diff --git a/themes/simple/LayoutTag.js b/themes/simple/LayoutTag.js
new file mode 100644
index 00000000..06a6d778
--- /dev/null
+++ b/themes/simple/LayoutTag.js
@@ -0,0 +1,10 @@
+import BLOG from '@/blog.config'
+import { BlogListPage } from './components/BlogListPage'
+import { BlogListScroll } from './components/BlogListScroll'
+import LayoutBase from './LayoutBase'
+
+export const LayoutTag = props => {
+ return
+ {BLOG.POST_LIST_STYLE === 'page' ? : }
+
+}
diff --git a/themes/simple/LayoutTagIndex.js b/themes/simple/LayoutTagIndex.js
new file mode 100644
index 00000000..0705c2d6
--- /dev/null
+++ b/themes/simple/LayoutTagIndex.js
@@ -0,0 +1,29 @@
+import Link from 'next/link'
+import LayoutBase from './LayoutBase'
+
+export const LayoutTagIndex = (props) => {
+ const { tagOptions } = props
+ return (
+
+
+ )
+}
diff --git a/themes/simple/components/Announcement.js b/themes/simple/components/Announcement.js
new file mode 100644
index 00000000..13d069d8
--- /dev/null
+++ b/themes/simple/components/Announcement.js
@@ -0,0 +1,13 @@
+import dynamic from 'next/dynamic'
+
+const NotionPage = dynamic(() => import('@/components/NotionPage'))
+
+const Announcement = ({ post, className }) => {
+ if (!post) {
+ return <>>
+ }
+ return <>{post && (
+
+
)} >
+}
+export default Announcement
diff --git a/themes/simple/components/ArticleInfo.js b/themes/simple/components/ArticleInfo.js
new file mode 100644
index 00000000..5a52750c
--- /dev/null
+++ b/themes/simple/components/ArticleInfo.js
@@ -0,0 +1,56 @@
+import Link from 'next/link'
+import { useGlobal } from '@/lib/global'
+import formatDate from '@/lib/formatDate'
+import CONFIG_SIMPLE from '../config_simple'
+import BLOG from '@/blog.config'
+
+export const ArticleInfo = (props) => {
+ const { post } = props
+
+ const { locale } = useGlobal()
+ const date = formatDate(post?.date?.start_date || post?.createdTime, locale.LOCALE)
+
+ return (
+
+
+
+ {post.title}
+
+
+ {post?.type !== 'Page' && (<>
+
+
{BLOG.AUTHOR}
+
- {post.date?.start_date || post.createdTime}
+ {post.category &&
- {post.category}}
+ {post.tags && post.tags?.length > 0 && post.tags.map(t =>
/ {t})}
+
+ >)}
+
+ {post?.type !== 'Page' && (<>
+
+
+ {date}
+
+
+
|
+
+ {locale.COMMON.LAST_EDITED_TIME}: {post.lastEditedTime}
+
+
|
+
+
+
+
+
+ >)}
+
+
+
+
+ )
+}
diff --git a/themes/simple/components/ArticleLock.js b/themes/simple/components/ArticleLock.js
new file mode 100644
index 00000000..f21ce74a
--- /dev/null
+++ b/themes/simple/components/ArticleLock.js
@@ -0,0 +1,38 @@
+import { useGlobal } from '@/lib/global'
+
+/**
+ * 加密文章校验组件
+ * @param {password, validPassword} props
+ * @param password 正确的密码
+ * @param validPassword(bool) 回调函数,校验正确回调入参为true
+ * @returns
+ */
+export const ArticleLock = props => {
+ const { validPassword } = props
+ const { locale } = useGlobal()
+
+ const submitPassword = () => {
+ const p = document.getElementById('password')
+ if (!validPassword(p?.value)) {
+ const tips = document.getElementById('tips')
+ if (tips) {
+ tips.innerHTML = ''
+ tips.innerHTML = `
${locale.COMMON.PASSWORD_ERROR}
`
+ }
+ }
+ }
+
+ return
+
+
{locale.COMMON.ARTICLE_LOCK_TIPS}
+
+
+
+ {locale.COMMON.SUBMIT}
+
+
+
+
+
+
+}
diff --git a/themes/simple/components/BlogItem.js b/themes/simple/components/BlogItem.js
new file mode 100644
index 00000000..038aeda7
--- /dev/null
+++ b/themes/simple/components/BlogItem.js
@@ -0,0 +1,36 @@
+import BLOG from '@/blog.config'
+import Link from 'next/link'
+import CONFIG_SIMPLE from '../config_simple'
+
+export const BlogItem = props => {
+ const { post } = props
+
+ return
+
+
+ {post.title}
+
+
+
+
+
{BLOG.AUTHOR}
+
- {post.date?.start_date || post.createdTime}
+ {post.category &&
- {post.category}}
+ {post.tags && post.tags?.length > 0 && post.tags.map(t =>
/ {t})}
+
+
+
+ {post.summary}
+ {post.summary && ...}
+
+
+
+
+ Continue Reading
+
+
+
+}
diff --git a/themes/simple/components/BlogListPage.js b/themes/simple/components/BlogListPage.js
new file mode 100644
index 00000000..f260da04
--- /dev/null
+++ b/themes/simple/components/BlogListPage.js
@@ -0,0 +1,40 @@
+
+import BLOG from '@/blog.config'
+import { useRouter } from 'next/router'
+import Link from 'next/link'
+import { BlogItem } from './BlogItem'
+
+export const BlogListPage = props => {
+ const { page = 1, posts, postCount } = props
+ const router = useRouter()
+ const totalPage = Math.ceil(postCount / BLOG.POSTS_PER_PAGE)
+ const currentPage = +page
+
+ const showPrev = currentPage > 1
+ const showNext = page < totalPage
+ const pagePrefix = router.asPath.replace(/\/page\/[1-9]\d*/, '').replace(/\/$/, '')
+
+ return (
+
+
+
+ {posts?.map(p => (
+
+ ))}
+
+
+
+
+ NEWER POSTS
+
+
+ OLDER POSTS
+
+
+
+ )
+}
diff --git a/themes/simple/components/BlogListScroll.js b/themes/simple/components/BlogListScroll.js
new file mode 100644
index 00000000..b1a12bc5
--- /dev/null
+++ b/themes/simple/components/BlogListScroll.js
@@ -0,0 +1,60 @@
+import BLOG from '@/blog.config'
+import { useGlobal } from '@/lib/global'
+import React from 'react'
+import throttle from 'lodash.throttle'
+import { BlogItem } from './BlogItem'
+
+export const BlogListScroll = props => {
+ const { posts } = props
+ const { locale } = useGlobal()
+
+ const [page, updatePage] = React.useState(1)
+
+ let hasMore = false
+ const postsToShow = posts
+ ? Object.assign(posts).slice(0, BLOG.POSTS_PER_PAGE * page)
+ : []
+
+ if (posts) {
+ const totalCount = posts.length
+ hasMore = page * BLOG.POSTS_PER_PAGE < totalCount
+ }
+ const handleGetMore = () => {
+ if (!hasMore) return
+ updatePage(page + 1)
+ }
+
+ const targetRef = React.useRef(null)
+
+ // 监听滚动自动分页加载
+ const scrollTrigger = React.useCallback(throttle(() => {
+ const scrollS = window.scrollY + window.outerHeight
+ const clientHeight = targetRef ? (targetRef.current ? (targetRef.current.clientHeight) : 0) : 0
+ if (scrollS > clientHeight + 100) {
+ handleGetMore()
+ }
+ }, 500))
+
+ React.useEffect(() => {
+ window.addEventListener('scroll', scrollTrigger)
+
+ return () => {
+ window.removeEventListener('scroll', scrollTrigger)
+ }
+ })
+
+ return (
+
+ {postsToShow.map(p => (
+
+ ))}
+
+
+ {' '}
+ {hasMore ? locale.COMMON.MORE : `${locale.COMMON.NO_MORE} 😰`}{' '}
+
+
+
+ )
+}
diff --git a/themes/simple/components/CollapseMenu.js b/themes/simple/components/CollapseMenu.js
new file mode 100644
index 00000000..73b0804b
--- /dev/null
+++ b/themes/simple/components/CollapseMenu.js
@@ -0,0 +1,50 @@
+import Collapse from '@/components/Collapse'
+import Link from 'next/link'
+import { useState } from 'react'
+
+/**
+ * 折叠菜单
+ * @param {*} param0
+ * @returns
+ */
+export const CollapseMenu = ({ link }) => {
+ const [show, changeShow] = useState(false)
+ const hasSubMenu = link?.subMenus?.length > 0
+
+ const [isOpen, changeIsOpen] = useState(false)
+
+ const toggleShow = () => {
+ changeShow(!show)
+ }
+
+ const toggleOpenSubMenu = () => {
+ changeIsOpen(!isOpen)
+ }
+
+ return <>
+
+ {!hasSubMenu &&
+
{link?.name}
+ }
+ {hasSubMenu &&
+ {link?.name}
+
+
}
+
+
+ {/* 折叠子菜单 */}
+ {hasSubMenu &&
+ {link.subMenus.map(sLink => {
+ return
+
+ {sLink.title}
+
+
+ })}
+ }
+ >
+}
diff --git a/themes/simple/components/DropMenu.js b/themes/simple/components/DropMenu.js
new file mode 100644
index 00000000..ad4865eb
--- /dev/null
+++ b/themes/simple/components/DropMenu.js
@@ -0,0 +1,37 @@
+import Link from 'next/link'
+import { useState } from 'react'
+
+export const DropMenu = ({ link }) => {
+ const [show, changeShow] = useState(false)
+ const hasSubMenu = link?.subMenus?.length > 0
+
+ return
changeShow(true)} onMouseOut={() => changeShow(false)} >
+
+ {!hasSubMenu &&
+
+ {link?.name}
+ {hasSubMenu &&
}
+ }
+
+ {hasSubMenu && <>
+
+ {link?.name}
+
+
+ >}
+
+ {/* 子菜单 */}
+ {hasSubMenu &&
+ {link.subMenus.map(sLink => {
+ return -
+
+ {sLink.title}
+
+
+ })}
+
}
+
+
+}
diff --git a/themes/simple/components/ExampleRecentComments.js b/themes/simple/components/ExampleRecentComments.js
new file mode 100644
index 00000000..b1555c1d
--- /dev/null
+++ b/themes/simple/components/ExampleRecentComments.js
@@ -0,0 +1,35 @@
+import React from 'react'
+import BLOG from '@/blog.config'
+import Link from 'next/link'
+import { RecentComments } from '@waline/client'
+
+/**
+ * @see https://waline.js.org/guide/get-started.html
+ * @param {*} props
+ * @returns
+ */
+const ExampleRecentComments = (props) => {
+ const [comments, updateComments] = React.useState([])
+ const [onLoading, changeLoading] = React.useState(true)
+ React.useEffect(() => {
+ RecentComments({
+ serverURL: BLOG.COMMENT_WALINE_SERVER_URL,
+ count: 5
+ }).then(({ comments }) => {
+ changeLoading(false)
+ updateComments(comments)
+ })
+ }, [])
+
+ return <>
+ {onLoading &&
Loading...
}
+ {!onLoading && comments && comments.length === 0 &&
No Comments
}
+ {!onLoading && comments && comments.length > 0 && comments.map((comment) =>
)}
+
+ >
+}
+
+export default ExampleRecentComments
diff --git a/themes/simple/components/Footer.js b/themes/simple/components/Footer.js
new file mode 100644
index 00000000..f92a43e5
--- /dev/null
+++ b/themes/simple/components/Footer.js
@@ -0,0 +1,30 @@
+import BLOG from '@/blog.config'
+import DarkModeButton from '@/components/DarkModeButton'
+
+export const Footer = (props) => {
+ const d = new Date()
+ const currentYear = d.getFullYear()
+ const copyrightDate = (function() {
+ if (Number.isInteger(BLOG.SINCE) && BLOG.SINCE < currentYear) {
+ return BLOG.SINCE + '-' + currentYear
+ }
+ return currentYear
+ })()
+
+ return
+}
diff --git a/themes/simple/components/Header.js b/themes/simple/components/Header.js
new file mode 100644
index 00000000..16104a56
--- /dev/null
+++ b/themes/simple/components/Header.js
@@ -0,0 +1,37 @@
+import BLOG from '@/blog.config'
+import Link from 'next/link'
+import CONFIG_SIMPLE from '../config_simple'
+// import CONFIG_SIMPLE from '../config_simple'
+
+/**
+ * 网站顶部
+ * @returns
+ */
+export const Header = (props) => {
+ const { siteInfo } = props
+
+ return (
+
+ )
+}
diff --git a/themes/simple/components/JumpToTopButton.js b/themes/simple/components/JumpToTopButton.js
new file mode 100644
index 00000000..2ed28ab4
--- /dev/null
+++ b/themes/simple/components/JumpToTopButton.js
@@ -0,0 +1,35 @@
+import { useGlobal } from '@/lib/global'
+import React from 'react'
+
+/**
+ * 跳转到网页顶部
+ * 当屏幕下滑500像素后会出现该控件
+ * @param targetRef 关联高度的目标html标签
+ * @param showPercent 是否显示百分比
+ * @returns {JSX.Element}
+ * @constructor
+ */
+const JumpToTopButton = () => {
+ const { locale } = useGlobal()
+ const [show, switchShow] = React.useState(false)
+ const scrollListener = () => {
+ const scrollY = window.pageYOffset
+ const shouldShow = scrollY > 200
+ if (shouldShow !== show) {
+ switchShow(shouldShow)
+ }
+ }
+
+ React.useEffect(() => {
+ document.addEventListener('scroll', scrollListener)
+ return () => document.removeEventListener('scroll', scrollListener)
+ }, [show])
+
+ return
window.scrollTo({ top: 0, behavior: 'smooth' })}
+ >
+
+}
+
+export default JumpToTopButton
diff --git a/themes/simple/components/NavBar.js b/themes/simple/components/NavBar.js
new file mode 100644
index 00000000..46aeb7af
--- /dev/null
+++ b/themes/simple/components/NavBar.js
@@ -0,0 +1,66 @@
+import BLOG from '@/blog.config'
+import { useGlobal } from '@/lib/global'
+import { useRouter } from 'next/router'
+import { useState } from 'react'
+import CONFIG_SIMPLE from '../config_simple'
+import { NavBarMenu } from './NavBarMenu'
+
+/**
+ * 菜单导航
+ * @param {*} props
+ * @returns
+ */
+export const NavBar = (props) => {
+ const { customNav, customMenu } = props
+ const { locale } = useGlobal()
+ const [showSearchInput, changeShowSearchInput] = useState(false)
+ const router = useRouter()
+
+ const toggleShowSearchInput = () => {
+ changeShowSearchInput(!showSearchInput)
+ }
+
+ const onKeyUp = (e) => {
+ if (e.keyCode === 13) {
+ const search = document.getElementById('theme-simple-search').value
+ if (search) {
+ router.push({ pathname: '/search/' + search })
+ }
+ }
+ }
+
+ let links = [
+ { icon: 'fas fa-search', name: locale.NAV.SEARCH, to: '/search', show: CONFIG_SIMPLE.MENU_SEARCH },
+ { icon: 'fas fa-archive', name: locale.NAV.ARCHIVE, to: '/archive', show: CONFIG_SIMPLE.MENU_ARCHIVE },
+ { icon: 'fas fa-folder', name: locale.COMMON.CATEGORY, to: '/category', show: CONFIG_SIMPLE.MENU_CATEGORY },
+ { icon: 'fas fa-tag', name: locale.COMMON.TAGS, to: '/tag', show: CONFIG_SIMPLE.MENU_TAG }
+ ]
+
+ if (customNav) {
+ links = links.concat(customNav)
+ }
+
+ if (BLOG.CUSTOM_MENU) {
+ links = customMenu
+ }
+ if (!links || links.length === 0) {
+ return null
+ }
+
+ return (
+
+ )
+}
diff --git a/themes/simple/components/NavBarMenu.js b/themes/simple/components/NavBarMenu.js
new file mode 100644
index 00000000..d4436c6e
--- /dev/null
+++ b/themes/simple/components/NavBarMenu.js
@@ -0,0 +1,78 @@
+import BLOG from '@/blog.config'
+import Collapse from '@/components/Collapse'
+import { useGlobal } from '@/lib/global'
+import { useRouter } from 'next/router'
+import { useEffect, useState } from 'react'
+import CONFIG_SIMPLE from '../config_simple'
+import { CollapseMenu } from './CollapseMenu'
+import { DropMenu } from './DropMenu'
+
+/**
+ * 菜单导航
+ * @param {*} props
+ * @returns
+ */
+export const NavBarMenu = ({ customNav, customMenu }) => {
+ const { locale } = useGlobal()
+ const [isOpen, changeIsOpen] = useState(false)
+ const toggleIsOpen = () => {
+ changeIsOpen(!isOpen)
+ }
+ const closeMenu = (e) => {
+ changeIsOpen(false)
+ }
+ const router = useRouter()
+ useEffect(() => {
+ router.events.on('routeChangeComplete', closeMenu)
+ })
+
+ let links = [
+ { icon: 'fas fa-search', name: locale.NAV.SEARCH, to: '/search', show: CONFIG_SIMPLE.MENU_SEARCH },
+ { icon: 'fas fa-archive', name: locale.NAV.ARCHIVE, to: '/archive', show: CONFIG_SIMPLE.MENU_ARCHIVE },
+ { icon: 'fas fa-folder', name: locale.COMMON.CATEGORY, to: '/category', show: CONFIG_SIMPLE.MENU_CATEGORY },
+ { icon: 'fas fa-tag', name: locale.COMMON.TAGS, to: '/tag', show: CONFIG_SIMPLE.MENU_TAG }
+ ]
+
+ if (customNav) {
+ links = links.concat(customNav)
+ }
+
+ if (BLOG.CUSTOM_MENU) {
+ links = customMenu
+ }
+ if (!links || links.length === 0) {
+ return null
+ }
+
+ return (<>
+
+
+ >
+
+ )
+}
diff --git a/themes/simple/components/SideBar.js b/themes/simple/components/SideBar.js
new file mode 100644
index 00000000..04c496ca
--- /dev/null
+++ b/themes/simple/components/SideBar.js
@@ -0,0 +1,19 @@
+import Live2D from '@/components/Live2D'
+import Announcement from './Announcement'
+
+export const SideBar = (props) => {
+ const { notice } = props
+ return (
+
+ )
+}
diff --git a/themes/simple/components/SocialButton.js b/themes/simple/components/SocialButton.js
new file mode 100644
index 00000000..b60e7a50
--- /dev/null
+++ b/themes/simple/components/SocialButton.js
@@ -0,0 +1,36 @@
+import BLOG from '@/blog.config'
+import React from 'react'
+
+/**
+ * 社交联系方式按钮组
+ * @returns {JSX.Element}
+ * @constructor
+ */
+const SocialButton = () => {
+ return
+
+ {BLOG.CONTACT_GITHUB &&
+
+ }
+ {BLOG.CONTACT_TWITTER &&
+
+ }
+ {BLOG.CONTACT_TELEGRAM &&
+
+ }
+ {BLOG.CONTACT_LINKEDIN &&
+
+ }
+ {BLOG.CONTACT_WEIBO &&
+
+ }
+ {BLOG.CONTACT_EMAIL &&
+
+ }
+
+
+
+
+
+}
+export default SocialButton
diff --git a/themes/simple/components/Title.js b/themes/simple/components/Title.js
new file mode 100644
index 00000000..e57e2347
--- /dev/null
+++ b/themes/simple/components/Title.js
@@ -0,0 +1,19 @@
+import BLOG from '@/blog.config'
+
+/**
+ * 标题栏
+ * @param {*} props
+ * @returns
+ */
+export const Title = (props) => {
+ const { siteInfo, post } = props
+ const title = post?.title || siteInfo?.description
+ const description = post?.description || BLOG.AUTHOR
+
+ return
+
{title}
+
+ {description}
+
+
+}
diff --git a/themes/simple/components/TopBar.js b/themes/simple/components/TopBar.js
new file mode 100644
index 00000000..ef18fd75
--- /dev/null
+++ b/themes/simple/components/TopBar.js
@@ -0,0 +1,16 @@
+import CONFIG_SIMPLE from '../config_simple'
+
+/**
+ * 网站顶部 提示栏
+ * @returns
+ */
+export const TopBar = (props) => {
+ if (CONFIG_SIMPLE.TOP_BAR_CONTENT) {
+ return
+ }
+ return <>>
+}
diff --git a/themes/simple/config_simple.js b/themes/simple/config_simple.js
new file mode 100644
index 00000000..068daf36
--- /dev/null
+++ b/themes/simple/config_simple.js
@@ -0,0 +1,16 @@
+const CONFIG_SIMPLE = {
+
+ LOGO_IMG: '/Logo.webp',
+ TOP_BAR: true, // 显示顶栏
+ TOP_BAR_CONTENT: process.env.NEXT_PUBLIC_THEME_SIMPLE_TOP_TIPS || '',
+ LOGO_DESCRIPTION: process.env.NEXT_PUBLIC_THEME_SIMPLE_LOGO_DESCRIPTION || '
编程爱好者
/互联网从业者
/知识分享博主
',
+
+ AUTHOR_LINK: process.env.NEXT_PUBLIC_AUTHOR_LINK || '#',
+
+ // 菜单配置
+ MENU_CATEGORY: true, // 显示分类
+ MENU_TAG: true, // 显示标签
+ MENU_ARCHIVE: true, // 显示归档
+ MENU_SEARCH: true // 显示搜索
+}
+export default CONFIG_SIMPLE
diff --git a/themes/simple/index.js b/themes/simple/index.js
new file mode 100644
index 00000000..7e560470
--- /dev/null
+++ b/themes/simple/index.js
@@ -0,0 +1,25 @@
+import CONFIG_SIMPLE from './config_simple'
+import { LayoutIndex } from './LayoutIndex'
+import { LayoutSearch } from './LayoutSearch'
+import { LayoutArchive } from './LayoutArchive'
+import { LayoutSlug } from './LayoutSlug'
+import { Layout404 } from './Layout404'
+import { LayoutCategory } from './LayoutCategory'
+import { LayoutCategoryIndex } from './LayoutCategoryIndex'
+import { LayoutPage } from './LayoutPage'
+import { LayoutTag } from './LayoutTag'
+import { LayoutTagIndex } from './LayoutTagIndex'
+
+export {
+ CONFIG_SIMPLE as THEME_CONFIG,
+ LayoutIndex,
+ LayoutSearch,
+ LayoutArchive,
+ LayoutSlug,
+ Layout404,
+ LayoutCategory,
+ LayoutCategoryIndex,
+ LayoutPage,
+ LayoutTag,
+ LayoutTagIndex
+}