From d9acf27c5b0c84be4a0270fddeabea68cb63e38f Mon Sep 17 00:00:00 2001 From: Azareal Date: Sun, 10 Feb 2019 15:52:26 +1000 Subject: [PATCH] The Search and Filter Widget is now partly implemented. Just Search to go in the basic implementation. Added AJAX Pagination for the Topic List and Forum Page. A new log file pair is now created every-time Gosora starts up. Added proper per-theme template overrides. Added EasyJSON to make JSON serialisation faster. Moved a bit of boilerplate into paginator.html Improved paginator.html with a richer template with first, last and symbols instead of text. Phased out direct access to Templates.ExecuteTemplate across the software. Fixed the Live Topic List so it should work again. Added MicroAvatar to WsJSONUser for topic list JSON requests. An instance of the plugin is now passed to plugin handlers rather than having the plugins manipulate the globals directly. Added the pre_render_panel_forum_edit and pre_render_panel_forum_edit_perms hooks to replace pre_render_panel_edit_forum. Renamed the pre_render_panel_edit_user hook to pre_render_panel_user_edit Reduced the amount of noise from fsnotify. Added RawPrepare() to qgen.Accumulator. Added a temporary phrase whitelist to the phrase endpoint. Moved the location of the zone data assignments in the topic list to reduce the chances of security issues in the future. Changed the signature of routes/panel/renderTemplate() requiring some changes across the panel routes. Removed bits of boilerplate in some of the panel routes with renderTemplate() Added a BenchmarkTopicsGuestJSRouteParallelWithRouter benchmark. Removed a fair bit of boilerplate for each page struct by generating a couple of interface casts for each template file instead. Added the profile_comments_row_alt template. Added the topics_quick_topic template to reuse part of the quick topic logic for both the topic list and forum page. Tweaked the CSS for the Online Users Widget. Tweaked the CSS for Widgets in every theme with a sidebar. Refactored the template initialisers to hopefully reduce the amount of boilerplate and make things easier to maintain and follow. Add genIntTmpl in the template initialiser file to reduce the amount of boilerplate needed for the fallback template bindings. Removed the topics_head phrase. Moved the paginator_ phrases into the paginator. namespace and renamed them accordingly. Added the paginator.first_page phrase. Added the paginator.first_page_aria phrase. Added the paginator.last_page phrase. Added the paginator.last_page_aria phrase. Added the panel_forum_delete_are_you_sure phrase. Fixed a data race in LogWarning() --- build-linux | 3 + build-linux-nowebsockets | 3 + build-nowebsockets.bat | 3 + build.bat | 3 + common/errors.go | 6 +- common/extend.go | 25 +- common/files.go | 28 +- common/module_ottojs.go | 2 +- common/parser.go | 3 + common/phrases/phrases.go | 2 +- common/search.go | 72 +++ common/template_init.go | 546 +++++++++--------- common/templates/context.go | 1 + common/templates/templates.go | 134 ++++- common/theme.go | 205 ++----- common/theme_list.go | 129 ++--- common/topic.go | 2 +- common/topic_list.go | 60 +- common/user.go | 21 +- common/websockets.go | 4 +- common/widget_search_and_filter.go | 13 +- common/widgets.go | 2 +- common/ws_hub.go | 10 +- docs/installation.md | 4 + extend/guilds/lib/guilds.go | 6 +- extend/guilds/plugin_guilds.go | 32 +- gen_router.go | 2 +- general_test.go | 14 +- go.mod | 1 + go.sum | 2 + langs/english.json | 18 +- main.go | 19 +- misc_test.go | 12 +- plugin_adventure.go | 6 +- plugin_bbcode.go | 8 +- plugin_heythere.go | 8 +- plugin_markdown.go | 8 +- plugin_skeleton.go | 6 +- plugin_test.go | 12 +- pre-run-linux | 3 + public/global.js | 133 ++++- public/init.js | 33 +- query_gen/accumulator.go | 4 + router_gen/main.go | 2 +- routes.go | 37 +- routes/forum.go | 15 +- routes/misc.go | 4 +- routes/panel/analytics.go | 30 +- routes/panel/backups.go | 2 +- routes/panel/common.go | 6 +- routes/panel/dashboard.go | 2 +- routes/panel/debug.go | 2 +- routes/panel/forums.go | 31 +- routes/panel/groups.go | 6 +- routes/panel/logs.go | 6 +- routes/panel/pages.go | 4 +- routes/panel/plugins.go | 14 +- routes/panel/settings.go | 4 +- routes/panel/themes.go | 10 +- routes/panel/users.go | 11 +- routes/panel/word_filters.go | 4 +- routes/topic_list.go | 89 ++- run-linux | 3 + run-linux-nowebsockets | 3 + run-linux-tests | 2 + run-nowebsockets.bat | 3 + run.bat | 3 + run_mssql.bat | 3 + run_tests.bat | 3 + run_tests_mssql.bat | 3 + templates/account_logins.html | 4 +- templates/forum.html | 27 +- templates/paginator.html | 10 +- templates/panel_adminlogs.html | 2 - templates/panel_are_you_sure.html | 15 + templates/panel_groups.html | 3 +- templates/panel_modlogs.html | 2 - templates/panel_reglogs.html | 2 - templates/panel_themes_widgets_widget.html | 2 +- templates/panel_users.html | 2 - templates/profile_comments_row.html | 25 +- templates/profile_comments_row_alt.html | 24 + templates/topic.html | 4 +- templates/topic_alt.html | 6 +- templates/topics.html | 32 +- templates/topics_quick_topic.html | 26 + templates/widget_search_and_filter.html | 2 +- templates/widget_simple.html | 4 +- themes/cosora/public/main.css | 6 + .../nox/overrides/profile_comments_row.html | 1 + themes/nox/public/main.css | 42 +- themes/tempra-simple/public/main.css | 7 + 92 files changed, 1287 insertions(+), 871 deletions(-) create mode 100644 common/search.go create mode 100644 templates/panel_are_you_sure.html create mode 100644 templates/profile_comments_row_alt.html create mode 100644 templates/topics_quick_topic.html create mode 100644 themes/nox/overrides/profile_comments_row.html 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}}
@@ -40,25 +40,7 @@ -
-
- -
-
-
-
Poll stuff
-
-
-
- - - {{if .CurrentUser.Perms.UploadFiles}} - - -
{{end}} - -
-
+ {{template "topics_quick_topic.html" . }} {{end}} @@ -100,9 +82,6 @@ {{else}}
{{lang "forum_no_topics"}}{{if .CurrentUser.Loggedin}}{{if .CurrentUser.Perms.CreateTopic}} {{lang "forum_start_one"}}{{end}}{{end}}
{{end}} -{{if gt .LastPage 1}} {{template "paginator.html" . }} -{{end}} -
{{template "footer.html" . }} diff --git a/templates/paginator.html b/templates/paginator.html index de85940e..dca2e4d9 100644 --- a/templates/paginator.html +++ b/templates/paginator.html @@ -1,10 +1,14 @@ +{{if gt .LastPage 1}}
- {{if gt .Page 1}} + {{if gt .Page 1}} + {{end}} {{range .PageList}} {{end}} {{if ne .LastPage .Page}} - {{end}} -
\ No newline at end of file +
+
{{end}} + +{{end}} \ No newline at end of file diff --git a/templates/panel_adminlogs.html b/templates/panel_adminlogs.html index ed0b7a47..ddd6e7a3 100644 --- a/templates/panel_adminlogs.html +++ b/templates/panel_adminlogs.html @@ -19,9 +19,7 @@ {{end}} - {{if gt .LastPage 1}} {{template "paginator.html" . }} - {{end}} {{template "footer.html" . }} diff --git a/templates/panel_are_you_sure.html b/templates/panel_are_you_sure.html new file mode 100644 index 00000000..19da61e4 --- /dev/null +++ b/templates/panel_are_you_sure.html @@ -0,0 +1,15 @@ +{{template "header.html" . }} +
+{{template "panel_menu.html" . }} +
+
+

