This commit is contained in:
tangly1024
2021-09-27 09:33:21 +08:00
parent 22ca7f6d63
commit dfc0f645d4
76 changed files with 3650 additions and 2 deletions

31
.eslintrc.js Normal file
View File

@@ -0,0 +1,31 @@
module.exports = {
env: {
browser: true,
es2021: true,
node: true
},
extends: [
'plugin:react/recommended',
'plugin:@next/next/recommended',
'standard'
],
parserOptions: {
ecmaFeatures: {
jsx: true
},
ecmaVersion: 12,
sourceType: 'module'
},
plugins: ['react'],
settings: {
react: {
version: 'detect'
}
},
rules: {
'react/prop-types': 'off'
},
globals: {
React: true
}
}

42
.gitignore vendored Normal file
View File

@@ -0,0 +1,42 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local
# vercel
.vercel
# sitemap
/public/robots.txt
/public/sitemap.xml
/data.json
/yarn.lock
.idea
.vscode

6
.prettierrc.json Normal file
View File

@@ -0,0 +1,6 @@
{
"singleQuote": true,
"semi": false,
"trailingComma": "none",
"arrowParens": "avoid"
}

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021-present, tangly1024
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

6
Nobelium-Logo.svg Normal file
View File

@@ -0,0 +1,6 @@
<svg width="50" height="50" viewBox="0 0 50 50" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="0.625" y="0.625" width="48.75" height="48.75" rx="14.375" fill="black" stroke="white" stroke-width="1.25"/>
<path d="M18.0876 23.1894L24.7284 18.7483L31.7252 23.2522C32.5632 23.2522 33.4011 23.0009 34.1134 22.5609L39.9791 18.7902L24.1208 11.5L9.75 19.6281L16.3489 22.7914C16.8935 23.0428 17.501 23.1685 18.0876 23.1894V23.1894ZM26.4881 36.1776L12.222 29.3483L9.75 30.7519L26.3624 38.6915L40 29.914L37.7794 28.9084L26.4881 36.1776Z" fill="white"/>
<path d="M26.4881 32.2602L12.222 25.4519L9.75 26.8345L26.3624 34.7741L40 26.0175L37.7794 24.991L26.4881 32.2602Z" fill="white"/>
<path d="M34.8257 23.5874C33.9249 24.174 32.8565 24.4882 31.7881 24.4882H31.7671L18.2552 24.4254C17.4382 24.4254 16.6002 24.2368 15.8461 23.8807L11.6354 21.8696L9.75 22.938L26.3624 30.8567L40 22.1001L38.345 21.3459L34.8257 23.5874V23.5874Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 944 B

View File

