finish fixing template method calls/chaining, simplify templates
This commit is contained in:
parent
b5e3413c63
commit
ddc5bd5f6e
BIN
data/dk.db
BIN
data/dk.db
Binary file not shown.
@ -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, "`")
|
||||||
|
@ -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,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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:
|
||||||
|
2
main.go
2
main.go
@ -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)
|
||||||
|
|
||||||
|
@ -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}
|
@ -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}
|
Loading…
x
Reference in New Issue
Block a user