{{lang "areyousure_head"}}

+
+
+
{{.Something.Message}}

+ {{lang "areyousure_continue"}} +
+
+
+
+{{template "footer.html" . }} diff --git a/templates/panel_groups.html b/templates/panel_groups.html index 89229ee9..4983e4e5 100644 --- a/templates/panel_groups.html +++ b/templates/panel_groups.html @@ -19,9 +19,8 @@ {{end}} - {{if gt .LastPage 1}} {{template "paginator.html" . }} - {{end}} + {{if .CurrentUser.Perms.EditGroup}}

{{lang "panel_groups_create_head"}}

diff --git a/templates/panel_modlogs.html b/templates/panel_modlogs.html index 5d9c7aa0..39de8a90 100644 --- a/templates/panel_modlogs.html +++ b/templates/panel_modlogs.html @@ -19,9 +19,7 @@
{{end}} - {{if gt .LastPage 1}} {{template "paginator.html" . }} - {{end}} {{template "footer.html" . }} diff --git a/templates/panel_reglogs.html b/templates/panel_reglogs.html index 6a1d2bcf..b53032ea 100644 --- a/templates/panel_reglogs.html +++ b/templates/panel_reglogs.html @@ -19,9 +19,7 @@ {{end}} - {{if gt .LastPage 1}} {{template "paginator.html" . }} - {{end}} {{template "footer.html" . }} diff --git a/templates/panel_themes_widgets_widget.html b/templates/panel_themes_widgets_widget.html index d75bdbc1..50f67aea 100644 --- a/templates/panel_themes_widgets_widget.html +++ b/templates/panel_themes_widgets_widget.html @@ -6,7 +6,7 @@ - + diff --git a/templates/panel_users.html b/templates/panel_users.html index b820d1bb..7445086b 100644 --- a/templates/panel_users.html +++ b/templates/panel_users.html @@ -20,9 +20,7 @@ {{end}} - {{if gt .LastPage 1}} {{template "paginator.html" . }} - {{end}} diff --git a/templates/profile_comments_row.html b/templates/profile_comments_row.html index db2a97e7..31d06ae7 100644 --- a/templates/profile_comments_row.html +++ b/templates/profile_comments_row.html @@ -17,28 +17,5 @@ {{end}} {{else}} - {{range .ItemList}} -
-
-
- {{.CreatedByName}}'s Avatar - - {{.CreatedByName}} - {{if .Tag}}{{.Tag}}{{end}} - -
- - {{if $.CurrentUser.IsMod}} - - - {{end}} - - -
-
- {{.ContentHtml}} -
-
-
- {{end}} +{{template "profile_comments_row_alt.html" . }} {{end}} \ No newline at end of file diff --git a/templates/profile_comments_row_alt.html b/templates/profile_comments_row_alt.html new file mode 100644 index 00000000..4fb7b084 --- /dev/null +++ b/templates/profile_comments_row_alt.html @@ -0,0 +1,24 @@ +{{range .ItemList}} +
+
+
+ {{.CreatedByName}}'s Avatar + + {{.CreatedByName}} + {{if .Tag}}{{.Tag}}{{end}} + +
+ + {{if $.CurrentUser.IsMod}} + + + {{end}} + + +
+
+ {{.ContentHtml}} +
+
+
+ {{end}} \ No newline at end of file diff --git a/templates/topic.html b/templates/topic.html index d0507177..40a72d94 100644 --- a/templates/topic.html +++ b/templates/topic.html @@ -2,11 +2,11 @@
{{if gt .Page 1}} -
{{end}} +
{{end}} {{if ne .LastPage .Page}}
- +
{{end}} diff --git a/templates/topic_alt.html b/templates/topic_alt.html index dfabe5b1..7d28f970 100644 --- a/templates/topic_alt.html +++ b/templates/topic_alt.html @@ -1,10 +1,10 @@ {{template "header.html" . }} {{if gt .Page 1}} -
{{end}} +
{{end}} {{if ne .LastPage .Page}} -
{{end}} +
{{end}} @@ -111,9 +111,7 @@ {{template "topic_alt_posts.html" . }} -{{if gt .LastPage 1}} {{template "paginator.html" . }} -{{end}} {{if .CurrentUser.Loggedin}} {{if .CurrentUser.Perms.CreateReply}} diff --git a/templates/topics.html b/templates/topics.html index 659061d8..e2ce15b8 100644 --- a/templates/topics.html +++ b/templates/topics.html @@ -3,7 +3,7 @@
-

{{lang "topics_head"}}

+

{{.Title}}

{{if .CurrentUser.Loggedin}}
{{if .ForumList}} @@ -55,7 +55,6 @@
-
-
- -
-
-
-
-
- - - -
-
-
-
-
- - - {{if .CurrentUser.Perms.UploadFiles}} - - -
{{end}} - -
-
+ {{template "topics_quick_topic.html" . }}
{{end}} @@ -106,9 +81,6 @@ {{range .TopicList}}{{template "topics_topic.html" . }}{{else}}
{{lang "topics_no_topics"}}{{if .CurrentUser.Loggedin}}{{if .CurrentUser.Perms.CreateTopic}} {{lang "topics_start_one"}}{{end}}{{end}}
{{end}} -{{if gt .LastPage 1}} {{template "paginator.html" . }} -{{end}} -
{{template "footer.html" . }} diff --git a/templates/topics_quick_topic.html b/templates/topics_quick_topic.html new file mode 100644 index 00000000..2d511ed4 --- /dev/null +++ b/templates/topics_quick_topic.html @@ -0,0 +1,26 @@ + +
+
+ +
+
+
+
+
+ + + +
+
+
+
+
+ + + {{if .CurrentUser.Perms.UploadFiles}} + + +
{{end}} + +
+
\ No newline at end of file diff --git a/templates/widget_search_and_filter.html b/templates/widget_search_and_filter.html index a78531cb..f61551e9 100644 --- a/templates/widget_search_and_filter.html +++ b/templates/widget_search_and_filter.html @@ -2,6 +2,6 @@
-{{range .Forums}}
{{.Name}}
+{{range .Forums}} {{end}}
\ No newline at end of file diff --git a/templates/widget_simple.html b/templates/widget_simple.html index 3cdfb6a8..7e33e986 100644 --- a/templates/widget_simple.html +++ b/templates/widget_simple.html @@ -1,6 +1,6 @@ -
+

