2025-2-26-fixed

This commit is contained in:
2025-02-26 09:16:07 +08:00
parent bf50b6c865
commit 4968d276dc
456 changed files with 27801 additions and 1 deletions

View File

@ -0,0 +1,55 @@
/* global hexo */
'use strict';
hexo.on('generateBefore', () => {
// Merge config.
require('./lib/config')(hexo);
// Add filter type `theme_inject`
require('./lib/injects')(hexo);
});
hexo.on('generateAfter', () => {
if (!hexo.theme.config.reminder) return;
const https = require('https');
const path = require('path');
const { version } = require(path.normalize('../../package.json'));
https.get('https://api.github.com/repos/theme-next/hexo-theme-next/releases/latest', {
headers: {
'User-Agent': 'Theme NexT Client'
}
}, res => {
let result = '';
res.on('data', data => {
result += data;
});
res.on('end', () => {
try {
let latest = JSON.parse(result).tag_name.replace('v', '').split('.');
let current = version.split('.');
let isOutdated = false;
for (let i = 0; i < Math.max(latest.length, current.length); i++) {
if (!current[i] || latest[i] > current[i]) {
isOutdated = true;
break;
}
if (latest[i] < current[i]) {
break;
}
}
if (isOutdated) {
hexo.log.warn(`Your theme NexT is outdated. Current version: v${current.join('.')}, latest version: v${latest.join('.')}`);
hexo.log.warn('Visit https://github.com/theme-next/hexo-theme-next/releases for more information.');
} else {
hexo.log.info('Congratulations! Your are using the latest version of theme NexT.');
}
} catch (err) {
hexo.log.error('Failed to detect version info. Error message:');
hexo.log.error(err);
}
});
}).on('error', err => {
hexo.log.error('Failed to detect version info. Error message:');
hexo.log.error(err);
});
});

View File