@@ -14,8 +14,6 @@
</a>
</p>
[中文说明 / [English](README.md)]
演示地址:[https://www.tangly1024.com/](https://www.tangly1024.com/)
<details><summary>截图</summary>

60
blog.config.js Normal file
View File

@@ -0,0 +1,60 @@
const BLOG = {
title: '塘里博客',
author: 'tangly',
email: 'tlyong1992@hotmail.com',
link: 'https://tangly1024.com',
description: '唐风集里,收卷波澜',
lang: 'zh-CN', // ['zh-CN','en-US']
appearance: 'auto', // ['light', 'dark', 'auto'],
font: 'font-sans', // ['font-sans', 'font-serif', 'font-mono']
lightBackground: '#ffffff', // use hex value, don't forget '#' e.g #fffefc
darkBackground: '#111827', // use hex value, don't forget '#'
path: '', // leave this empty unless you want to deploy in a folder
since: 2020, // if leave this empty, current year will be used.
postsPerPage: 6,
sortByDate: false,
showAbout: true, // WIP
showArchive: true, // WIP
autoCollapsedNavBar: false, // the automatically collapsed navigation bar
socialLink: 'https://weibo.com/u/2245301913',
seo: {
keywords: ['Blog', 'Website', 'Notion'],
googleSiteVerification: '' // Remove the value or replace it with your own google site verification code
},
notionPageId: process.env.NOTION_PAGE_ID || 'bee1fccfa3bd47a1a7be83cc71372d83', // DO NOT CHANGE THIS
notionAccessToken: process.env.NOTION_ACCESS_TOKEN || '', // Useful if you prefer not to make your database public
analytics: {
provider: 'ga', // Currently we support Google Analytics and Ackee, please fill with 'ga' or 'ackee', leave it empty to disable it.
ackeeConfig: {
tracker: '', // e.g 'https://ackee.tangly1024.net/tracker.js'
dataAckeeServer: '', // e.g https://ackee.tangly1024.net , don't end with a slash
domainId: '' // e.g '0e2257a8-54d4-4847-91a1-0311ea48cc7b'
},
gaConfig: {
measurementId: 'G-5EV4HZD0XX' // e.g: G-XXXXXXXXXX
}
},
comment: {
// support provider: gitalk, utterances, cusdis
provider: 'cusdis', // leave it empty if you don't need any comment plugin
gitalkConfig: {
repo: 'NotionNext', // The repository of store comments
owner: 'tangly1024',
admin: ['tangly1024'],
clientID: 'be7864a16b693e256f8f',
clientSecret: 'dbd0f6d9ceea8940f6ed20936b415274b8fe66a2',
distractionFreeMode: false
},
cusdisConfig: {
appId: '445ba48e-f751-487f-b22f-cdbe3310d28f', // data-app-id
host: 'https://cusdis.com', // data-host, change this if you're using self-hosted version
scriptSrc: 'https://cusdis.com/js/cusdis.es.js' // change this if you're using self-hosted version
},
utterancesConfig: {
repo: 'tangly1024/NotionNext'
}
},
isProd: process.env.VERCEL_ENV === 'production' // distinguish between development and production environment (ref: https://vercel.com/docs/environment-variables#system-environment-variables)
}
// export default BLOG
module.exports = BLOG

14
components/Ackee.js Normal file
View File

@@ -0,0 +1,14 @@
import { useRouter } from 'next/router'
import useAckee from 'use-ackee'
const Ackee = ({ ackeeServerUrl, ackeeDomainId }) => {
const router = useRouter()
useAckee(
router.asPath,
{ server: ackeeServerUrl, domainId: ackeeDomainId },
{ detailed: false, ignoreLocalhost: true }
)
return null
}
export default Ackee

25
components/BlogPost.js Normal file
View File

@@ -0,0 +1,25 @@
import BLOG from '@/blog.config'
const BlogPost = ({ post }) => {
return (
<article key={post.id}
className='md:mx-2 w-full md:max-w-md duration-200 transform hover:scale-105 hover:shadow-2xl bg-white dark:bg-gray-800 dark:hover:bg-gray-600 overflow-hidden'>
{/* 封面图 */}
{post.page_cover && post.page_cover.length > 1 && (
<a href={`${BLOG.path}/article/${post.slug}`} className='md:flex-shrink-0 md:w-52 md:h-52 rounded-lg'>
<img className='w-full max-h-60 object-cover p-3 cursor-pointer' src={post.page_cover} alt={post.title} />
</a>
)}
<main className='px-8 py-2'>
<a href={`${BLOG.path}/article/${post.slug}`}
className='block my-3 text-2xl leading-tight font-semibold text-black dark:text-gray-200 hover:underline'>
{post.title}
</a>
<p className='mt-2 text-gray-500 dark:text-gray-400 text-sm'>{post.summary}</p>
</main>
</article>
)
}
export default BlogPost

View File

@@ -0,0 +1,24 @@
import BLOG from '@/blog.config'
const BlogPostMini = ({ post }) => {
return (
<a key={post.id} href={`${BLOG.path}/article/${post.slug}`}
className='md:flex w-full border my-2 duration-200 transform hover:scale-105 hover:shadow-2xl bg-white dark:bg-gray-800 dark:hover:bg-gray-600'>
{/* 封面图 */}
{post.page_cover && post.page_cover.length > 1 && (
<img className='md:w-40 w-full max-h-32 object-cover cursor-pointer' src={post.page_cover} alt={post.title} />
)}
<main className='px-2 py-1'>
<a href={`${BLOG.path}/article/${post.slug}`}
className='block my-3 leading-tight font-semibold text-black dark:text-gray-200 hover:underline'>
{post.title}
</a>
<p className='mt-2 text-gray-500 dark:text-gray-400 text-xs overflow-x-hidden'>{post.summary}</p>
<p className='mt-2 text-gray-500 dark:text-gray-400 text-xs overflow-x-hidden'>{BLOG.link}/article/{post.slug}</p>
</main>
</a>
)
}
export default BlogPostMini

67
components/Comment.js Normal file
View File

@@ -0,0 +1,67 @@
import BLOG from '@/blog.config'
import dynamic from 'next/dynamic'
import { useRouter } from 'next/router'
const GitalkComponent = dynamic(
() => {
return import('gitalk/dist/gitalk-component')
},
{ ssr: false }
)
const UtterancesComponent = dynamic(
() => {
return import('@/components/Utterances')
},
{ ssr: false }
)
const CusdisComponent = dynamic(
() => {
return import('react-cusdis').then(m => m.ReactCusdis)
},
{ ssr: false }
)
const Comment = ({ frontMatter }) => {
const router = useRouter()
return <div className='comment'>
<div className='font-bold text-gray-800 pt-2 pb-4 dark:text-gray-300'>留下评论</div>
{/* 评论插件 */}
{BLOG.comment.provider === 'gitalk' && (
<GitalkComponent
options={{
id: frontMatter.id,
title: frontMatter.title,
clientID: BLOG.comment.gitalkConfig.clientID,
clientSecret: BLOG.comment.gitalkConfig.clientSecret,
repo: BLOG.comment.gitalkConfig.repo,
owner: BLOG.comment.gitalkConfig.owner,
admin: BLOG.comment.gitalkConfig.admin,
distractionFreeMode: BLOG.comment.gitalkConfig.distractionFreeMode
}}
/>
)}
{BLOG.comment.provider === 'utterances' && (
<UtterancesComponent issueTerm={frontMatter.id} className='px-2' />
)}
{BLOG.comment.provider === 'cusdis' && (
<>
<script defer src='https://cusdis.com/js/widget/lang/zh-cn.js' />
<CusdisComponent
attrs={{
host: BLOG.comment.cusdisConfig.host,
appId: BLOG.comment.cusdisConfig.appId,
pageId: frontMatter.id,
pageTitle: frontMatter.title,
pageUrl: BLOG.link + router.asPath,
theme: BLOG.appearance
}}
lang={BLOG.lang.toLowerCase()}
/>
</>
)}</div>
}
export default Comment

45
components/CommonHead.js Normal file
View File

@@ -0,0 +1,45 @@
import BLOG from '@/blog.config'
import Head from 'next/head'
const CommonHead = ({ meta }) => {
const url = BLOG.path.length ? `${BLOG.link}/${BLOG.path}` : BLOG.link
return <Head>
<title>{meta.title}</title>
<meta content={BLOG.darkBackground} name='theme-color' />
<meta name='robots' content='follow, index' />
<meta charSet='UTF-8' />
{BLOG.seo.googleSiteVerification && (
<meta
name='google-site-verification'
content={BLOG.seo.googleSiteVerification}
/>
)}
{BLOG.seo.keywords && (
<meta name='keywords' content={BLOG.seo.keywords.join(', ')} />
)}
<meta name='description' content={meta.description} />
<meta property='og:locale' content={BLOG.lang} />
<meta property='og:title' content={meta.title} />
<meta property='og:description' content={meta.description} />
<meta
property='og:url'
content={meta.slug ? `${url}/${meta.slug}` : url}
/>
<meta property='og:type' content={meta.type} />
<meta name='twitter:card' content='summary_large_image' />
<meta name='twitter:description' content={meta.description} />
<meta name='twitter:title' content={meta.title} />
{meta.type === 'article' && (
<>
<meta
property='article:published_time'
content={meta.date || meta.createdTime}
/>
<meta property='article:author' content={BLOG.author} />
</>
)}
</Head>
}
export default CommonHead

View File

@@ -0,0 +1,22 @@
import React from 'react'
import Link from 'next/link'
/**
* 悬浮在屏幕右下角,联系我的按钮
* @returns {JSX.Element}
* @constructor
*/
const ContactButton = () => {
return (
<Link href='/article/about'>
<a className={'fixed right-10 bottom-40 animate__fadeInRight animate__animated animate__faster'}>
<span
className='dark:bg-black bg-white px-5 py-3 cursor-pointer shadow-card text-xl hover:bg-blue-500 transform duration-200 hover:text-white hover:shadow'>
<span className='dark:text-gray-200 fa fa-info' title='about' />
</span>
</a>
</Link>
)
}
export default ContactButton

16
components/Container.js Normal file
View File

@@ -0,0 +1,16 @@
import PropTypes from 'prop-types'
const Container = ({ children, layout, fullWidth, ...customMeta }) => {
return (
<div>
{/* 公共头 */}
{children}
</div>
)
}
Container.propTypes = {
children: PropTypes.node
}
export default Container

33
components/Cusdis.js Normal file
View File

@@ -0,0 +1,33 @@
import BLOG from '@/blog.config'
import { useEffect } from 'react'
const Cusdis = ({ id, url, title }) => {
useEffect(() => {
const script = document.createElement('script')
const anchor = document.getElementById('comments')
script.setAttribute(
'src',
BLOG.comment.cusdisConfig.scriptSrc ||
'https://cusdis.com/js/cusdis.es.js'
)
script.setAttribute('async', true)
script.setAttribute('defer', true)
anchor.appendChild(script)
return () => {
anchor.innerHTML = ''
}
})
return (
<div id="comments">
<div
id="cusdis_thread"
data-host={BLOG.comment.cusdisConfig.host || 'https://cusdis.com'}
data-app-id={BLOG.comment.cusdisConfig.appId}
data-page-id={id}
data-page-url={url}
data-page-title={title}
></div>
</div>
)
}
export default Cusdis

View File

@@ -0,0 +1,19 @@
import { useTheme } from '@/lib/theme'
import localStorage from 'localStorage'
const DarkModeButton = () => {
const { theme, changeTheme } = useTheme()
const handleChangeDarkMode = () => {
const newTheme = (theme === 'light' ? 'dark' : 'light')
changeTheme(newTheme)
localStorage.setItem('theme', newTheme)
}
return <div className=''>
<div onClick={handleChangeDarkMode}
className='border w-10 h-10 justify-center align-middle font-bold text-lg rounded flex p-2.5 cursor-pointer text-gray-600 hover:scale-125 transform duration-200
dark:bg-gray-600 dark:text-gray-300 dark:hover:bg-gray-100 dark:hover:text-black'>
<span className={'fa px-1 ' + (theme === 'dark' ? ' fa-sun-o' : ' fa-moon-o')} />
</div>
</div>
}
export default DarkModeButton

23
components/Footer.js Normal file
View File

@@ -0,0 +1,23 @@
import BLOG from '@/blog.config'
import React from 'react'
import SocialButton from '@/components/SocialButton'
const Footer = ({ fullWidth = true }) => {
const d = new Date()
const y = d.getFullYear()
const from = +BLOG.since
return (
<div
className='py-4 flex-shrink-0 m-auto w-full text-gray-500 dark:text-gray-400 bottom-0'
>
<SocialButton/>
<div className='text-sm'>
<span className='fa fa-shield leading-6'><a href='https://beian.miit.gov.cn/' className='ml-1'>闽ICP备20010331号</a></span>
<br/>
<span className='fa fa-copyright leading-6'> {from === y || !from ? y : `${from} - ${y}`} {BLOG.author} </span>
</div>
</div>
)
}
export default Footer

18
components/Gtag.js Normal file
View File

@@ -0,0 +1,18 @@
import { useEffect } from 'react'
import { useRouter } from 'next/router'
import * as gtag from '@/lib/gtag'
const Gtag = () => {
const router = useRouter()
useEffect(() => {
const handleRouteChange = url => {
gtag.pageview(url)
}
router.events.on('routeChangeComplete', handleRouteChange)
return () => {
router.events.off('routeChangeComplete', handleRouteChange)
}
}, [router.events])
return null
}
export default Gtag

94
components/Header.js Normal file
View File

@@ -0,0 +1,94 @@
import { useEffect, useRef } from 'react'
import Link from 'next/link'
import BLOG from '@/blog.config'
import Image from 'next/image'
const NavBar = () => {
const links = []
return (
<div className='flex-shrink-0'>
<ul className='flex flex-row'>
{links.map(
link =>
link.show && (
<li
key={link.id}
className='block ml-4 text-black dark:text-gray-50 nav'
>
<Link href={link.to}>
<a>{(link.icon && (<i className={'px-1 fa ' + link.icon} />))} {link.name}</a>
</Link>
</li>
)
)}
</ul>
</div>
)
}
const Header = ({ navBarTitle, fullWidth }) => {
const navRef = useRef(null)
const sentinelRef = useRef([])
// 当Header移出屏幕时改变的样式
const handler = ([entry]) => {
if (navRef && navRef.current) {
if (!entry.isIntersecting && entry !== undefined) {
navRef.current.classList.add('sticky-nav-full')
} else {
navRef.current.classList.remove('sticky-nav-full')
}
}
}
useEffect(() => {
const observer = new window.IntersectionObserver(handler)
observer.observe(sentinelRef.current)
// Don't touch this, I have no idea how it works XD
// return () => {
// if (sentinalRef.current) obvserver.unobserve(sentinalRef.current)
// }
}, [sentinelRef])
return (
<>
<div className='observer-element h-0.5' ref={sentinelRef}/>
<div
className={`sticky-nav m-auto w-full h-6 flex flex-row justify-between items-center mb-2 py-8 bg-opacity-60 ${
!fullWidth ? 'max-w-5xl px-4' : 'px-4 md:px-24'
}`}
id='sticky-nav'
ref={navRef}
>
<div className='flex items-center'>
<Link href='/'>
<a>
<div className='h-6'>
<Image
alt={BLOG.author}
width={24}
height={24}
src='/favicon.svg'
className='rounded-full'
/>
</div>
</a>
</Link>
{navBarTitle
? (
<p className='ml-2 font-medium text-day dark:text-night header-name'>
{navBarTitle}
</p>
)
: (
<p className='ml-2 font-medium text-day dark:text-night header-name'>
{BLOG.title} {' '}
{BLOG.title},{' '}
<span className='font-normal'>{BLOG.description}</span>
</p>
)}
</div>
<NavBar />
</div>
</>
)
}
export default Header

70
components/LeftAside.js Normal file
View File

@@ -0,0 +1,70 @@
import Tags from '@/components/Tags'
import { useLocale } from '@/lib/locale'
import Link from 'next/link'
import BLOG from '@/blog.config'
import { useState } from 'react'
import Router, { useRouter } from 'next/router'
import DarkModeButton from '@/components/DarkModeButton'
import SocialButton from '@/components/SocialButton'
import Footer from '@/components/Footer'
const LeftAside = ({ tags, currentTag }) => {
const locale = useLocale()
const router = useRouter()
const [searchValue, setSearchValue] = useState('')
const handleKeyUp = (e) => {
if (e.keyCode === 13) {
Router.push({ pathname: '/', query: { s: searchValue } })
}
}
return <aside
style={{ width: '330px' }}
className='px-10 hidden xl:block py-5 bg-gray-50 dark:bg-gray-800 duration-200 border-r dark:border-black'
>
<div className='sticky top-16'>
<div className='my-5 flex'>
<Link href='/'>
<a
className='hover:shadow-xl dark:border-gray-600 border-black border-2 bg-white dark:bg-gray-800 dark:text-gray-300 font-semibold hover:bg-gray-800 hover:text-white p-2 duration-200'>{BLOG.title}</a>
</Link>
</div>
<div className='text-gray-500 dark:text-gray-300'>
<i className='fa fa-map-marker mr-1' />
Fuzhou, China
</div>
<hr className='my-5'/>
{/* 搜索框 */}
<div className='flex justify-center items-center py-5 '>
<i className='fa fa-search absolute right-8 text-gray-400' />
<input
type='text'
placeholder={
currentTag ? `${locale.SEARCH.TAGS} #${currentTag}` : `${locale.SEARCH.ARTICLES}`
}
className='hover:shadow-xl duration-200 px-5 bg-gray-100 rounded w-full py-2 border-black dark:border-gray-600 bg-white text-black dark:bg-gray-700 dark:text-white'
onKeyUp={handleKeyUp}
onChange={e => setSearchValue(e.target.value)}
defaultValue={router.query.s ?? ''}
/>
</div>
<hr className='my-5'/>
<div>
<span className='dark:text-gray-200'>标签</span>
<Tags tags={tags} currentTag={currentTag} />
</div>
<div className='bottom-1 fixed'>
<div className='justify-center flex '><DarkModeButton /></div>
<Footer/>
</div>
</div>
</aside>
}
export default LeftAside

44
components/Pagination.js Normal file
View File

@@ -0,0 +1,44 @@
import BLOG from '@/blog.config'
import { useLocale } from '@/lib/locale'
import Link from 'next/link'
import { useRouter } from 'next/router'
const Pagination = ({ page, showNext }) => {
const locale = useLocale()
const router = useRouter()
const currentPage = +page
return (
<div className=' my-10 flex justify-between font-medium text-black dark:text-gray-100 mx-5'>
<Link
href={
{
pathname: (currentPage - 1 === 1 ? `${BLOG.path || '/'}` : `/page/${currentPage - 1}`),
query: router.query.s ? { s: router.query.s } : {}
}
}
>
<button
rel='prev'
className={`${currentPage === 1 ? 'invisible' : 'block'} duration-200 px-4 py-2 hover:border-black border-b-2 hover:font-bold`}
>
{locale.PAGINATION.PREV}
</button>
</Link>
<Link href={
{
pathname: `/page/${currentPage + 1}`,
query: router.query.s ? { s: router.query.s } : {}
}
}>
<button
rel='next'
className={`${+showNext ? 'block' : 'invisible'} duration-200 px-4 py-2 hover:border-black border-b-2 hover:font-bold`}
>
{locale.PAGINATION.NEXT}
</button>
</Link>
</div>
)
}
export default Pagination

35
components/Progress.js Normal file
View File

@@ -0,0 +1,35 @@
import React, { useEffect, useState } from 'react'
import throttle from 'lodash.throttle'
/**
* 跳转到网页顶部当屏幕下滑500像素后会出现该控件
* @returns {JSX.Element}
* @constructor
*/
const Progress = ({ targetRef }) => {
const [percent, changePercent] = useState(0)
useEffect(() => {
const scrollListener = throttle(() => {
if (targetRef.current) {
const fullHeight = targetRef.current.clientHeight
const per = parseFloat(((window.scrollY / (fullHeight) * 100)).toFixed(0))
changePercent(per)
}
// console.log('滚动信息', window.scrollY, fullHeight, per)
}, 1)
document.addEventListener('scroll', scrollListener)
return () => document.removeEventListener('scroll', scrollListener)
}, [percent])
return (
<>
{/* 顶部进度条 */}
<div className='h-1.5 fixed top-0 w-full shadow-2xl z-40'>
<div className='h-1 bg-blue-500 fixed top-0 w-1 duration-200' style={{ width: `${percent}%` }}/>
{/* <div className='debug'>{percent}</div> */}
</div>
</>
)
}
export default Progress

View File

@@ -0,0 +1,63 @@
import React from 'react'
import { createPopper } from '@popperjs/core'
/**
* 赞赏模块
* @returns {JSX.Element}
* @constructor
*/
const RewardButton = () => {
const [popoverShow, setPopoverShow] = React.useState(false)
const btnRef = React.createRef()
const popoverRef = React.createRef()
const openPopover = () => {
createPopper(btnRef.current, popoverRef.current, {
placement: 'top'
})
setPopoverShow(true)
}
const closePopover = () => {
setPopoverShow(false)
}
return (
<div
onMouseEnter={() => {
openPopover()
}}
onMouseLeave={() => {
closePopover()
}}>
<div className='animate__jello animate__animated animate__faster'>
<div
ref={btnRef}
className='bg-blue-500 text-white hover:bg-white hover:text-black hover:shadow-2xl border duration-200 transform hover:scale-110 px-3 py-2 rounded cursor-pointer'>
<div>
<span className='fa fa-qrcode mr-2' />
<span>打赏</span>
</div>
</div>
</div>
<div
className={
(popoverShow ? 'animate__animated animate__fadeIn ' : 'hidden ') +
' animate__faster border-0 transform block z-50 font-normal'
}
ref={popoverRef}
>
<div>
<div
className='border animate__animated animate__fadeIn hover:shadow-2xl duration-200 my-5 px-5 py-6 w-96 grid justify-center bg-white dark:bg-black dark:text-gray-200'>
<span>
<img className='md:w-72 m-auto' src='/reward_code.jpg' />
</span>
<br />
<span className='text-center text-gray-500'>微信赞赏码或支付宝tlyong@126.com赞助</span>
</div>
</div>
</div>
</div>
)
}
export default RewardButton

14
components/RightAside.js Normal file
View File

@@ -0,0 +1,14 @@
import React from 'react'
import Toc from '@/components/Toc'
const RightAside = ({ toc }) => {
// 无目录就直接返回空
if (toc.length < 1) return <></>
return <aside className='bg-gray-800 px-5 hidden lg:block py-5 hover:shadow-2xl duration-200'>
<div className='sticky top-8 w-60 overflow-x-auto'>
<Toc toc={toc}/>
</div>
</aside>
}
export default RightAside

13
components/RightWidget.js Normal file
View File

@@ -0,0 +1,13 @@
import React from 'react'
import TopJumper from '@/components/TopJumper'
import ShareButton from '@/components/ShareButton'
const RightWidget = ({ post }) => {
return <div className='fixed right-0 lg:mr-72 bottom-10 flex justify-center'>
<div className='flex-wrap'>
<ShareButton post={post}/>
<TopJumper/>
</div>
</div>
}
export default RightWidget

84
components/ShareBar.js Normal file
View File

@@ -0,0 +1,84 @@
import BLOG from '@/blog.config'
import { useRouter } from 'next/router'
import React from 'react'
import { createPopper } from '@popperjs/core'
import copy from 'copy-to-clipboard'
import QRCode from 'qrcode.react'
const ShareBar = ({ post }) => {
const router = useRouter()
const shareUrl = BLOG.link + router.asPath
// 二维码悬浮
const [qrCodeShow, setQrCodeShow] = React.useState(false)
const btnRef = React.createRef()
const popoverRef = React.createRef()
const openPopover = () => {
createPopper(btnRef.current, popoverRef.current, {
placement: 'left'
})
setQrCodeShow(true)
}
const closePopover = () => {
setQrCodeShow(false)
}
const copyUrl = () => {
copy(shareUrl)
alert('当前链接已复制到剪贴板')
}
return <>
<div className='text-gray-500 flex-col text-center space-y-2 w-12 border my-1 bg-white dark:bg-gray-800 dark:text-white overflow-hidden'>
<div>
分享
</div>
<div>
<a className='fa fa-facebook-square cursor-pointer text-3xl'
href={`https://www.facebook.com/sharer.php?u=${shareUrl}`} />
</div>
<div>
<a className='fa fa-twitter-square text-3xl' target='_blank' rel='noreferrer'
href={`https://twitter.com/intent/tweet?title=${post.title}&url${shareUrl}`} />
</div>
<div>
<a className='fa fa-telegram text-3xl' href={`https://telegram.me/share/url?url=${shareUrl}&text=${post.title}`} />
</div>
<div>
<a className='fa fa-wechat cursor-pointer text-3xl' ref={btnRef}
onMouseEnter={() => { openPopover() }}
onMouseLeave={() => { closePopover() }}>
<div ref={popoverRef}
className={(qrCodeShow ? 'animate__animated animate__fadeIn ' : 'hidden') + ' text-center py-2 bg-white'}>
<div className='p-2 bg-white border-0 duration-200 transform block z-50 font-normal shadow-xl'>
<QRCode
value={shareUrl}// 生成二维码的内容
fgColor='#000000' // 二维码的颜色
/>
</div>
<span className='bg-white text-black font-semibold p-1 mb-0 rounded-t-lg text-sm mx-auto'>
扫一扫分享
</span>
</div>
</a>
</div>
<div>
<a className='fa fa-weibo text-3xl' target='_blank' rel='noreferrer'
href={`https://service.weibo.com/share/share.php?url=${shareUrl}&title=${post.title}`} />
</div>
<div>
<a className='fa fa-qq text-3xl' target='_blank' rel='noreferrer'
href={`http://connect.qq.com/widget/shareqq/index.html?url=${shareUrl}&sharesource=qzone&title=${post.title}&desc=${post.summary}`} />
</div>
<div>
<a className='fa fa-star text-3xl' target='_blank' rel='noreferrer'
href={`https://sns.qzone.qq.com/cgi-bin/qzshare/cgi_qzshare_onekey?url=${shareUrl}&sharesource=qzone&title=${post.title}&summary=${post.summary}`} />
</div>
<div>
<a className='fa fa-link cursor-pointer text-3xl' onClick={() => { copyUrl() }} />
</div>
</div>
</>
}
export default ShareBar

42
components/ShareButton.js Normal file
View File

@@ -0,0 +1,42 @@
import React from 'react'
import ShareBar from '@/components/ShareBar'
/**
* 悬浮在屏幕右下角,分享按钮
* @returns {JSX.Element}
* @constructor
*/
const ShareButton = ({ post }) => {
const [popoverShow, setPopoverShow] = React.useState(false)
const btnRef = React.createRef()
const openPopover = () => {
setPopoverShow(true)
}
const closePopover = () => {
setPopoverShow(false)
}
return (
<div className='my-2'
onMouseEnter={() => { openPopover() }}
onMouseLeave={() => { closePopover() }}>
<div className=' overflow-hidden '>
<div
className={
(popoverShow ? ' block ' : ' hidden ') +
' duration-200 transform transition z-50 font-normal'
}
>
<ShareBar post={post}/>
</div>
<div
ref={btnRef}
className='border dark:bg-black bg-white px-4 py-3 cursor-pointer text-md hover:bg-blue-500 transform duration-200 hover:text-white hover:shadow'>
<div className='dark:text-gray-200 fa fa-share-alt' title='share' />
</div>
</div>
</div>
)
}
export default ShareButton

View File

@@ -0,0 +1,28 @@
import React from 'react'
const SocialButton = () => {
return <>
<div className='space-x-3 text-xl'>
<a className='fa fa-rss hover:underline' href='/feed' target='_blank' id='feed'/>
<a className='fa fa-info hover:underline mx-1' href='/article/about' id='about'/>
<a className='fa fa-github' target='_blank' rel='noreferrer' title={'github'}
href={'https://github.com/tangly1024'} />
<a className='fa fa-twitter' target='_blank' rel='noreferrer' title={'twitter'}
href={'https://twitter.com/troy1024_1'} />
<a className='fa fa-telegram' href={'https://t.me/tangly_1024'} title={'telegram'} />
<a className='fa fa-weibo' target='_blank' rel='noreferrer' title={'weibo'}
href={'http://weibo.com/tangly1024'} />
<span id='busuanzi_container_site_pv' className='hidden'><span className='s'> | </span>
<a href='https://www.cnzz.com/stat/website.php?web_id=1279970751' target='_blank'
id='busuanzi_container_site_pv'
className='fa fa-user' rel='noreferrer'> pv <span id='busuanzi_value_site_pv'></span></a>
</span>
<span id='busuanzi_container_site_uv' className='hidden'><span className='s'> | </span>
<a href='http://tongji.baidu.com/web/10000363165/overview/index?siteId=16809429' target='_blank'
className='fa fa-eye' rel='noreferrer'> uv <span id='busuanzi_value_site_uv'></span></a>
</span>
</div>
</>
}
export default SocialButton

14
components/TagItem.js Normal file
View File

@@ -0,0 +1,14 @@
import Link from 'next/link'
const TagItem = ({ tag }) => (
<Link href={`/tag/${encodeURIComponent(tag)}`}>
<a>
<p className="hover:shadow hover:scale-105 hover:bg-blue-500 bg-gray-200 hover:text-white duration-200 mr-1 px-2 py-1 leading-none text-sm
dark:bg-gray-500 dark:hover:bg-black">
{tag}
</p>
</a>
</Link>
)
export default TagItem

27
components/Tags.js Normal file
View File

@@ -0,0 +1,27 @@
import Link from 'next/link'
const Tags = ({ tags, currentTag }) => {
if (!tags) return <></>
return (
<ul className='flex flex-wrap py-1 max-w-full overflow-x-auto'>
{Object.keys(tags).map(key => {
const selected = key === currentTag
return (
<Link key={key} href={`/tag/${encodeURIComponent(key)}`}>
<li
className={`cursor-pointer hover:bg-gray-600 rounded-sm hover:text-white duration-200 mr-1 my-1 px-2 py-1 font-medium text-xs whitespace-nowrap
dark:text-gray-300 dark:hover:bg-gray-600 ${selected ? 'text-white bg-black dark:border-gray-600' : 'bg-gray-200 text-gray-600 dark:bg-gray-900 dark:border-gray-600'
}`}
>
<a>
{`${key} (${tags[key]})`}
</a>
</li>
</Link>
)
})}
</ul>
)
}
export default Tags

86
components/Toc.js Normal file
View File

@@ -0,0 +1,86 @@
import React from 'react'
import throttle from 'lodash.throttle'
import { uuidToId } from 'notion-utils'
import { cs } from 'react-notion-x'
/**
* 目录组件
*/
const Toc = ({ toc }) => {
// 无目录就直接返回空
if (toc.length < 1) return <></>
// 监听滚动事件
React.useEffect(() => {
window.addEventListener('scroll', actionSectionScrollSpy)
actionSectionScrollSpy()
return () => {
window.removeEventListener('scroll', actionSectionScrollSpy)
}
}, [])
// 同步选中目录事件
const [activeSection, setActiveSection] = React.useState(null)
const throttleMs = 100
const actionSectionScrollSpy = throttle(() => {
const sections = document.getElementsByClassName('notion-h')
let prevBBox = null
let currentSectionId = activeSection
for (let i = 0; i < sections.length; ++i) {
const section = sections[i]
if (!section || !(section instanceof Element)) continue
if (!currentSectionId) {
currentSectionId = section.getAttribute('data-id')
}
const bbox = section.getBoundingClientRect()
const prevHeight = prevBBox ? bbox.top - prevBBox.bottom : 0
const offset = Math.max(150, prevHeight / 4)
// GetBoundingClientRect returns values relative to viewport
if (bbox.top - offset < 0) {
currentSectionId = section.getAttribute('data-id')
prevBBox = bbox
continue
}
// No need to continue loop, if last element has been detected
break
}
setActiveSection(currentSectionId)
}, throttleMs)
return (
<>
<div className='text-center font-bold text-white'>
文章目录
</div>
<nav className='notion-table-of-contents text-gray-400 underline'>
{toc.map((tocItem) => {
const id = uuidToId(tocItem.id)
return (
<a
key={id}
href={`#${id}`}
className={cs(
'notion-table-of-contents-item',
`notion-table-of-contents-item-indent-level-${tocItem.indentLevel}`,
activeSection === id &&
' font-bold text-white'
)}
>
<span
className='notion-table-of-contents-item-body'
style={{
display: 'inline-block',
marginLeft: tocItem.indentLevel * 16
}}
>
{tocItem.text}
</span>
</a>
)
})}
</nav>
</>
)
}
export default Toc

39
components/TopJumper.js Normal file
View File

@@ -0,0 +1,39 @@
import React, { useEffect, useState } from 'react'
import throttle from 'lodash.throttle'
import { useLocale } from '@/lib/locale'
/**
* 跳转到网页顶部当屏幕下滑500像素后会出现该控件
* @returns {JSX.Element}
* @constructor
*/
const TopJumper = () => {
const locale = useLocale()
const [show, switchShow] = useState(false)
useEffect(() => {
const scrollListener = throttle(() => {
// 处理是否显示回到顶部按钮
const shouldShow = window.scrollY > 100
if (shouldShow !== show) {
switchShow(shouldShow)
}
}, 500)
document.addEventListener('scroll', scrollListener)
return () => document.removeEventListener('scroll', scrollListener)
}, [show])
return (
<div
className={(show ? 'animate__fadeInUp' : 'animate__fadeOutUp') + ' animate__animated animate__faster'}>
<div
className='border dark:bg-black bg-white cursor-pointer hover:bg-blue-500 transform duration-200 hover:text-white hover:shadow-2xl hover:scale-125'
onClick={() => window.scrollTo({ top: 0, behavior: 'smooth' })}>
<a className='dark:text-gray-200 fa fa-arrow-up p-4' title={locale.POST.TOP}/>
</div>
</div>
)
}
export default TopJumper

88
components/TopNav.js Normal file
View File

@@ -0,0 +1,88 @@
import Link from 'next/link'
import BLOG from '@/blog.config'
import { useEffect, useState } from 'react'
import { useLocale } from '@/lib/locale'
import Router, { useRouter } from 'next/router'
import Tags from '@/components/Tags'
import localStorage from 'localStorage'
import { useTheme } from '@/lib/theme'
import DarkModeButton from '@/components/DarkModeButton'
import SocialButton from '@/components/SocialButton'
const TopNav = ({ tags, currentTag }) => {
const locale = useLocale()
const [hiddenMenu, switchHiddenMenu] = useState(!currentTag)
// 点击按钮更改菜单状态
const handleMenuClick = () => {
switchHiddenMenu(!hiddenMenu)
}
const router = useRouter()
const [searchValue, setSearchValue] = useState('')
const handleKeyUp = (e) => {
if (e.keyCode === 13) {
Router.push({ pathname: '/', query: { s: searchValue } })
}
}
return (
<div className='bg-white dark:bg-gray-600 block xl:hidden'>
{/* 隐藏的顶部菜单 */}
<div
className={(hiddenMenu ? 'h-0 ' : 'h-full ') + ' overflow-hidden bg-gray-800 text-xl text-gray-200 w-full transform ease-in-out duration-500'}>
<ul className='mx-5 duration-300'>
<li>
<div>
<Tags tags={tags} currentTag={currentTag} />
</div>
</li>
<li>
<SocialButton/>
</li>
</ul>
</div>
{/* 导航栏 */}
<div
id='sticky-nav'
className='text-sm ticky-nav m-auto w-full flex flex-row justify-between items-center px-5 pt-3 pb-2'
>
<div>
<Link href='/'>
<a
className='flex justify-center border-black border-2 bg-whitefont-semibold hover:bg-gray-800 hover:text-white p-2 duration-200
dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-100 dark:hover:text-black
'>{BLOG.title}</a>
</Link>
</div>
<div>
{/* 搜索框 */}
<div className='px-4 flex w-20'>
<i className='py-3 fa fa-search text-gray-400 absolute cursor-pointer px-2' />
<input
type='text'
placeholder={currentTag ? `${locale.SEARCH.TAGS} #${currentTag}` : `${locale.SEARCH.ARTICLES}`}
className={'transition duration-200 leading-10 pl-8 block border-gray-300 dark:border-gray-600 bg-white text-black dark:bg-gray-800 dark:text-white'}
onKeyUp={handleKeyUp}
onChange={e => setSearchValue(e.target.value)}
defaultValue={router.query.s ?? ''}
/>
</div>
</div>
<div className='flex flex-nowrap space-x-1'>
<div onClick={handleMenuClick}
className='p-2.5 cursor-pointer text-gray-600 bg-white hover:bg-gray-800 hover:text-white duration-200
dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-100 dark:hover:text-black'>
<div className={'fa' + (hiddenMenu ? ' fa-bars ' : ' fa-times')} />
<span
className='px-0.5'>{hiddenMenu ? '' : ''}</span>
</div>
<DarkModeButton/>
</div>
</div>
</div>
)
}
export default TopNav

36
components/Utterances.js Normal file
View File

@@ -0,0 +1,36 @@
import BLOG from '@/blog.config'
import { useEffect } from 'react'
const Utterances = ({ issueTerm, layout }) => {
useEffect(() => {
const theme =
BLOG.appearance === 'auto'
? 'preferred-color-scheme'
: BLOG.appearance === 'light'
? 'github-light'
: 'github-dark'
const script = document.createElement('script')
const anchor = document.getElementById('comments')
script.setAttribute('src', 'https://utteranc.es/client.js')
script.setAttribute('crossorigin', 'anonymous')
script.setAttribute('async', true)
script.setAttribute('repo', BLOG.comment.utterancesConfig.repo)
script.setAttribute('issue-term', issueTerm)
script.setAttribute('theme', theme)
anchor.appendChild(script)
return () => {
anchor.innerHTML = ''
}
})
return (
<>
<div
id="comments"
className={layout && layout === 'fullWidth' ? '' : 'md:-ml-16'}
>
<div className="utterances-frame"></div>
</div>
</>
)
}
export default Utterances

41
components/Vercel.js Normal file

File diff suppressed because one or more lines are too long

13
jsconfig.json Normal file
View File

@@ -0,0 +1,13 @@
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./*"],
"@/components/*": ["components/*"],
"@/data/*": ["data/*"],
"@/layouts/*": ["layouts/*"],
"@/lib/*": ["lib/*"],
"@/styles/*": ["styles/*"]
}
}
}

180
layouts/ArticleLayout.js Normal file
View File

@@ -0,0 +1,180 @@
import TagItem from '@/components/TagItem'
import { NotionRenderer, Equation, Code, CollectionRow, Collection } from 'react-notion-x'
import BLOG from '@/blog.config'
import formatDate from '@/lib/formatDate'
import 'gitalk/dist/gitalk.css'
import Comment from '@/components/Comment'
import CommonHead from '@/components/CommonHead'
import TopNav from '@/components/TopNav'
import Progress from '@/components/Progress'
import { useRef } from 'react'
import Image from 'next/image'
import Footer from '@/components/Footer'
import RightAside from '@/components/RightAside'
import RewardButton from '@/components/RewardButton'
import RightWidget from '@/components/RightWidget'
import { useTheme } from '@/lib/theme'
import LeftAside from '@/components/LeftAside'
import BlogPostMini from '@/components/BlogPostMini'
const mapPageUrl = id => {
return 'https://www.notion.so/' + id.replace(/-/g, '')
}
const ArticleLayout = ({
children,
blockMap,
frontMatter,
emailHash,
fullWidth = true,
tags,
prev,
next
}) => {
const meta = {
title: frontMatter.title,
type: 'article'
}
const targetRef = useRef(null)
const { theme } = useTheme()
return (
<div className={`${BLOG.font} ${theme}`}>
<CommonHead meta={meta} />
{/* live2d 看板娘 */}
<script async src='https://cdn.jsdelivr.net/gh/stevenjoezhang/live2d-widget@latest/autoload.js' />
<Progress targetRef={targetRef} />
<TopNav tags={tags} />
<div className='flex justify-between'>
<LeftAside tags={tags} />
{/* 主体区块 */}
<main className='bg-gray-100 dark:bg-black w-full'>
{/* 卡牌水平边距wrapper */}
{/* 文章卡牌 */}
<div className='bg-white dark:border-gray-700 dark:bg-gray-700 duration-200'>
<header className='md:flex-shrink-0 overflow-y-hidden shadow-sm animate__fadeIn animate__animated'>
{/* 封面图 */}
{frontMatter.page_cover && frontMatter.page_cover.length > 1 && (
<img className='bg-center object-cover w-full' style={{ maxHeight: '40rem' }}
src={frontMatter.page_cover} alt={frontMatter.title} />
)}
</header>
<article
ref={targetRef}
className='overflow-x-auto px-10 py-10 max-w-3xl mx-auto bg-white dark:border-gray-700 dark:bg-gray-700'>
{/* 文章标题 */}
<h1 className='font-bold text-4xl text-black my-5 dark:text-white animate__animated animate__fadeIn'>
{frontMatter.title}
</h1>
{/* 文章信息 */}
<div className='justify-between flex flex-wrap bg-gray-50 p-2
dark:bg-gray-700 dark:text-white'>
<div className='flex-nowrap flex'>
{frontMatter.tags && (
<div className='flex flex-nowrap leading-8 p-1'>
{frontMatter.tags.map(tag => (
<TagItem key={tag} tag={tag} />
))}
</div>
)}
{frontMatter.slug !== 'about' && (<>
<a className='flex-nowrap flex hover:bg-blue-500 hover:text-white duration-200 px-1 mx-1'
href='/article/about'>
<Image href='https://www.baidu.com' alt={BLOG.author} width={20} height={20} src='/avatar.svg'
className='rounded-full' />
<div className='mx-2 leading-6 my-1 md:block'>{BLOG.author}</div>
</a>
</>)}
{frontMatter.type[0] !== 'Page' && (
<div className='flex items-start text-gray-500 dark:text-gray-400 text-sm leading-8 pr-3'>
<div>
{formatDate(
frontMatter?.date?.start_date || frontMatter.createdTime,
BLOG.lang
)}
</div>
</div>
)}
</div>
{/* 不蒜子 */}
<div id='busuanzi_container_page_pv' className='hidden'>
<a href='https://analytics.google.com/analytics/web/#/p273013569/reports/reportinghub'
className='fa fa-eye text-gray-500 text-sm leading-none py-1 px-2'>
&nbsp;<span id='busuanzi_value_page_pv' className='leading-6'></span>
</a>
</div>
</div>
<div>{children}</div>
{/* Notion文章主体 */}
{blockMap && (
<div>
<NotionRenderer
recordMap={blockMap}
components={{
equation: Equation,
code: Code,
collectionRow: CollectionRow,
collection: Collection
}}
mapPageUrl={mapPageUrl}
/>
</div>
)}
<div className='flex justify-center py-10'>
<RewardButton />
</div>
<div className='text-gray-800 my-5 dark:text-gray-300'>
<div className='mt-4 my-2 font-bold'>继续阅读</div>
<div className='flex flex-wrap justify-between py-2'>
{/* <Link href={prev.slug}> */}
{/* <div>上一篇:<a className='py-1 underline cursor-pointer ml-1'>{prev.title}</a></div> */}
{/* </Link> */}
<BlogPostMini post={prev} />
<BlogPostMini post={next} />
{/* <Link href={next.slug}> */}
{/* <div>下一篇:<a className='py-1 underline cursor-pointer ml-1'>{next.title}</a></div> */}
{/* </Link> */}
</div>
</div>
{/* 分享 */}
{/* <ShareBar post={frontMatter} /> */}
{/* <Share url={shareUrl} title={frontMatter.title}/> */}
{/* 评论互动 */}
<Comment frontMatter={frontMatter} />
</article>
</div>
<RightWidget post={frontMatter} />
{/* <ShareButton post={frontMatter}/> */}
{/* <TopJumper /> */}
</main>
{/* 右侧内容 */}
<RightAside toc={frontMatter.toc} />
</div>
</div>
)
}
export default ArticleLayout

