Skip to content

Commit

Permalink
feat: support {{block.super}}, see #38
Browse files Browse the repository at this point in the history
  • Loading branch information
harttle committed Jan 23, 2021
1 parent 0e0dc2a commit a3af44d
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 18 deletions.
40 changes: 27 additions & 13 deletions src/builtin/tags/block.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import BlockMode from '../../context/block-mode'
import { ParseStream, TagToken, TopLevelToken, Template, Context, TagImplOptions, Emitter } from '../../types'
import { BlockDrop } from '../../drop/block-drop'
import { ParseStream, TagToken, TopLevelToken, Template, Context, TagImpl, Emitter } from '../../types'

export default {
parse: function (token: TagToken, remainTokens: TopLevelToken[]) {
parse (this: TagImpl, token: TagToken, remainTokens: TopLevelToken[]) {
const match = /\w+/.exec(token.args)
this.block = match ? match[0] : ''
this.tpls = [] as Template[]
Expand All @@ -14,18 +15,31 @@ export default {
})
stream.start()
},
render: function * (ctx: Context, emitter: Emitter) {
const blocks = ctx.getRegister('blocks')
const childDefined = blocks[this.block]
const r = this.liquid.renderer
const html = childDefined !== undefined
? childDefined
: yield r.renderTemplates(this.tpls, ctx)

* render (this: TagImpl, ctx: Context, emitter: Emitter) {
const blockRender = this.getBlockRender(ctx)
yield this.emitHTML(ctx, emitter, blockRender)
},

getBlockRender (this: TagImpl, ctx: Context) {
const { liquid, tpls } = this
const extendedBlockRender = ctx.getRegister('blocks')[this.block]
const defaultBlockRender = function * (superBlock: BlockDrop) {
ctx.push({ block: superBlock })
const result = yield liquid.renderer.renderTemplates(tpls, ctx)
ctx.pop()
return result
}
return extendedBlockRender
? (superBlock: BlockDrop) => extendedBlockRender(new BlockDrop(() => defaultBlockRender(superBlock)))
: defaultBlockRender
},

* emitHTML (this: TagImpl, ctx: Context, emitter: Emitter, blockRender: (block: BlockDrop) => string) {
if (ctx.getRegister('blockMode', BlockMode.OUTPUT) === BlockMode.STORE) {
blocks[this.block] = html
return
ctx.getRegister('blocks')[this.block] = blockRender
} else {
emitter.write(yield blockRender(new BlockDrop()))
}
emitter.write(html)
}
} as TagImplOptions
}
12 changes: 7 additions & 5 deletions src/builtin/tags/layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,17 @@ export default {
: evalToken(this.file, ctx))
: file.getText()
assert(filepath, () => `illegal filename "${file.getText()}":"${filepath}"`)
const templates = yield liquid._parseFile(filepath, ctx.opts, ctx.sync)

// render the remaining tokens immediately
// render remaining contents and store rendered results
ctx.setRegister('blockMode', BlockMode.STORE)
const blocks = ctx.getRegister('blocks')
const html = yield renderer.renderTemplates(this.tpls, ctx)
if (blocks[''] === undefined) blocks[''] = html
const templates = yield liquid._parseFile(filepath, ctx.opts, ctx.sync)
ctx.push(yield hash.render(ctx))
const blocks = ctx.getRegister('blocks')
if (blocks[''] === undefined) blocks[''] = () => html
ctx.setRegister('blockMode', BlockMode.OUTPUT)

// render the layout file use stored blocks
ctx.push(yield hash.render(ctx))
const partial = yield renderer.renderTemplates(templates, ctx)
ctx.pop()
emitter.write(partial)
Expand Down
13 changes: 13 additions & 0 deletions src/drop/block-drop.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Drop } from './drop'

export class BlockDrop extends Drop {
constructor (
// the block render from layout template
private superBlockRender: () => Iterable<string> = () => ''
) {
super()
}
public super () {
return this.superBlockRender()
}
}
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export { Context } from './context/context'
export { Template } from './template/template'
export { FilterImplOptions } from './template/filter/filter-impl-options'
export { TagImplOptions } from './template/tag/tag-impl-options'
export { TagImpl } from './template/tag/tag-impl'
export { ParseStream } from './parser/parse-stream'
export { Token } from './tokens/token'
export { TopLevelToken } from './tokens/toplevel-token'
Expand Down
30 changes: 30 additions & 0 deletions test/integration/builtin/tags/layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,36 @@ describe('tags/layout', function () {
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('XAYBZ')
})
it('should support block.super', async function () {
mock({
'/parent.html': '{% block css %}<link href="base.css" rel="stylesheet">{% endblock %}'
})
const src = '{% layout "parent.html" %}' +
'{%block css%}{{block.super}}<link href="extra.css" rel="stylesheet">{%endblock%}'
const html = await liquid.parseAndRender(src)
const output = '<link href="base.css" rel="stylesheet"><link href="extra.css" rel="stylesheet">'
return expect(html).to.equal(output)
})
it('should render block.super to empty if no parent exists', async function () {
mock({
'/parent.html': '{% block css %}{{block.super}}<link href="base.css" rel="stylesheet">{% endblock %}'
})
const src = '{% layout "parent.html" %}' +
'{%block css%}{{block.super}}<link href="extra.css" rel="stylesheet">{%endblock%}'
const html = await liquid.parseAndRender(src)
const output = '<link href="base.css" rel="stylesheet"><link href="extra.css" rel="stylesheet">'
return expect(html).to.equal(output)
})
it('should support nested block.super', async function () {
mock({
'/root.html': '{% block css %}<link href="root.css" rel="stylesheet">{% endblock %}',
'/parent.html': '{% layout "root.html" %}{% block css %}{{block.super}}<link href="parent.css" rel="stylesheet">{% endblock %}'
})
const src = '{% layout "parent.html" %}{%block css%}{{block.super}}<link href="extra.css" rel="stylesheet">{%endblock%}'
const html = await liquid.parseAndRender(src)
const output = '<link href="root.css" rel="stylesheet"><link href="parent.css" rel="stylesheet"><link href="extra.css" rel="stylesheet">'
return expect(html).to.equal(output)
})
it('should support variable as layout name', async function () {
mock({
'/parent.html': 'X{% block "a"%}{% endblock %}Y'
Expand Down

0 comments on commit a3af44d

Please sign in to comment.