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,28 @@
1
+ import PageNotFound from './pages/PageNotFound';
2
+ import Archive from './pages/Archive';
3
+ import Category from './pages/Category';
4
+ import Home from './pages/Home';
5
+ import PostList from './pages/PostList';
6
+ import Search from './pages/Search';
7
+ import Tag from './pages/Tag';
8
+ import Post from './pages/Post';
9
+ import CONFIG from './theme.config';
10
+
11
+ export {
12
+ CONFIG as THEME_CONFIG,
13
+ Home,
14
+ PostList as Page,
15
+ Archive,
16
+ Category,
17
+ PostList as CategoryDetail,
18
+ PostList as CategoryPage,
19
+ Tag,
20
+ PostList as TagDetail,
21
+ PostList as TagPage,
22
+ Search,
23
+ PostList as SearchDetail,
24
+ PostList as SearchPage,
25
+ Post as Prefix,
26
+ Post as PrefixSlug,
27
+ PageNotFound,
28
+ };
@@ -0,0 +1,79 @@
1
+ import { Transition } from '@headlessui/react';
2
+ import { useGlobal } from '@/lib/global';
3
+ import CommonHead from '@/components/CommonHead';
4
+ import Nav from '../components/Nav/Nav';
5
+ import Footer from '../components/Footer';
6
+ import JumpToTopButton from '../components/JumpToTopButton';
7
+ import { useNobeliumStore } from '../providers';
8
+
9
+ import { useEffect, type FC, type ReactNode } from 'react';
10
+ import type { ThemeBaseProps } from '@/types';
11
+
12
+ export interface LayoutBaseProps extends ThemeBaseProps {
13
+ topSlot?: ReactNode;
14
+ children: ReactNode;
15
+ }
16
+
17
+ /**
18
+ * @returns {JSX.Element}
19
+ * @constructor
20
+ */
21
+ const LayoutBase: FC<LayoutBaseProps> = (props) => {
22
+ const { children, topSlot, pageMeta, latestPosts, siteInfo } = props;
23
+
24
+ const { updateLatestPosts, updateSiteInfo } = useNobeliumStore(
25
+ (state) => state,
26
+ );
27
+
28
+ useEffect(() => {
29
+ updateLatestPosts(latestPosts);
30
+ }, [latestPosts, updateLatestPosts]);
31
+
32
+ useEffect(() => {
33
+ updateSiteInfo(siteInfo);
34
+ }, [siteInfo, updateSiteInfo]);
35
+
36
+ const { onLoading } = useGlobal();
37
+
38
+ return (
39
+ <div
40
+ id="theme-nobelium"
41
+ className="nobelium flex min-h-screen flex-col flex-nowrap items-stretch justify-start bg-white dark:bg-black dark:text-gray-300"
42
+ >
43
+ {/* SEO相关 */}
44
+ <CommonHead pageMeta={pageMeta} />
45
+
46
+ {/* 顶部导航栏 */}
47
+ <Nav />
48
+
49
+ {/* 主区 */}
50
+ <main className="max-w-2xl flex-grow self-center px-4 transition-all">
51
+ <Transition
52
+ show={!onLoading}
53
+ appear={true}
54
+ enter="transition ease-in-out duration-700 transform order-first"
55
+ enterFrom="opacity-0 translate-y-16"
56
+ enterTo="opacity-100"
57
+ leave="transition ease-in-out duration-300 transform"
58
+ leaveFrom="opacity-100 translate-y-0"
59
+ leaveTo="opacity-0 -translate-y-16"
60
+ unmount={false}
61
+ >
62
+ {/* 顶部插槽 */}
63
+ {topSlot}
64
+ {children}
65
+ </Transition>
66
+ </main>
67
+
68
+ {/* 页脚 */}
69
+ <Footer />
70
+
71
+ {/* 右下悬浮 */}
72
+ <div className="fixed bottom-4 right-4">
73
+ <JumpToTopButton />
74
+ </div>
75
+ </div>
76
+ );
77
+ };
78
+
79
+ export default LayoutBase;
@@ -0,0 +1,30 @@
1
+ import LayoutBase from '../layout/LayoutBase';
2
+ import BlogArchiveItem from '../components/BlogArchiveItem';
3
+ import { ContextWrapper } from '../providers';
4
+
5
+ import type { FC } from 'react';
6
+ import type { ThemeArchiveProps } from '@/types';
7
+
8
+ /**
9
+ * 归档
10
+ * @param {*} props
11
+ * @returns
12
+ */
13
+ const Archive: FC<ThemeArchiveProps> = (props) => {
14
+ const { archivePosts } = props;
15
+ return (
16
+ <LayoutBase {...props}>
17
+ <div className="mb-10 min-h-screen w-full p-3 pb-20 md:py-12">
18
+ {Object.keys(archivePosts).map((archiveTitle) => (
19
+ <BlogArchiveItem
20
+ key={archiveTitle}
21
+ archiveTitle={archiveTitle}
22
+ archivePosts={archivePosts}
23
+ />
24
+ ))}
25
+ </div>
26
+ </LayoutBase>
27
+ );
28
+ };
29
+
30
+ export default ContextWrapper(Archive);
@@ -0,0 +1,43 @@
1
+ import Link from 'next/link';
2
+ import LayoutBase from '../layout/LayoutBase';
3
+ import { ContextWrapper } from '../providers';
4
+
5
+ import type { FC } from 'react';
6
+ import type { ThemeCategoryProps } from '@/types';
7
+
8
+ /**
9
+ * 文章分类列表
10
+ * @param {*} props
11
+ * @returns
12
+ */
13
+ const Category: FC<ThemeCategoryProps> = (props) => {
14
+ const { categoryOptions } = props;
15
+
16
+ return (
17
+ <LayoutBase {...props}>
18
+ <div id="category-list" className="flex flex-wrap duration-200">
19
+ {categoryOptions?.map((category) => {
20
+ return (
21
+ <Link
22
+ key={category.name}
23
+ href={`/category/${category.name}`}
24
+ passHref
25
+ legacyBehavior
26
+ >
27
+ <div
28
+ className={
29
+ 'cursor-pointer px-5 py-2 hover:bg-gray-100 hover:text-black dark:text-gray-300 dark:hover:bg-gray-600 dark:hover:text-white'
30
+ }
31
+ >
32
+ <i className="fas fa-folder mr-4" />
33
+ {category.name}({category.count})
34
+ </div>
35
+ </Link>
36
+ );
37
+ })}
38
+ </div>
39
+ </LayoutBase>
40
+ );
41
+ };
42
+
43
+ export default ContextWrapper(Category);
@@ -0,0 +1,22 @@
1
+ import Announcement from '../components/Announcement';
2
+ import { OriginPostList } from './PostList';
3
+ import { ContextWrapper } from '../providers';
4
+
5
+ import type { FC } from 'react';
6
+ import type { ThemeHomeProps } from '@/types';
7
+
8
+ /**
9
+ * 首页
10
+ * 首页是个博客列表,加上顶部嵌入一个公告
11
+ * @param {*} props
12
+ * @returns
13
+ */
14
+
15
+ const Home: FC<ThemeHomeProps> = (props) => {
16
+ const { notice } = props;
17
+ return (
18
+ <OriginPostList {...props} topSlot={<Announcement notice={notice} />} />
19
+ );
20
+ };
21
+
22
+ export default ContextWrapper(Home);
@@ -0,0 +1,15 @@
1
+ import LayoutBase from '../layout/LayoutBase';
2
+ import { ContextWrapper } from '../providers';
3
+
4
+ import type { FC } from 'react';
5
+ import type { ThemePageNotFoundProps } from '@/types';
6
+ /**
7
+ * 404 页面
8
+ * @param {*} props
9
+ * @returns
10
+ */
11
+ const PageNotFound: FC<ThemePageNotFoundProps> = (props) => {
12
+ return <LayoutBase {...props}>404 Not found.</LayoutBase>;
13
+ };
14
+
15
+ export default ContextWrapper(PageNotFound);
@@ -0,0 +1,34 @@
1
+ import LayoutBase from '../layout/LayoutBase';
2
+ import ArticleLock from '../components/ArticleLock';
3
+ import ArticleInfo from '../components/ArticleInfo';
4
+ import ArticleFooter from '../components/ArticleFooter';
5
+ // TODO: move to theme file
6
+ import NotionPage from '@/components/NotionPage';
7
+ import ShareBar from '@/components/ShareBar';
8
+ import Comment from '@/components/Comment';
9
+ import { ContextWrapper } from '../providers';
10
+
11
+ import type { FC } from 'react';
12
+ import type { ThemePrefixProps, ThemePrefixSlugProps } from '@/types';
13
+
14
+ const Post: FC<ThemePrefixProps & ThemePrefixSlugProps> = (props) => {
15
+ const { post, isLock, validPassword } = props;
16
+
17
+ return (
18
+ <LayoutBase {...props}>
19
+ {isLock && <ArticleLock validPassword={validPassword} />}
20
+
21
+ {!isLock && (
22
+ <div id="article-wrapper" className="px-2">
23
+ {post && <ArticleInfo post={post} />}
24
+ {post && <NotionPage post={post} />}
25
+ <ShareBar post={post} />
26
+ <Comment frontMatter={post} />
27
+ <ArticleFooter />
28
+ </div>
29
+ )}
30
+ </LayoutBase>
31
+ );
32
+ };
33
+
34
+ export default ContextWrapper(Post);
@@ -0,0 +1,74 @@
1
+ import React, { useState } from 'react';
2
+ import BLOG from '@/blog.config';
3
+ import LayoutBase from '../layout/LayoutBase';
4
+ import { deepClone } from '@/lib/utils';
5
+ import BlogListPage from '../components/BlogListPage';
6
+ import BlogListScroll from '../components/BlogListScroll';
7
+ import BlogListBar from '../components/BlogListBar';
8
+ import { ContextWrapper } from '../providers';
9
+
10
+ import type { PageInfo } from '@/lib/notion/types';
11
+ import type {
12
+ ThemeHomeProps,
13
+ ThemeCategoryDetailProps,
14
+ ThemeCategoryPageProps,
15
+ ThemeTagDetailProps,
16
+ ThemeTagPageProps,
17
+ ThemeSearchDetailProps,
18
+ ThemeSearchPageProps,
19
+ } from '@/types';
20
+ import type { FC, ReactNode } from 'react';
21
+
22
+ /**
23
+ * 博客列表
24
+ * @param {*} props
25
+ * @returns
26
+ */
27
+ const PostList: FC<
28
+ ThemeHomeProps &
29
+ Partial<
30
+ ThemeCategoryDetailProps &
31
+ ThemeCategoryPageProps &
32
+ ThemeTagDetailProps &
33
+ ThemeTagPageProps &
34
+ ThemeSearchDetailProps &
35
+ ThemeSearchPageProps & {
36
+ topSlot: ReactNode;
37
+ }
38
+ >
39
+ > = (props) => {
40
+ const { posts, postCount, page, topSlot } = props;
41
+
42
+ // 在列表中进行实时过滤
43
+ const [filterKey, setFilterKey] = useState('');
44
+ let filteredBlogPosts: PageInfo[] = [];
45
+ if (filterKey && posts) {
46
+ filteredBlogPosts = posts.filter((post) => {
47
+ const tagContent = post?.tags ? post?.tags.join(' ') : '';
48
+ const searchContent = post.title + post.summary + tagContent;
49
+ return searchContent.toLowerCase().includes(filterKey.toLowerCase());
50
+ });
51
+ } else {
52
+ filteredBlogPosts = deepClone(posts);
53
+ }
54
+ return (
55
+ <LayoutBase
56
+ {...props}
57
+ topSlot={<BlogListBar {...props} setFilterKey={setFilterKey} />}
58
+ >
59
+ {topSlot}
60
+ {BLOG.POST_LIST_STYLE === 'page' ? (
61
+ <BlogListPage
62
+ postCount={postCount}
63
+ page={page}
64
+ posts={filteredBlogPosts}
65
+ />
66
+ ) : (
67
+ <BlogListScroll posts={filteredBlogPosts} />
68
+ )}
69
+ </LayoutBase>
70
+ );
71
+ };
72
+
73
+ export const OriginPostList = PostList;
74
+ export default ContextWrapper(PostList);
@@ -0,0 +1,65 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { deepClone } from '@/lib/utils';
3
+ import BLOG from '@/blog.config';
4
+ import replaceSearchResult from '@/components/Mark';
5
+ import { isBrowser } from '@/lib/utils';
6
+ import LayoutBase from '../layout/LayoutBase';
7
+ import BlogListBar from '../components/BlogListBar';
8
+ import SearchNavBar from '../components/SearchNavBar';
9
+ import BlogListPage from '../components/BlogListPage';
10
+ import BlogListScroll from '../components/BlogListScroll';
11
+ import { ContextWrapper } from '../providers/index';
12
+
13
+ import type { FC } from 'react';
14
+ import type { ThemeSearchProps } from '@/types';
15
+
16
+ /**
17
+ * 搜索
18
+ * 页面是博客列表,上方嵌入一个搜索引导条
19
+ * @param {*} props
20
+ * @returns
21
+ */
22
+ const Search: FC<ThemeSearchProps> = (props) => {
23
+ const { keyword, posts, postCount } = props;
24
+ useEffect(() => {
25
+ if (isBrowser) {
26
+ replaceSearchResult({
27
+ doms: document.getElementById('posts-wrapper'),
28
+ search: keyword,
29
+ target: {
30
+ element: 'span',
31
+ className: 'text-red-500 border-b border-dashed',
32
+ },
33
+ });
34
+ }
35
+ }, []);
36
+
37
+ // 在列表中进行实时过滤
38
+ const [filterKey, setFilterKey] = useState('');
39
+ let filteredBlogPosts = [];
40
+ if (filterKey && posts) {
41
+ filteredBlogPosts = posts.filter((post) => {
42
+ const tagContent = post?.tags ? post?.tags.join(' ') : '';
43
+ const searchContent = post.title + post.summary + tagContent;
44
+ return searchContent.toLowerCase().includes(filterKey.toLowerCase());
45
+ });
46
+ } else {
47
+ filteredBlogPosts = deepClone(posts);
48
+ }
49
+
50
+ return (
51
+ <LayoutBase
52
+ {...props}
53
+ topSlot={<BlogListBar {...props} setFilterKey={setFilterKey} />}
54
+ >
55
+ <SearchNavBar {...props} />
56
+ {BLOG.POST_LIST_STYLE === 'page' ? (
57
+ <BlogListPage postCount={postCount} posts={filteredBlogPosts} />
58
+ ) : (
59
+ <BlogListScroll posts={filteredBlogPosts} />
60
+ )}
61
+ </LayoutBase>
62
+ );
63
+ };
64
+
65
+ export default ContextWrapper(Search);
@@ -0,0 +1,42 @@
1
+ import Link from 'next/link';
2
+ import LayoutBase from '../layout/LayoutBase';
3
+ import { ContextWrapper } from '../providers/index';
4
+
5
+ import type { FC } from 'react';
6
+ import type { ThemeTagProps } from '@/types';
7
+
8
+ /**
9
+ * 文章标签列表
10
+ * @param {*} props
11
+ * @returns
12
+ */
13
+ const Tag: FC<ThemeTagProps> = (props) => {
14
+ const { tagOptions } = props;
15
+ return (
16
+ <LayoutBase {...props}>
17
+ <div>
18
+ <div id="tags-list" className="flex flex-wrap duration-200">
19
+ {tagOptions.map((tag) => {
20
+ return (
21
+ <div key={tag.name} className="p-2">
22
+ <Link
23
+ key={tag.id}
24
+ href={`/tag/${encodeURIComponent(tag.name)}`}
25
+ passHref
26
+ className={`mr-2 inline-block cursor-pointer whitespace-nowrap rounded px-2 py-1 text-xs text-gray-600 duration-200 hover:bg-gray-500 hover:text-white hover:shadow-xl dark:border-gray-400 dark:hover:text-white notion-${tag.color}_background dark:bg-gray-800`}
27
+ >
28
+ <div className="font-light dark:text-gray-400">
29
+ <i className="fas fa-tag mr-1" />{' '}
30
+ {tag.name + (tag.count ? `(${tag.count})` : '')}{' '}
31
+ </div>
32
+ </Link>
33
+ </div>
34
+ );
35
+ })}
36
+ </div>
37
+ </div>
38
+ </LayoutBase>
39
+ );
40
+ };
41
+
42
+ export default ContextWrapper(Tag);
@@ -0,0 +1,60 @@
1
+ import React, {
2
+ type ReactNode,
3
+ createContext,
4
+ useRef,
5
+ useContext,
6
+ } from 'react';
7
+ import { useStore } from 'zustand';
8
+
9
+ import { type NobeliumStore, createNobeliumStore } from '../stores';
10
+
11
+ export type NobeliumStoreApi = ReturnType<typeof createNobeliumStore>;
12
+
13
+ export const NobeliumStoreContext = createContext<NobeliumStoreApi | undefined>(
14
+ undefined,
15
+ );
16
+
17
+ export interface NobeliumStoreProviderProps {
18
+ children: ReactNode;
19
+ }
20
+
21
+ export const NobeliumStoreProvider = ({
22
+ children,
23
+ }: NobeliumStoreProviderProps) => {
24
+ const storeRef = useRef<NobeliumStoreApi>();
25
+ if (!storeRef.current) {
26
+ storeRef.current = createNobeliumStore();
27
+ }
28
+
29
+ return (
30
+ <NobeliumStoreContext.Provider value={storeRef.current}>
31
+ {children}
32
+ </NobeliumStoreContext.Provider>
33
+ );
34
+ };
35
+
36
+ export const useNobeliumStore = <T,>(
37
+ selector: (store: NobeliumStore) => T,
38
+ ): T => {
39
+ const nobeliumStoreContext = useContext(NobeliumStoreContext);
40
+
41
+ if (!nobeliumStoreContext) {
42
+ throw new Error(
43
+ `useNobeliumStore must be used within NobeliumStoreProvider`,
44
+ );
45
+ }
46
+
47
+ return useStore(nobeliumStoreContext, selector);
48
+ };
49
+
50
+ export const ContextWrapper = <P extends object>(
51
+ Component: React.ComponentType<P>,
52
+ ): React.FC<P> => {
53
+ const WrappedComponent: React.FC<P> = (props) => (
54
+ <NobeliumStoreProvider>
55
+ <Component {...props} />
56
+ </NobeliumStoreProvider>
57
+ );
58
+
59
+ return WrappedComponent;
60
+ };
@@ -0,0 +1,42 @@
1
+ import { createStore } from 'zustand';
2
+
3
+ import { SiteInfo, PageInfo } from '@/lib/notion/types';
4
+
5
+ export interface NobeliumState {
6
+ siteInfo: SiteInfo;
7
+ latestPosts: PageInfo[];
8
+ }
9
+
10
+ export interface NobeliumAction {
11
+ updateSiteInfo: (siteInfo: NobeliumStore['siteInfo']) => void;
12
+ updateLatestPosts: (latestPosts: NobeliumStore['latestPosts']) => void;
13
+ }
14
+
15
+ export type NobeliumStore = NobeliumState & NobeliumAction;
16
+
17
+ export const defaultInitState: NobeliumState = {
18
+ siteInfo: {
19
+ title: '',
20
+ description: '',
21
+ pageCover: '',
22
+ icon: '',
23
+ },
24
+ latestPosts: [],
25
+ };
26
+
27
+ export const createNobeliumStore = (
28
+ initState: NobeliumState = defaultInitState,
29
+ ) => {
30
+ return createStore<NobeliumStore>()((set) => ({
31
+ siteInfo: initState.siteInfo,
32
+ latestPosts: initState.latestPosts,
33
+ updateSiteInfo: (siteInfo) =>
34
+ set(() => ({
35
+ siteInfo: siteInfo,
36
+ })),
37
+ updateLatestPosts: (latestPosts) =>
38
+ set(() => ({
39
+ latestPosts: latestPosts,
40
+ })),
41
+ }));
42
+ };
@@ -0,0 +1,17 @@
1
+ const CONFIG = {
2
+ NAV_NOTION_ICON: true, // 是否读取Notion图标作为站点头像 ; 否则默认显示黑色SVG方块
3
+
4
+ // 特殊菜单
5
+ MENU_RANDOM_POST: true, // 是否显示随机跳转文章按钮
6
+ MENU_SEARCH_BUTTON: false, // 是否显示搜索按钮,该按钮支持Algolia搜索
7
+ MENU_LANGUAGE_SWITCH: true,
8
+ MENU_DARK_MODE_SWITCH: true,
9
+
10
+ // 默认菜单配置 (开启自定义菜单后,以下配置则失效,请在Notion中自行配置菜单)
11
+ MENU_CATEGORY: true, // 显示分类
12
+ MENU_TAG: true, // 显示标签
13
+ MENU_ARCHIVE: true, // 显示归档
14
+ MENU_SEARCH: true, // 显示搜索
15
+ MENU_RSS: true, // 显示订阅
16
+ };
17
+ export default CONFIG;
@@ -0,0 +1,10 @@
1
+ export type NavLink = {
2
+ id?: string;
3
+ icon?: string;
4
+ name: string;
5
+ to: string;
6
+ show: boolean;
7
+ target?: string;
8
+ subMenus?: NavLink[];
9
+ title?: '';
10
+ };
package/tsconfig.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "baseUrl": ".",
5
+ "lib": ["dom", "dom.iterable", "esnext"],
6
+ "allowJs": true,
7
+ "skipLibCheck": true,
8
+ "strict": true,
9
+ "noEmit": true,
10
+ "esModuleInterop": true,
11
+ "module": "esnext",
12
+ "moduleResolution": "node",
13
+ "resolveJsonModule": true,
14
+ "isolatedModules": true,
15
+ "jsx": "preserve",
16
+ "incremental": true,
17
+ "plugins": [
18
+ {
19
+ "name": "next"
20
+ }
21
+ ],
22
+ "paths": {
23
+ "@/*": ["./*"]
24
+ },
25
+ "forceConsistentCasingInFileNames": true
26
+ },
27
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "**/*.d.ts"],
28
+ "exclude": ["node_modules"]
29
+ }
package/types/index.ts ADDED
@@ -0,0 +1 @@
1
+ export * from './page';