Experimenting with Nox's Control Panel.

Experimenting with better cache busting for static resources.
HTTPSRedirect requests are now counted in the route analytics.
More scripts are loaded asynchronously now.
Upped the default ReadTimeout to eight seconds.
Reduce the number of unneccesary NewAcc calls.
Added panel_before_head as an injection point for themes.
Themes can now declare scripts to be loaded asynchronously.
Tweaked the WS resumption algorithm to mae the backoffs a little less aggressive.
Fixed an ordering issue in the WS resumption algorithm where backoffs weren't expiring as fast as they should have.
Fixed a bug where template logs weren't being written due to a panic.
You can now use byte slices in more places in the transpiled templates.
Fixed a bug where Cosora's misc.js seemed to be erroring out.
Fixed a bug where YT embeds were getting blocked by the CSP.

Added the panel_back_to_site phrase.
Added the panel_welcome phrase.
This commit is contained in:
Azareal 2019-03-22 08:59:41 +10:00
parent 3320cb4697
commit 660f24acff
74 changed files with 375 additions and 201 deletions

View File

@ -96,12 +96,11 @@ var langCodes = []string{
type DefaultLangViewCounter struct { type DefaultLangViewCounter struct {
buckets []*RWMutexCounterBucket //[OSID]count buckets []*RWMutexCounterBucket //[OSID]count
codesToIndices map[string]int codesToIndices map[string]int
insert *sql.Stmt
insert *sql.Stmt
} }
func NewDefaultLangViewCounter() (*DefaultLangViewCounter, error) { func NewDefaultLangViewCounter(acc *qgen.Accumulator) (*DefaultLangViewCounter, error) {
acc := qgen.NewAcc()
var langBuckets = make([]*RWMutexCounterBucket, len(langCodes)) var langBuckets = make([]*RWMutexCounterBucket, len(langCodes))
for bucketID, _ := range langBuckets { for bucketID, _ := range langBuckets {
langBuckets[bucketID] = &RWMutexCounterBucket{counter: 0} langBuckets[bucketID] = &RWMutexCounterBucket{counter: 0}

View File

@ -12,8 +12,7 @@ type DefaultRouteViewCounter struct {
insert *sql.Stmt insert *sql.Stmt
} }
func NewDefaultRouteViewCounter() (*DefaultRouteViewCounter, error) { func NewDefaultRouteViewCounter(acc *qgen.Accumulator) (*DefaultRouteViewCounter, error) {
acc := qgen.NewAcc()
var routeBuckets = make([]*RWMutexCounterBucket, len(routeMapEnum)) var routeBuckets = make([]*RWMutexCounterBucket, len(routeMapEnum))
for bucketID, _ := range routeBuckets { for bucketID, _ := range routeBuckets {
routeBuckets[bucketID] = &RWMutexCounterBucket{counter: 0} routeBuckets[bucketID] = &RWMutexCounterBucket{counter: 0}

View File

@ -14,8 +14,7 @@ type DefaultOSViewCounter struct {
insert *sql.Stmt insert *sql.Stmt
} }
func NewDefaultOSViewCounter() (*DefaultOSViewCounter, error) { func NewDefaultOSViewCounter(acc *qgen.Accumulator) (*DefaultOSViewCounter, error) {
acc := qgen.NewAcc()
var osBuckets = make([]*RWMutexCounterBucket, len(osMapEnum)) var osBuckets = make([]*RWMutexCounterBucket, len(osMapEnum))
for bucketID, _ := range osBuckets { for bucketID, _ := range osBuckets {
osBuckets[bucketID] = &RWMutexCounterBucket{counter: 0} osBuckets[bucketID] = &RWMutexCounterBucket{counter: 0}

View File

@ -27,7 +27,7 @@ var staticFileMutex sync.RWMutex
type SFile struct { type SFile struct {
Data []byte Data []byte
GzipData []byte GzipData []byte
Sha256 []byte Sha256 string
Pos int64 Pos int64
Length int64 Length int64
GzipLength int64 GzipLength int64
@ -240,7 +240,7 @@ func (list SFileList) JSTmplInit() error {
// Get a checksum for CSPs and cache busting // Get a checksum for CSPs and cache busting
hasher := sha256.New() hasher := sha256.New()
hasher.Write(data) hasher.Write(data)
checksum := []byte(hex.EncodeToString(hasher.Sum(nil))) checksum := hex.EncodeToString(hasher.Sum(nil))
list.Set("/static/"+path, SFile{data, gzipData, checksum, 0, int64(len(data)), int64(len(gzipData)), mime.TypeByExtension(ext), f, f.ModTime().UTC().Format(http.TimeFormat)}) list.Set("/static/"+path, SFile{data, gzipData, checksum, 0, int64(len(data)), int64(len(gzipData)), mime.TypeByExtension(ext), f, f.ModTime().UTC().Format(http.TimeFormat)})
@ -267,7 +267,7 @@ func (list SFileList) Init() error {
// Get a checksum for CSPs and cache busting // Get a checksum for CSPs and cache busting
hasher := sha256.New() hasher := sha256.New()
hasher.Write(data) hasher.Write(data)
checksum := []byte(hex.EncodeToString(hasher.Sum(nil))) checksum := hex.EncodeToString(hasher.Sum(nil))
// Avoid double-compressing images // Avoid double-compressing images
var gzipData []byte var gzipData []byte
@ -318,7 +318,7 @@ func (list SFileList) Add(path string, prefix string) error {
// Get a checksum for CSPs and cache busting // Get a checksum for CSPs and cache busting
hasher := sha256.New() hasher := sha256.New()
hasher.Write(data) hasher.Write(data)
checksum := []byte(hex.EncodeToString(hasher.Sum(nil))) checksum := hex.EncodeToString(hasher.Sum(nil))
list.Set("/static"+path, SFile{data, gzipData, checksum, 0, int64(len(data)), int64(len(gzipData)), mime.TypeByExtension(ext), f, f.ModTime().UTC().Format(http.TimeFormat)}) list.Set("/static"+path, SFile{data, gzipData, checksum, 0, int64(len(data)), int64(len(gzipData)), mime.TypeByExtension(ext), f, f.ModTime().UTC().Format(http.TimeFormat)})

View File

@ -10,14 +10,20 @@ import (
"github.com/Azareal/Gosora/common/phrases" "github.com/Azareal/Gosora/common/phrases"
) )
type HResource struct {
Name string
Hash string
}
// TODO: Allow resources in spots other than /static/ and possibly even external domains (e.g. CDNs) // TODO: Allow resources in spots other than /static/ and possibly even external domains (e.g. CDNs)
// TODO: Preload Trumboyg on Cosora on the forum list // TODO: Preload Trumboyg on Cosora on the forum list
type Header struct { type Header struct {
Title string Title string
//Title []byte // Experimenting with []byte for increased efficiency, let's avoid converting too many things to []byte, as it involves a lot of extra boilerplate //Title []byte // Experimenting with []byte for increased efficiency, let's avoid converting too many things to []byte, as it involves a lot of extra boilerplate
NoticeList []string NoticeList []string
Scripts []string Scripts []HResource
PreScriptsAsync []string PreScriptsAsync []HResource
ScriptsAsync []HResource
//Preload []string //Preload []string
Stylesheets []string Stylesheets []string
Widgets PageWidgets Widgets PageWidgets
@ -44,11 +50,41 @@ type Header struct {
} }
func (header *Header) AddScript(name string) { func (header *Header) AddScript(name string) {
header.Scripts = append(header.Scripts, name) fname := "/static/" + name
var hash string
if fname[0] == '/' && fname[1] != '/' {
file, ok := StaticFiles.Get(fname)
if ok {
hash = file.Sha256
}
}
//log.Print("name:", name)
//log.Print("hash:", hash)
header.Scripts = append(header.Scripts, HResource{name, hash})
} }
func (header *Header) AddPreScriptAsync(name string) { func (header *Header) AddPreScriptAsync(name string) {
header.PreScriptsAsync = append(header.PreScriptsAsync, name) fname := "/static/" + name
var hash string
if fname[0] == '/' && fname[1] != '/' {
file, ok := StaticFiles.Get(fname)
if ok {
hash = file.Sha256
}
}
header.PreScriptsAsync = append(header.PreScriptsAsync, HResource{name, hash})
}
func (header *Header) AddScriptAsync(name string) {
fname := "/static/" + name
var hash string
if fname[0] == '/' && fname[1] != '/' {
file, ok := StaticFiles.Get(fname)
if ok {
hash = file.Sha256
}
}
header.ScriptsAsync = append(header.ScriptsAsync, HResource{name, hash})
} }
/*func (header *Header) Preload(name string) { /*func (header *Header) Preload(name string) {

View File

@ -131,7 +131,11 @@ func panelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (header
if ext == "css" { if ext == "css" {
header.AddSheet(resource.Name) header.AddSheet(resource.Name)
} else if ext == "js" { } else if ext == "js" {
header.AddScript(resource.Name) if resource.Async {
header.AddScriptAsync(resource.Name)
} else {
header.AddScript(resource.Name)
}
} }
} }
} }
@ -229,7 +233,11 @@ func userCheck(w http.ResponseWriter, r *http.Request, user *User) (header *Head
if ext == "css" { if ext == "css" {
header.AddSheet(resource.Name) header.AddSheet(resource.Name)
} else if ext == "js" { } else if ext == "js" {
header.AddScript(resource.Name) if resource.Async {
header.AddScriptAsync(resource.Name)
} else {
header.AddScript(resource.Name)
}
} }
} }
} }

View File

@ -109,8 +109,9 @@ func tmplInitHeaders(user User, user2 User, user3 User) (*Header, *Header, *Head
CurrentUser: user, CurrentUser: user,
NoticeList: []string{"test"}, NoticeList: []string{"test"},
Stylesheets: []string{"panel.css"}, Stylesheets: []string{"panel.css"},
Scripts: []string{"whatever.js"}, Scripts: []HResource{HResource{"whatever.js", "d"}},
PreScriptsAsync: []string{"whatever.js"}, PreScriptsAsync: []HResource{HResource{"whatever.js", "d"}},
ScriptsAsync: []HResource{HResource{"whatever.js", "d"}},
Widgets: PageWidgets{ Widgets: PageWidgets{
LeftSidebar: template.HTML("lalala"), LeftSidebar: template.HTML("lalala"),
}, },

View File

@ -2,11 +2,13 @@ package tmpl
import ( import (
"bytes" "bytes"
"fmt"
"io/ioutil" "io/ioutil"
"log" "log"
"os" "os"
"path/filepath" "path/filepath"
"reflect" "reflect"
"runtime/debug"
"strconv" "strconv"
"strings" "strings"
"text/template/parse" "text/template/parse"
@ -70,7 +72,8 @@ type CTemplateSet struct {
themeName string themeName string
perThemeTmpls map[string]bool perThemeTmpls map[string]bool
logger *log.Logger logger *log.Logger
loggerf *os.File
} }
func NewCTemplateSet(in string) *CTemplateSet { func NewCTemplateSet(in string) *CTemplateSet {
@ -110,7 +113,8 @@ func NewCTemplateSet(in string) *CTemplateSet {
"dyntmpl": true, "dyntmpl": true,
"index": true, "index": true,
}, },
logger: log.New(f, "", log.LstdFlags), logger: log.New(f, "", log.LstdFlags),
loggerf: f,
} }
} }
@ -155,6 +159,7 @@ func (c *CTemplateSet) ResetLogs(in string) {
panic(err) panic(err)
} }
c.logger = log.New(f, "", log.LstdFlags) c.logger = log.New(f, "", log.LstdFlags)
c.loggerf = f
} }
type SkipBlock struct { type SkipBlock struct {
@ -268,6 +273,19 @@ func (c *CTemplateSet) Compile(name string, fileDir string, expects string, expe
} }
func (c *CTemplateSet) compile(name string, content string, expects string, expectsInt interface{}, varList map[string]VarItem, imports ...string) (out string, err error) { func (c *CTemplateSet) compile(name string, content string, expects string, expectsInt interface{}, varList map[string]VarItem, imports ...string) (out string, err error) {
defer func() {
r := recover()
if r != nil {
fmt.Println(r)
debug.PrintStack()
err := c.loggerf.Sync()
if err != nil {
fmt.Println(err)
}
log.Fatal("")
return
}
}()
//c.dumpCall("compile", name, content, expects, expectsInt, varList, imports) //c.dumpCall("compile", name, content, expects, expectsInt, varList, imports)
//c.detailf("c: %+v\n", c) //c.detailf("c: %+v\n", c)
c.importMap = map[string]string{} c.importMap = map[string]string{}
@ -1460,6 +1478,16 @@ func (c *CTemplateSet) compileVarSub(con CContext, varname string, val reflect.V
c.addText(con, []byte("false")) c.addText(con, []byte("false"))
con.Push("endelse", "}\n") con.Push("endelse", "}\n")
return return
case reflect.Slice:
if val.Len() == 0 {
c.critical("varname:", varname)
panic("The sample data needs at-least one or more elements for the slices. We're looking into removing this requirement at some point!")
}
item := val.Index(0)
if item.Type().Name() != "uint8" { // uint8 == byte, complicated because it's a type alias
panic("unable to format " + item.Type().Name() + " as text")
}
base = varname
case reflect.String: case reflect.String:
if val.Type().Name() != "string" && !strings.HasPrefix(varname, "string(") { if val.Type().Name() != "string" && !strings.HasPrefix(varname, "string(") {
varname = "string(" + varname + ")" varname = "string(" + varname + ")"

View File

@ -77,6 +77,7 @@ type ThemeResource struct {
Name string Name string
Location string Location string
Loggedin bool // Only serve this resource to logged in users Loggedin bool // Only serve this resource to logged in users
Async bool
} }
type ThemeMapTmplToDock struct { type ThemeMapTmplToDock struct {
@ -162,7 +163,7 @@ func (theme *Theme) AddThemeStaticFiles() error {
// Get a checksum for CSPs and cache busting // Get a checksum for CSPs and cache busting
hasher := sha256.New() hasher := sha256.New()
hasher.Write(data) hasher.Write(data)
checksum := []byte(hex.EncodeToString(hasher.Sum(nil))) checksum := hex.EncodeToString(hasher.Sum(nil))
StaticFiles.Set("/static/"+theme.Name+path, SFile{data, gzipData, checksum, 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, checksum, 0, int64(len(data)), int64(len(gzipData)), mime.TypeByExtension(ext), f, f.ModTime().UTC().Format(http.TimeFormat)})

View File

@ -92,6 +92,12 @@ func RouteWebsockets(w http.ResponseWriter, r *http.Request, user User) RouteErr
currentPage = string(msgblocks[1]) currentPage = string(msgblocks[1])
wsPageResponses(wsUser, conn, currentPage) wsPageResponses(wsUser, conn, currentPage)
} }
} else if bytes.HasPrefix(msg, []byte("resume ")) {
msgblocks := bytes.SplitN(msg, []byte(" "), 2)
if len(msgblocks) < 2 {
continue
}
//log.Print("resuming on " + string(msgblocks[1]))
} }
/*if bytes.Equal(message,[]byte(`start-view`)) { /*if bytes.Equal(message,[]byte(`start-view`)) {
} else if bytes.Equal(message,[]byte(`end-view`)) { } else if bytes.Equal(message,[]byte(`end-view`)) {

View File

@ -84,7 +84,7 @@ MaxTopicTitleLength - The maximum length that a topic can be. Please note that t
MaxUsernameLength - The maximum length that a user's name can be. Please note that this measures the number of bytes and may differ from language to language with it being equal to a letter in English and being two bytes in others. MaxUsernameLength - The maximum length that a user's name can be. Please note that this measures the number of bytes and may differ from language to language with it being equal to a letter in English and being two bytes in others.
ReadTimeout - The number of seconds that we are allowed to take to fully read a request. Defaults to 5. ReadTimeout - The number of seconds that we are allowed to take to fully read a request. Defaults to 8.
WriteTimeout - The number of seconds that a route is allowed to run for before the request is automatically terminated. Defaults to 10. WriteTimeout - The number of seconds that a route is allowed to run for before the request is automatically terminated. Defaults to 10.

View File

@ -164,6 +164,7 @@ var RouteMap = map[string]interface{}{
"routes.RobotsTxt": routes.RobotsTxt, "routes.RobotsTxt": routes.RobotsTxt,
"routes.SitemapXml": routes.SitemapXml, "routes.SitemapXml": routes.SitemapXml,
"routes.BadRoute": routes.BadRoute, "routes.BadRoute": routes.BadRoute,
"routes.HTTPSRedirect": routes.HTTPSRedirect,
} }
// ! NEVER RELY ON THESE REMAINING THE SAME BETWEEN COMMITS // ! NEVER RELY ON THESE REMAINING THE SAME BETWEEN COMMITS
@ -309,6 +310,7 @@ var routeMapEnum = map[string]int{
"routes.RobotsTxt": 138, "routes.RobotsTxt": 138,
"routes.SitemapXml": 139, "routes.SitemapXml": 139,
"routes.BadRoute": 140, "routes.BadRoute": 140,
"routes.HTTPSRedirect": 141,
} }
var reverseRouteMapEnum = map[int]string{ var reverseRouteMapEnum = map[int]string{
0: "routes.Overview", 0: "routes.Overview",
@ -452,6 +454,7 @@ var reverseRouteMapEnum = map[int]string{
138: "routes.RobotsTxt", 138: "routes.RobotsTxt",
139: "routes.SitemapXml", 139: "routes.SitemapXml",
140: "routes.BadRoute", 140: "routes.BadRoute",
141: "routes.HTTPSRedirect",
} }
var osMapEnum = map[string]int{ var osMapEnum = map[string]int{
"unknown": 0, "unknown": 0,
@ -600,6 +603,17 @@ func (writ *WriterIntercept) WriteHeader(code int) {
writ.ResponseWriter.WriteHeader(code) writ.ResponseWriter.WriteHeader(code)
} }
// HTTPSRedirect is a connection handler which redirects all HTTP requests to HTTPS
type HTTPSRedirect struct {
}
func (red *HTTPSRedirect) ServeHTTP(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Connection", "close")
counters.RouteViewCounter.Bump(141)
dest := "https://" + req.Host + req.URL.String()
http.Redirect(w, req, dest, http.StatusTemporaryRedirect)
}
type GenRouter struct { type GenRouter struct {
UploadHandler func(http.ResponseWriter, *http.Request) UploadHandler func(http.ResponseWriter, *http.Request)
extraRoutes map[string]func(http.ResponseWriter, *http.Request, common.User) common.RouteError extraRoutes map[string]func(http.ResponseWriter, *http.Request, common.User) common.RouteError

View File

@ -708,6 +708,8 @@
"option_yes":"Yes", "option_yes":"Yes",
"option_no":"No", "option_no":"No",
"panel_back_to_site":"Back to Site",
"panel_welcome":"Welcome ",
"panel_menu_head":"Control Panel", "panel_menu_head":"Control Panel",
"panel_menu_aria":"The control panel menu", "panel_menu_aria":"The control panel menu",
"panel_menu_users":"Users", "panel_menu_users":"Users",

11
main.go
View File

@ -28,7 +28,6 @@ import (
"github.com/Azareal/Gosora/common/counters" "github.com/Azareal/Gosora/common/counters"
"github.com/Azareal/Gosora/common/phrases" "github.com/Azareal/Gosora/common/phrases"
"github.com/Azareal/Gosora/query_gen" "github.com/Azareal/Gosora/query_gen"
"github.com/Azareal/Gosora/routes"
"github.com/fsnotify/fsnotify" "github.com/fsnotify/fsnotify"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -181,15 +180,15 @@ func afterDBInit() (err error) {
if err != nil { if err != nil {
return errors.WithStack(err) return errors.WithStack(err)
} }
counters.OSViewCounter, err = counters.NewDefaultOSViewCounter() counters.OSViewCounter, err = counters.NewDefaultOSViewCounter(acc)
if err != nil { if err != nil {
return errors.WithStack(err) return errors.WithStack(err)
} }
counters.LangViewCounter, err = counters.NewDefaultLangViewCounter() counters.LangViewCounter, err = counters.NewDefaultLangViewCounter(acc)
if err != nil { if err != nil {
return errors.WithStack(err) return errors.WithStack(err)
} }
counters.RouteViewCounter, err = counters.NewDefaultRouteViewCounter() counters.RouteViewCounter, err = counters.NewDefaultRouteViewCounter(acc)
if err != nil { if err != nil {
return errors.WithStack(err) return errors.WithStack(err)
} }
@ -472,7 +471,7 @@ func startServer() {
var newServer = func(addr string, handler http.Handler) *http.Server { var newServer = func(addr string, handler http.Handler) *http.Server {
rtime := common.Config.ReadTimeout rtime := common.Config.ReadTimeout
if rtime == 0 { if rtime == 0 {
rtime = 5 rtime = 8
} else if rtime == -1 { } else if rtime == -1 {
rtime = 0 rtime = 0
} }
@ -527,7 +526,7 @@ func startServer() {
// TODO: Redirect to port 443 // TODO: Redirect to port 443
go func() { go func() {
log.Print("Listening on port 80") log.Print("Listening on port 80")
common.StoppedServer(newServer(":80", &routes.HTTPSRedirect{}).ListenAndServe()) common.StoppedServer(newServer(":80", &HTTPSRedirect{}).ListenAndServe())
}() }()
} }
log.Printf("Listening on port %s", common.Site.Port) log.Printf("Listening on port %s", common.Site.Port)

View File

@ -59,4 +59,6 @@ function buildStatsChart(rawLabels, seriesData, timeRange, legendNames) {
labels: labels, labels: labels,
series: seriesData, series: seriesData,
}, config); }, config);
} }
runInitHook("analytics_loaded");

View File

@ -193,7 +193,7 @@ function wsAlertEvent(data) {
updateAlertList(generalAlerts/*, alist*/); updateAlertList(generalAlerts/*, alist*/);
} }
function runWebSockets() { function runWebSockets(resume = false) {
if(window.location.protocol == "https:") { if(window.location.protocol == "https:") {
conn = new WebSocket("wss://" + document.location.host + "/ws/"); conn = new WebSocket("wss://" + document.location.host + "/ws/");
} else conn = new WebSocket("ws://" + document.location.host + "/ws/"); } else conn = new WebSocket("ws://" + document.location.host + "/ws/");
@ -206,6 +206,7 @@ function runWebSockets() {
conn.onopen = () => { conn.onopen = () => {
console.log("The WebSockets connection was opened"); console.log("The WebSockets connection was opened");
conn.send("page " + document.location.pathname + '\r'); conn.send("page " + document.location.pathname + '\r');
if(resume) conn.send("resume " + Math.round((new Date()).getTime() / 1000) + '\r');
// TODO: Don't ask again, if it's denied. We could have a setting in the UCP which automatically requests this when someone flips desktop notifications on // TODO: Don't ask again, if it's denied. We could have a setting in the UCP which automatically requests this when someone flips desktop notifications on
if(me.User.ID > 0) Notification.requestPermission(); if(me.User.ID > 0) Notification.requestPermission();
} }
@ -213,23 +214,22 @@ function runWebSockets() {
conn.onclose = () => { conn.onclose = () => {
conn = false; conn = false;
console.log("The WebSockets connection was closed"); console.log("The WebSockets connection was closed");
let backoff = 1000; let backoff = 0.8;
if(wsBackoff < 0) wsBackoff = 0; if(wsBackoff < 0) wsBackoff = 0;
else if(wsBackoff > 12) backoff = 13000; else if(wsBackoff > 12) backoff = 11;
else if(wsBackoff > 5) backoff = 7000; else if(wsBackoff > 5) backoff = 5;
wsBackoff++; wsBackoff++;
setTimeout(() => { setTimeout(() => {
var alertMenuList = document.getElementsByClassName("menu_alerts"); var alertMenuList = document.getElementsByClassName("menu_alerts");
for(var i = 0; i < alertMenuList.length; i++) { for(var i = 0; i < alertMenuList.length; i++) loadAlerts(alertMenuList[i]);
loadAlerts(alertMenuList[i]); runWebSockets(true);
} }, backoff * 60 * 1000);
runWebSockets();
}, 60 * backoff);
if(wsBackoff > 0) { if(wsBackoff > 0) {
if(wsBackoff <= 5) setTimeout(() => wsBackoff--, 60 * 4000); if(wsBackoff <= 5) setTimeout(() => wsBackoff--, 5.5 * 60 * 1000);
else if(wsBackoff <= 12) setTimeout(() => wsBackoff--, 60 * 20000); else if(wsBackoff <= 12) setTimeout(() => wsBackoff--, 11.5 * 60 * 1000);
else setTimeout(() => wsBackoff--, 20 * 60 * 1000);
} }
} }
@ -333,16 +333,14 @@ function runWebSockets() {
notifyOnScriptW("template_alert", (e) => { notifyOnScriptW("template_alert", (e) => {
if(e!=undefined) console.log("failed alert? why?", e) if(e!=undefined) console.log("failed alert? why?", e)
}, () => { }, () => {
console.log("ha") //console.log("ha")
if(!Template_alert) throw("template function not found"); if(!Template_alert) throw("template function not found");
addInitHook("after_phrases", () => { addInitHook("after_phrases", () => {
// TODO: The load part of loadAlerts could be done asynchronously while the update of the DOM could be deferred // TODO: The load part of loadAlerts could be done asynchronously while the update of the DOM could be deferred
$(document).ready(() => { $(document).ready(() => {
alertsInitted = true; alertsInitted = true;
var alertMenuList = document.getElementsByClassName("menu_alerts"); var alertMenuList = document.getElementsByClassName("menu_alerts");
for(var i = 0; i < alertMenuList.length; i++) { for(var i = 0; i < alertMenuList.length; i++) loadAlerts(alertMenuList[i]);
loadAlerts(alertMenuList[i]);
}
if(window["WebSocket"]) runWebSockets(); if(window["WebSocket"]) runWebSockets();
}); });
}); });

View File

@ -4,7 +4,7 @@ var me = {};
var phraseBox = {}; var phraseBox = {};
if(tmplInits===undefined) var tmplInits = {}; if(tmplInits===undefined) var tmplInits = {};
var tmplPhrases = []; // [key] array of phrases indexed by order of use var tmplPhrases = []; // [key] array of phrases indexed by order of use
var hooks = { var hooks = { // Shorten this list by binding the hooks just in time?
"pre_iffe": [], "pre_iffe": [],
"pre_init": [], "pre_init": [],
"start_init": [], "start_init": [],
@ -15,6 +15,7 @@ var hooks = {
"open_edit":[], "open_edit":[],
"close_edit":[], "close_edit":[],
"edit_item_pre_bind":[], "edit_item_pre_bind":[],
"analytics_loaded":[],
}; };
var ranInitHooks = {} var ranInitHooks = {}

View File

@ -174,6 +174,7 @@ func main() {
mapIt("routes.RobotsTxt") mapIt("routes.RobotsTxt")
mapIt("routes.SitemapXml") mapIt("routes.SitemapXml")
mapIt("routes.BadRoute") mapIt("routes.BadRoute")
mapIt("routes.HTTPSRedirect")
tmplVars.AllRouteNames = allRouteNames tmplVars.AllRouteNames = allRouteNames
tmplVars.AllRouteMap = allRouteMap tmplVars.AllRouteMap = allRouteMap
@ -381,6 +382,17 @@ func (writ *WriterIntercept) WriteHeader(code int) {
writ.ResponseWriter.WriteHeader(code) writ.ResponseWriter.WriteHeader(code)
} }
// HTTPSRedirect is a connection handler which redirects all HTTP requests to HTTPS
type HTTPSRedirect struct {
}
func (red *HTTPSRedirect) ServeHTTP(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Connection", "close")
counters.RouteViewCounter.Bump({{ index .AllRouteMap "routes.HTTPSRedirect" }})
dest := "https://" + req.Host + req.URL.String()
http.Redirect(w, req, dest, http.StatusTemporaryRedirect)
}
type GenRouter struct { type GenRouter struct {
UploadHandler func(http.ResponseWriter, *http.Request) UploadHandler func(http.ResponseWriter, *http.Request)
extraRoutes map[string]func(http.ResponseWriter, *http.Request, common.User) common.RouteError extraRoutes map[string]func(http.ResponseWriter, *http.Request, common.User) common.RouteError

View File

@ -29,8 +29,9 @@ func renderTemplate(tmplName string, w http.ResponseWriter, r *http.Request, hea
} }
// TODO: Expand this to non-HTTPS requests too // TODO: Expand this to non-HTTPS requests too
if !header.LooseCSP && common.Site.EnableSsl { if !header.LooseCSP && common.Site.EnableSsl {
w.Header().Set("Content-Security-Policy", "default-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-eval' 'unsafe-inline'; img-src * data: 'unsafe-eval' 'unsafe-inline'; connect-src * 'unsafe-eval' 'unsafe-inline'; upgrade-insecure-requests") w.Header().Set("Content-Security-Policy", "default-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-eval' 'unsafe-inline'; img-src * data: 'unsafe-eval' 'unsafe-inline'; connect-src * 'unsafe-eval' 'unsafe-inline'; frame-src 'self' www.youtube-nocookie.com;upgrade-insecure-requests")
} }
header.AddScript("global.js")
if header.CurrentUser.IsAdmin { if header.CurrentUser.IsAdmin {
header.Elapsed1 = time.Since(header.StartedAt).String() header.Elapsed1 = time.Since(header.StartedAt).String()
} }

View File

@ -125,7 +125,7 @@ func PreAnalyticsDetail(w http.ResponseWriter, r *http.Request, user *common.Use
} }
basePage.AddSheet("chartist/chartist.min.css") basePage.AddSheet("chartist/chartist.min.css")
basePage.AddScript("chartist/chartist.min.js") basePage.AddScript("chartist/chartist.min.js")
basePage.AddScript("analytics.js") basePage.AddScriptAsync("analytics.js")
return basePage, nil return basePage, nil
} }

View File

@ -22,6 +22,7 @@ func successRedirect(dest string, w http.ResponseWriter, r *http.Request, isJs b
} }
func renderTemplate(tmplName string, w http.ResponseWriter, r *http.Request, header *common.Header, pi interface{}) common.RouteError { func renderTemplate(tmplName string, w http.ResponseWriter, r *http.Request, header *common.Header, pi interface{}) common.RouteError {
header.AddScript("global.js")
if common.RunPreRenderHook("pre_render_"+tmplName, w, r, &header.CurrentUser, pi) { if common.RunPreRenderHook("pre_render_"+tmplName, w, r, &header.CurrentUser, pi) {
return nil return nil
} }

View File

@ -1,17 +1,5 @@
package routes package routes
import "net/http"
// HTTPSRedirect is a connection handler which redirects all HTTP requests to HTTPS
type HTTPSRedirect struct {
}
func (red *HTTPSRedirect) ServeHTTP(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Connection", "close")
dest := "https://" + req.Host + req.URL.String()
http.Redirect(w, req, dest, http.StatusTemporaryRedirect)
}
// Temporary stubs for view tracking // Temporary stubs for view tracking
func DynamicRoute() { func DynamicRoute() {
} }
@ -19,3 +7,7 @@ func UploadedFile() {
} }
func BadRoute() { func BadRoute() {
} }
// Real implementation is in router_gen/main.go, this is just a stub to map the analytics onto
func HTTPSRedirect() {
}

View File

@ -6,13 +6,14 @@
{{range .Header.Stylesheets}} {{range .Header.Stylesheets}}
<link href="/static/{{.}}" rel="stylesheet" type="text/css">{{end}} <link href="/static/{{.}}" rel="stylesheet" type="text/css">{{end}}
{{range .Header.PreScriptsAsync}} {{range .Header.PreScriptsAsync}}
<script async type="text/javascript" src="/static/{{.}}"></script>{{end}} <script async type="text/javascript" src="/static/{{.Name}}{{if .Hash}}?h={{.Hash}}{{end}}"></script>{{end}}
<meta property="x-loggedin" content="{{.CurrentUser.Loggedin}}" /> <meta property="x-loggedin" content="{{.CurrentUser.Loggedin}}" />
<script type="text/javascript" src="/static/init.js"></script> <script type="text/javascript" src="/static/init.js"></script>
{{range .Header.ScriptsAsync}}
<script async type="text/javascript" src="/static/{{.Name}}{{if .Hash}}?h={{.Hash}}{{end}}"></script>{{end}}
<script type="text/javascript" src="/static/jquery-3.1.1.min.js"></script> <script type="text/javascript" src="/static/jquery-3.1.1.min.js"></script>
{{range .Header.Scripts}} {{range .Header.Scripts}}
<script type="text/javascript" src="/static/{{.}}"></script>{{end}} <script type="text/javascript" src="/static/{{.Name}}{{if .Hash}}?h={{.Hash}}{{end}}"></script>{{end}}
<script type="text/javascript" src="/static/global.js"></script>
<meta name="viewport" content="width=device-width,initial-scale = 1.0, maximum-scale=1.0,user-scalable=no" /> <meta name="viewport" content="width=device-width,initial-scale = 1.0, maximum-scale=1.0,user-scalable=no" />
{{if .Header.MetaDesc}}<meta name="description" content="{{.Header.MetaDesc}}" />{{end}} {{if .Header.MetaDesc}}<meta name="description" content="{{.Header.MetaDesc}}" />{{end}}
{{/** TODO: Have page / forum / topic level tags and descriptions below as-well **/}} {{/** TODO: Have page / forum / topic level tags and descriptions below as-well **/}}

View File

@ -2,6 +2,7 @@
<div class="colstack panel_stack"> <div class="colstack panel_stack">
{{template "panel_menu.html" . }} {{template "panel_menu.html" . }}
<main class="colstack_right"> <main class="colstack_right">
{{template "panel_before_head.html" . }}
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{lang "panel_logs_administration_head"}}</h1></div> <div class="rowitem"><h1>{{lang "panel_logs_administration_head"}}</h1></div>
</div> </div>

View File

@ -2,6 +2,7 @@
<div class="colstack panel_stack"> <div class="colstack panel_stack">
{{template "panel_menu.html" . }} {{template "panel_menu.html" . }}
<main id="panel_analytics_right" class="colstack_right"> <main id="panel_analytics_right" class="colstack_right">
{{template "panel_before_head.html" . }}
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/agent/{{.Agent}}" method="get"> <form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/agent/{{.Agent}}" method="get">
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"> <div class="rowitem">

View File

@ -2,6 +2,7 @@
<div class="colstack panel_stack"> <div class="colstack panel_stack">
{{template "panel_menu.html" . }} {{template "panel_menu.html" . }}
<main id="panel_analytics_right" class="colstack_right"> <main id="panel_analytics_right" class="colstack_right">
{{template "panel_before_head.html" . }}
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/agents/" method="get"> <form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/agents/" method="get">
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"> <div class="rowitem">

View File

@ -2,6 +2,7 @@
<div class="colstack panel_stack"> <div class="colstack panel_stack">
{{template "panel_menu.html" . }} {{template "panel_menu.html" . }}
<main id="panel_analytics_right" class="colstack_right"> <main id="panel_analytics_right" class="colstack_right">
{{template "panel_before_head.html" . }}
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/forum/{{.Agent}}" method="get"> <form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/forum/{{.Agent}}" method="get">
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"> <div class="rowitem">

View File

@ -2,6 +2,7 @@
<div class="colstack panel_stack"> <div class="colstack panel_stack">
{{template "panel_menu.html" . }} {{template "panel_menu.html" . }}
<main id="panel_analytics_right" class="colstack_right"> <main id="panel_analytics_right" class="colstack_right">
{{template "panel_before_head.html" . }}
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/forums/" method="get"> <form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/forums/" method="get">
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"> <div class="rowitem">

View File

@ -2,6 +2,7 @@
<div class="colstack panel_stack"> <div class="colstack panel_stack">
{{template "panel_menu.html" . }} {{template "panel_menu.html" . }}
<main id="panel_analytics_right" class="colstack_right"> <main id="panel_analytics_right" class="colstack_right">
{{template "panel_before_head.html" . }}
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/lang/{{.Agent}}" method="get"> <form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/lang/{{.Agent}}" method="get">
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"> <div class="rowitem">

View File

@ -2,6 +2,7 @@
<div class="colstack panel_stack"> <div class="colstack panel_stack">
{{template "panel_menu.html" . }} {{template "panel_menu.html" . }}
<main id="panel_analytics_right" class="colstack_right"> <main id="panel_analytics_right" class="colstack_right">
{{template "panel_before_head.html" . }}
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/langs/" method="get"> <form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/langs/" method="get">
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"> <div class="rowitem">

View File

@ -2,6 +2,7 @@
<div class="colstack panel_stack"> <div class="colstack panel_stack">
{{template "panel_menu.html" . }} {{template "panel_menu.html" . }}
<main id="panel_analytics_right" class="colstack_right"> <main id="panel_analytics_right" class="colstack_right">
{{template "panel_before_head.html" . }}
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/posts/" method="get"> <form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/posts/" method="get">
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"> <div class="rowitem">

View File

@ -2,6 +2,7 @@
<div class="colstack panel_stack"> <div class="colstack panel_stack">
{{template "panel_menu.html" . }} {{template "panel_menu.html" . }}
<main id="panel_analytics_right" class="colstack_right"> <main id="panel_analytics_right" class="colstack_right">
{{template "panel_before_head.html" . }}
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/referrer/{{.Agent}}" method="get"> <form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/referrer/{{.Agent}}" method="get">
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"> <div class="rowitem">

View File

@ -2,6 +2,7 @@
<div class="colstack panel_stack"> <div class="colstack panel_stack">
{{template "panel_menu.html" . }} {{template "panel_menu.html" . }}
<main id="panel_analytics_right" class="colstack_right"> <main id="panel_analytics_right" class="colstack_right">
{{template "panel_before_head.html" . }}
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/referrers/" method="get"> <form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/referrers/" method="get">
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"> <div class="rowitem">

View File

@ -2,6 +2,7 @@
<div class="colstack panel_stack"> <div class="colstack panel_stack">
{{template "panel_menu.html" . }} {{template "panel_menu.html" . }}
<main id="panel_analytics_right" class="colstack_right"> <main id="panel_analytics_right" class="colstack_right">
{{template "panel_before_head.html" . }}
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/route/{{.Route}}" method="get"> <form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/route/{{.Route}}" method="get">
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"> <div class="rowitem">

View File

@ -2,6 +2,7 @@
<div class="colstack panel_stack"> <div class="colstack panel_stack">
{{template "panel_menu.html" . }} {{template "panel_menu.html" . }}
<main id="panel_analytics_right" class="colstack_right"> <main id="panel_analytics_right" class="colstack_right">
{{template "panel_before_head.html" . }}
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/routes/" method="get"> <form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/routes/" method="get">
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"> <div class="rowitem">

View File

@ -11,6 +11,10 @@ let legendNames = [{{range .Graph.Legends}}
{{.}},{{end}} {{.}},{{end}}
]; ];
addInitHook("after_phrases", () => { addInitHook("after_phrases", () => {
buildStatsChart(rawLabels, seriesData, "{{.TimeRange}}",legendNames); addInitHook("end_init", () => {
addInitHook("analytics_loaded", () => {
buildStatsChart(rawLabels, seriesData, "{{.TimeRange}}",legendNames);
});
});
}); });
</script> </script>

View File

@ -2,6 +2,7 @@
<div class="colstack panel_stack"> <div class="colstack panel_stack">
{{template "panel_menu.html" . }} {{template "panel_menu.html" . }}
<main id="panel_analytics_right" class="colstack_right"> <main id="panel_analytics_right" class="colstack_right">
{{template "panel_before_head.html" . }}
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/system/{{.Agent}}" method="get"> <form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/system/{{.Agent}}" method="get">
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"> <div class="rowitem">

View File

@ -2,6 +2,7 @@
<div class="colstack panel_stack"> <div class="colstack panel_stack">
{{template "panel_menu.html" . }} {{template "panel_menu.html" . }}
<main id="panel_analytics_right" class="colstack_right"> <main id="panel_analytics_right" class="colstack_right">
{{template "panel_before_head.html" . }}
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/systems/" method="get"> <form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/systems/" method="get">
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"> <div class="rowitem">

View File

@ -2,6 +2,7 @@
<div class="colstack panel_stack"> <div class="colstack panel_stack">
{{template "panel_menu.html" . }} {{template "panel_menu.html" . }}
<main id="panel_analytics_right" class="colstack_right"> <main id="panel_analytics_right" class="colstack_right">
{{template "panel_before_head.html" . }}
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/topics/" method="get"> <form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/topics/" method="get">
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"> <div class="rowitem">

View File

@ -2,6 +2,7 @@
<div class="colstack panel_stack"> <div class="colstack panel_stack">
{{template "panel_menu.html" . }} {{template "panel_menu.html" . }}
<main id="panel_analytics_right" class="colstack_right"> <main id="panel_analytics_right" class="colstack_right">
{{template "panel_before_head.html" . }}
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/views/" method="get"> <form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/views/" method="get">
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"> <div class="rowitem">

View File

@ -2,6 +2,7 @@
<div class="colstack panel_stack"> <div class="colstack panel_stack">
{{template "panel_menu.html" . }} {{template "panel_menu.html" . }}
<main class="colstack_right"> <main class="colstack_right">
{{template "panel_before_head.html" . }}
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{lang "areyousure_head"}}</h1></div> <div class="rowitem"><h1>{{lang "areyousure_head"}}</h1></div>
</div> </div>

View File

@ -2,6 +2,7 @@
<div class="colstack panel_stack"> <div class="colstack panel_stack">
{{template "panel_menu.html" . }} {{template "panel_menu.html" . }}
<main class="colstack_right"> <main class="colstack_right">
{{template "panel_before_head.html" . }}
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{lang "panel_backups_head"}}</h1></div> <div class="rowitem"><h1>{{lang "panel_backups_head"}}</h1></div>
</div> </div>

View File

View File

@ -2,6 +2,7 @@
<div class="colstack panel_stack"> <div class="colstack panel_stack">
{{template "panel_menu.html" . }} {{template "panel_menu.html" . }}
<main id="panel_dashboard_right" class="colstack_right"> <main id="panel_dashboard_right" class="colstack_right">
{{template "panel_before_head.html" . }}
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{lang "panel_dashboard_head"}}</h1></div> <div class="rowitem"><h1>{{lang "panel_dashboard_head"}}</h1></div>
</div> </div>

View File

@ -2,6 +2,7 @@
<div class="colstack panel_stack"> <div class="colstack panel_stack">
{{template "panel_menu.html" . }} {{template "panel_menu.html" . }}
<main id="panel_dashboard_right" class="colstack_right"> <main id="panel_dashboard_right" class="colstack_right">
{{template "panel_before_head.html" . }}
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{lang "panel_debug_head"}}</h1></div> <div class="rowitem"><h1>{{lang "panel_debug_head"}}</h1></div>
</div> </div>

View File

@ -7,6 +7,7 @@ var formVars = {'perm_preset': ['can_moderate','can_post','read_only','no_access
</script> </script>
<main class="colstack_right"> <main class="colstack_right">
{{template "panel_before_head.html" . }}
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{.Name}}{{lang "panel_forum_head_suffix"}}</h1></div> <div class="rowitem"><h1>{{.Name}}{{lang "panel_forum_head_suffix"}}</h1></div>
</div> </div>

View File

@ -3,6 +3,7 @@
{{template "panel_menu.html" . }} {{template "panel_menu.html" . }}
<main class="colstack_right"> <main class="colstack_right">
{{template "panel_before_head.html" . }}
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{.Name}}{{lang "panel_forum_head_suffix"}}</h1></div> <div class="rowitem"><h1>{{.Name}}{{lang "panel_forum_head_suffix"}}</h1></div>
</div> </div>

View File

@ -8,6 +8,7 @@
</script> </script>
<main class="colstack_right"> <main class="colstack_right">
{{template "panel_before_head.html" . }}
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{lang "panel_forums_head"}}</h1></div> <div class="rowitem"><h1>{{lang "panel_forums_head"}}</h1></div>
</div> </div>

View File

@ -2,6 +2,7 @@
<div class="colstack panel_stack"> <div class="colstack panel_stack">
{{template "panel_group_menu.html" . }} {{template "panel_group_menu.html" . }}
<main class="colstack_right"> <main class="colstack_right">
{{template "panel_before_head.html" . }}
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{.Name}}{{lang "panel_group_head_suffix"}}</h1></div> <div class="rowitem"><h1>{{.Name}}{{lang "panel_group_head_suffix"}}</h1></div>
</div> </div>

View File

@ -2,6 +2,7 @@
<div class="colstack panel_stack"> <div class="colstack panel_stack">
{{template "panel_group_menu.html" . }} {{template "panel_group_menu.html" . }}
<main class="colstack_right"> <main class="colstack_right">
{{template "panel_before_head.html" . }}
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{.Name}}{{lang "panel_group_head_suffix"}}</h1></div> <div class="rowitem"><h1>{{.Name}}{{lang "panel_group_head_suffix"}}</h1></div>
</div> </div>

View File

@ -3,6 +3,7 @@
{{template "panel_menu.html" . }} {{template "panel_menu.html" . }}
<main class="colstack_right"> <main class="colstack_right">
{{template "panel_before_head.html" . }}
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{lang "panel_groups_head"}}</h1></div> <div class="rowitem"><h1>{{lang "panel_groups_head"}}</h1></div>
</div> </div>

View File

@ -2,6 +2,7 @@
<div class="colstack panel_stack"> <div class="colstack panel_stack">
{{template "panel_menu.html" . }} {{template "panel_menu.html" . }}
<main class="colstack_right"> <main class="colstack_right">
{{template "panel_before_head.html" . }}
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{lang "panel_logs_moderation_head"}}</h1></div> <div class="rowitem"><h1>{{lang "panel_logs_moderation_head"}}</h1></div>
</div> </div>

View File

@ -2,6 +2,7 @@
<div id="panel_page_list" class="colstack panel_stack"> <div id="panel_page_list" class="colstack panel_stack">
{{template "panel_menu.html" . }} {{template "panel_menu.html" . }}
<main class="colstack_right"> <main class="colstack_right">
{{template "panel_before_head.html" . }}
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{lang "panel_pages_head"}}</h1></div> <div class="rowitem"><h1>{{lang "panel_pages_head"}}</h1></div>
</div> </div>

View File

@ -2,6 +2,7 @@
<div id="panel_page_edit" class="colstack panel_stack"> <div id="panel_page_edit" class="colstack panel_stack">
{{template "panel_menu.html" . }} {{template "panel_menu.html" . }}
<main class="colstack_right"> <main class="colstack_right">
{{template "panel_before_head.html" . }}
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{lang "panel_pages_edit_head"}}</h1></div> <div class="rowitem"><h1>{{lang "panel_pages_edit_head"}}</h1></div>
</div> </div>

View File

@ -3,6 +3,7 @@
{{template "panel_menu.html" . }} {{template "panel_menu.html" . }}
<main class="colstack_right"> <main class="colstack_right">
{{template "panel_before_head.html" . }}
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{lang "panel_plugins_head"}}</h1></div> <div class="rowitem"><h1>{{lang "panel_plugins_head"}}</h1></div>
</div> </div>

View File

@ -2,6 +2,7 @@
<div class="colstack panel_stack"> <div class="colstack panel_stack">
{{template "panel_menu.html" . }} {{template "panel_menu.html" . }}
<main class="colstack_right"> <main class="colstack_right">
{{template "panel_before_head.html" . }}
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{lang "panel_logs_registration_head"}}</h1></div> <div class="rowitem"><h1>{{lang "panel_logs_registration_head"}}</h1></div>
</div> </div>

View File

@ -3,6 +3,7 @@
{{template "panel_menu.html" . }} {{template "panel_menu.html" . }}
<main class="colstack_right"> <main class="colstack_right">
{{template "panel_before_head.html" . }}
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{.Setting.FriendlyName}}</h1></div> <div class="rowitem"><h1>{{.Setting.FriendlyName}}</h1></div>
</div> </div>

View File

@ -2,6 +2,7 @@
<div class="colstack panel_stack"> <div class="colstack panel_stack">
{{template "panel_menu.html" . }} {{template "panel_menu.html" . }}
<main class="colstack_right"> <main class="colstack_right">
{{template "panel_before_head.html" . }}
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{lang "panel_settings_head"}}</h1></div> <div class="rowitem"><h1>{{lang "panel_settings_head"}}</h1></div>
</div> </div>

View File

@ -11,6 +11,7 @@
</style> </style>
<main class="colstack_right"> <main class="colstack_right">
{{template "panel_before_head.html" . }}
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{lang "panel_themes_primary_themes"}}</h1></div> <div class="rowitem"><h1>{{lang "panel_themes_primary_themes"}}</h1></div>
</div> </div>

View File

@ -2,6 +2,7 @@
<div class="colstack panel_stack"> <div class="colstack panel_stack">
{{template "panel_menu.html" . }} {{template "panel_menu.html" . }}
<main class="colstack_right"> <main class="colstack_right">
{{template "panel_before_head.html" . }}
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{lang "panel_themes_menus_head"}}</h1></div> <div class="rowitem"><h1>{{lang "panel_themes_menus_head"}}</h1></div>
</div> </div>

View File

@ -4,6 +4,7 @@
{{/** TODO: Write the backend code and JS code for saving this menu **/}} {{/** TODO: Write the backend code and JS code for saving this menu **/}}
{{template "panel_menu.html" . }} {{template "panel_menu.html" . }}
<main class="colstack_right"> <main class="colstack_right">
{{template "panel_before_head.html" . }}
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{lang "panel_themes_menus_edit_head"}}</h1></div> <div class="rowitem"><h1>{{lang "panel_themes_menus_edit_head"}}</h1></div>
</div> </div>

View File

@ -2,6 +2,7 @@
<div class="colstack panel_stack"> <div class="colstack panel_stack">
{{template "panel_menu.html" . }} {{template "panel_menu.html" . }}
<main class="colstack_right"> <main class="colstack_right">
{{template "panel_before_head.html" . }}
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{lang "panel_themes_menus_items_head"}}</h1></div> <div class="rowitem"><h1>{{lang "panel_themes_menus_items_head"}}</h1></div>
</div> </div>

View File

@ -13,6 +13,7 @@ type Widget struct {
<div class="colstack panel_stack"> <div class="colstack panel_stack">
{{template "panel_menu.html" . }} {{template "panel_menu.html" . }}
<main class="colstack_right"> <main class="colstack_right">
{{template "panel_before_head.html" . }}
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{lang "panel_themes_widgets_head"}}</h1></div> <div class="rowitem"><h1>{{lang "panel_themes_widgets_head"}}</h1></div>
</div> </div>

View File

@ -3,6 +3,7 @@
{{template "panel_menu.html" . }} {{template "panel_menu.html" . }}
<main class="colstack_right"> <main class="colstack_right">
{{template "panel_before_head.html" . }}
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{lang "panel_user_head"}}</h1></div> <div class="rowitem"><h1>{{lang "panel_user_head"}}</h1></div>
</div> </div>

View File

@ -3,6 +3,7 @@
{{template "panel_menu.html" . }} {{template "panel_menu.html" . }}
<main class="colstack_right"> <main class="colstack_right">
{{template "panel_before_head.html" . }}
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{lang "panel_users_head"}}</h1></div> <div class="rowitem"><h1>{{lang "panel_users_head"}}</h1></div>
</div> </div>

View File

@ -3,6 +3,7 @@
{{template "panel_menu.html" . }} {{template "panel_menu.html" . }}
<main class="colstack_right"> <main class="colstack_right">
{{template "panel_before_head.html" . }}
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{lang "panel_word_filters_head"}}</h1></div> <div class="rowitem"><h1>{{lang "panel_word_filters_head"}}</h1></div>
</div> </div>