119
layouts/DefaultLayout.js Normal file
View File

@@ -0,0 +1,119 @@
import BlogPost from '@/components/BlogPost'
import PropTypes from 'prop-types'
import Pagination from '@/components/Pagination'
import BLOG from '@/blog.config'
import CommonHead from '@/components/CommonHead'
import { useRouter } from 'next/router'
import TopNav from '@/components/TopNav'
import Footer from '@/components/Footer'
import localStorage from 'localStorage'
import { useTheme } from '@/lib/theme'
import { useEffect } from 'react'
import LeftAside from '@/components/LeftAside'
const DefaultLayout = ({ tags, posts, page, currentTag, ...customMeta }) => {
const meta = {
title: BLOG.title,
type: 'website',
...customMeta
}
page = page ?? 1
let postsToShow = []
let filteredBlogPosts = posts ?? []
let currentSearch = ''
if (posts) {
const router = useRouter()
if (router.query && router.query.s) {
currentSearch = router.query.s
filteredBlogPosts = posts.filter(post => {
const tagContent = post.tags ? post.tags.join(' ') : ''
const searchContent = post.title + post.summary + tagContent + post.slug
return searchContent.toLowerCase().includes(currentSearch.toLowerCase())
})
}
}
const totalPages = Math.ceil(filteredBlogPosts.length / BLOG.postsPerPage)
if (posts) {
postsToShow = filteredBlogPosts.slice(
BLOG.postsPerPage * (page - 1),
BLOG.postsPerPage * page
)
}
let showNext = false
if (filteredBlogPosts) {
const totalPosts = filteredBlogPosts.length
showNext = page * BLOG.postsPerPage < totalPosts
}
// 首页隐藏看板娘
useEffect(() => {
const ref = document.getElementById('waifu')
if (ref) {
ref.remove()
}
})
const { theme } = useTheme()
return (
<div className={theme}>
<CommonHead meta={meta} />
<TopNav tags={tags} currentTag={currentTag} />
<div className={`${BLOG.font} flex justify-between`}>
<LeftAside tags={tags} currentTag={currentTag} />
<main className='md:pb-10 md:px-24 p-5 bg-gray-100 dark:bg-gray-900 w-full'>
{(!page || page === 1) && (
<div className='py-5'></div>
)
}
{/* 标签 */}
{currentTag && (
<div className='pb-5 dark:text-gray-200'>
<div className='py-1'>标签: {currentTag}</div>
<hr />
</div>
)}
{/* 当前搜索 */}
{(currentSearch || (page && page !== 1)) && (
<div className='pb-5'>
<div className='dark:text-gray-200 flex justify-between py-1'>
{currentSearch && (
<span>搜索关键词: {currentSearch}</span>
)}
{page && page !== 1 && (
<span> {page} / {totalPages}</span>
)}
</div>
<hr />
</div>
)}
{/* 文章列表 */}
<div className='mx-auto animate__animated animate__fadeIn'>
<div className='grid xl:grid-cols-3 md:grid-cols-2 grid-cols-1 gap-6'>
{!postsToShow.length && (
<p className='text-gray-500 dark:text-gray-300 textc'>No posts found.</p>
)}
{postsToShow.map(post => (
<BlogPost key={post.id} post={post} tags={tags} />
))}
</div>
</div>
<Pagination page={page} showNext={showNext} />
</main>
</div>
{/* <Footer /> */}
</div>
)
}
DefaultLayout.propTypes = {
posts: PropTypes.array.isRequired,
tags: PropTypes.object.isRequired,
currentTag: PropTypes.string
}
export default DefaultLayout

