Merge branch 'main' into feat/new-theme-landing

This commit is contained in:
tangly1024.com
2023-07-05 12:30:45 +08:00
37 changed files with 338 additions and 100 deletions

View File

@@ -1,2 +1,2 @@
# 环境变量 @see https://www.nextjs.cn/docs/basic-features/environment-variables # 环境变量 @see https://www.nextjs.cn/docs/basic-features/environment-variables
NEXT_PUBLIC_VERSION=3.16.2 NEXT_PUBLIC_VERSION=3.16.3

View File

@@ -5,7 +5,7 @@ const BLOG = {
process.env.NOTION_PAGE_ID || '02ab3b8678004aa69e9e415905ef32a5', process.env.NOTION_PAGE_ID || '02ab3b8678004aa69e9e415905ef32a5',
PSEUDO_STATIC: false, // 伪静态路径开启后所有文章URL都以 .html 结尾。 PSEUDO_STATIC: false, // 伪静态路径开启后所有文章URL都以 .html 结尾。
NEXT_REVALIDATE_SECOND: process.env.NEXT_PUBLIC_REVALIDATE_SECOND || 5, // 更新内容缓存间隔 单位(秒)即每个页面有5秒的纯静态期、此期间无论多少次访问都不会抓取notion数据调大该值有助于节省Vercel资源、同时提升访问速率但也会使文章更新有延迟。 NEXT_REVALIDATE_SECOND: process.env.NEXT_PUBLIC_REVALIDATE_SECOND || 5, // 更新内容缓存间隔 单位(秒)即每个页面有5秒的纯静态期、此期间无论多少次访问都不会抓取notion数据调大该值有助于节省Vercel资源、同时提升访问速率但也会使文章更新有延迟。
THEME: process.env.NEXT_PUBLIC_THEME || 'fukasawa', // 主题, 支持 ['next','hexo',"fukasawa','medium','example','matery','gitbook','simple'] @see https://preview.tangly1024.com THEME: process.env.NEXT_PUBLIC_THEME || 'hexo', // 主题, 支持 ['next','hexo',"fukasawa','medium','example','matery','gitbook','simple'] @see https://preview.tangly1024.com
THEME_SWITCH: process.env.NEXT_PUBLIC_THEME_SWITCH || false, // 是否显示切换主题按钮 THEME_SWITCH: process.env.NEXT_PUBLIC_THEME_SWITCH || false, // 是否显示切换主题按钮
LANG: process.env.NEXT_PUBLIC_LANG || 'zh-CN', // e.g 'zh-CN','en-US' see /lib/lang.js for more. LANG: process.env.NEXT_PUBLIC_LANG || 'zh-CN', // e.g 'zh-CN','en-US' see /lib/lang.js for more.
SINCE: 2021, // e.g if leave this empty, current year will be used. SINCE: 2021, // e.g if leave this empty, current year will be used.
@@ -154,7 +154,7 @@ const BLOG = {
WIDGET_PET_LINK: WIDGET_PET_LINK:
process.env.NEXT_PUBLIC_WIDGET_PET_LINK || process.env.NEXT_PUBLIC_WIDGET_PET_LINK ||
'https://cdn.jsdelivr.net/npm/live2d-widget-model-wanko@1.0.5/assets/wanko.model.json', // 挂件模型地址 @see https://github.com/xiazeyu/live2d-widget-models 'https://cdn.jsdelivr.net/npm/live2d-widget-model-wanko@1.0.5/assets/wanko.model.json', // 挂件模型地址 @see https://github.com/xiazeyu/live2d-widget-models
WIDGET_PET_SWITCH_THEME: true, // 点击宠物挂件切换博客主题 WIDGET_PET_SWITCH_THEME: process.env.NEXT_PUBLIC_WIDGET_PET_SWITCH_THEME || true, // 点击宠物挂件切换博客主题
// 音乐播放插件 // 音乐播放插件
MUSIC_PLAYER: process.env.NEXT_PUBLIC_MUSIC_PLAYER || false, // 是否使用音乐播放插件 MUSIC_PLAYER: process.env.NEXT_PUBLIC_MUSIC_PLAYER || false, // 是否使用音乐播放插件

View File

@@ -56,7 +56,7 @@ const CommonHead = ({ meta, children }) => {
<> <>
<meta <meta
property="article:published_time" property="article:published_time"
content={meta.date || meta.createdTime} content={meta.publishTime}
/> />
<meta property="article:author" content={BLOG.AUTHOR} /> <meta property="article:author" content={BLOG.AUTHOR} />
<meta property="article:section" content={category} /> <meta property="article:section" content={category} />

View File

@@ -1,4 +1,4 @@
import { useRef, useEffect } from 'react' import { useRef, useEffect, useState } from 'react'
/** /**
* 可拖拽组件 * 可拖拽组件
*/ */
@@ -7,6 +7,7 @@ export const Draggable = (props) => {
const { children } = props const { children } = props
const draggableRef = useRef(null) const draggableRef = useRef(null)
const rafRef = useRef(null) const rafRef = useRef(null)
const [moving, setMoving] = useState(false)
let currentObj, offsetX, offsetY let currentObj, offsetX, offsetY
useEffect(() => { useEffect(() => {
@@ -54,6 +55,8 @@ export const Draggable = (props) => {
event.preventDefault() // 阻止默认的滚动行为 event.preventDefault() // 阻止默认的滚动行为
document.documentElement.style.overflow = 'hidden' // 防止页面一起滚动 document.documentElement.style.overflow = 'hidden' // 防止页面一起滚动
} }
setMoving(true)
offsetX = event.mx - currentObj.offsetLeft offsetX = event.mx - currentObj.offsetLeft
offsetY = event.my - currentObj.offsetTop offsetY = event.my - currentObj.offsetTop
@@ -73,6 +76,7 @@ export const Draggable = (props) => {
event = e(event) event = e(event)
document.documentElement.style.overflow = 'auto' // 恢复默认的滚动行为 document.documentElement.style.overflow = 'auto' // 恢复默认的滚动行为
cancelAnimationFrame(rafRef.current) cancelAnimationFrame(rafRef.current)
setMoving(false)
currentObj = document.ontouchmove = document.ontouchend = document.onmousemove = document.onmouseup = null currentObj = document.ontouchmove = document.ontouchend = document.onmousemove = document.onmouseup = null
} }
@@ -138,7 +142,7 @@ export const Draggable = (props) => {
} }
}, []) }, [])
return <div className='draggable cursor-move select-none' ref={draggableRef}> return <div className={`draggable ${moving ? 'cursor-grabbing' : 'cursor-grab'} select-none`} ref={draggableRef}>
{children} {children}
</div> </div>
} }

