view.js 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. 'use strict';
  2. const pathFn = require('path');
  3. const assignIn = require('lodash/assignIn');
  4. const omit = require('lodash/omit');
  5. const yfm = require('hexo-front-matter');
  6. const Promise = require('bluebird');
  7. function View(path, data) {
  8. this.path = path;
  9. this.source = pathFn.join(this._theme.base, 'layout', path);
  10. this.data = typeof data === 'string' ? yfm(data) : data;
  11. this._precompile();
  12. }
  13. View.prototype.render = function(options, callback) {
  14. if (!callback && typeof options === 'function') {
  15. callback = options;
  16. options = {};
  17. }
  18. options = options || {};
  19. const data = this.data;
  20. const layout = data.hasOwnProperty('layout') ? data.layout : options.layout;
  21. const locals = this._buildLocals(options);
  22. const self = this;
  23. return this._compiled(this._bindHelpers(locals)).then(result => {
  24. if (result == null || !layout) return result;
  25. const layoutView = self._resolveLayout(layout);
  26. if (!layoutView) return result;
  27. const layoutLocals = Object.assign({}, locals, {
  28. body: result,
  29. layout: false
  30. });
  31. return layoutView.render(layoutLocals, callback);
  32. }).asCallback(callback);
  33. };
  34. View.prototype.renderSync = function(options = {}) {
  35. const data = this.data;
  36. const layout = data.hasOwnProperty('layout') ? data.layout : options.layout;
  37. const locals = this._buildLocals(options);
  38. const result = this._compiledSync(this._bindHelpers(locals));
  39. if (result == null || !layout) return result;
  40. const layoutView = this._resolveLayout(layout);
  41. if (!layoutView) return result;
  42. const layoutLocals = Object.assign({}, locals, {
  43. body: result,
  44. layout: false
  45. });
  46. return layoutView.renderSync(layoutLocals);
  47. };
  48. View.prototype._buildLocals = function(locals) {
  49. return assignIn({}, locals, omit(this.data, ['layout', '_content']), {
  50. filename: this.source
  51. });
  52. };
  53. View.prototype._bindHelpers = function(locals) {
  54. const helpers = this._helper.list();
  55. const keys = Object.keys(helpers);
  56. let key = '';
  57. for (let i = 0, len = keys.length; i < len; i++) {
  58. key = keys[i];
  59. locals[key] = helpers[key].bind(locals);
  60. }
  61. return locals;
  62. };
  63. View.prototype._resolveLayout = function(name) {
  64. // Relative path
  65. const layoutPath = pathFn.join(pathFn.dirname(this.path), name);
  66. let layoutView = this._theme.getView(layoutPath);
  67. if (layoutView && layoutView.source !== this.source) return layoutView;
  68. // Absolute path
  69. layoutView = this._theme.getView(name);
  70. if (layoutView && layoutView.source !== this.source) return layoutView;
  71. };
  72. View.prototype._precompile = function() {
  73. const render = this._render;
  74. const ctx = render.context;
  75. const ext = pathFn.extname(this.path);
  76. const renderer = render.getRenderer(ext);
  77. const data = {
  78. path: this.source,
  79. text: this.data._content
  80. };
  81. function buildFilterArguments(result) {
  82. const output = render.getOutput(ext) || ext;
  83. return [
  84. `after_render:${output}`,
  85. result,
  86. {
  87. context: ctx,
  88. args: [data]
  89. }
  90. ];
  91. }
  92. if (renderer && typeof renderer.compile === 'function') {
  93. const compiled = renderer.compile(data);
  94. this._compiledSync = locals => {
  95. const result = compiled(locals);
  96. return ctx.execFilterSync(...buildFilterArguments(result));
  97. };
  98. this._compiled = locals => Promise.resolve(compiled(locals))
  99. .then(result => ctx.execFilter(...buildFilterArguments(result)));
  100. } else {
  101. this._compiledSync = locals => render.renderSync(data, locals);
  102. this._compiled = locals => render.render(data, locals);
  103. }
  104. };
  105. module.exports = View;