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
@@ -0,0 +1,173 @@
|
|
1
|
+
import BLOG from '@/blog.config'
|
2
|
+
import { useEffect, useState } from 'react'
|
3
|
+
import { useRouter } from 'next/router'
|
4
|
+
import Image from 'next/image'
|
5
|
+
|
6
|
+
/**
|
7
|
+
* 评论插件
|
8
|
+
* @param issueTerm
|
9
|
+
* @param layout
|
10
|
+
* @returns {JSX.Element}
|
11
|
+
* @constructor
|
12
|
+
*/
|
13
|
+
const WebmentionCount = ({ target }) => {
|
14
|
+
const initialCounts = {
|
15
|
+
count: 0,
|
16
|
+
type: {
|
17
|
+
like: 0,
|
18
|
+
mention: 0,
|
19
|
+
reply: 0,
|
20
|
+
repost: 0
|
21
|
+
}
|
22
|
+
}
|
23
|
+
const [counts, setCounts] = useState(initialCounts)
|
24
|
+
const fetchCounts = async (target) => {
|
25
|
+
const responseData = await fetch(`https://webmention.io/api/count.json?target=${encodeURIComponent(target)}`)
|
26
|
+
return (responseData.json) ? await responseData.json() : responseData
|
27
|
+
}
|
28
|
+
useEffect(() => {
|
29
|
+
async function getCounts() {
|
30
|
+
const responseCounts = await fetchCounts(target)
|
31
|
+
setCounts(responseCounts)
|
32
|
+
}
|
33
|
+
getCounts()
|
34
|
+
}, [target])
|
35
|
+
|
36
|
+
return (
|
37
|
+
<div className='webmention-counts'>
|
38
|
+
{counts
|
39
|
+
? (
|
40
|
+
<div className='counts'>
|
41
|
+
<span>
|
42
|
+
<span className='count'>{counts.type.like || 0}</span>Likes
|
43
|
+
</span>
|
44
|
+
<span>
|
45
|
+
<span className='count'>{counts.type.reply || 0}</span>Replies
|
46
|
+
</span>
|
47
|
+
<span>
|
48
|
+
<span className='count'>
|
49
|
+
{(counts.type.repost || 0) + (counts.type.mention || 0)}
|
50
|
+
</span>
|
51
|
+
Mentions
|
52
|
+
</span>
|
53
|
+
</div>
|
54
|
+
)
|
55
|
+
: (
|
56
|
+
<p>Failed to fetch Webmention counts</p>
|
57
|
+
)
|
58
|
+
}
|
59
|
+
</div>
|
60
|
+
)
|
61
|
+
}
|
62
|
+
|
63
|
+
const Avatar = ({ author }) => (
|
64
|
+
<a className='avatar-wrapper' href={author.url} key={author.name}>
|
65
|
+
<Image
|
66
|
+
className="avatar"
|
67
|
+
src={author.photo}
|
68
|
+
alt={author.name}
|
69
|
+
fill
|
70
|
+
sizes="(max-width: 768px) 100vw,
|
71
|
+
(max-width: 1200px) 50vw,
|
72
|
+
33vw"
|
73
|
+
/>
|
74
|
+
</a>
|
75
|
+
)
|
76
|
+
|
77
|
+
const WebmentionReplies = ({ target }) => {
|
78
|
+
const [mentions, setMentions] = useState([])
|
79
|
+
const fetchMentions = async (target) =>
|
80
|
+
fetch(
|
81
|
+
`https://webmention.io/api/mentions.jf2?per-page=500&target=${encodeURIComponent(target)}&token=${BLOG.COMMENT_WEBMENTION.TOKEN}`
|
82
|
+
).then((response) => (response.json ? response.json() : response))
|
83
|
+
useEffect(() => {
|
84
|
+
async function getMentions() {
|
85
|
+
const responseMentions = await fetchMentions(target)
|
86
|
+
if (responseMentions.children) {
|
87
|
+
setMentions(responseMentions.children)
|
88
|
+
}
|
89
|
+
}
|
90
|
+
|
91
|
+
getMentions()
|
92
|
+
}, [target])
|
93
|
+
|
94
|
+
const distinctMentions = [
|
95
|
+
...new Map(mentions.map((item) => [item.author.url, item])).values()
|
96
|
+
].sort((a, b) => new Date(a['wm-received']) - new Date(b['wm-received']))
|
97
|
+
|
98
|
+
const replies = mentions.filter(
|
99
|
+
(mention) => 'in-reply-to' in mention && 'content' in mention
|
100
|
+
)
|
101
|
+
|
102
|
+
return (
|
103
|
+
<div>
|
104
|
+
<p>
|
105
|
+
{distinctMentions.length > 0
|
106
|
+
? `Already ${distinctMentions.length} people liked, shared or talked about this article:`
|
107
|
+
: 'Be the first one to share this article!'}
|
108
|
+
</p>
|
109
|
+
<div className='webmention-avatars'>
|
110
|
+
{distinctMentions.map((reply) => (
|
111
|
+
<Avatar key={reply.author.name} author={reply.author} />
|
112
|
+
))}
|
113
|
+
</div>
|
114
|
+
{replies && replies.length
|
115
|
+
? (
|
116
|
+
<div className='webmention-replies'>
|
117
|
+
<h4>Replies</h4>
|
118
|
+
<ul className='replies'>
|
119
|
+
{replies.map((reply) => (
|
120
|
+
<li className='reply' key={reply.content.text}>
|
121
|
+
<div>
|
122
|
+
<Avatar key={reply.author.name} author={reply.author} />
|
123
|
+
</div>
|
124
|
+
<div className='text'>
|
125
|
+
<p className='reply-author-name'>{reply.author.name}</p>
|
126
|
+
<p className='reply-content'>{reply.content.text}</p>
|
127
|
+
</div>
|
128
|
+
</li>
|
129
|
+
))}
|
130
|
+
</ul>
|
131
|
+
</div>
|
132
|
+
)
|
133
|
+
: null}
|
134
|
+
</div>
|
135
|
+
)
|
136
|
+
}
|
137
|
+
|
138
|
+
const WebMentionBlock = ({ frontMatter }) => {
|
139
|
+
const router = useRouter()
|
140
|
+
const url = `https://${BLOG.COMMENT_WEBMENTION.HOSTNAME}${router.asPath}`
|
141
|
+
const tweet = `${frontMatter.title} by @${BLOG.COMMENT_WEBMENTION.TWITTER_USERNAME} ${url}`
|
142
|
+
|
143
|
+
return (
|
144
|
+
<div className='webmention-block'>
|
145
|
+
<h1 className='webmention-header'>
|
146
|
+
powered by <a href="https://webmention.io" target='_blank' rel='noreferrer'>WebMention.io</a>
|
147
|
+
</h1>
|
148
|
+
<div className='webmention-block-intro'>
|
149
|
+
You can{' '}
|
150
|
+
<a
|
151
|
+
target="_blank"
|
152
|
+
id='tweet-post-url'
|
153
|
+
href={`https://twitter.com/intent/tweet?text=${encodeURIComponent(tweet)}`}
|
154
|
+
rel="noopener noreferrer"
|
155
|
+
>tweet this post</a>{' '}
|
156
|
+
or{' '}
|
157
|
+
<a
|
158
|
+
target='_blank'
|
159
|
+
id='tweet-discuss-url'
|
160
|
+
href={`https://www.twitter.com/search?q=${url}`}
|
161
|
+
rel='noopener noreferrer'
|
162
|
+
>discuss it on Twitter</a>
|
163
|
+
, the comments will show up here.
|
164
|
+
</div>
|
165
|
+
<div className='webmention-info'>
|
166
|
+
<WebmentionCount target={url} />
|
167
|
+
<WebmentionReplies target={url} />
|
168
|
+
</div>
|
169
|
+
</div>
|
170
|
+
)
|
171
|
+
}
|
172
|
+
|
173
|
+
export default WebMentionBlock
|
@@ -0,0 +1,17 @@
|
|
1
|
+
import BLOG from '@/blog.config'
|
2
|
+
import ExternalScript from './ExternalScript'
|
3
|
+
|
4
|
+
/**
|
5
|
+
* 一个开源ai组件
|
6
|
+
* @see https://github.com/webwhiz-ai/webwhiz
|
7
|
+
* @returns
|
8
|
+
*/
|
9
|
+
export default function WebWhiz() {
|
10
|
+
const props = {
|
11
|
+
id: '__webwhizSdk__',
|
12
|
+
src: 'https://www.unpkg.com/webwhiz@1.0.0/dist/sdk.js',
|
13
|
+
baseUrl: BLOG.WEB_WHIZ_BASE_URL,
|
14
|
+
chatbotId: BLOG.WEB_WHIZ_CHAT_BOT_ID
|
15
|
+
}
|
16
|
+
return <ExternalScript {...props}/>
|
17
|
+
}
|
@@ -0,0 +1,73 @@
|
|
1
|
+
import { useEffect } from 'react';
|
2
|
+
import { useTranslation } from 'next-i18next';
|
3
|
+
|
4
|
+
/**
|
5
|
+
* 字数统计
|
6
|
+
* @returns
|
7
|
+
*/
|
8
|
+
export default function WordCount() {
|
9
|
+
const { t } = useTranslation('common');
|
10
|
+
|
11
|
+
useEffect(() => {
|
12
|
+
countWords();
|
13
|
+
});
|
14
|
+
|
15
|
+
return (
|
16
|
+
<span id="wordCountWrapper" className="flex gap-3 font-light">
|
17
|
+
<span className="flex items-center whitespace-nowrap">
|
18
|
+
<i className="fas fa-file-word pl-1 pr-2" />
|
19
|
+
<span id="wordCount">0</span>
|
20
|
+
</span>
|
21
|
+
<span className="flex items-center whitespace-nowrap">
|
22
|
+
<i className="fas fa-clock mr-1" />
|
23
|
+
<span></span>
|
24
|
+
<span id="readTime">0</span> {t('minute')}
|
25
|
+
</span>
|
26
|
+
</span>
|
27
|
+
);
|
28
|
+
}
|
29
|
+
|
30
|
+
/**
|
31
|
+
* 更新字数统计和阅读时间
|
32
|
+
*/
|
33
|
+
function countWords() {
|
34
|
+
const articleText = deleteHtmlTag(
|
35
|
+
document.getElementById('notion-article')?.innerHTML,
|
36
|
+
);
|
37
|
+
const wordCount = fnGetCpmisWords(articleText);
|
38
|
+
// 阅读速度 300-500每分钟
|
39
|
+
document.getElementById('wordCount').innerHTML = wordCount;
|
40
|
+
document.getElementById('readTime').innerHTML =
|
41
|
+
Math.floor(wordCount / 400) + 1;
|
42
|
+
const wordCountWrapper = document.getElementById('wordCountWrapper');
|
43
|
+
wordCountWrapper.classList.remove('hidden');
|
44
|
+
}
|
45
|
+
|
46
|
+
// 去除html标签
|
47
|
+
function deleteHtmlTag(str) {
|
48
|
+
if (!str) {
|
49
|
+
return '';
|
50
|
+
}
|
51
|
+
str = str.replace(/<[^>]+>|&[^>]+;/g, '').trim(); // 去掉所有的html标签和 之类的特殊符合
|
52
|
+
return str;
|
53
|
+
}
|
54
|
+
|
55
|
+
// 用word方式计算正文字数
|
56
|
+
function fnGetCpmisWords(str) {
|
57
|
+
if (!str) {
|
58
|
+
return 0;
|
59
|
+
}
|
60
|
+
let sLen = 0;
|
61
|
+
try {
|
62
|
+
// eslint-disable-next-line no-irregular-whitespace
|
63
|
+
str = str.replace(/(\r\n+|\s+| +)/g, '龘');
|
64
|
+
// eslint-disable-next-line no-control-regex
|
65
|
+
str = str.replace(/[\x00-\xff]/g, 'm');
|
66
|
+
str = str.replace(/m+/g, '*');
|
67
|
+
str = str.replace(/龘+/g, '');
|
68
|
+
sLen = str.length;
|
69
|
+
} catch (e) {
|
70
|
+
console.log(e);
|
71
|
+
}
|
72
|
+
return sLen;
|
73
|
+
}
|
@@ -0,0 +1,32 @@
|
|
1
|
+
import { useEffect } from 'react';
|
2
|
+
import type { RefObject } from 'react';
|
3
|
+
|
4
|
+
export default function useToggleClickOutSide(
|
5
|
+
targetRef: RefObject<HTMLElement> | RefObject<HTMLElement>[],
|
6
|
+
callback: (...arg: any) => any,
|
7
|
+
) {
|
8
|
+
useEffect(() => {
|
9
|
+
let targetRefs: RefObject<HTMLElement>[] = [];
|
10
|
+
if (!Array.isArray(targetRef)) {
|
11
|
+
targetRefs = [targetRef];
|
12
|
+
} else {
|
13
|
+
targetRefs = targetRef;
|
14
|
+
}
|
15
|
+
const handleClickOutside = (event: MouseEvent) => {
|
16
|
+
let isOutside = true;
|
17
|
+
targetRefs.forEach((ref) => {
|
18
|
+
if (ref.current && ref.current.contains(event.target as Node)) {
|
19
|
+
isOutside = false;
|
20
|
+
}
|
21
|
+
});
|
22
|
+
if (isOutside) {
|
23
|
+
callback();
|
24
|
+
}
|
25
|
+
};
|
26
|
+
|
27
|
+
document.addEventListener('mousedown', handleClickOutside);
|
28
|
+
return () => {
|
29
|
+
document.removeEventListener('mousedown', handleClickOutside);
|
30
|
+
};
|
31
|
+
}, [callback, targetRef]);
|
32
|
+
}
|
@@ -0,0 +1,30 @@
|
|
1
|
+
import { useEffect, useState } from 'react'
|
2
|
+
|
3
|
+
interface WindowSize {
|
4
|
+
width: number,
|
5
|
+
height: number
|
6
|
+
}
|
7
|
+
|
8
|
+
const useWindowSize = () => {
|
9
|
+
const [size, setSize] = useState<WindowSize>({
|
10
|
+
width: document.documentElement.clientWidth,
|
11
|
+
height: document.documentElement.clientHeight
|
12
|
+
})
|
13
|
+
|
14
|
+
useEffect(() => {
|
15
|
+
const onResize = () => {
|
16
|
+
setSize({
|
17
|
+
width: document.documentElement.clientWidth,
|
18
|
+
height: document.documentElement.clientHeight
|
19
|
+
})
|
20
|
+
}
|
21
|
+
onResize()
|
22
|
+
window.addEventListener('resize', onResize)
|
23
|
+
return () => {
|
24
|
+
window.removeEventListener('resize', onResize)
|
25
|
+
}
|
26
|
+
}, [])
|
27
|
+
return size
|
28
|
+
}
|
29
|
+
|
30
|
+
export default useWindowSize
|
package/lib/algolia.js
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
import BLOG from '@/blog.config';
|
2
|
+
import { getPageContentText } from '@/pages/search/[keyword]';
|
3
|
+
import algoliasearch from 'algoliasearch';
|
4
|
+
|
5
|
+
/**
|
6
|
+
* 生成全文索引
|
7
|
+
* @param {*} allPages
|
8
|
+
*/
|
9
|
+
const generateAlgoliaSearch = async ({ allPages }) => {
|
10
|
+
allPages?.forEach((p) => {
|
11
|
+
// 判断这篇文章是否需要重新创建索引
|
12
|
+
if (p && !p.password) {
|
13
|
+
uploadDataToAlgolia(p);
|
14
|
+
}
|
15
|
+
});
|
16
|
+
};
|
17
|
+
|
18
|
+
/**
|
19
|
+
* 上传数据
|
20
|
+
* 根据上次修改文章日期和上次更新索引数据判断是否需要更新algolia索引
|
21
|
+
*/
|
22
|
+
const uploadDataToAlgolia = async (post) => {
|
23
|
+
// Connect and authenticate with your Algolia app
|
24
|
+
const client = algoliasearch(BLOG.ALGOLIA_APP_ID, BLOG.ALGOLIA_ADMIN_APP_KEY);
|
25
|
+
|
26
|
+
// Create a new index and add a record
|
27
|
+
const index = client.initIndex(BLOG.ALGOLIA_INDEX);
|
28
|
+
|
29
|
+
if (!post) {
|
30
|
+
return;
|
31
|
+
}
|
32
|
+
|
33
|
+
// 检查是否有索引
|
34
|
+
let existed;
|
35
|
+
let needUpdateIndex = false;
|
36
|
+
try {
|
37
|
+
existed = await index.getObject(post.id);
|
38
|
+
} catch (error) {
|
39
|
+
// 通常是不存在索引
|
40
|
+
}
|
41
|
+
|
42
|
+
if (!existed || !existed?.lastEditedDate || !existed?.lastIndexDate) {
|
43
|
+
needUpdateIndex = true;
|
44
|
+
} else {
|
45
|
+
const lastEditedDate = new Date(existed.lastEditedDate);
|
46
|
+
const lastIndexDate = new Date(existed.lastIndexDate);
|
47
|
+
if (lastEditedDate.getTime() > lastIndexDate.getTime()) {
|
48
|
+
needUpdateIndex = true;
|
49
|
+
}
|
50
|
+
}
|
51
|
+
|
52
|
+
// 如果需要更新搜索
|
53
|
+
if (needUpdateIndex) {
|
54
|
+
const record = {
|
55
|
+
objectID: post.id,
|
56
|
+
title: post.title,
|
57
|
+
category: post.category,
|
58
|
+
tags: post.tags,
|
59
|
+
pageCover: post.pageCover,
|
60
|
+
slug: post.slug,
|
61
|
+
summary: post.summary,
|
62
|
+
lastEditedDate: post.lastEditedDate, // 更新文章时间
|
63
|
+
lastIndexDate: new Date(), // 更新索引时间
|
64
|
+
content: truncate(getPageContentText(post, post.blockMap), 9000), // 索引9000个字节,因为api限制总请求内容上限1万个字节
|
65
|
+
};
|
66
|
+
// console.log('更新Algolia索引', record)
|
67
|
+
index
|
68
|
+
.saveObject(record)
|
69
|
+
.wait()
|
70
|
+
.then((r) => {
|
71
|
+
console.log('Algolia索引更新', r);
|
72
|
+
})
|
73
|
+
.catch((err) => {
|
74
|
+
console.log('Algolia异常', err);
|
75
|
+
});
|
76
|
+
}
|
77
|
+
};
|
78
|
+
|
79
|
+
/**
|
80
|
+
* 限制内容字节数
|
81
|
+
* @param {*} str
|
82
|
+
* @param {*} maxBytes
|
83
|
+
* @returns
|
84
|
+
*/
|
85
|
+
function truncate(str, maxBytes) {
|
86
|
+
let count = 0;
|
87
|
+
let result = '';
|
88
|
+
for (let i = 0; i < str.length; i++) {
|
89
|
+
const code = str.charCodeAt(i);
|
90
|
+
if (code <= 0x7f) {
|
91
|
+
count += 1;
|
92
|
+
} else if (code <= 0x7ff) {
|
93
|
+
count += 2;
|
94
|
+
} else if (code <= 0xffff) {
|
95
|
+
count += 3;
|
96
|
+
} else {
|
97
|
+
count += 4;
|
98
|
+
}
|
99
|
+
if (count <= maxBytes) {
|
100
|
+
result += str[i];
|
101
|
+
} else {
|
102
|
+
break;
|
103
|
+
}
|
104
|
+
}
|
105
|
+
return result;
|
106
|
+
}
|
107
|
+
|
108
|
+
export { uploadDataToAlgolia, generateAlgoliaSearch };
|
package/lib/busuanzi.js
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
/* eslint-disable */
|
2
|
+
let bszCaller, bszTag, scriptTag, ready
|
3
|
+
|
4
|
+
let t; let e; let n; let a = !1
|
5
|
+
let c = []
|
6
|
+
|
7
|
+
// 修复Node同构代码的问题
|
8
|
+
if (typeof document !== 'undefined') {
|
9
|
+
ready = function (t) {
|
10
|
+
return a || document.readyState === 'interactive' || document.readyState === 'complete'
|
11
|
+
? t.call(document)
|
12
|
+
: c.push(function () {
|
13
|
+
return t.call(this)
|
14
|
+
}), this
|
15
|
+
}, e = function () {
|
16
|
+
for (let t = 0, e = c.length; t < e; t++) c[t].apply(document)
|
17
|
+
c = []
|
18
|
+
}, n = function () {
|
19
|
+
a || (a = !0, e.call(window),
|
20
|
+
document.removeEventListener ? document.removeEventListener('DOMContentLoaded', n, !1) : document.attachEvent && (document.detachEvent('onreadystatechange', n), window == window.top && (clearInterval(t), t = null)))
|
21
|
+
}, document.addEventListener
|
22
|
+
? document.addEventListener('DOMContentLoaded', n, !1)
|
23
|
+
: document.attachEvent && (document.attachEvent('onreadystatechange', function () {
|
24
|
+
/loaded|complete/.test(document.readyState) && n()
|
25
|
+
}), window == window.top && (t = setInterval(function () {
|
26
|
+
try {
|
27
|
+
a || document.documentElement.doScroll('left')
|
28
|
+
} catch (t) {
|
29
|
+
return
|
30
|
+
}
|
31
|
+
n()
|
32
|
+
}, 5)))
|
33
|
+
}
|
34
|
+
|
35
|
+
bszCaller = {
|
36
|
+
fetch: function (t, e) {
|
37
|
+
const n = 'BusuanziCallback_' + Math.floor(1099511627776 * Math.random())
|
38
|
+
t = t.replace('=BusuanziCallback', '=' + n)
|
39
|
+
scriptTag = document.createElement('SCRIPT'), scriptTag.type = 'text/javascript', scriptTag.defer = !0, scriptTag.src = t, scriptTag.referrerPolicy = "no-referrer-when-downgrade", document.getElementsByTagName('HEAD')[0].appendChild(scriptTag)
|
40
|
+
window[n] = this.evalCall(e)
|
41
|
+
},
|
42
|
+
evalCall: function (e) {
|
43
|
+
return function (t) {
|
44
|
+
ready(function () {
|
45
|
+
try {
|
46
|
+
e(t), scriptTag && scriptTag.parentElement && scriptTag.parentElement.removeChild && scriptTag.parentElement.removeChild(scriptTag)
|
47
|
+
} catch (t) {
|
48
|
+
// console.log(t), bszTag.hides()
|
49
|
+
}
|
50
|
+
})
|
51
|
+
}
|
52
|
+
}
|
53
|
+
}
|
54
|
+
|
55
|
+
const fetch = () => {
|
56
|
+
bszTag && bszTag.hides()
|
57
|
+
bszCaller.fetch('//busuanzi.ibruce.info/busuanzi?jsonpCallback=BusuanziCallback', function (t) {
|
58
|
+
// console.log('不蒜子',t)
|
59
|
+
bszTag.texts(t), bszTag.shows()
|
60
|
+
})
|
61
|
+
}
|
62
|
+
|
63
|
+
bszTag = {
|
64
|
+
bszs: ['site_pv', 'page_pv', 'site_uv'],
|
65
|
+
texts: function (n) {
|
66
|
+
this.bszs.map(function (t) {
|
67
|
+
const e = document.getElementsByClassName('busuanzi_value_' + t)
|
68
|
+
if(e){
|
69
|
+
for (var element of e) {
|
70
|
+
element.innerHTML = n[t]
|
71
|
+
}
|
72
|
+
}
|
73
|
+
})
|
74
|
+
},
|
75
|
+
hides: function () {
|
76
|
+
this.bszs.map(function (t) {
|
77
|
+
const e = document.getElementsByClassName('busuanzi_container_' + t)
|
78
|
+
if(e){
|
79
|
+
for (var element of e){
|
80
|
+
element.style.display = 'none'
|
81
|
+
}
|
82
|
+
}
|
83
|
+
})
|
84
|
+
},
|
85
|
+
shows: function () {
|
86
|
+
this.bszs.map(function (t) {
|
87
|
+
const e = document.getElementsByClassName('busuanzi_container_' + t)
|
88
|
+
if(e){
|
89
|
+
for(var element of e){
|
90
|
+
element.style.display = 'inline'
|
91
|
+
}
|
92
|
+
}
|
93
|
+
})
|
94
|
+
}
|
95
|
+
}
|
96
|
+
|
97
|
+
module.exports = {
|
98
|
+
fetch
|
99
|
+
}
|
@@ -0,0 +1,49 @@
|
|
1
|
+
import memoryCache from './memoryMache';
|
2
|
+
import fileCache from './localFileCache';
|
3
|
+
import mongoCache from './mongoDbCache';
|
4
|
+
import BLOG from '@/blog.config';
|
5
|
+
|
6
|
+
import type { CacheManager } from './types';
|
7
|
+
|
8
|
+
let cacheManager: CacheManager;
|
9
|
+
if (process.env.MONGO_DB_URL && process.env.MONGO_DB_NAME) {
|
10
|
+
cacheManager = mongoCache;
|
11
|
+
} else if (process.env.ENABLE_FILE_CACHE) {
|
12
|
+
cacheManager = fileCache;
|
13
|
+
} else {
|
14
|
+
cacheManager = memoryCache;
|
15
|
+
}
|
16
|
+
|
17
|
+
/**
|
18
|
+
* 为减少频繁接口请求,notion数据将被缓存
|
19
|
+
* @param {*} key
|
20
|
+
* @returns
|
21
|
+
*/
|
22
|
+
export async function getDataFromCache<T>(
|
23
|
+
key: string,
|
24
|
+
force?: boolean,
|
25
|
+
): Promise<T | null> {
|
26
|
+
if (BLOG.ENABLE_CACHE || force) {
|
27
|
+
const dataFromCache = await cacheManager.getCache(key);
|
28
|
+
if (JSON.stringify(dataFromCache) === '[]') {
|
29
|
+
return null;
|
30
|
+
}
|
31
|
+
return cacheManager.getCache(key);
|
32
|
+
} else {
|
33
|
+
return null;
|
34
|
+
}
|
35
|
+
}
|
36
|
+
|
37
|
+
export async function setDataToCache<T>(key: string, data: T) {
|
38
|
+
if (!data) {
|
39
|
+
return;
|
40
|
+
}
|
41
|
+
await cacheManager.setCache(key, data);
|
42
|
+
}
|
43
|
+
|
44
|
+
export async function delCacheData(key: string) {
|
45
|
+
if (!BLOG.ENABLE_CACHE) {
|
46
|
+
return;
|
47
|
+
}
|
48
|
+
await cacheManager.delCache(key);
|
49
|
+
}
|
@@ -0,0 +1,56 @@
|
|
1
|
+
import fs from 'fs';
|
2
|
+
import path from 'path';
|
3
|
+
|
4
|
+
// 文件缓存持续10秒
|
5
|
+
const cacheInvalidSeconds = 1000000000 * 1000;
|
6
|
+
// 文件名
|
7
|
+
const jsonFile = path.resolve('./data.json');
|
8
|
+
|
9
|
+
export async function getCache(key: string) {
|
10
|
+
const exist = await fs.existsSync(jsonFile);
|
11
|
+
if (!exist) return null;
|
12
|
+
const data = await fs.readFileSync(jsonFile);
|
13
|
+
let json = null;
|
14
|
+
if (!data) return null;
|
15
|
+
try {
|
16
|
+
json = JSON.parse(data.toString());
|
17
|
+
} catch (error) {
|
18
|
+
console.error('读取JSON缓存文件失败', data);
|
19
|
+
return null;
|
20
|
+
}
|
21
|
+
// 缓存超过有效期就作废
|
22
|
+
const cacheValidTime = new Date(
|
23
|
+
parseInt(json[key + '_expire_time']) + cacheInvalidSeconds,
|
24
|
+
);
|
25
|
+
const currentTime = new Date();
|
26
|
+
if (!cacheValidTime || cacheValidTime < currentTime) {
|
27
|
+
return null;
|
28
|
+
}
|
29
|
+
return json[key];
|
30
|
+
}
|
31
|
+
|
32
|
+
/**
|
33
|
+
* 并发请求写文件异常; Vercel生产环境不支持写文件。
|
34
|
+
* @param key
|
35
|
+
* @param data
|
36
|
+
* @returns {Promise<null>}
|
37
|
+
*/
|
38
|
+
export function setCache(key: string, data: any) {
|
39
|
+
const exist = fs.existsSync(jsonFile);
|
40
|
+
const json = exist ? JSON.parse(fs.readFileSync(jsonFile).toString()) : {};
|
41
|
+
json[key] = data;
|
42
|
+
json[key + '_expire_time'] = new Date().getTime();
|
43
|
+
fs.writeFileSync(jsonFile, JSON.stringify(json));
|
44
|
+
}
|
45
|
+
|
46
|
+
export function delCache(key: string) {
|
47
|
+
const exist = fs.existsSync(jsonFile);
|
48
|
+
const json = exist ? JSON.parse(fs.readFileSync(jsonFile).toString()) : {};
|
49
|
+
delete json.key;
|
50
|
+
json[key + '_expire_time'] = new Date().getTime();
|
51
|
+
fs.writeFileSync(jsonFile, JSON.stringify(json));
|
52
|
+
}
|
53
|
+
|
54
|
+
const localFileCache = { getCache, setCache, delCache };
|
55
|
+
|
56
|
+
export default localFileCache;
|
@@ -0,0 +1,20 @@
|
|
1
|
+
import cache from 'memory-cache';
|
2
|
+
import BLOG from 'blog.config';
|
3
|
+
|
4
|
+
const cacheTime = BLOG.isProd ? 10 * 60 : 120 * 60; // 120 minutes for dev,10 minutes for prod
|
5
|
+
|
6
|
+
export async function getCache(key: string) {
|
7
|
+
return await cache.get(key);
|
8
|
+
}
|
9
|
+
|
10
|
+
export async function setCache(key: string, data: any) {
|
11
|
+
await cache.put(key, data, cacheTime * 1000);
|
12
|
+
}
|
13
|
+
|
14
|
+
export async function delCache(key: string) {
|
15
|
+
await cache.del(key);
|
16
|
+
}
|
17
|
+
|
18
|
+
const memoryCache = { getCache, setCache, delCache };
|
19
|
+
|
20
|
+
export default memoryCache;
|