@ -0,0 +1,60 @@
'use strict';
function isObject(item) {
return item && typeof item === 'object' && !Array.isArray(item);
}
function merge(target, source) {
for (const key in source) {
if (isObject(target[key]) && isObject(source[key])) {
merge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
return target;
}
module.exports = hexo => {
let data = hexo.locals.get('data');
/**
* Merge configs from _data/next.yml into hexo.theme.config.
* If `override`, configs in next.yml will rewrite configs in hexo.theme.config.
* If next.yml not exists, merge all `theme_config.*` into hexo.theme.config.
*/
if (data.next) {
if (data.next.override) {
hexo.theme.config = data.next;
} else {
merge(hexo.config, data.next);
merge(hexo.theme.config, data.next);
}
} else {
merge(hexo.theme.config, hexo.config.theme_config);
}
if (hexo.theme.config.cache && hexo.theme.config.cache.enable && hexo.config.relative_link) {
hexo.log.warn('Since caching is turned on, the `relative_link` option in Hexo `_config.yml` is set to `false` to avoid potential hazards.');
hexo.config.relative_link = false;
}
hexo.config.meta_generator = false;
// Custom languages support. Introduced in NexT v6.3.0.
if (data.languages) {
let { language } = hexo.config;
let { i18n } = hexo.theme;
const mergeLang = lang => {
i18n.set(lang, merge(i18n.get([lang]), data.languages[lang]));
};
if (Array.isArray(language)) {
for (let lang of language) {
mergeLang(lang);
}
} else {
mergeLang(language);
}
}
};

View File

@ -0,0 +1,19 @@
'use strict';
module.exports = {
views: [
'head',
'header',
'sidebar',
'postMeta',
'postBodyEnd',
'footer',
'bodyEnd',
'comment'
],
styles: [
'variable',
'mixin',
'style'
]
};

View File

@ -0,0 +1,85 @@
'use strict';
const fs = require('fs');
const path = require('path');
const points = require('./injects-point');
const defaultExtname = '.swig';
// Defining stylus types
class StylusInject {
constructor(base_dir) {
this.base_dir = base_dir;
this.files = [];
}
push(file) {
// Get absolute path base on hexo dir
this.files.push(path.resolve(this.base_dir, file));
}
}
// Defining view types
class ViewInject {
constructor(base_dir) {
this.base_dir = base_dir;
this.raws = [];
}
raw(name, raw, ...args) {
// Set default extname
if (path.extname(name) === '') {
name += defaultExtname;
}
this.raws.push({name, raw, args});
}
file(name, file, ...args) {
// Set default extname from file's extname
if (path.extname(name) === '') {
name += path.extname(file);
}
// Get absolute path base on hexo dir
this.raw(name, fs.readFileSync(path.resolve(this.base_dir, file), 'utf8'), ...args);
}
}
// Init injects
function initInject(base_dir) {
let injects = {};
points.styles.forEach(item => {
injects[item] = new StylusInject(base_dir);
});
points.views.forEach(item => {
injects[item] = new ViewInject(base_dir);
});
return injects;
}
module.exports = hexo => {
// Exec theme_inject filter
let injects = initInject(hexo.base_dir);
hexo.execFilterSync('theme_inject', injects);
hexo.theme.config.injects = {};
// Inject stylus
points.styles.forEach(type => {
hexo.theme.config.injects[type] = injects[type].files;
});
// Inject views
points.views.forEach(type => {
let configs = Object.create(null);
hexo.theme.config.injects[type] = [];
// Add or override view.
injects[type].raws.forEach((injectObj, index) => {
let name = `inject/${type}/${injectObj.name}`;
hexo.theme.setView(name, injectObj.raw);
configs[name] = {
layout : name,
locals : injectObj.args[0],
options: injectObj.args[1],
order : injectObj.args[2] || index
};
});
// Views sort.
hexo.theme.config.injects[type] = Object.values(configs)
.sort((x, y) => x.order - y.order);
});
};

View File

@ -0,0 +1,45 @@
/* global hexo */
'use strict';
const path = require('path');
const { iconText } = require('./common');
// Add comment
hexo.extend.filter.register('theme_inject', injects => {
let theme = hexo.theme.config;
if (!theme.changyan.enable || !theme.changyan.appid || !theme.changyan.appkey) return;
injects.comment.raw('changyan', `
<div class="comments">
<div id="SOHUCS"></div>
</div>
`, {}, {cache: true});
injects.bodyEnd.file('changyan', path.join(hexo.theme_dir, 'layout/_third-party/comments/changyan.swig'));
});
// Add post_meta
hexo.extend.filter.register('theme_inject', injects => {
let theme = hexo.theme.config;
if (!theme.changyan.enable || !theme.changyan.appid || !theme.changyan.appkey) return;
injects.postMeta.raw('changyan', `
{% if post.comments %}
<span class="post-meta-item">
${iconText('far fa-comment', 'changyan')}
{% if is_post() %}
<a title="changyan" href="{{ url_for(post.path) }}#SOHUCS" itemprop="discussionUrl">
<span id="changyan_count_unit" class="post-comments-count hc-comment-count" data-xid="{{ post.path }}" itemprop="commentCount"></span>
</a>
{% else %}
<a title="changyan" href="{{ url_for(post.path) }}#SOHUCS" itemprop="discussionUrl">
<span id="url::{{ post.permalink }}" class="cy_cmt_count" data-xid="{{ post.path }}" itemprop="commentCount"></span>
</a>
{% endif %}
</span>
{% endif %}
`, {}, {}, theme.changyan.post_meta_order);
});

View File

@ -0,0 +1,23 @@
'use strict';
function capitalize(input) {
return input.toString().charAt(0).toUpperCase() + input.toString().substr(1);
}
module.exports = {
iconText(icon, key, defaultValue) {
if (!defaultValue) {
defaultValue = capitalize(key);
}
return `
<span class="post-meta-item-icon">
<i class="${icon}"></i>
</span>
{%- set post_meta_comment = __('post.comments.${key}') %}
{%- if post_meta_comment == 'post.comments.${key}' %}
{%- set post_meta_comment = '${defaultValue}' %}
{%- endif %}
<span class="post-meta-item-text">{{ post_meta_comment + __('symbol.colon') }}</span>
`;
}
};

View File

@ -0,0 +1,34 @@
/* global hexo */
'use strict';
const path = require('path');
hexo.extend.filter.register('theme_inject', injects => {
injects.comment.raws.forEach(element => {
// Set default button content
let injectName = path.basename(element.name, path.extname(element.name));
element.args[0] = Object.assign({
configKey: injectName,
class : injectName,
button : injectName
}, element.args[0]);
// Get locals and config
let locals = element.args[0];
let config = hexo.theme.config.comments;
// Set activeClass
if (config.active === locals.configKey) {
config.activeClass = locals.class;
}
// Set custom button content
if (config.nav) {
let nav = config.nav[locals.configKey] || {};
if (nav.order) {
element.args[2] = nav.order;
}
if (nav.text) {
locals.button = nav.text;
}
}
});
}, 99999);

View File

@ -0,0 +1,41 @@
/* global hexo */
'use strict';
const path = require('path');
const { iconText } = require('./common');
// Add comment
hexo.extend.filter.register('theme_inject', injects => {
let theme = hexo.theme.config;
if (!theme.disqus.enable || !theme.disqus.shortname) return;
injects.comment.raw('disqus', `
<div class="comments">
<div id="disqus_thread">
<noscript>Please enable JavaScript to view the comments powered by Disqus.</noscript>
</div>
</div>
`, {}, {cache: true});
injects.bodyEnd.file('disqus', path.join(hexo.theme_dir, 'layout/_third-party/comments/disqus.swig'));
});
// Add post_meta
hexo.extend.filter.register('theme_inject', injects => {
let theme = hexo.theme.config;
if (!theme.disqus.enable || !theme.disqus.shortname || !theme.disqus.count) return;
injects.postMeta.raw('disqus', `
{% if post.comments %}
<span class="post-meta-item">
${iconText('far fa-comment', 'disqus')}
<a title="disqus" href="{{ url_for(post.path) }}#disqus_thread" itemprop="discussionUrl">
<span class="post-comments-count disqus-comment-count" data-disqus-identifier="{{ post.path }}" itemprop="commentCount"></span>
</a>
</span>
{% endif %}
`, {}, {}, theme.disqus.post_meta_order);
});

View File

@ -0,0 +1,22 @@
/* global hexo */
'use strict';
const path = require('path');
// Add comment
hexo.extend.filter.register('theme_inject', injects => {
let theme = hexo.theme.config;
if (!theme.disqusjs.enable || !theme.disqusjs.shortname || !theme.disqusjs.apikey) return;
injects.comment.raw('disqusjs', `
<div class="comments">
<div id="disqus_thread">
<noscript>Please enable JavaScript to view the comments powered by Disqus.</noscript>
</div>
</div>
`, {}, {cache: true});
injects.bodyEnd.file('disqusjs', path.join(hexo.theme_dir, 'layout/_third-party/comments/disqusjs.swig'));
});

View File

@ -0,0 +1,16 @@
/* global hexo */
'use strict';
const path = require('path');
// Add comment
hexo.extend.filter.register('theme_inject', injects => {
let theme = hexo.theme.config;
if (!theme.gitalk.enable) return;
injects.comment.raw('gitalk', '<div class="comments" id="gitalk-container"></div>', {}, {cache: true});
injects.bodyEnd.file('gitalk', path.join(hexo.theme_dir, 'layout/_third-party/comments/gitalk.swig'));
});

View File

@ -0,0 +1,20 @@
/* global hexo */
'use strict';
const path = require('path');
// Add comment
hexo.extend.filter.register('theme_inject', injects => {
let theme = hexo.theme.config;
if (!theme.livere_uid) return;
injects.comment.raw('livere', `
<div class="comments">
<div id="lv-container" data-id="city" data-uid="{{ theme.livere_uid }}"></div>
</div>
`, {}, {cache: true});
injects.bodyEnd.file('livere', path.join(hexo.theme_dir, 'layout/_third-party/comments/livere.swig'));
});

View File

@ -0,0 +1,35 @@
/* global hexo */
'use strict';
const path = require('path');
const { iconText } = require('./common');
// Add comment
hexo.extend.filter.register('theme_inject', injects => {
let theme = hexo.theme.config;
if (!theme.valine.enable || !theme.valine.appid || !theme.valine.appkey) return;
injects.comment.raw('valine', '<div class="comments" id="valine-comments"></div>', {}, {cache: true});
injects.bodyEnd.file('valine', path.join(hexo.theme_dir, 'layout/_third-party/comments/valine.swig'));
});
// Add post_meta
hexo.extend.filter.register('theme_inject', injects => {
let theme = hexo.theme.config;
if (!theme.valine.enable || !theme.valine.appid || !theme.valine.appkey) return;
injects.postMeta.raw('valine', `
{% if post.comments and (is_post() or theme.valine.comment_count) %}
<span class="post-meta-item">
${iconText('far fa-comment', 'valine')}
<a title="valine" href="{{ url_for(post.path) }}#valine-comments" itemprop="discussionUrl">
<span class="post-comments-count valine-comment-count" data-xid="{{ url_for(post.path) }}" itemprop="commentCount"></span>
</a>
</span>
{% endif %}
`, {}, {}, theme.valine.post_meta_order);
});

View File

@ -0,0 +1,24 @@
/* global hexo */
'use strict';
const points = require('../events/lib/injects-point');
hexo.extend.filter.register('theme_inject', injects => {
let filePath = hexo.theme.config.custom_file_path;
if (!filePath) return;
points.views.forEach(key => {
if (filePath[key]) {
injects[key].file('custom', filePath[key]);
}
});
points.styles.forEach(key => {
if (filePath[key]) {
injects[key].push(filePath[key]);
}
});
}, 99);

View File

@ -0,0 +1,71 @@
/**
* Modify front-matter.
*
* Some keys are included by Hexo, please don't use them.
* e.g. layout title date updated comments tags categories permalink keywords
*
* Some keys are generated by Hexo, don't use them either.
* e.g. content excerpt more source full_source path permalink prev next raw photos link
*/
/* global hexo */
'use strict';
const keys = ['toc', 'reward_settings', 'quicklink'];
function showWarnLog(source, variable) {
hexo.log.warn(`front-matter: '${variable}' has deprecated, source: ${source}`);
hexo.log.warn('see: https://github.com/theme-next/hexo-theme-next/pull/1211');
}
function compatibleBeforeAssign(page) {
if (page.quicklink === true) {
page.quicklink = {enable: true};
showWarnLog(page.source, 'quicklink:true');
}
if (page.quicklink === false) {
page.quicklink = {enable: false};
showWarnLog(page.source, 'quicklink:true');
}
}
function compatibleAfterAssign(page) {
if (page.reward !== undefined) {
page.reward_settings.enable = page.reward;
showWarnLog(page.source, 'reward');
}
if (page.toc_number !== undefined) {
page.toc.number = page.toc_number;
showWarnLog(page.source, 'toc_number');
}
if (page.toc_max_depth !== undefined) {
page.toc.max_depth = page.toc_max_depth;
showWarnLog(page.source, 'toc_max_depth');
}
}
hexo.extend.filter.register('template_locals', locals => {
const { page, theme } = locals;
compatibleBeforeAssign(page);
keys.forEach(key => {
page[key] = Object.assign({}, theme[key], page[key]);
});
compatibleAfterAssign(page);
// Set default value for toc.max_depth
if (!page.toc.max_depth) {
page.toc.max_depth = 6;
}
// Set home or archive quicklink
if (page.__index) {
page.quicklink.enable = theme.quicklink.home;
}
if (page.archive) {
page.quicklink.enable = theme.quicklink.archive;
}
});

View File

@ -0,0 +1,26 @@
/* global hexo */
'use strict';
const path = require('path');
hexo.extend.filter.register('template_locals', locals => {
const { env, config } = hexo;
const { __, theme } = locals;
const { i18n } = hexo.theme;
// Hexo & NexT version
locals.hexo_version = env.version;
locals.next_version = require(path.normalize('../../package.json')).version;
// Language & Config
locals.title = __('title') !== 'title' ? __('title') : config.title;
locals.subtitle = __('subtitle') !== 'subtitle' ? __('subtitle') : config.subtitle;
locals.author = __('author') !== 'author' ? __('author') : config.author;
locals.description = __('description') !== 'description' ? __('description') : config.description;
locals.languages = [...i18n.languages];
locals.languages.splice(locals.languages.indexOf('default'), 1);
locals.page.lang = locals.page.lang || locals.page.language;
// Creative Commons
locals.ccURL = 'https://creativecommons.org/' + (theme.creative_commons.license === 'zero' ? 'publicdomain/zero/1.0/' : 'licenses/' + theme.creative_commons.license + '/4.0/') + (theme.creative_commons.language || '');
// PJAX
locals.pjax = theme.pjax ? ' data-pjax' : '';
});

View File

@ -0,0 +1,53 @@
/* global hexo */
'use strict';
hexo.extend.filter.register('after_generate', () => {
const theme = hexo.theme.config;
if (!theme.minify) return;
const lists = hexo.route.list();
const velocity = lists.filter(list => list.includes('lib/velocity'));
const fontawesome = lists.filter(list => list.includes('lib/font-awesome'));
if (!theme.bookmark.enable) {
hexo.route.remove('js/bookmark.js');
}
if (!theme.motion.enable) {
hexo.route.remove('js/motion.js');
velocity.forEach(path => {
hexo.route.remove(path);
});
}
if (theme.motion.enable && theme.vendors.velocity && theme.vendors.velocity_ui) {
velocity.forEach(path => {
hexo.route.remove(path);
});
}
if (theme.vendors.fontawesome) {
fontawesome.forEach(path => {
hexo.route.remove(path);
});
}
if (theme.vendors.anime) {
hexo.route.remove('lib/anime.min.js');
}
if (!theme.algolia_search.enable) {
hexo.route.remove('js/algolia-search.js');
}
if (!theme.local_search.enable) {
hexo.route.remove('js/local-search.js');
}
if (theme.scheme === 'Muse' || theme.scheme === 'Mist') {
hexo.route.remove('js/schemes/pisces.js');
} else if (theme.scheme === 'Pisces' || theme.scheme === 'Gemini') {
hexo.route.remove('js/schemes/muse.js');
}
});

View File

@ -0,0 +1,27 @@
/* global hexo */
'use strict';
hexo.extend.filter.register('after_post_render', data => {
const { config } = hexo;
const theme = hexo.theme.config;
if (!theme.exturl && !theme.lazyload) return;
if (theme.lazyload) {
data.content = data.content.replace(/(<img[^>]*) src=/img, '$1 data-src=');
}
if (theme.exturl) {
const url = require('url');
const siteHost = url.parse(config.url).hostname || config.url;
data.content = data.content.replace(/<a[^>]* href="([^"]+)"[^>]*>([^<]+)<\/a>/img, (match, href, html) => {
// Exit if the href attribute doesn't exists.
if (!href) return match;
// Exit if the url has same host with `config.url`, which means it's an internal link.
let link = url.parse(href);
if (!link.protocol || link.hostname === siteHost) return match;
return `<span class="exturl" data-url="${Buffer.from(href).toString('base64')}">${html}<i class="fa fa-external-link-alt"></i></span>`;
});
}
}, 0);

