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}"

)}
{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 (
{firstTenTags?.map((tag, index) => { return (
{tag.name}
{tag.count ? ( {tag.count} ) : ( <> )}
) })}
) } /** * 分页 * @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 (
{/* 延迟加载评论区 */} {!shouldLoad && (
Loading...
)} {shouldLoad && ( {COMMENT_ARTALK_SERVER && (
)} {COMMENT_TWIKOO_ENV_ID && (
)} {COMMENT_WALINE_SERVER_URL && (
)} {COMMENT_VALINE_APP_ID && (
)} {COMMENT_GISCUS_REPO && (
)} {COMMENT_CUSDIS_APP_ID && (
)} {COMMENT_UTTERRANCES_REPO && (
)} {COMMENT_GITALK_CLIENT_ID && (
)} {COMMENT_WEBMENTION_ENABLE && (
)}
)}
) } 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 && (
{locale.MENU.COPY}
)} {CUSTOM_RIGHT_CLICK_CONTEXT_MENU_SHARE_LINK && (
{locale.MENU.SHARE_URL}
)} {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 (
{' '} {isDarkMode ? : }
) } 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} )}
{/* 调试侧拉抽屉 */}