Compare commits

..

No commits in common. "master" and "v8.28.0" have entirely different histories.

40 changed files with 306 additions and 470 deletions

View File

@ -7,7 +7,7 @@ jobs:
triage: triage:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/labeler@v7 - uses: actions/labeler@v6
# https://github.com/peaceiris/actions-label-commenter#work-with-other-auto-label-actions # https://github.com/peaceiris/actions-label-commenter#work-with-other-auto-label-actions
with: with:
repo-token: "${{ secrets.GH_PAT }}" repo-token: "${{ secrets.GH_PAT }}"

View File

@ -8,7 +8,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v7 - uses: actions/checkout@v7
- name: Use Node.js - name: Use Node.js
uses: actions/setup-node@v7 uses: actions/setup-node@v6
- name: Install Dependencies - name: Install Dependencies
run: npm install run: npm install
- run: npm run eslint - run: npm run eslint
@ -21,7 +21,7 @@ jobs:
with: with:
github-token: ${{ secrets.GITHUB_TOKEN }} github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v7 uses: actions/setup-python@v6
with: with:
python-version: '3.x' python-version: '3.x'
- name: Install Dependencies - name: Install Dependencies

View File

@ -15,7 +15,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v7 - uses: actions/checkout@v7
- name: Use Node.js - name: Use Node.js
uses: actions/setup-node@v7 uses: actions/setup-node@v6
with: with:
node-version: '24' node-version: '24'
registry-url: 'https://registry.npmjs.org' registry-url: 'https://registry.npmjs.org'

View File

@ -14,7 +14,7 @@ jobs:
with: with:
repository: hexojs/hexo-starter repository: hexojs/hexo-starter
- name: Use Node.js - name: Use Node.js
uses: actions/setup-node@v7 uses: actions/setup-node@v6
- name: Install Dependencies - name: Install Dependencies
run: npm install run: npm install
- name: Install hexo-tag-embed - name: Install hexo-tag-embed

View File

@ -148,9 +148,9 @@ NexT send special thanks to these great services that sponsor our core infrastru
<a href="https://www.netlify.com"> <a href="https://www.netlify.com">
<picture> <picture>
<source media="(prefers-color-scheme: dark)" height="40" srcset="https://raw.githubusercontent.com/netlify/netlify-example-styles/main/public/images/logo-netlify-small-fullcolor-darkmode.svg"> <source media="(prefers-color-scheme: dark)" height="40" srcset="https://www.netlify.com/v3/img/components/full-logo-dark.svg">
<source media="(prefers-color-scheme: light)" height="40" srcset="https://raw.githubusercontent.com/netlify/netlify-example-styles/main/public/images/logo-netlify-small-fullcolor-lightmode.svg"> <source media="(prefers-color-scheme: light)" height="40" srcset="https://www.netlify.com/v3/img/components/full-logo-light.svg">
<img alt="Netlify Logo" height="40" src="https://raw.githubusercontent.com/netlify/netlify-example-styles/main/public/images/logo-netlify-small-fullcolor-lightmode.svg"> <img alt="Netlify Logo" height="40" src="https://www.netlify.com/v3/img/components/full-logo-light.svg">
</picture> </picture>
</a> </a>

View File

@ -88,6 +88,7 @@ open_graph:
#twitter_id: <twitter:creator> #twitter_id: <twitter:creator>
#twitter_site: <twitter:site> #twitter_site: <twitter:site>
#twitter_image: <twitter:image> #twitter_image: <twitter:image>
#google_plus: <g+:profile_link>
#fb_admins: <fb:admin_id> #fb_admins: <fb:admin_id>
#fb_app_id: <fb:app_id> #fb_app_id: <fb:app_id>
@ -104,7 +105,7 @@ menu:
#home: / || fa fa-home #home: / || fa fa-home
#about: /about/ || fa fa-user #about: /about/ || fa fa-user
#tags: /tags/ || fa fa-tags #tags: /tags/ || fa fa-tags
#categories: /categories/ || fa fa-layer-group #categories: /categories/ || fa fa-th
#archives: /archives/ || fa fa-archive #archives: /archives/ || fa fa-archive
#schedule: /schedule/ || fa fa-calendar #schedule: /schedule/ || fa fa-calendar
#sitemap: /sitemap.xml || fa fa-sitemap #sitemap: /sitemap.xml || fa fa-sitemap
@ -364,6 +365,8 @@ theme_color:
body_scrollbar: body_scrollbar:
# Place the scrollbar over the content. # Place the scrollbar over the content.
overlay: false overlay: false
# Reserving space for the scrollbar gutter even if the content is not overflowing.
stable: false
codeblock: codeblock:
# Code Highlight theme # Code Highlight theme
@ -572,8 +575,6 @@ math:
enable: false enable: false
# Available values: none | ams | all # Available values: none | ams | all
tags: none tags: none
# Available values: overflow | scroll | scale | truncate | linebreak
display_overflow: scroll
katex: katex:
enable: false enable: false
@ -599,6 +600,10 @@ fancybox: false
# For more information: https://medium-zoom.francoischalifour.com # For more information: https://medium-zoom.francoischalifour.com
mediumzoom: false mediumzoom: false
# Vanilla JavaScript plugin for lazyloading images.
# For more information: https://apoorv.pro/lozad.js/demo/
lazyload: false
# Automatically insert whitespace between CJK and half-width characters. # Automatically insert whitespace between CJK and half-width characters.
# For more information: https://github.com/vinta/pangu.js # For more information: https://github.com/vinta/pangu.js
# Server-side plugin: https://github.com/next-theme/hexo-pangu # Server-side plugin: https://github.com/next-theme/hexo-pangu
@ -809,13 +814,13 @@ plausible:
script_url: # https://plausible.io/js/script.js script_url: # https://plausible.io/js/script.js
site_domain: # www.example.com site_domain: # www.example.com
# Show the number of visitors to each article using the Firestore REST API. # Another tool to show number of visitors to each article.
# Visit https://console.firebase.google.com/ to get projectId. # Visit https://console.firebase.google.com/u/0/ to get apiKey and projectId.
# Visit https://firebase.google.com/docs/firestore/use-rest-api for more information. # Visit https://firebase.google.com/docs/firestore/ to get more information about firestore.
# Anonymous requests are authorized by your Firestore Security Rules.
firestore: firestore:
enable: false enable: false
collection: articles # Required, a string collection name to access firestore database collection: articles # Required, a string collection name to access firestore database
apiKey: # Required
projectId: # Required projectId: # Required
# Show Views / Visitors of the website / page with busuanzi. # Show Views / Visitors of the website / page with busuanzi.

View File

