Refactored the error handlers.
The static files for themes are now automatically reloaded without having to restart Gosora. Added the Get and Set methods to the SFileList type. Static files can now be updated during execution in a thread-safe way. Removed Riot as a dependency. Began work on making Cosora responsive like the other themes.
This commit is contained in:
parent
0e9cebfa47
commit
9eae8da180
|
@ -86,7 +86,7 @@ Several important features for saving memory in the templates system may have to
|
||||||
|
|
||||||
# Advanced Installation
|
# Advanced Installation
|
||||||
|
|
||||||
An example of running the commands directly on Windows.
|
An example of running the commands directly on Windows. We're looking into reducing the number of commands you need to type, for instance, you could invoke the update-deps batch or shell files to install / update all of the dependencies instead of typing each `get get -u`
|
||||||
|
|
||||||
Linux is similar, however you might need to use cd and mv a bit more like in the shell files due to the differences in go build across platforms. Additionally, Linux doesn't require `StackExchange/wmi` or ``/x/sys/windows`
|
Linux is similar, however you might need to use cd and mv a bit more like in the shell files due to the differences in go build across platforms. Additionally, Linux doesn't require `StackExchange/wmi` or ``/x/sys/windows`
|
||||||
|
|
||||||
|
@ -168,7 +168,7 @@ We're looking for ways to clean-up the plugin system so that all of them (except
|
||||||
|
|
||||||
More images in the /images/ folder. Beware though, some of them are *really* outdated.
|
More images in the /images/ folder. Beware though, some of them are *really* outdated.
|
||||||
|
|
||||||
# Dependencies (a few of these like Riot aren't currently in use, but we anticipate that we'll need some sort of search engine library in the very immediate future)
|
# Dependencies (a few of these like Rez aren't currently in use, but we anticipate that we'll need some sort of search engine library in the very immediate future)
|
||||||
|
|
||||||
* Go 1.9
|
* Go 1.9
|
||||||
|
|
||||||
|
@ -194,8 +194,6 @@ More images in the /images/ folder. Beware though, some of them are *really* out
|
||||||
|
|
||||||
* ithub.com/denisenkom/go-mssqldb For interfacing with MSSQL. You will be able to pick this instead of MSSQL soon.
|
* ithub.com/denisenkom/go-mssqldb For interfacing with MSSQL. You will be able to pick this instead of MSSQL soon.
|
||||||
|
|
||||||
* github.com/go-ego/riot A search engine library.
|
|
||||||
|
|
||||||
* github.com/bamiaux/rez An image resizer (e.g. for spitting out thumbnails)
|
* github.com/bamiaux/rez An image resizer (e.g. for spitting out thumbnails)
|
||||||
|
|
||||||
* github.com/fsnotify/fsnotify A library for watching events on the file system.
|
* github.com/fsnotify/fsnotify A library for watching events on the file system.
|
||||||
|
|
119
common/errors.go
119
common/errors.go
|
@ -1,7 +1,6 @@
|
||||||
package common
|
package common
|
||||||
|
|
||||||
import "log"
|
import "log"
|
||||||
|
|
||||||
import "sync"
|
import "sync"
|
||||||
import "net/http"
|
import "net/http"
|
||||||
import "runtime/debug"
|
import "runtime/debug"
|
||||||
|
@ -32,10 +31,6 @@ type RouteErrorImpl struct {
|
||||||
handled bool
|
handled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
/*func NewRouteError(msg string, system bool, json bool) RouteError {
|
|
||||||
return &RouteErrorImpl{msg, system, json, false}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
func (err *RouteErrorImpl) Type() string {
|
func (err *RouteErrorImpl) Type() string {
|
||||||
// System errors may contain sensitive information we don't want the user to see
|
// System errors may contain sensitive information we don't want the user to see
|
||||||
if err.system {
|
if err.system {
|
||||||
|
@ -80,21 +75,9 @@ func LogWarning(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
|
// 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?
|
// ? - Add a user parameter?
|
||||||
func InternalError(err error, w http.ResponseWriter, r *http.Request) RouteError {
|
func InternalError(err error, w http.ResponseWriter, r *http.Request) RouteError {
|
||||||
log.Print(err)
|
pi := Page{"Internal Server Error", GuestUser, DefaultHeaderVar(), tList, "A problem has occurred in the system."}
|
||||||
debug.PrintStack()
|
handleErrorTemplate(w, r, pi)
|
||||||
|
LogError(err)
|
||||||
// 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, DefaultHeaderVar(), 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)
|
|
||||||
log.Fatal("")
|
|
||||||
return HandledRouteError()
|
return HandledRouteError()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,26 +95,14 @@ func InternalErrorJSQ(err error, w http.ResponseWriter, r *http.Request, isJs bo
|
||||||
func InternalErrorJS(err error, w http.ResponseWriter, r *http.Request) RouteError {
|
func InternalErrorJS(err error, w http.ResponseWriter, r *http.Request) RouteError {
|
||||||
w.WriteHeader(500)
|
w.WriteHeader(500)
|
||||||
_, _ = w.Write([]byte(`{"errmsg":"A problem has occurred in the system."}`))
|
_, _ = w.Write([]byte(`{"errmsg":"A problem has occurred in the system."}`))
|
||||||
errorBufferMutex.Lock()
|
LogError(err)
|
||||||
defer errorBufferMutex.Unlock()
|
|
||||||
errorBuffer = append(errorBuffer, err)
|
|
||||||
log.Fatal(err)
|
|
||||||
return HandledRouteError()
|
return HandledRouteError()
|
||||||
}
|
}
|
||||||
|
|
||||||
func PreError(errmsg string, w http.ResponseWriter, r *http.Request) RouteError {
|
func PreError(errmsg string, w http.ResponseWriter, r *http.Request) RouteError {
|
||||||
w.WriteHeader(500)
|
w.WriteHeader(500)
|
||||||
user := User{ID: 0, Group: 6, Perms: GuestPerms}
|
pi := Page{"Error", GuestUser, DefaultHeaderVar(), tList, errmsg}
|
||||||
pi := Page{"Error", user, DefaultHeaderVar(), tList, errmsg}
|
handleErrorTemplate(w, r, pi)
|
||||||
if PreRenderHooks["pre_render_error"] != nil {
|
|
||||||
if RunPreRenderHook("pre_render_error", w, r, &user, &pi) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err := Templates.ExecuteTemplate(w, "error.html", pi)
|
|
||||||
if err != nil {
|
|
||||||
LogError(err)
|
|
||||||
}
|
|
||||||
return HandledRouteError()
|
return HandledRouteError()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,15 +123,7 @@ func PreErrorJSQ(errmsg string, w http.ResponseWriter, r *http.Request, isJs boo
|
||||||
func LocalError(errmsg string, w http.ResponseWriter, r *http.Request, user User) RouteError {
|
func LocalError(errmsg string, w http.ResponseWriter, r *http.Request, user User) RouteError {
|
||||||
w.WriteHeader(500)
|
w.WriteHeader(500)
|
||||||
pi := Page{"Local Error", user, DefaultHeaderVar(), tList, errmsg}
|
pi := Page{"Local Error", user, DefaultHeaderVar(), tList, errmsg}
|
||||||
if PreRenderHooks["pre_render_error"] != nil {
|
handleErrorTemplate(w, r, pi)
|
||||||
if RunPreRenderHook("pre_render_error", w, r, &user, &pi) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err := Templates.ExecuteTemplate(w, "error.html", pi)
|
|
||||||
if err != nil {
|
|
||||||
LogError(err)
|
|
||||||
}
|
|
||||||
return HandledRouteError()
|
return HandledRouteError()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -182,16 +145,7 @@ func LocalErrorJS(errmsg string, w http.ResponseWriter, r *http.Request) RouteEr
|
||||||
func NoPermissions(w http.ResponseWriter, r *http.Request, user User) RouteError {
|
func NoPermissions(w http.ResponseWriter, r *http.Request, user User) RouteError {
|
||||||
w.WriteHeader(403)
|
w.WriteHeader(403)
|
||||||
pi := Page{"Local Error", user, DefaultHeaderVar(), tList, "You don't have permission to do that."}
|
pi := Page{"Local Error", user, DefaultHeaderVar(), tList, "You don't have permission to do that."}
|
||||||
// TODO: What to do about this hook?
|
handleErrorTemplate(w, r, pi)
|
||||||
if PreRenderHooks["pre_render_error"] != nil {
|
|
||||||
if RunPreRenderHook("pre_render_error", w, r, &user, &pi) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err := Templates.ExecuteTemplate(w, "error.html", pi)
|
|
||||||
if err != nil {
|
|
||||||
LogError(err)
|
|
||||||
}
|
|
||||||
return HandledRouteError()
|
return HandledRouteError()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,15 +166,7 @@ func NoPermissionsJS(w http.ResponseWriter, r *http.Request, user User) RouteErr
|
||||||
func Banned(w http.ResponseWriter, r *http.Request, user User) RouteError {
|
func Banned(w http.ResponseWriter, r *http.Request, user User) RouteError {
|
||||||
w.WriteHeader(403)
|
w.WriteHeader(403)
|
||||||
pi := Page{"Banned", user, DefaultHeaderVar(), tList, "You have been banned from this site."}
|
pi := Page{"Banned", user, DefaultHeaderVar(), tList, "You have been banned from this site."}
|
||||||
if PreRenderHooks["pre_render_error"] != nil {
|
handleErrorTemplate(w, r, pi)
|
||||||
if RunPreRenderHook("pre_render_error", w, r, &user, &pi) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err := Templates.ExecuteTemplate(w, "error.html", pi)
|
|
||||||
if err != nil {
|
|
||||||
LogError(err)
|
|
||||||
}
|
|
||||||
return HandledRouteError()
|
return HandledRouteError()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -252,15 +198,7 @@ func LoginRequiredJSQ(w http.ResponseWriter, r *http.Request, user User, isJs bo
|
||||||
func LoginRequired(w http.ResponseWriter, r *http.Request, user User) RouteError {
|
func LoginRequired(w http.ResponseWriter, r *http.Request, user User) RouteError {
|
||||||
w.WriteHeader(401)
|
w.WriteHeader(401)
|
||||||
pi := Page{"Local Error", user, DefaultHeaderVar(), tList, "You need to login to do that."}
|
pi := Page{"Local Error", user, DefaultHeaderVar(), tList, "You need to login to do that."}
|
||||||
if PreRenderHooks["pre_render_error"] != nil {
|
handleErrorTemplate(w, r, pi)
|
||||||
if RunPreRenderHook("pre_render_error", w, r, &user, &pi) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err := Templates.ExecuteTemplate(w, "error.html", pi)
|
|
||||||
if err != nil {
|
|
||||||
LogError(err)
|
|
||||||
}
|
|
||||||
return HandledRouteError()
|
return HandledRouteError()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -292,30 +230,14 @@ func SecurityError(w http.ResponseWriter, r *http.Request, user User) RouteError
|
||||||
// ? - Add a JSQ and JS version of this?
|
// ? - Add a JSQ and JS version of this?
|
||||||
// ? - Add a user parameter?
|
// ? - Add a user parameter?
|
||||||
func NotFound(w http.ResponseWriter, r *http.Request) RouteError {
|
func NotFound(w http.ResponseWriter, r *http.Request) RouteError {
|
||||||
w.WriteHeader(404)
|
return CustomError("The requested page doesn't exist.", 404, "Not Found", w, r, GuestUser)
|
||||||
// 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, DefaultHeaderVar(), tList, "The requested page doesn't exist."}
|
|
||||||
err := Templates.ExecuteTemplate(w, "error.html", pi)
|
|
||||||
if err != nil {
|
|
||||||
LogError(err)
|
|
||||||
}
|
|
||||||
return HandledRouteError()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CustomError lets us make custom error types which aren't covered by the generic functions above
|
// CustomError lets us make custom error types which aren't covered by the generic functions above
|
||||||
func CustomError(errmsg string, errcode int, errtitle string, w http.ResponseWriter, r *http.Request, user User) RouteError {
|
func CustomError(errmsg string, errcode int, errtitle string, w http.ResponseWriter, r *http.Request, user User) RouteError {
|
||||||
w.WriteHeader(errcode)
|
w.WriteHeader(errcode)
|
||||||
pi := Page{errtitle, user, DefaultHeaderVar(), tList, errmsg}
|
pi := Page{errtitle, user, DefaultHeaderVar(), tList, errmsg}
|
||||||
if PreRenderHooks["pre_render_error"] != nil {
|
handleErrorTemplate(w, r, pi)
|
||||||
if RunPreRenderHook("pre_render_error", w, r, &user, &pi) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err := Templates.ExecuteTemplate(w, "error.html", pi)
|
|
||||||
if err != nil {
|
|
||||||
LogError(err)
|
|
||||||
}
|
|
||||||
return HandledRouteError()
|
return HandledRouteError()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -324,12 +246,25 @@ func CustomErrorJSQ(errmsg string, errcode int, errtitle string, w http.Response
|
||||||
if !isJs {
|
if !isJs {
|
||||||
return CustomError(errmsg, errcode, errtitle, w, r, user)
|
return CustomError(errmsg, errcode, errtitle, w, r, user)
|
||||||
}
|
}
|
||||||
return CustomErrorJS(errmsg, errcode, errtitle, w, r, user)
|
return CustomErrorJS(errmsg, errcode, w, r, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CustomErrorJS is the pure JSON version of CustomError
|
// CustomErrorJS is the pure JSON version of CustomError
|
||||||
func CustomErrorJS(errmsg string, errcode int, errtitle string, w http.ResponseWriter, r *http.Request, user User) RouteError {
|
func CustomErrorJS(errmsg string, errcode int, w http.ResponseWriter, r *http.Request, user User) RouteError {
|
||||||
w.WriteHeader(errcode)
|
w.WriteHeader(errcode)
|
||||||
_, _ = w.Write([]byte(`{"errmsg":"` + errmsg + `"}`))
|
_, _ = w.Write([]byte(`{"errmsg":"` + errmsg + `"}`))
|
||||||
return HandledRouteError()
|
return HandledRouteError()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleErrorTemplate(w http.ResponseWriter, r *http.Request, pi Page) {
|
||||||
|
// TODO: What to do about this hook?
|
||||||
|
if PreRenderHooks["pre_render_error"] != nil {
|
||||||
|
if RunPreRenderHook("pre_render_error", w, r, &pi.CurrentUser, &pi) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err := Templates.ExecuteTemplate(w, "error.html", pi)
|
||||||
|
if err != nil {
|
||||||
|
LogError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,9 +2,9 @@ package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"log"
|
|
||||||
"mime"
|
"mime"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
//"errors"
|
//"errors"
|
||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
@ -16,6 +16,7 @@ import (
|
||||||
type SFileList map[string]SFile
|
type SFileList map[string]SFile
|
||||||
|
|
||||||
var StaticFiles SFileList = make(map[string]SFile)
|
var StaticFiles SFileList = make(map[string]SFile)
|
||||||
|
var staticFileMutex sync.RWMutex
|
||||||
|
|
||||||
type SFile struct {
|
type SFile struct {
|
||||||
Data []byte
|
Data []byte
|
||||||
|
@ -48,11 +49,9 @@ func (list SFileList) Init() error {
|
||||||
var ext = filepath.Ext("/public/" + path)
|
var ext = filepath.Ext("/public/" + path)
|
||||||
gzipData := compressBytesGzip(data)
|
gzipData := compressBytesGzip(data)
|
||||||
|
|
||||||
list["/static/"+path] = SFile{data, gzipData, 0, int64(len(data)), int64(len(gzipData)), mime.TypeByExtension(ext), f, f.ModTime().UTC().Format(http.TimeFormat)}
|
list.Set("/static/"+path, SFile{data, gzipData, 0, int64(len(data)), int64(len(gzipData)), mime.TypeByExtension(ext), f, f.ModTime().UTC().Format(http.TimeFormat)})
|
||||||
|
|
||||||
if Dev.DebugMode {
|
debugLogf("Added the '%s' static file.", path)
|
||||||
log.Print("Added the '" + path + "' static file.")
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -75,14 +74,25 @@ func (list SFileList) Add(path string, prefix string) error {
|
||||||
path = strings.TrimPrefix(path, prefix)
|
path = strings.TrimPrefix(path, prefix)
|
||||||
gzipData := compressBytesGzip(data)
|
gzipData := compressBytesGzip(data)
|
||||||
|
|
||||||
list["/static"+path] = SFile{data, gzipData, 0, int64(len(data)), int64(len(gzipData)), mime.TypeByExtension(ext), f, f.ModTime().UTC().Format(http.TimeFormat)}
|
list.Set("/static"+path, SFile{data, gzipData, 0, int64(len(data)), int64(len(gzipData)), mime.TypeByExtension(ext), f, f.ModTime().UTC().Format(http.TimeFormat)})
|
||||||
|
|
||||||
if Dev.DebugMode {
|
debugLogf("Added the '%s' static file", path)
|
||||||
log.Printf("Added the '%s' static file", path)
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (list SFileList) Get(name string) (file SFile, exists bool) {
|
||||||
|
staticFileMutex.RLock()
|
||||||
|
defer staticFileMutex.RUnlock()
|
||||||
|
file, exists = list[name]
|
||||||
|
return file, exists
|
||||||
|
}
|
||||||
|
|
||||||
|
func (list SFileList) Set(name string, data SFile) {
|
||||||
|
staticFileMutex.Lock()
|
||||||
|
defer staticFileMutex.Unlock()
|
||||||
|
list[name] = data
|
||||||
|
}
|
||||||
|
|
||||||
func compressBytesGzip(in []byte) []byte {
|
func compressBytesGzip(in []byte) []byte {
|
||||||
var buff bytes.Buffer
|
var buff bytes.Buffer
|
||||||
gz := gzip.NewWriter(&buff)
|
gz := gzip.NewWriter(&buff)
|
||||||
|
|
|
@ -14,8 +14,8 @@ type HeaderVars struct {
|
||||||
Widgets PageWidgets
|
Widgets PageWidgets
|
||||||
Site *site
|
Site *site
|
||||||
Settings SettingMap
|
Settings SettingMap
|
||||||
Themes map[string]Theme // TODO: Use a slice containing every theme instead of the main map for speed?
|
Themes map[string]*Theme // TODO: Use a slice containing every theme instead of the main map for speed?
|
||||||
Theme Theme
|
Theme *Theme
|
||||||
//TemplateName string // TODO: Use this to move template calls to the router rather than duplicating them over and over and over?
|
//TemplateName string // TODO: Use this to move template calls to the router rather than duplicating them over and over and over?
|
||||||
Zone string
|
Zone string
|
||||||
Writer http.ResponseWriter
|
Writer http.ResponseWriter
|
||||||
|
@ -151,8 +151,8 @@ type PanelThemesPage struct {
|
||||||
CurrentUser User
|
CurrentUser User
|
||||||
Header *HeaderVars
|
Header *HeaderVars
|
||||||
Stats PanelStats
|
Stats PanelStats
|
||||||
PrimaryThemes []Theme
|
PrimaryThemes []*Theme
|
||||||
VariantThemes []Theme
|
VariantThemes []*Theme
|
||||||
}
|
}
|
||||||
|
|
||||||
type PanelUserPage struct {
|
type PanelUserPage struct {
|
||||||
|
|
|
@ -97,7 +97,7 @@ func cascadeForumPerms(fperms *ForumPerms, user *User) {
|
||||||
// Even if they have the right permissions, the control panel is only open to supermods+. There are many areas without subpermissions which assume that the current user is a supermod+ and admins are extremely unlikely to give these permissions to someone who isn't at-least a supermod to begin with
|
// Even if they have the right permissions, the control panel is only open to supermods+. There are many areas without subpermissions which assume that the current user is a supermod+ and admins are extremely unlikely to give these permissions to someone who isn't at-least a supermod to begin with
|
||||||
// TODO: Do a panel specific theme?
|
// TODO: Do a panel specific theme?
|
||||||
func panelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (headerVars *HeaderVars, stats PanelStats, rerr RouteError) {
|
func panelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (headerVars *HeaderVars, stats PanelStats, rerr RouteError) {
|
||||||
var theme Theme
|
var theme = &Theme{Name: ""}
|
||||||
|
|
||||||
cookie, err := r.Cookie("current_theme")
|
cookie, err := r.Cookie("current_theme")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
@ -116,6 +116,7 @@ func panelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (headerV
|
||||||
Themes: Themes,
|
Themes: Themes,
|
||||||
Theme: theme,
|
Theme: theme,
|
||||||
Zone: "panel",
|
Zone: "panel",
|
||||||
|
Writer: w,
|
||||||
}
|
}
|
||||||
// TODO: We should probably initialise headerVars.ExtData
|
// TODO: We should probably initialise headerVars.ExtData
|
||||||
|
|
||||||
|
@ -189,7 +190,7 @@ func simpleUserCheck(w http.ResponseWriter, r *http.Request, user *User) (header
|
||||||
|
|
||||||
// TODO: Add the ability for admins to restrict certain themes to certain groups?
|
// TODO: Add the ability for admins to restrict certain themes to certain groups?
|
||||||
func userCheck(w http.ResponseWriter, r *http.Request, user *User) (headerVars *HeaderVars, rerr RouteError) {
|
func userCheck(w http.ResponseWriter, r *http.Request, user *User) (headerVars *HeaderVars, rerr RouteError) {
|
||||||
var theme Theme
|
var theme = &Theme{Name: ""}
|
||||||
|
|
||||||
cookie, err := r.Cookie("current_theme")
|
cookie, err := r.Cookie("current_theme")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
@ -208,6 +209,7 @@ func userCheck(w http.ResponseWriter, r *http.Request, user *User) (headerVars *
|
||||||
Themes: Themes,
|
Themes: Themes,
|
||||||
Theme: theme,
|
Theme: theme,
|
||||||
Zone: "frontend",
|
Zone: "frontend",
|
||||||
|
Writer: w,
|
||||||
}
|
}
|
||||||
|
|
||||||
if user.IsBanned {
|
if user.IsBanned {
|
||||||
|
|
|
@ -22,9 +22,9 @@ import (
|
||||||
"../query_gen/lib"
|
"../query_gen/lib"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ThemeList map[string]Theme // ? Use pointers instead?
|
type ThemeList map[string]*Theme
|
||||||
|
|
||||||
var Themes ThemeList = make(map[string]Theme)
|
var Themes ThemeList = make(map[string]*Theme)
|
||||||
var DefaultThemeBox atomic.Value
|
var DefaultThemeBox atomic.Value
|
||||||
var ChangeDefaultThemeMutex sync.Mutex
|
var ChangeDefaultThemeMutex sync.Mutex
|
||||||
|
|
||||||
|
@ -45,7 +45,6 @@ type Theme struct {
|
||||||
Tag string
|
Tag string
|
||||||
URL string
|
URL string
|
||||||
Docks []string // Allowed Values: leftSidebar, rightSidebar, footer
|
Docks []string // Allowed Values: leftSidebar, rightSidebar, footer
|
||||||
AboutSegment bool // ? - Should this be a theme var instead?
|
|
||||||
Settings map[string]ThemeSetting
|
Settings map[string]ThemeSetting
|
||||||
Templates []TemplateMapping
|
Templates []TemplateMapping
|
||||||
TemplatesMap map[string]string
|
TemplatesMap map[string]string
|
||||||
|
@ -117,7 +116,7 @@ func (themes ThemeList) LoadActiveStatus() error {
|
||||||
log.Printf("Loading the default theme '%s'", theme.Name)
|
log.Printf("Loading the default theme '%s'", theme.Name)
|
||||||
theme.Active = true
|
theme.Active = true
|
||||||
DefaultThemeBox.Store(theme.Name)
|
DefaultThemeBox.Store(theme.Name)
|
||||||
MapThemeTemplates(theme)
|
theme.MapTemplates()
|
||||||
} else {
|
} else {
|
||||||
log.Printf("Loading the theme '%s'", theme.Name)
|
log.Printf("Loading the theme '%s'", theme.Name)
|
||||||
theme.Active = false
|
theme.Active = false
|
||||||
|
@ -147,8 +146,8 @@ func InitThemes() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var theme Theme
|
var theme = &Theme{Name: ""}
|
||||||
err = json.Unmarshal(themeFile, &theme)
|
err = json.Unmarshal(themeFile, theme)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -185,21 +184,25 @@ func InitThemes() error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
theme.ResourceTemplates = template.New("")
|
err = theme.LoadStaticFiles()
|
||||||
template.Must(theme.ResourceTemplates.ParseGlob("./themes/" + theme.Name + "/public/*.css"))
|
|
||||||
|
|
||||||
// It should be safe for us to load the files for all the themes in memory, as-long as the admin hasn't setup a ridiculous number of themes
|
|
||||||
err = AddThemeStaticFiles(theme)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
Themes[theme.Name] = theme
|
Themes[theme.Name] = theme
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func AddThemeStaticFiles(theme Theme) error {
|
// TODO: It might be unsafe to call the template parsing functions with fsnotify, do something more concurrent
|
||||||
|
func (theme *Theme) LoadStaticFiles() error {
|
||||||
|
theme.ResourceTemplates = template.New("")
|
||||||
|
template.Must(theme.ResourceTemplates.ParseGlob("./themes/" + theme.Name + "/public/*.css"))
|
||||||
|
|
||||||
|
// It should be safe for us to load the files for all the themes in memory, as-long as the admin hasn't setup a ridiculous number of themes
|
||||||
|
return theme.AddThemeStaticFiles()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (theme *Theme) AddThemeStaticFiles() error {
|
||||||
// TODO: Use a function instead of a closure to make this more testable? What about a function call inside the closure to take the theme variable into account?
|
// TODO: Use a function instead of a closure to make this more testable? What about a function call inside the closure to take the theme variable into account?
|
||||||
return filepath.Walk("./themes/"+theme.Name+"/public", func(path string, f os.FileInfo, err error) error {
|
return filepath.Walk("./themes/"+theme.Name+"/public", func(path string, f os.FileInfo, err error) error {
|
||||||
debugLog("Attempting to add static file '" + path + "' for default theme '" + theme.Name + "'")
|
debugLog("Attempting to add static file '" + path + "' for default theme '" + theme.Name + "'")
|
||||||
|
@ -230,14 +233,14 @@ func AddThemeStaticFiles(theme Theme) error {
|
||||||
|
|
||||||
path = strings.TrimPrefix(path, "themes/"+theme.Name+"/public")
|
path = strings.TrimPrefix(path, "themes/"+theme.Name+"/public")
|
||||||
gzipData := compressBytesGzip(data)
|
gzipData := compressBytesGzip(data)
|
||||||
StaticFiles["/static/"+theme.Name+path] = SFile{data, gzipData, 0, int64(len(data)), int64(len(gzipData)), mime.TypeByExtension(ext), f, f.ModTime().UTC().Format(http.TimeFormat)}
|
StaticFiles.Set("/static/"+theme.Name+path, SFile{data, gzipData, 0, int64(len(data)), int64(len(gzipData)), mime.TypeByExtension(ext), f, f.ModTime().UTC().Format(http.TimeFormat)})
|
||||||
|
|
||||||
debugLog("Added the '/" + theme.Name + path + "' static file for theme " + theme.Name + ".")
|
debugLog("Added the '/" + theme.Name + path + "' static file for theme " + theme.Name + ".")
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func MapThemeTemplates(theme Theme) {
|
func (theme *Theme) MapTemplates() {
|
||||||
if theme.Templates != nil {
|
if theme.Templates != nil {
|
||||||
for _, themeTmpl := range theme.Templates {
|
for _, themeTmpl := range theme.Templates {
|
||||||
if themeTmpl.Name == "" {
|
if themeTmpl.Name == "" {
|
||||||
|
|
|
@ -22,9 +22,6 @@ go get -u gopkg.in/sourcemap.v1
|
||||||
echo "Installing OttoJS"
|
echo "Installing OttoJS"
|
||||||
go get -u github.com/robertkrimen/otto
|
go get -u github.com/robertkrimen/otto
|
||||||
|
|
||||||
echo "Installing the Riot Search Engine"
|
|
||||||
go get -u github.com/robertkrimen/otto
|
|
||||||
|
|
||||||
echo "Installing the Rez Image Resizer"
|
echo "Installing the Rez Image Resizer"
|
||||||
go get -u github.com/bamiaux/rez
|
go get -u github.com/bamiaux/rez
|
||||||
|
|
||||||
|
|
|
@ -71,13 +71,6 @@ if %errorlevel% neq 0 (
|
||||||
exit /b %errorlevel%
|
exit /b %errorlevel%
|
||||||
)
|
)
|
||||||
|
|
||||||
echo Installing the Riot Search Engine
|
|
||||||
go get -u github.com/go-ego/riot
|
|
||||||
if %errorlevel% neq 0 (
|
|
||||||
pause
|
|
||||||
exit /b %errorlevel%
|
|
||||||
)
|
|
||||||
|
|
||||||
echo Installing the Rez Image Resizer
|
echo Installing the Rez Image Resizer
|
||||||
go get -u github.com/bamiaux/rez
|
go get -u github.com/bamiaux/rez
|
||||||
if %errorlevel% neq 0 (
|
if %errorlevel% neq 0 (
|
||||||
|
|
65
main.go
65
main.go
|
@ -13,10 +13,12 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
//"runtime/pprof"
|
//"runtime/pprof"
|
||||||
"./common"
|
"./common"
|
||||||
|
"github.com/fsnotify/fsnotify"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version = common.Version{Major: 0, Minor: 1, Patch: 0, Tag: "dev"}
|
var version = common.Version{Major: 0, Minor: 1, Patch: 0, Tag: "dev"}
|
||||||
|
@ -55,13 +57,11 @@ func afterDBInit() (err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Print("Initialising the widgets")
|
log.Print("Initialising the widgets")
|
||||||
err = common.InitWidgets()
|
err = common.InitWidgets()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Print("Initialising the authentication system")
|
log.Print("Initialising the authentication system")
|
||||||
common.Auth, err = common.NewDefaultAuth()
|
common.Auth, err = common.NewDefaultAuth()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -72,12 +72,10 @@ func afterDBInit() (err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
common.ModLogs, err = common.NewModLogStore()
|
common.ModLogs, err = common.NewModLogStore()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
common.AdminLogs, err = common.NewAdminLogStore()
|
common.AdminLogs, err = common.NewAdminLogStore()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -158,6 +156,65 @@ func main() {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
watcher, err := fsnotify.NewWatcher()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer watcher.Close()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case event := <-watcher.Events:
|
||||||
|
log.Println("event:", event)
|
||||||
|
if event.Op&fsnotify.Write == fsnotify.Write {
|
||||||
|
log.Println("modified file:", event.Name)
|
||||||
|
var pathBits = strings.Split(event.Name, "\\")
|
||||||
|
if len(pathBits) > 0 {
|
||||||
|
if pathBits[0] == "themes" {
|
||||||
|
var themeName string
|
||||||
|
if len(pathBits) >= 1 {
|
||||||
|
themeName = pathBits[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(pathBits) >= 2 && pathBits[2] == "public" {
|
||||||
|
// TODO: Handle new themes freshly plopped into the folder?
|
||||||
|
theme, ok := common.Themes[themeName]
|
||||||
|
if ok {
|
||||||
|
err = theme.LoadStaticFiles()
|
||||||
|
if err != nil {
|
||||||
|
common.LogError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if event.Op&fsnotify.Create == fsnotify.Create {
|
||||||
|
log.Println("new file:", event.Name)
|
||||||
|
}
|
||||||
|
case err := <-watcher.Errors:
|
||||||
|
log.Println("error:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// TODO: Keep tabs on the theme stuff, and the langpacks
|
||||||
|
err = watcher.Add("./public")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
err = watcher.Add("./templates")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, theme := range common.Themes {
|
||||||
|
err = watcher.Add("./themes/" + theme.Name + "/public")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Run this goroutine once a second
|
// Run this goroutine once a second
|
||||||
secondTicker := time.NewTicker(1 * time.Second)
|
secondTicker := time.NewTicker(1 * time.Second)
|
||||||
fifteenMinuteTicker := time.NewTicker(15 * time.Minute)
|
fifteenMinuteTicker := time.NewTicker(15 * time.Minute)
|
||||||
|
|
|
@ -42,7 +42,6 @@ func routeTopicCreate(w http.ResponseWriter, r *http.Request, user common.User,
|
||||||
return common.NoPermissions(w, r, user)
|
return common.NoPermissions(w, r, user)
|
||||||
}
|
}
|
||||||
headerVars.Zone = "create_topic"
|
headerVars.Zone = "create_topic"
|
||||||
headerVars.Writer = w
|
|
||||||
|
|
||||||
// Lock this to the forum being linked?
|
// Lock this to the forum being linked?
|
||||||
// Should we always put it in strictmode when it's linked from another forum? Well, the user might end up changing their mind on what forum they want to post in and it would be a hassle, if they had to switch pages, even if it is a single click for many (exc. mobile)
|
// Should we always put it in strictmode when it's linked from another forum? Well, the user might end up changing their mind on what forum they want to post in and it would be a hassle, if they had to switch pages, even if it is a single click for many (exc. mobile)
|
||||||
|
|
|
@ -1473,7 +1473,7 @@ func routePanelThemes(w http.ResponseWriter, r *http.Request, user common.User)
|
||||||
return common.NoPermissions(w, r, user)
|
return common.NoPermissions(w, r, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
var pThemeList, vThemeList []common.Theme
|
var pThemeList, vThemeList []*common.Theme
|
||||||
for _, theme := range common.Themes {
|
for _, theme := range common.Themes {
|
||||||
if theme.HideFromThemes {
|
if theme.HideFromThemes {
|
||||||
continue
|
continue
|
||||||
|
@ -1557,7 +1557,7 @@ func routePanelThemesSetDefault(w http.ResponseWriter, r *http.Request, user com
|
||||||
|
|
||||||
common.DefaultThemeBox.Store(uname)
|
common.DefaultThemeBox.Store(uname)
|
||||||
common.ResetTemplateOverrides()
|
common.ResetTemplateOverrides()
|
||||||
common.MapThemeTemplates(theme)
|
theme.MapTemplates()
|
||||||
common.ChangeDefaultThemeMutex.Unlock()
|
common.ChangeDefaultThemeMutex.Unlock()
|
||||||
|
|
||||||
http.Redirect(w, r, "/panel/themes/", http.StatusSeeOther)
|
http.Redirect(w, r, "/panel/themes/", http.StatusSeeOther)
|
||||||
|
|
22
routes.go
22
routes.go
|
@ -42,8 +42,7 @@ func (red *HTTPSRedirect) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
// GET functions
|
// GET functions
|
||||||
func routeStatic(w http.ResponseWriter, r *http.Request) {
|
func routeStatic(w http.ResponseWriter, r *http.Request) {
|
||||||
//log.Print("Outputting static file '" + r.URL.Path + "'")
|
file, ok := common.StaticFiles.Get(r.URL.Path)
|
||||||
file, ok := common.StaticFiles[r.URL.Path]
|
|
||||||
if !ok {
|
if !ok {
|
||||||
if common.Dev.DebugMode {
|
if common.Dev.DebugMode {
|
||||||
log.Printf("Failed to find '%s'", r.URL.Path)
|
log.Printf("Failed to find '%s'", r.URL.Path)
|
||||||
|
@ -61,11 +60,8 @@ func routeStatic(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
h.Set("Last-Modified", file.FormattedModTime)
|
h.Set("Last-Modified", file.FormattedModTime)
|
||||||
h.Set("Content-Type", file.Mimetype)
|
h.Set("Content-Type", file.Mimetype)
|
||||||
//Cache-Control: max-age=31536000
|
h.Set("Cache-Control", cacheControlMaxAge) //Cache-Control: max-age=31536000
|
||||||
h.Set("Cache-Control", cacheControlMaxAge)
|
|
||||||
h.Set("Vary", "Accept-Encoding")
|
h.Set("Vary", "Accept-Encoding")
|
||||||
//http.ServeContent(w,r,r.URL.Path,file.Info.ModTime(),file)
|
|
||||||
//w.Write(file.Data)
|
|
||||||
if strings.Contains(h.Get("Accept-Encoding"), "gzip") {
|
if strings.Contains(h.Get("Accept-Encoding"), "gzip") {
|
||||||
h.Set("Content-Encoding", "gzip")
|
h.Set("Content-Encoding", "gzip")
|
||||||
h.Set("Content-Length", strconv.FormatInt(file.GzipLength, 10))
|
h.Set("Content-Length", strconv.FormatInt(file.GzipLength, 10))
|
||||||
|
@ -74,15 +70,9 @@ func routeStatic(w http.ResponseWriter, r *http.Request) {
|
||||||
h.Set("Content-Length", strconv.FormatInt(file.Length, 10)) // Avoid doing a type conversion every time?
|
h.Set("Content-Length", strconv.FormatInt(file.Length, 10)) // Avoid doing a type conversion every time?
|
||||||
io.Copy(w, bytes.NewReader(file.Data))
|
io.Copy(w, bytes.NewReader(file.Data))
|
||||||
}
|
}
|
||||||
//io.CopyN(w, bytes.NewReader(file.Data), staticFiles[r.URL.Path].Length)
|
// Other options instead of io.Copy: io.CopyN(), w.Write(), http.ServeContent()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deprecated: Test route for stopping the server during a performance analysis
|
|
||||||
/*func routeExit(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError{
|
|
||||||
db.Close()
|
|
||||||
os.Exit(0)
|
|
||||||
}*/
|
|
||||||
|
|
||||||
// TODO: Make this a static file somehow? Is it possible for us to put this file somewhere else?
|
// TODO: Make this a static file somehow? Is it possible for us to put this file somewhere else?
|
||||||
// TODO: Add a sitemap
|
// TODO: Add a sitemap
|
||||||
// TODO: Add an API so that plugins can register disallowed areas. E.g. /guilds/join for plugin_guilds
|
// TODO: Add an API so that plugins can register disallowed areas. E.g. /guilds/join for plugin_guilds
|
||||||
|
@ -102,7 +92,6 @@ func routeOverview(w http.ResponseWriter, r *http.Request, user common.User) com
|
||||||
return ferr
|
return ferr
|
||||||
}
|
}
|
||||||
headerVars.Zone = "overview"
|
headerVars.Zone = "overview"
|
||||||
headerVars.Writer = w
|
|
||||||
|
|
||||||
pi := common.Page{common.GetTitlePhrase("overview"), user, headerVars, tList, nil}
|
pi := common.Page{common.GetTitlePhrase("overview"), user, headerVars, tList, nil}
|
||||||
if common.PreRenderHooks["pre_render_overview"] != nil {
|
if common.PreRenderHooks["pre_render_overview"] != nil {
|
||||||
|
@ -129,7 +118,6 @@ func routeCustomPage(w http.ResponseWriter, r *http.Request, user common.User) c
|
||||||
return common.NotFound(w, r)
|
return common.NotFound(w, r)
|
||||||
}
|
}
|
||||||
headerVars.Zone = "custom_page"
|
headerVars.Zone = "custom_page"
|
||||||
headerVars.Writer = w
|
|
||||||
|
|
||||||
pi := common.Page{common.GetTitlePhrase("page"), user, headerVars, tList, nil}
|
pi := common.Page{common.GetTitlePhrase("page"), user, headerVars, tList, nil}
|
||||||
if common.PreRenderHooks["pre_render_custom_page"] != nil {
|
if common.PreRenderHooks["pre_render_custom_page"] != nil {
|
||||||
|
@ -151,7 +139,6 @@ func routeTopics(w http.ResponseWriter, r *http.Request, user common.User) commo
|
||||||
return ferr
|
return ferr
|
||||||
}
|
}
|
||||||
headerVars.Zone = "topics"
|
headerVars.Zone = "topics"
|
||||||
headerVars.Writer = w
|
|
||||||
|
|
||||||
// TODO: Add a function for the qlist stuff
|
// TODO: Add a function for the qlist stuff
|
||||||
var qlist string
|
var qlist string
|
||||||
|
@ -337,7 +324,6 @@ func routeForum(w http.ResponseWriter, r *http.Request, user common.User, sfid s
|
||||||
return common.InternalError(err, w, r)
|
return common.InternalError(err, w, r)
|
||||||
}
|
}
|
||||||
headerVars.Zone = "view_forum"
|
headerVars.Zone = "view_forum"
|
||||||
headerVars.Writer = w
|
|
||||||
|
|
||||||
// Calculate the offset
|
// Calculate the offset
|
||||||
var offset int
|
var offset int
|
||||||
|
@ -424,7 +410,6 @@ func routeForums(w http.ResponseWriter, r *http.Request, user common.User) commo
|
||||||
return ferr
|
return ferr
|
||||||
}
|
}
|
||||||
headerVars.Zone = "forums"
|
headerVars.Zone = "forums"
|
||||||
headerVars.Writer = w
|
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
var forumList []common.Forum
|
var forumList []common.Forum
|
||||||
|
@ -514,7 +499,6 @@ func routeTopicID(w http.ResponseWriter, r *http.Request, user common.User) comm
|
||||||
return common.NoPermissions(w, r, user)
|
return common.NoPermissions(w, r, user)
|
||||||
}
|
}
|
||||||
headerVars.Zone = "view_topic"
|
headerVars.Zone = "view_topic"
|
||||||
headerVars.Writer = w
|
|
||||||
|
|
||||||
topic.ContentHTML = common.ParseMessage(topic.Content, topic.ParentID, "forums")
|
topic.ContentHTML = common.ParseMessage(topic.Content, topic.ParentID, "forums")
|
||||||
topic.ContentLines = strings.Count(topic.Content, "\n")
|
topic.ContentLines = strings.Count(topic.Content, "\n")
|
||||||
|
|
|
@ -495,6 +495,12 @@ select, input, textarea, button {
|
||||||
background-color: var(--element-background-color);
|
background-color: var(--element-background-color);
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
}
|
}
|
||||||
|
/* TODO: Refactor bgavatars to make the avatars rounded */
|
||||||
|
.rowlist.bgavatars .rowitem {
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: 40px;
|
||||||
|
padding-left: 46px;
|
||||||
|
}
|
||||||
|
|
||||||
.topic_list .rowtopic {
|
.topic_list .rowtopic {
|
||||||
font-size: 17px;
|
font-size: 17px;
|
||||||
|
@ -959,4 +965,41 @@ select, input, textarea, button {
|
||||||
.topic_inner_right {
|
.topic_inner_right {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@media(max-width: 520px) {
|
||||||
|
.topic_list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.topic_list .topic_row {
|
||||||
|
display: block;
|
||||||
|
width: calc(50% - 6px);
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
.topic_list .topic_row:nth-child(odd) {
|
||||||
|
margin-right: 12px;
|
||||||
|
}
|
||||||
|
.topic_left {
|
||||||
|
margin-bottom: 0px;
|
||||||
|
border-bottom: none;
|
||||||
|
border-right: 1px solid var(--element-border-color);
|
||||||
|
}
|
||||||
|
.topic_left .parent_forum {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.topic_right.rowitem {
|
||||||
|
border-top: none;
|
||||||
|
border-left: 1px solid var(--element-border-color);
|
||||||
|
background-color: hsl(0,0%,90%);
|
||||||
|
}
|
||||||
|
.topic_right br, .topic_right img {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.topic_right.topic_sticky {
|
||||||
|
border-bottom: 2px solid var(--element-border-color);
|
||||||
|
}
|
||||||
|
.topic_right > span {
|
||||||
|
margin-top: 6px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -6,7 +6,6 @@
|
||||||
"URL": "github.com/Azareal/Gosora",
|
"URL": "github.com/Azareal/Gosora",
|
||||||
"Tag": "WIP",
|
"Tag": "WIP",
|
||||||
"Docks":["rightSidebar","footer"],
|
"Docks":["rightSidebar","footer"],
|
||||||
"AboutSegment":true,
|
|
||||||
"Templates": [
|
"Templates": [
|
||||||
{
|
{
|
||||||
"Name": "topic",
|
"Name": "topic",
|
||||||
|
|
|
@ -22,9 +22,6 @@ go get -u gopkg.in/sourcemap.v1
|
||||||
echo "Updating OttoJS"
|
echo "Updating OttoJS"
|
||||||
go get -u github.com/robertkrimen/otto
|
go get -u github.com/robertkrimen/otto
|
||||||
|
|
||||||
echo "Updating the Riot Search Engine"
|
|
||||||
go get -u github.com/go-ego/riot
|
|
||||||
|
|
||||||
echo "Updating the Rez Image Resizer"
|
echo "Updating the Rez Image Resizer"
|
||||||
go get -u github.com/bamiaux/rez
|
go get -u github.com/bamiaux/rez
|
||||||
|
|
||||||
|
|
|
@ -68,13 +68,6 @@ if %errorlevel% neq 0 (
|
||||||
exit /b %errorlevel%
|
exit /b %errorlevel%
|
||||||
)
|
)
|
||||||
|
|
||||||
echo Updating the Riot Search Engine
|
|
||||||
go get -u github.com/go-ego/riot
|
|
||||||
if %errorlevel% neq 0 (
|
|
||||||
pause
|
|
||||||
exit /b %errorlevel%
|
|
||||||
)
|
|
||||||
|
|
||||||
echo Updating the Rez Image Resizer
|
echo Updating the Rez Image Resizer
|
||||||
go get -u github.com/bamiaux/rez
|
go get -u github.com/bamiaux/rez
|
||||||
if %errorlevel% neq 0 (
|
if %errorlevel% neq 0 (
|
||||||
|
|
Loading…
Reference in New Issue