diff --git a/scripts/filters/post.js b/scripts/filters/post.js
index 2faa1f9..ccdf16c 100644
--- a/scripts/filters/post.js
+++ b/scripts/filters/post.js
@@ -2,7 +2,6 @@
'use strict';
-const { parse } = require('url');
const { unescapeHTML } = require('hexo-util');
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(/(
]*)\ssrc=/ig, '$1 data-src=');
}
if (theme.exturl) {
- const siteHost = parse(config.url).hostname || config.url;
+ const siteHost = URL.parse(config.url)?.hostname || config.url;
// External URL icon
const exturlIcon = theme.exturl_icon ? '' : '';
data.content = data.content.replace(/]*\shref="([^"]+)"[^>]*>([^<]+)<\/a>/ig, (match, href, html) => {
@@ -21,8 +20,8 @@ hexo.extend.filter.register('after_post_render', data => {
if (!href) return match;
// Exit if the url has same host with `config.url`, which means it's an internal link.
- const link = parse(href);
- if (!link.protocol || link.hostname === siteHost) return match;
+ const link = URL.parse(href);
+ if (!link?.protocol || link.hostname === siteHost) return match;
// Return encrypted URL with title.
const title = match.match(/title="([^"]+)"/);
diff --git a/scripts/helpers/engine.js b/scripts/helpers/engine.js
index 13b1e7c..4eb419b 100644
--- a/scripts/helpers/engine.js
+++ b/scripts/helpers/engine.js
@@ -3,7 +3,6 @@
'use strict';
const crypto = require('crypto');
-const { parse } = require('url');
const nextFont = require('./font');
const nextUrl = require('./next-url');
const { getVendors } = require('../events/lib/utils');
@@ -58,16 +57,22 @@ hexo.extend.helper.register('next_data', function(name, ...data) {
}`;
});
+// 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() {
if (!this.theme.preconnect) return '';
const { enable, host } = this.theme.font;
const { internal, plugins, custom_cdn_url } = this.theme.vendors;
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',
unpkg : 'https://unpkg.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 i = links[internal];
diff --git a/scripts/helpers/next-url.js b/scripts/helpers/next-url.js
index 9f88b58..08f2505 100644
--- a/scripts/helpers/next-url.js
+++ b/scripts/helpers/next-url.js
@@ -1,19 +1,18 @@
'use strict';
const { htmlTag } = require('hexo-util');
-const { parse } = require('url');
module.exports = function(path, text, options = {}, decode = false) {
const { config, theme } = this;
- const data = parse(path);
- const siteHost = parse(config.url).hostname || config.url;
+ const data = URL.parse(path);
+ const siteHost = URL.parse(config.url)?.hostname || config.url;
let exturl = '';
let tag = 'a';
let attrs = { href: this.url_for(path) };
// 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';
exturl = 'exturl';
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 (data.protocol && data.hostname !== siteHost) {
+ if (data?.protocol && data.hostname !== siteHost) {
attrs.external = null;
if (!theme.exturl) {
diff --git a/test/helpers/engine.js b/test/helpers/engine.js
index c66f008..6980c9f 100644
--- a/test/helpers/engine.js
+++ b/test/helpers/engine.js
@@ -70,4 +70,34 @@ describe('engine', () => {
it('does not apply script loading options to stylesheets', () => {
nextVendors('stylesheet', { async: true }).should.eql('');
});
+
+ 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('');
+ });
+
+ 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('');
+ });
+
+ it('skips a protocol-relative custom CDN URL', () => {
+ nextPre({ vendors: { internal: 'custom', plugins: 'custom', custom_cdn_url: '//cdn.example.com' } }).should.eql('');
+ });
+ });
});
diff --git a/test/helpers/next-url.js b/test/helpers/next-url.js
index e1548b0..c06724b 100644
--- a/test/helpers/next-url.js
+++ b/test/helpers/next-url.js
@@ -46,4 +46,15 @@ describe('next-url', () => {
const encoded = btoa('https://theme-next.js.org');
nextUrl('https://theme-next.js.org', 'Text', { class: 'theme-link' }).should.eql(`Text`);
});
+
+ it('relative and anchor links stay links with exturl enabled', () => {
+ hexo.theme.exturl = true;
+ nextUrl('/child/', 'Text').should.eql('Text');
+ nextUrl('#comments', 'Text').should.eql('Text');
+ });
+
+ it('an absolute link to the site itself is internal', () => {
+ hexo.theme.exturl = false;
+ nextUrl('https://EXAMPLE.com/about/', 'Text').should.eql('Text');
+ });
});