Merge branch 'tangly1024:main' into main

This commit is contained in:
S.Y. Lee
2023-03-13 17:00:35 +08:00
committed by GitHub
25 changed files with 422 additions and 292 deletions

View File

@@ -1,7 +1,7 @@
import BLOG from '@/blog.config'
/**
* Notion图片映射处理有emjji的图标
* Notion图片映射处理有emoji的图标
* @param {*} img
* @param {*} value
* @returns
@@ -20,7 +20,7 @@ const mapImgUrl = (img, block, type = 'block') => {
}
// notion永久图床地址
if (!ret && img.indexOf('secure.notion-static.com') > 0 && BLOG.IMG_URL_TYPE === 'Notion') {
if (!ret && img.indexOf('secure.notion-static.com') > 0 && (BLOG.IMG_URL_TYPE === 'Notion' || type !== 'block')) {
ret = 'https://www.notion.so/image/' + encodeURIComponent(img) + '?table=' + type + '&id=' + block.id
}

View File

@@ -3,17 +3,23 @@ import fs from 'fs'
import BLOG from '@/blog.config'
export async function generateRobotsTxt() {
fs.mkdirSync('./public/rss', { recursive: true })
fs.writeFileSync('./public/robots.txt', `
# *
User-agent: *
Allow: /
# Host
Host: ${BLOG.LINK}
# Sitemaps
Sitemap: ${BLOG.LINK}/sitemap.xml
`)
const content = `
# *
User-agent: *
Allow: /
# Host
Host: ${BLOG.LINK}
# Sitemaps
Sitemap: ${BLOG.LINK}/sitemap.xml
`
try {
fs.mkdirSync('./public', { recursive: true })
fs.writeFileSync('./public/robots.txt', content)
} catch (error) {
// 在vercel运行环境是只读的这里会报错
// 但在vercel编译阶段、或VPS等其他平台这行代码会成功执行
}
}

View File

@@ -5,6 +5,26 @@ import ReactDOMServer from 'react-dom/server'
import { getPostBlocks } from './notion'
import NotionPage from '@/components/NotionPage'
/**
* 生成RSS内容
* @param {*} post
* @returns
*/
const createFeedContent = async post => {
// 加密的文章内容只返回摘要
if (post.password && post.password !== '') {
return post.summary
}
const blockMap = await getPostBlocks(post.id, 'rss-content')
if (blockMap) {
post.blockMap = blockMap
const content = ReactDOMServer.renderToString(<NotionPage post={post} />)
const regexExp =
/<div class="notion-collection-row"><div class="notion-collection-row-body"><div class="notion-collection-row-property"><div class="notion-collection-column-title"><svg.*?class="notion-collection-column-title-icon">.*?<\/svg><div class="notion-collection-column-title-body">.*?<\/div><\/div><div class="notion-collection-row-value">.*?<\/div><\/div><\/div><\/div>/g
return content.replace(regexExp, '')
}
}
export async function generateRss(posts) {
const year = new Date().getFullYear()
const feed = new Feed({
@@ -30,28 +50,14 @@ export async function generateRss(posts) {
})
}
fs.mkdirSync('./public/rss', { recursive: true })
fs.writeFileSync('./public/rss/feed.xml', feed.rss2())
fs.writeFileSync('./public/rss/atom.xml', feed.atom1())
fs.writeFileSync('./public/rss/feed.json', feed.json1())
}
/**
* 生成RSS内容
* @param {*} post
* @returns
*/
const createFeedContent = async post => {
// 加密的文章内容只返回摘要
if (post.password && post.password !== '') {
return post.summary
}
const blockMap = await getPostBlocks(post.id, 'rss-content')
if (blockMap) {
post.blockMap = blockMap
const content = ReactDOMServer.renderToString(<NotionPage post={post} />)
const regexExp =
/<div class="notion-collection-row"><div class="notion-collection-row-body"><div class="notion-collection-row-property"><div class="notion-collection-column-title"><svg.*?class="notion-collection-column-title-icon">.*?<\/svg><div class="notion-collection-column-title-body">.*?<\/div><\/div><div class="notion-collection-row-value">.*?<\/div><\/div><\/div><\/div>/g
return content.replace(regexExp, '')
try {
fs.mkdirSync('./public/rss', { recursive: true })
fs.writeFileSync('./public/rss/feed.xml', feed.rss2())
fs.writeFileSync('./public/rss/atom.xml', feed.atom1())
fs.writeFileSync('./public/rss/feed.json', feed.json1())
} catch (error) {
// 在vercel运行环境是只读的这里会报错
// 但在vercel编译阶段、或VPS等其他平台这行代码会成功执行
// RSS被高频词访问将大量消耗服务端资源故作为静态文件
}
}

View File

@@ -3,8 +3,6 @@ import fs from 'fs'
import BLOG from '@/blog.config'
export async function generateSitemapXml({ allPages }) {
fs.mkdirSync('./public', { recursive: true })
const urls = [{
loc: `${BLOG.LINK}`,
lastmod: new Date().toISOString().split('T')[0],
@@ -31,9 +29,13 @@ export async function generateSitemapXml({ allPages }) {
})
})
const xml = createSitemapXml(urls)
fs.writeFileSync('./public/sitemap.xml', xml)
try {
fs.writeFileSync('sitemap.xml', xml)
fs.writeFileSync('./public/sitemap.xml', xml)
} catch (error) {
console.warn('无法写入文件', error)
}
}
function createSitemapXml(urls) {
let urlsXml = ''
urls.forEach(u => {
@@ -46,7 +48,7 @@ function createSitemapXml(urls) {
})
return `
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:news="http://www.google.com/schemas/sitemap-news/0.9"
xmlns:xhtml="http://www.w3.org/1999/xhtml"
xmlns:mobile="http://www.google.com/schemas/sitemap-mobile/1.0"

View File

@@ -124,3 +124,32 @@ export const getListByPage = function (list, pageIndex, pageSize) {
pageIndex * pageSize
)
}
/**
* 判断是否移动设备
*/
export const isMobile = () => {
let isMobile = false
if (!isBrowser()) {
return isMobile
}
// 这个判断会引发 TypeError: navigator.userAgentData.mobile is undefined 问题,导致博客无法正常工作
// if (!isMobile && navigator.userAgentData.mobile) {
// isMobile = true
// }
if (!isMobile && (/Mobi|Android|iPhone/i.test(navigator.userAgent))) {
isMobile = true
}
if (/Android|iPhone|iPad|iPod/i.test(navigator.platform)) {
isMobile = true
}
if (typeof window.orientation !== 'undefined') {
isMobile = true
}
return isMobile
}

View File

@@ -24,6 +24,7 @@ import smoothscroll from 'smoothscroll-polyfill'
import AOS from 'aos'
import 'aos/dist/aos.css' // You can also use <link> for styles
import { isMobile } from '@/lib/utils'
const Ackee = dynamic(() => import('@/components/Ackee'), { ssr: false })
const Gtag = dynamic(() => import('@/components/Gtag'), { ssr: false })
@@ -57,7 +58,9 @@ const MyApp = ({ Component, pageProps }) => {
useEffect(() => {
AOS.init()
smoothscroll.polyfill()
if (isMobile()) {
smoothscroll.polyfill()
}
}, [])
return (

View File

@@ -5,7 +5,6 @@ import * as ThemeMap from '@/themes'
import { useGlobal } from '@/lib/global'
import { generateRss } from '@/lib/rss'
import { generateRobotsTxt } from '@/lib/robots.txt'
import { generateSitemapXml } from '@/lib/sitemap.xml'
const Index = props => {
const { theme } = useGlobal()
const ThemeComponents = ThemeMap[theme]
@@ -44,14 +43,12 @@ export async function getStaticProps() {
}
}
// 生成robotTxt
generateRobotsTxt()
// 生成Feed订阅
if (JSON.parse(BLOG.ENABLE_RSS)) {
generateRss(props?.latestPosts || [])
}
// 生成robotTxt
generateRobotsTxt()
// 生成sitemap.xml
generateSitemapXml({ allPages: props.allPages })
delete props.allPages

60
pages/sitemap.xml.js Normal file
View File

@@ -0,0 +1,60 @@
// pages/sitemap.xml.js
import { getServerSideSitemap } from 'next-sitemap'
import { getGlobalNotionData } from '@/lib/notion/getNotionData'
import BLOG from '@/blog.config'
export const getServerSideProps = async (ctx) => {
const { allPages } = await getGlobalNotionData({ from: 'rss' })
const defaultFields = [
{
loc: `${BLOG.LINK}`,
lastmod: new Date().toISOString().split('T')[0],
changefreq: 'daily',
priority: '0.7'
}, {
loc: `${BLOG.LINK}/archive`,
lastmod: new Date().toISOString().split('T')[0],
changefreq: 'daily',
priority: '0.7'
}, {
loc: `${BLOG.LINK}/category`,
lastmod: new Date().toISOString().split('T')[0],
changefreq: 'daily',
priority: '0.7'
}, {
loc: `${BLOG.LINK}/feed`,
lastmod: new Date().toISOString().split('T')[0],
changefreq: 'daily',
priority: '0.7'
}, {
loc: `${BLOG.LINK}/search`,
lastmod: new Date().toISOString().split('T')[0],
changefreq: 'daily',
priority: '0.7'
}, {
loc: `${BLOG.LINK}/tag`,
lastmod: new Date().toISOString().split('T')[0],
changefreq: 'daily',
priority: '0.7'
}
]
const postFields = allPages?.map(post => {
return {
loc: `${BLOG.LINK}/${post.slug}`,
lastmod: new Date(post?.date?.start_date || post?.createdTime).toISOString().split('T')[0],
changefreq: 'daily',
priority: '0.7'
}
})
const fields = defaultFields.concat(postFields)
// 缓存
ctx.res.setHeader(
'Cache-Control',
'public, max-age=3600, stale-while-revalidate=59'
)
return getServerSideSitemap(ctx, fields)
}
export default () => { }

View File

@@ -78,5 +78,5 @@ export const BlogListScroll = props => {
</div>
</div>
);
)
}

View File

@@ -1,6 +1,6 @@
import CommonHead from '@/components/CommonHead'
import { useEffect, useState } from 'react'
import { useCallback, useEffect, useState } from 'react'
import throttle from 'lodash.throttle'
import Footer from './components/Footer'
import JumpToTopButton from './components/JumpToTopButton'
import SideRight from './components/SideRight'
@@ -42,23 +42,20 @@ const LayoutBase = props => {
</>
)
const { onLoading } = useGlobal()
const throttleMs = 200
const scrollListener = useCallback(throttle(() => {
const targetRef = document.getElementById('wrapper')
const clientHeight = targetRef?.clientHeight
const scrollY = window.pageYOffset
const fullHeight = clientHeight - window.outerHeight
let per = parseFloat(((scrollY / fullHeight) * 100).toFixed(0))
if (per > 100) per = 100
const shouldShow = scrollY > 100 && per > 0
const scrollListener = () => {
requestAnimationFrame(() => {
const targetRef = document.getElementById('wrapper')
const clientHeight = targetRef?.clientHeight
const scrollY = window.pageYOffset
const fullHeight = clientHeight - window.outerHeight
let per = parseFloat(((scrollY / fullHeight) * 100).toFixed(0))
if (per > 100) per = 100
const shouldShow = scrollY > 100 && per > 0
if (shouldShow !== showFloatButton) {
switchShow(shouldShow)
}
// changePercent(per)
})
}
if (shouldShow !== showFloatButton) {
switchShow(shouldShow)
}
}, throttleMs))
useEffect(() => {
document.addEventListener('scroll', scrollListener)
return () => document.removeEventListener('scroll', scrollListener)

View File

@@ -7,7 +7,7 @@ const Announcement = ({ post, className }) => {
const { locale } = useGlobal()
if (post?.blockMap) {
return <div className={className}>
<section id='announcement-wrapper' className="hover:shadow-md dark:text-gray-300 border dark:border-black rounded-xl px-2 py-4 bg-white dark:bg-hexo-black-gray">
<section id='announcement-wrapper' className="dark:text-gray-300 border dark:border-black rounded-xl px-2 py-4 bg-white dark:bg-hexo-black-gray">
<div><i className='mr-2 fas fa-bullhorn' />{locale.COMMON.ANNOUNCEMENT}</div>
{post && (<div id="announcement-content">
<NotionPage post={post} className='text-center ' />

View File

@@ -1,108 +1,50 @@
import BLOG from '@/blog.config'
import Link from 'next/link'
import React from 'react'
import TagItemMini from './TagItemMini'
import CONFIG_HEXO from '../config_hexo'
import NotionPage from '@/components/NotionPage'
import Image from 'next/image'
import { BlogPostCardInfo } from './BlogPostCardInfo'
// import Image from 'next/image'
const BlogPostCard = ({ post, showSummary, siteInfo }) => {
const BlogPostCard = ({ index, post, showSummary, siteInfo }) => {
const showPreview = CONFIG_HEXO.POST_LIST_PREVIEW && post.blockMap
if (post && !post.page_cover && CONFIG_HEXO.POST_LIST_COVER_DEFAULT) {
post.page_cover = siteInfo?.pageCover
}
const showPageCover = CONFIG_HEXO.POST_LIST_COVER && post?.page_cover
const delay = (index % 2) * 200
return (
<div
key={post.id}
data-aos="fade-up"
data-aso-delay="200"
data-aos-duration="200"
data-aos-once="true"
data-aos-anchor-placement="top-bottom"
className={`flex md:flex-row flex-col-reverse ${CONFIG_HEXO.POST_LIST_IMG_CROSSOVER ? 'even:md:flex-row-reverse' : ''}
w-full md:h-72 h-96 justify-between overflow-hidden shadow-md
w-full md:h-52 justify-between overflow-hidden
border dark:border-black rounded-xl bg-white dark:bg-hexo-black-gray`}>
<div className={`lg:p-8 p-4 flex flex-col ${showPageCover ? 'md:w-7/12 w-full' : 'w-full'}`}>
<Link
href={`${BLOG.SUB_PATH}/${post.slug}`}
passHref
className={`replace cursor-pointer hover:underline text-2xl ${showPreview ? 'text-center' : ''
} leading-tight text-gray-600 dark:text-gray-100 hover:text-indigo-700 dark:hover:text-indigo-400`}>
{/* 文字内容 */}
<div
data-aos="fade-up"
data-aos-duration="200"
data-aos-delay={delay}
data-aos-once="true"
data-aos-anchor-placement="top-bottom"
className={`flex flex-col lg:p-6 p-4 ${showPageCover ? 'md:w-7/12 w-full ' : 'w-full'}`}>
{post.title}
<BlogPostCardInfo post={post} showPreview={showPreview} showSummary={showSummary}/>
</Link>
<div
className={`flex mt-2 items-center ${showPreview ? 'justify-center' : 'justify-start'
} flex-wrap dark:text-gray-500 text-gray-400 hover:text-indigo-700 dark:hover:text-indigo-400`}
>
<Link
href={`/archive#${post?.date?.start_date?.substr(0, 7)}`}
passHref
className="font-light hover:underline cursor-pointer text-sm leading-4 mr-3">
<i className="far fa-calendar-alt mr-1" />
{post.date?.start_date || post.lastEditedTime}
</Link>
</div>
{(!showPreview || showSummary) && !post.results && (
<p style={{ overflow: 'hidden', textOverflow: 'ellipsis', display: '-webkit-box', WebkitLineClamp: '4', WebkitBoxOrient: 'vertical' }}
className="replace h-full max-h-32 my-4 text-gray-700 dark:text-gray-300 text-sm font-light leading-7">
{post.summary}
</p>
)}
{/* 搜索结果 */}
{post.results && (
<p className="mt-4 text-gray-700 dark:text-gray-300 text-sm font-light leading-7">
{post.results.map(r => (
<span key={r}>{r}</span>
))}
</p>
)}
{showPreview && (
<div className="overflow-ellipsis truncate">
<NotionPage post={post} />
</div>
)}
<div className="text-gray-400 justify-between flex">
<Link
href={`/category/${post.category}`}
passHref
className="cursor-pointer font-light text-sm hover:underline hover:text-indigo-700 dark:hover:text-indigo-400 transform">
<i className="mr-1 far fa-folder" />
{post.category}
</Link>
<div className="md:flex-nowrap flex-wrap md:justify-start inline-block">
<div>
{' '}
{post.tagItems.map(tag => (
<TagItemMini key={tag.name} tag={tag} />
))}
</div>
</div>
</div>
</div>
{/* 图片封面 */}
{showPageCover && !showPreview && post?.page_cover && (
<div className="flex overflow-hidden md:w-5/12 h-full">
<Link href={`${BLOG.SUB_PATH}/${post.slug}`} passHref legacyBehavior>
{/* eslint-disable-next-line @next/next/no-img-element */}
{/* <img
<img
src={post?.page_cover}
alt={post.title}
loading='lazy'
className="w-full cursor-pointer object-cover duration-200 hover:scale-125 "
/> */}
<div className='relative w-full h-full'>
/>
{/* <div className='relative w-full h-full'>
<Image
className='hover:scale-125 transition cursor-pointer duration-500'
src={post?.page_cover}
@@ -112,7 +54,7 @@ const BlogPostCard = ({ post, showSummary, siteInfo }) => {
blurDataURL='/bg_image.jpg'
style={{ objectFit: 'cover' }}
fill/>
</div>
</div> */}
</Link>
</div>
)}

View File

@@ -0,0 +1,79 @@
import BLOG from '@/blog.config'
import NotionPage from '@/components/NotionPage'
import Link from 'next/link'
import TagItemMini from './TagItemMini'
export const BlogPostCardInfo = ({ post, showPreview, showSummary }) => {
return <>
{/* 标题 */}
<Link
href={`${BLOG.SUB_PATH}/${post.slug}`}
passHref
className={`replace cursor-pointer hover:underline text-2xl ${showPreview ? 'text-center' : ''
} leading-tight text-gray-600 dark:text-gray-100 hover:text-indigo-700 dark:hover:text-indigo-400`}>
{post.title}
</Link>
{/* 日期 */}
<div
className={`flex mt-2 items-center ${showPreview ? 'justify-center' : 'justify-start'
} flex-wrap dark:text-gray-500 text-gray-400 hover:text-indigo-700 dark:hover:text-indigo-400`}
>
<Link
href={`/archive#${post?.date?.start_date?.substr(0, 7)}`}
passHref
className="font-light hover:underline cursor-pointer text-sm leading-4 mr-3">
<i className="far fa-calendar-alt mr-1" />
{post.date?.start_date || post.lastEditedTime}
</Link>
</div>
{/* 摘要 */}
{(!showPreview || showSummary) && !post.results && (
<p style={{ overflow: 'hidden', textOverflow: 'ellipsis', display: '-webkit-box', WebkitLineClamp: '2', WebkitBoxOrient: 'vertical' }}
className="replace py-3 h-28 text-gray-700 dark:text-gray-300 text-sm font-light leading-7">
{post.summary}
</p>
)}
{/* 搜索结果 */}
{post.results && (
<p className="mt-4 text-gray-700 dark:text-gray-300 text-sm font-light leading-7">
{post.results.map(r => (
<span key={r}>{r}</span>
))}
</p>
)}
{/* 预览 */}
{showPreview && (
<div className="overflow-ellipsis truncate">
<NotionPage post={post} />
</div>
)}
{/* 分类标签 */}
<div className="text-gray-400 justify-between flex">
<Link
href={`/category/${post.category}`}
passHref
className="cursor-pointer font-light text-sm hover:underline hover:text-indigo-700 dark:hover:text-indigo-400 transform">
<i className="mr-1 far fa-folder" />
{post.category}
</Link>
<div className="md:flex-nowrap flex-wrap md:justify-start inline-block">
<div>
{' '}
{post.tagItems.map(tag => (
<TagItemMini key={tag.name} tag={tag} />
))}
</div>
</div>
</div>
</>
}

View File

@@ -22,7 +22,7 @@ const BlogPostListPage = ({ page = 1, posts = [], postCount, siteInfo }) => {
{/* 文章列表 */}
<div className="space-y-6 px-2">
{posts.map(post => (
<BlogPostCard key={post.id} post={post} siteInfo={siteInfo}/>
<BlogPostCard index={posts.indexOf(post)} key={post.id} post={post} siteInfo={siteInfo}/>
))}
</div>
{showPagination && <PaginationNumber page={page} totalPage={totalPage} />}

View File

@@ -1,8 +1,9 @@
import Image from 'next/image'
import { useEffect, useState } from 'react'
// import Image from 'next/image'
import { useCallback, useEffect, useState } from 'react'
import Typed from 'typed.js'
import CONFIG_HEXO from '../config_hexo'
import NavButtonGroup from './NavButtonGroup'
import throttle from 'lodash.throttle'
let wrapperTop = 0
let windowTop = 0
@@ -53,17 +54,47 @@ const Header = props => {
})
}
const autoScrollEnd = () => {
if (autoScroll) {
windowTop = window.scrollY
autoScroll = false
}
}
const throttleMs = 200
const scrollTrigger = useCallback(throttle(() => {
if (screen.width <= 768) {
return
}
const scrollS = window.scrollY
// 自动滚动
if ((scrollS > windowTop) & (scrollS < window.innerHeight) && !autoScroll
) {
autoScroll = true
window.scrollTo({ top: wrapperTop, behavior: 'smooth' })
autoScrollEnd()
}
if ((scrollS < windowTop) && (scrollS < window.innerHeight) && !autoScroll) {
autoScroll = true
window.scrollTo({ top: 0, behavior: 'smooth' })
autoScrollEnd()
}
windowTop = scrollS
}, throttleMs))
return (
<header
id="header"
className="w-full h-screen bg-black text-white relative"
>
<div className='w-full h-full'>
<Image src={siteInfo.pageCover} fill
{/* <Image src={siteInfo.pageCover} fill
style={{ objectFit: 'cover' }}
className='opacity-70'
placeholder='blur'
blurDataURL='/bg_image.jpg' />
blurDataURL='/bg_image.jpg' /> */}
{/* eslint-disable-next-line @next/next/no-img-element */}
<img src={siteInfo.pageCover} className='h-full w-full object-cover opacity-70 ' />
</div>
<div className="absolute bottom-0 flex flex-col h-full items-center justify-center w-full ">
@@ -87,37 +118,4 @@ const Header = props => {
)
}
const autoScrollEnd = () => {
if (autoScroll) {
windowTop = window.scrollY
autoScroll = false
}
}
/**
* 自动吸附滚动,移动端体验不好暂时关闭
*/
const scrollTrigger = () => {
requestAnimationFrame(() => {
if (screen.width <= 768) {
return
}
const scrollS = window.scrollY
// 自动滚动
if ((scrollS > windowTop) & (scrollS < window.innerHeight) && !autoScroll
) {
autoScroll = true
window.scrollTo({ top: wrapperTop, behavior: 'smooth' })
autoScrollEnd()
}
if ((scrollS < windowTop) && (scrollS < window.innerHeight) && !autoScroll) {
autoScroll = true
window.scrollTo({ top: 0, behavior: 'smooth' })
autoScrollEnd()
}
windowTop = scrollS
})
}
export default Header

View File

@@ -1,6 +1,6 @@
import BLOG from '@/blog.config'
import { useGlobal } from '@/lib/global'
import Image from 'next/image'
// import Image from 'next/image'
import Link from 'next/link'
import { useRouter } from 'next/router'
@@ -40,14 +40,16 @@ const LatestPostsGroup = ({ latestPosts, siteInfo }) => {
className={'my-2 flex'}>
<div className="w-20 h-16 overflow-hidden relative">
<Image
{/* <Image
src={headerImage}
fill
style={{ objectFit: 'cover' }}
placeholder='blur'
blurDataURL='/bg_image.jpg'
quality={10}
alt={post.title} />
alt={post.title} /> */}
{/* eslint-disable-next-line @next/next/no-img-element */}
<img src={headerImage} className='object-cover w-full h-full'/>
</div>
<div
className={

View File

@@ -1,6 +1,6 @@
import { useGlobal } from '@/lib/global'
import Link from 'next/link'
import { useEffect, useRef, useState } from 'react'
import { useCallback, useEffect, useRef, useState } from 'react'
import CategoryGroup from './CategoryGroup'
import Collapse from './Collapse'
import Logo from './Logo'
@@ -9,6 +9,7 @@ import TagGroups from './TagGroups'
import MenuButtonGroupTop from './MenuButtonGroupTop'
import MenuList from './MenuList'
import { useRouter } from 'next/router'
import throttle from 'lodash.throttle'
let windowTop = 0
@@ -39,39 +40,40 @@ const TopNav = props => {
}
}, [])
const scrollTrigger = () => {
requestAnimationFrame(() => {
const scrollS = window.scrollY
const nav = document.querySelector('#sticky-nav')
const header = document.querySelector('#header')
// 是否将导航栏透明
const navTransparent = (scrollS < document.documentElement.clientHeight - 12 && router.route === '/') || scrollS < 300 // 透明导航条的条件
const throttleMs = 200
if (header && navTransparent) {
nav && nav.classList.replace('bg-white', 'bg-none')
nav && nav.classList.replace('text-black', 'text-white')
nav && nav.classList.replace('border', 'border-transparent')
nav && nav.classList.replace('drop-shadow-md', 'shadow-none')
nav && nav.classList.replace('dark:bg-hexo-black-gray', 'transparent')
} else {
nav && nav.classList.replace('bg-none', 'bg-white')
nav && nav.classList.replace('text-white', 'text-black')
nav && nav.classList.replace('border-transparent', 'border')
nav && nav.classList.replace('shadow-none', 'drop-shadow-md')
nav && nav.classList.replace('transparent', 'dark:bg-hexo-black-gray')
}
const scrollTrigger = useCallback(throttle(() => {
const scrollS = window.scrollY
const nav = document.querySelector('#sticky-nav')
const header = document.querySelector('#header')
// 是否将导航栏透明
const navTransparent = (scrollS < document.documentElement.clientHeight - 12 && router.route === '/') || scrollS < 300 // 透明导航条的条件
const showNav = scrollS <= windowTop || scrollS < 5 || (header && scrollS <= header.clientHeight * 2)// 非首页无大图时影藏顶部 滚动条置顶时隐藏
if (!showNav) {
nav && nav.classList.replace('top-0', '-top-20')
windowTop = scrollS
} else {
nav && nav.classList.replace('-top-20', 'top-0')
windowTop = scrollS
}
navDarkMode()
})
}
if (header && navTransparent) {
nav && nav.classList.replace('bg-white', 'bg-none')
nav && nav.classList.replace('text-black', 'text-white')
nav && nav.classList.replace('border', 'border-transparent')
nav && nav.classList.replace('drop-shadow-md', 'shadow-none')
nav && nav.classList.replace('dark:bg-hexo-black-gray', 'transparent')
} else {
nav && nav.classList.replace('bg-none', 'bg-white')
nav && nav.classList.replace('text-white', 'text-black')
nav && nav.classList.replace('border-transparent', 'border')
nav && nav.classList.replace('shadow-none', 'drop-shadow-md')
nav && nav.classList.replace('transparent', 'dark:bg-hexo-black-gray')
}
const showNav = scrollS <= windowTop || scrollS < 5 || (header && scrollS <= header.clientHeight * 2)// 非首页无大图时影藏顶部 滚动条置顶时隐藏
if (!showNav) {
nav && nav.classList.replace('top-0', '-top-20')
windowTop = scrollS
} else {
nav && nav.classList.replace('-top-20', 'top-0')
windowTop = scrollS
}
navDarkMode()
}, throttleMs)
)
const navDarkMode = () => {
const nav = document.getElementById('sticky-nav')

View File

@@ -1,5 +1,5 @@
import CommonHead from '@/components/CommonHead'
import { useEffect, useState } from 'react'
import { useCallback, useEffect, useState } from 'react'
import Footer from './components/Footer'
import JumpToTopButton from './components/JumpToTopButton'
@@ -9,6 +9,7 @@ import LoadingCover from './components/LoadingCover'
import { useGlobal } from '@/lib/global'
import BLOG from '@/blog.config'
import FloatDarkModeButton from './components/FloatDarkModeButton'
import throttle from 'lodash.throttle'
/**
* 基础布局 采用左右两侧布局,移动端使用顶部导航栏
@@ -21,15 +22,15 @@ const LayoutBase = props => {
const [show, switchShow] = useState(false)
const { onLoading } = useGlobal()
const scrollListener = () => {
requestAnimationFrame(() => {
const scrollY = window.pageYOffset
const shouldShow = scrollY > 300
if (shouldShow !== show) {
switchShow(shouldShow)
}
})
}
const throttleMs = 200
const scrollListener = useCallback(throttle(() => {
const scrollY = window.pageYOffset
const shouldShow = scrollY > 300
if (shouldShow !== show) {
switchShow(shouldShow)
}
}, throttleMs))
useEffect(() => {
document.addEventListener('scroll', scrollListener)
return () => document.removeEventListener('scroll', scrollListener)

View File

@@ -1,4 +1,4 @@
import React, { useEffect } from 'react'
import React, { useCallback, useEffect } from 'react'
import { ArticleLock } from './components/ArticleLock'
import HeaderArticle from './components/HeaderArticle'
import LayoutBase from './LayoutBase'
@@ -9,21 +9,21 @@ import ArticleCopyright from './components/ArticleCopyright'
import { ArticleInfo } from './components/ArticleInfo'
import Catalog from './components/Catalog'
import JumpToCommentButton from './components/JumpToCommentButton'
import throttle from 'lodash.throttle'
export const LayoutSlug = props => {
const { post, lock, validPassword } = props
const [show, switchShow] = React.useState(false)
const throttleMs = 200
const scrollListener = () => {
requestAnimationFrame(() => {
const scrollY = window.pageYOffset
const shouldShow = scrollY > 220 && post?.toc?.length > 0
if (shouldShow !== show) {
switchShow(shouldShow)
}
})
}
const scrollListener = useCallback(throttle(() => {
const scrollY = window.pageYOffset
const shouldShow = scrollY > 220 && post?.toc?.length > 0
if (shouldShow !== show) {
switchShow(shouldShow)
}
}, throttleMs))
useEffect(() => {
document.addEventListener('scroll', scrollListener)
return () => document.removeEventListener('scroll', scrollListener)

View File

@@ -3,20 +3,22 @@ import Link from 'next/link'
import React from 'react'
import TagItemMini from './TagItemMini'
import CONFIG_MATERY from '../config_matery'
import Image from 'next/image'
// import Image from 'next/image'
const BlogPostCard = ({ post, showSummary, siteInfo }) => {
const BlogPostCard = ({ index, post, showSummary, siteInfo }) => {
const showPreview = CONFIG_MATERY.POST_LIST_PREVIEW && post.blockMap
// matery 主题默认强制显示图片
if (post && !post.page_cover) {
post.page_cover = siteInfo?.pageCover
}
const showPageCover = CONFIG_MATERY.POST_LIST_COVER && !showPreview && post?.page_cover
const delay = (index % 3) * 300
return (
<div
data-aos="zoom-in"
data-aos-duration="200"
data-aos-once="false"
data-aos-duration="500"
data-aos-delay={delay}
data-aos-once="true"
data-aos-anchor-placement="top-bottom"
className="w-full mb-4 h-full overflow-auto shadow-md border dark:border-black rounded-xl bg-white dark:bg-hexo-black-gray">
@@ -26,14 +28,15 @@ const BlogPostCard = ({ post, showSummary, siteInfo }) => {
{/* 头部图片 填充卡片 */}
{showPageCover && (
<Link href={`${BLOG.SUB_PATH}/${post.slug}`} passHref legacyBehavior>
<div className="flex flex-grow w-full relative duration-200 bg-black rounded-t-md cursor-pointer transform overflow-hidden">
<div
className="flex flex-grow w-full relative duration-200 bg-black rounded-t-md cursor-pointer transform overflow-hidden">
{/* eslint-disable-next-line @next/next/no-img-element */}
{/* <img
<img
src={post?.page_cover}
alt={post.title}
className="opacity-50 h-full w-full hover:scale-125 rounded-t-md transform object-cover duration-500"
/> */}
<div className='relative w-full'>
/>
{/* <div className='relative w-full'>
<Image
className='hover:scale-125 opacity-50 transition cursor-pointer duration-500'
src={post?.page_cover}
@@ -43,7 +46,7 @@ const BlogPostCard = ({ post, showSummary, siteInfo }) => {
blurDataURL='/bg_image.jpg'
style={{ objectFit: 'cover' }}
fill/>
</div>
</div> */}
<span className='absolute bottom-0 left-0 text-white p-6 text-2xl replace' > {post.title}</span>
</div>
</Link>

View File

@@ -23,7 +23,7 @@ const BlogPostListPage = ({ page = 1, posts = [], postCount, siteInfo }) => {
{/* 文章列表 */}
<div className="px-4 pt-4 xl:columns-3 md:columns-2 pb-24" >
{posts.map(post => (
<BlogPostCard key={post.id} post={post} siteInfo={siteInfo} />
<BlogPostCard index={posts.indexOf(post)} key={post.id} post={post} siteInfo={siteInfo} />
))}
</div>
{showPagination && <PaginationSimple page={page} totalPage={totalPage} />}

View File

@@ -2,9 +2,10 @@ import BLOG from '@/blog.config'
import BlogPostCard from './BlogPostCard'
import BlogPostListEmpty from './BlogPostListEmpty'
import { useGlobal } from '@/lib/global'
import React from 'react'
import React, { useCallback } from 'react'
import CONFIG_MATERY from '../config_matery'
import { getListByPage } from '@/lib/utils'
import throttle from 'lodash.throttle'
/**
* 博客列表滚动分页
@@ -29,8 +30,8 @@ const BlogPostListScroll = ({ posts = [], currentSearch, showSummary = CONFIG_MA
updatePage(page + 1)
}
// 监听滚动自动分页加载
const scrollTrigger = () => {
const throttleMs = 200
const scrollTrigger = useCallback(throttle(() => {
requestAnimationFrame(() => {
const scrollS = window.scrollY + window.outerHeight
const clientHeight = targetRef ? (targetRef.current ? (targetRef.current.clientHeight) : 0) : 0
@@ -38,14 +39,13 @@ const BlogPostListScroll = ({ posts = [], currentSearch, showSummary = CONFIG_MA
handleGetMore()
}
})
}
}, throttleMs))
// 监听滚动
React.useEffect(() => {
// window.addEventListener('scroll', scrollTrigger)
// return () => {
// window.removeEventListener('scroll', scrollTrigger)
// }
window.addEventListener('scroll', scrollTrigger)
return () => {
window.removeEventListener('scroll', scrollTrigger)
}
})
const targetRef = React.useRef(null)
@@ -59,7 +59,7 @@ const BlogPostListScroll = ({ posts = [], currentSearch, showSummary = CONFIG_MA
{/* 文章列表 */}
<div className='flex flex-wrap space-y-1 lg:space-y-4 px-2'>
{postsToShow.map(post => (
<BlogPostCard key={post.id} post={post} showSummary={showSummary} siteInfo={siteInfo}/>
<BlogPostCard index={posts.indexOf(post)} key={post.id} post={post} showSummary={showSummary} siteInfo={siteInfo}/>
))}
</div>

View File

@@ -1,12 +1,14 @@
import Image from 'next/image'
import { useEffect, useState } from 'react'
// import Image from 'next/image'
import { useCallback, useEffect, useState } from 'react'
import Typed from 'typed.js'
import CONFIG_MATERY from '../config_matery'
import throttle from 'lodash.throttle'
let wrapperTop = 0
let windowTop = 0
let autoScroll = false
const enableAutoScroll = false // 是否开启自动吸附滚动
const throttleMs = 200
/**
*
@@ -53,29 +55,27 @@ const Header = props => {
* 吸附滚动,移动端关闭
* @returns
*/
const scrollTrigger = () => {
requestAnimationFrame(() => {
if (screen.width <= 768) {
return
}
const scrollTrigger = useCallback(throttle(() => {
if (screen.width <= 768) {
return
}
const scrollS = window.scrollY
const scrollS = window.scrollY
// 自动滚动
if ((scrollS > windowTop) & (scrollS < window.innerHeight) && !autoScroll
) {
autoScroll = true
window.scrollTo({ top: wrapperTop, behavior: 'smooth' })
setTimeout(autoScrollEnd, 500)
}
if ((scrollS < windowTop) && (scrollS < window.innerHeight) && !autoScroll) {
autoScroll = true
window.scrollTo({ top: 0, behavior: 'smooth' })
setTimeout(autoScrollEnd, 500)
}
windowTop = scrollS
})
}
// 自动滚动
if ((scrollS > windowTop) & (scrollS < window.innerHeight) && !autoScroll
) {
autoScroll = true
window.scrollTo({ top: wrapperTop, behavior: 'smooth' })
setTimeout(autoScrollEnd, 500)
}
if ((scrollS < windowTop) && (scrollS < window.innerHeight) && !autoScroll) {
autoScroll = true
window.scrollTo({ top: 0, behavior: 'smooth' })
setTimeout(autoScrollEnd, 500)
}
windowTop = scrollS
}, throttleMs))
function updateHeaderHeight() {
requestAnimationFrame(() => {
@@ -90,11 +90,13 @@ const Header = props => {
className="md:bg-fixed w-full h-screen bg-black text-white relative"
>
<div className='w-full h-full absolute'>
<Image src={siteInfo.pageCover} fill
{/* <Image src={siteInfo.pageCover} fill
style={{ objectFit: 'cover' }}
className='opacity-70'
placeholder='blur'
blurDataURL='/bg_image.jpg' />
blurDataURL='/bg_image.jpg' /> */}
{/* eslint-disable-next-line @next/next/no-img-element */}
<img src={siteInfo.pageCover} className='h-full w-full object-cover opacity-70 ' />
</div>
<div className="absolute flex flex-col h-full items-center justify-center w-full ">

View File

@@ -1,6 +1,6 @@
import { useGlobal } from '@/lib/global'
import Link from 'next/link'
import { useEffect, useRef, useState } from 'react'
import { useCallback, useEffect, useRef, useState } from 'react'
import CategoryGroup from './CategoryGroup'
import Logo from './Logo'
import SearchDrawer from './SearchDrawer'
@@ -8,6 +8,7 @@ import TagGroups from './TagGroups'
import MenuButtonGroupTop from './MenuButtonGroupTop'
import SideBarDrawer from '@/components/SideBarDrawer'
import SideBar from './SideBar'
import throttle from 'lodash.throttle'
let windowTop = 0
@@ -21,8 +22,8 @@ const TopNav = props => {
const { locale } = useGlobal()
const searchDrawer = useRef()
const { isDarkMode } = useGlobal()
const scrollTrigger = () => {
const throttleMs = 200
const scrollTrigger = useCallback(throttle(() => {
requestAnimationFrame(() => {
const scrollS = window.scrollY
const nav = document.querySelector('#sticky-nav')
@@ -52,7 +53,7 @@ const TopNav = props => {
}
navDarkMode()
})
}
}, throttleMs))
const navDarkMode = () => {
const nav = document.getElementById('sticky-nav')

View File

@@ -23,8 +23,8 @@ export const ArticleInfo = (props) => {
<div className="mb-4 text-sm text-gray-700 dark:text-gray-300">
<span> <i className="fa-regular fa-user"></i> <a href={CONFIG_SIMPLE.AUTHOR_LINK}>{BLOG.AUTHOR}</a></span>
<span> - <i className="fa-regular fa-clock"></i> {post.date?.start_date || post.createdTime}</span>
{post.category && <span> - <i className="fa-regular fa-folder"></i> <a href="#" className="hover:text-red-400 transition-all duration-200">{post.category}</a></span>}
{post.tags && post.tags?.length > 0 && post.tags.map(t => <span key={t}> / <Link href={`tag/${t}`}><span className=' hover:text-red-400 transition-all duration-200'>{t}</span></Link></span>)}
{post.category && <span> - <i className="fa-regular fa-folder"></i> <a href={`/category/${post.category}`} className="hover:text-red-400 transition-all duration-200">{post.category}</a></span>}
{post.tags && post.tags?.length > 0 && post.tags.map(t => <span key={t}> / <Link href={`/tag/${t}`}><span className=' hover:text-red-400 transition-all duration-200'>{t}</span></Link></span>)}
</div>
</>)}