diff --git a/blog.config.js b/blog.config.js index a9fb8dda..422bb7e0 100644 --- a/blog.config.js +++ b/blog.config.js @@ -41,8 +41,9 @@ const BLOG = { CONTACT_TELEGRAM: '', // 悬浮挂件 - WIDGET_PET: process.env.NEXT_PUBLIC_WIDGET_PET || false, // 是否显示宠物挂件 + WIDGET_PET: process.env.NEXT_PUBLIC_WIDGET_PET || true, // 是否显示宠物挂件 WIDGET_PET_LINK: 'https://cdn.jsdelivr.net/npm/live2d-widget-model-wanko@1.0.5/assets/wanko.model.json', // 挂件模型地址 @see https://github.com/xiazeyu/live2d-widget-models + WIDGET_PET_SWITCH_THEME: true, // 点击宠物挂件切换博客主题 // 评论互动 可同时开启 CUSDIS UTTERRANCES GITALK COMMENT_CUSDIS_APP_ID: process.env.NEXT_PUBLIC_COMMENT_CUSDIS_APP_ID || '', // data-app-id 36位 see https://cusdis.com/ @@ -78,7 +79,7 @@ const BLOG = { ADSENSE_GOOGLE_ID: process.env.NEXT_PUBLIC_ADSENSE_GOOGLE_ID || '', // 谷歌广告ID e.g ca-pub-xxxxxxxxxxxxxxxx isProd: process.env.VERCEL_ENV === 'production', // distinguish between development and production environment (ref: https://vercel.com/docs/environment-variables#system-environment-variables) isProd: process.env.VERCEL_ENV === 'production' // distinguish between development and production environment (ref: https://vercel.com/docs/environment-variables#system-environment-variables) - VERSION: '2.8.0' // 版本号 + VERSION: '2.8.1' // 版本号 } module.exports = BLOG diff --git a/components/Live2D.js b/components/Live2D.js index 48d438a6..3954decc 100644 --- a/components/Live2D.js +++ b/components/Live2D.js @@ -11,10 +11,11 @@ export default function Live2D () { const [init, setInit] = useState() const { switchTheme } = useGlobal() - // if (typeof window !== 'undefined' && !hasLoad) { - // initLive2D() - // hasLoad = true - // } + function handleClick () { + if (BLOG.WIDGET_PET_SWITCH_THEME) { + switchTheme() + } + } useEffect(() => { if (!init) { @@ -23,7 +24,7 @@ export default function Live2D () { } }, [init]) - return + return } function initLive2D () { diff --git a/lib/theme.js b/lib/theme.js index 30682f97..55d47aad 100644 --- a/lib/theme.js +++ b/lib/theme.js @@ -1,7 +1,7 @@ import cookie from 'react-cookies' import BLOG from '@/blog.config' -export const ALL_THEME = ['next', 'fukasawa', 'hexo', 'empty', 'medium'] +export const ALL_THEME = ['hexo', 'next', 'medium', 'fukasawa', 'empty'] /** * 初始化主题 * @param isDarkMode diff --git a/themes/empty/LayoutArchive.js b/themes/empty/LayoutArchive.js index bf9ebdc5..04a6d647 100644 --- a/themes/empty/LayoutArchive.js +++ b/themes/empty/LayoutArchive.js @@ -1,6 +1,69 @@ -export const LayoutArchive = (props) => { - // const { posts, tags, categories, postCount } = props - return
- Archive Page -
+import BLOG from '@/blog.config' +import { useGlobal } from '@/lib/global' +import Link from 'next/link' +import LayoutBase from './LayoutBase' + +export const LayoutArchive = props => { + const { posts } = props + const { locale } = useGlobal() + 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] + } + }) + return ( + +
+ {Object.keys(archivePosts).map(archiveTitle => ( +
+
+ {archiveTitle} +
+
    + {archivePosts[archiveTitle].map(post => ( +
  • +
    + + {post.date.start_date} + {' '} +   + + + {post.title} + + +
    +
  • + ))} +
+
+ ))} +
+
+ ) } diff --git a/themes/empty/LayoutBase.js b/themes/empty/LayoutBase.js index 211b34ac..7fe22844 100644 --- a/themes/empty/LayoutBase.js +++ b/themes/empty/LayoutBase.js @@ -3,6 +3,7 @@ import Live2D from '@/components/Live2D' import Link from 'next/link' import React from 'react' import BLOG from '@/blog.config' +import { useGlobal } from '@/lib/global' /** * 基础布局 采用左右两侧布局,移动端使用顶部导航栏 @@ -10,47 +11,123 @@ import BLOG from '@/blog.config' * @constructor */ const LayoutBase = props => { - const { children, meta } = props + const { children, meta, customNav } = props + const { locale } = useGlobal() const d = new Date() const currentYear = d.getFullYear() const startYear = BLOG.SINCE && BLOG.SINCE !== currentYear && BLOG.SINCE + '-' + + let links = [ + { icon: 'fas fa-search', name: locale.NAV.SEARCH, to: '/search' }, + { icon: 'fas fa-archive', name: locale.NAV.ARCHIVE, to: '/archive' }, + { icon: 'fas fa-folder', name: locale.COMMON.CATEGORY, to: '/category' }, + { icon: 'fas fa-tag', name: locale.COMMON.TAGS, to: '/tag' } + ] + + if (customNav) { + links = links.concat(customNav) + } + return (
{/* 导航菜单 */}
- +
+
+ + + +
{BLOG.TITLE}
+
+ +
+ +
+ {/* 内容主体 */}
-
{children}
-
-
- +
{children}
+
+
+
-
) } diff --git a/themes/empty/LayoutCategory.js b/themes/empty/LayoutCategory.js index 3a4bb0e9..85d399f6 100644 --- a/themes/empty/LayoutCategory.js +++ b/themes/empty/LayoutCategory.js @@ -1,8 +1,48 @@ +import BLOG from '@/blog.config' +import { useGlobal } from '@/lib/global' +import Link from 'next/link' +import { useState } from 'react' import LayoutBase from './LayoutBase' -export const LayoutCategory = (props) => { - const { category } = props - return - Category - {category} - +export const LayoutCategory = props => { + const { category, posts } = props + const { locale } = useGlobal() + + const [page, updatePage] = useState(1) + let hasMore = false + const postsToShow = posts + ? Object.assign(posts).slice(0, BLOG.POSTS_PER_PAGE * page) + : [] + + if (posts) { + const totalCount = posts.length + hasMore = page * BLOG.POSTS_PER_PAGE < totalCount + } + const handleGetMore = () => { + if (!hasMore) return + updatePage(page + 1) + } + + return ( + + Category - {category} + {postsToShow.map(p => ( +
+ + {p.title} + +
{p.summary}
+
+ ))} +
+
+ {' '} + {hasMore ? locale.COMMON.MORE : `${locale.COMMON.NO_MORE} 😰`}{' '} +
+
+
+ ) } diff --git a/themes/empty/LayoutCategoryIndex.js b/themes/empty/LayoutCategoryIndex.js index fcde0a08..b748b140 100644 --- a/themes/empty/LayoutCategoryIndex.js +++ b/themes/empty/LayoutCategoryIndex.js @@ -1,8 +1,25 @@ +import { useGlobal } from '@/lib/global' +import Link from 'next/link' import LayoutBase from './LayoutBase' export const LayoutCategoryIndex = (props) => { - // const { tags, allPosts, categories, postCount, latestPosts } = props + const { categories } = props + const { locale } = useGlobal() return - CategoryIndex +
+
+ {locale.COMMON.CATEGORY}: +
+
+ {categories && categories.map(category => { + return +
+ {category.name}({category.count}) +
+ + })} +
+
} diff --git a/themes/empty/LayoutSearch.js b/themes/empty/LayoutSearch.js index 52b61ba2..d24024a5 100644 --- a/themes/empty/LayoutSearch.js +++ b/themes/empty/LayoutSearch.js @@ -1,41 +1,59 @@ +import BLOG from '@/blog.config' +import { useGlobal } from '@/lib/global' import Link from 'next/link' -import { useRouter } from 'next/router' -import { useEffect } from 'react' +import { useEffect, useState } from 'react' +import SearchInput from './components/SearchInput' import LayoutBase from './LayoutBase' export const LayoutSearch = props => { const { keyword, posts } = props - const router = useRouter() - const currentSearch = keyword || router?.query?.s - let handleTextColor = false useEffect(() => { setTimeout(() => { - if (currentSearch && !handleTextColor) { - const container = document.getElementById('container') - if (container && container.innerHTML) { - const re = new RegExp(`${currentSearch}`, 'gim') - container.innerHTML = container.innerHTML.replace( - re, - `${currentSearch}` - ) - handleTextColor = true - } + const container = document.getElementById('container') + if (container && container.innerHTML) { + const re = new RegExp(`${keyword}`, 'gim') + container.innerHTML = container.innerHTML.replace(re, `${keyword}`) } }, 100) }) + const { locale } = useGlobal() + + const [page, updatePage] = useState(1) + let hasMore = false + const postsToShow = posts + ? Object.assign(posts).slice(0, BLOG.POSTS_PER_PAGE * page) + : [] + + if (posts) { + const totalCount = posts.length + hasMore = page * BLOG.POSTS_PER_PAGE < totalCount + } + const handleGetMore = () => { + if (!hasMore) return + updatePage(page + 1) + } + return ( -

Search

-
- {posts.map(p => ( -
- - {p.title} - -
{p.summary}
-
- ))} +

Search - {keyword}

+ + {postsToShow.map(p => ( +
+ + {p.title} + +
{p.summary}
+
+ ))} +
+
+ {' '} + {hasMore ? locale.COMMON.MORE : `${locale.COMMON.NO_MORE} 😰`}{' '} +
) diff --git a/themes/empty/LayoutTag.js b/themes/empty/LayoutTag.js index 5cfacb20..e1a549f4 100644 --- a/themes/empty/LayoutTag.js +++ b/themes/empty/LayoutTag.js @@ -1,8 +1,49 @@ +import BLOG from '@/blog.config' +import { useGlobal } from '@/lib/global' +import Link from 'next/link' +import { useState } from 'react' import LayoutBase from './LayoutBase' -export const LayoutTag = (props) => { - const { tag } = props - return - Tag - {tag} - +export const LayoutTag = props => { + const { tag, posts } = props + const { locale } = useGlobal() + + const [page, updatePage] = useState(1) + + let hasMore = false + const postsToShow = posts + ? Object.assign(posts).slice(0, BLOG.POSTS_PER_PAGE * page) + : [] + + if (posts) { + const totalCount = posts.length + hasMore = page * BLOG.POSTS_PER_PAGE < totalCount + } + const handleGetMore = () => { + if (!hasMore) return + updatePage(page + 1) + } + + return ( + + Tag - {tag} + {postsToShow.map(p => ( +
+ + {p.title} + +
{p.summary}
+
+ ))} +
+
+ {' '} + {hasMore ? locale.COMMON.MORE : `${locale.COMMON.NO_MORE} 😰`}{' '} +
+
+
+ ) } diff --git a/themes/empty/LayoutTagIndex.js b/themes/empty/LayoutTagIndex.js index 01a16e62..3fb41c9f 100644 --- a/themes/empty/LayoutTagIndex.js +++ b/themes/empty/LayoutTagIndex.js @@ -1,8 +1,24 @@ +import { useGlobal } from '@/lib/global' +import Link from 'next/link' import LayoutBase from './LayoutBase' export const LayoutTagIndex = (props) => { - // const { tags, categories, postCount, latestPosts } = props + const { tags } = props + const { locale } = useGlobal() return - TagIndex - +
+
{locale.COMMON.TAGS}:
+
+ { tags.map(tag => { + return + }) } +
+
} diff --git a/themes/empty/components/SearchInput.js b/themes/empty/components/SearchInput.js new file mode 100644 index 00000000..6a95ba01 --- /dev/null +++ b/themes/empty/components/SearchInput.js @@ -0,0 +1,87 @@ +import { useRouter } from 'next/router' +import { useGlobal } from '@/lib/global' +import { useImperativeHandle, useRef, useState } from 'react' + +let lock = false + +const SearchInput = ({ currentTag, currentSearch, cRef }) => { + const { locale } = useGlobal() + const router = useRouter() + const searchInputRef = useRef(null) + useImperativeHandle(cRef, () => { + return { + focus: () => { + searchInputRef?.current?.focus() + } + } + }) + const handleSearch = () => { + const key = searchInputRef.current.value + if (key && key !== '') { + router.push({ pathname: '/search/' + key }).then(r => { + console.log('搜索', key) + }) + } else { + router.push({ pathname: '/' }).then(r => { + }) + } + } + const handleKeyUp = (e) => { + if (e.keyCode === 13) { // 回车 + handleSearch(searchInputRef.current.value) + } else if (e.keyCode === 27) { // ESC + cleanSearch() + } + } + const cleanSearch = () => { + searchInputRef.current.value = '' + setShowClean(false) + } + function lockSearchInput () { + lock = true + } + + function unLockSearchInput () { + lock = false + } + const [showClean, setShowClean] = useState(false) + const updateSearchKey = (val) => { + if (lock) { + return + } + searchInputRef.current.value = val + if (val) { + setShowClean(true) + } else { + setShowClean(false) + } + } + + return
+ updateSearchKey(e.target.value)} + defaultValue={currentSearch || ''} + /> + +
+ +
+ + {(showClean && +
+ +
+ )} +
+} + +export default SearchInput diff --git a/themes/fukasawa/LayoutCategoryIndex.js b/themes/fukasawa/LayoutCategoryIndex.js index f7c8196e..2c9f2298 100644 --- a/themes/fukasawa/LayoutCategoryIndex.js +++ b/themes/fukasawa/LayoutCategoryIndex.js @@ -26,5 +26,6 @@ export const LayoutCategoryIndex = (props) => { })}
-
+ + } diff --git a/themes/fukasawa/components/AsideLeft.js b/themes/fukasawa/components/AsideLeft.js index f7efefe6..c9a0175f 100644 --- a/themes/fukasawa/components/AsideLeft.js +++ b/themes/fukasawa/components/AsideLeft.js @@ -6,9 +6,11 @@ import GroupTag from './GroupTag' import SearchInput from './SearchInput' import SiteInfo from './SiteInfo' import Catalog from './Catalog' +import { useRouter } from 'next/router' function AsideLeft (props) { const { tags, currentTag, categories, currentCategory, post, slot } = props + const router = useRouter() return
@@ -27,15 +29,15 @@ function AsideLeft (props) { { BLOG.DESCRIPTION } -
+ {router.asPath !== '/tag' &&

-
+
} -
+ {router.asPath !== '/category' &&

-
+
}

diff --git a/themes/hexo/LayoutSearch.js b/themes/hexo/LayoutSearch.js index 0d4a7c64..60af7cd0 100644 --- a/themes/hexo/LayoutSearch.js +++ b/themes/hexo/LayoutSearch.js @@ -19,7 +19,7 @@ export const LayoutSearch = props => { useEffect(() => { setTimeout(() => { // 自动聚焦到搜索框 - cRef.current.focus() + cRef?.current?.focus() if (currentSearch && !handleTextColor) { const targets = document.getElementsByClassName('replace') for (const container of targets) { diff --git a/themes/hexo/components/BlogPostListEmpty.js b/themes/hexo/components/BlogPostListEmpty.js index 86977fd0..5f75c3e7 100644 --- a/themes/hexo/components/BlogPostListEmpty.js +++ b/themes/hexo/components/BlogPostListEmpty.js @@ -1,3 +1,4 @@ +import { useGlobal } from '@/lib/global' /** * 空白博客 列表 @@ -5,8 +6,9 @@ * @constructor */ const BlogPostListEmpty = ({ currentSearch }) => { + const { locale } = useGlobal() return
-

没有找到文章 {(currentSearch &&

{currentSearch}
)}

+
{locale.COMMON.NO_MORE} {(currentSearch &&
{currentSearch}
)}
} export default BlogPostListEmpty diff --git a/themes/medium/LayoutArchive.js b/themes/medium/LayoutArchive.js index bf9ebdc5..04a6d647 100644 --- a/themes/medium/LayoutArchive.js +++ b/themes/medium/LayoutArchive.js @@ -1,6 +1,69 @@ -export const LayoutArchive = (props) => { - // const { posts, tags, categories, postCount } = props - return
- Archive Page -
+import BLOG from '@/blog.config' +import { useGlobal } from '@/lib/global' +import Link from 'next/link' +import LayoutBase from './LayoutBase' + +export const LayoutArchive = props => { + const { posts } = props + const { locale } = useGlobal() + 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] + } + }) + return ( + +
+ {Object.keys(archivePosts).map(archiveTitle => ( +
+
+ {archiveTitle} +
+
    + {archivePosts[archiveTitle].map(post => ( +
  • +
    + + {post.date.start_date} + {' '} +   + + + {post.title} + + +
    +
  • + ))} +
+
+ ))} +
+
+ ) } diff --git a/themes/medium/LayoutCategoryIndex.js b/themes/medium/LayoutCategoryIndex.js index fcde0a08..5dc91f81 100644 --- a/themes/medium/LayoutCategoryIndex.js +++ b/themes/medium/LayoutCategoryIndex.js @@ -1,8 +1,25 @@ +import { useGlobal } from '@/lib/global' +import Link from 'next/link' import LayoutBase from './LayoutBase' export const LayoutCategoryIndex = (props) => { - // const { tags, allPosts, categories, postCount, latestPosts } = props + const { categories } = props + const { locale } = useGlobal() return - CategoryIndex +
+
+ {locale.COMMON.CATEGORY}: +
+
+ {categories && categories.map(category => { + return +
+ {category.name}({category.count}) +
+ + })} +
+
} diff --git a/themes/medium/LayoutTagIndex.js b/themes/medium/LayoutTagIndex.js index 01a16e62..2c754495 100644 --- a/themes/medium/LayoutTagIndex.js +++ b/themes/medium/LayoutTagIndex.js @@ -1,8 +1,27 @@ +import { useGlobal } from '@/lib/global' +import TagItemMini from './components/TagItemMini' import LayoutBase from './LayoutBase' -export const LayoutTagIndex = (props) => { - // const { tags, categories, postCount, latestPosts } = props - return - TagIndex - +export const LayoutTagIndex = props => { + const { tags } = props + const { locale } = useGlobal() + return ( + +
+
+ + {locale.COMMON.TAGS}: +
+
+ {tags.map(tag => { + return ( +
+ +
+ ) + })} +
+
+
+ ) }