refactor(schema): add schema default value gen
parent
9e08b5c4fe
commit
46530f3c4e
|
@ -9,7 +9,7 @@ module.exports = function(hexo) {
|
||||||
|
|
||||||
function findParent(category) {
|
function findParent(category) {
|
||||||
let parents = [];
|
let parents = [];
|
||||||
if (category && 'parent' in category) {
|
if (typeof category === 'object' && 'parent' in category) {
|
||||||
const parent = locals.categories.filter(cat => cat._id === category.parent).first();
|
const parent = locals.categories.filter(cat => cat._id === category.parent).first();
|
||||||
parents = [parent].concat(findParent(parent));
|
parents = [parent].concat(findParent(parent));
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,9 @@ module.exports = function(hexo) {
|
||||||
|
|
||||||
hexo.extend.helper.register('has_thumbnail', function(post) {
|
hexo.extend.helper.register('has_thumbnail', function(post) {
|
||||||
const { article } = this.config;
|
const { article } = this.config;
|
||||||
|
if (typeof post !== 'object') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (article && article.thumbnail === false) {
|
if (article && article.thumbnail === false) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,10 +5,10 @@
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"oneOf": [
|
"oneOf": [
|
||||||
{
|
{
|
||||||
"$ref": "/comment/changyan.json"
|
"$ref": "/comment/disqus.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"$ref": "/comment/disqus.json"
|
"$ref": "/comment/changyan.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"$ref": "/comment/disqusjs.json"
|
"$ref": "/comment/disqusjs.json"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
"$id": "/common/footer.json",
|
"$id": "/common/head.json",
|
||||||
"description": "Page metadata configurations",
|
"description": "Page metadata configurations",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
"$id": "/common/footer.json",
|
"$id": "/common/navbar.json",
|
||||||
"description": "Page top navigation bar configurations",
|
"description": "Page top navigation bar configurations",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
|
@ -18,6 +18,6 @@
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Name or URL of the webfont Icon CDN provider",
|
"description": "Name or URL of the webfont Icon CDN provider",
|
||||||
"default": "fontawesome"
|
"default": "fontawesome"
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -4,14 +4,14 @@
|
||||||
"description": "Search plugin configurations",
|
"description": "Search plugin configurations",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"oneOf": [
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"$ref": "/search/insight.json"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"$ref": "/search/baidu.json"
|
"$ref": "/search/baidu.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"$ref": "/search/google_cse.json"
|
"$ref": "/search/google_cse.json"
|
||||||
},
|
|
||||||
{
|
|
||||||
"$ref": "/search/insight.json"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
|
@ -4,6 +4,9 @@
|
||||||
"description": "Share plugin configurations",
|
"description": "Share plugin configurations",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"oneOf": [
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"$ref": "/share/sharethis.json"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"$ref": "/share/addthis.json"
|
"$ref": "/share/addthis.json"
|
||||||
},
|
},
|
||||||
|
@ -15,9 +18,6 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"$ref": "/share/sharejs.json"
|
"$ref": "/share/sharejs.json"
|
||||||
},
|
|
||||||
{
|
|
||||||
"$ref": "/share/sharethis.json"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
|
@ -14,19 +14,28 @@
|
||||||
},
|
},
|
||||||
"oneOf": [
|
"oneOf": [
|
||||||
{
|
{
|
||||||
"$ref": "/widget/alipay.json"
|
"$ref": "/widget/profile.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"$ref": "/widget/buymeacoffee.json"
|
"$ref": "/widget/toc.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"$ref": "/widget/patreon.json"
|
"$ref": "/widget/links.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"$ref": "/widget/paypal.json"
|
"$ref": "/widget/categories.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"$ref": "/widget/wechat.json"
|
"$ref": "/widget/recent_posts.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "/widget/archives.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "/widget/tags.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "/widget/subscribe_email.json"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"required": [
|
"required": [
|
||||||
|
|
|
@ -0,0 +1,222 @@
|
||||||
|
const Ajv = require('ajv');
|
||||||
|
const path = require('path');
|
||||||
|
const deepmerge = require('deepmerge');
|
||||||
|
|
||||||
|
const PRIMITIVE_DEFAULTS = {
|
||||||
|
'null': null,
|
||||||
|
'boolean': false,
|
||||||
|
'number': 0,
|
||||||
|
'integer': 0,
|
||||||
|
'string': '',
|
||||||
|
'array': [],
|
||||||
|
'object': {}
|
||||||
|
};
|
||||||
|
|
||||||
|
class DefaultValue {
|
||||||
|
constructor(value, description) {
|
||||||
|
this.value = value;
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
merge(source) {
|
||||||
|
if ('description' in source && source.description) {
|
||||||
|
this.description = source.description;
|
||||||
|
}
|
||||||
|
if ('value' in source && source.value) {
|
||||||
|
this.value = deepmerge(this.value, source.value);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
return '[DefaultValue]' + JSON.stringify(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Schema {
|
||||||
|
constructor(loader, def) {
|
||||||
|
if (!(loader instanceof SchemaLoader)) {
|
||||||
|
throw new Error('loader must be an instance of SchemaLoader');
|
||||||
|
}
|
||||||
|
if (typeof def !== 'object') {
|
||||||
|
throw new Error('schema definition must be an object');
|
||||||
|
}
|
||||||
|
this.loader = loader;
|
||||||
|
this.def = def;
|
||||||
|
this.compiledSchema = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
validate(obj) {
|
||||||
|
if (!this.compiledSchema) {
|
||||||
|
this.compiledSchema = this.loader.compileValidator(this.def.$id);
|
||||||
|
}
|
||||||
|
return this.compiledSchema(obj) ? true : this.compiledSchema.errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
getArrayDefaultValue(def) {
|
||||||
|
let value;
|
||||||
|
if ('items' in def && typeof def.items === 'object') {
|
||||||
|
const items = Object.assign({}, def.items);
|
||||||
|
delete items.oneOf;
|
||||||
|
value = this.getDefaultValue(items);
|
||||||
|
}
|
||||||
|
if ('oneOf' in def.items && Array.isArray(def.items.oneOf)) {
|
||||||
|
value = def.items.oneOf.map(one => {
|
||||||
|
if (!value) {
|
||||||
|
return this.getDefaultValue(one);
|
||||||
|
}
|
||||||
|
return new DefaultValue(value.value, value.description)
|
||||||
|
.merge(this.getDefaultValue(one));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!Array.isArray(value)) {
|
||||||
|
value = [value];
|
||||||
|
}
|
||||||
|
return new DefaultValue(value, def.description);
|
||||||
|
}
|
||||||
|
|
||||||
|
getObjectDefaultValue(def) {
|
||||||
|
let value = {};
|
||||||
|
if ('properties' in def && typeof def.properties === 'object') {
|
||||||
|
for (let property in def.properties) {
|
||||||
|
value[property] = this.getDefaultValue(def.properties[property]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ('oneOf' in def && Array.isArray(def.oneOf) && def.oneOf.length) {
|
||||||
|
value = deepmerge(value, this.getDefaultValue(def.oneOf[0]));
|
||||||
|
}
|
||||||
|
return new DefaultValue(value, def.description);
|
||||||
|
}
|
||||||
|
|
||||||
|
getTypedDefaultValue(def) {
|
||||||
|
let defaultValue;
|
||||||
|
const type = Array.isArray(def.type) ? def.type[0] : def.type;
|
||||||
|
if (type === 'array') {
|
||||||
|
defaultValue = this.getArrayDefaultValue(def);
|
||||||
|
} else if (type === 'object') {
|
||||||
|
defaultValue = this.getObjectDefaultValue(def);
|
||||||
|
} else if (type in PRIMITIVE_DEFAULTS) {
|
||||||
|
defaultValue = new DefaultValue(PRIMITIVE_DEFAULTS[type], def.description);
|
||||||
|
} else {
|
||||||
|
throw new Error(`Cannot get default value for type ${type}`)
|
||||||
|
}
|
||||||
|
// referred default value always get overwritten by its parent default value
|
||||||
|
if ('$ref' in def && def.$ref) {
|
||||||
|
defaultValue = this.getReferredDefaultValue(def).merge(defaultValue);
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
getReferredDefaultValue(def) {
|
||||||
|
const schema = this.loader.getSchema(def.$ref);
|
||||||
|
if (!schema) {
|
||||||
|
throw new Error(`Schema ${def.$ref} is not loaded`);
|
||||||
|
}
|
||||||
|
return this.getDefaultValue(schema.def).merge({ description: def.description });
|
||||||
|
}
|
||||||
|
|
||||||
|
getDefaultValue(def = null) {
|
||||||
|
if (!def) {
|
||||||
|
def = this.def;
|
||||||
|
}
|
||||||
|
if ('const' in def) {
|
||||||
|
return new DefaultValue(def['const'], def.description);
|
||||||
|
}
|
||||||
|
if ('default' in def) {
|
||||||
|
return new DefaultValue(def['default'], def.description);
|
||||||
|
}
|
||||||
|
if ('examples' in def && Array.isArray(def.examples) && def.examples.length) {
|
||||||
|
return new DefaultValue(def.examples[0], def.description);
|
||||||
|
}
|
||||||
|
if ('type' in def && def.type) {
|
||||||
|
return this.getTypedDefaultValue(def);
|
||||||
|
}
|
||||||
|
// $ref only schemas
|
||||||
|
if ('$ref' in def && def.$ref) {
|
||||||
|
return this.getReferredDefaultValue(def);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SchemaLoader {
|
||||||
|
constructor() {
|
||||||
|
this.schemas = {};
|
||||||
|
this.ajv = new Ajv();
|
||||||
|
}
|
||||||
|
|
||||||
|
getSchema($id) {
|
||||||
|
return this.schemas[$id];
|
||||||
|
}
|
||||||
|
|
||||||
|
addSchema(def) {
|
||||||
|
if (!Object.prototype.hasOwnProperty.call(def, '$id')) {
|
||||||
|
throw new Error('The schema definition does not have an $id field');
|
||||||
|
}
|
||||||
|
this.ajv.addSchema(def);
|
||||||
|
this.schemas[def['$id']] = new Schema(this, def);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeSchema($id) {
|
||||||
|
this.ajv.removeSchema(def);
|
||||||
|
delete this.schemas[$id];
|
||||||
|
}
|
||||||
|
|
||||||
|
compileValidator($id) {
|
||||||
|
return this.ajv.compile(this.schemas[$id].def);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function traverseObj(obj, targetKey, handler) {
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
for (const child of obj) {
|
||||||
|
traverseObj(child, targetKey, handler);
|
||||||
|
}
|
||||||
|
} else if (typeof obj === 'object') {
|
||||||
|
for (const key in obj) {
|
||||||
|
if (key === targetKey) {
|
||||||
|
handler(obj[key]);
|
||||||
|
} else {
|
||||||
|
traverseObj(obj[key], targetKey, handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SchemaLoader.load = (rootSchemaDef, resolveDirs = []) => {
|
||||||
|
if (!Array.isArray(resolveDirs)) {
|
||||||
|
resolveDirs = [resolveDirs];
|
||||||
|
}
|
||||||
|
|
||||||
|
const loader = new SchemaLoader();
|
||||||
|
loader.addSchema(rootSchemaDef);
|
||||||
|
|
||||||
|
function handler($ref) {
|
||||||
|
if (typeof $ref !== 'string') {
|
||||||
|
throw new Error('Invalid schema reference id: ' + JSON.stringify($ref));
|
||||||
|
}
|
||||||
|
if (loader.getSchema($ref)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const dir of resolveDirs) {
|
||||||
|
let def;
|
||||||
|
try {
|
||||||
|
def = require(path.join(dir, $ref));
|
||||||
|
} catch (e) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (typeof def !== 'object' || def['$id'] !== $ref) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
loader.addSchema(def);
|
||||||
|
traverseObj(def, '$ref', handler);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw new Error('Cannot find schema definition ' + $ref + '.\n'
|
||||||
|
+ 'Please check if the file exists and its $id is correct');
|
||||||
|
}
|
||||||
|
|
||||||
|
traverseObj(rootSchemaDef, '$ref', handler);
|
||||||
|
return loader;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = SchemaLoader;
|
|
@ -5,7 +5,7 @@ const classname = require('../util/classname');
|
||||||
function formatWidgets(widgets) {
|
function formatWidgets(widgets) {
|
||||||
const result = {};
|
const result = {};
|
||||||
if (Array.isArray(widgets)) {
|
if (Array.isArray(widgets)) {
|
||||||
widgets.forEach(widget => {
|
widgets.filter(widget => typeof widget === 'object').forEach(widget => {
|
||||||
if ('position' in widget && (widget.position === 'left' || widget.position === 'right')) {
|
if ('position' in widget && (widget.position === 'left' || widget.position === 'right')) {
|
||||||
if (!(widget.position in result)) {
|
if (!(widget.position in result)) {
|
||||||
result[widget.position] = [widget];
|
result[widget.position] = [widget];
|
||||||
|
@ -52,7 +52,9 @@ function getColumnOrderClass(position) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function isColumnSticky(config, position) {
|
function isColumnSticky(config, position) {
|
||||||
return config.sidebar && position in config.sidebar && config.sidebar[position].sticky === true;
|
return typeof config.sidebar === 'object'
|
||||||
|
&& position in config.sidebar
|
||||||
|
&& config.sidebar[position].sticky === true;
|
||||||
}
|
}
|
||||||
|
|
||||||
class Widgets extends Component {
|
class Widgets extends Component {
|
||||||
|
|
|
@ -8,7 +8,7 @@ class Profile extends Component {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return <div class="level is-mobile">
|
return <div class="level is-mobile">
|
||||||
{links.map(link => {
|
{links.filter(link => typeof link === 'object').map(link => {
|
||||||
return <a class="level-item button is-white is-marginless"
|
return <a class="level-item button is-white is-marginless"
|
||||||
target="_blank" rel="noopener" title={link.name} href={link.url}>
|
target="_blank" rel="noopener" title={link.name} href={link.url}>
|
||||||
{'icon' in link ? <i class={link.icon}></i> : link.name}
|
{'icon' in link ? <i class={link.icon}></i> : link.name}
|
||||||
|
|
|
@ -32,7 +32,7 @@
|
||||||
"inferno-create-element": "^7.3.3",
|
"inferno-create-element": "^7.3.3",
|
||||||
"moment": "^2.22.2",
|
"moment": "^2.22.2",
|
||||||
"ajv": "^6.10.2",
|
"ajv": "^6.10.2",
|
||||||
"glob": "^7.1.4",
|
"js-yaml": "^3.13.1",
|
||||||
"js-yaml": "^3.13.1"
|
"deepmerge": "^4.2.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue