Added the level list page.

Levels can now be localised individually.
Added the level template function.
Fixed the padding on alerts without avatars.
The level data is now in it's own block in the profiles.
Removed three level phrases and replaced them with the new Level API.
This commit is contained in:
Azareal 2018-10-10 17:33:51 +10:00
parent de78268b20
commit 05ab585d41
18 changed files with 285 additions and 129 deletions

View File

@ -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

View File

@ -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]

View File

@ -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 ""
}

View File

@ -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

View File

@ -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
}

View File

@ -2,7 +2,7 @@
"Name": "english",
"Levels": {
"Level": "Level {0}",
"Level": "<span class='level_hideable'>Level </span>{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":"&lt;",

View File

@ -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)

View File

@ -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
}

View File

@ -21,7 +21,9 @@
</div>
<div id="dash_right" class="coldyn_item">
<div class="rowitem">{{if not .MFASetup}}<a href="/user/edit/mfa/setup/">{{lang "account_dash_2fa_setup"}}</a>{{else}}<a href="/user/edit/mfa/">{{lang "account_dash_2fa_manage"}}</a>{{end}} <span class="dash_security">{{lang "account_dash_security_notice"}}</span></div>
<div class="rowitem">Level {{.CurrentUser.Level}}: [{{.CurrentScore}} / {{.NextScore}}] <span class="account_soon">{{lang "account_coming_soon"}}</span></div>
<div class="rowitem">
<a href="/user/levels/">{{level .CurrentUser.Level}}: [{{.CurrentScore}} / {{.NextScore}}]</a> <span class="account_soon">{{lang "account_coming_soon"}}</span>
</div>
</div>
</div>
</main>

17
templates/level_list.html Normal file
View File

@ -0,0 +1,17 @@
{{template "header.html" . }}
<main>
<div class="rowblock rowhead">
<div class="rowitem"><h1>{{.Title}}</h1></div>
</div>
{{range .Levels}}
<div class="rowblock">
<div class="rowitem passive rowmsg level_{{.Status}}">
<div>{{level .Level}}</div>
<div class="progressWrap"{{if eq .Status "inprogress"}} style="width: {{.Percentage}}%;"{{end}}>
<div>Score: {{.Score}}</div>
</div>
</div>
</div>
{{end}}
</main>
{{template "footer.html" . }}

View File

@ -12,10 +12,12 @@
<span class="profileName" title="{{.ProfileOwner.Name}}">{{.ProfileOwner.Name}}</span>{{if .ProfileOwner.Tag}}<span class="username" title="{{.ProfileOwner.Tag}}">{{.ProfileOwner.Tag}}</span>{{end}}
</div>
</div>
<div class="passiveBlock">
<div class="levelBlock">
<div class="rowitem passive">
<a class="profile_menu_item">Level {{.ProfileOwner.Level}}: [{{.CurrentScore}} / {{.NextScore}}]</a>
<a class="profile_menu_item">{{level .ProfileOwner.Level}}: [{{.CurrentScore}} / {{.NextScore}}]</a>
</div>
</div>
<div class="passiveBlock">
{{if not .CurrentUser.Loggedin}}<div class="rowitem passive">
<a class="profile_menu_item">{{lang "profile_login_for_options"}}</a>
</div>{{else}}

View File

@ -75,7 +75,7 @@
<a class="username hide_on_micro like_count" aria-label="{{lang "topic.like_count_aria"}}">{{.Topic.LikeCount}}</a><a class="username hide_on_micro like_count_label" title="{{lang "topic.like_count_tooltip"}}"></a>
{{if .Topic.Tag}}<a class="username hide_on_micro user_tag">{{.Topic.Tag}}</a>{{else}}<a class="username hide_on_micro level" aria-label="{{lang "topic.level_aria"}}">{{.Topic.Level}}</a><a class="username hide_on_micro level_label" style="float:right;" title="{{lang "topic.level_tooltip"}}"></a>{{end}}
{{if .Topic.Tag}}<a class="username hide_on_micro user_tag">{{.Topic.Tag}}</a>{{else}}<a class="username hide_on_micro level" aria-label="{{lang "topic.level_aria"}}">{{level .Topic.Level}}</a><a class="username hide_on_micro level_label" style="float:right;" title="{{lang "topic.level_tooltip"}}"></a>{{end}}
</span>
</div>

View File

