Reduce bandwidth usage for client templates.

Add js and ptmpl (stub) template functions.
Simple constant folding for true / false values in templates.
Use empty string instead of 0 for poll vote ips when DisablePollIP is enabled.

Shorten some things.
This commit is contained in:
Azareal 2020-02-19 09:04:14 +10:00
parent 7e3cd48284
commit 363826624f
17 changed files with 135 additions and 97 deletions

View File

@ -126,10 +126,10 @@ func LogWarning(err error, extra ...string) {
} }
func errorHeader(w http.ResponseWriter, user User, title string) *Header { func errorHeader(w http.ResponseWriter, user User, title string) *Header {
header := DefaultHeader(w, user) h := DefaultHeader(w, user)
header.Title = title h.Title = title
header.Zone = "error" h.Zone = "error"
return header return h
} }
// TODO: Dump the request? // TODO: Dump the request?
@ -400,4 +400,4 @@ func handleErrorTemplate(w http.ResponseWriter, r *http.Request, pi ErrorPage) {
} }
// Alias of routes.renderTemplate // Alias of routes.renderTemplate
var RenderTemplateAlias func(tmplName string, hookName string, w http.ResponseWriter, r *http.Request, header *Header, pi interface{}) error var RenderTemplateAlias func(tmplName, hookName string, w http.ResponseWriter, r *http.Request, header *Header, pi interface{}) error

View File