@ -1,12 +1,17 @@
# This file is automatically generated # This file is automatically generated
# See https://github.com/next-theme/plugins # See https://github.com/next-theme/plugins
anime:
name: animejs
version: 3.2.1
file: lib/anime.min.js
integrity: sha256-XL2inqUJaslATFnHdJOi9GfQ60on8Wx1C2H8DYiN1xY=
fontawesome: fontawesome:
name: '@fortawesome/fontawesome-free' name: '@fortawesome/fontawesome-free'
version: 7.3.1 version: 7.0.0
file: css/all.min.css file: css/all.min.css
alias: font-awesome alias: font-awesome
integrity: sha256-4Lad8m4ZWW1Lgb9+sMVLYEfnIh7BjV1NQMEe79Pviks= integrity: sha256-VHqXKFhhMxcpubYf9xiWdCiojEbY9NexQ4jh8AxbvcM=
prism: prism:
name: prismjs name: prismjs
version: 1.30.0 version: 1.30.0
@ -27,21 +32,21 @@ prism_line_numbers:
integrity: sha256-9cmf7tcLdXpKsPi/2AWE93PbZpTp4M4tqzFk+lWomjU= integrity: sha256-9cmf7tcLdXpKsPi/2AWE93PbZpTp4M4tqzFk+lWomjU=
mathjax: mathjax:
name: mathjax name: mathjax
version: 4.1.3 version: 3.2.2
file: tex-mml-chtml.js file: es5/tex-mml-chtml.js
integrity: sha256-ihPtDDm7pTmSF9ViEsItlvsrBTsNkz0MOXMchzOwCQw= integrity: sha256-MASABpB4tYktI2Oitl4t+78w/lyA+D7b/s9GEP0JOGI=
katex: katex:
name: katex name: katex
version: 0.18.4 version: 0.16.9
file: dist/katex.min.css file: dist/katex.min.css
alias: KaTeX alias: KaTeX
integrity: sha256-GAwtd9Q019pR1mJcUKlk1P1v29ubyHlqCgFsMMSZMfs= integrity: sha256-UF1fgpAiu3tPJN/uCqEUHNe7pnr+QR0SQDNfgglgtcM=
copy_tex_js: copy_tex_js:
name: katex name: katex
version: 0.18.4 version: 0.16.9
file: dist/contrib/copy-tex.min.js file: dist/contrib/copy-tex.min.js
alias: KaTeX alias: KaTeX
integrity: sha256-xXHEm1IMuOkEAoczgoayF4Pjm0dmYLi1bZ4cA37q6v8= integrity: sha256-Us54+rSGDSTvIhKKUs4kygE2ipA0RXpWWh0/zLqw3bs=
pjax: pjax:
name: '@next-theme/pjax' name: '@next-theme/pjax'
version: 0.6.0 version: 0.6.0
@ -50,16 +55,16 @@ pjax:
integrity: sha256-vxLn1tSKWD4dqbMRyv940UYw4sXgMtYcK6reefzZrao= integrity: sha256-vxLn1tSKWD4dqbMRyv940UYw4sXgMtYcK6reefzZrao=
fancybox_js: fancybox_js:
name: '@fancyapps/ui' name: '@fancyapps/ui'
version: 6.1.14 version: 5.0.31
file: dist/fancybox/fancybox.umd.js file: dist/fancybox/fancybox.umd.js
alias: fancyapps-ui alias: fancyapps-ui
integrity: sha256-QnJV4HT5r8i89ovZGRD86iynZqHerEbT2xt69vDM36I= integrity: sha256-a+H7FYzJv6oU2hfsfDGM2Ohw/cR9v+hPfxHCLdmCrE8=
fancybox_css: fancybox_css:
name: '@fancyapps/ui' name: '@fancyapps/ui'
version: 6.1.14 version: 5.0.31
file: dist/fancybox/fancybox.css file: dist/fancybox/fancybox.css
alias: fancyapps-ui alias: fancyapps-ui
integrity: sha256-mdaMxrItEW6hMIVJJNPKuX0B7KCSJQVEv3mVY4N2we4= integrity: sha256-gkQVf8UKZgQ0HyuxL/VnacadJ+D2Kox2TCEBuNQg5+w=
mediumzoom: mediumzoom:
name: medium-zoom name: medium-zoom
version: 1.1.0 version: 1.1.0
@ -73,24 +78,24 @@ lazyload:
integrity: sha256-mOFREFhqmHeQbXpK2lp4nA3qooVgACfh88fpJftLBbc= integrity: sha256-mOFREFhqmHeQbXpK2lp4nA3qooVgACfh88fpJftLBbc=
pangu: pangu:
name: pangu name: pangu
version: 7.2.1 version: 7.2.0
file: dist/browser/pangu.umd.js file: dist/browser/pangu.umd.js
integrity: sha256-kRsxecBnwG6mULFPe2E6UKnNm8XfDOuDwCOprjLZuX8= integrity: sha256-JnmRRnJK7DC6RQJbAJb6AXOM9OmWzS6z8eYultk/48Y=
quicklink: quicklink:
name: quicklink name: quicklink
version: 3.0.2 version: 3.0.1
file: dist/quicklink.umd.js file: dist/quicklink.umd.js
integrity: sha256-Qo2BTvlsBVXIr5fEHp8xlBpUmkbDINr5cQZeSfMTES8= integrity: sha256-44BednzIpUeQJcY8qtLyarFu0UCCTbgmWOvaoehiFQQ=
disqusjs_js: disqusjs_js:
name: disqusjs name: disqusjs
version: 3.2.1 version: 3.1.0
file: dist/browser/disqusjs.es2015.umd.min.js file: dist/browser/disqusjs.es2015.umd.min.js
integrity: sha256-6a5jzAWTjd18Nq6rZZn1x9hZLDZAilRDnd0arUFteBM= integrity: sha256-gdbBT62HLzqWTnXQED3WQL/ItFCuWsA5gd6dM/wgdiE=
disqusjs_css: disqusjs_css:
name: disqusjs name: disqusjs
version: 3.2.1 version: 3.1.0
file: dist/browser/styles/disqusjs.css file: dist/browser/styles/disqusjs.css
integrity: sha256-9SkCf6N7ls5uSFyENCeHM+m7Qv3lHH9Eg/CxdCajarI= integrity: sha256-E4RzceSomICFY1PT/yDQrN8DsLSwdScDEiQAevHabp0=
gitalk_js: gitalk_js:
name: gitalk name: gitalk
version: 1.8.0 version: 1.8.0
@ -101,11 +106,21 @@ gitalk_css:
version: 1.8.0 version: 1.8.0
file: dist/gitalk.css file: dist/gitalk.css
integrity: sha256-AJnUHL7dBv6PGaeyPQJcgQPDjt/Hn/PvYZde1iqfp8U= integrity: sha256-AJnUHL7dBv6PGaeyPQJcgQPDjt/Hn/PvYZde1iqfp8U=
firebase_app:
name: firebase
version: 12.2.1
file: firebase-app-compat.js
integrity: sha256-d21UgtIcA6c6qwr8nWk6lxs/KrpbWhknQN7qBjWA2Kk=
firebase_firestore:
name: firebase
version: 12.2.1
file: firebase-firestore-compat.js
integrity: sha256-leHatffkFuVGIg7ABaA5BDsqsEUtktL4tftb3OdQfg0=
algolia_search: algolia_search:
name: algoliasearch name: algoliasearch
version: 5.57.0 version: 5.36.0
file: dist/lite/builds/browser.umd.js file: dist/lite/builds/browser.umd.js
integrity: sha256-NJrnXIMFVRGMM2IieD83YpTvsPPIFKKyj4K/MNyTRQU= integrity: sha256-o49zoHLDPBm9WLg6AiliAVi3axCRTqe3ltTxLvhzAVg=
local_search: local_search:
name: hexo-generator-searchdb name: hexo-generator-searchdb
version: 1.5.0 version: 1.5.0
@ -149,11 +164,11 @@ creative_commons:
alias: creativecommons-vocabulary alias: creativecommons-vocabulary
wavedrom: wavedrom:
name: wavedrom name: wavedrom
version: 3.6.2 version: 3.5.0
file: wavedrom.min.js file: wavedrom.min.js
integrity: sha256-3hPWxTqi1K1sjmlESiOTH6lfGwYmz8/SFqoAcPxrjwg= integrity: sha256-INLAoJc6quTNfiMWkGZniYO2cxE8mHpddnLow1m6RFs=
wavedrom_skin: wavedrom_skin:
name: wavedrom name: wavedrom
version: 3.6.2 version: 3.5.0
file: skins/default.js file: skins/default.js
integrity: sha256-bUKjUpel+qLgflZd3EfdbhIorwRYgSkaZKM2s/Bi0VI= integrity: sha256-fduc/Zszk5ezWws2uInY/ALWVmIrmV6VTgXbsYSReFI=

View File

@ -147,9 +147,9 @@ Also, we welcome Issue or PR to our [official-plugins][official-plugins-url].
<a href="https://www.netlify.com"> <a href="https://www.netlify.com">
<picture> <picture>
<source media="(prefers-color-scheme: dark)" height="40" srcset="https://raw.githubusercontent.com/netlify/netlify-example-styles/main/public/images/logo-netlify-small-fullcolor-darkmode.svg"> <source media="(prefers-color-scheme: dark)" height="40" srcset="https://www.netlify.com/v3/img/components/full-logo-dark.svg">
<source media="(prefers-color-scheme: light)" height="40" srcset="https://raw.githubusercontent.com/netlify/netlify-example-styles/main/public/images/logo-netlify-small-fullcolor-lightmode.svg"> <source media="(prefers-color-scheme: light)" height="40" srcset="https://www.netlify.com/v3/img/components/full-logo-light.svg">
<img alt="Netlify Logo" height="40" src="https://raw.githubusercontent.com/netlify/netlify-example-styles/main/public/images/logo-netlify-small-fullcolor-lightmode.svg"> <img alt="Netlify Logo" height="40" src="https://www.netlify.com/v3/img/components/full-logo-light.svg">
</picture> </picture>
</a> </a>

View File

@ -148,9 +148,9 @@ NexT 特别感谢这些支持我们核心基础设施的优质服务:
<a href="https://www.netlify.com"> <a href="https://www.netlify.com">
<picture> <picture>
<source media="(prefers-color-scheme: dark)" height="40" srcset="https://raw.githubusercontent.com/netlify/netlify-example-styles/main/public/images/logo-netlify-small-fullcolor-darkmode.svg"> <source media="(prefers-color-scheme: dark)" height="40" srcset="https://www.netlify.com/v3/img/components/full-logo-dark.svg">
<source media="(prefers-color-scheme: light)" height="40" srcset="https://raw.githubusercontent.com/netlify/netlify-example-styles/main/public/images/logo-netlify-small-fullcolor-lightmode.svg"> <source media="(prefers-color-scheme: light)" height="40" srcset="https://www.netlify.com/v3/img/components/full-logo-light.svg">
<img alt="Netlify Logo" height="40" src="https://raw.githubusercontent.com/netlify/netlify-example-styles/main/public/images/logo-netlify-small-fullcolor-lightmode.svg"> <img alt="Netlify Logo" height="40" src="https://www.netlify.com/v3/img/components/full-logo-light.svg">
</picture> </picture>
</a> </a>

View File

@ -10,7 +10,7 @@
{{- meta_generator() }} {{- meta_generator() }}
{%- endif %} {%- endif %}
{%- if theme.preconnect %} {%- if config.preconnect %}
<link rel="preload" href="{{ url_for(theme.css) }}/main.css" as="style"> <link rel="preload" href="{{ url_for(theme.css) }}/main.css" as="style">
{%- endif %} {%- endif %}
{{ next_pre() }} {{ next_pre() }}

View File

@ -1,9 +1,13 @@
<div class="site-brand-container"> <div class="site-brand-container">
<div class="site-nav-search popup-trigger" aria-label="{{ __('menu.search') }}" role="button"> <div class="site-nav-toggle">
{%- if theme.algolia_search.enable or theme.local_search.enable %} <div class="toggle" aria-label="{{ __('accessibility.nav_toggle') }}" role="button">
<i class="fa fa-search fa-fw fa-lg"></i> {%- if theme.menu %}
<span class="toggle-line"></span>
<span class="toggle-line"></span>
<span class="toggle-line"></span>
{%- endif %} {%- endif %}
</div> </div>
</div>
<div class="site-meta"> <div class="site-meta">
{%- if theme.custom_logo and theme.scheme === 'Muse' %} {%- if theme.custom_logo and theme.scheme === 'Muse' %}
@ -23,9 +27,11 @@
{%- endif %} {%- endif %}
</div> </div>
<div class="site-nav-toggle" aria-label="{{ __('accessibility.nav_toggle') }}" role="button"> <div class="site-nav-right">
{%- if theme.menu %} <div class="toggle popup-trigger" aria-label="{{ __('menu.search') }}" role="button">
<i class="fa fa-ellipsis fa-fw fa-lg"></i> {%- if theme.algolia_search.enable or theme.local_search.enable %}
<i class="fa fa-search fa-fw fa-lg"></i>
{%- endif %} {%- endif %}
</div> </div>
</div> </div>
</div>

View File

@ -88,7 +88,7 @@
{%- if config.symbols_count_time.symbols %} {%- if config.symbols_count_time.symbols %}
<span class="post-meta-item" title="{{ __('symbols_count_time.count') }}"> <span class="post-meta-item" title="{{ __('symbols_count_time.count') }}">
<span class="post-meta-item-icon"> <span class="post-meta-item-icon">
<i class="far fa-file-lines"></i> <i class="far fa-file-word"></i>
</span> </span>
<span class="post-meta-item-text">{{ __('symbols_count_time.count') + __('symbol.colon') }}</span> <span class="post-meta-item-text">{{ __('symbols_count_time.count') + __('symbol.colon') }}</span>
<span>{{ symbolsCount(post) }}</span> <span>{{ symbolsCount(post) }}</span>

View File

