Compare commits

..

3 Commits

Author SHA1 Message Date
Mimi
234d0e22e8 Adjust header icon spacing 2026-10-27 21:19:48 +08:00
Mimi
63cb371ff8 Update Hexo and Node.js version 2026-10-27 21:05:55 +08:00
Hien Hoang
4fdb961088
Replace the remaining url.parse() calls with the WHATWG URL API (#1003) 2026-10-27 14:12:13 +08:00
10 changed files with 72 additions and 19 deletions

View File

@ -19,7 +19,7 @@
[![NPM version](https://img.shields.io/npm/v/hexo-theme-next?color=red&logo=npm&style=for-the-badge)](https://www.npmjs.com/package/hexo-theme-next) [![NPM version](https://img.shields.io/npm/v/hexo-theme-next?color=red&logo=npm&style=for-the-badge)](https://www.npmjs.com/package/hexo-theme-next)
[![NPM Downloads](https://img.shields.io/npm/dm/hexo-theme-next?logo=npm&style=for-the-badge)](https://www.npmjs.com/package/hexo-theme-next) [![NPM Downloads](https://img.shields.io/npm/dm/hexo-theme-next?logo=npm&style=for-the-badge)](https://www.npmjs.com/package/hexo-theme-next)
[![Required Hexo version](https://img.shields.io/badge/hexo-%3E=7.0.0-blue?style=for-the-badge&logo=hexo)](https://hexo.io) [![Required Hexo version](https://img.shields.io/badge/hexo-%3E=8.0.0-blue?style=for-the-badge&logo=hexo)](https://hexo.io)
[![License](https://img.shields.io/badge/license-%20AGPL-orange?style=for-the-badge&logo=gnu)](https://github.com/next-theme/hexo-theme-next/blob/master/LICENSE.md) [![License](https://img.shields.io/badge/license-%20AGPL-orange?style=for-the-badge&logo=gnu)](https://github.com/next-theme/hexo-theme-next/blob/master/LICENSE.md)
[![Build Status](https://img.shields.io/github/actions/workflow/status/next-theme/hexo-theme-next/linter.yml?branch=master&label=test&logo=github&style=for-the-badge)](https://github.com/next-theme/hexo-theme-next/actions?query=workflow%3ALinter) [![Build Status](https://img.shields.io/github/actions/workflow/status/next-theme/hexo-theme-next/linter.yml?branch=master&label=test&logo=github&style=for-the-badge)](https://github.com/next-theme/hexo-theme-next/actions?query=workflow%3ALinter)
[![Build Status](https://img.shields.io/github/actions/workflow/status/next-theme/hexo-theme-next/tester.yml?branch=master&logo=github&style=for-the-badge)](https://github.com/next-theme/hexo-theme-next/actions?query=workflow%3ATester) [![Build Status](https://img.shields.io/github/actions/workflow/status/next-theme/hexo-theme-next/tester.yml?branch=master&logo=github&style=for-the-badge)](https://github.com/next-theme/hexo-theme-next/actions?query=workflow%3ATester)
@ -36,7 +36,9 @@
## Installation ## Installation
If you're using Hexo 5.0 or later, the simplest way to install is through npm: Requires Hexo 8.0.0 or later and Node.js `^22.13.0 || >=24.0.0`.
The simplest way to install is through npm:
```sh ```sh
cd hexo-site cd hexo-site

View File

@ -19,7 +19,7 @@
[![NPM version](https://img.shields.io/npm/v/hexo-theme-next?color=red&logo=npm&style=for-the-badge)](https://www.npmjs.com/package/hexo-theme-next) [![NPM version](https://img.shields.io/npm/v/hexo-theme-next?color=red&logo=npm&style=for-the-badge)](https://www.npmjs.com/package/hexo-theme-next)
[![NPM Downloads](https://img.shields.io/npm/dm/hexo-theme-next?logo=npm&style=for-the-badge)](https://www.npmjs.com/package/hexo-theme-next) [![NPM Downloads](https://img.shields.io/npm/dm/hexo-theme-next?logo=npm&style=for-the-badge)](https://www.npmjs.com/package/hexo-theme-next)
[![Required Hexo version](https://img.shields.io/badge/hexo-%3E=7.0.0-blue?style=for-the-badge&logo=hexo)](https://hexo.io) [![Required Hexo version](https://img.shields.io/badge/hexo-%3E=8.0.0-blue?style=for-the-badge&logo=hexo)](https://hexo.io)
[![License](https://img.shields.io/badge/license-%20AGPL-orange?style=for-the-badge&logo=gnu)](https://github.com/next-theme/hexo-theme-next/blob/master/LICENSE.md) [![License](https://img.shields.io/badge/license-%20AGPL-orange?style=for-the-badge&logo=gnu)](https://github.com/next-theme/hexo-theme-next/blob/master/LICENSE.md)
[![Build Status](https://img.shields.io/github/actions/workflow/status/next-theme/hexo-theme-next/linter.yml?branch=master&label=test&logo=github&style=for-the-badge)](https://github.com/next-theme/hexo-theme-next/actions?query=workflow%3ALinter) [![Build Status](https://img.shields.io/github/actions/workflow/status/next-theme/hexo-theme-next/linter.yml?branch=master&label=test&logo=github&style=for-the-badge)](https://github.com/next-theme/hexo-theme-next/actions?query=workflow%3ALinter)
[![Build Status](https://img.shields.io/github/actions/workflow/status/next-theme/hexo-theme-next/tester.yml?branch=master&logo=github&style=for-the-badge)](https://github.com/next-theme/hexo-theme-next/actions?query=workflow%3ATester) [![Build Status](https://img.shields.io/github/actions/workflow/status/next-theme/hexo-theme-next/tester.yml?branch=master&logo=github&style=for-the-badge)](https://github.com/next-theme/hexo-theme-next/actions?query=workflow%3ATester)
@ -36,7 +36,9 @@
## Установка ## Установка
If you're using Hexo 5.0 or later, the simplest way to install is through npm: Требуются Hexo 8.0.0 или новее и Node.js `^22.13.0 || >=24.0.0`.
Самый простой способ установки — через npm:
```sh ```sh
cd hexo-site cd hexo-site

View File

@ -19,7 +19,7 @@
[![NPM version](https://img.shields.io/npm/v/hexo-theme-next?color=red&logo=npm&style=for-the-badge)](https://www.npmjs.com/package/hexo-theme-next) [![NPM version](https://img.shields.io/npm/v/hexo-theme-next?color=red&logo=npm&style=for-the-badge)](https://www.npmjs.com/package/hexo-theme-next)
[![NPM Downloads](https://img.shields.io/npm/dm/hexo-theme-next?logo=npm&style=for-the-badge)](https://www.npmjs.com/package/hexo-theme-next) [![NPM Downloads](https://img.shields.io/npm/dm/hexo-theme-next?logo=npm&style=for-the-badge)](https://www.npmjs.com/package/hexo-theme-next)
[![Required Hexo version](https://img.shields.io/badge/hexo-%3E=7.0.0-blue?style=for-the-badge&logo=hexo)](https://hexo.io) [![Required Hexo version](https://img.shields.io/badge/hexo-%3E=8.0.0-blue?style=for-the-badge&logo=hexo)](https://hexo.io)
[![License](https://img.shields.io/badge/license-%20AGPL-orange?style=for-the-badge&logo=gnu)](https://github.com/next-theme/hexo-theme-next/blob/master/LICENSE.md) [![License](https://img.shields.io/badge/license-%20AGPL-orange?style=for-the-badge&logo=gnu)](https://github.com/next-theme/hexo-theme-next/blob/master/LICENSE.md)
[![Build Status](https://img.shields.io/github/actions/workflow/status/next-theme/hexo-theme-next/linter.yml?branch=master&label=test&logo=github&style=for-the-badge)](https://github.com/next-theme/hexo-theme-next/actions?query=workflow%3ALinter) [![Build Status](https://img.shields.io/github/actions/workflow/status/next-theme/hexo-theme-next/linter.yml?branch=master&label=test&logo=github&style=for-the-badge)](https://github.com/next-theme/hexo-theme-next/actions?query=workflow%3ALinter)
[![Build Status](https://img.shields.io/github/actions/workflow/status/next-theme/hexo-theme-next/tester.yml?branch=master&logo=github&style=for-the-badge)](https://github.com/next-theme/hexo-theme-next/actions?query=workflow%3ATester) [![Build Status](https://img.shields.io/github/actions/workflow/status/next-theme/hexo-theme-next/tester.yml?branch=master&logo=github&style=for-the-badge)](https://github.com/next-theme/hexo-theme-next/actions?query=workflow%3ATester)
@ -36,7 +36,9 @@
## 安装 ## 安装
如果你在使用 Hexo 5.0 或更新版本,最简单的安装方式是通过 npm: 需要 Hexo 8.0.0 或更新版本,以及 Node.js `^22.13.0 || >=24.0.0`。
最简单的安装方式是通过 npm:
```sh ```sh
cd hexo-site cd hexo-site

View File

@ -34,6 +34,9 @@
"url": "https://github.com/next-theme/hexo-theme-next/issues" "url": "https://github.com/next-theme/hexo-theme-next/issues"
}, },
"homepage": "https://theme-next.js.org", "homepage": "https://theme-next.js.org",
"engines": {
"node": "^22.13.0 || >=24.0.0"
},
"devDependencies": { "devDependencies": {
"@next-theme/eslint-config": "0.0.5", "@next-theme/eslint-config": "0.0.5",
"c8": "12.0.0", "c8": "12.0.0",

View File

@ -2,7 +2,6 @@
'use strict'; 'use strict';
const { parse } = require('url');
const { unescapeHTML } = require('hexo-util'); const { unescapeHTML } = require('hexo-util');
hexo.extend.filter.register('after_post_render', data => { hexo.extend.filter.register('after_post_render', data => {
@ -13,7 +12,7 @@ hexo.extend.filter.register('after_post_render', data => {
data.content = data.content.replace(/(<img[^>]*)\ssrc=/ig, '$1 data-src='); data.content = data.content.replace(/(<img[^>]*)\ssrc=/ig, '$1 data-src=');
} }
if (theme.exturl) { if (theme.exturl) {
const siteHost = parse(config.url).hostname || config.url; const siteHost = URL.parse(config.url)?.hostname || config.url;
// External URL icon // External URL icon
const exturlIcon = theme.exturl_icon ? '<i class="fa fa-external-link-alt"></i>' : ''; const exturlIcon = theme.exturl_icon ? '<i class="fa fa-external-link-alt"></i>' : '';
data.content = data.content.replace(/<a[^>]*\shref="([^"]+)"[^>]*>([^<]+)<\/a>/ig, (match, href, html) => { data.content = data.content.replace(/<a[^>]*\shref="([^"]+)"[^>]*>([^<]+)<\/a>/ig, (match, href, html) => {
@ -21,8 +20,8 @@ hexo.extend.filter.register('after_post_render', data => {
if (!href) return match; if (!href) return match;
// Exit if the url has same host with `config.url`, which means it's an internal link. // Exit if the url has same host with `config.url`, which means it's an internal link.
const link = parse(href); const link = URL.parse(href);
if (!link.protocol || link.hostname === siteHost) return match; if (!link?.protocol || link.hostname === siteHost) return match;
// Return encrypted URL with title. // Return encrypted URL with title.
const title = match.match(/title="([^"]+)"/); const title = match.match(/title="([^"]+)"/);

View File

@ -3,7 +3,6 @@
'use strict'; 'use strict';
const crypto = require('crypto'); const crypto = require('crypto');
const { parse } = require('url');
const nextFont = require('./font'); const nextFont = require('./font');
const nextUrl = require('./next-url'); const nextUrl = require('./next-url');
const { getVendors } = require('../events/lib/utils'); const { getVendors } = require('../events/lib/utils');
@ -58,16 +57,22 @@ hexo.extend.helper.register('next_data', function(name, ...data) {
}</script>`; }</script>`;
}); });
// Scheme and hostname of an absolute URL, or '' for a relative one.
function urlOrigin(url) {
const data = url && URL.parse(url);
return data?.hostname ? data.protocol + '//' + data.hostname : '';
}
hexo.extend.helper.register('next_pre', function() { hexo.extend.helper.register('next_pre', function() {
if (!this.theme.preconnect) return ''; if (!this.theme.preconnect) return '';
const { enable, host } = this.theme.font; const { enable, host } = this.theme.font;
const { internal, plugins, custom_cdn_url } = this.theme.vendors; const { internal, plugins, custom_cdn_url } = this.theme.vendors;
const links = { const links = {
local : this.theme.js && parse(this.theme.js).hostname ? parse(this.theme.js).protocol + '//' + parse(this.theme.js).hostname : '', local : urlOrigin(this.theme.js),
jsdelivr: 'https://cdn.jsdelivr.net', jsdelivr: 'https://cdn.jsdelivr.net',
unpkg : 'https://unpkg.com', unpkg : 'https://unpkg.com',
cdnjs : 'https://cdnjs.cloudflare.com', cdnjs : 'https://cdnjs.cloudflare.com',
custom : custom_cdn_url && parse(custom_cdn_url).hostname ? parse(custom_cdn_url).protocol + '//' + parse(custom_cdn_url).hostname : '' custom : urlOrigin(custom_cdn_url)
}; };
const h = enable ? host || 'https://fonts.googleapis.com' : ''; const h = enable ? host || 'https://fonts.googleapis.com' : '';
const i = links[internal]; const i = links[internal];

View File

@ -1,19 +1,18 @@
'use strict'; 'use strict';
const { htmlTag } = require('hexo-util'); const { htmlTag } = require('hexo-util');
const { parse } = require('url');
module.exports = function(path, text, options = {}, decode = false) { module.exports = function(path, text, options = {}, decode = false) {
const { config, theme } = this; const { config, theme } = this;
const data = parse(path); const data = URL.parse(path);
const siteHost = parse(config.url).hostname || config.url; const siteHost = URL.parse(config.url)?.hostname || config.url;
let exturl = ''; let exturl = '';
let tag = 'a'; let tag = 'a';
let attrs = { href: this.url_for(path) }; let attrs = { href: this.url_for(path) };
// If `exturl` enabled, set spanned links only on external links. // If `exturl` enabled, set spanned links only on external links.
if (theme.exturl && data.protocol && data.hostname !== siteHost) { if (theme.exturl && data?.protocol && data.hostname !== siteHost) {
tag = 'span'; tag = 'span';
exturl = 'exturl'; exturl = 'exturl';
const encoded = Buffer.from(path).toString('base64'); const encoded = Buffer.from(path).toString('base64');
@ -37,7 +36,7 @@ module.exports = function(path, text, options = {}, decode = false) {
} }
// If it's external link, rewrite attributes. // If it's external link, rewrite attributes.
if (data.protocol && data.hostname !== siteHost) { if (data?.protocol && data.hostname !== siteHost) {
attrs.external = null; attrs.external = null;
if (!theme.exturl) { if (!theme.exturl) {

View File

@ -1,7 +1,7 @@
.site-nav-toggle, .site-nav-search { .site-nav-toggle, .site-nav-search {
color: var(--text-color); color: var(--text-color);
display: none; display: none;
padding: 0 10px; padding: 0 15px;
.fa { .fa {
transition: transform .5s; transition: transform .5s;

View File

@ -70,4 +70,34 @@ describe('engine', () => {
it('does not apply script loading options to stylesheets', () => { 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">'); nextVendors('stylesheet', { async: true }).should.eql('<link rel="stylesheet" href="https://cdn.example.com/library.css">');
}); });
describe('next_pre', () => {
const nextPre = (theme = {}) => helpers.next_pre.call({
theme: {
preconnect: true,
js : 'js',
font : { enable: false },
vendors : { internal: 'local', plugins: 'local', custom_cdn_url: '' },
...theme
}
});
it('skips a relative local script path', () => {
nextPre().should.eql('');
});
it('uses the scheme and hostname of an absolute local script URL', () => {
nextPre({ js: 'https://static.example.com/next/js' }).should.eql('<link rel="preconnect" href="https://static.example.com" crossorigin>');
});
it('drops the port and path of a custom CDN URL', () => {
nextPre({
vendors: { internal: 'custom', plugins: 'custom', custom_cdn_url: 'https://Cdn.Example.com:8443/npm/${npm_name}@${version}/${minified}' }
}).should.eql('<link rel="preconnect" href="https://cdn.example.com" crossorigin>');
});
it('skips a protocol-relative custom CDN URL', () => {
nextPre({ vendors: { internal: 'custom', plugins: 'custom', custom_cdn_url: '//cdn.example.com' } }).should.eql('');
});
});
}); });

View File

@ -46,4 +46,15 @@ describe('next-url', () => {
const encoded = btoa('https://theme-next.js.org'); const encoded = btoa('https://theme-next.js.org');
nextUrl('https://theme-next.js.org', 'Text', { class: 'theme-link' }).should.eql(`<span class="exturl theme-link" data-url="${encoded}">Text</span>`); nextUrl('https://theme-next.js.org', 'Text', { class: 'theme-link' }).should.eql(`<span class="exturl theme-link" data-url="${encoded}">Text</span>`);
}); });
it('relative and anchor links stay links with exturl enabled', () => {
hexo.theme.exturl = true;
nextUrl('/child/', 'Text').should.eql('<a href="/child/">Text</a>');
nextUrl('#comments', 'Text').should.eql('<a href="#comments">Text</a>');
});
it('an absolute link to the site itself is internal', () => {
hexo.theme.exturl = false;
nextUrl('https://EXAMPLE.com/about/', 'Text').should.eql('<a href="https://example.com/about/">Text</a>');
});
}); });