Client templates can now be overriden like every other template.

The client templates load earlier now for a smoother user experience.
Added a setting for setting a Google Site Verification meta tag without editing header.html
Added support for favicons. /static/favicon.ico will be mapped to favicon.ico, if it exists.
The parent forum is now visible on the topics list for Nox.

Language headers which contain the wildcard character are no longer considered unknowns.
Meta descriptions and open graph descriptions are no longer emitted for logged in users.
Slimmed down topics_topic slightly for Nox.
Pre-parsed widgets are now minified.
Stale WebSockets connections should be cleaned up far quicker now.
Template generation is now logged separately.
Commented out some obsolete template logic.
Marked a few template generator fields as unexported.

Fixed the styling for the ban page in the profile for Nox.
Fixed the styling for colline for Cosora and Tempra Simple.
Fixed the sidebar overflowing outside of the box on Nox.
Fixed the meta description text overflowing the box in the Setting Manager on Nox.
Fixed excessive padding in the Page Manager.
Fixed a few missing border on the profiles for Tempra Simple.
Fixed the sidebar appearing in places it shouldn't on Tempra Simple.
Fixed the status code emitted by NotFoundJS
Fixed a bug where Gosora kept falling back to interpreted templates.
Fixed a bug where WebSockets connections weren't getting closed properly if the user cache overflowed.
Fixed a bug where WebSocket connections weren't getting initialised for guests.
Fixed a bug where template overrides weren't always getting applied.
Fixed a bug where root template overrides weren't always getting applied.

Added the google_site_verify setting.
Added the google_site_verify phrase.

You will need to run the patcher or updater for this commit.
This commit is contained in:
Azareal 2019-02-28 17:28:17 +10:00
parent fe33112827
commit cc1d0f089a
37 changed files with 671 additions and 190 deletions

View File

@ -125,6 +125,7 @@ func seedTables(adapter qgen.Adapter) error {
qgen.Install.SimpleInsert("settings", "name, content, type", "'megapost_min_words','1000','int'")
qgen.Install.SimpleInsert("settings", "name, content, type", "'meta_desc','','html-attribute'")
qgen.Install.SimpleInsert("settings", "name, content, type", "'rapid_loading','1','bool'")
qgen.Install.SimpleInsert("settings", "name, content, type", "'google_site_verify','','html-attribute'")
qgen.Install.SimpleInsert("themes", "uname, default", "'cosora',1")
qgen.Install.SimpleInsert("emails", "email, uid, validated", "'admin@localhost',1,1") // ? - Use a different default email or let the admin input it during installation?

View File

@ -311,6 +311,12 @@ func SecurityError(w http.ResponseWriter, r *http.Request, user User) RouteError
return HandledRouteError()
}
func MicroNotFound(w http.ResponseWriter, r *http.Request) RouteError {
w.WriteHeader(404)
_, _ = w.Write([]byte("file not found"))
return HandledRouteError()
}
// NotFound is used when the requested page doesn't exist
// ? - Add a JSQ version of this?
// ? - Add a user parameter?
@ -320,7 +326,7 @@ func NotFound(w http.ResponseWriter, r *http.Request, header *Header) RouteError
// ? - Add a user parameter?
func NotFoundJS(w http.ResponseWriter, r *http.Request) RouteError {
w.WriteHeader(401)
w.WriteHeader(404)
writeJsonError(phrases.GetErrorPhrase("not_found_body"), w)
return HandledRouteError()
}

View File

