mirror of
https://github.com/d0zingcat/NotionNext.git
synced 2026-05-14 07:26:52 +00:00
@@ -1,2 +1,2 @@
|
||||
# 环境变量 @see https://www.nextjs.cn/docs/basic-features/environment-variables
|
||||
NEXT_PUBLIC_VERSION=3.3.4
|
||||
NEXT_PUBLIC_VERSION=3.3.5
|
||||
74
components/Collapse.js
Normal file
74
components/Collapse.js
Normal file
@@ -0,0 +1,74 @@
|
||||
import React from 'react'
|
||||
|
||||
/**
|
||||
* 折叠面板组件,支持水平折叠、垂直折叠
|
||||
* @param {type:['horizontal','vertical'],isOpen} props
|
||||
* @returns
|
||||
*/
|
||||
const Collapse = props => {
|
||||
const collapseRef = React.useRef(null)
|
||||
const type = props.type || 'vertical'
|
||||
const collapseSection = element => {
|
||||
const sectionHeight = element.scrollHeight
|
||||
const sectionWidth = element.scrollWidth
|
||||
|
||||
requestAnimationFrame(function () {
|
||||
switch (type) {
|
||||
case 'horizontal':
|
||||
element.style.width = sectionWidth + 'px'
|
||||
requestAnimationFrame(function () {
|
||||
element.style.width = 0 + 'px'
|
||||
})
|
||||
break
|
||||
case 'vertical':
|
||||
element.style.height = sectionHeight + 'px'
|
||||
requestAnimationFrame(function () {
|
||||
element.style.height = 0 + 'px'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 展开
|
||||
* @param {*} element
|
||||
*/
|
||||
const expandSection = element => {
|
||||
const sectionHeight = element.scrollHeight
|
||||
const sectionWidth = element.scrollWidth
|
||||
let clearTime = 0
|
||||
switch (type) {
|
||||
case 'horizontal':
|
||||
element.style.width = sectionWidth + 'px'
|
||||
clearTime = setTimeout(() => {
|
||||
element.style.width = 'auto'
|
||||
}, 400)
|
||||
break
|
||||
case 'vertical':
|
||||
element.style.height = sectionHeight + 'px'
|
||||
clearTime = setTimeout(() => {
|
||||
element.style.height = 'auto'
|
||||
}, 400)
|
||||
}
|
||||
|
||||
clearTimeout(clearTime)
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
const element = collapseRef.current
|
||||
if (props.isOpen) {
|
||||
expandSection(element)
|
||||
} else {
|
||||
collapseSection(element)
|
||||
}
|
||||
}, [props.isOpen])
|
||||
|
||||
return (
|
||||
<div ref={collapseRef} style={type === 'vertical' ? { height: '0px' } : { width: '0px' }} className={'overflow-hidden duration-200 ' + props.className }>
|
||||
{props.children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Collapse.defaultProps = { isOpen: false }
|
||||
|
||||
export default Collapse
|
||||
@@ -1,38 +0,0 @@
|
||||
import React, { useEffect, useRef } from 'react'
|
||||
|
||||
const Collapse = props => {
|
||||
const collapseRef = useRef(null)
|
||||
const collapseSection = element => {
|
||||
const sectionWidth = element.scrollWidth
|
||||
requestAnimationFrame(function () {
|
||||
element.style.width = sectionWidth + 'px'
|
||||
requestAnimationFrame(function () {
|
||||
element.style.width = 0 + 'px'
|
||||
})
|
||||
})
|
||||
}
|
||||
const expandSection = element => {
|
||||
const sectionWidth = element.scrollWidth
|
||||
element.style.width = sectionWidth + 'px'
|
||||
const clearTime = setTimeout(() => {
|
||||
element.style.width = 'auto'
|
||||
}, 400)
|
||||
clearTimeout(clearTime)
|
||||
}
|
||||
useEffect(() => {
|
||||
const element = collapseRef.current
|
||||
if (props.isOpen) {
|
||||
expandSection(element)
|
||||
} else {
|
||||
collapseSection(element)
|
||||
}
|
||||
}, [props.isOpen])
|
||||
return (
|
||||
<div ref={collapseRef} style={{ width: '0px' }} className={'overflow-hidden duration-200 ' + props.className}>
|
||||
{props.children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Collapse.defaultProps = { isOpen: false }
|
||||
|
||||
export default Collapse
|
||||
21
components/DarkModeButton.js
Normal file
21
components/DarkModeButton.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import { saveDarkModeToCookies } from '@/lib/theme'
|
||||
|
||||
const DarkModeButton = (props) => {
|
||||
const { isDarkMode, updateDarkMode } = useGlobal()
|
||||
// 用户手动设置主题
|
||||
const handleChangeDarkMode = () => {
|
||||
const newStatus = !isDarkMode
|
||||
saveDarkModeToCookies(newStatus)
|
||||
updateDarkMode(newStatus)
|
||||
const htmlElement = document.getElementsByTagName('html')[0]
|
||||
htmlElement.classList?.remove(newStatus ? 'light' : 'dark')
|
||||
htmlElement.classList?.add(newStatus ? 'dark' : 'light')
|
||||
}
|
||||
|
||||
return <div className={'z-10 duration-200 text-xl cursor-pointer py-2 ' + props.className}>
|
||||
<i id='darkModeButton' className={`hover:scale-125 transform duration-200 fas ${isDarkMode ? 'fa-sun' : 'fa-moon'}`}
|
||||
onClick={handleChangeDarkMode} />
|
||||
</div>
|
||||
}
|
||||
export default DarkModeButton
|
||||
138
components/Draggable.js
Normal file
138
components/Draggable.js
Normal file
@@ -0,0 +1,138 @@
|
||||
import React from 'react'
|
||||
/**
|
||||
* 可拖拽组件
|
||||
*/
|
||||
|
||||
export const Draggable = (props) => {
|
||||
const { children } = props
|
||||
let currentObj, offsetX, offsetY// 初始化变量,定义备用变量
|
||||
|
||||
React.useEffect(() => {
|
||||
const draggableElements = document.getElementsByClassName('draggable')
|
||||
|
||||
// 标准化鼠标事件对象
|
||||
function e(event) { // 定义事件对象标准化函数
|
||||
if (!event) { // 兼容IE浏览器
|
||||
event = window.event
|
||||
event.target = event.srcElement
|
||||
event.layerX = event.offsetX
|
||||
event.layerY = event.offsetY
|
||||
}
|
||||
// 移动端
|
||||
if (event.type === 'touchstart' || event.type === 'touchmove') {
|
||||
event.clientX = event.touches[0].clientX
|
||||
event.clientY = event.touches[0].clientY
|
||||
}
|
||||
|
||||
event.mx = event.pageX || event.clientX + document.body.scrollLeft
|
||||
// 计算鼠标指针的x轴距离
|
||||
event.my = event.pageY || event.clientY + document.body.scrollTop
|
||||
// 计算鼠标指针的y轴距离
|
||||
|
||||
return event // 返回标准化的事件对象
|
||||
}
|
||||
|
||||
// 定义鼠标事件处理函数
|
||||
// document.pointerdown = start
|
||||
document.onmousedown = start
|
||||
document.ontouchstart = start
|
||||
|
||||
function start (event) { // 按下鼠标时,初始化处理
|
||||
if (!draggableElements) return
|
||||
event = e(event)// 获取标准事件对象
|
||||
|
||||
for (const drag of draggableElements) {
|
||||
// 判断鼠标点击的区域是否是拖拽框内
|
||||
if (inDragBox(event, drag)) {
|
||||
currentObj = drag.firstElementChild
|
||||
}
|
||||
}
|
||||
if (currentObj) {
|
||||
if (event.type === 'touchstart') {
|
||||
document.documentElement.style.overflow = 'hidden' // 防止页面一起滚动
|
||||
}
|
||||
offsetX = event.mx - currentObj.offsetLeft
|
||||
offsetY = event.my - currentObj.offsetTop
|
||||
|
||||
document.onmousemove = move// 注册鼠标移动事件处理函数
|
||||
document.ontouchmove = move
|
||||
document.onmouseup = stop// 注册松开鼠标事件处理函数
|
||||
document.ontouchend = stop
|
||||
}
|
||||
}
|
||||
|
||||
function move(event) { // 鼠标移动处理函数
|
||||
event = e(event)
|
||||
if (currentObj) {
|
||||
const left = event.mx - offsetX// 定义拖动元素的x轴距离
|
||||
const top = event.my - offsetY// 定义拖动元素的y轴距离
|
||||
currentObj.style.left = left + 'px'// 定义拖动元素的x轴距离
|
||||
currentObj.style.top = top + 'px'// 定义拖动元素的y轴距离
|
||||
checkInWindow()
|
||||
}
|
||||
}
|
||||
|
||||
function stop(event) { // 松开鼠标处理函数
|
||||
event = e(event)
|
||||
// 释放所有操作对象
|
||||
document.documentElement.style.overflow = 'auto' // 解除页面滚动限制
|
||||
currentObj = document.ontouchmove = document.ontouchend = document.onmousemove = document.onmouseup = null
|
||||
}
|
||||
|
||||
/**
|
||||
* 鼠标是否在可拖拽区域内
|
||||
* @param {*} event
|
||||
* @returns
|
||||
*/
|
||||
function inDragBox(event, drag) {
|
||||
const { clientX, clientY } = event // 鼠标位置
|
||||
const { offsetHeight, offsetWidth, offsetTop, offsetLeft } = drag.firstElementChild // 窗口位置
|
||||
const horizontal = clientX > offsetLeft && clientX < offsetLeft + offsetWidth
|
||||
const vertical = clientY > offsetTop && clientY < offsetTop + offsetHeight
|
||||
|
||||
if (horizontal && vertical) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 若超出窗口则吸附。
|
||||
*/
|
||||
function checkInWindow() {
|
||||
// 检查是否悬浮在窗口内
|
||||
for (const drag of draggableElements) {
|
||||
// 判断鼠标点击的区域是否是拖拽框内
|
||||
const { offsetHeight, offsetWidth, offsetTop, offsetLeft } = drag.firstElementChild
|
||||
const { clientHeight, clientWidth } = document.documentElement
|
||||
if (offsetTop < 0) {
|
||||
drag.firstElementChild.style.top = 0
|
||||
}
|
||||
if (offsetTop > (clientHeight - offsetHeight)) {
|
||||
drag.firstElementChild.style.top = clientHeight - offsetHeight + 'px'
|
||||
}
|
||||
if (offsetLeft < 0) {
|
||||
drag.firstElementChild.style.left = 0
|
||||
}
|
||||
if (offsetLeft > (clientWidth - offsetWidth)) {
|
||||
drag.firstElementChild.style.left = clientWidth - offsetWidth + 'px'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('resize', checkInWindow)
|
||||
|
||||
return () => {
|
||||
return () => {
|
||||
window.removeEventListener('resize', checkInWindow)
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
return <div className='draggable cursor-move'>
|
||||
{children}
|
||||
</div>
|
||||
}
|
||||
|
||||
Draggable.defaultProps = { left: 0, top: 0 }
|
||||
@@ -1,17 +1,22 @@
|
||||
import { useGlobal } from '@/lib/global'
|
||||
|
||||
import React from 'react'
|
||||
import { Draggable } from './Draggable'
|
||||
/**
|
||||
*
|
||||
* @returns 主题切换
|
||||
*/
|
||||
export function ThemeSwitch () {
|
||||
export function ThemeSwitch() {
|
||||
const { theme, switchTheme } = useGlobal()
|
||||
return (
|
||||
<div draggable="true" className="fixed left-4 bottom-12 text-white bg-black rounded z-50">
|
||||
<div className="p-2 cursor-pointer" onClick={switchTheme}>
|
||||
<i className="fas fa-sync mr-1" />
|
||||
切换主题:{theme}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
return (<>
|
||||
<Draggable>
|
||||
<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>
|
||||
</div>
|
||||
</Draggable>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "notion-next",
|
||||
"version": "3.3.4",
|
||||
"version": "3.3.5",
|
||||
"homepage": "https://github.com/tangly1024/NotionNext.git",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
@@ -15,7 +15,7 @@ import JumpToTopButton from './components/JumpToTopButton'
|
||||
const LayoutBase = props => {
|
||||
const { children, meta } = props
|
||||
return (
|
||||
<div className='dark:text-gray-300'>
|
||||
<div className='dark:text-gray-300 bg-white dark:bg-black'>
|
||||
<CommonHead meta={meta} />
|
||||
{/* 顶栏LOGO */}
|
||||
<Header {...props} />
|
||||
@@ -24,7 +24,7 @@ const LayoutBase = props => {
|
||||
<Nav {...props} />
|
||||
|
||||
{/* 主体 */}
|
||||
<div className="w-full bg-white">
|
||||
<div className="w-full">
|
||||
|
||||
<Title {...props} />
|
||||
|
||||
|
||||
@@ -28,9 +28,9 @@ export const ArticleLock = props => {
|
||||
<div className='text-center space-y-3'>
|
||||
<div className='font-bold'>{locale.COMMON.ARTICLE_LOCK_TIPS}</div>
|
||||
<div className='flex'>
|
||||
<input id="password" type='password' className='w-full text-sm pl-5 rounded-l transition focus:shadow-lgfont-light leading-10 text-black bg-white dark:bg-gray-500'></input>
|
||||
<input id="password" type='password' className='w-full text-sm pl-5 rounded-l transition font-light leading-10 text-black dark:bg-gray-500 bg-gray-50'></input>
|
||||
<div onClick={submitPassword} className="px-3 whitespace-nowrap cursor-pointer items-center justify-center py-2 rounded-r duration-300 bg-gray-300" >
|
||||
<i className={'duration-200 cursor-pointer fas fa-key'} > {locale.COMMON.SUBMIT}</i>
|
||||
<i className={'duration-200 cursor-pointer fas fa-key dark:text-black'} > {locale.COMMON.SUBMIT}</i>
|
||||
</div>
|
||||
</div>
|
||||
<div id='tips'>
|
||||
|
||||
@@ -24,19 +24,19 @@ export const BlogList = (props) => {
|
||||
<article key={p.id} className="mb-12" >
|
||||
<h2 className="mb-4">
|
||||
<Link href={`/article/${p.slug}`}>
|
||||
<a className="text-black text-xl md:text-2xl no-underline hover:underline"> {p.title}</a>
|
||||
<a className="text-black dark:text-gray-100 text-xl md:text-2xl no-underline hover:underline"> {p.title}</a>
|
||||
</Link>
|
||||
</h2>
|
||||
|
||||
<div className="mb-4 text-sm text-gray-700">
|
||||
by <a href="#" className="text-gray-700">{BLOG.AUTHOR}</a> on {p.date?.start_date || p.createdTime}
|
||||
<div className="mb-4 text-sm text-gray-700 dark:text-gray-300">
|
||||
by <a href="#" className="text-gray-700 dark:text-gray-300">{BLOG.AUTHOR}</a> on {p.date?.start_date || p.createdTime}
|
||||
<span className="font-bold mx-1"> | </span>
|
||||
<a href="#" className="text-gray-700">{p.category}</a>
|
||||
<a href="#" className="text-gray-700 dark:text-gray-300">{p.category}</a>
|
||||
{/* <span className="font-bold mx-1"> | </span> */}
|
||||
{/* <a href="#" className="text-gray-700">2 Comments</a> */}
|
||||
</div>
|
||||
|
||||
<p className="text-gray-700 leading-normal">
|
||||
<p className="text-gray-700 dark:text-gray-400 leading-normal">
|
||||
{p.summary}
|
||||
</p>
|
||||
{/* 搜索结果 */}
|
||||
|
||||
@@ -1,45 +1,21 @@
|
||||
import BLOG from '@/blog.config'
|
||||
import DarkModeButton from '@/components/DarkModeButton'
|
||||
|
||||
export const Footer = (props) => {
|
||||
const d = new Date()
|
||||
const currentYear = d.getFullYear()
|
||||
const startYear = BLOG.SINCE && BLOG.SINCE !== currentYear && BLOG.SINCE + '-'
|
||||
|
||||
// {/* 页脚 */}
|
||||
// <footer className="font-sans dark:bg-gray-900 flex-shrink-0 justify-center text-center m-auto w-full leading-6 text-sm p-6">
|
||||
// <i className="fas fa-copyright" /> {`${startYear}${currentYear}`}{' '}
|
||||
return <footer className="w-full bg-white px-6 border-t dark:border-hexo-black-gray dark:bg-hexo-black-gray ">
|
||||
<DarkModeButton className='text-center pt-4'/>
|
||||
|
||||
// <br />
|
||||
// <span className="hidden busuanzi_container_site_pv">
|
||||
// <i className="fas fa-eye" />
|
||||
// <span className="px-1 busuanzi_value_site_pv"> </span>{' '}
|
||||
// </span>
|
||||
// <span className="pl-2 hidden busuanzi_container_site_uv">
|
||||
// <i className="fas fa-users" />{' '}
|
||||
// <span className="px-1 busuanzi_value_site_uv"> </span>{' '}
|
||||
// </span>
|
||||
// <br />
|
||||
// <h1>{meta?.title || siteInfo?.title}</h1>
|
||||
// <span className='text-xs font-serif'>
|
||||
// Powered by{' '}
|
||||
// <a
|
||||
// href="https://github.com/tangly1024/NotionNext"
|
||||
// className="underline dark:text-gray-300"
|
||||
// >
|
||||
// NotionNext {BLOG.VERSION}
|
||||
// </a>
|
||||
// .
|
||||
// </span>
|
||||
// </footer>
|
||||
|
||||
return <footer className="w-full bg-white px-6 border-t">
|
||||
<div className="container mx-auto max-w-4xl py-6 flex flex-wrap md:flex-no-wrap justify-between items-center text-sm">
|
||||
©{`${startYear}${currentYear}`} {BLOG.AUTHOR}. All rights reserved.
|
||||
<div className="pt-4 md:p-0 text-center md:text-right text-xs">
|
||||
<div className="container mx-auto max-w-4xl py-6 md:flex flex-wrap md:flex-no-wrap md:justify-between items-center text-sm">
|
||||
<div className='text-center'> ©{`${startYear}${currentYear}`} {BLOG.AUTHOR}. All rights reserved.</div>
|
||||
<div className="md:p-0 text-center md:text-right text-xs">
|
||||
{/* 右侧链接 */}
|
||||
{/* <a href="#" className="text-black no-underline hover:underline">Privacy Policy</a> */}
|
||||
{BLOG.BEI_AN && (<a href="https://beian.miit.gov.cn/" className="text-black no-underline hover:underline ml-4">{BLOG.BEI_AN} </a>)}
|
||||
<span className='text-black no-underline ml-4'>
|
||||
{BLOG.BEI_AN && (<a href="https://beian.miit.gov.cn/" className="text-black dark:text-gray-200 no-underline hover:underline ml-4">{BLOG.BEI_AN} </a>)}
|
||||
<span className='dark:text-gray-200 no-underline ml-4'>
|
||||
Powered by
|
||||
<a href="https://github.com/tangly1024/NotionNext" className=' hover:underline'> NotionNext {BLOG.VERSION} </a>
|
||||
</span>
|
||||
|
||||
@@ -7,7 +7,7 @@ import Link from 'next/link'
|
||||
export const Header = (props) => {
|
||||
const { siteInfo } = props
|
||||
|
||||
return <header className="w-full px-6 bg-white">
|
||||
return <header className="w-full px-6 bg-white dark:bg-black">
|
||||
<div className="container mx-auto max-w-4xl md:flex justify-between items-center">
|
||||
<Link href='/'>
|
||||
<a className="py-6 w-full text-center md:text-left md:w-auto text-gray-dark no-underline flex justify-center items-center">
|
||||
|
||||
@@ -20,12 +20,12 @@ export const Nav = (props) => {
|
||||
links = links.concat(customNav)
|
||||
}
|
||||
|
||||
return <nav className="w-full bg-white md:pt-0 px-6 relative z-20 border-t border-b border-gray-light">
|
||||
return <nav className="w-full bg-white md:pt-0 px-6 relative z-20 border-t border-b border-gray-light dark:border-hexo-black-gray dark:bg-black">
|
||||
<div className="container mx-auto max-w-4xl md:flex justify-between items-center text-sm md:text-md md:justify-start">
|
||||
<div className="w-full md:w-2/3 text-center md:text-left py-4 flex flex-wrap justify-center items-stretch md:justify-start md:items-start">
|
||||
{links.map(link => {
|
||||
return link && <Link href={link.to}>
|
||||
<a className="px-2 md:pl-0 md:mr-3 md:pr-3 text-gray-700 no-underline md:border-r border-gray-light">
|
||||
return link && <Link href={link.to} key={link.to}>
|
||||
<a className="px-2 md:pl-0 md:mr-3 md:pr-3 text-gray-700 dark:text-gray-200 no-underline md:border-r border-gray-light">
|
||||
{link.name}
|
||||
</a>
|
||||
</Link>
|
||||
|
||||
@@ -8,7 +8,7 @@ export const SideBar = (props) => {
|
||||
return <div className="w-full md:w-64 sticky top-8">
|
||||
|
||||
<aside className="rounded shadow overflow-hidden mb-6">
|
||||
<h3 className="text-sm bg-gray-100 text-gray-700 py-3 px-4 border-b">{locale.COMMON.CATEGORY}</h3>
|
||||
<h3 className="text-sm bg-gray-100 text-gray-700 dark:bg-hexo-black-gray dark:text-gray-200 py-3 px-4 dark:border-hexo-black-gray border-b">{locale.COMMON.CATEGORY}</h3>
|
||||
|
||||
<div className="p-4">
|
||||
<ul className="list-reset leading-normal">
|
||||
@@ -23,7 +23,7 @@ export const SideBar = (props) => {
|
||||
</aside>
|
||||
|
||||
<aside className="rounded shadow overflow-hidden mb-6">
|
||||
<h3 className="text-sm bg-gray-100 text-gray-700 py-3 px-4 border-b">{locale.COMMON.LATEST_POSTS}</h3>
|
||||
<h3 className="text-sm bg-gray-100 text-gray-700 dark:bg-hexo-black-gray dark:text-gray-200 py-3 px-4 dark:border-hexo-black-gray border-b">{locale.COMMON.LATEST_POSTS}</h3>
|
||||
|
||||
<div className="p-4">
|
||||
<ul className="list-reset leading-normal">
|
||||
|
||||
@@ -10,7 +10,7 @@ export const Title = (props) => {
|
||||
const title = post?.title || siteInfo?.description
|
||||
const description = post?.description || BLOG.AUTHOR
|
||||
|
||||
return <div className="text-center px-6 py-12 mb-6 bg-gray-100 border-b">
|
||||
return <div className="text-center px-6 py-12 mb-6 bg-gray-100 dark:bg-hexo-black-gray dark:border-hexo-black-gray border-b">
|
||||
<h1 className=" text-xl md:text-4xl pb-4">{title}</h1>
|
||||
<p className="leading-loose text-gray-dark">
|
||||
{description}
|
||||
|
||||
@@ -11,12 +11,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-white bg-opacity-40 hover:bg-gray-500 hover:text-white duration-300'>
|
||||
<a className='text-sm cursor-pointer justify-center items-center flex w-full h-full bg-white bg-opacity-40 hover:bg-hexo-black-gray dark:bg-hexo-black-gray dark:text-gray-200 hover:text-white duration-300'>
|
||||
<i className='mr-1 fas fa-angle-double-left' />{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-white bg-opacity-40 hover:bg-gray-500 hover:text-white duration-300'>{next.title}
|
||||
<a className='text-sm cursor-pointer justify-center items-center flex w-full h-full bg-white bg-opacity-40 hover:bg-hexo-black-gray dark:bg-hexo-black-gray dark:text-gray-200 hover:text-white duration-300'>{next.title}
|
||||
<i className='ml-1 my-1 fas fa-angle-double-right' />
|
||||
</a>
|
||||
</Link>
|
||||
|
||||
@@ -25,7 +25,7 @@ export default function ArticleDetail(props) {
|
||||
</div>
|
||||
)}
|
||||
<article itemScope itemType="https://schema.org/Movie"
|
||||
className="subpixel-antialiased py-10 px-5 lg:pt-24 md:px-32 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-hexo-black-gray"
|
||||
>
|
||||
|
||||
<header className='animate__slideInDown animate__animated'>
|
||||
@@ -92,7 +92,7 @@ export default function ArticleDetail(props) {
|
||||
<ArticleAround prev={prev} next={next} />
|
||||
|
||||
{/* 评论互动 */}
|
||||
<div className="duration-200 shadow px-12 w-screen md:w-full overflow-x-auto dark:border-gray-700 bg-white dark:bg-gray-800">
|
||||
<div className="duration-200 shadow px-12 w-screen md:w-full overflow-x-auto dark:border-gray-700 bg-white dark:bg-hexo-black-gray">
|
||||
<Comment frontMatter={post} />
|
||||
</div>
|
||||
</div>)
|
||||
|
||||
@@ -6,11 +6,12 @@ import SearchInput from './SearchInput'
|
||||
import SiteInfo from './SiteInfo'
|
||||
import Catalog from './Catalog'
|
||||
import { useRouter } from 'next/router'
|
||||
import DarkModeButton from '@/components/DarkModeButton'
|
||||
|
||||
function AsideLeft (props) {
|
||||
const { tags, currentTag, categories, currentCategory, post, slot, siteInfo } = props
|
||||
const router = useRouter()
|
||||
return <div className='w-72 bg-white dark:bg-gray-800 min-h-screen px-10 py-14 hidden lg:block'>
|
||||
return <div className='w-72 bg-white dark:bg-hexo-black-gray min-h-screen px-10 py-14 hidden lg:block'>
|
||||
<Logo {...props}/>
|
||||
|
||||
<section className='flex flex-col text-gray-600'>
|
||||
@@ -43,12 +44,17 @@ function AsideLeft (props) {
|
||||
<SiteInfo/>
|
||||
</section>
|
||||
|
||||
<section className='flex justify-center dark:text-gray-200'>
|
||||
<DarkModeButton/>
|
||||
</section>
|
||||
|
||||
<section className='sticky top-0 pt-12'>
|
||||
<Catalog toc={post?.toc}/>
|
||||
<div className='flex justify-center'>
|
||||
<div>{slot}</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const Card = ({ children, headerSlot, className }) => {
|
||||
return <div className={className}>
|
||||
<>{headerSlot}</>
|
||||
<section className="shadow mb-4 p-2 bg-white dark:bg-gray-800 hover:shadow-lg duration-200">
|
||||
<section className="shadow mb-4 p-2 bg-white dark:bg-hexo-black-gray hover:shadow-lg duration-200">
|
||||
{children}
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
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
|
||||
@@ -53,7 +53,7 @@ const SearchInput = ({ currentTag, currentSearch, cRef }) => {
|
||||
<input
|
||||
ref={searchInputRef}
|
||||
type='text'
|
||||
className={'w-full text-sm pl-2 transition focus:shadow-lg font-light leading-10 text-black bg-gray-100 dark:bg-gray-900 dark:text-white'}
|
||||
className={'w-full text-sm pl-2 transition focus:shadow-lg font-light leading-10 text-black bg-gray-100 dark:bg-gray-800 dark:text-white'}
|
||||
onKeyUp={handleKeyUp}
|
||||
onCompositionStart={lockSearchInput}
|
||||
onCompositionUpdate={lockSearchInput}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState } from 'react'
|
||||
import Collapse from './Collapse'
|
||||
import Collapse from '@/components/Collapse'
|
||||
import GroupMenu from './GroupMenu'
|
||||
import Logo from './Logo'
|
||||
import SearchInput from './SearchInput'
|
||||
@@ -20,7 +20,7 @@ const TopNav = props => {
|
||||
|
||||
{/* 导航栏 */}
|
||||
<div id='sticky-nav' className={'lg:relative w-full top-0 z-20 transform duration-500 bg-white dark:bg-black'}>
|
||||
<Collapse isOpen={isOpen}>
|
||||
<Collapse type='vertical' isOpen={isOpen}>
|
||||
<div className='py-1 px-5'>
|
||||
<GroupMenu {...props} />
|
||||
<SearchInput {...props} />
|
||||
@@ -36,8 +36,8 @@ const TopNav = props => {
|
||||
|
||||
{/* 右侧功能 */}
|
||||
<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 ? <i className='fas fa-times' /> : <i className='fas fa-bars' />}
|
||||
<div onClick={toggleMenuOpen} className='cursor-pointer'>
|
||||
{isOpen ? <i className='fas fa-times' /> : <i className='fas fa-bars' />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from 'react'
|
||||
import BLOG from '@/blog.config'
|
||||
import DarkModeButton from '@/components/DarkModeButton'
|
||||
|
||||
const Footer = ({ title }) => {
|
||||
const d = new Date()
|
||||
@@ -9,6 +10,8 @@ const Footer = ({ title }) => {
|
||||
<footer
|
||||
className='font-sans dark:bg-black flex-shrink-0 bg-hexo-light-gray justify-center text-center m-auto w-full leading-6 text-gray-600 dark:text-gray-100 text-sm p-6'
|
||||
>
|
||||
<DarkModeButton/>
|
||||
|
||||
<i className='fas fa-copyright' /> {`${startYear}${currentYear}`} <span><i className='mx-1 animate-pulse fas fa-heart'/> <a href={BLOG.LINK} className='underline font-bold dark:text-gray-300 '>{BLOG.AUTHOR}</a>.<br/>
|
||||
|
||||
{BLOG.BEI_AN && <><i className='fas fa-shield-alt' /> <a href='https://beian.miit.gov.cn/' className='mr-2'>{BLOG.BEI_AN}</a><br/></>}
|
||||
|
||||
@@ -13,7 +13,7 @@ export default function SideRight(props) {
|
||||
} = props
|
||||
|
||||
return (
|
||||
<div className={'space-y-4 lg:w-80 lg:pt-0 lg:px-2 pt-4'}>
|
||||
<div className={'space-y-4 lg:w-80 lg:pt-0 px-2 pt-4'}>
|
||||
<InfoCard {...props} />
|
||||
{CONFIG_HEXO.WIDGET_ANALYTICS && <AnalyticsCard {...props} />}
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ const TopNav = props => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Collapse isOpen={isOpen} className='shadow-xl'>
|
||||
<Collapse type='vertical' isOpen={isOpen} className='shadow-xl'>
|
||||
<div className='bg-white dark:bg-hexo-black-gray pt-1 py-2 px-5 lg:hidden '>
|
||||
<MenuList {...props}/>
|
||||
</div>
|
||||
|
||||
@@ -17,7 +17,7 @@ export const LayoutSearch = (props) => {
|
||||
container.innerHTML = container.innerHTML.replace(re, `<span class='text-red-500 border-b border-dashed'>${keyword}</span>`)
|
||||
}
|
||||
},
|
||||
100)
|
||||
100)
|
||||
})
|
||||
return <LayoutBase {...props}>
|
||||
<div className='py-12'>
|
||||
|
||||
@@ -4,7 +4,7 @@ import JumpToTopButton from './JumpToTopButton'
|
||||
|
||||
export default function BottomMenuBar ({ className }) {
|
||||
return (
|
||||
<div className={'sticky bottom-0 w-full h-12 bg-white ' + className}>
|
||||
<div className={'sticky bottom-0 w-full h-12 bg-white dark:bg-hexo-black-gray ' + className}>
|
||||
<div className='flex justify-between h-full shadow-card'>
|
||||
<Link href='/' passHref>
|
||||
<div className='flex w-full items-center justify-center cursor-pointer'>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from 'react'
|
||||
import BLOG from '@/blog.config'
|
||||
import DarkModeButton from '@/components/DarkModeButton'
|
||||
|
||||
const Footer = ({ title }) => {
|
||||
const d = new Date()
|
||||
@@ -9,6 +10,7 @@ const Footer = ({ title }) => {
|
||||
<footer
|
||||
className='dark:bg-hexo-black-gray flex-shrink-0 justify-center text-center m-auto w-full leading-6 text-gray-400 text-sm p-6'
|
||||
>
|
||||
<DarkModeButton/>
|
||||
<i className='fas fa-copyright' /> {`${startYear}${currentYear}`} <span><i className='mx-1 animate-pulse fas fa-heart'/> <a href={BLOG.LINK} className='underline font-bold text-gray-500 dark:text-gray-300 '>{BLOG.AUTHOR}</a>.<br/>
|
||||
|
||||
{BLOG.BEI_AN && <><i className='fas fa-shield-alt'/> <a href='https://beian.miit.gov.cn/' className='mr-2'>{BLOG.BEI_AN}</a><br/></>}
|
||||
|
||||
43
themes/medium/components/GroupMenu.js
Normal file
43
themes/medium/components/GroupMenu.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import React from 'react'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import CONFIG_MEDIUM from '../config_medium'
|
||||
|
||||
function GroupMenu ({ customNav }) {
|
||||
const { locale } = useGlobal()
|
||||
const router = useRouter()
|
||||
|
||||
let links = [
|
||||
{ name: locale.NAV.INDEX, to: '/' || '/', show: true },
|
||||
{ name: locale.COMMON.CATEGORY, to: '/category', show: CONFIG_MEDIUM.MENU_CATEGORY },
|
||||
{ name: locale.COMMON.TAGS, to: '/tag', show: CONFIG_MEDIUM.MENU_TAG },
|
||||
{ name: locale.NAV.ARCHIVE, to: '/archive', show: CONFIG_MEDIUM.MENU_ARCHIVE },
|
||||
{ name: locale.NAV.SEARCH, to: '/search', show: CONFIG_MEDIUM.MENU_SEARCH }
|
||||
]
|
||||
|
||||
if (customNav) {
|
||||
links = links.concat(customNav)
|
||||
}
|
||||
|
||||
return <nav id='nav' className='font-sans text-sm'>
|
||||
{links.map(link => {
|
||||
if (link.show) {
|
||||
const selected = (router.pathname === link.to) || (router.asPath === link.to)
|
||||
return <Link key={`${link.to}`} title={link.to} href={link.to} >
|
||||
<a className={'py-0.5 duration-500 justify-between text-gray-500 dark:text-gray-300 hover:text-black hover:underline cursor-pointer flex flex-nowrap items-center ' +
|
||||
(selected ? 'text-black' : ' ')} >
|
||||
<div className='my-auto items-center justify-center flex '>
|
||||
<div className={ 'hover:text-black'}>{link.name}</div>
|
||||
</div>
|
||||
{link.slot}
|
||||
</a>
|
||||
</Link>
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
})}
|
||||
</nav>
|
||||
}
|
||||
|
||||
export default GroupMenu
|
||||
@@ -1,40 +1,60 @@
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import LogoBar from './LogoBar'
|
||||
|
||||
import React from 'react'
|
||||
import Collapse from '@/components/Collapse'
|
||||
import GroupMenu from './GroupMenu'
|
||||
/**
|
||||
* 顶部导航栏 + 菜单
|
||||
* @param {} param0
|
||||
* @returns
|
||||
*/
|
||||
export default function TopNavBar (props) {
|
||||
export default function TopNavBar(props) {
|
||||
const { className, customNav } = props
|
||||
const router = useRouter()
|
||||
const [isOpen, changeShow] = React.useState(false)
|
||||
|
||||
const toggleMenuOpen = () => {
|
||||
changeShow(!isOpen)
|
||||
}
|
||||
|
||||
return <div id='top-nav' className={'sticky top-0 lg:relative w-full z-40 ' + className}>
|
||||
<div className='flex w-full h-12 shadow bg-white dark:bg-hexo-black-gray px-5 items-between'>
|
||||
<LogoBar {...props}/>
|
||||
<Collapse type='vertical' isOpen={isOpen} className='md:hidden'>
|
||||
<div className='bg-white dark:bg-hexo-black-gray pt-1 py-2 px-5 lg:hidden '>
|
||||
<GroupMenu {...props} />
|
||||
</div>
|
||||
</Collapse>
|
||||
|
||||
{/* 顶部菜单 */}
|
||||
<div className='flex'>
|
||||
{customNav && customNav.map(link => {
|
||||
if (link.show) {
|
||||
const selected = (router.pathname === link.to) || (router.asPath === link.to)
|
||||
return <Link key={`${link.id}-${link.to}`} title={link.to} href={link.to} >
|
||||
<a target={link.to.indexOf('http') === 0 ? '_blank' : '_self'} className={'px-2 duration-300 text-sm justify-between dark:text-gray-300 hover:underline cursor-pointer flex flex-nowrap items-center ' +
|
||||
(selected ? 'bg-green-600 text-white hover:text-white' : 'hover:text-green-600')} >
|
||||
<div className='items-center justify-center flex '>
|
||||
<i className={link.icon} />
|
||||
<div className='ml-2 whitespace-nowrap'>{link.name}</div>
|
||||
<div className='flex w-full h-12 shadow bg-white dark:bg-hexo-black-gray px-5 items-between'>
|
||||
<LogoBar {...props} />
|
||||
|
||||
{/* 右侧功能 */}
|
||||
<div className='mr-1 flex md:hidden justify-end items-center text-sm space-x-4 font-serif dark:text-gray-200'>
|
||||
<div onClick={toggleMenuOpen} className='cursor-pointer'>
|
||||
{isOpen ? <i className='fas fa-times' /> : <i className='fas fa-bars' />}
|
||||
</div>
|
||||
{link.slot}
|
||||
</a>
|
||||
</Link>
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 顶部菜单 */}
|
||||
<div className='hidden md:flex'>
|
||||
{customNav && customNav.map(link => {
|
||||
if (link.show) {
|
||||
const selected = (router.pathname === link.to) || (router.asPath === link.to)
|
||||
return <Link key={`${link.id}-${link.to}`} title={link.to} href={link.to} >
|
||||
<a target={link.to.indexOf('http') === 0 ? '_blank' : '_self'} className={'px-2 duration-300 text-sm justify-between dark:text-gray-300 hover:underline cursor-pointer flex flex-nowrap items-center ' +
|
||||
(selected ? 'bg-green-600 text-white hover:text-white' : 'hover:text-green-600')} >
|
||||
<div className='items-center justify-center flex '>
|
||||
<i className={link.icon} />
|
||||
<div className='ml-2 whitespace-nowrap'>{link.name}</div>
|
||||
</div>
|
||||
{link.slot}
|
||||
</a>
|
||||
</Link>
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import { loadDarkModeFromCookies, saveDarkModeToCookies } from '@/lib/theme'
|
||||
import { saveDarkModeToCookies } from '@/lib/theme'
|
||||
|
||||
const DarkModeButton = () => {
|
||||
const { updateDarkMode } = useGlobal()
|
||||
const isDarkMode = loadDarkModeFromCookies()
|
||||
|
||||
const { isDarkMode, updateDarkMode } = useGlobal()
|
||||
// 用户手动设置主题
|
||||
const handleChangeDarkMode = () => {
|
||||
const newTheme = (isDarkMode ? 'dark' : 'light')
|
||||
saveDarkModeToCookies(newTheme)
|
||||
updateDarkMode(newTheme)
|
||||
const newStatus = !isDarkMode
|
||||
saveDarkModeToCookies(newStatus)
|
||||
updateDarkMode(newStatus)
|
||||
const htmlElement = document.getElementsByTagName('html')[0]
|
||||
htmlElement.classList?.remove(newStatus ? 'light' : 'dark')
|
||||
htmlElement.classList?.add(newStatus ? 'dark' : 'light')
|
||||
}
|
||||
|
||||
return <div className='z-10 duration-200 text-xs cursor-pointer py-1.5 px-1'>
|
||||
<i id='darkModeButton' className={`hover:scale-125 transform duration-200 fas ${isDarkMode ? 'fa-sun' : 'fa-moon'}`}
|
||||
onClick={handleChangeDarkMode} />
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from 'react'
|
||||
import BLOG from '@/blog.config'
|
||||
import DarkModeButton from '@/components/DarkModeButton'
|
||||
|
||||
const Footer = ({ title }) => {
|
||||
const d = new Date()
|
||||
@@ -9,6 +10,7 @@ const Footer = ({ title }) => {
|
||||
<footer
|
||||
className='dark:bg-gray-900 flex-shrink-0 justify-center text-center m-auto w-full leading-6 text-sm p-6 dark:text-gray-400'
|
||||
>
|
||||
<DarkModeButton/>
|
||||
<i className='fas fa-copyright' /> {`${startYear}${currentYear}`} <span><i className='mx-1 animate-pulse fas fas-heart' /> <a href={BLOG.LINK} className='underline font-bold '>{BLOG.AUTHOR}</a>.<br />
|
||||
|
||||
{BLOG.BEI_AN && <><i className='fas fa-shield-alt' /> <a href='https://beian.miit.gov.cn/' className='mr-2'>{BLOG.BEI_AN}</a><br /></>}
|
||||
|
||||
@@ -109,7 +109,7 @@ const TopNav = (props) => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Collapse isOpen={isOpen}>
|
||||
<Collapse type='vertical' isOpen={isOpen}>
|
||||
<div className='bg-white py-1 px-5'>
|
||||
<MenuButtonGroup {...props} from='top'/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user