feat(templates): full bn chrome sync from cms + VersionedAssetURL hook

cms is now the authoring source for the bn chrome (head/toolbar/engagement);
this repo carries a mechanically-synced copy solely for guest-side plugin
templates (cms `make sync-templates`, drift gated by check-safety check 31).
head.templ picks up everything it had drifted behind on (RFG-parity favicon
head, analytics/web-vitals beacons, custom-scripts placement, bnApplyTheme)
and is now SDK-clean: asset versioning goes through the new
bn.VersionedAssetURL hook (identity default; the CMS host wires it to its
internal assets registry). HeadData is shape-compatible with existing plugin
usage; BrandingData/SiteSettingsData internals changed — plugins see that at
compile time on their next core bump.

Spec: cms docs/superpowers/specs/2026-07-07-bn-chrome-single-source-design.md

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Alex Dunmow 2026-07-07 03:02:34 +08:00
parent 1ee9c280e2
commit 20c67b80ee
5 changed files with 1028 additions and 510 deletions

View File

@ -0,0 +1,11 @@
package bn
// VersionedAssetURL resolves an asset URL to a cache-versioned form. The
// default is the identity function; the CMS host wires it to
// internal/assets.VersionedURL at startup so plugin stylesheet links get
// content-hash ?v= params. Guest-side (wasm) renders keep the identity
// default — asset hashing is a host concern.
//
// This file is part of the cms→core template sync set (make sync-templates);
// it must stay SDK-clean (no cms imports).
var VersionedAssetURL = func(url string) string { return url }

View File

