Merge remote-tracking branch 'origin/main' into theme-medium

# Conflicts:
#	themes/index.js
This commit is contained in:
tangly1024
2022-03-03 16:34:50 +08:00
40 changed files with 211 additions and 142 deletions

View File

@@ -1,8 +1,9 @@
import lang from './lang'
import { useContext, createContext, useState, useEffect } from 'react'
import { useContext, createContext, useState } from 'react'
import Router from 'next/router'
import { initTheme, loadUserThemeFromCookies } from './theme'
import { initDarkMode } from './theme'
const GlobalContext = createContext()
let hasInit = false
/**
* 全局变量Provider包括语言本地化、样式主题、搜索词
@@ -11,8 +12,8 @@ const GlobalContext = createContext()
* @constructor
*/
export function GlobalContextProvider ({ children }) {
const [locale, changeLocale] = useState(generateLocaleDict('en-US'))
const [theme, changeTheme] = useState(loadUserThemeFromCookies())
const [locale, updateLocale] = useState(generateLocaleDict('en-US'))
const [isDarkMode, updateDarkMode] = useState(false)
const [onLoading, changeLoadingState] = useState(false)
Router.events.on('routeChangeStart', (...args) => {
changeLoadingState(true)
@@ -23,13 +24,17 @@ export function GlobalContextProvider ({ children }) {
})
// 服务端静态渲染在渲染hooks后根据前端变量做初始化工作
useEffect(() => {
initTheme(theme, changeTheme)
initLocale(locale, changeLocale)
})
setTimeout(() => {
console.log('初始化', hasInit, isDarkMode)
if (!hasInit) {
hasInit = true
initLocale(locale, updateLocale)
initDarkMode(isDarkMode, updateDarkMode)
}
}, 1000)
return (
<GlobalContext.Provider value={{ onLoading, locale, theme, changeTheme }}>
<GlobalContext.Provider value={{ onLoading, locale, isDarkMode, updateDarkMode }}>
{children}
</GlobalContext.Provider>
)

View File

@@ -49,7 +49,6 @@ export async function getAllPosts ({ notionPageData, from, pageType }) {
})
if (!posts || posts.length === 0) {
console.warn('文章列表为空')
const cacheKey = 'page_block_' + BLOG.NOTION_PAGE_ID
await delCacheData(cacheKey)
}

View File

@@ -1,37 +1,45 @@
import cookie from 'react-cookies'
import BLOG from '@/blog.config'
/**
* 初始化主题
* @param theme 用户默认主题state
* @param changeTheme 更改主题ChangeState函数
* @param isDarkMode
* @param updateDarkMode 更改主题ChangeState函数
* @description 读取cookie中存的用户主题
*/
export const initTheme = (theme, changeTheme) => {
// 若未指定主题,则从时间和浏览器偏好中决定初始主题
if (!theme) {
const date = new Date()
const prefersDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches
const useDark = prefersDarkMode || (date.getHours() >= 18 || date.getHours() < 6)
if (useDark) {
theme = 'dark'
} else {
theme = 'light'
}
export const initDarkMode = (isDarkMode, updateDarkMode) => {
if (!isDarkMode) {
isDarkMode = isPreferDark()
}
if (typeof window !== 'undefined') {
const htmlElement = document.getElementsByTagName('html')
htmlElement.className = ''
changeTheme(theme)
saveTheme(theme)
htmlElement.classList?.add(theme)
updateDarkMode(isDarkMode)
saveDarkModeToCookies(isDarkMode)
document.getElementsByTagName('html')[0].setAttribute('class', isDarkMode ? 'dark' : 'light')
}
}
/**
* 是否优先深色模式
* @returns {*}
*/
export function isPreferDark () {
if (BLOG.APPEARANCE === 'dark') {
return true
}
if (BLOG.APPEARANCE === 'auto') {
// 系统深色模式或时间是夜间时,强行置为夜间模式
const date = new Date()
const prefersDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches
return prefersDarkMode || (date.getHours() >= 18 || date.getHours() < 6)
}
return false
}
/**
* 读取默认主题
* @returns {*}
*/
export const loadUserThemeFromCookies = () => {
export const loadDarkModeFromCookies = () => {
return cookie.load('theme')
}
@@ -39,6 +47,6 @@ export const loadUserThemeFromCookies = () => {
* 保存默认主题
* @param newTheme
*/
export const saveTheme = (newTheme) => {
export const saveDarkModeToCookies = (newTheme) => {
cookie.save('theme', newTheme, { path: '/' })
}

View File

@@ -11,7 +11,7 @@ class MyDocument extends Document {
render () {
return (
<Html lang={BLOG.LANG}>
<Html lang={BLOG.LANG} className='test'>
<Head>
<link rel='icon' href='/favicon.ico' />
<link rel='icon' href='/favicon.svg' type='image/svg+xml' />

View File

@@ -4,6 +4,11 @@ import BLOG from '@/blog.config'
import { useGlobal } from '@/lib/global'
import { getDataFromCache } from '@/lib/cache/cache_manager'
/**
* 服务端搜索
* @param {*} param0
* @returns
*/
export async function getServerSideProps ({ params: { keyword } }) {
const {
allPosts,
@@ -110,18 +115,16 @@ async function filterByMemCache (allPosts, keyword) {
// console.log('搜索是否命中缓存', page !== null, indexContent)
post.results = []
let hitCount = 0
const re = new RegExp(`${keyword}`, 'gim')
for (const i in indexContent) {
const c = indexContent[i]
const index = c.toLowerCase().indexOf(keyword.toLowerCase())
const referText = c?.replace(re, `<span class='text-red-500'>${keyword}</span>`)
if (index > -1) {
hit = true
hitCount += 1
post.results.push(`<span>${referText}</span>`)
post.results.push(c)
} else {
if ((post.results.length - 1) / hitCount < 3 || i === 0) {
post.results.push(`<span>${referText}</span>`)
post.results.push(c)
}
}
}

View File

@@ -4,6 +4,9 @@ import BLOG from '@/blog.config'
import { useGlobal } from '@/lib/global'
import { useRouter } from 'next/router'
/**
* 浏览器前端搜索
*/
export async function getStaticProps () {
const {
allPosts,

View File

@@ -1,8 +1,23 @@
import LayoutBase from './LayoutBase'
import BlogListPage from './components/BlogListPage'
import { useRouter } from 'next/router'
import { useEffect } from 'react'
export const LayoutSearch = (props) => {
return <LayoutBase {...props}>
const { keyword } = props
const router = useRouter()
const currentSearch = keyword || router?.query?.s
useEffect(() => {
setTimeout(() => {
const container = document.getElementById('container')
if (container && container.innerHTML) {
const re = new RegExp(`${currentSearch}`, 'gim')
container.innerHTML = container.innerHTML.replace(re, `<span class='text-red-500 border-b border-dashed'>${currentSearch}</span>`)
}
},
100)
})
return <LayoutBase {...props} currentSearch={currentSearch}>
<BlogListPage {...props}/>
</LayoutBase>
}

View File

@@ -9,7 +9,7 @@ import Catalog from './Catalog'
function AsideLeft (props) {
const { tags, currentTag, categories, currentCategory, post } = props
return <div className='w-72 bg-white min-h-screen px-10 py-14 hidden lg:block'>
return <div className='w-72 bg-white dark:bg-gray-800 min-h-screen px-10 py-14 hidden lg:block'>
<Logo />
<section className='flex flex-col text-gray-600'>
@@ -19,11 +19,11 @@ function AsideLeft (props) {
<section className='flex flex-col text-gray-600'>
<hr className='w-12 my-8' />
<SearchInput/>
<SearchInput {...props}/>
</section>
<section className='flex flex-col'>
<hr className='w-12 my-8 ' />
<section className='flex flex-col dark:text-gray-300'>
<hr className='w-12 my-8' />
{ BLOG.DESCRIPTION }
</section>

View File

@@ -51,7 +51,7 @@ const Catalog = ({ toc }) => {
}, throttleMs))
return <div>
<div className='w-full'><i className='mr-1 fas fa-stream' /> 目录</div>
<div className='w-full dark:text-gray-300'><i className='mr-1 fas fa-stream' /> 目录</div>
<nav className='font-sans overflow-y-auto scroll-hidden text-black'>
{toc.map((tocItem) => {
const id = uuidToId(tocItem.id)
@@ -59,7 +59,7 @@ const Catalog = ({ toc }) => {
<a
key={id}
href={`#${id}`}
className={`notion-table-of-contents-item duration-300 transform font-light
className={`notion-table-of-contents-item duration-300 transform font-light dark:text-gray-300
notion-table-of-contents-item-indent-level-${tocItem.indentLevel} `}
>
<span

View File

@@ -24,10 +24,10 @@ function GroupMenu ({ customNav }) {
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 hover:text-black cursor-pointer flex flex-nowrap items-center ' +
<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={ 'text-gray-500 hover:text-black'}>{link.name}</div>
<div className={ 'hover:text-black'}>{link.name}</div>
</div>
{link.slot}
</a>

View File

@@ -4,7 +4,7 @@ import Link from 'next/link'
function Logo () {
return <section className='flex'>
<Link href='/'>
<a className='hover:bg-black hover:text-white border-black border-2 duration-500 px-4 py-2 cursor-pointer font-black'>{BLOG.TITLE}</a>
<a className='hover:bg-black hover:text-white border-black border-2 duration-500 px-4 py-2 cursor-pointer dark:text-gray-300 dark:border-gray-300 font-black'>{BLOG.TITLE}</a>
</Link>
</section>
}

View File

@@ -62,9 +62,9 @@ const SearchInput = ({ currentTag, currentSearch, cRef }) => {
defaultValue={searchKey}
/>
<div className='-ml-8 cursor-pointer dark:bg-gray-600 dark:hover:bg-gray-800 float-right items-center justify-center py-2'
<div className='-ml-8 cursor-pointer float-right items-center justify-center py-2'
onClick={() => { handleSearch(searchKey) }}>
<i className={`hover:text-black transform duration-200 text-gray-500 cursor-pointer fas ${onLoading ? 'fa-spinner animate-spin' : 'fa-search'}`} />
<i className={`hover:text-black transform duration-200 text-gray-500 cursor-pointer fas ${onLoading ? 'fa-spinner animate-spin' : 'fa-search'}`} />
</div>
{(searchKey && searchKey.length &&

View File

@@ -38,7 +38,7 @@ const LayoutBase = (props) => {
return () => document.removeEventListener('scroll', scrollListener)
}, [show])
return (<div className='bg-white'>
return (<div className='bg-white dark:bg-gray-900'>
<CommonHead meta={meta} />
<TopNav {...props}/>

View File

@@ -14,7 +14,7 @@ export const LayoutCategoryIndex = props => {
}
return (
<LayoutBase {...props} meta={meta}>
<Card className="bg-white dark:bg-gray-700 w-full min-h-screen">
<Card className="w-full min-h-screen">
<div className="dark:text-gray-200 mb-5 mx-3">
<i className="mr-4 fas fa-th" />
{locale.COMMON.CATEGORY}:

View File

@@ -1,9 +1,26 @@
import { useRouter } from 'next/router'
import { useEffect } from 'react'
import BlogPostListPage from './components/BlogPostListPage'
import LayoutBase from './LayoutBase'
export const LayoutSearch = (props) => {
return <LayoutBase {...props}>
<BlogPostListPage {...props}/>
const { keyword } = props
const router = useRouter()
const currentSearch = keyword || router?.query?.s
useEffect(() => {
setTimeout(() => {
const container = document.getElementById('container')
if (container && container.innerHTML) {
const re = new RegExp(`${currentSearch}`, 'gim')
container.innerHTML = container.innerHTML.replace(re, `<span class='text-red-500 border-b border-dashed'>${currentSearch}</span>`)
}
},
100)
})
return <LayoutBase {...props} currentSearch={currentSearch}>
<div ic='container'>
<BlogPostListPage {...props}/>
</div>
</LayoutBase>
}

View File

@@ -53,7 +53,7 @@ export const LayoutSlug = props => {
showTag={false}
floatSlot={floatSlot}
>
<div className="w-full lg:shadow-md lg:hover:shadow-2xl lg:border lg:border-gray-100 lg:rounded-xl lg:px-2 lg:py-4 lg:bg-white lg:dark:bg-gray-800 lg:duration-300">
<div className="w-full dark:border-gray-600 lg:shadow-md lg:hover:shadow-2xl lg:border lg:border-gray-100 lg:rounded-xl lg:px-2 lg:py-4 lg:bg-white lg:dark:bg-gray-800 lg:duration-300">
<ArticleDetail {...props} />
</div>

View File

@@ -34,9 +34,7 @@ export default function ArticleDetail ({ post, recommendPosts, prev, next }) {
})
return (<div id="container" className="max-w-5xl overflow-x-auto flex-grow mx-auto md:w-full md:px-5 ">
<article itemScope itemType="https://schema.org/Movie"
className="subpixel-antialiased dark:border-gray-700 bg-white dark:bg-gray-800"
>
<article itemScope itemType="https://schema.org/Movie" className="subpixel-antialiased bg-white dark:bg-gray-800" >
{/* Notion文章主体 */}
<section id='notion-article' className='px-5'>

View File

@@ -9,7 +9,7 @@ import CONFIG_HEXO from '../config_hexo'
const BlogPostCard = ({ post, showSummary }) => {
const showPreview = CONFIG_HEXO.POST_LIST_PREVIEW && post.blockMap
return (
<div className='w-full shadow hover:shadow-2xl border border-gray-100 rounded-xl bg-white dark:bg-gray-800 duration-300'>
<div className='w-full shadow hover:shadow-2xl border border-gray-100 dark:border-gray-600 rounded-xl bg-white dark:bg-gray-800 duration-300'>
<div key={post.id} className='animate__animated animate__fadeIn flex flex-col-reverse lg:flex-row justify-between duration-300'>
<div className='lg:p-8 p-4 flex flex-col w-full'>

View File

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

View File

@@ -63,7 +63,7 @@ const Catalog = ({ toc }) => {
<a
key={id}
href={`#${id}`}
className={`notion-table-of-contents-item duration-300 transform font-light
className={`notion-table-of-contents-item duration-300 transform font-light dark:text-gray-200
notion-table-of-contents-item-indent-level-${tocItem.indentLevel} `}
>
<span

View File

@@ -14,7 +14,7 @@ let autoScroll = false
*/
export default function Header () {
const [typed, changeType] = useState()
const { theme } = useGlobal()
const { isDarkMode } = useGlobal()
useEffect(() => {
scrollTrigger()
@@ -76,7 +76,7 @@ export default function Header () {
}
const updateTopNav = () => {
if (theme !== 'dark') {
if (!isDarkMode) {
const stickyNavElement = document.getElementById('sticky-nav')
if (window.scrollY < window.innerHeight) {
stickyNavElement?.classList?.add('dark')

View File

@@ -47,7 +47,7 @@ export default function HeaderArticle ({ post }) {
</div>
<section className="flex-wrap shadow-text flex text-sm justify-center mt-2 text-white dark:text-gray-400 font-light leading-8">
<div>
<div className='dark:text-gray-200'>
{post.category && <>
<Link href={`/category/${post.category}`} passHref>
<a className="cursor-pointer mr-2 dark:hover:text-white border-b dark:border-gray-500 border-dashed">

View File

@@ -8,7 +8,7 @@ const MenuButtonGroup = (props) => {
const { postCount, customNav } = props
const { locale } = useGlobal()
const router = useRouter()
const archiveSlot = <div className='bg-blue-300 dark:bg-blue-500 rounded-md text-gray-50 px-1 text-xs'>{postCount}</div>
const archiveSlot = <div className='bg-blue-300 dark:bg-blue-400 rounded-md text-gray-50 px-1 text-xs'>{postCount}</div>
let links = [
{ icon: 'fas fa-home', name: locale.NAV.INDEX, to: '/' || '/', show: true },

View File

@@ -52,7 +52,7 @@ const SearchInput = ({ currentTag, currentSearch, cRef }) => {
<input
ref={searchInputRef}
type='text'
className={'w-full rounded-lg text-sm pl-5 transition focus:shadow-lg font-light leading-10 text-black bg-gray-100'}
className={'w-full rounded-lg text-sm pl-5 transition focus:shadow-lg dark:text-gray-300 font-light leading-10 text-black bg-gray-100 dark:bg-gray-500'}
onKeyUp={handleKeyUp}
onCompositionStart={lockSearchInput}
onCompositionUpdate={lockSearchInput}
@@ -61,14 +61,14 @@ const SearchInput = ({ currentTag, currentSearch, cRef }) => {
defaultValue={currentSearch}
/>
<div className='-ml-8 cursor-pointer dark:hover:bg-gray-800 float-right items-center justify-center py-2'
<div className='-ml-8 cursor-pointer float-right items-center justify-center py-2'
onClick={() => { handleSearch(searchKey) }}>
<i className={`hover:text-black transform duration-200 text-gray-500 cursor-pointer fas ${onLoading ? 'fa-spinner animate-spin' : 'fa-search'}`} />
<i className={`hover:text-black transform duration-200 text-gray-500 dark:text-gray-200 cursor-pointer fas ${onLoading ? 'fa-spinner animate-spin' : 'fa-search'}`} />
</div>
{(searchKey && searchKey.length &&
<div className='-ml-12 cursor-pointer dark:hover:bg-gray-800 float-right items-center justify-center py-2'>
<i className='hover:text-black transform duration-200 text-gray-400 cursor-pointer fas fa-times' onClick={cleanSearch} />
<div className='-ml-12 cursor-pointer float-right items-center justify-center py-2'>
<i className='hover:text-black transform duration-200 text-gray-400 dark:text-gray-300 cursor-pointer fas fa-times' onClick={cleanSearch} />
</div>
)}
</div>

View File

@@ -40,12 +40,12 @@ export default function SideRight (props) {
className='rounded-full'
/>
</div>
<div className='text-center font-sans text-xl pb-4'>{BLOG.TITLE}</div>
<div className='text-center font-sans text-xl pb-4 dark:text-gray-300'>{BLOG.TITLE}</div>
<SocialButton />
</Card>
<Card>
<MenuButtonGroup {...props}/>
<SearchInput />
<SearchInput {...props}/>
</Card>
<Card>
<div className='ml-2 mb-3 font-sans'>

View File

@@ -2,12 +2,12 @@ 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 py-0.5 px-1 text-xs whitespace-nowrap dark:hover:text-white
<a className={`cursor-pointer inline-block rounded hover:bg-blue-400 dark:hover:text-white hover:text-white duration-200
mr-2 py-0.5 px-1 text-xs whitespace-nowrap
${selected
? 'text-white dark:text-gray-300 bg-black dark:bg-black dark:hover:bg-blue-900'
: `text-gray-600 hover:shadow-xl dark:border-gray-400 notion-${tag.color}_background dark:bg-blue-800`}` }>
<div className='font-light dark:text-gray-400'>{selected && <i className='mr-1 fa-tag'/>} {tag.name + (tag.count ? `(${tag.count})` : '')} </div>
: `text-gray-600 hover:shadow-xl dark:border-gray-400 notion-${tag.color}_background `}` }>
<div className='font-light'>{selected && <i className='mr-1 fa-tag'/>} {tag.name + (tag.count ? `(${tag.count})` : '')} </div>
</a>
</Link>
}

View File

@@ -90,7 +90,7 @@ const TopNav = ({ tags, currentTag, categories, currentCategory, postCount }) =>
<SearchDrawer cRef={searchDrawer} slot={searchDrawerSlot}/>
{/* 导航栏 */}
<div id='sticky-nav' className={`${CONFIG_HEXO.NAV_TYPE !== 'normal' ? 'fixed bg-white' : ' bg-none -mb-10'} bg-opacity-70 text-black w-full top-0 z-20 transform duration-500 font-sans`}>
<div id='sticky-nav' className={`${CONFIG_HEXO.NAV_TYPE !== 'normal' ? 'fixed bg-white' : ' bg-none -mb-10'} dark:bg-black dark:bg-opacity-50 dark:text-gray-200 bg-opacity-70 text-black w-full top-0 z-20 transform duration-500 font-sans`}>
<div className='w-full flex justify-between items-center px-4 py-2 shad'>
<div className='flex'>
<Logo/>

View File

@@ -9,6 +9,7 @@ import TopNavBar from './components/TopNavBar'
import SearchInput from './components/SearchInput'
import BottomMenuBar from './components/BottomMenuBar'
import { useGlobal } from '@/lib/global'
import { useRouter } from 'next/router'
/**
* 基础布局 采用左右两侧布局,移动端使用顶部导航栏
@@ -19,9 +20,10 @@ import { useGlobal } from '@/lib/global'
const LayoutBase = props => {
const { children, meta, showInfoCard = true, slotRight, slotTop } = props
const { locale } = useGlobal()
const router = useRouter()
return (
<div id='container' className='bg-white w-full h-full min-h-screen justify-center'>
<div className='bg-white dark:bg-gray-800 w-full h-full min-h-screen justify-center dark:text-gray-300'>
<CommonHead meta={meta} />
<main id='wrapper' className='flex justify-between w-full h-full mx-auto'>
{/* 桌面端左侧菜单 */}
@@ -37,11 +39,11 @@ const LayoutBase = props => {
</div>
{/* 桌面端右侧 */}
<div className='hidden xl:block border-l w-96'>
<div className='hidden xl:block border-l dark:border-gray-500 w-96'>
<Tabs className='py-14 px-6 sticky top-0'>
{slotRight}
<div key={locale.NAV.ABOUT}>
<SearchInput className='mt-6 mb-12' />
{router.pathname !== '/search' && <SearchInput className='mt-6 mb-12' />}
{showInfoCard && <InfoCard />}
{CONFIG_MEDIUM.WIDGET_REVOLVER_MAPS === 'true' && <RevolverMaps />}
</div>

View File

@@ -4,21 +4,30 @@ import { useGlobal } from '@/lib/global'
import TagGroups from './components/TagGroups'
import CategoryGroup from './components/CategoryGroup'
import BlogPostListScroll from './components/BlogPostListScroll'
import { useEffect } from 'react'
export const LayoutSearch = (props) => {
const { locale } = useGlobal()
const { keyword } = props
useEffect(() => {
setTimeout(() => {
const container = document.getElementById('container')
if (container && container.innerHTML) {
const re = new RegExp(`${keyword}`, 'gim')
container.innerHTML = container.innerHTML.replace(re, `<span class='text-red-500 border-b border-dashed'>${keyword}</span>`)
}
},
100)
})
return <LayoutBase {...props}>
<div className='py-12'>
<div className='pb-4 w-full'>
{locale.NAV.SEARCH}
</div>
<SearchInput/>
<div className='pb-4 w-full'>{locale.NAV.SEARCH}</div>
<SearchInput currentSearch={keyword} {...props}/>
<TagGroups {...props}/>
<CategoryGroup {...props}/>
</div>
<BlogPostListScroll {...props}/>
<div id='container'>
<BlogPostListScroll {...props}/>
</div>
</LayoutBase>
}

View File

@@ -83,7 +83,7 @@ export const LayoutSlug = props => {
showInfoCard={true}
slotRight={slotRight}
>
<h1 className="text-4xl pt-12 font-sans">{post?.title}</h1>
<h1 className="text-4xl pt-12 font-sans dark:text-gray-100">{post?.title}</h1>
<section className="flex py-4 items-center font-sans px-1">
<Link href="/about" passHref>
<>

View File

@@ -62,7 +62,7 @@ const Catalog = ({ toc }) => {
<a
key={id}
href={`#${id}`}
className={`notion-table-of-contents-item duration-300 transform font-light
className={`notion-table-of-contents-item duration-300 transform font-light dark:text-gray-300
notion-table-of-contents-item-indent-level-${tocItem.indentLevel} `}
>
<span

View File

@@ -17,8 +17,8 @@ const InfoCard = () => {
className='rounded-full'
/>
</div>
<div className='text-xl py-2 hover:scale-105 transform duration-200 flex justify-center'>{BLOG.AUTHOR}</div>
<div className='font-light text-gray-600 mb-2 hover:scale-105 transform duration-200 flex justify-center'>{BLOG.BIO}</div>
<div className='text-xl py-2 hover:scale-105 transform duration-200 flex justify-center dark:text-gray-300'>{BLOG.AUTHOR}</div>
<div className='font-light text-gray-600 mb-2 hover:scale-105 transform duration-200 flex justify-center dark:text-gray-400'>{BLOG.BIO}</div>
<SocialButton/>
</div>
</div>

View File

@@ -5,7 +5,7 @@ export default function LogoBar () {
return <div id='top-wrapper' className='w-full max-w-5xl justify-center mx-auto font-sans'>
<div className='w-full'>
<Link href='/'>
<a className='text-2xl'>{BLOG.TITLE}</a>
<a className='text-2xl dark:text-gray-200'>{BLOG.TITLE}</a>
</Link>
</div>
</div>

View File

@@ -1,8 +1,9 @@
import { useRouter } from 'next/router'
import { useImperativeHandle, useRef, useState } from 'react'
let lock = false
const SearchInput = ({ currentTag, currentSearch, cRef, className }) => {
const [searchKey, setSearchKey] = useState(currentSearch || getSearchKey() || '')
// const [searchKey, setSearchKey] = useState(currentSearch || getSearchKey() || '')
const [onLoading, setLoadingState] = useState(false)
const router = useRouter()
const searchInputRef = useRef()
@@ -14,12 +15,15 @@ const SearchInput = ({ currentTag, currentSearch, cRef, className }) => {
}
})
const handleSearch = (key) => {
const handleSearch = () => {
const key = searchInputRef.current.value
if (key && key !== '') {
setLoadingState(true)
router.push({ pathname: '/search', query: { s: key } }).then(r => {
setLoadingState(false)
})
// router.push({ pathname: '/search/' + key }).then(r => {
// setLoadingState(false)
// })
location.href = '/search/' + key
} else {
router.push({ pathname: '/' }).then(r => {
})
@@ -34,13 +38,19 @@ const SearchInput = ({ currentTag, currentSearch, cRef, className }) => {
}
const cleanSearch = () => {
searchInputRef.current.value = ''
setSearchKey('')
}
let lock = false
const [showClean, setShowClean] = useState(false)
const updateSearchKey = (val) => {
if (!lock) {
setSearchKey(val)
if (lock) {
return
}
searchInputRef.current.value = val
if (val) {
setShowClean(true)
} else {
setShowClean(false)
}
}
function lockSearchInput () {
@@ -61,28 +71,20 @@ const SearchInput = ({ currentTag, currentSearch, cRef, className }) => {
onCompositionUpdate={lockSearchInput}
onCompositionEnd={unLockSearchInput}
onChange={e => updateSearchKey(e.target.value)}
defaultValue={searchKey}
defaultValue={currentSearch}
/>
<div className='-ml-8 cursor-pointer dark:bg-gray-600 dark:hover:bg-gray-800 float-right items-center justify-center py-2'
onClick={() => { handleSearch(searchKey) }}>
<i className={`hover:text-black transform duration-200 text-gray-500 cursor-pointer fas ${onLoading ? 'fa-spinner animate-spin' : 'fa-search'} `} />
<div className='-ml-8 cursor-pointer float-right items-center justify-center py-2'
onClick={handleSearch}>
<i className={`hover:text-black transform duration-200 text-gray-500 dark:hover:text-gray-300 cursor-pointer fas ${onLoading ? 'fa-spinner animate-spin' : 'fa-search'} `} />
</div>
{(searchKey && searchKey.length &&
<div className='-ml-12 cursor-pointer dark:bg-gray-600 dark:hover:bg-gray-800 float-right items-center justify-center py-2'>
<i className='fas fa-times hover:text-black transform duration-200 text-gray-400 cursor-pointer' onClick={cleanSearch} />
{(showClean &&
<div className='-ml-12 cursor-pointer float-right items-center justify-center py-2'>
<i className='fas fa-times hover:text-black transform duration-200 text-gray-400 cursor-pointer dark:hover:text-gray-300' onClick={cleanSearch} />
</div>
)}
</div>
}
function getSearchKey () {
const router = useRouter()
if (router.query && router.query.s) {
return router.query.s
}
return null
}
export default SearchInput

View File

@@ -12,7 +12,7 @@ export default function TopNavBar (props) {
const router = useRouter()
return <div id='top-nav' className={'sticky top-0 lg:relative w-full z-50 ' + className}>
<div className='flex w-full h-12 shadow bg-white px-5 items-between'>
<div className='flex w-full h-12 shadow bg-white dark:bg-gray-900 px-5 items-between'>
<LogoBar />
{/* 顶部菜单 */}
@@ -21,8 +21,8 @@ export default function TopNavBar (props) {
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 className={'px-5 duration-300 text-base justify-between hover:underline cursor-pointer flex flex-nowrap items-center ' +
(selected ? 'bg-green-600 text-white hover:text-white' : 'hover:text-green-600 ')} >
<a className={'px-5 duration-300 text-base 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-4 whitespace-nowrap'>{link.name}</div>

View File

@@ -2,11 +2,21 @@ import LayoutBase from './LayoutBase'
import StickyBar from './components/StickyBar'
import BlogPostListScroll from './components/BlogPostListScroll'
import { useGlobal } from '@/lib/global'
import { useEffect } from 'react'
export const LayoutSearch = (props) => {
const { locale } = useGlobal()
const { posts } = props
const { posts, keyword } = props
useEffect(() => {
setTimeout(() => {
const container = document.getElementById('container')
if (container && container.innerHTML) {
const re = new RegExp(`${keyword}`, 'gim')
container.innerHTML = container.innerHTML.replace(re, `<span class='text-red-500 border-b border-dashed'>${keyword}</span>`)
}
},
100)
})
return (
<LayoutBase {...props} >
<StickyBar>

View File

@@ -12,8 +12,8 @@ const BlogPostCard = ({ post, showSummary }) => {
const { locale } = useGlobal()
const showPreview = CONFIG_NEXT.POST_LIST_PREVIEW && post.blockMap
return (
<Card className='w-full'>
<div key={post.id} className='animate__animated animate__fadeIn flex flex-col-reverse justify-between duration-300'>
<Card className='w-full animate__animated animate__fadeIn'>
<div key={post.id} className='flex flex-col-reverse justify-between duration-300'>
<div className='lg:p-8 p-4 flex flex-col w-full'>
<Link href={`${BLOG.PATH}/article/${post.slug}`} passHref>
@@ -47,9 +47,8 @@ const BlogPostCard = ({ post, showSummary }) => {
{/* 搜索结果 */}
{post.results && <p className='mt-4 text-gray-700 dark:text-gray-300 text-sm font-light leading-7'>
{post.results.map(r => <span key={r}><span dangerouslySetInnerHTML={{ __html: r }}/>...</span>)}
</p>
}
{post.results.map(r => <span key={r}>{r}</span>)}
</p> }
{showPreview && post?.blockMap && <div className='overflow-ellipsis truncate'>
<NotionRenderer

View File

@@ -1,18 +1,18 @@
import { useGlobal } from '@/lib/global'
import { loadUserThemeFromCookies, saveTheme } from '@/lib/theme'
import { loadDarkModeFromCookies, saveDarkModeToCookies } from '@/lib/theme'
const DarkModeButton = () => {
const { changeTheme } = useGlobal()
const userTheme = loadUserThemeFromCookies()
const { updateDarkMode } = useGlobal()
const isDarkMode = loadDarkModeFromCookies()
// 用户手动设置主题
const handleChangeDarkMode = () => {
const newTheme = (userTheme === 'light' ? 'dark' : 'light')
saveTheme(newTheme)
changeTheme(newTheme)
const newTheme = (isDarkMode ? 'dark' : 'light')
saveDarkModeToCookies(newTheme)
updateDarkMode(newTheme)
}
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 ${userTheme === 'dark' ? 'fa-sun' : 'fa-moon'}`}
<i id='darkModeButton' className={`hover:scale-125 transform duration-200 fas ${isDarkMode ? 'fa-sun' : 'fa-moon'}`}
onClick={handleChangeDarkMode} />
</div>
}

View File

@@ -1,5 +1,5 @@
import { useGlobal } from '@/lib/global'
import { loadUserThemeFromCookies, saveTheme } from '@/lib/theme'
import { saveDarkModeToCookies } from '@/lib/theme'
import CONFIG_NEXT from '../config_next'
export default function FloatDarkModeButton () {
@@ -7,16 +7,15 @@ export default function FloatDarkModeButton () {
return <></>
}
const { changeTheme } = useGlobal()
const userTheme = loadUserThemeFromCookies()
const { isDarkMode, updateDarkMode } = useGlobal()
// 用户手动设置主题
const handleChangeDarkMode = () => {
const newTheme = userTheme === 'light' ? 'dark' : 'light'
saveTheme(newTheme)
changeTheme(newTheme)
const newStatus = !isDarkMode
saveDarkModeToCookies(newStatus)
updateDarkMode(newStatus)
const htmlElement = document.getElementsByTagName('html')[0]
htmlElement.classList?.remove(userTheme)
htmlElement.classList?.add(newTheme)
htmlElement.classList?.remove(newStatus ? 'light' : 'dark')
htmlElement.classList?.add(newStatus ? 'dark' : 'light')
}
return (
@@ -27,7 +26,7 @@ export default function FloatDarkModeButton () {
>
<i
id="darkModeButton"
className={`${userTheme === 'dark' ? 'fa-sun' : 'fa-moon'} fas hover:scale-150 transform duration-200`}
className={`${isDarkMode ? 'fa-sun' : 'fa-moon'} fas hover:scale-150 transform duration-200`}
/>
</div>
)

View File

@@ -27,7 +27,7 @@ export default function Header () {
)
}
})
const { theme } = useGlobal()
const { isDarkMode } = useGlobal()
const autoScrollEnd = () => {
if (autoScroll) {
@@ -61,7 +61,7 @@ export default function Header () {
}
const updateTopNav = () => {
if (theme !== 'dark') {
if (!isDarkMode) {
const stickyNavElement = document.getElementById('sticky-nav')
if (window.scrollY < window.innerHeight) {
stickyNavElement.classList.add('dark')