diff --git a/database.go b/database.go
index fb0be30e..206ab9d2 100644
--- a/database.go
+++ b/database.go
@@ -49,6 +49,7 @@ func initDatabase() (err error) {
if err != nil {
return err
}
+ fpstore = NewForumPermsStore()
log.Print("Loading the settings.")
err = LoadSettings()
diff --git a/extend.go b/extend.go
index 02b5422d..e55a1b37 100644
--- a/extend.go
+++ b/extend.go
@@ -21,17 +21,23 @@ var hooks = map[string][]func(interface{}) interface{}{
// Hooks with a variable number of arguments
var vhooks = map[string]func(...interface{}) interface{}{
- "simple_forum_check_pre_perms": nil,
- "forum_check_pre_perms": nil,
- "intercept_build_widgets": nil,
- "forum_trow_assign": nil,
- "topics_topic_row_assign": nil,
+ "intercept_build_widgets": nil,
+ "forum_trow_assign": nil,
+ "topics_topic_row_assign": nil,
//"topics_user_row_assign": nil,
"topic_reply_row_assign": nil,
"create_group_preappend": nil, // What is this? Investigate!
"topic_create_pre_loop": nil,
}
+// Hooks with a variable number of arguments and return values for skipping the parent function and propagating an error upwards
+var vhookSkippable = map[string]func(...interface{}) (bool, RouteError){
+ "simple_forum_check_pre_perms": nil,
+ "forum_check_pre_perms": nil,
+}
+
+//var vhookErrorable = map[string]func(...interface{}) (interface{}, RouteError){}
+
// Coming Soon:
type Message interface {
ID() int
@@ -221,6 +227,9 @@ func (plugin *Plugin) AddHook(name string, handler interface{}) {
case func(...interface{}) interface{}:
vhooks[name] = h
plugin.Hooks[name] = 0
+ case func(...interface{}) (bool, RouteError):
+ vhookSkippable[name] = h
+ plugin.Hooks[name] = 0
default:
panic("I don't recognise this kind of handler!") // Should this be an error for the plugin instead of a panic()?
}
@@ -258,6 +267,8 @@ func (plugin *Plugin) RemoveHook(name string, handler interface{}) {
preRenderHooks[name] = hook
case func(...interface{}) interface{}:
delete(vhooks, name)
+ case func(...interface{}) (bool, RouteError):
+ delete(vhookSkippable, name)
default:
panic("I don't recognise this kind of handler!") // Should this be an error for the plugin instead of a panic()?
}
@@ -302,6 +313,10 @@ func runVhook(name string, data ...interface{}) interface{} {
return vhooks[name](data...)
}
+func runVhookSkippable(name string, data ...interface{}) (bool, RouteError) {
+ return vhookSkippable[name](data...)
+}
+
func runVhookNoreturn(name string, data ...interface{}) {
_ = vhooks[name](data...)
}
diff --git a/forum_perms_store.go b/forum_perms_store.go
new file mode 100644
index 00000000..864030c7
--- /dev/null
+++ b/forum_perms_store.go
@@ -0,0 +1,19 @@
+package main
+
+var fpstore *ForumPermsStore
+
+type ForumPermsStore struct {
+}
+
+func NewForumPermsStore() *ForumPermsStore {
+ return &ForumPermsStore{}
+}
+
+func (fps *ForumPermsStore) Get(fid int, gid int) (fperms ForumPerms, err error) {
+ // TODO: Add a hook here and have plugin_socialgroups use it
+ group, err := gstore.Get(gid)
+ if err != nil {
+ return fperms, ErrNoRows
+ }
+ return group.Forums[fid], nil
+}
diff --git a/permissions.go b/permissions.go
index cb28c117..159194e2 100644
--- a/permissions.go
+++ b/permissions.go
@@ -532,8 +532,8 @@ func buildForumPermissions() error {
groups, err := gstore.GetAll()
if err != nil {
return err
-
}
+
for _, group := range groups {
if dev.DebugMode {
log.Print("Adding the forum permissions for Group #" + strconv.Itoa(group.ID) + " - " + group.Name)
diff --git a/plugin_markdown.go b/plugin_markdown.go
index 71ce1aaa..ec632b12 100644
--- a/plugin_markdown.go
+++ b/plugin_markdown.go
@@ -45,7 +45,7 @@ func deactivateMarkdown() {
// An adapter for the parser, so that the parser can call itself recursively.
// This is less for the simple Markdown elements like bold and italics and more for the really complicated ones I plan on adding at some point.
func markdownParse(msg string) string {
- msg = strings.TrimSpace(_markdownParse(msg+" ", 0))
+ msg = strings.TrimSuffix(_markdownParse(msg+" ", 0), " ")
log.Print("final msg: ", msg)
return msg
}
@@ -172,9 +172,9 @@ func _markdownParse(msg string, n int) string {
index++
- //log.Print("preskip index",index)
- //log.Print("preskip msg[index]",msg[index])
- //log.Print("preskip string(msg[index])",string(msg[index]))
+ //log.Print("preskip index: ", index)
+ //log.Print("preskip msg[index]: ", msg[index])
+ //log.Print("preskip string(msg[index]): ", string(msg[index]))
index = markdownSkipUntilAsterisk(msg, index)
if index >= len(msg) {
@@ -222,8 +222,8 @@ func _markdownParse(msg string, n int) string {
sIndex++
}
- //log.Print("sIndex",sIndex)
- //log.Print("lIndex",lIndex)
+ //log.Print("sIndex: ", sIndex)
+ //log.Print("lIndex: ", lIndex)
if lIndex <= sIndex {
//log.Print("unclosed markdown element @ lIndex <= sIndex")
@@ -241,19 +241,19 @@ func _markdownParse(msg string, n int) string {
break
}
- //log.Print("final sIndex",sIndex)
- //log.Print("final lIndex",lIndex)
- //log.Print("final index",index)
- //log.Print("final msg[index]",msg[index])
- //log.Print("final string(msg[index])",string(msg[index]))
+ //log.Print("final sIndex: ", sIndex)
+ //log.Print("final lIndex: ",lIndex)
+ //log.Print("final index: ", index)
+ //log.Print("final msg[index]: ", msg[index])
+ //log.Print("final string(msg[index]): ", string(msg[index]))
- //log.Print("final msg[sIndex]",msg[sIndex])
- //log.Print("final string(msg[sIndex])",string(msg[sIndex]))
- //log.Print("final msg[lIndex]",msg[lIndex])
- //log.Print("final string(msg[lIndex])",string(msg[lIndex]))
+ //log.Print("final msg[sIndex]: ", msg[sIndex])
+ //log.Print("final string(msg[sIndex]): ", string(msg[sIndex]))
+ //log.Print("final msg[lIndex]: ", msg[lIndex])
+ //log.Print("final string(msg[lIndex]): ", string(msg[lIndex]))
- //log.Print("[]byte(msg[:sIndex])",[]byte(msg[:sIndex]))
- //log.Print("[]byte(msg[:lIndex])",[]byte(msg[:lIndex]))
+ //log.Print("[]byte(msg[:sIndex]): ", []byte(msg[:sIndex]))
+ //log.Print("[]byte(msg[:lIndex]): ", []byte(msg[:lIndex]))
outbytes = append(outbytes, msg[lastElement:startIndex]...)
@@ -278,13 +278,13 @@ func _markdownParse(msg string, n int) string {
index--
case '\\':
if (index + 1) < len(msg) {
- outbytes = append(outbytes, msg[lastElement:index]...)
- index++
- lastElement = index
+ if isMarkdownStartChar(msg[index+1]) && msg[index+1] != '\\' {
+ outbytes = append(outbytes, msg[lastElement:index]...)
+ index++
+ lastElement = index
+ }
}
//case '`':
- //case '_':
- //case '~':
//case 10: // newline
}
}
@@ -298,6 +298,10 @@ func _markdownParse(msg string, n int) string {
return string(outbytes)
}
+func isMarkdownStartChar(char byte) bool {
+ return char == '\\' || char == '~' || char == '_' || char == 10 || char == '`' || char == '*'
+}
+
func markdownFindChar(data string, index int, char byte) bool {
for ; index < len(data); index++ {
item := data[index]
diff --git a/plugin_socialgroups.go b/plugin_socialgroups.go
index d2f0b31d..89d34a8a 100644
--- a/plugin_socialgroups.go
+++ b/plugin_socialgroups.go
@@ -561,7 +561,7 @@ func socialgroupsTopicCreatePreLoop(args ...interface{}) interface{} {
// TODO: Add privacy options
// TODO: Add support for multiple boards and add per-board simplified permissions
// TODO: Take isJs into account for routes which expect JSON responses
-func socialgroupsForumCheck(args ...interface{}) (skip interface{}) {
+func socialgroupsForumCheck(args ...interface{}) (skip bool, rerr RouteError) {
var r = args[1].(*http.Request)
var fid = args[3].(*int)
var forum = fstore.DirtyGet(*fid)
@@ -569,19 +569,14 @@ func socialgroupsForumCheck(args ...interface{}) (skip interface{}) {
if forum.ParentType == "socialgroup" {
var err error
var w = args[0].(http.ResponseWriter)
- var success = args[4].(*bool)
sgItem, ok := r.Context().Value("socialgroups_current_group").(*SocialGroup)
if !ok {
sgItem, err = socialgroupsGetGroup(forum.ParentID)
if err != nil {
- InternalError(errors.New("Unable to find the parent group for a forum"), w, r)
- *success = false
- return false
+ return true, InternalError(errors.New("Unable to find the parent group for a forum"), w, r)
}
if !sgItem.Active {
- NotFound(w, r)
- *success = false
- return false
+ return true, NotFound(w, r)
}
r = r.WithContext(context.WithValue(r.Context(), "socialgroups_current_group", sgItem))
}
@@ -600,16 +595,16 @@ func socialgroupsForumCheck(args ...interface{}) (skip interface{}) {
err = socialgroupsGetMemberStmt.QueryRow(sgItem.ID, user.ID).Scan(&rank, &posts, &joinedAt)
if err != nil && err != ErrNoRows {
- *success = false
- InternalError(err, w, r)
- return false
+ return true, InternalError(err, w, r)
} else if err != nil {
- return true
+ // TODO: Should we let admins / guests into public groups?
+ return true, LocalError("You're not part of this group!", w, r, *user)
}
// TODO: Implement bans properly by adding the Local Ban API in the next commit
+ // TODO: How does this even work? Refactor it along with the rest of this plugin!
if rank < 0 {
- return true
+ return true, LocalError("You've been banned from this group!", w, r, *user)
}
// Basic permissions for members, more complicated permissions coming in the next commit!
@@ -622,10 +617,10 @@ func socialgroupsForumCheck(args ...interface{}) (skip interface{}) {
} else {
overrideForumPerms(&user.Perms, true)
}
- return true
+ return true, nil
}
- return false
+ return false, nil
}
// TODO: Override redirects? I don't think this is needed quite yet
diff --git a/plugin_test.go b/plugin_test.go
index 8908ef80..54b8d817 100644
--- a/plugin_test.go
+++ b/plugin_test.go
@@ -214,6 +214,8 @@ func TestMarkdownRender(t *testing.T) {
msgList = addMEPair(msgList, "**hi*", "*hi")
msgList = addMEPair(msgList, "***hi***", "hi")
msgList = addMEPair(msgList, "***h***", "h")
+ msgList = addMEPair(msgList, "\\***h**\\*", "*h*")
+ msgList = addMEPair(msgList, "\\*\\**h*\\*\\*", "**h**")
msgList = addMEPair(msgList, "\\*hi\\*", "*hi*")
msgList = addMEPair(msgList, "d\\*hi\\*", "d*hi*")
msgList = addMEPair(msgList, "\\*hi\\*d", "*hi*d")
@@ -225,8 +227,10 @@ func TestMarkdownRender(t *testing.T) {
msgList = addMEPair(msgList, "\\\\\\d", "\\\\\\d")
msgList = addMEPair(msgList, "d\\", "d\\")
msgList = addMEPair(msgList, "\\d\\", "\\d\\")
+ msgList = addMEPair(msgList, "*_hi_*", "hi")
msgList = addMEPair(msgList, "*~hi~*", "hi")
msgList = addMEPair(msgList, "~*hi*~", "hi")
+ msgList = addMEPair(msgList, "~ *hi* ~", " hi ")
msgList = addMEPair(msgList, "_~hi~_", "hi")
msgList = addMEPair(msgList, "***~hi~***", "hi")
msgList = addMEPair(msgList, "**", "**")
@@ -255,4 +259,37 @@ func TestMarkdownRender(t *testing.T) {
t.Error("Expected:", item.Expects)
}
}
+
+ for _, item := range msgList {
+ res = markdownParse("\n" + item.Msg)
+ if res != "\n"+item.Expects {
+ t.Error("Testing string '\n" + item.Msg + "'")
+ t.Error("Bad output:", "'"+res+"'")
+ //t.Error("Ouput in bytes:", []byte(res))
+ t.Error("Expected:", "\n"+item.Expects)
+ }
+ }
+
+ for _, item := range msgList {
+ res = markdownParse("\t" + item.Msg)
+ if res != "\t"+item.Expects {
+ t.Error("Testing string '\t" + item.Msg + "'")
+ t.Error("Bad output:", "'"+res+"'")
+ //t.Error("Ouput in bytes:", []byte(res))
+ t.Error("Expected:", "\t"+item.Expects)
+ }
+ }
+
+ for _, item := range msgList {
+ res = markdownParse("d" + item.Msg)
+ if res != "d"+item.Expects {
+ t.Error("Testing string 'd" + item.Msg + "'")
+ t.Error("Bad output:", "'"+res+"'")
+ //t.Error("Ouput in bytes:", []byte(res))
+ t.Error("Expected:", "d"+item.Expects)
+ }
+ }
+
+ // TODO: Write suffix tests and double string tests
+ // TODO: Write similar prefix, suffix, and double string tests for plugin_bbcode. Ditto for the outer parser along with suitable tests for that like making sure the URL parser and media embedder works.
}
diff --git a/reply.go b/reply.go
index b956829e..fb10d3f6 100644
--- a/reply.go
+++ b/reply.go
@@ -6,52 +6,57 @@
*/
package main
-import "errors"
+import (
+ "errors"
+ "time"
+)
// ? - Should we add a reply store to centralise all the reply logic? Would this cover profile replies too or would that be separate?
var rstore ReplyStore
var prstore ProfileReplyStore
type ReplyUser struct {
- ID int
- ParentID int
- Content string
- ContentHtml string
- CreatedBy int
- UserLink string
- CreatedByName string
- Group int
- CreatedAt string
- LastEdit int
- LastEditBy int
- Avatar string
- ClassName string
- ContentLines int
- Tag string
- URL string
- URLPrefix string
- URLName string
- Level int
- IPAddress string
- Liked bool
- LikeCount int
- ActionType string
- ActionIcon string
+ ID int
+ ParentID int
+ Content string
+ ContentHtml string
+ CreatedBy int
+ UserLink string
+ CreatedByName string
+ Group int
+ CreatedAt time.Time
+ RelativeCreatedAt string
+ LastEdit int
+ LastEditBy int
+ Avatar string
+ ClassName string
+ ContentLines int
+ Tag string
+ URL string
+ URLPrefix string
+ URLName string
+ Level int
+ IPAddress string
+ Liked bool
+ LikeCount int
+ ActionType string
+ ActionIcon string
}
type Reply struct {
- ID int
- ParentID int
- Content string
- CreatedBy int
- Group int
- CreatedAt string
- LastEdit int
- LastEditBy int
- ContentLines int
- IPAddress string
- Liked bool
- LikeCount int
+ ID int
+ ParentID int
+ Content string
+ CreatedBy int
+ Group int
+ CreatedAt time.Time
+ RelativeCreatedAt string
+ LastEdit int
+ LastEditBy int
+ ContentLines int
+ IPAddress string
+ Liked bool
+ LikeCount int
}
var ErrAlreadyLiked = errors.New("You already liked this!")
diff --git a/routes.go b/routes.go
index c7008eec..5058da76 100644
--- a/routes.go
+++ b/routes.go
@@ -533,22 +533,7 @@ func routeTopicID(w http.ResponseWriter, r *http.Request, user User) RouteError
if postGroup.IsMod || postGroup.IsAdmin {
topic.ClassName = config.StaffCSS
}
-
- /*if headerVars.Settings["url_tags"] == false {
- topic.URLName = ""
- } else {
- topic.URL, ok = external_sites[topic.URLPrefix]
- if !ok {
- topic.URL = topic.URLName
- } else {
- topic.URL = topic.URL + topic.URLName
- }
- }*/
-
- topic.CreatedAt, err = relativeTimeFromString(topic.CreatedAt)
- if err != nil {
- topic.CreatedAt = ""
- }
+ topic.RelativeCreatedAt = relativeTime(topic.CreatedAt)
// TODO: Make a function for this? Build a more sophisticated noavatar handling system?
if topic.Avatar != "" {
@@ -604,7 +589,7 @@ func routeTopicID(w http.ResponseWriter, r *http.Request, user User) RouteError
replyItem.ClassName = ""
}
- // TODO: Make a function for this? Build a more sophisticated noavatar handling system?
+ // TODO: Make a function for this? Build a more sophisticated noavatar handling system? Do bulk user loads and let the UserStore initialise this?
if replyItem.Avatar != "" {
if replyItem.Avatar[0] == '.' {
replyItem.Avatar = "/uploads/avatar_" + strconv.Itoa(replyItem.CreatedBy) + replyItem.Avatar
@@ -614,22 +599,7 @@ func routeTopicID(w http.ResponseWriter, r *http.Request, user User) RouteError
}
replyItem.Tag = postGroup.Tag
-
- /*if headerVars.Settings["url_tags"] == false {
- replyItem.URLName = ""
- } else {
- replyItem.URL, ok = external_sites[replyItem.URLPrefix]
- if !ok {
- replyItem.URL = replyItem.URLName
- } else {
- replyItem.URL = replyItem.URL + replyItem.URLName
- }
- }*/
-
- replyItem.CreatedAt, err = relativeTimeFromString(replyItem.CreatedAt)
- if err != nil {
- replyItem.CreatedAt = ""
- }
+ replyItem.RelativeCreatedAt = relativeTime(replyItem.CreatedAt)
// We really shouldn't have inline HTML, we should do something about this...
if replyItem.ActionType != "" {
@@ -683,7 +653,8 @@ func routeProfile(w http.ResponseWriter, r *http.Request, user User) RouteError
}
var err error
- var replyContent, replyCreatedByName, replyCreatedAt, replyAvatar, replyTag, replyClassName string
+ var replyCreatedAt time.Time
+ var replyContent, replyCreatedByName, replyRelativeCreatedAt, replyAvatar, replyTag, replyClassName string
var rid, replyCreatedBy, replyLastEdit, replyLastEditBy, replyLines, replyGroup int
var replyList []ReplyUser
@@ -736,6 +707,7 @@ func routeProfile(w http.ResponseWriter, r *http.Request, user User) RouteError
} else {
replyClassName = ""
}
+
if replyAvatar != "" {
if replyAvatar[0] == '.' {
replyAvatar = "/uploads/avatar_" + strconv.Itoa(replyCreatedBy) + replyAvatar
@@ -754,10 +726,11 @@ func routeProfile(w http.ResponseWriter, r *http.Request, user User) RouteError
replyLiked := false
replyLikeCount := 0
+ replyRelativeCreatedAt = relativeTime(replyCreatedAt)
// TODO: Add a hook here
- replyList = append(replyList, ReplyUser{rid, puser.ID, replyContent, parseMessage(replyContent, 0, ""), replyCreatedBy, buildProfileURL(nameToSlug(replyCreatedByName), replyCreatedBy), replyCreatedByName, replyGroup, replyCreatedAt, replyLastEdit, replyLastEditBy, replyAvatar, replyClassName, replyLines, replyTag, "", "", "", 0, "", replyLiked, replyLikeCount, "", ""})
+ replyList = append(replyList, ReplyUser{rid, puser.ID, replyContent, parseMessage(replyContent, 0, ""), replyCreatedBy, buildProfileURL(nameToSlug(replyCreatedByName), replyCreatedBy), replyCreatedByName, replyGroup, replyCreatedAt, replyRelativeCreatedAt, replyLastEdit, replyLastEditBy, replyAvatar, replyClassName, replyLines, replyTag, "", "", "", 0, "", replyLiked, replyLikeCount, "", ""})
}
err = rows.Err()
if err != nil {
diff --git a/routes_common.go b/routes_common.go
index 804749e2..7c5550d0 100644
--- a/routes_common.go
+++ b/routes_common.go
@@ -59,51 +59,51 @@ func simpleForumUserCheck(w http.ResponseWriter, r *http.Request, user *User, fi
}
// Is there a better way of doing the skip AND the success flag on this hook like multiple returns?
- if vhooks["simple_forum_check_pre_perms"] != nil {
- if runVhook("simple_forum_check_pre_perms", w, r, user, &fid, &rerr, &headerLite).(bool) {
+ if vhookSkippable["simple_forum_check_pre_perms"] != nil {
+ var skip bool
+ skip, rerr = runVhookSkippable("simple_forum_check_pre_perms", w, r, user, &fid, &headerLite)
+ if skip {
return headerLite, rerr
}
}
- group, err := gstore.Get(user.Group)
+ fperms, err := fpstore.Get(fid, user.Group)
if err != nil {
// TODO: Refactor this
- log.Printf("Group #%d doesn't exist despite being used by User #%d", user.Group, user.ID)
+ log.Printf("Unable to get the forum perms for Group #%d for User #%d", user.Group, user.ID)
return nil, PreError("Something weird happened", w, r)
}
-
- fperms := group.Forums[fid]
cascadeForumPerms(fperms, user)
return headerLite, nil
}
-func forumUserCheck(w http.ResponseWriter, r *http.Request, user *User, fid int) (headerVars *HeaderVars, ferr RouteError) {
- headerVars, ferr = UserCheck(w, r, user)
- if ferr != nil {
- return headerVars, ferr
+func forumUserCheck(w http.ResponseWriter, r *http.Request, user *User, fid int) (headerVars *HeaderVars, rerr RouteError) {
+ headerVars, rerr = UserCheck(w, r, user)
+ if rerr != nil {
+ return headerVars, rerr
}
if !fstore.Exists(fid) {
return headerVars, NotFound(w, r)
}
- if vhooks["forum_check_pre_perms"] != nil {
- if runVhook("forum_check_pre_perms", w, r, user, &fid, &ferr, &headerVars).(bool) {
- return headerVars, ferr
+ if vhookSkippable["forum_check_pre_perms"] != nil {
+ var skip bool
+ skip, rerr = runVhookSkippable("forum_check_pre_perms", w, r, user, &fid, &headerVars)
+ if skip {
+ return headerVars, rerr
}
}
- group, err := gstore.Get(user.Group)
+ fperms, err := fpstore.Get(fid, user.Group)
if err != nil {
// TODO: Refactor this
- log.Printf("Group #%d doesn't exist despite being used by User #%d", user.Group, user.ID)
- return headerVars, PreError("Something weird happened", w, r)
+ log.Printf("Unable to get the forum perms for Group #%d for User #%d", user.Group, user.ID)
+ return nil, PreError("Something weird happened", w, r)
}
-
- fperms := group.Forums[fid]
//log.Printf("user.Perms: %+v\n", user.Perms)
//log.Printf("fperms: %+v\n", fperms)
cascadeForumPerms(fperms, user)
- return headerVars, ferr
+ return headerVars, rerr
}
// TODO: Put this on the user instance? Do we really want forum specific logic in there? Maybe, a method which spits a new pointer with the same contents as user?
diff --git a/template_init.go b/template_init.go
index 76e98032..7fa9cbe9 100644
--- a/template_init.go
+++ b/template_init.go
@@ -92,9 +92,10 @@ func compileTemplates() error {
log.Print("Compiling the templates")
- topic := TopicUser{1, "blah", "Blah", "Hey there!", 0, false, false, "Date", time.Now(), "Date", 0, "", "127.0.0.1", 0, 1, "classname", "weird-data", buildProfileURL("fake-user", 62), "Fake User", config.DefaultGroup, "", 0, "", "", "", "", "", 58, false}
+ var now = time.Now()
+ topic := TopicUser{1, "blah", "Blah", "Hey there!", 0, false, false, now, relativeTime(now), now, relativeTime(now), 0, "", "127.0.0.1", 0, 1, "classname", "weird-data", buildProfileURL("fake-user", 62), "Fake User", config.DefaultGroup, "", 0, "", "", "", "", "", 58, false}
var replyList []ReplyUser
- replyList = append(replyList, ReplyUser{0, 0, "Yo!", "Yo!", 0, "alice", "Alice", config.DefaultGroup, "", 0, 0, "", "", 0, "", "", "", "", 0, "127.0.0.1", false, 1, "", ""})
+ replyList = append(replyList, ReplyUser{0, 0, "Yo!", "Yo!", 0, "alice", "Alice", config.DefaultGroup, now, relativeTime(now), 0, 0, "", "", 0, "", "", "", "", 0, "127.0.0.1", false, 1, "", ""})
var varList = make(map[string]VarItem)
tpage := TopicPage{"Title", user, headerVars, replyList, topic, 1, 1}
diff --git a/template_list.go b/template_list.go
index 0a75deba..3b088c26 100644
--- a/template_list.go
+++ b/template_list.go
@@ -351,7 +351,7 @@ var topic_alt_29 = []byte(`
`)
-var topic_alt_54 = []byte(`
+var topic_alt_55 = []byte(`
+var topic_alt_56 = []byte(`action_item`)
+var topic_alt_57 = []byte(`">
+var topic_alt_58 = []byte(`), url(/static/white-dot.jpg);background-position: 0px -10px;">
`)
-var topic_alt_59 = []byte(`
+var topic_alt_59 = []byte(`" class="the_name" rel="author">`)
+var topic_alt_60 = []byte(`
`)
-var topic_alt_60 = []byte(``)
-var topic_alt_61 = []byte(`
`)
-var topic_alt_62 = []byte(`Level `)
-var topic_alt_63 = []byte(`
`)
-var topic_alt_64 = []byte(`
+var topic_alt_61 = []byte(``)
+var topic_alt_62 = []byte(`
`)
+var topic_alt_63 = []byte(`Level `)
+var topic_alt_64 = []byte(`
`)
+var topic_alt_65 = []byte(`
+var topic_alt_66 = []byte(`style="margin-left: 0px;"`)
+var topic_alt_67 = []byte(`>
`)
-var topic_alt_67 = []byte(`
+var topic_alt_68 = []byte(`
`)
-var topic_alt_68 = []byte(`
-
`)
var topic_alt_69 = []byte(`
+
`)
+var topic_alt_70 = []byte(`
`)
-var topic_alt_70 = []byte(`
+var topic_alt_71 = []byte(`
`)
-var topic_alt_71 = []byte(`
+var topic_alt_72 = []byte(`
`)
-var topic_alt_88 = []byte(`
+var topic_alt_90 = []byte(`
`)
-var topic_alt_89 = []byte(`
+var topic_alt_91 = []byte(`
`)
-var topic_alt_90 = []byte(`
-