mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-17 08:05:19 +00:00
306 lines
7.8 KiB
JavaScript
306 lines
7.8 KiB
JavaScript
var is = require('is');
|
|
var Immutable = require('immutable');
|
|
|
|
var Promise = require('../utils/promise');
|
|
var genKey = require('../utils/genKey');
|
|
|
|
var NODE_ENDARGS = '%%endargs%%';
|
|
|
|
var blockBodies = {};
|
|
|
|
var TemplateBlock = Immutable.Record({
|
|
// Name of block, also the start tag
|
|
name: String(),
|
|
|
|
// End tag, default to "end<name>"
|
|
end: String(),
|
|
|
|
// Function to process the block content
|
|
process: Function(),
|
|
|
|
// List of String, for inner block tags
|
|
blocks: Immutable.List(),
|
|
|
|
// List of shortcuts to replace with this block
|
|
shortcuts: Immutable.List(),
|
|
|
|
// Function to execute in post processing
|
|
post: null,
|
|
|
|
parse: true
|
|
}, 'TemplateBlock');
|
|
|
|
TemplateBlock.prototype.getName = function() {
|
|
return this.get('name');
|
|
};
|
|
|
|
TemplateBlock.prototype.getPost = function() {
|
|
return this.get('post');
|
|
};
|
|
|
|
TemplateBlock.prototype.getParse = function() {
|
|
return this.get('parse');
|
|
};
|
|
|
|
TemplateBlock.prototype.getEndTag = function() {
|
|
return this.get('end') || ('end' + this.getName());
|
|
};
|
|
|
|
TemplateBlock.prototype.getProcess = function() {
|
|
return this.get('process');
|
|
};
|
|
|
|
TemplateBlock.prototype.getBlocks = function() {
|
|
return this.get('blocks');
|
|
};
|
|
|
|
TemplateBlock.prototype.getShortcuts = function() {
|
|
return this.get('shortcuts');
|
|
};
|
|
|
|
/**
|
|
Return name for the nunjucks extension
|
|
|
|
@return {String}
|
|
*/
|
|
TemplateBlock.prototype.getExtensionName = function() {
|
|
return 'Block' + this.getName() + 'Extension';
|
|
};
|
|
|
|
/**
|
|
Return a nunjucks extension to represents this block
|
|
|
|
@return {Nunjucks.Extension}
|
|
*/
|
|
TemplateBlock.prototype.toNunjucksExt = function() {
|
|
var that = this;
|
|
var name = this.getName();
|
|
var endTag = this.getEndTag();
|
|
var blocks = this.getBlocks();
|
|
|
|
var Ext = function () {
|
|
this.tags = [name];
|
|
|
|
this.parse = function(parser, nodes) {
|
|
var lastBlockName = null;
|
|
var lastBlockArgs = null;
|
|
var allBlocks = blocks.concat([endTag]);
|
|
|
|
// Parse first block
|
|
var tok = parser.nextToken();
|
|
lastBlockArgs = parser.parseSignature(null, true);
|
|
parser.advanceAfterBlockEnd(tok.value);
|
|
|
|
var args = new nodes.NodeList();
|
|
var bodies = [];
|
|
var blockNamesNode = new nodes.Array(tok.lineno, tok.colno);
|
|
var blockArgCounts = new nodes.Array(tok.lineno, tok.colno);
|
|
|
|
// Parse while we found "end<block>"
|
|
do {
|
|
// Read body
|
|
var currentBody = parser.parseUntilBlocks.apply(parser, allBlocks);
|
|
|
|
// Handle body with previous block name and args
|
|
blockNamesNode.addChild(new nodes.Literal(args.lineno, args.colno, lastBlockName));
|
|
blockArgCounts.addChild(new nodes.Literal(args.lineno, args.colno, lastBlockArgs.children.length));
|
|
bodies.push(currentBody);
|
|
|
|
// Append arguments of this block as arguments of the run function
|
|
lastBlockArgs.children.forEach(function(child) {
|
|
args.addChild(child);
|
|
});
|
|
|
|
// Read new block
|
|
lastBlockName = parser.nextToken().value;
|
|
|
|
// Parse signature and move to the end of the block
|
|
if (lastBlockName != endTag) {
|
|
lastBlockArgs = parser.parseSignature(null, true);
|
|
}
|
|
|
|
parser.advanceAfterBlockEnd(lastBlockName);
|
|
} while (lastBlockName != endTag);
|
|
|
|
args.addChild(blockNamesNode);
|
|
args.addChild(blockArgCounts);
|
|
args.addChild(new nodes.Literal(args.lineno, args.colno, NODE_ENDARGS));
|
|
|
|
return new nodes.CallExtensionAsync(this, 'run', args, bodies);
|
|
};
|
|
|
|
this.run = function(context) {
|
|
var fnArgs = Array.prototype.slice.call(arguments, 1);
|
|
|
|
var args;
|
|
var blocks = [];
|
|
var bodies = [];
|
|
var blockNames;
|
|
var blockArgCounts;
|
|
var callback;
|
|
|
|
// Extract callback
|
|
callback = fnArgs.pop();
|
|
|
|
// Detect end of arguments
|
|
var endArgIndex = fnArgs.indexOf(NODE_ENDARGS);
|
|
|
|
// Extract arguments and bodies
|
|
args = fnArgs.slice(0, endArgIndex);
|
|
bodies = fnArgs.slice(endArgIndex + 1);
|
|
|
|
// Extract block counts
|
|
blockArgCounts = args.pop();
|
|
blockNames = args.pop();
|
|
|
|
// Recreate list of blocks
|
|
blockNames.forEach(function(name, i) {
|
|
var countArgs = blockArgCounts[i];
|
|
var blockBody = bodies.shift();
|
|
|
|
var blockArgs = countArgs > 0? args.slice(0, countArgs) : [];
|
|
args = args.slice(countArgs);
|
|
var blockKwargs = extractKwargs(blockArgs);
|
|
|
|
blocks.push({
|
|
name: name,
|
|
body: blockBody(),
|
|
args: blockArgs,
|
|
kwargs: blockKwargs
|
|
});
|
|
});
|
|
|
|
var mainBlock = blocks.shift();
|
|
mainBlock.blocks = blocks;
|
|
|
|
Promise()
|
|
.then(function() {
|
|
return that.applyBlock(mainBlock, context);
|
|
})
|
|
.then(function(result) {
|
|
return that.blockResultToHtml(result);
|
|
})
|
|
.nodeify(callback);
|
|
};
|
|
};
|
|
|
|
return Ext;
|
|
};
|
|
|
|
/**
|
|
Apply a block to a content
|
|
@param {Object} inner
|
|
@param {Object} context
|
|
@return {Promise<String>|String}
|
|
*/
|
|
TemplateBlock.prototype.applyBlock = function(inner, context) {
|
|
var processFn = this.getProcess();
|
|
|
|
inner = inner || {};
|
|
inner.args = inner.args || [];
|
|
inner.kwargs = inner.kwargs || {};
|
|
inner.blocks = inner.blocks || [];
|
|
|
|
var r = processFn.call(context, inner);
|
|
|
|
if (Promise.isPromiseAlike(r)) {
|
|
return r.then(this.handleBlockResult);
|
|
} else {
|
|
return this.handleBlockResult(r);
|
|
}
|
|
};
|
|
|
|
/**
|
|
Handle result from a block process function
|
|
|
|
@param {Object} result
|
|
@return {Object}
|
|
*/
|
|
TemplateBlock.prototype.handleBlockResult = function(result) {
|
|
if (is.string(result)) {
|
|
result = { body: result };
|
|
}
|
|
result.name = this.getName();
|
|
|
|
return result;
|
|
};
|
|
|
|
/**
|
|
Convert a block result to HTML
|
|
|
|
@param {Object} result
|
|
@return {String}
|
|
*/
|
|
TemplateBlock.prototype.blockResultToHtml = function(result) {
|
|
var parse = this.getParse();
|
|
var indexedKey;
|
|
var toIndex = (!parse) || (this.getPost() !== undefined);
|
|
|
|
if (toIndex) {
|
|
indexedKey = TemplateBlock.indexBlockResult(result);
|
|
}
|
|
|
|
// Parsable block, just return it
|
|
if (parse) {
|
|
return result.body;
|
|
}
|
|
|
|
// Return it as a position marker
|
|
return '{{-%' + indexedKey + '%-}}';
|
|
|
|
};
|
|
|
|
/**
|
|
Index a block result, and return the indexed key
|
|
|
|
@param {Object} blk
|
|
@return {String}
|
|
*/
|
|
TemplateBlock.indexBlockResult = function(blk) {
|
|
var key = genKey();
|
|
blockBodies[key] = blk;
|
|
|
|
return key;
|
|
};
|
|
|
|
/**
|
|
Get a block results indexed for a specific key
|
|
|
|
@param {String} key
|
|
@return {Object|undefined}
|
|
*/
|
|
TemplateBlock.getBlockResultByKey = function(key) {
|
|
return blockBodies[key];
|
|
};
|
|
|
|
/**
|
|
Create a template block from a function or an object
|
|
|
|
@param {String} blockName
|
|
@param {Object} block
|
|
@return {TemplateBlock}
|
|
*/
|
|
TemplateBlock.create = function(blockName, block) {
|
|
if (is.fn(block)) {
|
|
block = new Immutable.Map({
|
|
process: block
|
|
});
|
|
}
|
|
|
|
block = block.set('name', blockName);
|
|
return new TemplateBlock(block);
|
|
};
|
|
|
|
/**
|
|
Extract kwargs from an arguments array
|
|
|
|
@param {Array} args
|
|
@return {Object}
|
|
*/
|
|
function extractKwargs(args) {
|
|
var last = args[args.length - 1];
|
|
return (is.object(last) && last.__keywords)? args.pop() : {};
|
|
}
|
|
|
|
module.exports = TemplateBlock;
|