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,101 @@
1
+ import BLOG from '@/blog.config';
2
+ import Head from 'next/head';
3
+
4
+ import type { FC } from 'react';
5
+ import type { PageMeta } from '@/types';
6
+
7
+ export interface CommonHeadProps {
8
+ pageMeta?: PageMeta;
9
+ children?: React.ReactNode;
10
+ }
11
+
12
+ const CommonHead: FC<CommonHeadProps> = ({ pageMeta, children }) => {
13
+ let url = BLOG.SUB_PATH?.length ? `${BLOG.LINK}/${BLOG.SUB_PATH}` : BLOG.LINK;
14
+ let image;
15
+ if (pageMeta) {
16
+ url = `${url}/${pageMeta.slug}`;
17
+ image = pageMeta.image || '/bg_image.jpg';
18
+ }
19
+ const title = pageMeta?.title || BLOG.TITLE;
20
+ const description = pageMeta?.description || BLOG.DESCRIPTION;
21
+ const type = pageMeta?.type || 'website';
22
+ const keywords = pageMeta?.tags?.join(',') || BLOG.KEYWORDS;
23
+ const lang = BLOG.LANG.replace('-', '_'); // Facebook OpenGraph 要 zh_CN 這樣的格式才抓得到語言
24
+ const category = pageMeta?.category || BLOG.KEYWORDS || '軟體科技'; // section 主要是像是 category 這樣的分類,Facebook 用這個來抓連結的分類
25
+
26
+ return (
27
+ <Head>
28
+ <title>{title}</title>
29
+ <meta name="theme-color" content={BLOG.BACKGROUND_DARK} />
30
+ <meta
31
+ name="viewport"
32
+ content="width=device-width, initial-scale=1.0, maximum-scale=5.0, minimum-scale=1.0"
33
+ />
34
+ <meta name="robots" content="follow, index" />
35
+ <meta charSet="UTF-8" />
36
+ {BLOG.SEO_GOOGLE_SITE_VERIFICATION && (
37
+ <meta
38
+ name="google-site-verification"
39
+ content={BLOG.SEO_GOOGLE_SITE_VERIFICATION}
40
+ />
41
+ )}
42
+ {BLOG.SEO_BAIDU_SITE_VERIFICATION && (
43
+ <meta
44
+ name="baidu-site-verification"
45
+ content={BLOG.SEO_BAIDU_SITE_VERIFICATION}
46
+ />
47
+ )}
48
+ <meta name="keywords" content={keywords} />
49
+ <meta name="description" content={description} />
50
+ <meta property="og:locale" content={lang} />
51
+ <meta property="og:title" content={title} />
52
+ <meta property="og:description" content={description} />
53
+ <meta property="og:url" content={url} />
54
+ <meta property="og:image" content={image} />
55
+ <meta property="og:site_name" content={BLOG.TITLE} />
56
+ <meta property="og:type" content={type} />
57
+ <meta name="twitter:card" content="summary_large_image" />
58
+ <meta name="twitter:description" content={description} />
59
+ <meta name="twitter:title" content={title} />
60
+
61
+ {BLOG.COMMENT_WEBMENTION.ENABLE && (
62
+ <>
63
+ <link
64
+ rel="webmention"
65
+ href={`https://webmention.io/${BLOG.COMMENT_WEBMENTION.HOSTNAME}/webmention`}
66
+ />
67
+ <link
68
+ rel="pingback"
69
+ href={`https://webmention.io/${BLOG.COMMENT_WEBMENTION.HOSTNAME}/xmlrpc`}
70
+ />
71
+ </>
72
+ )}
73
+
74
+ {BLOG.COMMENT_WEBMENTION.ENABLE &&
75
+ BLOG.COMMENT_WEBMENTION.AUTH !== '' && (
76
+ <link href={BLOG.COMMENT_WEBMENTION.AUTH} rel="me" />
77
+ )}
78
+
79
+ {BLOG.ANALYTICS_BUSUANZI_ENABLE && (
80
+ <meta name="referrer" content="no-referrer-when-downgrade" />
81
+ )}
82
+ {pageMeta?.type === 'Post' && (
83
+ <>
84
+ <meta
85
+ property="article:published_time"
86
+ content={pageMeta?.publishDay}
87
+ />
88
+ <meta property="article:author" content={BLOG.AUTHOR} />
89
+ <meta property="article:section" content={category} />
90
+ <meta
91
+ property="article:publisher"
92
+ content={BLOG.FACEBOOK_PAGE || ''}
93
+ />
94
+ </>
95
+ )}
96
+ {children}
97
+ </Head>
98
+ );
99
+ };
100
+
101
+ export default CommonHead;
@@ -0,0 +1,125 @@
1
+ import BLOG from '@/blog.config'
2
+
3
+ /**
4
+ * 第三方代码 统计脚本
5
+ * @returns {JSX.Element}
6
+ * @constructor
7
+ */
8
+ const CommonScript = () => {
9
+ return (<>
10
+
11
+ {BLOG.CHATBASE_ID && (<>
12
+ <script id={BLOG.CHATBASE_ID} src="https://www.chatbase.co/embed.min.js" defer/>
13
+ <script async dangerouslySetInnerHTML={{
14
+ __html: `
15
+ window.chatbaseConfig = {
16
+ chatbotId: "${BLOG.CHATBASE_ID}",
17
+ }
18
+ `
19
+ }}/>
20
+ </>)}
21
+
22
+ {BLOG.COMMENT_DAO_VOICE_ID && (<>
23
+ {/* DaoVoice 反馈 */}
24
+ <script async dangerouslySetInnerHTML={{
25
+ __html: `
26
+ (function(i,s,o,g,r,a,m){i["DaoVoiceObject"]=r;i[r]=i[r]||function(){(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;a.charset="utf-8";m.parentNode.insertBefore(a,m)})(window,document,"script",('https:' == document.location.protocol ? 'https:' : 'http:') + "//widget.daovoice.io/widget/daf1a94b.js","daovoice")
27
+ `
28
+ }}
29
+ />
30
+ <script async dangerouslySetInnerHTML={{
31
+ __html: `
32
+ daovoice('init', {
33
+ app_id: "${BLOG.COMMENT_DAO_VOICE_ID}"
34
+ });
35
+ daovoice('update');
36
+ `
37
+ }}
38
+ />
39
+ </>)}
40
+
41
+ {BLOG.AD_WWADS_ID && <script type="text/javascript" charSet="UTF-8" src="https://cdn.wwads.cn/js/makemoney.js" async></script>}
42
+
43
+ {BLOG.COMMENT_CUSDIS_APP_ID && <script defer src={`https://cusdis.com/js/widget/lang/${BLOG.LANG.toLowerCase()}.js`} />}
44
+
45
+ {BLOG.COMMENT_TWIKOO_ENV_ID && <script defer src={BLOG.COMMENT_TWIKOO_CDN_URL}/> }
46
+
47
+ {BLOG.COMMENT_ARTALK_SERVER && <script defer src={BLOG.COMMENT_ARTALK_JS}/> }
48
+
49
+ {BLOG.COMMENT_TIDIO_ID && <script async src={`//code.tidio.co/${BLOG.COMMENT_TIDIO_ID}.js`} />}
50
+
51
+ {/* gitter聊天室 */}
52
+ {BLOG.COMMENT_GITTER_ROOM && (<>
53
+ <script src="https://sidecar.gitter.im/dist/sidecar.v1.js" async defer/>
54
+ <script async dangerouslySetInnerHTML={{
55
+ __html: `
56
+ ((window.gitter = {}).chat = {}).options = {
57
+ room: '${BLOG.COMMENT_GITTER_ROOM}'
58
+ };
59
+ `
60
+ }}/>
61
+ </>)}
62
+
63
+ {/* 代码统计 */}
64
+ {/* ackee统计脚本 */}
65
+ {/* {BLOG.ANALYTICS_ACKEE_TRACKER && (
66
+ <script async src={BLOG.ANALYTICS_ACKEE_TRACKER}
67
+ data-ackee-server={BLOG.ANALYTICS_ACKEE_DATA_SERVER}
68
+ data-ackee-domain-id={BLOG.ANALYTICS_ACKEE_DOMAIN_ID}
69
+ />
70
+ )} */}
71
+
72
+ {/* 百度统计 */}
73
+ {BLOG.ANALYTICS_BAIDU_ID && (
74
+ <script async
75
+ dangerouslySetInnerHTML={{
76
+ __html: `
77
+ var _hmt = _hmt || [];
78
+ (function() {
79
+ var hm = document.createElement("script");
80
+ hm.src = "https://hm.baidu.com/hm.js?${BLOG.ANALYTICS_BAIDU_ID}";
81
+ var s = document.getElementsByTagName("script")[0];
82
+ s.parentNode.insertBefore(hm, s);
83
+ })();
84
+ `
85
+ }}
86
+ />
87
+ )}
88
+
89
+ {/* 站长统计 */}
90
+ {BLOG.ANALYTICS_CNZZ_ID && (
91
+ <script async
92
+ dangerouslySetInnerHTML={{
93
+ __html: `
94
+ document.write(unescape("%3Cspan style='display:none' id='cnzz_stat_icon_${BLOG.ANALYTICS_CNZZ_ID}'%3E%3C/span%3E%3Cscript src='https://s9.cnzz.com/z_stat.php%3Fid%3D${BLOG.ANALYTICS_CNZZ_ID}' type='text/javascript'%3E%3C/script%3E"));
95
+ `
96
+ }}
97
+ />
98
+ )}
99
+
100
+ {/* 谷歌统计 */}
101
+ {BLOG.ANALYTICS_GOOGLE_ID && (<>
102
+ <script async
103
+ src={`https://www.googletagmanager.com/gtag/js?id=${BLOG.ANALYTICS_GOOGLE_ID}`}
104
+ />
105
+ <script async
106
+ dangerouslySetInnerHTML={{
107
+ __html: `
108
+ window.dataLayer = window.dataLayer || [];
109
+ function gtag(){dataLayer.push(arguments);}
110
+ gtag('js', new Date());
111
+ gtag('config', '${BLOG.ANALYTICS_GOOGLE_ID}', {
112
+ page_path: window.location.pathname,
113
+ });
114
+ `
115
+ }}
116
+ />
117
+ </>)}
118
+
119
+ {/* 引入音乐播放 */}
120
+ {JSON.parse(BLOG.MUSIC_PLAYER) && <script async src={BLOG.MUSIC_PLAYER_CDN_URL} />}
121
+ {JSON.parse(BLOG.MUSIC_PLAYER) && JSON.parse(BLOG.MUSIC_PLAYER_METING) && <script async src="https://cdnjs.cloudflare.com/ajax/libs/meting/2.0.1/Meting.min.js" />}
122
+ </>)
123
+ }
124
+
125
+ export default CommonScript
@@ -0,0 +1,35 @@
1
+ import { useGlobal } from '@/lib/global';
2
+ import BLOG from '@/blog.config';
3
+ import { useRouter } from 'next/router';
4
+ import { useEffect } from 'react';
5
+ import { loadExternalResource } from '@/lib/utils';
6
+ import { useTranslation } from 'next-i18next';
7
+
8
+ const CusdisComponent = ({ frontMatter }) => {
9
+ const router = useRouter();
10
+ const { isDarkMode } = useGlobal();
11
+ const { i18n } = useTranslation();
12
+
13
+ // 处理cusdis主题
14
+ useEffect(() => {
15
+ loadExternalResource(BLOG.COMMENT_CUSDIS_SCRIPT_SRC, 'js').then(() => {
16
+ const CUSDIS = window.CUSDIS;
17
+ CUSDIS?.initial();
18
+ });
19
+ }, [isDarkMode]);
20
+
21
+ return (
22
+ <div
23
+ id="cusdis_thread"
24
+ lang={i18n.language}
25
+ data-host={BLOG.COMMENT_CUSDIS_HOST}
26
+ data-app-id={BLOG.COMMENT_CUSDIS_APP_ID}
27
+ data-page-id={frontMatter.id}
28
+ data-page-url={BLOG.LINK + router.asPath}
29
+ data-page-title={frontMatter.title}
30
+ data-theme={isDarkMode ? 'dark' : 'light'}
31
+ ></div>
32
+ );
33
+ };
34
+
35
+ export default CusdisComponent;
@@ -0,0 +1,221 @@
1
+ import Link from 'next/link';
2
+ import { useRouter } from 'next/router';
3
+ import { useEffect, useState, useRef, useLayoutEffect } from 'react';
4
+ import { useGlobal } from '@/lib/global';
5
+ import { saveDarkModeToLocalStorage, THEMES } from '@/theme';
6
+ import BLOG from '@/blog.config';
7
+ import useWindowSize from '@/hooks/useWindowSize';
8
+ import { useTranslation } from 'next-i18next';
9
+
10
+ /**
11
+ * 自定义右键菜单
12
+ * @param {*} props
13
+ * @returns
14
+ */
15
+ export default function CustomContextMenu(props) {
16
+ const [position, setPosition] = useState({ x: '0px', y: '0px' });
17
+ const [show, setShow] = useState(false);
18
+ const { isDarkMode, updateDarkMode } = useGlobal();
19
+ const menuRef = useRef(null);
20
+ const windowSize = useWindowSize();
21
+ const [width, setWidth] = useState(0);
22
+ const [height, setHeight] = useState(0);
23
+
24
+ const { latestPosts } = props;
25
+ const router = useRouter();
26
+ const { t } = useTranslation('menu');
27
+ /**
28
+ * 随机跳转文章
29
+ */
30
+ function handleJumpToRandomPost() {
31
+ const randomIndex = Math.floor(Math.random() * latestPosts.length);
32
+ const randomPost = latestPosts[randomIndex];
33
+ router.push(`${BLOG.SUB_PATH}/${randomPost?.slug}`);
34
+ }
35
+
36
+ useLayoutEffect(() => {
37
+ setWidth(menuRef.current.offsetWidth);
38
+ setHeight(menuRef.current.offsetHeight);
39
+ }, []);
40
+
41
+ useEffect(() => {
42
+ const handleContextMenu = (event) => {
43
+ event.preventDefault();
44
+ // 计算点击位置加菜单宽高是否超出屏幕,如果超出则贴边弹出
45
+ const x =
46
+ event.clientX < windowSize.width - width
47
+ ? event.clientX
48
+ : windowSize.width - width;
49
+ const y =
50
+ event.clientY < windowSize.height - height
51
+ ? event.clientY
52
+ : windowSize.height - height;
53
+ setPosition({ y: `${y}px`, x: `${x}px` });
54
+ setShow(true);
55
+ };
56
+
57
+ const handleClick = (event) => {
58
+ if (menuRef.current && !menuRef.current.contains(event.target)) {
59
+ setShow(false);
60
+ }
61
+ };
62
+
63
+ window.addEventListener('contextmenu', handleContextMenu);
64
+ window.addEventListener('click', handleClick);
65
+
66
+ return () => {
67
+ window.removeEventListener('contextmenu', handleContextMenu);
68
+ window.removeEventListener('click', handleClick);
69
+ };
70
+ }, [windowSize]);
71
+
72
+ function handleBack() {
73
+ window.history.back();
74
+ }
75
+
76
+ function handleForward() {
77
+ window.history.forward();
78
+ }
79
+
80
+ function handleRefresh() {
81
+ window.location.reload();
82
+ }
83
+
84
+ function handleScrollTop() {
85
+ window.scrollTo({ top: 0, behavior: 'smooth' });
86
+ setShow(false);
87
+ }
88
+
89
+ function handleCopyLink() {
90
+ const url = window.location.href;
91
+ navigator.clipboard
92
+ .writeText(url)
93
+ .then(() => {
94
+ console.log('页面地址已复制');
95
+ })
96
+ .catch((error) => {
97
+ console.error('复制页面地址失败:', error);
98
+ });
99
+ setShow(false);
100
+ }
101
+
102
+ /**
103
+ * 切换主题
104
+ */
105
+ function handeChangeTheme() {
106
+ const randomTheme = THEMES[Math.floor(Math.random() * THEMES.length)]; // 从THEMES数组中 随机取一个主题
107
+ const query = router.query;
108
+ query.theme = randomTheme;
109
+ router.push({ pathname: router.pathname, query });
110
+ }
111
+
112
+ function handleChangeDarkMode() {
113
+ const newStatus = !isDarkMode;
114
+ saveDarkModeToLocalStorage(newStatus);
115
+ updateDarkMode(newStatus);
116
+ const htmlElement = document.getElementsByTagName('html')[0];
117
+ htmlElement.classList?.remove(newStatus ? 'light' : 'dark');
118
+ htmlElement.classList?.add(newStatus ? 'dark' : 'light');
119
+ }
120
+
121
+ return (
122
+ <div
123
+ ref={menuRef}
124
+ style={{ top: position.y, left: position.x }}
125
+ className={`${show ? '' : 'invisible opacity-0'} fixed z-50 select-none transition-opacity duration-200`}
126
+ >
127
+ {/* 菜单内容 */}
128
+ <div className="w-52 flex-col rounded-xl border bg-white p-3 drop-shadow-lg transition-colors duration-300 dark:border-gray-600 dark:bg-[#040404] dark:text-gray-200 dark:hover:border-yellow-600">
129
+ {/* 顶部导航按钮 */}
130
+ <div className="flex justify-between">
131
+ <i
132
+ onClick={handleBack}
133
+ className="fa-solid fa-arrow-left w-8 cursor-pointer rounded px-2 py-2 text-center hover:bg-blue-600 hover:text-white"
134
+ ></i>
135
+ <i
136
+ onClick={handleForward}
137
+ className="fa-solid fa-arrow-right w-8 cursor-pointer rounded px-2 py-2 text-center hover:bg-blue-600 hover:text-white"
138
+ ></i>
139
+ <i
140
+ onClick={handleRefresh}
141
+ className="fa-solid fa-rotate-right w-8 cursor-pointer rounded px-2 py-2 text-center hover:bg-blue-600 hover:text-white"
142
+ ></i>
143
+ <i
144
+ onClick={handleScrollTop}
145
+ className="fa-solid fa-arrow-up w-8 cursor-pointer rounded px-2 py-2 text-center hover:bg-blue-600 hover:text-white"
146
+ ></i>
147
+ </div>
148
+
149
+ <hr className="my-2 border-dashed" />
150
+
151
+ {/* 跳转导航按钮 */}
152
+ <div className="w-full px-2">
153
+ <div
154
+ onClick={handleJumpToRandomPost}
155
+ title={t('walk-around')}
156
+ className="flex h-10 w-full cursor-pointer flex-nowrap items-center justify-start rounded-lg px-2 transition-all duration-200 hover:bg-blue-600 hover:text-white"
157
+ >
158
+ <i className="fa-solid fa-podcast mr-2" />
159
+ <div className="whitespace-nowrap">{t('walk-around')}</div>
160
+ </div>
161
+
162
+ <Link
163
+ href="/category"
164
+ title={t('category')}
165
+ className="flex h-10 w-full cursor-pointer flex-nowrap items-center justify-start rounded-lg px-2 transition-all duration-200 hover:bg-blue-600 hover:text-white"
166
+ >
167
+ <i className="fa-solid fa-square-minus mr-2" />
168
+ <div className="whitespace-nowrap">{t('category')}</div>
169
+ </Link>
170
+
171
+ <Link
172
+ href="/tag"
173
+ title={t('tags')}
174
+ className="flex h-10 w-full cursor-pointer flex-nowrap items-center justify-start rounded-lg px-2 transition-all duration-200 hover:bg-blue-600 hover:text-white"
175
+ >
176
+ <i className="fa-solid fa-tag mr-2" />
177
+ <div className="whitespace-nowrap">{t('tags')}</div>
178
+ </Link>
179
+ </div>
180
+
181
+ <hr className="my-2 border-dashed" />
182
+
183
+ {/* 功能按钮 */}
184
+ <div className="w-full px-2">
185
+ <div
186
+ onClick={handleCopyLink}
187
+ title={t('copy-url')}
188
+ className="flex h-10 w-full cursor-pointer flex-nowrap items-center justify-start rounded-lg px-2 transition-all duration-200 hover:bg-blue-600 hover:text-white"
189
+ >
190
+ <i className="fa-solid fa-arrow-up-right-from-square mr-2" />
191
+ <div className="whitespace-nowrap">{t('copy-url')}</div>
192
+ </div>
193
+
194
+ <div
195
+ onClick={handleChangeDarkMode}
196
+ title={isDarkMode ? t('light-mode') : t('dark-mode')}
197
+ className="flex h-10 w-full cursor-pointer flex-nowrap items-center justify-start rounded-lg px-2 transition-all duration-200 hover:bg-blue-600 hover:text-white"
198
+ >
199
+ {isDarkMode ? (
200
+ <i className="fa-regular fa-sun mr-2" />
201
+ ) : (
202
+ <i className="fa-regular fa-moon mr-2" />
203
+ )}
204
+ <div className="whitespace-nowrap">
205
+ {' '}
206
+ {isDarkMode ? t('light-mode') : t('dark-mode')}
207
+ </div>
208
+ </div>
209
+ <div
210
+ onClick={handeChangeTheme}
211
+ title={t('theme-switch')}
212
+ className="flex h-10 w-full cursor-pointer flex-nowrap items-center justify-start rounded-lg px-2 transition-all duration-200 hover:bg-blue-600 hover:text-white"
213
+ >
214
+ <i className="fa-solid fa-palette mr-2" />
215
+ <div className="whitespace-nowrap">{t('theme-switch')}</div>
216
+ </div>
217
+ </div>
218
+ </div>
219
+ </div>
220
+ );
221
+ }
@@ -0,0 +1,134 @@
1
+ import BLOG from '@/blog.config';
2
+ import { useEffect, useState } from 'react';
3
+ import Select from './Select';
4
+ import { useGlobal } from '@/lib/global';
5
+ import { THEMES } from '@/theme';
6
+ import { useRouter } from 'next/router';
7
+ import { useTranslation } from 'next-i18next';
8
+
9
+ /**
10
+ *
11
+ * @returns 调试面板
12
+ */
13
+ const DebugPanel = () => {
14
+ const [show, setShow] = useState(false);
15
+ const { theme, switchTheme } = useGlobal();
16
+ const router = useRouter();
17
+ const [siteConfig, updateSiteConfig] = useState({});
18
+ const { t } = useTranslation('common');
19
+
20
+ // 主题下拉框
21
+ const themeOptions = THEMES?.map((t) => ({ value: t, text: t }));
22
+
23
+ useEffect(() => {
24
+ updateSiteConfig(Object.assign({}, BLOG));
25
+ // updateThemeConfig(Object.assign({}, ThemeMap[BLOG.THEME].THEME_CONFIG))
26
+ }, []);
27
+
28
+ function toggleShow() {
29
+ setShow(!show);
30
+ }
31
+
32
+ function handleChangeDebugTheme() {
33
+ switchTheme();
34
+ }
35
+
36
+ function handleUpdateDebugTheme(newTheme) {
37
+ const query = { ...router.query, theme: newTheme };
38
+ router.push({ pathname: router.pathname, query });
39
+ }
40
+
41
+ function filterResult(text) {
42
+ switch (text) {
43
+ case 'true':
44
+ return <span className="text-green-500">true</span>;
45
+ case 'false':
46
+ return <span className="text-red-500">false</span>;
47
+ case '':
48
+ return '-';
49
+ }
50
+ return text;
51
+ }
52
+
53
+ return (
54
+ <>
55
+ {/* 调试按钮 */}
56
+ <div>
57
+ <div
58
+ style={{ writingMode: 'vertical-lr' }}
59
+ className={`cursor-pointer rounded-l-xl bg-black p-1.5 text-xs text-white shadow-2xl ${show ? 'right-96' : 'right-0'} fixed bottom-72 z-50 duration-200`}
60
+ onClick={toggleShow}
61
+ >
62
+ {show ? (
63
+ <i className="fas fa-times">&nbsp;{t('debug-close')}</i>
64
+ ) : (
65
+ <i className="fas fa-tools">&nbsp;{t('debug-open')}</i>
66
+ )}
67
+ </div>
68
+ </div>
69
+
70
+ {/* 调试侧拉抽屉 */}
71
+ <div
72
+ className={` ${show ? 'shadow-card right-0 w-96 ' : 'invisible -right-96 w-0'} fixed bottom-0 z-50 h-full overflow-y-scroll bg-white p-5 duration-200`}
73
+ >
74
+ <div className="my-5 flex justify-between space-x-1">
75
+ <div className="flex">
76
+ <Select
77
+ label={t('theme-switch')}
78
+ value={theme}
79
+ options={themeOptions}
80
+ onChange={handleUpdateDebugTheme}
81
+ />
82
+ <div
83
+ className="cursor-pointer p-2"
84
+ onClick={handleChangeDebugTheme}
85
+ >
86
+ <i className="fas fa-sync" />
87
+ </div>
88
+ </div>
89
+
90
+ <div className="p-2">
91
+ <i className="fas fa-times" onClick={toggleShow} />
92
+ </div>
93
+ </div>
94
+
95
+ <div>
96
+ {/* <div>
97
+ <div className="font-bold w-18 border-b my-2">
98
+ 主题配置{`config_${debugTheme}.js`}:
99
+ </div>
100
+ <div className="text-xs">
101
+ {Object.keys(themeConfig).map(k => (
102
+ <div key={k} className="justify-between flex py-1">
103
+ <span className="bg-indigo-500 p-0.5 rounded text-white mr-2">
104
+ {k}
105
+ </span>
106
+ <span className="whitespace-nowrap">
107
+ {filterResult(themeConfig[k] + '')}
108
+ </span>
109
+ </div>
110
+ ))}
111
+ </div>
112
+ </div> */}
113
+ <div className="w-18 my-2 border-b font-bold">
114
+ 站点配置[blog.config.js]
115
+ </div>
116
+ <div className="text-xs">
117
+ {siteConfig &&
118
+ Object.keys(siteConfig).map((k) => (
119
+ <div key={k} className="flex justify-between py-1">
120
+ <span className="mr-2 rounded bg-blue-500 p-0.5 text-white">
121
+ {k}
122
+ </span>
123
+ <span className="whitespace-nowrap">
124
+ {filterResult(siteConfig[k] + '')}
125
+ </span>
126
+ </div>
127
+ ))}
128
+ </div>
129
+ </div>
130
+ </div>
131
+ </>
132
+ );
133
+ };
134
+ export default DebugPanel;
@@ -0,0 +1,21 @@
1
+ import BLOG from '@/blog.config'
2
+ import { useEffect } from 'react'
3
+
4
+ /**
5
+ * 禁止用户拷贝文章的插件
6
+ */
7
+ export default function DisableCopy() {
8
+ useEffect(() => {
9
+ if (!JSON.parse(BLOG.CAN_COPY)) {
10
+ // 全栈添加禁止复制的样式
11
+ document.getElementsByTagName('html')[0].classList.add('forbid-copy')
12
+ // 监听复制事件
13
+ document.addEventListener('copy', function (event) {
14
+ event.preventDefault() // 阻止默认复制行为
15
+ alert('抱歉,本网页内容不可复制!')
16
+ })
17
+ }
18
+ }, [])
19
+
20
+ return null
21
+ }