@ -1,7 +1,5 @@
{%- if theme.algolia_search.enable or theme.local_search.enable %} {%- if theme.algolia_search.enable or theme.local_search.enable %}
{# Keep the popup out of the render tree to prevent Safari from animating it before main.css is applied. <div class="search-pop-overlay">
When main.css loads slowly, the unstyled search popup briefly appears and shrinks away. #}
<template id="search-popup-template"><div class="search-pop-overlay">
<div class="popup search-popup"> <div class="popup search-popup">
<div class="search-header"> <div class="search-header">
<span class="search-icon"> <span class="search-icon">
@ -22,5 +20,5 @@
</div> </div>
</div> </div>
</div> </div>
</div></template> </div>
{%- endif %} {%- endif %}

View File

@ -6,6 +6,7 @@
<span class="toggle-line"></span> <span class="toggle-line"></span>
<span class="toggle-line"></span> <span class="toggle-line"></span>
</div> </div>
<div class="sidebar-dimmer"></div>
{%- endif %} {%- endif %}
{%- if theme.back2top.enable and not theme.back2top.sidebar %} {%- if theme.back2top.enable and not theme.back2top.sidebar %}

View File

@ -1,5 +1,4 @@
{{ next_data('mathjax', theme.math.mathjax, { {{ next_data('mathjax', theme.math.mathjax, {
js: theme.vendors.mathjax, js: theme.vendors.mathjax
font_path: theme.vendors.mathjax_font_path
}) }} }) }}
{{ next_js('third-party/math/mathjax.js') }} {{ next_js('third-party/math/mathjax.js') }}

View File

@ -1,7 +1,6 @@
{%- if theme.firestore.enable %} {%- if theme.firestore.enable %}
{{ next_data('firestore', { {{ next_vendors('firebase_app') }}
collection: theme.firestore.collection, {{ next_vendors('firebase_firestore') }}
projectId: theme.firestore.projectId {{ next_data('firestore', theme.firestore) }}
}) }}
{{ next_js('third-party/statistics/firestore.js') }} {{ next_js('third-party/statistics/firestore.js') }}
{%- endif %} {%- endif %}

View File

@ -1,6 +1,6 @@
{ {
"name": "hexo-theme-next", "name": "hexo-theme-next",
"version": "8.29.0", "version": "8.28.0",
"description": "Elegant and powerful theme for Hexo.", "description": "Elegant and powerful theme for Hexo.",
"main": "package.json", "main": "package.json",
"files": [ "files": [
@ -36,13 +36,13 @@
"homepage": "https://theme-next.js.org", "homepage": "https://theme-next.js.org",
"devDependencies": { "devDependencies": {
"@next-theme/eslint-config": "0.0.5", "@next-theme/eslint-config": "0.0.5",
"c8": "12.0.0", "c8": "11.0.0",
"chai": "6.2.2", "chai": "6.2.2",
"eslint": "10.8.0", "eslint": "10.6.0",
"hexo": "8.1.2", "hexo": "8.1.2",
"hexo-renderer-marked": "7.0.1", "hexo-renderer-marked": "7.0.1",
"js-yaml": "4.3.0", "js-yaml": "4.2.0",
"mocha": "11.8.0", "mocha": "11.7.6",
"stylelint": "16.26.1", "stylelint": "16.26.1",
"stylelint-stylus": "1.0.0" "stylelint-stylus": "1.0.0"
} }

View File

@ -10,7 +10,7 @@ module.exports = hexo => {
hexo.log.warn('Documentation: https://theme-next.js.org/docs/getting-started/configuration.html'); hexo.log.warn('Documentation: https://theme-next.js.org/docs/getting-started/configuration.html');
} }
const { body_scrollbar, cache, changyan, growingio_analytics, language_switcher, lazyload, leancloud_visitors } = hexo.theme.config; const { cache, changyan, growingio_analytics, language_switcher, leancloud_visitors } = hexo.theme.config;
const warning = function(...args) { const warning = function(...args) {
hexo.log.warn(`Since ${args[0]} is turned on, the ${args[1]} is disabled to avoid potential hazards.`); hexo.log.warn(`Since ${args[0]} is turned on, the ${args[1]} is disabled to avoid potential hazards.`);
}; };
@ -29,14 +29,6 @@ module.exports = hexo => {
if (growingio_analytics) { if (growingio_analytics) {
hexo.log.warn('growingio_analytics is deprecated. Please migrate to another analytics provider.'); hexo.log.warn('growingio_analytics is deprecated. Please migrate to another analytics provider.');
} }
if (body_scrollbar?.stable) {
hexo.log.warn('The `body_scrollbar.stable` theme option is deprecated and will be removed in the next version.');
}
if (lazyload) {
hexo.log.warn('The `lazyload` theme option is deprecated and will be removed in the next version.');
hexo.log.warn('Use `marked.lazyload: true` in Hexo `_config.yml` instead. hexo-renderer-marked will add `loading="lazy"` to images.');
hexo.log.warn('Documentation: https://github.com/hexojs/hexo-renderer-marked#options');
}
if (leancloud_visitors?.enable) { if (leancloud_visitors?.enable) {
hexo.log.warn('LeanCloud will stop public services on 2027-01-12. Please migrate your data as soon as possible.'); hexo.log.warn('LeanCloud will stop public services on 2027-01-12. Please migrate your data as soon as possible.');
hexo.log.warn('Announcement: https://docs.leancloud.app/sdk/announcements/sunset-announcement/'); hexo.log.warn('Announcement: https://docs.leancloud.app/sdk/announcements/sunset-announcement/');

View File

@ -57,10 +57,5 @@ module.exports = hexo => {
// Omit integrity for local assets. // Omit integrity for local assets.
integrity: plugins === 'local' ? undefined : value.integrity integrity: plugins === 'local' ? undefined : value.integrity
}; };
if (key === 'mathjax') {
vendors.mathjax_font_path = plugins === 'local'
? url_for.call(hexo, 'lib/@mathjax/mathjax-newcm-font')
: undefined;
}
} }
}; };

View File

@ -19,9 +19,7 @@ hexo.extend.helper.register('next_inject', function(point) {
hexo.extend.helper.register('next_js', function(file, { hexo.extend.helper.register('next_js', function(file, {
pjax = false, pjax = false,
module = false, module = false
async = false,
defer = !async
} = {}) { } = {}) {
const { next_version } = this; const { next_version } = this;
const { internal, custom_cdn_url } = this.theme.vendors; const { internal, custom_cdn_url } = this.theme.vendors;
@ -34,21 +32,18 @@ hexo.extend.helper.register('next_js', function(file, {
custom : custom_cdn_url custom : custom_cdn_url
}); });
const src = links[internal] || links.local; const src = links[internal] || links.local;
return `<script ${pjax ? 'data-pjax ' : ''}${module ? 'type="module" ' : ''}src="${src}"${defer ? ' defer' : ''}${async ? ' async' : ''}></script>`; return `<script ${pjax ? 'data-pjax ' : ''}${module ? 'type="module" ' : ''}src="${src}" defer></script>`;
}); });
hexo.extend.helper.register('next_vendors', function(name, { hexo.extend.helper.register('next_vendors', function(name) {
async = false,
defer = !async
} = {}) {
const { url, integrity } = this.theme.vendors[name]; const { url, integrity } = this.theme.vendors[name];
const type = url.endsWith('css') ? 'css' : 'js'; const type = url.endsWith('css') ? 'css' : 'js';
if (type === 'css') { if (type === 'css') {
if (integrity) return `<link rel="stylesheet" href="${url}" integrity="${integrity}" crossorigin="anonymous">`; if (integrity) return `<link rel="stylesheet" href="${url}" integrity="${integrity}" crossorigin="anonymous">`;
return `<link rel="stylesheet" href="${url}">`; return `<link rel="stylesheet" href="${url}">`;
} }
if (integrity) return `<script src="${url}" integrity="${integrity}" crossorigin="anonymous"${defer ? ' defer' : ''}${async ? ' async' : ''}></script>`; if (integrity) return `<script src="${url}" integrity="${integrity}" crossorigin="anonymous" defer></script>`;
return `<script src="${url}"${defer ? ' defer' : ''}${async ? ' async' : ''}></script>`; return `<script src="${url}" defer></script>`;
}); });
hexo.extend.helper.register('next_data', function(name, ...data) { hexo.extend.helper.register('next_data', function(name, ...data) {

View File

@ -4,7 +4,7 @@
hexo.extend.helper.register('js_vendors', function() { hexo.extend.helper.register('js_vendors', function() {
const { config, theme } = this; const { config, theme } = this;
const vendors = []; const vendors = ['anime'];
if (theme.prism.enable && !config.prismjs.preprocess) { if (theme.prism.enable && !config.prismjs.preprocess) {
vendors.push('prism', 'prism_autoloader'); vendors.push('prism', 'prism_autoloader');
if (config.prismjs.line_number) { if (config.prismjs.line_number) {

View File

@ -28,21 +28,11 @@
} }
if (hexo-config('reward_settings.animation')) { if (hexo-config('reward_settings.animation')) {
span {
transform-origin: center bottom;
}
&:hover span { &:hover span {
animation: next-bounce 1.4s infinite ease-in-out; animation: next-roll .1s infinite linear;
// The animation may affect :hover of img in dark mode // The animation may affect :hover of img in dark mode
pointer-events: none; pointer-events: none;
} }
@media (prefers-reduced-motion: reduce) {
&:hover span {
animation: none;
}
}
} }
} }
@ -54,28 +44,12 @@
} }
} }
@keyframes next-bounce { @keyframes next-roll {
0%, 100% { from {
transform: translateY(0) scale(1); transform: rotateZ(30deg);
} }
12% { to {
transform: translateY(0) scale(1.04, .96); transform: rotateZ(-30deg);
}
36% {
transform: translateY(-7px) scale(.98, 1.02);
}
58% {
transform: translateY(0) scale(1.03, .97);
}
72% {
transform: translateY(-2px) scale(1);
}
84% {
transform: translateY(0) scale(1);
} }
} }

View File

@ -1,5 +1,5 @@
if (hexo-config('math.mathjax.enable')) { if (hexo-config('math.mathjax.enable')) {
.has-jax { mjx-container[jax='CHTML'][display='true'], .has-jax {
overflow: auto hidden; overflow: auto hidden;
} }

View File

@ -12,10 +12,11 @@
.site-brand-container { .site-brand-container {
display: flex; display: flex;
flex-shrink: 0; flex-shrink: 0;
padding: 0 10px;
} }
.use-motion { .use-motion {
.column { .column, .site-brand-container .toggle {
opacity: 0; opacity: 0;
} }
} }

View File

@ -1,19 +1,20 @@
.site-nav-toggle, .site-nav-search { .site-nav-toggle, .site-nav-right {
color: var(--text-color);
display: none; display: none;
padding: 0 10px;
.fa {
transition: transform .5s;
}
+mobile() { +mobile() {
flex-column(); flex-column();
} }
}
.site-nav-on .site-nav-toggle .fa { .toggle {
transform: rotate(-90deg); color: var(--text-color);
padding: 10px;
width: 22px;
.toggle-line {
background: var(--text-color);
border-radius: 1px;
}
}
} }
.site-nav { .site-nav {

View File

@ -20,10 +20,16 @@
padding: 20px 0; padding: 20px 0;
} }
.site-nav-toggle, .site-nav-search { .site-nav-toggle, .site-nav-right {
color: white;
+tablet() { +tablet() {
flex-column(); flex-column();
} }
.toggle {
color: white;
.toggle-line {
background: white;
}
}
} }

View File

@ -15,6 +15,7 @@ body { margin-top: 2rem; }
} }
.use-motion .column, .use-motion .column,
.use-motion .site-brand-container .toggle,
.use-motion .footer { opacity: initial; } .use-motion .footer { opacity: initial; }
.use-motion .site-title, .use-motion .site-title,

View File

@ -1,4 +1,4 @@
/* global NexT, CONFIG */ /* global CONFIG */
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
'use strict'; 'use strict';
@ -13,7 +13,12 @@ document.addEventListener('DOMContentLoaded', () => {
// If the page opens with a specific hash, just jump out // If the page opens with a specific hash, just jump out
if (!isNaN(top) && location.hash === '') { if (!isNaN(top) && location.hash === '') {
// Auto scroll to the position // Auto scroll to the position
NexT.utils.scrollTo(window, top); window.anime({
targets : document.scrollingElement,
duration : 200,
easing : 'linear',
scrollTop: top
});
} }
}; };
// Register everything // Register everything
@ -31,25 +36,17 @@ document.addEventListener('DOMContentLoaded', () => {
// Save the position by clicking the icon // Save the position by clicking the icon
link.addEventListener('click', () => { link.addEventListener('click', () => {
doSaveScroll(); doSaveScroll();
if (typeof link.animate !== 'function') { window.anime({
link.style.top = '-30px'; targets : link,
setTimeout(() => {
link.style.top = '';
}, 600);
return;
}
const animation = link.animate([{}, { top: '-30px' }], {
duration: 200, duration: 200,
easing : 'linear', easing : 'linear',
fill : 'forwards' top : -30,
}); complete: () => {
animation.finished.then(() => {
link.style.top = '-30px';
animation.cancel();
setTimeout(() => { setTimeout(() => {
link.style.top = ''; link.style.top = '';
}, 400); }, 400);
}).catch(() => {}); }
});
}); });
scrollToMark(); scrollToMark();
document.addEventListener('pjax:success', scrollToMark); document.addEventListener('pjax:success', scrollToMark);

View File

@ -14,38 +14,16 @@ NexT.motion.integrator = {
return this; return this;
}, },
bootstrap() { bootstrap() {
if (typeof Element.prototype.animate !== 'function') {
document.body.classList.remove('use-motion');
CONFIG.motion.enable = false;
return;
}
if (!CONFIG.motion.async) this.queue = [this.queue.flat()]; if (!CONFIG.motion.async) this.queue = [this.queue.flat()];
this.queue.forEach(sequence => this.schedule(sequence)); this.queue.forEach(sequence => {
}, const timeline = window.anime.timeline({
schedule(sequence) { duration: CONFIG.motion?.duration ?? 200,
let cursor = 0; easing : 'linear'
});
sequence.forEach(item => { sequence.forEach(item => {
const duration = item.duration ?? CONFIG.motion?.duration ?? 200; if (item.deltaT) timeline.add(item, item.deltaT);
const start = Math.max(0, cursor - (item.overlap ?? 0)); else timeline.add(item);
const end = start + duration;
cursor = Math.max(cursor, end);
if (item.styles) {
const targets = typeof item.targets === 'string' ? document.querySelectorAll(item.targets) : [item.targets].filter(Boolean);
targets.forEach(target => {
const animation = target.animate([{}, item.styles], {
delay : start,
duration,
easing: 'linear',
fill : 'forwards'
}); });
animation.finished.then(() => {
Object.assign(target.style, item.styles);
animation.cancel();
}).catch(() => {});
});
}
if (item.complete) setTimeout(item.complete, end);
}); });
} }
}; };
@ -57,17 +35,18 @@ NexT.motion.middleWares = {
function getMistLineSettings(targets) { function getMistLineSettings(targets) {
sequence.push({ sequence.push({
targets, targets,
styles : { transform: 'scaleX(1)' }, scaleX : [0, 1],
duration: 500, duration: 500,
overlap : 200 deltaT : '-=200'
}); });
} }
function pushToSequence(targets, sequenceQueue = false) { function pushToSequence(targets, sequenceQueue = false) {
sequence.push({ sequence.push({
targets, targets,
styles : { opacity: 1, top: '0px' }, opacity: 1,
overlap: sequenceQueue ? 200 : 0 top : 0,
deltaT : sequenceQueue ? '-=200' : '-=0'
}); });
} }
@ -85,7 +64,7 @@ NexT.motion.middleWares = {
sequence.push({ sequence.push({
targets, targets,
complete: () => targets.classList.add('animated', menuItemTransition), complete: () => targets.classList.add('animated', menuItemTransition),
overlap : 200 deltaT : '-=200'
}); });
}); });
} }
@ -113,7 +92,7 @@ NexT.motion.middleWares = {
sequence.push({ sequence.push({
targets, targets,
complete: () => targets.classList.add('animated', animation), complete: () => targets.classList.add('animated', animation),
overlap : 100 deltaT : '-=100'
}); });
}); });
} }
@ -122,7 +101,7 @@ NexT.motion.middleWares = {
sequence.push({ sequence.push({
targets, targets,
complete: () => targets.classList.add('animated', post_block), complete: () => targets.classList.add('animated', post_block),
overlap : 100 deltaT : '-=100'
}); });
animate(coll_header, targets.querySelectorAll('.collection-header')); animate(coll_header, targets.querySelectorAll('.collection-header'));
animate(post_header, targets.querySelectorAll('.post-header')); animate(post_header, targets.querySelectorAll('.post-header'));
@ -144,7 +123,7 @@ NexT.motion.middleWares = {
sequence.push({ sequence.push({
targets, targets,
complete: () => targets.classList.add('animated', sidebarTransition), complete: () => targets.classList.add('animated', sidebarTransition),
overlap : 100 deltaT : '-=100'
}); });
}); });
} }
@ -154,7 +133,7 @@ NexT.motion.middleWares = {
footer() { footer() {
return [{ return [{
targets: document.querySelector('.footer'), targets: document.querySelector('.footer'),
styles : { opacity: 1 } opacity: 1
}]; }];
} }
}; };

