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.
- package/.dockerignore +1 -0
- package/.env.local +2 -0
- package/.eslintrc.json +15 -0
- package/.github/stale.yml +16 -0
- package/.github/workflows/codeql-analysis.yml +73 -0
- package/.github/workflows/docker-ghcr.yaml +59 -0
- package/.prettierrc +9 -0
- package/.vscode/launch.json +28 -0
- package/.vscode/settings.json +6 -0
- package/Dockerfile +38 -0
- package/LICENSE +21 -0
- package/README.md +16 -0
- package/blog.config.js +454 -0
- package/components/Ackee.js +83 -0
- package/components/AdBlockDetect.js +40 -0
- package/components/AlgoliaSearchModal.js +250 -0
- package/components/Artalk.js +30 -0
- package/components/Busuanzi.js +26 -0
- package/components/ChatBase.js +19 -0
- package/components/Collapse.tsx +134 -0
- package/components/Comment.js +161 -0
- package/components/CommonHead.tsx +101 -0
- package/components/CommonScript.js +125 -0
- package/components/CusdisComponent.js +35 -0
- package/components/CustomContextMenu.js +221 -0
- package/components/DebugPanel.js +134 -0
- package/components/DisableCopy.js +21 -0
- package/components/Draggable.js +167 -0
- package/components/Equation.js +31 -0
- package/components/ExternalPlugins.js +75 -0
- package/components/ExternalScript.js +29 -0
- package/components/FacebookMessenger.js +255 -0
- package/components/FacebookPage.js +34 -0
- package/components/Fireworks.js +210 -0
- package/components/FlipCard.js +56 -0
- package/components/FlutteringRibbon.js +322 -0
- package/components/FullScreenButton.js +48 -0
- package/components/Giscus.js +33 -0
- package/components/Gitalk.js +42 -0
- package/components/GoogleAdsense.js +111 -0
- package/components/Gtag.js +18 -0
- package/components/HeroIcons.tsx +321 -0
- package/components/KatexReact.js +53 -0
- package/components/LazyImage.js +95 -0
- package/components/Live2D.js +52 -0
- package/components/Loading.js +20 -0
- package/components/Mark.js +28 -0
- package/components/NProgress.ts +8 -0
- package/components/Nest.js +124 -0
- package/components/NotionIcon.js +20 -0
- package/components/NotionPage.tsx +206 -0
- package/components/Player.js +54 -0
- package/components/PrismMac.js +236 -0
- package/components/QrCode.js +34 -0
- package/components/Ribbon.js +98 -0
- package/components/Sakura.js +192 -0
- package/components/Select.js +40 -0
- package/components/ShareBar.js +29 -0
- package/components/ShareButtons.js +403 -0
- package/components/SideBarDrawer.js +50 -0
- package/components/StarrySky.js +130 -0
- package/components/Tabs.js +64 -0
- package/components/ThemeSwitch.js +67 -0
- package/components/Twikoo.js +27 -0
- package/components/TwikooCommentCount.js +22 -0
- package/components/TwikooCommentCounter.js +78 -0
- package/components/TwikooRecentComments.js +11 -0
- package/components/Utterances.js +35 -0
- package/components/VConsole.js +76 -0
- package/components/ValineComponent.js +59 -0
- package/components/ValineCount.js +6 -0
- package/components/ValinePanel.js +3 -0
- package/components/Vercel.tsx +54 -0
- package/components/WWAds.js +18 -0
- package/components/WalineComponent.js +83 -0
- package/components/WebMention.js +173 -0
- package/components/Webwhiz.js +17 -0
- package/components/WordCount.js +73 -0
- package/hooks/useToggleClickOutSide.ts +32 -0
- package/hooks/useWindowSize.ts +30 -0
- package/lib/algolia.js +108 -0
- package/lib/busuanzi.js +99 -0
- package/lib/cache/cacheManager.ts +49 -0
- package/lib/cache/localFileCache.ts +56 -0
- package/lib/cache/memoryMache.ts +20 -0
- package/lib/cache/mongoDbCache.ts +70 -0
- package/lib/cache/types.ts +5 -0
- package/lib/font.js +46 -0
- package/lib/global.tsx +129 -0
- package/lib/gtag.js +17 -0
- package/lib/mailchimp.js +49 -0
- package/lib/memorize.js +0 -0
- package/lib/mhchem.js +1696 -0
- package/lib/notion/getAllCategories.ts +51 -0
- package/lib/notion/getAllPageIds.ts +51 -0
- package/lib/notion/getAllPosts.js +68 -0
- package/lib/notion/getAllTags.ts +43 -0
- package/lib/notion/getNotionData.ts +340 -0
- package/lib/notion/getPageInfoOfPostPage.ts +58 -0
- package/lib/notion/getPageProperties.ts +203 -0
- package/lib/notion/getPageTableOfContents.ts +107 -0
- package/lib/notion/getPostBlocks.ts +147 -0
- package/lib/notion/mapImage.ts +130 -0
- package/lib/notion/types.ts +125 -0
- package/lib/notion.js +2 -0
- package/lib/robots.txt.js +25 -0
- package/lib/rss.js +63 -0
- package/lib/sitemap.xml.js +67 -0
- package/lib/utils.js +212 -0
- package/next-env.d.ts +5 -0
- package/next-i18next.config.js +7 -0
- package/next-sitemap.config.js +11 -0
- package/next.config.js +124 -0
- package/package.json +92 -0
- package/pages/404.tsx +40 -0
- package/pages/[prefix]/[slug].tsx +123 -0
- package/pages/[prefix]/index.tsx +223 -0
- package/pages/_app.js +59 -0
- package/pages/_document.js +42 -0
- package/pages/api/subscribe.js +22 -0
- package/pages/archive/index.tsx +79 -0
- package/pages/category/[category]/index.tsx +87 -0
- package/pages/category/[category]/page/[page].tsx +103 -0
- package/pages/category/index.tsx +43 -0
- package/pages/index.tsx +88 -0
- package/pages/page/[page].tsx +93 -0
- package/pages/search/[keyword]/index.tsx +162 -0
- package/pages/search/[keyword]/page/[page].tsx +166 -0
- package/pages/search/index.tsx +69 -0
- package/pages/sitemap.xml.js +70 -0
- package/pages/tag/[tag]/index.tsx +73 -0
- package/pages/tag/[tag]/page/[page].tsx +87 -0
- package/pages/tag/index.tsx +42 -0
- package/postcss.config.js +6 -0
- package/public/ads.txt +1 -0
- package/public/avatar.png +0 -0
- package/public/avatar.svg +11 -0
- package/public/bg_image.jpg +0 -0
- package/public/css/all.min.css +9 -0
- package/public/css/custom.css +8 -0
- package/public/css/img-shadow.css +5 -0
- package/public/css/prism-mac-style.css +58 -0
- package/public/favicon.ico +0 -0
- package/public/favicon.svg +9 -0
- package/public/js/cusdis.es.js +107 -0
- package/public/js/custom.js +1 -0
- package/public/locales/en/common.json +44 -0
- package/public/locales/en/menu.json +9 -0
- package/public/locales/en/nav.json +11 -0
- package/public/locales/zh-CN/common.json +44 -0
- package/public/locales/zh-CN/menu.json +9 -0
- package/public/locales/zh-CN/nav.json +9 -0
- package/public/webfonts/fa-brands-400.ttf +0 -0
- package/public/webfonts/fa-brands-400.woff2 +0 -0
- package/public/webfonts/fa-regular-400.ttf +0 -0
- package/public/webfonts/fa-regular-400.woff2 +0 -0
- package/public/webfonts/fa-solid-900.ttf +0 -0
- package/public/webfonts/fa-solid-900.woff2 +0 -0
- package/public/webfonts/fa-v4compatibility.ttf +0 -0
- package/public/webfonts/fa-v4compatibility.woff2 +0 -0
- package/styles/animate.css +503 -0
- package/styles/globals.css +183 -0
- package/styles/notion.css +2064 -0
- package/styles/nprogress.css +84 -0
- package/styles/prism-theme.css +119 -0
- package/styles/utility-patterns.css +79 -0
- package/tailwind.config.js +37 -0
- package/theme/index.ts +6 -0
- package/theme/types/@theme-components.d.ts +29 -0
- package/theme/useLayout.ts +41 -0
- package/theme/utils.ts +108 -0
- package/themes/innocent/package.json +7 -0
- package/themes/innocent/theme.config.js +1 -0
- package/themes/nobelium/components/Announcement.tsx +27 -0
- package/themes/nobelium/components/ArticleFooter.tsx +39 -0
- package/themes/nobelium/components/ArticleInfo.tsx +58 -0
- package/themes/nobelium/components/ArticleLock.tsx +86 -0
- package/themes/nobelium/components/BlogArchiveItem.js +41 -0
- package/themes/nobelium/components/BlogListBar.js +39 -0
- package/themes/nobelium/components/BlogListPage.tsx +67 -0
- package/themes/nobelium/components/BlogListScroll.tsx +96 -0
- package/themes/nobelium/components/BlogPost.tsx +33 -0
- package/themes/nobelium/components/DarkModeButton.tsx +50 -0
- package/themes/nobelium/components/ExampleRecentComments.js +35 -0
- package/themes/nobelium/components/Footer.tsx +35 -0
- package/themes/nobelium/components/JumpToTopButton.tsx +39 -0
- package/themes/nobelium/components/LanguageSwitchButton.tsx +58 -0
- package/themes/nobelium/components/MenuItemCollapse.tsx +92 -0
- package/themes/nobelium/components/MenuItemDrop.tsx +83 -0
- package/themes/nobelium/components/Nav/Nav.module.css +50 -0
- package/themes/nobelium/components/Nav/Nav.tsx +187 -0
- package/themes/nobelium/components/RandomPostButton.tsx +31 -0
- package/themes/nobelium/components/SearchButton.tsx +31 -0
- package/themes/nobelium/components/SearchInput.tsx +94 -0
- package/themes/nobelium/components/SearchNavBar.js +19 -0
- package/themes/nobelium/components/SideBar.js +83 -0
- package/themes/nobelium/components/SvgIcon.js +29 -0
- package/themes/nobelium/components/TagItem.js +13 -0
- package/themes/nobelium/components/Tags.tsx +44 -0
- package/themes/nobelium/components/Title.js +19 -0
- package/themes/nobelium/index.tsx +28 -0
- package/themes/nobelium/layout/LayoutBase.tsx +79 -0
- package/themes/nobelium/pages/Archive.tsx +30 -0
- package/themes/nobelium/pages/Category.tsx +43 -0
- package/themes/nobelium/pages/Home.tsx +22 -0
- package/themes/nobelium/pages/PageNotFound.tsx +15 -0
- package/themes/nobelium/pages/Post.tsx +34 -0
- package/themes/nobelium/pages/PostList.tsx +74 -0
- package/themes/nobelium/pages/Search.tsx +65 -0
- package/themes/nobelium/pages/Tag.tsx +42 -0
- package/themes/nobelium/providers/index.tsx +60 -0
- package/themes/nobelium/stores/index.tsx +42 -0
- package/themes/nobelium/theme.config.ts +17 -0
- package/themes/nobelium/types/index.ts +10 -0
- package/tsconfig.json +29 -0
- package/types/index.ts +1 -0
- package/types/page.ts +102 -0
- 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"> {t('debug-close')}</i>
|
64
|
+
) : (
|
65
|
+
<i className="fas fa-tools"> {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
|
+
}
|