finish fixing template method calls/chaining, simplify templates

This commit is contained in:
Sky Johnson 2025-08-22 20:54:14 -05:00
parent b5e3413c63
commit ddc5bd5f6e
7 changed files with 88 additions and 94 deletions

Binary file not shown.

View File

@ -1,6 +1,7 @@
package markdown package markdown
import ( import (
"fmt"
"html" "html"
"strings" "strings"
) )
@ -40,6 +41,17 @@ func MarkdownToHTML(s string) string {
return `<div class="md-lib">` + s + "</div>" return `<div class="md-lib">` + s + "</div>"
} }
// MarkdownTemplateFunc is a TemplateFunc-compatible wrapper for MarkdownToHTML
func MarkdownTemplateFunc(args ...any) any {
if len(args) == 0 {
return ""
}
// Convert first argument to string
input := fmt.Sprintf("%v", args[0])
return MarkdownToHTML(input)
}
func replaceCodeSpans(s string) string { func replaceCodeSpans(s string) string {
for { for {
start := strings.Index(s, "`") start := strings.Index(s, "`")

View File

@ -17,16 +17,13 @@ import (
type ThreadInfo struct { type ThreadInfo struct {
Thread *forum.Forum Thread *forum.Forum
Author *users.User Author *users.User
AuthorClass string
LastReplyBy *users.User LastReplyBy *users.User
} }
// PostInfo combines a forum post/reply with its author // PostInfo combines a forum post/reply with its author
type PostInfo struct { type PostInfo struct {
Post *forum.Forum Post *forum.Forum
Author *users.User Author *users.User
AuthorClass string
Content string // Pre-processed markdown content
} }
func RegisterForumRoutes(app *sushi.App) { func RegisterForumRoutes(app *sushi.App) {
@ -68,8 +65,6 @@ func index(ctx sushi.Ctx) {
author = &users.User{Username: "[Deleted]", ClassID: 1} author = &users.User{Username: "[Deleted]", ClassID: 1}
} }
authorClass := author.Class().Name
// Get last reply author if there are replies // Get last reply author if there are replies
var lastReplyBy *users.User var lastReplyBy *users.User
if thread.Replies > 0 { if thread.Replies > 0 {
@ -83,7 +78,6 @@ func index(ctx sushi.Ctx) {
threadInfos = append(threadInfos, ThreadInfo{ threadInfos = append(threadInfos, ThreadInfo{
Thread: thread, Thread: thread,
Author: author, Author: author,
AuthorClass: authorClass,
LastReplyBy: lastReplyBy, LastReplyBy: lastReplyBy,
}) })
} }
@ -161,8 +155,6 @@ func showThread(ctx sushi.Ctx) {
if threadAuthor == nil { if threadAuthor == nil {
threadAuthor = &users.User{Username: "[Deleted]", Level: 1, ClassID: 1} threadAuthor = &users.User{Username: "[Deleted]", Level: 1, ClassID: 1}
} }
threadAuthorClass := threadAuthor.Class().Name
threadContent := markdown.MarkdownToHTML(thread.Content)
// Get replies with pagination // Get replies with pagination
perPage := 30 perPage := 30
@ -174,21 +166,17 @@ func showThread(ctx sushi.Ctx) {
replies = make([]*forum.Forum, 0) replies = make([]*forum.Forum, 0)
} }
// Build reply info with authors and processed content // Build reply info with authors
var replyInfos []PostInfo var replyInfos []PostInfo
for _, reply := range replies { for _, reply := range replies {
author, _ := users.Find(reply.Author) author, _ := users.Find(reply.Author)
if author == nil { if author == nil {
author = &users.User{Username: "[Deleted]", Level: 1, ClassID: 1} author = &users.User{Username: "[Deleted]", Level: 1, ClassID: 1}
} }
authorClass := author.Class().Name
content := markdown.MarkdownToHTML(reply.Content)
replyInfos = append(replyInfos, PostInfo{ replyInfos = append(replyInfos, PostInfo{
Post: reply, Post: reply,
Author: author, Author: author,
AuthorClass: authorClass,
Content: content,
}) })
} }
@ -198,15 +186,14 @@ func showThread(ctx sushi.Ctx) {
} }
components.RenderPage(ctx, thread.Title, "forum/thread.html", map[string]any{ components.RenderPage(ctx, thread.Title, "forum/thread.html", map[string]any{
"thread": thread, "thread": thread,
"threadContent": threadContent, "threadAuthor": threadAuthor,
"threadAuthor": threadAuthor, "replyInfos": replyInfos,
"threadAuthorClass": threadAuthorClass, "currentPage": page,
"replyInfos": replyInfos, "totalPages": totalPages,
"currentPage": page, "hasNext": page < totalPages,
"totalPages": totalPages, "hasPrev": page > 1,
"hasNext": page < totalPages, "markdown": markdown.MarkdownToHTML,
"hasPrev": page > 1,
}) })
} }

View File

@ -25,8 +25,6 @@ type TemplateFunc func(args ...any) any
var ( var (
funcRegistry = make(map[string]TemplateFunc) funcRegistry = make(map[string]TemplateFunc)
funcMutex sync.RWMutex funcMutex sync.RWMutex
methodCache = make(map[string]reflect.Method)
cacheMutex sync.RWMutex
) )
func init() { func init() {
@ -465,23 +463,9 @@ func (t *Template) callMethod(obj any, methodCall string, data map[string]any) a
argsStr := methodCall[parenIdx+1 : len(methodCall)-1] argsStr := methodCall[parenIdx+1 : len(methodCall)-1]
rv := reflect.ValueOf(obj) rv := reflect.ValueOf(obj)
objType := rv.Type() method := rv.MethodByName(methodName)
if !method.IsValid() {
// Check method cache return nil
cacheKey := fmt.Sprintf("%s.%s", objType.String(), methodName)
cacheMutex.RLock()
method, cached := methodCache[cacheKey]
cacheMutex.RUnlock()
if !cached {
method = rv.MethodByName(methodName)
if !method.IsValid() {
return nil
}
cacheMutex.Lock()
methodCache[cacheKey] = method
cacheMutex.Unlock()
} }
args := t.parseArgs(argsStr, data) args := t.parseArgs(argsStr, data)
@ -523,7 +507,7 @@ func (t *Template) parseArgs(argsStr string, data map[string]any) []any {
inQuotes := false inQuotes := false
parenLevel := 0 parenLevel := 0
for i, r := range argsStr { for _, r := range argsStr {
switch r { switch r {
case '"': case '"':
inQuotes = !inQuotes inQuotes = !inQuotes
@ -682,12 +666,20 @@ func (t *Template) getNestedValue(data map[string]any, path string) any {
var current any = data var current any = data
for i, key := range keys { for i, key := range keys {
// Check for method call // Check for method call at any point in the chain
if strings.Contains(key, "(") && strings.HasSuffix(key, ")") { if strings.Contains(key, "(") && strings.HasSuffix(key, ")") {
return t.callMethod(current, key, data) result := t.callMethod(current, key, data)
if i == len(keys)-1 {
// This was the final key, return the method result
return result
}
// Continue with the method result for further chaining
current = t.convertToStringMap(result)
continue
} }
if i == len(keys)-1 { if i == len(keys)-1 {
// Final key - get field/value
switch v := current.(type) { switch v := current.(type) {
case map[string]any: case map[string]any:
return v[key] return v[key]
@ -696,6 +688,7 @@ func (t *Template) getNestedValue(data map[string]any, path string) any {
} }
} }
// Intermediate key - get next object in chain
var next any var next any
switch v := current.(type) { switch v := current.(type) {
case map[string]any: case map[string]any:

View File

@ -13,6 +13,7 @@ import (
"dk/internal/control" "dk/internal/control"
"dk/internal/database" "dk/internal/database"
"dk/internal/helpers/markdown"
"dk/internal/models/users" "dk/internal/models/users"
"dk/internal/routes" "dk/internal/routes"
"dk/internal/template" "dk/internal/template"
@ -143,6 +144,7 @@ func start(port string) error {
defer control.Save() defer control.Save()
template.InitializeCache(cwd) template.InitializeCache(cwd)
template.RegisterFunc("md", markdown.MarkdownTemplateFunc)
authMW := auth.New(getUserByID) authMW := auth.New(getUserByID)

View File

@ -9,32 +9,32 @@
</div> </div>
{if #threadInfos > 0} {if #threadInfos > 0}
<table style="width: 100%; border-collapse: collapse;"> <table>
<thead> <thead>
<tr style="background-color: #f5f5f5;"> <tr>
<th style="padding: 10px; text-align: left; border-bottom: 1px solid #ddd;">Thread</th> <th>Thread</th>
<th style="padding: 10px; text-align: center; border-bottom: 1px solid #ddd; width: 100px;">Replies</th> <th>Replies</th>
<th style="padding: 10px; text-align: left; border-bottom: 1px solid #ddd; width: 200px;">Last Reply</th> <th>Last Reply</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{for threadInfo in threadInfos} {for threadInfo in threadInfos}
<tr style="border-bottom: 1px solid #eee;"> <tr>
<td style="padding: 10px;"> <td>
<div style="font-weight: bold; margin-bottom: 5px;"> <div>
<a href="/forum/{threadInfo.Thread.ID}" style="text-decoration: none; color: #333;">{threadInfo.Thread.Title}</a> <a href="/forum/{threadInfo.Thread.ID}">{threadInfo.Thread.Title}</a>
</div> </div>
<div style="font-size: 12px; color: #666;"> <div>
by {threadInfo.Author.Username} • {threadInfo.PostedTime} by {threadInfo.Author.Username} • {threadInfo.Thread.PostedTime().Format("Jan 2, 2006 3:04 PM")}
</div> </div>
</td> </td>
<td style="padding: 10px; text-align: center;"> <td>
{threadInfo.Thread.Replies} {threadInfo.Thread.Replies}
</td> </td>
<td style="padding: 10px; font-size: 12px; color: #666;"> <td>
{if threadInfo.LastReplyBy} {if threadInfo.LastReplyBy}
by {threadInfo.LastReplyBy.Username}<br> by {threadInfo.LastReplyBy.Username}<br>
{threadInfo.LastPostTime} {threadInfo.Thread.LastPostTime().Format("Jan 2, 2006 3:04 PM")}
{else} {else}
No replies No replies
{/if} {/if}
@ -45,13 +45,13 @@
</table> </table>
{if totalPages > 1} {if totalPages > 1}
<div style="margin-top: 20px; text-align: center;"> <div>
{if hasPrev} {if hasPrev}
<a href="/forum?page={currentPage - 1}"><button class="btn">Previous</button></a> <a href="/forum?page={currentPage - 1}"><button class="btn">Previous</button></a>
{/if} {/if}
<span style="margin: 0 10px;">Page {currentPage} of {totalPages}</span> <span>Page {currentPage} of {totalPages}</span>
{if hasNext} {if hasNext}
<a href="/forum?page={currentPage + 1}"><button class="btn">Next</button></a> <a href="/forum?page={currentPage + 1}"><button class="btn">Next</button></a>
{/if} {/if}
@ -59,8 +59,8 @@
{/if} {/if}
{else} {else}
<div style="text-align: center; padding: 40px; color: #666;"> <div>
No threads yet. <a href="/forum/new">Create the first one!</a> No threads yet. <a href="/forum/new">Create the first one!</a>
</div> </div>
{/if} {/if}
{/block} {/block}

View File

@ -10,20 +10,20 @@
</div> </div>
<!-- Original Thread Post --> <!-- Original Thread Post -->
<div style="display: flex; border: 1px solid #ddd; margin-bottom: 20px; background-color: #fafafa;"> <div>
<div style="width: 150px; padding: 15px; background-color: #f5f5f5; border-right: 1px solid #ddd;"> <div>
<div style="font-weight: bold; color: #333;">{threadAuthor.Username}</div> <div>{threadAuthor.Username}</div>
<div style="font-size: 12px; color: #666; margin-top: 5px;"> <div>
Level {threadAuthor.Level}<br> Level {threadAuthor.Level}<br>
{threadAuthorClass} {threadAuthor.Class().Name}
</div> </div>
</div> </div>
<div style="flex: 1; padding: 15px;"> <div>
<div style="margin-bottom: 10px;"> <div>
{threadContent} {md(thread.Content)}
</div> </div>
<div style="font-size: 11px; color: #888; border-top: 1px solid #eee; padding-top: 10px; margin-top: 10px;"> <div>
Posted: {threadPostedTime} Posted: {thread.PostedTime().Format("Jan 2, 2006 3:04 PM")}
</div> </div>
</div> </div>
</div> </div>
@ -31,20 +31,20 @@
<!-- Replies --> <!-- Replies -->
{if #replyInfos > 0} {if #replyInfos > 0}
{for replyInfo in replyInfos} {for replyInfo in replyInfos}
<div style="display: flex; border: 1px solid #ddd; margin-bottom: 10px;"> <div>
<div style="width: 150px; padding: 15px; background-color: #f9f9f9; border-right: 1px solid #ddd;"> <div>
<div style="font-weight: bold; color: #333;">{replyInfo.Author.Username}</div> <div>{replyInfo.Author.Username}</div>
<div style="font-size: 12px; color: #666; margin-top: 5px;"> <div>
Level {replyInfo.Author.Level}<br> Level {replyInfo.Author.Level}<br>
{replyInfo.AuthorClass} {replyInfo.Author.Class().Name}
</div> </div>
</div> </div>
<div style="flex: 1; padding: 15px;"> <div>
<div style="margin-bottom: 10px;"> <div>
{replyInfo.Content} {markdown(replyInfo.Post.Content)}
</div> </div>
<div style="font-size: 11px; color: #888; border-top: 1px solid #eee; padding-top: 10px; margin-top: 10px;"> <div>
Posted: {replyInfo.PostedTime} Posted: {replyInfo.Post.PostedTime().Format("Jan 2, 2006 3:04 PM")}
</div> </div>
</div> </div>
</div> </div>
@ -53,13 +53,13 @@
<!-- Pagination --> <!-- Pagination -->
{if totalPages > 1} {if totalPages > 1}
<div style="margin-top: 20px; text-align: center;"> <div>
{if hasPrev} {if hasPrev}
<a href="/forum/{thread.ID}?page={currentPage - 1}"><button class="btn">Previous</button></a> <a href="/forum/{thread.ID}?page={currentPage - 1}"><button class="btn">Previous</button></a>
{/if} {/if}
<span style="margin: 0 10px;">Page {currentPage} of {totalPages}</span> <span>Page {currentPage} of {totalPages}</span>
{if hasNext} {if hasNext}
<a href="/forum/{thread.ID}?page={currentPage + 1}"><button class="btn">Next</button></a> <a href="/forum/{thread.ID}?page={currentPage + 1}"><button class="btn">Next</button></a>
{/if} {/if}
@ -67,7 +67,7 @@
{/if} {/if}
<!-- Reply Button at Bottom --> <!-- Reply Button at Bottom -->
<div style="margin-top: 20px; text-align: center;"> <div>
<a href="/forum/{thread.ID}/reply"><button class="btn btn-primary">Reply to Thread</button></a> <a href="/forum/{thread.ID}/reply"><button class="btn btn-primary">Reply to Thread</button></a>
</div> </div>
{/block} {/block}