21
lib/cache/cache_manager.js vendored Normal file
View File

@@ -0,0 +1,21 @@
import { getCacheFromFile, setCacheToFile } from '@/lib/cache/local_file_cache'
import { getCacheFromMemory, setCacheToMemory } from '@/lib/cache/memory_cache'
import BLOG from '@/blog.config'
export async function getDataFromCache (key) {
let dataFromCache
if (BLOG.isProd) {
dataFromCache = await getCacheFromMemory(key)
} else {
dataFromCache = await getCacheFromFile(key)
}
return dataFromCache
}
export async function setDataToCache (key, data) {
if (BLOG.isProd) {
await setCacheToMemory(key, data)
} else {
await setCacheToFile(key, data)
}
}

36
lib/cache/local_file_cache.js vendored Normal file
View File

@@ -0,0 +1,36 @@
import fs from 'fs'
import BLOG from '@/blog.config'
const path = require('path')
// 文件缓存持续10秒
const cacheInvalidSeconds = 1000000000 * 1000
// 文件名
const jsonFile = path.resolve('./data.json')
export async function getCacheFromFile (key) {
const exist = await fs.existsSync(jsonFile)
if (!exist) return null
const data = await fs.readFileSync(jsonFile)
const json = data ? JSON.parse(data) : {}
// 缓存超过有效期就作废
const cacheValidTime = new Date(parseInt(json[key + '_expire_time']) + cacheInvalidSeconds)
const currentTime = new Date()
if (!cacheValidTime || cacheValidTime < currentTime) {
return null
}
return json[key]
}
/**
* 并发请求写文件异常; Vercel生产环境不支持写文件。
* @param key
* @param data
* @returns {Promise<null>}
*/
export async function setCacheToFile (key, data) {
const exist = await fs.existsSync(jsonFile)
const json = exist ? JSON.parse(await fs.readFileSync(jsonFile)) : {}
json[key] = data
json[key + '_expire_time'] = new Date().getTime()
fs.writeFileSync(jsonFile, JSON.stringify(json))
}