View File

@@ -26,7 +26,7 @@ export default function Live2D() {
}, [theme]) }, [theme])
function handleClick() { function handleClick() {
if (BLOG.WIDGET_PET_SWITCH_THEME) { if (JSON.parse(BLOG.WIDGET_PET_SWITCH_THEME)) {
switchTheme() switchTheme()
} }
} }
@@ -35,5 +35,9 @@ export default function Live2D() {
return <></> return <></>
} }
return <canvas id="live2d" className='cursor-pointer' width="280" height="250" onClick={handleClick} alt='切换主题' title='切换主题' /> return <canvas id="live2d" width="280" height="250" onClick={handleClick}
className="cursor-grab"
onMouseDown={(e) => e.target.classList.add('cursor-grabbing')}
onMouseUp={(e) => e.target.classList.remove('cursor-grabbing')}
/>
} }

View File

@@ -23,14 +23,13 @@ const ThemeSwitch = () => {
return (<> return (<>
<Draggable> <Draggable>
<div id="draggableBox" style={{ left: '10px', top: '85vh' }} className="fixed text-white bg-black z-50 rounded-lg shadow-card"> <div id="draggableBox" style={{ left: '10px', top: '85vh' }} className="fixed text-white bg-black z-50 rounded-lg shadow-card">
<div className="py-2 flex items-center text-sm"> <div className="py-2 flex items-center text-sm px-2">
<i className='fas fa-arrows cursor-move px-2' />
{/* <div className='uppercase font-sans whitespace-nowrap cursor-pointer ' onClick={switchTheme}> {theme}</div> */}
<select value={theme} onChange={onSelectChange} name="cars" className='text-white bg-black uppercase cursor-pointer'> <select value={theme} onChange={onSelectChange} name="cars" className='text-white bg-black uppercase cursor-pointer'>
{THEMES.map(t => { {THEMES.map(t => {
return <option key={t} value={t}>{t}</option> return <option key={t} value={t}>{t}</option>
})} })}
</select> </select>
<i className='fas fa-palette pl-1' />
</div> </div>
</div> </div>
</Draggable> </Draggable>

View File

@@ -13,7 +13,6 @@ export default function formatDate (date, local) {
? res.replace('年', '-').replace('月', '-').replace('日', '') ? res.replace('年', '-').replace('月', '-').replace('日', '')
: res : res
} }
export function formatDateFmt (timestamp, fmt) { export function formatDateFmt (timestamp, fmt) {
const date = new Date(timestamp) const date = new Date(timestamp)
const o = { const o = {

View File

@@ -45,7 +45,7 @@ export async function getAllPosts({ notionPageData, from, pageType }) {
// Sort by date // Sort by date
if (BLOG.POSTS_SORT_BY === 'date') { if (BLOG.POSTS_SORT_BY === 'date') {
posts.sort((a, b) => { posts.sort((a, b) => {
return b?.sortDate - a?.sortDate return b?.publishDate - a?.publishDate
}) })
} }
return posts return posts

View File

@@ -20,23 +20,23 @@ import { mapImgUrl, compressImage } from './mapImage'
* @returns * @returns
* *
*/ */
export async function getGlobalNotionData({ export async function getGlobalData({
pageId = BLOG.NOTION_PAGE_ID, pageId = BLOG.NOTION_PAGE_ID,
from from
}) { }) {
// 获取Notion数据 // 从notion获取
const notionPageData = deepClone(await getNotionPageData({ pageId, from })) const db = deepClone(await getNotionPageData({ pageId, from }))
notionPageData.allNavPages = getNavPages({ allPages: notionPageData.allPages }) // 不返回的敏感数据
delete notionPageData.block delete db.block
delete notionPageData.schema delete db.schema
delete notionPageData.rawMetadata delete db.rawMetadata
delete notionPageData.pageIds delete db.pageIds
delete notionPageData.viewIds delete db.viewIds
delete notionPageData.collection delete db.collection
delete notionPageData.collectionQuery delete db.collectionQuery
delete notionPageData.collectionId delete db.collectionId
delete notionPageData.collectionView delete db.collectionView
return notionPageData return db
} }
/** /**
@@ -48,8 +48,8 @@ function getLatestPosts({ allPages, from, latestPostCount }) {
const allPosts = allPages?.filter(page => page.type === 'Post' && page.status === 'Published') const allPosts = allPages?.filter(page => page.type === 'Post' && page.status === 'Published')
const latestPosts = Object.create(allPosts).sort((a, b) => { const latestPosts = Object.create(allPosts).sort((a, b) => {
const dateA = new Date(a?.lastEditedTime || a?.sortDate) const dateA = new Date(a?.lastEditedTime || a?.publishDate)
const dateB = new Date(b?.lastEditedTime || b?.sortDate) const dateB = new Date(b?.lastEditedTime || b?.publishDate)
return dateB - dateA return dateB - dateA
}) })
return latestPosts.slice(0, latestPostCount) return latestPosts.slice(0, latestPostCount)
@@ -69,12 +69,12 @@ export async function getNotionPageData({ pageId, from }) {
console.log('[缓存]:', `from:${from}`, `root-page-id:${pageId}`) console.log('[缓存]:', `from:${from}`, `root-page-id:${pageId}`)
return data return data
} }
const pageRecordMap = await getDataBaseInfoByNotionAPI({ pageId, from }) const db = await getDataBaseInfoByNotionAPI({ pageId, from })
// 存入缓存 // 存入缓存
if (pageRecordMap) { if (db) {
await setDataToCache(cacheKey, pageRecordMap) await setDataToCache(cacheKey, db)
} }
return pageRecordMap return db
} }
/** /**
@@ -298,7 +298,7 @@ async function getDataBaseInfoByNotionAPI({ pageId, from }) {
// Sort by date // Sort by date
if (BLOG.POSTS_SORT_BY === 'date') { if (BLOG.POSTS_SORT_BY === 'date') {
allPages.sort((a, b) => { allPages.sort((a, b) => {
return b?.sortDate - a?.sortDate return b?.publishDate - a?.publishDate
}) })
} }

View File

@@ -92,11 +92,9 @@ export default async function getPageProperties(id, block, schema, authToken, ta
properties.slug += '.html' properties.slug += '.html'
} }
} }
properties.publishDate = new Date(properties?.date?.start_date || value.created_time).getTime()
properties.sortDate = value?.date?.start_date || value.created_time properties.publishTime = formatDate(properties.publishDate, BLOG.LANG)
properties.createdTime = formatDate(new Date(value.created_time).toString(), BLOG.LANG) properties.lastEditedTime = formatDate(new Date(value?.last_edited_time), BLOG.LANG)
properties.publishTime = value?.date?.start_date ? formatDate(new Date(value?.date?.start_date).toString, BLOG.LANG) : properties.createdTime
properties.lastEditedTime = formatDate(new Date(value?.last_edited_time).toString(), BLOG.LANG)
properties.fullWidth = value.format?.page_full_width ?? false properties.fullWidth = value.format?.page_full_width ?? false
properties.pageIcon = mapImgUrl(block[id].value?.format?.page_icon, block[id].value) ?? '' properties.pageIcon = mapImgUrl(block[id].value?.format?.page_icon, block[id].value) ?? ''
properties.pageCover = mapImgUrl(block[id].value?.format?.page_cover, block[id].value) ?? '' properties.pageCover = mapImgUrl(block[id].value?.format?.page_cover, block[id].value) ?? ''

View File

@@ -46,7 +46,7 @@ export async function generateRss(posts) {
link: `${BLOG.LINK}/${post.slug}`, link: `${BLOG.LINK}/${post.slug}`,
description: post.summary, description: post.summary,
content: await createFeedContent(post), content: await createFeedContent(post),
date: new Date(post?.publishTime || post?.createdTime) date: new Date(post?.publishTime)
}) })
} }

View File

@@ -24,7 +24,7 @@ export async function generateSitemapXml({ allPages }) {
allPages?.forEach(post => { allPages?.forEach(post => {
urls.push({ urls.push({
loc: `${BLOG.LINK}/${post.slug}`, loc: `${BLOG.LINK}/${post.slug}`,
lastmod: new Date(post?.publishTime || post?.createdTime).toISOString().split('T')[0], lastmod: new Date(post?.publishTime).toISOString().split('T')[0],
changefreq: 'daily' changefreq: 'daily'
}) })
}) })

View File

@@ -94,6 +94,9 @@ module.exports = withBundleAnalyzer({
config.resolve.alias['@theme-components'] = path.resolve(__dirname, 'themes', THEME) config.resolve.alias['@theme-components'] = path.resolve(__dirname, 'themes', THEME)
return config return config
}, },
experimental: {
scrollRestoration: true
},
publicRuntimeConfig: { // 这里的配置既可以服务端获取到,也可以在浏览器端获取到 publicRuntimeConfig: { // 这里的配置既可以服务端获取到,也可以在浏览器端获取到
NODE_ENV_API: process.env.NODE_ENV_API || 'prod', NODE_ENV_API: process.env.NODE_ENV_API || 'prod',
THEMES: themes THEMES: themes

View File

@@ -1,6 +1,6 @@
{ {
"name": "notion-next", "name": "notion-next",
"version": "3.16.2", "version": "3.16.3",
"homepage": "https://github.com/tangly1024/NotionNext.git", "homepage": "https://github.com/tangly1024/NotionNext.git",
"license": "MIT", "license": "MIT",
"repository": { "repository": {

View File

@@ -1,4 +1,4 @@
import { getGlobalNotionData } from '@/lib/notion/getNotionData' import { getGlobalData } from '@/lib/notion/getNotionData'
import { useGlobal } from '@/lib/global' import { useGlobal } from '@/lib/global'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { getLayoutByTheme } from '@/themes/theme' import { getLayoutByTheme } from '@/themes/theme'
@@ -21,7 +21,7 @@ const NoFound = props => {
} }
export async function getStaticProps () { export async function getStaticProps () {
const props = (await getGlobalNotionData({ from: '404' })) || {} const props = (await getGlobalData({ from: '404' })) || {}
return { props } return { props }
} }

View File

@@ -1,6 +1,6 @@
import BLOG from '@/blog.config' import BLOG from '@/blog.config'
import { getPostBlocks } from '@/lib/notion' import { getPostBlocks } from '@/lib/notion'
import { getGlobalNotionData } from '@/lib/notion/getNotionData' import { getGlobalData } from '@/lib/notion/getNotionData'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { idToUuid } from 'notion-utils' import { idToUuid } from 'notion-utils'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
@@ -61,9 +61,6 @@ const Slug = props => {
post.toc = getPageTableOfContents(post, post.blockMap) post.toc = getPageTableOfContents(post, post.blockMap)
} }
} }
router.events.on('routeChangeComplete', () => {
window.scrollTo({ top: 0, behavior: 'smooth' })
})
}, [post]) }, [post])
const meta = { const meta = {
@@ -90,7 +87,7 @@ export async function getStaticPaths() {
} }
const from = 'slug-paths' const from = 'slug-paths'
const { allPages } = await getGlobalNotionData({ from }) const { allPages } = await getGlobalData({ from })
return { return {
paths: allPages?.map(row => ({ params: { slug: [row.slug] } })), paths: allPages?.map(row => ({ params: { slug: [row.slug] } })),
fallback: true fallback: true
@@ -105,7 +102,7 @@ export async function getStaticProps({ params: { slug } }) {
} }
} }
const from = `slug-props-${fullSlug}` const from = `slug-props-${fullSlug}`
const props = await getGlobalNotionData({ from }) const props = await getGlobalData({ from })
// 在列表内查找文章 // 在列表内查找文章
props.post = props?.allPages?.find((p) => { props.post = props?.allPages?.find((p) => {
return p.slug === fullSlug || p.id === idToUuid(fullSlug) return p.slug === fullSlug || p.id === idToUuid(fullSlug)

View File

@@ -1,10 +1,11 @@
import { getGlobalNotionData } from '@/lib/notion/getNotionData' import { getGlobalData } from '@/lib/notion/getNotionData'
import React, { useEffect } from 'react' import { useEffect } from 'react'
import { useGlobal } from '@/lib/global' import { useGlobal } from '@/lib/global'
import BLOG from '@/blog.config' import BLOG from '@/blog.config'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { getLayoutByTheme } from '@/themes/theme' import { getLayoutByTheme } from '@/themes/theme'
import { isBrowser } from '@/lib/utils' import { isBrowser } from '@/lib/utils'
import { formatDateFmt } from '@/lib/formatDate'
const ArchiveIndex = props => { const ArchiveIndex = props => {
const { siteInfo } = props const { siteInfo } = props
@@ -41,7 +42,7 @@ const ArchiveIndex = props => {
} }
export async function getStaticProps() { export async function getStaticProps() {
const props = await getGlobalNotionData({ from: 'archive-index' }) const props = await getGlobalData({ from: 'archive-index' })
// 处理分页 // 处理分页
props.posts = props.allPages.filter(page => page.type === 'Post' && page.status === 'Published') props.posts = props.allPages.filter(page => page.type === 'Post' && page.status === 'Published')
delete props.allPages delete props.allPages
@@ -49,15 +50,13 @@ export async function getStaticProps() {
const postsSortByDate = Object.create(props.posts) const postsSortByDate = Object.create(props.posts)
postsSortByDate.sort((a, b) => { postsSortByDate.sort((a, b) => {
const dateA = new Date(a?.publishTime || a.createdTime) return b?.publishDate - a?.publishDate
const dateB = new Date(b?.publishTime || b.createdTime)
return dateB - dateA
}) })
const archivePosts = {} const archivePosts = {}
postsSortByDate.forEach(post => { postsSortByDate.forEach(post => {
const date = post.date?.start_date?.slice(0, 7) || post.createdTime const date = formatDateFmt(post.publishDate, 'yyyy-MM')
if (archivePosts[date]) { if (archivePosts[date]) {
archivePosts[date].push(post) archivePosts[date].push(post)
} else { } else {

View File

@@ -1,4 +1,4 @@
import { getGlobalNotionData } from '@/lib/notion/getNotionData' import { getGlobalData } from '@/lib/notion/getNotionData'
import React from 'react' import React from 'react'
import { useGlobal } from '@/lib/global' import { useGlobal } from '@/lib/global'
import BLOG from '@/blog.config' import BLOG from '@/blog.config'
@@ -34,7 +34,7 @@ export default function Category(props) {
export async function getStaticProps({ params: { category } }) { export async function getStaticProps({ params: { category } }) {
const from = 'category-props' const from = 'category-props'
let props = await getGlobalNotionData({ from }) let props = await getGlobalData({ from })
// 过滤状态 // 过滤状态
props.posts = props.allPages.filter(page => page.type === 'Post' && page.status === 'Published') props.posts = props.allPages.filter(page => page.type === 'Post' && page.status === 'Published')
@@ -61,7 +61,7 @@ export async function getStaticProps({ params: { category } }) {
export async function getStaticPaths() { export async function getStaticPaths() {
const from = 'category-paths' const from = 'category-paths'
const { categoryOptions } = await getGlobalNotionData({ from }) const { categoryOptions } = await getGlobalData({ from })
return { return {
paths: Object.keys(categoryOptions).map(category => ({ paths: Object.keys(categoryOptions).map(category => ({
params: { category: categoryOptions[category]?.name } params: { category: categoryOptions[category]?.name }

View File

@@ -1,4 +1,4 @@
import { getGlobalNotionData } from '@/lib/notion/getNotionData' import { getGlobalData } from '@/lib/notion/getNotionData'
import React from 'react' import React from 'react'
import { useGlobal } from '@/lib/global' import { useGlobal } from '@/lib/global'
import BLOG from '@/blog.config' import BLOG from '@/blog.config'
@@ -34,7 +34,7 @@ export default function Category(props) {
export async function getStaticProps({ params: { category, page } }) { export async function getStaticProps({ params: { category, page } }) {
const from = 'category-page-props' const from = 'category-page-props'
let props = await getGlobalNotionData({ from }) let props = await getGlobalData({ from })
// 过滤状态类型 // 过滤状态类型
props.posts = props.allPages.filter(page => page.type === 'Post' && page.status === 'Published').filter(post => post && post.category && post.category.includes(category)) props.posts = props.allPages.filter(page => page.type === 'Post' && page.status === 'Published').filter(post => post && post.category && post.category.includes(category))
@@ -56,7 +56,7 @@ export async function getStaticProps({ params: { category, page } }) {
export async function getStaticPaths() { export async function getStaticPaths() {
const from = 'category-paths' const from = 'category-paths'
const { categoryOptions, allPages } = await getGlobalNotionData({ from }) const { categoryOptions, allPages } = await getGlobalData({ from })
const paths = [] const paths = []
categoryOptions?.forEach(category => { categoryOptions?.forEach(category => {

View File

@@ -1,4 +1,4 @@
import { getGlobalNotionData } from '@/lib/notion/getNotionData' import { getGlobalData } from '@/lib/notion/getNotionData'
import React from 'react' import React from 'react'
import { useGlobal } from '@/lib/global' import { useGlobal } from '@/lib/global'
import BLOG from '@/blog.config' import BLOG from '@/blog.config'
@@ -30,7 +30,7 @@ export default function Category(props) {
} }
export async function getStaticProps() { export async function getStaticProps() {
const props = await getGlobalNotionData({ from: 'category-index-props' }) const props = await getGlobalData({ from: 'category-index-props' })
delete props.allPages delete props.allPages
return { return {
props, props,

View File

@@ -1,6 +1,6 @@
import BLOG from '@/blog.config' import BLOG from '@/blog.config'
import { getPostBlocks } from '@/lib/notion' import { getPostBlocks } from '@/lib/notion'
import { getGlobalNotionData } from '@/lib/notion/getNotionData' import { getGlobalData } from '@/lib/notion/getNotionData'
import { generateRss } from '@/lib/rss' import { generateRss } from '@/lib/rss'
import { generateRobotsTxt } from '@/lib/robots.txt' import { generateRobotsTxt } from '@/lib/robots.txt'
@@ -24,7 +24,7 @@ const Index = props => {
*/ */
export async function getStaticProps() { export async function getStaticProps() {
const from = 'index' const from = 'index'
const props = await getGlobalNotionData({ from }) const props = await getGlobalData({ from })
const { siteInfo } = props const { siteInfo } = props
props.posts = props.allPages.filter(page => page.type === 'Post' && page.status === 'Published') props.posts = props.allPages.filter(page => page.type === 'Post' && page.status === 'Published')

View File

@@ -1,6 +1,6 @@
import BLOG from '@/blog.config' import BLOG from '@/blog.config'
import { getPostBlocks } from '@/lib/notion' import { getPostBlocks } from '@/lib/notion'
import { getGlobalNotionData } from '@/lib/notion/getNotionData' import { getGlobalData } from '@/lib/notion/getNotionData'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { getLayoutByTheme } from '@/themes/theme' import { getLayoutByTheme } from '@/themes/theme'
@@ -30,7 +30,7 @@ const Page = props => {
export async function getStaticPaths() { export async function getStaticPaths() {
const from = 'page-paths' const from = 'page-paths'
const { postCount } = await getGlobalNotionData({ from }) const { postCount } = await getGlobalData({ from })
const totalPages = Math.ceil(postCount / BLOG.POSTS_PER_PAGE) const totalPages = Math.ceil(postCount / BLOG.POSTS_PER_PAGE)
return { return {
// remove first page, we 're not gonna handle that. // remove first page, we 're not gonna handle that.
@@ -43,7 +43,7 @@ export async function getStaticPaths() {
export async function getStaticProps({ params: { page } }) { export async function getStaticProps({ params: { page } }) {
const from = `page-${page}` const from = `page-${page}`
const props = await getGlobalNotionData({ from }) const props = await getGlobalData({ from })
const { allPages } = props const { allPages } = props
const allPosts = allPages.filter(page => page.type === 'Post' && page.status === 'Published') const allPosts = allPages.filter(page => page.type === 'Post' && page.status === 'Published')
// 处理分页 // 处理分页

View File

@@ -1,4 +1,4 @@
import { getGlobalNotionData } from '@/lib/notion/getNotionData' import { getGlobalData } from '@/lib/notion/getNotionData'
import { useGlobal } from '@/lib/global' import { useGlobal } from '@/lib/global'
import { getDataFromCache } from '@/lib/cache/cache_manager' import { getDataFromCache } from '@/lib/cache/cache_manager'
import BLOG from '@/blog.config' import BLOG from '@/blog.config'
@@ -31,7 +31,7 @@ const Index = props => {
* @returns * @returns
*/ */
export async function getStaticProps({ params: { keyword } }) { export async function getStaticProps({ params: { keyword } }) {
const props = await getGlobalNotionData({ const props = await getGlobalData({
from: 'search-props', from: 'search-props',
pageType: ['Post'] pageType: ['Post']
}) })

View File

@@ -1,4 +1,4 @@
import { getGlobalNotionData } from '@/lib/notion/getNotionData' import { getGlobalData } from '@/lib/notion/getNotionData'
import { useGlobal } from '@/lib/global' import { useGlobal } from '@/lib/global'
import { getDataFromCache } from '@/lib/cache/cache_manager' import { getDataFromCache } from '@/lib/cache/cache_manager'
import BLOG from '@/blog.config' import BLOG from '@/blog.config'
@@ -31,7 +31,7 @@ const Index = props => {
* @returns * @returns
*/ */
export async function getStaticProps({ params: { keyword, page } }) { export async function getStaticProps({ params: { keyword, page } }) {
const props = await getGlobalNotionData({ const props = await getGlobalData({
from: 'search-props', from: 'search-props',
pageType: ['Post'] pageType: ['Post']
}) })

View File

@@ -1,4 +1,4 @@
import { getGlobalNotionData } from '@/lib/notion/getNotionData' import { getGlobalData } from '@/lib/notion/getNotionData'
import { useGlobal } from '@/lib/global' import { useGlobal } from '@/lib/global'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import BLOG from '@/blog.config' import BLOG from '@/blog.config'
@@ -45,7 +45,7 @@ const Search = props => {
* 浏览器前端搜索 * 浏览器前端搜索
*/ */
export async function getStaticProps() { export async function getStaticProps() {
const props = await getGlobalNotionData({ const props = await getGlobalData({
from: 'search-props', from: 'search-props',
pageType: ['Post'] pageType: ['Post']
}) })

View File

@@ -1,10 +1,10 @@
// pages/sitemap.xml.js // pages/sitemap.xml.js
import { getServerSideSitemap } from 'next-sitemap' import { getServerSideSitemap } from 'next-sitemap'
import { getGlobalNotionData } from '@/lib/notion/getNotionData' import { getGlobalData } from '@/lib/notion/getNotionData'
import BLOG from '@/blog.config' import BLOG from '@/blog.config'
export const getServerSideProps = async (ctx) => { export const getServerSideProps = async (ctx) => {
const { allPages } = await getGlobalNotionData({ from: 'rss' }) const { allPages } = await getGlobalData({ from: 'rss' })
const defaultFields = [ const defaultFields = [
{ {
loc: `${BLOG.LINK}`, loc: `${BLOG.LINK}`,
@@ -41,7 +41,7 @@ export const getServerSideProps = async (ctx) => {
const postFields = allPages?.filter(p => p.status === BLOG.NOTION_PROPERTY_NAME.status_publish)?.map(post => { const postFields = allPages?.filter(p => p.status === BLOG.NOTION_PROPERTY_NAME.status_publish)?.map(post => {
return { return {
loc: `${BLOG.LINK}/${post.slug}`, loc: `${BLOG.LINK}/${post.slug}`,
lastmod: new Date(post?.publishTime || post?.createdTime).toISOString().split('T')[0], lastmod: new Date(post?.publishTime).toISOString().split('T')[0],
changefreq: 'daily', changefreq: 'daily',
priority: '0.7' priority: '0.7'
} }

View File

@@ -1,5 +1,5 @@
import { useGlobal } from '@/lib/global' import { useGlobal } from '@/lib/global'
import { getGlobalNotionData } from '@/lib/notion/getNotionData' import { getGlobalData } from '@/lib/notion/getNotionData'
import BLOG from '@/blog.config' import BLOG from '@/blog.config'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { getLayoutByTheme } from '@/themes/theme' import { getLayoutByTheme } from '@/themes/theme'
@@ -30,7 +30,7 @@ const Tag = props => {
export async function getStaticProps({ params: { tag } }) { export async function getStaticProps({ params: { tag } }) {
const from = 'tag-props' const from = 'tag-props'
const props = await getGlobalNotionData({ from }) const props = await getGlobalData({ from })
// 过滤状态 // 过滤状态
props.posts = props.allPages.filter(page => page.type === 'Post' && page.status === 'Published').filter(post => post && post.tags && post.tags.includes(tag)) props.posts = props.allPages.filter(page => page.type === 'Post' && page.status === 'Published').filter(post => post && post.tags && post.tags.includes(tag))
@@ -68,7 +68,7 @@ function getTagNames(tags) {
export async function getStaticPaths() { export async function getStaticPaths() {
const from = 'tag-static-path' const from = 'tag-static-path'
const { tagOptions } = await getGlobalNotionData({ from }) const { tagOptions } = await getGlobalData({ from })
const tagNames = getTagNames(tagOptions) const tagNames = getTagNames(tagOptions)
return { return {

View File

@@ -1,5 +1,5 @@
import { useGlobal } from '@/lib/global' import { useGlobal } from '@/lib/global'
import { getGlobalNotionData } from '@/lib/notion/getNotionData' import { getGlobalData } from '@/lib/notion/getNotionData'
import BLOG from '@/blog.config' import BLOG from '@/blog.config'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { getLayoutByTheme } from '@/themes/theme' import { getLayoutByTheme } from '@/themes/theme'
@@ -25,7 +25,7 @@ const Tag = props => {
export async function getStaticProps({ params: { tag, page } }) { export async function getStaticProps({ params: { tag, page } }) {
const from = 'tag-page-props' const from = 'tag-page-props'
const props = await getGlobalNotionData({ from }) const props = await getGlobalData({ from })
// 过滤状态、标签 // 过滤状态、标签
props.posts = props.allPages.filter(page => page.type === 'Post' && page.status === 'Published').filter(post => post && post.tags && post.tags.includes(tag)) props.posts = props.allPages.filter(page => page.type === 'Post' && page.status === 'Published').filter(post => post && post.tags && post.tags.includes(tag))
// 处理文章数 // 处理文章数
@@ -44,7 +44,7 @@ export async function getStaticProps({ params: { tag, page } }) {
export async function getStaticPaths() { export async function getStaticPaths() {
const from = 'tag-page-static-path' const from = 'tag-page-static-path'
const { tagOptions, allPages } = await getGlobalNotionData({ from }) const { tagOptions, allPages } = await getGlobalData({ from })
const paths = [] const paths = []
tagOptions?.forEach(tag => { tagOptions?.forEach(tag => {
// 过滤状态类型 // 过滤状态类型

View File

@@ -1,4 +1,4 @@
import { getGlobalNotionData } from '@/lib/notion/getNotionData' import { getGlobalData } from '@/lib/notion/getNotionData'
import { useGlobal } from '@/lib/global' import { useGlobal } from '@/lib/global'
import BLOG from '@/blog.config' import BLOG from '@/blog.config'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
@@ -30,7 +30,7 @@ const TagIndex = props => {
export async function getStaticProps() { export async function getStaticProps() {
const from = 'tag-index-props' const from = 'tag-index-props'
const props = await getGlobalNotionData({ from }) const props = await getGlobalData({ from })
delete props.allPages delete props.allPages
return { return {
props, props,

View File

@@ -19,7 +19,7 @@ export default function ArticleDetail(props) {
if (!post) { if (!post) {
return <></> return <></>
} }
const date = formatDate(post?.publishTime || post?.createdTime, locale.LOCALE) const date = formatDate(post?.publishTime, locale.LOCALE)
return ( return (
<div id="container" className="max-w-5xl overflow-x-auto flex-grow mx-auto w-screen md:w-full "> <div id="container" className="max-w-5xl overflow-x-auto flex-grow mx-auto w-screen md:w-full ">
{post?.type && !post?.type !== 'Page' && post?.pageCover && ( {post?.type && !post?.type !== 'Page' && post?.pageCover && (

View File

@@ -0,0 +1,138 @@
import CommonHead from '@/components/CommonHead'
import { useState, createContext, useContext, useEffect } from 'react'
import Footer from './components/Footer'
import InfoCard from './components/InfoCard'
import RevolverMaps from './components/RevolverMaps'
import CONFIG_GITBOOK from './config_gitbook'
import TopNavBar from './components/TopNavBar'
import SearchInput from './components/SearchInput'
import { useGlobal } from '@/lib/global'
import Live2D from '@/components/Live2D'
import BLOG from '@/blog.config'
import NavPostList from './components/NavPostList'
import ArticleInfo from './components/ArticleInfo'
import Catalog from './components/Catalog'
import { useRouter } from 'next/router'
import Announcement from './components/Announcement'
import PageNavDrawer from './components/PageNavDrawer'
import FloatTocButton from './components/FloatTocButton'
import { AdSlot } from '@/components/GoogleAdsense'
import JumpToTopButton from './components/JumpToTopButton'
const ThemeGlobalMedium = createContext()
/**
* 基础布局 采用左右两侧布局,移动端使用顶部导航栏
* @returns {JSX.Element}
* @constructor
*/
const LayoutBase = (props) => {
const { children, meta, post, allNavPages, slotLeft, slotRight, slotTop } = props
const [tocVisible, changeTocVisible] = useState(false)
const [pageNavVisible, changePageNavVisible] = useState(false)
const [filterPosts, setFilterPosts] = useState(allNavPages)
const { onLoading } = useGlobal()
const router = useRouter()
const showTocButton = post?.toc?.length > 1
useEffect(() => {
setFilterPosts(allNavPages)
}, [post])
const LoadingCover = <div id='cover-loading' className={`${onLoading ? 'z-50 opacity-50' : '-z-10 opacity-0'} pointer-events-none transition-all duration-300`}>
<div className='w-full h-screen flex justify-center items-center'>
<i className="fa-solid fa-spinner text-2xl text-black dark:text-white animate-spin"> </i>
</div>
</div>
return (
<ThemeGlobalMedium.Provider value={{ tocVisible, changeTocVisible, filterPosts, setFilterPosts, allNavPages, pageNavVisible, changePageNavVisible }}>
<CommonHead meta={meta} />
<div id='theme-medium' className='bg-white dark:bg-hexo-black-gray w-full h-full min-h-screen justify-center dark:text-gray-300'>
{/* 顶部导航栏 */}
<TopNavBar {...props} />
<main id='wrapper' className={(BLOG.LAYOUT_SIDEBAR_REVERSE ? 'flex-row-reverse' : '') + 'relative flex justify-between w-full h-full mx-auto'}>
{/* 左侧推拉抽屉 */}
<div style={{ width: '32rem' }} className={'font-sans hidden md:block border-r dark:border-transparent relative z-10 '}>
<div className='pt-14 pb-4 px-6 sticky top-0 overflow-y-scroll h-screen flex flex-col justify-between'>
{slotLeft}
<SearchInput className='my-3 rounded-md' />
{/* 所有文章列表 */}
<NavPostList posts={filterPosts} />
<div className='mt-2'>
<Footer {...props} />
</div>
</div>
</div>
<div id='center-wrapper' className='flex flex-col justify-between w-full relative z-10 pt-12 min-h-screen'>
<div id='container-inner' className='w-full px-7 max-w-3xl justify-center mx-auto'>
{slotTop}
<AdSlot type='in-article' />
{onLoading ? LoadingCover : children}
<AdSlot type='in-article' />
{/* 回顶按钮 */}
<JumpToTopButton />
</div>
{/* 底部 */}
<div className='md:hidden'>
<Footer {...props}/>
</div>
<div className='text-center'>
<AdSlot type='native' />
</div>
</div>
{/* 右侧侧推拉抽屉 */}
<div style={{ width: '32rem' }} className={'hidden xl:block dark:border-transparent relative z-10 '}>
<div className='py-14 px-6 sticky top-0'>
<ArticleInfo post={props?.post ? props?.post : props.notice} />
<div className='py-6'>
<Catalog {...props} />
{slotRight}
{router.route === '/' && <>
<InfoCard {...props} />
{CONFIG_GITBOOK.WIDGET_REVOLVER_MAPS === 'true' && <RevolverMaps />}
<Live2D />
</>}
{/* gitbook主题首页只显示公告 */}
<Announcement {...props} />
</div>
<Live2D />
</div>
</div>
</main>
{showTocButton && !tocVisible && <div className='md:hidden fixed right-0 bottom-52 z-30 bg-white border-l border-t border-b dark:border-gray-800 rounded'>
<FloatTocButton {...props} />
</div>}
<PageNavDrawer {...props} />
{/* 移动端底部导航栏 */}
{/* <BottomMenuBar {...props} className='block md:hidden' /> */}
</div>
</ThemeGlobalMedium.Provider>
)
}
export default LayoutBase
export const useGitBookGlobal = () => useContext(ThemeGlobalMedium)

View File

@@ -1,7 +1,7 @@
import React from 'react' import React from 'react'
import BLOG from '@/blog.config' import BLOG from '@/blog.config'
const Footer = ({ title }) => { const Footer = ({ siteInfo }) => {
const d = new Date() const d = new Date()
const currentYear = d.getFullYear() const currentYear = d.getFullYear()
const copyrightDate = (function () { const copyrightDate = (function () {
@@ -13,23 +13,24 @@ const Footer = ({ title }) => {
return ( return (
<footer <footer
className='max-w-3xl z-10 dark:bg-hexo-black-gray flex-shrink-0 justify-center text-center mx-auto w-full leading-6 text-sm px-6 pb-6 relative' className='z-10 bg:white dark:bg-hexo-black-gray justify-center text-center w-full text-sm relative'
> >
<hr /> <hr className='py-2' />
<div className='flex justify-between'> <div className='flex justify-center'>
<div className='flex'>© {`${copyrightDate}`} <div><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 /></div></div> <div><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 /></div>
<div className='text-xs font-serif'><a href='https://github.com/tangly1024/NotionNext' className='underline text-gray-500 dark:text-gray-300'>NotionNext {BLOG.VERSION}</a></div> © {`${copyrightDate}`}
</div> </div>
<div className='text-xs font-serif'>Powered By <a href='https://github.com/tangly1024/NotionNext' className='underline text-gray-500 dark:text-gray-300'>NotionNext</a></div>
{BLOG.BEI_AN && <><i className='fas fa-shield-alt' /> <a href='https://beian.miit.gov.cn/' className='mr-2'>{BLOG.BEI_AN}</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 /></>}
<span className='hidden busuanzi_container_site_pv'> <span className='hidden busuanzi_container_site_pv'>
<i className='fas fa-eye' /><span className='px-1 busuanzi_value_site_pv'> </span> </span> <i className='fas fa-eye' /><span className='px-1 busuanzi_value_site_pv'> </span> </span>
<span className='pl-2 hidden busuanzi_container_site_uv'> <span className='pl-2 hidden busuanzi_container_site_uv'>
<i className='fas fa-users' /> <span className='px-1 busuanzi_value_site_uv'> </span> </span> <i className='fas fa-users' /> <span className='px-1 busuanzi_value_site_uv'> </span> </span>
<br /> <h1 className='pt-1'>{siteInfo?.title}</h1>
<h1>{title}</h1>
</footer> </footer>
) )

View File

@@ -36,7 +36,7 @@ const NavPostList = (props) => {
if (!filteredPosts || filteredPosts.length === 0) { if (!filteredPosts || filteredPosts.length === 0) {
return <NavPostListEmpty currentSearch={currentSearch} /> return <NavPostListEmpty currentSearch={currentSearch} />
} else { } else {
return <div id='posts-wrapper' className='w-full'> return <div id='posts-wrapper' className='w-full flex-grow'>
{/* 文章列表 */} {/* 文章列表 */}
{filteredPosts?.map((group, index) => <NavPostItem key={index} group={group} onHeightChange={props.onHeightChange}/>)} {filteredPosts?.map((group, index) => <NavPostItem key={index} group={group} onHeightChange={props.onHeightChange}/>)}
</div> </div>

View File

@@ -10,7 +10,7 @@ import BLOG from '@/blog.config'
* @returns * @returns
*/ */
export const BlogPostCardInfo = ({ post, showPreview, showPageCover, showSummary }) => { export const BlogPostCardInfo = ({ post, showPreview, showPageCover, showSummary }) => {
return <div className={`flex flex-col justify-between lg:p-6 p-4 ${showPageCover && !showPreview ? 'md:w-7/12 w-full h-56 md:max-h-60 ' : 'w-full '}`}> return <div className={`flex flex-col justify-between lg:p-6 p-4 ${showPageCover && !showPreview ? 'md:w-7/12 w-full md:max-h-60' : 'w-full'}`}>
<div> <div>
{/* 标题 */} {/* 标题 */}
<Link <Link

View File

@@ -14,7 +14,7 @@ export default function HeaderArticle({ post, siteInfo }) {
const headerImage = post?.pageCover ? `url("${post.pageCover}")` : `url("${siteInfo?.pageCover}")` const headerImage = post?.pageCover ? `url("${post.pageCover}")` : `url("${siteInfo?.pageCover}")`
const date = formatDate( const date = formatDate(
post?.publishTime || post?.createdTime, post?.publishTime,
locale.LOCALE locale.LOCALE
) )

View File

@@ -8,7 +8,7 @@ export const ArticleInfo = (props) => {
const { post } = props const { post } = props
const { locale } = useGlobal() const { locale } = useGlobal()
const date = formatDate(post?.publishTime || post?.createdTime, locale.LOCALE) const date = formatDate(post?.publishTime, locale.LOCALE)
return ( return (
<section className='mb-3 dark:text-gray-200'> <section className='mb-3 dark:text-gray-200'>

View File

@@ -0,0 +1,96 @@
import LayoutBase from './LayoutBase'
import { useGlobal } from '@/lib/global'
import React from 'react'
import Catalog from './components/Catalog'
import { ArticleLock } from './components/ArticleLock'
import formatDate from '@/lib/formatDate'
import BLOG from '@/blog.config'
import Link from 'next/link'
import NotionPage from '@/components/NotionPage'
import CONFIG_MEDIUM from './config_medium'
import Comment from '@/components/Comment'
import ArticleAround from './components/ArticleAround'
import TocDrawer from './components/TocDrawer'
import CategoryItem from './components/CategoryItem'
import TagItemMini from './components/TagItemMini'
import ShareBar from '@/components/ShareBar'
export const LayoutSlug = props => {
const { post, prev, next, siteInfo, lock, validPassword } = props
const { locale } = useGlobal()
const date = formatDate(
post?.publishTime || post?.createdTime,
locale.LOCALE
)
if (!post) {
return <LayoutBase {...props} showInfoCard={true} />
}
const slotRight = post?.toc && post?.toc?.length >= 3 && (
<div key={locale.COMMON.TABLE_OF_CONTENTS} >
<Catalog toc={post.toc} />
{/* <JumpToTopButton className='text-gray-400 hover:text-green-500 hover:bg-gray-100 py-1 duration-200' /> */}
</div>
)
return (
<LayoutBase showInfoCard={true} slotRight={slotRight} {...props} >
{/* 文章锁 */}
{lock && <ArticleLock validPassword={validPassword} />}
{!lock && <div id='container'>
{/* title */}
<h1 className="text-3xl pt-12 dark:text-gray-300">{post?.title}</h1>
{/* meta */}
<section className="py-2 items-center text-sm px-1">
<div className='flex flex-wrap text-gray-500 py-1 dark:text-gray-600'>
<span className='whitespace-nowrap'> <i className='far fa-calendar mr-2' />{date}</span>
<span className='mx-1'>|</span>
<span className='whitespace-nowrap mr-2'><i className='far fa-calendar-check mr-2' />{post.lastEditedTime}</span>
<div className="hidden busuanzi_container_page_pv font-light mr-2 whitespace-nowrap">
<i className="mr-1 fas fa-eye" /><span className="busuanzi_value_page_pv" />
</div>
</div>
<Link href="/about" passHref legacyBehavior>
<div className='flex pt-2'>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img src={siteInfo?.icon} className='rounded-full cursor-pointer' width={22} alt={BLOG.AUTHOR} />
<div className="mr-3 ml-2 my-auto text-green-500 cursor-pointer">
{BLOG.AUTHOR}
</div>
</div>
</Link>
</section>
{/* Notion文章主体 */}
<section id="notion-article" className="px-1 max-w-4xl">
{post && (<NotionPage post={post} />)}
</section>
<section>
{/* 分享 */}
<ShareBar post={post} />
{/* 文章分类和标签信息 */}
<div className='flex justify-between'>
{CONFIG_MEDIUM.POST_DETAIL_CATEGORY && post.category && <CategoryItem category={post.category} />}
<div>
{CONFIG_MEDIUM.POST_DETAIL_TAG && post?.tagItems?.map(tag => <TagItemMini key={tag.name} tag={tag} />)}
</div>
</div>
{post.type === 'Post' && <ArticleAround prev={prev} next={next} />}
<Comment frontMatter={post} />
</section>
<TocDrawer {...props} />
</div>}
</LayoutBase>
)
}
export default LayoutSlug