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,250 @@
1
+ import { useState, useImperativeHandle, useRef } from 'react';
2
+ import BLOG from '@/blog.config';
3
+ import algoliasearch from 'algoliasearch';
4
+ import replaceSearchResult from '@/components/Mark';
5
+ import Link from 'next/link';
6
+ import { useGlobal } from '@/lib/global';
7
+ import throttle from 'lodash/throttle';
8
+
9
+ /**
10
+ * 结合 Algolia 实现的弹出式搜索框
11
+ * 打开方式 cRef.current.openSearch()
12
+ * https://www.algolia.com/doc/api-reference/search-api-parameters/
13
+ */
14
+ export default function AlgoliaSearchModal({ cRef }) {
15
+ const [searchResults, setSearchResults] = useState([]);
16
+ const [isModalOpen, setIsModalOpen] = useState(false);
17
+ const [page, setPage] = useState(0);
18
+ const [keyword, setKeyword] = useState(null);
19
+ const [totalPage, setTotalPage] = useState(0);
20
+ const [totalHit, setTotalHit] = useState(0);
21
+ const [useTime, setUseTime] = useState(0);
22
+
23
+ /**
24
+ * 对外暴露方法
25
+ */
26
+ useImperativeHandle(cRef, () => {
27
+ return {
28
+ openSearch: () => {
29
+ setIsModalOpen(true);
30
+ },
31
+ };
32
+ });
33
+
34
+ const client = algoliasearch(
35
+ BLOG.ALGOLIA_APP_ID || '',
36
+ BLOG.ALGOLIA_SEARCH_ONLY_APP_KEY || '',
37
+ );
38
+ const index = client.initIndex(BLOG.ALGOLIA_INDEX);
39
+
40
+ /**
41
+ * 搜索
42
+ * @param {*} query
43
+ */
44
+ const handleSearch = async (query, page) => {
45
+ setKeyword(query);
46
+ setPage(page);
47
+ setSearchResults([]);
48
+ setUseTime(0);
49
+ setTotalPage(0);
50
+ setTotalHit(0);
51
+ if (!query || query === '') {
52
+ return;
53
+ }
54
+
55
+ try {
56
+ const res = await index.search(query, { page, hitsPerPage: 10 });
57
+ const { hits, nbHits, nbPages, processingTimeMS } = res;
58
+ setUseTime(processingTimeMS);
59
+ setTotalPage(nbPages);
60
+ setTotalHit(nbHits);
61
+ setSearchResults(hits);
62
+
63
+ const doms = document
64
+ .getElementById('search-wrapper')
65
+ .getElementsByClassName('replace');
66
+
67
+ setTimeout(() => {
68
+ replaceSearchResult({
69
+ doms,
70
+ search: query,
71
+ target: {
72
+ element: 'span',
73
+ className: 'text-blue-600 border-b border-dashed',
74
+ },
75
+ });
76
+ }, 150);
77
+ } catch (error) {
78
+ console.error('Algolia search error:', error);
79
+ }
80
+ };
81
+
82
+ const throttledHandleSearch = useRef(throttle(handleSearch, 300)); // 设置节流延迟时间
83
+
84
+ // 修改input的onChange事件处理函数
85
+ const handleInputChange = (e) => {
86
+ const query = e.target.value;
87
+ throttledHandleSearch.current(query, 0);
88
+ };
89
+
90
+ /**
91
+ * 切换页码
92
+ * @param {*} page
93
+ */
94
+ const switchPage = (page) => {
95
+ throttledHandleSearch.current(keyword, page);
96
+ };
97
+
98
+ /**
99
+ * 关闭弹窗
100
+ */
101
+ const closeModal = () => {
102
+ setIsModalOpen(false);
103
+ };
104
+
105
+ if (!BLOG.ALGOLIA_APP_ID) {
106
+ return <></>;
107
+ }
108
+
109
+ return (
110
+ <div
111
+ id="search-wrapper"
112
+ className={`${isModalOpen ? 'opacity-100' : 'pointer-events-none invisible opacity-0'} fixed left-0 top-0 z-30 mt-12 flex h-screen w-screen items-start justify-center`}
113
+ >
114
+ {/* 模态框 */}
115
+ <div
116
+ className={`${isModalOpen ? 'opacity-100' : 'invisible translate-y-10 opacity-0'} dark:bg- z-50 flex min-h-[10rem] w-full max-w-xl flex-col justify-between rounded-lg border bg-white p-5 shadow transition-all duration-300 hover:border-blue-600 dark:border-gray-800 dark:bg-hexo-black-gray `}
117
+ >
118
+ <div className="flex items-center justify-between">
119
+ <div className="text-2xl font-bold text-blue-600">搜索</div>
120
+ <div>
121
+ <i
122
+ className="fa-solid fa-xmark cursor-pointer p-1 text-gray-600 hover:text-blue-600"
123
+ onClick={closeModal}
124
+ ></i>
125
+ </div>
126
+ </div>
127
+
128
+ <input
129
+ type="text"
130
+ placeholder="在这里输入搜索关键词..."
131
+ onChange={(e) => handleInputChange(e)}
132
+ className="my-2 mb-4 w-full rounded-md border bg-gray-50 px-4 py-1 text-black outline-blue-500 dark:bg-gray-600 dark:text-gray-200"
133
+ />
134
+
135
+ {/* 标签组 */}
136
+ <div className="mb-4">
137
+ <TagGroups />
138
+ </div>
139
+
140
+ <ul>
141
+ {searchResults.map((result) => (
142
+ <li key={result.objectID} className="replace my-2">
143
+ <a
144
+ href={`${BLOG.SUB_PATH}/${result.slug}`}
145
+ className="font-bold text-black hover:text-blue-600 dark:text-gray-200"
146
+ >
147
+ {result.title}
148
+ </a>
149
+ </li>
150
+ ))}
151
+ </ul>
152
+
153
+ <Pagination totalPage={totalPage} page={page} switchPage={switchPage} />
154
+ <div>
155
+ {totalHit > 0 && (
156
+ <div>
157
+ 共搜索到 {totalHit} 条结果,用时 {useTime} 毫秒
158
+ </div>
159
+ )}
160
+ </div>
161
+ <div className="mt-2 text-gray-600">
162
+ <span>
163
+ <i className="fa-brands fa-algolia"></i> Algolia 提供搜索服务
164
+ </span>{' '}
165
+ </div>
166
+ </div>
167
+
168
+ {/* 遮罩 */}
169
+ <div
170
+ onClick={closeModal}
171
+ className="glassmorphism fixed left-0 top-0 z-30 flex h-full w-full items-center justify-center"
172
+ />
173
+ </div>
174
+ );
175
+ }
176
+
177
+ /**
178
+ * 标签组
179
+ */
180
+ function TagGroups() {
181
+ const { tagOptions } = useGlobal();
182
+ // 获取tagOptions数组前十个
183
+ const firstTenTags = tagOptions?.slice(0, 10);
184
+
185
+ return (
186
+ <div id="tags-group" className="space-y-2 dark:border-gray-700">
187
+ {firstTenTags?.map((tag, index) => {
188
+ return (
189
+ <Link
190
+ passHref
191
+ key={index}
192
+ href={`/tag/${encodeURIComponent(tag.name)}`}
193
+ className={'inline-block cursor-pointer whitespace-nowrap'}
194
+ >
195
+ <div
196
+ className={
197
+ ' flex items-center rounded-lg px-2 py-0.5 text-black transition-all duration-150 hover:scale-110 hover:bg-blue-600 hover:text-white dark:text-gray-300 dark:hover:bg-yellow-600'
198
+ }
199
+ >
200
+ <div className="text-lg">{tag.name} </div>
201
+ {tag.count ? (
202
+ <sup className="relative ml-1">{tag.count}</sup>
203
+ ) : (
204
+ <></>
205
+ )}
206
+ </div>
207
+ </Link>
208
+ );
209
+ })}
210
+ </div>
211
+ );
212
+ }
213
+
214
+ /**
215
+ * 分页
216
+ * @param {*} param0
217
+ */
218
+ function Pagination(props) {
219
+ const { totalPage, page, switchPage } = props;
220
+ if (totalPage <= 0) {
221
+ return <></>;
222
+ }
223
+ const pagesElement = [];
224
+
225
+ for (let i = 0; i < totalPage; i++) {
226
+ const selected = page === i;
227
+ pagesElement.push(getPageElement(i, selected, switchPage));
228
+ }
229
+ return (
230
+ <div className="flex w-full justify-center space-x-1 py-1">
231
+ {pagesElement.map((p) => p)}
232
+ </div>
233
+ );
234
+ }
235
+
236
+ /**
237
+ * 获取分页按钮
238
+ * @param {*} i
239
+ * @param {*} selected
240
+ */
241
+ function getPageElement(i, selected, switchPage) {
242
+ return (
243
+ <div
244
+ onClick={() => switchPage(i)}
245
+ className={`${selected ? 'rounded bg-blue-600 font-bold text-white' : 'hover:font-bold hover:text-blue-600'} h-6 w-6 cursor-pointer text-center `}
246
+ >
247
+ {i + 1}
248
+ </div>
249
+ );
250
+ }
@@ -0,0 +1,30 @@
1
+ import BLOG from '@/blog.config'
2
+ import { loadExternalResource } from '@/lib/utils'
3
+ // import { loadExternalResource } from '@/lib/utils'
4
+ import { useEffect } from 'react'
5
+
6
+ /**
7
+ * Giscus评论 @see https://giscus.app/zh-CN
8
+ * Contribute by @txs https://github.com/txs/NotionNext/commit/1bf7179d0af21fb433e4c7773504f244998678cb
9
+ * @returns {JSX.Element}
10
+ * @constructor
11
+ */
12
+
13
+ const Artalk = ({ siteInfo }) => {
14
+ useEffect(() => {
15
+ loadExternalResource(BLOG.COMMENT_ARTALK_CSS, 'css')
16
+ window?.Artalk?.init({
17
+ server: BLOG.COMMENT_ARTALK_SERVER, // 后端地址
18
+ el: '#artalk', // 容器元素
19
+ locale: BLOG.LANG,
20
+ // pageKey: '/post/1', // 固定链接 (留空自动获取)
21
+ // pageTitle: '关于引入 Artalk 的这档子事', // 页面标题 (留空自动获取)
22
+ site: siteInfo?.title // 你的站点名
23
+ })
24
+ }, [])
25
+ return (
26
+ <div id="artalk"></div>
27
+ )
28
+ }
29
+
30
+ export default Artalk
@@ -0,0 +1,26 @@
1
+ import busuanzi from '@/lib/busuanzi';
2
+ import { useRouter } from 'next/router';
3
+ import { useGlobal } from '@/lib/global';
4
+ // import { useRouter } from 'next/router'
5
+ import React from 'react';
6
+
7
+ let path = '';
8
+
9
+ export default function Busuanzi() {
10
+ const { theme } = useGlobal();
11
+ const Router = useRouter();
12
+ Router.events.on('routeChangeComplete', (url) => {
13
+ if (url !== path) {
14
+ path = url;
15
+ busuanzi.fetch();
16
+ }
17
+ });
18
+
19
+ // 更换主题时更新
20
+ React.useEffect(() => {
21
+ if (theme) {
22
+ busuanzi.fetch();
23
+ }
24
+ }, [theme]);
25
+ return null;
26
+ }
@@ -0,0 +1,19 @@
1
+ import BLOG from '@/blog.config'
2
+
3
+ /**
4
+ * 这是一个嵌入组件,可以在任意位置全屏显示您的chat-base对话框
5
+ * 暂时没有页面引用
6
+ * 因为您可以直接用内嵌网页的方式放入您的notion中 https://www.chatbase.co/chatbot-iframe/${BLOG.CHATBASE_ID}
7
+ */
8
+ export default function ChatBase() {
9
+ if (!BLOG.CHATBASE_ID) {
10
+ return <></>
11
+ }
12
+
13
+ return <iframe
14
+ src={`https://www.chatbase.co/chatbot-iframe/${BLOG.CHATBASE_ID}`}
15
+ width="100%"
16
+ style={{ height: '100%', minHeight: '700px' }}
17
+ frameborder="0"
18
+ ></iframe>
19
+ }
@@ -0,0 +1,134 @@
1
+ import React, { useRef, useEffect, useImperativeHandle } from 'react';
2
+ import type { FC, ForwardedRef, ReactNode } from 'react';
3
+
4
+ const VerticalStyle = { height: '0px', willChange: 'height' };
5
+ const horizontalStyle = { width: '0px', willChange: 'width' };
6
+
7
+ export interface CollapseHandle {
8
+ updateCollapseHeight: () => void;
9
+ }
10
+
11
+ export interface CollapseProps {
12
+ type?: 'horizontal' | 'vertical';
13
+ isOpen: boolean;
14
+ className?: string;
15
+ onHeightChange?: (param: {
16
+ height: number;
17
+ width: number;
18
+ increase: boolean;
19
+ }) => void;
20
+ collapseRef?: ForwardedRef<CollapseHandle>;
21
+ children: ReactNode;
22
+ }
23
+
24
+ /**
25
+ * 折叠面板组件,支持水平折叠、垂直折叠
26
+ * @param {type:['horizontal','vertical'],isOpen} props
27
+ * @returns
28
+ */
29
+ const Collapse: FC<CollapseProps> = (props) => {
30
+ const {
31
+ collapseRef,
32
+ type = 'vertical',
33
+ onHeightChange,
34
+ isOpen = false,
35
+ className,
36
+ children,
37
+ } = props;
38
+ const ref = useRef<HTMLDivElement>(null);
39
+
40
+ useImperativeHandle(collapseRef, () => {
41
+ return {
42
+ /**
43
+ * 当子元素高度变化时,可调用此方法更新折叠组件的高度
44
+ * @param {*} param0
45
+ */
46
+ updateCollapseHeight: () => {
47
+ const style = ref.current?.style;
48
+ if (style) {
49
+ style.height = 'auto';
50
+ style.width = 'auto';
51
+ }
52
+ // ref.current.style.height = ref.current.scrollHeight;
53
+ // ref.current.style.height = 'auto';
54
+ },
55
+ };
56
+ });
57
+
58
+ /**
59
+ * 折叠
60
+ * @param {*} element
61
+ */
62
+ const collapseSection = (element: HTMLDivElement) => {
63
+ const sectionHeight = element.scrollHeight;
64
+ const sectionWidth = element.scrollWidth;
65
+
66
+ requestAnimationFrame(function () {
67
+ switch (type) {
68
+ case 'horizontal':
69
+ element.style.width = sectionWidth + 'px';
70
+ requestAnimationFrame(function () {
71
+ element.style.width = 0 + 'px';
72
+ });
73
+ break;
74
+ case 'vertical':
75
+ element.style.height = sectionHeight + 'px';
76
+ requestAnimationFrame(function () {
77
+ element.style.height = 0 + 'px';
78
+ });
79
+ }
80
+ });
81
+ };
82
+
83
+ /**
84
+ * 展开
85
+ * @param {*} element
86
+ */
87
+ const expandSection = (element: HTMLDivElement) => {
88
+ const sectionHeight = element.scrollHeight;
89
+ const sectionWidth = element.scrollWidth;
90
+ let clearTime: NodeJS.Timeout;
91
+ switch (type) {
92
+ case 'horizontal':
93
+ element.style.width = sectionWidth + 'px';
94
+ clearTime = setTimeout(() => {
95
+ element.style.width = 'auto';
96
+ }, 400);
97
+ break;
98
+ case 'vertical':
99
+ element.style.height = sectionHeight + 'px';
100
+ clearTime = setTimeout(() => {
101
+ element.style.height = 'auto';
102
+ }, 400);
103
+ }
104
+
105
+ clearTimeout(clearTime);
106
+ };
107
+
108
+ useEffect(() => {
109
+ if (isOpen) {
110
+ if (ref.current) expandSection(ref.current);
111
+ } else {
112
+ if (ref.current) collapseSection(ref.current);
113
+ }
114
+ // 通知父组件高度变化
115
+ onHeightChange &&
116
+ onHeightChange({
117
+ height: ref.current?.scrollHeight || 0,
118
+ width: ref.current?.scrollWidth || 0,
119
+ increase: isOpen,
120
+ });
121
+ }, [isOpen]);
122
+
123
+ return (
124
+ <div
125
+ ref={ref}
126
+ style={type === 'vertical' ? VerticalStyle : horizontalStyle}
127
+ className={`${className || ''} overflow-hidden duration-200 `}
128
+ >
129
+ {children}
130
+ </div>
131
+ );
132
+ };
133
+
134
+ export default Collapse;
@@ -0,0 +1,161 @@
1
+ import BLOG from '@/blog.config';
2
+ import dynamic from 'next/dynamic';
3
+ import Tabs from '@/components/Tabs';
4
+ import { isBrowser } from '@/lib/utils';
5
+ import { useRouter } from 'next/router';
6
+ import Artalk from './Artalk';
7
+
8
+ const WalineComponent = dynamic(
9
+ () => {
10
+ return import('@/components/WalineComponent');
11
+ },
12
+ { ssr: false },
13
+ );
14
+
15
+ const CusdisComponent = dynamic(
16
+ () => {
17
+ return import('@/components/CusdisComponent');
18
+ },
19
+ { ssr: false },
20
+ );
21
+
22
+ const TwikooCompenent = dynamic(
23
+ () => {
24
+ return import('@/components/Twikoo');
25
+ },
26
+ { ssr: false },
27
+ );
28
+
29
+ const GitalkComponent = dynamic(
30
+ () => {
31
+ return import('@/components/Gitalk');
32
+ },
33
+ { ssr: false },
34
+ );
35
+ const UtterancesComponent = dynamic(
36
+ () => {
37
+ return import('@/components/Utterances');
38
+ },
39
+ { ssr: false },
40
+ );
41
+ const GiscusComponent = dynamic(
42
+ () => {
43
+ return import('@/components/Giscus');
44
+ },
45
+ { ssr: false },
46
+ );
47
+ const WebMentionComponent = dynamic(
48
+ () => {
49
+ return import('@/components/WebMention');
50
+ },
51
+ { ssr: false },
52
+ );
53
+
54
+ const ValineComponent = dynamic(() => import('@/components/ValineComponent'), {
55
+ ssr: false,
56
+ });
57
+
58
+ /**
59
+ * 是否有评论
60
+ */
61
+ export const commentEnable =
62
+ BLOG.COMMENT_TWIKOO_ENV_ID ||
63
+ BLOG.COMMENT_WALINE_SERVER_URL ||
64
+ BLOG.COMMENT_VALINE_APP_ID ||
65
+ BLOG.COMMENT_GISCUS_REPO ||
66
+ BLOG.COMMENT_CUSDIS_APP_ID ||
67
+ BLOG.COMMENT_UTTERRANCES_REPO ||
68
+ BLOG.COMMENT_GITALK_CLIENT_ID ||
69
+ BLOG.COMMENT_WEBMENTION.ENABLE;
70
+
71
+ /**
72
+ * 评论组件
73
+ * @param {*} param0
74
+ * @returns
75
+ */
76
+ const Comment = ({ siteInfo, frontMatter, className }) => {
77
+ const router = useRouter();
78
+
79
+ if (
80
+ isBrowser &&
81
+ ('giscus' in router.query || router.query.target === 'comment')
82
+ ) {
83
+ setTimeout(() => {
84
+ const url = router.asPath.replace('?target=comment', '');
85
+ history.replaceState({}, '', url);
86
+ document
87
+ ?.getElementById('comment')
88
+ ?.scrollIntoView({ block: 'start', behavior: 'smooth' });
89
+ }, 1000);
90
+ }
91
+
92
+ if (!frontMatter) {
93
+ return <>Loading...</>;
94
+ }
95
+
96
+ return (
97
+ <div
98
+ key={frontMatter?.id}
99
+ id="comment"
100
+ className={`comment mt-5 text-gray-800 dark:text-gray-300 ${className || ''}`}
101
+ >
102
+ <Tabs>
103
+ {BLOG.COMMENT_ARTALK_SERVER && (
104
+ <div key="Artalk">
105
+ <Artalk siteInfo={siteInfo} />
106
+ </div>
107
+ )}
108
+
109
+ {BLOG.COMMENT_TWIKOO_ENV_ID && (
110
+ <div key="Twikoo">
111
+ <TwikooCompenent />
112
+ </div>
113
+ )}
114
+
115
+ {BLOG.COMMENT_WALINE_SERVER_URL && (
116
+ <div key="Waline">
117
+ <WalineComponent />
118
+ </div>
119
+ )}
120
+
121
+ {BLOG.COMMENT_VALINE_APP_ID && (
122
+ <div key="Valine" name="reply">
123
+ <ValineComponent path={frontMatter.id} />
124
+ </div>
125
+ )}
126
+
127
+ {BLOG.COMMENT_GISCUS_REPO && (
128
+ <div key="Giscus">
129
+ <GiscusComponent className="px-2" />
130
+ </div>
131
+ )}
132
+
133
+ {BLOG.COMMENT_CUSDIS_APP_ID && (
134
+ <div key="Cusdis">
135
+ <CusdisComponent frontMatter={frontMatter} />
136
+ </div>
137
+ )}
138
+
139
+ {BLOG.COMMENT_UTTERRANCES_REPO && (
140
+ <div key="Utterance">
141
+ <UtterancesComponent issueTerm={frontMatter.id} className="px-2" />
142
+ </div>
143
+ )}
144
+
145
+ {BLOG.COMMENT_GITALK_CLIENT_ID && (
146
+ <div key="GitTalk">
147
+ <GitalkComponent frontMatter={frontMatter} />
148
+ </div>
149
+ )}
150
+
151
+ {BLOG.COMMENT_WEBMENTION.ENABLE && (
152
+ <div key="WebMention">
153
+ <WebMentionComponent frontMatter={frontMatter} className="px-2" />
154
+ </div>
155
+ )}
156
+ </Tabs>
157
+ </div>
158
+ );
159
+ };
160
+
161
+ export default Comment;