9
lib/cache/memory_cache.js vendored Normal file
View File

@@ -0,0 +1,9 @@
import cache from 'memory-cache'
export async function getCacheFromMemory (key, options) { // url为缓存标识
return cache.get(key)
}
export async function setCacheToMemory (key, data) { // url为缓存标识
await cache.put(key, data, 60 * 1000)
}

21
lib/cjk.js Normal file
View File

@@ -0,0 +1,21 @@
const BLOG = require('../blog.config')
module.exports = function () {
switch (BLOG.lang.toLowerCase()) {
case 'zh-cn':
case 'zh-sg':
return 'SC'
case 'zh':
case 'zh-hk':
case 'zh-tw':
return 'TC'
case 'ja':
case 'ja-jp':
return 'JP'
case 'ko':
case 'ko-kr':
return 'KR'
default:
return null
}
}

8
lib/formatDate.js Normal file
View File

@@ -0,0 +1,8 @@
export default function formatDate(date, local) {
const d = new Date(date)
const options = { year: 'numeric', month: 'short', day: 'numeric' }
const res = d.toLocaleDateString(local, options)
return local.slice(0, 2).toLowerCase() === 'zh'
? res.replace('年', ' 年 ').replace('月', ' 月 ').replace('日', ' 日')
: res
}