View File

@ -0,0 +1,84 @@
/* global hexo */
'use strict';
const crypto = require('crypto');
hexo.extend.helper.register('next_inject', function(point) {
return hexo.theme.config.injects[point]
.map(item => this.partial(item.layout, item.locals, item.options))
.join('');
});
hexo.extend.helper.register('next_js', function(...urls) {
const { js } = hexo.theme.config;
return urls.map(url => this.js(`${js}/${url}`)).join('');
});
hexo.extend.helper.register('next_vendors', function(url) {
if (url.startsWith('//')) return url;
const internal = hexo.theme.config.vendors._internal;
return this.url_for(`${internal}/${url}`);
});
hexo.extend.helper.register('post_edit', function(src) {
const theme = hexo.theme.config;
if (!theme.post_edit.enable) return '';
return this.next_url(theme.post_edit.url + src, '<i class="fa fa-pencil-alt"></i>', {
class: 'post-edit-link',
title: this.__('post.edit')
});
});
hexo.extend.helper.register('post_nav', function(post) {
const theme = hexo.theme.config;
if (theme.post_navigation === false || (!post.prev && !post.next)) return '';
const prev = theme.post_navigation === 'right' ? post.prev : post.next;
const next = theme.post_navigation === 'right' ? post.next : post.prev;
const left = prev ? `
<a href="${this.url_for(prev.path)}" rel="prev" title="${prev.title}">
<i class="fa fa-chevron-left"></i> ${prev.title}
</a>` : '';
const right = next ? `
<a href="${this.url_for(next.path)}" rel="next" title="${next.title}">
${next.title} <i class="fa fa-chevron-right"></i>
</a>` : '';
return `
<div class="post-nav">
<div class="post-nav-item">${left}</div>
<div class="post-nav-item">${right}</div>
</div>`;
});
hexo.extend.helper.register('gitalk_md5', function(path) {
let str = this.url_for(path);
str.replace('index.html', '');
return crypto.createHash('md5').update(str).digest('hex');
});
hexo.extend.helper.register('canonical', function() {
// https://support.google.com/webmasters/answer/139066
const { permalink } = hexo.config;
let url = this.url.replace(/index\.html$/, '');
if (!permalink.endsWith('.html')) {
url = url.replace(/\.html$/, '');
}
return `<link rel="canonical" href="${url}">`;
});
/**
* Get page path given a certain language tag
*/
hexo.extend.helper.register('i18n_path', function(language) {
const { path, lang } = this.page;
const base = path.startsWith(lang) ? path.slice(lang.length + 1) : path;
return this.url_for(`${this.languages.indexOf(language) === 0 ? '' : '/' + language}/${base}`);
});
/**
* Get the language name
*/
hexo.extend.helper.register('language_name', function(language) {
const name = hexo.theme.i18n.__(language)('name');
return name === 'name' ? language : name;
});

