From 6f45c62815b9c92fe4ac6686fb45bf258f63e0da Mon Sep 17 00:00:00 2001 From: Azareal Date: Thu, 2 Nov 2017 02:52:21 +0000 Subject: [PATCH] We made more progress with topic views on Cosora. We accidentally broke Tempra Conflux's topic view in the process and we're planning to fix it in the following commit. Added the ForumPermsStore. Fixed the creation dates of the posts. Added a skippable version of the vhooks. Social Groups now work again, although I'm planning to refactor them ;) Added more Markdown tests and fixed a few Markdown bugs. --- database.go | 1 + extend.go | 25 +++- forum_perms_store.go | 19 +++ permissions.go | 2 +- plugin_markdown.go | 48 +++--- plugin_socialgroups.go | 25 ++-- plugin_test.go | 37 +++++ reply.go | 79 +++++----- routes.go | 43 +----- routes_common.go | 38 ++--- template_init.go | 5 +- template_list.go | 203 ++++++++++++++------------ template_topic_alt.go | 134 +++++++++-------- templates/topic_alt.html | 55 ++++--- templates/topics.html | 46 +++--- themes/cosora/public/main.css | 81 ++++++++-- themes/tempra-conflux/public/main.css | 22 ++- topic.go | 6 +- 18 files changed, 521 insertions(+), 348 deletions(-) create mode 100644 forum_perms_store.go 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_30 = []byte(`+1`) +var topic_alt_31 = []byte(`" class="action_button like_item">+1`) var topic_alt_32 = []byte(`Edit`) var topic_alt_34 = []byte(`Report `) -var topic_alt_47 = []byte(``) -var topic_alt_49 = []byte(` - `) -var topic_alt_50 = []byte(` - `) -var topic_alt_51 = []byte(``) -var topic_alt_52 = []byte(``) -var topic_alt_53 = []byte(` +var topic_alt_47 = []byte(` +
+ `) +var topic_alt_48 = []byte(``) +var topic_alt_50 = []byte(` + `) +var topic_alt_51 = []byte(` + `) +var topic_alt_52 = []byte(``) +var topic_alt_53 = []byte(``) +var topic_alt_54 = []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(`" class="the_name" rel="author">`) +var topic_alt_60 = []byte(` `) -var topic_alt_60 = []byte(`
`) -var topic_alt_62 = []byte(`
`) -var topic_alt_64 = []byte(` +var topic_alt_61 = []byte(`
`) +var topic_alt_63 = []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_72 = []byte(`+1`) -var topic_alt_74 = []byte(`Edit`) -var topic_alt_76 = []byte(`Delete`) -var topic_alt_78 = []byte(` +var topic_alt_73 = []byte(``) +var topic_alt_75 = []byte(`Edit`) +var topic_alt_77 = []byte(`Delete`) +var topic_alt_79 = []byte(` Report +var topic_alt_80 = []byte(`?session=`) +var topic_alt_81 = []byte(`&type=reply" class="action_button report_item">Report `) -var topic_alt_81 = []byte(``) -var topic_alt_83 = []byte(` - `) -var topic_alt_84 = []byte(` - `) -var topic_alt_85 = []byte(``) -var topic_alt_86 = []byte(``) -var topic_alt_87 = []byte(` +var topic_alt_82 = []byte(` +
+ `) +var topic_alt_83 = []byte(``) +var topic_alt_85 = []byte(` + `) +var topic_alt_86 = []byte(` + `) +var topic_alt_87 = []byte(``) +var topic_alt_88 = []byte(``) +var topic_alt_89 = []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(` -
-
- -
-
- -
-
-
-
- - `) var topic_alt_92 = []byte(` - - -
`) -var topic_alt_93 = []byte(` +
+
+
 
+ + `) +var topic_alt_96 = []byte(`
`) +var topic_alt_98 = []byte(`
`) +var topic_alt_100 = []byte(` +
+
+
+ +
+
+ +
+
+
+
+ + `) +var topic_alt_102 = []byte(` + + +
`) +var topic_alt_103 = []byte(` +
`) -var topic_alt_94 = []byte(` +var topic_alt_104 = []byte(` @@ -738,40 +761,40 @@ var topics_10 = []byte(``) var topics_12 = []byte(`
-
-
-
+ `) var topics_13 = []byte(``) var topics_18 = []byte(` -
-
-
-
- +
+
+
+
+ +
-
-
-
- +
+
+ +
-
-
-
- - `) +
+
+ + `) var topics_19 = []byte(` - - -
`) + + +
`) var topics_20 = []byte(` - -
+ +
diff --git a/template_topic_alt.go b/template_topic_alt.go index 0f3cb999..60f3acea 100644 --- a/template_topic_alt.go +++ b/template_topic_alt.go @@ -3,8 +3,8 @@ // Code generated by Gosora. More below: /* This file was automatically generated by the software. Please don't edit it as your changes may be overwritten at any moment. */ package main -import "net/http" import "strconv" +import "net/http" // nolint func init() { @@ -182,108 +182,126 @@ w.Write(topic_alt_45) w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Session)) w.Write(topic_alt_46) } -if tmpl_topic_alt_vars.Topic.LikeCount > 0 { w.Write(topic_alt_47) -w.Write([]byte(strconv.Itoa(tmpl_topic_alt_vars.Topic.LikeCount))) +if tmpl_topic_alt_vars.Topic.LikeCount > 0 { w.Write(topic_alt_48) -} +w.Write([]byte(strconv.Itoa(tmpl_topic_alt_vars.Topic.LikeCount))) w.Write(topic_alt_49) -w.Write([]byte(tmpl_topic_alt_vars.Topic.CreatedAt)) -w.Write(topic_alt_50) -if tmpl_topic_alt_vars.CurrentUser.Perms.ViewIPs { -w.Write(topic_alt_51) -w.Write([]byte(tmpl_topic_alt_vars.Topic.IPAddress)) -w.Write(topic_alt_52) } +w.Write(topic_alt_50) +w.Write([]byte(tmpl_topic_alt_vars.Topic.RelativeCreatedAt)) +w.Write(topic_alt_51) +if tmpl_topic_alt_vars.CurrentUser.Perms.ViewIPs { +w.Write(topic_alt_52) +w.Write([]byte(tmpl_topic_alt_vars.Topic.IPAddress)) w.Write(topic_alt_53) +} +w.Write(topic_alt_54) if len(tmpl_topic_alt_vars.ItemList) != 0 { for _, item := range tmpl_topic_alt_vars.ItemList { -w.Write(topic_alt_54) -if item.ActionType != "" { w.Write(topic_alt_55) -} +if item.ActionType != "" { w.Write(topic_alt_56) -w.Write([]byte(item.Avatar)) +} w.Write(topic_alt_57) -w.Write([]byte(item.UserLink)) +w.Write([]byte(item.Avatar)) w.Write(topic_alt_58) -w.Write([]byte(item.CreatedByName)) +w.Write([]byte(item.UserLink)) w.Write(topic_alt_59) -if item.Tag != "" { +w.Write([]byte(item.CreatedByName)) w.Write(topic_alt_60) -w.Write([]byte(item.Tag)) +if item.Tag != "" { w.Write(topic_alt_61) -} else { +w.Write([]byte(item.Tag)) w.Write(topic_alt_62) -w.Write([]byte(strconv.Itoa(item.Level))) -w.Write(topic_alt_63) -} -w.Write(topic_alt_64) -if item.ActionType != "" { -w.Write(topic_alt_65) -} -w.Write(topic_alt_66) -if item.ActionType != "" { -w.Write(topic_alt_67) -w.Write([]byte(item.ActionIcon)) -w.Write(topic_alt_68) -w.Write([]byte(item.ActionType)) -w.Write(topic_alt_69) } else { +w.Write(topic_alt_63) +w.Write([]byte(strconv.Itoa(item.Level))) +w.Write(topic_alt_64) +} +w.Write(topic_alt_65) +if item.ActionType != "" { +w.Write(topic_alt_66) +} +w.Write(topic_alt_67) +if item.ActionType != "" { +w.Write(topic_alt_68) +w.Write([]byte(item.ActionIcon)) +w.Write(topic_alt_69) +w.Write([]byte(item.ActionType)) w.Write(topic_alt_70) -w.Write([]byte(item.ContentHtml)) +} else { w.Write(topic_alt_71) +w.Write([]byte(item.ContentHtml)) +w.Write(topic_alt_72) if tmpl_topic_alt_vars.CurrentUser.Loggedin { if tmpl_topic_alt_vars.CurrentUser.Perms.LikeItem { -w.Write(topic_alt_72) -w.Write([]byte(strconv.Itoa(item.ID))) w.Write(topic_alt_73) +w.Write([]byte(strconv.Itoa(item.ID))) +w.Write(topic_alt_74) } if tmpl_topic_alt_vars.CurrentUser.Perms.EditReply { -w.Write(topic_alt_74) -w.Write([]byte(strconv.Itoa(item.ID))) w.Write(topic_alt_75) +w.Write([]byte(strconv.Itoa(item.ID))) +w.Write(topic_alt_76) } if tmpl_topic_alt_vars.CurrentUser.Perms.DeleteReply { -w.Write(topic_alt_76) -w.Write([]byte(strconv.Itoa(item.ID))) w.Write(topic_alt_77) -} -w.Write(topic_alt_78) w.Write([]byte(strconv.Itoa(item.ID))) +w.Write(topic_alt_78) +} w.Write(topic_alt_79) -w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Session)) +w.Write([]byte(strconv.Itoa(item.ID))) w.Write(topic_alt_80) -} -if item.LikeCount > 0 { +w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Session)) w.Write(topic_alt_81) -w.Write([]byte(strconv.Itoa(item.LikeCount))) +} w.Write(topic_alt_82) -} +if item.LikeCount > 0 { w.Write(topic_alt_83) -w.Write([]byte(item.CreatedAt)) +w.Write([]byte(strconv.Itoa(item.LikeCount))) w.Write(topic_alt_84) -if tmpl_topic_alt_vars.CurrentUser.Perms.ViewIPs { +} w.Write(topic_alt_85) -w.Write([]byte(item.IPAddress)) +w.Write([]byte(item.RelativeCreatedAt)) w.Write(topic_alt_86) -} +if tmpl_topic_alt_vars.CurrentUser.Perms.ViewIPs { w.Write(topic_alt_87) -} +w.Write([]byte(item.IPAddress)) w.Write(topic_alt_88) } -} w.Write(topic_alt_89) -if tmpl_topic_alt_vars.CurrentUser.Perms.CreateReply { +} w.Write(topic_alt_90) -w.Write([]byte(strconv.Itoa(tmpl_topic_alt_vars.Topic.ID))) +} +} w.Write(topic_alt_91) -if tmpl_topic_alt_vars.CurrentUser.Perms.UploadFiles { +if tmpl_topic_alt_vars.CurrentUser.Perms.CreateReply { w.Write(topic_alt_92) -} +w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Avatar)) w.Write(topic_alt_93) -} +w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Link)) w.Write(topic_alt_94) +w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Name)) +w.Write(topic_alt_95) +if tmpl_topic_alt_vars.CurrentUser.Tag != "" { +w.Write(topic_alt_96) +w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Tag)) +w.Write(topic_alt_97) +} else { +w.Write(topic_alt_98) +w.Write([]byte(strconv.Itoa(tmpl_topic_alt_vars.CurrentUser.Level))) +w.Write(topic_alt_99) +} +w.Write(topic_alt_100) +w.Write([]byte(strconv.Itoa(tmpl_topic_alt_vars.Topic.ID))) +w.Write(topic_alt_101) +if tmpl_topic_alt_vars.CurrentUser.Perms.UploadFiles { +w.Write(topic_alt_102) +} +w.Write(topic_alt_103) +} +w.Write(topic_alt_104) w.Write(footer_0) if len(tmpl_topic_alt_vars.Header.Themes) != 0 { for _, item := range tmpl_topic_alt_vars.Header.Themes { diff --git a/templates/topic_alt.html b/templates/topic_alt.html index 10342263..8ceec0bb 100644 --- a/templates/topic_alt.html +++ b/templates/topic_alt.html @@ -34,7 +34,7 @@
{{if .CurrentUser.Loggedin}} - {{if .CurrentUser.Perms.LikeItem}}+1{{end}} + {{if .CurrentUser.Perms.LikeItem}}{{end}} {{if .CurrentUser.Perms.EditTopic}}Edit{{end}} {{if .CurrentUser.Perms.DeleteTopic}}Delete{{end}} {{if .CurrentUser.Perms.CloseTopic}} @@ -43,9 +43,11 @@ {{if .Topic.Sticky}}Unpin{{else}}Pin{{end}}{{end}} Report {{end}} - {{if .Topic.LikeCount}}{{end}} - {{.Topic.CreatedAt}} - {{if .CurrentUser.Perms.ViewIPs}}{{.Topic.IPAddress}}{{end}} +
+ {{if .Topic.LikeCount}}{{end}} + {{.Topic.RelativeCreatedAt}} + {{if .CurrentUser.Perms.ViewIPs}}{{.Topic.IPAddress}}{{end}} +
@@ -66,14 +68,16 @@
{{.ContentHtml}}
{{if $.CurrentUser.Loggedin}} - {{if $.CurrentUser.Perms.LikeItem}}+1{{end}} + {{if $.CurrentUser.Perms.LikeItem}}{{end}} {{if $.CurrentUser.Perms.EditReply}}Edit{{end}} {{if $.CurrentUser.Perms.DeleteReply}}Delete{{end}} Report {{end}} - {{if .LikeCount}}{{end}} - {{.CreatedAt}} - {{if $.CurrentUser.Perms.ViewIPs}}{{.IPAddress}}{{end}} +
+ {{if .LikeCount}}{{end}} + {{.RelativeCreatedAt}} + {{if $.CurrentUser.Perms.ViewIPs}}{{.IPAddress}}{{end}} +
{{end}}
@@ -82,21 +86,28 @@ {{end}}
{{if .CurrentUser.Perms.CreateReply}} -
-
- -
-
- -
+
+
+
 
+ + {{if .CurrentUser.Tag}}
{{else}}
{{end}}
-
-
- - {{if .CurrentUser.Perms.UploadFiles}} - - -
{{end}} +
+
+ +
+
+ +
+
+
+
+ + {{if .CurrentUser.Perms.UploadFiles}} + + +
{{end}} +
diff --git a/templates/topics.html b/templates/topics.html index aeeeb139..2266e4ec 100644 --- a/templates/topics.html +++ b/templates/topics.html @@ -37,32 +37,32 @@
{{if .CurrentUser.Avatar}}{{end}}
-
-
-
-
-
-
- +
+
+
+
+
+
+ +
-
-
-
- -
-
-
-
- - {{if .CurrentUser.Perms.UploadFiles}} - - -
{{end}} - +
+
+ +
+
+
+ + {{if .CurrentUser.Perms.UploadFiles}} + + +
{{end}} + +
diff --git a/themes/cosora/public/main.css b/themes/cosora/public/main.css index 0c3dd2e5..e7d3e96e 100644 --- a/themes/cosora/public/main.css +++ b/themes/cosora/public/main.css @@ -8,8 +8,10 @@ --primary-link-color: hsl(0,0%,40%); --primary-text-color: hsl(0,0%,20%); --lightened-primary-text-color: hsl(0,0%,30%); + --extra-lightened-primary-text-color: hsl(0,0%,40%); --inverse-primary-text-color: white; --light-text-color: hsl(0,0%,55%); + --lighter-text-color: hsl(0,0%,65%); } * { @@ -402,6 +404,25 @@ select, input, textarea { color: hsl(0,0%,30%); } +.topic_reply_container { + display: flex; + border: 0; +} +.topic_reply_form { + margin: 0px; + width: 100%; +} +.topic_reply_form .trumbowyg-button-pane:after { + display: none; +} +.topic_reply_form .trumbowyg-editor { + border-left: none; + border-right: none; +} +.topic_reply_form .quick_button_row { + margin-bottom: 7px; +} + #prevFloat, #nextFloat { display: none; } @@ -600,33 +621,67 @@ select, input, textarea { } .userinfo { margin-right: 16px; - padding: 18px; display: flex; flex-direction: column; - padding-left: 24px; - padding-right: 24px; - padding-bottom: 12px; + padding-top: 30px; + padding-left: 42px; + padding-right: 42px; + padding-bottom: 18px; + height: min-content; + /*overflow: hidden; + text-overflow: ellipsis;*/ } .content_container { width: 100%; - padding: 16px; + padding: 17px; + display: flex; + flex-direction: column; } .avatar_item { border-radius: 50px; - width: 90px; - height: 90px; - margin-bottom: 5px; - background-size: 128px; + width: 84px; + height: 84px; + margin-bottom: 12px; + background-size: 120px; +} +.the_name, .userinfo .tag_block { + margin-left: auto; + margin-right: auto; } .the_name { - font-size: 19px; + font-size: 18px; color: var(--lightened-primary-text-color); - margin-left: auto; - margin-right: auto; } .userinfo .tag_block { + color: var(--extra-lightened-primary-text-color); +} +.button_container { + margin-top: auto; + display: flex; +} +.action_button { + margin-right: 5px; + color: var(--light-text-color); + font-size: 14px; + display: inline-block; +} +.action_button_right { + display: inline-flex; margin-left: auto; - margin-right: auto; +} +.like_count:after { + content: " likes"; + margin-right: 6px; +} +.created_at:before, .ip_item:before { + border-left: 1px solid var(--element-border-color); + content: ""; + margin-right: 10px; + margin-top: 1px; + margin-bottom: 1px; +} +.created_at { + margin-right: 10px; } @media(max-width: 670px) { diff --git a/themes/tempra-conflux/public/main.css b/themes/tempra-conflux/public/main.css index 4211a4f4..6d1d3894 100644 --- a/themes/tempra-conflux/public/main.css +++ b/themes/tempra-conflux/public/main.css @@ -652,6 +652,9 @@ button.username { padding-right: 7px; } .action_button_right { + display: flex; +} +.action_button_right .action_button { border-left: solid 1px #eaeaea; border-right: none; } @@ -660,10 +663,21 @@ button.username { margin-right: auto; } -.like_label:before { content: "😀"; } -.edit_label:before { content: "🖊️"; } -.trash_label:before { content: "🗑️"; } -.flag_label:before { content: "🚩"; } +.like_label:before { + content: "😀"; +} +.like_count:after { + content: " up"; +} +.edit_label:before { + content: "🖊️"; +} +.trash_label:before { + content: "🗑️"; +} +.flag_label:before { + content: "🚩"; +} .mod_button { margin-right: 4px; diff --git a/topic.go b/topic.go index f43de0ea..db22fcd9 100644 --- a/topic.go +++ b/topic.go @@ -24,7 +24,8 @@ type Topic struct { CreatedBy int IsClosed bool Sticky bool - CreatedAt string + CreatedAt time.Time + RelativeCreatedAt string LastReplyAt time.Time RelativeLastReplyAt string //LastReplyBy int @@ -45,7 +46,8 @@ type TopicUser struct { CreatedBy int IsClosed bool Sticky bool - CreatedAt string + CreatedAt time.Time + RelativeCreatedAt string LastReplyAt time.Time RelativeLastReplyAt string //LastReplyBy int