@ -16,7 +16,7 @@ import (
"strings" "strings"
"sync" "sync"
"github.com/Azareal/Gosora/tmpl_client" tmpl "github.com/Azareal/Gosora/tmpl_client"
) )
type SFileList map[string]SFile type SFileList map[string]SFile
@ -28,11 +28,11 @@ type SFile struct {
Data []byte Data []byte
GzipData []byte GzipData []byte
Sha256 string Sha256 string
OName string OName string
Pos int64 Pos int64
Length int64 Length int64
GzipLength int64 GzipLength int64
StrGzipLength string StrGzipLength string
Mimetype string Mimetype string
Info os.FileInfo Info os.FileInfo
FormattedModTime string FormattedModTime string
@ -70,7 +70,7 @@ func (list SFileList) JSTmplInit() error {
data = data[startIndex-len([]byte("if(tmplInits===undefined)")):] data = data[startIndex-len([]byte("if(tmplInits===undefined)")):]
data = replace(data, "// nolint", "") data = replace(data, "// nolint", "")
data = replace(data, "func ", "function ") data = replace(data, "func ", "function ")
data = replace(data, " error {\n", " {\nlet out = \"\"\n") data = replace(data, " error {\n", " {\nlet o = \"\"\n")
funcIndex, hasFunc := skipAllUntilCharsExist(data, 0, []byte("function Template_")) funcIndex, hasFunc := skipAllUntilCharsExist(data, 0, []byte("function Template_"))
if !hasFunc { if !hasFunc {
return errors.New("no template function found") return errors.New("no template function found")
@ -175,9 +175,9 @@ func (list SFileList) JSTmplInit() error {
} }
}) })
data = replace(data, "for _, item := range ", "for(item of ") data = replace(data, "for _, item := range ", "for(item of ")
data = replace(data, "w.Write([]byte(", "out += ") data = replace(data, "w.Write([]byte(", "o += ")
data = replace(data, "w.Write(StringToBytes(", "out += ") data = replace(data, "w.Write(StringToBytes(", "o += ")
data = replace(data, "w.Write(", "out += ") data = replace(data, "w.Write(", "o += ")
data = replace(data, "+= c.", "+= ") data = replace(data, "+= c.", "+= ")
data = replace(data, "strconv.Itoa(", "") data = replace(data, "strconv.Itoa(", "")
data = replace(data, "strconv.FormatInt(", "") data = replace(data, "strconv.FormatInt(", "")
@ -186,7 +186,7 @@ func (list SFileList) JSTmplInit() error {
data = replace(data, ", 10;", "") data = replace(data, ", 10;", "")
data = replace(data, "var plist = GetTmplPhrasesBytes("+shortName+"_tmpl_phrase_id)", "const plist = tmplPhrases[\""+tmplName+"\"];") data = replace(data, "var plist = GetTmplPhrasesBytes("+shortName+"_tmpl_phrase_id)", "const plist = tmplPhrases[\""+tmplName+"\"];")
data = replace(data, "var cached_var_", "let cached_var_") data = replace(data, "var cached_var_", "let cached_var_")
data = replace(data, `tmpl_`+shortName+`_vars, ok := tmpl_`+shortName+`_i.`, `/*`) data = replace(data, `tmpl_vars, ok := tmpl_i.`, `/*`)
data = replace(data, "[]byte(", "") data = replace(data, "[]byte(", "")
data = replace(data, "StringToBytes(", "") data = replace(data, "StringToBytes(", "")
data = replace(data, "RelativeTime(tmpl_"+shortName+"_vars.", "tmpl_"+shortName+"_vars.Relative") data = replace(data, "RelativeTime(tmpl_"+shortName+"_vars.", "tmpl_"+shortName+"_vars.Relative")
@ -194,7 +194,7 @@ func (list SFileList) JSTmplInit() error {
data = replace(data, ".Format(\"2006-01-02 15:04:05\"", "") data = replace(data, ".Format(\"2006-01-02 15:04:05\"", "")
data = replace(data, ", 10", "") data = replace(data, ", 10", "")
data = replace(data, "if ", "if(") data = replace(data, "if ", "if(")
data = replace(data, "return nil", "return out") data = replace(data, "return nil", "return o")
data = replace(data, " )", ")") data = replace(data, " )", ")")
data = replace(data, " \n", "\n") data = replace(data, " \n", "\n")
data = replace(data, "\n", ";\n") data = replace(data, "\n", ";\n")
@ -212,10 +212,12 @@ func (list SFileList) JSTmplInit() error {
fragset := tmpl.GetFrag(shortName) fragset := tmpl.GetFrag(shortName)
if fragset != nil { if fragset != nil {
sfrags := []byte("let " + shortName + "_frags = [];\n") sfrags := []byte("let " + shortName + "_frags = [\n")
for _, frags := range fragset { for _, frags := range fragset {
sfrags = append(sfrags, []byte(shortName+"_frags.push(`"+string(frags)+"`);\n")...) //sfrags = append(sfrags, []byte(shortName+"_frags.push(`"+string(frags)+"`);\n")...)
sfrags = append(sfrags, []byte("\t`"+string(frags)+"`,\n")...)
} }
sfrags = append(sfrags, []byte("];\n")...)
data = append(sfrags, data...) data = append(sfrags, data...)
} }
data = replace(data, "\n;", "\n") data = replace(data, "\n;", "\n")
@ -240,7 +242,7 @@ func (list SFileList) JSTmplInit() error {
hasher.Write(data) hasher.Write(data)
checksum := hex.EncodeToString(hasher.Sum(nil)) checksum := hex.EncodeToString(hasher.Sum(nil))
list.Set("/s/"+path, SFile{data, gzipData, checksum,path + "?h=" + checksum, 0, int64(len(data)), int64(len(gzipData)),strconv.Itoa(len(gzipData)), mime.TypeByExtension(ext), f, f.ModTime().UTC().Format(http.TimeFormat)}) list.Set("/s/"+path, SFile{data, gzipData, checksum, path + "?h=" + checksum, 0, int64(len(data)), int64(len(gzipData)), strconv.Itoa(len(gzipData)), mime.TypeByExtension(ext), f, f.ModTime().UTC().Format(http.TimeFormat)})
DebugLogf("Added the '%s' static file.", path) DebugLogf("Added the '%s' static file.", path)
return nil return nil
@ -285,7 +287,7 @@ func (list SFileList) Init() error {
} }
} }
list.Set("/s/"+path, SFile{data, gzipData, checksum,path + "?h=" + checksum, 0, int64(len(data)), int64(len(gzipData)),strconv.Itoa(len(gzipData)), mimetype, f, f.ModTime().UTC().Format(http.TimeFormat)}) list.Set("/s/"+path, SFile{data, gzipData, checksum, path + "?h=" + checksum, 0, int64(len(data)), int64(len(gzipData)), strconv.Itoa(len(gzipData)), mimetype, f, f.ModTime().UTC().Format(http.TimeFormat)})
DebugLogf("Added the '%s' static file.", path) DebugLogf("Added the '%s' static file.", path)
return nil return nil
@ -318,7 +320,7 @@ func (list SFileList) Add(path, prefix string) error {
hasher.Write(data) hasher.Write(data)
checksum := hex.EncodeToString(hasher.Sum(nil)) checksum := hex.EncodeToString(hasher.Sum(nil))
list.Set("/s"+path, SFile{data, gzipData, checksum,path + "?h=" + checksum, 0, int64(len(data)), int64(len(gzipData)),strconv.Itoa(len(gzipData)), mime.TypeByExtension(ext), f, f.ModTime().UTC().Format(http.TimeFormat)}) list.Set("/s"+path, SFile{data, gzipData, checksum, path + "?h=" + checksum, 0, int64(len(data)), int64(len(gzipData)), strconv.Itoa(len(gzipData)), mime.TypeByExtension(ext), f, f.ModTime().UTC().Format(http.TimeFormat)})
DebugLogf("Added the '%s' static file", path) DebugLogf("Added the '%s' static file", path)
return nil return nil

View File

@ -13,8 +13,8 @@ var FPStore ForumPermsStore
type ForumPermsStore interface { type ForumPermsStore interface {
Init() error Init() error
GetAllMap() (bigMap map[int]map[int]*ForumPerms) GetAllMap() (bigMap map[int]map[int]*ForumPerms)
Get(fid, gid int) (fperms *ForumPerms, err error) Get(fid, gid int) (fp *ForumPerms, err error)
GetCopy(fid, gid int) (fperms ForumPerms, err error) GetCopy(fid, gid int) (fp ForumPerms, err error)
ReloadAll() error ReloadAll() error
Reload(id int) error Reload(id int) error
} }
@ -193,7 +193,7 @@ func (s *MemoryForumPermsStore) GetAllMap() (bigMap map[int]map[int]*ForumPerms)
// TODO: Add a hook here and have plugin_guilds use it // TODO: Add a hook here and have plugin_guilds use it
// TODO: Check if the forum exists? // TODO: Check if the forum exists?
// TODO: Fix the races // TODO: Fix the races
func (s *MemoryForumPermsStore) Get(fid, gid int) (fperms *ForumPerms, err error) { func (s *MemoryForumPermsStore) Get(fid, gid int) (fp *ForumPerms, err error) {
var fmap map[int]*ForumPerms var fmap map[int]*ForumPerms
var ok bool var ok bool
if fid%2 == 0 { if fid%2 == 0 {
@ -206,22 +206,22 @@ func (s *MemoryForumPermsStore) Get(fid, gid int) (fperms *ForumPerms, err error
s.oddLock.RUnlock() s.oddLock.RUnlock()
} }
if !ok { if !ok {
return fperms, ErrNoRows return fp, ErrNoRows
} }
fperms, ok = fmap[gid] fp, ok = fmap[gid]
if !ok { if !ok {
return fperms, ErrNoRows return fp, ErrNoRows
} }
return fperms, nil return fp, nil
} }
// TODO: Check if the forum exists? // TODO: Check if the forum exists?
// TODO: Fix the races // TODO: Fix the races
func (s *MemoryForumPermsStore) GetCopy(fid, gid int) (fperms ForumPerms, err error) { func (s *MemoryForumPermsStore) GetCopy(fid, gid int) (fp ForumPerms, err error) {
fPermsPtr, err := s.Get(fid, gid) fPermsPtr, err := s.Get(fid, gid)
if err != nil { if err != nil {
return fperms, err return fp, err
} }
return *fPermsPtr, nil return *fPermsPtr, nil
} }

View File

@ -74,17 +74,17 @@ func InitPhrases(lang string) error {
return err return err
} }
data, err := ioutil.ReadFile(path) ext := filepath.Ext("/langs/" + path)
if err != nil {
return err
}
var ext = filepath.Ext("/langs/" + path)
if ext != ".json" { if ext != ".json" {
log.Printf("Found a '%s' in /langs/", ext) log.Printf("Found a '%s' in /langs/", ext)
return nil return nil
} }
data, err := ioutil.ReadFile(path)
if err != nil {
return err
}
var langPack LanguagePack var langPack LanguagePack
err = json.Unmarshal(data, &langPack) err = json.Unmarshal(data, &langPack)
if err != nil { if err != nil {
@ -98,7 +98,7 @@ func InitPhrases(lang string) error {
// [prefix][name]phrase // [prefix][name]phrase
langPack.TmplPhrasesPrefixes = make(map[string]map[string]string) langPack.TmplPhrasesPrefixes = make(map[string]map[string]string)
var conMap = make(map[string]string) // Cache phrase strings so we can de-dupe items to reduce memory use. There appear to be some minor improvements with this, although we would need a more thorough check to be sure. conMap := make(map[string]string) // Cache phrase strings so we can de-dupe items to reduce memory use. There appear to be some minor improvements with this, although we would need a more thorough check to be sure.
for name, phrase := range langPack.TmplPhrases { for name, phrase := range langPack.TmplPhrases {
_, ok := conMap[phrase] _, ok := conMap[phrase]
if !ok { if !ok {
@ -304,10 +304,10 @@ func GetTmplPhrasesByPrefix(prefix string) (phrases map[string]string, ok bool)
return res, ok return res, ok
} }
func getPlaceholder(prefix string, suffix string) string { func getPlaceholder(prefix, suffix string) string {
return "{lang." + prefix + "[" + suffix + "]}" return "{lang." + prefix + "[" + suffix + "]}"
} }
func getPlaceholderBytes(prefix string, suffix string) []byte { func getPlaceholderBytes(prefix, suffix string) []byte {
return []byte("{lang." + prefix + "[" + suffix + "]}") return []byte("{lang." + prefix + "[" + suffix + "]}")
} }

View File

@ -27,7 +27,7 @@ type PollStore interface {
Get(id int) (*Poll, error) Get(id int) (*Poll, error)
Exists(id int) bool Exists(id int) bool
Create(parent Pollable, pollType int, pollOptions map[int]string) (int, error) Create(parent Pollable, pollType int, pollOptions map[int]string) (int, error)
CastVote(optionIndex int, pollID int, uid int, ip string) error CastVote(optionIndex, pollID, uid int, ip string) error
Reload(id int) error Reload(id int) error
//Count() int //Count() int
@ -77,24 +77,24 @@ func (s *DefaultPollStore) Exists(id int) bool {
} }
func (s *DefaultPollStore) Get(id int) (*Poll, error) { func (s *DefaultPollStore) Get(id int) (*Poll, error) {
poll, err := s.cache.Get(id) p, err := s.cache.Get(id)
if err == nil { if err == nil {
return poll, nil return p, nil
} }
poll = &Poll{ID: id} p = &Poll{ID: id}
var optionTxt []byte var optionTxt []byte
err = s.get.QueryRow(id).Scan(&poll.ParentID, &poll.ParentTable, &poll.Type, &optionTxt, &poll.VoteCount) err = s.get.QueryRow(id).Scan(&p.ParentID, &p.ParentTable, &p.Type, &optionTxt, &p.VoteCount)
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = json.Unmarshal(optionTxt, &poll.Options) err = json.Unmarshal(optionTxt, &p.Options)
if err == nil { if err == nil {
poll.QuickOptions = s.unpackOptionsMap(poll.Options) p.QuickOptions = s.unpackOptionsMap(p.Options)
s.cache.Set(poll) s.cache.Set(p)
} }
return poll, err return p, err
} }
// TODO: Optimise the query to avoid preparing it on the spot? Maybe, use knowledge of the most common IN() parameter counts? // TODO: Optimise the query to avoid preparing it on the spot? Maybe, use knowledge of the most common IN() parameter counts?
@ -212,9 +212,9 @@ func (s *DefaultPollStore) unpackOptionsMap(rawOptions map[int]string) []PollOpt
} }
// TODO: Use a transaction for this? // TODO: Use a transaction for this?
func (s *DefaultPollStore) CastVote(optionIndex int, pollID int, uid int, ip string) error { func (s *DefaultPollStore) CastVote(optionIndex, pollID, uid int, ip string) error {
if Config.DisablePollIP { if Config.DisablePollIP {
ip = "0" ip = ""
} }
_, err := s.addVote.Exec(pollID, uid, optionIndex, ip) _, err := s.addVote.Exec(pollID, uid, optionIndex, ip)
if err != nil { if err != nil {

View File

@ -526,7 +526,7 @@ func compileJSTemplates(wg *sync.WaitGroup, c *tmpl.CTemplateSet, themeName stri
t := TItemHold(make(map[string]TItem)) t := TItemHold(make(map[string]TItem))
topicsRow := &TopicsRow{1, "topic-title", "Topic Title", "The topic content.", 1, false, false, now, now, user3.ID, 1, 1, "", "127.0.0.1", 1, 0, 1, 0, 1, "classname", 0, "", &user2, "", 0, &user3, "General", "/forum/general.2", nil} topicsRow := &TopicsRow{1, "topic-title", "Topic Title", "The topic content.", 1, false, false, now, now, user3.ID, 1, 1, "", "::1", 1, 0, 1, 0, 1, "classname", 0, "", &user2, "", 0, &user3, "General", "/forum/general.2", nil}
t.AddStd("topics_topic", "c.TopicsRow", topicsRow) t.AddStd("topics_topic", "c.TopicsRow", topicsRow)
poll := Poll{ID: 1, Type: 0, Options: map[int]string{0: "Nothing", 1: "Something"}, Results: map[int]int{0: 5, 1: 2}, QuickOptions: []PollOption{ poll := Poll{ID: 1, Type: 0, Options: map[int]string{0: "Nothing", 1: "Something"}, Results: map[int]int{0: 5, 1: 2}, QuickOptions: []PollOption{
@ -535,7 +535,7 @@ func compileJSTemplates(wg *sync.WaitGroup, c *tmpl.CTemplateSet, themeName stri
}, VoteCount: 7} }, VoteCount: 7}
avatar, microAvatar := BuildAvatar(62, "") avatar, microAvatar := BuildAvatar(62, "")
miniAttach := []*MiniAttachment{&MiniAttachment{Path: "/"}} miniAttach := []*MiniAttachment{&MiniAttachment{Path: "/"}}
topic := TopicUser{1, "blah", "Blah", "Hey there!", 62, false, false, now, now, 1, 1, 0, "", "127.0.0.1", 1, 0, 1, 0, "classname", poll.ID, "weird-data", BuildProfileURL("fake-user", 62), "Fake User", Config.DefaultGroup, avatar, microAvatar, 0, "", "", "", 58, false, miniAttach, nil, false} topic := TopicUser{1, "blah", "Blah", "Hey there!", 62, false, false, now, now, 1, 1, 0, "", "::1", 1, 0, 1, 0, "classname", poll.ID, "weird-data", BuildProfileURL("fake-user", 62), "Fake User", Config.DefaultGroup, avatar, microAvatar, 0, "", "", "", 58, false, miniAttach, nil, false}
var replyList []*ReplyUser var replyList []*ReplyUser
// TODO: Do we really want the UID here to be zero? // TODO: Do we really want the UID here to be zero?
avatar, microAvatar = BuildAvatar(0, "") avatar, microAvatar = BuildAvatar(0, "")
@ -816,6 +816,19 @@ func initDefaultTmplFuncMap() {
return "" return ""
} }
fmap["ptmpl"] = func(nameInt, pageInt, headerInt interface{}) interface{} {
header := headerInt.(*Header)
err := header.Theme.RunTmpl(nameInt.(string), pageInt, header.Writer)
if err != nil {
return err
}
return ""
}
fmap["js"] = func() interface{} {
return false
}
fmap["flush"] = func() interface{} { fmap["flush"] = func() interface{} {
return nil return nil
} }

View File

@ -114,6 +114,8 @@ func NewCTemplateSet(in string) *CTemplateSet {
"reltime": true, "reltime": true,
"scope": true, "scope": true,
"dyntmpl": true, "dyntmpl": true,
"ptmpl": true,
"js": true,
"index": true, "index": true,
"flush": true, "flush": true,
}, },
@ -183,7 +185,7 @@ type OutFrag struct {
Body string Body string
} }
func (c *CTemplateSet) CompileByLoggedin(name string, fileDir string, expects string, expectsInt interface{}, varList map[string]VarItem, imports ...string) (stub string, gout string, mout string, err error) { func (c *CTemplateSet) CompileByLoggedin(name, fileDir, expects string, expectsInt interface{}, varList map[string]VarItem, imports ...string) (stub, gout, mout string, err error) {
c.importMap = map[string]string{} c.importMap = map[string]string{}
for index, item := range c.baseImportMap { for index, item := range c.baseImportMap {
c.importMap[index] = item c.importMap[index] = item
@ -233,15 +235,15 @@ import "errors"
// TODO: Try to remove this redundant interface cast // TODO: Try to remove this redundant interface cast
stub += ` stub += `
// nolint // nolint
func Template_` + fname + `(tmpl_` + fname + `_i interface{}, w io.Writer) error { func Template_` + fname + `(tmpl_i interface{}, w io.Writer) error {
tmpl_` + fname + `_vars, ok := tmpl_` + fname + `_i.(` + expects + `) tmpl_vars, ok := tmpl_i.(` + expects + `)
if !ok { if !ok {
return errors.New("invalid page struct value") return errors.New("invalid page struct value")
} }
if tmpl_` + fname + `_vars.CurrentUser.Loggedin { if tmpl_vars.CurrentUser.Loggedin {
return Template_` + fname + `_member(tmpl_` + fname + `_i, w) return Template_` + fname + `_member(tmpl_i, w)
} }
return Template_` + fname + `_guest(tmpl_` + fname + `_i, w) return Template_` + fname + `_guest(tmpl_i, w)
}` }`
c.fileDir = fileDir c.fileDir = fileDir
@ -265,7 +267,7 @@ func Template_` + fname + `(tmpl_` + fname + `_i interface{}, w io.Writer) error
return stub, gout, mout, err return stub, gout, mout, err
} }
func (c *CTemplateSet) Compile(name string, fileDir string, expects string, expectsInt interface{}, varList map[string]VarItem, imports ...string) (out string, err error) { func (c *CTemplateSet) Compile(name, fileDir, expects string, expectsInt interface{}, varList map[string]VarItem, imports ...string) (out string, err error) {
if c.config.Debug { if c.config.Debug {
c.logger.Println("Compiling template '" + name + "'") c.logger.Println("Compiling template '" + name + "'")
} }
@ -279,7 +281,7 @@ func (c *CTemplateSet) Compile(name string, fileDir string, expects string, expe
return c.compile(name, content, expects, expectsInt, varList, imports...) return c.compile(name, content, expects, expectsInt, varList, imports...)
} }
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, content, expects string, expectsInt interface{}, varList map[string]VarItem, imports ...string) (out string, err error) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
fmt.Println(r) fmt.Println(r)
@ -363,6 +365,7 @@ func (c *CTemplateSet) compile(name string, content string, expects string, expe
var outBuf []OutBufferFrame var outBuf []OutBufferFrame
rootHold := "tmpl_" + fname + "_vars" rootHold := "tmpl_" + fname + "_vars"
//rootHold := "tmpl_vars"
con := CContext{ con := CContext{
RootHolder: rootHold, RootHolder: rootHold,
VarHolder: rootHold, VarHolder: rootHold,
@ -486,12 +489,17 @@ func (c *CTemplateSet) compile(name string, content string, expects string, expe
} }
if c.lang == "normal" { if c.lang == "normal" {
fout += "// nolint\nfunc Template_" + fname + "(tmpl_" + fname + "_i interface{}, w io.Writer) error {\n" fout += "// nolint\nfunc Template_" + fname + "(tmpl_i interface{}, w io.Writer) error {\n"
fout += `tmpl_` + fname + `_vars, ok := tmpl_` + fname + `_i.(` + expects + `) fout += `tmpl_` + fname + `_vars, ok := tmpl_i.(` + expects + `)
if !ok { if !ok {
return errors.New("invalid page struct value") return errors.New("invalid page struct value")
} }
` `
/*fout += `tmpl_vars, ok := tmpl_i.(` + expects + `)
if !ok {
return errors.New("invalid page struct value")
}
`*/
fout += `var iw http.ResponseWriter fout += `var iw http.ResponseWriter
gzw, ok := w.(c.GzipResponseWriter) gzw, ok := w.(c.GzipResponseWriter)
if ok { if ok {
@ -501,6 +509,7 @@ func (c *CTemplateSet) compile(name string, content string, expects string, expe
` `
} else { } else {
fout += "// nolint\nfunc Template_" + fname + "(tmpl_" + fname + "_vars interface{}, w io.Writer) error {\n" fout += "// nolint\nfunc Template_" + fname + "(tmpl_" + fname + "_vars interface{}, w io.Writer) error {\n"
//fout += "// nolint\nfunc Template_" + fname + "(tmpl_vars interface{}, w io.Writer) error {\n"
} }
if len(c.langIndexToName) > 0 { if len(c.langIndexToName) > 0 {
@ -678,6 +687,15 @@ func (c *CTemplateSet) compileSwitch(con CContext, node parse.Node) {
} }
} }
// simple constant folding
if expr == "true" {
c.compileSwitch(con, node.List)
return
} else if expr == "false" {
c.compileSwitch(con, node.ElseList)
return
}
con.Push("startif", "if "+expr+" {\n") con.Push("startif", "if "+expr+" {\n")
c.compileSwitch(con, node.List) c.compileSwitch(con, node.List)
if node.ElseList == nil { if node.ElseList == nil {
@ -980,16 +998,16 @@ func (c *CTemplateSet) compileExprSwitch(con CContext, node *parse.CommandNode)
return out return out
} }
func (c *CTemplateSet) unknownNode(node parse.Node) { func (c *CTemplateSet) unknownNode(n parse.Node) {
elem := reflect.ValueOf(node).Elem() elem := reflect.ValueOf(n).Elem()
c.logger.Println("Unknown Kind:", elem.Kind()) c.logger.Println("Unknown Kind:", elem.Kind())
c.logger.Println("Unknown Type:", elem.Type().Name()) c.logger.Println("Unknown Type:", elem.Type().Name())
panic("I don't know what node this is! Grr...") panic("I don't know what node this is! Grr...")
} }
func (c *CTemplateSet) compileIdentSwitchN(con CContext, node *parse.CommandNode) (out string) { func (c *CTemplateSet) compileIdentSwitchN(con CContext, n *parse.CommandNode) (out string) {
c.detail("in compileIdentSwitchN") c.detail("in compileIdentSwitchN")
out, _, _, _ = c.compileIdentSwitch(con, node) out, _, _, _ = c.compileIdentSwitch(con, n)
return out return out
} }
@ -1052,7 +1070,7 @@ func (c *CTemplateSet) compareJoin(con CContext, pos int, node *parse.CommandNod
return pos, out return pos, out
} }
func (c *CTemplateSet) compileIdentSwitch(con CContext, node *parse.CommandNode) (out string, val reflect.Value, literal bool, notident bool) { func (c *CTemplateSet) compileIdentSwitch(con CContext, node *parse.CommandNode) (out string, val reflect.Value, literal, notident bool) {
c.dumpCall("compileIdentSwitch", con, node) c.dumpCall("compileIdentSwitch", con, node)
litString := func(inner string, bytes bool) { litString := func(inner string, bytes bool) {
if !bytes { if !bytes {
@ -1136,6 +1154,14 @@ ArgLoop:
out = "c.HasWidgets(" + leftParam + "," + rightParam + ")" out = "c.HasWidgets(" + leftParam + "," + rightParam + ")"
literal = true literal = true
break ArgLoop break ArgLoop
case "js":
if c.lang == "js" {
out = "true"
} else {
out = "false"
}
literal = true
break ArgLoop
case "lang": case "lang":
// TODO: Implement string literals properly // TODO: Implement string literals properly
leftOp := node.Args[pos+1].String() leftOp := node.Args[pos+1].String()
@ -1172,7 +1198,7 @@ ArgLoop:
for i := pos + 2; i < len(node.Args); i++ { for i := pos + 2; i < len(node.Args); i++ {
op := node.Args[i].String() op := node.Args[i].String()
if op != "" { if op != "" {
if /*op[0] == '.' || */op[0] == '$' { if /*op[0] == '.' || */ op[0] == '$' {
panic("langf args cannot be dynamic") panic("langf args cannot be dynamic")
} }
if op[0] != '.' && op[0] != '"' && !unicode.IsDigit(rune(op[0])) { if op[0] != '.' && op[0] != '"' && !unicode.IsDigit(rune(op[0])) {
@ -1262,7 +1288,8 @@ ArgLoop:
case "scope": case "scope":
literal = true literal = true
break ArgLoop break ArgLoop
case "dyntmpl": // TODO: Optimise ptmpl
case "dyntmpl", "ptmpl":
var pageParam, headParam string var pageParam, headParam string
// TODO: Implement string literals properly // TODO: Implement string literals properly
// TODO: Should we check to see if pos+3 is within the bounds of the slice? // TODO: Should we check to see if pos+3 is within the bounds of the slice?

View File

@ -16,8 +16,8 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"reflect" "reflect"
"strings"
"strconv" "strconv"
"strings"
"text/template" "text/template"
p "github.com/Azareal/Gosora/common/phrases" p "github.com/Azareal/Gosora/common/phrases"
@ -91,7 +91,7 @@ type ThemeMapTmplToDock struct {
func (t *Theme) LoadStaticFiles() error { func (t *Theme) LoadStaticFiles() error {
t.ResourceTemplates = template.New("") t.ResourceTemplates = template.New("")
fmap := make(map[string]interface{}) fmap := make(map[string]interface{})
fmap["lang"] = func(phraseNameInt interface{}, tmplInt interface{}) interface{} { fmap["lang"] = func(phraseNameInt, tmplInt interface{}) interface{} {
phraseName, ok := phraseNameInt.(string) phraseName, ok := phraseNameInt.(string)
if !ok { if !ok {
panic("phraseNameInt is not a string") panic("phraseNameInt is not a string")
@ -167,7 +167,7 @@ func (t *Theme) AddThemeStaticFiles() error {
hasher.Write(data) hasher.Write(data)
checksum := hex.EncodeToString(hasher.Sum(nil)) checksum := hex.EncodeToString(hasher.Sum(nil))
StaticFiles.Set("/s/"+t.Name+path, SFile{data, gzipData, checksum,t.Name+path + "?h=" + checksum, 0, int64(len(data)), int64(len(gzipData)),strconv.Itoa(len(gzipData)), mime.TypeByExtension(ext), f, f.ModTime().UTC().Format(http.TimeFormat)}) StaticFiles.Set("/s/"+t.Name+path, SFile{data, gzipData, checksum, t.Name + path + "?h=" + checksum, 0, int64(len(data)), int64(len(gzipData)), strconv.Itoa(len(gzipData)), mime.TypeByExtension(ext), f, f.ModTime().UTC().Format(http.TimeFormat)})
DebugLog("Added the '/" + t.Name + path + "' static file for theme " + t.Name + ".") DebugLog("Added the '/" + t.Name + path + "' static file for theme " + t.Name + ".")
return nil return nil
@ -252,7 +252,6 @@ func UpdateDefaultTheme(t *Theme) error {
if !ok { if !ok {
return ErrNoDefaultTheme return ErrNoDefaultTheme
} }
err = dtheme.setActive(false) err = dtheme.setActive(false)
if err != nil { if err != nil {
return err return err

View File

@ -236,7 +236,7 @@ var imageExts = ["png", "jpg", "jpe","jpeg","jif","jfi","jfif", "svg", "bmp", "g
}); });
let bulkActionSender = function(action, selectedTopics, fragBit) { let bulkActionSender = function(action, selectedTopics, fragBit) {
let url = "/topic/"+action+"/submit/"+fragBit+"?s=" + me.User.S; let url = "/topic/"+action+"/submit/"+fragBit+"?s="+me.User.S;
$.ajax({ $.ajax({
url: url, url: url,
type: "POST", type: "POST",
@ -265,7 +265,7 @@ var imageExts = ["png", "jpg", "jpe","jpeg","jif","jfi","jfif", "svg", "bmp", "g
let fid = this.getAttribute("data-fid"); let fid = this.getAttribute("data-fid");
if (fid == null) return; if (fid == null) return;
this.classList.add("pane_selected"); this.classList.add("pane_selected");
console.log("fid: " + fid); console.log("fid:" + fid);
forumToMoveTo = fid; forumToMoveTo = fid;
$("#mover_submit").unbind("click"); $("#mover_submit").unbind("click");
@ -298,8 +298,8 @@ var imageExts = ["png", "jpg", "jpe","jpeg","jif","jfi","jfif", "svg", "bmp", "g
} }
var pollInputIndex = 1; var pollInputIndex = 1;
$("#add_poll_button").click((event) => { $("#add_poll_button").click((ev) => {
event.preventDefault(); ev.preventDefault();
$(".poll_content_row").removeClass("auto_hide"); $(".poll_content_row").removeClass("auto_hide");
$("#has_poll_input").val("1"); $("#has_poll_input").val("1");
$(".pollinputinput").click(addPollInput); $(".pollinputinput").click(addPollInput);

View File

@ -1,13 +1,13 @@
(() => { (() => {
addInitHook("end_init", () => { addInitHook("end_init", () => {
fetch("/api/watches/") fetch("/api/watches/")
.then(response => { .then(resp => {
if(response.status!==200) { if(resp.status!==200) {
console.log("error"); console.log("error");
console.log("response:", response); console.log("response:", resp);
return; return;
} }
response.text().then(data => eval(data)); resp.text().then(data => eval(data));
}) })
.catch(err => console.log("err:", err)); .catch(err => console.log("err:", err));
}); });

View File

@ -43,7 +43,7 @@ func renderTemplate(tmplName string, w http.ResponseWriter, r *http.Request, hea
return nil return nil
} }
func buildBasePage(w http.ResponseWriter, r *http.Request, user *c.User, titlePhrase string, zone string) (*c.BasePanelPage, c.RouteError) { func buildBasePage(w http.ResponseWriter, r *http.Request, user *c.User, titlePhrase, zone string) (*c.BasePanelPage, c.RouteError) {
header, stats, ferr := c.PanelUserCheck(w, r, user) header, stats, ferr := c.PanelUserCheck(w, r, user)
if ferr != nil { if ferr != nil {
return nil, ferr return nil, ferr

View File

@ -214,19 +214,19 @@ func GroupsPromotionsCreateSubmit(w http.ResponseWriter, r *http.Request, user c
return c.LocalError("posts must be integer", w, r, user) return c.LocalError("posts must be integer", w, r, user)
} }
registeredHours, err := strconv.Atoi(r.FormValue("registered_hours")) regHours, err := strconv.Atoi(r.FormValue("registered_hours"))
if err != nil { if err != nil {
return c.LocalError("registered_hours must be integer", w, r, user) return c.LocalError("registered_hours must be integer", w, r, user)
} }
registeredDays, err := strconv.Atoi(r.FormValue("registered_days")) regDays, err := strconv.Atoi(r.FormValue("registered_days"))
if err != nil { if err != nil {
return c.LocalError("registered_days must be integer", w, r, user) return c.LocalError("registered_days must be integer", w, r, user)
} }
registeredMonths, err := strconv.Atoi(r.FormValue("registered_months")) regMonths, err := strconv.Atoi(r.FormValue("registered_months"))
if err != nil { if err != nil {
return c.LocalError("registered_months must be integer", w, r, user) return c.LocalError("registered_months must be integer", w, r, user)
} }
registeredMinutes := (registeredHours * 60) + (registeredDays * 24 * 60) + (registeredMonths * 30 * 24 * 60) regMinutes := (regHours * 60) + (regDays * 24 * 60) + (regMonths * 30 * 24 * 60)
g, err := c.Groups.Get(from) g, err := c.Groups.Get(from)
ferr := groupCheck(w, r, user, g, err) ferr := groupCheck(w, r, user, g, err)
@ -238,7 +238,7 @@ func GroupsPromotionsCreateSubmit(w http.ResponseWriter, r *http.Request, user c
if err != nil { if err != nil {
return ferr return ferr
} }
pid, err := c.GroupPromotions.Create(from, to, twoWay, level, posts, registeredMinutes) pid, err := c.GroupPromotions.Create(from, to, twoWay, level, posts, regMinutes)
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }

View File

@ -17,7 +17,6 @@ func LogsRegs(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError
if ferr != nil { if ferr != nil {
return ferr return ferr
} }
logCount := c.RegLogs.Count() logCount := c.RegLogs.Count()
page, _ := strconv.Atoi(r.FormValue("page")) page, _ := strconv.Atoi(r.FormValue("page"))
perPage := 12 perPage := 12
@ -174,7 +173,6 @@ func LogsMod(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError {
if ferr != nil { if ferr != nil {
return ferr return ferr
} }
page, _ := strconv.Atoi(r.FormValue("page")) page, _ := strconv.Atoi(r.FormValue("page"))
perPage := 12 perPage := 12
offset, page, lastPage := c.PageOffset(c.ModLogs.Count(), page, perPage) offset, page, lastPage := c.PageOffset(c.ModLogs.Count(), page, perPage)

View File

@ -27,7 +27,7 @@ func init() {
replyStmts = ReplyStmts{ replyStmts = ReplyStmts{
// TODO: Less race-y attachment count updates // TODO: Less race-y attachment count updates
updateAttachs: acc.Update("replies").Set("attachCount=?").Where("rid=?").Prepare(), updateAttachs: acc.Update("replies").Set("attachCount=?").Where("rid=?").Prepare(),
createReplyPaging: acc.Select("replies").Cols("rid").Where("rid >= ? - 1 AND tid = ?").Orderby("rid ASC").Prepare(), createReplyPaging: acc.Select("replies").Cols("rid").Where("rid >= ? - 1 AND tid=?").Orderby("rid ASC").Prepare(),
} }
return acc.FirstError() return acc.FirstError()
}) })

View File

@ -117,7 +117,6 @@ func UnbanUser(w http.ResponseWriter, r *http.Request, user c.User, suid string)
} else if err != nil { } else if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
if !targetUser.IsBanned { if !targetUser.IsBanned {
return c.LocalError("The user you're trying to unban isn't banned.", w, r, user) return c.LocalError("The user you're trying to unban isn't banned.", w, r, user)
} }

View File

@ -2,7 +2,7 @@
{{if gt .Page 1}}<div id="prevFloat" class="prev_button"><a class="prev_link" aria-label="{{lang "paginator.prev_page_aria"}}" rel="prev" href="{{.Forum.Link}}?page={{subtract .Page 1}}">{{lang "paginator.less_than"}}</a></div>{{end}} {{if gt .Page 1}}<div id="prevFloat" class="prev_button"><a class="prev_link" aria-label="{{lang "paginator.prev_page_aria"}}" rel="prev" href="{{.Forum.Link}}?page={{subtract .Page 1}}">{{lang "paginator.less_than"}}</a></div>{{end}}
{{if ne .LastPage .Page}}<div id="nextFloat" class="next_button"><a class="next_link" aria-label="{{lang "paginator.next_page_aria"}}" rel="next" href="{{.Forum.Link}}?page={{add .Page 1}}">{{lang "paginator.greater_than"}}</a></div>{{end}} {{if ne .LastPage .Page}}<div id="nextFloat" class="next_button"><a class="next_link" aria-label="{{lang "paginator.next_page_aria"}}" rel="next" href="{{.Forum.Link}}?page={{add .Page 1}}">{{lang "paginator.greater_than"}}</a></div>{{end}}
<link rel="canonical" href="//{{.Site.URL}}{{.Forum.Link}}{{if gt .Page 1}}?page={{.Page}}{{end}}" /> <link rel="canonical" href="//{{.Site.URL}}{{.Forum.Link}}{{if gt .Page 1}}?page={{.Page}}{{end}}"/>
<main id="forumItemList" itemscope itemtype="http://schema.org/ItemList"> <main id="forumItemList" itemscope itemtype="http://schema.org/ItemList">
<div id="forum_head_block" class="rowblock rowhead topic_list_title_block{{if .CurrentUser.Loggedin}} has_opt{{end}}"> <div id="forum_head_block" class="rowblock rowhead topic_list_title_block{{if .CurrentUser.Loggedin}} has_opt{{end}}">
@ -49,7 +49,7 @@
{{range .ItemList}}<div class="topic_row{{if .Sticky}} topic_sticky{{else if .IsClosed}} topic_closed{{end}}" data-tid="{{.ID}}"> {{range .ItemList}}<div class="topic_row{{if .Sticky}} topic_sticky{{else if .IsClosed}} topic_closed{{end}}" data-tid="{{.ID}}">
<div class="rowitem topic_left passive datarow"> <div class="rowitem topic_left passive datarow">
<span class="selector"></span> <span class="selector"></span>
<a href="{{.Creator.Link}}"><img src="{{.Creator.MicroAvatar}}" height=64 alt="Avatar" title="{{.Creator.Name}}'s Avatar" aria-hidden="true" /></a> <a href="{{.Creator.Link}}"><img src="{{.Creator.MicroAvatar}}" height=64 alt="Avatar" title="{{.Creator.Name}}'s Avatar" aria-hidden="true"/></a>
<span class="topic_inner_left"> <span class="topic_inner_left">
<a class="rowtopic" href="{{.Link}}" itemprop="itemListElement" title="{{.Title}}"><span>{{.Title}}</span></a> <a class="rowtopic" href="{{.Link}}" itemprop="itemListElement" title="{{.Title}}"><span>{{.Title}}</span></a>
<br /><a class="rowsmall starter" href="{{.Creator.Link}}" title="{{.Creator.Name}}">{{.Creator.Name}}</a> <br /><a class="rowsmall starter" href="{{.Creator.Link}}" title="{{.Creator.Name}}">{{.Creator.Name}}</a>
@ -72,7 +72,7 @@
</div> </div>
<div class="rowitem topic_right passive datarow"> <div class="rowitem topic_right passive datarow">
<div class="topic_right_inside"> <div class="topic_right_inside">
<a href="{{.LastUser.Link}}"><img src="{{.LastUser.MicroAvatar}}" height=64 alt="Avatar" title="{{.LastUser.Name}}'s Avatar" aria-hidden="true" /></a> <a href="{{.LastUser.Link}}"><img src="{{.LastUser.MicroAvatar}}" height=64 alt="Avatar" title="{{.LastUser.Name}}'s Avatar" aria-hidden="true"/></a>
<span> <span>
<a href="{{.LastUser.Link}}" class="lastName" title="{{.LastUser.Name}}">{{.LastUser.Name}}</a><br> <a href="{{.LastUser.Link}}" class="lastName" title="{{.LastUser.Name}}">{{.LastUser.Name}}</a><br>
<a href="{{.Link}}?page={{.LastPage}}{{if .LastReplyID}}#post-{{.LastReplyID}}{{end}}" class="rowsmall lastReplyAt" title="{{abstime .LastReplyAt}}">{{reltime .LastReplyAt}}</a> <a href="{{.Link}}?page={{.LastPage}}{{if .LastReplyID}}#post-{{.LastReplyID}}{{end}}" class="rowsmall lastReplyAt" title="{{abstime .LastReplyAt}}">{{reltime .LastReplyAt}}</a>

View File

@ -1,5 +1,5 @@
{{range .ItemList}}<article {{scope "post"}} id="post-{{.ID}}" itemscope itemtype="http://schema.org/CreativeWork" class="rowitem passive deletable_block editable_parent post_item{{if .ActionType}} action_item{{end}}{{if .Attachments}} has_attachs{{end}}"> {{range .ItemList}}<article {{scope "post"}} id="post-{{.ID}}" itemscope itemtype="http://schema.org/CreativeWork" class="rowitem passive deletable_block editable_parent post_item{{if .ActionType}} action_item{{end}}{{if .Attachments}} has_attachs{{end}}">
{{template "topic_alt_userinfo.html" . }} {{if js}}js{{/**{{ptmpl "topic_alt_userinfo" .}}**/}}{{else}}{{template "topic_alt_userinfo.html" . }}{{end}}
<div class="content_container"> <div class="content_container">
{{if .ActionType}} {{if .ActionType}}
<span class="action_icon" aria-hidden="true">{{.ActionIcon}}</span> <span class="action_icon" aria-hidden="true">{{.ActionIcon}}</span>
@ -14,7 +14,7 @@
{{range .Attachments}} {{range .Attachments}}
<div class="attach_item attach_item_item{{if .Image}} attach_image_holder{{end}}"> <div class="attach_item attach_item_item{{if .Image}} attach_image_holder{{end}}">
{{if .Image}}<img src="//{{$.Header.Site.URL}}/attachs/{{.Path}}?sid={{.SectionID}}&amp;stype=forums" height=24 width=24 />{{end}} {{if .Image}}<img src="//{{$.Header.Site.URL}}/attachs/{{.Path}}?sid={{.SectionID}}&amp;stype=forums" height=24 width=24 />{{end}}
<span class="attach_item_path" aid="{{.ID}}" fullPath="//{{$.Header.Site.URL}}/attachs/{{.Path}}">{{.Path}}</span> <span class="attach_item_path" aid={{.ID}} fullPath="//{{$.Header.Site.URL}}/attachs/{{.Path}}">{{.Path}}</span>
<button class="attach_item_select">{{lang "topic.select_button_text"}}</button> <button class="attach_item_select">{{lang "topic.select_button_text"}}</button>
<button class="attach_item_copy">{{lang "topic.copy_button_text"}}</button> <button class="attach_item_copy">{{lang "topic.copy_button_text"}}</button>
</div> </div>