@ -37,7 +37,7 @@
<div class="avatar_item" style="background-image: url({{.Topic.Avatar}}), url(/static/white-dot.jpg);background-position: 0px -10px;">&nbsp;</div>
<div class="user_meta">
<a href="{{.Topic.UserLink}}" class="the_name" rel="author">{{.Topic.CreatedByName}}</a>
{{if .Topic.Tag}}<div class="tag_block"><div class="tag_pre"></div><div class="post_tag">{{.Topic.Tag}}</div><div class="tag_post"></div></div>{{else}}<div class="tag_block"><div class="tag_pre"></div><div class="post_tag post_level">{{lang "topic.level_prefix"}}{{.Topic.Level}}</div><div class="tag_post"></div></div>{{end}}
{{if .Topic.Tag}}<div class="tag_block"><div class="tag_pre"></div><div class="post_tag">{{.Topic.Tag}}</div><div class="tag_post"></div></div>{{else}}<div class="tag_block"><div class="tag_pre"></div><div class="post_tag post_level">{{level .Topic.Level}}</div><div class="tag_post"></div></div>{{end}}
</div>
</div>
<div id="poll_voter_{{.Poll.ID}}" class="content_container poll_voter">
@ -68,7 +68,7 @@
<div class="avatar_item" style="background-image: url({{.Topic.Avatar}}), url(/static/white-dot.jpg);background-position: 0px -10px;">&nbsp;</div>
<div class="user_meta">
<a href="{{.Topic.UserLink}}" class="the_name" rel="author">{{.Topic.CreatedByName}}</a>
{{if .Topic.Tag}}<div class="tag_block"><div class="tag_pre"></div><div class="post_tag">{{.Topic.Tag}}</div><div class="tag_post"></div></div>{{else}}<div class="tag_block"><div class="tag_pre"></div><div class="post_tag post_level">{{lang "topic.level_prefix"}}{{.Topic.Level}}</div><div class="tag_post"></div></div>{{end}}
{{if .Topic.Tag}}<div class="tag_block"><div class="tag_pre"></div><div class="post_tag">{{.Topic.Tag}}</div><div class="tag_post"></div></div>{{else}}<div class="tag_block"><div class="tag_pre"></div><div class="post_tag post_level">{{level .Topic.Level}}</div><div class="tag_post"></div></div>{{end}}
</div>
</div>
<div class="content_container">
@ -109,7 +109,7 @@
<div class="avatar_item" style="background-image: url({{.CurrentUser.Avatar}}), url(/static/white-dot.jpg);background-position: 0px -10px;">&nbsp;</div>
<div class="user_meta">
<a href="{{.CurrentUser.Link}}" class="the_name" rel="author">{{.CurrentUser.Name}}</a>
{{if .CurrentUser.Tag}}<div class="tag_block"><div class="tag_pre"></div><div class="post_tag">{{.CurrentUser.Tag}}</div><div class="tag_post"></div></div>{{else}}<div class="tag_block"><div class="tag_pre"></div><div class="post_tag post_level">{{lang "topic.level_prefix"}}{{.CurrentUser.Level}}</div><div class="tag_post"></div></div>{{end}}
{{if .CurrentUser.Tag}}<div class="tag_block"><div class="tag_pre"></div><div class="post_tag">{{.CurrentUser.Tag}}</div><div class="tag_post"></div></div>{{else}}<div class="tag_block"><div class="tag_pre"></div><div class="post_tag post_level">{{level .CurrentUser.Level}}</div><div class="tag_post"></div></div>{{end}}
</div>
</div>
<div class="rowblock topic_reply_form quick_create_form" aria-label="{{lang "topic.reply_aria"}}">

View File

@ -3,7 +3,7 @@
<div class="avatar_item" style="background-image: url({{.Avatar}}), url(/static/white-dot.jpg);background-position: 0px -10px;">&nbsp;</div>
<div class="user_meta">
<a href="{{.UserLink}}" class="the_name" rel="author">{{.CreatedByName}}</a>
{{if .Tag}}<div class="tag_block"><div class="tag_pre"></div><div class="post_tag">{{.Tag}}</div><div class="tag_post"></div></div>{{else}}<div class="tag_block"><div class="tag_pre"></div><div class="post_tag post_level">{{lang "topic.level_prefix"}}{{.Level}}</div><div class="tag_post"></div></div>{{end}}
{{if .Tag}}<div class="tag_block"><div class="tag_pre"></div><div class="post_tag">{{.Tag}}</div><div class="tag_post"></div></div>{{else}}<div class="tag_block"><div class="tag_pre"></div><div class="post_tag post_level">{{level .Level}}</div><div class="tag_post"></div></div>{{end}}
</div>
</div>
<div class="content_container"{{if .ActionType}} style="margin-left: 0px;"{{end}}>

View File

@ -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;

View File

@ -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;
}

View File

@ -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;

View File

@ -707,6 +707,9 @@ button.username {
color: #505050;
opacity: 0.85;
}
.level_hideable {
display: none;
}
.controls {
margin-top: 23px;