View File

@ -0,0 +1,29 @@
/* global hexo */
'use strict';
hexo.extend.helper.register('next_font', () => {
const config = hexo.theme.config.font;
if (!config || !config.enable) return '';
const fontDisplay = '&display=swap';
const fontSubset = '&subset=latin,latin-ext';
const fontStyles = ':300,300italic,400,400italic,700,700italic';
const fontHost = config.host || '//fonts.googleapis.com';
//Get a font list from config
let fontFamilies = ['global', 'title', 'headings', 'posts', 'codes'].map(item => {
if (config[item] && config[item].family && config[item].external) {
return config[item].family + fontStyles;
}
return '';
});
fontFamilies = fontFamilies.filter(item => item !== '');
fontFamilies = [...new Set(fontFamilies)];
fontFamilies = fontFamilies.join('|');
// Merge extra parameters to the final processed font string
return fontFamilies ? `<link rel="stylesheet" href="${fontHost}/css?family=${fontFamilies.concat(fontDisplay, fontSubset)}">` : '';
});

View File

@ -0,0 +1,45 @@
/* global hexo */
'use strict';
const url = require('url');
/**
* Export theme config to js
*/
hexo.extend.helper.register('next_config', function() {
let { config, theme, next_version } = this;
config.algolia = config.algolia || {};
let exportConfig = {
hostname : url.parse(config.url).hostname || config.url,
root : config.root,
scheme : theme.scheme,
version : next_version,
exturl : theme.exturl,
sidebar : theme.sidebar,
copycode : theme.codeblock.copy_button,
back2top : theme.back2top,
bookmark : theme.bookmark,
fancybox : theme.fancybox,
mediumzoom: theme.mediumzoom,
lazyload : theme.lazyload,
pangu : theme.pangu,
comments : theme.comments,
algolia : {
appID : config.algolia.applicationID,
apiKey : config.algolia.apiKey,
indexName: config.algolia.indexName,
hits : theme.algolia_search.hits,
labels : theme.algolia_search.labels
},
localsearch: theme.local_search,
motion : theme.motion
};
if (config.search) {
exportConfig.path = config.search.path;
}
return `<script id="hexo-configurations">
var NexT = window.NexT || {};
var CONFIG = ${JSON.stringify(exportConfig)};
</script>`;
});

