mirror of
https://github.com/next-theme/hexo-theme-next.git
synced 2026-09-14 06:50:20 +00:00
Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d25776ea38 | ||
|
|
3e4d43597d | ||
|
|
6037f68b7a | ||
|
|
6468f139cb | ||
|
|
d62fdfa759 | ||
|
|
96d34a0bb9 | ||
|
|
6560fa6dd6 | ||
|
|
66e3095a31 | ||
|
|
120d41d0fa | ||
|
|
4b249983ed | ||
|
|
ba0ab0e8f7 | ||
|
|
41c19bfc47 | ||
|
|
88cdd52f52 | ||
|
|
b3ac16962d | ||
|
|
5b71ed5eae | ||
|
|
5a476ca06f | ||
|
|
abf7ec2aeb | ||
|
|
f7f13675e5 | ||
|
|
db11583d55 | ||
|
|
abd6a790ba | ||
|
|
c0232764b3 | ||
|
|
091af0fd4f | ||
|
|
854e2006b6 | ||
|
|
5c7574fd3f | ||
|
|
4352696dba | ||
|
|
940b2263f6 | ||
|
|
8fa97a173a | ||
|
|
1b306b9824 | ||
|
|
d54ab89ea2 | ||
|
|
333b2c7908 |
2
.github/workflows/labeler.yml
vendored
2
.github/workflows/labeler.yml
vendored
@ -7,7 +7,7 @@ jobs:
|
||||
triage:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/labeler@v6
|
||||
- uses: actions/labeler@v7
|
||||
# https://github.com/peaceiris/actions-label-commenter#work-with-other-auto-label-actions
|
||||
with:
|
||||
repo-token: "${{ secrets.GH_PAT }}"
|
||||
|
||||
4
.github/workflows/linter.yml
vendored
4
.github/workflows/linter.yml
vendored
@ -8,7 +8,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v7
|
||||
- name: Install Dependencies
|
||||
run: npm install
|
||||
- run: npm run eslint
|
||||
@ -21,7 +21,7 @@ jobs:
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v6
|
||||
uses: actions/setup-python@v7
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- name: Install Dependencies
|
||||
|
||||
2
.github/workflows/npm-publish.yml
vendored
2
.github/workflows/npm-publish.yml
vendored
@ -15,7 +15,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '24'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
2
.github/workflows/tester.yml
vendored
2
.github/workflows/tester.yml
vendored
@ -14,7 +14,7 @@ jobs:
|
||||
with:
|
||||
repository: hexojs/hexo-starter
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v7
|
||||
- name: Install Dependencies
|
||||
run: npm install
|
||||
- name: Install hexo-tag-embed
|
||||
|
||||
@ -148,9 +148,9 @@ NexT send special thanks to these great services that sponsor our core infrastru
|
||||
|
||||
<a href="https://www.netlify.com">
|
||||
<picture>
|
||||
<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://www.netlify.com/v3/img/components/full-logo-light.svg">
|
||||
<img alt="Netlify Logo" height="40" src="https://www.netlify.com/v3/img/components/full-logo-light.svg">
|
||||
<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: light)" height="40" srcset="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://raw.githubusercontent.com/netlify/netlify-example-styles/main/public/images/logo-netlify-small-fullcolor-lightmode.svg">
|
||||
</picture>
|
||||
</a>
|
||||
|
||||
|
||||
19
_config.yml
19
_config.yml
@ -88,7 +88,6 @@ open_graph:
|
||||
#twitter_id: <twitter:creator>
|
||||
#twitter_site: <twitter:site>
|
||||
#twitter_image: <twitter:image>
|
||||
#google_plus: <g+:profile_link>
|
||||
#fb_admins: <fb:admin_id>
|
||||
#fb_app_id: <fb:app_id>
|
||||
|
||||
@ -105,7 +104,7 @@ menu:
|
||||
#home: / || fa fa-home
|
||||
#about: /about/ || fa fa-user
|
||||
#tags: /tags/ || fa fa-tags
|
||||
#categories: /categories/ || fa fa-th
|
||||
#categories: /categories/ || fa fa-layer-group
|
||||
#archives: /archives/ || fa fa-archive
|
||||
#schedule: /schedule/ || fa fa-calendar
|
||||
#sitemap: /sitemap.xml || fa fa-sitemap
|
||||
@ -365,8 +364,6 @@ theme_color:
|
||||
body_scrollbar:
|
||||
# Place the scrollbar over the content.
|
||||
overlay: false
|
||||
# Reserving space for the scrollbar gutter even if the content is not overflowing.
|
||||
stable: false
|
||||
|
||||
codeblock:
|
||||
# Code Highlight theme
|
||||
@ -575,6 +572,8 @@ math:
|
||||
enable: false
|
||||
# Available values: none | ams | all
|
||||
tags: none
|
||||
# Available values: overflow | scroll | scale | truncate | linebreak
|
||||
display_overflow: scroll
|
||||
|
||||
katex:
|
||||
enable: false
|
||||
@ -600,10 +599,6 @@ fancybox: false
|
||||
# For more information: https://medium-zoom.francoischalifour.com
|
||||
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.
|
||||
# For more information: https://github.com/vinta/pangu.js
|
||||
# Server-side plugin: https://github.com/next-theme/hexo-pangu
|
||||
@ -814,13 +809,13 @@ plausible:
|
||||
script_url: # https://plausible.io/js/script.js
|
||||
site_domain: # www.example.com
|
||||
|
||||
# Another tool to show number of visitors to each article.
|
||||
# Visit https://console.firebase.google.com/u/0/ to get apiKey and projectId.
|
||||
# Visit https://firebase.google.com/docs/firestore/ to get more information about firestore.
|
||||
# Show the number of visitors to each article using the Firestore REST API.
|
||||
# Visit https://console.firebase.google.com/ to get projectId.
|
||||
# Visit https://firebase.google.com/docs/firestore/use-rest-api for more information.
|
||||
# Anonymous requests are authorized by your Firestore Security Rules.
|
||||
firestore:
|
||||
enable: false
|
||||
collection: articles # Required, a string collection name to access firestore database
|
||||
apiKey: # Required
|
||||
projectId: # Required
|
||||
|
||||
# Show Views / Visitors of the website / page with busuanzi.
|
||||
|
||||
69
_vendors.yml
69
_vendors.yml
@ -1,17 +1,12 @@
|
||||
# This file is automatically generated
|
||||
# See https://github.com/next-theme/plugins
|
||||
|
||||
anime:
|
||||
name: animejs
|
||||
version: 3.2.1
|
||||
file: lib/anime.min.js
|
||||
integrity: sha256-XL2inqUJaslATFnHdJOi9GfQ60on8Wx1C2H8DYiN1xY=
|
||||
fontawesome:
|
||||
name: '@fortawesome/fontawesome-free'
|
||||
version: 7.0.0
|
||||
version: 7.3.1
|
||||
file: css/all.min.css
|
||||
alias: font-awesome
|
||||
integrity: sha256-VHqXKFhhMxcpubYf9xiWdCiojEbY9NexQ4jh8AxbvcM=
|
||||
integrity: sha256-4Lad8m4ZWW1Lgb9+sMVLYEfnIh7BjV1NQMEe79Pviks=
|
||||
prism:
|
||||
name: prismjs
|
||||
version: 1.30.0
|
||||
@ -32,21 +27,21 @@ prism_line_numbers:
|
||||
integrity: sha256-9cmf7tcLdXpKsPi/2AWE93PbZpTp4M4tqzFk+lWomjU=
|
||||
mathjax:
|
||||
name: mathjax
|
||||
version: 3.2.2
|
||||
file: es5/tex-mml-chtml.js
|
||||
integrity: sha256-MASABpB4tYktI2Oitl4t+78w/lyA+D7b/s9GEP0JOGI=
|
||||
version: 4.1.3
|
||||
file: tex-mml-chtml.js
|
||||
integrity: sha256-ihPtDDm7pTmSF9ViEsItlvsrBTsNkz0MOXMchzOwCQw=
|
||||
katex:
|
||||
name: katex
|
||||
version: 0.16.9
|
||||
version: 0.18.4
|
||||
file: dist/katex.min.css
|
||||
alias: KaTeX
|
||||
integrity: sha256-UF1fgpAiu3tPJN/uCqEUHNe7pnr+QR0SQDNfgglgtcM=
|
||||
integrity: sha256-GAwtd9Q019pR1mJcUKlk1P1v29ubyHlqCgFsMMSZMfs=
|
||||
copy_tex_js:
|
||||
name: katex
|
||||
version: 0.16.9
|
||||
version: 0.18.4
|
||||
file: dist/contrib/copy-tex.min.js
|
||||
alias: KaTeX
|
||||
integrity: sha256-Us54+rSGDSTvIhKKUs4kygE2ipA0RXpWWh0/zLqw3bs=
|
||||
integrity: sha256-xXHEm1IMuOkEAoczgoayF4Pjm0dmYLi1bZ4cA37q6v8=
|
||||
pjax:
|
||||
name: '@next-theme/pjax'
|
||||
version: 0.6.0
|
||||
@ -55,16 +50,16 @@ pjax:
|
||||
integrity: sha256-vxLn1tSKWD4dqbMRyv940UYw4sXgMtYcK6reefzZrao=
|
||||
fancybox_js:
|
||||
name: '@fancyapps/ui'
|
||||
version: 5.0.31
|
||||
version: 6.1.14
|
||||
file: dist/fancybox/fancybox.umd.js
|
||||
alias: fancyapps-ui
|
||||
integrity: sha256-a+H7FYzJv6oU2hfsfDGM2Ohw/cR9v+hPfxHCLdmCrE8=
|
||||
integrity: sha256-QnJV4HT5r8i89ovZGRD86iynZqHerEbT2xt69vDM36I=
|
||||
fancybox_css:
|
||||
name: '@fancyapps/ui'
|
||||
version: 5.0.31
|
||||
version: 6.1.14
|
||||
file: dist/fancybox/fancybox.css
|
||||
alias: fancyapps-ui
|
||||
integrity: sha256-gkQVf8UKZgQ0HyuxL/VnacadJ+D2Kox2TCEBuNQg5+w=
|
||||
integrity: sha256-mdaMxrItEW6hMIVJJNPKuX0B7KCSJQVEv3mVY4N2we4=
|
||||
mediumzoom:
|
||||
name: medium-zoom
|
||||
version: 1.1.0
|
||||
@ -78,24 +73,24 @@ lazyload:
|
||||
integrity: sha256-mOFREFhqmHeQbXpK2lp4nA3qooVgACfh88fpJftLBbc=
|
||||
pangu:
|
||||
name: pangu
|
||||
version: 7.2.0
|
||||
version: 7.2.1
|
||||
file: dist/browser/pangu.umd.js
|
||||
integrity: sha256-JnmRRnJK7DC6RQJbAJb6AXOM9OmWzS6z8eYultk/48Y=
|
||||
integrity: sha256-kRsxecBnwG6mULFPe2E6UKnNm8XfDOuDwCOprjLZuX8=
|
||||
quicklink:
|
||||
name: quicklink
|
||||
version: 3.0.1
|
||||
version: 3.0.2
|
||||
file: dist/quicklink.umd.js
|
||||
integrity: sha256-44BednzIpUeQJcY8qtLyarFu0UCCTbgmWOvaoehiFQQ=
|
||||
integrity: sha256-Qo2BTvlsBVXIr5fEHp8xlBpUmkbDINr5cQZeSfMTES8=
|
||||
disqusjs_js:
|
||||
name: disqusjs
|
||||
version: 3.1.0
|
||||
version: 3.2.1
|
||||
file: dist/browser/disqusjs.es2015.umd.min.js
|
||||
integrity: sha256-gdbBT62HLzqWTnXQED3WQL/ItFCuWsA5gd6dM/wgdiE=
|
||||
integrity: sha256-6a5jzAWTjd18Nq6rZZn1x9hZLDZAilRDnd0arUFteBM=
|
||||
disqusjs_css:
|
||||
name: disqusjs
|
||||
version: 3.1.0
|
||||
version: 3.2.1
|
||||
file: dist/browser/styles/disqusjs.css
|
||||
integrity: sha256-E4RzceSomICFY1PT/yDQrN8DsLSwdScDEiQAevHabp0=
|
||||
integrity: sha256-9SkCf6N7ls5uSFyENCeHM+m7Qv3lHH9Eg/CxdCajarI=
|
||||
gitalk_js:
|
||||
name: gitalk
|
||||
version: 1.8.0
|
||||
@ -106,21 +101,11 @@ gitalk_css:
|
||||
version: 1.8.0
|
||||
file: dist/gitalk.css
|
||||
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:
|
||||
name: algoliasearch
|
||||
version: 5.36.0
|
||||
version: 5.57.0
|
||||
file: dist/lite/builds/browser.umd.js
|
||||
integrity: sha256-o49zoHLDPBm9WLg6AiliAVi3axCRTqe3ltTxLvhzAVg=
|
||||
integrity: sha256-NJrnXIMFVRGMM2IieD83YpTvsPPIFKKyj4K/MNyTRQU=
|
||||
local_search:
|
||||
name: hexo-generator-searchdb
|
||||
version: 1.5.0
|
||||
@ -164,11 +149,11 @@ creative_commons:
|
||||
alias: creativecommons-vocabulary
|
||||
wavedrom:
|
||||
name: wavedrom
|
||||
version: 3.5.0
|
||||
version: 3.6.2
|
||||
file: wavedrom.min.js
|
||||
integrity: sha256-INLAoJc6quTNfiMWkGZniYO2cxE8mHpddnLow1m6RFs=
|
||||
integrity: sha256-3hPWxTqi1K1sjmlESiOTH6lfGwYmz8/SFqoAcPxrjwg=
|
||||
wavedrom_skin:
|
||||
name: wavedrom
|
||||
version: 3.5.0
|
||||
version: 3.6.2
|
||||
file: skins/default.js
|
||||
integrity: sha256-fduc/Zszk5ezWws2uInY/ALWVmIrmV6VTgXbsYSReFI=
|
||||
integrity: sha256-bUKjUpel+qLgflZd3EfdbhIorwRYgSkaZKM2s/Bi0VI=
|
||||
|
||||
@ -147,9 +147,9 @@ Also, we welcome Issue or PR to our [official-plugins][official-plugins-url].
|
||||
|
||||
<a href="https://www.netlify.com">
|
||||
<picture>
|
||||
<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://www.netlify.com/v3/img/components/full-logo-light.svg">
|
||||
<img alt="Netlify Logo" height="40" src="https://www.netlify.com/v3/img/components/full-logo-light.svg">
|
||||
<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: light)" height="40" srcset="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://raw.githubusercontent.com/netlify/netlify-example-styles/main/public/images/logo-netlify-small-fullcolor-lightmode.svg">
|
||||
</picture>
|
||||
</a>
|
||||
|
||||
|
||||
@ -148,9 +148,9 @@ NexT 特别感谢这些支持我们核心基础设施的优质服务:
|
||||
|
||||
<a href="https://www.netlify.com">
|
||||
<picture>
|
||||
<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://www.netlify.com/v3/img/components/full-logo-light.svg">
|
||||
<img alt="Netlify Logo" height="40" src="https://www.netlify.com/v3/img/components/full-logo-light.svg">
|
||||
<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: light)" height="40" srcset="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://raw.githubusercontent.com/netlify/netlify-example-styles/main/public/images/logo-netlify-small-fullcolor-lightmode.svg">
|
||||
</picture>
|
||||
</a>
|
||||
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
{{- meta_generator() }}
|
||||
{%- endif %}
|
||||
|
||||
{%- if config.preconnect %}
|
||||
{%- if theme.preconnect %}
|
||||
<link rel="preload" href="{{ url_for(theme.css) }}/main.css" as="style">
|
||||
{%- endif %}
|
||||
{{ next_pre() }}
|
||||
|
||||
@ -1,12 +1,8 @@
|
||||
<div class="site-brand-container">
|
||||
<div class="site-nav-toggle">
|
||||
<div class="toggle" aria-label="{{ __('accessibility.nav_toggle') }}" role="button">
|
||||
{%- if theme.menu %}
|
||||
<span class="toggle-line"></span>
|
||||
<span class="toggle-line"></span>
|
||||
<span class="toggle-line"></span>
|
||||
{%- endif %}
|
||||
</div>
|
||||
<div class="site-nav-search popup-trigger" aria-label="{{ __('menu.search') }}" role="button">
|
||||
{%- if theme.algolia_search.enable or theme.local_search.enable %}
|
||||
<i class="fa fa-search fa-fw fa-lg"></i>
|
||||
{%- endif %}
|
||||
</div>
|
||||
|
||||
<div class="site-meta">
|
||||
@ -27,11 +23,9 @@
|
||||
{%- endif %}
|
||||
</div>
|
||||
|
||||
<div class="site-nav-right">
|
||||
<div class="toggle popup-trigger" aria-label="{{ __('menu.search') }}" role="button">
|
||||
{%- if theme.algolia_search.enable or theme.local_search.enable %}
|
||||
<i class="fa fa-search fa-fw fa-lg"></i>
|
||||
{%- endif %}
|
||||
</div>
|
||||
<div class="site-nav-toggle" aria-label="{{ __('accessibility.nav_toggle') }}" role="button">
|
||||
{%- if theme.menu %}
|
||||
<i class="fa fa-ellipsis fa-fw fa-lg"></i>
|
||||
{%- endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -88,7 +88,7 @@
|
||||
{%- if config.symbols_count_time.symbols %}
|
||||
<span class="post-meta-item" title="{{ __('symbols_count_time.count') }}">
|
||||
<span class="post-meta-item-icon">
|
||||
<i class="far fa-file-word"></i>
|
||||
<i class="far fa-file-lines"></i>
|
||||
</span>
|
||||
<span class="post-meta-item-text">{{ __('symbols_count_time.count') + __('symbol.colon') }}</span>
|
||||
<span>{{ symbolsCount(post) }}</span>
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
{%- if theme.algolia_search.enable or theme.local_search.enable %}
|
||||
<div class="search-pop-overlay">
|
||||
{# Keep the popup out of the render tree to prevent Safari from animating it before main.css is applied.
|
||||
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="search-header">
|
||||
<span class="search-icon">
|
||||
@ -20,5 +22,5 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div></template>
|
||||
{%- endif %}
|
||||
|
||||
@ -6,7 +6,6 @@
|
||||
<span class="toggle-line"></span>
|
||||
<span class="toggle-line"></span>
|
||||
</div>
|
||||
<div class="sidebar-dimmer"></div>
|
||||
{%- endif %}
|
||||
|
||||
{%- if theme.back2top.enable and not theme.back2top.sidebar %}
|
||||
|
||||
3
layout/_third-party/math/mathjax.njk
vendored
3
layout/_third-party/math/mathjax.njk
vendored
@ -1,4 +1,5 @@
|
||||
{{ 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') }}
|
||||
|
||||
7
layout/_third-party/statistics/firestore.njk
vendored
7
layout/_third-party/statistics/firestore.njk
vendored
@ -1,6 +1,7 @@
|
||||
{%- if theme.firestore.enable %}
|
||||
{{ next_vendors('firebase_app') }}
|
||||
{{ next_vendors('firebase_firestore') }}
|
||||
{{ next_data('firestore', theme.firestore) }}
|
||||
{{ next_data('firestore', {
|
||||
collection: theme.firestore.collection,
|
||||
projectId: theme.firestore.projectId
|
||||
}) }}
|
||||
{{ next_js('third-party/statistics/firestore.js') }}
|
||||
{%- endif %}
|
||||
|
||||
10
package.json
10
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "hexo-theme-next",
|
||||
"version": "8.28.0",
|
||||
"version": "8.29.0",
|
||||
"description": "Elegant and powerful theme for Hexo.",
|
||||
"main": "package.json",
|
||||
"files": [
|
||||
@ -36,13 +36,13 @@
|
||||
"homepage": "https://theme-next.js.org",
|
||||
"devDependencies": {
|
||||
"@next-theme/eslint-config": "0.0.5",
|
||||
"c8": "11.0.0",
|
||||
"c8": "12.0.0",
|
||||
"chai": "6.2.2",
|
||||
"eslint": "10.6.0",
|
||||
"eslint": "10.8.0",
|
||||
"hexo": "8.1.2",
|
||||
"hexo-renderer-marked": "7.0.1",
|
||||
"js-yaml": "4.2.0",
|
||||
"mocha": "11.7.6",
|
||||
"js-yaml": "4.3.0",
|
||||
"mocha": "11.8.0",
|
||||
"stylelint": "16.26.1",
|
||||
"stylelint-stylus": "1.0.0"
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ module.exports = hexo => {
|
||||
hexo.log.warn('Documentation: https://theme-next.js.org/docs/getting-started/configuration.html');
|
||||
}
|
||||
|
||||
const { cache, changyan, growingio_analytics, language_switcher, leancloud_visitors } = hexo.theme.config;
|
||||
const { body_scrollbar, cache, changyan, growingio_analytics, language_switcher, lazyload, leancloud_visitors } = hexo.theme.config;
|
||||
const warning = function(...args) {
|
||||
hexo.log.warn(`Since ${args[0]} is turned on, the ${args[1]} is disabled to avoid potential hazards.`);
|
||||
};
|
||||
@ -29,6 +29,14 @@ module.exports = hexo => {
|
||||
if (growingio_analytics) {
|
||||
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) {
|
||||
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/');
|
||||
|
||||
@ -57,5 +57,10 @@ module.exports = hexo => {
|
||||
// Omit integrity for local assets.
|
||||
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;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -19,7 +19,9 @@ hexo.extend.helper.register('next_inject', function(point) {
|
||||
|
||||
hexo.extend.helper.register('next_js', function(file, {
|
||||
pjax = false,
|
||||
module = false
|
||||
module = false,
|
||||
async = false,
|
||||
defer = !async
|
||||
} = {}) {
|
||||
const { next_version } = this;
|
||||
const { internal, custom_cdn_url } = this.theme.vendors;
|
||||
@ -32,18 +34,21 @@ hexo.extend.helper.register('next_js', function(file, {
|
||||
custom : custom_cdn_url
|
||||
});
|
||||
const src = links[internal] || links.local;
|
||||
return `<script ${pjax ? 'data-pjax ' : ''}${module ? 'type="module" ' : ''}src="${src}" defer></script>`;
|
||||
return `<script ${pjax ? 'data-pjax ' : ''}${module ? 'type="module" ' : ''}src="${src}"${defer ? ' defer' : ''}${async ? ' async' : ''}></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 type = url.endsWith('css') ? 'css' : 'js';
|
||||
if (type === 'css') {
|
||||
if (integrity) return `<link rel="stylesheet" href="${url}" integrity="${integrity}" crossorigin="anonymous">`;
|
||||
return `<link rel="stylesheet" href="${url}">`;
|
||||
}
|
||||
if (integrity) return `<script src="${url}" integrity="${integrity}" crossorigin="anonymous" defer></script>`;
|
||||
return `<script src="${url}" defer></script>`;
|
||||
if (integrity) return `<script src="${url}" integrity="${integrity}" crossorigin="anonymous"${defer ? ' defer' : ''}${async ? ' async' : ''}></script>`;
|
||||
return `<script src="${url}"${defer ? ' defer' : ''}${async ? ' async' : ''}></script>`;
|
||||
});
|
||||
|
||||
hexo.extend.helper.register('next_data', function(name, ...data) {
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
|
||||
hexo.extend.helper.register('js_vendors', function() {
|
||||
const { config, theme } = this;
|
||||
const vendors = ['anime'];
|
||||
const vendors = [];
|
||||
if (theme.prism.enable && !config.prismjs.preprocess) {
|
||||
vendors.push('prism', 'prism_autoloader');
|
||||
if (config.prismjs.line_number) {
|
||||
|
||||
@ -28,11 +28,21 @@
|
||||
}
|
||||
|
||||
if (hexo-config('reward_settings.animation')) {
|
||||
span {
|
||||
transform-origin: center bottom;
|
||||
}
|
||||
|
||||
&:hover span {
|
||||
animation: next-roll .1s infinite linear;
|
||||
animation: next-bounce 1.4s infinite ease-in-out;
|
||||
// The animation may affect :hover of img in dark mode
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
&:hover span {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,12 +54,28 @@
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes next-roll {
|
||||
from {
|
||||
transform: rotateZ(30deg);
|
||||
@keyframes next-bounce {
|
||||
0%, 100% {
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: rotateZ(-30deg);
|
||||
12% {
|
||||
transform: translateY(0) scale(1.04, .96);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
if (hexo-config('math.mathjax.enable')) {
|
||||
mjx-container[jax='CHTML'][display='true'], .has-jax {
|
||||
.has-jax {
|
||||
overflow: auto hidden;
|
||||
}
|
||||
|
||||
|
||||
@ -12,11 +12,10 @@
|
||||
.site-brand-container {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.use-motion {
|
||||
.column, .site-brand-container .toggle {
|
||||
.column {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,20 +1,19 @@
|
||||
.site-nav-toggle, .site-nav-right {
|
||||
.site-nav-toggle, .site-nav-search {
|
||||
color: var(--text-color);
|
||||
display: none;
|
||||
padding: 0 10px;
|
||||
|
||||
.fa {
|
||||
transition: transform .5s;
|
||||
}
|
||||
|
||||
+mobile() {
|
||||
flex-column();
|
||||
}
|
||||
}
|
||||
|
||||
.toggle {
|
||||
color: var(--text-color);
|
||||
padding: 10px;
|
||||
width: 22px;
|
||||
|
||||
.toggle-line {
|
||||
background: var(--text-color);
|
||||
border-radius: 1px;
|
||||
}
|
||||
}
|
||||
.site-nav-on .site-nav-toggle .fa {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
.site-nav {
|
||||
|
||||
@ -20,16 +20,10 @@
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.site-nav-toggle, .site-nav-right {
|
||||
.site-nav-toggle, .site-nav-search {
|
||||
color: white;
|
||||
|
||||
+tablet() {
|
||||
flex-column();
|
||||
}
|
||||
|
||||
.toggle {
|
||||
color: white;
|
||||
|
||||
.toggle-line {
|
||||
background: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,7 +15,6 @@ body { margin-top: 2rem; }
|
||||
}
|
||||
|
||||
.use-motion .column,
|
||||
.use-motion .site-brand-container .toggle,
|
||||
.use-motion .footer { opacity: initial; }
|
||||
|
||||
.use-motion .site-title,
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
/* global CONFIG */
|
||||
/* global NexT, CONFIG */
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
'use strict';
|
||||
@ -13,12 +13,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
// If the page opens with a specific hash, just jump out
|
||||
if (!isNaN(top) && location.hash === '') {
|
||||
// Auto scroll to the position
|
||||
window.anime({
|
||||
targets : document.scrollingElement,
|
||||
duration : 200,
|
||||
easing : 'linear',
|
||||
scrollTop: top
|
||||
});
|
||||
NexT.utils.scrollTo(window, top);
|
||||
}
|
||||
};
|
||||
// Register everything
|
||||
@ -36,17 +31,25 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
// Save the position by clicking the icon
|
||||
link.addEventListener('click', () => {
|
||||
doSaveScroll();
|
||||
window.anime({
|
||||
targets : link,
|
||||
if (typeof link.animate !== 'function') {
|
||||
link.style.top = '-30px';
|
||||
setTimeout(() => {
|
||||
link.style.top = '';
|
||||
}, 600);
|
||||
return;
|
||||
}
|
||||
const animation = link.animate([{}, { top: '-30px' }], {
|
||||
duration: 200,
|
||||
easing : 'linear',
|
||||
top : -30,
|
||||
complete: () => {
|
||||
setTimeout(() => {
|
||||
link.style.top = '';
|
||||
}, 400);
|
||||
}
|
||||
fill : 'forwards'
|
||||
});
|
||||
animation.finished.then(() => {
|
||||
link.style.top = '-30px';
|
||||
animation.cancel();
|
||||
setTimeout(() => {
|
||||
link.style.top = '';
|
||||
}, 400);
|
||||
}).catch(() => {});
|
||||
});
|
||||
scrollToMark();
|
||||
document.addEventListener('pjax:success', scrollToMark);
|
||||
|
||||
@ -14,16 +14,38 @@ NexT.motion.integrator = {
|
||||
return this;
|
||||
},
|
||||
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()];
|
||||
this.queue.forEach(sequence => {
|
||||
const timeline = window.anime.timeline({
|
||||
duration: CONFIG.motion?.duration ?? 200,
|
||||
easing : 'linear'
|
||||
});
|
||||
sequence.forEach(item => {
|
||||
if (item.deltaT) timeline.add(item, item.deltaT);
|
||||
else timeline.add(item);
|
||||
});
|
||||
this.queue.forEach(sequence => this.schedule(sequence));
|
||||
},
|
||||
schedule(sequence) {
|
||||
let cursor = 0;
|
||||
sequence.forEach(item => {
|
||||
const duration = item.duration ?? CONFIG.motion?.duration ?? 200;
|
||||
const start = Math.max(0, cursor - (item.overlap ?? 0));
|
||||
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);
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -35,18 +57,17 @@ NexT.motion.middleWares = {
|
||||
function getMistLineSettings(targets) {
|
||||
sequence.push({
|
||||
targets,
|
||||
scaleX : [0, 1],
|
||||
styles : { transform: 'scaleX(1)' },
|
||||
duration: 500,
|
||||
deltaT : '-=200'
|
||||
overlap : 200
|
||||
});
|
||||
}
|
||||
|
||||
function pushToSequence(targets, sequenceQueue = false) {
|
||||
sequence.push({
|
||||
targets,
|
||||
opacity: 1,
|
||||
top : 0,
|
||||
deltaT : sequenceQueue ? '-=200' : '-=0'
|
||||
styles : { opacity: 1, top: '0px' },
|
||||
overlap: sequenceQueue ? 200 : 0
|
||||
});
|
||||
}
|
||||
|
||||
@ -64,7 +85,7 @@ NexT.motion.middleWares = {
|
||||
sequence.push({
|
||||
targets,
|
||||
complete: () => targets.classList.add('animated', menuItemTransition),
|
||||
deltaT : '-=200'
|
||||
overlap : 200
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -92,7 +113,7 @@ NexT.motion.middleWares = {
|
||||
sequence.push({
|
||||
targets,
|
||||
complete: () => targets.classList.add('animated', animation),
|
||||
deltaT : '-=100'
|
||||
overlap : 100
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -101,7 +122,7 @@ NexT.motion.middleWares = {
|
||||
sequence.push({
|
||||
targets,
|
||||
complete: () => targets.classList.add('animated', post_block),
|
||||
deltaT : '-=100'
|
||||
overlap : 100
|
||||
});
|
||||
animate(coll_header, targets.querySelectorAll('.collection-header'));
|
||||
animate(post_header, targets.querySelectorAll('.post-header'));
|
||||
@ -123,7 +144,7 @@ NexT.motion.middleWares = {
|
||||
sequence.push({
|
||||
targets,
|
||||
complete: () => targets.classList.add('animated', sidebarTransition),
|
||||
deltaT : '-=100'
|
||||
overlap : 100
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -133,7 +154,7 @@ NexT.motion.middleWares = {
|
||||
footer() {
|
||||
return [{
|
||||
targets: document.querySelector('.footer'),
|
||||
opacity: 1
|
||||
styles : { opacity: 1 }
|
||||
}];
|
||||
}
|
||||
};
|
||||
|
||||
@ -9,8 +9,7 @@ NexT.boot.registerEvents = function() {
|
||||
NexT.utils.updateFooterPosition();
|
||||
|
||||
// Mobile top menu bar.
|
||||
document.querySelector('.site-nav-toggle .toggle').addEventListener('click', event => {
|
||||
event.currentTarget.classList.toggle('toggle-close');
|
||||
document.querySelector('.site-nav-toggle').addEventListener('click', event => {
|
||||
const siteNav = document.querySelector('.site-nav');
|
||||
if (!siteNav) return;
|
||||
siteNav.style.setProperty('--scroll-height', siteNav.scrollHeight + 'px');
|
||||
|
||||
@ -26,7 +26,8 @@ const pjax = new Pjax({
|
||||
},
|
||||
analytics: false,
|
||||
cacheBust: false,
|
||||
scrollTo : !CONFIG.bookmark.enable
|
||||
// scrollTo accepts a number (vertical offset), a number array ([x, y]), or false (do not scroll).
|
||||
scrollTo : CONFIG.bookmark.enable ? false : 0
|
||||
});
|
||||
|
||||
document.addEventListener('pjax:success', () => {
|
||||
|
||||
@ -3,13 +3,17 @@
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
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 = {
|
||||
mouse: {},
|
||||
init() {
|
||||
window.addEventListener('mousedown', this.mousedownHandler.bind(this));
|
||||
window.addEventListener('mouseup', this.mouseupHandler.bind(this));
|
||||
document.querySelector('.sidebar-dimmer').addEventListener('click', this.clickHandler.bind(this));
|
||||
sidebarDimmer.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:hide', this.hideSidebar);
|
||||
|
||||
5
source/js/third-party/fancybox.js
vendored
5
source/js/third-party/fancybox.js
vendored
@ -31,5 +31,8 @@ document.addEventListener('page:loaded', () => {
|
||||
image.wrap(imageWrapLink);
|
||||
});
|
||||
|
||||
Fancybox.bind('[data-fancybox]');
|
||||
// Disable hash handling to avoid conflicts with PJAX navigation.
|
||||
Fancybox.bind('[data-fancybox]', {
|
||||
Hash: false
|
||||
});
|
||||
});
|
||||
|
||||
11
source/js/third-party/math/mathjax.js
vendored
11
source/js/third-party/math/mathjax.js
vendored
@ -4,6 +4,12 @@ document.addEventListener('page:loaded', () => {
|
||||
if (!CONFIG.enableMath) return;
|
||||
|
||||
if (typeof MathJax === 'undefined') {
|
||||
const output = {
|
||||
displayOverflow: CONFIG.mathjax.display_overflow
|
||||
};
|
||||
if (CONFIG.mathjax.font_path) {
|
||||
output.fontPath = CONFIG.mathjax.font_path;
|
||||
}
|
||||
window.MathJax = {
|
||||
tex: {
|
||||
inlineMath: { '[+]': [['$', '$']] },
|
||||
@ -12,7 +18,7 @@ document.addEventListener('page:loaded', () => {
|
||||
options: {
|
||||
renderActions: {
|
||||
insertedScript: [200, () => {
|
||||
document.querySelectorAll('mjx-container').forEach(node => {
|
||||
document.querySelectorAll('mjx-container:not([display])').forEach(node => {
|
||||
const target = node.parentNode;
|
||||
if (target.nodeName.toLowerCase() === 'li') {
|
||||
target.parentNode.classList.add('has-jax');
|
||||
@ -20,7 +26,8 @@ document.addEventListener('page:loaded', () => {
|
||||
});
|
||||
}, '', false]
|
||||
}
|
||||
}
|
||||
},
|
||||
output
|
||||
};
|
||||
NexT.utils.getScript(CONFIG.mathjax.js, {
|
||||
attributes: {
|
||||
|
||||
128
source/js/third-party/search/algolia-search.js
vendored
128
source/js/third-party/search/algolia-search.js
vendored
@ -1,6 +1,7 @@
|
||||
/* global CONFIG, NexT, pjax */
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
document.body.appendChild(document.querySelector('#search-popup-template').content.cloneNode(true));
|
||||
const { indexName, appID, apiKey, hits } = CONFIG.algolia;
|
||||
const client = window['algoliasearch/lite'].liteClient(appID, apiKey);
|
||||
|
||||
@ -19,83 +20,92 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
return result;
|
||||
};
|
||||
|
||||
let isSearching = false;
|
||||
let pendingQuery = null;
|
||||
let latestSearchId = 0;
|
||||
|
||||
const searchAlgolia = async (searchText, page = 0) => {
|
||||
if (isSearching) {
|
||||
pendingQuery = { searchText, page };
|
||||
return;
|
||||
}
|
||||
isSearching = true;
|
||||
const searchAlgolia = async (searchText, page, searchId) => {
|
||||
if (searchId !== latestSearchId) return;
|
||||
container.setAttribute('aria-busy', 'true');
|
||||
const startTime = Date.now();
|
||||
const result = await client.search({
|
||||
requests: [{
|
||||
indexName,
|
||||
page,
|
||||
query : searchText,
|
||||
hitsPerPage : hits.per_page || 10,
|
||||
attributesToRetrieve : ['permalink'],
|
||||
attributesToHighlight: ['title', 'excerpt', 'excerptStrip', 'contentStripTruncate'],
|
||||
highlightPreTag : '<mark class="search-keyword">',
|
||||
highlightPostTag : '</mark>'
|
||||
}]
|
||||
});
|
||||
const data = result.results[0];
|
||||
if (data.nbHits === 0) {
|
||||
container.innerHTML = '<div class="search-result-icon"><i class="far fa-frown fa-5x"></i></div>';
|
||||
} else {
|
||||
const stats = CONFIG.i18n.hits_time
|
||||
.replace('${hits}', data.nbHits)
|
||||
.replace('${time}', Date.now() - startTime);
|
||||
let pagination = '';
|
||||
if (data.nbPages > 1) {
|
||||
pagination += '<nav class="pagination algolia-pagination">';
|
||||
for (let i = 0; i < data.nbPages; i++) {
|
||||
if (i === page) {
|
||||
pagination += `<span class="page-number current">${i + 1}</span>`;
|
||||
} else {
|
||||
pagination += `<a class="page-number" href="#" data-index=${i}>${i + 1}</a>`;
|
||||
}
|
||||
}
|
||||
pagination += '</nav>';
|
||||
}
|
||||
try {
|
||||
const result = await client.search({
|
||||
requests: [{
|
||||
indexName,
|
||||
page,
|
||||
query : searchText,
|
||||
hitsPerPage : hits.per_page || 10,
|
||||
attributesToRetrieve : ['permalink'],
|
||||
attributesToHighlight: ['title', 'excerpt', 'excerptStrip', 'contentStripTruncate'],
|
||||
highlightPreTag : '<mark class="search-keyword">',
|
||||
highlightPostTag : '</mark>'
|
||||
}]
|
||||
});
|
||||
if (searchId !== latestSearchId) return;
|
||||
|
||||
container.innerHTML = `<div class="search-stats">
|
||||
const data = result.results[0];
|
||||
if (data.nbHits === 0) {
|
||||
container.innerHTML = '<div class="search-result-icon"><i class="far fa-frown fa-5x"></i></div>';
|
||||
} else {
|
||||
const stats = CONFIG.i18n.hits_time
|
||||
.replace('${hits}', data.nbHits)
|
||||
.replace('${time}', Date.now() - startTime);
|
||||
let pagination = '';
|
||||
if (data.nbPages > 1) {
|
||||
pagination += '<nav class="pagination algolia-pagination">';
|
||||
for (let i = 0; i < data.nbPages; i++) {
|
||||
if (i === page) {
|
||||
pagination += `<span class="page-number current">${i + 1}</span>`;
|
||||
} else {
|
||||
pagination += `<a class="page-number" href="#" data-index=${i}>${i + 1}</a>`;
|
||||
}
|
||||
}
|
||||
pagination += '</nav>';
|
||||
}
|
||||
|
||||
container.innerHTML = `<div class="search-stats">
|
||||
<span>${stats}</span>
|
||||
<img src="${CONFIG.images}/logo-algolia-nebula-blue-full.svg" alt="Algolia">
|
||||
</div>
|
||||
<hr>
|
||||
<ul class="search-result-list">${data.hits.map(formatHits).join('')}</ul>
|
||||
${pagination}`;
|
||||
if (typeof pjax === 'object') pjax.refresh(container);
|
||||
container.querySelectorAll('.page-number').forEach(element => {
|
||||
element.addEventListener('click', async event => {
|
||||
event.preventDefault();
|
||||
await searchAlgolia(searchText, Number(element.dataset.index));
|
||||
if (typeof pjax === 'object') pjax.refresh(container);
|
||||
container.querySelectorAll('.page-number').forEach(element => {
|
||||
element.addEventListener('click', async event => {
|
||||
event.preventDefault();
|
||||
if (input.value.trim() !== searchText) return;
|
||||
const searchId = ++latestSearchId;
|
||||
await searchAlgolia(searchText, Number(element.dataset.index), searchId);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
isSearching = false;
|
||||
if (pendingQuery !== null && (pendingQuery.searchText !== searchText || pendingQuery.page !== page)) {
|
||||
const { searchText, page } = pendingQuery;
|
||||
pendingQuery = null;
|
||||
searchAlgolia(searchText, page);
|
||||
}
|
||||
} catch (error) {
|
||||
if (searchId !== latestSearchId) return;
|
||||
console.warn('Algolia search failed:', error);
|
||||
container.innerHTML = '<div class="search-result-icon"><i class="far fa-frown fa-5x"></i></div>';
|
||||
} finally {
|
||||
if (searchId === latestSearchId) container.removeAttribute('aria-busy');
|
||||
}
|
||||
};
|
||||
|
||||
const inputEventFunction = async () => {
|
||||
const inputEventFunction = async searchId => {
|
||||
if (searchId !== latestSearchId) return;
|
||||
const searchText = input.value.trim();
|
||||
if (searchText === '') {
|
||||
container.innerHTML = '<div class="search-result-icon"><i class="fab fa-algolia fa-5x"></i></div>';
|
||||
return;
|
||||
}
|
||||
if (searchText === '') return;
|
||||
// Algolia client will automatically cache the data for same queries
|
||||
await searchAlgolia(searchText, 0);
|
||||
await searchAlgolia(searchText, 0, searchId);
|
||||
};
|
||||
|
||||
const debouncedSearch = NexT.utils.debounce(inputEventFunction, 500);
|
||||
input.addEventListener('input', debouncedSearch);
|
||||
input.addEventListener('input', () => {
|
||||
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
|
||||
document.querySelectorAll('.popup-trigger').forEach(element => {
|
||||
|
||||
1
source/js/third-party/search/local-search.js
vendored
1
source/js/third-party/search/local-search.js
vendored
@ -6,6 +6,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
console.warn('`hexo-generator-searchdb` plugin is not installed!');
|
||||
return;
|
||||
}
|
||||
document.body.appendChild(document.querySelector('#search-popup-template').content.cloneNode(true));
|
||||
const localSearch = new LocalSearch({
|
||||
path : CONFIG.path,
|
||||
top_n_per_article: CONFIG.localsearch.top_n_per_article,
|
||||
|
||||
132
source/js/third-party/statistics/firestore.js
vendored
132
source/js/third-party/statistics/firestore.js
vendored
@ -1,57 +1,95 @@
|
||||
/* global CONFIG, firebase */
|
||||
|
||||
firebase.initializeApp({
|
||||
apiKey : CONFIG.firestore.apiKey,
|
||||
projectId: CONFIG.firestore.projectId
|
||||
});
|
||||
/* global CONFIG */
|
||||
|
||||
(function() {
|
||||
const getCount = async (doc, increaseCount) => {
|
||||
// IncreaseCount will be false when not in article page
|
||||
const d = await doc.get();
|
||||
// Has no data, initialize count
|
||||
let count = d.exists ? d.data().count : 0;
|
||||
// If first view this article
|
||||
if (increaseCount) {
|
||||
// Increase count
|
||||
count++;
|
||||
doc.set({
|
||||
count
|
||||
});
|
||||
}
|
||||
return count;
|
||||
const database = `projects/${CONFIG.firestore.projectId}/databases/(default)`;
|
||||
const documents = `${database}/documents`;
|
||||
const api = `https://firestore.googleapis.com/v1/${documents}`;
|
||||
|
||||
const fetchFirestore = async (path, options) => {
|
||||
const response = await fetch(`${api}${path}`, options);
|
||||
if (response.status === 404) return null;
|
||||
if (!response.ok) throw new Error(`Firestore request failed with status ${response.status}`);
|
||||
return response.json();
|
||||
};
|
||||
|
||||
const db = firebase.firestore();
|
||||
const articles = db.collection(CONFIG.firestore.collection);
|
||||
const documentName = title => `${documents}/${CONFIG.firestore.collection}/${title}`;
|
||||
const getValue = value => Number(value?.integerValue ?? value?.doubleValue ?? 0);
|
||||
|
||||
const getCount = async (title, increaseCount) => {
|
||||
if (increaseCount) {
|
||||
const data = await fetchFirestore(':commit', {
|
||||
method : 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
writes: [{
|
||||
transform: {
|
||||
document : documentName(title),
|
||||
fieldTransforms: [{
|
||||
fieldPath: 'count',
|
||||
increment: {
|
||||
integerValue: '1'
|
||||
}
|
||||
}]
|
||||
}
|
||||
}]
|
||||
})
|
||||
});
|
||||
return getValue(data.writeResults[0].transformResults[0]);
|
||||
}
|
||||
|
||||
const collection = encodeURIComponent(CONFIG.firestore.collection);
|
||||
const document = await fetchFirestore(`/${collection}/${encodeURIComponent(title)}`);
|
||||
return getValue(document?.fields?.count);
|
||||
};
|
||||
|
||||
const getCounts = async titles => {
|
||||
if (titles.length === 0) return [];
|
||||
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 () => {
|
||||
|
||||
if (CONFIG.page.isPost) {
|
||||
// Fix issue #118
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent
|
||||
const title = document.querySelector('.post-title').textContent.trim();
|
||||
const doc = articles.doc(title);
|
||||
let increaseCount = CONFIG.hostname === location.hostname;
|
||||
if (localStorage.getItem(title)) {
|
||||
increaseCount = false;
|
||||
} else {
|
||||
// Mark as visited
|
||||
localStorage.setItem(title, true);
|
||||
try {
|
||||
if (CONFIG.page.isPost) {
|
||||
// Fix issue #118
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent
|
||||
const title = document.querySelector('.post-title').textContent.trim();
|
||||
let increaseCount = CONFIG.hostname === location.hostname;
|
||||
if (localStorage.getItem(title)) {
|
||||
increaseCount = false;
|
||||
} else {
|
||||
// Mark as visited
|
||||
localStorage.setItem(title, true);
|
||||
}
|
||||
const count = await getCount(title, increaseCount);
|
||||
document.querySelector('.firestore-visitors-count').innerText = count;
|
||||
} else if (CONFIG.page.isHome) {
|
||||
const titles = [...document.querySelectorAll('.post-title')].map(element => element.textContent.trim());
|
||||
const counts = await getCounts(titles);
|
||||
const metas = document.querySelectorAll('.firestore-visitors-count');
|
||||
counts.forEach((val, idx) => {
|
||||
metas[idx].innerText = val;
|
||||
});
|
||||
}
|
||||
const count = await getCount(doc, increaseCount);
|
||||
document.querySelector('.firestore-visitors-count').innerText = count;
|
||||
} else if (CONFIG.page.isHome) {
|
||||
const promises = [...document.querySelectorAll('.post-title')].map(element => {
|
||||
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');
|
||||
counts.forEach((val, idx) => {
|
||||
metas[idx].innerText = val;
|
||||
});
|
||||
} catch (error) {
|
||||
console.warn('Failed to load Firestore visitor count:', error);
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
@ -19,6 +19,17 @@ HTMLElement.prototype.wrap = function(wrapper) {
|
||||
|
||||
NexT.utils = {
|
||||
|
||||
scrollTo(target, top) {
|
||||
if (window.CSS?.supports('scroll-behavior', 'smooth')) {
|
||||
target.scrollTo({
|
||||
top,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
} else {
|
||||
target.scrollTo(0, top);
|
||||
}
|
||||
},
|
||||
|
||||
registerExtURL() {
|
||||
document.querySelectorAll('span.exturl').forEach(element => {
|
||||
const link = document.createElement('a');
|
||||
@ -193,12 +204,7 @@ NexT.utils = {
|
||||
}, { passive: true });
|
||||
|
||||
backToTop?.addEventListener('click', () => {
|
||||
window.anime({
|
||||
targets : document.scrollingElement,
|
||||
duration : 500,
|
||||
easing : 'linear',
|
||||
scrollTop: 0
|
||||
});
|
||||
NexT.utils.scrollTo(window, 0);
|
||||
});
|
||||
},
|
||||
|
||||
@ -258,12 +264,7 @@ NexT.utils = {
|
||||
}, 1000);
|
||||
if (!CONFIG.stickytabs) return;
|
||||
const offset = nav.parentNode.getBoundingClientRect().top + window.scrollY + 10;
|
||||
window.anime({
|
||||
targets : document.scrollingElement,
|
||||
duration : 500,
|
||||
easing : 'linear',
|
||||
scrollTop: offset
|
||||
});
|
||||
NexT.utils.scrollTo(window, offset);
|
||||
});
|
||||
});
|
||||
|
||||
@ -311,15 +312,8 @@ NexT.utils = {
|
||||
element.addEventListener('click', event => {
|
||||
event.preventDefault();
|
||||
const offset = target.getBoundingClientRect().top + window.scrollY;
|
||||
window.anime({
|
||||
targets : document.scrollingElement,
|
||||
duration : 500,
|
||||
easing : 'linear',
|
||||
scrollTop: offset,
|
||||
complete : () => {
|
||||
history.pushState(null, document.title, element.href);
|
||||
}
|
||||
});
|
||||
NexT.utils.scrollTo(window, offset);
|
||||
history.pushState(null, document.title, element.href);
|
||||
});
|
||||
return target;
|
||||
});
|
||||
@ -342,8 +336,6 @@ NexT.utils = {
|
||||
const target = navItemList[index];
|
||||
if (!target || target.classList.contains('active-current')) return;
|
||||
|
||||
const singleHeight = navItemList[navItemList.length - 1].offsetHeight;
|
||||
|
||||
nav.querySelectorAll('.active').forEach(navItem => {
|
||||
navItem.classList.remove('active', 'active-current');
|
||||
});
|
||||
@ -356,9 +348,13 @@ NexT.utils = {
|
||||
if (activateEle.classList.contains('nav-item')) {
|
||||
activateEle.classList.add('active');
|
||||
} else { // .nav-child or .nav
|
||||
// scrollHeight isn't reliable for transitioning child items.
|
||||
// Exclude nested lists so each item's own wrapped row is counted once.
|
||||
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.
|
||||
navChildHeight += (singleHeight * activateEle.childElementCount) + 5;
|
||||
navChildHeight += listItemHeight + 5;
|
||||
activateEle.style.setProperty('--height', `${navChildHeight}px`);
|
||||
}
|
||||
activateEle = activateEle.parentElement;
|
||||
@ -367,12 +363,7 @@ NexT.utils = {
|
||||
// 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');
|
||||
if (!document.querySelector('.sidebar-toc-active')) return;
|
||||
window.anime({
|
||||
targets : tocElement,
|
||||
duration : 200,
|
||||
easing : 'linear',
|
||||
scrollTop: tocElement.scrollTop - (tocElement.offsetHeight / 2) + target.getBoundingClientRect().top - tocElement.getBoundingClientRect().top
|
||||
});
|
||||
NexT.utils.scrollTo(tocElement, tocElement.scrollTop - (tocElement.offsetHeight / 2) + target.getBoundingClientRect().top - tocElement.getBoundingClientRect().top);
|
||||
},
|
||||
|
||||
updateSidebarPosition() {
|
||||
|
||||
73
test/helpers/engine.js
Normal file
73
test/helpers/engine.js
Normal file
@ -0,0 +1,73 @@
|
||||
'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">');
|
||||
});
|
||||
});
|
||||
@ -1,6 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
describe('Helpers', () => {
|
||||
require('./engine');
|
||||
require('./font');
|
||||
require('./next-url');
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user