View File

@ -1,116 +1,113 @@
"use strict" "use strict";
$(document).ready(function(){ (() => {
let loggedIn = document.head.querySelector("[property='x-loggedin']").content; console.log("bf")
if(loggedIn) { addInitHook("end_init", () => {
// Is there we way we can append instead? Maybe, an editor plugin? console.log("af")
attachItemCallback = function(attachItem) { let loggedIn = document.head.querySelector("[property='x-loggedin']").content;
let currentContent = $('#input_content').trumbowyg('html'); if(loggedIn) {
$('#input_content').trumbowyg('html', currentContent); // Is there we way we can append instead? Maybe, an editor plugin?
} attachItemCallback = function(attachItem) {
let currentContent = $('#input_content').trumbowyg('html');
$(".topic_name_row").click(function(){ $('#input_content').trumbowyg('html', currentContent);
$(".topic_create_form").addClass("selectedInput"); }
});
//$.trumbowyg.svgPath = false; $(".topic_name_row").click(function(){
$(".topic_create_form").addClass("selectedInput");
});
//$.trumbowyg.svgPath = false;
// TODO: Bind this to the viewport resize event // TODO: Bind this to the viewport resize event
var btnlist = []; var btnlist = [];
if(document.documentElement.clientWidth > 550) { if(document.documentElement.clientWidth > 550) {
btnlist = [['viewHTML'],['undo','redo'],['formatting'],['strong','em','del'],['link'],['insertImage'],['unorderedList','orderedList'],['removeformat']]; btnlist = [['viewHTML'],['undo','redo'],['formatting'],['strong','em','del'],['link'],['insertImage'],['unorderedList','orderedList'],['removeformat']];
} else { } else {
btnlist = [['viewHTML'],['strong','em','del'],['link'],['insertImage'],['unorderedList','orderedList'],['removeformat']]; btnlist = [['viewHTML'],['strong','em','del'],['link'],['insertImage'],['unorderedList','orderedList'],['removeformat']];
} }
$('.topic_create_form #input_content').trumbowyg({ $('.topic_create_form #input_content').trumbowyg({
btns: btnlist, btns: btnlist,
}); });
$('.topic_reply_form #input_content').trumbowyg({ $('.topic_reply_form #input_content').trumbowyg({
btns: btnlist,
autogrow: true,
});
$('#profile_comments_form .topic_reply_form .input_content').trumbowyg({
btns: [['viewHTML'],['strong','em','del'],['link'],['insertImage'],['removeformat']],
autogrow: true,
});
addHook("edit_item_pre_bind", () => {
$('.user_content textarea').trumbowyg({
btns: btnlist, btns: btnlist,
autogrow: true, autogrow: true,
}); });
}); $('#profile_comments_form .topic_reply_form .input_content').trumbowyg({
} btns: [['viewHTML'],['strong','em','del'],['link'],['insertImage'],['removeformat']],
autogrow: true,
// TODO: Refactor this to use `each` less });
$('.button_menu').click(function(){ addHook("edit_item_pre_bind", () => {
console.log(".button_menu"); $('.user_content textarea').trumbowyg({
// The outer container btns: btnlist,
let buttonPane = newElement("div","button_menu_pane"); autogrow: true,
let postItem = $(this).parents('.post_item'); });
});
// Create the userinfo row in the pane
let userInfo = newElement("div","userinfo");
postItem.find('.avatar_item').each(function(){
userInfo.appendChild(this);
});
let userText = newElement("div","userText");
postItem.find('.userinfo:not(.avatar_item)').children().each(function(){
userText.appendChild(this);
});
userInfo.appendChild(userText);
buttonPane.appendChild(userInfo);
// Copy a short preview of the post contents into the pane
postItem.find('.user_content').each(function(){
// TODO: Truncate an excessive number of lines to 5 or so
let contents = this.innerHTML;
if(contents.length > 45) {
this.innerHTML = contents.substring(0,45) + "...";
}
buttonPane.appendChild(this);
});
// Copy the buttons from the post to the pane
let buttonGrid = newElement("div","buttonGrid");
let gridElementCount = 0;
$(this).parent().children('a:not(.button_menu)').each(function(){
buttonGrid.appendChild(this);
gridElementCount++;
});
// Fill in the placeholder grid nodes
let rowCount = 4;
console.log("rowCount: ",rowCount);
console.log("gridElementCount: ",gridElementCount);
if(gridElementCount%rowCount != 0) {
let fillerNodes = (rowCount - (gridElementCount%rowCount));
console.log("fillerNodes: ",fillerNodes);
for(let i = 0; i < fillerNodes;i++ ) {
console.log("added a gridFiller");
buttonGrid.appendChild(newElement("div","gridFiller"));
}
} }
buttonPane.appendChild(buttonGrid);
document.getElementById("back").appendChild(buttonPane); // TODO: Refactor this to use `each` less
$('.button_menu').click(function(){
console.log(".button_menu");
// The outer container
let buttonPane = newElement("div","button_menu_pane");
let postItem = $(this).parents('.post_item');
// Create the userinfo row in the pane
let userInfo = newElement("div","userinfo");
postItem.find('.avatar_item').each(function(){
userInfo.appendChild(this);
});
let userText = newElement("div","userText");
postItem.find('.userinfo:not(.avatar_item)').children().each(function(){
userText.appendChild(this);
});
userInfo.appendChild(userText);
buttonPane.appendChild(userInfo);
// Copy a short preview of the post contents into the pane
postItem.find('.user_content').each(function(){
// TODO: Truncate an excessive number of lines to 5 or so
let contents = this.innerHTML;
if(contents.length > 45) this.innerHTML = contents.substring(0,45) + "...";
buttonPane.appendChild(this);
});
// Copy the buttons from the post to the pane
let buttonGrid = newElement("div","buttonGrid");
let gridElementCount = 0;
$(this).parent().children('a:not(.button_menu)').each(function(){
buttonGrid.appendChild(this);
gridElementCount++;
});
// Fill in the placeholder grid nodes
let rowCount = 4;
console.log("rowCount: ",rowCount);
console.log("gridElementCount: ",gridElementCount);
if(gridElementCount%rowCount != 0) {
let fillerNodes = (rowCount - (gridElementCount%rowCount));
console.log("fillerNodes: ",fillerNodes);
for(let i = 0; i < fillerNodes;i++ ) {
console.log("added a gridFiller");
buttonGrid.appendChild(newElement("div","gridFiller"));
}
}
buttonPane.appendChild(buttonGrid);
document.getElementById("back").appendChild(buttonPane);
});
// Move the alerts under the first header
let colSel = $(".colstack_right .colstack_head:first");
let colSelAlt = $(".colstack_right .colstack_item:first");
let colSelAltAlt = $(".colstack_right .coldyn_block:first");
if(colSel.length > 0) $('.alert').insertAfter(colSel);
else if (colSelAlt.length > 0) $('.alert').insertBefore(colSelAlt);
else if (colSelAltAlt.length > 0) $('.alert').insertBefore(colSelAltAlt);
else $('.alert').insertAfter(".rowhead:first");
}); });
})();
// Move the alerts under the first header
let colSel = $(".colstack_right .colstack_head:first");
let colSelAlt = $(".colstack_right .colstack_item:first");
let colSelAltAlt = $(".colstack_right .coldyn_block:first");
if(colSel.length > 0) {
$('.alert').insertAfter(colSel);
} else if (colSelAlt.length > 0) {
$('.alert').insertBefore(colSelAlt);
} else if (colSelAltAlt.length > 0) {
$('.alert').insertBefore(colSelAltAlt);
} else {
$('.alert').insertAfter(".rowhead:first");
}
});
function newElement(etype, eclass) { function newElement(etype, eclass) {
let element = document.createElement(etype); let element = document.createElement(etype);

View File

@ -29,7 +29,8 @@
}, },
{ {
"Name":"cosora/misc.js", "Name":"cosora/misc.js",
"Location":"global" "Location":"global",
"Async":true
} }
] ]
} }

View File

@ -0,0 +1,6 @@
<div class="above_right">
<div class="left_bit">{{lang "panel_back_to_site"}}</div>
<div class="right_bit">
<img src="{{.CurrentUser.MicroAvatar}}" height=32 width=32 />
<span>{{lang "panel_welcome"}}{{.CurrentUser.Name}}</span></div>
</div>

View File

@ -1,7 +1,7 @@
<nav class="colstack_left" aria-label="{{lang "panel_menu_aria"}}"> <nav class="colstack_left" aria-label="{{lang "panel_menu_aria"}}">
<div class="colstack_item colstack_head"> <!--<div class="colstack_item colstack_head">
<div class="rowitem back_to_site"><a href="/">Back to site</a></div> <div class="rowitem back_to_site"><a href="/">Back to site</a></div>
</div> </div>-->
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"><a href="/panel/groups/edit/{{.ID}}">{{lang "panel_group_menu_head"}}</a></div> <div class="rowitem"><a href="/panel/groups/edit/{{.ID}}">{{lang "panel_group_menu_head"}}</a></div>
</div> </div>

View File

@ -1,5 +1,5 @@
<nav class="colstack_left" aria-label="{{lang "panel_menu_aria"}}"> <nav class="colstack_left" aria-label="{{lang "panel_menu_aria"}}">
<div class="colstack_item colstack_head"> <!--<div class="colstack_item colstack_head">
<div class="rowitem back_to_site"><a href="/">Back to site</a></div> <div class="rowitem back_to_site"><a href="/">Back to site</a></div>
</div> </div>-->
{{template "panel_inner_menu.html" . }}</nav> {{template "panel_inner_menu.html" . }}</nav>

