Added the menu manager and menu item editor.

Refactored the menu system.
Updated the README and revamped it a tad to make it easier to understand. Also, added manual instructions for patching.
Revamped the update scripts, especially on Windows.
Merged the CSS and Tmpl phrase namespaces.
Added lastSchema to .gitignore
Added DropTable to the database adapters.
Implemented DbVersion in the PgSQL Adapter.
Swapped out the checkboxes for cleaner looking yes-no dropdowns.
Began revamping small bits of the user logic.

We now open to contributions, just open a pull request and sign the CLA.
Schema has been updated, run the patcher or update script.
This commit is contained in:
Azareal 2018-05-11 15:41:51 +10:00
parent 894e545973
commit d0318191c9
43 changed files with 1109 additions and 612 deletions

1
.gitignore vendored
View File

@ -20,6 +20,7 @@ out/*
*.log *.log
.DS_Store .DS_Store
.vscode/launch.json .vscode/launch.json
schema/lastSchema.json
config/config.go config/config.go
Gosora Gosora
Install Install

View File

@ -1,4 +1,8 @@
We're not accepting contributions right now, although you're welcome to poke me about things. I'd like to put a process together at some point. If you want to add a contribution, you'll have to open a pull request and to sign the CLA (contributor level agreement).
It's mainly there to deal with any legal issues which come our way and to switch licenses without having to chase down contributors who have long stopped using the internet or are deceased or incapacitated.
Other uses may arise in the future, e.g. commercial licensing, although that's currently uncertain.
# Coding Standards # Coding Standards

View File

@ -31,7 +31,7 @@ Other modern features like alerts, likes, advanced dashboard with live stats (CP
# Dependencies # Dependencies
Go 1.9 - You will need to install this. Pick the .msi, if you want everything sorted out for you rather than having to go around updating the environment settings. https://golang.org/doc/install Go 1.10 - You will need to install this. Pick the .msi, if you want everything sorted out for you rather than having to go around updating the environment settings. https://golang.org/doc/install
MySQL Database - You will need to setup a MySQL Database somewhere. A MariaDB Database works equally well and is much faster than MySQL. You could use something like WNMP / XAMPP which have a little PHP script called PhpMyAdmin for managing MySQL databases or you could install MariaDB directly. MySQL Database - You will need to setup a MySQL Database somewhere. A MariaDB Database works equally well and is much faster than MySQL. You could use something like WNMP / XAMPP which have a little PHP script called PhpMyAdmin for managing MySQL databases or you could install MariaDB directly.
@ -48,6 +48,8 @@ It's entirely possible that your host might already have MySQL, so you might be
At some point, we'll have releases which you can download, but right now, you'll have to use the `git clone` command as mentioned down in the advanced setup section to download a copy of Gosora. At some point, we'll have releases which you can download, but right now, you'll have to use the `git clone` command as mentioned down in the advanced setup section to download a copy of Gosora.
On Windows, you might also want to try the [GosoraBootstrapper](https://github.com/Azareal/GosoraBootstrapper), if you can't find the command prompt. It's just a matter of double-clicking on the bat file there and it'll download the rest of the files for you.
# Installation Instructions # Installation Instructions
@ -61,7 +63,7 @@ Follow the instructions shown on the screen.
*Windows* *Windows*
Run install.bat Run install.bat, e.g. double-click on it. You will also have to start-up MySQL, which if you're using Wnmp or friends is just a matter of opening that program and starting the MySQL process via it.
Follow the instructions shown on the screen. Follow the instructions shown on the screen.
@ -74,7 +76,7 @@ In the same directory you installed it, you simply have to type: ./run-linux
*Windows* *Windows*
Run run.bat Run run.bat, e.g. double-clicking on it.
*Updating Dependencies* *Updating Dependencies*
@ -97,6 +99,8 @@ Linux is similar, however you might need to use cd and mv a bit more like in the
You also need to substitute the `gosora.exe` bits for `./Gosora` on Linux. For more info, you might want to take a gander inside the `./run-linux` and `./install-linux` shell files to see how they're implemented. You also need to substitute the `gosora.exe` bits for `./Gosora` on Linux. For more info, you might want to take a gander inside the `./run-linux` and `./install-linux` shell files to see how they're implemented.
If you want to skip typing all the `go get`s, you can run `./update-deps.bat` (Windows) or `./update-deps-linux` to do that for you.
```bash ```bash
git clone https://github.com/Azareal/Gosora git clone https://github.com/Azareal/Gosora
@ -153,6 +157,17 @@ The update system is currently under development, however if you have Git instal
In addition to this, you can update the dependencies without updating Gosora by running `update-deps.bat` or `./update-deps-linux` (.bat is for Windows, the other for Linux as the names would suggest). In addition to this, you can update the dependencies without updating Gosora by running `update-deps.bat` or `./update-deps-linux` (.bat is for Windows, the other for Linux as the names would suggest).
If you want to manually patch Gosora rather than relying on the above scripts to do it, you'll first have to create a copy of `./schema/schema.json` named `./schema/lastSchema.json`, and then, you'll overwrite the files with the new ones.
After that, you'll need to run `go build ./patcher` on Windows or the following code block on Linux:
```
cd ./patcher
go build -o Patcher
mv ./Patcher ..
```
Once you've done that, you just need to run `patcher.exe` (Windows) or `./Patcher` to apply the latest patches to the database, etc.
# How do I install plugins? # How do I install plugins?

30
common/menu_item_store.go Normal file
View File

@ -0,0 +1,30 @@
package common
import "sync"
type DefaultMenuItemStore struct {
items map[int]MenuItem
itemLock sync.RWMutex
}
func NewDefaultMenuItemStore() *DefaultMenuItemStore {
return &DefaultMenuItemStore{
items: make(map[int]MenuItem),
}
}
func (store *DefaultMenuItemStore) Add(item MenuItem) {
store.itemLock.Lock()
defer store.itemLock.Unlock()
store.items[item.ID] = item
}
func (store *DefaultMenuItemStore) Get(id int) (MenuItem, error) {
store.itemLock.RLock()
item, ok := store.items[id]
store.itemLock.RUnlock()
if ok {
return item, nil
}
return item, ErrNoRows
}

76
common/menu_store.go Normal file
View File

@ -0,0 +1,76 @@
package common
import (
"database/sql"
"strconv"
"sync/atomic"
"../query_gen/lib"
)
var Menus *DefaultMenuStore
type DefaultMenuStore struct {
menus map[int]*atomic.Value
itemStore *DefaultMenuItemStore
}
func NewDefaultMenuStore() *DefaultMenuStore {
return &DefaultMenuStore{
make(map[int]*atomic.Value),
NewDefaultMenuItemStore(),
}
}
// TODO: Add actual support for multiple menus
func (store *DefaultMenuStore) GetAllMap() (out map[int]*MenuListHolder) {
out = make(map[int]*MenuListHolder)
for mid, atom := range store.menus {
out[mid] = atom.Load().(*MenuListHolder)
}
return out
}
func (store *DefaultMenuStore) Get(mid int) (*MenuListHolder, error) {
aStore, ok := store.menus[mid]
if ok {
return aStore.Load().(*MenuListHolder), nil
}
return nil, ErrNoRows
}
func (store *DefaultMenuStore) Items(mid int) (mlist MenuItemList, err error) {
acc := qgen.Builder.Accumulator()
err = acc.Select("menu_items").Columns("miid, name, htmlID, cssClass, position, path, aria, tooltip, order, tmplName, guestOnly, memberOnly, staffOnly, adminOnly").Where("mid = " + strconv.Itoa(mid)).Orderby("order ASC").Each(func(rows *sql.Rows) error {
var mitem = MenuItem{MenuID: mid}
err := rows.Scan(&mitem.ID, &mitem.Name, &mitem.HTMLID, &mitem.CSSClass, &mitem.Position, &mitem.Path, &mitem.Aria, &mitem.Tooltip, &mitem.Order, &mitem.TmplName, &mitem.GuestOnly, &mitem.MemberOnly, &mitem.SuperModOnly, &mitem.AdminOnly)
if err != nil {
return err
}
store.itemStore.Add(mitem)
mlist = append(mlist, mitem)
return nil
})
return mlist, err
}
func (store *DefaultMenuStore) Load(mid int) error {
mlist, err := store.Items(mid)
if err != nil {
return err
}
hold := &MenuListHolder{mlist, make(map[int]menuTmpl)}
err = hold.Preparse()
if err != nil {
return err
}
var aStore = &atomic.Value{}
aStore.Store(hold)
store.menus[mid] = aStore
return nil
}
func (store *DefaultMenuStore) ItemStore() *DefaultMenuItemStore {
return store.itemStore
}

View File

@ -7,31 +7,12 @@ import (
"io" "io"
"io/ioutil" "io/ioutil"
"strconv" "strconv"
"sync/atomic"
"../query_gen/lib" "../query_gen/lib"
) )
var Menus *DefaultMenuStore
type MenuItemList []MenuItem type MenuItemList []MenuItem
type DefaultMenuStore struct {
menus map[int]*atomic.Value
}
func NewDefaultMenuStore() *DefaultMenuStore {
return &DefaultMenuStore{make(map[int]*atomic.Value)}
}
func (store *DefaultMenuStore) Get(mid int) *MenuListHolder {
aStore, ok := store.menus[mid]
if ok {
return aStore.Load().(*MenuListHolder)
}
return nil
}
type MenuListHolder struct { type MenuListHolder struct {
List MenuItemList List MenuItemList
Variations map[int]menuTmpl // 0 = Guest Menu, 1 = Member Menu, 2 = Super Mod Menu, 3 = Admin Menu Variations map[int]menuTmpl // 0 = Guest Menu, 1 = Member Menu, 2 = Super Mod Menu, 3 = Admin Menu
@ -43,7 +24,10 @@ type menuTmpl struct {
} }
type MenuItem struct { type MenuItem struct {
ID int ID int
MenuID int
Name string
HTMLID string HTMLID string
CSSClass string CSSClass string
Position string Position string
@ -59,50 +43,56 @@ type MenuItem struct {
AdminOnly bool AdminOnly bool
} }
func (store *DefaultMenuStore) Load(mid int) error { type MenuItemStmts struct {
var mlist MenuItemList update *sql.Stmt
acc := qgen.Builder.Accumulator() }
err := acc.Select("menu_items").Columns("htmlID, cssClass, position, path, aria, tooltip, order, tmplName, guestOnly, memberOnly, staffOnly, adminOnly").Where("mid = " + strconv.Itoa(mid)).Orderby("order ASC").Each(func(rows *sql.Rows) error {
var mitem = MenuItem{ID: 1} var menuItemStmts MenuItemStmts
err := rows.Scan(&mitem.HTMLID, &mitem.CSSClass, &mitem.Position, &mitem.Path, &mitem.Aria, &mitem.Tooltip, &mitem.Order, &mitem.TmplName, &mitem.GuestOnly, &mitem.MemberOnly, &mitem.SuperModOnly, &mitem.AdminOnly)
func init() {
DbInits.Add(func(acc *qgen.Accumulator) error {
menuItemStmts = MenuItemStmts{
update: acc.Update("menu_items").Set("name = ?, htmlID = ?, cssClass = ?, position = ?, path = ?, aria = ?, tooltip = ?, tmplName = ?, guestOnly = ?, memberOnly = ?, staffOnly = ?, adminOnly = ?").Where("miid = ?").Prepare(),
}
return acc.FirstError()
})
}
func (item MenuItem) Commit() error {
_, err := menuItemStmts.update.Exec(item.Name, item.HTMLID, item.CSSClass, item.Position, item.Path, item.Aria, item.Tooltip, item.TmplName, item.GuestOnly, item.MemberOnly, item.SuperModOnly, item.AdminOnly, item.ID)
Menus.Load(item.MenuID)
return err
}
func (hold *MenuListHolder) LoadTmpl(name string) (menuTmpl MenuTmpl, err error) {
data, err := ioutil.ReadFile("./templates/" + name + ".html")
if err != nil {
return menuTmpl, err
}
return hold.Parse(name, data), nil
}
func (hold *MenuListHolder) LoadTmpls() (tmpls map[string]MenuTmpl, err error) {
tmpls = make(map[string]MenuTmpl)
var loadTmpl = func(name string) error {
menuTmpl, err := hold.LoadTmpl(name)
if err != nil { if err != nil {
return err return err
} }
mlist = append(mlist, mitem) tmpls[name] = menuTmpl
return nil return nil
})
if err != nil {
return err
} }
err = loadTmpl("menu_item")
hold := &MenuListHolder{mlist, make(map[int]menuTmpl)}
err = hold.Preparse()
if err != nil { if err != nil {
return err return tmpls, err
} }
err = loadTmpl("menu_alerts")
var aStore = &atomic.Value{} return tmpls, err
aStore.Store(hold)
store.menus[mid] = aStore
return nil
} }
// TODO: Run this in main, sync ticks, when the phrase file changes (need to implement the sync for that first), and when the settings are changed // TODO: Run this in main, sync ticks, when the phrase file changes (need to implement the sync for that first), and when the settings are changed
func (hold *MenuListHolder) Preparse() error { func (hold *MenuListHolder) Preparse() error {
var tmpls = make(map[string]MenuTmpl) tmpls, err := hold.LoadTmpls()
var loadTmpl = func(name string) error {
data, err := ioutil.ReadFile("./templates/" + name + ".html")
if err != nil {
return err
}
tmpls[name] = hold.Parse(name, data)
return nil
}
err := loadTmpl("menu_item")
if err != nil {
return err
}
err = loadTmpl("menu_alerts")
if err != nil { if err != nil {
return err return err
} }
@ -194,6 +184,7 @@ func (hold *MenuListHolder) Parse(name string, tmplData []byte) (menuTmpl MenuTm
// ? We only support simple properties on MenuItem right now // ? We only support simple properties on MenuItem right now
var addVariable = func(name []byte) { var addVariable = func(name []byte) {
//fmt.Println("appending subBuffer: ", string(subBuffer)) //fmt.Println("appending subBuffer: ", string(subBuffer))
// TODO: Check if the subBuffer has any items or is empty
textBuffer = append(textBuffer, subBuffer) textBuffer = append(textBuffer, subBuffer)
subBuffer = nil subBuffer = nil
@ -203,13 +194,17 @@ func (hold *MenuListHolder) Parse(name string, tmplData []byte) (menuTmpl MenuTm
renderList = append(renderList, menuRenderItem{1, len(variableBuffer) - 1}) renderList = append(renderList, menuRenderItem{1, len(variableBuffer) - 1})
} }
tmplData = bytes.Replace(tmplData, []byte("{{"), []byte("{"), -1)
tmplData = bytes.Replace(tmplData, []byte("}}"), []byte("}}"), -1)
for i := 0; i < len(tmplData); i++ { for i := 0; i < len(tmplData); i++ {
char := tmplData[i] char := tmplData[i]
if char == '{' && nextCharIs(tmplData, i, '{') { if char == '{' {
//fmt.Println("found open fence")
dotIndex, hasDot := skipUntilIfExists(tmplData, i, '.') dotIndex, hasDot := skipUntilIfExists(tmplData, i, '.')
if !hasDot { if !hasDot {
//fmt.Println("no dot, assumed template function style")
// Template function style // Template function style
langIndex, hasChars := skipUntilCharsExist(tmplData, i+2, []byte("lang")) langIndex, hasChars := skipUntilCharsExist(tmplData, i+1, []byte("lang"))
if hasChars { if hasChars {
startIndex, hasStart := skipUntilIfExists(tmplData, langIndex, '"') startIndex, hasStart := skipUntilIfExists(tmplData, langIndex, '"')
endIndex, hasEnd := skipUntilIfExists(tmplData, startIndex+1, '"') endIndex, hasEnd := skipUntilIfExists(tmplData, startIndex+1, '"')
@ -228,7 +223,8 @@ func (hold *MenuListHolder) Parse(name string, tmplData []byte) (menuTmpl MenuTm
break break
} }
fenceIndex, hasFence := skipUntilIfExists(tmplData, dotIndex, '}') fenceIndex, hasFence := skipUntilIfExists(tmplData, dotIndex, '}')
if !hasFence || !nextCharIs(tmplData, fenceIndex, '}') { if !hasFence {
//fmt.Println("no end fence")
break break
} }
addVariable(tmplData[dotIndex:fenceIndex]) addVariable(tmplData[dotIndex:fenceIndex])
@ -258,78 +254,88 @@ func (hold *MenuListHolder) Scan(menuTmpls map[string]MenuTmpl, showItem func(mi
if !showItem(mitem) { if !showItem(mitem) {
continue continue
} }
renderBuffer, variableIndices = hold.ScanItem(menuTmpls, mitem, renderBuffer, variableIndices)
}
// TODO: Need more coalescing in the renderBuffer
return renderBuffer, variableIndices
}
menuTmpl, ok := menuTmpls[mitem.TmplName] // Note: This doesn't do a visibility check like hold.Scan() does
if !ok { func (hold *MenuListHolder) ScanItem(menuTmpls map[string]MenuTmpl, mitem MenuItem, renderBuffer [][]byte, variableIndices []int) ([][]byte, []int) {
menuTmpl = menuTmpls["menu_item"] menuTmpl, ok := menuTmpls[mitem.TmplName]
if !ok {
menuTmpl = menuTmpls["menu_item"]
}
//fmt.Println("menuTmpl: ", menuTmpl)
for _, renderItem := range menuTmpl.RenderList {
if renderItem.Type == 0 {
renderBuffer = append(renderBuffer, menuTmpl.TextBuffer[renderItem.Index])
continue
} }
//fmt.Println("menuTmpl: ", menuTmpl)
for _, renderItem := range menuTmpl.RenderList { variable := menuTmpl.VariableBuffer[renderItem.Index]
if renderItem.Type == 0 { //fmt.Println("initial variable: ", string(variable))
renderBuffer = append(renderBuffer, menuTmpl.TextBuffer[renderItem.Index]) dotAt, hasDot := skipUntilIfExists(variable, 0, '.')
continue 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 {
var renderItem []byte
switch string(variable) {
case ".ID":
renderItem = []byte(strconv.Itoa(mitem.ID))
case ".Name":
renderItem = []byte(mitem.Name)
case ".HTMLID":
renderItem = []byte(mitem.HTMLID)
case ".CSSClass":
renderItem = []byte(mitem.CSSClass)
case ".Position":
renderItem = []byte(mitem.Position)
case ".Path":
renderItem = []byte(mitem.Path)
case ".Aria":
renderItem = []byte(mitem.Aria)
case ".Tooltip":
renderItem = []byte(mitem.Tooltip)
} }
variable := menuTmpl.VariableBuffer[renderItem.Index] _, hasInnerVar := skipUntilIfExists(renderItem, 0, '{')
//fmt.Println("initial variable: ", string(variable)) if hasInnerVar {
dotAt, hasDot := skipUntilIfExists(variable, 0, '.') //fmt.Println("inner var: ", string(renderItem))
if !hasDot { dotAt, hasDot := skipUntilIfExists(renderItem, 0, '.')
//fmt.Println("no dot") endFence, hasEndFence := skipUntilIfExists(renderItem, dotAt, '}')
continue if !hasDot || !hasEndFence || (endFence-dotAt) <= 1 {
} renderBuffer = append(renderBuffer, renderItem)
variableIndices = append(variableIndices, len(renderBuffer)-1)
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 {
var renderItem []byte
switch string(variable) {
case ".HTMLID":
renderItem = []byte(mitem.HTMLID)
case ".CSSClass":
renderItem = []byte(mitem.CSSClass)
case ".Position":
renderItem = []byte(mitem.Position)
case ".Path":
renderItem = []byte(mitem.Path)
case ".Aria":
renderItem = []byte(mitem.Aria)
case ".Tooltip":
renderItem = []byte(mitem.Tooltip)
}
_, hasInnerVar := skipUntilIfExists(renderItem, 0, '{')
if hasInnerVar {
//fmt.Println("inner var: ", string(renderItem))
dotAt, hasDot := skipUntilIfExists(renderItem, 0, '.')
endFence, hasEndFence := skipUntilIfExists(renderItem, dotAt, '}')
if !hasDot || !hasEndFence || (endFence-dotAt) <= 1 {
renderBuffer = append(renderBuffer, renderItem)
variableIndices = append(variableIndices, len(renderBuffer)-1)
continue
}
if bytes.Equal(renderItem[1:dotAt], []byte("lang")) {
//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]))
if len(renderItem) > 0 {
renderBuffer = append(renderBuffer, renderItem)
variableIndices = append(variableIndices, len(renderBuffer)-1)
}
}
continue continue
} }
//fmt.Println("normal var: ", string(variable[:dotAt])) if bytes.Equal(renderItem[1:dotAt], []byte("lang")) {
if len(renderItem) > 0 { //fmt.Println("lang var: ", string(renderItem[dotAt+1:endFence]))
renderBuffer = append(renderBuffer, renderItem) renderBuffer = append(renderBuffer, []byte(GetTmplPhrase(string(renderItem[dotAt+1:endFence]))))
} else {
//fmt.Println("other var: ", string(variable[:dotAt]))
if len(renderItem) > 0 {
renderBuffer = append(renderBuffer, renderItem)
variableIndices = append(variableIndices, len(renderBuffer)-1)
}
} }
continue
}
//fmt.Println("normal var: ", string(variable[:dotAt]))
if len(renderItem) > 0 {
renderBuffer = append(renderBuffer, renderItem)
} }
} }
} }
// TODO: Need more coalescing in the renderBuffer
return renderBuffer, variableIndices return renderBuffer, variableIndices
} }

View File

@ -170,12 +170,10 @@ type GridElement struct {
} }
type PanelDashboardPage struct { type PanelDashboardPage struct {
Title string *Header
CurrentUser User Stats PanelStats
Header *Header Zone string
Stats PanelStats GridItems []GridElement
Zone string
GridItems []GridElement
} }
type PanelTimeGraph struct { type PanelTimeGraph struct {
@ -255,15 +253,40 @@ type PanelAnalyticsAgentPage struct {
} }
type PanelThemesPage struct { type PanelThemesPage struct {
Title string *Header
CurrentUser User
Header *Header
Stats PanelStats Stats PanelStats
Zone string Zone string
PrimaryThemes []*Theme PrimaryThemes []*Theme
VariantThemes []*Theme VariantThemes []*Theme
} }
type PanelMenuListItem struct {
ID int
ItemCount int
}
type PanelMenuListPage struct {
*Header
Stats PanelStats
Zone string
ItemList []PanelMenuListItem
}
type PanelMenuPage struct {
*Header
Stats PanelStats
Zone string
ID int
ItemList []MenuItem
}
type PanelMenuItemPage struct {
*Header
Stats PanelStats
Zone string
Item MenuItem
}
type PanelUserPage struct { type PanelUserPage struct {
Title string Title string
CurrentUser User CurrentUser User

View File

@ -49,7 +49,6 @@ type LanguagePack struct {
NoticePhrases map[string]string NoticePhrases map[string]string
PageTitles map[string]string PageTitles map[string]string
TmplPhrases map[string]string TmplPhrases map[string]string
CSSPhrases map[string]string
TmplIndicesToPhrases [][][]byte // [tmplID][index]phrase TmplIndicesToPhrases [][][]byte // [tmplID][index]phrase
} }
@ -231,8 +230,8 @@ func GetTmplPhrase(name string) string {
return res return res
} }
func GetCSSPhrases() map[string]string { func GetTmplPhrases() map[string]string {
return currentLangPack.Load().(*LanguagePack).CSSPhrases return currentLangPack.Load().(*LanguagePack).TmplPhrases
} }
func getPhrasePlaceholder(prefix string, suffix string) string { func getPhrasePlaceholder(prefix string, suffix string) string {

View File

@ -212,7 +212,7 @@ func (theme *Theme) LoadStaticFiles() error {
} }
func (theme *Theme) AddThemeStaticFiles() error { func (theme *Theme) AddThemeStaticFiles() error {
phraseMap := GetCSSPhrases() phraseMap := GetTmplPhrases()
// TODO: Use a function instead of a closure to make this more testable? What about a function call inside the closure to take the theme variable into account? // TODO: Use a function instead of a closure to make this more testable? What about a function call inside the closure to take the theme variable into account?
return filepath.Walk("./themes/"+theme.Name+"/public", func(path string, f os.FileInfo, err error) error { return filepath.Walk("./themes/"+theme.Name+"/public", func(path string, f os.FileInfo, err error) error {
DebugLog("Attempting to add static file '" + path + "' for default theme '" + theme.Name + "'") DebugLog("Attempting to add static file '" + path + "' for default theme '" + theme.Name + "'")

View File

@ -83,24 +83,25 @@ var userStmts UserStmts
func init() { func init() {
DbInits.Add(func(acc *qgen.Accumulator) error { DbInits.Add(func(acc *qgen.Accumulator) error {
var where = "uid = ?"
userStmts = UserStmts{ userStmts = UserStmts{
activate: acc.SimpleUpdate("users", "active = 1", "uid = ?"), activate: acc.SimpleUpdate("users", "active = 1", where),
changeGroup: acc.SimpleUpdate("users", "group = ?", "uid = ?"), // TODO: Implement user_count for users_groups here changeGroup: acc.SimpleUpdate("users", "group = ?", where), // TODO: Implement user_count for users_groups here
delete: acc.SimpleDelete("users", "uid = ?"), delete: acc.SimpleDelete("users", where),
setAvatar: acc.SimpleUpdate("users", "avatar = ?", "uid = ?"), setAvatar: acc.SimpleUpdate("users", "avatar = ?", where),
setUsername: acc.SimpleUpdate("users", "name = ?", "uid = ?"), setUsername: acc.Update("users").Set("name = ?").Where(where).Prepare(),
incrementTopics: acc.SimpleUpdate("users", "topics = topics + ?", "uid = ?"), incrementTopics: acc.SimpleUpdate("users", "topics = topics + ?", where),
updateLevel: acc.SimpleUpdate("users", "level = ?", "uid = ?"), updateLevel: acc.SimpleUpdate("users", "level = ?", where),
incrementScore: acc.SimpleUpdate("users", "score = score + ?", "uid = ?"), incrementScore: acc.SimpleUpdate("users", "score = score + ?", where),
incrementPosts: acc.SimpleUpdate("users", "posts = posts + ?", "uid = ?"), incrementPosts: acc.SimpleUpdate("users", "posts = posts + ?", where),
incrementBigposts: acc.SimpleUpdate("users", "posts = posts + ?, bigposts = bigposts + ?", "uid = ?"), incrementBigposts: acc.SimpleUpdate("users", "posts = posts + ?, bigposts = bigposts + ?", where),
incrementMegaposts: acc.SimpleUpdate("users", "posts = posts + ?, bigposts = bigposts + ?, megaposts = megaposts + ?", "uid = ?"), incrementMegaposts: acc.SimpleUpdate("users", "posts = posts + ?, bigposts = bigposts + ?, megaposts = megaposts + ?", where),
incrementLiked: acc.SimpleUpdate("users", "liked = liked + ?, lastLiked = UTC_TIMESTAMP()", "uid = ?"), incrementLiked: acc.SimpleUpdate("users", "liked = liked + ?, lastLiked = UTC_TIMESTAMP()", where),
decrementLiked: acc.SimpleUpdate("users", "liked = liked - ?", "uid = ?"), decrementLiked: acc.SimpleUpdate("users", "liked = liked - ?", where),
//recalcLastLiked: acc... //recalcLastLiked: acc...
updateLastIP: acc.SimpleUpdate("users", "last_ip = ?", "uid = ?"), updateLastIP: acc.SimpleUpdate("users", "last_ip = ?", where),
setPassword: acc.SimpleUpdate("users", "password = ?, salt = ?", "uid = ?"), setPassword: acc.SimpleUpdate("users", "password = ?, salt = ?", where),
} }
return acc.FirstError() return acc.FirstError()
}) })
@ -231,12 +232,17 @@ func (user *User) Delete() error {
return err return err
} }
func (user *User) ChangeName(username string) (err error) { func (user *User) bindStmt(stmt *sql.Stmt, params ...interface{}) (err error) {
_, err = userStmts.setUsername.Exec(username, user.ID) params = append(params, user.ID)
_, err = stmt.Exec(params...)
user.CacheRemove() user.CacheRemove()
return err return err
} }
func (user *User) ChangeName(username string) (err error) {
return user.bindStmt(userStmts.setUsername, username)
}
func (user *User) ChangeAvatar(avatar string) (err error) { func (user *User) ChangeAvatar(avatar string) (err error) {
_, err = userStmts.setAvatar.Exec(avatar, user.ID) _, err = userStmts.setAvatar.Exec(avatar, user.ID)
user.CacheRemove() user.CacheRemove()

View File

@ -134,8 +134,8 @@ func BuildWidget(dock string, header *Header) (sbody string) {
widgets = Docks.RightOfNav widgets = Docks.RightOfNav
case "topMenu": case "topMenu":
// 1 = id for the default menu // 1 = id for the default menu
mhold := Menus.Get(1) mhold, err := Menus.Get(1)
if mhold != nil { if err == nil {
err := mhold.Build(header.Writer, &header.CurrentUser) err := mhold.Build(header.Writer, &header.CurrentUser)
if err != nil { if err != nil {
LogError(err) LogError(err)

View File

@ -94,7 +94,10 @@ if %errorlevel% neq 0 (
echo Updating Gosora echo Updating Gosora
copy ./schema/schema.json ./schema/lastSchema.json cd schema
del /Q lastSchema.json
copy schema.json lastSchema.json
cd ..
git pull origin master git pull origin master
if %errorlevel% neq 0 ( if %errorlevel% neq 0 (
pause pause

View File

@ -49,6 +49,10 @@ var RouteMap = map[string]interface{}{
"routePanelWordFiltersDeleteSubmit": routePanelWordFiltersDeleteSubmit, "routePanelWordFiltersDeleteSubmit": routePanelWordFiltersDeleteSubmit,
"routePanelThemes": routePanelThemes, "routePanelThemes": routePanelThemes,
"routePanelThemesSetDefault": routePanelThemesSetDefault, "routePanelThemesSetDefault": routePanelThemesSetDefault,
"routePanelThemesMenus": routePanelThemesMenus,
"routePanelThemesMenusEdit": routePanelThemesMenusEdit,
"routePanelThemesMenuItemEdit": routePanelThemesMenuItemEdit,
"routePanelThemesMenuItemEditSubmit": routePanelThemesMenuItemEditSubmit,
"routePanelPlugins": routePanelPlugins, "routePanelPlugins": routePanelPlugins,
"routePanelPluginsActivate": routePanelPluginsActivate, "routePanelPluginsActivate": routePanelPluginsActivate,
"routePanelPluginsDeactivate": routePanelPluginsDeactivate, "routePanelPluginsDeactivate": routePanelPluginsDeactivate,
@ -158,81 +162,85 @@ var routeMapEnum = map[string]int{
"routePanelWordFiltersDeleteSubmit": 27, "routePanelWordFiltersDeleteSubmit": 27,
"routePanelThemes": 28, "routePanelThemes": 28,
"routePanelThemesSetDefault": 29, "routePanelThemesSetDefault": 29,
"routePanelPlugins": 30, "routePanelThemesMenus": 30,
"routePanelPluginsActivate": 31, "routePanelThemesMenusEdit": 31,
"routePanelPluginsDeactivate": 32, "routePanelThemesMenuItemEdit": 32,
"routePanelPluginsInstall": 33, "routePanelThemesMenuItemEditSubmit": 33,
"routePanelUsers": 34, "routePanelPlugins": 34,
"routePanelUsersEdit": 35, "routePanelPluginsActivate": 35,
"routePanelUsersEditSubmit": 36, "routePanelPluginsDeactivate": 36,
"routePanelAnalyticsViews": 37, "routePanelPluginsInstall": 37,
"routePanelAnalyticsRoutes": 38, "routePanelUsers": 38,
"routePanelAnalyticsAgents": 39, "routePanelUsersEdit": 39,
"routePanelAnalyticsSystems": 40, "routePanelUsersEditSubmit": 40,
"routePanelAnalyticsLanguages": 41, "routePanelAnalyticsViews": 41,
"routePanelAnalyticsReferrers": 42, "routePanelAnalyticsRoutes": 42,
"routePanelAnalyticsRouteViews": 43, "routePanelAnalyticsAgents": 43,
"routePanelAnalyticsAgentViews": 44, "routePanelAnalyticsSystems": 44,
"routePanelAnalyticsForumViews": 45, "routePanelAnalyticsLanguages": 45,
"routePanelAnalyticsSystemViews": 46, "routePanelAnalyticsReferrers": 46,
"routePanelAnalyticsLanguageViews": 47, "routePanelAnalyticsRouteViews": 47,
"routePanelAnalyticsReferrerViews": 48, "routePanelAnalyticsAgentViews": 48,
"routePanelAnalyticsPosts": 49, "routePanelAnalyticsForumViews": 49,
"routePanelAnalyticsTopics": 50, "routePanelAnalyticsSystemViews": 50,
"routePanelAnalyticsForums": 51, "routePanelAnalyticsLanguageViews": 51,
"routePanelGroups": 52, "routePanelAnalyticsReferrerViews": 52,
"routePanelGroupsEdit": 53, "routePanelAnalyticsPosts": 53,
"routePanelGroupsEditPerms": 54, "routePanelAnalyticsTopics": 54,
"routePanelGroupsEditSubmit": 55, "routePanelAnalyticsForums": 55,
"routePanelGroupsEditPermsSubmit": 56, "routePanelGroups": 56,
"routePanelGroupsCreateSubmit": 57, "routePanelGroupsEdit": 57,
"routePanelBackups": 58, "routePanelGroupsEditPerms": 58,
"routePanelLogsMod": 59, "routePanelGroupsEditSubmit": 59,
"routePanelDebug": 60, "routePanelGroupsEditPermsSubmit": 60,
"routePanelDashboard": 61, "routePanelGroupsCreateSubmit": 61,
"routes.AccountEditCritical": 62, "routePanelBackups": 62,
"routeAccountEditCriticalSubmit": 63, "routePanelLogsMod": 63,
"routeAccountEditAvatar": 64, "routePanelDebug": 64,
"routeAccountEditAvatarSubmit": 65, "routePanelDashboard": 65,
"routeAccountEditUsername": 66, "routes.AccountEditCritical": 66,
"routeAccountEditUsernameSubmit": 67, "routeAccountEditCriticalSubmit": 67,
"routeAccountEditEmail": 68, "routeAccountEditAvatar": 68,
"routeAccountEditEmailTokenSubmit": 69, "routeAccountEditAvatarSubmit": 69,
"routes.ViewProfile": 70, "routeAccountEditUsername": 70,
"routes.BanUserSubmit": 71, "routeAccountEditUsernameSubmit": 71,
"routes.UnbanUser": 72, "routeAccountEditEmail": 72,
"routes.ActivateUser": 73, "routeAccountEditEmailTokenSubmit": 73,
"routes.IPSearch": 74, "routes.ViewProfile": 74,
"routes.CreateTopicSubmit": 75, "routes.BanUserSubmit": 75,
"routes.EditTopicSubmit": 76, "routes.UnbanUser": 76,
"routes.DeleteTopicSubmit": 77, "routes.ActivateUser": 77,
"routes.StickTopicSubmit": 78, "routes.IPSearch": 78,
"routes.UnstickTopicSubmit": 79, "routes.CreateTopicSubmit": 79,
"routes.LockTopicSubmit": 80, "routes.EditTopicSubmit": 80,
"routes.UnlockTopicSubmit": 81, "routes.DeleteTopicSubmit": 81,
"routes.MoveTopicSubmit": 82, "routes.StickTopicSubmit": 82,
"routeLikeTopicSubmit": 83, "routes.UnstickTopicSubmit": 83,
"routes.ViewTopic": 84, "routes.LockTopicSubmit": 84,
"routes.CreateReplySubmit": 85, "routes.UnlockTopicSubmit": 85,
"routes.ReplyEditSubmit": 86, "routes.MoveTopicSubmit": 86,
"routes.ReplyDeleteSubmit": 87, "routeLikeTopicSubmit": 87,
"routeReplyLikeSubmit": 88, "routes.ViewTopic": 88,
"routeProfileReplyCreateSubmit": 89, "routes.CreateReplySubmit": 89,
"routes.ProfileReplyEditSubmit": 90, "routes.ReplyEditSubmit": 90,
"routes.ProfileReplyDeleteSubmit": 91, "routes.ReplyDeleteSubmit": 91,
"routes.PollVote": 92, "routeReplyLikeSubmit": 92,
"routes.PollResults": 93, "routeProfileReplyCreateSubmit": 93,
"routes.AccountLogin": 94, "routes.ProfileReplyEditSubmit": 94,
"routes.AccountRegister": 95, "routes.ProfileReplyDeleteSubmit": 95,
"routeLogout": 96, "routes.PollVote": 96,
"routes.AccountLoginSubmit": 97, "routes.PollResults": 97,
"routes.AccountRegisterSubmit": 98, "routes.AccountLogin": 98,
"routeDynamic": 99, "routes.AccountRegister": 99,
"routeUploads": 100, "routeLogout": 100,
"routes.StaticFile": 101, "routes.AccountLoginSubmit": 101,
"routes.RobotsTxt": 102, "routes.AccountRegisterSubmit": 102,
"routes.SitemapXml": 103, "routeDynamic": 103,
"BadRoute": 104, "routeUploads": 104,
"routes.StaticFile": 105,
"routes.RobotsTxt": 106,
"routes.SitemapXml": 107,
"BadRoute": 108,
} }
var reverseRouteMapEnum = map[int]string{ var reverseRouteMapEnum = map[int]string{
0: "routeAPI", 0: "routeAPI",
@ -265,81 +273,85 @@ var reverseRouteMapEnum = map[int]string{
27: "routePanelWordFiltersDeleteSubmit", 27: "routePanelWordFiltersDeleteSubmit",
28: "routePanelThemes", 28: "routePanelThemes",
29: "routePanelThemesSetDefault", 29: "routePanelThemesSetDefault",
30: "routePanelPlugins", 30: "routePanelThemesMenus",
31: "routePanelPluginsActivate", 31: "routePanelThemesMenusEdit",
32: "routePanelPluginsDeactivate", 32: "routePanelThemesMenuItemEdit",
33: "routePanelPluginsInstall", 33: "routePanelThemesMenuItemEditSubmit",
34: "routePanelUsers", 34: "routePanelPlugins",
35: "routePanelUsersEdit", 35: "routePanelPluginsActivate",
36: "routePanelUsersEditSubmit", 36: "routePanelPluginsDeactivate",
37: "routePanelAnalyticsViews", 37: "routePanelPluginsInstall",
38: "routePanelAnalyticsRoutes", 38: "routePanelUsers",
39: "routePanelAnalyticsAgents", 39: "routePanelUsersEdit",
40: "routePanelAnalyticsSystems", 40: "routePanelUsersEditSubmit",
41: "routePanelAnalyticsLanguages", 41: "routePanelAnalyticsViews",
42: "routePanelAnalyticsReferrers", 42: "routePanelAnalyticsRoutes",
43: "routePanelAnalyticsRouteViews", 43: "routePanelAnalyticsAgents",
44: "routePanelAnalyticsAgentViews", 44: "routePanelAnalyticsSystems",
45: "routePanelAnalyticsForumViews", 45: "routePanelAnalyticsLanguages",
46: "routePanelAnalyticsSystemViews", 46: "routePanelAnalyticsReferrers",
47: "routePanelAnalyticsLanguageViews", 47: "routePanelAnalyticsRouteViews",
48: "routePanelAnalyticsReferrerViews", 48: "routePanelAnalyticsAgentViews",
49: "routePanelAnalyticsPosts", 49: "routePanelAnalyticsForumViews",
50: "routePanelAnalyticsTopics", 50: "routePanelAnalyticsSystemViews",
51: "routePanelAnalyticsForums", 51: "routePanelAnalyticsLanguageViews",
52: "routePanelGroups", 52: "routePanelAnalyticsReferrerViews",
53: "routePanelGroupsEdit", 53: "routePanelAnalyticsPosts",
54: "routePanelGroupsEditPerms", 54: "routePanelAnalyticsTopics",
55: "routePanelGroupsEditSubmit", 55: "routePanelAnalyticsForums",
56: "routePanelGroupsEditPermsSubmit", 56: "routePanelGroups",
57: "routePanelGroupsCreateSubmit", 57: "routePanelGroupsEdit",
58: "routePanelBackups", 58: "routePanelGroupsEditPerms",
59: "routePanelLogsMod", 59: "routePanelGroupsEditSubmit",
60: "routePanelDebug", 60: "routePanelGroupsEditPermsSubmit",
61: "routePanelDashboard", 61: "routePanelGroupsCreateSubmit",
62: "routes.AccountEditCritical", 62: "routePanelBackups",
63: "routeAccountEditCriticalSubmit", 63: "routePanelLogsMod",
64: "routeAccountEditAvatar", 64: "routePanelDebug",
65: "routeAccountEditAvatarSubmit", 65: "routePanelDashboard",
66: "routeAccountEditUsername", 66: "routes.AccountEditCritical",
67: "routeAccountEditUsernameSubmit", 67: "routeAccountEditCriticalSubmit",
68: "routeAccountEditEmail", 68: "routeAccountEditAvatar",
69: "routeAccountEditEmailTokenSubmit", 69: "routeAccountEditAvatarSubmit",
70: "routes.ViewProfile", 70: "routeAccountEditUsername",
71: "routes.BanUserSubmit", 71: "routeAccountEditUsernameSubmit",
72: "routes.UnbanUser", 72: "routeAccountEditEmail",
73: "routes.ActivateUser", 73: "routeAccountEditEmailTokenSubmit",
74: "routes.IPSearch", 74: "routes.ViewProfile",
75: "routes.CreateTopicSubmit", 75: "routes.BanUserSubmit",
76: "routes.EditTopicSubmit", 76: "routes.UnbanUser",
77: "routes.DeleteTopicSubmit", 77: "routes.ActivateUser",
78: "routes.StickTopicSubmit", 78: "routes.IPSearch",
79: "routes.UnstickTopicSubmit", 79: "routes.CreateTopicSubmit",
80: "routes.LockTopicSubmit", 80: "routes.EditTopicSubmit",
81: "routes.UnlockTopicSubmit", 81: "routes.DeleteTopicSubmit",
82: "routes.MoveTopicSubmit", 82: "routes.StickTopicSubmit",
83: "routeLikeTopicSubmit", 83: "routes.UnstickTopicSubmit",
84: "routes.ViewTopic", 84: "routes.LockTopicSubmit",
85: "routes.CreateReplySubmit", 85: "routes.UnlockTopicSubmit",
86: "routes.ReplyEditSubmit", 86: "routes.MoveTopicSubmit",
87: "routes.ReplyDeleteSubmit", 87: "routeLikeTopicSubmit",
88: "routeReplyLikeSubmit", 88: "routes.ViewTopic",
89: "routeProfileReplyCreateSubmit", 89: "routes.CreateReplySubmit",
90: "routes.ProfileReplyEditSubmit", 90: "routes.ReplyEditSubmit",
91: "routes.ProfileReplyDeleteSubmit", 91: "routes.ReplyDeleteSubmit",
92: "routes.PollVote", 92: "routeReplyLikeSubmit",
93: "routes.PollResults", 93: "routeProfileReplyCreateSubmit",
94: "routes.AccountLogin", 94: "routes.ProfileReplyEditSubmit",
95: "routes.AccountRegister", 95: "routes.ProfileReplyDeleteSubmit",
96: "routeLogout", 96: "routes.PollVote",
97: "routes.AccountLoginSubmit", 97: "routes.PollResults",
98: "routes.AccountRegisterSubmit", 98: "routes.AccountLogin",
99: "routeDynamic", 99: "routes.AccountRegister",
100: "routeUploads", 100: "routeLogout",
101: "routes.StaticFile", 101: "routes.AccountLoginSubmit",
102: "routes.RobotsTxt", 102: "routes.AccountRegisterSubmit",
103: "routes.SitemapXml", 103: "routeDynamic",
104: "BadRoute", 104: "routeUploads",
105: "routes.StaticFile",
106: "routes.RobotsTxt",
107: "routes.SitemapXml",
108: "BadRoute",
} }
var osMapEnum = map[string]int{ var osMapEnum = map[string]int{
"unknown": 0, "unknown": 0,
@ -635,7 +647,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(101) counters.RouteViewCounter.Bump(105)
req.URL.Path += extraData req.URL.Path += extraData
routes.StaticFile(w, req) routes.StaticFile(w, req)
return return
@ -1038,8 +1050,26 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
counters.RouteViewCounter.Bump(29) counters.RouteViewCounter.Bump(29)
err = routePanelThemesSetDefault(w,req,user,extraData) err = routePanelThemesSetDefault(w,req,user,extraData)
case "/panel/plugins/": case "/panel/themes/menus/":
counters.RouteViewCounter.Bump(30) counters.RouteViewCounter.Bump(30)
err = routePanelThemesMenus(w,req,user)
case "/panel/themes/menus/edit/":
counters.RouteViewCounter.Bump(31)
err = routePanelThemesMenusEdit(w,req,user,extraData)
case "/panel/themes/menus/item/edit/":
counters.RouteViewCounter.Bump(32)
err = routePanelThemesMenuItemEdit(w,req,user,extraData)
case "/panel/themes/menus/item/edit/submit/":
err = common.NoSessionMismatch(w,req,user)
if err != nil {
router.handleError(err,w,req,user)
return
}
counters.RouteViewCounter.Bump(33)
err = routePanelThemesMenuItemEditSubmit(w,req,user,extraData)
case "/panel/plugins/":
counters.RouteViewCounter.Bump(34)
err = routePanelPlugins(w,req,user) err = routePanelPlugins(w,req,user)
case "/panel/plugins/activate/": case "/panel/plugins/activate/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1048,7 +1078,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(31) counters.RouteViewCounter.Bump(35)
err = routePanelPluginsActivate(w,req,user,extraData) err = routePanelPluginsActivate(w,req,user,extraData)
case "/panel/plugins/deactivate/": case "/panel/plugins/deactivate/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1057,7 +1087,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(32) counters.RouteViewCounter.Bump(36)
err = routePanelPluginsDeactivate(w,req,user,extraData) err = routePanelPluginsDeactivate(w,req,user,extraData)
case "/panel/plugins/install/": case "/panel/plugins/install/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1066,13 +1096,13 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(33) counters.RouteViewCounter.Bump(37)
err = routePanelPluginsInstall(w,req,user,extraData) err = routePanelPluginsInstall(w,req,user,extraData)
case "/panel/users/": case "/panel/users/":
counters.RouteViewCounter.Bump(34) counters.RouteViewCounter.Bump(38)
err = routePanelUsers(w,req,user) err = routePanelUsers(w,req,user)
case "/panel/users/edit/": case "/panel/users/edit/":
counters.RouteViewCounter.Bump(35) counters.RouteViewCounter.Bump(39)
err = routePanelUsersEdit(w,req,user,extraData) err = routePanelUsersEdit(w,req,user,extraData)
case "/panel/users/edit/submit/": case "/panel/users/edit/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1081,7 +1111,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(36) counters.RouteViewCounter.Bump(40)
err = routePanelUsersEditSubmit(w,req,user,extraData) err = routePanelUsersEditSubmit(w,req,user,extraData)
case "/panel/analytics/views/": case "/panel/analytics/views/":
err = common.ParseForm(w,req,user) err = common.ParseForm(w,req,user)
@ -1090,7 +1120,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(37) counters.RouteViewCounter.Bump(41)
err = routePanelAnalyticsViews(w,req,user) err = routePanelAnalyticsViews(w,req,user)
case "/panel/analytics/routes/": case "/panel/analytics/routes/":
err = common.ParseForm(w,req,user) err = common.ParseForm(w,req,user)
@ -1099,7 +1129,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(38) counters.RouteViewCounter.Bump(42)
err = routePanelAnalyticsRoutes(w,req,user) err = routePanelAnalyticsRoutes(w,req,user)
case "/panel/analytics/agents/": case "/panel/analytics/agents/":
err = common.ParseForm(w,req,user) err = common.ParseForm(w,req,user)
@ -1108,7 +1138,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(39) counters.RouteViewCounter.Bump(43)
err = routePanelAnalyticsAgents(w,req,user) err = routePanelAnalyticsAgents(w,req,user)
case "/panel/analytics/systems/": case "/panel/analytics/systems/":
err = common.ParseForm(w,req,user) err = common.ParseForm(w,req,user)
@ -1117,7 +1147,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(40) counters.RouteViewCounter.Bump(44)
err = routePanelAnalyticsSystems(w,req,user) err = routePanelAnalyticsSystems(w,req,user)
case "/panel/analytics/langs/": case "/panel/analytics/langs/":
err = common.ParseForm(w,req,user) err = common.ParseForm(w,req,user)
@ -1126,7 +1156,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(41) counters.RouteViewCounter.Bump(45)
err = routePanelAnalyticsLanguages(w,req,user) err = routePanelAnalyticsLanguages(w,req,user)
case "/panel/analytics/referrers/": case "/panel/analytics/referrers/":
err = common.ParseForm(w,req,user) err = common.ParseForm(w,req,user)
@ -1135,25 +1165,25 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(42) counters.RouteViewCounter.Bump(46)
err = routePanelAnalyticsReferrers(w,req,user) err = routePanelAnalyticsReferrers(w,req,user)
case "/panel/analytics/route/": case "/panel/analytics/route/":
counters.RouteViewCounter.Bump(43) counters.RouteViewCounter.Bump(47)
err = routePanelAnalyticsRouteViews(w,req,user,extraData) err = routePanelAnalyticsRouteViews(w,req,user,extraData)
case "/panel/analytics/agent/": case "/panel/analytics/agent/":
counters.RouteViewCounter.Bump(44) counters.RouteViewCounter.Bump(48)
err = routePanelAnalyticsAgentViews(w,req,user,extraData) err = routePanelAnalyticsAgentViews(w,req,user,extraData)
case "/panel/analytics/forum/": case "/panel/analytics/forum/":
counters.RouteViewCounter.Bump(45) counters.RouteViewCounter.Bump(49)
err = routePanelAnalyticsForumViews(w,req,user,extraData) err = routePanelAnalyticsForumViews(w,req,user,extraData)
case "/panel/analytics/system/": case "/panel/analytics/system/":
counters.RouteViewCounter.Bump(46) counters.RouteViewCounter.Bump(50)
err = routePanelAnalyticsSystemViews(w,req,user,extraData) err = routePanelAnalyticsSystemViews(w,req,user,extraData)
case "/panel/analytics/lang/": case "/panel/analytics/lang/":
counters.RouteViewCounter.Bump(47) counters.RouteViewCounter.Bump(51)
err = routePanelAnalyticsLanguageViews(w,req,user,extraData) err = routePanelAnalyticsLanguageViews(w,req,user,extraData)
case "/panel/analytics/referrer/": case "/panel/analytics/referrer/":
counters.RouteViewCounter.Bump(48) counters.RouteViewCounter.Bump(52)
err = routePanelAnalyticsReferrerViews(w,req,user,extraData) err = routePanelAnalyticsReferrerViews(w,req,user,extraData)
case "/panel/analytics/posts/": case "/panel/analytics/posts/":
err = common.ParseForm(w,req,user) err = common.ParseForm(w,req,user)
@ -1162,7 +1192,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(49) counters.RouteViewCounter.Bump(53)
err = routePanelAnalyticsPosts(w,req,user) err = routePanelAnalyticsPosts(w,req,user)
case "/panel/analytics/topics/": case "/panel/analytics/topics/":
err = common.ParseForm(w,req,user) err = common.ParseForm(w,req,user)
@ -1171,7 +1201,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(50) counters.RouteViewCounter.Bump(54)
err = routePanelAnalyticsTopics(w,req,user) err = routePanelAnalyticsTopics(w,req,user)
case "/panel/analytics/forums/": case "/panel/analytics/forums/":
err = common.ParseForm(w,req,user) err = common.ParseForm(w,req,user)
@ -1180,16 +1210,16 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(51) counters.RouteViewCounter.Bump(55)
err = routePanelAnalyticsForums(w,req,user) err = routePanelAnalyticsForums(w,req,user)
case "/panel/groups/": case "/panel/groups/":
counters.RouteViewCounter.Bump(52) counters.RouteViewCounter.Bump(56)
err = routePanelGroups(w,req,user) err = routePanelGroups(w,req,user)
case "/panel/groups/edit/": case "/panel/groups/edit/":
counters.RouteViewCounter.Bump(53) counters.RouteViewCounter.Bump(57)
err = routePanelGroupsEdit(w,req,user,extraData) err = routePanelGroupsEdit(w,req,user,extraData)
case "/panel/groups/edit/perms/": case "/panel/groups/edit/perms/":
counters.RouteViewCounter.Bump(54) counters.RouteViewCounter.Bump(58)
err = routePanelGroupsEditPerms(w,req,user,extraData) err = routePanelGroupsEditPerms(w,req,user,extraData)
case "/panel/groups/edit/submit/": case "/panel/groups/edit/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1198,7 +1228,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(55) counters.RouteViewCounter.Bump(59)
err = routePanelGroupsEditSubmit(w,req,user,extraData) err = routePanelGroupsEditSubmit(w,req,user,extraData)
case "/panel/groups/edit/perms/submit/": case "/panel/groups/edit/perms/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1207,7 +1237,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(56) counters.RouteViewCounter.Bump(60)
err = routePanelGroupsEditPermsSubmit(w,req,user,extraData) err = routePanelGroupsEditPermsSubmit(w,req,user,extraData)
case "/panel/groups/create/": case "/panel/groups/create/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1216,7 +1246,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(57) counters.RouteViewCounter.Bump(61)
err = routePanelGroupsCreateSubmit(w,req,user) err = routePanelGroupsCreateSubmit(w,req,user)
case "/panel/backups/": case "/panel/backups/":
err = common.SuperAdminOnly(w,req,user) err = common.SuperAdminOnly(w,req,user)
@ -1225,10 +1255,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(58) counters.RouteViewCounter.Bump(62)
err = routePanelBackups(w,req,user,extraData) err = routePanelBackups(w,req,user,extraData)
case "/panel/logs/mod/": case "/panel/logs/mod/":
counters.RouteViewCounter.Bump(59) counters.RouteViewCounter.Bump(63)
err = routePanelLogsMod(w,req,user) err = routePanelLogsMod(w,req,user)
case "/panel/debug/": case "/panel/debug/":
err = common.AdminOnly(w,req,user) err = common.AdminOnly(w,req,user)
@ -1237,10 +1267,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(60) counters.RouteViewCounter.Bump(64)
err = routePanelDebug(w,req,user) err = routePanelDebug(w,req,user)
default: default:
counters.RouteViewCounter.Bump(61) counters.RouteViewCounter.Bump(65)
err = routePanelDashboard(w,req,user) err = routePanelDashboard(w,req,user)
} }
if err != nil { if err != nil {
@ -1255,7 +1285,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(62) counters.RouteViewCounter.Bump(66)
err = routes.AccountEditCritical(w,req,user) err = routes.AccountEditCritical(w,req,user)
case "/user/edit/critical/submit/": case "/user/edit/critical/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1270,7 +1300,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(63) counters.RouteViewCounter.Bump(67)
err = routeAccountEditCriticalSubmit(w,req,user) err = routeAccountEditCriticalSubmit(w,req,user)
case "/user/edit/avatar/": case "/user/edit/avatar/":
err = common.MemberOnly(w,req,user) err = common.MemberOnly(w,req,user)
@ -1279,7 +1309,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(64) counters.RouteViewCounter.Bump(68)
err = routeAccountEditAvatar(w,req,user) err = routeAccountEditAvatar(w,req,user)
case "/user/edit/avatar/submit/": case "/user/edit/avatar/submit/":
err = common.MemberOnly(w,req,user) err = common.MemberOnly(w,req,user)
@ -1299,7 +1329,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(65) counters.RouteViewCounter.Bump(69)
err = routeAccountEditAvatarSubmit(w,req,user) err = routeAccountEditAvatarSubmit(w,req,user)
case "/user/edit/username/": case "/user/edit/username/":
err = common.MemberOnly(w,req,user) err = common.MemberOnly(w,req,user)
@ -1308,7 +1338,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(66) counters.RouteViewCounter.Bump(70)
err = routeAccountEditUsername(w,req,user) err = routeAccountEditUsername(w,req,user)
case "/user/edit/username/submit/": case "/user/edit/username/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1323,7 +1353,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(67) counters.RouteViewCounter.Bump(71)
err = routeAccountEditUsernameSubmit(w,req,user) err = routeAccountEditUsernameSubmit(w,req,user)
case "/user/edit/email/": case "/user/edit/email/":
err = common.MemberOnly(w,req,user) err = common.MemberOnly(w,req,user)
@ -1332,7 +1362,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(68) counters.RouteViewCounter.Bump(72)
err = routeAccountEditEmail(w,req,user) err = routeAccountEditEmail(w,req,user)
case "/user/edit/token/": case "/user/edit/token/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1347,11 +1377,11 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(69) counters.RouteViewCounter.Bump(73)
err = routeAccountEditEmailTokenSubmit(w,req,user,extraData) err = routeAccountEditEmailTokenSubmit(w,req,user,extraData)
default: default:
req.URL.Path += extraData req.URL.Path += extraData
counters.RouteViewCounter.Bump(70) counters.RouteViewCounter.Bump(74)
err = routes.ViewProfile(w,req,user) err = routes.ViewProfile(w,req,user)
} }
if err != nil { if err != nil {
@ -1372,7 +1402,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(71) counters.RouteViewCounter.Bump(75)
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)
@ -1387,7 +1417,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(72) counters.RouteViewCounter.Bump(76)
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)
@ -1402,7 +1432,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(73) counters.RouteViewCounter.Bump(77)
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)
@ -1411,7 +1441,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(74) counters.RouteViewCounter.Bump(78)
err = routes.IPSearch(w,req,user) err = routes.IPSearch(w,req,user)
} }
if err != nil { if err != nil {
@ -1437,7 +1467,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(75) counters.RouteViewCounter.Bump(79)
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)
@ -1452,7 +1482,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(76) counters.RouteViewCounter.Bump(80)
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)
@ -1468,7 +1498,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
} }
req.URL.Path += extraData req.URL.Path += extraData
counters.RouteViewCounter.Bump(77) counters.RouteViewCounter.Bump(81)
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)
@ -1483,7 +1513,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(78) counters.RouteViewCounter.Bump(82)
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)
@ -1498,7 +1528,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(79) counters.RouteViewCounter.Bump(83)
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)
@ -1514,7 +1544,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
} }
req.URL.Path += extraData req.URL.Path += extraData
counters.RouteViewCounter.Bump(80) counters.RouteViewCounter.Bump(84)
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)
@ -1529,7 +1559,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(81) counters.RouteViewCounter.Bump(85)
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)
@ -1544,7 +1574,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(82) counters.RouteViewCounter.Bump(86)
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)
@ -1565,10 +1595,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(83) counters.RouteViewCounter.Bump(87)
err = routeLikeTopicSubmit(w,req,user,extraData) err = routeLikeTopicSubmit(w,req,user,extraData)
default: default:
counters.RouteViewCounter.Bump(84) counters.RouteViewCounter.Bump(88)
err = routes.ViewTopic(w,req,user, extraData) err = routes.ViewTopic(w,req,user, extraData)
} }
if err != nil { if err != nil {
@ -1594,7 +1624,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(85) counters.RouteViewCounter.Bump(89)
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)
@ -1609,7 +1639,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(86) counters.RouteViewCounter.Bump(90)
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)
@ -1624,7 +1654,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(87) counters.RouteViewCounter.Bump(91)
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)
@ -1645,7 +1675,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(88) counters.RouteViewCounter.Bump(92)
err = routeReplyLikeSubmit(w,req,user,extraData) err = routeReplyLikeSubmit(w,req,user,extraData)
} }
if err != nil { if err != nil {
@ -1666,7 +1696,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(89) counters.RouteViewCounter.Bump(93)
err = routeProfileReplyCreateSubmit(w,req,user) err = routeProfileReplyCreateSubmit(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)
@ -1681,7 +1711,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(90) counters.RouteViewCounter.Bump(94)
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)
@ -1696,7 +1726,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(91) counters.RouteViewCounter.Bump(95)
err = routes.ProfileReplyDeleteSubmit(w,req,user,extraData) err = routes.ProfileReplyDeleteSubmit(w,req,user,extraData)
} }
if err != nil { if err != nil {
@ -1717,10 +1747,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(92) counters.RouteViewCounter.Bump(96)
err = routes.PollVote(w,req,user,extraData) err = routes.PollVote(w,req,user,extraData)
case "/poll/results/": case "/poll/results/":
counters.RouteViewCounter.Bump(93) counters.RouteViewCounter.Bump(97)
err = routes.PollResults(w,req,user,extraData) err = routes.PollResults(w,req,user,extraData)
} }
if err != nil { if err != nil {
@ -1729,10 +1759,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
case "/accounts": case "/accounts":
switch(req.URL.Path) { switch(req.URL.Path) {
case "/accounts/login/": case "/accounts/login/":
counters.RouteViewCounter.Bump(94) counters.RouteViewCounter.Bump(98)
err = routes.AccountLogin(w,req,user) err = routes.AccountLogin(w,req,user)
case "/accounts/create/": case "/accounts/create/":
counters.RouteViewCounter.Bump(95) counters.RouteViewCounter.Bump(99)
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)
@ -1747,7 +1777,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(96) counters.RouteViewCounter.Bump(100)
err = routeLogout(w,req,user) err = routeLogout(w,req,user)
case "/accounts/login/submit/": case "/accounts/login/submit/":
err = common.ParseForm(w,req,user) err = common.ParseForm(w,req,user)
@ -1756,7 +1786,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(97) counters.RouteViewCounter.Bump(101)
err = routes.AccountLoginSubmit(w,req,user) err = routes.AccountLoginSubmit(w,req,user)
case "/accounts/create/submit/": case "/accounts/create/submit/":
err = common.ParseForm(w,req,user) err = common.ParseForm(w,req,user)
@ -1765,7 +1795,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(98) counters.RouteViewCounter.Bump(102)
err = routes.AccountRegisterSubmit(w,req,user) err = routes.AccountRegisterSubmit(w,req,user)
} }
if err != nil { if err != nil {
@ -1782,7 +1812,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
common.NotFound(w,req,nil) common.NotFound(w,req,nil)
return return
} }
counters.RouteViewCounter.Bump(100) counters.RouteViewCounter.Bump(104)
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
@ -1791,14 +1821,14 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// 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(102) counters.RouteViewCounter.Bump(106)
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(103) counters.RouteViewCounter.Bump(107)
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)
@ -1827,7 +1857,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
router.RUnlock() router.RUnlock()
if ok { if ok {
counters.RouteViewCounter.Bump(99) // TODO: Be more specific about *which* dynamic route it is counters.RouteViewCounter.Bump(103) // 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 {
@ -1842,7 +1872,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
} else { } else {
router.DumpRequest(req,"Bad Route") router.DumpRequest(req,"Bad Route")
} }
counters.RouteViewCounter.Bump(104) counters.RouteViewCounter.Bump(108)
common.NotFound(w,req,nil) common.NotFound(w,req,nil)
} }
} }

View File

@ -2,17 +2,18 @@
package main package main
var dbTablePrimaryKeys = map[string]string{ var dbTablePrimaryKeys = map[string]string{
"users":"uid",
"topics":"tid",
"revisions":"reviseID",
"polls":"pollID",
"users_replies":"rid",
"activity_stream":"asid",
"word_filters":"wfid",
"users_groups":"gid", "users_groups":"gid",
"users_groups_scheduler":"uid", "users_groups_scheduler":"uid",
"forums":"fid", "users_replies":"rid",
"topics":"tid",
"replies":"rid", "replies":"rid",
"attachments":"attachID", "revisions":"reviseID",
"activity_stream":"asid",
"word_filters":"wfid",
"menus":"mid", "menus":"mid",
"users":"uid",
"menu_items":"miid",
"forums":"fid",
"attachments":"attachID",
"polls":"pollID",
} }

View File

@ -93,6 +93,8 @@
"panel_groups":"Group Manager", "panel_groups":"Group Manager",
"panel_edit_group":"Group Editor", "panel_edit_group":"Group Editor",
"panel_themes":"Theme Manager", "panel_themes":"Theme Manager",
"panel_themes_menus":"Menu Manager",
"panel_themes_menus_edit":"Menu Editor",
"panel_backups":"Backups", "panel_backups":"Backups",
"panel_mod_logs":"Moderation Logs", "panel_mod_logs":"Moderation Logs",
"panel_admin_logs":"Administration Logs", "panel_admin_logs":"Administration Logs",
@ -246,6 +248,66 @@
}, },
"TmplPhrases": { "TmplPhrases": {
"pipe":"|",
"menu_forums":"Forums",
"menu_topics":"Topics",
"menu_alerts":"Alerts",
"menu_account":"Account",
"menu_profile":"Profile",
"menu_panel":"Panel",
"menu_logout":"Logout",
"menu_login":"Login",
"menu_register":"Register",
"topics_click_topics_to_select":"Click the topics to select them",
"topics_new_topic":"New Topic",
"forum_locked":"Locked",
"topics_replies_suffix":" replies",
"forums_topics_suffix":" topics",
"topics_gap_likes_suffix":" likes",
"topics_likes_suffix":"likes",
"topics_last":"Last",
"topics_starter":"Starter",
"topic_like_count_suffix":" likes",
"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",
"topic_lock_button_text":"Lock",
"topic_unlock_button_text":"Unlock",
"topic_pin_button_text":"Pin",
"topic_unpin_button_text":"Unpin",
"topic_report_button_text":"Report",
"topic_flag_button_text":"Flag",
"panel_rank_admins":"Admins",
"panel_rank_mods":"Mods",
"panel_rank_banned":"Banned",
"panel_rank_guests":"Guests",
"panel_rank_members":"Members",
"panel_preset_announcements":"Announcements",
"panel_preset_member_only":"Member Only",
"panel_preset_staff_only":"Staff Only",
"panel_preset_admin_only":"Admin Only",
"panel_preset_archive":"Archive",
"panel_preset_public":"Public",
"panel_active_hidden":"Hidden",
"panel_perms_no_access":"No Access",
"panel_perms_read_only":"Read Only",
"panel_perms_can_post":"Can Post",
"panel_perms_can_moderate":"Can Moderate",
"panel_perms_custom":"Custom",
"panel_perms_default":"Default",
"panel_edit_button_text":"Edit",
"panel_delete_button_text":"Delete",
"menu_forums_tooltip":"Forum List", "menu_forums_tooltip":"Forum List",
"menu_forums_aria":"The Forum list", "menu_forums_aria":"The Forum list",
"menu_topics_tooltip":"Topic List", "menu_topics_tooltip":"Topic List",
@ -639,6 +701,25 @@
"panel_themes_default":"Default", "panel_themes_default":"Default",
"panel_themes_make_default":"Make Default", "panel_themes_make_default":"Make Default",
"panel_themes_menus_head":"Menus",
"panel_themes_menus_edit_head":"Menu Editor",
"panel_themes_menus_name":"Name",
"panel_themes_menus_htmlid":"HTML ID",
"panel_themes_menus_cssclass":"CSS Class",
"panel_themes_menus_position":"Position",
"panel_themes_menus_path":"Path",
"panel_themes_menus_aria":"Aria",
"panel_themes_menus_tooltip":"Tooltip",
"panel_themes_menus_tmplname":"Template",
"panel_themes_menus_permissions":"Who Can See",
"panel_themes_menus_everyone": "Everyone",
"panel_themes_menus_guestonly":"Guests",
"panel_themes_menus_memberonly":"Members",
"panel_themes_menus_supermodonly":"Super Mods",
"panel_themes_menus_adminonly":"Admins",
"panel_themes_menus_edit_update_button":"Update",
"panel_settings_head":"Settings", "panel_settings_head":"Settings",
"panel_setting_head":"Edit Setting", "panel_setting_head":"Edit Setting",
"panel_setting_name":"Setting Name", "panel_setting_name":"Setting Name",
@ -654,67 +735,5 @@
"panel_debug_uptime_label":"Uptime", "panel_debug_uptime_label":"Uptime",
"panel_debug_open_database_connections_label":"Open DB Conns", "panel_debug_open_database_connections_label":"Open DB Conns",
"panel_debug_adapter_label":"Adapter" "panel_debug_adapter_label":"Adapter"
},
"CSSPhrases": {
"pipe":"|",
"menu_forums":"Forums",
"menu_topics":"Topics",
"menu_alerts":"Alerts",
"menu_account":"Account",
"menu_profile":"Profile",
"menu_panel":"Panel",
"menu_logout":"Logout",
"menu_login":"Login",
"menu_register":"Register",
"topics_click_topics_to_select":"Click the topics to select them",
"topics_new_topic":"New Topic",
"forum_locked":"Locked",
"topics_replies_suffix":" replies",
"forums_topics_suffix":" topics",
"topics_gap_likes_suffix":" likes",
"topics_likes_suffix":"likes",
"topics_last":"Last",
"topics_starter":"Starter",
"topic_like_count_suffix":" likes",
"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",
"topic_lock_button_text":"Lock",
"topic_unlock_button_text":"Unlock",
"topic_pin_button_text":"Pin",
"topic_unpin_button_text":"Unpin",
"topic_report_button_text":"Report",
"topic_flag_button_text":"Flag",
"panel_rank_admins":"Admins",
"panel_rank_mods":"Mods",
"panel_rank_banned":"Banned",
"panel_rank_guests":"Guests",
"panel_rank_members":"Members",
"panel_preset_announcements":"Announcements",
"panel_preset_member_only":"Member Only",
"panel_preset_staff_only":"Staff Only",
"panel_preset_admin_only":"Admin Only",
"panel_preset_archive":"Archive",
"panel_preset_public":"Public",
"panel_active_hidden":"Hidden",
"panel_perms_no_access":"No Access",
"panel_perms_read_only":"Read Only",
"panel_perms_can_post":"Can Post",
"panel_perms_can_moderate":"Can Moderate",
"panel_perms_custom":"Custom",
"panel_perms_default":"Default",
"panel_edit_button_text":"Edit",
"panel_delete_button_text":"Delete"
} }
} }

View File

@ -78,7 +78,10 @@ func afterDBInit() (err error) {
if err != nil { if err != nil {
return err return err
} }
menuHold := common.Menus.Get(1) menuHold, err := common.Menus.Get(1)
if err != nil {
return err
}
fmt.Printf("menuHold: %+v\n", menuHold) fmt.Printf("menuHold: %+v\n", menuHold)
var b bytes.Buffer var b bytes.Buffer
menuHold.Build(&b, &common.GuestUser) menuHold.Build(&b, &common.GuestUser)

View File

@ -47,6 +47,7 @@ func routePanelDashboard(w http.ResponseWriter, r *http.Request, user common.Use
if ferr != nil { if ferr != nil {
return ferr return ferr
} }
headerVars.Title = common.GetTitlePhrase("panel_dashboard")
// We won't calculate this on the spot anymore, as the system doesn't seem to like it if we do multiple fetches simultaneously. Should we constantly calculate this on a background thread? Perhaps, the watchdog to scale back heavy features under load? One plus side is that we'd get immediate CPU percentages here instead of waiting it to kick in with WebSockets // We won't calculate this on the spot anymore, as the system doesn't seem to like it if we do multiple fetches simultaneously. Should we constantly calculate this on a background thread? Perhaps, the watchdog to scale back heavy features under load? One plus side is that we'd get immediate CPU percentages here instead of waiting it to kick in with WebSockets
var cpustr = "Unknown" var cpustr = "Unknown"
@ -167,7 +168,7 @@ func routePanelDashboard(w http.ResponseWriter, r *http.Request, user common.Use
gridElements = append(gridElements, common.GridElement{"dash-postsperuser", "5 posts / user / week", 14, "grid_stat stat_disabled", "", "", "Coming Soon!" /*"The average number of posts made by each active user over the past week"*/}) gridElements = append(gridElements, common.GridElement{"dash-postsperuser", "5 posts / user / week", 14, "grid_stat stat_disabled", "", "", "Coming Soon!" /*"The average number of posts made by each active user over the past week"*/})
} }
pi := common.PanelDashboardPage{common.GetTitlePhrase("panel_dashboard"), user, headerVars, stats, "dashboard", gridElements} pi := common.PanelDashboardPage{headerVars, stats, "dashboard", gridElements}
return panelRenderTemplate("panel_dashboard", w, r, user, &pi) return panelRenderTemplate("panel_dashboard", w, r, user, &pi)
} }
@ -2288,13 +2289,14 @@ func routePanelGroupsCreateSubmit(w http.ResponseWriter, r *http.Request, user c
} }
func routePanelThemes(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { func routePanelThemes(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
headerVars, stats, ferr := common.PanelUserCheck(w, r, &user) header, stats, ferr := common.PanelUserCheck(w, r, &user)
if ferr != nil { if ferr != nil {
return ferr return ferr
} }
if !user.Perms.ManageThemes { if !user.Perms.ManageThemes {
return common.NoPermissions(w, r, user) return common.NoPermissions(w, r, user)
} }
header.Title = common.GetTitlePhrase("panel_themes")
var pThemeList, vThemeList []*common.Theme var pThemeList, vThemeList []*common.Theme
for _, theme := range common.Themes { for _, theme := range common.Themes {
@ -2309,7 +2311,7 @@ func routePanelThemes(w http.ResponseWriter, r *http.Request, user common.User)
} }
pi := common.PanelThemesPage{common.GetTitlePhrase("panel_themes"), user, headerVars, stats, "themes", pThemeList, vThemeList} pi := common.PanelThemesPage{header, stats, "themes", pThemeList, vThemeList}
return panelRenderTemplate("panel_themes", w, r, user, &pi) return panelRenderTemplate("panel_themes", w, r, user, &pi)
} }
@ -2378,6 +2380,176 @@ func routePanelThemesSetDefault(w http.ResponseWriter, r *http.Request, user com
return nil return nil
} }
func routePanelThemesMenus(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
header, stats, ferr := common.PanelUserCheck(w, r, &user)
if ferr != nil {
return ferr
}
if !user.Perms.ManageThemes {
return common.NoPermissions(w, r, user)
}
header.Title = common.GetTitlePhrase("panel_themes_menus")
var menuList []common.PanelMenuListItem
for mid, list := range common.Menus.GetAllMap() {
menuList = append(menuList, common.PanelMenuListItem{
ID: mid,
ItemCount: len(list.List),
})
}
pi := common.PanelMenuListPage{header, stats, "themes", menuList}
return panelRenderTemplate("panel_themes_menus", w, r, user, &pi)
}
func routePanelThemesMenusEdit(w http.ResponseWriter, r *http.Request, user common.User, smid string) common.RouteError {
header, stats, ferr := common.PanelUserCheck(w, r, &user)
if ferr != nil {
return ferr
}
if !user.Perms.ManageThemes {
return common.NoPermissions(w, r, user)
}
// TODO: Something like Menu #1 for the title?
header.Title = common.GetTitlePhrase("panel_themes_menus_edit")
mid, err := strconv.Atoi(smid)
if err != nil {
return common.LocalError("Invalid integer", w, r, user)
}
menuHold, err := common.Menus.Get(mid)
if err == ErrNoRows {
return common.NotFound(w, r, header)
} else if err != nil {
return common.InternalError(err, w, r)
}
var menuList []common.MenuItem
for _, item := range menuHold.List {
var menuTmpls = map[string]common.MenuTmpl{
item.TmplName: menuHold.Parse(item.Name, []byte("{{.Name}}")),
}
var renderBuffer [][]byte
var variableIndices []int
renderBuffer, _ = menuHold.ScanItem(menuTmpls, item, renderBuffer, variableIndices)
var out string
for _, renderItem := range renderBuffer {
out += string(renderItem)
}
item.Name = out
if item.Name == "" {
item.Name = "???"
}
menuList = append(menuList, item)
}
pi := common.PanelMenuPage{header, stats, "themes", mid, menuList}
return panelRenderTemplate("panel_themes_menus_items", w, r, user, &pi)
}
func routePanelThemesMenuItemEdit(w http.ResponseWriter, r *http.Request, user common.User, sitemID string) common.RouteError {
header, stats, ferr := common.PanelUserCheck(w, r, &user)
if ferr != nil {
return ferr
}
if !user.Perms.ManageThemes {
return common.NoPermissions(w, r, user)
}
// TODO: Something like Menu #1 for the title?
header.Title = common.GetTitlePhrase("panel_themes_menus_edit")
itemID, err := strconv.Atoi(sitemID)
if err != nil {
return common.LocalError("Invalid integer", w, r, user)
}
menuItem, err := common.Menus.ItemStore().Get(itemID)
if err == ErrNoRows {
return common.NotFound(w, r, header)
} else if err != nil {
return common.InternalError(err, w, r)
}
pi := common.PanelMenuItemPage{header, stats, "themes", menuItem}
return panelRenderTemplate("panel_themes_menus_item_edit", w, r, user, &pi)
}
func routePanelThemesMenuItemEditSubmit(w http.ResponseWriter, r *http.Request, user common.User, sitemID string) common.RouteError {
_, ferr := common.SimplePanelUserCheck(w, r, &user)
if ferr != nil {
return ferr
}
isJs := (r.PostFormValue("js") == "1")
if !user.Perms.ManageThemes {
return common.NoPermissionsJSQ(w, r, user, isJs)
}
itemID, err := strconv.Atoi(sitemID)
if err != nil {
return common.LocalErrorJSQ("Invalid integer", w, r, user, isJs)
}
menuItem, err := common.Menus.ItemStore().Get(itemID)
if err == ErrNoRows {
return common.LocalErrorJSQ("This item doesn't exist.", w, r, user, isJs)
} else if err != nil {
return common.InternalErrorJSQ(err, w, r, isJs)
}
//menuItem = menuItem.Copy() // If we switch this for a pointer, we might need this as a scratchpad
var getItem = func(name string) string {
return html.EscapeString(strings.Replace(r.PostFormValue("item-"+name), "\n", "", -1))
}
menuItem.Name = getItem("name")
menuItem.HTMLID = getItem("htmlid")
menuItem.CSSClass = getItem("cssclass")
menuItem.Position = getItem("position")
if menuItem.Position != "left" && menuItem.Position != "right" {
menuItem.Position = "left"
}
menuItem.Path = getItem("path")
menuItem.Aria = getItem("aria")
menuItem.Tooltip = getItem("tooltip")
menuItem.TmplName = getItem("tmplname")
var perms = getItem("permissions")
switch perms {
case "everyone":
menuItem.GuestOnly = false
menuItem.MemberOnly = false
menuItem.SuperModOnly = false
menuItem.AdminOnly = false
case "guest-only":
menuItem.GuestOnly = true
menuItem.MemberOnly = false
menuItem.SuperModOnly = false
menuItem.AdminOnly = false
case "member-only":
menuItem.GuestOnly = false
menuItem.MemberOnly = true
menuItem.SuperModOnly = false
menuItem.AdminOnly = false
case "supermod-only":
menuItem.GuestOnly = false
menuItem.MemberOnly = true
menuItem.SuperModOnly = true
menuItem.AdminOnly = false
case "admin-only":
menuItem.GuestOnly = false
menuItem.MemberOnly = true
menuItem.SuperModOnly = true
menuItem.AdminOnly = true
}
err = menuItem.Commit()
if err != nil {
return common.InternalErrorJSQ(err, w, r, isJs)
}
return panelSuccessRedirect("/panel/themes/menus/item/edit/"+strconv.Itoa(itemID), w, r, isJs)
}
func routePanelBackups(w http.ResponseWriter, r *http.Request, user common.User, backupURL string) common.RouteError { func routePanelBackups(w http.ResponseWriter, r *http.Request, user common.User, backupURL string) common.RouteError {
headerVars, stats, ferr := common.PanelUserCheck(w, r, &user) headerVars, stats, ferr := common.PanelUserCheck(w, r, &user)
if ferr != nil { if ferr != nil {

View File

@ -26,6 +26,7 @@ func main() {
fmt.Println(r) fmt.Println(r)
debug.PrintStack() debug.PrintStack()
pressAnyKey(scanner) pressAnyKey(scanner)
log.Fatal("")
return return
} }
}() }()
@ -73,6 +74,7 @@ type SchemaFile struct {
} }
func patcher(scanner *bufio.Scanner) error { func patcher(scanner *bufio.Scanner) error {
fmt.Println("Loading the schema file")
data, err := ioutil.ReadFile("./schema/lastSchema.json") data, err := ioutil.ReadFile("./schema/lastSchema.json")
if err != nil { if err != nil {
return err return err
@ -84,6 +86,8 @@ func patcher(scanner *bufio.Scanner) error {
return err return err
} }
_ = schemaFile _ = schemaFile
fmt.Println("Applying the patches")
return patch0(scanner) return patch0(scanner)
} }

View File

@ -7,8 +7,18 @@ import (
"../query_gen/lib" "../query_gen/lib"
) )
func patch0(scanner *bufio.Scanner) error { func patch0(scanner *bufio.Scanner) (err error) {
err := execStmt(qgen.Builder.CreateTable("menus", "", "", err = execStmt(qgen.Builder.DropTable("menus"))
if err != nil {
return err
}
err = execStmt(qgen.Builder.DropTable("menu_items"))
if err != nil {
return err
}
err = execStmt(qgen.Builder.CreateTable("menus", "", "",
[]qgen.DBTableColumn{ []qgen.DBTableColumn{
qgen.DBTableColumn{"mid", "int", 0, false, true, ""}, qgen.DBTableColumn{"mid", "int", 0, false, true, ""},
}, },
@ -22,7 +32,9 @@ func patch0(scanner *bufio.Scanner) error {
err = execStmt(qgen.Builder.CreateTable("menu_items", "", "", err = execStmt(qgen.Builder.CreateTable("menu_items", "", "",
[]qgen.DBTableColumn{ []qgen.DBTableColumn{
qgen.DBTableColumn{"miid", "int", 0, false, true, ""},
qgen.DBTableColumn{"mid", "int", 0, false, false, ""}, qgen.DBTableColumn{"mid", "int", 0, false, false, ""},
qgen.DBTableColumn{"name", "varchar", 200, false, false, ""},
qgen.DBTableColumn{"htmlID", "varchar", 200, false, false, "''"}, qgen.DBTableColumn{"htmlID", "varchar", 200, false, false, "''"},
qgen.DBTableColumn{"cssClass", "varchar", 200, false, false, "''"}, qgen.DBTableColumn{"cssClass", "varchar", 200, false, false, "''"},
qgen.DBTableColumn{"position", "varchar", 100, false, false, ""}, qgen.DBTableColumn{"position", "varchar", 100, false, false, ""},
@ -37,7 +49,9 @@ func patch0(scanner *bufio.Scanner) error {
qgen.DBTableColumn{"staffOnly", "boolean", 0, false, false, "0"}, qgen.DBTableColumn{"staffOnly", "boolean", 0, false, false, "0"},
qgen.DBTableColumn{"adminOnly", "boolean", 0, false, false, "0"}, qgen.DBTableColumn{"adminOnly", "boolean", 0, false, false, "0"},
}, },
[]qgen.DBTableKey{}, []qgen.DBTableKey{
qgen.DBTableKey{"miid", "primary"},
},
)) ))
if err != nil { if err != nil {
return err return err
@ -49,7 +63,7 @@ func patch0(scanner *bufio.Scanner) error {
} }
var order int var order int
var mOrder = "mid, htmlID, cssClass, position, path, aria, tooltip, guestOnly, memberOnly, staffOnly, adminOnly" var mOrder = "mid, name, htmlID, cssClass, position, path, aria, tooltip, guestOnly, memberOnly, staffOnly, adminOnly"
var addMenuItem = func(data map[string]interface{}) error { var addMenuItem = func(data map[string]interface{}) error {
cols, values := qgen.InterfaceMapToInsertStrings(data, mOrder) cols, values := qgen.InterfaceMapToInsertStrings(data, mOrder)
err := execStmt(qgen.Builder.SimpleInsert("menu_items", cols+", order", values+","+strconv.Itoa(order))) err := execStmt(qgen.Builder.SimpleInsert("menu_items", cols+", order", values+","+strconv.Itoa(order)))
@ -57,12 +71,12 @@ func patch0(scanner *bufio.Scanner) error {
return err return err
} }
err = addMenuItem(map[string]interface{}{"mid": 1, "htmlID": "menu_forums", "position": "left", "path": "/forums/", "aria": "{lang.menu_forums_aria}", "tooltip": "{lang.menu_forums_tooltip}"}) err = addMenuItem(map[string]interface{}{"mid": 1, "name": "{lang.menu_forums}", "htmlID": "menu_forums", "position": "left", "path": "/forums/", "aria": "{lang.menu_forums_aria}", "tooltip": "{lang.menu_forums_tooltip}"})
if err != nil { if err != nil {
return err return err
} }
err = addMenuItem(map[string]interface{}{"mid": 1, "htmlID": "menu_topics", "cssClass": "menu_topics", "position": "left", "path": "/topics/", "aria": "{lang.menu_topics_aria}", "tooltip": "{lang.menu_topics_tooltip}"}) err = addMenuItem(map[string]interface{}{"mid": 1, "name": "{lang.menu_topics}", "htmlID": "menu_topics", "cssClass": "menu_topics", "position": "left", "path": "/topics/", "aria": "{lang.menu_topics_aria}", "tooltip": "{lang.menu_topics_tooltip}"})
if err != nil { if err != nil {
return err return err
} }
@ -72,35 +86,39 @@ func patch0(scanner *bufio.Scanner) error {
return err return err
} }
err = addMenuItem(map[string]interface{}{"mid": 1, "cssClass": "menu_account", "position": "left", "path": "/user/edit/critical/", "aria": "{lang.menu_account_aria}", "tooltip": "{lang.menu_account_tooltip}", "memberOnly": true}) err = addMenuItem(map[string]interface{}{"mid": 1, "name": "{lang.menu_account}", "cssClass": "menu_account", "position": "left", "path": "/user/edit/critical/", "aria": "{lang.menu_account_aria}", "tooltip": "{lang.menu_account_tooltip}", "memberOnly": true})
if err != nil { if err != nil {
return err return err
} }
err = addMenuItem(map[string]interface{}{"mid": 1, "cssClass": "menu_profile", "position": "left", "path": "{me.Link}", "aria": "{lang.menu_profile_aria}", "tooltip": "{lang.menu_profile_tooltip}", "memberOnly": true}) err = addMenuItem(map[string]interface{}{"mid": 1, "name": "{lang.menu_profile}", "cssClass": "menu_profile", "position": "left", "path": "{me.Link}", "aria": "{lang.menu_profile_aria}", "tooltip": "{lang.menu_profile_tooltip}", "memberOnly": true})
if err != nil { if err != nil {
return err return err
} }
err = addMenuItem(map[string]interface{}{"mid": 1, "cssClass": "menu_panel menu_account", "position": "left", "path": "/panel/", "aria": "{lang.menu_panel_aria}", "tooltip": "{lang.menu_panel_tooltip}", "memberOnly": true, "staffOnly": true}) err = addMenuItem(map[string]interface{}{"mid": 1, "name": "{lang.menu_panel}", "cssClass": "menu_panel menu_account", "position": "left", "path": "/panel/", "aria": "{lang.menu_panel_aria}", "tooltip": "{lang.menu_panel_tooltip}", "memberOnly": true, "staffOnly": true})
if err != nil { if err != nil {
return err return err
} }
err = addMenuItem(map[string]interface{}{"mid": 1, "cssClass": "menu_logout", "position": "left", "path": "/accounts/logout/?session={me.Session}", "aria": "{lang.menu_logout_aria}", "tooltip": "{lang.menu_logout_tooltip}", "memberOnly": true}) err = addMenuItem(map[string]interface{}{"mid": 1, "name": "{lang.menu_logout}", "cssClass": "menu_logout", "position": "left", "path": "/accounts/logout/?session={me.Session}", "aria": "{lang.menu_logout_aria}", "tooltip": "{lang.menu_logout_tooltip}", "memberOnly": true})
if err != nil { if err != nil {
return err return err
} }
err = addMenuItem(map[string]interface{}{"mid": 1, "cssClass": "menu_register", "position": "left", "path": "/accounts/create/", "aria": "{lang.menu_register_aria}", "tooltip": "{lang.menu_register_tooltip}", "guestOnly": true}) err = addMenuItem(map[string]interface{}{"mid": 1, "name": "{lang.menu_register}", "cssClass": "menu_register", "position": "left", "path": "/accounts/create/", "aria": "{lang.menu_register_aria}", "tooltip": "{lang.menu_register_tooltip}", "guestOnly": true})
if err != nil { if err != nil {
return err return err
} }
err = addMenuItem(map[string]interface{}{"mid": 1, "cssClass": "menu_login", "position": "left", "path": "/accounts/login/", "aria": "{lang.menu_login_aria}", "tooltip": "{lang.menu_login_tooltip}", "guestOnly": true}) err = addMenuItem(map[string]interface{}{"mid": 1, "name": "{lang.menu_login}", "cssClass": "menu_login", "position": "left", "path": "/accounts/login/", "aria": "{lang.menu_login_aria}", "tooltip": "{lang.menu_login_tooltip}", "guestOnly": true})
if err != nil { if err != nil {
return err return err
} }
return nil return nil
} }
func patch1(scanner *bufio.Scanner) error {
return nil
}

View File

@ -96,6 +96,10 @@ func (build *builder) SimpleInnerJoin(table1 string, table2 string, columns stri
return build.prepare(build.adapter.SimpleInnerJoin("_builder", table1, table2, columns, joiners, where, orderby, limit)) return build.prepare(build.adapter.SimpleInnerJoin("_builder", table1, table2, columns, joiners, where, orderby, limit))
} }
func (build *builder) DropTable(table string) (stmt *sql.Stmt, err error) {
return build.prepare(build.adapter.DropTable("_builder", table))
}
func (build *builder) CreateTable(table string, charset string, collation string, columns []DBTableColumn, keys []DBTableKey) (stmt *sql.Stmt, err error) { func (build *builder) CreateTable(table string, charset string, collation string, columns []DBTableColumn, keys []DBTableKey) (stmt *sql.Stmt, err error) {
return build.prepare(build.adapter.CreateTable("_builder", table, charset, collation, columns, keys)) return build.prepare(build.adapter.CreateTable("_builder", table, charset, collation, columns, keys))
} }

View File

@ -44,6 +44,18 @@ func (adapter *MssqlAdapter) DbVersion() string {
return "SELECT CONCAT(SERVERPROPERTY('productversion'), SERVERPROPERTY ('productlevel'), SERVERPROPERTY ('edition'))" return "SELECT CONCAT(SERVERPROPERTY('productversion'), SERVERPROPERTY ('productlevel'), SERVERPROPERTY ('edition'))"
} }
func (adapter *MssqlAdapter) DropTable(name string, table string) (string, error) {
if name == "" {
return "", errors.New("You need a name for this statement")
}
if table == "" {
return "", errors.New("You need a name for this table")
}
querystr := "DROP TABLE IF EXISTS [" + table + "];"
adapter.pushStatement(name, "drop-table", querystr)
return querystr, nil
}
// TODO: Convert any remaining stringy types to nvarchar // TODO: Convert any remaining stringy types to nvarchar
// We may need to change the CreateTable API to better suit Mssql and the other database drivers which are coming up // We may need to change the CreateTable API to better suit Mssql and the other database drivers which are coming up
func (adapter *MssqlAdapter) CreateTable(name string, table string, charset string, collation string, columns []DBTableColumn, keys []DBTableKey) (string, error) { func (adapter *MssqlAdapter) CreateTable(name string, table string, charset string, collation string, columns []DBTableColumn, keys []DBTableKey) (string, error) {

View File

@ -61,6 +61,18 @@ func (adapter *MysqlAdapter) DbVersion() string {
return "SELECT VERSION()" return "SELECT VERSION()"
} }
func (adapter *MysqlAdapter) DropTable(name string, table string) (string, error) {
if name == "" {
return "", errors.New("You need a name for this statement")
}
if table == "" {
return "", errors.New("You need a name for this table")
}
querystr := "DROP TABLE IF EXISTS `" + table + "`;"
adapter.pushStatement(name, "drop-table", querystr)
return querystr, nil
}
func (adapter *MysqlAdapter) CreateTable(name string, table string, charset string, collation string, columns []DBTableColumn, keys []DBTableKey) (string, error) { func (adapter *MysqlAdapter) CreateTable(name string, table string, charset string, collation string, columns []DBTableColumn, keys []DBTableKey) (string, error) {
if name == "" { if name == "" {
return "", errors.New("You need a name for this statement") return "", errors.New("You need a name for this statement")

View File

@ -38,9 +38,20 @@ func (adapter *PgsqlAdapter) BuildConn(config map[string]string) (*sql.DB, error
return nil, nil return nil, nil
} }
// TODO: Implement this
func (adapter *PgsqlAdapter) DbVersion() string { func (adapter *PgsqlAdapter) DbVersion() string {
return "" return "SELECT version()"
}
func (adapter *PgsqlAdapter) DropTable(name string, table string) (string, error) {
if name == "" {
return "", errors.New("You need a name for this statement")
}
if table == "" {
return "", errors.New("You need a name for this table")
}
querystr := "DROP TABLE IF EXISTS \"" + table + "\";"
adapter.pushStatement(name, "drop-table", querystr)
return querystr, nil
} }
// TODO: Implement this // TODO: Implement this

View File

@ -98,11 +98,13 @@ type DBStmt struct {
Type string // create-table, insert, update, delete Type string // create-table, insert, update, delete
} }
// TODO: Add the DropTable, TableExists, AddColumn, ColumnExists, and RemoveColumn methods
type Adapter interface { type Adapter interface {
GetName() string GetName() string
BuildConn(config map[string]string) (*sql.DB, error) BuildConn(config map[string]string) (*sql.DB, error)
DbVersion() string DbVersion() string
DropTable(name string, table string) (string, error)
CreateTable(name string, table string, charset string, collation string, columns []DBTableColumn, keys []DBTableKey) (string, error) CreateTable(name string, table string, charset string, collation string, columns []DBTableColumn, keys []DBTableKey) (string, error)
SimpleInsert(name string, table string, columns string, fields string) (string, error) SimpleInsert(name string, table string, columns string, fields string) (string, error)
SimpleUpdate(name string, table string, set string, where string) (string, error) SimpleUpdate(name string, table string, set string, where string) (string, error)

View File

@ -210,47 +210,34 @@ func seedTables(adapter qgen.Adapter) error {
qgen.Install.SimpleInsert("replies", "tid, content, parsed_content, createdAt, createdBy, lastUpdated, lastEdit, lastEditBy, ipaddress", "1,'A reply!','A reply!',UTC_TIMESTAMP(),1,UTC_TIMESTAMP(),0,0,'::1'") qgen.Install.SimpleInsert("replies", "tid, content, parsed_content, createdAt, createdBy, lastUpdated, lastEdit, lastEditBy, ipaddress", "1,'A reply!','A reply!',UTC_TIMESTAMP(),1,UTC_TIMESTAMP(),0,0,'::1'")
/*
Quick Reminder of the HTML layout, so I don't need to flip back and forth between menu_item.html, etc.
{{range .MenuItems}}
<li id="menu_{{.ID}}" class="menu_{{.Position}}"><a href="{{.Path}}" aria-label="{{.Aria}}" title="{{.Tooltip}}"></a></li>
{{end}}
qgen.DBTableColumn{"guestOnly", "boolean", 1, false, false, "0"},
qgen.DBTableColumn{"memberOnly", "boolean", 1, false, false, "0"},
qgen.DBTableColumn{"staffOnly", "boolean", 1, false, false, "0"},
qgen.DBTableColumn{"adminOnly", "boolean", 1, false, false, "0"},
*/
qgen.Install.SimpleInsert("menus", "", "") qgen.Install.SimpleInsert("menus", "", "")
// Go maps have a random iteration order, so we have to do this, otherwise the schema files will become unstable and harder to audit // Go maps have a random iteration order, so we have to do this, otherwise the schema files will become unstable and harder to audit
var order = 0 var order = 0
var mOrder = "mid, htmlID, cssClass, position, path, aria, tooltip, guestOnly, memberOnly, staffOnly, adminOnly" var mOrder = "mid, name, htmlID, cssClass, position, path, aria, tooltip, guestOnly, memberOnly, staffOnly, adminOnly"
var addMenuItem = func(data map[string]interface{}) { var addMenuItem = func(data map[string]interface{}) {
cols, values := qgen.InterfaceMapToInsertStrings(data, mOrder) cols, values := qgen.InterfaceMapToInsertStrings(data, mOrder)
qgen.Install.SimpleInsert("menu_items", cols+", order", values+","+strconv.Itoa(order)) qgen.Install.SimpleInsert("menu_items", cols+", order", values+","+strconv.Itoa(order))
order++ order++
} }
addMenuItem(map[string]interface{}{"mid": 1, "htmlID": "menu_forums", "position": "left", "path": "/forums/", "aria": "{lang.menu_forums_aria}", "tooltip": "{lang.menu_forums_tooltip}"}) addMenuItem(map[string]interface{}{"mid": 1, "name": "{lang.menu_forums}", "htmlID": "menu_forums", "position": "left", "path": "/forums/", "aria": "{lang.menu_forums_aria}", "tooltip": "{lang.menu_forums_tooltip}"})
addMenuItem(map[string]interface{}{"mid": 1, "htmlID": "menu_topics", "cssClass": "menu_topics", "position": "left", "path": "/topics/", "aria": "{lang.menu_topics_aria}", "tooltip": "{lang.menu_topics_tooltip}"}) addMenuItem(map[string]interface{}{"mid": 1, "name": "{lang.menu_topics}", "htmlID": "menu_topics", "cssClass": "menu_topics", "position": "left", "path": "/topics/", "aria": "{lang.menu_topics_aria}", "tooltip": "{lang.menu_topics_tooltip}"})
addMenuItem(map[string]interface{}{"mid": 1, "htmlID": "general_alerts", "cssClass": "menu_alerts", "position": "right", "tmplName": "menu_alerts"}) addMenuItem(map[string]interface{}{"mid": 1, "htmlID": "general_alerts", "cssClass": "menu_alerts", "position": "right", "tmplName": "menu_alerts"})
addMenuItem(map[string]interface{}{"mid": 1, "cssClass": "menu_account", "position": "left", "path": "/user/edit/critical/", "aria": "{lang.menu_account_aria}", "tooltip": "{lang.menu_account_tooltip}", "memberOnly": true}) addMenuItem(map[string]interface{}{"mid": 1, "name": "{lang.menu_account}", "cssClass": "menu_account", "position": "left", "path": "/user/edit/critical/", "aria": "{lang.menu_account_aria}", "tooltip": "{lang.menu_account_tooltip}", "memberOnly": true})
addMenuItem(map[string]interface{}{"mid": 1, "cssClass": "menu_profile", "position": "left", "path": "{me.Link}", "aria": "{lang.menu_profile_aria}", "tooltip": "{lang.menu_profile_tooltip}", "memberOnly": true}) addMenuItem(map[string]interface{}{"mid": 1, "name": "{lang.menu_profile}", "cssClass": "menu_profile", "position": "left", "path": "{me.Link}", "aria": "{lang.menu_profile_aria}", "tooltip": "{lang.menu_profile_tooltip}", "memberOnly": true})
addMenuItem(map[string]interface{}{"mid": 1, "cssClass": "menu_panel menu_account", "position": "left", "path": "/panel/", "aria": "{lang.menu_panel_aria}", "tooltip": "{lang.menu_panel_tooltip}", "memberOnly": true, "staffOnly": true}) addMenuItem(map[string]interface{}{"mid": 1, "name": "{lang.menu_panel}", "cssClass": "menu_panel menu_account", "position": "left", "path": "/panel/", "aria": "{lang.menu_panel_aria}", "tooltip": "{lang.menu_panel_tooltip}", "memberOnly": true, "staffOnly": true})
addMenuItem(map[string]interface{}{"mid": 1, "cssClass": "menu_logout", "position": "left", "path": "/accounts/logout/?session={me.Session}", "aria": "{lang.menu_logout_aria}", "tooltip": "{lang.menu_logout_tooltip}", "memberOnly": true}) addMenuItem(map[string]interface{}{"mid": 1, "name": "{lang.menu_logout}", "cssClass": "menu_logout", "position": "left", "path": "/accounts/logout/?session={me.Session}", "aria": "{lang.menu_logout_aria}", "tooltip": "{lang.menu_logout_tooltip}", "memberOnly": true})
addMenuItem(map[string]interface{}{"mid": 1, "cssClass": "menu_register", "position": "left", "path": "/accounts/create/", "aria": "{lang.menu_register_aria}", "tooltip": "{lang.menu_register_tooltip}", "guestOnly": true}) addMenuItem(map[string]interface{}{"mid": 1, "name": "{lang.menu_register}", "cssClass": "menu_register", "position": "left", "path": "/accounts/create/", "aria": "{lang.menu_register_aria}", "tooltip": "{lang.menu_register_tooltip}", "guestOnly": true})
addMenuItem(map[string]interface{}{"mid": 1, "cssClass": "menu_login", "position": "left", "path": "/accounts/login/", "aria": "{lang.menu_login_aria}", "tooltip": "{lang.menu_login_tooltip}", "guestOnly": true}) addMenuItem(map[string]interface{}{"mid": 1, "name": "{lang.menu_login}", "cssClass": "menu_login", "position": "left", "path": "/accounts/login/", "aria": "{lang.menu_login_aria}", "tooltip": "{lang.menu_login_tooltip}", "guestOnly": true})
return nil return nil
} }

View File

@ -390,6 +390,7 @@ func createTables(adapter qgen.Adapter) error {
qgen.Install.CreateTable("menu_items", "", "", qgen.Install.CreateTable("menu_items", "", "",
[]qgen.DBTableColumn{ []qgen.DBTableColumn{
qgen.DBTableColumn{"miid", "int", 0, false, true, ""},
qgen.DBTableColumn{"mid", "int", 0, false, false, ""}, qgen.DBTableColumn{"mid", "int", 0, false, false, ""},
qgen.DBTableColumn{"htmlID", "varchar", 200, false, false, "''"}, qgen.DBTableColumn{"htmlID", "varchar", 200, false, false, "''"},
qgen.DBTableColumn{"cssClass", "varchar", 200, false, false, "''"}, qgen.DBTableColumn{"cssClass", "varchar", 200, false, false, "''"},
@ -405,7 +406,9 @@ func createTables(adapter qgen.Adapter) error {
qgen.DBTableColumn{"staffOnly", "boolean", 0, false, false, "0"}, qgen.DBTableColumn{"staffOnly", "boolean", 0, false, false, "0"},
qgen.DBTableColumn{"adminOnly", "boolean", 0, false, false, "0"}, qgen.DBTableColumn{"adminOnly", "boolean", 0, false, false, "0"},
}, },
[]qgen.DBTableKey{}, []qgen.DBTableKey{
qgen.DBTableKey{"miid", "primary"},
},
) )
/* /*

View File

@ -153,6 +153,10 @@ func buildPanelRoutes() {
View("routePanelThemes", "/panel/themes/"), View("routePanelThemes", "/panel/themes/"),
Action("routePanelThemesSetDefault", "/panel/themes/default/", "extraData"), Action("routePanelThemesSetDefault", "/panel/themes/default/", "extraData"),
View("routePanelThemesMenus", "/panel/themes/menus/"),
View("routePanelThemesMenusEdit", "/panel/themes/menus/edit/", "extraData"),
View("routePanelThemesMenuItemEdit", "/panel/themes/menus/item/edit/", "extraData"),
Action("routePanelThemesMenuItemEditSubmit", "/panel/themes/menus/item/edit/submit/", "extraData"),
View("routePanelPlugins", "/panel/plugins/"), View("routePanelPlugins", "/panel/plugins/"),
Action("routePanelPluginsActivate", "/panel/plugins/activate/", "extraData"), Action("routePanelPluginsActivate", "/panel/plugins/activate/", "extraData"),

View File

@ -29,12 +29,12 @@ INSERT INTO [forums_permissions] ([gid],[fid],[permissions]) VALUES (6,2,'{"View
INSERT INTO [topics] ([title],[content],[parsed_content],[createdAt],[lastReplyAt],[lastReplyBy],[createdBy],[parentID],[ipaddress]) VALUES ('Test Topic','A topic automatically generated by the software.','A topic automatically generated by the software.',GETUTCDATE(),GETUTCDATE(),1,1,2,'::1'); INSERT INTO [topics] ([title],[content],[parsed_content],[createdAt],[lastReplyAt],[lastReplyBy],[createdBy],[parentID],[ipaddress]) VALUES ('Test Topic','A topic automatically generated by the software.','A topic automatically generated by the software.',GETUTCDATE(),GETUTCDATE(),1,1,2,'::1');
INSERT INTO [replies] ([tid],[content],[parsed_content],[createdAt],[createdBy],[lastUpdated],[lastEdit],[lastEditBy],[ipaddress]) VALUES (1,'A reply!','A reply!',GETUTCDATE(),1,GETUTCDATE(),0,0,'::1'); INSERT INTO [replies] ([tid],[content],[parsed_content],[createdAt],[createdBy],[lastUpdated],[lastEdit],[lastEditBy],[ipaddress]) VALUES (1,'A reply!','A reply!',GETUTCDATE(),1,GETUTCDATE(),0,0,'::1');
INSERT INTO [menus] () VALUES (); INSERT INTO [menus] () VALUES ();
INSERT INTO [menu_items] ([mid],[htmlID],[position],[path],[aria],[tooltip],[order]) VALUES (1,'menu_forums','left','/forums/','{lang.menu_forums_aria}','{lang.menu_forums_tooltip}',0); INSERT INTO [menu_items] ([mid],[name],[htmlID],[position],[path],[aria],[tooltip],[order]) VALUES (1,'{lang.menu_forums}','menu_forums','left','/forums/','{lang.menu_forums_aria}','{lang.menu_forums_tooltip}',0);
INSERT INTO [menu_items] ([mid],[htmlID],[cssClass],[position],[path],[aria],[tooltip],[order]) VALUES (1,'menu_topics','menu_topics','left','/topics/','{lang.menu_topics_aria}','{lang.menu_topics_tooltip}',1); INSERT INTO [menu_items] ([mid],[name],[htmlID],[cssClass],[position],[path],[aria],[tooltip],[order]) VALUES (1,'{lang.menu_topics}','menu_topics','menu_topics','left','/topics/','{lang.menu_topics_aria}','{lang.menu_topics_tooltip}',1);
INSERT INTO [menu_items] ([mid],[htmlID],[cssClass],[position],[tmplName],[order]) VALUES (1,'general_alerts','menu_alerts','right','menu_alerts',2); INSERT INTO [menu_items] ([mid],[htmlID],[cssClass],[position],[tmplName],[order]) VALUES (1,'general_alerts','menu_alerts','right','menu_alerts',2);
INSERT INTO [menu_items] ([mid],[cssClass],[position],[path],[aria],[tooltip],[memberOnly],[order]) VALUES (1,'menu_account','left','/user/edit/critical/','{lang.menu_account_aria}','{lang.menu_account_tooltip}',1,3); INSERT INTO [menu_items] ([mid],[name],[cssClass],[position],[path],[aria],[tooltip],[memberOnly],[order]) VALUES (1,'{lang.menu_account}','menu_account','left','/user/edit/critical/','{lang.menu_account_aria}','{lang.menu_account_tooltip}',1,3);
INSERT INTO [menu_items] ([mid],[cssClass],[position],[path],[aria],[tooltip],[memberOnly],[order]) VALUES (1,'menu_profile','left','{me.Link}','{lang.menu_profile_aria}','{lang.menu_profile_tooltip}',1,4); INSERT INTO [menu_items] ([mid],[name],[cssClass],[position],[path],[aria],[tooltip],[memberOnly],[order]) VALUES (1,'{lang.menu_profile}','menu_profile','left','{me.Link}','{lang.menu_profile_aria}','{lang.menu_profile_tooltip}',1,4);
INSERT INTO [menu_items] ([mid],[cssClass],[position],[path],[aria],[tooltip],[memberOnly],[staffOnly],[order]) VALUES (1,'menu_panel menu_account','left','/panel/','{lang.menu_panel_aria}','{lang.menu_panel_tooltip}',1,1,5); INSERT INTO [menu_items] ([mid],[name],[cssClass],[position],[path],[aria],[tooltip],[memberOnly],[staffOnly],[order]) VALUES (1,'{lang.menu_panel}','menu_panel menu_account','left','/panel/','{lang.menu_panel_aria}','{lang.menu_panel_tooltip}',1,1,5);
INSERT INTO [menu_items] ([mid],[cssClass],[position],[path],[aria],[tooltip],[memberOnly],[order]) VALUES (1,'menu_logout','left','/accounts/logout/?session={me.Session}','{lang.menu_logout_aria}','{lang.menu_logout_tooltip}',1,6); INSERT INTO [menu_items] ([mid],[name],[cssClass],[position],[path],[aria],[tooltip],[memberOnly],[order]) VALUES (1,'{lang.menu_logout}','menu_logout','left','/accounts/logout/?session={me.Session}','{lang.menu_logout_aria}','{lang.menu_logout_tooltip}',1,6);
INSERT INTO [menu_items] ([mid],[cssClass],[position],[path],[aria],[tooltip],[guestOnly],[order]) VALUES (1,'menu_register','left','/accounts/create/','{lang.menu_register_aria}','{lang.menu_register_tooltip}',1,7); INSERT INTO [menu_items] ([mid],[name],[cssClass],[position],[path],[aria],[tooltip],[guestOnly],[order]) VALUES (1,'{lang.menu_register}','menu_register','left','/accounts/create/','{lang.menu_register_aria}','{lang.menu_register_tooltip}',1,7);
INSERT INTO [menu_items] ([mid],[cssClass],[position],[path],[aria],[tooltip],[guestOnly],[order]) VALUES (1,'menu_login','left','/accounts/login/','{lang.menu_login_aria}','{lang.menu_login_tooltip}',1,8); INSERT INTO [menu_items] ([mid],[name],[cssClass],[position],[path],[aria],[tooltip],[guestOnly],[order]) VALUES (1,'{lang.menu_login}','menu_login','left','/accounts/login/','{lang.menu_login_aria}','{lang.menu_login_tooltip}',1,8);

View File

@ -1,4 +1,5 @@
CREATE TABLE [menu_items] ( CREATE TABLE [menu_items] (
[miid] int not null IDENTITY,
[mid] int not null, [mid] int not null,
[htmlID] nvarchar (200) DEFAULT '' not null, [htmlID] nvarchar (200) DEFAULT '' not null,
[cssClass] nvarchar (200) DEFAULT '' not null, [cssClass] nvarchar (200) DEFAULT '' not null,
@ -11,5 +12,6 @@ CREATE TABLE [menu_items] (
[guestOnly] bit DEFAULT 0 not null, [guestOnly] bit DEFAULT 0 not null,
[memberOnly] bit DEFAULT 0 not null, [memberOnly] bit DEFAULT 0 not null,
[staffOnly] bit DEFAULT 0 not null, [staffOnly] bit DEFAULT 0 not null,
[adminOnly] bit DEFAULT 0 not null [adminOnly] bit DEFAULT 0 not null,
primary key([miid])
); );

View File

@ -29,12 +29,12 @@ INSERT INTO `forums_permissions`(`gid`,`fid`,`permissions`) VALUES (6,2,'{"ViewT
INSERT INTO `topics`(`title`,`content`,`parsed_content`,`createdAt`,`lastReplyAt`,`lastReplyBy`,`createdBy`,`parentID`,`ipaddress`) VALUES ('Test Topic','A topic automatically generated by the software.','A topic automatically generated by the software.',UTC_TIMESTAMP(),UTC_TIMESTAMP(),1,1,2,'::1'); INSERT INTO `topics`(`title`,`content`,`parsed_content`,`createdAt`,`lastReplyAt`,`lastReplyBy`,`createdBy`,`parentID`,`ipaddress`) VALUES ('Test Topic','A topic automatically generated by the software.','A topic automatically generated by the software.',UTC_TIMESTAMP(),UTC_TIMESTAMP(),1,1,2,'::1');
INSERT INTO `replies`(`tid`,`content`,`parsed_content`,`createdAt`,`createdBy`,`lastUpdated`,`lastEdit`,`lastEditBy`,`ipaddress`) VALUES (1,'A reply!','A reply!',UTC_TIMESTAMP(),1,UTC_TIMESTAMP(),0,0,'::1'); INSERT INTO `replies`(`tid`,`content`,`parsed_content`,`createdAt`,`createdBy`,`lastUpdated`,`lastEdit`,`lastEditBy`,`ipaddress`) VALUES (1,'A reply!','A reply!',UTC_TIMESTAMP(),1,UTC_TIMESTAMP(),0,0,'::1');
INSERT INTO `menus`() VALUES (); INSERT INTO `menus`() VALUES ();
INSERT INTO `menu_items`(`mid`,`htmlID`,`position`,`path`,`aria`,`tooltip`,`order`) VALUES (1,'menu_forums','left','/forums/','{lang.menu_forums_aria}','{lang.menu_forums_tooltip}',0); INSERT INTO `menu_items`(`mid`,`name`,`htmlID`,`position`,`path`,`aria`,`tooltip`,`order`) VALUES (1,'{lang.menu_forums}','menu_forums','left','/forums/','{lang.menu_forums_aria}','{lang.menu_forums_tooltip}',0);
INSERT INTO `menu_items`(`mid`,`htmlID`,`cssClass`,`position`,`path`,`aria`,`tooltip`,`order`) VALUES (1,'menu_topics','menu_topics','left','/topics/','{lang.menu_topics_aria}','{lang.menu_topics_tooltip}',1); INSERT INTO `menu_items`(`mid`,`name`,`htmlID`,`cssClass`,`position`,`path`,`aria`,`tooltip`,`order`) VALUES (1,'{lang.menu_topics}','menu_topics','menu_topics','left','/topics/','{lang.menu_topics_aria}','{lang.menu_topics_tooltip}',1);
INSERT INTO `menu_items`(`mid`,`htmlID`,`cssClass`,`position`,`tmplName`,`order`) VALUES (1,'general_alerts','menu_alerts','right','menu_alerts',2); INSERT INTO `menu_items`(`mid`,`htmlID`,`cssClass`,`position`,`tmplName`,`order`) VALUES (1,'general_alerts','menu_alerts','right','menu_alerts',2);
INSERT INTO `menu_items`(`mid`,`cssClass`,`position`,`path`,`aria`,`tooltip`,`memberOnly`,`order`) VALUES (1,'menu_account','left','/user/edit/critical/','{lang.menu_account_aria}','{lang.menu_account_tooltip}',1,3); INSERT INTO `menu_items`(`mid`,`name`,`cssClass`,`position`,`path`,`aria`,`tooltip`,`memberOnly`,`order`) VALUES (1,'{lang.menu_account}','menu_account','left','/user/edit/critical/','{lang.menu_account_aria}','{lang.menu_account_tooltip}',1,3);
INSERT INTO `menu_items`(`mid`,`cssClass`,`position`,`path`,`aria`,`tooltip`,`memberOnly`,`order`) VALUES (1,'menu_profile','left','{me.Link}','{lang.menu_profile_aria}','{lang.menu_profile_tooltip}',1,4); INSERT INTO `menu_items`(`mid`,`name`,`cssClass`,`position`,`path`,`aria`,`tooltip`,`memberOnly`,`order`) VALUES (1,'{lang.menu_profile}','menu_profile','left','{me.Link}','{lang.menu_profile_aria}','{lang.menu_profile_tooltip}',1,4);
INSERT INTO `menu_items`(`mid`,`cssClass`,`position`,`path`,`aria`,`tooltip`,`memberOnly`,`staffOnly`,`order`) VALUES (1,'menu_panel menu_account','left','/panel/','{lang.menu_panel_aria}','{lang.menu_panel_tooltip}',1,1,5); INSERT INTO `menu_items`(`mid`,`name`,`cssClass`,`position`,`path`,`aria`,`tooltip`,`memberOnly`,`staffOnly`,`order`) VALUES (1,'{lang.menu_panel}','menu_panel menu_account','left','/panel/','{lang.menu_panel_aria}','{lang.menu_panel_tooltip}',1,1,5);
INSERT INTO `menu_items`(`mid`,`cssClass`,`position`,`path`,`aria`,`tooltip`,`memberOnly`,`order`) VALUES (1,'menu_logout','left','/accounts/logout/?session={me.Session}','{lang.menu_logout_aria}','{lang.menu_logout_tooltip}',1,6); INSERT INTO `menu_items`(`mid`,`name`,`cssClass`,`position`,`path`,`aria`,`tooltip`,`memberOnly`,`order`) VALUES (1,'{lang.menu_logout}','menu_logout','left','/accounts/logout/?session={me.Session}','{lang.menu_logout_aria}','{lang.menu_logout_tooltip}',1,6);
INSERT INTO `menu_items`(`mid`,`cssClass`,`position`,`path`,`aria`,`tooltip`,`guestOnly`,`order`) VALUES (1,'menu_register','left','/accounts/create/','{lang.menu_register_aria}','{lang.menu_register_tooltip}',1,7); INSERT INTO `menu_items`(`mid`,`name`,`cssClass`,`position`,`path`,`aria`,`tooltip`,`guestOnly`,`order`) VALUES (1,'{lang.menu_register}','menu_register','left','/accounts/create/','{lang.menu_register_aria}','{lang.menu_register_tooltip}',1,7);
INSERT INTO `menu_items`(`mid`,`cssClass`,`position`,`path`,`aria`,`tooltip`,`guestOnly`,`order`) VALUES (1,'menu_login','left','/accounts/login/','{lang.menu_login_aria}','{lang.menu_login_tooltip}',1,8); INSERT INTO `menu_items`(`mid`,`name`,`cssClass`,`position`,`path`,`aria`,`tooltip`,`guestOnly`,`order`) VALUES (1,'{lang.menu_login}','menu_login','left','/accounts/login/','{lang.menu_login_aria}','{lang.menu_login_tooltip}',1,8);

View File

@ -1,4 +1,5 @@
CREATE TABLE `menu_items` ( CREATE TABLE `menu_items` (
`miid` int not null AUTO_INCREMENT,
`mid` int not null, `mid` int not null,
`htmlID` varchar(200) DEFAULT '' not null, `htmlID` varchar(200) DEFAULT '' not null,
`cssClass` varchar(200) DEFAULT '' not null, `cssClass` varchar(200) DEFAULT '' not null,
@ -11,5 +12,6 @@ CREATE TABLE `menu_items` (
`guestOnly` boolean DEFAULT 0 not null, `guestOnly` boolean DEFAULT 0 not null,
`memberOnly` boolean DEFAULT 0 not null, `memberOnly` boolean DEFAULT 0 not null,
`staffOnly` boolean DEFAULT 0 not null, `staffOnly` boolean DEFAULT 0 not null,
`adminOnly` boolean DEFAULT 0 not null `adminOnly` boolean DEFAULT 0 not null,
primary key(`miid`)
); );

View File

@ -1,4 +1,5 @@
CREATE TABLE `menu_items` ( CREATE TABLE `menu_items` (
`miid` serial not null,
`mid` int not null, `mid` int not null,
`htmlID` varchar (200) DEFAULT '' not null, `htmlID` varchar (200) DEFAULT '' not null,
`cssClass` varchar (200) DEFAULT '' not null, `cssClass` varchar (200) DEFAULT '' not null,
@ -11,5 +12,6 @@ CREATE TABLE `menu_items` (
`guestOnly` boolean DEFAULT 0 not null, `guestOnly` boolean DEFAULT 0 not null,
`memberOnly` boolean DEFAULT 0 not null, `memberOnly` boolean DEFAULT 0 not null,
`staffOnly` boolean DEFAULT 0 not null, `staffOnly` boolean DEFAULT 0 not null,
`adminOnly` boolean DEFAULT 0 not null `adminOnly` boolean DEFAULT 0 not null,
primary key(`miid`)
); );

View File

@ -1 +1 @@
<li id="{{.HTMLID}}" class="menu_{{.Position}} {{.CSSClass}}"><a href="{{.Path}}" aria-label="{{.Aria}}" title="{{.Tooltip}}"></a></li> <li id="{{.HTMLID}}" class="menu_{{.Position}} {{.CSSClass}}"><a href="{{.Path}}" aria-label="{{.Aria}}" title="{{.Tooltip}}">{{.Name}}</a></li>

View File

@ -24,7 +24,12 @@
{{else if eq .Something.Type "bool"}} {{else if eq .Something.Type "bool"}}
<div class="formrow"> <div class="formrow">
<div class="formitem formlabel"><a>{{lang "panel_setting_value"}}</a></div> <div class="formitem formlabel"><a>{{lang "panel_setting_value"}}</a></div>
<div class="formitem"><input name="setting-value" type="checkbox"{{if eq .Something.Content "1"}} checked{{end}} /></div> <div class="formitem">
<select name="setting-value">
<option{{if eq .Something.Content "1"}} selected{{end}} value="1">{{lang "option_yes"}}</option>
<option{{if eq .Something.Content "0"}} selected{{end}} value="0">{{lang "option_no"}}</option>
</select>
</div>
</div> </div>
{{else}}<div class="formrow"> {{else}}<div class="formrow">
<div class="formitem formlabel"><a>{{lang "panel_setting_value"}}</a></div> <div class="formitem formlabel"><a>{{lang "panel_setting_value"}}</a></div>

View File

@ -1,7 +1,7 @@
{{template "header.html" . }} {{template "header.html" . }}
<div class="colstack panel_stack"> <div class="colstack panel_stack">
<nav class="colstack_left" aria-label="The control panel menu"> <nav class="colstack_left" aria-label="Theme manager menu">
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"><a href="/panel/themes/">Theme Manager</a></div> <div class="rowitem"><a href="/panel/themes/">Theme Manager</a></div>
</div> </div>

View File

@ -0,0 +1,31 @@
{{template "header.html" . }}
<div class="colstack panel_stack">
<nav class="colstack_left" aria-label="Theme manager menu">
<div class="colstack_item colstack_head">
<div class="rowitem"><a href="/panel/themes/">Theme Manager</a></div>
</div>
<div class="colstack_item rowmenu">
<div class="rowitem passive"><a href="/panel/themes/menus/">Menus</a></div>
</div>
<div class="colstack_item rowmenu">
<div class="rowitem passive"><a href="#">Widgets</a></div>
</div>
{{template "panel-inner-menu.html" . }}
</nav>
<main class="colstack_right">
<div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{lang "panel_themes_menus_head"}}</h1></div>
</div>
<div id="panel_settings" class="colstack_item rowlist">
{{range .ItemList}}
<div class="rowitem panel_compactrow editable_parent">
<a href="/panel/themes/menus/edit/{{.ID}}" class="editable_block panel_upshift">#{{.ID}}</a>
<a class="panel_compacttext to_right">{{.ItemCount}} items</a>
</div>
{{end}}
</div>
</main>
</div>
{{template "footer.html" . }}

View File

@ -0,0 +1,80 @@
{{template "header.html" . }}
<div class="colstack panel_stack">
{{/** TODO: Set the order based on the order here **/}}
{{/** TODO: Write the backend code and JS code for saving this menu **/}}
<nav class="colstack_left" aria-label="Theme manager menu">
<div class="colstack_item colstack_head">
<div class="rowitem"><a href="/panel/themes/">Theme Manager</a></div>
</div>
<div class="colstack_item rowmenu">
<div class="rowitem passive"><a href="/panel/themes/menus/">Menus</a></div>
</div>
<div class="colstack_item rowmenu">
<div class="rowitem passive"><a href="#">Widgets</a></div>
</div>
{{template "panel-inner-menu.html" . }}
</nav>
<main class="colstack_right">
<div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{lang "panel_themes_menus_edit_head"}}</h1></div>
</div>
<form action="/panel/themes/menus/item/edit/submit/{{.Item.ID}}?session={{.CurrentUser.Session}}" method="post">
<div id="panel_themes_menu_item_edit" class="colstack_item">
{{/** TODO: Let an admin move a menu item from one menu to another? **/}}
<div class="formrow">
<div class="formitem formlabel"><a>{{lang "panel_themes_menus_name"}}</a></div>
<div class="formitem"><input name="item-name" type="text" value="{{.Item.Name}}" /></div>
</div>
<div class="formrow">
<div class="formitem formlabel"><a>{{lang "panel_themes_menus_htmlid"}}</a></div>
<div class="formitem"><input name="item-htmlid" type="text" value="{{.Item.HTMLID}}" /></div>
</div>
<div class="formrow">
<div class="formitem formlabel"><a>{{lang "panel_themes_menus_cssclass"}}</a></div>
<div class="formitem"><input name="item-cssclass" type="text" value="{{.Item.CSSClass}}" /></div>
</div>
<div class="formrow">
<div class="formitem formlabel"><a>{{lang "panel_themes_menus_position"}}</a></div>
<div class="formitem">
<select name="item-position">
<option{{if eq .Item.Position "left"}} selected{{end}} value="left">left</option>
<option{{if eq .Item.Position "right"}} selected{{end}} value="right">right</option>
</select>
</div>
</div>
<div class="formrow">
<div class="formitem formlabel"><a>{{lang "panel_themes_menus_path"}}</a></div>
<div class="formitem"><input name="item-path" type="text" value="{{.Item.Path}}" /></div>
</div>
<div class="formrow">
<div class="formitem formlabel"><a>{{lang "panel_themes_menus_aria"}}</a></div>
<div class="formitem"><input name="item-aria" type="text" value="{{.Item.Aria}}" /></div>
</div>
<div class="formrow">
<div class="formitem formlabel"><a>{{lang "panel_themes_menus_tooltip"}}</a></div>
<div class="formitem"><input name="item-tooltip" type="text" value="{{.Item.Tooltip}}" /></div>
</div>
<div class="formrow">
<div class="formitem formlabel"><a>{{lang "panel_themes_menus_tmplname"}}</a></div>
<div class="formitem"><input name="item-tmplname" type="text" value="{{.Item.TmplName}}" /></div>
</div>
<div class="formrow">
<div class="formitem formlabel"><a>{{lang "panel_themes_menus_permissions"}}</a></div>
<div class="formitem"><select name="item-permissions">
<option value="everyone">{{lang "panel_themes_menus_everyone" }}</option>
<option{{if .Item.GuestOnly}} selected{{end}} value="guest-only">{{lang "panel_themes_menus_guestonly"}}</option>
<option{{if .Item.MemberOnly}} selected{{end}} value="member-only">{{lang "panel_themes_menus_memberonly"}}</option>
<option{{if .Item.SuperModOnly}} selected{{end}} value="supermod-only">{{lang "panel_themes_menus_supermodonly"}}</option>
<option{{if .Item.AdminOnly}} selected{{end}} value="admin-only">{{lang "panel_themes_menus_adminonly"}}</option>
</select></div>
</div>
<div class="formrow">
<div class="formitem"><button name="panel-button" class="formbutton">{{lang "panel_themes_menus_edit_update_button"}}</button></div>
</div>
</div>
</form>
</main>
</div>
{{template "footer.html" . }}

View File

@ -0,0 +1,29 @@
{{template "header.html" . }}
<div class="colstack panel_stack">
<nav class="colstack_left" aria-label="Theme manager menu">
<div class="colstack_item colstack_head">
<div class="rowitem"><a href="/panel/themes/">Theme Manager</a></div>
</div>
<div class="colstack_item rowmenu">
<div class="rowitem passive"><a href="/panel/themes/menus/">Menus</a></div>
</div>
<div class="colstack_item rowmenu">
<div class="rowitem passive"><a href="#">Widgets</a></div>
</div>
{{template "panel-inner-menu.html" . }}
</nav>
<main class="colstack_right">
<div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{lang "panel_themes_menus_head"}}</h1></div>
</div>
<div id="panel_settings" class="colstack_item rowlist">
{{range .ItemList}}
<div class="rowitem panel_compactrow editable_parent">
<a href="/panel/themes/menus/item/edit/{{.ID}}" class="editable_block panel_upshift">{{.Name}}</a>
<a class="panel_compacttext to_right">...</a>
</div>
{{end}}
</div>
</main>
</div>
{{template "footer.html" . }}

View File

@ -104,18 +104,12 @@ li {
content: "\f03a"; content: "\f03a";
margin-right: 6px; margin-right: 6px;
} }
#menu_forums a:after {
content: "{{index .Phrases "menu_forums"}}";
}
.menu_topics a:before { .menu_topics a:before {
margin-right: 4px; margin-right: 4px;
content: "\f27b"; content: "\f27b";
position: relative; position: relative;
top: -2px; top: -2px;
} }
.menu_topics a:after {
content: "{{index .Phrases "menu_topics"}}";
}
.menu_alerts { .menu_alerts {
color: var(--primary-link-color); color: var(--primary-link-color);
display: flex; display: flex;
@ -146,9 +140,6 @@ li {
content: "\f2c3"; content: "\f2c3";
margin-right: 6px; margin-right: 6px;
} }
.menu_account a:after {
content: "{{index .Phrases "menu_account"}}";
}
.menu_profile a:before { .menu_profile a:before {
content: "\f2c0"; content: "\f2c0";
margin-right: 5px; margin-right: 5px;
@ -156,29 +147,14 @@ li {
top: -1px; top: -1px;
font-size: 14px; font-size: 14px;
} }
.menu_profile a:after {
content: "{{index .Phrases "menu_profile"}}";
}
.menu_panel a:before { .menu_panel a:before {
margin-right: 6px; margin-right: 6px;
content: "\f108"; content: "\f108";
} }
.menu_panel a:after {
content: "{{index .Phrases "menu_panel"}}";
}
.menu_logout a:before { .menu_logout a:before {
content: "\f08b"; content: "\f08b";
margin-right: 3px; margin-right: 3px;
} }
.menu_logout a:after {
content: "{{index .Phrases "menu_logout"}}";
}
.menu_login a:after {
content: "{{index .Phrases "menu_login"}}";
}
.menu_register a:after {
content: "{{index .Phrases "menu_register"}}";
}
ul { ul {
display: flex; display: flex;

View File

@ -72,31 +72,6 @@ li {
padding-top: 13px; padding-top: 13px;
} }
#menu_forums a:after {
content: "{{index .Phrases "menu_forums"}}";
}
.menu_topics a:after {
content: "{{index .Phrases "menu_topics"}}";
}
.menu_account a:after {
content: "{{index .Phrases "menu_account"}}";
}
.menu_profile a:after {
content: "{{index .Phrases "menu_profile"}}";
}
.menu_panel a:after {
content: "{{index .Phrases "menu_panel"}}";
}
.menu_logout a:after {
content: "{{index .Phrases "menu_logout"}}";
}
.menu_login a:after {
content: "{{index .Phrases "menu_login"}}";
}
.menu_register a:after {
content: "{{index .Phrases "menu_register"}}";
}
.alert_bell { .alert_bell {
float: right; float: right;
} }

View File

@ -61,31 +61,6 @@ li a {
padding-right: 10px; padding-right: 10px;
} }
#menu_forums a:after {
content: "{{index .Phrases "menu_forums"}}";
}
.menu_topics a:after {
content: "{{index .Phrases "menu_topics"}}";
}
.menu_account a:after {
content: "{{index .Phrases "menu_account"}}";
}
.menu_profile a:after {
content: "{{index .Phrases "menu_profile"}}";
}
.menu_panel a:after {
content: "{{index .Phrases "menu_panel"}}";
}
.menu_logout a:after {
content: "{{index .Phrases "menu_logout"}}";
}
.menu_login a:after {
content: "{{index .Phrases "menu_login"}}";
}
.menu_register a:after {
content: "{{index .Phrases "menu_register"}}";
}
.alert_bell:before { .alert_bell:before {
content: '🔔︎'; content: '🔔︎';
} }

View File

@ -53,31 +53,6 @@ li a {
padding-left: 3px; padding-left: 3px;
} }
#menu_forums a:after {
content: "{{index .Phrases "menu_forums"}}";
}
.menu_topics a:after {
content: "{{index .Phrases "menu_topics"}}";
}
.menu_account a:after {
content: "{{index .Phrases "menu_account"}}";
}
.menu_profile a:after {
content: "{{index .Phrases "menu_profile"}}";
}
.menu_panel a:after {
content: "{{index .Phrases "menu_panel"}}";
}
.menu_logout a:after {
content: "{{index .Phrases "menu_logout"}}";
}
.menu_login a:after {
content: "{{index .Phrases "menu_login"}}";
}
.menu_register a:after {
content: "{{index .Phrases "menu_register"}}";
}
.alert_bell:before { .alert_bell:before {
content: '🔔︎'; content: '🔔︎';
} }