Merge branch 'develop' of https://github.com/tangly1024/NotionNext into develop

This commit is contained in:
tangly
2022-11-18 23:28:20 +08:00
41 changed files with 2409 additions and 324 deletions

20
components/NotionIcon.js Normal file
View 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

View File

@@ -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,17 @@ 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 'prismjs/plugins/line-numbers/prism-line-numbers'
import 'prismjs/plugins/line-numbers/prism-line-numbers.css'
// 化学方程式
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 +77,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))
@@ -93,18 +96,17 @@ const NotionPage = ({ post }) => {
}
}
// 相册图片不允许点击
// 相册图片点击不跳转
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}
@@ -120,72 +122,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

117
components/PrismMac.js Normal file
View File

@@ -0,0 +1,117 @@
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'
// mermaid图
import mermaid from 'mermaid'
import { useRouter } from 'next/router'
/**
* @author https://github.com/txs/
* @returns
*/
const PrismMac = () => {
const router = useRouter()
React.useEffect(() => {
// addWatch4Dom()
renderPrismMac()
router.events.on('routeChangeComplete', renderPrismMac)
return () => {
router.events.off('routeChangeComplete', renderPrismMac)
}
}, [])
return <></>
}
function renderPrismMac() {
const container = document?.getElementById('container-inner')
const codeBlocks = container?.getElementsByTagName('pre')
Array.from(codeBlocks).forEach(item => {
// Add line numbers
if (!item.classList.contains('line-numbers')) {
item.classList.add('line-numbers')
item.style.whiteSpace = 'pre-wrap'
}
})
const codeToolBars = container?.getElementsByClassName('code-toolbar')
Array.from(codeToolBars).forEach(item => {
// Add pre-mac element for Mac Style UI
const findPreMac = item.getElementsByClassName('pre-mac')
if (findPreMac.length === 0) {
const preMac = document.createElement('div')
preMac.classList.add('pre-mac')
preMac.innerHTML = '<span></span><span></span><span></span>'
item?.appendChild(preMac, item)
}
})
// 支持 Mermaid
const mermaids = document.querySelectorAll('.notion-code .language-mermaid')
for (const e of mermaids) {
const chart = e.innerText
e.parentElement.parentElement.classList.remove('code-toolbar')
e.parentElement.parentElement.innerHTML = `<div class="mermaid">${chart}</div>`
mermaid.contentLoaded()
}
try {
Prism.highlightAll()
} catch (err) {
console.log('代码渲染', err)
}
}
/**
* 监听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.target)
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', observer)
// 以上述配置开始观察目标节点
if (targetNode) {
observer.observe(targetNode, config)
}
// observer.disconnect()
}
export default PrismMac

1696
lib/mhchem.js Normal file

File diff suppressed because it is too large Load Diff

37
lib/notion/getNotion.js Normal file
View 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)
}
}

View File

@@ -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
}
}

View File

@@ -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 {

View File

@@ -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",

View File

@@ -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,19 @@ 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 post = await getNotion(slug.slice(-1)[0])
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 +141,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) {

View File

@@ -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'

View File

@@ -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,

View 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

View File

@@ -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;
}

View 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: 10px;
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;
}

View File

@@ -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>
)
}

View File

@@ -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>
}

View File

@@ -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

View File

@@ -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 } : {} }}>

View File

@@ -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">

View File

@@ -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>
}

View File

@@ -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>
}

View File

@@ -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

View File

@@ -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} />

View File

@@ -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} />}

View File

@@ -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>
)
}

View File

@@ -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} />}

View File

@@ -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)}`}

View File

@@ -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>}

View File

@@ -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>
}

View File

@@ -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) => {

View File

@@ -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>
)

View File

@@ -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>}

View File

@@ -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>
)
}

View File

@@ -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>
}

View File

@@ -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

View File

@@ -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>

View File

@@ -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} />
))}

View File

@@ -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} />
))}

View File

@@ -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
}

View File

@@ -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>}

View File

@@ -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, // 右侧栏最新文章