hexo
Version:
A fast, simple & powerful blog framework, powered by Node.js.
209 lines (177 loc) • 4.24 kB
JavaScript
var async = require('async'),
pathFn = require('path'),
_ = require('lodash');
/**
* The view object of the Theme class.
*
* @class View
* @param {String} source The full source of the view file.
* @param {String} path The relative path of the view file.
* @param {Theme} theme
* @constructor
* @namespace Theme
* @module hexo
*/
var View = module.exports = function View(source, path, theme){
/**
* The full path of the view file.
*
* @property source
* @type {String}
*/
this.source = source;
/**
* The relative path of the view file.
*
* @property path
* @type {String}
*/
this.path = path;
/**
* The extension name of the view file. (With a prefixed dot)
*
* @property extname
* @type {String}
*/
this.extname = pathFn.extname(path);
/**
* The theme object.
*
* @property theme
* @type {Theme}
*/
this.theme = theme;
/**
* View data.
*
* @property data
* @type {Object}
*/
this.data = null;
/**
* View cache.
*
* @property cache
* @type {Object}
* @private
*/
this.cache = null;
};
/**
* Renders the view.
*
* @method render
* @param {Object} [options]
* @param {Boolean} [options.cache=true]
* @param {Function} callback
*/
View.prototype.render = function(options, callback){
if (!callback){
if (typeof options === 'function'){
callback = options;
options = {};
} else {
callback = function(){};
}
}
options = _.extend({
cache: true
}, options);
var data = this.data;
if (!data) return callback();
var layout = data.hasOwnProperty('layout') ? data.layout : options.layout,
locals = _.extend(this._buildLocals(options), _.omit(data, 'layout', '_content'), {filename: this.source}),
self = this;
hexo.render.render({
path: this.source,
text: data._content
}, locals, function(err, result){
if (err) return callback(err);
if (!layout) return callback(null, result);
var layoutView = self._resolveLayout(layout);
if (layoutView){
var layoutLocals = _.extend({}, locals, {body: result, layout: false});
layoutView.render(layoutLocals, callback);
} else {
callback(null, result);
}
});
};
/**
* Resolves the layout path.
*
* @method _resolveLayout
* @param {String} name
* @return {View}
* @private
*/
View.prototype._resolveLayout = function(name){
// Relative path
var layoutView = this.theme.getView(pathFn.join(pathFn.dirname(this.path), name));
if (layoutView && layoutView.source === this.source) layoutView = null;
// Absolute path
if (!layoutView){
layoutView = this.theme.getView(name);
if (layoutView && layoutView.source === this.source) layoutView = null;
}
return layoutView;
};
/**
* Clones the object and binds the helper functions.
*
* @method _buildLocals
* @param {Object} locals
* @return {Object}
* @private
*/
View.prototype._buildLocals = function(locals){
var helpers = hexo.extend.helper.list(),
obj = {};
_.each(helpers, function(helper, name){
obj[name] = helper.bind(obj);
});
_.each(locals, function(fn, name){
if (!helpers.hasOwnProperty(name) || helpers[name].toString() === fn.toString()){
obj[name] = fn;
}
});
return obj;
};
/**
* Renders the view synchronizedly.
*
* @method renderSync
* @param {Object} [options]
* @param {Boolean} [options.cache=true]
* @return {String}
*/
View.prototype.renderSync = function(options){
options = _.extend({
cache: true
}, options);
var data = this.data;
if (!data) return;
var layout = data.hasOwnProperty('layout') ? data.layout : options.layout,
locals = _.extend(this._buildLocals(options), _.omit(data, 'layout', '_content'), {filename: this.source});
var result = hexo.render.renderSync({
path: this.source,
text: data._content
}, locals);
if (!result) return;
if (!layout) return result;
var layoutView = this._resolveLayout(layout);
if (layoutView){
var layoutLocals = _.extend({}, locals, {body: result, layout: false});
return layoutView.renderSync(layoutLocals);
} else {
return result;
}
};
/**
* Invalidates the cache.
*
* @method invalidate
*/
View.prototype.invalidate = function(){
this.cache = null;
};