@ -62,7 +62,7 @@ func EngagementScript(config EngagementConfig) templ.Component {
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.ResolveAttributeValue(engagementConfigJSON(config))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/bn/engagement.templ`, Line: 30, Col: 63}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `engagement.templ`, Line: 30, Col: 63}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var2)
if templ_7745c5c3_Err != nil {

View File

@ -16,34 +16,69 @@ func resolveMediaURL(url string) string {
return url
}
// canonicalURL resolves the canonical link. An admin-set value wins (made absolute
// against the page's own origin when it is root-relative); otherwise the page's own
// absolute URL is used as a self-referencing canonical. Returns "" only when neither
// is available.
func canonicalURL(canonical, pageURL string) string {
if canonical == "" {
return pageURL
}
if strings.HasPrefix(canonical, "http://") || strings.HasPrefix(canonical, "https://") {
return canonical
}
if strings.HasPrefix(canonical, "/") && pageURL != "" {
if i := strings.Index(pageURL, "://"); i >= 0 {
if j := strings.IndexByte(pageURL[i+3:], '/'); j >= 0 {
return pageURL[:i+3+j] + canonical
}
return pageURL + canonical
}
}
return canonical
}
// BrandingData contains generated favicon/icon URLs
type BrandingData struct {
FaviconICO string // /brand/{id}/favicon.ico
Favicon16 string // /brand/{id}/favicon-16x16.png
Favicon32 string // /brand/{id}/favicon-32x32.png
AppleTouchIcon string // /brand/{id}/apple-touch-icon.png (180x180)
Android192 string // /brand/{id}/android-chrome-192x192.png
Android512 string // /brand/{id}/android-chrome-512x512.png
Maskable512 string // /brand/{id}/maskable-512x512.png
Master1024 string // /brand/{id}/icon-1024x1024.png
ManifestURL string // /brand/{id}/site.webmanifest
FaviconICO string // /.brand/{id}/favicon.ico
Favicon16 string // /.brand/{id}/favicon-16x16.png
Favicon32 string // /.brand/{id}/favicon-32x32.png
Favicon96 string // /.brand/{id}/favicon-96x96.png
AppleTouchIcon string // /.brand/{id}/apple-touch-icon.png (180x180)
Android192 string // /.brand/{id}/android-chrome-192x192.png
Android512 string // /.brand/{id}/android-chrome-512x512.png
Maskable512 string // /.brand/{id}/maskable-512x512.png
Master1024 string // /.brand/{id}/icon-1024x1024.png
ManifestURL string // /.brand/{id}/site.webmanifest
MaskIcon string // /.brand/{id}/mask-icon.svg
MSTile150 string // /.brand/{id}/mstile-150x150.png
BrowserConfig string // /.brand/{id}/browserconfig.xml
ThemeColor string
SVG string // Optional SVG pass-through
IsGenerated bool
}
// CustomScriptEntry represents a single custom script with placement control
type CustomScriptEntry struct {
Name string
Placement string // "head" or "body"
Code string
Enabled bool
}
// SiteSettingsData contains site-wide settings for head injection
type SiteSettingsData struct {
Title string
Description string
Favicon string
Logo string
LogoAlt string
AppleTouchIcon string
GoogleAnalyticsID string
CustomHeadScripts string
CustomBodyScripts string
MetaDescription string
Title string
Description string
Favicon string
Logo string
LogoAlt string
AppleTouchIcon string
GoogleAnalyticsID string
CustomScripts []CustomScriptEntry
GoogleAnalyticsEnabled bool
GoogleAnalyticsExplicitlySet bool // true if the enabled flag was explicitly set in JSON (not nil)
MetaDescription string
DefaultOGImage string
TwitterHandle string
OGSiteName string
@ -67,6 +102,10 @@ type PageMeta struct {
TwitterImage string // Twitter card image URL
CanonicalURL string // Canonical URL for this page
RobotsDirective string // Robots directive (e.g., "noindex, nofollow")
OGType string // Open Graph type ("article" for posts, else "website")
PageURL string // Absolute URL of this page (og:url + auto-canonical)
ArticlePublishedTime string // ISO 8601 published time (articles only)
ArticleAuthor string // Author name (articles only)
}
// HeadData contains all data needed to render the <head> element
@ -159,15 +198,75 @@ func ParseSiteSettings(doc map[string]any) SiteSettingsData {
}
// Analytics
var legacyHeadScripts, legacyBodyScripts string
if analyticsData, ok := siteData["analytics"].(map[string]any); ok {
if v, ok := analyticsData["google_analytics_id"].(string); ok {
settings.GoogleAnalyticsID = v
}
// Read GA enabled flag — if present, use it; if absent but ID is set, default to enabled (backward compat)
if v, ok := analyticsData["google_analytics_enabled"].(bool); ok {
settings.GoogleAnalyticsEnabled = v
settings.GoogleAnalyticsExplicitlySet = true
} else if settings.GoogleAnalyticsID != "" {
settings.GoogleAnalyticsEnabled = true
}
// Preserve legacy flat fields for fallback
if v, ok := analyticsData["custom_head_scripts"].(string); ok {
settings.CustomHeadScripts = v
legacyHeadScripts = v
}
if v, ok := analyticsData["custom_body_scripts"].(string); ok {
settings.CustomBodyScripts = v
legacyBodyScripts = v
}
}
// Structured custom scripts
if scriptsArr, ok := siteData["custom_scripts"].([]any); ok && len(scriptsArr) > 0 {
for _, item := range scriptsArr {
entry, ok := item.(map[string]any)
if !ok {
continue
}
cs := CustomScriptEntry{}
if v, ok := entry["name"].(string); ok {
cs.Name = v
}
// Placement: proto enum stored as float64 (1=head, 2=body) or string
switch p := entry["placement"].(type) {
case float64:
if p == 1 {
cs.Placement = "head"
} else if p == 2 {
cs.Placement = "body"
}
case string:
cs.Placement = p
}
if v, ok := entry["code"].(string); ok {
cs.Code = v
}
if v, ok := entry["enabled"].(bool); ok {
cs.Enabled = v
}
settings.CustomScripts = append(settings.CustomScripts, cs)
}
}
// Fallback: if no structured scripts, migrate old flat fields
if len(settings.CustomScripts) == 0 {
if legacyHeadScripts != "" {
settings.CustomScripts = append(settings.CustomScripts, CustomScriptEntry{
Name: "Legacy Head Scripts",
Placement: "head",
Code: legacyHeadScripts,
Enabled: true,
})
}
if legacyBodyScripts != "" {
settings.CustomScripts = append(settings.CustomScripts, CustomScriptEntry{
Name: "Legacy Body Scripts",
Placement: "body",
Code: legacyBodyScripts,
Enabled: true,
})
}
}
@ -209,6 +308,18 @@ func ParseSiteSettings(doc map[string]any) SiteSettingsData {
if v, ok := brandingData["svg"].(string); ok {
settings.Branding.SVG = v
}
if v, ok := brandingData["favicon_96"].(string); ok {
settings.Branding.Favicon96 = v
}
if v, ok := brandingData["mask_icon"].(string); ok {
settings.Branding.MaskIcon = v
}
if v, ok := brandingData["mstile_150"].(string); ok {
settings.Branding.MSTile150 = v
}
if v, ok := brandingData["browserconfig"].(string); ok {
settings.Branding.BrowserConfig = v
}
}
// Admin bypass mode (for showing banner when admin bypasses maintenance/coming_soon)
@ -276,6 +387,18 @@ func ParsePageMeta(doc map[string]any) PageMeta {
if v, ok := doc["robotsDirective"].(string); ok {
meta.RobotsDirective = v
}
if v, ok := doc["ogType"].(string); ok {
meta.OGType = v
}
if v, ok := doc["pageUrl"].(string); ok {
meta.PageURL = v
}
if v, ok := doc["articlePublishedTime"].(string); ok {
meta.ArticlePublishedTime = v
}
if v, ok := doc["articleAuthor"].(string); ok {
meta.ArticleAuthor = v
}
return meta
}
@ -310,6 +433,12 @@ func ParseToolbarData(data map[string]any) ToolbarData {
if v, ok := data["preview_mode"].(string); ok {
toolbar.PreviewMode = v
}
if v, ok := data["hide_preview_toggle"].(bool); ok {
toolbar.HidePreviewToggle = v
}
if v, ok := data["animate"].(bool); ok {
toolbar.Animate = v
}
if v, ok := data["position"].(string); ok {
toolbar.Position = v
}
@ -390,15 +519,30 @@ templ Head(data HeadData) {
@themeInitScript(data.ThemeMode)
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
if data.Settings.Branding.IsGenerated {
// Use generated brand assets
<link rel="icon" type="image/x-icon" href={ data.Settings.Branding.FaviconICO }/>
<link rel="icon" type="image/png" sizes="16x16" href={ data.Settings.Branding.Favicon16 }/>
// Use generated brand assets (modern RFG-parity set; SVG first)
if data.Settings.Branding.SVG != "" {
<link rel="icon" type="image/svg+xml" href={ data.Settings.Branding.SVG }/>
}
if data.Settings.Branding.Favicon96 != "" {
<link rel="icon" type="image/png" sizes="96x96" href={ data.Settings.Branding.Favicon96 }/>
}
<link rel="icon" type="image/png" sizes="32x32" href={ data.Settings.Branding.Favicon32 }/>
<link rel="icon" type="image/png" sizes="16x16" href={ data.Settings.Branding.Favicon16 }/>
<link rel="icon" type="image/x-icon" href={ data.Settings.Branding.FaviconICO }/>
<link rel="apple-touch-icon" sizes="180x180" href={ data.Settings.Branding.AppleTouchIcon }/>
if data.Settings.Branding.MaskIcon != "" {
<link rel="mask-icon" href={ data.Settings.Branding.MaskIcon } color={ data.Settings.Branding.ThemeColor }/>
}
<link rel="manifest" href={ data.Settings.Branding.ManifestURL }/>
if data.Settings.Branding.ThemeColor != "" {
<meta name="theme-color" content={ data.Settings.Branding.ThemeColor }/>
}
if data.Settings.Branding.BrowserConfig != "" {
<meta name="msapplication-config" content={ data.Settings.Branding.BrowserConfig }/>
}
if data.Title != "" {
<meta name="apple-mobile-web-app-title" content={ data.Title }/>
}
} else {
// Legacy fallback - manual favicon uploads
if data.Settings.Favicon != "" {
@ -418,9 +562,9 @@ templ Head(data HeadData) {
<meta name="description" content={ data.Settings.MetaDescription }/>
}
// Canonical URL (page-level only, no site default)
if data.PageMeta.CanonicalURL != "" {
<link rel="canonical" href={ data.PageMeta.CanonicalURL }/>
// Canonical URL: admin override (resolved to absolute) → auto self-canonical (M8)
if canonical := canonicalURL(data.PageMeta.CanonicalURL, data.PageMeta.PageURL); canonical != "" {
<link rel="canonical" href={ canonical }/>
}
// Robots directive (page-level only - defaults to index,follow if not set)
@ -459,8 +603,25 @@ templ Head(data HeadData) {
<meta property="og:image" content={ resolveMediaURL(data.Settings.DefaultOGImage) }/>
}
// og:type - default to website
<meta property="og:type" content="website"/>
// og:type: article for posts, website otherwise (M5)
if data.PageMeta.OGType != "" {
<meta property="og:type" content={ data.PageMeta.OGType }/>
} else {
<meta property="og:type" content="website"/>
}
// og:url: absolute URL of this page (M6)
if data.PageMeta.PageURL != "" {
<meta property="og:url" content={ data.PageMeta.PageURL }/>
}
// article:* metadata for posts (M5)
if data.PageMeta.OGType == "article" {
if data.PageMeta.ArticlePublishedTime != "" {
<meta property="article:published_time" content={ data.PageMeta.ArticlePublishedTime }/>
}
if data.PageMeta.ArticleAuthor != "" {
<meta property="article:author" content={ data.PageMeta.ArticleAuthor }/>
}
}
// === TWITTER CARD META TAGS ===
if data.Settings.TwitterHandle != "" {
@ -512,7 +673,7 @@ templ Head(data HeadData) {
<link rel="alternate" type="application/atom+xml" title={ data.Settings.RSSFeedTitle + " (Atom)" } href={ data.Settings.RSSFeedURL + "/atom" }/>
}
if data.Settings.GoogleAnalyticsID != "" {
if data.Settings.GoogleAnalyticsID != "" && (data.Settings.GoogleAnalyticsEnabled || !data.Settings.GoogleAnalyticsExplicitlySet) {
<script async src={ "https://www.googletagmanager.com/gtag/js?id=" + data.Settings.GoogleAnalyticsID }></script>
@googleAnalyticsScript(data.Settings.GoogleAnalyticsID)
}
@ -521,14 +682,16 @@ templ Head(data HeadData) {
// Blog post engagement tracking (only rendered for posts with engagement config)
@EngagementScript(data.EngagementConfig)
for _, style := range data.PluginStyles {
<link rel="stylesheet" href={ style }/>
<link rel="stylesheet" href={ VersionedAssetURL(style) }/>
}
// Theme CSS injected AFTER plugin styles to take precedence
if data.ThemeCSS != "" {
@themeStyle(data.ThemeCSS)
}
if data.Settings.CustomHeadScripts != "" {
@templ.Raw(data.Settings.CustomHeadScripts)
for _, script := range data.Settings.CustomScripts {
if script.Enabled && script.Placement == "head" {
@templ.Raw(script.Code)
}
}
if data.StructuredData != "" {
@structuredDataScript(data.StructuredData)
@ -570,8 +733,10 @@ templ AdminBypassBanner(settings SiteSettingsData) {
// BodyEnd renders custom scripts and admin toolbar before </body>
templ BodyEnd(settings SiteSettingsData) {
@recordValidationCall(ctx, "BodyEnd")
if settings.CustomBodyScripts != "" {
@templ.Raw(settings.CustomBodyScripts)
for _, script := range settings.CustomScripts {
if script.Enabled && script.Placement == "body" {
@templ.Raw(script.Code)
}
}
// Render admin editor toolbar if enabled (auto-injects for all templates)
@AdminEditorToolbar(settings.Toolbar)
@ -779,7 +944,6 @@ func themeInitScript(themeMode string) templ.Component {
// preference (bn-theme cookie/localStorage) → site default → system.
// Exposed as window.bnApplyTheme so the toolbar theme tester can re-apply
// after changing the override without duplicating this resolution.
// KEEP IN SYNC with cms backend/templates/bn/head.templ.
return templ.Raw(`<script>
window.bnApplyTheme=function(){
var t=null;try{t=sessionStorage.getItem('bn-theme-override')}catch(e){}

File diff suppressed because one or more lines are too long

View File

@ -242,7 +242,7 @@ func AdminEditorToolbar(data ToolbarData) templ.Component {
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: `templates/bn/toolbar.templ`, Line: 1, Col: 0}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `toolbar.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var3)
if templ_7745c5c3_Err != nil {
@ -255,7 +255,7 @@ func AdminEditorToolbar(data ToolbarData) templ.Component {
var templ_7745c5c3_Var4 templ.SafeURL
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(data.EditURL))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/bn/toolbar.templ`, Line: 301, Col: 38}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `toolbar.templ`, Line: 301, Col: 38}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
@ -268,7 +268,7 @@ func AdminEditorToolbar(data ToolbarData) templ.Component {
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.ResolveAttributeValue(data.StatusLabel())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/bn/toolbar.templ`, Line: 311, Col: 73}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `toolbar.templ`, Line: 311, Col: 73}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var5)
if templ_7745c5c3_Err != nil {
@ -290,7 +290,7 @@ func AdminEditorToolbar(data ToolbarData) templ.Component {
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: `templates/bn/toolbar.templ`, Line: 1, Col: 0}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `toolbar.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var7)
if templ_7745c5c3_Err != nil {
@ -303,7 +303,7 @@ func AdminEditorToolbar(data ToolbarData) templ.Component {
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(data.StatusLabel())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/bn/toolbar.templ`, Line: 313, Col: 80}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `toolbar.templ`, Line: 313, Col: 80}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil {
@ -334,7 +334,7 @@ func AdminEditorToolbar(data ToolbarData) templ.Component {
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: `templates/bn/toolbar.templ`, Line: 1, Col: 0}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `toolbar.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var10)
if templ_7745c5c3_Err != nil {
@ -362,7 +362,7 @@ func AdminEditorToolbar(data ToolbarData) templ.Component {
}
}())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/bn/toolbar.templ`, Line: 323, Col: 170}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `toolbar.templ`, Line: 323, Col: 170}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var12)
if templ_7745c5c3_Err != nil {
@ -400,7 +400,7 @@ func AdminEditorToolbar(data ToolbarData) templ.Component {
var templ_7745c5c3_Var13 string
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d min", data.ReadingTime))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/bn/toolbar.templ`, Line: 435, Col: 46}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `toolbar.templ`, Line: 435, Col: 46}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
if templ_7745c5c3_Err != nil {
@ -423,7 +423,7 @@ func AdminEditorToolbar(data ToolbarData) templ.Component {
var templ_7745c5c3_Var14 string
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.ResolveAttributeValue(fmt.Sprintf("/toolbar/publish/%s", data.PageID))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/bn/toolbar.templ`, Line: 443, Col: 62}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `toolbar.templ`, Line: 443, Col: 62}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var14)
if templ_7745c5c3_Err != nil {
@ -441,7 +441,7 @@ func AdminEditorToolbar(data ToolbarData) templ.Component {
var templ_7745c5c3_Var15 string
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.ResolveAttributeValue(fmt.Sprintf("/toolbar/page-info/%s", data.PageID))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/bn/toolbar.templ`, Line: 460, Col: 63}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `toolbar.templ`, Line: 460, Col: 63}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var15)
if templ_7745c5c3_Err != nil {
@ -454,7 +454,7 @@ func AdminEditorToolbar(data ToolbarData) templ.Component {
var templ_7745c5c3_Var16 string
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.ResolveAttributeValue(data.DropdownDirection())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/bn/toolbar.templ`, Line: 473, Col: 46}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `toolbar.templ`, Line: 473, Col: 46}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var16)
if templ_7745c5c3_Err != nil {
@ -467,7 +467,7 @@ func AdminEditorToolbar(data ToolbarData) templ.Component {
var templ_7745c5c3_Var17 string
templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.ResolveAttributeValue(data.DropdownAlign())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/bn/toolbar.templ`, Line: 474, Col: 38}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `toolbar.templ`, Line: 474, Col: 38}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var17)
if templ_7745c5c3_Err != nil {
@ -516,7 +516,7 @@ func PageInfoDropdown(data ToolbarData) templ.Component {
var templ_7745c5c3_Var19 string
templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", data.TodayPageviews))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/bn/toolbar.templ`, Line: 515, Col: 91}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `toolbar.templ`, Line: 515, Col: 91}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
if templ_7745c5c3_Err != nil {
@ -539,7 +539,7 @@ func PageInfoDropdown(data ToolbarData) templ.Component {
var templ_7745c5c3_Var21 string
templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var20).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/bn/toolbar.templ`, Line: 1, Col: 0}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `toolbar.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var21)
if templ_7745c5c3_Err != nil {
@ -552,7 +552,7 @@ func PageInfoDropdown(data ToolbarData) templ.Component {
var templ_7745c5c3_Var22 string
templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(data.TrendIcon())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/bn/toolbar.templ`, Line: 519, Col: 25}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `toolbar.templ`, Line: 519, Col: 25}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22))
if templ_7745c5c3_Err != nil {
@ -565,7 +565,7 @@ func PageInfoDropdown(data ToolbarData) templ.Component {
var templ_7745c5c3_Var23 string
templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d%%", abs(data.TrendPercent)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/bn/toolbar.templ`, Line: 519, Col: 73}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `toolbar.templ`, Line: 519, Col: 73}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23))
if templ_7745c5c3_Err != nil {
@ -603,7 +603,7 @@ func PageInfoDropdown(data ToolbarData) templ.Component {
var templ_7745c5c3_Var24 templ.SafeURL
templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL("/author/" + data.AuthorSlug))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/bn/toolbar.templ`, Line: 535, Col: 61}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `toolbar.templ`, Line: 535, Col: 61}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24))
if templ_7745c5c3_Err != nil {
@ -616,7 +616,7 @@ func PageInfoDropdown(data ToolbarData) templ.Component {
var templ_7745c5c3_Var25 string
templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(data.AuthorName)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/bn/toolbar.templ`, Line: 535, Col: 100}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `toolbar.templ`, Line: 535, Col: 100}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25))
if templ_7745c5c3_Err != nil {
@ -634,7 +634,7 @@ func PageInfoDropdown(data ToolbarData) templ.Component {
var templ_7745c5c3_Var26 string
templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(data.AuthorName)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/bn/toolbar.templ`, Line: 537, Col: 50}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `toolbar.templ`, Line: 537, Col: 50}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26))
if templ_7745c5c3_Err != nil {
@ -658,7 +658,7 @@ func PageInfoDropdown(data ToolbarData) templ.Component {
var templ_7745c5c3_Var27 string
templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d min read", data.ReadingTime))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/bn/toolbar.templ`, Line: 542, Col: 86}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `toolbar.templ`, Line: 542, Col: 86}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27))
if templ_7745c5c3_Err != nil {
@ -681,7 +681,7 @@ func PageInfoDropdown(data ToolbarData) templ.Component {
var templ_7745c5c3_Var28 string
templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d words", data.WordCount))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/bn/toolbar.templ`, Line: 546, Col: 84}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `toolbar.templ`, Line: 546, Col: 84}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28))
if templ_7745c5c3_Err != nil {
@ -704,7 +704,7 @@ func PageInfoDropdown(data ToolbarData) templ.Component {
var templ_7745c5c3_Var29 string
templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(data.TemplateName)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/bn/toolbar.templ`, Line: 554, Col: 48}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `toolbar.templ`, Line: 554, Col: 48}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29))
if templ_7745c5c3_Err != nil {
@ -717,7 +717,7 @@ func PageInfoDropdown(data ToolbarData) templ.Component {
var templ_7745c5c3_Var30 string
templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(data.LastModifiedFormatted())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/bn/toolbar.templ`, Line: 558, Col: 59}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `toolbar.templ`, Line: 558, Col: 59}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30))
if templ_7745c5c3_Err != nil {
@ -730,7 +730,7 @@ func PageInfoDropdown(data ToolbarData) templ.Component {
var templ_7745c5c3_Var31 templ.SafeURL
templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(data.SettingsURL))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/bn/toolbar.templ`, Line: 563, Col: 44}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `toolbar.templ`, Line: 563, Col: 44}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31))
if templ_7745c5c3_Err != nil {
@ -743,7 +743,7 @@ func PageInfoDropdown(data ToolbarData) templ.Component {
var templ_7745c5c3_Var32 templ.SafeURL
templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(data.HistoryURL))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/bn/toolbar.templ`, Line: 569, Col: 43}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `toolbar.templ`, Line: 569, Col: 43}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32))
if templ_7745c5c3_Err != nil {
@ -756,7 +756,7 @@ func PageInfoDropdown(data ToolbarData) templ.Component {
var templ_7745c5c3_Var33 templ.SafeURL
templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(data.AnalyticsURL))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/bn/toolbar.templ`, Line: 575, Col: 45}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `toolbar.templ`, Line: 575, Col: 45}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var33))
if templ_7745c5c3_Err != nil {
@ -786,7 +786,7 @@ func PageInfoDropdown(data ToolbarData) templ.Component {
var templ_7745c5c3_Var35 string
templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.ResolveAttributeValue(fmt.Sprintf("/toolbar/share-preview/%s", data.PageID))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/bn/toolbar.templ`, Line: 597, Col: 67}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `toolbar.templ`, Line: 597, Col: 67}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var35)
if templ_7745c5c3_Err != nil {
@ -832,7 +832,7 @@ func PageInfoDropdown(data ToolbarData) templ.Component {
var templ_7745c5c3_Var36 string
templ_7745c5c3_Var36, templ_7745c5c3_Err = templ.ResolveAttributeValue(fmt.Sprintf("/toolbar/discard/%s", data.PageID))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/bn/toolbar.templ`, Line: 625, Col: 62}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `toolbar.templ`, Line: 625, Col: 62}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var36)
if templ_7745c5c3_Err != nil {
@ -886,7 +886,7 @@ func positionButton(pageID uuid.UUID, pos string, currentPos string, label strin
var templ_7745c5c3_Var39 string
templ_7745c5c3_Var39, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var38).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/bn/toolbar.templ`, Line: 1, Col: 0}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `toolbar.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var39)
if templ_7745c5c3_Err != nil {
@ -899,7 +899,7 @@ func positionButton(pageID uuid.UUID, pos string, currentPos string, label strin
var templ_7745c5c3_Var40 string
templ_7745c5c3_Var40, templ_7745c5c3_Err = templ.ResolveAttributeValue(fmt.Sprintf("/toolbar/set-position/%s?pos=%s", pageID, pos))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/bn/toolbar.templ`, Line: 646, Col: 71}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `toolbar.templ`, Line: 646, Col: 71}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var40)
if templ_7745c5c3_Err != nil {
@ -912,7 +912,7 @@ func positionButton(pageID uuid.UUID, pos string, currentPos string, label strin
var templ_7745c5c3_Var41 string
templ_7745c5c3_Var41, templ_7745c5c3_Err = templ.ResolveAttributeValue(label)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/bn/toolbar.templ`, Line: 649, Col: 15}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `toolbar.templ`, Line: 649, Col: 15}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var41)
if templ_7745c5c3_Err != nil {
@ -934,7 +934,7 @@ func positionButton(pageID uuid.UUID, pos string, currentPos string, label strin
var templ_7745c5c3_Var43 string
templ_7745c5c3_Var43, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var42).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/bn/toolbar.templ`, Line: 1, Col: 0}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `toolbar.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var43)
if templ_7745c5c3_Err != nil {