18
lib/gtag.js Normal file
View File

@@ -0,0 +1,18 @@
import BLOG from '@/blog.config'
export const GA_TRACKING_ID = BLOG.analytics.gaConfig.measurementId
// https://developers.google.com/analytics/devguides/collection/gtagjs/pages
export const pageview = url => {
window.gtag('config', GA_TRACKING_ID, {
page_path: url
})
}
// https://developers.google.com/analytics/devguides/collection/gtagjs/events
export const event = ({ action, category, label, value }) => {
window.gtag('event', action, {
event_category: category,
event_label: label,
value: value
})
}

83
lib/lang.js Normal file
View File

@@ -0,0 +1,83 @@
const lang = {
en: {
NAV: {
INDEX: 'Blog',
RSS: 'RSS',
SEARCH: 'Search',
ABOUT: 'About',
NAVGATION: 'NAVGATION'
},
PAGINATION: {
PREV: 'Prev',
NEXT: 'Next'
},
SEARCH: {
ARTICLES: 'Search Articles',
TAGS: 'Search in'
},
POST: {
BACK: 'Back',
TOP: 'Top'
}
},
'zh-CN': {
NAV: {
INDEX: '首页',
RSS: '订阅',
SEARCH: '搜索',
ABOUT: '关于',
NAVGATION: '导航'
},
PAGINATION: {
PREV: '上一页',
NEXT: '下一页'
},
SEARCH: {
ARTICLES: '搜索文章',
TAGS: '搜索标签'
},
POST: {
BACK: '返回上页',
TOP: '回到顶部'
}
},
'zh-HK': {
NAV: {
INDEX: '網誌',
RSS: '訂閱',
SEARCH: '搜尋',
ABOUT: '關於',
NAVGATION: '導航'
},
PAGINATION: {
PREV: '上一頁',
NEXT: '下一頁'
},
SEARCH: {
ARTICLES: '搜尋文章',
TAGS: '搜尋標簽'
},
POST: {
BACK: '返回',
TOP: '回到頂端'
}
},
'zh-TW': {
NAV: {
INDEX: '部落格',
RSS: '訂閱',
SEARCH: '搜尋',
ABOUT: '關於',
NAVGATION: '導航'
},
PAGINATION: {
PREV: '上一頁',
NEXT: '下一頁'
},
POST: {
BACK: '返回',
TOP: '回到頂端'
}
}
}
export default lang

34
lib/locale.js Normal file
View File

@@ -0,0 +1,34 @@
import BLOG from '@/blog.config'
import lang from './lang'
import { useContext, createContext } from 'react'
let locale = {}
if (BLOG.lang.slice(0, 2).toLowerCase() === 'zh') {
switch (BLOG.lang.toLowerCase()) {
case 'zh-cn':
case 'zh-sg':
locale = lang['zh-CN']
break
case 'zh-hk':
locale = lang['zh-HK']
break
case 'zh-tw':
locale = lang['zh-TW']
break
default:
locale = lang['zh-TW']
break
}
} else {
locale = lang.en
}
const LocaleContext = createContext()
export function LocaleProvider({ children }) {
return (
<LocaleContext.Provider value={locale}>{children}</LocaleContext.Provider>
)
}
export const useLocale = () => useContext(LocaleContext)

3
lib/notion.js Normal file
View File

@@ -0,0 +1,3 @@
export { getAllPosts } from './notion/getAllPosts'
export { getAllTags } from './notion/getAllTags'
export { getPostBlocks } from './notion/getPostBlocks'

View File

@@ -0,0 +1,20 @@
import { idToUuid } from 'notion-utils'
export default function getAllPageIds (collectionQuery, viewId) {
const views = Object.values(collectionQuery)[0]
if (!views) {
return []
}
let pageIds = []
if (viewId) {
const vId = idToUuid(viewId)
pageIds = views[vId]?.blockIds
} else {
const pageSet = new Set()
Object.values(views).forEach(view => {
view?.blockIds?.forEach(id => pageSet.add(id))
})
pageIds = [...pageSet]
}
return pageIds
}

101
lib/notion/getAllPosts.js Normal file
View File

@@ -0,0 +1,101 @@
import BLOG from '@/blog.config'
import { idToUuid } from 'notion-utils'
import getAllPageIds from './getAllPageIds'
import getPageProperties from './getPageProperties'
import { defaultMapImageUrl } from 'react-notion-x'
import { getDataFromCache, setDataToCache } from '@/lib/cache/cache_manager'
import { getPostBlocks } from '@/lib/notion/getPostBlocks'
export async function getAllPosts () {
const data = await getDataFromCache('posts_list')
if (data) {
return data
}
let id = BLOG.notionPageId
const pageRecordMap = await getPostBlocks(id)
if (!pageRecordMap) {
return <>获取数据异常</>
}
id = idToUuid(id)
const collection = Object.values(pageRecordMap.collection)[0]?.value
const collectionQuery = pageRecordMap.collection_query
const block = pageRecordMap.block
const schema = collection?.schema
const rawMetadata = block[id].value
// Check Type 兼容Page-Database和Inline-Database
if (rawMetadata?.type !== 'collection_view_page' && rawMetadata?.type !== 'collection_view') {
console.warn(`pageId "${id}" is not a database`)
return null
} else {
// Construct Data
const pageIds = getAllPageIds(collectionQuery)
const data = []
for (let i = 0; i < pageIds.length; i++) {
const id = pageIds[i]
const properties = (await getPageProperties(id, block, schema)) || null
// Add fullwidth, createdtime to properties
properties.createdTime = new Date(
block[id].value?.created_time
).toString()
properties.fullWidth = block[id].value?.format?.page_full_width ?? false
properties.page_cover = getPostCover(id, block, pageRecordMap) ?? getContentFirstImage(id, block, pageRecordMap)
properties.content = block[id].value?.content ?? []
data.push(properties)
}
// remove all the the items doesn't meet requirements
const posts = data.filter(post => {
return (
post.title &&
post.slug &&
post?.status?.[0] === 'Published' &&
(post?.type?.[0] === 'Post' || post?.type?.[0] === 'Page')
)
})
// Sort by date
if (BLOG.sortByDate) {
posts.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
})
}
if (posts) {
await setDataToCache('posts_list', posts)
}
return posts
}
}
// 从Block获取封面图;优先取PageCover否则取内容图片
function getPostCover (id, block, pageRecordMap) {
const pageCover = block[id].value?.format?.page_cover
if (pageCover) {
if (pageCover.startsWith('/')) return 'https://www.notion.so' + pageCover
if (pageCover.startsWith('http')) return defaultMapImageUrl(pageCover, block[id].value)
}
}
// 取文章的第一个图片内容作为封面
function getContentFirstImage (id, block, pageRecordMap) {
const pageBlock = block[id]?.value
const contentBlockId = pageBlock?.content?.find((blockId) => {
const block = pageRecordMap.block[blockId]?.value
if (block?.type === 'image') {
return true
}
})
if (contentBlockId) {
const contentBlock = pageRecordMap.block[contentBlockId]?.value
const source = contentBlock.properties?.source?.[0]?.[0] ??
contentBlock.format?.display_source
return defaultMapImageUrl(source, contentBlock)
}
return ''
}

23
lib/notion/getAllTags.js Normal file
View File

@@ -0,0 +1,23 @@
import { getAllPosts } from './getAllPosts'
export async function getAllTags (posts) {
if (!posts) {
const response = await getAllPosts()
posts = response.filter(
post =>
post.status[0] === 'Published' && post.type[0] === 'Post' && post.tags
)
}
let tags = posts.map(p => p.tags)
tags = [...tags.flat()]
const tagObj = {}
tags.forEach(tag => {
if (tag in tagObj) {
tagObj[tag]++
} else {
tagObj[tag] = 1
}
})
return tagObj
}

11
lib/notion/getMetadata.js Normal file
View File

@@ -0,0 +1,11 @@
export default function getMetadata(rawMetadata) {
const metadata = {
locked: rawMetadata?.format?.block_locked,
page_full_width: rawMetadata?.format?.page_full_width,
page_font: rawMetadata?.format?.page_font,
page_small_text: rawMetadata?.format?.page_small_text,
created_time: rawMetadata.created_time,
last_edited_time: rawMetadata.last_edited_time
}
return metadata
}

View File

@@ -0,0 +1,60 @@
import { getTextContent, getDateValue } from 'notion-utils'
import { NotionAPI } from 'notion-client'
async function getPageProperties (id, block, schema, authToken) {
const rawProperties = Object.entries(block?.[id]?.value?.properties || [])
const excludeProperties = ['date', 'select', 'multi_select', 'person']
const properties = {}
for (let i = 0; i < rawProperties.length; i++) {
const [key, val] = rawProperties[i]
properties.id = id
if (schema[key]?.type && !excludeProperties.includes(schema[key].type)) {
properties[schema[key].name] = getTextContent(val)
} else {
switch (schema[key]?.type) {
case 'date': {
const dateProperty = getDateValue(val)
delete dateProperty.type
properties[schema[key].name] = dateProperty
break
}
case 'select':
case 'multi_select': {
const selects = getTextContent(val)
if (selects[0]?.length) {
properties[schema[key].name] = selects.split(',')
}
break
}
case 'person': {
const rawUsers = val.flat()
const users = []
const api = new NotionAPI({ authToken })
for (let i = 0; i < rawUsers.length; i++) {
if (rawUsers[i][0][1]) {
const userId = rawUsers[i][0]
const res = await api.getUsers(userId)
const resValue =
res?.recordMapWithRoles?.notion_user?.[userId[1]]?.value
const user = {
id: resValue?.id,
first_name: resValue?.given_name,
last_name: resValue?.family_name,
profile_photo: resValue?.profile_photo
}
users.push(user)
}
}
properties[schema[key].name] = users
break
}
default:
break
}
}
}
return properties
}
export { getPageProperties as default }