View File

@ -0,0 +1,61 @@
/* global hexo */
'use strict';
const { htmlTag } = require('hexo-util');
const url = require('url');
hexo.extend.helper.register('next_url', function(path, text, options = {}) {
const { config } = this;
const data = url.parse(path);
const siteHost = url.parse(config.url).hostname || config.url;
const theme = hexo.theme.config;
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) {
tag = 'span';
exturl = 'exturl';
const encoded = Buffer.from(path).toString('base64');
attrs = {
class : exturl,
'data-url': encoded
};
}
for (let key in options) {
/**
* If option have `class` attribute, add it to
* 'exturl' class if `exturl` option enabled.
*/
if (exturl !== '' && key === 'class') {
attrs[key] += ' ' + options[key];
} else {
attrs[key] = options[key];
}
}
if (attrs.class && Array.isArray(attrs.class)) {
attrs.class = attrs.class.join(' ');
}
// If it's external link, rewrite attributes.
if (data.protocol && data.hostname !== siteHost) {
attrs.external = null;
if (!theme.exturl) {
// Only for simple link need to rewrite/add attributes.
attrs.rel = 'noopener';
attrs.target = '_blank';
} else {
// Remove rel attributes for `exturl` in main menu.
attrs.rel = null;
}
}
return htmlTag(tag, attrs, decodeURI(text), false);
});

