diff --git a/data/dk.db b/data/dk.db index 3c31fb3..79b4a30 100644 Binary files a/data/dk.db and b/data/dk.db differ diff --git a/internal/helpers/markdown/md.go b/internal/helpers/markdown/md.go index 22c54a6..3459626 100644 --- a/internal/helpers/markdown/md.go +++ b/internal/helpers/markdown/md.go @@ -1,6 +1,7 @@ package markdown import ( + "fmt" "html" "strings" ) @@ -40,6 +41,17 @@ func MarkdownToHTML(s string) string { return `
` + s + "
" } +// 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, "`") diff --git a/internal/routes/forum.go b/internal/routes/forum.go index f744d48..7406c4e 100644 --- a/internal/routes/forum.go +++ b/internal/routes/forum.go @@ -17,16 +17,13 @@ import ( type ThreadInfo struct { Thread *forum.Forum Author *users.User - AuthorClass string LastReplyBy *users.User } // PostInfo combines a forum post/reply with its author type PostInfo struct { - Post *forum.Forum - Author *users.User - AuthorClass string - Content string // Pre-processed markdown content + Post *forum.Forum + Author *users.User } 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, + Post: reply, + Author: author, }) } @@ -198,15 +186,14 @@ 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, + "thread": thread, + "threadAuthor": threadAuthor, + "replyInfos": replyInfos, + "currentPage": page, + "totalPages": totalPages, + "hasNext": page < totalPages, + "hasPrev": page > 1, + "markdown": markdown.MarkdownToHTML, }) } diff --git a/internal/template/template.go b/internal/template/template.go index c5b5f10..3f42e63 100644 --- a/internal/template/template.go +++ b/internal/template/template.go @@ -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,23 +463,9 @@ 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) - if !method.IsValid() { - return nil - } - - cacheMutex.Lock() - methodCache[cacheKey] = method - cacheMutex.Unlock() + method := rv.MethodByName(methodName) + if !method.IsValid() { + return nil } args := t.parseArgs(argsStr, data) @@ -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: diff --git a/main.go b/main.go index df24a8b..2e8785f 100644 --- a/main.go +++ b/main.go @@ -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) diff --git a/templates/forum/index.html b/templates/forum/index.html index 0c46194..42f36fb 100644 --- a/templates/forum/index.html +++ b/templates/forum/index.html @@ -9,32 +9,32 @@ {if #threadInfos > 0} - +
- - - - + + + + {for threadInfo in threadInfos} - - + - -
ThreadRepliesLast Reply
ThreadRepliesLast Reply
-
- {threadInfo.Thread.Title} +
+
+ {threadInfo.Thread.Title}
-
- by {threadInfo.Author.Username} • {threadInfo.PostedTime} +
+ by {threadInfo.Author.Username} • {threadInfo.Thread.PostedTime().Format("Jan 2, 2006 3:04 PM")}
+ {threadInfo.Thread.Replies} + {if threadInfo.LastReplyBy} by {threadInfo.LastReplyBy.Username}
- {threadInfo.LastPostTime} + {threadInfo.Thread.LastPostTime().Format("Jan 2, 2006 3:04 PM")} {else} No replies {/if} @@ -45,13 +45,13 @@
{if totalPages > 1} -
+
{if hasPrev} {/if} - - Page {currentPage} of {totalPages} - + + Page {currentPage} of {totalPages} + {if hasNext} {/if} @@ -59,8 +59,8 @@ {/if} {else} -
+
No threads yet. Create the first one!
{/if} -{/block} +{/block} \ No newline at end of file diff --git a/templates/forum/thread.html b/templates/forum/thread.html index 63b48df..25ef39b 100644 --- a/templates/forum/thread.html +++ b/templates/forum/thread.html @@ -10,20 +10,20 @@
-
-
-
{threadAuthor.Username}
-
+
+
+
{threadAuthor.Username}
+
Level {threadAuthor.Level}
- {threadAuthorClass} + {threadAuthor.Class().Name}
-
-
- {threadContent} +
+
+ {md(thread.Content)}
-
- Posted: {threadPostedTime} +
+ Posted: {thread.PostedTime().Format("Jan 2, 2006 3:04 PM")}
@@ -31,20 +31,20 @@ {if #replyInfos > 0} {for replyInfo in replyInfos} -
-
-
{replyInfo.Author.Username}
-
+
+
+
{replyInfo.Author.Username}
+
Level {replyInfo.Author.Level}
- {replyInfo.AuthorClass} + {replyInfo.Author.Class().Name}
-
-
- {replyInfo.Content} +
+
+ {markdown(replyInfo.Post.Content)}
-
- Posted: {replyInfo.PostedTime} +
+ Posted: {replyInfo.Post.PostedTime().Format("Jan 2, 2006 3:04 PM")}
@@ -53,13 +53,13 @@ {if totalPages > 1} -
+
{if hasPrev} {/if} - - Page {currentPage} of {totalPages} - + + Page {currentPage} of {totalPages} + {if hasNext} {/if} @@ -67,7 +67,7 @@ {/if} -
+ -{/block} +{/block} \ No newline at end of file