231 lines
8.7 KiB
JavaScript
231 lines
8.7 KiB
JavaScript
"use strict";
|
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
};
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
const is_1 = __importDefault(require("is"));
|
|
const extend_1 = __importDefault(require("extend"));
|
|
const immutable_1 = __importDefault(require("immutable"));
|
|
const promise_1 = __importDefault(require("../utils/promise"));
|
|
const genKey_1 = __importDefault(require("../utils/genKey"));
|
|
const templateShortcut_1 = __importDefault(require("./templateShortcut"));
|
|
const NODE_ENDARGS = "%%endargs%%";
|
|
class TemplateBlock extends immutable_1.default.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_1.default.List(),
|
|
// List of shortcuts to replace with this block
|
|
shortcuts: immutable_1.default.Map()
|
|
}, "TemplateBlock") {
|
|
getName() {
|
|
return this.get("name");
|
|
}
|
|
getEndTag() {
|
|
return this.get("end") || `end${this.getName()}`;
|
|
}
|
|
getProcess() {
|
|
return this.get("process");
|
|
}
|
|
getBlocks() {
|
|
return this.get("blocks");
|
|
}
|
|
/**
|
|
* Return shortcuts associated with this block or undefined
|
|
* @return {TemplateShortcut|undefined}
|
|
*/
|
|
getShortcuts() {
|
|
const shortcuts = this.get("shortcuts");
|
|
if (shortcuts.size === 0) {
|
|
return undefined;
|
|
}
|
|
return templateShortcut_1.default.createForBlock(this, shortcuts);
|
|
}
|
|
/**
|
|
* Return name for the nunjucks extension
|
|
* @return {string}
|
|
*/
|
|
getExtensionName() {
|
|
return `Block${this.getName()}Extension`;
|
|
}
|
|
/**
|
|
* Return a nunjucks extension to represents this block
|
|
* @return {Nunjucks.Extension}
|
|
*/
|
|
toNunjucksExt(mainContext, blocksOutput) {
|
|
blocksOutput = blocksOutput || {};
|
|
const that = this;
|
|
const name = this.getName();
|
|
const endTag = this.getEndTag();
|
|
const blocks = this.getBlocks().toJS();
|
|
function Ext() {
|
|
this.tags = [name];
|
|
this.parse = function (parser, nodes) {
|
|
let lastBlockName = null;
|
|
let lastBlockArgs = null;
|
|
const allBlocks = blocks.concat([endTag]);
|
|
// Parse first block
|
|
const tok = parser.nextToken();
|
|
lastBlockArgs = parser.parseSignature(null, true);
|
|
parser.advanceAfterBlockEnd(tok.value);
|
|
const args = new nodes.NodeList();
|
|
const bodies = [];
|
|
const blockNamesNode = new nodes.Array(tok.lineno, tok.colno);
|
|
const blockArgCounts = new nodes.Array(tok.lineno, tok.colno);
|
|
// Parse while we found "end<block>"
|
|
do {
|
|
// Read body
|
|
const 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((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) {
|
|
const fnArgs = Array.prototype.slice.call(arguments, 1);
|
|
let args;
|
|
const blocks = [];
|
|
let bodies = [];
|
|
// Extract callback
|
|
const callback = fnArgs.pop();
|
|
// Detect end of arguments
|
|
const endArgIndex = fnArgs.indexOf(NODE_ENDARGS);
|
|
// Extract arguments and bodies
|
|
args = fnArgs.slice(0, endArgIndex);
|
|
bodies = fnArgs.slice(endArgIndex + 1);
|
|
// Extract block counts
|
|
const blockArgCounts = args.pop();
|
|
const blockNames = args.pop();
|
|
// Recreate list of blocks
|
|
blockNames.forEach((name, i) => {
|
|
const countArgs = blockArgCounts[i];
|
|
const blockBody = bodies.shift();
|
|
const blockArgs = countArgs > 0 ? args.slice(0, countArgs) : [];
|
|
args = args.slice(countArgs);
|
|
const blockKwargs = extractKwargs(blockArgs);
|
|
blocks.push({
|
|
name: name,
|
|
body: blockBody(),
|
|
args: blockArgs,
|
|
kwargs: blockKwargs
|
|
});
|
|
});
|
|
const mainBlock = blocks.shift();
|
|
mainBlock.blocks = blocks;
|
|
(0, promise_1.default)()
|
|
.then(() => {
|
|
const ctx = (0, extend_1.default)({
|
|
ctx: context
|
|
}, mainContext || {});
|
|
return that.applyBlock(mainBlock, ctx);
|
|
})
|
|
.then((result) => {
|
|
return that.blockResultToHtml(result, blocksOutput);
|
|
})
|
|
.nodeify(callback);
|
|
};
|
|
}
|
|
// @ts-expect-error: nunjucks.Extension
|
|
return Ext;
|
|
}
|
|
/**
|
|
* Apply a block to a content
|
|
* @param {Object} inner
|
|
* @param {Object} context
|
|
* @return {Promise<String>|String}
|
|
*/
|
|
applyBlock(inner, context) {
|
|
const processFn = this.getProcess();
|
|
inner = inner || {};
|
|
inner.args = inner.args || [];
|
|
inner.kwargs = inner.kwargs || {};
|
|
inner.blocks = inner.blocks || [];
|
|
const r = processFn.call(context, inner);
|
|
if (promise_1.default.isPromiseAlike(r)) {
|
|
return r.then(this.normalizeBlockResult.bind(this));
|
|
}
|
|
else {
|
|
return this.normalizeBlockResult(r);
|
|
}
|
|
}
|
|
/**
|
|
* Normalize result from a block process function
|
|
* @param {Object|String} result
|
|
* @return {Object}
|
|
*/
|
|
normalizeBlockResult(result) {
|
|
if (is_1.default.string(result)) {
|
|
result = { body: result };
|
|
}
|
|
result.name = this.getName();
|
|
return result;
|
|
}
|
|
/**
|
|
* Convert a block result to HTML
|
|
* @param {Object} result
|
|
* @param {Object} blocksOutput: stored post processing blocks in this object
|
|
* @return {string}
|
|
*/
|
|
blockResultToHtml(result, blocksOutput) {
|
|
let indexedKey;
|
|
const toIndex = !result.parse || result.post !== undefined;
|
|
if (toIndex) {
|
|
indexedKey = (0, genKey_1.default)();
|
|
blocksOutput[indexedKey] = result;
|
|
}
|
|
// Parsable block, just return it
|
|
if (result.parse) {
|
|
return result.body;
|
|
}
|
|
// Return it as a position marker
|
|
return `{{-%${indexedKey}%-}}`;
|
|
}
|
|
/**
|
|
* Create a template block from a function or an object
|
|
* @param {string} blockName
|
|
* @param {Object} block
|
|
* @return {TemplateBlock}
|
|
*/
|
|
static create(blockName, block) {
|
|
if (is_1.default.fn(block)) {
|
|
// @ts-expect-error ts-migrate(2350) FIXME: Only a void function can be called with the 'new' ... Remove this comment to see the full error message
|
|
block = new immutable_1.default.Map({
|
|
process: block
|
|
});
|
|
}
|
|
block = new TemplateBlock(block);
|
|
block = block.set("name", blockName);
|
|
return block;
|
|
}
|
|
}
|
|
/**
|
|
* Extract kwargs from an arguments array
|
|
* @param {Array} args
|
|
* @return {Object}
|
|
*/
|
|
function extractKwargs(args) {
|
|
const last = args[args.length - 1];
|
|
return is_1.default.object(last) && last.__keywords ? args.pop() : {};
|
|
}
|
|
exports.default = TemplateBlock;
|