nnbb 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dockerignore +1 -0
- package/.env.local +2 -0
- package/.eslintrc.json +15 -0
- package/.github/stale.yml +16 -0
- package/.github/workflows/codeql-analysis.yml +73 -0
- package/.github/workflows/docker-ghcr.yaml +59 -0
- package/.prettierrc +9 -0
- package/.vscode/launch.json +28 -0
- package/.vscode/settings.json +6 -0
- package/Dockerfile +38 -0
- package/LICENSE +21 -0
- package/README.md +16 -0
- package/blog.config.js +454 -0
- package/components/Ackee.js +83 -0
- package/components/AdBlockDetect.js +40 -0
- package/components/AlgoliaSearchModal.js +250 -0
- package/components/Artalk.js +30 -0
- package/components/Busuanzi.js +26 -0
- package/components/ChatBase.js +19 -0
- package/components/Collapse.tsx +134 -0
- package/components/Comment.js +161 -0
- package/components/CommonHead.tsx +101 -0
- package/components/CommonScript.js +125 -0
- package/components/CusdisComponent.js +35 -0
- package/components/CustomContextMenu.js +221 -0
- package/components/DebugPanel.js +134 -0
- package/components/DisableCopy.js +21 -0
- package/components/Draggable.js +167 -0
- package/components/Equation.js +31 -0
- package/components/ExternalPlugins.js +75 -0
- package/components/ExternalScript.js +29 -0
- package/components/FacebookMessenger.js +255 -0
- package/components/FacebookPage.js +34 -0
- package/components/Fireworks.js +210 -0
- package/components/FlipCard.js +56 -0
- package/components/FlutteringRibbon.js +322 -0
- package/components/FullScreenButton.js +48 -0
- package/components/Giscus.js +33 -0
- package/components/Gitalk.js +42 -0
- package/components/GoogleAdsense.js +111 -0
- package/components/Gtag.js +18 -0
- package/components/HeroIcons.tsx +321 -0
- package/components/KatexReact.js +53 -0
- package/components/LazyImage.js +95 -0
- package/components/Live2D.js +52 -0
- package/components/Loading.js +20 -0
- package/components/Mark.js +28 -0
- package/components/NProgress.ts +8 -0
- package/components/Nest.js +124 -0
- package/components/NotionIcon.js +20 -0
- package/components/NotionPage.tsx +206 -0
- package/components/Player.js +54 -0
- package/components/PrismMac.js +236 -0
- package/components/QrCode.js +34 -0
- package/components/Ribbon.js +98 -0
- package/components/Sakura.js +192 -0
- package/components/Select.js +40 -0
- package/components/ShareBar.js +29 -0
- package/components/ShareButtons.js +403 -0
- package/components/SideBarDrawer.js +50 -0
- package/components/StarrySky.js +130 -0
- package/components/Tabs.js +64 -0
- package/components/ThemeSwitch.js +67 -0
- package/components/Twikoo.js +27 -0
- package/components/TwikooCommentCount.js +22 -0
- package/components/TwikooCommentCounter.js +78 -0
- package/components/TwikooRecentComments.js +11 -0
- package/components/Utterances.js +35 -0
- package/components/VConsole.js +76 -0
- package/components/ValineComponent.js +59 -0
- package/components/ValineCount.js +6 -0
- package/components/ValinePanel.js +3 -0
- package/components/Vercel.tsx +54 -0
- package/components/WWAds.js +18 -0
- package/components/WalineComponent.js +83 -0
- package/components/WebMention.js +173 -0
- package/components/Webwhiz.js +17 -0
- package/components/WordCount.js +73 -0
- package/hooks/useToggleClickOutSide.ts +32 -0
- package/hooks/useWindowSize.ts +30 -0
- package/lib/algolia.js +108 -0
- package/lib/busuanzi.js +99 -0
- package/lib/cache/cacheManager.ts +49 -0
- package/lib/cache/localFileCache.ts +56 -0
- package/lib/cache/memoryMache.ts +20 -0
- package/lib/cache/mongoDbCache.ts +70 -0
- package/lib/cache/types.ts +5 -0
- package/lib/font.js +46 -0
- package/lib/global.tsx +129 -0
- package/lib/gtag.js +17 -0
- package/lib/mailchimp.js +49 -0
- package/lib/memorize.js +0 -0
- package/lib/mhchem.js +1696 -0
- package/lib/notion/getAllCategories.ts +51 -0
- package/lib/notion/getAllPageIds.ts +51 -0
- package/lib/notion/getAllPosts.js +68 -0
- package/lib/notion/getAllTags.ts +43 -0
- package/lib/notion/getNotionData.ts +340 -0
- package/lib/notion/getPageInfoOfPostPage.ts +58 -0
- package/lib/notion/getPageProperties.ts +203 -0
- package/lib/notion/getPageTableOfContents.ts +107 -0
- package/lib/notion/getPostBlocks.ts +147 -0
- package/lib/notion/mapImage.ts +130 -0
- package/lib/notion/types.ts +125 -0
- package/lib/notion.js +2 -0
- package/lib/robots.txt.js +25 -0
- package/lib/rss.js +63 -0
- package/lib/sitemap.xml.js +67 -0
- package/lib/utils.js +212 -0
- package/next-env.d.ts +5 -0
- package/next-i18next.config.js +7 -0
- package/next-sitemap.config.js +11 -0
- package/next.config.js +124 -0
- package/package.json +92 -0
- package/pages/404.tsx +40 -0
- package/pages/[prefix]/[slug].tsx +123 -0
- package/pages/[prefix]/index.tsx +223 -0
- package/pages/_app.js +59 -0
- package/pages/_document.js +42 -0
- package/pages/api/subscribe.js +22 -0
- package/pages/archive/index.tsx +79 -0
- package/pages/category/[category]/index.tsx +87 -0
- package/pages/category/[category]/page/[page].tsx +103 -0
- package/pages/category/index.tsx +43 -0
- package/pages/index.tsx +88 -0
- package/pages/page/[page].tsx +93 -0
- package/pages/search/[keyword]/index.tsx +162 -0
- package/pages/search/[keyword]/page/[page].tsx +166 -0
- package/pages/search/index.tsx +69 -0
- package/pages/sitemap.xml.js +70 -0
- package/pages/tag/[tag]/index.tsx +73 -0
- package/pages/tag/[tag]/page/[page].tsx +87 -0
- package/pages/tag/index.tsx +42 -0
- package/postcss.config.js +6 -0
- package/public/ads.txt +1 -0
- package/public/avatar.png +0 -0
- package/public/avatar.svg +11 -0
- package/public/bg_image.jpg +0 -0
- package/public/css/all.min.css +9 -0
- package/public/css/custom.css +8 -0
- package/public/css/img-shadow.css +5 -0
- package/public/css/prism-mac-style.css +58 -0
- package/public/favicon.ico +0 -0
- package/public/favicon.svg +9 -0
- package/public/js/cusdis.es.js +107 -0
- package/public/js/custom.js +1 -0
- package/public/locales/en/common.json +44 -0
- package/public/locales/en/menu.json +9 -0
- package/public/locales/en/nav.json +11 -0
- package/public/locales/zh-CN/common.json +44 -0
- package/public/locales/zh-CN/menu.json +9 -0
- package/public/locales/zh-CN/nav.json +9 -0
- package/public/webfonts/fa-brands-400.ttf +0 -0
- package/public/webfonts/fa-brands-400.woff2 +0 -0
- package/public/webfonts/fa-regular-400.ttf +0 -0
- package/public/webfonts/fa-regular-400.woff2 +0 -0
- package/public/webfonts/fa-solid-900.ttf +0 -0
- package/public/webfonts/fa-solid-900.woff2 +0 -0
- package/public/webfonts/fa-v4compatibility.ttf +0 -0
- package/public/webfonts/fa-v4compatibility.woff2 +0 -0
- package/styles/animate.css +503 -0
- package/styles/globals.css +183 -0
- package/styles/notion.css +2064 -0
- package/styles/nprogress.css +84 -0
- package/styles/prism-theme.css +119 -0
- package/styles/utility-patterns.css +79 -0
- package/tailwind.config.js +37 -0
- package/theme/index.ts +6 -0
- package/theme/types/@theme-components.d.ts +29 -0
- package/theme/useLayout.ts +41 -0
- package/theme/utils.ts +108 -0
- package/themes/innocent/package.json +7 -0
- package/themes/innocent/theme.config.js +1 -0
- package/themes/nobelium/components/Announcement.tsx +27 -0
- package/themes/nobelium/components/ArticleFooter.tsx +39 -0
- package/themes/nobelium/components/ArticleInfo.tsx +58 -0
- package/themes/nobelium/components/ArticleLock.tsx +86 -0
- package/themes/nobelium/components/BlogArchiveItem.js +41 -0
- package/themes/nobelium/components/BlogListBar.js +39 -0
- package/themes/nobelium/components/BlogListPage.tsx +67 -0
- package/themes/nobelium/components/BlogListScroll.tsx +96 -0
- package/themes/nobelium/components/BlogPost.tsx +33 -0
- package/themes/nobelium/components/DarkModeButton.tsx +50 -0
- package/themes/nobelium/components/ExampleRecentComments.js +35 -0
- package/themes/nobelium/components/Footer.tsx +35 -0
- package/themes/nobelium/components/JumpToTopButton.tsx +39 -0
- package/themes/nobelium/components/LanguageSwitchButton.tsx +58 -0
- package/themes/nobelium/components/MenuItemCollapse.tsx +92 -0
- package/themes/nobelium/components/MenuItemDrop.tsx +83 -0
- package/themes/nobelium/components/Nav/Nav.module.css +50 -0
- package/themes/nobelium/components/Nav/Nav.tsx +187 -0
- package/themes/nobelium/components/RandomPostButton.tsx +31 -0
- package/themes/nobelium/components/SearchButton.tsx +31 -0
- package/themes/nobelium/components/SearchInput.tsx +94 -0
- package/themes/nobelium/components/SearchNavBar.js +19 -0
- package/themes/nobelium/components/SideBar.js +83 -0
- package/themes/nobelium/components/SvgIcon.js +29 -0
- package/themes/nobelium/components/TagItem.js +13 -0
- package/themes/nobelium/components/Tags.tsx +44 -0
- package/themes/nobelium/components/Title.js +19 -0
- package/themes/nobelium/index.tsx +28 -0
- package/themes/nobelium/layout/LayoutBase.tsx +79 -0
- package/themes/nobelium/pages/Archive.tsx +30 -0
- package/themes/nobelium/pages/Category.tsx +43 -0
- package/themes/nobelium/pages/Home.tsx +22 -0
- package/themes/nobelium/pages/PageNotFound.tsx +15 -0
- package/themes/nobelium/pages/Post.tsx +34 -0
- package/themes/nobelium/pages/PostList.tsx +74 -0
- package/themes/nobelium/pages/Search.tsx +65 -0
- package/themes/nobelium/pages/Tag.tsx +42 -0
- package/themes/nobelium/providers/index.tsx +60 -0
- package/themes/nobelium/stores/index.tsx +42 -0
- package/themes/nobelium/theme.config.ts +17 -0
- package/themes/nobelium/types/index.ts +10 -0
- package/tsconfig.json +29 -0
- package/types/index.ts +1 -0
- package/types/page.ts +102 -0
- package/vercel.json +5 -0
package/lib/notion.js
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
|
2
|
+
import fs from 'fs'
|
3
|
+
import BLOG from '@/blog.config'
|
4
|
+
|
5
|
+
export async function generateRobotsTxt() {
|
6
|
+
const content = `
|
7
|
+
# *
|
8
|
+
User-agent: *
|
9
|
+
Allow: /
|
10
|
+
|
11
|
+
# Host
|
12
|
+
Host: ${BLOG.LINK}
|
13
|
+
|
14
|
+
# Sitemaps
|
15
|
+
Sitemap: ${BLOG.LINK}/sitemap.xml
|
16
|
+
|
17
|
+
`
|
18
|
+
try {
|
19
|
+
fs.mkdirSync('./public', { recursive: true })
|
20
|
+
fs.writeFileSync('./public/robots.txt', content)
|
21
|
+
} catch (error) {
|
22
|
+
// 在vercel运行环境是只读的,这里会报错;
|
23
|
+
// 但在vercel编译阶段、或VPS等其他平台这行代码会成功执行
|
24
|
+
}
|
25
|
+
}
|
package/lib/rss.js
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
import fs from 'fs'
|
2
|
+
import { Feed } from 'feed'
|
3
|
+
import BLOG from '@/blog.config'
|
4
|
+
import ReactDOMServer from 'react-dom/server'
|
5
|
+
import { getPostBlocks } from './notion'
|
6
|
+
import NotionPage from '@/components/NotionPage'
|
7
|
+
|
8
|
+
/**
|
9
|
+
* 生成RSS内容
|
10
|
+
* @param {*} post
|
11
|
+
* @returns
|
12
|
+
*/
|
13
|
+
const createFeedContent = async post => {
|
14
|
+
// 加密的文章内容只返回摘要
|
15
|
+
if (post.password && post.password !== '') {
|
16
|
+
return post.summary
|
17
|
+
}
|
18
|
+
const blockMap = await getPostBlocks(post.id, 'rss-content')
|
19
|
+
if (blockMap) {
|
20
|
+
post.blockMap = blockMap
|
21
|
+
const content = ReactDOMServer.renderToString(<NotionPage post={post} />)
|
22
|
+
const regexExp =
|
23
|
+
/<div class="notion-collection-row"><div class="notion-collection-row-body"><div class="notion-collection-row-property"><div class="notion-collection-column-title"><svg.*?class="notion-collection-column-title-icon">.*?<\/svg><div class="notion-collection-column-title-body">.*?<\/div><\/div><div class="notion-collection-row-value">.*?<\/div><\/div><\/div><\/div>/g
|
24
|
+
return content.replace(regexExp, '')
|
25
|
+
}
|
26
|
+
}
|
27
|
+
|
28
|
+
export async function generateRss(posts) {
|
29
|
+
const year = new Date().getFullYear()
|
30
|
+
const feed = new Feed({
|
31
|
+
title: BLOG.TITLE,
|
32
|
+
description: BLOG.DESCRIPTION,
|
33
|
+
link: `${BLOG.LINK}/${BLOG.SUB_PATH}`,
|
34
|
+
language: BLOG.LANG,
|
35
|
+
favicon: `${BLOG.LINK}/favicon.png`,
|
36
|
+
copyright: `All rights reserved ${year}, ${BLOG.AUTHOR}`,
|
37
|
+
author: {
|
38
|
+
name: BLOG.AUTHOR,
|
39
|
+
email: BLOG.CONTACT_EMAIL,
|
40
|
+
link: BLOG.LINK
|
41
|
+
}
|
42
|
+
})
|
43
|
+
for (const post of posts) {
|
44
|
+
feed.addItem({
|
45
|
+
title: post.title,
|
46
|
+
link: `${BLOG.LINK}/${post.slug}`,
|
47
|
+
description: post.summary,
|
48
|
+
content: await createFeedContent(post),
|
49
|
+
date: new Date(post?.publishDay)
|
50
|
+
})
|
51
|
+
}
|
52
|
+
|
53
|
+
try {
|
54
|
+
fs.mkdirSync('./public/rss', { recursive: true })
|
55
|
+
fs.writeFileSync('./public/rss/feed.xml', feed.rss2())
|
56
|
+
fs.writeFileSync('./public/rss/atom.xml', feed.atom1())
|
57
|
+
fs.writeFileSync('./public/rss/feed.json', feed.json1())
|
58
|
+
} catch (error) {
|
59
|
+
// 在vercel运行环境是只读的,这里会报错;
|
60
|
+
// 但在vercel编译阶段、或VPS等其他平台这行代码会成功执行
|
61
|
+
// RSS被高频词访问将大量消耗服务端资源,故作为静态文件
|
62
|
+
}
|
63
|
+
}
|
@@ -0,0 +1,67 @@
|
|
1
|
+
|
2
|
+
import fs from 'fs'
|
3
|
+
import BLOG from '@/blog.config'
|
4
|
+
|
5
|
+
export async function generateSitemapXml({ allPages }) {
|
6
|
+
const urls = [{
|
7
|
+
loc: `${BLOG.LINK}`,
|
8
|
+
lastmod: new Date().toISOString().split('T')[0],
|
9
|
+
changefreq: 'daily'
|
10
|
+
}, {
|
11
|
+
loc: `${BLOG.LINK}/archive`,
|
12
|
+
lastmod: new Date().toISOString().split('T')[0],
|
13
|
+
changefreq: 'daily'
|
14
|
+
}, {
|
15
|
+
loc: `${BLOG.LINK}/category`,
|
16
|
+
lastmod: new Date().toISOString().split('T')[0],
|
17
|
+
changefreq: 'daily'
|
18
|
+
}, {
|
19
|
+
loc: `${BLOG.LINK}/tag`,
|
20
|
+
lastmod: new Date().toISOString().split('T')[0],
|
21
|
+
changefreq: 'daily'
|
22
|
+
}]
|
23
|
+
|
24
|
+
// 循环页面生成
|
25
|
+
allPages?.forEach(post => {
|
26
|
+
const slugWithoutLeadingSlash = post?.slug?.startsWith('/') ? post?.slug?.slice(1) : post.slug
|
27
|
+
urls.push({
|
28
|
+
loc: `${BLOG.LINK}/${slugWithoutLeadingSlash}`,
|
29
|
+
lastmod: new Date(post?.publishDay).toISOString().split('T')[0],
|
30
|
+
changefreq: 'daily'
|
31
|
+
})
|
32
|
+
})
|
33
|
+
const xml = createSitemapXml(urls)
|
34
|
+
try {
|
35
|
+
fs.writeFileSync('sitemap.xml', xml)
|
36
|
+
fs.writeFileSync('./public/sitemap.xml', xml)
|
37
|
+
} catch (error) {
|
38
|
+
console.warn('无法写入文件', error)
|
39
|
+
}
|
40
|
+
}
|
41
|
+
|
42
|
+
/**
|
43
|
+
* 生成站点地图
|
44
|
+
* @param {*} urls
|
45
|
+
* @returns
|
46
|
+
*/
|
47
|
+
function createSitemapXml(urls) {
|
48
|
+
let urlsXml = ''
|
49
|
+
urls.forEach(u => {
|
50
|
+
urlsXml += `<url>
|
51
|
+
<loc>${u.loc}</loc>
|
52
|
+
<lastmod>${u.lastmod}</lastmod>
|
53
|
+
<changefreq>${u.changefreq}</changefreq>
|
54
|
+
</url>
|
55
|
+
`
|
56
|
+
})
|
57
|
+
|
58
|
+
return `
|
59
|
+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
|
60
|
+
xmlns:news="http://www.google.com/schemas/sitemap-news/0.9"
|
61
|
+
xmlns:xhtml="http://www.w3.org/1999/xhtml"
|
62
|
+
xmlns:mobile="http://www.google.com/schemas/sitemap-mobile/1.0"
|
63
|
+
xmlns:image="http://www.google.com/schemas/sitemap-image/1.1">
|
64
|
+
${urlsXml}
|
65
|
+
</urlset>
|
66
|
+
`
|
67
|
+
}
|
package/lib/utils.js
ADDED
@@ -0,0 +1,212 @@
|
|
1
|
+
// 封装异步加载资源的方法
|
2
|
+
import { memo } from 'react';
|
3
|
+
|
4
|
+
/**
|
5
|
+
* 判断是否客户端
|
6
|
+
* @returns {boolean}
|
7
|
+
*/
|
8
|
+
export const isBrowser = typeof window !== 'undefined';
|
9
|
+
|
10
|
+
/**
|
11
|
+
* 组件持久化
|
12
|
+
*/
|
13
|
+
export const memorize = (Component) => {
|
14
|
+
const MemoizedComponent = (props) => {
|
15
|
+
return <Component {...props} />;
|
16
|
+
};
|
17
|
+
return memo(MemoizedComponent);
|
18
|
+
};
|
19
|
+
|
20
|
+
/**
|
21
|
+
* 加载外部资源
|
22
|
+
* @param url 地址 例如 https://xx.com/xx.js
|
23
|
+
* @param type js 或 css
|
24
|
+
* @returns {Promise<unknown>}
|
25
|
+
*/
|
26
|
+
export function loadExternalResource(url, type) {
|
27
|
+
// 检查是否已存在
|
28
|
+
const elements =
|
29
|
+
type === 'js'
|
30
|
+
? document.querySelectorAll(`[src='${url}']`)
|
31
|
+
: document.querySelectorAll(`[href='${url}']`);
|
32
|
+
|
33
|
+
return new Promise((resolve, reject) => {
|
34
|
+
if (elements.length > 0 || !url) {
|
35
|
+
resolve(url);
|
36
|
+
return url;
|
37
|
+
}
|
38
|
+
|
39
|
+
let tag;
|
40
|
+
|
41
|
+
if (type === 'css') {
|
42
|
+
tag = document.createElement('link');
|
43
|
+
tag.rel = 'stylesheet';
|
44
|
+
tag.href = url;
|
45
|
+
} else if (type === 'font') {
|
46
|
+
tag = document.createElement('link');
|
47
|
+
tag.rel = 'preload';
|
48
|
+
tag.as = 'font';
|
49
|
+
tag.href = url;
|
50
|
+
} else if (type === 'js') {
|
51
|
+
tag = document.createElement('script');
|
52
|
+
tag.src = url;
|
53
|
+
}
|
54
|
+
if (tag) {
|
55
|
+
tag.onload = () => {
|
56
|
+
console.log('Load Success', url);
|
57
|
+
resolve(url);
|
58
|
+
};
|
59
|
+
tag.onerror = () => {
|
60
|
+
console.log('Load Error', url);
|
61
|
+
reject(url);
|
62
|
+
};
|
63
|
+
document.head.appendChild(tag);
|
64
|
+
}
|
65
|
+
});
|
66
|
+
}
|
67
|
+
|
68
|
+
/**
|
69
|
+
* 查询url中的query参数
|
70
|
+
* @param {}} variable
|
71
|
+
* @returns
|
72
|
+
*/
|
73
|
+
export function getQueryVariable(key) {
|
74
|
+
const query = isBrowser ? window.location.search.substring(1) : '';
|
75
|
+
const vars = query.split('&');
|
76
|
+
for (let i = 0; i < vars.length; i++) {
|
77
|
+
const pair = vars[i].split('=');
|
78
|
+
if (pair[0] === key) {
|
79
|
+
return pair[1];
|
80
|
+
}
|
81
|
+
}
|
82
|
+
return false;
|
83
|
+
}
|
84
|
+
|
85
|
+
/**
|
86
|
+
* 获取 URL 中指定参数的值
|
87
|
+
* @param {string} url
|
88
|
+
* @param {string} param
|
89
|
+
* @returns {string|null}
|
90
|
+
*/
|
91
|
+
export function getQueryParam(url, param) {
|
92
|
+
const searchParams = new URLSearchParams(url.split('?')[1]);
|
93
|
+
return searchParams.get(param);
|
94
|
+
}
|
95
|
+
|
96
|
+
/**
|
97
|
+
* 深度合并两个对象
|
98
|
+
* @param target
|
99
|
+
* @param sources
|
100
|
+
*/
|
101
|
+
export function mergeDeep(target, ...sources) {
|
102
|
+
if (!sources.length) return target;
|
103
|
+
const source = sources.shift();
|
104
|
+
|
105
|
+
if (isObject(target) && isObject(source)) {
|
106
|
+
for (const key in source) {
|
107
|
+
if (isObject(source[key])) {
|
108
|
+
if (!target[key]) Object.assign(target, { [key]: {} });
|
109
|
+
mergeDeep(target[key], source[key]);
|
110
|
+
} else {
|
111
|
+
Object.assign(target, { [key]: source[key] });
|
112
|
+
}
|
113
|
+
}
|
114
|
+
}
|
115
|
+
return mergeDeep(target, ...sources);
|
116
|
+
}
|
117
|
+
|
118
|
+
/**
|
119
|
+
* 是否对象
|
120
|
+
* @param item
|
121
|
+
* @returns {boolean}
|
122
|
+
*/
|
123
|
+
export function isObject(item) {
|
124
|
+
return item && typeof item === 'object' && !Array.isArray(item);
|
125
|
+
}
|
126
|
+
|
127
|
+
/**
|
128
|
+
* 是否可迭代
|
129
|
+
* @param {*} obj
|
130
|
+
* @returns
|
131
|
+
*/
|
132
|
+
export function isIterable(obj) {
|
133
|
+
return obj != null && typeof obj[Symbol.iterator] === 'function';
|
134
|
+
}
|
135
|
+
|
136
|
+
/**
|
137
|
+
* 深拷贝对象
|
138
|
+
* 根据源对象类型深度复制,支持object和array
|
139
|
+
* @param {*} obj
|
140
|
+
* @returns
|
141
|
+
*/
|
142
|
+
export function deepClone(obj) {
|
143
|
+
if (Array.isArray(obj)) {
|
144
|
+
// If obj is an array, create a new array and deep clone each element
|
145
|
+
return obj.map((item) => deepClone(item));
|
146
|
+
} else if (obj && typeof obj === 'object') {
|
147
|
+
const newObj = {};
|
148
|
+
for (const key in obj) {
|
149
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
150
|
+
if (obj[key] instanceof Date) {
|
151
|
+
newObj[key] = new Date(obj[key].getTime()).toISOString();
|
152
|
+
} else {
|
153
|
+
newObj[key] = deepClone(obj[key]);
|
154
|
+
}
|
155
|
+
}
|
156
|
+
}
|
157
|
+
return newObj;
|
158
|
+
} else {
|
159
|
+
return obj;
|
160
|
+
}
|
161
|
+
}
|
162
|
+
/**
|
163
|
+
* 延时
|
164
|
+
* @param {*} ms
|
165
|
+
* @returns
|
166
|
+
*/
|
167
|
+
export const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
168
|
+
|
169
|
+
/**
|
170
|
+
* 获取从第1页到指定页码的文章
|
171
|
+
* @param pageIndex 第几页
|
172
|
+
* @param list 所有文章
|
173
|
+
* @param pageSize 每页文章数量
|
174
|
+
* @returns {*}
|
175
|
+
*/
|
176
|
+
export const getListByPage = function (list, pageIndex, pageSize) {
|
177
|
+
return list.slice(0, pageIndex * pageSize);
|
178
|
+
};
|
179
|
+
|
180
|
+
/**
|
181
|
+
* 判断是否移动设备
|
182
|
+
*/
|
183
|
+
export const isMobile = () => {
|
184
|
+
let isMobile = false;
|
185
|
+
if (!isBrowser) {
|
186
|
+
return isMobile;
|
187
|
+
}
|
188
|
+
|
189
|
+
// 这个判断会引发 TypeError: navigator.userAgentData.mobile is undefined 问题,导致博客无法正常工作
|
190
|
+
// if (!isMobile && navigator.userAgentData.mobile) {
|
191
|
+
// isMobile = true
|
192
|
+
// }
|
193
|
+
|
194
|
+
if (!isMobile && /Mobi|Android|iPhone/i.test(navigator.userAgent)) {
|
195
|
+
isMobile = true;
|
196
|
+
}
|
197
|
+
|
198
|
+
if (/Android|iPhone|iPad|iPod/i.test(navigator.platform)) {
|
199
|
+
isMobile = true;
|
200
|
+
}
|
201
|
+
|
202
|
+
if (typeof window.orientation !== 'undefined') {
|
203
|
+
isMobile = true;
|
204
|
+
}
|
205
|
+
|
206
|
+
return isMobile;
|
207
|
+
};
|
208
|
+
|
209
|
+
export function isUrl(website) {
|
210
|
+
const urlPattern = /^(https?|ftp):\/\/[^\s/$.?#].[^\s]*$/i;
|
211
|
+
return urlPattern.test(website);
|
212
|
+
}
|
package/next-env.d.ts
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
const BLOG = require('./blog.config')
|
2
|
+
|
3
|
+
module.exports = {
|
4
|
+
siteUrl: BLOG.LINK,
|
5
|
+
changefreq: 'daily',
|
6
|
+
priority: 0.7,
|
7
|
+
generateRobotsTxt: true,
|
8
|
+
sitemapSize: 7000
|
9
|
+
// ...other options
|
10
|
+
// https://github.com/iamvishnusankar/next-sitemap#configuration-options
|
11
|
+
}
|
package/next.config.js
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
const fs = require('fs');
|
2
|
+
const path = require('path');
|
3
|
+
const { i18n } = require('./next-i18next.config');
|
4
|
+
const { THEME } = require('./blog.config');
|
5
|
+
const withBundleAnalyzer = require('@next/bundle-analyzer')({
|
6
|
+
enabled: process.env.ANALYZE === 'true',
|
7
|
+
});
|
8
|
+
|
9
|
+
/**
|
10
|
+
* 扫描指定目录下的文件夹名,用于获取当前有几个主题
|
11
|
+
* @param {*} directory
|
12
|
+
* @returns
|
13
|
+
*/
|
14
|
+
function scanSubdirectories(directory) {
|
15
|
+
const subdirectories = [];
|
16
|
+
|
17
|
+
fs.readdirSync(directory).forEach((file) => {
|
18
|
+
const fullPath = path.join(directory, file);
|
19
|
+
const stats = fs.statSync(fullPath);
|
20
|
+
|
21
|
+
// landing主题比较特殊,不在可切换的主题中显示
|
22
|
+
if (stats.isDirectory() && file !== 'landing') {
|
23
|
+
subdirectories.push(file);
|
24
|
+
}
|
25
|
+
});
|
26
|
+
|
27
|
+
return subdirectories;
|
28
|
+
}
|
29
|
+
// 扫描项目 /themes下的目录名
|
30
|
+
const themes = scanSubdirectories(path.resolve(__dirname, 'themes'));
|
31
|
+
|
32
|
+
/** @type {import('next').NextConfig} */
|
33
|
+
const nextConfig = withBundleAnalyzer({
|
34
|
+
images: {
|
35
|
+
// 图片压缩
|
36
|
+
formats: ['image/avif', 'image/webp'],
|
37
|
+
// 允许next/image加载的图片 域名
|
38
|
+
domains: [
|
39
|
+
'gravatar.com',
|
40
|
+
'www.notion.so',
|
41
|
+
'avatars.githubusercontent.com',
|
42
|
+
'images.unsplash.com',
|
43
|
+
'source.unsplash.com',
|
44
|
+
'p1.qhimg.com',
|
45
|
+
'webmention.io',
|
46
|
+
],
|
47
|
+
},
|
48
|
+
// 默认将feed重定向至 /public/rss/feed.xml
|
49
|
+
async redirects() {
|
50
|
+
return [
|
51
|
+
{
|
52
|
+
source: '/feed',
|
53
|
+
destination: '/rss/feed.xml',
|
54
|
+
permanent: true,
|
55
|
+
},
|
56
|
+
];
|
57
|
+
},
|
58
|
+
async rewrites() {
|
59
|
+
return [
|
60
|
+
{
|
61
|
+
source: '/:path*.html',
|
62
|
+
destination: '/:path*',
|
63
|
+
},
|
64
|
+
];
|
65
|
+
},
|
66
|
+
async headers() {
|
67
|
+
return [
|
68
|
+
{
|
69
|
+
source: '/:path*{/}?',
|
70
|
+
headers: [
|
71
|
+
{ key: 'Access-Control-Allow-Credentials', value: 'true' },
|
72
|
+
{ key: 'Access-Control-Allow-Origin', value: '*' },
|
73
|
+
{
|
74
|
+
key: 'Access-Control-Allow-Methods',
|
75
|
+
value: 'GET,OPTIONS,PATCH,DELETE,POST,PUT',
|
76
|
+
},
|
77
|
+
{
|
78
|
+
key: 'Access-Control-Allow-Headers',
|
79
|
+
value:
|
80
|
+
'X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version',
|
81
|
+
},
|
82
|
+
],
|
83
|
+
},
|
84
|
+
];
|
85
|
+
},
|
86
|
+
webpack: (config, { dev, isServer }) => {
|
87
|
+
// Replace React with Preact only in client production build
|
88
|
+
// if (!dev && !isServer) {
|
89
|
+
// Object.assign(config.resolve.alias, {
|
90
|
+
// react: 'preact/compat',
|
91
|
+
// 'react-dom/test-utils': 'preact/test-utils',
|
92
|
+
// 'react-dom': 'preact/compat'
|
93
|
+
// })
|
94
|
+
// }
|
95
|
+
|
96
|
+
// 动态主题:添加 resolve.alias 配置,将动态路径映射到实际路径
|
97
|
+
config.resolve.alias['@theme-components'] = path.resolve(
|
98
|
+
__dirname,
|
99
|
+
'themes',
|
100
|
+
THEME,
|
101
|
+
);
|
102
|
+
return config;
|
103
|
+
},
|
104
|
+
experimental: {
|
105
|
+
scrollRestoration: true,
|
106
|
+
},
|
107
|
+
exportPathMap: async function (
|
108
|
+
defaultPathMap,
|
109
|
+
{ dev, dir, outDir, distDir, buildId },
|
110
|
+
) {
|
111
|
+
// 导出时 忽略/pages/sitemap.xml.js , 否则报错getServerSideProps
|
112
|
+
const pages = { ...defaultPathMap };
|
113
|
+
delete pages['/sitemap.xml'];
|
114
|
+
return pages;
|
115
|
+
},
|
116
|
+
publicRuntimeConfig: {
|
117
|
+
// 这里的配置既可以服务端获取到,也可以在浏览器端获取到
|
118
|
+
NODE_ENV_API: process.env.NODE_ENV_API || 'prod',
|
119
|
+
THEMES: themes,
|
120
|
+
},
|
121
|
+
i18n,
|
122
|
+
});
|
123
|
+
|
124
|
+
module.exports = nextConfig;
|
package/package.json
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
{
|
2
|
+
"name": "nnbb",
|
3
|
+
"version": "0.0.1",
|
4
|
+
"homepage": "https://github.com/czgaotian/NotionNextBlogBase.git",
|
5
|
+
"license": "MIT",
|
6
|
+
"repository": {
|
7
|
+
"type": "git",
|
8
|
+
"url": "https://github.com/czgaotian/NotionNextBlogBase.git"
|
9
|
+
},
|
10
|
+
"author": {
|
11
|
+
"name": "czgaotian",
|
12
|
+
"email": "me@04.lu",
|
13
|
+
"url": "http://04.lu"
|
14
|
+
},
|
15
|
+
"scripts": {
|
16
|
+
"dev": "NODE_OPTIONS='--inspect' next dev",
|
17
|
+
"build": "next build",
|
18
|
+
"start": "next start",
|
19
|
+
"post-build": "next-sitemap --config next-sitemap.config.js",
|
20
|
+
"export": "next build && next-sitemap --config next-sitemap.config.js && next export",
|
21
|
+
"bundle-report": "ANALYZE=true pnpm build",
|
22
|
+
"lint": "next lint"
|
23
|
+
},
|
24
|
+
"dependencies": {
|
25
|
+
"@giscus/react": "^2.4.0",
|
26
|
+
"@headlessui/react": "^1.7.18",
|
27
|
+
"@next/bundle-analyzer": "^12.3.4",
|
28
|
+
"@vercel/analytics": "^1.2.2",
|
29
|
+
"add": "^2.0.6",
|
30
|
+
"algoliasearch": "^4.23.2",
|
31
|
+
"animejs": "^3.2.2",
|
32
|
+
"aos": "3.0.0-beta.6",
|
33
|
+
"axios": "^1.7.7",
|
34
|
+
"canvas": "^2.11.2",
|
35
|
+
"copy-to-clipboard": "^3.3.3",
|
36
|
+
"dayjs": "^1.11.12",
|
37
|
+
"feed": "^4.2.2",
|
38
|
+
"i18next": "^23.12.2",
|
39
|
+
"js-md5": "^0.7.3",
|
40
|
+
"lodash": "^4.17.21",
|
41
|
+
"medium-zoom": "^1.1.0",
|
42
|
+
"memory-cache": "^0.2.0",
|
43
|
+
"mongodb": "^4.17.2",
|
44
|
+
"next": "14.1.4",
|
45
|
+
"next-i18next": "^15.3.0",
|
46
|
+
"notion-client": "6.16.0",
|
47
|
+
"notion-utils": "6.16.0",
|
48
|
+
"nprogress": "^0.2.0",
|
49
|
+
"preact": "^10.20.1",
|
50
|
+
"prism-themes": "1.9.0",
|
51
|
+
"react": "^18.2.0",
|
52
|
+
"react-dom": "^18.2.0",
|
53
|
+
"react-facebook": "^8.1.4",
|
54
|
+
"react-i18next": "^15.0.0",
|
55
|
+
"react-notion-x": "6.16.0",
|
56
|
+
"react-share": "^4.4.1",
|
57
|
+
"react-tweet-embed": "~2.0.0",
|
58
|
+
"store": "^2.0.12",
|
59
|
+
"typed.js": "^2.1.0",
|
60
|
+
"zustand": "^5.0.0"
|
61
|
+
},
|
62
|
+
"devDependencies": {
|
63
|
+
"@eslint/js": "^9.3.0",
|
64
|
+
"@types/js-md5": "^0.7.2",
|
65
|
+
"@types/lodash": "^4.17.7",
|
66
|
+
"@types/memory-cache": "^0.2.6",
|
67
|
+
"@types/node": "^20.12.12",
|
68
|
+
"@types/nprogress": "^0.2.3",
|
69
|
+
"@types/react": "18.2.75",
|
70
|
+
"@types/store": "^2.0.5",
|
71
|
+
"@waline/client": "^2.15.8",
|
72
|
+
"autoprefixer": "^10.4.19",
|
73
|
+
"eslint": "^8.57.0",
|
74
|
+
"eslint-config-next": "^14.2.3",
|
75
|
+
"eslint-config-prettier": "^9.1.0",
|
76
|
+
"next-sitemap": "^1.9.12",
|
77
|
+
"notion-types": "^6.16.0",
|
78
|
+
"postcss": "^8.4.38",
|
79
|
+
"prettier": "^3.2.5",
|
80
|
+
"prettier-plugin-tailwindcss": "^0.5.13",
|
81
|
+
"tailwindcss": "^3.4.3",
|
82
|
+
"typescript": "^5.4.4",
|
83
|
+
"typescript-eslint": "^7.10.0",
|
84
|
+
"webpack-bundle-analyzer": "^4.10.1"
|
85
|
+
},
|
86
|
+
"resolutions": {
|
87
|
+
"axios": ">=0.21.1"
|
88
|
+
},
|
89
|
+
"bugs": {
|
90
|
+
"url": "https://github.com/meannoharm/NotionNextBlogBase/issues"
|
91
|
+
}
|
92
|
+
}
|
package/pages/404.tsx
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
import { getGlobalData } from '@/lib/notion/getNotionData';
|
2
|
+
import { useLayout } from '@/theme';
|
3
|
+
|
4
|
+
import type { FC } from 'react';
|
5
|
+
import type {
|
6
|
+
PageNotFoundIndexProps,
|
7
|
+
PageMeta,
|
8
|
+
ThemePageNotFoundProps,
|
9
|
+
} from '@/types';
|
10
|
+
import type { GetStaticProps } from 'next';
|
11
|
+
|
12
|
+
/**
|
13
|
+
* 404
|
14
|
+
* @param {*} props
|
15
|
+
* @returns
|
16
|
+
*/
|
17
|
+
const NoFound: FC<PageNotFoundIndexProps> = (props) => {
|
18
|
+
const { siteInfo } = props;
|
19
|
+
const pageMeta: PageMeta = {
|
20
|
+
title: `${props?.siteInfo?.title} | 页面找不到啦`,
|
21
|
+
description: '404 page not found',
|
22
|
+
slug: '',
|
23
|
+
type: 'website',
|
24
|
+
image: siteInfo?.pageCover,
|
25
|
+
};
|
26
|
+
|
27
|
+
// 根据页面路径加载不同Layout文件
|
28
|
+
const Layout = useLayout() as FC<ThemePageNotFoundProps>;
|
29
|
+
|
30
|
+
return <Layout pageMeta={pageMeta} {...props} />;
|
31
|
+
};
|
32
|
+
|
33
|
+
export const getStaticProps: GetStaticProps<
|
34
|
+
PageNotFoundIndexProps
|
35
|
+
> = async () => {
|
36
|
+
const props = (await getGlobalData('404')) || {};
|
37
|
+
return { props };
|
38
|
+
};
|
39
|
+
|
40
|
+
export default NoFound;
|