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
import (
"fmt"
"html"
"strings"
)
@ -40,6 +41,17 @@ func MarkdownToHTML(s string) string {
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 {
for {
start := strings.Index(s, "`")

View File

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

View File

@ -25,8 +25,6 @@ type TemplateFunc func(args ...any) any
var (
funcRegistry = make(map[string]TemplateFunc)
funcMutex sync.RWMutex
methodCache = make(map[string]reflect.Method)
cacheMutex sync.RWMutex
)
func init() {
@ -465,25 +463,11 @@ func (t *Template) callMethod(obj any, methodCall string, data map[string]any) a
argsStr := methodCall[parenIdx+1 : len(methodCall)-1]
rv := reflect.ValueOf(obj)
objType := rv.Type()
// Check method cache
cacheKey := fmt.Sprintf("%s.%s", objType.String(), methodName)
cacheMutex.RLock()
method, cached := methodCache[cacheKey]
cacheMutex.RUnlock()
if !cached {
method = rv.MethodByName(methodName)
method := rv.MethodByName(methodName)
if !method.IsValid() {
return nil
}
cacheMutex.Lock()
methodCache[cacheKey] = method
cacheMutex.Unlock()
}
args := t.parseArgs(argsStr, data)
methodType := method.Type()
@ -523,7 +507,7 @@ func (t *Template) parseArgs(argsStr string, data map[string]any) []any {
inQuotes := false
parenLevel := 0
for i, r := range argsStr {
for _, r := range argsStr {
switch r {
case '"':
inQuotes = !inQuotes
@ -682,12 +666,20 @@ func (t *Template) getNestedValue(data map[string]any, path string) any {
var current any = data
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, ")") {
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 {
// Final key - get field/value
switch v := current.(type) {
case map[string]any:
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
switch v := current.(type) {
case map[string]any:

View File

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

View File

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

View File

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