diff --git a/blocks/context.go b/blocks/context.go index 1bc93ef..b6e05e2 100644 --- a/blocks/context.go +++ b/blocks/context.go @@ -8,6 +8,61 @@ import ( "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 { @@ -21,56 +76,84 @@ func GetTemplateKey(ctx context.Context) string { return "" } +// --- Queries (generic) --- + type queriesContextKey struct{} -func WithQueries(ctx context.Context, queries any) context.Context { +// 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) } -func GetQueries(ctx context.Context) any { - return ctx.Value(queriesContextKey{}) +// 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 any) context.Context { +func WithCurrentPage(ctx context.Context, page *PageContext) context.Context { return context.WithValue(ctx, currentPageContextKey{}, page) } -func GetCurrentPage(ctx context.Context) any { - return ctx.Value(currentPageContextKey{}) +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 any) context.Context { +func WithCurrentBlogPost(ctx context.Context, post *PostContext) context.Context { return context.WithValue(ctx, currentBlogPostContextKey{}, post) } -func GetCurrentBlogPost(ctx context.Context) any { - return ctx.Value(currentBlogPostContextKey{}) +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 any) context.Context { +func WithCurrentAuthor(ctx context.Context, author *AuthorContext) context.Context { return context.WithValue(ctx, currentAuthorContextKey{}, author) } -func GetCurrentAuthor(ctx context.Context) any { - return ctx.Value(currentAuthorContextKey{}) +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 any) context.Context { +func WithCurrentCategory(ctx context.Context, category *CategoryContext) context.Context { return context.WithValue(ctx, currentCategoryContextKey{}, category) } -func GetCurrentCategory(ctx context.Context) any { - return ctx.Value(currentCategoryContextKey{}) +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 { @@ -84,6 +167,8 @@ func GetRequestedPath(ctx context.Context) string { return "" } +// --- Injected slots (master page rendering) --- + type injectedSlotsContextKey struct{} func WithInjectedSlots(ctx context.Context, slots map[string]string) context.Context { @@ -104,20 +189,27 @@ func GetInjectedSlots(ctx context.Context) map[string]string { return nil } +// --- Master page --- + type masterPageContextKey struct{} -func WithMasterPage(ctx context.Context, masterPage any) context.Context { +func WithMasterPage(ctx context.Context, masterPage *MasterPageContext) context.Context { return context.WithValue(ctx, masterPageContextKey{}, masterPage) } -func GetMasterPage(ctx context.Context) any { - return ctx.Value(masterPageContextKey{}) +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 { @@ -131,6 +223,8 @@ func GetRequest(ctx context.Context) *http.Request { return nil } +// --- Editor mode --- + type isEditorContextKey struct{} func WithIsEditor(ctx context.Context, isEditor bool) context.Context { @@ -144,6 +238,8 @@ func IsEditor(ctx context.Context) bool { return false } +// --- Expected slots --- + type expectedSlotsContextKey struct{} func WithExpectedSlots(ctx context.Context, slots []string) context.Context { @@ -157,6 +253,8 @@ func GetExpectedSlots(ctx context.Context) []string { return nil } +// --- Block ID --- + type blockIDContextKey struct{} func WithBlockID(ctx context.Context, id uuid.UUID) context.Context { @@ -170,6 +268,8 @@ func GetBlockID(ctx context.Context) uuid.UUID { return uuid.Nil } +// --- Current page ID --- + type currentPageIDContextKey struct{} func WithCurrentPageID(ctx context.Context, id uuid.UUID) context.Context { @@ -183,6 +283,8 @@ func GetCurrentPageID(ctx context.Context) uuid.UUID { return uuid.Nil } +// --- Slot renderer --- + type slotRendererContextKey struct{} func WithSlotRenderer(ctx context.Context, r SlotRenderer) context.Context { @@ -196,6 +298,8 @@ func GetSlotRenderer(ctx context.Context) SlotRenderer { return nil } +// --- Human proof banner --- + type humanProofBannerContextKey struct{} func WithHumanProofBanner(ctx context.Context, data *HumanProofBannerData) context.Context { @@ -209,16 +313,23 @@ func GetHumanProofBanner(ctx context.Context) *HumanProofBannerData { return nil } +// --- Embed resolver --- + type embedResolverContextKey struct{} -func WithEmbedResolver(ctx context.Context, resolver any) context.Context { +func WithEmbedResolver(ctx context.Context, resolver EmbedResolver) context.Context { return context.WithValue(ctx, embedResolverContextKey{}, resolver) } -func GetEmbedResolver(ctx context.Context) any { - return ctx.Value(embedResolverContextKey{}) +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) @@ -232,6 +343,8 @@ func RenderSlot(ctx context.Context) string { 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"` @@ -312,6 +425,8 @@ func (bc *BlockContext) ToMap() map[string]any { } } +// --- Detail row --- + // DetailRowInfo holds the resolved data table row for detail pages. type DetailRowInfo struct { TableID string diff --git a/content/content.go b/content/content.go index 3cff802..b67ae95 100644 --- a/content/content.go +++ b/content/content.go @@ -41,7 +41,7 @@ type Content interface { GetPage(ctx context.Context, slug string) (*PageInfo, error) GetPost(ctx context.Context, slug string) (*PostInfo, error) Slugify(text string) string - BlockNoteToHTML(doc any) string + BlockNoteToHTML(doc map[string]any) string GenerateExcerpt(html string, maxLen int) string StripHTML(s string) string }