Renamed setting.go to settings.go

Added the BypassGet method to SettingMap and refactored the Control Panel to use it.
Refactored the query builder.

More work on Cosora.
This commit is contained in:
Azareal 2017-11-13 09:23:43 +00:00
parent b42181c0c6
commit 8d0479f4b2
19 changed files with 320 additions and 335 deletions

View File

@ -56,7 +56,6 @@ type ForumCache interface {
type MemoryForumStore struct { type MemoryForumStore struct {
forums sync.Map // map[int]*Forum forums sync.Map // map[int]*Forum
forumView atomic.Value // []*Forum forumView atomic.Value // []*Forum
//fids []int
get *sql.Stmt get *sql.Stmt
getAll *sql.Stmt getAll *sql.Stmt

View File

@ -12,7 +12,7 @@ type HeaderVars struct {
Stylesheets []string Stylesheets []string
Widgets PageWidgets Widgets PageWidgets
Site *site Site *site
Settings map[string]interface{} Settings SettingMap
Themes map[string]Theme // TODO: Use a slice containing every theme instead of the main map for speed Themes map[string]Theme // TODO: Use a slice containing every theme instead of the main map for speed
ThemeName string ThemeName string
//TemplateName string // TODO: Use this to move template calls to the router rather than duplicating them over and over and over? //TemplateName string // TODO: Use this to move template calls to the router rather than duplicating them over and over and over?

View File

@ -9,10 +9,15 @@ import (
"../query_gen/lib" "../query_gen/lib"
) )
var SettingBox atomic.Value // An atomic value pointing to a SettingBox
// SettingMap is a map type specifically for holding the various settings admins set to toggle features on and off or to otherwise alter Gosora's behaviour from the Control Panel // SettingMap is a map type specifically for holding the various settings admins set to toggle features on and off or to otherwise alter Gosora's behaviour from the Control Panel
type SettingMap map[string]interface{} type SettingMap map[string]interface{}
var SettingBox atomic.Value // An atomic value pointing to a SettingBox type SettingStore interface {
ParseSetting(sname string, scontent string, stype string, sconstraint string) string
BypassGet(name string) (*Setting, error)
}
type OptionLabel struct { type OptionLabel struct {
Label string Label string
@ -28,7 +33,8 @@ type Setting struct {
} }
type SettingStmts struct { type SettingStmts struct {
getFull *sql.Stmt getAll *sql.Stmt
get *sql.Stmt
} }
var settingStmts SettingStmts var settingStmts SettingStmts
@ -37,14 +43,15 @@ func init() {
SettingBox.Store(SettingMap(make(map[string]interface{}))) SettingBox.Store(SettingMap(make(map[string]interface{})))
DbInits.Add(func(acc *qgen.Accumulator) error { DbInits.Add(func(acc *qgen.Accumulator) error {
settingStmts = SettingStmts{ settingStmts = SettingStmts{
getFull: acc.Select("settings").Columns("name, content, type, constraints").Prepare(), getAll: acc.Select("settings").Columns("name, content, type, constraints").Prepare(),
get: acc.Select("settings").Columns("content, type, constraints").Where("name = ?").Prepare(),
} }
return acc.FirstError() return acc.FirstError()
}) })
} }
func LoadSettings() error { func LoadSettings() error {
rows, err := settingStmts.getFull.Query() rows, err := settingStmts.getAll.Query()
if err != nil { if err != nil {
return err return err
} }
@ -113,3 +120,9 @@ func (sBox SettingMap) ParseSetting(sname string, scontent string, stype string,
} }
return "" return ""
} }
func (sBox SettingMap) BypassGet(name string) (*Setting, error) {
setting := &Setting{Name: name}
err := settingStmts.get.QueryRow(name).Scan(&setting.Content, &setting.Type, &setting.Constraint)
return setting, err
}

View File

@ -11,8 +11,6 @@ import "./common"
type Stmts struct { type Stmts struct {
getPassword *sql.Stmt getPassword *sql.Stmt
getSettings *sql.Stmt getSettings *sql.Stmt
getSetting *sql.Stmt
getFullSetting *sql.Stmt
isPluginActive *sql.Stmt isPluginActive *sql.Stmt
getUsersOffset *sql.Stmt getUsersOffset *sql.Stmt
isThemeDefault *sql.Stmt isThemeDefault *sql.Stmt
@ -99,20 +97,6 @@ func _gen_mssql() (err error) {
return err return err
} }
log.Print("Preparing getSetting statement.")
stmts.getSetting, err = db.Prepare("SELECT [content],[type] FROM [settings] WHERE [name] = ?1")
if err != nil {
log.Print("Bad Query: ","SELECT [content],[type] FROM [settings] WHERE [name] = ?1")
return err
}
log.Print("Preparing getFullSetting statement.")
stmts.getFullSetting, err = db.Prepare("SELECT [name],[type],[constraints] FROM [settings] WHERE [name] = ?1")
if err != nil {
log.Print("Bad Query: ","SELECT [name],[type],[constraints] FROM [settings] WHERE [name] = ?1")
return err
}
log.Print("Preparing isPluginActive statement.") log.Print("Preparing isPluginActive statement.")
stmts.isPluginActive, err = db.Prepare("SELECT [active] FROM [plugins] WHERE [uname] = ?1") stmts.isPluginActive, err = db.Prepare("SELECT [active] FROM [plugins] WHERE [uname] = ?1")
if err != nil { if err != nil {

View File

@ -13,8 +13,6 @@ import "./common"
type Stmts struct { type Stmts struct {
getPassword *sql.Stmt getPassword *sql.Stmt
getSettings *sql.Stmt getSettings *sql.Stmt
getSetting *sql.Stmt
getFullSetting *sql.Stmt
isPluginActive *sql.Stmt isPluginActive *sql.Stmt
getUsersOffset *sql.Stmt getUsersOffset *sql.Stmt
isThemeDefault *sql.Stmt isThemeDefault *sql.Stmt
@ -99,18 +97,6 @@ func _gen_mysql() (err error) {
return err return err
} }
log.Print("Preparing getSetting statement.")
stmts.getSetting, err = db.Prepare("SELECT `content`,`type` FROM `settings` WHERE `name` = ?")
if err != nil {
return err
}
log.Print("Preparing getFullSetting statement.")
stmts.getFullSetting, err = db.Prepare("SELECT `name`,`type`,`constraints` FROM `settings` WHERE `name` = ?")
if err != nil {
return err
}
log.Print("Preparing isPluginActive statement.") log.Print("Preparing isPluginActive statement.")
stmts.isPluginActive, err = db.Prepare("SELECT `active` FROM `plugins` WHERE `uname` = ?") stmts.isPluginActive, err = db.Prepare("SELECT `active` FROM `plugins` WHERE `uname` = ?")
if err != nil { if err != nil {

View File

@ -455,7 +455,6 @@ func routePanelSettings(w http.ResponseWriter, r *http.Request, user common.User
return common.NoPermissions(w, r, user) return common.NoPermissions(w, r, user)
} }
//log.Print("headerVars.Settings",headerVars.Settings)
var settingList = make(map[string]interface{}) var settingList = make(map[string]interface{})
rows, err := stmts.getSettings.Query() rows, err := stmts.getSettings.Query()
if err != nil { if err != nil {
@ -515,9 +514,8 @@ func routePanelSetting(w http.ResponseWriter, r *http.Request, user common.User,
if !user.Perms.EditSettings { if !user.Perms.EditSettings {
return common.NoPermissions(w, r, user) return common.NoPermissions(w, r, user)
} }
setting := common.Setting{sname, "", "", ""}
err := stmts.getSetting.QueryRow(setting.Name).Scan(&setting.Content, &setting.Type) setting, err := headerVars.Settings.BypassGet(sname)
if err == ErrNoRows { if err == ErrNoRows {
return common.LocalError("The setting you want to edit doesn't exist.", w, r, user) return common.LocalError("The setting you want to edit doesn't exist.", w, r, user)
} else if err != nil { } else if err != nil {
@ -532,8 +530,7 @@ func routePanelSetting(w http.ResponseWriter, r *http.Request, user common.User,
return common.LocalError("The value of this setting couldn't be converted to an integer", w, r, user) return common.LocalError("The value of this setting couldn't be converted to an integer", w, r, user)
} }
labels := strings.Split(llist, ",") for index, label := range strings.Split(llist, ",") {
for index, label := range labels {
itemList = append(itemList, common.OptionLabel{ itemList = append(itemList, common.OptionLabel{
Label: label, Label: label,
Value: index + 1, Value: index + 1,
@ -564,17 +561,15 @@ func routePanelSettingEdit(w http.ResponseWriter, r *http.Request, user common.U
return common.NoPermissions(w, r, user) return common.NoPermissions(w, r, user)
} }
var stype, sconstraints string
scontent := r.PostFormValue("setting-value") scontent := r.PostFormValue("setting-value")
setting, err := headerLite.Settings.BypassGet(sname)
err := stmts.getFullSetting.QueryRow(sname).Scan(&sname, &stype, &sconstraints)
if err == ErrNoRows { if err == ErrNoRows {
return common.LocalError("The setting you want to edit doesn't exist.", w, r, user) return common.LocalError("The setting you want to edit doesn't exist.", w, r, user)
} else if err != nil { } else if err != nil {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
if stype == "bool" { if setting.Type == "bool" {
if scontent == "on" || scontent == "1" { if scontent == "on" || scontent == "1" {
scontent = "1" scontent = "1"
} else { } else {
@ -588,10 +583,11 @@ func routePanelSettingEdit(w http.ResponseWriter, r *http.Request, user common.U
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
errmsg := headerLite.Settings.ParseSetting(sname, scontent, stype, sconstraints) errmsg := headerLite.Settings.ParseSetting(sname, scontent, setting.Type, setting.Constraint)
if errmsg != "" { if errmsg != "" {
return common.LocalError(errmsg, w, r, user) return common.LocalError(errmsg, w, r, user)
} }
// TODO: Do a reload instead?
common.SettingBox.Store(headerLite.Settings) common.SettingBox.Store(headerLite.Settings)
http.Redirect(w, r, "/panel/settings/", http.StatusSeeOther) http.Redirect(w, r, "/panel/settings/", http.StatusSeeOther)
@ -649,6 +645,7 @@ func routePanelWordFiltersCreate(w http.ResponseWriter, r *http.Request, user co
} }
common.AddWordFilter(int(lastID), find, replacement) common.AddWordFilter(int(lastID), find, replacement)
if !isJs { if !isJs {
http.Redirect(w, r, "/panel/settings/word-filters/", http.StatusSeeOther) http.Redirect(w, r, "/panel/settings/word-filters/", http.StatusSeeOther)
} else { } else {

View File

@ -110,100 +110,71 @@ func (build *builder) Purge(table string) (stmt *sql.Stmt, err error) {
return build.prepare(build.adapter.Purge("_builder", table)) return build.prepare(build.adapter.Purge("_builder", table))
} }
func (build *builder) prepareTx(tx *sql.Tx, res string, err error) (*sql.Stmt, error) {
if err != nil {
return nil, err
}
return tx.Prepare(res)
}
// These ones support transactions // These ones support transactions
func (build *builder) SimpleSelectTx(tx *sql.Tx, table string, columns string, where string, orderby string, limit string) (stmt *sql.Stmt, err error) { func (build *builder) SimpleSelectTx(tx *sql.Tx, table string, columns string, where string, orderby string, limit string) (stmt *sql.Stmt, err error) {
res, err := build.adapter.SimpleSelect("_builder", table, columns, where, orderby, limit) res, err := build.adapter.SimpleSelect("_builder", table, columns, where, orderby, limit)
if err != nil { return build.prepareTx(tx, res, err)
return stmt, err
}
return tx.Prepare(res)
} }
func (build *builder) SimpleCountTx(tx *sql.Tx, table string, where string, limit string) (stmt *sql.Stmt, err error) { func (build *builder) SimpleCountTx(tx *sql.Tx, table string, where string, limit string) (stmt *sql.Stmt, err error) {
res, err := build.adapter.SimpleCount("_builder", table, where, limit) res, err := build.adapter.SimpleCount("_builder", table, where, limit)
if err != nil { return build.prepareTx(tx, res, err)
return stmt, err
}
return tx.Prepare(res)
} }
func (build *builder) SimpleLeftJoinTx(tx *sql.Tx, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (stmt *sql.Stmt, err error) { func (build *builder) SimpleLeftJoinTx(tx *sql.Tx, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (stmt *sql.Stmt, err error) {
res, err := build.adapter.SimpleLeftJoin("_builder", table1, table2, columns, joiners, where, orderby, limit) res, err := build.adapter.SimpleLeftJoin("_builder", table1, table2, columns, joiners, where, orderby, limit)
if err != nil { return build.prepareTx(tx, res, err)
return stmt, err
}
return tx.Prepare(res)
} }
func (build *builder) SimpleInnerJoinTx(tx *sql.Tx, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (stmt *sql.Stmt, err error) { func (build *builder) SimpleInnerJoinTx(tx *sql.Tx, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (stmt *sql.Stmt, err error) {
res, err := build.adapter.SimpleInnerJoin("_builder", table1, table2, columns, joiners, where, orderby, limit) res, err := build.adapter.SimpleInnerJoin("_builder", table1, table2, columns, joiners, where, orderby, limit)
if err != nil { return build.prepareTx(tx, res, err)
return stmt, err
}
return tx.Prepare(res)
} }
func (build *builder) CreateTableTx(tx *sql.Tx, table string, charset string, collation string, columns []DBTableColumn, keys []DBTableKey) (stmt *sql.Stmt, err error) { func (build *builder) CreateTableTx(tx *sql.Tx, table string, charset string, collation string, columns []DBTableColumn, keys []DBTableKey) (stmt *sql.Stmt, err error) {
res, err := build.adapter.CreateTable("_builder", table, charset, collation, columns, keys) res, err := build.adapter.CreateTable("_builder", table, charset, collation, columns, keys)
if err != nil { return build.prepareTx(tx, res, err)
return stmt, err
}
return tx.Prepare(res)
} }
func (build *builder) SimpleInsertTx(tx *sql.Tx, table string, columns string, fields string) (stmt *sql.Stmt, err error) { func (build *builder) SimpleInsertTx(tx *sql.Tx, table string, columns string, fields string) (stmt *sql.Stmt, err error) {
res, err := build.adapter.SimpleInsert("_builder", table, columns, fields) res, err := build.adapter.SimpleInsert("_builder", table, columns, fields)
if err != nil { return build.prepareTx(tx, res, err)
return stmt, err
}
return tx.Prepare(res)
} }
func (build *builder) SimpleInsertSelectTx(tx *sql.Tx, ins DBInsert, sel DBSelect) (stmt *sql.Stmt, err error) { func (build *builder) SimpleInsertSelectTx(tx *sql.Tx, ins DBInsert, sel DBSelect) (stmt *sql.Stmt, err error) {
res, err := build.adapter.SimpleInsertSelect("_builder", ins, sel) res, err := build.adapter.SimpleInsertSelect("_builder", ins, sel)
if err != nil { return build.prepareTx(tx, res, err)
return stmt, err
}
return tx.Prepare(res)
} }
func (build *builder) SimpleInsertLeftJoinTx(tx *sql.Tx, ins DBInsert, sel DBJoin) (stmt *sql.Stmt, err error) { func (build *builder) SimpleInsertLeftJoinTx(tx *sql.Tx, ins DBInsert, sel DBJoin) (stmt *sql.Stmt, err error) {
res, err := build.adapter.SimpleInsertLeftJoin("_builder", ins, sel) res, err := build.adapter.SimpleInsertLeftJoin("_builder", ins, sel)
if err != nil { return build.prepareTx(tx, res, err)
return stmt, err
}
return tx.Prepare(res)
} }
func (build *builder) SimpleInsertInnerJoinTx(tx *sql.Tx, ins DBInsert, sel DBJoin) (stmt *sql.Stmt, err error) { func (build *builder) SimpleInsertInnerJoinTx(tx *sql.Tx, ins DBInsert, sel DBJoin) (stmt *sql.Stmt, err error) {
res, err := build.adapter.SimpleInsertInnerJoin("_builder", ins, sel) res, err := build.adapter.SimpleInsertInnerJoin("_builder", ins, sel)
if err != nil { return build.prepareTx(tx, res, err)
return stmt, err
}
return tx.Prepare(res)
} }
func (build *builder) SimpleUpdateTx(tx *sql.Tx, table string, set string, where string) (stmt *sql.Stmt, err error) { func (build *builder) SimpleUpdateTx(tx *sql.Tx, table string, set string, where string) (stmt *sql.Stmt, err error) {
res, err := build.adapter.SimpleUpdate("_builder", table, set, where) res, err := build.adapter.SimpleUpdate("_builder", table, set, where)
if err != nil { return build.prepareTx(tx, res, err)
return stmt, err
}
return tx.Prepare(res)
} }
func (build *builder) SimpleDeleteTx(tx *sql.Tx, table string, where string) (stmt *sql.Stmt, err error) { func (build *builder) SimpleDeleteTx(tx *sql.Tx, table string, where string) (stmt *sql.Stmt, err error) {
res, err := build.adapter.SimpleDelete("_builder", table, where) res, err := build.adapter.SimpleDelete("_builder", table, where)
if err != nil { return build.prepareTx(tx, res, err)
return stmt, err
}
return tx.Prepare(res)
} }
// I don't know why you need this, but here it is x.x // I don't know why you need this, but here it is x.x
func (build *builder) PurgeTx(tx *sql.Tx, table string) (stmt *sql.Stmt, err error) { func (build *builder) PurgeTx(tx *sql.Tx, table string) (stmt *sql.Stmt, err error) {
res, err := build.adapter.Purge("_builder", table) res, err := build.adapter.Purge("_builder", table)
if err != nil { return build.prepareTx(tx, res, err)
return stmt, err
}
return tx.Prepare(res)
} }

View File

@ -228,10 +228,6 @@ func writeSelects(adapter qgen.Adapter) error {
build.Select("getSettings").Table("settings").Columns("name, content, type").Parse() build.Select("getSettings").Table("settings").Columns("name, content, type").Parse()
build.Select("getSetting").Table("settings").Columns("content, type").Where("name = ?").Parse()
build.Select("getFullSetting").Table("settings").Columns("name, type, constraints").Where("name = ?").Parse()
build.Select("isPluginActive").Table("plugins").Columns("active").Where("uname = ?").Parse() build.Select("isPluginActive").Table("plugins").Columns("active").Where("uname = ?").Parse()
//build.Select("isPluginInstalled").Table("plugins").Columns("installed").Where("uname = ?").Parse() //build.Select("isPluginInstalled").Table("plugins").Columns("installed").Where("uname = ?").Parse()

View File

@ -1013,12 +1013,6 @@ func routeAPI(w http.ResponseWriter, r *http.Request, user common.User) common.R
//case "forums": //case "forums":
//case "users": //case "users":
//case "pages": //case "pages":
// This might not be possible. We might need .xml paths for sitemaps
/*case "sitemap":
if format != "xml" {
PreError("You can only fetch sitemaps in the XML format!",w,r)
return
}*/
default: default:
return common.PreErrorJS("Invalid Module", w, r) return common.PreErrorJS("Invalid Module", w, r)
} }

View File

@ -882,7 +882,6 @@ var forum_7 = []byte(`">&gt;</a></div>`)
var forum_8 = []byte(` var forum_8 = []byte(`
<main> <main>
<div id="forum_head_block" class="rowblock rowhead topic_list_title_block"> <div id="forum_head_block" class="rowblock rowhead topic_list_title_block">
<div class="rowitem forum_title`) <div class="rowitem forum_title`)
var forum_9 = []byte(` has_opt`) var forum_9 = []byte(` has_opt`)

View File

@ -1,24 +1,26 @@
{{template "header.html" . }} {{template "header.html" . }}
{{template "account-menu.html" . }} <main class="colstack account">
<main class="colstack_right"> {{template "account-menu.html" . }}
<div class="colstack_item colstack_head rowhead"> <div class="colstack_right">
<div class="rowitem"><h1>Edit Avatar</h1></div> <div class="colstack_item colstack_head rowhead">
</div> <div class="rowitem"><h1>Edit Avatar</h1></div>
{{if .CurrentUser.Avatar}} </div>
<div class="colstack_item"> {{if .CurrentUser.Avatar}}
<div class="rowitem"><img src="{{.CurrentUser.Avatar}}" height="128px" max-width="128px" /></div> <div class="colstack_item">
</div> <div class="rowitem"><img src="{{.CurrentUser.Avatar}}" height="128px" max-width="128px" /></div>
{{end}} </div>
<div class="colstack_item"> {{end}}
<form action="/user/edit/avatar/submit/" method="post" enctype="multipart/form-data"> <div class="colstack_item form_item">
<div class="formrow real_first_child"> <form action="/user/edit/avatar/submit/" method="post" enctype="multipart/form-data">
<div class="formitem formlabel"><a>Upload Avatar</a></div> <div class="formrow real_first_child">
<div class="formitem"><input name="account-avatar" type="file" required /></div> <div class="formitem formlabel"><a>Upload Avatar</a></div>
</div> <div class="formitem"><input name="account-avatar" type="file" required /></div>
<div class="formrow"> </div>
<div class="formitem"><button name="account-button" class="formbutton">Update</button></div> <div class="formrow">
</div> <div class="formitem"><button name="account-button" class="formbutton">Update</button></div>
</form> </div>
</form>
</div>
</div> </div>
</main> </main>
{{template "footer.html" . }} {{template "footer.html" . }}

View File

@ -1,20 +1,22 @@
{{template "header.html" . }} {{template "header.html" . }}
{{template "account-menu.html" . }} <main class="colstack account">
<main class="colstack_right"> {{template "account-menu.html" . }}
<div class="colstack_item colstack_head rowhead"> <div class="colstack_right">
<div class="rowitem"><h1>Emails</h1></div> <div class="colstack_item colstack_head rowhead">
</div> <div class="rowitem"><h1>Emails</h1></div>
<div class="colstack_item"> </div>
<!-- TODO: Do we need this inline CSS? --> <div class="colstack_item">
{{range .ItemList}} <!-- TODO: Do we need this inline CSS? -->
<div class="rowitem" style="font-weight: normal;"> {{range .ItemList}}
<a style="text-transform: none;">{{.Email}}</a> <div class="rowitem" style="font-weight: normal;">
<span style="float: right"> <a style="text-transform: none;">{{.Email}}</a>
<span class="username">{{if .Primary}}Primary{{else}}Secondary{{end}}</span> <span style="float: right">
{{if .Validated}}<span class="username" style="color: green;">Verified</span>{{else}}<a class="username" style="color: crimson;">Resend Verification Email</a>{{end}} <span class="username">{{if .Primary}}Primary{{else}}Secondary{{end}}</span>
</span> {{if .Validated}}<span class="username" style="color: green;">Verified</span>{{else}}<a class="username" style="color: crimson;">Resend Verification Email</a>{{end}}
</span>
</div>
{{end}}
</div> </div>
{{end}}
</div> </div>
</main> </main>
{{template "footer.html" . }} {{template "footer.html" . }}

View File

@ -1,23 +1,25 @@
{{template "header.html" . }} {{template "header.html" . }}
{{template "account-menu.html" . }} <main class="colstack account">
<main class="colstack_right"> {{template "account-menu.html" . }}
<div class="colstack_item colstack_head rowhead"> <div class="colstack_right">
<div class="rowitem"><h1>Edit Username</h1></div> <div class="colstack_item colstack_head rowhead">
</div> <div class="rowitem"><h1>Edit Username</h1></div>
<div class="colstack_item"> </div>
<form action="/user/edit/username/submit/" method="post"> <div class="colstack_item form_item">
<div class="formrow real_first_child"> <form action="/user/edit/username/submit/" method="post">
<div class="formitem formlabel"><a>Current Username</a></div> <div class="formrow real_first_child">
<div class="formitem formlabel">{{.CurrentUser.Name}}</div> <div class="formitem formlabel"><a>Current Username</a></div>
</div> <div class="formitem formlabel">{{.CurrentUser.Name}}</div>
<div class="formrow"> </div>
<div class="formitem formlabel"><a>New Username</a></div> <div class="formrow">
<div class="formitem"><input name="account-new-username" type="text" required /></div> <div class="formitem formlabel"><a>New Username</a></div>
</div> <div class="formitem"><input name="account-new-username" type="text" required /></div>
<div class="formrow"> </div>
<div class="formitem"><button name="account-button" class="formbutton form_middle_button">Update</button></div> <div class="formrow">
</div> <div class="formitem"><button name="account-button" class="formbutton form_middle_button">Update</button></div>
</form> </div>
</form>
</div>
</div> </div>
</main> </main>
{{template "footer.html" . }} {{template "footer.html" . }}

View File

@ -1,27 +1,29 @@
{{template "header.html" . }} {{template "header.html" . }}
{{template "account-menu.html" . }} <main class="colstack account">
<main class="colstack_right"> {{template "account-menu.html" . }}
<div class="colstack_item colstack_head rowhead"> <div class="colstack_right">
<div class="rowitem"><h1>Edit Password</h1></div> <div class="colstack_item colstack_head rowhead">
</div> <div class="rowitem"><h1>Edit Password</h1></div>
<div class="colstack_item"> </div>
<form action="/user/edit/critical/submit/" method="post"> <div class="colstack_item form_item">
<div class="formrow real_first_child"> <form action="/user/edit/critical/submit/" method="post">
<div class="formitem formlabel"><a>Current Password</a></div> <div class="formrow real_first_child">
<div class="formitem"><input name="account-current-password" type="password" placeholder="*****" required /></div> <div class="formitem formlabel"><a>Current Password</a></div>
</div> <div class="formitem"><input name="account-current-password" type="password" placeholder="*****" required /></div>
<div class="formrow"> </div>
<div class="formitem formlabel"><a>New Password</a></div> <div class="formrow">
<div class="formitem"><input name="account-new-password" type="password" placeholder="*****" required /></div> <div class="formitem formlabel"><a>New Password</a></div>
</div> <div class="formitem"><input name="account-new-password" type="password" placeholder="*****" required /></div>
<div class="formrow"> </div>
<div class="formitem formlabel"><a>Confirm Password</a></div> <div class="formrow">
<div class="formitem"><input name="account-confirm-password" type="password" placeholder="*****" required /></div> <div class="formitem formlabel"><a>Confirm Password</a></div>
</div> <div class="formitem"><input name="account-confirm-password" type="password" placeholder="*****" required /></div>
<div class="formrow"> </div>
<div class="formitem"><button name="account-button" class="formbutton form_middle_button">Update</button></div> <div class="formrow">
</div> <div class="formitem"><button name="account-button" class="formbutton form_middle_button">Update</button></div>
</form> </div>
</form>
</div>
</div> </div>
</main> </main>
{{template "footer.html" . }} {{template "footer.html" . }}

View File

@ -5,96 +5,94 @@
<div id="nextFloat" class="next_button"><a class="next_link" aria-label="Go to the next page" rel="next" href="/forum/{{.Forum.ID}}?page={{add .Page 1}}">&gt;</a></div>{{end}} <div id="nextFloat" class="next_button"><a class="next_link" aria-label="Go to the next page" rel="next" href="/forum/{{.Forum.ID}}?page={{add .Page 1}}">&gt;</a></div>{{end}}
<main> <main>
<div id="forum_head_block" class="rowblock rowhead topic_list_title_block">
<div id="forum_head_block" class="rowblock rowhead topic_list_title_block"> <div class="rowitem forum_title{{if ne .CurrentUser.ID 0}} has_opt{{end}}">
<div class="rowitem forum_title{{if ne .CurrentUser.ID 0}} has_opt{{end}}"><h1>{{.Title}}</h1> <h1>{{.Title}}</h1>
</div>
{{if ne .CurrentUser.ID 0}}
{{if .CurrentUser.Perms.CreateTopic}}
<div class="pre_opt auto_hide"></div>
<div class="opt create_topic_opt" title="Create Topic" aria-label="Create a topic"><a class="create_topic_link" href="/topics/create/{{.Forum.ID}}"></a></div>
{{/** TODO: Add a permissions check for this **/}}
<div class="opt mod_opt" title="Moderate">
<a class="moderate_link" href="#"></a>
</div>
{{else}}<div class="opt locked_opt" title="You don't have the permissions needed to create a topic" aria-label="You don't have the permissions needed to make a topic in this forum"><a></a></div>{{end}}
<div style="clear: both;"></div>
{{end}}
</div> </div>
{{if ne .CurrentUser.ID 0}} {{if ne .CurrentUser.ID 0}}
{{if .CurrentUser.Perms.CreateTopic}} <div class="mod_floater auto_hide">
<div class="pre_opt auto_hide"></div> <form method="post">
<div class="opt create_topic_opt" title="Create Topic" aria-label="Create a topic"><a class="create_topic_link" href="/topics/create/{{.Forum.ID}}"></a></div> <div class="mod_floater_head">
{{/** TODO: Add a permissions check for this **/}} <span>What do you want to do with these 18 topics?</span>
<div class="opt mod_opt" title="Moderate"> </div>
<a class="moderate_link" href="#"></a> <div class="mod_floater_body">
</div> <select class="mod_floater_options">
{{else}}<div class="opt locked_opt" title="You don't have the permissions needed to create a topic" aria-label="You don't have the permissions needed to make a topic in this forum"><a></a></div>{{end}} <option val="delete">Delete them</option>
<div style="clear: both;"></div> <option val="lock">Lock them</option>
{{end}} </select>
</div> <button>Run</button>
{{if ne .CurrentUser.ID 0}} </div>
<div class="mod_floater auto_hide"> </form>
<form method="post">
<div class="mod_floater_head">
<span>What do you want to do with these 18 topics?</span>
</div> </div>
<div class="mod_floater_body"> {{if .CurrentUser.Perms.CreateTopic}}
<select class="mod_floater_options"> <div id="forum_topic_create_form" class="rowblock topic_create_form quick_create_form" style="display: none;" aria-label="Quick Topic Form">
<option val="delete">Delete them</option> <form id="topic_create_form_form" enctype="multipart/form-data" action="/topic/create/submit/" method="post"></form>
<option val="lock">Lock them</option> {{if .CurrentUser.Avatar}}<img class="little_row_avatar" src="{{.CurrentUser.Avatar}}" height="64" />{{end}}
</select> <input form="topic_create_form_form" id="topic_board_input" name="topic-board" value="{{.Forum.ID}}" type="hidden">
<button>Run</button> <div class="main_form">
</div> <div class="topic_meta">
</form> <div class="formrow topic_name_row real_first_child">
</div> <div class="formitem">
<input form="topic_create_form_form" name="topic-name" placeholder="What's up?" required>
{{if .CurrentUser.Perms.CreateTopic}} </div>
<div id="forum_topic_create_form" class="rowblock topic_create_form quick_create_form" style="display: none;" aria-label="Quick Topic Form"> </div>
<form id="topic_create_form_form" enctype="multipart/form-data" action="/topic/create/submit/" method="post"></form> </div>
{{if .CurrentUser.Avatar}}<img class="little_row_avatar" src="{{.CurrentUser.Avatar}}" height="64" />{{end}} <div class="formrow topic_content_row">
<input form="topic_create_form_form" id="topic_board_input" name="topic-board" value="{{.Forum.ID}}" type="hidden"> <div class="formitem">
<div class="main_form"> <textarea form="topic_create_form_form" id="input_content" name="topic-content" placeholder="Insert post here" required></textarea>
<div class="topic_meta"> </div>
<div class="formrow topic_name_row real_first_child"> </div>
<div class="formitem"> <div class="formrow quick_button_row">
<input form="topic_create_form_form" name="topic-name" placeholder="What's up?" required> <div class="formitem">
<button form="topic_create_form_form" name="topic-button" class="formbutton">Create Topic</button>
{{if .CurrentUser.Perms.UploadFiles}}
<input name="upload_files" form="topic_create_form_form" id="upload_files" multiple type="file" style="display: none;" />
<label for="upload_files" class="formbutton add_file_button">Add File</label>
<div id="upload_file_dock"></div>{{end}}
<button class="formbutton close_form">Cancel</button>
</div>
</div> </div>
</div> </div>
</div> </div>
<div class="formrow topic_content_row"> {{end}}
<div class="formitem"> {{end}}
<textarea form="topic_create_form_form" id="input_content" name="topic-content" placeholder="Insert post here" required></textarea> <div id="forum_topic_list" class="rowblock topic_list">
{{range .ItemList}}<div class="topic_row" data-tid="{{.ID}}">
<div class="rowitem topic_left passive datarow {{if .Sticky}}topic_sticky{{else if .IsClosed}}topic_closed{{end}}">
<span class="selector"></span>
{{if .Creator.Avatar}}<a href="{{.Creator.Link}}"><img src="{{.Creator.Avatar}}" height="64" /></a>{{end}}
<span class="topic_inner_left">
<a class="rowtopic" href="{{.Link}}"><span>{{.Title}}</span></a>
<br /><a class="rowsmall starter" href="{{.Creator.Link}}">{{.Creator.Name}}</a>
{{/** TODO: Avoid the double '|' when both .IsClosed and .Sticky are set to true. We could probably do this with CSS **/}}
{{if .IsClosed}}<span class="rowsmall topic_status_e topic_status_closed" title="Status: Closed"> | &#x1F512;&#xFE0E</span>{{end}}
{{if .Sticky}}<span class="rowsmall topic_status_e topic_status_sticky" title="Status: Pinned"> | &#x1F4CD;&#xFE0E</span>{{end}}
</span>
<span class="topic_inner_right rowsmall" style="float: right;">
<span class="replyCount">{{.PostCount}}</span><br />
<span class="likeCount">{{.LikeCount}}</span>
</span>
</div> </div>
</div> <div class="rowitem topic_right passive datarow {{if .Sticky}}topic_sticky{{else if .IsClosed}}topic_closed{{end}}">
<div class="formrow quick_button_row"> {{if .LastUser.Avatar}}<a href="{{.LastUser.Link}}"><img src="{{.LastUser.Avatar}}" height="64" /></a>{{end}}
<div class="formitem"> <span>
<button form="topic_create_form_form" name="topic-button" class="formbutton">Create Topic</button> <a href="{{.LastUser.Link}}" class="lastName" style="font-size: 14px;">{{.LastUser.Name}}</a><br>
{{if .CurrentUser.Perms.UploadFiles}} <span class="rowsmall lastReplyAt">{{.RelativeLastReplyAt}}</span>
<input name="upload_files" form="topic_create_form_form" id="upload_files" multiple type="file" style="display: none;" /> </span>
<label for="upload_files" class="formbutton add_file_button">Add File</label>
<div id="upload_file_dock"></div>{{end}}
<button class="formbutton close_form">Cancel</button>
</div> </div>
</div>{{else}}<div class="rowitem passive">There aren't any topics in this forum yet.{{if .CurrentUser.Perms.CreateTopic}} <a href="/topics/create/{{.Forum.ID}}">Start one?</a>{{end}}</div>{{end}}
</div> </div>
</div>
</div>
{{end}}
{{end}}
<div id="forum_topic_list" class="rowblock topic_list">
{{range .ItemList}}<div class="topic_row" data-tid="{{.ID}}">
<div class="rowitem topic_left passive datarow {{if .Sticky}}topic_sticky{{else if .IsClosed}}topic_closed{{end}}">
<span class="selector"></span>
{{if .Creator.Avatar}}<a href="{{.Creator.Link}}"><img src="{{.Creator.Avatar}}" height="64" /></a>{{end}}
<span class="topic_inner_left">
<a class="rowtopic" href="{{.Link}}"><span>{{.Title}}</span></a>
<br /><a class="rowsmall starter" href="{{.Creator.Link}}">{{.Creator.Name}}</a>
{{/** TODO: Avoid the double '|' when both .IsClosed and .Sticky are set to true. We could probably do this with CSS **/}}
{{if .IsClosed}}<span class="rowsmall topic_status_e topic_status_closed" title="Status: Closed"> | &#x1F512;&#xFE0E</span>{{end}}
{{if .Sticky}}<span class="rowsmall topic_status_e topic_status_sticky" title="Status: Pinned"> | &#x1F4CD;&#xFE0E</span>{{end}}
</span>
<span class="topic_inner_right rowsmall" style="float: right;">
<span class="replyCount">{{.PostCount}}</span><br />
<span class="likeCount">{{.LikeCount}}</span>
</span>
</div>
<div class="rowitem topic_right passive datarow {{if .Sticky}}topic_sticky{{else if .IsClosed}}topic_closed{{end}}">
{{if .LastUser.Avatar}}<a href="{{.LastUser.Link}}"><img src="{{.LastUser.Avatar}}" height="64" /></a>{{end}}
<span>
<a href="{{.LastUser.Link}}" class="lastName" style="font-size: 14px;">{{.LastUser.Name}}</a><br>
<span class="rowsmall lastReplyAt">{{.RelativeLastReplyAt}}</span>
</span>
</div>
</div>{{else}}<div class="rowitem passive">There aren't any topics in this forum yet.{{if .CurrentUser.Perms.CreateTopic}} <a href="/topics/create/{{.Forum.ID}}">Start one?</a>{{end}}</div>{{end}}
</div>
</main> </main>
{{template "footer.html" . }} {{template "footer.html" . }}

View File

@ -1,28 +1,24 @@
{{template "header.html" . }} {{template "header.html" . }}
<main> <main>
<div class="rowblock rowhead">
<div class="rowblock rowhead"> <div class="rowitem">
<div class="rowitem"> <h1>IP Search</h1>
<h1>IP Search</h1> </div>
</div> </div>
</div> <form action="/users/ips/" method="get" id="ip-search-form"></form>
<div class="rowblock ip_search_block">
<form action="/users/ips/" method="get" id="ip-search-form"></form> <div class="rowitem passive">
<div class="rowblock ip_search_block"> <input form="ip-search-form" name="ip" class="ip_search_input" type="search" placeholder="🔍︎" {{if .IP}}value="{{.IP}}"{{end}}/>
<div class="rowitem passive"> <input form="ip-search-form" class="ip_search_search" type="submit" value="Search" />
<input form="ip-search-form" name="ip" class="ip_search_input" type="search" placeholder="🔍︎" {{if .IP}}value="{{.IP}}"{{end}}/> </div>
<input form="ip-search-form" class="ip_search_search" type="submit" value="Search" />
</div> </div>
</div> {{if .IP}}
<div class="rowblock bgavatars">
{{if .IP}} {{range .ItemList}}<div class="rowitem"{{if .Avatar}} style="background-image: url('{{.Avatar}}');"{{end}}>
<div class="rowblock bgavatars"> <a href="{{.Link}}">{{.Name}}</a>
{{range .ItemList}}<div class="rowitem"{{if .Avatar}} style="background-image: url('{{.Avatar}}');"{{end}}> </div>
<a href="{{.Link}}">{{.Name}}</a> {{else}}<div class="rowitem">No users found.</div>{{end}}
</div> </div>
{{else}}<div class="rowitem">No users found.</div>{{end}} {{end}}
</div>
{{end}}
</main> </main>
{{template "footer.html" . }} {{template "footer.html" . }}

View File

@ -1,21 +1,23 @@
{{template "header.html" . }} {{template "header.html" . }}
<div class="rowblock rowhead"> <main>
<div class="rowitem"><h1>Login</h1></div> <div class="rowblock rowhead">
</div> <div class="rowitem"><h1>Login</h1></div>
<div class="rowblock"> </div>
<form action="/accounts/login/submit/" method="post"> <div class="rowblock">
<div class="formrow"> <form action="/accounts/login/submit/" method="post">
<div class="formitem formlabel"><a>Account Name</a></div> <div class="formrow">
<div class="formitem"><input name="username" type="text" placeholder="Account Name" required /></div> <div class="formitem formlabel"><a>Account Name</a></div>
</div> <div class="formitem"><input name="username" type="text" placeholder="Account Name" required /></div>
<div class="formrow"> </div>
<div class="formitem formlabel"><a>Password</a></div> <div class="formrow">
<div class="formitem"><input name="password" type="password" autocomplete="current-password" placeholder="*****" required /></div> <div class="formitem formlabel"><a>Password</a></div>
</div> <div class="formitem"><input name="password" type="password" autocomplete="current-password" placeholder="*****" required /></div>
<div class="formrow"> </div>
<div class="formitem"><button name="login-button" class="formbutton">Login</div> <div class="formrow">
<div class="formitem" style="color: #505050; font-size: 12px; font-weight: normal; float: right;">Don't have an account?</div> <div class="formitem"><button name="login-button" class="formbutton">Login</div>
</div> <div class="formitem" style="color: #505050; font-size: 12px; font-weight: normal; float: right;">Don't have an account?</div>
</form> </div>
</div> </form>
</div>
</main>
{{template "footer.html" . }} {{template "footer.html" . }}

View File

@ -1,32 +1,30 @@
{{template "header.html" . }} {{template "header.html" . }}
<main> <main>
<div class="rowblock rowhead">
<div class="rowblock rowhead"> <div class="rowitem"><h1>Create Account</h1></div>
<div class="rowitem"><h1>Create Account</h1></div> </div>
</div> <div class="rowblock">
<div class="rowblock"> <form action="/accounts/create/submit/" method="post">
<form action="/accounts/create/submit/" method="post"> <div class="formrow">
<div class="formrow"> <div class="formitem formlabel"><a id="username_label">Account Name</a></div>
<div class="formitem formlabel"><a id="username_label">Account Name</a></div> <div class="formitem"><input name="username" type="text" placeholder="Account Name" aria-labelledby="username_label" required /></div>
<div class="formitem"><input name="username" type="text" placeholder="Account Name" aria-labelledby="username_label" required /></div> </div>
</div> <div class="formrow">
<div class="formrow"> <div class="formitem formlabel"><a id="email_label">Email</a></div>
<div class="formitem formlabel"><a id="email_label">Email</a></div> <div class="formitem"><input name="email" type="email" placeholder="joe.doe@example.com" aria-labelledby="email_label" required /></div>
<div class="formitem"><input name="email" type="email" placeholder="joe.doe@example.com" aria-labelledby="email_label" required /></div> </div>
</div> <div class="formrow">
<div class="formrow"> <div class="formitem formlabel"><a id="password_label">Password</a></div>
<div class="formitem formlabel"><a id="password_label">Password</a></div> <div class="formitem"><input name="password" type="password" autocomplete="new-password" placeholder="*****" aria-labelledby="password_label" required /></div>
<div class="formitem"><input name="password" type="password" autocomplete="new-password" placeholder="*****" aria-labelledby="password_label" required /></div> </div>
</div> <div class="formrow">
<div class="formrow"> <div class="formitem formlabel"><a id="confirm_password_label">Confirm Password</a></div>
<div class="formitem formlabel"><a id="confirm_password_label">Confirm Password</a></div> <div class="formitem"><input name="confirm_password" type="password" placeholder="*****" aria-labelledby="confirm_password_label" required /></div>
<div class="formitem"><input name="confirm_password" type="password" placeholder="*****" aria-labelledby="confirm_password_label" required /></div> </div>
</div> <div class="formrow">
<div class="formrow"> <div class="formitem"><button name="register-button" class="formbutton">Create Account</div></div>
<div class="formitem"><button name="register-button" class="formbutton">Create Account</div></div> </div>
</div> </form>
</form> </div>
</div>
</main> </main>
{{template "footer.html" . }} {{template "footer.html" . }}

View File

@ -157,7 +157,7 @@ ul {
display: none; display: none;
} }
.rowblock, .colstack_item { .rowblock, .colstack_head {
margin-bottom: 12px; margin-bottom: 12px;
border: 1px solid var(--header-border-color); border: 1px solid var(--header-border-color);
border-bottom: 2px solid var(--header-border-color); border-bottom: 2px solid var(--header-border-color);
@ -182,6 +182,16 @@ ul {
display: inline-block; display: inline-block;
} }
.colstack {
display: flex;
}
#main .colstack_left {
width: 300px;
}
#main .colstack_right {
width: calc(90% - 300px);
}
.extra_little_row_avatar { .extra_little_row_avatar {
height: 38px; height: 38px;
width: 38px; width: 38px;
@ -735,7 +745,7 @@ select, input, textarea {
#profile_left_pane .profileName { #profile_left_pane .profileName {
font-size: 19px; font-size: 19px;
} }
#profile_left_pane .passiveBlock .passive { .rowmenu .passive {
border: 1px solid var(--element-border-color); border: 1px solid var(--element-border-color);
border-bottom: 2px solid var(--element-border-color); border-bottom: 2px solid var(--element-border-color);
margin-top: 6px; margin-top: 6px;
@ -743,14 +753,48 @@ select, input, textarea {
padding-top: 10px; padding-top: 10px;
padding-bottom: 10px; padding-bottom: 10px;
} }
.rowmenu {
padding-left: 12px;
padding-right: 12px;
}
.rowmenu .passive:hover {
margin-left: 4px;
}
#profile_left_pane .passiveBlock .passive {
padding-left: 12px;
}
#profile_right_lane { #profile_right_lane {
width: 100%; width: 100%;
margin-right: 12px; margin-right: 12px;
} }
#profile_right_lane .colstack_item { #profile_right_lane .colstack_item, .colstack_right .colstack_item {
border: 1px solid var(--element-border-color); border: 1px solid var(--element-border-color);
border-bottom: 2px solid var(--element-border-color); border-bottom: 2px solid var(--element-border-color);
margin-left: 16px;
}
.form_item {
display: flex;
}
.form_item form, .colstack_item .formrow {
display: contents;
}
.colstack_right .formrow {
padding-left: 16px;
padding-right: 16px;
padding-bottom: 4px;
}
.colstack_right .formrow:first-child {
padding-top: 16px;
}
.colstack_right .formrow:last-child {
padding-bottom: 16px;
}
.colstack_item:not(#profile_right_lane) .formrow .formlabel {
width: min-content;
margin-right: 12px;
white-space: nowrap;
} }
.footer { .footer {