github.com/Azareal/Gosora@v0.0.0-20210729070923-553e66b59003/common/template_init.go (about)

     1  package common
     2  
     3  import (
     4  	"fmt"
     5  	"html/template"
     6  	"io"
     7  	"io/ioutil"
     8  	"log"
     9  	"path/filepath"
    10  	"runtime"
    11  	"strconv"
    12  	"strings"
    13  	"sync"
    14  	"time"
    15  
    16  	"github.com/Azareal/Gosora/common/alerts"
    17  	p "github.com/Azareal/Gosora/common/phrases"
    18  	tmpl "github.com/Azareal/Gosora/common/templates"
    19  	qgen "github.com/Azareal/Gosora/query_gen"
    20  	"github.com/Azareal/Gosora/uutils"
    21  )
    22  
    23  var Ctemplates []string // TODO: Use this to filter out top level templates we don't need
    24  var DefaultTemplates = template.New("")
    25  var DefaultTemplateFuncMap map[string]interface{}
    26  
    27  //var Templates = template.New("")
    28  var PrebuildTmplList []func(User, *Header) CTmpl
    29  
    30  func skipCTmpl(key string) bool {
    31  	for _, tmpl := range Ctemplates {
    32  		if strings.HasSuffix(key, "/"+tmpl+".html") {
    33  			return true
    34  		}
    35  	}
    36  	return false
    37  }
    38  
    39  type CTmpl struct {
    40  	Name       string
    41  	Filename   string
    42  	Path       string
    43  	StructName string
    44  	Data       interface{}
    45  	Imports    []string
    46  }
    47  
    48  func genIntTmpl(name string) func(pi interface{}, w io.Writer) error {
    49  	return func(pi interface{}, w io.Writer) error {
    50  		theme := Themes[DefaultThemeBox.Load().(string)]
    51  		mapping, ok := theme.TemplatesMap[name]
    52  		if !ok {
    53  			mapping = name
    54  		}
    55  		return DefaultTemplates.ExecuteTemplate(w, mapping+".html", pi)
    56  	}
    57  }
    58  
    59  // TODO: Refactor the template trees to not need these
    60  // nolint
    61  var Template_topic_handle = genIntTmpl("topic")
    62  var Template_topic_guest_handle = Template_topic_handle
    63  var Template_topic_member_handle = Template_topic_handle
    64  var Template_topic_alt_handle = genIntTmpl("topic")
    65  var Template_topic_alt_guest_handle = Template_topic_alt_handle
    66  var Template_topic_alt_member_handle = Template_topic_alt_handle
    67  
    68  // nolint
    69  var Template_topics_handle = genIntTmpl("topics")
    70  var Template_topics_guest_handle = Template_topics_handle
    71  var Template_topics_member_handle = Template_topics_handle
    72  
    73  // nolint
    74  var Template_forum_handle = genIntTmpl("forum")
    75  var Template_forum_guest_handle = Template_forum_handle
    76  var Template_forum_member_handle = Template_forum_handle
    77  
    78  // nolint
    79  var Template_forums_handle = genIntTmpl("forums")
    80  var Template_forums_guest_handle = Template_forums_handle
    81  var Template_forums_member_handle = Template_forums_handle
    82  
    83  // nolint
    84  var Template_profile_handle = genIntTmpl("profile")
    85  var Template_profile_guest_handle = Template_profile_handle
    86  var Template_profile_member_handle = Template_profile_handle
    87  
    88  // nolint
    89  var Template_create_topic_handle = genIntTmpl("create_topic")
    90  var Template_login_handle = genIntTmpl("login")
    91  var Template_register_handle = genIntTmpl("register")
    92  var Template_error_handle = genIntTmpl("error")
    93  var Template_ip_search_handle = genIntTmpl("ip_search")
    94  var Template_account_handle = genIntTmpl("account")
    95  
    96  func tmplInitUsers() (*User, *User, *User) {
    97  	avatar, microAvatar := BuildAvatar(62, "")
    98  	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, UserPrivacy{}}
    99  
   100  	// TODO: Do a more accurate level calculation for this?
   101  	avatar, microAvatar = BuildAvatar(1, "")
   102  	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, UserPrivacy{}}
   103  
   104  	avatar, microAvatar = BuildAvatar(2, "")
   105  	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, UserPrivacy{}}
   106  	return &u, &u2, &u3
   107  }
   108  
   109  func tmplInitHeaders(u, u2, u3 *User) (*Header, *Header, *Header) {
   110  	header := &Header{
   111  		Site:     Site,
   112  		Settings: SettingBox.Load().(SettingMap),
   113  		//Themes:          Themes,
   114  		ThemesSlice:     ThemesSlice,
   115  		Theme:           Themes[DefaultThemeBox.Load().(string)],
   116  		CurrentUser:     u,
   117  		NoticeList:      []string{"test"},
   118  		Stylesheets:     []HScript{{"panel.css", ""}},
   119  		Scripts:         []HScript{{"whatever.js", ""}},
   120  		PreScriptsAsync: []HScript{{"whatever.js", ""}},
   121  		ScriptsAsync:    []HScript{{"whatever.js", ""}},
   122  		Widgets: PageWidgets{
   123  			LeftSidebar: template.HTML("lalala"),
   124  		},
   125  	}
   126  
   127  	buildHeader := func(u *User) *Header {
   128  		head := &Header{Site: Site}
   129  		*head = *header
   130  		head.CurrentUser = u
   131  		return head
   132  	}
   133  
   134  	return header, buildHeader(u2), buildHeader(u3)
   135  }
   136  
   137  type TmplLoggedin struct {
   138  	Stub   string
   139  	Guest  string
   140  	Member string
   141  }
   142  
   143  type nobreak interface{}
   144  
   145  type TItem struct {
   146  	Expects    string
   147  	ExpectsInt interface{}
   148  	LoggedIn   bool
   149  }
   150  
   151  type TItemHold map[string]TItem
   152  
   153  func (h TItemHold) Add(name, expects string, expectsInt interface{}) {
   154  	h[name] = TItem{expects, expectsInt, true}
   155  }
   156  
   157  func (h TItemHold) AddStd(name, expects string, expectsInt interface{}) {
   158  	h[name] = TItem{expects, expectsInt, false}
   159  }
   160  
   161  // ? - Add template hooks?
   162  func CompileTemplates() error {
   163  	log.Print("Compiling the templates")
   164  	// TODO: Implement per-theme template overrides here too
   165  	overriden := make(map[string]map[string]bool)
   166  	for _, th := range Themes {
   167  		overriden[th.Name] = make(map[string]bool)
   168  		log.Printf("th.OverridenTemplates: %+v\n", th.OverridenTemplates)
   169  		for _, override := range th.OverridenTemplates {
   170  			overriden[th.Name][override] = true
   171  		}
   172  	}
   173  	log.Printf("overriden: %+v\n", overriden)
   174  
   175  	config := tmpl.CTemplateConfig{
   176  		Minify:     Config.MinifyTemplates,
   177  		Debug:      Dev.DebugMode,
   178  		SuperDebug: Dev.TemplateDebug,
   179  		DockToID:   DockToID,
   180  	}
   181  	c := tmpl.NewCTemplateSet("normal", "./logs/")
   182  	c.SetConfig(config)
   183  	c.SetBaseImportMap(map[string]string{
   184  		"io":                               "io",
   185  		"github.com/Azareal/Gosora/common": "c github.com/Azareal/Gosora/common",
   186  	})
   187  	c.SetBuildTags("!no_templategen")
   188  	c.SetOverrideTrack(overriden)
   189  	c.SetPerThemeTmpls(make(map[string]bool))
   190  
   191  	log.Print("Compiling the default templates")
   192  	var wg sync.WaitGroup
   193  	if err := compileTemplates(&wg, c, ""); err != nil {
   194  		return err
   195  	}
   196  	oroots := c.GetOverridenRoots()
   197  	log.Printf("oroots: %+v\n", oroots)
   198  
   199  	log.Print("Compiling the per-theme templates")
   200  	for th, tmpls := range oroots {
   201  		c.ResetLogs("normal-" + th)
   202  		c.SetThemeName(th)
   203  		c.SetPerThemeTmpls(tmpls)
   204  		log.Print("th: ", th)
   205  		log.Printf("perThemeTmpls: %+v\n", tmpls)
   206  		err := compileTemplates(&wg, c, th)
   207  		if err != nil {
   208  			return err
   209  		}
   210  	}
   211  	writeTemplateList(c, &wg, "./")
   212  	return nil
   213  }
   214  
   215  func compileCommons(c *tmpl.CTemplateSet, head, head2 *Header, forumList []Forum, o TItemHold) error {
   216  	// TODO: Add support for interface{}s
   217  	_, user2, user3 := tmplInitUsers()
   218  	now := time.Now()
   219  
   220  	// Convienience function to save a line here and there
   221  	htitle := func(name string) *Header {
   222  		head.Title = name
   223  		return head
   224  	}
   225  	/*htitle2 := func(name string) *Header {
   226  		head2.Title = name
   227  		return head2
   228  	}*/
   229  
   230  	var topicsList []TopicsRowMut
   231  	topic := Topic{1, "/topic/topic-title.1", "Topic Title", "The topic content.", 1, false, false, now, now, user3.ID, 1, 1, "", "::1", 1, 0, 1, 1, 1, "classname", 0, "", nil}
   232  	topicsList = append(topicsList, TopicsRowMut{&TopicsRow{topic, 1, user2, "", 0, user3, "General", "/forum/general.2"}, false})
   233  	topicListPage := TopicListPage{htitle("Topic List"), topicsList, forumList, Config.DefaultForum, TopicListSort{"lastupdated", false}, []int{1}, QuickTools{false, false, false}, Paginator{[]int{1}, 1, 1}}
   234  	o.Add("topics", "c.TopicListPage", topicListPage)
   235  	o.Add("topics_mini", "c.TopicListPage", topicListPage)
   236  
   237  	forumItem := BlankForum(1, "general-forum.1", "General Forum", "Where the general stuff happens", true, "all", 0, "", 0)
   238  	forumPage := ForumPage{htitle("General Forum"), topicsList, forumItem, false, false, Paginator{[]int{1}, 1, 1}}
   239  	o.Add("forum", "c.ForumPage", forumPage)
   240  	o.Add("forums", "c.ForumsPage", ForumsPage{htitle("Forum List"), forumList})
   241  
   242  	poll := Poll{ID: 1, Type: 0, Options: map[int]string{0: "Nothing", 1: "Something"}, Results: map[int]int{0: 5, 1: 2}, QuickOptions: []PollOption{
   243  		{0, "Nothing"},
   244  		{1, "Something"},
   245  	}, VoteCount: 7}
   246  	avatar, microAvatar := BuildAvatar(62, "")
   247  	miniAttach := []*MiniAttachment{{Path: "/"}}
   248  	tu := 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}
   249  
   250  	var replyList []*ReplyUser
   251  	reply := Reply{1, 1, "Yo!", 1 /*, Config.DefaultGroup*/, now, 0, 0, 1, "::1", true, 1, 1, ""}
   252  	ru := &ReplyUser{ClassName: "", Reply: reply, CreatedByName: "Alice", Avatar: avatar, Group: Config.DefaultGroup, Level: 0, Attachments: miniAttach}
   253  	_, err := ru.Init(user2)
   254  	if err != nil {
   255  		return err
   256  	}
   257  	replyList = append(replyList, ru)
   258  	tpage := TopicPage{htitle("Topic Name"), replyList, tu, &Forum{ID: 1, Name: "Hahaha"}, &poll, Paginator{[]int{1}, 1, 1}}
   259  	tpage.Forum.Link = BuildForumURL(NameToSlug(tpage.Forum.Name), tpage.Forum.ID)
   260  	o.Add("topic", "c.TopicPage", tpage)
   261  	o.Add("topic_mini", "c.TopicPage", tpage)
   262  	o.Add("topic_alt", "c.TopicPage", tpage)
   263  	o.Add("topic_alt_mini", "c.TopicPage", tpage)
   264  	return nil
   265  }
   266  
   267  func compileTemplates(wg *sync.WaitGroup, c *tmpl.CTemplateSet, themeName string) error {
   268  	// Schemas to train the template compiler on what to expect
   269  	// TODO: Add support for interface{}s
   270  	user, user2, user3 := tmplInitUsers()
   271  	header, header2, _ := tmplInitHeaders(user, user2, user3)
   272  	now := time.Now()
   273  
   274  	/*poll := Poll{ID: 1, Type: 0, Options: map[int]string{0: "Nothing", 1: "Something"}, Results: map[int]int{0: 5, 1: 2}, QuickOptions: []PollOption{
   275  		PollOption{0, "Nothing"},
   276  		PollOption{1, "Something"},
   277  	}, VoteCount: 7}*/
   278  	//avatar, microAvatar := BuildAvatar(62, "")
   279  	miniAttach := []*MiniAttachment{{Path: "/"}}
   280  	var replyList []*ReplyUser
   281  	//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}
   282  	// TODO: Do we want the UID on this to be 0?
   283  	//avatar, microAvatar = BuildAvatar(0, "")
   284  	reply := Reply{1, 1, "Yo!", 1 /*, Config.DefaultGroup*/, now, 0, 0, 1, "::1", true, 1, 1, ""}
   285  	ru := &ReplyUser{ClassName: "", Reply: reply, CreatedByName: "Alice", Avatar: "", Group: Config.DefaultGroup, Level: 0, Attachments: miniAttach}
   286  	_, err := ru.Init(user)
   287  	if err != nil {
   288  		return err
   289  	}
   290  	replyList = append(replyList, ru)
   291  
   292  	forum := BlankForum(1, "/forum/d.1", "d", "d desc", true, "", 0, "", 1)
   293  	forum.LastTopic = BlankTopic()
   294  	forum.LastReplyer = BlankUser()
   295  	forumList := []Forum{*forum}
   296  
   297  	// Convienience function to save a line here and there
   298  	htitle := func(name string) *Header {
   299  		header.Title = name
   300  		return header
   301  	}
   302  	t := TItemHold(make(map[string]TItem))
   303  	err = compileCommons(c, header, header2, forumList, t)
   304  	if err != nil {
   305  		return err
   306  	}
   307  
   308  	ppage := ProfilePage{htitle("User 526"), replyList, *user, 0, 0, false, false, false, false} // TODO: Use the score from user to generate the currentScore and nextScore
   309  	t.Add("profile", "c.ProfilePage", ppage)
   310  
   311  	var topicsList []TopicsRowMut
   312  	topic := Topic{1, "topic-title", "Topic Title", "The topic content.", 1, false, false, now, now, user3.ID, 1, 1, "", "::1", 1, 0, 1, 1, 1, "classname", 0, "", nil}
   313  	topicsList = append(topicsList, TopicsRowMut{&TopicsRow{topic, 0, user2, "", 0, user3, "General", "/forum/general.2"}, false})
   314  	topicListPage := TopicListPage{htitle("Topic List"), topicsList, forumList, Config.DefaultForum, TopicListSort{"lastupdated", false}, []int{1}, QuickTools{false, false, false}, Paginator{[]int{1}, 1, 1}}
   315  
   316  	forumItem := BlankForum(1, "general-forum.1", "General Forum", "Where the general stuff happens", true, "all", 0, "", 0)
   317  	forumPage := ForumPage{htitle("General Forum"), topicsList, forumItem, false, false, Paginator{[]int{1}, 1, 1}}
   318  
   319  	// Experimental!
   320  	for _, tmpl := range strings.Split(Dev.ExtraTmpls, ",") {
   321  		sp := strings.Split(tmpl, ":")
   322  		if len(sp) < 2 {
   323  			continue
   324  		}
   325  		typ := "0"
   326  		if len(sp) == 3 {
   327  			typ = sp[2]
   328  		}
   329  
   330  		var pi interface{}
   331  		switch sp[1] {
   332  		case "c.TopicListPage":
   333  			pi = topicListPage
   334  		case "c.ForumPage":
   335  			pi = forumPage
   336  		case "c.ProfilePage":
   337  			pi = ppage
   338  		case "c.Page":
   339  			pi = Page{htitle("Something"), tList, nil}
   340  		default:
   341  			continue
   342  		}
   343  
   344  		if typ == "1" {
   345  			t.Add(sp[0], sp[1], pi)
   346  		} else {
   347  			t.AddStd(sp[0], sp[1], pi)
   348  		}
   349  	}
   350  
   351  	t.AddStd("login", "c.Page", Page{htitle("Login Page"), tList, nil})
   352  	t.AddStd("register", "c.RegisterPage", RegisterPage{htitle("Registration Page"), false, "", []RegisterVerify{{true, &RegisterVerifyImageGrid{"What?", []RegisterVerifyImageGridImage{{"something.png"}}}}}})
   353  	t.AddStd("error", "c.ErrorPage", ErrorPage{htitle("Error"), "A problem has occurred in the system."})
   354  
   355  	ipSearchPage := IPSearchPage{htitle("IP Search"), map[int]*User{1: user2}, "::1"}
   356  	t.AddStd("ip_search", "c.IPSearchPage", ipSearchPage)
   357  
   358  	var inter nobreak
   359  	accountPage := Account{header, "dashboard", "account_own_edit", inter}
   360  	t.AddStd("account", "c.Account", accountPage)
   361  
   362  	parti := []*User{user}
   363  	convo := &Conversation{1, BuildConvoURL(1), user.ID, time.Now(), 0, time.Now()}
   364  	convoItems := []ConvoViewRow{{&ConversationPost{1, 1, "hey", "", user.ID}, user, "", 4, true}}
   365  	convoPage := ConvoViewPage{header, convo, convoItems, parti, true, Paginator{[]int{1}, 1, 1}}
   366  	t.AddStd("convo", "c.ConvoViewPage", convoPage)
   367  
   368  	convos := []*ConversationExtra{{&Conversation{}, []*User{user}}}
   369  	var cRows []ConvoListRow
   370  	for _, convo := range convos {
   371  		cRows = append(cRows, ConvoListRow{convo, convo.Users, false})
   372  	}
   373  	convoListPage := ConvoListPage{header, cRows, Paginator{[]int{1}, 1, 1}}
   374  	t.AddStd("convos", "c.ConvoListPage", convoListPage)
   375  
   376  	basePage := &BasePanelPage{header, PanelStats{}, "dashboard", ReportForumID, true}
   377  	t.AddStd("panel", "c.Panel", Panel{basePage, "panel_dashboard_right", "", "panel_dashboard", inter})
   378  	ges := []GridElement{{"", "", "", 1, "grid_istat", "", "", ""}}
   379  	t.AddStd("panel_dashboard", "c.DashGrids", DashGrids{ges, ges})
   380  
   381  	goVersion := runtime.Version()
   382  	dbVersion := qgen.Builder.DbVersion()
   383  	var memStats runtime.MemStats
   384  	runtime.ReadMemStats(&memStats)
   385  	debugTasks := DebugPageTasks{0, 0, 0, 0, 0, 0}
   386  	debugCache := DebugPageCache{1, 1, 1, 2, 2, 2, true}
   387  	debugDatabase := DebugPageDatabase{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
   388  	debugDisk := DebugPageDisk{1, 1, 1, 1, 1, 1}
   389  	dpage := PanelDebugPage{basePage, goVersion, dbVersion, "0s", 1, qgen.Builder.GetAdapter().GetName(), 1, 1, 1, debugTasks, memStats, debugCache, debugDatabase, debugDisk}
   390  	t.AddStd("panel_debug", "c.PanelDebugPage", dpage)
   391  	//t.AddStd("panel_analytics", "c.PanelAnalytics", Panel{basePage, "panel_dashboard_right","panel_dashboard", inter})
   392  
   393  	writeTemplate := func(name string, content interface{}) {
   394  		log.Print("Writing template '" + name + "'")
   395  		writeTmpl := func(name, content string) {
   396  			if content == "" {
   397  				return //log.Fatal("No content body for " + name)
   398  			}
   399  			e := writeFile("./tmpl_"+name+".go", content)
   400  			if e != nil {
   401  				log.Fatal(e)
   402  			}
   403  		}
   404  		wg.Add(1)
   405  		go func() {
   406  			defer EatPanics()
   407  			tname := themeName
   408  			if tname != "" {
   409  				tname = "_" + tname
   410  			}
   411  			switch content := content.(type) {
   412  			case string:
   413  				writeTmpl(name+tname, content)
   414  			case TmplLoggedin:
   415  				writeTmpl(name+tname, content.Stub)
   416  				writeTmpl(name+tname+"_guest", content.Guest)
   417  				writeTmpl(name+tname+"_member", content.Member)
   418  			}
   419  			wg.Done()
   420  		}()
   421  	}
   422  
   423  	// Let plugins register their own templates
   424  	DebugLog("Registering the templates for the plugins")
   425  	config := c.GetConfig()
   426  	config.SkipHandles = true
   427  	c.SetConfig(config)
   428  	for _, tmplfunc := range PrebuildTmplList {
   429  		tmplItem := tmplfunc(*user, header)
   430  		varList := make(map[string]tmpl.VarItem)
   431  		compiledTmpl, err := c.Compile(tmplItem.Filename, tmplItem.Path, tmplItem.StructName, tmplItem.Data, varList, tmplItem.Imports...)
   432  		if err != nil {
   433  			return err
   434  		}
   435  		writeTemplate(tmplItem.Name, compiledTmpl)
   436  	}
   437  
   438  	log.Print("Writing the templates")
   439  	for name, titem := range t {
   440  		log.Print("Writing " + name)
   441  		varList := make(map[string]tmpl.VarItem)
   442  		if titem.LoggedIn {
   443  			stub, guest, member, err := c.CompileByLoggedin(name+".html", "templates/", titem.Expects, titem.ExpectsInt, varList)
   444  			if err != nil {
   445  				return err
   446  			}
   447  			writeTemplate(name, TmplLoggedin{stub, guest, member})
   448  		} else {
   449  			tmpl, err := c.Compile(name+".html", "templates/", titem.Expects, titem.ExpectsInt, varList)
   450  			if err != nil {
   451  				return err
   452  			}
   453  			writeTemplate(name, tmpl)
   454  		}
   455  	}
   456  
   457  	return nil
   458  }
   459  
   460  // ? - Add template hooks?
   461  func CompileJSTemplates() error {
   462  	log.Print("Compiling the JS templates")
   463  	// TODO: Implement per-theme template overrides here too
   464  	overriden := make(map[string]map[string]bool)
   465  	for _, theme := range Themes {
   466  		overriden[theme.Name] = make(map[string]bool)
   467  		log.Printf("theme.OverridenTemplates: %+v\n", theme.OverridenTemplates)
   468  		for _, override := range theme.OverridenTemplates {
   469  			overriden[theme.Name][override] = true
   470  		}
   471  	}
   472  	log.Printf("overriden: %+v\n", overriden)
   473  
   474  	config := tmpl.CTemplateConfig{
   475  		Minify:         Config.MinifyTemplates,
   476  		Debug:          Dev.DebugMode,
   477  		SuperDebug:     Dev.TemplateDebug,
   478  		SkipHandles:    true,
   479  		SkipTmplPtrMap: true,
   480  		SkipInitBlock:  false,
   481  		PackageName:    "tmpl",
   482  		DockToID:       DockToID,
   483  	}
   484  	c := tmpl.NewCTemplateSet("js", "./logs/")
   485  	c.SetConfig(config)
   486  	c.SetBuildTags("!no_templategen")
   487  	c.SetOverrideTrack(overriden)
   488  	c.SetPerThemeTmpls(make(map[string]bool))
   489  
   490  	log.Print("Compiling the default templates")
   491  	var wg sync.WaitGroup
   492  	err := compileJSTemplates(&wg, c, "")
   493  	if err != nil {
   494  		return err
   495  	}
   496  	oroots := c.GetOverridenRoots()
   497  	log.Printf("oroots: %+v\n", oroots)
   498  
   499  	log.Print("Compiling the per-theme templates")
   500  	for theme, tmpls := range oroots {
   501  		c.SetThemeName(theme)
   502  		c.SetPerThemeTmpls(tmpls)
   503  		log.Print("theme: ", theme)
   504  		log.Printf("perThemeTmpls: %+v\n", tmpls)
   505  		err = compileJSTemplates(&wg, c, theme)
   506  		if err != nil {
   507  			return err
   508  		}
   509  	}
   510  	dirPrefix := "./tmpl_client/"
   511  	writeTemplateList(c, &wg, dirPrefix)
   512  	return nil
   513  }
   514  
   515  func compileJSTemplates(wg *sync.WaitGroup, c *tmpl.CTemplateSet, themeName string) error {
   516  	user, user2, user3 := tmplInitUsers()
   517  	header, _, _ := tmplInitHeaders(user, user2, user3)
   518  	now := time.Now()
   519  	varList := make(map[string]tmpl.VarItem)
   520  
   521  	c.SetBaseImportMap(map[string]string{
   522  		"io": "io",
   523  		"github.com/Azareal/Gosora/common/alerts": "github.com/Azareal/Gosora/common/alerts",
   524  	})
   525  
   526  	// TODO: Check what sort of path is sent exactly and use it here
   527  	alertItem := alerts.AlertItem{Avatar: "", ASID: 1, Path: "/", Message: "uh oh, something happened"}
   528  	alertTmpl, err := c.Compile("alert.html", "templates/", "alerts.AlertItem", alertItem, varList)
   529  	if err != nil {
   530  		return err
   531  	}
   532  
   533  	c.SetBaseImportMap(map[string]string{
   534  		"io":                               "io",
   535  		"github.com/Azareal/Gosora/common": "c github.com/Azareal/Gosora/common",
   536  	})
   537  	// TODO: Fix the import loop so we don't have to use this hack anymore
   538  	c.SetBuildTags("!no_templategen,tmplgentopic")
   539  
   540  	t := TItemHold(make(map[string]TItem))
   541  
   542  	topic := Topic{1, "topic-title", "Topic Title", "The topic content.", 1, false, false, now, now, user3.ID, 1, 1, "", "::1", 1, 0, 1, 0, 1, "classname", 1, "", nil}
   543  	topicsRow := TopicsRowMut{&TopicsRow{topic, 0, user2, "", 0, user3, "General", "/forum/general.2"}, false}
   544  	t.AddStd("topics_topic", "c.TopicsRowMut", topicsRow)
   545  
   546  	poll := Poll{ID: 1, Type: 0, Options: map[int]string{0: "Nothing", 1: "Something"}, Results: map[int]int{0: 5, 1: 2}, QuickOptions: []PollOption{
   547  		{0, "Nothing"},
   548  		{1, "Something"},
   549  	}, VoteCount: 7}
   550  	avatar, microAvatar := BuildAvatar(62, "")
   551  	miniAttach := []*MiniAttachment{{Path: "/"}}
   552  	tu := 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}
   553  	var replyList []*ReplyUser
   554  	// TODO: Do we really want the UID here to be zero?
   555  	avatar, microAvatar = BuildAvatar(0, "")
   556  	reply := Reply{1, 1, "Yo!", 1 /*, Config.DefaultGroup*/, now, 0, 0, 1, "::1", true, 1, 1, ""}
   557  	ru := &ReplyUser{ClassName: "", Reply: reply, CreatedByName: "Alice", Avatar: avatar, Group: Config.DefaultGroup, Level: 0, Attachments: miniAttach}
   558  	_, err = ru.Init(user)
   559  	if err != nil {
   560  		return err
   561  	}
   562  	replyList = append(replyList, ru)
   563  
   564  	varList = make(map[string]tmpl.VarItem)
   565  	header.Title = "Topic Name"
   566  	tpage := TopicPage{header, replyList, tu, &Forum{ID: 1, Name: "Hahaha"}, &poll, Paginator{[]int{1}, 1, 1}}
   567  	tpage.Forum.Link = BuildForumURL(NameToSlug(tpage.Forum.Name), tpage.Forum.ID)
   568  	t.AddStd("topic_posts", "c.TopicPage", tpage)
   569  	t.AddStd("topic_alt_posts", "c.TopicPage", tpage)
   570  
   571  	itemsPerPage := 25
   572  	_, page, lastPage := PageOffset(20, 1, itemsPerPage)
   573  	pageList := Paginate(page, lastPage, 5)
   574  	t.AddStd("paginator", "c.Paginator", Paginator{pageList, page, lastPage})
   575  
   576  	t.AddStd("topic_c_edit_post", "c.TopicCEditPost", TopicCEditPost{ID: 0, Source: "", Ref: ""})
   577  	t.AddStd("topic_c_attach_item", "c.TopicCAttachItem", TopicCAttachItem{ID: 1, ImgSrc: "", Path: "", FullPath: ""})
   578  	t.AddStd("topic_c_poll_input", "c.TopicCPollInput", TopicCPollInput{Index: 0})
   579  
   580  	parti := []*User{user}
   581  	convo := &Conversation{1, BuildConvoURL(1), user.ID, time.Now(), 0, time.Now()}
   582  	convoItems := []ConvoViewRow{{&ConversationPost{1, 1, "hey", "", user.ID}, user, "", 4, true}}
   583  	convoPage := ConvoViewPage{header, convo, convoItems, parti, true, Paginator{[]int{1}, 1, 1}}
   584  	t.AddStd("convo", "c.ConvoViewPage", convoPage)
   585  
   586  	t.AddStd("notice", "string", "nonono")
   587  
   588  	dirPrefix := "./tmpl_client/"
   589  	writeTemplate := func(name, content string) {
   590  		log.Print("Writing template '" + name + "'")
   591  		if content == "" {
   592  			return //log.Fatal("No content body")
   593  		}
   594  		wg.Add(1)
   595  		go func() {
   596  			defer EatPanics()
   597  			tname := themeName
   598  			if tname != "" {
   599  				tname = "_" + tname
   600  			}
   601  			e := writeFile(dirPrefix+"tmpl_"+name+tname+".jgo", content)
   602  			if e != nil {
   603  				log.Fatal(e)
   604  			}
   605  			wg.Done()
   606  		}()
   607  	}
   608  
   609  	log.Print("Writing the templates")
   610  	for name, titem := range t {
   611  		log.Print("Writing " + name)
   612  		varList := make(map[string]tmpl.VarItem)
   613  		tmpl, err := c.Compile(name+".html", "templates/", titem.Expects, titem.ExpectsInt, varList)
   614  		if err != nil {
   615  			return err
   616  		}
   617  		writeTemplate(name, tmpl)
   618  	}
   619  	writeTemplate("alert", alertTmpl)
   620  	/*//writeTemplate("forum", forumTmpl)
   621  	writeTemplate("topic_posts", topicPostsTmpl)
   622  	writeTemplate("topic_alt_posts", topicAltPostsTmpl)
   623  	writeTemplateList(c, &wg, dirPrefix)*/
   624  	return nil
   625  }
   626  
   627  var poutlen = len("\n// nolint\nfunc init() {\n")
   628  var poutlooplen = len("__frags[0]=a_0[:]\n")
   629  
   630  func getTemplateList(c *tmpl.CTemplateSet, wg *sync.WaitGroup, prefix string) string {
   631  	DebugLog("in getTemplateList")
   632  	//pout := "\n// nolint\nfunc init() {\n"
   633  	tFragCount := make(map[string]int)
   634  	bodyMap := make(map[string]string) //map[body]fragmentPrefix
   635  	//tmplMap := make(map[string]map[string]string) // map[tmpl]map[body]fragmentPrefix
   636  	tmpCount := 0
   637  	var bsb strings.Builder
   638  	var poutsb strings.Builder
   639  	poutsb.Grow(poutlen + (poutlooplen * len(c.FragOut)))
   640  	poutsb.WriteString("\n// nolint\nfunc init() {\n")
   641  	for _, frag := range c.FragOut {
   642  		front := frag.TmplName + "_frags[" + strconv.Itoa(frag.Index) + "]"
   643  		DebugLog("front: ", front)
   644  		DebugLog("frag.Body: ", frag.Body)
   645  		/*bodyMap, tok := tmplMap[frag.TmplName]
   646  		if !tok {
   647  			tmplMap[frag.TmplName] = make(map[string]string)
   648  			bodyMap = tmplMap[frag.TmplName]
   649  		}*/
   650  		fp, ok := bodyMap[frag.Body]
   651  		if !ok {
   652  			bodyMap[frag.Body] = front
   653  			//var bits string
   654  			bsb.Reset()
   655  			DebugLog("encoding f.Body")
   656  			for _, char := range []byte(frag.Body) {
   657  				if char == '\'' {
   658  					//bits += "'\\" + string(char) + "',"
   659  					bsb.WriteString("'\\'',")
   660  				} else if char < 32 {
   661  					//bits += strconv.Itoa(int(char)) + ","
   662  					bsb.WriteString(strconv.Itoa(int(char)))
   663  					bsb.WriteByte(',')
   664  				} else {
   665  					//bits += "'" + string(char) + "',"
   666  					bsb.WriteByte('\'')
   667  					bsb.WriteString(string(char))
   668  					bsb.WriteString("',")
   669  				}
   670  			}
   671  			tmpStr := strconv.Itoa(tmpCount)
   672  			//"a_" + tmpStr + ":=[...]byte{" + /*bits*/ bsb.String() + "}\n"
   673  			poutsb.WriteString("a_")
   674  			poutsb.WriteString(tmpStr)
   675  			poutsb.WriteString(":=[...]byte{")
   676  			poutsb.WriteString(bsb.String())
   677  			poutsb.WriteString("}\n")
   678  
   679  			//front + "=a_" + tmpStr + "[:]\n"
   680  			poutsb.WriteString(front)
   681  			poutsb.WriteString("=a_")
   682  			poutsb.WriteString(tmpStr)
   683  			poutsb.WriteString("[:]\n")
   684  			tmpCount++
   685  			//pout += front + "=[]byte(`" + frag.Body + "`)\n"
   686  		} else {
   687  			DebugLog("encoding cached index " + fp)
   688  			poutsb.WriteString(front + "=" + fp + "\n")
   689  		}
   690  
   691  		_, ok = tFragCount[frag.TmplName]
   692  		if !ok {
   693  			tFragCount[frag.TmplName] = 0
   694  		}
   695  		tFragCount[frag.TmplName]++
   696  	}
   697  
   698  	//out := "package " + c.GetConfig().PackageName + "\n\n"
   699  	bsb.Reset()
   700  	sb := bsb
   701  	pkgName := c.GetConfig().PackageName
   702  	sb.Grow(tllenhint + ((looplenhint + 2) + (looplenhint2+2)*len(tFragCount)) + len(pkgName))
   703  	sb.WriteString("package ")
   704  	sb.WriteString(pkgName)
   705  	sb.WriteString("\n\n")
   706  	for templateName, count := range tFragCount {
   707  		//out += "var " + templateName + "_frags = make([][]byte," + strconv.Itoa(count) + ")\n"
   708  		//out += "var " + templateName + "_frags [" + strconv.Itoa(count) + "][]byte\n"
   709  		sb.WriteString("var ")
   710  		sb.WriteString(templateName)
   711  		sb.WriteString("_frags [")
   712  		sb.WriteString(strconv.Itoa(count))
   713  		sb.WriteString("][]byte\n")
   714  	}
   715  	sb.WriteString(poutsb.String())
   716  	sb.WriteString("\n\n// nolint\nGetFrag = func(name string) [][]byte {\nswitch(name) {\n")
   717  	//getterstr := "\n// nolint\nGetFrag = func(name string) [][]byte {\nswitch(name) {\n"
   718  	for templateName, _ := range tFragCount {
   719  		//getterstr += "\tcase \"" + templateName + "\":\n"
   720  		///getterstr += "\treturn " + templateName + "_frags\n"
   721  		//getterstr += "\treturn " + templateName + "_frags[:]\n"
   722  		sb.WriteString("\tcase \"")
   723  		sb.WriteString(templateName)
   724  		sb.WriteString("\":\n\treturn ")
   725  		sb.WriteString(templateName)
   726  		sb.WriteString("_frags[:]\n")
   727  	}
   728  	sb.WriteString("}\nreturn nil\n}\n}\n")
   729  	//getterstr += "}\nreturn nil\n}\n"
   730  	//out += pout + "\n" + getterstr + "}\n"
   731  
   732  	return sb.String()
   733  }
   734  
   735  var looplenhint = len("var _frags [][]byte\n")
   736  var looplenhint2 = len("\tcase \"\":\n\treturn _frags[:]\n")
   737  var tllenhint = len("package \n\n\n// nolint\nGetFrag = func(name string) [][]byte {\nswitch(name) {\nvar _frags [][]byte\n\tcase \"\":\n\treturn _frags[:]\n}\nreturn nil\n}\n\n}\n")
   738  
   739  func writeTemplateList(c *tmpl.CTemplateSet, wg *sync.WaitGroup, prefix string) {
   740  	log.Print("Writing template list")
   741  	wg.Add(1)
   742  	go func() {
   743  		defer EatPanics()
   744  		e := writeFile(prefix+"tmpl_list.go", getTemplateList(c, wg, prefix))
   745  		if e != nil {
   746  			log.Fatal(e)
   747  		}
   748  		wg.Done()
   749  	}()
   750  	wg.Wait()
   751  }
   752  
   753  func arithToInt64(in interface{}) (o int64) {
   754  	switch in := in.(type) {
   755  	case int64:
   756  		o = in
   757  	case int32:
   758  		o = int64(in)
   759  	case int:
   760  		o = int64(in)
   761  	case uint32:
   762  		o = int64(in)
   763  	case uint16:
   764  		o = int64(in)
   765  	case uint8:
   766  		o = int64(in)
   767  	case uint:
   768  		o = int64(in)
   769  	}
   770  	return o
   771  }
   772  
   773  func arithDuoToInt64(left, right interface{}) (leftInt, rightInt int64) {
   774  	return arithToInt64(left), arithToInt64(right)
   775  }
   776  
   777  func initDefaultTmplFuncMap() {
   778  	// TODO: Add support for floats
   779  	fmap := make(map[string]interface{})
   780  	fmap["add"] = func(left, right interface{}) interface{} {
   781  		leftInt, rightInt := arithDuoToInt64(left, right)
   782  		return leftInt + rightInt
   783  	}
   784  
   785  	fmap["subtract"] = func(left, right interface{}) interface{} {
   786  		leftInt, rightInt := arithDuoToInt64(left, right)
   787  		return leftInt - rightInt
   788  	}
   789  
   790  	fmap["multiply"] = func(left, right interface{}) interface{} {
   791  		leftInt, rightInt := arithDuoToInt64(left, right)
   792  		return leftInt * rightInt
   793  	}
   794  
   795  	fmap["divide"] = func(left, right interface{}) interface{} {
   796  		leftInt, rightInt := arithDuoToInt64(left, right)
   797  		if leftInt == 0 || rightInt == 0 {
   798  			return 0
   799  		}
   800  		return leftInt / rightInt
   801  	}
   802  
   803  	fmap["dock"] = func(dock, headerInt interface{}) interface{} {
   804  		return template.HTML(BuildWidget(dock.(string), headerInt.(*Header)))
   805  	}
   806  
   807  	fmap["hasWidgets"] = func(dock, headerInt interface{}) interface{} {
   808  		return HasWidgets(dock.(string), headerInt.(*Header))
   809  	}
   810  
   811  	fmap["elapsed"] = func(startedAtInt interface{}) interface{} {
   812  		//return time.Since(startedAtInt.(time.Time)).String()
   813  		return time.Duration(uutils.Nanotime() - startedAtInt.(int64)).String()
   814  	}
   815  
   816  	fmap["lang"] = func(phraseNameInt interface{}) interface{} {
   817  		phraseName, ok := phraseNameInt.(string)
   818  		if !ok {
   819  			panic("phraseNameInt is not a string")
   820  		}
   821  		// TODO: Log non-existent phrases?
   822  		return template.HTML(p.GetTmplPhrase(phraseName))
   823  	}
   824  
   825  	// TODO: Implement this in the template generator too
   826  	fmap["langf"] = func(phraseNameInt interface{}, args ...interface{}) interface{} {
   827  		phraseName, ok := phraseNameInt.(string)
   828  		if !ok {
   829  			panic("phraseNameInt is not a string")
   830  		}
   831  		// TODO: Log non-existent phrases?
   832  		// TODO: Optimise TmplPhrasef so we don't use slow Sprintf there
   833  		return template.HTML(p.GetTmplPhrasef(phraseName, args...))
   834  	}
   835  
   836  	fmap["level"] = func(levelInt interface{}) interface{} {
   837  		level, ok := levelInt.(int)
   838  		if !ok {
   839  			panic("levelInt is not an integer")
   840  		}
   841  		return template.HTML(p.GetLevelPhrase(level))
   842  	}
   843  
   844  	fmap["bunit"] = func(byteInt interface{}) interface{} {
   845  		var byteFloat float64
   846  		var unit string
   847  		switch bytes := byteInt.(type) {
   848  		case int:
   849  			byteFloat, unit = ConvertByteUnit(float64(bytes))
   850  		case int64:
   851  			byteFloat, unit = ConvertByteUnit(float64(bytes))
   852  		case uint64:
   853  			byteFloat, unit = ConvertByteUnit(float64(bytes))
   854  		case float64:
   855  			byteFloat, unit = ConvertByteUnit(bytes)
   856  		default:
   857  			panic("bytes is not an int, int64 or uint64")
   858  		}
   859  		return fmt.Sprintf("%.1f", byteFloat) + unit
   860  	}
   861  
   862  	fmap["abstime"] = func(timeInt interface{}) interface{} {
   863  		time, ok := timeInt.(time.Time)
   864  		if !ok {
   865  			panic("timeInt is not a time.Time")
   866  		}
   867  		return time.Format("2006-01-02 15:04:05")
   868  	}
   869  
   870  	fmap["reltime"] = func(timeInt interface{}) interface{} {
   871  		time, ok := timeInt.(time.Time)
   872  		if !ok {
   873  			panic("timeInt is not a time.Time")
   874  		}
   875  		return RelativeTime(time)
   876  	}
   877  
   878  	fmap["scope"] = func(name interface{}) interface{} {
   879  		return ""
   880  	}
   881  
   882  	fmap["dyntmpl"] = func(nameInt, pageInt, headerInt interface{}) interface{} {
   883  		header := headerInt.(*Header)
   884  		err := header.Theme.RunTmpl(nameInt.(string), pageInt, header.Writer)
   885  		if err != nil {
   886  			return err
   887  		}
   888  		return ""
   889  	}
   890  
   891  	fmap["ptmpl"] = func(nameInt, pageInt, headerInt interface{}) interface{} {
   892  		header := headerInt.(*Header)
   893  		err := header.Theme.RunTmpl(nameInt.(string), pageInt, header.Writer)
   894  		if err != nil {
   895  			return err
   896  		}
   897  		return ""
   898  	}
   899  
   900  	fmap["js"] = func() interface{} {
   901  		return false
   902  	}
   903  
   904  	fmap["flush"] = func() interface{} {
   905  		return nil
   906  	}
   907  
   908  	fmap["res"] = func(nameInt interface{}) interface{} {
   909  		n := nameInt.(string)
   910  		if n[0] == '/' && n[1] == '/' {
   911  		} else {
   912  			if f, ok := StaticFiles.GetShort(n); ok {
   913  				n = f.OName
   914  			}
   915  		}
   916  		return n
   917  	}
   918  
   919  	DefaultTemplateFuncMap = fmap
   920  }
   921  
   922  func loadTemplates(t *template.Template, themeName string) error {
   923  	t.Funcs(DefaultTemplateFuncMap)
   924  	tFiles, err := filepath.Glob("templates/*.html")
   925  	if err != nil {
   926  		return err
   927  	}
   928  
   929  	tFileMap := make(map[string]int)
   930  	for index, path := range tFiles {
   931  		path = strings.Replace(path, "\\", "/", -1)
   932  		log.Print("templateFile: ", path)
   933  		if skipCTmpl(path) {
   934  			log.Print("skipping")
   935  			continue
   936  		}
   937  		tFileMap[path] = index
   938  	}
   939  
   940  	overrideFiles, err := filepath.Glob("templates/overrides/*.html")
   941  	if err != nil {
   942  		return err
   943  	}
   944  	for _, path := range overrideFiles {
   945  		path = strings.Replace(path, "\\", "/", -1)
   946  		log.Print("overrideFile: ", path)
   947  		if skipCTmpl(path) {
   948  			log.Print("skipping")
   949  			continue
   950  		}
   951  		index, ok := tFileMap["templates/"+strings.TrimPrefix(path, "templates/overrides/")]
   952  		if !ok {
   953  			log.Print("not ok: templates/" + strings.TrimPrefix(path, "templates/overrides/"))
   954  			tFiles = append(tFiles, path)
   955  			continue
   956  		}
   957  		tFiles[index] = path
   958  	}
   959  
   960  	if themeName != "" {
   961  		overrideFiles, err := filepath.Glob("./themes/" + themeName + "/overrides/*.html")
   962  		if err != nil {
   963  			return err
   964  		}
   965  		for _, path := range overrideFiles {
   966  			path = strings.Replace(path, "\\", "/", -1)
   967  			log.Print("overrideFile: ", path)
   968  			if skipCTmpl(path) {
   969  				log.Print("skipping")
   970  				continue
   971  			}
   972  			index, ok := tFileMap["templates/"+strings.TrimPrefix(path, "themes/"+themeName+"/overrides/")]
   973  			if !ok {
   974  				log.Print("not ok: templates/" + strings.TrimPrefix(path, "themes/"+themeName+"/overrides/"))
   975  				tFiles = append(tFiles, path)
   976  				continue
   977  			}
   978  			tFiles[index] = path
   979  		}
   980  	}
   981  
   982  	// TODO: Minify these
   983  	/*err = t.ParseFiles(tFiles...)
   984  	if err != nil {
   985  		return err
   986  	}*/
   987  	for _, fname := range tFiles {
   988  		b, err := ioutil.ReadFile(fname)
   989  		if err != nil {
   990  			return err
   991  		}
   992  		s := tmpl.Minify(string(b))
   993  		name := filepath.Base(fname)
   994  		var tmpl *template.Template
   995  		if name == t.Name() {
   996  			tmpl = t
   997  		} else {
   998  			tmpl = t.New(name)
   999  		}
  1000  		_, err = tmpl.Parse(s)
  1001  		if err != nil {
  1002  			return err
  1003  		}
  1004  	}
  1005  	_, err = t.ParseGlob("pages/*")
  1006  	return err
  1007  }
  1008  
  1009  func InitTemplates() error {
  1010  	DebugLog("Initialising the template system")
  1011  	initDefaultTmplFuncMap()
  1012  
  1013  	// The interpreted templates...
  1014  	DebugLog("Loading the template files...")
  1015  	return loadTemplates(DefaultTemplates, "")
  1016  }