View File

@@ -0,0 +1,69 @@
const indentLevels = {
header: 0,
sub_header: 1,
sub_sub_header: 2
}
export const getPageTableOfContents = (page,recordMap)=> {
// 获取 header sub_header sub_sub_header
const toc = (page.content ?? [])
.map((blockId) => {
const block = recordMap.block[blockId]?.value
if (block) {
const { type } = block
if (
type === 'header' ||
type === 'sub_header' ||
type === 'sub_sub_header'
) {
return {
id: blockId,
type,
indentLevel: indentLevels[type]
}
}
}
return null
})
.filter(Boolean)
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()
}
} while (true)
}
return toc
}

View File

@@ -0,0 +1,17 @@
import BLOG from '@/blog.config'
import { NotionAPI } from 'notion-client'
import { getDataFromCache, setDataToCache } from '@/lib/cache/cache_manager'
export async function getPostBlocks (id) {
let pageBlock = await getDataFromCache('page_block_' + id)
if (pageBlock) {
return pageBlock
}
const authToken = BLOG.notionAccessToken || null
const api = new NotionAPI({ authToken })
pageBlock = await api.getPage(id)
if (pageBlock) {
await setDataToCache('page_block_' + id, pageBlock)
}
return pageBlock
}

30
lib/rss.js Normal file
View File

@@ -0,0 +1,30 @@
import { Feed } from 'feed'
import BLOG from '@/blog.config'
export function generateRss(posts) {
const year = new Date().getFullYear()
const feed = new Feed({
title: BLOG.title,
description: BLOG.description,
id: `${BLOG.link}/${BLOG.path}`,
link: `${BLOG.link}/${BLOG.path}`,
language: BLOG.lang,
favicon: `${BLOG.link}/favicon.png`,
copyright: `All rights reserved ${year}, ${BLOG.author}`,
author: {
name: BLOG.author,
email: BLOG.email,
link: BLOG.link
}
})
posts.forEach(post => {
feed.addItem({
title: post.title,
id: `${BLOG.link}/${post.slug}`,
link: `${BLOG.link}/${post.slug}`,
description: post.summary,
date: new Date(post?.date?.start_date || post.createdTime)
})
})
return feed.rss2()
}

19
lib/theme.js Normal file
View File

@@ -0,0 +1,19 @@
import { useContext, createContext, useState, useEffect } from 'react'
import localStorage from 'localStorage'
const ThemeContext = createContext()
export function ThemeProvider ({ children }) {
// 初始值
const defaultTheme = localStorage.getItem('theme')
const [theme, changeTheme] = useState()
useEffect(() => {
changeTheme(defaultTheme)
})
return (
<ThemeContext.Provider value={{ theme, changeTheme }}>{children}</ThemeContext.Provider>
)
}
export const useTheme = () => useContext(ThemeContext)

11
next-sitemap.config.js Normal file
View File

@@ -0,0 +1,11 @@
const BLOG = require('./blog.config')
module.exports = {
siteUrl: BLOG.link,
changefreq: 'daily',
priority: 0.7,
generateRobotsTxt: true,
sitemapSize: 7000
// ...other options
// https://github.com/iamvishnusankar/next-sitemap#configuration-options
}

32
next.config.js Normal file
View File

@@ -0,0 +1,32 @@
module.exports = {
future: {
webpack5: true
},
images: {
domains: ['gravatar.com']
},
async headers() {
return [
{
source: '/:path*{/}?',
headers: [
{
key: 'Permissions-Policy',
value: 'interest-cohort=()'
}
]
}
]
},
webpack: (config, { dev, isServer }) => {
// Replace React with Preact only in client production build
if (!dev && !isServer) {
Object.assign(config.resolve.alias, {
react: 'preact/compat',
'react-dom/test-utils': 'preact/test-utils',
'react-dom': 'preact/compat'
})
}
return config
}
}

62
package.json Normal file
View File

@@ -0,0 +1,62 @@
{
"name": "notion-next",
"version": "1.0.0",
"homepage": "https://github.com/tangly1024/NotionNext.git",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/tangly1024/NotionNext.git"
},
"author": {
"name": "tangly",
"email": "tlyong1992@hotmail.com",
"url": "http://tangly1024.com"
},
"scripts": {
"dev": "NODE_OPTIONS='--inspect' next dev",
"build": "next build",
"start": "next start",
"postbuild": "next-sitemap --config next-sitemap.config.js"
},
"dependencies": {
"@popperjs/core": "^2.9.3",
"animate.css": "^4.1.1",
"copy-to-clipboard": "^3.3.1",
"feed": "^4.2.2",
"font-awesome": "^4.7.0",
"gitalk": "^1.7.2",
"localStorage": "^1.0.4",
"lodash.throttle": "^4.1.1",
"memory-cache": "^0.2.0",
"next": "10.2.0",
"notion-client": "4.8.6",
"notion-utils": "4.8.6",
"preact": "^10.5.13",
"qrcode.react": "^1.0.1",
"react": "17.0.2",
"react-cusdis": "^2.0.1",
"react-dom": "17.0.2",
"react-notion-x": "^4.6.5",
"use-ackee": "^3.0.0"
},
"devDependencies": {
"autoprefixer": "^10.2.5",
"eslint": "^7.26.0",
"eslint-config-next": "^11.0.0",
"eslint-config-standard": "^16.0.2",
"eslint-plugin-import": "^2.23.0",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^5.1.0",
"eslint-plugin-react": "^7.23.2",
"next-sitemap": "^1.6.102",
"postcss": "^8.2.15",
"tailwindcss": "^2.1.2"
},
"resolutions": {
"axios": ">=0.21.1"
},
"bugs": {
"url": "https://github.com/tangly/NotionNext/issues",
"email": "tlyong1992@hotmail.com"
}
}

BIN
pageid.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

31
pages/404.js Normal file
View File

@@ -0,0 +1,31 @@
/**
* 自定义404界面
* @returns {JSX.Element}
* @constructor
*/
import { useRouter } from 'next/router'
import { useEffect } from 'react'
export default function Custom404 () {
const route = useRouter()
if (route.asPath.indexOf('/article') < 0 && route.asPath.indexOf('/404') < 0) {
// article 重定向,处理旧文章链接迁移。
const redirectUrl = '/article' + route.asPath
route.push(redirectUrl)
} else {
useEffect(() => {
setTimeout(() => {
window.location.href = '/'
}, 3000)
})
}
return <div
className='text-black bg-white h-screen text-center justify-center content-center items-center flex flex-col'>
<div>
<h1 className='inline-block border-r-2 border-gray-600 mr-2 px-3 py-2 align-top'>404</h1>
<div className='inline-block text-left h-32 leading-10 align-middle'>
<h2 className='m-0 p-0'>页面丢失了3秒后返回首页</h2></div>
</div>
</div>
}

37
pages/_app.js Normal file
View File

@@ -0,0 +1,37 @@
import '@/styles/notion.css'
import 'rc-dropdown/assets/index.css'
import 'katex/dist/katex.min.css'
import '@/styles/globals.css'
import 'prismjs'
import 'prismjs/themes/prism-okaidia.css'
import 'prismjs/components/prism-markup'
import 'prismjs/components/prism-python'
import 'prismjs/components/prism-bash'
import 'animate.css'
import 'font-awesome/css/font-awesome.min.css'
import BLOG from '@/blog.config'
import dynamic from 'next/dynamic'
import { LocaleProvider } from '@/lib/locale'
import { ThemeProvider } from '@/lib/theme'
const Ackee = dynamic(() => import('@/components/Ackee'), { ssr: false })
const Gtag = dynamic(() => import('@/components/Gtag'), { ssr: false })
function MyApp ({ Component, pageProps }) {
return (
<LocaleProvider>
<ThemeProvider>
{BLOG.isProd && BLOG?.analytics?.provider === 'ackee' && (
<Ackee
ackeeServerUrl={BLOG.analytics.ackeeConfig.dataAckeeServer}
ackeeDomainId={BLOG.analytics.ackeeConfig.domainId}
/>
)}
{BLOG.isProd && BLOG?.analytics?.provider === 'ga' && <Gtag />}
<Component {...pageProps} />
</ThemeProvider>
</LocaleProvider>
)
}
export default MyApp

119
pages/_document.js Normal file
View File

