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:
parent
3320cb4697
commit
660f24acff
|
@ -96,12 +96,11 @@ var langCodes = []string{
|
|||
type DefaultLangViewCounter struct {
|
||||
buckets []*RWMutexCounterBucket //[OSID]count
|
||||
codesToIndices map[string]int
|
||||
insert *sql.Stmt
|
||||
|
||||
insert *sql.Stmt
|
||||
}
|
||||
|
||||
func NewDefaultLangViewCounter() (*DefaultLangViewCounter, error) {
|
||||
acc := qgen.NewAcc()
|
||||
|
||||
func NewDefaultLangViewCounter(acc *qgen.Accumulator) (*DefaultLangViewCounter, error) {
|
||||
var langBuckets = make([]*RWMutexCounterBucket, len(langCodes))
|
||||
for bucketID, _ := range langBuckets {
|
||||
langBuckets[bucketID] = &RWMutexCounterBucket{counter: 0}
|
||||
|
|
|
@ -12,8 +12,7 @@ type DefaultRouteViewCounter struct {
|
|||
insert *sql.Stmt
|
||||
}
|
||||
|
||||
func NewDefaultRouteViewCounter() (*DefaultRouteViewCounter, error) {
|
||||
acc := qgen.NewAcc()
|
||||
func NewDefaultRouteViewCounter(acc *qgen.Accumulator) (*DefaultRouteViewCounter, error) {
|
||||
var routeBuckets = make([]*RWMutexCounterBucket, len(routeMapEnum))
|
||||
for bucketID, _ := range routeBuckets {
|
||||
routeBuckets[bucketID] = &RWMutexCounterBucket{counter: 0}
|
||||
|
|
|
@ -14,8 +14,7 @@ type DefaultOSViewCounter struct {
|
|||
insert *sql.Stmt
|
||||
}
|
||||
|
||||
func NewDefaultOSViewCounter() (*DefaultOSViewCounter, error) {
|
||||
acc := qgen.NewAcc()
|
||||
func NewDefaultOSViewCounter(acc *qgen.Accumulator) (*DefaultOSViewCounter, error) {
|
||||
var osBuckets = make([]*RWMutexCounterBucket, len(osMapEnum))
|
||||
for bucketID, _ := range osBuckets {
|
||||
osBuckets[bucketID] = &RWMutexCounterBucket{counter: 0}
|
||||
|
|
|
@ -27,7 +27,7 @@ var staticFileMutex sync.RWMutex
|
|||
type SFile struct {
|
||||
Data []byte
|
||||
GzipData []byte
|
||||
Sha256 []byte
|
||||
Sha256 string
|
||||
Pos int64
|
||||
Length int64
|
||||
GzipLength int64
|
||||
|
@ -240,7 +240,7 @@ func (list SFileList) JSTmplInit() error {
|
|||
// Get a checksum for CSPs and cache busting
|
||||
hasher := sha256.New()
|
||||
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)})
|
||||
|
||||
|
@ -267,7 +267,7 @@ func (list SFileList) Init() error {
|
|||
// Get a checksum for CSPs and cache busting
|
||||
hasher := sha256.New()
|
||||
hasher.Write(data)
|
||||
checksum := []byte(hex.EncodeToString(hasher.Sum(nil)))
|
||||
checksum := hex.EncodeToString(hasher.Sum(nil))
|
||||
|
||||
// Avoid double-compressing images
|
||||
var gzipData []byte
|
||||
|
@ -318,7 +318,7 @@ func (list SFileList) Add(path string, prefix string) error {
|
|||
// Get a checksum for CSPs and cache busting
|
||||
hasher := sha256.New()
|
||||
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)})
|
||||
|
||||
|
|
|
@ -10,14 +10,20 @@ import (
|
|||
"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: Preload Trumboyg on Cosora on the forum list
|
||||
type Header struct {
|
||||
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
|
||||
NoticeList []string
|
||||
Scripts []string
|
||||
PreScriptsAsync []string
|
||||
Scripts []HResource
|
||||
PreScriptsAsync []HResource
|
||||
ScriptsAsync []HResource
|
||||
//Preload []string
|
||||
Stylesheets []string
|
||||
Widgets PageWidgets
|
||||
|
@ -44,11 +50,41 @@ type Header struct {
|
|||
}
|
||||
|
||||
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) {
|
||||
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) {
|
||||
|
|
|
@ -131,7 +131,11 @@ func panelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (header
|
|||
if ext == "css" {
|
||||
header.AddSheet(resource.Name)
|
||||
} 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" {
|
||||
header.AddSheet(resource.Name)
|
||||
} else if ext == "js" {
|
||||
header.AddScript(resource.Name)
|
||||
if resource.Async {
|
||||
header.AddScriptAsync(resource.Name)
|
||||
} else {
|
||||
header.AddScript(resource.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -109,8 +109,9 @@ func tmplInitHeaders(user User, user2 User, user3 User) (*Header, *Header, *Head
|
|||
CurrentUser: user,
|
||||
NoticeList: []string{"test"},
|
||||
Stylesheets: []string{"panel.css"},
|
||||
Scripts: []string{"whatever.js"},
|
||||
PreScriptsAsync: []string{"whatever.js"},
|
||||
Scripts: []HResource{HResource{"whatever.js", "d"}},
|
||||
PreScriptsAsync: []HResource{HResource{"whatever.js", "d"}},
|
||||
ScriptsAsync: []HResource{HResource{"whatever.js", "d"}},
|
||||
Widgets: PageWidgets{
|
||||
LeftSidebar: template.HTML("lalala"),
|
||||
},
|
||||
|
|
|
@ -2,11 +2,13 @@ package tmpl
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template/parse"
|
||||
|
@ -70,7 +72,8 @@ type CTemplateSet struct {
|
|||
themeName string
|
||||
perThemeTmpls map[string]bool
|
||||
|
||||
logger *log.Logger
|
||||
logger *log.Logger
|
||||
loggerf *os.File
|
||||
}
|
||||
|
||||
func NewCTemplateSet(in string) *CTemplateSet {
|
||||
|
@ -110,7 +113,8 @@ func NewCTemplateSet(in string) *CTemplateSet {
|
|||
"dyntmpl": 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)
|
||||
}
|
||||
c.logger = log.New(f, "", log.LstdFlags)
|
||||
c.loggerf = f
|
||||
}
|
||||
|
||||
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) {
|
||||
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.detailf("c: %+v\n", c)
|
||||
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"))
|
||||
con.Push("endelse", "}\n")
|
||||
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:
|
||||
if val.Type().Name() != "string" && !strings.HasPrefix(varname, "string(") {
|
||||
varname = "string(" + varname + ")"
|
||||
|
|
|
@ -77,6 +77,7 @@ type ThemeResource struct {
|
|||
Name string
|
||||
Location string
|
||||
Loggedin bool // Only serve this resource to logged in users
|
||||
Async bool
|
||||
}
|
||||
|
||||
type ThemeMapTmplToDock struct {
|
||||
|
@ -162,7 +163,7 @@ func (theme *Theme) AddThemeStaticFiles() error {
|
|||
// Get a checksum for CSPs and cache busting
|
||||
hasher := sha256.New()
|
||||
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)})
|
||||
|
||||
|
|
|
@ -92,6 +92,12 @@ func RouteWebsockets(w http.ResponseWriter, r *http.Request, user User) RouteErr
|
|||
currentPage = string(msgblocks[1])
|
||||
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`)) {
|
||||
} else if bytes.Equal(message,[]byte(`end-view`)) {
|
||||
|
|
|
@ -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.
|
||||
|
||||
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.
|
||||
|
||||
|
|
|
@ -164,6 +164,7 @@ var RouteMap = map[string]interface{}{
|
|||
"routes.RobotsTxt": routes.RobotsTxt,
|
||||
"routes.SitemapXml": routes.SitemapXml,
|
||||
"routes.BadRoute": routes.BadRoute,
|
||||
"routes.HTTPSRedirect": routes.HTTPSRedirect,
|
||||
}
|
||||
|
||||
// ! NEVER RELY ON THESE REMAINING THE SAME BETWEEN COMMITS
|
||||
|
@ -309,6 +310,7 @@ var routeMapEnum = map[string]int{
|
|||
"routes.RobotsTxt": 138,
|
||||
"routes.SitemapXml": 139,
|
||||
"routes.BadRoute": 140,
|
||||
"routes.HTTPSRedirect": 141,
|
||||
}
|
||||
var reverseRouteMapEnum = map[int]string{
|
||||
0: "routes.Overview",
|
||||
|
@ -452,6 +454,7 @@ var reverseRouteMapEnum = map[int]string{
|
|||
138: "routes.RobotsTxt",
|
||||
139: "routes.SitemapXml",
|
||||
140: "routes.BadRoute",
|
||||
141: "routes.HTTPSRedirect",
|
||||
}
|
||||
var osMapEnum = map[string]int{
|
||||
"unknown": 0,
|
||||
|
@ -600,6 +603,17 @@ func (writ *WriterIntercept) WriteHeader(code int) {
|
|||
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 {
|
||||
UploadHandler func(http.ResponseWriter, *http.Request)
|
||||
extraRoutes map[string]func(http.ResponseWriter, *http.Request, common.User) common.RouteError
|
||||
|
|
|
@ -708,6 +708,8 @@
|
|||
"option_yes":"Yes",
|
||||
"option_no":"No",
|
||||
|
||||
"panel_back_to_site":"Back to Site",
|
||||
"panel_welcome":"Welcome ",
|
||||
"panel_menu_head":"Control Panel",
|
||||
"panel_menu_aria":"The control panel menu",
|
||||
"panel_menu_users":"Users",
|
||||
|
|
11
main.go
11
main.go
|
@ -28,7 +28,6 @@ import (
|
|||
"github.com/Azareal/Gosora/common/counters"
|
||||
"github.com/Azareal/Gosora/common/phrases"
|
||||
"github.com/Azareal/Gosora/query_gen"
|
||||
"github.com/Azareal/Gosora/routes"
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
@ -181,15 +180,15 @@ func afterDBInit() (err error) {
|
|||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
counters.OSViewCounter, err = counters.NewDefaultOSViewCounter()
|
||||
counters.OSViewCounter, err = counters.NewDefaultOSViewCounter(acc)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
counters.LangViewCounter, err = counters.NewDefaultLangViewCounter()
|
||||
counters.LangViewCounter, err = counters.NewDefaultLangViewCounter(acc)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
counters.RouteViewCounter, err = counters.NewDefaultRouteViewCounter()
|
||||
counters.RouteViewCounter, err = counters.NewDefaultRouteViewCounter(acc)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
@ -472,7 +471,7 @@ func startServer() {
|
|||
var newServer = func(addr string, handler http.Handler) *http.Server {
|
||||
rtime := common.Config.ReadTimeout
|
||||
if rtime == 0 {
|
||||
rtime = 5
|
||||
rtime = 8
|
||||
} else if rtime == -1 {
|
||||
rtime = 0
|
||||
}
|
||||
|
@ -527,7 +526,7 @@ func startServer() {
|
|||
// TODO: Redirect to port 443
|
||||
go func() {
|
||||
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)
|
||||
|
|
|
@ -59,4 +59,6 @@ function buildStatsChart(rawLabels, seriesData, timeRange, legendNames) {
|
|||
labels: labels,
|
||||
series: seriesData,
|
||||
}, config);
|
||||
}
|
||||
}
|
||||
|
||||
runInitHook("analytics_loaded");
|
|
@ -193,7 +193,7 @@ function wsAlertEvent(data) {
|
|||
updateAlertList(generalAlerts/*, alist*/);
|
||||
}
|
||||
|
||||
function runWebSockets() {
|
||||
function runWebSockets(resume = false) {
|
||||
if(window.location.protocol == "https:") {
|
||||
conn = new WebSocket("wss://" + document.location.host + "/ws/");
|
||||
} else conn = new WebSocket("ws://" + document.location.host + "/ws/");
|
||||
|
@ -206,6 +206,7 @@ function runWebSockets() {
|
|||
conn.onopen = () => {
|
||||
console.log("The WebSockets connection was opened");
|
||||
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
|
||||
if(me.User.ID > 0) Notification.requestPermission();
|
||||
}
|
||||
|
@ -213,23 +214,22 @@ function runWebSockets() {
|
|||
conn.onclose = () => {
|
||||
conn = false;
|
||||
console.log("The WebSockets connection was closed");
|
||||
let backoff = 1000;
|
||||
let backoff = 0.8;
|
||||
if(wsBackoff < 0) wsBackoff = 0;
|
||||
else if(wsBackoff > 12) backoff = 13000;
|
||||
else if(wsBackoff > 5) backoff = 7000;
|
||||
else if(wsBackoff > 12) backoff = 11;
|
||||
else if(wsBackoff > 5) backoff = 5;
|
||||
wsBackoff++;
|
||||
|
||||
setTimeout(() => {
|
||||
var alertMenuList = document.getElementsByClassName("menu_alerts");
|
||||
for(var i = 0; i < alertMenuList.length; i++) {
|
||||
loadAlerts(alertMenuList[i]);
|
||||
}
|
||||
runWebSockets();
|
||||
}, 60 * backoff);
|
||||
for(var i = 0; i < alertMenuList.length; i++) loadAlerts(alertMenuList[i]);
|
||||
runWebSockets(true);
|
||||
}, backoff * 60 * 1000);
|
||||
|
||||
if(wsBackoff > 0) {
|
||||
if(wsBackoff <= 5) setTimeout(() => wsBackoff--, 60 * 4000);
|
||||
else if(wsBackoff <= 12) setTimeout(() => wsBackoff--, 60 * 20000);
|
||||
if(wsBackoff <= 5) setTimeout(() => wsBackoff--, 5.5 * 60 * 1000);
|
||||
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) => {
|
||||
if(e!=undefined) console.log("failed alert? why?", e)
|
||||
}, () => {
|
||||
console.log("ha")
|
||||
//console.log("ha")
|
||||
if(!Template_alert) throw("template function not found");
|
||||
addInitHook("after_phrases", () => {
|
||||
// TODO: The load part of loadAlerts could be done asynchronously while the update of the DOM could be deferred
|
||||
$(document).ready(() => {
|
||||
alertsInitted = true;
|
||||
var alertMenuList = document.getElementsByClassName("menu_alerts");
|
||||
for(var i = 0; i < alertMenuList.length; i++) {
|
||||
loadAlerts(alertMenuList[i]);
|
||||
}
|
||||
for(var i = 0; i < alertMenuList.length; i++) loadAlerts(alertMenuList[i]);
|
||||
if(window["WebSocket"]) runWebSockets();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -4,7 +4,7 @@ var me = {};
|
|||
var phraseBox = {};
|
||||
if(tmplInits===undefined) var tmplInits = {};
|
||||
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_init": [],
|
||||
"start_init": [],
|
||||
|
@ -15,6 +15,7 @@ var hooks = {
|
|||
"open_edit":[],
|
||||
"close_edit":[],
|
||||
"edit_item_pre_bind":[],
|
||||
"analytics_loaded":[],
|
||||
};
|
||||
var ranInitHooks = {}
|
||||
|
||||
|
|
|
@ -174,6 +174,7 @@ func main() {
|
|||
mapIt("routes.RobotsTxt")
|
||||
mapIt("routes.SitemapXml")
|
||||
mapIt("routes.BadRoute")
|
||||
mapIt("routes.HTTPSRedirect")
|
||||
tmplVars.AllRouteNames = allRouteNames
|
||||
tmplVars.AllRouteMap = allRouteMap
|
||||
|
||||
|
@ -381,6 +382,17 @@ func (writ *WriterIntercept) WriteHeader(code int) {
|
|||
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 {
|
||||
UploadHandler func(http.ResponseWriter, *http.Request)
|
||||
extraRoutes map[string]func(http.ResponseWriter, *http.Request, common.User) common.RouteError
|
||||
|
|
|
@ -29,8 +29,9 @@ func renderTemplate(tmplName string, w http.ResponseWriter, r *http.Request, hea
|
|||
}
|
||||
// TODO: Expand this to non-HTTPS requests too
|
||||
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 {
|
||||
header.Elapsed1 = time.Since(header.StartedAt).String()
|
||||
}
|
||||
|
|
|
@ -125,7 +125,7 @@ func PreAnalyticsDetail(w http.ResponseWriter, r *http.Request, user *common.Use
|
|||
}
|
||||
basePage.AddSheet("chartist/chartist.min.css")
|
||||
basePage.AddScript("chartist/chartist.min.js")
|
||||
basePage.AddScript("analytics.js")
|
||||
basePage.AddScriptAsync("analytics.js")
|
||||
return basePage, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
header.AddScript("global.js")
|
||||
if common.RunPreRenderHook("pre_render_"+tmplName, w, r, &header.CurrentUser, pi) {
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1,17 +1,5 @@
|
|||
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
|
||||
func DynamicRoute() {
|
||||
}
|
||||
|
@ -19,3 +7,7 @@ func UploadedFile() {
|
|||
}
|
||||
func BadRoute() {
|
||||
}
|
||||
|
||||
// Real implementation is in router_gen/main.go, this is just a stub to map the analytics onto
|
||||
func HTTPSRedirect() {
|
||||
}
|
||||
|
|
|
@ -6,13 +6,14 @@
|
|||
{{range .Header.Stylesheets}}
|
||||
<link href="/static/{{.}}" rel="stylesheet" type="text/css">{{end}}
|
||||
{{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}}" />
|
||||
<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>
|
||||
{{range .Header.Scripts}}
|
||||
<script type="text/javascript" src="/static/{{.}}"></script>{{end}}
|
||||
<script type="text/javascript" src="/static/global.js"></script>
|
||||
<script type="text/javascript" src="/static/{{.Name}}{{if .Hash}}?h={{.Hash}}{{end}}"></script>{{end}}
|
||||
<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}}
|
||||
{{/** TODO: Have page / forum / topic level tags and descriptions below as-well **/}}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<div class="colstack panel_stack">
|
||||
{{template "panel_menu.html" . }}
|
||||
<main class="colstack_right">
|
||||
{{template "panel_before_head.html" . }}
|
||||
<div class="colstack_item colstack_head">
|
||||
<div class="rowitem"><h1>{{lang "panel_logs_administration_head"}}</h1></div>
|
||||
</div>
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<div class="colstack panel_stack">
|
||||
{{template "panel_menu.html" . }}
|
||||
<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">
|
||||
<div class="colstack_item colstack_head">
|
||||
<div class="rowitem">
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<div class="colstack panel_stack">
|
||||
{{template "panel_menu.html" . }}
|
||||
<main id="panel_analytics_right" class="colstack_right">
|
||||
{{template "panel_before_head.html" . }}
|
||||
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/agents/" method="get">
|
||||
<div class="colstack_item colstack_head">
|
||||
<div class="rowitem">
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<div class="colstack panel_stack">
|
||||
{{template "panel_menu.html" . }}
|
||||
<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">
|
||||
<div class="colstack_item colstack_head">
|
||||
<div class="rowitem">
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<div class="colstack panel_stack">
|
||||
{{template "panel_menu.html" . }}
|
||||
<main id="panel_analytics_right" class="colstack_right">
|
||||
{{template "panel_before_head.html" . }}
|
||||
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/forums/" method="get">
|
||||
<div class="colstack_item colstack_head">
|
||||
<div class="rowitem">
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<div class="colstack panel_stack">
|
||||
{{template "panel_menu.html" . }}
|
||||
<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">
|
||||
<div class="colstack_item colstack_head">
|
||||
<div class="rowitem">
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<div class="colstack panel_stack">
|
||||
{{template "panel_menu.html" . }}
|
||||
<main id="panel_analytics_right" class="colstack_right">
|
||||
{{template "panel_before_head.html" . }}
|
||||
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/langs/" method="get">
|
||||
<div class="colstack_item colstack_head">
|
||||
<div class="rowitem">
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<div class="colstack panel_stack">
|
||||
{{template "panel_menu.html" . }}
|
||||
<main id="panel_analytics_right" class="colstack_right">
|
||||
{{template "panel_before_head.html" . }}
|
||||
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/posts/" method="get">
|
||||
<div class="colstack_item colstack_head">
|
||||
<div class="rowitem">
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<div class="colstack panel_stack">
|
||||
{{template "panel_menu.html" . }}
|
||||
<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">
|
||||
<div class="colstack_item colstack_head">
|
||||
<div class="rowitem">
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<div class="colstack panel_stack">
|
||||
{{template "panel_menu.html" . }}
|
||||
<main id="panel_analytics_right" class="colstack_right">
|
||||
{{template "panel_before_head.html" . }}
|
||||
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/referrers/" method="get">
|
||||
<div class="colstack_item colstack_head">
|
||||
<div class="rowitem">
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<div class="colstack panel_stack">
|
||||
{{template "panel_menu.html" . }}
|
||||
<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">
|
||||
<div class="colstack_item colstack_head">
|
||||
<div class="rowitem">
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<div class="colstack panel_stack">
|
||||
{{template "panel_menu.html" . }}
|
||||
<main id="panel_analytics_right" class="colstack_right">
|
||||
{{template "panel_before_head.html" . }}
|
||||
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/routes/" method="get">
|
||||
<div class="colstack_item colstack_head">
|
||||
<div class="rowitem">
|
||||
|
|
|
@ -11,6 +11,10 @@ let legendNames = [{{range .Graph.Legends}}
|
|||
{{.}},{{end}}
|
||||
];
|
||||
addInitHook("after_phrases", () => {
|
||||
buildStatsChart(rawLabels, seriesData, "{{.TimeRange}}",legendNames);
|
||||
addInitHook("end_init", () => {
|
||||
addInitHook("analytics_loaded", () => {
|
||||
buildStatsChart(rawLabels, seriesData, "{{.TimeRange}}",legendNames);
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
|
@ -2,6 +2,7 @@
|
|||
<div class="colstack panel_stack">
|
||||
{{template "panel_menu.html" . }}
|
||||
<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">
|
||||
<div class="colstack_item colstack_head">
|
||||
<div class="rowitem">
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<div class="colstack panel_stack">
|
||||
{{template "panel_menu.html" . }}
|
||||
<main id="panel_analytics_right" class="colstack_right">
|
||||
{{template "panel_before_head.html" . }}
|
||||
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/systems/" method="get">
|
||||
<div class="colstack_item colstack_head">
|
||||
<div class="rowitem">
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<div class="colstack panel_stack">
|
||||
{{template "panel_menu.html" . }}
|
||||
<main id="panel_analytics_right" class="colstack_right">
|
||||
{{template "panel_before_head.html" . }}
|
||||
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/topics/" method="get">
|
||||
<div class="colstack_item colstack_head">
|
||||
<div class="rowitem">
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<div class="colstack panel_stack">
|
||||
{{template "panel_menu.html" . }}
|
||||
<main id="panel_analytics_right" class="colstack_right">
|
||||
{{template "panel_before_head.html" . }}
|
||||
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/views/" method="get">
|
||||
<div class="colstack_item colstack_head">
|
||||
<div class="rowitem">
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<div class="colstack panel_stack">
|
||||
{{template "panel_menu.html" . }}
|
||||
<main class="colstack_right">
|
||||
{{template "panel_before_head.html" . }}
|
||||
<div class="colstack_item colstack_head">
|
||||
<div class="rowitem"><h1>{{lang "areyousure_head"}}</h1></div>
|
||||
</div>
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<div class="colstack panel_stack">
|
||||
{{template "panel_menu.html" . }}
|
||||
<main class="colstack_right">
|
||||
{{template "panel_before_head.html" . }}
|
||||
<div class="colstack_item colstack_head">
|
||||
<div class="rowitem"><h1>{{lang "panel_backups_head"}}</h1></div>
|
||||
</div>
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<div class="colstack panel_stack">
|
||||
{{template "panel_menu.html" . }}
|
||||
<main id="panel_dashboard_right" class="colstack_right">
|
||||
{{template "panel_before_head.html" . }}
|
||||
<div class="colstack_item colstack_head">
|
||||
<div class="rowitem"><h1>{{lang "panel_dashboard_head"}}</h1></div>
|
||||
</div>
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<div class="colstack panel_stack">
|
||||
{{template "panel_menu.html" . }}
|
||||
<main id="panel_dashboard_right" class="colstack_right">
|
||||
{{template "panel_before_head.html" . }}
|
||||
<div class="colstack_item colstack_head">
|
||||
<div class="rowitem"><h1>{{lang "panel_debug_head"}}</h1></div>
|
||||
</div>
|
||||
|
|
|
@ -7,6 +7,7 @@ var formVars = {'perm_preset': ['can_moderate','can_post','read_only','no_access
|
|||
</script>
|
||||
|
||||
<main class="colstack_right">
|
||||
{{template "panel_before_head.html" . }}
|
||||
<div class="colstack_item colstack_head">
|
||||
<div class="rowitem"><h1>{{.Name}}{{lang "panel_forum_head_suffix"}}</h1></div>
|
||||
</div>
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
{{template "panel_menu.html" . }}
|
||||
|
||||
<main class="colstack_right">
|
||||
{{template "panel_before_head.html" . }}
|
||||
<div class="colstack_item colstack_head">
|
||||
<div class="rowitem"><h1>{{.Name}}{{lang "panel_forum_head_suffix"}}</h1></div>
|
||||
</div>
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
</script>
|
||||
|
||||
<main class="colstack_right">
|
||||
{{template "panel_before_head.html" . }}
|
||||
<div class="colstack_item colstack_head">
|
||||
<div class="rowitem"><h1>{{lang "panel_forums_head"}}</h1></div>
|
||||
</div>
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<div class="colstack panel_stack">
|
||||
{{template "panel_group_menu.html" . }}
|
||||
<main class="colstack_right">
|
||||
{{template "panel_before_head.html" . }}
|
||||
<div class="colstack_item colstack_head">
|
||||
<div class="rowitem"><h1>{{.Name}}{{lang "panel_group_head_suffix"}}</h1></div>
|
||||
</div>
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<div class="colstack panel_stack">
|
||||
{{template "panel_group_menu.html" . }}
|
||||
<main class="colstack_right">
|
||||
{{template "panel_before_head.html" . }}
|
||||
<div class="colstack_item colstack_head">
|
||||
<div class="rowitem"><h1>{{.Name}}{{lang "panel_group_head_suffix"}}</h1></div>
|
||||
</div>
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
{{template "panel_menu.html" . }}
|
||||
<main class="colstack_right">
|
||||
{{template "panel_before_head.html" . }}
|
||||
<div class="colstack_item colstack_head">
|
||||
<div class="rowitem"><h1>{{lang "panel_groups_head"}}</h1></div>
|
||||
</div>
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<div class="colstack panel_stack">
|
||||
{{template "panel_menu.html" . }}
|
||||
<main class="colstack_right">
|
||||
{{template "panel_before_head.html" . }}
|
||||
<div class="colstack_item colstack_head">
|
||||
<div class="rowitem"><h1>{{lang "panel_logs_moderation_head"}}</h1></div>
|
||||
</div>
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<div id="panel_page_list" class="colstack panel_stack">
|
||||
{{template "panel_menu.html" . }}
|
||||
<main class="colstack_right">
|
||||
{{template "panel_before_head.html" . }}
|
||||
<div class="colstack_item colstack_head">
|
||||
<div class="rowitem"><h1>{{lang "panel_pages_head"}}</h1></div>
|
||||
</div>
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<div id="panel_page_edit" class="colstack panel_stack">
|
||||
{{template "panel_menu.html" . }}
|
||||
<main class="colstack_right">
|
||||
{{template "panel_before_head.html" . }}
|
||||
<div class="colstack_item colstack_head">
|
||||
<div class="rowitem"><h1>{{lang "panel_pages_edit_head"}}</h1></div>
|
||||
</div>
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
{{template "panel_menu.html" . }}
|
||||
<main class="colstack_right">
|
||||
{{template "panel_before_head.html" . }}
|
||||
<div class="colstack_item colstack_head">
|
||||
<div class="rowitem"><h1>{{lang "panel_plugins_head"}}</h1></div>
|
||||
</div>
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<div class="colstack panel_stack">
|
||||
{{template "panel_menu.html" . }}
|
||||
<main class="colstack_right">
|
||||
{{template "panel_before_head.html" . }}
|
||||
<div class="colstack_item colstack_head">
|
||||
<div class="rowitem"><h1>{{lang "panel_logs_registration_head"}}</h1></div>
|
||||
</div>
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
{{template "panel_menu.html" . }}
|
||||
<main class="colstack_right">
|
||||
{{template "panel_before_head.html" . }}
|
||||
<div class="colstack_item colstack_head">
|
||||
<div class="rowitem"><h1>{{.Setting.FriendlyName}}</h1></div>
|
||||
</div>
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<div class="colstack panel_stack">
|
||||
{{template "panel_menu.html" . }}
|
||||
<main class="colstack_right">
|
||||
{{template "panel_before_head.html" . }}
|
||||
<div class="colstack_item colstack_head">
|
||||
<div class="rowitem"><h1>{{lang "panel_settings_head"}}</h1></div>
|
||||
</div>
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
</style>
|
||||
|
||||
<main class="colstack_right">
|
||||
{{template "panel_before_head.html" . }}
|
||||
<div class="colstack_item colstack_head">
|
||||
<div class="rowitem"><h1>{{lang "panel_themes_primary_themes"}}</h1></div>
|
||||
</div>
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<div class="colstack panel_stack">
|
||||
{{template "panel_menu.html" . }}
|
||||
<main class="colstack_right">
|
||||
{{template "panel_before_head.html" . }}
|
||||
<div class="colstack_item colstack_head">
|
||||
<div class="rowitem"><h1>{{lang "panel_themes_menus_head"}}</h1></div>
|
||||
</div>
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
{{/** TODO: Write the backend code and JS code for saving this menu **/}}
|
||||
{{template "panel_menu.html" . }}
|
||||
<main class="colstack_right">
|
||||
{{template "panel_before_head.html" . }}
|
||||
<div class="colstack_item colstack_head">
|
||||
<div class="rowitem"><h1>{{lang "panel_themes_menus_edit_head"}}</h1></div>
|
||||
</div>
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<div class="colstack panel_stack">
|
||||
{{template "panel_menu.html" . }}
|
||||
<main class="colstack_right">
|
||||
{{template "panel_before_head.html" . }}
|
||||
<div class="colstack_item colstack_head">
|
||||
<div class="rowitem"><h1>{{lang "panel_themes_menus_items_head"}}</h1></div>
|
||||
</div>
|
||||
|
|
|
@ -13,6 +13,7 @@ type Widget struct {
|
|||
<div class="colstack panel_stack">
|
||||
{{template "panel_menu.html" . }}
|
||||
<main class="colstack_right">
|
||||
{{template "panel_before_head.html" . }}
|
||||
<div class="colstack_item colstack_head">
|
||||
<div class="rowitem"><h1>{{lang "panel_themes_widgets_head"}}</h1></div>
|
||||
</div>
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
{{template "panel_menu.html" . }}
|
||||
<main class="colstack_right">
|
||||
{{template "panel_before_head.html" . }}
|
||||
<div class="colstack_item colstack_head">
|
||||
<div class="rowitem"><h1>{{lang "panel_user_head"}}</h1></div>
|
||||
</div>
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
{{template "panel_menu.html" . }}
|
||||
<main class="colstack_right">
|
||||
{{template "panel_before_head.html" . }}
|
||||
<div class="colstack_item colstack_head">
|
||||
<div class="rowitem"><h1>{{lang "panel_users_head"}}</h1></div>
|
||||
</div>
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
{{template "panel_menu.html" . }}
|
||||
<main class="colstack_right">
|
||||
{{template "panel_before_head.html" . }}
|
||||
<div class="colstack_item colstack_head">
|
||||
<div class="rowitem"><h1>{{lang "panel_word_filters_head"}}</h1></div>
|
||||
</div>
|
||||
|
|
|
@ -1,116 +1,113 @@
|
|||
"use strict"
|
||||
"use strict";
|
||||
|
||||
$(document).ready(function(){
|
||||
let loggedIn = document.head.querySelector("[property='x-loggedin']").content;
|
||||
if(loggedIn) {
|
||||
// Is there we way we can append instead? Maybe, an editor plugin?
|
||||
attachItemCallback = function(attachItem) {
|
||||
let currentContent = $('#input_content').trumbowyg('html');
|
||||
$('#input_content').trumbowyg('html', currentContent);
|
||||
}
|
||||
|
||||
$(".topic_name_row").click(function(){
|
||||
$(".topic_create_form").addClass("selectedInput");
|
||||
});
|
||||
//$.trumbowyg.svgPath = false;
|
||||
(() => {
|
||||
console.log("bf")
|
||||
addInitHook("end_init", () => {
|
||||
console.log("af")
|
||||
let loggedIn = document.head.querySelector("[property='x-loggedin']").content;
|
||||
if(loggedIn) {
|
||||
// Is there we way we can append instead? Maybe, an editor plugin?
|
||||
attachItemCallback = function(attachItem) {
|
||||
let currentContent = $('#input_content').trumbowyg('html');
|
||||
$('#input_content').trumbowyg('html', currentContent);
|
||||
}
|
||||
|
||||
$(".topic_name_row").click(function(){
|
||||
$(".topic_create_form").addClass("selectedInput");
|
||||
});
|
||||
//$.trumbowyg.svgPath = false;
|
||||
|
||||
// TODO: Bind this to the viewport resize event
|
||||
var btnlist = [];
|
||||
if(document.documentElement.clientWidth > 550) {
|
||||
btnlist = [['viewHTML'],['undo','redo'],['formatting'],['strong','em','del'],['link'],['insertImage'],['unorderedList','orderedList'],['removeformat']];
|
||||
} else {
|
||||
btnlist = [['viewHTML'],['strong','em','del'],['link'],['insertImage'],['unorderedList','orderedList'],['removeformat']];
|
||||
}
|
||||
|
||||
$('.topic_create_form #input_content').trumbowyg({
|
||||
btns: btnlist,
|
||||
});
|
||||
$('.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({
|
||||
// TODO: Bind this to the viewport resize event
|
||||
var btnlist = [];
|
||||
if(document.documentElement.clientWidth > 550) {
|
||||
btnlist = [['viewHTML'],['undo','redo'],['formatting'],['strong','em','del'],['link'],['insertImage'],['unorderedList','orderedList'],['removeformat']];
|
||||
} else {
|
||||
btnlist = [['viewHTML'],['strong','em','del'],['link'],['insertImage'],['unorderedList','orderedList'],['removeformat']];
|
||||
}
|
||||
|
||||
$('.topic_create_form #input_content').trumbowyg({
|
||||
btns: btnlist,
|
||||
});
|
||||
$('.topic_reply_form #input_content').trumbowyg({
|
||||
btns: btnlist,
|
||||
autogrow: true,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 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"));
|
||||
}
|
||||
$('#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,
|
||||
autogrow: true,
|
||||
});
|
||||
});
|
||||
}
|
||||
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) {
|
||||
let element = document.createElement(etype);
|
||||
|
|
|
@ -29,7 +29,8 @@
|
|||
},
|
||||
{
|
||||
"Name":"cosora/misc.js",
|
||||
"Location":"global"
|
||||
"Location":"global",
|
||||
"Async":true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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>
|
|
@ -1,7 +1,7 @@
|
|||
<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>
|
||||
</div>-->
|
||||
<div class="colstack_item colstack_head">
|
||||
<div class="rowitem"><a href="/panel/groups/edit/{{.ID}}">{{lang "panel_group_menu_head"}}</a></div>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<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>
|
||||
</div>-->
|
||||
{{template "panel_inner_menu.html" . }}</nav>
|
||||
|
|
|
@ -15,29 +15,24 @@
|
|||
});
|
||||
addHook("open_edit", () => $('.topic_block').addClass("edithead"));
|
||||
addHook("close_edit", () => $('.topic_block').removeClass("edithead"));
|
||||
})();
|
||||
|
||||
$(document).ready(() => {
|
||||
$(".alerts").click((event) => {
|
||||
event.stopPropagation();
|
||||
var alerts = $(".menu_alerts")[0];
|
||||
if($(alerts).hasClass("selectedAlert")) return;
|
||||
if(!conn) loadAlerts(alerts);
|
||||
alerts.className += " selectedAlert";
|
||||
document.getElementById("back").className += " alertActive"
|
||||
addInitHook("end_init", () => {
|
||||
$(".alerts").click((event) => {
|
||||
event.stopPropagation();
|
||||
var alerts = $(".menu_alerts")[0];
|
||||
if($(alerts).hasClass("selectedAlert")) return;
|
||||
if(!conn) loadAlerts(alerts);
|
||||
alerts.className += " selectedAlert";
|
||||
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");
|
||||
}
|
||||
});
|
||||
})();
|
|
@ -24,8 +24,37 @@
|
|||
.menu_stats {
|
||||
margin-left: 4px;
|
||||
}
|
||||
.back_to_site {
|
||||
/*.back_to_site {
|
||||
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 {
|
||||
|
|
|
@ -31,7 +31,8 @@
|
|||
},
|
||||
{
|
||||
"Name":"nox/misc.js",
|
||||
"Location":"global"
|
||||
"Location":"global",
|
||||
"Async":true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue