mirror of
https://github.com/d0zingcat/NotionNext.git
synced 2026-05-14 15:09:22 +00:00
Feature:
三列排版
This commit is contained in:
@@ -23,7 +23,8 @@ import { useGlobal } from '@/lib/global'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faEye, faFolderOpen } from '@fortawesome/free-solid-svg-icons'
|
||||
import BlogAround from '@/components/BlogAround'
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { useRef } from 'react'
|
||||
import WordCount from './WordCount'
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -37,10 +38,6 @@ export default function ArticleDetail ({ post, blockMap, recommendPosts, prev, n
|
||||
const { locale } = useGlobal()
|
||||
const date = formatDate(post?.date?.start_date || post.createdTime, BLOG.lang)
|
||||
|
||||
useEffect(() => {
|
||||
countWords()
|
||||
})
|
||||
|
||||
return (<>
|
||||
<div id="article-wrapper" ref={targetRef} className="flex-grow mt-14 md:mt-0 max-w-5xl mx-auto w-screen md:w-full ">
|
||||
<article itemScope itemType="https://schema.org/Movie"
|
||||
@@ -100,7 +97,7 @@ export default function ArticleDetail ({ post, blockMap, recommendPosts, prev, n
|
||||
</section>
|
||||
|
||||
<section className='flex justify-end py-2 pl-1 dark:text-white items-center font-light italic text-sm'>
|
||||
本文共<strong id='wordCount'>0</strong>字,阅读需要约<strong id='readTime'>0</strong>分钟
|
||||
<WordCount/>
|
||||
</section>
|
||||
|
||||
</header>
|
||||
@@ -181,7 +178,7 @@ export default function ArticleDetail ({ post, blockMap, recommendPosts, prev, n
|
||||
</article>
|
||||
|
||||
{/* 评论互动 */}
|
||||
<div className="mt-5 lg:px-40 shadow-lg rounded-xl w-screen md:w-full overflow-x-auto dark:border-gray-700 bg-white dark:bg-gray-700">
|
||||
<div className="mt-5 lg:px-40 hover:shadow-2xl duration-200 shadow-lg rounded-xl w-screen md:w-full overflow-x-auto dark:border-gray-700 bg-white dark:bg-gray-700">
|
||||
<Comment frontMatter={post} />
|
||||
</div>
|
||||
</div>
|
||||
@@ -199,42 +196,3 @@ export default function ArticleDetail ({ post, blockMap, recommendPosts, prev, n
|
||||
const mapPageUrl = id => {
|
||||
return 'https://www.notion.so/' + id.replace(/-/g, '')
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新字数统计和阅读时间
|
||||
*/
|
||||
function countWords () {
|
||||
if (window) {
|
||||
const articleElement = document.getElementById('notion-article')
|
||||
if (articleElement) {
|
||||
const articleText = deleteHtmlTag(articleElement.innerHTML)
|
||||
const wordCount = fnGetCpmisWords(articleText)
|
||||
// 阅读速度 300-500每分钟
|
||||
document.getElementById('wordCount').innerHTML = wordCount
|
||||
document.getElementById('readTime').innerHTML = Math.floor(wordCount / 400)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 去除html标签
|
||||
function deleteHtmlTag (str) {
|
||||
str = str.replace(/<[^>]+>|&[^>]+;/g, '').trim()// 去掉所有的html标签和 之类的特殊符合
|
||||
return str
|
||||
}
|
||||
|
||||
// 用word方式计算正文字数
|
||||
function fnGetCpmisWords (str) {
|
||||
let sLen = 0
|
||||
try {
|
||||
// eslint-disable-next-line no-irregular-whitespace
|
||||
str = str.replace(/(\r\n+|\s+| +)/g, '龘')
|
||||
// eslint-disable-next-line no-control-regex
|
||||
str = str.replace(/[\x00-\xff]/g, 'm')
|
||||
str = str.replace(/m+/g, '*')
|
||||
str = str.replace(/龘+/g, '')
|
||||
sLen = str.length
|
||||
} catch (e) {
|
||||
|
||||
}
|
||||
return sLen
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import TagItemMini from './TagItemMini'
|
||||
|
||||
const BlogPostCard = ({ post, tags }) => {
|
||||
return (
|
||||
<div key={post.id} className='flex md:flex-row flex-col-reverse justify-between hover:shadow-2xl shadow-md mb-8 duration-300 rounded-xl
|
||||
<div key={post.id} className='flex 2xl:flex-row flex-col-reverse justify-between hover:shadow-2xl shadow-md mb-8 duration-300 rounded-xl
|
||||
w-full mx-2 bg-white dark:bg-gray-800 dark:hover:bg-gray-700 dark:border-gray-600'>
|
||||
|
||||
<div className='p-8 flex flex-col justify-between w-full'>
|
||||
@@ -41,8 +41,8 @@ const BlogPostCard = ({ post, tags }) => {
|
||||
|
||||
{post?.page_cover && (
|
||||
<Link href={`${BLOG.path}/article/${post.slug}`} passHref>
|
||||
<div className='h-60 w-full md:max-w-sm relative rounded-t-xl md:rounded-t-none md:rounded-r-xl md:h-full duration-200 cursor-pointer transform overflow-hidden'>
|
||||
<Image className='hover:scale-105 transform duration-500 rounded-t-xl md:rounded-t-none md:rounded-r-xl' src={post?.page_cover} alt={post.title} layout='fill' objectFit='cover' loading='lazy' />
|
||||
<div className='h-60 w-full 2xl:max-w-sm relative rounded-t-xl 2xl:rounded-t-none 2xl:rounded-r-xl 2xl:h-full duration-200 cursor-pointer transform overflow-hidden'>
|
||||
<Image className='hover:scale-105 transform duration-500 rounded-t-xl 2xl:rounded-t-none 2xl:rounded-r-xl' src={post?.page_cover} alt={post.title} layout='fill' objectFit='cover' loading='lazy' />
|
||||
</div>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
@@ -64,7 +64,7 @@ const BlogPostListScroll = ({ posts = [], tags, currentSearch, currentCategory,
|
||||
<div onClick={() => {
|
||||
handleGetMore()
|
||||
}}
|
||||
className='w-full my-4 py-4 text-center cursor-pointer glassmorphism dark:text-gray-200'
|
||||
className='w-full my-4 py-4 text-center cursor-pointer glassmorphism shadow-xl rounded-xl dark:text-gray-200'
|
||||
> {hasMore ? '继续加载' : '加载完了😰'} </div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,9 +10,9 @@ const CategoryGroup = ({ currentCategory, categories }) => {
|
||||
const selected = currentCategory === category
|
||||
return <Link key={category} href={`/category/${category}`} passHref>
|
||||
<a className={(selected
|
||||
? 'hover:text-white bg-blue-500 dark:bg-black text-white '
|
||||
? 'hover:text-white bg-blue-500 text-white '
|
||||
: 'dark:text-gray-400 text-gray-500 hover:text-blue-500 ') +
|
||||
' text-sm w-full items-center duration-300 dark:hover:text-blue-400 hover:underline px-5 cursor-pointer py-1 font-light'}>
|
||||
' rounded-xl text-md w-full items-center duration-300 dark:hover:text-blue-400 hover:underline px-3 mx-2 cursor-pointer py-2 font-light'}>
|
||||
<FontAwesomeIcon icon={selected ? faFolderOpen : faFolder} className={`${selected ? 'text-white' : 'text-gray-400'} mr-2`} />{category}({categories[category]})
|
||||
</a>
|
||||
</Link>
|
||||
|
||||
@@ -32,7 +32,7 @@ export default function FloatDarkModeButton () {
|
||||
onClick={handleChangeDarkMode}
|
||||
className={
|
||||
(show ? 'animate__fadeInRight ' : 'hidden lg:block') +
|
||||
' px-3.5 py-3 animate__animated animate__faster shadow-card fixed right-5 bottom-36 z-10 duration-200 text-xs cursor-pointer rounded-xl' +
|
||||
' px-3.5 py-3 animate__animated animate__faster shadow-card fixed right-3 bottom-36 z-10 duration-200 text-xs cursor-pointer rounded-xl' +
|
||||
' text-black shadow-card dark:border-gray-500 glassmorphism dark:bg-gray-700 dark:text-gray-200'
|
||||
}
|
||||
>
|
||||
|
||||
@@ -37,7 +37,7 @@ const JumpToTopButton = ({ targetRef, showPercent = true }) => {
|
||||
return () => document.removeEventListener('scroll', scrollListener)
|
||||
}, [show])
|
||||
|
||||
return (<div id='jump-to-top' className='right-5 fixed flex bottom-48 duration-500 z-20'>
|
||||
return (<div id='jump-to-top' className='right-3 fixed flex bottom-48 duration-500 z-20'>
|
||||
<div onClick={() => window.scrollTo({ top: 0, behavior: 'smooth' })}
|
||||
className={(show ? '' : 'hidden') + ' animate__fadeInRight animate__animated animate__faster shadow-card rounded-xl glassmorphism py-1 cursor-pointer '}>
|
||||
<div className='text-center'>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import Link from 'next/link'
|
||||
import BLOG from '@/blog.config'
|
||||
import { formatDateFmt } from '@/lib/formatDate'
|
||||
import { faFileAlt } from '@fortawesome/free-solid-svg-icons'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
/**
|
||||
@@ -32,13 +33,11 @@ const LatestPostsGroup = ({ posts, sliceCount = 5 }) => {
|
||||
return (
|
||||
<Link key={post.id} title={post.title} href={`${BLOG.path}/article/${post.slug}`} passHref>
|
||||
<a className={(selected ? 'text-white bg-blue-500 dark:bg-black ' : 'text-gray-500 dark:text-gray-300 ') + ' my-1 px-5 flex font-light justify-between'}>
|
||||
<div className={ 'text-xs py-1 flex w-40 overflow-x-hidden whitespace-nowrap overflow-hidden ' +
|
||||
<div className={ 'text-xs py-1.5 flex overflow-x-hidden whitespace-nowrap overflow-hidden ' +
|
||||
'hover:text-blue-500 dark:hover:text-blue-400 cursor-pointer hover:underline ' }>
|
||||
<FontAwesomeIcon icon={faFileAlt} className='mr-2'/>
|
||||
{post.title}
|
||||
</div>
|
||||
<div className='text-xs py-1'>
|
||||
{formatDateFmt(post.lastEditedTime, 'yyyy/MM/dd')}
|
||||
</div>
|
||||
</a>
|
||||
</Link>
|
||||
)
|
||||
|
||||
@@ -2,7 +2,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faSpinner } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
export default function LoadingCover () {
|
||||
return (<div id="loading-cover" className={'md:-mt-20 dark:text-white text-black animate__animated animate__fadeIn flex-grow flex flex-col justify-center z-10 w-full h-screen container mx-auto'}>
|
||||
return (<div id="loading-cover" className={'md:-mt-20 flex-grow dark:text-white text-black animate__animated animate__fadeIn flex-grow flex flex-col justify-center z-10 w-full h-screen container mx-auto'}>
|
||||
<div className="mx-auto">
|
||||
<FontAwesomeIcon icon={faSpinner} spin size='2x'/>
|
||||
</div>
|
||||
|
||||
@@ -28,7 +28,7 @@ const MenuButtonGroup = ({ allowCollapse = false }) => {
|
||||
if (link.show) {
|
||||
const selected = (router.pathname === link.to) || (router.asPath === link.to)
|
||||
return <Link key={link.id + link.icon} title={link.to} href={link.to} >
|
||||
<a className={'py-1 px-5 mx-2 hover:bg-blue-400 hover:text-white hover:shadow-lg cursor-pointer duration-100 font-light flex flex-nowrap items-center ' +
|
||||
<a className={'py-1 my-1 px-5 mx-2 rounded-xl hover:bg-blue-400 hover:text-white hover:shadow-lg cursor-pointer duration-100 font-light flex flex-nowrap items-center ' +
|
||||
(selected ? 'bg-blue-500 text-white ' : ' ')} >
|
||||
<div className='my-auto justify-center flex '>
|
||||
<FontAwesomeIcon icon={link.icon} />
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
import React from 'react'
|
||||
import MenuButtonGroup from '@/components/MenuButtonGroup'
|
||||
import InfoCard from '@/components/InfoCard'
|
||||
import TagGroups from '@/components/TagGroups'
|
||||
import LatestPostsGroup from '@/components/LatestPostsGroup'
|
||||
import CategoryGroup from '@/components/CategoryGroup'
|
||||
import InfoCard from '@/components/InfoCard'
|
||||
import MenuButtonGroup from '@/components/MenuButtonGroup'
|
||||
import SearchInput from '@/components/SearchInput'
|
||||
import Link from 'next/link'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import Toc from '@/components/Toc'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import { faAngleDoubleRight, faThList } from '@fortawesome/free-solid-svg-icons'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faAngleDoubleRight, faArchive, faTags, faThList } from '@fortawesome/free-solid-svg-icons'
|
||||
import Link from 'next/link'
|
||||
import React from 'react'
|
||||
|
||||
/**
|
||||
* 侧边平铺
|
||||
@@ -26,36 +24,26 @@ import { faAngleDoubleRight, faArchive, faTags, faThList } from '@fortawesome/fr
|
||||
const SideArea = ({ title, tags, currentTag, post, posts, categories, currentCategory, currentSearch }) => {
|
||||
const { locale } = useGlobal()
|
||||
|
||||
return <aside id='sidebar' className='w-72 z-10 scroll-hidden h-full'>
|
||||
return <aside id='sidebar' className='scroll-hidden h-full w-72 z-10'>
|
||||
|
||||
<section className='hidden lg:block bg-white dark:bg-gray-800 rounded-xl hover:shadow-2xl duration-200 py-8 shadow-lg'>
|
||||
<InfoCard />
|
||||
</section>
|
||||
<div className={(!post ? 'sticky top-5' : '') + ' pb-4'}>
|
||||
|
||||
<div className={(!post ? 'sticky top-2' : '') + ' pb-4'}>
|
||||
<section className='hidden lg:block bg-white dark:bg-gray-800 rounded-xl hover:shadow-2xl duration-200 py-8 shadow-lg'>
|
||||
<InfoCard />
|
||||
</section>
|
||||
|
||||
{/* 菜单 */}
|
||||
<section className='hidden lg:block mt-5 py-4 rounded-xl shadow-lg bg-white dark:bg-gray-800 hover:shadow-2xl duration-200'>
|
||||
<section className='hidden lg:block mt-5 py-5 rounded-xl shadow-lg bg-white dark:bg-gray-800 hover:shadow-2xl duration-200'>
|
||||
<MenuButtonGroup allowCollapse={true} />
|
||||
<div className='px-5 pt-2'>
|
||||
<SearchInput currentTag={currentTag} currentSearch={currentSearch} />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 最新文章 */}
|
||||
{posts && (
|
||||
<section className='rounded-xl shadow-lg py-4 mt-5 bg-white dark:bg-gray-800 hover:shadow-2xl duration-200'>
|
||||
<div className='text-sm pb-2 px-5 flex flex-nowrap justify-between'>
|
||||
<div className='font-light text-gray-600 dark:text-gray-200'><FontAwesomeIcon icon={faArchive} className='mr-2' />{locale.COMMON.LATEST_POSTS}</div>
|
||||
</div>
|
||||
<LatestPostsGroup posts={posts} />
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* 分类 */}
|
||||
{categories && (
|
||||
{!post && categories && (
|
||||
<section className='rounded-xl shadow-lg py-4 mt-5 bg-white dark:bg-gray-800 hover:shadow-2xl duration-200'>
|
||||
<div className='text-sm px-5 flex flex-nowrap justify-between font-light'>
|
||||
<div className='text-sm px-5 mb-2 flex flex-nowrap justify-between font-light'>
|
||||
<div className='pb-1 text-gray-600 dark:text-gray-200'><FontAwesomeIcon icon={faThList} className='mr-2' />{locale.COMMON.CATEGORY}</div>
|
||||
<Link href='/category' passHref>
|
||||
<a className='text-gray-400 hover:text-black dark:text-gray-400 dark:hover:text-white hover:underline cursor-pointer'>
|
||||
@@ -67,26 +55,10 @@ const SideArea = ({ title, tags, currentTag, post, posts, categories, currentCat
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* 标签云 */}
|
||||
{tags && (
|
||||
<section className='rounded-xl shadow-lg py-4 mt-5 bg-white dark:bg-gray-800 hover:shadow-2xl duration-200'>
|
||||
<div className='text-sm pb-1 px-5 flex flex-nowrap justify-between font-light dark:text-gray-200'>
|
||||
<div className='text-gray-600 dark:text-gray-200'><FontAwesomeIcon icon={faTags} className='mr-2'/>{locale.COMMON.TAGS}</div>
|
||||
<Link href='/tag' passHref>
|
||||
<a className='text-gray-400 hover:text-black dark:hover:text-white hover:underline cursor-pointer'>
|
||||
{locale.COMMON.MORE} <FontAwesomeIcon icon={faAngleDoubleRight} />
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
<div className='px-5 pt-2'>
|
||||
<TagGroups tags={tags} currentTag={currentTag} />
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{post && post.toc && post.toc.length > 1 && (
|
||||
<section id='left-toc' className='sticky pb-20 top-6 rounded-xl shadow-lg bg-white dark:bg-gray-800 hover:shadow-2xl duration-200'>
|
||||
<section id='left-toc' className='sticky top-6 pb-20 rounded-xl shadow-lg bg-white dark:bg-gray-800 hover:shadow-2xl duration-200'>
|
||||
<div className='border-b text-2xl bg-white text-black rounded-t-xl dark:border-gray-700 dark:bg-gray-700 dark:text-white py-6 px-6'>
|
||||
{locale.COMMON.TABLE_OF_CONTENTS}
|
||||
</div>
|
||||
@@ -94,8 +66,6 @@ const SideArea = ({ title, tags, currentTag, post, posts, categories, currentCat
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* <section id='blank' className='bg-white dark:bg-gray-800 py-20' /> */}
|
||||
|
||||
</aside>
|
||||
}
|
||||
export default SideArea
|
||||
|
||||
108
components/SideAreaRight.js
Normal file
108
components/SideAreaRight.js
Normal file
@@ -0,0 +1,108 @@
|
||||
import LatestPostsGroup from '@/components/LatestPostsGroup'
|
||||
import TagGroups from '@/components/TagGroups'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import {
|
||||
faAngleDoubleRight,
|
||||
faArchive,
|
||||
faTags,
|
||||
faThList
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import Link from 'next/link'
|
||||
import React from 'react'
|
||||
import CategoryGroup from './CategoryGroup'
|
||||
|
||||
/**
|
||||
* 侧边平铺
|
||||
* @param tags
|
||||
* @param currentTag
|
||||
* @param post
|
||||
* @param posts
|
||||
* @param categories
|
||||
* @param currentCategory
|
||||
* @param currentSearch
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
const SideAreaRight = ({
|
||||
title,
|
||||
tags,
|
||||
currentTag,
|
||||
post,
|
||||
posts,
|
||||
categories,
|
||||
currentCategory,
|
||||
currentSearch
|
||||
}) => {
|
||||
const { locale } = useGlobal()
|
||||
|
||||
return (
|
||||
<>
|
||||
<section className="rounded-xl shadow-lg py-4 px-2 bg-white dark:bg-gray-800 hover:shadow-2xl duration-200">
|
||||
{/* 展示广告 */}
|
||||
<ins
|
||||
className="adsbygoogle"
|
||||
style={{ display: 'block' }}
|
||||
data-adtest="on"
|
||||
data-ad-client="ca-pub-2708419466378217"
|
||||
data-ad-slot="8807314373"
|
||||
data-ad-format="auto"
|
||||
data-full-width-responsive="true"
|
||||
></ins>
|
||||
</section>
|
||||
|
||||
<div className="sticky top-5">
|
||||
{/* 最新文章 */}
|
||||
{posts && (
|
||||
<section className="rounded-xl shadow-lg py-4 mt-5 bg-white dark:bg-gray-800 hover:shadow-2xl duration-200">
|
||||
<div className="text-sm pb-2 px-5 flex flex-nowrap justify-between">
|
||||
<div className="font-light text-gray-600 dark:text-gray-200">
|
||||
<FontAwesomeIcon icon={faArchive} className="mr-2" />
|
||||
{locale.COMMON.LATEST_POSTS}
|
||||
<span className='text-red-500 text-xs ml-1'>NEW</span>
|
||||
</div>
|
||||
</div>
|
||||
<LatestPostsGroup posts={posts} />
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* 标签云 */}
|
||||
{tags && (
|
||||
<section className="rounded-xl shadow-lg py-4 mt-5 bg-white dark:bg-gray-800 hover:shadow-2xl duration-200">
|
||||
<div className="text-sm pb-1 px-5 flex flex-nowrap justify-between font-light dark:text-gray-200">
|
||||
<div className="text-gray-600 dark:text-gray-200">
|
||||
<FontAwesomeIcon icon={faTags} className="mr-2" />
|
||||
{locale.COMMON.TAGS}
|
||||
</div>
|
||||
<Link href="/tag" passHref>
|
||||
<a className="text-gray-400 hover:text-black dark:hover:text-white hover:underline cursor-pointer">
|
||||
{locale.COMMON.MORE}{' '}
|
||||
<FontAwesomeIcon icon={faAngleDoubleRight} />
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="px-5 pt-2">
|
||||
<TagGroups tags={tags} currentTag={currentTag} />
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* 分类 */}
|
||||
{post && categories && (
|
||||
<section className='rounded-xl shadow-lg py-4 mt-5 bg-white dark:bg-gray-800 hover:shadow-2xl duration-200'>
|
||||
<div className='text-sm px-5 mb-2 flex flex-nowrap justify-between font-light'>
|
||||
<div className='pb-1 text-gray-600 dark:text-gray-200'><FontAwesomeIcon icon={faThList} className='mr-2' />{locale.COMMON.CATEGORY}</div>
|
||||
<Link href='/category' passHref>
|
||||
<a className='text-gray-400 hover:text-black dark:text-gray-400 dark:hover:text-white hover:underline cursor-pointer'>
|
||||
{locale.COMMON.MORE} <FontAwesomeIcon icon={faAngleDoubleRight} />
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
<CategoryGroup currentCategory={currentCategory} categories={categories} />
|
||||
</section>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
export default SideAreaRight
|
||||
@@ -8,7 +8,7 @@
|
||||
const StickyBar = ({ children }) => {
|
||||
if (!children) return <></>
|
||||
return (
|
||||
<div id='sticky-bar' className='sticky flex justify-center top-0 duration-500 z-10 w-full'>
|
||||
<div id='sticky-bar' className='sticky flex justify-center top-14 md:top-0 duration-500 z-10 w-full'>
|
||||
<div className='bg-white dark:bg-gray-800 dark:border-gray-600 w-full px-5 rounded-none md:rounded-xl shadow-xl border overflow-x-auto'>
|
||||
<div className="flex">
|
||||
<div className='z-30 absolute h-12 left-0 flex'>
|
||||
|
||||
@@ -10,7 +10,7 @@ import TagItemMini from '@/components/TagItemMini'
|
||||
const TagGroups = ({ tags, currentTag }) => {
|
||||
if (!tags) return <></>
|
||||
return (
|
||||
<div id='tags-group' className='dark:border-gray-600 w-66'>
|
||||
<div id='tags-group' className='dark:border-gray-600 w-66 space-y-3'>
|
||||
{
|
||||
tags.map(tag => {
|
||||
const selected = tag.name === currentTag
|
||||
|
||||
@@ -5,7 +5,7 @@ import Link from 'next/link'
|
||||
const TagItemMini = ({ tag, selected = false }) => {
|
||||
return <Link key={tag} href={selected ? '/' : `/tag/${encodeURIComponent(tag.name)}`} passHref>
|
||||
<a className={`cursor-pointer inline-block rounded hover:bg-blue-500 hover:text-white duration-200
|
||||
mr-2 my-1 py-1 px-2 text-xs whitespace-nowrap dark:hover:text-white
|
||||
mr-2 py-1 px-2 text-xs whitespace-nowrap dark:hover:text-white
|
||||
${selected
|
||||
? 'text-white dark:text-gray-300 bg-black dark:bg-black dark:hover:bg-gray-900'
|
||||
: `text-gray-600 dark:text-gray-600 hover:shadow-xl dark:border-gray-400 notion-${tag.color}_background `}` }>
|
||||
|
||||
@@ -27,7 +27,7 @@ const TocDrawerButton = (props) => {
|
||||
})
|
||||
|
||||
return (
|
||||
<div id='toc-drawer-button' className='right-5 fixed bottom-60 duration-500 z-40'>
|
||||
<div id='toc-drawer-button' className='right-3 fixed bottom-60 duration-500 z-20'>
|
||||
<div onClick={props.onClick} className={(show ? 'animate__fadeInRight' : 'hidden') + ' py-3 px-3.5 animate__animated glassmorphism rounded-xl cursor-pointer shadow-card' }>
|
||||
<div className='dark:text-gray-200 text-center transform hover:scale-150 duration-200 text-xs' title={locale.POST.TOP} >
|
||||
<FontAwesomeIcon icon={faListOl} />
|
||||
|
||||
56
components/WordCount.js
Normal file
56
components/WordCount.js
Normal file
@@ -0,0 +1,56 @@
|
||||
import { useEffect } from 'react'
|
||||
|
||||
/**
|
||||
* 字数统计
|
||||
* @returns
|
||||
*/
|
||||
export default function WordCount () {
|
||||
useEffect(() => {
|
||||
countWords()
|
||||
})
|
||||
|
||||
return <div id='wordCountWrapper' className='hidden'>
|
||||
本文共<strong id='wordCount'>0</strong>字,阅读需要约<strong id='readTime'>0</strong>分钟
|
||||
</div>
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新字数统计和阅读时间
|
||||
*/
|
||||
function countWords () {
|
||||
if (window) {
|
||||
const articleElement = document.getElementById('notion-article')
|
||||
if (articleElement) {
|
||||
const articleText = deleteHtmlTag(articleElement.innerHTML)
|
||||
const wordCount = fnGetCpmisWords(articleText)
|
||||
// 阅读速度 300-500每分钟
|
||||
document.getElementById('wordCount').innerHTML = wordCount
|
||||
document.getElementById('readTime').innerHTML = Math.floor(wordCount / 400) + 1
|
||||
const wordCountWrapper = document.getElementById('wordCountWrapper')
|
||||
wordCountWrapper.classList.remove('hidden')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 去除html标签
|
||||
function deleteHtmlTag (str) {
|
||||
str = str.replace(/<[^>]+>|&[^>]+;/g, '').trim()// 去掉所有的html标签和 之类的特殊符合
|
||||
return str
|
||||
}
|
||||
|
||||
// 用word方式计算正文字数
|
||||
function fnGetCpmisWords (str) {
|
||||
let sLen = 0
|
||||
try {
|
||||
// eslint-disable-next-line no-irregular-whitespace
|
||||
str = str.replace(/(\r\n+|\s+| +)/g, '龘')
|
||||
// eslint-disable-next-line no-control-regex
|
||||
str = str.replace(/[\x00-\xff]/g, 'm')
|
||||
str = str.replace(/m+/g, '*')
|
||||
str = str.replace(/龘+/g, '')
|
||||
sLen = str.length
|
||||
} catch (e) {
|
||||
|
||||
}
|
||||
return sLen
|
||||
}
|
||||
@@ -1,15 +1,15 @@
|
||||
import CommonHead from '@/components/CommonHead'
|
||||
import FloatDarkModeButton from '@/components/FloatDarkModeButton'
|
||||
import Footer from '@/components/Footer'
|
||||
import JumpToTopButton from '@/components/JumpToTopButton'
|
||||
import LoadingCover from '@/components/LoadingCover'
|
||||
import SideArea from '@/components/SideArea'
|
||||
import SideAreaRight from '@/components/SideAreaRight'
|
||||
import TopNav from '@/components/TopNav'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import throttle from 'lodash.throttle'
|
||||
import PropTypes from 'prop-types'
|
||||
import React, { useCallback, useEffect, useRef } from 'react'
|
||||
import CommonHead from '@/components/CommonHead'
|
||||
import throttle from 'lodash.throttle'
|
||||
import TopNav from '@/components/TopNav'
|
||||
import Footer from '@/components/Footer'
|
||||
import SideArea from '@/components/SideArea'
|
||||
import JumpToTopButton from '@/components/JumpToTopButton'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import LoadingCover from '@/components/LoadingCover'
|
||||
import FloatDarkModeButton from '@/components/FloatDarkModeButton'
|
||||
|
||||
/**
|
||||
* 基础布局 采用左右两侧布局,移动端使用顶部导航栏
|
||||
* @param children
|
||||
@@ -61,34 +61,37 @@ const BaseLayout = ({
|
||||
window.removeEventListener('scroll', scrollTrigger)
|
||||
}
|
||||
}, [])
|
||||
const { onLoading, theme } = useGlobal()
|
||||
const { theme, onLoading } = useGlobal()
|
||||
const targetRef = useRef(null)
|
||||
|
||||
return (
|
||||
<div id='wrapper' className={theme}>
|
||||
return (<div id='wrapper' className={`${theme}`}>
|
||||
|
||||
<CommonHead meta={meta} />
|
||||
|
||||
{/* 顶部导航栏 */}
|
||||
<TopNav tags={tags} post={post} posts={totalPosts} currentSearch={currentSearch} categories={categories} currentCategory={currentCategory} />
|
||||
|
||||
{/* Middle Wrapper */}
|
||||
<div className='flex bg-gray-100 dark:bg-gray-900 xl:px-32 md:px-16 md:pt-20 pb-12'>
|
||||
<div className='hidden lg:block'>
|
||||
<div className='flex max-w-full mx-auto lg:px-16 lg:space-x-10 md:pt-10 pb-12 bg-gray-100 dark:bg-gray-900'>
|
||||
<div id='left' className='hidden lg:block'>
|
||||
<SideArea title={meta.title} post={post} posts={totalPosts} tags={tags} currentSearch={currentSearch} currentTag={currentTag} categories={categories} currentCategory={currentCategory} />
|
||||
</div>
|
||||
|
||||
<div className='flex flex-grow min-h-screen' ref={targetRef}>
|
||||
<div id='center' className='flex flex-grow min-h-screen' ref={targetRef}>
|
||||
{onLoading
|
||||
? <LoadingCover/>
|
||||
: <div className='flex-grow md:pl-10'>
|
||||
: <div className='flex-grow'>
|
||||
{children}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id='right' className='hidden xl:block flex-col w-72'>
|
||||
<SideAreaRight post={post} posts={totalPosts} tags={tags} currentSearch={currentSearch} currentTag={currentTag} categories={categories} currentCategory={currentCategory}/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<Footer title={meta.title}/>
|
||||
|
||||
<JumpToTopButton targetRef={targetRef} showPercent={true} />
|
||||
<FloatDarkModeButton/>
|
||||
</div>
|
||||
|
||||
@@ -96,8 +96,8 @@ const initTheme = (theme, changeTheme) => {
|
||||
saveTheme('light')
|
||||
}
|
||||
}
|
||||
const baseLayoutClass = document.getElementById('wrapper').classList
|
||||
if (!baseLayoutClass.contains(theme)) {
|
||||
const baseLayoutClass = document.getElementById('wrapper')?.classList
|
||||
if (baseLayoutClass && !baseLayoutClass.contains(theme)) {
|
||||
baseLayoutClass.add(theme)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* @param tagOptions tags的下拉选项
|
||||
* @returns {Promise<{}|*[]>}
|
||||
*/
|
||||
export async function getAllTags ({ allPosts, sliceCount = 12, tagOptions }) {
|
||||
export async function getAllTags ({ allPosts, sliceCount = 16, tagOptions }) {
|
||||
if (!allPosts) {
|
||||
return []
|
||||
}
|
||||
@@ -31,7 +31,7 @@ export async function getAllTags ({ allPosts, sliceCount = 12, tagOptions }) {
|
||||
})
|
||||
list.sort((a, b) => b.count - a.count)
|
||||
if (sliceCount && sliceCount > 0) {
|
||||
return list.slice(0, 12)
|
||||
return list.slice(0, sliceCount)
|
||||
} else {
|
||||
return list
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ const Index = ({ allPosts, tags, categories }) => {
|
||||
|
||||
return (
|
||||
<BaseLayout meta={meta} tags={tags} categories={categories}>
|
||||
<div className='md:mt-4 mb-10 pb-20 bg-white md:p-12 p-3 dark:bg-gray-800 rounded-xl shadow-md '>
|
||||
<div className='mb-10 pb-20 bg-white md:p-12 p-3 dark:bg-gray-800 rounded-xl shadow-md '>
|
||||
{Object.keys(archivePosts).map(archiveTitle => (
|
||||
<BlogPostArchive key={archiveTitle} posts={archivePosts[archiveTitle]} archiveTitle={archiveTitle}/>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user