mattery主题

This commit is contained in:
tangly1024
2022-12-25 15:58:28 +08:00
parent 3a45457ddb
commit 461b0f6057
15 changed files with 300 additions and 140 deletions

View File

@@ -13,7 +13,8 @@ export function ThemeSwitch() {
<div id="draggableBox" style={{ left: '10px', top: '90vh' }} className="fixed text-white bg-black z-50 rounded-lg shadow-card">
<div className="p-2 flex items-center">
<i className="fas fa-palette mr-1 cursor-move" />
<div className='uppercase font-sans whitespace-nowrap' >{theme} <i className='fas fa-sync cursor-pointer border-l pl-2' title='click to change theme' alt='click to change theme' onClick={switchTheme} /></div>
<div className='uppercase font-sans whitespace-nowrap cursor-pointer ' onClick={switchTheme}> {theme}
<i className='fas fa-arrows cursor-move pl-2' title='click to change theme' alt='click to change theme' /></div>
</div>
</div>
</Draggable>

View File

@@ -11,6 +11,7 @@ import { useGlobal } from '@/lib/global'
import BLOG from '@/blog.config'
import AOS from 'aos'
import 'aos/dist/aos.css' // You can also use <link> for styles
import FloatDarkModeButton from './components/FloatDarkModeButton'
/**
* 基础布局 采用左右两侧布局,移动端使用顶部导航栏
@@ -46,39 +47,41 @@ const LayoutBase = props => {
AOS.init()
return (
<div id="outer-wrapper" className="min-h-screen flex flex-col justify-between bg-hexo-background-gray dark:bg-black w-full overflow-hidden">
<div id="outer-wrapper" className="min-h-screen flex flex-col justify-between bg-hexo-background-gray dark:bg-black w-full overflow-hidden">
<CommonHead meta={meta} siteInfo={siteInfo}/>
<CommonHead meta={meta} siteInfo={siteInfo} />
<TopNav {...props} />
<TopNav {...props} />
{headerSlot}
{headerSlot}
<main id="wrapper" className="flex-1 w-full py-8 md:px-8 lg:px-24">
<div id="container-inner" className="w-full mx-auto lg:flex lg:space-x-4 justify-center">
{onLoading ? <LoadingCover /> : children}
<main id="wrapper" className="flex-1 w-full py-8 md:px-8 lg:px-24">
<div id="container-inner" className="w-full max-w-4xl mx-auto lg:flex lg:space-x-4 justify-center">
{onLoading ? <LoadingCover /> : children}
</div>
</main>
{/* 左下角悬浮 */}
<div className="bottom-4 -left-14 fixed justify-end z-40">
<Live2D />
</div>
<div className="bottom-24 right-2 fixed justify-end z-20">
<FloatDarkModeButton />
</div>
{/* 右下角悬浮 */}
<div className="bottom-12 right-2 fixed justify-end z-20">
<div className={
(show ? 'animate__animated ' : 'hidden') +
' animate__fadeInUp justify-center duration-300 animate__faster flex flex-col items-center cursor-pointer '
}
>
<JumpToTopButton />
</div>
</div>
<Footer title={siteInfo?.title || BLOG.TITLE} />
</div>
</main>
{/* 左下角悬浮 */}
<div className="bottom-4 -left-14 fixed justify-end z-40">
<Live2D />
</div>
{/* 右下角悬浮 */}
<div className="bottom-12 right-2 fixed justify-end z-20">
<div
className={
(show ? 'animate__animated ' : 'hidden') +
' animate__fadeInUp justify-center duration-300 animate__faster flex flex-col items-center cursor-pointer '
}
>
<JumpToTopButton />
</div>
</div>
<Footer title={siteInfo?.title || BLOG.TITLE} />
</div>
)
}

View File

