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.
Files changed (218) hide show
  1. package/.dockerignore +1 -0
  2. package/.env.local +2 -0
  3. package/.eslintrc.json +15 -0
  4. package/.github/stale.yml +16 -0
  5. package/.github/workflows/codeql-analysis.yml +73 -0
  6. package/.github/workflows/docker-ghcr.yaml +59 -0
  7. package/.prettierrc +9 -0
  8. package/.vscode/launch.json +28 -0
  9. package/.vscode/settings.json +6 -0
  10. package/Dockerfile +38 -0
  11. package/LICENSE +21 -0
  12. package/README.md +16 -0
  13. package/blog.config.js +454 -0
  14. package/components/Ackee.js +83 -0
  15. package/components/AdBlockDetect.js +40 -0
  16. package/components/AlgoliaSearchModal.js +250 -0
  17. package/components/Artalk.js +30 -0
  18. package/components/Busuanzi.js +26 -0
  19. package/components/ChatBase.js +19 -0
  20. package/components/Collapse.tsx +134 -0
  21. package/components/Comment.js +161 -0
  22. package/components/CommonHead.tsx +101 -0
  23. package/components/CommonScript.js +125 -0
  24. package/components/CusdisComponent.js +35 -0
  25. package/components/CustomContextMenu.js +221 -0
  26. package/components/DebugPanel.js +134 -0
  27. package/components/DisableCopy.js +21 -0
  28. package/components/Draggable.js +167 -0
  29. package/components/Equation.js +31 -0
  30. package/components/ExternalPlugins.js +75 -0
  31. package/components/ExternalScript.js +29 -0
  32. package/components/FacebookMessenger.js +255 -0
  33. package/components/FacebookPage.js +34 -0
  34. package/components/Fireworks.js +210 -0
  35. package/components/FlipCard.js +56 -0
  36. package/components/FlutteringRibbon.js +322 -0
  37. package/components/FullScreenButton.js +48 -0
  38. package/components/Giscus.js +33 -0
  39. package/components/Gitalk.js +42 -0
  40. package/components/GoogleAdsense.js +111 -0
  41. package/components/Gtag.js +18 -0
  42. package/components/HeroIcons.tsx +321 -0
  43. package/components/KatexReact.js +53 -0
  44. package/components/LazyImage.js +95 -0
  45. package/components/Live2D.js +52 -0
  46. package/components/Loading.js +20 -0
  47. package/components/Mark.js +28 -0
  48. package/components/NProgress.ts +8 -0
  49. package/components/Nest.js +124 -0
  50. package/components/NotionIcon.js +20 -0
  51. package/components/NotionPage.tsx +206 -0
  52. package/components/Player.js +54 -0
  53. package/components/PrismMac.js +236 -0
  54. package/components/QrCode.js +34 -0
  55. package/components/Ribbon.js +98 -0
  56. package/components/Sakura.js +192 -0
  57. package/components/Select.js +40 -0
  58. package/components/ShareBar.js +29 -0
  59. package/components/ShareButtons.js +403 -0
  60. package/components/SideBarDrawer.js +50 -0
  61. package/components/StarrySky.js +130 -0
  62. package/components/Tabs.js +64 -0
  63. package/components/ThemeSwitch.js +67 -0
  64. package/components/Twikoo.js +27 -0
  65. package/components/TwikooCommentCount.js +22 -0
  66. package/components/TwikooCommentCounter.js +78 -0
  67. package/components/TwikooRecentComments.js +11 -0
  68. package/components/Utterances.js +35 -0
  69. package/components/VConsole.js +76 -0
  70. package/components/ValineComponent.js +59 -0
  71. package/components/ValineCount.js +6 -0
  72. package/components/ValinePanel.js +3 -0
  73. package/components/Vercel.tsx +54 -0
  74. package/components/WWAds.js +18 -0
  75. package/components/WalineComponent.js +83 -0
  76. package/components/WebMention.js +173 -0
  77. package/components/Webwhiz.js +17 -0
  78. package/components/WordCount.js +73 -0
  79. package/hooks/useToggleClickOutSide.ts +32 -0
  80. package/hooks/useWindowSize.ts +30 -0
  81. package/lib/algolia.js +108 -0
  82. package/lib/busuanzi.js +99 -0
  83. package/lib/cache/cacheManager.ts +49 -0
  84. package/lib/cache/localFileCache.ts +56 -0
  85. package/lib/cache/memoryMache.ts +20 -0
  86. package/lib/cache/mongoDbCache.ts +70 -0
  87. package/lib/cache/types.ts +5 -0
  88. package/lib/font.js +46 -0
  89. package/lib/global.tsx +129 -0
  90. package/lib/gtag.js +17 -0
  91. package/lib/mailchimp.js +49 -0
  92. package/lib/memorize.js +0 -0
  93. package/lib/mhchem.js +1696 -0
  94. package/lib/notion/getAllCategories.ts +51 -0
  95. package/lib/notion/getAllPageIds.ts +51 -0
  96. package/lib/notion/getAllPosts.js +68 -0
  97. package/lib/notion/getAllTags.ts +43 -0
  98. package/lib/notion/getNotionData.ts +340 -0
  99. package/lib/notion/getPageInfoOfPostPage.ts +58 -0
  100. package/lib/notion/getPageProperties.ts +203 -0
  101. package/lib/notion/getPageTableOfContents.ts +107 -0
  102. package/lib/notion/getPostBlocks.ts +147 -0
  103. package/lib/notion/mapImage.ts +130 -0
  104. package/lib/notion/types.ts +125 -0
  105. package/lib/notion.js +2 -0
  106. package/lib/robots.txt.js +25 -0
  107. package/lib/rss.js +63 -0
  108. package/lib/sitemap.xml.js +67 -0
  109. package/lib/utils.js +212 -0
  110. package/next-env.d.ts +5 -0
  111. package/next-i18next.config.js +7 -0
  112. package/next-sitemap.config.js +11 -0
  113. package/next.config.js +124 -0
  114. package/package.json +92 -0
  115. package/pages/404.tsx +40 -0
  116. package/pages/[prefix]/[slug].tsx +123 -0
  117. package/pages/[prefix]/index.tsx +223 -0
  118. package/pages/_app.js +59 -0
  119. package/pages/_document.js +42 -0
  120. package/pages/api/subscribe.js +22 -0
  121. package/pages/archive/index.tsx +79 -0
  122. package/pages/category/[category]/index.tsx +87 -0
  123. package/pages/category/[category]/page/[page].tsx +103 -0
  124. package/pages/category/index.tsx +43 -0
  125. package/pages/index.tsx +88 -0
  126. package/pages/page/[page].tsx +93 -0
  127. package/pages/search/[keyword]/index.tsx +162 -0
  128. package/pages/search/[keyword]/page/[page].tsx +166 -0
  129. package/pages/search/index.tsx +69 -0
  130. package/pages/sitemap.xml.js +70 -0
  131. package/pages/tag/[tag]/index.tsx +73 -0
  132. package/pages/tag/[tag]/page/[page].tsx +87 -0
  133. package/pages/tag/index.tsx +42 -0
  134. package/postcss.config.js +6 -0
  135. package/public/ads.txt +1 -0
  136. package/public/avatar.png +0 -0
  137. package/public/avatar.svg +11 -0
  138. package/public/bg_image.jpg +0 -0
  139. package/public/css/all.min.css +9 -0
  140. package/public/css/custom.css +8 -0
  141. package/public/css/img-shadow.css +5 -0
  142. package/public/css/prism-mac-style.css +58 -0
  143. package/public/favicon.ico +0 -0
  144. package/public/favicon.svg +9 -0
  145. package/public/js/cusdis.es.js +107 -0
  146. package/public/js/custom.js +1 -0
  147. package/public/locales/en/common.json +44 -0
  148. package/public/locales/en/menu.json +9 -0
  149. package/public/locales/en/nav.json +11 -0
  150. package/public/locales/zh-CN/common.json +44 -0
  151. package/public/locales/zh-CN/menu.json +9 -0
  152. package/public/locales/zh-CN/nav.json +9 -0
  153. package/public/webfonts/fa-brands-400.ttf +0 -0
  154. package/public/webfonts/fa-brands-400.woff2 +0 -0
  155. package/public/webfonts/fa-regular-400.ttf +0 -0
  156. package/public/webfonts/fa-regular-400.woff2 +0 -0
  157. package/public/webfonts/fa-solid-900.ttf +0 -0
  158. package/public/webfonts/fa-solid-900.woff2 +0 -0
  159. package/public/webfonts/fa-v4compatibility.ttf +0 -0
  160. package/public/webfonts/fa-v4compatibility.woff2 +0 -0
  161. package/styles/animate.css +503 -0
  162. package/styles/globals.css +183 -0
  163. package/styles/notion.css +2064 -0
  164. package/styles/nprogress.css +84 -0
  165. package/styles/prism-theme.css +119 -0
  166. package/styles/utility-patterns.css +79 -0
  167. package/tailwind.config.js +37 -0
  168. package/theme/index.ts +6 -0
  169. package/theme/types/@theme-components.d.ts +29 -0
  170. package/theme/useLayout.ts +41 -0
  171. package/theme/utils.ts +108 -0
  172. package/themes/innocent/package.json +7 -0
  173. package/themes/innocent/theme.config.js +1 -0
  174. package/themes/nobelium/components/Announcement.tsx +27 -0
  175. package/themes/nobelium/components/ArticleFooter.tsx +39 -0
  176. package/themes/nobelium/components/ArticleInfo.tsx +58 -0
  177. package/themes/nobelium/components/ArticleLock.tsx +86 -0
  178. package/themes/nobelium/components/BlogArchiveItem.js +41 -0
  179. package/themes/nobelium/components/BlogListBar.js +39 -0
  180. package/themes/nobelium/components/BlogListPage.tsx +67 -0
  181. package/themes/nobelium/components/BlogListScroll.tsx +96 -0
  182. package/themes/nobelium/components/BlogPost.tsx +33 -0
  183. package/themes/nobelium/components/DarkModeButton.tsx +50 -0
  184. package/themes/nobelium/components/ExampleRecentComments.js +35 -0
  185. package/themes/nobelium/components/Footer.tsx +35 -0
  186. package/themes/nobelium/components/JumpToTopButton.tsx +39 -0
  187. package/themes/nobelium/components/LanguageSwitchButton.tsx +58 -0
  188. package/themes/nobelium/components/MenuItemCollapse.tsx +92 -0
  189. package/themes/nobelium/components/MenuItemDrop.tsx +83 -0
  190. package/themes/nobelium/components/Nav/Nav.module.css +50 -0
  191. package/themes/nobelium/components/Nav/Nav.tsx +187 -0
  192. package/themes/nobelium/components/RandomPostButton.tsx +31 -0
  193. package/themes/nobelium/components/SearchButton.tsx +31 -0
  194. package/themes/nobelium/components/SearchInput.tsx +94 -0
  195. package/themes/nobelium/components/SearchNavBar.js +19 -0
  196. package/themes/nobelium/components/SideBar.js +83 -0
  197. package/themes/nobelium/components/SvgIcon.js +29 -0
  198. package/themes/nobelium/components/TagItem.js +13 -0
  199. package/themes/nobelium/components/Tags.tsx +44 -0
  200. package/themes/nobelium/components/Title.js +19 -0
  201. package/themes/nobelium/index.tsx +28 -0
  202. package/themes/nobelium/layout/LayoutBase.tsx +79 -0
  203. package/themes/nobelium/pages/Archive.tsx +30 -0
  204. package/themes/nobelium/pages/Category.tsx +43 -0
  205. package/themes/nobelium/pages/Home.tsx +22 -0
  206. package/themes/nobelium/pages/PageNotFound.tsx +15 -0
  207. package/themes/nobelium/pages/Post.tsx +34 -0
  208. package/themes/nobelium/pages/PostList.tsx +74 -0
  209. package/themes/nobelium/pages/Search.tsx +65 -0
  210. package/themes/nobelium/pages/Tag.tsx +42 -0
  211. package/themes/nobelium/providers/index.tsx +60 -0
  212. package/themes/nobelium/stores/index.tsx +42 -0
  213. package/themes/nobelium/theme.config.ts +17 -0
  214. package/themes/nobelium/types/index.ts +10 -0
  215. package/tsconfig.json +29 -0
  216. package/types/index.ts +1 -0
  217. package/types/page.ts +102 -0
  218. 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>&nbsp;{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标签和&nbsp;之类的特殊符合
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 };
@@ -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;