mirror of
https://github.com/d0zingcat/NotionNext.git
synced 2026-05-14 07:26:52 +00:00
Merge pull request #827 from tangly1024/feat/theme-simple
Feat/theme simple
This commit is contained in:
@@ -1,2 +1,2 @@
|
||||
# 环境变量 @see https://www.nextjs.cn/docs/basic-features/environment-variables
|
||||
NEXT_PUBLIC_VERSION=3.11.1
|
||||
NEXT_PUBLIC_VERSION=3.12.0"
|
||||
|
||||
@@ -10,6 +10,8 @@ const BLOG = {
|
||||
SINCE: 2021, // e.g if leave this empty, current year will be used.
|
||||
APPEARANCE: process.env.NEXT_PUBLIC_APPEARANCE || 'light', // ['light', 'dark', 'auto'], // light 日间模式 , dark夜间模式, auto根据时间和主题自动夜间模式
|
||||
|
||||
CUSTOM_MENU: process.env.NEXT_PUBLIC_CUSTOM_MENU || false, // 支持Menu 类型,从3.12.0版本起,各主题将逐步支持灵活的二级菜单配置,替代了原来的Page类型,此配置是试验功能、默认关闭。
|
||||
|
||||
AUTHOR: process.env.NEXT_PUBLIC_AUTHOR || 'NotionNext', // 您的昵称 例如 tangly1024
|
||||
BIO: process.env.NEXT_PUBLIC_BIO || '一个普通的干饭人🍚', // 作者简介
|
||||
LINK: process.env.NEXT_PUBLIC_LINK || 'https://tangly1024.com', // 网站地址
|
||||
@@ -198,6 +200,8 @@ const BLOG = {
|
||||
type_post: process.env.NEXT_PUBLIC_NOTION_PROPERTY_TYPE_POST || 'Post', // 当type文章类型与此值相同时,为博文。
|
||||
type_page: process.env.NEXT_PUBLIC_NOTION_PROPERTY_TYPE_PAGE || 'Page', // 当type文章类型与此值相同时,为单页。
|
||||
type_notice: process.env.NEXT_PUBLIC_NOTION_PROPERTY_TYPE_NOTICE || 'Notice', // 当type文章类型与此值相同时,为公告。
|
||||
type_menu: process.env.NEXT_PUBLIC_NOTION_PROPERTY_TYPE_MENU || 'Menu', // 当type文章类型与此值相同时,为菜单。
|
||||
type_sub_menu: process.env.NEXT_PUBLIC_NOTION_PROPERTY_TYPE_SUB_MENU || 'SubMenu', // 当type文章类型与此值相同时,为子菜单。
|
||||
title: process.env.NEXT_PUBLIC_NOTION_PROPERTY_TITLE || 'title', // 文章标题
|
||||
status: process.env.NEXT_PUBLIC_NOTION_PROPERTY_STATUS || 'status',
|
||||
status_publish: process.env.NEXT_PUBLIC_NOTION_PROPERTY_STATUS_PUBLISH || 'Published', // 当status状态值与此相同时为发布,可以为中文
|
||||
|
||||
@@ -8,6 +8,10 @@ import React from 'react'
|
||||
const Collapse = props => {
|
||||
const collapseRef = React.useRef(null)
|
||||
const type = props.type || 'vertical'
|
||||
/**
|
||||
* 折叠
|
||||
* @param {*} element
|
||||
*/
|
||||
const collapseSection = element => {
|
||||
const sectionHeight = element.scrollHeight
|
||||
const sectionWidth = element.scrollWidth
|
||||
@@ -54,17 +58,20 @@ const Collapse = props => {
|
||||
clearTimeout(clearTime)
|
||||
}
|
||||
|
||||
const updateHeight = () => {
|
||||
collapseRef.current.style.height = 'auto'
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
const element = collapseRef.current
|
||||
if (props.isOpen) {
|
||||
expandSection(element)
|
||||
expandSection(collapseRef.current)
|
||||
} else {
|
||||
collapseSection(element)
|
||||
collapseSection(collapseRef.current)
|
||||
}
|
||||
}, [props.isOpen])
|
||||
|
||||
return (
|
||||
<div ref={collapseRef} style={type === 'vertical' ? { height: '0px' } : { width: '0px' }} className={'overflow-hidden duration-200 ' + props.className }>
|
||||
<div ref={collapseRef} onClick={updateHeight} style={type === 'vertical' ? { height: '0px' } : { width: '0px' }} className={'overflow-hidden duration-200 ' + props.className }>
|
||||
{props.children}
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -13,7 +13,7 @@ const DarkModeButton = (props) => {
|
||||
htmlElement.classList?.add(newStatus ? 'dark' : 'light')
|
||||
}
|
||||
|
||||
return <div className={'z-10 duration-200 text-xl py-2 ' + props.className}>
|
||||
return <div className={'text-white z-10 duration-200 text-xl py-2 ' + props.className}>
|
||||
<i id='darkModeButton' className={`hover:scale-125 cursor-pointer transform duration-200 fas ${isDarkMode ? 'fa-sun' : 'fa-moon'}`}
|
||||
onClick={handleChangeDarkMode} />
|
||||
</div>
|
||||
|
||||
@@ -85,18 +85,39 @@ function getCustomNav({ allPages }) {
|
||||
const customNav = []
|
||||
if (allPages && allPages.length > 0) {
|
||||
allPages.forEach(p => {
|
||||
if (p?.status === 'Published' && p?.type === 'Page') {
|
||||
if (p?.slug?.indexOf('http') === 0) {
|
||||
customNav.push({ icon: p.icon || null, name: p.title, to: p.slug, show: true })
|
||||
} else {
|
||||
customNav.push({ icon: p.icon || null, name: p.title, to: '/' + p.slug, show: true })
|
||||
}
|
||||
if (p?.slug?.indexOf('http') === 0) {
|
||||
customNav.push({ icon: p.icon || null, name: p.title, to: p.slug, show: true })
|
||||
} else {
|
||||
customNav.push({ icon: p.icon || null, name: p.title, to: '/' + p.slug, show: true })
|
||||
}
|
||||
})
|
||||
}
|
||||
return customNav
|
||||
}
|
||||
|
||||
function getCustomMenu({ collectionData }) {
|
||||
const menuPages = collectionData.filter(post => (post.type === BLOG.NOTION_PROPERTY_NAME.type_menu || post.type === BLOG.NOTION_PROPERTY_NAME.type_sub_menu) && post.status === 'Published')
|
||||
const menus = []
|
||||
if (menuPages && menuPages.length > 0) {
|
||||
menuPages.forEach(e => {
|
||||
e.show = true
|
||||
if (e.type === BLOG.NOTION_PROPERTY_NAME.type_menu) {
|
||||
menus.push(e)
|
||||
} else if (e.type === BLOG.NOTION_PROPERTY_NAME.type_sub_menu) {
|
||||
const parentMenu = menus[menus.length - 1]
|
||||
if (parentMenu) {
|
||||
if (parentMenu.subMenus) {
|
||||
parentMenu.subMenus.push(e)
|
||||
} else {
|
||||
parentMenu.subMenus = [e]
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
return menus
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取标签选项
|
||||
* @param schema
|
||||
@@ -214,11 +235,13 @@ async function getPageRecordMapByNotionAPI({ pageId, from }) {
|
||||
})
|
||||
}
|
||||
|
||||
const notice = await getNotice(collectionData.filter(post => { return post?.type === 'Notice' && post.status === 'Published' })?.[0])
|
||||
const notice = await getNotice(collectionData.filter(post => { return post && post?.type && post?.type === 'Notice' && post.status === 'Published' })?.[0])
|
||||
const categoryOptions = getAllCategories({ allPages, categoryOptions: getCategoryOptions(schema) })
|
||||
const tagOptions = getAllTags({ allPages, tagOptions: getTagOptions(schema) })
|
||||
const siteInfo = getBlogInfo({ collection, block })
|
||||
const customNav = getCustomNav({ allPages: collectionData.filter(post => post.type === 'Page' && post.status === 'Published') })
|
||||
// 新的菜单
|
||||
const customMenu = getCustomMenu({ collectionData })
|
||||
const latestPosts = getLatestPosts({ allPages, from, latestPostCount: 5 })
|
||||
|
||||
return {
|
||||
@@ -236,6 +259,7 @@ async function getPageRecordMapByNotionAPI({ pageId, from }) {
|
||||
categoryOptions,
|
||||
rawMetadata,
|
||||
customNav,
|
||||
customMenu,
|
||||
postCount,
|
||||
pageIds,
|
||||
latestPosts
|
||||
|
||||
@@ -76,10 +76,14 @@ export default async function getPageProperties(id, block, schema, authToken, ta
|
||||
// 映射值:用户个性化type和status字段的下拉框选项,在此映射回代码的英文标识
|
||||
mapProperties(properties)
|
||||
|
||||
if (properties.type === 'Post') {
|
||||
if (properties.type === BLOG.NOTION_PROPERTY_NAME.type_post) {
|
||||
properties.slug = BLOG.POST_URL_PREFIX ? (BLOG.POST_URL_PREFIX + '/' + (properties.slug ?? properties.id)) : (properties.slug ?? properties.id)
|
||||
} else {
|
||||
properties.slug = (properties.slug ?? properties.id)
|
||||
} else if (properties.type === BLOG.NOTION_PROPERTY_NAME.type_page) {
|
||||
properties.slug = properties.slug ?? properties.id
|
||||
} else if (properties.type === BLOG.NOTION_PROPERTY_NAME.type_menu || properties.type === BLOG.NOTION_PROPERTY_NAME.type_sub_menu) {
|
||||
// 菜单路径为空、作为可展开菜单使用
|
||||
properties.to = properties.slug ?? null
|
||||
properties.name = properties.title ?? ''
|
||||
}
|
||||
|
||||
// 开启伪静态路径
|
||||
|
||||
@@ -28,7 +28,8 @@ export const initDarkMode = (isDarkMode, updateDarkMode) => {
|
||||
*/
|
||||
export const initTheme = (theme, changeTheme) => {
|
||||
if (isBrowser()) {
|
||||
const queryTheme = getQueryVariable('theme') || loadThemeFromCookies() || BLOG.THEME
|
||||
// const queryTheme = getQueryVariable('theme') || loadThemeFromCookies() || BLOG.THEME
|
||||
const queryTheme = getQueryVariable('theme') || BLOG.THEME
|
||||
let currentTheme = theme
|
||||
if (queryTheme !== theme && ALL_THEME.indexOf(queryTheme) > -1) {
|
||||
currentTheme = queryTheme
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "notion-next",
|
||||
"version": "3.11.1",
|
||||
"version": "3.12.0",
|
||||
"homepage": "https://github.com/tangly1024/NotionNext.git",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
@@ -21,6 +21,7 @@ import { StarrySky } from '@/components/StarrySky'
|
||||
import MusicPlayer from '@/components/MusicPlayer'
|
||||
import ExternalScript from '@/components/ExternalScript'
|
||||
import { isBrowser } from '@/lib/utils'
|
||||
import smoothscroll from 'smoothscroll-polyfill'
|
||||
|
||||
import AOS from 'aos'
|
||||
import 'aos/dist/aos.css' // You can also use <link> for styles
|
||||
@@ -57,6 +58,7 @@ const MyApp = ({ Component, pageProps }) => {
|
||||
|
||||
if (isBrowser()) {
|
||||
AOS.init()
|
||||
smoothscroll.polyfill()
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
33
public/css/theme-simple.css
Normal file
33
public/css/theme-simple.css
Normal file
@@ -0,0 +1,33 @@
|
||||
#theme-simple #announcement-content {
|
||||
/* background-color: #f6f6f6; */
|
||||
}
|
||||
|
||||
#theme-simple #blog-item-title {
|
||||
color: #276077;
|
||||
}
|
||||
|
||||
.dark #theme-simple #blog-item-title {
|
||||
color: #d1d5db;
|
||||
}
|
||||
|
||||
.notion {
|
||||
margin-top: 0 !important;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
|
||||
/* 菜单下划线动画 */
|
||||
.menu-link {
|
||||
text-decoration: none;
|
||||
background-image: linear-gradient(#dd3333, #dd3333);
|
||||
background-repeat: no-repeat;
|
||||
background-position: bottom center;
|
||||
background-size: 0 2px;
|
||||
transition: background-size 100ms ease-in-out;
|
||||
}
|
||||
|
||||
.menu-link:hover {
|
||||
background-size: 100% 2px;
|
||||
color: #dd3333;
|
||||
}
|
||||
|
||||
@@ -207,7 +207,6 @@
|
||||
|
||||
.medium-zoom-image {
|
||||
border-radius: 0;
|
||||
@apply bg-gray-100 dark:bg-black
|
||||
}
|
||||
|
||||
.medium-zoom-image--opened {
|
||||
@@ -939,7 +938,7 @@ code[class*='language-'] {
|
||||
align-self: flex-start;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
font-size: 1.3em;
|
||||
font-size: 1em;
|
||||
line-height: 1em;
|
||||
}
|
||||
|
||||
@@ -1118,10 +1117,11 @@ code[class*='language-'] {
|
||||
.notion-table-of-contents {
|
||||
width: 100%;
|
||||
margin: 4px 0;
|
||||
@apply bg-gray-50 dark:bg-black p-2
|
||||
}
|
||||
|
||||
.notion-table-of-contents-item {
|
||||
color: inherit;
|
||||
/* color: inherit; */
|
||||
text-decoration: none;
|
||||
user-select: none;
|
||||
transition: background 20ms ease-in 0s;
|
||||
@@ -1137,6 +1137,8 @@ code[class*='language-'] {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
@apply text-blue-600 dark:text-blue-200
|
||||
}
|
||||
|
||||
.notion-table-of-contents-item:hover {
|
||||
|
||||
@@ -21,9 +21,10 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
maxWidth: {
|
||||
side: '14rem'
|
||||
side: '14rem',
|
||||
'9/10': '90%'
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
variants: {
|
||||
extend: {}
|
||||
|
||||
@@ -16,7 +16,7 @@ import BLOG from '@/blog.config'
|
||||
const LayoutBase = props => {
|
||||
const { children, meta } = props
|
||||
return (
|
||||
<div className='dark:text-gray-300 bg-white dark:bg-black'>
|
||||
<div id='theme-example' className='dark:text-gray-300 bg-white dark:bg-black'>
|
||||
<CommonHead meta={meta} />
|
||||
{/* 顶栏LOGO */}
|
||||
<Header {...props} />
|
||||
|
||||
@@ -26,7 +26,7 @@ export const ArticleLock = props => {
|
||||
<div className='text-center space-y-3'>
|
||||
<div className='font-bold'>{locale.COMMON.ARTICLE_LOCK_TIPS}</div>
|
||||
<div className='flex mx-4'>
|
||||
<input id="password" type='password' className='w-full text-sm pl-5 rounded-l transition font-light leading-10 text-black dark:bg-gray-500 bg-gray-50'></input>
|
||||
<input id="password" type='password' className='outline-none w-full text-sm pl-5 rounded-l transition font-light leading-10 text-black dark:bg-gray-500 bg-gray-50'></input>
|
||||
<div onClick={submitPassword} className="px-3 whitespace-nowrap cursor-pointer items-center justify-center py-2 rounded-r duration-300 bg-gray-300" >
|
||||
<i className={'duration-200 cursor-pointer fas fa-key dark:text-black'} > {locale.COMMON.SUBMIT}</i>
|
||||
</div>
|
||||
|
||||
@@ -21,5 +21,5 @@ export const Header = (props) => {
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import Link from 'next/link'
|
||||
import CONFIG_EMPTY from '../config_empty'
|
||||
import CONFIG_EXAMPLE from '../config_example'
|
||||
|
||||
/**
|
||||
* 菜单导航
|
||||
@@ -11,10 +11,10 @@ export const Nav = (props) => {
|
||||
const { customNav } = props
|
||||
const { locale } = useGlobal()
|
||||
let links = [
|
||||
{ icon: 'fas fa-search', name: locale.NAV.SEARCH, to: '/search', show: CONFIG_EMPTY.MENU_SEARCH },
|
||||
{ icon: 'fas fa-archive', name: locale.NAV.ARCHIVE, to: '/archive', show: CONFIG_EMPTY.MENU_ARCHIVE },
|
||||
{ icon: 'fas fa-folder', name: locale.COMMON.CATEGORY, to: '/category', show: CONFIG_EMPTY.MENU_CATEGORY },
|
||||
{ icon: 'fas fa-tag', name: locale.COMMON.TAGS, to: '/tag', show: CONFIG_EMPTY.MENU_TAG }
|
||||
{ icon: 'fas fa-search', name: locale.NAV.SEARCH, to: '/search', show: CONFIG_EXAMPLE.MENU_SEARCH },
|
||||
{ icon: 'fas fa-archive', name: locale.NAV.ARCHIVE, to: '/archive', show: CONFIG_EXAMPLE.MENU_ARCHIVE },
|
||||
{ icon: 'fas fa-folder', name: locale.COMMON.CATEGORY, to: '/category', show: CONFIG_EXAMPLE.MENU_CATEGORY },
|
||||
{ icon: 'fas fa-tag', name: locale.COMMON.TAGS, to: '/tag', show: CONFIG_EXAMPLE.MENU_TAG }
|
||||
]
|
||||
|
||||
if (customNav) {
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
const CONFIG_EMPTY = {
|
||||
const CONFIG_EXAMPLE = {
|
||||
// 菜单配置
|
||||
MENU_CATEGORY: true, // 显示分类
|
||||
MENU_TAG: true, // 显示标签
|
||||
MENU_ARCHIVE: true, // 显示归档
|
||||
MENU_SEARCH: true // 显示搜索
|
||||
}
|
||||
export default CONFIG_EMPTY
|
||||
export default CONFIG_EXAMPLE
|
||||
@@ -1,4 +1,4 @@
|
||||
import CONFIG_EMPTY from './config_empty'
|
||||
import CONFIG_EXAMPLE from './config_example'
|
||||
import { LayoutIndex } from './LayoutIndex'
|
||||
import { LayoutSearch } from './LayoutSearch'
|
||||
import { LayoutArchive } from './LayoutArchive'
|
||||
@@ -11,7 +11,7 @@ import { LayoutTag } from './LayoutTag'
|
||||
import { LayoutTagIndex } from './LayoutTagIndex'
|
||||
|
||||
export {
|
||||
CONFIG_EMPTY as THEME_CONFIG,
|
||||
CONFIG_EXAMPLE as THEME_CONFIG,
|
||||
LayoutIndex,
|
||||
LayoutSearch,
|
||||
LayoutArchive,
|
||||
|
||||
@@ -25,7 +25,7 @@ const LayoutBase = (props) => {
|
||||
meta
|
||||
} = props
|
||||
const leftAreaSlot = <Live2D/>
|
||||
return (<>
|
||||
return (<div id='theme-fukasawa' >
|
||||
<CommonHead meta={meta} />
|
||||
<TopNav {...props}/>
|
||||
<div className={(BLOG.LAYOUT_SIDEBAR_REVERSE ? 'flex-row-reverse' : '') + ' flex'}>
|
||||
@@ -38,7 +38,7 @@ const LayoutBase = (props) => {
|
||||
</main>
|
||||
</div>
|
||||
|
||||
</>)
|
||||
</div>)
|
||||
}
|
||||
|
||||
export default LayoutBase
|
||||
|
||||
@@ -30,7 +30,7 @@ export const ArticleLock = props => {
|
||||
<div className="flex mx-4">
|
||||
<input
|
||||
id="password" type='password'
|
||||
className="w-full text-sm pl-5 rounded-l transition focus:shadow-lg dark:text-gray-300 font-light leading-10 text-black bg-gray-100 dark:bg-gray-500"
|
||||
className="outline-none w-full text-sm pl-5 rounded-l transition focus:shadow-lg dark:text-gray-300 font-light leading-10 text-black bg-gray-100 dark:bg-gray-500"
|
||||
></input>
|
||||
<div
|
||||
onClick={submitPassword}
|
||||
|
||||
@@ -53,7 +53,7 @@ const SearchInput = ({ currentTag, currentSearch, cRef }) => {
|
||||
<input
|
||||
ref={searchInputRef}
|
||||
type='text'
|
||||
className={'w-full text-sm pl-2 transition focus:shadow-lg font-light leading-10 text-black bg-gray-100 dark:bg-gray-800 dark:text-white'}
|
||||
className={'outline-none w-full text-sm pl-2 transition focus:shadow-lg font-light leading-10 text-black bg-gray-100 dark:bg-gray-800 dark:text-white'}
|
||||
onKeyUp={handleKeyUp}
|
||||
onCompositionStart={lockSearchInput}
|
||||
onCompositionUpdate={lockSearchInput}
|
||||
|
||||
@@ -5,7 +5,6 @@ import Footer from './components/Footer'
|
||||
import JumpToTopButton from './components/JumpToTopButton'
|
||||
import SideRight from './components/SideRight'
|
||||
import TopNav from './components/TopNav'
|
||||
import smoothscroll from 'smoothscroll-polyfill'
|
||||
import FloatDarkModeButton from './components/FloatDarkModeButton'
|
||||
import Live2D from '@/components/Live2D'
|
||||
import LoadingCover from './components/LoadingCover'
|
||||
@@ -53,13 +52,12 @@ const LayoutBase = props => {
|
||||
// changePercent(per)
|
||||
}
|
||||
useEffect(() => {
|
||||
smoothscroll.polyfill()
|
||||
document.addEventListener('scroll', scrollListener)
|
||||
return () => document.removeEventListener('scroll', scrollListener)
|
||||
}, [show])
|
||||
|
||||
return (
|
||||
<div className="bg-hexo-background-gray dark:bg-black">
|
||||
<div id='theme-hexo' className="bg-hexo-background-gray dark:bg-black">
|
||||
<CommonHead meta={meta} siteInfo={siteInfo}/>
|
||||
|
||||
<TopNav {...props} />
|
||||
|
||||
@@ -25,7 +25,7 @@ export const ArticleLock = props => {
|
||||
<div className='text-center space-y-3'>
|
||||
<div className='font-bold'>{locale.COMMON.ARTICLE_LOCK_TIPS}</div>
|
||||
<div className='flex mx-4'>
|
||||
<input id="password" type='password' className='w-full text-sm pl-5 rounded-l transition focus:shadow-lg dark:text-gray-300 font-light leading-10 text-black bg-gray-100 dark:bg-gray-500'></input>
|
||||
<input id="password" type='password' className='outline-none w-full text-sm pl-5 rounded-l transition focus:shadow-lg dark:text-gray-300 font-light leading-10 text-black bg-gray-100 dark:bg-gray-500'></input>
|
||||
<div onClick={submitPassword} className="px-3 whitespace-nowrap cursor-pointer items-center justify-center py-2 bg-indigo-500 hover:bg-indigo-400 text-white rounded-r duration-300" >
|
||||
<i className={'duration-200 cursor-pointer fas fa-key'} > {locale.COMMON.SUBMIT}</i>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@ import TagItemMini from './TagItemMini'
|
||||
import CONFIG_HEXO from '../config_hexo'
|
||||
import NotionPage from '@/components/NotionPage'
|
||||
|
||||
const BlogPostCard = ({ post, showSummary, index, siteInfo }) => {
|
||||
const BlogPostCard = ({ post, showSummary, siteInfo }) => {
|
||||
const showPreview = CONFIG_HEXO.POST_LIST_PREVIEW && post.blockMap
|
||||
if (post && !post.page_cover && CONFIG_HEXO.POST_LIST_COVER_DEFAULT) {
|
||||
post.page_cover = siteInfo?.pageCover
|
||||
@@ -19,9 +19,9 @@ const BlogPostCard = ({ post, showSummary, index, siteInfo }) => {
|
||||
data-aos-easing="ease-in-out"
|
||||
data-aos-once="true"
|
||||
data-aos-anchor-placement="top-bottom"
|
||||
className={`flex md:flex-row flex-col-reverse ${index % 2 === 0 ? 'md:flex-row-reverse' : ''}
|
||||
w-full md:h-72 h-96 justify-between overflow-hidden drop-shadow-md
|
||||
border dark:border-black rounded-xl bg-white dark:bg-hexo-black-gray `}>
|
||||
className='flex md:flex-row flex-col-reverse even:md:flex-row-reverse
|
||||
w-full md:h-72 h-96 justify-between overflow-hidden drop-shadow-md
|
||||
border dark:border-black rounded-xl bg-white dark:bg-hexo-black-gray'>
|
||||
|
||||
<div className={`lg:p-8 p-4 flex flex-col ${showPageCover ? 'md:w-7/12 w-full' : 'w-full'}`}>
|
||||
<Link
|
||||
|
||||
@@ -22,7 +22,7 @@ const BlogPostListPage = ({ page = 1, posts = [], postCount, siteInfo }) => {
|
||||
{/* 文章列表 */}
|
||||
<div className="space-y-6 px-2">
|
||||
{posts.map(post => (
|
||||
<BlogPostCard key={post.id} post={post} index={posts.indexOf(post)} siteInfo={siteInfo}/>
|
||||
<BlogPostCard key={post.id} post={post} siteInfo={siteInfo}/>
|
||||
))}
|
||||
</div>
|
||||
{showPagination && <PaginationNumber page={page} totalPage={totalPage} />}
|
||||
|
||||
@@ -58,7 +58,7 @@ const BlogPostListScroll = ({ posts = [], currentSearch, showSummary = CONFIG_HE
|
||||
{/* 文章列表 */}
|
||||
<div className='flex flex-wrap space-y-1 lg:space-y-4 px-2'>
|
||||
{postsToShow.map(post => (
|
||||
<BlogPostCard key={post.id} post={post} index={posts.indexOf(post)} showSummary={showSummary} siteInfo={siteInfo}/>
|
||||
<BlogPostCard key={post.id} post={post} showSummary={showSummary} siteInfo={siteInfo}/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ const Progress = ({ targetRef, showPercent = true }) => {
|
||||
useEffect(() => {
|
||||
document.addEventListener('scroll', scrollListener)
|
||||
return () => document.removeEventListener('scroll', scrollListener)
|
||||
}, [percent])
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="h-4 w-full shadow-2xl bg-gray-400 ">
|
||||
|
||||
@@ -69,7 +69,7 @@ const SearchInput = props => {
|
||||
ref={searchInputRef}
|
||||
type="text"
|
||||
className={
|
||||
'w-full text-sm pl-5 rounded-lg transition focus:shadow-lg dark:text-gray-300 font-light leading-10 text-black bg-gray-100 dark:bg-gray-500'
|
||||
'outline-none w-full text-sm pl-5 rounded-lg transition focus:shadow-lg dark:text-gray-300 font-light leading-10 text-black bg-gray-100 dark:bg-gray-500'
|
||||
}
|
||||
onKeyUp={handleKeyUp}
|
||||
onCompositionStart={lockSearchInput}
|
||||
|
||||
@@ -8,6 +8,25 @@ import * as medium from './medium'
|
||||
import * as nobelium from './nobelium'
|
||||
import * as matery from './matery'
|
||||
import * as example from './example'
|
||||
import * as simple from './simple'
|
||||
|
||||
export const ALL_THEME = ['hexo', 'matery', 'next', 'medium', 'fukasawa', 'nobelium', 'example']
|
||||
export { hexo, next, medium, fukasawa, nobelium, matery, example }
|
||||
export const ALL_THEME = [
|
||||
'hexo',
|
||||
'matery',
|
||||
'next',
|
||||
'medium',
|
||||
'fukasawa',
|
||||
'nobelium',
|
||||
'example',
|
||||
'simple'
|
||||
]
|
||||
export {
|
||||
hexo,
|
||||
next,
|
||||
medium,
|
||||
fukasawa,
|
||||
nobelium,
|
||||
matery,
|
||||
example,
|
||||
simple
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import { useEffect, useState } from 'react'
|
||||
import Footer from './components/Footer'
|
||||
import JumpToTopButton from './components/JumpToTopButton'
|
||||
import TopNav from './components/TopNav'
|
||||
import smoothscroll from 'smoothscroll-polyfill'
|
||||
import Live2D from '@/components/Live2D'
|
||||
import LoadingCover from './components/LoadingCover'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
@@ -37,13 +36,12 @@ const LayoutBase = props => {
|
||||
// changePercent(per)
|
||||
}
|
||||
useEffect(() => {
|
||||
smoothscroll.polyfill()
|
||||
document.addEventListener('scroll', scrollListener)
|
||||
return () => document.removeEventListener('scroll', scrollListener)
|
||||
}, [show])
|
||||
|
||||
return (
|
||||
<div id="outer-wrapper" className="min-h-screen flex flex-col justify-between bg-hexo-background-gray dark:bg-black w-full">
|
||||
<div id='theme-matery' className="min-h-screen flex flex-col justify-between bg-hexo-background-gray dark:bg-black w-full">
|
||||
|
||||
<CommonHead meta={meta} siteInfo={siteInfo} />
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ export const LayoutSlug = props => {
|
||||
{lock && <ArticleLock validPassword={validPassword} />}
|
||||
|
||||
{!lock && <div id="container" className="overflow-x-auto md:w-full px-3 ">
|
||||
{post?.type === 'Post' && <>
|
||||
{ post?.type && post?.type === 'Post' && <>
|
||||
<div
|
||||
data-aos="fade-down"
|
||||
data-aos-duration="500"
|
||||
|
||||
@@ -25,7 +25,7 @@ export const ArticleLock = props => {
|
||||
<div className='text-center space-y-3'>
|
||||
<div className='font-bold'>{locale.COMMON.ARTICLE_LOCK_TIPS}</div>
|
||||
<div className='flex mx-4'>
|
||||
<input id="password" type='password' className='w-full text-sm pl-5 rounded-l transition focus:shadow-lg dark:text-gray-300 font-light leading-10 text-black bg-gray-100 dark:bg-gray-500'></input>
|
||||
<input id="password" type='password' className='outline-none w-full text-sm pl-5 rounded-l transition focus:shadow-lg dark:text-gray-300 font-light leading-10 text-black bg-gray-100 dark:bg-gray-500'></input>
|
||||
<div onClick={submitPassword} className="px-3 whitespace-nowrap cursor-pointer items-center justify-center py-2 bg-indigo-500 hover:bg-indigo-400 text-white rounded-r duration-300" >
|
||||
<i className={'duration-200 cursor-pointer fas fa-key'} > {locale.COMMON.SUBMIT}</i>
|
||||
</div>
|
||||
|
||||
@@ -25,7 +25,7 @@ const Progress = ({ targetRef, showPercent = true }) => {
|
||||
useEffect(() => {
|
||||
document.addEventListener('scroll', scrollListener)
|
||||
return () => document.removeEventListener('scroll', scrollListener)
|
||||
}, [percent])
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="h-4 w-full shadow-2xl bg-gray-400 ">
|
||||
|
||||
@@ -69,7 +69,7 @@ const SearchInput = props => {
|
||||
ref={searchInputRef}
|
||||
type="text"
|
||||
className={
|
||||
'w-full text-sm pl-5 rounded-lg transition focus:shadow-lg dark:text-gray-300 font-light leading-10 text-black bg-gray-100 dark:bg-gray-500'
|
||||
'outline-none w-full text-sm pl-5 rounded-lg transition focus:shadow-lg dark:text-gray-300 font-light leading-10 text-black bg-gray-100 dark:bg-gray-500'
|
||||
}
|
||||
onKeyUp={handleKeyUp}
|
||||
onCompositionStart={lockSearchInput}
|
||||
|
||||
@@ -28,7 +28,7 @@ const LayoutBase = props => {
|
||||
|
||||
return (
|
||||
<ThemeGlobalMedium.Provider value={{ tocVisible, changeTocVisible }}>
|
||||
<div className='bg-white dark:bg-hexo-black-gray w-full h-full min-h-screen justify-center dark:text-gray-300'>
|
||||
<div id='theme-medium' className='bg-white dark:bg-hexo-black-gray w-full h-full min-h-screen justify-center dark:text-gray-300'>
|
||||
<CommonHead meta={meta} />
|
||||
|
||||
<main id='wrapper' className={(BLOG.LAYOUT_SIDEBAR_REVERSE ? 'flex-row-reverse' : '') + 'relative flex justify-between w-full h-full mx-auto'}>
|
||||
|
||||
@@ -26,7 +26,7 @@ export const ArticleLock = props => {
|
||||
<div className='text-center space-y-3'>
|
||||
<div className='font-bold'>{locale.COMMON.ARTICLE_LOCK_TIPS}</div>
|
||||
<div className='flex mx-4'>
|
||||
<input id="password" type='password' className='w-full text-sm pl-5 rounded-l transition focus:shadow-lg dark:text-gray-300 font-light leading-10 text-black bg-gray-100 dark:bg-gray-500'></input>
|
||||
<input id="password" type='password' className='outline-none w-full text-sm pl-5 rounded-l transition focus:shadow-lg dark:text-gray-300 font-light leading-10 text-black bg-gray-100 dark:bg-gray-500'></input>
|
||||
<div onClick={submitPassword} className="px-3 whitespace-nowrap cursor-pointer items-center justify-center py-2 bg-green-500 hover:bg-green-400 text-white rounded-r duration-300" >
|
||||
<i className={'duration-200 cursor-pointer fas fa-key'} > {locale.COMMON.SUBMIT}</i>
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useRouter } from 'next/router'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import CONFIG_MEDIUM from '../config_medium'
|
||||
|
||||
function GroupMenu ({ customNav }) {
|
||||
function GroupMenu ({ customMenu, customNav }) {
|
||||
const { locale } = useGlobal()
|
||||
const router = useRouter()
|
||||
|
||||
@@ -39,13 +39,13 @@ function GroupMenu ({ customNav }) {
|
||||
{link.slot}
|
||||
|
||||
</Link>
|
||||
);
|
||||
)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
})}
|
||||
</nav>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default GroupMenu
|
||||
|
||||
@@ -25,7 +25,7 @@ const Progress = ({ targetRef, showPercent = true }) => {
|
||||
useEffect(() => {
|
||||
document.addEventListener('scroll', scrollListener)
|
||||
return () => document.removeEventListener('scroll', scrollListener)
|
||||
}, [percent])
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="h-4 w-full shadow-2xl bg-hexo-light-gray dark:bg-black">
|
||||
|
||||
@@ -61,7 +61,7 @@ const SearchInput = ({ currentTag, currentSearch, cRef, className }) => {
|
||||
<input
|
||||
ref={searchInputRef}
|
||||
type='text'
|
||||
className={'w-full text-sm pl-2 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-2 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}
|
||||
|
||||
@@ -74,7 +74,7 @@ export default function TopNavBar(props) {
|
||||
{link.slot}
|
||||
|
||||
</Link>
|
||||
);
|
||||
)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
@@ -82,5 +82,5 @@ export default function TopNavBar(props) {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import TopNav from './components/TopNav'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import smoothscroll from 'smoothscroll-polyfill'
|
||||
import CONFIG_NEXT from './config_next'
|
||||
import Live2D from '@/components/Live2D'
|
||||
import BLOG from '@/blog.config'
|
||||
@@ -45,8 +44,6 @@ const LayoutBase = (props) => {
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
smoothscroll.polyfill()
|
||||
|
||||
// facebook messenger 插件需要调整右下角悬浮按钮的高度
|
||||
const fb = document.getElementsByClassName('fb-customerchat')
|
||||
if (fb.length === 0) {
|
||||
@@ -59,7 +56,7 @@ const LayoutBase = (props) => {
|
||||
return () => document.removeEventListener('scroll', scrollListener)
|
||||
}, [show])
|
||||
|
||||
return (<>
|
||||
return (<div id='theme-next'>
|
||||
|
||||
<CommonHead meta={meta} />
|
||||
|
||||
@@ -90,7 +87,7 @@ const LayoutBase = (props) => {
|
||||
</div>
|
||||
|
||||
<Footer title={siteInfo?.title}/>
|
||||
</>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ export const ArticleLock = props => {
|
||||
<div className="flex mx-4">
|
||||
<input
|
||||
id="password" type='password'
|
||||
className="w-full text-sm pl-5 transition focus:shadow-lg dark:text-gray-300 font-light leading-10 text-black bg-gray-100 dark:bg-gray-500"
|
||||
className="outline-none w-full text-sm pl-5 transition focus:shadow-lg dark:text-gray-300 font-light leading-10 text-black bg-gray-100 dark:bg-gray-500"
|
||||
></input>
|
||||
<div
|
||||
onClick={submitPassword}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import smoothscroll from 'smoothscroll-polyfill'
|
||||
import CONFIG_NEXT from '../config_next'
|
||||
|
||||
/**
|
||||
@@ -11,12 +10,14 @@ import CONFIG_NEXT from '../config_next'
|
||||
* @constructor
|
||||
*/
|
||||
const JumpToBottomButton = ({ showPercent = false }) => {
|
||||
if (!CONFIG_NEXT.WIDGET_TO_BOTTOM) {
|
||||
return <></>
|
||||
}
|
||||
|
||||
const [show, switchShow] = useState(false)
|
||||
const [percent, changePercent] = useState(0)
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener('scroll', scrollListener)
|
||||
return () => document.removeEventListener('scroll', scrollListener)
|
||||
}, [show])
|
||||
|
||||
const scrollListener = () => {
|
||||
const targetRef = document.getElementById('wrapper')
|
||||
const clientHeight = targetRef?.clientHeight
|
||||
@@ -36,12 +37,9 @@ const JumpToBottomButton = ({ showPercent = false }) => {
|
||||
window.scrollTo({ top: targetRef.clientHeight, behavior: 'smooth' })
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
smoothscroll.polyfill()
|
||||
|
||||
document.addEventListener('scroll', scrollListener)
|
||||
return () => document.removeEventListener('scroll', scrollListener)
|
||||
}, [show])
|
||||
if (!CONFIG_NEXT.WIDGET_TO_BOTTOM) {
|
||||
return <></>
|
||||
}
|
||||
|
||||
return (<div className='flex space-x-1 transform hover:scale-105 duration-200 py-2 px-3' onClick={scrollToBottom} >
|
||||
<div className='dark:text-gray-200' >
|
||||
|
||||
@@ -11,10 +11,10 @@ import CONFIG_NEXT from '../config_next'
|
||||
* @constructor
|
||||
*/
|
||||
const JumpToTopButton = ({ showPercent = true, percent }) => {
|
||||
const { locale } = useGlobal()
|
||||
if (!CONFIG_NEXT.WIDGET_TO_TOP) {
|
||||
return <></>
|
||||
}
|
||||
const { locale } = useGlobal()
|
||||
return (<div className='flex space-x-1 items-center transform hover:scale-105 duration-200 py-2 px-3' onClick={() => window.scrollTo({ top: 0, behavior: 'smooth' })} >
|
||||
<div className='dark:text-gray-200' title={locale.POST.TOP} >
|
||||
<i className='fa-arrow-up fas' />
|
||||
|
||||
@@ -25,7 +25,7 @@ const Progress = ({ targetRef, showPercent = true }) => {
|
||||
useEffect(() => {
|
||||
document.addEventListener('scroll', scrollListener)
|
||||
return () => document.removeEventListener('scroll', scrollListener)
|
||||
}, [percent])
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="h-4 w-full shadow-2xl bg-gray-400 font-sans">
|
||||
|
||||
@@ -66,7 +66,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-800 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-800 dark:text-white'}
|
||||
onKeyUp={handleKeyUp}
|
||||
onCompositionStart={lockSearchInput}
|
||||
onCompositionUpdate={lockSearchInput}
|
||||
|
||||
@@ -22,7 +22,7 @@ const NextRecentComments = dynamic(() => import('./NextRecentComments'))
|
||||
* @constructor
|
||||
*/
|
||||
const SideAreaRight = (props) => {
|
||||
const { tags, currentTag, slot, categories, currentCategory, notice } = props
|
||||
const { tagOptions, currentTag, slot, categoryOptions, currentCategory, notice } = props
|
||||
const { locale } = useGlobal()
|
||||
const router = useRouter()
|
||||
return (<aside id='right' className={(BLOG.LAYOUT_SIDEBAR_REVERSE ? 'mr-4' : 'ml-4') + ' space-y-4 hidden 2xl:block flex-col w-60 relative z-10'}>
|
||||
@@ -50,7 +50,7 @@ const SideAreaRight = (props) => {
|
||||
{slot}
|
||||
|
||||
{/* 分类 */}
|
||||
{CONFIG_NEXT.RIGHT_CATEGORY_LIST && router.asPath !== '/category' && categories && (
|
||||
{CONFIG_NEXT.RIGHT_CATEGORY_LIST && router.asPath !== '/category' && categoryOptions && (
|
||||
<Card>
|
||||
<div className='text-sm px-2 flex flex-nowrap justify-between font-light'>
|
||||
<div className='pb-2 text-gray-600 dark:text-gray-300'><i className='mr-2 fas fa-th-list' />{locale.COMMON.CATEGORY}</div>
|
||||
@@ -63,11 +63,11 @@ const SideAreaRight = (props) => {
|
||||
|
||||
</Link>
|
||||
</div>
|
||||
<CategoryGroup currentCategory={currentCategory} categories={categories} />
|
||||
<CategoryGroup currentCategory={currentCategory} categories={categoryOptions} />
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{CONFIG_NEXT.RIGHT_TAG_LIST && router.asPath !== '/tag' && tags && (
|
||||
{CONFIG_NEXT.RIGHT_TAG_LIST && router.asPath !== '/tag' && tagOptions && (
|
||||
<Card>
|
||||
<div className="text-sm pb-1 px-2 flex flex-nowrap justify-between font-light dark:text-gray-200">
|
||||
<div className="text-gray-600 dark:text-gray-200">
|
||||
@@ -85,7 +85,7 @@ const SideAreaRight = (props) => {
|
||||
</Link>
|
||||
</div>
|
||||
<div className="px-2 pt-2">
|
||||
<TagGroups tags={tags} currentTag={currentTag} />
|
||||
<TagGroups tags={tagOptions} currentTag={currentTag} />
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
@@ -16,7 +16,7 @@ const LayoutBase = props => {
|
||||
const fullWidth = post?.fullWidth ?? false
|
||||
|
||||
return (
|
||||
<div className='nobelium relative dark:text-gray-300 w-full bg-white dark:bg-black min-h-screen'>
|
||||
<div id='theme-nobelium' className='nobelium relative dark:text-gray-300 w-full bg-white dark:bg-black min-h-screen'>
|
||||
<CommonHead meta={meta} />
|
||||
|
||||
{/* 顶部导航栏 */}
|
||||
|
||||
@@ -25,7 +25,7 @@ export const LayoutTag = props => {
|
||||
placeholder={
|
||||
currentTag ? `Search in #${currentTag}` : 'Search Articles'
|
||||
}
|
||||
className="block w-full border px-4 py-2 border-black bg-white text-black dark:bg-night dark:border-white dark:text-white"
|
||||
className="outline-none block w-full border px-4 py-2 border-black bg-white text-black dark:bg-night dark:border-white dark:text-white"
|
||||
onChange={e => setSearchValue(e.target.value)}
|
||||
/>
|
||||
<svg
|
||||
|
||||
@@ -26,7 +26,7 @@ export const ArticleLock = props => {
|
||||
<div className='text-center space-y-3'>
|
||||
<div className='font-bold'>{locale.COMMON.ARTICLE_LOCK_TIPS}</div>
|
||||
<div className='flex'>
|
||||
<input id="password" type='password' className='w-full text-sm pl-5 rounded-l transition font-light leading-10 text-black dark:bg-gray-500 bg-gray-50'></input>
|
||||
<input id="password" type='password' className='outline-none w-full text-sm pl-5 rounded-l transition font-light leading-10 text-black dark:bg-gray-500 bg-gray-50'></input>
|
||||
<div onClick={submitPassword} className="px-3 whitespace-nowrap cursor-pointer items-center justify-center py-2 rounded-r duration-300 bg-gray-300" >
|
||||
<i className={'duration-200 cursor-pointer fas fa-key dark:text-black'} > {locale.COMMON.SUBMIT}</i>
|
||||
</div>
|
||||
|
||||
@@ -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}
|
||||
|
||||
7
themes/simple/Layout404.js
Normal file
7
themes/simple/Layout404.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import LayoutBase from './LayoutBase'
|
||||
|
||||
export const Layout404 = (props) => {
|
||||
return <LayoutBase {...props}>
|
||||
404 Not found.
|
||||
</LayoutBase>
|
||||
}
|
||||
45
themes/simple/LayoutArchive.js
Normal file
45
themes/simple/LayoutArchive.js
Normal file
@@ -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 (
|
||||
<LayoutBase {...props}>
|
||||
<div className="mb-10 pb-20 md:py-12 p-3 min-h-screen w-full">
|
||||
{Object.keys(archivePosts).map(archiveTitle => (
|
||||
<div key={archiveTitle}>
|
||||
<div id={archiveTitle} className="pt-16 pb-4 text-3xl dark:text-gray-300" >
|
||||
{archiveTitle}
|
||||
</div>
|
||||
|
||||
<ul>
|
||||
{archivePosts[archiveTitle].map(post => (
|
||||
<li
|
||||
key={post.id}
|
||||
className="border-l-2 p-1 text-xs md:text-base items-center hover:scale-x-105 hover:border-gray-500 dark:hover:border-gray-300 dark:border-gray-400 transform duration-500"
|
||||
>
|
||||
<div id={post?.date?.start_date}>
|
||||
<span className="text-gray-400">
|
||||
{post.date?.start_date}
|
||||
</span>{' '}
|
||||
|
||||
<Link
|
||||
href={`${BLOG.SUB_PATH}/${post.slug}`}
|
||||
passHref
|
||||
className="dark:text-gray-400 dark:hover:text-gray-300 overflow-x-hidden hover:underline cursor-pointer text-gray-600">
|
||||
|
||||
{post.title}
|
||||
|
||||
</Link>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</LayoutBase>
|
||||
)
|
||||
}
|
||||
61
themes/simple/LayoutBase.js
Normal file
61
themes/simple/LayoutBase.js
Normal file
@@ -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 (
|
||||
<div id='theme-simple' className='dark:text-gray-300 bg-white dark:bg-black'>
|
||||
<CommonHead meta={meta} />
|
||||
|
||||
{CONFIG_SIMPLE.TOP_BAR && <TopBar {...props} />}
|
||||
|
||||
{/* 顶部LOGO */}
|
||||
<Header {...props} />
|
||||
|
||||
{/* 导航栏 */}
|
||||
<NavBar {...props} />
|
||||
|
||||
{/* 主体 */}
|
||||
<div id='container-wrap' className="w-full relative">
|
||||
|
||||
{/* <Title {...props} /> */}
|
||||
|
||||
<div id='container-inner' className={(BLOG.LAYOUT_SIDEBAR_REVERSE ? 'flex-row-reverse' : '') + ' max-w-9/10 w-full relative container mx-auto flex justify-center items-start pt-12'}>
|
||||
|
||||
<div className='w-full flex-grow'>{children}</div>
|
||||
|
||||
<SideBar {...props} />
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div className='fixed right-4 bottom-4'>
|
||||
<JumpToTopButton />
|
||||
</div>
|
||||
|
||||
<Footer {...props} />
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default LayoutBase
|
||||
10
themes/simple/LayoutCategory.js
Normal file
10
themes/simple/LayoutCategory.js
Normal file
@@ -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 <LayoutBase {...props}>
|
||||
{BLOG.POST_LIST_STYLE === 'page' ? <BlogListPage {...props} /> : <BlogListScroll {...props} />}
|
||||
</LayoutBase >
|
||||
}
|
||||
26
themes/simple/LayoutCategoryIndex.js
Normal file
26
themes/simple/LayoutCategoryIndex.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import Link from 'next/link'
|
||||
import LayoutBase from './LayoutBase'
|
||||
|
||||
export const LayoutCategoryIndex = props => {
|
||||
const { categoryOptions } = props
|
||||
return (
|
||||
<LayoutBase {...props}>
|
||||
<div id='category-list' className='duration-200 flex flex-wrap'>
|
||||
{categoryOptions?.map(category => {
|
||||
return (
|
||||
<Link
|
||||
key={category.name}
|
||||
href={`/category/${category.name}`}
|
||||
passHref
|
||||
legacyBehavior>
|
||||
<div
|
||||
className={'hover:text-black dark:hover:text-white dark:text-gray-300 dark:hover:bg-gray-600 px-5 cursor-pointer py-2 hover:bg-gray-100'}>
|
||||
<i className='mr-4 fas fa-folder' />{category.name}({category.count})
|
||||
</div>
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</LayoutBase>
|
||||
)
|
||||
}
|
||||
13
themes/simple/LayoutIndex.js
Normal file
13
themes/simple/LayoutIndex.js
Normal file
@@ -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 (
|
||||
<LayoutBase {...props}>
|
||||
{BLOG.POST_LIST_STYLE === 'page' ? <BlogListPage {...props} /> : <BlogListScroll {...props} />}
|
||||
</LayoutBase>
|
||||
)
|
||||
}
|
||||
10
themes/simple/LayoutPage.js
Normal file
10
themes/simple/LayoutPage.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import { BlogListPage } from './components/BlogListPage'
|
||||
import LayoutBase from './LayoutBase'
|
||||
|
||||
export const LayoutPage = props => {
|
||||
return (
|
||||
<LayoutBase {...props}>
|
||||
<BlogListPage {...props} />
|
||||
</LayoutBase>
|
||||
)
|
||||
}
|
||||
50
themes/simple/LayoutSearch.js
Normal file
50
themes/simple/LayoutSearch.js
Normal file
@@ -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,
|
||||
`<span class='text-red-500 border-b border-dashed'>${keyword}</span>`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 100)
|
||||
}, [])
|
||||
|
||||
return <LayoutBase {...props}>
|
||||
|
||||
{BLOG.POST_LIST_STYLE === 'page' ? <BlogListPage {...props} /> : <BlogListScroll {...props} />}
|
||||
|
||||
</LayoutBase>
|
||||
}
|
||||
30
themes/simple/LayoutSlug.js
Normal file
30
themes/simple/LayoutSlug.js
Normal file
@@ -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 <LayoutBase {...props} />
|
||||
}
|
||||
|
||||
return (
|
||||
<LayoutBase {...props}>
|
||||
|
||||
{lock && <ArticleLock validPassword={validPassword} />}
|
||||
|
||||
{!lock && <div id="notion-article" className="px-2">
|
||||
|
||||
{post && <>
|
||||
<ArticleInfo post={post} />
|
||||
<NotionPage post={post} />
|
||||
<Comment frontMatter={post}/>
|
||||
</>}
|
||||
</div>}
|
||||
|
||||
</LayoutBase>
|
||||
)
|
||||
}
|
||||
10
themes/simple/LayoutTag.js
Normal file
10
themes/simple/LayoutTag.js
Normal file
@@ -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 <LayoutBase {...props}>
|
||||
{BLOG.POST_LIST_STYLE === 'page' ? <BlogListPage {...props} /> : <BlogListScroll {...props} />}
|
||||
</LayoutBase >
|
||||
}
|
||||
29
themes/simple/LayoutTagIndex.js
Normal file
29
themes/simple/LayoutTagIndex.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import Link from 'next/link'
|
||||
import LayoutBase from './LayoutBase'
|
||||
|
||||
export const LayoutTagIndex = (props) => {
|
||||
const { tagOptions } = props
|
||||
return (
|
||||
<LayoutBase {...props}>
|
||||
<div>
|
||||
<div id='tags-list' className='duration-200 flex flex-wrap'>
|
||||
{tagOptions.map(tag => {
|
||||
return (
|
||||
<div key={tag.name} className='p-2'>
|
||||
<Link
|
||||
key={tag}
|
||||
href={`/tag/${encodeURIComponent(tag.name)}`}
|
||||
passHref
|
||||
className={`cursor-pointer inline-block rounded hover:bg-gray-500 hover:text-white duration-200
|
||||
mr-2 py-1 px-2 text-xs whitespace-nowrap dark:hover:text-white text-gray-600 hover:shadow-xl dark:border-gray-400 notion-${tag.color}_background dark:bg-gray-800`}>
|
||||
|
||||
<div className='font-light dark:text-gray-400'><i className='mr-1 fas fa-tag' /> {tag.name + (tag.count ? `(${tag.count})` : '')} </div>
|
||||
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div> </LayoutBase>
|
||||
)
|
||||
}
|
||||
13
themes/simple/components/Announcement.js
Normal file
13
themes/simple/components/Announcement.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import dynamic from 'next/dynamic'
|
||||
|
||||
const NotionPage = dynamic(() => import('@/components/NotionPage'))
|
||||
|
||||
const Announcement = ({ post, className }) => {
|
||||
if (!post) {
|
||||
return <></>
|
||||
}
|
||||
return <>{post && (<div id="announcement-content" className='px-3'>
|
||||
<NotionPage post={post} />
|
||||
</div>)} </>
|
||||
}
|
||||
export default Announcement
|
||||
56
themes/simple/components/ArticleInfo.js
Normal file
56
themes/simple/components/ArticleInfo.js
Normal file
@@ -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 (
|
||||
<section className="flex-wrap flex mt-2 text-gray-400 dark:text-gray-400 font-light leading-8">
|
||||
<div>
|
||||
<h2
|
||||
id='blog-item-title'
|
||||
className="mb-5 font-bold text-black text-xl md:text-2xl no-underline">
|
||||
{post.title}
|
||||
</h2>
|
||||
|
||||
{post?.type !== 'Page' && (<>
|
||||
<div className="mb-4 text-sm text-gray-700 dark:text-gray-300">
|
||||
<span> <i className="fa-regular fa-user"></i> <a href={CONFIG_SIMPLE.AUTHOR_LINK}>{BLOG.AUTHOR}</a></span>
|
||||
<span> - <i className="fa-regular fa-clock"></i> {post.date?.start_date || post.createdTime}</span>
|
||||
{post.category && <span> - <i className="fa-regular fa-folder"></i> <a href="#" className="hover:text-red-400 transition-all duration-200">{post.category}</a></span>}
|
||||
{post.tags && post.tags?.length > 0 && post.tags.map(t => <span key={t}> / <Link href={`tag/${t}`}><span className=' hover:text-red-400 transition-all duration-200'>{t}</span></Link></span>)}
|
||||
</div>
|
||||
</>)}
|
||||
|
||||
{post?.type !== 'Page' && (<>
|
||||
<Link
|
||||
href={`/archive#${post?.date?.start_date?.substr(0, 7)}`}
|
||||
passHref
|
||||
className="pl-1 mr-2 cursor-pointer hover:text-gray-700 dark:hover:text-gray-200 border-b dark:border-gray-500 border-dashed">
|
||||
|
||||
{date}
|
||||
|
||||
</Link>
|
||||
<span className='mr-2'>|</span>
|
||||
<span className='mx-2 text-gray-400 dark:text-gray-500'>
|
||||
{locale.COMMON.LAST_EDITED_TIME}: {post.lastEditedTime}
|
||||
</span>
|
||||
<span className='mr-2'>|</span>
|
||||
<span className="hidden busuanzi_container_page_pv font-light mr-2">
|
||||
<i className='mr-1 fas fa-eye' />
|
||||
|
||||
<span className="mr-2 busuanzi_value_page_pv" />
|
||||
</span>
|
||||
</>)}
|
||||
|
||||
</div>
|
||||
|
||||
</section>
|
||||
)
|
||||
}
|
||||
38
themes/simple/components/ArticleLock.js
Normal file
38
themes/simple/components/ArticleLock.js
Normal file
@@ -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 = `<div class='text-red-500 animate__shakeX animate__animated'>${locale.COMMON.PASSWORD_ERROR}</div>`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return <div id='container' className='w-full flex justify-center items-center h-96 font-sans'>
|
||||
<div className='text-center space-y-3'>
|
||||
<div className='font-bold'>{locale.COMMON.ARTICLE_LOCK_TIPS}</div>
|
||||
<div className='flex mx-4'>
|
||||
<input id="password" type='password' className='outline-none w-full text-sm pl-5 rounded-l transition font-light leading-10 text-black dark:bg-gray-500 bg-gray-50'></input>
|
||||
<div onClick={submitPassword} className="px-3 whitespace-nowrap cursor-pointer items-center justify-center py-2 rounded-r duration-300 bg-gray-300" >
|
||||
<i className={'duration-200 cursor-pointer fas fa-key dark:text-black'} > {locale.COMMON.SUBMIT}</i>
|
||||
</div>
|
||||
</div>
|
||||
<div id='tips'>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
36
themes/simple/components/BlogItem.js
Normal file
36
themes/simple/components/BlogItem.js
Normal file
@@ -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 <div key={post.id} className="mb-10 pb-12 border-b dark:border-gray-800" >
|
||||
<h2 className="mb-5 ">
|
||||
<Link
|
||||
id='blog-item-title'
|
||||
href={`/${post.slug}`}
|
||||
className="font-bold text-black text-xl md:text-2xl no-underline hover:underline">
|
||||
{post.title}
|
||||
</Link>
|
||||
</h2>
|
||||
|
||||
<div className="mb-4 text-sm text-gray-700 dark:text-gray-300">
|
||||
<span> <i className="fa-regular fa-user"></i> <a href={CONFIG_SIMPLE.AUTHOR_LINK}>{BLOG.AUTHOR}</a></span>
|
||||
<span> - <i className="fa-regular fa-clock"></i> {post.date?.start_date || post.createdTime}</span>
|
||||
{post.category && <span> - <i className="fa-regular fa-folder"></i> <a href="#" className="hover:text-red-400 transition-all duration-200">{post.category}</a></span>}
|
||||
{post.tags && post.tags?.length > 0 && post.tags.map(t => <span key={t}> / <Link href={`tag/${t}`}><span className=' hover:text-red-400 transition-all duration-200'>{t}</span></Link></span>)}
|
||||
</div>
|
||||
|
||||
<div className="text-gray-700 dark:text-gray-300 leading-normal mb-6">
|
||||
{post.summary}
|
||||
{post.summary && <span>...</span>}
|
||||
</div>
|
||||
|
||||
<div className='block'>
|
||||
<Link href={post.slug} className='inline-block rounded-sm text-blue-400 text-xs dark:border-gray-800 border hover:text-red-400 transition-all duration-200 hover:border-red-300 h-9 leading-8 px-5'>
|
||||
Continue Reading <i className="fa-solid fa-angle-right align-middle"></i>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
40
themes/simple/components/BlogListPage.js
Normal file
40
themes/simple/components/BlogListPage.js
Normal file
@@ -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 (
|
||||
<div className="w-full md:pr-8 mb-12">
|
||||
|
||||
<div id="container">
|
||||
{posts?.map(p => (
|
||||
<BlogItem key={p.id} post={p}/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between text-xs">
|
||||
<Link
|
||||
href={{ pathname: currentPage - 1 === 1 ? `${pagePrefix}/` : `${pagePrefix}/page/${currentPage - 1}`, query: router.query.s ? { s: router.query.s } : {} }}
|
||||
className={`${showPrev ? 'text-blue-400 border-b border-blue-400 ' : 'bg-gray pointer-events-none '} text-white no-underline pb-1 px-3`}>
|
||||
NEWER POSTS <i className="fa-solid fa-arrow-left"></i>
|
||||
</Link>
|
||||
<Link
|
||||
href={{ pathname: `${pagePrefix}/page/${currentPage + 1}`, query: router.query.s ? { s: router.query.s } : {} }}
|
||||
className={`${showNext ? 'text-blue-400 border-b border-blue-400 ' : 'bg-gray pointer-events-none '} text-white no-underline pb-1 px-3`}>
|
||||
OLDER POSTS <i className="fa-solid fa-arrow-right"></i>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
60
themes/simple/components/BlogListScroll.js
Normal file
60
themes/simple/components/BlogListScroll.js
Normal file
@@ -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 (
|
||||
<div id="container" className="w-full md:pr-8 mb-12" ref={targetRef}>
|
||||
{postsToShow.map(p => (
|
||||
<BlogItem key={p.id} post={p}/>
|
||||
))}
|
||||
|
||||
<div onClick={handleGetMore}
|
||||
className="w-full my-4 py-4 text-center cursor-pointer ">
|
||||
{' '}
|
||||
{hasMore ? locale.COMMON.MORE : `${locale.COMMON.NO_MORE} 😰`}{' '}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
50
themes/simple/components/CollapseMenu.js
Normal file
50
themes/simple/components/CollapseMenu.js
Normal file
@@ -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 <>
|
||||
<div className='w-full px-8 py-3 text-left border-b' onClick={toggleShow} >
|
||||
{!hasSubMenu && <Link
|
||||
href={link?.to}
|
||||
className="font-extralight flex justify-between pl-2 pr-4 dark:text-gray-200 no-underline tracking-widest pb-1">
|
||||
<span className='text-blue-400 hover:text-red-400 transition-all items-center duration-200'>{link?.name}</span>
|
||||
</Link>}
|
||||
{hasSubMenu && <div
|
||||
onClick={hasSubMenu ? toggleOpenSubMenu : null}
|
||||
className="font-extralight flex justify-between pl-2 pr-4 cursor-pointer dark:text-gray-200 no-underline tracking-widest pb-1">
|
||||
<span className='text-blue-400 hover:text-red-400 transition-all items-center duration-200'>{link?.name}</span>
|
||||
<i className='px-2 fa fa-plus text-gray-400'></i>
|
||||
</div>}
|
||||
</div>
|
||||
|
||||
{/* 折叠子菜单 */}
|
||||
{hasSubMenu && <Collapse isOpen={isOpen}>
|
||||
{link.subMenus.map(sLink => {
|
||||
return <div key={sLink.id} className='font-extralight text-left px-10 justify-start text-blue-400 bg-gray-50 hover:bg-gray-50 dark:hover:bg-gray-900 tracking-widest transition-all duration-200 border-b dark:border-gray-800 py-3 pr-6'>
|
||||
<Link href={sLink.to}>
|
||||
<span className='text-xs'>{sLink.title}</span>
|
||||
</Link>
|
||||
</div>
|
||||
})}
|
||||
</Collapse>}
|
||||
</>
|
||||
}
|
||||
37
themes/simple/components/DropMenu.js
Normal file
37
themes/simple/components/DropMenu.js
Normal file
@@ -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 <div onMouseOver={() => changeShow(true)} onMouseOut={() => changeShow(false)} >
|
||||
|
||||
{!hasSubMenu &&
|
||||
<Link
|
||||
href={link?.to}
|
||||
className="font-sans menu-link pl-2 pr-4 text-gray-700 dark:text-gray-200 no-underline tracking-widest pb-1">
|
||||
{link?.name}
|
||||
{hasSubMenu && <i className='px-2 fa fa-angle-down'></i>}
|
||||
</Link>}
|
||||
|
||||
{hasSubMenu && <>
|
||||
<div className='cursor-pointer font-sans menu-link pl-2 pr-4 text-gray-700 dark:text-gray-200 no-underline tracking-widest pb-1'>
|
||||
{link?.name}
|
||||
<i className='px-2 fa fa-angle-down'></i>
|
||||
</div>
|
||||
</>}
|
||||
|
||||
{/* 子菜单 */}
|
||||
{hasSubMenu && <ul className={`${show ? 'visible opacity-100' : 'invisible opacity-0'} border-gray-100 bg-white dark:bg-black dark:border-gray-800 transition-all duration-300 z-20 top-12 absolute block border drop-shadow-lg `}>
|
||||
{link.subMenus.map(sLink => {
|
||||
return <li key={sLink.id} className=' text-blue-500 hover:bg-gray-50 dark:hover:bg-gray-900 tracking-widest transition-all duration-200 border-b dark:border-gray-800 py-3 pr-6 pl-2'>
|
||||
<Link href={sLink.to}>
|
||||
<span className='text-xs font-extralight'>{sLink.title}</span>
|
||||
</Link>
|
||||
</li>
|
||||
})}
|
||||
</ul>}
|
||||
|
||||
</div>
|
||||
}
|
||||
35
themes/simple/components/ExampleRecentComments.js
Normal file
35
themes/simple/components/ExampleRecentComments.js
Normal file
@@ -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 && <div>Loading...<i className='ml-2 fas fa-spinner animate-spin' /></div>}
|
||||
{!onLoading && comments && comments.length === 0 && <div>No Comments</div>}
|
||||
{!onLoading && comments && comments.length > 0 && comments.map((comment) => <div key={comment.objectId} className='pb-2'>
|
||||
<div className='dark:text-gray-300 text-gray-600 text-xs waline-recent-content wl-content' dangerouslySetInnerHTML={{ __html: comment.comment }} />
|
||||
<div className='dark:text-gray-400 text-gray-400 font-sans text-sm text-right cursor-pointer hover:text-red-500 hover:underline pt-1'><Link href={{ pathname: comment.url, hash: comment.objectId, query: { target: 'comment' } }}>--{comment.nick}</Link></div>
|
||||
</div>)}
|
||||
|
||||
</>
|
||||
}
|
||||
|
||||
export default ExampleRecentComments
|
||||
30
themes/simple/components/Footer.js
Normal file
30
themes/simple/components/Footer.js
Normal file
@@ -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 <footer className="z-10 relative w-full bg-black px-6 border-t">
|
||||
<DarkModeButton className='text-center pt-4'/>
|
||||
|
||||
<div className="text-yellow-300 container mx-auto max-w-4xl py-6 md:flex flex-wrap md:flex-no-wrap md:justify-between items-center text-sm">
|
||||
<div className='text-center'> ©{`${copyrightDate}`} {BLOG.AUTHOR}. All rights reserved.</div>
|
||||
<div className="md:p-0 text-center md:text-right text-xs">
|
||||
{/* 右侧链接 */}
|
||||
{/* <a href="#" className="text-black no-underline hover:underline">Privacy Policy</a> */}
|
||||
{BLOG.BEI_AN && (<a href="https://beian.miit.gov.cn/" className="text-black dark:text-gray-200 no-underline hover:underline ml-4">{BLOG.BEI_AN} </a>)}
|
||||
<span className='no-underline ml-4'>
|
||||
Powered by
|
||||
<a href="https://github.com/tangly1024/NotionNext" className=' hover:underline'> NotionNext {BLOG.VERSION} </a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
}
|
||||
37
themes/simple/components/Header.js
Normal file
37
themes/simple/components/Header.js
Normal file
@@ -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 (
|
||||
<header className="text-center justify-between items-center px-6 bg-white h-80 dark:bg-black relative z-10">
|
||||
<div className="float-none inline-block py-12">
|
||||
<Link href='/'>
|
||||
{/* 可使用一张单图作为logo */}
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
{/* <img className='max-h-48 hover:opacity-60 duration-200 transition-all cursor-pointer' src={CONFIG_SIMPLE.LOGO_IMG}/> */}
|
||||
<div className='flex space-x-12'>
|
||||
<div className='hover:rotate-45 hover:scale-125 transform duration-200 cursor-pointer'>
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img src={siteInfo?.icon} className='rounded-full' width={160} alt={BLOG.AUTHOR} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className='text-3xl font-serif dark:text-white py-2 hover:scale-105 transform duration-200'>{BLOG.AUTHOR}</div>
|
||||
<div className='font-light dark:text-white py-2 hover:scale-105 transform duration-200 text-center' dangerouslySetInnerHTML={{ __html: CONFIG_SIMPLE.LOGO_DESCRIPTION }}/>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<div className='text-xs mt-4 text-gray-500 dark:text-gray-300'>{siteInfo?.description}</div>
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
35
themes/simple/components/JumpToTopButton.js
Normal file
35
themes/simple/components/JumpToTopButton.js
Normal file
@@ -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 <div title={locale.POST.TOP}
|
||||
className={(show ? ' opacity-100 ' : 'invisible opacity-0') + ' transition-all duration-300 flex items-center justify-center cursor-pointer bg-black h-10 w-10 bg-opacity-40 rounded-sm'}
|
||||
onClick={() => window.scrollTo({ top: 0, behavior: 'smooth' })}
|
||||
><i className='fas fa-angle-up text-white ' />
|
||||
</div>
|
||||
}
|
||||
|
||||
export default JumpToTopButton
|
||||
66
themes/simple/components/NavBar.js
Normal file
66
themes/simple/components/NavBar.js
Normal file
@@ -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 (
|
||||
<nav className="w-full bg-white md:pt-0 relative z-20 shadow border-t border-gray-100 dark:border-hexo-black-gray dark:bg-black">
|
||||
<div id="nav-bar-inner" className="h-12 mx-auto max-w-9/10 justify-between items-center text-sm md:text-md md:justify-start">
|
||||
{/* 左侧菜单 */}
|
||||
<div className="h-full w-full float-left text-center md:text-left flex flex-wrap items-stretch md:justify-start md:items-start space-x-4">
|
||||
{showSearchInput && <input id="theme-simple-search" onKeyUp={onKeyUp} className='float-left w-full outline-none h-full px-4' aria-label="Submit search" type="search" name="s" autoComplete="off" placeholder="Type then hit enter to search..." />}
|
||||
{!showSearchInput && (<NavBarMenu {...props}/>)}
|
||||
</div>
|
||||
|
||||
<div className="absolute right-12 h-full text-center px-2 flex items-center text-blue-400">
|
||||
{/* <!-- extra links --> */}
|
||||
<i className={showSearchInput ? 'fa-regular fa-circle-xmark' : 'fa-solid fa-magnifying-glass' + ' align-middle cursor-pointer'} onClick={toggleShowSearchInput}></i>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
78
themes/simple/components/NavBarMenu.js
Normal file
78
themes/simple/components/NavBarMenu.js
Normal file
@@ -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 (<>
|
||||
<div id='nav-menu-pc' className='hidden md:flex my-auto'>
|
||||
{links?.map(link => {
|
||||
if (link?.show) {
|
||||
return <DropMenu key={link.id} link={link} />
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
<div id='nav-menu-mobile' className='flex md:hidden my-auto justify-start'>
|
||||
<div onClick={toggleIsOpen} className='cursor-pointer hover:text-red-400 transition-all duration-200'>
|
||||
<i className='fa fa-bars mr-3' />
|
||||
<span>{!isOpen ? 'MENU' : 'CLOSE'}</span>
|
||||
</div>
|
||||
|
||||
<Collapse className='absolute w-full top-12 left-0' isOpen={isOpen}>
|
||||
<div id='menu-wrap' className='bg-white border'>
|
||||
{links?.map(link => {
|
||||
if (link?.show) {
|
||||
return <CollapseMenu key={link.id} link={link} />
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
</Collapse>
|
||||
</div>
|
||||
</>
|
||||
|
||||
)
|
||||
}
|
||||
19
themes/simple/components/SideBar.js
Normal file
19
themes/simple/components/SideBar.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import Live2D from '@/components/Live2D'
|
||||
import Announcement from './Announcement'
|
||||
|
||||
export const SideBar = (props) => {
|
||||
const { notice } = props
|
||||
return (
|
||||
<div className="hidden lg:block flex-none max-w-md sticky top-8 border-l dark:border-gray-800 pl-12 border-gray-100">
|
||||
|
||||
<aside>
|
||||
<Announcement post={notice}/>
|
||||
</aside>
|
||||
|
||||
<aside className=" overflow-hidden mb-6">
|
||||
<Live2D />
|
||||
</aside>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
36
themes/simple/components/SocialButton.js
Normal file
36
themes/simple/components/SocialButton.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import BLOG from '@/blog.config'
|
||||
import React from 'react'
|
||||
|
||||
/**
|
||||
* 社交联系方式按钮组
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
const SocialButton = () => {
|
||||
return <div className='w-52 justify-center flex-wrap flex'>
|
||||
<div className='space-x-3 text-xl text-gray-600 dark:text-gray-400 text-center'>
|
||||
{BLOG.CONTACT_GITHUB && <a target='_blank' rel='noreferrer' title={'github'} href={BLOG.CONTACT_GITHUB} >
|
||||
<i className='fab fa-github transform hover:scale-125 duration-150'/>
|
||||
</a>}
|
||||
{BLOG.CONTACT_TWITTER && <a target='_blank' rel='noreferrer' title={'twitter'} href={BLOG.CONTACT_TWITTER} >
|
||||
<i className='fab fa-twitter transform hover:scale-125 duration-150'/>
|
||||
</a>}
|
||||
{BLOG.CONTACT_TELEGRAM && <a target='_blank' rel='noreferrer' href={BLOG.CONTACT_TELEGRAM} title={'telegram'} >
|
||||
<i className='fab fa-telegram transform hover:scale-125 duration-150'/>
|
||||
</a>}
|
||||
{BLOG.CONTACT_LINKEDIN && <a target='_blank' rel='noreferrer' href={BLOG.CONTACT_LINKEDIN} title={'linkedIn'} >
|
||||
<i className='transform hover:scale-125 duration-150 fab fa-linkedin dark:hover:text-indigo-400 hover:text-indigo-600'/>
|
||||
</a>}
|
||||
{BLOG.CONTACT_WEIBO && <a target='_blank' rel='noreferrer' title={'weibo'} href={BLOG.CONTACT_WEIBO} >
|
||||
<i className='fab fa-weibo transform hover:scale-125 duration-150'/>
|
||||
</a>}
|
||||
{BLOG.CONTACT_EMAIL && <a target='_blank' rel='noreferrer' title={'email'} href={`mailto:${BLOG.CONTACT_EMAIL}`} >
|
||||
<i className='fas fa-envelope transform hover:scale-125 duration-150'/>
|
||||
</a>}
|
||||
<a target='_blank' rel='noreferrer' title={'RSS'} href={'/feed'} >
|
||||
<i className='fas fa-rss transform hover:scale-125 duration-150'/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
export default SocialButton
|
||||
19
themes/simple/components/Title.js
Normal file
19
themes/simple/components/Title.js
Normal file
@@ -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 <div className="text-center px-6 py-12 mb-6 bg-gray-100 dark:bg-hexo-black-gray dark:border-hexo-black-gray border-b">
|
||||
<h1 className=" text-xl md:text-4xl pb-4">{title}</h1>
|
||||
<p className="leading-loose text-gray-dark">
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
16
themes/simple/components/TopBar.js
Normal file
16
themes/simple/components/TopBar.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import CONFIG_SIMPLE from '../config_simple'
|
||||
|
||||
/**
|
||||
* 网站顶部 提示栏
|
||||
* @returns
|
||||
*/
|
||||
export const TopBar = (props) => {
|
||||
if (CONFIG_SIMPLE.TOP_BAR_CONTENT) {
|
||||
return <header className="flex justify-center items-center bg-black dark:bg-hexo-black-gray">
|
||||
<div id='top-bar-inner' className='max-w-9/10 w-full z-20'>
|
||||
<div className='text-xs text-center float-left text-white z-50 leading-5 py-2.5' dangerouslySetInnerHTML={{ __html: CONFIG_SIMPLE.TOP_BAR_CONTENT }}/>
|
||||
</div>
|
||||
</header>
|
||||
}
|
||||
return <></>
|
||||
}
|
||||
16
themes/simple/config_simple.js
Normal file
16
themes/simple/config_simple.js
Normal file
@@ -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 || '<div>编程爱好者<br/>/互联网从业者<br/>/知识分享博主</div>',
|
||||
|
||||
AUTHOR_LINK: process.env.NEXT_PUBLIC_AUTHOR_LINK || '#',
|
||||
|
||||
// 菜单配置
|
||||
MENU_CATEGORY: true, // 显示分类
|
||||
MENU_TAG: true, // 显示标签
|
||||
MENU_ARCHIVE: true, // 显示归档
|
||||
MENU_SEARCH: true // 显示搜索
|
||||
}
|
||||
export default CONFIG_SIMPLE
|
||||
25
themes/simple/index.js
Normal file
25
themes/simple/index.js
Normal file
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user