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,83 @@
1
+ import Link from 'next/link';
2
+ import { useState } from 'react';
3
+ import useToggleClickOutSide from '@/hooks/useToggleClickOutSide';
4
+ import { useTranslation } from 'next-i18next';
5
+ import { useRef } from 'react';
6
+
7
+ import type { FC } from 'react';
8
+ import type { NavLink } from '../types';
9
+
10
+ export interface MenuItemDropProps {
11
+ link: NavLink;
12
+ }
13
+ export const MenuItemDrop: FC<MenuItemDropProps> = ({ link }) => {
14
+ const [show, changeShow] = useState(false);
15
+ const menuRef = useRef(null);
16
+ const { t } = useTranslation('nav');
17
+
18
+ useToggleClickOutSide(menuRef, () => {
19
+ changeShow(false);
20
+ });
21
+
22
+ const toggleShow = () => {
23
+ changeShow(!show);
24
+ };
25
+
26
+ if (!link || !link.show) {
27
+ return null;
28
+ }
29
+
30
+ const hasSubMenu = link.subMenus && link?.subMenus.length > 0;
31
+
32
+ return (
33
+ <div
34
+ className="relative cursor-pointer rounded-full px-3 py-2 hover:bg-black hover:bg-opacity-10 dark:hover:bg-white dark:hover:bg-opacity-10"
35
+ ref={menuRef}
36
+ >
37
+ {!hasSubMenu && (
38
+ <Link
39
+ className={`block text-black dark:text-gray-50`}
40
+ href={link?.to}
41
+ target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'}
42
+ >
43
+ {link?.icon && <i className={link?.icon} />} {t(link.name)}
44
+ </Link>
45
+ )}
46
+
47
+ {hasSubMenu && (
48
+ <div
49
+ className={`block text-black dark:text-gray-50`}
50
+ onClick={toggleShow}
51
+ >
52
+ {link?.icon && <i className={link?.icon} />} {link?.name}
53
+ <i
54
+ className={`fas fa-chevron-down px-2 transition-all duration-500 ${show ? ' rotate-180' : ''}`}
55
+ ></i>
56
+ </div>
57
+ )}
58
+
59
+ {/* 子菜单 */}
60
+ {hasSubMenu && (
61
+ <div
62
+ className={`${show ? 'visible top-10 opacity-100 ' : 'invisible top-8 opacity-0 '} absolute z-20 block w-40 rounded border border-gray-100 bg-white drop-shadow-lg transition-all duration-300 dark:border-gray-800 dark:bg-black`}
63
+ >
64
+ {link.subMenus?.map((sLink) => {
65
+ return (
66
+ <Link
67
+ key={sLink.id}
68
+ className="p-3 text-gray-700 transition-all duration-200 hover:bg-gray-50 dark:border-gray-800 dark:text-gray-200 dark:hover:bg-gray-900"
69
+ href={sLink.to}
70
+ target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'}
71
+ >
72
+ <span className="text-nowrap">
73
+ {link?.icon && <i className={sLink?.icon}> &nbsp; </i>}
74
+ {sLink.title}
75
+ </span>
76
+ </Link>
77
+ );
78
+ })}
79
+ </div>
80
+ )}
81
+ </div>
82
+ );
83
+ };
@@ -0,0 +1,50 @@
1
+ .stickyNav {
2
+ position: sticky;
3
+ z-index: 10;
4
+ top: 0;
5
+ backdrop-filter: blur(5px);
6
+ transition: all 0.5s cubic-bezier(0.4, 0, 0, 1);
7
+ border-bottom-color: transparent;
8
+ }
9
+
10
+ .stickyNavFull {
11
+ @apply border-b border-gray-200 border-opacity-50 dark:border-gray-600 dark:border-opacity-50;
12
+ }
13
+
14
+ .article-tags::-webkit-scrollbar {
15
+ width: 0 !important;
16
+ }
17
+
18
+ .tag-container ul::-webkit-scrollbar {
19
+ width: 0 !important;
20
+ }
21
+
22
+ .tag-container ul {
23
+ -ms-overflow-style: none;
24
+ overflow: -moz-scrollbars-none;
25
+ -moz-user-select: none;
26
+ -webkit-user-select: none;
27
+ -ms-user-select: none;
28
+ -khtml-user-select: none;
29
+ user-select: none;
30
+ }
31
+
32
+ @media (min-width: 768px) {
33
+ .stickyNavFull {
34
+ @apply max-w-full border-b border-gray-200 border-opacity-50 dark:border-gray-600 dark:border-opacity-50;
35
+ }
36
+ .headerName {
37
+ display: block;
38
+ transition: all 0.5s cubic-bezier(0.4, 0, 0, 1);
39
+ }
40
+ .stickyNavFull .headerName {
41
+ @apply text-gray-600 dark:text-gray-300;
42
+ }
43
+ }
44
+
45
+ @supports not (backdrop-filter: none) {
46
+ .stickyNav {
47
+ backdrop-filter: none;
48
+ @apply bg-day dark:bg-gray-800;
49
+ }
50
+ }
@@ -0,0 +1,187 @@
1
+ import { useEffect, useRef, useState } from 'react';
2
+ import Link from 'next/link';
3
+ import BLOG from '@/blog.config';
4
+ import CONFIG from '../../theme.config';
5
+ import { SvgIcon } from '../SvgIcon';
6
+ import { MenuItemDrop } from '../MenuItemDrop';
7
+ import Collapse from '@/components/Collapse';
8
+ import { MenuItemCollapse } from '../MenuItemCollapse';
9
+ import LazyImage from '@/components/LazyImage';
10
+ import DarkModeButton from '../DarkModeButton';
11
+ import RandomPostButton from '../RandomPostButton';
12
+ import SearchButton from '../SearchButton';
13
+ import LanguageSwitchButton from '../LanguageSwitchButton';
14
+ import useToggleClickOutSide from '@/hooks/useToggleClickOutSide';
15
+ import { useNobeliumStore } from '../../providers';
16
+ import styles from './Nav.module.css';
17
+
18
+ import type { FC } from 'react';
19
+ import type { NavLink } from '../../types';
20
+ import type { CollapseHandle } from '@/components/Collapse';
21
+
22
+ const Nav: FC = () => {
23
+ const navRef = useRef<HTMLDivElement>(null);
24
+ const sentinelRef = useRef<HTMLDivElement>(null);
25
+ const { siteInfo } = useNobeliumStore((state) => state);
26
+
27
+ const handler: IntersectionObserverCallback = ([entry]) => {
28
+ if (navRef && navRef.current) {
29
+ if (!entry.isIntersecting && entry !== undefined) {
30
+ navRef.current?.classList.add(styles.stickyNavFull);
31
+ } else {
32
+ navRef.current?.classList.remove(styles.stickyNavFull);
33
+ }
34
+ } else {
35
+ navRef.current?.classList.add('remove-sticky');
36
+ }
37
+ };
38
+
39
+ useEffect(() => {
40
+ const observer = new window.IntersectionObserver(handler);
41
+ const sentinelRefCurrent = sentinelRef.current;
42
+ if (sentinelRefCurrent) observer.observe(sentinelRefCurrent);
43
+ return () => {
44
+ if (sentinelRefCurrent) observer.unobserve(sentinelRefCurrent);
45
+ };
46
+ }, [sentinelRef]);
47
+
48
+ return (
49
+ <>
50
+ <div className="h-4 md:h-12" ref={sentinelRef} />
51
+ <div
52
+ className={`${styles.stickyNav} m-auto mb-2 flex h-6 w-full max-w-3xl flex-row items-center justify-between bg-opacity-60 px-4 py-8 md:mb-12`}
53
+ ref={navRef}
54
+ >
55
+ <Link
56
+ className="flex items-center rounded-full px-3 py-2 hover:bg-black hover:bg-opacity-10 dark:hover:bg-white dark:hover:bg-opacity-10"
57
+ href="/"
58
+ aria-label={BLOG.TITLE}
59
+ >
60
+ <div className="h-6 w-6">
61
+ {/* <SvgIcon/> */}
62
+ {CONFIG.NAV_NOTION_ICON ? (
63
+ <LazyImage
64
+ src={siteInfo?.icon}
65
+ width={24}
66
+ height={24}
67
+ alt={BLOG.AUTHOR}
68
+ />
69
+ ) : (
70
+ <SvgIcon />
71
+ )}
72
+ </div>
73
+ <p
74
+ className={`${styles.headerName} ml-2 overflow-hidden font-medium text-gray-800 dark:text-gray-300`}
75
+ >
76
+ {siteInfo?.title}
77
+ </p>
78
+ </Link>
79
+ <NavBar />
80
+ </div>
81
+ </>
82
+ );
83
+ };
84
+
85
+ const NavBar: FC = () => {
86
+ const [isOpen, changeOpen] = useState(false);
87
+ const collapseRef = useRef<CollapseHandle>(null);
88
+ const mobileMenuRef = useRef(null);
89
+ const mobileMenuToggleButtonRef = useRef(null);
90
+
91
+ const toggleOpen = () => {
92
+ changeOpen(!isOpen);
93
+ };
94
+
95
+ useToggleClickOutSide([mobileMenuRef, mobileMenuToggleButtonRef], () => {
96
+ changeOpen(false);
97
+ });
98
+
99
+ const links: NavLink[] = [
100
+ {
101
+ id: 'rss',
102
+ icon: 'fas fa-rss',
103
+ name: 'rss',
104
+ to: '/feed',
105
+ show: !!(BLOG.ENABLE_RSS && CONFIG.MENU_RSS),
106
+ target: '_blank',
107
+ },
108
+ {
109
+ id: 'search',
110
+ icon: 'fas fa-search',
111
+ name: 'search',
112
+ to: '/search',
113
+ show: CONFIG.MENU_SEARCH,
114
+ },
115
+ {
116
+ id: 'archive',
117
+ icon: 'fas fa-archive',
118
+ name: 'archive',
119
+ to: '/archive',
120
+ show: CONFIG.MENU_ARCHIVE,
121
+ },
122
+ {
123
+ id: 'category',
124
+ icon: 'fas fa-folder',
125
+ name: 'category',
126
+ to: '/category',
127
+ show: CONFIG.MENU_CATEGORY,
128
+ },
129
+ {
130
+ id: 'tags',
131
+ icon: 'fas fa-tag',
132
+ name: 'tags',
133
+ to: '/tag',
134
+ show: CONFIG.MENU_TAG,
135
+ },
136
+ ];
137
+
138
+ if (!links || links.length === 0) {
139
+ return null;
140
+ }
141
+
142
+ return (
143
+ <div className="flex flex-shrink-0">
144
+ <ul className="hidden flex-row md:flex">
145
+ {links.map((link) => (
146
+ <MenuItemDrop key={link?.id} link={link} />
147
+ ))}
148
+ </ul>
149
+
150
+ {CONFIG.MENU_RANDOM_POST && <RandomPostButton />}
151
+ {CONFIG.MENU_SEARCH_BUTTON && <SearchButton />}
152
+ {CONFIG.MENU_LANGUAGE_SWITCH && <LanguageSwitchButton />}
153
+ {CONFIG.MENU_DARK_MODE_SWITCH && <DarkModeButton />}
154
+
155
+ {/* 移动端菜单按钮 */}
156
+ <div
157
+ onClick={toggleOpen}
158
+ ref={mobileMenuToggleButtonRef}
159
+ className="flex h-10 w-10 cursor-pointer items-center justify-center md:hidden"
160
+ >
161
+ <i className="fas fa-bars"></i>
162
+ </div>
163
+
164
+ {/* 移动端使用的菜单 */}
165
+ <div className="md:hidden" ref={mobileMenuRef}>
166
+ <Collapse
167
+ collapseRef={collapseRef}
168
+ isOpen={isOpen}
169
+ type="vertical"
170
+ className="fixed right-6 top-16"
171
+ >
172
+ <div className="flex w-48 flex-col rounded border bg-white py-2 text-sm dark:border-black dark:bg-black">
173
+ {links.map((link) => (
174
+ <MenuItemCollapse
175
+ key={link?.id}
176
+ link={link}
177
+ onHeightChange={collapseRef.current?.updateCollapseHeight}
178
+ />
179
+ ))}
180
+ </div>
181
+ </Collapse>
182
+ </div>
183
+ </div>
184
+ );
185
+ };
186
+
187
+ export default Nav;
@@ -0,0 +1,31 @@
1
+ import BLOG from '@/blog.config';
2
+ import { useRouter } from 'next/router';
3
+ import { useTranslation } from 'next-i18next';
4
+ import { useNobeliumStore } from '../providers';
5
+
6
+ /**
7
+ * 随机跳转到一个文章
8
+ */
9
+ export default function RandomPostButton() {
10
+ const router = useRouter();
11
+ const { t } = useTranslation('menu');
12
+ const { latestPosts } = useNobeliumStore((state) => state);
13
+ /**
14
+ * 随机跳转文章
15
+ */
16
+ function handleClick() {
17
+ const randomIndex = Math.floor(Math.random() * latestPosts.length);
18
+ const randomPost = latestPosts[randomIndex];
19
+ router.push(`${BLOG.SUB_PATH}/${randomPost?.slug}`);
20
+ }
21
+
22
+ return (
23
+ <div
24
+ title={t('walk-around')}
25
+ className="flex h-10 w-10 cursor-pointer items-center justify-center rounded-full hover:bg-black hover:bg-opacity-10 dark:hover:bg-white dark:hover:bg-opacity-10"
26
+ onClick={handleClick}
27
+ >
28
+ <i className="fa-solid fa-podcast"></i>
29
+ </div>
30
+ );
31
+ }
@@ -0,0 +1,31 @@
1
+ // import BLOG from '@/blog.config';
2
+ import { useRouter } from 'next/router';
3
+ import { useTranslation } from 'next-i18next';
4
+
5
+ /**
6
+ * 搜索按钮
7
+ * @returns
8
+ */
9
+ export default function SearchButton() {
10
+ // const { searchModal } = useNobeliumGlobal();
11
+ const { t } = useTranslation('nav');
12
+ const router = useRouter();
13
+
14
+ function handleSearch() {
15
+ // if (BLOG.ALGOLIA_APP_ID) {
16
+ // searchModal.current.openSearch();
17
+ // } else {
18
+ router.push('/search');
19
+ // }
20
+ }
21
+
22
+ return (
23
+ <div
24
+ onClick={handleSearch}
25
+ title={t('search')}
26
+ className="flex h-10 w-10 cursor-pointer items-center justify-center rounded-full hover:bg-black hover:bg-opacity-10 dark:hover:bg-white dark:hover:bg-opacity-10"
27
+ >
28
+ <i title={t('search')} className="fa-solid fa-magnifying-glass" />
29
+ </div>
30
+ );
31
+ }
@@ -0,0 +1,94 @@
1
+ import { useRouter } from 'next/router';
2
+ import { type FC, useRef, useState } from 'react';
3
+ import { useTranslation } from 'next-i18next';
4
+
5
+ export interface SearchInputProps {
6
+ tag: string;
7
+ keyword: string;
8
+ }
9
+
10
+ const SearchInput: FC<SearchInputProps> = ({ tag, keyword }) => {
11
+ const router = useRouter();
12
+ const { t } = useTranslation('common');
13
+ const [showClean, setShowClean] = useState(false);
14
+ const searchInputRef = useRef<HTMLInputElement>(null);
15
+ const lock = useRef(false);
16
+
17
+ const handleSearch = () => {
18
+ const key = searchInputRef.current?.value.trim();
19
+ if (key) {
20
+ router.push(`/search/${key}`);
21
+ } else {
22
+ router.push('/');
23
+ }
24
+ };
25
+
26
+ const handleKeyUp = (e: React.KeyboardEvent<HTMLInputElement>) => {
27
+ if (e.key === 'Enter') handleSearch();
28
+ else if (e.key === 'Escape') cleanSearch();
29
+ };
30
+
31
+ const cleanSearch = () => {
32
+ (searchInputRef.current as HTMLInputElement).value = '';
33
+ setShowClean(false);
34
+ };
35
+
36
+ function lockSearchInput() {
37
+ lock.current = true;
38
+ }
39
+
40
+ function unLockSearchInput() {
41
+ lock.current = false;
42
+ }
43
+ const updateSearchKey = () => {
44
+ if (lock.current) {
45
+ return;
46
+ }
47
+ if (searchInputRef.current?.value) {
48
+ setShowClean(true);
49
+ } else {
50
+ setShowClean(false);
51
+ }
52
+ };
53
+
54
+ return (
55
+ <section className="flex w-full bg-gray-100">
56
+ <input
57
+ ref={searchInputRef}
58
+ type="text"
59
+ placeholder={tag ? `${t('tags')} #${tag}` : `${t('search-articles')}`}
60
+ className={
61
+ 'w-full bg-gray-100 pl-4 text-sm font-light leading-10 text-black outline-none transition focus:shadow-lg dark:bg-gray-900 dark:text-white'
62
+ }
63
+ onKeyUp={handleKeyUp}
64
+ onCompositionStart={lockSearchInput}
65
+ onCompositionUpdate={lockSearchInput}
66
+ onCompositionEnd={unLockSearchInput}
67
+ onChange={updateSearchKey}
68
+ defaultValue={keyword || ''}
69
+ />
70
+
71
+ <div
72
+ className="float-right -ml-8 cursor-pointer items-center justify-center py-2"
73
+ onClick={handleSearch}
74
+ >
75
+ <i
76
+ className={
77
+ 'fas fa-search transform cursor-pointer text-gray-500 duration-200 hover:text-black'
78
+ }
79
+ />
80
+ </div>
81
+
82
+ {showClean && (
83
+ <div className="float-right -ml-12 cursor-pointer items-center justify-center py-2 dark:bg-gray-600 dark:hover:bg-gray-800">
84
+ <i
85
+ className="fas fa-times transform cursor-pointer text-gray-400 duration-200 hover:text-black"
86
+ onClick={cleanSearch}
87
+ />
88
+ </div>
89
+ )}
90
+ </section>
91
+ );
92
+ };
93
+
94
+ export default SearchInput;
@@ -0,0 +1,19 @@
1
+ import SearchInput from './SearchInput';
2
+ import Tags from './Tags';
3
+
4
+ /**
5
+ * 搜索页面上方嵌入内容
6
+ * @param {*} props
7
+ * @returns
8
+ */
9
+ export default function SearchNavBar(props) {
10
+ return (
11
+ <>
12
+ <div className="pb-12">
13
+ <SearchInput {...props} />
14
+ </div>
15
+
16
+ <Tags tagOptions={props.tagOptions} tag={props.keyword} />
17
+ </>
18
+ );
19
+ }
@@ -0,0 +1,83 @@
1
+ import BLOG from '@/blog.config';
2
+ import Live2D from '@/components/Live2D';
3
+ import Link from 'next/link';
4
+ import dynamic from 'next/dynamic';
5
+ import { useTranslation } from 'next-i18next';
6
+
7
+ const ExampleRecentComments = dynamic(() => import('./ExampleRecentComments'));
8
+
9
+ export const SideBar = (props) => {
10
+ const { latestPosts, categories } = props;
11
+ const { t } = useTranslation('common');
12
+
13
+ return (
14
+ <div className="sticky top-8 w-full md:w-64">
15
+ <aside className="mb-6 overflow-hidden rounded shadow">
16
+ <h3 className="border-b bg-gray-100 px-4 py-3 text-sm text-gray-700 dark:border-hexo-black-gray dark:bg-hexo-black-gray dark:text-gray-200">
17
+ {t('category')}
18
+ </h3>
19
+
20
+ <div className="p-4">
21
+ <ul className="list-reset leading-normal">
22
+ {categories?.map((category) => {
23
+ return (
24
+ <Link
25
+ key={category.name}
26
+ href={`/category/${category.name}`}
27
+ passHref
28
+ legacyBehavior
29
+ >
30
+ <li>
31
+ {' '}
32
+ <a href="#" className="text-gray-darkest text-sm">
33
+ {category.name}({category.count})
34
+ </a>
35
+ </li>
36
+ </Link>
37
+ );
38
+ })}
39
+ </ul>
40
+ </div>
41
+ </aside>
42
+
43
+ <aside className="mb-6 overflow-hidden rounded shadow">
44
+ <h3 className="border-b bg-gray-100 px-4 py-3 text-sm text-gray-700 dark:border-hexo-black-gray dark:bg-hexo-black-gray dark:text-gray-200">
45
+ {t('latest-posts')}
46
+ </h3>
47
+
48
+ <div className="p-4">
49
+ <ul className="list-reset leading-normal">
50
+ {latestPosts?.map((p) => {
51
+ return (
52
+ <Link key={p.id} href={`/${p.slug}`} passHref legacyBehavior>
53
+ <li>
54
+ {' '}
55
+ <a href="#" className="text-gray-darkest text-sm">
56
+ {p.title}
57
+ </a>
58
+ </li>
59
+ </Link>
60
+ );
61
+ })}
62
+ </ul>
63
+ </div>
64
+ </aside>
65
+
66
+ {BLOG.COMMENT_WALINE_SERVER_URL && BLOG.COMMENT_WALINE_RECENT && (
67
+ <aside className="mb-6 overflow-hidden rounded shadow">
68
+ <h3 className="border-b bg-gray-100 px-4 py-3 text-sm text-gray-700 dark:border-hexo-black-gray dark:bg-hexo-black-gray dark:text-gray-200">
69
+ {t('recent-comments')}
70
+ </h3>
71
+
72
+ <div className="p-4">
73
+ <ExampleRecentComments />
74
+ </div>
75
+ </aside>
76
+ )}
77
+
78
+ <aside className="mb-6 overflow-hidden rounded">
79
+ <Live2D />
80
+ </aside>
81
+ </div>
82
+ );
83
+ };
@@ -0,0 +1,29 @@
1
+ export const SvgIcon = () => {
2
+ return <svg
3
+ width="24"
4
+ height="24"
5
+ viewBox="0 0 24 24"
6
+ fill="none"
7
+ xmlns="http://www.w3.org/2000/svg"
8
+ >
9
+ <rect
10
+ width="24"
11
+ height="24"
12
+ className="fill-current text-black dark:text-white"
13
+ />
14
+ <rect width="24" height="24" fill="url(#paint0_radial)" />
15
+ <defs>
16
+ <radialGradient
17
+ id="paint0_radial"
18
+ cx="0"
19
+ cy="0"
20
+ r="1"
21
+ gradientUnits="userSpaceOnUse"
22
+ gradientTransform="rotate(45) scale(39.598)"
23
+ >
24
+ <stop stopColor="#CFCFCF" stopOpacity="0.6" />
25
+ <stop offset="1" stopColor="#E9E9E9" stopOpacity="0" />
26
+ </radialGradient>
27
+ </defs>
28
+ </svg>
29
+ }
@@ -0,0 +1,13 @@
1
+ import Link from 'next/link'
2
+
3
+ const TagItem = ({ tag }) => (
4
+ (<Link href={`/tag/${encodeURIComponent(tag)}`}>
5
+
6
+ <p className="mr-1 rounded-full px-2 py-1 border leading-none text-sm dark:border-gray-600">
7
+ {tag}
8
+ </p>
9
+
10
+ </Link>)
11
+ )
12
+
13
+ export default TagItem
@@ -0,0 +1,44 @@
1
+ import Link from 'next/link';
2
+ import type { TagInfo } from '@/lib/notion/types';
3
+ import type { FC } from 'react';
4
+
5
+ export interface TagsProps {
6
+ tagOptions: TagInfo[];
7
+ tag: string;
8
+ }
9
+
10
+ const Tags: FC<TagsProps> = ({ tagOptions, tag: currentTag }) => {
11
+ if (!tagOptions) return null;
12
+ return (
13
+ <div className="tag-container">
14
+ <ul className="mt-4 flex max-w-full overflow-x-auto">
15
+ {tagOptions.map((tag) => {
16
+ return (
17
+ <li
18
+ key={tag.id}
19
+ className={`mr-3 whitespace-nowrap border font-medium dark:text-gray-300 ${
20
+ tag.name === currentTag
21
+ ? 'border-black bg-black text-white dark:border-gray-600 dark:bg-gray-600'
22
+ : 'border-gray-100 bg-gray-100 text-gray-400 dark:border-gray-800 dark:bg-night'
23
+ }`}
24
+ >
25
+ <Link
26
+ key={tag.id}
27
+ href={
28
+ tag.name === currentTag
29
+ ? '/search'
30
+ : `/tag/${encodeURIComponent(tag.name)}`
31
+ }
32
+ className="block px-4 py-2"
33
+ >
34
+ {`${tag.name} (${tag.count})`}
35
+ </Link>
36
+ </li>
37
+ );
38
+ })}
39
+ </ul>
40
+ </div>
41
+ );
42
+ };
43
+
44
+ export default Tags;
@@ -0,0 +1,19 @@
1
+ import BLOG from '@/blog.config'
2
+
3
+ /**
4
+ * 标题栏
5
+ * @param {*} props
6
+ * @returns
7
+ */
8
+ export const Title = (props) => {
9
+ const { siteInfo, post } = props
10
+ const title = post?.title || siteInfo?.description
11
+ const description = post?.description || BLOG.AUTHOR
12
+
13
+ return <div className="text-center px-6 py-12 mb-6 bg-gray-100 dark:bg-hexo-black-gray dark:border-hexo-black-gray border-b">
14
+ <h1 className=" text-xl md:text-4xl pb-4">{title}</h1>
15
+ <p className="leading-loose text-gray-dark">
16
+ {description}
17
+ </p>
18
+ </div>
19
+ }