pluginsdk/blocks/template.go
Alex Dunmow 25bcbad081 feat(render): image block layout, link, and live media attribution
MediaValue gains an Attribution credit resolved through the existing
MediaResolver context hook. The BlockNote image case renders width and
align presets, alt text, an optional link wrap, and the attribution chip
in chip, chipHover, and below modes with the same themed markup as the
cms page-builder image block. Legacy url-and-caption blocks render
byte-identical to the previous output.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 10:51:18 +08:00

86 lines
2.9 KiB
Go

package blocks
import (
"fmt"
"html"
)
// MediaValue represents a rich media object for template substitution.
// Implements fmt.Stringer to render a full <img> tag when used directly.
type MediaValue struct {
Src string // Full URL path (e.g., "/media/uuid/webp.webp")
Alt string // Alt text from media library
Filename string // Original filename
Width int // Image width in pixels
Height int // Image height in pixels
// Attribution is the stock-source credit (Pexels import), nil for
// ordinary uploads. JSON tags mirror the CMS media row's attribution JSONB.
Attribution *MediaAttribution
}
// MediaAttribution is the stock-source credit for imported media.
type MediaAttribution struct {
Source string `json:"source"`
SourceURL string `json:"source_url"`
Photographer string `json:"photographer"`
PhotographerURL string `json:"photographer_url"`
}
// String renders the default <img> tag representation.
func (m MediaValue) String() string {
attrs := fmt.Sprintf(`src="%s" alt="%s" class="w-full h-auto"`,
html.EscapeString(m.Src),
html.EscapeString(m.Alt))
if m.Width > 0 && m.Height > 0 {
attrs += fmt.Sprintf(` width="%d" height="%d"`, m.Width, m.Height)
}
return fmt.Sprintf(`<img %s>`, attrs)
}
// RenderWithClass renders the <img> tag with custom CSS classes.
func (m MediaValue) RenderWithClass(class string) string {
attrs := fmt.Sprintf(`src="%s" alt="%s" class="%s"`,
html.EscapeString(m.Src),
html.EscapeString(m.Alt),
html.EscapeString(class))
if m.Width > 0 && m.Height > 0 {
attrs += fmt.Sprintf(` width="%d" height="%d"`, m.Width, m.Height)
}
return fmt.Sprintf(`<img %s>`, attrs)
}
// TemplateRenderer is the interface for rendering pongo2/Jinja2-style templates.
// The CMS registers an implementation at startup.
type TemplateRenderer interface {
RenderTemplate(tmpl string, data map[string]any) (string, error)
ProcessIcons(html string) string
}
// templateRenderer holds the registered template renderer.
var templateRenderer TemplateRenderer
// RegisterTemplateRenderer sets the global template renderer.
// Called by the CMS at startup to provide pongo2 rendering.
func RegisterTemplateRenderer(r TemplateRenderer) {
templateRenderer = r
}
// RenderTemplate renders a pongo2/Jinja2-style template string with the given data.
// Returns the rendered HTML or an error. Falls back to a no-op if no renderer is registered.
func RenderTemplate(tmpl string, data map[string]any) (string, error) {
if templateRenderer == nil {
// No renderer registered — return template as-is (no variable substitution)
return tmpl, nil
}
return templateRenderer.RenderTemplate(tmpl, data)
}
// ProcessIcons processes ::pack:name:: icon syntax in HTML.
// Returns the HTML with icons replaced, or the original if no renderer is registered.
func ProcessIcons(htmlStr string) string {
if templateRenderer == nil {
return htmlStr
}
return templateRenderer.ProcessIcons(htmlStr)
}