package blocks import ( "encoding/json" "strings" ) // poweredBlockSentinel prefixes a PoweredBlock marker string. It uses NUL // bytes so it can never collide with real block HTML: a BlockFunc returns a // plain HTML string today, and this marker is a distinct, out-of-band signal // that the block is "powered" (template + data, rendered host-side). const poweredBlockSentinel = "\x00bn:powered\x00" // PoweredResult is the decoded payload of a PoweredBlock marker: the template // source and its data map. The wasm guest's RENDER_BLOCK handler decodes it // and forwards it as abiv1.PoweredBlock so the HOST renders the template with // pongo2 — after the block-invoke has returned, keeping the guest free. type PoweredResult struct { Template string `json:"template"` Data map[string]any `json:"data"` } // PoweredBlock marks a block's return value as "powered": instead of final // HTML, the block hands back a template string plus a data map, and the host // renders it (pongo2/ninjatpl) host-side. Return its result directly from a // BlockFunc: // // func MyBlock(ctx context.Context, content map[string]any) string { // posts := loadPosts(ctx) // build data via capabilities // return blocks.PoweredBlock(tmpl, map[string]any{"posts": posts}) // } // // This is the guest-safe replacement for calling blocks.RenderTemplate inside // a block: pongo2 never crosses the wasm boundary, so the guest cannot render // itself — it defers rendering to the host. Because the host renders only // after RENDER_BLOCK returns, any plugin-declared tag/filter the template // hits ({% mytag %} / |myfilter) is a fresh RENDER_TAG / APPLY_FILTER invoke, // never a re-entrant one. func PoweredBlock(template string, data map[string]any) string { payload, err := json.Marshal(PoweredResult{Template: template, Data: data}) if err != nil { // A non-serializable data map is a programming error; fall back to an // empty-data powered result so the template still renders. payload, _ = json.Marshal(PoweredResult{Template: template}) } return poweredBlockSentinel + string(payload) } // DecodePoweredBlock reports whether s is a PoweredBlock marker and, if so, // returns the decoded template + data. The wasm guest uses it to distinguish a // powered result from plain HTML. A non-marker (ordinary HTML) returns ok=false. func DecodePoweredBlock(s string) (PoweredResult, bool) { rest, ok := strings.CutPrefix(s, poweredBlockSentinel) if !ok { return PoweredResult{}, false } var pr PoweredResult if err := json.Unmarshal([]byte(rest), &pr); err != nil { return PoweredResult{}, false } return pr, true }