save bytes in the client templates

rename template file and functions to reduce bandwidth usage
replace x-loggedin with theoretically faster x-mem
remove redundant check in ua loop
move extraData initialisation down to where it's needed
This commit is contained in:
Azareal 2020-03-23 09:14:08 +10:00
parent 6b7c51a604
commit a668cd296b
28 changed files with 229 additions and 196 deletions

View File

@ -7,13 +7,13 @@
/public/EQCSS.js
/schema/**
template_list.go
template_forum.go
template_forums.go
template_topic.go
template_topic_alt.go
template_topics.go
template_profile.go
tmpl_list.go
tmpl_forum.go
tmpl_forums.go
tmpl_topic.go
tmpl_topic_alt.go
tmpl_topics.go
tmpl_profile.go
gen_mysql.go
gen_mssql.go

4
.gitignore vendored
View File

@ -30,5 +30,5 @@ RouterGen
Patcher
Gosora
Installer
template_*.go
template_*.jgo
tmpl_*.go
tmpl_*.jgo

View File

@ -1,7 +1,9 @@
echo "Deleting artifacts from previous builds"
rm -f template_*.go
rm -f tmpl_*.go
rm -f gen_*.go
rm -f tmpl_client/template_*.go
rm -f tmpl_client/template_*
rm -f tmpl_client/tmpl_*
rm -f ./Gosora
echo "Building the router generator"

View File

@ -1,7 +1,9 @@
echo "Deleting artifacts from previous builds"
rm -f template_*.go
rm -f tmpl_*.go
rm -f gen_*.go
rm -f tmpl_client/template_*.go
rm -f tmpl_client/template_*
rm -f tmpl_client/tmpl_*
rm -f ./Gosora
echo "Building the router generator"

View File

@ -1,8 +1,10 @@
@echo off
rem TODO: Make these deletes a little less noisy
del "template_*.go"
del "tmpl_*.go"
del "gen_*.go"
del "tmpl_client/template_*.go"
del "tmpl_client/template_*"
del "tmpl_client/tmpl_*"
del "gosora.exe"
echo Generating the dynamic code

View File

@ -1,9 +1,11 @@
@echo off
rem TODO: Make these deletes a little less noisy
del "template_*.go"
del "tmpl_*.go"
del "gen_*.go"
cd tmpl_client
del "template_*.go"
del "template_*"
del "tmpl_*"
cd ..
del "gosora.exe"

View File

@ -46,7 +46,7 @@ type CSSData struct {
func (list SFileList) JSTmplInit() error {
DebugLog("Initialising the client side templates")
return filepath.Walk("./tmpl_client", func(path string, f os.FileInfo, err error) error {
if f.IsDir() || strings.HasSuffix(path, "template_list.go") || strings.HasSuffix(path, "stub.go") {
if f.IsDir() || strings.HasSuffix(path, "tmpl_list.go") || strings.HasSuffix(path, "stub.go") {
return nil
}
path = strings.Replace(path, "\\", "/", -1)
@ -58,7 +58,7 @@ func (list SFileList) JSTmplInit() error {
path = strings.TrimPrefix(path, "tmpl_client/")
tmplName := strings.TrimSuffix(path, ".jgo")
shortName := strings.TrimPrefix(tmplName, "template_")
shortName := strings.TrimPrefix(tmplName, "tmpl_")
replace := func(data []byte, replaceThis, withThis string) []byte {
return bytes.Replace(data, []byte(replaceThis), []byte(withThis), -1)
@ -73,9 +73,10 @@ func (list SFileList) JSTmplInit() error {
}
data = data[startIndex-len([]byte("if(tmplInits===undefined)")):]
rep("// nolint", "")
//rep("func ", "function ")
rep("func ", "function ")
rep(" error {\n", " {\nlet o = \"\"\n")
funcIndex, hasFunc := skipAllUntilCharsExist(data, 0, []byte("function Template_"))
rep(" error {\n", " {\nlet o=\"\"\n")
funcIndex, hasFunc := skipAllUntilCharsExist(data, 0, []byte("function Tmpl_"))
if !hasFunc {
return errors.New("no template function found")
}
@ -193,13 +194,13 @@ func (list SFileList) JSTmplInit() error {
//rep("//var plist = GetTmplPhrasesBytes("+shortName+"_tmpl_phrase_id)", "const "+shortName+"_phrase_arr = tmplPhrases[\""+tmplName+"\"];")
rep("//var plist = GetTmplPhrasesBytes("+shortName+"_tmpl_phrase_id)", "const pl=tmplPhrases[\""+tmplName+"\"];")
rep(shortName+"_phrase_arr", "pl")
rep("tmpl_"+shortName+"_vars", "t_vars")
rep("tmpl_"+shortName+"_vars", "t_v")
rep("var c_v_", "let c_v_")
rep(`t_vars, ok := tmpl_i.`, `/*`)
rep("[]byte(", "")
rep("StringToBytes(", "")
rep("RelativeTime(t_vars.", "t_vars.Relative")
rep("RelativeTime(t_v.", "t_v.Relative")
// TODO: Format dates properly on the client side
rep(".Format(\"2006-01-02 15:04:05\"", "")
rep(", 10", "")
@ -211,7 +212,6 @@ func (list SFileList) JSTmplInit() error {
rep("{;", "{")
rep("};", "}")
rep("[;", "[")
rep(";;", ";")
rep(",;", ",")
rep("=;", "=")
rep(`,
@ -220,22 +220,33 @@ func (list SFileList) JSTmplInit() error {
rep(`=
}`, "=[]")
rep("o += ", "o+=")
rep(shortName+"_frags[", "fr[")
rep("function Tmpl_"+shortName+"(t_v) {","var Tmpl_"+shortName+"=(t_v)=>{")
fragset := tmpl.GetFrag(shortName)
if fragset != nil {
sfrags := []byte("let " + shortName + "_frags=[\n")
for _, frags := range fragset {
//sfrags := []byte("let " + shortName + "_frags=[\n")
sfrags := []byte("{const fr=[")
for i, frags := range fragset {
//sfrags = append(sfrags, []byte(shortName+"_frags.push(`"+string(frags)+"`);\n")...)
sfrags = append(sfrags, []byte("`"+string(frags)+"`,\n")...)
//sfrags = append(sfrags, []byte("`"+string(frags)+"`,\n")...)
if i == 0 {
sfrags = append(sfrags, []byte("`"+string(frags)+"`")...)
} else {
sfrags = append(sfrags, []byte(",`"+string(frags)+"`")...)
}
}
sfrags = append(sfrags, []byte("];\n")...)
//sfrags = append(sfrags, []byte("];\n")...)
sfrags = append(sfrags, []byte("];")...)
data = append(sfrags, data...)
}
rep("\n;", "\n")
rep(";;", ";")
data = append(data, '}')
for name, _ := range Themes {
if strings.HasSuffix(shortName, "_"+name) {
data = append(data, "\nvar Template_"+strings.TrimSuffix(shortName, "_"+name)+"=Template_"+shortName+";"...)
data = append(data, "var Tmpl_"+strings.TrimSuffix(shortName, "_"+name)+"=Tmpl_"+shortName+";"...)
break
}
}

View File

@ -23,14 +23,14 @@ var PreRoute func(http.ResponseWriter, *http.Request) (User, bool) = preRoute
// nolint We need these types so people can tell what they are without scrolling to the bottom of the file
var PanelUserCheck func(http.ResponseWriter, *http.Request, *User) (*Header, PanelStats, RouteError) = panelUserCheck
var SimplePanelUserCheck func(http.ResponseWriter, *http.Request, *User) (*HeaderLite, RouteError) = simplePanelUserCheck
var SimpleForumUserCheck func(w http.ResponseWriter, r *http.Request, user *User, fid int) (headerLite *HeaderLite, err RouteError) = simpleForumUserCheck
var ForumUserCheck func(header *Header, w http.ResponseWriter, r *http.Request, user *User, fid int) (err RouteError) = forumUserCheck
var SimpleUserCheck func(w http.ResponseWriter, r *http.Request, user *User) (headerLite *HeaderLite, err RouteError) = simpleUserCheck
var UserCheck func(w http.ResponseWriter, r *http.Request, user *User) (header *Header, err RouteError) = userCheck
var UserCheckNano func(w http.ResponseWriter, r *http.Request, user *User, nano int64) (header *Header, err RouteError) = userCheck2
var SimpleForumUserCheck func(w http.ResponseWriter, r *http.Request, u *User, fid int) (headerLite *HeaderLite, err RouteError) = simpleForumUserCheck
var ForumUserCheck func(header *Header, w http.ResponseWriter, r *http.Request, u *User, fid int) (err RouteError) = forumUserCheck
var SimpleUserCheck func(w http.ResponseWriter, r *http.Request, u *User) (headerLite *HeaderLite, err RouteError) = simpleUserCheck
var UserCheck func(w http.ResponseWriter, r *http.Request, u *User) (h *Header, err RouteError) = userCheck
var UserCheckNano func(w http.ResponseWriter, r *http.Request, u *User, nano int64) (h *Header, err RouteError) = userCheck2
func simpleForumUserCheck(w http.ResponseWriter, r *http.Request, user *User, fid int) (header *HeaderLite, rerr RouteError) {
header, rerr = SimpleUserCheck(w, r, user)
func simpleForumUserCheck(w http.ResponseWriter, r *http.Request, u *User, fid int) (header *HeaderLite, rerr RouteError) {
header, rerr = SimpleUserCheck(w, r, u)
if rerr != nil {
return header, rerr
}
@ -39,39 +39,39 @@ func simpleForumUserCheck(w http.ResponseWriter, r *http.Request, user *User, fi
}
// Is there a better way of doing the skip AND the success flag on this hook like multiple returns?
skip, rerr := header.Hooks.VhookSkippable("simple_forum_check_pre_perms", w, r, user, &fid, &header)
skip, rerr := header.Hooks.VhookSkippable("simple_forum_check_pre_perms", w, r, u, &fid, &header)
if skip || rerr != nil {
return header, rerr
}
fperms, err := FPStore.Get(fid, user.Group)
fperms, err := FPStore.Get(fid, u.Group)
if err == ErrNoRows {
fperms = BlankForumPerms()
} else if err != nil {
return header, InternalError(err, w, r)
}
cascadeForumPerms(fperms, user)
cascadeForumPerms(fperms, u)
return header, nil
}
func forumUserCheck(header *Header, w http.ResponseWriter, r *http.Request, user *User, fid int) (rerr RouteError) {
func forumUserCheck(h *Header, w http.ResponseWriter, r *http.Request, u *User, fid int) (rerr RouteError) {
if !Forums.Exists(fid) {
return NotFound(w, r, header)
return NotFound(w, r, h)
}
skip, rerr := header.Hooks.VhookSkippable("forum_check_pre_perms", w, r, user, &fid, &header)
skip, rerr := h.Hooks.VhookSkippable("forum_check_pre_perms", w, r, u, &fid, &h)
if skip || rerr != nil {
return rerr
}
fperms, err := FPStore.Get(fid, user.Group)
fperms, err := FPStore.Get(fid, u.Group)
if err == ErrNoRows {
fperms = BlankForumPerms()
} else if err != nil {
return InternalError(err, w, r)
}
cascadeForumPerms(fperms, user)
header.CurrentUser = user // TODO: Use a pointer instead for CurrentUser, so we don't have to do this
cascadeForumPerms(fperms, u)
h.CurrentUser = u // TODO: Use a pointer instead for CurrentUser, so we don't have to do this
return rerr
}
@ -100,14 +100,14 @@ func cascadeForumPerms(fp *ForumPerms, u *User) {
// Even if they have the right permissions, the control panel is only open to supermods+. There are many areas without subpermissions which assume that the current user is a supermod+ and admins are extremely unlikely to give these permissions to someone who isn't at-least a supermod to begin with
// TODO: Do a panel specific theme?
func panelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (h *Header, stats PanelStats, rerr RouteError) {
func panelUserCheck(w http.ResponseWriter, r *http.Request, u *User) (h *Header, stats PanelStats, rerr RouteError) {
theme := GetThemeByReq(r)
h = &Header{
Site: Site,
Settings: SettingBox.Load().(SettingMap),
Themes: Themes,
Theme: theme,
CurrentUser: user,
CurrentUser: u,
Hooks: GetHookTable(),
Zone: "panel",
Writer: w,
@ -164,7 +164,7 @@ func panelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (h *Head
tname = "_" + theme.Name
}
}
h.AddPreScriptAsync("template_" + name + tname + ".js")
h.AddPreScriptAsync("tmpl_" + name + tname + ".js")
}
addPreScript("alert")
addPreScript("notice")
@ -172,12 +172,12 @@ func panelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (h *Head
return h, stats, nil
}
func simplePanelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (headerLite *HeaderLite, rerr RouteError) {
return SimpleUserCheck(w, r, user)
func simplePanelUserCheck(w http.ResponseWriter, r *http.Request, u *User) (lite *HeaderLite, rerr RouteError) {
return SimpleUserCheck(w, r, u)
}
// SimpleUserCheck is back from the grave, yay :D
func simpleUserCheck(w http.ResponseWriter, r *http.Request, user *User) (headerLite *HeaderLite, rerr RouteError) {
func simpleUserCheck(w http.ResponseWriter, r *http.Request, u *User) (lite *HeaderLite, rerr RouteError) {
return &HeaderLite{
Site: Site,
Settings: SettingBox.Load().(SettingMap),
@ -201,20 +201,20 @@ func GetThemeByReq(r *http.Request) *Theme {
return theme
}
func userCheck(w http.ResponseWriter, r *http.Request, user *User) (header *Header, rerr RouteError) {
return userCheck2(w, r, user, uutils.Nanotime())
func userCheck(w http.ResponseWriter, r *http.Request, u *User) (h *Header, rerr RouteError) {
return userCheck2(w, r, u, uutils.Nanotime())
}
// TODO: Add the ability for admins to restrict certain themes to certain groups?
// ! Be careful about firing errors off here as CustomError uses this
func userCheck2(w http.ResponseWriter, r *http.Request, user *User, nano int64) (h *Header, rerr RouteError) {
func userCheck2(w http.ResponseWriter, r *http.Request, u *User, nano int64) (h *Header, rerr RouteError) {
theme := GetThemeByReq(r)
h = &Header{
Site: Site,
Settings: SettingBox.Load().(SettingMap),
Themes: Themes,
Theme: theme,
CurrentUser: user, // ! Some things rely on this being a pointer downstream from this function
CurrentUser: u, // ! Some things rely on this being a pointer downstream from this function
Hooks: GetHookTable(),
Zone: "frontend",
Writer: w,
@ -222,24 +222,24 @@ func userCheck2(w http.ResponseWriter, r *http.Request, user *User, nano int64)
StartedAt: nano,
}
// TODO: Optimise this by avoiding accessing a map string index
if !user.Loggedin {
if !u.Loggedin {
h.GoogSiteVerify = h.Settings["google_site_verify"].(string)
}
if user.IsBanned {
if u.IsBanned {
h.AddNotice("account_banned")
}
if user.Loggedin && !user.Active {
if u.Loggedin && !u.Active {
h.AddNotice("account_inactive")
}
// An optimisation so we don't populate StartedAt for users who shouldn't see the stat anyway
// ? - Should we only show this in debug mode? It might be useful for detecting issues in production, if we show it there as-well
//if user.IsAdmin {
//if u.IsAdmin {
//h.StartedAt = time.Now()
//}
//PrepResources(user,h,theme)
//PrepResources(u,h,theme)
return h, nil
}
@ -280,7 +280,7 @@ func PrepResources(u *User, h *Header, theme *Theme) {
}
}
//fmt.Printf("tname %+v\n", tname)
h.AddPreScriptAsync("template_" + name + tname + ".js")
h.AddPreScriptAsync("tmpl_" + name + tname + ".js")
}
addPreScript("topics_topic")
addPreScript("paginator")
@ -432,63 +432,63 @@ func ChangeAvatar(path string, w http.ResponseWriter, r *http.Request, user *Use
}
// SuperAdminOnly makes sure that only super admin can access certain critical panel routes
func SuperAdminOnly(w http.ResponseWriter, r *http.Request, user *User) RouteError {
if !user.IsSuperAdmin {
return NoPermissions(w, r, user)
func SuperAdminOnly(w http.ResponseWriter, r *http.Request, u *User) RouteError {
if !u.IsSuperAdmin {
return NoPermissions(w, r, u)
}
return nil
}
// AdminOnly makes sure that only admins can access certain panel routes
func AdminOnly(w http.ResponseWriter, r *http.Request, user *User) RouteError {
if !user.IsAdmin {
return NoPermissions(w, r, user)
func AdminOnly(w http.ResponseWriter, r *http.Request, u *User) RouteError {
if !u.IsAdmin {
return NoPermissions(w, r, u)
}
return nil
}
// SuperModeOnly makes sure that only super mods or higher can access the panel routes
func SuperModOnly(w http.ResponseWriter, r *http.Request, user *User) RouteError {
if !user.IsSuperMod {
return NoPermissions(w, r, user)
func SuperModOnly(w http.ResponseWriter, r *http.Request, u *User) RouteError {
if !u.IsSuperMod {
return NoPermissions(w, r, u)
}
return nil
}
// MemberOnly makes sure that only logged in users can access this route
func MemberOnly(w http.ResponseWriter, r *http.Request, user *User) RouteError {
if !user.Loggedin {
return LoginRequired(w, r, user)
func MemberOnly(w http.ResponseWriter, r *http.Request, u *User) RouteError {
if !u.Loggedin {
return LoginRequired(w, r, u)
}
return nil
}
// NoBanned stops any banned users from accessing this route
func NoBanned(w http.ResponseWriter, r *http.Request, user *User) RouteError {
if user.IsBanned {
return Banned(w, r, user)
func NoBanned(w http.ResponseWriter, r *http.Request, u *User) RouteError {
if u.IsBanned {
return Banned(w, r, u)
}
return nil
}
func ParseForm(w http.ResponseWriter, r *http.Request, user *User) RouteError {
if err := r.ParseForm(); err != nil {
return LocalError("Bad Form", w, r, user)
func ParseForm(w http.ResponseWriter, r *http.Request, u *User) RouteError {
if e := r.ParseForm(); e != nil {
return LocalError("Bad Form", w, r, u)
}
return nil
}
func NoSessionMismatch(w http.ResponseWriter, r *http.Request, user *User) RouteError {
if err := r.ParseForm(); err != nil {
return LocalError("Bad Form", w, r, user)
func NoSessionMismatch(w http.ResponseWriter, r *http.Request, u *User) RouteError {
if e := r.ParseForm(); e != nil {
return LocalError("Bad Form", w, r, u)
}
// TODO: Try to eliminate some of these allocations
sess := []byte(user.Session)
sess := []byte(u.Session)
if len(sess) == 0 {
return SecurityError(w, r, user)
return SecurityError(w, r, u)
}
if subtle.ConstantTimeCompare([]byte(r.FormValue("session")), sess) != 1 && subtle.ConstantTimeCompare([]byte(r.FormValue("s")), sess) != 1 {
return SecurityError(w, r, user)
return SecurityError(w, r, u)
}
return nil
}
@ -497,29 +497,29 @@ func ReqIsJson(r *http.Request) bool {
return r.Header.Get("Content-type") == "application/json"
}
func HandleUploadRoute(w http.ResponseWriter, r *http.Request, user *User, maxFileSize int) RouteError {
func HandleUploadRoute(w http.ResponseWriter, r *http.Request, u *User, maxFileSize int) RouteError {
// TODO: Reuse this code more
if r.ContentLength > int64(maxFileSize) {
size, unit := ConvertByteUnit(float64(maxFileSize))
return CustomError("Your upload is too big. Your files need to be smaller than "+strconv.Itoa(int(size))+unit+".", http.StatusExpectationFailed, "Error", w, r, nil, user)
return CustomError("Your upload is too big. Your files need to be smaller than "+strconv.Itoa(int(size))+unit+".", http.StatusExpectationFailed, "Error", w, r, nil, u)
}
r.Body = http.MaxBytesReader(w, r.Body, r.ContentLength)
err := r.ParseMultipartForm(int64(Megabyte))
if err != nil {
return LocalError("Bad Form", w, r, user)
return LocalError("Bad Form", w, r, u)
}
return nil
}
func NoUploadSessionMismatch(w http.ResponseWriter, r *http.Request, user *User) RouteError {
func NoUploadSessionMismatch(w http.ResponseWriter, r *http.Request, u *User) RouteError {
// TODO: Try to eliminate some of these allocations
sess := []byte(user.Session)
sess := []byte(u.Session)
if len(sess) == 0 {
return SecurityError(w, r, user)
return SecurityError(w, r, u)
}
if subtle.ConstantTimeCompare([]byte(r.FormValue("session")), sess) != 1 && subtle.ConstantTimeCompare([]byte(r.FormValue("s")), sess) != 1 {
return SecurityError(w, r, user)
return SecurityError(w, r, u)
}
return nil
}

View File

@ -389,7 +389,7 @@ func compileTemplates(wg *sync.WaitGroup, c *tmpl.CTemplateSet, themeName string
if content == "" {
return //log.Fatal("No content body for " + name)
}
err := writeFile("./template_"+name+".go", content)
err := writeFile("./tmpl_"+name+".go", content)
if err != nil {
log.Fatal(err)
}
@ -584,7 +584,7 @@ func compileJSTemplates(wg *sync.WaitGroup, c *tmpl.CTemplateSet, themeName stri
if tname != "" {
tname = "_" + tname
}
err := writeFile(dirPrefix+"template_"+name+tname+".jgo", content)
err := writeFile(dirPrefix+"tmpl_"+name+tname+".jgo", content)
if err != nil {
log.Fatal(err)
}
@ -676,7 +676,7 @@ func writeTemplateList(c *tmpl.CTemplateSet, wg *sync.WaitGroup, prefix string)
log.Print("Writing template list")
wg.Add(1)
go func() {
err := writeFile(prefix+"template_list.go", getTemplateList(c, wg, prefix))
err := writeFile(prefix+"tmpl_list.go", getTemplateList(c, wg, prefix))
if err != nil {
log.Fatal(err)
}
@ -685,24 +685,24 @@ func writeTemplateList(c *tmpl.CTemplateSet, wg *sync.WaitGroup, prefix string)
wg.Wait()
}
func arithToInt64(in interface{}) (out int64) {
func arithToInt64(in interface{}) (o int64) {
switch in := in.(type) {
case int64:
out = in
o = in
case int32:
out = int64(in)
o = int64(in)
case int:
out = int64(in)
o = int64(in)
case uint32:
out = int64(in)
o = int64(in)
case uint16:
out = int64(in)
o = int64(in)
case uint8:
out = int64(in)
o = int64(in)
case uint:
out = int64(in)
o = int64(in)
}
return out
return o
}
func arithDuoToInt64(left, right interface{}) (leftInt, rightInt int64) {

View File

@ -221,11 +221,11 @@ import "errors"
if !c.config.SkipInitBlock {
stub += "// nolint\nfunc init() {\n"
if !c.config.SkipHandles && c.themeName == "" {
stub += "\tc.Template_" + fname + "_handle = Template_" + fname + "\n"
stub += "\tc.Tmpl_" + fname + "_handle = Tmpl_" + fname + "\n"
stub += "\tc.Ctemplates = append(c.Ctemplates,\"" + fname + "\")\n"
}
if !c.config.SkipTmplPtrMap {
stub += "tmpl := Template_" + fname + "\n"
stub += "tmpl := Tmpl_" + fname + "\n"
stub += "\tc.TmplPtrMap[\"" + fname + "\"] = &tmpl\n"
stub += "\tc.TmplPtrMap[\"o_" + fname + "\"] = tmpl\n"
}
@ -235,15 +235,15 @@ import "errors"
// TODO: Try to remove this redundant interface cast
stub += `
// nolint
func Template_` + fname + `(tmpl_i interface{}, w io.Writer) error {
func Tmpl_` + fname + `(tmpl_i interface{}, w io.Writer) error {
tmpl_vars, ok := tmpl_i.(` + expects + `)
if !ok {
return errors.New("invalid page struct value")
}
if tmpl_vars.CurrentUser.Loggedin {
return Template_` + fname + `_member(tmpl_i, w)
return Tmpl_` + fname + `_member(tmpl_i, w)
}
return Template_` + fname + `_guest(tmpl_i, w)
return Tmpl_` + fname + `_guest(tmpl_i, w)
}`
c.fileDir = fileDir
@ -453,15 +453,20 @@ func (c *CTemplateSet) compile(name, content, expects string, expectsInt interfa
if c.lang == "js" {
var l string
if len(c.langIndexToName) > 0 {
for _, name := range c.langIndexToName {
l += "\t" + `"` + name + `"` + ",\n"
for i, name := range c.langIndexToName {
//l += `"` + name + `"` + ",\n"
if i == 0{
l += `"` + name + `"`
} else {
l += `,"` + name + `"`
}
}
}
if len(l) > 0 {
/*if len(l) > 0 {
l = "\n" + l
}
fout += "if(tmplInits===undefined) var tmplInits = {}\n"
fout += "tmplInits[\"template_" + fname + "\"] = [" + l + "]\n"
}*/
fout += "if(tmplInits===undefined) var tmplInits={}\n"
fout += "tmplInits[\"tmpl_" + fname + "\"]=[" + l + "]"
} else if !c.config.SkipInitBlock {
if len(c.langIndexToName) > 0 {
fout += "var " + fname + "_tmpl_phrase_id int\n\n"
@ -470,12 +475,12 @@ func (c *CTemplateSet) compile(name, content, expects string, expectsInt interfa
fout += "// nolint\nfunc init() {\n"
if !c.config.SkipHandles && c.themeName == "" {
fout += "\tc.Template_" + fname + "_handle = Template_" + fname + "\n"
fout += "\tc.Tmpl_" + fname + "_handle = Tmpl_" + fname + "\n"
fout += "\tc.Ctemplates = append(c.Ctemplates,\"" + fname + "\")\n"
}
if !c.config.SkipTmplPtrMap {
fout += "tmpl := Template_" + fname + "\n"
fout += "tmpl := Tmpl_" + fname + "\n"
fout += "\tc.TmplPtrMap[\"" + fname + "\"] = &tmpl\n"
fout += "\tc.TmplPtrMap[\"o_" + fname + "\"] = tmpl\n"
}
@ -495,7 +500,7 @@ func (c *CTemplateSet) compile(name, content, expects string, expectsInt interfa
}
if c.lang == "normal" {
fout += "// nolint\nfunc Template_" + fname + "(tmpl_i interface{}, w io.Writer) error {\n"
fout += "// nolint\nfunc Tmpl_" + fname + "(tmpl_i interface{}, w io.Writer) error {\n"
fout += `tmpl_` + fname + `_vars, ok := tmpl_i.(` + expects + `)
if !ok {
return errors.New("invalid page struct value")
@ -514,8 +519,8 @@ if !ok {
_ = iw
`
} else {
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"
fout += "// nolint\nfunc Tmpl_" + fname + "(tmpl_" + fname + "_vars interface{}, w io.Writer) error {\n"
//fout += "// nolint\nfunc Tmpl_" + fname + "(tmpl_vars interface{}, w io.Writer) error {\n"
}
if len(c.langIndexToName) > 0 {

View File

@ -133,11 +133,11 @@ easyjson -pkg common
go get
rm -f template_*.go
rm -f tmpl_*.go
rm -f gen_*.go
rm -f tmpl_client/template_*.go
rm -f tmpl_client/tmpl_*.go
rm -f ./Gosora

View File

@ -5,7 +5,7 @@ package main
import (
"log"
"strings"
"bytes"
//"bytes"
"strconv"
"compress/gzip"
"sync"
@ -1058,12 +1058,6 @@ func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
r.requestLogger.Print("before PreRoute")
}
var extraData string
if req.URL.Path[len(req.URL.Path) - 1] != '/' {
extraData = req.URL.Path[strings.LastIndexByte(req.URL.Path,'/') + 1:]
req.URL.Path = req.URL.Path[:strings.LastIndexByte(req.URL.Path,'/') + 1]
}
/*if c.Dev.QuicPort != 0 {
w.Header().Set("Alt-Svc", "quic=\":"+strconv.Itoa(c.Dev.QuicPort)+"\"; ma=2592000; v=\"44,43,39\", h3-23=\":"+strconv.Itoa(c.Dev.QuicPort)+"\"; ma=3600, h3-24=\":"+strconv.Itoa(c.Dev.QuicPort)+"\"; ma=3600, h2=\":443\"; ma=3600")
}*/
@ -1093,7 +1087,7 @@ func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
for _, it := range uutils.StringToBytes(ua) {
if (it > 64 && it < 91) || (it > 96 && it < 123) || it == '_' {
buffer = append(buffer, it)
} else if it == ' ' || it == '(' || it == ')' || it == '-' || (it > 47 && it < 58) || it == ';' || it == ':' || it == '.' || it == '+' || it == '~' || it == '@' || (it == ':' && bytes.Equal(buffer,[]byte("http"))) || it == ',' || it == '/' {
} else if it == ' ' || it == '(' || it == ')' || it == '-' || (it > 47 && it < 58) || it == ';' || it == ':' || it == '.' || it == '+' || it == '~' || it == '@' /*|| (it == ':' && bytes.Equal(buffer,[]byte("http")))*/ || it == ',' || it == '/' {
if len(buffer) != 0 {
if len(buffer) > 2 {
// Use an unsafe zero copy conversion here just to use the switch, it's not safe for this string to escape from here, as it will get mutated, so do a regular string conversion in append
@ -1120,8 +1114,8 @@ func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// TODO: Test this
items = items[:0]
r.SuspiciousRequest(req,"Illegal char "+strconv.Itoa(int(it))+" in UA")
r.requestLogger.Print("UA Buffer: ", buffer)
r.requestLogger.Print("UA Buffer String: ", string(buffer))
r.requestLogger.Print("UA Buf: ", buffer)
r.requestLogger.Print("UA Buf String: ", string(buffer))
break
}
}
@ -1245,6 +1239,12 @@ func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
w = gzw
}
var extraData string
if req.URL.Path[len(req.URL.Path) - 1] != '/' {
extraData = req.URL.Path[strings.LastIndexByte(req.URL.Path,'/') + 1:]
req.URL.Path = req.URL.Path[:strings.LastIndexByte(req.URL.Path,'/') + 1]
}
skip, ferr = hTbl.VhookSkippable("router_pre_route", w, req, user, prefix, extraData)
if skip || ferr != nil {
r.handleError(ferr,w,req,user)

View File

@ -1,7 +1,9 @@
echo "Deleting artifacts from previous builds"
rm -f template_*.go
rm -f tmpl_*.go
rm -f gen_*.go
rm -f tmpl_client/template_*.go
rm -f tmpl_client/template_*
rm -f tmpl_client/tmpl_*
rm -f ./Gosora
echo "Generating the dynamic code"

View File

@ -18,7 +18,7 @@ var forumToMoveTo = 0;
function pushNotice(msg) {
let aBox = document.getElementsByClassName("alertbox")[0];
let div = document.createElement('div');
div.innerHTML = Template_notice(msg).trim();
div.innerHTML = Tmpl_notice(msg).trim();
aBox.appendChild(div);
runInitHook("after_notice");
}
@ -26,9 +26,9 @@ function pushNotice(msg) {
// TODO: Write a friendlier error handler which uses a .notice or something, we could have a specialised one for alerts
function ajaxError(xhr,status,errstr) {
console.log("The AJAX request failed");
console.log("xhr", xhr);
console.log("status", status);
console.log("err", errstr);
console.log("xhr",xhr);
console.log("status",status);
console.log("err",errstr);
if(status=="parsererror") console.log("The server didn't respond with a valid JSON response");
console.trace();
}
@ -65,7 +65,7 @@ function addAlert(msg, notice=false) {
for(var i = 0; i < msg.sub.length; i++) mmsg = mmsg.replace("\{"+i+"\}", msg.sub[i]);
}
let aItem = Template_alert({
let aItem = Tmpl_alert({
ASID: msg.id,
Path: msg.path,
Avatar: msg.img || "",
@ -291,12 +291,12 @@ function runWebSockets(resume=false) {
console.log("topic in data");
console.log("data",data);
let topic = data.Topics[0];
if(topic === undefined){
if(topic===undefined){
console.log("empty topic list");
return;
}
// TODO: Fix the data race where the function hasn't been loaded yet
let renTopic = Template_topics_topic(topic);
let renTopic = Tmpl_topics_topic(topic);
$(".topic_row[data-tid='"+topic.ID+"']").addClass("ajax_topic_dupe");
let node = $(renTopic);
@ -316,8 +316,7 @@ function runWebSockets(resume=false) {
msgBox.innerText = phraseBox["topic_list"]["topic_list.changed_topics"].replace("%d",moreTopicCount);
}
} else {
console.log("unknown message");
console.log(data);
console.log("unknown message", data);
}
}
@ -353,10 +352,10 @@ function getExt(name) {
console.log("before notify on alert")
// We can only get away with this because template_alert has no phrases, otherwise it too would have to be part of the "dance", I miss Go concurrency :(
if(!noAlerts) {
notifyOnScriptW("template_alert", e => {
notifyOnScriptW("tmpl_alert", e => {
if(e!=undefined) console.log("failed alert? why?",e)
}, () => {
if(!Template_alert) throw("template function not found");
if(!Tmpl_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(() => {
@ -381,7 +380,7 @@ function getExt(name) {
// TODO: Use these in .filter_item and pass back an item count from the backend to work with here
// Ported from common/parser.go
function PageOffset(count, page, perPage) {
function PageOffset(count,page,perPage) {
let offset = 0;
let lastPage = LastPage(count, perPage)
if(page > 1) offset = (perPage * page) - perPage;
@ -394,10 +393,10 @@ function PageOffset(count, page, perPage) {
//if(offset >= (count - 1)) offset = 0;
return {Offset:offset, Page:page, LastPage:lastPage};
}
function LastPage(count, perPage) {
function LastPage(count,perPage) {
return (count / perPage) + 1
}
function Paginate(currentPage, lastPage, maxPages) {
function Paginate(currentPage,lastPage,maxPages) {
let diff = lastPage - currentPage;
let pre = 3;
if(diff < 3) pre = maxPages - diff;
@ -488,13 +487,13 @@ function mainInit(){
if(page=="") page = 1;
let pageList = Paginate(page,lastPage,5)
//$(".pageset").html(Template_paginator({PageList: pageList, Page: page, LastPage: lastPage}));
//$(".pageset").html(Tmpl_paginator({PageList: pageList, Page: page, LastPage: lastPage}));
let ok = false;
$(".pageset").each(function(){
this.outerHTML = Template_paginator({PageList: pageList, Page: page, LastPage: lastPage});
this.outerHTML = Tmpl_paginator({PageList: pageList, Page: page, LastPage: lastPage});
ok = true;
});
if(!ok) $(Template_paginator({PageList: pageList, Page: page, LastPage: lastPage})).insertAfter("#topic_list");
if(!ok) $(Tmpl_paginator({PageList: pageList, Page: page, LastPage: lastPage})).insertAfter("#topic_list");
}
function rebindPaginator() {
@ -521,7 +520,7 @@ function mainInit(){
// TODO: Fix the data race where the function hasn't been loaded yet
let out = "";
for(let i = 0; i < topics.length;i++) out += Template_topics_topic(topics[i]);
for(let i = 0; i < topics.length;i++) out += Tmpl_topics_topic(topics[i]);
$(".topic_list").html(out);
let obj = {Title: document.title, Url: url+q};
@ -530,7 +529,7 @@ function mainInit(){
rebindPaginator();
}).catch(ex => {
console.log("Unable to get script '"+url+q+"&js=1"+"'");
console.log("ex", ex);
console.log("ex",ex);
console.trace();
});
});
@ -557,7 +556,7 @@ function mainInit(){
// TODO: Fix the data race where the function hasn't been loaded yet
let out = "";
for(let i = 0; i < topics.length;i++) out += Template_topics_topic(topics[i]);
for(let i = 0; i < topics.length;i++) out += Tmpl_topics_topic(topics[i]);
$(".topic_list").html(out);
//$(".topic_list").addClass("single_forum");
@ -608,7 +607,7 @@ function mainInit(){
// TODO: Fix the data race where the function hasn't been loaded yet
let out = "";
for(let i = 0; i < topics.length;i++) out += Template_topics_topic(topics[i]);
for(let i = 0; i < topics.length;i++) out += Tmpl_topics_topic(topics[i]);
$(".topic_list").html(out);
baseTitle = phraseBox["topic_list"]["topic_list.search_head"];
@ -791,7 +790,7 @@ function mainInit(){
console.log("date", date);
let minutes = "0" + date.getMinutes();
let formattedTime = date.getHours() + ":" + minutes.substr(-2);
console.log("formattedTime", formattedTime);
console.log("formattedTime",formattedTime);
this.innerText = formattedTime;
});
@ -799,7 +798,7 @@ function mainInit(){
// TODO: Localise this
let monthList = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
let date = new Date(this.innerText * 1000);
console.log("date", date);
console.log("date",date);
let day = "0" + date.getDate();
let formattedTime = monthList[date.getMonth()] + " " + day.substr(-2) + " " + date.getFullYear();
console.log("formattedTime",formattedTime);
@ -976,7 +975,7 @@ function bindTopic() {
let src = "";
if(srcNode!=null) src = srcNode.innerText;
else src = block.innerHTML;
block.innerHTML = Template_topic_c_edit_post({
block.innerHTML = Tmpl_topic_c_edit_post({
ID: bp.getAttribute("id").slice("post-".length),
Source: src,
Ref: this.closest('a').getAttribute("href")
@ -1014,13 +1013,13 @@ function bindTopic() {
ev.stopPropagation();
let src = this.closest(".post_item").getElementsByClassName("edit_source")[0];
let content = document.getElementById("input_content")
console.log("content.value", content.value);
console.log("content.value",content.value);
let item;
if(content.value=="") item = "<blockquote>" + src.innerHTML + "</blockquote>"
else item = "\r\n<blockquote>" + src.innerHTML + "</blockquote>";
content.value = content.value + item;
console.log("content.value", content.value);
console.log("content.value",content.value);
// For custom / third party text editors
quoteItemCallback(src.innerHTML,item);

View File

@ -185,7 +185,7 @@ function fetchPhrases(plist) {
(() => {
runInitHook("pre_iife");
let loggedIn = document.head.querySelector("[property='x-loggedin']").content=="true";
let loggedIn = document.head.querySelector("[property='x-mem']")!=null;
let panel = window.location.pathname.startsWith("/panel/");
let toLoad = 1;
@ -200,21 +200,20 @@ function fetchPhrases(plist) {
toLoad += 2;
if(loggedIn) {
toLoad += 3;
notifyOnScriptW("template_topic_c_edit_post", () => q(!Template_topic_c_edit_post));
notifyOnScriptW("template_topic_c_attach_item", () => q(!Template_topic_c_attach_item));
notifyOnScriptW("template_topic_c_poll_input", () => q(!Template_topic_c_poll_input));
notifyOnScriptW("tmpl_topic_c_edit_post", () => q(!Tmpl_topic_c_edit_post));
notifyOnScriptW("tmpl_topic_c_attach_item", () => q(!Tmpl_topic_c_attach_item));
notifyOnScriptW("tmpl_topic_c_poll_input", () => q(!Tmpl_topic_c_poll_input));
}
notifyOnScriptW("template_topics_topic", () => q(!Template_topics_topic));
notifyOnScriptW("template_paginator", () => q(!Template_paginator));
notifyOnScriptW("tmpl_topics_topic", () => q(!Tmpl_topics_topic));
notifyOnScriptW("tmpl_paginator", () => q(!Tmpl_paginator));
}
notifyOnScriptW("template_notice", () => q(!Template_notice));
notifyOnScriptW("tmpl_notice", () => q(!Tmpl_notice));
if(loggedIn) {
fetch("/api/me/")
.then(resp => resp.json())
.then(data => {
console.log("loaded me endpoint data");
console.log("data",data);
console.log("loaded me endpoint data",data);
me = data;
runInitHook("pre_init");
});

View File

@ -76,7 +76,7 @@ var imageExts = ["png", "jpg", "jpe","jpeg","jif","jfi","jfif", "svg", "bmp", "g
let c = "";
if(isImage) c = " attach_image_holder"
fileItem.className = "attach_item attach_item_item" + c;
fileItem.innerHTML = Template_topic_c_attach_item({
fileItem.innerHTML = Tmpl_topic_c_attach_item({
ID: data.elems[hash+"."+ext],
ImgSrc: isImage ? e.target.result : "",
Path: hash+"."+ext,
@ -286,7 +286,7 @@ var imageExts = ["png", "jpg", "jpe","jpeg","jif","jfi","jfif", "svg", "bmp", "g
console.log("dataPollInput",dataPollInput);
if(dataPollInput==undefined) return;
if(dataPollInput!=(pollInputIndex-1)) return;
$(".poll_content_row .formitem").append(Template_topic_c_poll_input({
$(".poll_content_row .formitem").append(Tmpl_topic_c_poll_input({
Index: pollInputIndex,
Place: phraseBox["topic"]["topic.reply_add_poll_option"].replace("%d",pollInputIndex),
}));

View File

@ -416,7 +416,7 @@ package main
import (
"log"
"strings"
"bytes"
//"bytes"
"strconv"
"compress/gzip"
"sync"
@ -736,12 +736,6 @@ func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
r.requestLogger.Print("before PreRoute")
}
var extraData string
if req.URL.Path[len(req.URL.Path) - 1] != '/' {
extraData = req.URL.Path[strings.LastIndexByte(req.URL.Path,'/') + 1:]
req.URL.Path = req.URL.Path[:strings.LastIndexByte(req.URL.Path,'/') + 1]
}
/*if c.Dev.QuicPort != 0 {
w.Header().Set("Alt-Svc", "quic=\":"+strconv.Itoa(c.Dev.QuicPort)+"\"; ma=2592000; v=\"44,43,39\", h3-23=\":"+strconv.Itoa(c.Dev.QuicPort)+"\"; ma=3600, h3-24=\":"+strconv.Itoa(c.Dev.QuicPort)+"\"; ma=3600, h2=\":443\"; ma=3600")
}*/
@ -771,7 +765,7 @@ func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
for _, it := range uutils.StringToBytes(ua) {
if (it > 64 && it < 91) || (it > 96 && it < 123) || it == '_' {
buffer = append(buffer, it)
} else if it == ' ' || it == '(' || it == ')' || it == '-' || (it > 47 && it < 58) || it == ';' || it == ':' || it == '.' || it == '+' || it == '~' || it == '@' || (it == ':' && bytes.Equal(buffer,[]byte("http"))) || it == ',' || it == '/' {
} else if it == ' ' || it == '(' || it == ')' || it == '-' || (it > 47 && it < 58) || it == ';' || it == ':' || it == '.' || it == '+' || it == '~' || it == '@' /*|| (it == ':' && bytes.Equal(buffer,[]byte("http")))*/ || it == ',' || it == '/' {
if len(buffer) != 0 {
if len(buffer) > 2 {
// Use an unsafe zero copy conversion here just to use the switch, it's not safe for this string to escape from here, as it will get mutated, so do a regular string conversion in append
@ -798,8 +792,8 @@ func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// TODO: Test this
items = items[:0]
r.SuspiciousRequest(req,"Illegal char "+strconv.Itoa(int(it))+" in UA")
r.requestLogger.Print("UA Buffer: ", buffer)
r.requestLogger.Print("UA Buffer String: ", string(buffer))
r.requestLogger.Print("UA Buf: ", buffer)
r.requestLogger.Print("UA Buf String: ", string(buffer))
break
}
}
@ -923,6 +917,12 @@ func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
w = gzw
}
var extraData string
if req.URL.Path[len(req.URL.Path) - 1] != '/' {
extraData = req.URL.Path[strings.LastIndexByte(req.URL.Path,'/') + 1:]
req.URL.Path = req.URL.Path[:strings.LastIndexByte(req.URL.Path,'/') + 1]
}
skip, ferr = hTbl.VhookSkippable("router_pre_route", w, req, user, prefix, extraData)
if skip || ferr != nil {
r.handleError(ferr,w,req,user)

View File

@ -1,7 +1,9 @@
echo "Deleting artifacts from previous builds"
rm -f template_*.go
rm -f tmpl_*.go
rm -f gen_*.go
rm -f tmpl_client/template_*.go
rm -f tmpl_client/template_*
rm -f tmpl_client/tmpl_*
rm -f ./Gosora
echo "Generating the dynamic code"

View File

@ -1,7 +1,9 @@
echo "Deleting artifacts from previous builds"
rm -f template_*.go
rm -f tmpl_*.go
rm -f gen_*.go
rm -f tmpl_client/template_*.go
rm -f tmpl_client/template_*
rm -f tmpl_client/tmpl_*
rm -f ./Gosora
echo "Generating the dynamic code"

View File

@ -1,9 +1,11 @@
@echo off
rem TODO: Make these deletes a little less noisy
del "template_*.go"
del "tmpl_*.go"
del "gen_*.go"
cd tmpl_client
del "template_*.go"
del "template_*"
del "tmpl_*"
cd ..
del "gosora.exe"

View File

@ -1,9 +1,11 @@
@echo off
rem TODO: Make these deletes a little less noisy
del "template_*.go"
del "tmpl_*.go"
del "gen_*.go"
cd tmpl_client
del "template_*.go"
del "template_*"
del "tmpl_*"
cd ..
del "gosora.exe"
@ -58,7 +60,7 @@ if %errorlevel% neq 0 (
)
echo Building the executable... again
go build -ldflags="-s -w" -o gosora.exe
go build -ldflags="-s -w" -gcflags="-d=ssa/check_bce/debug=1" -o gosora.exe
if %errorlevel% neq 0 (
pause
exit /b %errorlevel%

View File

@ -1,9 +1,11 @@
@echo off
rem TODO: Make these deletes a little less noisy
del "template_*.go"
del "tmpl_*.go"
del "gen_*.go"
cd tmpl_client
del "template_*.go"
del "template_*"
del "tmpl_*"
cd ..
del "gosora.exe"

View File

@ -1,9 +1,11 @@
@echo off
rem TODO: Make these deletes a little less noisy
del "template_*.go"
del "tmpl_*.go"
del "gen_*.go"
cd tmpl_client
del "template_*.go"
del "template_*"
del "tmpl_*"
cd ..
del "gosora.exe"

View File

@ -1,9 +1,11 @@
@echo off
rem TODO: Make these deletes a little less noisy
del "template_*.go"
del "tmpl_*.go"
del "gen_*.go"
cd tmpl_client
del "template_*.go"
del "template_*"
del "tmpl_*"
cd ..
del "gosora.exe"

View File

@ -6,8 +6,8 @@
<link href="/s/{{.}}"rel="stylesheet"type="text/css">{{end}}
{{range .Header.PreScriptsAsync}}
<script async src="/s/{{.}}"></script>{{end}}
<meta property="x-loggedin"content="{{.CurrentUser.Loggedin}}">
<script src="/s/init.js?i=10"></script>
{{if .CurrentUser.Loggedin}}<meta property="x-mem"content="1">{{end}}
<script src="/s/init.js?i=11"></script>
{{range .Header.ScriptsAsync}}
<script async src="/s/{{.}}"></script>{{end}}
<script src="/s/jquery-3.1.1.min.js"></script>

View File

@ -18,12 +18,10 @@
else $('.alert').insertAfter(".rowhead:first");
}
//console.log("bf")
addInitHook("end_init", () => {
//console.log("af")
let loggedIn = document.head.querySelector("[property='x-loggedin']").content=="true";
let loggedIn = document.head.querySelector("[property='x-mem']")!=null;
if(loggedIn) {
if(navigator.userAgent.indexOf("Firefox") != -1) $.trumbowyg.svgPath = "/s/trumbowyg/ui/icons.svg";
if(navigator.userAgent.indexOf("Firefox")!=-1) $.trumbowyg.svgPath = "/s/trumbowyg/ui/icons.svg";
// Is there we way we can append instead? Maybe, an editor plugin?
attachItemCallback = function(attachItem) {

View File

@ -1,6 +1,6 @@
<div class="topic_row{{if .Sticky}} topic_sticky{{else if .IsClosed}} topic_closed{{end}}"data-tid={{.ID}}>
<div class="rowitem topic_left passive datarow">
<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="rowtopic"itemprop="itemListElement"title="{{.Title}}"><a href="{{.Link}}">{{.Title}}</a>{{if .ForumName}}<a class="parent_forum_sep">-</a><a href="{{.ForumLink}}"title="{{.ForumName}}"class="rowsmall parent_forum">{{.ForumName}}</a>{{end}}</span>
<br><a class="rowsmall starter"href="{{.Creator.Link}}"title="{{.Creator.Name}}">{{.Creator.Name}}</a>
@ -15,7 +15,7 @@
</div>
<div class="rowitem topic_right passive datarow">
<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>
<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>

View File

@ -31,7 +31,6 @@ func StringToBytes(s string) (bytes []byte) {
runtime.KeepAlive(&s)
return bytes
}
func BytesToString(bytes []byte) (s string) {
slice := (*reflect.SliceHeader)(unsafe.Pointer(&bytes))
str := (*reflect.StringHeader)(unsafe.Pointer(&s))
@ -40,11 +39,9 @@ func BytesToString(bytes []byte) (s string) {
runtime.KeepAlive(&bytes)
return s
}
//go:noescape
//go:linkname nanotime runtime.nanotime
func nanotime() int64
func Nanotime() int64 {
return nanotime()
}*/