Removed the images for Cosmo from /images/, it's very saddening to see it go :(

Moved Reload from the UserStore into the UserCache.
Replaced many of the calls to Reload with CacheRemove.
Simplified the error functions.
Fixed a bug in the errors where they didn't have any CSS. Oops.
Added CustomErrorJS(), unused for now, but it may be useful in the future, especially for plugins.
Fixed many problems in the themes.
Added the Stick(), Unstick(), CreateActionReply(), Lock() and Unlock() methods to *Topic.
Renamed ip-search.html to ip-search-results.html
Added the Activate() and initPerms methods to *User.

Tempra Cursive is hiding from sight for this one commit.
Added Font Awesome to /public/, it isn't used yet.
This commit is contained in:
Azareal 2017-09-22 03:21:17 +01:00
parent 217e7abab3
commit 31f506c50c
45 changed files with 5989 additions and 796 deletions

1
.gitignore vendored
View File

@ -8,6 +8,7 @@ brun.bat
uploads/avatar_*
uploads/socialgroup_*
bin/*
out/*
*.exe
*.exe~
*.prof

15
auth.go
View File

@ -92,10 +92,10 @@ func (auth *DefaultAuth) ForceLogout(uid int) error {
return errors.New("There was a glitch in the system. Please contact your local administrator.")
}
// Flush the user out of the cache and reload
err = users.Reload(uid)
if err != nil {
return errors.New("Your account no longer exists.")
// Flush the user out of the cache
ucache, ok := users.(UserCache)
if ok {
ucache.CacheRemove(uid)
}
return nil
@ -167,7 +167,10 @@ func (auth *DefaultAuth) CreateSession(uid int) (session string, err error) {
return "", err
}
// Reload the user data
_ = users.Reload(uid)
// Flush the user data from the cache
ucache, ok := users.(UserCache)
if ok {
ucache.CacheRemove(uid)
}
return session, nil
}

208
errors.go
View File

@ -1,40 +1,18 @@
package main
import "fmt"
import "log"
import "bytes"
import "sync"
import "net/http"
import "runtime/debug"
// TODO: Use the error_buffer variable to construct the system log in the Control Panel. Should we log errors caused by users too? Or just collect statistics on those or do nothing? Intercept recover()? Could we intercept the logger instead here? We might get too much information, if we intercept the logger, maybe make it part of the Debug page?
// ? - Should we pass HeaderVars / HeaderLite rather than forcing the errors to pull the global HeaderVars instance?
var errorBufferMutex sync.RWMutex
var errorBuffer []error
//var notfoundCountPerSecond int
//var nopermsCountPerSecond int
var errorInternal []byte
var errorNotfound []byte
func initErrors() error {
var b bytes.Buffer
user := User{0, "guest", "Guest", "", 0, false, false, false, false, false, false, GuestPerms, nil, "", false, "", "", "", "", "", 0, 0, "0.0.0.0.0", 0}
pi := Page{"Internal Server Error", user, hvars, tList, "A problem has occurred in the system."}
err := templates.ExecuteTemplate(&b, "error.html", pi)
if err != nil {
return err
}
errorInternal = b.Bytes()
b.Reset()
pi = Page{"Not Found", user, hvars, tList, "The requested page doesn't exist."}
err = templates.ExecuteTemplate(&b, "error.html", pi)
if err != nil {
return err
}
errorNotfound = b.Bytes()
return nil
}
// LogError logs internal handler errors which can't be handled with InternalError() as a wrapper for log.Fatal(), we might do more with it in the future
func LogError(err error) {
@ -47,10 +25,19 @@ func LogError(err error) {
}
// InternalError is the main function for handling internal errors, while simultaneously printing out a page for the end-user to let them know that *something* has gone wrong
// ? - Add a user parameter?
func InternalError(err error, w http.ResponseWriter) {
_, _ = w.Write(errorInternal)
log.Print(err)
debug.PrintStack()
// TODO: Centralise the user struct somewhere else
user := User{0, "guest", "Guest", "", 0, false, false, false, false, false, false, GuestPerms, nil, "", false, "", "", "", "", "", 0, 0, "0.0.0.0.0", 0}
pi := Page{"Internal Server Error", user, getDefaultHeaderVar(), tList, "A problem has occurred in the system."}
err = templates.ExecuteTemplate(w, "error.html", pi)
if err != nil {
log.Print(err)
}
errorBufferMutex.Lock()
defer errorBufferMutex.Unlock()
errorBuffer = append(errorBuffer, err)
@ -58,22 +45,17 @@ func InternalError(err error, w http.ResponseWriter) {
}
// InternalErrorJSQ is the JSON "maybe" version of InternalError which can handle both JSON and normal requests
// ? - Add a user parameter?
func InternalErrorJSQ(err error, w http.ResponseWriter, r *http.Request, isJs bool) {
w.WriteHeader(500)
if !isJs {
_, _ = w.Write(errorInternal)
InternalError(err, w)
} else {
_, _ = w.Write([]byte(`{"errmsg":"A problem has occured in the system."}`))
InternalErrorJS(err, w, r)
}
log.Print(err)
debug.PrintStack()
errorBufferMutex.Lock()
defer errorBufferMutex.Unlock()
errorBuffer = append(errorBuffer, err)
log.Fatal("")
}
// InternalErrorJS is the JSON version of InternalError on routes we know will only be requested via JSON. E.g. An API.
// ? - Add a user parameter?
func InternalErrorJS(err error, w http.ResponseWriter, r *http.Request) {
w.WriteHeader(500)
_, _ = w.Write([]byte(`{"errmsg":"A problem has occured in the system."}`))
@ -87,35 +69,31 @@ func InternalErrorJS(err error, w http.ResponseWriter, r *http.Request) {
// LoginRequired is an error shown to the end-user when they try to access an area which requires them to login
func LoginRequired(w http.ResponseWriter, r *http.Request, user User) {
w.WriteHeader(401)
pi := Page{"Local Error", user, hvars, tList, "You need to login to do that."}
pi := Page{"Local Error", user, getDefaultHeaderVar(), tList, "You need to login to do that."}
if preRenderHooks["pre_render_error"] != nil {
if runPreRenderHook("pre_render_error", w, r, &user, &pi) {
return
}
}
var b bytes.Buffer
err := templates.ExecuteTemplate(&b, "error.html", pi)
err := templates.ExecuteTemplate(w, "error.html", pi)
if err != nil {
LogError(err)
}
fmt.Fprintln(w, b.String())
}
func PreError(errmsg string, w http.ResponseWriter, r *http.Request) {
w.WriteHeader(500)
user := User{ID: 0, Group: 6, Perms: GuestPerms}
pi := Page{"Error", user, hvars, tList, errmsg}
pi := Page{"Error", user, getDefaultHeaderVar(), tList, errmsg}
if preRenderHooks["pre_render_error"] != nil {
if runPreRenderHook("pre_render_error", w, r, &user, &pi) {
return
}
}
var b bytes.Buffer
err := templates.ExecuteTemplate(&b, "error.html", pi)
err := templates.ExecuteTemplate(w, "error.html", pi)
if err != nil {
LogError(err)
}
fmt.Fprintln(w, b.String())
}
func PreErrorJS(errmsg string, w http.ResponseWriter, r *http.Request) {
@ -124,60 +102,33 @@ func PreErrorJS(errmsg string, w http.ResponseWriter, r *http.Request) {
}
func PreErrorJSQ(errmsg string, w http.ResponseWriter, r *http.Request, isJs bool) {
w.WriteHeader(500)
if !isJs {
user := User{ID: 0, Group: 6, Perms: GuestPerms}
pi := Page{"Local Error", user, hvars, tList, errmsg}
if preRenderHooks["pre_render_error"] != nil {
if runPreRenderHook("pre_render_error", w, r, &user, &pi) {
return
}
}
var b bytes.Buffer
err := templates.ExecuteTemplate(&b, "error.html", pi)
if err != nil {
LogError(err)
}
fmt.Fprintln(w, b.String())
PreError(errmsg, w, r)
} else {
_, _ = w.Write([]byte(`{"errmsg":"` + errmsg + `"}`))
PreErrorJS(errmsg, w, r)
}
}
// LocalError is an error shown to the end-user when something goes wrong and it's not the software's fault
func LocalError(errmsg string, w http.ResponseWriter, r *http.Request, user User) {
w.WriteHeader(500)
pi := Page{"Local Error", user, hvars, tList, errmsg}
pi := Page{"Local Error", user, getDefaultHeaderVar(), tList, errmsg}
if preRenderHooks["pre_render_error"] != nil {
if runPreRenderHook("pre_render_error", w, r, &user, &pi) {
return
}
}
var b bytes.Buffer
err := templates.ExecuteTemplate(&b, "error.html", pi)
err := templates.ExecuteTemplate(w, "error.html", pi)
if err != nil {
LogError(err)
}
fmt.Fprintln(w, b.String())
}
func LocalErrorJSQ(errmsg string, w http.ResponseWriter, r *http.Request, user User, isJs bool) {
w.WriteHeader(500)
if !isJs {
pi := Page{"Local Error", user, hvars, tList, errmsg}
if preRenderHooks["pre_render_error"] != nil {
if runPreRenderHook("pre_render_error", w, r, &user, &pi) {
return
}
}
var b bytes.Buffer
err := templates.ExecuteTemplate(&b, "error.html", pi)
if err != nil {
LogError(err)
}
fmt.Fprintln(w, b.String())
LocalError(errmsg, w, r, user)
} else {
_, _ = w.Write([]byte(`{"errmsg":"` + errmsg + `"}`))
LocalErrorJS(errmsg, w, r)
}
}
@ -189,156 +140,135 @@ func LocalErrorJS(errmsg string, w http.ResponseWriter, r *http.Request) {
// NoPermissions is an error shown to the end-user when they try to access an area which they aren't authorised to access
func NoPermissions(w http.ResponseWriter, r *http.Request, user User) {
w.WriteHeader(403)
pi := Page{"Local Error", user, hvars, tList, "You don't have permission to do that."}
pi := Page{"Local Error", user, getDefaultHeaderVar(), tList, "You don't have permission to do that."}
if preRenderHooks["pre_render_error"] != nil {
if runPreRenderHook("pre_render_error", w, r, &user, &pi) {
return
}
}
var b bytes.Buffer
err := templates.ExecuteTemplate(&b, "error.html", pi)
err := templates.ExecuteTemplate(w, "error.html", pi)
if err != nil {
LogError(err)
}
errpage := b.String()
fmt.Fprintln(w, errpage)
}
func NoPermissionsJSQ(w http.ResponseWriter, r *http.Request, user User, isJs bool) {
w.WriteHeader(403)
if !isJs {
pi := Page{"Local Error", user, hvars, tList, "You don't have permission to do that."}
if preRenderHooks["pre_render_error"] != nil {
if runPreRenderHook("pre_render_error", w, r, &user, &pi) {
return
}
}
var b bytes.Buffer
err := templates.ExecuteTemplate(&b, "error.html", pi)
if err != nil {
LogError(err)
}
fmt.Fprintln(w, b.String())
NoPermissions(w, r, user)
} else {
_, _ = w.Write([]byte(`{"errmsg":"You don't have permission to do that."}`))
NoPermissionsJS(w, r, user)
}
}
func NoPermissionsJS(w http.ResponseWriter, r *http.Request, user User) {
w.WriteHeader(403)
_, _ = w.Write([]byte(`{"errmsg":"You don't have permission to do that."}`))
}
// ? - Is this actually used? Should it be used? A ban in Gosora should be more of a permission revocation to stop them posting rather than something which spits up an error page, right?
func Banned(w http.ResponseWriter, r *http.Request, user User) {
w.WriteHeader(403)
pi := Page{"Banned", user, hvars, tList, "You have been banned from this site."}
pi := Page{"Banned", user, getDefaultHeaderVar(), tList, "You have been banned from this site."}
if preRenderHooks["pre_render_error"] != nil {
if runPreRenderHook("pre_render_error", w, r, &user, &pi) {
return
}
}
var b bytes.Buffer
err := templates.ExecuteTemplate(&b, "error.html", pi)
err := templates.ExecuteTemplate(w, "error.html", pi)
if err != nil {
LogError(err)
}
fmt.Fprintln(w, b.String())
}
// nolint
// BannedJSQ is the version of the banned error page which handles both JavaScript requests and normal page loads
func BannedJSQ(w http.ResponseWriter, r *http.Request, user User, isJs bool) {
w.WriteHeader(403)
if !isJs {
pi := Page{"Banned", user, hvars, tList, "You have been banned from this site."}
if preRenderHooks["pre_render_error"] != nil {
if runPreRenderHook("pre_render_error", w, r, &user, &pi) {
return
}
}
var b bytes.Buffer
err := templates.ExecuteTemplate(&b, "error.html", pi)
if err != nil {
LogError(err)
}
fmt.Fprintln(w, b.String())
Banned(w, r, user)
} else {
_, _ = w.Write([]byte(`{"errmsg":"You have been banned from this site."}`))
BannedJS(w, r, user)
}
}
func BannedJS(w http.ResponseWriter, r *http.Request, user User) {
w.WriteHeader(403)
_, _ = w.Write([]byte(`{"errmsg":"You have been banned from this site."}`))
}
// nolint
func LoginRequiredJSQ(w http.ResponseWriter, r *http.Request, user User, isJs bool) {
w.WriteHeader(401)
if !isJs {
pi := Page{"Local Error", user, hvars, tList, "You need to login to do that."}
pi := Page{"Local Error", user, getDefaultHeaderVar(), tList, "You need to login to do that."}
if preRenderHooks["pre_render_error"] != nil {
if runPreRenderHook("pre_render_error", w, r, &user, &pi) {
return
}
}
var b bytes.Buffer
err := templates.ExecuteTemplate(&b, "error.html", pi)
err := templates.ExecuteTemplate(w, "error.html", pi)
if err != nil {
LogError(err)
}
fmt.Fprintln(w, b.String())
} else {
_, _ = w.Write([]byte(`{"errmsg":"You need to login to do that."}`))
}
}
// SecurityError is used whenever a session mismatch is found
// ? - Should we add JS and JSQ versions of this?
func SecurityError(w http.ResponseWriter, r *http.Request, user User) {
w.WriteHeader(403)
pi := Page{"Security Error", user, hvars, tList, "There was a security issue with your request."}
pi := Page{"Security Error", user, getDefaultHeaderVar(), tList, "There was a security issue with your request."}
if preRenderHooks["pre_render_security_error"] != nil {
if runPreRenderHook("pre_render_security_error", w, r, &user, &pi) {
return
}
}
var b bytes.Buffer
err := templates.ExecuteTemplate(&b, "error.html", pi)
err := templates.ExecuteTemplate(w, "error.html", pi)
if err != nil {
LogError(err)
}
fmt.Fprintln(w, b.String())
}
// ? - Add a JSQ and JS version of this?
// ? - Add a user parameter?
func NotFound(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(404)
_, _ = w.Write(errorNotfound)
// TODO: Centralise the user struct somewhere else
user := User{0, "guest", "Guest", "", 0, false, false, false, false, false, false, GuestPerms, nil, "", false, "", "", "", "", "", 0, 0, "0.0.0.0.0", 0}
pi := Page{"Not Found", user, getDefaultHeaderVar(), tList, "The requested page doesn't exist."}
err := templates.ExecuteTemplate(w, "error.html", pi)
if err != nil {
LogError(err)
}
}
// nolint
func CustomError(errmsg string, errcode int, errtitle string, w http.ResponseWriter, r *http.Request, user User) {
w.WriteHeader(errcode)
pi := Page{errtitle, user, hvars, tList, errmsg}
pi := Page{errtitle, user, getDefaultHeaderVar(), tList, errmsg}
if preRenderHooks["pre_render_error"] != nil {
if runPreRenderHook("pre_render_error", w, r, &user, &pi) {
return
}
}
var b bytes.Buffer
err := templates.ExecuteTemplate(&b, "error.html", pi)
err := templates.ExecuteTemplate(w, "error.html", pi)
if err != nil {
LogError(err)
}
fmt.Fprintln(w, b.String())
}
// nolint
func CustomErrorJSQ(errmsg string, errcode int, errtitle string, w http.ResponseWriter, r *http.Request, user User, isJs bool) {
w.WriteHeader(errcode)
if !isJs {
pi := Page{errtitle, user, hvars, tList, errmsg}
if preRenderHooks["pre_render_error"] != nil {
if runPreRenderHook("pre_render_error", w, r, &user, &pi) {
return
}
}
var b bytes.Buffer
err := templates.ExecuteTemplate(&b, "error.html", pi)
if err != nil {
LogError(err)
}
fmt.Fprintln(w, b.String())
CustomError(errmsg, errcode, errtitle, w, r, user)
} else {
_, _ = w.Write([]byte(`{"errmsg":"` + errmsg + `"}`))
CustomErrorJS(errmsg, errcode, errtitle, w, r, user)
}
}
// nolint
func CustomErrorJS(errmsg string, errcode int, errtitle string, w http.ResponseWriter, r *http.Request, user User) {
w.WriteHeader(errcode)
_, _ = w.Write([]byte(`{"errmsg":"` + errmsg + `"}`))
}

View File

@ -156,6 +156,7 @@ func NewPlugin(uname string, name string, author string, url string, settings st
}
}
// ? - Is this racey?
func (plugin *Plugin) AddHook(name string, handler interface{}) {
switch h := handler.(type) {
case func(interface{}) interface{}:
@ -193,6 +194,7 @@ func (plugin *Plugin) AddHook(name string, handler interface{}) {
}
}
// ? - Is this racey?
func (plugin *Plugin) RemoveHook(name string, handler interface{}) {
switch handler.(type) {
case func(interface{}) interface{}:
@ -250,6 +252,7 @@ func initPlugins() {
pluginsInited = true
}
// ? - Are the following functions racey?
func runHook(name string, data interface{}) interface{} {
for _, hook := range hooks[name] {
data = hook(data)

View File

@ -35,7 +35,6 @@ var getUserReplyUIDStmt *sql.Stmt
var hasLikedTopicStmt *sql.Stmt
var hasLikedReplyStmt *sql.Stmt
var getUserNameStmt *sql.Stmt
var getUserActiveStmt *sql.Stmt
var getEmailsByUserStmt *sql.Stmt
var getTopicBasicStmt *sql.Stmt
var getActivityEntryStmt *sql.Stmt
@ -83,6 +82,8 @@ var editTopicStmt *sql.Stmt
var editReplyStmt *sql.Stmt
var stickTopicStmt *sql.Stmt
var unstickTopicStmt *sql.Stmt
var lockTopicStmt *sql.Stmt
var unlockTopicStmt *sql.Stmt
var updateLastIPStmt *sql.Stmt
var updateSessionStmt *sql.Stmt
var setPasswordStmt *sql.Stmt
@ -292,12 +293,6 @@ func _gen_mysql() (err error) {
return err
}
log.Print("Preparing getUserActive statement.")
getUserActiveStmt, err = db.Prepare("SELECT `active` FROM `users` WHERE `uid` = ?")
if err != nil {
return err
}
log.Print("Preparing getEmailsByUser statement.")
getEmailsByUserStmt, err = db.Prepare("SELECT `email`,`validated`,`token` FROM `emails` WHERE `uid` = ?")
if err != nil {
@ -557,7 +552,7 @@ func _gen_mysql() (err error) {
}
log.Print("Preparing editTopic statement.")
editTopicStmt, err = db.Prepare("UPDATE `topics` SET `title` = ?,`content` = ?,`parsed_content` = ?,`is_closed` = ? WHERE `tid` = ?")
editTopicStmt, err = db.Prepare("UPDATE `topics` SET `title` = ?,`content` = ?,`parsed_content` = ? WHERE `tid` = ?")
if err != nil {
return err
}
@ -580,6 +575,18 @@ func _gen_mysql() (err error) {
return err
}
log.Print("Preparing lockTopic statement.")
lockTopicStmt, err = db.Prepare("UPDATE `topics` SET `is_closed` = 1 WHERE `tid` = ?")
if err != nil {
return err
}
log.Print("Preparing unlockTopic statement.")
unlockTopicStmt, err = db.Prepare("UPDATE `topics` SET `is_closed` = 0 WHERE `tid` = ?")
if err != nil {
return err
}
log.Print("Preparing updateLastIP statement.")
updateLastIPStmt, err = db.Prepare("UPDATE `users` SET `last_ip` = ? WHERE `uid` = ?")
if err != nil {

View File

@ -18,6 +18,8 @@ var editTopicStmt *sql.Stmt
var editReplyStmt *sql.Stmt
var stickTopicStmt *sql.Stmt
var unstickTopicStmt *sql.Stmt
var lockTopicStmt *sql.Stmt
var unlockTopicStmt *sql.Stmt
var updateLastIPStmt *sql.Stmt
var updateSessionStmt *sql.Stmt
var setPasswordStmt *sql.Stmt
@ -96,7 +98,7 @@ func _gen_pgsql() (err error) {
}
log.Print("Preparing editTopic statement.")
editTopicStmt, err = db.Prepare("UPDATE `topics` SET `title` = ?,`content` = ?,`parsed_content` = ?,`is_closed` = ? WHERE `tid` = ?")
editTopicStmt, err = db.Prepare("UPDATE `topics` SET `title` = ?,`content` = ?,`parsed_content` = ? WHERE `tid` = ?")
if err != nil {
return err
}
@ -119,6 +121,18 @@ func _gen_pgsql() (err error) {
return err
}
log.Print("Preparing lockTopic statement.")
lockTopicStmt, err = db.Prepare("UPDATE `topics` SET `is_closed` = 1 WHERE `tid` = ?")
if err != nil {
return err
}
log.Print("Preparing unlockTopic statement.")
unlockTopicStmt, err = db.Prepare("UPDATE `topics` SET `is_closed` = 0 WHERE `tid` = ?")
if err != nil {
return err
}
log.Print("Preparing updateLastIP statement.")
updateLastIPStmt, err = db.Prepare("UPDATE `users` SET `last_ip` = ? WHERE `uid` = ?")
if err != nil {

View File

@ -47,10 +47,6 @@ func gloinit() error {
initTemplates()
dbProd.SetMaxOpenConns(64)
err = initErrors()
if err != nil {
return err
}
err = initPhrases()
if err != nil {

43
main.go
View File

@ -72,7 +72,6 @@ func main() {
log.Print("Running Gosora v" + version.String())
fmt.Println("")
startTime = time.Now()
//timeLocation = startTime.Location()
log.Print("Processing configuration data")
processConfig()
@ -88,10 +87,6 @@ func main() {
}
initTemplates()
err = initErrors()
if err != nil {
log.Fatal(err)
}
err = initPhrases()
if err != nil {
@ -135,6 +130,8 @@ func main() {
select {
case <-secondTicker.C:
//log.Print("Running the second ticker")
// TODO: Add a plugin hook here
err := handleExpiredScheduledGroups()
if err != nil {
LogError(err)
@ -152,10 +149,16 @@ func main() {
// TODO: Manage the TopicStore, UserStore, and ForumStore
// TODO: Alert the admin, if CPU usage, RAM usage, or the number of posts in the past second are too high
// TODO: Clean-up alerts with no unread matches which are over two weeks old. Move this to a 24 hour task?
// TODO: Add a plugin hook here
case <-fifteenMinuteTicker.C:
// TODO: Add a plugin hook here
// TODO: Automatically lock topics, if they're really old, and the associated setting is enabled.
// TODO: Publish scheduled posts.
// TODO: Delete the empty users_groups_scheduler entries
// TODO: Add a plugin hook here
}
}
}()
@ -181,6 +184,8 @@ func main() {
router.HandleFunc("/topic/delete/submit/", routeDeleteTopic)
router.HandleFunc("/topic/stick/submit/", routeStickTopic)
router.HandleFunc("/topic/unstick/submit/", routeUnstickTopic)
router.HandleFunc("/topic/lock/submit/", routeLockTopic)
router.HandleFunc("/topic/unlock/submit/", routeUnlockTopic)
router.HandleFunc("/topic/like/submit/", routeLikeTopic)
// Custom Pages
@ -217,17 +222,17 @@ func main() {
// The Control Panel
// TODO: Rename the commented route handlers to the new camelCase format :'(
///router.HandleFunc("/panel/", route_panel)
///router.HandleFunc("/panel/forums/", route_panel_forums)
///router.HandleFunc("/panel/forums/create/", route_panel_forums_create_submit)
///router.HandleFunc("/panel/forums/delete/", route_panel_forums_delete)
///router.HandleFunc("/panel/forums/delete/submit/", route_panel_forums_delete_submit)
///router.HandleFunc("/panel/forums/edit/", route_panel_forums_edit)
///router.HandleFunc("/panel/forums/edit/submit/", route_panel_forums_edit_submit)
///router.HandleFunc("/panel/forums/edit/perms/submit/", route_panel_forums_edit_perms_submit)
///router.HandleFunc("/panel/settings/", route_panel_settings)
///router.HandleFunc("/panel/settings/edit/", route_panel_setting)
///router.HandleFunc("/panel/settings/edit/submit/", route_panel_setting_edit)
////router.HandleFunc("/panel/", routePanel)
////router.HandleFunc("/panel/forums/", routePanelForums)
////router.HandleFunc("/panel/forums/create/", routePanelForumsCreateSubmit)
////router.HandleFunc("/panel/forums/delete/", routePanelForumsDelete)
////router.HandleFunc("/panel/forums/delete/submit/", routePanelForumsDeleteSubmit)
////router.HandleFunc("/panel/forums/edit/", routePanelForumsEdit)
////router.HandleFunc("/panel/forums/edit/submit/", routePanelForumsEditSubmit)
////router.HandleFunc("/panel/forums/edit/perms/submit/", routePanelForumsEditPermsSubmit)
////router.HandleFunc("/panel/settings/", routePanelSettings)
////router.HandleFunc("/panel/settings/edit/", routePanelSetting)
////router.HandleFunc("/panel/settings/edit/submit/", routePanelSettingEdit)
///router.HandleFunc("/panel/themes/", route_panel_themes)
///router.HandleFunc("/panel/themes/default/", route_panel_themes_default)
///router.HandleFunc("/panel/plugins/", route_panel_plugins)
@ -245,9 +250,9 @@ func main() {
///router.HandleFunc("/panel/logs/mod/", route_panel_logs_mod)
///router.HandleFunc("/panel/debug/", route_panel_debug)
///router.HandleFunc("/api/", route_api)
//router.HandleFunc("/exit/", route_exit)
///router.HandleFunc("/", default_route)
////router.HandleFunc("/api/", routeAPI)
//router.HandleFunc("/exit/", routeExit)
////router.HandleFunc("/", config.DefaultRoute)
router.HandleFunc("/ws/", routeWebsockets)
log.Print("Initialising the plugins")

View File

@ -242,14 +242,10 @@ func routeCreateReply(w http.ResponseWriter, r *http.Request, user User) {
go notifyWatchers(lastID)
}
// Reload the topic...
err = topics.Reload(tid)
if err != nil && err == ErrNoRows {
LocalError("The destination no longer exists", w, r, user)
return
} else if err != nil {
InternalError(err, w)
return
// Flush the topic out of the cache
tcache, ok := topics.(TopicCache)
if ok {
tcache.CacheRemove(tid)
}
http.Redirect(w, r, "/topic/"+strconv.Itoa(tid), http.StatusSeeOther)
@ -348,16 +344,11 @@ func routeLikeTopic(w http.ResponseWriter, r *http.Request, user User) {
// Live alerts, if the poster is online and WebSockets is enabled
_ = wsHub.pushAlert(topic.CreatedBy, int(lastID), "like", "topic", user.ID, topic.CreatedBy, tid)
// Reload the topic...
err = topics.Reload(tid)
if err != nil && err == ErrNoRows {
LocalError("The liked topic no longer exists", w, r, user)
return
} else if err != nil {
InternalError(err, w)
return
// Flush the topic out of the cache
tcache, ok := topics.(TopicCache)
if ok {
tcache.CacheRemove(tid)
}
http.Redirect(w, r, "/topic/"+strconv.Itoa(tid), http.StatusSeeOther)
}
@ -818,10 +809,9 @@ func routeAccountOwnEditAvatarSubmit(w http.ResponseWriter, r *http.Request, use
return
}
user.Avatar = "/uploads/avatar_" + strconv.Itoa(user.ID) + "." + ext
err = users.Reload(user.ID)
if err != nil {
LocalError("This user no longer exists!", w, r, user)
return
ucache, ok := users.(UserCache)
if ok {
ucache.CacheRemove(user.ID)
}
headerVars.NoticeList = append(headerVars.NoticeList, "Your avatar was successfully updated")
@ -876,10 +866,9 @@ func routeAccountOwnEditUsernameSubmit(w http.ResponseWriter, r *http.Request, u
// TODO: Use the reloaded data instead for the name?
user.Name = newUsername
err = users.Reload(user.ID)
if err != nil {
LocalError("Your account doesn't exist!", w, r, user)
return
ucache, ok := users.(UserCache)
if ok {
ucache.CacheRemove(user.ID)
}
headerVars.NoticeList = append(headerVars.NoticeList, "Your username was successfully updated")

View File

@ -13,7 +13,6 @@ import (
// TODO: Update the stats after edits so that we don't under or over decrement stats during deletes
// TODO: Disable stat updates in posts handled by plugin_socialgroups
func routeEditTopic(w http.ResponseWriter, r *http.Request, user User) {
//log.Print("in routeEditTopic")
err := r.ParseForm()
if err != nil {
PreError("Bad Form", w, r)
@ -21,8 +20,7 @@ func routeEditTopic(w http.ResponseWriter, r *http.Request, user User) {
}
isJs := (r.PostFormValue("js") == "1")
var tid int
tid, err = strconv.Atoi(r.URL.Path[len("/topic/edit/submit/"):])
tid, err := strconv.Atoi(r.URL.Path[len("/topic/edit/submit/"):])
if err != nil {
PreErrorJSQ("The provided TopicID is not a valid number.", w, r, isJs)
return
@ -48,60 +46,24 @@ func routeEditTopic(w http.ResponseWriter, r *http.Request, user User) {
}
topicName := r.PostFormValue("topic_name")
topicStatus := r.PostFormValue("topic_status")
isClosed := (topicStatus == "closed")
topicContent := html.EscapeString(r.PostFormValue("topic_content"))
// TODO: Move this bit to the TopicStore
_, err = editTopicStmt.Exec(topicName, preparseMessage(topicContent), parseMessage(html.EscapeString(preparseMessage(topicContent))), isClosed, tid)
_, err = editTopicStmt.Exec(topicName, preparseMessage(topicContent), parseMessage(html.EscapeString(preparseMessage(topicContent))), tid)
if err != nil {
InternalErrorJSQ(err, w, r, isJs)
return
}
ipaddress, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
LocalError("Bad IP", w, r, user)
err = fstore.UpdateLastTopic(topicName, tid, user.Name, user.ID, time.Now().Format("2006-01-02 15:04:05"), oldTopic.ParentID)
if err != nil && err != ErrNoRows {
InternalError(err, w)
return
}
if oldTopic.IsClosed != isClosed {
var action string
if isClosed {
action = "lock"
} else {
action = "unlock"
}
err = addModLog(action, tid, "topic", ipaddress, user.ID)
if err != nil {
InternalError(err, w)
return
}
_, err = createActionReplyStmt.Exec(tid, action, ipaddress, user.ID)
if err != nil {
InternalError(err, w)
return
}
_, err = addRepliesToTopicStmt.Exec(1, user.ID, tid)
if err != nil {
InternalError(err, w)
return
}
err = fstore.UpdateLastTopic(topicName, tid, user.Name, user.ID, time.Now().Format("2006-01-02 15:04:05"), oldTopic.ParentID)
if err != nil && err != ErrNoRows {
InternalError(err, w)
return
}
}
err = topics.Reload(tid)
if err == ErrNoRows {
LocalErrorJSQ("This topic no longer exists!", w, r, user, isJs)
return
} else if err != nil {
InternalErrorJSQ(err, w, r, isJs)
return
tcache, ok := topics.(TopicCache)
if ok {
tcache.CacheRemove(oldTopic.ID)
}
if !isJs {
@ -194,13 +156,13 @@ func routeStickTopic(w http.ResponseWriter, r *http.Request, user User) {
return
}
// TODO: Move this into the TopicStore?
_, err = stickTopicStmt.Exec(tid)
err = topic.Stick()
if err != nil {
InternalError(err, w)
return
}
// ! - Can we use user.LastIP here? It might be racey, if another thread mutates it... We need to fix this.
ipaddress, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
LocalError("Bad IP", w, r, user)
@ -211,17 +173,11 @@ func routeStickTopic(w http.ResponseWriter, r *http.Request, user User) {
InternalError(err, w)
return
}
_, err = createActionReplyStmt.Exec(tid, "stick", ipaddress, user.ID)
err = topic.CreateActionReply("stick", ipaddress, user)
if err != nil {
InternalError(err, w)
return
}
err = topics.Reload(tid)
if err != nil {
LocalError("This topic doesn't exist!", w, r, user)
return
}
http.Redirect(w, r, "/topic/"+strconv.Itoa(tid), http.StatusSeeOther)
}
@ -251,7 +207,7 @@ func routeUnstickTopic(w http.ResponseWriter, r *http.Request, user User) {
return
}
_, err = unstickTopicStmt.Exec(tid)
err = topic.Unstick()
if err != nil {
InternalError(err, w)
return
@ -267,15 +223,111 @@ func routeUnstickTopic(w http.ResponseWriter, r *http.Request, user User) {
InternalError(err, w)
return
}
_, err = createActionReplyStmt.Exec(tid, "unstick", ipaddress, user.ID)
err = topic.CreateActionReply("unstick", ipaddress, user)
if err != nil {
InternalError(err, w)
return
}
http.Redirect(w, r, "/topic/"+strconv.Itoa(tid), http.StatusSeeOther)
}
func routeLockTopic(w http.ResponseWriter, r *http.Request, user User) {
tid, err := strconv.Atoi(r.URL.Path[len("/topic/lock/submit/"):])
if err != nil {
PreError("The provided TopicID is not a valid number.", w, r)
return
}
topic, err := topics.Get(tid)
if err == ErrNoRows {
PreError("The topic you tried to pin doesn't exist.", w, r)
return
} else if err != nil {
InternalError(err, w)
return
}
// TODO: Add hooks to make use of headerLite
_, ok := SimpleForumUserCheck(w, r, &user, topic.ParentID)
if !ok {
return
}
if !user.Perms.ViewTopic || !user.Perms.CloseTopic {
NoPermissions(w, r, user)
return
}
err = topic.Lock()
if err != nil {
InternalError(err, w)
return
}
err = topics.Reload(tid)
// ! - Can we use user.LastIP here? It might be racey, if another thread mutates it... We need to fix this.
ipaddress, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
LocalError("This topic doesn't exist!", w, r, user)
LocalError("Bad IP", w, r, user)
return
}
err = addModLog("lock", tid, "topic", ipaddress, user.ID)
if err != nil {
InternalError(err, w)
return
}
err = topic.CreateActionReply("lock", ipaddress, user)
if err != nil {
InternalError(err, w)
return
}
http.Redirect(w, r, "/topic/"+strconv.Itoa(tid), http.StatusSeeOther)
}
func routeUnlockTopic(w http.ResponseWriter, r *http.Request, user User) {
tid, err := strconv.Atoi(r.URL.Path[len("/topic/unlock/submit/"):])
if err != nil {
PreError("The provided TopicID is not a valid number.", w, r)
return
}
topic, err := topics.Get(tid)
if err == ErrNoRows {
PreError("The topic you tried to pin doesn't exist.", w, r)
return
} else if err != nil {
InternalError(err, w)
return
}
// TODO: Add hooks to make use of headerLite
_, ok := SimpleForumUserCheck(w, r, &user, topic.ParentID)
if !ok {
return
}
if !user.Perms.ViewTopic || !user.Perms.CloseTopic {
NoPermissions(w, r, user)
return
}
err = topic.Unlock()
if err != nil {
InternalError(err, w)
return
}
// ! - Can we use user.LastIP here? It might be racey, if another thread mutates it... We need to fix this.
ipaddress, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
LocalError("Bad IP", w, r, user)
return
}
err = addModLog("unlock", tid, "topic", ipaddress, user.ID)
if err != nil {
InternalError(err, w)
return
}
err = topic.CreateActionReply("unlock", ipaddress, user)
if err != nil {
InternalError(err, w)
return
}
http.Redirect(w, r, "/topic/"+strconv.Itoa(tid), http.StatusSeeOther)
@ -422,11 +474,9 @@ func routeReplyDeleteSubmit(w http.ResponseWriter, r *http.Request, user User) {
InternalError(err, w)
return
}
err = topics.Reload(reply.ParentID)
if err != nil {
LocalError("This topic no longer exists!", w, r, user)
return
tcache, ok := topics.(TopicCache)
if ok {
tcache.CacheRemove(reply.ParentID)
}
}
@ -612,7 +662,7 @@ func routeIps(w http.ResponseWriter, r *http.Request, user User) {
return
}
}
err = templates.ExecuteTemplate(w, "ip-search.html", pi)
err = templates.ExecuteTemplate(w, "ip-search-results.html", pi)
if err != nil {
InternalError(err, w)
}
@ -821,8 +871,7 @@ func routeActivate(w http.ResponseWriter, r *http.Request, user User) {
return
}
var active bool
err = getUserActiveStmt.QueryRow(uid).Scan(&active)
targetUser, err := users.Get(uid)
if err == ErrNoRows {
LocalError("The account you're trying to activate no longer exists.", w, r, user)
return
@ -831,17 +880,11 @@ func routeActivate(w http.ResponseWriter, r *http.Request, user User) {
return
}
if active {
if targetUser.Active {
LocalError("The account you're trying to activate has already been activated.", w, r, user)
return
}
_, err = activateUserStmt.Exec(uid)
if err != nil {
InternalError(err, w)
return
}
_, err = changeGroupStmt.Exec(config.DefaultGroup, uid)
err = targetUser.Activate()
if err != nil {
InternalError(err, w)
return
@ -852,16 +895,10 @@ func routeActivate(w http.ResponseWriter, r *http.Request, user User) {
LocalError("Bad IP", w, r, user)
return
}
err = addModLog("activate", uid, "user", ipaddress, user.ID)
err = addModLog("activate", targetUser.ID, "user", ipaddress, user.ID)
if err != nil {
InternalError(err, w)
return
}
err = users.Reload(uid)
if err != nil {
LocalError("This user no longer exists!", w, r, user)
return
}
http.Redirect(w, r, "/user/"+strconv.Itoa(uid), http.StatusSeeOther)
http.Redirect(w, r, "/user/"+strconv.Itoa(targetUser.ID), http.StatusSeeOther)
}

View File

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 106 KiB

View File

Before

Width:  |  Height:  |  Size: 196 KiB

After

Width:  |  Height:  |  Size: 196 KiB

View File

Before

Width:  |  Height:  |  Size: 388 KiB

After

Width:  |  Height:  |  Size: 388 KiB

View File

Before

Width:  |  Height:  |  Size: 579 KiB

After

Width:  |  Height:  |  Size: 579 KiB

View File

Before

Width:  |  Height:  |  Size: 408 KiB

After

Width:  |  Height:  |  Size: 408 KiB

View File

@ -1,12 +1,14 @@
package main
//import "fmt"
import "sync"
import "bytes"
import "strings"
import "strconv"
import "regexp"
import "html/template"
import (
//"fmt"
"bytes"
"html/template"
"regexp"
"strconv"
"strings"
"sync"
)
type HeaderVars struct {
NoticeList []string
@ -32,12 +34,8 @@ type PageWidgets struct {
RightSidebar template.HTML
}
/*type UnsafeExtData struct
{
items map[string]interface{} // Key: pluginname
}*/
// TODO: Add a ExtDataHolder interface with methods for manipulating the contents?
// ? - Could we use a sync.Map instead?
type ExtData struct {
items map[string]interface{} // Key: pluginname
sync.RWMutex

View File

@ -1154,14 +1154,14 @@ func routePanelUsers(w http.ResponseWriter, r *http.Request, user User) {
// TODO: Add a UserStore method for iterating over global users and global user offsets
for rows.Next() {
puser := User{ID: 0}
puser := &User{ID: 0}
err := rows.Scan(&puser.ID, &puser.Name, &puser.Group, &puser.Active, &puser.IsSuperAdmin, &puser.Avatar)
if err != nil {
InternalError(err, w)
return
}
initUserPerms(&puser)
puser.initPerms()
if puser.Avatar != "" {
if puser.Avatar[0] == '.' {
puser.Avatar = "/uploads/avatar_" + strconv.Itoa(puser.ID) + puser.Avatar
@ -1175,7 +1175,7 @@ func routePanelUsers(w http.ResponseWriter, r *http.Request, user User) {
} else {
puser.Tag = ""
}
userList = append(userList, puser)
userList = append(userList, *puser)
}
err = rows.Err()
if err != nil {
@ -1346,12 +1346,10 @@ func routePanelUsersEditSubmit(w http.ResponseWriter, r *http.Request, user User
SetPassword(targetUser.ID, newpassword)
}
err = users.Reload(targetUser.ID)
if err != nil {
LocalError("This user no longer exists!", w, r, user)
return
ucache, ok := users.(UserCache)
if ok {
ucache.CacheRemove(targetUser.ID)
}
http.Redirect(w, r, "/panel/users/edit/"+strconv.Itoa(targetUser.ID), http.StatusSeeOther)
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 434 KiB

View File

@ -253,8 +253,6 @@ func write_selects(adapter qgen.DB_Adapter) error {
adapter.SimpleSelect("getUserName", "users", "name", "uid = ?", "", "")
adapter.SimpleSelect("getUserActive", "users", "active", "uid = ?", "", "")
adapter.SimpleSelect("getEmailsByUser", "emails", "email, validated, token", "uid = ?", "", "")
adapter.SimpleSelect("getTopicBasic", "topics", "title, content", "tid = ?", "", "")
@ -366,7 +364,7 @@ func write_updates(adapter qgen.DB_Adapter) error {
adapter.SimpleUpdate("addLikesToReply", "replies", "likeCount = likeCount + ?", "rid = ?")
adapter.SimpleUpdate("editTopic", "topics", "title = ?, content = ?, parsed_content = ?, is_closed = ?", "tid = ?")
adapter.SimpleUpdate("editTopic", "topics", "title = ?, content = ?, parsed_content = ?", "tid = ?")
adapter.SimpleUpdate("editReply", "replies", "content = ?, parsed_content = ?", "rid = ?")
@ -374,6 +372,10 @@ func write_updates(adapter qgen.DB_Adapter) error {
adapter.SimpleUpdate("unstickTopic", "topics", "sticky = 0", "tid = ?")
adapter.SimpleUpdate("lockTopic", "topics", "is_closed = 1", "tid = ?")
adapter.SimpleUpdate("unlockTopic", "topics", "is_closed = 0", "tid = ?")
adapter.SimpleUpdate("updateLastIP", "users", "last_ip = ?", "uid = ?")
adapter.SimpleUpdate("updateSession", "users", "session = ?", "uid = ?")

View File

@ -25,14 +25,9 @@ import (
var tList []interface{}
//var nList []string
var hvars *HeaderVars // We might need to rethink this now that it's a pointer
var successJSONBytes = []byte(`{"success":"1"}`)
var cacheControlMaxAge = "max-age=" + strconv.Itoa(day)
func init() {
hvars = &HeaderVars{Site: site}
}
// HTTPSRedirect is a connection handler which redirects all HTTP requests to HTTPS
type HTTPSRedirect struct {
}

View File

@ -23,6 +23,12 @@ var MemberCheck func(w http.ResponseWriter, r *http.Request, user *User) (header
var SimpleUserCheck func(w http.ResponseWriter, r *http.Request, user *User) (headerLite *HeaderLite, success bool) = simpleUserCheck
var UserCheck func(w http.ResponseWriter, r *http.Request, user *User) (headerVars *HeaderVars, success bool) = userCheck
// This is mostly for errors.go, please create *HeaderVars on the spot instead of relying on this or the atomic store underlying it, if possible
// TODO: Write a test for this
func getDefaultHeaderVar() *HeaderVars {
return &HeaderVars{Site: site, ThemeName: fallbackTheme}
}
// TODO: Support for left sidebars and sidebars on both sides
// http.Request is for context.Context middleware. Mostly for plugin_socialgroups right now
func BuildWidgets(zone string, data interface{}, headerVars *HeaderVars, r *http.Request) {
@ -312,7 +318,7 @@ func preRoute(w http.ResponseWriter, r *http.Request) (User, bool) {
InternalError(err, w)
return *user, false
}
user.LastIP = host
user.LastIP = host // ! - Is this racey?
}
h := w.Header()

View File

@ -22,6 +22,7 @@ func handleExpiredScheduledGroups() error {
defer rows.Close()
var uid int
ucache, ok := users.(UserCache)
for rows.Next() {
err := rows.Scan(&uid)
if err != nil {
@ -35,7 +36,9 @@ func handleExpiredScheduledGroups() error {
if err != nil {
return err
}
_ = users.Reload(uid)
if ok {
ucache.CacheRemove(uid)
}
}
return rows.Err()
}

View File

@ -103,134 +103,132 @@ var topic_13 = []byte(`">
<h1 class='topic_name hide_on_edit'>`)
var topic_14 = []byte(`</h1>
`)
var topic_15 = []byte(`<span class='username hide_on_micro topic_status_e topic_status_closed hide_on_edit' title='Status: Closed' style="font-weight:normal;float: right;position:relative;top:-5px;">&#x1F512;&#xFE0E</span>`)
var topic_15 = []byte(`<span class='username hide_on_micro topic_status_e topic_status_closed hide_on_edit' title='Status: Closed'>&#x1F512;&#xFE0E</span>`)
var topic_16 = []byte(`
<input form='edit_topic_form' class='show_on_edit topic_name_input' name="topic_name" value='`)
var topic_17 = []byte(`' type="text" />
`)
var topic_18 = []byte(`<select form='edit_topic_form' name="topic_status" class='show_on_edit topic_status_input' style='float: right;'>
<option>open</option>
<option>closed</option>
</select>`)
var topic_19 = []byte(`
<button form='edit_topic_form' name="topic-button" class="formbutton show_on_edit submit_edit">Update</button>
`)
var topic_20 = []byte(`
var topic_18 = []byte(`
</div>
</div>
<article class="rowblock post_container top_post">
<div class="rowitem passive editable_parent post_item `)
var topic_21 = []byte(`" style="`)
var topic_22 = []byte(`background-image:url(`)
var topic_23 = []byte(`), url(/static/post-avatar-bg.jpg);background-position: 0px `)
var topic_24 = []byte(`-1`)
var topic_25 = []byte(`0px;background-repeat:no-repeat, repeat-y;`)
var topic_26 = []byte(`">
var topic_19 = []byte(`" style="`)
var topic_20 = []byte(`background-image:url(`)
var topic_21 = []byte(`), url(/static/post-avatar-bg.jpg);background-position: 0px `)
var topic_22 = []byte(`-1`)
var topic_23 = []byte(`0px;background-repeat:no-repeat, repeat-y;`)
var topic_24 = []byte(`">
<p class="hide_on_edit topic_content user_content" style="margin:0;padding:0;">`)
var topic_27 = []byte(`</p>
var topic_25 = []byte(`</p>
<textarea name="topic_content" class="show_on_edit topic_content_input">`)
var topic_28 = []byte(`</textarea>
var topic_26 = []byte(`</textarea>
<span class="controls">
<a href="`)
var topic_29 = []byte(`" class="username real_username">`)
var topic_30 = []byte(`</a>&nbsp;&nbsp;
var topic_27 = []byte(`" class="username real_username">`)
var topic_28 = []byte(`</a>&nbsp;&nbsp;
`)
var topic_31 = []byte(`<a href="/topic/like/submit/`)
var topic_32 = []byte(`" class="mod_button" title="Love it" style="color:#202020;">
var topic_29 = []byte(`<a href="/topic/like/submit/`)
var topic_30 = []byte(`" class="mod_button" title="Love it" style="color:#202020;">
<button class="username like_label" style="`)
var topic_33 = []byte(`background-color:/*#eaffea*/#D6FFD6;`)
var topic_34 = []byte(`"></button></a>`)
var topic_35 = []byte(`<a href='/topic/edit/`)
var topic_36 = []byte(`' class="mod_button open_edit" style="font-weight:normal;" title="Edit Topic"><button class="username edit_label"></button></a>`)
var topic_37 = []byte(`<a href='/topic/delete/submit/`)
var topic_38 = []byte(`' class="mod_button" style="font-weight:normal;" title="Delete Topic"><button class="username trash_label"></button></a>`)
var topic_39 = []byte(`<a class="mod_button" href='/topic/unstick/submit/`)
var topic_40 = []byte(`' style="font-weight:normal;" title="Unpin Topic"><button class="username unpin_label"></button></a>`)
var topic_41 = []byte(`<a href='/topic/stick/submit/`)
var topic_42 = []byte(`' class="mod_button" style="font-weight:normal;" title="Pin Topic"><button class="username pin_label"></button></a>`)
var topic_43 = []byte(`<a class="mod_button" href='/users/ips/`)
var topic_44 = []byte(`' style="font-weight:normal;" title="View IP"><button class="username ip_label"></button></a>`)
var topic_45 = []byte(`
var topic_31 = []byte(`background-color:/*#eaffea*/#D6FFD6;`)
var topic_32 = []byte(`"></button></a>`)
var topic_33 = []byte(`<a href='/topic/edit/`)
var topic_34 = []byte(`' class="mod_button open_edit" style="font-weight:normal;" title="Edit Topic"><button class="username edit_label"></button></a>`)
var topic_35 = []byte(`<a href='/topic/delete/submit/`)
var topic_36 = []byte(`' class="mod_button" style="font-weight:normal;" title="Delete Topic"><button class="username trash_label"></button></a>`)
var topic_37 = []byte(`<a class="mod_button" href='/topic/unlock/submit/`)
var topic_38 = []byte(`' style="font-weight:normal;" title="Unlock Topic"><button class="username unlock_label"></button></a>`)
var topic_39 = []byte(`<a href='/topic/lock/submit/`)
var topic_40 = []byte(`' class="mod_button" style="font-weight:normal;" title="Lock Topic"><button class="username lock_label"></button></a>`)
var topic_41 = []byte(`<a class="mod_button" href='/topic/unstick/submit/`)
var topic_42 = []byte(`' style="font-weight:normal;" title="Unpin Topic"><button class="username unpin_label"></button></a>`)
var topic_43 = []byte(`<a href='/topic/stick/submit/`)
var topic_44 = []byte(`' class="mod_button" style="font-weight:normal;" title="Pin Topic"><button class="username pin_label"></button></a>`)
var topic_45 = []byte(`<a class="mod_button" href='/users/ips/`)
var topic_46 = []byte(`' style="font-weight:normal;" title="View IP"><button class="username ip_label"></button></a>`)
var topic_47 = []byte(`
<a href="/report/submit/`)
var topic_46 = []byte(`?session=`)
var topic_47 = []byte(`&type=topic" class="mod_button report_item" style="font-weight:normal;" title="Flag Topic"><button class="username flag_label"></button></a>
var topic_48 = []byte(`?session=`)
var topic_49 = []byte(`&type=topic" class="mod_button report_item" style="font-weight:normal;" title="Flag Topic"><button class="username flag_label"></button></a>
`)
var topic_48 = []byte(`<a class="username hide_on_micro like_count">`)
var topic_49 = []byte(`</a><a class="username hide_on_micro like_count_label" title="Like Count"></a>`)
var topic_50 = []byte(`<a class="username hide_on_micro user_tag">`)
var topic_51 = []byte(`</a>`)
var topic_52 = []byte(`<a class="username hide_on_micro level">`)
var topic_53 = []byte(`</a><a class="username hide_on_micro level_label" style="float:right;" title="Level"></a>`)
var topic_54 = []byte(`
var topic_50 = []byte(`<a class="username hide_on_micro like_count">`)
var topic_51 = []byte(`</a><a class="username hide_on_micro like_count_label" title="Like Count"></a>`)
var topic_52 = []byte(`<a class="username hide_on_micro user_tag">`)
var topic_53 = []byte(`</a>`)
var topic_54 = []byte(`<a class="username hide_on_micro level">`)
var topic_55 = []byte(`</a><a class="username hide_on_micro level_label" style="float:right;" title="Level"></a>`)
var topic_56 = []byte(`
</span>
</div>
</article>
<div class="rowblock post_container" style="overflow: hidden;">`)
var topic_55 = []byte(`
var topic_57 = []byte(`
<article class="rowitem passive deletable_block editable_parent post_item action_item">
<span class="action_icon" style="font-size: 18px;padding-right: 5px;">`)
var topic_56 = []byte(`</span>
var topic_58 = []byte(`</span>
<span>`)
var topic_57 = []byte(`</span>
var topic_59 = []byte(`</span>
</article>
`)
var topic_58 = []byte(`
var topic_60 = []byte(`
<article class="rowitem passive deletable_block editable_parent post_item `)
var topic_59 = []byte(`" style="`)
var topic_60 = []byte(`background-image:url(`)
var topic_61 = []byte(`), url(/static/post-avatar-bg.jpg);background-position: 0px `)
var topic_62 = []byte(`-1`)
var topic_63 = []byte(`0px;background-repeat:no-repeat, repeat-y;`)
var topic_64 = []byte(`">
var topic_61 = []byte(`" style="`)
var topic_62 = []byte(`background-image:url(`)
var topic_63 = []byte(`), url(/static/post-avatar-bg.jpg);background-position: 0px `)
var topic_64 = []byte(`-1`)
var topic_65 = []byte(`0px;background-repeat:no-repeat, repeat-y;`)
var topic_66 = []byte(`">
<p class="editable_block user_content" style="margin:0;padding:0;">`)
var topic_65 = []byte(`</p>
var topic_67 = []byte(`</p>
<span class="controls">
<a href="`)
var topic_66 = []byte(`" class="username real_username">`)
var topic_67 = []byte(`</a>&nbsp;&nbsp;
var topic_68 = []byte(`" class="username real_username">`)
var topic_69 = []byte(`</a>&nbsp;&nbsp;
`)
var topic_68 = []byte(`<a href="/reply/like/submit/`)
var topic_69 = []byte(`" class="mod_button" title="Love it" style="color:#202020;"><button class="username like_label" style="`)
var topic_70 = []byte(`background-color:/*#eaffea*/#D6FFD6;`)
var topic_71 = []byte(`"></button></a>`)
var topic_72 = []byte(`<a href="/reply/edit/submit/`)
var topic_73 = []byte(`" class="mod_button" title="Edit Reply"><button class="username edit_item edit_label"></button></a>`)
var topic_74 = []byte(`<a href="/reply/delete/submit/`)
var topic_75 = []byte(`" class="mod_button" title="Delete Reply"><button class="username delete_item trash_label"></button></a>`)
var topic_76 = []byte(`<a class="mod_button" href='/users/ips/`)
var topic_77 = []byte(`' style="font-weight:normal;" title="View IP"><button class="username ip_label"></button></a>`)
var topic_78 = []byte(`
var topic_70 = []byte(`<a href="/reply/like/submit/`)
var topic_71 = []byte(`" class="mod_button" title="Love it" style="color:#202020;"><button class="username like_label" style="`)
var topic_72 = []byte(`background-color:/*#eaffea*/#D6FFD6;`)
var topic_73 = []byte(`"></button></a>`)
var topic_74 = []byte(`<a href="/reply/edit/submit/`)
var topic_75 = []byte(`" class="mod_button" title="Edit Reply"><button class="username edit_item edit_label"></button></a>`)
var topic_76 = []byte(`<a href="/reply/delete/submit/`)
var topic_77 = []byte(`" class="mod_button" title="Delete Reply"><button class="username delete_item trash_label"></button></a>`)
var topic_78 = []byte(`<a class="mod_button" href='/users/ips/`)
var topic_79 = []byte(`' style="font-weight:normal;" title="View IP"><button class="username ip_label"></button></a>`)
var topic_80 = []byte(`
<a href="/report/submit/`)
var topic_79 = []byte(`?session=`)
var topic_80 = []byte(`&type=reply" class="mod_button report_item" title="Flag Reply"><button class="username report_item flag_label"></button></a>
var topic_81 = []byte(`?session=`)
var topic_82 = []byte(`&type=reply" class="mod_button report_item" title="Flag Reply"><button class="username report_item flag_label"></button></a>
`)
var topic_81 = []byte(`<a class="username hide_on_micro like_count">`)
var topic_82 = []byte(`</a><a class="username hide_on_micro like_count_label" title="Like Count"></a>`)
var topic_83 = []byte(`<a class="username hide_on_micro user_tag">`)
var topic_84 = []byte(`</a>`)
var topic_85 = []byte(`<a class="username hide_on_micro level">`)
var topic_86 = []byte(`</a><a class="username hide_on_micro level_label" style="float:right;" title="Level"></a>`)
var topic_87 = []byte(`
var topic_83 = []byte(`<a class="username hide_on_micro like_count">`)
var topic_84 = []byte(`</a><a class="username hide_on_micro like_count_label" title="Like Count"></a>`)
var topic_85 = []byte(`<a class="username hide_on_micro user_tag">`)
var topic_86 = []byte(`</a>`)
var topic_87 = []byte(`<a class="username hide_on_micro level">`)
var topic_88 = []byte(`</a><a class="username hide_on_micro level_label" style="float:right;" title="Level"></a>`)
var topic_89 = []byte(`
</span>
</article>
`)
var topic_88 = []byte(`</div>
var topic_90 = []byte(`</div>
`)
var topic_89 = []byte(`
var topic_91 = []byte(`
<div class="rowblock topic_reply_form">
<form action="/reply/create/" method="post">
<input name="tid" value='`)
var topic_90 = []byte(`' type="hidden" />
var topic_92 = []byte(`' type="hidden" />
<div class="formrow real_first_child">
<div class="formitem"><textarea name="reply-content" placeholder="Insert reply here" required></textarea></div>
</div>
@ -240,7 +238,7 @@ var topic_90 = []byte(`' type="hidden" />
</form>
</div>
`)
var topic_91 = []byte(`
var topic_93 = []byte(`
</main>
@ -299,15 +297,9 @@ var topic_alt_14 = []byte(`<span class='username hide_on_micro topic_status_e to
var topic_alt_15 = []byte(`
<input class='show_on_edit topic_name_input' name="topic_name" value='`)
var topic_alt_16 = []byte(`' type="text" />
`)
var topic_alt_17 = []byte(`<select name="topic_status" class='show_on_edit topic_status_input' style='float: right;'>
<option>open</option>
<option>closed</option>
</select>`)
var topic_alt_18 = []byte(`
<button name="topic-button" class="formbutton show_on_edit submit_edit">Update</button>
`)
var topic_alt_19 = []byte(`
var topic_alt_17 = []byte(`
</div>
</form>
</div>
@ -316,118 +308,122 @@ var topic_alt_19 = []byte(`
<article class="rowitem passive deletable_block editable_parent post_item top_post">
<div class="userinfo">
<div class="avatar_item" style="background-image: url(`)
var topic_alt_20 = []byte(`), url(/static/white-dot.jpg);background-position: 0px -10px;">&nbsp;</div>
var topic_alt_18 = []byte(`), url(/static/white-dot.jpg);background-position: 0px -10px;">&nbsp;</div>
<a href="`)
var topic_alt_21 = []byte(`" class="the_name">`)
var topic_alt_22 = []byte(`</a>
var topic_alt_19 = []byte(`" class="the_name">`)
var topic_alt_20 = []byte(`</a>
`)
var topic_alt_23 = []byte(`<div class="tag_block"><div class="tag_pre"></div><div class="post_tag">`)
var topic_alt_21 = []byte(`<div class="tag_block"><div class="tag_pre"></div><div class="post_tag">`)
var topic_alt_22 = []byte(`</div><div class="tag_post"></div></div>`)
var topic_alt_23 = []byte(`<div class="tag_block"><div class="tag_pre"></div><div class="post_tag post_level">Level `)
var topic_alt_24 = []byte(`</div><div class="tag_post"></div></div>`)
var topic_alt_25 = []byte(`<div class="tag_block"><div class="tag_pre"></div><div class="post_tag post_level">Level `)
var topic_alt_26 = []byte(`</div><div class="tag_post"></div></div>`)
var topic_alt_27 = []byte(`
var topic_alt_25 = []byte(`
</div>
<div class="content_container">
<div class="hide_on_edit topic_content user_content">`)
var topic_alt_28 = []byte(`</div>
var topic_alt_26 = []byte(`</div>
<textarea name="topic_content" class="show_on_edit topic_content_input">`)
var topic_alt_29 = []byte(`</textarea>
var topic_alt_27 = []byte(`</textarea>
<div class="button_container">
`)
var topic_alt_30 = []byte(`<a href="/topic/like/submit/`)
var topic_alt_31 = []byte(`" class="action_button">+1</a>`)
var topic_alt_32 = []byte(`<a href="/topic/edit/`)
var topic_alt_33 = []byte(`" class="action_button open_edit">Edit</a>`)
var topic_alt_34 = []byte(`<a href="/topic/delete/submit/`)
var topic_alt_35 = []byte(`" class="action_button delete_item">Delete</a>`)
var topic_alt_36 = []byte(`<a href='/topic/unstick/submit/`)
var topic_alt_37 = []byte(`' class="action_button">Unpin</a>`)
var topic_alt_38 = []byte(`<a href='/topic/stick/submit/`)
var topic_alt_39 = []byte(`' class="action_button">Pin</a>`)
var topic_alt_40 = []byte(`
var topic_alt_28 = []byte(`<a href="/topic/like/submit/`)
var topic_alt_29 = []byte(`" class="action_button">+1</a>`)
var topic_alt_30 = []byte(`<a href="/topic/edit/`)
var topic_alt_31 = []byte(`" class="action_button open_edit">Edit</a>`)
var topic_alt_32 = []byte(`<a href="/topic/delete/submit/`)
var topic_alt_33 = []byte(`" class="action_button delete_item">Delete</a>`)
var topic_alt_34 = []byte(`<a href='/topic/unlock/submit/`)
var topic_alt_35 = []byte(`' class="action_button">Unlock</a>`)
var topic_alt_36 = []byte(`<a href='/topic/lock/submit/`)
var topic_alt_37 = []byte(`' class="action_button">Lock</a>`)
var topic_alt_38 = []byte(`<a href='/topic/unstick/submit/`)
var topic_alt_39 = []byte(`' class="action_button">Unpin</a>`)
var topic_alt_40 = []byte(`<a href='/topic/stick/submit/`)
var topic_alt_41 = []byte(`' class="action_button">Pin</a>`)
var topic_alt_42 = []byte(`
<a href="/report/submit/`)
var topic_alt_41 = []byte(`?session=`)
var topic_alt_42 = []byte(`&type=topic" class="action_button report_item">Report</a>
var topic_alt_43 = []byte(`?session=`)
var topic_alt_44 = []byte(`&type=topic" class="action_button report_item">Report</a>
`)
var topic_alt_43 = []byte(`<a class="action_button action_button_right like_count hide_on_micro">`)
var topic_alt_44 = []byte(` up</a>`)
var topic_alt_45 = []byte(`
var topic_alt_45 = []byte(`<a class="action_button action_button_right like_count hide_on_micro">`)
var topic_alt_46 = []byte(` up</a>`)
var topic_alt_47 = []byte(`
<a class="action_button action_button_right created_at hide_on_mobile">`)
var topic_alt_46 = []byte(`</a>
var topic_alt_48 = []byte(`</a>
`)
var topic_alt_47 = []byte(`<a href="#" title="IP Address" class="action_button action_button_right ip_item hide_on_mobile">`)
var topic_alt_48 = []byte(`</a>`)
var topic_alt_49 = []byte(`
var topic_alt_49 = []byte(`<a href="#" title="IP Address" class="action_button action_button_right ip_item hide_on_mobile">`)
var topic_alt_50 = []byte(`</a>`)
var topic_alt_51 = []byte(`
</div>
</div><div style="clear:both;"></div>
</article>
`)
var topic_alt_50 = []byte(`
var topic_alt_52 = []byte(`
<article class="rowitem passive deletable_block editable_parent post_item `)
var topic_alt_51 = []byte(`action_item`)
var topic_alt_52 = []byte(`">
var topic_alt_53 = []byte(`action_item`)
var topic_alt_54 = []byte(`">
<div class="userinfo">
<div class="avatar_item" style="background-image: url(`)
var topic_alt_53 = []byte(`), url(/static/white-dot.jpg);background-position: 0px -10px;">&nbsp;</div>
var topic_alt_55 = []byte(`), url(/static/white-dot.jpg);background-position: 0px -10px;">&nbsp;</div>
<a href="`)
var topic_alt_54 = []byte(`" class="the_name">`)
var topic_alt_55 = []byte(`</a>
var topic_alt_56 = []byte(`" class="the_name">`)
var topic_alt_57 = []byte(`</a>
`)
var topic_alt_56 = []byte(`<div class="tag_block"><div class="tag_pre"></div><div class="post_tag">`)
var topic_alt_57 = []byte(`</div><div class="tag_post"></div></div>`)
var topic_alt_58 = []byte(`<div class="tag_block"><div class="tag_pre"></div><div class="post_tag post_level">Level `)
var topic_alt_58 = []byte(`<div class="tag_block"><div class="tag_pre"></div><div class="post_tag">`)
var topic_alt_59 = []byte(`</div><div class="tag_post"></div></div>`)
var topic_alt_60 = []byte(`
var topic_alt_60 = []byte(`<div class="tag_block"><div class="tag_pre"></div><div class="post_tag post_level">Level `)
var topic_alt_61 = []byte(`</div><div class="tag_post"></div></div>`)
var topic_alt_62 = []byte(`
</div>
<div class="content_container" `)
var topic_alt_61 = []byte(`style="margin-left: 0px;"`)
var topic_alt_62 = []byte(`>
var topic_alt_63 = []byte(`style="margin-left: 0px;"`)
var topic_alt_64 = []byte(`>
`)
var topic_alt_63 = []byte(`
var topic_alt_65 = []byte(`
<span class="action_icon" style="font-size: 18px;padding-right: 5px;">`)
var topic_alt_64 = []byte(`</span>
var topic_alt_66 = []byte(`</span>
<span>`)
var topic_alt_65 = []byte(`</span>
var topic_alt_67 = []byte(`</span>
`)
var topic_alt_66 = []byte(`
var topic_alt_68 = []byte(`
<div class="editable_block user_content">`)
var topic_alt_67 = []byte(`</div>
var topic_alt_69 = []byte(`</div>
<div class="button_container">
`)
var topic_alt_68 = []byte(`<a href="/reply/like/submit/`)
var topic_alt_69 = []byte(`" class="action_button">+1</a>`)
var topic_alt_70 = []byte(`<a href="/reply/edit/submit/`)
var topic_alt_71 = []byte(`" class="action_button edit_item">Edit</a>`)
var topic_alt_72 = []byte(`<a href="/reply/delete/submit/`)
var topic_alt_73 = []byte(`" class="action_button delete_item">Delete</a>`)
var topic_alt_74 = []byte(`
var topic_alt_70 = []byte(`<a href="/reply/like/submit/`)
var topic_alt_71 = []byte(`" class="action_button">+1</a>`)
var topic_alt_72 = []byte(`<a href="/reply/edit/submit/`)
var topic_alt_73 = []byte(`" class="action_button edit_item">Edit</a>`)
var topic_alt_74 = []byte(`<a href="/reply/delete/submit/`)
var topic_alt_75 = []byte(`" class="action_button delete_item">Delete</a>`)
var topic_alt_76 = []byte(`
<a href="/report/submit/`)
var topic_alt_75 = []byte(`?session=`)
var topic_alt_76 = []byte(`&type=reply" class="action_button report_item">Report</a>
var topic_alt_77 = []byte(`?session=`)
var topic_alt_78 = []byte(`&type=reply" class="action_button report_item">Report</a>
`)
var topic_alt_77 = []byte(`<a class="action_button action_button_right like_count hide_on_micro">`)
var topic_alt_78 = []byte(` up</a>`)
var topic_alt_79 = []byte(`
var topic_alt_79 = []byte(`<a class="action_button action_button_right like_count hide_on_micro">`)
var topic_alt_80 = []byte(` up</a>`)
var topic_alt_81 = []byte(`
<a class="action_button action_button_right created_at hide_on_mobile">`)
var topic_alt_80 = []byte(`</a>
var topic_alt_82 = []byte(`</a>
`)
var topic_alt_81 = []byte(`<a href="#" title="IP Address" class="action_button action_button_right ip_item hide_on_mobile">`)
var topic_alt_82 = []byte(`</a>`)
var topic_alt_83 = []byte(`
var topic_alt_83 = []byte(`<a href="#" title="IP Address" class="action_button action_button_right ip_item hide_on_mobile">`)
var topic_alt_84 = []byte(`</a>`)
var topic_alt_85 = []byte(`
</div>
`)
var topic_alt_84 = []byte(`
var topic_alt_86 = []byte(`
</div>
<div style="clear:both;"></div>
</article>
`)
var topic_alt_85 = []byte(`</div>
var topic_alt_87 = []byte(`</div>
`)
var topic_alt_86 = []byte(`
var topic_alt_88 = []byte(`
<div class="rowblock topic_reply_form">
<form action="/reply/create/" method="post">
<input name="tid" value='`)
var topic_alt_87 = []byte(`' type="hidden" />
var topic_alt_89 = []byte(`' type="hidden" />
<div class="formrow">
<div class="formitem"><textarea name="reply-content" placeholder="Insert reply here" required></textarea></div>
</div>
@ -437,7 +433,7 @@ var topic_alt_87 = []byte(`' type="hidden" />
</form>
</div>
`)
var topic_alt_88 = []byte(`
var topic_alt_90 = []byte(`
</main>

View File

@ -109,169 +109,176 @@ if tmpl_topic_vars.CurrentUser.Perms.EditTopic {
w.Write(topic_16)
w.Write([]byte(tmpl_topic_vars.Topic.Title))
w.Write(topic_17)
if tmpl_topic_vars.CurrentUser.Perms.CloseTopic {
}
w.Write(topic_18)
}
w.Write(topic_19)
}
w.Write(topic_20)
w.Write([]byte(tmpl_topic_vars.Topic.ClassName))
w.Write(topic_21)
w.Write(topic_19)
if tmpl_topic_vars.Topic.Avatar != "" {
w.Write(topic_22)
w.Write(topic_20)
w.Write([]byte(tmpl_topic_vars.Topic.Avatar))
w.Write(topic_23)
w.Write(topic_21)
if tmpl_topic_vars.Topic.ContentLines <= 5 {
w.Write(topic_22)
}
w.Write(topic_23)
}
w.Write(topic_24)
}
w.Write([]byte(tmpl_topic_vars.Topic.Content))
w.Write(topic_25)
}
w.Write([]byte(tmpl_topic_vars.Topic.Content))
w.Write(topic_26)
w.Write([]byte(tmpl_topic_vars.Topic.Content))
w.Write(topic_27)
w.Write([]byte(tmpl_topic_vars.Topic.Content))
w.Write(topic_28)
w.Write([]byte(tmpl_topic_vars.Topic.UserLink))
w.Write(topic_29)
w.Write(topic_27)
w.Write([]byte(tmpl_topic_vars.Topic.CreatedByName))
w.Write(topic_30)
w.Write(topic_28)
if tmpl_topic_vars.CurrentUser.Perms.LikeItem {
w.Write(topic_31)
w.Write(topic_29)
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID)))
w.Write(topic_32)
w.Write(topic_30)
if tmpl_topic_vars.Topic.Liked {
w.Write(topic_33)
w.Write(topic_31)
}
w.Write(topic_34)
w.Write(topic_32)
}
if tmpl_topic_vars.CurrentUser.Perms.EditTopic {
w.Write(topic_33)
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID)))
w.Write(topic_34)
}
if tmpl_topic_vars.CurrentUser.Perms.DeleteTopic {
w.Write(topic_35)
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID)))
w.Write(topic_36)
}
if tmpl_topic_vars.CurrentUser.Perms.DeleteTopic {
if tmpl_topic_vars.CurrentUser.Perms.CloseTopic {
if tmpl_topic_vars.Topic.IsClosed {
w.Write(topic_37)
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID)))
w.Write(topic_38)
}
if tmpl_topic_vars.CurrentUser.Perms.PinTopic {
if tmpl_topic_vars.Topic.Sticky {
} else {
w.Write(topic_39)
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID)))
w.Write(topic_40)
} else {
}
}
if tmpl_topic_vars.CurrentUser.Perms.PinTopic {
if tmpl_topic_vars.Topic.Sticky {
w.Write(topic_41)
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID)))
w.Write(topic_42)
} else {
w.Write(topic_43)
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID)))
w.Write(topic_44)
}
}
if tmpl_topic_vars.CurrentUser.Perms.ViewIPs {
w.Write(topic_43)
w.Write([]byte(tmpl_topic_vars.Topic.IPAddress))
w.Write(topic_44)
}
w.Write(topic_45)
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID)))
w.Write([]byte(tmpl_topic_vars.Topic.IPAddress))
w.Write(topic_46)
w.Write([]byte(tmpl_topic_vars.CurrentUser.Session))
}
w.Write(topic_47)
if tmpl_topic_vars.Topic.LikeCount > 0 {
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID)))
w.Write(topic_48)
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.LikeCount)))
w.Write([]byte(tmpl_topic_vars.CurrentUser.Session))
w.Write(topic_49)
if tmpl_topic_vars.Topic.LikeCount > 0 {
w.Write(topic_50)
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.LikeCount)))
w.Write(topic_51)
}
if tmpl_topic_vars.Topic.Tag != "" {
w.Write(topic_50)
w.Write([]byte(tmpl_topic_vars.Topic.Tag))
w.Write(topic_51)
} else {
w.Write(topic_52)
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.Level)))
w.Write([]byte(tmpl_topic_vars.Topic.Tag))
w.Write(topic_53)
}
} else {
w.Write(topic_54)
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.Level)))
w.Write(topic_55)
}
w.Write(topic_56)
if len(tmpl_topic_vars.ItemList) != 0 {
for _, item := range tmpl_topic_vars.ItemList {
if item.ActionType != "" {
w.Write(topic_55)
w.Write([]byte(item.ActionIcon))
w.Write(topic_56)
w.Write([]byte(item.ActionType))
w.Write(topic_57)
} else {
w.Write([]byte(item.ActionIcon))
w.Write(topic_58)
w.Write([]byte(item.ClassName))
w.Write([]byte(item.ActionType))
w.Write(topic_59)
if item.Avatar != "" {
} else {
w.Write(topic_60)
w.Write([]byte(item.Avatar))
w.Write([]byte(item.ClassName))
w.Write(topic_61)
if item.ContentLines <= 5 {
if item.Avatar != "" {
w.Write(topic_62)
}
w.Write([]byte(item.Avatar))
w.Write(topic_63)
}
if item.ContentLines <= 5 {
w.Write(topic_64)
w.Write([]byte(item.ContentHtml))
}
w.Write(topic_65)
w.Write([]byte(item.UserLink))
}
w.Write(topic_66)
w.Write([]byte(item.CreatedByName))
w.Write([]byte(item.ContentHtml))
w.Write(topic_67)
if tmpl_topic_vars.CurrentUser.Perms.LikeItem {
w.Write([]byte(item.UserLink))
w.Write(topic_68)
w.Write([]byte(strconv.Itoa(item.ID)))
w.Write([]byte(item.CreatedByName))
w.Write(topic_69)
if item.Liked {
if tmpl_topic_vars.CurrentUser.Perms.LikeItem {
w.Write(topic_70)
}
w.Write(topic_71)
}
if tmpl_topic_vars.CurrentUser.Perms.EditReply {
w.Write(topic_72)
w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(topic_71)
if item.Liked {
w.Write(topic_72)
}
w.Write(topic_73)
}
if tmpl_topic_vars.CurrentUser.Perms.DeleteReply {
if tmpl_topic_vars.CurrentUser.Perms.EditReply {
w.Write(topic_74)
w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(topic_75)
}
if tmpl_topic_vars.CurrentUser.Perms.ViewIPs {
if tmpl_topic_vars.CurrentUser.Perms.DeleteReply {
w.Write(topic_76)
w.Write([]byte(item.IPAddress))
w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(topic_77)
}
if tmpl_topic_vars.CurrentUser.Perms.ViewIPs {
w.Write(topic_78)
w.Write([]byte(strconv.Itoa(item.ID)))
w.Write([]byte(item.IPAddress))
w.Write(topic_79)
w.Write([]byte(tmpl_topic_vars.CurrentUser.Session))
}
w.Write(topic_80)
if item.LikeCount > 0 {
w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(topic_81)
w.Write([]byte(strconv.Itoa(item.LikeCount)))
w.Write([]byte(tmpl_topic_vars.CurrentUser.Session))
w.Write(topic_82)
if item.LikeCount > 0 {
w.Write(topic_83)
w.Write([]byte(strconv.Itoa(item.LikeCount)))
w.Write(topic_84)
}
if item.Tag != "" {
w.Write(topic_83)
w.Write([]byte(item.Tag))
w.Write(topic_84)
} else {
w.Write(topic_85)
w.Write([]byte(strconv.Itoa(item.Level)))
w.Write([]byte(item.Tag))
w.Write(topic_86)
}
} else {
w.Write(topic_87)
}
}
}
w.Write([]byte(strconv.Itoa(item.Level)))
w.Write(topic_88)
if tmpl_topic_vars.CurrentUser.Perms.CreateReply {
w.Write(topic_89)
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID)))
w.Write(topic_90)
}
w.Write(topic_89)
}
}
}
w.Write(topic_90)
if tmpl_topic_vars.CurrentUser.Perms.CreateReply {
w.Write(topic_91)
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID)))
w.Write(topic_92)
}
w.Write(topic_93)
w.Write(footer_0)
if len(tmpl_topic_vars.Header.Themes) != 0 {
for _, item := range tmpl_topic_vars.Header.Themes {

View File

@ -108,163 +108,170 @@ if tmpl_topic_alt_vars.CurrentUser.Perms.EditTopic {
w.Write(topic_alt_15)
w.Write([]byte(tmpl_topic_alt_vars.Topic.Title))
w.Write(topic_alt_16)
if tmpl_topic_alt_vars.CurrentUser.Perms.CloseTopic {
}
w.Write(topic_alt_17)
}
w.Write(topic_alt_18)
}
w.Write(topic_alt_19)
w.Write([]byte(tmpl_topic_alt_vars.Topic.Avatar))
w.Write(topic_alt_20)
w.Write(topic_alt_18)
w.Write([]byte(tmpl_topic_alt_vars.Topic.UserLink))
w.Write(topic_alt_21)
w.Write(topic_alt_19)
w.Write([]byte(tmpl_topic_alt_vars.Topic.CreatedByName))
w.Write(topic_alt_22)
w.Write(topic_alt_20)
if tmpl_topic_alt_vars.Topic.Tag != "" {
w.Write(topic_alt_23)
w.Write(topic_alt_21)
w.Write([]byte(tmpl_topic_alt_vars.Topic.Tag))
w.Write(topic_alt_24)
w.Write(topic_alt_22)
} else {
w.Write(topic_alt_25)
w.Write(topic_alt_23)
w.Write([]byte(strconv.Itoa(tmpl_topic_alt_vars.Topic.Level)))
w.Write(topic_alt_26)
w.Write(topic_alt_24)
}
w.Write(topic_alt_25)
w.Write([]byte(tmpl_topic_alt_vars.Topic.Content))
w.Write(topic_alt_26)
w.Write([]byte(tmpl_topic_alt_vars.Topic.Content))
w.Write(topic_alt_27)
w.Write([]byte(tmpl_topic_alt_vars.Topic.Content))
w.Write(topic_alt_28)
w.Write([]byte(tmpl_topic_alt_vars.Topic.Content))
w.Write(topic_alt_29)
if tmpl_topic_alt_vars.CurrentUser.Loggedin {
if tmpl_topic_alt_vars.CurrentUser.Perms.LikeItem {
w.Write(topic_alt_28)
w.Write([]byte(strconv.Itoa(tmpl_topic_alt_vars.Topic.ID)))
w.Write(topic_alt_29)
}
if tmpl_topic_alt_vars.CurrentUser.Perms.EditTopic {
w.Write(topic_alt_30)
w.Write([]byte(strconv.Itoa(tmpl_topic_alt_vars.Topic.ID)))
w.Write(topic_alt_31)
}
if tmpl_topic_alt_vars.CurrentUser.Perms.EditTopic {
if tmpl_topic_alt_vars.CurrentUser.Perms.DeleteTopic {
w.Write(topic_alt_32)
w.Write([]byte(strconv.Itoa(tmpl_topic_alt_vars.Topic.ID)))
w.Write(topic_alt_33)
}
if tmpl_topic_alt_vars.CurrentUser.Perms.DeleteTopic {
if tmpl_topic_alt_vars.CurrentUser.Perms.CloseTopic {
if tmpl_topic_alt_vars.Topic.IsClosed {
w.Write(topic_alt_34)
w.Write([]byte(strconv.Itoa(tmpl_topic_alt_vars.Topic.ID)))
w.Write(topic_alt_35)
}
if tmpl_topic_alt_vars.CurrentUser.Perms.PinTopic {
if tmpl_topic_alt_vars.Topic.Sticky {
} else {
w.Write(topic_alt_36)
w.Write([]byte(strconv.Itoa(tmpl_topic_alt_vars.Topic.ID)))
w.Write(topic_alt_37)
} else {
}
}
if tmpl_topic_alt_vars.CurrentUser.Perms.PinTopic {
if tmpl_topic_alt_vars.Topic.Sticky {
w.Write(topic_alt_38)
w.Write([]byte(strconv.Itoa(tmpl_topic_alt_vars.Topic.ID)))
w.Write(topic_alt_39)
}
}
} else {
w.Write(topic_alt_40)
w.Write([]byte(strconv.Itoa(tmpl_topic_alt_vars.Topic.ID)))
w.Write(topic_alt_41)
w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Session))
w.Write(topic_alt_42)
}
if tmpl_topic_alt_vars.Topic.LikeCount > 0 {
}
w.Write(topic_alt_42)
w.Write([]byte(strconv.Itoa(tmpl_topic_alt_vars.Topic.ID)))
w.Write(topic_alt_43)
w.Write([]byte(strconv.Itoa(tmpl_topic_alt_vars.Topic.LikeCount)))
w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Session))
w.Write(topic_alt_44)
}
if tmpl_topic_alt_vars.Topic.LikeCount > 0 {
w.Write(topic_alt_45)
w.Write([]byte(tmpl_topic_alt_vars.Topic.CreatedAt))
w.Write([]byte(strconv.Itoa(tmpl_topic_alt_vars.Topic.LikeCount)))
w.Write(topic_alt_46)
if tmpl_topic_alt_vars.CurrentUser.Perms.ViewIPs {
w.Write(topic_alt_47)
w.Write([]byte(tmpl_topic_alt_vars.Topic.IPAddress))
w.Write(topic_alt_48)
}
w.Write(topic_alt_47)
w.Write([]byte(tmpl_topic_alt_vars.Topic.CreatedAt))
w.Write(topic_alt_48)
if tmpl_topic_alt_vars.CurrentUser.Perms.ViewIPs {
w.Write(topic_alt_49)
w.Write([]byte(tmpl_topic_alt_vars.Topic.IPAddress))
w.Write(topic_alt_50)
}
w.Write(topic_alt_51)
if len(tmpl_topic_alt_vars.ItemList) != 0 {
for _, item := range tmpl_topic_alt_vars.ItemList {
w.Write(topic_alt_50)
if item.ActionType != "" {
w.Write(topic_alt_51)
}
w.Write(topic_alt_52)
w.Write([]byte(item.Avatar))
w.Write(topic_alt_53)
w.Write([]byte(item.UserLink))
w.Write(topic_alt_54)
w.Write([]byte(item.CreatedByName))
w.Write(topic_alt_55)
if item.Tag != "" {
w.Write(topic_alt_56)
w.Write([]byte(item.Tag))
w.Write(topic_alt_57)
} else {
w.Write(topic_alt_58)
w.Write([]byte(strconv.Itoa(item.Level)))
w.Write(topic_alt_59)
}
w.Write(topic_alt_60)
if item.ActionType != "" {
w.Write(topic_alt_53)
}
w.Write(topic_alt_54)
w.Write([]byte(item.Avatar))
w.Write(topic_alt_55)
w.Write([]byte(item.UserLink))
w.Write(topic_alt_56)
w.Write([]byte(item.CreatedByName))
w.Write(topic_alt_57)
if item.Tag != "" {
w.Write(topic_alt_58)
w.Write([]byte(item.Tag))
w.Write(topic_alt_59)
} else {
w.Write(topic_alt_60)
w.Write([]byte(strconv.Itoa(item.Level)))
w.Write(topic_alt_61)
}
w.Write(topic_alt_62)
if item.ActionType != "" {
w.Write(topic_alt_63)
w.Write([]byte(item.ActionIcon))
}
w.Write(topic_alt_64)
w.Write([]byte(item.ActionType))
if item.ActionType != "" {
w.Write(topic_alt_65)
} else {
w.Write([]byte(item.ActionIcon))
w.Write(topic_alt_66)
w.Write([]byte(item.ContentHtml))
w.Write([]byte(item.ActionType))
w.Write(topic_alt_67)
} else {
w.Write(topic_alt_68)
w.Write([]byte(item.ContentHtml))
w.Write(topic_alt_69)
if tmpl_topic_alt_vars.CurrentUser.Loggedin {
if tmpl_topic_alt_vars.CurrentUser.Perms.LikeItem {
w.Write(topic_alt_68)
w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(topic_alt_69)
}
if tmpl_topic_alt_vars.CurrentUser.Perms.EditReply {
w.Write(topic_alt_70)
w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(topic_alt_71)
}
if tmpl_topic_alt_vars.CurrentUser.Perms.DeleteReply {
if tmpl_topic_alt_vars.CurrentUser.Perms.EditReply {
w.Write(topic_alt_72)
w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(topic_alt_73)
}
if tmpl_topic_alt_vars.CurrentUser.Perms.DeleteReply {
w.Write(topic_alt_74)
w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(topic_alt_75)
w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Session))
w.Write(topic_alt_76)
}
if item.LikeCount > 0 {
w.Write(topic_alt_76)
w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(topic_alt_77)
w.Write([]byte(strconv.Itoa(item.LikeCount)))
w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Session))
w.Write(topic_alt_78)
}
if item.LikeCount > 0 {
w.Write(topic_alt_79)
w.Write([]byte(item.CreatedAt))
w.Write([]byte(strconv.Itoa(item.LikeCount)))
w.Write(topic_alt_80)
if tmpl_topic_alt_vars.CurrentUser.Perms.ViewIPs {
}
w.Write(topic_alt_81)
w.Write([]byte(item.IPAddress))
w.Write([]byte(item.CreatedAt))
w.Write(topic_alt_82)
}
if tmpl_topic_alt_vars.CurrentUser.Perms.ViewIPs {
w.Write(topic_alt_83)
}
w.Write([]byte(item.IPAddress))
w.Write(topic_alt_84)
}
}
w.Write(topic_alt_85)
if tmpl_topic_alt_vars.CurrentUser.Perms.CreateReply {
w.Write(topic_alt_86)
w.Write([]byte(strconv.Itoa(tmpl_topic_alt_vars.Topic.ID)))
w.Write(topic_alt_87)
}
w.Write(topic_alt_86)
}
}
w.Write(topic_alt_87)
if tmpl_topic_alt_vars.CurrentUser.Perms.CreateReply {
w.Write(topic_alt_88)
w.Write([]byte(strconv.Itoa(tmpl_topic_alt_vars.Topic.ID)))
w.Write(topic_alt_89)
}
w.Write(topic_alt_90)
w.Write(footer_0)
if len(tmpl_topic_alt_vars.Header.Themes) != 0 {
for _, item := range tmpl_topic_alt_vars.Header.Themes {

View File

@ -13,13 +13,9 @@
<div class="rowblock rowhead topic_block">
<div class="rowitem topic_item{{if .Topic.Sticky}} topic_sticky_head{{else if .Topic.IsClosed}} topic_closed_head{{end}}">
<h1 class='topic_name hide_on_edit'>{{.Topic.Title}}</h1>
{{if .Topic.IsClosed}}<span class='username hide_on_micro topic_status_e topic_status_closed hide_on_edit' title='Status: Closed' style="font-weight:normal;float: right;position:relative;top:-5px;">&#x1F512;&#xFE0E</span>{{end}}
{{if .Topic.IsClosed}}<span class='username hide_on_micro topic_status_e topic_status_closed hide_on_edit' title='Status: Closed'>&#x1F512;&#xFE0E</span>{{end}}
{{if .CurrentUser.Perms.EditTopic}}
<input form='edit_topic_form' class='show_on_edit topic_name_input' name="topic_name" value='{{.Topic.Title}}' type="text" />
{{if .CurrentUser.Perms.CloseTopic}}<select form='edit_topic_form' name="topic_status" class='show_on_edit topic_status_input' style='float: right;'>
<option>open</option>
<option>closed</option>
</select>{{end}}
<button form='edit_topic_form' name="topic-button" class="formbutton show_on_edit submit_edit">Update</button>
{{end}}
</div>
@ -40,6 +36,8 @@
{{if .CurrentUser.Perms.DeleteTopic}}<a href='/topic/delete/submit/{{.Topic.ID}}' class="mod_button" style="font-weight:normal;" title="Delete Topic"><button class="username trash_label"></button></a>{{end}}
{{if .CurrentUser.Perms.CloseTopic}}{{if .Topic.IsClosed}}<a class="mod_button" href='/topic/unlock/submit/{{.Topic.ID}}' style="font-weight:normal;" title="Unlock Topic"><button class="username unlock_label"></button></a>{{else}}<a href='/topic/lock/submit/{{.Topic.ID}}' class="mod_button" style="font-weight:normal;" title="Lock Topic"><button class="username lock_label"></button></a>{{end}}{{end}}
{{if .CurrentUser.Perms.PinTopic}}{{if .Topic.Sticky}}<a class="mod_button" href='/topic/unstick/submit/{{.Topic.ID}}' style="font-weight:normal;" title="Unpin Topic"><button class="username unpin_label"></button></a>{{else}}<a href='/topic/stick/submit/{{.Topic.ID}}' class="mod_button" style="font-weight:normal;" title="Pin Topic"><button class="username pin_label"></button></a>{{end}}{{end}}
{{if .CurrentUser.Perms.ViewIPs}}<a class="mod_button" href='/users/ips/{{.Topic.IPAddress}}' style="font-weight:normal;" title="View IP"><button class="username ip_label"></button></a>{{end}}
<a href="/report/submit/{{.Topic.ID}}?session={{.CurrentUser.Session}}&type=topic" class="mod_button report_item" style="font-weight:normal;" title="Flag Topic"><button class="username flag_label"></button></a>

View File

@ -9,13 +9,10 @@
<form action='/topic/edit/submit/{{.Topic.ID}}' method="post">
<div class="rowitem topic_item{{if .Topic.Sticky}} topic_sticky_head{{else if .Topic.IsClosed}} topic_closed_head{{end}}">
<h1 class='topic_name hide_on_edit'>{{.Topic.Title}}</h1>
{{/** TODO: Inline this CSS **/}}
{{if .Topic.IsClosed}}<span class='username hide_on_micro topic_status_e topic_status_closed hide_on_edit' title='Status: Closed' style="font-weight:normal;float: right;position:relative;top:-5px;">&#x1F512;&#xFE0E</span>{{end}}
{{if .CurrentUser.Perms.EditTopic}}
<input class='show_on_edit topic_name_input' name="topic_name" value='{{.Topic.Title}}' type="text" />
{{if .CurrentUser.Perms.CloseTopic}}<select name="topic_status" class='show_on_edit topic_status_input' style='float: right;'>
<option>open</option>
<option>closed</option>
</select>{{end}}
<button name="topic-button" class="formbutton show_on_edit submit_edit">Update</button>
{{end}}
</div>
@ -37,6 +34,8 @@
{{if .CurrentUser.Perms.LikeItem}}<a href="/topic/like/submit/{{.Topic.ID}}" class="action_button">+1</a>{{end}}
{{if .CurrentUser.Perms.EditTopic}}<a href="/topic/edit/{{.Topic.ID}}" class="action_button open_edit">Edit</a>{{end}}
{{if .CurrentUser.Perms.DeleteTopic}}<a href="/topic/delete/submit/{{.Topic.ID}}" class="action_button delete_item">Delete</a>{{end}}
{{if .CurrentUser.Perms.CloseTopic}}
{{if .Topic.IsClosed}}<a href='/topic/unlock/submit/{{.Topic.ID}}' class="action_button">Unlock</a>{{else}}<a href='/topic/lock/submit/{{.Topic.ID}}' class="action_button">Lock</a>{{end}}{{end}}
{{if .CurrentUser.Perms.PinTopic}}
{{if .Topic.Sticky}}<a href='/topic/unstick/submit/{{.Topic.ID}}' class="action_button">Unpin</a>{{else}}<a href='/topic/stick/submit/{{.Topic.ID}}' class="action_button">Pin</a>{{end}}{{end}}
<a href="/report/submit/{{.Topic.ID}}?session={{.CurrentUser.Session}}&type=topic" class="action_button report_item">Report</a>

View File

@ -23,6 +23,9 @@ var themes = make(map[string]Theme)
var defaultThemeBox atomic.Value
var changeDefaultThemeMutex sync.Mutex
// TODO: Use this when the default theme doesn't exist
var fallbackTheme = "shadow"
//var overridenTemplates map[string]interface{} = make(map[string]interface{})
var overridenTemplates = make(map[string]bool)

View File

@ -244,6 +244,12 @@ a {
.pin_label:before {
content: "Pin";
}
.lock_label:before {
content: "Lock";
}
.unlock_label:before {
content: "Unlock";
}
.unpin_label:before {
content: "Unpin";
}
@ -321,7 +327,7 @@ textarea.large {
min-height: 80px;
}
.formitem button {
.formitem button, .formbutton {
background-color: #444444;
border: 1px solid #555555;
color: #999999;
@ -527,6 +533,28 @@ input, select, textarea {
white-space: nowrap;
}
.topic_item {
display: flex;
}
.topic_name_input {
width: 100%;
margin-right: 10px;
background-color: #444444;
border: 1px solid #555555;
color: #999999;
padding-bottom: 6px;
font-size: 13px;
padding: 5px;
}
.topic_item .submit_edit {
margin-left: auto;
}
.topic_item .topic_status_closed {
margin-left: auto;
position: relative;
top: -5px;
}
/* Profiles */
#profile_left_lane {
width: 220px;

View File

@ -389,6 +389,9 @@ button {
.topic_status_sticky {
display: none;
}
.topic_status_closed {
margin-left: auto;
}
.topic_sticky {
background-color: rgb(255,255,234);
}
@ -407,7 +410,26 @@ button {
color: #505050; /* 80,80,80 */
border-radius: 2px;
}
.topic_status:empty { display: none; }
.topic_status:empty {
display: none;
}
.topic_item {
display: flex;
}
.topic_item .topic_name_input {
padding: 5px;
width: 100%;
margin-right: 9px;
}
.topic_item .submit_edit {
margin-right: 0;
}
.topic_content_input {
width: 100%;
min-height: 143px;
}
.rowhead, .colstack_head {
border-bottom: none;
@ -739,6 +761,23 @@ button.username {
font-size: 12px;
}
.pageset {
display: flex;
margin-bottom: 10px;
margin-top: -5px;
}
.pageitem {
border: 1px solid hsl(0,0%,80%);
background-color: white;
padding: 5px;
margin-right: 5px;
padding-bottom: 4px;
}
.pageitem a {
color: black;
text-decoration: none;
}
/* Firefox specific CSS */
@supports (-moz-appearance: none) {
.footer, .rowmenu, #profile_right_lane .topic_reply_form, .content_container {

View File

@ -1,6 +1,6 @@
/* The Media Queries */
@media(min-width: 881px) {
@media(min-width: 951px) {
.shrink_main {
float: left;
width: calc(75% - 12px);
@ -12,7 +12,7 @@
}
}
@media (max-width: 880px) {
@media (max-width: 950px) {
li {
height: 29px;
font-size: 15px;

View File

@ -6,6 +6,7 @@
"FullImage": "tempra-cursive.png",
"ForkOf": "tempra-simple",
"MobileFriendly": true,
"HideFromThemes": true,
"URL": "github.com/Azareal/Gosora",
"Sidebars":"right"
}

View File

@ -390,9 +390,33 @@ button {
}
}
.username, .panel_tag {
text-transform: none;
margin-left: 0px;
padding-left: 4px;
padding-right: 4px;
padding-top: 2px;
padding-bottom: 2px;
color: #505050; /* 80,80,80 */
background-color: #FFFFFF;
border-style: solid;
border-color: #ccc;
border-width: 1px;
font-size: 15px;
}
.topic_item {
display: flex;
}
.topic_status_sticky {
display: none;
}
.topic_status_closed {
margin-left: auto;
margin-top: -5px;
font-size: 0.90em;
margin-bottom: -2px;
}
.topic_sticky {
background-color: rgb(255,255,234);
}
@ -421,20 +445,6 @@ button {
display: none;
}
.username, .panel_tag {
text-transform: none;
margin-left: 0px;
padding-left: 4px;
padding-right: 4px;
padding-top: 2px;
padding-bottom: 2px;
color: #505050; /* 80,80,80 */
background-color: #FFFFFF;
border-style: solid;
border-color: #ccc;
border-width: 1px;
font-size: 15px;
}
button.username {
position: relative;
top: -0.25px;
@ -501,15 +511,34 @@ button.username {
font-size: 17px;
}
.edit_label:before { content: "🖊️"; }
.trash_label:before { content: "🗑️"; }
.pin_label:before { content: "📌"; }
.unpin_label:before { content: "📌"; }
.unpin_label { background-color: #D6FFD6; }
.ip_label:before { content: "🔍"; }
.flag_label:before { content: "🚩"; }
.level_label:before { content: "👑"; }
.level_label { color: #505050; opacity:0.85; }
.edit_label:before {
content: "🖊️";
}
.trash_label:before {
content: "🗑️";
}
.pin_label:before, .unpin_label:before {
content: "📌";
}
.unpin_label, .unlock_label {
background-color: #D6FFD6;
}
.lock_label:before, .unlock_label:before {
content: "🔒";
}
.ip_label:before {
content: "🔍";
}
.flag_label:before {
content: "🚩";
}
.level_label:before {
content: "👑";
}
.level_label {
color: #505050;
opacity: 0.85;
}
.controls {
margin-top: 23px;
@ -652,9 +681,27 @@ button.username {
#profile_comments {
overflow: hidden;
border-top: none;
margin-bottom: 0;
}
.simple .user_tag {
font-size: 14px;
}
.pageset {
display: flex;
margin-bottom: 10px;
margin-top: -5px;
}
.pageitem {
background-color: white;
padding: 5px;
margin-right: 5px;
padding-bottom: 4px;
border: 1px solid #ccc;
}
.pageitem a {
color: black;
text-decoration: none;
}
{{template "media.partial.css" }}

View File

@ -90,6 +90,68 @@ type TopicsRow struct {
ForumLink string
}
func (topic *Topic) Lock() (err error) {
_, err = lockTopicStmt.Exec(topic.ID)
tcache, ok := topics.(TopicCache)
if ok {
tcache.CacheRemove(topic.ID)
}
return err
}
func (topic *Topic) Unlock() (err error) {
_, err = unlockTopicStmt.Exec(topic.ID)
tcache, ok := topics.(TopicCache)
if ok {
tcache.CacheRemove(topic.ID)
}
return err
}
// TODO: We might want more consistent terminology rather than using stick in some places and pin in others. If you don't understand the difference, there is none, they are one and the same.
// ? - We do a CacheDelete() here instead of mutating the pointer to avoid creating a race condition
func (topic *Topic) Stick() (err error) {
_, err = stickTopicStmt.Exec(topic.ID)
tcache, ok := topics.(TopicCache)
if ok {
tcache.CacheRemove(topic.ID)
}
return err
}
func (topic *Topic) Unstick() (err error) {
_, err = unstickTopicStmt.Exec(topic.ID)
tcache, ok := topics.(TopicCache)
if ok {
tcache.CacheRemove(topic.ID)
}
return err
}
// TODO: Implement this
func (topic *Topic) AddLike(uid int) error {
return nil
}
// TODO: Implement this
func (topic *Topic) RemoveLike(uid int) error {
return nil
}
func (topic *Topic) CreateActionReply(action string, ipaddress string, user User) (err error) {
_, err = createActionReplyStmt.Exec(topic.ID, action, ipaddress, user.ID)
if err != nil {
return err
}
_, err = addRepliesToTopicStmt.Exec(1, user.ID, topic.ID)
tcache, ok := topics.(TopicCache)
if ok {
tcache.CacheRemove(topic.ID)
}
// ? - Update the last topic cache for the parent forum?
return err
}
// TODO: Refactor the caller to take a Topic and a User rather than a combined TopicUser
func getTopicuser(tid int) (TopicUser, error) {
tcache, tok := topics.(TopicCache)

View File

@ -22,7 +22,6 @@ import (
var topics TopicStore
type TopicStore interface {
Reload(id int) error // ? - Should we move this to TopicCache? Might require us to do a lot more casting in Gosora though...
Get(id int) (*Topic, error)
BypassGet(id int) (*Topic, error)
Delete(id int) error
@ -43,6 +42,7 @@ type TopicCache interface {
CacheRemove(id int) error
CacheRemoveUnsafe(id int) error
Flush()
Reload(id int) error
GetLength() int
SetCapacity(capacity int)
GetCapacity() int
@ -310,11 +310,6 @@ func (sts *SQLTopicStore) BypassGet(id int) (*Topic, error) {
return topic, err
}
// Reload uses a similar query to Exists(), as we don't have any entries to reload, and the secondary benefit of calling Reload() is seeing if the item you're trying to reload exists
func (sts *SQLTopicStore) Reload(id int) error {
return sts.exists.QueryRow(id).Scan(&id)
}
func (sts *SQLTopicStore) Exists(id int) bool {
return sts.exists.QueryRow(id).Scan(&id) == nil
}

240
user.go
View File

@ -64,10 +64,11 @@ func (user *User) Ban(duration time.Duration, issuedBy int) error {
func (user *User) Unban() error {
err := user.RevertGroupUpdate()
if err != nil {
return err
ucache, ok := users.(UserCache)
if ok {
ucache.CacheRemove(user.ID)
}
return users.Reload(user.ID)
return err
}
// TODO: Use a transaction to avoid race conditions
@ -84,10 +85,11 @@ func (user *User) ScheduleGroupUpdate(gid int, issuedBy int, duration time.Durat
return err
}
_, err = setTempGroupStmt.Exec(gid, user.ID)
if err != nil {
return err
ucache, ok := users.(UserCache)
if ok {
ucache.CacheRemove(user.ID)
}
return users.Reload(user.ID)
return err
}
// TODO: Use a transaction to avoid race conditions
@ -97,10 +99,129 @@ func (user *User) RevertGroupUpdate() error {
return err
}
_, err = setTempGroupStmt.Exec(0, user.ID)
ucache, ok := users.(UserCache)
if ok {
ucache.CacheRemove(user.ID)
}
return err
}
// TODO: Use a transaction here
// TODO: Add a Deactivate method?
func (user *User) Activate() (err error) {
_, err = activateUserStmt.Exec(user.ID)
if err != nil {
return err
}
return users.Reload(user.ID)
_, err = changeGroupStmt.Exec(config.DefaultGroup, user.ID)
ucache, ok := users.(UserCache)
if ok {
ucache.CacheRemove(user.ID)
}
return err
}
func (user *User) increasePostStats(wcount int, topic bool) error {
var mod int
baseScore := 1
if topic {
_, err := incrementUserTopicsStmt.Exec(1, user.ID)
if err != nil {
return err
}
baseScore = 2
}
settings := settingBox.Load().(SettingBox)
if wcount >= settings["megapost_min_words"].(int) {
_, err := incrementUserMegapostsStmt.Exec(1, 1, 1, user.ID)
if err != nil {
return err
}
mod = 4
} else if wcount >= settings["bigpost_min_words"].(int) {
_, err := incrementUserBigpostsStmt.Exec(1, 1, user.ID)
if err != nil {
return err
}
mod = 1
} else {
_, err := incrementUserPostsStmt.Exec(1, user.ID)
if err != nil {
return err
}
}
_, err := incrementUserScoreStmt.Exec(baseScore+mod, user.ID)
if err != nil {
return err
}
//log.Print(user.Score + base_score + mod)
//log.Print(getLevel(user.Score + base_score + mod))
// TODO: Use a transaction to prevent level desyncs?
_, err = updateUserLevelStmt.Exec(getLevel(user.Score+baseScore+mod), user.ID)
return err
}
func (user *User) decreasePostStats(wcount int, topic bool) error {
var mod int
baseScore := -1
if topic {
_, err := incrementUserTopicsStmt.Exec(-1, user.ID)
if err != nil {
return err
}
baseScore = -2
}
settings := settingBox.Load().(SettingBox)
if wcount >= settings["megapost_min_words"].(int) {
_, err := incrementUserMegapostsStmt.Exec(-1, -1, -1, user.ID)
if err != nil {
return err
}
mod = 4
} else if wcount >= settings["bigpost_min_words"].(int) {
_, err := incrementUserBigpostsStmt.Exec(-1, -1, user.ID)
if err != nil {
return err
}
mod = 1
} else {
_, err := incrementUserPostsStmt.Exec(-1, user.ID)
if err != nil {
return err
}
}
_, err := incrementUserScoreStmt.Exec(baseScore-mod, user.ID)
if err != nil {
return err
}
// TODO: Use a transaction to prevent level desyncs?
_, err = updateUserLevelStmt.Exec(getLevel(user.Score-baseScore-mod), user.ID)
return err
}
func (user *User) initPerms() {
if user.TempGroup != 0 {
user.Group = user.TempGroup
}
group := gstore.DirtyGet(user.Group)
if user.IsSuperAdmin {
user.Perms = AllPerms
user.PluginPerms = AllPluginPerms
} else {
user.Perms = group.Perms
user.PluginPerms = group.PluginPerms
}
user.IsAdmin = user.IsSuperAdmin || group.IsAdmin
user.IsSuperMod = user.IsAdmin || group.IsMod
user.IsMod = user.IsSuperMod
user.IsBanned = group.IsBanned
if user.IsBanned && user.IsSuperMod {
user.IsBanned = false
}
}
func BcryptCheckPassword(realPassword string, password string, salt string) (err error) {
@ -167,111 +288,6 @@ func wordsToScore(wcount int, topic bool) (score int) {
return score
}
// TODO: Move this to where the other User methods are
func (user *User) increasePostStats(wcount int, topic bool) error {
var mod int
baseScore := 1
if topic {
_, err := incrementUserTopicsStmt.Exec(1, user.ID)
if err != nil {
return err
}
baseScore = 2
}
settings := settingBox.Load().(SettingBox)
if wcount >= settings["megapost_min_words"].(int) {
_, err := incrementUserMegapostsStmt.Exec(1, 1, 1, user.ID)
if err != nil {
return err
}
mod = 4
} else if wcount >= settings["bigpost_min_words"].(int) {
_, err := incrementUserBigpostsStmt.Exec(1, 1, user.ID)
if err != nil {
return err
}
mod = 1
} else {
_, err := incrementUserPostsStmt.Exec(1, user.ID)
if err != nil {
return err
}
}
_, err := incrementUserScoreStmt.Exec(baseScore+mod, user.ID)
if err != nil {
return err
}
//log.Print(user.Score + base_score + mod)
//log.Print(getLevel(user.Score + base_score + mod))
// TODO: Use a transaction to prevent level desyncs?
_, err = updateUserLevelStmt.Exec(getLevel(user.Score+baseScore+mod), user.ID)
return err
}
// TODO: Move this to where the other User methods are
func (user *User) decreasePostStats(wcount int, topic bool) error {
var mod int
baseScore := -1
if topic {
_, err := incrementUserTopicsStmt.Exec(-1, user.ID)
if err != nil {
return err
}
baseScore = -2
}
settings := settingBox.Load().(SettingBox)
if wcount >= settings["megapost_min_words"].(int) {
_, err := incrementUserMegapostsStmt.Exec(-1, -1, -1, user.ID)
if err != nil {
return err
}
mod = 4
} else if wcount >= settings["bigpost_min_words"].(int) {
_, err := incrementUserBigpostsStmt.Exec(-1, -1, user.ID)
if err != nil {
return err
}
mod = 1
} else {
_, err := incrementUserPostsStmt.Exec(-1, user.ID)
if err != nil {
return err
}
}
_, err := incrementUserScoreStmt.Exec(baseScore-mod, user.ID)
if err != nil {
return err
}
// TODO: Use a transaction to prevent level desyncs?
_, err = updateUserLevelStmt.Exec(getLevel(user.Score-baseScore-mod), user.ID)
return err
}
func initUserPerms(user *User) {
if user.TempGroup != 0 {
user.Group = user.TempGroup
}
group := gstore.DirtyGet(user.Group)
if user.IsSuperAdmin {
user.Perms = AllPerms
user.PluginPerms = AllPluginPerms
} else {
user.Perms = group.Perms
user.PluginPerms = group.PluginPerms
}
user.IsAdmin = user.IsSuperAdmin || group.IsAdmin
user.IsSuperMod = user.IsAdmin || group.IsMod
user.IsMod = user.IsSuperMod
user.IsBanned = group.IsBanned
if user.IsBanned && user.IsSuperMod {
user.IsBanned = false
}
}
func buildProfileURL(slug string, uid int) string {
if slug == "" {
return "/user/" + strconv.Itoa(uid)

View File

@ -19,7 +19,6 @@ var users UserStore
var errAccountExists = errors.New("this username is already in use")
type UserStore interface {
Reload(id int) error // ? - Should we move this to TopicCache? Might require us to do a lot more casting in Gosora though...
Get(id int) (*User, error)
Exists(id int) bool
//BulkGet(ids []int) ([]*User, error)
@ -38,6 +37,7 @@ type UserCache interface {
CacheRemove(id int) error
CacheRemoveUnsafe(id int) error
Flush()
Reload(id int) error
GetLength() int
SetCapacity(capacity int)
GetCapacity() int
@ -133,7 +133,7 @@ func (mus *MemoryUserStore) Get(id int) (*User, error) {
}
user.Link = buildProfileURL(nameToSlug(user.Name), id)
user.Tag = gstore.DirtyGet(user.Group).Tag
initUserPerms(user)
user.initPerms()
if err == nil {
mus.CacheSet(user)
}
@ -211,7 +211,7 @@ func (mus *MemoryUserStore) BulkGetMap(ids []int) (list map[int]*User, err error
}
user.Link = buildProfileURL(nameToSlug(user.Name), user.ID)
user.Tag = gstore.DirtyGet(user.Group).Tag
initUserPerms(user)
user.initPerms()
// Add it to the cache...
_ = mus.CacheSet(user)
@ -261,7 +261,7 @@ func (mus *MemoryUserStore) BypassGet(id int) (*User, error) {
}
user.Link = buildProfileURL(nameToSlug(user.Name), id)
user.Tag = gstore.DirtyGet(user.Group).Tag
initUserPerms(user)
user.initPerms()
return user, err
}
@ -282,7 +282,7 @@ func (mus *MemoryUserStore) Reload(id int) error {
}
user.Link = buildProfileURL(nameToSlug(user.Name), id)
user.Tag = gstore.DirtyGet(user.Group).Tag
initUserPerms(user)
user.initPerms()
_ = mus.CacheSet(user)
return nil
}
@ -443,7 +443,7 @@ func NewSQLUserStore() *SQLUserStore {
}
func (mus *SQLUserStore) Get(id int) (*User, error) {
user := User{ID: id, Loggedin: true}
user := &User{ID: id, Loggedin: true}
err := mus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.IsSuperAdmin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.LastIP, &user.TempGroup)
if user.Avatar != "" {
@ -455,8 +455,8 @@ func (mus *SQLUserStore) Get(id int) (*User, error) {
}
user.Link = buildProfileURL(nameToSlug(user.Name), id)
user.Tag = gstore.DirtyGet(user.Group).Tag
initUserPerms(&user)
return &user, err
user.initPerms()
return user, err
}
// TODO: Optimise the query to avoid preparing it on the spot? Maybe, use knowledge of the most common IN() parameter counts?
@ -497,7 +497,7 @@ func (mus *SQLUserStore) BulkGetMap(ids []int) (list map[int]*User, err error) {
}
user.Link = buildProfileURL(nameToSlug(user.Name), user.ID)
user.Tag = gstore.DirtyGet(user.Group).Tag
initUserPerms(user)
user.initPerms()
// Add it to the list to be returned
list[user.ID] = user
@ -507,7 +507,7 @@ func (mus *SQLUserStore) BulkGetMap(ids []int) (list map[int]*User, err error) {
}
func (mus *SQLUserStore) BypassGet(id int) (*User, error) {
user := User{ID: id, Loggedin: true}
user := &User{ID: id, Loggedin: true}
err := mus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.IsSuperAdmin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.LastIP, &user.TempGroup)
if user.Avatar != "" {
@ -519,12 +519,8 @@ func (mus *SQLUserStore) BypassGet(id int) (*User, error) {
}
user.Link = buildProfileURL(nameToSlug(user.Name), id)
user.Tag = gstore.DirtyGet(user.Group).Tag
initUserPerms(&user)
return &user, err
}
func (mus *SQLUserStore) Reload(id int) error {
return mus.exists.QueryRow(id).Scan(&id)
user.initPerms()
return user, err
}
func (mus *SQLUserStore) Exists(id int) bool {