View File

@ -0,0 +1,37 @@
/* global hexo */
'use strict';
const nunjucks = require('nunjucks');
const path = require('path');
function njkCompile(data) {
const templateDir = path.dirname(data.path);
const env = nunjucks.configure(templateDir, {
autoescape: false
});
env.addFilter('attr', (dictionary, key, value) => {
dictionary[key] = value;
return dictionary;
});
env.addFilter('json', dictionary => {
return JSON.stringify(dictionary || '');
});
return nunjucks.compile(data.text, env, data.path);
}
function njkRenderer(data, locals) {
return njkCompile(data).render(locals);
}
// Return a compiled renderer.
njkRenderer.compile = function(data) {
const compiledTemplate = njkCompile(data);
// Need a closure to keep the compiled template.
return function(locals) {
return compiledTemplate.render(locals);
};
};
hexo.extend.renderer.register('njk', 'html', njkRenderer);
hexo.extend.renderer.register('swig', 'html', njkRenderer);

View File

@ -0,0 +1,31 @@
/**
* button.js | https://theme-next.org/docs/tag-plugins/button
*/
/* global hexo */
'use strict';
function postButton(args) {
args = args.join(' ').split(',');
let url = args[0];
let text = args[1] || '';
let icon = args[2] || '';
let title = args[3] || '';
if (!url) {
hexo.log.warn('URL can NOT be empty.');
}
text = text.trim();
icon = icon.trim();
icon = icon.startsWith('fa') ? icon : 'fa fa-' + icon;
title = title.trim();
return `<a class="btn" href="${url}"${title.length > 0 ? ` title="${title}"` : ''}>
${icon.length > 0 ? `<i class="${icon}"></i>` : ''}${text}
</a>`;
}
hexo.extend.tag.register('button', postButton, {ends: false});
hexo.extend.tag.register('btn', postButton, {ends: false});

View File

