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_48 = []byte(` up`) -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