46a87defee
optimise profiles simple nil ptr handling in template boolean expressions
949 lines
31 KiB
Go
949 lines
31 KiB
Go
package common
|
|
|
|
import (
|
|
"fmt"
|
|
"html/template"
|
|
"io"
|
|
"io/ioutil"
|
|
"log"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/Azareal/Gosora/common/alerts"
|
|
p "github.com/Azareal/Gosora/common/phrases"
|
|
tmpl "github.com/Azareal/Gosora/common/templates"
|
|
qgen "github.com/Azareal/Gosora/query_gen"
|
|
"github.com/Azareal/Gosora/uutils"
|
|
)
|
|
|
|
var Ctemplates []string // TODO: Use this to filter out top level templates we don't need
|
|
var DefaultTemplates = template.New("")
|
|
var DefaultTemplateFuncMap map[string]interface{}
|
|
|
|
//var Templates = template.New("")
|
|
var PrebuildTmplList []func(User, *Header) CTmpl
|
|
|
|
func skipCTmpl(key string) bool {
|
|
for _, tmpl := range Ctemplates {
|
|
if strings.HasSuffix(key, "/"+tmpl+".html") {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
type CTmpl struct {
|
|
Name string
|
|
Filename string
|
|
Path string
|
|
StructName string
|
|
Data interface{}
|
|
Imports []string
|
|
}
|
|
|
|
func genIntTmpl(name string) func(pi interface{}, w io.Writer) error {
|
|
return func(pi interface{}, w io.Writer) error {
|
|
theme := Themes[DefaultThemeBox.Load().(string)]
|
|
mapping, ok := theme.TemplatesMap[name]
|
|
if !ok {
|
|
mapping = name
|
|
}
|
|
return DefaultTemplates.ExecuteTemplate(w, mapping+".html", pi)
|
|
}
|
|
}
|
|
|
|
// TODO: Refactor the template trees to not need these
|
|
// nolint
|
|
var Template_topic_handle = genIntTmpl("topic")
|
|
var Template_topic_guest_handle = Template_topic_handle
|
|
var Template_topic_member_handle = Template_topic_handle
|
|
var Template_topic_alt_handle = genIntTmpl("topic")
|
|
var Template_topic_alt_guest_handle = Template_topic_alt_handle
|
|
var Template_topic_alt_member_handle = Template_topic_alt_handle
|
|
|
|
// nolint
|
|
var Template_topics_handle = genIntTmpl("topics")
|
|
var Template_topics_guest_handle = Template_topics_handle
|
|
var Template_topics_member_handle = Template_topics_handle
|
|
|
|
// nolint
|
|
var Template_forum_handle = genIntTmpl("forum")
|
|
var Template_forum_guest_handle = Template_forum_handle
|
|
var Template_forum_member_handle = Template_forum_handle
|
|
|
|
// nolint
|
|
var Template_forums_handle = genIntTmpl("forums")
|
|
var Template_forums_guest_handle = Template_forums_handle
|
|
var Template_forums_member_handle = Template_forums_handle
|
|
|
|
// nolint
|
|
var Template_profile_handle = genIntTmpl("profile")
|
|
var Template_profile_guest_handle = Template_profile_handle
|
|
var Template_profile_member_handle = Template_profile_handle
|
|
|
|
// nolint
|
|
var Template_create_topic_handle = genIntTmpl("create_topic")
|
|
var Template_login_handle = genIntTmpl("login")
|
|
var Template_register_handle = genIntTmpl("register")
|
|
var Template_error_handle = genIntTmpl("error")
|
|
var Template_ip_search_handle = genIntTmpl("ip_search")
|
|
var Template_account_handle = genIntTmpl("account")
|
|
|
|
func tmplInitUsers() (*User, *User, *User) {
|
|
avatar, microAvatar := BuildAvatar(62, "")
|
|
u := User{62, BuildProfileURL("fake-user", 62), "Fake User", "compiler@localhost", 0, false, false, false, false, false, false, GuestPerms, make(map[string]bool), "", false, "", avatar, microAvatar, "", "", 0, 0, 0, 0, StartTime, "0.0.0.0.0", 0, 0, nil}
|
|
|
|
// TODO: Do a more accurate level calculation for this?
|
|
avatar, microAvatar = BuildAvatar(1, "")
|
|
u2 := User{1, BuildProfileURL("admin-alice", 1), "Admin Alice", "alice@localhost", 1, true, true, true, true, false, false, AllPerms, make(map[string]bool), "", true, "", avatar, microAvatar, "", "", 58, 1000, 0, 1000, StartTime, "127.0.0.1", 0, 0, nil}
|
|
|
|
avatar, microAvatar = BuildAvatar(2, "")
|
|
u3 := User{2, BuildProfileURL("admin-fred", 62), "Admin Fred", "fred@localhost", 1, true, true, true, true, false, false, AllPerms, make(map[string]bool), "", true, "", avatar, microAvatar, "", "", 42, 900, 0, 900, StartTime, "::1", 0, 0, nil}
|
|
return &u, &u2, &u3
|
|
}
|
|
|
|
func tmplInitHeaders(user, user2, 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.css"},
|
|
Scripts: []string{"whatever.js"},
|
|
PreScriptsAsync: []string{"whatever.js"},
|
|
ScriptsAsync: []string{"whatever.js"},
|
|
Widgets: PageWidgets{
|
|
LeftSidebar: template.HTML("lalala"),
|
|
},
|
|
}
|
|
|
|
buildHeader := func(user *User) *Header {
|
|
head := &Header{Site: Site}
|
|
*head = *header
|
|
head.CurrentUser = user
|
|
return head
|
|
}
|
|
|
|
return header, buildHeader(user2), buildHeader(user3)
|
|
}
|
|
|
|
type TmplLoggedin struct {
|
|
Stub string
|
|
Guest string
|
|
Member string
|
|
}
|
|
|
|
type nobreak interface{}
|
|
|
|
type TItem struct {
|
|
Expects string
|
|
ExpectsInt interface{}
|
|
LoggedIn bool
|
|
}
|
|
|
|
type TItemHold map[string]TItem
|
|
|
|
func (h TItemHold) Add(name, expects string, expectsInt interface{}) {
|
|
h[name] = TItem{expects, expectsInt, true}
|
|
}
|
|
|
|
func (h TItemHold) AddStd(name, expects string, expectsInt interface{}) {
|
|
h[name] = TItem{expects, expectsInt, false}
|
|
}
|
|
|
|
// ? - Add template hooks?
|
|
func CompileTemplates() error {
|
|
log.Print("Compiling the templates")
|
|
// TODO: Implement per-theme template overrides here too
|
|
overriden := make(map[string]map[string]bool)
|
|
for _, th := range Themes {
|
|
overriden[th.Name] = make(map[string]bool)
|
|
log.Printf("th.OverridenTemplates: %+v\n", th.OverridenTemplates)
|
|
for _, override := range th.OverridenTemplates {
|
|
overriden[th.Name][override] = true
|
|
}
|
|
}
|
|
log.Printf("overriden: %+v\n", overriden)
|
|
|
|
config := tmpl.CTemplateConfig{
|
|
Minify: Config.MinifyTemplates,
|
|
Debug: Dev.DebugMode,
|
|
SuperDebug: Dev.TemplateDebug,
|
|
DockToID: DockToID,
|
|
}
|
|
c := tmpl.NewCTemplateSet("normal")
|
|
c.SetConfig(config)
|
|
c.SetBaseImportMap(map[string]string{
|
|
"io": "io",
|
|
"github.com/Azareal/Gosora/common": "c github.com/Azareal/Gosora/common",
|
|
})
|
|
c.SetBuildTags("!no_templategen")
|
|
c.SetOverrideTrack(overriden)
|
|
c.SetPerThemeTmpls(make(map[string]bool))
|
|
|
|
log.Print("Compiling the default templates")
|
|
var wg sync.WaitGroup
|
|
if err := compileTemplates(&wg, c, ""); err != nil {
|
|
return err
|
|
}
|
|
oroots := c.GetOverridenRoots()
|
|
log.Printf("oroots: %+v\n", oroots)
|
|
|
|
log.Print("Compiling the per-theme templates")
|
|
for th, tmpls := range oroots {
|
|
c.ResetLogs("normal-" + th)
|
|
c.SetThemeName(th)
|
|
c.SetPerThemeTmpls(tmpls)
|
|
log.Print("th: ", th)
|
|
log.Printf("perThemeTmpls: %+v\n", tmpls)
|
|
err := compileTemplates(&wg, c, th)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
writeTemplateList(c, &wg, "./")
|
|
return nil
|
|
}
|
|
|
|
func compileCommons(c *tmpl.CTemplateSet, head, head2 *Header, forumList []Forum, o TItemHold) error {
|
|
// TODO: Add support for interface{}s
|
|
_, user2, user3 := tmplInitUsers()
|
|
now := time.Now()
|
|
|
|
// Convienience function to save a line here and there
|
|
htitle := func(name string) *Header {
|
|
head.Title = name
|
|
return head
|
|
}
|
|
/*htitle2 := func(name string) *Header {
|
|
head2.Title = name
|
|
return head2
|
|
}*/
|
|
|
|
var topicsList []*TopicsRow
|
|
topicsList = append(topicsList, &TopicsRow{1, "topic-title", "Topic Title", "The topic content.", 1, false, false, now, now, user3.ID, 1, 1, "", "::1", 1, 0, 1, 1, 0, "classname", 0, "", user2, "", 0, user3, "General", "/forum/general.2", nil})
|
|
topicListPage := TopicListPage{htitle("Topic List"), topicsList, forumList, Config.DefaultForum, TopicListSort{"lastupdated", false}, Paginator{[]int{1}, 1, 1}}
|
|
o.Add("topics", "c.TopicListPage", topicListPage)
|
|
o.Add("topics_mini", "c.TopicListPage", topicListPage)
|
|
|
|
forumItem := BlankForum(1, "general-forum.1", "General Forum", "Where the general stuff happens", true, "all", 0, "", 0)
|
|
forumPage := ForumPage{htitle("General Forum"), topicsList, forumItem, Paginator{[]int{1}, 1, 1}}
|
|
o.Add("forum", "c.ForumPage", forumPage)
|
|
o.Add("forums", "c.ForumsPage", ForumsPage{htitle("Forum List"), forumList})
|
|
|
|
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!", 0, false, false, now, now, 1, 1, 0, "", "127.0.0.1", 1, 0, 1, 0, "classname", poll.ID, "weird-data", BuildProfileURL("fake-user", 62), "Fake User", Config.DefaultGroup, avatar, microAvatar, 0, "", "", "", 58, false, miniAttach, nil, false}
|
|
|
|
var replyList []*ReplyUser
|
|
reply := Reply{1, 1, "Yo!", 1 /*, Config.DefaultGroup*/, now, 0, 0, 1, "::1", true, 1, 1, ""}
|
|
ru := &ReplyUser{ClassName: "", Reply: reply, CreatedByName: "Alice", Avatar: avatar, Group: Config.DefaultGroup, Level: 0, Attachments: miniAttach}
|
|
ru.Init(user2)
|
|
replyList = append(replyList, ru)
|
|
tpage := TopicPage{htitle("Topic Name"), replyList, topic, &Forum{ID: 1, Name: "Hahaha"}, &poll, Paginator{[]int{1}, 1, 1}}
|
|
tpage.Forum.Link = BuildForumURL(NameToSlug(tpage.Forum.Name), tpage.Forum.ID)
|
|
o.Add("topic", "c.TopicPage", tpage)
|
|
o.Add("topic_mini", "c.TopicPage", tpage)
|
|
o.Add("topic_alt", "c.TopicPage", tpage)
|
|
o.Add("topic_alt_mini", "c.TopicPage", tpage)
|
|
return nil
|
|
}
|
|
|
|
func compileTemplates(wg *sync.WaitGroup, c *tmpl.CTemplateSet, themeName string) error {
|
|
// Schemas to train the template compiler on what to expect
|
|
// TODO: Add support for interface{}s
|
|
user, user2, user3 := tmplInitUsers()
|
|
header, header2, _ := tmplInitHeaders(user, user2, user3)
|
|
now := time.Now()
|
|
|
|
/*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: "/"}}
|
|
var replyList []*ReplyUser
|
|
//topic := TopicUser{1, "blah", "Blah", "Hey there!", 0, false, false, now, now, 1, 1, 0, "", "127.0.0.1", 1, 0, 1, 0, "classname", poll.ID, "weird-data", BuildProfileURL("fake-user", 62), "Fake User", Config.DefaultGroup, avatar, microAvatar, 0, "", "", "", "", "", 58, false, miniAttach, nil}
|
|
// TODO: Do we want the UID on this to be 0?
|
|
//avatar, microAvatar = BuildAvatar(0, "")
|
|
reply := Reply{1, 1, "Yo!", 1 /*, Config.DefaultGroup*/, now, 0, 0, 1, "::1", true, 1, 1, ""}
|
|
ru := &ReplyUser{ClassName: "", Reply: reply, CreatedByName: "Alice", Avatar: "", Group: Config.DefaultGroup, Level: 0, Attachments: miniAttach}
|
|
ru.Init(user)
|
|
replyList = append(replyList, ru)
|
|
|
|
// TODO: Use a dummy forum list to avoid o(n) problems
|
|
/*var forumList []Forum
|
|
forums, err := Forums.GetAll()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, forum := range forums {
|
|
forumList = append(forumList, *forum)
|
|
}*/
|
|
forum := BlankForum(1, "/forum/d.1", "d", "d desc", true, "", 0, "", 1)
|
|
forum.LastTopic = BlankTopic()
|
|
forum.LastReplyer = BlankUser()
|
|
forumList := []Forum{*forum}
|
|
|
|
// Convienience function to save a line here and there
|
|
htitle := func(name string) *Header {
|
|
header.Title = name
|
|
return header
|
|
}
|
|
t := TItemHold(make(map[string]TItem))
|
|
err := compileCommons(c, header, header2, forumList, t)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ppage := ProfilePage{htitle("User 526"), replyList, *user, 0, 0, false, false, false} // TODO: Use the score from user to generate the currentScore and nextScore
|
|
t.Add("profile", "c.ProfilePage", ppage)
|
|
|
|
var topicsList []*TopicsRow
|
|
topicsList = append(topicsList, &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, 1, 0, "classname", 0, "", user2, "", 0, user3, "General", "/forum/general.2", nil})
|
|
topicListPage := TopicListPage{htitle("Topic List"), topicsList, forumList, Config.DefaultForum, TopicListSort{"lastupdated", false}, Paginator{[]int{1}, 1, 1}}
|
|
|
|
forumItem := BlankForum(1, "general-forum.1", "General Forum", "Where the general stuff happens", true, "all", 0, "", 0)
|
|
forumPage := ForumPage{htitle("General Forum"), topicsList, forumItem, Paginator{[]int{1}, 1, 1}}
|
|
|
|
// Experimental!
|
|
for _, tmpl := range strings.Split(Dev.ExtraTmpls, ",") {
|
|
sp := strings.Split(tmpl, ":")
|
|
if len(sp) < 2 {
|
|
continue
|
|
}
|
|
typ := "0"
|
|
if len(sp) == 3 {
|
|
typ = sp[2]
|
|
}
|
|
|
|
var pi interface{}
|
|
switch sp[1] {
|
|
case "c.TopicListPage":
|
|
pi = topicListPage
|
|
case "c.ForumPage":
|
|
pi = forumPage
|
|
case "c.ProfilePage":
|
|
pi = ppage
|
|
case "c.Page":
|
|
pi = Page{htitle("Something"), tList, nil}
|
|
default:
|
|
continue
|
|
}
|
|
|
|
if typ == "1" {
|
|
t.Add(sp[0], sp[1], pi)
|
|
} else {
|
|
t.AddStd(sp[0], sp[1], pi)
|
|
}
|
|
}
|
|
|
|
t.AddStd("login", "c.Page", Page{htitle("Login Page"), tList, nil})
|
|
t.AddStd("register", "c.Page", Page{htitle("Registration Page"), tList, false})
|
|
t.AddStd("error", "c.ErrorPage", ErrorPage{htitle("Error"), "A problem has occurred in the system."})
|
|
|
|
ipSearchPage := IPSearchPage{htitle("IP Search"), map[int]*User{1: user2}, "::1"}
|
|
t.AddStd("ip_search", "c.IPSearchPage", ipSearchPage)
|
|
|
|
var inter nobreak
|
|
accountPage := Account{header, "dashboard", "account_own_edit", inter}
|
|
t.AddStd("account", "c.Account", accountPage)
|
|
|
|
parti := []*User{user}
|
|
convo := &Conversation{1, BuildConvoURL(1), user.ID, time.Now(), 0, time.Now()}
|
|
convoItems := []ConvoViewRow{ConvoViewRow{&ConversationPost{1, 1, "hey", "", user.ID}, user, "", 4, true}}
|
|
convoPage := ConvoViewPage{header, convo, convoItems, parti, true, Paginator{[]int{1}, 1, 1}}
|
|
t.AddStd("convo", "c.ConvoViewPage", convoPage)
|
|
|
|
convos := []*ConversationExtra{&ConversationExtra{&Conversation{}, []*User{user}}}
|
|
var cRows []ConvoListRow
|
|
for _, convo := range convos {
|
|
cRows = append(cRows, ConvoListRow{convo, convo.Users, false})
|
|
}
|
|
convoListPage := ConvoListPage{header, cRows, Paginator{[]int{1}, 1, 1}}
|
|
t.AddStd("convos", "c.ConvoListPage", convoListPage)
|
|
|
|
basePage := &BasePanelPage{header, PanelStats{}, "dashboard", ReportForumID}
|
|
t.AddStd("panel", "c.Panel", Panel{basePage, "panel_dashboard_right", "", "panel_dashboard", inter})
|
|
ges := []GridElement{GridElement{"", "", "", 1, "grid_istat", "", "", ""}}
|
|
t.AddStd("panel_dashboard", "c.DashGrids", DashGrids{ges, ges})
|
|
|
|
goVersion := runtime.Version()
|
|
dbVersion := qgen.Builder.DbVersion()
|
|
var memStats runtime.MemStats
|
|
runtime.ReadMemStats(&memStats)
|
|
debugTasks := DebugPageTasks{0, 0, 0, 0, 0}
|
|
debugCache := DebugPageCache{1, 1, 1, 2, 2, 2, true}
|
|
debugDatabase := DebugPageDatabase{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
|
|
debugDisk := DebugPageDisk{1, 1, 1, 1, 1, 1}
|
|
dpage := PanelDebugPage{basePage, goVersion, dbVersion, "0s", 1, qgen.Builder.GetAdapter().GetName(), 1, 1, debugTasks, memStats, debugCache, debugDatabase, debugDisk}
|
|
t.AddStd("panel_debug", "c.PanelDebugPage", dpage)
|
|
//t.AddStd("panel_analytics", "c.PanelAnalytics", Panel{basePage, "panel_dashboard_right","panel_dashboard", inter})
|
|
|
|
writeTemplate := func(name string, content interface{}) {
|
|
log.Print("Writing template '" + name + "'")
|
|
writeTmpl := func(name, content string) {
|
|
if content == "" {
|
|
return //log.Fatal("No content body for " + name)
|
|
}
|
|
err := writeFile("./tmpl_"+name+".go", content)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
wg.Add(1)
|
|
go func() {
|
|
tname := themeName
|
|
if tname != "" {
|
|
tname = "_" + tname
|
|
}
|
|
switch content := content.(type) {
|
|
case string:
|
|
writeTmpl(name+tname, content)
|
|
case TmplLoggedin:
|
|
writeTmpl(name+tname, content.Stub)
|
|
writeTmpl(name+tname+"_guest", content.Guest)
|
|
writeTmpl(name+tname+"_member", content.Member)
|
|
}
|
|
wg.Done()
|
|
}()
|
|
}
|
|
|
|
// Let plugins register their own templates
|
|
DebugLog("Registering the templates for the plugins")
|
|
config := c.GetConfig()
|
|
config.SkipHandles = true
|
|
c.SetConfig(config)
|
|
for _, tmplfunc := range PrebuildTmplList {
|
|
tmplItem := tmplfunc(*user, header)
|
|
varList := make(map[string]tmpl.VarItem)
|
|
compiledTmpl, err := c.Compile(tmplItem.Filename, tmplItem.Path, tmplItem.StructName, tmplItem.Data, varList, tmplItem.Imports...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
writeTemplate(tmplItem.Name, compiledTmpl)
|
|
}
|
|
|
|
log.Print("Writing the templates")
|
|
for name, titem := range t {
|
|
log.Print("Writing " + name)
|
|
varList := make(map[string]tmpl.VarItem)
|
|
if titem.LoggedIn {
|
|
stub, guest, member, err := c.CompileByLoggedin(name+".html", "templates/", titem.Expects, titem.ExpectsInt, varList)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
writeTemplate(name, TmplLoggedin{stub, guest, member})
|
|
} else {
|
|
tmpl, err := c.Compile(name+".html", "templates/", titem.Expects, titem.ExpectsInt, varList)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
writeTemplate(name, tmpl)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ? - Add template hooks?
|
|
func CompileJSTemplates() error {
|
|
log.Print("Compiling the JS templates")
|
|
// TODO: Implement per-theme template overrides here too
|
|
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)
|
|
|
|
config := tmpl.CTemplateConfig{
|
|
Minify: Config.MinifyTemplates,
|
|
Debug: Dev.DebugMode,
|
|
SuperDebug: Dev.TemplateDebug,
|
|
SkipHandles: true,
|
|
SkipTmplPtrMap: true,
|
|
SkipInitBlock: false,
|
|
PackageName: "tmpl",
|
|
DockToID: DockToID,
|
|
}
|
|
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
|
|
}
|
|
}
|
|
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()
|
|
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": "c 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")
|
|
|
|
t := TItemHold(make(map[string]TItem))
|
|
|
|
topicsRow := &TopicsRow{1, "topic-title", "Topic Title", "The topic content.", 1, false, false, now, now, user3.ID, 1, 1, "", "::1", 1, 0, 1, 0, 1, "classname", 0, "", user2, "", 0, user3, "General", "/forum/general.2", nil}
|
|
t.AddStd("topics_topic", "c.TopicsRow", topicsRow)
|
|
|
|
poll := Poll{ID: 1, Type: 0, Options: map[int]string{0: "Nothing", 1: "Something"}, Results: map[int]int{0: 5, 1: 2}, QuickOptions: []PollOption{
|
|
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, "", "::1", 1, 0, 1, 0, "classname", poll.ID, "weird-data", BuildProfileURL("fake-user", 62), "Fake User", Config.DefaultGroup, avatar, microAvatar, 0, "", "", "", 58, false, miniAttach, nil, false}
|
|
var replyList []*ReplyUser
|
|
// TODO: Do we really want the UID here to be zero?
|
|
avatar, microAvatar = BuildAvatar(0, "")
|
|
reply := Reply{1, 1, "Yo!", 1 /*, Config.DefaultGroup*/, now, 0, 0, 1, "::1", true, 1, 1, ""}
|
|
ru := &ReplyUser{ClassName: "", Reply: reply, CreatedByName: "Alice", Avatar: avatar, Group: Config.DefaultGroup, Level: 0, Attachments: miniAttach}
|
|
ru.Init(user)
|
|
replyList = append(replyList, ru)
|
|
|
|
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)
|
|
t.AddStd("topic_posts", "c.TopicPage", tpage)
|
|
t.AddStd("topic_alt_posts", "c.TopicPage", tpage)
|
|
|
|
itemsPerPage := 25
|
|
_, page, lastPage := PageOffset(20, 1, itemsPerPage)
|
|
pageList := Paginate(page, lastPage, 5)
|
|
t.AddStd("paginator", "c.Paginator", Paginator{pageList, page, lastPage})
|
|
|
|
t.AddStd("topic_c_edit_post", "c.TopicCEditPost", TopicCEditPost{ID: 0, Source: "", Ref: ""})
|
|
t.AddStd("topic_c_attach_item", "c.TopicCAttachItem", TopicCAttachItem{ID: 1, ImgSrc: "", Path: "", FullPath: ""})
|
|
t.AddStd("topic_c_poll_input", "c.TopicCPollInput", TopicCPollInput{Index: 0})
|
|
|
|
parti := []*User{user}
|
|
convo := &Conversation{1, BuildConvoURL(1), user.ID, time.Now(), 0, time.Now()}
|
|
convoItems := []ConvoViewRow{ConvoViewRow{&ConversationPost{1, 1, "hey", "", user.ID}, user, "", 4, true}}
|
|
convoPage := ConvoViewPage{header, convo, convoItems, parti, true, Paginator{[]int{1}, 1, 1}}
|
|
t.AddStd("convo", "c.ConvoViewPage", convoPage)
|
|
|
|
t.AddStd("notice", "string", "nonono")
|
|
|
|
dirPrefix := "./tmpl_client/"
|
|
writeTemplate := func(name, 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+"tmpl_"+name+tname+".jgo", content)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
wg.Done()
|
|
}()
|
|
}
|
|
|
|
log.Print("Writing the templates")
|
|
for name, titem := range t {
|
|
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("topic_posts", topicPostsTmpl)
|
|
writeTemplate("topic_alt_posts", topicAltPostsTmpl)
|
|
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"
|
|
tFragCount := make(map[string]int)
|
|
bodyMap := make(map[string]string) //map[body]fragmentPrefix
|
|
//tmplMap := make(map[string]map[string]string) // map[tmpl]map[body]fragmentPrefix
|
|
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)
|
|
bodyMap = tmplMap[frag.TmplName]
|
|
}*/
|
|
fp, ok := bodyMap[frag.Body]
|
|
if !ok {
|
|
bodyMap[frag.Body] = front
|
|
var bits string
|
|
DebugLog("encoding f.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) + "',"
|
|
}
|
|
}
|
|
tmpStr := strconv.Itoa(tmpCount)
|
|
pout += "arr_" + tmpStr + " := [...]byte{" + bits + "}\n"
|
|
pout += front + " = arr_" + tmpStr + "[:]\n"
|
|
tmpCount++
|
|
//pout += front + " = []byte(`" + frag.Body + "`)\n"
|
|
} else {
|
|
DebugLog("encoding cached index " + fp)
|
|
pout += front + " = " + fp + "\n"
|
|
}
|
|
|
|
_, ok = tFragCount[frag.TmplName]
|
|
if !ok {
|
|
tFragCount[frag.TmplName] = 0
|
|
}
|
|
tFragCount[frag.TmplName]++
|
|
}
|
|
|
|
out := "package " + c.GetConfig().PackageName + "\n\n"
|
|
getterstr := "\n// nolint\nGetFrag = func(name string) [][]byte {\nswitch(name) {\n"
|
|
for templateName, count := range tFragCount {
|
|
//out += "var " + templateName + "_frags = make([][]byte," + strconv.Itoa(count) + ")\n"
|
|
out += "var " + templateName + "_frags [" + strconv.Itoa(count) + "][]byte\n"
|
|
getterstr += "\tcase \"" + templateName + "\":\n"
|
|
//getterstr += "\treturn " + templateName + "_frags\n"
|
|
getterstr += "\treturn " + templateName + "_frags[:]\n"
|
|
}
|
|
getterstr += "}\nreturn nil\n}\n"
|
|
out += pout + "\n" + getterstr + "}\n"
|
|
|
|
return out
|
|
}
|
|
|
|
func writeTemplateList(c *tmpl.CTemplateSet, wg *sync.WaitGroup, prefix string) {
|
|
log.Print("Writing template list")
|
|
wg.Add(1)
|
|
go func() {
|
|
err := writeFile(prefix+"tmpl_list.go", getTemplateList(c, wg, prefix))
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
wg.Done()
|
|
}()
|
|
wg.Wait()
|
|
}
|
|
|
|
func arithToInt64(in interface{}) (o int64) {
|
|
switch in := in.(type) {
|
|
case int64:
|
|
o = in
|
|
case int32:
|
|
o = int64(in)
|
|
case int:
|
|
o = int64(in)
|
|
case uint32:
|
|
o = int64(in)
|
|
case uint16:
|
|
o = int64(in)
|
|
case uint8:
|
|
o = int64(in)
|
|
case uint:
|
|
o = int64(in)
|
|
}
|
|
return o
|
|
}
|
|
|
|
func arithDuoToInt64(left, right interface{}) (leftInt, rightInt int64) {
|
|
return arithToInt64(left), arithToInt64(right)
|
|
}
|
|
|
|
func initDefaultTmplFuncMap() {
|
|
// TODO: Add support for floats
|
|
fmap := make(map[string]interface{})
|
|
fmap["add"] = func(left, right interface{}) interface{} {
|
|
leftInt, rightInt := arithDuoToInt64(left, right)
|
|
return leftInt + rightInt
|
|
}
|
|
|
|
fmap["subtract"] = func(left, right interface{}) interface{} {
|
|
leftInt, rightInt := arithDuoToInt64(left, right)
|
|
return leftInt - rightInt
|
|
}
|
|
|
|
fmap["multiply"] = func(left, right interface{}) interface{} {
|
|
leftInt, rightInt := arithDuoToInt64(left, right)
|
|
return leftInt * rightInt
|
|
}
|
|
|
|
fmap["divide"] = func(left, right interface{}) interface{} {
|
|
leftInt, rightInt := arithDuoToInt64(left, right)
|
|
if leftInt == 0 || rightInt == 0 {
|
|
return 0
|
|
}
|
|
return leftInt / rightInt
|
|
}
|
|
|
|
fmap["dock"] = func(dock, headerInt interface{}) interface{} {
|
|
return template.HTML(BuildWidget(dock.(string), headerInt.(*Header)))
|
|
}
|
|
|
|
fmap["hasWidgets"] = func(dock, headerInt interface{}) interface{} {
|
|
return HasWidgets(dock.(string), headerInt.(*Header))
|
|
}
|
|
|
|
fmap["elapsed"] = func(startedAtInt interface{}) interface{} {
|
|
//return time.Since(startedAtInt.(time.Time)).String()
|
|
return time.Duration(uutils.Nanotime() - startedAtInt.(int64)).String()
|
|
}
|
|
|
|
fmap["lang"] = func(phraseNameInt interface{}) interface{} {
|
|
phraseName, ok := phraseNameInt.(string)
|
|
if !ok {
|
|
panic("phraseNameInt is not a string")
|
|
}
|
|
// TODO: Log non-existent phrases?
|
|
return template.HTML(p.GetTmplPhrase(phraseName))
|
|
}
|
|
|
|
// TODO: Implement this in the template generator too
|
|
fmap["langf"] = func(phraseNameInt interface{}, args ...interface{}) interface{} {
|
|
phraseName, ok := phraseNameInt.(string)
|
|
if !ok {
|
|
panic("phraseNameInt is not a string")
|
|
}
|
|
// TODO: Log non-existent phrases?
|
|
// TODO: Optimise TmplPhrasef so we don't use slow Sprintf there
|
|
return template.HTML(p.GetTmplPhrasef(phraseName, args...))
|
|
}
|
|
|
|
fmap["level"] = func(levelInt interface{}) interface{} {
|
|
level, ok := levelInt.(int)
|
|
if !ok {
|
|
panic("levelInt is not an integer")
|
|
}
|
|
return template.HTML(p.GetLevelPhrase(level))
|
|
}
|
|
|
|
fmap["bunit"] = func(byteInt interface{}) interface{} {
|
|
var byteFloat float64
|
|
var unit string
|
|
switch bytes := byteInt.(type) {
|
|
case int:
|
|
byteFloat, unit = ConvertByteUnit(float64(bytes))
|
|
case int64:
|
|
byteFloat, unit = ConvertByteUnit(float64(bytes))
|
|
case uint64:
|
|
byteFloat, unit = ConvertByteUnit(float64(bytes))
|
|
case float64:
|
|
byteFloat, unit = ConvertByteUnit(bytes)
|
|
default:
|
|
panic("bytes is not an int, int64 or uint64")
|
|
}
|
|
return fmt.Sprintf("%.1f", byteFloat) + unit
|
|
}
|
|
|
|
fmap["abstime"] = func(timeInt interface{}) interface{} {
|
|
time, ok := timeInt.(time.Time)
|
|
if !ok {
|
|
panic("timeInt is not a time.Time")
|
|
}
|
|
return time.Format("2006-01-02 15:04:05")
|
|
}
|
|
|
|
fmap["reltime"] = func(timeInt interface{}) interface{} {
|
|
time, ok := timeInt.(time.Time)
|
|
if !ok {
|
|
panic("timeInt is not a time.Time")
|
|
}
|
|
return RelativeTime(time)
|
|
}
|
|
|
|
fmap["scope"] = func(name interface{}) interface{} {
|
|
return ""
|
|
}
|
|
|
|
fmap["dyntmpl"] = func(nameInt, pageInt, headerInt interface{}) interface{} {
|
|
header := headerInt.(*Header)
|
|
err := header.Theme.RunTmpl(nameInt.(string), pageInt, header.Writer)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return ""
|
|
}
|
|
|
|
fmap["ptmpl"] = func(nameInt, pageInt, headerInt interface{}) interface{} {
|
|
header := headerInt.(*Header)
|
|
err := header.Theme.RunTmpl(nameInt.(string), pageInt, header.Writer)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return ""
|
|
}
|
|
|
|
fmap["js"] = func() interface{} {
|
|
return false
|
|
}
|
|
|
|
fmap["flush"] = func() interface{} {
|
|
return nil
|
|
}
|
|
|
|
DefaultTemplateFuncMap = fmap
|
|
}
|
|
|
|
func loadTemplates(t *template.Template, themeName string) error {
|
|
t.Funcs(DefaultTemplateFuncMap)
|
|
tFiles, err := filepath.Glob("templates/*.html")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
tFileMap := make(map[string]int)
|
|
for index, path := range tFiles {
|
|
path = strings.Replace(path, "\\", "/", -1)
|
|
log.Print("templateFile: ", path)
|
|
if skipCTmpl(path) {
|
|
log.Print("skipping")
|
|
continue
|
|
}
|
|
tFileMap[path] = index
|
|
}
|
|
|
|
overrideFiles, err := filepath.Glob("templates/overrides/*.html")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, path := range overrideFiles {
|
|
path = strings.Replace(path, "\\", "/", -1)
|
|
log.Print("overrideFile: ", path)
|
|
if skipCTmpl(path) {
|
|
log.Print("skipping")
|
|
continue
|
|
}
|
|
index, ok := tFileMap["templates/"+strings.TrimPrefix(path, "templates/overrides/")]
|
|
if !ok {
|
|
log.Print("not ok: templates/" + strings.TrimPrefix(path, "templates/overrides/"))
|
|
tFiles = append(tFiles, path)
|
|
continue
|
|
}
|
|
tFiles[index] = path
|
|
}
|
|
|
|
if themeName != "" {
|
|
overrideFiles, err := filepath.Glob("./themes/" + themeName + "/overrides/*.html")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, path := range overrideFiles {
|
|
path = strings.Replace(path, "\\", "/", -1)
|
|
log.Print("overrideFile: ", path)
|
|
if skipCTmpl(path) {
|
|
log.Print("skipping")
|
|
continue
|
|
}
|
|
index, ok := tFileMap["templates/"+strings.TrimPrefix(path, "themes/"+themeName+"/overrides/")]
|
|
if !ok {
|
|
log.Print("not ok: templates/" + strings.TrimPrefix(path, "themes/"+themeName+"/overrides/"))
|
|
tFiles = append(tFiles, path)
|
|
continue
|
|
}
|
|
tFiles[index] = path
|
|
}
|
|
}
|
|
|
|
// TODO: Minify these
|
|
/*err = t.ParseFiles(tFiles...)
|
|
if err != nil {
|
|
return err
|
|
}*/
|
|
for _, fname := range tFiles {
|
|
b, err := ioutil.ReadFile(fname)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
s := tmpl.Minify(string(b))
|
|
name := filepath.Base(fname)
|
|
var tmpl *template.Template
|
|
if name == t.Name() {
|
|
tmpl = t
|
|
} else {
|
|
tmpl = t.New(name)
|
|
}
|
|
_, err = tmpl.Parse(s)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
_, err = t.ParseGlob("pages/*")
|
|
return err
|
|
}
|
|
|
|
func InitTemplates() error {
|
|
DebugLog("Initialising the template system")
|
|
initDefaultTmplFuncMap()
|
|
|
|
// The interpreted templates...
|
|
DebugLog("Loading the template files...")
|
|
return loadTemplates(DefaultTemplates, "")
|
|
}
|