View File

@ -9,7 +9,8 @@ NexT.boot.registerEvents = function() {
NexT.utils.updateFooterPosition(); NexT.utils.updateFooterPosition();
// Mobile top menu bar. // Mobile top menu bar.
document.querySelector('.site-nav-toggle').addEventListener('click', event => { document.querySelector('.site-nav-toggle .toggle').addEventListener('click', event => {
event.currentTarget.classList.toggle('toggle-close');
const siteNav = document.querySelector('.site-nav'); const siteNav = document.querySelector('.site-nav');
if (!siteNav) return; if (!siteNav) return;
siteNav.style.setProperty('--scroll-height', siteNav.scrollHeight + 'px'); siteNav.style.setProperty('--scroll-height', siteNav.scrollHeight + 'px');

View File

@ -26,8 +26,7 @@ const pjax = new Pjax({
}, },
analytics: false, analytics: false,
cacheBust: false, cacheBust: false,
// scrollTo accepts a number (vertical offset), a number array ([x, y]), or false (do not scroll). scrollTo : !CONFIG.bookmark.enable
scrollTo : CONFIG.bookmark.enable ? false : 0
}); });
document.addEventListener('pjax:success', () => { document.addEventListener('pjax:success', () => {

View File

@ -3,17 +3,13 @@
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
const isRight = CONFIG.sidebar.position === 'right'; const isRight = CONFIG.sidebar.position === 'right';
// Create the dimmer dynamically to prevent an unwanted animation before main.css is applied.
const sidebarDimmer = document.createElement('div');
sidebarDimmer.className = 'sidebar-dimmer';
document.body.appendChild(sidebarDimmer);
const sidebarToggleMotion = { const sidebarToggleMotion = {
mouse: {}, mouse: {},
init() { init() {
window.addEventListener('mousedown', this.mousedownHandler.bind(this)); window.addEventListener('mousedown', this.mousedownHandler.bind(this));
window.addEventListener('mouseup', this.mouseupHandler.bind(this)); window.addEventListener('mouseup', this.mouseupHandler.bind(this));
sidebarDimmer.addEventListener('click', this.clickHandler.bind(this)); document.querySelector('.sidebar-dimmer').addEventListener('click', this.clickHandler.bind(this));
document.querySelector('.sidebar-toggle').addEventListener('click', this.clickHandler.bind(this)); document.querySelector('.sidebar-toggle').addEventListener('click', this.clickHandler.bind(this));
window.addEventListener('sidebar:show', this.showSidebar); window.addEventListener('sidebar:show', this.showSidebar);
window.addEventListener('sidebar:hide', this.hideSidebar); window.addEventListener('sidebar:hide', this.hideSidebar);

View File

@ -31,8 +31,5 @@ document.addEventListener('page:loaded', () => {
image.wrap(imageWrapLink); image.wrap(imageWrapLink);
}); });
// Disable hash handling to avoid conflicts with PJAX navigation. Fancybox.bind('[data-fancybox]');
Fancybox.bind('[data-fancybox]', {
Hash: false
});
}); });

View File

@ -4,12 +4,6 @@ document.addEventListener('page:loaded', () => {
if (!CONFIG.enableMath) return; if (!CONFIG.enableMath) return;
if (typeof MathJax === 'undefined') { if (typeof MathJax === 'undefined') {
const output = {
displayOverflow: CONFIG.mathjax.display_overflow
};
if (CONFIG.mathjax.font_path) {
output.fontPath = CONFIG.mathjax.font_path;
}
window.MathJax = { window.MathJax = {
tex: { tex: {
inlineMath: { '[+]': [['$', '$']] }, inlineMath: { '[+]': [['$', '$']] },
@ -18,7 +12,7 @@ document.addEventListener('page:loaded', () => {
options: { options: {
renderActions: { renderActions: {
insertedScript: [200, () => { insertedScript: [200, () => {
document.querySelectorAll('mjx-container:not([display])').forEach(node => { document.querySelectorAll('mjx-container').forEach(node => {
const target = node.parentNode; const target = node.parentNode;
if (target.nodeName.toLowerCase() === 'li') { if (target.nodeName.toLowerCase() === 'li') {
target.parentNode.classList.add('has-jax'); target.parentNode.classList.add('has-jax');
@ -26,8 +20,7 @@ document.addEventListener('page:loaded', () => {
}); });
}, '', false] }, '', false]
} }
}, }
output
}; };
NexT.utils.getScript(CONFIG.mathjax.js, { NexT.utils.getScript(CONFIG.mathjax.js, {
attributes: { attributes: {

View File

@ -1,7 +1,6 @@
/* global CONFIG, NexT, pjax */ /* global CONFIG, NexT, pjax */
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
document.body.appendChild(document.querySelector('#search-popup-template').content.cloneNode(true));
const { indexName, appID, apiKey, hits } = CONFIG.algolia; const { indexName, appID, apiKey, hits } = CONFIG.algolia;
const client = window['algoliasearch/lite'].liteClient(appID, apiKey); const client = window['algoliasearch/lite'].liteClient(appID, apiKey);
@ -20,13 +19,16 @@ document.addEventListener('DOMContentLoaded', () => {
return result; return result;
}; };
let latestSearchId = 0; let isSearching = false;
let pendingQuery = null;
const searchAlgolia = async (searchText, page, searchId) => { const searchAlgolia = async (searchText, page = 0) => {
if (searchId !== latestSearchId) return; if (isSearching) {
container.setAttribute('aria-busy', 'true'); pendingQuery = { searchText, page };
return;
}
isSearching = true;
const startTime = Date.now(); const startTime = Date.now();
try {
const result = await client.search({ const result = await client.search({
requests: [{ requests: [{
indexName, indexName,
@ -39,8 +41,6 @@ document.addEventListener('DOMContentLoaded', () => {
highlightPostTag : '</mark>' highlightPostTag : '</mark>'
}] }]
}); });
if (searchId !== latestSearchId) return;
const data = result.results[0]; const data = result.results[0];
if (data.nbHits === 0) { if (data.nbHits === 0) {
container.innerHTML = '<div class="search-result-icon"><i class="far fa-frown fa-5x"></i></div>'; container.innerHTML = '<div class="search-result-icon"><i class="far fa-frown fa-5x"></i></div>';
@ -72,40 +72,30 @@ document.addEventListener('DOMContentLoaded', () => {
container.querySelectorAll('.page-number').forEach(element => { container.querySelectorAll('.page-number').forEach(element => {
element.addEventListener('click', async event => { element.addEventListener('click', async event => {
event.preventDefault(); event.preventDefault();
if (input.value.trim() !== searchText) return; await searchAlgolia(searchText, Number(element.dataset.index));
const searchId = ++latestSearchId;
await searchAlgolia(searchText, Number(element.dataset.index), searchId);
}); });
}); });
} }
} catch (error) { isSearching = false;
if (searchId !== latestSearchId) return; if (pendingQuery !== null && (pendingQuery.searchText !== searchText || pendingQuery.page !== page)) {
console.warn('Algolia search failed:', error); const { searchText, page } = pendingQuery;
container.innerHTML = '<div class="search-result-icon"><i class="far fa-frown fa-5x"></i></div>'; pendingQuery = null;
} finally { searchAlgolia(searchText, page);
if (searchId === latestSearchId) container.removeAttribute('aria-busy');
} }
}; };
const inputEventFunction = async searchId => { const inputEventFunction = async () => {
if (searchId !== latestSearchId) return;
const searchText = input.value.trim(); const searchText = input.value.trim();
if (searchText === '') return; if (searchText === '') {
container.innerHTML = '<div class="search-result-icon"><i class="fab fa-algolia fa-5x"></i></div>';
return;
}
// Algolia client will automatically cache the data for same queries // Algolia client will automatically cache the data for same queries
await searchAlgolia(searchText, 0, searchId); await searchAlgolia(searchText, 0);
}; };
const debouncedSearch = NexT.utils.debounce(inputEventFunction, 500); const debouncedSearch = NexT.utils.debounce(inputEventFunction, 500);
input.addEventListener('input', () => { input.addEventListener('input', debouncedSearch);
const searchId = ++latestSearchId;
if (input.value.trim() === '') {
container.removeAttribute('aria-busy');
container.innerHTML = '<div class="search-result-icon"><i class="fab fa-algolia fa-5x"></i></div>';
} else {
container.setAttribute('aria-busy', 'true');
}
debouncedSearch(searchId);
});
// Handle and trigger popup window // Handle and trigger popup window
document.querySelectorAll('.popup-trigger').forEach(element => { document.querySelectorAll('.popup-trigger').forEach(element => {

View File

@ -6,7 +6,6 @@ document.addEventListener('DOMContentLoaded', () => {
console.warn('`hexo-generator-searchdb` plugin is not installed!'); console.warn('`hexo-generator-searchdb` plugin is not installed!');
return; return;
} }
document.body.appendChild(document.querySelector('#search-popup-template').content.cloneNode(true));
const localSearch = new LocalSearch({ const localSearch = new LocalSearch({
path : CONFIG.path, path : CONFIG.path,
top_n_per_article: CONFIG.localsearch.top_n_per_article, top_n_per_article: CONFIG.localsearch.top_n_per_article,

View File

@ -1,76 +1,37 @@
/* global CONFIG */ /* global CONFIG, firebase */
firebase.initializeApp({
apiKey : CONFIG.firestore.apiKey,
projectId: CONFIG.firestore.projectId
});
(function() { (function() {
const database = `projects/${CONFIG.firestore.projectId}/databases/(default)`; const getCount = async (doc, increaseCount) => {
const documents = `${database}/documents`; // IncreaseCount will be false when not in article page
const api = `https://firestore.googleapis.com/v1/${documents}`; const d = await doc.get();
// Has no data, initialize count
const fetchFirestore = async (path, options) => { let count = d.exists ? d.data().count : 0;
const response = await fetch(`${api}${path}`, options); // If first view this article
if (response.status === 404) return null;
if (!response.ok) throw new Error(`Firestore request failed with status ${response.status}`);
return response.json();
};
const documentName = title => `${documents}/${CONFIG.firestore.collection}/${title}`;
const getValue = value => Number(value?.integerValue ?? value?.doubleValue ?? 0);
const getCount = async (title, increaseCount) => {
if (increaseCount) { if (increaseCount) {
const data = await fetchFirestore(':commit', { // Increase count
method : 'POST', count++;
headers: { doc.set({
'Content-Type': 'application/json' count
},
body: JSON.stringify({
writes: [{
transform: {
document : documentName(title),
fieldTransforms: [{
fieldPath: 'count',
increment: {
integerValue: '1'
}
}]
}
}]
})
}); });
return getValue(data.writeResults[0].transformResults[0]);
} }
return count;
const collection = encodeURIComponent(CONFIG.firestore.collection);
const document = await fetchFirestore(`/${collection}/${encodeURIComponent(title)}`);
return getValue(document?.fields?.count);
}; };
const getCounts = async titles => { const db = firebase.firestore();
if (titles.length === 0) return []; const articles = db.collection(CONFIG.firestore.collection);
const data = await fetchFirestore(':batchGet', {
method : 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
documents: titles.map(documentName),
mask : {
fieldPaths: ['count']
}
})
});
const counts = new Map(data.map(result => {
const document = result.found;
return [document?.name ?? result.missing, getValue(document?.fields?.count)];
}));
return titles.map(title => counts.get(documentName(title)) ?? 0);
};
document.addEventListener('page:loaded', async () => { document.addEventListener('page:loaded', async () => {
try {
if (CONFIG.page.isPost) { if (CONFIG.page.isPost) {
// Fix issue #118 // Fix issue #118
// https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent // https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent
const title = document.querySelector('.post-title').textContent.trim(); const title = document.querySelector('.post-title').textContent.trim();
const doc = articles.doc(title);
let increaseCount = CONFIG.hostname === location.hostname; let increaseCount = CONFIG.hostname === location.hostname;
if (localStorage.getItem(title)) { if (localStorage.getItem(title)) {
increaseCount = false; increaseCount = false;
@ -78,18 +39,19 @@
// Mark as visited // Mark as visited
localStorage.setItem(title, true); localStorage.setItem(title, true);
} }
const count = await getCount(title, increaseCount); const count = await getCount(doc, increaseCount);
document.querySelector('.firestore-visitors-count').innerText = count; document.querySelector('.firestore-visitors-count').innerText = count;
} else if (CONFIG.page.isHome) { } else if (CONFIG.page.isHome) {
const titles = [...document.querySelectorAll('.post-title')].map(element => element.textContent.trim()); const promises = [...document.querySelectorAll('.post-title')].map(element => {
const counts = await getCounts(titles); const title = element.textContent.trim();
const doc = articles.doc(title);
return getCount(doc);
});
const counts = await Promise.all(promises);
const metas = document.querySelectorAll('.firestore-visitors-count'); const metas = document.querySelectorAll('.firestore-visitors-count');
counts.forEach((val, idx) => { counts.forEach((val, idx) => {
metas[idx].innerText = val; metas[idx].innerText = val;
}); });
} }
} catch (error) {
console.warn('Failed to load Firestore visitor count:', error);
}
}); });
})(); })();

View File

@ -19,17 +19,6 @@ HTMLElement.prototype.wrap = function(wrapper) {
NexT.utils = { NexT.utils = {
scrollTo(target, top) {
if (window.CSS?.supports('scroll-behavior', 'smooth')) {
target.scrollTo({
top,
behavior: 'smooth'
});
} else {
target.scrollTo(0, top);
}
},
registerExtURL() { registerExtURL() {
document.querySelectorAll('span.exturl').forEach(element => { document.querySelectorAll('span.exturl').forEach(element => {
const link = document.createElement('a'); const link = document.createElement('a');
@ -204,7 +193,12 @@ NexT.utils = {
}, { passive: true }); }, { passive: true });
backToTop?.addEventListener('click', () => { backToTop?.addEventListener('click', () => {
NexT.utils.scrollTo(window, 0); window.anime({
targets : document.scrollingElement,
duration : 500,
easing : 'linear',
scrollTop: 0
});
}); });
}, },
@ -264,7 +258,12 @@ NexT.utils = {
}, 1000); }, 1000);
if (!CONFIG.stickytabs) return; if (!CONFIG.stickytabs) return;
const offset = nav.parentNode.getBoundingClientRect().top + window.scrollY + 10; const offset = nav.parentNode.getBoundingClientRect().top + window.scrollY + 10;
NexT.utils.scrollTo(window, offset); window.anime({
targets : document.scrollingElement,
duration : 500,
easing : 'linear',
scrollTop: offset
});
}); });
}); });
@ -312,8 +311,15 @@ NexT.utils = {
element.addEventListener('click', event => { element.addEventListener('click', event => {
event.preventDefault(); event.preventDefault();
const offset = target.getBoundingClientRect().top + window.scrollY; const offset = target.getBoundingClientRect().top + window.scrollY;
NexT.utils.scrollTo(window, offset); window.anime({
targets : document.scrollingElement,
duration : 500,
easing : 'linear',
scrollTop: offset,
complete : () => {
history.pushState(null, document.title, element.href); history.pushState(null, document.title, element.href);
}
});
}); });
return target; return target;
}); });
@ -336,6 +342,8 @@ NexT.utils = {
const target = navItemList[index]; const target = navItemList[index];
if (!target || target.classList.contains('active-current')) return; if (!target || target.classList.contains('active-current')) return;
const singleHeight = navItemList[navItemList.length - 1].offsetHeight;
nav.querySelectorAll('.active').forEach(navItem => { nav.querySelectorAll('.active').forEach(navItem => {
navItem.classList.remove('active', 'active-current'); navItem.classList.remove('active', 'active-current');
}); });
@ -348,13 +356,9 @@ NexT.utils = {
if (activateEle.classList.contains('nav-item')) { if (activateEle.classList.contains('nav-item')) {
activateEle.classList.add('active'); activateEle.classList.add('active');
} else { // .nav-child or .nav } else { // .nav-child or .nav
// Exclude nested lists so each item's own wrapped row is counted once. // scrollHeight isn't reliable for transitioning child items.
const listItemHeight = [...activateEle.children].reduce((height, navItem) => {
const navChild = [...navItem.children].find(element => element.classList.contains('nav-child'));
return height + navItem.getBoundingClientRect().height - (navChild?.getBoundingClientRect().height || 0);
}, 0);
// The last nav-item in a list has a margin-bottom of 5px. // The last nav-item in a list has a margin-bottom of 5px.
navChildHeight += listItemHeight + 5; navChildHeight += (singleHeight * activateEle.childElementCount) + 5;
activateEle.style.setProperty('--height', `${navChildHeight}px`); activateEle.style.setProperty('--height', `${navChildHeight}px`);
} }
activateEle = activateEle.parentElement; activateEle = activateEle.parentElement;
@ -363,7 +367,12 @@ NexT.utils = {
// Scrolling to center active TOC element if TOC content is taller then viewport. // Scrolling to center active TOC element if TOC content is taller then viewport.
const tocElement = document.querySelector(CONFIG.scheme === 'Pisces' || CONFIG.scheme === 'Gemini' ? '.sidebar-panel-container' : '.sidebar'); const tocElement = document.querySelector(CONFIG.scheme === 'Pisces' || CONFIG.scheme === 'Gemini' ? '.sidebar-panel-container' : '.sidebar');
if (!document.querySelector('.sidebar-toc-active')) return; if (!document.querySelector('.sidebar-toc-active')) return;
NexT.utils.scrollTo(tocElement, tocElement.scrollTop - (tocElement.offsetHeight / 2) + target.getBoundingClientRect().top - tocElement.getBoundingClientRect().top); window.anime({
targets : tocElement,
duration : 200,
easing : 'linear',
scrollTop: tocElement.scrollTop - (tocElement.offsetHeight / 2) + target.getBoundingClientRect().top - tocElement.getBoundingClientRect().top
});
}, },
updateSidebarPosition() { updateSidebarPosition() {

View File

@ -1,73 +0,0 @@
'use strict';
const helpers = {};
global.hexo = {
extend: {
helper: {
register(name, helper) {
helpers[name] = helper;
}
}
}
};
require('../../scripts/helpers/engine');
delete global.hexo;
describe('engine', () => {
const context = {
next_version: '8.28.0',
theme : {
js : 'js',
vendors: {
internal : 'local',
custom_cdn_url: '',
library : {
url: 'https://cdn.example.com/library.js'
},
secure_library: {
url : 'https://cdn.example.com/secure.js',
integrity: 'sha256-example'
},
stylesheet: {
url: 'https://cdn.example.com/library.css'
}
}
},
url_for(path) {
return '/' + path;
}
};
const nextJs = helpers.next_js.bind(context);
const nextVendors = helpers.next_vendors.bind(context);
it('keeps defer as the default for NexT scripts', () => {
nextJs('main.js').should.eql('<script src="/js/main.js" defer></script>');
});
it('supports custom loading options for NexT scripts', () => {
nextJs('main.js', { defer: false }).should.eql('<script src="/js/main.js"></script>');
nextJs('main.js', { async: true }).should.eql('<script src="/js/main.js" async></script>');
nextJs('main.js', { defer: true, async: true }).should.eql('<script src="/js/main.js" defer async></script>');
});
it('preserves existing NexT script options', () => {
nextJs('main.js', { pjax: true, module: true }).should.eql('<script data-pjax type="module" src="/js/main.js" defer></script>');
});
it('keeps defer as the default for vendor scripts', () => {
nextVendors('library').should.eql('<script src="https://cdn.example.com/library.js" defer></script>');
nextVendors('secure_library').should.eql('<script src="https://cdn.example.com/secure.js" integrity="sha256-example" crossorigin="anonymous" defer></script>');
});
it('supports custom loading options for vendor scripts', () => {
nextVendors('library', { defer: false }).should.eql('<script src="https://cdn.example.com/library.js"></script>');
nextVendors('library', { async: true }).should.eql('<script src="https://cdn.example.com/library.js" async></script>');
});
it('does not apply script loading options to stylesheets', () => {
nextVendors('stylesheet', { async: true }).should.eql('<link rel="stylesheet" href="https://cdn.example.com/library.css">');
});
});

View File

@ -1,7 +1,6 @@
'use strict'; 'use strict';
describe('Helpers', () => { describe('Helpers', () => {
require('./engine');
require('./font'); require('./font');
require('./next-url'); require('./next-url');
}); });