Highlight the currently active zone in the menu for Nox.
Replaced the Level Progress widget in the Account Dashboard with a similar progressbar to the one in the level progress page.
This commit is contained in:
parent
c451358156
commit
aabfbe3622
|
@ -7,6 +7,7 @@ import (
|
|||
"io"
|
||||
"io/ioutil"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"../query_gen/lib"
|
||||
)
|
||||
|
@ -19,9 +20,15 @@ type MenuListHolder struct {
|
|||
Variations map[int]menuTmpl // 0 = Guest Menu, 1 = Member Menu, 2 = Super Mod Menu, 3 = Admin Menu
|
||||
}
|
||||
|
||||
type menuPath struct {
|
||||
Path string
|
||||
Index int
|
||||
}
|
||||
|
||||
type menuTmpl struct {
|
||||
RenderBuffer [][]byte
|
||||
VariableIndices []int
|
||||
PathMappings []menuPath
|
||||
}
|
||||
|
||||
type MenuItem struct {
|
||||
|
@ -135,11 +142,8 @@ func (hold *MenuListHolder) Preparse() error {
|
|||
}
|
||||
|
||||
var addVariation = func(index int, callback func(mitem MenuItem) bool) {
|
||||
renderBuffer, variableIndices := hold.Scan(tmpls, callback)
|
||||
hold.Variations[index] = menuTmpl{renderBuffer, variableIndices}
|
||||
//fmt.Print("renderBuffer: ")
|
||||
//menuDumpSlice(renderBuffer)
|
||||
//fmt.Printf("\nvariableIndices: %+v\n", variableIndices)
|
||||
renderBuffer, variableIndices, pathList := hold.Scan(tmpls, callback)
|
||||
hold.Variations[index] = menuTmpl{renderBuffer, variableIndices, pathList}
|
||||
}
|
||||
|
||||
// Guest Menu
|
||||
|
@ -202,15 +206,11 @@ func skipUntilCharsExist(tmplData []byte, i int, expects []byte) (newI int, hasI
|
|||
func skipAllUntilCharsExist(tmplData []byte, i int, expects []byte) (newI int, hasIt bool) {
|
||||
j := i
|
||||
expectIndex := 0
|
||||
//fmt.Printf("tmplData: %+v\n", string(tmplData))
|
||||
for ; j < len(tmplData) && expectIndex < len(expects); j++ {
|
||||
//fmt.Println("j: ", j)
|
||||
//fmt.Println("tmplData[j]: ", string(tmplData[j])+" ")
|
||||
if tmplData[j] == expects[expectIndex] {
|
||||
//fmt.Printf("expects[expectIndex]: %+v - %d\n", string(expects[expectIndex]), expectIndex)
|
||||
expectIndex++
|
||||
if len(expects) <= expectIndex {
|
||||
//fmt.Println("breaking")
|
||||
break
|
||||
}
|
||||
} else {
|
||||
|
@ -226,8 +226,6 @@ func skipAllUntilCharsExist(tmplData []byte, i int, expects []byte) (newI int, h
|
|||
expectIndex = 0
|
||||
}
|
||||
}
|
||||
//fmt.Println("len(expects): ", len(expects))
|
||||
//fmt.Println("expectIndex: ", expectIndex)
|
||||
return j, len(expects) == expectIndex
|
||||
}
|
||||
|
||||
|
@ -254,19 +252,16 @@ func menuDumpSlice(outerSlice [][]byte) {
|
|||
}
|
||||
|
||||
func (hold *MenuListHolder) Parse(name string, tmplData []byte) (menuTmpl MenuTmpl) {
|
||||
//fmt.Println("tmplData: ", string(tmplData))
|
||||
var textBuffer, variableBuffer [][]byte
|
||||
var renderList []menuRenderItem
|
||||
var subBuffer []byte
|
||||
|
||||
// ? We only support simple properties on MenuItem right now
|
||||
var addVariable = func(name []byte) {
|
||||
//fmt.Println("appending subBuffer: ", string(subBuffer))
|
||||
// TODO: Check if the subBuffer has any items or is empty
|
||||
textBuffer = append(textBuffer, subBuffer)
|
||||
subBuffer = nil
|
||||
|
||||
//fmt.Println("adding variable: ", string(name))
|
||||
variableBuffer = append(variableBuffer, name)
|
||||
renderList = append(renderList, menuRenderItem{0, len(textBuffer) - 1})
|
||||
renderList = append(renderList, menuRenderItem{1, len(variableBuffer) - 1})
|
||||
|
@ -277,10 +272,8 @@ func (hold *MenuListHolder) Parse(name string, tmplData []byte) (menuTmpl MenuTm
|
|||
for i := 0; i < len(tmplData); i++ {
|
||||
char := tmplData[i]
|
||||
if char == '{' {
|
||||
//fmt.Println("found open fence")
|
||||
dotIndex, hasDot := skipUntilIfExists(tmplData, i, '.')
|
||||
if !hasDot {
|
||||
//fmt.Println("no dot, assumed template function style")
|
||||
// Template function style
|
||||
langIndex, hasChars := skipUntilCharsExist(tmplData, i+1, []byte("lang"))
|
||||
if hasChars {
|
||||
|
@ -302,7 +295,6 @@ func (hold *MenuListHolder) Parse(name string, tmplData []byte) (menuTmpl MenuTm
|
|||
}
|
||||
fenceIndex, hasFence := skipUntilIfExists(tmplData, dotIndex, '}')
|
||||
if !hasFence {
|
||||
//fmt.Println("no end fence")
|
||||
break
|
||||
}
|
||||
addVariable(tmplData[dotIndex:fenceIndex])
|
||||
|
@ -317,25 +309,21 @@ func (hold *MenuListHolder) Parse(name string, tmplData []byte) (menuTmpl MenuTm
|
|||
renderList = append(renderList, menuRenderItem{0, len(textBuffer) - 1})
|
||||
}
|
||||
|
||||
//fmt.Println("name: ", name)
|
||||
//fmt.Print("textBuffer: ")
|
||||
//menuDumpSlice(textBuffer)
|
||||
//fmt.Print("\nvariableBuffer: ")
|
||||
//menuDumpSlice(variableBuffer)
|
||||
//fmt.Printf("\nrenderList: %+v\n", renderList)
|
||||
return MenuTmpl{name, textBuffer, variableBuffer, renderList}
|
||||
}
|
||||
|
||||
func (hold *MenuListHolder) Scan(menuTmpls map[string]MenuTmpl, showItem func(mitem MenuItem) bool) (renderBuffer [][]byte, variableIndices []int) {
|
||||
func (hold *MenuListHolder) Scan(menuTmpls map[string]MenuTmpl, showItem func(mitem MenuItem) bool) (renderBuffer [][]byte, variableIndices []int, pathList []menuPath) {
|
||||
for _, mitem := range hold.List {
|
||||
// Do we want this item in this variation of the menu?
|
||||
if !showItem(mitem) {
|
||||
continue
|
||||
}
|
||||
renderBuffer, variableIndices = hold.ScanItem(menuTmpls, mitem, renderBuffer, variableIndices)
|
||||
pathList = append(pathList, menuPath{mitem.Path, len(renderBuffer) - 1})
|
||||
}
|
||||
|
||||
// TODO: Need more coalescing in the renderBuffer
|
||||
return renderBuffer, variableIndices
|
||||
return renderBuffer, variableIndices, pathList
|
||||
}
|
||||
|
||||
// Note: This doesn't do a visibility check like hold.Scan() does
|
||||
|
@ -345,7 +333,6 @@ func (hold *MenuListHolder) ScanItem(menuTmpls map[string]MenuTmpl, mitem MenuIt
|
|||
menuTmpl = menuTmpls["menu_item"]
|
||||
}
|
||||
|
||||
//fmt.Println("menuTmpl: ", menuTmpl)
|
||||
for _, renderItem := range menuTmpl.RenderList {
|
||||
if renderItem.Type == 0 {
|
||||
renderBuffer = append(renderBuffer, menuTmpl.TextBuffer[renderItem.Index])
|
||||
|
@ -353,17 +340,16 @@ func (hold *MenuListHolder) ScanItem(menuTmpls map[string]MenuTmpl, mitem MenuIt
|
|||
}
|
||||
|
||||
variable := menuTmpl.VariableBuffer[renderItem.Index]
|
||||
//fmt.Println("initial variable: ", string(variable))
|
||||
dotAt, hasDot := skipUntilIfExists(variable, 0, '.')
|
||||
if !hasDot {
|
||||
//fmt.Println("no dot")
|
||||
continue
|
||||
}
|
||||
|
||||
if bytes.Equal(variable[:dotAt], []byte("lang")) {
|
||||
//fmt.Println("lang: ", string(bytes.TrimPrefix(variable[dotAt:], []byte("."))))
|
||||
renderBuffer = append(renderBuffer, []byte(GetTmplPhrase(string(bytes.TrimPrefix(variable[dotAt:], []byte("."))))))
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
|
||||
var renderItem []byte
|
||||
switch string(variable) {
|
||||
case ".ID":
|
||||
|
@ -382,11 +368,13 @@ func (hold *MenuListHolder) ScanItem(menuTmpls map[string]MenuTmpl, mitem MenuIt
|
|||
renderItem = []byte(mitem.Aria)
|
||||
case ".Tooltip":
|
||||
renderItem = []byte(mitem.Tooltip)
|
||||
case ".CSSActive":
|
||||
renderItem = []byte("{dyn.active}")
|
||||
}
|
||||
|
||||
_, hasInnerVar := skipUntilIfExists(renderItem, 0, '{')
|
||||
if hasInnerVar {
|
||||
//fmt.Println("inner var: ", string(renderItem))
|
||||
fmt.Println("inner var: ", string(renderItem))
|
||||
dotAt, hasDot := skipUntilIfExists(renderItem, 0, '.')
|
||||
endFence, hasEndFence := skipUntilIfExists(renderItem, dotAt, '}')
|
||||
if !hasDot || !hasEndFence || (endFence-dotAt) <= 1 {
|
||||
|
@ -399,7 +387,7 @@ func (hold *MenuListHolder) ScanItem(menuTmpls map[string]MenuTmpl, mitem MenuIt
|
|||
//fmt.Println("lang var: ", string(renderItem[dotAt+1:endFence]))
|
||||
renderBuffer = append(renderBuffer, []byte(GetTmplPhrase(string(renderItem[dotAt+1:endFence]))))
|
||||
} else {
|
||||
//fmt.Println("other var: ", string(variable[:dotAt]))
|
||||
fmt.Println("other var: ", string(variable[:dotAt]))
|
||||
if len(renderItem) > 0 {
|
||||
renderBuffer = append(renderBuffer, renderItem)
|
||||
variableIndices = append(variableIndices, len(renderBuffer)-1)
|
||||
|
@ -407,18 +395,15 @@ func (hold *MenuListHolder) ScanItem(menuTmpls map[string]MenuTmpl, mitem MenuIt
|
|||
}
|
||||
continue
|
||||
}
|
||||
|
||||
//fmt.Println("normal var: ", string(variable[:dotAt]))
|
||||
if len(renderItem) > 0 {
|
||||
renderBuffer = append(renderBuffer, renderItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
return renderBuffer, variableIndices
|
||||
}
|
||||
|
||||
// TODO: Pre-render the lang stuff
|
||||
func (hold *MenuListHolder) Build(w io.Writer, user *User) error {
|
||||
func (hold *MenuListHolder) Build(w io.Writer, user *User, pathPrefix string) error {
|
||||
var mTmpl menuTmpl
|
||||
if !user.Loggedin {
|
||||
mTmpl = hold.Variations[0]
|
||||
|
@ -429,11 +414,12 @@ func (hold *MenuListHolder) Build(w io.Writer, user *User) error {
|
|||
} else {
|
||||
mTmpl = hold.Variations[1]
|
||||
}
|
||||
if pathPrefix == "" {
|
||||
pathPrefix = Config.DefaultPath
|
||||
}
|
||||
|
||||
if len(mTmpl.VariableIndices) == 0 {
|
||||
//fmt.Println("no variable indices")
|
||||
for _, renderItem := range mTmpl.RenderBuffer {
|
||||
//fmt.Printf("renderItem: %+v\n", renderItem)
|
||||
w.Write(renderItem)
|
||||
}
|
||||
return nil
|
||||
|
@ -442,17 +428,15 @@ func (hold *MenuListHolder) Build(w io.Writer, user *User) error {
|
|||
var nearIndex = 0
|
||||
for index, renderItem := range mTmpl.RenderBuffer {
|
||||
if index != mTmpl.VariableIndices[nearIndex] {
|
||||
//fmt.Println("wrote text: ", string(renderItem))
|
||||
w.Write(renderItem)
|
||||
continue
|
||||
}
|
||||
|
||||
//fmt.Println("variable: ", string(renderItem))
|
||||
variable := renderItem
|
||||
// ? - I can probably remove this check now that I've kicked it upstream, or we could keep it here for safety's sake?
|
||||
if len(variable) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
prevIndex := 0
|
||||
for i := 0; i < len(renderItem); i++ {
|
||||
fenceStart, hasFence := skipUntilIfExists(variable, i, '{')
|
||||
|
@ -469,9 +453,9 @@ func (hold *MenuListHolder) Build(w io.Writer, user *User) error {
|
|||
if !hasDot {
|
||||
continue
|
||||
}
|
||||
//fmt.Println("checking me: ", string(variable[fenceStart+1:dotAt]))
|
||||
if bytes.Equal(variable[fenceStart+1:dotAt], []byte("me")) {
|
||||
//fmt.Println("maybe me variable")
|
||||
|
||||
switch string(variable[fenceStart+1 : dotAt]) {
|
||||
case "me":
|
||||
w.Write(variable[prevIndex:fenceStart])
|
||||
switch string(variable[dotAt+1 : fenceEnd]) {
|
||||
case "Link":
|
||||
|
@ -480,10 +464,31 @@ func (hold *MenuListHolder) Build(w io.Writer, user *User) error {
|
|||
w.Write([]byte(user.Session))
|
||||
}
|
||||
prevIndex = fenceEnd
|
||||
// TODO: Optimise this
|
||||
case "dyn":
|
||||
w.Write(variable[prevIndex:fenceStart])
|
||||
var pmi int
|
||||
for ii, pathItem := range mTmpl.PathMappings {
|
||||
pmi = ii
|
||||
if pathItem.Index > index {
|
||||
break
|
||||
}
|
||||
}
|
||||
//fmt.Println("prevIndex: ", prevIndex)
|
||||
//fmt.Println("len(variable)-1: ", len(variable)-1)
|
||||
|
||||
if len(mTmpl.PathMappings) != 0 {
|
||||
path := mTmpl.PathMappings[pmi].Path
|
||||
if path == "" || path == "/" {
|
||||
path = Config.DefaultPath
|
||||
}
|
||||
if strings.HasPrefix(path, pathPrefix) {
|
||||
w.Write([]byte(" menu_active"))
|
||||
}
|
||||
}
|
||||
|
||||
prevIndex = fenceEnd
|
||||
}
|
||||
}
|
||||
|
||||
w.Write(variable[prevIndex : len(variable)-1])
|
||||
if len(mTmpl.VariableIndices) > (nearIndex + 1) {
|
||||
nearIndex++
|
||||
|
|
|
@ -24,6 +24,7 @@ type Header struct {
|
|||
//TemplateName string // TODO: Use this to move template calls to the router rather than duplicating them over and over and over?
|
||||
CurrentUser User // TODO: Deprecate CurrentUser on the page structs and use a pointer here
|
||||
Zone string
|
||||
Path string
|
||||
MetaDesc string
|
||||
Writer http.ResponseWriter
|
||||
ExtData ExtData
|
||||
|
@ -158,6 +159,7 @@ type AccountDashPage struct {
|
|||
CurrentScore int
|
||||
NextScore int
|
||||
NextLevel int
|
||||
Percentage int
|
||||
}
|
||||
|
||||
type LevelListItem struct {
|
||||
|
|
|
@ -239,6 +239,14 @@ func GetTitlePhrase(name string) string {
|
|||
return res
|
||||
}
|
||||
|
||||
func GetTitlePhrasef(name string, params ...interface{}) string {
|
||||
res, ok := currentLangPack.Load().(*LanguagePack).PageTitles[name]
|
||||
if !ok {
|
||||
return getPhrasePlaceholder("title", name)
|
||||
}
|
||||
return fmt.Sprintf(res, params...)
|
||||
}
|
||||
|
||||
func GetTmplPhrase(name string) string {
|
||||
res, ok := currentLangPack.Load().(*LanguagePack).TmplPhrases[name]
|
||||
if !ok {
|
||||
|
|
|
@ -136,7 +136,7 @@ func BuildWidget(dock string, header *Header) (sbody string) {
|
|||
// 1 = id for the default menu
|
||||
mhold, err := Menus.Get(1)
|
||||
if err == nil {
|
||||
err := mhold.Build(header.Writer, &header.CurrentUser)
|
||||
err := mhold.Build(header.Writer, &header.CurrentUser, header.Path)
|
||||
if err != nil {
|
||||
LogError(err)
|
||||
}
|
||||
|
|
2
main.go
2
main.go
|
@ -93,7 +93,7 @@ func afterDBInit() (err error) {
|
|||
}
|
||||
fmt.Printf("menuHold: %+v\n", menuHold)
|
||||
var b bytes.Buffer
|
||||
menuHold.Build(&b, &common.GuestUser)
|
||||
menuHold.Build(&b, &common.GuestUser, "/")
|
||||
fmt.Println("menuHold output: ", string(b.Bytes()))
|
||||
|
||||
log.Print("Initialising the authentication system")
|
||||
|
|
|
@ -362,6 +362,7 @@ func accountEditHead(titlePhrase string, w http.ResponseWriter, r *http.Request,
|
|||
return nil, ferr
|
||||
}
|
||||
header.Title = common.GetTitlePhrase(titlePhrase)
|
||||
header.Path = "/user/edit/"
|
||||
header.AddSheet(header.Theme.Name + "/account.css")
|
||||
header.AddScript("account.js")
|
||||
return header, nil
|
||||
|
@ -394,8 +395,9 @@ func AccountEdit(w http.ResponseWriter, r *http.Request, user common.User) commo
|
|||
prevScore := common.GetLevelScore(user.Level)
|
||||
currentScore := user.Score - prevScore
|
||||
nextScore := common.GetLevelScore(user.Level+1) - prevScore
|
||||
perc := int(math.Ceil((float64(nextScore) / float64(currentScore)) * 100))
|
||||
|
||||
pi := common.AccountDashPage{header, mfaSetup, currentScore, nextScore, user.Level + 1}
|
||||
pi := common.AccountDashPage{header, mfaSetup, currentScore, nextScore, user.Level + 1, perc * 2}
|
||||
if common.RunPreRenderHook("pre_render_account_own_edit", w, r, &user, &pi) {
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ func ForumList(w http.ResponseWriter, r *http.Request, user common.User) common.
|
|||
}
|
||||
header.Title = common.GetTitlePhrase("forums")
|
||||
header.Zone = "forums"
|
||||
header.Path = "/forums/"
|
||||
header.MetaDesc = header.Settings["meta_desc"].(string)
|
||||
|
||||
var err error
|
||||
|
|
|
@ -27,6 +27,7 @@ func init() {
|
|||
})
|
||||
}
|
||||
|
||||
// TODO: Remove the View part of the name?
|
||||
func ViewProfile(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
|
||||
header, ferr := common.UserCheck(w, r, &user)
|
||||
if ferr != nil {
|
||||
|
@ -68,6 +69,7 @@ func ViewProfile(w http.ResponseWriter, r *http.Request, user common.User) commo
|
|||
}
|
||||
// TODO: Add a phrase for this title
|
||||
header.Title = puser.Name + "'s Profile"
|
||||
header.Path = common.BuildProfileURL(common.NameToSlug(puser.Name), puser.ID)
|
||||
|
||||
// Get the replies..
|
||||
rows, err := profileStmts.getReplies.Query(puser.ID)
|
||||
|
|
|
@ -59,18 +59,17 @@ func ViewTopic(w http.ResponseWriter, r *http.Request, user common.User, urlBit
|
|||
return common.InternalError(err, w, r)
|
||||
}
|
||||
topic.ClassName = ""
|
||||
//log.Printf("topic: %+v\n", topic)
|
||||
|
||||
header, ferr := common.ForumUserCheck(w, r, &user, topic.ParentID)
|
||||
if ferr != nil {
|
||||
return ferr
|
||||
}
|
||||
if !user.Perms.ViewTopic {
|
||||
//log.Printf("user.Perms: %+v\n", user.Perms)
|
||||
return common.NoPermissions(w, r, user)
|
||||
}
|
||||
header.Title = topic.Title
|
||||
header.Zone = "view_topic"
|
||||
header.Path = common.BuildTopicURL(common.NameToSlug(topic.Title), topic.ID)
|
||||
|
||||
topic.ContentHTML = common.ParseMessage(topic.Content, topic.ParentID, "forums")
|
||||
topic.ContentLines = strings.Count(topic.Content, "\n")
|
||||
|
|
|
@ -15,6 +15,7 @@ func TopicList(w http.ResponseWriter, r *http.Request, user common.User) common.
|
|||
}
|
||||
header.Title = common.GetTitlePhrase("topics")
|
||||
header.Zone = "topics"
|
||||
header.Path = "/topics/"
|
||||
header.MetaDesc = header.Settings["meta_desc"].(string)
|
||||
|
||||
group, err := common.Groups.Get(user.Group)
|
||||
|
@ -62,6 +63,7 @@ func TopicListMostViewed(w http.ResponseWriter, r *http.Request, user common.Use
|
|||
}
|
||||
header.Title = common.GetTitlePhrase("topics")
|
||||
header.Zone = "topics"
|
||||
header.Path = "/topics/"
|
||||
header.MetaDesc = header.Settings["meta_desc"].(string)
|
||||
|
||||
group, err := common.Groups.Get(user.Group)
|
||||
|
|
|
@ -21,14 +21,11 @@
|
|||
</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">
|
||||
<a href="/user/levels/">{{level .CurrentUser.Level}}: [{{.CurrentScore}} / {{.NextScore}}]</a> <span class="account_soon">{{lang "account_coming_soon"}}</span>
|
||||
</div>-->
|
||||
<div class="rowitem level_inprogress">
|
||||
<div class="levelBit">
|
||||
<a href="/user/levels/">{{level .CurrentUser.Level}}</a>
|
||||
</div>
|
||||
<div class="progressWrap" style="width: 180%;">
|
||||
<div class="progressWrap" style="width: {{.Percentage}}%;">
|
||||
<div>{{.CurrentScore}} / {{.NextScore}}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
<nav class="nav">
|
||||
<div class="move_left">
|
||||
<div class="move_right">
|
||||
<ul>{{/** TODO: Have the theme control whether the long or short form of the name is used **/}}
|
||||
<ul class="zone_{{.Header.Zone}}">{{/** TODO: Have the theme control whether the long or short form of the name is used **/}}
|
||||
<li id="menu_overview" class="menu_left"><a href="/" rel="home">{{if eq .Header.Theme.Name "nox"}}{{.Header.Site.Name}}{{else}}{{.Header.Site.ShortName}}{{end}}</a></li>
|
||||
{{dock "topMenu" .Header }}
|
||||
<li class="menu_left menu_hamburger" title="{{lang "menu_hamburger_tooltip"}}"><a></a></li>
|
||||
|
|
|
@ -1 +1 @@
|
|||
<li id="{{.HTMLID}}" class="menu_{{.Position}} {{.CSSClass}}"><a href="{{.Path}}" aria-label="{{.Aria}}" title="{{.Tooltip}}">{{.Name}}</a></li>
|
||||
<li id="{{.HTMLID}}" class="menu_{{.Position}} {{.CSSClass}}{{.CSSActive}}"><a href="{{.Path}}" aria-label="{{.Aria}}" title="{{.Tooltip}}">{{.Name}}</a></li>
|
Loading…
Reference in New Issue