@ -39,17 +39,6 @@ type CSSData struct {
func (list SFileList) JSTmplInit() error {
DebugLog("Initialising the client side templates")
var fragMap = make(map[string][][]byte)
var parseFrags = func(name string) {
fragMap[name] = tmpl.GetFrag(name)
}
parseFrags("alert")
parseFrags("forum")
parseFrags("topics_topic")
parseFrags("topic_posts")
parseFrags("topic_alt_posts")
parseFrags("paginator")
DebugLog("fragMap: ", fragMap)
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") {
return nil
@ -75,7 +64,7 @@ func (list SFileList) JSTmplInit() error {
}
data = data[startIndex-len([]byte("func init() {")):]
data = replace(data, "func ", "function ")
data = replace(data, "function init() {", "tmplInits[\""+tmplName+"\"] = ")
data = replace(data, "function init() {", "if(tmplInits===undefined) var tmplInits = {};\ntmplInits[\""+tmplName+"\"] = ")
data = replace(data, " error {\n", " {\nlet out = \"\"\n")
funcIndex, hasFunc := skipAllUntilCharsExist(data, 0, []byte("function Template_"))
if !hasFunc {
@ -220,19 +209,23 @@ func (list SFileList) JSTmplInit() error {
data = replace(data, `=
}`, "= []")
fragset, ok := fragMap[shortName]
if !ok {
DebugLog("tmplName: ", tmplName)
return errors.New("couldn't find template in fragmap")
fragset := tmpl.GetFrag(shortName)
if fragset != nil {
var sfrags = []byte("let " + shortName + "_frags = [];\n")
for _, frags := range fragset {
sfrags = append(sfrags, []byte(shortName+"_frags.push(`"+string(frags)+"`);\n")...)
}
data = append(sfrags, data...)
}
var sfrags = []byte("let " + shortName + "_frags = [];\n")
for _, frags := range fragset {
sfrags = append(sfrags, []byte(shortName+"_frags.push(`"+string(frags)+"`);\n")...)
}
data = append(sfrags, data...)
data = replace(data, "\n;", "\n")
for name, _ := range Themes {
if strings.HasSuffix(shortName, "_"+name) {
data = append(data, "\nlet Template_"+strings.TrimSuffix(shortName, "_"+name)+" = Template_"+shortName+";"...)
break
}
}
path = tmplName + ".js"
DebugLog("js path: ", path)
var ext = filepath.Ext("/tmpl_client/" + path)

View File

@ -15,8 +15,9 @@ import (
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
NoticeList []string
Scripts []string
PreScriptsAsync []string
//Preload []string
Stylesheets []string
Widgets PageWidgets
@ -33,18 +34,23 @@ type Header struct {
Path string
MetaDesc string
//OGImage string
OGDesc string
LooseCSP bool
StartedAt time.Time
Elapsed1 string
Writer http.ResponseWriter
ExtData ExtData
OGDesc string
GoogSiteVerify string
LooseCSP bool
StartedAt time.Time
Elapsed1 string
Writer http.ResponseWriter
ExtData ExtData
}
func (header *Header) AddScript(name string) {
header.Scripts = append(header.Scripts, name)
}
func (header *Header) AddPreScriptAsync(name string) {
header.PreScriptsAsync = append(header.PreScriptsAsync, name)
}
/*func (header *Header) Preload(name string) {
header.Preload = append(header.Preload, name)
}*/

View File

@ -150,6 +150,18 @@ func panelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (header
stats.Themes = len(Themes)
stats.Reports = 0 // TODO: Do the report count. Only show open threads?
var addPreScript = func(name string) {
var tname string
if theme.OverridenMap != nil {
_, ok := theme.OverridenMap[name+".html"]
if ok {
tname = "_" + theme.Name
}
}
header.AddPreScriptAsync("template_" + name + tname + ".js")
}
addPreScript("alert")
return header, stats, nil
}
@ -182,14 +194,15 @@ func userCheck(w http.ResponseWriter, r *http.Request, user *User) (header *Head
}
header = &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
Hooks: GetHookTable(),
Zone: "frontend",
Writer: w,
Site: Site,
Settings: SettingBox.Load().(SettingMap),
Themes: Themes,
Theme: theme,
CurrentUser: *user, // ! Some things rely on this being a pointer downstream from this function
Hooks: GetHookTable(),
Zone: "frontend",
Writer: w,
GoogSiteVerify: header.Settings["google_site_verify"].(string),
}
if user.IsBanned {
@ -222,6 +235,22 @@ func userCheck(w http.ResponseWriter, r *http.Request, user *User) (header *Head
}
}
var addPreScript = func(name string) {
var tname string
if theme.OverridenMap != nil {
//fmt.Printf("name %+v\n", name)
//fmt.Printf("theme.OverridenMap %+v\n", theme.OverridenMap)
_, ok := theme.OverridenMap[name+".html"]
if ok {
tname = "_" + theme.Name
}
}
header.AddPreScriptAsync("template_" + name + tname + ".js")
}
addPreScript("topics_topic")
addPreScript("paginator")
addPreScript("alert")
return header, nil
}

View File

@ -102,14 +102,15 @@ func tmplInitUsers() (User, User, User) {
func tmplInitHeaders(user User, user2 User, user3 User) (*Header, *Header, *Header) {
header := &Header{
Site: Site,
Settings: SettingBox.Load().(SettingMap),
Themes: Themes,
Theme: Themes[DefaultThemeBox.Load().(string)],
CurrentUser: user,
NoticeList: []string{"test"},
Stylesheets: []string{"panel"},
Scripts: []string{"whatever"},
Site: Site,
Settings: SettingBox.Load().(SettingMap),
Themes: Themes,
Theme: Themes[DefaultThemeBox.Load().(string)],
CurrentUser: user,
NoticeList: []string{"test"},
Stylesheets: []string{"panel.css"},
Scripts: []string{"whatever.js"},
PreScriptsAsync: []string{"whatever.js"},
Widgets: PageWidgets{
LeftSidebar: template.HTML("lalala"),
},
@ -168,7 +169,7 @@ func CompileTemplates() error {
config.Debug = Dev.DebugMode
config.SuperDebug = Dev.TemplateDebug
c := tmpl.NewCTemplateSet()
c := tmpl.NewCTemplateSet("normal")
c.SetConfig(config)
c.SetBaseImportMap(map[string]string{
"io": "io",
@ -189,6 +190,7 @@ func CompileTemplates() error {
log.Print("Compiling the per-theme templates")
for theme, tmpls := range oroots {
c.ResetLogs("normal-" + theme)
c.SetThemeName(theme)
c.SetPerThemeTmpls(tmpls)
log.Print("theme: ", theme)
@ -366,7 +368,156 @@ func compileTemplates(wg *sync.WaitGroup, c *tmpl.CTemplateSet, themeName string
return nil
}
// ? - Add template hooks?
func CompileJSTemplates() error {
log.Print("Compiling the JS templates")
// TODO: Implement per-theme template overrides here too
var overriden = make(map[string]map[string]bool)
for _, theme := range Themes {
overriden[theme.Name] = make(map[string]bool)
log.Printf("theme.OverridenTemplates: %+v\n", theme.OverridenTemplates)
for _, override := range theme.OverridenTemplates {
overriden[theme.Name][override] = true
}
}
log.Printf("overriden: %+v\n", overriden)
var config tmpl.CTemplateConfig
config.Minify = Config.MinifyTemplates
config.Debug = Dev.DebugMode
config.SuperDebug = Dev.TemplateDebug
config.SkipHandles = true
config.SkipTmplPtrMap = true
config.SkipInitBlock = false
config.PackageName = "tmpl"
c := tmpl.NewCTemplateSet("js")
c.SetConfig(config)
c.SetBuildTags("!no_templategen")
c.SetOverrideTrack(overriden)
c.SetPerThemeTmpls(make(map[string]bool))
log.Print("Compiling the default templates")
var wg sync.WaitGroup
err := compileJSTemplates(&wg, c, "")
if err != nil {
return err
}
oroots := c.GetOverridenRoots()
log.Printf("oroots: %+v\n", oroots)
log.Print("Compiling the per-theme templates")
for theme, tmpls := range oroots {
c.SetThemeName(theme)
c.SetPerThemeTmpls(tmpls)
log.Print("theme: ", theme)
log.Printf("perThemeTmpls: %+v\n", tmpls)
err = compileJSTemplates(&wg, c, theme)
if err != nil {
return err
}
}
var dirPrefix = "./tmpl_client/"
writeTemplateList(c, &wg, dirPrefix)
return nil
}
func compileJSTemplates(wg *sync.WaitGroup, c *tmpl.CTemplateSet, themeName string) error {
user, user2, user3 := tmplInitUsers()
header, _, _ := tmplInitHeaders(user, user2, user3)
now := time.Now()
var varList = make(map[string]tmpl.VarItem)
c.SetBaseImportMap(map[string]string{
"io": "io",
"github.com/Azareal/Gosora/common/alerts": "github.com/Azareal/Gosora/common/alerts",
})
// TODO: Check what sort of path is sent exactly and use it here
alertItem := alerts.AlertItem{Avatar: "", ASID: 1, Path: "/", Message: "uh oh, something happened"}
alertTmpl, err := c.Compile("alert.html", "templates/", "alerts.AlertItem", alertItem, varList)
if err != nil {
return err
}
c.SetBaseImportMap(map[string]string{
"io": "io",
"github.com/Azareal/Gosora/common": "github.com/Azareal/Gosora/common",
})
// TODO: Fix the import loop so we don't have to use this hack anymore
c.SetBuildTags("!no_templategen,tmplgentopic")
tmpls := TItemHold(make(map[string]TItem))
var 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", "", &user2, "", 0, &user3, "General", "/forum/general.2"}
tmpls.AddStd("topics_topic", "common.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{
PollOption{0, "Nothing"},
PollOption{1, "Something"},
}, VoteCount: 7}
avatar, microAvatar := BuildAvatar(62, "")
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}
var replyList []ReplyUser
// TODO: Do we really want the UID here to be zero?
avatar, microAvatar = BuildAvatar(0, "")
replyList = append(replyList, ReplyUser{0, 0, "Yo!", "Yo!", 0, "alice", "Alice", Config.DefaultGroup, now, 0, 0, avatar, microAvatar, "", 0, "", "", "", "", 0, "127.0.0.1", false, 1, 1, "", "", miniAttach})
varList = make(map[string]tmpl.VarItem)
header.Title = "Topic Name"
tpage := TopicPage{header, replyList, topic, &Forum{ID: 1, Name: "Hahaha"}, poll, Paginator{[]int{1}, 1, 1}}
tpage.Forum.Link = BuildForumURL(NameToSlug(tpage.Forum.Name), tpage.Forum.ID)
tmpls.AddStd("topic_posts", "common.TopicPage", tpage)
tmpls.AddStd("topic_alt_posts", "common.TopicPage", tpage)
itemsPerPage := 25
_, page, lastPage := PageOffset(20, 1, itemsPerPage)
pageList := Paginate(20, itemsPerPage, 5)
tmpls.AddStd("paginator", "common.Paginator", Paginator{pageList, page, lastPage})
var dirPrefix = "./tmpl_client/"
var writeTemplate = func(name string, content string) {
log.Print("Writing template '" + name + "'")
if content == "" {
return //log.Fatal("No content body")
}
wg.Add(1)
go func() {
tname := themeName
if tname != "" {
tname = "_" + tname
}
err := writeFile(dirPrefix+"template_"+name+tname+".go", content)
if err != nil {
log.Fatal(err)
}
wg.Done()
}()
}
log.Print("Writing the templates")
for name, titem := range tmpls {
log.Print("Writing " + name)
varList := make(map[string]tmpl.VarItem)
tmpl, err := c.Compile(name+".html", "templates/", titem.Expects, titem.ExpectsInt, varList)
if err != nil {
return err
}
writeTemplate(name, tmpl)
}
writeTemplate("alert", alertTmpl)
/*//writeTemplate("forum", forumTmpl)
writeTemplate("topics_topic", topicListItemTmpl)
writeTemplate("topic_posts", topicPostsTmpl)
writeTemplate("topic_alt_posts", topicAltPostsTmpl)
writeTemplate("paginator", paginatorTmpl)
//writeTemplate("panel_themes_widgets_widget", panelWidgetsWidgetTmpl)
writeTemplateList(c, &wg, dirPrefix)*/
return nil
}
/*func CompileJSTemplates() error {
log.Print("Compiling the JS templates")
var config tmpl.CTemplateConfig
config.Minify = Config.MinifyTemplates
@ -443,12 +594,6 @@ func CompileJSTemplates() error {
return err
}
/*widget := &Widget{ID: 0}
panelWidgetsWidgetTmpl, err := c.Compile("panel_themes_widgets_widget.html", "templates/", "*common.Widget", widget, varList)
if err != nil {
return err
}*/
var dirPrefix = "./tmpl_client/"
var wg sync.WaitGroup
var writeTemplate = func(name string, content string) {
@ -474,9 +619,10 @@ func CompileJSTemplates() error {
//writeTemplate("panel_themes_widgets_widget", panelWidgetsWidgetTmpl)
writeTemplateList(c, &wg, dirPrefix)
return nil
}
}*/
func getTemplateList(c *tmpl.CTemplateSet, wg *sync.WaitGroup, prefix string) string {
DebugLog("in getTemplateList")
pout := "\n// nolint\nfunc init() {\n"
var tFragCount = make(map[string]int)
var bodyMap = make(map[string]string) //map[body]fragmentPrefix
@ -484,6 +630,8 @@ func getTemplateList(c *tmpl.CTemplateSet, wg *sync.WaitGroup, prefix string) st
var tmpCount = 0
for _, frag := range c.FragOut {
front := frag.TmplName + "_frags[" + strconv.Itoa(frag.Index) + "]"
DebugLog("front: ", front)
DebugLog("frag.Body: ", frag.Body)
/*bodyMap, tok := tmplMap[frag.TmplName]
if !tok {
tmplMap[frag.TmplName] = make(map[string]string)
@ -493,9 +641,12 @@ func getTemplateList(c *tmpl.CTemplateSet, wg *sync.WaitGroup, prefix string) st
if !ok {
bodyMap[frag.Body] = front
var bits string
DebugLog("encoding frag.Body")
for _, char := range []byte(frag.Body) {
if char == '\'' {
bits += "'\\" + string(char) + "',"
} else if char < 32 {
bits += strconv.Itoa(int(char)) + ","
} else {
bits += "'" + string(char) + "',"
}
@ -506,6 +657,7 @@ func getTemplateList(c *tmpl.CTemplateSet, wg *sync.WaitGroup, prefix string) st
tmpCount++
//pout += front + " = []byte(`" + frag.Body + "`)\n"
} else {
DebugLog("encoding cached index " + fp)
pout += front + " = " + fp + "\n"
}

View File

@ -6,7 +6,7 @@ import (
)
// TODO: Write unit tests for this
func minify(data string) string {
func Minify(data string) string {
data = strings.Replace(data, "\t", "", -1)
data = strings.Replace(data, "\v", "", -1)
data = strings.Replace(data, "\n", "", -1)
@ -19,7 +19,7 @@ func minify(data string) string {
// TODO: Handle CSS nested in <style> tags?
// TODO: Write unit tests for this
func minifyHTML(data string) string {
return minify(data)
return Minify(data)
}
// TODO: Have static files use this
@ -27,7 +27,7 @@ func minifyHTML(data string) string {
// TODO: Convert the rgb()s to hex codes?
// TODO: Write unit tests for this
func minifyCSS(data string) string {
return minify(data)
return Minify(data)
}
// TODO: Convert this to three character hex strings whenever possible?

View File

@ -2,14 +2,15 @@ package tmpl
import (
"bytes"
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"reflect"
"strconv"
"strings"
"text/template/parse"
"time"
)
// TODO: Turn this file into a library
@ -42,23 +43,23 @@ type CTemplateConfig struct {
// nolint
type CTemplateSet struct {
templateList map[string]*parse.Tree
fileDir string
funcMap map[string]interface{}
importMap map[string]string
TemplateFragmentCount map[string]int
FragOnce map[string]bool
fragmentCursor map[string]int
FragOut []OutFrag
fragBuf []Fragment
varList map[string]VarItem
localVars map[string]map[string]VarItemReflect
hasDispInt bool
localDispStructIndex int
langIndexToName []string
guestOnly bool
memberOnly bool
stats map[string]int
templateList map[string]*parse.Tree
fileDir string
funcMap map[string]interface{}
importMap map[string]string
//templateFragmentCount map[string]int
fragOnce map[string]bool
fragmentCursor map[string]int
FragOut []OutFrag
fragBuf []Fragment
varList map[string]VarItem
localVars map[string]map[string]VarItemReflect
hasDispInt bool
localDispStructIndex int
langIndexToName []string
guestOnly bool
memberOnly bool
stats map[string]int
//tempVars map[string]string
config CTemplateConfig
baseImportMap map[string]string
@ -68,9 +69,15 @@ type CTemplateSet struct {
overridenRoots map[string]map[string]bool
themeName string
perThemeTmpls map[string]bool
logger *log.Logger
}
func NewCTemplateSet() *CTemplateSet {
func NewCTemplateSet(in string) *CTemplateSet {
f, err := os.OpenFile("./logs/tmpls-"+in+"-"+strconv.FormatInt(time.Now().Unix(), 10)+".log", os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0755)
if err != nil {
panic(err)
}
return &CTemplateSet{
config: CTemplateConfig{
PackageName: "main",
@ -102,6 +109,7 @@ func NewCTemplateSet() *CTemplateSet {
"dyntmpl": true,
"index": true,
},
logger: log.New(f, "", log.LstdFlags),
}
}
@ -140,6 +148,14 @@ func (c *CTemplateSet) SetPerThemeTmpls(perThemeTmpls map[string]bool) {
c.perThemeTmpls = perThemeTmpls
}
func (c *CTemplateSet) ResetLogs(in string) {
f, err := os.OpenFile("./logs/tmpls-"+in+"-"+strconv.FormatInt(time.Now().Unix(), 10)+".log", os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0755)
if err != nil {
panic(err)
}
c.logger = log.New(f, "", log.LstdFlags)
}
type SkipBlock struct {
Frags map[int]int
LastCount int
@ -187,13 +203,15 @@ import "errors"
if !c.config.SkipInitBlock {
stub += "// nolint\nfunc init() {\n"
if !c.config.SkipHandles {
if !c.config.SkipHandles && c.themeName == "" {
stub += "\tcommon.Template_" + fname + "_handle = Template_" + fname + "\n"
stub += "\tcommon.Ctemplates = append(common.Ctemplates,\"" + fname + "\")\n\tcommon.TmplPtrMap[\"" + fname + "\"] = &common.Template_" + fname + "_handle\n"
stub += "\tcommon.Ctemplates = append(common.Ctemplates,\"" + fname + "\")\n"
}
if !c.config.SkipTmplPtrMap {
stub += "\tcommon.TmplPtrMap[\"o_" + fname + "\"] = Template_" + fname + "\n"
stub += "tmpl := Template_" + fname + "\n"
stub += "\tcommon.TmplPtrMap[\"" + fname + "\"] = &tmpl\n"
stub += "\tcommon.TmplPtrMap[\"o_" + fname + "\"] = tmpl\n"
}
stub += "}\n\n"
@ -216,6 +234,7 @@ func Template_` + fname + `(tmpl_` + fname + `_i interface{}, w io.Writer) error
c.fileDir = fileDir
content, err := c.loadTemplate(c.fileDir, name)
if err != nil {
c.detail("bailing out: ", err)
return "", "", "", err
}
@ -235,11 +254,12 @@ func Template_` + fname + `(tmpl_` + fname + `_i interface{}, w io.Writer) error
func (c *CTemplateSet) Compile(name string, fileDir string, expects string, expectsInt interface{}, varList map[string]VarItem, imports ...string) (out string, err error) {
if c.config.Debug {
fmt.Println("Compiling template '" + name + "'")
c.logger.Println("Compiling template '" + name + "'")
}
c.fileDir = fileDir
content, err := c.loadTemplate(c.fileDir, name)
if err != nil {
c.detail("bailing out: ", err)
return "", err
}
@ -247,6 +267,8 @@ 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) {
//c.dumpCall("compile", name, content, expects, expectsInt, varList, imports)
//c.detailf("c: %+v\n", c)
c.importMap = map[string]string{}
for index, item := range c.baseImportMap {
c.importMap[index] = item
@ -273,6 +295,8 @@ func (c *CTemplateSet) compile(name string, content string, expects string, expe
if c.themeName != "" {
_, ok := c.perThemeTmpls[fname]
if !ok {
c.detail("fname not in c.perThemeTmpls")
c.detail("c.perThemeTmpls", c.perThemeTmpls)
return "", nil
}
fname += "_" + c.themeName
@ -283,6 +307,35 @@ func (c *CTemplateSet) compile(name string, content string, expects string, expe
fname += "_member"
}
c.detail("root overridenTrack loop")
c.detail("fname:", fname)
for themeName, track := range c.overridenTrack {
c.detail("themeName:", themeName)
c.detailf("track: %+v\n", track)
croot, ok := c.overridenRoots[themeName]
if !ok {
croot = make(map[string]bool)
c.overridenRoots[themeName] = croot
}
c.detailf("croot: %+v\n", croot)
for tmplName, _ := range track {
cname := tmplName
if c.guestOnly {
cname += "_guest"
} else if c.memberOnly {
cname += "_member"
}
c.detail("cname:", cname)
if fname == cname {
c.detail("match")
croot[strings.TrimSuffix(strings.TrimSuffix(fname, "_guest"), "_member")] = true
} else {
c.detail("no match")
}
}
}
c.detailf("c.overridenRoots: %+v\n", c.overridenRoots)
var outBuf []OutBufferFrame
var rootHold = "tmpl_" + fname + "_vars"
con := CContext{
@ -298,27 +351,28 @@ func (c *CTemplateSet) compile(name string, content string, expects string, expe
c.localVars = make(map[string]map[string]VarItemReflect)
c.localVars[fname] = make(map[string]VarItemReflect)
c.localVars[fname]["."] = VarItemReflect{".", con.VarHolder, con.HoldReflect}
if c.FragOnce == nil {
c.FragOnce = make(map[string]bool)
if c.fragOnce == nil {
c.fragOnce = make(map[string]bool)
}
c.fragmentCursor = map[string]int{fname: 0}
c.fragBuf = nil
c.langIndexToName = nil
// TODO: Is this the first template loaded in? We really should have some sort of constructor for CTemplateSet
if c.TemplateFragmentCount == nil {
c.TemplateFragmentCount = make(map[string]int)
}
//if c.templateFragmentCount == nil {
// c.templateFragmentCount = make(map[string]int)
//}
//c.detailf("c: %+v\n", c)
startIndex := con.StartTemplate("")
c.rootIterate(c.templateList[fname], con)
con.EndTemplate("")
c.afterTemplate(con, startIndex)
c.TemplateFragmentCount[fname] = c.fragmentCursor[fname] + 1
//c.templateFragmentCount[fname] = c.fragmentCursor[fname] + 1
_, ok := c.FragOnce[fname]
_, ok := c.fragOnce[fname]
if !ok {
c.FragOnce[fname] = true
c.fragOnce[fname] = true
}
if len(c.langIndexToName) > 0 {
c.importMap[langPkg] = langPkg
@ -346,13 +400,15 @@ func (c *CTemplateSet) compile(name string, content string, expects string, expe
}
fout += "// nolint\nfunc init() {\n"
if !c.config.SkipHandles {
if !c.config.SkipHandles && c.themeName == "" {
fout += "\tcommon.Template_" + fname + "_handle = Template_" + fname + "\n"
fout += "\tcommon.Ctemplates = append(common.Ctemplates,\"" + fname + "\")\n\tcommon.TmplPtrMap[\"" + fname + "\"] = &common.Template_" + fname + "_handle\n"
fout += "\tcommon.Ctemplates = append(common.Ctemplates,\"" + fname + "\")\n"
}
if !c.config.SkipTmplPtrMap {
fout += "\tcommon.TmplPtrMap[\"o_" + fname + "\"] = Template_" + fname + "\n"
fout += "tmpl := Template_" + fname + "\n"
fout += "\tcommon.TmplPtrMap[\"" + fname + "\"] = &tmpl\n"
fout += "\tcommon.TmplPtrMap[\"o_" + fname + "\"] = tmpl\n"
}
if len(c.langIndexToName) > 0 {
fout += "\t" + fname + "_tmpl_phrase_id = phrases.RegisterTmplPhraseNames([]string{\n"
@ -458,9 +514,9 @@ w.Write([]byte(`, " + ", -1)
if c.config.Debug {
for index, count := range c.stats {
fmt.Println(index+": ", strconv.Itoa(count))
c.logger.Println(index+": ", strconv.Itoa(count))
}
fmt.Println(" ")
c.logger.Println(" ")
}
c.detail("Output!")
c.detail(fout)
@ -579,13 +635,15 @@ func (c *CTemplateSet) compileSwitch(con CContext, node parse.Node) {
}
func (c *CTemplateSet) addText(con CContext, text []byte) {
c.dumpCall("addText", con, text)
tmpText := bytes.TrimSpace(text)
if len(tmpText) == 0 {
return
}
nodeText := string(text)
c.detail("con.TemplateName: ", con.TemplateName)
fragIndex := c.fragmentCursor[con.TemplateName]
_, ok := c.FragOnce[con.TemplateName]
_, ok := c.fragOnce[con.TemplateName]
c.fragBuf = append(c.fragBuf, Fragment{nodeText, con.TemplateName, fragIndex, ok})
con.PushText(strconv.Itoa(fragIndex), fragIndex, len(c.fragBuf)-1)
c.fragmentCursor[con.TemplateName] = fragIndex + 1
@ -816,9 +874,9 @@ func (c *CTemplateSet) compileExprSwitch(con CContext, node *parse.CommandNode)
switch n := firstWord.(type) {
case *parse.FieldNode:
if c.config.SuperDebug {
fmt.Println("Field Node:", n.Ident)
c.logger.Println("Field Node:", n.Ident)
for _, id := range n.Ident {
fmt.Println("Field Bit:", id)
c.logger.Println("Field Bit:", id)
}
}
/* Use reflect to determine if the field is for a method, otherwise assume it's a variable. Coming Soon. */
@ -851,8 +909,8 @@ func (c *CTemplateSet) compileExprSwitch(con CContext, node *parse.CommandNode)
}
func (c *CTemplateSet) unknownNode(node parse.Node) {
fmt.Println("Unknown Kind:", reflect.ValueOf(node).Elem().Kind())
fmt.Println("Unknown Type:", reflect.ValueOf(node).Elem().Type().Name())
c.logger.Println("Unknown Kind:", reflect.ValueOf(node).Elem().Kind())
c.logger.Println("Unknown Type:", reflect.ValueOf(node).Elem().Type().Name())
panic("I don't know what node this is! Grr...")
}
@ -893,12 +951,12 @@ func (c *CTemplateSet) simpleMath(con CContext, pos int, node *parse.CommandNode
func (c *CTemplateSet) compareJoin(con CContext, pos int, node *parse.CommandNode, symbol string) (pos2 int, out string) {
c.detailf("Building %s function", symbol)
if pos == 0 {
fmt.Println("pos:", pos)
c.logger.Println("pos:", pos)
panic(symbol + " is missing a left operand")
}
if len(node.Args) <= pos {
fmt.Println("post pos:", pos)
fmt.Println("len(node.Args):", len(node.Args))
c.logger.Println("post pos:", pos)
c.logger.Println("len(node.Args):", len(node.Args))
panic(symbol + " is missing a right operand")
}
@ -1090,9 +1148,9 @@ func (c *CTemplateSet) compileReflectSwitch(con CContext, node *parse.CommandNod
switch n := firstWord.(type) {
case *parse.FieldNode:
if c.config.SuperDebug {
fmt.Println("Field Node:", n.Ident)
c.logger.Println("Field Node:", n.Ident)
for _, id := range n.Ident {
fmt.Println("Field Bit:", id)
c.logger.Println("Field Bit:", id)
}
}
/* Use reflect to determine if the field is for a method, otherwise assume it's a variable. Coming Soon. */
@ -1176,12 +1234,12 @@ func (c *CTemplateSet) compileIfVarSub(con CContext, varname string) (out string
cur = cur.FieldByName(bit)
out += "." + bit
if !cur.IsValid() {
fmt.Println("cur: ", cur)
c.logger.Println("cur: ", cur)
panic(out + "^\n" + "Invalid value. Maybe, it doesn't exist?")
}
stepInterface()
if !cur.IsValid() {
fmt.Println("cur: ", cur)
c.logger.Println("cur: ", cur)
panic(out + "^\n" + "Invalid value. Maybe, it doesn't exist?")
}
dumpKind("Data")
@ -1219,9 +1277,9 @@ func (c *CTemplateSet) compileBoolSub(con CContext, varname string) string {
case reflect.Slice, reflect.Map:
out = "len(" + out + ") != 0"
default:
fmt.Println("Variable Name:", varname)
fmt.Println("Variable Holder:", con.VarHolder)
fmt.Println("Variable Kind:", con.HoldReflect.Kind())
c.logger.Println("Variable Name:", varname)
c.logger.Println("Variable Holder:", con.VarHolder)
c.logger.Println("Variable Kind:", con.HoldReflect.Kind())
panic("I don't know what this variable's type is o.o\n")
}
c.retCall("compileBoolSub", out)
@ -1398,17 +1456,17 @@ func (c *CTemplateSet) compileVarSub(con CContext, varname string, val reflect.V
if !val.IsValid() {
panic(assLines + varname + "^\n" + "Invalid value. Maybe, it doesn't exist?")
}
fmt.Println("Unknown Struct Name:", varname)
fmt.Println("Unknown Struct:", val.Type().Name())
c.logger.Println("Unknown Struct Name:", varname)
c.logger.Println("Unknown Struct:", val.Type().Name())
panic("-- I don't know what this variable's type is o.o\n")
}
default:
if !val.IsValid() {
panic(assLines + varname + "^\n" + "Invalid value. Maybe, it doesn't exist?")
}
fmt.Println("Unknown Variable Name:", varname)
fmt.Println("Unknown Kind:", val.Kind())
fmt.Println("Unknown Type:", val.Type().Name())
c.logger.Println("Unknown Variable Name:", varname)
c.logger.Println("Unknown Kind:", val.Kind())
c.logger.Println("Unknown Type:", val.Type().Name())
panic("-- I don't know what this variable's type is o.o\n")
}
c.detail("base: ", base)
@ -1427,17 +1485,25 @@ func (c *CTemplateSet) compileSubTemplate(pcon CContext, node *parse.TemplateNod
// TODO: Cascade errors back up the tree to the caller?
content, err := c.loadTemplate(c.fileDir, node.Name)
if err != nil {
log.Fatal(err)
c.logger.Fatal(err)
}
tree := parse.New(node.Name, c.funcMap)
var treeSet = make(map[string]*parse.Tree)
tree, err = tree.Parse(content, "{{", "}}", treeSet, c.funcMap)
if err != nil {
log.Fatal(err)
c.logger.Fatal(err)
}
fname := strings.TrimSuffix(node.Name, filepath.Ext(node.Name))
if c.themeName != "" {
_, ok := c.perThemeTmpls[fname]
if !ok {
c.detail("fname not in c.perThemeTmpls")
c.detail("c.perThemeTmpls", c.perThemeTmpls)
}
fname += "_" + c.themeName
}
if c.guestOnly {
fname += "_guest"
} else if c.memberOnly {
@ -1523,11 +1589,11 @@ func (c *CTemplateSet) compileSubTemplate(pcon CContext, node *parse.TemplateNod
con.StartTemplate(startBit)
c.rootIterate(subtree, con)
con.EndTemplate(endBit)
c.TemplateFragmentCount[fname] = c.fragmentCursor[fname] + 1
//c.templateFragmentCount[fname] = c.fragmentCursor[fname] + 1
_, ok := c.FragOnce[fname]
_, ok := c.fragOnce[fname]
if !ok {
c.FragOnce[fname] = true
c.fragOnce[fname] = true
}
// map[string]map[string]bool
@ -1562,13 +1628,16 @@ func (c *CTemplateSet) compileSubTemplate(pcon CContext, node *parse.TemplateNod
}
func (c *CTemplateSet) loadTemplate(fileDir string, name string) (content string, err error) {
c.dumpCall("loadTemplate", fileDir, name)
c.detail("c.themeName: ", c.themeName)
if c.themeName != "" {
c.detail("per-theme override: ", "./themes/"+c.themeName+"/overrides/"+name)
res, err := ioutil.ReadFile("./themes/" + c.themeName + "/overrides/" + name)
if err == nil {
content = string(res)
if c.config.Minify {
content = minify(content)
content = Minify(content)
}
return content, nil
}
@ -1586,7 +1655,7 @@ func (c *CTemplateSet) loadTemplate(fileDir string, name string) (content string
}
content = string(res)
if c.config.Minify {
content = minify(content)
content = Minify(content)
}
return content, nil
}
@ -1664,22 +1733,22 @@ func (c *CTemplateSet) afterTemplate(con CContext, startIndex int) {
func (c *CTemplateSet) detail(args ...interface{}) {
if c.config.SuperDebug {
log.Println(args...)
c.logger.Println(args...)
}
}
func (c *CTemplateSet) detailf(left string, args ...interface{}) {
if c.config.SuperDebug {
log.Printf(left, args...)
c.logger.Printf(left, args...)
}
}
func (c *CTemplateSet) error(args ...interface{}) {
if c.config.Debug {
log.Println(args...)
c.logger.Println(args...)
}
}
func (c *CTemplateSet) critical(args ...interface{}) {
log.Println(args...)
c.logger.Println(args...)
}

View File

@ -25,23 +25,25 @@ var ErrNoDefaultTheme = errors.New("The default theme isn't registered in the sy
type Theme struct {
Path string // Redirect this file to another folder
Name string
FriendlyName string
Version string
Creator string
FullImage string
MobileFriendly bool
Disabled bool
HideFromThemes bool
BgAvatars bool // For profiles, at the moment
GridLists bool // User Manager
ForkOf string
Tag string
URL string
Docks []string // Allowed Values: leftSidebar, rightSidebar, footer
Settings map[string]ThemeSetting
IntTmplHandle *htmpl.Template
Name string
FriendlyName string
Version string
Creator string
FullImage string
MobileFriendly bool
Disabled bool
HideFromThemes bool
BgAvatars bool // For profiles, at the moment
GridLists bool // User Manager
ForkOf string
Tag string
URL string
Docks []string // Allowed Values: leftSidebar, rightSidebar, footer
Settings map[string]ThemeSetting
IntTmplHandle *htmpl.Template
// TODO: Do we really need both OverridenTemplates AND OverridenMap?
OverridenTemplates []string
OverridenMap map[string]bool
Templates []TemplateMapping
TemplatesMap map[string]string
TmplPtr map[string]interface{}
@ -297,6 +299,7 @@ func (theme *Theme) RunTmpl(template string, pi interface{}, w io.Writer) error
case func(interface{}, io.Writer) error:
return tmplO(pi, w)
case nil, string:
//fmt.Println("falling back to interpreted for " + template)
mapping, ok := theme.TemplatesMap[template]
if !ok {
mapping = template
@ -330,6 +333,10 @@ func (theme *Theme) GetTmpl(template string) interface{} {
if ok {
return tmpl
}
tmpl, ok = TmplPtrMap[template+"_"+theme.Name]
if ok {
return tmpl
}
tmpl, ok = TmplPtrMap[template]
if ok {
return tmpl

View File

@ -146,6 +146,7 @@ func NewThemeList() (themes ThemeList, err error) {
}
if len(overrides) > 0 {
var overCount = 0
theme.OverridenMap = make(map[string]bool)
for _, override := range overrides {
if override.IsDir() {
continue
@ -157,7 +158,10 @@ func NewThemeList() (themes ThemeList, err error) {
continue
}
overCount++
theme.OverridenTemplates = append(theme.OverridenTemplates, strings.TrimSuffix(override.Name(), ext))
nosuf := strings.TrimSuffix(override.Name(), ext)
theme.OverridenTemplates = append(theme.OverridenTemplates, nosuf)
theme.OverridenMap[nosuf] = true
//theme.TmplPtr[nosuf] = TmplPtrMap["o_"+nosuf]
log.Print("succeeded")
}
@ -247,7 +251,6 @@ func ResetTemplateOverrides() {
log.Print("The origin template doesn't exist!")
return
}
destTmplPtr, ok := TmplPtrMap[name]
if !ok {
log.Print("The destination template doesn't exist!")

View File

@ -49,8 +49,10 @@ func RouteWebsockets(w http.ResponseWriter, r *http.Request, user User) RouteErr
// TODO: Spit out a 500 instead of nil?
conn, err := wsUpgrader.Upgrade(w, r, nil)
if err != nil {
return nil
return LocalError("unable to upgrade", w, r, user)
}
defer conn.Close()
wsUser, err := WsHub.AddConn(user, conn)
if err != nil {
return nil
@ -96,7 +98,6 @@ func RouteWebsockets(w http.ResponseWriter, r *http.Request, user User) RouteErr
}*/
}
}
conn.Close()
DebugLog("Closing connection for user " + strconv.Itoa(user.ID))
return nil
}

View File

@ -9,6 +9,8 @@ import (
"strings"
"sync"
"sync/atomic"
min "github.com/Azareal/Gosora/common/templates"
)
// TODO: Clean this file up
@ -49,7 +51,11 @@ func preparseWidget(widget *Widget, wdata string) (err error) {
prebuildWidget := func(name string, data interface{}) (string, error) {
var b bytes.Buffer
err := DefaultTemplates.ExecuteTemplate(&b, name+".html", data)
return string(b.Bytes()), err
content := string(b.Bytes())
if Config.MinifyTemplates {
content = min.Minify(content)
}
return content, err
}
sbytes := []byte(wdata)

View File

@ -38,6 +38,28 @@ func init() {
}
func (hub *WsHubImpl) Start() {
ticker := time.NewTicker(time.Minute * 5)
defer func() {
ticker.Stop()
}()
go func() {
for {
var item = func(lock *sync.RWMutex, userMap map[int]*WSUser) {
lock.RLock()
defer lock.RUnlock()
// TODO: Copy to temporary slice for less contention?
for _, user := range userMap {
user.Ping()
}
}
select {
case <-ticker.C:
item(&hub.evenUserLock, hub.evenOnlineUsers)
item(&hub.oddUserLock, hub.oddOnlineUsers)
}
}
}()
if Config.DisableLiveTopicList {
return
}
@ -298,15 +320,10 @@ func (hub *WsHubImpl) removeUser(uid int) {
}
func (hub *WsHubImpl) AddConn(user User, conn *websocket.Conn) (*WSUser, error) {
// TODO: How should we handle user state changes if we're holding a pointer which never changes?
userptr, err := Users.Get(user.ID)
if err != nil && err != ErrStoreCapacityOverflow {
return nil, err
}
if user.ID == 0 {
wsUser := new(WSUser)
wsUser.User = userptr
wsUser.User = new(User)
*wsUser.User = user
wsUser.AddSocket(conn, "")
WsHub.GuestLock.Lock()
WsHub.OnlineGuests[wsUser] = true
@ -314,6 +331,12 @@ func (hub *WsHubImpl) AddConn(user User, conn *websocket.Conn) (*WSUser, error)
return wsUser, nil
}
// TODO: How should we handle user state changes if we're holding a pointer which never changes?
userptr, err := Users.Get(user.ID)
if err != nil && err != ErrStoreCapacityOverflow {
return nil, err
}
var mutex *sync.RWMutex
var theMap map[int]*WSUser
if user.ID%2 == 0 {

View File

@ -3,6 +3,7 @@ package common
import (
"errors"
"sync"
"time"
"github.com/gorilla/websocket"
)
@ -21,6 +22,20 @@ type WSUserSocket struct {
Page string
}
func (wsUser *WSUser) Ping() error {
for _, socket := range wsUser.Sockets {
if socket == nil {
continue
}
socket.conn.SetWriteDeadline(time.Now().Add(time.Minute))
err := socket.conn.WriteMessage(websocket.PingMessage, nil)
if err != nil {
socket.conn.Close()
}
}
return nil
}
func (wsUser *WSUser) WriteAll(msg string) error {
msgbytes := []byte(msg)
for _, socket := range wsUser.Sockets {

View File

@ -862,11 +862,22 @@ func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if lang != "" {
lang = strings.TrimSpace(lang)
lLang := strings.Split(lang,"-")
llLang := strings.Split(strings.Split(lLang[0],";")[0],",")
common.DebugDetail("llLang:", llLang)
validCode := counters.LangViewCounter.Bump(llLang[0])
if !validCode {
r.DumpRequest(req,"Invalid ISO Code")
tLang := strings.Split(strings.Split(lLang[0],";")[0],",")
common.DebugDetail("tLang:", tLang)
var llLang string
for _, seg := range tLang {
if seg == "*" {
continue
}
llLang = seg
}
if llLang == "" {
counters.LangViewCounter.Bump("none")
} else {
validCode := counters.LangViewCounter.Bump(llLang)
if !validCode {
r.DumpRequest(req,"Invalid ISO Code")
}
}
} else {
counters.LangViewCounter.Bump("none")
@ -2104,6 +2115,11 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
case "robots.txt":
counters.RouteViewCounter.Bump(134)
return routes.RobotsTxt(w,req)
case "favicon.ico":
req.URL.Path = "/static"+req.URL.Path+extraData
//log.Print("req.URL.Path: ",req.URL.Path)
routes.StaticFile(w,req)
return nil
/*case "sitemap.xml":
counters.RouteViewCounter.Bump(135)
return routes.SitemapXml(w,req)*/

View File

@ -51,7 +51,8 @@
"bigpost_min_words":"Big Post Minimum Words",
"megapost_min_words":"Mega Post Minimum Words",
"meta_desc":"Meta Description",
"rapid_loading":"Rapid Loaded?"
"rapid_loading":"Rapid Loaded?",
"google_site_verify":"Google Site Verify"
},
"PermPresets": {

View File

@ -26,6 +26,7 @@ func init() {
addPatch(12, patch12)
addPatch(13, patch13)
addPatch(14, patch14)
addPatch(15, patch15)
}
func patch0(scanner *bufio.Scanner) (err error) {
@ -532,3 +533,7 @@ func patch14(scanner *bufio.Scanner) error {
return nil
}
func patch15(scanner *bufio.Scanner) error {
return execStmt(qgen.Builder.SimpleInsert("settings", "name, content, type", "'google_site_verify','','html-attribute'"))
}

View File

@ -283,8 +283,7 @@ function runWebSockets() {
(() => {
addInitHook("pre_init", () => {
// 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 :(
loadScript("template_alert.js", () => {
console.log("Loaded template_alert.js");
notifyOnScriptW("/static/template_alert", () => {}, () => {
addInitHook("after_phrases", () => {
// TODO: The load part of loadAlerts could be done asynchronously while the update of the DOM could be deferred
$(document).ready(() => {
@ -471,6 +470,7 @@ function mainInit(){
let out = "";
for(let i = 0; i < topics.length;i++) out += Template_topics_topic(topics[i]);
$(".topic_list").html(out);
//$(".topic_list").addClass("single_forum");
let obj = {Title: document.title, Url: url};
history.pushState(obj, obj.Title, obj.Url);

View File

@ -2,7 +2,7 @@
var me = {};
var phraseBox = {};
var tmplInits = {};
if(tmplInits===undefined) var tmplInits = {};
var tmplPhrases = []; // [key] array of phrases indexed by order of use
var hooks = {
"pre_iffe": [],
@ -52,41 +52,101 @@ function len(item) {
return item.length;
}
const asyncGetScript = (source) => {
function asyncGetScript(source) {
return new Promise((resolve, reject) => {
let script = document.createElement('script');
script.async = true;
const onloadHander = (haha, isAbort) => {
const onloadHandler = (e, isAbort) => {
if (isAbort || !script.readyState || /loaded|complete/.test(script.readyState)) {
script.onload = null;
script.onreadystatechange = null;
script = undefined;
isAbort ? reject(haha) : resolve();
isAbort ? reject(e) : resolve();
}
}
script.onerror = (haha) => {
reject(haha);
script.onerror = (e) => {
reject(e);
};
script.onload = onloadHander;
script.onreadystatechange = onloadHander;
script.onload = onloadHandler;
script.onreadystatechange = onloadHandler;
script.src = source;
const prior = document.getElementsByTagName('script')[0];
prior.parentNode.insertBefore(script, prior);
});
};
}
function loadScript(name, callback) {
let url = "/static/"+name
function notifyOnScript(source) {
return new Promise((resolve, reject) => {
let script = document.querySelectorAll('[src^="'+source+'"]')[0];
if(script===undefined) {
reject("no script found");
return;
}
if(!script.readyState) {
resolve();
return;
}
const onloadHandler = (e, isAbort) => {
if (isAbort || !script.readyState || /loaded|complete/.test(script.readyState)) {
script.onload = null;
script.onreadystatechange = null;
isAbort ? reject(e) : resolve();
}
}
script.onerror = (e) => {
reject(e);
};
script.onload = onloadHandler;
script.onreadystatechange = onloadHandler;
script.src = source;
});
}
function notifyOnScriptW(name, complete, success) {
notifyOnScript(name)
.then(() => {
console.log("Loaded " +name+".js");
complete();
if(success!==undefined) success();
}).catch((e) => {
console.log("Unable to get script name '"+name+"'");
console.log("e: ", e);
console.trace();
complete();
});
}
// TODO: Send data at load time so we don't have to rely on a fallback template here
function loadScript(name, callback,fail) {
let fname = name;
let value = "; " + document.cookie;
let parts = value.split("; current_theme=");
if (parts.length == 2) fname += "_"+ parts.pop().split(";").shift();
let url = "/static/"+fname+".js"
let iurl = "/static/"+name+".js"
asyncGetScript(url)
.then(callback)
.catch((haha) => {
.catch((e) => {
console.log("Unable to get script '"+url+"'");
console.log("haha: ", haha);
if(fname!=name) {
asyncGetScript(iurl)
.then(callback)
.catch((e) => {
console.log("Unable to get script '"+iurl+"'");
console.log("e: ", e);
console.trace();
});
}
console.log("e: ", e);
console.trace();
fail(e);
});
}
@ -106,6 +166,7 @@ function RelativeTime(date) {
}
function initPhrases() {
console.log("in initPhrases")
fetchPhrases("status,topic_list,alerts,paginator")
}
@ -144,13 +205,11 @@ function fetchPhrases(plist) {
runInitHook("pre_iife");
let toLoad = 2;
// TODO: Shunt this into loggedIn if there aren't any search and filter widgets?
loadScript("template_topics_topic.js", () => {
console.log("Loaded template_topics_topic.js");
notifyOnScriptW("/static/template_topics_topic", () => {
toLoad--;
if(toLoad===0) initPhrases();
});
loadScript("template_paginator.js", () => {
console.log("Loaded template_paginator.js");
notifyOnScriptW("/static/template_paginator", () => {
toLoad--;
if(toLoad===0) initPhrases();
});

View File

@ -654,11 +654,22 @@ func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if lang != "" {
lang = strings.TrimSpace(lang)
lLang := strings.Split(lang,"-")
llLang := strings.Split(strings.Split(lLang[0],";")[0],",")
common.DebugDetail("llLang:", llLang)
validCode := counters.LangViewCounter.Bump(llLang[0])
if !validCode {
r.DumpRequest(req,"Invalid ISO Code")
tLang := strings.Split(strings.Split(lLang[0],";")[0],",")
common.DebugDetail("tLang:", tLang)
var llLang string
for _, seg := range tLang {
if seg == "*" {
continue
}
llLang = seg
}
if llLang == "" {
counters.LangViewCounter.Bump("none")
} else {
validCode := counters.LangViewCounter.Bump(llLang)
if !validCode {
r.DumpRequest(req,"Invalid ISO Code")
}
}
} else {
counters.LangViewCounter.Bump("none")
@ -734,6 +745,11 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
case "robots.txt":
counters.RouteViewCounter.Bump({{index .AllRouteMap "routes.RobotsTxt"}})
return routes.RobotsTxt(w,req)
case "favicon.ico":
req.URL.Path = "/static"+req.URL.Path+extraData
//log.Print("req.URL.Path: ",req.URL.Path)
routes.StaticFile(w,req)
return nil
/*case "sitemap.xml":
counters.RouteViewCounter.Bump({{index .AllRouteMap "routes.SitemapXml"}})
return routes.SitemapXml(w,req)*/

View File

@ -21,7 +21,10 @@ func ParseSEOURL(urlBit string) (slug string, id int, err error) {
}
func renderTemplate(tmplName string, w http.ResponseWriter, r *http.Request, header *common.Header, pi interface{}) common.RouteError {
if header.MetaDesc != "" && header.OGDesc == "" {
if header.CurrentUser.Loggedin {
header.MetaDesc = ""
header.OGDesc = ""
} else if header.MetaDesc != "" && header.OGDesc == "" {
header.OGDesc = header.MetaDesc
}
// TODO: Expand this to non-HTTPS requests too

View File

@ -19,7 +19,7 @@ var cacheControlMaxAge = "max-age=" + strconv.Itoa(int(common.Day)) // TODO: Mak
func StaticFile(w http.ResponseWriter, r *http.Request) {
file, ok := common.StaticFiles.Get(r.URL.Path)
if !ok {
common.DebugLogf("Failed to find '%s'", r.URL.Path)
common.DebugLogf("Failed to find '%s'", r.URL.Path) // TODO: Use MicroNotFound? Might be better than the unneccessary overhead of sprintf
w.WriteHeader(http.StatusNotFound)
return
}

View File

@ -4,6 +4,7 @@ INSERT INTO [settings] ([name],[content],[type]) VALUES ('bigpost_min_words','25
INSERT INTO [settings] ([name],[content],[type]) VALUES ('megapost_min_words','1000','int');
INSERT INTO [settings] ([name],[content],[type]) VALUES ('meta_desc','','html-attribute');
INSERT INTO [settings] ([name],[content],[type]) VALUES ('rapid_loading','1','bool');
INSERT INTO [settings] ([name],[content],[type]) VALUES ('google_site_verify','','html-attribute');
INSERT INTO [themes] ([uname],[default]) VALUES ('cosora',1);
INSERT INTO [emails] ([email],[uid],[validated]) VALUES ('admin@localhost',1,1);
INSERT INTO [users_groups] ([name],[permissions],[plugin_perms],[is_mod],[is_admin],[tag]) VALUES ('Administrator','{"BanUsers":true,"ActivateUsers":true,"EditUser":true,"EditUserEmail":true,"EditUserPassword":true,"EditUserGroup":true,"EditUserGroupSuperMod":true,"EditUserGroupAdmin":false,"EditGroup":true,"EditGroupLocalPerms":true,"EditGroupGlobalPerms":true,"EditGroupSuperMod":true,"EditGroupAdmin":false,"ManageForums":true,"EditSettings":true,"ManageThemes":true,"ManagePlugins":true,"ViewAdminLogs":true,"ViewIPs":true,"UploadFiles":true,"ViewTopic":true,"LikeItem":true,"CreateTopic":true,"EditTopic":true,"DeleteTopic":true,"CreateReply":true,"EditReply":true,"DeleteReply":true,"PinTopic":true,"CloseTopic":true,"MoveTopic":true}','{}',1,1,'Admin');

View File

@ -12,6 +12,7 @@ INSERT INTO `settings`(`name`,`content`,`type`) VALUES ('bigpost_min_words','250
INSERT INTO `settings`(`name`,`content`,`type`) VALUES ('megapost_min_words','1000','int');
INSERT INTO `settings`(`name`,`content`,`type`) VALUES ('meta_desc','','html-attribute');
INSERT INTO `settings`(`name`,`content`,`type`) VALUES ('rapid_loading','1','bool');
INSERT INTO `settings`(`name`,`content`,`type`) VALUES ('google_site_verify','','html-attribute');
INSERT INTO `themes`(`uname`,`default`) VALUES ('cosora',1);
INSERT INTO `emails`(`email`,`uid`,`validated`) VALUES ('admin@localhost',1,1);
INSERT INTO `users_groups`(`name`,`permissions`,`plugin_perms`,`is_mod`,`is_admin`,`tag`) VALUES ('Administrator','{"BanUsers":true,"ActivateUsers":true,"EditUser":true,"EditUserEmail":true,"EditUserPassword":true,"EditUserGroup":true,"EditUserGroupSuperMod":true,"EditUserGroupAdmin":false,"EditGroup":true,"EditGroupLocalPerms":true,"EditGroupGlobalPerms":true,"EditGroupSuperMod":true,"EditGroupAdmin":false,"ManageForums":true,"EditSettings":true,"ManageThemes":true,"ManagePlugins":true,"ViewAdminLogs":true,"ViewIPs":true,"UploadFiles":true,"ViewTopic":true,"LikeItem":true,"CreateTopic":true,"EditTopic":true,"DeleteTopic":true,"CreateReply":true,"EditReply":true,"DeleteReply":true,"PinTopic":true,"CloseTopic":true,"MoveTopic":true}','{}',1,1,'Admin');

View File

@ -4,6 +4,7 @@ INSERT INTO "settings"("name","content","type") VALUES ('bigpost_min_words','250
INSERT INTO "settings"("name","content","type") VALUES ('megapost_min_words','1000','int');
INSERT INTO "settings"("name","content","type") VALUES ('meta_desc','','html-attribute');
INSERT INTO "settings"("name","content","type") VALUES ('rapid_loading','1','bool');
INSERT INTO "settings"("name","content","type") VALUES ('google_site_verify','','html-attribute');
INSERT INTO "themes"("uname","default") VALUES ('cosora',1);
INSERT INTO "emails"("email","uid","validated") VALUES ('admin@localhost',1,1);
INSERT INTO "users_groups"("name","permissions","plugin_perms","is_mod","is_admin","tag") VALUES ('Administrator','{"BanUsers":true,"ActivateUsers":true,"EditUser":true,"EditUserEmail":true,"EditUserPassword":true,"EditUserGroup":true,"EditUserGroupSuperMod":true,"EditUserGroupAdmin":false,"EditGroup":true,"EditGroupLocalPerms":true,"EditGroupGlobalPerms":true,"EditGroupSuperMod":true,"EditGroupAdmin":false,"ManageForums":true,"EditSettings":true,"ManageThemes":true,"ManagePlugins":true,"ViewAdminLogs":true,"ViewIPs":true,"UploadFiles":true,"ViewTopic":true,"LikeItem":true,"CreateTopic":true,"EditTopic":true,"DeleteTopic":true,"CreateReply":true,"EditReply":true,"DeleteReply":true,"PinTopic":true,"CloseTopic":true,"MoveTopic":true}','{}',1,1,'Admin');

View File

@ -45,7 +45,7 @@
</div>
{{end}}
{{end}}
<div id="forum_topic_list" class="rowblock topic_list" aria-label="{{lang "forum_list_aria"}}">
<div id="forum_topic_list" class="rowblock topic_list single_forum" aria-label="{{lang "forum_list_aria"}}">
{{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">
<span class="selector"></span>

View File

@ -5,6 +5,8 @@
<link href="/static/{{.Header.Theme.Name}}/main.css" rel="stylesheet" type="text/css">
{{range .Header.Stylesheets}}
<link href="/static/{{.}}" rel="stylesheet" type="text/css">{{end}}
{{range .Header.PreScriptsAsync}}
<script async type="text/javascript" src="/static/{{.}}"></script>{{end}}
<meta property="x-loggedin" content="{{.CurrentUser.Loggedin}}" />
<script type="text/javascript" src="/static/init.js"></script>
<script type="text/javascript" src="/static/jquery-3.1.1.min.js"></script>
@ -20,6 +22,7 @@
<meta name="twitter:title" content="{{.Title}} | {{.Header.Site.Name}}" />
{{if .OGDesc}}<meta property="og:description" content="{{.OGDesc}}" />
<meta property="twitter:description" content="{{.OGDesc}}" />{{end}}
{{if .GoogSiteVerify}}<meta name="google-site-verification" content="{{.GoogSiteVerify}}" />{{end}}
</head>
<body>
{{if not .CurrentUser.IsSuperMod}}<style>.supermod_only { display: none !important; }</style>{{end}}

View File

@ -5,7 +5,7 @@
<div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{lang "panel_pages_head"}}</h1></div>
</div>
<div id="panel_settings" class="colstack_item rowlist">
<div id="panel_pages" class="colstack_item rowlist">
{{range .ItemList}}
<div class="rowitem panel_compactrow">
<a href="/panel/pages/edit/{{.ID}}" class="panel_upshift">{{.Title}}</a>&nbsp;<a href="/pages/{{.Name}}">[{{.Name}}]</a>

View File

@ -51,6 +51,7 @@
<div class="rowitem"><h1><a>{{lang "profile_ban_user_head"}}</a></h1></div>
</div>
<form id="ban_user_form" class="hash_hide ban_user_hash" action="/users/ban/submit/{{.ProfileOwner.ID}}?session={{.CurrentUser.Session}}" method="post" style="display: none;">
<div class="the_form">
{{/** TODO: Put a JS duration calculator here instead of this text? **/}}
<div class="colline">{{lang "profile_ban_user_notice"}}</div>
<div class="colstack_item">
@ -80,6 +81,7 @@
<div class="formitem"><button name="ban-button" class="formbutton form_middle_button">{{lang "profile_ban_user_button"}}</button></div>
</div>
</div>
</div>
</form>
{{end}}
{{end}}

View File

@ -0,0 +1,8 @@
.colline {
border: 1px solid var(--element-border-color);
border-bottom: 2px solid var(--element-border-color);
background-color: var(--element-background-color);
padding: 8px;
margin-left: 16px;
margin-bottom: 12px;
}

View File

@ -0,0 +1,25 @@
<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="{{.Creator.Name}}'s Avatar" title="{{.Creator.Name}}'s Avatar" /></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>
</span>
</div>
<div class="topic_middle">
<div class="topic_middle_inside rowsmall">
<span class="replyCount">{{.PostCount}}&nbsp;{{lang "topic_list.replies_suffix"}}</span>
<span class="likeCount">{{.LikeCount}}&nbsp;{{lang "topic_list.likes_suffix"}}</span>
<span class="viewCount">{{.ViewCount}}&nbsp;{{lang "topic_list.views_suffix"}}</span>
</div>
</div>
<div class="rowitem topic_right passive datarow">
<div class="topic_right_inside">
<a href="{{.LastUser.Link}}"><img src="{{.LastUser.MicroAvatar}}" height=64 alt="{{.LastUser.Name}}'s Avatar" title="{{.LastUser.Name}}'s Avatar" /></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>
</span>
</div>
</div>
</div>

View File

@ -1,3 +1,6 @@
#main {
max-width: none;
}
.colstack_left {
width: 200px;
padding-bottom: 6px;

View File

@ -209,6 +209,8 @@ li a {
}
.filter_item a {
color: #BBBBBB;
text-overflow: ellipsis;
overflow: hidden;
}
.colstack_right .colstack_item:not(.colstack_head):not(.rowhead) .rowitem:not(:last-child) {
margin-bottom: 8px;
@ -512,8 +514,9 @@ h2 {
text-overflow: ellipsis;
overflow: hidden;
}
.topic_inner_left .parent_forum {
display: none; /* Comment this until we figure out how to make it work */
.parent_forum_sep {
margin-left: 6px;
margin-right: 6px;
}
.topic_right_inside {
display: flex;
@ -1176,6 +1179,9 @@ input[type=checkbox]:checked + label .sel {
.topic_list_title_block .mod_opt a:before {
content: "{{lang "topic_list.moderate_short" . }}";
}
.topic_inner_left .parent_forum, .parent_forum_sep {
display: none;
}
}
@media(max-width: 601px) {
@ -1274,6 +1280,9 @@ input[type=checkbox]:checked + label .sel {
.menu_profile {
display: none;
}
#main {
max-width: calc(100% - 180px);
}
}
@media(max-width: 850px) {
@ -1327,4 +1336,4 @@ input[type=checkbox]:checked + label .sel {
.user_box {
width: 200px;
}
}
}

View File

@ -165,6 +165,8 @@ button, .formbutton, .panel_right_button:not(.has_inner_button), #panel_users .p
border-radius: 5px;
padding-left: 5px;
padding: 12px;
overflow: hidden;
text-overflow: ellipsis;
}
#panel_settings.rowlist.bgavatars.micro_grid, .micro_grid {
grid-gap: 24px;

View File

@ -1,3 +1,6 @@
#main {
max-width: none;
}
#profile_left_lane {
margin-right: 24px;
}

View File

@ -168,6 +168,9 @@ li a {
.rowmenu {
border: 1px solid hsl(0, 0%, 80%);
}
.rowmenu > div:not(:last-child) {
border-bottom: 1px solid hsl(0, 0%, 80%);
}
.rowsmall {
font-size: 12px;
}

View File

@ -0,0 +1,9 @@
.sidebar {
display: none;
}
.colline {
border-left: 1px solid hsl(0, 0%, 80%);
padding: 10px;
border-right: 1px solid hsl(0, 0%, 80%);
background-color: white;
}