fix: replace any with typed context accessors in SDK

- Define PageContext, PostContext, AuthorContext, CategoryContext,
  MasterPageContext structs for typed context passing
- Define EmbedResolver interface
- Make GetQueries generic: GetQueries[T](ctx) (T, bool)
- Fix Content.BlockNoteToHTML to take map[string]any, not any

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Alex Dunmow 2026-04-30 22:39:34 +08:00
parent 99fc63ddfd
commit 79c558a968
2 changed files with 137 additions and 22 deletions

View File

@ -8,6 +8,61 @@ import (
"github.com/google/uuid" "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{} type templateKeyContextKey struct{}
func WithTemplateKey(ctx context.Context, templateKey string) context.Context { func WithTemplateKey(ctx context.Context, templateKey string) context.Context {
@ -21,55 +76,83 @@ func GetTemplateKey(ctx context.Context) string {
return "" return ""
} }
// --- Queries (generic) ---
type queriesContextKey struct{} 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) return context.WithValue(ctx, queriesContextKey{}, queries)
} }
func GetQueries(ctx context.Context) any { // GetQueries retrieves a typed queries value from context.
return ctx.Value(queriesContextKey{}) // 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{} 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) return context.WithValue(ctx, currentPageContextKey{}, page)
} }
func GetCurrentPage(ctx context.Context) any { func GetCurrentPage(ctx context.Context) *PageContext {
return ctx.Value(currentPageContextKey{}) if v, ok := ctx.Value(currentPageContextKey{}).(*PageContext); ok {
return v
} }
return nil
}
// --- Current blog post ---
type currentBlogPostContextKey struct{} 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) return context.WithValue(ctx, currentBlogPostContextKey{}, post)
} }
func GetCurrentBlogPost(ctx context.Context) any { func GetCurrentBlogPost(ctx context.Context) *PostContext {
return ctx.Value(currentBlogPostContextKey{}) if v, ok := ctx.Value(currentBlogPostContextKey{}).(*PostContext); ok {
return v
} }
return nil
}
// --- Current author ---
type currentAuthorContextKey struct{} 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) return context.WithValue(ctx, currentAuthorContextKey{}, author)
} }
func GetCurrentAuthor(ctx context.Context) any { func GetCurrentAuthor(ctx context.Context) *AuthorContext {
return ctx.Value(currentAuthorContextKey{}) if v, ok := ctx.Value(currentAuthorContextKey{}).(*AuthorContext); ok {
return v
} }
return nil
}
// --- Current category ---
type currentCategoryContextKey struct{} 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) return context.WithValue(ctx, currentCategoryContextKey{}, category)
} }
func GetCurrentCategory(ctx context.Context) any { func GetCurrentCategory(ctx context.Context) *CategoryContext {
return ctx.Value(currentCategoryContextKey{}) if v, ok := ctx.Value(currentCategoryContextKey{}).(*CategoryContext); ok {
return v
} }
return nil
}
// --- Requested path (for 404 pages) ---
type requestedPathContextKey struct{} type requestedPathContextKey struct{}
@ -84,6 +167,8 @@ func GetRequestedPath(ctx context.Context) string {
return "" return ""
} }
// --- Injected slots (master page rendering) ---
type injectedSlotsContextKey struct{} type injectedSlotsContextKey struct{}
func WithInjectedSlots(ctx context.Context, slots map[string]string) context.Context { 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 return nil
} }
// --- Master page ---
type masterPageContextKey struct{} 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) return context.WithValue(ctx, masterPageContextKey{}, masterPage)
} }
func GetMasterPage(ctx context.Context) any { func GetMasterPage(ctx context.Context) *MasterPageContext {
return ctx.Value(masterPageContextKey{}) if v, ok := ctx.Value(masterPageContextKey{}).(*MasterPageContext); ok {
return v
}
return nil
} }
func IsMasterPageContext(ctx context.Context) bool { func IsMasterPageContext(ctx context.Context) bool {
return ctx.Value(masterPageContextKey{}) != nil return ctx.Value(masterPageContextKey{}) != nil
} }
// --- HTTP request ---
type requestContextKey struct{} type requestContextKey struct{}
func WithRequest(ctx context.Context, r *http.Request) context.Context { func WithRequest(ctx context.Context, r *http.Request) context.Context {
@ -131,6 +223,8 @@ func GetRequest(ctx context.Context) *http.Request {
return nil return nil
} }
// --- Editor mode ---
type isEditorContextKey struct{} type isEditorContextKey struct{}
func WithIsEditor(ctx context.Context, isEditor bool) context.Context { func WithIsEditor(ctx context.Context, isEditor bool) context.Context {
@ -144,6 +238,8 @@ func IsEditor(ctx context.Context) bool {
return false return false
} }
// --- Expected slots ---
type expectedSlotsContextKey struct{} type expectedSlotsContextKey struct{}
func WithExpectedSlots(ctx context.Context, slots []string) context.Context { func WithExpectedSlots(ctx context.Context, slots []string) context.Context {
@ -157,6 +253,8 @@ func GetExpectedSlots(ctx context.Context) []string {
return nil return nil
} }
// --- Block ID ---
type blockIDContextKey struct{} type blockIDContextKey struct{}
func WithBlockID(ctx context.Context, id uuid.UUID) context.Context { func WithBlockID(ctx context.Context, id uuid.UUID) context.Context {
@ -170,6 +268,8 @@ func GetBlockID(ctx context.Context) uuid.UUID {
return uuid.Nil return uuid.Nil
} }
// --- Current page ID ---
type currentPageIDContextKey struct{} type currentPageIDContextKey struct{}
func WithCurrentPageID(ctx context.Context, id uuid.UUID) context.Context { func WithCurrentPageID(ctx context.Context, id uuid.UUID) context.Context {
@ -183,6 +283,8 @@ func GetCurrentPageID(ctx context.Context) uuid.UUID {
return uuid.Nil return uuid.Nil
} }
// --- Slot renderer ---
type slotRendererContextKey struct{} type slotRendererContextKey struct{}
func WithSlotRenderer(ctx context.Context, r SlotRenderer) context.Context { func WithSlotRenderer(ctx context.Context, r SlotRenderer) context.Context {
@ -196,6 +298,8 @@ func GetSlotRenderer(ctx context.Context) SlotRenderer {
return nil return nil
} }
// --- Human proof banner ---
type humanProofBannerContextKey struct{} type humanProofBannerContextKey struct{}
func WithHumanProofBanner(ctx context.Context, data *HumanProofBannerData) context.Context { func WithHumanProofBanner(ctx context.Context, data *HumanProofBannerData) context.Context {
@ -209,15 +313,22 @@ func GetHumanProofBanner(ctx context.Context) *HumanProofBannerData {
return nil return nil
} }
// --- Embed resolver ---
type embedResolverContextKey struct{} 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) return context.WithValue(ctx, embedResolverContextKey{}, resolver)
} }
func GetEmbedResolver(ctx context.Context) any { func GetEmbedResolver(ctx context.Context) EmbedResolver {
return ctx.Value(embedResolverContextKey{}) if r, ok := ctx.Value(embedResolverContextKey{}).(EmbedResolver); ok {
return r
} }
return nil
}
// --- RenderSlot ---
// RenderSlot renders children for the current container block. // RenderSlot renders children for the current container block.
func RenderSlot(ctx context.Context) string { func RenderSlot(ctx context.Context) string {
@ -232,6 +343,8 @@ func RenderSlot(ctx context.Context) string {
return renderer.RenderContainerSlot(ctx, blockID) return renderer.RenderContainerSlot(ctx, blockID)
} }
// --- BlockContext (pongo2 template data) ---
// BlockContext provides contextual information available to all block templates. // BlockContext provides contextual information available to all block templates.
type BlockContext struct { type BlockContext struct {
URL string `pongo2:"url"` 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. // DetailRowInfo holds the resolved data table row for detail pages.
type DetailRowInfo struct { type DetailRowInfo struct {
TableID string TableID string

View File

@ -41,7 +41,7 @@ type Content interface {
GetPage(ctx context.Context, slug string) (*PageInfo, error) GetPage(ctx context.Context, slug string) (*PageInfo, error)
GetPost(ctx context.Context, slug string) (*PostInfo, error) GetPost(ctx context.Context, slug string) (*PostInfo, error)
Slugify(text string) string Slugify(text string) string
BlockNoteToHTML(doc any) string BlockNoteToHTML(doc map[string]any) string
GenerateExcerpt(html string, maxLen int) string GenerateExcerpt(html string, maxLen int) string
StripHTML(s string) string StripHTML(s string) string
} }