移动端适配

This commit is contained in:
tangly1024
2022-01-16 18:01:30 +08:00
parent 1feba5bc9f
commit 7c68d1aad0
16 changed files with 308 additions and 44 deletions

View File

@@ -1,7 +1,61 @@
import BLOG from '@/blog.config'
import { useGlobal } from '@/lib/global'
import { useEffect } from 'react'
import BlogArchiveItem from './components/BlogPostArchive'
import LayoutBase from './LayoutBase'
export const LayoutArchive = (props) => {
return <LayoutBase {...props}>
Archive Page
const { locale } = useGlobal()
const { posts } = props
// 深拷贝
const postsSortByDate = Object.create(posts)
// 时间排序
postsSortByDate.sort((a, b) => {
const dateA = new Date(a?.date.start_date || a.createdTime)
const dateB = new Date(b?.date.start_date || b.createdTime)
return dateB - dateA
})
const meta = {
title: `${locale.NAV.ARCHIVE} | ${BLOG.title}`,
description: BLOG.description,
type: 'website'
}
const archivePosts = {}
postsSortByDate.forEach(post => {
const date = post.date.start_date.slice(0, 7)
if (archivePosts[date]) {
archivePosts[date].push(post)
} else {
archivePosts[date] = [post]
}
})
useEffect(() => {
if (window) {
const anchor = window.location.hash
if (anchor) {
setTimeout(() => {
const anchorElement = document.getElementById(anchor.substring(1))
if (anchorElement) {
anchorElement.scrollIntoView({ block: 'start', behavior: 'smooth' })
}
}, 300)
}
}
}, [])
return <LayoutBase {...props} meta={meta}>
<div className="mb-10 pb-20 bg-white md:p-12 p-3 dark:bg-gray-800 shadow-md min-h-full">
{Object.keys(archivePosts).map(archiveTitle => (
<BlogArchiveItem
key={archiveTitle}
posts={archivePosts[archiveTitle]}
archiveTitle={archiveTitle}
/>
))}
</div>
</LayoutBase>
}

View File