@ -0,0 +1,23 @@
/**
* caniuse.js | https://theme-next.org/docs/tag-plugins/caniuse
*/
/* global hexo */
'use strict';
function caniUse(args) {
args = args.join('').split('@');
var feature = args[0];
var periods = args[1] || 'current';
if (!feature) {
hexo.log.warn('Caniuse feature can NOT be empty.');
return '';
}
return `<iframe data-feature="${feature}" src="https://caniuse.bitsofco.de/embed/index.html?feat=${feature}&periods=${periods}&accessible-colours=false" frameborder="0" width="100%" height="400px"></iframe>`;
}
hexo.extend.tag.register('caniuse', caniUse);
hexo.extend.tag.register('can', caniUse);

View File

@ -0,0 +1,18 @@
/**
* center-quote.js | https://theme-next.org/docs/tag-plugins/
*/
/* global hexo */
'use strict';
function centerQuote(args, content) {
return `<blockquote class="blockquote-center">
<i class="fa fa-quote-left"></i>
${hexo.render.renderSync({ text: content, engine: 'markdown' })}
<i class="fa fa-quote-right"></i>
</blockquote>`;
}
hexo.extend.tag.register('centerquote', centerQuote, {ends: true});
hexo.extend.tag.register('cq', centerQuote, {ends: true});

View File

@ -0,0 +1,141 @@
/**
* group-pictures.js | https://theme-next.org/docs/tag-plugins/group-pictures
*/
/* global hexo */
'use strict';
var LAYOUTS = {
2: {
1: [1, 1],
2: [2]
},
3: {
1: [3],
2: [1, 2],
3: [2, 1]
},
4: {
1: [1, 2, 1],
2: [1, 3],
3: [2, 2],
4: [3, 1]
},
5: {
1: [1, 2, 2],
2: [2, 1, 2],
3: [2, 3],
4: [3, 2]
},
6: {
1: [1, 2, 3],
2: [1, 3, 2],
3: [2, 1, 3],
4: [2, 2, 2],
5: [3, 3]
},
7: {
1: [1, 2, 2, 2],
2: [1, 3, 3],
3: [2, 2, 3],
4: [2, 3, 2],
5: [3, 2, 2]
},
8: {
1: [1, 2, 2, 3],
2: [1, 2, 3, 2],
3: [1, 3, 2, 2],
4: [2, 2, 2, 2],
5: [2, 3, 3],
6: [3, 2, 3],
7: [3, 3, 2]
},
9: {
1: [1, 2, 3, 3],
2: [1, 3, 2, 3],
3: [2, 2, 2, 3],
4: [2, 2, 3, 2],
5: [2, 3, 2, 2],
6: [3, 2, 2, 2],
7: [3, 3, 3]
},
10: {
1: [1, 3, 3, 3],
2: [2, 2, 3, 3],
3: [2, 3, 2, 3],
4: [2, 3, 3, 2],
5: [3, 2, 2, 3],
6: [3, 2, 3, 2],
7: [3, 3, 2, 2]
}
};
function groupBy(group, data) {
var r = [];
for (let count of group) {
r.push(data.slice(0, count));
data = data.slice(count);
}
return r;
}
var templates = {
dispatch: function(pictures, group, layout) {
var rule = LAYOUTS[group] ? LAYOUTS[group][layout] : null;
return rule ? this.getHTML(groupBy(rule, pictures)) : templates.defaults(pictures);
},
/**
* Defaults Layout
*
* □ □ □
* □ □ □
* ...
*
* @param pictures
*/
defaults: function(pictures) {
var ROW_SIZE = 3;
var rows = pictures.length / ROW_SIZE;
var pictureArr = [];
for (var i = 0; i < rows; i++) {
pictureArr.push(pictures.slice(i * ROW_SIZE, (i + 1) * ROW_SIZE));
}
return this.getHTML(pictureArr);
},
getHTML: function(rows) {
var rowHTML = rows.map(row => {
return `<div class="group-picture-row">${this.getColumnHTML(row)}</div>`;
}).join('');
return `<div class="group-picture-container">${rowHTML}</div>`;
},
getColumnHTML: function(pictures) {
var columnWidth = 100 / pictures.length;
var columnStyle = `style="width: ${columnWidth}%;"`;
return pictures.map(picture => {
return `<div class="group-picture-column" ${columnStyle}>${picture}</div>`;
}).join('');
}
};
function groupPicture(args, content) {
args = args[0].split('-');
var group = parseInt(args[0], 10);
var layout = parseInt(args[1], 10);
content = hexo.render.renderSync({text: content, engine: 'markdown'});
var pictures = content.match(/<img[\s\S]*?>/g);
return `<div class="group-picture">${templates.dispatch(pictures, group, layout)}</div>`;
}
hexo.extend.tag.register('grouppicture', groupPicture, {ends: true});
hexo.extend.tag.register('gp', groupPicture, {ends: true});

View File

