Repository: tangly1024/NotionNext
Branch: main
Commit: e2b7a3447706
Files: 1135
Total size: 2.9 MB
Directory structure:
gitextract_1waq930q/
├── .dockerignore
├── .eslintrc.js
├── .github/
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ ├── config.yml
│ │ ├── deployment-error.md
│ │ └── feature_request.md
│ ├── pull_request_template.md
│ ├── stale.yml
│ └── workflows/
│ ├── codeql-analysis.yml
│ ├── docker-ghcr.yaml
│ ├── pushUrl.yml
│ └── sync.yaml
├── .gitignore
├── .npmrc
├── .nvmrc
├── .prettierrc.json
├── CONTRIBUTING.md
├── DEPLOYMENT.md
├── DEVELOPMENT.md
├── Dockerfile
├── LICENSE
├── OPTIMIZATION_SUMMARY.md
├── PROJECT_COMPLETION_REPORT.md
├── README.md
├── README_EN.md
├── SECURITY.md
├── __tests__/
│ ├── components/
│ │ └── LazyImage.test.js
│ └── lib/
│ └── utils/
│ └── validation.test.js
├── blog.config.js
├── components/
│ ├── AISummary.js
│ ├── AISummary.module.css
│ ├── AOSAnimation.js
│ ├── Accessibility.js
│ ├── Ackee.js
│ ├── AdBlockDetect.js
│ ├── AlgoliaSearchModal.js
│ ├── AnalyticsBusuanzi.js
│ ├── Artalk.js
│ ├── ArticleExpirationNotice.js
│ ├── Badge.js
│ ├── BeiAnGongAn.tsx
│ ├── BeiAnSite.js
│ ├── Busuanzi.js
│ ├── CanvasEmail.js
│ ├── ChatBase.js
│ ├── Collapse.js
│ ├── Comment.js
│ ├── CopyRightDate.js
│ ├── Coze.js
│ ├── CursorDot.js
│ ├── CusdisComponent.js
│ ├── CustomContextMenu.js
│ ├── DarkModeButton.js
│ ├── DebugPanel.js
│ ├── DifyChatbot.js
│ ├── DisableCopy.js
│ ├── Draggable.js
│ ├── Equation.js
│ ├── ExternalPlugins.js
│ ├── ExternalScript.js
│ ├── FacebookMessenger.js
│ ├── FacebookPage.js
│ ├── Fireworks.js
│ ├── FlipCard.js
│ ├── FlutteringRibbon.js
│ ├── FullScreenButton.js
│ ├── Giscus.js
│ ├── Gitalk.js
│ ├── GlobalStyle.js
│ ├── GoogleAdsense.js
│ ├── Gtag.js
│ ├── HeroIcons.js
│ ├── IconFont.js
│ ├── KatexReact.js
│ ├── LA51.js
│ ├── LazyImage.js
│ ├── Lenis.js
│ ├── Live2D.js
│ ├── Loading.js
│ ├── LoadingCover.js
│ ├── LoadingProgress.js
│ ├── Mark.js
│ ├── MouseFollow.js
│ ├── Nest.js
│ ├── NotByAI.js
│ ├── Notification.js
│ ├── NotionIcon.js
│ ├── NotionPage.js
│ ├── OpenWrite.js
│ ├── PWA.js
│ ├── Pdf.js
│ ├── PerformanceMonitor.js
│ ├── Player.js
│ ├── PoweredBy.js
│ ├── PrismMac.js
│ ├── QrCode.js
│ ├── Ribbon.js
│ ├── SEO.js
│ ├── Sakura.js
│ ├── Select.js
│ ├── ShareBar.js
│ ├── ShareButtons.js
│ ├── SideBarDrawer.js
│ ├── SmartLink.js
│ ├── StarrySky.js
│ ├── Tabs.js
│ ├── ThemeSwitch.js
│ ├── TianliGPT.js
│ ├── Twikoo.js
│ ├── TwikooCommentCount.js
│ ├── TwikooCommentCounter.js
│ ├── TwikooRecentComments.js
│ ├── Utterances.js
│ ├── VConsole.js
│ ├── ValineComponent.js
│ ├── Vercel.js
│ ├── WWAds.js
│ ├── WalineComponent.js
│ ├── WebMention.js
│ ├── Webwhiz.js
│ ├── WordCount.js
│ └── ui/
│ └── dashboard/
│ ├── DashboardBody.js
│ ├── DashboardButton.js
│ ├── DashboardHeader.js
│ ├── DashboardItemAffliate.js
│ ├── DashboardItemBalance.js
│ ├── DashboardItemHome.js
│ ├── DashboardItemMembership.js
│ ├── DashboardItemOrder.js
│ ├── DashboardMenuList.js
│ ├── DashboardSignOutButton.js
│ └── DashboardUser.js
├── conf/
│ ├── ad.config.js
│ ├── ai.confg.js
│ ├── analytics.config.js
│ ├── animation.config.js
│ ├── code.config.js
│ ├── comment.config.js
│ ├── contact.config.js
│ ├── dev.config.js
│ ├── font.config.js
│ ├── image.config.js
│ ├── layout-map.config.js
│ ├── notion.config.js
│ ├── performance.config.js
│ ├── plugin.config.js
│ ├── post.config.js
│ ├── right-click-menu.js
│ └── widget.config.js
├── hooks/
│ ├── useAdjustStyle.js
│ └── useWindowSize.ts
├── jest.config.js
├── jest.env.js
├── jest.setup.js
├── jsconfig.json
├── lib/
│ ├── cache/
│ │ ├── cache_manager.js
│ │ ├── local_file_cache.js
│ │ ├── memory_cache.js
│ │ └── redis_cache.js
│ ├── config/
│ │ └── env-validation.js
│ ├── config.js
│ ├── db/
│ │ ├── SiteDataApi.js
│ │ └── notion/
│ │ ├── CustomNotionApi.ts
│ │ ├── RateLimiter.ts
│ │ ├── convertInnerUrl.js
│ │ ├── getAllCategories.js
│ │ ├── getAllPageIds.js
│ │ ├── getAllTags.js
│ │ ├── getMetadata.js
│ │ ├── getNotionAPI.js
│ │ ├── getNotionConfig.js
│ │ ├── getNotionPost.js
│ │ ├── getPageContentText.js
│ │ ├── getPageProperties.js
│ │ ├── getPageTableOfContents.js
│ │ ├── getPostBlocks.js
│ │ ├── mapImage.js
│ │ └── normalizeUtil.js
│ ├── global.js
│ ├── lang/
│ │ ├── en-US.js
│ │ ├── fr-FR.js
│ │ ├── ja-JP.js
│ │ ├── tr-TR.js
│ │ ├── zh-CN.js
│ │ ├── zh-HK.js
│ │ └── zh-TW.js
│ ├── middleware/
│ │ └── security.js
│ ├── plugins/
│ │ ├── aiSummary.js
│ │ ├── algolia.js
│ │ ├── busuanzi.js
│ │ ├── gtag.js
│ │ ├── mailEncrypt.js
│ │ ├── mailchimp.js
│ │ ├── mhchem.js
│ │ ├── wordCount.js
│ │ └── wow.js
│ ├── site/
│ │ ├── adapters/
│ │ │ └── notion/
│ │ │ ├── notion.adapter.ts
│ │ │ ├── notion.fetcher.ts
│ │ │ └── notion.normalizer.ts
│ │ ├── processors/
│ │ │ ├── empty.processor.ts
│ │ │ ├── page.processor.ts
│ │ │ └── schedule.processor.ts
│ │ ├── site.api.ts
│ │ ├── site.service.ts
│ │ └── site.types.ts
│ └── utils/
│ ├── clean.util.ts
│ ├── errorHandler.js
│ ├── font.js
│ ├── formatDate.js
│ ├── index.js
│ ├── lang.js
│ ├── notion.util.js
│ ├── pageId.js
│ ├── password.js
│ ├── post.js
│ ├── redirect.js
│ ├── robots.txt.js
│ ├── rss.js
│ ├── sitemap.js
│ ├── sitemap.xml.js
│ ├── time.util.ts
│ └── validation.js
├── lighthouserc.js
├── middleware.ts
├── next-env.d.ts
├── next-sitemap.config.js
├── next.config.js
├── package.json
├── pages/
│ ├── 404.js
│ ├── 500.js
│ ├── [prefix]/
│ │ ├── [slug]/
│ │ │ ├── [...suffix].js
│ │ │ └── index.js
│ │ └── index.js
│ ├── _app.js
│ ├── _document.js
│ ├── _error.js
│ ├── api/
│ │ ├── auth/
│ │ │ └── callback/
│ │ │ └── notion.ts
│ │ ├── cache.js
│ │ ├── subscribe.js
│ │ └── user.ts
│ ├── archive/
│ │ └── index.js
│ ├── auth/
│ │ ├── index.js
│ │ └── result.js
│ ├── category/
│ │ ├── [category]/
│ │ │ ├── index.js
│ │ │ └── page/
│ │ │ └── [page].js
│ │ └── index.js
│ ├── dashboard/
│ │ └── [[...index]].js
│ ├── index.js
│ ├── page/
│ │ └── [page].js
│ ├── search/
│ │ ├── [keyword]/
│ │ │ ├── index.js
│ │ │ └── page/
│ │ │ └── [page].js
│ │ └── index.js
│ ├── sign-in/
│ │ └── [[...index]].js
│ ├── sign-up/
│ │ └── [[...index]].js
│ ├── sitemap.xml.js
│ └── tag/
│ ├── [tag]/
│ │ ├── index.js
│ │ └── page/
│ │ └── [page].js
│ └── index.js
├── postcss.config.js
├── public/
│ ├── ads.txt
│ ├── css/
│ │ ├── aos.css
│ │ ├── custom.css
│ │ ├── img-shadow.css
│ │ ├── prism-mac-style.css
│ │ ├── spoiler-text.css
│ │ └── wow/
│ │ └── animate.css
│ ├── dplayer.htm
│ ├── games-external/
│ │ └── common/
│ │ └── index.htm
│ └── js/
│ ├── aos.js
│ ├── cusdis.es.js
│ ├── custom.js
│ ├── fireworks.js
│ ├── flutteringRibbon.js
│ ├── fullscreen.js
│ ├── giscus.js
│ ├── lenis.js
│ ├── mouse-follow.js
│ ├── nest.js
│ ├── ribbon.js
│ ├── sakura.js
│ ├── spoilerText.js
│ └── starrySky.js
├── pushUrl.py
├── scripts/
│ ├── dev-tools.js
│ ├── final-validation.js
│ ├── health-check.js
│ ├── quality-check.js
│ └── setup-git-hooks.js
├── styles/
│ ├── globals.css
│ ├── notion.css
│ ├── prism-theme.css
│ └── utility-patterns.css
├── tailwind.config.js
├── themes/
│ ├── commerce/
│ │ ├── components/
│ │ │ ├── AnalyticsCard.js
│ │ │ ├── Announcement.js
│ │ │ ├── ArticleAdjacent.js
│ │ │ ├── ArticleCopyright.js
│ │ │ ├── ArticleLock.js
│ │ │ ├── ArticleRecommend.js
│ │ │ ├── BlogPostArchive.js
│ │ │ ├── BlogPostCardInfo.js
│ │ │ ├── BlogPostListEmpty.js
│ │ │ ├── BlogPostListPage.js
│ │ │ ├── BlogPostListScroll.js
│ │ │ ├── Card.js
│ │ │ ├── Catalog.js
│ │ │ ├── CategoryGroup.js
│ │ │ ├── FloatDarkModeButton.js
│ │ │ ├── Footer.js
│ │ │ ├── Header.js
│ │ │ ├── Hero.js
│ │ │ ├── HexoRecentComments.js
│ │ │ ├── InfoCard.js
│ │ │ ├── JumpToCommentButton.js
│ │ │ ├── JumpToTopButton.js
│ │ │ ├── LatestPostsGroup.js
│ │ │ ├── LoadingCover.js
│ │ │ ├── LogoBar.js
│ │ │ ├── MenuBarMobile.js
│ │ │ ├── MenuGroupCard.js
│ │ │ ├── MenuItemCollapse.js
│ │ │ ├── MenuItemDrop.js
│ │ │ ├── MenuListSide.js
│ │ │ ├── MenuListTop.js
│ │ │ ├── NavButtonGroup.js
│ │ │ ├── PaginationNumber.js
│ │ │ ├── PostHeader.js
│ │ │ ├── ProductCard.js
│ │ │ ├── ProductCategories.js
│ │ │ ├── ProductCenter.js
│ │ │ ├── Progress.js
│ │ │ ├── RightFloatArea.js
│ │ │ ├── SearchDrawer.js
│ │ │ ├── SearchInput.js
│ │ │ ├── SearchNav.js
│ │ │ ├── SideBar.js
│ │ │ ├── SideBarDrawer.js
│ │ │ ├── SideRight.js
│ │ │ ├── SlotBar.js
│ │ │ ├── SocialButton.js
│ │ │ ├── TagGroups.js
│ │ │ ├── TagItemMini.js
│ │ │ ├── TocDrawer.js
│ │ │ └── TocDrawerButton.js
│ │ ├── config.js
│ │ ├── index.js
│ │ └── style.js
│ ├── example/
│ │ ├── components/
│ │ │ ├── Announcement.js
│ │ │ ├── BlogItem.js
│ │ │ ├── BlogListArchive.js
│ │ │ ├── BlogListPage.js
│ │ │ ├── BlogListScroll.js
│ │ │ ├── Catalog.js
│ │ │ ├── Footer.js
│ │ │ ├── Header.js
│ │ │ ├── MenuItemDrop.js
│ │ │ ├── MenuList.js
│ │ │ ├── PostLock.js
│ │ │ ├── PostMeta.js
│ │ │ ├── RecentCommentListForExample.js
│ │ │ ├── SearchInput.js
│ │ │ ├── SideBar.js
│ │ │ └── TitleBar.js
│ │ ├── config.js
│ │ ├── index.js
│ │ └── style.js
│ ├── fukasawa/
│ │ ├── components/
│ │ │ ├── Announcement.js
│ │ │ ├── ArticleAround.js
│ │ │ ├── ArticleDetail.js
│ │ │ ├── ArticleLock.js
│ │ │ ├── AsideLeft.js
│ │ │ ├── BlogCard.js
│ │ │ ├── BlogListEmpty.js
│ │ │ ├── BlogListPage.js
│ │ │ ├── BlogListScroll.js
│ │ │ ├── BlogPostArchive.js
│ │ │ ├── Card.js
│ │ │ ├── Catalog.js
│ │ │ ├── GroupCategory.js
│ │ │ ├── GroupTag.js
│ │ │ ├── Header.js
│ │ │ ├── LoadingCover.js
│ │ │ ├── Logo.js
│ │ │ ├── MailChimpForm.js
│ │ │ ├── MenuItemCollapse.js
│ │ │ ├── MenuItemDrop.js
│ │ │ ├── MenuItemNormal.js
│ │ │ ├── MenuList.js
│ │ │ ├── PaginationSimple.js
│ │ │ ├── SearchInput.js
│ │ │ ├── SiteInfo.js
│ │ │ ├── SocialButton.js
│ │ │ ├── TagItem.js
│ │ │ └── TagItemMini.js
│ │ ├── config.js
│ │ ├── index.js
│ │ └── style.js
│ ├── game/
│ │ ├── components/
│ │ │ ├── AdBlockerDetect.js
│ │ │ ├── Announcement.js
│ │ │ ├── ArticleLock.js
│ │ │ ├── BlogArchiveItem.js
│ │ │ ├── BlogListBar.js
│ │ │ ├── BlogListPage.js
│ │ │ ├── BlogListScroll.js
│ │ │ ├── BlogPost.js
│ │ │ ├── BlogPostBar.js
│ │ │ ├── DarkModeButton.js
│ │ │ ├── DownloadButton.js
│ │ │ ├── ExampleRecentComments.js
│ │ │ ├── Footer.js
│ │ │ ├── FullScreenButton.js
│ │ │ ├── GameEmbed.js
│ │ │ ├── GameListIndexCombine.js
│ │ │ ├── GameListNormal.js
│ │ │ ├── GameListRealate.js
│ │ │ ├── GameListRecent.js
│ │ │ ├── GroupCategory.js
│ │ │ ├── GroupTag.js
│ │ │ ├── Header.js
│ │ │ ├── JumpToTopButton.js
│ │ │ ├── Logo.js
│ │ │ ├── LogoMini.js
│ │ │ ├── MenuItemCollapse.js
│ │ │ ├── MenuItemDrop.js
│ │ │ ├── MenuList.js
│ │ │ ├── PaginationSimple.js
│ │ │ ├── PostInfo.js
│ │ │ ├── RandomPostButton.js
│ │ │ ├── SearchButton.js
│ │ │ ├── SearchInput.js
│ │ │ ├── SideBar.js
│ │ │ ├── SideBarContent.js
│ │ │ ├── SideBarDrawer.js
│ │ │ ├── SvgIcon.js
│ │ │ ├── TagItem.js
│ │ │ ├── TagItemMini.js
│ │ │ ├── Tags.js
│ │ │ └── Title.js
│ │ ├── config.js
│ │ ├── index.js
│ │ └── style.js
│ ├── gitbook/
│ │ ├── components/
│ │ │ ├── Announcement.js
│ │ │ ├── ArticleAround.js
│ │ │ ├── ArticleInfo.js
│ │ │ ├── ArticleLock.js
│ │ │ ├── BlogArchiveItem.js
│ │ │ ├── BlogPostCard.js
│ │ │ ├── BottomMenuBar.js
│ │ │ ├── Card.js
│ │ │ ├── Catalog.js
│ │ │ ├── CatalogDrawerWrapper.js
│ │ │ ├── CategoryGroup.js
│ │ │ ├── CategoryItem.js
│ │ │ ├── Footer.js
│ │ │ ├── Header.js
│ │ │ ├── InfoCard.js
│ │ │ ├── JumpToTopButton.js
│ │ │ ├── LeftMenuBar.js
│ │ │ ├── LogoBar.js
│ │ │ ├── MenuBarMobile.js
│ │ │ ├── MenuItemCollapse.js
│ │ │ ├── MenuItemDrop.js
│ │ │ ├── MenuItemMobileNormal.js
│ │ │ ├── MenuItemPCNormal.js
│ │ │ ├── NavPostItem.js
│ │ │ ├── NavPostList.js
│ │ │ ├── PageNavDrawer.js
│ │ │ ├── PaginationSimple.js
│ │ │ ├── Progress.js
│ │ │ ├── RevolverMaps.js
│ │ │ ├── SearchInput.js
│ │ │ ├── SocialButton.js
│ │ │ ├── TagGroups.js
│ │ │ └── TagItemMini.js
│ │ ├── config.js
│ │ ├── index.js
│ │ └── style.js
│ ├── heo/
│ │ ├── components/
│ │ │ ├── AnalyticsCard.js
│ │ │ ├── Announcement.js
│ │ │ ├── BlogPostArchive.js
│ │ │ ├── BlogPostCard.js
│ │ │ ├── BlogPostListEmpty.js
│ │ │ ├── BlogPostListPage.js
│ │ │ ├── BlogPostListScroll.js
│ │ │ ├── Card.js
│ │ │ ├── Catalog.js
│ │ │ ├── CategoryBar.js
│ │ │ ├── CategoryGroup.js
│ │ │ ├── DarkModeButton.js
│ │ │ ├── FloatDarkModeButton.js
│ │ │ ├── FloatTocButton.js
│ │ │ ├── Footer.js
│ │ │ ├── Header.js
│ │ │ ├── Hero.js
│ │ │ ├── HexoRecentComments.js
│ │ │ ├── InfoCard.js
│ │ │ ├── JumpToCommentButton.js
│ │ │ ├── JumpToTopButton.js
│ │ │ ├── LatestPostsGroup.js
│ │ │ ├── LatestPostsGroupMini.js
│ │ │ ├── Logo.js
│ │ │ ├── MenuGroupCard.js
│ │ │ ├── MenuItemCollapse.js
│ │ │ ├── MenuItemDrop.js
│ │ │ ├── MenuListSide.js
│ │ │ ├── MenuListTop.js
│ │ │ ├── NavButtonGroup.js
│ │ │ ├── NoticeBar.js
│ │ │ ├── NotionIcon.js
│ │ │ ├── PaginationNumber.js
│ │ │ ├── PostAdjacent.js
│ │ │ ├── PostCopyright.js
│ │ │ ├── PostHeader.js
│ │ │ ├── PostLock.js
│ │ │ ├── PostRecommend.js
│ │ │ ├── RandomPostButton.js
│ │ │ ├── ReadingProgress.js
│ │ │ ├── SearchButton.js
│ │ │ ├── SearchDrawer.js
│ │ │ ├── SearchInput.js
│ │ │ ├── SearchNav.js
│ │ │ ├── SideBar.js
│ │ │ ├── SideBarDrawer.js
│ │ │ ├── SideRight.js
│ │ │ ├── SlideOver.js
│ │ │ ├── SocialButton.js
│ │ │ ├── Swipe.js
│ │ │ ├── TagGroups.js
│ │ │ ├── TagItemMini.js
│ │ │ ├── TocDrawerButton.js
│ │ │ ├── TouchMeCard.js
│ │ │ └── WavesArea.js
│ │ ├── config.js
│ │ ├── index.js
│ │ └── style.js
│ ├── hexo/
│ │ ├── components/
│ │ │ ├── AnalyticsCard.js
│ │ │ ├── Announcement.js
│ │ │ ├── ArticleAdjacent.js
│ │ │ ├── ArticleCopyright.js
│ │ │ ├── ArticleLock.js
│ │ │ ├── ArticleRecommend.js
│ │ │ ├── BlogPostArchive.js
│ │ │ ├── BlogPostCard.js
│ │ │ ├── BlogPostCardInfo.js
│ │ │ ├── BlogPostListEmpty.js
│ │ │ ├── BlogPostListPage.js
│ │ │ ├── BlogPostListScroll.js
│ │ │ ├── ButtonFloatDarkMode.js
│ │ │ ├── ButtonJumpToComment.js
│ │ │ ├── ButtonJumpToTop.js
│ │ │ ├── ButtonRandomPost.js
│ │ │ ├── ButtonRandomPostMini.js
│ │ │ ├── Card.js
│ │ │ ├── Catalog.js
│ │ │ ├── CategoryGroup.js
│ │ │ ├── Footer.js
│ │ │ ├── Header.js
│ │ │ ├── Hero.js
│ │ │ ├── HexoRecentComments.js
│ │ │ ├── InfoCard.js
│ │ │ ├── LatestPostsGroup.js
│ │ │ ├── LoadingCover.js
│ │ │ ├── Logo.js
│ │ │ ├── MenuGroupCard.js
│ │ │ ├── MenuItemCollapse.js
│ │ │ ├── MenuItemDrop.js
│ │ │ ├── MenuListSide.js
│ │ │ ├── MenuListTop.js
│ │ │ ├── NavButtonGroup.js
│ │ │ ├── PaginationNumber.js
│ │ │ ├── PostHero.js
│ │ │ ├── Progress.js
│ │ │ ├── RightFloatArea.js
│ │ │ ├── SearchButton.js
│ │ │ ├── SearchDrawer.js
│ │ │ ├── SearchInput.js
│ │ │ ├── SearchNav.js
│ │ │ ├── SideBar.js
│ │ │ ├── SideBarDrawer.js
│ │ │ ├── SideRight.js
│ │ │ ├── SlotBar.js
│ │ │ ├── SocialButton.js
│ │ │ ├── TagGroups.js
│ │ │ ├── TagItemMini.js
│ │ │ ├── TocDrawer.js
│ │ │ └── TocDrawerButton.js
│ │ ├── config.js
│ │ ├── index.js
│ │ └── style.js
│ ├── landing/
│ │ ├── components/
│ │ │ ├── Features.js
│ │ │ ├── FeaturesBlocks.js
│ │ │ ├── Footer.js
│ │ │ ├── Header.js
│ │ │ ├── Hero.js
│ │ │ ├── Logo.js
│ │ │ ├── MobileMenu.js
│ │ │ ├── ModalVideo.js
│ │ │ ├── Newsletter.js
│ │ │ ├── Pricing.js
│ │ │ └── Testimonials.js
│ │ ├── config.js
│ │ ├── index.js
│ │ └── style.js
│ ├── magzine/
│ │ ├── components/
│ │ │ ├── Announcement.js
│ │ │ ├── ArticleInfo.js
│ │ │ ├── ArticleLock.js
│ │ │ ├── BannerFullWidth.js
│ │ │ ├── BannerItem.js
│ │ │ ├── CTA.js
│ │ │ ├── Card.js
│ │ │ ├── Catalog.js
│ │ │ ├── CatalogFloat.js
│ │ │ ├── CatalogFloatButton.js
│ │ │ ├── CategoryGroup.js
│ │ │ ├── CategoryItem.js
│ │ │ ├── Footer.js
│ │ │ ├── Header.js
│ │ │ ├── Hero.js
│ │ │ ├── InfoCard.js
│ │ │ ├── JumpToTopButton.js
│ │ │ ├── LeftMenuBar.js
│ │ │ ├── LogoBar.js
│ │ │ ├── MenuBarMobile.js
│ │ │ ├── MenuItemCollapse.js
│ │ │ ├── MenuItemDrop.js
│ │ │ ├── MenuItemMobileNormal.js
│ │ │ ├── MenuItemPCNormal.js
│ │ │ ├── PaginationSimple.js
│ │ │ ├── PostBannerGroupByCategory.js
│ │ │ ├── PostGroupArchive.js
│ │ │ ├── PostGroupLatest.js
│ │ │ ├── PostItemCard.js
│ │ │ ├── PostItemCardSimple.js
│ │ │ ├── PostItemCardTop.js
│ │ │ ├── PostItemCardWide.js
│ │ │ ├── PostListEmpty.js
│ │ │ ├── PostListHorizontal.js
│ │ │ ├── PostListPage.js
│ │ │ ├── PostListRecommend.js
│ │ │ ├── PostListScroll.js
│ │ │ ├── PostListSimpleHorizontal.js
│ │ │ ├── PostListSlotBar.js
│ │ │ ├── PostNavAround.js
│ │ │ ├── Progress.js
│ │ │ ├── SearchInput.js
│ │ │ ├── SocialButton.js
│ │ │ ├── Swiper.js
│ │ │ ├── TagGroups.js
│ │ │ ├── TagItemMini.js
│ │ │ └── TouchMeCard.js
│ │ ├── config.js
│ │ ├── index.js
│ │ └── style.js
│ ├── matery/
│ │ ├── components/
│ │ │ ├── AnalyticsCard.js
│ │ │ ├── Announcement.js
│ │ │ ├── ArticleAdjacent.js
│ │ │ ├── ArticleCopyright.js
│ │ │ ├── ArticleInfo.js
│ │ │ ├── ArticleLock.js
│ │ │ ├── ArticleRecommend.js
│ │ │ ├── BlogListBar.js
│ │ │ ├── BlogPostArchive.js
│ │ │ ├── BlogPostCard.js
│ │ │ ├── BlogPostListEmpty.js
│ │ │ ├── BlogPostListPage.js
│ │ │ ├── BlogPostListScroll.js
│ │ │ ├── Card.js
│ │ │ ├── Catalog.js
│ │ │ ├── CatalogWrapper.js
│ │ │ ├── CategoryGroup.js
│ │ │ ├── FloatDarkModeButton.js
│ │ │ ├── Footer.js
│ │ │ ├── Header.js
│ │ │ ├── Hero.js
│ │ │ ├── HexoRecentComments.js
│ │ │ ├── InfoCard.js
│ │ │ ├── JumpToCommentButton.js
│ │ │ ├── JumpToTopButton.js
│ │ │ ├── LoadingCover.js
│ │ │ ├── Logo.js
│ │ │ ├── MenuGroupCard.js
│ │ │ ├── MenuItemCollapse.js
│ │ │ ├── MenuItemDrop.js
│ │ │ ├── MenuItemNormal.js
│ │ │ ├── MenuList.js
│ │ │ ├── MenuListSide.js
│ │ │ ├── MenuListTop.js
│ │ │ ├── NavButtonGroup.js
│ │ │ ├── PaginationNumber.js
│ │ │ ├── PaginationSimple.js
│ │ │ ├── PostHero.js
│ │ │ ├── Progress.js
│ │ │ ├── RightFloatButtons.js
│ │ │ ├── SearchButton.js
│ │ │ ├── SearchDrawer.js
│ │ │ ├── SearchInput.js
│ │ │ ├── SearchNav.js
│ │ │ ├── SideBar.js
│ │ │ ├── SocialButton.js
│ │ │ ├── TagGroups.js
│ │ │ ├── TagItemMiddle.js
│ │ │ ├── TagItemMini.js
│ │ │ ├── TocDrawer.js
│ │ │ └── TocDrawerButton.js
│ │ ├── config.js
│ │ ├── index.js
│ │ └── style.js
│ ├── medium/
│ │ ├── components/
│ │ │ ├── Announcement.js
│ │ │ ├── ArticleAround.js
│ │ │ ├── ArticleInfo.js
│ │ │ ├── ArticleLock.js
│ │ │ ├── BlogArchiveItem.js
│ │ │ ├── BlogPostBar.js
│ │ │ ├── BlogPostCard.js
│ │ │ ├── BlogPostListEmpty.js
│ │ │ ├── BlogPostListPage.js
│ │ │ ├── BlogPostListScroll.js
│ │ │ ├── BottomMenuBar.js
│ │ │ ├── Card.js
│ │ │ ├── Catalog.js
│ │ │ ├── CategoryGroup.js
│ │ │ ├── CategoryItem.js
│ │ │ ├── Footer.js
│ │ │ ├── InfoCard.js
│ │ │ ├── JumpToTopButton.js
│ │ │ ├── LeftMenuBar.js
│ │ │ ├── LoadingCover.js
│ │ │ ├── LogoBar.js
│ │ │ ├── MenuBarMobile.js
│ │ │ ├── MenuItemCollapse.js
│ │ │ ├── MenuItemDrop.js
│ │ │ ├── MenuItemMobileNormal.js
│ │ │ ├── MenuItemPCNormal.js
│ │ │ ├── PaginationSimple.js
│ │ │ ├── Progress.js
│ │ │ ├── RevolverMaps.js
│ │ │ ├── SearchInput.js
│ │ │ ├── SocialButton.js
│ │ │ ├── TagGroups.js
│ │ │ ├── TagItemMini.js
│ │ │ ├── TocDrawer.js
│ │ │ └── TopNavBar.js
│ │ ├── config.js
│ │ ├── index.js
│ │ └── style.js
│ ├── movie/
│ │ ├── components/
│ │ │ ├── Announcement.js
│ │ │ ├── ArchiveDateList.js
│ │ │ ├── ArticleInfo.js
│ │ │ ├── ArticleLock.js
│ │ │ ├── BlogListGroupByDate.js
│ │ │ ├── BlogListPage.js
│ │ │ ├── BlogListScroll.js
│ │ │ ├── BlogPostCard.js
│ │ │ ├── BlogRecommend.js
│ │ │ ├── CategoryGroup.js
│ │ │ ├── CategoryItem.js
│ │ │ ├── ExampleRecentComments.js
│ │ │ ├── Footer.js
│ │ │ ├── Header.js
│ │ │ ├── HomeBackgroundImage.js
│ │ │ ├── JumpToTopButton.js
│ │ │ ├── LatestPostsGroup.js
│ │ │ ├── LoadingCover.js
│ │ │ ├── MenuItemCollapse.js
│ │ │ ├── MenuItemDrop.js
│ │ │ ├── NormalMenuItem.js
│ │ │ ├── PaginationNumber.js
│ │ │ ├── SearchInput.js
│ │ │ ├── SideBar.js
│ │ │ ├── SlotBar.js
│ │ │ ├── TagGroups.js
│ │ │ ├── TagItem.js
│ │ │ ├── TagItemMini.js
│ │ │ └── Title.js
│ │ ├── config.js
│ │ ├── index.js
│ │ └── style.js
│ ├── nav/
│ │ ├── components/
│ │ │ ├── Announcement.js
│ │ │ ├── ArticleAround.js
│ │ │ ├── ArticleInfo.js
│ │ │ ├── ArticleLock.js
│ │ │ ├── BlogArchiveItem.js
│ │ │ ├── BlogPostCard.js
│ │ │ ├── BlogPostItem.js
│ │ │ ├── BlogPostListAll.js
│ │ │ ├── BlogPostListEmpty.js
│ │ │ ├── BlogPostListPage.js
│ │ │ ├── BottomMenuBar.js
│ │ │ ├── Card.js
│ │ │ ├── Catalog.js
│ │ │ ├── CategoryGroup.js
│ │ │ ├── CategoryItem.js
│ │ │ ├── Collapse.js
│ │ │ ├── FloatButtonCatalog.js
│ │ │ ├── Footer.js
│ │ │ ├── InfoCard.js
│ │ │ ├── JumpToTopButton.js
│ │ │ ├── LeftMenuBar.js
│ │ │ ├── LoadingCover.js
│ │ │ ├── LogoBar.js
│ │ │ ├── MenuBarMobile.js
│ │ │ ├── MenuItem.js
│ │ │ ├── MenuItemCollapse.js
│ │ │ ├── MenuItemDrop.js
│ │ │ ├── MenuItemMobileNormal.js
│ │ │ ├── MenuItemPCNormal.js
│ │ │ ├── NavPostItem.js
│ │ │ ├── NavPostList.js
│ │ │ ├── NavPostListEmpty.js
│ │ │ ├── NotionIcon.js
│ │ │ ├── PageNavDrawer.js
│ │ │ ├── PaginationSimple.js
│ │ │ ├── Progress.js
│ │ │ ├── RevolverMaps.js
│ │ │ ├── SearchInput.js
│ │ │ ├── SocialButton.js
│ │ │ ├── TagGroups.js
│ │ │ ├── TagItemMini.js
│ │ │ ├── TocDrawer.js
│ │ │ └── TopNavBar.js
│ │ ├── config.js
│ │ ├── index.js
│ │ └── style.js
│ ├── next/
│ │ ├── components/
│ │ │ ├── Announcement.js
│ │ │ ├── ArticleCopyright.js
│ │ │ ├── ArticleDetail.js
│ │ │ ├── ArticleLock.js
│ │ │ ├── BlogAround.js
│ │ │ ├── BlogListBar.js
│ │ │ ├── BlogPostArchive.js
│ │ │ ├── BlogPostCard.js
│ │ │ ├── BlogPostListEmpty.js
│ │ │ ├── BlogPostListPage.js
│ │ │ ├── BlogPostListScroll.js
│ │ │ ├── Card.js
│ │ │ ├── CategoryGroup.js
│ │ │ ├── CategoryList.js
│ │ │ ├── ContactButton.js
│ │ │ ├── DarkModeButton.js
│ │ │ ├── FloatDarkModeButton.js
│ │ │ ├── Footer.js
│ │ │ ├── InfoCard.js
│ │ │ ├── JumpToBottomButton.js
│ │ │ ├── JumpToTopButton.js
│ │ │ ├── LatestPostsGroup.js
│ │ │ ├── LeftFloatButton.js
│ │ │ ├── Live2DWaifu.js
│ │ │ ├── LoadingCover.js
│ │ │ ├── Logo.js
│ │ │ ├── MenuItemCollapse.js
│ │ │ ├── MenuItemDrop.js
│ │ │ ├── MenuList.js
│ │ │ ├── NextRecentComments.js
│ │ │ ├── PaginationNumber.js
│ │ │ ├── PaginationSimple.js
│ │ │ ├── Progress.js
│ │ │ ├── RecommendPosts.js
│ │ │ ├── RewardButton.js
│ │ │ ├── SearchDrawer.js
│ │ │ ├── SearchInput.js
│ │ │ ├── SideAreaLeft.js
│ │ │ ├── SideAreaRight.js
│ │ │ ├── SideBar.js
│ │ │ ├── SideBarDrawer.js
│ │ │ ├── SocialButton.js
│ │ │ ├── StickyBar.js
│ │ │ ├── TagGroups.js
│ │ │ ├── TagItem.js
│ │ │ ├── TagItemMini.js
│ │ │ ├── TagList.js
│ │ │ ├── Toc.js
│ │ │ ├── TocDrawer.js
│ │ │ ├── TocDrawerButton.js
│ │ │ └── TopNav.js
│ │ ├── config.js
│ │ ├── index.js
│ │ └── style.js
│ ├── nobelium/
│ │ ├── components/
│ │ │ ├── Announcement.js
│ │ │ ├── ArticleFooter.js
│ │ │ ├── ArticleInfo.js
│ │ │ ├── ArticleLock.js
│ │ │ ├── BlogArchiveItem.js
│ │ │ ├── BlogListBar.js
│ │ │ ├── BlogListPage.js
│ │ │ ├── BlogListScroll.js
│ │ │ ├── BlogPost.js
│ │ │ ├── Catalog.js
│ │ │ ├── ExampleRecentComments.js
│ │ │ ├── Footer.js
│ │ │ ├── JumpToTopButton.js
│ │ │ ├── MenuItemCollapse.js
│ │ │ ├── MenuItemDrop.js
│ │ │ ├── Nav.js
│ │ │ ├── RandomPostButton.js
│ │ │ ├── SearchButton.js
│ │ │ ├── SearchInput.js
│ │ │ ├── SearchNavBar.js
│ │ │ ├── SideBar.js
│ │ │ ├── SvgIcon.js
│ │ │ ├── TagItem.js
│ │ │ ├── Tags.js
│ │ │ └── Title.js
│ │ ├── config.js
│ │ ├── index.js
│ │ └── style.js
│ ├── photo/
│ │ ├── components/
│ │ │ ├── Announcement.js
│ │ │ ├── ArchiveDateList.js
│ │ │ ├── ArticleFooter.js
│ │ │ ├── ArticleInfo.js
│ │ │ ├── ArticleLock.js
│ │ │ ├── BlogListGroupByDate.js
│ │ │ ├── BlogListPage.js
│ │ │ ├── BlogListScroll.js
│ │ │ ├── BlogPostCard.js
│ │ │ ├── BlogRecommend.js
│ │ │ ├── CategoryGroup.js
│ │ │ ├── CategoryItem.js
│ │ │ ├── ExampleRecentComments.js
│ │ │ ├── Footer.js
│ │ │ ├── Header.js
│ │ │ ├── HomeBackgroundImage.js
│ │ │ ├── JumpToTopButton.js
│ │ │ ├── LatestPostsGroup.js
│ │ │ ├── LoadingCover.js
│ │ │ ├── MenuHierarchical.js
│ │ │ ├── MenuItemCollapse.js
│ │ │ ├── MenuItemDrop.js
│ │ │ ├── NormalMenuItem.js
│ │ │ ├── PaginationNumber.js
│ │ │ ├── PostItemCard.js
│ │ │ ├── SearchInput.js
│ │ │ ├── SideBar.js
│ │ │ ├── SlotBar.js
│ │ │ ├── Swiper.js
│ │ │ ├── TagGroups.js
│ │ │ ├── TagItem.js
│ │ │ ├── TagItemMini.js
│ │ │ └── Title.js
│ │ ├── config.js
│ │ ├── index.js
│ │ └── style.js
│ ├── plog/
│ │ ├── components/
│ │ │ ├── Announcement.js
│ │ │ ├── ArticleFooter.js
│ │ │ ├── ArticleInfo.js
│ │ │ ├── ArticleLock.js
│ │ │ ├── BlogArchiveItem.js
│ │ │ ├── BlogListPage.js
│ │ │ ├── BlogListScroll.js
│ │ │ ├── BlogPost.js
│ │ │ ├── BottomNav.js
│ │ │ ├── ExampleRecentComments.js
│ │ │ ├── Footer.js
│ │ │ ├── InformationButton.js
│ │ │ ├── JumpToTopButton.js
│ │ │ ├── LogoBar.js
│ │ │ ├── MenuItemCollapse.js
│ │ │ ├── MenuItemDrop.js
│ │ │ ├── Modal.js
│ │ │ ├── Nav.js
│ │ │ ├── SearchInput.js
│ │ │ ├── SearchNavBar.js
│ │ │ ├── SideBar.js
│ │ │ ├── SlideOvers.js
│ │ │ ├── SocialButton.js
│ │ │ ├── SvgIcon.js
│ │ │ ├── TagItem.js
│ │ │ ├── Tags.js
│ │ │ └── Title.js
│ │ ├── config.js
│ │ ├── index.js
│ │ └── style.js
│ ├── proxio/
│ │ ├── components/
│ │ │ ├── Announcement.js
│ │ │ ├── ArticleLock.js
│ │ │ ├── BackToTopButton.js
│ │ │ ├── Banner.js
│ │ │ ├── Blog.js
│ │ │ ├── Brand.js
│ │ │ ├── CTA.js
│ │ │ ├── Career.js
│ │ │ ├── DarkModeButton.js
│ │ │ ├── FAQ.js
│ │ │ ├── Features.js
│ │ │ ├── Footer.js
│ │ │ ├── Header.js
│ │ │ ├── Hero.js
│ │ │ ├── LoadingCover.js
│ │ │ ├── Logo.js
│ │ │ ├── MadeWithButton.js
│ │ │ ├── MenuItem.js
│ │ │ ├── MenuList.js
│ │ │ ├── MessageForm.js
│ │ │ ├── Pricing.js
│ │ │ ├── SearchInput.js
│ │ │ ├── SignInForm.js
│ │ │ ├── SignUpForm.js
│ │ │ ├── SocialButton.js
│ │ │ ├── Team.js
│ │ │ ├── Testimonials.js
│ │ │ └── svg/
│ │ │ ├── SVG404.js
│ │ │ ├── SVGAvatarBG.js
│ │ │ ├── SVGCircleBG.js
│ │ │ ├── SVGCircleBG2.js
│ │ │ ├── SVGCircleBG3.js
│ │ │ ├── SVGDesign.js
│ │ │ ├── SVGEmail.js
│ │ │ ├── SVGEssential.js
│ │ │ ├── SVGFacebook.js
│ │ │ ├── SVGFooterCircleBG.js
│ │ │ ├── SVGGifts.js
│ │ │ ├── SVGGoogle.js
│ │ │ ├── SVGInstagram.js
│ │ │ ├── SVGLeftArrow.js
│ │ │ ├── SVGLocation.js
│ │ │ ├── SVGPlayAstro.js
│ │ │ ├── SVGPlayBoostrap.js
│ │ │ ├── SVGPlayNext.js
│ │ │ ├── SVGPlayReact.js
│ │ │ ├── SVGPlayTailWind.js
│ │ │ ├── SVGQuestion.js
│ │ │ ├── SVGRightArrow.js
│ │ │ ├── SVGTemplate.js
│ │ │ └── SVGTwitter.js
│ │ ├── config.js
│ │ ├── index.js
│ │ └── style.js
│ ├── simple/
│ │ ├── components/
│ │ │ ├── Announcement.js
│ │ │ ├── ArticleAround.js
│ │ │ ├── ArticleInfo.js
│ │ │ ├── ArticleLock.js
│ │ │ ├── BlogArchiveItem.js
│ │ │ ├── BlogItem.js
│ │ │ ├── BlogListPage.js
│ │ │ ├── BlogListScroll.js
│ │ │ ├── BlogPostBar.js
│ │ │ ├── Catalog.js
│ │ │ ├── ExampleRecentComments.js
│ │ │ ├── Footer.js
│ │ │ ├── Header.js
│ │ │ ├── JumpToTopButton.js
│ │ │ ├── MenuItemCollapse.js
│ │ │ ├── MenuItemDrop.js
│ │ │ ├── MenuList.js
│ │ │ ├── NavBar.js
│ │ │ ├── RecommendPosts.js
│ │ │ ├── SearchInput.js
│ │ │ ├── SideBar.js
│ │ │ ├── SocialButton.js
│ │ │ ├── Title.js
│ │ │ └── TopBar.js
│ │ ├── config.js
│ │ ├── index.js
│ │ └── style.js
│ ├── starter/
│ │ ├── components/
│ │ │ ├── About.js
│ │ │ ├── ArticleLock.js
│ │ │ ├── BackToTopButton.js
│ │ │ ├── Banner.js
│ │ │ ├── Blog.js
│ │ │ ├── Brand.js
│ │ │ ├── CTA.js
│ │ │ ├── Contact.js
│ │ │ ├── DarkModeButton.js
│ │ │ ├── FAQ.js
│ │ │ ├── Features.js
│ │ │ ├── Footer.js
│ │ │ ├── Header.js
│ │ │ ├── Hero.js
│ │ │ ├── Logo.js
│ │ │ ├── MadeWithButton.js
│ │ │ ├── MenuItem.js
│ │ │ ├── MenuList.js
│ │ │ ├── MessageForm.js
│ │ │ ├── Pricing.js
│ │ │ ├── SearchInput.js
│ │ │ ├── SignInForm.js
│ │ │ ├── SignUpForm.js
│ │ │ ├── SocialButton.js
│ │ │ ├── Team.js
│ │ │ ├── Testimonials.js
│ │ │ └── svg/
│ │ │ ├── SVG404.js
│ │ │ ├── SVGAvatarBG.js
│ │ │ ├── SVGCircleBG.js
│ │ │ ├── SVGCircleBG2.js
│ │ │ ├── SVGCircleBG3.js
│ │ │ ├── SVGDesign.js
│ │ │ ├── SVGEmail.js
│ │ │ ├── SVGEssential.js
│ │ │ ├── SVGFacebook.js
│ │ │ ├── SVGFooterCircleBG.js
│ │ │ ├── SVGGifts.js
│ │ │ ├── SVGGoogle.js
│ │ │ ├── SVGInstagram.js
│ │ │ ├── SVGLeftArrow.js
│ │ │ ├── SVGLocation.js
│ │ │ ├── SVGPlayAstro.js
│ │ │ ├── SVGPlayBoostrap.js
│ │ │ ├── SVGPlayNext.js
│ │ │ ├── SVGPlayReact.js
│ │ │ ├── SVGPlayTailWind.js
│ │ │ ├── SVGQuestion.js
│ │ │ ├── SVGRightArrow.js
│ │ │ ├── SVGTemplate.js
│ │ │ └── SVGTwitter.js
│ │ ├── config.js
│ │ ├── index.js
│ │ └── style.js
│ ├── theme.js
│ └── typography/
│ ├── components/
│ │ ├── ArticleAround.js
│ │ ├── ArticleInfo.js
│ │ ├── ArticleLock.js
│ │ ├── BlogArchiveItem.js
│ │ ├── BlogItem.js
│ │ ├── BlogListPage.js
│ │ ├── BlogListScroll.js
│ │ ├── BlogPostBar.js
│ │ ├── Catalog.js
│ │ ├── ExampleRecentComments.js
│ │ ├── Footer.js
│ │ ├── JumpToTopButton.js
│ │ ├── MenuItemCollapse.js
│ │ ├── MenuItemDrop.js
│ │ ├── MenuList.js
│ │ ├── NavBar.js
│ │ ├── RecommendPosts.js
│ │ ├── SocialButton.js
│ │ ├── Title.js
│ │ └── TopBar.js
│ ├── config.js
│ ├── index.js
│ └── style.js
├── tsconfig.eslint.json
├── tsconfig.json
├── types/
│ └── index.ts
├── validation-report.json
└── vercel.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .dockerignore
================================================
.next*
================================================
FILE: .eslintrc.js
================================================
module.exports = {
env: {
browser: true,
es2021: true,
node: true
},
extends: [
'plugin:react/jsx-runtime',
'plugin:react/recommended',
'plugin:@next/next/recommended',
'next',
'prettier',
'plugin:@typescript-eslint/recommended', // 添加 TypeScript 推荐规则
'plugin:@typescript-eslint/recommended-requiring-type-checking' // 添加需要类型检查的规则
],
parser: '@typescript-eslint/parser', // 使用 TypeScript 解析器
parserOptions: {
ecmaFeatures: {
jsx: true
},
ecmaVersion: 12,
sourceType: 'module',
project: './tsconfig.eslint.json' // 指向新的 ESLint 配置文件
},
plugins: [
'react',
'react-hooks',
'prettier',
'@typescript-eslint' // 添加 TypeScript 插件
],
settings: {
react: {
version: 'detect'
}
},
rules: {
semi: 0,
'react/no-unknown-property': 'off', // 
"
)
}
}
}
// check document ready
function docReady(t) {
document.readyState === 'complete' ||
document.readyState === 'interactive'
? setTimeout(() => t(), 1)
: document.addEventListener('DOMContentLoaded', t)
}
// check if wwads' fire function was blocked after document is ready with 3s timeout (waiting the ad loading)
docReady(function () {
setTimeout(function () {
if (window._AdBlockInit === undefined) {
ABDetected()
}
}, 3000)
})
}, [])
return null
}
================================================
FILE: components/AlgoliaSearchModal.js
================================================
import replaceSearchResult from '@/components/Mark'
import { siteConfig } from '@/lib/config'
import { useGlobal } from '@/lib/global'
import algoliasearch from 'algoliasearch'
import throttle from 'lodash/throttle'
import SmartLink from '@/components/SmartLink'
import { useRouter } from 'next/router'
import {
Fragment,
useEffect,
useImperativeHandle,
useRef,
useState
} from 'react'
import { useHotkeys } from 'react-hotkeys-hook'
const ShortCutActions = [
{
key: '↑ ↓',
action: '选择'
},
{
key: 'Enter',
action: '跳转'
},
{
key: 'Esc',
action: '关闭'
}
]
/**
* 结合 Algolia 实现的弹出式搜索框
* 打开方式 cRef.current.openSearch()
* https://www.algolia.com/doc/api-reference/search-api-parameters/
*/
export default function AlgoliaSearchModal({ cRef }) {
const [searchResults, setSearchResults] = useState([])
const [isModalOpen, setIsModalOpen] = useState(false)
const [page, setPage] = useState(0)
const [keyword, setKeyword] = useState(null)
const [totalPage, setTotalPage] = useState(0)
const [totalHit, setTotalHit] = useState(0)
const [useTime, setUseTime] = useState(0)
const [activeIndex, setActiveIndex] = useState(0)
const [isLoading, setIsLoading] = useState(false)
const [isInputFocused, setIsInputFocused] = useState(false)
const inputRef = useRef(null)
const router = useRouter()
/**
* 快捷键设置
*/
useHotkeys('ctrl+k', e => {
e.preventDefault()
setIsModalOpen(true)
})
// 修改快捷键的使用逻辑
useHotkeys(
'down',
e => {
if (isInputFocused) {
// 只有在聚焦时才触发
e.preventDefault()
if (activeIndex < searchResults.length - 1) {
setActiveIndex(activeIndex + 1)
}
}
},
{ enableOnFormTags: true }
)
useHotkeys(
'up',
e => {
if (isInputFocused) {
e.preventDefault()
if (activeIndex > 0) {
setActiveIndex(activeIndex - 1)
}
}
},
{ enableOnFormTags: true }
)
useHotkeys(
'esc',
e => {
if (isInputFocused) {
e.preventDefault()
setIsModalOpen(false)
}
},
{ enableOnFormTags: true }
)
useHotkeys(
'enter',
e => {
if (isInputFocused && searchResults.length > 0) {
onJumpSearchResult(index)
}
},
{ enableOnFormTags: true }
)
// 跳转Search结果
const onJumpSearchResult = () => {
if (searchResults.length > 0) {
const searchResult = searchResults[activeIndex]
window.location.href = `${siteConfig('SUB_PATH', '')}/${searchResult.slug || searchResult.objectID}`
}
}
const resetSearch = () => {
setActiveIndex(0)
setKeyword('')
setSearchResults([])
setUseTime(0)
setTotalPage(0)
setTotalHit(0)
if (inputRef.current) inputRef.current.value = ''
}
/**
* 页面路径变化后,自动关闭此modal
*/
useEffect(() => {
setIsModalOpen(false)
}, [router])
/**
* 自动聚焦搜索框
*/
useEffect(() => {
if (isModalOpen) {
setTimeout(() => {
inputRef.current?.focus()
}, 100)
} else {
resetSearch()
}
}, [isModalOpen])
/**
* 对外暴露方法
**/
useImperativeHandle(cRef, () => {
return {
openSearch: () => {
setIsModalOpen(true)
}
}
})
const client = algoliasearch(
siteConfig('ALGOLIA_APP_ID'),
siteConfig('ALGOLIA_SEARCH_ONLY_APP_KEY')
)
const index = client.initIndex(siteConfig('ALGOLIA_INDEX'))
/**
* 搜索
* @param {*} query
*/
const handleSearch = async (query, page) => {
setKeyword(query)
setPage(page)
setSearchResults([])
setUseTime(0)
setTotalPage(0)
setTotalHit(0)
setActiveIndex(0)
if (!query || query === '') {
return
}
setIsLoading(true)
try {
const res = await index.search(query, { page, hitsPerPage: 10 })
const { hits, nbHits, nbPages, processingTimeMS } = res
setUseTime(processingTimeMS)
setTotalPage(nbPages)
setTotalHit(nbHits)
setSearchResults(hits)
setIsLoading(false)
const doms = document
.getElementById('search-wrapper')
.getElementsByClassName('replace')
setTimeout(() => {
replaceSearchResult({
doms,
search: query,
target: {
element: 'span',
className: 'font-bold border-b border-dashed'
}
})
}, 200) // 延时高亮
} catch (error) {
console.error('Algolia search error:', error)
}
}
// 定义节流函数,确保在用户停止输入一段时间后才会调用处理搜索的方法
const throttledHandleInputChange = useRef(
throttle((query, page = 0) => {
handleSearch(query, page)
}, 1000)
)
// 用于存储搜索延迟的计时器
const searchTimer = useRef(null)
// 修改input的onChange事件处理函数
const handleInputChange = e => {
const query = e.target.value
// 如果已经有计时器在等待搜索,先清除之前的计时器
if (searchTimer.current) {
clearTimeout(searchTimer.current)
}
// 设置新的计时器,在用户停止输入一段时间后触发搜索
searchTimer.current = setTimeout(() => {
throttledHandleInputChange.current(query)
}, 800)
}
/**
* 切换页码
* @param {*} page
*/
const switchPage = page => {
throttledHandleInputChange.current(keyword, page)
}
/**
* 关闭弹窗
*/
const closeModal = () => {
setIsModalOpen(false)
}
if (!siteConfig('ALGOLIA_APP_ID')) {
return <>>
}
return (
{/* 模态框 */}
handleInputChange(e)}
onFocus={() => setIsInputFocused(true)} // 聚焦时
onBlur={() => setIsInputFocused(false)} // 失去焦点时
className='text-black dark:text-gray-200 bg-gray-50 dark:bg-gray-600 outline-blue-500 w-full px-4 my-2 py-1 mb-4 border rounded-md'
ref={inputRef}
/>
{/* 标签组 */}
{searchResults.length === 0 && keyword && !isLoading && (
{' '}
无法找到相关结果
"{keyword}"
)}
{searchResults.map((result, index) => (
- setActiveIndex(index)}
onClick={() => onJumpSearchResult(index)}
className={`cursor-pointer replace my-2 p-2 duration-100
rounded-lg
${activeIndex === index ? 'bg-blue-600 dark:bg-yellow-600' : ''}`}>
{result.title}
))}
{totalHit === 0 && (
{ShortCutActions.map((action, index) => {
return (
{action.key}
{action.action}
)
})}
)}
{totalHit > 0 && (
共搜索到 {totalHit} 条结果,用时 {useTime} 毫秒
)}
Algolia 提供搜索服务
{/* 遮罩 */}
)
}
/**
* 标签组
*/
function TagGroups() {
const { tagOptions } = useGlobal()
// 获取tagOptions数组前十个
const firstTenTags = tagOptions?.slice(0, 10)
return (
)
}
/**
* 分页
* @param {*} param0
*/
function Pagination(props) {
const { totalPage, page, switchPage } = props
if (totalPage <= 0) {
return <>>
}
return (
{Array.from({ length: totalPage }, (_, i) => {
const classNames =
page === i
? 'font-bold text-white bg-blue-600 dark:bg-yellow-600 rounded'
: 'hover:text-blue-600 hover:font-bold dark:text-gray-300'
return (
switchPage(i)}
className={`text-center cursor-pointer w-6 h-6 ${classNames}`}
key={i}>
{i + 1}
)
})}
)
}
================================================
FILE: components/AnalyticsBusuanzi.js
================================================
/**
* 不蒜子统计 访客和阅读量
* @returns
*/
export default function AnalyticsBusuanzi() {
return (
)
}
================================================
FILE: components/Artalk.js
================================================
import { siteConfig } from '@/lib/config'
import { loadExternalResource } from '@/lib/utils'
import { useEffect } from 'react'
/**
* Artalk 自托管评论系统 @see https://artalk.js.org/
* @returns {JSX.Element}
* @constructor
*/
const Artalk = ({ siteInfo }) => {
const artalkCss = siteConfig('COMMENT_ARTALK_CSS')
const artalkServer = siteConfig('COMMENT_ARTALK_SERVER')
const artalkLocale = siteConfig('LANG')
const site = siteConfig('TITLE')
useEffect(() => {
initArtalk()
}, [])
const initArtalk = async () => {
await loadExternalResource(artalkCss, 'css')
const artalk = window?.Artalk?.init({
server: artalkServer,
el: '#artalk',
locale: artalkLocale,
site: site,
darkMode: document.documentElement.classList.contains('dark')
})
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.attributeName === 'class') {
const isDark = document.documentElement.classList.contains('dark')
artalk?.setDarkMode(isDark)
}
})
})
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ['class']
})
return () => observer.disconnect()
}
return
}
export default Artalk
================================================
FILE: components/ArticleExpirationNotice.js
================================================
import { siteConfig } from '@/lib/config'
/**
* 文章过期提醒组件
* 当文章超过指定天数时显示提醒
* @param {Object} props - 组件属性
* @param {Object} props.post - 文章数据
* @param {number} [props.daysThreshold=90] - 过期阈值(天)
* @returns {JSX.Element|null}
*/
export default function ArticleExpirationNotice({
post,
daysThreshold = siteConfig('ARTICLE_EXPIRATION_DAYS', 90)
}) {
const articleExpirationEnabled = siteConfig(
'ARTICLE_EXPIRATION_ENABLED',
false
)
if (!articleExpirationEnabled || !post?.lastEditedDay) {
return null
}
const postDate = new Date(post.lastEditedDay)
const today = new Date()
const diffTime = Math.abs(today - postDate)
const daysOld = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
const isVisible = daysOld >= daysThreshold
if (!isVisible) {
return null
}
// 使用 %%DAYS%% 作为占位符
const articleExpirationMessage = siteConfig(
'ARTICLE_EXPIRATION_MESSAGE',
'这篇文章发布于 %%DAYS%% 天前,内容可能已过时,请谨慎参考。'
)
const articleExpirationMessageParts =
articleExpirationMessage.split('%%DAYS%%')
// 直接返回 JSX 内容
return (
{siteConfig('ARTICLE_EXPIRATION_TITLE', '温馨提醒')}
{(() => {
return (
<>
{articleExpirationMessageParts[0]}
{daysOld}
{articleExpirationMessageParts[1]}
>
)
})()}
)
}
================================================
FILE: components/Badge.js
================================================
/**
* 红点
*/
export default function Badge() {
return <>
{/* 红点 */}
>
}
================================================
FILE: components/BeiAnGongAn.tsx
================================================
import { siteConfig } from '@/lib/config'
import LazyImage from './LazyImage'
import React from 'react'
interface BeiAnGongAnProps {
className?: string
/**
* 自定义图标路径,默认为'/images/gongan.png'
*/
iconPath?: string
/**
* 自定义图标尺寸,默认为15
*/
iconSize?: number
}
/**
* 公安备案号组件
* @param {BeiAnGongAnProps} props - 组件属性
* @returns {JSX.Element | null} 返回公安备案号组件或null
*/
export const BeiAnGongAn: React.FC = ({
className = '',
iconPath = '/images/gongan.png',
iconSize = 15
}: BeiAnGongAnProps): JSX.Element | null => {
const BEI_AN_GONGAN = siteConfig('BEI_AN_GONGAN') as string | null | undefined
// 更精确的正则匹配,匹配类似"京公网安备11010502030143号"中的数字部分
const codeMatch = BEI_AN_GONGAN && String(BEI_AN_GONGAN).match(/(\d+)号?$/)
const code = codeMatch?.[1] ?? null
// 如果code无效则不渲染
if (!BEI_AN_GONGAN || !code) {
return null
}
const href = `https://beian.mps.gov.cn/#/query/webSearch?code=${code}`
return (
)
}
BeiAnGongAn.displayName = 'BeiAnGongAn'
================================================
FILE: components/BeiAnSite.js
================================================
import { siteConfig } from '@/lib/config'
/**
* 站点域名备案
* @returns
*/
export default function BeiAnSite() {
const beian = siteConfig('BEI_AN')
const beianLink = siteConfig('BEI_AN_LINK')
if (!beian) {
return null
}
return (
{beian}
)
}
================================================
FILE: components/Busuanzi.js
================================================
import busuanzi from '@/lib/plugins/busuanzi'
import { useRouter } from 'next/router'
import { useGlobal } from '@/lib/global'
// import { useRouter } from 'next/router'
import { useEffect } from 'react'
let path = ''
export default function Busuanzi () {
const { theme } = useGlobal()
const router = useRouter()
router.events.on('routeChangeComplete', (url, option) => {
if (url !== path) {
path = url
busuanzi.fetch()
}
})
// 更换主题时更新
useEffect(() => {
if (theme) {
busuanzi.fetch()
}
}, [theme])
return null
}
================================================
FILE: components/CanvasEmail.js
================================================
import { useEffect, useRef, useState } from 'react'
const CanvasEmail = ({ email, className = '' }) => {
const canvasRef = useRef(null)
const textRef = useRef(null)
const [isCopied, setIsCopied] = useState(false)
useEffect(() => {
if (!textRef.current || !canvasRef.current) return
const canvas = canvasRef.current
const ctx = canvas.getContext('2d')
const textElement = textRef.current
// Get computed styles from the hidden text element
const style = window.getComputedStyle(textElement)
const font = style.font
const color = style.color
// Set canvas font and measure text
ctx.font = font
const metrics = ctx.measureText(email)
const fontSize = parseInt(style.fontSize)
const lineHeight = fontSize * 1.2
// Set canvas dimensions
const scale = window.devicePixelRatio || 1
canvas.width = metrics.width * scale
canvas.height = lineHeight * scale
canvas.style.width = `${metrics.width}px`
canvas.style.height = `${lineHeight}px`
// Redraw with high DPI support
ctx.scale(scale, scale)
ctx.font = font
ctx.fillStyle = color
ctx.textBaseline = 'top' // Changed to 'top' for better vertical alignment
ctx.fillText(email, 0, 0)
// Handle copy to clipboard
const handleCopy = e => {
e.preventDefault()
navigator.clipboard.writeText(email).then(() => {
setIsCopied(true)
setTimeout(() => setIsCopied(false), 2000)
})
}
canvas.style.cursor = 'pointer'
canvas.addEventListener('click', handleCopy)
return () => canvas.removeEventListener('click', handleCopy)
}, [email])
return (
{/* Hidden span for measuring text metrics */}
{/* Canvas that displays the text */}
)
}
export default CanvasEmail
================================================
FILE: components/ChatBase.js
================================================
import { siteConfig } from '@/lib/config'
/**
* 这是一个嵌入组件,可以在任意位置全屏显示您的chat-base对话框
* 暂时没有页面引用
* 因为您可以直接用内嵌网页的方式放入您的notion中 https://www.chatbase.co/chatbot-iframe/${siteConfig('CHATBASE_ID')}
*/
export default function ChatBase() {
if (!siteConfig('CHATBASE_ID')) {
return <>>
}
return
}
================================================
FILE: components/Collapse.js
================================================
import { useEffect, useImperativeHandle, useRef } from 'react'
/**
* 折叠面板组件,支持水平折叠、垂直折叠
* @param {type:['horizontal','vertical'], isOpen} props
* @returns
*/
const Collapse = ({
type = 'vertical',
isOpen = false,
children,
onHeightChange,
className,
collapseRef
}) => {
const ref = useRef(null)
useImperativeHandle(collapseRef, () => {
return {
/**
* 当子元素高度变化时,可调用此方法更新折叠组件的高度
* @param {*} param0
*/
updateCollapseHeight: ({ height, increase }) => {
if (isOpen) {
ref.current.style.height = ref.current.scrollHeight
ref.current.style.height = 'auto'
}
}
}
})
/**
* 折叠
* @param {*} element
*/
const collapseSection = element => {
const sectionHeight = element.scrollHeight
const sectionWidth = element.scrollWidth
requestAnimationFrame(function () {
switch (type) {
case 'horizontal':
element.style.width = sectionWidth + 'px'
requestAnimationFrame(function () {
element.style.width = 0 + 'px'
})
break
case 'vertical':
element.style.height = sectionHeight + 'px'
requestAnimationFrame(function () {
element.style.height = 0 + 'px'
})
}
})
}
/**
* 展开
* @param {*} element
*/
const expandSection = element => {
const sectionHeight = element.scrollHeight
const sectionWidth = element.scrollWidth
let clearTime = 0
switch (type) {
case 'horizontal':
element.style.width = sectionWidth + 'px'
clearTime = setTimeout(() => {
element.style.width = 'auto'
}, 400)
break
case 'vertical':
element.style.height = sectionHeight + 'px'
clearTime = setTimeout(() => {
element.style.height = 'auto'
}, 400)
}
clearTimeout(clearTime)
}
useEffect(() => {
if (isOpen) {
expandSection(ref.current)
} else {
collapseSection(ref.current)
}
// 通知父组件高度变化
onHeightChange &&
onHeightChange({
height: ref.current.scrollHeight,
increase: isOpen
})
}, [isOpen])
return (
{children}
)
}
export default Collapse
================================================
FILE: components/Comment.js
================================================
import Tabs from '@/components/Tabs'
import { siteConfig } from '@/lib/config'
import { isBrowser, isSearchEngineBot } from '@/lib/utils'
import dynamic from 'next/dynamic'
import { useRouter } from 'next/router'
import { useEffect, useRef, useState } from 'react'
import Artalk from './Artalk'
/**
* 评论组件
* 只有当前组件在浏览器可见范围内才会加载内容
* @param {*} param0
* @returns
*/
const Comment = ({ frontMatter, className }) => {
const router = useRouter()
const [shouldLoad, setShouldLoad] = useState(false)
const commentRef = useRef(null)
const COMMENT_ARTALK_SERVER = siteConfig('COMMENT_ARTALK_SERVER')
const COMMENT_TWIKOO_ENV_ID = siteConfig('COMMENT_TWIKOO_ENV_ID')
const COMMENT_WALINE_SERVER_URL = siteConfig('COMMENT_WALINE_SERVER_URL')
const COMMENT_VALINE_APP_ID = siteConfig('COMMENT_VALINE_APP_ID')
const COMMENT_GISCUS_REPO = siteConfig('COMMENT_GISCUS_REPO')
const COMMENT_CUSDIS_APP_ID = siteConfig('COMMENT_CUSDIS_APP_ID')
const COMMENT_UTTERRANCES_REPO = siteConfig('COMMENT_UTTERRANCES_REPO')
const COMMENT_GITALK_CLIENT_ID = siteConfig('COMMENT_GITALK_CLIENT_ID')
const COMMENT_WEBMENTION_ENABLE = siteConfig('COMMENT_WEBMENTION_ENABLE')
useEffect(() => {
// Check if the component is visible in the viewport
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
setShouldLoad(true)
observer.unobserve(entry.target)
}
})
})
if (commentRef.current) {
observer.observe(commentRef.current)
}
return () => {
if (commentRef.current) {
observer.unobserve(commentRef.current)
}
}
}, [frontMatter])
// 当连接中有特殊参数时跳转到评论区
if (
isBrowser &&
('giscus' in router.query || router.query.target === 'comment')
) {
setTimeout(() => {
const url = router.asPath.replace('?target=comment', '')
history.replaceState({}, '', url)
document
?.getElementById('comment')
?.scrollIntoView({ block: 'start', behavior: 'smooth' })
}, 1000)
}
if (!frontMatter) {
return null
}
if (isSearchEngineBot) {
return null
}
// 特定文章关闭评论区
if (frontMatter?.comment === 'Hide') {
return null
}
return (
)
}
const WalineComponent = dynamic(
() => {
return import('@/components/WalineComponent')
},
{ ssr: false }
)
const CusdisComponent = dynamic(
() => {
return import('@/components/CusdisComponent')
},
{ ssr: false }
)
const TwikooCompenent = dynamic(
() => {
return import('@/components/Twikoo')
},
{ ssr: false }
)
const GitalkComponent = dynamic(
() => {
return import('@/components/Gitalk')
},
{ ssr: false }
)
const UtterancesComponent = dynamic(
() => {
return import('@/components/Utterances')
},
{ ssr: false }
)
const GiscusComponent = dynamic(
() => {
return import('@/components/Giscus')
},
{ ssr: false }
)
const WebMentionComponent = dynamic(
() => {
return import('@/components/WebMention')
},
{ ssr: false }
)
const ValineComponent = dynamic(() => import('@/components/ValineComponent'), {
ssr: false
})
export default Comment
================================================
FILE: components/CopyRightDate.js
================================================
import { siteConfig } from '@/lib/config'
/**
* 网站版权日期
* 示例: 2021-2024
* @returns
*/
export default function CopyRightDate() {
const d = new Date()
const currentYear = d.getFullYear()
const since = siteConfig('SINCE')
const copyrightDate =
parseInt(since) < currentYear ? since + '-' + currentYear : currentYear
return (
{copyrightDate}
)
}
================================================
FILE: components/Coze.js
================================================
import { siteConfig } from '@/lib/config'
import { loadExternalResource } from '@/lib/utils'
import { useEffect } from 'react'
/**
* Coze-AI机器人
* @returns
*/
export default function Coze() {
const cozeSrc = siteConfig(
'COZE_SRC_URL',
'https://lf-cdn.coze.cn/obj/unpkg/flow-platform/chat-app-sdk/0.1.0-beta.6/libs/cn/index.js'
)
const title = siteConfig('COZE_TITLE', 'NotionNext助手')
const botId = siteConfig('COZE_BOT_ID')
const loadCoze = async () => {
await loadExternalResource(cozeSrc)
const CozeWebSDK = window?.CozeWebSDK
if (CozeWebSDK) {
const cozeClient = new CozeWebSDK.WebChatClient({
config: {
bot_id: botId
},
componentProps: {
title: title
}
})
console.log('coze', cozeClient)
}
}
useEffect(() => {
if (!botId) {
return
}
loadCoze()
}, [])
return <>>
}
================================================
FILE: components/CursorDot.js
================================================
import { useRouter } from 'next/router';
import { useEffect } from 'react';
/**
* 白点鼠标跟随
* @returns
*/
const CursorDot = () => {
const router = useRouter();
useEffect(() => {
// 创建小白点元素
const dot = document.createElement('div');
dot.classList.add('cursor-dot');
document.body.appendChild(dot);
// 鼠标坐标和缓动目标坐标
let mouse = { x: -100, y: -100 }; // 初始位置在屏幕外
let dotPos = { x: mouse.x, y: mouse.y };
// 监听鼠标移动
const handleMouseMove = (e) => {
mouse.x = e.clientX;
mouse.y = e.clientY;
};
document.addEventListener('mousemove', handleMouseMove);
// 监听鼠标悬停在可点击对象上的事件
const handleMouseEnter = () => {
dot.classList.add('cursor-dot-hover'); // 添加放大样式
};
const handleMouseLeave = () => {
dot.classList.remove('cursor-dot-hover'); // 移除放大样式
};
// 为所有可点击元素和包含 hover 或 group-hover 类名的元素添加事件监听
setTimeout(() => {
const clickableElements = document.querySelectorAll(
'a, button, [role="button"], [onclick], [cursor="pointer"], [class*="hover"], [class*="group-hover"], [class*="cursor-pointer"]'
);
clickableElements.forEach((el) => {
el.addEventListener('mouseenter', handleMouseEnter);
el.addEventListener('mouseleave', handleMouseLeave);
});
}, 200); // 延时 200ms 执行
// 动画循环:延迟更新小白点位置
const updateDotPosition = () => {
const damping = 0.2; // 阻尼系数,值越小延迟越明显
dotPos.x += (mouse.x - dotPos.x) * damping;
dotPos.y += (mouse.y - dotPos.y) * damping;
// 更新DOM
dot.style.left = `${dotPos.x}px`;
dot.style.top = `${dotPos.y}px`;
requestAnimationFrame(updateDotPosition);
};
// 启动动画
updateDotPosition();
// 清理函数
return () => {
document.removeEventListener('mousemove', handleMouseMove);
const clickableElements = document.querySelectorAll(
'a, button, [role="button"], [onclick], [cursor="pointer"], [class*="hover"], [class*="group-hover"], [class*="cursor-pointer"]'
);
clickableElements.forEach((el) => {
el.removeEventListener('mouseenter', handleMouseEnter);
el.removeEventListener('mouseleave', handleMouseLeave);
});
document.body.removeChild(dot);
};
}, [router]);
return (
);
};
export default CursorDot;
================================================
FILE: components/CusdisComponent.js
================================================
import { useGlobal } from '@/lib/global'
import { useRouter } from 'next/router'
import { useEffect } from 'react'
import { loadExternalResource } from '@/lib/utils'
import { siteConfig } from '@/lib/config'
const CusdisComponent = ({ frontMatter }) => {
const router = useRouter()
const { isDarkMode, lang } = useGlobal()
const src = siteConfig('COMMENT_CUSDIS_SCRIPT_SRC')
const i18nForCusdis = siteConfig('LANG').toLowerCase().indexOf('zh') === 0 ? siteConfig('LANG').toLowerCase() : siteConfig('LANG').toLowerCase().substring(0, 2)
const langCDN = siteConfig('COMMENT_CUSDIS_LANG_SRC', `https://cusdis.com/js/widget/lang/${i18nForCusdis}.js`)
// 处理cusdis主题
useEffect(() => {
loadCusdis()
}, [isDarkMode, lang])
const loadCusdis = async () => {
await loadExternalResource(langCDN, 'js')
await loadExternalResource(src, 'js')
window?.CUSDIS?.initial()
}
return
}
export default CusdisComponent
================================================
FILE: components/CustomContextMenu.js
================================================
import useWindowSize from '@/hooks/useWindowSize'
import { siteConfig } from '@/lib/config'
import { useGlobal } from '@/lib/global'
import { THEMES, saveDarkModeToLocalStorage } from '@/themes/theme'
import SmartLink from '@/components/SmartLink'
import { useRouter } from 'next/router'
import { useEffect, useLayoutEffect, useRef, useState } from 'react'
/**
* 自定义右键菜单
* @param {*} props
* @returns
*/
export default function CustomContextMenu(props) {
const [position, setPosition] = useState({ x: '0px', y: '0px' })
const [show, setShow] = useState(false)
const { isDarkMode, updateDarkMode, locale } = useGlobal()
const menuRef = useRef(null)
const windowSize = useWindowSize()
const [width, setWidth] = useState(0)
const [height, setHeight] = useState(0)
const { allNavPages } = props
const router = useRouter()
/**
* 随机跳转文章
*/
function handleJumpToRandomPost() {
const randomIndex = Math.floor(Math.random() * allNavPages.length)
const randomPost = allNavPages[randomIndex]
router.push(`${siteConfig('SUB_PATH', '')}/${randomPost?.slug}`)
}
useLayoutEffect(() => {
setWidth(menuRef.current.offsetWidth)
setHeight(menuRef.current.offsetHeight)
}, [])
useEffect(() => {
setShow(false)
}, [router])
useEffect(() => {
const handleContextMenu = event => {
event.preventDefault()
// 计算点击位置加菜单宽高是否超出屏幕,如果超出则贴边弹出
const x =
event.clientX < windowSize.width - width
? event.clientX
: windowSize.width - width
const y =
event.clientY < windowSize.height - height
? event.clientY
: windowSize.height - height
setPosition({ y: `${y}px`, x: `${x}px` })
setShow(true)
}
/**
* 鼠标点击即关闭菜单
*/
const handleClick = event => {
setShow(false)
}
window.addEventListener('contextmenu', handleContextMenu)
window.addEventListener('click', handleClick)
return () => {
window.removeEventListener('contextmenu', handleContextMenu)
window.removeEventListener('click', handleClick)
}
}, [windowSize])
function handleBack() {
window.history.back()
}
function handleForward() {
window.history.forward()
}
function handleRefresh() {
window.location.reload()
}
function handleScrollTop() {
window.scrollTo({ top: 0, behavior: 'smooth' })
}
function handleCopyLink() {
const url = window.location.href
navigator.clipboard
.writeText(url)
.then(() => {
// console.log('页面地址已复制')
alert(`${locale.COMMON.PAGE_URL_COPIED} : ${url}`)
})
.catch(error => {
console.error('复制页面地址失败:', error)
})
}
/**
* 切换主题
*/
function handleChangeTheme() {
const randomTheme = THEMES[Math.floor(Math.random() * THEMES.length)] // 从THEMES数组中 随机取一个主题
const query = router.query
query.theme = randomTheme
router.push({ pathname: router.pathname, query })
}
/**
* 复制内容
*/
function handleCopy() {
const selectedText = document.getSelection().toString()
if (selectedText) {
const tempInput = document.createElement('input');
tempInput.value = selectedText;
document.body.appendChild(tempInput);
tempInput.select();
document.execCommand('copy');
if (tempInput && tempInput.parentNode && tempInput.parentNode.contains(tempInput)) {
tempInput.parentNode.removeChild(tempInput);
}
// alert("Text copied: " + selectedText);
} else {
// alert("Please select some text first.");
}
}
function handleChangeDarkMode() {
const newStatus = !isDarkMode
saveDarkModeToLocalStorage(newStatus)
updateDarkMode(newStatus)
const htmlElement = document.getElementsByTagName('html')[0]
htmlElement.classList?.remove(newStatus ? 'light' : 'dark')
htmlElement.classList?.add(newStatus ? 'dark' : 'light')
}
// 一些配置变量
const CUSTOM_RIGHT_CLICK_CONTEXT_MENU_RANDOM_POST = siteConfig(
'CUSTOM_RIGHT_CLICK_CONTEXT_MENU_RANDOM_POST'
)
const CUSTOM_RIGHT_CLICK_CONTEXT_MENU_CATEGORY = siteConfig(
'CUSTOM_RIGHT_CLICK_CONTEXT_MENU_CATEGORY'
)
const CUSTOM_RIGHT_CLICK_CONTEXT_MENU_TAG = siteConfig(
'CUSTOM_RIGHT_CLICK_CONTEXT_MENU_TAG'
)
const CAN_COPY = siteConfig('CAN_COPY')
const CUSTOM_RIGHT_CLICK_CONTEXT_MENU_SHARE_LINK = siteConfig(
'CUSTOM_RIGHT_CLICK_CONTEXT_MENU_SHARE_LINK'
)
const CUSTOM_RIGHT_CLICK_CONTEXT_MENU_DARK_MODE = siteConfig(
'CUSTOM_RIGHT_CLICK_CONTEXT_MENU_DARK_MODE'
)
const CUSTOM_RIGHT_CLICK_CONTEXT_MENU_THEME_SWITCH = siteConfig(
'CUSTOM_RIGHT_CLICK_CONTEXT_MENU_THEME_SWITCH'
)
return (
{/* 菜单内容 */}
{/* 顶部导航按钮 */}
{/* 跳转导航按钮 */}
{CUSTOM_RIGHT_CLICK_CONTEXT_MENU_RANDOM_POST && (
{locale.MENU.WALK_AROUND}
)}
{CUSTOM_RIGHT_CLICK_CONTEXT_MENU_CATEGORY && (
{locale.MENU.CATEGORY}
)}
{CUSTOM_RIGHT_CLICK_CONTEXT_MENU_TAG && (
{locale.MENU.TAGS}
)}
{/* 功能按钮 */}
{CAN_COPY && (
)}
{CUSTOM_RIGHT_CLICK_CONTEXT_MENU_SHARE_LINK && (
)}
{CUSTOM_RIGHT_CLICK_CONTEXT_MENU_DARK_MODE && (
{isDarkMode ? (
) : (
)}
{' '}
{isDarkMode ? locale.MENU.LIGHT_MODE : locale.MENU.DARK_MODE}
)}
{CUSTOM_RIGHT_CLICK_CONTEXT_MENU_THEME_SWITCH && (
{locale.MENU.THEME_SWITCH}
)}
)
}
================================================
FILE: components/DarkModeButton.js
================================================
import { useGlobal } from '@/lib/global'
import { useImperativeHandle } from 'react'
import { Moon, Sun } from './HeroIcons'
/**
* 深色模式按钮
*/
const DarkModeButton = props => {
const { cRef, className } = props
const { isDarkMode, toggleDarkMode } = useGlobal()
/**
* 对外暴露方法
*/
useImperativeHandle(cRef, () => {
return {
handleChangeDarkMode: () => {
toggleDarkMode()
}
}
})
return (
)
}
export default DarkModeButton
================================================
FILE: components/DebugPanel.js
================================================
import { siteConfigMap } from '@/lib/config'
import { useGlobal } from '@/lib/global'
import { getQueryParam } from '@/lib/utils'
import { THEMES } from '@/themes/theme'
import { useRouter } from 'next/router'
import { useEffect, useState } from 'react'
import Select from './Select'
/**
*
* @returns 调试面板
*/
const DebugPanel = () => {
const [show, setShow] = useState(false)
const { theme, switchTheme, locale } = useGlobal()
const router = useRouter()
const currentTheme = getQueryParam(router.asPath, 'theme') || theme
const [siteConfig, updateSiteConfig] = useState({})
// 主题下拉框
const themeOptions = THEMES?.map(t => ({ value: t, text: t }))
useEffect(() => {
updateSiteConfig(Object.assign({}, siteConfigMap()))
}, [])
function toggleShow() {
setShow(!show)
}
function handleChangeDebugTheme() {
switchTheme()
}
function handleUpdateDebugTheme(newTheme) {
const query = { ...router.query, theme: newTheme }
router.push({ pathname: router.pathname, query })
}
function filterResult(text) {
switch (text) {
case 'true':
return true
case 'false':
return false
case '':
return '-'
}
return text
}
return (
<>
{/* 调试按钮 */}
{show ? (
{locale.COMMON.DEBUG_CLOSE}
) : (
{locale.COMMON.DEBUG_OPEN}
)}
{/* 调试侧拉抽屉 */}
{/*
主题配置{`config_${debugTheme}.js`}:
{Object.keys(themeConfig).map(k => (
{k}
{filterResult(themeConfig[k] + '')}
))}
*/}
站点配置[blog.config.js]
{siteConfig &&
Object.keys(siteConfig).map(k => (
{k}
{filterResult(siteConfig[k] + '')}
))}
>
)
}
export default DebugPanel
================================================
FILE: components/DifyChatbot.js
================================================
import { useEffect } from 'react';
import { siteConfig } from '@/lib/config';
export default function DifyChatbot() {
useEffect(() => {
// 这里使用 siteConfig() 函数调用来获取配置值
if (!siteConfig('DIFY_CHATBOT_ENABLED')) {
return;
}
// 配置 DifyChatbot,同样需要调用 siteConfig() 获取相应的配置值
window.difyChatbotConfig = {
token: siteConfig('DIFY_CHATBOT_TOKEN'),
baseUrl: siteConfig('DIFY_CHATBOT_BASE_URL')
};
// 加载 DifyChatbot 脚本
const script = document.createElement('script');
script.src = `${siteConfig('DIFY_CHATBOT_BASE_URL')}/embed.min.js`; // 注意调用 siteConfig()
script.id = siteConfig('DIFY_CHATBOT_TOKEN'); // 注意调用 siteConfig()
script.defer = true;
document.body.appendChild(script);
return () => {
// 在组件卸载时清理 script 标签
const existingScript = document.getElementById(siteConfig('DIFY_CHATBOT_TOKEN')); // 注意调用 siteConfig()
if (existingScript && existingScript.parentNode && existingScript.parentNode.contains(existingScript)) {
existingScript.parentNode.removeChild(existingScript);
}
};
}, []); // 注意依赖数组为空,意味着脚本将仅在加载页面时执行一次
return null;
}
================================================
FILE: components/DisableCopy.js
================================================
import { siteConfig } from '@/lib/config'
import { useEffect } from 'react'
/**
* 禁止用户拷贝文章的插件
*/
export default function DisableCopy() {
useEffect(() => {
if (!JSON.parse(siteConfig('CAN_COPY'))) {
// 全栈添加禁止复制的样式
document.getElementsByTagName('html')[0].classList.add('forbid-copy')
// 监听复制事件
document.addEventListener('copy', function (event) {
event.preventDefault() // 阻止默认复制行为
alert('抱歉,本网页内容不可复制!')
})
}
}, [])
return null
}
================================================
FILE: components/Draggable.js
================================================
import { useEffect, useRef, useState } from 'react'
/**
* 可拖拽组件
* @param {children} 渲染的子元素
* @param {stick} 是否要吸附
* @returns
*/
export const Draggable = ({ children, stick }) => {
const draggableRef = useRef(null)
const rafRef = useRef(null)
const [moving, setMoving] = useState(false)
let currentObj, offsetX, offsetY
useEffect(() => {
const draggableElements = document.getElementsByClassName('draggable')
function e(event) {
if (!event) {
event = window.event
event.target = event.srcElement
event.layerX = event.offsetX
event.layerY = event.offsetY
}
if (event.type === 'touchstart' || event.type === 'touchmove') {
event.clientX = event.touches[0].clientX
event.clientY = event.touches[0].clientY
}
event.mx = event.pageX || event.clientX + document.body.scrollLeft
event.my = event.pageY || event.clientY + document.body.scrollTop
return event
}
document.onmousedown = start
document.ontouchstart = start
function start(event) {
if (!draggableElements) return
event = e(event)
for (const drag of draggableElements) {
if (inDragBox(event, drag)) {
currentObj = drag.firstElementChild
}
}
if (currentObj) {
if (event.type === 'touchstart') {
event.preventDefault()
document.documentElement.style.overflow = 'hidden'
}
setMoving(true)
offsetX = event.mx - currentObj.offsetLeft
offsetY = event.my - currentObj.offsetTop
document.onmousemove = move
document.ontouchmove = move
document.onmouseup = stop
document.ontouchend = stop
}
}
function move(event) {
event = e(event)
rafRef.current = requestAnimationFrame(() => updatePosition(event))
}
const stop = event => {
event = e(event)
document.documentElement.style.overflow = 'auto'
cancelAnimationFrame(rafRef.current)
setMoving(false)
if (stick) {
checkInWindow() // 吸附逻辑
}
currentObj =
document.ontouchmove =
document.ontouchend =
document.onmousemove =
document.onmouseup =
null
}
const updatePosition = event => {
if (currentObj) {
const left = event.mx - offsetX
const top = event.my - offsetY
currentObj.style.left = left + 'px'
currentObj.style.top = top + 'px'
}
}
function inDragBox(event, drag) {
const { clientX, clientY } = event
const { offsetHeight, offsetWidth, offsetTop, offsetLeft } =
drag.firstElementChild
const horizontal =
clientX > offsetLeft && clientX < offsetLeft + offsetWidth
const vertical = clientY > offsetTop && clientY < offsetTop + offsetHeight
return horizontal && vertical
}
function checkInWindow() {
for (const drag of draggableElements) {
const { offsetHeight, offsetWidth, offsetTop, offsetLeft } =
drag.firstElementChild
const { clientHeight, clientWidth } = document.documentElement
if (offsetTop < 0) {
drag.firstElementChild.style.top = '0px'
}
if (offsetTop > clientHeight - offsetHeight) {
drag.firstElementChild.style.top = clientHeight - offsetHeight + 'px'
}
if (offsetLeft < 0) {
drag.firstElementChild.style.left = '0px'
}
if (offsetLeft > clientWidth - offsetWidth) {
drag.firstElementChild.style.left = clientWidth - offsetWidth + 'px'
}
if (stick === 'left') {
drag.firstElementChild.style.left = '0px'
} else if (stick === 'right') {
drag.firstElementChild.style.left = clientWidth - offsetWidth + 'px'
}
}
}
window.addEventListener('resize', checkInWindow)
return () => {
window.removeEventListener('resize', checkInWindow)
cancelAnimationFrame(rafRef.current)
}
}, [stick])
return (
{children}
)
}
================================================
FILE: components/Equation.js
================================================
import * as React from 'react'
import Katex from '@/components/KatexReact'
import { getBlockTitle } from 'notion-utils'
const katexSettings = {
throwOnError: false,
strict: false
}
/**
* 数学公式
* @param {} param0
* @returns
*/
export const Equation = ({ block, math, inline = false, className, ...rest }) => {
math = math || getBlockTitle(block, null)
if (!math) return null
return (
)
}
================================================
FILE: components/ExternalPlugins.js
================================================
import { siteConfig } from '@/lib/config'
import { convertInnerUrl } from '@/lib/db/notion/convertInnerUrl'
import { isBrowser, loadExternalResource } from '@/lib/utils'
import dynamic from 'next/dynamic'
import { useRouter } from 'next/router'
import { useEffect } from 'react'
import { GlobalStyle } from './GlobalStyle'
import { initGoogleAdsense } from './GoogleAdsense'
import Head from 'next/head'
import ExternalScript from './ExternalScript'
import WebWhiz from './Webwhiz'
import { useGlobal } from '@/lib/global'
import IconFont from './IconFont'
/**
* 各种插件脚本
* @param {*} props
* @returns
*/
const ExternalPlugin = props => {
// 读取自Notion的配置
const { NOTION_CONFIG } = props
const { lang } = useGlobal()
const DISABLE_PLUGIN = siteConfig('DISABLE_PLUGIN', null, NOTION_CONFIG)
const THEME_SWITCH = siteConfig('THEME_SWITCH', null, NOTION_CONFIG)
const DEBUG = siteConfig('DEBUG', null, NOTION_CONFIG)
const ANALYTICS_ACKEE_TRACKER = siteConfig(
'ANALYTICS_ACKEE_TRACKER',
null,
NOTION_CONFIG
)
const ANALYTICS_VERCEL = siteConfig('ANALYTICS_VERCEL', null, NOTION_CONFIG)
const ANALYTICS_BUSUANZI_ENABLE = siteConfig(
'ANALYTICS_BUSUANZI_ENABLE',
null,
NOTION_CONFIG
)
const ADSENSE_GOOGLE_ID = siteConfig('ADSENSE_GOOGLE_ID', null, NOTION_CONFIG)
const FACEBOOK_APP_ID = siteConfig('FACEBOOK_APP_ID', null, NOTION_CONFIG)
const FACEBOOK_PAGE_ID = siteConfig('FACEBOOK_PAGE_ID', null, NOTION_CONFIG)
const FIREWORKS = siteConfig('FIREWORKS', null, NOTION_CONFIG)
const SAKURA = siteConfig('SAKURA', null, NOTION_CONFIG)
const STARRY_SKY = siteConfig('STARRY_SKY', null, NOTION_CONFIG)
const MUSIC_PLAYER = siteConfig('MUSIC_PLAYER', null, NOTION_CONFIG)
const NEST = siteConfig('NEST', null, NOTION_CONFIG)
const FLUTTERINGRIBBON = siteConfig('FLUTTERINGRIBBON', null, NOTION_CONFIG)
const COMMENT_TWIKOO_COUNT_ENABLE = siteConfig(
'COMMENT_TWIKOO_COUNT_ENABLE',
null,
NOTION_CONFIG
)
const RIBBON = siteConfig('RIBBON', null, NOTION_CONFIG)
const CUSTOM_RIGHT_CLICK_CONTEXT_MENU = siteConfig(
'CUSTOM_RIGHT_CLICK_CONTEXT_MENU',
null,
NOTION_CONFIG
)
const CAN_COPY = siteConfig('CAN_COPY', null, NOTION_CONFIG)
const WEB_WHIZ_ENABLED = siteConfig('WEB_WHIZ_ENABLED', null, NOTION_CONFIG)
const AD_WWADS_BLOCK_DETECT = siteConfig(
'AD_WWADS_BLOCK_DETECT',
null,
NOTION_CONFIG
)
const CHATBASE_ID = siteConfig('CHATBASE_ID', null, NOTION_CONFIG)
const COMMENT_DAO_VOICE_ID = siteConfig(
'COMMENT_DAO_VOICE_ID',
null,
NOTION_CONFIG
)
const AD_WWADS_ID = siteConfig('AD_WWADS_ID', null, NOTION_CONFIG)
const COMMENT_ARTALK_SERVER = siteConfig(
'COMMENT_ARTALK_SERVER',
null,
NOTION_CONFIG
)
const COMMENT_ARTALK_JS = siteConfig('COMMENT_ARTALK_JS', null, NOTION_CONFIG)
const COMMENT_TIDIO_ID = siteConfig('COMMENT_TIDIO_ID', null, NOTION_CONFIG)
const COMMENT_GITTER_ROOM = siteConfig(
'COMMENT_GITTER_ROOM',
null,
NOTION_CONFIG
)
const ANALYTICS_BAIDU_ID = siteConfig(
'ANALYTICS_BAIDU_ID',
null,
NOTION_CONFIG
)
const ANALYTICS_CNZZ_ID = siteConfig('ANALYTICS_CNZZ_ID', null, NOTION_CONFIG)
const ANALYTICS_GOOGLE_ID = siteConfig(
'ANALYTICS_GOOGLE_ID',
null,
NOTION_CONFIG
)
const MATOMO_HOST_URL = siteConfig('MATOMO_HOST_URL', null, NOTION_CONFIG)
const MATOMO_SITE_ID = siteConfig('MATOMO_SITE_ID', null, NOTION_CONFIG)
const ANALYTICS_51LA_ID = siteConfig('ANALYTICS_51LA_ID', null, NOTION_CONFIG)
const ANALYTICS_51LA_CK = siteConfig('ANALYTICS_51LA_CK', null, NOTION_CONFIG)
const DIFY_CHATBOT_ENABLED = siteConfig(
'DIFY_CHATBOT_ENABLED',
null,
NOTION_CONFIG
)
const TIANLI_KEY = siteConfig('TianliGPT_KEY', null, NOTION_CONFIG)
const GLOBAL_JS = siteConfig('GLOBAL_JS', '', NOTION_CONFIG)
const CLARITY_ID = siteConfig('CLARITY_ID', null, NOTION_CONFIG)
const IMG_SHADOW = siteConfig('IMG_SHADOW', null, NOTION_CONFIG)
const ANIMATE_CSS_URL = siteConfig('ANIMATE_CSS_URL', null, NOTION_CONFIG)
const MOUSE_FOLLOW = siteConfig('MOUSE_FOLLOW', null, NOTION_CONFIG)
const CUSTOM_EXTERNAL_CSS = siteConfig(
'CUSTOM_EXTERNAL_CSS',
null,
NOTION_CONFIG
)
const CUSTOM_EXTERNAL_JS = siteConfig(
'CUSTOM_EXTERNAL_JS',
null,
NOTION_CONFIG
)
// 默认关闭NProgress
const ENABLE_NPROGRSS = siteConfig('ENABLE_NPROGRSS', false)
const COZE_BOT_ID = siteConfig('COZE_BOT_ID')
const HILLTOP_ADS_META_ID = siteConfig(
'HILLTOP_ADS_META_ID',
null,
NOTION_CONFIG
)
const ENABLE_ICON_FONT = siteConfig('ENABLE_ICON_FONT', false)
const UMAMI_HOST = siteConfig('UMAMI_HOST', null, NOTION_CONFIG)
const UMAMI_ID = siteConfig('UMAMI_ID', null, NOTION_CONFIG)
// 自定义样式css和js引入
if (isBrowser) {
// 初始化AOS动画
// 静态导入本地自定义样式
loadExternalResource('/css/custom.css', 'css')
loadExternalResource('/js/custom.js', 'js')
// 自动添加图片阴影
if (IMG_SHADOW) {
loadExternalResource('/css/img-shadow.css', 'css')
}
if (ANIMATE_CSS_URL) {
loadExternalResource(ANIMATE_CSS_URL, 'css')
}
// 导入外部自定义脚本
if (CUSTOM_EXTERNAL_JS && CUSTOM_EXTERNAL_JS.length > 0) {
for (const url of CUSTOM_EXTERNAL_JS) {
loadExternalResource(url, 'js')
}
}
// 导入外部自定义样式
if (CUSTOM_EXTERNAL_CSS && CUSTOM_EXTERNAL_CSS.length > 0) {
for (const url of CUSTOM_EXTERNAL_CSS) {
loadExternalResource(url, 'css')
}
}
}
const router = useRouter()
useEffect(() => {
// 异步渲染谷歌广告
if (ADSENSE_GOOGLE_ID) {
setTimeout(() => {
initGoogleAdsense(ADSENSE_GOOGLE_ID)
}, 3000)
}
setTimeout(() => {
// 映射url
convertInnerUrl({ allPages: props?.allNavPages, lang: lang })
}, 500)
}, [router])
useEffect(() => {
// 执行注入脚本
// eslint-disable-next-line no-eval
if (GLOBAL_JS && GLOBAL_JS.trim() !== '') {
// console.log('Inject JS:', GLOBAL_JS);
}
eval(GLOBAL_JS)
})
if (DISABLE_PLUGIN) {
return null
}
return (
<>
{/* 全局样式嵌入 */}
{ENABLE_ICON_FONT && }
{MOUSE_FOLLOW && }
{THEME_SWITCH && }
{DEBUG && }
{ANALYTICS_ACKEE_TRACKER && }
{ANALYTICS_GOOGLE_ID && }
{ANALYTICS_VERCEL && }
{ANALYTICS_BUSUANZI_ENABLE && }
{FACEBOOK_APP_ID && FACEBOOK_PAGE_ID && }
{FIREWORKS && }
{SAKURA && }
{STARRY_SKY && }
{MUSIC_PLAYER && }
{NEST && }
{FLUTTERINGRIBBON && }
{COMMENT_TWIKOO_COUNT_ENABLE && }
{RIBBON && }
{DIFY_CHATBOT_ENABLED && }
{CUSTOM_RIGHT_CLICK_CONTEXT_MENU && }
{!CAN_COPY && }
{WEB_WHIZ_ENABLED && }
{AD_WWADS_BLOCK_DETECT && }
{TIANLI_KEY && }
{ENABLE_NPROGRSS && }
{ANALYTICS_51LA_ID && ANALYTICS_51LA_CK && }
{COZE_BOT_ID && }
{ANALYTICS_51LA_ID && ANALYTICS_51LA_CK && (
<>
{/* */}
>
)}
{CHATBASE_ID && (
<>
>
)}
{CLARITY_ID && (
<>
>
)}
{COMMENT_DAO_VOICE_ID && (
<>
{/* DaoVoice 反馈 */}
>
)}
{/* HILLTOP广告验证 */}
{HILLTOP_ADS_META_ID && (
)}
{AD_WWADS_ID && (
<>
{/* 提前连接到广告服务器 */}
>
)}
{/* {COMMENT_TWIKOO_ENV_ID && } */}
{COMMENT_ARTALK_SERVER && }
{COMMENT_TIDIO_ID && (
)}
{/* gitter聊天室 */}
{COMMENT_GITTER_ROOM && (
<>
>
)}
{/* 百度统计 */}
{ANALYTICS_BAIDU_ID && (
)}
{/* 站长统计 */}
{ANALYTICS_CNZZ_ID && (
)}
{/* UMAMI 统计 */}
{UMAMI_ID && (
)}
{/* 谷歌统计 */}
{ANALYTICS_GOOGLE_ID && (
<>
>
)}
{/* Matomo 统计 */}
{MATOMO_HOST_URL && MATOMO_SITE_ID && (
)}
>
)
}
const TwikooCommentCounter = dynamic(
() => import('@/components/TwikooCommentCounter'),
{ ssr: false }
)
const DebugPanel = dynamic(() => import('@/components/DebugPanel'), {
ssr: false
})
const ThemeSwitch = dynamic(() => import('@/components/ThemeSwitch'), {
ssr: false
})
const Fireworks = dynamic(() => import('@/components/Fireworks'), {
ssr: false
})
const MouseFollow = dynamic(() => import('@/components/MouseFollow'), {
ssr: false
})
const Nest = dynamic(() => import('@/components/Nest'), { ssr: false })
const FlutteringRibbon = dynamic(
() => import('@/components/FlutteringRibbon'),
{ ssr: false }
)
const Ribbon = dynamic(() => import('@/components/Ribbon'), { ssr: false })
const Sakura = dynamic(() => import('@/components/Sakura'), { ssr: false })
const StarrySky = dynamic(() => import('@/components/StarrySky'), {
ssr: false
})
const DifyChatbot = dynamic(() => import('@/components/DifyChatbot'), {
ssr: false
})
const Analytics = dynamic(
() =>
import('@vercel/analytics/react').then(m => {
return m.Analytics
}),
{ ssr: false }
)
const MusicPlayer = dynamic(() => import('@/components/Player'), { ssr: false })
const Ackee = dynamic(() => import('@/components/Ackee'), { ssr: false })
const Gtag = dynamic(() => import('@/components/Gtag'), { ssr: false })
const Busuanzi = dynamic(() => import('@/components/Busuanzi'), { ssr: false })
const Messenger = dynamic(() => import('@/components/FacebookMessenger'), {
ssr: false
})
const VConsole = dynamic(() => import('@/components/VConsole'), { ssr: false })
const CustomContextMenu = dynamic(
() => import('@/components/CustomContextMenu'),
{ ssr: false }
)
const DisableCopy = dynamic(() => import('@/components/DisableCopy'), {
ssr: false
})
const AdBlockDetect = dynamic(() => import('@/components/AdBlockDetect'), {
ssr: false
})
const LoadingProgress = dynamic(() => import('@/components/LoadingProgress'), {
ssr: false
})
const AosAnimation = dynamic(() => import('@/components/AOSAnimation'), {
ssr: false
})
const Coze = dynamic(() => import('@/components/Coze'), {
ssr: false
})
const LA51 = dynamic(() => import('@/components/LA51'), {
ssr: false
})
const TianliGPT = dynamic(() => import('@/components/TianliGPT'), {
ssr: false
})
export default ExternalPlugin
================================================
FILE: components/ExternalScript.js
================================================
'use client'
import { isBrowser } from '@/lib/utils'
/**
* 自定义外部 script
* 传入参数将转为