{{.Name}}

-
+
{{.Text}}
diff --git a/themes/cosora/public/main.css b/themes/cosora/public/main.css index 7e1c8324..b0d9b727 100644 --- a/themes/cosora/public/main.css +++ b/themes/cosora/public/main.css @@ -1415,6 +1415,12 @@ textarea { .pageitem:last-child { border-right: 1px solid var(--element-border-color); } +.pagefirst, .pagenext, .pageprev, .pagelast { + padding-top: 5px; +} +.pagefirst a, .pagenext a, .pageprev a, .pagelast a { + font-size: 18px; +} /* TODO: Make widget_about's CSS less footer centric */ .footerBit, .footer .widget { diff --git a/themes/nox/overrides/profile_comments_row.html b/themes/nox/overrides/profile_comments_row.html new file mode 100644 index 00000000..0a1160d2 --- /dev/null +++ b/themes/nox/overrides/profile_comments_row.html @@ -0,0 +1 @@ +{{template "profile_comments_row_alt.html" . }} \ No newline at end of file diff --git a/themes/nox/public/main.css b/themes/nox/public/main.css index f49ab36c..ea82ca7d 100644 --- a/themes/nox/public/main.css +++ b/themes/nox/public/main.css @@ -156,6 +156,11 @@ li a { .sidebar { width: 320px; } +.widget_simple .rowitem { + line-height: 18px; + padding-top: 14px !important; + padding-bottom: 14px !important; +} .the_form { border-radius: 3px; background-color: #444444; @@ -193,6 +198,9 @@ li a { margin-bottom: 5px; padding: 4px; } +.filter_item a { + color: #BBBBBB; +} .colstack_right .colstack_item:not(.colstack_head):not(.rowhead) .rowitem:not(:last-child) { margin-bottom: 8px; } @@ -214,6 +222,22 @@ h1, h2, h3, h4, h5 { white-space: nowrap; } +/* new */ +.filter_list { + margin-top: 5px; + background-color: #444444; + margin-left: 12px; + border-radius: 3px; +} +.filter_item { + margin-left: 0px !important; +} +.filter_selected { + background-color: #555555 !important; + border-radius: 0px !important; +} +/* new end */ + @keyframes fadein { from { opacity: 0; } to { opacity: 1; } @@ -586,7 +610,6 @@ button, .formbutton, .panel_right_button:not(.has_inner_button) { display: flex; margin-top: 8px; } - .pageitem { font-size: 17px; border-radius: 3px; @@ -594,6 +617,13 @@ button, .formbutton, .panel_right_button:not(.has_inner_button) { padding: 7px; margin-right: 6px; } +.pagefirst, .pagenext, .pageprev, .pagelast { + padding-top: 2px; + padding-bottom: 6px; +} +.pagefirst a, .pagenext a, .pageprev a, .pagelast a { + font-size: 22px; +} #prevFloat, #nextFloat { display: none; @@ -934,13 +964,15 @@ input[type=checkbox]:checked + label .sel { margin-top: 4px; } .rowlist.bgavatars.not_grid .bgsub { - height: 36px; - width: 36px; - margin-left: 8px; - margin-right: 12px; + height: 28px; + width: 28px; + margin-left: 4px; + margin-right: 10px; } .rowlist.bgavatars.not_grid .rowTitle { + font-size: 17px; margin-left: 0px; + margin-top: 0px; } .ip_search_block { diff --git a/themes/tempra-simple/public/main.css b/themes/tempra-simple/public/main.css index eebb61c5..c887f899 100644 --- a/themes/tempra-simple/public/main.css +++ b/themes/tempra-simple/public/main.css @@ -890,6 +890,13 @@ input[type=checkbox]:checked + label.poll_option_label .sel { text-decoration: none; } +.sidebar .rowhead:not(:first-child) { + margin-top: 12px; +} +.widget_search { + margin-bottom: 8px; +} + #profile_comments .rowitem { background-repeat: no-repeat, repeat-y; background-size: 128px;