feat(script): add config checking specs
parent
b60e51694d
commit
c56a3c46c4
|
@ -1,95 +0,0 @@
|
|||
# Website's icon url.
|
||||
favicon: /favicon.png
|
||||
|
||||
# Open Graph metadata (https://hexo.io/docs/helpers.html#open-graph)
|
||||
open_graph:
|
||||
fb_app_id:
|
||||
fb_admins:
|
||||
twitter_id:
|
||||
google_plus:
|
||||
|
||||
rss:
|
||||
|
||||
# Website's logo shown on the left of the navigation bar.
|
||||
logo:
|
||||
|
||||
# Navigation bar menu links.
|
||||
menu:
|
||||
Home: /
|
||||
Archives: /archives
|
||||
Categories: /categories
|
||||
Tags: /tags
|
||||
About: /about
|
||||
|
||||
article:
|
||||
highlight: atom-one-light
|
||||
thumbnail: true
|
||||
|
||||
search:
|
||||
type: insight
|
||||
|
||||
comment:
|
||||
type: disqus
|
||||
shortname: hexo-theme-icarus
|
||||
|
||||
share:
|
||||
type: sharethis
|
||||
install_url:
|
||||
|
||||
widgets:
|
||||
- type: profile
|
||||
position: left
|
||||
author: PPOffice
|
||||
author_title: Web Developer
|
||||
location: Earth, Solar System
|
||||
avatar:
|
||||
gravatar:
|
||||
follow_link: http://github.com/ppoffice
|
||||
social_links:
|
||||
Github:
|
||||
icon: fab fa-github
|
||||
url: http://github.com/ppoffice
|
||||
Facebook:
|
||||
icon: fab fa-facebook
|
||||
url: http://facebook.com
|
||||
Twitter:
|
||||
icon: fab fa-twitter
|
||||
url: http://twitter.com
|
||||
Dribbble:
|
||||
icon: fab fa-dribbble
|
||||
url: http://dribbble.com
|
||||
RSS:
|
||||
icon: fas fa-rss
|
||||
url: /
|
||||
- type: toc
|
||||
position: left
|
||||
- type: links
|
||||
position: left
|
||||
links:
|
||||
Hexo: https://hexo.io
|
||||
Github: https://github.com/ppoffice
|
||||
- type: category
|
||||
position: left
|
||||
- type: tagcloud
|
||||
position: left
|
||||
- type: recent_posts
|
||||
position: right
|
||||
- type: archive
|
||||
position: right
|
||||
- type: tag
|
||||
position: right
|
||||
|
||||
plugins:
|
||||
gallery: true
|
||||
outdated-browser: true
|
||||
animejs: true
|
||||
mathjax: true
|
||||
google-analytics:
|
||||
tracking_id:
|
||||
baidu-analytics:
|
||||
tracking_id:
|
||||
|
||||
providers:
|
||||
cdn:
|
||||
fontcdn:
|
||||
iconcdn:
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* CDN static file resolvers.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* <%- cdn(package, version, filename) %>
|
||||
* <%- fontcdn(fontName) %>
|
||||
|
@ -23,9 +23,9 @@ const icon_providers = {
|
|||
};
|
||||
|
||||
module.exports = function (hexo) {
|
||||
hexo.extend.helper.register('cdn', function (package, version, filename, provider = 'cdnjs') {
|
||||
provider = hexo.extend.helper.get('get_config').bind(this)('providers.cdn', provider);
|
||||
if (provider != null && cdn_providers.hasOwnProperty(provider)) {
|
||||
hexo.extend.helper.register('cdn', function (package, version, filename) {
|
||||
let provider = hexo.extend.helper.get('get_config').bind(this)('providers.cdn');
|
||||
if (provider !== null && cdn_providers.hasOwnProperty(provider)) {
|
||||
provider = cdn_providers[provider];
|
||||
}
|
||||
return provider.replace(/\${\s*package\s*}/gi, package)
|
||||
|
@ -33,18 +33,22 @@ module.exports = function (hexo) {
|
|||
.replace(/\${\s*filename\s*}/gi, filename);
|
||||
});
|
||||
|
||||
hexo.extend.helper.register('fontcdn', function (fontName, provider = 'google') {
|
||||
provider = hexo.extend.helper.get('get_config').bind(this)('providers.font', provider);
|
||||
if (provider != null && font_providers.hasOwnProperty(provider)) {
|
||||
hexo.extend.helper.register('fontcdn', function (fontName) {
|
||||
let provider = hexo.extend.helper.get('get_config').bind(this)('providers.fontcdn');
|
||||
if (provider !== null && font_providers.hasOwnProperty(provider)) {
|
||||
provider = font_providers[provider];
|
||||
}
|
||||
return provider.replace(/\${\s*fontname\s*}/gi, fontName);
|
||||
});
|
||||
|
||||
hexo.extend.helper.register('iconcdn', function (provider = 'fontawesome') {
|
||||
provider = hexo.extend.helper.get('get_config').bind(this)('providers.icon', provider);
|
||||
if (provider != null && icon_providers.hasOwnProperty(provider)) {
|
||||
hexo.extend.helper.register('iconcdn', function (provider = null) {
|
||||
if (provider !== null && icon_providers.hasOwnProperty(provider)) {
|
||||
provider = icon_providers[provider];
|
||||
} else {
|
||||
provider = hexo.extend.helper.get('get_config').bind(this)('providers.iconcdn');
|
||||
if (provider !== null && icon_providers.hasOwnProperty(provider)) {
|
||||
provider = icon_providers[provider];
|
||||
}
|
||||
}
|
||||
return provider;
|
||||
});
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
/**
|
||||
* Theme configuration helpers.
|
||||
*
|
||||
*
|
||||
* @description Test if a configuration is set or fetch its value. If `exclude_page` is set, the helpers will
|
||||
* not look up configurations in the current page's front matter.
|
||||
* @example
|
||||
* <%- has_config(config_name, exclude_page) %>
|
||||
* <%- get_config(config_name, default_value, exclude_page) %>
|
||||
*/
|
||||
const specs = require('../specs/_config.yml');
|
||||
const descriptors = require('../specs/common').descriptor;
|
||||
|
||||
module.exports = function (hexo) {
|
||||
function readProperty(object, path) {
|
||||
const paths = path.split('.');
|
||||
|
@ -19,10 +22,18 @@ module.exports = function (hexo) {
|
|||
return object;
|
||||
}
|
||||
|
||||
hexo.extend.helper.register('get_config', function (configName, defaultValue = null, excludePage = false) {
|
||||
hexo.extend.helper.register('get_config', function (configName, defaultValue = undefined, excludePage = false) {
|
||||
const value = readProperty(Object.assign({}, this.config, hexo.theme.config,
|
||||
!excludePage ? this.page : {}), configName);
|
||||
return value == null ? defaultValue : value;
|
||||
if (value === null) {
|
||||
if (typeof(defaultValue) !== 'undefined') {
|
||||
return defaultValue;
|
||||
} else {
|
||||
const property = readProperty(specs, configName);
|
||||
return property === null ? null : property[descriptors.defaultValue];
|
||||
}
|
||||
}
|
||||
return value;
|
||||
});
|
||||
|
||||
hexo.extend.helper.register('has_config', function (configName, excludePage = false) {
|
||||
|
@ -32,6 +43,6 @@ module.exports = function (hexo) {
|
|||
|
||||
hexo.extend.helper.register('get_config_from_obj', function (object, configName, defaultValue = null) {
|
||||
const value = readProperty(object, configName);
|
||||
return value == null ? defaultValue : value;
|
||||
return value === null ? defaultValue : value;
|
||||
});
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* Helper functions for controlling layout.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* <%- get_widgets(position) %>
|
||||
* <%- has_column() %>
|
||||
|
@ -8,7 +8,11 @@
|
|||
*/
|
||||
module.exports = function (hexo) {
|
||||
hexo.extend.helper.register('get_widgets', function (position) {
|
||||
const widgets = hexo.extend.helper.get('get_config').bind(this)('widgets', []);
|
||||
const hasWidgets = hexo.extend.helper.get('has_config').bind(this)('widgets');
|
||||
if (!hasWidgets) {
|
||||
return [];
|
||||
}
|
||||
const widgets = hexo.extend.helper.get('get_config').bind(this)('widgets');
|
||||
return widgets.filter(widget => widget.hasOwnProperty('position') && widget.position === position);
|
||||
});
|
||||
|
||||
|
|
|
@ -0,0 +1,473 @@
|
|||
const { version } = require('../../package.json');
|
||||
const { type, required, defaultValue, description, condition } = require('./common').descriptor;
|
||||
const desc = description;
|
||||
|
||||
const IconLink = {
|
||||
[type]: 'object',
|
||||
[desc]: 'Link icon settings',
|
||||
'*': {
|
||||
[type]: ['string', 'object'],
|
||||
[desc]: 'Path or URL to the menu item, and/or link icon class names',
|
||||
icon: {
|
||||
[required]: true,
|
||||
[type]: 'string',
|
||||
[desc]: 'Link icon class names'
|
||||
},
|
||||
url: {
|
||||
[required]: true,
|
||||
[type]: 'string',
|
||||
[desc]: 'Path or URL to the menu item'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const DEFAULT_WIDGETS = [
|
||||
{
|
||||
type: 'profile',
|
||||
position: 'left',
|
||||
author: 'Your name',
|
||||
author_title: 'Your title',
|
||||
location: 'Your location',
|
||||
avatar: null,
|
||||
gravatar: null,
|
||||
follow_link: 'http://github.com/ppoffice',
|
||||
social_links: {
|
||||
Github: {
|
||||
icon: 'fab fa-github',
|
||||
url: 'http://github.com/ppoffice'
|
||||
},
|
||||
Facebook: {
|
||||
icon: 'fab fa-facebook',
|
||||
url: 'http://facebook.com'
|
||||
},
|
||||
Twitter: {
|
||||
icon: 'fab fa-twitter',
|
||||
url: 'http://twitter.com'
|
||||
},
|
||||
Dribbble: {
|
||||
icon: 'fab fa-dribbble',
|
||||
url: 'http://dribbble.com'
|
||||
},
|
||||
RSS: {
|
||||
icon: 'fas fa-rss',
|
||||
url: '/'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'toc',
|
||||
position: 'left'
|
||||
},
|
||||
{
|
||||
type: 'links',
|
||||
position: 'left',
|
||||
links: {
|
||||
Hexo: 'https://hexo.io',
|
||||
Github: 'https://github.com/ppoffice'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
position: 'left'
|
||||
},
|
||||
{
|
||||
type: 'tagcloud',
|
||||
position: 'left'
|
||||
},
|
||||
{
|
||||
type: 'recent_posts',
|
||||
position: 'right'
|
||||
},
|
||||
{
|
||||
type: 'archive',
|
||||
position: 'right'
|
||||
},
|
||||
{
|
||||
type: 'tag',
|
||||
position: 'right'
|
||||
}
|
||||
];
|
||||
|
||||
module.exports = {
|
||||
[type]: 'object',
|
||||
[desc]: 'Root of the configuration file',
|
||||
[required]: true,
|
||||
version: {
|
||||
[type]: 'string',
|
||||
[desc]: 'Version of the Icarus theme that is currently used',
|
||||
[required]: true,
|
||||
[defaultValue]: version
|
||||
},
|
||||
favicon: {
|
||||
[type]: 'string',
|
||||
[desc]: 'Path or URL to the website\'s icon',
|
||||
[defaultValue]: null
|
||||
},
|
||||
open_graph: {
|
||||
[type]: 'object',
|
||||
[desc]: 'Open Graph metadata (https://hexo.io/docs/helpers.html#open-graph)',
|
||||
fb_app_id: {
|
||||
[type]: 'string',
|
||||
[desc]: 'Facebook App ID',
|
||||
[defaultValue]: null
|
||||
},
|
||||
fb_admins: {
|
||||
[type]: 'string',
|
||||
[desc]: 'Facebook Admin ID',
|
||||
[defaultValue]: null
|
||||
},
|
||||
twitter_id: {
|
||||
[type]: 'string',
|
||||
[desc]: 'Twitter ID',
|
||||
[defaultValue]: null
|
||||
},
|
||||
twitter_site: {
|
||||
[type]: 'string',
|
||||
[desc]: 'Twitter site',
|
||||
[defaultValue]: null
|
||||
},
|
||||
google_plus: {
|
||||
[type]: 'string',
|
||||
[desc]: 'Google+ profile link',
|
||||
[defaultValue]: null
|
||||
}
|
||||
},
|
||||
rss: {
|
||||
[type]: 'string',
|
||||
[desc]: 'Path or URL to RSS atom.xml',
|
||||
[defaultValue]: null
|
||||
},
|
||||
logo: {
|
||||
[type]: ['string', 'object'],
|
||||
[defaultValue]: '/images/logo.svg',
|
||||
[desc]: 'Path or URL to the website\'s logo to be shown on the left of the navigation bar or footer',
|
||||
text: {
|
||||
[type]: 'string',
|
||||
[desc]: 'Text to be shown in place of the logo image'
|
||||
}
|
||||
},
|
||||
navbar: {
|
||||
[type]: 'object',
|
||||
[desc]: 'Navigation bar link settings',
|
||||
menu: {
|
||||
[type]: 'object',
|
||||
[desc]: 'Navigation bar menu links',
|
||||
[defaultValue]: {
|
||||
Home: '/',
|
||||
Archives: '/archives',
|
||||
Categories: '/categories',
|
||||
Tags: '/tags',
|
||||
About: '/about'
|
||||
},
|
||||
'*': {
|
||||
[type]: 'string',
|
||||
[desc]: 'Path or URL to the menu item'
|
||||
}
|
||||
},
|
||||
links: {
|
||||
...IconLink,
|
||||
[desc]: 'Navigation bar links to be shown on the right',
|
||||
[defaultValue]: {
|
||||
'Download on GitHub': {
|
||||
icon: 'fab fa-github',
|
||||
url: 'http://github.com/ppoffice/hexo-theme-icarus'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
footer: {
|
||||
[type]: 'object',
|
||||
[description]: 'Footer section link settings',
|
||||
links: {
|
||||
...IconLink,
|
||||
[desc]: 'Links to be shown on the right of the footer section',
|
||||
[defaultValue]: {
|
||||
'Creative Commons': {
|
||||
icon: 'fab fa-creative-commons',
|
||||
url: 'https://creativecommons.org/'
|
||||
},
|
||||
'Attribution 4.0 International': {
|
||||
icon: 'fab fa-creative-commons-by',
|
||||
url: 'https://creativecommons.org/licenses/by/4.0/'
|
||||
},
|
||||
'Download on GitHub': {
|
||||
icon: 'fab fa-github',
|
||||
url: 'http://github.com/ppoffice/hexo-theme-icarus'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
article: {
|
||||
[type]: 'object',
|
||||
[desc]: 'Article display settings',
|
||||
highlight: {
|
||||
[type]: 'string',
|
||||
[desc]: 'Code highlight theme (https://github.com/highlightjs/highlight.js/tree/master/src/styles)',
|
||||
[defaultValue]: 'atom-one-light'
|
||||
},
|
||||
thumbnail: {
|
||||
[type]: 'boolean',
|
||||
[desc]: 'Whether to show article thumbnail images',
|
||||
[defaultValue]: true
|
||||
},
|
||||
readtime: {
|
||||
[type]: 'boolean',
|
||||
[desc]: 'Whether to show estimate article reading time',
|
||||
[defaultValue]: true
|
||||
}
|
||||
},
|
||||
search: {
|
||||
[type]: 'object',
|
||||
[desc]: 'Search plugin settings (http://ppoffice.github.io/hexo-theme-icarus/categories/Configuration/Search-Plugins)',
|
||||
type: {
|
||||
[type]: 'string',
|
||||
[desc]: 'Name of the search plugin',
|
||||
[defaultValue]: 'insight'
|
||||
},
|
||||
cx: {
|
||||
[type]: 'string',
|
||||
[desc]: 'Google CSE cx value',
|
||||
[required]: true,
|
||||
[condition]: parent => parent.type === 'google-cse'
|
||||
}
|
||||
},
|
||||
comment: {
|
||||
[type]: 'object',
|
||||
[desc]: 'Comment plugin settings (http://ppoffice.github.io/hexo-theme-icarus/categories/Configuration/Comment-Plugins)',
|
||||
type: {
|
||||
[type]: 'string',
|
||||
[desc]: 'Name of the comment plugin',
|
||||
[defaultValue]: null
|
||||
},
|
||||
appid: {
|
||||
[type]: 'string',
|
||||
[desc]: 'Changyan comment app ID',
|
||||
[required]: true,
|
||||
[condition]: parent => parent.type === 'changyan'
|
||||
},
|
||||
conf: {
|
||||
[type]: 'string',
|
||||
[desc]: 'Changyan comment configuration ID',
|
||||
[required]: true,
|
||||
[condition]: parent => parent.type === 'changyan'
|
||||
},
|
||||
shortname: {
|
||||
[type]: 'string',
|
||||
[desc]: 'Disqus shortname',
|
||||
[required]: true,
|
||||
[condition]: parent => parent.type === 'disqus'
|
||||
},
|
||||
owner: {
|
||||
[type]: 'string',
|
||||
[desc]: 'Your GitHub ID',
|
||||
[required]: true,
|
||||
[condition]: parent => parent.type === 'gitment'
|
||||
},
|
||||
repo: {
|
||||
[type]: 'string',
|
||||
[desc]: 'The repo to store comments',
|
||||
[required]: true,
|
||||
[condition]: parent => parent.type === 'gitment'
|
||||
},
|
||||
client_id: {
|
||||
[type]: 'string',
|
||||
[desc]: 'Your client ID',
|
||||
[required]: true,
|
||||
[condition]: parent => parent.type === 'gitment'
|
||||
},
|
||||
client_secret: {
|
||||
[type]: 'string',
|
||||
[desc]: 'Your client secret',
|
||||
[required]: true,
|
||||
[condition]: parent => parent.type === 'gitment'
|
||||
},
|
||||
url: {
|
||||
[type]: 'string',
|
||||
[desc]: 'URL to your Isso comment service',
|
||||
[required]: true,
|
||||
[condition]: parent => parent.type === 'isso'
|
||||
},
|
||||
uid: {
|
||||
[type]: 'string',
|
||||
[desc]: 'LiveRe comment service UID',
|
||||
[required]: true,
|
||||
[condition]: parent => parent.type === 'livere'
|
||||
},
|
||||
app_id: {
|
||||
[type]: 'boolean',
|
||||
[desc]: 'LeanCloud APP ID',
|
||||
[required]: true,
|
||||
[condition]: parent => parent.type === 'valine'
|
||||
},
|
||||
app_key: {
|
||||
[type]: 'boolean',
|
||||
[desc]: 'LeanCloud APP key',
|
||||
[required]: true,
|
||||
[condition]: parent => parent.type === 'valine'
|
||||
},
|
||||
notify: {
|
||||
[type]: 'boolean',
|
||||
[desc]: 'Enable email notification when someone comments',
|
||||
[defaultValue]: false,
|
||||
[condition]: parent => parent.type === 'valine'
|
||||
},
|
||||
verify: {
|
||||
[type]: 'boolean',
|
||||
[desc]: 'Enable verification code service',
|
||||
[defaultValue]: false,
|
||||
[condition]: parent => parent.type === 'valine'
|
||||
},
|
||||
placeholder: {
|
||||
[type]: 'boolean',
|
||||
[desc]: 'Placeholder text in the comment box',
|
||||
[defaultValue]: 'Say something...',
|
||||
[condition]: parent => parent.type === 'valine'
|
||||
}
|
||||
},
|
||||
share: {
|
||||
[type]: 'object',
|
||||
[desc]: 'Share plugin settings (http://ppoffice.github.io/hexo-theme-icarus/categories/Configuration/Share-Plugins)',
|
||||
type: {
|
||||
[type]: 'string',
|
||||
[desc]: 'Share plugin name',
|
||||
[defaultValue]: null
|
||||
},
|
||||
install_url: {
|
||||
[type]: 'string',
|
||||
[desc]: 'URL to the share plugin script provided by share plugin service provider',
|
||||
[required]: true,
|
||||
[condition]: parent => parent.type === 'sharethis' || parent.type === 'addthis'
|
||||
}
|
||||
},
|
||||
widgets: {
|
||||
[type]: 'array',
|
||||
[desc]: 'Sidebar widget settings',
|
||||
[defaultValue]: DEFAULT_WIDGETS,
|
||||
'*': {
|
||||
[type]: 'object',
|
||||
[desc]: 'Single widget settings',
|
||||
type: {
|
||||
[type]: 'string',
|
||||
[desc]: 'Widget name',
|
||||
[required]: true,
|
||||
[defaultValue]: 'profile'
|
||||
},
|
||||
position: {
|
||||
[type]: 'string',
|
||||
[desc]: 'Where should the widget be placed, left or right',
|
||||
[required]: true,
|
||||
[defaultValue]: 'left'
|
||||
},
|
||||
author: {
|
||||
[type]: 'string',
|
||||
[desc]: 'Author name to be shown in the profile widget',
|
||||
[condition]: parent => parent.type === 'profile',
|
||||
[defaultValue]: 'Your name'
|
||||
},
|
||||
author_title: {
|
||||
[type]: 'string',
|
||||
[desc]: 'Title of the author to be shown in the profile widget',
|
||||
[condition]: parent => parent.type === 'profile',
|
||||
[defaultValue]: 'Your title'
|
||||
},
|
||||
location: {
|
||||
[type]: 'string',
|
||||
[desc]: 'Author\'s current location to be shown in the profile widget',
|
||||
[condition]: parent => parent.type === 'profile',
|
||||
[defaultValue]: 'Your location'
|
||||
},
|
||||
avatar: {
|
||||
[type]: 'string',
|
||||
[desc]: 'Path or URL to the avatar to be shown in the profile widget',
|
||||
[condition]: parent => parent.type === 'profile',
|
||||
[defaultValue]: '/images/avatar.png'
|
||||
},
|
||||
gravatar: {
|
||||
[type]: 'string',
|
||||
[desc]: 'Email address for the Gravatar to be shown in the profile widget',
|
||||
[condition]: parent => parent.type === 'profile'
|
||||
},
|
||||
follow_link: {
|
||||
[type]: 'string',
|
||||
[desc]: 'Path or URL for the follow button',
|
||||
[condition]: parent => parent.type === 'profile'
|
||||
},
|
||||
social_links: {
|
||||
...IconLink,
|
||||
[desc]: 'Links to be shown on the bottom of the profile widget',
|
||||
[condition]: parent => parent.type === 'profile'
|
||||
},
|
||||
links: {
|
||||
[type]: 'object',
|
||||
[desc]: 'Links to be shown in the links widget',
|
||||
[condition]: parent => parent.type === 'links',
|
||||
'*': {
|
||||
[type]: 'string',
|
||||
[desc]: 'Path or URL to the link',
|
||||
[required]: true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
[type]: 'object',
|
||||
[desc]: 'Other plugin settings',
|
||||
gallery: {
|
||||
[type]: 'boolean',
|
||||
[desc]: 'Enable the lightGallery and Justified Gallery plugins',
|
||||
[defaultValue]: true
|
||||
},
|
||||
'outdated-browser': {
|
||||
[type]: 'boolean',
|
||||
[desc]: 'Enable the Outdated Browser plugin',
|
||||
[defaultValue]: true
|
||||
},
|
||||
animejs: {
|
||||
[type]: 'boolean',
|
||||
[desc]: 'Enable page animations',
|
||||
[defaultValue]: true
|
||||
},
|
||||
mathjax: {
|
||||
[type]: 'boolean',
|
||||
[desc]: 'Enable the MathJax plugin',
|
||||
[defaultValue]: true
|
||||
},
|
||||
'google-analytics': {
|
||||
[type]: ['boolean', 'object'],
|
||||
[desc]: 'Google Analytics plugin settings (http://ppoffice.github.io/hexo-theme-icarus/2018/01/01/plugin/Analytics/#Google-Analytics)',
|
||||
tracking_id: {
|
||||
[type]: 'string',
|
||||
[desc]: 'Google Analytics tracking id',
|
||||
[defaultValue]: null
|
||||
}
|
||||
},
|
||||
'baidu-analytics': {
|
||||
[type]: ['boolean', 'object'],
|
||||
[desc]: 'Baidu Analytics plugin settings (http://ppoffice.github.io/hexo-theme-icarus/2018/01/01/plugin/Analytics/#Baidu-Analytics)',
|
||||
tracking_id: {
|
||||
[type]: 'string',
|
||||
[desc]: 'Baidu Analytics tracking id',
|
||||
[defaultValue]: null
|
||||
}
|
||||
}
|
||||
},
|
||||
providers: {
|
||||
[type]: 'object',
|
||||
[desc]: 'CDN provider settings',
|
||||
cdn: {
|
||||
[type]: 'string',
|
||||
[desc]: 'Name or URL of the JavaScript and/or stylesheet CDN provider',
|
||||
[defaultValue]: 'cdnjs'
|
||||
},
|
||||
fontcdn: {
|
||||
[type]: 'string',
|
||||
[desc]: 'Name or URL of the webfont CDN provider',
|
||||
[defaultValue]: 'google'
|
||||
},
|
||||
iconcdn: {
|
||||
[type]: 'string',
|
||||
[desc]: 'Name or URL of the webfont Icon CDN provider',
|
||||
[defaultValue]: 'fontawesome'
|
||||
}
|
||||
}
|
||||
};
|
|
@ -0,0 +1,24 @@
|
|||
module.exports = {
|
||||
projectName: 'Icarus',
|
||||
is: {
|
||||
string(value) {
|
||||
return typeof(value) === 'string';
|
||||
},
|
||||
array(value) {
|
||||
return Array.isArray(value);
|
||||
},
|
||||
boolean(value) {
|
||||
return typeof(value) === 'boolean';
|
||||
},
|
||||
object(value) {
|
||||
return typeof(value) === 'object' && value.constructor == Object;
|
||||
}
|
||||
},
|
||||
descriptor: {
|
||||
type: Symbol('@type'),
|
||||
required: Symbol('@required'),
|
||||
description: Symbol('@description'),
|
||||
defaultValue: Symbol('@defaultValue'),
|
||||
condition: Symbol('@condition'),
|
||||
}
|
||||
};
|
|
@ -1,10 +1,260 @@
|
|||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const yaml = require('js-yaml');
|
||||
const logger = require('hexo-log')();
|
||||
const Schema = require('js-yaml/lib/js-yaml/schema');
|
||||
const Type = require('js-yaml/lib/js-yaml/type');
|
||||
|
||||
const conf = path.join(__dirname, '../..', '_config.yml');
|
||||
const rootSpec = require('../specs/_config.yml');
|
||||
const { projectName, is } = require('../specs/common');
|
||||
const { type, required, condition, defaultValue, description } = require('../specs/common').descriptor;
|
||||
|
||||
logger.info('Checking if the configuration file exists');
|
||||
if (!fs.existsSync(conf)) {
|
||||
logger.warn(`${conf} is not found. Please create one from the template _config.yml.example.`)
|
||||
}
|
||||
const UNDEFINED = Symbol('undefined');
|
||||
const CONFIG_PATH = path.join(__dirname, '../..', '_config.yml');
|
||||
const YAML_SCHEMA = new Schema({
|
||||
include: [
|
||||
require('js-yaml/lib/js-yaml/schema/default_full')
|
||||
],
|
||||
implicit: [
|
||||
new Type('tag:yaml.org,2002:null', {
|
||||
kind: 'scalar',
|
||||
resolve(data) {
|
||||
if (data === null) {
|
||||
return true;
|
||||
}
|
||||
const max = data.length;
|
||||
return (max === 1 && data === '~') ||
|
||||
(max === 4 && (data === 'null' || data === 'Null' || data === 'NULL'));
|
||||
},
|
||||
construct: () => null,
|
||||
predicate: object => object === null,
|
||||
represent: {
|
||||
empty: function () { return ''; }
|
||||
},
|
||||
defaultStyle: 'empty'
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
function toPlainObject(object, depth) {
|
||||
if (object === null || (!is.object(object) && !is.array(object))) {
|
||||
return object;
|
||||
}
|
||||
if (depth <= 0) {
|
||||
return is.array(object) ? '[Array]' : '[Object]';
|
||||
}
|
||||
if (is.array(object)) {
|
||||
const result = [];
|
||||
for (let child of object) {
|
||||
result.push(toPlainObject(child, depth - 1));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
const result = {};
|
||||
for (let key in object) {
|
||||
result[key] = toPlainObject(object[key], depth - 1);
|
||||
}
|
||||
for (let key of Object.getOwnPropertySymbols(object)) {
|
||||
result[key.toString()] = toPlainObject(object[key], depth - 1);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function isValidSpec(spec) {
|
||||
if (!spec.hasOwnProperty(type)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function checkPrecondition(spec, parameter) {
|
||||
if (!spec.hasOwnProperty(condition)) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
if (spec[condition](parameter) === true) {
|
||||
return true;
|
||||
}
|
||||
} catch (e) { }
|
||||
return false;
|
||||
}
|
||||
|
||||
function createDefaultConfig(spec, parentConfig = null) {
|
||||
if (!isValidSpec(spec)) {
|
||||
return UNDEFINED;
|
||||
}
|
||||
if (!checkPrecondition(spec, parentConfig)) {
|
||||
return UNDEFINED;
|
||||
}
|
||||
if (spec.hasOwnProperty(defaultValue)) {
|
||||
return spec[defaultValue];
|
||||
}
|
||||
const types = is.array(spec[type]) ? spec[type] : [spec[type]];
|
||||
if (types.includes('object')) {
|
||||
let defaults = UNDEFINED;
|
||||
for (let key in spec) {
|
||||
if (typeof (key) === 'symbol' || key === '*') {
|
||||
continue;
|
||||
}
|
||||
const value = createDefaultConfig(spec[key], defaults);
|
||||
if (value !== UNDEFINED) {
|
||||
if (defaults === UNDEFINED) {
|
||||
defaults = {};
|
||||
}
|
||||
if (spec[key].hasOwnProperty(description)) {
|
||||
defaults['#' + key] = spec[key][description];
|
||||
}
|
||||
defaults[key] = value;
|
||||
}
|
||||
}
|
||||
return defaults;
|
||||
} else if (types.includes('array') && spec.hasOwnProperty('*')) {
|
||||
return [createDefaultConfig(spec['*'], {})];
|
||||
}
|
||||
return UNDEFINED;
|
||||
}
|
||||
|
||||
function dumpConfig(config, path) {
|
||||
const configYaml = yaml.safeDump(config, {
|
||||
indent: 4,
|
||||
lineWidth: 1024,
|
||||
schema: YAML_SCHEMA
|
||||
}).replace(/^(\s*)\'#.*?\':\s*\'*(.*?)\'*$/mg, '$1# $2');
|
||||
fs.writeFileSync(path, configYaml);
|
||||
}
|
||||
|
||||
function validateConfigVersion(config, spec) {
|
||||
function getMajorVersion(version) {
|
||||
try {
|
||||
return parseInt(version.split('.')[0]);
|
||||
} catch (e) {
|
||||
logger.error(`Configuration version number ${version} is malformed.`);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (!config.hasOwnProperty('version')) {
|
||||
logger.error('Failed to get the version number of the configuration file.');
|
||||
logger.warn('You are probably using a previous version of confiugration.');
|
||||
logger.warn(`Please be noted that it may not work in the newer versions of ${projectName}.`);
|
||||
return false;
|
||||
}
|
||||
const specMajorVersion = getMajorVersion(spec.version[defaultValue]);
|
||||
const configMajorVersion = getMajorVersion(config.version);
|
||||
if (configMajorVersion === null || specMajorVersion === null) {
|
||||
return false;
|
||||
}
|
||||
if (configMajorVersion < specMajorVersion) {
|
||||
logger.warn('You are using a previous version of confiugration.');
|
||||
logger.warn(`Please be noted that it may not work in the newer versions of ${projectName}.`);
|
||||
return false;
|
||||
}
|
||||
if (configMajorVersion > specMajorVersion) {
|
||||
logger.warn('You are probably using a more recent version of confiugration.');
|
||||
logger.warn(`Please be noted that it may not work in the previous versions of ${projectName}.`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function validateConfigType(config, specTypes) {
|
||||
specTypes = is.array(specTypes) ? specTypes : [specTypes];
|
||||
for (let specType of specTypes) {
|
||||
if (is[specType](config)) {
|
||||
return specType;
|
||||
}
|
||||
}
|
||||
logger.error(`Config ${toPlainObject(config, 2)} do not match types ${specTypes}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const INVALID_SPEC = Symbol();
|
||||
const MISSING_REQUIRED = Symbol();
|
||||
const INVALID_TYPE = Symbol();
|
||||
|
||||
function validateConfigAndWarn(config, spec, parentConfig, configPath = []) {
|
||||
const result = validateConfig(config, spec, parentConfig, configPath);
|
||||
if (result !== true) {
|
||||
const pathString = configPath.join('.');
|
||||
const specTypes = is.array(spec[type]) ? spec[type] : [spec[type]];
|
||||
switch(result) {
|
||||
case INVALID_SPEC:
|
||||
logger.error(`Invalid specification! The specification '${pathString}' does not have a [type] field:`);
|
||||
logger.error('The specification of this configuration is:');
|
||||
logger.error(JSON.stringify(toPlainObject(spec, 2), null, 4));
|
||||
break;
|
||||
case MISSING_REQUIRED:
|
||||
logger.error(`Configuration '${pathString}' in required by the specification but is missing from the configuration!`);
|
||||
logger.error('The specification of this configuration is:');
|
||||
logger.error(JSON.stringify(toPlainObject(spec, 2), null, 4));
|
||||
break;
|
||||
case INVALID_TYPE:
|
||||
logger.error(`Type mismatch! Configuration '${pathString}' is not the '${specTypes.join(' or ')}' type.`);
|
||||
logger.error('The configuration value is:');
|
||||
logger.error(JSON.stringify(toPlainObject(config, 2), null, 4));
|
||||
logger.error('The specification of this configuration is:');
|
||||
logger.error(JSON.stringify(toPlainObject(spec, 2), null, 4));
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function validateConfig(config, spec, parentConfig = null, configPath = []) {
|
||||
if (!isValidSpec(spec)) {
|
||||
return INVALID_SPEC;
|
||||
}
|
||||
if (!checkPrecondition(spec, parentConfig)) {
|
||||
return true;
|
||||
}
|
||||
if (typeof(config) === 'undefined' || config === null) {
|
||||
if (spec[required] === true) {
|
||||
return MISSING_REQUIRED;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
const configType = validateConfigType(config, spec[type]);
|
||||
if (configType === null) {
|
||||
return INVALID_TYPE;
|
||||
}
|
||||
if (configType === 'array' && spec.hasOwnProperty('*')) {
|
||||
for (let i = 0; i < config.length; i++) {
|
||||
if (!validateConfigAndWarn(config[i], spec['*'], config, configPath.concat(`[${i}]`))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else if (configType === 'object') {
|
||||
for (let key in spec) {
|
||||
if (key === '*') {
|
||||
for (let configKey in config) {
|
||||
if (!validateConfigAndWarn(config[configKey], spec['*'], config, configPath.concat(configKey))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!validateConfigAndWarn(config[key], spec[key], config, configPath.concat(key))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
logger.info('Checking if the configuration file exists...');
|
||||
|
||||
if (!fs.existsSync(CONFIG_PATH)) {
|
||||
const relativePath = path.relative(process.cwd(), CONFIG_PATH);
|
||||
logger.warn(`${relativePath} is not found. We are creating one for you...`);
|
||||
dumpConfig(createDefaultConfig(rootSpec), CONFIG_PATH);
|
||||
logger.info(`${relativePath} is created. Please restart Hexo to apply changes.`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
logger.info('Validating the configuration file...');
|
||||
const config = yaml.safeLoad(fs.readFileSync(CONFIG_PATH));
|
||||
if (!validateConfigVersion(config, rootSpec)) {
|
||||
logger.info(`To let ${projectName} create a fresh configuration file for you, please delete or rename the following file:`);
|
||||
logger.info(CONFIG_PATH);
|
||||
} else {
|
||||
validateConfigAndWarn(config, rootSpec);
|
||||
}
|
||||
|
|
|
@ -12,13 +12,13 @@ function checkDependency(name) {
|
|||
|
||||
logger.info('Checking dependencies');
|
||||
const missingDeps = [
|
||||
// 'moment',
|
||||
// 'lodash',
|
||||
// 'cheerio',
|
||||
// 'js-yaml',
|
||||
// 'highlight.js',
|
||||
// 'hexo-util',
|
||||
'js-yaml',
|
||||
'moment',
|
||||
'lodash',
|
||||
'cheerio',
|
||||
'hexo-util',
|
||||
'hexo-log',
|
||||
'hexo-pagination',
|
||||
'hexo-generator-archive',
|
||||
'hexo-generator-category',
|
||||
'hexo-generator-index',
|
||||
|
|
|
@ -9,11 +9,11 @@
|
|||
<script>
|
||||
new Valine({
|
||||
el: '#valine-thread' ,
|
||||
notify: <%= get_config('comment.notify', false) %>,
|
||||
verify: <%= get_config('comment.verify', false) %>,
|
||||
notify: <%= get_config('comment.notify') %>,
|
||||
verify: <%= get_config('comment.verify') %>,
|
||||
app_id: '<%= get_config('comment.app_id') %>',
|
||||
app_key: '<%= get_config('comment.app_key') %>',
|
||||
placeholder: '<%= get_config('comment.placeholder', 'Say something...') %>'
|
||||
placeholder: '<%= get_config('comment.placeholder') %>'
|
||||
});
|
||||
</script>
|
||||
<% } %>
|
|
@ -6,7 +6,7 @@
|
|||
<% if (has_config('logo.text') && get_config('logo.text')) { %>
|
||||
<%= get_config('logo.text') %>
|
||||
<% } else { %>
|
||||
<img src="<%- url_for(get_config('logo', 'images/logo.svg')) %>" alt="" height="28">
|
||||
<img src="<%- url_for(get_config('logo')) %>" alt="" height="28">
|
||||
<% } %>
|
||||
</a>
|
||||
<p class="is-size-7">
|
||||
|
|
|
@ -5,11 +5,11 @@
|
|||
|
||||
<% if (has_config('open_graph')) { %>
|
||||
<%- open_graph({
|
||||
twitter_id: get_config('open_graph.twitter_id', ''),
|
||||
twitter_site: get_config('open_graph.twitter_site', ''),
|
||||
google_plus: get_config('open_graph.google_plus', ''),
|
||||
fb_admins: get_config('open_graph.fb_admins', ''),
|
||||
fb_app_id: get_config('open_graph.fb_app_id', '')
|
||||
twitter_id: get_config('open_graph.twitter_id'),
|
||||
twitter_site: get_config('open_graph.twitter_site'),
|
||||
google_plus: get_config('open_graph.google_plus'),
|
||||
fb_admins: get_config('open_graph.fb_admins'),
|
||||
fb_app_id: get_config('open_graph.fb_app_id')
|
||||
}) %>
|
||||
<% } %>
|
||||
|
||||
|
@ -25,7 +25,7 @@
|
|||
<%- css(iconcdn()) %>
|
||||
<%- css(iconcdn('material')) %>
|
||||
<%- css(fontcdn('Ubuntu:400,600|Source+Code+Pro')) %>
|
||||
<%- css(cdn('highlight.js', '9.12.0', 'styles/' + get_config('article.highlight', 'atom-one-light') + '.min.css')) %>
|
||||
<%- css(cdn('highlight.js', '9.12.0', 'styles/' + get_config('article.highlight') + '.min.css')) %>
|
||||
<%- css('css/style') %>
|
||||
|
||||
<% if (has_config('plugins')) { %>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<% if (has_config('logo.text') && get_config('logo.text')) { %>
|
||||
<%= get_config('logo.text') %>
|
||||
<% } else { %>
|
||||
<img src="<%- url_for(get_config('logo', 'images/logo.svg')) %>" alt="" height="28">
|
||||
<img src="<%- url_for(get_config('logo')) %>" alt="" height="28">
|
||||
<% } %>
|
||||
</a>
|
||||
</div>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
</h3>
|
||||
<% site.posts.sort('date', -1).limit(5).each(post => { %>
|
||||
<article class="media">
|
||||
<% if (!has_config('article.thumbnail') || get_config('article.thumbnail', true) !== false) { %>
|
||||
<% if (!has_config('article.thumbnail') || get_config('article.thumbnail') !== false) { %>
|
||||
<a href="<%- url_for((post.link?post.link:post.path)) %>" class="media-left">
|
||||
<p class="image is-64x64">
|
||||
<img class="thumbnail" src="<%= get_thumbnail(post) %>" alt="<%= post.title %>">
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "hexo-theme-icarus",
|
||||
"version": "0.2.1",
|
||||
"version": "2.0.0",
|
||||
"private": true
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue