mirror of
https://github.com/d0zingcat/NotionNext.git
synced 2026-05-14 07:26:52 +00:00
Merge branch 'main' of https://github.com/tangly1024/NotionNext into release/V3.5.0
This commit is contained in:
@@ -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)
|
||||
|
||||
## 更新日志
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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() {
|
||||
|
||||
21
lib/cache/cache_manager.js
vendored
21
lib/cache/cache_manager.js
vendored
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 []
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
115
pages/[slug].js
115
pages/[slug].js
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ export const LayoutArchive = props => {
|
||||
</span>{' '}
|
||||
|
||||
<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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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'> ©{`${startYear}${currentYear}`} {BLOG.AUTHOR}. All rights reserved.</div>
|
||||
<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> */}
|
||||
|
||||
@@ -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>
|
||||
})}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -29,7 +29,7 @@ const BlogArchiveItem = ({ posts = [], archiveTitle }) => {
|
||||
<div id={post?.date?.start_date}>
|
||||
<span className="text-gray-400">{post.date?.start_date}</span>{' '}
|
||||
|
||||
<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>
|
||||
|
||||
@@ -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/></>}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -29,7 +29,7 @@ const BlogPostArchive = ({ posts = [], archiveTitle }) => {
|
||||
<div id={post?.date?.start_date}>
|
||||
<span className="text-gray-400">{post.date?.start_date}</span>{' '}
|
||||
|
||||
<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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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/></>}
|
||||
|
||||
|
||||
@@ -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={() => {
|
||||
|
||||
@@ -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'}>
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
21
themes/hexo/components/NavButtonGroup.js
Normal file
21
themes/hexo/components/NavButtonGroup.js
Normal 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
|
||||
@@ -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}/>
|
||||
|
||||
@@ -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, // 显示标签
|
||||
|
||||
@@ -45,7 +45,7 @@ export const LayoutArchive = props => {
|
||||
</span>{' '}
|
||||
|
||||
<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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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/></>}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -29,7 +29,7 @@ const BlogPostArchive = ({ posts = [], archiveTitle }) => {
|
||||
<div id={post?.date?.start_date}>
|
||||
<span className="text-gray-400">{post.date?.start_date}</span>{' '}
|
||||
|
||||
<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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 /></>}
|
||||
|
||||
|
||||
@@ -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'}>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user