Merge branch 'main' into develop

This commit is contained in:
tangly1024
2022-12-20 20:33:29 +08:00
54 changed files with 216 additions and 197 deletions

View File

@@ -1,2 +1,2 @@
# 环境变量 @see https://www.nextjs.cn/docs/basic-features/environment-variables
NEXT_PUBLIC_VERSION=3.6.4
NEXT_PUBLIC_VERSION=3.6.7

View File

@@ -1,7 +1,5 @@
# NotionNext
一个使用 NextJS + Notion API 实现的,部署在 Vercel 上的静态博客系统。为Notion和所有创作者设计。
<p>
<a aria-label="GitHub commit activity" href="https://github.com/tangly1024/NotionNext/commits/main" title="GitHub commit activity">
<img src="https://img.shields.io/github/commit-activity/m/tangly1024/NotionNext?style=for-the-badge"/>
@@ -17,74 +15,20 @@
</a>
</p>
演示地址:[https://preview.tangly1024.com/](https://preview.tangly1024.com/)
一个使用 NextJS + Notion API 实现的,部署在 Vercel 上的静态博客系统。为Notion和所有创作者设计。
## 继承自Nobelium的亮点 ✨
**🚀 &nbsp;秒开,设备全适配**
## 预览效果
- 快速的页面渲染和响应式设计
- 高效编译器的快速静态页面生成
**🤖 &nbsp;自动,无需重新部署**
- 部署在免费、高速的 Vercel 平台
- 支持增量式更新,更新文章后无需重复部署
**🚙 &nbsp;全功能,完全不操心**
- 评论、搜索、标签、分类
- 订阅、网站统计
- 本地化多语言
- 服务端渲染、优秀的SEO
**🎨 &nbsp;美观,轻松自定义**
- 丰富的配置项,更支持多语言
- 使用 Tailwind CSS轻松实现二次开发
## 特色
- 支持更多的页面,功能,更多特性、欢迎移步[我的博客](https://tangly1024.com/article/notion-next)查看
- 支持多主题切换
在线演示:[https://preview.tangly1024.com/](https://preview.tangly1024.com/) ,项目支持多主题切换,没找到喜欢的主题?[贡献](/CONTRIBUTING.md)一个吧~
| Next | Medium | Hexo | Fukasawa |
|--|--|--|--|
| <img src='./docs/theme-next.png' width='300'/> [预览NEXT](https://preview.tangly1024.com/?theme=next) | <img src='./docs/theme-medium.png' width='300'/> [预览MEDIUM](https://preview.tangly1024.com/?theme=medium) | <img src='./docs/theme-hexo.png' width='300'/> [预览HEXO](https://preview.tangly1024.com/?theme=hexo) | <img src='./docs/theme-fukasawa.png' width='300'/> [预览FUKASAWA](https://preview.tangly1024.com/?theme=fukasawa) |
*只需修改`blog.config.js`文件的`THEME`即可实现主题切换。* 没找到喜欢的主题?[贡献](/CONTRIBUTING.md)一个吧~
## 我要如何开始?
## 快速起步
- 给这个项目点个小星星 😉
- 将 [这个 Notion 模板](https://tanghh.notion.site/02ab3b8678004aa69e9e415905ef32a5) 制作副本,并分享这个页面给所有人
- [Fork](https://github.com/tangly1024/NotionNext/fork) 这个项目
- _(可选)_ 用自己的图片替换 `/public` 文件夹里的 `avatar.jpg``favicon.svg``favicon.ico`
-`blog.config.js` 配置相关选项,`NOTION_PAGE_ID`: 你刚刚分享出去的 Notion 页面网址中的页面 ID通常是网址中工作区地址后的 32 位字符串
- 在 [Vercel](https://vercel.com)中部署项目
- **稍微等等就可以访问了!** 简单吗?
更多项目特性及配置的说明,请移步 [NotionNext文档](https://docs.tangly1024.com/zh) 查看,文档正在完善中,欢迎 [参与编辑](https://github.com/tangly1024/nextjs-docs-notion-next)
## 快速开发
- 需要安装Nodejs环境
```bash
yarn # 安装依赖
yarn run dev # 本地开发
yarn run build # 本地打包编译
yarn run start # 本地启动NextJS服务
```
## 引用技术
- **框架**: [Next.js](https://nextjs.org)
- **样式**: [Tailwind CSS](https://www.tailwindcss.cn/) 和 `@tailwindcss/jit` compiler
- **渲染**: [React-notion-x](https://github.com/NotionX/react-notion-x)
- **评论**: [Giscus](https://giscus.app/zh-CN), [Gitalk](https://gitalk.github.io), [Cusdis](https://cusdis.com), [Utterances](https://utteranc.es)
- **图标**[fontawesome v5.15](https://fontawesome.com/v5.15/icons?d=gallery)
## 更新日志
请移步 [更新文档](https://docs.tangly1024.com/zh/changelog)查看
只需几分钟即可搭建您的个人站点,欢迎移步[我的博客](https://tangly1024.com/article/notion-next) 查看教程
## 致谢
@@ -120,12 +64,17 @@ yarn run start # 本地启动NextJS服务
<td align="center"><a href="https://github.com/SwwweetOrange"><img src="https://avatars.githubusercontent.com/u/71168966" width="64px;" alt="SwwweetOrange"/><br/><sub><b>SwwweetOrange</b></sub></a><br/><a href="https://github.com/tangly1024/NotionNext/commits?author=SwwweetOrange" title="SwwweetOrange" >🔧 🐛</a></td>
<td align="center"><a href="https://github.com/Ylarod"><img src="https://avatars.githubusercontent.com/u/30978685" width="64px;" alt="Ylarod"/><br/><sub><b>Ylarod</b></sub></a><br/><a href="https://github.com/tangly1024/NotionNext/commits?author=Ylarod" title="Ylarod" >🔧 🐛</a></td>
v
</tr>
</table>
十分期待你的[贡献](/CONTRIBUTING.md),一起来完善这个项目~
## 引用技术
- **框架**: [Next.js](https://nextjs.org)
- **样式**: [Tailwind CSS](https://www.tailwindcss.cn/) 和 `@tailwindcss/jit` compiler
- **渲染**: [React-notion-x](https://github.com/NotionX/react-notion-x)
- **评论**: [Giscus](https://giscus.app/zh-CN), [Gitalk](https://gitalk.github.io), [Cusdis](https://cusdis.com), [Utterances](https://utteranc.es)
- **图标**: [Fontawesome](https://fontawesome.com/v6/icons/)
## License

View File

@@ -43,6 +43,12 @@ const CommonHead = ({ meta, children }) => {
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:description" content={description} />
<meta name="twitter:title" content={title} />
{ BLOG.CUSTOM_FONT
? BLOG.CUSTOM_FONT_URL?.map(fontUrl =>
<link href={`${fontUrl}`} key={fontUrl} rel="stylesheet"/>)
: <link href='https://fonts.font.im/css2?family=Noto+Serif+SC&display=optional' rel="stylesheet"/> }
{JSON.parse(BLOG.ANALYTICS_BUSUANZI_ENABLE) && <meta name="referrer" content="no-referrer-when-downgrade" />}
{meta?.type === 'Post' && (
<>

View File

@@ -8,7 +8,6 @@ import 'prismjs/plugins/line-numbers/prism-line-numbers.css'
// 所有语言的prismjs 使用autoloader引入
import 'prismjs/plugins/autoloader/prism-autoloader'
// mermaid图
import mermaid from 'mermaid'
import BLOG from '@/blog.config'
/**
@@ -18,6 +17,7 @@ import BLOG from '@/blog.config'
const PrismMac = () => {
React.useEffect(() => {
renderPrismMac()
renderMermaid()
// 折叠代码行号bug
const observer = new MutationObserver(mutationsList => {
@@ -35,21 +35,10 @@ const PrismMac = () => {
return <></>
}
function renderPrismMac() {
const container = document?.getElementById('container-inner')
const codeToolBars = container?.getElementsByClassName('code-toolbar')
// Add line numbers
const codeBlocks = container?.getElementsByTagName('pre')
if (codeBlocks) {
Array.from(codeBlocks).forEach(item => {
if (!item.classList.contains('line-numbers')) {
item.classList.add('line-numbers')
item.style.whiteSpace = 'pre-wrap'
}
})
}
/**
* 将mermaid语言 渲染成图片
*/
const renderMermaid = async() => {
// 支持 Mermaid
const mermaidPres = document.querySelectorAll('pre.notion-code.language-mermaid')
if (mermaidPres) {
@@ -73,9 +62,26 @@ function renderPrismMac() {
}
}
if (needLoad) {
mermaid.contentLoaded()
const asyncMermaid = await import('mermaid')
asyncMermaid.default.contentLoaded()
}
}
}
function renderPrismMac() {
const container = document?.getElementById('container-inner')
const codeToolBars = container?.getElementsByClassName('code-toolbar')
// Add line numbers
const codeBlocks = container?.getElementsByTagName('pre')
if (codeBlocks) {
Array.from(codeBlocks).forEach(item => {
if (!item.classList.contains('line-numbers')) {
item.classList.add('line-numbers')
item.style.whiteSpace = 'pre-wrap'
}
})
}
// 重新渲染之前检查所有的多余text

View File

@@ -94,11 +94,6 @@ bszTag = {
}
}
// 修复Node同构代码的问题
if (typeof document !== 'undefined') {
fetch()
}
module.exports = {
fetch
}

View File

@@ -50,4 +50,4 @@ const fontFamilies = {
]
}
module.exports = fontFamilies
module.exports = { fontFamilies }

View File

@@ -14,7 +14,7 @@ import { isIterable } from '../utils'
* @returns {Promise<{}|*[]>}
*/
export function getAllCategories({ allPages, categoryOptions, sliceCount = 0 }) {
const allPosts = allPages.filter(page => page.type === 'Post')
const allPosts = allPages.filter(page => page.type === 'Post' && page.status === 'Published')
if (!allPosts || !categoryOptions) {
return []
}

View File

@@ -8,7 +8,7 @@ import { isIterable } from '../utils'
* @returns {Promise<{}|*[]>}
*/
export function getAllTags({ allPages, sliceCount = 0, tagOptions }) {
const allPosts = allPages.filter(page => page.type === 'Post')
const allPosts = allPages.filter(page => page.type === 'Post' && page.status === 'Published')
if (!allPosts || !tagOptions) {
return []

View File

@@ -212,17 +212,16 @@ async function getPageRecordMapByNotionAPI({ pageId, from }) {
collectionData.push(properties)
}
}
// 读取映射 配置
let postCount = 0
// 获取page作为自定义菜单
const customNav = getCustomNav({ allPages: collectionData.filter(post => post.type === 'Page' && post.status === 'Published') })
// 文章计数
let postCount = 0
const allPages = collectionData.filter(post => {
if (post.type === 'Post' && post.status === 'Published') {
postCount++
}
return post &&
post.type &&
(post.type === 'Post' || post.type === 'Page') &&

View File

@@ -3,6 +3,7 @@ import { NotionAPI } from 'notion-client'
import BLOG from '@/blog.config'
import formatDate from '../formatDate'
import { defaultMapImageUrl } from 'react-notion-x'
import md5 from 'js-md5'
export default async function getPageProperties(id, block, schema, authToken, tagOptions, siteInfo) {
const rawProperties = Object.entries(block?.[id]?.value?.properties || [])
@@ -89,6 +90,7 @@ export default async function getPageProperties(id, block, schema, authToken, ta
properties.pageIcon = getImageUrl(block[id].value?.format?.page_icon, block[id].value) ?? ''
properties.page_cover = getImageUrl(block[id].value?.format?.page_cover, block[id].value) ?? siteInfo?.pageCover
properties.content = value.content ?? []
properties.password = properties.password ? md5(properties.slug + properties.password) : ''
properties.tagItems = properties?.tags?.map(tag => {
return { name: tag, color: tagOptions?.find(t => t.value === tag)?.color || 'gray' }
}) || []

View File

@@ -0,0 +1,90 @@
import { getTextContent } from 'notion-utils'
const indentLevels = {
header: 0,
sub_header: 1,
sub_sub_header: 2
}
/**
* @see https://github.com/NotionX/react-notion-x/blob/master/packages/notion-utils/src/get-page-table-of-contents.ts
* Gets the metadata for a table of contents block by parsing the page's
* H1, H2, and H3 elements.
*/
export const getPageTableOfContents = (page, recordMap) => {
const contents = (page.content ?? [])
const toc = getBlockHeader(contents, recordMap)
const indentLevelStack = [
{
actual: -1,
effective: -1
}
]
// Adjust indent levels to always change smoothly.
// This is a little tricky, but the key is that when increasing indent levels,
// they should never jump more than one at a time.
for (const tocItem of toc) {
const { indentLevel } = tocItem
const actual = indentLevel
do {
const prevIndent = indentLevelStack[indentLevelStack.length - 1]
const { actual: prevActual, effective: prevEffective } = prevIndent
if (actual > prevActual) {
tocItem.indentLevel = prevEffective + 1
indentLevelStack.push({
actual,
effective: tocItem.indentLevel
})
} else if (actual === prevActual) {
tocItem.indentLevel = prevEffective
break
} else {
indentLevelStack.pop()
}
// eslint-disable-next-line no-constant-condition
} while (true)
}
return toc
}
/**
* 重写获取目录方法
*/
function getBlockHeader(contents, recordMap, toc) {
if (!toc) {
toc = []
}
if (!contents) {
return toc
}
for (const blockId of contents) {
const block = recordMap.block[blockId]?.value
if (!block) {
continue
}
const { type } = block
if (type.indexOf('header') >= 0) {
const existed = toc.find(e => e.id === blockId)
if (!existed) {
toc.push({
id: blockId,
type,
text: getTextContent(block.properties?.title),
indentLevel: indentLevels[type]
})
}
}
if (block.content?.length > 0) {
getBlockHeader(block.content, recordMap, toc)
}
}
return toc
}

View File

@@ -1,6 +1,6 @@
{
"name": "notion-next",
"version": "3.6.4",
"version": "3.6.7",
"homepage": "https://github.com/tangly1024/NotionNext.git",
"license": "MIT",
"repository": {
@@ -30,6 +30,7 @@
"eslint-plugin-react-hooks": "^4.6.0",
"feed": "^4.2.2",
"gitalk": "^1.7.2",
"js-md5": "^0.7.3",
"localStorage": "^1.0.4",
"lodash.throttle": "^4.1.1",
"mark.js": "^8.11.1",

View File

@@ -8,6 +8,8 @@ import { idToUuid } from 'notion-utils'
import Router from 'next/router'
import { isBrowser } from '@/lib/utils'
import { getNotion } from '@/lib/notion/getNotion'
import md5 from 'js-md5'
import { getPageTableOfContents } from '@/lib/notion/getPageTableOfContents'
/**
* 根据notion的slug访问页面
@@ -28,6 +30,11 @@ const Slug = props => {
if (post?.password && post?.password !== '') {
setLock(true)
} else {
if (!lock && post?.blockMap?.block) {
post.content = Object.keys(post.blockMap.block)
post.toc = getPageTableOfContents(post, post.blockMap)
}
setLock(false)
}
}, [post])
@@ -51,10 +58,12 @@ const Slug = props => {
* 验证文章密码
* @param {*} result
*/
const validPassword = result => {
if (result) {
const validPassword = passInput => {
if (passInput && md5(post.slug + passInput) === post.password) {
setLock(false)
return true
}
return false
}
props = { ...props, lock, setLock, validPassword }

View File

@@ -23,6 +23,8 @@ import { GlobalContextProvider } from '@/lib/global'
import { DebugPanel } from '@/components/DebugPanel'
import { ThemeSwitch } from '@/components/ThemeSwitch'
import { Fireworks } from '@/components/Fireworks'
import React from 'react'
import { loadExternalResource } from '@/lib/utils'
const Ackee = dynamic(() => import('@/components/Ackee'), { ssr: false })
const Gtag = dynamic(() => import('@/components/Gtag'), { ssr: false })
@@ -47,10 +49,13 @@ const MyApp = ({ Component, pageProps }) => {
{JSON.parse(BLOG.FIREWORKS) && <Fireworks/>}
</>
// 延迟加载fontAwesome
React.useEffect(() => {
loadExternalResource(BLOG.FONT_AWESOME_PATH, 'css')
}, [])
return (
<GlobalContextProvider>
{/* FontawesomeCDN */}
<link rel="stylesheet" href={BLOG.FONT_AWESOME_PATH} referrerPolicy="no-referrer" />
{externalPlugins}
<Component {...pageProps} />
</GlobalContextProvider>

View File

@@ -15,10 +15,6 @@ class MyDocument extends Document {
<Head>
<link rel='icon' href='/favicon.ico' />
<link rel='icon' href='/favicon.svg' type='image/svg+xml' />
{ BLOG.CUSTOM_FONT
? BLOG.CUSTOM_FONT_URL?.map(fontUrl =>
<link href={`${fontUrl}`} key={fontUrl} rel='stylesheet' />)
: <link href='https://fonts.font.im/css2?family=Noto+Serif+SC&display=swap' rel='stylesheet' /> }
<CommonScript />
</Head>

View File

@@ -132,11 +132,6 @@ nav {
backdrop-filter: blur(10px);
}
.dark .glassmorphism {
background: rgba(31, 41, 55, 0.75);
-webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(10px);
}
.medium-zoom-overlay {
background: none !important;

View File

@@ -392,7 +392,7 @@
.notion-h {
position: relative;
display: block;
/* display: block; */
font-weight: 600;
line-height: 1.3;
padding: 3px 2px;
@@ -427,7 +427,7 @@
margin-top: 2px;
} */
.notion-h2 {
font-size: 1.5em;
font-size: 1.4em;
margin-top: 1.1em;
}
.notion-h3 {

View File

@@ -1,5 +1,5 @@
const BLOG = require('./blog.config')
const fontFamilies = require('./lib/font')
const { fontFamilies } = require('./lib/font')
module.exports = {
purge: ['./pages/**/*.js', './components/**/*.js', './layouts/**/*.js', './themes/**/*.js'],

View File

@@ -1,4 +1,3 @@
import { getPageTableOfContents } from 'notion-utils'
import LayoutBase from './LayoutBase'
import { ArticleLock } from './components/ArticleLock'
import NotionPage from '@/components/NotionPage'
@@ -12,15 +11,10 @@ export const LayoutSlug = props => {
return <LayoutBase {...props} />
}
if (!lock && post?.blockMap?.block) {
post.content = Object.keys(post.blockMap.block)
post.toc = getPageTableOfContents(post, post.blockMap)
}
return (
<LayoutBase {...props}>
{lock && <ArticleLock password={post.password} validPassword={validPassword} />}
{lock && <ArticleLock validPassword={validPassword} />}
{!lock && <div id="notion-article" className="px-2">

View File

@@ -8,14 +8,12 @@ import { useGlobal } from '@/lib/global'
* @returns
*/
export const ArticleLock = props => {
const { password, validPassword } = props
const { validPassword } = props
const { locale } = useGlobal()
const submitPassword = () => {
const p = document.getElementById('password')
if (p && p.value && p.value === password) {
validPassword(true)
} else {
if (!validPassword(p?.value)) {
const tips = document.getElementById('tips')
if (tips) {
tips.innerHTML = ''

View File

@@ -1,7 +1,7 @@
import React from 'react'
import { RecentComments } from '@waline/client'
import BLOG from '@/blog.config'
import Link from 'next/link'
import { RecentComments } from '@waline/client'
/**
* @see https://waline.js.org/guide/get-started.html

View File

@@ -2,7 +2,8 @@ import BLOG from '@/blog.config'
import Live2D from '@/components/Live2D'
import { useGlobal } from '@/lib/global'
import Link from 'next/link'
import ExampleRecentComments from './ExampleRecentComments'
import dynamic from 'next/dynamic'
const ExampleRecentComments = dynamic(() => import('./ExampleRecentComments'))
export const SideBar = (props) => {
const { locale } = useGlobal()

View File

@@ -1,19 +1,13 @@
import { getPageTableOfContents } from 'notion-utils'
import ArticleDetail from './components/ArticleDetail'
import LayoutBase from './LayoutBase'
import { ArticleLock } from './components/ArticleLock'
export const LayoutSlug = (props) => {
const { post, lock, validPassword } = props
if (!lock && post?.blockMap?.block) {
post.content = Object.keys(post.blockMap.block)
post.toc = getPageTableOfContents(post, post.blockMap)
}
const { lock, validPassword } = props
return (
<LayoutBase {...props} >
{!lock && <ArticleDetail {...props} />}
{lock && <ArticleLock password={post.password} validPassword={validPassword} />}
{lock && <ArticleLock validPassword={validPassword} />}
</LayoutBase>
)
}

View File

@@ -8,14 +8,12 @@ import { useGlobal } from '@/lib/global'
* @returns
*/
export const ArticleLock = props => {
const { password, validPassword } = props
const { validPassword } = props
const { locale } = useGlobal()
const submitPassword = () => {
const p = document.getElementById('password')
if (p && p.value && p.value === password) {
validPassword(true)
} else {
if (!validPassword(p?.value)) {
const tips = document.getElementById('tips')
if (tips) {
tips.innerHTML = ''

View File

@@ -39,7 +39,7 @@ export const LayoutArchive = (props) => {
}, [])
return <LayoutBase {...props} >
<Card className='w-full'>
<div className="mb-10 pb-20 bg-white md:p-12 p-3 dark:bg-gray-800 min-h-full">
<div className="mb-10 pb-20 bg-white md:p-12 p-3 min-h-full dark:bg-hexo-black-gray">
{Object.keys(archivePosts).map(archiveTitle => (
<BlogPostArchive
key={archiveTitle}

View File

@@ -1,4 +1,3 @@
import { getPageTableOfContents } from 'notion-utils'
import { useRef } from 'react'
import { ArticleLock } from './components/ArticleLock'
import HeaderArticle from './components/HeaderArticle'
@@ -15,6 +14,7 @@ import { isBrowser } from '@/lib/utils'
export const LayoutSlug = props => {
const { post, lock, validPassword } = props
const drawerRight = useRef(null)
if (!post) {
return <LayoutBase
@@ -25,12 +25,6 @@ export const LayoutSlug = props => {
></LayoutBase>
}
if (!lock && post?.blockMap?.block) {
post.content = Object.keys(post.blockMap.block)
post.toc = getPageTableOfContents(post, post.blockMap)
}
const drawerRight = useRef(null)
const targetRef = isBrowser() ? document.getElementById('container') : null
const floatSlot = <>
@@ -53,7 +47,7 @@ export const LayoutSlug = props => {
floatSlot={floatSlot}
>
<div className="w-full lg:shadow-sm lg:hover:shadow lg:border lg:rounded-xl lg:px-2 lg:py-4 bg-white dark:bg-hexo-black-gray dark:border-black">
{lock && <ArticleLock password={post.password} validPassword={validPassword} />}
{lock && <ArticleLock validPassword={validPassword} />}
{!lock && <div id="container" className="overflow-x-auto flex-grow mx-auto md:w-full md:px-5 ">

View File

@@ -8,13 +8,11 @@ import { useGlobal } from '@/lib/global'
* @returns
*/
export const ArticleLock = props => {
const { password, validPassword } = props
const { validPassword } = props
const { locale } = useGlobal()
const submitPassword = () => {
const p = document.getElementById('password')
if (p && p.value && p.value === password) {
validPassword(true)
} else {
if (!validPassword(p?.value)) {
const tips = document.getElementById('tips')
if (tips) {
tips.innerHTML = ''

View File

@@ -4,12 +4,13 @@ import formatDate from '@/lib/formatDate'
import BLOG from '@/blog.config'
export default function HeaderArticle({ post, siteInfo }) {
const { locale } = useGlobal()
if (!post) {
return <></>
}
const headerImage = post?.page_cover ? `url("${post.page_cover}")` : `url("${siteInfo?.pageCover}")`
const { locale } = useGlobal()
const date = formatDate(
post?.date?.start_date || post?.createdTime,
locale.LOCALE

View File

@@ -1,9 +1,9 @@
import React from 'react'
import { RecentComments } from '@waline/client'
import BLOG from '@/blog.config'
import Card from '@/themes/hexo/components/Card'
import { useGlobal } from '@/lib/global'
import Link from 'next/link'
import { RecentComments } from '@waline/client'
/**
* @see https://waline.js.org/guide/get-started.html

View File

@@ -14,7 +14,7 @@ export function InfoCard (props) {
}}
>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img src={siteInfo?.icon} className='rounded-full' width={120}/>
<img src={siteInfo?.icon} className='rounded-full' width={120} alt={BLOG.AUTHOR}/>
</div>
<div className='text-center text-xl pb-4'>{BLOG.AUTHOR}</div>
<div className='text-sm text-center'>{BLOG.BIO}</div>

View File

@@ -16,7 +16,7 @@ const NavButtonGroup = (props) => {
return <nav id='home-nav-button' className={'md:h-52 md:mt-6 xl:mt-32 px-5 py-2 mt-8 flex flex-wrap md:max-w-5xl space-y-2 md:space-y-0 md:flex justify-center max-h-80 overflow-auto'}>
{categories.map(category => {
return <Link key={`${category.name}`} title={`${category.name}`} href={`/category/${category.name}`} passHref>
<a className='text-center w-full md:mx-6 md:w-40 md:h-14 lg:h-20 h-14 justify-center items-center flex border-2 cursor-pointer rounded-lg glassmorphism hover:bg-white hover:text-black duration-200 font-bold hover:scale-110 transform'>{category.name}</a>
<a className='text-center w-full md:mx-6 md:w-40 md:h-14 lg:h-20 h-14 justify-center items-center flex border-2 cursor-pointer rounded-lg glassmorphism hover:bg-white hover:text-black duration-200 font-bold hover:scale-110 transform'>{category.name}</a>
</Link>
})}
</nav>

View File

@@ -6,9 +6,10 @@ import Catalog from './Catalog'
import { InfoCard } from './InfoCard'
import { AnalyticsCard } from './AnalyticsCard'
import CONFIG_HEXO from '../config_hexo'
import HexoRecentComments from './HexoRecentComments'
import BLOG from '@/blog.config'
import dynamic from 'next/dynamic'
const HexoRecentComments = dynamic(() => import('./HexoRecentComments'))
/**
* Hexo主题右侧栏
* @param {*} props

View File

@@ -5,7 +5,7 @@ import BLOG from '@/blog.config'
export const LayoutCategory = props => {
const { category } = props
const slotTop = <div className='flex items-center font-sans p-8'><div className='text-xl'><i className='mr-2 fas fa-th' />分类</div>{category}</div>
const slotTop = <div className='flex items-center font-sans py-8'><div className='text-xl'><i className='mr-2 fas fa-th' />分类</div>{category}</div>
return <LayoutBase {...props} slotTop={slotTop}>
{BLOG.POST_LIST_STYLE === 'page' ? <BlogPostListPage {...props} /> : <BlogPostListScroll {...props} />}

View File

@@ -1,5 +1,3 @@
import { getPageTableOfContents } from 'notion-utils'
import LayoutBase from './LayoutBase'
import { useGlobal } from '@/lib/global'
import React from 'react'
@@ -16,11 +14,6 @@ export const LayoutSlug = props => {
/>
}
if (!lock && post?.blockMap?.block) {
post.content = Object.keys(post.blockMap.block)
post.toc = getPageTableOfContents(post, post.blockMap)
}
const slotRight = post?.toc && post?.toc?.length > 3 && (
<div key={locale.COMMON.TABLE_OF_CONTENTS} >
<Catalog toc={post.toc} />
@@ -29,7 +22,7 @@ export const LayoutSlug = props => {
return (
<LayoutBase showInfoCard={true} slotRight={slotRight} {...props} >
{!lock ? <ArticleDetail {...props} /> : <ArticleLock password={post.password} validPassword={validPassword} />}
{!lock ? <ArticleDetail {...props} /> : <ArticleLock validPassword={validPassword} />}
</LayoutBase>
)
}

View File

@@ -5,7 +5,7 @@ import BlogPostListPage from './components/BlogPostListPage'
export const LayoutTag = (props) => {
const { tag } = props
const slotTop = <div className='flex items-center font-sans p-8'><div className='text-xl'><i className='mr-2 fas fa-tag'/>标签</div>{tag}</div>
const slotTop = <div className='flex items-center font-sans py-8'><div className='text-xl'><i className='mr-2 fas fa-tag'/>标签</div>{tag}</div>
return <LayoutBase {...props} slotTop={slotTop}>
{BLOG.POST_LIST_STYLE === 'page' ? <BlogPostListPage {...props} /> : <BlogPostListScroll {...props} />}

View File

@@ -36,7 +36,7 @@ export const ArticleDetail = props => {
<Link href="/about" passHref>
<div className='flex pt-2'>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img src={siteInfo?.icon} className='rounded-full cursor-pointer' width={22} />
<img src={siteInfo?.icon} className='rounded-full cursor-pointer' width={22} alt={BLOG.AUTHOR} />
<div className="mr-3 ml-2 my-auto text-green-500 cursor-pointer">
{BLOG.AUTHOR}

View File

@@ -8,14 +8,12 @@ import { useGlobal } from '@/lib/global'
* @returns
*/
export const ArticleLock = props => {
const { password, validPassword } = props
const { validPassword } = props
const { locale } = useGlobal()
const submitPassword = () => {
const p = document.getElementById('password')
if (p && p.value && p.value === password) {
validPassword(true)
} else {
if (!validPassword(p?.value)) {
const tips = document.getElementById('tips')
if (tips) {
tips.innerHTML = ''

View File

@@ -9,7 +9,7 @@ const InfoCard = (props) => {
<div className='items-center justify-center font-sans '>
<div className='hover:scale-105 transform duration-200 cursor-pointer flex justify-center' onClick={ () => { Router.push('/about') }}>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img src={siteInfo?.icon} className='rounded-full' width={120}/>
<img src={siteInfo?.icon} className='rounded-full' width={120} alt={BLOG.AUTHOR}/>
</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>

View File

@@ -39,7 +39,7 @@ export const LayoutArchive = (props) => {
return (
<LayoutBase {...props}>
<div className="mb-10 pb-20 bg-white md:p-12 p-3 dark:bg-gray-800 shadow-md min-h-full">
<div className="mb-10 pb-20 bg-white md:p-12 p-3 dark:bg-hexo-black-gray shadow-md min-h-full">
{Object.keys(archivePosts).map(archiveTitle => (
<BlogPostArchive
key={archiveTitle}

View File

@@ -6,7 +6,7 @@ export const LayoutCategoryIndex = (props) => {
const { allPosts, categories } = props
const { locale } = useGlobal()
return <LayoutBase totalPosts={allPosts} {...props}>
<div className='bg-white dark:bg-gray-700 px-10 py-10 shadow h-full'>
<div className='bg-white dark:bg-hexo-black-gray px-10 py-10 shadow h-full'>
<div className='dark:text-gray-200 mb-5'>
<i className='mr-4 fas faTh' />{locale.COMMON.CATEGORY}:
</div>

View File

@@ -1,4 +1,3 @@
import { getPageTableOfContents } from 'notion-utils'
import TocDrawerButton from './components/TocDrawerButton'
import LayoutBase from './LayoutBase'
import Card from './components/Card'
@@ -12,11 +11,6 @@ import { isBrowser } from '@/lib/utils'
export const LayoutSlug = (props) => {
const { post, latestPosts, lock, validPassword } = props
if (!lock && post?.blockMap?.block) {
post.content = Object.keys(post.blockMap.block)
post.toc = getPageTableOfContents(post, post.blockMap)
}
const drawerRight = useRef(null)
const targetRef = isBrowser() ? document.getElementById('container') : null
const floatSlot = post?.toc?.length > 1
@@ -45,7 +39,7 @@ export const LayoutSlug = (props) => {
{!lock && <ArticleDetail {...props} />}
{lock && <ArticleLock password={post.password} validPassword={validPassword} />}
{lock && <ArticleLock validPassword={validPassword} />}
{/* 悬浮目录按钮 */}
<div className='block lg:hidden'>

View File

@@ -6,7 +6,7 @@ export const LayoutTagIndex = (props) => {
const { tags } = props
const { locale } = useGlobal()
return <LayoutBase {...props}>
<div className='bg-white dark:bg-gray-700 px-10 py-10 shadow h-full'>
<div className='bg-white dark:bg-hexo-black-gray px-10 py-10 shadow h-full'>
<div className='dark:text-gray-200 mb-5'><i className='fas fa-tags mr-4'/>{locale.COMMON.TAGS}:</div>
<div id='tags-list' className='duration-200 flex flex-wrap'>
{ tags.map(tag => {

View File

@@ -8,14 +8,12 @@ import { useGlobal } from '@/lib/global'
* @returns
*/
export const ArticleLock = props => {
const { password, validPassword } = props
const { validPassword } = props
const { locale } = useGlobal()
const submitPassword = () => {
const p = document.getElementById('password')
if (p && p.value && p.value === password) {
validPassword(true)
} else {
if (!validPassword(p?.value)) {
const tips = document.getElementById('tips')
if (tips) {
tips.innerHTML = ''

View File

@@ -86,7 +86,7 @@ const BlogPostCard = ({ post, showSummary }) => {
<div className="text-right border-t pt-8 border-dashed">
<Link href={`${BLOG.SUB_PATH}/${post.slug}`}>
<a className="hover:bg-opacity-100 hover:underline transform duration-300 p-3 text-white bg-gray-800 dark:bg-black cursor-pointer">
<a className="hover:bg-opacity-100 hover:underline transform duration-300 p-3 text-white bg-gray-800 cursor-pointer">
{locale.COMMON.ARTICLE_DETAIL}
<i className="ml-1 fas fa-angle-right" />
</a>

View File

@@ -14,7 +14,7 @@ const Footer = ({ title }) => {
return (
<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'
className='dark:bg-gray-800 flex-shrink-0 justify-center text-center m-auto w-full leading-6 text-sm p-6 bg-white dark:text-gray-400'
>
<DarkModeButton/>
<span>

View File

@@ -9,7 +9,7 @@ const InfoCard = (props) => {
<div className='flex flex-col items-center justify-center '>
<div className='hover:rotate-45 hover:scale-125 transform duration-200 cursor-pointer' onClick={ () => { Router.push('/') }}>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img src={siteInfo?.icon} className='rounded-full' width={120}/>
<img src={siteInfo?.icon} className='rounded-full' width={120} alt={BLOG.AUTHOR}/>
</div>
<div className='text-2xl font-serif dark:text-white py-2 hover:scale-105 transform duration-200'>{BLOG.AUTHOR}</div>
<div className='font-light dark:text-white py-2 hover:scale-105 transform duration-200 text-center'>{BLOG.BIO}</div>

View File

@@ -4,7 +4,7 @@ import React from 'react'
const Logo = props => {
const { siteInfo, className } = props
return <Link href='/' passHref>
<div className={'flex flex-col justify-center items-center cursor-pointer bg-black space-y-3 font-bold ' + className}>
<div className={'flex flex-col justify-center items-center cursor-pointer bg-black dark:bg-gray-800 space-y-3 font-bold ' + className}>
<div className='font-serif text-xl text-white'> {siteInfo?.title}</div>
<div className='text-sm text-gray-300 font-light text-center'> {siteInfo?.description}</div>
</div>

View File

@@ -1,7 +1,7 @@
import React from 'react'
import { RecentComments } from '@waline/client'
import BLOG from '@/blog.config'
import Link from 'next/link'
import { RecentComments } from '@waline/client'
/**
* @see https://waline.js.org/guide/get-started.html

View File

@@ -16,7 +16,7 @@ const PaginationNumber = ({ page, totalPage }) => {
const pages = generatePages(pagePrefix, page, currentPage, totalPage)
return (
<div className="my-5 flex justify-center items-end font-medium text-black hover:shadow-xl duration-500 bg-white dark:bg-gray-700 dark:text-gray-300 py-3 shadow space-x-2">
<div className="my-5 flex justify-center items-end font-medium text-black hover:shadow-xl duration-500 bg-white dark:bg-hexo-black-gray dark:text-gray-300 py-3 shadow space-x-2">
{/* 上一页 */}
<Link
href={{

View File

@@ -66,7 +66,7 @@ const SearchInput = ({ currentTag, currentSearch, cRef }) => {
ref={searchInputRef}
type='text'
placeholder={currentTag ? `${locale.SEARCH.TAGS} #${currentTag}` : `${locale.SEARCH.ARTICLES}`}
className={'w-full text-sm pl-4 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-4 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}

View File

@@ -6,8 +6,9 @@ import CategoryGroup from './CategoryGroup'
import TagGroups from './TagGroups'
import CONFIG_NEXT from '../config_next'
import { useRouter } from 'next/router'
import NextRecentComments from './NextRecentComments'
import BLOG from '@/blog.config'
import dynamic from 'next/dynamic'
const NextRecentComments = dynamic(() => import('./NextRecentComments'))
/**
* 侧边平铺

View File

@@ -11,7 +11,7 @@ let windowTop = 0
* @constructor
*/
const StickyBar = ({ children }) => {
if (!children) return <></>
// 滚动页面时导航条样式调整
const scrollTrigger = useCallback(throttle(() => {
if (CONFIG_NEXT.NAV_TYPE === 'normal') {
return
@@ -37,9 +37,11 @@ const StickyBar = ({ children }) => {
}
}, [])
if (!children) return <></>
return (
<div id='sticky-bar' className='sticky flex-grow justify-center top-0 duration-500 z-10 pb-16'>
<div className='bg-white dark:bg-black px-5 absolute shadow-md w-full scroll-hidden'>
<div className='bg-white dark:bg-hexo-black-gray px-5 absolute shadow-md w-full scroll-hidden'>
<div id='tag-container' className="md:pl-3 overflow-x-auto">
{children}
</div>

View File

@@ -11,10 +11,6 @@ import Progress from './Progress'
* @constructor
*/
const Toc = ({ toc }) => {
// 无目录就直接返回空
if (!toc || toc.length < 1) {
return <></>
}
// 监听滚动事件
React.useEffect(() => {
window.addEventListener('scroll', actionSectionScrollSpy)
@@ -58,6 +54,11 @@ const Toc = ({ toc }) => {
tRef?.current?.scrollTo({ top: 28 * index, behavior: 'smooth' })
}, throttleMs))
// 无目录就直接返回空
if (!toc || toc.length < 1) {
return <></>
}
return <div className='px-3'>
<div className='w-full pb-1'>
<Progress />

View File

@@ -89,7 +89,7 @@ const TopNav = (props) => {
{/* 导航栏 */}
<div id='sticky-nav' className={`${CONFIG_NEXT.NAV_TYPE !== 'normal' ? 'fixed' : ''} lg:relative w-full top-0 z-20 transform duration-500`}>
<div className='w-full flex justify-between items-center p-4 bg-black text-white'>
<div className='w-full flex justify-between items-center p-4 bg-black dark:bg-gray-800 text-white'>
{/* 左侧LOGO 标题 */}
<div className='flex flex-none flex-grow-0'>
<div onClick={toggleMenuOpen} className='w-8 cursor-pointer'>