mirror of
https://github.com/d0zingcat/NotionNext.git
synced 2026-05-16 07:26:47 +00:00
@@ -1,2 +1,2 @@
|
||||
# 环境变量 @see https://www.nextjs.cn/docs/basic-features/environment-variables
|
||||
NEXT_PUBLIC_VERSION=3.5.1
|
||||
NEXT_PUBLIC_VERSION=3.6.0
|
||||
@@ -115,6 +115,8 @@ yarn run start # 本地启动NextJS服务
|
||||
|
||||
<td align="center"><a href="https://github.com/qfdk"><img src="https://avatars.githubusercontent.com/u/2404478" width="64px;" alt="qfdk"/><br/><sub><b>qfdk</b></sub></a><br/><a href="https://github.com/tangly1024/NotionNext/commits?author=qfdk" title="qfdk" >🔧 🐛</a></td>
|
||||
|
||||
<td align="center"><a href="https://github.com/ifyz"><img src="https://avatars.githubusercontent.com/u/118271360" width="64px;" alt="ifyz"/><br/><sub><b>ifyz</b></sub></a><br/><a href="https://github.com/tangly1024/NotionNext/commits?author=ifyz" title="ifyz" >🔧 🐛</a></td>
|
||||
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
@@ -27,9 +27,9 @@ const BLOG = {
|
||||
|
||||
// 自定义字体示例: 请先将 CUSTOM_FONT 改为 true, 并将 CUSTOM_FONT_URL 改为你的字体CSS地址,同时在 CUSTOM_FONT_SANS 与 CUSTOM_FONT_SERIF 中指定你的 fontfamily
|
||||
CUSTOM_FONT: true, // 是否使用自定义字体
|
||||
CUSTOM_FONT_URL: ['https://cdn.jsdelivr.net/npm/lxgw-wenkai-screen-webfont@1.6.0/lxgwwenkaiscreen.css'], // 自定义字体的CSS
|
||||
CUSTOM_FONT_SANS: ['LXGW WenKai Screen'], // 自定义无衬线字体
|
||||
CUSTOM_FONT_SERIF: ['LXGW WenKai Screen'], // 自定义衬线字体
|
||||
CUSTOM_FONT_URL: ['https://npm.elemecdn.com/lxgw-wenkai-webfont@1.6.0/style.css'], // 自定义字体的CSS
|
||||
CUSTOM_FONT_SANS: ['LXGW WenKai'], // 自定义无衬线字体
|
||||
CUSTOM_FONT_SERIF: ['LXGW WenKai'], // 自定义衬线字体
|
||||
|
||||
// 图标库CDN(可以直接改版本号)
|
||||
FONT_AWESOME_PATH: 'https://cdn.bootcdn.net/ajax/libs/font-awesome/6.2.0/css/all.min.css',
|
||||
@@ -52,11 +52,13 @@ const BLOG = {
|
||||
PREVIEW_TAG_COUNT: 16, // 首页最多展示的标签数量,0为不限制
|
||||
|
||||
// 社交链接,不需要可留空白,例如 CONTACT_WEIBO:''
|
||||
CONTACT_EMAIL: 'mail@tangly1024.com',
|
||||
CONTACT_WEIBO: '',
|
||||
CONTACT_TWITTER: '',
|
||||
CONTACT_GITHUB: 'https://github.com/tangly1024',
|
||||
CONTACT_TELEGRAM: 'https://t.me/tangly_1024',
|
||||
CONTACT_EMAIL: 'mail@tangly1024.com', // 邮箱
|
||||
CONTACT_WEIBO: '', // 你的微博个人主页
|
||||
CONTACT_TWITTER: '', // 你的twitter个人主页
|
||||
CONTACT_GITHUB: 'https://github.com/tangly1024', // 你的github个人主页
|
||||
CONTACT_TELEGRAM: 'https://t.me/tangly_1024', // 你的telegram 地址 例如 https://t.me/tangly_1024
|
||||
CONTACT_LINKEDIN: '', // 你的linkedIn 首页
|
||||
|
||||
|
||||
// 鼠标点击烟花特效
|
||||
FIREWORKS: process.env.NEXT_PUBLIC_FIREWORKS || false, // 鼠标点击烟花特效
|
||||
|
||||
20
components/NotionIcon.js
Normal file
20
components/NotionIcon.js
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* notion的图标icon
|
||||
* 可能是emoji 可能是 svg 也可能是 图片
|
||||
* @returns
|
||||
*/
|
||||
const NotionIcon = ({ icon }) => {
|
||||
if (!icon) {
|
||||
return <></>
|
||||
}
|
||||
|
||||
if (icon.startsWith('http')) {
|
||||
// return <Image src={icon} width={30} height={30}/>
|
||||
// eslint-disable-next-line @next/next/no-img-element
|
||||
return <img src={icon} className='w-8 float-left mr-1'/>
|
||||
}
|
||||
|
||||
return <span className='mr-1'>{icon}</span>
|
||||
}
|
||||
|
||||
export default NotionIcon
|
||||
@@ -9,6 +9,7 @@ import Link from 'next/link'
|
||||
import { Code } from 'react-notion-x/build/third-party/code'
|
||||
import { Pdf } from 'react-notion-x/build/third-party/pdf'
|
||||
import { Equation } from 'react-notion-x/build/third-party/equation'
|
||||
|
||||
import 'prismjs/components/prism-bash.js'
|
||||
import 'prismjs/components/prism-markup-templating.js'
|
||||
import 'prismjs/components/prism-markup.js'
|
||||
@@ -41,7 +42,15 @@ import 'prismjs/components/prism-swift.js'
|
||||
import 'prismjs/components/prism-wasm.js'
|
||||
import 'prismjs/components/prism-yaml.js'
|
||||
import 'prismjs/components/prism-r.js'
|
||||
import mermaid from 'mermaid'
|
||||
|
||||
// 化学方程式
|
||||
import '@/lib/mhchem'
|
||||
|
||||
// https://github.com/txs
|
||||
// import PrismMac from '@/components/PrismMac'
|
||||
const PrismMac = dynamic(() => import('@/components/PrismMac'), {
|
||||
ssr: false
|
||||
})
|
||||
|
||||
const Collection = dynamic(() =>
|
||||
import('react-notion-x/build/third-party/collection').then((m) => m.Collection), { ssr: true }
|
||||
@@ -66,14 +75,6 @@ const NotionPage = ({ post }) => {
|
||||
const zoomRef = React.useRef(zoom ? zoom.clone() : null)
|
||||
|
||||
React.useEffect(() => {
|
||||
// 支持 Mermaid
|
||||
const mermaids = document.querySelectorAll('.notion-code .language-mermaid')
|
||||
for (const e of mermaids) {
|
||||
const chart = e.innerText
|
||||
e.parentElement.parentElement.innerHTML = `<div class="mermaid">${chart}</div>`
|
||||
mermaid.contentLoaded()
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
if (window.location.hash) {
|
||||
const tocNode = document.getElementById(window.location.hash.substring(1))
|
||||
@@ -92,13 +93,18 @@ const NotionPage = ({ post }) => {
|
||||
(zoomRef.current).attach(imgList[i])
|
||||
}
|
||||
}
|
||||
|
||||
// 相册图片点击不跳转
|
||||
const cards = document.getElementsByClassName('notion-collection-card')
|
||||
for (const e of cards) {
|
||||
e.removeAttribute('href')
|
||||
}
|
||||
}
|
||||
}, 800)
|
||||
|
||||
addWatch4Dom()
|
||||
}, [])
|
||||
|
||||
return <div id='container' className='max-w-5xl overflow-x-hidden mx-auto'>
|
||||
return <div id='container' className='max-w-5xl overflow-x-visible mx-auto'>
|
||||
<PrismMac />
|
||||
<NotionRenderer
|
||||
recordMap={post.blockMap}
|
||||
mapPageUrl={mapPageUrl}
|
||||
@@ -114,72 +120,6 @@ const NotionPage = ({ post }) => {
|
||||
</div>
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听DOM变化
|
||||
* @param {*} element
|
||||
*/
|
||||
function addWatch4Dom(element) {
|
||||
// 选择需要观察变动的节点
|
||||
const targetNode = element || document?.getElementById('container')
|
||||
// 观察器的配置(需要观察什么变动)
|
||||
const config = {
|
||||
attributes: true,
|
||||
childList: true,
|
||||
subtree: true
|
||||
}
|
||||
|
||||
// 当观察到变动时执行的回调函数
|
||||
const mutationCallback = (mutations) => {
|
||||
for (const mutation of mutations) {
|
||||
const type = mutation.type
|
||||
switch (type) {
|
||||
case 'childList':
|
||||
if (mutation.target.className === 'notion-code-copy') {
|
||||
fixCopy(mutation.target)
|
||||
} else if (mutation.target.className && typeof (mutation.target.className) === 'string' && mutation?.target?.className?.indexOf('language-') > -1) {
|
||||
const copyCode = mutation.target.parentElement?.firstElementChild
|
||||
if (copyCode) {
|
||||
fixCopy(copyCode)
|
||||
}
|
||||
}
|
||||
// console.log('A child node has been added or removed.')
|
||||
break
|
||||
case 'attributes':
|
||||
// console.log(`The ${mutation.attributeName} attribute was modified.`)
|
||||
// console.log(mutation.attributeName)
|
||||
break
|
||||
case 'subtree':
|
||||
// console.log('The subtree was modified.')
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 创建一个观察器实例并传入回调函数
|
||||
const observer = new MutationObserver(mutationCallback)
|
||||
// console.log(observer)
|
||||
// 以上述配置开始观察目标节点
|
||||
if (targetNode) {
|
||||
observer.observe(targetNode, config)
|
||||
}
|
||||
|
||||
// observer.disconnect();
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制代码后,会重复 @see https://github.com/tangly1024/NotionNext/issues/165
|
||||
* @param {*} e
|
||||
*/
|
||||
function fixCopy(codeCopy) {
|
||||
const codeE = codeCopy.parentElement.lastElementChild
|
||||
const codeEnd = codeE.lastChild
|
||||
if (codeEnd.nodeName === '#text' && codeE.childNodes.length > 1) {
|
||||
codeEnd.nodeValue = null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将id映射成博文内部链接。
|
||||
* @param {*} id
|
||||
|
||||
147
components/PrismMac.js
Normal file
147
components/PrismMac.js
Normal file
@@ -0,0 +1,147 @@
|
||||
import React from 'react'
|
||||
import Prism from 'prismjs'
|
||||
import 'prismjs/plugins/toolbar/prism-toolbar'
|
||||
import 'prismjs/plugins/show-language/prism-show-language'
|
||||
import 'prismjs/plugins/copy-to-clipboard/prism-copy-to-clipboard'
|
||||
import 'prismjs/plugins/autoloader/prism-autoloader'
|
||||
import 'prismjs/plugins/line-numbers/prism-line-numbers'
|
||||
import 'prismjs/plugins/line-numbers/prism-line-numbers.css'
|
||||
|
||||
// mermaid图
|
||||
import mermaid from 'mermaid'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
/**
|
||||
* @author https://github.com/txs/
|
||||
* @returns
|
||||
*/
|
||||
const PrismMac = () => {
|
||||
const router = useRouter()
|
||||
const { isDarkMode } = useGlobal()
|
||||
const scrollTop = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop
|
||||
|
||||
React.useEffect(() => {
|
||||
renderPrismMac()
|
||||
window.scrollTo(0, scrollTop)
|
||||
router.events.on('routeChangeComplete', renderPrismMac)
|
||||
return () => {
|
||||
router.events.off('routeChangeComplete', renderPrismMac)
|
||||
}
|
||||
}, [isDarkMode])
|
||||
|
||||
React.useEffect(() => {
|
||||
addWatch4Dom()
|
||||
}, [])
|
||||
return <></>
|
||||
}
|
||||
|
||||
function renderPrismMac() {
|
||||
const container = document?.getElementById('container-inner')
|
||||
const codeToolBars = container?.getElementsByClassName('code-toolbar')
|
||||
|
||||
Array.from(codeToolBars).forEach(item => {
|
||||
const codeBlocks = item.getElementsByTagName('pre')
|
||||
if (codeBlocks.length === 0) {
|
||||
item.remove()
|
||||
}
|
||||
})
|
||||
|
||||
// 重新渲染之前检查所有的多余text
|
||||
|
||||
try {
|
||||
Prism.highlightAll()
|
||||
// Add line numbers
|
||||
const codeBlocks = container?.getElementsByTagName('pre')
|
||||
Array.from(codeBlocks).forEach(item => {
|
||||
if (!item.classList.contains('line-numbers')) {
|
||||
item.classList.add('line-numbers')
|
||||
item.style.whiteSpace = 'pre-wrap'
|
||||
}
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
// Add pre-mac element for Mac Style UI
|
||||
Array.from(codeToolBars).forEach(item => {
|
||||
const existPreMac = item.getElementsByClassName('pre-mac')
|
||||
if (existPreMac.length < codeToolBars.length) {
|
||||
const preMac = document.createElement('div')
|
||||
preMac.classList.add('pre-mac')
|
||||
preMac.innerHTML = '<span></span><span></span><span></span>'
|
||||
item?.appendChild(preMac, item)
|
||||
}
|
||||
})
|
||||
}, 0)
|
||||
} catch (err) {
|
||||
console.log('代码渲染', err)
|
||||
}
|
||||
|
||||
// 支持 Mermaid
|
||||
const mermaids = document.querySelectorAll('.notion-code .language-mermaid')
|
||||
for (const e of mermaids) {
|
||||
e.parentElement.parentElement.classList.remove('code-toolbar')
|
||||
const chart = e.firstChild.textContent
|
||||
if (e.firstElementChild) {
|
||||
e.parentElement.parentElement.remove()
|
||||
continue
|
||||
}
|
||||
if (chart) {
|
||||
e.parentElement.parentElement.innerHTML = `<div class="mermaid">${chart}</div>`
|
||||
}
|
||||
}
|
||||
mermaid.contentLoaded()
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听DOM变化
|
||||
* @param {*} element
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function addWatch4Dom(element) {
|
||||
// 选择需要观察变动的节点
|
||||
const targetNode = element || document?.getElementById('container')
|
||||
// 观察器的配置(需要观察什么变动)
|
||||
const config = {
|
||||
attributes: true,
|
||||
childList: true,
|
||||
subtree: true
|
||||
}
|
||||
|
||||
// 当观察到变动时执行的回调函数
|
||||
const mutationCallback = (mutations) => {
|
||||
for (const mutation of mutations) {
|
||||
const type = mutation.type
|
||||
switch (type) {
|
||||
case 'childList':
|
||||
// console.log('A child node has been added or removed.', mutation)
|
||||
if (mutation.addedNodes.length > 0) {
|
||||
if (mutation.addedNodes[0].nodeName === '#text') {
|
||||
mutation.addedNodes[0].remove() // 移除新增的内容
|
||||
}
|
||||
}
|
||||
break
|
||||
case 'attributes':
|
||||
// console.log(`The ${mutation.attributeName} attribute was modified.`, mutation.target)
|
||||
// console.log(mutation.attributeName)
|
||||
break
|
||||
case 'subtree':
|
||||
// console.log('The subtree was modified.', mutation.target)
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 创建一个观察器实例并传入回调函数
|
||||
const observer = new MutationObserver(mutationCallback)
|
||||
// console.log('observer', observer)
|
||||
// 以上述配置开始观察目标节点
|
||||
if (targetNode) {
|
||||
observer.observe(targetNode, config)
|
||||
}
|
||||
|
||||
// observer.disconnect()
|
||||
}
|
||||
|
||||
export default PrismMac
|
||||
1696
lib/mhchem.js
Normal file
1696
lib/mhchem.js
Normal file
File diff suppressed because it is too large
Load Diff
37
lib/notion/getNotion.js
Normal file
37
lib/notion/getNotion.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import BLOG from '@/blog.config'
|
||||
import { idToUuid } from 'notion-utils'
|
||||
import formatDate from '../formatDate'
|
||||
import { getPostBlocks } from './getPostBlocks'
|
||||
import { defaultMapImageUrl } from 'react-notion-x'
|
||||
|
||||
export async function getNotion(pageId) {
|
||||
const blockMap = await getPostBlocks(pageId, 'slug')
|
||||
if (!blockMap) {
|
||||
return null
|
||||
}
|
||||
|
||||
const postInfo = blockMap?.block?.[idToUuid(pageId)].value
|
||||
|
||||
return {
|
||||
id: pageId,
|
||||
type: postInfo,
|
||||
category: '',
|
||||
tags: [],
|
||||
title: postInfo?.properties?.title?.[0],
|
||||
status: 'Published',
|
||||
createdTime: formatDate(new Date(postInfo.created_time).toString(), BLOG.LANG),
|
||||
lastEditedTime: formatDate(new Date(postInfo?.last_edited_time).toString(), BLOG.LANG),
|
||||
fullWidth: false,
|
||||
page_cover: getPageCover(postInfo),
|
||||
date: { start_date: formatDate(new Date(postInfo?.last_edited_time).toString(), BLOG.LANG) },
|
||||
blockMap
|
||||
}
|
||||
}
|
||||
|
||||
function getPageCover(postInfo) {
|
||||
const pageCover = postInfo.format?.page_cover
|
||||
if (pageCover) {
|
||||
if (pageCover.startsWith('/')) return 'https://www.notion.so' + pageCover
|
||||
if (pageCover.startsWith('http')) return defaultMapImageUrl(pageCover, postInfo)
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import BLOG from '@/blog.config'
|
||||
import formatDate from '../formatDate'
|
||||
import { defaultMapImageUrl } from 'react-notion-x'
|
||||
|
||||
async function getPageProperties(id, block, schema, authToken, tagOptions, siteInfo) {
|
||||
export default async function getPageProperties(id, block, schema, authToken, tagOptions, siteInfo) {
|
||||
const rawProperties = Object.entries(block?.[id]?.value?.properties || [])
|
||||
const excludeProperties = ['date', 'select', 'multi_select', 'person']
|
||||
const value = block[id]?.value
|
||||
@@ -79,6 +79,7 @@ async function getPageProperties(id, block, schema, authToken, tagOptions, siteI
|
||||
properties.createdTime = formatDate(new Date(value.created_time).toString(), BLOG.LANG)
|
||||
properties.lastEditedTime = formatDate(new Date(value?.last_edited_time).toString(), BLOG.LANG)
|
||||
properties.fullWidth = value.format?.page_full_width ?? false
|
||||
properties.pageIcon = getPageIcon(id, block) ?? ''
|
||||
properties.page_cover = getPostCover(id, block) ?? siteInfo?.pageCover
|
||||
properties.content = value.content ?? []
|
||||
properties.tagItems = properties?.tags?.map(tag => {
|
||||
@@ -97,4 +98,11 @@ async function getPageProperties(id, block, schema, authToken, tagOptions, siteI
|
||||
}
|
||||
}
|
||||
|
||||
export { getPageProperties as default }
|
||||
function getPageIcon(id, block) {
|
||||
const pageIcon = block[id].value?.format?.page_icon
|
||||
if (pageIcon) {
|
||||
if (pageIcon.startsWith('/')) return 'https://www.notion.so' + pageIcon
|
||||
if (pageIcon.startsWith('http')) return defaultMapImageUrl(pageIcon, block[id].value)
|
||||
return pageIcon
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ export async function getPostBlocks(id, from, slice) {
|
||||
* @param {*} id
|
||||
* @param {*} retryAttempts
|
||||
*/
|
||||
async function getPageWithRetry(id, from, retryAttempts = 3) {
|
||||
export async function getPageWithRetry(id, from, retryAttempts = 3) {
|
||||
if (retryAttempts && retryAttempts > 0) {
|
||||
console.log('[请求API]', `from:${from}`, `id:${id}`, retryAttempts < 3 ? `剩余重试次数:${retryAttempts}` : '')
|
||||
try {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "notion-next",
|
||||
"version": "3.5.1",
|
||||
"version": "3.6.0",
|
||||
"homepage": "https://github.com/tangly1024/NotionNext.git",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
@@ -32,12 +32,13 @@
|
||||
"localStorage": "^1.0.4",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"memory-cache": "^0.2.0",
|
||||
"mongodb": "^4.6.0",
|
||||
"mermaid": "9.2.2",
|
||||
"mongodb": "^4.6.0",
|
||||
"next": "12.1.6",
|
||||
"notion-client": "6.15.6",
|
||||
"notion-utils": "6.15.6",
|
||||
"preact": "^10.5.15",
|
||||
"prism-themes": "^1.9.0",
|
||||
"qrcode.react": "^1.0.1",
|
||||
"react": "17.0.2",
|
||||
"react-cookies": "^0.1.1",
|
||||
|
||||
@@ -7,6 +7,7 @@ import React from 'react'
|
||||
import { idToUuid } from 'notion-utils'
|
||||
import Router from 'next/router'
|
||||
import { isBrowser } from '@/lib/utils'
|
||||
import { getNotion } from '@/lib/notion/getNotion'
|
||||
|
||||
/**
|
||||
* 根据notion的slug访问页面
|
||||
@@ -26,11 +27,11 @@ const Slug = props => {
|
||||
const article = document.getElementById('container')
|
||||
if (!article) {
|
||||
router.push('/404').then(() => {
|
||||
// console.warn('找不到页面', router.asPath)
|
||||
console.warn('找不到页面', router.asPath)
|
||||
})
|
||||
}
|
||||
}
|
||||
}, 10000)
|
||||
}, 20 * 1000)
|
||||
const meta = { title: `${props?.siteInfo?.title || BLOG.TITLE} | loading`, image: siteInfo?.pageCover }
|
||||
return <ThemeComponents.LayoutSlug {...props} showArticleInfo={true} meta={meta} />
|
||||
}
|
||||
@@ -62,7 +63,7 @@ const Slug = props => {
|
||||
const meta = {
|
||||
title: `${post?.title} | ${siteInfo?.title}`,
|
||||
description: post?.summary,
|
||||
type: post.type,
|
||||
type: post?.type,
|
||||
slug: post?.slug,
|
||||
image: post?.page_cover,
|
||||
category: post?.category?.[0],
|
||||
@@ -102,13 +103,23 @@ export async function getStaticProps({ params: { slug } }) {
|
||||
props.post = props.allPages.find((p) => {
|
||||
return p.slug === fullSlug || p.id === idToUuid(fullSlug)
|
||||
})
|
||||
if (!props.post) {
|
||||
console.warn('无效地址', fullSlug)
|
||||
return { props, revalidate: 1 }
|
||||
}
|
||||
props.post.blockMap = await getPostBlocks(props.post.id, 'slug')
|
||||
|
||||
const allPosts = props.allPages.filter(page => page.type === 'Post')
|
||||
if (!props.post) {
|
||||
const pageId = slug.slice(-1)[0]
|
||||
if (pageId.length < 32) {
|
||||
return { props, revalidate: 1 }
|
||||
}
|
||||
const post = await getNotion(pageId)
|
||||
if (post) {
|
||||
props.post = post
|
||||
} else {
|
||||
return { props, revalidate: 1 }
|
||||
}
|
||||
} else {
|
||||
props.post.blockMap = await getPostBlocks(props.post.id, 'slug')
|
||||
}
|
||||
|
||||
const allPosts = props.allPages.filter(page => page.type === 'Post' && page.status === 'Published')
|
||||
const index = allPosts.indexOf(props.post)
|
||||
props.prev = allPosts.slice(index - 1, index)[0] ?? allPosts.slice(-1)[0]
|
||||
props.next = allPosts.slice(index + 1, index + 2)[0] ?? allPosts[0]
|
||||
@@ -134,7 +145,7 @@ export async function getStaticProps({ params: { slug } }) {
|
||||
function getRecommendPost(post, allPosts, count = 6) {
|
||||
let recommendPosts = []
|
||||
const postIds = []
|
||||
const currentTags = post.tags || []
|
||||
const currentTags = post?.tags || []
|
||||
for (let i = 0; i < allPosts.length; i++) {
|
||||
const p = allPosts[i]
|
||||
if (p.id === post.id || p.type.indexOf('Post') < 0) {
|
||||
|
||||
@@ -8,7 +8,10 @@ import '@/styles/notion.css' // 重写部分样式
|
||||
|
||||
// used for collection views (optional)
|
||||
// import 'rc-dropdown/assets/index.css'
|
||||
import 'prismjs/themes/prism-tomorrow.min.css'
|
||||
// import 'prismjs/themes/prism-tomorrow.min.css'
|
||||
import 'prism-themes/themes/prism-one-dark.css'
|
||||
import '@/styles/prism-mac-style.css' // 將 Prism 加入 mac 視窗樣式
|
||||
|
||||
// import 'react-notion-x/build/third-party/equation.css'
|
||||
import 'katex/dist/katex.min.css'
|
||||
|
||||
|
||||
@@ -38,6 +38,13 @@ export async function getStaticProps({ params: { keyword } }) {
|
||||
const { allPages } = props
|
||||
const allPosts = allPages.filter(page => page.type === 'Post' && page.status === 'Published')
|
||||
props.posts = await filterByMemCache(allPosts, keyword)
|
||||
props.postCount = props.posts.length
|
||||
// 处理分页
|
||||
if (BLOG.POST_LIST_STYLE === 'scroll') {
|
||||
// 滚动列表 给前端返回所有数据
|
||||
} else if (BLOG.POST_LIST_STYLE === 'page') {
|
||||
props.posts = props.posts?.slice(0, BLOG.POSTS_PER_PAGE - 1)
|
||||
}
|
||||
props.keyword = keyword
|
||||
return {
|
||||
props,
|
||||
156
pages/search/[keyword]/page/[page].js
Normal file
156
pages/search/[keyword]/page/[page].js
Normal file
@@ -0,0 +1,156 @@
|
||||
import { getGlobalNotionData } from '@/lib/notion/getNotionData'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import { getDataFromCache } from '@/lib/cache/cache_manager'
|
||||
import * as ThemeMap from '@/themes'
|
||||
import BLOG from '@/blog.config'
|
||||
|
||||
const Index = props => {
|
||||
const { keyword, siteInfo } = props
|
||||
const { locale } = useGlobal()
|
||||
const meta = {
|
||||
title: `${keyword || ''}${keyword ? ' | ' : ''}${locale.NAV.SEARCH} | ${siteInfo?.title}`,
|
||||
description: siteInfo?.title,
|
||||
image: siteInfo?.pageCover,
|
||||
slug: 'search/' + (keyword || ''),
|
||||
type: 'website'
|
||||
}
|
||||
const { theme } = useGlobal()
|
||||
const ThemeComponents = ThemeMap[theme]
|
||||
return (
|
||||
<ThemeComponents.LayoutSearch
|
||||
{...props}
|
||||
meta={meta}
|
||||
currentSearch={keyword}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 服务端搜索
|
||||
* @param {*} param0
|
||||
* @returns
|
||||
*/
|
||||
export async function getStaticProps({ params: { keyword, page } }) {
|
||||
const props = await getGlobalNotionData({
|
||||
from: 'search-props',
|
||||
pageType: ['Post']
|
||||
})
|
||||
const { allPages } = props
|
||||
const allPosts = allPages.filter(page => page.type === 'Post' && page.status === 'Published')
|
||||
props.posts = await filterByMemCache(allPosts, keyword)
|
||||
props.postCount = props.posts.length
|
||||
// 处理分页
|
||||
props.posts = props.posts.slice(BLOG.POSTS_PER_PAGE * (page - 1), BLOG.POSTS_PER_PAGE * page - 1)
|
||||
props.keyword = keyword
|
||||
props.page = page
|
||||
return {
|
||||
props,
|
||||
revalidate: 1
|
||||
}
|
||||
}
|
||||
|
||||
export async function getStaticPaths() {
|
||||
return {
|
||||
paths: [{ params: { keyword: BLOG.TITLE, page: '1' } }],
|
||||
fallback: true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将对象的指定字段拼接到字符串
|
||||
* @param sourceTextArray
|
||||
* @param targetObj
|
||||
* @param key
|
||||
* @returns {*}
|
||||
*/
|
||||
function appendText(sourceTextArray, targetObj, key) {
|
||||
if (!targetObj) {
|
||||
return sourceTextArray
|
||||
}
|
||||
const textArray = targetObj[key]
|
||||
const text = textArray ? getTextContent(textArray) : ''
|
||||
if (text && text !== 'Untitled') {
|
||||
return sourceTextArray.concat(text)
|
||||
}
|
||||
return sourceTextArray
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归获取层层嵌套的数组
|
||||
* @param {*} textArray
|
||||
* @returns
|
||||
*/
|
||||
function getTextContent(textArray) {
|
||||
if (typeof textArray === 'object' && isIterable(textArray)) {
|
||||
let result = ''
|
||||
for (const textObj of textArray) {
|
||||
result = result + getTextContent(textObj)
|
||||
}
|
||||
return result
|
||||
} else if (typeof textArray === 'string') {
|
||||
return textArray
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 对象是否可以遍历
|
||||
* @param {*} obj
|
||||
* @returns
|
||||
*/
|
||||
const isIterable = obj =>
|
||||
obj != null && typeof obj[Symbol.iterator] === 'function'
|
||||
|
||||
/**
|
||||
* 在内存缓存中进行全文索引
|
||||
* @param {*} allPosts
|
||||
* @param keyword 关键词
|
||||
* @returns
|
||||
*/
|
||||
async function filterByMemCache(allPosts, keyword) {
|
||||
const filterPosts = []
|
||||
if (keyword) {
|
||||
keyword = keyword.trim()
|
||||
}
|
||||
for (const post of allPosts) {
|
||||
const cacheKey = 'page_block_' + post.id
|
||||
const page = await getDataFromCache(cacheKey, true)
|
||||
const tagContent = post.tags && Array.isArray(post.tags) ? post.tags.join(' ') : ''
|
||||
const categoryContent = post.category && Array.isArray(post.category) ? post.category.join(' ') : ''
|
||||
const articleInfo = post.title + post.summary + tagContent + categoryContent
|
||||
let hit = articleInfo.indexOf(keyword) > -1
|
||||
let indexContent = [post.summary]
|
||||
if (page && page.block) {
|
||||
const contentIds = Object.keys(page.block)
|
||||
contentIds.forEach(id => {
|
||||
const properties = page?.block[id]?.value?.properties
|
||||
indexContent = appendText(indexContent, properties, 'title')
|
||||
indexContent = appendText(indexContent, properties, 'caption')
|
||||
})
|
||||
}
|
||||
// console.log('全文搜索缓存', cacheKey, page != null)
|
||||
post.results = []
|
||||
let hitCount = 0
|
||||
for (const i in indexContent) {
|
||||
const c = indexContent[i]
|
||||
if (!c) {
|
||||
continue
|
||||
}
|
||||
const index = c.toLowerCase().indexOf(keyword.toLowerCase())
|
||||
if (index > -1) {
|
||||
hit = true
|
||||
hitCount += 1
|
||||
post.results.push(c)
|
||||
} else {
|
||||
if ((post.results.length - 1) / hitCount < 3 || i === 0) {
|
||||
post.results.push(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hit) {
|
||||
filterPosts.push(post)
|
||||
}
|
||||
}
|
||||
return filterPosts
|
||||
}
|
||||
|
||||
export default Index
|
||||
@@ -1835,6 +1835,7 @@ pre[class*='language-'] {
|
||||
line-height: 120%;
|
||||
min-width: 0;
|
||||
font-size: 14px;
|
||||
@apply dark:text-gray-200
|
||||
}
|
||||
|
||||
.notion-collection-column-title-icon {
|
||||
@@ -1845,6 +1846,8 @@ pre[class*='language-'] {
|
||||
min-height: 14px;
|
||||
fill: var(--fg-color-2);
|
||||
margin-right: 6px;
|
||||
@apply dark:text-gray-200
|
||||
|
||||
}
|
||||
|
||||
.notion-collection-column-title-body {
|
||||
@@ -1909,4 +1912,33 @@ pre[class*='language-'] {
|
||||
|
||||
.notion-simple-table td {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.pre-mac {
|
||||
position: relative;
|
||||
margin-top: -7px;
|
||||
top: 21px;
|
||||
left: 10px;
|
||||
width: 100px;
|
||||
z-index: 99;
|
||||
}
|
||||
|
||||
.pre-mac > span {
|
||||
float: left;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.pre-mac > span:nth-child(1) {
|
||||
background: red;
|
||||
}
|
||||
|
||||
.pre-mac > span:nth-child(2) {
|
||||
background: sandybrown;
|
||||
}
|
||||
|
||||
.pre-mac > span:nth-child(3) {
|
||||
background: limegreen;
|
||||
}
|
||||
70
styles/prism-mac-style.css
Normal file
70
styles/prism-mac-style.css
Normal file
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* @author https://github.com/txs
|
||||
**/
|
||||
.code-toolbar {
|
||||
position: relative;
|
||||
box-shadow: 0 10px 30px 0 rgba(0, 0, 0, .4);
|
||||
@apply mb-8 pt-6 w-full rounded-lg bg-black
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
position: absolute;
|
||||
margin-left: 100%;
|
||||
top: 8px;
|
||||
right: 4px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-size: 0.8rem;
|
||||
line-height: 1rem;
|
||||
}
|
||||
|
||||
.toolbar-item{
|
||||
@apply whitespace-nowrap
|
||||
}
|
||||
|
||||
.toolbar-item > button {
|
||||
margin-top: -0.1rem;
|
||||
}
|
||||
|
||||
pre[class*='language-'].line-numbers {
|
||||
position: relative;
|
||||
padding: 3px; /*修改*/
|
||||
padding-left: 3.8em;
|
||||
counter-reset: linenumber;
|
||||
max-height: 400px; /*修改*/
|
||||
background: black;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.notion-code > code[class*='language-'],
|
||||
pre[class*='language-'] {
|
||||
background: black;
|
||||
}
|
||||
|
||||
.pre-mac {
|
||||
position: absolute;
|
||||
left: 15px;
|
||||
@apply z-10 top-4 left-3
|
||||
}
|
||||
|
||||
.pre-mac > span {
|
||||
border-radius: 50%;
|
||||
margin-right: 5px;
|
||||
@apply float-left
|
||||
}
|
||||
|
||||
.pre-mac > span:nth-child(1) {
|
||||
background: red;
|
||||
}
|
||||
|
||||
.pre-mac > span:nth-child(2) {
|
||||
background: sandybrown;
|
||||
}
|
||||
|
||||
.pre-mac > span:nth-child(3) {
|
||||
background: limegreen;
|
||||
}
|
||||
|
||||
.notion-code-copy{
|
||||
display: none;
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
|
||||
import BLOG from '@/blog.config'
|
||||
import { BlogListPage } from './components/BlogListPage'
|
||||
import { BlogListScroll } from './components/BlogListScroll'
|
||||
import LayoutBase from './LayoutBase'
|
||||
|
||||
export const LayoutIndex = props => {
|
||||
return (
|
||||
<LayoutBase {...props}>
|
||||
<BlogListPage {...props} page={1} />
|
||||
{BLOG.POST_LIST_STYLE === 'page' ? <BlogListPage {...props} /> : <BlogListScroll {...props} />}
|
||||
</LayoutBase>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import BLOG from '@/blog.config'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import Link from 'next/link'
|
||||
import { BlogListPage } from './components/BlogListPage'
|
||||
import { BlogListScroll } from './components/BlogListScroll'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useEffect } from 'react'
|
||||
import SearchInput from './components/SearchInput'
|
||||
import LayoutBase from './LayoutBase'
|
||||
import { isBrowser } from '@/lib/utils'
|
||||
|
||||
export const LayoutSearch = props => {
|
||||
const { keyword, posts } = props
|
||||
const { keyword } = props
|
||||
const router = useRouter()
|
||||
|
||||
useEffect(() => {
|
||||
@@ -21,22 +21,6 @@ export const LayoutSearch = props => {
|
||||
}, 100)
|
||||
}, [router.events])
|
||||
|
||||
const { locale } = useGlobal()
|
||||
|
||||
const [page, updatePage] = useState(1)
|
||||
let hasMore = false
|
||||
const postsToShow = posts
|
||||
? Object.assign(posts).slice(0, BLOG.POSTS_PER_PAGE * page)
|
||||
: []
|
||||
|
||||
if (posts) {
|
||||
const totalCount = posts.length
|
||||
hasMore = page * BLOG.POSTS_PER_PAGE < totalCount
|
||||
}
|
||||
const handleGetMore = () => {
|
||||
if (!hasMore) return
|
||||
updatePage(page + 1)
|
||||
}
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
if (keyword) {
|
||||
@@ -59,36 +43,7 @@ export const LayoutSearch = props => {
|
||||
<SearchInput {...props} />
|
||||
</div>
|
||||
|
||||
{postsToShow.map(p => (
|
||||
<article key={p.id} className="mb-12" >
|
||||
<h2 className="mb-4">
|
||||
<Link href={`/${p.slug}`}>
|
||||
<a className="text-black text-xl md:text-2xl no-underline hover:underline replace"> {p.title}</a>
|
||||
</Link>
|
||||
</h2>
|
||||
{BLOG.POST_LIST_STYLE === 'page' ? <BlogListPage {...props} /> : <BlogListScroll {...props} />}
|
||||
|
||||
<div className="mb-4 text-sm text-gray-700">
|
||||
by <a href="#" className="text-gray-700">{BLOG.AUTHOR}</a> on {p.date?.start_date || p.createdTime}
|
||||
<span className="font-bold mx-1"> | </span>
|
||||
<a href="#" className="text-gray-700">{p.category}</a>
|
||||
<span className="font-bold mx-1"> | </span>
|
||||
{/* <a href="#" className="text-gray-700">2 Comments</a> */}
|
||||
</div>
|
||||
|
||||
<p className="text-gray-700 leading-normal replace">
|
||||
{p.summary}
|
||||
</p>
|
||||
</article>
|
||||
))}
|
||||
|
||||
<div>
|
||||
<div
|
||||
onClick={handleGetMore}
|
||||
className="w-full my-4 py-4 text-center cursor-pointer "
|
||||
>
|
||||
{' '}
|
||||
{hasMore ? locale.COMMON.MORE : `${locale.COMMON.NO_MORE} 😰`}{' '}
|
||||
</div>
|
||||
</div>
|
||||
</LayoutBase>
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ export const ArticleInfo = (props) => {
|
||||
|
||||
return <section className="flex-wrap flex mt-2 text-gray-400 dark:text-gray-400 font-light leading-8">
|
||||
<div>
|
||||
{post?.type[0] !== 'Page' && <>
|
||||
{post?.type !== 'Page' && <>
|
||||
<Link href={`/category/${post.category}`} passHref>
|
||||
<a className="cursor-pointer text-md mr-2 hover:text-black dark:hover:text-white border-b dark:border-gray-500 border-dashed">
|
||||
<i className="mr-1 fas fa-folder-open" />
|
||||
@@ -20,7 +20,7 @@ export const ArticleInfo = (props) => {
|
||||
<span className='mr-2'>|</span>
|
||||
</>}
|
||||
|
||||
{post?.type[0] !== 'Page' && (<>
|
||||
{post?.type !== 'Page' && (<>
|
||||
<Link
|
||||
href={`/archive#${post?.date?.start_date?.substr(0, 7)}`}
|
||||
passHref
|
||||
|
||||
@@ -17,35 +17,37 @@ export const BlogListPage = props => {
|
||||
|
||||
return <div className="w-full md:pr-12 mb-12">
|
||||
|
||||
{posts.map(p => (
|
||||
<article key={p.id} className="mb-12" >
|
||||
<h2 className="mb-4">
|
||||
<Link href={`/${p.slug}`}>
|
||||
<a className="text-black dark:text-gray-100 text-xl md:text-2xl no-underline hover:underline"> {p.title}</a>
|
||||
</Link>
|
||||
</h2>
|
||||
<div id="container">
|
||||
{posts?.map(p => (
|
||||
<article key={p.id} className="mb-12" >
|
||||
<h2 className="mb-4">
|
||||
<Link href={`/${p.slug}`}>
|
||||
<a className="text-black dark:text-gray-100 text-xl md:text-2xl no-underline hover:underline"> {p.title}</a>
|
||||
</Link>
|
||||
</h2>
|
||||
|
||||
<div className="mb-4 text-sm text-gray-700 dark:text-gray-300">
|
||||
by <a href="#" className="text-gray-700 dark:text-gray-300">{BLOG.AUTHOR}</a> on {p.date?.start_date || p.createdTime}
|
||||
<span className="font-bold mx-1"> | </span>
|
||||
<a href="#" className="text-gray-700 dark:text-gray-300">{p.category}</a>
|
||||
{/* <span className="font-bold mx-1"> | </span> */}
|
||||
{/* <a href="#" className="text-gray-700">2 Comments</a> */}
|
||||
</div>
|
||||
<div className="mb-4 text-sm text-gray-700 dark:text-gray-300">
|
||||
by <a href="#" className="text-gray-700 dark:text-gray-300">{BLOG.AUTHOR}</a> on {p.date?.start_date || p.createdTime}
|
||||
<span className="font-bold mx-1"> | </span>
|
||||
<a href="#" className="text-gray-700 dark:text-gray-300">{p.category}</a>
|
||||
{/* <span className="font-bold mx-1"> | </span> */}
|
||||
{/* <a href="#" className="text-gray-700">2 Comments</a> */}
|
||||
</div>
|
||||
|
||||
<p className="text-gray-700 dark:text-gray-400 leading-normal">
|
||||
{p.summary}
|
||||
</p>
|
||||
{/* 搜索结果 */}
|
||||
{p.results && (
|
||||
<p className="mt-4 text-gray-700 dark:text-gray-300 text-sm font-light leading-7">
|
||||
{p.results.map(r => (
|
||||
<span key={r}>{r}</span>
|
||||
))}
|
||||
<p className="text-gray-700 dark:text-gray-400 leading-normal">
|
||||
{p.summary}
|
||||
</p>
|
||||
)}
|
||||
</article>
|
||||
))}
|
||||
{/* 搜索结果 */}
|
||||
{p.results && (
|
||||
<p className="mt-4 text-gray-700 dark:text-gray-300 text-sm font-light leading-7">
|
||||
{p.results.map(r => (
|
||||
<span key={r}>{r}</span>
|
||||
))}
|
||||
</p>
|
||||
)}
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between text-xs">
|
||||
<Link href={{ pathname: currentPage - 1 === 1 ? `${BLOG.SUB_PATH || pagePrefix}` : `${pagePrefix}/page/${currentPage - 1}`, query: router.query.s ? { s: router.query.s } : {} }}>
|
||||
|
||||
@@ -43,7 +43,7 @@ export const BlogListScroll = props => {
|
||||
}
|
||||
})
|
||||
|
||||
return <div className="w-full md:pr-12 mb-12" ref={targetRef}>
|
||||
return <div id="container" className="w-full md:pr-12 mb-12" ref={targetRef}>
|
||||
{postsToShow.map(p => (
|
||||
<article key={p.id} className="mb-12" >
|
||||
<h2 className="mb-4">
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import LayoutBase from './LayoutBase'
|
||||
import BLOG from '@/blog.config'
|
||||
import BlogListPage from './components/BlogListPage'
|
||||
import BlogListScroll from './components/BlogListScroll'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useEffect } from 'react'
|
||||
import { isBrowser } from '@/lib/utils'
|
||||
@@ -18,6 +20,6 @@ export const LayoutSearch = (props) => {
|
||||
}, 100)
|
||||
})
|
||||
return <LayoutBase {...props} currentSearch={currentSearch}>
|
||||
<BlogListPage {...props} />
|
||||
{BLOG.POST_LIST_STYLE === 'page' ? <BlogListPage {...props} /> : <BlogListScroll {...props}/>}
|
||||
</LayoutBase>
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import BLOG from '@/blog.config'
|
||||
import BlogListPage from './components/BlogListPage'
|
||||
import BlogListScroll from './components/BlogListScroll'
|
||||
import LayoutBase from './LayoutBase'
|
||||
|
||||
export const LayoutTag = (props) => {
|
||||
return <LayoutBase {...props}>
|
||||
<BlogListPage {...props} />
|
||||
{BLOG.POST_LIST_STYLE === 'page' ? <BlogListPage {...props} /> : <BlogListScroll {...props}/>}
|
||||
</LayoutBase>
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ export default function ArticleDetail(props) {
|
||||
<span className='mr-2'>|</span>
|
||||
</>)}
|
||||
|
||||
{post?.type[0] !== 'Page' && (<>
|
||||
{post?.type !== 'Page' && (<>
|
||||
<Link
|
||||
href={`/archive#${post?.date?.start_date?.substr(0, 7)}`}
|
||||
passHref
|
||||
|
||||
@@ -39,9 +39,9 @@ const BlogListPage = ({ page = 1, posts = [], postCount }) => {
|
||||
return <BlogPostListEmpty />
|
||||
} else {
|
||||
return (
|
||||
<div id="container">
|
||||
<div>
|
||||
{/* 文章列表 */}
|
||||
<div style={{ columnCount: colCount }}>
|
||||
<div id="container" style={{ columnCount: colCount }}>
|
||||
{posts?.map(post => (
|
||||
<div key={post.id} className='justify-center flex' style={{ breakInside: 'avoid' }}>
|
||||
<BlogCard key={post.id} post={post} />
|
||||
|
||||
@@ -6,8 +6,8 @@ import BLOG from '@/blog.config'
|
||||
export const LayoutCategory = props => {
|
||||
const { category } = props
|
||||
return <LayoutBase {...props}>
|
||||
<div className="cursor-pointer px-5 py-1 mb-2 font-light hover:underline hover:text-indigo-700 dark:hover:text-indigo-400 transform text-center dark:text-white">
|
||||
<i className="mr-1 far fa-folder" />
|
||||
<div className="cursor-pointer text-lg px-5 py-1 mb-2 font-light hover:text-indigo-700 dark:hover:text-indigo-400 transform dark:text-white">
|
||||
<i className="mr-1 far fa-folder-open" />
|
||||
{category}
|
||||
</div>
|
||||
{BLOG.POST_LIST_STYLE === 'page' ? <BlogPostListPage {...props} /> : <BlogPostListScroll {...props} />}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { useRouter } from 'next/router'
|
||||
import { useEffect, useRef } from 'react'
|
||||
import BLOG from '@/blog.config'
|
||||
import BlogPostListScroll from './components/BlogPostListScroll'
|
||||
import BlogPostListPage from './components/BlogPostListPage'
|
||||
import LayoutBase from './LayoutBase'
|
||||
import SearchInput from './components/SearchInput'
|
||||
@@ -25,8 +27,7 @@ export const LayoutSearch = props => {
|
||||
if (container && container.innerHTML) {
|
||||
const re = new RegExp(`${currentSearch}`, 'gim')
|
||||
container.innerHTML = container.innerHTML.replace(
|
||||
re,
|
||||
`<span class='text-red-500 border-b border-dashed'>${currentSearch}</span>`
|
||||
re, `<span class='text-red-500 border-b border-dashed'>${currentSearch}</span>`
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -34,56 +35,62 @@ export const LayoutSearch = props => {
|
||||
}, 100)
|
||||
})
|
||||
return (
|
||||
<LayoutBase {...props} currentSearch={currentSearch}>
|
||||
<div className="my-6 px-2">
|
||||
<SearchInput cRef={cRef} {...props} />
|
||||
{/* 分类 */}
|
||||
<Card className="w-full mt-4">
|
||||
<div className="dark:text-gray-200 mb-5 mx-3">
|
||||
<i className="mr-4 fas fa-th" />
|
||||
{locale.COMMON.CATEGORY}:
|
||||
</div>
|
||||
<div id="category-list" className="duration-200 flex flex-wrap mx-8">
|
||||
{categories?.map(category => {
|
||||
return (
|
||||
<Link
|
||||
key={category.name}
|
||||
href={`/category/${category.name}`}
|
||||
passHref
|
||||
>
|
||||
<div
|
||||
className={
|
||||
' duration-300 dark:hover:text-white rounded-lg px-5 cursor-pointer py-2 hover:bg-indigo-400 hover:text-white'
|
||||
}
|
||||
>
|
||||
<i className="mr-4 fas fa-folder" />
|
||||
{category.name}({category.count})
|
||||
</div>
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</Card>
|
||||
{/* 标签 */}
|
||||
<Card className="w-full mt-4">
|
||||
<div className="dark:text-gray-200 mb-5 ml-4">
|
||||
<i className="mr-4 fas fa-tag" />
|
||||
{locale.COMMON.TAGS}:
|
||||
</div>
|
||||
<div id="tags-list" className="duration-200 flex flex-wrap ml-8">
|
||||
{tags?.map(tag => {
|
||||
return (
|
||||
<div key={tag.name} className="p-2">
|
||||
<TagItemMini key={tag.name} tag={tag} />
|
||||
<LayoutBase {...props} currentSearch={currentSearch}>
|
||||
{!currentSearch && <>
|
||||
<div className="my-6 px-2">
|
||||
<SearchInput cRef={cRef} {...props} />
|
||||
{/* 分类 */}
|
||||
<Card className="w-full mt-4">
|
||||
<div className="dark:text-gray-200 mb-5 mx-3">
|
||||
<i className="mr-4 fas fa-th" />
|
||||
{locale.COMMON.CATEGORY}:
|
||||
</div>
|
||||
<div id="category-list" className="duration-200 flex flex-wrap mx-8">
|
||||
{categories?.map(category => {
|
||||
return (
|
||||
<Link
|
||||
key={category.name}
|
||||
href={`/category/${category.name}`}
|
||||
passHref
|
||||
>
|
||||
<div
|
||||
className={
|
||||
' duration-300 dark:hover:text-white rounded-lg px-5 cursor-pointer py-2 hover:bg-indigo-400 hover:text-white'
|
||||
}
|
||||
>
|
||||
<i className="mr-4 fas fa-folder" />
|
||||
{category.name}({category.count})
|
||||
</div>
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</Card>
|
||||
{/* 标签 */}
|
||||
<Card className="w-full mt-4">
|
||||
<div className="dark:text-gray-200 mb-5 ml-4">
|
||||
<i className="mr-4 fas fa-tag" />
|
||||
{locale.COMMON.TAGS}:
|
||||
</div>
|
||||
<div id="tags-list" className="duration-200 flex flex-wrap ml-8">
|
||||
{tags?.map(tag => {
|
||||
return (
|
||||
<div key={tag.name} className="p-2">
|
||||
<TagItemMini key={tag.name} tag={tag} />
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
<div id="container">
|
||||
<BlogPostListPage {...props} />
|
||||
</div>
|
||||
</LayoutBase>
|
||||
</>}
|
||||
|
||||
{currentSearch && <>
|
||||
<div id="container">
|
||||
{BLOG.POST_LIST_STYLE === 'page' ? <BlogPostListPage {...props} /> : <BlogPostListScroll {...props} />}
|
||||
</div>
|
||||
</>}
|
||||
|
||||
</LayoutBase>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,18 +2,23 @@ import BLOG from '@/blog.config'
|
||||
import BlogPostListScroll from './components/BlogPostListScroll'
|
||||
import BlogPostListPage from './components/BlogPostListPage'
|
||||
import LayoutBase from './LayoutBase'
|
||||
import TagItemMini from '../next/components/TagItemMini'
|
||||
import React from 'react'
|
||||
import Link from 'next/link'
|
||||
|
||||
export const LayoutTag = (props) => {
|
||||
const currentTag = props.tags.find((t) => {
|
||||
const tag = props.tags.find((t) => {
|
||||
return t.name === props.tag
|
||||
})
|
||||
|
||||
return <LayoutBase {...props}>
|
||||
{currentTag && (
|
||||
<div className="cursor-pointer px-5 py-1 mb-2 font-light hover:underline hover:text-indigo-700 dark:hover:text-indigo-400 transform text-center dark:text-white">
|
||||
<TagItemMini tag={currentTag} />
|
||||
{tag && (
|
||||
<div className="cursor-pointer px-3 py-2 mb-2 font-light hover:text-indigo-700 dark:hover:text-indigo-400 transform dark:text-white">
|
||||
<Link key={tag} href={`/tag/${encodeURIComponent(tag.name)}`} passHref>
|
||||
<a className={`cursor-pointer inline-block rounded duration-200
|
||||
mr-2 py-0.5 px-1 text-xl whitespace-nowrap ` }>
|
||||
<div className='font-light dark:text-gray-400 dark:hover:text-white'> #{tag.name + (tag.count ? `(${tag.count})` : '')} </div>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
{BLOG.POST_LIST_STYLE === 'page' ? <BlogPostListPage {...props} /> : <BlogPostListScroll {...props} />}
|
||||
|
||||
@@ -40,7 +40,7 @@ export default function HeaderArticle({ post, siteInfo }) {
|
||||
</>}
|
||||
</div>
|
||||
<div className='flex justify-center'>
|
||||
{post?.type[0] !== 'Page' && (
|
||||
{post?.type !== 'Page' && (
|
||||
<>
|
||||
<Link
|
||||
href={`/archive#${post?.date?.start_date?.substr(0, 7)}`}
|
||||
|
||||
@@ -9,6 +9,9 @@ import Link from 'next/link'
|
||||
*/
|
||||
const NavButtonGroup = (props) => {
|
||||
const { categories } = props
|
||||
if (!categories || categories.length === 0) {
|
||||
return <></>
|
||||
}
|
||||
|
||||
return <nav id='home-nav-button' className={'md:h-52 md:mt-6 xl:mt-32 px-5 py-2 mt-8 flex flex-wrap md:max-w-5xl space-y-2 md:space-y-0 md:flex justify-center max-h-80 overflow-auto'}>
|
||||
{categories.map(category => {
|
||||
|
||||
@@ -18,6 +18,9 @@ const SocialButton = () => {
|
||||
{BLOG.CONTACT_TELEGRAM && <a target='_blank' rel='noreferrer' href={BLOG.CONTACT_TELEGRAM} title={'telegram'} >
|
||||
<i className='transform hover:scale-125 duration-150 fab fa-telegram dark:hover:text-indigo-400 hover:text-indigo-600'/>
|
||||
</a>}
|
||||
{BLOG.CONTACT_LINKEDIN && <a target='_blank' rel='noreferrer' href={BLOG.CONTACT_LINKEDIN} title={'linkIn'} >
|
||||
<i className='transform hover:scale-125 duration-150 fab fa-linkedin dark:hover:text-indigo-400 hover:text-indigo-600'/>
|
||||
</a>}
|
||||
{BLOG.CONTACT_WEIBO && <a target='_blank' rel='noreferrer' title={'weibo'} href={BLOG.CONTACT_WEIBO} >
|
||||
<i className='transform hover:scale-125 duration-150 fab fa-weibo dark:hover:text-indigo-400 hover:text-indigo-600'/>
|
||||
</a>}
|
||||
|
||||
@@ -3,9 +3,11 @@ import SearchInput from './components/SearchInput'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import TagGroups from './components/TagGroups'
|
||||
import CategoryGroup from './components/CategoryGroup'
|
||||
import BlogPostListScroll from './components/BlogPostListScroll'
|
||||
import { useEffect } from 'react'
|
||||
import { isBrowser } from '@/lib/utils'
|
||||
import BLOG from '@/blog.config'
|
||||
import BlogPostListScroll from './components/BlogPostListScroll'
|
||||
import BlogPostListPage from './components/BlogPostListPage'
|
||||
|
||||
export const LayoutSearch = (props) => {
|
||||
const { locale } = useGlobal()
|
||||
@@ -27,8 +29,8 @@ export const LayoutSearch = (props) => {
|
||||
<TagGroups {...props} />
|
||||
<CategoryGroup {...props} />
|
||||
</div>
|
||||
<div id='container'>
|
||||
<BlogPostListScroll {...props} />
|
||||
<div>
|
||||
{BLOG.POST_LIST_STYLE === 'page' ? <BlogPostListPage {...props} /> : <BlogPostListScroll {...props} />}
|
||||
</div>
|
||||
</LayoutBase>
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import LayoutBase from './LayoutBase'
|
||||
import BlogPostListScroll from './components/BlogPostListScroll'
|
||||
import BLOG from '@/blog.config'
|
||||
import BlogPostListScroll from './components/BlogPostListScroll'
|
||||
import BlogPostListPage from './components/BlogPostListPage'
|
||||
|
||||
export const LayoutTag = (props) => {
|
||||
|
||||
@@ -18,11 +18,13 @@ const BlogPostListPage = ({ page = 1, posts = [], postCount }) => {
|
||||
return <BlogPostListEmpty />
|
||||
} else {
|
||||
return (
|
||||
<div id="container" className='w-full justify-center'>
|
||||
<div className='w-full justify-center'>
|
||||
<div id='container'>
|
||||
{/* 文章列表 */}
|
||||
{posts.map(post => (
|
||||
<BlogPostCard key={post.id} post={post} />
|
||||
))}
|
||||
</div>
|
||||
<PaginationSimple page={page} totalPage={totalPage} />
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -17,6 +17,9 @@ const SocialButton = () => {
|
||||
{BLOG.CONTACT_TELEGRAM && <a target='_blank' rel='noreferrer' href={BLOG.CONTACT_TELEGRAM} title={'telegram'} >
|
||||
<i className='fab fa-telegram transform hover:scale-125 duration-150 hover:text-green-600'/>
|
||||
</a>}
|
||||
{BLOG.CONTACT_LINKEDIN && <a target='_blank' rel='noreferrer' href={BLOG.CONTACT_LINKEDIN} title={'linkedIn'} >
|
||||
<i className='transform hover:scale-125 duration-150 fab fa-linkedin dark:hover:text-indigo-400 hover:text-indigo-600'/>
|
||||
</a>}
|
||||
{BLOG.CONTACT_WEIBO && <a target='_blank' rel='noreferrer' title={'weibo'} href={BLOG.CONTACT_WEIBO} >
|
||||
<i className='fab fa-weibo transform hover:scale-125 duration-150 hover:text-green-600'/>
|
||||
</a>}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import LayoutBase from './LayoutBase'
|
||||
import StickyBar from './components/StickyBar'
|
||||
import BlogPostListScroll from './components/BlogPostListScroll'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import { isBrowser } from '@/lib/utils'
|
||||
import BlogPostListScroll from './components/BlogPostListScroll'
|
||||
import BlogPostListPage from './components/BlogPostListPage'
|
||||
import BLOG from '@/blog.config'
|
||||
|
||||
export const LayoutSearch = (props) => {
|
||||
const { locale } = useGlobal()
|
||||
@@ -23,8 +25,10 @@ export const LayoutSearch = (props) => {
|
||||
</div>
|
||||
</StickyBar>
|
||||
<div className="md:mt-5">
|
||||
<BlogPostListScroll {...props} showSummary={true} />
|
||||
</div>
|
||||
{BLOG.POST_LIST_STYLE !== 'page'
|
||||
? <BlogPostListScroll {...props} showSummary={true} />
|
||||
: <BlogPostListPage {...props} />
|
||||
} </div>
|
||||
</LayoutBase>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -9,14 +9,14 @@ export const LayoutTag = (props) => {
|
||||
const { tags, tag } = props
|
||||
|
||||
return <LayoutBase currentTag={tag} {...props}>
|
||||
<StickyBar>
|
||||
<TagList tags={tags} currentTag={tag}/>
|
||||
</StickyBar>
|
||||
<div className='md:mt-8'>
|
||||
{BLOG.POST_LIST_STYLE !== 'page'
|
||||
? <BlogPostListScroll {...props} showSummary={true} />
|
||||
: <BlogPostListPage {...props} />
|
||||
}
|
||||
</div>
|
||||
</LayoutBase>
|
||||
<StickyBar>
|
||||
<TagList tags={tags} currentTag={tag} />
|
||||
</StickyBar>
|
||||
<div className='md:mt-8'>
|
||||
{BLOG.POST_LIST_STYLE !== 'page'
|
||||
? <BlogPostListScroll {...props} showSummary={true} />
|
||||
: <BlogPostListPage {...props} />
|
||||
}
|
||||
</div>
|
||||
</LayoutBase>
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ import { useRouter } from 'next/router'
|
||||
import ArticleCopyright from './ArticleCopyright'
|
||||
import WordCount from './WordCount'
|
||||
import NotionPage from '@/components/NotionPage'
|
||||
import CONFIG_NEXT from '../config_next'
|
||||
import NotionIcon from '@/components/NotionIcon'
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -29,7 +31,7 @@ export default function ArticleDetail(props) {
|
||||
>
|
||||
|
||||
{showArticleInfo && <header className='animate__slideInDown animate__animated'>
|
||||
{post?.type && !post?.type.includes('Page') && post?.page_cover && (
|
||||
{CONFIG_NEXT.POST_HEADER_IMAGE_VISIBLE && post?.type && !post?.type !== 'Page' && post?.page_cover && (
|
||||
<div className="w-full relative md:flex-shrink-0 overflow-hidden">
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img alt={post.title} src={post?.page_cover} className='object-center w-full' />
|
||||
@@ -38,7 +40,7 @@ export default function ArticleDetail(props) {
|
||||
|
||||
{/* 文章Title */}
|
||||
<div className="font-bold text-3xl text-black dark:text-white font-serif pt-10">
|
||||
{post.title}
|
||||
<NotionIcon icon={post.pageIcon}/>{post.title}
|
||||
</div>
|
||||
|
||||
<section className="flex-wrap flex mt-2 text-gray-400 dark:text-gray-400 font-light leading-8">
|
||||
@@ -51,7 +53,7 @@ export default function ArticleDetail(props) {
|
||||
</Link>
|
||||
<span className='mr-2'>|</span>
|
||||
</>}
|
||||
{post?.type[0] !== 'Page' && (<>
|
||||
{post?.type !== 'Page' && (<>
|
||||
<Link
|
||||
href={`/archive#${post?.date?.start_date?.substr(0, 7)}`}
|
||||
passHref
|
||||
|
||||
@@ -7,6 +7,7 @@ import Card from './Card'
|
||||
import TagItemMini from './TagItemMini'
|
||||
import CONFIG_NEXT from '../config_next'
|
||||
import NotionPage from '@/components/NotionPage'
|
||||
import NotionIcon from '@/components/NotionIcon'
|
||||
|
||||
const BlogPostCard = ({ post, showSummary }) => {
|
||||
const { locale } = useGlobal()
|
||||
@@ -23,7 +24,7 @@ const BlogPostCard = ({ post, showSummary }) => {
|
||||
className={`cursor-pointer hover:underline text-3xl ${showPreview ? 'text-center' : ''
|
||||
} leading-tight text-gray-700 dark:text-gray-100 hover:text-blue-500 dark:hover:text-blue-400`}
|
||||
>
|
||||
{post.title}
|
||||
<NotionIcon icon={post.pageIcon} /> {post.title}
|
||||
</a>
|
||||
</Link>
|
||||
|
||||
|
||||
@@ -18,9 +18,9 @@ const BlogPostListPage = ({ page = 1, posts = [], postCount }) => {
|
||||
return <BlogPostListEmpty />
|
||||
} else {
|
||||
return (
|
||||
<div id="container">
|
||||
<div>
|
||||
{/* 文章列表 */}
|
||||
<div className="flex flex-wrap lg:space-y-4 space-y-1">
|
||||
<div id="container" className="flex flex-wrap lg:space-y-4 space-y-1">
|
||||
{posts.map(post => (
|
||||
<BlogPostCard key={post.id} post={post} />
|
||||
))}
|
||||
|
||||
@@ -52,10 +52,10 @@ const BlogPostListScroll = ({ posts = [], currentSearch, showSummary = CONFIG_NE
|
||||
if (!postsToShow || postsToShow.length === 0) {
|
||||
return <BlogPostListEmpty currentSearch={currentSearch} />
|
||||
} else {
|
||||
return <div id='container' ref={targetRef}>
|
||||
return <div ref={targetRef}>
|
||||
|
||||
{/* 文章列表 */}
|
||||
<div className='flex flex-wrap space-y-1 lg:space-y-4'>
|
||||
<div id='container' className='flex flex-wrap space-y-1 lg:space-y-4'>
|
||||
{postsToShow.map(post => (
|
||||
<BlogPostCard key={post.id} post={post} showSummary={showSummary} />
|
||||
))}
|
||||
|
||||
@@ -99,7 +99,7 @@ export default function Header(props) {
|
||||
`linear-gradient(rgba(0, 0, 0, 0.8), rgba(0,0,0,0.2), rgba(0, 0, 0, 0.8) ),url("${siteInfo?.pageCover}")`
|
||||
}}
|
||||
>
|
||||
<div className="absolute flex h-full items-center lg:-mt-14 justify-center w-full text-4xl md:text-7xl text-white">
|
||||
<div className="absolute flex h-full items-center lg:-mt-14 justify-center w-full text-2xl md:text-4xl text-white">
|
||||
<div id='typed' className='flex text-center font-serif' />
|
||||
</div>
|
||||
<div
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import BLOG from '@/blog.config'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
@@ -13,79 +12,78 @@ const PaginationNumber = ({ page, totalPage }) => {
|
||||
const router = useRouter()
|
||||
const currentPage = +page
|
||||
const showNext = page !== totalPage
|
||||
const pages = generatePages(page, currentPage, totalPage)
|
||||
const pagePrefix = router.asPath.replace(/\/page\/[1-9]\d*/, '').replace(/\/$/, '')
|
||||
const pages = generatePages(pagePrefix, page, currentPage, totalPage)
|
||||
|
||||
return (
|
||||
<div className="my-5 flex justify-center items-end font-medium text-black hover:shadow-xl duration-500 bg-white dark:bg-gray-700 dark:text-gray-300 py-3 shadow space-x-2">
|
||||
{/* 上一页 */}
|
||||
<Link
|
||||
href={{
|
||||
pathname:
|
||||
currentPage - 1 === 1
|
||||
? `${BLOG.SUB_PATH || '/'}`
|
||||
: `/page/${currentPage - 1}`,
|
||||
query: router.query.s ? { s: router.query.s } : {}
|
||||
}}
|
||||
passHref
|
||||
>
|
||||
<div
|
||||
rel="prev"
|
||||
className={`${
|
||||
currentPage === 1 ? 'invisible' : 'block'
|
||||
} border-white dark:border-gray-700 hover:border-gray-400 dark:hover:border-gray-400 w-6 text-center cursor-pointer duration-200 hover:font-bold`}
|
||||
>
|
||||
<i className="fas fa-angle-left" />
|
||||
</div>
|
||||
</Link>
|
||||
<div className="my-5 flex justify-center items-end font-medium text-black hover:shadow-xl duration-500 bg-white dark:bg-gray-700 dark:text-gray-300 py-3 shadow space-x-2">
|
||||
{/* 上一页 */}
|
||||
<Link
|
||||
href={{
|
||||
pathname:
|
||||
currentPage - 1 === 1
|
||||
? `${pagePrefix}}`
|
||||
: `${pagePrefix}/page/${currentPage - 1}`,
|
||||
query: router.query.s ? { s: router.query.s } : {}
|
||||
}}
|
||||
passHref
|
||||
>
|
||||
<div
|
||||
rel="prev"
|
||||
className={`${currentPage === 1 ? 'invisible' : 'block'
|
||||
} border-white dark:border-gray-700 hover:border-gray-400 dark:hover:border-gray-400 w-6 text-center cursor-pointer duration-200 hover:font-bold`}
|
||||
>
|
||||
<i className="fas fa-angle-left" />
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
{pages}
|
||||
{pages}
|
||||
|
||||
{/* 下一页 */}
|
||||
<Link
|
||||
href={{
|
||||
pathname: `/page/${currentPage + 1}`,
|
||||
query: router.query.s ? { s: router.query.s } : {}
|
||||
}}
|
||||
passHref
|
||||
>
|
||||
<div
|
||||
rel="next"
|
||||
className={`${
|
||||
+showNext ? 'block' : 'invisible'
|
||||
} border-t-2 border-white dark:border-gray-700 hover:border-gray-400 dark:hover:border-gray-400 w-6 text-center cursor-pointer duration-500 hover:font-bold`}
|
||||
>
|
||||
<i className="fas fa-angle-right" />
|
||||
{/* 下一页 */}
|
||||
<Link
|
||||
href={{
|
||||
pathname: `${pagePrefix}/page/${currentPage + 1}`,
|
||||
query: router.query.s ? { s: router.query.s } : {}
|
||||
}}
|
||||
passHref
|
||||
>
|
||||
<div
|
||||
rel="next"
|
||||
className={`${+showNext ? 'block' : 'invisible'
|
||||
} border-t-2 border-white dark:border-gray-700 hover:border-gray-400 dark:hover:border-gray-400 w-6 text-center cursor-pointer duration-500 hover:font-bold`}
|
||||
>
|
||||
<i className="fas fa-angle-right" />
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function getPageElement(page, currentPage) {
|
||||
function getPageElement(pagePrefix, page, currentPage) {
|
||||
console.log(pagePrefix, page, currentPage)
|
||||
|
||||
return (
|
||||
<Link href={page === 1 ? '/' : `/page/${page}`} key={page} passHref>
|
||||
<a
|
||||
className={
|
||||
(page + '' === currentPage + ''
|
||||
? 'font-bold bg-gray-500 dark:bg-gray-400 text-white '
|
||||
: 'border-t-2 duration-500 border-white hover:border-gray-400 ') +
|
||||
' border-white dark:border-gray-700 dark:hover:border-gray-400 cursor-pointer w-6 text-center font-light hover:font-bold'
|
||||
}
|
||||
>
|
||||
{page}
|
||||
</a>
|
||||
</Link>
|
||||
<Link href={page === 1 ? `${pagePrefix}` : `${pagePrefix}/page/${page}`} key={page} passHref>
|
||||
<a className={
|
||||
(page + '' === currentPage + ''
|
||||
? 'font-bold bg-gray-500 dark:bg-gray-400 text-white '
|
||||
: 'border-t-2 duration-500 border-white hover:border-gray-400 ') +
|
||||
' border-white dark:border-gray-700 dark:hover:border-gray-400 cursor-pointer w-6 text-center font-light hover:font-bold'
|
||||
} >
|
||||
{page}
|
||||
</a>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
function generatePages(page, currentPage, totalPage) {
|
||||
function generatePages(pagePrefix, page, currentPage, totalPage) {
|
||||
const pages = []
|
||||
const groupCount = 7 // 最多显示页签数
|
||||
if (totalPage <= groupCount) {
|
||||
for (let i = 1; i <= totalPage; i++) {
|
||||
pages.push(getPageElement(i, page))
|
||||
pages.push(getPageElement(pagePrefix, i, page))
|
||||
}
|
||||
} else {
|
||||
pages.push(getPageElement(1, page))
|
||||
pages.push(getPageElement(pagePrefix, 1, page))
|
||||
const dynamicGroupCount = groupCount - 2
|
||||
let startPage = currentPage - 2
|
||||
if (startPage <= 1) {
|
||||
@@ -100,7 +98,7 @@ function generatePages(page, currentPage, totalPage) {
|
||||
|
||||
for (let i = 0; i < dynamicGroupCount; i++) {
|
||||
if (startPage + i < totalPage) {
|
||||
pages.push(getPageElement(startPage + i, page))
|
||||
pages.push(getPageElement(pagePrefix, startPage + i, page))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,7 +106,7 @@ function generatePages(page, currentPage, totalPage) {
|
||||
pages.push(<div key={-2}>... </div>)
|
||||
}
|
||||
|
||||
pages.push(getPageElement(totalPage, page))
|
||||
pages.push(getPageElement(pagePrefix, totalPage, page))
|
||||
}
|
||||
return pages
|
||||
}
|
||||
|
||||
@@ -18,6 +18,9 @@ const SocialButton = () => {
|
||||
{BLOG.CONTACT_TELEGRAM && <a target='_blank' rel='noreferrer' href={BLOG.CONTACT_TELEGRAM} title={'telegram'} >
|
||||
<i className='fab fa-telegram transform hover:scale-125 duration-150'/>
|
||||
</a>}
|
||||
{BLOG.CONTACT_LINKEDIN && <a target='_blank' rel='noreferrer' href={BLOG.CONTACT_LINKEDIN} title={'linkedIn'} >
|
||||
<i className='transform hover:scale-125 duration-150 fab fa-linkedin dark:hover:text-indigo-400 hover:text-indigo-600'/>
|
||||
</a>}
|
||||
{BLOG.CONTACT_WEIBO && <a target='_blank' rel='noreferrer' title={'weibo'} href={BLOG.CONTACT_WEIBO} >
|
||||
<i className='fab fa-weibo transform hover:scale-125 duration-150'/>
|
||||
</a>}
|
||||
|
||||
@@ -9,6 +9,8 @@ const CONFIG_NEXT = {
|
||||
POST_LIST_PREVIEW: true, // 显示文章预览
|
||||
POST_LIST_SUMMARY: false, // 显示用户自定义摘要,有预览时优先只展示预览
|
||||
|
||||
POST_HEADER_IMAGE_VISIBLE: false, // 文章详情页是否显示封面图
|
||||
|
||||
// 右侧组件
|
||||
RIGHT_BAR: true, // 是否显示右侧栏
|
||||
RIGHT_LATEST_POSTS: true, // 右侧栏最新文章
|
||||
|
||||
Reference in New Issue
Block a user