diff --git a/.gitignore b/.gitignore index f2b3649c..705c6af2 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ brun.bat uploads/avatar_* uploads/socialgroup_* bin/* +out/* *.exe *.exe~ *.prof diff --git a/auth.go b/auth.go index 5896fb83..45b18224 100644 --- a/auth.go +++ b/auth.go @@ -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 } diff --git a/errors.go b/errors.go index 4ca0aa29..dc501904 100644 --- a/errors.go +++ b/errors.go @@ -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 + `"}`)) +} diff --git a/extend.go b/extend.go index a015b2da..eaf66f80 100644 --- a/extend.go +++ b/extend.go @@ -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) diff --git a/gen_mysql.go b/gen_mysql.go index a78691a6..7dfd8c2c 100644 --- a/gen_mysql.go +++ b/gen_mysql.go @@ -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 { diff --git a/gen_pgsql.go b/gen_pgsql.go index bae46de9..cfa68b2d 100644 --- a/gen_pgsql.go +++ b/gen_pgsql.go @@ -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 { diff --git a/general_test.go b/general_test.go index 382b1995..b34053c6 100644 --- a/general_test.go +++ b/general_test.go @@ -47,10 +47,6 @@ func gloinit() error { initTemplates() dbProd.SetMaxOpenConns(64) - err = initErrors() - if err != nil { - return err - } err = initPhrases() if err != nil { diff --git a/main.go b/main.go index 5f49027d..44f9e11c 100644 --- a/main.go +++ b/main.go @@ -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") diff --git a/member_routes.go b/member_routes.go index 29f4c769..8d3b40f1 100644 --- a/member_routes.go +++ b/member_routes.go @@ -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") diff --git a/mod_routes.go b/mod_routes.go index 57aa9807..f916af3c 100644 --- a/mod_routes.go +++ b/mod_routes.go @@ -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) } diff --git a/images/cosmo-320px.PNG b/old-images/cosmo-320px.PNG similarity index 100% rename from images/cosmo-320px.PNG rename to old-images/cosmo-320px.PNG diff --git a/images/cosmo-alerts.png b/old-images/cosmo-alerts.png similarity index 100% rename from images/cosmo-alerts.png rename to old-images/cosmo-alerts.png diff --git a/images/cosmo-conflux.png b/old-images/cosmo-conflux.png similarity index 100% rename from images/cosmo-conflux.png rename to old-images/cosmo-conflux.png diff --git a/images/cosmo-profiles.PNG b/old-images/cosmo-profiles.PNG similarity index 100% rename from images/cosmo-profiles.PNG rename to old-images/cosmo-profiles.PNG diff --git a/images/cosmo.png b/old-images/cosmo.png similarity index 100% rename from images/cosmo.png rename to old-images/cosmo.png diff --git a/pages.go b/pages.go index cd57ba56..723da854 100644 --- a/pages.go +++ b/pages.go @@ -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 diff --git a/panel_routes.go b/panel_routes.go index aa3fbe10..14f10a53 100644 --- a/panel_routes.go +++ b/panel_routes.go @@ -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) } diff --git a/public/font-awesome-4.7.0/css/font-awesome.css b/public/font-awesome-4.7.0/css/font-awesome.css new file mode 100644 index 00000000..ee906a81 --- /dev/null +++ b/public/font-awesome-4.7.0/css/font-awesome.css @@ -0,0 +1,2337 @@ +/*! + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */ +/* FONT PATH + * -------------------------- */ +@font-face { + font-family: 'FontAwesome'; + src: url('../fonts/fontawesome-webfont.eot?v=4.7.0'); + src: url('../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'), url('../fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'), url('../fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'), url('../fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'), url('../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg'); + font-weight: normal; + font-style: normal; +} +.fa { + display: inline-block; + font: normal normal normal 14px/1 FontAwesome; + font-size: inherit; + text-rendering: auto; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} +/* makes the font 33% larger relative to the icon container */ +.fa-lg { + font-size: 1.33333333em; + line-height: 0.75em; + vertical-align: -15%; +} +.fa-2x { + font-size: 2em; +} +.fa-3x { + font-size: 3em; +} +.fa-4x { + font-size: 4em; +} +.fa-5x { + font-size: 5em; +} +.fa-fw { + width: 1.28571429em; + text-align: center; +} +.fa-ul { + padding-left: 0; + margin-left: 2.14285714em; + list-style-type: none; +} +.fa-ul > li { + position: relative; +} +.fa-li { + position: absolute; + left: -2.14285714em; + width: 2.14285714em; + top: 0.14285714em; + text-align: center; +} +.fa-li.fa-lg { + left: -1.85714286em; +} +.fa-border { + padding: .2em .25em .15em; + border: solid 0.08em #eeeeee; + border-radius: .1em; +} +.fa-pull-left { + float: left; +} +.fa-pull-right { + float: right; +} +.fa.fa-pull-left { + margin-right: .3em; +} +.fa.fa-pull-right { + margin-left: .3em; +} +/* Deprecated as of 4.4.0 */ +.pull-right { + float: right; +} +.pull-left { + float: left; +} +.fa.pull-left { + margin-right: .3em; +} +.fa.pull-right { + margin-left: .3em; +} +.fa-spin { + -webkit-animation: fa-spin 2s infinite linear; + animation: fa-spin 2s infinite linear; +} +.fa-pulse { + -webkit-animation: fa-spin 1s infinite steps(8); + animation: fa-spin 1s infinite steps(8); +} +@-webkit-keyframes fa-spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(359deg); + transform: rotate(359deg); + } +} +@keyframes fa-spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(359deg); + transform: rotate(359deg); + } +} +.fa-rotate-90 { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=1)"; + -webkit-transform: rotate(90deg); + -ms-transform: rotate(90deg); + transform: rotate(90deg); +} +.fa-rotate-180 { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2)"; + -webkit-transform: rotate(180deg); + -ms-transform: rotate(180deg); + transform: rotate(180deg); +} +.fa-rotate-270 { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=3)"; + -webkit-transform: rotate(270deg); + -ms-transform: rotate(270deg); + transform: rotate(270deg); +} +.fa-flip-horizontal { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)"; + -webkit-transform: scale(-1, 1); + -ms-transform: scale(-1, 1); + transform: scale(-1, 1); +} +.fa-flip-vertical { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"; + -webkit-transform: scale(1, -1); + -ms-transform: scale(1, -1); + transform: scale(1, -1); +} +:root .fa-rotate-90, +:root .fa-rotate-180, +:root .fa-rotate-270, +:root .fa-flip-horizontal, +:root .fa-flip-vertical { + filter: none; +} +.fa-stack { + position: relative; + display: inline-block; + width: 2em; + height: 2em; + line-height: 2em; + vertical-align: middle; +} +.fa-stack-1x, +.fa-stack-2x { + position: absolute; + left: 0; + width: 100%; + text-align: center; +} +.fa-stack-1x { + line-height: inherit; +} +.fa-stack-2x { + font-size: 2em; +} +.fa-inverse { + color: #ffffff; +} +/* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen + readers do not read off random characters that represent icons */ +.fa-glass:before { + content: "\f000"; +} +.fa-music:before { + content: "\f001"; +} +.fa-search:before { + content: "\f002"; +} +.fa-envelope-o:before { + content: "\f003"; +} +.fa-heart:before { + content: "\f004"; +} +.fa-star:before { + content: "\f005"; +} +.fa-star-o:before { + content: "\f006"; +} +.fa-user:before { + content: "\f007"; +} +.fa-film:before { + content: "\f008"; +} +.fa-th-large:before { + content: "\f009"; +} +.fa-th:before { + content: "\f00a"; +} +.fa-th-list:before { + content: "\f00b"; +} +.fa-check:before { + content: "\f00c"; +} +.fa-remove:before, +.fa-close:before, +.fa-times:before { + content: "\f00d"; +} +.fa-search-plus:before { + content: "\f00e"; +} +.fa-search-minus:before { + content: "\f010"; +} +.fa-power-off:before { + content: "\f011"; +} +.fa-signal:before { + content: "\f012"; +} +.fa-gear:before, +.fa-cog:before { + content: "\f013"; +} +.fa-trash-o:before { + content: "\f014"; +} +.fa-home:before { + content: "\f015"; +} +.fa-file-o:before { + content: "\f016"; +} +.fa-clock-o:before { + content: "\f017"; +} +.fa-road:before { + content: "\f018"; +} +.fa-download:before { + content: "\f019"; +} +.fa-arrow-circle-o-down:before { + content: "\f01a"; +} +.fa-arrow-circle-o-up:before { + content: "\f01b"; +} +.fa-inbox:before { + content: "\f01c"; +} +.fa-play-circle-o:before { + content: "\f01d"; +} +.fa-rotate-right:before, +.fa-repeat:before { + content: "\f01e"; +} +.fa-refresh:before { + content: "\f021"; +} +.fa-list-alt:before { + content: "\f022"; +} +.fa-lock:before { + content: "\f023"; +} +.fa-flag:before { + content: "\f024"; +} +.fa-headphones:before { + content: "\f025"; +} +.fa-volume-off:before { + content: "\f026"; +} +.fa-volume-down:before { + content: "\f027"; +} +.fa-volume-up:before { + content: "\f028"; +} +.fa-qrcode:before { + content: "\f029"; +} +.fa-barcode:before { + content: "\f02a"; +} +.fa-tag:before { + content: "\f02b"; +} +.fa-tags:before { + content: "\f02c"; +} +.fa-book:before { + content: "\f02d"; +} +.fa-bookmark:before { + content: "\f02e"; +} +.fa-print:before { + content: "\f02f"; +} +.fa-camera:before { + content: "\f030"; +} +.fa-font:before { + content: "\f031"; +} +.fa-bold:before { + content: "\f032"; +} +.fa-italic:before { + content: "\f033"; +} +.fa-text-height:before { + content: "\f034"; +} +.fa-text-width:before { + content: "\f035"; +} +.fa-align-left:before { + content: "\f036"; +} +.fa-align-center:before { + content: "\f037"; +} +.fa-align-right:before { + content: "\f038"; +} +.fa-align-justify:before { + content: "\f039"; +} +.fa-list:before { + content: "\f03a"; +} +.fa-dedent:before, +.fa-outdent:before { + content: "\f03b"; +} +.fa-indent:before { + content: "\f03c"; +} +.fa-video-camera:before { + content: "\f03d"; +} +.fa-photo:before, +.fa-image:before, +.fa-picture-o:before { + content: "\f03e"; +} +.fa-pencil:before { + content: "\f040"; +} +.fa-map-marker:before { + content: "\f041"; +} +.fa-adjust:before { + content: "\f042"; +} +.fa-tint:before { + content: "\f043"; +} +.fa-edit:before, +.fa-pencil-square-o:before { + content: "\f044"; +} +.fa-share-square-o:before { + content: "\f045"; +} +.fa-check-square-o:before { + content: "\f046"; +} +.fa-arrows:before { + content: "\f047"; +} +.fa-step-backward:before { + content: "\f048"; +} +.fa-fast-backward:before { + content: "\f049"; +} +.fa-backward:before { + content: "\f04a"; +} +.fa-play:before { + content: "\f04b"; +} +.fa-pause:before { + content: "\f04c"; +} +.fa-stop:before { + content: "\f04d"; +} +.fa-forward:before { + content: "\f04e"; +} +.fa-fast-forward:before { + content: "\f050"; +} +.fa-step-forward:before { + content: "\f051"; +} +.fa-eject:before { + content: "\f052"; +} +.fa-chevron-left:before { + content: "\f053"; +} +.fa-chevron-right:before { + content: "\f054"; +} +.fa-plus-circle:before { + content: "\f055"; +} +.fa-minus-circle:before { + content: "\f056"; +} +.fa-times-circle:before { + content: "\f057"; +} +.fa-check-circle:before { + content: "\f058"; +} +.fa-question-circle:before { + content: "\f059"; +} +.fa-info-circle:before { + content: "\f05a"; +} +.fa-crosshairs:before { + content: "\f05b"; +} +.fa-times-circle-o:before { + content: "\f05c"; +} +.fa-check-circle-o:before { + content: "\f05d"; +} +.fa-ban:before { + content: "\f05e"; +} +.fa-arrow-left:before { + content: "\f060"; +} +.fa-arrow-right:before { + content: "\f061"; +} +.fa-arrow-up:before { + content: "\f062"; +} +.fa-arrow-down:before { + content: "\f063"; +} +.fa-mail-forward:before, +.fa-share:before { + content: "\f064"; +} +.fa-expand:before { + content: "\f065"; +} +.fa-compress:before { + content: "\f066"; +} +.fa-plus:before { + content: "\f067"; +} +.fa-minus:before { + content: "\f068"; +} +.fa-asterisk:before { + content: "\f069"; +} +.fa-exclamation-circle:before { + content: "\f06a"; +} +.fa-gift:before { + content: "\f06b"; +} +.fa-leaf:before { + content: "\f06c"; +} +.fa-fire:before { + content: "\f06d"; +} +.fa-eye:before { + content: "\f06e"; +} +.fa-eye-slash:before { + content: "\f070"; +} +.fa-warning:before, +.fa-exclamation-triangle:before { + content: "\f071"; +} +.fa-plane:before { + content: "\f072"; +} +.fa-calendar:before { + content: "\f073"; +} +.fa-random:before { + content: "\f074"; +} +.fa-comment:before { + content: "\f075"; +} +.fa-magnet:before { + content: "\f076"; +} +.fa-chevron-up:before { + content: "\f077"; +} +.fa-chevron-down:before { + content: "\f078"; +} +.fa-retweet:before { + content: "\f079"; +} +.fa-shopping-cart:before { + content: "\f07a"; +} +.fa-folder:before { + content: "\f07b"; +} +.fa-folder-open:before { + content: "\f07c"; +} +.fa-arrows-v:before { + content: "\f07d"; +} +.fa-arrows-h:before { + content: "\f07e"; +} +.fa-bar-chart-o:before, +.fa-bar-chart:before { + content: "\f080"; +} +.fa-twitter-square:before { + content: "\f081"; +} +.fa-facebook-square:before { + content: "\f082"; +} +.fa-camera-retro:before { + content: "\f083"; +} +.fa-key:before { + content: "\f084"; +} +.fa-gears:before, +.fa-cogs:before { + content: "\f085"; +} +.fa-comments:before { + content: "\f086"; +} +.fa-thumbs-o-up:before { + content: "\f087"; +} +.fa-thumbs-o-down:before { + content: "\f088"; +} +.fa-star-half:before { + content: "\f089"; +} +.fa-heart-o:before { + content: "\f08a"; +} +.fa-sign-out:before { + content: "\f08b"; +} +.fa-linkedin-square:before { + content: "\f08c"; +} +.fa-thumb-tack:before { + content: "\f08d"; +} +.fa-external-link:before { + content: "\f08e"; +} +.fa-sign-in:before { + content: "\f090"; +} +.fa-trophy:before { + content: "\f091"; +} +.fa-github-square:before { + content: "\f092"; +} +.fa-upload:before { + content: "\f093"; +} +.fa-lemon-o:before { + content: "\f094"; +} +.fa-phone:before { + content: "\f095"; +} +.fa-square-o:before { + content: "\f096"; +} +.fa-bookmark-o:before { + content: "\f097"; +} +.fa-phone-square:before { + content: "\f098"; +} +.fa-twitter:before { + content: "\f099"; +} +.fa-facebook-f:before, +.fa-facebook:before { + content: "\f09a"; +} +.fa-github:before { + content: "\f09b"; +} +.fa-unlock:before { + content: "\f09c"; +} +.fa-credit-card:before { + content: "\f09d"; +} +.fa-feed:before, +.fa-rss:before { + content: "\f09e"; +} +.fa-hdd-o:before { + content: "\f0a0"; +} +.fa-bullhorn:before { + content: "\f0a1"; +} +.fa-bell:before { + content: "\f0f3"; +} +.fa-certificate:before { + content: "\f0a3"; +} +.fa-hand-o-right:before { + content: "\f0a4"; +} +.fa-hand-o-left:before { + content: "\f0a5"; +} +.fa-hand-o-up:before { + content: "\f0a6"; +} +.fa-hand-o-down:before { + content: "\f0a7"; +} +.fa-arrow-circle-left:before { + content: "\f0a8"; +} +.fa-arrow-circle-right:before { + content: "\f0a9"; +} +.fa-arrow-circle-up:before { + content: "\f0aa"; +} +.fa-arrow-circle-down:before { + content: "\f0ab"; +} +.fa-globe:before { + content: "\f0ac"; +} +.fa-wrench:before { + content: "\f0ad"; +} +.fa-tasks:before { + content: "\f0ae"; +} +.fa-filter:before { + content: "\f0b0"; +} +.fa-briefcase:before { + content: "\f0b1"; +} +.fa-arrows-alt:before { + content: "\f0b2"; +} +.fa-group:before, +.fa-users:before { + content: "\f0c0"; +} +.fa-chain:before, +.fa-link:before { + content: "\f0c1"; +} +.fa-cloud:before { + content: "\f0c2"; +} +.fa-flask:before { + content: "\f0c3"; +} +.fa-cut:before, +.fa-scissors:before { + content: "\f0c4"; +} +.fa-copy:before, +.fa-files-o:before { + content: "\f0c5"; +} +.fa-paperclip:before { + content: "\f0c6"; +} +.fa-save:before, +.fa-floppy-o:before { + content: "\f0c7"; +} +.fa-square:before { + content: "\f0c8"; +} +.fa-navicon:before, +.fa-reorder:before, +.fa-bars:before { + content: "\f0c9"; +} +.fa-list-ul:before { + content: "\f0ca"; +} +.fa-list-ol:before { + content: "\f0cb"; +} +.fa-strikethrough:before { + content: "\f0cc"; +} +.fa-underline:before { + content: "\f0cd"; +} +.fa-table:before { + content: "\f0ce"; +} +.fa-magic:before { + content: "\f0d0"; +} +.fa-truck:before { + content: "\f0d1"; +} +.fa-pinterest:before { + content: "\f0d2"; +} +.fa-pinterest-square:before { + content: "\f0d3"; +} +.fa-google-plus-square:before { + content: "\f0d4"; +} +.fa-google-plus:before { + content: "\f0d5"; +} +.fa-money:before { + content: "\f0d6"; +} +.fa-caret-down:before { + content: "\f0d7"; +} +.fa-caret-up:before { + content: "\f0d8"; +} +.fa-caret-left:before { + content: "\f0d9"; +} +.fa-caret-right:before { + content: "\f0da"; +} +.fa-columns:before { + content: "\f0db"; +} +.fa-unsorted:before, +.fa-sort:before { + content: "\f0dc"; +} +.fa-sort-down:before, +.fa-sort-desc:before { + content: "\f0dd"; +} +.fa-sort-up:before, +.fa-sort-asc:before { + content: "\f0de"; +} +.fa-envelope:before { + content: "\f0e0"; +} +.fa-linkedin:before { + content: "\f0e1"; +} +.fa-rotate-left:before, +.fa-undo:before { + content: "\f0e2"; +} +.fa-legal:before, +.fa-gavel:before { + content: "\f0e3"; +} +.fa-dashboard:before, +.fa-tachometer:before { + content: "\f0e4"; +} +.fa-comment-o:before { + content: "\f0e5"; +} +.fa-comments-o:before { + content: "\f0e6"; +} +.fa-flash:before, +.fa-bolt:before { + content: "\f0e7"; +} +.fa-sitemap:before { + content: "\f0e8"; +} +.fa-umbrella:before { + content: "\f0e9"; +} +.fa-paste:before, +.fa-clipboard:before { + content: "\f0ea"; +} +.fa-lightbulb-o:before { + content: "\f0eb"; +} +.fa-exchange:before { + content: "\f0ec"; +} +.fa-cloud-download:before { + content: "\f0ed"; +} +.fa-cloud-upload:before { + content: "\f0ee"; +} +.fa-user-md:before { + content: "\f0f0"; +} +.fa-stethoscope:before { + content: "\f0f1"; +} +.fa-suitcase:before { + content: "\f0f2"; +} +.fa-bell-o:before { + content: "\f0a2"; +} +.fa-coffee:before { + content: "\f0f4"; +} +.fa-cutlery:before { + content: "\f0f5"; +} +.fa-file-text-o:before { + content: "\f0f6"; +} +.fa-building-o:before { + content: "\f0f7"; +} +.fa-hospital-o:before { + content: "\f0f8"; +} +.fa-ambulance:before { + content: "\f0f9"; +} +.fa-medkit:before { + content: "\f0fa"; +} +.fa-fighter-jet:before { + content: "\f0fb"; +} +.fa-beer:before { + content: "\f0fc"; +} +.fa-h-square:before { + content: "\f0fd"; +} +.fa-plus-square:before { + content: "\f0fe"; +} +.fa-angle-double-left:before { + content: "\f100"; +} +.fa-angle-double-right:before { + content: "\f101"; +} +.fa-angle-double-up:before { + content: "\f102"; +} +.fa-angle-double-down:before { + content: "\f103"; +} +.fa-angle-left:before { + content: "\f104"; +} +.fa-angle-right:before { + content: "\f105"; +} +.fa-angle-up:before { + content: "\f106"; +} +.fa-angle-down:before { + content: "\f107"; +} +.fa-desktop:before { + content: "\f108"; +} +.fa-laptop:before { + content: "\f109"; +} +.fa-tablet:before { + content: "\f10a"; +} +.fa-mobile-phone:before, +.fa-mobile:before { + content: "\f10b"; +} +.fa-circle-o:before { + content: "\f10c"; +} +.fa-quote-left:before { + content: "\f10d"; +} +.fa-quote-right:before { + content: "\f10e"; +} +.fa-spinner:before { + content: "\f110"; +} +.fa-circle:before { + content: "\f111"; +} +.fa-mail-reply:before, +.fa-reply:before { + content: "\f112"; +} +.fa-github-alt:before { + content: "\f113"; +} +.fa-folder-o:before { + content: "\f114"; +} +.fa-folder-open-o:before { + content: "\f115"; +} +.fa-smile-o:before { + content: "\f118"; +} +.fa-frown-o:before { + content: "\f119"; +} +.fa-meh-o:before { + content: "\f11a"; +} +.fa-gamepad:before { + content: "\f11b"; +} +.fa-keyboard-o:before { + content: "\f11c"; +} +.fa-flag-o:before { + content: "\f11d"; +} +.fa-flag-checkered:before { + content: "\f11e"; +} +.fa-terminal:before { + content: "\f120"; +} +.fa-code:before { + content: "\f121"; +} +.fa-mail-reply-all:before, +.fa-reply-all:before { + content: "\f122"; +} +.fa-star-half-empty:before, +.fa-star-half-full:before, +.fa-star-half-o:before { + content: "\f123"; +} +.fa-location-arrow:before { + content: "\f124"; +} +.fa-crop:before { + content: "\f125"; +} +.fa-code-fork:before { + content: "\f126"; +} +.fa-unlink:before, +.fa-chain-broken:before { + content: "\f127"; +} +.fa-question:before { + content: "\f128"; +} +.fa-info:before { + content: "\f129"; +} +.fa-exclamation:before { + content: "\f12a"; +} +.fa-superscript:before { + content: "\f12b"; +} +.fa-subscript:before { + content: "\f12c"; +} +.fa-eraser:before { + content: "\f12d"; +} +.fa-puzzle-piece:before { + content: "\f12e"; +} +.fa-microphone:before { + content: "\f130"; +} +.fa-microphone-slash:before { + content: "\f131"; +} +.fa-shield:before { + content: "\f132"; +} +.fa-calendar-o:before { + content: "\f133"; +} +.fa-fire-extinguisher:before { + content: "\f134"; +} +.fa-rocket:before { + content: "\f135"; +} +.fa-maxcdn:before { + content: "\f136"; +} +.fa-chevron-circle-left:before { + content: "\f137"; +} +.fa-chevron-circle-right:before { + content: "\f138"; +} +.fa-chevron-circle-up:before { + content: "\f139"; +} +.fa-chevron-circle-down:before { + content: "\f13a"; +} +.fa-html5:before { + content: "\f13b"; +} +.fa-css3:before { + content: "\f13c"; +} +.fa-anchor:before { + content: "\f13d"; +} +.fa-unlock-alt:before { + content: "\f13e"; +} +.fa-bullseye:before { + content: "\f140"; +} +.fa-ellipsis-h:before { + content: "\f141"; +} +.fa-ellipsis-v:before { + content: "\f142"; +} +.fa-rss-square:before { + content: "\f143"; +} +.fa-play-circle:before { + content: "\f144"; +} +.fa-ticket:before { + content: "\f145"; +} +.fa-minus-square:before { + content: "\f146"; +} +.fa-minus-square-o:before { + content: "\f147"; +} +.fa-level-up:before { + content: "\f148"; +} +.fa-level-down:before { + content: "\f149"; +} +.fa-check-square:before { + content: "\f14a"; +} +.fa-pencil-square:before { + content: "\f14b"; +} +.fa-external-link-square:before { + content: "\f14c"; +} +.fa-share-square:before { + content: "\f14d"; +} +.fa-compass:before { + content: "\f14e"; +} +.fa-toggle-down:before, +.fa-caret-square-o-down:before { + content: "\f150"; +} +.fa-toggle-up:before, +.fa-caret-square-o-up:before { + content: "\f151"; +} +.fa-toggle-right:before, +.fa-caret-square-o-right:before { + content: "\f152"; +} +.fa-euro:before, +.fa-eur:before { + content: "\f153"; +} +.fa-gbp:before { + content: "\f154"; +} +.fa-dollar:before, +.fa-usd:before { + content: "\f155"; +} +.fa-rupee:before, +.fa-inr:before { + content: "\f156"; +} +.fa-cny:before, +.fa-rmb:before, +.fa-yen:before, +.fa-jpy:before { + content: "\f157"; +} +.fa-ruble:before, +.fa-rouble:before, +.fa-rub:before { + content: "\f158"; +} +.fa-won:before, +.fa-krw:before { + content: "\f159"; +} +.fa-bitcoin:before, +.fa-btc:before { + content: "\f15a"; +} +.fa-file:before { + content: "\f15b"; +} +.fa-file-text:before { + content: "\f15c"; +} +.fa-sort-alpha-asc:before { + content: "\f15d"; +} +.fa-sort-alpha-desc:before { + content: "\f15e"; +} +.fa-sort-amount-asc:before { + content: "\f160"; +} +.fa-sort-amount-desc:before { + content: "\f161"; +} +.fa-sort-numeric-asc:before { + content: "\f162"; +} +.fa-sort-numeric-desc:before { + content: "\f163"; +} +.fa-thumbs-up:before { + content: "\f164"; +} +.fa-thumbs-down:before { + content: "\f165"; +} +.fa-youtube-square:before { + content: "\f166"; +} +.fa-youtube:before { + content: "\f167"; +} +.fa-xing:before { + content: "\f168"; +} +.fa-xing-square:before { + content: "\f169"; +} +.fa-youtube-play:before { + content: "\f16a"; +} +.fa-dropbox:before { + content: "\f16b"; +} +.fa-stack-overflow:before { + content: "\f16c"; +} +.fa-instagram:before { + content: "\f16d"; +} +.fa-flickr:before { + content: "\f16e"; +} +.fa-adn:before { + content: "\f170"; +} +.fa-bitbucket:before { + content: "\f171"; +} +.fa-bitbucket-square:before { + content: "\f172"; +} +.fa-tumblr:before { + content: "\f173"; +} +.fa-tumblr-square:before { + content: "\f174"; +} +.fa-long-arrow-down:before { + content: "\f175"; +} +.fa-long-arrow-up:before { + content: "\f176"; +} +.fa-long-arrow-left:before { + content: "\f177"; +} +.fa-long-arrow-right:before { + content: "\f178"; +} +.fa-apple:before { + content: "\f179"; +} +.fa-windows:before { + content: "\f17a"; +} +.fa-android:before { + content: "\f17b"; +} +.fa-linux:before { + content: "\f17c"; +} +.fa-dribbble:before { + content: "\f17d"; +} +.fa-skype:before { + content: "\f17e"; +} +.fa-foursquare:before { + content: "\f180"; +} +.fa-trello:before { + content: "\f181"; +} +.fa-female:before { + content: "\f182"; +} +.fa-male:before { + content: "\f183"; +} +.fa-gittip:before, +.fa-gratipay:before { + content: "\f184"; +} +.fa-sun-o:before { + content: "\f185"; +} +.fa-moon-o:before { + content: "\f186"; +} +.fa-archive:before { + content: "\f187"; +} +.fa-bug:before { + content: "\f188"; +} +.fa-vk:before { + content: "\f189"; +} +.fa-weibo:before { + content: "\f18a"; +} +.fa-renren:before { + content: "\f18b"; +} +.fa-pagelines:before { + content: "\f18c"; +} +.fa-stack-exchange:before { + content: "\f18d"; +} +.fa-arrow-circle-o-right:before { + content: "\f18e"; +} +.fa-arrow-circle-o-left:before { + content: "\f190"; +} +.fa-toggle-left:before, +.fa-caret-square-o-left:before { + content: "\f191"; +} +.fa-dot-circle-o:before { + content: "\f192"; +} +.fa-wheelchair:before { + content: "\f193"; +} +.fa-vimeo-square:before { + content: "\f194"; +} +.fa-turkish-lira:before, +.fa-try:before { + content: "\f195"; +} +.fa-plus-square-o:before { + content: "\f196"; +} +.fa-space-shuttle:before { + content: "\f197"; +} +.fa-slack:before { + content: "\f198"; +} +.fa-envelope-square:before { + content: "\f199"; +} +.fa-wordpress:before { + content: "\f19a"; +} +.fa-openid:before { + content: "\f19b"; +} +.fa-institution:before, +.fa-bank:before, +.fa-university:before { + content: "\f19c"; +} +.fa-mortar-board:before, +.fa-graduation-cap:before { + content: "\f19d"; +} +.fa-yahoo:before { + content: "\f19e"; +} +.fa-google:before { + content: "\f1a0"; +} +.fa-reddit:before { + content: "\f1a1"; +} +.fa-reddit-square:before { + content: "\f1a2"; +} +.fa-stumbleupon-circle:before { + content: "\f1a3"; +} +.fa-stumbleupon:before { + content: "\f1a4"; +} +.fa-delicious:before { + content: "\f1a5"; +} +.fa-digg:before { + content: "\f1a6"; +} +.fa-pied-piper-pp:before { + content: "\f1a7"; +} +.fa-pied-piper-alt:before { + content: "\f1a8"; +} +.fa-drupal:before { + content: "\f1a9"; +} +.fa-joomla:before { + content: "\f1aa"; +} +.fa-language:before { + content: "\f1ab"; +} +.fa-fax:before { + content: "\f1ac"; +} +.fa-building:before { + content: "\f1ad"; +} +.fa-child:before { + content: "\f1ae"; +} +.fa-paw:before { + content: "\f1b0"; +} +.fa-spoon:before { + content: "\f1b1"; +} +.fa-cube:before { + content: "\f1b2"; +} +.fa-cubes:before { + content: "\f1b3"; +} +.fa-behance:before { + content: "\f1b4"; +} +.fa-behance-square:before { + content: "\f1b5"; +} +.fa-steam:before { + content: "\f1b6"; +} +.fa-steam-square:before { + content: "\f1b7"; +} +.fa-recycle:before { + content: "\f1b8"; +} +.fa-automobile:before, +.fa-car:before { + content: "\f1b9"; +} +.fa-cab:before, +.fa-taxi:before { + content: "\f1ba"; +} +.fa-tree:before { + content: "\f1bb"; +} +.fa-spotify:before { + content: "\f1bc"; +} +.fa-deviantart:before { + content: "\f1bd"; +} +.fa-soundcloud:before { + content: "\f1be"; +} +.fa-database:before { + content: "\f1c0"; +} +.fa-file-pdf-o:before { + content: "\f1c1"; +} +.fa-file-word-o:before { + content: "\f1c2"; +} +.fa-file-excel-o:before { + content: "\f1c3"; +} +.fa-file-powerpoint-o:before { + content: "\f1c4"; +} +.fa-file-photo-o:before, +.fa-file-picture-o:before, +.fa-file-image-o:before { + content: "\f1c5"; +} +.fa-file-zip-o:before, +.fa-file-archive-o:before { + content: "\f1c6"; +} +.fa-file-sound-o:before, +.fa-file-audio-o:before { + content: "\f1c7"; +} +.fa-file-movie-o:before, +.fa-file-video-o:before { + content: "\f1c8"; +} +.fa-file-code-o:before { + content: "\f1c9"; +} +.fa-vine:before { + content: "\f1ca"; +} +.fa-codepen:before { + content: "\f1cb"; +} +.fa-jsfiddle:before { + content: "\f1cc"; +} +.fa-life-bouy:before, +.fa-life-buoy:before, +.fa-life-saver:before, +.fa-support:before, +.fa-life-ring:before { + content: "\f1cd"; +} +.fa-circle-o-notch:before { + content: "\f1ce"; +} +.fa-ra:before, +.fa-resistance:before, +.fa-rebel:before { + content: "\f1d0"; +} +.fa-ge:before, +.fa-empire:before { + content: "\f1d1"; +} +.fa-git-square:before { + content: "\f1d2"; +} +.fa-git:before { + content: "\f1d3"; +} +.fa-y-combinator-square:before, +.fa-yc-square:before, +.fa-hacker-news:before { + content: "\f1d4"; +} +.fa-tencent-weibo:before { + content: "\f1d5"; +} +.fa-qq:before { + content: "\f1d6"; +} +.fa-wechat:before, +.fa-weixin:before { + content: "\f1d7"; +} +.fa-send:before, +.fa-paper-plane:before { + content: "\f1d8"; +} +.fa-send-o:before, +.fa-paper-plane-o:before { + content: "\f1d9"; +} +.fa-history:before { + content: "\f1da"; +} +.fa-circle-thin:before { + content: "\f1db"; +} +.fa-header:before { + content: "\f1dc"; +} +.fa-paragraph:before { + content: "\f1dd"; +} +.fa-sliders:before { + content: "\f1de"; +} +.fa-share-alt:before { + content: "\f1e0"; +} +.fa-share-alt-square:before { + content: "\f1e1"; +} +.fa-bomb:before { + content: "\f1e2"; +} +.fa-soccer-ball-o:before, +.fa-futbol-o:before { + content: "\f1e3"; +} +.fa-tty:before { + content: "\f1e4"; +} +.fa-binoculars:before { + content: "\f1e5"; +} +.fa-plug:before { + content: "\f1e6"; +} +.fa-slideshare:before { + content: "\f1e7"; +} +.fa-twitch:before { + content: "\f1e8"; +} +.fa-yelp:before { + content: "\f1e9"; +} +.fa-newspaper-o:before { + content: "\f1ea"; +} +.fa-wifi:before { + content: "\f1eb"; +} +.fa-calculator:before { + content: "\f1ec"; +} +.fa-paypal:before { + content: "\f1ed"; +} +.fa-google-wallet:before { + content: "\f1ee"; +} +.fa-cc-visa:before { + content: "\f1f0"; +} +.fa-cc-mastercard:before { + content: "\f1f1"; +} +.fa-cc-discover:before { + content: "\f1f2"; +} +.fa-cc-amex:before { + content: "\f1f3"; +} +.fa-cc-paypal:before { + content: "\f1f4"; +} +.fa-cc-stripe:before { + content: "\f1f5"; +} +.fa-bell-slash:before { + content: "\f1f6"; +} +.fa-bell-slash-o:before { + content: "\f1f7"; +} +.fa-trash:before { + content: "\f1f8"; +} +.fa-copyright:before { + content: "\f1f9"; +} +.fa-at:before { + content: "\f1fa"; +} +.fa-eyedropper:before { + content: "\f1fb"; +} +.fa-paint-brush:before { + content: "\f1fc"; +} +.fa-birthday-cake:before { + content: "\f1fd"; +} +.fa-area-chart:before { + content: "\f1fe"; +} +.fa-pie-chart:before { + content: "\f200"; +} +.fa-line-chart:before { + content: "\f201"; +} +.fa-lastfm:before { + content: "\f202"; +} +.fa-lastfm-square:before { + content: "\f203"; +} +.fa-toggle-off:before { + content: "\f204"; +} +.fa-toggle-on:before { + content: "\f205"; +} +.fa-bicycle:before { + content: "\f206"; +} +.fa-bus:before { + content: "\f207"; +} +.fa-ioxhost:before { + content: "\f208"; +} +.fa-angellist:before { + content: "\f209"; +} +.fa-cc:before { + content: "\f20a"; +} +.fa-shekel:before, +.fa-sheqel:before, +.fa-ils:before { + content: "\f20b"; +} +.fa-meanpath:before { + content: "\f20c"; +} +.fa-buysellads:before { + content: "\f20d"; +} +.fa-connectdevelop:before { + content: "\f20e"; +} +.fa-dashcube:before { + content: "\f210"; +} +.fa-forumbee:before { + content: "\f211"; +} +.fa-leanpub:before { + content: "\f212"; +} +.fa-sellsy:before { + content: "\f213"; +} +.fa-shirtsinbulk:before { + content: "\f214"; +} +.fa-simplybuilt:before { + content: "\f215"; +} +.fa-skyatlas:before { + content: "\f216"; +} +.fa-cart-plus:before { + content: "\f217"; +} +.fa-cart-arrow-down:before { + content: "\f218"; +} +.fa-diamond:before { + content: "\f219"; +} +.fa-ship:before { + content: "\f21a"; +} +.fa-user-secret:before { + content: "\f21b"; +} +.fa-motorcycle:before { + content: "\f21c"; +} +.fa-street-view:before { + content: "\f21d"; +} +.fa-heartbeat:before { + content: "\f21e"; +} +.fa-venus:before { + content: "\f221"; +} +.fa-mars:before { + content: "\f222"; +} +.fa-mercury:before { + content: "\f223"; +} +.fa-intersex:before, +.fa-transgender:before { + content: "\f224"; +} +.fa-transgender-alt:before { + content: "\f225"; +} +.fa-venus-double:before { + content: "\f226"; +} +.fa-mars-double:before { + content: "\f227"; +} +.fa-venus-mars:before { + content: "\f228"; +} +.fa-mars-stroke:before { + content: "\f229"; +} +.fa-mars-stroke-v:before { + content: "\f22a"; +} +.fa-mars-stroke-h:before { + content: "\f22b"; +} +.fa-neuter:before { + content: "\f22c"; +} +.fa-genderless:before { + content: "\f22d"; +} +.fa-facebook-official:before { + content: "\f230"; +} +.fa-pinterest-p:before { + content: "\f231"; +} +.fa-whatsapp:before { + content: "\f232"; +} +.fa-server:before { + content: "\f233"; +} +.fa-user-plus:before { + content: "\f234"; +} +.fa-user-times:before { + content: "\f235"; +} +.fa-hotel:before, +.fa-bed:before { + content: "\f236"; +} +.fa-viacoin:before { + content: "\f237"; +} +.fa-train:before { + content: "\f238"; +} +.fa-subway:before { + content: "\f239"; +} +.fa-medium:before { + content: "\f23a"; +} +.fa-yc:before, +.fa-y-combinator:before { + content: "\f23b"; +} +.fa-optin-monster:before { + content: "\f23c"; +} +.fa-opencart:before { + content: "\f23d"; +} +.fa-expeditedssl:before { + content: "\f23e"; +} +.fa-battery-4:before, +.fa-battery:before, +.fa-battery-full:before { + content: "\f240"; +} +.fa-battery-3:before, +.fa-battery-three-quarters:before { + content: "\f241"; +} +.fa-battery-2:before, +.fa-battery-half:before { + content: "\f242"; +} +.fa-battery-1:before, +.fa-battery-quarter:before { + content: "\f243"; +} +.fa-battery-0:before, +.fa-battery-empty:before { + content: "\f244"; +} +.fa-mouse-pointer:before { + content: "\f245"; +} +.fa-i-cursor:before { + content: "\f246"; +} +.fa-object-group:before { + content: "\f247"; +} +.fa-object-ungroup:before { + content: "\f248"; +} +.fa-sticky-note:before { + content: "\f249"; +} +.fa-sticky-note-o:before { + content: "\f24a"; +} +.fa-cc-jcb:before { + content: "\f24b"; +} +.fa-cc-diners-club:before { + content: "\f24c"; +} +.fa-clone:before { + content: "\f24d"; +} +.fa-balance-scale:before { + content: "\f24e"; +} +.fa-hourglass-o:before { + content: "\f250"; +} +.fa-hourglass-1:before, +.fa-hourglass-start:before { + content: "\f251"; +} +.fa-hourglass-2:before, +.fa-hourglass-half:before { + content: "\f252"; +} +.fa-hourglass-3:before, +.fa-hourglass-end:before { + content: "\f253"; +} +.fa-hourglass:before { + content: "\f254"; +} +.fa-hand-grab-o:before, +.fa-hand-rock-o:before { + content: "\f255"; +} +.fa-hand-stop-o:before, +.fa-hand-paper-o:before { + content: "\f256"; +} +.fa-hand-scissors-o:before { + content: "\f257"; +} +.fa-hand-lizard-o:before { + content: "\f258"; +} +.fa-hand-spock-o:before { + content: "\f259"; +} +.fa-hand-pointer-o:before { + content: "\f25a"; +} +.fa-hand-peace-o:before { + content: "\f25b"; +} +.fa-trademark:before { + content: "\f25c"; +} +.fa-registered:before { + content: "\f25d"; +} +.fa-creative-commons:before { + content: "\f25e"; +} +.fa-gg:before { + content: "\f260"; +} +.fa-gg-circle:before { + content: "\f261"; +} +.fa-tripadvisor:before { + content: "\f262"; +} +.fa-odnoklassniki:before { + content: "\f263"; +} +.fa-odnoklassniki-square:before { + content: "\f264"; +} +.fa-get-pocket:before { + content: "\f265"; +} +.fa-wikipedia-w:before { + content: "\f266"; +} +.fa-safari:before { + content: "\f267"; +} +.fa-chrome:before { + content: "\f268"; +} +.fa-firefox:before { + content: "\f269"; +} +.fa-opera:before { + content: "\f26a"; +} +.fa-internet-explorer:before { + content: "\f26b"; +} +.fa-tv:before, +.fa-television:before { + content: "\f26c"; +} +.fa-contao:before { + content: "\f26d"; +} +.fa-500px:before { + content: "\f26e"; +} +.fa-amazon:before { + content: "\f270"; +} +.fa-calendar-plus-o:before { + content: "\f271"; +} +.fa-calendar-minus-o:before { + content: "\f272"; +} +.fa-calendar-times-o:before { + content: "\f273"; +} +.fa-calendar-check-o:before { + content: "\f274"; +} +.fa-industry:before { + content: "\f275"; +} +.fa-map-pin:before { + content: "\f276"; +} +.fa-map-signs:before { + content: "\f277"; +} +.fa-map-o:before { + content: "\f278"; +} +.fa-map:before { + content: "\f279"; +} +.fa-commenting:before { + content: "\f27a"; +} +.fa-commenting-o:before { + content: "\f27b"; +} +.fa-houzz:before { + content: "\f27c"; +} +.fa-vimeo:before { + content: "\f27d"; +} +.fa-black-tie:before { + content: "\f27e"; +} +.fa-fonticons:before { + content: "\f280"; +} +.fa-reddit-alien:before { + content: "\f281"; +} +.fa-edge:before { + content: "\f282"; +} +.fa-credit-card-alt:before { + content: "\f283"; +} +.fa-codiepie:before { + content: "\f284"; +} +.fa-modx:before { + content: "\f285"; +} +.fa-fort-awesome:before { + content: "\f286"; +} +.fa-usb:before { + content: "\f287"; +} +.fa-product-hunt:before { + content: "\f288"; +} +.fa-mixcloud:before { + content: "\f289"; +} +.fa-scribd:before { + content: "\f28a"; +} +.fa-pause-circle:before { + content: "\f28b"; +} +.fa-pause-circle-o:before { + content: "\f28c"; +} +.fa-stop-circle:before { + content: "\f28d"; +} +.fa-stop-circle-o:before { + content: "\f28e"; +} +.fa-shopping-bag:before { + content: "\f290"; +} +.fa-shopping-basket:before { + content: "\f291"; +} +.fa-hashtag:before { + content: "\f292"; +} +.fa-bluetooth:before { + content: "\f293"; +} +.fa-bluetooth-b:before { + content: "\f294"; +} +.fa-percent:before { + content: "\f295"; +} +.fa-gitlab:before { + content: "\f296"; +} +.fa-wpbeginner:before { + content: "\f297"; +} +.fa-wpforms:before { + content: "\f298"; +} +.fa-envira:before { + content: "\f299"; +} +.fa-universal-access:before { + content: "\f29a"; +} +.fa-wheelchair-alt:before { + content: "\f29b"; +} +.fa-question-circle-o:before { + content: "\f29c"; +} +.fa-blind:before { + content: "\f29d"; +} +.fa-audio-description:before { + content: "\f29e"; +} +.fa-volume-control-phone:before { + content: "\f2a0"; +} +.fa-braille:before { + content: "\f2a1"; +} +.fa-assistive-listening-systems:before { + content: "\f2a2"; +} +.fa-asl-interpreting:before, +.fa-american-sign-language-interpreting:before { + content: "\f2a3"; +} +.fa-deafness:before, +.fa-hard-of-hearing:before, +.fa-deaf:before { + content: "\f2a4"; +} +.fa-glide:before { + content: "\f2a5"; +} +.fa-glide-g:before { + content: "\f2a6"; +} +.fa-signing:before, +.fa-sign-language:before { + content: "\f2a7"; +} +.fa-low-vision:before { + content: "\f2a8"; +} +.fa-viadeo:before { + content: "\f2a9"; +} +.fa-viadeo-square:before { + content: "\f2aa"; +} +.fa-snapchat:before { + content: "\f2ab"; +} +.fa-snapchat-ghost:before { + content: "\f2ac"; +} +.fa-snapchat-square:before { + content: "\f2ad"; +} +.fa-pied-piper:before { + content: "\f2ae"; +} +.fa-first-order:before { + content: "\f2b0"; +} +.fa-yoast:before { + content: "\f2b1"; +} +.fa-themeisle:before { + content: "\f2b2"; +} +.fa-google-plus-circle:before, +.fa-google-plus-official:before { + content: "\f2b3"; +} +.fa-fa:before, +.fa-font-awesome:before { + content: "\f2b4"; +} +.fa-handshake-o:before { + content: "\f2b5"; +} +.fa-envelope-open:before { + content: "\f2b6"; +} +.fa-envelope-open-o:before { + content: "\f2b7"; +} +.fa-linode:before { + content: "\f2b8"; +} +.fa-address-book:before { + content: "\f2b9"; +} +.fa-address-book-o:before { + content: "\f2ba"; +} +.fa-vcard:before, +.fa-address-card:before { + content: "\f2bb"; +} +.fa-vcard-o:before, +.fa-address-card-o:before { + content: "\f2bc"; +} +.fa-user-circle:before { + content: "\f2bd"; +} +.fa-user-circle-o:before { + content: "\f2be"; +} +.fa-user-o:before { + content: "\f2c0"; +} +.fa-id-badge:before { + content: "\f2c1"; +} +.fa-drivers-license:before, +.fa-id-card:before { + content: "\f2c2"; +} +.fa-drivers-license-o:before, +.fa-id-card-o:before { + content: "\f2c3"; +} +.fa-quora:before { + content: "\f2c4"; +} +.fa-free-code-camp:before { + content: "\f2c5"; +} +.fa-telegram:before { + content: "\f2c6"; +} +.fa-thermometer-4:before, +.fa-thermometer:before, +.fa-thermometer-full:before { + content: "\f2c7"; +} +.fa-thermometer-3:before, +.fa-thermometer-three-quarters:before { + content: "\f2c8"; +} +.fa-thermometer-2:before, +.fa-thermometer-half:before { + content: "\f2c9"; +} +.fa-thermometer-1:before, +.fa-thermometer-quarter:before { + content: "\f2ca"; +} +.fa-thermometer-0:before, +.fa-thermometer-empty:before { + content: "\f2cb"; +} +.fa-shower:before { + content: "\f2cc"; +} +.fa-bathtub:before, +.fa-s15:before, +.fa-bath:before { + content: "\f2cd"; +} +.fa-podcast:before { + content: "\f2ce"; +} +.fa-window-maximize:before { + content: "\f2d0"; +} +.fa-window-minimize:before { + content: "\f2d1"; +} +.fa-window-restore:before { + content: "\f2d2"; +} +.fa-times-rectangle:before, +.fa-window-close:before { + content: "\f2d3"; +} +.fa-times-rectangle-o:before, +.fa-window-close-o:before { + content: "\f2d4"; +} +.fa-bandcamp:before { + content: "\f2d5"; +} +.fa-grav:before { + content: "\f2d6"; +} +.fa-etsy:before { + content: "\f2d7"; +} +.fa-imdb:before { + content: "\f2d8"; +} +.fa-ravelry:before { + content: "\f2d9"; +} +.fa-eercast:before { + content: "\f2da"; +} +.fa-microchip:before { + content: "\f2db"; +} +.fa-snowflake-o:before { + content: "\f2dc"; +} +.fa-superpowers:before { + content: "\f2dd"; +} +.fa-wpexplorer:before { + content: "\f2de"; +} +.fa-meetup:before { + content: "\f2e0"; +} +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; +} +.sr-only-focusable:active, +.sr-only-focusable:focus { + position: static; + width: auto; + height: auto; + margin: 0; + overflow: visible; + clip: auto; +} diff --git a/public/font-awesome-4.7.0/css/font-awesome.min.css b/public/font-awesome-4.7.0/css/font-awesome.min.css new file mode 100644 index 00000000..540440ce --- /dev/null +++ b/public/font-awesome-4.7.0/css/font-awesome.min.css @@ -0,0 +1,4 @@ +/*! + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.7.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-signing:before,.fa-sign-language:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-pied-piper:before{content:"\f2ae"}.fa-first-order:before{content:"\f2b0"}.fa-yoast:before{content:"\f2b1"}.fa-themeisle:before{content:"\f2b2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\f2b3"}.fa-fa:before,.fa-font-awesome:before{content:"\f2b4"}.fa-handshake-o:before{content:"\f2b5"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-o:before{content:"\f2b7"}.fa-linode:before{content:"\f2b8"}.fa-address-book:before{content:"\f2b9"}.fa-address-book-o:before{content:"\f2ba"}.fa-vcard:before,.fa-address-card:before{content:"\f2bb"}.fa-vcard-o:before,.fa-address-card-o:before{content:"\f2bc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-circle-o:before{content:"\f2be"}.fa-user-o:before{content:"\f2c0"}.fa-id-badge:before{content:"\f2c1"}.fa-drivers-license:before,.fa-id-card:before{content:"\f2c2"}.fa-drivers-license-o:before,.fa-id-card-o:before{content:"\f2c3"}.fa-quora:before{content:"\f2c4"}.fa-free-code-camp:before{content:"\f2c5"}.fa-telegram:before{content:"\f2c6"}.fa-thermometer-4:before,.fa-thermometer:before,.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-thermometer-2:before,.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:"\f2cb"}.fa-shower:before{content:"\f2cc"}.fa-bathtub:before,.fa-s15:before,.fa-bath:before{content:"\f2cd"}.fa-podcast:before{content:"\f2ce"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-times-rectangle:before,.fa-window-close:before{content:"\f2d3"}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:"\f2d4"}.fa-bandcamp:before{content:"\f2d5"}.fa-grav:before{content:"\f2d6"}.fa-etsy:before{content:"\f2d7"}.fa-imdb:before{content:"\f2d8"}.fa-ravelry:before{content:"\f2d9"}.fa-eercast:before{content:"\f2da"}.fa-microchip:before{content:"\f2db"}.fa-snowflake-o:before{content:"\f2dc"}.fa-superpowers:before{content:"\f2dd"}.fa-wpexplorer:before{content:"\f2de"}.fa-meetup:before{content:"\f2e0"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto} diff --git a/public/font-awesome-4.7.0/fonts/FontAwesome.otf b/public/font-awesome-4.7.0/fonts/FontAwesome.otf new file mode 100644 index 00000000..401ec0f3 Binary files /dev/null and b/public/font-awesome-4.7.0/fonts/FontAwesome.otf differ diff --git a/public/font-awesome-4.7.0/fonts/fontawesome-webfont.eot b/public/font-awesome-4.7.0/fonts/fontawesome-webfont.eot new file mode 100644 index 00000000..e9f60ca9 Binary files /dev/null and b/public/font-awesome-4.7.0/fonts/fontawesome-webfont.eot differ diff --git a/public/font-awesome-4.7.0/fonts/fontawesome-webfont.svg b/public/font-awesome-4.7.0/fonts/fontawesome-webfont.svg new file mode 100644 index 00000000..855c845e --- /dev/null +++ b/public/font-awesome-4.7.0/fonts/fontawesome-webfont.svg @@ -0,0 +1,2671 @@ + + + + +Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 + By ,,, +Copyright Dave Gandy 2016. All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/font-awesome-4.7.0/fonts/fontawesome-webfont.ttf b/public/font-awesome-4.7.0/fonts/fontawesome-webfont.ttf new file mode 100644 index 00000000..35acda2f Binary files /dev/null and b/public/font-awesome-4.7.0/fonts/fontawesome-webfont.ttf differ diff --git a/public/font-awesome-4.7.0/fonts/fontawesome-webfont.woff b/public/font-awesome-4.7.0/fonts/fontawesome-webfont.woff new file mode 100644 index 00000000..400014a4 Binary files /dev/null and b/public/font-awesome-4.7.0/fonts/fontawesome-webfont.woff differ diff --git a/public/font-awesome-4.7.0/fonts/fontawesome-webfont.woff2 b/public/font-awesome-4.7.0/fonts/fontawesome-webfont.woff2 new file mode 100644 index 00000000..4d13fc60 Binary files /dev/null and b/public/font-awesome-4.7.0/fonts/fontawesome-webfont.woff2 differ diff --git a/query_gen/main.go b/query_gen/main.go index 7ba38aac..08db342c 100644 --- a/query_gen/main.go +++ b/query_gen/main.go @@ -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 = ?") diff --git a/routes.go b/routes.go index ae3dc8e8..e97721d4 100644 --- a/routes.go +++ b/routes.go @@ -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 { } diff --git a/routes_common.go b/routes_common.go index 8b097052..2891c24b 100644 --- a/routes_common.go +++ b/routes_common.go @@ -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() diff --git a/tasks.go b/tasks.go index 72ab4b23..5cd449f9 100644 --- a/tasks.go +++ b/tasks.go @@ -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() } diff --git a/template_list.go b/template_list.go index d48d5527..80f3309b 100644 --- a/template_list.go +++ b/template_list.go @@ -103,134 +103,132 @@ var topic_13 = []byte(`">

`) var topic_14 = []byte(`

`) -var topic_15 = []byte(`🔒︎`) +var topic_15 = []byte(`🔒︎`) var topic_16 = []byte(` - `) -var topic_18 = []byte(``) -var topic_19 = []byte(` `) -var topic_20 = []byte(` +var topic_18 = []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(`">

`) -var topic_27 = []byte(`

+var topic_25 = []byte(`

+var topic_26 = []byte(` `) -var topic_30 = []byte(`   +var topic_27 = []byte(`" class="username real_username">`) +var topic_28 = []byte(`   `) -var topic_31 = []byte(` +var topic_29 = []byte(` `) -var topic_35 = []byte(``) -var topic_37 = []byte(``) -var topic_39 = []byte(``) -var topic_41 = []byte(``) -var topic_43 = []byte(``) -var topic_45 = []byte(` +var topic_31 = []byte(`background-color:/*#eaffea*/#D6FFD6;`) +var topic_32 = []byte(`">`) +var topic_33 = []byte(``) +var topic_35 = []byte(``) +var topic_37 = []byte(``) +var topic_39 = []byte(``) +var topic_41 = []byte(``) +var topic_43 = []byte(``) +var topic_45 = []byte(``) +var topic_47 = []byte(` +var topic_48 = []byte(`?session=`) +var topic_49 = []byte(`&type=topic" class="mod_button report_item" style="font-weight:normal;" title="Flag Topic"> `) -var topic_48 = []byte(``) -var topic_50 = []byte(``) -var topic_51 = []byte(``) -var topic_52 = []byte(``) -var topic_53 = []byte(``) -var topic_54 = []byte(` +var topic_50 = []byte(``) +var topic_52 = []byte(``) +var topic_53 = []byte(``) +var topic_54 = []byte(``) +var topic_55 = []byte(``) +var topic_56 = []byte(`
`) -var topic_55 = []byte(` +var topic_57 = []byte(`
`) -var topic_56 = []byte(` +var topic_58 = []byte(` `) -var topic_57 = []byte(` +var topic_59 = []byte(`
`) -var topic_58 = []byte(` +var topic_60 = []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(`">

`) -var topic_65 = []byte(`

+var topic_67 = []byte(`

`) -var topic_67 = []byte(`   +var topic_68 = []byte(`" class="username real_username">`) +var topic_69 = []byte(`   `) -var topic_68 = []byte(``) -var topic_72 = []byte(``) -var topic_74 = []byte(``) -var topic_76 = []byte(``) -var topic_78 = []byte(` +var topic_70 = []byte(``) +var topic_74 = []byte(``) +var topic_76 = []byte(``) +var topic_78 = []byte(``) +var topic_80 = []byte(` +var topic_81 = []byte(`?session=`) +var topic_82 = []byte(`&type=reply" class="mod_button report_item" title="Flag Reply"> `) -var topic_81 = []byte(``) -var topic_83 = []byte(``) -var topic_84 = []byte(``) -var topic_85 = []byte(``) -var topic_86 = []byte(``) -var topic_87 = []byte(` +var topic_83 = []byte(``) +var topic_85 = []byte(``) +var topic_86 = []byte(``) +var topic_87 = []byte(``) +var topic_88 = []byte(``) +var topic_89 = []byte(`
`) -var topic_88 = []byte(`
+var topic_90 = []byte(` `) -var topic_89 = []byte(` +var topic_91 = []byte(`
+var topic_92 = []byte(`' type="hidden" />
@@ -240,7 +238,7 @@ var topic_90 = []byte(`' type="hidden" />
`) -var topic_91 = []byte(` +var topic_93 = []byte(` @@ -299,15 +297,9 @@ var topic_alt_14 = []byte(` - `) -var topic_alt_17 = []byte(``) -var topic_alt_18 = []byte(` `) -var topic_alt_19 = []byte(` +var topic_alt_17 = []byte(` @@ -316,118 +308,122 @@ var topic_alt_19 = []byte(`
 
+var topic_alt_18 = []byte(`), url(/static/white-dot.jpg);background-position: 0px -10px;"> 
`) -var topic_alt_22 = []byte(` +var topic_alt_19 = []byte(`" class="the_name">`) +var topic_alt_20 = []byte(` `) -var topic_alt_23 = []byte(`
`) -var topic_alt_28 = []byte(`
+var topic_alt_26 = []byte(`
+var topic_alt_27 = []byte(`
`) -var topic_alt_30 = []byte(`+1`) -var topic_alt_32 = []byte(`Edit`) -var topic_alt_34 = []byte(`Delete`) -var topic_alt_36 = []byte(`Unpin`) -var topic_alt_38 = []byte(`Pin`) -var topic_alt_40 = []byte(` +var topic_alt_28 = []byte(`+1`) +var topic_alt_30 = []byte(`Edit`) +var topic_alt_32 = []byte(`Delete`) +var topic_alt_34 = []byte(`Unlock`) +var topic_alt_36 = []byte(`Lock`) +var topic_alt_38 = []byte(`Unpin`) +var topic_alt_40 = []byte(`Pin`) +var topic_alt_42 = []byte(` Report +var topic_alt_43 = []byte(`?session=`) +var topic_alt_44 = []byte(`&type=topic" class="action_button report_item">Report `) -var topic_alt_43 = []byte(``) -var topic_alt_45 = []byte(` +var topic_alt_45 = []byte(``) +var topic_alt_47 = []byte(` `) -var topic_alt_46 = []byte(` +var topic_alt_48 = []byte(` `) -var topic_alt_47 = []byte(``) -var topic_alt_48 = []byte(``) -var topic_alt_49 = []byte(` +var topic_alt_49 = []byte(``) +var topic_alt_50 = []byte(``) +var topic_alt_51 = []byte(`
`) -var topic_alt_50 = []byte(` +var topic_alt_52 = []byte(`
+var topic_alt_53 = []byte(`action_item`) +var topic_alt_54 = []byte(`">
 
+var topic_alt_55 = []byte(`), url(/static/white-dot.jpg);background-position: 0px -10px;"> 
`) -var topic_alt_55 = []byte(` +var topic_alt_56 = []byte(`" class="the_name">`) +var topic_alt_57 = []byte(` `) -var topic_alt_56 = []byte(`
`) -var topic_alt_58 = []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(` `) -var topic_alt_64 = []byte(` +var topic_alt_66 = []byte(` `) -var topic_alt_65 = []byte(` +var topic_alt_67 = []byte(` `) -var topic_alt_66 = []byte(` +var topic_alt_68 = []byte(`
`) -var topic_alt_67 = []byte(`
+var topic_alt_69 = []byte(`
`) -var topic_alt_68 = []byte(`+1`) -var topic_alt_70 = []byte(`Edit`) -var topic_alt_72 = []byte(`Delete`) -var topic_alt_74 = []byte(` +var topic_alt_70 = []byte(`+1`) +var topic_alt_72 = []byte(`Edit`) +var topic_alt_74 = []byte(`Delete`) +var topic_alt_76 = []byte(` Report +var topic_alt_77 = []byte(`?session=`) +var topic_alt_78 = []byte(`&type=reply" class="action_button report_item">Report `) -var topic_alt_77 = []byte(``) -var topic_alt_79 = []byte(` +var topic_alt_79 = []byte(``) +var topic_alt_81 = []byte(` `) -var topic_alt_80 = []byte(` +var topic_alt_82 = []byte(` `) -var topic_alt_81 = []byte(``) -var topic_alt_82 = []byte(``) -var topic_alt_83 = []byte(` +var topic_alt_83 = []byte(``) +var topic_alt_84 = []byte(``) +var topic_alt_85 = []byte(`
`) -var topic_alt_84 = []byte(` +var topic_alt_86 = []byte(`
`) -var topic_alt_85 = []byte(` +var topic_alt_87 = []byte(` `) -var topic_alt_86 = []byte(` +var topic_alt_88 = []byte(`
+var topic_alt_89 = []byte(`' type="hidden" />
@@ -437,7 +433,7 @@ var topic_alt_87 = []byte(`' type="hidden" />
`) -var topic_alt_88 = []byte(` +var topic_alt_90 = []byte(` diff --git a/template_topic.go b/template_topic.go index 53458c8c..9cb30820 100644 --- a/template_topic.go +++ b/template_topic.go @@ -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 { diff --git a/template_topic_alt.go b/template_topic_alt.go index ed48f6da..ec2748e7 100644 --- a/template_topic_alt.go +++ b/template_topic_alt.go @@ -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 { diff --git a/templates/ip-search.html b/templates/ip-search-results.html similarity index 100% rename from templates/ip-search.html rename to templates/ip-search-results.html diff --git a/templates/topic.html b/templates/topic.html index 88c35464..e49c04db 100644 --- a/templates/topic.html +++ b/templates/topic.html @@ -13,13 +13,9 @@

{{.Topic.Title}}

- {{if .Topic.IsClosed}}🔒︎{{end}} + {{if .Topic.IsClosed}}🔒︎{{end}} {{if .CurrentUser.Perms.EditTopic}} - {{if .CurrentUser.Perms.CloseTopic}}{{end}} {{end}}
@@ -40,6 +36,8 @@ {{if .CurrentUser.Perms.DeleteTopic}}{{end}} + {{if .CurrentUser.Perms.CloseTopic}}{{if .Topic.IsClosed}}{{else}}{{end}}{{end}} + {{if .CurrentUser.Perms.PinTopic}}{{if .Topic.Sticky}}{{else}}{{end}}{{end}} {{if .CurrentUser.Perms.ViewIPs}}{{end}} diff --git a/templates/topic_alt.html b/templates/topic_alt.html index 1eedccba..3a88c875 100644 --- a/templates/topic_alt.html +++ b/templates/topic_alt.html @@ -9,13 +9,10 @@

{{.Topic.Title}}

+ {{/** TODO: Inline this CSS **/}} {{if .Topic.IsClosed}}🔒︎{{end}} {{if .CurrentUser.Perms.EditTopic}} - {{if .CurrentUser.Perms.CloseTopic}}{{end}} {{end}}
@@ -37,6 +34,8 @@ {{if .CurrentUser.Perms.LikeItem}}+1{{end}} {{if .CurrentUser.Perms.EditTopic}}Edit{{end}} {{if .CurrentUser.Perms.DeleteTopic}}Delete{{end}} + {{if .CurrentUser.Perms.CloseTopic}} + {{if .Topic.IsClosed}}Unlock{{else}}Lock{{end}}{{end}} {{if .CurrentUser.Perms.PinTopic}} {{if .Topic.Sticky}}Unpin{{else}}Pin{{end}}{{end}} Report diff --git a/themes.go b/themes.go index 0ed5d7bb..2463ec84 100644 --- a/themes.go +++ b/themes.go @@ -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) diff --git a/themes/shadow/public/main.css b/themes/shadow/public/main.css index c8b5a5b5..49c00d8d 100644 --- a/themes/shadow/public/main.css +++ b/themes/shadow/public/main.css @@ -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; diff --git a/themes/tempra-conflux/public/main.css b/themes/tempra-conflux/public/main.css index 613e8d70..12c30d9e 100644 --- a/themes/tempra-conflux/public/main.css +++ b/themes/tempra-conflux/public/main.css @@ -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 { diff --git a/themes/tempra-conflux/public/media.partial.css b/themes/tempra-conflux/public/media.partial.css index 8aa3feca..e8326007 100644 --- a/themes/tempra-conflux/public/media.partial.css +++ b/themes/tempra-conflux/public/media.partial.css @@ -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; diff --git a/themes/tempra-cursive/theme.json b/themes/tempra-cursive/theme.json index cd9b4085..6c60ed3e 100644 --- a/themes/tempra-cursive/theme.json +++ b/themes/tempra-cursive/theme.json @@ -6,6 +6,7 @@ "FullImage": "tempra-cursive.png", "ForkOf": "tempra-simple", "MobileFriendly": true, + "HideFromThemes": true, "URL": "github.com/Azareal/Gosora", "Sidebars":"right" } diff --git a/themes/tempra-simple/public/main.css b/themes/tempra-simple/public/main.css index d7716860..9ea12e1f 100644 --- a/themes/tempra-simple/public/main.css +++ b/themes/tempra-simple/public/main.css @@ -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" }} diff --git a/topic.go b/topic.go index e989efc4..ff7d0757 100644 --- a/topic.go +++ b/topic.go @@ -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) diff --git a/topic_store.go b/topic_store.go index b6ed950f..c9819e19 100644 --- a/topic_store.go +++ b/topic_store.go @@ -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 } diff --git a/user.go b/user.go index 6f1bbd54..1a607634 100644 --- a/user.go +++ b/user.go @@ -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) diff --git a/user_store.go b/user_store.go index 4b10c141..9f60d2bd 100644 --- a/user_store.go +++ b/user_store.go @@ -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 {