diff --git a/common/pages.go b/common/pages.go index 446c4fbb..c68089ef 100644 --- a/common/pages.go +++ b/common/pages.go @@ -160,6 +160,18 @@ type AccountDashPage struct { NextLevel int } +type LevelListItem struct { + Level int + Score int + Status string + Percentage int // 0 to 200 to fit with the CSS logic +} + +type LevelListPage struct { + *Header + Levels []LevelListItem +} + type PanelStats struct { Users int Groups int diff --git a/common/phrases.go b/common/phrases.go index 5d0d4413..8bd245e6 100644 --- a/common/phrases.go +++ b/common/phrases.go @@ -14,6 +14,7 @@ import ( "log" "os" "path/filepath" + "strconv" "strings" "sync" "sync/atomic" @@ -141,6 +142,14 @@ func SaveLangPack(langPack *LanguagePack) error { return nil } +func GetLevelPhrase(level int) string { + levelPhrases := currentLangPack.Load().(*LanguagePack).Levels + if len(levelPhrases.Levels) > 0 && level < len(levelPhrases.Levels) { + return strings.Replace(levelPhrases.Levels[level], "{0}", strconv.Itoa(level), -1) + } + return strings.Replace(levelPhrases.Level, "{0}", strconv.Itoa(level), -1) +} + // TODO: Merge these two maps? func GetGlobalPermPhrase(name string) string { res, ok := currentLangPack.Load().(*LanguagePack).GlobalPerms[name] diff --git a/common/template_init.go b/common/template_init.go index 17614c47..07cda49e 100644 --- a/common/template_init.go +++ b/common/template_init.go @@ -511,6 +511,14 @@ func InitTemplates() error { return GetTmplPhrase(phraseName) } + fmap["level"] = func(levelInt interface{}) interface{} { + level, ok := levelInt.(int) + if !ok { + panic("levelInt is not an integer") + } + return GetLevelPhrase(level) + } + fmap["scope"] = func(name interface{}) interface{} { return "" } diff --git a/common/templates/templates.go b/common/templates/templates.go index f0964d62..0fdf75ef 100644 --- a/common/templates/templates.go +++ b/common/templates/templates.go @@ -85,6 +85,7 @@ func NewCTemplateSet() *CTemplateSet { "divide": true, "dock": true, "lang": true, + "level": true, "scope": true, }, } @@ -715,6 +716,19 @@ ArgLoop: out = "w.Write(phrases[" + strconv.Itoa(len(c.langIndexToName)-1) + "])\n" literal = true break ArgLoop + case "level": + var leftParam string + // TODO: Implement level literals + leftOperand := node.Args[pos+1].String() + if len(leftOperand) == 0 { + panic("The leftoperand for function level cannot be left blank") + } + + leftParam, _ = c.compileIfVarsub(leftOperand, varholder, templateName, holdreflect) + // TODO: Refactor this + out = "w.Write([]byte(common.GetLevelPhrase(" + leftParam + ")))\n" + literal = true + break ArgLoop case "scope": literal = true break ArgLoop diff --git a/gen_router.go b/gen_router.go index 8ec77f83..0b7de7ee 100644 --- a/gen_router.go +++ b/gen_router.go @@ -113,6 +113,7 @@ var RouteMap = map[string]interface{}{ "routes.AccountEditMFADisableSubmit": routes.AccountEditMFADisableSubmit, "routes.AccountEditEmail": routes.AccountEditEmail, "routes.AccountEditEmailTokenSubmit": routes.AccountEditEmailTokenSubmit, + "routes.LevelList": routes.LevelList, "routes.ViewProfile": routes.ViewProfile, "routes.BanUserSubmit": routes.BanUserSubmit, "routes.UnbanUser": routes.UnbanUser, @@ -244,43 +245,44 @@ var routeMapEnum = map[string]int{ "routes.AccountEditMFADisableSubmit": 87, "routes.AccountEditEmail": 88, "routes.AccountEditEmailTokenSubmit": 89, - "routes.ViewProfile": 90, - "routes.BanUserSubmit": 91, - "routes.UnbanUser": 92, - "routes.ActivateUser": 93, - "routes.IPSearch": 94, - "routes.CreateTopicSubmit": 95, - "routes.EditTopicSubmit": 96, - "routes.DeleteTopicSubmit": 97, - "routes.StickTopicSubmit": 98, - "routes.UnstickTopicSubmit": 99, - "routes.LockTopicSubmit": 100, - "routes.UnlockTopicSubmit": 101, - "routes.MoveTopicSubmit": 102, - "routes.LikeTopicSubmit": 103, - "routes.ViewTopic": 104, - "routes.CreateReplySubmit": 105, - "routes.ReplyEditSubmit": 106, - "routes.ReplyDeleteSubmit": 107, - "routes.ReplyLikeSubmit": 108, - "routes.ProfileReplyCreateSubmit": 109, - "routes.ProfileReplyEditSubmit": 110, - "routes.ProfileReplyDeleteSubmit": 111, - "routes.PollVote": 112, - "routes.PollResults": 113, - "routes.AccountLogin": 114, - "routes.AccountRegister": 115, - "routes.AccountLogout": 116, - "routes.AccountLoginSubmit": 117, - "routes.AccountLoginMFAVerify": 118, - "routes.AccountLoginMFAVerifySubmit": 119, - "routes.AccountRegisterSubmit": 120, - "routes.DynamicRoute": 121, - "routes.UploadedFile": 122, - "routes.StaticFile": 123, - "routes.RobotsTxt": 124, - "routes.SitemapXml": 125, - "routes.BadRoute": 126, + "routes.LevelList": 90, + "routes.ViewProfile": 91, + "routes.BanUserSubmit": 92, + "routes.UnbanUser": 93, + "routes.ActivateUser": 94, + "routes.IPSearch": 95, + "routes.CreateTopicSubmit": 96, + "routes.EditTopicSubmit": 97, + "routes.DeleteTopicSubmit": 98, + "routes.StickTopicSubmit": 99, + "routes.UnstickTopicSubmit": 100, + "routes.LockTopicSubmit": 101, + "routes.UnlockTopicSubmit": 102, + "routes.MoveTopicSubmit": 103, + "routes.LikeTopicSubmit": 104, + "routes.ViewTopic": 105, + "routes.CreateReplySubmit": 106, + "routes.ReplyEditSubmit": 107, + "routes.ReplyDeleteSubmit": 108, + "routes.ReplyLikeSubmit": 109, + "routes.ProfileReplyCreateSubmit": 110, + "routes.ProfileReplyEditSubmit": 111, + "routes.ProfileReplyDeleteSubmit": 112, + "routes.PollVote": 113, + "routes.PollResults": 114, + "routes.AccountLogin": 115, + "routes.AccountRegister": 116, + "routes.AccountLogout": 117, + "routes.AccountLoginSubmit": 118, + "routes.AccountLoginMFAVerify": 119, + "routes.AccountLoginMFAVerifySubmit": 120, + "routes.AccountRegisterSubmit": 121, + "routes.DynamicRoute": 122, + "routes.UploadedFile": 123, + "routes.StaticFile": 124, + "routes.RobotsTxt": 125, + "routes.SitemapXml": 126, + "routes.BadRoute": 127, } var reverseRouteMapEnum = map[int]string{ 0: "routes.Overview", @@ -373,43 +375,44 @@ var reverseRouteMapEnum = map[int]string{ 87: "routes.AccountEditMFADisableSubmit", 88: "routes.AccountEditEmail", 89: "routes.AccountEditEmailTokenSubmit", - 90: "routes.ViewProfile", - 91: "routes.BanUserSubmit", - 92: "routes.UnbanUser", - 93: "routes.ActivateUser", - 94: "routes.IPSearch", - 95: "routes.CreateTopicSubmit", - 96: "routes.EditTopicSubmit", - 97: "routes.DeleteTopicSubmit", - 98: "routes.StickTopicSubmit", - 99: "routes.UnstickTopicSubmit", - 100: "routes.LockTopicSubmit", - 101: "routes.UnlockTopicSubmit", - 102: "routes.MoveTopicSubmit", - 103: "routes.LikeTopicSubmit", - 104: "routes.ViewTopic", - 105: "routes.CreateReplySubmit", - 106: "routes.ReplyEditSubmit", - 107: "routes.ReplyDeleteSubmit", - 108: "routes.ReplyLikeSubmit", - 109: "routes.ProfileReplyCreateSubmit", - 110: "routes.ProfileReplyEditSubmit", - 111: "routes.ProfileReplyDeleteSubmit", - 112: "routes.PollVote", - 113: "routes.PollResults", - 114: "routes.AccountLogin", - 115: "routes.AccountRegister", - 116: "routes.AccountLogout", - 117: "routes.AccountLoginSubmit", - 118: "routes.AccountLoginMFAVerify", - 119: "routes.AccountLoginMFAVerifySubmit", - 120: "routes.AccountRegisterSubmit", - 121: "routes.DynamicRoute", - 122: "routes.UploadedFile", - 123: "routes.StaticFile", - 124: "routes.RobotsTxt", - 125: "routes.SitemapXml", - 126: "routes.BadRoute", + 90: "routes.LevelList", + 91: "routes.ViewProfile", + 92: "routes.BanUserSubmit", + 93: "routes.UnbanUser", + 94: "routes.ActivateUser", + 95: "routes.IPSearch", + 96: "routes.CreateTopicSubmit", + 97: "routes.EditTopicSubmit", + 98: "routes.DeleteTopicSubmit", + 99: "routes.StickTopicSubmit", + 100: "routes.UnstickTopicSubmit", + 101: "routes.LockTopicSubmit", + 102: "routes.UnlockTopicSubmit", + 103: "routes.MoveTopicSubmit", + 104: "routes.LikeTopicSubmit", + 105: "routes.ViewTopic", + 106: "routes.CreateReplySubmit", + 107: "routes.ReplyEditSubmit", + 108: "routes.ReplyDeleteSubmit", + 109: "routes.ReplyLikeSubmit", + 110: "routes.ProfileReplyCreateSubmit", + 111: "routes.ProfileReplyEditSubmit", + 112: "routes.ProfileReplyDeleteSubmit", + 113: "routes.PollVote", + 114: "routes.PollResults", + 115: "routes.AccountLogin", + 116: "routes.AccountRegister", + 117: "routes.AccountLogout", + 118: "routes.AccountLoginSubmit", + 119: "routes.AccountLoginMFAVerify", + 120: "routes.AccountLoginMFAVerifySubmit", + 121: "routes.AccountRegisterSubmit", + 122: "routes.DynamicRoute", + 123: "routes.UploadedFile", + 124: "routes.StaticFile", + 125: "routes.RobotsTxt", + 126: "routes.SitemapXml", + 127: "routes.BadRoute", } var osMapEnum = map[string]int{ "unknown": 0, @@ -708,7 +711,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { counters.GlobalViewCounter.Bump() if prefix == "/static" { - counters.RouteViewCounter.Bump(123) + counters.RouteViewCounter.Bump(124) req.URL.Path += extraData routes.StaticFile(w, req) return @@ -1586,9 +1589,18 @@ func (router *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, u counters.RouteViewCounter.Bump(89) err = routes.AccountEditEmailTokenSubmit(w,req,user,extraData) + case "/user/levels/": + err = common.MemberOnly(w,req,user) + if err != nil { + router.handleError(err,w,req,user) + return + } + + counters.RouteViewCounter.Bump(90) + err = routes.LevelList(w,req,user) default: req.URL.Path += extraData - counters.RouteViewCounter.Bump(90) + counters.RouteViewCounter.Bump(91) err = routes.ViewProfile(w,req,user) } case "/users": @@ -1606,7 +1618,7 @@ func (router *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, u return } - counters.RouteViewCounter.Bump(91) + counters.RouteViewCounter.Bump(92) err = routes.BanUserSubmit(w,req,user,extraData) case "/users/unban/": err = common.NoSessionMismatch(w,req,user) @@ -1621,7 +1633,7 @@ func (router *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, u return } - counters.RouteViewCounter.Bump(92) + counters.RouteViewCounter.Bump(93) err = routes.UnbanUser(w,req,user,extraData) case "/users/activate/": err = common.NoSessionMismatch(w,req,user) @@ -1636,7 +1648,7 @@ func (router *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, u return } - counters.RouteViewCounter.Bump(93) + counters.RouteViewCounter.Bump(94) err = routes.ActivateUser(w,req,user,extraData) case "/users/ips/": err = common.MemberOnly(w,req,user) @@ -1645,7 +1657,7 @@ func (router *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, u return } - counters.RouteViewCounter.Bump(94) + counters.RouteViewCounter.Bump(95) err = routes.IPSearch(w,req,user) } case "/topic": @@ -1668,7 +1680,7 @@ func (router *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, u return } - counters.RouteViewCounter.Bump(95) + counters.RouteViewCounter.Bump(96) err = routes.CreateTopicSubmit(w,req,user) case "/topic/edit/submit/": err = common.NoSessionMismatch(w,req,user) @@ -1683,7 +1695,7 @@ func (router *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, u return } - counters.RouteViewCounter.Bump(96) + counters.RouteViewCounter.Bump(97) err = routes.EditTopicSubmit(w,req,user,extraData) case "/topic/delete/submit/": err = common.NoSessionMismatch(w,req,user) @@ -1699,7 +1711,7 @@ func (router *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, u } req.URL.Path += extraData - counters.RouteViewCounter.Bump(97) + counters.RouteViewCounter.Bump(98) err = routes.DeleteTopicSubmit(w,req,user) case "/topic/stick/submit/": err = common.NoSessionMismatch(w,req,user) @@ -1714,7 +1726,7 @@ func (router *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, u return } - counters.RouteViewCounter.Bump(98) + counters.RouteViewCounter.Bump(99) err = routes.StickTopicSubmit(w,req,user,extraData) case "/topic/unstick/submit/": err = common.NoSessionMismatch(w,req,user) @@ -1729,7 +1741,7 @@ func (router *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, u return } - counters.RouteViewCounter.Bump(99) + counters.RouteViewCounter.Bump(100) err = routes.UnstickTopicSubmit(w,req,user,extraData) case "/topic/lock/submit/": err = common.NoSessionMismatch(w,req,user) @@ -1745,7 +1757,7 @@ func (router *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, u } req.URL.Path += extraData - counters.RouteViewCounter.Bump(100) + counters.RouteViewCounter.Bump(101) err = routes.LockTopicSubmit(w,req,user) case "/topic/unlock/submit/": err = common.NoSessionMismatch(w,req,user) @@ -1760,7 +1772,7 @@ func (router *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, u return } - counters.RouteViewCounter.Bump(101) + counters.RouteViewCounter.Bump(102) err = routes.UnlockTopicSubmit(w,req,user,extraData) case "/topic/move/submit/": err = common.NoSessionMismatch(w,req,user) @@ -1775,7 +1787,7 @@ func (router *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, u return } - counters.RouteViewCounter.Bump(102) + counters.RouteViewCounter.Bump(103) err = routes.MoveTopicSubmit(w,req,user,extraData) case "/topic/like/submit/": err = common.NoSessionMismatch(w,req,user) @@ -1796,10 +1808,10 @@ func (router *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, u return } - counters.RouteViewCounter.Bump(103) + counters.RouteViewCounter.Bump(104) err = routes.LikeTopicSubmit(w,req,user,extraData) default: - counters.RouteViewCounter.Bump(104) + counters.RouteViewCounter.Bump(105) err = routes.ViewTopic(w,req,user, extraData) } case "/reply": @@ -1822,7 +1834,7 @@ func (router *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, u return } - counters.RouteViewCounter.Bump(105) + counters.RouteViewCounter.Bump(106) err = routes.CreateReplySubmit(w,req,user) case "/reply/edit/submit/": err = common.NoSessionMismatch(w,req,user) @@ -1837,7 +1849,7 @@ func (router *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, u return } - counters.RouteViewCounter.Bump(106) + counters.RouteViewCounter.Bump(107) err = routes.ReplyEditSubmit(w,req,user,extraData) case "/reply/delete/submit/": err = common.NoSessionMismatch(w,req,user) @@ -1852,7 +1864,7 @@ func (router *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, u return } - counters.RouteViewCounter.Bump(107) + counters.RouteViewCounter.Bump(108) err = routes.ReplyDeleteSubmit(w,req,user,extraData) case "/reply/like/submit/": err = common.NoSessionMismatch(w,req,user) @@ -1873,7 +1885,7 @@ func (router *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, u return } - counters.RouteViewCounter.Bump(108) + counters.RouteViewCounter.Bump(109) err = routes.ReplyLikeSubmit(w,req,user,extraData) } case "/profile": @@ -1891,7 +1903,7 @@ func (router *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, u return } - counters.RouteViewCounter.Bump(109) + counters.RouteViewCounter.Bump(110) err = routes.ProfileReplyCreateSubmit(w,req,user) case "/profile/reply/edit/submit/": err = common.NoSessionMismatch(w,req,user) @@ -1906,7 +1918,7 @@ func (router *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, u return } - counters.RouteViewCounter.Bump(110) + counters.RouteViewCounter.Bump(111) err = routes.ProfileReplyEditSubmit(w,req,user,extraData) case "/profile/reply/delete/submit/": err = common.NoSessionMismatch(w,req,user) @@ -1921,7 +1933,7 @@ func (router *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, u return } - counters.RouteViewCounter.Bump(111) + counters.RouteViewCounter.Bump(112) err = routes.ProfileReplyDeleteSubmit(w,req,user,extraData) } case "/poll": @@ -1939,19 +1951,19 @@ func (router *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, u return } - counters.RouteViewCounter.Bump(112) + counters.RouteViewCounter.Bump(113) err = routes.PollVote(w,req,user,extraData) case "/poll/results/": - counters.RouteViewCounter.Bump(113) + counters.RouteViewCounter.Bump(114) err = routes.PollResults(w,req,user,extraData) } case "/accounts": switch(req.URL.Path) { case "/accounts/login/": - counters.RouteViewCounter.Bump(114) + counters.RouteViewCounter.Bump(115) err = routes.AccountLogin(w,req,user) case "/accounts/create/": - counters.RouteViewCounter.Bump(115) + counters.RouteViewCounter.Bump(116) err = routes.AccountRegister(w,req,user) case "/accounts/logout/": err = common.NoSessionMismatch(w,req,user) @@ -1966,7 +1978,7 @@ func (router *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, u return } - counters.RouteViewCounter.Bump(116) + counters.RouteViewCounter.Bump(117) err = routes.AccountLogout(w,req,user) case "/accounts/login/submit/": err = common.ParseForm(w,req,user) @@ -1975,10 +1987,10 @@ func (router *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, u return } - counters.RouteViewCounter.Bump(117) + counters.RouteViewCounter.Bump(118) err = routes.AccountLoginSubmit(w,req,user) case "/accounts/mfa_verify/": - counters.RouteViewCounter.Bump(118) + counters.RouteViewCounter.Bump(119) err = routes.AccountLoginMFAVerify(w,req,user) case "/accounts/mfa_verify/submit/": err = common.ParseForm(w,req,user) @@ -1987,7 +1999,7 @@ func (router *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, u return } - counters.RouteViewCounter.Bump(119) + counters.RouteViewCounter.Bump(120) err = routes.AccountLoginMFAVerifySubmit(w,req,user) case "/accounts/create/submit/": err = common.ParseForm(w,req,user) @@ -1996,7 +2008,7 @@ func (router *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, u return } - counters.RouteViewCounter.Bump(120) + counters.RouteViewCounter.Bump(121) err = routes.AccountRegisterSubmit(w,req,user) } /*case "/sitemaps": // TODO: Count these views @@ -2013,7 +2025,7 @@ func (router *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, u w.Header().Del("Content-Type") w.Header().Del("Content-Encoding") } - counters.RouteViewCounter.Bump(122) + counters.RouteViewCounter.Bump(123) req.URL.Path += extraData // TODO: Find a way to propagate errors up from this? router.UploadHandler(w,req) // TODO: Count these views @@ -2023,14 +2035,14 @@ func (router *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, u // TODO: Add support for favicons and robots.txt files switch(extraData) { case "robots.txt": - counters.RouteViewCounter.Bump(124) + counters.RouteViewCounter.Bump(125) err = routes.RobotsTxt(w,req) if err != nil { router.handleError(err,w,req,user) } return /*case "sitemap.xml": - counters.RouteViewCounter.Bump(125) + counters.RouteViewCounter.Bump(126) err = routes.SitemapXml(w,req) if err != nil { router.handleError(err,w,req,user) @@ -2046,7 +2058,7 @@ func (router *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, u router.RUnlock() if ok { - counters.RouteViewCounter.Bump(121) // TODO: Be more specific about *which* dynamic route it is + counters.RouteViewCounter.Bump(122) // TODO: Be more specific about *which* dynamic route it is req.URL.Path += extraData err = handle(w,req,user) if err != nil { @@ -2061,7 +2073,7 @@ func (router *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, u } else { router.DumpRequest(req,"Bad Route") } - counters.RouteViewCounter.Bump(126) + counters.RouteViewCounter.Bump(127) common.NotFound(w,req,nil) return } diff --git a/langs/english.json b/langs/english.json index 0a7e34e9..ae9b7b27 100644 --- a/langs/english.json +++ b/langs/english.json @@ -2,7 +2,7 @@ "Name": "english", "Levels": { - "Level": "Level {0}", + "Level": "Level {0}", "LevelMax": "" }, @@ -361,7 +361,6 @@ "topic.plus":"+", "topic.plus_one":"+1", "topic.gap_up":" up", - "topic.level":"Level", "topic.edit_button_text":"Edit", "topic.delete_button_text":"Delete", "topic.ip_button_text":"IP", @@ -448,7 +447,6 @@ "account_dash_2fa_setup":"Setup your two-factor authentication.", "account_dash_2fa_manage":"Remove or manage your two-factor authentication.", - "account_dash_level":"Level %d", "account_dash_security_notice":"Security", "account_avatar_select":"Select", "account_avatar_update_button":"Upload", @@ -596,7 +594,6 @@ "topic.reply_add_poll_button":"Add Poll", "topic.reply_add_file_button":"Add File", - "topic.level_prefix":"Level ", "topic.your_information":"Your information", "paginator_less_than":"<", diff --git a/router_gen/routes.go b/router_gen/routes.go index 0aff7d11..98802c5b 100644 --- a/router_gen/routes.go +++ b/router_gen/routes.go @@ -48,6 +48,7 @@ func buildUserRoutes() { userGroup := newRouteGroup("/user/") userGroup.Routes( View("routes.ViewProfile", "/user/").LitBefore("req.URL.Path += extraData"), + MemberView("routes.AccountEdit", "/user/edit/"), MemberView("routes.AccountEditPassword", "/user/edit/password/"), Action("routes.AccountEditPasswordSubmit", "/user/edit/password/submit/"), // TODO: Full test this @@ -59,6 +60,8 @@ func buildUserRoutes() { Action("routes.AccountEditMFADisableSubmit", "/user/edit/mfa/disable/submit/"), MemberView("routes.AccountEditEmail", "/user/edit/email/"), Action("routes.AccountEditEmailTokenSubmit", "/user/edit/token/", "extraData"), + + MemberView("routes.LevelList", "/user/levels/"), ) addRouteGroup(userGroup) diff --git a/routes/account.go b/routes/account.go index d3196d56..848443ce 100644 --- a/routes/account.go +++ b/routes/account.go @@ -7,6 +7,7 @@ import ( "encoding/hex" "io" "log" + "math" "net/http" "os" "regexp" @@ -761,3 +762,37 @@ func AccountEditEmailTokenSubmit(w http.ResponseWriter, r *http.Request, user co return nil } + +func LevelList(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { + header, ferr := common.UserCheck(w, r, &user) + if ferr != nil { + return ferr + } + header.Title = "Level Progress" + + var fScores = common.GetLevels(20) + var levels = make([]common.LevelListItem, len(fScores)) + for i, fScore := range fScores { + var status string + if user.Level > i { + status = "complete" + } else if user.Level < i { + status = "future" + } else { + status = "inprogress" + } + iScore := int(math.Ceil(fScore)) + perc := int(math.Ceil((fScore / float64(user.Score)) * 100)) + levels[i] = common.LevelListItem{i, iScore, status, perc * 2} + } + + pi := common.LevelListPage{header, levels[1:]} + if common.RunPreRenderHook("pre_render_level_list", w, r, &user, &pi) { + return nil + } + err := common.Templates.ExecuteTemplate(w, "level_list.html", pi) + if err != nil { + return common.InternalError(err, w, r) + } + return nil +} diff --git a/templates/account_own_edit.html b/templates/account_own_edit.html index ef9ce14f..106cb60d 100644 --- a/templates/account_own_edit.html +++ b/templates/account_own_edit.html @@ -21,7 +21,9 @@
{{if not .MFASetup}}{{lang "account_dash_2fa_setup"}}{{else}}{{lang "account_dash_2fa_manage"}}{{end}} {{lang "account_dash_security_notice"}}
-
Level {{.CurrentUser.Level}}: [{{.CurrentScore}} / {{.NextScore}}]
+
+ {{level .CurrentUser.Level}}: [{{.CurrentScore}} / {{.NextScore}}] +
diff --git a/templates/level_list.html b/templates/level_list.html new file mode 100644 index 00000000..1bb2ac41 --- /dev/null +++ b/templates/level_list.html @@ -0,0 +1,17 @@ +{{template "header.html" . }} +
+
+

{{.Title}}

+
+ {{range .Levels}} +
+
+
{{level .Level}}
+
+
Score: {{.Score}}
+
+
+
+ {{end}} +
+{{template "footer.html" . }} diff --git a/templates/profile.html b/templates/profile.html index 6f3be6f7..c0e245ac 100644 --- a/templates/profile.html +++ b/templates/profile.html @@ -12,10 +12,12 @@ {{.ProfileOwner.Name}}{{if .ProfileOwner.Tag}}{{.ProfileOwner.Tag}}{{end}} -
+
+
+
{{if not .CurrentUser.Loggedin}}{{else}} diff --git a/templates/topic.html b/templates/topic.html index 3f204b37..95654e75 100644 --- a/templates/topic.html +++ b/templates/topic.html @@ -75,7 +75,7 @@ - {{if .Topic.Tag}}{{.Topic.Tag}}{{else}}{{.Topic.Level}}{{end}} + {{if .Topic.Tag}}{{.Topic.Tag}}{{else}}{{level .Topic.Level}}{{end}}
diff --git a/templates/topic_alt.html b/templates/topic_alt.html index 37284469..5dd0312e 100644 --- a/templates/topic_alt.html +++ b/templates/topic_alt.html @@ -37,7 +37,7 @@
 
- {{if .Topic.Tag}}
{{else}}
{{end}} + {{if .Topic.Tag}}
{{else}}
{{end}}
@@ -68,7 +68,7 @@
 
- {{if .Topic.Tag}}
{{else}}
{{end}} + {{if .Topic.Tag}}
{{else}}
{{end}}
@@ -109,7 +109,7 @@
 
- {{if .CurrentUser.Tag}}
{{else}}
{{end}} + {{if .CurrentUser.Tag}}
{{else}}
{{end}}
diff --git a/templates/topic_alt_posts.html b/templates/topic_alt_posts.html index 8a2d39c2..9dc70117 100644 --- a/templates/topic_alt_posts.html +++ b/templates/topic_alt_posts.html @@ -3,7 +3,7 @@
 
- {{if .Tag}}
{{else}}
{{end}} + {{if .Tag}}
{{else}}
{{end}}
diff --git a/themes/nox/public/main.css b/themes/nox/public/main.css index 7bfb1416..22091f94 100644 --- a/themes/nox/public/main.css +++ b/themes/nox/public/main.css @@ -55,7 +55,9 @@ li a { padding-bottom: 21px; color: #dddddd; } -.menu_alerts .alert_bell, .menu_alerts .alert_counter, .menu_alerts:not(.selectedAlert) .alertList { +.menu_alerts .alert_bell, +.menu_alerts .alert_counter, +.menu_alerts:not(.selectedAlert) .alertList { display: none; } .alertList { @@ -70,6 +72,11 @@ li a { padding-left: 16px; padding-right: 16px; } +.alertItem { + padding: 10px; + padding-left: 8px; + padding-right: 8px; +} .alertItem.withAvatar { background: none !important; height: 66px; @@ -853,6 +860,44 @@ input[type=checkbox]:checked + label .sel { background-color: #444444; } +.level_complete, .level_future, .level_inprogress { + display: flex; +} +.level_complete { + background-color: rgb(68, 93, 68) !important; + width: 100%; +} +.level_future { + background-color: rgb(88, 68, 68) !important; + width: 100%; +} +.progressWrap { + display: flex; + margin-left: auto; +} +/* CSS behaves in stupid ways, so we need to be very specific about this */ +.rowblock:not(.topic_list):not(.rowhead):not(.opthead) .rowitem.level_inprogress:not(.post_item) { + padding: 0px; +} +.level_inprogress > div { + display: flex; + padding-top: 12px; + padding-bottom: 12px; + padding-left: 12px; + border-radius: 3px; + width: 100%; + background-color: rgb(68, 93, 68) !important; +} +.level_inprogress .progressWrap { + width: 100%; + padding-left: 0px; + padding-right: 12px; + background-color: rgb(68, 68, 68) !important; +} +.level_inprogress .progressWrap div { + margin-left: auto; +} + @media(max-width: 600px) { .rowhead h1, .opthead h1, .colstack_head h1 { font-size: 19px; diff --git a/themes/nox/public/profile.css b/themes/nox/public/profile.css index 2a27025d..efa9f2c9 100644 --- a/themes/nox/public/profile.css +++ b/themes/nox/public/profile.css @@ -23,13 +23,13 @@ .profileName { font-size: 21px; } -.topBlock, .passiveBlock { +.topBlock, .levelBlock, .passiveBlock { background-color: #444444; border-radius: 3px; width: 180px; padding: 16px; } -.passiveBlock { +.levelBlock, .passiveBlock { margin-top: 12px; padding: 12px; } diff --git a/themes/shadow/public/main.css b/themes/shadow/public/main.css index 3968c7b9..25ed83f8 100644 --- a/themes/shadow/public/main.css +++ b/themes/shadow/public/main.css @@ -316,9 +316,6 @@ a { .flag_label:before { content: "{{index .Phrases "topic.flag_button_text"}}"; } -.level_label:before { - content: "{{index .Phrases "topic.level"}}"; -} .like_count_label, .like_count { display: none; diff --git a/themes/tempra-simple/public/main.css b/themes/tempra-simple/public/main.css index 21ee0dfb..c7769180 100644 --- a/themes/tempra-simple/public/main.css +++ b/themes/tempra-simple/public/main.css @@ -707,6 +707,9 @@ button.username { color: #505050; opacity: 0.85; } +.level_hideable { + display: none; +} .controls { margin-top: 23px;