Merge branch 'main' of https://github.com/tangly1024/NotionNext into release/V3.5.0

This commit is contained in:
tangly
2022-10-18 18:45:07 +08:00
54 changed files with 223 additions and 234 deletions

View File

@@ -80,7 +80,7 @@ yarn run start # 本地启动NextJS服务
- **框架**: [Next.js](https://nextjs.org)
- **样式**: [Tailwind CSS](https://www.tailwindcss.cn/) 和 `@tailwindcss/jit` compiler
- **渲染**: [React-notion-x](https://github.com/NotionX/react-notion-x)
- **评论**: [Giscus](https://giscus.app/zh-CN), [Gitalk](https://gitalk.github.io), [Cusdis](https://gitalk.github.io), [Utterances](https://utteranc.es)
- **评论**: [Giscus](https://giscus.app/zh-CN), [Gitalk](https://gitalk.github.io), [Cusdis](https://cusdis.com), [Utterances](https://utteranc.es)
- **图标**[fontawesome v5.15](https://fontawesome.com/v5.15/icons?d=gallery)
## 更新日志

View File

@@ -31,6 +31,9 @@ const BLOG = {
BACKGROUND_DARK: '#000000', // use hex value, don't forget '#'
SUB_PATH: '', // leave this empty unless you want to deploy in a folder
POST_URL_PREFIX: process.env.NEXT_PUBLIC_POST_URL_PREFIX || 'article', // POST类型文章的默认路径前缀例如默认POST类型的路径是 /article/[slug]
// 如果此项配置为 '' 空, 则文章将没有前缀路径,使用场景: 希望 文章前缀路径为 /post 的情况 支持多级
POST_LIST_STYLE: 'page', // ['page','scroll] 文章列表样式:页码分页、单页滚动加载
POST_LIST_PREVIEW: process.env.NEXT_PUBLIC_POST_PREVIEW || 'false', // 是否在列表加载文章预览
POST_PREVIEW_LINES: 12, // 预览博客行数
@@ -128,20 +131,22 @@ const BLOG = {
ADSENSE_GOOGLE_ID: process.env.NEXT_PUBLIC_ADSENSE_GOOGLE_ID || '', // 谷歌广告ID e.g ca-pub-xxxxxxxxxxxxxxxx
// 无关紧要的配置
// 自定义配置notion数据库字段名
NOTION_PROPERTY_NAME: {
password: 'password',
type: 'type',
title: 'title',
status: 'status',
summary: 'summary',
slug: 'slug',
category: 'category',
date: 'date',
tags: 'tags',
icon: 'icon'
password: process.env.NEXT_PUBLIC_NOTION_PROPERTY_PASSWORD || 'password',
type: process.env.NEXT_PUBLIC_NOTION_PROPERTY_TYPE || 'type',
title: process.env.NEXT_PUBLIC_NOTION_PROPERTY_TITLE || 'title',
status: process.env.NEXT_PUBLIC_NOTION_PROPERTY_STATUS || 'status',
summary: process.env.NEXT_PUBLIC_NOTION_PROPERTY_SUMMARY || 'summary',
slug: process.env.NEXT_PUBLIC_NOTION_PROPERTY_SLUG || 'slug',
category: process.env.NEXT_PUBLIC_NOTION_PROPERTY_CATEGORY || 'category',
date: process.env.NEXT_PUBLIC_NOTION_PROPERTY_DATE || 'date',
tags: process.env.NEXT_PUBLIC_NOTION_PROPERTY_TAGS || 'tags',
icon: process.env.NEXT_PUBLIC_NOTION_PROPERTY_ICON || 'icon'
},
ENABLE_CACHE: false, // 开启缓存 会将Notion数据缓存在内存中稍微提升访问速度但要更新内容需要多次刷新页面
AVATAR: '/avatar.png', // 作者头像被notion中的ICON覆盖。如果没有ICON则取public目录下的avatar.png
TITLE: process.env.NEXT_PUBLIC_TITLE || 'NotionNext BLOG', // 站点标题 被notion中的页面标题覆盖
DESCRIPTION:

View File

@@ -44,7 +44,7 @@ const CommonHead = ({ meta, children }) => {
<meta name="twitter:description" content={description} />
<meta name="twitter:title" content={title} />
{JSON.parse(BLOG.ANALYTICS_BUSUANZI_ENABLE) && <meta name="referrer" content="no-referrer-when-downgrade" />}
{meta?.type === 'article' && (
{meta?.type === 'Post' && (
<>
<meta
property="article:published_time"

View File

@@ -164,7 +164,7 @@ function fixCopy(codeCopy) {
*/
const mapPageUrl = id => {
// return 'https://www.notion.so/' + id.replace(/-/g, '')
return '/article/' + id.replace(/-/g, '')
return '/' + id.replace(/-/g, '')
}
function getMediumZoomMargin() {

View File

@@ -1,7 +1,7 @@
import MemoryCache from './memory_cache'
import FileCache from './local_file_cache'
import MongoCache from './mongo_db_cache'
const enableCache = true
import BLOG from '@/blog.config'
let api
if (process.env.MONGO_DB_URL && process.env.MONGO_DB_NAME) {
@@ -17,26 +17,27 @@ if (process.env.MONGO_DB_URL && process.env.MONGO_DB_NAME) {
* @param {*} key
* @returns
*/
export async function getDataFromCache(key) {
if (!enableCache) {
export async function getDataFromCache(key, force) {
if (BLOG.ENABLE_CACHE || force) {
const dataFromCache = await api.getCache(key)
if (JSON.stringify(dataFromCache) === '[]') {
return null
}
return api.getCache(key)
} else {
return null
}
const dataFromCache = await api.getCache(key)
if (JSON.stringify(dataFromCache) === '[]') {
return null
}
return api.getCache(key)
}
export async function setDataToCache(key, data) {
if (!enableCache || !data) {
if (!data) {
return
}
await api.setCache(key, data)
}
export async function delCacheData(key) {
if (!enableCache) {
if (!BLOG.ENABLE_CACHE) {
return
}
await api.delCache(key)

View File

@@ -11,10 +11,10 @@ export default function getAllPageIds (collectionQuery, collectionId, collection
view?.collection_group_results?.blockIds?.forEach(id => pageSet.add(id)) // table视图
})
pageIds = [...pageSet]
console.log('PageIds: 从collectionQuery获取', collectionQuery)
// console.log('PageIds: 从collectionQuery获取', collectionQuery, pageIds.length)
} else if (viewIds && viewIds.length > 0) {
const ids = collectionView[viewIds[0]].value.page_sort
console.log('PageIds: 从viewId获取', viewIds)
// console.log('PageIds: 从viewId获取', viewIds)
for (const id of ids) {
pageIds.push(id)
}

View File

@@ -7,7 +7,9 @@ import { isIterable } from '../utils'
* @param tagOptions tags的下拉选项
* @returns {Promise<{}|*[]>}
*/
export function getAllTags({ allPosts, sliceCount = 0, tagOptions }) {
export function getAllTags({ allPages, sliceCount = 0, tagOptions }) {
const allPosts = allPages.filter(page => page.type === 'Post')
if (!allPosts || !tagOptions) {
return []
}

View File

@@ -21,8 +21,7 @@ import getPageProperties from './getPageProperties'
*/
export async function getGlobalNotionData({
pageId = BLOG.NOTION_PAGE_ID,
from,
pageType = ['Post']
from
}) {
// 获取Notion数据
const notionPageData = deepClone(await getNotionPageData({ pageId, from }))
@@ -42,7 +41,9 @@ export async function getGlobalNotionData({
* @param {*}} param0
* @returns
*/
function getLatestPosts({ allPosts, from, latestPostCount }) {
function getLatestPosts({ allPages, from, latestPostCount }) {
const allPosts = allPages.filter(page => page.type === 'Post')
const latestPosts = Object.create(allPosts).sort((a, b) => {
const dateA = new Date(a?.lastEditedTime || a?.createdTime || a?.date?.start_date)
const dateB = new Date(b?.lastEditedTime || b?.createdTime || b?.date?.start_date)
@@ -82,7 +83,7 @@ function getCustomNav({ allPages }) {
const customNav = []
if (allPages && allPages.length > 0) {
allPages.forEach(p => {
if (p?.status?.[0] === 'Published') {
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 {
@@ -101,7 +102,7 @@ function getCustomNav({ allPages }) {
*/
function getTagOptions(schema) {
if (!schema) return {}
const tagSchema = Object.values(schema).find(e => e.name === 'tags')
const tagSchema = Object.values(schema).find(e => e.name === BLOG.NOTION_PROPERTY_NAME.tags)
return tagSchema?.options || []
}
@@ -112,7 +113,7 @@ function getTagOptions(schema) {
*/
function getCategoryOptions(schema) {
if (!schema) return {}
const categorySchema = Object.values(schema).find(e => e.name === 'category')
const categorySchema = Object.values(schema).find(e => e.name === BLOG.NOTION_PROPERTY_NAME.category)
return categorySchema?.options || []
}
@@ -121,7 +122,8 @@ function getCategoryOptions(schema) {
* @param allPosts
* @returns {Promise<{}|*[]>}
*/
function getAllCategories({ allPosts, categoryOptions, sliceCount = 0 }) {
function getAllCategories({ allPages, categoryOptions, sliceCount = 0 }) {
const allPosts = allPages.filter(page => page.type === 'Post')
if (!allPosts || !categoryOptions) {
return []
}
@@ -220,7 +222,7 @@ async function getPageRecordMapByNotionAPI({ pageId, from }) {
// Check Type Page-Database和Inline-Database
if (
rawMetadata?.type !== 'collection_view_page' &&
rawMetadata?.type !== 'collection_view'
rawMetadata?.type !== 'collection_view'
) {
console.warn(`pageId "${pageId}" is not a database`)
return null
@@ -251,17 +253,24 @@ async function getPageRecordMapByNotionAPI({ pageId, from }) {
collectionData.push(properties)
}
}
// 读取映射 配置
let postCount = 0
const allPages = collectionData.filter(post => {
return post.title && ['Page'].indexOf(post?.type?.[0]) > -1 && (post?.status?.[0] === 'Published' || post?.status?.[0] === 'Invisible')
})
const allPosts = collectionData.filter(post => {
return post.title && ['Post'].indexOf(post?.type?.[0]) > -1 && post?.status?.[0] === 'Published'
})
if (post.type === 'Post' && (post.status === 'Published' || post.status === 'Invisible')) {
postCount++
}
return post &&
post.type &&
(post.type === 'Post' || post.type === 'Page') &&
(post.status === 'Published' || post.status === 'Invisible')
}
)
// Sort by date
if (BLOG.POSTS_SORT_BY === 'date') {
allPosts.sort((a, b) => {
allPages.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
@@ -269,15 +278,13 @@ async function getPageRecordMapByNotionAPI({ pageId, from }) {
}
const customNav = getCustomNav({ allPages })
const postCount = allPosts?.length || 0
const categories = getAllCategories({ allPosts, categoryOptions, sliceCount: BLOG.PREVIEW_CATEGORY_COUNT })
const tags = getAllTags({ allPosts, tagOptions, sliceCount: BLOG.PREVIEW_TAG_COUNT })
const latestPosts = getLatestPosts({ allPosts, from, latestPostCount: 5 })
const categories = getAllCategories({ allPages, categoryOptions, sliceCount: BLOG.PREVIEW_CATEGORY_COUNT })
const tags = getAllTags({ allPages, tagOptions, sliceCount: BLOG.PREVIEW_TAG_COUNT })
const latestPosts = getLatestPosts({ allPages, from, latestPostCount: 5 })
return {
siteInfo,
allPages,
allPosts,
collection,
collectionQuery,
collectionId,

View File

@@ -58,6 +58,7 @@ async function getPageProperties(id, block, schema, authToken, tagOptions, siteI
}
}
}
// 设置自定义字段
const fieldNames = BLOG.NOTION_PROPERTY_NAME
if (fieldNames) {
@@ -65,7 +66,16 @@ async function getPageProperties(id, block, schema, authToken, tagOptions, siteI
if (fieldNames[key] && properties[fieldNames[key]]) properties[key] = properties[fieldNames[key]]
})
}
properties.slug = properties.slug ?? properties.id
properties.type = properties.type[0]
properties.status = properties.status[0]
if (properties.type === 'Post') {
properties.slug = BLOG.POST_URL_PREFIX + '/' + (properties.slug ?? properties.id)
} else {
properties.slug = (properties.slug ?? properties.id)
}
properties.createdTime = formatDate(new Date(value.created_time).toString(), BLOG.LANG)
properties.lastEditedTime = formatDate(new Date(value?.last_edited_time).toString(), BLOG.LANG)
properties.fullWidth = value.format?.page_full_width ?? false

View File

@@ -35,7 +35,7 @@ async function getPageWithRetry(id, from, retryAttempts = 3) {
const authToken = BLOG.NOTION_ACCESS_TOKEN || null
const api = new NotionAPI({ authToken, userTimeZone: 'Asia/ShangHai' })
const pageData = await api.getPage(id)
console.info('[响应成功]:', `from:${from}`, `id:${id}`)
console.info('[响应成功]:', `from:${from}`)
return pageData
} catch (e) {
console.warn('[响应异常]:', e)

View File

@@ -38,7 +38,7 @@ export async function generateRss(posts) {
feed.addItem({
title: post.title,
guid: `${post.id}`,
link: `${BLOG.LINK}/article/${post.slug}`,
link: `${BLOG.LINK}/${post.slug}`,
description: post.summary,
content: await createFeedContent(post),
date: new Date(post?.date?.start_date || post?.createdTime)

View File

@@ -62,8 +62,8 @@ const Slug = props => {
const meta = {
title: `${post?.title} | ${siteInfo?.title}`,
description: post?.summary,
type: 'article',
slug: 'article/' + post?.slug,
type: post.type,
slug: post?.slug,
image: post?.page_cover,
category: post?.category?.[0],
tags: post?.tags
@@ -95,17 +95,20 @@ export async function getStaticPaths() {
}
export async function getStaticProps({ params: { slug } }) {
const from = `slug-props-${slug}`
// slug 是个数组
const fullSlug = slug.join('/')
const from = `slug-props-${fullSlug}`
const props = await getGlobalNotionData({ from, pageType: ['Post'] })
const allPosts = props.allPosts
props.post = props.allPosts.find((p) => {
return p.slug === slug || p.id === idToUuid(slug)
props.post = props.allPages.find((p) => {
return p.slug === fullSlug || p.id === idToUuid(fullSlug)
})
if (!props.post) {
console.warn('无效地址', fullSlug)
return { props, revalidate: 1 }
}
props.post.blockMap = await getPostBlocks(props.post.id, 'slug')
const allPosts = props.allPages.filter(page => page.type === 'Post')
const index = allPosts.indexOf(props.post)
props.prev = allPosts.slice(index - 1, index)[0] ?? allPosts.slice(-1)[0]
props.next = allPosts.slice(index + 1, index + 2)[0] ?? allPosts[0]
@@ -114,6 +117,7 @@ export async function getStaticProps({ params: { slug } }) {
allPosts,
BLOG.POST_RECOMMEND_COUNT
)
delete props.allPages
return {
props,
revalidate: 1

View File

@@ -1,115 +0,0 @@
import BLOG from '@/blog.config'
import { getPostBlocks } from '@/lib/notion'
import { getGlobalNotionData } from '@/lib/notion/getNotionData'
import { useGlobal } from '@/lib/global'
import * as ThemeMap from '@/themes'
import React from 'react'
import { useRouter } from 'next/router'
import { isBrowser } from '@/lib/utils'
/**
* 根据notion的slug访问页面针对类型为Page的页面
* @param {*} props
* @returns
*/
const Slug = props => {
const { theme, changeLoadingState } = useGlobal()
const ThemeComponents = ThemeMap[theme]
const { post } = props
if (!post) {
changeLoadingState(true)
const router = useRouter()
setTimeout(() => {
if (isBrowser()) {
const article = document.getElementById('container')
if (!article) {
router.push('/404').then(() => {
console.warn('找不到页面', router.asPath)
})
}
}
}, 5000)
const meta = { title: `${props?.siteInfo?.title || BLOG.TITLE} | loading` }
return <ThemeComponents.LayoutSlug {...props} showArticleInfo={true} meta={meta} />
}
changeLoadingState(false)
// 文章锁🔐
const [lock, setLock] = React.useState(post.password && post.password !== '')
React.useEffect(() => {
if (post.password && post.password !== '') {
setLock(true)
} else {
setLock(false)
}
}, [post])
/**
* 验证文章密码
* @param {*} result
*/
const validPassword = result => {
if (result) {
setLock(false)
}
}
const { siteInfo } = props
const meta = {
title: `${post?.title} | ${siteInfo?.title}`,
description: post?.summary,
type: 'article',
slug: 'article/' + post?.slug,
image: post?.page_cover,
category: post?.category?.[0],
tags: post?.tags
}
props = { ...props, meta, lock, setLock, validPassword }
return <ThemeComponents.LayoutSlug {...props} showArticleInfo={false} />
}
export async function getStaticPaths() {
if (!BLOG.isProd) {
return {
paths: [],
fallback: true
}
}
const from = 'slug-paths'
const { allPages } = await getGlobalNotionData({ from, pageType: ['Page'] })
return {
paths: allPages.map(row => ({ params: { slug: row.slug } })),
fallback: true
}
}
export async function getStaticProps({ params: { slug } }) {
const from = `slug-props-${slug}`
const props = await getGlobalNotionData({ from, pageType: ['Page'] })
const { allPages } = props
const page = allPages?.find(p => p.slug === slug)
if (!page) {
return { props: {}, revalidate: 1 }
}
try {
page.blockMap = await getPostBlocks(page.id, 'slug')
} catch (error) {
console.error('获取文章详情失败', error)
}
props.post = page
return {
props,
revalidate: 1
}
}
export default Slug

View File

@@ -20,7 +20,10 @@ const ArchiveIndex = props => {
export async function getStaticProps() {
const props = await getGlobalNotionData({ from: 'archive-index' })
props.posts = props.allPosts
const { allPages } = props
const allPosts = allPages.filter(page => page.type === 'Post')
// 处理分页
props.posts = allPosts
return {
props,
revalidate: 1

View File

@@ -26,7 +26,9 @@ export default function Category(props) {
export async function getStaticProps({ params: { category } }) {
const from = 'category-props'
let props = await getGlobalNotionData({ from })
const posts = props.allPosts.filter(
const { allPages } = props
const allPosts = allPages.filter(page => page.type === 'Post')
const posts = allPosts.filter(
post => post && post.category && post.category.includes(category)
)
props = { ...props, posts, category }

View File

@@ -11,8 +11,9 @@ const Index = props => {
export async function getStaticProps() {
const from = 'index'
const props = await getGlobalNotionData({ from, pageType: ['Post'] })
const { allPosts, siteInfo } = props
const props = await getGlobalNotionData({ from })
const { allPages, siteInfo } = props
const allPosts = allPages.filter(page => page.type === 'Post')
const meta = {
title: `${siteInfo?.title} | ${siteInfo?.description}`,
description: siteInfo?.description,

View File

@@ -38,8 +38,10 @@ export async function getStaticProps({ params: { page } }) {
const from = `page-${page}`
const props = await getGlobalNotionData({ from })
props.page = page
const { allPages } = props
const allPosts = allPages.filter(page => page.type === 'Post')
// 处理分页
props.posts = props.allPosts.slice(
props.posts = allPosts.slice(
BLOG.POSTS_PER_PAGE * (page - 1),
BLOG.POSTS_PER_PAGE * page
)

View File

@@ -35,7 +35,9 @@ export async function getStaticProps({ params: { keyword } }) {
from: 'search-props',
pageType: ['Post']
})
props.posts = await filterByMemCache(props.allPosts, keyword)
const { allPages } = props
const allPosts = allPages.filter(page => page.type === 'Post')
props.posts = await filterByMemCache(allPosts, keyword)
props.keyword = keyword
return {
props,
@@ -107,7 +109,7 @@ async function filterByMemCache(allPosts, keyword) {
}
for (const post of allPosts) {
const cacheKey = 'page_block_' + post.id
const page = await getDataFromCache(cacheKey)
const page = await getDataFromCache(cacheKey, true)
const tagContent = post.tags && Array.isArray(post.tags) ? post.tags.join(' ') : ''
const categoryContent = post.category && Array.isArray(post.category) ? post.category.join(' ') : ''
const articleInfo = post.title + post.summary + tagContent + categoryContent
@@ -121,7 +123,7 @@ async function filterByMemCache(allPosts, keyword) {
indexContent = appendText(indexContent, properties, 'caption')
})
}
console.log('全文搜索缓存', cacheKey, page != null)
// console.log('全文搜索缓存', cacheKey, page != null)
post.results = []
let hitCount = 0
for (const i in indexContent) {

View File

@@ -52,7 +52,9 @@ export async function getStaticProps() {
from: 'search-props',
pageType: ['Post']
})
props.posts = props.allPosts
const { allPages } = props
const allPosts = allPages.filter(page => page.type === 'Post')
props.posts = allPosts
return {
props,
revalidate: 1

View File

@@ -28,7 +28,8 @@ export async function getStaticProps({ params: { tag } }) {
includePage: false,
tagsCount: 0
})
const { allPosts } = props
const { allPages } = props
const allPosts = allPages.filter(page => page.type === 'Post')
props.posts = allPosts.filter(
post => post && post.tags && post.tags.includes(tag)
)

View File

@@ -388,13 +388,11 @@
.notion-h {
position: relative;
display: inline-block;
display: block;
font-weight: 600;
line-height: 1.3;
padding: 3px 2px;
margin-bottom: 1px;
max-width: 100%;
white-space: pre-wrap;
word-break: break-word;
@@ -655,6 +653,10 @@ svg.notion-page-icon {
margin-bottom: 0;
}
.notion-list-numbered > .notion-list-numbered {
list-style-type: lower-alpha;
}
.notion-list-disc li {
padding-left: 0.1em;
}

View File

@@ -43,7 +43,7 @@ export const LayoutArchive = props => {
</span>{' '}
&nbsp;
<Link
href={`${BLOG.SUB_PATH}/article/${post.slug}`}
href={`${BLOG.SUB_PATH}/${post.slug}`}
passHref
>
<a className="dark:text-gray-400 dark:hover:text-gray-300 overflow-x-hidden hover:underline cursor-pointer text-gray-600">

View File

@@ -30,7 +30,7 @@ export const LayoutCategory = props => {
{postsToShow.map(p => (
<article key={p.id} className="mb-12" >
<h2 className="mb-4">
<Link href={`/article/${p.slug}`}>
<Link href={`/${p.slug}`}>
<a className="text-black text-xl md:text-2xl no-underline hover:underline"> {p.title}</a>
</Link>
</h2>

View File

@@ -62,7 +62,7 @@ export const LayoutSearch = props => {
{postsToShow.map(p => (
<article key={p.id} className="mb-12" >
<h2 className="mb-4">
<Link href={`/article/${p.slug}`}>
<Link href={`/${p.slug}`}>
<a className="text-black text-xl md:text-2xl no-underline hover:underline replace"> {p.title}</a>
</Link>
</h2>

View File

@@ -28,7 +28,7 @@ export const LayoutTag = props => {
{postsToShow.map(p => (
<article key={p.id} className="mb-12" >
<h2 className="mb-4">
<Link href={`/article/${p.slug}`}>
<Link href={`/${p.slug}`}>
<a className="text-black text-xl md:text-2xl no-underline hover:underline"> {p.title}</a>
</Link>
</h2>

View File

@@ -23,7 +23,7 @@ export const BlogList = (props) => {
{posts.map(p => (
<article key={p.id} className="mb-12" >
<h2 className="mb-4">
<Link href={`/article/${p.slug}`}>
<Link href={`/${p.slug}`}>
<a className="text-black dark:text-gray-100 text-xl md:text-2xl no-underline hover:underline"> {p.title}</a>
</Link>
</h2>

View File

@@ -4,13 +4,18 @@ import DarkModeButton from '@/components/DarkModeButton'
export const Footer = (props) => {
const d = new Date()
const currentYear = d.getFullYear()
const startYear = BLOG.SINCE && BLOG.SINCE !== currentYear && BLOG.SINCE + '-'
const copyrightDate = (function() {
if (Number.isInteger(BLOG.SINCE) && BLOG.SINCE < currentYear) {
return BLOG.SINCE + '-' + currentYear
}
return currentYear
})()
return <footer className="w-full bg-white px-6 border-t dark:border-hexo-black-gray dark:bg-hexo-black-gray ">
<DarkModeButton className='text-center pt-4'/>
<div className="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'> &copy;{`${startYear}${currentYear}`} {BLOG.AUTHOR}. All rights reserved.</div>
<div className='text-center'> &copy;{`${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> */}

View File

@@ -30,7 +30,7 @@ export const SideBar = (props) => {
<div className="p-4">
<ul className="list-reset leading-normal">
{latestPosts?.map(p => {
return <Link key={p.id} href={`/article/${p.slug}`} passHref>
return <Link key={p.id} href={`/${p.slug}`} passHref>
<li> <a href="#" className="text-gray-darkest text-sm">{p.title}</a></li>
</Link>
})}

View File

@@ -10,12 +10,12 @@ export default function ArticleAround ({ prev, next }) {
return <></>
}
return <section className='text-gray-800 h-28 flex items-center justify-between space-x-5 my-4'>
<Link href={`/article/${prev.slug}`} passHref>
<Link href={`/${prev.slug}`} passHref>
<a className='text-sm cursor-pointer justify-center items-center flex w-full h-full bg-white bg-opacity-40 hover:bg-hexo-black-gray dark:bg-hexo-black-gray dark:text-gray-200 hover:text-white duration-300'>
<i className='mr-1 fas fa-angle-double-left' />{prev.title}
</a>
</Link>
<Link href={`/article/${next.slug}`} passHref>
<Link href={`/${next.slug}`} passHref>
<a className='text-sm cursor-pointer justify-center items-center flex w-full h-full bg-white bg-opacity-40 hover:bg-hexo-black-gray dark:bg-hexo-black-gray dark:text-gray-200 hover:text-white duration-300'>{next.title}
<i className='ml-1 my-1 fas fa-angle-double-right' />
</a>

View File

@@ -13,7 +13,7 @@ const BlogCard = ({ post, showSummary }) => {
className="animate__animated animate__fadeIn flex flex-col-reverse justify-between duration-300"
>
<div className="p-2 flex flex-col w-full">
<Link href={`${BLOG.SUB_PATH}/article/${post.slug}`} passHref>
<Link href={`${BLOG.SUB_PATH}/${post.slug}`} passHref>
<a
className={`cursor-pointer font-bold hover:underline text-xl ${showPreview ? 'justify-center' : 'justify-start'
} leading-tight text-gray-700 dark:text-gray-100 hover:text-blue-500 dark:hover:text-blue-400`}
@@ -30,7 +30,7 @@ const BlogCard = ({ post, showSummary }) => {
</div>
{CONFIG_FUKA.POST_LIST_COVER && post?.page_cover && (
<Link href={`${BLOG.SUB_PATH}/article/${post.slug}`} passHref>
<Link href={`${BLOG.SUB_PATH}/${post.slug}`} passHref>
<div className="h-40 w-full relative duration-200 cursor-pointer transform overflow-hidden">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img

View File

@@ -29,7 +29,7 @@ const BlogArchiveItem = ({ posts = [], archiveTitle }) => {
<div id={post?.date?.start_date}>
<span className="text-gray-400">{post.date?.start_date}</span>{' '}
&nbsp;
<Link href={`${BLOG.SUB_PATH}/article/${post.slug}`} passHref>
<Link href={`${BLOG.SUB_PATH}/${post.slug}`} passHref>
<a className="dark:text-gray-400 dark:hover:text-gray-300 overflow-x-hidden hover:underline cursor-pointer text-gray-600">
{post.title}
</a>

View File

@@ -3,12 +3,18 @@ import BLOG from '@/blog.config'
function SiteInfo ({ title }) {
const d = new Date()
const currentYear = d.getFullYear()
const startYear = BLOG.SINCE && BLOG.SINCE !== currentYear && BLOG.SINCE + '-'
const copyrightDate = (function() {
if (Number.isInteger(BLOG.SINCE) && BLOG.SINCE < currentYear) {
return BLOG.SINCE + '-' + currentYear
}
return currentYear
})()
return (
<footer
className='leading-6 justify-start w-full text-gray-400 text-xs font-sans'
>
<span> © {`${startYear}${currentYear}`} <span> <a href={BLOG.LINK} className='text-gray-500 dark:text-gray-300 '> <i className='mx-1 animate-pulse fas fa-heart'/> {BLOG.AUTHOR}</a>. <br /></span>
<span> © {`${copyrightDate}`} <span> <a href={BLOG.LINK} className='text-gray-500 dark:text-gray-300 '> <i className='mx-1 animate-pulse fas fa-heart'/> {BLOG.AUTHOR}</a>. <br /></span>
{BLOG.BEI_AN && <><i className='fas fa-shield-alt' /> <a href='https://beian.miit.gov.cn/' className='mr-2'>{BLOG.BEI_AN}</a><br/></>}

View File

@@ -11,12 +11,12 @@ export default function ArticleAdjacent ({ prev, next }) {
return <></>
}
return <section className='text-gray-800 items-center text-xs md:text-sm flex justify-between m-1 '>
<Link href={`/article/${prev.slug}`} passHref>
<Link href={`/${prev.slug}`} passHref>
<a className='py-1 cursor-pointer hover:underline justify-start items-center dark:text-white flex w-full h-full duration-200'>
<i className='mr-1 fas fa-angle-left' />{prev.title}
</a>
</Link>
<Link href={`/article/${next.slug}`} passHref>
<Link href={`/${next.slug}`} passHref>
<a className='py-1 cursor-pointer hover:underline justify-end items-center dark:text-white flex w-full h-full duration-200'>{next.title}
<i className='ml-1 my-1 fas fa-angle-right' />
</a>

View File

@@ -35,7 +35,7 @@ export default function ArticleRecommend({ recommendPosts, siteInfo }) {
<Link
key={post.id}
title={post.title}
href={`${BLOG.SUB_PATH}/article/${post.slug}`}
href={`${BLOG.SUB_PATH}/${post.slug}`}
passHref
>
<a

View File

@@ -29,7 +29,7 @@ const BlogPostArchive = ({ posts = [], archiveTitle }) => {
<div id={post?.date?.start_date}>
<span className="text-gray-400">{post.date?.start_date}</span>{' '}
&nbsp;
<Link href={`${BLOG.SUB_PATH}/article/${post.slug}`} passHref>
<Link href={`${BLOG.SUB_PATH}/${post.slug}`} passHref>
<a className="dark:text-gray-400 dark:hover:text-indigo-300 overflow-x-hidden hover:underline cursor-pointer text-gray-600">
{post.title}
</a>

View File

@@ -14,7 +14,7 @@ const BlogPostCard = ({ post, showSummary }) => {
className="animate__animated animate__fadeIn flex flex-col-reverse lg:flex-row justify-between duration-300"
>
<div className="lg:p-8 p-4 flex flex-col w-full">
<Link href={`${BLOG.SUB_PATH}/article/${post.slug}`} passHref>
<Link href={`${BLOG.SUB_PATH}/${post.slug}`} passHref>
<a
className={`replace cursor-pointer hover:underline text-2xl font-sans ${showPreview ? 'text-center' : ''
} leading-tight text-gray-700 dark:text-gray-100 hover:text-indigo-700 dark:hover:text-indigo-400`}
@@ -79,7 +79,7 @@ const BlogPostCard = ({ post, showSummary }) => {
</div>
{CONFIG_HEXO.POST_LIST_COVER && !showPreview && post?.page_cover && !post.results && (
<Link href={`${BLOG.SUB_PATH}/article/${post.slug}`} passHref>
<Link href={`${BLOG.SUB_PATH}/${post.slug}`} passHref>
<div className="flex w-full relative duration-200 rounded-t-xl lg:rounded-r-xl lg:rounded-t-none cursor-pointer transform overflow-hidden">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img

View File

@@ -5,14 +5,20 @@ import DarkModeButton from '@/components/DarkModeButton'
const Footer = ({ title }) => {
const d = new Date()
const currentYear = d.getFullYear()
const startYear = BLOG.SINCE && BLOG.SINCE !== currentYear && BLOG.SINCE + '-'
const copyrightDate = (function() {
if (Number.isInteger(BLOG.SINCE) && BLOG.SINCE < currentYear) {
return BLOG.SINCE + '-' + currentYear
}
return currentYear
})()
return (
<footer
className='font-sans dark:bg-black flex-shrink-0 bg-hexo-light-gray justify-center text-center m-auto w-full leading-6 text-gray-600 dark:text-gray-100 text-sm p-6'
>
<DarkModeButton/>
<i className='fas fa-copyright' /> {`${startYear}${currentYear}`} <span><i className='mx-1 animate-pulse fas fa-heart'/> <a href={BLOG.LINK} className='underline font-bold dark:text-gray-300 '>{BLOG.AUTHOR}</a>.<br/>
<i className='fas fa-copyright' /> {`${copyrightDate}`} <span><i className='mx-1 animate-pulse fas fa-heart'/> <a href={BLOG.LINK} className='underline font-bold dark:text-gray-300 '>{BLOG.AUTHOR}</a>.<br/>
{BLOG.BEI_AN && <><i className='fas fa-shield-alt' /> <a href='https://beian.miit.gov.cn/' className='mr-2'>{BLOG.BEI_AN}</a><br/></>}

View File

@@ -1,6 +1,7 @@
import { useEffect, useState } from 'react'
import Typed from 'typed.js'
import CONFIG_HEXO from '../config_hexo'
import NavButtonGroup from './NavButtonGroup'
let wrapperTop = 0
let windowTop = 0
@@ -74,7 +75,7 @@ const Header = props => {
className="duration-500 md:bg-fixed w-full bg-cover bg-center h-screen bg-black text-white"
style={{
backgroundImage:
`linear-gradient(rgba(0, 0, 0, 0.8), rgba(0,0,0,0.2), rgba(0,0,0,0.2), rgba(0,0,0,0.2), rgba(0, 0, 0, 0.8) ),url("${siteInfo?.pageCover}")`
`linear-gradient(rgba(0, 0, 0, 0.9), rgba(0,0,0,0.5), rgba(0,0,0,0.3), rgba(0,0,0,0.5), rgba(0, 0, 0, 0.9) ),url("${siteInfo?.pageCover}")`
}}
>
<div className="absolute flex flex-col h-full items-center justify-center w-full font-sans">
@@ -82,6 +83,10 @@ const Header = props => {
<div className='mt-2 h-12 items-center text-center shadow-text text-white text-lg'>
<span id='typed'/>
</div>
{/* 首页导航插件 */}
{ CONFIG_HEXO.HOME_NAV_BUTTONS && <NavButtonGroup {...props}/>}
</div>
<div
onClick={() => {

View File

@@ -25,7 +25,7 @@ const LatestPostsGroup = ({ latestPosts, siteInfo }) => {
</div>
</div>
{latestPosts.map(post => {
const selected = currentPath === `${BLOG.SUB_PATH}/article/${post.slug}`
const selected = currentPath === `${BLOG.SUB_PATH}/${post.slug}`
const headerImage = post?.page_cover
? `url("${post.page_cover}")`
: `url("${siteInfo?.pageCover}")`
@@ -34,7 +34,7 @@ const LatestPostsGroup = ({ latestPosts, siteInfo }) => {
<Link
key={post.id}
title={post.title}
href={`${BLOG.SUB_PATH}/article/${post.slug}`}
href={`${BLOG.SUB_PATH}/${post.slug}`}
passHref
>
<a className={'my-1 flex font-sans'}>

View File

@@ -6,7 +6,7 @@ const Logo = props => {
const { siteInfo } = props
return <Link href='/' passHref>
<div className='flex flex-col justify-center items-center cursor-pointer space-y-3'>
<div className='font-sans text-lg p-1.5 rounded bg-black text-white dark:border-white border-black border'> {siteInfo?.title || BLOG.TITLE}</div>
<div className='font-sans text-lg p-1.5 rounded dark:border-white hover:scale-110 transform duration-200'> {siteInfo?.title || BLOG.TITLE}</div>
</div>
</Link>
}

View File

@@ -9,9 +9,9 @@ const MenuButtonGroupTop = (props) => {
let links = [
{ icon: 'fas fa-search', name: locale.NAV.SEARCH, to: '/search', show: CONFIG_HEXO.MENU_SEARCH },
{ icon: 'fas fa-archive', name: locale.COMMON.ARTICLE, to: '/archive', show: CONFIG_HEXO.MENU_ARCHIVE },
{ icon: 'fas fa-folder', name: locale.COMMON.CATEGORY, to: '/category', show: CONFIG_HEXO.MENU_CATEGORY },
{ icon: 'fas fa-tag', name: locale.COMMON.TAGS, to: '/tag', show: CONFIG_HEXO.MENU_TAG }
{ icon: 'fas fa-archive', name: locale.NAV.ARCHIVE, to: '/archive', show: CONFIG_HEXO.MENU_ARCHIVE }
// { icon: 'fas fa-folder', name: locale.COMMON.CATEGORY, to: '/category', show: CONFIG_HEXO.MENU_CATEGORY },
// { icon: 'fas fa-tag', name: locale.COMMON.TAGS, to: '/tag', show: CONFIG_HEXO.MENU_TAG }
]
if (customNav) {
@@ -22,8 +22,8 @@ const MenuButtonGroupTop = (props) => {
{links.map(link => {
if (link.show) {
return <Link key={`${link.to}`} title={link.to} href={link.to} >
<a target={link.to.indexOf('http') === 0 ? '_blank' : '_self'} className={'py-1.5 my-1 px-3 duration-300 text-base justify-center items-center cursor-pointer'} >
<div className='w-full flex text-sm items-center justify-center hover:scale-105 transform'>
<a target={link.to.indexOf('http') === 0 ? '_blank' : '_self'} className={'py-1.5 my-1 px-3 text-base justify-center items-center cursor-pointer'} >
<div className='w-full flex text-sm items-center justify-center hover:scale-125 duration-200 transform'>
<i className={`${link.icon} mr-1`}/>
<div className='text-center'>{link.name}</div>
</div>

View File

@@ -0,0 +1,21 @@
import React from 'react'
import Link from 'next/link'
/**
* 首页导航大按钮组件
* @param {*} props
* @returns
*/
const NavButtonGroup = (props) => {
const { categories } = props
return <nav id='home-nav-button' className={'py-5 md:mt-24 mt-12 flex md:max-w-3xl md:space-x-12 md:flex justify-center'}>
{categories.map(category => {
return <Link key={`${category.name}`} title={`${category.name}`} href={`/category/${category.name}`} passHref>
<a className='text-center w-full md:w-40 md:h-20 h-14 mr-4 mb-4 justify-center items-center flex border-white border-2 cursor-pointer rounded-lg font-serif hover:bg-white hover:text-black duration-200 font-bold hover:scale-110 transform'>{category.name}</a>
</Link>
})}
</nav>
}
export default NavButtonGroup

View File

@@ -30,6 +30,7 @@ const TopNav = props => {
const nav = document.querySelector('#sticky-nav')
const header = document.querySelector('#header')
const showNav = scrollS <= windowTop || scrollS < 5 || (header && scrollS <= header.clientHeight)// 非首页无大图时影藏顶部 滚动条置顶时隐藏
// 是否将导航栏透明
const navTransparent = (scrollS < document.documentElement.clientHeight - 12 && router.route === '/') || scrollS < 300 // 透明导航条的条件
if (header && navTransparent) {
@@ -37,11 +38,13 @@ const TopNav = props => {
nav && nav.classList.replace('text-black', 'text-white')
nav && nav.classList.replace('border', 'border-transparent')
nav && nav.classList.replace('shadow-md', 'shadow-none')
nav && nav.classList.replace('dark:bg-hexo-black-gray', 'transparent')
} else {
nav && nav.classList.replace('bg-none', 'bg-white')
nav && nav.classList.replace('text-white', 'text-black')
nav && nav.classList.replace('border-transparent', 'border')
nav && nav.classList.replace('shadow-none', 'shadow-md')
nav && nav.classList.replace('transparent', 'dark:bg-hexo-black-gray')
}
if (!showNav) {
@@ -118,7 +121,7 @@ const TopNav = props => {
<SearchDrawer cRef={searchDrawer} slot={searchDrawerSlot}/>
{/* 导航栏 */}
<div id='sticky-nav' className={'top-0 shadow-md fixed bg-none animate__animated animate__fadeIn dark:bg-hexo-black-gray dark:text-gray-200 text-black w-full z-20 transform duration-200 font-san border-transparent dark:border-transparent'}>
<div id='sticky-nav' className={'top-0 shadow-md fixed bg-none animate__animated animate__fadeIn dark:bg-hexo-black-gray dark:text-gray-200 text-black w-full z-20 transform duration-200 font-san border-transparent dark:border-transparent'}>
<div className='w-full flex justify-between items-center px-4 py-2'>
<div className='flex'>
<Logo {...props}/>

View File

@@ -2,6 +2,8 @@ const CONFIG_HEXO = {
HOME_BANNER_ENABLE: true,
HOME_BANNER_GREETINGS: ['Hi我是一个程序员', 'Hi我是一个打工人', 'Hi我是一个干饭人', '欢迎来到我的博客🎉'], // 首页大图标语文字
HOME_NAV_BUTTONS: true, // 首页是否显示分类大图标按钮
// 菜单配置
MENU_CATEGORY: true, // 显示分类
MENU_TAG: true, // 显示标签

View File

@@ -45,7 +45,7 @@ export const LayoutArchive = props => {
</span>{' '}
&nbsp;
<Link
href={`${BLOG.SUB_PATH}/article/${post.slug}`}
href={`${BLOG.SUB_PATH}/${post.slug}`}
passHref
>
<a className="dark:text-gray-400 dark:hover:text-gray-300 overflow-x-hidden hover:underline cursor-pointer text-gray-600">

View File

@@ -10,12 +10,12 @@ export default function ArticleAround ({ prev, next }) {
return <></>
}
return <section className='text-gray-800 h-12 flex items-center justify-between space-x-5 my-4'>
<Link href={`/article/${prev.slug}`} passHref>
<Link href={`/${prev.slug}`} passHref>
<a className='text-sm cursor-pointer justify-start items-center flex hover:underline duration-300'>
<i className='mr-1 fas fa-angle-double-left' />{prev.title}
</a>
</Link>
<Link href={`/article/${next.slug}`} passHref>
<Link href={`/${next.slug}`} passHref>
<a className='text-sm cursor-pointer justify-end items-center flex hover:underline duration-300'>{next.title}
<i className='ml-1 my-1 fas fa-angle-double-right' />
</a>

View File

@@ -16,7 +16,7 @@ const BlogPostCard = ({ post, showSummary }) => {
className="animate__animated animate__fadeIn duration-300 mb-6 max-w-7xl border-b dark:border-gray-800 "
>
<div className="lg:p-8 p-4 flex flex-col w-full">
<Link href={`${BLOG.SUB_PATH}/article/${post.slug}`} passHref>
<Link href={`${BLOG.SUB_PATH}/${post.slug}`} passHref>
<a
className={
'cursor-pointer font-bold font-sans hover:underline text-3xl leading-tight text-gray-700 dark:text-gray-100 hover:text-green-500 dark:hover:text-green-400'
@@ -54,7 +54,7 @@ const BlogPostCard = ({ post, showSummary }) => {
<NotionPage post={post} />
<div className="pointer-events-none border-t pt-8 border-dashed">
<div className="w-full justify-start flex">
<Link href={`${BLOG.SUB_PATH}/article/${post.slug}`} passHref>
<Link href={`${BLOG.SUB_PATH}/${post.slug}`} passHref>
<a className="hover:bg-opacity-100 hover:scale-105 duration-200 pointer-events-auto transform font-bold text-green-500 cursor-pointer">
{locale.COMMON.ARTICLE_DETAIL}
<i className="ml-1 fas fa-angle-right" />

View File

@@ -5,13 +5,19 @@ import DarkModeButton from '@/components/DarkModeButton'
const Footer = ({ title }) => {
const d = new Date()
const currentYear = d.getFullYear()
const startYear = BLOG.SINCE && BLOG.SINCE !== currentYear && BLOG.SINCE + '-'
const copyrightDate = (function() {
if (Number.isInteger(BLOG.SINCE) && BLOG.SINCE < currentYear) {
return BLOG.SINCE + '-' + currentYear
}
return currentYear
})()
return (
<footer
className='dark:bg-hexo-black-gray flex-shrink-0 justify-center text-center m-auto w-full leading-6 text-gray-400 text-sm p-6'
>
<DarkModeButton/>
<i className='fas fa-copyright' /> {`${startYear}${currentYear}`} <span><i className='mx-1 animate-pulse fas fa-heart'/> <a href={BLOG.LINK} className='underline font-bold text-gray-500 dark:text-gray-300 '>{BLOG.AUTHOR}</a>.<br/>
<i className='fas fa-copyright' /> {`${copyrightDate}`} <span><i className='mx-1 animate-pulse fas fa-heart'/> <a href={BLOG.LINK} className='underline font-bold text-gray-500 dark:text-gray-300 '>{BLOG.AUTHOR}</a>.<br/>
{BLOG.BEI_AN && <><i className='fas fa-shield-alt'/> <a href='https://beian.miit.gov.cn/' className='mr-2'>{BLOG.BEI_AN}</a><br/></>}

View File

@@ -10,12 +10,12 @@ export default function BlogAround ({ prev, next }) {
return <></>
}
return <section className='text-gray-800 border-t dark:text-gray-300 flex flex-wrap lg:flex-nowrap lg:space-x-10 justify-between py-2'>
{prev && <Link href={`/article/${prev.slug}`} passHref>
{prev && <Link href={`/${prev.slug}`} passHref>
<a className='text-sm py-3 text-gray-400 hover:underline cursor-pointer'>
<i className='mr-1 fas fa-angle-double-left' />{prev.title}
</a>
</Link>}
{next && <Link href={`/article/${next.slug}`} passHref>
{next && <Link href={`/${next.slug}`} passHref>
<a className='text-sm flex py-3 text-gray-400 hover:underline cursor-pointer'>{next.title}
<i className='ml-1 my-1 fas fa-angle-double-right' />
</a>

View File

@@ -29,7 +29,7 @@ const BlogPostArchive = ({ posts = [], archiveTitle }) => {
<div id={post?.date?.start_date}>
<span className="text-gray-400">{post.date?.start_date}</span>{' '}
&nbsp;
<Link href={`${BLOG.SUB_PATH}/article/${post.slug}`} passHref>
<Link href={`${BLOG.SUB_PATH}/${post.slug}`} passHref>
<a className="dark:text-gray-400 dark:hover:text-gray-300 overflow-x-hidden hover:underline cursor-pointer text-gray-600">
{post.title}
</a>

View File

@@ -18,7 +18,7 @@ const BlogPostCard = ({ post, showSummary }) => {
className="flex flex-col-reverse justify-between duration-300"
>
<div className="lg:p-8 p-4 flex flex-col w-full">
<Link href={`${BLOG.SUB_PATH}/article/${post.slug}`} passHref>
<Link href={`${BLOG.SUB_PATH}/${post.slug}`} passHref>
<a
className={`cursor-pointer font-bold hover:underline text-3xl ${showPreview ? 'text-center' : ''
} leading-tight text-gray-700 dark:text-gray-100 hover:text-blue-500 dark:hover:text-blue-400`}
@@ -84,7 +84,7 @@ const BlogPostCard = ({ post, showSummary }) => {
)}
<div className="text-right border-t pt-8 border-dashed">
<Link href={`${BLOG.SUB_PATH}/article/${post.slug}`}>
<Link href={`${BLOG.SUB_PATH}/${post.slug}`}>
<a className="hover:bg-opacity-100 hover:underline transform duration-300 p-3 text-white bg-gray-800 dark:bg-black cursor-pointer">
{locale.COMMON.ARTICLE_DETAIL}
<i className="ml-1 fas fa-angle-right" />
@@ -94,7 +94,7 @@ const BlogPostCard = ({ post, showSummary }) => {
</div>
{CONFIG_NEXT.POST_LIST_COVER && post?.page_cover && (
<Link href={`${BLOG.SUB_PATH}/article/${post.slug}`} passHref>
<Link href={`${BLOG.SUB_PATH}/${post.slug}`} passHref>
<div className="h-72 w-full relative duration-200 cursor-pointer transform overflow-hidden">
<Image
className="hover:scale-105 transform duration-500"

View File

@@ -5,14 +5,20 @@ import DarkModeButton from '@/components/DarkModeButton'
const Footer = ({ title }) => {
const d = new Date()
const currentYear = d.getFullYear()
const startYear = BLOG.SINCE && BLOG.SINCE !== currentYear && BLOG.SINCE + '-'
const copyrightDate = (function() {
if (Number.isInteger(BLOG.SINCE) && BLOG.SINCE < currentYear) {
return BLOG.SINCE + '-' + currentYear
}
return currentYear
})()
return (
<footer
className='dark:bg-gray-900 flex-shrink-0 justify-center text-center m-auto w-full leading-6 text-sm p-6 dark:text-gray-400'
>
<DarkModeButton/>
<span>
<i className='fas fa-copyright' /> {`${startYear}${currentYear}`} <span className='mx-1 animate-pulse'><i className='fas fa-heart' /></span> <a href={BLOG.LINK} className='underline font-bold '>{BLOG.AUTHOR}</a>.<br />
<i className='fas fa-copyright' /> {`${copyrightDate}`} <span className='mx-1 animate-pulse'><i className='fas fa-heart' /></span> <a href={BLOG.LINK} className='underline font-bold '>{BLOG.AUTHOR}</a>.<br />
{BLOG.BEI_AN && <><i className='fas fa-shield-alt' /> <a href='https://beian.miit.gov.cn/' className='mr-2'>{BLOG.BEI_AN}</a><br /></>}

View File

@@ -26,12 +26,12 @@ const LatestPostsGroup = ({ latestPosts }) => {
</div>
</div>
{latestPosts.map(post => {
const selected = currentPath === `${BLOG.SUB_PATH}/article/${post.slug}`
const selected = currentPath === `${BLOG.SUB_PATH}/${post.slug}`
return (
<Link
key={post.id}
title={post.title}
href={`${BLOG.SUB_PATH}/article/${post.slug}`}
href={`${BLOG.SUB_PATH}/${post.slug}`}
passHref
>
<a className={'my-1 flex font-light'}>

View File

@@ -18,7 +18,7 @@ const RecommendPosts = ({ recommendPosts }) => {
<ul className="font-light text-sm">
{recommendPosts.map(post => (
<li className="py-1" key={post.id}>
<Link href={`/article/${post.slug}`}>
<Link href={`/${post.slug}`}>
<a className="cursor-pointer hover:underline">
{post.title}
</a>