@paroicms/server
Version:
The ParoiCMS server
172 lines • 7.2 kB
JavaScript
import { Hash, Tag, TypeGuards, assert, evalQuotedToken, evalToken } from 'liquidjs';
import { toEnumerable } from './collection.js';
import { ForloopDrop } from './forloop-drop.js';
export default class RenderTag extends Tag {
static passThroughSymbol = Symbol("passThrough");
static renderScopeSymbol = Symbol("renderScope");
file;
currentFile;
hash;
constructor(token, remainTokens, liquid, parser) {
super(token, remainTokens, liquid);
const tokenizer = this.tokenizer;
this.file = parseFilePath(tokenizer, this.liquid, parser);
this.currentFile = token.file;
while (!tokenizer.end()) {
tokenizer.skipBlank();
const begin = tokenizer.p;
const keyword = tokenizer.readIdentifier();
if (keyword.content === 'with' || keyword.content === 'for') {
tokenizer.skipBlank();
if (tokenizer.peek() !== ':') {
const value = tokenizer.readValue();
if (value) {
const beforeAs = tokenizer.p;
const asStr = tokenizer.readIdentifier();
let alias;
if (asStr.content === 'as')
alias = tokenizer.readIdentifier();
else
tokenizer.p = beforeAs;
this[keyword.content] = { value, alias: alias && alias.content };
tokenizer.skipBlank();
if (tokenizer.peek() === ',')
tokenizer.advance();
continue;
}
}
}
tokenizer.p = begin;
break;
}
this.hash = new Hash(tokenizer, liquid.options.keyValueSeparator);
}
*render(ctx, emitter) {
const { liquid, hash } = this;
let filepath = (yield renderFilePath(this['file'], ctx, liquid));
assert(filepath, () => `illegal file path "${filepath}"`);
if (!filepath.endsWith('.liquid'))
filepath += '.liquid';
const parentEnvs = ctx.environments || {};
const passThrough = parentEnvs[RenderTag.passThroughSymbol];
const renderScope = parentEnvs[RenderTag.renderScopeSymbol];
const childCtx = ctx.spawn();
if (passThrough !== undefined)
childCtx.environments[RenderTag.passThroughSymbol] = passThrough;
if (renderScope !== undefined) {
const ok = renderScope && typeof renderScope === 'object' && !Array.isArray(renderScope);
if (!ok)
throw new Error('RenderTag: renderScopeSymbol payload must be a plain object');
const env = childCtx.bottom();
for (const k of Object.keys(renderScope)) {
const inParent = Object.prototype.hasOwnProperty.call(parentEnvs, k);
const existing = childCtx.getSync([k]);
if (existing !== undefined)
continue;
if (inParent) {
env[k] = parentEnvs[k];
continue;
}
env[k] = renderScope[k];
}
;
childCtx.environments[RenderTag.renderScopeSymbol] = renderScope;
}
const scope = childCtx.bottom();
Object.assign(scope, yield hash.render(ctx));
if (this['with']) {
const { value, alias } = this['with'];
scope[alias || filepath] = yield evalToken(value, ctx);
}
if (this['for']) {
const { value, alias } = this['for'];
const collection = toEnumerable(yield evalToken(value, ctx));
scope['forloop'] = new ForloopDrop(collection.length, value.getText(), alias);
for (const item of collection) {
scope[alias] = item;
const templates = (yield liquid._parsePartialFile(filepath, childCtx.sync, this['currentFile']));
yield liquid.renderer.renderTemplates(templates, childCtx, emitter);
scope['forloop'].next();
}
}
else {
const templates = (yield liquid._parsePartialFile(filepath, childCtx.sync, this['currentFile']));
yield liquid.renderer.renderTemplates(templates, childCtx, emitter);
}
}
*children(partials, sync) {
if (partials && typeof this['file'] === 'string') {
return (yield this.liquid._parsePartialFile(this['file'], sync, this['currentFile']));
}
return [];
}
partialScope() {
if (typeof this['file'] === 'string') {
const names = Object.keys(this.hash.hash);
if (this['with']) {
const { value, alias } = this['with'];
if (typeof alias === 'string') {
names.push([alias, value]);
}
else if (typeof this.file === 'string') {
names.push([this.file, value]);
}
}
if (this['for']) {
const { value, alias } = this['for'];
if (typeof alias === 'string') {
names.push([alias, value]);
}
else if (typeof this.file === 'string') {
names.push([this.file, value]);
}
}
return { name: this['file'], isolated: true, scope: names };
}
}
*arguments() {
for (const v of Object.values(this.hash.hash)) {
if (TypeGuards.isValueToken ? TypeGuards.isValueToken(v) : true)
yield v;
}
if (this['with']) {
const { value } = this['with'];
if (TypeGuards.isValueToken ? TypeGuards.isValueToken(value) : true)
yield value;
}
if (this['for']) {
const { value } = this['for'];
if (TypeGuards.isValueToken ? TypeGuards.isValueToken(value) : true)
yield value;
}
}
}
export function parseFilePath(tokenizer, liquid, parser) {
if (liquid.options.dynamicPartials) {
const file = tokenizer.readValue();
tokenizer.assert(file, 'illegal file path');
if (file.getText() === 'none')
return;
if (TypeGuards.isQuotedToken(file)) {
const templates = parser.parse(evalQuotedToken(file));
return optimize(templates);
}
return file;
}
const tokens = [...tokenizer.readFileNameTemplate(liquid.options)];
const templates = optimize(parser.parseTokens(tokens));
return templates === 'none' ? undefined : templates;
}
function optimize(templates) {
if (templates.length === 1 && TypeGuards.isHTMLToken(templates[0].token))
return templates[0].token.getContent();
return templates;
}
export function* renderFilePath(file, ctx, liquid) {
if (typeof file === 'string')
return file;
if (Array.isArray(file))
return liquid.renderer.renderTemplates(file, ctx);
return yield evalToken(file, ctx);
}
//# sourceMappingURL=render.js.map