diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index 809bb97e..111a813a 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -1,33 +1,33 @@
---
name: Bug report (Bug反馈)
-about: Create a report to help Nobelium get better.
+about: 报告一个软件的BUG来让NotionNext变得更好
title: ''
labels: bug
assignees: tangly1024
---
-**Describe the bug**
-A clear and concise description of what the bug is.
+**描述bug**
+简单说明bug的现象、相关的错误提示、日志等
-**To Reproduce**
-Steps to reproduce the behavior:
+**复现步骤**
+出现这个bug的操作步骤
-**Expected behavior**
-A clear and concise description of what you expected to happen.
+**期望的正常结果**
+希望按这个步骤,正常操作结果是什么
-**Screenshots**
-If applicable, add screenshots to help explain your problem.
+**截图**
+相关的页面,应该用结果
-**Platform:**
+**环境**
-- OS: [e.g. iOS, Android, macOS]
-- Browser [e.g. chrome, safari, firefox]
-- Version [e.g. 22]
+- 操作系统: [例如. iOS, Android, macOS, windows]
+- 浏览器 [例如. chrome, safari, firefox]
+- 版本 [e.g. 22]
-**Additional context**
-Add any other context about the problem here.
+**补充说明**
+与问题相关的其它说明
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/deployment-error.md b/.github/ISSUE_TEMPLATE/deployment-error.md
index 12e47613..14c6bc8a 100644
--- a/.github/ISSUE_TEMPLATE/deployment-error.md
+++ b/.github/ISSUE_TEMPLATE/deployment-error.md
@@ -1,7 +1,28 @@
---
name: Deployment error (部署错误)
-about: Do you need help to install NotionNext?
+about: 在安装部署NotionNext时需要什么帮助吗
title: ''
labels: deployment
assignees: tangly1024
---
+
+
+
+
+**描述遇到的问题**
+简单说明你遇到的问题,相关的日志、错误信息
+
+**相应配置**
+相关的配置,例如notion_page_id;你的网站地址
+
+**截图**
+相关的页面,应该用结果
+
+**环境**
+
+- 操作系统: [例如. iOS, Android, macOS, windows]
+- 浏览器 [例如. chrome, safari, firefox]
+- 版本 [e.g. 22]
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
index e454490b..8a50c9d3 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -7,18 +7,18 @@ assignees: tangly1024
---
-**Is your feature request related to a problem? Please describe.**
-A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+**为什么提出这个新的特性改动**
+简要说明此特性解决的问题,例如,『博客站点的读者互动性不够强,和读者无法建立紧密的联系...』
-**Describe the solution you'd like**
-A clear and concise description of what you want to happen.
+**描述一下你推荐的解决方案**
+简要说明你的解决方案建议,例如,『Giscus评论插件功能更加强大,用户只需留言既可在你的邮箱收到通知。。。』
-**Describe alternatives you've considered**
-A clear and concise description of any alternative solutions or features you've considered.
+**描述一下你考虑过的其它替代解决方案**
+简要说明你所有想过的有可能解决此问题的方案。
-**Additional context**
-Add any other context or screenshots about the feature request here.
+**补充说明**
+补充与此特性相关的内容
diff --git a/blog.config.js b/blog.config.js
index 19720ff9..7b94df71 100644
--- a/blog.config.js
+++ b/blog.config.js
@@ -18,7 +18,7 @@ const BLOG = {
FACEBOOK_PAGE_ID: process.env.NEXT_PUBLIC_FACEBOOK_PAGE_ID || '', // Facebook Page ID 來啟用 messenger 聊天功能
FACEBOOK_APP_ID: process.env.NEXT_PUBLIC_FACEBOOK_APP_ID || '', // Facebook App ID 來啟用 messenger 聊天功能 获取: https://developers.facebook.com/
- THEME: process.env.NEXT_PUBLIC_THEME || 'next', // 主题, 支持 ['next','hexo',"fukasawa','medium']
+ THEME: process.env.NEXT_PUBLIC_THEME || 'next', // 主题, 支持 ['next','hexo',"fukasawa','medium','example'] @see https://preview.tangly1024.com
THEME_SWITCH: process.env.NEXT_PUBLIC_THEME_SWITCH || false, // 是否显示切换主题按钮
LANG: 'zh-CN', // e.g 'zh-CN','en-US' see /lib/lang.js for more.
HOME_BANNER_IMAGE: './bg_image.jpg', // 首页背景大图,默认文件:/public/bg_image.jpg 。会被Notion中的封面图覆盖。
@@ -49,6 +49,9 @@ const BLOG = {
CONTACT_GITHUB: 'https://github.com/tangly1024',
CONTACT_TELEGRAM: '',
+ // 鼠标点击烟花特效
+ FIREWORKS: process.env.NEXT_PUBLIC_FIREWORKS || true, // 鼠标点击烟花特效
+
// 悬浮挂件
WIDGET_PET: process.env.NEXT_PUBLIC_WIDGET_PET || true, // 是否显示宠物挂件
WIDGET_PET_LINK:
@@ -124,7 +127,7 @@ const BLOG = {
process.env.NEXT_PUBLIC_DESCRIPTION || '这是一个由NotionNext生成的站点', // 站点描述,被notion中的页面描述覆盖
isProd: process.env.VERCEL_ENV === 'production', // distinguish between development and production environment (ref: https://vercel.com/docs/environment-variables#system-environment-variables) isProd: process.env.VERCEL_ENV === 'production' // distinguish between development and production environment (ref: https://vercel.com/docs/environment-variables#system-environment-variables)
- VERSION: '3.2.1' // 版本号
+ VERSION: '3.3.1' // 版本号
}
module.exports = BLOG
diff --git a/components/Fireworks.js b/components/Fireworks.js
new file mode 100644
index 00000000..3ccdec7e
--- /dev/null
+++ b/components/Fireworks.js
@@ -0,0 +1,209 @@
+/**
+ * https://codepen.io/juliangarnier/pen/gmOwJX
+ * custom by hexo-theme-yun @YunYouJun
+ */
+import React from 'react'
+import anime from 'animejs'
+
+export const Fireworks = () => {
+ React.useEffect(() => {
+ createFireworks({})
+ }, [])
+ return
+}
+
+/**
+ * 创建烟花
+ * @param config
+ */
+function createFireworks(config) {
+ const defaultColors = ['102, 167, 221', '62, 131, 225', '33, 78, 194']
+ const defaultConfig = {
+ colors: defaultColors,
+ numberOfParticules: 20,
+ orbitRadius: {
+ min: 50,
+ max: 100
+ },
+ circleRadius: {
+ min: 10,
+ max: 20
+ },
+ diffuseRadius: {
+ min: 50,
+ max: 100
+ },
+ animeDuration: {
+ min: 900,
+ max: 1500
+ }
+ }
+ config = Object.assign(defaultConfig, config)
+
+ let pointerX = 0
+ let pointerY = 0
+
+ // sky blue
+ const colors = config.colors || defaultColors
+
+ const canvasEl = document.querySelector('.fireworks')
+ const ctx = canvasEl.getContext('2d')
+
+ /**
+ * 设置画布尺寸
+ */
+ function setCanvasSize(canvasEl) {
+ canvasEl.width = window.innerWidth
+ canvasEl.height = window.innerHeight
+ canvasEl.style.width = `${window.innerWidth}px`
+ canvasEl.style.height = `${window.innerHeight}px`
+ }
+
+ /**
+ * update pointer
+ * @param {TouchEvent} e
+ */
+ function updateCoords(e) {
+ pointerX =
+ e.clientX ||
+ (e.touches[0] ? e.touches[0].clientX : e.changedTouches[0].clientX)
+ pointerY =
+ e.clientY ||
+ (e.touches[0] ? e.touches[0].clientY : e.changedTouches[0].clientY)
+ }
+
+ function setParticuleDirection(p) {
+ const angle = (anime.random(0, 360) * Math.PI) / 180
+ const value = anime.random(
+ config.diffuseRadius.min,
+ config.diffuseRadius.max
+ )
+ const radius = [-1, 1][anime.random(0, 1)] * value
+ return {
+ x: p.x + radius * Math.cos(angle),
+ y: p.y + radius * Math.sin(angle)
+ }
+ }
+
+ /**
+ * 在指定位置创建粒子
+ * @param {number} x
+ * @param {number} y
+ * @returns
+ */
+ function createParticule(x, y) {
+ const p = {
+ x,
+ y,
+ color: `rgba(${
+ colors[anime.random(0, colors.length - 1)]
+ },${
+ anime.random(0.2, 0.8)
+ })`,
+ radius: anime.random(config.circleRadius.min, config.circleRadius.max),
+ endPos: null,
+ draw() {}
+ }
+ p.endPos = setParticuleDirection(p)
+ p.draw = function() {
+ ctx.beginPath()
+ ctx.arc(p.x, p.y, p.radius, 0, 2 * Math.PI, true)
+ ctx.fillStyle = p.color
+ ctx.fill()
+ }
+ return p
+ }
+
+ function createCircle(x, y) {
+ const p = {
+ x,
+ y,
+ color: '#000',
+ radius: 0.1,
+ alpha: 0.5,
+ lineWidth: 6,
+ draw() {}
+ }
+ p.draw = function() {
+ ctx.globalAlpha = p.alpha
+ ctx.beginPath()
+ ctx.arc(p.x, p.y, p.radius, 0, 2 * Math.PI, true)
+ ctx.lineWidth = p.lineWidth
+ ctx.strokeStyle = p.color
+ ctx.stroke()
+ ctx.globalAlpha = 1
+ }
+ return p
+ }
+
+ function renderParticule(anim) {
+ for (let i = 0; i < anim.animatables.length; i++) { anim.animatables[i].target.draw() }
+ }
+
+ function animateParticules(x, y) {
+ const circle = createCircle(x, y)
+ const particules = []
+ for (let i = 0; i < config.numberOfParticules; i++) { particules.push(createParticule(x, y)) }
+
+ anime
+ .timeline()
+ .add({
+ targets: particules,
+ x(p) {
+ return p.endPos.x
+ },
+ y(p) {
+ return p.endPos.y
+ },
+ radius: 0.1,
+ duration: anime.random(
+ config.animeDuration.min,
+ config.animeDuration.max
+ ),
+ easing: 'easeOutExpo',
+ update: renderParticule
+ })
+ .add(
+ {
+ targets: circle,
+ radius: anime.random(config.orbitRadius.min, config.orbitRadius.max),
+ lineWidth: 0,
+ alpha: {
+ value: 0,
+ easing: 'linear',
+ duration: anime.random(600, 800)
+ },
+ duration: anime.random(1200, 1800),
+ easing: 'easeOutExpo',
+ update: renderParticule
+ },
+ 0
+ )
+ }
+
+ const render = anime({
+ duration: Infinity,
+ update: () => {
+ ctx.clearRect(0, 0, canvasEl.width, canvasEl.height)
+ }
+ })
+
+ document.addEventListener(
+ 'mousedown',
+ (e) => {
+ render.play()
+ updateCoords(e)
+ animateParticules(pointerX, pointerY)
+ },
+ false
+ )
+
+ setCanvasSize(canvasEl)
+ window.addEventListener(
+ 'resize',
+ () => {
+ setCanvasSize(canvasEl)
+ },
+ false
+ )
+}
diff --git a/components/Live2D.js b/components/Live2D.js
index 89e68301..e91a3806 100644
--- a/components/Live2D.js
+++ b/components/Live2D.js
@@ -23,15 +23,20 @@ export default function Live2D() {
return
}
+/**
+ * 加载宠物
+ */
function initLive2D() {
- // 加载 waifu.css live2d.min.js waifu-tips.js
- if (screen.width >= 768) {
- Promise.all([
- // loadExternalResource('https://cdn.zhangxinxu.com/sp/demo/live2d/live2d/js/live2d.js', 'js')
- loadExternalResource('https://cdn.jsdelivr.net/gh/stevenjoezhang/live2d-widget@latest/live2d.min.js', 'js')
- ]).then((e) => {
- // https://github.com/xiazeyu/live2d-widget-models
- loadlive2d('live2d', BLOG.WIDGET_PET_LINK)
- })
- }
+ setTimeout(() => {
+ // 加载 waifu.css live2d.min.js waifu-tips.js
+ if (screen.width >= 768) {
+ Promise.all([
+ // loadExternalResource('https://cdn.zhangxinxu.com/sp/demo/live2d/live2d/js/live2d.js', 'js')
+ loadExternalResource('https://cdn.jsdelivr.net/gh/stevenjoezhang/live2d-widget@latest/live2d.min.js', 'js')
+ ]).then((e) => {
+ // https://github.com/xiazeyu/live2d-widget-models
+ loadlive2d('live2d', BLOG.WIDGET_PET_LINK)
+ })
+ }
+ }, 1000)
}
diff --git a/components/NotionPage.js b/components/NotionPage.js
index bff705f5..fd03fabc 100644
--- a/components/NotionPage.js
+++ b/components/NotionPage.js
@@ -1,5 +1,6 @@
import { NotionRenderer } from 'react-notion-x'
import dynamic from 'next/dynamic'
+import mediumZoom from 'medium-zoom'
import React from 'react'
const Code = dynamic(() =>
@@ -26,11 +27,32 @@ const NotionPage = ({ post }) => {
return <>{post?.summary || ''}>
}
- React.useEffect(() => {
- addWatch4Dom()
+ const zoom = typeof window !== 'undefined' && mediumZoom({
+ container: '.notion-viewport',
+ background: 'rgba(0, 0, 0, 0.2)',
+ margin: getMediumZoomMargin()
})
- return
+ const zoomRef = React.useRef(zoom ? zoom.clone() : null)
+
+ React.useEffect(() => {
+ addWatch4Dom()
+ // 将相册gallery下的图片加入放大功能
+ // const container = document?.getElementById('container')
+ const imgList = document?.querySelectorAll('.notion-collection-card-cover img')
+ if (imgList && zoomRef.current) {
+ for (let i = 0; i < imgList.length; i++) {
+ (zoomRef.current).attach(imgList[i])
+ }
+ }
+
+ const cards = document.getElementsByClassName('notion-collection-card')
+ for (const e of cards) {
+ e.removeAttribute('href')
+ }
+ })
+
+ return
{
}
-export default NotionPage
-
/**
* 监听DOM变化
* @param {*} element
@@ -116,3 +136,23 @@ const mapPageUrl = id => {
// return 'https://www.notion.so/' + id.replace(/-/g, '')
return '/article/' + id.replace(/-/g, '')
}
+
+function getMediumZoomMargin() {
+ const width = window.innerWidth
+
+ if (width < 500) {
+ return 8
+ } else if (width < 800) {
+ return 20
+ } else if (width < 1280) {
+ return 30
+ } else if (width < 1600) {
+ return 40
+ } else if (width < 1920) {
+ return 48
+ } else {
+ return 72
+ }
+}
+
+export default NotionPage
diff --git a/lib/global.js b/lib/global.js
index ea93caff..6e0c5fee 100644
--- a/lib/global.js
+++ b/lib/global.js
@@ -49,7 +49,7 @@ export function GlobalContextProvider({ children }) {
}, [])
return (
-
+
{children}
)
diff --git a/lib/notion.js b/lib/notion.js
index a2f19e88..51c02180 100644
--- a/lib/notion.js
+++ b/lib/notion.js
@@ -1,4 +1,3 @@
export { getAllPosts } from './notion/getAllPosts'
export { getAllTags } from './notion/getAllTags'
export { getPostBlocks } from './notion/getPostBlocks'
-export { getAllCategories } from './notion/getAllCategories'
diff --git a/lib/notion/getAllCategories.js b/lib/notion/getAllCategories.js
deleted file mode 100644
index b9a543d8..00000000
--- a/lib/notion/getAllCategories.js
+++ /dev/null
@@ -1,40 +0,0 @@
-import { isIterable } from '../utils'
-
-/**
- * 获取所有文章的分类
- * @param allPosts
- * @returns {Promise<{}|*[]>}
- */
-export async function getAllCategories({ allPosts, categoryOptions, sliceCount = 0 }) {
- if (!allPosts || !categoryOptions) {
- return []
- }
- // 计数
- let categories = allPosts.map(p => p.category)
- categories = [...categories.flat()]
- const categoryObj = {}
- categories.forEach(category => {
- if (category in categoryObj) {
- categoryObj[category]++
- } else {
- categoryObj[category] = 1
- }
- })
- const list = []
- if (isIterable(categoryOptions)) {
- for (const c of categoryOptions) {
- const count = categoryObj[c.value]
- if (count) {
- list.push({ id: c.id, name: c.value, color: c.color, count })
- }
- }
- }
-
- // 按照数量排序
- // list.sort((a, b) => b.count - a.count)
- if (sliceCount && sliceCount > 0) {
- return list.slice(0, sliceCount)
- } else {
- return list
- }
-}
diff --git a/lib/notion/getAllPosts.js b/lib/notion/getAllPosts.js
index fbe588da..47198789 100644
--- a/lib/notion/getAllPosts.js
+++ b/lib/notion/getAllPosts.js
@@ -23,7 +23,7 @@ export async function getAllPosts({ notionPageData, from, pageType }) {
const pageBlock = notionPageData.block
const schema = notionPageData.schema
- const tagOptions = notionPageData.tagOptions
+ const tagOptions = notionPageData.tags
const collectionQuery = notionPageData.collectionQuery
const data = []
@@ -73,19 +73,3 @@ function getPostCover(id, block) {
if (pageCover.startsWith('http')) return defaultMapImageUrl(pageCover, block[id].value)
}
}
-
-/**
- * 获取博文总数
- * @param {*} param0
- * @returns
- */
-export async function getAllPostCount({ notionPageData, from }) {
- if (!notionPageData) {
- notionPageData = await getNotionPageData({ from })
- }
- if (!notionPageData) {
- return []
- }
- const allPosts = await getAllPosts({ notionPageData, from, pageType: ['Post'] })
- return allPosts?.length || 0
-}
diff --git a/lib/notion/getAllTags.js b/lib/notion/getAllTags.js
index 174055d1..33e82f51 100644
--- a/lib/notion/getAllTags.js
+++ b/lib/notion/getAllTags.js
@@ -7,7 +7,7 @@ import { isIterable } from '../utils'
* @param tagOptions tags的下拉选项
* @returns {Promise<{}|*[]>}
*/
-export async function getAllTags({ allPosts, sliceCount = 0, tagOptions }) {
+export function getAllTags({ allPosts, sliceCount = 0, tagOptions }) {
if (!allPosts || !tagOptions) {
return []
}
diff --git a/lib/notion/getNotionData.js b/lib/notion/getNotionData.js
index 55a3d031..0b2d5ba1 100644
--- a/lib/notion/getNotionData.js
+++ b/lib/notion/getNotionData.js
@@ -3,9 +3,11 @@ import { getDataFromCache, setDataToCache } from '@/lib/cache/cache_manager'
import { getPostBlocks } from '@/lib/notion/getPostBlocks'
import { idToUuid } from 'notion-utils'
import { defaultMapImageUrl } from 'react-notion-x'
-import { getAllCategories } from './getAllCategories'
-import { getAllPosts, getAllPostCount } from './getAllPosts'
+import { deepClone, isIterable } from '../utils'
+import getAllPageIds from './getAllPageIds'
+import { getAllPosts } from './getAllPosts'
import { getAllTags } from './getAllTags'
+import getPageProperties from './getPageProperties'
/**
* 获取博客数据
@@ -15,40 +17,29 @@ import { getAllTags } from './getAllTags'
* @param categoryCount
* @param tagsCount 截取标签数量
* @param pageType 过滤的文章类型,数组格式 ['Page','Post']
- * @returns {
- allPosts, 所有博客
- latestPosts,
- categories, 所有分类
- postCount,
- customNav, 自定义导航菜单
- tags, 所有标签
- siteInfo:{
- title,
- description,
- pageCover
- }
- }
+ * @returns
*
*/
export async function getGlobalNotionData({
pageId = BLOG.NOTION_PAGE_ID,
from,
- latestPostCount = 5,
- categoryCount = BLOG.PREVIEW_CATEGORY_COUNT,
- tagsCount = BLOG.PREVIEW_TAG_COUNT,
pageType = ['Post']
}) {
- const notionPageData = await getNotionPageData({ pageId, from })
- const siteInfo = await getBlogInfo({ notionPageData, from })
- const tagOptions = notionPageData.tagOptions
- const categoryOptions = notionPageData.categoryOptions
- const customNav = await getCustomNav({ notionPageData })
+ // 深拷贝数据
+ const notionPageData = deepClone(await getNotionPageData({ pageId, from }))
+
const allPosts = await getAllPosts({ notionPageData, from, pageType })
- const postCount = await getAllPostCount({ notionPageData, from })
- const categories = await getAllCategories({ allPosts, categoryOptions, sliceCount: categoryCount })
- const tags = await getAllTags({ allPosts, tagOptions, sliceCount: tagsCount })
- const latestPosts = await getLatestPosts({ notionPageData, from, latestPostCount })
- return { allPosts, latestPosts, categories, postCount, customNav, tags, siteInfo }
+ notionPageData.allPosts = allPosts
+ // 删除前端不需要的数据
+ delete notionPageData.block
+ delete notionPageData.collection
+ delete notionPageData.collectionQuery
+ delete notionPageData.schema
+ delete notionPageData.rawMetadata
+ delete notionPageData.pageIds
+ delete notionPageData.tagOptions
+ delete notionPageData.categoryOptions
+ return notionPageData
}
/**
@@ -56,8 +47,7 @@ export async function getGlobalNotionData({
* @param {*}} param0
* @returns
*/
-async function getLatestPosts({ notionPageData, from, latestPostCount }) {
- const allPosts = await getAllPosts({ notionPageData, from, pageType: ['Post'] })
+function getLatestPosts({ allPosts, from, latestPostCount }) {
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)
@@ -79,7 +69,7 @@ export async function getNotionPageData({ pageId, from }) {
// 尝试从缓存获取
const cacheKey = 'page_block_' + pageId
const data = await getDataFromCache(cacheKey)
- if (data) {
+ if (data && data.pageIds?.length > 0) {
console.log('[请求缓存]:', `from:${from}`, `root-page-id:${pageId}`)
return data
}
@@ -96,14 +86,7 @@ export async function getNotionPageData({ pageId, from }) {
* @param notionPageData
* @returns {Promise<[]|*[]>}
*/
-async function getCustomNav({ notionPageData }) {
- if (!notionPageData) {
- notionPageData = await getNotionPageData({ from: 'custom-nav' })
- }
- if (!notionPageData) {
- return []
- }
- const allPage = await getAllPosts({ notionPageData, from: 'custom-nav', pageType: ['Page'] })
+function getCustomNav({ allPage }) {
const customNav = []
if (allPage && allPage.length > 0) {
allPage.forEach(p => {
@@ -139,23 +122,55 @@ function getCategoryOptions(schema) {
return categorySchema?.options || []
}
+/**
+ * 获取所有文章的分类
+ * @param allPosts
+ * @returns {Promise<{}|*[]>}
+ */
+function getAllCategories({ allPosts, categoryOptions, sliceCount = 0 }) {
+ if (!allPosts || !categoryOptions) {
+ return []
+ }
+ // 计数
+ let categories = allPosts.map(p => p.category)
+ categories = [...categories.flat()]
+ const categoryObj = {}
+ categories.forEach(category => {
+ if (category in categoryObj) {
+ categoryObj[category]++
+ } else {
+ categoryObj[category] = 1
+ }
+ })
+ const list = []
+ if (isIterable(categoryOptions)) {
+ for (const c of categoryOptions) {
+ const count = categoryObj[c.value]
+ if (count) {
+ list.push({ id: c.id, name: c.value, color: c.color, count })
+ }
+ }
+ }
+
+ // 按照数量排序
+ // list.sort((a, b) => b.count - a.count)
+ if (sliceCount && sliceCount > 0) {
+ return list.slice(0, sliceCount)
+ } else {
+ return list
+ }
+}
+
/**
* 站点信息
* @param notionPageData
* @param from
* @returns {Promise<{title,description,pageCover}>}
*/
-async function getBlogInfo({ notionPageData, from }) {
- if (!notionPageData) {
- notionPageData = await getNotionPageData({ from })
- }
- if (!notionPageData) {
- return null
- }
- const collection = notionPageData?.collection
+function getBlogInfo({ collection, block }) {
const title = collection?.name?.[0][0] || BLOG.TITLE
const description = collection?.description?.[0][0] || BLOG.DESCRIPTION
- const pageCover = mapCoverUrl(collection?.cover, notionPageData.block)
+ const pageCover = mapCoverUrl(collection?.cover, block)
return { title, description, pageCover }
}
@@ -184,23 +199,47 @@ async function getPageRecordMapByNotionAPI({ pageId, from }) {
return []
}
pageId = idToUuid(pageId)
- const collection = Object.values(pageRecordMap.collection)[0]?.value
- const collectionQuery = pageRecordMap.collection_query
const block = pageRecordMap.block
- const schema = collection?.schema
- const rawMetadata = block[pageId].value
- const tagOptions = getTagOptions(schema)
- const categoryOptions = getCategoryOptions(schema)
-
+ const rawMetadata = block[pageId]?.value
// 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
}
+ const collection = Object.values(pageRecordMap.collection)[0]?.value
+ const collectionQuery = pageRecordMap.collection_query
+ const schema = collection?.schema
+ const tagOptions = getTagOptions(schema)
+ const categoryOptions = getCategoryOptions(schema)
+
+ const data = []
+ const pageIds = getAllPageIds(collectionQuery)
+ for (let i = 0; i < pageIds.length; i++) {
+ const id = pageIds[i]
+ const properties = (await getPageProperties(id, block, schema)) || null
+ properties.slug = properties.slug ?? properties.id
+ delete properties.content
+ data.push(properties)
+ }
+
+ const allPage = data.filter(post => {
+ return post.title && post?.status?.[0] === 'Published' && ['Page'].indexOf(post?.type?.[0]) > -1
+ })
+ const allPosts = data.filter(post => {
+ return post.title && post?.status?.[0] === 'Published' && ['Post'].indexOf(post?.type?.[0]) > -1
+ })
+
+ const customNav = getCustomNav({ allPage })
+ 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 siteInfo = getBlogInfo({ collection, block })
+
return {
collection,
collectionQuery,
@@ -208,6 +247,13 @@ async function getPageRecordMapByNotionAPI({ pageId, from }) {
schema,
tagOptions,
categoryOptions,
- rawMetadata
+ rawMetadata,
+ siteInfo,
+ customNav,
+ postCount,
+ pageIds,
+ categories,
+ tags,
+ latestPosts
}
}
diff --git a/lib/notion/getPostBlocks.js b/lib/notion/getPostBlocks.js
index 52cbdf32..1f58d94f 100644
--- a/lib/notion/getPostBlocks.js
+++ b/lib/notion/getPostBlocks.js
@@ -1,28 +1,19 @@
import BLOG from '@/blog.config'
import { NotionAPI } from 'notion-client'
import { getDataFromCache, setDataToCache } from '@/lib/cache/cache_manager'
+import { deepClone, delay } from '../utils'
-export async function getPostBlocks(id, from, slice, retryCount = 3) {
+export async function getPostBlocks(id, from, slice) {
const cacheKey = 'page_block_' + id
let pageBlock = await getDataFromCache(cacheKey)
if (pageBlock) {
- console.log('[请求缓存]:', `from:${from}`, `id:${id}`, cacheKey)
+ console.log('[请求缓存]:', `from:${from}`, cacheKey)
return filterPostBlocks(id, pageBlock, slice)
}
- const authToken = BLOG.NOTION_ACCESS_TOKEN || null
- const api = new NotionAPI({ authToken })
- try {
- console.warn('[请求API]:', `from:${from}`, `id:${id}`)
- pageBlock = await api.getPage(id)
- console.warn('[请求成功]', `from:${from}`, `id:${id}`)
- } catch (error) {
- console.error('[请求失败]', `from:${from}`, `id:${id}`, `error:${error}`)
- if (retryCount && retryCount > 0) { // 重试
- console.error('[重试请求]', `from:${from}`, `id:${id}`, `剩余次数:${retryCount}`)
- return getPostBlocks(id, from, slice, retryCount - 1)
- }
- return null
- }
+
+ console.warn('[请求API]:', `from:${from}`, `id:${id}`)
+ pageBlock = await getPageWithRetry(id, from)
+ console.warn('[请求完成]: 结果', `${pageBlock ? '成功' : '失败'}`, `from:${from}`, `id:${id}`)
if (pageBlock) {
await setDataToCache(cacheKey, pageBlock)
@@ -31,6 +22,33 @@ export async function getPostBlocks(id, from, slice, retryCount = 3) {
return pageBlock
}
+/**
+ * 调用接口,失败会重试
+ * @param {*} id
+ * @param {*} retryAttempts
+ */
+async function getPageWithRetry(id, from, retryAttempts = 3) {
+ if (retryAttempts && retryAttempts > 0) {
+ console.error('[发起请求]', `from:${from}`, `id:${id}`, `剩余重试次数:${retryAttempts}`)
+ try {
+ const authToken = BLOG.NOTION_ACCESS_TOKEN || null
+ const api = new NotionAPI({ authToken })
+ return await api.getPage(id)
+ } catch (e) {
+ await delay(1000)
+ const cacheKey = 'page_block_' + id
+ const pageBlock = await getDataFromCache(cacheKey)
+ if (pageBlock) {
+ console.error('[重试获取缓存]', `from:${from}`, `id:${id}`)
+ return pageBlock
+ }
+ return await getPageWithRetry(id, from, retryAttempts - 1)
+ }
+ } else {
+ return null
+ }
+}
+
/**
* 获取到的blockMap删除不需要的字段
* @param {*} id 页面ID
@@ -49,6 +67,16 @@ function filterPostBlocks(id, pageBlock, slice) {
continue
}
count++
+ // 处理 c++ 和 c#两种语言
+ if (b?.value?.type === 'code') {
+ if (b?.value?.properties?.language?.[0][0] === 'C++') {
+ b.value.properties.language[0][0] = 'cpp'
+ }
+ if (b?.value?.properties?.language?.[0][0] === 'C#') {
+ b.value.properties.language[0][0] = 'csharp'
+ }
+ }
+
delete b?.role
delete b?.value?.version
delete b?.value?.created_by_table
@@ -64,15 +92,3 @@ function filterPostBlocks(id, pageBlock, slice) {
}
return clonePageBlock
}
-
-function deepClone(obj) {
- const newObj = Array.isArray(obj) ? [] : {}
- if (obj && typeof obj === 'object') {
- for (const key in obj) {
- if (Object.prototype.hasOwnProperty.call(obj, key)) {
- newObj[key] = (obj && typeof obj[key] === 'object') ? deepClone(obj[key]) : obj[key]
- }
- }
- }
- return newObj
-}
diff --git a/lib/utils.js b/lib/utils.js
index be9db197..e4b854a7 100644
--- a/lib/utils.js
+++ b/lib/utils.js
@@ -80,3 +80,22 @@ export function isObject(item) {
export function isIterable(obj) {
return obj != null && typeof obj[Symbol.iterator] === 'function'
}
+
+export function deepClone(obj) {
+ const newObj = Array.isArray(obj) ? [] : {}
+ if (obj && typeof obj === 'object') {
+ for (const key in obj) {
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
+ newObj[key] = (obj && typeof obj[key] === 'object') ? deepClone(obj[key]) : obj[key]
+ }
+ }
+ }
+ return newObj
+}
+
+/**
+ * 延时
+ * @param {*} ms
+ * @returns
+ */
+export const delay = ms => new Promise(resolve => setTimeout(resolve, ms))
diff --git a/package.json b/package.json
index 9b48294c..99bb3f68 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "notion-next",
- "version": "3.2.1",
+ "version": "3.3.1",
"homepage": "https://github.com/tangly1024/NotionNext.git",
"license": "MIT",
"repository": {
@@ -24,6 +24,7 @@
"@next/bundle-analyzer": "^12.1.1",
"@popperjs/core": "^2.9.3",
"animate.css": "^4.1.1",
+ "animejs": "^3.2.1",
"axios": ">=0.21.1",
"copy-to-clipboard": "^3.3.1",
"feed": "^4.2.2",
diff --git a/pages/[slug].js b/pages/[slug].js
index cabde864..43c98844 100644
--- a/pages/[slug].js
+++ b/pages/[slug].js
@@ -3,7 +3,8 @@ import { getPostBlocks } from '@/lib/notion'
import { getGlobalNotionData } from '@/lib/notion/getNotionData'
import { useGlobal } from '@/lib/global'
import * as ThemeMap from '@/themes'
-import { useEffect, useState } from 'react'
+import React from 'react'
+import { useRouter } from 'next/router'
/**
* 根据notion的slug访问页面,针对类型为Page的页面
@@ -11,16 +12,32 @@ import { useEffect, useState } from 'react'
* @returns
*/
const Slug = props => {
- const { theme } = useGlobal()
+ const { theme, changeLoadingState } = useGlobal()
const ThemeComponents = ThemeMap[theme]
const { post } = props
+
if (!post) {
- return
+ changeLoadingState(true)
+ const router = useRouter()
+ setTimeout(() => {
+ if (typeof document !== 'undefined') {
+ 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
}
+ changeLoadingState(false)
+
// 文章锁🔐
- const [lock, setLock] = useState(post.password && post.password !== '')
- useEffect(() => {
+ const [lock, setLock] = React.useState(post.password && post.password !== '')
+ React.useEffect(() => {
if (post.password && post.password !== '') {
setLock(true)
} else {
@@ -40,12 +57,13 @@ const Slug = props => {
const { siteInfo } = props
const meta = {
- title: `${post.title} | ${siteInfo.title}`,
- description: post.summary,
+ title: `${post?.title} | ${siteInfo?.title}`,
+ description: post?.summary,
type: 'article',
- image: post.page_cover,
- slug: post.slug,
- tags: post.tags
+ slug: 'article/' + post?.slug,
+ image: post?.page_cover,
+ category: post?.category?.[0],
+ tags: post?.tags
}
props = { ...props, meta, lock, setLock, validPassword }
diff --git a/pages/_app.js b/pages/_app.js
index d9ae363f..3554446b 100644
--- a/pages/_app.js
+++ b/pages/_app.js
@@ -8,15 +8,14 @@ import '@/styles/notion.css' // 重写部分样式
// used for collection views (optional)
// import 'rc-dropdown/assets/index.css'
-// used for code syntax highlighting (optional)
-import 'prismjs/themes/prism-okaidia.css'
-// used for rendering equations (optional)
+import 'prismjs/themes/prism-tomorrow.min.css'
import 'react-notion-x/build/third-party/equation.css'
import dynamic from 'next/dynamic'
import { GlobalContextProvider } from '@/lib/global'
import { DebugPanel } from '@/components/DebugPanel'
import { ThemeSwitch } from '@/components/ThemeSwitch'
+import { Fireworks } from '@/components/Fireworks'
const Ackee = dynamic(() => import('@/components/Ackee'), { ssr: false })
const Gtag = dynamic(() => import('@/components/Gtag'), { ssr: false })
@@ -29,23 +28,25 @@ const Messenger = dynamic(() => import('@/components/FacebookMessenger'), {
})
const MyApp = ({ Component, pageProps }) => {
+ // 外部插件
+ const externalPlugins = <>
+ {JSON.parse(BLOG.THEME_SWITCH) && }
+ {JSON.parse(BLOG.DEBUG) && }
+ {BLOG.ANALYTICS_ACKEE_TRACKER && }
+ {BLOG.ANALYTICS_GOOGLE_ID && }
+ {JSON.parse(BLOG.ANALYTICS_BUSUANZI_ENABLE) && }
+ {BLOG.ADSENSE_GOOGLE_ID && }
+ {BLOG.FACEBOOK_APP_ID && BLOG.FACEBOOK_PAGE_ID && }
+ {JSON.parse(BLOG.FIREWORKS) && }
+ >
+
return (
-
- {BLOG.THEME_SWITCH && }
- {BLOG.DEBUG && }
- {BLOG.ANALYTICS_ACKEE_TRACKER && }
- {BLOG.ANALYTICS_GOOGLE_ID && }
- {JSON.parse(BLOG.ANALYTICS_BUSUANZI_ENABLE) && }
- {BLOG.ADSENSE_GOOGLE_ID && }
- {BLOG.FACEBOOK_APP_ID && BLOG.FACEBOOK_PAGE_ID && }
- {/* FontawesomeCDN */}
-
-
-
+
+ {/* FontawesomeCDN */}
+
+ {externalPlugins}
+
+
)
}
diff --git a/pages/archive/index.js b/pages/archive/index.js
index 5241697d..e66b2abd 100644
--- a/pages/archive/index.js
+++ b/pages/archive/index.js
@@ -8,8 +8,8 @@ const ArchiveIndex = props => {
const ThemeComponents = ThemeMap[theme]
const { siteInfo } = props
const meta = {
- title: `${locale.NAV.ARCHIVE} | ${siteInfo.title}`,
- description: siteInfo.description,
+ title: `${locale.NAV.ARCHIVE} | ${siteInfo?.title}`,
+ description: siteInfo?.description,
slug: 'archive',
type: 'website'
}
diff --git a/pages/article/[slug].js b/pages/article/[slug].js
index 538cbc90..40fe2ef2 100644
--- a/pages/article/[slug].js
+++ b/pages/article/[slug].js
@@ -3,9 +3,9 @@ import { getPostBlocks } from '@/lib/notion'
import { getGlobalNotionData } from '@/lib/notion/getNotionData'
import { useGlobal } from '@/lib/global'
import * as ThemeMap from '@/themes'
-import { useEffect, useState } from 'react'
-import { useRouter } from 'next/router'
+import React from 'react'
import { idToUuid } from 'notion-utils'
+import { useRouter } from 'next/router'
/**
* 根据notion的slug访问页面
@@ -13,32 +13,33 @@ import { idToUuid } from 'notion-utils'
* @returns
*/
const Slug = props => {
- const { theme } = useGlobal()
+ const { theme, changeLoadingState } = useGlobal()
const ThemeComponents = ThemeMap[theme]
const { post } = props
if (!post) {
+ changeLoadingState(true)
const router = useRouter()
- useEffect(() => {
- setTimeout(() => {
- if (window) {
- const article = document.getElementById('container')
- if (!article) {
- router.push('/404').then(() => {
- console.warn('找不到页面', router.asPath)
- })
- }
+ setTimeout(() => {
+ if (typeof document !== 'undefined') {
+ const article = document.getElementById('container')
+ if (!article) {
+ router.push('/404').then(() => {
+ console.warn('找不到页面', router.asPath)
+ })
}
- }, 3000)
- })
+ }
+ }, 10000)
const meta = { title: `${props?.siteInfo?.title || BLOG.TITLE} | loading` }
return
}
+ changeLoadingState(false)
+
// 文章锁🔐
- const [lock, setLock] = useState(post.password && post.password !== '')
- useEffect(() => {
- if (post.password && post.password !== '') {
+ const [lock, setLock] = React.useState(post?.password && post?.password !== '')
+ React.useEffect(() => {
+ if (post?.password && post?.password !== '') {
setLock(true)
} else {
setLock(false)
@@ -46,9 +47,9 @@ const Slug = props => {
}, [post])
/**
- * 验证文章密码
- * @param {*} result
- */
+ * 验证文章密码
+ * @param {*} result
+ */
const validPassword = result => {
if (result) {
setLock(false)
@@ -59,13 +60,13 @@ const Slug = props => {
const { siteInfo } = props
const meta = {
- title: `${props.post.title} | ${siteInfo.title}`,
- description: props.post.summary,
+ title: `${post?.title} | ${siteInfo?.title}`,
+ description: post?.summary,
type: 'article',
- slug: 'article/' + props.post.slug,
- image: props.post.page_cover,
- category: props.post.category?.[0],
- tags: props.post.tags
+ slug: 'article/' + post?.slug,
+ image: post?.page_cover,
+ category: post?.category?.[0],
+ tags: post?.tags
}
return (
diff --git a/pages/category/index.js b/pages/category/index.js
index 1545408b..f099d29f 100644
--- a/pages/category/index.js
+++ b/pages/category/index.js
@@ -9,8 +9,8 @@ export default function Category(props) {
const { locale } = useGlobal()
const { siteInfo } = props
const meta = {
- title: `${locale.COMMON.CATEGORY} | ${siteInfo.title}`,
- description: siteInfo.description,
+ title: `${locale.COMMON.CATEGORY} | ${siteInfo?.title}`,
+ description: siteInfo?.description,
slug: 'category',
type: 'website'
}
diff --git a/pages/feed.js b/pages/feed.js
index ccfe5ff4..127f9402 100644
--- a/pages/feed.js
+++ b/pages/feed.js
@@ -4,7 +4,7 @@ import { getGlobalNotionData } from '@/lib/notion/getNotionData'
export async function getServerSideProps ({ res }) {
res.setHeader('Content-Type', 'text/xml')
// 获取最新文章
- const globalNotionData = await getGlobalNotionData({ from: 'rss', latestPostCount: 5 })
+ const globalNotionData = await getGlobalNotionData({ from: 'rss' })
const xmlFeed = await generateRss(globalNotionData?.latestPosts || [])
res.write(xmlFeed)
res.end()
diff --git a/pages/index.js b/pages/index.js
index 9b8c8511..fe8de2cb 100644
--- a/pages/index.js
+++ b/pages/index.js
@@ -14,9 +14,9 @@ export async function getStaticProps() {
const props = await getGlobalNotionData({ from, pageType: ['Post'] })
const { allPosts, siteInfo } = props
const meta = {
- title: `${siteInfo.title} | ${siteInfo.description}`,
- description: siteInfo.description,
- image: siteInfo.pageCover,
+ title: `${siteInfo?.title} | ${siteInfo?.description}`,
+ description: siteInfo?.description,
+ image: siteInfo?.pageCover,
slug: '',
type: 'website'
}
diff --git a/pages/page/[page].js b/pages/page/[page].js
index 1de94693..b6787758 100644
--- a/pages/page/[page].js
+++ b/pages/page/[page].js
@@ -12,8 +12,8 @@ const Page = props => {
return <>>
}
const meta = {
- title: `${props.page} | Page | ${siteInfo.title}`,
- description: siteInfo.description,
+ title: `${props.page} | Page | ${siteInfo?.title}`,
+ description: siteInfo?.description,
slug: 'page/' + props.page,
type: 'website'
}
diff --git a/pages/search/index.js b/pages/search/index.js
index 222d03d0..80cfe52c 100644
--- a/pages/search/index.js
+++ b/pages/search/index.js
@@ -23,9 +23,9 @@ const Search = props => {
const { locale } = useGlobal()
const meta = {
title: `${searchKey || ''}${searchKey ? ' | ' : ''}${locale.NAV.SEARCH} | ${
- siteInfo.title
+ siteInfo?.title
}`,
- description: siteInfo.description,
+ description: siteInfo?.description,
slug: 'search',
type: 'website'
}
diff --git a/pages/tag/[tag].js b/pages/tag/[tag].js
index b5f36c0a..be89d2fa 100644
--- a/pages/tag/[tag].js
+++ b/pages/tag/[tag].js
@@ -53,7 +53,7 @@ function getTagNames(tags) {
export async function getStaticPaths() {
const from = 'tag-static-path'
- const { tags } = await getGlobalNotionData({ from, tagsCount: 0 })
+ const { tags } = await getGlobalNotionData({ from })
const tagNames = getTagNames(tags)
return {
diff --git a/pages/tag/index.js b/pages/tag/index.js
index 50c9ff8c..302511dc 100644
--- a/pages/tag/index.js
+++ b/pages/tag/index.js
@@ -9,8 +9,8 @@ const TagIndex = props => {
const { locale } = useGlobal()
const { siteInfo } = props
const meta = {
- title: `${locale.COMMON.TAGS} | ${siteInfo.title}`,
- description: siteInfo.description,
+ title: `${locale.COMMON.TAGS} | ${siteInfo?.title}`,
+ description: siteInfo?.description,
slug: 'tag',
type: 'website'
}
@@ -19,7 +19,7 @@ const TagIndex = props => {
export async function getStaticProps() {
const from = 'tag-index-props'
- const props = await getGlobalNotionData({ from, tagsCount: 0 })
+ const props = await getGlobalNotionData({ from })
return {
props,
revalidate: 1
diff --git a/styles/globals.css b/styles/globals.css
index 1df15802..feb5f75e 100644
--- a/styles/globals.css
+++ b/styles/globals.css
@@ -145,4 +145,12 @@ nav {
.notion-code-copy-button > svg{
pointer-events:none
-}
\ No newline at end of file
+}
+
+.fireworks{
+ position: fixed;
+ left: 0;
+ top: 0;
+ z-index: 1000;
+ pointer-events: none;
+}
diff --git a/styles/notion.css b/styles/notion.css
index b176aed2..76807da7 100644
--- a/styles/notion.css
+++ b/styles/notion.css
@@ -530,8 +530,7 @@
width: 100%;
margin: 6px 0;
padding: 0;
- border-top: none;
- border-color: var(--fg-color-0);
+ border-bottom-width: 1px;
}
.notion-link {
diff --git a/themes/example/Layout404.js b/themes/example/Layout404.js
index cd28a607..5f92f0cc 100644
--- a/themes/example/Layout404.js
+++ b/themes/example/Layout404.js
@@ -1,6 +1,7 @@
+import LayoutBase from './LayoutBase'
-export const Layout404 = () => {
- return
- 404 Not found.
-
+export const Layout404 = (props) => {
+ return
+ 404 Not found.
+
}
diff --git a/themes/example/LayoutArchive.js b/themes/example/LayoutArchive.js
index 9edb9c24..1b3d1e34 100644
--- a/themes/example/LayoutArchive.js
+++ b/themes/example/LayoutArchive.js
@@ -24,15 +24,13 @@ export const LayoutArchive = props => {
})
return (
-
+
{Object.keys(archivePosts).map(archiveTitle => (
-
+
{archiveTitle}
+
{archivePosts[archiveTitle].map(post => (
- {
- const { children, meta, customNav, siteInfo } = props
- const { locale } = useGlobal()
- const d = new Date()
- const currentYear = d.getFullYear()
- const startYear = BLOG.SINCE && BLOG.SINCE !== currentYear && BLOG.SINCE + '-'
-
- let links = [
- { icon: 'fas fa-search', name: locale.NAV.SEARCH, to: '/search' },
- { icon: 'fas fa-archive', name: locale.NAV.ARCHIVE, to: '/archive' },
- { icon: 'fas fa-folder', name: locale.COMMON.CATEGORY, to: '/category' },
- { icon: 'fas fa-tag', name: locale.COMMON.TAGS, to: '/tag' }
- ]
-
- if (customNav) {
- links = links.concat(customNav)
- }
-
+ const { children, meta } = props
return (
-
-
- {/* 导航菜单 */}
-
-
-
-
-
-
+
+
+ {/* 顶栏LOGO */}
+
- {/* 内容主体 */}
-
- {children}
-
-
+ {/* 菜单 */}
+
- {/* 页脚 */}
-
-
+ {/* 主体 */}
+
+
+
+
+
+
+
+
)
}
diff --git a/themes/example/LayoutCategory.js b/themes/example/LayoutCategory.js
index 85d399f6..ef344ea2 100644
--- a/themes/example/LayoutCategory.js
+++ b/themes/example/LayoutCategory.js
@@ -23,26 +23,39 @@ export const LayoutCategory = props => {
updatePage(page + 1)
}
- return (
-
- Category - {category}
- {postsToShow.map(p => (
-
-
-
{p.title}
-
-
{p.summary}
+ return
+
+
{category}
+
+ {postsToShow.map(p => (
+
+
+
+
+
+
+ {p.summary}
+
+
+ ))}
+
+
+ {' '}
+ {hasMore ? locale.COMMON.MORE : `${locale.COMMON.NO_MORE} 😰`}{' '}
+
- ))}
-
-
- {' '}
- {hasMore ? locale.COMMON.MORE : `${locale.COMMON.NO_MORE} 😰`}{' '}
-
-
-
- )
+
}
diff --git a/themes/example/LayoutCategoryIndex.js b/themes/example/LayoutCategoryIndex.js
index b748b140..7690b316 100644
--- a/themes/example/LayoutCategoryIndex.js
+++ b/themes/example/LayoutCategoryIndex.js
@@ -1,15 +1,11 @@
-import { useGlobal } from '@/lib/global'
import Link from 'next/link'
import LayoutBase from './LayoutBase'
export const LayoutCategoryIndex = (props) => {
const { categories } = props
- const { locale } = useGlobal()
+
return
-
-
- {locale.COMMON.CATEGORY}:
-
+
{categories && categories.map(category => {
return
diff --git a/themes/example/LayoutIndex.js b/themes/example/LayoutIndex.js
index 11da972e..7d6a17a6 100644
--- a/themes/example/LayoutIndex.js
+++ b/themes/example/LayoutIndex.js
@@ -1,74 +1,11 @@
-import BLOG from '@/blog.config'
-import { useGlobal } from '@/lib/global'
-import Link from 'next/link'
-import { useRouter } from 'next/router'
+
+import { BlogList } from './components/BlogList'
import LayoutBase from './LayoutBase'
export const LayoutIndex = props => {
- const { posts, postCount } = props
-
- const { locale } = useGlobal()
- const router = useRouter()
- const totalPage = Math.ceil(postCount / BLOG.POSTS_PER_PAGE)
-
- const page = 1
- const showNext =
- page < totalPage &&
- posts.length === BLOG.POSTS_PER_PAGE &&
- posts.length < postCount
-
- const currentPage = +page
return (
- {posts.map(p => (
-
- ))}
-
-
+
)
}
diff --git a/themes/example/LayoutPage.js b/themes/example/LayoutPage.js
index d8bb93ba..fc19035f 100644
--- a/themes/example/LayoutPage.js
+++ b/themes/example/LayoutPage.js
@@ -1,71 +1,10 @@
-import BLOG from '@/blog.config'
-import { useGlobal } from '@/lib/global'
-import Link from 'next/link'
-import { useRouter } from 'next/router'
+import { BlogList } from './components/BlogList'
import LayoutBase from './LayoutBase'
export const LayoutPage = props => {
- const { page } = props
- const { posts, postCount } = props
-
- const { locale } = useGlobal()
- const router = useRouter()
- const totalPage = Math.ceil(postCount / BLOG.POSTS_PER_PAGE)
-
- const showNext =
- page < totalPage &&
- posts.length === BLOG.POSTS_PER_PAGE &&
- posts.length < postCount
-
- const currentPage = +page
return (
- {posts.map(p => (
-
- ))}
-
-
+
)
}
diff --git a/themes/example/LayoutSearch.js b/themes/example/LayoutSearch.js
index 110c11a6..c79c3ed0 100644
--- a/themes/example/LayoutSearch.js
+++ b/themes/example/LayoutSearch.js
@@ -1,21 +1,24 @@
import BLOG from '@/blog.config'
import { useGlobal } from '@/lib/global'
import Link from 'next/link'
+import { useRouter } from 'next/router'
import { useEffect, useState } from 'react'
import SearchInput from './components/SearchInput'
import LayoutBase from './LayoutBase'
export const LayoutSearch = props => {
const { keyword, posts } = props
+ const router = useRouter()
+
useEffect(() => {
setTimeout(() => {
- const container = document.getElementById('container')
+ const container = typeof document !== 'undefined' && document.getElementById('container')
if (container && container.innerHTML) {
const re = new RegExp(`${keyword}`, 'gim')
container.innerHTML = container.innerHTML.replace(re, `
${keyword}`)
}
}, 100)
- })
+ }, [router.events])
const { locale } = useGlobal()
@@ -33,28 +36,58 @@ export const LayoutSearch = props => {
if (!hasMore) return
updatePage(page + 1)
}
+ useEffect(() => {
+ setTimeout(() => {
+ if (keyword) {
+ const targets = document.getElementsByClassName('replace')
+ for (const container of targets) {
+ if (container && container.innerHTML) {
+ const re = new RegExp(`${keyword}`, 'gim')
+ container.innerHTML = container.innerHTML.replace(
+ re,
+ `
${keyword}`
+ )
+ }
+ }
+ }
+ }, 100)
+ }, [])
- return (
-
- Search - {keyword}
-
- {postsToShow?.map(p => (
-
-
-
{p.title}
-
-
{p.summary}
+ return
+
+
- ))}
-
-
- {' '}
- {hasMore ? locale.COMMON.MORE : `${locale.COMMON.NO_MORE} 😰`}{' '}
+
+ {postsToShow.map(p => (
+
+
+
+
+
+
+ {p.summary}
+
+
+ ))}
+
+
+
+ {' '}
+ {hasMore ? locale.COMMON.MORE : `${locale.COMMON.NO_MORE} 😰`}{' '}
+
-
- )
}
diff --git a/themes/example/LayoutSlug.js b/themes/example/LayoutSlug.js
index 91b11d80..1f7fd409 100644
--- a/themes/example/LayoutSlug.js
+++ b/themes/example/LayoutSlug.js
@@ -2,9 +2,8 @@ import { getPageTableOfContents } from 'notion-utils'
import LayoutBase from './LayoutBase'
import { ArticleLock } from './components/ArticleLock'
import NotionPage from '@/components/NotionPage'
-import Link from 'next/link'
-import { useGlobal } from '@/lib/global'
-import formatDate from '@/lib/formatDate'
+import { ArticleInfo } from './components/ArticleInfo'
+import Comment from '@/components/Comment'
export const LayoutSlug = props => {
const { post, lock, validPassword } = props
@@ -18,57 +17,20 @@ export const LayoutSlug = props => {
post.toc = getPageTableOfContents(post, post.blockMap)
}
- const { locale } = useGlobal()
- const date = formatDate(post?.date?.start_date || post?.createdTime, locale.LOCALE)
-
return (
-
-
-
{post?.title}
+
- {lock && }
+ {lock && }
- {!lock &&
-
-
-
-
-
- {post.category}
-
-
-
|
+ {!lock &&
- {post?.type[0] !== 'Page' && (<>
-
-
- {date}
-
-
-
|
-
- {locale.COMMON.LAST_EDITED_TIME}: {post.lastEditedTime}
-
-
|
+ {post && <>
+
+
+
+ >}
+ }
- >)}
-
-
-
-
-
-
-
-
-
-
- {post && }
- }
-
-
-
+
)
}
diff --git a/themes/example/LayoutTag.js b/themes/example/LayoutTag.js
index e1a549f4..e9809123 100644
--- a/themes/example/LayoutTag.js
+++ b/themes/example/LayoutTag.js
@@ -5,7 +5,7 @@ import { useState } from 'react'
import LayoutBase from './LayoutBase'
export const LayoutTag = props => {
- const { tag, posts } = props
+ const { posts } = props
const { locale } = useGlobal()
const [page, updatePage] = useState(1)
@@ -24,26 +24,36 @@ export const LayoutTag = props => {
updatePage(page + 1)
}
- return (
-
- Tag - {tag}
- {postsToShow.map(p => (
-
- ))}
-
+ return
+ {postsToShow.map(p => (
+
+
+
+
+
+
+ {p.summary}
+
+
+ ))}
+
- {' '}
- {hasMore ? locale.COMMON.MORE : `${locale.COMMON.NO_MORE} 😰`}{' '}
+ {' '}
+ {hasMore ? locale.COMMON.MORE : `${locale.COMMON.NO_MORE} 😰`}{' '}
-
-
- )
+
+
}
diff --git a/themes/example/LayoutTagIndex.js b/themes/example/LayoutTagIndex.js
index 3fb41c9f..6a89e168 100644
--- a/themes/example/LayoutTagIndex.js
+++ b/themes/example/LayoutTagIndex.js
@@ -1,24 +1,21 @@
-import { useGlobal } from '@/lib/global'
import Link from 'next/link'
import LayoutBase from './LayoutBase'
export const LayoutTagIndex = (props) => {
const { tags } = props
- const { locale } = useGlobal()
return
-
-
{locale.COMMON.TAGS}:
-
+ })}
+
+
}
diff --git a/themes/example/components/ArticleInfo.js b/themes/example/components/ArticleInfo.js
new file mode 100644
index 00000000..f7dc5a80
--- /dev/null
+++ b/themes/example/components/ArticleInfo.js
@@ -0,0 +1,47 @@
+import Link from 'next/link'
+import { useGlobal } from '@/lib/global'
+import formatDate from '@/lib/formatDate'
+
+export const ArticleInfo = (props) => {
+ const { post } = props
+
+ const { locale } = useGlobal()
+ const date = formatDate(post?.date?.start_date || post?.createdTime, locale.LOCALE)
+
+ return
+
+ {post?.type[0] !== 'Page' && <>
+
+
+
+ {post.category}
+
+
+
|
+ >}
+
+ {post?.type[0] !== 'Page' && (<>
+
+
+ {date}
+
+
+
|
+
+ {locale.COMMON.LAST_EDITED_TIME}: {post.lastEditedTime}
+
+
|
+
+
+
+
+
+ >)}
+
+
+
+
+}
diff --git a/themes/example/components/ArticleLock.js b/themes/example/components/ArticleLock.js
index bfdd00ce..1f4ade0e 100644
--- a/themes/example/components/ArticleLock.js
+++ b/themes/example/components/ArticleLock.js
@@ -24,9 +24,9 @@ export const ArticleLock = props => {
}
}
- return
+ return
-
{locale.COMMON.ARTICLE_LOCK_TIPS}
+
{locale.COMMON.ARTICLE_LOCK_TIPS}
@@ -36,5 +36,5 @@ export const ArticleLock = props => {
-
+
}
diff --git a/themes/example/components/BlogList.js b/themes/example/components/BlogList.js
new file mode 100644
index 00000000..7e2b49f0
--- /dev/null
+++ b/themes/example/components/BlogList.js
@@ -0,0 +1,62 @@
+
+import BLOG from '@/blog.config'
+import { useGlobal } from '@/lib/global'
+import { useRouter } from 'next/router'
+import Link from 'next/link'
+
+export const BlogList = (props) => {
+ const { page, posts, postCount } = props
+
+ const { locale } = useGlobal()
+ const router = useRouter()
+ const totalPage = Math.ceil(postCount / BLOG.POSTS_PER_PAGE)
+
+ const showNext =
+ page < totalPage &&
+ posts.length === BLOG.POSTS_PER_PAGE &&
+ posts.length < postCount
+
+ const currentPage = +page
+
+ return
+
+ {posts.map(p => (
+
+
+
+
+
+
+ {p.summary}
+
+ {/* 搜索结果 */}
+ {p.results && (
+
+ {p.results.map(r => (
+ {r}
+ ))}
+
+ )}
+
+ ))}
+
+
+
+}
diff --git a/themes/example/components/Footer.js b/themes/example/components/Footer.js
new file mode 100644
index 00000000..d94e6648
--- /dev/null
+++ b/themes/example/components/Footer.js
@@ -0,0 +1,49 @@
+import BLOG from '@/blog.config'
+
+export const Footer = (props) => {
+ const d = new Date()
+ const currentYear = d.getFullYear()
+ const startYear = BLOG.SINCE && BLOG.SINCE !== currentYear && BLOG.SINCE + '-'
+
+ // {/* 页脚 */}
+ //
+
+ return
+}
diff --git a/themes/example/components/Header.js b/themes/example/components/Header.js
new file mode 100644
index 00000000..399fb5b7
--- /dev/null
+++ b/themes/example/components/Header.js
@@ -0,0 +1,21 @@
+import Link from 'next/link'
+
+/**
+ * 网站顶部
+ * @returns
+ */
+export const Header = (props) => {
+ const { siteInfo } = props
+
+ return
+}
diff --git a/themes/example/components/JumpToTopButton.js b/themes/example/components/JumpToTopButton.js
new file mode 100644
index 00000000..30e684a8
--- /dev/null
+++ b/themes/example/components/JumpToTopButton.js
@@ -0,0 +1,19 @@
+import { useGlobal } from '@/lib/global'
+import React from 'react'
+
+/**
+ * 跳转到网页顶部
+ * 当屏幕下滑500像素后会出现该控件
+ * @param targetRef 关联高度的目标html标签
+ * @param showPercent 是否显示百分比
+ * @returns {JSX.Element}
+ * @constructor
+ */
+const JumpToTopButton = () => {
+ const { locale } = useGlobal()
+ return
window.scrollTo({ top: 0, behavior: 'smooth' })}
+ >
+
+}
+
+export default JumpToTopButton
diff --git a/themes/example/components/Nav.js b/themes/example/components/Nav.js
new file mode 100644
index 00000000..4c3217c0
--- /dev/null
+++ b/themes/example/components/Nav.js
@@ -0,0 +1,39 @@
+import { useGlobal } from '@/lib/global'
+import Link from 'next/link'
+
+/**
+ * 菜单导航
+ * @param {*} props
+ * @returns
+ */
+export const Nav = (props) => {
+ const { customNav } = props
+ const { locale } = useGlobal()
+ let links = [
+ { icon: 'fas fa-search', name: locale.NAV.SEARCH, to: '/search' },
+ { icon: 'fas fa-archive', name: locale.NAV.ARCHIVE, to: '/archive' },
+ { icon: 'fas fa-folder', name: locale.COMMON.CATEGORY, to: '/category' },
+ { icon: 'fas fa-tag', name: locale.COMMON.TAGS, to: '/tag' }
+ ]
+
+ if (customNav) {
+ links = links.concat(customNav)
+ }
+
+ return
+}
diff --git a/themes/example/components/SideBar.js b/themes/example/components/SideBar.js
new file mode 100644
index 00000000..b082bc6d
--- /dev/null
+++ b/themes/example/components/SideBar.js
@@ -0,0 +1,44 @@
+import Live2D from '@/components/Live2D'
+import { useGlobal } from '@/lib/global'
+import Link from 'next/link'
+
+export const SideBar = (props) => {
+ const { locale } = useGlobal()
+ const { latestPosts, categories } = props
+ return
+
+
+
+
+
+
+
+
+}
diff --git a/themes/example/components/Title.js b/themes/example/components/Title.js
new file mode 100644
index 00000000..b0535341
--- /dev/null
+++ b/themes/example/components/Title.js
@@ -0,0 +1,19 @@
+import BLOG from '@/blog.config'
+
+/**
+ * 标题栏
+ * @param {*} props
+ * @returns
+ */
+export const Title = (props) => {
+ const { siteInfo, post } = props
+ const title = post?.title || siteInfo?.description
+ const description = post?.description || BLOG.AUTHOR
+
+ return
+
{title}
+
+ {description}
+
+
+}
diff --git a/themes/fukasawa/LayoutSearch.js b/themes/fukasawa/LayoutSearch.js
index 46d01031..f3e8da7d 100644
--- a/themes/fukasawa/LayoutSearch.js
+++ b/themes/fukasawa/LayoutSearch.js
@@ -9,7 +9,7 @@ export const LayoutSearch = (props) => {
const currentSearch = keyword || router?.query?.s
useEffect(() => {
setTimeout(() => {
- const container = document.getElementById('container')
+ const container = typeof document !== 'undefined' && document.getElementById('container')
if (container && container.innerHTML) {
const re = new RegExp(`${currentSearch}`, 'gim')
container.innerHTML = container.innerHTML.replace(re, `
${currentSearch}`)
diff --git a/themes/fukasawa/components/ArticleLock.js b/themes/fukasawa/components/ArticleLock.js
index 0d10b513..23112eeb 100644
--- a/themes/fukasawa/components/ArticleLock.js
+++ b/themes/fukasawa/components/ArticleLock.js
@@ -24,11 +24,11 @@ export const ArticleLock = props => {
}
}
- return (
+ return (
-
{locale.COMMON.ARTICLE_LOCK_TIPS}
+
{locale.COMMON.ARTICLE_LOCK_TIPS}
- { siteInfo.description }
+ { siteInfo?.description }
{router.asPath !== '/tag' &&
diff --git a/themes/hexo/Layout404.js b/themes/hexo/Layout404.js
index 2bc61804..f64e8016 100644
--- a/themes/hexo/Layout404.js
+++ b/themes/hexo/Layout404.js
@@ -8,7 +8,7 @@ export const Layout404 = props => {
// 延时3秒如果加载失败就返回首页
setTimeout(() => {
if (window) {
- const article = document.getElementById('container')
+ const article = typeof document !== 'undefined' && document.getElementById('container')
if (!article) {
router.push('/').then(() => {
console.log('找不到页面', router.asPath)
diff --git a/themes/hexo/LayoutBase.js b/themes/hexo/LayoutBase.js
index d7fdbf2b..c8ca3f8a 100644
--- a/themes/hexo/LayoutBase.js
+++ b/themes/hexo/LayoutBase.js
@@ -59,15 +59,14 @@ const LayoutBase = props => {
{headerSlot}
-
+
-
+
{onLoading ? : children}
-
diff --git a/themes/hexo/LayoutSlug.js b/themes/hexo/LayoutSlug.js
index e914a191..ce832a68 100644
--- a/themes/hexo/LayoutSlug.js
+++ b/themes/hexo/LayoutSlug.js
@@ -58,7 +58,7 @@ export const LayoutSlug = props => {
{/* Notion文章主体 */}
-
+
diff --git a/themes/hexo/components/ArticleLock.js b/themes/hexo/components/ArticleLock.js
index b7165362..9a2c5705 100644
--- a/themes/hexo/components/ArticleLock.js
+++ b/themes/hexo/components/ArticleLock.js
@@ -23,7 +23,7 @@ export const ArticleLock = props => {
}
}
- return
+ return
{locale.COMMON.ARTICLE_LOCK_TIPS}
@@ -35,5 +35,5 @@ export const ArticleLock = props => {
-
+
}
diff --git a/themes/hexo/components/Header.js b/themes/hexo/components/Header.js
index 388f01a3..c1e4e71f 100644
--- a/themes/hexo/components/Header.js
+++ b/themes/hexo/components/Header.js
@@ -103,11 +103,11 @@ 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.8) ),url("${siteInfo.pageCover}")`
+ `linear-gradient(rgba(0, 0, 0, 0.8), rgba(0,0,0,0.2), rgba(0, 0, 0, 0.8) ),url("${siteInfo?.pageCover}")`
}}
>
-
{siteInfo.title}
+
{siteInfo?.title}
diff --git a/themes/hexo/components/HeaderArticle.js b/themes/hexo/components/HeaderArticle.js
index bd0bea4a..8513419d 100644
--- a/themes/hexo/components/HeaderArticle.js
+++ b/themes/hexo/components/HeaderArticle.js
@@ -5,7 +5,7 @@ import { useEffect } from 'react'
export default function HeaderArticle({ post, siteInfo }) {
if (!post) {
- return <>loading...>
+ return <>>
}
const headerImage = post?.page_cover ? `url("${post.page_cover}")` : `url("${siteInfo?.pageCover}")`
const { isDarkMode } = useGlobal()
@@ -41,6 +41,9 @@ export default function HeaderArticle({ post, siteInfo }) {
if (!isDarkMode) {
const stickyNavElement = document.getElementById('sticky-nav')
const header = document.querySelector('#header')
+ if (!header || !stickyNavElement) {
+ return
+ }
if (window.scrollY < header.clientHeight) {
stickyNavElement?.classList?.add('dark')
} else {
diff --git a/themes/hexo/components/Logo.js b/themes/hexo/components/Logo.js
index 8844d1ad..e1adeb3d 100644
--- a/themes/hexo/components/Logo.js
+++ b/themes/hexo/components/Logo.js
@@ -1,12 +1,13 @@
+import BLOG from '@/blog.config'
import Link from 'next/link'
import React from 'react'
const Logo = props => {
const { siteInfo } = props
return
-
+
+
{siteInfo?.title || BLOG.TITLE}
+
}
export default Logo
diff --git a/themes/hexo/components/MenuList.js b/themes/hexo/components/MenuList.js
index c8103624..3f16a2ff 100644
--- a/themes/hexo/components/MenuList.js
+++ b/themes/hexo/components/MenuList.js
@@ -21,7 +21,7 @@ const MenuList = (props) => {
links = links.concat(customNav)
}
- return