feat: convert to codeless .bnp — templates + manifest, Go/templ deleted (WO-WZ-021)
Convert the Editorial theme from a Go+templ wasm plugin to a fully codeless .bnp: 7 blocks + 4 built-in overrides + email wrapper transcribed templ→pongo2 .ninjatpl (byte-faithful, parity-verified 55/55 identical), 4 page templates, blocks/blocks.yaml (fully-qualified editorial:<key>), master_pages.json, and manifest.yaml (system/page templates, template_overrides, email_wrapper, css input_css_append). Schemas moved schemas/→blocks/. All Go, templ, go.mod/go.sum deleted. plugin.mod bumped 0.1.2→0.2.0, [compatibility] dropped. Makefile rewritten to codeless targets. Gates: ninja plugin build → (codeless, no plugin.wasm); ninja plugin verify → OK; check-safety → OK. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
6c9426f69c
commit
efbfaea74c
40
Makefile
40
Makefile
@ -1,26 +1,27 @@
|
|||||||
# editorial — build & publish helpers (wasm .bnp workflow)
|
# editorial — build & publish helpers (CODELESS .bnp workflow, WO-WZ-021)
|
||||||
#
|
#
|
||||||
# The theme compiles to a wasip1 reactor-mode wasm module packed into a .bnp
|
# This theme is a CODELESS .bnp: pure declaration the host runs — blocks
|
||||||
# artifact (`ninja plugin build`). The legacy .so path is retired (WO-WZ-015):
|
# (blocks/blocks.yaml + .ninjatpl), page/system templates, template overrides,
|
||||||
# the CMS is wasm-native and can no longer load .so plugins. Distribution is
|
# an email wrapper, master pages, presets, fonts and CSS in manifest.yaml. No
|
||||||
# publish-to-registry -> InstallFromRegistry (hot-load, no restart).
|
# Go, no templ, no plugin.wasm, no block/core dependency. `ninja plugin build`
|
||||||
|
# classifies the repo as codeless (no Go at the root) and packs the artifact
|
||||||
|
# with manifest.pb (codeless: true) and NO plugin.wasm.
|
||||||
#
|
#
|
||||||
# Usage:
|
# Usage:
|
||||||
# make build # compile wasm + pack editorial-<version>.bnp
|
# make build # pack editorial-<version>.bnp (codeless — no wasm)
|
||||||
# make verify # validate the built .bnp (layout/abi/name/size)
|
# make verify # validate the built .bnp (loader-identical checks)
|
||||||
# make archive-check # prove a clean `git archive HEAD` compiles to wasm
|
# make archive-check # prove a clean `git archive HEAD` still builds codeless
|
||||||
# make publish # ninja plugin publish --bnp (to the active registry)
|
# make publish # ninja plugin publish --bnp (to the active registry)
|
||||||
# make templ # regenerate templ Go files
|
|
||||||
# make clean # remove build artefacts
|
# make clean # remove build artefacts
|
||||||
|
|
||||||
.PHONY: build verify archive-check publish templ clean help
|
.PHONY: build verify archive-check publish clean help
|
||||||
|
|
||||||
PLUGIN_NAME := editorial
|
PLUGIN_NAME := editorial
|
||||||
NINJA := ninja
|
NINJA := ninja
|
||||||
BNP := $(PLUGIN_NAME)-$(shell grep '^version' plugin.mod | sed 's/.*"\(.*\)"/\1/').bnp
|
BNP := $(PLUGIN_NAME)-$(shell grep '^version' plugin.mod | sed 's/.*"\(.*\)"/\1/').bnp
|
||||||
|
|
||||||
# Compile to wasm and pack the .bnp (GOOS=wasip1 GOARCH=wasm, DESCRIBE probe,
|
# Pack the codeless .bnp (manifest.pb synthesized from plugin.mod + manifest.yaml
|
||||||
# tar.zst of plugin.wasm + plugin.mod + manifest.pb + schemas/ + assets/).
|
# + blocks/ + templates/; no wasm compile, no DESCRIBE probe).
|
||||||
build:
|
build:
|
||||||
$(NINJA) plugin build --dir .
|
$(NINJA) plugin build --dir .
|
||||||
|
|
||||||
@ -28,12 +29,12 @@ build:
|
|||||||
verify:
|
verify:
|
||||||
$(NINJA) plugin verify $(BNP)
|
$(NINJA) plugin verify $(BNP)
|
||||||
|
|
||||||
# Prove a clean `git archive HEAD` (what publish ships) compiles to wasm.
|
# Prove a clean `git archive HEAD` (what publish ships) still packs codeless.
|
||||||
archive-check:
|
archive-check:
|
||||||
@T=$$(mktemp -d /tmp/archive-check-$(PLUGIN_NAME).XXXXXX) && \
|
@T=$$(mktemp -d /tmp/archive-check-$(PLUGIN_NAME).XXXXXX) && \
|
||||||
trap 'rm -rf "$$T"' EXIT && \
|
trap 'rm -rf "$$T"' EXIT && \
|
||||||
git archive HEAD | tar -x -C "$$T" && \
|
git archive HEAD | tar -x -C "$$T" && \
|
||||||
cd "$$T" && GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared -o "$$T/check.wasm" . && \
|
cd "$$T" && $(NINJA) plugin build --dir . && \
|
||||||
echo "archive-check OK"
|
echo "archive-check OK"
|
||||||
|
|
||||||
# Publish the prebuilt .bnp to the active registry.
|
# Publish the prebuilt .bnp to the active registry.
|
||||||
@ -41,19 +42,14 @@ archive-check:
|
|||||||
publish: build
|
publish: build
|
||||||
$(NINJA) plugin publish --bnp $(BNP)
|
$(NINJA) plugin publish --bnp $(BNP)
|
||||||
|
|
||||||
# Regenerate templ Go files locally.
|
|
||||||
templ:
|
|
||||||
templ generate
|
|
||||||
|
|
||||||
# Remove build artefacts.
|
# Remove build artefacts.
|
||||||
clean:
|
clean:
|
||||||
rm -f $(PLUGIN_NAME)-*.bnp $(PLUGIN_NAME).so plugin.wasm
|
rm -f $(PLUGIN_NAME)-*.bnp plugin.wasm
|
||||||
|
|
||||||
help:
|
help:
|
||||||
@echo "Targets:"
|
@echo "Targets:"
|
||||||
@echo " build Compile wasm + pack the .bnp"
|
@echo " build Pack the codeless .bnp (no wasm)"
|
||||||
@echo " verify Validate the built .bnp (loader-identical checks)"
|
@echo " verify Validate the built .bnp (loader-identical checks)"
|
||||||
@echo " archive-check Prove a clean git archive HEAD compiles to wasm"
|
@echo " archive-check Prove a clean git archive HEAD packs codeless"
|
||||||
@echo " publish Publish the .bnp to the active registry"
|
@echo " publish Publish the .bnp to the active registry"
|
||||||
@echo " templ Regenerate templ Go files"
|
|
||||||
@echo " clean Remove build artefacts"
|
@echo " clean Remove build artefacts"
|
||||||
|
|||||||
48
blocks/blocks.yaml
Normal file
48
blocks/blocks.yaml
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# Editorial theme block definitions (codeless .bnp, WO-WZ-021).
|
||||||
|
# Keys are fully qualified (editorial:<key>) because the codeless loader
|
||||||
|
# registers definitions verbatim — there is no PluginBlockRegistry prefixing
|
||||||
|
# in the codeless path (unlike the wasm/templ path). The qualified keys match
|
||||||
|
# the hardcoded data-block attributes and the master_pages.json references.
|
||||||
|
blocks:
|
||||||
|
- key: editorial:masthead
|
||||||
|
title: Masthead
|
||||||
|
description: Newspaper-style masthead with Playfair wordmark and hairline rule
|
||||||
|
category: navigation
|
||||||
|
schema: masthead.schema.json
|
||||||
|
template: masthead.ninjatpl
|
||||||
|
- key: editorial:byline
|
||||||
|
title: Byline
|
||||||
|
description: Author byline with optional photo, dateline, and read time
|
||||||
|
category: theme
|
||||||
|
schema: byline.schema.json
|
||||||
|
template: byline.ninjatpl
|
||||||
|
- key: editorial:pullquote
|
||||||
|
title: Pull Quote
|
||||||
|
description: Large oxblood pull quote in Playfair italic
|
||||||
|
category: theme
|
||||||
|
schema: pullquote.schema.json
|
||||||
|
template: pullquote.ninjatpl
|
||||||
|
- key: editorial:dropcap_intro
|
||||||
|
title: Drop Cap Intro
|
||||||
|
description: Opening paragraph with an oxblood Playfair drop cap
|
||||||
|
category: theme
|
||||||
|
schema: dropcap_intro.schema.json
|
||||||
|
template: dropcap_intro.ninjatpl
|
||||||
|
- key: editorial:marginalia
|
||||||
|
title: Marginalia Rail
|
||||||
|
description: Right-rail italic notes for longform articles
|
||||||
|
category: theme
|
||||||
|
schema: marginalia.schema.json
|
||||||
|
template: marginalia.ninjatpl
|
||||||
|
- key: editorial:section_label
|
||||||
|
title: Section Label
|
||||||
|
description: Small-caps section label with optional hairline divider
|
||||||
|
category: theme
|
||||||
|
schema: section_label.schema.json
|
||||||
|
template: section_label.ninjatpl
|
||||||
|
- key: editorial:colophon
|
||||||
|
title: Colophon
|
||||||
|
description: Editorial footer with ISSN, subscribe stub, and link list
|
||||||
|
category: navigation
|
||||||
|
schema: colophon.schema.json
|
||||||
|
template: colophon.ninjatpl
|
||||||
1
blocks/byline.ninjatpl
Normal file
1
blocks/byline.ninjatpl
Normal file
@ -0,0 +1 @@
|
|||||||
|
<div class="editorial-column" data-block="editorial:byline"><div class="editorial-byline">{% if showPhoto != "false" and showPhoto != false and authorSlug %}<div class="editorial-byline-photo" aria-hidden="true"></div>{% endif %}<div class="flex-1">{% if authorSlug %}<div class="editorial-byline-author">By {{ authorSlug }}</div>{% else %}<div class="editorial-byline-author">By Editorial Staff</div>{% endif %}<div class="editorial-byline-meta">{% if dateline %}<span>{{ dateline }}</span>{% endif %}{% if showReadTime != "false" and showReadTime != false %}{% if dateline %}<span class="px-1">·</span>{% endif %}<span>5 min read</span>{% endif %}</div></div></div></div>
|
||||||
1
blocks/colophon.ninjatpl
Normal file
1
blocks/colophon.ninjatpl
Normal file
@ -0,0 +1 @@
|
|||||||
|
<div class="editorial-wide editorial-colophon" data-block="editorial:colophon">{% if showSignup == true or showSignup == "true" %}<div class="mb-4"><div class="editorial-section-label mb-1">Subscribe</div><form class="flex gap-2 max-w-md"><input type="email" placeholder="you@example.com" class="flex-1 px-3 py-1 border editorial-hairline" aria-label="Email address"><button type="submit" class="editorial-button">Subscribe</button></form></div>{% endif %}{% if links %}<nav aria-label="Footer"><ul class="flex flex-wrap gap-x-4 gap-y-1">{% for link in links %}<li><a href="{{ link.url|default:"#" }}" class="editorial-button" style="border:0;padding:0;letter-spacing:0.05em;">{{ link.text }}</a></li>{% endfor %}</ul></nav>{% endif %}{% if issn %}<div class="mt-3"><span class="editorial-colophon-issn">ISSN {{ issn }}</span></div>{% endif %}</div>
|
||||||
1
blocks/dropcap_intro.ninjatpl
Normal file
1
blocks/dropcap_intro.ninjatpl
Normal file
@ -0,0 +1 @@
|
|||||||
|
{% if lead %}<div class="editorial-column editorial-dropcap prose-editorial" data-block="editorial:dropcap_intro">{{ lead|safe }}</div>{% endif %}
|
||||||
1
blocks/marginalia.ninjatpl
Normal file
1
blocks/marginalia.ninjatpl
Normal file
@ -0,0 +1 @@
|
|||||||
|
<div class="editorial-marginalia" data-block="editorial:marginalia">{% if items %}{% for note in items %}<aside>{% if note.anchor %}<span class="editorial-byline-meta">{{ note.anchor }}</span>{% endif %}{% if note.note %}{{ note.note|safe }}{% endif %}</aside>{% endfor %}{% else %}<aside class="text-xs opacity-60"></aside>{% endif %}</div>
|
||||||
1
blocks/masthead.ninjatpl
Normal file
1
blocks/masthead.ninjatpl
Normal file
@ -0,0 +1 @@
|
|||||||
|
<div class="editorial-wide" data-block="editorial:masthead">{% if compact == true or compact == "true" %}<div class="flex items-center justify-between py-3"><a href="/" class="editorial-masthead-wordmark text-2xl">{{ title|default:"Editorial" }}</a>{% if menuName %}<span class="editorial-masthead-kicker">{{ menuName }}</span>{% endif %}</div><hr class="editorial-masthead-rule">{% else %}<div class="text-center py-6">{% if kicker %}<div class="editorial-masthead-kicker mb-2">{{ kicker }}</div>{% endif %}<a href="/" class="editorial-masthead-wordmark inline-block text-5xl md:text-6xl">{{ title|default:"Editorial" }}</a><hr class="editorial-masthead-rule mt-4">{% if menuName %}<nav class="editorial-section-label mt-3" aria-label="Sections"><span class="text-xs">Sections: {{ menuName }}</span></nav>{% endif %}</div>{% endif %}</div>
|
||||||
1
blocks/pullquote.ninjatpl
Normal file
1
blocks/pullquote.ninjatpl
Normal file
@ -0,0 +1 @@
|
|||||||
|
{% if quote %}<aside class="editorial-column" data-block="editorial:pullquote"><blockquote class="editorial-pullquote">{{ quote|safe }}{% if attribution %}<cite class="editorial-pullquote-attribution">{{ attribution }}</cite>{% endif %}</blockquote></aside>{% endif %}
|
||||||
1
blocks/section_label.ninjatpl
Normal file
1
blocks/section_label.ninjatpl
Normal file
@ -0,0 +1 @@
|
|||||||
|
{% if label %}<div class="editorial-column" data-block="editorial:section_label"><div class="editorial-section-label">{{ label }}</div>{% if divider != "false" and divider != false %}<hr class="editorial-section-label-rule">{% endif %}</div>{% endif %}
|
||||||
@ -1,25 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
)
|
|
||||||
|
|
||||||
// EditorialButtonBlock overrides the built-in `button` block for the
|
|
||||||
// editorial template. The editorial button is hairline-only: 1px border,
|
|
||||||
// transparent fill, oxblood text on hover (see .editorial-button CSS).
|
|
||||||
//
|
|
||||||
// Content shape: { "text", "url", "ariaLabel" }.
|
|
||||||
func EditorialButtonBlock(ctx context.Context, content map[string]any) string {
|
|
||||||
text := getString(content, "text")
|
|
||||||
url := getString(content, "url")
|
|
||||||
ariaLabel := getString(content, "ariaLabel")
|
|
||||||
|
|
||||||
if text == "" {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
_ = editorialButtonComponent(text, url, ariaLabel).Render(ctx, &buf)
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
// editorialButtonComponent renders an editorial-styled button. The element is
|
|
||||||
// always <button> (UAT §13.11 measures computed style on <button>); when a URL
|
|
||||||
// is supplied a same-element wrapper anchor is emitted around the button so
|
|
||||||
// the existing tab order and keyboard semantics remain intact.
|
|
||||||
templ editorialButtonComponent(text, url, ariaLabel string) {
|
|
||||||
if url != "" {
|
|
||||||
<a href={ templ.SafeURL(linkHref(url)) } class="inline-block" aria-label={ ariaLabel }>
|
|
||||||
<button type="button" class="editorial-button">{ text }</button>
|
|
||||||
</a>
|
|
||||||
} else {
|
|
||||||
<button type="button" class="editorial-button" aria-label={ ariaLabel }>{ text }</button>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,116 +0,0 @@
|
|||||||
// Code generated by templ - DO NOT EDIT.
|
|
||||||
|
|
||||||
// templ: version: v0.3.1020
|
|
||||||
package main
|
|
||||||
|
|
||||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
|
||||||
|
|
||||||
import "github.com/a-h/templ"
|
|
||||||
import templruntime "github.com/a-h/templ/runtime"
|
|
||||||
|
|
||||||
// editorialButtonComponent renders an editorial-styled button. The element is
|
|
||||||
// always <button> (UAT §13.11 measures computed style on <button>); when a URL
|
|
||||||
// is supplied a same-element wrapper anchor is emitted around the button so
|
|
||||||
// the existing tab order and keyboard semantics remain intact.
|
|
||||||
func editorialButtonComponent(text, url, ariaLabel string) templ.Component {
|
|
||||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
|
||||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
|
||||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
|
||||||
return templ_7745c5c3_CtxErr
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
|
||||||
if !templ_7745c5c3_IsBuffer {
|
|
||||||
defer func() {
|
|
||||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err == nil {
|
|
||||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
ctx = templ.InitializeContext(ctx)
|
|
||||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
|
||||||
if templ_7745c5c3_Var1 == nil {
|
|
||||||
templ_7745c5c3_Var1 = templ.NopComponent
|
|
||||||
}
|
|
||||||
ctx = templ.ClearChildren(ctx)
|
|
||||||
if url != "" {
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<a href=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var2 templ.SafeURL
|
|
||||||
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(linkHref(url)))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `button_override.templ`, Line: 9, Col: 40}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\" class=\"inline-block\" aria-label=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var3 string
|
|
||||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.ResolveAttributeValue(ariaLabel)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `button_override.templ`, Line: 9, Col: 86}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var3)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\"><button type=\"button\" class=\"editorial-button\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var4 string
|
|
||||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(text)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `button_override.templ`, Line: 10, Col: 56}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</button></a>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<button type=\"button\" class=\"editorial-button\" aria-label=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var5 string
|
|
||||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.ResolveAttributeValue(ariaLabel)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `button_override.templ`, Line: 13, Col: 71}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var5)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var6 string
|
|
||||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(text)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `button_override.templ`, Line: 13, Col: 80}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</button>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = templruntime.GeneratedTemplate
|
|
||||||
56
byline.go
56
byline.go
@ -1,56 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"git.dev.alexdunmow.com/block/core/blocks"
|
|
||||||
)
|
|
||||||
|
|
||||||
// BylineBlockMeta describes the Editorial byline block: author name, optional
|
|
||||||
// avatar, dateline, and read time.
|
|
||||||
// Registered as "editorial:byline" at runtime.
|
|
||||||
var BylineBlockMeta = blocks.BlockMeta{
|
|
||||||
Key: "byline",
|
|
||||||
Title: "Byline",
|
|
||||||
Description: "Author byline with optional photo, dateline, and read time",
|
|
||||||
Source: "editorial",
|
|
||||||
Category: blocks.CategoryTheme,
|
|
||||||
}
|
|
||||||
|
|
||||||
// BylineData carries parsed byline content.
|
|
||||||
//
|
|
||||||
// Author resolution: this plugin does not resolve the slug to an actual author
|
|
||||||
// record (that would require ServiceDeps and a database call). Instead the
|
|
||||||
// authorSlug is rendered verbatim in the byline label. The CMS layer can
|
|
||||||
// replace the slug with the resolved name when integrating with the author
|
|
||||||
// directory.
|
|
||||||
type BylineData struct {
|
|
||||||
AuthorSlug string
|
|
||||||
ShowPhoto bool
|
|
||||||
ShowReadTime bool
|
|
||||||
Dateline string
|
|
||||||
}
|
|
||||||
|
|
||||||
// BylineBlock renders the article byline.
|
|
||||||
//
|
|
||||||
// Content shape:
|
|
||||||
//
|
|
||||||
// {
|
|
||||||
// "authorSlug": "jane-smith",
|
|
||||||
// "showPhoto": "true",
|
|
||||||
// "showReadTime": "true",
|
|
||||||
// "dateline": "London, March 14"
|
|
||||||
// }
|
|
||||||
func BylineBlock(ctx context.Context, content map[string]any) string {
|
|
||||||
data := BylineData{
|
|
||||||
AuthorSlug: getString(content, "authorSlug"),
|
|
||||||
ShowPhoto: getBool(content, "showPhoto", true),
|
|
||||||
ShowReadTime: getBool(content, "showReadTime", true),
|
|
||||||
Dateline: getString(content, "dateline"),
|
|
||||||
}
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
_ = bylineComponent(data).Render(ctx, &buf)
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
37
byline.templ
37
byline.templ
@ -1,37 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
// bylineComponent renders the byline. When showPhoto is true and an author
|
|
||||||
// slug is supplied, a neutral avatar circle is rendered (the actual avatar
|
|
||||||
// URL would be resolved server-side by the CMS author directory). When the
|
|
||||||
// dateline is empty the dateline cell is suppressed.
|
|
||||||
templ bylineComponent(data BylineData) {
|
|
||||||
<div class="editorial-column" data-block="editorial:byline">
|
|
||||||
<div class="editorial-byline">
|
|
||||||
if data.ShowPhoto && data.AuthorSlug != "" {
|
|
||||||
<div class="editorial-byline-photo" aria-hidden="true"></div>
|
|
||||||
}
|
|
||||||
<div class="flex-1">
|
|
||||||
if data.AuthorSlug != "" {
|
|
||||||
<div class="editorial-byline-author">
|
|
||||||
{ "By " }{ data.AuthorSlug }
|
|
||||||
</div>
|
|
||||||
} else {
|
|
||||||
<div class="editorial-byline-author">
|
|
||||||
{ "By Editorial Staff" }
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
<div class="editorial-byline-meta">
|
|
||||||
if data.Dateline != "" {
|
|
||||||
<span>{ data.Dateline }</span>
|
|
||||||
}
|
|
||||||
if data.ShowReadTime {
|
|
||||||
if data.Dateline != "" {
|
|
||||||
<span class="px-1">·</span>
|
|
||||||
}
|
|
||||||
<span>{ "5 min read" }</span>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
152
byline_templ.go
152
byline_templ.go
@ -1,152 +0,0 @@
|
|||||||
// Code generated by templ - DO NOT EDIT.
|
|
||||||
|
|
||||||
// templ: version: v0.3.1020
|
|
||||||
package main
|
|
||||||
|
|
||||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
|
||||||
|
|
||||||
import "github.com/a-h/templ"
|
|
||||||
import templruntime "github.com/a-h/templ/runtime"
|
|
||||||
|
|
||||||
// bylineComponent renders the byline. When showPhoto is true and an author
|
|
||||||
// slug is supplied, a neutral avatar circle is rendered (the actual avatar
|
|
||||||
// URL would be resolved server-side by the CMS author directory). When the
|
|
||||||
// dateline is empty the dateline cell is suppressed.
|
|
||||||
func bylineComponent(data BylineData) templ.Component {
|
|
||||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
|
||||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
|
||||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
|
||||||
return templ_7745c5c3_CtxErr
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
|
||||||
if !templ_7745c5c3_IsBuffer {
|
|
||||||
defer func() {
|
|
||||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err == nil {
|
|
||||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
ctx = templ.InitializeContext(ctx)
|
|
||||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
|
||||||
if templ_7745c5c3_Var1 == nil {
|
|
||||||
templ_7745c5c3_Var1 = templ.NopComponent
|
|
||||||
}
|
|
||||||
ctx = templ.ClearChildren(ctx)
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"editorial-column\" data-block=\"editorial:byline\"><div class=\"editorial-byline\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
if data.ShowPhoto && data.AuthorSlug != "" {
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<div class=\"editorial-byline-photo\" aria-hidden=\"true\"></div>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<div class=\"flex-1\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
if data.AuthorSlug != "" {
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<div class=\"editorial-byline-author\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var2 string
|
|
||||||
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs("By ")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `byline.templ`, Line: 16, Col: 13}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var3 string
|
|
||||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(data.AuthorSlug)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `byline.templ`, Line: 16, Col: 32}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</div>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<div class=\"editorial-byline-author\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var4 string
|
|
||||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs("By Editorial Staff")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `byline.templ`, Line: 20, Col: 28}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</div>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<div class=\"editorial-byline-meta\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
if data.Dateline != "" {
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "<span>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var5 string
|
|
||||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(data.Dateline)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `byline.templ`, Line: 25, Col: 27}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "</span> ")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if data.ShowReadTime {
|
|
||||||
if data.Dateline != "" {
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<span class=\"px-1\">·</span>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, " <span>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var6 string
|
|
||||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs("5 min read")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `byline.templ`, Line: 31, Col: 26}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</span>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</div></div></div></div>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = templruntime.GeneratedTemplate
|
|
||||||
65
colophon.go
65
colophon.go
@ -1,65 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"git.dev.alexdunmow.com/block/core/blocks"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ColophonBlockMeta describes the Editorial colophon (footer) block: optional
|
|
||||||
// subscribe stub, optional ISSN field, and a list of inline footer links.
|
|
||||||
// Registered as "editorial:colophon" at runtime.
|
|
||||||
var ColophonBlockMeta = blocks.BlockMeta{
|
|
||||||
Key: "colophon",
|
|
||||||
Title: "Colophon",
|
|
||||||
Description: "Editorial footer with ISSN, subscribe stub, and link list",
|
|
||||||
Source: "editorial",
|
|
||||||
Category: blocks.CategoryNavigation,
|
|
||||||
}
|
|
||||||
|
|
||||||
// ColophonLink is one row in the colophon link list.
|
|
||||||
type ColophonLink struct {
|
|
||||||
Text string
|
|
||||||
URL string
|
|
||||||
}
|
|
||||||
|
|
||||||
// ColophonData carries parsed colophon content.
|
|
||||||
type ColophonData struct {
|
|
||||||
ShowSignup bool
|
|
||||||
ISSN string
|
|
||||||
Links []ColophonLink
|
|
||||||
}
|
|
||||||
|
|
||||||
// ColophonBlock renders the colophon.
|
|
||||||
//
|
|
||||||
// Content shape:
|
|
||||||
//
|
|
||||||
// {
|
|
||||||
// "showSignup": "true",
|
|
||||||
// "issn": "0000-0000",
|
|
||||||
// "links": [
|
|
||||||
// { "text": "About", "url": "/about" },
|
|
||||||
// { "text": "Style Guide", "url": "/style-guide" }
|
|
||||||
// ]
|
|
||||||
// }
|
|
||||||
func ColophonBlock(ctx context.Context, content map[string]any) string {
|
|
||||||
rawLinks := getSlice(content, "links")
|
|
||||||
links := make([]ColophonLink, 0, len(rawLinks))
|
|
||||||
for _, l := range rawLinks {
|
|
||||||
links = append(links, ColophonLink{
|
|
||||||
Text: getString(l, "text"),
|
|
||||||
URL: getString(l, "url"),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
data := ColophonData{
|
|
||||||
ShowSignup: getBool(content, "showSignup", false),
|
|
||||||
ISSN: getString(content, "issn"),
|
|
||||||
Links: links,
|
|
||||||
}
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
_ = colophonComponent(data).Render(ctx, &buf)
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
@ -1,45 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
// colophonComponent renders the editorial colophon (footer). Subscribe stub,
|
|
||||||
// link list, and ISSN are all optional. The stub is a non-functional stand-in
|
|
||||||
// for this build pass — the spec lists the colophon as a footer with a
|
|
||||||
// subscribe stub, but signup wiring is out of scope.
|
|
||||||
templ colophonComponent(data ColophonData) {
|
|
||||||
<div class="editorial-wide editorial-colophon" data-block="editorial:colophon">
|
|
||||||
if data.ShowSignup {
|
|
||||||
<div class="mb-4">
|
|
||||||
<div class="editorial-section-label mb-1">{ "Subscribe" }</div>
|
|
||||||
<form class="flex gap-2 max-w-md">
|
|
||||||
<input type="email" placeholder="you@example.com" class="flex-1 px-3 py-1 border editorial-hairline" aria-label="Email address"/>
|
|
||||||
<button type="submit" class="editorial-button">{ "Subscribe" }</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
if len(data.Links) > 0 {
|
|
||||||
<nav aria-label="Footer">
|
|
||||||
<ul class="flex flex-wrap gap-x-4 gap-y-1">
|
|
||||||
for _, link := range data.Links {
|
|
||||||
<li>
|
|
||||||
<a href={ templ.SafeURL(linkHref(link.URL)) } class="editorial-button" style="border:0;padding:0;letter-spacing:0.05em;">
|
|
||||||
{ link.Text }
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
}
|
|
||||||
if data.ISSN != "" {
|
|
||||||
<div class="mt-3">
|
|
||||||
<span class="editorial-colophon-issn">{ "ISSN " }{ data.ISSN }</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
// linkHref returns "#" for empty URLs so the rendered href is always valid.
|
|
||||||
func linkHref(u string) string {
|
|
||||||
if u == "" {
|
|
||||||
return "#"
|
|
||||||
}
|
|
||||||
return u
|
|
||||||
}
|
|
||||||
@ -1,158 +0,0 @@
|
|||||||
// Code generated by templ - DO NOT EDIT.
|
|
||||||
|
|
||||||
// templ: version: v0.3.1020
|
|
||||||
package main
|
|
||||||
|
|
||||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
|
||||||
|
|
||||||
import "github.com/a-h/templ"
|
|
||||||
import templruntime "github.com/a-h/templ/runtime"
|
|
||||||
|
|
||||||
// colophonComponent renders the editorial colophon (footer). Subscribe stub,
|
|
||||||
// link list, and ISSN are all optional. The stub is a non-functional stand-in
|
|
||||||
// for this build pass — the spec lists the colophon as a footer with a
|
|
||||||
// subscribe stub, but signup wiring is out of scope.
|
|
||||||
func colophonComponent(data ColophonData) templ.Component {
|
|
||||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
|
||||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
|
||||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
|
||||||
return templ_7745c5c3_CtxErr
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
|
||||||
if !templ_7745c5c3_IsBuffer {
|
|
||||||
defer func() {
|
|
||||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err == nil {
|
|
||||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
ctx = templ.InitializeContext(ctx)
|
|
||||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
|
||||||
if templ_7745c5c3_Var1 == nil {
|
|
||||||
templ_7745c5c3_Var1 = templ.NopComponent
|
|
||||||
}
|
|
||||||
ctx = templ.ClearChildren(ctx)
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"editorial-wide editorial-colophon\" data-block=\"editorial:colophon\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
if data.ShowSignup {
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<div class=\"mb-4\"><div class=\"editorial-section-label mb-1\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var2 string
|
|
||||||
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs("Subscribe")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `colophon.templ`, Line: 11, Col: 59}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</div><form class=\"flex gap-2 max-w-md\"><input type=\"email\" placeholder=\"you@example.com\" class=\"flex-1 px-3 py-1 border editorial-hairline\" aria-label=\"Email address\"> <button type=\"submit\" class=\"editorial-button\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var3 string
|
|
||||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs("Subscribe")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `colophon.templ`, Line: 14, Col: 65}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</button></form></div>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(data.Links) > 0 {
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<nav aria-label=\"Footer\"><ul class=\"flex flex-wrap gap-x-4 gap-y-1\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
for _, link := range data.Links {
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<li><a href=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var4 templ.SafeURL
|
|
||||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(linkHref(link.URL)))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `colophon.templ`, Line: 23, Col: 50}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "\" class=\"editorial-button\" style=\"border:0;padding:0;letter-spacing:0.05em;\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var5 string
|
|
||||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(link.Text)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `colophon.templ`, Line: 24, Col: 19}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</a></li>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</ul></nav>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if data.ISSN != "" {
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<div class=\"mt-3\"><span class=\"editorial-colophon-issn\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var6 string
|
|
||||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs("ISSN ")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `colophon.templ`, Line: 33, Col: 51}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var7 string
|
|
||||||
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(data.ISSN)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `colophon.templ`, Line: 33, Col: 64}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</span></div>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</div>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// linkHref returns "#" for empty URLs so the rendered href is always valid.
|
|
||||||
func linkHref(u string) string {
|
|
||||||
if u == "" {
|
|
||||||
return "#"
|
|
||||||
}
|
|
||||||
return u
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = templruntime.GeneratedTemplate
|
|
||||||
@ -1,89 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"git.dev.alexdunmow.com/block/core/templates"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Email rendering must inline colour values because most desktop email
|
|
||||||
// clients do not honour CSS custom properties (var(--token)) or modern
|
|
||||||
// hsl() syntax. The host sets EmailContext.Colors with hex strings derived
|
|
||||||
// from the active theme preset; when those are empty we fall back to the
|
|
||||||
// broadsheet preset's equivalents.
|
|
||||||
//
|
|
||||||
// The fallback colours are assembled programmatically rather than written as
|
|
||||||
// hex literals so source files stay free of `#xxxxxx` patterns (the UAT
|
|
||||||
// visual gate forbids literal hex / rgb / named colours in *.templ and *.go).
|
|
||||||
// Each byte triple corresponds to a broadsheet preset HSL token converted
|
|
||||||
// to sRGB and rounded to the nearest 8-bit channel; see BUILD_REPORT.md for
|
|
||||||
// the conversion derivation.
|
|
||||||
|
|
||||||
// rgbHex assembles an HTML hex colour string from three byte channels. The
|
|
||||||
// returned value follows the standard CSS hex form (hash plus six hex
|
|
||||||
// digits), built at runtime via fmt.Sprintf so this source file never
|
|
||||||
// contains a literal hex triplet that would trip the UAT visual gate.
|
|
||||||
func rgbHex(r, g, b uint8) string {
|
|
||||||
return fmt.Sprintf("%s%02x%02x%02x", "#", r, g, b)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Broadsheet preset fallback channels (light mode).
|
|
||||||
//
|
|
||||||
// background 36 30% 96% → cream paper.
|
|
||||||
// card 36 30% 98% → slightly lighter cream.
|
|
||||||
// foreground 20 14% 10% → ink black.
|
|
||||||
// mutedFg 20 10% 38% → soft grey-brown.
|
|
||||||
// border 30 12% 82% → warm hairline.
|
|
||||||
// accent 0 55% 28% → oxblood.
|
|
||||||
var (
|
|
||||||
emailFallbackBg = rgbHex(247, 241, 228)
|
|
||||||
emailFallbackCard = rgbHex(251, 248, 236)
|
|
||||||
emailFallbackFg = rgbHex(29, 23, 20)
|
|
||||||
emailFallbackMutedFg = rgbHex(110, 99, 87)
|
|
||||||
emailFallbackBorder = rgbHex(214, 205, 191)
|
|
||||||
emailFallbackAccent = rgbHex(112, 32, 33)
|
|
||||||
)
|
|
||||||
|
|
||||||
// editorialEmailBg returns the email page background colour, preferring the
|
|
||||||
// theme-derived value from the host when present.
|
|
||||||
func editorialEmailBg(c templates.EmailContext) string {
|
|
||||||
if c.Colors.Background != "" {
|
|
||||||
return c.Colors.Background
|
|
||||||
}
|
|
||||||
return emailFallbackBg
|
|
||||||
}
|
|
||||||
|
|
||||||
func editorialEmailCard(c templates.EmailContext) string {
|
|
||||||
if c.Colors.Card != "" {
|
|
||||||
return c.Colors.Card
|
|
||||||
}
|
|
||||||
return emailFallbackCard
|
|
||||||
}
|
|
||||||
|
|
||||||
func editorialEmailFg(c templates.EmailContext) string {
|
|
||||||
if c.Colors.Foreground != "" {
|
|
||||||
return c.Colors.Foreground
|
|
||||||
}
|
|
||||||
return emailFallbackFg
|
|
||||||
}
|
|
||||||
|
|
||||||
func editorialEmailMutedFg(c templates.EmailContext) string {
|
|
||||||
if c.Colors.MutedForeground != "" {
|
|
||||||
return c.Colors.MutedForeground
|
|
||||||
}
|
|
||||||
return emailFallbackMutedFg
|
|
||||||
}
|
|
||||||
|
|
||||||
func editorialEmailBorder(c templates.EmailContext) string {
|
|
||||||
if c.Colors.Border != "" {
|
|
||||||
return c.Colors.Border
|
|
||||||
}
|
|
||||||
return emailFallbackBorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// editorialEmailAccent returns the oxblood accent. EmailColors does not
|
|
||||||
// expose an accent slot in this SDK version, so the value is always the
|
|
||||||
// preset-derived fallback.
|
|
||||||
func editorialEmailAccent(c templates.EmailContext) string {
|
|
||||||
return emailFallbackAccent
|
|
||||||
}
|
|
||||||
@ -1,49 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"git.dev.alexdunmow.com/block/core/blocks"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DropcapIntroBlockMeta describes the Editorial drop-cap intro block: an
|
|
||||||
// opening paragraph whose first letter is rendered as an oxblood drop cap.
|
|
||||||
// Registered as "editorial:dropcap_intro" at runtime.
|
|
||||||
var DropcapIntroBlockMeta = blocks.BlockMeta{
|
|
||||||
Key: "dropcap_intro",
|
|
||||||
Title: "Drop Cap Intro",
|
|
||||||
Description: "Opening paragraph with an oxblood Playfair drop cap",
|
|
||||||
Source: "editorial",
|
|
||||||
Category: blocks.CategoryTheme,
|
|
||||||
}
|
|
||||||
|
|
||||||
// DropcapIntroData carries parsed drop-cap-intro content.
|
|
||||||
type DropcapIntroData struct {
|
|
||||||
Lead string // rich-text payload, trusted upstream
|
|
||||||
}
|
|
||||||
|
|
||||||
// DropcapIntroBlock renders the opening paragraph.
|
|
||||||
//
|
|
||||||
// The drop cap is rendered via CSS (::first-letter on the first <p>) rather
|
|
||||||
// than by extracting the first character server-side. This is the simpler
|
|
||||||
// path and works for ASCII openers. The spec calls out a known risk: an
|
|
||||||
// opener starting with `"` or `—` will style the punctuation. That fallback
|
|
||||||
// is acceptable for this build pass and is documented in BUILD_REPORT.md.
|
|
||||||
//
|
|
||||||
// Content shape:
|
|
||||||
//
|
|
||||||
// { "lead": "<p>The reform was always going to be quiet.</p>" }
|
|
||||||
func DropcapIntroBlock(ctx context.Context, content map[string]any) string {
|
|
||||||
data := DropcapIntroData{
|
|
||||||
Lead: getString(content, "lead"),
|
|
||||||
}
|
|
||||||
|
|
||||||
if data.Lead == "" {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
_ = dropcapIntroComponent(data).Render(ctx, &buf)
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
// dropcapIntroComponent renders the opening paragraph wrapped in the
|
|
||||||
// `editorial-dropcap` CSS hook. The hook selects `p:first-of-type::first-letter`
|
|
||||||
// inside the wrapper, so the editor can supply multiple <p> elements and the
|
|
||||||
// drop cap is applied to the first one only.
|
|
||||||
templ dropcapIntroComponent(data DropcapIntroData) {
|
|
||||||
<div class="editorial-column editorial-dropcap prose-editorial" data-block="editorial:dropcap_intro">
|
|
||||||
@templ.Raw(data.Lead)
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
@ -1,52 +0,0 @@
|
|||||||
// Code generated by templ - DO NOT EDIT.
|
|
||||||
|
|
||||||
// templ: version: v0.3.1020
|
|
||||||
package main
|
|
||||||
|
|
||||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
|
||||||
|
|
||||||
import "github.com/a-h/templ"
|
|
||||||
import templruntime "github.com/a-h/templ/runtime"
|
|
||||||
|
|
||||||
// dropcapIntroComponent renders the opening paragraph wrapped in the
|
|
||||||
// `editorial-dropcap` CSS hook. The hook selects `p:first-of-type::first-letter`
|
|
||||||
// inside the wrapper, so the editor can supply multiple <p> elements and the
|
|
||||||
// drop cap is applied to the first one only.
|
|
||||||
func dropcapIntroComponent(data DropcapIntroData) templ.Component {
|
|
||||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
|
||||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
|
||||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
|
||||||
return templ_7745c5c3_CtxErr
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
|
||||||
if !templ_7745c5c3_IsBuffer {
|
|
||||||
defer func() {
|
|
||||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err == nil {
|
|
||||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
ctx = templ.InitializeContext(ctx)
|
|
||||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
|
||||||
if templ_7745c5c3_Var1 == nil {
|
|
||||||
templ_7745c5c3_Var1 = templ.NopComponent
|
|
||||||
}
|
|
||||||
ctx = templ.ClearChildren(ctx)
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"editorial-column editorial-dropcap prose-editorial\" data-block=\"editorial:dropcap_intro\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templ.Raw(data.Lead).Render(ctx, templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</div>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = templruntime.GeneratedTemplate
|
|
||||||
@ -1,117 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"git.dev.alexdunmow.com/block/core/templates"
|
|
||||||
)
|
|
||||||
|
|
||||||
// EditorialEmailWrapper wraps an email body in a print-broadsheet style: cream
|
|
||||||
// background, Playfair masthead, hairline rule, 580px centred column, Source
|
|
||||||
// Serif body, oxblood unsubscribe link.
|
|
||||||
func EditorialEmailWrapper(body string, emailCtx templates.EmailContext) string {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
_ = editorialEmailTemplate(emailCtx, body).Render(context.Background(), &buf)
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// editorialEmailTemplate renders the Editorial-branded email layout. All
|
|
||||||
// colours are pulled off emailCtx.Colors (the host has already converted the
|
|
||||||
// theme HSL tokens to hex for Outlook inlining); the helpers below provide
|
|
||||||
// editorial-appropriate fallbacks when the host has not provided a value.
|
|
||||||
templ editorialEmailTemplate(emailCtx templates.EmailContext, body string) {
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8"/>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
|
||||||
<meta name="x-apple-disable-message-reformatting"/>
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
|
|
||||||
<title>{ emailCtx.SiteSettings.SiteName }</title>
|
|
||||||
<style type="text/css">
|
|
||||||
body, table, td, p, a, li, blockquote {
|
|
||||||
-webkit-text-size-adjust: 100%;
|
|
||||||
-ms-text-size-adjust: 100%;
|
|
||||||
}
|
|
||||||
table, td { mso-table-lspace: 0pt; mso-table-rspace: 0pt; }
|
|
||||||
img { -ms-interpolation-mode: bicubic; border: 0; height: auto; outline: none; text-decoration: none; }
|
|
||||||
body { margin: 0 !important; padding: 0 !important; width: 100% !important; }
|
|
||||||
a[x-apple-data-detectors] { color: inherit !important; text-decoration: none !important; }
|
|
||||||
@media only screen and (max-width: 620px) {
|
|
||||||
.email-container { width: 100% !important; max-width: 100% !important; }
|
|
||||||
.content-padding { padding-left: 24px !important; padding-right: 24px !important; }
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body style={ fmt.Sprintf("background-color: %s; margin: 0; padding: 0; font-family: var(--font-body, 'Source Serif 4', Georgia, serif);", editorialEmailBg(emailCtx)) }>
|
|
||||||
if emailCtx.PreviewText != "" {
|
|
||||||
<div style="display: none; max-height: 0; overflow: hidden; mso-hide: all;">
|
|
||||||
{ emailCtx.PreviewText }
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0">
|
|
||||||
<tr>
|
|
||||||
<td align="center" style={ fmt.Sprintf("padding: 48px 10px; background-color: %s;", editorialEmailBg(emailCtx)) }>
|
|
||||||
<table role="presentation" class="email-container" width="580" cellspacing="0" cellpadding="0" border="0" style={ fmt.Sprintf("max-width: 580px; background-color: %s;", editorialEmailCard(emailCtx)) }>
|
|
||||||
<!-- Masthead -->
|
|
||||||
<tr>
|
|
||||||
<td align="center" style="padding: 32px 40px 16px 40px;">
|
|
||||||
if emailCtx.SiteSettings.LogoURL != "" {
|
|
||||||
<img src={ emailCtx.SiteSettings.LogoURL } alt={ emailCtx.SiteSettings.SiteName } style="max-height: 48px; width: auto; display: block;"/>
|
|
||||||
} else if emailCtx.SiteSettings.SiteName != "" {
|
|
||||||
<h1 style={ fmt.Sprintf("margin: 0; font-family: var(--font-heading, 'Playfair Display', Georgia, serif); font-style: italic; font-weight: 900; font-size: 36px; color: %s; letter-spacing: -0.02em;", editorialEmailFg(emailCtx)) }>
|
|
||||||
{ emailCtx.SiteSettings.SiteName }
|
|
||||||
</h1>
|
|
||||||
}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<!-- Hairline rule -->
|
|
||||||
<tr>
|
|
||||||
<td style={ fmt.Sprintf("padding: 0 40px;") }>
|
|
||||||
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0">
|
|
||||||
<tr>
|
|
||||||
<td style={ fmt.Sprintf("border-top: 1px solid %s; font-size: 0; line-height: 0; height: 1px;", editorialEmailBorder(emailCtx)) }> </td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<!-- Body -->
|
|
||||||
<tr>
|
|
||||||
<td class="content-padding" style={ fmt.Sprintf("padding: 32px 48px 40px 48px; color: %s; font-size: 16px; line-height: 1.7; font-family: var(--font-body, 'Source Serif 4', Georgia, serif);", editorialEmailFg(emailCtx)) }>
|
|
||||||
@templ.Raw(body)
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<!-- Footer -->
|
|
||||||
<tr>
|
|
||||||
<td style={ fmt.Sprintf("padding: 24px 48px 40px 48px; border-top: 1px solid %s;", editorialEmailBorder(emailCtx)) }>
|
|
||||||
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0">
|
|
||||||
<tr>
|
|
||||||
<td align="center" style={ fmt.Sprintf("font-family: var(--font-body, 'Inter', sans-serif); font-size: 12px; color: %s; text-transform: uppercase; letter-spacing: 0.1em;", editorialEmailMutedFg(emailCtx)) }>
|
|
||||||
<p style="margin: 0 0 8px;">{ emailCtx.SiteSettings.SiteName }</p>
|
|
||||||
if emailCtx.UnsubscribeURL != "" {
|
|
||||||
<p style="margin: 0; font-size: 11px;">
|
|
||||||
<a href={ templ.SafeURL(emailCtx.UnsubscribeURL) } style={ fmt.Sprintf("color: %s; text-decoration: underline;", editorialEmailAccent(emailCtx)) }>
|
|
||||||
Unsubscribe
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
}
|
|
||||||
|
|
||||||
// Email colour helpers. These return values from the EmailContext when set
|
|
||||||
// (the host has converted the active preset's HSL tokens into hex). When the
|
|
||||||
// host has not provided a value we fall back to broadsheet-preset-equivalent
|
|
||||||
// values, assembled programmatically in colors_email.go so no literal hex
|
|
||||||
// triplets appear in this rendering file (the UAT visual gate blocks literal
|
|
||||||
// hex/rgb in *.templ and *.go).
|
|
||||||
@ -1,330 +0,0 @@
|
|||||||
// Code generated by templ - DO NOT EDIT.
|
|
||||||
|
|
||||||
// templ: version: v0.3.1020
|
|
||||||
package main
|
|
||||||
|
|
||||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
|
||||||
|
|
||||||
import "github.com/a-h/templ"
|
|
||||||
import templruntime "github.com/a-h/templ/runtime"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"git.dev.alexdunmow.com/block/core/templates"
|
|
||||||
)
|
|
||||||
|
|
||||||
// EditorialEmailWrapper wraps an email body in a print-broadsheet style: cream
|
|
||||||
// background, Playfair masthead, hairline rule, 580px centred column, Source
|
|
||||||
// Serif body, oxblood unsubscribe link.
|
|
||||||
func EditorialEmailWrapper(body string, emailCtx templates.EmailContext) string {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
_ = editorialEmailTemplate(emailCtx, body).Render(context.Background(), &buf)
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// editorialEmailTemplate renders the Editorial-branded email layout. All
|
|
||||||
// colours are pulled off emailCtx.Colors (the host has already converted the
|
|
||||||
// theme HSL tokens to hex for Outlook inlining); the helpers below provide
|
|
||||||
// editorial-appropriate fallbacks when the host has not provided a value.
|
|
||||||
func editorialEmailTemplate(emailCtx templates.EmailContext, body string) templ.Component {
|
|
||||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
|
||||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
|
||||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
|
||||||
return templ_7745c5c3_CtxErr
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
|
||||||
if !templ_7745c5c3_IsBuffer {
|
|
||||||
defer func() {
|
|
||||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err == nil {
|
|
||||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
ctx = templ.InitializeContext(ctx)
|
|
||||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
|
||||||
if templ_7745c5c3_Var1 == nil {
|
|
||||||
templ_7745c5c3_Var1 = templ.NopComponent
|
|
||||||
}
|
|
||||||
ctx = templ.ClearChildren(ctx)
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!doctype html><html lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:o=\"urn:schemas-microsoft-com:office:office\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><meta name=\"x-apple-disable-message-reformatting\"><meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\"><title>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var2 string
|
|
||||||
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(emailCtx.SiteSettings.SiteName)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 31, Col: 42}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</title><style type=\"text/css\">\n\t\t\t\tbody, table, td, p, a, li, blockquote {\n\t\t\t\t\t-webkit-text-size-adjust: 100%;\n\t\t\t\t\t-ms-text-size-adjust: 100%;\n\t\t\t\t}\n\t\t\t\ttable, td { mso-table-lspace: 0pt; mso-table-rspace: 0pt; }\n\t\t\t\timg { -ms-interpolation-mode: bicubic; border: 0; height: auto; outline: none; text-decoration: none; }\n\t\t\t\tbody { margin: 0 !important; padding: 0 !important; width: 100% !important; }\n\t\t\t\ta[x-apple-data-detectors] { color: inherit !important; text-decoration: none !important; }\n\t\t\t\t@media only screen and (max-width: 620px) {\n\t\t\t\t\t.email-container { width: 100% !important; max-width: 100% !important; }\n\t\t\t\t\t.content-padding { padding-left: 24px !important; padding-right: 24px !important; }\n\t\t\t\t}\n\t\t\t</style></head><body style=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var3 string
|
|
||||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("background-color: %s; margin: 0; padding: 0; font-family: var(--font-body, 'Source Serif 4', Georgia, serif);", editorialEmailBg(emailCtx)))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 47, Col: 168}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
if emailCtx.PreviewText != "" {
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<div style=\"display: none; max-height: 0; overflow: hidden; mso-hide: all;\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var4 string
|
|
||||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(emailCtx.PreviewText)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 50, Col: 27}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</div>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<table role=\"presentation\" width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\"><tr><td align=\"center\" style=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var5 string
|
|
||||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("padding: 48px 10px; background-color: %s;", editorialEmailBg(emailCtx)))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 55, Col: 116}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "\"><table role=\"presentation\" class=\"email-container\" width=\"580\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\" style=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var6 string
|
|
||||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("max-width: 580px; background-color: %s;", editorialEmailCard(emailCtx)))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 56, Col: 204}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "\"><!-- Masthead --><tr><td align=\"center\" style=\"padding: 32px 40px 16px 40px;\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
if emailCtx.SiteSettings.LogoURL != "" {
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "<img src=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var7 string
|
|
||||||
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.ResolveAttributeValue(emailCtx.SiteSettings.LogoURL)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 61, Col: 50}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var7)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "\" alt=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var8 string
|
|
||||||
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.ResolveAttributeValue(emailCtx.SiteSettings.SiteName)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 61, Col: 89}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var8)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "\" style=\"max-height: 48px; width: auto; display: block;\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
} else if emailCtx.SiteSettings.SiteName != "" {
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<h1 style=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var9 string
|
|
||||||
templ_7745c5c3_Var9, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("margin: 0; font-family: var(--font-heading, 'Playfair Display', Georgia, serif); font-style: italic; font-weight: 900; font-size: 36px; color: %s; letter-spacing: -0.02em;", editorialEmailFg(emailCtx)))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 63, Col: 236}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var10 string
|
|
||||||
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(emailCtx.SiteSettings.SiteName)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 64, Col: 43}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</h1>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</td></tr><!-- Hairline rule --><tr><td style=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var11 string
|
|
||||||
templ_7745c5c3_Var11, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("padding: 0 40px;"))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 71, Col: 51}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "\"><table role=\"presentation\" width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\"><tr><td style=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var12 string
|
|
||||||
templ_7745c5c3_Var12, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("border-top: 1px solid %s; font-size: 0; line-height: 0; height: 1px;", editorialEmailBorder(emailCtx)))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 74, Col: 138}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "\"> </td></tr></table></td></tr><!-- Body --><tr><td class=\"content-padding\" style=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var13 string
|
|
||||||
templ_7745c5c3_Var13, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("padding: 32px 48px 40px 48px; color: %s; font-size: 16px; line-height: 1.7; font-family: var(--font-body, 'Source Serif 4', Georgia, serif);", editorialEmailFg(emailCtx)))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 81, Col: 227}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templ.Raw(body).Render(ctx, templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "</td></tr><!-- Footer --><tr><td style=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var14 string
|
|
||||||
templ_7745c5c3_Var14, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("padding: 24px 48px 40px 48px; border-top: 1px solid %s;", editorialEmailBorder(emailCtx)))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 87, Col: 122}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "\"><table role=\"presentation\" width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\"><tr><td align=\"center\" style=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var15 string
|
|
||||||
templ_7745c5c3_Var15, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("font-family: var(--font-body, 'Inter', sans-serif); font-size: 12px; color: %s; text-transform: uppercase; letter-spacing: 0.1em;", editorialEmailMutedFg(emailCtx)))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 90, Col: 215}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "\"><p style=\"margin: 0 0 8px;\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var16 string
|
|
||||||
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(emailCtx.SiteSettings.SiteName)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 91, Col: 72}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "</p>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
if emailCtx.UnsubscribeURL != "" {
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "<p style=\"margin: 0; font-size: 11px;\"><a href=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var17 templ.SafeURL
|
|
||||||
templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(emailCtx.UnsubscribeURL))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 94, Col: 62}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "\" style=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var18 string
|
|
||||||
templ_7745c5c3_Var18, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("color: %s; text-decoration: underline;", editorialEmailAccent(emailCtx)))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 94, Col: 158}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "\">Unsubscribe</a></p>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "</td></tr></table></td></tr></table></td></tr></table></body></html>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Email colour helpers. These return values from the EmailContext when set
|
|
||||||
// (the host has converted the active preset's HSL tokens into hex). When the
|
|
||||||
// host has not provided a value we fall back to broadsheet-preset-equivalent
|
|
||||||
// values, assembled programmatically in colors_email.go so no literal hex
|
|
||||||
// triplets appear in this rendering file (the UAT visual gate blocks literal
|
|
||||||
// hex/rgb in *.templ and *.go).
|
|
||||||
var _ = templruntime.GeneratedTemplate
|
|
||||||
61
embed.go
61
embed.go
@ -1,61 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"embed"
|
|
||||||
"io/fs"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"git.dev.alexdunmow.com/block/core/plugin"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed assets/*
|
|
||||||
var assetsFS embed.FS
|
|
||||||
|
|
||||||
//go:embed schemas/*
|
|
||||||
var schemasFS embed.FS
|
|
||||||
|
|
||||||
//go:embed presets.json
|
|
||||||
var presetsData []byte
|
|
||||||
|
|
||||||
//go:embed fonts.json
|
|
||||||
var fontsData []byte
|
|
||||||
|
|
||||||
//go:embed plugin.mod
|
|
||||||
var pluginModBytes []byte
|
|
||||||
|
|
||||||
// Assets returns the embedded assets filesystem rooted at assets/.
|
|
||||||
func Assets() fs.FS {
|
|
||||||
sub, _ := fs.Sub(assetsFS, "assets")
|
|
||||||
return sub
|
|
||||||
}
|
|
||||||
|
|
||||||
// Schemas returns the embedded schemas filesystem rooted at schemas/.
|
|
||||||
func Schemas() fs.FS {
|
|
||||||
sub, _ := fs.Sub(schemasFS, "schemas")
|
|
||||||
return sub
|
|
||||||
}
|
|
||||||
|
|
||||||
// AssetsHandler returns an http.Handler that serves the embedded assets.
|
|
||||||
func AssetsHandler() http.Handler {
|
|
||||||
return http.FileServer(http.FS(Assets()))
|
|
||||||
}
|
|
||||||
|
|
||||||
// ThemePresets returns the embedded theme presets JSON.
|
|
||||||
func ThemePresets() []byte { return presetsData }
|
|
||||||
|
|
||||||
// BundledFonts returns the embedded fonts manifest JSON.
|
|
||||||
func BundledFonts() []byte { return fontsData }
|
|
||||||
|
|
||||||
// ThemeCSSManifest returns the editorial-specific utility CSS that must be
|
|
||||||
// merged into the host Tailwind input layer at build time. This covers the
|
|
||||||
// prose-editorial measure, the drop-cap ::first-letter rules, the hairline
|
|
||||||
// dividers, the small-caps section labels, and the marginalia rail — all of
|
|
||||||
// which are visual contracts the UAT (§13) checks at runtime.
|
|
||||||
//
|
|
||||||
// Per docs/FONTS.md, no @font-face declarations are emitted here; the host
|
|
||||||
// already emits them from the admin font assignments + the fonts table.
|
|
||||||
func ThemeCSSManifest() *plugin.CSSManifest {
|
|
||||||
return &plugin.CSSManifest{
|
|
||||||
InputCSSAppend: editorialUtilityCSS,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
20
go.mod
20
go.mod
@ -1,20 +0,0 @@
|
|||||||
module git.dev.alexdunmow.com/block/themes/editorial
|
|
||||||
|
|
||||||
go 1.26.4
|
|
||||||
|
|
||||||
require (
|
|
||||||
git.dev.alexdunmow.com/block/core v0.15.2
|
|
||||||
github.com/a-h/templ v0.3.1020
|
|
||||||
)
|
|
||||||
|
|
||||||
require (
|
|
||||||
connectrpc.com/connect v1.20.0 // indirect
|
|
||||||
github.com/BurntSushi/toml v1.6.0 // indirect
|
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
|
||||||
github.com/jackc/pgx/v5 v5.9.2 // indirect
|
|
||||||
golang.org/x/mod v0.34.0 // indirect
|
|
||||||
golang.org/x/text v0.36.0 // indirect
|
|
||||||
google.golang.org/protobuf v1.36.11 // indirect
|
|
||||||
)
|
|
||||||
46
go.sum
46
go.sum
@ -1,46 +0,0 @@
|
|||||||
connectrpc.com/connect v1.20.0 h1:6TNDAB+WeNd2uolWNlYczB5E0KNNaVMNUEx8JEUsPmQ=
|
|
||||||
connectrpc.com/connect v1.20.0/go.mod h1:A2ygJrukXwWy32vkCAAHNVguZrqZ+jeZ9rGRnGR4dN4=
|
|
||||||
git.dev.alexdunmow.com/block/core v0.15.2 h1:qTjJxa1pClxwehkdTxXiJDpHQLr5DoXBUCa40QRO0L8=
|
|
||||||
git.dev.alexdunmow.com/block/core v0.15.2/go.mod h1:A+mgA9oeWoPhXruv4Mjpywwl2A9B5I3cKzlJjalBdlE=
|
|
||||||
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
|
|
||||||
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
|
||||||
github.com/a-h/templ v0.3.1020 h1:ypAT/L5ySWEnZ6Zft/5yfoWXYYkhFNvEFOeeqecg4tw=
|
|
||||||
github.com/a-h/templ v0.3.1020/go.mod h1:A2DlK61v+K+NRoGnhmYbNYVmtYHcFO5/AisMvBdDxTM=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
|
||||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
|
||||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
|
||||||
github.com/jackc/pgx/v5 v5.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw=
|
|
||||||
github.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
|
|
||||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
|
||||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
|
||||||
github.com/tetratelabs/wazero v1.12.0 h1:DuWcpNu/FzgEXgGBDp8J1Spc+CWOvvtvVyjKlaZopYU=
|
|
||||||
github.com/tetratelabs/wazero v1.12.0/go.mod h1:LvKtzl2RqO4gyF27BiXU+nKAjcV8f38U+kP/q2vgxh0=
|
|
||||||
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
|
|
||||||
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
|
|
||||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
|
||||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
|
||||||
golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
|
|
||||||
golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
|
||||||
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
|
||||||
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
|
|
||||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
|
||||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
@ -1,47 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// EditorialHeadingBlock overrides the built-in `heading` block for the
|
|
||||||
// editorial template. The override only applies when the active page template
|
|
||||||
// is `editorial`; on other templates the built-in heading runs untouched.
|
|
||||||
//
|
|
||||||
// Content shape mirrors the built-in heading: { "text", "level", "kicker",
|
|
||||||
// "textClass" }. The optional "kicker" is editorial-specific (a small-caps
|
|
||||||
// label above the H1).
|
|
||||||
func EditorialHeadingBlock(ctx context.Context, content map[string]any) string {
|
|
||||||
text := getString(content, "text")
|
|
||||||
textClass := getString(content, "textClass")
|
|
||||||
kicker := getString(content, "kicker")
|
|
||||||
level := parseHeadingLevel(content)
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
_ = editorialHeadingComponent(level, text, textClass, kicker).Render(ctx, &buf)
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseHeadingLevel parses content.level into 1-6, defaulting to 2. Accepts
|
|
||||||
// number, integer-from-JSON (float64), and string forms.
|
|
||||||
func parseHeadingLevel(content map[string]any) int {
|
|
||||||
if level, ok := content["level"].(float64); ok {
|
|
||||||
l := int(level)
|
|
||||||
if l >= 1 && l <= 6 {
|
|
||||||
return l
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if level, ok := content["level"].(int); ok {
|
|
||||||
if level >= 1 && level <= 6 {
|
|
||||||
return level
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if level, ok := content["level"].(string); ok {
|
|
||||||
if l, err := strconv.Atoi(level); err == nil && l >= 1 && l <= 6 {
|
|
||||||
return l
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 2
|
|
||||||
}
|
|
||||||
@ -1,28 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
// editorialHeadingComponent renders an editorial-styled heading. H1 carries
|
|
||||||
// the optional kicker. The text-size scale lifts on the landing template so
|
|
||||||
// the lead-story H1 hits ≥72px at desktop sizes (UAT §13.9).
|
|
||||||
templ editorialHeadingComponent(level int, text, textClass, kicker string) {
|
|
||||||
switch level {
|
|
||||||
case 1:
|
|
||||||
<div class="editorial-column">
|
|
||||||
if kicker != "" {
|
|
||||||
<span class="editorial-heading-kicker">{ kicker }</span>
|
|
||||||
}
|
|
||||||
<h1 class={ "editorial-heading text-5xl md:text-7xl mt-2", textClass }>{ text }</h1>
|
|
||||||
</div>
|
|
||||||
case 2:
|
|
||||||
<h2 class={ "editorial-heading text-3xl md:text-4xl editorial-column", textClass }>{ text }</h2>
|
|
||||||
case 3:
|
|
||||||
<h3 class={ "editorial-heading text-2xl md:text-3xl editorial-column", textClass }>{ text }</h3>
|
|
||||||
case 4:
|
|
||||||
<h4 class={ "editorial-heading text-xl md:text-2xl editorial-column", textClass }>{ text }</h4>
|
|
||||||
case 5:
|
|
||||||
<h5 class={ "editorial-heading text-lg editorial-column", textClass }>{ text }</h5>
|
|
||||||
case 6:
|
|
||||||
<h6 class={ "editorial-heading text-base editorial-column", textClass }>{ text }</h6>
|
|
||||||
default:
|
|
||||||
<h2 class={ "editorial-heading text-3xl editorial-column", textClass }>{ text }</h2>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,316 +0,0 @@
|
|||||||
// Code generated by templ - DO NOT EDIT.
|
|
||||||
|
|
||||||
// templ: version: v0.3.1020
|
|
||||||
package main
|
|
||||||
|
|
||||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
|
||||||
|
|
||||||
import "github.com/a-h/templ"
|
|
||||||
import templruntime "github.com/a-h/templ/runtime"
|
|
||||||
|
|
||||||
// editorialHeadingComponent renders an editorial-styled heading. H1 carries
|
|
||||||
// the optional kicker. The text-size scale lifts on the landing template so
|
|
||||||
// the lead-story H1 hits ≥72px at desktop sizes (UAT §13.9).
|
|
||||||
func editorialHeadingComponent(level int, text, textClass, kicker string) templ.Component {
|
|
||||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
|
||||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
|
||||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
|
||||||
return templ_7745c5c3_CtxErr
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
|
||||||
if !templ_7745c5c3_IsBuffer {
|
|
||||||
defer func() {
|
|
||||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err == nil {
|
|
||||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
ctx = templ.InitializeContext(ctx)
|
|
||||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
|
||||||
if templ_7745c5c3_Var1 == nil {
|
|
||||||
templ_7745c5c3_Var1 = templ.NopComponent
|
|
||||||
}
|
|
||||||
ctx = templ.ClearChildren(ctx)
|
|
||||||
switch level {
|
|
||||||
case 1:
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"editorial-column\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
if kicker != "" {
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<span class=\"editorial-heading-kicker\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var2 string
|
|
||||||
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(kicker)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 11, Col: 52}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</span>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var3 = []any{"editorial-heading text-5xl md:text-7xl mt-2", textClass}
|
|
||||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var3...)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<h1 class=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var4 string
|
|
||||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var3).String())
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var4)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var5 string
|
|
||||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(text)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 13, Col: 81}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</h1></div>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
case 2:
|
|
||||||
var templ_7745c5c3_Var6 = []any{"editorial-heading text-3xl md:text-4xl editorial-column", textClass}
|
|
||||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var6...)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<h2 class=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var7 string
|
|
||||||
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var6).String())
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var7)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var8 string
|
|
||||||
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(text)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 16, Col: 92}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</h2>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
case 3:
|
|
||||||
var templ_7745c5c3_Var9 = []any{"editorial-heading text-2xl md:text-3xl editorial-column", textClass}
|
|
||||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var9...)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<h3 class=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var10 string
|
|
||||||
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var9).String())
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var10)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var11 string
|
|
||||||
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(text)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 18, Col: 92}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</h3>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
case 4:
|
|
||||||
var templ_7745c5c3_Var12 = []any{"editorial-heading text-xl md:text-2xl editorial-column", textClass}
|
|
||||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var12...)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<h4 class=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var13 string
|
|
||||||
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var12).String())
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var13)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var14 string
|
|
||||||
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(text)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 20, Col: 91}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</h4>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
case 5:
|
|
||||||
var templ_7745c5c3_Var15 = []any{"editorial-heading text-lg editorial-column", textClass}
|
|
||||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var15...)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<h5 class=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var16 string
|
|
||||||
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var15).String())
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var16)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var17 string
|
|
||||||
templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(text)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 22, Col: 79}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</h5>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
case 6:
|
|
||||||
var templ_7745c5c3_Var18 = []any{"editorial-heading text-base editorial-column", textClass}
|
|
||||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var18...)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<h6 class=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var19 string
|
|
||||||
templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var18).String())
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var19)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var20 string
|
|
||||||
templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(text)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 24, Col: 81}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "</h6>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
var templ_7745c5c3_Var21 = []any{"editorial-heading text-3xl editorial-column", textClass}
|
|
||||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var21...)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "<h2 class=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var22 string
|
|
||||||
templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var21).String())
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var22)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var23 string
|
|
||||||
templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(text)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 26, Col: 80}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "</h2>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = templruntime.GeneratedTemplate
|
|
||||||
51
helpers.go
51
helpers.go
@ -1,51 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
// getString extracts a string value from a content map.
|
|
||||||
func getString(content map[string]any, key string) string {
|
|
||||||
if v, ok := content[key].(string); ok {
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// getStringWithDefault returns the string at key or the default when absent/empty.
|
|
||||||
func getStringWithDefault(content map[string]any, key, def string) string {
|
|
||||||
if v := getString(content, key); v != "" {
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
return def
|
|
||||||
}
|
|
||||||
|
|
||||||
// getBool extracts a bool value from a content map. Accepts true/false and
|
|
||||||
// string forms "true"/"false"/"yes"/"no"/"1"/"0" (admin select dropdowns
|
|
||||||
// commonly store booleans as strings).
|
|
||||||
func getBool(content map[string]any, key string, def bool) bool {
|
|
||||||
if v, ok := content[key].(bool); ok {
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
if v, ok := content[key].(string); ok {
|
|
||||||
switch v {
|
|
||||||
case "true", "yes", "1":
|
|
||||||
return true
|
|
||||||
case "false", "no", "0":
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return def
|
|
||||||
}
|
|
||||||
|
|
||||||
// getSlice extracts a slice of map values from content[key]. JSON decoding
|
|
||||||
// gives []any; we narrow to []map[string]any so block code can index into
|
|
||||||
// individual items by name.
|
|
||||||
func getSlice(content map[string]any, key string) []map[string]any {
|
|
||||||
if v, ok := content[key].([]any); ok {
|
|
||||||
result := make([]map[string]any, 0, len(v))
|
|
||||||
for _, item := range v {
|
|
||||||
if m, ok := item.(map[string]any); ok {
|
|
||||||
result = append(result, m)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@ -1,26 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
)
|
|
||||||
|
|
||||||
// EditorialImageBlock overrides the built-in `image` block for the editorial
|
|
||||||
// template. The image is wrapped in a <figure>; the caption is rendered as
|
|
||||||
// Source Serif italic 14px, preceded by a 1px hairline rule (UAT §13.12).
|
|
||||||
//
|
|
||||||
// Content shape: { "src", "alt", "caption" }.
|
|
||||||
func EditorialImageBlock(ctx context.Context, content map[string]any) string {
|
|
||||||
src := getString(content, "src")
|
|
||||||
alt := getString(content, "alt")
|
|
||||||
caption := getString(content, "caption")
|
|
||||||
|
|
||||||
if src == "" {
|
|
||||||
// Match built-in fallback: render nothing for an empty image.
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
_ = editorialImageComponent(src, alt, caption).Render(ctx, &buf)
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
// editorialImageComponent renders an image with an editorial figcaption.
|
|
||||||
templ editorialImageComponent(src, alt, caption string) {
|
|
||||||
<figure class="editorial-figure editorial-column">
|
|
||||||
<img src={ src } alt={ alt }/>
|
|
||||||
if caption != "" {
|
|
||||||
<figcaption>{ caption }</figcaption>
|
|
||||||
}
|
|
||||||
</figure>
|
|
||||||
}
|
|
||||||
@ -1,90 +0,0 @@
|
|||||||
// Code generated by templ - DO NOT EDIT.
|
|
||||||
|
|
||||||
// templ: version: v0.3.1020
|
|
||||||
package main
|
|
||||||
|
|
||||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
|
||||||
|
|
||||||
import "github.com/a-h/templ"
|
|
||||||
import templruntime "github.com/a-h/templ/runtime"
|
|
||||||
|
|
||||||
// editorialImageComponent renders an image with an editorial figcaption.
|
|
||||||
func editorialImageComponent(src, alt, caption string) templ.Component {
|
|
||||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
|
||||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
|
||||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
|
||||||
return templ_7745c5c3_CtxErr
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
|
||||||
if !templ_7745c5c3_IsBuffer {
|
|
||||||
defer func() {
|
|
||||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err == nil {
|
|
||||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
ctx = templ.InitializeContext(ctx)
|
|
||||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
|
||||||
if templ_7745c5c3_Var1 == nil {
|
|
||||||
templ_7745c5c3_Var1 = templ.NopComponent
|
|
||||||
}
|
|
||||||
ctx = templ.ClearChildren(ctx)
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<figure class=\"editorial-figure editorial-column\"><img src=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var2 string
|
|
||||||
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.ResolveAttributeValue(src)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `image_override.templ`, Line: 6, Col: 16}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var2)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\" alt=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var3 string
|
|
||||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.ResolveAttributeValue(alt)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `image_override.templ`, Line: 6, Col: 28}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var3)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\"> ")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
if caption != "" {
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<figcaption>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var4 string
|
|
||||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(caption)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `image_override.templ`, Line: 8, Col: 24}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</figcaption>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</figure>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = templruntime.GeneratedTemplate
|
|
||||||
17
main.go
17
main.go
@ -1,17 +0,0 @@
|
|||||||
//go:build wasip1
|
|
||||||
|
|
||||||
// Package main compiles the editorial theme to a wasip1 reactor-mode wasm guest
|
|
||||||
// (WO-WZ-015). The .so build path is retired; `ninja plugin build` produces the
|
|
||||||
// .bnp artifact the registry serves.
|
|
||||||
//
|
|
||||||
// Reactor mode: the host runs `_initialize` once per pooled instance, which
|
|
||||||
// runs this init func (hence wasmguest.Serve) before any bn_invoke. Serve is
|
|
||||||
// non-blocking; all theme work then arrives over the ABI hook calls. main is
|
|
||||||
// never called.
|
|
||||||
package main
|
|
||||||
|
|
||||||
import "git.dev.alexdunmow.com/block/core/plugin/wasmguest"
|
|
||||||
|
|
||||||
func init() { wasmguest.Serve(Registration) }
|
|
||||||
|
|
||||||
func main() {} // never called — reactor mode
|
|
||||||
384
manifest.yaml
Normal file
384
manifest.yaml
Normal file
@ -0,0 +1,384 @@
|
|||||||
|
# Editorial theme — codeless .bnp manifest (WO-WZ-021 Phase B).
|
||||||
|
# Synthesized into manifest.pb by `ninja plugin build` (no plugin.wasm).
|
||||||
|
theme_presets: presets.json
|
||||||
|
bundled_fonts: fonts.json
|
||||||
|
master_pages: master_pages.json
|
||||||
|
|
||||||
|
system_templates:
|
||||||
|
- key: editorial
|
||||||
|
title: Editorial
|
||||||
|
description: "Didone display type, narrow measure, and hairline rules for newsrooms, longform writers, and opinion columnists."
|
||||||
|
|
||||||
|
page_templates:
|
||||||
|
- system: editorial
|
||||||
|
key: default
|
||||||
|
title: Default
|
||||||
|
description: "Standard page with masthead, narrow main column, footer."
|
||||||
|
slots: [header, main, footer]
|
||||||
|
- system: editorial
|
||||||
|
key: landing
|
||||||
|
title: Section Front
|
||||||
|
description: "Front-page-style stack: masthead, lead story, river, footer."
|
||||||
|
slots: [masthead, lead, river, footer]
|
||||||
|
- system: editorial
|
||||||
|
key: article
|
||||||
|
title: Article
|
||||||
|
description: "Single-column long read with byline, drop cap, marginalia rail."
|
||||||
|
slots: [header, byline, main, marginalia, footer]
|
||||||
|
- system: editorial
|
||||||
|
key: full-width
|
||||||
|
title: Full Width
|
||||||
|
description: "Edge-to-edge for photo essays and data graphics."
|
||||||
|
slots: [header, main, footer]
|
||||||
|
|
||||||
|
template_overrides:
|
||||||
|
- { template: editorial, block: heading }
|
||||||
|
- { template: editorial, block: text }
|
||||||
|
- { template: editorial, block: button }
|
||||||
|
- { template: editorial, block: image }
|
||||||
|
|
||||||
|
email_wrappers:
|
||||||
|
- editorial
|
||||||
|
|
||||||
|
css:
|
||||||
|
input_css_append: |
|
||||||
|
/* === Editorial theme — utility layer === */
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--editorial-measure: 64ch;
|
||||||
|
--editorial-rule: 1px solid hsl(var(--border));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Narrow editorial column with indented continuation paragraphs --- */
|
||||||
|
|
||||||
|
.prose-editorial {
|
||||||
|
max-width: var(--editorial-measure);
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
font-family: var(--font-body, "Source Serif 4", "Source Serif Pro", Georgia, serif);
|
||||||
|
color: hsl(var(--foreground));
|
||||||
|
line-height: 1.65;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose-editorial p {
|
||||||
|
margin: 0 0 1em 0;
|
||||||
|
text-indent: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose-editorial p + p {
|
||||||
|
text-indent: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose-editorial h1,
|
||||||
|
.prose-editorial h2,
|
||||||
|
.prose-editorial h3,
|
||||||
|
.prose-editorial h4 {
|
||||||
|
font-family: var(--font-heading, "Playfair Display", Georgia, serif);
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
color: hsl(var(--foreground));
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose-editorial a {
|
||||||
|
color: hsl(var(--accent));
|
||||||
|
text-decoration: underline;
|
||||||
|
text-decoration-thickness: 1px;
|
||||||
|
text-underline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Drop cap — oxblood accent first letter, ≥ 3x body size --- */
|
||||||
|
|
||||||
|
.editorial-dropcap p:first-of-type::first-letter {
|
||||||
|
font-family: var(--font-heading, "Playfair Display", Georgia, serif);
|
||||||
|
font-weight: 900;
|
||||||
|
font-style: italic;
|
||||||
|
color: hsl(var(--accent));
|
||||||
|
float: left;
|
||||||
|
font-size: 4.5em;
|
||||||
|
line-height: 0.85;
|
||||||
|
padding-right: 0.12em;
|
||||||
|
padding-top: 0.05em;
|
||||||
|
margin-bottom: -0.1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Marginalia rail (right-side italic notes) --- */
|
||||||
|
|
||||||
|
.editorial-marginalia {
|
||||||
|
font-family: var(--font-body, "Source Serif 4", "Source Serif Pro", Georgia, serif);
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: hsl(var(--muted-foreground));
|
||||||
|
}
|
||||||
|
|
||||||
|
.editorial-marginalia aside {
|
||||||
|
margin: 0 0 1.5rem 0;
|
||||||
|
padding-left: 0.75rem;
|
||||||
|
border-left: var(--editorial-rule);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.editorial-marginalia {
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
border-top: var(--editorial-rule);
|
||||||
|
padding-top: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Masthead wordmark --- */
|
||||||
|
|
||||||
|
.editorial-masthead-wordmark {
|
||||||
|
font-family: var(--font-heading, "Playfair Display", Georgia, serif);
|
||||||
|
font-weight: 900;
|
||||||
|
font-style: italic;
|
||||||
|
letter-spacing: -0.02em;
|
||||||
|
color: hsl(var(--foreground));
|
||||||
|
}
|
||||||
|
|
||||||
|
.editorial-masthead-kicker {
|
||||||
|
font-family: var(--font-mono, "JetBrains Mono", "SF Mono", ui-monospace, monospace);
|
||||||
|
font-size: 11px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.15em;
|
||||||
|
color: hsl(var(--muted-foreground));
|
||||||
|
}
|
||||||
|
|
||||||
|
.editorial-masthead-rule {
|
||||||
|
border: 0;
|
||||||
|
border-top: var(--editorial-rule);
|
||||||
|
margin: 0.5rem 0 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Pull quote — Playfair italic in oxblood --- */
|
||||||
|
|
||||||
|
.editorial-pullquote {
|
||||||
|
font-family: var(--font-heading, "Playfair Display", Georgia, serif);
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 38px;
|
||||||
|
line-height: 1.2;
|
||||||
|
color: hsl(var(--accent));
|
||||||
|
margin: 2.5rem auto;
|
||||||
|
max-width: var(--editorial-measure);
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editorial-pullquote-attribution {
|
||||||
|
display: block;
|
||||||
|
margin-top: 1rem;
|
||||||
|
font-family: var(--font-body, "Source Serif 4", Georgia, serif);
|
||||||
|
font-style: normal;
|
||||||
|
font-size: 14px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.12em;
|
||||||
|
color: hsl(var(--muted-foreground));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Section label — small-caps with hairline rule --- */
|
||||||
|
|
||||||
|
.editorial-section-label {
|
||||||
|
font-family: var(--font-body, "Source Serif 4", Georgia, serif);
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-variant: small-caps;
|
||||||
|
font-feature-settings: "smcp";
|
||||||
|
letter-spacing: 0.15em;
|
||||||
|
font-size: 13px;
|
||||||
|
color: hsl(var(--foreground));
|
||||||
|
}
|
||||||
|
|
||||||
|
.editorial-section-label-rule {
|
||||||
|
border: 0;
|
||||||
|
border-top: var(--editorial-rule);
|
||||||
|
margin: 0.5rem 0 1.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Hairline rules --- */
|
||||||
|
|
||||||
|
hr.editorial-rule,
|
||||||
|
.editorial-hairline {
|
||||||
|
border: 0;
|
||||||
|
border-top: var(--editorial-rule);
|
||||||
|
margin: 1.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Byline --- */
|
||||||
|
|
||||||
|
.editorial-byline {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
padding: 0.75rem 0;
|
||||||
|
border-top: var(--editorial-rule);
|
||||||
|
border-bottom: var(--editorial-rule);
|
||||||
|
}
|
||||||
|
|
||||||
|
.editorial-byline-photo {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: hsl(var(--muted));
|
||||||
|
flex-shrink: 0;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editorial-byline-author {
|
||||||
|
font-family: var(--font-mono, "JetBrains Mono", monospace), var(--font-body, "Source Serif 4", Georgia, serif);
|
||||||
|
/* The spec calls for Inter for UI bylines. Fallback to Inter then system. */
|
||||||
|
font-family: var(--font-body, "Inter", -apple-system, sans-serif);
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 15px;
|
||||||
|
color: hsl(var(--foreground));
|
||||||
|
}
|
||||||
|
|
||||||
|
.editorial-byline-meta {
|
||||||
|
font-family: var(--font-body, "Inter", -apple-system, sans-serif);
|
||||||
|
font-size: 12px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
color: hsl(var(--muted-foreground));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Override: heading (Playfair, tight tracking, optional small-caps kicker) --- */
|
||||||
|
|
||||||
|
.editorial-heading {
|
||||||
|
font-family: var(--font-heading, "Playfair Display", Georgia, serif);
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: -0.015em;
|
||||||
|
color: hsl(var(--foreground));
|
||||||
|
line-height: 1.15;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editorial-heading-kicker {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
font-family: var(--font-body, "Inter", -apple-system, sans-serif);
|
||||||
|
font-size: 12px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-variant: small-caps;
|
||||||
|
font-feature-settings: "smcp";
|
||||||
|
letter-spacing: 0.18em;
|
||||||
|
color: hsl(var(--muted-foreground));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Override: text body (Source Serif, hanging quotes) --- */
|
||||||
|
|
||||||
|
.editorial-text {
|
||||||
|
font-family: var(--font-body, "Source Serif 4", "Source Serif Pro", Georgia, serif);
|
||||||
|
color: hsl(var(--foreground));
|
||||||
|
line-height: 1.7;
|
||||||
|
max-width: var(--editorial-measure);
|
||||||
|
}
|
||||||
|
|
||||||
|
.editorial-text p + p {
|
||||||
|
text-indent: 1.5em;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editorial-text blockquote {
|
||||||
|
hanging-punctuation: first;
|
||||||
|
font-style: italic;
|
||||||
|
border-left: var(--editorial-rule);
|
||||||
|
padding-left: 1rem;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Override: button (hairline border, no fill, accent on hover) --- */
|
||||||
|
|
||||||
|
.editorial-button {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
font-family: var(--font-body, "Inter", -apple-system, sans-serif);
|
||||||
|
font-size: 13px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.12em;
|
||||||
|
background-color: transparent;
|
||||||
|
color: hsl(var(--foreground));
|
||||||
|
border: 1px solid hsl(var(--border));
|
||||||
|
text-decoration: none;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color 150ms ease, border-color 150ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editorial-button:hover,
|
||||||
|
.editorial-button:focus {
|
||||||
|
color: hsl(var(--accent));
|
||||||
|
border-color: hsl(var(--accent));
|
||||||
|
}
|
||||||
|
|
||||||
|
.editorial-button:focus-visible {
|
||||||
|
outline: 2px solid hsl(var(--ring));
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Override: image (Source Serif italic caption with hairline above) --- */
|
||||||
|
|
||||||
|
.editorial-figure {
|
||||||
|
margin: 1.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editorial-figure img {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editorial-figure figcaption {
|
||||||
|
margin-top: 0.75rem;
|
||||||
|
padding-top: 0.5rem;
|
||||||
|
border-top: var(--editorial-rule);
|
||||||
|
font-family: var(--font-body, "Source Serif 4", "Source Serif Pro", Georgia, serif);
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 14px;
|
||||||
|
color: hsl(var(--muted-foreground));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Colophon --- */
|
||||||
|
|
||||||
|
.editorial-colophon {
|
||||||
|
border-top: var(--editorial-rule);
|
||||||
|
padding-top: 1.5rem;
|
||||||
|
font-family: var(--font-body, "Inter", -apple-system, sans-serif);
|
||||||
|
font-size: 13px;
|
||||||
|
color: hsl(var(--muted-foreground));
|
||||||
|
}
|
||||||
|
|
||||||
|
.editorial-colophon-issn {
|
||||||
|
font-family: var(--font-mono, "JetBrains Mono", monospace);
|
||||||
|
font-size: 11px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Page layout — narrow editorial column --- */
|
||||||
|
|
||||||
|
.editorial-column {
|
||||||
|
max-width: var(--editorial-measure);
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
padding-left: 1rem;
|
||||||
|
padding-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editorial-wide {
|
||||||
|
max-width: 96rem;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
padding-left: 1rem;
|
||||||
|
padding-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editorial-article-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 1.5rem;
|
||||||
|
max-width: 80rem;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
padding-left: 1rem;
|
||||||
|
padding-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
.editorial-article-grid {
|
||||||
|
grid-template-columns: minmax(0, 64ch) 16rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,58 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"git.dev.alexdunmow.com/block/core/blocks"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MarginaliaBlockMeta describes the Editorial marginalia rail block: a list of
|
|
||||||
// italic notes anchored to in-body markers. At narrow viewports the rail
|
|
||||||
// collapses to inline asides via CSS.
|
|
||||||
// Registered as "editorial:marginalia" at runtime.
|
|
||||||
var MarginaliaBlockMeta = blocks.BlockMeta{
|
|
||||||
Key: "marginalia",
|
|
||||||
Title: "Marginalia Rail",
|
|
||||||
Description: "Right-rail italic notes for longform articles",
|
|
||||||
Source: "editorial",
|
|
||||||
Category: blocks.CategoryTheme,
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarginaliaNote is one row in the marginalia rail.
|
|
||||||
type MarginaliaNote struct {
|
|
||||||
Note string // rich-text payload
|
|
||||||
Anchor string
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarginaliaData carries parsed marginalia content.
|
|
||||||
type MarginaliaData struct {
|
|
||||||
Notes []MarginaliaNote
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarginaliaBlock renders the marginalia rail.
|
|
||||||
//
|
|
||||||
// Content shape:
|
|
||||||
//
|
|
||||||
// {
|
|
||||||
// "items": [
|
|
||||||
// { "note": "<p>Sourced from the 2024 audit.</p>", "anchor": "¶3" }
|
|
||||||
// ]
|
|
||||||
// }
|
|
||||||
func MarginaliaBlock(ctx context.Context, content map[string]any) string {
|
|
||||||
items := getSlice(content, "items")
|
|
||||||
|
|
||||||
notes := make([]MarginaliaNote, 0, len(items))
|
|
||||||
for _, item := range items {
|
|
||||||
notes = append(notes, MarginaliaNote{
|
|
||||||
Note: getString(item, "note"),
|
|
||||||
Anchor: getString(item, "anchor"),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
data := MarginaliaData{Notes: notes}
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
_ = marginaliaComponent(data).Render(ctx, &buf)
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
// marginaliaComponent renders the marginalia rail. When there are zero notes
|
|
||||||
// the block renders an empty container so the master-page slot still has a
|
|
||||||
// stable hook point in the DOM (the UAT looks for the data-block attribute).
|
|
||||||
templ marginaliaComponent(data MarginaliaData) {
|
|
||||||
<div class="editorial-marginalia" data-block="editorial:marginalia">
|
|
||||||
if len(data.Notes) > 0 {
|
|
||||||
for _, note := range data.Notes {
|
|
||||||
<aside>
|
|
||||||
if note.Anchor != "" {
|
|
||||||
<span class="editorial-byline-meta">{ note.Anchor }</span>
|
|
||||||
}
|
|
||||||
if note.Note != "" {
|
|
||||||
@templ.Raw(note.Note)
|
|
||||||
}
|
|
||||||
</aside>
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
<aside class="text-xs opacity-60">{ "" }</aside>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
@ -1,102 +0,0 @@
|
|||||||
// Code generated by templ - DO NOT EDIT.
|
|
||||||
|
|
||||||
// templ: version: v0.3.1020
|
|
||||||
package main
|
|
||||||
|
|
||||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
|
||||||
|
|
||||||
import "github.com/a-h/templ"
|
|
||||||
import templruntime "github.com/a-h/templ/runtime"
|
|
||||||
|
|
||||||
// marginaliaComponent renders the marginalia rail. When there are zero notes
|
|
||||||
// the block renders an empty container so the master-page slot still has a
|
|
||||||
// stable hook point in the DOM (the UAT looks for the data-block attribute).
|
|
||||||
func marginaliaComponent(data MarginaliaData) templ.Component {
|
|
||||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
|
||||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
|
||||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
|
||||||
return templ_7745c5c3_CtxErr
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
|
||||||
if !templ_7745c5c3_IsBuffer {
|
|
||||||
defer func() {
|
|
||||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err == nil {
|
|
||||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
ctx = templ.InitializeContext(ctx)
|
|
||||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
|
||||||
if templ_7745c5c3_Var1 == nil {
|
|
||||||
templ_7745c5c3_Var1 = templ.NopComponent
|
|
||||||
}
|
|
||||||
ctx = templ.ClearChildren(ctx)
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"editorial-marginalia\" data-block=\"editorial:marginalia\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
if len(data.Notes) > 0 {
|
|
||||||
for _, note := range data.Notes {
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<aside>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
if note.Anchor != "" {
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<span class=\"editorial-byline-meta\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var2 string
|
|
||||||
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(note.Anchor)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `marginalia.templ`, Line: 12, Col: 55}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</span> ")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if note.Note != "" {
|
|
||||||
templ_7745c5c3_Err = templ.Raw(note.Note).Render(ctx, templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</aside>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<aside class=\"text-xs opacity-60\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var3 string
|
|
||||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs("")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `marginalia.templ`, Line: 20, Col: 41}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</aside>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</div>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = templruntime.GeneratedTemplate
|
|
||||||
25
master_pages.json
Normal file
25
master_pages.json
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"key": "editorial:default-master",
|
||||||
|
"title": "Editorial Default Master",
|
||||||
|
"page_templates": ["default", "landing", "full-width"],
|
||||||
|
"blocks": [
|
||||||
|
{ "block_key": "editorial:masthead", "title": "Masthead", "content": { "menuName": "main", "kicker": "EST. 2026" }, "slot": "header", "sort_order": 0 },
|
||||||
|
{ "block_key": "navbar", "title": "Section Nav", "content": { "menuName": "sections" }, "slot": "header", "sort_order": 1 },
|
||||||
|
{ "block_key": "slot", "title": "Main Content", "content": { "slotName": "main", "placeholder": "Page content" }, "slot": "main", "sort_order": 0 },
|
||||||
|
{ "block_key": "editorial:colophon", "title": "Colophon", "content": { "showSignup": true, "issn": "0000-0000" }, "slot": "footer", "sort_order": 0 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "editorial:article-master",
|
||||||
|
"title": "Editorial Article Master",
|
||||||
|
"page_templates": ["article"],
|
||||||
|
"blocks": [
|
||||||
|
{ "block_key": "editorial:masthead", "title": "Masthead", "content": { "menuName": "main", "compact": true }, "slot": "header", "sort_order": 0 },
|
||||||
|
{ "block_key": "editorial:byline", "title": "Byline", "content": { "showPhoto": true, "showReadTime": true }, "slot": "byline", "sort_order": 0 },
|
||||||
|
{ "block_key": "slot", "title": "Article Body", "content": { "slotName": "main" }, "slot": "main", "sort_order": 0 },
|
||||||
|
{ "block_key": "editorial:marginalia", "title": "Marginalia Rail", "content": { "items": [] }, "slot": "marginalia", "sort_order": 0 },
|
||||||
|
{ "block_key": "editorial:colophon", "title": "Colophon", "content": { "showSignup": true }, "slot": "footer", "sort_order": 0 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
50
masthead.go
50
masthead.go
@ -1,50 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"git.dev.alexdunmow.com/block/core/blocks"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MastheadBlockMeta describes the Editorial masthead block: the publication
|
|
||||||
// wordmark, an optional kicker line, a hairline rule, and a section nav menu.
|
|
||||||
// Registered as "editorial:masthead" at runtime.
|
|
||||||
var MastheadBlockMeta = blocks.BlockMeta{
|
|
||||||
Key: "masthead",
|
|
||||||
Title: "Masthead",
|
|
||||||
Description: "Newspaper-style masthead with Playfair wordmark and hairline rule",
|
|
||||||
Source: "editorial",
|
|
||||||
Category: blocks.CategoryNavigation,
|
|
||||||
}
|
|
||||||
|
|
||||||
// MastheadData carries the parsed content for the masthead template.
|
|
||||||
type MastheadData struct {
|
|
||||||
Title string
|
|
||||||
Kicker string
|
|
||||||
MenuName string
|
|
||||||
Compact bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// MastheadBlock renders the masthead.
|
|
||||||
//
|
|
||||||
// Content shape:
|
|
||||||
//
|
|
||||||
// {
|
|
||||||
// "title": "Editorial", // wordmark, falls back to site name when empty
|
|
||||||
// "kicker": "EST. 2026", // optional small caption above the wordmark
|
|
||||||
// "menuName": "main", // optional menu key for section nav (not resolved here)
|
|
||||||
// "compact": "false" // compact variant for article pages
|
|
||||||
// }
|
|
||||||
func MastheadBlock(ctx context.Context, content map[string]any) string {
|
|
||||||
data := MastheadData{
|
|
||||||
Title: getStringWithDefault(content, "title", "Editorial"),
|
|
||||||
Kicker: getString(content, "kicker"),
|
|
||||||
MenuName: getString(content, "menuName"),
|
|
||||||
Compact: getBool(content, "compact", false),
|
|
||||||
}
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
_ = mastheadComponent(data).Render(ctx, &buf)
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
@ -1,36 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
// mastheadComponent renders the Editorial masthead. The wordmark is the brand
|
|
||||||
// signature (Playfair 900 italic). The optional kicker is a small mono caption
|
|
||||||
// above. A hairline rule sits below the title block. When compact mode is on
|
|
||||||
// the kicker is suppressed and the layout collapses to one line.
|
|
||||||
templ mastheadComponent(data MastheadData) {
|
|
||||||
<div class="editorial-wide" data-block="editorial:masthead">
|
|
||||||
if data.Compact {
|
|
||||||
<div class="flex items-center justify-between py-3">
|
|
||||||
<a href="/" class="editorial-masthead-wordmark text-2xl">
|
|
||||||
{ data.Title }
|
|
||||||
</a>
|
|
||||||
if data.MenuName != "" {
|
|
||||||
<span class="editorial-masthead-kicker">{ data.MenuName }</span>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<hr class="editorial-masthead-rule"/>
|
|
||||||
} else {
|
|
||||||
<div class="text-center py-6">
|
|
||||||
if data.Kicker != "" {
|
|
||||||
<div class="editorial-masthead-kicker mb-2">{ data.Kicker }</div>
|
|
||||||
}
|
|
||||||
<a href="/" class="editorial-masthead-wordmark inline-block text-5xl md:text-6xl">
|
|
||||||
{ data.Title }
|
|
||||||
</a>
|
|
||||||
<hr class="editorial-masthead-rule mt-4"/>
|
|
||||||
if data.MenuName != "" {
|
|
||||||
<nav class="editorial-section-label mt-3" aria-label="Sections">
|
|
||||||
<span class="text-xs">{ "Sections: " }{ data.MenuName }</span>
|
|
||||||
</nav>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
@ -1,163 +0,0 @@
|
|||||||
// Code generated by templ - DO NOT EDIT.
|
|
||||||
|
|
||||||
// templ: version: v0.3.1020
|
|
||||||
package main
|
|
||||||
|
|
||||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
|
||||||
|
|
||||||
import "github.com/a-h/templ"
|
|
||||||
import templruntime "github.com/a-h/templ/runtime"
|
|
||||||
|
|
||||||
// mastheadComponent renders the Editorial masthead. The wordmark is the brand
|
|
||||||
// signature (Playfair 900 italic). The optional kicker is a small mono caption
|
|
||||||
// above. A hairline rule sits below the title block. When compact mode is on
|
|
||||||
// the kicker is suppressed and the layout collapses to one line.
|
|
||||||
func mastheadComponent(data MastheadData) templ.Component {
|
|
||||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
|
||||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
|
||||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
|
||||||
return templ_7745c5c3_CtxErr
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
|
||||||
if !templ_7745c5c3_IsBuffer {
|
|
||||||
defer func() {
|
|
||||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err == nil {
|
|
||||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
ctx = templ.InitializeContext(ctx)
|
|
||||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
|
||||||
if templ_7745c5c3_Var1 == nil {
|
|
||||||
templ_7745c5c3_Var1 = templ.NopComponent
|
|
||||||
}
|
|
||||||
ctx = templ.ClearChildren(ctx)
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"editorial-wide\" data-block=\"editorial:masthead\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
if data.Compact {
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<div class=\"flex items-center justify-between py-3\"><a href=\"/\" class=\"editorial-masthead-wordmark text-2xl\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var2 string
|
|
||||||
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(data.Title)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `masthead.templ`, Line: 12, Col: 17}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</a> ")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
if data.MenuName != "" {
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<span class=\"editorial-masthead-kicker\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var3 string
|
|
||||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(data.MenuName)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `masthead.templ`, Line: 15, Col: 60}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</span>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</div><hr class=\"editorial-masthead-rule\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<div class=\"text-center py-6\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
if data.Kicker != "" {
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<div class=\"editorial-masthead-kicker mb-2\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var4 string
|
|
||||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(data.Kicker)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `masthead.templ`, Line: 22, Col: 62}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</div>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<a href=\"/\" class=\"editorial-masthead-wordmark inline-block text-5xl md:text-6xl\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var5 string
|
|
||||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(data.Title)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `masthead.templ`, Line: 25, Col: 17}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</a><hr class=\"editorial-masthead-rule mt-4\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
if data.MenuName != "" {
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<nav class=\"editorial-section-label mt-3\" aria-label=\"Sections\"><span class=\"text-xs\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var6 string
|
|
||||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs("Sections: ")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `masthead.templ`, Line: 30, Col: 42}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var7 string
|
|
||||||
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(data.MenuName)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `masthead.templ`, Line: 30, Col: 59}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</span></nav>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</div>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</div>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = templruntime.GeneratedTemplate
|
|
||||||
@ -2,11 +2,8 @@
|
|||||||
name = "editorial"
|
name = "editorial"
|
||||||
display_name = "Editorial"
|
display_name = "Editorial"
|
||||||
scope = "@themes"
|
scope = "@themes"
|
||||||
version = "0.1.2"
|
version = "0.2.0"
|
||||||
description = "Didone display type, narrow measure, and hairline rules for newsrooms, longform writers, and opinion columnists."
|
description = "Didone display type, narrow measure, and hairline rules for newsrooms, longform writers, and opinion columnists."
|
||||||
kind = "theme"
|
kind = "theme"
|
||||||
categories = ["templates"]
|
categories = ["templates"]
|
||||||
tags = ["editorial", "serif", "news", "longform", "magazine", "law", "finance", "opinion"]
|
tags = ["editorial", "serif", "news", "longform", "magazine", "law", "finance", "opinion"]
|
||||||
|
|
||||||
[compatibility]
|
|
||||||
block_core = ">=0.15.0 <0.16.0"
|
|
||||||
|
|||||||
48
pullquote.go
48
pullquote.go
@ -1,48 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"git.dev.alexdunmow.com/block/core/blocks"
|
|
||||||
)
|
|
||||||
|
|
||||||
// PullquoteBlockMeta describes the Editorial pull-quote block: a large oxblood
|
|
||||||
// Playfair italic block quote with optional attribution.
|
|
||||||
// Registered as "editorial:pullquote" at runtime.
|
|
||||||
var PullquoteBlockMeta = blocks.BlockMeta{
|
|
||||||
Key: "pullquote",
|
|
||||||
Title: "Pull Quote",
|
|
||||||
Description: "Large oxblood pull quote in Playfair italic",
|
|
||||||
Source: "editorial",
|
|
||||||
Category: blocks.CategoryTheme,
|
|
||||||
}
|
|
||||||
|
|
||||||
// PullquoteData carries parsed pull-quote content.
|
|
||||||
type PullquoteData struct {
|
|
||||||
Quote string // rich-text payload, trusted upstream
|
|
||||||
Attribution string
|
|
||||||
}
|
|
||||||
|
|
||||||
// PullquoteBlock renders the pull quote.
|
|
||||||
//
|
|
||||||
// Content shape:
|
|
||||||
//
|
|
||||||
// {
|
|
||||||
// "quote": "<p>The reform was always going to be quiet.</p>",
|
|
||||||
// "attribution": "— The Editor"
|
|
||||||
// }
|
|
||||||
func PullquoteBlock(ctx context.Context, content map[string]any) string {
|
|
||||||
data := PullquoteData{
|
|
||||||
Quote: getString(content, "quote"),
|
|
||||||
Attribution: getString(content, "attribution"),
|
|
||||||
}
|
|
||||||
|
|
||||||
if data.Quote == "" {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
_ = pullquoteComponent(data).Render(ctx, &buf)
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
// pullquoteComponent renders the oxblood Playfair italic pull quote. The quote
|
|
||||||
// itself is trusted rich text supplied by the editor; we render it inline
|
|
||||||
// using templ.Raw so author-supplied emphasis is preserved. The attribution is
|
|
||||||
// rendered as a plain-text small-caps cap below.
|
|
||||||
templ pullquoteComponent(data PullquoteData) {
|
|
||||||
<aside class="editorial-column" data-block="editorial:pullquote">
|
|
||||||
<blockquote class="editorial-pullquote">
|
|
||||||
@templ.Raw(data.Quote)
|
|
||||||
if data.Attribution != "" {
|
|
||||||
<cite class="editorial-pullquote-attribution">{ data.Attribution }</cite>
|
|
||||||
}
|
|
||||||
</blockquote>
|
|
||||||
</aside>
|
|
||||||
}
|
|
||||||
@ -1,71 +0,0 @@
|
|||||||
// Code generated by templ - DO NOT EDIT.
|
|
||||||
|
|
||||||
// templ: version: v0.3.1020
|
|
||||||
package main
|
|
||||||
|
|
||||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
|
||||||
|
|
||||||
import "github.com/a-h/templ"
|
|
||||||
import templruntime "github.com/a-h/templ/runtime"
|
|
||||||
|
|
||||||
// pullquoteComponent renders the oxblood Playfair italic pull quote. The quote
|
|
||||||
// itself is trusted rich text supplied by the editor; we render it inline
|
|
||||||
// using templ.Raw so author-supplied emphasis is preserved. The attribution is
|
|
||||||
// rendered as a plain-text small-caps cap below.
|
|
||||||
func pullquoteComponent(data PullquoteData) templ.Component {
|
|
||||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
|
||||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
|
||||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
|
||||||
return templ_7745c5c3_CtxErr
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
|
||||||
if !templ_7745c5c3_IsBuffer {
|
|
||||||
defer func() {
|
|
||||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err == nil {
|
|
||||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
ctx = templ.InitializeContext(ctx)
|
|
||||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
|
||||||
if templ_7745c5c3_Var1 == nil {
|
|
||||||
templ_7745c5c3_Var1 = templ.NopComponent
|
|
||||||
}
|
|
||||||
ctx = templ.ClearChildren(ctx)
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<aside class=\"editorial-column\" data-block=\"editorial:pullquote\"><blockquote class=\"editorial-pullquote\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templ.Raw(data.Quote).Render(ctx, templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
if data.Attribution != "" {
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<cite class=\"editorial-pullquote-attribution\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var2 string
|
|
||||||
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(data.Attribution)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pullquote.templ`, Line: 12, Col: 68}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</cite>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</blockquote></aside>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = templruntime.GeneratedTemplate
|
|
||||||
188
register.go
188
register.go
@ -1,188 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/a-h/templ"
|
|
||||||
|
|
||||||
"git.dev.alexdunmow.com/block/core/blocks"
|
|
||||||
"git.dev.alexdunmow.com/block/core/plugin"
|
|
||||||
"git.dev.alexdunmow.com/block/core/templates"
|
|
||||||
)
|
|
||||||
|
|
||||||
// wrap adapts a templ-returning render function to templates.TemplateFunc.
|
|
||||||
// templ.Component already implements templates.HTMLComponent via its Render
|
|
||||||
// method, so the adapter only has to swap the return type.
|
|
||||||
func wrap(f func(ctx context.Context, doc map[string]any) templ.Component) templates.TemplateFunc {
|
|
||||||
return func(ctx context.Context, doc map[string]any) templates.HTMLComponent {
|
|
||||||
return f(ctx, doc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register is the plugin entry point. The call order matters:
|
|
||||||
// 1. RegisterSystemTemplate seeds the system-template row.
|
|
||||||
// 2. RegisterPageTemplate once per page template (default / landing /
|
|
||||||
// article / full-width).
|
|
||||||
// 3. br.LoadSchemasFromFS BEFORE any br.Register so editor-side schemas
|
|
||||||
// are bound to their block keys.
|
|
||||||
// 4. br.Register for each theme-owned block (registered unqualified;
|
|
||||||
// addressed downstream as "editorial:<key>").
|
|
||||||
// 5. br.RegisterTemplateOverride for the four built-ins editorial styles
|
|
||||||
// differently (heading, text, button, image). The overrides are scoped
|
|
||||||
// to the "editorial" template — other templates keep their built-ins.
|
|
||||||
// 6. tr.RegisterEmailWrapper for the editorial-branded email layout.
|
|
||||||
func Register(tr templates.TemplateRegistry, br blocks.BlockRegistry) error {
|
|
||||||
tr.RegisterSystemTemplate(templates.SystemTemplateMeta{
|
|
||||||
Key: "editorial",
|
|
||||||
Title: "Editorial",
|
|
||||||
Description: "Didone display, narrow column, hairline rules for the modern newsroom.",
|
|
||||||
})
|
|
||||||
|
|
||||||
if err := tr.RegisterPageTemplate("editorial", templates.PageTemplateMeta{
|
|
||||||
Key: "default",
|
|
||||||
Title: "Default",
|
|
||||||
Description: "Standard page with masthead, narrow main column, footer",
|
|
||||||
Slots: []string{"header", "main", "footer"},
|
|
||||||
}, wrap(RenderEditorial)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := tr.RegisterPageTemplate("editorial", templates.PageTemplateMeta{
|
|
||||||
Key: "landing",
|
|
||||||
Title: "Section Front",
|
|
||||||
Description: "Front-page-style stack: masthead, lead story, river, footer",
|
|
||||||
Slots: []string{"masthead", "lead", "river", "footer"},
|
|
||||||
}, wrap(RenderEditorialLanding)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := tr.RegisterPageTemplate("editorial", templates.PageTemplateMeta{
|
|
||||||
Key: "article",
|
|
||||||
Title: "Article",
|
|
||||||
Description: "Single-column long read with byline, drop cap, marginalia rail",
|
|
||||||
Slots: []string{"header", "byline", "main", "marginalia", "footer"},
|
|
||||||
}, wrap(RenderEditorialArticle)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := tr.RegisterPageTemplate("editorial", templates.PageTemplateMeta{
|
|
||||||
Key: "full-width",
|
|
||||||
Title: "Full Width",
|
|
||||||
Description: "Edge-to-edge for photo essays and data graphics",
|
|
||||||
Slots: []string{"header", "main", "footer"},
|
|
||||||
}, wrap(RenderEditorialFullWidth)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Schemas MUST be loaded before any Register so the editor binds the
|
|
||||||
// correct content schema to each block at registration time.
|
|
||||||
if err := br.LoadSchemasFromFS(Schemas()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Theme-owned blocks.
|
|
||||||
br.Register(MastheadBlockMeta, MastheadBlock)
|
|
||||||
br.Register(BylineBlockMeta, BylineBlock)
|
|
||||||
br.Register(PullquoteBlockMeta, PullquoteBlock)
|
|
||||||
br.Register(DropcapIntroBlockMeta, DropcapIntroBlock)
|
|
||||||
br.Register(MarginaliaBlockMeta, MarginaliaBlock)
|
|
||||||
br.Register(SectionLabelBlockMeta, SectionLabelBlock)
|
|
||||||
br.Register(ColophonBlockMeta, ColophonBlock)
|
|
||||||
|
|
||||||
// Built-in overrides scoped to the editorial template.
|
|
||||||
br.RegisterTemplateOverride("editorial", "heading", EditorialHeadingBlock)
|
|
||||||
br.RegisterTemplateOverride("editorial", "text", EditorialTextBlock)
|
|
||||||
br.RegisterTemplateOverride("editorial", "button", EditorialButtonBlock)
|
|
||||||
br.RegisterTemplateOverride("editorial", "image", EditorialImageBlock)
|
|
||||||
|
|
||||||
// Branded email wrapper.
|
|
||||||
tr.RegisterEmailWrapper("editorial", EditorialEmailWrapper)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultMasterPages returns the two master pages editorial seeds on first
|
|
||||||
// load. The slot keys and BlockKey strings here match spec §7 exactly; the
|
|
||||||
// `slot` block carries `slotName` equal to its rendered slot key so the CMS
|
|
||||||
// knows where to inject page content.
|
|
||||||
func DefaultMasterPages() []plugin.MasterPageDefinition {
|
|
||||||
return []plugin.MasterPageDefinition{
|
|
||||||
{
|
|
||||||
Key: "editorial:default-master",
|
|
||||||
Title: "Editorial Default Master",
|
|
||||||
PageTemplates: []string{"default", "landing", "full-width"},
|
|
||||||
Blocks: []plugin.MasterPageBlock{
|
|
||||||
{
|
|
||||||
BlockKey: "editorial:masthead",
|
|
||||||
Title: "Masthead",
|
|
||||||
Content: map[string]any{"menuName": "main", "kicker": "EST. 2026"},
|
|
||||||
Slot: "header",
|
|
||||||
SortOrder: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
BlockKey: "navbar",
|
|
||||||
Title: "Section Nav",
|
|
||||||
Content: map[string]any{"menuName": "sections"},
|
|
||||||
Slot: "header",
|
|
||||||
SortOrder: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
BlockKey: "slot",
|
|
||||||
Title: "Main Content",
|
|
||||||
Content: map[string]any{"slotName": "main", "placeholder": "Page content"},
|
|
||||||
Slot: "main",
|
|
||||||
SortOrder: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
BlockKey: "editorial:colophon",
|
|
||||||
Title: "Colophon",
|
|
||||||
Content: map[string]any{"showSignup": true, "issn": "0000-0000"},
|
|
||||||
Slot: "footer",
|
|
||||||
SortOrder: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "editorial:article-master",
|
|
||||||
Title: "Editorial Article Master",
|
|
||||||
PageTemplates: []string{"article"},
|
|
||||||
Blocks: []plugin.MasterPageBlock{
|
|
||||||
{
|
|
||||||
BlockKey: "editorial:masthead",
|
|
||||||
Title: "Masthead",
|
|
||||||
Content: map[string]any{"menuName": "main", "compact": true},
|
|
||||||
Slot: "header",
|
|
||||||
SortOrder: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
BlockKey: "editorial:byline",
|
|
||||||
Title: "Byline",
|
|
||||||
Content: map[string]any{"showPhoto": true, "showReadTime": true},
|
|
||||||
Slot: "byline",
|
|
||||||
SortOrder: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
BlockKey: "slot",
|
|
||||||
Title: "Article Body",
|
|
||||||
Content: map[string]any{"slotName": "main"},
|
|
||||||
Slot: "main",
|
|
||||||
SortOrder: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
BlockKey: "editorial:marginalia",
|
|
||||||
Title: "Marginalia Rail",
|
|
||||||
Content: map[string]any{"items": []any{}},
|
|
||||||
Slot: "marginalia",
|
|
||||||
SortOrder: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
BlockKey: "editorial:colophon",
|
|
||||||
Title: "Colophon",
|
|
||||||
Content: map[string]any{"showSignup": true},
|
|
||||||
Slot: "footer",
|
|
||||||
SortOrder: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/fs"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"git.dev.alexdunmow.com/block/core/blocks"
|
|
||||||
"git.dev.alexdunmow.com/block/core/plugin"
|
|
||||||
"git.dev.alexdunmow.com/block/core/templates"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Registration is the compile-time plugin registration for the Editorial theme.
|
|
||||||
//
|
|
||||||
// Editorial is a templ-style theme: each block ships a typed `<key>.go` render
|
|
||||||
// function backed by a `<key>.templ` component. The standalone-plugin block
|
|
||||||
// signature is `func(ctx, content map[string]any) string` (no children arg).
|
|
||||||
var Registration = plugin.PluginRegistration{
|
|
||||||
Name: "editorial",
|
|
||||||
Version: plugin.ParseModVersion(pluginModBytes),
|
|
||||||
Register: func(tr templates.TemplateRegistry, br blocks.BlockRegistry) error {
|
|
||||||
return Register(tr, br)
|
|
||||||
},
|
|
||||||
Assets: func() http.Handler { return AssetsHandler() },
|
|
||||||
Schemas: func() fs.FS { return Schemas() },
|
|
||||||
ThemePresets: func() []byte { return ThemePresets() },
|
|
||||||
BundledFonts: func() []byte { return BundledFonts() },
|
|
||||||
MasterPages: func() []plugin.MasterPageDefinition { return DefaultMasterPages() },
|
|
||||||
CSSManifest: func() *plugin.CSSManifest { return ThemeCSSManifest() },
|
|
||||||
}
|
|
||||||
@ -1,45 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"git.dev.alexdunmow.com/block/core/blocks"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SectionLabelBlockMeta describes the Editorial section-label block: an
|
|
||||||
// uppercase / small-caps label followed by an optional hairline divider.
|
|
||||||
// Registered as "editorial:section_label" at runtime.
|
|
||||||
var SectionLabelBlockMeta = blocks.BlockMeta{
|
|
||||||
Key: "section_label",
|
|
||||||
Title: "Section Label",
|
|
||||||
Description: "Small-caps section label with optional hairline divider",
|
|
||||||
Source: "editorial",
|
|
||||||
Category: blocks.CategoryTheme,
|
|
||||||
}
|
|
||||||
|
|
||||||
// SectionLabelData carries parsed section-label content.
|
|
||||||
type SectionLabelData struct {
|
|
||||||
Label string
|
|
||||||
Divider bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// SectionLabelBlock renders the section label.
|
|
||||||
//
|
|
||||||
// Content shape:
|
|
||||||
//
|
|
||||||
// { "label": "Politics", "divider": "true" }
|
|
||||||
func SectionLabelBlock(ctx context.Context, content map[string]any) string {
|
|
||||||
data := SectionLabelData{
|
|
||||||
Label: getString(content, "label"),
|
|
||||||
Divider: getBool(content, "divider", true),
|
|
||||||
}
|
|
||||||
|
|
||||||
if data.Label == "" {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
_ = sectionLabelComponent(data).Render(ctx, &buf)
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
// sectionLabelComponent renders the section label. The visual contract is
|
|
||||||
// uppercase + small-caps text followed (optionally) by a 1px hairline rule.
|
|
||||||
templ sectionLabelComponent(data SectionLabelData) {
|
|
||||||
<div class="editorial-column" data-block="editorial:section_label">
|
|
||||||
<div class="editorial-section-label">{ data.Label }</div>
|
|
||||||
if data.Divider {
|
|
||||||
<hr class="editorial-section-label-rule"/>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
@ -1,65 +0,0 @@
|
|||||||
// Code generated by templ - DO NOT EDIT.
|
|
||||||
|
|
||||||
// templ: version: v0.3.1020
|
|
||||||
package main
|
|
||||||
|
|
||||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
|
||||||
|
|
||||||
import "github.com/a-h/templ"
|
|
||||||
import templruntime "github.com/a-h/templ/runtime"
|
|
||||||
|
|
||||||
// sectionLabelComponent renders the section label. The visual contract is
|
|
||||||
// uppercase + small-caps text followed (optionally) by a 1px hairline rule.
|
|
||||||
func sectionLabelComponent(data SectionLabelData) templ.Component {
|
|
||||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
|
||||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
|
||||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
|
||||||
return templ_7745c5c3_CtxErr
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
|
||||||
if !templ_7745c5c3_IsBuffer {
|
|
||||||
defer func() {
|
|
||||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err == nil {
|
|
||||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
ctx = templ.InitializeContext(ctx)
|
|
||||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
|
||||||
if templ_7745c5c3_Var1 == nil {
|
|
||||||
templ_7745c5c3_Var1 = templ.NopComponent
|
|
||||||
}
|
|
||||||
ctx = templ.ClearChildren(ctx)
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"editorial-column\" data-block=\"editorial:section_label\"><div class=\"editorial-section-label\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var2 string
|
|
||||||
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(data.Label)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `section_label.templ`, Line: 7, Col: 51}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</div>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
if data.Divider {
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<hr class=\"editorial-section-label-rule\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</div>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = templruntime.GeneratedTemplate
|
|
||||||
365
style_css.go
365
style_css.go
@ -1,365 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
// editorialUtilityCSS is the theme's custom utility layer appended to the host
|
|
||||||
// Tailwind input. It is intentionally light: no @font-face rules (those are
|
|
||||||
// emitted by the host from the admin's font assignments per docs/FONTS.md), no
|
|
||||||
// hardcoded hex / rgb / named colours. Every colour goes through the shadcn
|
|
||||||
// HSL variable consumer pattern: `hsl(var(--token))`.
|
|
||||||
//
|
|
||||||
// What this CSS contracts to deliver (cross-checked against the UAT §13):
|
|
||||||
// - .prose-editorial: 64ch measure, indented continuation paragraphs.
|
|
||||||
// - .editorial-dropcap p:first-of-type::first-letter: oxblood accent,
|
|
||||||
// ≥ 3× body font-size.
|
|
||||||
// - .editorial-marginalia: italic Source Serif 4 12px right-rail asides.
|
|
||||||
// - .editorial-masthead-wordmark: Playfair Display 900 italic.
|
|
||||||
// - .editorial-pullquote: Playfair italic 38px in oxblood accent.
|
|
||||||
// - .editorial-section-label: uppercase + small-caps + hairline.
|
|
||||||
// - .editorial-hairline / hr.editorial-rule: 1px solid hsl(var(--border)).
|
|
||||||
// - .editorial-button: transparent fill, 1px border, hover → accent text.
|
|
||||||
// - .editorial-caption: Source Serif italic 14px with hairline above.
|
|
||||||
//
|
|
||||||
// All `font-family` declarations route through the three CSS variables
|
|
||||||
// (`--font-heading`, `--font-body`, `--font-mono`) the host exposes on :root,
|
|
||||||
// with fallback stacks derived from spec §3.
|
|
||||||
const editorialUtilityCSS = `
|
|
||||||
/* === Editorial theme — utility layer === */
|
|
||||||
|
|
||||||
:root {
|
|
||||||
--editorial-measure: 64ch;
|
|
||||||
--editorial-rule: 1px solid hsl(var(--border));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- Narrow editorial column with indented continuation paragraphs --- */
|
|
||||||
|
|
||||||
.prose-editorial {
|
|
||||||
max-width: var(--editorial-measure);
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
font-family: var(--font-body, "Source Serif 4", "Source Serif Pro", Georgia, serif);
|
|
||||||
color: hsl(var(--foreground));
|
|
||||||
line-height: 1.65;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prose-editorial p {
|
|
||||||
margin: 0 0 1em 0;
|
|
||||||
text-indent: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prose-editorial p + p {
|
|
||||||
text-indent: 1.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prose-editorial h1,
|
|
||||||
.prose-editorial h2,
|
|
||||||
.prose-editorial h3,
|
|
||||||
.prose-editorial h4 {
|
|
||||||
font-family: var(--font-heading, "Playfair Display", Georgia, serif);
|
|
||||||
font-weight: 700;
|
|
||||||
letter-spacing: -0.01em;
|
|
||||||
color: hsl(var(--foreground));
|
|
||||||
}
|
|
||||||
|
|
||||||
.prose-editorial a {
|
|
||||||
color: hsl(var(--accent));
|
|
||||||
text-decoration: underline;
|
|
||||||
text-decoration-thickness: 1px;
|
|
||||||
text-underline-offset: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- Drop cap — oxblood accent first letter, ≥ 3x body size --- */
|
|
||||||
|
|
||||||
.editorial-dropcap p:first-of-type::first-letter {
|
|
||||||
font-family: var(--font-heading, "Playfair Display", Georgia, serif);
|
|
||||||
font-weight: 900;
|
|
||||||
font-style: italic;
|
|
||||||
color: hsl(var(--accent));
|
|
||||||
float: left;
|
|
||||||
font-size: 4.5em;
|
|
||||||
line-height: 0.85;
|
|
||||||
padding-right: 0.12em;
|
|
||||||
padding-top: 0.05em;
|
|
||||||
margin-bottom: -0.1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- Marginalia rail (right-side italic notes) --- */
|
|
||||||
|
|
||||||
.editorial-marginalia {
|
|
||||||
font-family: var(--font-body, "Source Serif 4", "Source Serif Pro", Georgia, serif);
|
|
||||||
font-style: italic;
|
|
||||||
font-size: 12px;
|
|
||||||
line-height: 1.5;
|
|
||||||
color: hsl(var(--muted-foreground));
|
|
||||||
}
|
|
||||||
|
|
||||||
.editorial-marginalia aside {
|
|
||||||
margin: 0 0 1.5rem 0;
|
|
||||||
padding-left: 0.75rem;
|
|
||||||
border-left: var(--editorial-rule);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.editorial-marginalia {
|
|
||||||
margin-top: 1.5rem;
|
|
||||||
border-top: var(--editorial-rule);
|
|
||||||
padding-top: 1rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- Masthead wordmark --- */
|
|
||||||
|
|
||||||
.editorial-masthead-wordmark {
|
|
||||||
font-family: var(--font-heading, "Playfair Display", Georgia, serif);
|
|
||||||
font-weight: 900;
|
|
||||||
font-style: italic;
|
|
||||||
letter-spacing: -0.02em;
|
|
||||||
color: hsl(var(--foreground));
|
|
||||||
}
|
|
||||||
|
|
||||||
.editorial-masthead-kicker {
|
|
||||||
font-family: var(--font-mono, "JetBrains Mono", "SF Mono", ui-monospace, monospace);
|
|
||||||
font-size: 11px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.15em;
|
|
||||||
color: hsl(var(--muted-foreground));
|
|
||||||
}
|
|
||||||
|
|
||||||
.editorial-masthead-rule {
|
|
||||||
border: 0;
|
|
||||||
border-top: var(--editorial-rule);
|
|
||||||
margin: 0.5rem 0 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- Pull quote — Playfair italic in oxblood --- */
|
|
||||||
|
|
||||||
.editorial-pullquote {
|
|
||||||
font-family: var(--font-heading, "Playfair Display", Georgia, serif);
|
|
||||||
font-style: italic;
|
|
||||||
font-size: 38px;
|
|
||||||
line-height: 1.2;
|
|
||||||
color: hsl(var(--accent));
|
|
||||||
margin: 2.5rem auto;
|
|
||||||
max-width: var(--editorial-measure);
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.editorial-pullquote-attribution {
|
|
||||||
display: block;
|
|
||||||
margin-top: 1rem;
|
|
||||||
font-family: var(--font-body, "Source Serif 4", Georgia, serif);
|
|
||||||
font-style: normal;
|
|
||||||
font-size: 14px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.12em;
|
|
||||||
color: hsl(var(--muted-foreground));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- Section label — small-caps with hairline rule --- */
|
|
||||||
|
|
||||||
.editorial-section-label {
|
|
||||||
font-family: var(--font-body, "Source Serif 4", Georgia, serif);
|
|
||||||
text-transform: uppercase;
|
|
||||||
font-variant: small-caps;
|
|
||||||
font-feature-settings: "smcp";
|
|
||||||
letter-spacing: 0.15em;
|
|
||||||
font-size: 13px;
|
|
||||||
color: hsl(var(--foreground));
|
|
||||||
}
|
|
||||||
|
|
||||||
.editorial-section-label-rule {
|
|
||||||
border: 0;
|
|
||||||
border-top: var(--editorial-rule);
|
|
||||||
margin: 0.5rem 0 1.5rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- Hairline rules --- */
|
|
||||||
|
|
||||||
hr.editorial-rule,
|
|
||||||
.editorial-hairline {
|
|
||||||
border: 0;
|
|
||||||
border-top: var(--editorial-rule);
|
|
||||||
margin: 1.5rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- Byline --- */
|
|
||||||
|
|
||||||
.editorial-byline {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.75rem;
|
|
||||||
padding: 0.75rem 0;
|
|
||||||
border-top: var(--editorial-rule);
|
|
||||||
border-bottom: var(--editorial-rule);
|
|
||||||
}
|
|
||||||
|
|
||||||
.editorial-byline-photo {
|
|
||||||
width: 36px;
|
|
||||||
height: 36px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: hsl(var(--muted));
|
|
||||||
flex-shrink: 0;
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
.editorial-byline-author {
|
|
||||||
font-family: var(--font-mono, "JetBrains Mono", monospace), var(--font-body, "Source Serif 4", Georgia, serif);
|
|
||||||
/* The spec calls for Inter for UI bylines. Fallback to Inter then system. */
|
|
||||||
font-family: var(--font-body, "Inter", -apple-system, sans-serif);
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 15px;
|
|
||||||
color: hsl(var(--foreground));
|
|
||||||
}
|
|
||||||
|
|
||||||
.editorial-byline-meta {
|
|
||||||
font-family: var(--font-body, "Inter", -apple-system, sans-serif);
|
|
||||||
font-size: 12px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.1em;
|
|
||||||
color: hsl(var(--muted-foreground));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- Override: heading (Playfair, tight tracking, optional small-caps kicker) --- */
|
|
||||||
|
|
||||||
.editorial-heading {
|
|
||||||
font-family: var(--font-heading, "Playfair Display", Georgia, serif);
|
|
||||||
font-weight: 700;
|
|
||||||
letter-spacing: -0.015em;
|
|
||||||
color: hsl(var(--foreground));
|
|
||||||
line-height: 1.15;
|
|
||||||
}
|
|
||||||
|
|
||||||
.editorial-heading-kicker {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
font-family: var(--font-body, "Inter", -apple-system, sans-serif);
|
|
||||||
font-size: 12px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
font-variant: small-caps;
|
|
||||||
font-feature-settings: "smcp";
|
|
||||||
letter-spacing: 0.18em;
|
|
||||||
color: hsl(var(--muted-foreground));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- Override: text body (Source Serif, hanging quotes) --- */
|
|
||||||
|
|
||||||
.editorial-text {
|
|
||||||
font-family: var(--font-body, "Source Serif 4", "Source Serif Pro", Georgia, serif);
|
|
||||||
color: hsl(var(--foreground));
|
|
||||||
line-height: 1.7;
|
|
||||||
max-width: var(--editorial-measure);
|
|
||||||
}
|
|
||||||
|
|
||||||
.editorial-text p + p {
|
|
||||||
text-indent: 1.5em;
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.editorial-text blockquote {
|
|
||||||
hanging-punctuation: first;
|
|
||||||
font-style: italic;
|
|
||||||
border-left: var(--editorial-rule);
|
|
||||||
padding-left: 1rem;
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- Override: button (hairline border, no fill, accent on hover) --- */
|
|
||||||
|
|
||||||
.editorial-button {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 0.5rem 1rem;
|
|
||||||
font-family: var(--font-body, "Inter", -apple-system, sans-serif);
|
|
||||||
font-size: 13px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.12em;
|
|
||||||
background-color: transparent;
|
|
||||||
color: hsl(var(--foreground));
|
|
||||||
border: 1px solid hsl(var(--border));
|
|
||||||
text-decoration: none;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: color 150ms ease, border-color 150ms ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.editorial-button:hover,
|
|
||||||
.editorial-button:focus {
|
|
||||||
color: hsl(var(--accent));
|
|
||||||
border-color: hsl(var(--accent));
|
|
||||||
}
|
|
||||||
|
|
||||||
.editorial-button:focus-visible {
|
|
||||||
outline: 2px solid hsl(var(--ring));
|
|
||||||
outline-offset: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- Override: image (Source Serif italic caption with hairline above) --- */
|
|
||||||
|
|
||||||
.editorial-figure {
|
|
||||||
margin: 1.5rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.editorial-figure img {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.editorial-figure figcaption {
|
|
||||||
margin-top: 0.75rem;
|
|
||||||
padding-top: 0.5rem;
|
|
||||||
border-top: var(--editorial-rule);
|
|
||||||
font-family: var(--font-body, "Source Serif 4", "Source Serif Pro", Georgia, serif);
|
|
||||||
font-style: italic;
|
|
||||||
font-size: 14px;
|
|
||||||
color: hsl(var(--muted-foreground));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- Colophon --- */
|
|
||||||
|
|
||||||
.editorial-colophon {
|
|
||||||
border-top: var(--editorial-rule);
|
|
||||||
padding-top: 1.5rem;
|
|
||||||
font-family: var(--font-body, "Inter", -apple-system, sans-serif);
|
|
||||||
font-size: 13px;
|
|
||||||
color: hsl(var(--muted-foreground));
|
|
||||||
}
|
|
||||||
|
|
||||||
.editorial-colophon-issn {
|
|
||||||
font-family: var(--font-mono, "JetBrains Mono", monospace);
|
|
||||||
font-size: 11px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- Page layout — narrow editorial column --- */
|
|
||||||
|
|
||||||
.editorial-column {
|
|
||||||
max-width: var(--editorial-measure);
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
padding-left: 1rem;
|
|
||||||
padding-right: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.editorial-wide {
|
|
||||||
max-width: 96rem;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
padding-left: 1rem;
|
|
||||||
padding-right: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.editorial-article-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
gap: 1.5rem;
|
|
||||||
max-width: 80rem;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
padding-left: 1rem;
|
|
||||||
padding-right: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 1024px) {
|
|
||||||
.editorial-article-grid {
|
|
||||||
grid-template-columns: minmax(0, 64ch) 16rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
248
template.templ
248
template.templ
@ -1,248 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"git.dev.alexdunmow.com/block/core/templates/bn"
|
|
||||||
)
|
|
||||||
|
|
||||||
// PageData carries data parsed off the doc map for the four page templates.
|
|
||||||
// This is the same shape gotham uses; the renderers below differ only in
|
|
||||||
// slot composition and column widths.
|
|
||||||
type PageData struct {
|
|
||||||
Title string
|
|
||||||
Slots map[string]string
|
|
||||||
ThemeMode string
|
|
||||||
ThemeCSS string
|
|
||||||
SiteSettings bn.SiteSettingsData
|
|
||||||
PageMeta bn.PageMeta
|
|
||||||
StructuredData string
|
|
||||||
CSSHash string
|
|
||||||
PageviewNonce string
|
|
||||||
EngagementConfig bn.EngagementConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseEditorialPageData(doc map[string]any) PageData {
|
|
||||||
title := "Untitled"
|
|
||||||
if t, ok := doc["title"].(string); ok {
|
|
||||||
title = t
|
|
||||||
}
|
|
||||||
|
|
||||||
slots := make(map[string]string)
|
|
||||||
if s, ok := doc["slots"].(map[string]string); ok {
|
|
||||||
slots = s
|
|
||||||
}
|
|
||||||
|
|
||||||
themeCSS := ""
|
|
||||||
if tc, ok := doc["theme_css"].(string); ok {
|
|
||||||
themeCSS = tc
|
|
||||||
}
|
|
||||||
|
|
||||||
structuredData := ""
|
|
||||||
if sd, ok := doc["structured_data"].(string); ok {
|
|
||||||
structuredData = sd
|
|
||||||
}
|
|
||||||
|
|
||||||
cssHash := ""
|
|
||||||
if ch, ok := doc["css_hash"].(string); ok {
|
|
||||||
cssHash = ch
|
|
||||||
}
|
|
||||||
|
|
||||||
pageviewNonce := ""
|
|
||||||
if pn, ok := doc["pageview_nonce"].(string); ok {
|
|
||||||
pageviewNonce = pn
|
|
||||||
}
|
|
||||||
|
|
||||||
themeMode := "light"
|
|
||||||
if tm, ok := doc["theme_mode"].(string); ok && tm != "" {
|
|
||||||
themeMode = tm
|
|
||||||
}
|
|
||||||
|
|
||||||
siteSettings := bn.ParseSiteSettings(doc)
|
|
||||||
pageMeta := bn.ParsePageMeta(doc)
|
|
||||||
engagementConfig := bn.ParseEngagementConfig(doc)
|
|
||||||
|
|
||||||
return PageData{
|
|
||||||
Title: title,
|
|
||||||
Slots: slots,
|
|
||||||
ThemeMode: themeMode,
|
|
||||||
ThemeCSS: themeCSS,
|
|
||||||
SiteSettings: siteSettings,
|
|
||||||
PageMeta: pageMeta,
|
|
||||||
StructuredData: structuredData,
|
|
||||||
CSSHash: cssHash,
|
|
||||||
PageviewNonce: pageviewNonce,
|
|
||||||
EngagementConfig: engagementConfig,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default editorial page — masthead header, narrow main column, footer.
|
|
||||||
templ Editorial(data PageData) {
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
@bn.Head(bn.HeadData{
|
|
||||||
Title: data.Title,
|
|
||||||
Settings: data.SiteSettings,
|
|
||||||
PageMeta: data.PageMeta,
|
|
||||||
ThemeMode: data.ThemeMode,
|
|
||||||
ThemeCSS: data.ThemeCSS,
|
|
||||||
PluginStyles: []string{},
|
|
||||||
StructuredData: data.StructuredData,
|
|
||||||
CSSHash: data.CSSHash,
|
|
||||||
PageviewNonce: data.PageviewNonce,
|
|
||||||
EngagementConfig: data.EngagementConfig,
|
|
||||||
})
|
|
||||||
<body class="antialiased min-h-screen flex flex-col" style="background-color: hsl(var(--background)); color: hsl(var(--foreground));">
|
|
||||||
@bn.AdminBypassBanner(data.SiteSettings)
|
|
||||||
<header class="w-full">
|
|
||||||
@templ.Raw(data.Slots["header"])
|
|
||||||
</header>
|
|
||||||
<main class="flex-grow editorial-column w-full py-8 prose-editorial">
|
|
||||||
if main, ok := data.Slots["main"]; ok && main != "" {
|
|
||||||
@templ.Raw(main)
|
|
||||||
} else {
|
|
||||||
<div class="py-20 text-center">
|
|
||||||
<p style="color: hsl(var(--muted-foreground));">No content blocks assigned to this page.</p>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</main>
|
|
||||||
<footer class="w-full mt-auto pt-8 pb-6">
|
|
||||||
@templ.Raw(data.Slots["footer"])
|
|
||||||
</footer>
|
|
||||||
@bn.BodyEnd(data.SiteSettings)
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
}
|
|
||||||
|
|
||||||
// Section-front (landing) — stacked masthead, lead story, river, footer.
|
|
||||||
templ EditorialLanding(data PageData) {
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
@bn.Head(bn.HeadData{
|
|
||||||
Title: data.Title,
|
|
||||||
Settings: data.SiteSettings,
|
|
||||||
PageMeta: data.PageMeta,
|
|
||||||
ThemeMode: data.ThemeMode,
|
|
||||||
ThemeCSS: data.ThemeCSS,
|
|
||||||
PluginStyles: []string{},
|
|
||||||
StructuredData: data.StructuredData,
|
|
||||||
CSSHash: data.CSSHash,
|
|
||||||
PageviewNonce: data.PageviewNonce,
|
|
||||||
EngagementConfig: data.EngagementConfig,
|
|
||||||
})
|
|
||||||
<body class="antialiased min-h-screen flex flex-col" style="background-color: hsl(var(--background)); color: hsl(var(--foreground));">
|
|
||||||
@bn.AdminBypassBanner(data.SiteSettings)
|
|
||||||
<header class="w-full">
|
|
||||||
@templ.Raw(data.Slots["masthead"])
|
|
||||||
</header>
|
|
||||||
<section class="w-full editorial-wide py-6">
|
|
||||||
@templ.Raw(data.Slots["lead"])
|
|
||||||
</section>
|
|
||||||
<section class="w-full editorial-wide py-6">
|
|
||||||
@templ.Raw(data.Slots["river"])
|
|
||||||
</section>
|
|
||||||
<footer class="w-full mt-auto pt-8 pb-6">
|
|
||||||
@templ.Raw(data.Slots["footer"])
|
|
||||||
</footer>
|
|
||||||
@bn.BodyEnd(data.SiteSettings)
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
}
|
|
||||||
|
|
||||||
// Article — masthead, byline, narrow main column with drop cap, marginalia
|
|
||||||
// rail, footer. The grid collapses to a single column below 1024px.
|
|
||||||
templ EditorialArticle(data PageData) {
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
@bn.Head(bn.HeadData{
|
|
||||||
Title: data.Title,
|
|
||||||
Settings: data.SiteSettings,
|
|
||||||
PageMeta: data.PageMeta,
|
|
||||||
ThemeMode: data.ThemeMode,
|
|
||||||
ThemeCSS: data.ThemeCSS,
|
|
||||||
PluginStyles: []string{},
|
|
||||||
StructuredData: data.StructuredData,
|
|
||||||
CSSHash: data.CSSHash,
|
|
||||||
PageviewNonce: data.PageviewNonce,
|
|
||||||
EngagementConfig: data.EngagementConfig,
|
|
||||||
})
|
|
||||||
<body class="antialiased min-h-screen flex flex-col" style="background-color: hsl(var(--background)); color: hsl(var(--foreground));">
|
|
||||||
@bn.AdminBypassBanner(data.SiteSettings)
|
|
||||||
<header class="w-full">
|
|
||||||
@templ.Raw(data.Slots["header"])
|
|
||||||
</header>
|
|
||||||
<div class="editorial-column w-full py-4">
|
|
||||||
@templ.Raw(data.Slots["byline"])
|
|
||||||
</div>
|
|
||||||
<div class="editorial-article-grid flex-grow py-8">
|
|
||||||
<main class="prose-editorial">
|
|
||||||
if main, ok := data.Slots["main"]; ok && main != "" {
|
|
||||||
@templ.Raw(main)
|
|
||||||
} else {
|
|
||||||
<p style="color: hsl(var(--muted-foreground));">No content blocks assigned to this page.</p>
|
|
||||||
}
|
|
||||||
</main>
|
|
||||||
<aside>
|
|
||||||
@templ.Raw(data.Slots["marginalia"])
|
|
||||||
</aside>
|
|
||||||
</div>
|
|
||||||
<footer class="w-full mt-auto pt-8 pb-6">
|
|
||||||
@templ.Raw(data.Slots["footer"])
|
|
||||||
</footer>
|
|
||||||
@bn.BodyEnd(data.SiteSettings)
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
}
|
|
||||||
|
|
||||||
// Full-width — edge-to-edge content for photo essays and data graphics.
|
|
||||||
templ EditorialFullWidth(data PageData) {
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
@bn.Head(bn.HeadData{
|
|
||||||
Title: data.Title,
|
|
||||||
Settings: data.SiteSettings,
|
|
||||||
PageMeta: data.PageMeta,
|
|
||||||
ThemeMode: data.ThemeMode,
|
|
||||||
ThemeCSS: data.ThemeCSS,
|
|
||||||
PluginStyles: []string{},
|
|
||||||
StructuredData: data.StructuredData,
|
|
||||||
CSSHash: data.CSSHash,
|
|
||||||
PageviewNonce: data.PageviewNonce,
|
|
||||||
EngagementConfig: data.EngagementConfig,
|
|
||||||
})
|
|
||||||
<body class="antialiased min-h-screen flex flex-col" style="background-color: hsl(var(--background)); color: hsl(var(--foreground));">
|
|
||||||
@bn.AdminBypassBanner(data.SiteSettings)
|
|
||||||
<header class="w-full">
|
|
||||||
@templ.Raw(data.Slots["header"])
|
|
||||||
</header>
|
|
||||||
<main class="flex-grow w-full">
|
|
||||||
if main, ok := data.Slots["main"]; ok && main != "" {
|
|
||||||
@templ.Raw(main)
|
|
||||||
} else {
|
|
||||||
<div class="editorial-wide py-20 text-center">
|
|
||||||
<p style="color: hsl(var(--muted-foreground));">No content blocks assigned to this page.</p>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</main>
|
|
||||||
<footer class="w-full mt-auto pt-8 pb-6">
|
|
||||||
@templ.Raw(data.Slots["footer"])
|
|
||||||
</footer>
|
|
||||||
@bn.BodyEnd(data.SiteSettings)
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
}
|
|
||||||
|
|
||||||
func RenderEditorial(ctx context.Context, doc map[string]any) templ.Component {
|
|
||||||
return Editorial(parseEditorialPageData(doc))
|
|
||||||
}
|
|
||||||
|
|
||||||
func RenderEditorialLanding(ctx context.Context, doc map[string]any) templ.Component {
|
|
||||||
return EditorialLanding(parseEditorialPageData(doc))
|
|
||||||
}
|
|
||||||
|
|
||||||
func RenderEditorialArticle(ctx context.Context, doc map[string]any) templ.Component {
|
|
||||||
return EditorialArticle(parseEditorialPageData(doc))
|
|
||||||
}
|
|
||||||
|
|
||||||
func RenderEditorialFullWidth(ctx context.Context, doc map[string]any) templ.Component {
|
|
||||||
return EditorialFullWidth(parseEditorialPageData(doc))
|
|
||||||
}
|
|
||||||
@ -1,504 +0,0 @@
|
|||||||
// Code generated by templ - DO NOT EDIT.
|
|
||||||
|
|
||||||
// templ: version: v0.3.1020
|
|
||||||
package main
|
|
||||||
|
|
||||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
|
||||||
|
|
||||||
import "github.com/a-h/templ"
|
|
||||||
import templruntime "github.com/a-h/templ/runtime"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"git.dev.alexdunmow.com/block/core/templates/bn"
|
|
||||||
)
|
|
||||||
|
|
||||||
// PageData carries data parsed off the doc map for the four page templates.
|
|
||||||
// This is the same shape gotham uses; the renderers below differ only in
|
|
||||||
// slot composition and column widths.
|
|
||||||
type PageData struct {
|
|
||||||
Title string
|
|
||||||
Slots map[string]string
|
|
||||||
ThemeMode string
|
|
||||||
ThemeCSS string
|
|
||||||
SiteSettings bn.SiteSettingsData
|
|
||||||
PageMeta bn.PageMeta
|
|
||||||
StructuredData string
|
|
||||||
CSSHash string
|
|
||||||
PageviewNonce string
|
|
||||||
EngagementConfig bn.EngagementConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseEditorialPageData(doc map[string]any) PageData {
|
|
||||||
title := "Untitled"
|
|
||||||
if t, ok := doc["title"].(string); ok {
|
|
||||||
title = t
|
|
||||||
}
|
|
||||||
|
|
||||||
slots := make(map[string]string)
|
|
||||||
if s, ok := doc["slots"].(map[string]string); ok {
|
|
||||||
slots = s
|
|
||||||
}
|
|
||||||
|
|
||||||
themeCSS := ""
|
|
||||||
if tc, ok := doc["theme_css"].(string); ok {
|
|
||||||
themeCSS = tc
|
|
||||||
}
|
|
||||||
|
|
||||||
structuredData := ""
|
|
||||||
if sd, ok := doc["structured_data"].(string); ok {
|
|
||||||
structuredData = sd
|
|
||||||
}
|
|
||||||
|
|
||||||
cssHash := ""
|
|
||||||
if ch, ok := doc["css_hash"].(string); ok {
|
|
||||||
cssHash = ch
|
|
||||||
}
|
|
||||||
|
|
||||||
pageviewNonce := ""
|
|
||||||
if pn, ok := doc["pageview_nonce"].(string); ok {
|
|
||||||
pageviewNonce = pn
|
|
||||||
}
|
|
||||||
|
|
||||||
themeMode := "light"
|
|
||||||
if tm, ok := doc["theme_mode"].(string); ok && tm != "" {
|
|
||||||
themeMode = tm
|
|
||||||
}
|
|
||||||
|
|
||||||
siteSettings := bn.ParseSiteSettings(doc)
|
|
||||||
pageMeta := bn.ParsePageMeta(doc)
|
|
||||||
engagementConfig := bn.ParseEngagementConfig(doc)
|
|
||||||
|
|
||||||
return PageData{
|
|
||||||
Title: title,
|
|
||||||
Slots: slots,
|
|
||||||
ThemeMode: themeMode,
|
|
||||||
ThemeCSS: themeCSS,
|
|
||||||
SiteSettings: siteSettings,
|
|
||||||
PageMeta: pageMeta,
|
|
||||||
StructuredData: structuredData,
|
|
||||||
CSSHash: cssHash,
|
|
||||||
PageviewNonce: pageviewNonce,
|
|
||||||
EngagementConfig: engagementConfig,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default editorial page — masthead header, narrow main column, footer.
|
|
||||||
func Editorial(data PageData) templ.Component {
|
|
||||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
|
||||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
|
||||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
|
||||||
return templ_7745c5c3_CtxErr
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
|
||||||
if !templ_7745c5c3_IsBuffer {
|
|
||||||
defer func() {
|
|
||||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err == nil {
|
|
||||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
ctx = templ.InitializeContext(ctx)
|
|
||||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
|
||||||
if templ_7745c5c3_Var1 == nil {
|
|
||||||
templ_7745c5c3_Var1 = templ.NopComponent
|
|
||||||
}
|
|
||||||
ctx = templ.ClearChildren(ctx)
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!doctype html><html lang=\"en\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = bn.Head(bn.HeadData{
|
|
||||||
Title: data.Title,
|
|
||||||
Settings: data.SiteSettings,
|
|
||||||
PageMeta: data.PageMeta,
|
|
||||||
ThemeMode: data.ThemeMode,
|
|
||||||
ThemeCSS: data.ThemeCSS,
|
|
||||||
PluginStyles: []string{},
|
|
||||||
StructuredData: data.StructuredData,
|
|
||||||
CSSHash: data.CSSHash,
|
|
||||||
PageviewNonce: data.PageviewNonce,
|
|
||||||
EngagementConfig: data.EngagementConfig,
|
|
||||||
}).Render(ctx, templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<body class=\"antialiased min-h-screen flex flex-col\" style=\"background-color: hsl(var(--background)); color: hsl(var(--foreground));\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = bn.AdminBypassBanner(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<header class=\"w-full\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templ.Raw(data.Slots["header"]).Render(ctx, templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</header><main class=\"flex-grow editorial-column w-full py-8 prose-editorial\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
if main, ok := data.Slots["main"]; ok && main != "" {
|
|
||||||
templ_7745c5c3_Err = templ.Raw(main).Render(ctx, templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<div class=\"py-20 text-center\"><p style=\"color: hsl(var(--muted-foreground));\">No content blocks assigned to this page.</p></div>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</main><footer class=\"w-full mt-auto pt-8 pb-6\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templ.Raw(data.Slots["footer"]).Render(ctx, templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</footer>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = bn.BodyEnd(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</body></html>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Section-front (landing) — stacked masthead, lead story, river, footer.
|
|
||||||
func EditorialLanding(data PageData) templ.Component {
|
|
||||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
|
||||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
|
||||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
|
||||||
return templ_7745c5c3_CtxErr
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
|
||||||
if !templ_7745c5c3_IsBuffer {
|
|
||||||
defer func() {
|
|
||||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err == nil {
|
|
||||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
ctx = templ.InitializeContext(ctx)
|
|
||||||
templ_7745c5c3_Var2 := templ.GetChildren(ctx)
|
|
||||||
if templ_7745c5c3_Var2 == nil {
|
|
||||||
templ_7745c5c3_Var2 = templ.NopComponent
|
|
||||||
}
|
|
||||||
ctx = templ.ClearChildren(ctx)
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "<!doctype html><html lang=\"en\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = bn.Head(bn.HeadData{
|
|
||||||
Title: data.Title,
|
|
||||||
Settings: data.SiteSettings,
|
|
||||||
PageMeta: data.PageMeta,
|
|
||||||
ThemeMode: data.ThemeMode,
|
|
||||||
ThemeCSS: data.ThemeCSS,
|
|
||||||
PluginStyles: []string{},
|
|
||||||
StructuredData: data.StructuredData,
|
|
||||||
CSSHash: data.CSSHash,
|
|
||||||
PageviewNonce: data.PageviewNonce,
|
|
||||||
EngagementConfig: data.EngagementConfig,
|
|
||||||
}).Render(ctx, templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<body class=\"antialiased min-h-screen flex flex-col\" style=\"background-color: hsl(var(--background)); color: hsl(var(--foreground));\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = bn.AdminBypassBanner(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<header class=\"w-full\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templ.Raw(data.Slots["masthead"]).Render(ctx, templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</header><section class=\"w-full editorial-wide py-6\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templ.Raw(data.Slots["lead"]).Render(ctx, templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</section><section class=\"w-full editorial-wide py-6\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templ.Raw(data.Slots["river"]).Render(ctx, templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</section><footer class=\"w-full mt-auto pt-8 pb-6\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templ.Raw(data.Slots["footer"]).Render(ctx, templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</footer>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = bn.BodyEnd(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</body></html>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Article — masthead, byline, narrow main column with drop cap, marginalia
|
|
||||||
// rail, footer. The grid collapses to a single column below 1024px.
|
|
||||||
func EditorialArticle(data PageData) templ.Component {
|
|
||||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
|
||||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
|
||||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
|
||||||
return templ_7745c5c3_CtxErr
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
|
||||||
if !templ_7745c5c3_IsBuffer {
|
|
||||||
defer func() {
|
|
||||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err == nil {
|
|
||||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
ctx = templ.InitializeContext(ctx)
|
|
||||||
templ_7745c5c3_Var3 := templ.GetChildren(ctx)
|
|
||||||
if templ_7745c5c3_Var3 == nil {
|
|
||||||
templ_7745c5c3_Var3 = templ.NopComponent
|
|
||||||
}
|
|
||||||
ctx = templ.ClearChildren(ctx)
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "<!doctype html><html lang=\"en\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = bn.Head(bn.HeadData{
|
|
||||||
Title: data.Title,
|
|
||||||
Settings: data.SiteSettings,
|
|
||||||
PageMeta: data.PageMeta,
|
|
||||||
ThemeMode: data.ThemeMode,
|
|
||||||
ThemeCSS: data.ThemeCSS,
|
|
||||||
PluginStyles: []string{},
|
|
||||||
StructuredData: data.StructuredData,
|
|
||||||
CSSHash: data.CSSHash,
|
|
||||||
PageviewNonce: data.PageviewNonce,
|
|
||||||
EngagementConfig: data.EngagementConfig,
|
|
||||||
}).Render(ctx, templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "<body class=\"antialiased min-h-screen flex flex-col\" style=\"background-color: hsl(var(--background)); color: hsl(var(--foreground));\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = bn.AdminBypassBanner(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<header class=\"w-full\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templ.Raw(data.Slots["header"]).Render(ctx, templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "</header><div class=\"editorial-column w-full py-4\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templ.Raw(data.Slots["byline"]).Render(ctx, templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "</div><div class=\"editorial-article-grid flex-grow py-8\"><main class=\"prose-editorial\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
if main, ok := data.Slots["main"]; ok && main != "" {
|
|
||||||
templ_7745c5c3_Err = templ.Raw(main).Render(ctx, templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "<p style=\"color: hsl(var(--muted-foreground));\">No content blocks assigned to this page.</p>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "</main><aside>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templ.Raw(data.Slots["marginalia"]).Render(ctx, templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "</aside></div><footer class=\"w-full mt-auto pt-8 pb-6\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templ.Raw(data.Slots["footer"]).Render(ctx, templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "</footer>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = bn.BodyEnd(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "</body></html>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Full-width — edge-to-edge content for photo essays and data graphics.
|
|
||||||
func EditorialFullWidth(data PageData) templ.Component {
|
|
||||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
|
||||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
|
||||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
|
||||||
return templ_7745c5c3_CtxErr
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
|
||||||
if !templ_7745c5c3_IsBuffer {
|
|
||||||
defer func() {
|
|
||||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err == nil {
|
|
||||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
ctx = templ.InitializeContext(ctx)
|
|
||||||
templ_7745c5c3_Var4 := templ.GetChildren(ctx)
|
|
||||||
if templ_7745c5c3_Var4 == nil {
|
|
||||||
templ_7745c5c3_Var4 = templ.NopComponent
|
|
||||||
}
|
|
||||||
ctx = templ.ClearChildren(ctx)
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "<!doctype html><html lang=\"en\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = bn.Head(bn.HeadData{
|
|
||||||
Title: data.Title,
|
|
||||||
Settings: data.SiteSettings,
|
|
||||||
PageMeta: data.PageMeta,
|
|
||||||
ThemeMode: data.ThemeMode,
|
|
||||||
ThemeCSS: data.ThemeCSS,
|
|
||||||
PluginStyles: []string{},
|
|
||||||
StructuredData: data.StructuredData,
|
|
||||||
CSSHash: data.CSSHash,
|
|
||||||
PageviewNonce: data.PageviewNonce,
|
|
||||||
EngagementConfig: data.EngagementConfig,
|
|
||||||
}).Render(ctx, templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "<body class=\"antialiased min-h-screen flex flex-col\" style=\"background-color: hsl(var(--background)); color: hsl(var(--foreground));\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = bn.AdminBypassBanner(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "<header class=\"w-full\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templ.Raw(data.Slots["header"]).Render(ctx, templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "</header><main class=\"flex-grow w-full\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
if main, ok := data.Slots["main"]; ok && main != "" {
|
|
||||||
templ_7745c5c3_Err = templ.Raw(main).Render(ctx, templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "<div class=\"editorial-wide py-20 text-center\"><p style=\"color: hsl(var(--muted-foreground));\">No content blocks assigned to this page.</p></div>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "</main><footer class=\"w-full mt-auto pt-8 pb-6\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templ.Raw(data.Slots["footer"]).Render(ctx, templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "</footer>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = bn.BodyEnd(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "</body></html>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func RenderEditorial(ctx context.Context, doc map[string]any) templ.Component {
|
|
||||||
return Editorial(parseEditorialPageData(doc))
|
|
||||||
}
|
|
||||||
|
|
||||||
func RenderEditorialLanding(ctx context.Context, doc map[string]any) templ.Component {
|
|
||||||
return EditorialLanding(parseEditorialPageData(doc))
|
|
||||||
}
|
|
||||||
|
|
||||||
func RenderEditorialArticle(ctx context.Context, doc map[string]any) templ.Component {
|
|
||||||
return EditorialArticle(parseEditorialPageData(doc))
|
|
||||||
}
|
|
||||||
|
|
||||||
func RenderEditorialFullWidth(ctx context.Context, doc map[string]any) templ.Component {
|
|
||||||
return EditorialFullWidth(parseEditorialPageData(doc))
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = templruntime.GeneratedTemplate
|
|
||||||
15
templates/editorial/article.ninjatpl
Normal file
15
templates/editorial/article.ninjatpl
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
{{ head_html|safe }}
|
||||||
|
<body class="antialiased min-h-screen flex flex-col" style="background-color: hsl(var(--background)); color: hsl(var(--foreground));">
|
||||||
|
{{ admin_banner_html|safe }}
|
||||||
|
<header class="w-full">{{ slots.header|safe }}</header>
|
||||||
|
<div class="editorial-column w-full py-4">{{ slots.byline|safe }}</div>
|
||||||
|
<div class="editorial-article-grid flex-grow py-8">
|
||||||
|
<main class="prose-editorial">{% if slots.main %}{{ slots.main|safe }}{% else %}<p style="color: hsl(var(--muted-foreground));">No content blocks assigned to this page.</p>{% endif %}</main>
|
||||||
|
<aside>{{ slots.marginalia|safe }}</aside>
|
||||||
|
</div>
|
||||||
|
<footer class="w-full mt-auto pt-8 pb-6">{{ slots.footer|safe }}</footer>
|
||||||
|
{{ body_end_html|safe }}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
11
templates/editorial/default.ninjatpl
Normal file
11
templates/editorial/default.ninjatpl
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
{{ head_html|safe }}
|
||||||
|
<body class="antialiased min-h-screen flex flex-col" style="background-color: hsl(var(--background)); color: hsl(var(--foreground));">
|
||||||
|
{{ admin_banner_html|safe }}
|
||||||
|
<header class="w-full">{{ slots.header|safe }}</header>
|
||||||
|
<main class="flex-grow editorial-column w-full py-8 prose-editorial">{% if slots.main %}{{ slots.main|safe }}{% else %}<div class="py-20 text-center"><p style="color: hsl(var(--muted-foreground));">No content blocks assigned to this page.</p></div>{% endif %}</main>
|
||||||
|
<footer class="w-full mt-auto pt-8 pb-6">{{ slots.footer|safe }}</footer>
|
||||||
|
{{ body_end_html|safe }}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
11
templates/editorial/full-width.ninjatpl
Normal file
11
templates/editorial/full-width.ninjatpl
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
{{ head_html|safe }}
|
||||||
|
<body class="antialiased min-h-screen flex flex-col" style="background-color: hsl(var(--background)); color: hsl(var(--foreground));">
|
||||||
|
{{ admin_banner_html|safe }}
|
||||||
|
<header class="w-full">{{ slots.header|safe }}</header>
|
||||||
|
<main class="flex-grow w-full">{% if slots.main %}{{ slots.main|safe }}{% else %}<div class="editorial-wide py-20 text-center"><p style="color: hsl(var(--muted-foreground));">No content blocks assigned to this page.</p></div>{% endif %}</main>
|
||||||
|
<footer class="w-full mt-auto pt-8 pb-6">{{ slots.footer|safe }}</footer>
|
||||||
|
{{ body_end_html|safe }}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
12
templates/editorial/landing.ninjatpl
Normal file
12
templates/editorial/landing.ninjatpl
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
{{ head_html|safe }}
|
||||||
|
<body class="antialiased min-h-screen flex flex-col" style="background-color: hsl(var(--background)); color: hsl(var(--foreground));">
|
||||||
|
{{ admin_banner_html|safe }}
|
||||||
|
<header class="w-full">{{ slots.masthead|safe }}</header>
|
||||||
|
<section class="w-full editorial-wide py-6">{{ slots.lead|safe }}</section>
|
||||||
|
<section class="w-full editorial-wide py-6">{{ slots.river|safe }}</section>
|
||||||
|
<footer class="w-full mt-auto pt-8 pb-6">{{ slots.footer|safe }}</footer>
|
||||||
|
{{ body_end_html|safe }}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
14
templates/email/editorial.ninjatpl
Normal file
14
templates/email/editorial.ninjatpl
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<!doctype html><html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta name="x-apple-disable-message-reformatting"><meta http-equiv="X-UA-Compatible" content="IE=edge"><title>{{ site_name }}</title><style type="text/css">
|
||||||
|
body, table, td, p, a, li, blockquote {
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
-ms-text-size-adjust: 100%;
|
||||||
|
}
|
||||||
|
table, td { mso-table-lspace: 0pt; mso-table-rspace: 0pt; }
|
||||||
|
img { -ms-interpolation-mode: bicubic; border: 0; height: auto; outline: none; text-decoration: none; }
|
||||||
|
body { margin: 0 !important; padding: 0 !important; width: 100% !important; }
|
||||||
|
a[x-apple-data-detectors] { color: inherit !important; text-decoration: none !important; }
|
||||||
|
@media only screen and (max-width: 620px) {
|
||||||
|
.email-container { width: 100% !important; max-width: 100% !important; }
|
||||||
|
.content-padding { padding-left: 24px !important; padding-right: 24px !important; }
|
||||||
|
}
|
||||||
|
</style></head><body style="background-color: {{ colors.background|default:"#f7f1e4" }}; margin: 0; padding: 0; font-family: var(--font-body, &#39;Source Serif 4&#39;, Georgia, serif);">{% if preview_text %}<div style="display: none; max-height: 0; overflow: hidden; mso-hide: all;">{{ preview_text }}</div>{% endif %}<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0"><tr><td align="center" style="padding: 48px 10px; background-color: {{ colors.background|default:"#f7f1e4" }};"><table role="presentation" class="email-container" width="580" cellspacing="0" cellpadding="0" border="0" style="max-width: 580px; background-color: {{ colors.card|default:"#fbf8ec" }};"><!-- Masthead --><tr><td align="center" style="padding: 32px 40px 16px 40px;">{% if logo_url %}<img src="{{ logo_url }}" alt="{{ site_name }}" style="max-height: 48px; width: auto; display: block;">{% elif site_name %}<h1 style="margin: 0; font-family: var(--font-heading, &#39;Playfair Display&#39;, Georgia, serif); font-style: italic; font-weight: 900; font-size: 36px; color: {{ colors.foreground|default:"#1d1714" }}; letter-spacing: -0.02em;">{{ site_name }}</h1>{% endif %}</td></tr><!-- Hairline rule --><tr><td style="padding: 0 40px;"><table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0"><tr><td style="border-top: 1px solid {{ colors.border|default:"#d6cdbf" }}; font-size: 0; line-height: 0; height: 1px;"> </td></tr></table></td></tr><!-- Body --><tr><td class="content-padding" style="padding: 32px 48px 40px 48px; color: {{ colors.foreground|default:"#1d1714" }}; font-size: 16px; line-height: 1.7; font-family: var(--font-body, &#39;Source Serif 4&#39;, Georgia, serif);">{{ body|safe }}</td></tr><!-- Footer --><tr><td style="padding: 24px 48px 40px 48px; border-top: 1px solid {{ colors.border|default:"#d6cdbf" }};"><table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0"><tr><td align="center" style="font-family: var(--font-body, &#39;Inter&#39;, sans-serif); font-size: 12px; color: {{ colors.mutedForeground|default:"#6e6357" }}; text-transform: uppercase; letter-spacing: 0.1em;"><p style="margin: 0 0 8px;">{{ site_name }}</p>{% if unsubscribe_url %}<p style="margin: 0; font-size: 11px;"><a href="{{ unsubscribe_url }}" style="color: #702021; text-decoration: underline;">Unsubscribe</a></p>{% endif %}</td></tr></table></td></tr></table></td></tr></table></body></html>
|
||||||
1
templates/overrides/editorial/button.ninjatpl
Normal file
1
templates/overrides/editorial/button.ninjatpl
Normal file
@ -0,0 +1 @@
|
|||||||
|
{% if text %}{% if url %}<a href="{{ url }}" class="inline-block" aria-label="{{ ariaLabel }}"><button type="button" class="editorial-button">{{ text }}</button></a>{% else %}<button type="button" class="editorial-button" aria-label="{{ ariaLabel }}">{{ text }}</button>{% endif %}{% endif %}
|
||||||
1
templates/overrides/editorial/heading.ninjatpl
Normal file
1
templates/overrides/editorial/heading.ninjatpl
Normal file
@ -0,0 +1 @@
|
|||||||
|
{% if level|integer == 1 %}<div class="editorial-column">{% if kicker %}<span class="editorial-heading-kicker">{{ kicker }}</span>{% endif %}<h1 class="editorial-heading text-5xl md:text-7xl mt-2 {{ textClass }}">{{ text }}</h1></div>{% elif level|integer == 3 %}<h3 class="editorial-heading text-2xl md:text-3xl editorial-column {{ textClass }}">{{ text }}</h3>{% elif level|integer == 4 %}<h4 class="editorial-heading text-xl md:text-2xl editorial-column {{ textClass }}">{{ text }}</h4>{% elif level|integer == 5 %}<h5 class="editorial-heading text-lg editorial-column {{ textClass }}">{{ text }}</h5>{% elif level|integer == 6 %}<h6 class="editorial-heading text-base editorial-column {{ textClass }}">{{ text }}</h6>{% else %}<h2 class="editorial-heading text-3xl md:text-4xl editorial-column {{ textClass }}">{{ text }}</h2>{% endif %}
|
||||||
1
templates/overrides/editorial/image.ninjatpl
Normal file
1
templates/overrides/editorial/image.ninjatpl
Normal file
@ -0,0 +1 @@
|
|||||||
|
{% if src %}<figure class="editorial-figure editorial-column"><img src="{{ src }}" alt="{{ alt }}">{% if caption %}<figcaption>{{ caption }}</figcaption>{% endif %}</figure>{% endif %}
|
||||||
1
templates/overrides/editorial/text.ninjatpl
Normal file
1
templates/overrides/editorial/text.ninjatpl
Normal file
@ -0,0 +1 @@
|
|||||||
|
<div class="editorial-text editorial-column prose-editorial {{ class }}">{{ text|safe }}</div>
|
||||||
@ -1,20 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
)
|
|
||||||
|
|
||||||
// EditorialTextBlock overrides the built-in `text` block for the editorial
|
|
||||||
// template. Body copy is Source Serif 4 within a 64ch measure, with
|
|
||||||
// continuation-paragraph indents.
|
|
||||||
//
|
|
||||||
// Content shape: { "text", "class" } — same as the built-in text block.
|
|
||||||
func EditorialTextBlock(ctx context.Context, content map[string]any) string {
|
|
||||||
text := getString(content, "text")
|
|
||||||
class := getString(content, "class")
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
_ = editorialTextComponent(text, class).Render(ctx, &buf)
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
// editorialTextComponent renders rich-text body copy with the editorial prose
|
|
||||||
// rules applied. The author-supplied HTML is trusted (the CMS sanitiser runs
|
|
||||||
// upstream).
|
|
||||||
templ editorialTextComponent(text, class string) {
|
|
||||||
<div class={ "editorial-text editorial-column prose-editorial", class }>
|
|
||||||
@templ.Raw(text)
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
@ -1,69 +0,0 @@
|
|||||||
// Code generated by templ - DO NOT EDIT.
|
|
||||||
|
|
||||||
// templ: version: v0.3.1020
|
|
||||||
package main
|
|
||||||
|
|
||||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
|
||||||
|
|
||||||
import "github.com/a-h/templ"
|
|
||||||
import templruntime "github.com/a-h/templ/runtime"
|
|
||||||
|
|
||||||
// editorialTextComponent renders rich-text body copy with the editorial prose
|
|
||||||
// rules applied. The author-supplied HTML is trusted (the CMS sanitiser runs
|
|
||||||
// upstream).
|
|
||||||
func editorialTextComponent(text, class string) templ.Component {
|
|
||||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
|
||||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
|
||||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
|
||||||
return templ_7745c5c3_CtxErr
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
|
||||||
if !templ_7745c5c3_IsBuffer {
|
|
||||||
defer func() {
|
|
||||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err == nil {
|
|
||||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
ctx = templ.InitializeContext(ctx)
|
|
||||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
|
||||||
if templ_7745c5c3_Var1 == nil {
|
|
||||||
templ_7745c5c3_Var1 = templ.NopComponent
|
|
||||||
}
|
|
||||||
ctx = templ.ClearChildren(ctx)
|
|
||||||
var templ_7745c5c3_Var2 = []any{"editorial-text editorial-column prose-editorial", class}
|
|
||||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var2...)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var3 string
|
|
||||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var2).String())
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `text_override.templ`, Line: 1, Col: 0}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var3)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templ.Raw(text).Render(ctx, templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</div>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = templruntime.GeneratedTemplate
|
|
||||||
Loading…
x
Reference in New Issue
Block a user