@@ -0,0 +1,119 @@
import Document, { Html, Head, Main, NextScript } from 'next/document'
import BLOG from '@/blog.config'
class MyDocument extends Document {
static async getInitialProps (ctx) {
const initialProps = await Document.getInitialProps(ctx)
return { ...initialProps }
}
render () {
return (
<Html lang={BLOG.lang} >
<Head>
<link rel='icon' href='/favicon.ico' />
<link rel='icon' href='/favicon.svg' type='image/svg+xml'></link>
{/* 字体 */}
{/* <link rel='stylesheet' href='/font-awesome-4.7.0/css/font-awesome.min.css'/> */}
{/* <link rel='stylesheet' href='https://cdn.bootcdn.net/ajax/libs/font-awesome/5.15.3/css/all.min.css'/> */}
{/* GoogleAdsense */}
{BLOG.isProd && (
<script data-ad-client="ca-pub-2708419466378217" async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
)}
{/* 统计脚本 */}
{BLOG.isProd && BLOG.analytics && BLOG.analytics.provider === 'ackee' && (
<script async src={BLOG.analytics.ackeeConfig.tracker}
data-ackee-server={BLOG.analytics.ackeeConfig.dataAckeeServer}
data-ackee-domain-id={BLOG.analytics.ackeeConfig.domainId}
/>
)}
{BLOG.isProd && BLOG.autoCollapsedNavBar === true && (
<script
dangerouslySetInnerHTML={{
__html: `
var windowTop=0;
function scrollTrigger(){
let scrollS = window.scrollY;
let nav = document.querySelector('.sticky-nav');
if(scrollS >= windowTop){
nav.style.opacity = 0;
windowTop = scrollS;
}else{
nav.style.opacity = 1;
windowTop = scrollS;
}
};
window.addEventListener('scroll',scrollTrigger);
`
}}
/>
)}
{/* 百度 */}
{BLOG.isProd && (
<script async
dangerouslySetInnerHTML={{
__html: `
var _hmt = _hmt || [];
(function() {
var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?f683ef76f06bb187cbed5546f6f28f28";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
`
}}
/>
)}
{/* 不蒜子 */}
{BLOG.isProd && (
<script async
src={'//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js'}
/>
)}
{BLOG.isProd && (
<script async
dangerouslySetInnerHTML={{
__html: `
document.write(unescape("%3Cspan style='display:none' id='cnzz_stat_icon_1279970751'%3E%3C/span%3E%3Cscript src='https://s9.cnzz.com/z_stat.php%3Fid%3D1279970751' type='text/javascript'%3E%3C/script%3E"));
`
}}
/>
)}
{/* 谷歌统计 */}
{BLOG.isProd && BLOG.analytics && BLOG.analytics.provider === 'ga' && (
<>
<script async
src={`https://www.googletagmanager.com/gtag/js?id=${BLOG.analytics.gaConfig.measurementId}`}
/>
<script async
dangerouslySetInnerHTML={{
__html: `
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '${BLOG.analytics.gaConfig.measurementId}', {
page_path: window.location.pathname,
});
`
}}
/>
</>
)}
</Head>
<body className='dark:bg-black h-full'>
<Main />
<NextScript />
</body>
</Html>
)
}
}
export default MyDocument

60
pages/article/[slug].js Normal file
View File

@@ -0,0 +1,60 @@
import ArticleLayout from '@/layouts/ArticleLayout'
import { getAllPosts, getAllTags, getPostBlocks } from '@/lib/notion'
import BLOG from '@/blog.config'
import { createHash } from 'crypto'
import { getPageTableOfContents } from 'notion-utils'
import Custom404 from '@/pages/404'
const BlogPost = ({ post, blockMap, emailHash, tags, prev, next }) => {
if (!post) {
return <Custom404/>
}
return (
<ArticleLayout
blockMap={blockMap}
frontMatter={post}
emailHash={emailHash}
tags={tags}
prev={prev}
next={next}
></ArticleLayout>
)
}
export async function getStaticPaths () {
let posts = await getAllPosts()
posts = posts.filter(post => post.status[0] === 'Published')
return {
paths: posts.map(row => `${BLOG.path}/article/${row.slug}`),
fallback: true
}
}
export async function getStaticProps ({ params: { slug } }) {
let posts = await getAllPosts()
posts = posts.filter(post => post.status[0] === 'Published')
const post = posts.find(t => t.slug === slug)
if (!post) {
return {
props: { },
revalidate: 1
}
}
const blockMap = await getPostBlocks(post.id)
const emailHash = createHash('md5').update(BLOG.email).digest('hex')
post.toc = getPageTableOfContents(post, blockMap)
posts = posts.filter(post => post.type[0] === 'Post')
const tags = await getAllTags(posts)
// 获取推荐文章
const index = posts.indexOf(post)
const prev = posts.slice(index - 1, index)[0] ?? posts.slice(-1)[0]
const next = posts.slice(index + 1, index + 2)[0] ?? posts[0]
return {
props: { post, blockMap, emailHash, tags, prev, next },
revalidate: 1
}
}
export default BlogPost

19
pages/feed.js Normal file
View File

@@ -0,0 +1,19 @@
import { getAllPosts } from '@/lib/notion'
import { generateRss } from '@/lib/rss'
export async function getServerSideProps ({ res }) {
res.setHeader('Content-Type', 'text/xml')
let posts = await getAllPosts()
posts = posts
.filter(post => post.status[0] === 'Published' && post.type[0] === 'Post')
.slice(0, 10)
const xmlFeed = generateRss(posts)
res.write(xmlFeed)
res.end()
return {
props: {}
}
}
const feed = () => null
export default feed

27
pages/index.js Normal file
View File

@@ -0,0 +1,27 @@
import { getAllPosts, getAllTags } from '@/lib/notion'
import DefaultLayout from '@/layouts/DefaultLayout'
export async function getStaticProps () {
let posts = await getAllPosts()
posts = posts.filter(
post => post.status[0] === 'Published' && post.type[0] === 'Post'
)
const tags = await getAllTags(posts)
return {
props: {
page: 1, // current page is 1
posts,
tags
},
revalidate: 1
}
}
const blog = ({ posts, page, tags }) => {
return (
<DefaultLayout tags={tags} posts={posts} page={page} />
)
}
export default blog

63
pages/page/[page].js Normal file
View File

@@ -0,0 +1,63 @@
import { getAllPosts, getAllTags } from '@/lib/notion'
import BLOG from '@/blog.config'
import DefaultLayout from '@/layouts/DefaultLayout'
import { useRouter } from 'next/router'
const Page = ({ posts, tags, page }) => {
let filteredBlogPosts = posts
if (posts) {
const router = useRouter()
if (router.query && router.query.s) {
filteredBlogPosts = posts.filter(post => {
const tagContent = post.tags ? post.tags.join(' ') : ''
const searchContent = post.title + post.summary + tagContent
return searchContent.toLowerCase().includes(router.query.s.toLowerCase())
})
}
}
return <DefaultLayout tags={tags} posts={filteredBlogPosts} page={page} />
}
export async function getStaticProps (context) {
const { page } = context.params // Get Current Page No.
let posts = await getAllPosts()
posts = posts.filter(
post => post.status[0] === 'Published' && post.type[0] === 'Post'
)
const tags = await getAllTags(posts)
return {
props: {
tags,
posts,
page
},
revalidate: 1
}
}
export async function getStaticPaths () {
if (BLOG.isProd) {
// 预渲染
let posts = await getAllPosts()
posts = posts.filter(
post => post.status[0] === 'Published' && post.type[0] === 'Post'
)
const totalPosts = posts.length
const totalPages = Math.ceil(totalPosts / BLOG.postsPerPage)
return {
// remove first page, we 're not gonna handle that.
paths: Array.from({ length: totalPages - 1 }, (_, i) => ({
params: { page: '' + (i + 2) }
})),
fallback: true
}
} else {
return {
paths: [],
fallback: true
}
}
}
export default Page

43
pages/tag/[tag].js Normal file
View File

@@ -0,0 +1,43 @@
import { getAllPosts, getAllTags } from '@/lib/notion'
import DefaultLayout from '@/layouts/DefaultLayout'
import BLOG from '@/blog.config'
export default function Tag ({ tags, posts, currentTag }) {
return <DefaultLayout tags={tags} posts={posts} currentTag={currentTag} />
}
export async function getStaticProps ({ params }) {
const currentTag = params.tag
let posts = await getAllPosts()
posts = posts.filter(
post => post.status[0] === 'Published' && post.type[0] === 'Post'
)
const tags = await getAllTags(posts)
const filteredPosts = posts.filter(
post => post && post.tags && post.tags.includes(currentTag)
)
return {
props: {
tags,
posts: filteredPosts,
currentTag
},
revalidate: 1
}
}
export async function getStaticPaths () {
if (BLOG.isProd) {
// 预渲染
const tags = await getAllTags()
return {
paths: Object.keys(tags).map(tag => ({ params: { tag } })),
fallback: true
}
} else {
return {
paths: [],
fallback: true
}
}
}

6
postcss.config.js Normal file
View File

@@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {}
}
}

1
public/ads.txt Normal file
View File

@@ -0,0 +1 @@
google.com, pub-2708419466378217, DIRECT, f08c47fec0942fa0

898
public/avatar.svg Normal file
View File

@@ -0,0 +1,898 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="256px" height="256px" viewBox="0 0 256 256" enable-background="new 0 0 256 256" xml:space="preserve"> <image id="image0" width="256" height="256" x="0" y="0"
href="data:image/png;base64,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
YII=" />
</svg>

After

Width:  |  Height:  |  Size: 68 KiB

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 KiB

9
public/favicon.svg Normal file
View File

@@ -0,0 +1,9 @@
<svg width="117" height="110" viewBox="0 0 117 110" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M102.282 87.5128C84.385 102.427 66.8743 109.829 49.7503 109.719C32.6263 109.608 19.4242 102.151 10.1441 87.3471C0.863999 72.5431 -1.78746 53.2095 2.18973 29.3464C6.16692 5.48324 20.3633 -3.68639 44.7788 1.83748C69.1943 7.36136 89.3565 19.2929 105.265 37.6322C121.174 55.9715 120.18 72.5983 102.282 87.5128Z" fill="url(#paint0_linear)"/>
<defs>
<linearGradient id="paint0_linear" x1="0.399994" y1="0.279999" x2="0.399994" y2="109.72" gradientUnits="userSpaceOnUse">
<stop stop-color="#FF5F6D"/>
<stop offset="1" stop-color="#FFC371"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 676 B

BIN
public/reward_code.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

42
tailwind.config.js Normal file
View File

@@ -0,0 +1,42 @@
const BLOG = require('./blog.config')
const { fontFamily } = require('tailwindcss/defaultTheme')
const CJK = require('./lib/cjk')
const fontSansCJK = !CJK()
? []
: [`"Noto Sans CJK ${CJK()}"`, `"Noto Sans ${CJK()}"`]
const fontSerifCJK = !CJK()
? []
: [`"Noto Serif CJK ${CJK()}"`, `"Noto Serif ${CJK()}"`]
module.exports = {
purge: ['./pages/**/*.js', './components/**/*.js', './layouts/**/*.js'],
darkMode: BLOG.appearance === 'class' ? 'media' : 'class', // or 'media' or 'class'
theme: {
extend: {
colors: {
day: {
DEFAULT: BLOG.lightBackground || '#ffffff'
},
night: {
DEFAULT: BLOG.darkBackground || '#111827'
}
},
fontFamily: {
sans: ['"IBM Plex Sans"', ...fontSansCJK, ...fontFamily.sans],
serif: ['"Source Serif"', ...fontSerifCJK, ...fontFamily.serif],
noEmoji: [
'"IBM Plex Sans"',
'ui-sans-serif',
'system-ui',
'-apple-system',
'BlinkMacSystemFont',
'sans-serif'
]
}
}
},
variants: {
extend: {}
},
plugins: []
}

5
vercel.json Normal file
View File

@@ -0,0 +1,5 @@
{
"cleanUrls": true,
"trailingSlash": false,
"headers": []
}