@ -0,0 +1,19 @@
/**
* label.js | https://theme-next.org/docs/tag-plugins/label
*/
/* global hexo */
'use strict';
function postLabel(args) {
args = args.join(' ').split('@');
var classes = args[0] || 'default';
var text = args[1] || '';
!text && hexo.log.warn('Label text must be defined!');
return `<span class="label ${classes.trim()}">${text}</span>`;
}
hexo.extend.tag.register('label', postLabel, {ends: false});

View File

@ -0,0 +1,16 @@
/**
* mermaid.js | https://theme-next.org/docs/tag-plugins/mermaid
*/
/* global hexo */
'use strict';
function mermaid(args, content) {
return `<pre class="mermaid" style="text-align: center;">
${args.join(' ')}
${content}
</pre>`;
}
hexo.extend.tag.register('mermaid', mermaid, {ends: true});

View File

@ -0,0 +1,16 @@
/**
* note.js | https://theme-next.org/docs/tag-plugins/note
*/
/* global hexo */
'use strict';
function postNote(args, content) {
return `<div class="note ${args.join(' ')}">
${hexo.render.renderSync({text: content, engine: 'markdown'}).split('\n').join('')}
</div>`;
}
hexo.extend.tag.register('note', postNote, {ends: true});
hexo.extend.tag.register('subnote', postNote, {ends: true});

View File

@ -0,0 +1,14 @@
/**
* pdf.js | https://theme-next.org/docs/tag-plugins/pdf
*/
/* global hexo */
'use strict';
function pdf(args) {
let theme = hexo.theme.config;
return `<div class="pdfobject-container" data-target="${args[0]}" data-height="${args[1] || theme.pdf.height}"></div>`;
}
hexo.extend.tag.register('pdf', pdf, {ends: false});

View File

@ -0,0 +1,61 @@
/**
* tabs.js | https://theme-next.org/docs/tag-plugins/tabs
*/
/* global hexo */
'use strict';
function postTabs(args, content) {
var tabBlock = /<!--\s*tab (.*?)\s*-->\n([\w\W\s\S]*?)<!--\s*endtab\s*-->/g;
args = args.join(' ').split(',');
var tabName = args[0];
var tabActive = Number(args[1]) || 0;
var matches = [];
var match;
var tabId = 0;
var tabNav = '';
var tabContent = '';
!tabName && hexo.log.warn('Tabs block must have unique name!');
while ((match = tabBlock.exec(content)) !== null) {
matches.push(match[1]);
matches.push(match[2]);
}
for (var i = 0; i < matches.length; i += 2) {
var tabParameters = matches[i].split('@');
var postContent = matches[i + 1];
var tabCaption = tabParameters[0] || '';
var tabIcon = tabParameters[1] || '';
var tabHref = '';
postContent = hexo.render.renderSync({text: postContent, engine: 'markdown'}).trim();
tabId += 1;
tabHref = (tabName + ' ' + tabId).toLowerCase().split(' ').join('-');
((tabCaption.length === 0) && (tabIcon.length === 0)) && (tabCaption = tabName + ' ' + tabId);
var isOnlyicon = tabIcon.length > 0 && tabCaption.length === 0 ? ' style="text-align: center;"' : '';
let icon = tabIcon.trim();
icon = icon.startsWith('fa') ? icon : 'fa fa-' + icon;
tabIcon.length > 0 && (tabIcon = `<i class="${icon}"${isOnlyicon}></i>`);
var isActive = (tabActive > 0 && tabActive === tabId) || (tabActive === 0 && tabId === 1) ? ' active' : '';
tabNav += `<li class="tab${isActive}"><a href="#${tabHref}">${tabIcon + tabCaption.trim()}</a></li>`;
tabContent += `<div class="tab-pane${isActive}" id="${tabHref}">${postContent}</div>`;
}
tabNav = `<ul class="nav-tabs">${tabNav}</ul>`;
tabContent = `<div class="tab-content">${tabContent}</div>`;
return `<div class="tabs" id="${tabName.toLowerCase().split(' ').join('-')}">${tabNav + tabContent}</div>`;
}
hexo.extend.tag.register('tabs', postTabs, {ends: true});
hexo.extend.tag.register('subtabs', postTabs, {ends: true});
hexo.extend.tag.register('subsubtabs', postTabs, {ends: true});

View File

@ -0,0 +1,13 @@
/**
* video.js | https://theme-next.org/docs/tag-plugins/video
*/
/* global hexo */
'use strict';
function postVideo(args) {
return `<video src="${args}" preload="metadata" controls playsinline poster="">Sorry, your browser does not support the video tag.</video>`;
}
hexo.extend.tag.register('video', postVideo, {ends: false});