diff --git a/build-linux b/build-linux index 974ea96d..4a75802d 100644 --- a/build-linux +++ b/build-linux @@ -14,6 +14,9 @@ go build -o QueryGen "./cmd/query_gen" echo "Running the query generator" ./QueryGen +echo "Generating the JSON handlers" +easyjson -pkg common + echo "Building Gosora" go generate go build -o Gosora diff --git a/build-linux-nowebsockets b/build-linux-nowebsockets index 07016fba..41432be8 100644 --- a/build-linux-nowebsockets +++ b/build-linux-nowebsockets @@ -20,6 +20,9 @@ cd ../.. echo "Running the query generator" ./QueryGen +echo "Generating the JSON handlers" +easyjson -pkg common + echo "Building Gosora" go generate go build -o Gosora -tags no_ws diff --git a/build-nowebsockets.bat b/build-nowebsockets.bat index b18b8c80..33e00c48 100644 --- a/build-nowebsockets.bat +++ b/build-nowebsockets.bat @@ -12,6 +12,9 @@ if %errorlevel% neq 0 ( exit /b %errorlevel% ) +echo Generating the JSON handlers +easyjson -pkg common + echo Building the executable go build -o gosora.exe -tags no_ws if %errorlevel% neq 0 ( diff --git a/build.bat b/build.bat index 808a450d..a85adc49 100644 --- a/build.bat +++ b/build.bat @@ -14,6 +14,9 @@ if %errorlevel% neq 0 ( exit /b %errorlevel% ) +echo Generating the JSON handlers +easyjson -pkg common + echo Building the executable go build -o gosora.exe if %errorlevel% neq 0 ( diff --git a/common/errors.go b/common/errors.go index 90dda12a..1f59b2de 100644 --- a/common/errors.go +++ b/common/errors.go @@ -118,10 +118,10 @@ func LogWarning(err error, extra ...string) { } else { errmsg += err.Error() } - stack := debug.Stack() - log.Print(errmsg+"\n", string(stack)) errorBufferMutex.Lock() defer errorBufferMutex.Unlock() + stack := debug.Stack() // debug.Stack() can't be executed concurrently, so we'll guard this with a mutex too + log.Print(errmsg+"\n", string(stack)) errorBuffer = append(errorBuffer, ErrorItem{err, stack}) } @@ -304,7 +304,7 @@ func SecurityError(w http.ResponseWriter, r *http.Request, user User) RouteError if RunPreRenderHook("pre_render_security_error", w, r, &user, &pi) { return nil } - err := Templates.ExecuteTemplate(w, "error.html", pi) + err := pi.Header.Theme.RunTmpl("error", pi, w) if err != nil { LogError(err) } diff --git a/common/extend.go b/common/extend.go index 30bcf16d..cb650d62 100644 --- a/common/extend.go +++ b/common/extend.go @@ -237,10 +237,11 @@ var PreRenderHooks = map[string][]func(http.ResponseWriter, *http.Request, *User "pre_render_ban": nil, "pre_render_ip_search": nil, - "pre_render_panel_dashboard": nil, - "pre_render_panel_forums": nil, - "pre_render_panel_delete_forum": nil, - "pre_render_panel_edit_forum": nil, + "pre_render_panel_dashboard": nil, + "pre_render_panel_forums": nil, + "pre_render_panel_delete_forum": nil, + "pre_render_panel_forum_edit": nil, + "pre_render_panel_forum_edit_perms": nil, "pre_render_panel_analytics_views": nil, "pre_render_panel_analytics_routes": nil, @@ -258,7 +259,7 @@ var PreRenderHooks = map[string][]func(http.ResponseWriter, *http.Request, *User "pre_render_panel_word_filters_edit": nil, "pre_render_panel_plugins": nil, "pre_render_panel_users": nil, - "pre_render_panel_edit_user": nil, + "pre_render_panel_user_edit": nil, "pre_render_panel_groups": nil, "pre_render_panel_group_edit": nil, "pre_render_panel_group_edit_perms": nil, @@ -284,11 +285,11 @@ type Plugin struct { Installable bool Installed bool - Init func() error - Activate func() error - Deactivate func() // TODO: We might want to let this return an error? - Install func() error - Uninstall func() error // TODO: I'm not sure uninstall is implemented + Init func(plugin *Plugin) error + Activate func(plugin *Plugin) error + Deactivate func(plugin *Plugin) // TODO: We might want to let this return an error? + Install func(plugin *Plugin) error + Uninstall func(plugin *Plugin) error // TODO: I'm not sure uninstall is implemented Hooks map[string]int Data interface{} // Usually used for hosting the VMs / reusable elements of non-native plugins @@ -511,8 +512,8 @@ func InitPlugins() { log.Printf("Added plugin '%s'", name) if body.Active { log.Printf("Initialised plugin '%s'", name) - if Plugins[name].Init != nil { - err := Plugins[name].Init() + if body.Init != nil { + err := body.Init(body) if err != nil { log.Print(err) } diff --git a/common/files.go b/common/files.go index e10712fc..ac18a260 100644 --- a/common/files.go +++ b/common/files.go @@ -40,19 +40,20 @@ type CSSData struct { func (list SFileList) JSTmplInit() error { DebugLog("Initialising the client side templates") var fragMap = make(map[string][][]byte) - fragMap["alert"] = tmpl.GetFrag("alert") - fragMap["topics_topic"] = tmpl.GetFrag("topics_topic") - fragMap["topic_posts"] = tmpl.GetFrag("topic_posts") - fragMap["topic_alt_posts"] = tmpl.GetFrag("topic_alt_posts") + var parseFrags = func(name string) { + fragMap[name] = tmpl.GetFrag(name) + } + parseFrags("alert") + parseFrags("forum") + parseFrags("topics_topic") + parseFrags("topic_posts") + parseFrags("topic_alt_posts") + parseFrags("paginator") DebugLog("fragMap: ", fragMap) return filepath.Walk("./tmpl_client", func(path string, f os.FileInfo, err error) error { - if f.IsDir() { + if f.IsDir() || strings.HasSuffix(path, "template_list.go") || strings.HasSuffix(path, "stub.go") { return nil } - if strings.HasSuffix(path, "template_list.go") || strings.HasSuffix(path, "stub.go") { - return nil - } - path = strings.Replace(path, "\\", "/", -1) DebugLog("Processing client template " + path) data, err := ioutil.ReadFile(path) @@ -110,6 +111,10 @@ func (list SFileList) JSTmplInit() error { } return out + "]" }*/ + data = replace(data, `) + if !ok { + return errors.New("invalid page struct value") + }`, "*/tmpl_"+shortName+"_vars = tmpl_"+shortName+"_i") // ? Can we just use a regex? I'm thinking of going more efficient, or just outright rolling wasm, this is a temp hack in a place where performance doesn't particularly matter var each = func(phrase string, handle func(index int)) { @@ -156,7 +161,7 @@ func (list SFileList) JSTmplInit() error { each("RelativeTime(", func(index int) { braceAt, _ := skipUntilIfExistsOrLine(data, index, 10) if data[braceAt-1] == ' ' { - data[braceAt-1] = ')' // Blank it + data[braceAt-1] = ' ' // Blank it } }) each("if ", func(index int) { @@ -191,8 +196,10 @@ func (list SFileList) JSTmplInit() error { data = replace(data, shortName+"_tmpl_phrase_id = RegisterTmplPhraseNames([]string{", "[") data = replace(data, "var plist = GetTmplPhrasesBytes("+shortName+"_tmpl_phrase_id)", "let plist = tmplPhrases[\""+tmplName+"\"];") data = replace(data, "var cached_var_", "let cached_var_") + data = replace(data, `tmpl_`+shortName+`_vars, ok := tmpl_`+shortName+`_i.`, `/*`) data = replace(data, "[]byte(", "") data = replace(data, "StringToBytes(", "") + data = replace(data, "RelativeTime(tmpl_"+shortName+"_vars.", "tmpl_"+shortName+"_vars.Relative") // TODO: Format dates properly on the client side data = replace(data, ".Format(\"2006-01-02 15:04:05\"", "") data = replace(data, ", 10", "") @@ -252,7 +259,6 @@ func (list SFileList) Init() error { if err != nil { return err } - path = strings.TrimPrefix(path, "public/") var ext = filepath.Ext("/public/" + path) mimetype := mime.TypeByExtension(ext) diff --git a/common/module_ottojs.go b/common/module_ottojs.go index bd0e4718..a7614435 100644 --- a/common/module_ottojs.go +++ b/common/module_ottojs.go @@ -45,7 +45,7 @@ func (js *OttoPluginLang) AddPlugin(meta PluginMeta) (plugin *Plugin, err error) return nil, err } - var pluginInit = func() error { + var pluginInit = func(plugin *Plugin) error { retValue, err := js.vm.Run(script) if err != nil { return err diff --git a/common/parser.go b/common/parser.go index 436b173a..527de1f0 100644 --- a/common/parser.go +++ b/common/parser.go @@ -849,6 +849,7 @@ func CoerceIntString(data string) (res int, length int) { } // TODO: Write tests for this +// Make sure we reflect changes to this in the JS port in /public/global.js func Paginate(count int, perPage int, maxPages int) []int { if count < perPage { return []int{1} @@ -866,6 +867,7 @@ func Paginate(count int, perPage int, maxPages int) []int { } // TODO: Write tests for this +// Make sure we reflect changes to this in the JS port in /public/global.js func PageOffset(count int, page int, perPage int) (int, int, int) { var offset int lastPage := LastPage(count, perPage) @@ -886,6 +888,7 @@ func PageOffset(count int, page int, perPage int) (int, int, int) { } // TODO: Write tests for this +// Make sure we reflect changes to this in the JS port in /public/global.js func LastPage(count int, perPage int) int { return (count / perPage) + 1 } diff --git a/common/phrases/phrases.go b/common/phrases/phrases.go index aaa2e79b..9b8a78c1 100644 --- a/common/phrases/phrases.go +++ b/common/phrases/phrases.go @@ -1,7 +1,7 @@ /* * * Gosora Phrase System -* Copyright Azareal 2017 - 2019 +* Copyright Azareal 2017 - 2020 * */ package phrases diff --git a/common/search.go b/common/search.go new file mode 100644 index 00000000..6277a746 --- /dev/null +++ b/common/search.go @@ -0,0 +1,72 @@ +package common + +import ( + "database/sql" + "errors" + + "github.com/Azareal/Gosora/query_gen" +) + +//var RepliesSearch Searcher + +type Searcher interface { + Query(q string) ([]int, error) +} + +type ZoneSearcher interface { + QueryZone(q string, zoneID int) ([]int, error) +} + +// TODO: Implement this +// Note: This is slow compared to something like ElasticSearch and very limited +type SQLSearcher struct { + queryReplies *sql.Stmt + queryTopics *sql.Stmt + queryZoneReplies *sql.Stmt + queryZoneTopics *sql.Stmt +} + +// TODO: Support things other than MySQL +func NewSQLSearcher(acc *qgen.Accumulator) (*SQLSearcher, error) { + if acc.GetAdapter().GetName() != "mysql" { + return nil, errors.New("SQLSearcher only supports MySQL at this time") + } + return &SQLSearcher{ + queryReplies: acc.RawPrepare("SELECT `rid` FROM `replies` WHERE MATCH(content) AGAINST (? IN NATURAL LANGUAGE MODE);"), + queryTopics: acc.RawPrepare("SELECT `tid` FROM `topics` WHERE MATCH(title,content) AGAINST (? IN NATURAL LANGUAGE MODE);"), + queryZoneReplies: acc.RawPrepare("SELECT `rid` FROM `replies` WHERE MATCH(content) AGAINST (? IN NATURAL LANGUAGE MODE) AND `parentID` = ?;"), + queryZoneTopics: acc.RawPrepare("SELECT `tid` FROM `topics` WHERE MATCH(title,content) AGAINST (? IN NATURAL LANGUAGE MODE) AND `parentID` = ?;"), + }, acc.FirstError() +} + +func (searcher *SQLSearcher) Query(q string) ([]int, error) { + return nil, nil + + /* + rows, err := stmt.Query(q) + if err != nil { + return nil, err + } + defer rows.Close() + */ +} + +func (searcher *SQLSearcher) QueryZone(q string, zoneID int) ([]int, error) { + return nil, nil +} + +// TODO: Implement this +type ElasticSearchSearcher struct { +} + +func NewElasticSearchSearcher() *ElasticSearchSearcher { + return &ElasticSearchSearcher{} +} + +func (searcher *ElasticSearchSearcher) Query(q string) ([]int, error) { + return nil, nil +} + +func (searcher *ElasticSearchSearcher) QueryZone(q string, zoneID int) ([]int, error) { + return nil, nil +} diff --git a/common/template_init.go b/common/template_init.go index cb9b76e3..45d19437 100644 --- a/common/template_init.go +++ b/common/template_init.go @@ -16,7 +16,10 @@ import ( ) var Ctemplates []string // TODO: Use this to filter out top level templates we don't need -var Templates = template.New("") +var DefaultTemplates = template.New("") +var DefaultTemplateFuncMap map[string]interface{} + +//var Templates = template.New("") var PrebuildTmplList []func(User, *Header) CTmpl func skipCTmpl(key string) bool { @@ -37,120 +40,52 @@ type CTmpl struct { Imports []string } +func genIntTmpl(name string) func(pi interface{}, w io.Writer) error { + return func(pi interface{}, w io.Writer) error { + mapping, ok := Themes[DefaultThemeBox.Load().(string)].TemplatesMap[name] + if !ok { + mapping = name + } + return DefaultTemplates.ExecuteTemplate(w, mapping+".html", pi) + } +} + // TODO: Refactor the template trees to not need these -// TODO: Stop duplicating these bits of code // nolint -func interpretedTopicTemplate(pi TopicPage, w io.Writer) error { - mapping, ok := Themes[DefaultThemeBox.Load().(string)].TemplatesMap["topic"] - if !ok { - mapping = "topic" - } - return Templates.ExecuteTemplate(w, mapping+".html", pi) -} +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_topic_handle = interpretedTopicTemplate -var Template_topic_alt_handle = interpretedTopicTemplate -var Template_topic_alt_guest_handle = interpretedTopicTemplate -var Template_topic_alt_member_handle = interpretedTopicTemplate - -// nolint -var Template_topics_handle = func(pi TopicListPage, w io.Writer) error { - mapping, ok := Themes[DefaultThemeBox.Load().(string)].TemplatesMap["topics"] - if !ok { - mapping = "topics" - } - return Templates.ExecuteTemplate(w, mapping+".html", pi) -} +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 = func(pi ForumPage, w io.Writer) error { - mapping, ok := Themes[DefaultThemeBox.Load().(string)].TemplatesMap["forum"] - if !ok { - mapping = "forum" - } - return Templates.ExecuteTemplate(w, mapping+".html", pi) -} +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 = func(pi ForumsPage, w io.Writer) error { - mapping, ok := Themes[DefaultThemeBox.Load().(string)].TemplatesMap["forums"] - if !ok { - mapping = "forums" - } - return Templates.ExecuteTemplate(w, mapping+".html", pi) -} +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 = func(pi ProfilePage, w io.Writer) error { - mapping, ok := Themes[DefaultThemeBox.Load().(string)].TemplatesMap["profile"] - if !ok { - mapping = "profile" - } - return Templates.ExecuteTemplate(w, mapping+".html", pi) -} +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 = func(pi CreateTopicPage, w io.Writer) error { - mapping, ok := Themes[DefaultThemeBox.Load().(string)].TemplatesMap["create_topic"] - if !ok { - mapping = "create_topic" - } - return Templates.ExecuteTemplate(w, mapping+".html", pi) -} - -// nolint -var Template_login_handle = func(pi Page, w io.Writer) error { - mapping, ok := Themes[DefaultThemeBox.Load().(string)].TemplatesMap["login"] - if !ok { - mapping = "login" - } - return Templates.ExecuteTemplate(w, mapping+".html", pi) -} - -// nolint -var Template_register_handle = func(pi Page, w io.Writer) error { - mapping, ok := Themes[DefaultThemeBox.Load().(string)].TemplatesMap["register"] - if !ok { - mapping = "register" - } - return Templates.ExecuteTemplate(w, mapping+".html", pi) -} - -// nolint -var Template_error_handle = func(pi ErrorPage, w io.Writer) error { - mapping, ok := Themes[DefaultThemeBox.Load().(string)].TemplatesMap["error"] - if !ok { - mapping = "error" - } - return Templates.ExecuteTemplate(w, mapping+".html", pi) -} - -// nolint -var Template_ip_search_handle = func(pi IPSearchPage, w io.Writer) error { - mapping, ok := Themes[DefaultThemeBox.Load().(string)].TemplatesMap["ip_search"] - if !ok { - mapping = "ip_search" - } - return Templates.ExecuteTemplate(w, mapping+".html", pi) -} - -// nolint -var Template_account_handle = func(pi Account, w io.Writer) error { - mapping, ok := Themes[DefaultThemeBox.Load().(string)].TemplatesMap["account"] - if !ok { - mapping = "account" - } - return Templates.ExecuteTemplate(w, mapping+".html", pi) -} +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, "") @@ -198,8 +133,36 @@ type TmplLoggedin struct { type nobreak interface{} +type TItem struct { + Expects string + ExpectsInt interface{} + LoggedIn bool +} + +type TItemHold map[string]TItem + +func (hold TItemHold) Add(name string, expects string, expectsInt interface{}) { + hold[name] = TItem{expects, expectsInt, true} +} + +func (hold TItemHold) AddStd(name string, expects string, expectsInt interface{}) { + hold[name] = TItem{expects, expectsInt, false} +} + // ? - Add template hooks? func CompileTemplates() error { + log.Print("Compiling the templates") + // TODO: Implement per-theme template overrides here too + var overriden = make(map[string]map[string]bool) + for _, theme := range Themes { + overriden[theme.Name] = make(map[string]bool) + log.Printf("theme.OverridenTemplates: %+v\n", theme.OverridenTemplates) + for _, override := range theme.OverridenTemplates { + overriden[theme.Name][override] = true + } + } + log.Printf("overriden: %+v\n", overriden) + var config tmpl.CTemplateConfig config.Minify = Config.MinifyTemplates config.Debug = Dev.DebugMode @@ -212,61 +175,48 @@ func CompileTemplates() error { "github.com/Azareal/Gosora/common": "github.com/Azareal/Gosora/common", }) c.SetBuildTags("!no_templategen") + c.SetOverrideTrack(overriden) + c.SetPerThemeTmpls(make(map[string]bool)) - // Schemas to train the template compiler on what to expect + log.Print("Compiling the default templates") + var wg sync.WaitGroup + err := compileTemplates(&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 = compileTemplates(&wg, c, theme) + if err != nil { + return err + } + } + writeTemplateList(c, &wg, "./") + return nil +} + +func compileCommons(c *tmpl.CTemplateSet, header *Header, header2 *Header, out TItemHold) error { // TODO: Add support for interface{}s - user, user2, user3 := tmplInitUsers() - header, header2, _ := tmplInitHeaders(user, user2, user3) + _, user2, user3 := tmplInitUsers() now := time.Now() - log.Print("Compiling the templates") - - 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} - var replyList []ReplyUser - // TODO: Do we want the UID on this to be 0? - avatar, microAvatar = BuildAvatar(0, "") - replyList = append(replyList, ReplyUser{0, 0, "Yo!", "Yo!", 0, "alice", "Alice", Config.DefaultGroup, now, 0, 0, avatar, microAvatar, "", 0, "", "", "", "", 0, "127.0.0.1", false, 1, 1, "", "", miniAttach}) - - var varList = make(map[string]tmpl.VarItem) - var compile = func(name string, expects string, expectsInt interface{}) (tmpl string, err error) { - return c.Compile(name+".html", "templates/", expects, expectsInt, varList) + // Convienience function to save a line here and there + var htitle = func(name string) *Header { + header.Title = name + return header } - var compileByLoggedin = func(name string, expects string, expectsInt interface{}) (tmpl TmplLoggedin, err error) { - stub, guest, member, err := c.CompileByLoggedin(name+".html", "templates/", expects, expectsInt, varList) - return TmplLoggedin{stub, guest, member}, err - } - - 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) - topicTmpl, err := compile("topic", "common.TopicPage", tpage) - if err != nil { - return err - } - /*topicAltTmpl, err := compile("topic_alt", "common.TopicPage", tpage) - if err != nil { - return err + /*var htitle2 = func(name string) *Header { + header2.Title = name + return header2 }*/ - topicAltTmpl, err := compileByLoggedin("topic_alt", "common.TopicPage", tpage) - if err != nil { - return err - } - - varList = make(map[string]tmpl.VarItem) - header.Title = "User 526" - ppage := ProfilePage{header, replyList, user, 0, 0} // TODO: Use the score from user to generate the currentScore and nextScore - profileTmpl, err := compileByLoggedin("profile", "common.ProfilePage", ppage) - if err != nil { - return err - } - // TODO: Use a dummy forum list to avoid o(n) problems var forumList []Forum forums, err := Forums.GetAll() @@ -277,95 +227,100 @@ func CompileTemplates() error { forumList = append(forumList, *forum) } - varList = make(map[string]tmpl.VarItem) - header.Title = "Forum List" - forumsPage := ForumsPage{header, forumList} - forumsTmpl, err := compileByLoggedin("forums", "common.ForumsPage", forumsPage) - if err != nil { - return err - } - 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", "", &user2, "", 0, &user3, "General", "/forum/general.2"}) - header2.Title = "Topic List" - topicListPage := TopicListPage{header, topicsList, forumList, Config.DefaultForum, TopicListSort{"lastupdated", false}, Paginator{[]int{1}, 1, 1}} - /*topicListTmpl, err := compile("topics", "common.TopicListPage", topicListPage) - if err != nil { - return err - }*/ - topicListTmpl, err := compileByLoggedin("topics", "common.TopicListPage", topicListPage) - if err != nil { - return err - } + topicListPage := TopicListPage{htitle("Topic List"), topicsList, forumList, Config.DefaultForum, TopicListSort{"lastupdated", false}, Paginator{[]int{1}, 1, 1}} + out.Add("topics", "common.TopicListPage", topicListPage) forumItem := BlankForum(1, "general-forum.1", "General Forum", "Where the general stuff happens", true, "all", 0, "", 0) - header.Title = "General Forum" - forumPage := ForumPage{header, topicsList, forumItem, Paginator{[]int{1}, 1, 1}} - forumTmpl, err := compileByLoggedin("forum", "common.ForumPage", forumPage) + forumPage := ForumPage{htitle("General Forum"), topicsList, forumItem, Paginator{[]int{1}, 1, 1}} + out.Add("forum", "common.ForumPage", forumPage) + out.Add("forums", "common.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} + var replyList []ReplyUser + replyList = append(replyList, ReplyUser{0, 0, "Yo!", "Yo!", 0, "alice", "Alice", Config.DefaultGroup, now, 0, 0, avatar, microAvatar, "", 0, "", "", "", "", 0, "127.0.0.1", false, 1, 1, "", "", miniAttach}) + 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) + out.Add("topic", "common.TopicPage", tpage) + out.Add("topic_alt", "common.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} + // TODO: Do we want the UID on this to be 0? + avatar, microAvatar = BuildAvatar(0, "") + replyList = append(replyList, ReplyUser{0, 0, "Yo!", "Yo!", 0, "alice", "Alice", Config.DefaultGroup, now, 0, 0, avatar, microAvatar, "", 0, "", "", "", "", 0, "127.0.0.1", false, 1, 1, "", "", miniAttach}) + + // Convienience function to save a line here and there + var htitle = func(name string) *Header { + header.Title = name + return header + } + tmpls := TItemHold(make(map[string]TItem)) + err := compileCommons(c, header, header2, tmpls) if err != nil { return err } - header.Title = "Login Page" - loginPage := Page{header, tList, nil} - loginTmpl, err := compile("login", "common.Page", loginPage) - if err != nil { - return err - } + ppage := ProfilePage{htitle("User 526"), replyList, user, 0, 0} // TODO: Use the score from user to generate the currentScore and nextScore + tmpls.Add("profile", "common.ProfilePage", ppage) - header.Title = "Registration Page" - registerPage := Page{header, tList, "nananana"} - registerTmpl, err := compile("register", "common.Page", registerPage) - if err != nil { - return err - } + tmpls.AddStd("login", "common.Page", Page{htitle("Login Page"), tList, nil}) + tmpls.AddStd("register", "common.Page", Page{htitle("Registration Page"), tList, "nananana"}) + tmpls.AddStd("error", "common.ErrorPage", ErrorPage{htitle("Error"), "A problem has occurred in the system."}) - header.Title = "Error" - errorPage := ErrorPage{header, "A problem has occurred in the system."} - errorTmpl, err := compile("error", "common.ErrorPage", errorPage) - if err != nil { - return err - } - - var ipUserList = make(map[int]*User) - ipUserList[1] = &user2 - header.Title = "IP Search" - ipSearchPage := IPSearchPage{header2, ipUserList, "::1"} - ipSearchTmpl, err := compile("ip_search", "common.IPSearchPage", ipSearchPage) - if err != nil { - return err - } + ipSearchPage := IPSearchPage{htitle("IP Search"), map[int]*User{1: &user2}, "::1"} + tmpls.AddStd("ip_search", "common.IPSearchPage", ipSearchPage) var inter nobreak accountPage := Account{header, "dashboard", "account_own_edit", inter} - accountTmpl, err := compile("account", "common.Account", accountPage) - if err != nil { - return err - } + tmpls.AddStd("account", "common.Account", accountPage) - var wg sync.WaitGroup var writeTemplate = func(name string, content interface{}) { log.Print("Writing template '" + name + "'") - var writeTmpl = func(name string, content string) { if content == "" { - log.Fatal("No content body for " + name) + return //log.Fatal("No content body for " + name) } err := writeFile("./template_"+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, content) + writeTmpl(name+tname, content) case TmplLoggedin: - writeTmpl(name, content.Stub) - writeTmpl(name+"_guest", content.Guest) - writeTmpl(name+"_member", content.Member) + writeTmpl(name+tname, content.Stub) + writeTmpl(name+tname+"_guest", content.Guest) + writeTmpl(name+tname+"_member", content.Member) } wg.Done() }() @@ -373,12 +328,12 @@ func CompileTemplates() error { // Let plugins register their own templates DebugLog("Registering the templates for the plugins") - config = c.GetConfig() + config := c.GetConfig() config.SkipHandles = true c.SetConfig(config) for _, tmplfunc := range PrebuildTmplList { tmplItem := tmplfunc(user, header) - varList = make(map[string]tmpl.VarItem) + 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 @@ -387,18 +342,30 @@ func CompileTemplates() error { } log.Print("Writing the templates") - writeTemplate("topic", topicTmpl) - writeTemplate("topic_alt", topicAltTmpl) - writeTemplate("profile", profileTmpl) + for name, titem := range tmpls { + 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) + } + } + /*writeTemplate("profile", profileTmpl) writeTemplate("forums", forumsTmpl) - writeTemplate("topics", topicListTmpl) - writeTemplate("forum", forumTmpl) writeTemplate("login", loginTmpl) writeTemplate("register", registerTmpl) writeTemplate("ip_search", ipSearchTmpl) writeTemplate("account", accountTmpl) - writeTemplate("error", errorTmpl) - writeTemplateList(c, &wg, "./") + writeTemplate("error", errorTmpl)*/ return nil } @@ -470,6 +437,15 @@ func CompileJSTemplates() error { if err != nil { return err } + + itemsPerPage := 25 + _, page, lastPage := PageOffset(20, 1, itemsPerPage) + pageList := Paginate(20, itemsPerPage, 5) + paginatorTmpl, err := c.Compile("paginator.html", "templates/", "common.Paginator", Paginator{pageList, page, lastPage}, varList) + if err != nil { + return err + } + /*widget := &Widget{ID: 0} panelWidgetsWidgetTmpl, err := c.Compile("panel_themes_widgets_widget.html", "templates/", "*common.Widget", widget, varList) if err != nil { @@ -481,9 +457,8 @@ func CompileJSTemplates() error { var writeTemplate = func(name string, content string) { log.Print("Writing template '" + name + "'") if content == "" { - log.Fatal("No content body") + return //log.Fatal("No content body") } - wg.Add(1) go func() { err := writeFile(dirPrefix+"template_"+name+".go", content) @@ -494,59 +469,74 @@ func CompileJSTemplates() error { }() } writeTemplate("alert", alertTmpl) + //writeTemplate("forum", forumTmpl) writeTemplate("topics_topic", topicListItemTmpl) writeTemplate("topic_posts", topicPostsTmpl) writeTemplate("topic_alt_posts", topicAltPostsTmpl) + writeTemplate("paginator", paginatorTmpl) //writeTemplate("panel_themes_widgets_widget", panelWidgetsWidgetTmpl) writeTemplateList(c, &wg, dirPrefix) return nil } +func getTemplateList(c *tmpl.CTemplateSet, wg *sync.WaitGroup, prefix string) string { + pout := "\n// nolint\nfunc init() {\n" + var tFragCount = make(map[string]int) + var bodyMap = make(map[string]string) //map[body]fragmentPrefix + //var tmplMap = make(map[string]map[string]string) // map[tmpl]map[body]fragmentPrefix + var tmpCount = 0 + for _, frag := range c.FragOut { + front := frag.TmplName + "_frags[" + strconv.Itoa(frag.Index) + "]" + /*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 + for _, char := range []byte(frag.Body) { + if char == '\'' { + bits += "'\\" + string(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 { + pout += front + " = " + fp + "\n" + } + + _, ok = tFragCount[frag.TmplName] + if !ok { + tFragCount[frag.TmplName] = 0 + } + tFragCount[frag.TmplName]++ + } + + out := "package " + c.GetConfig().PackageName + "\n\n" + var 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" + getterstr += "\tcase \"" + templateName + "\":\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() { - out := "package " + c.GetConfig().PackageName + "\n\n" - var getterstr = "\n// nolint\nGetFrag = func(name string) [][]byte {\nswitch(name) {\n" - for templateName, count := range c.TemplateFragmentCount { - out += "var " + templateName + "_frags = make([][]byte," + strconv.Itoa(count) + ")\n" - getterstr += "\tcase \"" + templateName + "\":\n" - getterstr += "\treturn " + templateName + "_frags\n" - } - getterstr += "}\nreturn nil\n}\n" - out += "\n// nolint\nfunc init() {\n" - var bodyMap = make(map[string]string) //map[body]fragmentPrefix - //var tmplMap = make(map[string]map[string]string) // map[tmpl]map[body]fragmentPrefix - var tmpCount = 0 - for _, frag := range c.FragOut { - front := frag.TmplName + "_frags[" + strconv.Itoa(frag.Index) + "]" - /*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 - for _, char := range []byte(frag.Body) { - if char == '\'' { - bits += "'\\" + string(char) + "'," - } else { - bits += "'" + string(char) + "'," - } - } - tmpStr := strconv.Itoa(tmpCount) - out += "arr_" + tmpStr + " := [...]byte{" + bits + "}\n" - out += front + " = arr_" + tmpStr + "[:]\n" - tmpCount++ - //out += front + " = []byte(`" + frag.Body + "`)\n" - } else { - out += front + " = " + fp + "\n" - } - } - out += "\n" + getterstr + "}\n" - err := writeFile(prefix+"template_list.go", out) + err := writeFile(prefix+"template_list.go", getTemplateList(c, wg, prefix)) if err != nil { log.Fatal(err) } @@ -579,8 +569,7 @@ func arithDuoToInt64(left interface{}, right interface{}) (leftInt int64, rightI return arithToInt64(left), arithToInt64(right) } -func InitTemplates() error { - DebugLog("Initialising the template system") +func initDefaultTmplFuncMap() { // TODO: Add support for floats fmap := make(map[string]interface{}) fmap["add"] = func(left interface{}, right interface{}) interface{} { @@ -671,9 +660,11 @@ func InitTemplates() error { return "" } - // The interpreted templates... - DebugLog("Loading the template files...") - Templates.Funcs(fmap) + DefaultTemplateFuncMap = fmap +} + +func loadTemplates(tmpls *template.Template, themeName string) error { + tmpls.Funcs(DefaultTemplateFuncMap) templateFiles, err := filepath.Glob("templates/*.html") if err != nil { return err @@ -709,8 +700,39 @@ func InitTemplates() error { } templateFiles[index] = path } - template.Must(Templates.ParseFiles(templateFiles...)) - template.Must(Templates.ParseGlob("pages/*")) + 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 := templateFileMap["templates/"+strings.TrimPrefix(path, "themes/"+themeName+"/overrides/")] + if !ok { + log.Print("not ok: templates/" + strings.TrimPrefix(path, "themes/"+themeName+"/overrides/")) + templateFiles = append(templateFiles, path) + continue + } + templateFiles[index] = path + } + } + + template.Must(tmpls.ParseFiles(templateFiles...)) + template.Must(tmpls.ParseGlob("pages/*")) return nil } + +func InitTemplates() error { + DebugLog("Initialising the template system") + initDefaultTmplFuncMap() + + // The interpreted templates... + DebugLog("Loading the template files...") + return loadTemplates(DefaultTemplates, "") +} diff --git a/common/templates/context.go b/common/templates/context.go index 2e9e66a7..f91be400 100644 --- a/common/templates/context.go +++ b/common/templates/context.go @@ -28,6 +28,7 @@ type CContext struct { RootHolder string VarHolder string HoldReflect reflect.Value + RootTemplateName string TemplateName string LoopDepth int OutBuf *[]OutBufferFrame diff --git a/common/templates/templates.go b/common/templates/templates.go index 0805fe7b..399c36f9 100644 --- a/common/templates/templates.go +++ b/common/templates/templates.go @@ -63,6 +63,11 @@ type CTemplateSet struct { config CTemplateConfig baseImportMap map[string]string buildTags string + + overridenTrack map[string]map[string]bool + overridenRoots map[string]map[string]bool + themeName string + perThemeTmpls map[string]bool } func NewCTemplateSet() *CTemplateSet { @@ -70,7 +75,8 @@ func NewCTemplateSet() *CTemplateSet { config: CTemplateConfig{ PackageName: "main", }, - baseImportMap: map[string]string{}, + baseImportMap: map[string]string{}, + overridenRoots: map[string]map[string]bool{}, funcMap: map[string]interface{}{ "and": "&&", "not": "!", @@ -118,6 +124,22 @@ func (c *CTemplateSet) SetBuildTags(tags string) { c.buildTags = tags } +func (c *CTemplateSet) SetOverrideTrack(overriden map[string]map[string]bool) { + c.overridenTrack = overriden +} + +func (c *CTemplateSet) GetOverridenRoots() map[string]map[string]bool { + return c.overridenRoots +} + +func (c *CTemplateSet) SetThemeName(name string) { + c.themeName = name +} + +func (c *CTemplateSet) SetPerThemeTmpls(perThemeTmpls map[string]bool) { + c.perThemeTmpls = perThemeTmpls +} + type SkipBlock struct { Frags map[int]int LastCount int @@ -139,10 +161,8 @@ func (c *CTemplateSet) CompileByLoggedin(name string, fileDir string, expects st for index, item := range c.baseImportMap { c.importMap[index] = item } - if len(imports) > 0 { - for _, importItem := range imports { - c.importMap[importItem] = importItem - } + for _, importItem := range imports { + c.importMap[importItem] = importItem } var importList string for _, item := range c.importMap { @@ -150,10 +170,18 @@ func (c *CTemplateSet) CompileByLoggedin(name string, fileDir string, expects st } fname := strings.TrimSuffix(name, filepath.Ext(name)) + if c.themeName != "" { + _, ok := c.perThemeTmpls[fname] + if !ok { + return "", "", "", nil + } + fname += "_" + c.themeName + } c.importMap["github.com/Azareal/Gosora/common"] = "github.com/Azareal/Gosora/common" stub = `package ` + c.config.PackageName + ` ` + importList + ` +import "errors" ` if !c.config.SkipInitBlock { @@ -171,13 +199,18 @@ func (c *CTemplateSet) CompileByLoggedin(name string, fileDir string, expects st stub += "}\n\n" } + // TODO: Try to remove this redundant interface cast stub += ` // nolint -func Template_` + fname + `(tmpl_` + fname + `_vars ` + expects + `, w io.Writer) error { - if tmpl_` + fname + `_vars.CurrentUser.Loggedin { - return Template_` + fname + `_member(tmpl_` + fname + `_vars, w) +func Template_` + fname + `(tmpl_` + fname + `_i interface{}, w io.Writer) error { + tmpl_` + fname + `_vars, ok := tmpl_` + fname + `_i.(` + expects + `) + if !ok { + return errors.New("invalid page struct value") } - return Template_` + fname + `_guest(tmpl_` + fname + `_vars, w) + if tmpl_` + fname + `_vars.CurrentUser.Loggedin { + return Template_` + fname + `_member(tmpl_` + fname + `_i, w) + } + return Template_` + fname + `_guest(tmpl_` + fname + `_i, w) }` c.fileDir = fileDir @@ -213,15 +246,14 @@ func (c *CTemplateSet) Compile(name string, fileDir string, expects string, expe return c.compile(name, content, expects, expectsInt, varList, imports...) } -func (c *CTemplateSet) compile(name string, content, expects string, expectsInt interface{}, varList map[string]VarItem, imports ...string) (out string, err error) { +func (c *CTemplateSet) compile(name string, content string, expects string, expectsInt interface{}, varList map[string]VarItem, imports ...string) (out string, err error) { c.importMap = map[string]string{} for index, item := range c.baseImportMap { c.importMap[index] = item } - if len(imports) > 0 { - for _, importItem := range imports { - c.importMap[importItem] = importItem - } + c.importMap["errors"] = "errors" + for _, importItem := range imports { + c.importMap[importItem] = importItem } c.varList = varList @@ -238,6 +270,13 @@ func (c *CTemplateSet) compile(name string, content, expects string, expectsInt c.detail(name) fname := strings.TrimSuffix(name, filepath.Ext(name)) + if c.themeName != "" { + _, ok := c.perThemeTmpls[fname] + if !ok { + return "", nil + } + fname += "_" + c.themeName + } if c.guestOnly { fname += "_guest" } else if c.memberOnly { @@ -246,7 +285,14 @@ func (c *CTemplateSet) compile(name string, content, expects string, expectsInt var outBuf []OutBufferFrame var rootHold = "tmpl_" + fname + "_vars" - con := CContext{RootHolder: rootHold, VarHolder: rootHold, HoldReflect: reflect.ValueOf(expectsInt), TemplateName: fname, OutBuf: &outBuf} + con := CContext{ + RootHolder: rootHold, + VarHolder: rootHold, + HoldReflect: reflect.ValueOf(expectsInt), + RootTemplateName: fname, + TemplateName: fname, + OutBuf: &outBuf, + } c.templateList = map[string]*parse.Tree{fname: tree} c.detail(c.templateList) c.localVars = make(map[string]map[string]VarItemReflect) @@ -274,7 +320,6 @@ func (c *CTemplateSet) compile(name string, content, expects string, expectsInt if !ok { c.FragOnce[fname] = true } - if len(c.langIndexToName) > 0 { c.importMap[langPkg] = langPkg } @@ -319,7 +364,12 @@ func (c *CTemplateSet) compile(name string, content, expects string, expectsInt fout += "}\n\n" } - fout += "// nolint\nfunc Template_" + fname + "(tmpl_" + fname + "_vars " + expects + ", w io.Writer) error {\n" + fout += "// nolint\nfunc Template_" + fname + "(tmpl_" + fname + "_i interface{}, w io.Writer) error {\n" + fout += `tmpl_` + fname + `_vars, ok := tmpl_` + fname + `_i.(` + expects + `) + if !ok { + return errors.New("invalid page struct value") + } +` if len(c.langIndexToName) > 0 { fout += "var plist = phrases.GetTmplPhrasesBytes(" + fname + "_tmpl_phrase_id)\n" } @@ -427,7 +477,7 @@ func (c *CTemplateSet) rootIterate(tree *parse.Tree, con CContext) { c.retCall("rootIterate") } -var inSlice = func(haystack []string, expr string) bool { +func inSlice(haystack []string, expr string) bool { for _, needle := range haystack { if needle == expr { return true @@ -600,11 +650,15 @@ func (c *CTemplateSet) compileRangeNode(con CContext, node *parse.RangeNode) { } c.detail("Range item:", item) if !item.IsValid() { + c.critical("expr:", expr) + c.critical("con.VarHolder", con.VarHolder) panic("item" + "^\n" + "Invalid map. Maybe, it doesn't have any entries for the template engine to analyse?") } startIf(item, true) case reflect.Slice: if outVal.Len() == 0 { + c.critical("expr:", expr) + c.critical("con.VarHolder", con.VarHolder) panic("The sample data needs at-least one or more elements for the slices. We're looking into removing this requirement at some point!") } startIf(outVal.Index(0), false) @@ -1367,6 +1421,7 @@ func (c *CTemplateSet) compileVarSub(con CContext, varname string, val reflect.V func (c *CTemplateSet) compileSubTemplate(pcon CContext, node *parse.TemplateNode) { c.dumpCall("compileSubTemplate", pcon, node) + defer c.retCall("compileSubTemplate") c.detail("Template Node: ", node.Name) // TODO: Cascade errors back up the tree to the caller? @@ -1474,9 +1529,52 @@ func (c *CTemplateSet) compileSubTemplate(pcon CContext, node *parse.TemplateNod if !ok { c.FragOnce[fname] = true } + + // map[string]map[string]bool + c.detail("overridenTrack loop") + c.detail("fname:", fname) + for themeName, track := range c.overridenTrack { + c.detail("themeName:", themeName) + c.detailf("track: %+v\n", track) + croot, ok := c.overridenRoots[themeName] + if !ok { + croot = make(map[string]bool) + c.overridenRoots[themeName] = croot + } + c.detailf("croot: %+v\n", croot) + for tmplName, _ := range track { + cname := tmplName + if c.guestOnly { + cname += "_guest" + } else if c.memberOnly { + cname += "_member" + } + c.detail("cname:", cname) + if fname == cname { + c.detail("match") + croot[strings.TrimSuffix(strings.TrimSuffix(con.RootTemplateName, "_guest"), "_member")] = true + } else { + c.detail("no match") + } + } + } + c.detailf("c.overridenRoots: %+v\n", c.overridenRoots) } func (c *CTemplateSet) loadTemplate(fileDir string, name string) (content string, err error) { + if c.themeName != "" { + c.detail("per-theme override: ", "./themes/"+c.themeName+"/overrides/"+name) + res, err := ioutil.ReadFile("./themes/" + c.themeName + "/overrides/" + name) + if err == nil { + content = string(res) + if c.config.Minify { + content = minify(content) + } + return content, nil + } + c.detail("override err: ", err) + } + res, err := ioutil.ReadFile(c.fileDir + "overrides/" + name) if err != nil { c.detail("override path: ", c.fileDir+"overrides/"+name) diff --git a/common/theme.go b/common/theme.go index 122fc98b..95bd0a6f 100644 --- a/common/theme.go +++ b/common/theme.go @@ -5,6 +5,7 @@ import ( "bytes" "database/sql" "errors" + htmpl "html/template" "io" "io/ioutil" "log" @@ -24,26 +25,28 @@ var ErrNoDefaultTheme = errors.New("The default theme isn't registered in the sy type Theme struct { Path string // Redirect this file to another folder - Name string - FriendlyName string - Version string - Creator string - FullImage string - MobileFriendly bool - Disabled bool - HideFromThemes bool - BgAvatars bool // For profiles, at the moment - GridLists bool // User Manager - ForkOf string - Tag string - URL string - Docks []string // Allowed Values: leftSidebar, rightSidebar, footer - Settings map[string]ThemeSetting - Templates []TemplateMapping - TemplatesMap map[string]string - TmplPtr map[string]interface{} - Resources []ThemeResource - ResourceTemplates *template.Template + Name string + FriendlyName string + Version string + Creator string + FullImage string + MobileFriendly bool + Disabled bool + HideFromThemes bool + BgAvatars bool // For profiles, at the moment + GridLists bool // User Manager + ForkOf string + Tag string + URL string + Docks []string // Allowed Values: leftSidebar, rightSidebar, footer + Settings map[string]ThemeSetting + IntTmplHandle *htmpl.Template + OverridenTemplates []string + Templates []TemplateMapping + TemplatesMap map[string]string + TmplPtr map[string]interface{} + Resources []ThemeResource + ResourceTemplates *template.Template // Dock intercepters // TODO: Implement this @@ -180,100 +183,22 @@ func (theme *Theme) MapTemplates() { LogError(errors.New("The source template doesn't exist!")) } - switch dTmplPtr := destTmplPtr.(type) { - case *func(CustomPagePage, io.Writer) error: - switch sTmplPtr := sourceTmplPtr.(type) { - case *func(CustomPagePage, io.Writer) error: - overridenTemplates[themeTmpl.Name] = true - *dTmplPtr = *sTmplPtr - default: - LogError(errors.New("The source and destination templates are incompatible")) - } - case *func(TopicPage, io.Writer) error: - switch sTmplPtr := sourceTmplPtr.(type) { - case *func(TopicPage, io.Writer) error: - overridenTemplates[themeTmpl.Name] = true - *dTmplPtr = *sTmplPtr - default: - LogError(errors.New("The source and destination templates are incompatible")) - } - case *func(TopicListPage, io.Writer) error: - switch sTmplPtr := sourceTmplPtr.(type) { - case *func(TopicListPage, io.Writer) error: - overridenTemplates[themeTmpl.Name] = true - *dTmplPtr = *sTmplPtr - default: - LogError(errors.New("The source and destination templates are incompatible")) - } - case *func(ForumPage, io.Writer) error: - switch sTmplPtr := sourceTmplPtr.(type) { - case *func(ForumPage, io.Writer) error: - overridenTemplates[themeTmpl.Name] = true - *dTmplPtr = *sTmplPtr - default: - LogError(errors.New("The source and destination templates are incompatible")) - } - case *func(ForumsPage, io.Writer) error: - switch sTmplPtr := sourceTmplPtr.(type) { - case *func(ForumsPage, io.Writer) error: - overridenTemplates[themeTmpl.Name] = true - *dTmplPtr = *sTmplPtr - default: - LogError(errors.New("The source and destination templates are incompatible")) - } - case *func(ProfilePage, io.Writer) error: - switch sTmplPtr := sourceTmplPtr.(type) { - case *func(ProfilePage, io.Writer) error: - overridenTemplates[themeTmpl.Name] = true - *dTmplPtr = *sTmplPtr - default: - LogError(errors.New("The source and destination templates are incompatible")) - } - case *func(CreateTopicPage, io.Writer) error: - switch sTmplPtr := sourceTmplPtr.(type) { - case *func(CreateTopicPage, io.Writer) error: - overridenTemplates[themeTmpl.Name] = true - *dTmplPtr = *sTmplPtr - default: - LogError(errors.New("The source and destination templates are incompatible")) - } - case *func(IPSearchPage, io.Writer) error: - switch sTmplPtr := sourceTmplPtr.(type) { - case *func(IPSearchPage, io.Writer) error: - overridenTemplates[themeTmpl.Name] = true - *dTmplPtr = *sTmplPtr - default: - LogError(errors.New("The source and destination templates are incompatible")) - } - case *func(AccountDashPage, io.Writer) error: - switch sTmplPtr := sourceTmplPtr.(type) { - case *func(AccountDashPage, io.Writer) error: - overridenTemplates[themeTmpl.Name] = true - *dTmplPtr = *sTmplPtr - default: - LogError(errors.New("The source and destination templates are incompatible")) - } - case *func(ErrorPage, io.Writer) error: - switch sTmplPtr := sourceTmplPtr.(type) { - case *func(ErrorPage, io.Writer) error: - overridenTemplates[themeTmpl.Name] = true - *dTmplPtr = *sTmplPtr - default: - LogError(errors.New("The source and destination templates are incompatible")) - } - case *func(Page, io.Writer) error: - switch sTmplPtr := sourceTmplPtr.(type) { - case *func(Page, io.Writer) error: - overridenTemplates[themeTmpl.Name] = true - *dTmplPtr = *sTmplPtr - default: - LogError(errors.New("The source and destination templates are incompatible")) - } - default: + dTmplPtr, ok := destTmplPtr.(*func(interface{}, io.Writer) error) + if !ok { log.Print("themeTmpl.Name: ", themeTmpl.Name) log.Print("themeTmpl.Source: ", themeTmpl.Source) LogError(errors.New("Unknown destination template type!")) + return } + + sTmplPtr, ok := sourceTmplPtr.(*func(interface{}, io.Writer) error) + if !ok { + LogError(errors.New("The source and destination templates are incompatible")) + return + } + + overridenTemplates[themeTmpl.Name] = true + *dTmplPtr = *sTmplPtr } } } @@ -366,67 +291,17 @@ func (theme *Theme) RunTmpl(template string, pi interface{}, w io.Writer) error var getTmpl = theme.GetTmpl(template) switch tmplO := getTmpl.(type) { - case *func(CustomPagePage, io.Writer) error: + case *func(interface{}, io.Writer) error: var tmpl = *tmplO - return tmpl(pi.(CustomPagePage), w) - case *func(TopicPage, io.Writer) error: - var tmpl = *tmplO - return tmpl(pi.(TopicPage), w) - case *func(TopicListPage, io.Writer) error: - var tmpl = *tmplO - return tmpl(pi.(TopicListPage), w) - case *func(ForumPage, io.Writer) error: - var tmpl = *tmplO - return tmpl(pi.(ForumPage), w) - case *func(ForumsPage, io.Writer) error: - var tmpl = *tmplO - return tmpl(pi.(ForumsPage), w) - case *func(ProfilePage, io.Writer) error: - var tmpl = *tmplO - return tmpl(pi.(ProfilePage), w) - case *func(CreateTopicPage, io.Writer) error: - var tmpl = *tmplO - return tmpl(pi.(CreateTopicPage), w) - case *func(IPSearchPage, io.Writer) error: - var tmpl = *tmplO - return tmpl(pi.(IPSearchPage), w) - case *func(Account, io.Writer) error: - var tmpl = *tmplO - return tmpl(pi.(Account), w) - case *func(ErrorPage, io.Writer) error: - var tmpl = *tmplO - return tmpl(pi.(ErrorPage), w) - case *func(Page, io.Writer) error: - var tmpl = *tmplO - return tmpl(pi.(Page), w) - case func(CustomPagePage, io.Writer) error: - return tmplO(pi.(CustomPagePage), w) - case func(TopicPage, io.Writer) error: - return tmplO(pi.(TopicPage), w) - case func(TopicListPage, io.Writer) error: - return tmplO(pi.(TopicListPage), w) - case func(ForumPage, io.Writer) error: - return tmplO(pi.(ForumPage), w) - case func(ForumsPage, io.Writer) error: - return tmplO(pi.(ForumsPage), w) - case func(ProfilePage, io.Writer) error: - return tmplO(pi.(ProfilePage), w) - case func(CreateTopicPage, io.Writer) error: - return tmplO(pi.(CreateTopicPage), w) - case func(IPSearchPage, io.Writer) error: - return tmplO(pi.(IPSearchPage), w) - case func(Account, io.Writer) error: - return tmplO(pi.(Account), w) - case func(ErrorPage, io.Writer) error: - return tmplO(pi.(ErrorPage), w) - case func(Page, io.Writer) error: - return tmplO(pi.(Page), w) + return tmpl(pi, w) + case func(interface{}, io.Writer) error: + return tmplO(pi, w) case nil, string: mapping, ok := theme.TemplatesMap[template] if !ok { mapping = template } - return Templates.ExecuteTemplate(w, mapping+".html", pi) + return theme.IntTmplHandle.ExecuteTemplate(w, mapping+".html", pi) default: log.Print("theme ", theme) log.Print("template ", template) diff --git a/common/theme_list.go b/common/theme_list.go index a415dda2..fcb0e9ac 100644 --- a/common/theme_list.go +++ b/common/theme_list.go @@ -4,11 +4,14 @@ import ( "database/sql" "encoding/json" "errors" + "html/template" "io" "io/ioutil" "log" "net/http" "os" + "path/filepath" + "strings" "sync" "sync/atomic" @@ -136,6 +139,40 @@ func NewThemeList() (themes ThemeList, err error) { } } + theme.IntTmplHandle = DefaultTemplates + overrides, err := ioutil.ReadDir(theme.Path + "/overrides/") + if err != nil && !os.IsNotExist(err) { + return themes, err + } + if len(overrides) > 0 { + var overCount = 0 + for _, override := range overrides { + if override.IsDir() { + continue + } + var ext = filepath.Ext(themePath + "/overrides/" + override.Name()) + log.Print("attempting to add " + themePath + "/overrides/" + override.Name()) + if ext != ".html" { + log.Print("not a html file") + continue + } + overCount++ + theme.OverridenTemplates = append(theme.OverridenTemplates, strings.TrimSuffix(override.Name(), ext)) + log.Print("succeeded") + } + + localTmpls := template.New("") + err = loadTemplates(localTmpls, theme.Name) + if err != nil { + return themes, err + } + theme.IntTmplHandle = localTmpls + log.Printf("theme.OverridenTemplates: %+v\n", theme.OverridenTemplates) + log.Printf("theme.IntTmplHandle: %+v\n", theme.IntTmplHandle) + } else { + log.Print("no overrides for " + theme.Name) + } + // TODO: Bind the built template, or an interpreted one for any dock overrides this theme has themes[theme.Name] = theme @@ -218,88 +255,20 @@ func ResetTemplateOverrides() { } // Not really a pointer, more of a function handle, an artifact from one of the earlier versions of themes.go - switch oPtr := originPointer.(type) { - case func(CustomPagePage, io.Writer) error: - switch dPtr := destTmplPtr.(type) { - case *func(CustomPagePage, io.Writer) error: - *dPtr = oPtr - default: - LogError(errors.New("The source and destination templates are incompatible")) - } - case func(TopicPage, io.Writer) error: - switch dPtr := destTmplPtr.(type) { - case *func(TopicPage, io.Writer) error: - *dPtr = oPtr - default: - LogError(errors.New("The source and destination templates are incompatible")) - } - case func(TopicListPage, io.Writer) error: - switch dPtr := destTmplPtr.(type) { - case *func(TopicListPage, io.Writer) error: - *dPtr = oPtr - default: - LogError(errors.New("The source and destination templates are incompatible")) - } - case func(ForumPage, io.Writer) error: - switch dPtr := destTmplPtr.(type) { - case *func(ForumPage, io.Writer) error: - *dPtr = oPtr - default: - LogError(errors.New("The source and destination templates are incompatible")) - } - case func(ForumsPage, io.Writer) error: - switch dPtr := destTmplPtr.(type) { - case *func(ForumsPage, io.Writer) error: - *dPtr = oPtr - default: - LogError(errors.New("The source and destination templates are incompatible")) - } - case func(ProfilePage, io.Writer) error: - switch dPtr := destTmplPtr.(type) { - case *func(ProfilePage, io.Writer) error: - *dPtr = oPtr - default: - LogError(errors.New("The source and destination templates are incompatible")) - } - case func(CreateTopicPage, io.Writer) error: - switch dPtr := destTmplPtr.(type) { - case *func(CreateTopicPage, io.Writer) error: - *dPtr = oPtr - default: - LogError(errors.New("The source and destination templates are incompatible")) - } - case func(IPSearchPage, io.Writer) error: - switch dPtr := destTmplPtr.(type) { - case *func(IPSearchPage, io.Writer) error: - *dPtr = oPtr - default: - LogError(errors.New("The source and destination templates are incompatible")) - } - case func(AccountDashPage, io.Writer) error: - switch dPtr := destTmplPtr.(type) { - case *func(AccountDashPage, io.Writer) error: - *dPtr = oPtr - default: - LogError(errors.New("The source and destination templates are incompatible")) - } - case func(ErrorPage, io.Writer) error: - switch dPtr := destTmplPtr.(type) { - case *func(ErrorPage, io.Writer) error: - *dPtr = oPtr - default: - LogError(errors.New("The source and destination templates are incompatible")) - } - case func(Page, io.Writer) error: - switch dPtr := destTmplPtr.(type) { - case *func(Page, io.Writer) error: - *dPtr = oPtr - default: - LogError(errors.New("The source and destination templates are incompatible")) - } - default: + oPtr, ok := originPointer.(func(interface{}, io.Writer) error) + if !ok { log.Print("name: ", name) LogError(errors.New("Unknown destination template type!")) + return } + + dPtr, ok := destTmplPtr.(*func(interface{}, io.Writer) error) + if !ok { + LogError(errors.New("The source and destination templates are incompatible")) + return + } + + *dPtr = oPtr log.Print("The template override was reset") } overridenTemplates = make(map[string]bool) @@ -313,7 +282,7 @@ func CreateThemeTemplate(theme string, name string) { if !ok { mapping = name } - return Templates.ExecuteTemplate(w, mapping+".html", pi) + return DefaultTemplates.ExecuteTemplate(w, mapping+".html", pi) } } diff --git a/common/topic.go b/common/topic.go index 70410c0d..4c539a94 100644 --- a/common/topic.go +++ b/common/topic.go @@ -1,7 +1,7 @@ /* * * Gosora Topic File -* Copyright Azareal 2017 - 2019 +* Copyright Azareal 2017 - 2020 * */ package common diff --git a/common/topic_list.go b/common/topic_list.go index f5fe681d..b1ce9513 100644 --- a/common/topic_list.go +++ b/common/topic_list.go @@ -16,9 +16,9 @@ type TopicListHolder struct { } type TopicListInt interface { - GetListByCanSee(canSee []int, page int, orderby string) (topicList []*TopicsRow, forumList []Forum, paginator Paginator, err error) - GetListByGroup(group *Group, page int, orderby string) (topicList []*TopicsRow, forumList []Forum, paginator Paginator, err error) - GetList(page int, orderby string) (topicList []*TopicsRow, forumList []Forum, paginator Paginator, err error) + GetListByCanSee(canSee []int, page int, orderby string, filterIDs []int) (topicList []*TopicsRow, forumList []Forum, paginator Paginator, err error) + GetListByGroup(group *Group, page int, orderby string, filterIDs []int) (topicList []*TopicsRow, forumList []Forum, paginator Paginator, err error) + GetList(page int, orderby string, filterIDs []int) (topicList []*TopicsRow, forumList []Forum, paginator Paginator, err error) } type DefaultTopicList struct { @@ -94,7 +94,7 @@ func (tList *DefaultTopicList) Tick() error { var canSeeHolders = make(map[string]*TopicListHolder) for name, canSee := range permTree { - topicList, forumList, paginator, err := tList.GetListByCanSee(canSee, 1, "") + topicList, forumList, paginator, err := tList.GetListByCanSee(canSee, 1, "", nil) if err != nil { return err } @@ -115,12 +115,12 @@ func (tList *DefaultTopicList) Tick() error { return nil } -func (tList *DefaultTopicList) GetListByGroup(group *Group, page int, orderby string) (topicList []*TopicsRow, forumList []Forum, paginator Paginator, err error) { +func (tList *DefaultTopicList) GetListByGroup(group *Group, page int, orderby string, filterIDs []int) (topicList []*TopicsRow, forumList []Forum, paginator Paginator, err error) { if page == 0 { page = 1 } // TODO: Cache the first three pages not just the first along with all the topics on this beaten track - if page == 1 && orderby == "" { + if page == 1 && orderby == "" && len(filterIDs) == 0 { var holder *TopicListHolder var ok bool if group.ID%2 == 0 { @@ -139,10 +139,10 @@ func (tList *DefaultTopicList) GetListByGroup(group *Group, page int, orderby st // TODO: Make CanSee a method on *Group with a canSee field? Have a CanSee method on *User to cover the case of superadmins? //log.Printf("deoptimising for %d on page %d\n", group.ID, page) - return tList.GetListByCanSee(group.CanSee, page, orderby) + return tList.GetListByCanSee(group.CanSee, page, orderby, filterIDs) } -func (tList *DefaultTopicList) GetListByCanSee(canSee []int, page int, orderby string) (topicList []*TopicsRow, forumList []Forum, paginator Paginator, err error) { +func (tList *DefaultTopicList) GetListByCanSee(canSee []int, page int, orderby string, filterIDs []int) (topicList []*TopicsRow, forumList []Forum, paginator Paginator, err error) { // We need a list of the visible forums for Quick Topic // ? - Would it be useful, if we could post in social groups from /topics/? for _, fid := range canSee { @@ -154,6 +154,26 @@ func (tList *DefaultTopicList) GetListByCanSee(canSee []int, page int, orderby s } } + var inSlice = func(haystack []int, needle int) bool { + for _, item := range haystack { + if needle == item { + return true + } + } + return false + } + + var filteredForums []Forum + if len(filterIDs) > 0 { + for _, forum := range forumList { + if inSlice(filterIDs, forum.ID) { + filteredForums = append(filteredForums, forum) + } + } + } else { + filteredForums = forumList + } + // ? - Should we be showing plugin_guilds posts on /topics/? argList, qlist := ForumListToArgQ(forumList) if qlist == "" { @@ -166,13 +186,33 @@ func (tList *DefaultTopicList) GetListByCanSee(canSee []int, page int, orderby s } // TODO: Reduce the number of returns -func (tList *DefaultTopicList) GetList(page int, orderby string) (topicList []*TopicsRow, forumList []Forum, paginator Paginator, err error) { +func (tList *DefaultTopicList) GetList(page int, orderby string, filterIDs []int) (topicList []*TopicsRow, forumList []Forum, paginator Paginator, err error) { // TODO: Make CanSee a method on *Group with a canSee field? Have a CanSee method on *User to cover the case of superadmins? - canSee, err := Forums.GetAllVisibleIDs() + cCanSee, err := Forums.GetAllVisibleIDs() if err != nil { return nil, nil, Paginator{nil, 1, 1}, err } + var inSlice = func(haystack []int, needle int) bool { + for _, item := range haystack { + if needle == item { + return true + } + } + return false + } + + var canSee []int + if len(filterIDs) > 0 { + for _, fid := range cCanSee { + if inSlice(filterIDs, fid) { + canSee = append(canSee, fid) + } + } + } else { + canSee = cCanSee + } + // We need a list of the visible forums for Quick Topic // ? - Would it be useful, if we could post in social groups from /topics/? for _, fid := range canSee { diff --git a/common/user.go b/common/user.go index 6358f428..d5962379 100644 --- a/common/user.go +++ b/common/user.go @@ -62,20 +62,21 @@ func (user *User) WebSockets() *WsJSONUser { groupID = user.TempGroup } // TODO: Do we want to leak the user's permissions? Users will probably be able to see their status from the group tags, but still - return &WsJSONUser{user.ID, user.Link, user.Name, groupID, user.IsMod, user.Avatar, user.Level, user.Score, user.Liked} + return &WsJSONUser{user.ID, user.Link, user.Name, groupID, user.IsMod, user.Avatar, user.MicroAvatar, user.Level, user.Score, user.Liked} } // Use struct tags to avoid having to define this? It really depends on the circumstances, sometimes we want the whole thing, sometimes... not. type WsJSONUser struct { - ID int - Link string - Name string - Group int // Be sure to mask with TempGroup - IsMod bool - Avatar string - Level int - Score int - Liked int + ID int + Link string + Name string + Group int // Be sure to mask with TempGroup + IsMod bool + Avatar string + MicroAvatar string + Level int + Score int + Liked int } func (user *User) Me() *MeUser { diff --git a/common/websockets.go b/common/websockets.go index 0cc52f25..8ad5d847 100644 --- a/common/websockets.go +++ b/common/websockets.go @@ -37,8 +37,10 @@ func init() { topicWatchers = make(map[int]map[*WSUser]bool) } +//easyjson:json type WsTopicList struct { - Topics []*WsTopicsRow + Topics []*WsTopicsRow + LastPage int // Not for WebSockets, but for the JSON endpoint for /topics/ to keep the paginator functional } // TODO: How should we handle errors for this? diff --git a/common/widget_search_and_filter.go b/common/widget_search_and_filter.go index 69a23aa3..4d51ffbc 100644 --- a/common/widget_search_and_filter.go +++ b/common/widget_search_and_filter.go @@ -3,16 +3,19 @@ package common import "errors" // TODO: Move this into it's own package to make neater and tidier +type filterForum struct { + *Forum + Selected bool +} type searchAndFilter struct { *Header - Forums []*Forum + Forums []filterForum } func widgetSearchAndFilter(widget *Widget, hvars interface{}) (out string, err error) { header := hvars.(*Header) user := header.CurrentUser - - var forums []*Forum + var forums []filterForum var canSee []int if user.IsSuperAdmin { canSee, err = Forums.GetAllVisibleIDs() @@ -31,11 +34,11 @@ func widgetSearchAndFilter(widget *Widget, hvars interface{}) (out string, err e for _, fid := range canSee { forum := Forums.DirtyGet(fid) if forum.ParentID == 0 && forum.Name != "" && forum.Active { - forums = append(forums, forum) + forums = append(forums, filterForum{forum, (header.Zone == "view_forum" || header.Zone == "topics") && header.ZoneID == forum.ID}) } } saf := &searchAndFilter{header, forums} err = saf.Header.Theme.RunTmpl("widget_search_and_filter", saf, saf.Header.Writer) return "", err -} \ No newline at end of file +} diff --git a/common/widgets.go b/common/widgets.go index 7f91205f..f2b785dd 100644 --- a/common/widgets.go +++ b/common/widgets.go @@ -48,7 +48,7 @@ type NameTextPair struct { func preparseWidget(widget *Widget, wdata string) (err error) { prebuildWidget := func(name string, data interface{}) (string, error) { var b bytes.Buffer - err := Templates.ExecuteTemplate(&b, name+".html", data) + err := DefaultTemplates.ExecuteTemplate(&b, name+".html", data) return string(b.Bytes()), err } diff --git a/common/ws_hub.go b/common/ws_hub.go index 7d009292..e7475649 100644 --- a/common/ws_hub.go +++ b/common/ws_hub.go @@ -47,9 +47,13 @@ func (hub *WsHubImpl) Start() { // This Tick is separate from the admin one, as we want to process that in parallel with this due to the blocking calls to gopsutil func (hub *WsHubImpl) Tick() error { + return wsTopicListTick(hub) +} + +func wsTopicListTick(hub *WsHubImpl) error { // Don't waste CPU time if nothing has happened // TODO: Get a topic list method which strips stickies? - tList, _, _, err := TopicList.GetList(1, "") + tList, _, _, err := TopicList.GetList(1, "", nil) if err != nil { hub.lastTick = time.Now() return err // TODO: Do we get ErrNoRows here? @@ -117,7 +121,7 @@ func (hub *WsHubImpl) Tick() error { var canSeeRenders = make(map[string][]byte) for name, canSee := range canSeeMap { - topicList, forumList, _, err := TopicList.GetListByCanSee(canSee, 1, "") + topicList, forumList, _, err := TopicList.GetListByCanSee(canSee, 1, "", nil) if err != nil { return err // TODO: Do we get ErrNoRows here? } @@ -146,7 +150,7 @@ func (hub *WsHubImpl) Tick() error { wsTopicList[i] = topicRow.WebSockets() } - outBytes, err := json.Marshal(&WsTopicList{wsTopicList}) + outBytes, err := json.Marshal(&WsTopicList{wsTopicList, 0}) if err != nil { return err } diff --git a/docs/installation.md b/docs/installation.md index f7ced87e..deb0035d 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -126,6 +126,10 @@ go build "./cmd/install" install.exe +go get github.com/mailru/easyjson/... + +easyjson -pkg common + gosora.exe -build-templates gosora.exe diff --git a/extend/guilds/lib/guilds.go b/extend/guilds/lib/guilds.go index 9a12328a..bdf9bf5d 100644 --- a/extend/guilds/lib/guilds.go +++ b/extend/guilds/lib/guilds.go @@ -116,7 +116,7 @@ func CommonAreaWidgets(header *common.Header) { common.WidgetMenuItem{"Create Guild", "/guild/create/", false}, }} - err := common.Templates.ExecuteTemplate(&b, "widget_menu.html", menu) + err := header.Theme.RunTmpl("widget_menu", pi, w) if err != nil { common.LogError(err) return @@ -232,7 +232,7 @@ func RouteCreateGuild(w http.ResponseWriter, r *http.Request, user common.User) CommonAreaWidgets(header) pi := common.Page{header, tList, nil} - err := common.Templates.ExecuteTemplate(w, "guilds_create_guild.html", pi) + err := header.Theme.RunTmpl("guilds_create_guild", pi, w) if err != nil { return common.InternalError(err, w, r) } @@ -384,7 +384,7 @@ func PreRenderViewForum(w http.ResponseWriter, r *http.Request, user *common.Use guildItem := guildData.(*Guild) guildpi := Page{pi.Title, pi.Header, pi.ItemList, pi.Forum, guildItem, pi.Page, pi.LastPage} - err := common.Templates.ExecuteTemplate(w, "guilds_view_guild.html", guildpi) + err := header.Theme.RunTmpl("guilds_view_guild", guildpi, w) if err != nil { common.LogError(err) return false diff --git a/extend/guilds/plugin_guilds.go b/extend/guilds/plugin_guilds.go index 27971e44..2b8d51af 100644 --- a/extend/guilds/plugin_guilds.go +++ b/extend/guilds/plugin_guilds.go @@ -15,13 +15,13 @@ func init() { common.PrebuildTmplList = append(common.PrebuildTmplList, guilds.PrebuildTmplList) } -func initGuilds() (err error) { - common.Plugins["guilds"].AddHook("intercept_build_widgets", guilds.Widgets) - common.Plugins["guilds"].AddHook("trow_assign", guilds.TrowAssign) - common.Plugins["guilds"].AddHook("topic_create_pre_loop", guilds.TopicCreatePreLoop) - common.Plugins["guilds"].AddHook("pre_render_forum", guilds.PreRenderViewForum) - common.Plugins["guilds"].AddHook("simple_forum_check_pre_perms", guilds.ForumCheck) - common.Plugins["guilds"].AddHook("forum_check_pre_perms", guilds.ForumCheck) +func initGuilds(plugin *common.Plugin) (err error) { + plugin.AddHook("intercept_build_widgets", guilds.Widgets) + plugin.AddHook("trow_assign", guilds.TrowAssign) + plugin.AddHook("topic_create_pre_loop", guilds.TopicCreatePreLoop) + plugin.AddHook("pre_render_forum", guilds.PreRenderViewForum) + plugin.AddHook("simple_forum_check_pre_perms", guilds.ForumCheck) + plugin.AddHook("forum_check_pre_perms", guilds.ForumCheck) // TODO: Auto-grant this perm to admins upon installation? common.RegisterPluginPerm("CreateGuild") router.HandleFunc("/guilds/", guilds.RouteGuildList) @@ -54,13 +54,13 @@ func initGuilds() (err error) { return acc.FirstError() } -func deactivateGuilds() { - common.Plugins["guilds"].RemoveHook("intercept_build_widgets", guilds.Widgets) - common.Plugins["guilds"].RemoveHook("trow_assign", guilds.TrowAssign) - common.Plugins["guilds"].RemoveHook("topic_create_pre_loop", guilds.TopicCreatePreLoop) - common.Plugins["guilds"].RemoveHook("pre_render_forum", guilds.PreRenderViewForum) - common.Plugins["guilds"].RemoveHook("simple_forum_check_pre_perms", guilds.ForumCheck) - common.Plugins["guilds"].RemoveHook("forum_check_pre_perms", guilds.ForumCheck) +func deactivateGuilds(plugin *common.Plugin) { + plugin.RemoveHook("intercept_build_widgets", guilds.Widgets) + plugin.RemoveHook("trow_assign", guilds.TrowAssign) + plugin.RemoveHook("topic_create_pre_loop", guilds.TopicCreatePreLoop) + plugin.RemoveHook("pre_render_forum", guilds.PreRenderViewForum) + plugin.RemoveHook("simple_forum_check_pre_perms", guilds.ForumCheck) + plugin.RemoveHook("forum_check_pre_perms", guilds.ForumCheck) common.DeregisterPluginPerm("CreateGuild") _ = router.RemoveFunc("/guilds/") _ = router.RemoveFunc("/guild/") @@ -76,7 +76,7 @@ func deactivateGuilds() { } // TODO: Stop accessing the query builder directly and add a feature in Gosora which is more easily reversed, if an error comes up during the installation process -func installGuilds() error { +func installGuilds(plugin *common.Plugin) error { guildTableStmt, err := qgen.Builder.CreateTable("guilds", "utf8mb4", "utf8mb4_general_ci", []qgen.DBTableColumn{ qgen.DBTableColumn{"guildID", "int", 0, false, true, ""}, @@ -125,6 +125,6 @@ func installGuilds() error { } // TO-DO; Implement an uninstallation system into Gosora. And a better installation system. -func uninstallGuilds() error { +func uninstallGuilds(plugin *common.Plugin) error { return nil } diff --git a/gen_router.go b/gen_router.go index 4d6634f7..b97c29c4 100644 --- a/gen_router.go +++ b/gen_router.go @@ -587,7 +587,7 @@ type GenRouter struct { } func NewGenRouter(uploads http.Handler) (*GenRouter, error) { - f, err := os.OpenFile("./logs/requests.log", os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0755) + f, err := os.OpenFile("./logs/reqs-"+strconv.FormatInt(common.StartTime.Unix(),10)+".log", os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0755) if err != nil { return nil, err } diff --git a/general_test.go b/general_test.go index f32a252d..6dc7fdb5 100644 --- a/general_test.go +++ b/general_test.go @@ -6,11 +6,11 @@ import ( "log" "net/http" "net/http/httptest" + "runtime/debug" "strconv" "strings" "testing" "time" - "runtime/debug" "github.com/pkg/errors" @@ -66,6 +66,10 @@ func gloinit() (err error) { return errors.WithStack(err) } + err = common.InitTemplates() + if err != nil { + return errors.WithStack(err) + } common.Themes, err = common.NewThemeList() if err != nil { return errors.WithStack(err) @@ -342,6 +346,14 @@ func BenchmarkTopicsGuestRouteParallelWithRouter(b *testing.B) { obRoute(b, "/topics/") } +func BenchmarkTopicsGuestJSRouteParallelWithRouter(b *testing.B) { + obRoute(b, "/topics/?js=1") +} + +func BenchmarkTopicsGuestEJSRouteParallelWithRouter(b *testing.B) { + obRoute(b, "/topics/?ejs=1") +} + func BenchmarkForumsGuestRouteParallelWithRouter(b *testing.B) { obRoute(b, "/forums/") } diff --git a/go.mod b/go.mod index 670f50e6..ab8d2cc1 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/go-sql-driver/mysql v1.4.0 github.com/gorilla/websocket v1.4.0 github.com/lib/pq v1.0.0 + github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329 github.com/oschwald/geoip2-golang v1.2.1 github.com/oschwald/maxminddb-golang v1.3.0 // indirect github.com/pkg/errors v0.8.0 diff --git a/go.sum b/go.sum index 13c03fd2..3d841ed4 100644 --- a/go.sum +++ b/go.sum @@ -41,6 +41,8 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329 h1:2gxZ0XQIU/5z3Z3bUBu+FXuk2pFbkN6tcwi/pjyaDic= +github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/oschwald/geoip2-golang v1.2.1 h1:3iz+jmeJc6fuCyWeKgtXSXu7+zvkxJbHFXkMT5FVebU= diff --git a/langs/english.json b/langs/english.json index c16b337a..06677c73 100644 --- a/langs/english.json +++ b/langs/english.json @@ -537,7 +537,6 @@ "status.closed_tooltip":"Status: Closed", "status.pinned_tooltip":"Status: Pinned", - "topics_head":"All Topics", "topics_locked_tooltip":"You don't have the permissions needed to create a topic", "topics_locked_aria":"You don't have the permissions needed to make a topic anywhere", "topics_list_aria":"A list containing topics from every forum", @@ -624,12 +623,16 @@ "topic.your_information":"Your information", - "paginator_less_than":"<", - "paginator_greater_than":">", - "paginator_prev_page":"Prev", - "paginator_prev_page_aria":"Go to the previous page", - "paginator_next_page":"Next", - "paginator_next_page_aria":"Go to the next page", + "paginator.less_than":"<", + "paginator.greater_than":">", + "paginator.first_page":"‹‹", + "paginator.first_page_aria":"Go to the first page", + "paginator.last_page":"››", + "paginator.last_page_aria":"Go to the last page", + "paginator.prev_page":"‹", + "paginator.prev_page_aria":"Go to the previous page", + "paginator.next_page":"›", + "paginator.next_page_aria":"Go to the next page", "profile_login_for_options":"Login for options", "profile_add_friend":"Add Friend", @@ -754,6 +757,7 @@ "panel_forum_edit_button":"Edit", "panel_forum_short_update_button":"Update", "panel_forum_full_edit_button":"Full Edit", + "panel_forum_delete_are_you_sure":"Are you sure you want to delete the '%s' forum?", "panel_groups_head":"Groups", "panel_groups_rank_prefix":"Rank ", diff --git a/main.go b/main.go index 72296bb8..a882e222 100644 --- a/main.go +++ b/main.go @@ -19,6 +19,7 @@ import ( "os/signal" "runtime" "runtime/pprof" + "strconv" "strings" "syscall" "time" @@ -55,10 +56,6 @@ func afterDBInit() (err error) { return errors.WithStack(err) } - err = common.InitTemplates() - if err != nil { - return errors.WithStack(err) - } err = phrases.InitPhrases(common.Site.Language) if err != nil { return errors.WithStack(err) @@ -222,10 +219,11 @@ func main() { return } }()*/ + common.StartTime = time.Now() // TODO: Have a file for each run with the time/date the server started as the file name? // TODO: Log panics with recover() - f, err := os.OpenFile("./logs/ops.log", os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0755) + f, err := os.OpenFile("./logs/ops-"+strconv.FormatInt(common.StartTime.Unix(), 10)+".log", os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0755) if err != nil { log.Fatal(err) } @@ -233,7 +231,6 @@ func main() { log.SetOutput(logWriter) log.Print("Running Gosora v" + common.SoftwareVersion.String()) fmt.Println("") - common.StartTime = time.Now() // TODO: Add a flag for enabling the profiler if false { @@ -261,6 +258,10 @@ func main() { log.Fatal(err) } + err = common.InitTemplates() + if err != nil { + log.Fatal(err) + } common.Themes, err = common.NewThemeList() if err != nil { log.Fatal(err) @@ -332,7 +333,6 @@ func main() { for { select { case event := <-watcher.Events: - log.Println("event:", event) // TODO: Handle file deletes (and renames more graciously by removing the old version of it) if event.Op&fsnotify.Write == fsnotify.Write { log.Println("modified file:", event.Name) @@ -340,12 +340,15 @@ func main() { } else if event.Op&fsnotify.Create == fsnotify.Create { log.Println("new file:", event.Name) err = modifiedFileEvent(event.Name) + } else { + log.Println("unknown event:", event) + err = nil } if err != nil { common.LogError(err) } case err = <-watcher.Errors: - common.LogError(err) + common.LogWarning(err) } } }() diff --git a/misc_test.go b/misc_test.go index 370fe847..fb52aa7f 100644 --- a/misc_test.go +++ b/misc_test.go @@ -861,7 +861,7 @@ func TestPluginManager(t *testing.T) { expectNilErr(t, err) expect(t, hasPlugin, "Plugin bbcode should exist in the database") expect(t, plugin.Init != nil, "Plugin bbcode should have an init function") - expectNilErr(t, plugin.Init()) + expectNilErr(t, plugin.Init(plugin)) expectNilErr(t, plugin.SetActive(true)) expect(t, !plugin.Installable, "Plugin bbcode shouldn't be installable") @@ -885,7 +885,7 @@ func TestPluginManager(t *testing.T) { expectNilErr(t, err) expect(t, hasPlugin, "Plugin bbcode should still exist in the database") expect(t, plugin.Deactivate != nil, "Plugin bbcode should have an init function") - plugin.Deactivate() // Returns nothing + plugin.Deactivate(plugin) // Returns nothing // Not installable, should not be mutated expect(t, plugin.SetInstalled(true) == common.ErrPluginNotInstallable, "Plugin was set as installed despite not being installable") @@ -949,12 +949,12 @@ func TestPluginManager(t *testing.T) { expect(t, !hasPlugin, "Plugin markdown shouldn't exist in the database") expectNilErr(t, plugin2.AddToDatabase(true, false)) - expectNilErr(t, plugin2.Init()) + expectNilErr(t, plugin2.Init(plugin2)) expectNilErr(t, plugin.SetActive(true)) - expectNilErr(t, plugin.Init()) - plugin2.Deactivate() + expectNilErr(t, plugin.Init(plugin)) + plugin2.Deactivate(plugin2) expectNilErr(t, plugin2.SetActive(false)) - plugin.Deactivate() + plugin.Deactivate(plugin) expectNilErr(t, plugin.SetActive(false)) // Hook tests diff --git a/plugin_adventure.go b/plugin_adventure.go index 2f93104e..3c117d97 100644 --- a/plugin_adventure.go +++ b/plugin_adventure.go @@ -16,14 +16,14 @@ func init() { }) } -func initAdventure() error { +func initAdventure(plugin *common.Plugin) error { return nil } // TODO: Change the signature to return an error? -func deactivateAdventure() { +func deactivateAdventure(plugin *common.Plugin) { } -func installAdventure() error { +func installAdventure(plugin *common.Plugin) error { return nil } diff --git a/plugin_bbcode.go b/plugin_bbcode.go index 0e958dc0..8d5ca916 100644 --- a/plugin_bbcode.go +++ b/plugin_bbcode.go @@ -28,8 +28,8 @@ func init() { common.Plugins.Add(&common.Plugin{UName: "bbcode", Name: "BBCode", Author: "Azareal", URL: "https://github.com/Azareal", Init: initBbcode, Deactivate: deactivateBbcode}) } -func initBbcode() error { - common.Plugins["bbcode"].AddHook("parse_assign", bbcodeFullParse) +func initBbcode(plugin *common.Plugin) error { + plugin.AddHook("parse_assign", bbcodeFullParse) bbcodeInvalidNumber = []byte("[Invalid Number]") bbcodeNoNegative = []byte("[No Negative Numbers]") @@ -49,8 +49,8 @@ func initBbcode() error { return nil } -func deactivateBbcode() { - common.Plugins["bbcode"].RemoveHook("parse_assign", bbcodeFullParse) +func deactivateBbcode(plugin *common.Plugin) { + plugin.RemoveHook("parse_assign", bbcodeFullParse) } func bbcodeRegexParse(msg string) string { diff --git a/plugin_heythere.go b/plugin_heythere.go index 7a48ca27..80c26c7e 100644 --- a/plugin_heythere.go +++ b/plugin_heythere.go @@ -7,13 +7,13 @@ func init() { } // init_heythere is separate from init() as we don't want the plugin to run if the plugin is disabled -func initHeythere() error { - common.Plugins["heythere"].AddHook("topic_reply_row_assign", heythereReply) +func initHeythere(plugin *common.Plugin) error { + plugin.AddHook("topic_reply_row_assign", heythereReply) return nil } -func deactivateHeythere() { - common.Plugins["heythere"].RemoveHook("topic_reply_row_assign", heythereReply) +func deactivateHeythere(plugin *common.Plugin) { + plugin.RemoveHook("topic_reply_row_assign", heythereReply) } func heythereReply(data ...interface{}) interface{} { diff --git a/plugin_markdown.go b/plugin_markdown.go index 2f5126a9..cd1d0184 100644 --- a/plugin_markdown.go +++ b/plugin_markdown.go @@ -22,8 +22,8 @@ func init() { common.Plugins.Add(&common.Plugin{UName: "markdown", Name: "Markdown", Author: "Azareal", URL: "https://github.com/Azareal", Init: initMarkdown, Deactivate: deactivateMarkdown}) } -func initMarkdown() error { - common.Plugins["markdown"].AddHook("parse_assign", markdownParse) +func initMarkdown(plugin *common.Plugin) error { + plugin.AddHook("parse_assign", markdownParse) markdownUnclosedElement = []byte("[Unclosed Element]") @@ -38,8 +38,8 @@ func initMarkdown() error { return nil } -func deactivateMarkdown() { - common.Plugins["markdown"].RemoveHook("parse_assign", markdownParse) +func deactivateMarkdown(plugin *common.Plugin) { + plugin.RemoveHook("parse_assign", markdownParse) } // An adapter for the parser, so that the parser can call itself recursively. diff --git a/plugin_skeleton.go b/plugin_skeleton.go index 6f638c07..88c1560b 100644 --- a/plugin_skeleton.go +++ b/plugin_skeleton.go @@ -31,9 +31,9 @@ func init() { common.Plugins.Add(&common.Plugin{UName: "skeleton", Name: "Skeleton", Author: "Azareal", Init: initSkeleton, Activate: activateSkeleton, Deactivate: deactivateSkeleton}) } -func initSkeleton() error { return nil } +func initSkeleton(plugin *common.Plugin) error { return nil } // Any errors encountered while trying to activate the plugin are reported back to the admin and the activation is aborted -func activateSkeleton() error { return nil } +func activateSkeleton(plugin *common.Plugin) error { return nil } -func deactivateSkeleton() {} +func deactivateSkeleton(plugin *common.Plugin) {} diff --git a/plugin_test.go b/plugin_test.go index 09c3b831..9b2db4cf 100644 --- a/plugin_test.go +++ b/plugin_test.go @@ -1,7 +1,11 @@ package main -import "strconv" -import "testing" +import ( + "strconv" + "testing" + + "github.com/Azareal/Gosora/common" +) // go test -v @@ -22,7 +26,7 @@ func (tlist *MEPairList) Add(msg string, expects string) { func TestBBCodeRender(t *testing.T) { //t.Skip() - err := initBbcode() + err := initBbcode(common.Plugins["bbcode"]) if err != nil { t.Fatal(err) } @@ -211,7 +215,7 @@ func TestBBCodeRender(t *testing.T) { func TestMarkdownRender(t *testing.T) { //t.Skip() - err := initMarkdown() + err := initMarkdown(common.Plugins["markdown"]) if err != nil { t.Fatal(err) } diff --git a/pre-run-linux b/pre-run-linux index 790920ab..450c5916 100644 --- a/pre-run-linux +++ b/pre-run-linux @@ -17,6 +17,9 @@ go build -o QueryGen "./cmd/query_gen" echo "Running the query generator" ./QueryGen +echo "Generating the JSON handlers" +easyjson -pkg common + echo "Building Gosora" go build -o Gosora diff --git a/public/global.js b/public/global.js index 1db61a12..1d90c360 100644 --- a/public/global.js +++ b/public/global.js @@ -303,6 +303,39 @@ function runWebSockets() { }); })(); +// TODO: Use these in .filter_item and pass back an item count from the backend to work with here +// Ported from common/parser.go +function PageOffset(count, page, perPage) { + let offset = 0; + let lastPage = LastPage(count, perPage) + if(page > 1) { + offset = (perPage * page) - perPage + } else if (page == -1) { + page = lastPage + offset = (perPage * page) - perPage + } else { + page = 1 + } + + // We don't want the offset to overflow the slices, if everything's in memory + if(offset >= (count - 1)) offset = 0; + return {Offset:offset, Page:page, LastPage:lastPage} +} +function LastPage(count, perPage) { + return (count / perPage) + 1 +} +function Paginate(count, perPage, maxPages) { + if(count < perPage) return [1]; + let page = 0; + let out = []; + for(let current = 0; current < count; current += perPage){ + page++; + out.push(page); + if(out.length >= maxPages) break; + } + return out; +} + function mainInit(){ runInitHook("start_init"); @@ -343,9 +376,7 @@ function mainInit(){ error: ajaxError, success: function (data, status, xhr) { if("success" in data) { - if(data["success"] == "1") { - return; - } + if(data["success"] == "1") return; } // addNotice("Failed to add a like: {err}") likeButton.classList.add("add_like"); @@ -369,6 +400,102 @@ function mainInit(){ } }); + function rebuildPaginator(lastPage) { + let urlParams = new URLSearchParams(window.location.search); + let page = urlParams.get('page'); + if(page=="") page = 1; + let stopAtPage = lastPage; + if(stopAtPage>5) stopAtPage = 5; + + let pageList = []; + for(let i = 0; i < stopAtPage;i++) pageList.push(i+1); + //$(".pageset").html(Template_paginator({PageList: pageList, Page: page, LastPage: lastPage})); + let ok = false; + $(".pageset").each(function(){ + this.outerHTML = Template_paginator({PageList: pageList, Page: page, LastPage: lastPage}); + ok = true; + }); + if(!ok) { + $(Template_paginator({PageList: pageList, Page: page, LastPage: lastPage})).insertAfter("#topic_list"); + } + } + + function rebindPaginator() { + $(".pageitem a").unbind("click"); + $(".pageitem a").click(function() { + event.preventDefault(); + // TODO: Take mostviewed into account + let url = "//"+window.location.host+window.location.pathname; + let urlParams = new URLSearchParams(window.location.search); + urlParams.set("page",new URLSearchParams(this.getAttribute("href")).get("page")); + let q = "?"; + for(let item of urlParams.entries()) q += item[0]+"="+item[1]+"&"; + if(q.length>1) q = q.slice(0,-1); + + // TODO: Try to de-duplicate some of these fetch calls + fetch(url+q+"&js=1", {credentials: "same-origin"}) + .then((resp) => resp.json()) + .then((data) => { + if(!"Topics" in data) throw("no Topics in data"); + let topics = data["Topics"]; + + // TODO: Fix the data race where the function hasn't been loaded yet + let out = ""; + for(let i = 0; i < topics.length;i++) out += Template_topics_topic(topics[i]); + $(".topic_list").html(out); + + let obj = {Title: document.title, Url: url+q}; + history.pushState(obj, obj.Title, obj.Url); + rebuildPaginator(data.LastPage); + rebindPaginator(); + }).catch((ex) => { + console.log("Unable to get script '"+url+q+"&js=1"+"'"); + console.log("ex: ", ex); + console.trace(); + }); + }); + } + + // TODO: Render a headless topics.html instead of the individual topic rows and a bit of JS glue + $(".filter_item").click(function(event) { + if(!window.location.pathname.startsWith("/topics/")) return + event.preventDefault(); + let that = this; + let fid = this.getAttribute("data-fid"); + // TODO: Take mostviewed into account + let url = "//"+window.location.host+"/topics/?fids="+fid; + + fetch(url+"&js=1", {credentials: "same-origin"}) + .then((resp) => resp.json()) + .then((data) => { + if(!"Topics" in data) throw("no Topics in data"); + let topics = data["Topics"]; + + // TODO: Fix the data race where the function hasn't been loaded yet + let out = ""; + for(let i = 0; i < topics.length;i++) out += Template_topics_topic(topics[i]); + $(".topic_list").html(out); + + let obj = {Title: document.title, Url: url}; + history.pushState(obj, obj.Title, obj.Url); + rebuildPaginator(data.LastPage) + rebindPaginator(); + + $(".filter_item").each(function(){ + this.classList.remove("filter_selected"); + }); + that.classList.add("filter_selected"); + $(".topic_list_title h1").text(that.innerText); + }).catch((ex) => { + console.log("Unable to get script '"+url+"&js=1"+"'"); + console.log("ex: ", ex); + console.trace(); + }); + }); + + if (document.getElementById("topicsItemList")!==null) rebindPaginator(); + if (document.getElementById("forumItemList")!==null) rebindPaginator(); + $(".open_edit").click((event) => { event.preventDefault(); $('.hide_on_edit').addClass("edit_opened"); diff --git a/public/init.js b/public/init.js index a4f19adf..647df554 100644 --- a/public/init.js +++ b/public/init.js @@ -90,12 +90,23 @@ function loadScript(name, callback) { }); } +/* +function loadTmpl(name,callback) { + let url = "/static/"+name + let worker = new Worker(url); +} +*/ + function DoNothingButPassBack(item) { return item; } +function RelativeTime(date) { + return date; +} + function initPhrases() { - fetchPhrases("status,topic_list,alerts") + fetchPhrases("status,topic_list,alerts,paginator") } function fetchPhrases(plist) { @@ -131,6 +142,19 @@ function fetchPhrases(plist) { (() => { runInitHook("pre_iife"); + let toLoad = 2; + // TODO: Shunt this into loggedIn if there aren't any search and filter widgets? + loadScript("template_topics_topic.js", () => { + console.log("Loaded template_topics_topic.js"); + toLoad--; + if(toLoad===0) initPhrases(); + }); + loadScript("template_paginator.js", () => { + console.log("Loaded template_paginator.js"); + toLoad--; + if(toLoad===0) initPhrases(); + }); + let loggedIn = document.head.querySelector("[property='x-loggedin']").content; if(loggedIn=="true") { fetch("/api/me/") @@ -141,13 +165,6 @@ function fetchPhrases(plist) { me = data; runInitHook("pre_init"); }); - - let toLoad = 1; - loadScript("template_topics_topic.js", () => { - console.log("Loaded template_topics_topic.js"); - toLoad--; - if(toLoad===0) initPhrases(); - }); } else { me = {User:{ID:0,Session:""},Site:{"MaxRequestSize":0}}; runInitHook("pre_init"); diff --git a/query_gen/accumulator.go b/query_gen/accumulator.go index 84c70488..457c3a03 100644 --- a/query_gen/accumulator.go +++ b/query_gen/accumulator.go @@ -63,6 +63,10 @@ func (build *Accumulator) prepare(res string, err error) *sql.Stmt { return stmt } +func (build *Accumulator) RawPrepare(res string) *sql.Stmt { + return build.prepare(res, nil) +} + func (build *Accumulator) query(query string, args ...interface{}) (rows *sql.Rows, err error) { err = build.FirstError() if err != nil { diff --git a/router_gen/main.go b/router_gen/main.go index e744f3e9..6880f1bf 100644 --- a/router_gen/main.go +++ b/router_gen/main.go @@ -379,7 +379,7 @@ type GenRouter struct { } func NewGenRouter(uploads http.Handler) (*GenRouter, error) { - f, err := os.OpenFile("./logs/requests.log", os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0755) + f, err := os.OpenFile("./logs/reqs-"+strconv.FormatInt(common.StartTime.Unix(),10)+".log", os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0755) if err != nil { return nil, err } diff --git a/routes.go b/routes.go index 31eea75b..30c7595a 100644 --- a/routes.go +++ b/routes.go @@ -1,7 +1,7 @@ /* * * Gosora Route Handlers -* Copyright Azareal 2016 - 2019 +* Copyright Azareal 2016 - 2020 * */ package main @@ -138,6 +138,13 @@ var cacheControlMaxAge = "max-age=" + strconv.Itoa(int(common.Day)) // TODO: Be careful with exposing the panel phrases here, maybe move them into a different namespace? We also need to educate the admin that phrases aren't necessarily secret // TODO: Move to the routes package +var phraseWhitelist = []string{ + "topic", + "status", + "alerts", + "paginator", +} + func routeAPIPhrases(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { // TODO: Don't make this too JSON dependent so that we can swap in newer more efficient formats h := w.Header() @@ -148,7 +155,6 @@ func routeAPIPhrases(w http.ResponseWriter, r *http.Request, user common.User) c if err != nil { return common.PreErrorJS("Bad Form", w, r) } - query := r.FormValue("query") if query == "" { return common.PreErrorJS("No query provided", w, r) @@ -183,12 +189,20 @@ func routeAPIPhrases(w http.ResponseWriter, r *http.Request, user common.User) c var plist map[string]string // A little optimisation to avoid copying entries from one map to the other, if we don't have to mutate it + // TODO: Reduce the amount of duplication here if len(positives) > 1 { plist = make(map[string]string) for _, positive := range positives { - // ! Constrain it to topic and status phrases for now - if !strings.HasPrefix(positive, "topic") && !strings.HasPrefix(positive, "status") && !strings.HasPrefix(positive, "alerts") { - return common.PreErrorJS("Not implemented!", w, r) + // ! Constrain it to a subset of phrases for now + var ok = false + for _, item := range phraseWhitelist { + if strings.HasPrefix(positive, item) { + ok = true + break + } + } + if !ok { + return common.PreErrorJS("Outside of phrase prefix whitelist", w, r) } pPhrases, ok := phrases.GetTmplPhrasesByPrefix(positive) if !ok { @@ -199,9 +213,16 @@ func routeAPIPhrases(w http.ResponseWriter, r *http.Request, user common.User) c } } } else { - // ! Constrain it to topic and status phrases for now - if !strings.HasPrefix(positives[0], "topic") && !strings.HasPrefix(positives[0], "status") && !strings.HasPrefix(positives[0], "alerts") { - return common.PreErrorJS("Not implemented!", w, r) + // ! Constrain it to a subset of phrases for now + var ok = false + for _, item := range phraseWhitelist { + if strings.HasPrefix(positives[0], item) { + ok = true + break + } + } + if !ok { + return common.PreErrorJS("Outside of phrase prefix whitelist", w, r) } pPhrases, ok := phrases.GetTmplPhrasesByPrefix(positives[0]) if !ok { diff --git a/routes/forum.go b/routes/forum.go index 2bacaf3d..54da1bd0 100644 --- a/routes/forum.go +++ b/routes/forum.go @@ -2,6 +2,7 @@ package routes import ( "database/sql" + "encoding/json" "net/http" "strconv" @@ -27,6 +28,7 @@ func init() { }) } +// TODO: Retire this in favour of an alias for /topics/? func ViewForum(w http.ResponseWriter, r *http.Request, user common.User, header *common.Header, sfid string) common.RouteError { page, _ := strconv.Atoi(r.FormValue("page")) _, fid, err := ParseSEOURL(sfid) @@ -41,7 +43,6 @@ func ViewForum(w http.ResponseWriter, r *http.Request, user common.User, header if !user.Perms.ViewTopic { return common.NoPermissions(w, r, user) } - header.Zone = "view_forum" header.Path = "/forums/" // TODO: Fix this double-check @@ -108,6 +109,18 @@ func ViewForum(w http.ResponseWriter, r *http.Request, user common.User, header topicItem.Creator = userList[topicItem.CreatedBy] topicItem.LastUser = userList[topicItem.LastReplyBy] } + header.Zone = "view_forum" + header.ZoneID = forum.ID + + // TODO: Reduce the amount of boilerplate here + if r.FormValue("js") == "1" { + outBytes, err := json.Marshal(wsTopicList(topicList, lastPage)) + if err != nil { + return common.InternalError(err, w, r) + } + w.Write(outBytes) + return nil + } pageList := common.Paginate(forum.TopicCount, common.Config.ItemsPerPage, 5) pi := common.ForumPage{header, topicList, forum, common.Paginator{pageList, page, lastPage}} diff --git a/routes/misc.go b/routes/misc.go index b2bcdd01..176508e4 100644 --- a/routes/misc.go +++ b/routes/misc.go @@ -67,7 +67,7 @@ func CustomPage(w http.ResponseWriter, r *http.Request, user common.User, header } // ! Is this safe? - if common.Templates.Lookup("page_"+name+".html") == nil { + if common.DefaultTemplates.Lookup("page_"+name+".html") == nil { return common.NotFound(w, r, header) } @@ -77,7 +77,7 @@ func CustomPage(w http.ResponseWriter, r *http.Request, user common.User, header if common.RunPreRenderHook("pre_render_tmpl_page", w, r, &user, &pi) { return nil } - err = common.Templates.ExecuteTemplate(w, "page_"+name+".html", pi) + err = header.Theme.RunTmpl("page_"+name, pi, w) if err != nil { return common.InternalError(err, w, r) } diff --git a/routes/panel/analytics.go b/routes/panel/analytics.go index 4f063b4a..fbe5830e 100644 --- a/routes/panel/analytics.go +++ b/routes/panel/analytics.go @@ -154,7 +154,7 @@ func AnalyticsViews(w http.ResponseWriter, r *http.Request, user common.User) co common.DebugLogf("graph: %+v\n", graph) pi := common.PanelAnalyticsPage{basePage, graph, viewItems, timeRange.Range} - return renderTemplate("panel_analytics_views", w, r, user, &pi) + return renderTemplate("panel_analytics_views", w, r, basePage.Header, &pi) } func AnalyticsRouteViews(w http.ResponseWriter, r *http.Request, user common.User, route string) common.RouteError { @@ -191,7 +191,7 @@ func AnalyticsRouteViews(w http.ResponseWriter, r *http.Request, user common.Use common.DebugLogf("graph: %+v\n", graph) pi := common.PanelAnalyticsRoutePage{basePage, common.SanitiseSingleLine(route), graph, viewItems, timeRange.Range} - return renderTemplate("panel_analytics_route_views", w, r, user, &pi) + return renderTemplate("panel_analytics_route_views", w, r, basePage.Header, &pi) } func AnalyticsAgentViews(w http.ResponseWriter, r *http.Request, user common.User, agent string) common.RouteError { @@ -234,7 +234,7 @@ func AnalyticsAgentViews(w http.ResponseWriter, r *http.Request, user common.Use } pi := common.PanelAnalyticsAgentPage{basePage, agent, friendlyAgent, graph, timeRange.Range} - return renderTemplate("panel_analytics_agent_views", w, r, user, &pi) + return renderTemplate("panel_analytics_agent_views", w, r, basePage.Header, &pi) } func AnalyticsForumViews(w http.ResponseWriter, r *http.Request, user common.User, sfid string) common.RouteError { @@ -278,7 +278,7 @@ func AnalyticsForumViews(w http.ResponseWriter, r *http.Request, user common.Use } pi := common.PanelAnalyticsAgentPage{basePage, sfid, forum.Name, graph, timeRange.Range} - return renderTemplate("panel_analytics_forum_views", w, r, user, &pi) + return renderTemplate("panel_analytics_forum_views", w, r, basePage.Header, &pi) } func AnalyticsSystemViews(w http.ResponseWriter, r *http.Request, user common.User, system string) common.RouteError { @@ -319,7 +319,7 @@ func AnalyticsSystemViews(w http.ResponseWriter, r *http.Request, user common.Us } pi := common.PanelAnalyticsAgentPage{basePage, system, friendlySystem, graph, timeRange.Range} - return renderTemplate("panel_analytics_system_views", w, r, user, &pi) + return renderTemplate("panel_analytics_system_views", w, r, basePage.Header, &pi) } func AnalyticsLanguageViews(w http.ResponseWriter, r *http.Request, user common.User, lang string) common.RouteError { @@ -359,7 +359,7 @@ func AnalyticsLanguageViews(w http.ResponseWriter, r *http.Request, user common. } pi := common.PanelAnalyticsAgentPage{basePage, lang, friendlyLang, graph, timeRange.Range} - return renderTemplate("panel_analytics_lang_views", w, r, user, &pi) + return renderTemplate("panel_analytics_lang_views", w, r, basePage.Header, &pi) } func AnalyticsReferrerViews(w http.ResponseWriter, r *http.Request, user common.User, domain string) common.RouteError { @@ -393,7 +393,7 @@ func AnalyticsReferrerViews(w http.ResponseWriter, r *http.Request, user common. common.DebugLogf("graph: %+v\n", graph) pi := common.PanelAnalyticsAgentPage{basePage, common.SanitiseSingleLine(domain), "", graph, timeRange.Range} - return renderTemplate("panel_analytics_referrer_views", w, r, user, &pi) + return renderTemplate("panel_analytics_referrer_views", w, r, basePage.Header, &pi) } func AnalyticsTopics(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { @@ -428,7 +428,7 @@ func AnalyticsTopics(w http.ResponseWriter, r *http.Request, user common.User) c common.DebugLogf("graph: %+v\n", graph) pi := common.PanelAnalyticsPage{basePage, graph, viewItems, timeRange.Range} - return renderTemplate("panel_analytics_topics", w, r, user, &pi) + return renderTemplate("panel_analytics_topics", w, r, basePage.Header, &pi) } func AnalyticsPosts(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { @@ -463,7 +463,7 @@ func AnalyticsPosts(w http.ResponseWriter, r *http.Request, user common.User) co common.DebugLogf("graph: %+v\n", graph) pi := common.PanelAnalyticsPage{basePage, graph, viewItems, timeRange.Range} - return renderTemplate("panel_analytics_posts", w, r, user, &pi) + return renderTemplate("panel_analytics_posts", w, r, basePage.Header, &pi) } func analyticsRowsToNameMap(rows *sql.Rows) (map[string]int, error) { @@ -526,7 +526,7 @@ func AnalyticsForums(w http.ResponseWriter, r *http.Request, user common.User) c } pi := common.PanelAnalyticsAgentsPage{basePage, forumItems, timeRange.Range} - return renderTemplate("panel_analytics_forums", w, r, user, &pi) + return renderTemplate("panel_analytics_forums", w, r, basePage.Header, &pi) } func AnalyticsRoutes(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { @@ -559,7 +559,7 @@ func AnalyticsRoutes(w http.ResponseWriter, r *http.Request, user common.User) c } pi := common.PanelAnalyticsRoutesPage{basePage, routeItems, timeRange.Range} - return renderTemplate("panel_analytics_routes", w, r, user, &pi) + return renderTemplate("panel_analytics_routes", w, r, basePage.Header, &pi) } func AnalyticsAgents(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { @@ -597,7 +597,7 @@ func AnalyticsAgents(w http.ResponseWriter, r *http.Request, user common.User) c } pi := common.PanelAnalyticsAgentsPage{basePage, agentItems, timeRange.Range} - return renderTemplate("panel_analytics_agents", w, r, user, &pi) + return renderTemplate("panel_analytics_agents", w, r, basePage.Header, &pi) } func AnalyticsSystems(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { @@ -635,7 +635,7 @@ func AnalyticsSystems(w http.ResponseWriter, r *http.Request, user common.User) } pi := common.PanelAnalyticsAgentsPage{basePage, systemItems, timeRange.Range} - return renderTemplate("panel_analytics_systems", w, r, user, &pi) + return renderTemplate("panel_analytics_systems", w, r, basePage.Header, &pi) } func AnalyticsLanguages(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { @@ -674,7 +674,7 @@ func AnalyticsLanguages(w http.ResponseWriter, r *http.Request, user common.User } pi := common.PanelAnalyticsAgentsPage{basePage, langItems, timeRange.Range} - return renderTemplate("panel_analytics_langs", w, r, user, &pi) + return renderTemplate("panel_analytics_langs", w, r, basePage.Header, &pi) } func AnalyticsReferrers(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { @@ -707,5 +707,5 @@ func AnalyticsReferrers(w http.ResponseWriter, r *http.Request, user common.User } pi := common.PanelAnalyticsAgentsPage{basePage, refItems, timeRange.Range} - return renderTemplate("panel_analytics_referrers", w, r, user, &pi) + return renderTemplate("panel_analytics_referrers", w, r, basePage.Header, &pi) } diff --git a/routes/panel/backups.go b/routes/panel/backups.go index 386e967d..a2f8f289 100644 --- a/routes/panel/backups.go +++ b/routes/panel/backups.go @@ -51,5 +51,5 @@ func Backups(w http.ResponseWriter, r *http.Request, user common.User, backupURL } pi := common.PanelBackupPage{basePage, backupList} - return renderTemplate("panel_backups", w, r, user, &pi) + return renderTemplate("panel_backups", w, r, basePage.Header, &pi) } diff --git a/routes/panel/common.go b/routes/panel/common.go index ea2282c6..4d6874d7 100644 --- a/routes/panel/common.go +++ b/routes/panel/common.go @@ -21,12 +21,12 @@ func successRedirect(dest string, w http.ResponseWriter, r *http.Request, isJs b return nil } -func renderTemplate(tmplName string, w http.ResponseWriter, r *http.Request, user common.User, pi interface{}) common.RouteError { - if common.RunPreRenderHook("pre_render_"+tmplName, w, r, &user, pi) { +func renderTemplate(tmplName string, w http.ResponseWriter, r *http.Request, header *common.Header, pi interface{}) common.RouteError { + if common.RunPreRenderHook("pre_render_"+tmplName, w, r, &header.CurrentUser, pi) { return nil } // TODO: Prepend this with panel_? - err := common.Templates.ExecuteTemplate(w, tmplName+".html", pi) + err := header.Theme.RunTmpl(tmplName, pi, w) if err != nil { return common.InternalError(err, w, r) } diff --git a/routes/panel/dashboard.go b/routes/panel/dashboard.go index 63f603c9..82099a72 100644 --- a/routes/panel/dashboard.go +++ b/routes/panel/dashboard.go @@ -209,5 +209,5 @@ func Dashboard(w http.ResponseWriter, r *http.Request, user common.User) common. } pi := common.PanelDashboardPage{basePage, gridElements} - return renderTemplate("panel_dashboard", w, r, user, &pi) + return renderTemplate("panel_dashboard", w, r, basePage.Header, &pi) } diff --git a/routes/panel/debug.go b/routes/panel/debug.go index b196ba82..71eafda9 100644 --- a/routes/panel/debug.go +++ b/routes/panel/debug.go @@ -42,5 +42,5 @@ func Debug(w http.ResponseWriter, r *http.Request, user common.User) common.Rout runtime.ReadMemStats(&memStats) pi := common.PanelDebugPage{basePage, goVersion, dbVersion, uptime, openConnCount, qgen.Builder.GetAdapter().GetName(), goroutines, cpus, memStats} - return renderTemplate("panel_debug", w, r, user, &pi) + return renderTemplate("panel_debug", w, r, basePage.Header, &pi) } diff --git a/routes/panel/forums.go b/routes/panel/forums.go index 80baca19..c58cf20d 100644 --- a/routes/panel/forums.go +++ b/routes/panel/forums.go @@ -47,7 +47,7 @@ func Forums(w http.ResponseWriter, r *http.Request, user common.User) common.Rou } pi := common.PanelPage{basePage, forumList, nil} - return renderTemplate("panel_forums", w, r, user, &pi) + return renderTemplate("panel_forums", w, r, basePage.Header, &pi) } func ForumsCreateSubmit(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { @@ -96,19 +96,14 @@ func ForumsDelete(w http.ResponseWriter, r *http.Request, user common.User, sfid return common.InternalError(err, w, r) } - // TODO: Make this a phrase - confirmMsg := "Are you sure you want to delete the '" + forum.Name + "' forum?" + confirmMsg := phrases.GetTmplPhrasef("panel_forum_delete_are_you_sure", forum.Name) yousure := common.AreYouSure{"/panel/forums/delete/submit/" + strconv.Itoa(fid), confirmMsg} pi := common.PanelPage{basePage, tList, yousure} if common.RunPreRenderHook("pre_render_panel_delete_forum", w, r, &user, &pi) { return nil } - err = common.Templates.ExecuteTemplate(w, "are_you_sure.html", pi) - if err != nil { - return common.InternalError(err, w, r) - } - return nil + return renderTemplate("panel_are_you_sure", w, r, basePage.Header, &pi) } func ForumsDeleteSubmit(w http.ResponseWriter, r *http.Request, user common.User, sfid string) common.RouteError { @@ -184,15 +179,7 @@ func ForumsEdit(w http.ResponseWriter, r *http.Request, user common.User, sfid s } pi := common.PanelEditForumPage{basePage, forum.ID, forum.Name, forum.Desc, forum.Active, forum.Preset, gplist} - if common.RunPreRenderHook("pre_render_panel_edit_forum", w, r, &user, &pi) { - return nil - } - err = common.Templates.ExecuteTemplate(w, "panel_forum_edit.html", pi) - if err != nil { - return common.InternalError(err, w, r) - } - - return nil + return renderTemplate("panel_forum_edit", w, r, basePage.Header, &pi) } func ForumsEditSubmit(w http.ResponseWriter, r *http.Request, user common.User, sfid string) common.RouteError { @@ -350,15 +337,7 @@ func ForumsEditPermsAdvance(w http.ResponseWriter, r *http.Request, user common. } pi := common.PanelEditForumGroupPage{basePage, forum.ID, gid, forum.Name, forum.Desc, forum.Active, forum.Preset, formattedPermList} - if common.RunPreRenderHook("pre_render_panel_edit_forum", w, r, &user, &pi) { - return nil - } - err = common.Templates.ExecuteTemplate(w, "panel_forum_edit_perms.html", pi) - if err != nil { - return common.InternalError(err, w, r) - } - - return nil + return renderTemplate("panel_forum_edit_perms", w, r, basePage.Header, &pi) } func ForumsEditPermsAdvanceSubmit(w http.ResponseWriter, r *http.Request, user common.User, paramList string) common.RouteError { diff --git a/routes/panel/groups.go b/routes/panel/groups.go index a8afb92e..051e45d9 100644 --- a/routes/panel/groups.go +++ b/routes/panel/groups.go @@ -59,7 +59,7 @@ func Groups(w http.ResponseWriter, r *http.Request, user common.User) common.Rou pageList := common.Paginate(basePage.Stats.Groups, perPage, 5) pi := common.PanelGroupPage{basePage, groupList, common.Paginator{pageList, page, lastPage}} - return renderTemplate("panel_groups", w, r, user, &pi) + return renderTemplate("panel_groups", w, r, basePage.Header, &pi) } func GroupsEdit(w http.ResponseWriter, r *http.Request, user common.User, sgid string) common.RouteError { @@ -107,7 +107,7 @@ func GroupsEdit(w http.ResponseWriter, r *http.Request, user common.User, sgid s disableRank := !user.Perms.EditGroupGlobalPerms || (group.ID == 6) pi := common.PanelEditGroupPage{basePage, group.ID, group.Name, group.Tag, rank, disableRank} - return renderTemplate("panel_group_edit", w, r, user, pi) + return renderTemplate("panel_group_edit", w, r, basePage.Header, pi) } func GroupsEditPerms(w http.ResponseWriter, r *http.Request, user common.User, sgid string) common.RouteError { @@ -186,7 +186,7 @@ func GroupsEditPerms(w http.ResponseWriter, r *http.Request, user common.User, s addGlobalPerm("UploadFiles", group.Perms.UploadFiles) pi := common.PanelEditGroupPermsPage{basePage, group.ID, group.Name, localPerms, globalPerms} - return renderTemplate("panel_group_edit_perms", w, r, user, pi) + return renderTemplate("panel_group_edit_perms", w, r, basePage.Header, pi) } func GroupsEditSubmit(w http.ResponseWriter, r *http.Request, user common.User, sgid string) common.RouteError { diff --git a/routes/panel/logs.go b/routes/panel/logs.go index 5433cffa..b89958a6 100644 --- a/routes/panel/logs.go +++ b/routes/panel/logs.go @@ -33,7 +33,7 @@ func LogsRegs(w http.ResponseWriter, r *http.Request, user common.User) common.R pageList := common.Paginate(logCount, perPage, 5) pi := common.PanelRegLogsPage{basePage, llist, common.Paginator{pageList, page, lastPage}} - return renderTemplate("panel_reglogs", w, r, user, &pi) + return renderTemplate("panel_reglogs", w, r, basePage.Header, &pi) } // TODO: Log errors when something really screwy is going on? @@ -125,7 +125,7 @@ func LogsMod(w http.ResponseWriter, r *http.Request, user common.User) common.Ro pageList := common.Paginate(logCount, perPage, 5) pi := common.PanelLogsPage{basePage, llist, common.Paginator{pageList, page, lastPage}} - return renderTemplate("panel_modlogs", w, r, user, &pi) + return renderTemplate("panel_modlogs", w, r, basePage.Header, &pi) } func LogsAdmin(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { @@ -152,5 +152,5 @@ func LogsAdmin(w http.ResponseWriter, r *http.Request, user common.User) common. pageList := common.Paginate(logCount, perPage, 5) pi := common.PanelLogsPage{basePage, llist, common.Paginator{pageList, page, lastPage}} - return renderTemplate("panel_adminlogs", w, r, user, &pi) + return renderTemplate("panel_adminlogs", w, r, basePage.Header, &pi) } diff --git a/routes/panel/pages.go b/routes/panel/pages.go index 8dbae85a..93fa0a7a 100644 --- a/routes/panel/pages.go +++ b/routes/panel/pages.go @@ -33,7 +33,7 @@ func Pages(w http.ResponseWriter, r *http.Request, user common.User) common.Rout pageList := common.Paginate(pageCount, perPage, 5) pi := common.PanelCustomPagesPage{basePage, cPages, common.Paginator{pageList, page, lastPage}} - return renderTemplate("panel_pages", w, r, user, &pi) + return renderTemplate("panel_pages", w, r, basePage.Header, &pi) } func PagesCreateSubmit(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { @@ -90,7 +90,7 @@ func PagesEdit(w http.ResponseWriter, r *http.Request, user common.User, spid st } pi := common.PanelCustomPageEditPage{basePage, page} - return renderTemplate("panel_pages_edit", w, r, user, &pi) + return renderTemplate("panel_pages_edit", w, r, basePage.Header, &pi) } func PagesEditSubmit(w http.ResponseWriter, r *http.Request, user common.User, spid string) common.RouteError { diff --git a/routes/panel/plugins.go b/routes/panel/plugins.go index 49b0f629..54714050 100644 --- a/routes/panel/plugins.go +++ b/routes/panel/plugins.go @@ -23,7 +23,7 @@ func Plugins(w http.ResponseWriter, r *http.Request, user common.User) common.Ro } pi := common.PanelPage{basePage, pluginList, nil} - return renderTemplate("panel_plugins", w, r, user, &pi) + return renderTemplate("panel_plugins", w, r, basePage.Header, &pi) } // TODO: Abstract more of the plugin activation / installation / deactivation logic, so we can test all that more reliably and easily @@ -51,7 +51,7 @@ func PluginsActivate(w http.ResponseWriter, r *http.Request, user common.User, u } if plugin.Activate != nil { - err = plugin.Activate() + err = plugin.Activate(plugin) if err != nil { return common.LocalError(err.Error(), w, r, user) } @@ -70,7 +70,7 @@ func PluginsActivate(w http.ResponseWriter, r *http.Request, user common.User, u } log.Printf("Activating plugin '%s'", plugin.Name) - err = plugin.Init() + err = plugin.Init(plugin) if err != nil { return common.LocalError(err.Error(), w, r, user) } @@ -106,7 +106,7 @@ func PluginsDeactivate(w http.ResponseWriter, r *http.Request, user common.User, return common.InternalError(err, w, r) } if plugin.Deactivate != nil { - plugin.Deactivate() + plugin.Deactivate(plugin) } http.Redirect(w, r, "/panel/plugins/", http.StatusSeeOther) @@ -143,14 +143,14 @@ func PluginsInstall(w http.ResponseWriter, r *http.Request, user common.User, un } if plugin.Install != nil { - err = plugin.Install() + err = plugin.Install(plugin) if err != nil { return common.LocalError(err.Error(), w, r, user) } } if plugin.Activate != nil { - err = plugin.Activate() + err = plugin.Activate(plugin) if err != nil { return common.LocalError(err.Error(), w, r, user) } @@ -170,7 +170,7 @@ func PluginsInstall(w http.ResponseWriter, r *http.Request, user common.User, un } log.Printf("Installing plugin '%s'", plugin.Name) - err = plugin.Init() + err = plugin.Init(plugin) if err != nil { return common.LocalError(err.Error(), w, r, user) } diff --git a/routes/panel/settings.go b/routes/panel/settings.go index e1c9632d..acb6410d 100644 --- a/routes/panel/settings.go +++ b/routes/panel/settings.go @@ -50,7 +50,7 @@ func Settings(w http.ResponseWriter, r *http.Request, user common.User) common.R } pi := common.PanelPage{basePage, tList, settingList} - return renderTemplate("panel_settings", w, r, user, &pi) + return renderTemplate("panel_settings", w, r, basePage.Header, &pi) } func SettingEdit(w http.ResponseWriter, r *http.Request, user common.User, sname string) common.RouteError { @@ -90,7 +90,7 @@ func SettingEdit(w http.ResponseWriter, r *http.Request, user common.User, sname pSetting := &common.PanelSetting{setting, phrases.GetSettingPhrase(setting.Name)} pi := common.PanelSettingPage{basePage, itemList, pSetting} - return renderTemplate("panel_setting", w, r, user, &pi) + return renderTemplate("panel_setting", w, r, basePage.Header, &pi) } func SettingEditSubmit(w http.ResponseWriter, r *http.Request, user common.User, sname string) common.RouteError { diff --git a/routes/panel/themes.go b/routes/panel/themes.go index 53c26961..136ac29a 100644 --- a/routes/panel/themes.go +++ b/routes/panel/themes.go @@ -34,7 +34,7 @@ func Themes(w http.ResponseWriter, r *http.Request, user common.User) common.Rou } pi := common.PanelThemesPage{basePage, pThemeList, vThemeList} - return renderTemplate("panel_themes", w, r, user, &pi) + return renderTemplate("panel_themes", w, r, basePage.Header, &pi) } func ThemesSetDefault(w http.ResponseWriter, r *http.Request, user common.User, uname string) common.RouteError { @@ -86,7 +86,7 @@ func ThemesMenus(w http.ResponseWriter, r *http.Request, user common.User) commo } pi := common.PanelMenuListPage{basePage, menuList} - return renderTemplate("panel_themes_menus", w, r, user, &pi) + return renderTemplate("panel_themes_menus", w, r, basePage.Header, &pi) } func ThemesMenusEdit(w http.ResponseWriter, r *http.Request, user common.User, smid string) common.RouteError { @@ -133,7 +133,7 @@ func ThemesMenusEdit(w http.ResponseWriter, r *http.Request, user common.User, s } pi := common.PanelMenuPage{basePage, mid, menuList} - return renderTemplate("panel_themes_menus_items", w, r, user, &pi) + return renderTemplate("panel_themes_menus_items", w, r, basePage.Header, &pi) } func ThemesMenuItemEdit(w http.ResponseWriter, r *http.Request, user common.User, sitemID string) common.RouteError { @@ -159,7 +159,7 @@ func ThemesMenuItemEdit(w http.ResponseWriter, r *http.Request, user common.User } pi := common.PanelMenuItemPage{basePage, menuItem} - return renderTemplate("panel_themes_menus_item_edit", w, r, user, &pi) + return renderTemplate("panel_themes_menus_item_edit", w, r, basePage.Header, &pi) } func themesMenuItemSetters(r *http.Request, menuItem common.MenuItem) common.MenuItem { @@ -361,7 +361,7 @@ func ThemesWidgets(w http.ResponseWriter, r *http.Request, user common.User) com } pi := common.PanelWidgetListPage{basePage, docks, common.WidgetEdit{&common.Widget{ID: 0, Type: "simple"}, make(map[string]string)}} - return renderTemplate("panel_themes_widgets", w, r, user, &pi) + return renderTemplate("panel_themes_widgets", w, r, basePage.Header, &pi) } func widgetsParseInputs(r *http.Request, widget *common.Widget) (*common.WidgetEdit, error) { diff --git a/routes/panel/users.go b/routes/panel/users.go index a988591c..642c02f0 100644 --- a/routes/panel/users.go +++ b/routes/panel/users.go @@ -25,7 +25,7 @@ func Users(w http.ResponseWriter, r *http.Request, user common.User) common.Rout pageList := common.Paginate(basePage.Stats.Users, perPage, 5) pi := common.PanelUserPage{basePage, users, common.Paginator{pageList, page, lastPage}} - return renderTemplate("panel_users", w, r, user, &pi) + return renderTemplate("panel_users", w, r, basePage.Header, &pi) } func UsersEdit(w http.ResponseWriter, r *http.Request, user common.User, suid string) common.RouteError { @@ -75,14 +75,7 @@ func UsersEdit(w http.ResponseWriter, r *http.Request, user common.User, suid st } pi := common.PanelPage{basePage, groupList, targetUser} - if common.RunPreRenderHook("pre_render_panel_edit_user", w, r, &user, &pi) { - return nil - } - err = common.Templates.ExecuteTemplate(w, "panel_user_edit.html", pi) - if err != nil { - return common.InternalError(err, w, r) - } - return nil + return renderTemplate("panel_user_edit", w, r, basePage.Header, &pi) } func UsersEditSubmit(w http.ResponseWriter, r *http.Request, user common.User, suid string) common.RouteError { diff --git a/routes/panel/word_filters.go b/routes/panel/word_filters.go index dbd7a1c1..ca0cb18b 100644 --- a/routes/panel/word_filters.go +++ b/routes/panel/word_filters.go @@ -25,7 +25,7 @@ func WordFilters(w http.ResponseWriter, r *http.Request, user common.User) commo } pi := common.PanelPage{basePage, tList, filterList} - return renderTemplate("panel_word_filters", w, r, user, &pi) + return renderTemplate("panel_word_filters", w, r, basePage.Header, &pi) } func WordFiltersCreateSubmit(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { @@ -67,7 +67,7 @@ func WordFiltersEdit(w http.ResponseWriter, r *http.Request, user common.User, w _ = wfid pi := common.PanelPage{basePage, tList, nil} - return renderTemplate("panel_word_filters_edit", w, r, user, &pi) + return renderTemplate("panel_word_filters_edit", w, r, basePage.Header, &pi) } func WordFiltersEditSubmit(w http.ResponseWriter, r *http.Request, user common.User, wfid string) common.RouteError { diff --git a/routes/topic_list.go b/routes/topic_list.go index 053dc7b3..8546f4fa 100644 --- a/routes/topic_list.go +++ b/routes/topic_list.go @@ -4,17 +4,14 @@ import ( "log" "net/http" "strconv" + "strings" "github.com/Azareal/Gosora/common" "github.com/Azareal/Gosora/common/phrases" ) +// TODO: Implement search func TopicList(w http.ResponseWriter, r *http.Request, user common.User, header *common.Header) common.RouteError { - header.Title = phrases.GetTitlePhrase("topics") - header.Zone = "topics" - header.Path = "/topics/" - header.MetaDesc = header.Settings["meta_desc"].(string) - group, err := common.Groups.Get(user.Group) if err != nil { log.Printf("Group #%d doesn't exist despite being used by common.User #%d", user.Group, user.ID) @@ -23,29 +20,70 @@ func TopicList(w http.ResponseWriter, r *http.Request, user common.User, header // Get the current page page, _ := strconv.Atoi(r.FormValue("page")) + sfids := r.FormValue("fids") + var fids []int + if sfids != "" { + for _, sfid := range strings.Split(sfids, ",") { + fid, err := strconv.Atoi(sfid) + if err != nil { + return common.LocalError("Invalid fid", w, r, user) + } + fids = append(fids, fid) + } + } // TODO: Pass a struct back rather than passing back so many variables var topicList []*common.TopicsRow var forumList []common.Forum var paginator common.Paginator if user.IsSuperAdmin { - topicList, forumList, paginator, err = common.TopicList.GetList(page, "") + topicList, forumList, paginator, err = common.TopicList.GetList(page, "", fids) } else { - topicList, forumList, paginator, err = common.TopicList.GetListByGroup(group, page, "") + topicList, forumList, paginator, err = common.TopicList.GetListByGroup(group, page, "", fids) } if err != nil { return common.InternalError(err, w, r) } - // ! Need an inline error not a page level error if len(topicList) == 0 { return common.NotFound(w, r, header) } + // TODO: Reduce the amount of boilerplate here + if r.FormValue("js") == "1" { + outBytes, err := wsTopicList(topicList, paginator.LastPage).MarshalJSON() + if err != nil { + return common.InternalError(err, w, r) + } + w.Write(outBytes) + return nil + } + + header.Title = phrases.GetTitlePhrase("topics") + header.Zone = "topics" + header.Path = "/topics/" + header.MetaDesc = header.Settings["meta_desc"].(string) + if len(fids) == 1 { + forum, err := common.Forums.Get(fids[0]) + if err != nil { + return common.LocalError("Invalid fid forum", w, r, user) + } + header.Title = forum.Name + header.ZoneID = forum.ID + } + pi := common.TopicListPage{header, topicList, forumList, common.Config.DefaultForum, common.TopicListSort{"lastupdated", false}, paginator} return renderTemplate("topics", w, r, header, pi) } +func wsTopicList(topicList []*common.TopicsRow, lastPage int) *common.WsTopicList { + wsTopicList := make([]*common.WsTopicsRow, len(topicList)) + for i, topicRow := range topicList { + wsTopicList[i] = topicRow.WebSockets() + } + return &common.WsTopicList{wsTopicList, lastPage} +} + func TopicListMostViewed(w http.ResponseWriter, r *http.Request, user common.User, header *common.Header) common.RouteError { header.Title = phrases.GetTitlePhrase("topics") header.Zone = "topics" @@ -60,25 +98,54 @@ func TopicListMostViewed(w http.ResponseWriter, r *http.Request, user common.Use // Get the current page page, _ := strconv.Atoi(r.FormValue("page")) + sfids := r.FormValue("fids") + var fids []int + if sfids != "" { + for _, sfid := range strings.Split(sfids, ",") { + fid, err := strconv.Atoi(sfid) + if err != nil { + return common.LocalError("Invalid fid", w, r, user) + } + fids = append(fids, fid) + } + if len(fids) == 1 { + forum, err := common.Forums.Get(fids[0]) + if err != nil { + return common.LocalError("Invalid fid forum", w, r, user) + } + header.Title = forum.Name + header.ZoneID = forum.ID + } + } // TODO: Pass a struct back rather than passing back so many variables var topicList []*common.TopicsRow var forumList []common.Forum var paginator common.Paginator if user.IsSuperAdmin { - topicList, forumList, paginator, err = common.TopicList.GetList(page, "most-viewed") + topicList, forumList, paginator, err = common.TopicList.GetList(page, "most-viewed", fids) } else { - topicList, forumList, paginator, err = common.TopicList.GetListByGroup(group, page, "most-viewed") + topicList, forumList, paginator, err = common.TopicList.GetListByGroup(group, page, "most-viewed", fids) } if err != nil { return common.InternalError(err, w, r) } - // ! Need an inline error not a page level error if len(topicList) == 0 { return common.NotFound(w, r, header) } + //MarshalJSON() ([]byte, error) + // TODO: Reduce the amount of boilerplate here + if r.FormValue("js") == "1" { + outBytes, err := wsTopicList(topicList, paginator.LastPage).MarshalJSON() + if err != nil { + return common.InternalError(err, w, r) + } + w.Write(outBytes) + return nil + } + pi := common.TopicListPage{header, topicList, forumList, common.Config.DefaultForum, common.TopicListSort{"mostviewed", false}, paginator} return renderTemplate("topics", w, r, header, pi) } diff --git a/run-linux b/run-linux index 8036b39a..ce5c2be1 100644 --- a/run-linux +++ b/run-linux @@ -17,6 +17,9 @@ go build -o QueryGen "./cmd/query_gen" echo "Running the query generator" ./QueryGen +echo "Generating the JSON handlers" +easyjson -pkg common + echo "Building Gosora" go build -o Gosora diff --git a/run-linux-nowebsockets b/run-linux-nowebsockets index d713d08f..5d7251df 100644 --- a/run-linux-nowebsockets +++ b/run-linux-nowebsockets @@ -23,6 +23,9 @@ cd ../.. echo "Running the query generator" ./QueryGen +echo "Generating the JSON handlers" +easyjson -pkg common + echo "Building Gosora" go build -o Gosora -tags no_ws diff --git a/run-linux-tests b/run-linux-tests index 88739519..dbf25a02 100644 --- a/run-linux-tests +++ b/run-linux-tests @@ -1,5 +1,7 @@ echo "Generating the dynamic code" go generate +echo Generating the JSON handlers +easyjson -pkg common echo "Running tests" go build -o mssqlBuild -tags mssql go test -coverprofile c.out diff --git a/run-nowebsockets.bat b/run-nowebsockets.bat index 5043ac4d..bc9ebb9a 100644 --- a/run-nowebsockets.bat +++ b/run-nowebsockets.bat @@ -40,6 +40,9 @@ if %errorlevel% neq 0 ( exit /b %errorlevel% ) +echo Generating the JSON handlers +easyjson -pkg common + echo Building the executable go build -o gosora.exe -tags no_ws if %errorlevel% neq 0 ( diff --git a/run.bat b/run.bat index 5b541ad7..936504f1 100644 --- a/run.bat +++ b/run.bat @@ -40,6 +40,9 @@ if %errorlevel% neq 0 ( exit /b %errorlevel% ) +echo Generating the JSON handlers +easyjson -pkg common + echo Building the executable go build -o gosora.exe if %errorlevel% neq 0 ( diff --git a/run_mssql.bat b/run_mssql.bat index 81e8ebcd..ed712f5d 100644 --- a/run_mssql.bat +++ b/run_mssql.bat @@ -40,6 +40,9 @@ if %errorlevel% neq 0 ( exit /b %errorlevel% ) +echo Generating the JSON handlers +easyjson -pkg common + echo Building the executable go build -o gosora.exe -tags mssql if %errorlevel% neq 0 ( diff --git a/run_tests.bat b/run_tests.bat index 49d48f23..18d8368b 100644 --- a/run_tests.bat +++ b/run_tests.bat @@ -40,6 +40,9 @@ if %errorlevel% neq 0 ( exit /b %errorlevel% ) +echo Generating the JSON handlers +easyjson -pkg common + echo Building the executable go test if %errorlevel% neq 0 ( diff --git a/run_tests_mssql.bat b/run_tests_mssql.bat index 3d9753a4..943690c5 100644 --- a/run_tests_mssql.bat +++ b/run_tests_mssql.bat @@ -40,6 +40,9 @@ if %errorlevel% neq 0 ( exit /b %errorlevel% ) +echo Generating the JSON handlers +easyjson -pkg common + echo Building the executable go test -tags mssql if %errorlevel% neq 0 ( diff --git a/templates/account_logins.html b/templates/account_logins.html index ddcf2bfc..c567f8bb 100644 --- a/templates/account_logins.html +++ b/templates/account_logins.html @@ -15,6 +15,4 @@ {{end}} -{{if gt .LastPage 1}} -{{template "paginator.html" . }} -{{end}} \ No newline at end of file +{{template "paginator.html" . }} \ No newline at end of file diff --git a/templates/forum.html b/templates/forum.html index 47809f78..1beff9c7 100644 --- a/templates/forum.html +++ b/templates/forum.html @@ -1,7 +1,7 @@ {{template "header.html" . }} -{{if gt .Page 1}}
{{end}} -{{if ne .LastPage .Page}}{{end}} +{{if gt .Page 1}}{{end}} +{{if ne .LastPage .Page}}{{end}}