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:
parent
fe33112827
commit
cc1d0f089a
|
@ -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?
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}*/
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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...)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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!")
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)*/
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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'"))
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
|
|
@ -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)*/
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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}}
|
||||
|
|
|
@ -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> <a href="/pages/{{.Name}}">[{{.Name}}]</a>
|
||||
|
|
|
@ -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}}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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}} {{lang "topic_list.replies_suffix"}}</span>
|
||||
<span class="likeCount">{{.LikeCount}} {{lang "topic_list.likes_suffix"}}</span>
|
||||
<span class="viewCount">{{.ViewCount}} {{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>
|
|
@ -1,3 +1,6 @@
|
|||
#main {
|
||||
max-width: none;
|
||||
}
|
||||
.colstack_left {
|
||||
width: 200px;
|
||||
padding-bottom: 6px;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
#main {
|
||||
max-width: none;
|
||||
}
|
||||
#profile_left_lane {
|
||||
margin-right: 24px;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue