package blocks import ( "context" "fmt" "strings" "github.com/google/uuid" ) // BlockFunc renders a block given its content data. type BlockFunc func(ctx context.Context, content map[string]any) string // BlockCategory represents categories for organizing blocks in the palette. type BlockCategory string const ( CategoryContent BlockCategory = "content" CategoryLayout BlockCategory = "layout" CategoryNavigation BlockCategory = "navigation" CategoryBlog BlockCategory = "blog" CategoryTheme BlockCategory = "theme" ) // BlockMeta provides metadata about a block type for admin UI and validation. type BlockMeta struct { Key string Title string Description string Category BlockCategory HasInternalSlot bool Source string Hidden bool EditorJS string } // BlockNode represents a block with its content and children for rendering. type BlockNode struct { ID uuid.UUID BlockKey string Title string Content map[string]any HtmlContent string Children []*BlockNode SortOrder int } // SlotRenderer is the interface for rendering container slots. type SlotRenderer interface { RenderContainerSlot(ctx context.Context, containerID uuid.UUID) string } // HumanProofBannerData holds data for the public human proof banner. type HumanProofBannerData struct { ActiveTimeMinutes int KeystrokeCount int SessionCount int PostSlug string } // RenderHumanProofBanner renders the public-facing banner HTML. func RenderHumanProofBanner(hp *HumanProofBannerData) string { return fmt.Sprintf(`
`+ `
`+ `
`+ `
HP
`+ `
`+ `
Human Proof
`+ `
%[1]d min active · %[2]s keystrokes · %[3]d sessions
`+ `
`+ ``+ `
`+ ``+ ``, hp.ActiveTimeMinutes, formatThousands(hp.KeystrokeCount), hp.SessionCount, hp.PostSlug, ) } func formatThousands(n int) string { if n < 1000 { return fmt.Sprintf("%d", n) } s := fmt.Sprintf("%d", n) var result []byte for i, c := range s { if i > 0 && (len(s)-i)%3 == 0 { result = append(result, ',') } result = append(result, byte(c)) } return string(result) } // ResolveMediaPath converts a media: prefixed path to a /media/ URL path. func ResolveMediaPath(path string) string { if after, ok := strings.CutPrefix(path, "media:"); ok { return "/media/" + after } if _, err := uuid.Parse(path); err == nil { return "/media/" + path } return path }