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 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 { type PanelStats struct {
Users int Users int
Groups int Groups int

View File

@ -14,6 +14,7 @@ import (
"log" "log"
"os" "os"
"path/filepath" "path/filepath"
"strconv"
"strings" "strings"
"sync" "sync"
"sync/atomic" "sync/atomic"
@ -141,6 +142,14 @@ func SaveLangPack(langPack *LanguagePack) error {
return nil 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? // TODO: Merge these two maps?
func GetGlobalPermPhrase(name string) string { func GetGlobalPermPhrase(name string) string {
res, ok := currentLangPack.Load().(*LanguagePack).GlobalPerms[name] res, ok := currentLangPack.Load().(*LanguagePack).GlobalPerms[name]

View File

@ -511,6 +511,14 @@ func InitTemplates() error {
return GetTmplPhrase(phraseName) 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{} { fmap["scope"] = func(name interface{}) interface{} {
return "" return ""
} }

View File

@ -85,6 +85,7 @@ func NewCTemplateSet() *CTemplateSet {
"divide": true, "divide": true,
"dock": true, "dock": true,
"lang": true, "lang": true,
"level": true,
"scope": true, "scope": true,
}, },
} }
@ -715,6 +716,19 @@ ArgLoop:
out = "w.Write(phrases[" + strconv.Itoa(len(c.langIndexToName)-1) + "])\n" out = "w.Write(phrases[" + strconv.Itoa(len(c.langIndexToName)-1) + "])\n"
literal = true literal = true
break ArgLoop 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": case "scope":
literal = true literal = true
break ArgLoop break ArgLoop

View File

@ -113,6 +113,7 @@ var RouteMap = map[string]interface{}{
"routes.AccountEditMFADisableSubmit": routes.AccountEditMFADisableSubmit, "routes.AccountEditMFADisableSubmit": routes.AccountEditMFADisableSubmit,
"routes.AccountEditEmail": routes.AccountEditEmail, "routes.AccountEditEmail": routes.AccountEditEmail,
"routes.AccountEditEmailTokenSubmit": routes.AccountEditEmailTokenSubmit, "routes.AccountEditEmailTokenSubmit": routes.AccountEditEmailTokenSubmit,
"routes.LevelList": routes.LevelList,
"routes.ViewProfile": routes.ViewProfile, "routes.ViewProfile": routes.ViewProfile,
"routes.BanUserSubmit": routes.BanUserSubmit, "routes.BanUserSubmit": routes.BanUserSubmit,
"routes.UnbanUser": routes.UnbanUser, "routes.UnbanUser": routes.UnbanUser,
@ -244,43 +245,44 @@ var routeMapEnum = map[string]int{
"routes.AccountEditMFADisableSubmit": 87, "routes.AccountEditMFADisableSubmit": 87,
"routes.AccountEditEmail": 88, "routes.AccountEditEmail": 88,
"routes.AccountEditEmailTokenSubmit": 89, "routes.AccountEditEmailTokenSubmit": 89,
"routes.ViewProfile": 90, "routes.LevelList": 90,
"routes.BanUserSubmit": 91, "routes.ViewProfile": 91,
"routes.UnbanUser": 92, "routes.BanUserSubmit": 92,
"routes.ActivateUser": 93, "routes.UnbanUser": 93,
"routes.IPSearch": 94, "routes.ActivateUser": 94,
"routes.CreateTopicSubmit": 95, "routes.IPSearch": 95,
"routes.EditTopicSubmit": 96, "routes.CreateTopicSubmit": 96,
"routes.DeleteTopicSubmit": 97, "routes.EditTopicSubmit": 97,
"routes.StickTopicSubmit": 98, "routes.DeleteTopicSubmit": 98,
"routes.UnstickTopicSubmit": 99, "routes.StickTopicSubmit": 99,
"routes.LockTopicSubmit": 100, "routes.UnstickTopicSubmit": 100,
"routes.UnlockTopicSubmit": 101, "routes.LockTopicSubmit": 101,
"routes.MoveTopicSubmit": 102, "routes.UnlockTopicSubmit": 102,
"routes.LikeTopicSubmit": 103, "routes.MoveTopicSubmit": 103,
"routes.ViewTopic": 104, "routes.LikeTopicSubmit": 104,
"routes.CreateReplySubmit": 105, "routes.ViewTopic": 105,
"routes.ReplyEditSubmit": 106, "routes.CreateReplySubmit": 106,
"routes.ReplyDeleteSubmit": 107, "routes.ReplyEditSubmit": 107,
"routes.ReplyLikeSubmit": 108, "routes.ReplyDeleteSubmit": 108,
"routes.ProfileReplyCreateSubmit": 109, "routes.ReplyLikeSubmit": 109,
"routes.ProfileReplyEditSubmit": 110, "routes.ProfileReplyCreateSubmit": 110,
"routes.ProfileReplyDeleteSubmit": 111, "routes.ProfileReplyEditSubmit": 111,
"routes.PollVote": 112, "routes.ProfileReplyDeleteSubmit": 112,
"routes.PollResults": 113, "routes.PollVote": 113,
"routes.AccountLogin": 114, "routes.PollResults": 114,
"routes.AccountRegister": 115, "routes.AccountLogin": 115,
"routes.AccountLogout": 116, "routes.AccountRegister": 116,
"routes.AccountLoginSubmit": 117, "routes.AccountLogout": 117,
"routes.AccountLoginMFAVerify": 118, "routes.AccountLoginSubmit": 118,
"routes.AccountLoginMFAVerifySubmit": 119, "routes.AccountLoginMFAVerify": 119,
"routes.AccountRegisterSubmit": 120, "routes.AccountLoginMFAVerifySubmit": 120,
"routes.DynamicRoute": 121, "routes.AccountRegisterSubmit": 121,
"routes.UploadedFile": 122, "routes.DynamicRoute": 122,
"routes.StaticFile": 123, "routes.UploadedFile": 123,
"routes.RobotsTxt": 124, "routes.StaticFile": 124,
"routes.SitemapXml": 125, "routes.RobotsTxt": 125,
"routes.BadRoute": 126, "routes.SitemapXml": 126,
"routes.BadRoute": 127,
} }
var reverseRouteMapEnum = map[int]string{ var reverseRouteMapEnum = map[int]string{
0: "routes.Overview", 0: "routes.Overview",
@ -373,43 +375,44 @@ var reverseRouteMapEnum = map[int]string{
87: "routes.AccountEditMFADisableSubmit", 87: "routes.AccountEditMFADisableSubmit",
88: "routes.AccountEditEmail", 88: "routes.AccountEditEmail",
89: "routes.AccountEditEmailTokenSubmit", 89: "routes.AccountEditEmailTokenSubmit",
90: "routes.ViewProfile", 90: "routes.LevelList",
91: "routes.BanUserSubmit", 91: "routes.ViewProfile",
92: "routes.UnbanUser", 92: "routes.BanUserSubmit",
93: "routes.ActivateUser", 93: "routes.UnbanUser",
94: "routes.IPSearch", 94: "routes.ActivateUser",
95: "routes.CreateTopicSubmit", 95: "routes.IPSearch",
96: "routes.EditTopicSubmit", 96: "routes.CreateTopicSubmit",
97: "routes.DeleteTopicSubmit", 97: "routes.EditTopicSubmit",
98: "routes.StickTopicSubmit", 98: "routes.DeleteTopicSubmit",
99: "routes.UnstickTopicSubmit", 99: "routes.StickTopicSubmit",
100: "routes.LockTopicSubmit", 100: "routes.UnstickTopicSubmit",
101: "routes.UnlockTopicSubmit", 101: "routes.LockTopicSubmit",
102: "routes.MoveTopicSubmit", 102: "routes.UnlockTopicSubmit",
103: "routes.LikeTopicSubmit", 103: "routes.MoveTopicSubmit",
104: "routes.ViewTopic", 104: "routes.LikeTopicSubmit",
105: "routes.CreateReplySubmit", 105: "routes.ViewTopic",
106: "routes.ReplyEditSubmit", 106: "routes.CreateReplySubmit",
107: "routes.ReplyDeleteSubmit", 107: "routes.ReplyEditSubmit",
108: "routes.ReplyLikeSubmit", 108: "routes.ReplyDeleteSubmit",
109: "routes.ProfileReplyCreateSubmit", 109: "routes.ReplyLikeSubmit",
110: "routes.ProfileReplyEditSubmit", 110: "routes.ProfileReplyCreateSubmit",
111: "routes.ProfileReplyDeleteSubmit", 111: "routes.ProfileReplyEditSubmit",
112: "routes.PollVote", 112: "routes.ProfileReplyDeleteSubmit",
113: "routes.PollResults", 113: "routes.PollVote",
114: "routes.AccountLogin", 114: "routes.PollResults",
115: "routes.AccountRegister", 115: "routes.AccountLogin",
116: "routes.AccountLogout", 116: "routes.AccountRegister",
117: "routes.AccountLoginSubmit", 117: "routes.AccountLogout",
118: "routes.AccountLoginMFAVerify", 118: "routes.AccountLoginSubmit",
119: "routes.AccountLoginMFAVerifySubmit", 119: "routes.AccountLoginMFAVerify",
120: "routes.AccountRegisterSubmit", 120: "routes.AccountLoginMFAVerifySubmit",
121: "routes.DynamicRoute", 121: "routes.AccountRegisterSubmit",
122: "routes.UploadedFile", 122: "routes.DynamicRoute",
123: "routes.StaticFile", 123: "routes.UploadedFile",
124: "routes.RobotsTxt", 124: "routes.StaticFile",
125: "routes.SitemapXml", 125: "routes.RobotsTxt",
126: "routes.BadRoute", 126: "routes.SitemapXml",
127: "routes.BadRoute",
} }
var osMapEnum = map[string]int{ var osMapEnum = map[string]int{
"unknown": 0, "unknown": 0,
@ -708,7 +711,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
counters.GlobalViewCounter.Bump() counters.GlobalViewCounter.Bump()
if prefix == "/static" { if prefix == "/static" {
counters.RouteViewCounter.Bump(123) counters.RouteViewCounter.Bump(124)
req.URL.Path += extraData req.URL.Path += extraData
routes.StaticFile(w, req) routes.StaticFile(w, req)
return return
@ -1586,9 +1589,18 @@ func (router *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, u
counters.RouteViewCounter.Bump(89) counters.RouteViewCounter.Bump(89)
err = routes.AccountEditEmailTokenSubmit(w,req,user,extraData) 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: default:
req.URL.Path += extraData req.URL.Path += extraData
counters.RouteViewCounter.Bump(90) counters.RouteViewCounter.Bump(91)
err = routes.ViewProfile(w,req,user) err = routes.ViewProfile(w,req,user)
} }
case "/users": case "/users":
@ -1606,7 +1618,7 @@ func (router *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, u
return return
} }
counters.RouteViewCounter.Bump(91) counters.RouteViewCounter.Bump(92)
err = routes.BanUserSubmit(w,req,user,extraData) err = routes.BanUserSubmit(w,req,user,extraData)
case "/users/unban/": case "/users/unban/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1621,7 +1633,7 @@ func (router *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, u
return return
} }
counters.RouteViewCounter.Bump(92) counters.RouteViewCounter.Bump(93)
err = routes.UnbanUser(w,req,user,extraData) err = routes.UnbanUser(w,req,user,extraData)
case "/users/activate/": case "/users/activate/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1636,7 +1648,7 @@ func (router *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, u
return return
} }
counters.RouteViewCounter.Bump(93) counters.RouteViewCounter.Bump(94)
err = routes.ActivateUser(w,req,user,extraData) err = routes.ActivateUser(w,req,user,extraData)
case "/users/ips/": case "/users/ips/":
err = common.MemberOnly(w,req,user) err = common.MemberOnly(w,req,user)
@ -1645,7 +1657,7 @@ func (router *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, u
return return
} }
counters.RouteViewCounter.Bump(94) counters.RouteViewCounter.Bump(95)
err = routes.IPSearch(w,req,user) err = routes.IPSearch(w,req,user)
} }
case "/topic": case "/topic":
@ -1668,7 +1680,7 @@ func (router *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, u
return return
} }
counters.RouteViewCounter.Bump(95) counters.RouteViewCounter.Bump(96)
err = routes.CreateTopicSubmit(w,req,user) err = routes.CreateTopicSubmit(w,req,user)
case "/topic/edit/submit/": case "/topic/edit/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1683,7 +1695,7 @@ func (router *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, u
return return
} }
counters.RouteViewCounter.Bump(96) counters.RouteViewCounter.Bump(97)
err = routes.EditTopicSubmit(w,req,user,extraData) err = routes.EditTopicSubmit(w,req,user,extraData)
case "/topic/delete/submit/": case "/topic/delete/submit/":
err = common.NoSessionMismatch(w,req,user) 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 req.URL.Path += extraData
counters.RouteViewCounter.Bump(97) counters.RouteViewCounter.Bump(98)
err = routes.DeleteTopicSubmit(w,req,user) err = routes.DeleteTopicSubmit(w,req,user)
case "/topic/stick/submit/": case "/topic/stick/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1714,7 +1726,7 @@ func (router *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, u
return return
} }
counters.RouteViewCounter.Bump(98) counters.RouteViewCounter.Bump(99)
err = routes.StickTopicSubmit(w,req,user,extraData) err = routes.StickTopicSubmit(w,req,user,extraData)
case "/topic/unstick/submit/": case "/topic/unstick/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1729,7 +1741,7 @@ func (router *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, u
return return
} }
counters.RouteViewCounter.Bump(99) counters.RouteViewCounter.Bump(100)
err = routes.UnstickTopicSubmit(w,req,user,extraData) err = routes.UnstickTopicSubmit(w,req,user,extraData)
case "/topic/lock/submit/": case "/topic/lock/submit/":
err = common.NoSessionMismatch(w,req,user) 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 req.URL.Path += extraData
counters.RouteViewCounter.Bump(100) counters.RouteViewCounter.Bump(101)
err = routes.LockTopicSubmit(w,req,user) err = routes.LockTopicSubmit(w,req,user)
case "/topic/unlock/submit/": case "/topic/unlock/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1760,7 +1772,7 @@ func (router *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, u
return return
} }
counters.RouteViewCounter.Bump(101) counters.RouteViewCounter.Bump(102)
err = routes.UnlockTopicSubmit(w,req,user,extraData) err = routes.UnlockTopicSubmit(w,req,user,extraData)
case "/topic/move/submit/": case "/topic/move/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1775,7 +1787,7 @@ func (router *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, u
return return
} }
counters.RouteViewCounter.Bump(102) counters.RouteViewCounter.Bump(103)
err = routes.MoveTopicSubmit(w,req,user,extraData) err = routes.MoveTopicSubmit(w,req,user,extraData)
case "/topic/like/submit/": case "/topic/like/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1796,10 +1808,10 @@ func (router *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, u
return return
} }
counters.RouteViewCounter.Bump(103) counters.RouteViewCounter.Bump(104)
err = routes.LikeTopicSubmit(w,req,user,extraData) err = routes.LikeTopicSubmit(w,req,user,extraData)
default: default:
counters.RouteViewCounter.Bump(104) counters.RouteViewCounter.Bump(105)
err = routes.ViewTopic(w,req,user, extraData) err = routes.ViewTopic(w,req,user, extraData)
} }
case "/reply": case "/reply":
@ -1822,7 +1834,7 @@ func (router *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, u
return return
} }
counters.RouteViewCounter.Bump(105) counters.RouteViewCounter.Bump(106)
err = routes.CreateReplySubmit(w,req,user) err = routes.CreateReplySubmit(w,req,user)
case "/reply/edit/submit/": case "/reply/edit/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1837,7 +1849,7 @@ func (router *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, u
return return
} }
counters.RouteViewCounter.Bump(106) counters.RouteViewCounter.Bump(107)
err = routes.ReplyEditSubmit(w,req,user,extraData) err = routes.ReplyEditSubmit(w,req,user,extraData)
case "/reply/delete/submit/": case "/reply/delete/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1852,7 +1864,7 @@ func (router *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, u
return return
} }
counters.RouteViewCounter.Bump(107) counters.RouteViewCounter.Bump(108)
err = routes.ReplyDeleteSubmit(w,req,user,extraData) err = routes.ReplyDeleteSubmit(w,req,user,extraData)
case "/reply/like/submit/": case "/reply/like/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1873,7 +1885,7 @@ func (router *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, u
return return
} }
counters.RouteViewCounter.Bump(108) counters.RouteViewCounter.Bump(109)
err = routes.ReplyLikeSubmit(w,req,user,extraData) err = routes.ReplyLikeSubmit(w,req,user,extraData)
} }
case "/profile": case "/profile":
@ -1891,7 +1903,7 @@ func (router *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, u
return return
} }
counters.RouteViewCounter.Bump(109) counters.RouteViewCounter.Bump(110)
err = routes.ProfileReplyCreateSubmit(w,req,user) err = routes.ProfileReplyCreateSubmit(w,req,user)
case "/profile/reply/edit/submit/": case "/profile/reply/edit/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1906,7 +1918,7 @@ func (router *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, u
return return
} }
counters.RouteViewCounter.Bump(110) counters.RouteViewCounter.Bump(111)
err = routes.ProfileReplyEditSubmit(w,req,user,extraData) err = routes.ProfileReplyEditSubmit(w,req,user,extraData)
case "/profile/reply/delete/submit/": case "/profile/reply/delete/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1921,7 +1933,7 @@ func (router *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, u
return return
} }
counters.RouteViewCounter.Bump(111) counters.RouteViewCounter.Bump(112)
err = routes.ProfileReplyDeleteSubmit(w,req,user,extraData) err = routes.ProfileReplyDeleteSubmit(w,req,user,extraData)
} }
case "/poll": case "/poll":
@ -1939,19 +1951,19 @@ func (router *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, u
return return
} }
counters.RouteViewCounter.Bump(112) counters.RouteViewCounter.Bump(113)
err = routes.PollVote(w,req,user,extraData) err = routes.PollVote(w,req,user,extraData)
case "/poll/results/": case "/poll/results/":
counters.RouteViewCounter.Bump(113) counters.RouteViewCounter.Bump(114)
err = routes.PollResults(w,req,user,extraData) err = routes.PollResults(w,req,user,extraData)
} }
case "/accounts": case "/accounts":
switch(req.URL.Path) { switch(req.URL.Path) {
case "/accounts/login/": case "/accounts/login/":
counters.RouteViewCounter.Bump(114) counters.RouteViewCounter.Bump(115)
err = routes.AccountLogin(w,req,user) err = routes.AccountLogin(w,req,user)
case "/accounts/create/": case "/accounts/create/":
counters.RouteViewCounter.Bump(115) counters.RouteViewCounter.Bump(116)
err = routes.AccountRegister(w,req,user) err = routes.AccountRegister(w,req,user)
case "/accounts/logout/": case "/accounts/logout/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1966,7 +1978,7 @@ func (router *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, u
return return
} }
counters.RouteViewCounter.Bump(116) counters.RouteViewCounter.Bump(117)
err = routes.AccountLogout(w,req,user) err = routes.AccountLogout(w,req,user)
case "/accounts/login/submit/": case "/accounts/login/submit/":
err = common.ParseForm(w,req,user) err = common.ParseForm(w,req,user)
@ -1975,10 +1987,10 @@ func (router *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, u
return return
} }
counters.RouteViewCounter.Bump(117) counters.RouteViewCounter.Bump(118)
err = routes.AccountLoginSubmit(w,req,user) err = routes.AccountLoginSubmit(w,req,user)
case "/accounts/mfa_verify/": case "/accounts/mfa_verify/":
counters.RouteViewCounter.Bump(118) counters.RouteViewCounter.Bump(119)
err = routes.AccountLoginMFAVerify(w,req,user) err = routes.AccountLoginMFAVerify(w,req,user)
case "/accounts/mfa_verify/submit/": case "/accounts/mfa_verify/submit/":
err = common.ParseForm(w,req,user) err = common.ParseForm(w,req,user)
@ -1987,7 +1999,7 @@ func (router *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, u
return return
} }
counters.RouteViewCounter.Bump(119) counters.RouteViewCounter.Bump(120)
err = routes.AccountLoginMFAVerifySubmit(w,req,user) err = routes.AccountLoginMFAVerifySubmit(w,req,user)
case "/accounts/create/submit/": case "/accounts/create/submit/":
err = common.ParseForm(w,req,user) err = common.ParseForm(w,req,user)
@ -1996,7 +2008,7 @@ func (router *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, u
return return
} }
counters.RouteViewCounter.Bump(120) counters.RouteViewCounter.Bump(121)
err = routes.AccountRegisterSubmit(w,req,user) err = routes.AccountRegisterSubmit(w,req,user)
} }
/*case "/sitemaps": // TODO: Count these views /*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-Type")
w.Header().Del("Content-Encoding") w.Header().Del("Content-Encoding")
} }
counters.RouteViewCounter.Bump(122) counters.RouteViewCounter.Bump(123)
req.URL.Path += extraData req.URL.Path += extraData
// TODO: Find a way to propagate errors up from this? // TODO: Find a way to propagate errors up from this?
router.UploadHandler(w,req) // TODO: Count these views 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 // TODO: Add support for favicons and robots.txt files
switch(extraData) { switch(extraData) {
case "robots.txt": case "robots.txt":
counters.RouteViewCounter.Bump(124) counters.RouteViewCounter.Bump(125)
err = routes.RobotsTxt(w,req) err = routes.RobotsTxt(w,req)
if err != nil { if err != nil {
router.handleError(err,w,req,user) router.handleError(err,w,req,user)
} }
return return
/*case "sitemap.xml": /*case "sitemap.xml":
counters.RouteViewCounter.Bump(125) counters.RouteViewCounter.Bump(126)
err = routes.SitemapXml(w,req) err = routes.SitemapXml(w,req)
if err != nil { if err != nil {
router.handleError(err,w,req,user) router.handleError(err,w,req,user)
@ -2046,7 +2058,7 @@ func (router *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, u
router.RUnlock() router.RUnlock()
if ok { 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 req.URL.Path += extraData
err = handle(w,req,user) err = handle(w,req,user)
if err != nil { if err != nil {
@ -2061,7 +2073,7 @@ func (router *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, u
} else { } else {
router.DumpRequest(req,"Bad Route") router.DumpRequest(req,"Bad Route")
} }
counters.RouteViewCounter.Bump(126) counters.RouteViewCounter.Bump(127)
common.NotFound(w,req,nil) common.NotFound(w,req,nil)
return return
} }

View File

@ -2,7 +2,7 @@
"Name": "english", "Name": "english",
"Levels": { "Levels": {
"Level": "Level {0}", "Level": "<span class='level_hideable'>Level </span>{0}",
"LevelMax": "" "LevelMax": ""
}, },
@ -361,7 +361,6 @@
"topic.plus":"+", "topic.plus":"+",
"topic.plus_one":"+1", "topic.plus_one":"+1",
"topic.gap_up":" up", "topic.gap_up":" up",
"topic.level":"Level",
"topic.edit_button_text":"Edit", "topic.edit_button_text":"Edit",
"topic.delete_button_text":"Delete", "topic.delete_button_text":"Delete",
"topic.ip_button_text":"IP", "topic.ip_button_text":"IP",
@ -448,7 +447,6 @@
"account_dash_2fa_setup":"Setup your two-factor authentication.", "account_dash_2fa_setup":"Setup your two-factor authentication.",
"account_dash_2fa_manage":"Remove or manage 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_dash_security_notice":"Security",
"account_avatar_select":"Select", "account_avatar_select":"Select",
"account_avatar_update_button":"Upload", "account_avatar_update_button":"Upload",
@ -596,7 +594,6 @@
"topic.reply_add_poll_button":"Add Poll", "topic.reply_add_poll_button":"Add Poll",
"topic.reply_add_file_button":"Add File", "topic.reply_add_file_button":"Add File",
"topic.level_prefix":"Level ",
"topic.your_information":"Your information", "topic.your_information":"Your information",
"paginator_less_than":"&lt;", "paginator_less_than":"&lt;",

View File

@ -48,6 +48,7 @@ func buildUserRoutes() {
userGroup := newRouteGroup("/user/") userGroup := newRouteGroup("/user/")
userGroup.Routes( userGroup.Routes(
View("routes.ViewProfile", "/user/").LitBefore("req.URL.Path += extraData"), View("routes.ViewProfile", "/user/").LitBefore("req.URL.Path += extraData"),
MemberView("routes.AccountEdit", "/user/edit/"), MemberView("routes.AccountEdit", "/user/edit/"),
MemberView("routes.AccountEditPassword", "/user/edit/password/"), MemberView("routes.AccountEditPassword", "/user/edit/password/"),
Action("routes.AccountEditPasswordSubmit", "/user/edit/password/submit/"), // TODO: Full test this 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/"), Action("routes.AccountEditMFADisableSubmit", "/user/edit/mfa/disable/submit/"),
MemberView("routes.AccountEditEmail", "/user/edit/email/"), MemberView("routes.AccountEditEmail", "/user/edit/email/"),
Action("routes.AccountEditEmailTokenSubmit", "/user/edit/token/", "extraData"), Action("routes.AccountEditEmailTokenSubmit", "/user/edit/token/", "extraData"),
MemberView("routes.LevelList", "/user/levels/"),
) )
addRouteGroup(userGroup) addRouteGroup(userGroup)

View File

@ -7,6 +7,7 @@ import (
"encoding/hex" "encoding/hex"
"io" "io"
"log" "log"
"math"
"net/http" "net/http"
"os" "os"
"regexp" "regexp"
@ -761,3 +762,37 @@ func AccountEditEmailTokenSubmit(w http.ResponseWriter, r *http.Request, user co
return nil 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>
<div id="dash_right" class="coldyn_item"> <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">{{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>
</div> </div>
</main> </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}} <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> </div>
<div class="passiveBlock"> <div class="levelBlock">
<div class="rowitem passive"> <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>
<div class="passiveBlock">
{{if not .CurrentUser.Loggedin}}<div class="rowitem passive"> {{if not .CurrentUser.Loggedin}}<div class="rowitem passive">
<a class="profile_menu_item">{{lang "profile_login_for_options"}}</a> <a class="profile_menu_item">{{lang "profile_login_for_options"}}</a>
</div>{{else}} </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> <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> </span>
</div> </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="avatar_item" style="background-image: url({{.Topic.Avatar}}), url(/static/white-dot.jpg);background-position: 0px -10px;">&nbsp;</div>
<div class="user_meta"> <div class="user_meta">
<a href="{{.Topic.UserLink}}" class="the_name" rel="author">{{.Topic.CreatedByName}}</a> <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> </div>
<div id="poll_voter_{{.Poll.ID}}" class="content_container poll_voter"> <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="avatar_item" style="background-image: url({{.Topic.Avatar}}), url(/static/white-dot.jpg);background-position: 0px -10px;">&nbsp;</div>
<div class="user_meta"> <div class="user_meta">
<a href="{{.Topic.UserLink}}" class="the_name" rel="author">{{.Topic.CreatedByName}}</a> <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> </div>
<div class="content_container"> <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="avatar_item" style="background-image: url({{.CurrentUser.Avatar}}), url(/static/white-dot.jpg);background-position: 0px -10px;">&nbsp;</div>
<div class="user_meta"> <div class="user_meta">
<a href="{{.CurrentUser.Link}}" class="the_name" rel="author">{{.CurrentUser.Name}}</a> <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> </div>
<div class="rowblock topic_reply_form quick_create_form" aria-label="{{lang "topic.reply_aria"}}"> <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="avatar_item" style="background-image: url({{.Avatar}}), url(/static/white-dot.jpg);background-position: 0px -10px;">&nbsp;</div>
<div class="user_meta"> <div class="user_meta">
<a href="{{.UserLink}}" class="the_name" rel="author">{{.CreatedByName}}</a> <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> </div>
<div class="content_container"{{if .ActionType}} style="margin-left: 0px;"{{end}}> <div class="content_container"{{if .ActionType}} style="margin-left: 0px;"{{end}}>

View File

@ -55,7 +55,9 @@ li a {
padding-bottom: 21px; padding-bottom: 21px;
color: #dddddd; 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; display: none;
} }
.alertList { .alertList {
@ -70,6 +72,11 @@ li a {
padding-left: 16px; padding-left: 16px;
padding-right: 16px; padding-right: 16px;
} }
.alertItem {
padding: 10px;
padding-left: 8px;
padding-right: 8px;
}
.alertItem.withAvatar { .alertItem.withAvatar {
background: none !important; background: none !important;
height: 66px; height: 66px;
@ -853,6 +860,44 @@ input[type=checkbox]:checked + label .sel {
background-color: #444444; 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) { @media(max-width: 600px) {
.rowhead h1, .opthead h1, .colstack_head h1 { .rowhead h1, .opthead h1, .colstack_head h1 {
font-size: 19px; font-size: 19px;

View File

@ -23,13 +23,13 @@
.profileName { .profileName {
font-size: 21px; font-size: 21px;
} }
.topBlock, .passiveBlock { .topBlock, .levelBlock, .passiveBlock {
background-color: #444444; background-color: #444444;
border-radius: 3px; border-radius: 3px;
width: 180px; width: 180px;
padding: 16px; padding: 16px;
} }
.passiveBlock { .levelBlock, .passiveBlock {
margin-top: 12px; margin-top: 12px;
padding: 12px; padding: 12px;
} }

View File

@ -316,9 +316,6 @@ a {
.flag_label:before { .flag_label:before {
content: "{{index .Phrases "topic.flag_button_text"}}"; content: "{{index .Phrases "topic.flag_button_text"}}";
} }
.level_label:before {
content: "{{index .Phrases "topic.level"}}";
}
.like_count_label, .like_count { .like_count_label, .like_count {
display: none; display: none;

View File

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