@@ -40,7 +40,7 @@ export const LayoutSearch = props => {
return (
<LayoutBase {...props} currentSearch={currentSearch}>
{!currentSearch && <>
<div className="my-6 px-2">
<div className="my-6 px-2 mt-12">
<SearchInput cRef={cRef} {...props} />
{/* 分类 */}
<Card className="w-full mt-4">

View File

@@ -10,6 +10,7 @@ import NotionPage from '@/components/NotionPage'
import ArticleAdjacent from './components/ArticleAdjacent'
import ArticleCopyright from './components/ArticleCopyright'
import { isBrowser } from '@/lib/utils'
import { ArticleInfo } from './components/ArticleInfo'
export const LayoutSlug = props => {
const { post, lock, validPassword } = props
@@ -17,79 +18,83 @@ export const LayoutSlug = props => {
if (!post) {
return <LayoutBase
headerSlot={<HeaderArticle {...props} />}
{...props}
showCategory={false}
showTag={false}
></LayoutBase>
headerSlot={<HeaderArticle {...props} />}
{...props}
showCategory={false}
showTag={false}
></LayoutBase>
}
const targetRef = isBrowser() ? document.getElementById('container') : null
const floatSlot = <>
{post?.toc?.length > 1 && <div className="block lg:hidden">
<TocDrawerButton
onClick={() => {
drawerRight?.current?.handleSwitchVisible()
}}
/>
</div>}
<JumpToCommentButton />
</>
{post?.toc?.length > 1 && <div className="block lg:hidden">
<TocDrawerButton
onClick={() => {
drawerRight?.current?.handleSwitchVisible()
}}
/>
</div>}
<JumpToCommentButton />
</>
return (
<LayoutBase
headerSlot={<HeaderArticle {...props} />}
{...props}
showCategory={false}
showTag={false}
floatSlot={floatSlot}
>
<div>
<div className="-mt-32 rounded-md mx-3 lg:shadow-sm lg:hover:shadow lg:border lg:rounded-xl lg:px-2 lg:py-4 bg-white dark:bg-hexo-black-gray dark:border-black">
{lock && <ArticleLock validPassword={validPassword} />}
<LayoutBase
headerSlot={<HeaderArticle {...props} />}
{...props}
showCategory={false}
showTag={false}
floatSlot={floatSlot}
>
<div className='inner-wrapper drop-shadow-xl'>
<div className="-mt-32 rounded-md mx-3 lg:border lg:rounded-xl lg:px-2 lg:py-4 bg-white dark:bg-hexo-black-gray dark:border-black">
{lock && <ArticleLock validPassword={validPassword} />}
{!lock && <div id="container" className="overflow-x-auto flex-grow md:w-full ">
{!lock && <div id="container" className="overflow-x-auto flex-grow md:w-full ">
{/* <ArticleInfo */}
<div className='px-5'>
<ArticleInfo post={post} />
</div>
<article itemScope itemType="https://schema.org/Movie" className="subpixel-antialiased" >
{/* Notion文章主体 */}
<section id='notion-article' className='px-5 justify-center mx-auto max-w-2xl lg:max-w-full'>
{post && <NotionPage post={post} />}
</section>
<hr />
<section className="px-1 py-2 my-1 text-sm font-light overflow-auto text-gray-600 dark:text-gray-400">
{/* 文章内嵌广告 */}
<ins className="adsbygoogle"
style={{ display: 'block', textAlign: 'center' }}
data-adtest="on"
data-ad-layout="in-article"
data-ad-format="fluid"
data-ad-client="ca-pub-2708419466378217"
data-ad-slot="3806269138" />
</section>
<article itemScope itemType="https://schema.org/Movie" className="subpixel-antialiased" >
{/* Notion文章主体 */}
<section id='notion-article' className='px-5 justify-center mx-auto max-w-2xl lg:max-w-full'>
{post && <NotionPage post={post} />}
</section>
{post.type === 'Post' && <ArticleCopyright {...props} /> }
<section className="px-1 py-2 my-1 text-sm font-light overflow-auto text-gray-600 dark:text-gray-400">
{/* 文章内嵌广告 */}
<ins className="adsbygoogle"
style={{ display: 'block', textAlign: 'center' }}
data-adtest="on"
data-ad-layout="in-article"
data-ad-format="fluid"
data-ad-client="ca-pub-2708419466378217"
data-ad-slot="3806269138" />
</section>
</article>
{post.type === 'Post' && <ArticleCopyright {...props} />}
<hr className='border-dashed' />
</article>
{/* 评论互动 */}
<div className="duration-200 overflow-x-auto dark:bg-hexo-black-gray px-3">
<Comment frontMatter={post} />
</div>
</div>}
</div>
<hr className='border-dashed' />
<div className='px-3'>
{post.type === 'Post' && <ArticleAdjacent {...props} /> }
</div>
{/* 评论互动 */}
<div className="duration-200 overflow-x-auto dark:bg-hexo-black-gray px-3">
<Comment frontMatter={post} />
</div>
</div>}
</div>
</div>
<div className='block lg:hidden'>
<TocDrawer post={post} cRef={drawerRight} targetRef={targetRef} />
</div>
{post.type === 'Post' && <ArticleAdjacent {...props} />}
</LayoutBase>
</div>
<div className='block lg:hidden'>
<TocDrawer post={post} cRef={drawerRight} targetRef={targetRef} />
</div>
</LayoutBase>
)
}

View File

@@ -3,24 +3,38 @@ import BlogPostListScroll from './components/BlogPostListScroll'
import BlogPostListPage from './components/BlogPostListPage'
import LayoutBase from './LayoutBase'
import React from 'react'
import Link from 'next/link'
import HeaderArticle from './components/HeaderArticle'
import { useGlobal } from '@/lib/global'
import TagItemMiddle from './components/TagItemMiddle'
export const LayoutTag = (props) => {
const tag = props.tags.find((t) => {
return t.name === props.tag
})
const { tags, tag } = props
return <LayoutBase {...props}>
{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>
const { locale } = useGlobal()
return <LayoutBase {...props} headerSlot={<HeaderArticle {...props} />} >
<div className='inner-wrapper drop-shadow-xl'>
<div className="-mt-32 rounded-md mx-3 px-5 lg:border lg:rounded-xl lg:px-2 lg:py-4 bg-white dark:bg-hexo-black-gray dark:border-black">
<div className="dark:text-gray-200 py-5 text-center text-2xl">
<i className="fas fa-tags" /> {locale.COMMON.TAGS}
</div>
<div id="tags-list" className="duration-200 flex flex-wrap justify-center pb-12">
{tags.map(e => {
const selected = tag === e.name
return (
<div key={e.id} className="p-2">
<TagItemMiddle key={e.id} tag={e} selected={selected} />
</div>
)
})}
</div>
</div>
)}
</div>
{BLOG.POST_LIST_STYLE === 'page' ? <BlogPostListPage {...props} /> : <BlogPostListScroll {...props} />}
</LayoutBase>
}

View File

@@ -1,28 +1,32 @@
import { useGlobal } from '@/lib/global'
import Card from './components/Card'
import TagItemMini from './components/TagItemMini'
import HeaderArticle from './components/HeaderArticle'
import TagItemMiddle from './components/TagItemMiddle'
import LayoutBase from './LayoutBase'
export const LayoutTagIndex = props => {
const { tags } = props
const { locale } = useGlobal()
return (
<LayoutBase {...props}>
<Card className='w-full'>
<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>
</LayoutBase>
<LayoutBase {...props} headerSlot={<HeaderArticle {...props} />} >
<div className='inner-wrapper drop-shadow-xl'>
<div className="-mt-32 rounded-md mx-3 px-5 lg:border lg:rounded-xl lg:px-2 lg:py-4 bg-white dark:bg-hexo-black-gray dark:border-black">
<div className="dark:text-gray-200 py-5 text-center text-2xl">
<i className="fas fa-tags" /> {locale.COMMON.TAGS}
</div>
<div id="tags-list" className="duration-200 flex flex-wrap justify-center pb-12">
{tags.map(tag => {
return (
<div key={tag.name} className="p-2">
<TagItemMiddle key={tag.name} tag={tag} />
</div>
)
})}
</div>
</div>
</div>
</LayoutBase>
)
}

View File

@@ -10,7 +10,7 @@ export default function ArticleAdjacent ({ prev, next }) {
if (!prev || !next || !CONFIG_MATERY.ARTICLE_ADJACENT) {
return <></>
}
return <section className='flex-col py-3 space-y-3 text-gray-800 items-center text-xs md:text-sm md:flex md:flex-row md:space-y-0 md:space-x-4 justify-between m-1 '>
return <section className='flex flex-col justify-between p-3 text-gray-800 items-center text-xs md:text-sm md:flex-row md:gap-2 '>
<BlogPostCard post={prev}/>
<BlogPostCard post={next}/>

View File

@@ -0,0 +1,46 @@
import Link from 'next/link'
import { useGlobal } from '@/lib/global'
import formatDate from '@/lib/formatDate'
import TagItemMiddle from './TagItemMiddle'
import WordCount from './WordCount'
export const ArticleInfo = (props) => {
const { post } = props
const { locale } = useGlobal()
const date = formatDate(post?.date?.start_date || post?.createdTime, locale.LOCALE)
return <section className='mb-3'>
<div className='my-3'>
{post.tagItems && (
<div className="flex flex-nowrap overflow-x-auto">
{post.tagItems.map(tag => (
<TagItemMiddle key={tag.name} tag={tag} />
))}
</div>
)}
</div>
<div className='flex flex-wrap gap-3 mt-5 text-sm'>
{post?.type !== 'Page' && (<>
<Link
href={`/archive#${post?.date?.start_date?.substr(0, 7)}`}
passHref
>
<a className="cursor-pointer whitespace-nowrap">
<i className='far fa-calendar-minus fa-fw'/>发布日期: {date}
</a>
</Link>
<span className='whitespace-nowrap'>
<i className='far fa-calendar-check fa-fw' /> 更新日期: {post.lastEditedTime}
</span>
<span className="hidden busuanzi_container_page_pv font-light mr-2">
<i className='mr-1 fas fa-eye' />
<span className="busuanzi_value_page_pv" />
</span>
<WordCount />
</>)}
</div>
</section>
}

View File

@@ -6,6 +6,7 @@ import CONFIG_MATERY from '../config_matery'
const BlogPostCard = ({ post, showSummary }) => {
const showPreview = CONFIG_MATERY.POST_LIST_PREVIEW && post.blockMap
console.log('文章', post)
return (
<div
data-aos="fade-up"
@@ -22,7 +23,7 @@ const BlogPostCard = ({ post, showSummary }) => {
<div key={post.id} className="flex flex-col justify-between h-96">
{/* 头部图片 填充卡片 */}
{CONFIG_MATERY.POST_LIST_COVER && !showPreview && post?.page_cover && !post.results && (
{CONFIG_MATERY.POST_LIST_COVER && !showPreview && post?.page_cover && (
<Link href={`${BLOG.SUB_PATH}/${post.slug}`} passHref>
<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 */}
@@ -31,7 +32,7 @@ const BlogPostCard = ({ post, showSummary }) => {
alt={post.title}
className="opacity-50 h-full w-full hover:scale-125 rounded-t-md transform object-cover duration-500"
/>
<span className='absolute bottom-0 left-0 text-white p-6 text-2xl' > {post.title}</span>
<span className='absolute bottom-0 left-0 text-white p-6 text-2xl replace' > {post.title}</span>
</div>
</Link>
)}
@@ -46,6 +47,14 @@ const BlogPostCard = ({ post, showSummary }) => {
{post.summary}
</p>
)}
{/* 搜索结果 */}
{post.results && (
<p className="mt-4 replace text-gray-700 dark:text-gray-300 text-sm font-light leading-7">
{post.results.map(r => (
<span key={r}>{r}</span>
))}
</p>
)}
<div className='text-gray-800 justify-between flex my-2'>
<Link

View File

@@ -1,7 +1,7 @@
const Card = ({ children, headerSlot, className }) => {
return <div className={className}>
<>{headerSlot}</>
<section className="shadow hover:shadow dark:text-gray-300 border dark:border-black rounded-xl px-2 py-4 bg-white dark:bg-hexo-black-gray lg:duration-100">
<section className="drop-shadow-xl dark:text-gray-300 border dark:border-black rounded-xl px-2 py-4 bg-white dark:bg-hexo-black-gray lg:duration-100">
{children}
</section>
</div>

View File

@@ -2,12 +2,13 @@ import { useGlobal } from '@/lib/global'
import { saveDarkModeToCookies } from '@/lib/theme'
import CONFIG_MATERY from '../config_matery'
export default function FloatDarkModeButton () {
export default function FloatDarkModeButton() {
const { isDarkMode, updateDarkMode } = useGlobal()
if (!CONFIG_MATERY.WIDGET_DARK_MODE) {
return <></>
}
const { isDarkMode, updateDarkMode } = useGlobal()
// 用户手动设置主题
const handleChangeDarkMode = () => {
const newStatus = !isDarkMode
@@ -19,12 +20,13 @@ export default function FloatDarkModeButton () {
}
return (
<div
onClick={handleChangeDarkMode}
className={'justify-center items-center w-7 h-7 text-center transform hover:scale-105 duration-200'
}
>
<i id="darkModeButton" className={`${isDarkMode ? 'fa-sun' : 'fa-moon'} fas text-xs`}/>
</div>
<div
onClick={handleChangeDarkMode}
className={'justify-center items-center text-center'
}
>
<i id="darkModeButton" className={`${isDarkMode ? 'fa-sun' : 'fa-moon'} fas
text-2xl text-white bg-indigo-700 px-3 py-2.5 rounded-full dark:bg-black cursor-pointer`} />
</div>
)
}

View File

@@ -1,8 +1,6 @@
export default function HeaderArticle({ post, siteInfo }) {
if (!post) {
return <></>
}
const headerImage = post?.page_cover ? `url("${post.page_cover}")` : `url("${siteInfo?.pageCover}")`
const headerImage = post?.page_cover ? `url("${post?.page_cover}")` : `url("${siteInfo?.pageCover}")`
const title = post?.title
return (
<div
id="header"
@@ -10,9 +8,8 @@ export default function HeaderArticle({ post, siteInfo }) {
style={{ backgroundImage: headerImage }}
>
<div className="flex flex-col h-80 justify-center ">
{/* 文章Title */}
<div className="font-bold text-xl shadow-text flex justify-center text-center text-white dark:text-white ">
{post.title}
{title}
</div>
</div>
</div>

View File

@@ -30,7 +30,7 @@ const SideBar = (props) => {
<div className="h-48 w-full bg-indigo-700">
<div className='mx-5 pt-6'>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img src={siteInfo?.icon} className='rounded-full' width={80} alt={BLOG.AUTHOR} />
<img src={siteInfo?.icon} className='cursor-pointer rounded-full' width={80} alt={BLOG.AUTHOR} />
<div className='text-white text-xl my-1'>{siteInfo?.title}</div>
<div className='text-xs my-1 text-gray-300'>{siteInfo?.description}</div>
</div>

View File

@@ -0,0 +1,14 @@
import Link from 'next/link'
const TagItemMiddle = ({ tag, selected = false }) => {
return <Link key={tag} href={selected ? '/' : `/tag/${encodeURIComponent(tag.name)}`} passHref>
<a className={`cursor-pointer inline-block rounded-xl hover:text-white duration-200
mr-2 py-0.5 px-2 text-md whitespace-nowrap text-white ${selected ? 'bg-black' : 'bg-indigo-700'}`}>
<div className='font-light'>
{selected && <i className='mr-1 fas fa-tag' />}
{tag.name + (tag.count ? `(${tag.count})` : '')} </div>
</a>
</Link>
}
export default TagItemMiddle

View File

@@ -0,0 +1,65 @@
import { useEffect } from 'react'
/**
* 字数统计
* @returns
*/
export default function WordCount() {
useEffect(() => {
countWords()
})
return <span id='wordCountWrapper' className='flex gap-3 font-light'>
<span className='flex whitespace-nowrap'>
<i className='mr-1 fas fa-file-word' />
<span>文章字数</span>
<span id='wordCount'>0</span></span>
<span className='flex whitespace-nowrap'>
<i className='mr-1 fas fa-clock' />
<span>阅读时长:</span>
<span id='readTime'>0</span>
</span>
</span>
}
/**
* 更新字数统计和阅读时间
*/
function countWords() {
const articleText = deleteHtmlTag(document.getElementById('notion-article')?.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) {
if (!str) {
return ''
}
str = str.replace(/<[^>]+>|&[^>]+;/g, '').trim()// 去掉所有的html标签和&nbsp;之类的特殊符合
return str
}
// 用word方式计算正文字数
function fnGetCpmisWords(str) {
if (!str) {
return 0
}
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
}