@@ -1,4 +1,5 @@
import CommonHead from '@/components/CommonHead'
import TopNav from './components/TopNav'
import AsideLeft from './components/AsideLeft'
/**
@@ -15,27 +16,23 @@ import AsideLeft from './components/AsideLeft'
* @returns {JSX.Element}
* @constructor
*/
const LayoutBase = ({
children,
headerSlot,
tags,
meta,
post,
postCount,
sideBarSlot,
floatSlot,
rightAreaSlot,
currentSearch,
currentCategory,
currentTag,
categories
}) => {
const LayoutBase = (props) => {
const {
children,
headerSlot,
tags,
meta,
currentCategory,
currentTag,
categories
} = props
return (<>
<CommonHead meta={meta} />
<div className='flex flex-between'>
<TopNav {...props}/>
<div className='flex'>
<AsideLeft tags={tags} currentTag={currentTag} categories={categories} currentCategory={currentCategory}/>
<main id='wrapper' className='flex flex-grow py-8 justify-center'>
<div className='2xl:max-w-6xl md:max-w-3xl max-w-md w-full'>
<main id='wrapper' className='flex w-full py-8 justify-center'>
<div className='2xl:max-w-6xl md:max-w-3xl w-full'>
<div> {headerSlot} </div>
<div>{children}</div>
</div>

View File

@@ -1,7 +1,8 @@
import BlogListPage from './components/BlogListPage'
import LayoutBase from './LayoutBase'
export const LayoutCategory = (props) => {
return <LayoutBase {...props}>
Category
<BlogListPage page={props.page} posts={props.posts} postCount={props.postCount} />
</LayoutBase>
}

View File

@@ -1,7 +1,32 @@
import BLOG from '@/blog.config'
import { useGlobal } from '@/lib/global'
import { faFolder, faTh } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import Link from 'next/link'
import LayoutBase from './LayoutBase'
export const LayoutCategoryIndex = (props) => {
return <LayoutBase {...props}>
Category
</LayoutBase>
const { locale } = useGlobal()
const { categories } = props
const meta = {
title: `${locale.COMMON.CATEGORY} | ${BLOG.title}`,
description: BLOG.description,
type: 'website'
}
return <LayoutBase {...props} meta={meta}>
<div className='bg-white dark:bg-gray-700 px-10 py-10 shadow'>
<div className='dark:text-gray-200 mb-5'>
<FontAwesomeIcon icon={faTh} className='mr-4' />{locale.COMMON.CATEGORY}:
</div>
<div id='category-list' className='duration-200 flex flex-wrap'>
{Object.keys(categories).map(category => {
return <Link key={category} href={`/category/${category}`} passHref>
<div
className={'hover:text-black dark:hover:text-white dark:text-gray-300 dark:hover:bg-gray-600 px-5 cursor-pointer py-2 hover:bg-gray-100'}>
<FontAwesomeIcon icon={faFolder} className='mr-4' />{category}({categories[category]})
</div>
</Link>
})}
</div>
</div> </LayoutBase>
}

View File

@@ -1,7 +1,8 @@
import BlogListPage from './components/BlogListPage'
import LayoutBase from './LayoutBase'
export const LayoutTag = (props) => {
return <LayoutBase {...props}>
Tag - {props.tag}
<BlogListPage {...props} />
</LayoutBase>
}

View File

@@ -1,7 +1,27 @@
import BLOG from '@/blog.config'
import { useGlobal } from '@/lib/global'
import { faTag } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import TagItem from './components/TagItem'
import LayoutBase from './LayoutBase'
export const LayoutTagIndex = (props) => {
return <LayoutBase {...props}>
Tag - {props.tag}
const { locale } = useGlobal()
const { tags } = props
const meta = {
title: `${locale.COMMON.TAGS} | ${BLOG.title}`,
description: BLOG.description,
type: 'website'
}
return <LayoutBase {...props} meta={meta}>
<div className='bg-white dark:bg-gray-700 px-10 py-10 shadow'>
<div className='dark:text-gray-200 mb-5'><FontAwesomeIcon icon={faTag} className='mr-4'/>{locale.COMMON.TAGS}:</div>
<div id='tags-list' className='duration-200 flex flex-wrap'>
{ tags.map(tag => {
return <div key={tag.name} className='p-2'><TagItem key={tag.name} tag={tag} /></div>
}) }
</div>
</div>
</LayoutBase>
}

View File

@@ -13,12 +13,12 @@ export default function ArticleAround ({ prev, next }) {
}
return <section className='text-gray-800 h-28 flex items-center justify-between space-x-5 my-4'>
<Link href={`/article/${prev.slug}`} passHref>
<a className='text-sm cursor-pointer justify-center items-center flex w-full h-full bg-gray-400 bg-opacity-50 hover:bg-gray-700 hover:text-white duration-300'>
<a className='text-sm cursor-pointer justify-center items-center flex w-full h-full bg-gray-400 bg-opacity-40 hover:bg-gray-700 hover:text-white duration-300'>
<FontAwesomeIcon icon={faAngleDoubleLeft} className='mr-1' />{prev.title}
</a>
</Link>
<Link href={`/article/${next.slug}`} passHref>
<a className='text-sm cursor-pointer justify-center items-center flex w-full h-full bg-gray-400 bg-opacity-50 hover:bg-gray-700 hover:text-white duration-300'>{next.title}
<a className='text-sm cursor-pointer justify-center items-center flex w-full h-full bg-gray-400 bg-opacity-40 hover:bg-gray-700 hover:text-white duration-300'>{next.title}
<FontAwesomeIcon icon={faAngleDoubleRight} className='ml-1 my-1' />
</a>
</Link>

View File

@@ -13,7 +13,7 @@ import 'prismjs/components/prism-python'
import 'prismjs/components/prism-typescript'
import { useEffect, useRef } from 'react'
import { Code, Collection, CollectionRow, Equation, NotionRenderer } from 'react-notion-x'
import ArticleAround from './ArticleArount'
import ArticleAround from './ArticleAround'
/**
*
@@ -42,17 +42,18 @@ export default function ArticleDetail ({ post, recommendPosts, prev, next }) {
}
})
return (<div id="container" className=" max-w-4xl overflow-x-auto flex-grow mx-auto w-screen md:w-full ">
return (<div id="container" className="max-w-5xl overflow-x-auto flex-grow mx-auto w-screen md:w-full ">
{post.type && !post.type.includes('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' />
</div>
)}
<article itemScope itemType="https://schema.org/Movie"
className="subpixel-antialiased py-10 px-5 lg:pt-24 md:px-24 dark:border-gray-700 bg-white dark:bg-gray-800"
className="subpixel-antialiased py-10 px-5 lg:pt-24 md:px-32 dark:border-gray-700 bg-white dark:bg-gray-800"
>
<header className='animate__slideInDown animate__animated'>
{post.type && !post.type.includes('Page') && post?.page_cover && (
<div className="w-full relative md:flex-shrink-0 overflow-hidden">
<img alt={post.title} src={post?.page_cover} className='object-center w-full' />
</div>
)}
{/* 文章Title */}
<div className="font-bold text-3xl text-black dark:text-white font-serif pt-10">

View File

@@ -7,7 +7,7 @@ import SearchInput from './SearchInput'
import SiteInfo from './SiteInfo'
function AsideLeft ({ tags, currentTag, categories, currentCategory }) {
return <div className='w-72 bg-white min-h-screen px-10 py-14'>
return <div className='w-72 bg-white min-h-screen px-10 py-14 hidden lg:block'>
<Logo />

View File

@@ -8,7 +8,7 @@ import Card from './Card'
const BlogCard = ({ post, showSummary }) => {
const showPreview = CONFIG_FUKA.POST_LIST_PREVIEW && post.blockMap
return (
<Card className='w-full max-w-sm p-2'>
<Card className='w-full lg:max-w-sm p-2'>
<div key={post.id} className='animate__animated animate__fadeIn flex flex-col-reverse justify-between duration-300'>
<div className='p-2 flex flex-col w-full'>

View File

@@ -1,4 +1,5 @@
import BLOG from '@/blog.config'
import { useEffect, useState } from 'react'
import BlogCard from './BlogCard'
import BlogPostListEmpty from './BlogListEmpty'
import PaginationSimple from './PaginationSimple'
@@ -13,14 +14,32 @@ import PaginationSimple from './PaginationSimple'
*/
const BlogListPage = ({ page = 1, posts = [], postCount }) => {
const totalPage = Math.ceil(postCount / BLOG.postsPerPage)
const showNext = page < totalPage
const showNext = page < totalPage && posts.length < postCount
const [colCount, changeCol] = useState(3)
function updateCol () {
if (window.outerWidth > 1200) {
changeCol(3)
} else {
changeCol(1)
}
}
useEffect(() => {
updateCol()
window.addEventListener('resize', updateCol)
return () => {
window.removeEventListener('resize', updateCol)
}
})
if (!posts || posts.length === 0) {
return <BlogPostListEmpty />
} else {
return (
<div id="container">
{/* 文章列表 */}
<div style={{ columnCount: 3 }}>
<div 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

@@ -0,0 +1,32 @@
import React from 'react'
import Link from 'next/link'
import BLOG from '@/blog.config'
/**
* 博客归档
* @param posts 所有文章
* @param archiveTitle 归档标题
* @returns {JSX.Element}
* @constructor
*/
const BlogArchiveItem = ({ posts = [], archiveTitle }) => {
if (!posts || posts.length === 0) {
return <></>
} else {
return <div>
<div className='pt-16 pb-4 text-3xl dark:text-gray-300' id={archiveTitle}>{archiveTitle}</div>
<ul>
{posts.map(post => (
<li key={post.id} className='border-l-2 p-1 text-xs md:text-base items-center hover:scale-x-105 hover:border-gray-500 dark:hover:border-gray-300 dark:border-gray-400 transform duration-500'>
<div name={post?.date?.start_date}><span className='text-gray-400'>{post.date.start_date}</span> &nbsp;
<Link href={`${BLOG.path}/article/${post.slug}`} passHref>
<a className='dark:text-gray-400 dark:hover:text-gray-300 overflow-x-hidden hover:underline cursor-pointer text-gray-600'>{post.title}</a>
</Link>
</div>
</li>
))}
</ul>
</div>
}
}
export default BlogArchiveItem

View File

@@ -0,0 +1,38 @@
import React, { useEffect, useRef } from 'react'
const Collapse = props => {
const collapseRef = useRef(null)
const collapseSection = element => {
const sectionHeight = element.scrollHeight
requestAnimationFrame(function () {
element.style.height = sectionHeight + 'px'
requestAnimationFrame(function () {
element.style.height = 0 + 'px'
})
})
}
const expandSection = element => {
const sectionHeight = element.scrollHeight
element.style.height = sectionHeight + 'px'
const clearTime = setTimeout(() => {
element.style.height = 'auto'
}, 400)
clearTimeout(clearTime)
}
useEffect(() => {
const element = collapseRef.current
if (props.isOpen) {
expandSection(element)
} else {
collapseSection(element)
}
}, [props.isOpen])
return (
<div ref={collapseRef} style={{ height: '0px' }} className='overflow-hidden duration-200'>
{props.children}
</div>
)
}
Collapse.defaultProps = { isOpen: false }
export default Collapse

View File

@@ -0,0 +1,26 @@
import { faTag } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import Link from 'next/link'
import React from 'react'
import { useGlobal } from '@/lib/global'
const TagItem = ({ tag, selected }) => {
const { locale } = useGlobal()
if (!tag) {
<div> { locale.COMMON.NOTAG } </div>
}
return (
<Link href={selected ? '/' : `/tag/${encodeURIComponent(tag.name)}`} passHref>
<li
className={`notion-${tag.color}_background dark:bg-gray-700 list-none cursor-pointer rounded-md
duration-200 mr-1 my-1 px-2 py-1 text-sm whitespace-nowrap
hover:bg-gray-200 dark:hover:bg-gray-800 `}>
<div className='text-gray-600 dark:text-gray-300 dark:hover:text-white'>
{selected && <FontAwesomeIcon icon={faTag} className='mr-1'/>} {`${tag.name} `} {tag.count ? `(${tag.count})` : ''}
</div>
</li>
</Link>
)
}
export default TagItem

View File

@@ -0,0 +1,51 @@
import { faBars, faTimes } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { useState } from 'react'
import Collapse from './Collapse'
import GroupMenu from './GroupMenu'
import Logo from './Logo'
/**
* 顶部导航
* @param {*} param0
* @returns
*/
const TopNav = ({ tags, currentTag, categories, currentCategory, postCount }) => {
const [isOpen, changeShow] = useState(false)
const toggleMenuOpen = () => {
changeShow(!isOpen)
}
return (<div id='top-nav' className='z-40 block lg:hidden'>
{/* 导航栏 */}
<div id='sticky-nav' className={'lg:relative w-full top-0 z-20 transform duration-500'}>
<Collapse isOpen={isOpen}>
<div className='bg-white py-1 px-5'>
<GroupMenu/>
</div>
</Collapse>
<div className='w-full flex justify-between items-center p-4 bg-white'>
{/* 左侧LOGO 标题 */}
<div className='flex flex-none flex-grow-0'>
<Logo/>
</div>
<div className='flex'>
</div>
{/* 右侧功能 */}
<div className='mr-1 flex justify-end items-center text-sm space-x-4 font-serif dark:text-gray-200'>
<div onClick={toggleMenuOpen} className='w-18 cursor-pointer'>
菜单 { isOpen ? <FontAwesomeIcon icon={faTimes} size={'lg'}/> : <FontAwesomeIcon icon={faBars} size={'lg'}/> }
</div>
</div>
</div>
</div>
</div>)
}
export default TopNav

View File

@@ -1,4 +1,4 @@
import React, { useRef } from 'react'
import React from 'react'
import Link from 'next/link'
import BLOG from '@/blog.config'
/**
@@ -9,11 +9,10 @@ import BLOG from '@/blog.config'
* @constructor
*/
const BlogPostArchive = ({ posts = [], archiveTitle }) => {
const targetRef = useRef(null)
if (!posts || posts.length === 0) {
return <></>
} else {
return <div ref={targetRef}>
return <div>
<div className='pt-16 pb-4 text-3xl dark:text-gray-300' id={archiveTitle}>{archiveTitle}</div>
<ul>
{posts.map(post => (