package bn import ( "fmt" "time" "github.com/google/uuid" ) // ToolbarData contains data for the admin editor toolbar type ToolbarData struct { Enabled bool PageID uuid.UUID PageSlug string PageTitle string PostType string // "page", "post", "master", "system" Status string // "published", "draft", "scheduled" HasUnpublishedChanges bool PreviewMode string // "published" or "draft" Position string // "tl", "tc", "tr", "bl", "bc", "br" (default: "tr") ScheduledAt *time.Time TemplateName string AuthorName string LastModified time.Time EditURL string SettingsURL string HistoryURL string AnalyticsURL string // Analytics snapshot TodayPageviews int64 PageviewsTrend string // "up", "down", "flat" TrendPercent int // Blog-specific fields ReadingTime int // minutes WordCount int CategoryCount int AuthorSlug string } // StatusBadgeClass returns the Tailwind classes for the status badge func (t ToolbarData) StatusBadgeClass() string { switch t.Status { case "published": return "bg-success text-success-foreground" case "scheduled": return "bg-info text-info-foreground" default: return "bg-warning text-warning-foreground" } } // StatusLabel returns a human-readable status label func (t ToolbarData) StatusLabel() string { switch t.Status { case "published": return "Published" case "scheduled": return "Scheduled" default: return "Draft" } } // PostTypeLabel returns a human-readable post type label func (t ToolbarData) PostTypeLabel() string { switch t.PostType { case "post": return "Post" case "master": return "Master" case "system": return "System" default: return "Page" } } // IsPreviewMode returns true if currently viewing draft/preview func (t ToolbarData) IsPreviewMode() bool { return t.PreviewMode == "draft" } // CanPublish returns true if the publish button should be enabled func (t ToolbarData) CanPublish() bool { return t.HasUnpublishedChanges } // LastModifiedFormatted returns the last modified time in a readable format func (t ToolbarData) LastModifiedFormatted() string { return t.LastModified.Format("Jan 2, 2006 3:04 PM") } // IsTopPosition returns true if toolbar should be at top (for legacy compatibility) func (t ToolbarData) IsTopPosition() bool { return t.Position == "tl" || t.Position == "tc" || t.Position == "tr" || t.Position == "" } // IsBottomPosition returns true if toolbar is at bottom func (t ToolbarData) IsBottomPosition() bool { return t.Position == "bl" || t.Position == "bc" || t.Position == "br" } // IsLeftPosition returns true if toolbar is on the left side func (t ToolbarData) IsLeftPosition() bool { return t.Position == "tl" || t.Position == "bl" } // IsRightPosition returns true if toolbar is on the right side func (t ToolbarData) IsRightPosition() bool { return t.Position == "tr" || t.Position == "br" || t.Position == "" } // IsCenterPosition returns true if toolbar is centered func (t ToolbarData) IsCenterPosition() bool { return t.Position == "tc" || t.Position == "bc" } // DropdownDirection returns "up" or "down" based on toolbar vertical position func (t ToolbarData) DropdownDirection() string { if t.IsBottomPosition() { return "up" } return "down" } // DropdownAlign returns "left", "center", or "right" based on toolbar horizontal position func (t ToolbarData) DropdownAlign() string { if t.IsLeftPosition() { return "left" } else if t.IsCenterPosition() { return "center" } return "right" } // PositionClasses returns the positioning classes for the floating pill func (t ToolbarData) PositionClasses() string { switch t.Position { case "tl": return "top-4 left-4" case "tc": return "top-4 left-1/2 -translate-x-1/2" case "bl": return "bottom-4 left-4" case "bc": return "bottom-4 left-1/2 -translate-x-1/2" case "br": return "bottom-4 right-4" default: // "tr" or empty return "top-4 right-4" } } // IsBlogPost returns true if this is a blog post func (t ToolbarData) IsBlogPost() bool { return t.PostType == "post" } // TrendIcon returns the trend icon for pageviews func (t ToolbarData) TrendIcon() string { switch t.PageviewsTrend { case "up": return "↑" case "down": return "↓" default: return "→" } } // TrendColorClass returns the color class for the trend indicator func (t ToolbarData) TrendColorClass() string { switch t.PageviewsTrend { case "up": return "text-success" case "down": return "text-destructive" default: return "text-muted-foreground" } } // AdminEditorToolbar renders the floating pill toolbar for admins on public pages // Uses inverted color scheme - dark on light backgrounds, light on dark backgrounds templ AdminEditorToolbar(data ToolbarData) { if data.Enabled {
if data.IsBlogPost() && data.ReadingTime > 0 { } if data.CanPublish() { }
} } // PageInfoDropdown renders the page info and quick actions dropdown templ PageInfoDropdown(data ToolbarData) {
if data.TodayPageviews > 0 || data.PageviewsTrend != "" {
{ fmt.Sprintf("%d", data.TodayPageviews) } views today
if data.TrendPercent != 0 { { data.TrendIcon() } { fmt.Sprintf("%d%%", abs(data.TrendPercent)) } }
} if data.IsBlogPost() {
if data.AuthorName != "" {
if data.AuthorSlug != "" { { data.AuthorName } } else { { data.AuthorName } }
} if data.ReadingTime > 0 { { fmt.Sprintf("%d min read", data.ReadingTime) } }
if data.WordCount > 0 {
{ fmt.Sprintf("%d words", data.WordCount) }
}
}
Template { data.TemplateName }
Modified { data.LastModifiedFormatted() }
Settings History Analytics
Move toolbar
@positionButton(data.PageID, "tl", data.Position, "Top left") @positionButton(data.PageID, "tc", data.Position, "Top center") @positionButton(data.PageID, "tr", data.Position, "Top right") @positionButton(data.PageID, "bl", data.Position, "Bottom left") @positionButton(data.PageID, "bc", data.Position, "Bottom center") @positionButton(data.PageID, "br", data.Position, "Bottom right")
if data.HasUnpublishedChanges {
}
} // positionButton renders a position selector button templ positionButton(pageID uuid.UUID, pos string, currentPos string, label string) { } // abs returns the absolute value of an integer func abs(n int) int { if n < 0 { return -n } return n } // copyPageURL generates the script to copy page URL script copyPageURL(slug string) { const url = window.location.origin + slug; navigator.clipboard.writeText(url).then(() => { // Show brief feedback - could enhance with toast later const btn = event.currentTarget; const originalText = btn.innerHTML; btn.innerHTML = ' Copied!'; setTimeout(() => { btn.innerHTML = originalText; }, 2000); }); } // togglePreviewMode toggles between preview (draft) and published mode via URL script togglePreviewMode(isCurrentlyPreview bool) { const url = new URL(window.location.href); if (isCurrentlyPreview) { // Currently previewing, remove preview param url.searchParams.delete('preview'); } else { // Not previewing, add preview param url.searchParams.set('preview', '1'); } window.location.href = url.toString(); }