├── .editorconfig ├── .gitattributes ├── .githooks ├── install.js └── pre-commit ├── .github ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE │ ├── bug-report.yml │ ├── config.yml │ ├── feature-request.yml │ └── other.yml ├── PULL_REQUEST_TEMPLATE.md ├── config.yml ├── issue_label_bot.yaml ├── label-commenter-config.yml ├── labeler.yml ├── release.yml └── workflows │ ├── codeql.yml │ ├── label-commenter.yml │ ├── labeler.yml │ ├── linter.yml │ ├── lock.yml │ ├── npm-publish.yml │ └── tester.yml ├── .gitignore ├── .stylelintrc ├── LICENSE.md ├── README.md ├── _config.yml ├── _vendors.yml ├── crowdin.yml ├── docs ├── AGPL3.md ├── AUTHORS.md ├── LICENSE.txt ├── ru │ └── README.md └── zh-CN │ ├── CODE_OF_CONDUCT.md │ ├── CONTRIBUTING.md │ └── README.md ├── eslint.config.js ├── languages ├── README.md ├── ar.yml ├── bn.yml ├── de.yml ├── default.yml ├── en.yml ├── eo.yml ├── es.yml ├── fa.yml ├── fr.yml ├── id.yml ├── it.yml ├── ja.yml ├── ko.yml ├── nl.yml ├── pt-BR.yml ├── pt.yml ├── ru.yml ├── si.yml ├── th.yml ├── tk.yml ├── tr.yml ├── uk.yml ├── vi.yml ├── zh-CN.yml ├── zh-HK.yml └── zh-TW.yml ├── layout ├── _layout.njk ├── _macro │ ├── post-collapse.njk │ ├── post.njk │ └── sidebar.njk ├── _partials │ ├── comments.njk │ ├── footer.njk │ ├── head │ │ ├── head-unique.njk │ │ └── head.njk │ ├── header │ │ ├── brand.njk │ │ ├── index.njk │ │ ├── menu-item.njk │ │ ├── menu.njk │ │ └── sub-menu.njk │ ├── languages.njk │ ├── page │ │ ├── breadcrumb.njk │ │ ├── categories.njk │ │ ├── page-header.njk │ │ ├── schedule.njk │ │ └── tags.njk │ ├── pagination.njk │ ├── post │ │ ├── post-copyright.njk │ │ ├── post-followme.njk │ │ ├── post-meta.njk │ │ ├── post-related.njk │ │ ├── post-reward.njk │ │ └── post-share.njk │ ├── search │ │ └── index.njk │ ├── sidebar │ │ └── site-overview.njk │ └── widgets.njk ├── _scripts │ ├── index.njk │ └── vendors.njk ├── _third-party │ ├── addtoany.njk │ ├── analytics │ │ ├── baidu-analytics.njk │ │ ├── cloudflare.njk │ │ ├── google-analytics.njk │ │ ├── growingio.njk │ │ ├── index.njk │ │ ├── matomo.njk │ │ ├── microsoft-clarity.njk │ │ ├── plausible.njk │ │ └── umami.njk │ ├── chat │ │ ├── chatra.njk │ │ └── tidio.njk │ ├── comments │ │ ├── changyan.njk │ │ ├── disqus.njk │ │ ├── disqusjs.njk │ │ ├── gitalk.njk │ │ ├── isso.njk │ │ ├── livere.njk │ │ └── utterances.njk │ ├── fancybox.njk │ ├── index.njk │ ├── math │ │ ├── index.njk │ │ ├── katex.njk │ │ └── mathjax.njk │ ├── pace.njk │ ├── quicklink.njk │ ├── search │ │ ├── algolia-search.njk │ │ └── localsearch.njk │ ├── statistics │ │ ├── busuanzi-counter.njk │ │ ├── firestore.njk │ │ ├── index.njk │ │ └── lean-analytics.njk │ └── tags │ │ ├── mermaid.njk │ │ ├── pdf.njk │ │ └── wavedrom.njk ├── archive.njk ├── category.njk ├── index.njk ├── page.njk ├── post.njk └── tag.njk ├── package.json ├── renovate.json ├── scripts ├── events │ ├── index.js │ └── lib │ │ ├── config.js │ │ ├── highlight.js │ │ ├── injects.js │ │ ├── navigation.js │ │ ├── utils.js │ │ └── vendors.js ├── filters │ ├── comment │ │ ├── changyan.js │ │ ├── common.js │ │ ├── default-config.js │ │ ├── disqus.js │ │ ├── disqusjs.js │ │ ├── gitalk.js │ │ ├── isso.js │ │ ├── livere.js │ │ └── utterances.js │ ├── default-injects.js │ ├── locals.js │ ├── minify.js │ └── post.js ├── helpers │ ├── engine.js │ ├── font.js │ ├── navigation.js │ ├── next-config.js │ ├── next-paginator.js │ ├── next-url.js │ └── next-vendors.js └── tags │ ├── button.js │ ├── caniuse.js │ ├── center-quote.js │ ├── group-pictures.js │ ├── index.js │ ├── label.js │ ├── link-grid.js │ ├── mermaid.js │ ├── note.js │ ├── pdf.js │ ├── tabs.js │ ├── video.js │ └── wavedrom.js ├── source ├── css │ ├── _colors.styl │ ├── _common │ │ ├── components │ │ │ ├── back-to-top.styl │ │ │ ├── index.styl │ │ │ ├── pages │ │ │ │ ├── breadcrumb.styl │ │ │ │ ├── categories.styl │ │ │ │ ├── index.styl │ │ │ │ ├── schedule.styl │ │ │ │ └── tag-cloud.styl │ │ │ ├── post │ │ │ │ ├── index.styl │ │ │ │ ├── post-body.styl │ │ │ │ ├── post-collapse.styl │ │ │ │ ├── post-followme.styl │ │ │ │ ├── post-footer.styl │ │ │ │ ├── post-gallery.styl │ │ │ │ ├── post-header.styl │ │ │ │ ├── post-nav.styl │ │ │ │ ├── post-reward.styl │ │ │ │ └── post-widgets.styl │ │ │ ├── reading-progress.styl │ │ │ └── third-party │ │ │ │ ├── disqusjs.styl │ │ │ │ ├── gitalk.styl │ │ │ │ ├── index.styl │ │ │ │ ├── math.styl │ │ │ │ ├── search.styl │ │ │ │ └── utterances.styl │ │ ├── outline │ │ │ ├── footer │ │ │ │ └── index.styl │ │ │ ├── header │ │ │ │ ├── bookmark.styl │ │ │ │ ├── github-banner.styl │ │ │ │ ├── index.styl │ │ │ │ ├── menu.styl │ │ │ │ ├── site-meta.styl │ │ │ │ └── site-nav.styl │ │ │ ├── index.styl │ │ │ ├── mobile.styl │ │ │ └── sidebar │ │ │ │ ├── index.styl │ │ │ │ ├── related-posts.styl │ │ │ │ ├── sidebar-author-links.styl │ │ │ │ ├── sidebar-author.styl │ │ │ │ ├── sidebar-blogroll.styl │ │ │ │ ├── sidebar-button.styl │ │ │ │ ├── sidebar-copyright.styl │ │ │ │ ├── sidebar-nav.styl │ │ │ │ ├── sidebar-toc.styl │ │ │ │ ├── sidebar-toggle.styl │ │ │ │ └── site-state.styl │ │ └── scaffolding │ │ │ ├── base.styl │ │ │ ├── buttons.styl │ │ │ ├── comments.styl │ │ │ ├── highlight │ │ │ ├── copy-code.styl │ │ │ ├── fold.styl │ │ │ └── index.styl │ │ │ ├── index.styl │ │ │ ├── normalize.styl │ │ │ ├── pagination.styl │ │ │ ├── tables.styl │ │ │ ├── tags │ │ │ ├── blockquote-center.styl │ │ │ ├── group-pictures.styl │ │ │ ├── index.styl │ │ │ ├── label.styl │ │ │ ├── link-grid.styl │ │ │ ├── mermaid.styl │ │ │ ├── note.styl │ │ │ ├── pdf.styl │ │ │ ├── tabs.styl │ │ │ └── wavedrom.styl │ │ │ └── toggles.styl │ ├── _mixins.styl │ ├── _schemes │ │ ├── Gemini │ │ │ └── index.styl │ │ ├── Mist │ │ │ ├── _header.styl │ │ │ ├── _layout.styl │ │ │ ├── _menu.styl │ │ │ ├── _posts-expand.styl │ │ │ └── index.styl │ │ ├── Muse │ │ │ ├── _header.styl │ │ │ ├── _layout.styl │ │ │ ├── _menu.styl │ │ │ ├── _sidebar.styl │ │ │ ├── _sub-menu.styl │ │ │ └── index.styl │ │ └── Pisces │ │ │ ├── _header.styl │ │ │ ├── _layout.styl │ │ │ ├── _menu.styl │ │ │ ├── _sidebar.styl │ │ │ ├── _sub-menu.styl │ │ │ └── index.styl │ ├── _variables │ │ ├── Gemini.styl │ │ ├── Mist.styl │ │ ├── Muse.styl │ │ ├── Pisces.styl │ │ └── base.styl │ ├── main.styl │ └── noscript.styl ├── images │ ├── apple-touch-icon-next.png │ ├── avatar.gif │ ├── favicon-16x16-next.png │ ├── favicon-32x32-next.png │ ├── logo-algolia-nebula-blue-full.svg │ └── logo.svg └── js │ ├── bookmark.js │ ├── comments-buttons.js │ ├── comments.js │ ├── config.js │ ├── motion.js │ ├── next-boot.js │ ├── pjax.js │ ├── schedule.js │ ├── sidebar.js │ ├── third-party │ ├── addtoany.js │ ├── analytics │ │ ├── baidu-analytics.js │ │ ├── google-analytics.js │ │ ├── growingio.js │ │ └── matomo.js │ ├── chat │ │ ├── chatra.js │ │ └── tidio.js │ ├── comments │ │ ├── changyan.js │ │ ├── disqus.js │ │ ├── disqusjs.js │ │ ├── gitalk.js │ │ ├── isso.js │ │ ├── livere.js │ │ └── utterances.js │ ├── fancybox.js │ ├── math │ │ ├── katex.js │ │ └── mathjax.js │ ├── pace.js │ ├── quicklink.js │ ├── search │ │ ├── algolia-search.js │ │ └── local-search.js │ ├── statistics │ │ ├── firestore.js │ │ └── lean-analytics.js │ └── tags │ │ ├── mermaid.js │ │ ├── pdf.js │ │ └── wavedrom.js │ └── utils.js └── test ├── helpers ├── font.js ├── index.js └── next-url.js ├── index.js ├── tags ├── button.js ├── caniuse.js ├── center-quote.js ├── group-pictures.js ├── index.js ├── label.js ├── link-grid.js ├── mermaid.js ├── note.js ├── pdf.js ├── tabs.js └── video.js └── validate └── index.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | indent_style = space 11 | indent_size = 2 12 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | test/* linguist-vendored 2 | -------------------------------------------------------------------------------- /.githooks/install.js: -------------------------------------------------------------------------------- 1 | const { spawn } = require('child_process'); 2 | const path = require('path'); 3 | 4 | const subprocess = spawn('git', ['config', '--local', 'core.hooksPath', path.join(__dirname, '.githooks/')]); 5 | 6 | subprocess.on('error', () => { 7 | console.error('Failed to install git hook.'); 8 | }); 9 | -------------------------------------------------------------------------------- /.githooks/pre-commit: -------------------------------------------------------------------------------- 1 | npm run eslint && npm test 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: GitHub Discussions 4 | url: https://github.com/next-theme/hexo-theme-next/discussions 5 | about: Please ask and answer questions here. 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.yml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: Suggest an idea for this project 3 | #title: "" 4 | labels: 5 | - Feature Request 6 | #assignees: "" 7 | body: 8 | - type: markdown 9 | id: overall 10 | attributes: 11 | value: | 12 | Please follow this Issue template to provide relevant information, such as source code repository, website URL and screenshots, which will help us investigate. 13 | 请按照此 Issue 模版提供相关信息,例如源码仓库、网站链接和屏幕截图,这将有助于我们进行调查。 14 | - type: checkboxes 15 | id: checklist 16 | attributes: 17 | label: Issue Checklist 18 | description: | 19 | (我确认我已经查看了) 20 | options: 21 | - label: I am using NexT version 8.0 or later. 22 | required: true 23 | - label: I have already read the [Troubleshooting page of Hexo](https://hexo.io/docs/troubleshooting) and [Troubleshooting page of NexT](https://theme-next.js.org/docs/troubleshooting.html). 24 | required: true 25 | - label: I have already searched for current [issues](https://github.com/next-theme/hexo-theme-next/issues), which does not help me. 26 | required: true 27 | - type: textarea 28 | id: expected-behavior 29 | attributes: 30 | label: Expected behavior 31 | description: "(预期行为)" 32 | validations: 33 | required: true 34 | - type: textarea 35 | id: actual-behavior 36 | attributes: 37 | label: Actual behavior 38 | description: | 39 | (实际行为) 40 | Please provide the following information (请同时提供网站链接和屏幕截图) 41 | value: | 42 | - Links to demo site with this issue: 43 | - Links to repository or source code of the blog: 44 | - Screenshots: 45 | validations: 46 | required: true 47 | - type: textarea 48 | id: reproduce 49 | attributes: 50 | label: Steps to reproduce the behavior 51 | description: "(重现步骤)" 52 | validations: 53 | required: true 54 | - type: textarea 55 | id: other-info 56 | attributes: 57 | label: Other Information 58 | description: "e.g. Browser, System" 59 | validations: 60 | required: false 61 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/other.yml: -------------------------------------------------------------------------------- 1 | name: Other 2 | description: Not a feature request or bug report 3 | #title: "" 4 | labels: 5 | - Question 6 | #assignees: "" 7 | body: 8 | - type: markdown 9 | id: overall 10 | attributes: 11 | value: | 12 | Please follow this Issue template to provide relevant information, such as source code repository, website URL and screenshots, which will help us investigate. 13 | 请按照此 Issue 模版提供相关信息,例如源码仓库、网站链接和屏幕截图,这将有助于我们进行调查。 14 | - type: checkboxes 15 | id: checklist 16 | attributes: 17 | label: Issue Checklist 18 | description: | 19 | (我确认我已经查看了) 20 | options: 21 | - label: I am using NexT version 8.0 or later. 22 | required: true 23 | - label: I have already read the [Troubleshooting page of Hexo](https://hexo.io/docs/troubleshooting) and [Troubleshooting page of NexT](https://theme-next.js.org/docs/troubleshooting.html). 24 | required: true 25 | - label: I have already searched for current [issues](https://github.com/next-theme/hexo-theme-next/issues), which does not help me. 26 | required: true 27 | - type: textarea 28 | id: other-info 29 | attributes: 30 | label: Other Information 31 | description: "e.g. Browser, System" 32 | validations: 33 | required: true 34 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 10 | 11 | ## PR Checklist 12 | 13 | 14 | - [ ] The changes have been tested (for bug fixes / features). 15 | - [ ] [Docs](https://github.com/next-theme/theme-next-docs/tree/master/source/docs) in [NexT website](https://theme-next.js.org/docs/) have been added / updated (for features). 16 | 17 | 18 | ## PR Type 19 | 20 | 21 | - [ ] Bugfix. 22 | - [ ] Feature. 23 | - [ ] Improvement. 24 | - [ ] Code style update (e.g. formatting, linting). 25 | - [ ] Refactoring (no changes to functionality and APIs). 26 | - [ ] Documentation. 27 | - [ ] Translation. 28 | - [ ] Other... Please describe: 29 | 30 | ## What is the current behavior? 31 | 32 | 33 | Issue resolved: 34 | 35 | ## What is the new behavior? 36 | 37 | 38 | - Link to demo site with this changes: 39 | - Screenshots with this changes: 40 | 41 | ### How to use? 42 | 43 | In NexT `_config.yml`: 44 | ```yml 45 | 46 | ``` 47 | -------------------------------------------------------------------------------- /.github/config.yml: -------------------------------------------------------------------------------- 1 | # =============================================================================================== # 2 | # Configuration for welcome - https://github.com/behaviorbot/welcome 3 | 4 | # Comment to be posted to on first time issues 5 | newIssueWelcomeComment: > 6 | Thanks for opening this issue, maintainers will get back to you as soon as possible! 7 | 8 | # Comment to be posted to on PRs from first time contributors in your repository 9 | newPRWelcomeComment: > 10 | Thanks so much for opening your first PR here! 11 | 12 | # Comment to be posted to on pull requests merged by a first time user 13 | firstPRMergeComment: > 14 | Congrats on merging your first pull request here! :tada: How awesome! 15 | -------------------------------------------------------------------------------- /.github/issue_label_bot.yaml: -------------------------------------------------------------------------------- 1 | label-alias: 2 | bug: 'Bug' 3 | feature_request: 'Feature Request' 4 | question: 'Question' 5 | -------------------------------------------------------------------------------- /.github/label-commenter-config.yml: -------------------------------------------------------------------------------- 1 | # Configuration for Label Commenter - https://github.com/peaceiris/actions-label-commenter 2 | 3 | labels: 4 | - name: Invalid 5 | labeled: 6 | issue: 7 | body: This issue has been closed because it does not seem right. 8 | action: close 9 | - name: Need More Info 10 | labeled: 11 | issue: 12 | body: We would appreciate it if you could provide us with more info about this issue! 13 | - name: 🎯 Roadmap 14 | labeled: 15 | issue: 16 | body: | 17 | This issue has been added to the latest roadmap. :tada: 18 | - name: Support 19 | labeled: 20 | issue: 21 | body: | 22 | :wave: Hey there! we use the issue tracker exclusively for bug reports and feature requests. However, this issue appears to be a support request. 23 | Please use our [support channels](https://github.com/next-theme/hexo-theme-next#feedback) to get help with the project. 24 | action: close 25 | - name: Wontfix 26 | labeled: 27 | issue: 28 | body: There wasn't enough interest from the team or community to implement this change. Thanks for contributing and we appreciate your understanding. 29 | action: close 30 | - name: Configurations 31 | labeled: 32 | pr: 33 | body: | 34 | This pull request contains changes to the configuration file. Please make sure the documentation in [NexT website](https://theme-next.js.org/docs/) is changed or added. 35 | Please edit relevant source files here: https://github.com/next-theme/theme-next-docs/tree/master/source/docs and create a pull request with the changes here: https://github.com/next-theme/theme-next-docs/pulls 36 | -------------------------------------------------------------------------------- /.github/labeler.yml: -------------------------------------------------------------------------------- 1 | # Configuration for labeler - https://github.com/actions/labeler 2 | 3 | 📦 Dependencies: 4 | - changed-files: 5 | - any-glob-to-any-file: package.json 6 | Configurations: 7 | - changed-files: 8 | - any-glob-to-any-file: _config.yml 9 | CSS: 10 | - changed-files: 11 | - any-glob-to-any-file: source/css/**/* 12 | 📖 Docs: 13 | - changed-files: 14 | - any-glob-to-any-file: docs/**/* 15 | Layout: 16 | - changed-files: 17 | - any-glob-to-any-file: layout/**/* 18 | 🌍 i18n: 19 | - changed-files: 20 | - any-glob-to-any-file: languages/**/* 21 | Actions: 22 | - changed-files: 23 | - any-glob-to-any-file: .github/workflows/**/* 24 | 🔌 3rd Party Plugin: 25 | - changed-files: 26 | - any-glob-to-any-file: '**/*third-party/**/*' 27 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | changelog: 2 | categories: 3 | - title: '💥 Breaking Changes' 4 | labels: 5 | - '💥 Breaking Change' 6 | 7 | - title: '🌟 New Features' 8 | labels: 9 | - '🌟 New Feature' 10 | 11 | - title: '⭐ Features' 12 | labels: 13 | - '⭐ Feature' 14 | 15 | - title: '🐞 Bug Fixes' 16 | labels: 17 | - '🐞 Bug Fix' 18 | 19 | - title: '🛠 Improvements' 20 | labels: 21 | - '🛠 Improvement' 22 | 23 | - title: '🌀 External Changes' 24 | labels: 25 | - '📦 Dependencies' 26 | - 'Actions' 27 | 28 | - title: '📖 Documentation' 29 | labels: 30 | - '📖 Docs' 31 | 32 | - title: '🌍 Localization' 33 | labels: 34 | - '🌍 i18n' 35 | 36 | exclude: 37 | labels: 38 | - 'Skip Release' 39 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | schedule: 9 | - cron: "33 18 * * 0" 10 | 11 | jobs: 12 | analyze: 13 | name: Analyze 14 | runs-on: ubuntu-latest 15 | permissions: 16 | actions: read 17 | contents: read 18 | security-events: write 19 | 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | language: [ javascript ] 24 | 25 | steps: 26 | - name: Checkout 27 | uses: actions/checkout@v4 28 | 29 | - name: Initialize CodeQL 30 | uses: github/codeql-action/init@v3 31 | with: 32 | languages: ${{ matrix.language }} 33 | queries: +security-and-quality 34 | 35 | - name: Autobuild 36 | uses: github/codeql-action/autobuild@v3 37 | 38 | - name: Perform CodeQL Analysis 39 | uses: github/codeql-action/analyze@v3 40 | with: 41 | category: "/language:${{ matrix.language }}" 42 | -------------------------------------------------------------------------------- /.github/workflows/label-commenter.yml: -------------------------------------------------------------------------------- 1 | name: Label Commenter 2 | 3 | on: 4 | issues: 5 | types: 6 | - labeled 7 | pull_request_target: 8 | types: 9 | - labeled 10 | 11 | jobs: 12 | comment: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | with: 17 | ref: master 18 | - name: Label Commenter 19 | uses: peaceiris/actions-label-commenter@v1 20 | -------------------------------------------------------------------------------- /.github/workflows/labeler.yml: -------------------------------------------------------------------------------- 1 | name: Pull Request Labeler 2 | 3 | on: 4 | - pull_request_target 5 | 6 | jobs: 7 | triage: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/labeler@v5 11 | # https://github.com/peaceiris/actions-label-commenter#work-with-other-auto-label-actions 12 | with: 13 | repo-token: "${{ secrets.GH_PAT }}" 14 | -------------------------------------------------------------------------------- /.github/workflows/linter.yml: -------------------------------------------------------------------------------- 1 | name: Linter 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | linter: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v4 10 | - name: Use Node.js 11 | uses: actions/setup-node@v4 12 | - name: Install Dependencies 13 | run: npm install 14 | - run: npm run eslint 15 | - name: Coverage 16 | run: npx c8 --reporter=lcovonly npm test 17 | env: 18 | CI: true 19 | - name: Coveralls 20 | uses: coverallsapp/github-action@master 21 | with: 22 | github-token: ${{ secrets.GITHUB_TOKEN }} 23 | - name: Set up Python 24 | uses: actions/setup-python@v5 25 | with: 26 | python-version: '3.x' 27 | - name: Install Dependencies 28 | run: | 29 | python -m pip install --upgrade pip 30 | pip install curlylint 31 | - run: curlylint --include .njk layout 32 | -------------------------------------------------------------------------------- /.github/workflows/lock.yml: -------------------------------------------------------------------------------- 1 | # Configuration for Lock Threads - https://github.com/dessant/lock-threads 2 | name: Lock Threads 3 | 4 | on: 5 | schedule: 6 | - cron: '0 0 * * *' 7 | 8 | jobs: 9 | lock: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: dessant/lock-threads@v5 13 | with: 14 | github-token: ${{ github.token }} 15 | issue-comment: > 16 | This thread has been automatically locked since there has not been 17 | any recent activity after it was closed. It is possible issue was 18 | solved or at least outdated. Feel free to open new for related bugs. 19 | process-only: 'issues' 20 | -------------------------------------------------------------------------------- /.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish to npm 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | npm-publish: 10 | runs-on: ubuntu-latest 11 | permissions: 12 | contents: write 13 | id-token: write 14 | steps: 15 | - uses: actions/checkout@v4 16 | - name: Use Node.js 17 | uses: actions/setup-node@v4 18 | - run: | 19 | npm install 20 | npm publish --provenance 21 | env: 22 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 23 | -------------------------------------------------------------------------------- /.github/workflows/tester.yml: -------------------------------------------------------------------------------- 1 | name: Tester 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | tester: 7 | runs-on: ${{ matrix.os }} 8 | strategy: 9 | matrix: 10 | os: [ubuntu-latest, windows-latest, macos-latest] 11 | fail-fast: false 12 | steps: 13 | - uses: actions/checkout@v4 14 | with: 15 | repository: hexojs/hexo-starter 16 | - name: Use Node.js 17 | uses: actions/setup-node@v4 18 | - name: Install Dependencies 19 | run: npm install 20 | - name: Install hexo-tag-embed 21 | run: npm install hexo-tag-embed 22 | - uses: actions/checkout@v4 23 | with: 24 | path: themes/next 25 | - uses: actions/checkout@v4 26 | with: 27 | repository: hexojs/hexo-many-posts 28 | path: source/_posts/hexo-many-posts 29 | - run: npx hexo config theme next 30 | - uses: DamianReeves/write-file-action@master 31 | with: 32 | path: themes/next/scripts/error.js 33 | contents: | 34 | hexo.log.error = function(...params) { 35 | console.error("ERROR", ...params); 36 | process.exit(1); 37 | } 38 | - name: Test 39 | run: npx hexo g 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea/ 3 | .vscode/ 4 | *.log 5 | *.iml 6 | yarn.lock 7 | package-lock.json 8 | node_modules/ 9 | .nyc_output/ 10 | coverage/ 11 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "stylelint-stylus" 4 | ], 5 | "extends": [ 6 | "stylelint-stylus/standard" 7 | ], 8 | "rules": { 9 | "stylus/semicolon": "always", 10 | "stylus/pythonic": "never", 11 | "stylus/declaration-colon": "always", 12 | "stylus/number-leading-zero": "never", 13 | "stylus/selector-list-comma": "always", 14 | "stylus/selector-list-comma-newline-after": "never-multi-line", 15 | "stylus/media-feature-colon": "always", 16 | "stylus/single-line-comment": null, 17 | "stylus/single-line-comment-no-empty": null, 18 | "stylus/block-closing-brace-newline-after": "never-single-line" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /crowdin.yml: -------------------------------------------------------------------------------- 1 | files: 2 | - source: /languages/en.yml 3 | translation: /languages/%two_letters_code%.%file_extension% 4 | languages_mapping: 5 | two_letters_code: 6 | zh-CN: zh-CN 7 | zh-TW: zh-TW 8 | zh-HK: zh-HK 9 | pt-BR: pt-BR 10 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | const config = require("@next-theme/eslint-config"); 2 | 3 | module.exports = config; 4 | -------------------------------------------------------------------------------- /languages/README.md: -------------------------------------------------------------------------------- 1 | # Internationalization (i18n) 2 | 3 | [![Crowdin](https://badges.crowdin.net/hexo-theme-next/localized.svg)](https://crowdin.com/project/hexo-theme-next) 4 | 5 | You can use internationalization to present your site in different languages. The default language is set by modifying the `language` setting in Hexo `_config.yml`. You can also set multiple languages and modify the order of default languages. 6 | 7 | ```yml 8 | language: en 9 | ``` 10 | 11 | ```yml 12 | language: 13 | - zh-CN 14 | - en 15 | ``` 16 | 17 | ## Override Default Translations 18 | 19 | If you would like to customize the default translation, you do not need to modify the translation files in the `languages` directory. You can override all translations using [Data Files](https://hexo.io/docs/data-files). 20 | 21 | 1. Creat a `languages.yml` in `source/_data`. 22 | 2. Insert following codes: (be careful about the two-space indent) 23 | 24 | ```yml 25 | # language 26 | zh-CN: 27 | # items 28 | post: 29 | copyright: 30 | # the translation you perfer 31 | author: 本文博主 32 | en: 33 | menu: 34 | schedule: Calendar 35 | ``` 36 | 37 | ## Improve Translations 38 | 39 | The files in the `language` directory are automatically generated, you do not need to modify them directly. Please submit translations via [Crowdin](https://crowdin.com/project/hexo-theme-next) if you would like to add or improve translation for NexT theme. 40 | -------------------------------------------------------------------------------- /languages/default.yml: -------------------------------------------------------------------------------- 1 | en.yml -------------------------------------------------------------------------------- /languages/zh-CN.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: 简体中文 3 | title: 4 | archive: 归档 5 | category: 分类 6 | tag: 标签 7 | schedule: 日程表 8 | menu: 9 | home: 首页 10 | archives: 归档 11 | categories: 分类 12 | tags: 标签 13 | about: 关于 14 | search: 搜索 15 | schedule: 日程表 16 | sitemap: 站点地图 17 | commonweal: 公益 404 18 | sidebar: 19 | overview: 站点概览 20 | toc: 文章目录 21 | links: 链接 22 | post: 23 | posted: 发表于 24 | edited: 更新于 25 | created: 创建时间 26 | modified: 修改时间 27 | edit: 编辑 28 | in: 分类于 29 | read_more: 阅读全文 30 | untitled: 未命名 31 | sticky: 置顶 32 | views: 阅读次数 33 | related_posts: 相关文章 34 | copyright: 35 | author: 本文作者 36 | link: 本文链接 37 | post_author: 原作者 38 | post_link: 原文链接 39 | license_title: 版权声明 40 | license_content: "本博客所有文章除特别声明外,均采用 %s 许可协议。转载请注明出处!" 41 | license_content_reprint: "本文章为转载文章,已获转载许可。转载请注明出处!" 42 | footer: 43 | powered: "由 %s 强力驱动" 44 | total_views: 总访问量 45 | total_visitors: 总访客量 46 | widget: 47 | github: 在 GitHub 上关注我 48 | chat: 聊天 49 | counter: 50 | tag_cloud: 51 | zero: 暂无标签 52 | one: 目前共计 1 个标签 53 | other: "目前共计 %d 个标签" 54 | categories: 55 | zero: 暂无分类 56 | one: 目前共计 1 个分类 57 | other: "目前共计 %d 个分类" 58 | archive_posts: 59 | zero: 暂无日志。 60 | one: 目前共计 1 篇日志。 61 | other: "目前共计 %d 篇日志。" 62 | state: 63 | posts: 日志 64 | tags: 标签 65 | categories: 分类 66 | search: 67 | placeholder: 搜索... 68 | empty: "没有找到任何搜索结果:%s" 69 | hits_time: "找到 %s 个搜索结果(用时 %s 毫秒)" 70 | hits: "找到 %s 个搜索结果" 71 | cheers: 72 | um: 嗯.. 73 | ok: 还行 74 | nice: 不错 75 | good: 很好 76 | great: 非常好 77 | excellent: 太棒了 78 | keep_on: 继续努力。 79 | symbol: 80 | comma: "," 81 | period: "。" 82 | colon: ":" 83 | reward: 84 | donate: 赞赏 85 | wechatpay: 微信 86 | alipay: 支付宝 87 | paypal: PayPal 88 | bitcoin: 比特币 89 | comment: 请我一杯咖啡吧! 90 | follow_me: 91 | welcome: 欢迎关注我的其它发布渠道 92 | accessibility: 93 | nav_toggle: 切换导航栏 94 | prev_page: 上一页 95 | next_page: 下一页 96 | back_to_top: 返回顶部 97 | select_lang: 选择语言 98 | symbols_count_time: 99 | count: 本文字数 100 | count_total: 站点总字数 101 | time: 阅读时长 102 | time_total: 站点阅读时长 103 | time_minutes: 分钟 104 | -------------------------------------------------------------------------------- /languages/zh-TW.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: 繁體中文 3 | title: 4 | archive: 歸檔 5 | category: 分類 6 | tag: 標籤 7 | schedule: 文章日曆 8 | menu: 9 | home: 首頁 10 | archives: 歸檔 11 | categories: 分類 12 | tags: 標籤 13 | about: 關於 14 | search: 搜尋 15 | schedule: 時間表 16 | sitemap: 網站地圖 17 | commonweal: 公益 404 18 | sidebar: 19 | overview: 本站概要 20 | toc: 文章目錄 21 | links: 連結 22 | post: 23 | posted: 發表於 24 | edited: 更新於 25 | created: 創建時間 26 | modified: 修改時間 27 | edit: 編輯 28 | in: 分類於 29 | read_more: 閱讀全文 30 | untitled: 未命名 31 | sticky: 置頂 32 | views: 閱讀次數 33 | related_posts: 相關文章 34 | copyright: 35 | author: 本文作者 36 | link: 文章連結 37 | post_author: 原作者 38 | post_link: 文章最初發表於 39 | license_title: 版權聲明 40 | license_content: "本網誌所有文章除特別聲明外,均採用 %s 許可協議。轉載請註明出處!" 41 | license_content_reprint: "本文章為轉載文章,已獲轉載許可。轉載請註明出處!" 42 | footer: 43 | powered: "由 %s 強力驅動" 44 | total_views: 總瀏覽次數 45 | total_visitors: 訪客總數 46 | widget: 47 | github: 在 GitHub 上關注我 48 | chat: 聊天 49 | counter: 50 | tag_cloud: 51 | zero: 暫無標籤 52 | one: 目前共有 1 個標籤 53 | other: "目前共有 %d 個標籤" 54 | categories: 55 | zero: 暫無分類 56 | one: 目前共有 1 個分類 57 | other: "目前共有 %d 個分類" 58 | archive_posts: 59 | zero: 沒有文章。 60 | one: 目前共有 1 篇文章。 61 | other: "目前共有 %d 篇文章。" 62 | state: 63 | posts: 文章 64 | tags: 標籤 65 | categories: 分類 66 | search: 67 | placeholder: 搜尋... 68 | empty: "我們無法找到任何有關 %s 的搜索結果" 69 | hits_time: "找到 %s 個搜索結果(用時 %s 毫秒)" 70 | hits: "找到 %s 個搜索結果" 71 | cheers: 72 | um: 嗯.. 73 | ok: 還行 74 | nice: 好 75 | good: 很好 76 | great: 非常好 77 | excellent: 太棒了 78 | keep_on: 繼續努力。 79 | symbol: 80 | comma: "," 81 | period: "。" 82 | colon: ":" 83 | reward: 84 | donate: 捐贈 85 | wechatpay: 微信支付 86 | alipay: 支付寶 87 | paypal: PayPal 88 | bitcoin: 比特幣 89 | comment: 請我喝杯咖啡! 90 | follow_me: 91 | welcome: 歡迎關注我的其它發布渠道 92 | accessibility: 93 | nav_toggle: 切換導航欄 94 | prev_page: 上一頁 95 | next_page: 下一頁 96 | back_to_top: 回到頂端 97 | select_lang: 選擇語言 98 | symbols_count_time: 99 | count: 文章字數 100 | count_total: 總字數 101 | time: 所需閱讀時間 102 | time_total: 所需總閱讀時間 103 | time_minutes: 分鐘 104 | -------------------------------------------------------------------------------- /layout/_layout.njk: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ partial('_partials/head/head.njk', {}, {cache: theme.cache.enable}) }} 5 | {%- include '_partials/head/head-unique.njk' -%} 6 | {% block title %}{% endblock %} 7 | {{ partial('_third-party/analytics/index.njk', {}, {cache: theme.cache.enable}) }} 8 | {{ partial('_scripts/index.njk', {}, {cache: theme.cache.enable}) }} 9 | {{ partial('_third-party/index.njk', {}, {cache: theme.cache.enable}) }} 10 | {{ partial('_third-party/statistics/index.njk', {}, {cache: theme.cache.enable}) }} 11 | {%- include '_third-party/math/index.njk' -%} 12 | {%- include '_third-party/quicklink.njk' -%} 13 | {{- next_inject('head') }} 14 | 17 | 18 | 19 | 20 |
21 | 22 |
23 |
24 |
25 | {%- include '_partials/header/index.njk' -%} 26 |
27 | {%- if theme.sidebar.display !== 'remove' %} 28 | {% block sidebar %}{% endblock %} 29 | {%- endif %} 30 |
31 | 32 |
33 | {%- include '_partials/header/sub-menu.njk' -%} 34 | {% block content %}{% endblock %} 35 | {%- include '_partials/comments.njk' -%} 36 |
37 |
38 | 39 | 45 | 46 | {{ partial('_partials/widgets.njk', {}, {cache: theme.cache.enable}) }} 47 | 48 | {{- next_inject('bodyEnd') }} 49 | 50 | 51 | -------------------------------------------------------------------------------- /layout/_macro/post-collapse.njk: -------------------------------------------------------------------------------- 1 | {% macro render(posts) %} 2 | {%- set current_year = '1970' %} 3 | {%- for post in posts.toArray() %} 4 | 5 | {%- set year = date(post.date, 'YYYY') %} 6 | 7 | {%- if year !== current_year %} 8 | {%- set current_year = year %} 9 |
10 | {{ current_year }}{% if is_archive() %}{{ post_count(year) }}{% endif %} 11 |
12 | {%- endif %} 13 | 14 |
15 |
16 | 23 | 24 |
25 | {%- if post.link %}{# Link posts #} 26 | {%- set postTitleIcon = '' %} 27 | {%- set postText = post.title or post.link %} 28 | {{ next_url(post.link, postText + postTitleIcon, {class: 'post-title-link post-title-link-external', itemprop: 'url'}) }} 29 | {% else %} 30 | 33 | {%- endif %} 34 |
35 | 36 | {{ post_gallery(post.photos) }} 37 |
38 |
39 | 40 | {%- endfor %} 41 | {% endmacro %} 42 | -------------------------------------------------------------------------------- /layout/_partials/comments.njk: -------------------------------------------------------------------------------- 1 | {%- if page.comments %} 2 | {%- if theme.injects.comment.length == 1 %} 3 | {%- set inject_item = theme.injects.comment[0] %} 4 | {{ partial(inject_item.layout, inject_item.locals, inject_item.options) }} 5 | {%- elif theme.injects.comment.length > 1 %} 6 | {%- if theme.comments.style == 'buttons' %} 7 |
8 | {%- for inject_item in theme.injects.comment %} 9 | {{ inject_item.locals.button }} 10 | {%- endfor %} 11 |
12 | {%- for inject_item in theme.injects.comment %} 13 |
14 | {{ partial(inject_item.layout, inject_item.locals, inject_item.options) }} 15 |
16 | {%- endfor %} 17 | {{- next_js('comments-buttons.js', { pjax: true }) }} 18 | {%- elif theme.comments.style == 'tabs' %} 19 |
20 | 25 |
26 | {%- for inject_item in theme.injects.comment %} 27 |
28 | {{ partial(inject_item.layout, inject_item.locals, inject_item.options) }} 29 |
30 | {%- endfor %} 31 |
32 |
33 | {%- endif %} 34 | {%- endif %} 35 | {%- endif %} 36 | -------------------------------------------------------------------------------- /layout/_partials/head/head-unique.njk: -------------------------------------------------------------------------------- 1 | {%- if theme.open_graph.enable %} 2 | {%- if theme.open_graph.options %} 3 | {{ open_graph(theme.open_graph.options) }} 4 | {%- else %} 5 | {{ open_graph() }} 6 | {%- endif %} 7 | {%- endif %} 8 | 9 | {# https://github.com/theme-next/hexo-theme-next/issues/866 #} 10 | {%- set canonical = url | replace(r/index\.html$/, '') %} 11 | {%- if not config.permalink.endsWith('.html') %} 12 | {%- set canonical = canonical | replace(r/\.html$/, '') %} 13 | {%- endif %} 14 | 15 | {%- if page.noindex %} 16 | 17 | {%- endif %} 18 | {# Exports some front-matter variables to Front-End #} 19 | {# https://hexo.io/docs/variables.html #} 20 | {{ next_data('page', next_config_unique()) }} 21 | 22 | {{ next_data('calendar', 23 | theme.calendar if page.type === 'schedule' else '') 24 | }} 25 | -------------------------------------------------------------------------------- /layout/_partials/header/brand.njk: -------------------------------------------------------------------------------- 1 |
2 | 11 | 12 |
13 | {%- if theme.custom_logo and theme.scheme === 'Muse' %} 14 | {{ title }} 15 | {%- endif %} 16 | 17 | 18 | 19 | <{% if is_home() or is_archive() %}h1{% else %}p{% endif %} class="site-title">{{ title }} 20 | 21 | 22 | {%- if subtitle %} 23 |

{{ subtitle }}

24 | {%- endif %} 25 | {%- if theme.custom_logo and (theme.scheme === 'Gemini' or theme.scheme === 'Pisces') %} 26 | {{ title }} 27 | {%- endif %} 28 |
29 | 30 | 37 |
38 | -------------------------------------------------------------------------------- /layout/_partials/header/index.njk: -------------------------------------------------------------------------------- 1 | {{ partial('_partials/header/brand.njk') }} 2 | 3 | {{ partial('_partials/header/menu.njk', {}, {cache: theme.cache.enable}) }} 4 | 5 | {{ partial('_partials/search/index.njk', {}, {cache: theme.cache.enable}) }} 6 | 7 | {{- next_inject('header') }} 8 | -------------------------------------------------------------------------------- /layout/_partials/header/menu-item.njk: -------------------------------------------------------------------------------- 1 | {% macro render(node) %} 2 | 3 | {%- set itemURL = node.path %} 4 | 27 | 28 | {% endmacro %} 29 | -------------------------------------------------------------------------------- /layout/_partials/header/menu.njk: -------------------------------------------------------------------------------- 1 | {% import 'menu-item.njk' as menu_item with context %} 2 | 3 | {%- if theme.menu or theme.algolia_search.enable or theme.local_search.enable %} 4 | 19 | {%- endif %} 20 | -------------------------------------------------------------------------------- /layout/_partials/header/sub-menu.njk: -------------------------------------------------------------------------------- 1 | {% import '_partials/header/menu-item.njk' as menu_item with context %} 2 | 3 | {%- if theme.menu and is_page() %} 4 | {%- set menus = next_menu(page.path) %} 5 | {%- for menu in menus %} 6 | 11 | {%- endfor %} 12 | {%- endif %} 13 | -------------------------------------------------------------------------------- /layout/_partials/languages.njk: -------------------------------------------------------------------------------- 1 | {%- if theme.language_switcher and languages.length > 1 %} 2 |
3 | 8 | 15 |
16 | {%- endif %} 17 | -------------------------------------------------------------------------------- /layout/_partials/page/breadcrumb.njk: -------------------------------------------------------------------------------- 1 | {%- set paths = page.path.split('/') %} 2 | {%- set count = paths.length %} 3 | {%- if count > 2 %} 4 | {%- set link = '/' %} 5 | 30 | {%- endif %} 31 | -------------------------------------------------------------------------------- /layout/_partials/page/categories.njk: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{ _p('counter.categories', site.categories.length) }} 4 |
5 |
6 | {{ list_categories() }} 7 |
8 |
9 | -------------------------------------------------------------------------------- /layout/_partials/page/page-header.njk: -------------------------------------------------------------------------------- 1 |
2 | 3 |

4 | {{- page.title }} 5 | {{- post_edit(page.source) }} 6 |

7 | 8 |
9 | {%- if page.description %} 10 |
{{ page.description }}
11 | {%- endif %} 12 | {%- include '_partials/page/breadcrumb.njk' -%} 13 |
14 | 15 |
16 | -------------------------------------------------------------------------------- /layout/_partials/page/schedule.njk: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{- next_js('schedule.js', { pjax: true }) }} 4 | -------------------------------------------------------------------------------- /layout/_partials/page/tags.njk: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{ _p('counter.tag_cloud', site.tags.length) }} 4 |
5 |
6 | {{ tagcloud({ 7 | min_font: theme.tagcloud.min, 8 | max_font: theme.tagcloud.max, 9 | amount : theme.tagcloud.amount, 10 | orderby : theme.tagcloud.orderby, 11 | order : theme.tagcloud.order, 12 | class : 'tag-cloud' 13 | }) 14 | }} 15 |
16 |
17 | -------------------------------------------------------------------------------- /layout/_partials/pagination.njk: -------------------------------------------------------------------------------- 1 | {%- if page.prev or page.next %} 2 | 5 | {%- endif %} 6 | -------------------------------------------------------------------------------- /layout/_partials/post/post-copyright.njk: -------------------------------------------------------------------------------- 1 | {%- set ccIcon = '' %} 2 | {%- set ccText = theme.creative_commons.license | upper %} 3 | 4 |
5 | 34 |
35 | -------------------------------------------------------------------------------- /layout/_partials/post/post-followme.njk: -------------------------------------------------------------------------------- 1 |
2 | {{ __('follow_me.welcome') }} 3 | 4 |
5 | {%- for name, value in theme.follow_me %} 6 | {%- set link = value.split('||')[0] | trim %} 7 | {%- set icon = value.split('||')[1] | trim if value.split('||')[1] else '' %} 8 | 9 | 30 | {%- endfor %} 31 |
32 |
33 | -------------------------------------------------------------------------------- /layout/_partials/post/post-related.njk: -------------------------------------------------------------------------------- 1 | 5 | 19 | -------------------------------------------------------------------------------- /layout/_partials/post/post-reward.njk: -------------------------------------------------------------------------------- 1 |
2 |
{{ __('reward.comment') }}
3 | 6 |
7 | 8 | {%- for name, image in theme.reward %} 9 | {%- set builtin = ['wechatpay', 'alipay', 'paypal', 'bitcoin'] %} 10 | {%- set translation = __('reward.' + name) if builtin.includes(name) else name %} 11 |
12 | {{ author }} {{ translation }} 13 | {{ translation }} 14 |
15 | {%- endfor %} 16 | 17 |
18 |
19 | -------------------------------------------------------------------------------- /layout/_partials/post/post-share.njk: -------------------------------------------------------------------------------- 1 | {%- if theme.addtoany.enable %} 2 |
3 | 4 | {%- for button in theme.addtoany.buttons %} 5 | 6 | {%- endfor %} 7 |
8 | {%- endif %} 9 | -------------------------------------------------------------------------------- /layout/_partials/search/index.njk: -------------------------------------------------------------------------------- 1 | {%- if theme.algolia_search.enable or theme.local_search.enable %} 2 |
3 | 23 |
24 | {%- endif %} 25 | -------------------------------------------------------------------------------- /layout/_scripts/index.njk: -------------------------------------------------------------------------------- 1 | {%- include 'vendors.njk' -%} 2 | 3 | {%- if theme.injects.comment.length > 1 %} 4 | {{- next_js('comments.js') }} 5 | {%- endif %} 6 | 7 | {{- next_js('utils.js') }} 8 | {%- if theme.motion.enable %} 9 | {{- next_js('motion.js') }} 10 | {%- endif %} 11 | 12 | {%- if theme.sidebar.display !== 'remove' %} 13 | {{- next_js('sidebar.js') }} 14 | {%- endif %} 15 | 16 | {{- next_js('next-boot.js') }} 17 | {%- if theme.bookmark.enable %} 18 | {{- next_js('bookmark.js') }} 19 | {%- endif %} 20 | {%- if theme.pjax %} 21 | {{- next_js('pjax.js') }} 22 | {%- endif %} 23 | -------------------------------------------------------------------------------- /layout/_scripts/vendors.njk: -------------------------------------------------------------------------------- 1 | {%- if theme.canvas_ribbon.enable %} 2 | 3 | {%- endif %} 4 | 5 | {%- for name in js_vendors() %} 6 | {{ next_vendors(name) }} 7 | {%- endfor %} 8 | -------------------------------------------------------------------------------- /layout/_third-party/addtoany.njk: -------------------------------------------------------------------------------- 1 | {%- if theme.addtoany.enable %} 2 | {{ next_js('third-party/addtoany.js') }} 3 | {%- endif %} 4 | -------------------------------------------------------------------------------- /layout/_third-party/analytics/baidu-analytics.njk: -------------------------------------------------------------------------------- 1 | {%- if theme.baidu_analytics %} 2 | {{ next_js('third-party/analytics/baidu-analytics.js') }} 3 | 4 | {%- endif %} 5 | -------------------------------------------------------------------------------- /layout/_third-party/analytics/cloudflare.njk: -------------------------------------------------------------------------------- 1 | {%- if theme.cloudflare_analytics %} 2 | 3 | {%- endif %} 4 | -------------------------------------------------------------------------------- /layout/_third-party/analytics/google-analytics.njk: -------------------------------------------------------------------------------- 1 | {%- if theme.google_analytics.tracking_id %} 2 | {%- if not theme.google_analytics.only_pageview %} 3 | 4 | {%- endif %} 5 | {{ next_data('google_analytics', theme.google_analytics) }} 6 | {{ next_js('third-party/analytics/google-analytics.js') }} 7 | {%- endif %} 8 | -------------------------------------------------------------------------------- /layout/_third-party/analytics/growingio.njk: -------------------------------------------------------------------------------- 1 | {%- if theme.growingio_analytics %} 2 | 3 | {{ next_data('growingio_analytics', theme.growingio_analytics) }} 4 | {{ next_js('third-party/analytics/growingio.js') }} 5 | {%- endif %} 6 | -------------------------------------------------------------------------------- /layout/_third-party/analytics/index.njk: -------------------------------------------------------------------------------- 1 | {%- include 'google-analytics.njk' -%} 2 | {%- include 'baidu-analytics.njk' -%} 3 | {%- include 'growingio.njk' -%} 4 | {%- include 'cloudflare.njk' -%} 5 | {%- include 'microsoft-clarity.njk' -%} 6 | {%- include 'matomo.njk' -%} 7 | {%- include 'umami.njk' -%} 8 | {%- include 'plausible.njk' -%} 9 | -------------------------------------------------------------------------------- /layout/_third-party/analytics/matomo.njk: -------------------------------------------------------------------------------- 1 | {%- if theme.matomo.enable %} 2 | {{ next_data('matomo', theme.matomo) }} 3 | {{ next_js('third-party/analytics/matomo.js') }} 4 | {%- endif %} 5 | -------------------------------------------------------------------------------- /layout/_third-party/analytics/microsoft-clarity.njk: -------------------------------------------------------------------------------- 1 | {%- if theme.clarity_analytics %} 2 | 9 | {%- endif %} 10 | -------------------------------------------------------------------------------- /layout/_third-party/analytics/plausible.njk: -------------------------------------------------------------------------------- 1 | {%- if theme.plausible.enable %} 2 | 3 | {%- endif %} 4 | -------------------------------------------------------------------------------- /layout/_third-party/analytics/umami.njk: -------------------------------------------------------------------------------- 1 | {%- if theme.umami.enable %} 2 | 3 | {%- endif %} 4 | -------------------------------------------------------------------------------- /layout/_third-party/chat/chatra.njk: -------------------------------------------------------------------------------- 1 | {{ next_data('chatra', theme.chatra) }} 2 | {{ next_js('third-party/chat/chatra.js') }} 3 | 4 | -------------------------------------------------------------------------------- /layout/_third-party/chat/tidio.njk: -------------------------------------------------------------------------------- 1 | 2 | {{ next_js('third-party/chat/tidio.js') }} 3 | -------------------------------------------------------------------------------- /layout/_third-party/comments/changyan.njk: -------------------------------------------------------------------------------- 1 | {{ next_data('changyan', theme.changyan) }} 2 | {{ next_js('third-party/comments/changyan.js') }} 3 | -------------------------------------------------------------------------------- /layout/_third-party/comments/disqus.njk: -------------------------------------------------------------------------------- 1 | {{ next_data('disqus', theme.disqus, { 2 | i18n: { 3 | disqus: __('disqus') 4 | } 5 | }) }} 6 | {{ next_js('third-party/comments/disqus.js') }} 7 | -------------------------------------------------------------------------------- /layout/_third-party/comments/disqusjs.njk: -------------------------------------------------------------------------------- 1 | {{ next_vendors('disqusjs_css') }} 2 | 3 | {{ next_data('disqusjs', theme.disqusjs, { 4 | js: theme.vendors.disqusjs_js 5 | }) }} 6 | {{ next_js('third-party/comments/disqusjs.js') }} 7 | -------------------------------------------------------------------------------- /layout/_third-party/comments/gitalk.njk: -------------------------------------------------------------------------------- 1 | {{ next_vendors('gitalk_css') }} 2 | 3 | {{ next_data('gitalk', theme.gitalk, { 4 | js: theme.vendors.gitalk_js, 5 | path_md5: gitalk_md5(page.path) 6 | }) }} 7 | {{ next_js('third-party/comments/gitalk.js') }} 8 | -------------------------------------------------------------------------------- /layout/_third-party/comments/isso.njk: -------------------------------------------------------------------------------- 1 | {{ next_data('isso', theme.isso) }} 2 | {{ next_js('third-party/comments/isso.js') }} 3 | -------------------------------------------------------------------------------- /layout/_third-party/comments/livere.njk: -------------------------------------------------------------------------------- 1 | {{ next_js('third-party/comments/livere.js') }} 2 | -------------------------------------------------------------------------------- /layout/_third-party/comments/utterances.njk: -------------------------------------------------------------------------------- 1 | {{ next_data('utterances', theme.utterances) }} 2 | {{ next_js('third-party/comments/utterances.js') }} 3 | -------------------------------------------------------------------------------- /layout/_third-party/fancybox.njk: -------------------------------------------------------------------------------- 1 | {%- if theme.fancybox %} 2 | {{ next_js('third-party/fancybox.js') }} 3 | {%- endif %} 4 | -------------------------------------------------------------------------------- /layout/_third-party/index.njk: -------------------------------------------------------------------------------- 1 | {%- if theme.algolia_search.enable %} 2 | {%- include 'search/algolia-search.njk' -%} 3 | {%- elif theme.local_search.enable %} 4 | {%- include 'search/localsearch.njk' -%} 5 | {%- endif %} 6 | 7 | {%- if theme.chatra.enable %} 8 | {%- include 'chat/chatra.njk' -%} 9 | {%- elif theme.tidio.enable %} 10 | {%- include 'chat/tidio.njk' -%} 11 | {%- endif %} 12 | 13 | {%- include 'tags/pdf.njk' -%} 14 | {%- include 'tags/mermaid.njk' -%} 15 | {%- include 'tags/wavedrom.njk' -%} 16 | 17 | {%- include 'fancybox.njk' -%} 18 | {%- include 'pace.njk' -%} 19 | {%- include 'addtoany.njk' -%} 20 | -------------------------------------------------------------------------------- /layout/_third-party/math/index.njk: -------------------------------------------------------------------------------- 1 | {%- if theme.math.mathjax.enable or theme.math.katex.enable %} 2 | {%- set enable_math = false %} 3 | 4 | {%- set is_index_has_math = false %} 5 | 6 | {# At home, check if there has `mathjax: true` post #} 7 | {%- if is_home() and not theme.math.every_page %} 8 | {%- for post in page.posts.toArray() %} 9 | {%- if post.mathjax and not is_index_has_math %} 10 | {%- set is_index_has_math = true %} 11 | {%- endif %} 12 | {%- endfor %} 13 | {%- endif %} 14 | 15 | {%- if theme.math.every_page or is_index_has_math or page.mathjax %} 16 | {%- set enable_math = true %} 17 | {%- endif %} 18 | 19 | {{ next_data('enableMath', enable_math) }} 20 | 21 | {%- if theme.math.mathjax.enable %} 22 | {%- include '_third-party/math/mathjax.njk' -%} 23 | {% elif theme.math.katex.enable %} 24 | {%- include '_third-party/math/katex.njk' -%} 25 | {%- endif %} 26 | {%- endif %} 27 | -------------------------------------------------------------------------------- /layout/_third-party/math/katex.njk: -------------------------------------------------------------------------------- 1 | {{ next_vendors('katex') }} 2 | {%- if theme.math.katex.copy_tex %} 3 | {{ next_data('katex', { 4 | copy_tex_js: theme.vendors.copy_tex_js 5 | }) }} 6 | {{ next_js('third-party/math/katex.js') }} 7 | {%- endif %} 8 | -------------------------------------------------------------------------------- /layout/_third-party/math/mathjax.njk: -------------------------------------------------------------------------------- 1 | {{ next_data('mathjax', theme.math.mathjax, { 2 | js: theme.vendors.mathjax 3 | }) }} 4 | {{ next_js('third-party/math/mathjax.js') }} 5 | -------------------------------------------------------------------------------- /layout/_third-party/pace.njk: -------------------------------------------------------------------------------- 1 | {%- if theme.pace.enable %} 2 | {{ next_js('third-party/pace.js') }} 3 | {%- endif %} 4 | -------------------------------------------------------------------------------- /layout/_third-party/quicklink.njk: -------------------------------------------------------------------------------- 1 | {%- if theme.quicklink.enable %} 2 | {{ next_vendors('quicklink') }} 3 | {{ next_data('quicklink', page.quicklink, { 4 | url: url | replace(r/index\.html$/, '') 5 | }) }} 6 | {{ next_js('third-party/quicklink.js') }} 7 | {%- endif %} 8 | -------------------------------------------------------------------------------- /layout/_third-party/search/algolia-search.njk: -------------------------------------------------------------------------------- 1 | {{ next_vendors('algolia_search') }} 2 | 3 | {{- next_js('third-party/search/algolia-search.js') }} 4 | -------------------------------------------------------------------------------- /layout/_third-party/search/localsearch.njk: -------------------------------------------------------------------------------- 1 | {{ next_vendors('local_search') }} 2 | {{ next_js('third-party/search/local-search.js') }} 3 | -------------------------------------------------------------------------------- /layout/_third-party/statistics/busuanzi-counter.njk: -------------------------------------------------------------------------------- 1 | {%- if theme.busuanzi_count.enable %} 2 | {%- if theme.vendors.busuanzi %} 3 | 4 | {%- else %} 5 | 6 | {%- endif %} 7 | {%- endif %} 8 | -------------------------------------------------------------------------------- /layout/_third-party/statistics/firestore.njk: -------------------------------------------------------------------------------- 1 | {%- if theme.firestore.enable %} 2 | {{ next_vendors('firebase_app') }} 3 | {{ next_vendors('firebase_firestore') }} 4 | {{ next_data('firestore', theme.firestore) }} 5 | {{ next_js('third-party/statistics/firestore.js') }} 6 | {%- endif %} 7 | -------------------------------------------------------------------------------- /layout/_third-party/statistics/index.njk: -------------------------------------------------------------------------------- 1 | {%- include 'busuanzi-counter.njk' -%} 2 | {%- include 'firestore.njk' -%} 3 | {%- include 'lean-analytics.njk' -%} 4 | -------------------------------------------------------------------------------- /layout/_third-party/statistics/lean-analytics.njk: -------------------------------------------------------------------------------- 1 | {%- if theme.leancloud_visitors.enable %} 2 | {{ next_data('leancloud_visitors', theme.leancloud_visitors) }} 3 | {{ next_js('third-party/statistics/lean-analytics.js') }} 4 | {%- endif %} 5 | -------------------------------------------------------------------------------- /layout/_third-party/tags/mermaid.njk: -------------------------------------------------------------------------------- 1 | {%- if theme.mermaid.enable %} 2 | {{ next_data('mermaid', theme.mermaid, { 3 | js: theme.vendors.mermaid 4 | }) }} 5 | {{ next_js('third-party/tags/mermaid.js') }} 6 | {%- endif %} 7 | -------------------------------------------------------------------------------- /layout/_third-party/tags/pdf.njk: -------------------------------------------------------------------------------- 1 | {%- if theme.pdf.enable %} 2 | {{ next_data('pdf', { 3 | object_url: theme.vendors.pdfobject, 4 | url: url_for('lib/pdf/web/viewer.html') 5 | }) }} 6 | {{ next_js('third-party/tags/pdf.js') }} 7 | {%- endif %} 8 | -------------------------------------------------------------------------------- /layout/_third-party/tags/wavedrom.njk: -------------------------------------------------------------------------------- 1 | {%- if theme.wavedrom.enable %} 2 | {{ next_data('wavedrom', theme.wavedrom, { 3 | js: theme.vendors.wavedrom 4 | }) }} 5 | {{ next_data('wavedrom_skin', theme.wavedrom, { 6 | js: theme.vendors.wavedrom_skin 7 | }) }} 8 | {{ next_js('third-party/tags/wavedrom.js') }} 9 | {%- endif %} 10 | -------------------------------------------------------------------------------- /layout/archive.njk: -------------------------------------------------------------------------------- 1 | {% extends '_layout.njk' %} 2 | {% import '_macro/post-collapse.njk' as post_template with context %} 3 | {% import '_macro/sidebar.njk' as sidebar_template with context %} 4 | 5 | {% block title %}{{ __('title.archive') }} | {{ title }}{% endblock %} 6 | 7 | {% block class %}archive posts-collapse{% endblock %} 8 | 9 | {% block content %} 10 | 11 | {#####################} 12 | {### ARCHIVE BLOCK ###} 13 | {#####################} 14 |
15 |
16 |
17 | {%- set posts_length = site.posts.length %} 18 | {%- if posts_length > 210 %} 19 | {%- set cheers = 'excellent' %} 20 | {% elif posts_length > 130 %} 21 | {%- set cheers = 'great' %} 22 | {% elif posts_length > 80 %} 23 | {%- set cheers = 'good' %} 24 | {% elif posts_length > 50 %} 25 | {%- set cheers = 'nice' %} 26 | {% elif posts_length > 30 %} 27 | {%- set cheers = 'ok' %} 28 | {% else %} 29 | {%- set cheers = 'um' %} 30 | {%- endif %} 31 | {{ __('cheers.' + cheers) }}! {{ _p('counter.archive_posts', site.posts.length) }} {{ __('keep_on') }} 32 |
33 | 34 | {{ post_template.render(page.posts) }} 35 | 36 |
37 |
38 | {#########################} 39 | {### END ARCHIVE BLOCK ###} 40 | {#########################} 41 | 42 | {%- include '_partials/pagination.njk' -%} 43 | 44 | {% endblock %} 45 | 46 | {% block sidebar %} 47 | {{ sidebar_template.render(false) }} 48 | {% endblock %} 49 | -------------------------------------------------------------------------------- /layout/category.njk: -------------------------------------------------------------------------------- 1 | {% extends '_layout.njk' %} 2 | {% import '_macro/post-collapse.njk' as post_template with context %} 3 | {% import '_macro/sidebar.njk' as sidebar_template with context %} 4 | 5 | {% block title %}{{ __('title.category') }}: {{ page.category }} | {{ title }}{% endblock %} 6 | 7 | {% block class %}category posts-collapse{% endblock %} 8 | 9 | {% block content %} 10 | 11 | {######################} 12 | {### CATEGORY BLOCK ###} 13 | {######################} 14 |
15 |
16 |
17 |

18 | {{- page.category }} 19 | {{ __('title.category') }} 20 |

21 |
22 | 23 | {{ post_template.render(page.posts) }} 24 |
25 |
26 | {##########################} 27 | {### END CATEGORY BLOCK ###} 28 | {##########################} 29 | 30 | {%- include '_partials/pagination.njk' -%} 31 | 32 | {% endblock %} 33 | 34 | {% block sidebar %} 35 | {{ sidebar_template.render(false) }} 36 | {% endblock %} 37 | -------------------------------------------------------------------------------- /layout/index.njk: -------------------------------------------------------------------------------- 1 | {% extends '_layout.njk' %} 2 | {% import '_macro/sidebar.njk' as sidebar_template with context %} 3 | 4 | {% block title %}{{ title }}{% if theme.index_with_subtitle and subtitle %} - {{ subtitle }}{% endif %}{% endblock %} 5 | 6 | {% block class %}index posts-expand{% endblock %} 7 | 8 | {% block content %} 9 | 10 | {%- for post in page.posts.toArray() %} 11 | {{ partial('_macro/post.njk', {post: post, is_index: true}) }} 12 | {%- endfor %} 13 | 14 | {%- include '_partials/pagination.njk' -%} 15 | 16 | {% endblock %} 17 | 18 | {% block sidebar %} 19 | {{ sidebar_template.render(false) }} 20 | {% endblock %} 21 | -------------------------------------------------------------------------------- /layout/page.njk: -------------------------------------------------------------------------------- 1 | {% extends '_layout.njk' %} 2 | {% import '_macro/sidebar.njk' as sidebar_template with context %} 3 | 4 | {% block title %} 5 | {%- set page_title_suffix = ' | ' + title %} 6 | 7 | {%- if page.type === 'categories' and not page.title %} 8 | {{- __('title.category') + page_title_suffix }} 9 | {%- elif page.type === 'tags' and not page.title %} 10 | {{- __('title.tag') + page_title_suffix }} 11 | {%- elif page.type === 'schedule' and not page.title %} 12 | {{- __('title.schedule') + page_title_suffix }} 13 | {%- else %} 14 | {{- page.title + page_title_suffix }} 15 | {%- endif %} 16 | {% endblock %} 17 | 18 | {% block class %}page posts-expand{% endblock %} 19 | 20 | {% block content %} 21 | 22 | {##################} 23 | {### PAGE BLOCK ###} 24 | {##################} 25 |
26 | {%- if page.header !== false %} 27 | {%- include '_partials/page/page-header.njk' -%} 28 | {%- endif %} 29 | {#################} 30 | {### PAGE BODY ###} 31 | {#################} 32 |
33 | {%- if page.type === 'tags' %} 34 | {%- include '_partials/page/tags.njk' -%} 35 | {% elif page.type === 'categories' %} 36 | {%- include '_partials/page/categories.njk' -%} 37 | {% elif page.type === 'schedule' %} 38 | {%- include '_partials/page/schedule.njk' -%} 39 | {% else %} 40 | {{ page.content }} 41 | {%- endif %} 42 |
43 | {#####################} 44 | {### END PAGE BODY ###} 45 | {#####################} 46 |
47 | {%- include '_partials/page/breadcrumb.njk' -%} 48 | {######################} 49 | {### END PAGE BLOCK ###} 50 | {######################} 51 | 52 | {% endblock %} 53 | 54 | {% block sidebar %} 55 | {{ sidebar_template.render(page.toc.enable) }} 56 | {% endblock %} 57 | -------------------------------------------------------------------------------- /layout/post.njk: -------------------------------------------------------------------------------- 1 | {% extends '_layout.njk' %} 2 | {% import '_macro/sidebar.njk' as sidebar_template with context %} 3 | 4 | {% block title %}{{ page.title }} | {{ title }}{% endblock %} 5 | 6 | {% block class %}post posts-expand{% endblock %} 7 | 8 | {% block content %} 9 | 10 | {{ partial('_macro/post.njk', {post: page}) }} 11 | 12 | {% endblock %} 13 | 14 | {% block sidebar %} 15 | {{ sidebar_template.render(page.toc.enable) }} 16 | {% endblock %} 17 | -------------------------------------------------------------------------------- /layout/tag.njk: -------------------------------------------------------------------------------- 1 | {% extends '_layout.njk' %} 2 | {% import '_macro/post-collapse.njk' as post_template with context %} 3 | {% import '_macro/sidebar.njk' as sidebar_template with context %} 4 | 5 | {% block title %}{{ __('title.tag') }}: {{ page.tag }} | {{ title }}{% endblock %} 6 | 7 | {% block class %}tag posts-collapse{% endblock %} 8 | 9 | {% block content %} 10 | 11 | {#################} 12 | {### TAG BLOCK ###} 13 | {#################} 14 |
15 |
16 |
17 |

18 | {{- page.tag }} 19 | {{ __('title.tag') }} 20 |

21 |
22 | 23 | {{ post_template.render(page.posts) }} 24 |
25 |
26 | {#####################} 27 | {### END TAG BLOCK ###} 28 | {#####################} 29 | 30 | {%- include '_partials/pagination.njk' -%} 31 | 32 | {% endblock %} 33 | 34 | {% block sidebar %} 35 | {{ sidebar_template.render(false) }} 36 | {% endblock %} 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hexo-theme-next", 3 | "version": "8.23.0", 4 | "description": "Elegant and powerful theme for Hexo.", 5 | "main": "package.json", 6 | "files": [ 7 | "docs", 8 | "languages", 9 | "layout", 10 | "scripts", 11 | "source", 12 | "_config.yml", 13 | "_vendors.yml" 14 | ], 15 | "scripts": { 16 | "eslint": "eslint scripts/ source/js test/", 17 | "prepare": "node .githooks/install.js", 18 | "stylint": "stylelint source/css/ --ip source/css/_common/scaffolding/highlight/index.styl", 19 | "test": "mocha test/index.js", 20 | "test-cov": "c8 npm test" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "git+https://github.com/next-theme/hexo-theme-next.git" 25 | }, 26 | "keywords": [ 27 | "hexo", 28 | "theme", 29 | "next" 30 | ], 31 | "author": "NexT (https://theme-next.js.org)", 32 | "license": "AGPL-3.0-only", 33 | "bugs": { 34 | "url": "https://github.com/next-theme/hexo-theme-next/issues" 35 | }, 36 | "homepage": "https://theme-next.js.org", 37 | "devDependencies": { 38 | "@next-theme/eslint-config": "0.0.4", 39 | "c8": "10.1.3", 40 | "chai": "5.2.0", 41 | "eslint": "9.23.0", 42 | "hexo": "7.3.0", 43 | "hexo-renderer-marked": "7.0.1", 44 | "js-yaml": "4.1.0", 45 | "mocha": "11.1.0", 46 | "stylelint": "16.17.0", 47 | "stylelint-stylus": "1.0.0" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /scripts/events/index.js: -------------------------------------------------------------------------------- 1 | /* global hexo */ 2 | 3 | 'use strict'; 4 | 5 | hexo.extend.filter.register('before_generate', () => { 6 | // Merge config 7 | require('./lib/config')(hexo); 8 | // Set vendors 9 | require('./lib/vendors')(hexo); 10 | // Add filter type `theme_inject` 11 | require('./lib/injects')(hexo); 12 | // Highlight 13 | require('./lib/highlight')(hexo); 14 | // Menu and sub menu 15 | require('./lib/navigation')(hexo); 16 | }, 0); 17 | 18 | hexo.on('ready', () => { 19 | if (!/^(g|s)/.test(hexo.env.cmd) || process.argv.includes('--next-disable-banner')) return; 20 | const { version } = require('../../package.json'); 21 | hexo.log.info(`================================== 22 | ███╗ ██╗███████╗██╗ ██╗████████╗ 23 | ████╗ ██║██╔════╝╚██╗██╔╝╚══██╔══╝ 24 | ██╔██╗ ██║█████╗ ╚███╔╝ ██║ 25 | ██║╚██╗██║██╔══╝ ██╔██╗ ██║ 26 | ██║ ╚████║███████╗██╔╝ ██╗ ██║ 27 | ╚═╝ ╚═══╝╚══════╝╚═╝ ╚═╝ ╚═╝ 28 | ======================================== 29 | NexT version ${version} 30 | Documentation: https://theme-next.js.org 31 | ========================================`); 32 | }); 33 | -------------------------------------------------------------------------------- /scripts/events/lib/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { deepMerge } = require('hexo-util'); 4 | 5 | module.exports = hexo => { 6 | const data = hexo.locals.get('data'); 7 | 8 | if (data.next) { 9 | hexo.log.warn('`next.yml` is deprecated. Please upgrade to Hexo 5 or later and use `_config.next.yml` instead.'); 10 | hexo.log.warn('Documentation: https://theme-next.js.org/docs/getting-started/configuration.html'); 11 | } 12 | 13 | const { cache, language_switcher } = hexo.theme.config; 14 | const warning = function(...args) { 15 | hexo.log.warn(`Since ${args[0]} is turned on, the ${args[1]} is disabled to avoid potential hazards.`); 16 | }; 17 | 18 | if (cache?.enable && language_switcher) { 19 | warning('language_switcher', 'caching'); 20 | cache.enable = false; 21 | } 22 | if (cache?.enable && hexo.config.relative_link) { 23 | warning('caching', '`relative_link` option in Hexo `_config.yml`'); 24 | hexo.config.relative_link = false; 25 | } 26 | 27 | // Custom languages support. Introduced in NexT v6.3.0. 28 | if (data.languages) { 29 | const { language } = hexo.config; 30 | const { i18n } = hexo.theme; 31 | 32 | const mergeLang = lang => { 33 | if (data.languages[lang]) i18n.set(lang, deepMerge(i18n.get([lang]), data.languages[lang])); 34 | }; 35 | 36 | if (Array.isArray(language)) { 37 | for (const lang of language) { 38 | mergeLang(lang); 39 | } 40 | } else { 41 | mergeLang(language); 42 | } 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /scripts/events/lib/highlight.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const { resolve, highlightTheme } = require('./utils'); 5 | 6 | function prismTheme(name) { 7 | let file = resolve('prismjs', `themes/${name}.css`); 8 | if (!fs.existsSync(file)) file = resolve('prism-themes', `themes/${name}.css`); 9 | return file; 10 | } 11 | 12 | module.exports = hexo => { 13 | const { config } = hexo; 14 | const theme = hexo.theme.config; 15 | config.highlight.hljs = false; 16 | theme.highlight = { 17 | enable: config.syntax_highlighter === 'highlight.js' || config.highlight.enable, 18 | light : highlightTheme(theme.codeblock.theme.light), 19 | dark : highlightTheme(theme.codeblock.theme.dark) 20 | }; 21 | theme.prism = { 22 | enable: config.syntax_highlighter === 'prismjs' || config.prismjs.enable, 23 | light : prismTheme(theme.codeblock.prism.light), 24 | dark : prismTheme(theme.codeblock.prism.dark), 25 | number: resolve('prismjs', 'plugins/line-numbers/prism-line-numbers.css') 26 | }; 27 | }; 28 | -------------------------------------------------------------------------------- /scripts/events/lib/navigation.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { join } = require('path').posix; 4 | 5 | class TreeNode { 6 | constructor(parent, path, name, icon) { 7 | if (parent && !path.startsWith('http')) { 8 | path = join(parent.path, path); 9 | } 10 | this.parent = parent; 11 | this.children = []; 12 | this.path = path; 13 | this.name = name; 14 | this.icon = icon; 15 | } 16 | 17 | append(child) { 18 | this.children.push(child); 19 | } 20 | } 21 | 22 | module.exports = hexo => { 23 | const menu_map = new Map(); 24 | const main_menu = []; 25 | hexo.theme.config.menu_map = menu_map; 26 | hexo.theme.config.main_menu = main_menu; 27 | 28 | function parse(menu, parent) { 29 | if (!menu) return; 30 | Object.entries(menu).forEach(([name, value]) => { 31 | if (name.toLowerCase() === 'default') return; 32 | let node; 33 | if (typeof value === 'string') { 34 | const [path, icon] = value.split('||').map(v => v.trim()); 35 | node = new TreeNode(parent, path, name, icon); 36 | } else if (typeof value === 'object') { 37 | if (typeof value.default !== 'string') { 38 | hexo.log.warn('Missing default entry for menu item:', name); 39 | return; 40 | } 41 | const [path, icon] = value.default.split('||').map(v => v.trim()); 42 | node = new TreeNode(parent, path, name, icon); 43 | parse(value, node); 44 | } 45 | if (node) { 46 | menu_map.set(node.path, node); 47 | if (parent) { 48 | parent.append(node); 49 | } else { 50 | main_menu.push(node); 51 | } 52 | } 53 | }); 54 | } 55 | 56 | parse(hexo.theme.config.menu); 57 | }; 58 | -------------------------------------------------------------------------------- /scripts/events/lib/vendors.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const yaml = require('js-yaml'); 6 | const { url_for } = require('hexo-util'); 7 | const { getVendors } = require('./utils'); 8 | 9 | let internal; 10 | try { 11 | internal = require('@next-theme/plugins'); 12 | } catch { 13 | } 14 | const vendorsFile = fs.readFileSync(path.join(__dirname, '../../../_vendors.yml')); 15 | const dependencies = yaml.load(vendorsFile); 16 | 17 | module.exports = hexo => { 18 | const { vendors, creative_commons, pace } = hexo.theme.config; 19 | if (typeof internal === 'function') { 20 | internal(hexo, dependencies); 21 | } 22 | let { plugins = 'cdnjs' } = vendors; 23 | if (plugins === 'local' && typeof internal === 'undefined') { 24 | hexo.log.warn('Dependencies for `plugins: local` not found. The default CDN provider CDNJS is used instead.'); 25 | hexo.log.warn('Run `npm install @next-theme/plugins` in Hexo site root directory to install the plugin.'); 26 | plugins = 'cdnjs'; 27 | } 28 | for (const [key, value] of Object.entries(dependencies)) { 29 | // This script will be executed repeatedly when Hexo listens file changes 30 | // But the variable vendors[key] only needs to be modified once 31 | if (vendors[key] && typeof vendors[key] === 'string') { 32 | vendors[key] = { 33 | url: url_for.call(hexo, vendors[key]) 34 | }; 35 | continue; 36 | } 37 | if (key === 'creative_commons') { 38 | value.file = `${value.dir}/${creative_commons.size}/${creative_commons.license.replace(/-/g, '_')}.svg`; 39 | } 40 | if (key === 'pace_css') { 41 | value.file = `${value.dir}/${pace.color}/pace-theme-${pace.theme}.css`; 42 | } 43 | const { name, file } = value; 44 | const links = getVendors({ 45 | ...value, 46 | minified: file, 47 | local : url_for.call(hexo, `lib/${name}/${file}`), 48 | custom : vendors.custom_cdn_url 49 | }); 50 | vendors[key] = { 51 | url : links[plugins] || links.cdnjs, 52 | integrity: value.integrity 53 | }; 54 | } 55 | }; 56 | -------------------------------------------------------------------------------- /scripts/filters/comment/changyan.js: -------------------------------------------------------------------------------- 1 | /* global hexo */ 2 | 3 | 'use strict'; 4 | 5 | const path = require('path'); 6 | const { iconText } = require('./common'); 7 | 8 | // Add comment 9 | hexo.extend.filter.register('theme_inject', injects => { 10 | const config = hexo.theme.config.changyan; 11 | if (!config.enable || !config.appid || !config.appkey) return; 12 | 13 | injects.comment.raw('changyan', '
', {}, {}); 14 | 15 | injects.bodyEnd.file('changyan', path.join(hexo.theme_dir, 'layout/_third-party/comments/changyan.njk')); 16 | 17 | }); 18 | 19 | // Add post_meta 20 | hexo.extend.filter.register('theme_inject', injects => { 21 | const config = hexo.theme.config.changyan; 22 | if (!config.enable || !config.count || !config.appid || !config.appkey) return; 23 | 24 | injects.postMeta.raw('changyan', ` 25 | {% if post.comments %} 26 | 32 | {% endif %} 33 | `, {}, {}); 34 | 35 | }); 36 | -------------------------------------------------------------------------------- /scripts/filters/comment/common.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function capitalize(input) { 4 | return input.toString().charAt(0).toUpperCase() + input.toString().substring(1); 5 | } 6 | 7 | module.exports = { 8 | iconText(icon, key, defaultValue) { 9 | if (!defaultValue) { 10 | defaultValue = capitalize(key); 11 | } 12 | return ` 13 | 16 | {%- set post_meta_comment = __('post.comments.${key}') %} 17 | {%- if post_meta_comment == 'post.comments.${key}' %} 18 | {%- set post_meta_comment = '${defaultValue}' %} 19 | {%- endif %} 20 | 21 | `; 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /scripts/filters/comment/default-config.js: -------------------------------------------------------------------------------- 1 | /* global hexo */ 2 | 3 | 'use strict'; 4 | 5 | const path = require('path'); 6 | 7 | hexo.extend.filter.register('theme_inject', injects => { 8 | injects.comment.raws.forEach(element => { 9 | // Set default button content 10 | const injectName = path.basename(element.name, path.extname(element.name)); 11 | element.args[0] = Object.assign({ 12 | configKey: injectName, 13 | class : injectName, 14 | button : injectName 15 | }, element.args[0]); 16 | // Get locals and config 17 | const locals = element.args[0]; 18 | const config = hexo.theme.config.comments; 19 | // Set activeClass 20 | if (config.active === locals.configKey) { 21 | config.activeClass = locals.class; 22 | } 23 | // Set custom button content 24 | if (config.nav) { 25 | const nav = config.nav[locals.configKey] || {}; 26 | if (nav.order) { 27 | element.args[2] = nav.order; 28 | } 29 | if (nav.text) { 30 | locals.button = nav.text; 31 | } 32 | } 33 | }); 34 | }, 99999); 35 | -------------------------------------------------------------------------------- /scripts/filters/comment/disqus.js: -------------------------------------------------------------------------------- 1 | /* global hexo */ 2 | 3 | 'use strict'; 4 | 5 | const path = require('path'); 6 | const { iconText } = require('./common'); 7 | 8 | // Add comment 9 | hexo.extend.filter.register('theme_inject', injects => { 10 | const config = hexo.theme.config.disqus; 11 | if (!config.enable || !config.shortname) return; 12 | 13 | injects.comment.raw('disqus', ` 14 |
15 | 16 |
17 | `, {}, { cache: true }); 18 | 19 | injects.bodyEnd.file('disqus', path.join(hexo.theme_dir, 'layout/_third-party/comments/disqus.njk')); 20 | 21 | }); 22 | 23 | // Add post_meta 24 | hexo.extend.filter.register('theme_inject', injects => { 25 | const config = hexo.theme.config.disqus; 26 | if (!config.enable || !config.shortname || !config.count) return; 27 | 28 | injects.postMeta.raw('disqus', ` 29 | {% if post.comments %} 30 | 36 | {% endif %} 37 | `, {}, {}); 38 | 39 | }); 40 | -------------------------------------------------------------------------------- /scripts/filters/comment/disqusjs.js: -------------------------------------------------------------------------------- 1 | /* global hexo */ 2 | 3 | 'use strict'; 4 | 5 | const path = require('path'); 6 | 7 | // Add comment 8 | hexo.extend.filter.register('theme_inject', injects => { 9 | const config = hexo.theme.config.disqusjs; 10 | if (!config.enable || !config.shortname || !config.apikey) return; 11 | 12 | injects.comment.raw('disqusjs', ` 13 |
14 | 15 |
16 | `, {}, { cache: true }); 17 | 18 | injects.bodyEnd.file('disqusjs', path.join(hexo.theme_dir, 'layout/_third-party/comments/disqusjs.njk')); 19 | 20 | }); 21 | -------------------------------------------------------------------------------- /scripts/filters/comment/gitalk.js: -------------------------------------------------------------------------------- 1 | /* global hexo */ 2 | 3 | 'use strict'; 4 | 5 | const path = require('path'); 6 | 7 | // Add comment 8 | hexo.extend.filter.register('theme_inject', injects => { 9 | const theme = hexo.theme.config; 10 | if (!theme.gitalk.enable) return; 11 | 12 | injects.comment.raw('gitalk', '
', {}, { cache: true }); 13 | 14 | injects.bodyEnd.file('gitalk', path.join(hexo.theme_dir, 'layout/_third-party/comments/gitalk.njk')); 15 | 16 | }); 17 | -------------------------------------------------------------------------------- /scripts/filters/comment/isso.js: -------------------------------------------------------------------------------- 1 | /* global hexo */ 2 | 3 | 'use strict'; 4 | 5 | const path = require('path'); 6 | 7 | // Add comment 8 | hexo.extend.filter.register('theme_inject', injects => { 9 | const theme = hexo.theme.config; 10 | if (!theme.isso) return; 11 | 12 | injects.comment.raw('isso', '
', {}, { cache: true }); 13 | 14 | injects.bodyEnd.file('isso', path.join(hexo.theme_dir, 'layout/_third-party/comments/isso.njk')); 15 | 16 | }); 17 | -------------------------------------------------------------------------------- /scripts/filters/comment/livere.js: -------------------------------------------------------------------------------- 1 | /* global hexo */ 2 | 3 | 'use strict'; 4 | 5 | const path = require('path'); 6 | 7 | // Add comment 8 | hexo.extend.filter.register('theme_inject', injects => { 9 | const theme = hexo.theme.config; 10 | if (!theme.livere_uid) return; 11 | 12 | injects.comment.raw('livere', '
', {}, { cache: true }); 13 | 14 | injects.bodyEnd.file('livere', path.join(hexo.theme_dir, 'layout/_third-party/comments/livere.njk')); 15 | 16 | }); 17 | -------------------------------------------------------------------------------- /scripts/filters/comment/utterances.js: -------------------------------------------------------------------------------- 1 | /* global hexo */ 2 | 3 | 'use strict'; 4 | 5 | const path = require('path'); 6 | 7 | // Add comment 8 | hexo.extend.filter.register('theme_inject', injects => { 9 | const config = hexo.theme.config.utterances; 10 | if (!config.enable) return; 11 | 12 | if (!config.repo) { 13 | hexo.log.warn('utterances.repo can\'t be null.'); 14 | return; 15 | } 16 | 17 | injects.comment.raw('utterances', '
', {}, { cache: true }); 18 | 19 | injects.bodyEnd.file('utterances', path.join(hexo.theme_dir, 'layout/_third-party/comments/utterances.njk')); 20 | 21 | }); 22 | -------------------------------------------------------------------------------- /scripts/filters/default-injects.js: -------------------------------------------------------------------------------- 1 | /* global hexo */ 2 | 3 | 'use strict'; 4 | 5 | const { points } = require('../events/lib/utils'); 6 | 7 | hexo.extend.filter.register('theme_inject', injects => { 8 | const filePath = hexo.theme.config.custom_file_path; 9 | 10 | if (!filePath) return; 11 | 12 | points.views.forEach(key => { 13 | if (filePath[key]) { 14 | injects[key].file('custom', filePath[key]); 15 | } 16 | }); 17 | 18 | points.styles.forEach(key => { 19 | if (filePath[key]) { 20 | injects[key].push(filePath[key]); 21 | } 22 | }); 23 | 24 | }, 99); 25 | -------------------------------------------------------------------------------- /scripts/filters/locals.js: -------------------------------------------------------------------------------- 1 | /* global hexo */ 2 | 3 | 'use strict'; 4 | 5 | const keys = ['toc', 'reward_settings', 'quicklink']; 6 | 7 | hexo.extend.filter.register('template_locals', locals => { 8 | const { config } = hexo; 9 | const { __, theme, page } = locals; 10 | const { i18n } = hexo.theme; 11 | // Hexo & NexT version 12 | locals.next_version = require('../../package.json').version; 13 | // Language & Config 14 | locals.title = __('title') !== 'title' ? __('title') : config.title; 15 | locals.subtitle = __('subtitle') !== 'subtitle' ? __('subtitle') : config.subtitle; 16 | locals.author = __('author') !== 'author' ? __('author') : config.author; 17 | locals.description = __('description') !== 'description' ? __('description') : config.description; 18 | locals.languages = [...i18n.languages]; 19 | locals.languages.splice(locals.languages.indexOf('default'), 1); 20 | // See https://github.com/hexojs/hexo/pull/4614 21 | page.lang = page.lang || page.language; 22 | // Creative Commons 23 | locals.ccURL = 'https://creativecommons.org/' + (theme.creative_commons.license === 'cc-zero' ? 'publicdomain/zero/1.0/' : 'licenses/' + theme.creative_commons.license + '/4.0/') + (theme.creative_commons.language || ''); 24 | // PJAX 25 | locals.pjax = theme.pjax ? ' data-pjax' : ''; 26 | // Front-matter 27 | keys.forEach(key => { 28 | page[key] = { ...theme[key], ...page[key] }; 29 | }); 30 | // Set home or archive quicklink 31 | if (page.__index) { 32 | page.quicklink.enable = theme.quicklink.home; 33 | } 34 | if (page.archive) { 35 | page.quicklink.enable = theme.quicklink.archive; 36 | } 37 | }); 38 | -------------------------------------------------------------------------------- /scripts/filters/post.js: -------------------------------------------------------------------------------- 1 | /* global hexo */ 2 | 3 | 'use strict'; 4 | 5 | const { parse } = require('url'); 6 | const { unescapeHTML } = require('hexo-util'); 7 | 8 | hexo.extend.filter.register('after_post_render', data => { 9 | const { config } = hexo; 10 | const theme = hexo.theme.config; 11 | if (!theme.exturl && !theme.lazyload) return; 12 | if (theme.lazyload) { 13 | data.content = data.content.replace(/(]*)\ssrc=/ig, '$1 data-src='); 14 | } 15 | if (theme.exturl) { 16 | const siteHost = parse(config.url).hostname || config.url; 17 | // External URL icon 18 | const exturlIcon = theme.exturl_icon ? '' : ''; 19 | data.content = data.content.replace(/]*\shref="([^"]+)"[^>]*>([^<]+)<\/a>/ig, (match, href, html) => { 20 | // Exit if the href attribute doesn't exist. 21 | if (!href) return match; 22 | 23 | // Exit if the url has same host with `config.url`, which means it's an internal link. 24 | const link = parse(href); 25 | if (!link.protocol || link.hostname === siteHost) return match; 26 | 27 | // Return encrypted URL with title. 28 | const title = match.match(/title="([^"]+)"/); 29 | const encoded = Buffer.from(unescapeHTML(href)).toString('base64'); 30 | if (title) return `${html}${exturlIcon}`; 31 | 32 | return `${html}${exturlIcon}`; 33 | }); 34 | } 35 | 36 | }, 0); 37 | -------------------------------------------------------------------------------- /scripts/helpers/font.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // https://developers.google.com/fonts/docs/getting_started 4 | module.exports = function() { 5 | const config = this.theme.font; 6 | 7 | if (!config || !config.enable) return ''; 8 | 9 | const fontStyles = ':ital,wght@0,300;0,400;0,700;1,300;1,400;1,700'; 10 | const fontHost = config.host || 'https://fonts.googleapis.com'; 11 | 12 | // Get a font list from config 13 | let fontFamilies = []; 14 | ['global', 'title', 'headings', 'posts', 'codes'].forEach(item => { 15 | if (config[item]?.family && config[item].external) { 16 | fontFamilies = fontFamilies.concat(config[item].family.split(',')); 17 | } 18 | }); 19 | 20 | fontFamilies = fontFamilies.map(name => name.trim().replace(/\s/g, '+') + fontStyles); 21 | fontFamilies = [...new Set(fontFamilies)].map(name => 'family=' + name).join('&'); 22 | 23 | // Merge extra parameters to the final processed font string 24 | return fontFamilies ? `` : ''; 25 | }; 26 | -------------------------------------------------------------------------------- /scripts/helpers/navigation.js: -------------------------------------------------------------------------------- 1 | /* global hexo */ 2 | 3 | 'use strict'; 4 | 5 | hexo.extend.helper.register('next_menu', function(path) { 6 | path = ('/' + path).replace(/index\.html$/, ''); 7 | const { menu_map } = this.theme; 8 | if (!menu_map.has(path)) return; 9 | let node = menu_map.get(path); 10 | const menus = []; 11 | if (node.children.length) { 12 | menus.unshift(node.children); 13 | } 14 | while (node.parent) { 15 | menus.unshift(node.parent.children); 16 | node = node.parent; 17 | } 18 | return menus; 19 | }); 20 | -------------------------------------------------------------------------------- /scripts/helpers/next-config.js: -------------------------------------------------------------------------------- 1 | /* global hexo */ 2 | 3 | 'use strict'; 4 | 5 | const { parse } = require('url'); 6 | 7 | /** 8 | * Export theme config 9 | */ 10 | hexo.extend.helper.register('next_config', function() { 11 | const { config, theme, url_for, __ } = this; 12 | const exportConfig = { 13 | hostname : parse(config.url).hostname || config.url, 14 | root : config.root, 15 | images : url_for(theme.images), 16 | scheme : theme.scheme, 17 | darkmode : theme.darkmode, 18 | version : this.next_version, 19 | exturl : theme.exturl, 20 | sidebar : theme.sidebar, 21 | hljswrap : theme.highlight.enable && config.highlight.wrap, 22 | codeblock : theme.codeblock, 23 | bookmark : theme.bookmark, 24 | mediumzoom: theme.mediumzoom, 25 | lazyload : theme.lazyload, 26 | pangu : theme.pangu, 27 | comments : theme.comments, 28 | stickytabs: theme.tabs.sticky, 29 | motion : theme.motion, 30 | prism : theme.prism.enable && !config.prismjs.preprocess, 31 | i18n : { 32 | placeholder: __('search.placeholder'), 33 | empty : __('search.empty', '${query}'), 34 | hits_time : __('search.hits_time', '${hits}', '${time}'), 35 | hits : __('search.hits', '${hits}') 36 | } 37 | }; 38 | if (config.algolia && theme.algolia_search?.enable) { 39 | exportConfig.algolia = { 40 | appID : config.algolia.applicationID || config.algolia.appId, 41 | apiKey : config.algolia.apiKey, 42 | indexName: config.algolia.indexName, 43 | hits : theme.algolia_search.hits 44 | }; 45 | } 46 | if (config.search && theme.local_search?.enable) { 47 | exportConfig.path = url_for(config.search.path); 48 | exportConfig.localsearch = theme.local_search; 49 | } 50 | return exportConfig; 51 | }); 52 | 53 | hexo.extend.helper.register('next_config_unique', function() { 54 | const { page, is_home, is_post } = this; 55 | return { 56 | sidebar : page.sidebar || '', 57 | isHome : is_home(), 58 | isPost : is_post(), 59 | lang : page.lang, 60 | comments : page.comments || '', 61 | permalink: page.permalink || '', 62 | path : page.path || '', 63 | title : page.title || '' 64 | }; 65 | }); 66 | -------------------------------------------------------------------------------- /scripts/helpers/next-paginator.js: -------------------------------------------------------------------------------- 1 | /* global hexo */ 2 | 3 | 'use strict'; 4 | 5 | hexo.extend.helper.register('next_paginator', function() { 6 | const prev = this.__('accessibility.prev_page'); 7 | const next = this.__('accessibility.next_page'); 8 | let paginator = this.paginator({ 9 | prev_text: '', 10 | next_text: '', 11 | mid_size : 1, 12 | escape : false 13 | }); 14 | paginator = paginator 15 | .replace('rel="prev"', `rel="prev" title="${prev}" aria-label="${prev}"`) 16 | .replace('rel="next"', `rel="next" title="${next}" aria-label="${next}"`); 17 | return paginator; 18 | }); 19 | -------------------------------------------------------------------------------- /scripts/helpers/next-url.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { htmlTag } = require('hexo-util'); 4 | const { parse } = require('url'); 5 | 6 | module.exports = function(path, text, options = {}, decode = false) { 7 | const { config, theme } = this; 8 | const data = parse(path); 9 | const siteHost = parse(config.url).hostname || config.url; 10 | 11 | let exturl = ''; 12 | let tag = 'a'; 13 | let attrs = { href: this.url_for(path) }; 14 | 15 | // If `exturl` enabled, set spanned links only on external links. 16 | if (theme.exturl && data.protocol && data.hostname !== siteHost) { 17 | tag = 'span'; 18 | exturl = 'exturl'; 19 | const encoded = Buffer.from(path).toString('base64'); 20 | attrs = { 21 | class : exturl, 22 | 'data-url': encoded 23 | }; 24 | } 25 | 26 | for (const key in options) { 27 | 28 | /** 29 | * If option have `class` attribute, add it to 30 | * 'exturl' class if `exturl` option enabled. 31 | */ 32 | if (exturl !== '' && key === 'class') { 33 | attrs[key] += ' ' + options[key]; 34 | } else { 35 | attrs[key] = options[key]; 36 | } 37 | } 38 | 39 | // If it's external link, rewrite attributes. 40 | if (data.protocol && data.hostname !== siteHost) { 41 | attrs.external = null; 42 | 43 | if (!theme.exturl) { 44 | // Only for simple link need to rewrite/add attributes. 45 | attrs.rel = attrs.rel || 'noopener'; 46 | attrs.target = '_blank'; 47 | } else { 48 | // Remove rel attributes for `exturl` in main menu. 49 | attrs.rel = null; 50 | } 51 | } 52 | 53 | return htmlTag(tag, attrs, decode ? decodeURI(text) : text, false); 54 | }; 55 | -------------------------------------------------------------------------------- /scripts/helpers/next-vendors.js: -------------------------------------------------------------------------------- 1 | /* global hexo */ 2 | 3 | 'use strict'; 4 | 5 | hexo.extend.helper.register('js_vendors', function() { 6 | const { config, theme } = this; 7 | const vendors = ['anime']; 8 | if (theme.prism.enable && !config.prismjs.preprocess) { 9 | vendors.push('prism', 'prism_autoloader'); 10 | if (config.prismjs.line_number) { 11 | vendors.push('prism_line_numbers'); 12 | } 13 | } 14 | if (theme.pjax) { 15 | vendors.push('pjax'); 16 | } 17 | if (theme.fancybox) { 18 | vendors.push('fancybox_js'); 19 | } 20 | if (theme.mediumzoom) { 21 | vendors.push('mediumzoom'); 22 | } 23 | if (theme.lazyload) { 24 | vendors.push('lazyload'); 25 | } 26 | if (theme.pangu) { 27 | vendors.push('pangu'); 28 | } 29 | return vendors; 30 | }); 31 | -------------------------------------------------------------------------------- /scripts/tags/button.js: -------------------------------------------------------------------------------- 1 | /** 2 | * button.js | https://theme-next.js.org/docs/tag-plugins/button 3 | */ 4 | 5 | 'use strict'; 6 | 7 | module.exports = ctx => function(args) { 8 | args = args.join(' ').split(','); 9 | const url = args[0]; 10 | const text = (args[1] || '').trim(); 11 | let icon = (args[2] || '').trim(); 12 | const title = (args[3] || '').trim(); 13 | 14 | if (!url) { 15 | ctx.log.warn('URL can NOT be empty.'); 16 | } 17 | if (icon.length > 0) { 18 | if (!icon.startsWith('fa')) icon = 'fa fa-' + icon; 19 | icon = ``; 20 | } 21 | 22 | return ` 0 ? ` title="${title}"` : ''}>${icon}${text}`; 23 | }; 24 | -------------------------------------------------------------------------------- /scripts/tags/caniuse.js: -------------------------------------------------------------------------------- 1 | /** 2 | * caniuse.js | https://theme-next.js.org/docs/tag-plugins/caniuse 3 | */ 4 | 5 | 'use strict'; 6 | 7 | module.exports = ctx => function(args) { 8 | const [feature, periods = 'current'] = args.join('').split('@'); 9 | 10 | if (!feature) { 11 | ctx.log.warn('Caniuse feature can NOT be empty.'); 12 | return ''; 13 | } 14 | 15 | return ``; 16 | }; 17 | -------------------------------------------------------------------------------- /scripts/tags/center-quote.js: -------------------------------------------------------------------------------- 1 | /** 2 | * center-quote.js | https://theme-next.js.org/docs/tag-plugins/ 3 | */ 4 | 5 | 'use strict'; 6 | 7 | module.exports = ctx => function(args, content) { 8 | return `
9 | ${ctx.render.renderSync({ text: content, engine: 'markdown' })} 10 |
`; 11 | }; 12 | -------------------------------------------------------------------------------- /scripts/tags/index.js: -------------------------------------------------------------------------------- 1 | /* global hexo */ 2 | 3 | 'use strict'; 4 | 5 | const postButton = require('./button')(hexo); 6 | 7 | hexo.extend.tag.register('button', postButton); 8 | hexo.extend.tag.register('btn', postButton); 9 | 10 | const caniUse = require('./caniuse')(hexo); 11 | 12 | hexo.extend.tag.register('caniuse', caniUse); 13 | hexo.extend.tag.register('can', caniUse); 14 | 15 | const centerQuote = require('./center-quote')(hexo); 16 | 17 | hexo.extend.tag.register('centerquote', centerQuote, true); 18 | hexo.extend.tag.register('cq', centerQuote, true); 19 | 20 | const groupPicture = require('./group-pictures')(hexo); 21 | 22 | hexo.extend.tag.register('grouppicture', groupPicture, true); 23 | hexo.extend.tag.register('gp', groupPicture, true); 24 | 25 | const postLabel = require('./label')(hexo); 26 | 27 | hexo.extend.tag.register('label', postLabel); 28 | 29 | const linkGrid = require('./link-grid'); 30 | 31 | hexo.extend.tag.register('linkgrid', linkGrid, true); 32 | hexo.extend.tag.register('lg', linkGrid, true); 33 | 34 | const mermaid = require('./mermaid'); 35 | 36 | hexo.extend.tag.register('mermaid', mermaid, true); 37 | 38 | const wavedrom = require('./wavedrom'); 39 | 40 | hexo.extend.tag.register('wavedrom', wavedrom, true); 41 | 42 | const postNote = require('./note')(hexo); 43 | 44 | hexo.extend.tag.register('note', postNote, true); 45 | hexo.extend.tag.register('subnote', postNote, true); 46 | 47 | const pdf = require('./pdf')(hexo); 48 | 49 | hexo.extend.tag.register('pdf', pdf); 50 | 51 | const postTabs = require('./tabs')(hexo); 52 | 53 | hexo.extend.tag.register('tabs', postTabs, true); 54 | hexo.extend.tag.register('subtabs', postTabs, true); 55 | hexo.extend.tag.register('subsubtabs', postTabs, true); 56 | 57 | const postVideo = require('./video'); 58 | 59 | hexo.extend.tag.register('video', postVideo); 60 | -------------------------------------------------------------------------------- /scripts/tags/label.js: -------------------------------------------------------------------------------- 1 | /** 2 | * label.js | https://theme-next.js.org/docs/tag-plugins/label 3 | */ 4 | 5 | 'use strict'; 6 | 7 | module.exports = ctx => function(args) { 8 | const [classes = 'default', text = ''] = args.join(' ').split('@'); 9 | 10 | if (!text) ctx.log.warn('Label text must be defined!'); 11 | 12 | return `${text}`; 13 | }; 14 | -------------------------------------------------------------------------------- /scripts/tags/link-grid.js: -------------------------------------------------------------------------------- 1 | /** 2 | * link-grid.js | https://theme-next.js.org/docs/tag-plugins/link-grid 3 | */ 4 | 5 | 'use strict'; 6 | 7 | module.exports = function([image = '/images/avatar.gif', delimiter = '|', comment = '%'], content) { 8 | const links = content.split('\n').filter(line => line.trim() !== '').map(line => { 9 | const item = line.split(delimiter).map(arg => arg.trim()); 10 | if (item[0][0] === comment) return ''; 11 | const imageSource = item[3] || image; 12 | const hasExtension = /\.[^/]+$/.test(imageSource); 13 | return ``; 18 | }); 19 | return ``; 20 | }; 21 | -------------------------------------------------------------------------------- /scripts/tags/mermaid.js: -------------------------------------------------------------------------------- 1 | /** 2 | * mermaid.js | https://theme-next.js.org/docs/tag-plugins/mermaid 3 | */ 4 | 5 | 'use strict'; 6 | 7 | const { escapeHTML } = require('hexo-util'); 8 | 9 | module.exports = function(args, content) { 10 | // Support mermaid inside backtick code block 11 | // Keep the same HTML structure 12 | // Fix issue #347 #797 13 | return `
14 | 
15 | ${args.join(' ')}
16 | ${escapeHTML(content)}
17 | 
18 | 
`; 19 | }; 20 | -------------------------------------------------------------------------------- /scripts/tags/note.js: -------------------------------------------------------------------------------- 1 | /** 2 | * note.js | https://theme-next.js.org/docs/tag-plugins/note 3 | */ 4 | 5 | 'use strict'; 6 | 7 | module.exports = ctx => function(args, content) { 8 | const keywords = ['default', 'primary', 'info', 'success', 'warning', 'danger', 'no-icon']; 9 | const className = []; 10 | for (let i = 0; i < 2; i++) { 11 | if (keywords.includes(args[0])) { 12 | className.push(args.shift()); 13 | } else { 14 | break; 15 | } 16 | } 17 | 18 | content = ctx.render.renderSync({ text: content, engine: 'markdown' }); 19 | if (args.length === 0) { 20 | return `
${content}
`; 21 | } 22 | return `
${ctx.render.renderSync({ text: args.join(' '), engine: 'markdown' })} 23 | ${content} 24 |
`; 25 | }; 26 | -------------------------------------------------------------------------------- /scripts/tags/pdf.js: -------------------------------------------------------------------------------- 1 | /** 2 | * pdf.js | https://theme-next.js.org/docs/tag-plugins/pdf 3 | */ 4 | 5 | 'use strict'; 6 | 7 | module.exports = ctx => function(args) { 8 | const theme = ctx.theme.config; 9 | return `
`; 10 | }; 11 | -------------------------------------------------------------------------------- /scripts/tags/tabs.js: -------------------------------------------------------------------------------- 1 | /** 2 | * tabs.js | https://theme-next.js.org/docs/tag-plugins/tabs 3 | */ 4 | 5 | 'use strict'; 6 | 7 | module.exports = ctx => function(args, content = '') { 8 | const tabBlock = /\n([\w\W\s\S]*?)/g; 9 | 10 | args = args.join(' ').split(','); 11 | const tabName = args[0]; 12 | const tabActive = Number(args[1]) || 0; 13 | 14 | let tabId = 0; 15 | let tabNav = ''; 16 | let tabContent = ''; 17 | 18 | if (!tabName) ctx.log.warn('Tabs block must have unique name!'); 19 | const matches = content.matchAll(tabBlock); 20 | 21 | for (const match of matches) { 22 | let [caption = '', icon = ''] = match[1].split('@'); 23 | let postContent = match[2]; 24 | 25 | postContent = ctx.render.renderSync({ text: postContent, engine: 'markdown' }).trim(); 26 | 27 | const abbr = tabName + ' ' + ++tabId; 28 | const href = abbr.toLowerCase().split(' ').join('-'); 29 | 30 | icon = icon.trim(); 31 | if (icon.length > 0) { 32 | if (!icon.startsWith('fa')) icon = 'fa fa-' + icon; 33 | icon = ``; 34 | } 35 | 36 | caption = icon + caption.trim(); 37 | 38 | const isActive = (tabActive > 0 && tabActive === tabId) || (tabActive === 0 && tabId === 1) ? ' active' : ''; 39 | tabNav += `
  • ${caption || abbr}
  • `; 40 | tabContent += `
    ${postContent}
    `; 41 | } 42 | 43 | tabNav = ``; 44 | tabContent = `
    ${tabContent}
    `; 45 | 46 | return `
    ${tabNav + tabContent}
    `; 47 | }; 48 | -------------------------------------------------------------------------------- /scripts/tags/video.js: -------------------------------------------------------------------------------- 1 | /** 2 | * video.js | https://theme-next.js.org/docs/tag-plugins/ 3 | */ 4 | 5 | 'use strict'; 6 | 7 | module.exports = function(args) { 8 | return ``; 9 | }; 10 | -------------------------------------------------------------------------------- /scripts/tags/wavedrom.js: -------------------------------------------------------------------------------- 1 | /** 2 | * wavedrom.js | https://theme-next.js.org/docs/tag-plugins/wavedrom 3 | */ 4 | 5 | 'use strict'; 6 | 7 | module.exports = function(args, content) { 8 | return `
    `; 11 | }; 12 | -------------------------------------------------------------------------------- /source/css/_common/components/back-to-top.styl: -------------------------------------------------------------------------------- 1 | if (hexo-config('back2top.enable')) { 2 | .back-to-top { 3 | font-size: $b2t-font-size; 4 | 5 | span { 6 | margin-right: 8px; 7 | if (not hexo-config('back2top.scrollpercent')) { 8 | display: none; 9 | } 10 | } 11 | 12 | .fa { 13 | text-align: center; 14 | width: $sidebar-toggle-size; 15 | } 16 | 17 | if (hexo-config('back2top.sidebar')) { 18 | margin: 20px - $sidebar-offset -10px -20px; 19 | // FIXME: opacity override by motion 20 | opacity: 0; 21 | transition: opacity $transition-ease; 22 | 23 | &.back-to-top-on { 24 | cursor: pointer; 25 | opacity: $b2t-opacity; 26 | 27 | &:hover { 28 | opacity: $b2t-opacity-hover; 29 | } 30 | } 31 | } else { 32 | align-items: center; 33 | bottom: $b2t-position-bottom; 34 | color: $b2t-color; 35 | display: flex; 36 | height: $sidebar-toggle-size; 37 | transition: $transition-ease; 38 | // Override in Pisces 39 | transition-property: bottom; 40 | sidebar-toggle(); 41 | 42 | &:hover { 43 | color: $sidebar-highlight; 44 | } 45 | 46 | &.back-to-top-on { 47 | bottom: $b2t-position-bottom-on; 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /source/css/_common/components/index.styl: -------------------------------------------------------------------------------- 1 | @import 'back-to-top'; 2 | @import 'reading-progress'; 3 | 4 | @import 'post'; 5 | @import 'pages'; 6 | @import 'third-party'; 7 | -------------------------------------------------------------------------------- /source/css/_common/components/pages/breadcrumb.styl: -------------------------------------------------------------------------------- 1 | ul.breadcrumb { 2 | font-size: $font-size-smallest; 3 | list-style: none; 4 | margin: 1em 0; 5 | padding: 0 2em; 6 | text-align: center; 7 | 8 | li { 9 | display: inline; 10 | } 11 | 12 | li:not(:first-child)::before { 13 | content: '/\00a0'; 14 | font-weight: normal; 15 | padding: .5em; 16 | } 17 | 18 | li:last-child { 19 | font-weight: bold; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /source/css/_common/components/pages/categories.styl: -------------------------------------------------------------------------------- 1 | .category-all-page { 2 | .category-all-title { 3 | text-align: center; 4 | } 5 | 6 | .category-all { 7 | margin-top: 20px; 8 | } 9 | 10 | .category-list { 11 | list-style: none; 12 | margin: 0; 13 | padding: 0; 14 | } 15 | 16 | .category-list-item { 17 | margin: 5px 10px; 18 | } 19 | 20 | .category-list-count { 21 | font-size: $font-size-smallest; 22 | badge(); 23 | } 24 | 25 | .category-list-child { 26 | padding-left: 10px; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /source/css/_common/components/pages/index.styl: -------------------------------------------------------------------------------- 1 | // Page specific styles 2 | @import 'categories'; 3 | @import 'schedule'; 4 | @import 'breadcrumb'; 5 | @import 'tag-cloud'; 6 | -------------------------------------------------------------------------------- /source/css/_common/components/pages/tag-cloud.styl: -------------------------------------------------------------------------------- 1 | .tag-cloud { 2 | text-align: center; 3 | 4 | a { 5 | display: inline-block; 6 | margin: 10px; 7 | } 8 | } 9 | 10 | for $tag-cloud in (0 .. 10) { 11 | $tag-cloud-color = mix($tag-cloud-end, $tag-cloud-start, $tag-cloud * 10); 12 | .tag-cloud-{$tag-cloud} { 13 | border-bottom-color: $tag-cloud-color; 14 | color: $tag-cloud-color; 15 | } 16 | } 17 | 18 | if (hexo-config('darkmode')) { 19 | @media (prefers-color-scheme: dark) { 20 | for $tag-cloud in (0 .. 10) { 21 | $tag-cloud-color = mix($tag-cloud-end-dark, $tag-cloud-start-dark, $tag-cloud * 10); 22 | .tag-cloud-{$tag-cloud} { 23 | border-bottom-color: $tag-cloud-color; 24 | color: $tag-cloud-color; 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /source/css/_common/components/post/index.styl: -------------------------------------------------------------------------------- 1 | .rtl { 2 | &.post-body { 3 | p, a, h1, h2, h3, h4, h5, h6, li, ul, ol { 4 | direction: rtl; 5 | font-family: UKIJ Ekran; 6 | } 7 | } 8 | 9 | &.post-title { 10 | font-family: UKIJ Ekran; 11 | } 12 | } 13 | 14 | .post-button { 15 | margin-top: 40px; 16 | text-align: $scheme-text-align; 17 | } 18 | 19 | .use-motion { 20 | if (hexo-config('motion.transition.post_block')) { 21 | .post-block, .pagination, .comments { 22 | visibility: hidden; 23 | } 24 | } 25 | 26 | if (hexo-config('motion.transition.post_header')) { 27 | .post-header { 28 | visibility: hidden; 29 | } 30 | } 31 | 32 | if (hexo-config('motion.transition.post_body')) { 33 | .post-body { 34 | visibility: hidden; 35 | } 36 | } 37 | 38 | if (hexo-config('motion.transition.coll_header')) { 39 | .collection-header { 40 | visibility: hidden; 41 | } 42 | } 43 | } 44 | 45 | @import 'post-collapse'; 46 | @import 'post-body'; 47 | @import 'post-gallery'; 48 | @import 'post-header'; 49 | @import 'post-nav'; 50 | @import 'post-footer'; 51 | @import 'post-widgets'; 52 | @import 'post-reward'; 53 | @import 'post-followme'; 54 | -------------------------------------------------------------------------------- /source/css/_common/components/post/post-body.styl: -------------------------------------------------------------------------------- 1 | .post-body { 2 | font-family: $font-family-posts; 3 | word-wrap(); 4 | 5 | +desktop-large() { 6 | font-size: $font-size-large; 7 | } 8 | 9 | +desktop() { 10 | text-align: unquote(hexo-config('text_align.desktop')); 11 | } 12 | 13 | +tablet-mobile() { 14 | text-align: unquote(hexo-config('text_align.mobile')); 15 | } 16 | 17 | h1, h2, h3, h4, h5, h6 { 18 | // Supported plugins: hexo-renderer-markdown-it hexo-renderer-marked 19 | .header-anchor, .headerlink { 20 | border-bottom-style: none; 21 | color: inherit; 22 | float: right; 23 | font-size: $font-size-small; 24 | margin-left: 10px; 25 | opacity: 0; 26 | 27 | &::before { 28 | font-family-icons('\f0c1'); 29 | } 30 | } 31 | 32 | &:hover { 33 | .header-anchor, .headerlink { 34 | opacity: .5; 35 | 36 | &:hover { 37 | opacity: 1; 38 | } 39 | } 40 | } 41 | } 42 | 43 | .exturl .fa { 44 | font-size: $font-size-small; 45 | margin-left: 4px; 46 | } 47 | 48 | // https://github.com/hexojs/hexo-renderer-marked/pull/264 49 | // https://github.com/next-theme/hexo-next-exif 50 | figure:not(.highlight) figcaption { 51 | color: $grey-dark; 52 | font-size: $font-size-small; 53 | font-weight: bold; 54 | line-height: 1; 55 | margin: -15px auto 15px; 56 | text-align: center; 57 | } 58 | 59 | iframe, img, video, embed { 60 | margin-bottom: 20px; 61 | } 62 | 63 | .video-container { 64 | height: 0; 65 | margin-bottom: 20px; 66 | overflow: hidden; 67 | padding-top: 75%; 68 | position: relative; 69 | width: 100%; 70 | 71 | iframe, object, embed { 72 | height: 100%; 73 | left: 0; 74 | margin: 0; 75 | position: absolute; 76 | top: 0; 77 | width: 100%; 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /source/css/_common/components/post/post-followme.styl: -------------------------------------------------------------------------------- 1 | if (hexo-config('follow_me')) { 2 | .followme { 3 | color: $grey; 4 | padding: 1em 1.5em; 5 | text-align: center; 6 | post-card(); 7 | 8 | .social-list { 9 | flex-wrap(); 10 | 11 | .social-item { 12 | margin: .5em 2em; 13 | position: relative; 14 | 15 | +tablet-mobile() { 16 | margin: .5em .75em; 17 | } 18 | } 19 | 20 | .social-link { 21 | border: 0; 22 | // Make the hit area continious 23 | display: block; 24 | 25 | .icon { 26 | font-size: 1.75em; 27 | } 28 | 29 | .label { 30 | display: block; 31 | font-size: 14px; 32 | } 33 | 34 | &:hover + .social-item-img { 35 | display: block; 36 | } 37 | } 38 | 39 | span.social-link { 40 | color: var(--link-color); 41 | 42 | &:hover { 43 | color: var(--link-hover-color); 44 | } 45 | } 46 | 47 | .social-item-img { 48 | display: none; 49 | left: 50%; 50 | max-width: $post-followme-img-width; 51 | position: absolute; 52 | transform: translate(-50%, 20px); 53 | z-index: 1; 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /source/css/_common/components/post/post-footer.styl: -------------------------------------------------------------------------------- 1 | // Flexbox layout makes it possible to reorder the child 2 | // elements of .post-footer through the `order` CSS property 3 | // Fix issue #16 4 | // To do: use `gap` instead of `margin` 5 | // See https://caniuse.com/flexbox-gap 6 | .post-footer { 7 | flex-column(); 8 | } 9 | 10 | .post-eof { 11 | background: $grey-light; 12 | height: 1px; 13 | margin: $post-eof-margin-top auto $post-eof-margin-bottom; 14 | width: 8%; 15 | 16 | .post-block:last-of-type & { 17 | display: none; 18 | } 19 | } 20 | 21 | if (hexo-config('creative_commons.post')) { 22 | .post-copyright ul { 23 | list-style: none; 24 | overflow: hidden; 25 | padding: .5em 1em; 26 | position: relative; 27 | post-card(); 28 | 29 | &::after { 30 | content: '\f25e'; 31 | font-family: 'Font Awesome 6 Brands'; 32 | font-size: 200px; 33 | opacity: $watermark-opacity; 34 | position: absolute; 35 | right: -50px; 36 | top: -150px; 37 | } 38 | } 39 | } 40 | 41 | .post-tags { 42 | margin-top: 40px; 43 | text-align: $scheme-text-align; 44 | 45 | a { 46 | display: inline-block; 47 | font-size: $font-size-smaller; 48 | 49 | &:not(:last-child) { 50 | margin-right: 10px; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /source/css/_common/components/post/post-gallery.styl: -------------------------------------------------------------------------------- 1 | .post-gallery { 2 | display: flex; 3 | min-height: 200px; 4 | 5 | .post-gallery-image { 6 | flex: 1; 7 | 8 | &:not(:first-child) { 9 | clip-path: polygon(40px 0, 100% 0, 100% 100%, 0 100%); 10 | margin-left: -20px; 11 | } 12 | 13 | &:not(:last-child) { 14 | margin-right: -20px; 15 | } 16 | 17 | img { 18 | height: 100%; 19 | object-fit: cover; 20 | // Override darkmode image opacity. 21 | opacity: 1; 22 | width: 100%; 23 | } 24 | } 25 | } 26 | 27 | .posts-expand .post-gallery { 28 | margin-bottom: 60px; 29 | } 30 | 31 | .posts-collapse .post-gallery { 32 | margin: 15px 0; 33 | } 34 | -------------------------------------------------------------------------------- /source/css/_common/components/post/post-nav.styl: -------------------------------------------------------------------------------- 1 | .post-nav { 2 | border-top: 1px solid $gainsboro; 3 | display: flex; 4 | gap: 30px; 5 | justify-content: space-between; 6 | margin-top: 1em; 7 | padding: 10px 5px 0; 8 | } 9 | 10 | .post-nav-item { 11 | flex: 1; 12 | 13 | a { 14 | border-bottom: 0; 15 | display: block; 16 | font-size: $font-size-small; 17 | line-height: 1.6; 18 | 19 | &:active { 20 | top: 2px; 21 | } 22 | } 23 | 24 | .fa { 25 | font-size: $font-size-smallest; 26 | } 27 | 28 | &:first-child { 29 | .fa { 30 | margin-right: 5px; 31 | } 32 | } 33 | 34 | &:last-child { 35 | text-align: right; 36 | 37 | .fa { 38 | margin-left: 5px; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /source/css/_common/components/post/post-reward.styl: -------------------------------------------------------------------------------- 1 | .reward-container { 2 | margin: $post-card-margin; 3 | padding: 1em 0; 4 | text-align: center; 5 | 6 | button { 7 | button($sidebar-highlight); 8 | border: 2px solid $sidebar-highlight; 9 | border-radius: 2px; 10 | outline: 0; 11 | vertical-align: text-top; 12 | } 13 | } 14 | 15 | .post-reward { 16 | display: none; 17 | padding-top: 20px; 18 | 19 | &.active { 20 | display: block; 21 | } 22 | 23 | div { 24 | display: inline-block; 25 | 26 | span { 27 | display: block; 28 | } 29 | 30 | if (hexo-config('reward_settings.animation')) { 31 | &:hover span { 32 | animation: next-roll .1s infinite linear; 33 | // The animation may affect :hover of img in dark mode 34 | pointer-events: none; 35 | } 36 | } 37 | } 38 | 39 | img { 40 | display: inline-block; 41 | margin: .8em 2em 0; 42 | max-width: 100%; 43 | width: $post-reward-img-width; 44 | } 45 | } 46 | 47 | @keyframes next-roll { 48 | from { 49 | transform: rotateZ(30deg); 50 | } 51 | 52 | to { 53 | transform: rotateZ(-30deg); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /source/css/_common/components/post/post-widgets.styl: -------------------------------------------------------------------------------- 1 | .social-like { 2 | border-top: 1px solid $gainsboro; 3 | font-size: $font-size-small; 4 | margin-top: 1em; 5 | padding-top: 1em; 6 | flex-wrap(); 7 | 8 | a { 9 | border-bottom: none; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /source/css/_common/components/reading-progress.styl: -------------------------------------------------------------------------------- 1 | if (hexo-config('reading_progress.enable')) { 2 | .reading-progress-bar { 3 | --progress: 0; 4 | background: convert(hexo-config('reading_progress.color')); 5 | height: convert(hexo-config('reading_progress.height')); 6 | position: fixed; 7 | z-index: $zindex-5; 8 | 9 | if (hexo-config('reading_progress.reversed')) { 10 | width: calc(100% - var(--progress)); 11 | } else { 12 | width: var(--progress); 13 | } 14 | 15 | if (hexo-config('reading_progress.start_at') == 'right') { 16 | right: 0; 17 | } else { 18 | left: 0; 19 | } 20 | 21 | if (hexo-config('reading_progress.position') == 'bottom') { 22 | bottom: 0; 23 | } else { 24 | top: 0; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /source/css/_common/components/third-party/disqusjs.styl: -------------------------------------------------------------------------------- 1 | if (hexo-config('disqusjs.enable') and hexo-config('darkmode')) { 2 | @media (prefers-color-scheme:dark) { 3 | html #dsqjs a { 4 | color: var(--link-color); 5 | } 6 | 7 | html #dsqjs a:focus,html #dsqjs a:hover { 8 | color: var(--link-hover-color); 9 | } 10 | 11 | html #dsqjs .dsqjs-nav,html #dsqjs footer { 12 | border-color: var(--card-bg-color); 13 | } 14 | 15 | html #dsqjs .dsqjs-load-more,html #dsqjs .dsqjs-load-more:hover,html #dsqjs .dsqjs-nav-tab,html #dsqjs .dsqjs-no-comment,html #dsqjs .dsqjs-post-content { 16 | color: var(--text-color); 17 | } 18 | 19 | html #dsqjs .dsqjs-order-label { 20 | background-color: #3e4b5e; 21 | } 22 | 23 | html #dsqjs .dsqjs-order-radio:checked+.dsqjs-order-label { 24 | background-color: var(--content-bg-color); 25 | } 26 | 27 | html #dsqjs .dsqjs-tab-active>span:after { 28 | background-color: #2e9fff!important; 29 | } 30 | 31 | html #dsqjs .dsqjs-footer,html #dsqjs .dsqjs-meta { 32 | color: var(--text-color); 33 | } 34 | 35 | html #dsqjs .dsqjs-post-body blockquote { 36 | border-color: var(--content-bg-color); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /source/css/_common/components/third-party/gitalk.styl: -------------------------------------------------------------------------------- 1 | if (hexo-config('gitalk.enable')) { 2 | .gt-header a, .gt-comments a, .gt-popup a { 3 | border-bottom: 0; 4 | } 5 | 6 | .gt-container .gt-popup .gt-action.is--active::before { 7 | top: .7em; 8 | } 9 | 10 | if (hexo-config('darkmode')) { 11 | @media (prefers-color-scheme: dark) { 12 | .gt-container .gt-header-textarea { 13 | background-color: var(--card-bg-color) !important; 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /source/css/_common/components/third-party/index.styl: -------------------------------------------------------------------------------- 1 | @import 'disqusjs'; 2 | @import 'gitalk'; 3 | @import 'utterances'; 4 | @import 'search'; 5 | @import 'math'; 6 | 7 | .use-motion .animated { 8 | // Fix issue #48 #55 9 | animation-fill-mode: none; 10 | // Fix issue #46 .animated in .sidebar 11 | visibility: inherit; 12 | } 13 | 14 | .use-motion .sidebar .animated { 15 | animation-fill-mode: both; 16 | } 17 | -------------------------------------------------------------------------------- /source/css/_common/components/third-party/math.styl: -------------------------------------------------------------------------------- 1 | if (hexo-config('math.mathjax.enable')) { 2 | mjx-container[jax='CHTML'][display='true'], .has-jax { 3 | overflow: auto hidden; 4 | } 5 | 6 | mjx-container[display='true'] + br { 7 | display: none; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /source/css/_common/components/third-party/utterances.styl: -------------------------------------------------------------------------------- 1 | if (hexo-config('utterances.enable')) { 2 | .utterances { 3 | max-width: unset; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /source/css/_common/outline/header/bookmark.styl: -------------------------------------------------------------------------------- 1 | if (hexo-config('bookmark.enable')) { 2 | .book-mark-link { 3 | border-bottom: 0; 4 | position: fixed; 5 | top: -10px; 6 | transition: top .3s; 7 | sidebar-toggle-position(true); 8 | 9 | +tablet-mobile() { 10 | display: none; 11 | } 12 | 13 | &::before { 14 | color: convert(hexo-config('bookmark.color')); 15 | font-size: 32px; 16 | line-height: 1; 17 | font-family-icons('\f02e'); 18 | } 19 | } 20 | 21 | .book-mark-link:hover, .book-mark-link-fixed { 22 | top: -2px; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /source/css/_common/outline/header/github-banner.styl: -------------------------------------------------------------------------------- 1 | if (hexo-config('github_banner.enable')) { 2 | @keyframes octocat-wave { 3 | 0%, 100% { 4 | transform: rotate(0); 5 | } 6 | 7 | 20%, 60% { 8 | transform: rotate(-25deg); 9 | } 10 | 11 | 40%, 80% { 12 | transform: rotate(10deg); 13 | } 14 | } 15 | 16 | .github-corner { 17 | :hover .octo-arm { 18 | animation: octocat-wave 560ms ease-in-out; 19 | } 20 | 21 | svg { 22 | color: white; 23 | fill: var(--theme-color); 24 | position: absolute; 25 | right: 0; 26 | top: 0; 27 | z-index: $zindex-0; 28 | } 29 | 30 | +tablet-mobile() { 31 | if (hexo-config('local_search.enable') or hexo-config('algolia_search.enable')) { 32 | display: none; 33 | } 34 | 35 | svg { 36 | if (($scheme == 'Pisces') or ($scheme == 'Gemini')) { 37 | color: var(--theme-color); 38 | fill: white; 39 | } 40 | } 41 | 42 | .github-corner:hover .octo-arm { 43 | animation: none; 44 | } 45 | 46 | .github-corner .octo-arm { 47 | animation: octocat-wave 560ms ease-in-out; 48 | } 49 | } 50 | 51 | if ($scheme == 'Mist') { 52 | +mobile() { 53 | svg { 54 | top: inherit; 55 | } 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /source/css/_common/outline/header/index.styl: -------------------------------------------------------------------------------- 1 | // Header Section 2 | // -------------------------------------------------- 3 | .headband { 4 | background: $headband-bg; 5 | height: $headband-height; 6 | 7 | +tablet-mobile() { 8 | display: none; 9 | } 10 | } 11 | 12 | .site-brand-container { 13 | display: flex; 14 | flex-shrink: 0; 15 | padding: 0 10px; 16 | } 17 | 18 | .use-motion { 19 | .column, .site-brand-container .toggle { 20 | opacity: 0; 21 | } 22 | } 23 | 24 | @import 'site-meta'; 25 | @import 'site-nav'; 26 | @import 'menu'; 27 | 28 | @import 'bookmark'; 29 | @import 'github-banner'; 30 | -------------------------------------------------------------------------------- /source/css/_common/outline/header/menu.styl: -------------------------------------------------------------------------------- 1 | // Menu 2 | // -------------------------------------------------- 3 | .menu { 4 | margin: 0; 5 | padding: 1em 0; 6 | text-align: center; 7 | } 8 | 9 | .menu-item { 10 | display: inline-block; 11 | list-style: none; 12 | margin: 0 10px; 13 | 14 | +mobile() { 15 | display: block; 16 | margin-top: 10px; 17 | 18 | &.menu-item-search { 19 | display: none; 20 | } 21 | } 22 | 23 | a { 24 | border-bottom: 0; 25 | display: block; 26 | font-size: $font-size-smaller; 27 | transition: border-color $transition-ease; 28 | 29 | &:hover, &.menu-item-active { 30 | background: var(--menu-item-bg-color); 31 | } 32 | } 33 | 34 | i[class^='fa'] { 35 | margin-right: 8px; 36 | } 37 | 38 | .badge { 39 | badge(); 40 | } 41 | } 42 | 43 | if (hexo-config('motion.transition.menu_item')) { 44 | .use-motion .menu-item { 45 | visibility: hidden; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /source/css/_common/outline/header/site-meta.styl: -------------------------------------------------------------------------------- 1 | .site-meta { 2 | flex-grow: 1; 3 | text-align: center; 4 | 5 | +mobile() { 6 | text-align: center; 7 | } 8 | } 9 | 10 | .custom-logo-image { 11 | margin-top: 20px; 12 | 13 | +tablet-mobile() { 14 | display: none; 15 | } 16 | } 17 | 18 | .brand { 19 | border-bottom: 0; 20 | color: var(--brand-color); 21 | display: inline-block; 22 | padding: $brand-padding; 23 | 24 | &:hover { 25 | color: var(--brand-hover-color); 26 | } 27 | } 28 | 29 | .site-title { 30 | font-family: $font-family-logo; 31 | font-size: $font-size-title; 32 | font-weight: normal; 33 | line-height: 1.5; 34 | margin: 0; 35 | } 36 | 37 | .site-subtitle { 38 | color: $subtitle-color; 39 | font-size: $font-size-subtitle; 40 | margin: $site-subtitle-margin; 41 | } 42 | 43 | .use-motion { 44 | .site-title, .site-subtitle, .custom-logo-image { 45 | opacity: 0; 46 | position: relative; 47 | top: -10px; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /source/css/_common/outline/header/site-nav.styl: -------------------------------------------------------------------------------- 1 | .site-nav-toggle, .site-nav-right { 2 | display: none; 3 | 4 | +mobile() { 5 | flex-column(); 6 | } 7 | 8 | .toggle { 9 | color: var(--text-color); 10 | padding: 10px; 11 | width: 22px; 12 | 13 | .toggle-line { 14 | background: var(--text-color); 15 | border-radius: 1px; 16 | } 17 | } 18 | } 19 | 20 | .site-nav { 21 | +mobile() { 22 | site-nav-hide-by-default(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /source/css/_common/outline/index.styl: -------------------------------------------------------------------------------- 1 | @import 'header'; 2 | @import 'sidebar'; 3 | @import 'footer'; 4 | 5 | @import 'mobile'; 6 | -------------------------------------------------------------------------------- /source/css/_common/outline/mobile.styl: -------------------------------------------------------------------------------- 1 | if (hexo-config('mobile_layout_economy')) { 2 | +mobile-small() { 3 | // For Pisces & Gemini schemes only wider width (remove main blocks in Gemini). 4 | .main-inner { 5 | padding: initial !important; 6 | } 7 | 8 | // For all schemes wider width. 9 | .posts-expand { 10 | .post-header { 11 | margin-bottom: 10px !important; 12 | } 13 | } 14 | 15 | .post-block { 16 | margin-top: initial !important; 17 | // Inside posts blocks content padding (default 40px). 18 | padding: $content-mobile-padding 18px $content-mobile-padding !important; 19 | } 20 | 21 | .post-body { 22 | // For headers narrow width. 23 | h1, h2, h3, h4, h5, h6 { 24 | margin: 20px 0 8px; 25 | } 26 | 27 | // Rewrite paddings & margins inside tags. 28 | .note, .tabs .tab-content .tab-pane { 29 | h1, h2, h3, h4, h5, h6 { 30 | margin: 0 5px; 31 | } 32 | } 33 | 34 | // For paragraphs narrow width. 35 | > p { 36 | margin: 0 0 10px; 37 | } 38 | 39 | // Rewrite paddings & margins inside tags. 40 | .note > p, .tabs .tab-content .tab-pane > p { 41 | padding: 0 5px; 42 | } 43 | 44 | img, video { 45 | margin-bottom: 10px !important; 46 | } 47 | 48 | // Fix issue #641 49 | figure:not(.highlight) figcaption { 50 | margin: -5px auto 15px !important; 51 | } 52 | 53 | .note { 54 | margin-bottom: 10px !important; 55 | padding: 10px !important; 56 | 57 | if (hexo-config('note.icons')) { 58 | &:not(.no-icon) { 59 | padding-left: 35px !important; 60 | } 61 | } 62 | } 63 | 64 | .tabs .tab-content .tab-pane { 65 | padding: 10px 10px 0 !important; 66 | } 67 | } 68 | 69 | .post-eof { 70 | margin: 40px auto 20px !important; 71 | } 72 | 73 | .pagination { 74 | margin-top: 40px; 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /source/css/_common/outline/sidebar/related-posts.styl: -------------------------------------------------------------------------------- 1 | if (hexo-config('related_posts.enable')) { 2 | .sidebar-post-related { 3 | font-size: $font-size-smaller; 4 | padding: $sidebar-padding 0 0 0; 5 | } 6 | 7 | .popular-posts { 8 | margin: 0; 9 | padding: 1em 0; 10 | text-align: left; 11 | 12 | .popular-posts-item { 13 | display: block; 14 | 15 | .popular-posts-link { 16 | border-bottom: 0; 17 | display: block; 18 | padding: 5px 20px; 19 | transition: background .2s ease-in-out; 20 | 21 | &:hover { 22 | background: var(--menu-item-bg-color); 23 | } 24 | } 25 | 26 | .popular-posts-time { 27 | color: $site-state-item-name-color; 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /source/css/_common/outline/sidebar/sidebar-author-links.styl: -------------------------------------------------------------------------------- 1 | .links-of-author { 2 | a { 3 | font-size: $font-size-smaller; 4 | } 5 | 6 | if (not hexo-config('social_icons.icons_only')) { 7 | i[class^='fa'] { 8 | margin-right: 2px; 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /source/css/_common/outline/sidebar/sidebar-author.styl: -------------------------------------------------------------------------------- 1 | .site-author-image { 2 | border: $site-author-image-border-width solid $site-author-image-border-color; 3 | max-width: $site-author-image-width; 4 | padding: 2px; 5 | 6 | if (hexo-config('avatar.rounded')) { 7 | border-radius: 50%; 8 | } 9 | 10 | if (hexo-config('avatar.rotated')) { 11 | transition: transform 1s ease-out; 12 | 13 | &:hover { 14 | transform: rotateZ(360deg); 15 | } 16 | } 17 | } 18 | 19 | .site-author-name { 20 | color: $site-author-name-color; 21 | font-weight: $site-author-name-weight; 22 | margin: $site-author-name-margin; 23 | } 24 | 25 | .site-description { 26 | color: $site-description-color; 27 | font-size: $site-description-font-size; 28 | margin-top: $site-description-margin-top; 29 | } 30 | -------------------------------------------------------------------------------- /source/css/_common/outline/sidebar/sidebar-blogroll.styl: -------------------------------------------------------------------------------- 1 | .links-of-blogroll { 2 | font-size: $font-size-smaller; 3 | } 4 | 5 | .links-of-blogroll-title { 6 | font-size: $font-size-small; 7 | font-weight: 600; 8 | } 9 | 10 | .links-of-blogroll-list { 11 | list-style: none; 12 | gap: 5px; 13 | margin: 5px 0 0; 14 | padding: 0; 15 | flex-wrap(); 16 | 17 | if (hexo-config('links_settings.layout') == 'block') { 18 | flex-direction: column; 19 | } 20 | } 21 | 22 | .links-of-blogroll-item { 23 | max-width: 100%; 24 | 25 | a { 26 | sidebar-inline-links-item(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /source/css/_common/outline/sidebar/sidebar-button.styl: -------------------------------------------------------------------------------- 1 | .sidebar .sidebar-button { 2 | &:not(:first-child) { 3 | margin-top: 15px; 4 | } 5 | 6 | button { 7 | button($orange); 8 | border: 1px solid $orange; 9 | border-radius: 4px; 10 | 11 | i[class^='fa'] { 12 | margin-right: 5px; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /source/css/_common/outline/sidebar/sidebar-copyright.styl: -------------------------------------------------------------------------------- 1 | .cc-license { 2 | .cc-opacity { 3 | border-bottom: 0; 4 | opacity: .7; 5 | 6 | &:hover { 7 | opacity: .9; 8 | } 9 | } 10 | 11 | img { 12 | display: inline-block; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /source/css/_common/outline/sidebar/sidebar-toc.styl: -------------------------------------------------------------------------------- 1 | if (hexo-config('toc.enable')) { 2 | .post-toc { 3 | font-size: $font-size-small; 4 | 5 | ol { 6 | list-style: none; 7 | margin: 0; 8 | padding: 0 2px 0 10px; 9 | text-align: left; 10 | 11 | > :last-child { 12 | margin-bottom: 5px; 13 | } 14 | 15 | > ol { 16 | padding-left: 0; 17 | } 18 | 19 | a { 20 | transition: all $transition-ease; 21 | } 22 | } 23 | 24 | .nav-item { 25 | line-height: 1.8; 26 | overflow: hidden; 27 | text-overflow: ellipsis; 28 | 29 | if (not hexo-config('toc.wrap')) { 30 | white-space: nowrap; 31 | } 32 | } 33 | 34 | .nav { 35 | if (not hexo-config('toc.expand_all')) { 36 | .nav-child { 37 | --height: 0; 38 | height: 0; 39 | opacity: 0; 40 | overflow: hidden; 41 | transition-property: height, opacity, visibility; 42 | transition: $transition-ease; 43 | visibility: hidden; 44 | } 45 | 46 | .active > .nav-child { 47 | height: var(--height, auto); 48 | opacity: 1; 49 | visibility: unset; 50 | } 51 | } 52 | 53 | .active > a { 54 | border-bottom-color: $sidebar-highlight; 55 | color: $sidebar-highlight; 56 | } 57 | 58 | .active-current > a { 59 | color: $sidebar-highlight; 60 | 61 | &:hover { 62 | color: $sidebar-highlight; 63 | } 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /source/css/_common/outline/sidebar/sidebar-toggle.styl: -------------------------------------------------------------------------------- 1 | .sidebar-toggle { 2 | bottom: $b2t-position-bottom-on + $sidebar-toggle-size + 5px; 3 | height: $sidebar-toggle-inner-size; 4 | padding: $sidebar-toggle-padding; 5 | width: $sidebar-toggle-inner-size; 6 | sidebar-toggle(); 7 | } 8 | 9 | .sidebar-toggle:hover .toggle-line { 10 | background: $sidebar-highlight; 11 | } 12 | 13 | @media (any-hover: hover) { 14 | body:not(.sidebar-active) .sidebar-toggle:hover { 15 | toggle-arrow($sidebar-toggle-alignment); 16 | } 17 | } 18 | 19 | .sidebar-active .sidebar-toggle { 20 | toggle-close($sidebar-toggle-alignment); 21 | } 22 | -------------------------------------------------------------------------------- /source/css/_common/outline/sidebar/site-state.styl: -------------------------------------------------------------------------------- 1 | if (hexo-config('site_state')) { 2 | .site-state { 3 | flex-wrap(); 4 | line-height: 1.4; 5 | } 6 | 7 | .site-state-item { 8 | // Fix issue #103 9 | // The click area of the link becomes smaller 10 | padding: 0 15px; 11 | 12 | a { 13 | border-bottom: 0; 14 | display: block; 15 | } 16 | } 17 | 18 | .site-state-item-count { 19 | display: block; 20 | font-size: $site-state-item-count-font-size; 21 | font-weight: 600; 22 | } 23 | 24 | .site-state-item-name { 25 | color: $site-state-item-name-color; 26 | font-size: $site-state-item-name-font-size; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /source/css/_common/scaffolding/base.styl: -------------------------------------------------------------------------------- 1 | ::selection { 2 | background: var(--selection-bg); 3 | color: var(--selection-color); 4 | } 5 | 6 | html, body { 7 | height: 100%; 8 | } 9 | 10 | body { 11 | background: var(--body-bg-color); 12 | box-sizing: border-box; 13 | color: var(--text-color); 14 | font-family: $font-family-base; 15 | font-size: $font-size-base; 16 | line-height: $line-height-base; 17 | min-height: 100%; 18 | position: relative; 19 | transition: padding $transition-ease; 20 | 21 | if (hexo-config('body_scrollbar.overlay')) { 22 | overflow-x: hidden; 23 | @supports (overflow-x: clip) { 24 | overflow-x: clip; 25 | } 26 | width: 100vw; 27 | } 28 | 29 | if (hexo-config('body_scrollbar.stable')) { 30 | // https://caniuse.com/mdn-css_properties_scrollbar-gutter 31 | scrollbar-gutter: stable; 32 | } 33 | } 34 | 35 | h1, h2, h3, h4, h5, h6 { 36 | font-family: $font-family-headings; 37 | font-weight: bold; 38 | line-height: 1.5; 39 | margin: 30px 0 15px; 40 | } 41 | 42 | for $headline in (1 .. 6) { 43 | h{$headline} { 44 | font-size: $font-size-headings-base - $font-size-headings-step * $headline; 45 | } 46 | } 47 | 48 | a { 49 | border-bottom: 1px solid $link-decoration-color; 50 | color: var(--link-color); 51 | // For a:not(:any-link) 52 | cursor: pointer; 53 | outline: 0; 54 | text-decoration: none; 55 | word-wrap(); 56 | 57 | &:hover { 58 | border-bottom-color: var(--link-hover-color); 59 | color: var(--link-hover-color); 60 | } 61 | } 62 | 63 | iframe, img, video, embed { 64 | display: block; 65 | margin-left: auto; 66 | margin-right: auto; 67 | max-width: 100%; 68 | } 69 | 70 | hr { 71 | background-image: repeating-linear-gradient(-45deg, $grey-lighter, $grey-lighter 4px, transparent 4px, transparent 8px); 72 | border: 0; 73 | height: 3px; 74 | margin: 40px 0; 75 | } 76 | 77 | blockquote { 78 | border-left: 4px solid $grey-lighter; 79 | color: var(--blockquote-color); 80 | margin: 0; 81 | padding: 0 15px; 82 | 83 | cite::before { 84 | content: '-'; 85 | padding: 0 5px; 86 | } 87 | } 88 | 89 | dt { 90 | font-weight: bold; 91 | } 92 | 93 | dd { 94 | margin: 0; 95 | padding: 0; 96 | } 97 | -------------------------------------------------------------------------------- /source/css/_common/scaffolding/buttons.styl: -------------------------------------------------------------------------------- 1 | .btn { 2 | background: var(--btn-default-bg); 3 | border: 2px solid var(--btn-default-border-color); 4 | border-radius: $btn-default-radius; 5 | color: var(--btn-default-color); 6 | display: inline-block; 7 | font-size: $font-size-small; 8 | line-height: 2; 9 | padding: 0 20px; 10 | transition: background-color $transition-ease; 11 | 12 | &:hover { 13 | background: var(--btn-default-hover-bg); 14 | border-color: var(--btn-default-hover-border-color); 15 | color: var(--btn-default-hover-color); 16 | } 17 | 18 | + .btn { 19 | margin: 0 0 8px 8px; 20 | } 21 | 22 | .fa-fw { 23 | text-align: left; 24 | width: (18em / 14); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /source/css/_common/scaffolding/comments.styl: -------------------------------------------------------------------------------- 1 | .comments { 2 | margin-top: 60px; 3 | overflow: hidden; 4 | } 5 | 6 | .comment-button-group { 7 | display: flex; 8 | flex-wrap: wrap-reverse; 9 | justify-content: center; 10 | margin: 1em 0; 11 | 12 | .comment-button { 13 | margin: .1em .2em; 14 | 15 | &.active { 16 | background: var(--btn-default-hover-bg); 17 | border-color: var(--btn-default-hover-border-color); 18 | color: var(--btn-default-hover-color); 19 | } 20 | } 21 | } 22 | 23 | .comment-position { 24 | display: none; 25 | 26 | &.active { 27 | display: block; 28 | } 29 | } 30 | 31 | .tabs-comment { 32 | margin-top: 4em; 33 | padding-top: 0; 34 | 35 | .comments { 36 | margin-top: 0; 37 | padding-top: 0; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /source/css/_common/scaffolding/highlight/copy-code.styl: -------------------------------------------------------------------------------- 1 | .highlight:hover .copy-btn, .code-container:hover .copy-btn { 2 | opacity: 1; 3 | } 4 | 5 | .code-container { 6 | position: relative; 7 | } 8 | 9 | .code-lang { 10 | font-size: 40px; 11 | line-height: 1; 12 | opacity: $watermark-opacity; 13 | pointer-events: none; 14 | position: absolute; 15 | right: 5px; 16 | } 17 | 18 | .copy-btn { 19 | color: $black-dim; 20 | cursor: pointer; 21 | line-height: 1.6; 22 | opacity: 0; 23 | padding: 2px 6px; 24 | position: absolute; 25 | transition: opacity $transition-ease; 26 | 27 | if (hexo-config('codeblock.copy_button.style') == 'flat') { 28 | background: white; 29 | border: 0; 30 | font-size: $font-size-smaller; 31 | right: 0; 32 | top: 0; 33 | } else if (hexo-config('codeblock.copy_button.style') == 'mac') { 34 | color: var(--highlight-foreground); 35 | font-size: 14px; 36 | right: 0; 37 | top: 2px; 38 | } else { 39 | background-color: $gainsboro; 40 | background-image: linear-gradient(#fcfcfc, $gainsboro); 41 | border: 1px solid #d5d5d5; 42 | border-radius: 3px; 43 | font-size: $font-size-smaller; 44 | right: 4px; 45 | top: 8px; 46 | } 47 | } 48 | 49 | if (hexo-config('codeblock.copy_button.style') == 'mac') { 50 | figure.highlight { 51 | border-radius: 5px; 52 | box-shadow: 0 10px 30px 0 rgba(0, 0, 0, .4); 53 | padding-top: 30px; 54 | 55 | .table-container { 56 | border-radius: 0 0 5px 5px; 57 | } 58 | 59 | &::before { 60 | background: #fc625d; 61 | box-shadow: 20px 0 #fdbc40, 40px 0 #35cd4b; 62 | left: 12px; 63 | margin-top: -20px; 64 | position: absolute; 65 | round-icon(12px); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /source/css/_common/scaffolding/highlight/fold.styl: -------------------------------------------------------------------------------- 1 | .expand-btn { 2 | bottom: 0; 3 | color: var(--highlight-foreground); 4 | cursor: pointer; 5 | display: none; 6 | left: 0; 7 | right: 0; 8 | position: absolute; 9 | text-align: center; 10 | } 11 | 12 | .fold-cover { 13 | background-image: linear-gradient(to top, var(--highlight-background) 0, rgba(0, 0, 0, 0) 100%); 14 | bottom: 0; 15 | display: none; 16 | height: 50px; 17 | left: 0; 18 | right: 0; 19 | position: absolute; 20 | } 21 | 22 | .highlight-fold { 23 | max-height: unit(hexo-config('codeblock.fold.height'), 'px'); 24 | overflow-y: hidden !important; 25 | 26 | .expand-btn, .fold-cover { 27 | display: block; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /source/css/_common/scaffolding/index.styl: -------------------------------------------------------------------------------- 1 | // 2 | // Scaffolding 3 | // ================================================== 4 | @import 'normalize'; 5 | @import 'base'; 6 | @import 'tables'; 7 | @import 'buttons'; 8 | @import 'toggles'; 9 | @import 'highlight'; 10 | @import 'tags'; 11 | @import 'pagination'; 12 | @import 'comments'; 13 | -------------------------------------------------------------------------------- /source/css/_common/scaffolding/pagination.styl: -------------------------------------------------------------------------------- 1 | $page-number-basic { 2 | display: inline-block; 3 | margin: -1px 10px 0; 4 | padding: 0 10px; 5 | 6 | +mobile() { 7 | margin: 0 5px; 8 | } 9 | } 10 | 11 | $page-number-current { 12 | background: $pagination-active-bg; 13 | border-color: $pagination-active-border; 14 | color: $pagination-active-color; 15 | } 16 | 17 | .pagination { 18 | border-top: 1px solid $pagination-border; 19 | margin: 120px 0 0; 20 | text-align: $scheme-text-align; 21 | 22 | .prev, .next, .page-number { 23 | @extend $page-number-basic; 24 | border-bottom: 0; 25 | border-top: 1px solid $pagination-link-border; 26 | transition: border-color $transition-ease; 27 | 28 | &:hover { 29 | border-top-color: $pagination-link-hover-border; 30 | } 31 | } 32 | 33 | +mobile() { 34 | border-top: 0; 35 | 36 | .prev, .next, .page-number { 37 | border-bottom: 1px solid $pagination-link-border; 38 | border-top: 0; 39 | 40 | &:hover { 41 | border-bottom-color: $pagination-link-hover-border; 42 | } 43 | } 44 | } 45 | 46 | .space { 47 | @extend $page-number-basic; 48 | margin: 0; 49 | padding: 0; 50 | } 51 | 52 | .page-number.current { 53 | @extend $page-number-current; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /source/css/_common/scaffolding/tables.styl: -------------------------------------------------------------------------------- 1 | .table-container { 2 | overflow: auto; 3 | } 4 | 5 | table { 6 | border-collapse: collapse; 7 | border-spacing: 0; 8 | font-size: $table-font-size; 9 | margin: 0 0 20px; 10 | width: 100%; 11 | } 12 | 13 | tbody tr { 14 | &:nth-of-type(odd) { 15 | background: var(--table-row-odd-bg-color); 16 | } 17 | 18 | &:hover { 19 | background: var(--table-row-hover-bg-color); 20 | } 21 | } 22 | 23 | caption, th, td { 24 | padding: 8px; 25 | } 26 | 27 | th, td { 28 | border: 1px solid $table-border-color; 29 | border-bottom: 3px solid $table-cell-border-bottom-color; 30 | } 31 | 32 | th { 33 | font-weight: 700; 34 | padding-bottom: 10px; 35 | } 36 | 37 | td { 38 | border-bottom-width: 1px; 39 | } 40 | -------------------------------------------------------------------------------- /source/css/_common/scaffolding/tags/blockquote-center.styl: -------------------------------------------------------------------------------- 1 | // Blockquote with all children centered. 2 | .blockquote-center { 3 | border-left: 0; 4 | margin: 40px 0; 5 | padding: 0; 6 | position: relative; 7 | text-align: center; 8 | 9 | &::before, &::after { 10 | left: 0; 11 | line-height: 1; 12 | opacity: .6; 13 | position: absolute; 14 | width: 100%; 15 | } 16 | 17 | &::before { 18 | border-top: 1px solid $grey-light; 19 | text-align: left; 20 | top: -20px; 21 | font-family-icons('\f10d'); 22 | } 23 | 24 | &::after { 25 | border-bottom: 1px solid $grey-light; 26 | bottom: -20px; 27 | text-align: right; 28 | font-family-icons('\f10e'); 29 | } 30 | 31 | p, div { 32 | text-align: center; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /source/css/_common/scaffolding/tags/group-pictures.styl: -------------------------------------------------------------------------------- 1 | .group-picture { 2 | margin-bottom: 20px; 3 | 4 | .group-picture-row { 5 | display: flex; 6 | gap: 3px; 7 | margin-bottom: 3px; 8 | } 9 | 10 | .group-picture-column { 11 | flex: 1; 12 | 13 | img { 14 | height: 100%; 15 | margin: 0; 16 | object-fit: cover; 17 | width: 100%; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /source/css/_common/scaffolding/tags/index.styl: -------------------------------------------------------------------------------- 1 | @import 'blockquote-center'; 2 | @import 'group-pictures'; 3 | @import 'label'; 4 | @import 'link-grid'; 5 | @import 'mermaid'; 6 | @import 'wavedrom'; 7 | @import 'note'; 8 | @import 'pdf'; 9 | @import 'tabs'; 10 | -------------------------------------------------------------------------------- /source/css/_common/scaffolding/tags/label.styl: -------------------------------------------------------------------------------- 1 | .post-body .label { 2 | color: $text-color; 3 | padding: 0 2px; 4 | 5 | for $type in $note-types { 6 | &.{$type} { 7 | background: $label[$type]; 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /source/css/_common/scaffolding/tags/mermaid.styl: -------------------------------------------------------------------------------- 1 | if (hexo-config('mermaid.enable')) { 2 | .mermaid { 3 | margin-bottom: 20px; 4 | text-align: center; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /source/css/_common/scaffolding/tags/pdf.styl: -------------------------------------------------------------------------------- 1 | if (hexo-config('pdf.enable')) { 2 | .pdfobject-container { 3 | iframe, embed { 4 | height: convert(hexo-config('pdf.height')); 5 | width: 100%; 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /source/css/_common/scaffolding/tags/wavedrom.styl: -------------------------------------------------------------------------------- 1 | if (hexo-config('wavedrom.enable')) { 2 | .wavedrom { 3 | margin-bottom: 20px; 4 | text-align: center; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /source/css/_common/scaffolding/toggles.styl: -------------------------------------------------------------------------------- 1 | .toggle { 2 | line-height: 0; 3 | 4 | .toggle-line { 5 | background: white; 6 | display: block; 7 | height: 2px; 8 | left: 0; 9 | position: relative; 10 | top: 0; 11 | transition: .4s; 12 | transition-property: left, opacity, top, transform, width; 13 | width: 100%; 14 | 15 | &:first-child { 16 | margin-top: 1px; 17 | } 18 | 19 | &:not(:first-child) { 20 | margin-top: 4px; 21 | } 22 | } 23 | } 24 | 25 | .toggle.toggle-arrow { 26 | toggle-arrow($sidebar-toggle-alignment); 27 | } 28 | 29 | .toggle.toggle-close { 30 | toggle-close($sidebar-toggle-alignment); 31 | } 32 | -------------------------------------------------------------------------------- /source/css/_schemes/Mist/_header.styl: -------------------------------------------------------------------------------- 1 | // Header 2 | // -------------------------------------------------- 3 | .column { 4 | background: var(--content-bg-color); 5 | } 6 | 7 | header.header { 8 | align-items: center; 9 | display: flex; 10 | padding: 20px 0; 11 | 12 | +mobile() { 13 | display: block; 14 | padding: 10px 0; 15 | } 16 | } 17 | 18 | .site-meta { 19 | line-height: normal; 20 | 21 | .brand { 22 | +mobile() { 23 | display: block; 24 | } 25 | } 26 | 27 | .site-title { 28 | font-weight: bolder; 29 | } 30 | } 31 | 32 | .logo-line { 33 | background: var(--brand-color); 34 | display: block; 35 | height: 2px; 36 | margin: 0 auto; 37 | width: 75%; 38 | 39 | +mobile() { 40 | display: none; 41 | } 42 | } 43 | 44 | .use-motion { 45 | .logo-line:first-of-type { 46 | transform: scaleX(0); 47 | transform-origin: left; 48 | } 49 | 50 | .logo-line:last-of-type { 51 | transform: scaleX(0); 52 | transform-origin: right; 53 | } 54 | } 55 | 56 | .site-subtitle { 57 | display: none; 58 | } 59 | -------------------------------------------------------------------------------- /source/css/_schemes/Mist/_layout.styl: -------------------------------------------------------------------------------- 1 | // Tags 2 | // -------------------------------------------------- 3 | hr { 4 | height: 2px; 5 | margin: 20px 0; 6 | } 7 | 8 | // Components 9 | // -------------------------------------------------- 10 | .btn { 11 | padding: 0 10px; 12 | } 13 | 14 | .headband { 15 | display: none; 16 | } 17 | 18 | // Pagination 19 | // -------------------------------------------------- 20 | .pagination { 21 | +mobile() { 22 | margin: 80px 0 0; 23 | text-align: center; 24 | } 25 | } 26 | 27 | // Footer 28 | // -------------------------------------------------- 29 | .footer { 30 | background: var(--content-bg-color); 31 | color: var(--text-color); 32 | padding: 10px 0; 33 | } 34 | 35 | .footer-inner { 36 | +mobile() { 37 | text-align: center; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /source/css/_schemes/Mist/_menu.styl: -------------------------------------------------------------------------------- 1 | // Menu 2 | // -------------------------------------------------- 3 | .site-nav { 4 | flex-grow: 1; 5 | 6 | +mobile() { 7 | padding: 0 10px 0; 8 | } 9 | } 10 | 11 | .main-menu { 12 | +mobile() { 13 | padding-top: 10px; 14 | } 15 | } 16 | 17 | .menu { 18 | padding: 0; 19 | 20 | .menu-item { 21 | margin: 0; 22 | 23 | +mobile() { 24 | margin-top: 5px; 25 | } 26 | 27 | a { 28 | border-radius: 2px; 29 | padding: 0 10px; 30 | transition-property: background; 31 | 32 | +mobile() { 33 | text-align: left; 34 | menu-item-row(); 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /source/css/_schemes/Mist/_posts-expand.styl: -------------------------------------------------------------------------------- 1 | // Post Expand 2 | // -------------------------------------------------- 3 | .posts-expand { 4 | &.index { 5 | .post-header { 6 | text-align: $scheme-text-align; 7 | 8 | +mobile() { 9 | text-align: center; 10 | } 11 | } 12 | 13 | .post-meta-container { 14 | margin-top: 5px; 15 | } 16 | 17 | .post-meta { 18 | justify-content: flex-start; 19 | 20 | +mobile() { 21 | justify-content: center; 22 | } 23 | } 24 | } 25 | 26 | .post-eof { 27 | display: none; 28 | } 29 | 30 | .post-block:not(:first-of-type) { 31 | margin-top: 120px; 32 | } 33 | 34 | .post-header { 35 | margin-bottom: 20px; 36 | } 37 | 38 | .post-tags { 39 | a { 40 | background: var(--content-bg-color); 41 | border-bottom: 0; 42 | padding: 1px 5px; 43 | 44 | &:hover { 45 | background: var(--menu-item-bg-color); 46 | } 47 | } 48 | } 49 | 50 | .post-nav { 51 | margin-top: 40px; 52 | } 53 | } 54 | 55 | .post-button { 56 | margin-top: 20px; 57 | 58 | .btn { 59 | background: none; 60 | border: 0; 61 | border-bottom: 2px solid var(--btn-default-border-color); 62 | padding: 0; 63 | transition-property: border; 64 | 65 | &:hover { 66 | border-bottom-color: var(--btn-default-hover-border-color); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /source/css/_schemes/Mist/index.styl: -------------------------------------------------------------------------------- 1 | // 2 | // Mist scheme 3 | // ================================================== 4 | @import '_layout'; 5 | @import '_header'; 6 | @import '_menu'; 7 | @import '_posts-expand'; 8 | @import '../Muse/_layout'; 9 | @import '../Muse/_sidebar'; 10 | @import '../Muse/_sub-menu'; 11 | -------------------------------------------------------------------------------- /source/css/_schemes/Muse/_header.styl: -------------------------------------------------------------------------------- 1 | .custom-logo-image { 2 | background: white; 3 | margin: 0 auto 10px; 4 | max-width: 150px; 5 | padding: 5px; 6 | } 7 | 8 | .brand { 9 | background: var(--btn-default-bg); 10 | } 11 | 12 | header.header { 13 | padding-top: 100px; 14 | 15 | +mobile() { 16 | padding-top: 50px; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /source/css/_schemes/Muse/_layout.styl: -------------------------------------------------------------------------------- 1 | header.header { 2 | main-container(); 3 | } 4 | 5 | .main-inner { 6 | main-container(); 7 | padding-bottom: $content-padding-bottom; 8 | 9 | +mobile() { 10 | padding-left: 20px; 11 | padding-right: 20px; 12 | } 13 | } 14 | 15 | // Page - Container 16 | // -------------------------------------------------- 17 | .post-block:first-of-type { 18 | padding-top: $posts-first-padding; 19 | 20 | +mobile() { 21 | padding-top: $posts-first-padding-mobile; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /source/css/_schemes/Muse/_menu.styl: -------------------------------------------------------------------------------- 1 | .site-nav { 2 | +mobile() { 3 | padding-top: 30px; 4 | } 5 | } 6 | 7 | .main-menu { 8 | +mobile() { 9 | border-bottom: 1px solid $grey-lighter; 10 | border-top: 1px solid $grey-lighter; 11 | } 12 | } 13 | 14 | .menu { 15 | +mobile() { 16 | text-align: left; 17 | } 18 | } 19 | 20 | .menu .menu-item { 21 | +mobile() { 22 | margin: 0 10px; 23 | } 24 | 25 | a { 26 | border-bottom: 1px solid transparent; 27 | 28 | +mobile() { 29 | padding: 5px 10px; 30 | menu-item-row(); 31 | } 32 | 33 | &:hover, &.menu-item-active { 34 | background: transparent; 35 | border-bottom: 1px solid var(--link-hover-color); 36 | 37 | +mobile() { 38 | border-bottom: 1px dotted $grey-lighter; 39 | } 40 | } 41 | } 42 | 43 | i[class^='fa'] { 44 | +tablet-desktop() { 45 | display: block; 46 | line-height: 2; 47 | margin-right: 0; 48 | width: 100%; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /source/css/_schemes/Muse/_sidebar.styl: -------------------------------------------------------------------------------- 1 | +desktop-large() { 2 | .sidebar-dimmer { 3 | display: none; 4 | } 5 | 6 | .sidebar-active { 7 | // Note: $sidebar-width-expanded + $content-desktop-large should be less than desktop-large threshold 8 | // Otherwise a horizontal scrollbar will appear 9 | if ($sidebar-toggle-alignment == 'right') { 10 | padding-right: $sidebar-width-expanded; 11 | 12 | .footer-fixed { 13 | right: $sidebar-width-expanded; 14 | } 15 | } else { 16 | padding-left: $sidebar-width-expanded; 17 | 18 | .footer-fixed { 19 | left: $sidebar-width-expanded; 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /source/css/_schemes/Muse/_sub-menu.styl: -------------------------------------------------------------------------------- 1 | .sub-menu { 2 | margin: 10px 0; 3 | 4 | .menu-item { 5 | display: inline-block; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /source/css/_schemes/Muse/index.styl: -------------------------------------------------------------------------------- 1 | @import '_layout'; 2 | @import '_header'; 3 | @import '_menu'; 4 | @import '_sub-menu'; 5 | @import '_sidebar'; 6 | -------------------------------------------------------------------------------- /source/css/_schemes/Pisces/_header.styl: -------------------------------------------------------------------------------- 1 | .column { 2 | width: $sidebar-width-dual-column; 3 | 4 | +tablet-mobile() { 5 | width: auto; 6 | } 7 | } 8 | 9 | .site-brand-container { 10 | background: var(--theme-color); 11 | 12 | .site-nav-on & { 13 | +tablet-mobile() { 14 | box-shadow: 0 0 16px rgba(0, 0, 0, .5); 15 | } 16 | } 17 | } 18 | 19 | .site-meta { 20 | padding: 20px 0; 21 | } 22 | 23 | .site-nav-toggle, .site-nav-right { 24 | +tablet() { 25 | flex-column(); 26 | } 27 | 28 | .toggle { 29 | color: white; 30 | 31 | .toggle-line { 32 | background: white; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /source/css/_schemes/Pisces/_layout.styl: -------------------------------------------------------------------------------- 1 | header.header { 2 | gemini-block(); 3 | 4 | +tablet-mobile() { 5 | border-radius: initial; 6 | } 7 | } 8 | 9 | .main { 10 | // Make sure that .header and .main-inner are the same height 11 | // Required for .sidebar `position: sticky;` 12 | align-items: stretch; 13 | display: flex; 14 | justify-content: space-between; 15 | main-container(); 16 | 17 | if ($sidebar-toggle-alignment == 'right') { 18 | flex-direction: row-reverse; 19 | } 20 | 21 | +tablet-mobile() { 22 | display: block; 23 | width: auto; 24 | } 25 | } 26 | 27 | .main-inner { 28 | border-radius: $border-radius-inner; 29 | box-sizing: border-box; 30 | width: $content-wrap; 31 | 32 | +tablet-mobile() { 33 | border-radius: initial; 34 | width: 100%; 35 | } 36 | } 37 | 38 | .footer-inner { 39 | if ($sidebar-toggle-alignment == 'right') { 40 | padding-right: $sidebar-width-dual-column + $sidebar-offset; 41 | } else { 42 | padding-left: $sidebar-width-dual-column + $sidebar-offset; 43 | } 44 | 45 | +tablet-mobile() { 46 | padding-left: 0; 47 | padding-right: 0; 48 | width: auto; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /source/css/_schemes/Pisces/_menu.styl: -------------------------------------------------------------------------------- 1 | .site-nav { 2 | +tablet() { 3 | site-nav-hide-by-default(); 4 | } 5 | } 6 | 7 | .menu .menu-item { 8 | display: block; 9 | margin: 0; 10 | 11 | a { 12 | padding: 5px 20px; 13 | // For .menu-item-active::after 14 | position: relative; 15 | transition-property: background-color; 16 | menu-item-row(); 17 | } 18 | 19 | +tablet-mobile() { 20 | &.menu-item-search { 21 | display: none; 22 | } 23 | } 24 | } 25 | 26 | if (not hexo-config('menu_settings.badges')) { 27 | // Only apply to the main menu 28 | // Fix issue #850 29 | .main-menu .menu-item-active::after { 30 | background: $grey; 31 | border-radius: 50%; 32 | content: ' '; 33 | height: 6px; 34 | margin-top: -3px; 35 | position: absolute; 36 | right: 15px; 37 | top: 50%; 38 | width: 6px; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /source/css/_schemes/Pisces/_sidebar.styl: -------------------------------------------------------------------------------- 1 | +desktop() { 2 | .sidebar { 3 | // https://caniuse.com/css-sticky 4 | position: sticky; 5 | top: $sidebar-offset; 6 | } 7 | 8 | .sidebar-toggle { 9 | display: none; 10 | } 11 | 12 | .sidebar-inner { 13 | background: var(--content-bg-color); 14 | border-radius: $border-radius; 15 | box-shadow: $box-shadow; 16 | box-sizing: border-box; 17 | color: var(--text-color); 18 | margin-top: $sidebar-offset; 19 | max-height: 'calc(100vh - %s)' % unit($sidebar-offset * 2, 'px'); 20 | 21 | if (hexo-config('motion.enable') and hexo-config('motion.transition.sidebar')) { 22 | visibility: hidden; 23 | } 24 | } 25 | 26 | .site-state-item { 27 | padding: 0 10px; 28 | } 29 | 30 | .sidebar .sidebar-button { 31 | border-bottom: 1px dotted $grey-light; 32 | border-top: 1px dotted $grey-light; 33 | 34 | button { 35 | border: 0; 36 | color: $orange; 37 | display: block; 38 | width: 100%; 39 | 40 | &:hover { 41 | background: none; 42 | border: 0; 43 | color: darken($orange, 20%); 44 | } 45 | } 46 | } 47 | 48 | .links-of-author { 49 | flex-wrap(); 50 | } 51 | 52 | .links-of-author-item { 53 | margin: 5px 0 0; 54 | 55 | if (not hexo-config('social_icons.icons_only')) { 56 | width: 50%; 57 | } 58 | 59 | a { 60 | border-bottom: 0; 61 | border-radius: 4px; 62 | display: block; 63 | padding: 0 5px; 64 | sidebar-inline-links-item(); 65 | 66 | &:hover { 67 | background: var(--body-bg-color); 68 | } 69 | } 70 | } 71 | 72 | .links-of-blogroll-item a { 73 | padding: 0 5px; 74 | } 75 | 76 | if (hexo-config('back2top.sidebar')) { 77 | // Only when back2top.sidebar is true, apply the following styles 78 | .back-to-top { 79 | background: var(--body-bg-color); 80 | margin: 8px - $sidebar-offset -10px -18px; 81 | transition-property: bottom, margin-top; 82 | 83 | &.back-to-top-on { 84 | margin-top: 16px; 85 | } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /source/css/_schemes/Pisces/_sub-menu.styl: -------------------------------------------------------------------------------- 1 | .sub-menu { 2 | margin: 0; 3 | padding: 6px 0; 4 | 5 | .menu-item { 6 | display: inline-block; 7 | 8 | a { 9 | background: transparent; 10 | margin: 5px 10px; 11 | padding: initial; 12 | 13 | &:hover { 14 | background: transparent; 15 | color: $sidebar-highlight; 16 | } 17 | } 18 | } 19 | 20 | .menu-item-active { 21 | border-bottom-color: $sidebar-highlight; 22 | color: $sidebar-highlight; 23 | 24 | &:hover { 25 | border-bottom-color: $sidebar-highlight; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /source/css/_schemes/Pisces/index.styl: -------------------------------------------------------------------------------- 1 | @import '_layout'; 2 | @import '_header'; 3 | @import '_menu'; 4 | @import '_sub-menu'; 5 | @import '_sidebar'; 6 | 7 | .main-inner { 8 | background: var(--content-bg-color); 9 | box-shadow: $box-shadow-inner; 10 | padding: $content-desktop-padding; 11 | 12 | +tablet-mobile() { 13 | padding: 20px; 14 | } 15 | } 16 | 17 | // Sub-menu(s). 18 | .sub-menu { 19 | border-bottom: 1px solid $table-border-color; 20 | } 21 | 22 | .post-block:first-of-type { 23 | padding-top: 40px; 24 | } 25 | 26 | .pagination { 27 | +mobile() { 28 | margin-bottom: 10px; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /source/css/_variables/Gemini.styl: -------------------------------------------------------------------------------- 1 | // Variables of Gemini scheme 2 | // ================================================== 3 | 4 | @import 'Pisces'; 5 | 6 | // Settings for some of the most global styles. 7 | // -------------------------------------------------- 8 | $body-bg-color = #eee; 9 | 10 | // Borders. 11 | // -------------------------------------------------- 12 | $box-shadow-inner = 0 2px 2px 0 rgba(0, 0, 0, .12), 0 3px 1px -2px rgba(0, 0, 0, .06), 0 1px 5px 0 rgba(0, 0, 0, .12); 13 | $box-shadow = 0 2px 2px 0 rgba(0, 0, 0, .12), 0 3px 1px -2px rgba(0, 0, 0, .06), 0 1px 5px 0 rgba(0, 0, 0, .12), 0 -1px .5px 0 rgba(0, 0, 0, .09); 14 | 15 | $border-radius-inner = initial; 16 | $border-radius = initial; 17 | // $border-radius-inner = 0 0 3px 3px; 18 | // $border-radius = 3px; 19 | -------------------------------------------------------------------------------- /source/css/_variables/Mist.styl: -------------------------------------------------------------------------------- 1 | // Variables of Mist scheme 2 | // ================================================== 3 | 4 | @import 'Muse'; 5 | 6 | $scheme-text-align = left; 7 | 8 | $content-padding-bottom = 80px; 9 | $posts-first-padding = 80px; 10 | $posts-first-padding-mobile = 60px; 11 | 12 | $link-decoration-color = $grey-light; 13 | $content-bg-color = $whitesmoke; 14 | $menu-item-bg-color = $grey-lighter; 15 | 16 | $brand-color = $black-deep; 17 | $brand-hover-color = $brand-color; 18 | $brand-padding = 2px 1px; 19 | 20 | $posts-collapse-left = 0; 21 | 22 | $btn-default-bg = transparent; 23 | $btn-default-color = var(--link-color); 24 | $btn-default-hover-bg = transparent; 25 | $btn-default-border-color = var(--link-color); 26 | $btn-default-hover-color = var(--link-hover-color); 27 | $btn-default-hover-border-color = var(--link-hover-color); 28 | 29 | $badge-background = white; 30 | $badge-border-radius = 10px; 31 | $badge-text-shadow = 1px 1px 0 rgba(0, 0, 0, .1); 32 | -------------------------------------------------------------------------------- /source/css/_variables/Muse.styl: -------------------------------------------------------------------------------- 1 | // Variables of Muse scheme 2 | // ================================================== 3 | 4 | $content-padding-bottom = 60px; 5 | $posts-first-padding = 70px; 6 | $posts-first-padding-mobile = 35px; 7 | -------------------------------------------------------------------------------- /source/css/main.styl: -------------------------------------------------------------------------------- 1 | // CSS Style Guide: https://codeguide.co/#css 2 | 3 | // https://stylus-lang.com/docs/keyframes.html 4 | vendors = official; 5 | 6 | $scheme = hexo-config('scheme') ? hexo-config('scheme') : 'Muse'; 7 | 8 | 9 | // Variables Layer 10 | // -------------------------------------------------- 11 | @import '_variables/base'; 12 | @import '_variables/' + $scheme; 13 | for $inject_variable in hexo-config('injects.variable') 14 | @import $inject_variable; 15 | 16 | // Mixins Layer 17 | // -------------------------------------------------- 18 | @import '_mixins'; 19 | for $inject_mixin in hexo-config('injects.mixin') 20 | @import $inject_mixin; 21 | 22 | // Dark mode colors 23 | // -------------------------------------------------- 24 | @import '_colors'; 25 | 26 | // Common Layer 27 | // -------------------------------------------------- 28 | 29 | // Scaffolding 30 | @import '_common/scaffolding'; 31 | 32 | // Layout 33 | @import '_common/outline'; 34 | 35 | // Components 36 | @import '_common/components'; 37 | 38 | 39 | // Schemes Layer 40 | // -------------------------------------------------- 41 | @import '_schemes/' + $scheme; 42 | 43 | 44 | // Custom Layer 45 | // -------------------------------------------------- 46 | for $inject_style in hexo-config('injects.style') 47 | @import $inject_style; 48 | -------------------------------------------------------------------------------- /source/css/noscript.styl: -------------------------------------------------------------------------------- 1 | @import '_variables/base'; 2 | 3 | body { margin-top: 2rem; } 4 | 5 | .use-motion .menu-item, 6 | .use-motion .sidebar, 7 | .use-motion .sidebar-inner, 8 | .use-motion .post-block, 9 | .use-motion .pagination, 10 | .use-motion .comments, 11 | .use-motion .post-header, 12 | .use-motion .post-body, 13 | .use-motion .collection-header { 14 | visibility: visible; 15 | } 16 | 17 | .use-motion .column, 18 | .use-motion .site-brand-container .toggle, 19 | .use-motion .footer { opacity: initial; } 20 | 21 | .use-motion .site-title, 22 | .use-motion .site-subtitle, 23 | .use-motion .custom-logo-image { 24 | opacity: initial; 25 | top: initial; 26 | } 27 | 28 | .use-motion .logo-line { 29 | transform: scaleX(1); 30 | } 31 | 32 | .search-pop-overlay, .sidebar-nav { display: none; } 33 | .sidebar-panel { display: block; } 34 | 35 | .noscript-warning { 36 | background-color: lighten($red, 20%); 37 | color: white; 38 | font-family: sans-serif; 39 | font-size: 1rem; 40 | font-weight: bold; 41 | left: 0; 42 | position: fixed; 43 | text-align: center; 44 | top: 0; 45 | width: 100%; 46 | z-index: $zindex-5; 47 | } 48 | -------------------------------------------------------------------------------- /source/images/apple-touch-icon-next.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/next-theme/hexo-theme-next/2c02824edb508482ab61c187ef95d089232918b4/source/images/apple-touch-icon-next.png -------------------------------------------------------------------------------- /source/images/avatar.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/next-theme/hexo-theme-next/2c02824edb508482ab61c187ef95d089232918b4/source/images/avatar.gif -------------------------------------------------------------------------------- /source/images/favicon-16x16-next.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/next-theme/hexo-theme-next/2c02824edb508482ab61c187ef95d089232918b4/source/images/favicon-16x16-next.png -------------------------------------------------------------------------------- /source/images/favicon-32x32-next.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/next-theme/hexo-theme-next/2c02824edb508482ab61c187ef95d089232918b4/source/images/favicon-32x32-next.png -------------------------------------------------------------------------------- /source/images/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /source/js/bookmark.js: -------------------------------------------------------------------------------- 1 | /* global CONFIG */ 2 | 3 | document.addEventListener('DOMContentLoaded', () => { 4 | 'use strict'; 5 | 6 | const doSaveScroll = () => { 7 | localStorage.setItem('bookmark' + location.pathname, window.scrollY); 8 | }; 9 | 10 | const scrollToMark = () => { 11 | let top = localStorage.getItem('bookmark' + location.pathname); 12 | top = Number(top); 13 | // If the page opens with a specific hash, just jump out 14 | if (!isNaN(top) && location.hash === '') { 15 | // Auto scroll to the position 16 | window.anime({ 17 | targets : document.scrollingElement, 18 | duration : 200, 19 | easing : 'linear', 20 | scrollTop: top 21 | }); 22 | } 23 | }; 24 | // Register everything 25 | const init = function(trigger) { 26 | // Create a link element 27 | const link = document.querySelector('.book-mark-link'); 28 | // Scroll event 29 | window.addEventListener('scroll', () => link.classList.toggle('book-mark-link-fixed', window.scrollY === 0), { passive: true }); 30 | // Register beforeunload event when the trigger is auto 31 | if (trigger === 'auto') { 32 | // Register beforeunload event 33 | window.addEventListener('beforeunload', doSaveScroll); 34 | document.addEventListener('pjax:send', doSaveScroll); 35 | } 36 | // Save the position by clicking the icon 37 | link.addEventListener('click', () => { 38 | doSaveScroll(); 39 | window.anime({ 40 | targets : link, 41 | duration: 200, 42 | easing : 'linear', 43 | top : -30, 44 | complete: () => { 45 | setTimeout(() => { 46 | link.style.top = ''; 47 | }, 400); 48 | } 49 | }); 50 | }); 51 | scrollToMark(); 52 | document.addEventListener('pjax:success', scrollToMark); 53 | }; 54 | 55 | init(CONFIG.bookmark.save); 56 | }); 57 | -------------------------------------------------------------------------------- /source/js/comments-buttons.js: -------------------------------------------------------------------------------- 1 | /* global CONFIG */ 2 | 3 | (function() { 4 | const commentButton = document.querySelectorAll('.comment-button'); 5 | commentButton.forEach(element => { 6 | const commentClass = element.classList[2]; 7 | element.addEventListener('click', () => { 8 | commentButton.forEach(active => active.classList.toggle('active', active === element)); 9 | document.querySelectorAll('.comment-position').forEach(active => active.classList.toggle('active', active.classList.contains(commentClass))); 10 | if (CONFIG.comments.storage) { 11 | localStorage.setItem('comments_active', commentClass); 12 | } 13 | }); 14 | }); 15 | let { activeClass } = CONFIG.comments; 16 | if (CONFIG.comments.storage) { 17 | activeClass = localStorage.getItem('comments_active') || activeClass; 18 | } 19 | if (activeClass) { 20 | const activeButton = document.querySelector(`.comment-button.${activeClass}`); 21 | if (activeButton) { 22 | activeButton.click(); 23 | } 24 | } 25 | })(); 26 | -------------------------------------------------------------------------------- /source/js/comments.js: -------------------------------------------------------------------------------- 1 | /* global CONFIG */ 2 | 3 | window.addEventListener('tabs:register', () => { 4 | let { activeClass } = CONFIG.comments; 5 | if (CONFIG.comments.storage) { 6 | activeClass = localStorage.getItem('comments_active') || activeClass; 7 | } 8 | if (activeClass) { 9 | const activeTab = document.querySelector(`a[href="#comment-${activeClass}"]`); 10 | if (activeTab) { 11 | activeTab.click(); 12 | } 13 | } 14 | }); 15 | if (CONFIG.comments.storage) { 16 | window.addEventListener('tabs:click', event => { 17 | if (!event.target.matches('.tabs-comment .tab-content .tab-pane')) return; 18 | const commentClass = event.target.classList[1]; 19 | localStorage.setItem('comments_active', commentClass); 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /source/js/config.js: -------------------------------------------------------------------------------- 1 | if (!window.NexT) window.NexT = {}; 2 | 3 | (function() { 4 | const className = 'next-config'; 5 | 6 | const staticConfig = {}; 7 | let variableConfig = {}; 8 | 9 | const parse = text => JSON.parse(text || '{}'); 10 | 11 | const update = name => { 12 | const targetEle = document.querySelector(`.${className}[data-name="${name}"]`); 13 | if (!targetEle) return; 14 | const parsedConfig = parse(targetEle.text); 15 | if (name === 'main') { 16 | Object.assign(staticConfig, parsedConfig); 17 | } else { 18 | variableConfig[name] = parsedConfig; 19 | } 20 | }; 21 | 22 | update('main'); 23 | 24 | window.CONFIG = new Proxy({}, { 25 | get(overrideConfig, name) { 26 | let existing; 27 | if (name in staticConfig) { 28 | existing = staticConfig[name]; 29 | } else { 30 | if (!(name in variableConfig)) update(name); 31 | existing = variableConfig[name]; 32 | } 33 | 34 | // For unset override and mixable existing 35 | if (!(name in overrideConfig) && typeof existing === 'object') { 36 | // Get ready to mix. 37 | overrideConfig[name] = {}; 38 | } 39 | 40 | if (name in overrideConfig) { 41 | const override = overrideConfig[name]; 42 | 43 | // When mixable 44 | if (typeof override === 'object' && typeof existing === 'object') { 45 | // Mix, proxy changes to the override. 46 | return new Proxy({ ...existing, ...override }, { 47 | set(target, prop, value) { 48 | target[prop] = value; 49 | override[prop] = value; 50 | return true; 51 | } 52 | }); 53 | } 54 | 55 | return override; 56 | } 57 | 58 | // Only when not mixable and override hasn't been set. 59 | return existing; 60 | } 61 | }); 62 | 63 | document.addEventListener('pjax:success', () => { 64 | variableConfig = {}; 65 | }); 66 | })(); 67 | -------------------------------------------------------------------------------- /source/js/pjax.js: -------------------------------------------------------------------------------- 1 | /* global NexT, CONFIG, Pjax */ 2 | 3 | const pjax = new Pjax({ 4 | selectors: [ 5 | 'head title', 6 | 'meta[property="og:title"]', 7 | 'script[type="application/json"]', 8 | // Precede .main-inner to prevent placeholder TOC changes asap 9 | '.post-toc-wrap', 10 | '.main-inner', 11 | '.languages', 12 | '.pjax' 13 | ], 14 | switches: { 15 | '.post-toc-wrap'(oldWrap, newWrap) { 16 | if (newWrap.querySelector('.post-toc')) { 17 | Pjax.switches.outerHTML.call(this, oldWrap, newWrap); 18 | } else { 19 | const curTOC = oldWrap.querySelector('.post-toc'); 20 | if (curTOC) { 21 | curTOC.classList.add('placeholder-toc'); 22 | } 23 | this.onSwitch(); 24 | } 25 | } 26 | }, 27 | analytics: false, 28 | cacheBust: false, 29 | scrollTo : !CONFIG.bookmark.enable 30 | }); 31 | 32 | document.addEventListener('pjax:success', () => { 33 | pjax.executeScripts(document.querySelectorAll('script[data-pjax]')); 34 | NexT.boot.refresh(); 35 | // Define Motion Sequence & Bootstrap Motion. 36 | if (CONFIG.motion.enable) { 37 | NexT.motion.integrator 38 | .init() 39 | .add(NexT.motion.middleWares.subMenu) 40 | // Add sidebar-post-related transition. 41 | .add(NexT.motion.middleWares.sidebar) 42 | .add(NexT.motion.middleWares.postList) 43 | .bootstrap(); 44 | } 45 | if (CONFIG.sidebar.display !== 'remove') { 46 | const hasTOC = document.querySelector('.post-toc:not(.placeholder-toc)'); 47 | document.querySelector('.sidebar-inner').classList.toggle('sidebar-nav-active', hasTOC); 48 | NexT.utils.activateSidebarPanel(hasTOC ? 0 : 1); 49 | NexT.utils.updateSidebarPosition(); 50 | } 51 | }); 52 | 53 | if (!window.pjax) window.pjax = pjax; 54 | -------------------------------------------------------------------------------- /source/js/sidebar.js: -------------------------------------------------------------------------------- 1 | /* global CONFIG */ 2 | 3 | document.addEventListener('DOMContentLoaded', () => { 4 | 5 | const isRight = CONFIG.sidebar.position === 'right'; 6 | 7 | const sidebarToggleMotion = { 8 | mouse: {}, 9 | init() { 10 | window.addEventListener('mousedown', this.mousedownHandler.bind(this)); 11 | window.addEventListener('mouseup', this.mouseupHandler.bind(this)); 12 | document.querySelector('.sidebar-dimmer').addEventListener('click', this.clickHandler.bind(this)); 13 | document.querySelector('.sidebar-toggle').addEventListener('click', this.clickHandler.bind(this)); 14 | window.addEventListener('sidebar:show', this.showSidebar); 15 | window.addEventListener('sidebar:hide', this.hideSidebar); 16 | }, 17 | mousedownHandler(event) { 18 | this.mouse.X = event.pageX; 19 | this.mouse.Y = event.pageY; 20 | }, 21 | mouseupHandler(event) { 22 | const deltaX = event.pageX - this.mouse.X; 23 | const deltaY = event.pageY - this.mouse.Y; 24 | const clickingBlankPart = Math.hypot(deltaX, deltaY) < 20 && event.target.matches('.main'); 25 | // Fancybox has z-index property, but medium-zoom does not, so the sidebar will overlay the zoomed image. 26 | if (clickingBlankPart || event.target.matches('img.medium-zoom-image')) { 27 | this.hideSidebar(); 28 | } 29 | }, 30 | clickHandler() { 31 | document.body.classList.contains('sidebar-active') ? this.hideSidebar() : this.showSidebar(); 32 | }, 33 | showSidebar() { 34 | document.body.classList.add('sidebar-active'); 35 | const animateAction = isRight ? 'fadeInRight' : 'fadeInLeft'; 36 | document.querySelectorAll('.sidebar .animated').forEach((element, index) => { 37 | element.style.animationDelay = (100 * index) + 'ms'; 38 | element.classList.remove(animateAction); 39 | setTimeout(() => { 40 | // Trigger a DOM reflow 41 | element.classList.add(animateAction); 42 | }); 43 | }); 44 | }, 45 | hideSidebar() { 46 | document.body.classList.remove('sidebar-active'); 47 | } 48 | }; 49 | sidebarToggleMotion.init(); 50 | }); 51 | -------------------------------------------------------------------------------- /source/js/third-party/addtoany.js: -------------------------------------------------------------------------------- 1 | /* global NexT */ 2 | 3 | document.addEventListener('page:loaded', () => { 4 | NexT.utils.getScript('https://static.addtoany.com/menu/page.js', { condition: window.a2a }) 5 | .then(() => { 6 | window.a2a.init(); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /source/js/third-party/analytics/baidu-analytics.js: -------------------------------------------------------------------------------- 1 | /* global _hmt */ 2 | 3 | if (!window._hmt) window._hmt = []; 4 | 5 | document.addEventListener('pjax:success', () => { 6 | _hmt.push(['_trackPageview', location.pathname]); 7 | }); 8 | -------------------------------------------------------------------------------- /source/js/third-party/analytics/google-analytics.js: -------------------------------------------------------------------------------- 1 | /* global CONFIG, dataLayer, gtag */ 2 | 3 | if (!CONFIG.google_analytics.only_pageview) { 4 | if (CONFIG.hostname === location.hostname) { 5 | window.dataLayer = window.dataLayer || []; 6 | window.gtag = function() { 7 | dataLayer.push(arguments); 8 | }; 9 | gtag('js', new Date()); 10 | gtag('config', CONFIG.google_analytics.tracking_id); 11 | 12 | document.addEventListener('pjax:success', () => { 13 | gtag('event', 'page_view', { 14 | page_location: location.href, 15 | page_path : location.pathname, 16 | page_title : document.title 17 | }); 18 | }); 19 | } 20 | } else { 21 | const sendPageView = () => { 22 | if (CONFIG.hostname !== location.hostname) return; 23 | const uid = localStorage.getItem('uid') || (Math.random() + '.' + Math.random()); 24 | localStorage.setItem('uid', uid); 25 | fetch( 26 | 'https://www.google-analytics.com/mp/collect?' + new URLSearchParams({ 27 | api_secret : CONFIG.google_analytics.measure_protocol_api_secret, 28 | measurement_id: CONFIG.google_analytics.tracking_id 29 | }), 30 | { 31 | method : 'POST', 32 | headers: { 33 | 'Content-Type': 'application/json' 34 | }, 35 | body: JSON.stringify({ 36 | client_id: uid, 37 | events : [ 38 | { 39 | name : 'page_view', 40 | params: { 41 | page_location: location.href, 42 | page_title : document.title 43 | } 44 | } 45 | ] 46 | }), 47 | mode: 'no-cors' 48 | } 49 | ); 50 | }; 51 | document.addEventListener('pjax:complete', sendPageView); 52 | sendPageView(); 53 | } 54 | -------------------------------------------------------------------------------- /source/js/third-party/analytics/growingio.js: -------------------------------------------------------------------------------- 1 | /* global CONFIG, gio */ 2 | 3 | if (!window.gio) { 4 | window.gio = function() { 5 | (window.gio.q = window.gio.q || []).push(arguments); 6 | }; 7 | } 8 | 9 | gio('init', `${CONFIG.growingio_analytics}`, {}); 10 | gio('send'); 11 | -------------------------------------------------------------------------------- /source/js/third-party/analytics/matomo.js: -------------------------------------------------------------------------------- 1 | /* global CONFIG */ 2 | 3 | if (CONFIG.matomo.enable) { 4 | window._paq = window._paq || []; 5 | const _paq = window._paq; 6 | 7 | /* tracker methods like "setCustomDimension" should be called before "trackPageView" */ 8 | _paq.push(['trackPageView']); 9 | _paq.push(['enableLinkTracking']); 10 | const u = CONFIG.matomo.server_url; 11 | _paq.push(['setTrackerUrl', u + 'matomo.php']); 12 | _paq.push(['setSiteId', CONFIG.matomo.site_id]); 13 | const d = document; 14 | const g = d.createElement('script'); 15 | const s = d.getElementsByTagName('script')[0]; 16 | g.async = true; 17 | g.src = u + 'matomo.js'; 18 | s.parentNode.insertBefore(g, s); 19 | } 20 | -------------------------------------------------------------------------------- /source/js/third-party/chat/chatra.js: -------------------------------------------------------------------------------- 1 | /* global CONFIG, Chatra */ 2 | 3 | (function() { 4 | if (CONFIG.chatra.embed) { 5 | window.ChatraSetup = { 6 | mode : 'frame', 7 | injectTo: CONFIG.chatra.embed 8 | }; 9 | } 10 | 11 | window.ChatraID = CONFIG.chatra.id; 12 | 13 | const chatButton = document.querySelector('.sidebar-button button'); 14 | if (chatButton) { 15 | chatButton.addEventListener('click', () => { 16 | Chatra('openChat', true); 17 | }); 18 | } 19 | })(); 20 | -------------------------------------------------------------------------------- /source/js/third-party/chat/tidio.js: -------------------------------------------------------------------------------- 1 | /* global tidioChatApi */ 2 | 3 | (function() { 4 | const chatButton = document.querySelector('.sidebar-button button'); 5 | if (chatButton) { 6 | chatButton.addEventListener('click', () => { 7 | tidioChatApi.open(); 8 | }); 9 | } 10 | })(); 11 | -------------------------------------------------------------------------------- /source/js/third-party/comments/changyan.js: -------------------------------------------------------------------------------- 1 | /* global NexT, CONFIG */ 2 | 3 | document.addEventListener('page:loaded', () => { 4 | const { appid, appkey } = CONFIG.changyan; 5 | const mainJs = 'https://cy-cdn.kuaizhan.com/upload/changyan.js'; 6 | const countJs = `https://cy-cdn.kuaizhan.com/upload/plugins/plugins.list.count.js?clientId=${appid}`; 7 | 8 | // Get the number of comments 9 | setTimeout(() => { 10 | return NexT.utils.getScript(countJs, { 11 | attributes: { 12 | async: true, 13 | id : 'cy_cmt_num' 14 | } 15 | }); 16 | }, 0); 17 | 18 | // When scroll to comment section 19 | if (CONFIG.page.comments && !CONFIG.page.isHome) { 20 | NexT.utils.loadComments('#SOHUCS') 21 | .then(() => { 22 | return NexT.utils.getScript(mainJs, { 23 | attributes: { 24 | async: true 25 | } 26 | }); 27 | }) 28 | .then(() => { 29 | window.changyan.api.config({ 30 | appid, 31 | conf: appkey 32 | }); 33 | }) 34 | .catch(error => { 35 | // eslint-disable-next-line no-console 36 | console.error('Failed to load Changyan', error); 37 | }); 38 | } 39 | }); 40 | -------------------------------------------------------------------------------- /source/js/third-party/comments/disqus.js: -------------------------------------------------------------------------------- 1 | /* global NexT, CONFIG, DISQUS */ 2 | 3 | document.addEventListener('page:loaded', () => { 4 | 5 | if (CONFIG.disqus.count) { 6 | if (window.DISQUSWIDGETS) { 7 | window.DISQUSWIDGETS.getCount({ reset: true }); 8 | } else { 9 | // Defer loading until the whole page loading is completed 10 | NexT.utils.getScript(`https://${CONFIG.disqus.shortname}.disqus.com/count.js`, { 11 | attributes: { id: 'dsq-count-scr', defer: true } 12 | }); 13 | } 14 | } 15 | 16 | if (CONFIG.page.comments) { 17 | // `disqus_config` should be a global variable 18 | // See https://help.disqus.com/en/articles/1717084-javascript-configuration-variables 19 | window.disqus_config = function() { 20 | this.page.url = CONFIG.page.permalink; 21 | this.page.identifier = CONFIG.page.path; 22 | this.page.title = CONFIG.page.title; 23 | if (CONFIG.disqus.i18n.disqus !== 'disqus') { 24 | this.language = CONFIG.disqus.i18n.disqus; 25 | } 26 | }; 27 | NexT.utils.loadComments('#disqus_thread').then(() => { 28 | if (window.DISQUS) { 29 | DISQUS.reset({ 30 | reload: true, 31 | config: window.disqus_config 32 | }); 33 | } else { 34 | NexT.utils.getScript(`https://${CONFIG.disqus.shortname}.disqus.com/embed.js`, { 35 | attributes: { dataset: { timestamp: '' + +new Date() } } 36 | }); 37 | } 38 | }); 39 | } 40 | 41 | }); 42 | -------------------------------------------------------------------------------- /source/js/third-party/comments/disqusjs.js: -------------------------------------------------------------------------------- 1 | /* global NexT, CONFIG, DisqusJS */ 2 | 3 | document.addEventListener('page:loaded', () => { 4 | if (!CONFIG.page.comments) return; 5 | 6 | NexT.utils.loadComments('#disqus_thread') 7 | .then(() => NexT.utils.getScript(CONFIG.disqusjs.js, { condition: window.DisqusJS })) 8 | .then(() => { 9 | window.dsqjs = new DisqusJS({ 10 | api : CONFIG.disqusjs.api || 'https://disqus.com/api/', 11 | apikey : CONFIG.disqusjs.apikey, 12 | shortname : CONFIG.disqusjs.shortname, 13 | url : CONFIG.page.permalink, 14 | identifier: CONFIG.page.path, 15 | title : CONFIG.page.title 16 | }); 17 | window.dsqjs.render(document.querySelector('.disqusjs-container')); 18 | }); 19 | }); 20 | 21 | document.addEventListener('pjax:send', () => { 22 | if (window.dsqjs) window.dsqjs.destroy(); 23 | }); 24 | -------------------------------------------------------------------------------- /source/js/third-party/comments/gitalk.js: -------------------------------------------------------------------------------- 1 | /* global NexT, CONFIG, Gitalk */ 2 | 3 | document.addEventListener('page:loaded', () => { 4 | if (!CONFIG.page.comments) return; 5 | 6 | NexT.utils.loadComments('.gitalk-container') 7 | .then(() => NexT.utils.getScript(CONFIG.gitalk.js, { 8 | condition: window.Gitalk 9 | })) 10 | .then(() => { 11 | const gitalk = new Gitalk({ 12 | clientID : CONFIG.gitalk.client_id, 13 | clientSecret : CONFIG.gitalk.client_secret, 14 | repo : CONFIG.gitalk.repo, 15 | owner : CONFIG.gitalk.github_id, 16 | admin : [CONFIG.gitalk.admin_user], 17 | id : CONFIG.gitalk.path_md5, 18 | proxy : CONFIG.gitalk.proxy, 19 | language : CONFIG.gitalk.language || window.navigator.language, 20 | distractionFreeMode: CONFIG.gitalk.distraction_free_mode 21 | }); 22 | gitalk.render(document.querySelector('.gitalk-container')); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /source/js/third-party/comments/isso.js: -------------------------------------------------------------------------------- 1 | /* global NexT, CONFIG */ 2 | 3 | document.addEventListener('page:loaded', () => { 4 | if (!CONFIG.page.comments) return; 5 | 6 | NexT.utils.loadComments('#isso-thread') 7 | .then(() => NexT.utils.getScript(`${CONFIG.isso}js/embed.min.js`, { 8 | attributes: { 9 | dataset: { 10 | isso: `${CONFIG.isso}` 11 | } 12 | }, 13 | parentNode: document.querySelector('#isso-thread') 14 | })); 15 | }); 16 | -------------------------------------------------------------------------------- /source/js/third-party/comments/livere.js: -------------------------------------------------------------------------------- 1 | /* global NexT, CONFIG, LivereTower */ 2 | 3 | document.addEventListener('page:loaded', () => { 4 | if (!CONFIG.page.comments) return; 5 | 6 | NexT.utils.loadComments('#lv-container').then(() => { 7 | window.livereOptions = { 8 | refer: CONFIG.page.path.replace(/index\.html$/, '') 9 | }; 10 | 11 | if (typeof LivereTower === 'function') return; 12 | 13 | NexT.utils.getScript('https://cdn-city.livere.com/js/embed.dist.js', { 14 | attributes: { 15 | async: true 16 | } 17 | }); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /source/js/third-party/comments/utterances.js: -------------------------------------------------------------------------------- 1 | /* global NexT, CONFIG */ 2 | 3 | document.addEventListener('page:loaded', () => { 4 | if (!CONFIG.page.comments) return; 5 | 6 | NexT.utils.loadComments('.utterances-container') 7 | .then(() => NexT.utils.getScript('https://utteranc.es/client.js', { 8 | attributes: { 9 | async : true, 10 | crossOrigin : 'anonymous', 11 | 'repo' : CONFIG.utterances.repo, 12 | 'issue-term': CONFIG.utterances.issue_term, 13 | 'theme' : CONFIG.utterances.theme 14 | }, 15 | parentNode: document.querySelector('.utterances-container') 16 | })); 17 | }); 18 | -------------------------------------------------------------------------------- /source/js/third-party/fancybox.js: -------------------------------------------------------------------------------- 1 | /* global Fancybox */ 2 | 3 | document.addEventListener('page:loaded', () => { 4 | 5 | /** 6 | * Wrap images with fancybox. 7 | */ 8 | document.querySelectorAll('.post-body :not(a) > img, .post-body > img').forEach(image => { 9 | const imageLink = image.dataset.src || image.src; 10 | const imageWrapLink = document.createElement('a'); 11 | imageWrapLink.classList.add('fancybox'); 12 | imageWrapLink.href = imageLink; 13 | imageWrapLink.setAttribute('itemscope', ''); 14 | imageWrapLink.setAttribute('itemtype', 'http://schema.org/ImageObject'); 15 | imageWrapLink.setAttribute('itemprop', 'url'); 16 | 17 | let dataFancybox = 'default'; 18 | if (image.closest('.post-gallery') !== null) { 19 | dataFancybox = 'gallery'; 20 | } else if (image.closest('.group-picture') !== null) { 21 | dataFancybox = 'group'; 22 | } 23 | imageWrapLink.dataset.fancybox = dataFancybox; 24 | 25 | const imageTitle = image.title || image.alt; 26 | if (imageTitle) { 27 | imageWrapLink.title = imageTitle; 28 | // Make sure img captions will show correctly in fancybox 29 | imageWrapLink.dataset.caption = imageTitle; 30 | } 31 | image.wrap(imageWrapLink); 32 | }); 33 | 34 | Fancybox.bind('[data-fancybox]'); 35 | }); 36 | -------------------------------------------------------------------------------- /source/js/third-party/math/katex.js: -------------------------------------------------------------------------------- 1 | /* global NexT, CONFIG */ 2 | 3 | document.addEventListener('page:loaded', () => { 4 | if (!CONFIG.enableMath) return; 5 | 6 | NexT.utils.getScript(CONFIG.katex.copy_tex_js).catch(() => {}); 7 | }); 8 | -------------------------------------------------------------------------------- /source/js/third-party/math/mathjax.js: -------------------------------------------------------------------------------- 1 | /* global NexT, CONFIG, MathJax */ 2 | 3 | document.addEventListener('page:loaded', () => { 4 | if (!CONFIG.enableMath) return; 5 | 6 | if (typeof MathJax === 'undefined') { 7 | window.MathJax = { 8 | tex: { 9 | inlineMath: { '[+]': [['$', '$']] }, 10 | tags : CONFIG.mathjax.tags 11 | }, 12 | options: { 13 | renderActions: { 14 | insertedScript: [200, () => { 15 | document.querySelectorAll('mjx-container').forEach(node => { 16 | const target = node.parentNode; 17 | if (target.nodeName.toLowerCase() === 'li') { 18 | target.parentNode.classList.add('has-jax'); 19 | } 20 | }); 21 | }, '', false] 22 | } 23 | } 24 | }; 25 | NexT.utils.getScript(CONFIG.mathjax.js, { 26 | attributes: { 27 | defer: true 28 | } 29 | }); 30 | } else { 31 | MathJax.startup.document.state(0); 32 | MathJax.typesetClear(); 33 | MathJax.texReset(); 34 | MathJax.typesetPromise(); 35 | } 36 | }); 37 | -------------------------------------------------------------------------------- /source/js/third-party/pace.js: -------------------------------------------------------------------------------- 1 | /* global Pace */ 2 | 3 | Pace.options.restartOnPushState = false; 4 | 5 | document.addEventListener('pjax:send', () => { 6 | Pace.restart(); 7 | }); 8 | -------------------------------------------------------------------------------- /source/js/third-party/quicklink.js: -------------------------------------------------------------------------------- 1 | /* global CONFIG, quicklink */ 2 | 3 | (function() { 4 | if (typeof CONFIG.quicklink.ignores === 'string') { 5 | const ignoresStr = `[${CONFIG.quicklink.ignores}]`; 6 | CONFIG.quicklink.ignores = JSON.parse(ignoresStr); 7 | } 8 | 9 | let resetFn = null; 10 | 11 | const onRefresh = () => { 12 | if (resetFn) resetFn(); 13 | if (!CONFIG.quicklink.enable) return; 14 | 15 | let ignoresArr = CONFIG.quicklink.ignores || []; 16 | if (!Array.isArray(ignoresArr)) { 17 | ignoresArr = [ignoresArr]; 18 | } 19 | 20 | resetFn = quicklink.listen({ 21 | timeout : CONFIG.quicklink.timeout, 22 | priority: CONFIG.quicklink.priority, 23 | ignores : [ 24 | uri => uri.includes('#'), 25 | uri => uri === CONFIG.quicklink.url, 26 | ...ignoresArr 27 | ] 28 | }); 29 | }; 30 | 31 | if (CONFIG.quicklink.delay) { 32 | window.addEventListener('load', onRefresh); 33 | document.addEventListener('pjax:success', onRefresh); 34 | } else { 35 | document.addEventListener('page:loaded', onRefresh); 36 | } 37 | })(); 38 | -------------------------------------------------------------------------------- /source/js/third-party/statistics/firestore.js: -------------------------------------------------------------------------------- 1 | /* global CONFIG, firebase */ 2 | 3 | firebase.initializeApp({ 4 | apiKey : CONFIG.firestore.apiKey, 5 | projectId: CONFIG.firestore.projectId 6 | }); 7 | 8 | (function() { 9 | const getCount = (doc, increaseCount) => { 10 | // IncreaseCount will be false when not in article page 11 | return doc.get().then(d => { 12 | // Has no data, initialize count 13 | let count = d.exists ? d.data().count : 0; 14 | // If first view this article 15 | if (increaseCount) { 16 | // Increase count 17 | count++; 18 | doc.set({ 19 | count 20 | }); 21 | } 22 | return count; 23 | }); 24 | }; 25 | 26 | const db = firebase.firestore(); 27 | const articles = db.collection(CONFIG.firestore.collection); 28 | 29 | document.addEventListener('page:loaded', () => { 30 | 31 | if (CONFIG.page.isPost) { 32 | // Fix issue #118 33 | // https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent 34 | const title = document.querySelector('.post-title').textContent.trim(); 35 | const doc = articles.doc(title); 36 | let increaseCount = CONFIG.hostname === location.hostname; 37 | if (localStorage.getItem(title)) { 38 | increaseCount = false; 39 | } else { 40 | // Mark as visited 41 | localStorage.setItem(title, true); 42 | } 43 | getCount(doc, increaseCount).then(count => { 44 | document.querySelector('.firestore-visitors-count').innerText = count; 45 | }); 46 | } else if (CONFIG.page.isHome) { 47 | const promises = [...document.querySelectorAll('.post-title')].map(element => { 48 | const title = element.textContent.trim(); 49 | const doc = articles.doc(title); 50 | return getCount(doc); 51 | }); 52 | Promise.all(promises).then(counts => { 53 | const metas = document.querySelectorAll('.firestore-visitors-count'); 54 | counts.forEach((val, idx) => { 55 | metas[idx].innerText = val; 56 | }); 57 | }); 58 | } 59 | }); 60 | })(); 61 | -------------------------------------------------------------------------------- /source/js/third-party/tags/mermaid.js: -------------------------------------------------------------------------------- 1 | /* global NexT, CONFIG, mermaid */ 2 | 3 | document.addEventListener('page:loaded', () => { 4 | const mermaidElements = document.querySelectorAll('pre > .mermaid'); 5 | if (mermaidElements.length) { 6 | NexT.utils.getScript(CONFIG.mermaid.js, { 7 | condition: window.mermaid 8 | }).then(() => { 9 | mermaidElements.forEach(element => { 10 | const box = document.createElement('div'); 11 | box.className = 'code-container'; 12 | const newElement = document.createElement('div'); 13 | newElement.innerHTML = element.innerHTML; 14 | newElement.className = 'mermaid'; 15 | box.appendChild(newElement); 16 | if (CONFIG.codeblock.copy_button.enable) { 17 | NexT.utils.registerCopyButton(box, box, element.textContent); 18 | } 19 | const parent = element.parentNode; 20 | parent.parentNode.replaceChild(box, parent); 21 | }); 22 | mermaid.initialize({ 23 | theme : CONFIG.darkmode && window.matchMedia('(prefers-color-scheme: dark)').matches ? CONFIG.mermaid.theme.dark : CONFIG.mermaid.theme.light, 24 | logLevel : 4, 25 | flowchart: { curve: 'linear' }, 26 | gantt : { axisFormat: '%m/%d/%Y' }, 27 | sequence : { actorMargin: 50 } 28 | }); 29 | mermaid.run(); 30 | }); 31 | } 32 | }); 33 | -------------------------------------------------------------------------------- /source/js/third-party/tags/pdf.js: -------------------------------------------------------------------------------- 1 | /* global NexT, CONFIG, PDFObject */ 2 | 3 | document.addEventListener('page:loaded', () => { 4 | if (document.querySelectorAll('.pdf-container').length) { 5 | NexT.utils.getScript(CONFIG.pdf.object_url, { 6 | condition: window.PDFObject 7 | }).then(() => { 8 | document.querySelectorAll('.pdf-container').forEach(element => { 9 | PDFObject.embed(element.dataset.target, element, { 10 | pdfOpenParams: { 11 | navpanes : 0, 12 | toolbar : 0, 13 | statusbar: 0, 14 | pagemode : 'thumbs', 15 | view : 'FitH' 16 | }, 17 | PDFJS_URL: CONFIG.pdf.url, 18 | height : element.dataset.height 19 | }); 20 | }); 21 | }); 22 | } 23 | }); 24 | -------------------------------------------------------------------------------- /source/js/third-party/tags/wavedrom.js: -------------------------------------------------------------------------------- 1 | /* global NexT, CONFIG, WaveDrom */ 2 | 3 | document.addEventListener('page:loaded', () => { 4 | NexT.utils.getScript(CONFIG.wavedrom.js, { 5 | condition: window.WaveDrom 6 | }).then(() => { 7 | NexT.utils.getScript(CONFIG.wavedrom_skin.js, { 8 | condition: window.WaveSkin 9 | }).then(() => { 10 | WaveDrom.ProcessAll(); 11 | }); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /test/helpers/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Helpers', () => { 4 | require('./font'); 5 | require('./next-url'); 6 | }); 7 | -------------------------------------------------------------------------------- /test/helpers/next-url.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Hexo = require('hexo'); 4 | const hexo = new Hexo(); 5 | 6 | function btoa(str) { 7 | return Buffer.from(str).toString('base64'); 8 | } 9 | 10 | describe('next-url', () => { 11 | const nextUrl = require('../../scripts/helpers/next-url').bind(hexo); 12 | 13 | before(() => { 14 | hexo.config.url = 'https://example.com'; 15 | hexo.url_for = require('hexo/dist/plugins/helper/url_for').bind(hexo); 16 | }); 17 | 18 | it('text', () => { 19 | nextUrl('/child/', 'Text').should.eql('Text'); 20 | }); 21 | 22 | it('icon', () => { 23 | nextUrl('/child/', '').should.eql(''); 24 | }); 25 | 26 | it('class', () => { 27 | nextUrl('/child/', 'Text', { class: 'theme-link' }).should.eql('Text'); 28 | }); 29 | 30 | it('external', () => { 31 | nextUrl('https://theme-next.js.org', 'Text').should.eql('Text'); 32 | }); 33 | 34 | it('decodeURI', () => { 35 | (() => nextUrl('https://theme-next.js.org', 'A % B')).should.not.throw(); 36 | }); 37 | 38 | it('exturl enabled', () => { 39 | hexo.theme.exturl = true; 40 | const encoded = btoa('https://theme-next.js.org'); 41 | nextUrl('https://theme-next.js.org', 'Text').should.eql(`Text`); 42 | }); 43 | 44 | it('class with exturl enabled', () => { 45 | hexo.theme.exturl = true; 46 | const encoded = btoa('https://theme-next.js.org'); 47 | nextUrl('https://theme-next.js.org', 'Text', { class: 'theme-link' }).should.eql(`Text`); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('chai').should(); 4 | 5 | describe('NexT', () => { 6 | require('./helpers'); 7 | require('./tags'); 8 | require('./validate'); 9 | }); 10 | -------------------------------------------------------------------------------- /test/tags/button.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Hexo = require('hexo'); 4 | const hexo = new Hexo(); 5 | 6 | describe('button', () => { 7 | const postButton = require('../../scripts/tags/button')(hexo); 8 | 9 | it('only url', () => { 10 | postButton(['#']).should.eql(''); 11 | }); 12 | 13 | it('url and text', () => { 14 | postButton('#, Hello world'.split(' ')).should.eql('Hello world'); 15 | }); 16 | 17 | it('url and icon (Font Awesome 4)', () => { 18 | postButton('#,, home fa-5x'.split(' ')).should.eql(''); 19 | }); 20 | 21 | it('url and icon', () => { 22 | postButton('#,, fab fa-fort-awesome fa-5x'.split(' ')).should.eql(''); 23 | }); 24 | 25 | it('url, text and title', () => { 26 | postButton('#, Hello world,, Title'.split(' ')).should.eql('Hello world'); 27 | }); 28 | 29 | it('url, text, icon and title', () => { 30 | postButton('#, Hello world, home, Title'.split(' ')).should.eql('Hello world'); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /test/tags/caniuse.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Hexo = require('hexo'); 4 | const hexo = new Hexo(); 5 | 6 | describe('caniuse', () => { 7 | const caniUse = require('../../scripts/tags/caniuse')(hexo); 8 | 9 | it('only feature', () => { 10 | caniUse(['loading-lazy-attr']).should.eql(''); 11 | }); 12 | 13 | it('feature and periods', () => { 14 | caniUse('fetch @ future_3,future_2,future_1'.split(' ')).should.eql(''); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /test/tags/center-quote.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Hexo = require('hexo'); 4 | const hexo = new Hexo(); 5 | 6 | const content = 'Test **Bold** *Italic*'; 7 | const result = '

    Test Bold Italic

    '; 8 | 9 | describe('center-quote', () => { 10 | const centerQuote = require('../../scripts/tags/center-quote')(hexo); 11 | 12 | before(() => hexo.init().then(() => hexo.loadPlugin(require.resolve('hexo-renderer-marked')))); 13 | 14 | it('markdown content', () => { 15 | centerQuote([], content).should.eql(`
    16 | ${result} 17 | 18 |
    `); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /test/tags/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Tags', () => { 4 | require('./button'); 5 | require('./caniuse'); 6 | require('./center-quote'); 7 | require('./group-pictures'); 8 | require('./label'); 9 | require('./link-grid'); 10 | require('./mermaid'); 11 | require('./note'); 12 | require('./pdf'); 13 | require('./tabs'); 14 | require('./video'); 15 | }); 16 | -------------------------------------------------------------------------------- /test/tags/label.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Hexo = require('hexo'); 4 | const hexo = new Hexo(); 5 | 6 | describe('label', () => { 7 | const postLabel = require('../../scripts/tags/label')(hexo); 8 | 9 | it('only text', () => { 10 | postLabel('@Hello world'.split(' ')).should.eql('Hello world'); 11 | }); 12 | 13 | it('classes and text', () => { 14 | postLabel('primary@Hello world'.split(' ')).should.eql('Hello world'); 15 | }); 16 | 17 | it('classes and text with space', () => { 18 | postLabel('primary @Hello world'.split(' ')).should.eql('Hello world'); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /test/tags/link-grid.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | const result = ``; 13 | 14 | describe('link-grid', () => { 15 | const linkGrid = require('../../scripts/tags/link-grid'); 16 | 17 | it('default', () => { 18 | linkGrid([], ` 19 | Theme NexT | https://theme-next.js.org/ | Stay Simple. Stay NexT. | /images/sample.png 20 | Theme NexT | https://theme-next.js.org/ | Stay Simple. Stay NexT. | /images/sample.png`).should.eql(result); 21 | }); 22 | 23 | it('comment', () => { 24 | linkGrid([], ` 25 | Theme NexT | https://theme-next.js.org/ | Stay Simple. Stay NexT. | /images/sample.png 26 | Theme NexT | https://theme-next.js.org/ | Stay Simple. Stay NexT. | /images/sample.png 27 | % Theme NexT | https://theme-next.js.org/ | Stay Simple. Stay NexT. | /images/sample.png`).should.eql(result); 28 | }); 29 | 30 | it('default image', () => { 31 | linkGrid(['/images/sample.png'], ` 32 | Theme NexT | https://theme-next.js.org/ | Stay Simple. Stay NexT. | 33 | Theme NexT | https://theme-next.js.org/ | Stay Simple. Stay NexT. |`).should.eql(result); 34 | }); 35 | 36 | it('custom delimiter', () => { 37 | linkGrid(['/images/sample.png', ','], ` 38 | Theme NexT , https://theme-next.js.org/ , Stay Simple. Stay NexT. , /images/sample.png 39 | Theme NexT , https://theme-next.js.org/ , Stay Simple. Stay NexT. , /images/sample.png`).should.eql(result); 40 | }); 41 | 42 | it('custom delimiter and comment', () => { 43 | linkGrid(['/images/sample.png', ',', '#'], ` 44 | Theme NexT , https://theme-next.js.org/ , Stay Simple. Stay NexT. , /images/sample.png 45 | Theme NexT , https://theme-next.js.org/ , Stay Simple. Stay NexT. , /images/sample.png 46 | # Theme NexT , https://theme-next.js.org/ , Stay Simple. Stay NexT. , /images/sample.png`).should.eql(result); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /test/tags/mermaid.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | const { escapeHTML } = require('hexo-util'); 5 | 6 | const result = `A[Hard] -->|Text| B(Round) 7 | B --> C{Decision} 8 | C -->|One| D[Result 1] 9 | C -->|Two| E[Result 2]`; 10 | 11 | describe('mermaid', () => { 12 | const mermaid = require('../../scripts/tags/mermaid'); 13 | 14 | it('default', () => { 15 | mermaid(['graph', 'TD'], result).should.eql(`
    16 | 
    17 | graph TD
    18 | ${escapeHTML(result)}
    19 | 
    20 | 
    `); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /test/tags/note.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Hexo = require('hexo'); 4 | const hexo = new Hexo(); 5 | 6 | const content = 'Test **Bold** *Italic*'; 7 | const result = '

    Test Bold Italic

    '; 8 | const args = 'This is a *summary*'.split(' '); 9 | const summary = '

    This is a summary'; 10 | 11 | describe('note', () => { 12 | const postNote = require('../../scripts/tags/note')(hexo); 13 | 14 | before(() => hexo.init().then(() => hexo.loadPlugin(require.resolve('hexo-renderer-marked')))); 15 | 16 | it('only text', () => { 17 | postNote([], content).should.eql(`

    ${result} 18 |
    `); 19 | }); 20 | 21 | it('classes and text', () => { 22 | postNote(['primary'], content).should.eql(`
    ${result} 23 |
    `); 24 | }); 25 | 26 | it('classes, no-icon and text', () => { 27 | postNote(['primary', 'no-icon'], content).should.eql(`
    ${result} 28 |
    `); 29 | }); 30 | 31 | it('summary and text', () => { 32 | postNote(args, content).should.eql(`
    ${summary}

    33 |
    34 | ${result} 35 | 36 | `); 37 | }); 38 | 39 | it('classes, summary and text', () => { 40 | postNote(['primary'].concat(args), content).should.eql(`
    ${summary}

    41 | 42 | ${result} 43 | 44 |
    `); 45 | }); 46 | 47 | it('classes, no-icon, summary and text', () => { 48 | postNote(['primary', 'no-icon'].concat(args), content).should.eql(`
    ${summary}

    49 | 50 | ${result} 51 | 52 |
    `); 53 | }); 54 | 55 | it('keywords in summary', () => { 56 | postNote(['It\'s', 'danger'], content).should.eql(`

    It’s danger

    57 |
    58 | ${result} 59 | 60 |
    `); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /test/tags/pdf.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Hexo = require('hexo'); 4 | const hexo = new Hexo(); 5 | 6 | describe('pdf', () => { 7 | const pdf = require('../../scripts/tags/pdf')(hexo); 8 | 9 | before(() => { 10 | hexo.theme.config.pdf = { 11 | height: '500px' 12 | }; 13 | }); 14 | 15 | it('default', () => { 16 | pdf(['https://example.com/sample.pdf']).should.eql('
    '); 17 | }); 18 | 19 | it('custom height', () => { 20 | pdf(['https://example.com/sample.pdf', '1000px']).should.eql('
    '); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /test/tags/video.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | describe('video', () => { 5 | const postVideo = require('../../scripts/tags/video'); 6 | 7 | it('default', () => { 8 | postVideo(['https://example.com/sample.mp4']).should.eql(''); 9 | }); 10 | 11 | it('poster', () => { 12 | postVideo(['https://example.com/sample.mp4', 'https://example.com/sample.jpg']).should.eql(''); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /test/validate/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const yaml = require('js-yaml'); 6 | 7 | describe('Validate', () => { 8 | it('config', () => { 9 | const themeConfig = fs.readFileSync(path.join(__dirname, '../../_config.yml')); 10 | (() => { 11 | yaml.load(themeConfig); 12 | }).should.not.throw(); 13 | }); 14 | 15 | it('vendors', () => { 16 | const vendorsFile = fs.readFileSync(path.join(__dirname, '../../_vendors.yml')); 17 | (() => { 18 | yaml.load(vendorsFile); 19 | }).should.not.throw(); 20 | }); 21 | 22 | it('language', () => { 23 | const languagesPath = path.join(__dirname, '../../languages'); 24 | (() => { 25 | fs.readdirSync(languagesPath).forEach(lang => { 26 | if (!lang.endsWith('.yml')) return; 27 | const languagePath = path.join(languagesPath, lang); 28 | yaml.load(fs.readFileSync(languagePath), { 29 | filename: path.relative(__dirname, languagePath) 30 | }); 31 | }); 32 | }).should.not.throw(); 33 | }); 34 | }); 35 | --------------------------------------------------------------------------------