diff --git a/components/Vercel.js b/components/Vercel.js
new file mode 100644
index 00000000..c7013fe1
--- /dev/null
+++ b/components/Vercel.js
@@ -0,0 +1,42 @@
+const Vercel = () => {
+ return (
+
+
+
+ )
+}
+
+export default Vercel
diff --git a/styles/globals.css b/styles/globals.css
index f2f180e8..d7b7ed75 100644
--- a/styles/globals.css
+++ b/styles/globals.css
@@ -197,4 +197,12 @@ nav {
-webkit-box-orient: vertical;
word-wrap: break-word;
word-break: break-all;
-}
\ No newline at end of file
+}
+
+.nobelium{
+ @apply flex flex-col justify-between
+}
+
+.nobelium .notion-code{
+ @apply max-w-2xl;
+}
diff --git a/themes/index.js b/themes/index.js
index e13cd4b3..f8540101 100644
--- a/themes/index.js
+++ b/themes/index.js
@@ -5,7 +5,8 @@ import * as next from './next'
import * as fukasawa from './fukasawa'
import * as hexo from './hexo'
import * as medium from './medium'
+import * as nobelium from './nobelium'
import * as example from './example'
-export const ALL_THEME = ['hexo', 'next', 'medium', 'fukasawa', 'example']
-export { hexo, next, medium, fukasawa, example }
+export const ALL_THEME = ['hexo', 'next', 'medium', 'fukasawa', 'nobelium', 'example']
+export { hexo, next, medium, fukasawa, nobelium, example }
diff --git a/themes/nobelium/Layout404.js b/themes/nobelium/Layout404.js
new file mode 100644
index 00000000..5f92f0cc
--- /dev/null
+++ b/themes/nobelium/Layout404.js
@@ -0,0 +1,7 @@
+import LayoutBase from './LayoutBase'
+
+export const Layout404 = (props) => {
+ return
+ 404 Not found.
+
+}
diff --git a/themes/nobelium/LayoutArchive.js b/themes/nobelium/LayoutArchive.js
new file mode 100644
index 00000000..b85860e0
--- /dev/null
+++ b/themes/nobelium/LayoutArchive.js
@@ -0,0 +1,62 @@
+import BLOG from '@/blog.config'
+import Link from 'next/link'
+import LayoutBase from './LayoutBase'
+
+export const LayoutArchive = props => {
+ const { posts } = props
+ const postsSortByDate = Object.create(posts)
+
+ postsSortByDate.sort((a, b) => {
+ const dateA = new Date(a?.date?.start_date || a.createdTime)
+ const dateB = new Date(b?.date?.start_date || b.createdTime)
+ return dateB - dateA
+ })
+
+ const archivePosts = {}
+
+ postsSortByDate.forEach(post => {
+ const date = post.date?.start_date.slice(0, 7)
+ if (archivePosts[date]) {
+ archivePosts[date].push(post)
+ } else {
+ archivePosts[date] = [post]
+ }
+ })
+ return (
+
+
+ {Object.keys(archivePosts).map(archiveTitle => (
+
+
+ {archiveTitle}
+
+
+
+ {archivePosts[archiveTitle].map(post => (
+ -
+
+
+ {post.date?.start_date}
+ {' '}
+
+
+
+ {post.title}
+
+
+
+
+ ))}
+
+
+ ))}
+
+
+ )
+}
diff --git a/themes/nobelium/LayoutBase.js b/themes/nobelium/LayoutBase.js
new file mode 100644
index 00000000..245705b0
--- /dev/null
+++ b/themes/nobelium/LayoutBase.js
@@ -0,0 +1,41 @@
+import CommonHead from '@/components/CommonHead'
+import React from 'react'
+import Header from './components/Header'
+import { Footer } from './components/Footer'
+import JumpToTopButton from './components/JumpToTopButton'
+/**
+ * 基础布局 采用左右两侧布局,移动端使用顶部导航栏
+
+ * @returns {JSX.Element}
+ * @constructor
+ */
+const LayoutBase = props => {
+ const { children, meta, post } = props
+
+ const fullWidth = post?.fullWidth ?? false
+
+ return (
+
+
+
+ {/* 顶栏LOGO */}
+
+
+
+
+ {children}
+
+
+
+
+
+
+
+
+
+ )
+}
+
+export default LayoutBase
diff --git a/themes/nobelium/LayoutCategory.js b/themes/nobelium/LayoutCategory.js
new file mode 100644
index 00000000..6d819185
--- /dev/null
+++ b/themes/nobelium/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/nobelium/LayoutCategoryIndex.js b/themes/nobelium/LayoutCategoryIndex.js
new file mode 100644
index 00000000..28b98b6e
--- /dev/null
+++ b/themes/nobelium/LayoutCategoryIndex.js
@@ -0,0 +1,19 @@
+import Link from 'next/link'
+import LayoutBase from './LayoutBase'
+
+export const LayoutCategoryIndex = (props) => {
+ const { categories } = props
+
+ return
+
+ {categories && categories.map(category => {
+ return
+
+ {category.name}({category.count})
+
+
+ })}
+
+
+}
diff --git a/themes/nobelium/LayoutIndex.js b/themes/nobelium/LayoutIndex.js
new file mode 100644
index 00000000..94eaac2f
--- /dev/null
+++ b/themes/nobelium/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/nobelium/LayoutPage.js b/themes/nobelium/LayoutPage.js
new file mode 100644
index 00000000..15355018
--- /dev/null
+++ b/themes/nobelium/LayoutPage.js
@@ -0,0 +1,10 @@
+import { BlogListPage } from './components/BlogListPage'
+import LayoutBase from './LayoutBase'
+
+export const LayoutPage = props => {
+ return (
+
+
+
+ )
+}
diff --git a/themes/nobelium/LayoutSearch.js b/themes/nobelium/LayoutSearch.js
new file mode 100644
index 00000000..23b10b7a
--- /dev/null
+++ b/themes/nobelium/LayoutSearch.js
@@ -0,0 +1,54 @@
+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 SearchInput from './components/SearchInput'
+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/nobelium/LayoutSlug.js b/themes/nobelium/LayoutSlug.js
new file mode 100644
index 00000000..d60062a0
--- /dev/null
+++ b/themes/nobelium/LayoutSlug.js
@@ -0,0 +1,31 @@
+import LayoutBase from './LayoutBase'
+import { ArticleLock } from './components/ArticleLock'
+import NotionPage from '@/components/NotionPage'
+import { ArticleInfo } from './components/ArticleInfo'
+import Comment from '@/components/Comment'
+import { ArticleFooter } from './components/ArticleFooter'
+
+export const LayoutSlug = props => {
+ const { post, lock, validPassword } = props
+
+ if (!post) {
+ return
+ }
+
+ return (
+
+
+ {lock && }
+
+ {!lock &&
+ {post && <>
+
+
+
+
+ >}
+ }
+
+
+ )
+}
diff --git a/themes/nobelium/LayoutTag.js b/themes/nobelium/LayoutTag.js
new file mode 100644
index 00000000..2f0ef119
--- /dev/null
+++ b/themes/nobelium/LayoutTag.js
@@ -0,0 +1,57 @@
+import React from 'react'
+import BlogPost from './components/BlogPost'
+import Tags from './components/Tags'
+import LayoutBase from './LayoutBase'
+
+export const LayoutTag = props => {
+ const { currentTag } = props
+ const [searchValue, setSearchValue] = React.useState('')
+ let filteredBlogPosts = []
+ const { posts } = props
+
+ if (posts) {
+ filteredBlogPosts = posts.filter(post => {
+ const tagContent = post.tags ? post.tags.join(' ') : ''
+ const searchContent = post.title + post.summary + tagContent
+ return searchContent.toLowerCase().includes(searchValue.toLowerCase())
+ })
+ }
+
+ return
+
+
+
setSearchValue(e.target.value)}
+ />
+
+
+
+
+
+ {!filteredBlogPosts.length && (
+
No posts found.
+ )}
+ {filteredBlogPosts.slice(0, 20).map(post => (
+
+ ))}
+
+
+}
diff --git a/themes/nobelium/LayoutTagIndex.js b/themes/nobelium/LayoutTagIndex.js
new file mode 100644
index 00000000..6a89e168
--- /dev/null
+++ b/themes/nobelium/LayoutTagIndex.js
@@ -0,0 +1,21 @@
+import Link from 'next/link'
+import LayoutBase from './LayoutBase'
+
+export const LayoutTagIndex = (props) => {
+ const { tags } = props
+ return
+
+}
diff --git a/themes/nobelium/components/ArticleFooter.js b/themes/nobelium/components/ArticleFooter.js
new file mode 100644
index 00000000..87cf7baf
--- /dev/null
+++ b/themes/nobelium/components/ArticleFooter.js
@@ -0,0 +1,34 @@
+import BLOG from '@/blog.config'
+import { useRouter } from 'next/router'
+import { useGlobal } from '@/lib/global'
+
+/**
+ * 加密文章校验组件
+ * @param {password, validPassword} props
+ * @param password 正确的密码
+ * @param validPassword(bool) 回调函数,校验正确回调入参为true
+ * @returns
+ */
+export const ArticleFooter = props => {
+ const router = useRouter()
+ const { locale } = useGlobal()
+
+ return
+}
diff --git a/themes/nobelium/components/ArticleInfo.js b/themes/nobelium/components/ArticleInfo.js
new file mode 100644
index 00000000..64c1cf8c
--- /dev/null
+++ b/themes/nobelium/components/ArticleInfo.js
@@ -0,0 +1,63 @@
+
+import formatDate from '@/lib/formatDate'
+import Image from 'next/image'
+import BLOG from '@/blog.config'
+import TagItem from './TagItem'
+import { createHash } from 'crypto'
+
+export const ArticleInfo = (props) => {
+ const { post } = props
+
+ const emailHash = createHash('md5')
+ .update(BLOG.CONTACT_EMAIL)
+ .digest('hex')
+ .trim()
+ .toLowerCase()
+
+ return
+
+
+
+ {post.title}
+
+
+ {post?.type !== 'Page' && <>
+
+ >}
+
+
+
+
+}
diff --git a/themes/nobelium/components/ArticleLock.js b/themes/nobelium/components/ArticleLock.js
new file mode 100644
index 00000000..43b6647e
--- /dev/null
+++ b/themes/nobelium/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/nobelium/components/BlogListPage.js b/themes/nobelium/components/BlogListPage.js
new file mode 100644
index 00000000..266d14e3
--- /dev/null
+++ b/themes/nobelium/components/BlogListPage.js
@@ -0,0 +1,44 @@
+
+import BLOG from '@/blog.config'
+import { useGlobal } from '@/lib/global'
+import { useRouter } from 'next/router'
+import Link from 'next/link'
+import BlogPost from './BlogPost'
+
+export const BlogListPage = props => {
+ const { page = 1, posts, postCount } = props
+ const { locale } = useGlobal()
+ 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(post => (
+
+ ))}
+
+
+
+
+}
diff --git a/themes/nobelium/components/BlogListScroll.js b/themes/nobelium/components/BlogListScroll.js
new file mode 100644
index 00000000..c9859c0b
--- /dev/null
+++ b/themes/nobelium/components/BlogListScroll.js
@@ -0,0 +1,78 @@
+import BLOG from '@/blog.config'
+import { useGlobal } from '@/lib/global'
+import Link from 'next/link'
+import React from 'react'
+import throttle from 'lodash.throttle'
+
+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 => (
+
+
+
+
+
+
+ {p.summary}
+
+
+ ))}
+
+
+ {' '}
+ {hasMore ? locale.COMMON.MORE : `${locale.COMMON.NO_MORE} 😰`}{' '}
+
+
+
+}
diff --git a/themes/nobelium/components/BlogPost.js b/themes/nobelium/components/BlogPost.js
new file mode 100644
index 00000000..b0a99330
--- /dev/null
+++ b/themes/nobelium/components/BlogPost.js
@@ -0,0 +1,29 @@
+import Link from 'next/link'
+import BLOG from '@/blog.config'
+import formatDate from '@/lib/formatDate'
+
+const BlogPost = ({ post }) => {
+ return (
+
+
+
+
+
+ {post.title}
+
+
+
+
+
+ {post.summary}
+
+
+
+
+
+ )
+}
+
+export default BlogPost
diff --git a/themes/nobelium/components/ExampleRecentComments.js b/themes/nobelium/components/ExampleRecentComments.js
new file mode 100644
index 00000000..e1fe86ca
--- /dev/null
+++ b/themes/nobelium/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/nobelium/components/Footer.js b/themes/nobelium/components/Footer.js
new file mode 100644
index 00000000..131a29a6
--- /dev/null
+++ b/themes/nobelium/components/Footer.js
@@ -0,0 +1,34 @@
+import BLOG from '@/blog.config'
+import DarkModeButton from '@/components/DarkModeButton'
+import Vercel from '@/components/Vercel'
+
+export const Footer = (props) => {
+ const d = new Date()
+ const currentYear = d.getFullYear()
+ const { post } = props
+ const fullWidth = post?.fullWidth ?? false
+
+ const copyrightDate = (function() {
+ if (Number.isInteger(BLOG.SINCE) && BLOG.SINCE < currentYear) {
+ return BLOG.SINCE + '-' + currentYear
+ }
+ return currentYear
+ })()
+
+ return
+}
diff --git a/themes/nobelium/components/Header.js b/themes/nobelium/components/Header.js
new file mode 100644
index 00000000..8d4df936
--- /dev/null
+++ b/themes/nobelium/components/Header.js
@@ -0,0 +1,128 @@
+import { useEffect, useRef } from 'react'
+import Link from 'next/link'
+import BLOG from '@/blog.config'
+import { useGlobal } from '@/lib/global'
+
+const NavBar = (props) => {
+ const { customNav } = props
+
+ const { locale } = useGlobal()
+ let links = [
+ { id: 2, name: locale.NAV.RSS, to: '/feed', show: true },
+ { icon: 'fas fa-search', name: locale.NAV.SEARCH, to: '/search', show: true },
+ { icon: 'fas fa-archive', name: locale.NAV.ARCHIVE, to: '/archive', show: true },
+ { icon: 'fas fa-folder', name: locale.COMMON.CATEGORY, to: '/category', show: false },
+ { icon: 'fas fa-tag', name: locale.COMMON.TAGS, to: '/tag', show: true }
+ ]
+ if (customNav) {
+ links = links.concat(customNav)
+ }
+ return (
+
+
+ {links.map(
+ link =>
+ link.show && (
+ -
+
+ {link.name}
+
+
+ )
+ )}
+
+
+ )
+}
+
+const Header = ({ navBarTitle, fullWidth }) => {
+ const useSticky = !BLOG.autoCollapsedNavBar
+ const navRef = useRef(null)
+ const sentinalRef = useRef([])
+ const handler = ([entry]) => {
+ if (navRef && navRef.current && useSticky) {
+ if (!entry.isIntersecting && entry !== undefined) {
+ navRef.current?.classList.add('sticky-nav-full')
+ } else {
+ navRef.current?.classList.remove('sticky-nav-full')
+ }
+ } else {
+ navRef.current?.classList.add('remove-sticky')
+ }
+ }
+ useEffect(() => {
+ const obvserver = new window.IntersectionObserver(handler)
+ obvserver.observe(sentinalRef.current)
+ // Don't touch this, I have no idea how it works XD
+ // return () => {
+ // if (sentinalRef.current) obvserver.unobserve(sentinalRef.current)
+ // }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [sentinalRef])
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+ {navBarTitle
+ ? (
+
+ {navBarTitle}
+
+ )
+ : (
+
+ {BLOG.title},{' '}
+ {BLOG.description}
+
+ )}
+
+
+
+ >
+ )
+}
+
+export default Header
diff --git a/themes/nobelium/components/JumpToTopButton.js b/themes/nobelium/components/JumpToTopButton.js
new file mode 100644
index 00000000..30e684a8
--- /dev/null
+++ b/themes/nobelium/components/JumpToTopButton.js
@@ -0,0 +1,19 @@
+import { useGlobal } from '@/lib/global'
+import React from 'react'
+
+/**
+ * 跳转到网页顶部
+ * 当屏幕下滑500像素后会出现该控件
+ * @param targetRef 关联高度的目标html标签
+ * @param showPercent 是否显示百分比
+ * @returns {JSX.Element}
+ * @constructor
+ */
+const JumpToTopButton = () => {
+ const { locale } = useGlobal()
+ return window.scrollTo({ top: 0, behavior: 'smooth' })}
+ >
+
+}
+
+export default JumpToTopButton
diff --git a/themes/nobelium/components/Nav.js b/themes/nobelium/components/Nav.js
new file mode 100644
index 00000000..26610035
--- /dev/null
+++ b/themes/nobelium/components/Nav.js
@@ -0,0 +1,39 @@
+import { useGlobal } from '@/lib/global'
+import Link from 'next/link'
+
+/**
+ * 菜单导航
+ * @param {*} props
+ * @returns
+ */
+export const Nav = (props) => {
+ const { customNav } = props
+ const { locale } = useGlobal()
+ let links = [
+ { icon: 'fas fa-search', name: locale.NAV.SEARCH, to: '/search' },
+ { icon: 'fas fa-archive', name: locale.NAV.ARCHIVE, to: '/archive' },
+ { icon: 'fas fa-folder', name: locale.COMMON.CATEGORY, to: '/category' },
+ { icon: 'fas fa-tag', name: locale.COMMON.TAGS, to: '/tag' }
+ ]
+
+ if (customNav) {
+ links = links.concat(customNav)
+ }
+
+ return
+}
diff --git a/themes/nobelium/components/SearchInput.js b/themes/nobelium/components/SearchInput.js
new file mode 100644
index 00000000..6a95ba01
--- /dev/null
+++ b/themes/nobelium/components/SearchInput.js
@@ -0,0 +1,87 @@
+import { useRouter } from 'next/router'
+import { useGlobal } from '@/lib/global'
+import { useImperativeHandle, useRef, useState } from 'react'
+
+let lock = false
+
+const SearchInput = ({ currentTag, currentSearch, cRef }) => {
+ const { locale } = useGlobal()
+ const router = useRouter()
+ const searchInputRef = useRef(null)
+ useImperativeHandle(cRef, () => {
+ return {
+ focus: () => {
+ searchInputRef?.current?.focus()
+ }
+ }
+ })
+ const handleSearch = () => {
+ const key = searchInputRef.current.value
+ if (key && key !== '') {
+ router.push({ pathname: '/search/' + key }).then(r => {
+ console.log('搜索', key)
+ })
+ } 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 = ''
+ setShowClean(false)
+ }
+ function lockSearchInput () {
+ lock = true
+ }
+
+ function unLockSearchInput () {
+ lock = false
+ }
+ const [showClean, setShowClean] = useState(false)
+ const updateSearchKey = (val) => {
+ if (lock) {
+ return
+ }
+ searchInputRef.current.value = val
+ if (val) {
+ setShowClean(true)
+ } else {
+ setShowClean(false)
+ }
+ }
+
+ return
+}
+
+export default SearchInput
diff --git a/themes/nobelium/components/SideBar.js b/themes/nobelium/components/SideBar.js
new file mode 100644
index 00000000..ea45f851
--- /dev/null
+++ b/themes/nobelium/components/SideBar.js
@@ -0,0 +1,55 @@
+import BLOG from '@/blog.config'
+import Live2D from '@/components/Live2D'
+import { useGlobal } from '@/lib/global'
+import Link from 'next/link'
+import dynamic from 'next/dynamic'
+const ExampleRecentComments = dynamic(() => import('./ExampleRecentComments'))
+
+export const SideBar = (props) => {
+ const { locale } = useGlobal()
+ const { latestPosts, categories } = props
+ return
+
+
+
+
+
+ {BLOG.COMMENT_WALINE_SERVER_URL && BLOG.COMMENT_WALINE_RECENT &&
}
+
+
+
+
+}
diff --git a/themes/nobelium/components/TagItem.js b/themes/nobelium/components/TagItem.js
new file mode 100644
index 00000000..6841f911
--- /dev/null
+++ b/themes/nobelium/components/TagItem.js
@@ -0,0 +1,13 @@
+import Link from 'next/link'
+
+const TagItem = ({ tag }) => (
+
+
+
+ {tag}
+
+
+
+)
+
+export default TagItem
diff --git a/themes/nobelium/components/Tags.js b/themes/nobelium/components/Tags.js
new file mode 100644
index 00000000..4555bfae
--- /dev/null
+++ b/themes/nobelium/components/Tags.js
@@ -0,0 +1,38 @@
+import Link from 'next/link'
+
+const Tags = (props) => {
+ const { tags, tag } = props
+ const currentTag = tag
+ if (!tags) return null
+ return (
+
+ )
+}
+
+export default Tags
diff --git a/themes/nobelium/components/Title.js b/themes/nobelium/components/Title.js
new file mode 100644
index 00000000..e57e2347
--- /dev/null
+++ b/themes/nobelium/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/nobelium/config_nobelium.js b/themes/nobelium/config_nobelium.js
new file mode 100644
index 00000000..9f1ac567
--- /dev/null
+++ b/themes/nobelium/config_nobelium.js
@@ -0,0 +1,4 @@
+const CONFIG_EMPTY = {
+ TEST_CONFIG: 'TESET'
+}
+export default CONFIG_EMPTY
diff --git a/themes/nobelium/index.js b/themes/nobelium/index.js
new file mode 100644
index 00000000..4e36cdf9
--- /dev/null
+++ b/themes/nobelium/index.js
@@ -0,0 +1,25 @@
+import CONFIG_EMPTY from './config_nobelium'
+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_EMPTY as THEME_CONFIG,
+ LayoutIndex,
+ LayoutSearch,
+ LayoutArchive,
+ LayoutSlug,
+ Layout404,
+ LayoutCategory,
+ LayoutCategoryIndex,
+ LayoutPage,
+ LayoutTag,
+ LayoutTagIndex
+}