View File

@ -15,29 +15,24 @@
}); });
addHook("open_edit", () => $('.topic_block').addClass("edithead")); addHook("open_edit", () => $('.topic_block').addClass("edithead"));
addHook("close_edit", () => $('.topic_block').removeClass("edithead")); addHook("close_edit", () => $('.topic_block').removeClass("edithead"));
})();
$(document).ready(() => { addInitHook("end_init", () => {
$(".alerts").click((event) => { $(".alerts").click((event) => {
event.stopPropagation(); event.stopPropagation();
var alerts = $(".menu_alerts")[0]; var alerts = $(".menu_alerts")[0];
if($(alerts).hasClass("selectedAlert")) return; if($(alerts).hasClass("selectedAlert")) return;
if(!conn) loadAlerts(alerts); if(!conn) loadAlerts(alerts);
alerts.className += " selectedAlert"; alerts.className += " selectedAlert";
document.getElementById("back").className += " alertActive" document.getElementById("back").className += " alertActive"
});
// Move the alerts above the first header
let colSel = $(".colstack_right .colstack_head:first");
let colSelAlt = $(".colstack_right .colstack_item:first");
let colSelAltAlt = $(".colstack_right .coldyn_block:first");
if(colSel.length > 0) $('.alert').insertBefore(colSel);
else if (colSelAlt.length > 0) $('.alert').insertBefore(colSelAlt);
else if (colSelAltAlt.length > 0) $('.alert').insertBefore(colSelAltAlt);
else $('.alert').insertAfter(".rowhead:first");
}); });
})();
// Move the alerts above the first header
let colSel = $(".colstack_right .colstack_head:first");
let colSelAlt = $(".colstack_right .colstack_item:first");
let colSelAltAlt = $(".colstack_right .coldyn_block:first");
if(colSel.length > 0) {
$('.alert').insertBefore(colSel);
} else if (colSelAlt.length > 0) {
$('.alert').insertBefore(colSelAlt);
} else if (colSelAltAlt.length > 0) {
$('.alert').insertBefore(colSelAltAlt);
} else {
$('.alert').insertAfter(".rowhead:first");
}
});

View File

@ -24,8 +24,37 @@
.menu_stats { .menu_stats {
margin-left: 4px; margin-left: 4px;
} }
.back_to_site { /*.back_to_site {
font-size: 18px; font-size: 18px;
}*/
.above_right {
background-color: rgb(62, 62, 62);
margin-top: -12px;
margin-left: -24px;
margin-right: -24px;
display: flex;
}
.above_right .left_bit {
padding-left: 20px;
margin-top: 16px;
font-size: 18px;
}
.above_right .right_bit {
margin-left: auto;
display: flex;
background-color: rgb(72, 72, 72);
padding-top: 12px;
padding-bottom: 12px;
padding-right: 20px;
padding-left: 20px;
}
.above_right img {
border-radius: 24px;
}
.above_right span {
margin-left: 12px;
margin-top: 5px;
color: rgb(180, 180, 180);
} }
.colstack_right { .colstack_right {

View File

@ -31,7 +31,8 @@
}, },
{ {
"Name":"nox/misc.js", "Name":"nox/misc.js",
"Location":"global" "Location":"global",
"Async":true
} }
] ]
} }