package blocks import ( "context" "net/http" "time" "github.com/google/uuid" ) // --- Typed context structs --- // PageContext provides page information to blocks via context. // The CMS populates this from its internal db.Page type. type PageContext struct { ID uuid.UUID Slug string Title string PostType string // "page", "post", "master", "system" Status string // "published", "draft", "scheduled" } // PostContext provides blog post information to blocks via context. type PostContext struct { ID uuid.UUID Slug string Title string Excerpt string FeaturedImageURL string AuthorID uuid.UUID PublishedAt time.Time ReadingTime int IsFeatured bool } // AuthorContext provides author information to blocks via context. type AuthorContext struct { ID uuid.UUID Name string Slug string Bio string AvatarURL string } // CategoryContext provides category information to blocks via context. type CategoryContext struct { ID uuid.UUID Name string Slug string } // MasterPageContext provides master page information during rendering. type MasterPageContext struct { ID uuid.UUID Slug string Title string } // EmbedResolver resolves embedded component blocks within blog content. type EmbedResolver interface { RenderEmbed(ctx context.Context, blockID uuid.UUID, dataSource, layout string) string } // --- Template key --- type templateKeyContextKey struct{} func WithTemplateKey(ctx context.Context, templateKey string) context.Context { return context.WithValue(ctx, templateKeyContextKey{}, templateKey) } func GetTemplateKey(ctx context.Context) string { if v, ok := ctx.Value(templateKeyContextKey{}).(string); ok { return v } return "" } // --- Queries (generic) --- type queriesContextKey struct{} // WithQueries stores a queries value in context. Use with GetQueries[T]. func WithQueries[T any](ctx context.Context, queries T) context.Context { return context.WithValue(ctx, queriesContextKey{}, queries) } // GetQueries retrieves a typed queries value from context. // Usage: queries, ok := blocks.GetQueries[*db.Queries](ctx) func GetQueries[T any](ctx context.Context) (T, bool) { val, ok := ctx.Value(queriesContextKey{}).(T) return val, ok } // --- Current page --- type currentPageContextKey struct{} func WithCurrentPage(ctx context.Context, page *PageContext) context.Context { return context.WithValue(ctx, currentPageContextKey{}, page) } func GetCurrentPage(ctx context.Context) *PageContext { if v, ok := ctx.Value(currentPageContextKey{}).(*PageContext); ok { return v } return nil } // --- Current blog post --- type currentBlogPostContextKey struct{} func WithCurrentBlogPost(ctx context.Context, post *PostContext) context.Context { return context.WithValue(ctx, currentBlogPostContextKey{}, post) } func GetCurrentBlogPost(ctx context.Context) *PostContext { if v, ok := ctx.Value(currentBlogPostContextKey{}).(*PostContext); ok { return v } return nil } // --- Current author --- type currentAuthorContextKey struct{} func WithCurrentAuthor(ctx context.Context, author *AuthorContext) context.Context { return context.WithValue(ctx, currentAuthorContextKey{}, author) } func GetCurrentAuthor(ctx context.Context) *AuthorContext { if v, ok := ctx.Value(currentAuthorContextKey{}).(*AuthorContext); ok { return v } return nil } // --- Current category --- type currentCategoryContextKey struct{} func WithCurrentCategory(ctx context.Context, category *CategoryContext) context.Context { return context.WithValue(ctx, currentCategoryContextKey{}, category) } func GetCurrentCategory(ctx context.Context) *CategoryContext { if v, ok := ctx.Value(currentCategoryContextKey{}).(*CategoryContext); ok { return v } return nil } // --- Requested path (for 404 pages) --- type requestedPathContextKey struct{} func WithRequestedPath(ctx context.Context, path string) context.Context { return context.WithValue(ctx, requestedPathContextKey{}, path) } func GetRequestedPath(ctx context.Context) string { if v, ok := ctx.Value(requestedPathContextKey{}).(string); ok { return v } return "" } // --- Injected slots (master page rendering) --- type injectedSlotsContextKey struct{} func WithInjectedSlots(ctx context.Context, slots map[string]string) context.Context { return context.WithValue(ctx, injectedSlotsContextKey{}, slots) } func GetInjectedSlotContent(ctx context.Context, slotName string) string { if slots, ok := ctx.Value(injectedSlotsContextKey{}).(map[string]string); ok { return slots[slotName] } return "" } func GetInjectedSlots(ctx context.Context) map[string]string { if slots, ok := ctx.Value(injectedSlotsContextKey{}).(map[string]string); ok { return slots } return nil } // --- Master page --- type masterPageContextKey struct{} func WithMasterPage(ctx context.Context, masterPage *MasterPageContext) context.Context { return context.WithValue(ctx, masterPageContextKey{}, masterPage) } func GetMasterPage(ctx context.Context) *MasterPageContext { if v, ok := ctx.Value(masterPageContextKey{}).(*MasterPageContext); ok { return v } return nil } func IsMasterPageContext(ctx context.Context) bool { return ctx.Value(masterPageContextKey{}) != nil } // --- HTTP request --- type requestContextKey struct{} func WithRequest(ctx context.Context, r *http.Request) context.Context { return context.WithValue(ctx, requestContextKey{}, r) } func GetRequest(ctx context.Context) *http.Request { if r, ok := ctx.Value(requestContextKey{}).(*http.Request); ok { return r } return nil } // --- Editor mode --- type isEditorContextKey struct{} func WithIsEditor(ctx context.Context, isEditor bool) context.Context { return context.WithValue(ctx, isEditorContextKey{}, isEditor) } func IsEditor(ctx context.Context) bool { if v, ok := ctx.Value(isEditorContextKey{}).(bool); ok { return v } return false } // --- Expected slots --- type expectedSlotsContextKey struct{} func WithExpectedSlots(ctx context.Context, slots []string) context.Context { return context.WithValue(ctx, expectedSlotsContextKey{}, slots) } func GetExpectedSlots(ctx context.Context) []string { if v, ok := ctx.Value(expectedSlotsContextKey{}).([]string); ok { return v } return nil } // --- Block ID --- type blockIDContextKey struct{} func WithBlockID(ctx context.Context, id uuid.UUID) context.Context { return context.WithValue(ctx, blockIDContextKey{}, id) } func GetBlockID(ctx context.Context) uuid.UUID { if v, ok := ctx.Value(blockIDContextKey{}).(uuid.UUID); ok { return v } return uuid.Nil } // --- Current page ID --- type currentPageIDContextKey struct{} func WithCurrentPageID(ctx context.Context, id uuid.UUID) context.Context { return context.WithValue(ctx, currentPageIDContextKey{}, id) } func GetCurrentPageID(ctx context.Context) uuid.UUID { if v, ok := ctx.Value(currentPageIDContextKey{}).(uuid.UUID); ok { return v } return uuid.Nil } // --- Slot renderer --- type slotRendererContextKey struct{} func WithSlotRenderer(ctx context.Context, r SlotRenderer) context.Context { return context.WithValue(ctx, slotRendererContextKey{}, r) } func GetSlotRenderer(ctx context.Context) SlotRenderer { if r, ok := ctx.Value(slotRendererContextKey{}).(SlotRenderer); ok { return r } return nil } // --- Human proof banner --- type humanProofBannerContextKey struct{} func WithHumanProofBanner(ctx context.Context, data *HumanProofBannerData) context.Context { return context.WithValue(ctx, humanProofBannerContextKey{}, data) } func GetHumanProofBanner(ctx context.Context) *HumanProofBannerData { if v, ok := ctx.Value(humanProofBannerContextKey{}).(*HumanProofBannerData); ok { return v } return nil } // --- Embed resolver --- type embedResolverContextKey struct{} func WithEmbedResolver(ctx context.Context, resolver EmbedResolver) context.Context { return context.WithValue(ctx, embedResolverContextKey{}, resolver) } func GetEmbedResolver(ctx context.Context) EmbedResolver { if r, ok := ctx.Value(embedResolverContextKey{}).(EmbedResolver); ok { return r } return nil } // --- RenderSlot --- // RenderSlot renders children for the current container block. func RenderSlot(ctx context.Context) string { blockID := GetBlockID(ctx) if blockID == uuid.Nil { return "" } renderer := GetSlotRenderer(ctx) if renderer == nil { return "" } return renderer.RenderContainerSlot(ctx, blockID) } // --- BlockContext (pongo2 template data) --- // BlockContext provides contextual information available to all block templates. type BlockContext struct { URL string `pongo2:"url"` Path string `pongo2:"path"` Slug string `pongo2:"slug"` PageID string `pongo2:"pageId"` PageTitle string `pongo2:"pageTitle"` TemplateKey string `pongo2:"templateKey"` IsEditor bool `pongo2:"isEditor"` Timestamp int64 `pongo2:"timestamp"` IsLoggedIn bool `pongo2:"isLoggedIn"` UserID string `pongo2:"userId"` UserEmail string `pongo2:"userEmail"` UserRole string `pongo2:"userRole"` Method string `pongo2:"method"` Host string `pongo2:"host"` Query map[string]string `pongo2:"query"` Referrer string `pongo2:"referrer"` UserAgent string `pongo2:"userAgent"` IP string `pongo2:"ip"` Cookies map[string]string `pongo2:"cookies"` Headers map[string]string `pongo2:"headers"` Country string `pongo2:"country"` City string `pongo2:"city"` Timezone string `pongo2:"timezone"` Now time.Time `pongo2:"now"` CurrentAuthor map[string]any `pongo2:"currentAuthor"` CurrentPost map[string]any `pongo2:"currentPost"` CurrentCategory map[string]any `pongo2:"currentCategory"` Site map[string]any `pongo2:"site"` IsPublicLoggedIn bool `pongo2:"isPublicLoggedIn"` PublicUserID string `pongo2:"publicUserId"` PublicUsername string `pongo2:"publicUsername"` PublicDisplayName string `pongo2:"publicDisplayName"` PublicEmailVerified bool `pongo2:"publicEmailVerified"` DetailRowID string `pongo2:"detailRowId"` DetailTableID string `pongo2:"detailTableId"` DetailRowData map[string]any `pongo2:"detailRowData"` BlogIndexURL string `pongo2:"blogIndexUrl"` CategoryPageURL string `pongo2:"categoryPageUrl"` } type blockContextKey struct{} func WithBlockContext(ctx context.Context, blockCtx *BlockContext) context.Context { return context.WithValue(ctx, blockContextKey{}, blockCtx) } func GetBlockContext(ctx context.Context) *BlockContext { if bc, ok := ctx.Value(blockContextKey{}).(*BlockContext); ok { return bc } return nil } // ToMap converts the BlockContext to a map for pongo2 template injection. func (bc *BlockContext) ToMap() map[string]any { return map[string]any{ "url": bc.URL, "path": bc.Path, "slug": bc.Slug, "pageId": bc.PageID, "pageTitle": bc.PageTitle, "templateKey": bc.TemplateKey, "isEditor": bc.IsEditor, "timestamp": bc.Timestamp, "now": bc.Now, "isLoggedIn": bc.IsLoggedIn, "userId": bc.UserID, "userEmail": bc.UserEmail, "userRole": bc.UserRole, "method": bc.Method, "host": bc.Host, "query": bc.Query, "referrer": bc.Referrer, "userAgent": bc.UserAgent, "ip": bc.IP, "cookies": bc.Cookies, "headers": bc.Headers, "country": bc.Country, "city": bc.City, "timezone": bc.Timezone, "currentAuthor": bc.CurrentAuthor, "currentPost": bc.CurrentPost, "currentCategory": bc.CurrentCategory, "site": bc.Site, "isPublicLoggedIn": bc.IsPublicLoggedIn, "publicUserId": bc.PublicUserID, "publicUsername": bc.PublicUsername, "publicDisplayName": bc.PublicDisplayName, "publicEmailVerified": bc.PublicEmailVerified, "detailRowId": bc.DetailRowID, "detailTableId": bc.DetailTableID, "detailRowData": bc.DetailRowData, "blogIndexUrl": bc.BlogIndexURL, "categoryPageUrl": bc.CategoryPageURL, } } // --- Detail row --- // DetailRowInfo holds the resolved data table row for detail pages. type DetailRowInfo struct { TableID string RowID string Data map[string]any } type detailRowKey struct{} func WithDetailRow(ctx context.Context, tableID, rowID string, data map[string]any) context.Context { return context.WithValue(ctx, detailRowKey{}, &DetailRowInfo{TableID: tableID, RowID: rowID, Data: data}) } func GetDetailRow(ctx context.Context) *DetailRowInfo { if v, ok := ctx.Value(detailRowKey{}).(*DetailRowInfo); ok { return v } return nil }