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
.DS_Store
.vscode/launch.json
schema/lastSchema.json
config/config.go
Gosora
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

View File

@ -31,7 +31,7 @@ Other modern features like alerts, likes, advanced dashboard with live stats (CP
# 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.
@ -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.
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
@ -61,7 +63,7 @@ Follow the instructions shown on the screen.
*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.
@ -74,7 +76,7 @@ In the same directory you installed it, you simply have to type: ./run-linux
*Windows*
Run run.bat
Run run.bat, e.g. double-clicking on it.
*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.
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
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).
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?

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/ioutil"
"strconv"
"sync/atomic"
"../query_gen/lib"
)
var Menus *DefaultMenuStore
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 {
List MenuItemList
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 {
ID int
ID int
MenuID int
Name string
HTMLID string
CSSClass string
Position string
@ -59,50 +43,56 @@ type MenuItem struct {
AdminOnly bool
}
func (store *DefaultMenuStore) Load(mid int) error {
var mlist MenuItemList
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}
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)
type MenuItemStmts struct {
update *sql.Stmt
}
var menuItemStmts MenuItemStmts
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 {
return err
}
mlist = append(mlist, mitem)
tmpls[name] = menuTmpl
return nil
})
if err != nil {
return err
}
hold := &MenuListHolder{mlist, make(map[int]menuTmpl)}
err = hold.Preparse()
err = loadTmpl("menu_item")
if err != nil {
return err
return tmpls, err
}
var aStore = &atomic.Value{}
aStore.Store(hold)
store.menus[mid] = aStore
return nil
err = loadTmpl("menu_alerts")
return tmpls, err
}
// 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 {
var tmpls = make(map[string]MenuTmpl)
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")
tmpls, err := hold.LoadTmpls()
if err != nil {
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
var addVariable = func(name []byte) {
//fmt.Println("appending subBuffer: ", string(subBuffer))
// TODO: Check if the subBuffer has any items or is empty
textBuffer = append(textBuffer, subBuffer)
subBuffer = nil
@ -203,13 +194,17 @@ func (hold *MenuListHolder) Parse(name string, tmplData []byte) (menuTmpl MenuTm
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++ {
char := tmplData[i]
if char == '{' && nextCharIs(tmplData, i, '{') {
if char == '{' {
//fmt.Println("found open fence")
dotIndex, hasDot := skipUntilIfExists(tmplData, i, '.')
if !hasDot {
//fmt.Println("no dot, assumed template function style")
// Template function style
langIndex, hasChars := skipUntilCharsExist(tmplData, i+2, []byte("lang"))
langIndex, hasChars := skipUntilCharsExist(tmplData, i+1, []byte("lang"))
if hasChars {
startIndex, hasStart := skipUntilIfExists(tmplData, langIndex, '"')
endIndex, hasEnd := skipUntilIfExists(tmplData, startIndex+1, '"')
@ -228,7 +223,8 @@ func (hold *MenuListHolder) Parse(name string, tmplData []byte) (menuTmpl MenuTm
break
}
fenceIndex, hasFence := skipUntilIfExists(tmplData, dotIndex, '}')
if !hasFence || !nextCharIs(tmplData, fenceIndex, '}') {
if !hasFence {
//fmt.Println("no end fence")
break
}
addVariable(tmplData[dotIndex:fenceIndex])
@ -258,78 +254,88 @@ func (hold *MenuListHolder) Scan(menuTmpls map[string]MenuTmpl, showItem func(mi
if !showItem(mitem) {
continue
}
renderBuffer, variableIndices = hold.ScanItem(menuTmpls, mitem, renderBuffer, variableIndices)
}
// TODO: Need more coalescing in the renderBuffer
return renderBuffer, variableIndices
}
menuTmpl, ok := menuTmpls[mitem.TmplName]
if !ok {
menuTmpl = menuTmpls["menu_item"]
// Note: This doesn't do a visibility check like hold.Scan() does
func (hold *MenuListHolder) ScanItem(menuTmpls map[string]MenuTmpl, mitem MenuItem, renderBuffer [][]byte, variableIndices []int) ([][]byte, []int) {
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 {
if renderItem.Type == 0 {
renderBuffer = append(renderBuffer, menuTmpl.TextBuffer[renderItem.Index])
continue
variable := menuTmpl.VariableBuffer[renderItem.Index]
//fmt.Println("initial variable: ", string(variable))
dotAt, hasDot := skipUntilIfExists(variable, 0, '.')
if !hasDot {
//fmt.Println("no dot")
continue
}
if bytes.Equal(variable[:dotAt], []byte("lang")) {
//fmt.Println("lang: ", string(bytes.TrimPrefix(variable[dotAt:], []byte("."))))
renderBuffer = append(renderBuffer, []byte(GetTmplPhrase(string(bytes.TrimPrefix(variable[dotAt:], []byte("."))))))
} else {
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]
//fmt.Println("initial variable: ", string(variable))
dotAt, hasDot := skipUntilIfExists(variable, 0, '.')
if !hasDot {
//fmt.Println("no dot")
continue
}
if bytes.Equal(variable[:dotAt], []byte("lang")) {
//fmt.Println("lang: ", string(bytes.TrimPrefix(variable[dotAt:], []byte("."))))
renderBuffer = append(renderBuffer, []byte(GetTmplPhrase(string(bytes.TrimPrefix(variable[dotAt:], []byte("."))))))
} else {
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)
}
}
_, 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
}
//fmt.Println("normal var: ", string(variable[:dotAt]))
if len(renderItem) > 0 {
renderBuffer = append(renderBuffer, renderItem)
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
}
//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
}

View File

@ -170,12 +170,10 @@ type GridElement struct {
}
type PanelDashboardPage struct {
Title string
CurrentUser User
Header *Header
Stats PanelStats
Zone string
GridItems []GridElement
*Header
Stats PanelStats
Zone string
GridItems []GridElement
}
type PanelTimeGraph struct {
@ -255,15 +253,40 @@ type PanelAnalyticsAgentPage struct {
}
type PanelThemesPage struct {
Title string
CurrentUser User
Header *Header
*Header
Stats PanelStats
Zone string
PrimaryThemes []*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 {
Title string
CurrentUser User

View File

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

View File

@ -212,7 +212,7 @@ func (theme *Theme) LoadStaticFiles() 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?
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 + "'")

View File

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

View File

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

View File

@ -94,7 +94,10 @@ if %errorlevel% neq 0 (
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
if %errorlevel% neq 0 (
pause

View File

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

View File

@ -2,17 +2,18 @@
package main
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_scheduler":"uid",
"forums":"fid",
"users_replies":"rid",
"topics":"tid",
"replies":"rid",
"attachments":"attachID",
"revisions":"reviseID",
"activity_stream":"asid",
"word_filters":"wfid",
"menus":"mid",
"users":"uid",
"menu_items":"miid",
"forums":"fid",
"attachments":"attachID",
"polls":"pollID",
}

View File

@ -93,6 +93,8 @@
"panel_groups":"Group Manager",
"panel_edit_group":"Group Editor",
"panel_themes":"Theme Manager",
"panel_themes_menus":"Menu Manager",
"panel_themes_menus_edit":"Menu Editor",
"panel_backups":"Backups",
"panel_mod_logs":"Moderation Logs",
"panel_admin_logs":"Administration Logs",
@ -246,6 +248,66 @@
},
"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_aria":"The Forum list",
"menu_topics_tooltip":"Topic List",
@ -639,6 +701,25 @@
"panel_themes_default":"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_setting_head":"Edit Setting",
"panel_setting_name":"Setting Name",
@ -654,67 +735,5 @@
"panel_debug_uptime_label":"Uptime",
"panel_debug_open_database_connections_label":"Open DB Conns",
"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 {
return err
}
menuHold := common.Menus.Get(1)
menuHold, err := common.Menus.Get(1)
if err != nil {
return err
}
fmt.Printf("menuHold: %+v\n", menuHold)
var b bytes.Buffer
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 {
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
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"*/})
}
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)
}
@ -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 {
headerVars, stats, ferr := common.PanelUserCheck(w, r, &user)
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")
var pThemeList, vThemeList []*common.Theme
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)
}
@ -2378,6 +2380,176 @@ func routePanelThemesSetDefault(w http.ResponseWriter, r *http.Request, user com
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 {
headerVars, stats, ferr := common.PanelUserCheck(w, r, &user)
if ferr != nil {

View File

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

View File

@ -7,8 +7,18 @@ import (
"../query_gen/lib"
)
func patch0(scanner *bufio.Scanner) error {
err := execStmt(qgen.Builder.CreateTable("menus", "", "",
func patch0(scanner *bufio.Scanner) (err error) {
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{"mid", "int", 0, false, true, ""},
},
@ -22,7 +32,9 @@ func patch0(scanner *bufio.Scanner) error {
err = execStmt(qgen.Builder.CreateTable("menu_items", "", "",
[]qgen.DBTableColumn{
qgen.DBTableColumn{"miid", "int", 0, false, true, ""},
qgen.DBTableColumn{"mid", "int", 0, false, false, ""},
qgen.DBTableColumn{"name", "varchar", 200, false, false, ""},
qgen.DBTableColumn{"htmlID", "varchar", 200, false, false, "''"},
qgen.DBTableColumn{"cssClass", "varchar", 200, 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{"adminOnly", "boolean", 0, false, false, "0"},
},
[]qgen.DBTableKey{},
[]qgen.DBTableKey{
qgen.DBTableKey{"miid", "primary"},
},
))
if err != nil {
return err
@ -49,7 +63,7 @@ func patch0(scanner *bufio.Scanner) error {
}
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 {
cols, values := qgen.InterfaceMapToInsertStrings(data, mOrder)
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
}
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 {
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 {
return err
}
@ -72,35 +86,39 @@ func patch0(scanner *bufio.Scanner) error {
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 {
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 {
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 {
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 {
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 {
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 {
return err
}
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))
}
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) {
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'))"
}
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
// 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) {

View File

@ -61,6 +61,18 @@ func (adapter *MysqlAdapter) DbVersion() string {
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) {
if name == "" {
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
}
// TODO: Implement this
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

View File

@ -98,11 +98,13 @@ type DBStmt struct {
Type string // create-table, insert, update, delete
}
// TODO: Add the DropTable, TableExists, AddColumn, ColumnExists, and RemoveColumn methods
type Adapter interface {
GetName() string
BuildConn(config map[string]string) (*sql.DB, error)
DbVersion() string
DropTable(name string, table string) (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)
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'")
/*
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", "", "")
// 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 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{}) {
cols, values := qgen.InterfaceMapToInsertStrings(data, mOrder)
qgen.Install.SimpleInsert("menu_items", cols+", order", values+","+strconv.Itoa(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, "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
}

View File

@ -390,6 +390,7 @@ func createTables(adapter qgen.Adapter) error {
qgen.Install.CreateTable("menu_items", "", "",
[]qgen.DBTableColumn{
qgen.DBTableColumn{"miid", "int", 0, false, true, ""},
qgen.DBTableColumn{"mid", "int", 0, false, false, ""},
qgen.DBTableColumn{"htmlID", "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{"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/"),
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/"),
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 [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 [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],[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],[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],[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],[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],[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],[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],[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],[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],[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],[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],[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],[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],[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],[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],[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] (
[miid] int not null IDENTITY,
[mid] int not null,
[htmlID] 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,
[memberOnly] 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 `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 `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`,`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`,`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`,`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`,`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`,`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`,`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`,`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`,`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`,`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`,`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`,`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`,`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`,`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`,`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`,`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` (
`miid` int not null AUTO_INCREMENT,
`mid` int not null,
`htmlID` 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,
`memberOnly` 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` (
`miid` serial not null,
`mid` int not null,
`htmlID` 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,
`memberOnly` 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"}}
<div class="formrow">
<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>
{{else}}<div class="formrow">
<div class="formitem formlabel"><a>{{lang "panel_setting_value"}}</a></div>

View File

@ -1,7 +1,7 @@
{{template "header.html" . }}
<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="rowitem"><a href="/panel/themes/">Theme Manager</a></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";
margin-right: 6px;
}
#menu_forums a:after {
content: "{{index .Phrases "menu_forums"}}";
}
.menu_topics a:before {
margin-right: 4px;
content: "\f27b";
position: relative;
top: -2px;
}
.menu_topics a:after {
content: "{{index .Phrases "menu_topics"}}";
}
.menu_alerts {
color: var(--primary-link-color);
display: flex;
@ -146,9 +140,6 @@ li {
content: "\f2c3";
margin-right: 6px;
}
.menu_account a:after {
content: "{{index .Phrases "menu_account"}}";
}
.menu_profile a:before {
content: "\f2c0";
margin-right: 5px;
@ -156,29 +147,14 @@ li {
top: -1px;
font-size: 14px;
}
.menu_profile a:after {
content: "{{index .Phrases "menu_profile"}}";
}
.menu_panel a:before {
margin-right: 6px;
content: "\f108";
}
.menu_panel a:after {
content: "{{index .Phrases "menu_panel"}}";
}
.menu_logout a:before {
content: "\f08b";
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 {
display: flex;

View File

@ -72,31 +72,6 @@ li {
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 {
float: right;
}

View File

@ -61,31 +61,6 @@ li a {
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 {
content: '🔔︎';
}

View File

@ -53,31 +53,6 @@ li a {
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 {
content: '🔔︎';
}