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 }