diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6e838550..eb8eb1ec 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -38,7 +38,9 @@ Always use strict mode. Don't worry about ES5, we're targetting modern browsers. If we decide to backport code to older browsers, then we'll transpile the files. -Please don't use await. It incurs too much of a cognitive overhead as to where and when you can use it. +Please don't use await. It incurs too much of a cognitive overhead as to where and when you can use it. We can't use it everywhere quite yet, which means that we really should be using it nowhere. + +Please don't abuse `const` just to shave off a few nanoseconds. Even in the Go server where I care about performance the most, I don't use const everywhere, only in about five spots in thirty thousand lines and I don't use it for performance at all there. To keep consistency with Go code, variables must be camelCase. diff --git a/cmd/query_gen/tables.go b/cmd/query_gen/tables.go index 123f4472..397e57f6 100644 --- a/cmd/query_gen/tables.go +++ b/cmd/query_gen/tables.go @@ -114,8 +114,7 @@ func createTables(adapter qgen.Adapter) error { tblColumn{"issued_by","int",0,false,false,""}, tblColumn{"issued_at","createdAt",0,false,false,""}, tblColumn{"expires_at","datetime",0,false,false,""}, - }, - []tblKey{}, + }, nil, )*/ qgen.Install.CreateTable("users_groups_scheduler", "", "", @@ -150,8 +149,7 @@ func createTables(adapter qgen.Adapter) error { tblColumn{"uid", "int", 0, false, false, ""}, // TODO: Make this a foreign key tblColumn{"validated", "boolean", 0, false, false, "0"}, tblColumn{"token", "varchar", 200, false, false, "''"}, - }, - []tblKey{}, + }, nil, ) // TODO: Allow for patterns in domains, if the bots try to shake things up there? @@ -167,6 +165,19 @@ func createTables(adapter qgen.Adapter) error { ) */ + // TODO: Implement password resets + /*qgen.Install.CreateTable("password_resets", "", "", + []tblColumn{ + tblColumn{"email", "varchar", 200, false, false, ""}, + tblColumn{"uid", "int", 0, false, false, ""}, // TODO: Make this a foreign key + tblColumn{"validated", "varchar", 200, false, false, ""}, // Token given once the one-use token is consumed, used to prevent multiple people consuming the same one-use token + tblColumn{"token", "varchar", 200, false, false, ""}, + }, + []tblKey{ + tblKey{"email", "unique"}, + }, + )*/ + qgen.Install.CreateTable("forums", mysqlPre, mysqlCol, []tblColumn{ tblColumn{"fid", "int", 0, false, true, ""}, @@ -305,8 +316,7 @@ func createTables(adapter qgen.Adapter) error { tblColumn{"pollID", "int", 0, false, false, ""}, tblColumn{"option", "int", 0, false, false, "0"}, tblColumn{"votes", "int", 0, false, false, "0"}, - }, - []tblKey{}, + }, nil, ) qgen.Install.CreateTable("polls_votes", mysqlPre, mysqlCol, @@ -316,8 +326,7 @@ func createTables(adapter qgen.Adapter) error { tblColumn{"option", "int", 0, false, false, "0"}, tblColumn{"castAt", "createdAt", 0, false, false, ""}, tblColumn{"ipaddress", "varchar", 200, false, false, "0.0.0.0.0"}, - }, - []tblKey{}, + }, nil, ) qgen.Install.CreateTable("users_replies", mysqlPre, mysqlCol, @@ -345,16 +354,14 @@ func createTables(adapter qgen.Adapter) error { tblColumn{"sentBy", "int", 0, false, false, ""}, // TODO: Make this a foreign key tblColumn{"createdAt", "createdAt", 0, false, false, ""}, tblColumn{"recalc", "tinyint", 0, false, false, "0"}, - }, - []tblKey{}, + }, nil, ) qgen.Install.CreateTable("activity_stream_matches", "", "", []tblColumn{ tblColumn{"watcher", "int", 0, false, false, ""}, // TODO: Make this a foreign key tblColumn{"asid", "int", 0, false, false, ""}, // TODO: Make this a foreign key - }, - []tblKey{}, + }, nil, ) qgen.Install.CreateTable("activity_stream", "", "", @@ -377,8 +384,7 @@ func createTables(adapter qgen.Adapter) error { tblColumn{"targetID", "int", 0, false, false, ""}, /* the ID of the element being acted upon */ tblColumn{"targetType", "varchar", 50, false, false, ""}, /* topic, post (calling it post here to differentiate it from the 'reply' event), forum, user */ tblColumn{"level", "int", 0, false, false, "0"}, /* 0: Mentions (aka the global default for any post), 1: Replies To You, 2: All Replies*/ - }, - []tblKey{}, + }, nil, ) /* Due to MySQL's design, we have to drop the unique keys for table settings, plugins, and themes down from 200 to 180 or it will error */ @@ -428,6 +434,7 @@ func createTables(adapter qgen.Adapter) error { qgen.Install.CreateTable("widgets", "", "", []tblColumn{ + tblColumn{"wid", "int", 0, false, true, ""}, tblColumn{"position", "int", 0, false, false, ""}, tblColumn{"side", "varchar", 100, false, false, ""}, tblColumn{"type", "varchar", 100, false, false, ""}, @@ -435,7 +442,9 @@ func createTables(adapter qgen.Adapter) error { tblColumn{"location", "varchar", 100, false, false, ""}, tblColumn{"data", "text", 0, false, false, "''"}, }, - []tblKey{}, + []tblKey{ + tblKey{"wid", "primary"}, + }, ) qgen.Install.CreateTable("menus", "", "", @@ -523,8 +532,7 @@ func createTables(adapter qgen.Adapter) error { tblColumn{"ipaddress", "varchar", 200, false, false, ""}, tblColumn{"actorID", "int", 0, false, false, ""}, // TODO: Make this a foreign key tblColumn{"doneAt", "datetime", 0, false, false, ""}, - }, - []tblKey{}, + }, nil, ) qgen.Install.CreateTable("administration_logs", "", "", @@ -535,8 +543,7 @@ func createTables(adapter qgen.Adapter) error { tblColumn{"ipaddress", "varchar", 200, false, false, ""}, tblColumn{"actorID", "int", 0, false, false, ""}, // TODO: Make this a foreign key tblColumn{"doneAt", "datetime", 0, false, false, ""}, - }, - []tblKey{}, + }, nil, ) qgen.Install.CreateTable("viewchunks", "", "", @@ -544,8 +551,7 @@ func createTables(adapter qgen.Adapter) error { tblColumn{"count", "int", 0, false, false, "0"}, tblColumn{"createdAt", "datetime", 0, false, false, ""}, tblColumn{"route", "varchar", 200, false, false, ""}, - }, - []tblKey{}, + }, nil, ) qgen.Install.CreateTable("viewchunks_agents", "", "", @@ -554,8 +560,7 @@ func createTables(adapter qgen.Adapter) error { tblColumn{"createdAt", "datetime", 0, false, false, ""}, tblColumn{"browser", "varchar", 200, false, false, ""}, // googlebot, firefox, opera, etc. //tblColumn{"version","varchar",0,false,false,""}, // the version of the browser or bot - }, - []tblKey{}, + }, nil, ) qgen.Install.CreateTable("viewchunks_systems", "", "", @@ -563,8 +568,7 @@ func createTables(adapter qgen.Adapter) error { tblColumn{"count", "int", 0, false, false, "0"}, tblColumn{"createdAt", "datetime", 0, false, false, ""}, tblColumn{"system", "varchar", 200, false, false, ""}, // windows, android, unknown, etc. - }, - []tblKey{}, + }, nil, ) qgen.Install.CreateTable("viewchunks_langs", "", "", @@ -572,8 +576,7 @@ func createTables(adapter qgen.Adapter) error { tblColumn{"count", "int", 0, false, false, "0"}, tblColumn{"createdAt", "datetime", 0, false, false, ""}, tblColumn{"lang", "varchar", 200, false, false, ""}, // en, ru, etc. - }, - []tblKey{}, + }, nil, ) qgen.Install.CreateTable("viewchunks_referrers", "", "", @@ -581,8 +584,7 @@ func createTables(adapter qgen.Adapter) error { tblColumn{"count", "int", 0, false, false, "0"}, tblColumn{"createdAt", "datetime", 0, false, false, ""}, tblColumn{"domain", "varchar", 200, false, false, ""}, - }, - []tblKey{}, + }, nil, ) qgen.Install.CreateTable("viewchunks_forums", "", "", @@ -590,8 +592,7 @@ func createTables(adapter qgen.Adapter) error { tblColumn{"count", "int", 0, false, false, "0"}, tblColumn{"createdAt", "datetime", 0, false, false, ""}, tblColumn{"forum", "int", 0, false, false, ""}, - }, - []tblKey{}, + }, nil, ) qgen.Install.CreateTable("topicchunks", "", "", @@ -599,8 +600,7 @@ func createTables(adapter qgen.Adapter) error { tblColumn{"count", "int", 0, false, false, "0"}, tblColumn{"createdAt", "datetime", 0, false, false, ""}, // TODO: Add a column for the parent forum? - }, - []tblKey{}, + }, nil, ) qgen.Install.CreateTable("postchunks", "", "", @@ -608,22 +608,19 @@ func createTables(adapter qgen.Adapter) error { tblColumn{"count", "int", 0, false, false, "0"}, tblColumn{"createdAt", "datetime", 0, false, false, ""}, // TODO: Add a column for the parent topic / profile? - }, - []tblKey{}, + }, nil, ) qgen.Install.CreateTable("sync", "", "", []tblColumn{ tblColumn{"last_update", "datetime", 0, false, false, ""}, - }, - []tblKey{}, + }, nil, ) qgen.Install.CreateTable("updates", "", "", []tblColumn{ tblColumn{"dbVersion", "int", 0, false, false, "0"}, - }, - []tblKey{}, + }, nil, ) return nil diff --git a/common/common.go b/common/common.go index e4055280..3ebc81ab 100644 --- a/common/common.go +++ b/common/common.go @@ -1,7 +1,7 @@ /* * * Gosora Common Resources -* Copyright Azareal 2018 - 2019 +* Copyright Azareal 2018 - 2020 * */ package common // import "github.com/Azareal/Gosora/common" diff --git a/common/errors.go b/common/errors.go index 07977c6e..90dda12a 100644 --- a/common/errors.go +++ b/common/errors.go @@ -128,6 +128,7 @@ func LogWarning(err error, extra ...string) { func errorHeader(w http.ResponseWriter, user User, title string) *Header { header := DefaultHeader(w, user) header.Title = title + header.Zone = "error" return header } @@ -160,7 +161,7 @@ func InternalErrorJS(err error, w http.ResponseWriter, r *http.Request) RouteErr return HandledRouteError() } -// When the task system detects if the database is down, some database errors might lip by this +// When the task system detects if the database is down, some database errors might slip by this func DatabaseError(w http.ResponseWriter, r *http.Request) RouteError { w.WriteHeader(500) pi := ErrorPage{errorHeader(w, GuestUser, phrases.GetErrorPhrase("internal_error_title")), phrases.GetErrorPhrase("internal_error_body")} @@ -285,10 +286,7 @@ func LoginRequiredJSQ(w http.ResponseWriter, r *http.Request, user User, isJs bo // ? - Where is this used? Should we use it more? // LoginRequired is an error shown to the end-user when they try to access an area which requires them to login func LoginRequired(w http.ResponseWriter, r *http.Request, user User) RouteError { - w.WriteHeader(401) - pi := ErrorPage{errorHeader(w, user, phrases.GetErrorPhrase("no_permissions_title")), phrases.GetErrorPhrase("login_required_body")} - handleErrorTemplate(w, r, pi) - return HandledRouteError() + return CustomError(phrases.GetErrorPhrase("login_required_body"), 401, phrases.GetErrorPhrase("no_permissions_title"), w, r, nil, user) } // nolint @@ -343,6 +341,7 @@ func CustomError(errmsg string, errcode int, errtitle string, w http.ResponseWri header = DefaultHeader(w, user) } header.Title = errtitle + header.Zone = "error" w.WriteHeader(errcode) pi := ErrorPage{header, errmsg} handleErrorTemplate(w, r, pi) diff --git a/common/forum_store.go b/common/forum_store.go index fd87b4a7..88661ecb 100644 --- a/common/forum_store.go +++ b/common/forum_store.go @@ -1,7 +1,7 @@ /* * * Gosora Forum Store -* Copyright Azareal 2017 - 2018 +* Copyright Azareal 2017 - 2019 * */ package common diff --git a/common/module_ottojs.go b/common/module_ottojs.go index ed6c9f3e..bd0e4718 100644 --- a/common/module_ottojs.go +++ b/common/module_ottojs.go @@ -1,7 +1,7 @@ /* * * OttoJS Plugin Module -* Copyright Azareal 2016 - 2018 +* Copyright Azareal 2016 - 2019 * */ package common diff --git a/common/pages.go b/common/pages.go index 5ba0ae10..cddd3a97 100644 --- a/common/pages.go +++ b/common/pages.go @@ -28,6 +28,8 @@ type Header struct { CurrentUser User // TODO: Deprecate CurrentUser on the page structs and use a pointer here Hooks *HookTable Zone string + ZoneID int + ZoneData interface{} Path string MetaDesc string StartedAt time.Time @@ -326,6 +328,12 @@ type PanelMenuListPage struct { ItemList []PanelMenuListItem } +type PanelWidgetListPage struct { + *BasePanelPage + Docks map[string][]WidgetEdit + BlankWidget WidgetEdit +} + type PanelMenuPage struct { *BasePanelPage MenuID int @@ -463,3 +471,6 @@ type AreYouSure struct { func DefaultHeader(w http.ResponseWriter, user User) *Header { return &Header{Site: Site, Theme: Themes[fallbackTheme], CurrentUser: user, Writer: w} } +func SimpleDefaultHeader(w http.ResponseWriter) *Header { + return &Header{Site: Site, Theme: Themes[fallbackTheme], CurrentUser: GuestUser, Writer: w} +} diff --git a/common/phrases/phrases.go b/common/phrases/phrases.go index 134fd42c..aaa2e79b 100644 --- a/common/phrases/phrases.go +++ b/common/phrases/phrases.go @@ -37,7 +37,9 @@ type LevelPhrases struct { // ! For the sake of thread safety, you must never modify a *LanguagePack directly, but to create a copy of it and overwrite the entry in the sync.Map type LanguagePack struct { - Name string + Name string + IsoCode string + // Should we use a sync map or a struct for these? It would be nice, if we could keep all the phrases consistent. Levels LevelPhrases GlobalPerms map[string]string diff --git a/common/reply.go b/common/reply.go index fdf3558c..a718d0e1 100644 --- a/common/reply.go +++ b/common/reply.go @@ -1,7 +1,7 @@ /* * * Reply Resources File -* Copyright Azareal 2016 - 2018 +* Copyright Azareal 2016 - 2019 * */ package common diff --git a/common/tasks.go b/common/tasks.go index 97e39c96..5416a60e 100644 --- a/common/tasks.go +++ b/common/tasks.go @@ -1,7 +1,7 @@ /* * * Gosora Task System -* Copyright Azareal 2017 - 2018 +* Copyright Azareal 2017 - 2019 * */ package common diff --git a/common/template_init.go b/common/template_init.go index 2786c060..cb9b76e3 100644 --- a/common/template_init.go +++ b/common/template_init.go @@ -470,6 +470,11 @@ func CompileJSTemplates() error { 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 { + return err + }*/ var dirPrefix = "./tmpl_client/" var wg sync.WaitGroup @@ -492,6 +497,7 @@ func CompileJSTemplates() error { writeTemplate("topics_topic", topicListItemTmpl) writeTemplate("topic_posts", topicPostsTmpl) writeTemplate("topic_alt_posts", topicAltPostsTmpl) + //writeTemplate("panel_themes_widgets_widget", panelWidgetsWidgetTmpl) writeTemplateList(c, &wg, dirPrefix) return nil } diff --git a/common/templates/templates.go b/common/templates/templates.go index d8801008..0805fe7b 100644 --- a/common/templates/templates.go +++ b/common/templates/templates.go @@ -94,6 +94,7 @@ func NewCTemplateSet() *CTemplateSet { "reltime": true, "scope": true, "dyntmpl": true, + "index": true, }, } } diff --git a/common/topic.go b/common/topic.go index 75a29410..70410c0d 100644 --- a/common/topic.go +++ b/common/topic.go @@ -1,7 +1,7 @@ /* * * Gosora Topic File -* Copyright Azareal 2017 - 2018 +* Copyright Azareal 2017 - 2019 * */ package common diff --git a/common/utils.go b/common/utils.go index f4e1ccc9..8a6c2032 100644 --- a/common/utils.go +++ b/common/utils.go @@ -1,7 +1,7 @@ /* * * Utility Functions And Stuff -* Copyright Azareal 2017 - 2018 +* Copyright Azareal 2017 - 2019 * */ package common diff --git a/common/websockets.go b/common/websockets.go index 686cdc54..a961a647 100644 --- a/common/websockets.go +++ b/common/websockets.go @@ -15,13 +15,14 @@ import ( "net/http" "runtime" "strconv" + "strings" "sync" "time" + "github.com/Azareal/Gosora/common/phrases" "github.com/Azareal/gopsutil/cpu" "github.com/Azareal/gopsutil/mem" "github.com/gorilla/websocket" - "github.com/Azareal/Gosora/common/phrases" ) // TODO: Disable WebSockets on high load? Add a Control Panel interface for disabling it? @@ -97,6 +98,16 @@ func RouteWebsockets(w http.ResponseWriter, r *http.Request, user User) RouteErr return nil } +// TODO: Copied from routes package for use in wsPageResponse, find a more elegant solution. +func ParseSEOURL(urlBit string) (slug string, id int, err error) { + halves := strings.Split(urlBit, ".") + if len(halves) < 2 { + halves = append(halves, halves[0]) + } + tid, err := strconv.Atoi(halves[1]) + return halves[0], tid, err +} + // TODO: Use a map instead of a switch to make this more modular? func wsPageResponses(wsUser *WSUser, conn *websocket.Conn, page string) { if page == "/" { @@ -104,14 +115,47 @@ func wsPageResponses(wsUser *WSUser, conn *websocket.Conn, page string) { } DebugLog("Entering page " + page) - switch page { + switch { // Live Topic List is an experimental feature // TODO: Optimise this to reduce the amount of contention - case "/topics/": + case page == "/topics/": topicListMutex.Lock() topicListWatchers[wsUser] = true topicListMutex.Unlock() - case "/panel/": + case strings.HasPrefix(page, "/topic/"): + //fmt.Println("entering topic prefix websockets zone") + _, tid, err := ParseSEOURL(page) + if err != nil { + return + } + topic, err := Topics.Get(tid) + if err != nil { + return + } + var usercpy *User = BlankUser() + *usercpy = *wsUser.User + usercpy.Init() + + if !Forums.Exists(topic.ParentID) { + return + } + + /*skip, rerr := header.Hooks.VhookSkippable("ws_topic_check_pre_perms", w, r, usercpy, &fid, &header) + if skip || rerr != nil { + return + }*/ + + fperms, err := FPStore.Get(topic.ParentID, usercpy.Group) + if err == ErrNoRows { + fperms = BlankForumPerms() + } else if err != nil { + return + } + cascadeForumPerms(fperms, usercpy) + if !usercpy.Perms.ViewTopic { + return + } + case page == "/panel/": if !wsUser.User.IsSuperMod { return } @@ -138,15 +182,19 @@ func wsLeavePage(wsUser *WSUser, conn *websocket.Conn, page string) { page = Config.DefaultPath } - DebugLog("Leaving page " + page) - switch page { - case "/topics/": + if page != "" { + DebugLog("Leaving page " + page) + } + switch { + case page == "/topics/": wsUser.FinalizePage("/topics/", func() { topicListMutex.Lock() delete(topicListWatchers, wsUser) topicListMutex.Unlock() }) - case "/panel/": + case strings.HasPrefix(page, "/topic/"): + //fmt.Println("leaving topic prefix websockets zone") + case page == "/panel/": adminStatsMutex.Lock() delete(adminStatsWatchers, conn) adminStatsMutex.Unlock() diff --git a/common/widget.go b/common/widget.go new file mode 100644 index 00000000..6c332df5 --- /dev/null +++ b/common/widget.go @@ -0,0 +1,148 @@ +package common + +import ( + "database/sql" + "encoding/json" + "strings" + "sync/atomic" + + "github.com/Azareal/Gosora/query_gen" +) + +type WidgetStmts struct { + //getList *sql.Stmt + getDockList *sql.Stmt + delete *sql.Stmt + create *sql.Stmt + update *sql.Stmt +} + +var widgetStmts WidgetStmts + +func init() { + DbInits.Add(func(acc *qgen.Accumulator) error { + widgetStmts = WidgetStmts{ + //getList: acc.Select("widgets").Columns("wid, position, side, type, active, location, data").Orderby("position ASC").Prepare(), + getDockList: acc.Select("widgets").Columns("wid, position, type, active, location, data").Where("side = ?").Orderby("position ASC").Prepare(), + delete: acc.Delete("widgets").Where("wid = ?").Prepare(), + create: acc.Insert("widgets").Columns("position, side, type, active, location, data").Fields("?,?,?,?,?,?").Prepare(), + update: acc.Update("widgets").Set("position = ?, side = ?, type = ?, active = ?, location = ?, data = ?").Where("wid = ?").Prepare(), + } + return acc.FirstError() + }) +} + +// TODO: Shrink this struct for common uses in the templates? Would that really make things go faster? +type Widget struct { + ID int + Enabled bool + Location string // Coming Soon: overview, topics, topic / topic_view, forums, forum, global + Position int + RawBody string + Body string + Side string + Type string + + Literal bool + TickMask atomic.Value + InitFunc func(widget *Widget, schedule *WidgetScheduler) error + ShutdownFunc func(widget *Widget) error + BuildFunc func(widget *Widget, hvars interface{}) (string, error) + TickFunc func(widget *Widget) error +} + +func (widget *Widget) Delete() error { + _, err := widgetStmts.delete.Exec(widget.ID) + if err != nil { + return err + } + + // Reload the dock + // TODO: Better synchronisation + Widgets.delete(widget.ID) + widgets, err := getDockWidgets(widget.Side) + if err != nil { + return err + } + setDock(widget.Side, widgets) + return nil +} + +func (widget *Widget) Copy() (owidget *Widget) { + owidget = &Widget{} + *owidget = *widget + return owidget +} + +// TODO: Test this +// TODO: Add support for zone:id. Perhaps, carry a ZoneID property around in *Header? It might allow some weirdness like frontend[5] which matches any zone with an ID of 5 but it would be a tad faster than verifying each zone, although it might be problematic if users end up relying on this behaviour for areas which don't pass IDs to the widgets system but *probably* should +func (widget *Widget) Allowed(zone string) bool { + for _, loc := range strings.Split(widget.Location, "|") { + if loc == "global" || loc == zone { + return true + } else if len(loc) > 0 && loc[0] == '!' { + loc = loc[1:] + if loc != "global" && loc != zone { + return true + } + } + } + return false +} + +// TODO: Refactor +func (widget *Widget) Build(hvars interface{}) (string, error) { + if widget.Literal { + return widget.Body, nil + } + if widget.BuildFunc != nil { + return widget.BuildFunc(widget, hvars) + } + + var header = hvars.(*Header) + err := header.Theme.RunTmpl(widget.Body, hvars, header.Writer) + return "", err +} + +type WidgetEdit struct { + *Widget + Data map[string]string +} + +func (widget *WidgetEdit) Create() error { + data, err := json.Marshal(widget.Data) + if err != nil { + return err + } + _, err = widgetStmts.create.Exec(widget.Position, widget.Side, widget.Type, widget.Enabled, widget.Location, data) + if err != nil { + return err + } + + // Reload the dock + widgets, err := getDockWidgets(widget.Side) + if err != nil { + return err + } + setDock(widget.Side, widgets) + return nil +} + +func (widget *WidgetEdit) Commit() error { + data, err := json.Marshal(widget.Data) + if err != nil { + return err + } + _, err = widgetStmts.update.Exec(widget.Position, widget.Side, widget.Type, widget.Enabled, widget.Location, data, widget.ID) + if err != nil { + return err + } + + // Reload the dock + widgets, err := getDockWidgets(widget.Side) + if err != nil { + return err + } + setDock(widget.Side, widgets) + return nil +} diff --git a/common/widget_search_and_filter.go b/common/widget_search_and_filter.go new file mode 100644 index 00000000..69a23aa3 --- /dev/null +++ b/common/widget_search_and_filter.go @@ -0,0 +1,41 @@ +package common + +import "errors" + +// TODO: Move this into it's own package to make neater and tidier +type searchAndFilter struct { + *Header + Forums []*Forum +} + +func widgetSearchAndFilter(widget *Widget, hvars interface{}) (out string, err error) { + header := hvars.(*Header) + user := header.CurrentUser + + var forums []*Forum + var canSee []int + if user.IsSuperAdmin { + canSee, err = Forums.GetAllVisibleIDs() + if err != nil { + return "", err + } + } else { + group, err := Groups.Get(user.Group) + if err != nil { + // TODO: Revisit this + return "", errors.New("Something weird happened") + } + canSee = group.CanSee + } + + for _, fid := range canSee { + forum := Forums.DirtyGet(fid) + if forum.ParentID == 0 && forum.Name != "" && forum.Active { + forums = append(forums, forum) + } + } + + 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/widget_store.go b/common/widget_store.go new file mode 100644 index 00000000..36fd3900 --- /dev/null +++ b/common/widget_store.go @@ -0,0 +1,39 @@ +package common + +import ( + "database/sql" + "sync" +) + +var Widgets *DefaultWidgetStore + +type DefaultWidgetStore struct { + widgets map[int]*Widget + sync.RWMutex +} + +func NewDefaultWidgetStore() *DefaultWidgetStore { + return &DefaultWidgetStore{widgets: make(map[int]*Widget)} +} + +func (widgets *DefaultWidgetStore) Get(id int) (*Widget, error) { + widgets.RLock() + defer widgets.RUnlock() + widget, ok := widgets.widgets[id] + if !ok { + return widget, sql.ErrNoRows + } + return widget, nil +} + +func (widgets *DefaultWidgetStore) set(widget *Widget) { + widgets.Lock() + defer widgets.Unlock() + widgets.widgets[widget.ID] = widget +} + +func (widgets *DefaultWidgetStore) delete(id int) { + widgets.Lock() + defer widgets.Unlock() + delete(widgets.widgets, id) +} diff --git a/common/widget_wol.go b/common/widget_wol.go new file mode 100644 index 00000000..b5a6271e --- /dev/null +++ b/common/widget_wol.go @@ -0,0 +1,55 @@ +package common + +import ( + "bytes" + "net/http/httptest" + + "github.com/Azareal/Gosora/common/phrases" +) + +type wolUsers struct { + *Header + Name string + Users []*User + UserCount int +} + +func wolInit(widget *Widget, schedule *WidgetScheduler) error { + schedule.Add(widget) + return nil +} + +func wolBuild(widget *Widget, hvars interface{}) (string, error) { + ucount := WsHub.UserCount() + // We don't want a ridiculously long list, so we'll show the number if it's too high and only show staff individually + var users []*User + if ucount < 30 { + users = WsHub.AllUsers() + } + wol := &wolUsers{hvars.(*Header), phrases.GetTmplPhrase("widget.online_name"), users, ucount} + err := wol.Header.Theme.RunTmpl("widget_online", wol, wol.Header.Writer) + return "", err +} + +func wolRender(widget *Widget, hvars interface{}) (string, error) { + iTickMask := widget.TickMask.Load() + if iTickMask != nil { + tickMask := iTickMask.(*Widget) + if tickMask != nil { + return tickMask.Body, nil + } + } + return wolBuild(widget, hvars) +} + +func wolTick(widget *Widget) error { + w := httptest.NewRecorder() + _, err := wolBuild(widget, SimpleDefaultHeader(w)) + if err != nil { + return err + } + buf := new(bytes.Buffer) + buf.ReadFrom(w.Result().Body) + widget.TickMask.Store(buf.String()) + return nil +} diff --git a/common/widget_wol_context.go b/common/widget_wol_context.go new file mode 100644 index 00000000..4d886da3 --- /dev/null +++ b/common/widget_wol_context.go @@ -0,0 +1,15 @@ +package common + +import "github.com/Azareal/Gosora/common/phrases" + +func wolContextRender(widget *Widget, hvars interface{}) (string, error) { + ucount := WsHub.UserCount() + // We don't want a ridiculously long list, so we'll show the number if it's too high and only show staff individually + var users []*User + if ucount < 30 { + users = WsHub.AllUsers() + } + wol := &wolUsers{hvars.(*Header), phrases.GetTmplPhrase("widget.online_name"), users, ucount} + err := wol.Header.Theme.RunTmpl("widget_online", wol, wol.Header.Writer) + return "", err +} diff --git a/common/widgets.go b/common/widgets.go index de90f155..7f91205f 100644 --- a/common/widgets.go +++ b/common/widgets.go @@ -1,37 +1,32 @@ -/* Copyright Azareal 2017 - 2018 */ +/* Copyright Azareal 2017 - 2019 */ package common import ( "bytes" - "database/sql" "encoding/json" + "fmt" "html/template" "strings" "sync" - - "github.com/Azareal/Gosora/query_gen" + "sync/atomic" ) +// TODO: Clean this file up var Docks WidgetDocks var widgetUpdateMutex sync.RWMutex +type WidgetDock struct { + Items []*Widget + Scheduler *WidgetScheduler +} + type WidgetDocks struct { LeftOfNav []*Widget RightOfNav []*Widget - LeftSidebar []*Widget - RightSidebar []*Widget + LeftSidebar WidgetDock + RightSidebar WidgetDock //PanelLeft []Menus - Footer []*Widget -} - -type Widget struct { - Enabled bool - Location string // Coming Soon: overview, topics, topic / topic_view, forums, forum, global - Position int - Body string - Side string - Type string - Literal bool + Footer WidgetDock } type WidgetMenu struct { @@ -50,21 +45,6 @@ type NameTextPair struct { Text template.HTML } -type WidgetStmts struct { - getWidgets *sql.Stmt -} - -var widgetStmts WidgetStmts - -func init() { - DbInits.Add(func(acc *qgen.Accumulator) error { - widgetStmts = WidgetStmts{ - getWidgets: acc.Select("widgets").Columns("position, side, type, active, location, data").Orderby("position ASC").Prepare(), - } - return acc.FirstError() - }) -} - func preparseWidget(widget *Widget, wdata string) (err error) { prebuildWidget := func(name string, data interface{}) (string, error) { var b bytes.Buffer @@ -73,25 +53,30 @@ func preparseWidget(widget *Widget, wdata string) (err error) { } sbytes := []byte(wdata) + widget.Literal = true + // TODO: Split these hard-coded items out of this file and into the files for the individual widget types switch widget.Type { - case "simple": + case "simple", "about": var tmp NameTextPair err = json.Unmarshal(sbytes, &tmp) if err != nil { return err } - widget.Body, err = prebuildWidget("widget_simple", tmp) - case "about": - var tmp NameTextPair - err = json.Unmarshal(sbytes, &tmp) - if err != nil { - return err - } - widget.Body, err = prebuildWidget("widget_about", tmp) + widget.Body, err = prebuildWidget("widget_"+widget.Type, tmp) + case "search_and_filter": + widget.Literal = false + widget.BuildFunc = widgetSearchAndFilter + case "wol": + widget.Literal = false + widget.InitFunc = wolInit + widget.BuildFunc = wolRender + widget.TickFunc = wolTick + case "wol_context": + widget.Literal = false + widget.BuildFunc = wolContextRender default: widget.Body = wdata } - widget.Literal = true // TODO: Test this // TODO: Should we toss this through a proper parser rather than crudely replacing it? @@ -115,6 +100,37 @@ func preparseWidget(widget *Widget, wdata string) (err error) { return err } +func GetDockList() []string { + return []string{ + "leftOfNav", + "rightOfNav", + "rightSidebar", + "footer", + } +} + +func GetDock(dock string) []*Widget { + switch dock { + case "leftOfNav": + return Docks.LeftOfNav + case "rightOfNav": + return Docks.RightOfNav + case "rightSidebar": + return Docks.RightSidebar.Items + case "footer": + return Docks.Footer.Items + } + return nil +} + +func HasDock(dock string) bool { + switch dock { + case "leftOfNav", "rightOfNav", "rightSidebar", "footer": + return true + } + return false +} + func BuildWidget(dock string, header *Header) (sbody string) { var widgets []*Widget if !header.Theme.HasDock(dock) { @@ -143,9 +159,9 @@ func BuildWidget(dock string, header *Header) (sbody string) { } return "" case "rightSidebar": - widgets = Docks.RightSidebar + widgets = Docks.RightSidebar.Items case "footer": - widgets = Docks.Footer + widgets = Docks.Footer.Items } for _, widget := range widgets { @@ -163,93 +179,138 @@ func BuildWidget(dock string, header *Header) (sbody string) { return sbody } -// TODO: Test this -// TODO: Add support for zone:id. Perhaps, carry a ZoneID property around in *Header? It might allow some weirdness like frontend[5] which matches any zone with an ID of 5 but it would be a tad faster than verifying each zone, although it might be problematic if users end up relying on this behaviour for areas which don't pass IDs to the widgets system but *probably* should -func (widget *Widget) Allowed(zone string) bool { - for _, loc := range strings.Split(widget.Location, "|") { - if loc == "global" || loc == zone { - return true - } else if len(loc) > 0 && loc[0] == '!' { - loc = loc[1:] - if loc != "global" && loc != zone { - return true - } +func getDockWidgets(dock string) (widgets []*Widget, err error) { + rows, err := widgetStmts.getDockList.Query(dock) + if err != nil { + return nil, err + } + defer rows.Close() + + for rows.Next() { + var widget = &Widget{Position: 0, Side: dock} + err = rows.Scan(&widget.ID, &widget.Position, &widget.Type, &widget.Enabled, &widget.Location, &widget.RawBody) + if err != nil { + return nil, err } - } - return false -} -// TODO: Refactor -func (widget *Widget) Build(hvars interface{}) (string, error) { - if widget.Literal { - return widget.Body, nil + err = preparseWidget(widget, widget.RawBody) + if err != nil { + return nil, err + } + Widgets.set(widget) + widgets = append(widgets, widget) } - - var header = hvars.(*Header) - err := header.Theme.RunTmpl(widget.Body, hvars, header.Writer) - return "", err + return widgets, rows.Err() } // TODO: Make a store for this? func InitWidgets() error { - rows, err := widgetStmts.getWidgets.Query() + leftOfNavWidgets, err := getDockWidgets("leftOfNav") if err != nil { return err } - defer rows.Close() - - var data string - var leftOfNavWidgets []*Widget - var rightOfNavWidgets []*Widget - var leftSidebarWidgets []*Widget - var rightSidebarWidgets []*Widget - var footerWidgets []*Widget - - for rows.Next() { - var widget = &Widget{Position: 0} - err = rows.Scan(&widget.Position, &widget.Side, &widget.Type, &widget.Enabled, &widget.Location, &data) - if err != nil { - return err - } - - err = preparseWidget(widget, data) - if err != nil { - return err - } - - switch widget.Side { - case "leftOfNav": - leftOfNavWidgets = append(leftOfNavWidgets, widget) - case "rightOfNav": - rightOfNavWidgets = append(rightOfNavWidgets, widget) - case "left": - leftSidebarWidgets = append(leftSidebarWidgets, widget) - case "right": - rightSidebarWidgets = append(rightSidebarWidgets, widget) - case "footer": - footerWidgets = append(footerWidgets, widget) - } + rightOfNavWidgets, err := getDockWidgets("rightOfNav") + if err != nil { + return err } - err = rows.Err() + leftSidebarWidgets, err := getDockWidgets("leftSidebar") + if err != nil { + return err + } + rightSidebarWidgets, err := getDockWidgets("rightSidebar") + if err != nil { + return err + } + footerWidgets, err := getDockWidgets("footer") if err != nil { return err } // TODO: Let themes set default values for widget docks, and let them lock in particular places with their stuff, e.g. leftOfNav and rightOfNav - widgetUpdateMutex.Lock() - Docks.LeftOfNav = leftOfNavWidgets - Docks.RightOfNav = rightOfNavWidgets - Docks.LeftSidebar = leftSidebarWidgets - Docks.RightSidebar = rightSidebarWidgets - Docks.Footer = footerWidgets - widgetUpdateMutex.Unlock() - - DebugLog("Docks.LeftOfNav", Docks.LeftOfNav) - DebugLog("Docks.RightOfNav", Docks.RightOfNav) - DebugLog("Docks.LeftSidebar", Docks.LeftSidebar) - DebugLog("Docks.RightSidebar", Docks.RightSidebar) - DebugLog("Docks.Footer", Docks.Footer) + setDock("leftOfNav", leftOfNavWidgets) + setDock("rightOfNav", rightOfNavWidgets) + setDock("leftSidebar", leftSidebarWidgets) + setDock("rightSidebar", rightSidebarWidgets) + setDock("footer", footerWidgets) + AddScheduledSecondTask(Docks.LeftSidebar.Scheduler.Tick) + AddScheduledSecondTask(Docks.RightSidebar.Scheduler.Tick) + AddScheduledSecondTask(Docks.Footer.Scheduler.Tick) return nil } + +func releaseWidgets(widgets []*Widget) { + for _, widget := range widgets { + if widget.ShutdownFunc != nil { + widget.ShutdownFunc(widget) + } + } +} + +// TODO: Use atomics +func setDock(dock string, widgets []*Widget) { + var dockHandle = func(dockWidgets []*Widget) { + widgetUpdateMutex.Lock() + DebugLog(dock, widgets) + releaseWidgets(dockWidgets) + } + var dockHandle2 = func(dockWidgets WidgetDock) WidgetDock { + dockHandle(dockWidgets.Items) + if dockWidgets.Scheduler == nil { + dockWidgets.Scheduler = &WidgetScheduler{} + } + for _, widget := range widgets { + if widget.InitFunc != nil { + widget.InitFunc(widget, dockWidgets.Scheduler) + } + } + dockWidgets.Scheduler.Store() + return WidgetDock{widgets, dockWidgets.Scheduler} + } + switch dock { + case "leftOfNav": + dockHandle(Docks.LeftOfNav) + Docks.LeftOfNav = widgets + case "rightOfNav": + dockHandle(Docks.RightOfNav) + Docks.RightOfNav = widgets + case "leftSidebar": + Docks.LeftSidebar = dockHandle2(Docks.LeftSidebar) + case "rightSidebar": + Docks.RightSidebar = dockHandle2(Docks.RightSidebar) + case "footer": + Docks.Footer = dockHandle2(Docks.Footer) + default: + fmt.Printf("bad dock '%s'\n", dock) + return + } + widgetUpdateMutex.Unlock() +} + +type WidgetScheduler struct { + widgets []*Widget + store atomic.Value +} + +func (schedule *WidgetScheduler) Add(widget *Widget) { + schedule.widgets = append(schedule.widgets, widget) +} + +func (schedule *WidgetScheduler) Store() { + schedule.store.Store(schedule.widgets) +} + +func (schedule *WidgetScheduler) Tick() error { + widgets := schedule.store.Load().([]*Widget) + for _, widget := range widgets { + if widget.TickFunc == nil { + continue + } + err := widget.TickFunc(widget.Copy()) + if err != nil { + return err + } + } + return nil +} diff --git a/common/ws_hub.go b/common/ws_hub.go index a20c9a2a..7d009292 100644 --- a/common/ws_hub.go +++ b/common/ws_hub.go @@ -251,24 +251,36 @@ func (hub *WsHubImpl) getUsers(uids []int) (wsUsers []*WSUser, err error) { if len(uids) == 0 { return nil, errWsNouser } - hub.evenUserLock.RLock() - // We don't want to keep a lock on this for too long, so we'll accept some nil pointers - for _, uid := range uids { - wsUsers = append(wsUsers, hub.evenOnlineUsers[uid]) + var appender = func(lock *sync.RWMutex, users map[int]*WSUser) { + lock.RLock() + defer lock.RUnlock() + // We don't want to keep a lock on this for too long, so we'll accept some nil pointers + for _, uid := range uids { + wsUsers = append(wsUsers, users[uid]) + } } - hub.evenUserLock.RUnlock() - hub.oddUserLock.RLock() - // We don't want to keep a lock on this for too long, so we'll accept some nil pointers - for _, uid := range uids { - wsUsers = append(wsUsers, hub.oddOnlineUsers[uid]) - } - hub.oddUserLock.RUnlock() + appender(&hub.evenUserLock, hub.evenOnlineUsers) + appender(&hub.oddUserLock, hub.oddOnlineUsers) if len(wsUsers) == 0 { return nil, errWsNouser } return wsUsers, nil } +// For Widget WOL, please avoid using this as it might wind up being really long and slow without the right safeguards +func (hub *WsHubImpl) AllUsers() (users []*User) { + var appender = func(lock *sync.RWMutex, userMap map[int]*WSUser) { + lock.RLock() + defer lock.RUnlock() + for _, user := range userMap { + users = append(users, user.User) + } + } + appender(&hub.evenUserLock, hub.evenOnlineUsers) + appender(&hub.oddUserLock, hub.oddOnlineUsers) + return users +} + func (hub *WsHubImpl) removeUser(uid int) { if uid%2 == 0 { hub.evenUserLock.Lock() diff --git a/gen_router.go b/gen_router.go index 354e77ab..4d6634f7 100644 --- a/gen_router.go +++ b/gen_router.go @@ -69,6 +69,10 @@ var RouteMap = map[string]interface{}{ "panel.ThemesMenuItemCreateSubmit": panel.ThemesMenuItemCreateSubmit, "panel.ThemesMenuItemDeleteSubmit": panel.ThemesMenuItemDeleteSubmit, "panel.ThemesMenuItemOrderSubmit": panel.ThemesMenuItemOrderSubmit, + "panel.ThemesWidgets": panel.ThemesWidgets, + "panel.ThemesWidgetsEditSubmit": panel.ThemesWidgetsEditSubmit, + "panel.ThemesWidgetsCreateSubmit": panel.ThemesWidgetsCreateSubmit, + "panel.ThemesWidgetsDeleteSubmit": panel.ThemesWidgetsDeleteSubmit, "panel.Plugins": panel.Plugins, "panel.PluginsActivate": panel.PluginsActivate, "panel.PluginsDeactivate": panel.PluginsDeactivate, @@ -206,93 +210,97 @@ var routeMapEnum = map[string]int{ "panel.ThemesMenuItemCreateSubmit": 43, "panel.ThemesMenuItemDeleteSubmit": 44, "panel.ThemesMenuItemOrderSubmit": 45, - "panel.Plugins": 46, - "panel.PluginsActivate": 47, - "panel.PluginsDeactivate": 48, - "panel.PluginsInstall": 49, - "panel.Users": 50, - "panel.UsersEdit": 51, - "panel.UsersEditSubmit": 52, - "panel.AnalyticsViews": 53, - "panel.AnalyticsRoutes": 54, - "panel.AnalyticsAgents": 55, - "panel.AnalyticsSystems": 56, - "panel.AnalyticsLanguages": 57, - "panel.AnalyticsReferrers": 58, - "panel.AnalyticsRouteViews": 59, - "panel.AnalyticsAgentViews": 60, - "panel.AnalyticsForumViews": 61, - "panel.AnalyticsSystemViews": 62, - "panel.AnalyticsLanguageViews": 63, - "panel.AnalyticsReferrerViews": 64, - "panel.AnalyticsPosts": 65, - "panel.AnalyticsTopics": 66, - "panel.AnalyticsForums": 67, - "panel.Groups": 68, - "panel.GroupsEdit": 69, - "panel.GroupsEditPerms": 70, - "panel.GroupsEditSubmit": 71, - "panel.GroupsEditPermsSubmit": 72, - "panel.GroupsCreateSubmit": 73, - "panel.Backups": 74, - "panel.LogsRegs": 75, - "panel.LogsMod": 76, - "panel.Debug": 77, - "panel.Dashboard": 78, - "routes.AccountEdit": 79, - "routes.AccountEditPassword": 80, - "routes.AccountEditPasswordSubmit": 81, - "routes.AccountEditAvatarSubmit": 82, - "routes.AccountEditUsernameSubmit": 83, - "routes.AccountEditMFA": 84, - "routes.AccountEditMFASetup": 85, - "routes.AccountEditMFASetupSubmit": 86, - "routes.AccountEditMFADisableSubmit": 87, - "routes.AccountEditEmail": 88, - "routes.AccountEditEmailTokenSubmit": 89, - "routes.AccountLogins": 90, - "routes.LevelList": 91, - "routes.ViewProfile": 92, - "routes.BanUserSubmit": 93, - "routes.UnbanUser": 94, - "routes.ActivateUser": 95, - "routes.IPSearch": 96, - "routes.CreateTopicSubmit": 97, - "routes.EditTopicSubmit": 98, - "routes.DeleteTopicSubmit": 99, - "routes.StickTopicSubmit": 100, - "routes.UnstickTopicSubmit": 101, - "routes.LockTopicSubmit": 102, - "routes.UnlockTopicSubmit": 103, - "routes.MoveTopicSubmit": 104, - "routes.LikeTopicSubmit": 105, - "routes.AddAttachToTopicSubmit": 106, - "routes.RemoveAttachFromTopicSubmit": 107, - "routes.ViewTopic": 108, - "routes.CreateReplySubmit": 109, - "routes.ReplyEditSubmit": 110, - "routes.ReplyDeleteSubmit": 111, - "routes.ReplyLikeSubmit": 112, - "routes.AddAttachToReplySubmit": 113, - "routes.RemoveAttachFromReplySubmit": 114, - "routes.ProfileReplyCreateSubmit": 115, - "routes.ProfileReplyEditSubmit": 116, - "routes.ProfileReplyDeleteSubmit": 117, - "routes.PollVote": 118, - "routes.PollResults": 119, - "routes.AccountLogin": 120, - "routes.AccountRegister": 121, - "routes.AccountLogout": 122, - "routes.AccountLoginSubmit": 123, - "routes.AccountLoginMFAVerify": 124, - "routes.AccountLoginMFAVerifySubmit": 125, - "routes.AccountRegisterSubmit": 126, - "routes.DynamicRoute": 127, - "routes.UploadedFile": 128, - "routes.StaticFile": 129, - "routes.RobotsTxt": 130, - "routes.SitemapXml": 131, - "routes.BadRoute": 132, + "panel.ThemesWidgets": 46, + "panel.ThemesWidgetsEditSubmit": 47, + "panel.ThemesWidgetsCreateSubmit": 48, + "panel.ThemesWidgetsDeleteSubmit": 49, + "panel.Plugins": 50, + "panel.PluginsActivate": 51, + "panel.PluginsDeactivate": 52, + "panel.PluginsInstall": 53, + "panel.Users": 54, + "panel.UsersEdit": 55, + "panel.UsersEditSubmit": 56, + "panel.AnalyticsViews": 57, + "panel.AnalyticsRoutes": 58, + "panel.AnalyticsAgents": 59, + "panel.AnalyticsSystems": 60, + "panel.AnalyticsLanguages": 61, + "panel.AnalyticsReferrers": 62, + "panel.AnalyticsRouteViews": 63, + "panel.AnalyticsAgentViews": 64, + "panel.AnalyticsForumViews": 65, + "panel.AnalyticsSystemViews": 66, + "panel.AnalyticsLanguageViews": 67, + "panel.AnalyticsReferrerViews": 68, + "panel.AnalyticsPosts": 69, + "panel.AnalyticsTopics": 70, + "panel.AnalyticsForums": 71, + "panel.Groups": 72, + "panel.GroupsEdit": 73, + "panel.GroupsEditPerms": 74, + "panel.GroupsEditSubmit": 75, + "panel.GroupsEditPermsSubmit": 76, + "panel.GroupsCreateSubmit": 77, + "panel.Backups": 78, + "panel.LogsRegs": 79, + "panel.LogsMod": 80, + "panel.Debug": 81, + "panel.Dashboard": 82, + "routes.AccountEdit": 83, + "routes.AccountEditPassword": 84, + "routes.AccountEditPasswordSubmit": 85, + "routes.AccountEditAvatarSubmit": 86, + "routes.AccountEditUsernameSubmit": 87, + "routes.AccountEditMFA": 88, + "routes.AccountEditMFASetup": 89, + "routes.AccountEditMFASetupSubmit": 90, + "routes.AccountEditMFADisableSubmit": 91, + "routes.AccountEditEmail": 92, + "routes.AccountEditEmailTokenSubmit": 93, + "routes.AccountLogins": 94, + "routes.LevelList": 95, + "routes.ViewProfile": 96, + "routes.BanUserSubmit": 97, + "routes.UnbanUser": 98, + "routes.ActivateUser": 99, + "routes.IPSearch": 100, + "routes.CreateTopicSubmit": 101, + "routes.EditTopicSubmit": 102, + "routes.DeleteTopicSubmit": 103, + "routes.StickTopicSubmit": 104, + "routes.UnstickTopicSubmit": 105, + "routes.LockTopicSubmit": 106, + "routes.UnlockTopicSubmit": 107, + "routes.MoveTopicSubmit": 108, + "routes.LikeTopicSubmit": 109, + "routes.AddAttachToTopicSubmit": 110, + "routes.RemoveAttachFromTopicSubmit": 111, + "routes.ViewTopic": 112, + "routes.CreateReplySubmit": 113, + "routes.ReplyEditSubmit": 114, + "routes.ReplyDeleteSubmit": 115, + "routes.ReplyLikeSubmit": 116, + "routes.AddAttachToReplySubmit": 117, + "routes.RemoveAttachFromReplySubmit": 118, + "routes.ProfileReplyCreateSubmit": 119, + "routes.ProfileReplyEditSubmit": 120, + "routes.ProfileReplyDeleteSubmit": 121, + "routes.PollVote": 122, + "routes.PollResults": 123, + "routes.AccountLogin": 124, + "routes.AccountRegister": 125, + "routes.AccountLogout": 126, + "routes.AccountLoginSubmit": 127, + "routes.AccountLoginMFAVerify": 128, + "routes.AccountLoginMFAVerifySubmit": 129, + "routes.AccountRegisterSubmit": 130, + "routes.DynamicRoute": 131, + "routes.UploadedFile": 132, + "routes.StaticFile": 133, + "routes.RobotsTxt": 134, + "routes.SitemapXml": 135, + "routes.BadRoute": 136, } var reverseRouteMapEnum = map[int]string{ 0: "routes.Overview", @@ -341,93 +349,97 @@ var reverseRouteMapEnum = map[int]string{ 43: "panel.ThemesMenuItemCreateSubmit", 44: "panel.ThemesMenuItemDeleteSubmit", 45: "panel.ThemesMenuItemOrderSubmit", - 46: "panel.Plugins", - 47: "panel.PluginsActivate", - 48: "panel.PluginsDeactivate", - 49: "panel.PluginsInstall", - 50: "panel.Users", - 51: "panel.UsersEdit", - 52: "panel.UsersEditSubmit", - 53: "panel.AnalyticsViews", - 54: "panel.AnalyticsRoutes", - 55: "panel.AnalyticsAgents", - 56: "panel.AnalyticsSystems", - 57: "panel.AnalyticsLanguages", - 58: "panel.AnalyticsReferrers", - 59: "panel.AnalyticsRouteViews", - 60: "panel.AnalyticsAgentViews", - 61: "panel.AnalyticsForumViews", - 62: "panel.AnalyticsSystemViews", - 63: "panel.AnalyticsLanguageViews", - 64: "panel.AnalyticsReferrerViews", - 65: "panel.AnalyticsPosts", - 66: "panel.AnalyticsTopics", - 67: "panel.AnalyticsForums", - 68: "panel.Groups", - 69: "panel.GroupsEdit", - 70: "panel.GroupsEditPerms", - 71: "panel.GroupsEditSubmit", - 72: "panel.GroupsEditPermsSubmit", - 73: "panel.GroupsCreateSubmit", - 74: "panel.Backups", - 75: "panel.LogsRegs", - 76: "panel.LogsMod", - 77: "panel.Debug", - 78: "panel.Dashboard", - 79: "routes.AccountEdit", - 80: "routes.AccountEditPassword", - 81: "routes.AccountEditPasswordSubmit", - 82: "routes.AccountEditAvatarSubmit", - 83: "routes.AccountEditUsernameSubmit", - 84: "routes.AccountEditMFA", - 85: "routes.AccountEditMFASetup", - 86: "routes.AccountEditMFASetupSubmit", - 87: "routes.AccountEditMFADisableSubmit", - 88: "routes.AccountEditEmail", - 89: "routes.AccountEditEmailTokenSubmit", - 90: "routes.AccountLogins", - 91: "routes.LevelList", - 92: "routes.ViewProfile", - 93: "routes.BanUserSubmit", - 94: "routes.UnbanUser", - 95: "routes.ActivateUser", - 96: "routes.IPSearch", - 97: "routes.CreateTopicSubmit", - 98: "routes.EditTopicSubmit", - 99: "routes.DeleteTopicSubmit", - 100: "routes.StickTopicSubmit", - 101: "routes.UnstickTopicSubmit", - 102: "routes.LockTopicSubmit", - 103: "routes.UnlockTopicSubmit", - 104: "routes.MoveTopicSubmit", - 105: "routes.LikeTopicSubmit", - 106: "routes.AddAttachToTopicSubmit", - 107: "routes.RemoveAttachFromTopicSubmit", - 108: "routes.ViewTopic", - 109: "routes.CreateReplySubmit", - 110: "routes.ReplyEditSubmit", - 111: "routes.ReplyDeleteSubmit", - 112: "routes.ReplyLikeSubmit", - 113: "routes.AddAttachToReplySubmit", - 114: "routes.RemoveAttachFromReplySubmit", - 115: "routes.ProfileReplyCreateSubmit", - 116: "routes.ProfileReplyEditSubmit", - 117: "routes.ProfileReplyDeleteSubmit", - 118: "routes.PollVote", - 119: "routes.PollResults", - 120: "routes.AccountLogin", - 121: "routes.AccountRegister", - 122: "routes.AccountLogout", - 123: "routes.AccountLoginSubmit", - 124: "routes.AccountLoginMFAVerify", - 125: "routes.AccountLoginMFAVerifySubmit", - 126: "routes.AccountRegisterSubmit", - 127: "routes.DynamicRoute", - 128: "routes.UploadedFile", - 129: "routes.StaticFile", - 130: "routes.RobotsTxt", - 131: "routes.SitemapXml", - 132: "routes.BadRoute", + 46: "panel.ThemesWidgets", + 47: "panel.ThemesWidgetsEditSubmit", + 48: "panel.ThemesWidgetsCreateSubmit", + 49: "panel.ThemesWidgetsDeleteSubmit", + 50: "panel.Plugins", + 51: "panel.PluginsActivate", + 52: "panel.PluginsDeactivate", + 53: "panel.PluginsInstall", + 54: "panel.Users", + 55: "panel.UsersEdit", + 56: "panel.UsersEditSubmit", + 57: "panel.AnalyticsViews", + 58: "panel.AnalyticsRoutes", + 59: "panel.AnalyticsAgents", + 60: "panel.AnalyticsSystems", + 61: "panel.AnalyticsLanguages", + 62: "panel.AnalyticsReferrers", + 63: "panel.AnalyticsRouteViews", + 64: "panel.AnalyticsAgentViews", + 65: "panel.AnalyticsForumViews", + 66: "panel.AnalyticsSystemViews", + 67: "panel.AnalyticsLanguageViews", + 68: "panel.AnalyticsReferrerViews", + 69: "panel.AnalyticsPosts", + 70: "panel.AnalyticsTopics", + 71: "panel.AnalyticsForums", + 72: "panel.Groups", + 73: "panel.GroupsEdit", + 74: "panel.GroupsEditPerms", + 75: "panel.GroupsEditSubmit", + 76: "panel.GroupsEditPermsSubmit", + 77: "panel.GroupsCreateSubmit", + 78: "panel.Backups", + 79: "panel.LogsRegs", + 80: "panel.LogsMod", + 81: "panel.Debug", + 82: "panel.Dashboard", + 83: "routes.AccountEdit", + 84: "routes.AccountEditPassword", + 85: "routes.AccountEditPasswordSubmit", + 86: "routes.AccountEditAvatarSubmit", + 87: "routes.AccountEditUsernameSubmit", + 88: "routes.AccountEditMFA", + 89: "routes.AccountEditMFASetup", + 90: "routes.AccountEditMFASetupSubmit", + 91: "routes.AccountEditMFADisableSubmit", + 92: "routes.AccountEditEmail", + 93: "routes.AccountEditEmailTokenSubmit", + 94: "routes.AccountLogins", + 95: "routes.LevelList", + 96: "routes.ViewProfile", + 97: "routes.BanUserSubmit", + 98: "routes.UnbanUser", + 99: "routes.ActivateUser", + 100: "routes.IPSearch", + 101: "routes.CreateTopicSubmit", + 102: "routes.EditTopicSubmit", + 103: "routes.DeleteTopicSubmit", + 104: "routes.StickTopicSubmit", + 105: "routes.UnstickTopicSubmit", + 106: "routes.LockTopicSubmit", + 107: "routes.UnlockTopicSubmit", + 108: "routes.MoveTopicSubmit", + 109: "routes.LikeTopicSubmit", + 110: "routes.AddAttachToTopicSubmit", + 111: "routes.RemoveAttachFromTopicSubmit", + 112: "routes.ViewTopic", + 113: "routes.CreateReplySubmit", + 114: "routes.ReplyEditSubmit", + 115: "routes.ReplyDeleteSubmit", + 116: "routes.ReplyLikeSubmit", + 117: "routes.AddAttachToReplySubmit", + 118: "routes.RemoveAttachFromReplySubmit", + 119: "routes.ProfileReplyCreateSubmit", + 120: "routes.ProfileReplyEditSubmit", + 121: "routes.ProfileReplyDeleteSubmit", + 122: "routes.PollVote", + 123: "routes.PollResults", + 124: "routes.AccountLogin", + 125: "routes.AccountRegister", + 126: "routes.AccountLogout", + 127: "routes.AccountLoginSubmit", + 128: "routes.AccountLoginMFAVerify", + 129: "routes.AccountLoginMFAVerifySubmit", + 130: "routes.AccountRegisterSubmit", + 131: "routes.DynamicRoute", + 132: "routes.UploadedFile", + 133: "routes.StaticFile", + 134: "routes.RobotsTxt", + 135: "routes.SitemapXml", + 136: "routes.BadRoute", } var osMapEnum = map[string]int{ "unknown": 0, @@ -716,7 +728,7 @@ func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { counters.GlobalViewCounter.Bump() if prefix == "/static" { - counters.RouteViewCounter.Bump(129) + counters.RouteViewCounter.Bump(133) req.URL.Path += extraData routes.StaticFile(w, req) return @@ -1230,8 +1242,35 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c counters.RouteViewCounter.Bump(45) err = panel.ThemesMenuItemOrderSubmit(w,req,user,extraData) - case "/panel/plugins/": + case "/panel/themes/widgets/": counters.RouteViewCounter.Bump(46) + err = panel.ThemesWidgets(w,req,user) + case "/panel/themes/widgets/edit/submit/": + err = common.NoSessionMismatch(w,req,user) + if err != nil { + return err + } + + counters.RouteViewCounter.Bump(47) + err = panel.ThemesWidgetsEditSubmit(w,req,user,extraData) + case "/panel/themes/widgets/create/submit/": + err = common.NoSessionMismatch(w,req,user) + if err != nil { + return err + } + + counters.RouteViewCounter.Bump(48) + err = panel.ThemesWidgetsCreateSubmit(w,req,user) + case "/panel/themes/widgets/delete/submit/": + err = common.NoSessionMismatch(w,req,user) + if err != nil { + return err + } + + counters.RouteViewCounter.Bump(49) + err = panel.ThemesWidgetsDeleteSubmit(w,req,user,extraData) + case "/panel/plugins/": + counters.RouteViewCounter.Bump(50) err = panel.Plugins(w,req,user) case "/panel/plugins/activate/": err = common.NoSessionMismatch(w,req,user) @@ -1239,7 +1278,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - counters.RouteViewCounter.Bump(47) + counters.RouteViewCounter.Bump(51) err = panel.PluginsActivate(w,req,user,extraData) case "/panel/plugins/deactivate/": err = common.NoSessionMismatch(w,req,user) @@ -1247,7 +1286,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - counters.RouteViewCounter.Bump(48) + counters.RouteViewCounter.Bump(52) err = panel.PluginsDeactivate(w,req,user,extraData) case "/panel/plugins/install/": err = common.NoSessionMismatch(w,req,user) @@ -1255,13 +1294,13 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - counters.RouteViewCounter.Bump(49) + counters.RouteViewCounter.Bump(53) err = panel.PluginsInstall(w,req,user,extraData) case "/panel/users/": - counters.RouteViewCounter.Bump(50) + counters.RouteViewCounter.Bump(54) err = panel.Users(w,req,user) case "/panel/users/edit/": - counters.RouteViewCounter.Bump(51) + counters.RouteViewCounter.Bump(55) err = panel.UsersEdit(w,req,user,extraData) case "/panel/users/edit/submit/": err = common.NoSessionMismatch(w,req,user) @@ -1269,7 +1308,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - counters.RouteViewCounter.Bump(52) + counters.RouteViewCounter.Bump(56) err = panel.UsersEditSubmit(w,req,user,extraData) case "/panel/analytics/views/": err = common.ParseForm(w,req,user) @@ -1277,7 +1316,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - counters.RouteViewCounter.Bump(53) + counters.RouteViewCounter.Bump(57) err = panel.AnalyticsViews(w,req,user) case "/panel/analytics/routes/": err = common.ParseForm(w,req,user) @@ -1285,7 +1324,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - counters.RouteViewCounter.Bump(54) + counters.RouteViewCounter.Bump(58) err = panel.AnalyticsRoutes(w,req,user) case "/panel/analytics/agents/": err = common.ParseForm(w,req,user) @@ -1293,7 +1332,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - counters.RouteViewCounter.Bump(55) + counters.RouteViewCounter.Bump(59) err = panel.AnalyticsAgents(w,req,user) case "/panel/analytics/systems/": err = common.ParseForm(w,req,user) @@ -1301,7 +1340,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - counters.RouteViewCounter.Bump(56) + counters.RouteViewCounter.Bump(60) err = panel.AnalyticsSystems(w,req,user) case "/panel/analytics/langs/": err = common.ParseForm(w,req,user) @@ -1309,7 +1348,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - counters.RouteViewCounter.Bump(57) + counters.RouteViewCounter.Bump(61) err = panel.AnalyticsLanguages(w,req,user) case "/panel/analytics/referrers/": err = common.ParseForm(w,req,user) @@ -1317,25 +1356,25 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - counters.RouteViewCounter.Bump(58) + counters.RouteViewCounter.Bump(62) err = panel.AnalyticsReferrers(w,req,user) case "/panel/analytics/route/": - counters.RouteViewCounter.Bump(59) + counters.RouteViewCounter.Bump(63) err = panel.AnalyticsRouteViews(w,req,user,extraData) case "/panel/analytics/agent/": - counters.RouteViewCounter.Bump(60) + counters.RouteViewCounter.Bump(64) err = panel.AnalyticsAgentViews(w,req,user,extraData) case "/panel/analytics/forum/": - counters.RouteViewCounter.Bump(61) + counters.RouteViewCounter.Bump(65) err = panel.AnalyticsForumViews(w,req,user,extraData) case "/panel/analytics/system/": - counters.RouteViewCounter.Bump(62) + counters.RouteViewCounter.Bump(66) err = panel.AnalyticsSystemViews(w,req,user,extraData) case "/panel/analytics/lang/": - counters.RouteViewCounter.Bump(63) + counters.RouteViewCounter.Bump(67) err = panel.AnalyticsLanguageViews(w,req,user,extraData) case "/panel/analytics/referrer/": - counters.RouteViewCounter.Bump(64) + counters.RouteViewCounter.Bump(68) err = panel.AnalyticsReferrerViews(w,req,user,extraData) case "/panel/analytics/posts/": err = common.ParseForm(w,req,user) @@ -1343,7 +1382,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - counters.RouteViewCounter.Bump(65) + counters.RouteViewCounter.Bump(69) err = panel.AnalyticsPosts(w,req,user) case "/panel/analytics/topics/": err = common.ParseForm(w,req,user) @@ -1351,7 +1390,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - counters.RouteViewCounter.Bump(66) + counters.RouteViewCounter.Bump(70) err = panel.AnalyticsTopics(w,req,user) case "/panel/analytics/forums/": err = common.ParseForm(w,req,user) @@ -1359,16 +1398,16 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - counters.RouteViewCounter.Bump(67) + counters.RouteViewCounter.Bump(71) err = panel.AnalyticsForums(w,req,user) case "/panel/groups/": - counters.RouteViewCounter.Bump(68) + counters.RouteViewCounter.Bump(72) err = panel.Groups(w,req,user) case "/panel/groups/edit/": - counters.RouteViewCounter.Bump(69) + counters.RouteViewCounter.Bump(73) err = panel.GroupsEdit(w,req,user,extraData) case "/panel/groups/edit/perms/": - counters.RouteViewCounter.Bump(70) + counters.RouteViewCounter.Bump(74) err = panel.GroupsEditPerms(w,req,user,extraData) case "/panel/groups/edit/submit/": err = common.NoSessionMismatch(w,req,user) @@ -1376,7 +1415,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - counters.RouteViewCounter.Bump(71) + counters.RouteViewCounter.Bump(75) err = panel.GroupsEditSubmit(w,req,user,extraData) case "/panel/groups/edit/perms/submit/": err = common.NoSessionMismatch(w,req,user) @@ -1384,7 +1423,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - counters.RouteViewCounter.Bump(72) + counters.RouteViewCounter.Bump(76) err = panel.GroupsEditPermsSubmit(w,req,user,extraData) case "/panel/groups/create/": err = common.NoSessionMismatch(w,req,user) @@ -1392,7 +1431,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - counters.RouteViewCounter.Bump(73) + counters.RouteViewCounter.Bump(77) err = panel.GroupsCreateSubmit(w,req,user) case "/panel/backups/": err = common.SuperAdminOnly(w,req,user) @@ -1406,13 +1445,13 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c w.Header().Del("Content-Type") w.Header().Del("Content-Encoding") } - counters.RouteViewCounter.Bump(74) + counters.RouteViewCounter.Bump(78) err = panel.Backups(w,req,user,extraData) case "/panel/logs/regs/": - counters.RouteViewCounter.Bump(75) + counters.RouteViewCounter.Bump(79) err = panel.LogsRegs(w,req,user) case "/panel/logs/mod/": - counters.RouteViewCounter.Bump(76) + counters.RouteViewCounter.Bump(80) err = panel.LogsMod(w,req,user) case "/panel/debug/": err = common.AdminOnly(w,req,user) @@ -1420,10 +1459,10 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - counters.RouteViewCounter.Bump(77) + counters.RouteViewCounter.Bump(81) err = panel.Debug(w,req,user) default: - counters.RouteViewCounter.Bump(78) + counters.RouteViewCounter.Bump(82) err = panel.Dashboard(w,req,user) } case "/user": @@ -1434,7 +1473,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - counters.RouteViewCounter.Bump(79) + counters.RouteViewCounter.Bump(83) head, err := common.UserCheck(w,req,&user) if err != nil { return err @@ -1446,7 +1485,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - counters.RouteViewCounter.Bump(80) + counters.RouteViewCounter.Bump(84) head, err := common.UserCheck(w,req,&user) if err != nil { return err @@ -1463,7 +1502,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - counters.RouteViewCounter.Bump(81) + counters.RouteViewCounter.Bump(85) err = routes.AccountEditPasswordSubmit(w,req,user) case "/user/edit/avatar/submit/": err = common.MemberOnly(w,req,user) @@ -1480,7 +1519,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - counters.RouteViewCounter.Bump(82) + counters.RouteViewCounter.Bump(86) err = routes.AccountEditAvatarSubmit(w,req,user) case "/user/edit/username/submit/": err = common.NoSessionMismatch(w,req,user) @@ -1493,7 +1532,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - counters.RouteViewCounter.Bump(83) + counters.RouteViewCounter.Bump(87) err = routes.AccountEditUsernameSubmit(w,req,user) case "/user/edit/mfa/": err = common.MemberOnly(w,req,user) @@ -1501,7 +1540,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - counters.RouteViewCounter.Bump(84) + counters.RouteViewCounter.Bump(88) head, err := common.UserCheck(w,req,&user) if err != nil { return err @@ -1513,7 +1552,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - counters.RouteViewCounter.Bump(85) + counters.RouteViewCounter.Bump(89) head, err := common.UserCheck(w,req,&user) if err != nil { return err @@ -1530,7 +1569,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - counters.RouteViewCounter.Bump(86) + counters.RouteViewCounter.Bump(90) err = routes.AccountEditMFASetupSubmit(w,req,user) case "/user/edit/mfa/disable/submit/": err = common.NoSessionMismatch(w,req,user) @@ -1543,7 +1582,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - counters.RouteViewCounter.Bump(87) + counters.RouteViewCounter.Bump(91) err = routes.AccountEditMFADisableSubmit(w,req,user) case "/user/edit/email/": err = common.MemberOnly(w,req,user) @@ -1551,7 +1590,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - counters.RouteViewCounter.Bump(88) + counters.RouteViewCounter.Bump(92) head, err := common.UserCheck(w,req,&user) if err != nil { return err @@ -1568,7 +1607,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - counters.RouteViewCounter.Bump(89) + counters.RouteViewCounter.Bump(93) err = routes.AccountEditEmailTokenSubmit(w,req,user,extraData) case "/user/edit/logins/": err = common.MemberOnly(w,req,user) @@ -1576,7 +1615,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - counters.RouteViewCounter.Bump(90) + counters.RouteViewCounter.Bump(94) head, err := common.UserCheck(w,req,&user) if err != nil { return err @@ -1588,7 +1627,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - counters.RouteViewCounter.Bump(91) + counters.RouteViewCounter.Bump(95) head, err := common.UserCheck(w,req,&user) if err != nil { return err @@ -1596,7 +1635,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c err = routes.LevelList(w,req,user,head) default: req.URL.Path += extraData - counters.RouteViewCounter.Bump(92) + counters.RouteViewCounter.Bump(96) head, err := common.UserCheck(w,req,&user) if err != nil { return err @@ -1616,7 +1655,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - counters.RouteViewCounter.Bump(93) + counters.RouteViewCounter.Bump(97) err = routes.BanUserSubmit(w,req,user,extraData) case "/users/unban/": err = common.NoSessionMismatch(w,req,user) @@ -1629,7 +1668,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - counters.RouteViewCounter.Bump(94) + counters.RouteViewCounter.Bump(98) err = routes.UnbanUser(w,req,user,extraData) case "/users/activate/": err = common.NoSessionMismatch(w,req,user) @@ -1642,7 +1681,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - counters.RouteViewCounter.Bump(95) + counters.RouteViewCounter.Bump(99) err = routes.ActivateUser(w,req,user,extraData) case "/users/ips/": err = common.MemberOnly(w,req,user) @@ -1650,7 +1689,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - counters.RouteViewCounter.Bump(96) + counters.RouteViewCounter.Bump(100) head, err := common.UserCheck(w,req,&user) if err != nil { return err @@ -1674,7 +1713,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - counters.RouteViewCounter.Bump(97) + counters.RouteViewCounter.Bump(101) err = routes.CreateTopicSubmit(w,req,user) case "/topic/edit/submit/": err = common.NoSessionMismatch(w,req,user) @@ -1687,7 +1726,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - counters.RouteViewCounter.Bump(98) + counters.RouteViewCounter.Bump(102) err = routes.EditTopicSubmit(w,req,user,extraData) case "/topic/delete/submit/": err = common.NoSessionMismatch(w,req,user) @@ -1701,7 +1740,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c } req.URL.Path += extraData - counters.RouteViewCounter.Bump(99) + counters.RouteViewCounter.Bump(103) err = routes.DeleteTopicSubmit(w,req,user) case "/topic/stick/submit/": err = common.NoSessionMismatch(w,req,user) @@ -1714,7 +1753,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - counters.RouteViewCounter.Bump(100) + counters.RouteViewCounter.Bump(104) err = routes.StickTopicSubmit(w,req,user,extraData) case "/topic/unstick/submit/": err = common.NoSessionMismatch(w,req,user) @@ -1727,7 +1766,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - counters.RouteViewCounter.Bump(101) + counters.RouteViewCounter.Bump(105) err = routes.UnstickTopicSubmit(w,req,user,extraData) case "/topic/lock/submit/": err = common.NoSessionMismatch(w,req,user) @@ -1741,7 +1780,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c } req.URL.Path += extraData - counters.RouteViewCounter.Bump(102) + counters.RouteViewCounter.Bump(106) err = routes.LockTopicSubmit(w,req,user) case "/topic/unlock/submit/": err = common.NoSessionMismatch(w,req,user) @@ -1754,7 +1793,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - counters.RouteViewCounter.Bump(103) + counters.RouteViewCounter.Bump(107) err = routes.UnlockTopicSubmit(w,req,user,extraData) case "/topic/move/submit/": err = common.NoSessionMismatch(w,req,user) @@ -1767,7 +1806,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - counters.RouteViewCounter.Bump(104) + counters.RouteViewCounter.Bump(108) err = routes.MoveTopicSubmit(w,req,user,extraData) case "/topic/like/submit/": err = common.NoSessionMismatch(w,req,user) @@ -1780,7 +1819,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - counters.RouteViewCounter.Bump(105) + counters.RouteViewCounter.Bump(109) err = routes.LikeTopicSubmit(w,req,user,extraData) case "/topic/attach/add/submit/": err = common.MemberOnly(w,req,user) @@ -1797,7 +1836,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - counters.RouteViewCounter.Bump(106) + counters.RouteViewCounter.Bump(110) err = routes.AddAttachToTopicSubmit(w,req,user,extraData) case "/topic/attach/remove/submit/": err = common.NoSessionMismatch(w,req,user) @@ -1810,10 +1849,10 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - counters.RouteViewCounter.Bump(107) + counters.RouteViewCounter.Bump(111) err = routes.RemoveAttachFromTopicSubmit(w,req,user,extraData) default: - counters.RouteViewCounter.Bump(108) + counters.RouteViewCounter.Bump(112) head, err := common.UserCheck(w,req,&user) if err != nil { return err @@ -1837,7 +1876,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - counters.RouteViewCounter.Bump(109) + counters.RouteViewCounter.Bump(113) err = routes.CreateReplySubmit(w,req,user) case "/reply/edit/submit/": err = common.NoSessionMismatch(w,req,user) @@ -1850,7 +1889,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - counters.RouteViewCounter.Bump(110) + counters.RouteViewCounter.Bump(114) err = routes.ReplyEditSubmit(w,req,user,extraData) case "/reply/delete/submit/": err = common.NoSessionMismatch(w,req,user) @@ -1863,7 +1902,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - counters.RouteViewCounter.Bump(111) + counters.RouteViewCounter.Bump(115) err = routes.ReplyDeleteSubmit(w,req,user,extraData) case "/reply/like/submit/": err = common.NoSessionMismatch(w,req,user) @@ -1876,7 +1915,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - counters.RouteViewCounter.Bump(112) + counters.RouteViewCounter.Bump(116) err = routes.ReplyLikeSubmit(w,req,user,extraData) case "/reply/attach/add/submit/": err = common.MemberOnly(w,req,user) @@ -1893,7 +1932,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - counters.RouteViewCounter.Bump(113) + counters.RouteViewCounter.Bump(117) err = routes.AddAttachToReplySubmit(w,req,user,extraData) case "/reply/attach/remove/submit/": err = common.NoSessionMismatch(w,req,user) @@ -1906,7 +1945,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - counters.RouteViewCounter.Bump(114) + counters.RouteViewCounter.Bump(118) err = routes.RemoveAttachFromReplySubmit(w,req,user,extraData) } case "/profile": @@ -1922,7 +1961,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - counters.RouteViewCounter.Bump(115) + counters.RouteViewCounter.Bump(119) err = routes.ProfileReplyCreateSubmit(w,req,user) case "/profile/reply/edit/submit/": err = common.NoSessionMismatch(w,req,user) @@ -1935,7 +1974,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - counters.RouteViewCounter.Bump(116) + counters.RouteViewCounter.Bump(120) err = routes.ProfileReplyEditSubmit(w,req,user,extraData) case "/profile/reply/delete/submit/": err = common.NoSessionMismatch(w,req,user) @@ -1948,7 +1987,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - counters.RouteViewCounter.Bump(117) + counters.RouteViewCounter.Bump(121) err = routes.ProfileReplyDeleteSubmit(w,req,user,extraData) } case "/poll": @@ -1964,23 +2003,23 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - counters.RouteViewCounter.Bump(118) + counters.RouteViewCounter.Bump(122) err = routes.PollVote(w,req,user,extraData) case "/poll/results/": - counters.RouteViewCounter.Bump(119) + counters.RouteViewCounter.Bump(123) err = routes.PollResults(w,req,user,extraData) } case "/accounts": switch(req.URL.Path) { case "/accounts/login/": - counters.RouteViewCounter.Bump(120) + counters.RouteViewCounter.Bump(124) head, err := common.UserCheck(w,req,&user) if err != nil { return err } err = routes.AccountLogin(w,req,user,head) case "/accounts/create/": - counters.RouteViewCounter.Bump(121) + counters.RouteViewCounter.Bump(125) head, err := common.UserCheck(w,req,&user) if err != nil { return err @@ -1997,7 +2036,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - counters.RouteViewCounter.Bump(122) + counters.RouteViewCounter.Bump(126) err = routes.AccountLogout(w,req,user) case "/accounts/login/submit/": err = common.ParseForm(w,req,user) @@ -2005,10 +2044,10 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - counters.RouteViewCounter.Bump(123) + counters.RouteViewCounter.Bump(127) err = routes.AccountLoginSubmit(w,req,user) case "/accounts/mfa_verify/": - counters.RouteViewCounter.Bump(124) + counters.RouteViewCounter.Bump(128) head, err := common.UserCheck(w,req,&user) if err != nil { return err @@ -2020,7 +2059,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - counters.RouteViewCounter.Bump(125) + counters.RouteViewCounter.Bump(129) err = routes.AccountLoginMFAVerifySubmit(w,req,user) case "/accounts/create/submit/": err = common.ParseForm(w,req,user) @@ -2028,7 +2067,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - counters.RouteViewCounter.Bump(126) + counters.RouteViewCounter.Bump(130) err = routes.AccountRegisterSubmit(w,req,user) } /*case "/sitemaps": // TODO: Count these views @@ -2044,7 +2083,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c w.Header().Del("Content-Type") w.Header().Del("Content-Encoding") } - counters.RouteViewCounter.Bump(128) + counters.RouteViewCounter.Bump(132) req.URL.Path += extraData // TODO: Find a way to propagate errors up from this? r.UploadHandler(w,req) // TODO: Count these views @@ -2054,10 +2093,10 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c // TODO: Add support for favicons and robots.txt files switch(extraData) { case "robots.txt": - counters.RouteViewCounter.Bump(130) + counters.RouteViewCounter.Bump(134) return routes.RobotsTxt(w,req) /*case "sitemap.xml": - counters.RouteViewCounter.Bump(131) + counters.RouteViewCounter.Bump(135) return routes.SitemapXml(w,req)*/ } return common.NotFound(w,req,nil) @@ -2068,7 +2107,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c r.RUnlock() if ok { - counters.RouteViewCounter.Bump(127) // TODO: Be more specific about *which* dynamic route it is + counters.RouteViewCounter.Bump(131) // TODO: Be more specific about *which* dynamic route it is req.URL.Path += extraData return handle(w,req,user) } @@ -2079,7 +2118,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c } else { r.DumpRequest(req,"Bad Route") } - counters.RouteViewCounter.Bump(132) + counters.RouteViewCounter.Bump(136) return common.NotFound(w,req,nil) } return err diff --git a/gen_tables.go b/gen_tables.go index e30a6664..44475f3c 100644 --- a/gen_tables.go +++ b/gen_tables.go @@ -2,23 +2,24 @@ package main var dbTablePrimaryKeys = map[string]string{ - "users":"uid", "users_groups":"gid", - "users_groups_scheduler":"uid", - "polls":"pollID", - "registration_logs":"rlid", - "activity_stream":"asid", + "users_avatar_queue":"uid", + "word_filters":"wfid", + "menus":"mid", "login_logs":"lid", - "users_2fa_keys":"uid", + "polls":"pollID", + "activity_stream":"asid", + "pages":"pid", + "forums":"fid", "topics":"tid", "replies":"rid", "attachments":"attachID", "revisions":"reviseID", - "users_avatar_queue":"uid", - "forums":"fid", + "users_2fa_keys":"uid", + "users_groups_scheduler":"uid", "menu_items":"miid", + "registration_logs":"rlid", + "users":"uid", "users_replies":"rid", - "word_filters":"wfid", - "menus":"mid", - "pages":"pid", + "widgets":"wid", } diff --git a/install/mysql.go b/install/mysql.go index 3c139f5e..b3a1cea8 100644 --- a/install/mysql.go +++ b/install/mysql.go @@ -1,7 +1,7 @@ /* * * Gosora MySQL Interface -* Copyright Azareal 2017 - 2018 +* Copyright Azareal 2017 - 2019 * */ package install diff --git a/install/pgsql.go b/install/pgsql.go index f9c6ea79..8d429ea1 100644 --- a/install/pgsql.go +++ b/install/pgsql.go @@ -2,7 +2,7 @@ * * Gosora PostgreSQL Interface * Under heavy development -* Copyright Azareal 2017 - 2018 +* Copyright Azareal 2017 - 2019 * */ package install diff --git a/langs/english.json b/langs/english.json index 163465b6..d52fdc54 100644 --- a/langs/english.json +++ b/langs/english.json @@ -1,5 +1,6 @@ { "Name": "english", + "IsoCode":"en", "Levels": { "Level": "Level {0}", @@ -154,6 +155,7 @@ "panel_themes":"Theme Manager", "panel_themes_menus":"Menu Manager", "panel_themes_menus_edit":"Menu Editor", + "panel_themes_widgets":"Widget Manager", "panel_backups":"Backups", "panel_registration_logs":"Registration Logs", "panel_mod_logs":"Mod Action Logs", @@ -664,6 +666,10 @@ "footer_made_with_love":"Made with love by Azareal", "footer_theme_selector_aria":"Change the site's appearance", + "widget.online_name":"Online Users", + "widget.online_none_online":"No one is online.", + "widget.online_some_online":"There are %d users online.", + "option_yes":"Yes", "option_no":"No", @@ -895,6 +901,23 @@ "panel_themes_menus_edit_update_button":"Update", "panel_themes_menus_create_button":"Create", + "panel_themes_widgets_head":"Widgets", + "panel_themes_widgets_disabled":"disabled", + "panel_themes_widgets_new":"New Widget", + "panel_themes_widgets_type":"Type", + "panel_themes_widgets_type_about":"About", + "panel_themes_widgets_type_simple":"Simple", + "panel_themes_widgets_type_wol":"Online Users", + "panel_themes_widgets_type_wol_context":"Online User Context", + "panel_themes_widgets_type_search_and_filter":"Search & Filter", + "panel_themes_widgets_enabled":"Enabled", + "panel_themes_widgets_location":"Location", + "panel_themes_widgets_name":"Name", + "panel_themes_widgets_body":"Body", + "panel_themes_widgets_raw_body":"Body", + "panel_themes_widgets_save":"Save", + "panel_themes_widgets_delete":"Delete", + "panel_settings_head":"Settings", "panel_setting_head":"Edit Setting", "panel_setting_name":"Setting Name", diff --git a/main.go b/main.go index 23ed1768..72296bb8 100644 --- a/main.go +++ b/main.go @@ -4,6 +4,7 @@ * Copyright Azareal 2016 - 2019 * */ +// Package main contains the main initialisation logic for Gosora package main // import "github.com/Azareal/Gosora" import ( @@ -78,6 +79,7 @@ func afterDBInit() (err error) { } log.Print("Initialising the widgets") + common.Widgets = common.NewDefaultWidgetStore() err = common.InitWidgets() if err != nil { return errors.WithStack(err) diff --git a/misc_test.go b/misc_test.go index f9be1e50..c6b6514c 100644 --- a/misc_test.go +++ b/misc_test.go @@ -750,6 +750,7 @@ func TestReplyStore(t *testing.T) { expectNilErr(t, err) expect(t, topic.PostCount == 3, fmt.Sprintf("TID #1's post count should be three, not %d", topic.PostCount)) + // TODO: Expand upon this rid, err = common.Rstore.Create(topic, "hiii", "::1", 1) expectNilErr(t, err) replyTest(rid, topic.ID, 1, "hiii", "::1") @@ -1014,6 +1015,7 @@ func TestWordFilters(t *testing.T) { // TODO: Add deletion tests } +// TODO: Expand upon the valid characters which can go in URLs? func TestSlugs(t *testing.T) { var res string var msgList = &MEPairList{nil} @@ -1050,6 +1052,64 @@ func TestSlugs(t *testing.T) { } } +func TestWidgets(t *testing.T) { + _, err := common.Widgets.Get(1) + recordMustNotExist(t, err, "There shouldn't be any widgets by default") + widgets := common.Docks.RightSidebar + expect(t, len(widgets) == 0, fmt.Sprintf("RightSidebar should have 0 items, not %d", len(widgets))) + + widget := &common.Widget{Position: 0, Side: "rightSidebar", Type: "simple", Enabled: true, Location: "global"} + ewidget := &common.WidgetEdit{widget, map[string]string{"Name": "Test", "Text": "Testing"}} + err = ewidget.Create() + expectNilErr(t, err) + + // TODO: Do a test for the widget body + widget2, err := common.Widgets.Get(1) + expectNilErr(t, err) + expect(t, widget2.Position == widget.Position, "wrong position") + expect(t, widget2.Side == widget.Side, "wrong side") + expect(t, widget2.Type == widget.Type, "wrong type") + expect(t, widget2.Enabled, "not enabled") + expect(t, widget2.Location == widget.Location, "wrong location") + + widgets = common.Docks.RightSidebar + expect(t, len(widgets) == 1, fmt.Sprintf("RightSidebar should have 1 item, not %d", len(widgets))) + expect(t, widgets[0].Position == widget.Position, "wrong position") + expect(t, widgets[0].Side == widget.Side, "wrong side") + expect(t, widgets[0].Type == widget.Type, "wrong type") + expect(t, widgets[0].Enabled, "not enabled") + expect(t, widgets[0].Location == widget.Location, "wrong location") + + widget2.Enabled = false + ewidget = &common.WidgetEdit{widget2, map[string]string{"Name": "Test", "Text": "Testing"}} + err = ewidget.Commit() + expectNilErr(t, err) + + widget2, err = common.Widgets.Get(1) + expectNilErr(t, err) + expect(t, widget2.Position == widget.Position, "wrong position") + expect(t, widget2.Side == widget.Side, "wrong side") + expect(t, widget2.Type == widget.Type, "wrong type") + expect(t, !widget2.Enabled, "not enabled") + expect(t, widget2.Location == widget.Location, "wrong location") + + widgets = common.Docks.RightSidebar + expect(t, len(widgets) == 1, fmt.Sprintf("RightSidebar should have 1 item, not %d", len(widgets))) + expect(t, widgets[0].Position == widget.Position, "wrong position") + expect(t, widgets[0].Side == widget.Side, "wrong side") + expect(t, widgets[0].Type == widget.Type, "wrong type") + expect(t, !widgets[0].Enabled, "not enabled") + expect(t, widgets[0].Location == widget.Location, "wrong location") + + err = widget2.Delete() + expectNilErr(t, err) + + _, err = common.Widgets.Get(1) + recordMustNotExist(t, err, "There shouldn't be any widgets anymore") + widgets = common.Docks.RightSidebar + expect(t, len(widgets) == 0, fmt.Sprintf("RightSidebar should have 0 items, not %d", len(widgets))) +} + func TestAuth(t *testing.T) { // bcrypt likes doing stupid things, so this test will probably fail realPassword := "Madame Cassandra's Mystic Orb" diff --git a/patcher/patches.go b/patcher/patches.go index 9f82e3ba..525f1664 100644 --- a/patcher/patches.go +++ b/patcher/patches.go @@ -24,6 +24,7 @@ func init() { addPatch(10, patch10) addPatch(11, patch11) addPatch(12, patch12) + addPatch(13, patch13) } func patch0(scanner *bufio.Scanner) (err error) { @@ -392,11 +393,11 @@ var acc = qgen.NewAcc var itoa = strconv.Itoa func patch10(scanner *bufio.Scanner) error { - err := execStmt(qgen.Builder.AddColumn("topics", tblColumn{"attachCount", "int", 0, false, false, "0"})) + err := execStmt(qgen.Builder.AddColumn("topics", tblColumn{"attachCount", "int", 0, false, false, "0"}, nil)) if err != nil { return err } - err = execStmt(qgen.Builder.AddColumn("topics", tblColumn{"lastReplyID", "int", 0, false, false, "0"})) + err = execStmt(qgen.Builder.AddColumn("topics", tblColumn{"lastReplyID", "int", 0, false, false, "0"}, nil)) if err != nil { return err } @@ -432,7 +433,7 @@ func patch10(scanner *bufio.Scanner) error { } func patch11(scanner *bufio.Scanner) error { - err := execStmt(qgen.Builder.AddColumn("replies", tblColumn{"attachCount", "int", 0, false, false, "0"})) + err := execStmt(qgen.Builder.AddColumn("replies", tblColumn{"attachCount", "int", 0, false, false, "0"}, nil)) if err != nil { return err } @@ -504,3 +505,12 @@ func patch12(scanner *bufio.Scanner) error { } return nil } + +func patch13(scanner *bufio.Scanner) error { + err := execStmt(qgen.Builder.AddColumn("widgets", tblColumn{"wid", "int", 0, false, true, ""}, &tblKey{"wid", "primary"})) + if err != nil { + return err + } + + return nil +} diff --git a/pgsql.go b/pgsql.go index 6e372137..eca25b34 100644 --- a/pgsql.go +++ b/pgsql.go @@ -1,6 +1,6 @@ // +build pgsql -/* Copyright Azareal 2016 - 2018 */ +/* Copyright Azareal 2016 - 2019 */ /* Super experimental and incomplete. DON'T USE IT YET! */ package main diff --git a/public/global.js b/public/global.js index c202243b..1db61a12 100644 --- a/public/global.js +++ b/public/global.js @@ -23,8 +23,7 @@ function ajaxError(xhr,status,errstr) { console.trace(); } -function postLink(event) -{ +function postLink(event) { event.preventDefault(); let formAction = $(event.target).closest('a').attr("href"); $.ajax({ url: formAction, type: "POST", dataType: "json", error: ajaxError, data: {js: "1"} }); @@ -115,9 +114,7 @@ function loadAlerts(menuAlerts) { } alertList = []; alertMapping = {}; - for(var i in data.msgs) { - addAlert(data.msgs[i]); - } + for(var i in data.msgs) addAlert(data.msgs[i]); console.log("data.msgCount:",data.msgCount) alertCount = data.msgCount; updateAlertList(menuAlerts) @@ -271,8 +268,13 @@ function runWebSockets() { let msgblocks = SplitN(message," ",3); if(msgblocks.length < 3) continue; if(message.startsWith("set ")) { + let oldInnerHTML = document.querySelector(msgblocks[1]).innerHTML; + if(msgblocks[2]==oldInnerHTML) continue; document.querySelector(msgblocks[1]).innerHTML = msgblocks[2]; } else if(message.startsWith("set-class ")) { + // Fix to stop the inspector from getting all jittery + let oldClassName = document.querySelector(msgblocks[1]).className; + if(msgblocks[2]==oldClassName) continue; document.querySelector(msgblocks[1]).className = msgblocks[2]; } } diff --git a/public/init.js b/public/init.js index 1f99ffc7..a4f19adf 100644 --- a/public/init.js +++ b/public/init.js @@ -94,8 +94,12 @@ function DoNothingButPassBack(item) { return item; } -function fetchPhrases() { - fetch("/api/phrases/?query=status,topic_list,alerts") +function initPhrases() { + fetchPhrases("status,topic_list,alerts") +} + +function fetchPhrases(plist) { + fetch("/api/phrases/?query="+plist) .then((resp) => resp.json()) .then((data) => { console.log("loaded phrase endpoint data"); @@ -103,9 +107,7 @@ function fetchPhrases() { Object.keys(tmplInits).forEach((key) => { let phrases = []; let tmplInit = tmplInits[key]; - for(let phraseName of tmplInit) { - phrases.push(data[phraseName]); - } + for(let phraseName of tmplInit) phrases.push(data[phraseName]); console.log("Adding phrases"); console.log("key:",key); console.log("phrases:",phrases); @@ -115,9 +117,7 @@ function fetchPhrases() { let prefixes = {}; Object.keys(data).forEach((key) => { let prefix = key.split(".")[0]; - if(prefixes[prefix]===undefined) { - prefixes[prefix] = {}; - } + if(prefixes[prefix]===undefined) prefixes[prefix] = {}; prefixes[prefix][key] = data[key]; }); Object.keys(prefixes).forEach((prefix) => { @@ -146,7 +146,7 @@ function fetchPhrases() { loadScript("template_topics_topic.js", () => { console.log("Loaded template_topics_topic.js"); toLoad--; - if(toLoad===0) fetchPhrases(); + if(toLoad===0) initPhrases(); }); } else { me = {User:{ID:0,Session:""},Site:{"MaxRequestSize":0}}; diff --git a/public/widgets.js b/public/widgets.js new file mode 100644 index 00000000..3d4fa813 --- /dev/null +++ b/public/widgets.js @@ -0,0 +1,66 @@ +"use strict"; + +$(document).ready(() => { + let clickHandle = function(event){ + console.log("in clickHandle") + event.preventDefault(); + let eparent = $(this).closest(".editable_parent"); + eparent.find(".hide_on_block_edit").addClass("edit_opened"); + eparent.find(".show_on_block_edit").addClass("edit_opened"); + eparent.addClass("in_edit"); + + eparent.find(".widget_save").click(() => { + eparent.find(".hide_on_block_edit").removeClass("edit_opened"); + eparent.find(".show_on_block_edit").removeClass("edit_opened"); + eparent.removeClass("in_edit"); + }); + + eparent.find(".widget_delete").click(function(event) { + event.preventDefault(); + eparent.remove(); + let formData = new URLSearchParams(); + formData.append("session",me.User.Session); + let req = new XMLHttpRequest(); + let target = this.closest("a").getAttribute("href"); + req.open("POST",target,true); + req.send(formData); + }); + }; + + $(".widget_item a").click(clickHandle); + + let changeHandle = function(event){ + let wtype = this.options[this.selectedIndex].value; + let typeBlock = this.closest(".widget_edit").querySelector(".wtypes"); + typeBlock.className = "wtypes wtype_"+wtype; + }; + + $(".wtype_sel").change(changeHandle); + + $(".widget_new a").click(function(event){ + console.log("clicked widget_new a") + let widgetList = this.closest(".panel_widgets"); + let widgetNew = this.closest(".widget_new"); + let widgetTmpl = document.getElementById("widgetTmpl").querySelector(".widget_item"); + let node = widgetTmpl.cloneNode(true); + node.querySelector(".wside").value = this.getAttribute("data-dock"); + widgetList.insertBefore(node,widgetNew); + $(".widget_item a").unbind("click"); + $(".widget_item a").click(clickHandle); + $(".wtype_sel").unbind("change"); + $(".wtype_sel").change(changeHandle); + }); + + $(".widget_save").click(function(event){ + console.log("in .widget_save") + event.preventDefault(); + event.stopPropagation(); + let pform = this.closest("form"); + let data = new URLSearchParams(); + for (const pair of new FormData(pform)) data.append(pair[0], pair[1]); + data.append("session",me.User.Session); + var req = new XMLHttpRequest(); + req.open("POST", pform.getAttribute("action")); + req.send(data); + }); +}); \ No newline at end of file diff --git a/query_gen/builder.go b/query_gen/builder.go index 4e298b53..0be74b44 100644 --- a/query_gen/builder.go +++ b/query_gen/builder.go @@ -108,8 +108,8 @@ func (build *builder) CreateTable(table string, charset string, collation string return build.prepare(build.adapter.CreateTable("", table, charset, collation, columns, keys)) } -func (build *builder) AddColumn(table string, column DBTableColumn) (stmt *sql.Stmt, err error) { - return build.prepare(build.adapter.AddColumn("", table, column)) +func (build *builder) AddColumn(table string, column DBTableColumn, key *DBTableKey) (stmt *sql.Stmt, err error) { + return build.prepare(build.adapter.AddColumn("", table, column, key)) } func (build *builder) AddIndex(table string, iname string, colname string) (stmt *sql.Stmt, err error) { diff --git a/query_gen/mssql.go b/query_gen/mssql.go index e336aa8e..2c9453f5 100644 --- a/query_gen/mssql.go +++ b/query_gen/mssql.go @@ -135,7 +135,8 @@ func (adapter *MssqlAdapter) parseColumn(column DBTableColumn) (col DBTableColum } // TODO: Test this, not sure if some things work -func (adapter *MssqlAdapter) AddColumn(name string, table string, column DBTableColumn) (string, error) { +// TODO: Add support for keys +func (adapter *MssqlAdapter) AddColumn(name string, table string, column DBTableColumn,key *DBTableKey) (string, error) { if table == "" { return "", errors.New("You need a name for this table") } diff --git a/query_gen/mysql.go b/query_gen/mysql.go index 03c2b575..387cccf8 100644 --- a/query_gen/mysql.go +++ b/query_gen/mysql.go @@ -173,13 +173,23 @@ func (adapter *MysqlAdapter) parseColumn(column DBTableColumn) (col DBTableColum // TODO: Support AFTER column // TODO: Test to make sure everything works here -func (adapter *MysqlAdapter) AddColumn(name string, table string, column DBTableColumn) (string, error) { +func (adapter *MysqlAdapter) AddColumn(name string, table string, column DBTableColumn, key *DBTableKey) (string, error) { if table == "" { return "", errors.New("You need a name for this table") } column, size, end := adapter.parseColumn(column) - querystr := "ALTER TABLE `" + table + "` ADD COLUMN " + "`" + column.Name + "` " + column.Type + size + end + ";" + querystr := "ALTER TABLE `" + table + "` ADD COLUMN " + "`" + column.Name + "` " + column.Type + size + end + + if key != nil { + querystr += " " + key.Type + if key.Type != "unique" { + querystr += " key" + } else if key.Type == "primary" { + querystr += " first" + } + } + // TODO: Shunt the table name logic and associated stmt list up to the a higher layer to reduce the amount of unnecessary overhead in the builder / accumulator adapter.pushStatement(name, "add-column", querystr) return querystr, nil diff --git a/query_gen/pgsql.go b/query_gen/pgsql.go index a6641c1d..57c00c85 100644 --- a/query_gen/pgsql.go +++ b/query_gen/pgsql.go @@ -113,7 +113,7 @@ func (adapter *PgsqlAdapter) CreateTable(name string, table string, charset stri } // TODO: Implement this -func (adapter *PgsqlAdapter) AddColumn(name string, table string, column DBTableColumn) (string, error) { +func (adapter *PgsqlAdapter) AddColumn(name string, table string, column DBTableColumn,key *DBTableKey) (string, error) { if table == "" { return "", errors.New("You need a name for this table") } diff --git a/query_gen/querygen.go b/query_gen/querygen.go index 37fdfd67..b1f28ff3 100644 --- a/query_gen/querygen.go +++ b/query_gen/querygen.go @@ -108,7 +108,7 @@ type Adapter interface { CreateTable(name string, table string, charset string, collation string, columns []DBTableColumn, keys []DBTableKey) (string, error) // TODO: Some way to add indices and keys // TODO: Test this - AddColumn(name string, table string, column DBTableColumn) (string, error) + AddColumn(name string, table string, column DBTableColumn, key *DBTableKey) (string, error) AddIndex(name string, table string, iname string, colname string) (string, error) SimpleInsert(name string, table string, columns string, fields string) (string, error) SimpleUpdate(up *updatePrebuilder) (string, error) diff --git a/query_gen/utils.go b/query_gen/utils.go index e6cb5197..52309279 100644 --- a/query_gen/utils.go +++ b/query_gen/utils.go @@ -2,7 +2,7 @@ * * Query Generator Library * WIP Under Construction -* Copyright Azareal 2017 - 2018 +* Copyright Azareal 2017 - 2019 * */ package qgen diff --git a/router_gen/routes.go b/router_gen/routes.go index 30fcdbc3..cf3c17c1 100644 --- a/router_gen/routes.go +++ b/router_gen/routes.go @@ -177,6 +177,12 @@ func panelRoutes() *RouteGroup { Action("panel.ThemesMenuItemDeleteSubmit", "/panel/themes/menus/item/delete/submit/", "extraData"), Action("panel.ThemesMenuItemOrderSubmit", "/panel/themes/menus/item/order/edit/submit/", "extraData"), + View("panel.ThemesWidgets", "/panel/themes/widgets/"), + //View("panel.ThemesWidgetsEdit", "/panel/themes/widgets/edit/", "extraData"), + Action("panel.ThemesWidgetsEditSubmit", "/panel/themes/widgets/edit/submit/", "extraData"), + Action("panel.ThemesWidgetsCreateSubmit", "/panel/themes/widgets/create/submit/"), + Action("panel.ThemesWidgetsDeleteSubmit", "/panel/themes/widgets/delete/submit/", "extraData"), + View("panel.Plugins", "/panel/plugins/"), Action("panel.PluginsActivate", "/panel/plugins/activate/", "extraData"), Action("panel.PluginsDeactivate", "/panel/plugins/deactivate/", "extraData"), diff --git a/routes.go b/routes.go index 697b1958..31eea75b 100644 --- a/routes.go +++ b/routes.go @@ -1,7 +1,7 @@ /* * * Gosora Route Handlers -* Copyright Azareal 2016 - 2018 +* Copyright Azareal 2016 - 2019 * */ package main diff --git a/routes/panel/themes.go b/routes/panel/themes.go index 825aaedf..b9c7fafa 100644 --- a/routes/panel/themes.go +++ b/routes/panel/themes.go @@ -2,6 +2,9 @@ package panel import ( "database/sql" + "encoding/json" + "errors" + "fmt" "net/http" "strconv" "strings" @@ -330,3 +333,155 @@ func ThemesMenuItemOrderSubmit(w http.ResponseWriter, r *http.Request, user comm return successRedirect("/panel/themes/menus/edit/"+strconv.Itoa(mid), w, r, isJs) } + +func ThemesWidgets(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { + basePage, ferr := buildBasePage(w, r, &user, "themes_widgets", "themes") + if ferr != nil { + return ferr + } + if !user.Perms.ManageThemes { + return common.NoPermissions(w, r, user) + } + basePage.Header.AddScript("widgets.js") + + var docks = make(map[string][]common.WidgetEdit) + for _, name := range common.GetDockList() { + var widgets []common.WidgetEdit + for _, widget := range common.GetDock(name) { + var data = make(map[string]string) + err := json.Unmarshal([]byte(widget.RawBody), &data) + if err != nil { + return common.InternalError(err, w, r) + } + widgets = append(widgets, common.WidgetEdit{widget, data}) + } + docks[name] = widgets + } + + 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) +} + +func widgetsParseInputs(r *http.Request, widget *common.Widget) (*common.WidgetEdit, error) { + var data = make(map[string]string) + widget.Enabled = (r.FormValue("wenabled") == "1") + widget.Location = r.FormValue("wlocation") + if widget.Location == "" { + return nil, errors.New("You need to specify a location for this widget.") + } + widget.Side = r.FormValue("wside") + if !common.HasDock(widget.Side) { + return nil, errors.New("The widget dock you specified doesn't exist.") + } + + var wtype = r.FormValue("wtype") + switch wtype { + case "simple", "about": + data["Name"] = r.FormValue("wname") + if data["Name"] == "" { + return nil, errors.New("You need to specify a title for this widget.") + } + data["Text"] = r.FormValue("wtext") + if data["Text"] == "" { + return nil, errors.New("You need to fill in the body for this widget.") + } + widget.Type = wtype // ? - Are we sure we should be directly assigning user provided data even if it's validated? + case "wol", "search_and_filter": + widget.Type = wtype // ? - Are we sure we should be directly assigning user provided data even if it's validated? + default: + return nil, errors.New("Unknown widget type") + } + + return &common.WidgetEdit{widget, data}, nil +} + +// ThemesWidgetsEditSubmit is an action which is triggered when someone sends an update request for a widget +func ThemesWidgetsEditSubmit(w http.ResponseWriter, r *http.Request, user common.User, swid string) common.RouteError { + fmt.Println("in ThemesWidgetsEditSubmit") + _, ferr := common.SimplePanelUserCheck(w, r, &user) + if ferr != nil { + return ferr + } + isJs := (r.PostFormValue("js") == "1") + if !user.Perms.ManageThemes { + return common.NoPermissionsJSQ(w, r, user, isJs) + } + + wid, err := strconv.Atoi(swid) + if err != nil { + return common.LocalErrorJSQ(phrases.GetErrorPhrase("id_must_be_integer"), w, r, user, isJs) + } + + widget, err := common.Widgets.Get(wid) + if err == sql.ErrNoRows { + return common.NotFoundJSQ(w, r, nil, isJs) + } else if err != nil { + return common.InternalErrorJSQ(err, w, r, isJs) + } + + ewidget, err := widgetsParseInputs(r, widget.Copy()) + if err != nil { + return common.LocalErrorJSQ(err.Error(), w, r, user, isJs) + } + + err = ewidget.Commit() + if err != nil { + return common.InternalErrorJSQ(err, w, r, isJs) + } + + return successRedirect("/panel/themes/widgets/", w, r, isJs) +} + +// ThemesWidgetsCreateSubmit is an action which is triggered when someone sends a create request for a widget +func ThemesWidgetsCreateSubmit(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { + fmt.Println("in ThemesWidgetsCreateSubmit") + isJs := (r.PostFormValue("js") == "1") + _, ferr := common.SimplePanelUserCheck(w, r, &user) + if ferr != nil { + return ferr + } + if !user.Perms.ManageThemes { + return common.NoPermissionsJSQ(w, r, user, isJs) + } + + ewidget, err := widgetsParseInputs(r, &common.Widget{}) + if err != nil { + return common.LocalErrorJSQ(err.Error(), w, r, user, isJs) + } + + err = ewidget.Create() + if err != nil { + return common.InternalErrorJSQ(err, w, r, isJs) + } + + return successRedirect("/panel/themes/widgets/", w, r, isJs) +} + +func ThemesWidgetsDeleteSubmit(w http.ResponseWriter, r *http.Request, user common.User, swid string) common.RouteError { + _, ferr := common.SimplePanelUserCheck(w, r, &user) + if ferr != nil { + return ferr + } + isJs := (r.PostFormValue("js") == "1") + if !user.Perms.ManageThemes { + return common.NoPermissionsJSQ(w, r, user, isJs) + } + + wid, err := strconv.Atoi(swid) + if err != nil { + return common.LocalErrorJSQ(phrases.GetErrorPhrase("id_must_be_integer"), w, r, user, isJs) + } + widget, err := common.Widgets.Get(wid) + if err == sql.ErrNoRows { + return common.NotFound(w, r, nil) + } else if err != nil { + return common.InternalError(err, w, r) + } + + err = widget.Delete() + if err != nil { + return common.InternalError(err, w, r) + } + + return successRedirect("/panel/themes/widgets/", w, r, isJs) +} diff --git a/routes/topic.go b/routes/topic.go index 6a45a4da..22a5f9ee 100644 --- a/routes/topic.go +++ b/routes/topic.go @@ -64,7 +64,6 @@ func ViewTopic(w http.ResponseWriter, r *http.Request, user common.User, header return common.NoPermissions(w, r, user) } header.Title = topic.Title - header.Zone = "view_topic" header.Path = common.BuildTopicURL(common.NameToSlug(topic.Title), topic.ID) // TODO: Cache ContentHTML when possible? @@ -253,6 +252,9 @@ func ViewTopic(w http.ResponseWriter, r *http.Request, user common.User, header } } + header.Zone = "view_topic" + header.ZoneID = topic.ID + header.ZoneData = topic rerr := renderTemplate("topic", w, r, header, tpage) counters.TopicViewCounter.Bump(topic.ID) // TODO: Move this into the router? counters.ForumViewCounter.Bump(topic.ParentID) diff --git a/schema/mssql/query_widgets.sql b/schema/mssql/query_widgets.sql index 11d42d9c..63402a41 100644 --- a/schema/mssql/query_widgets.sql +++ b/schema/mssql/query_widgets.sql @@ -1,8 +1,10 @@ CREATE TABLE [widgets] ( + [wid] int not null IDENTITY, [position] int not null, [side] nvarchar (100) not null, [type] nvarchar (100) not null, [active] bit DEFAULT 0 not null, [location] nvarchar (100) not null, - [data] nvarchar (MAX) DEFAULT '' not null + [data] nvarchar (MAX) DEFAULT '' not null, + primary key([wid]) ); \ No newline at end of file diff --git a/schema/mysql/query_widgets.sql b/schema/mysql/query_widgets.sql index e2d1e980..af1d345e 100644 --- a/schema/mysql/query_widgets.sql +++ b/schema/mysql/query_widgets.sql @@ -1,8 +1,10 @@ CREATE TABLE `widgets` ( + `wid` int not null AUTO_INCREMENT, `position` int not null, `side` varchar(100) not null, `type` varchar(100) not null, `active` boolean DEFAULT 0 not null, `location` varchar(100) not null, - `data` text not null + `data` text not null, + primary key(`wid`) ); \ No newline at end of file diff --git a/schema/pgsql/query_widgets.sql b/schema/pgsql/query_widgets.sql index 9b110701..87affb37 100644 --- a/schema/pgsql/query_widgets.sql +++ b/schema/pgsql/query_widgets.sql @@ -1,8 +1,10 @@ CREATE TABLE "widgets" ( + `wid` serial not null, `position` int not null, `side` varchar (100) not null, `type` varchar (100) not null, `active` boolean DEFAULT 0 not null, `location` varchar (100) not null, - `data` text DEFAULT '' not null + `data` text DEFAULT '' not null, + primary key(`wid`) ); \ No newline at end of file diff --git a/templates/account_logins.html b/templates/account_logins.html index 25925d5b..ddcf2bfc 100644 --- a/templates/account_logins.html +++ b/templates/account_logins.html @@ -4,7 +4,7 @@
{{range .ItemList}} -
+
{{if .Success}}{{lang "account_logins_success"}}{{else}}{{lang "account_logins_failure"}}"{{end}}
{{.IPAddress}} diff --git a/templates/forums.html b/templates/forums.html index 04409d73..63a7e77c 100644 --- a/templates/forums.html +++ b/templates/forums.html @@ -5,7 +5,7 @@

{{lang "forums_head"}}

- {{range .ItemList}}
{{.Name}}
diff --git a/templates/header.html b/templates/header.html index 1c6fe5b0..af4a6914 100644 --- a/templates/header.html +++ b/templates/header.html @@ -4,14 +4,12 @@ {{.Title}} | {{.Header.Site.Name}} {{range .Header.Stylesheets}} - - {{end}} + {{end}} {{range .Header.Scripts}} - - {{end}} + {{end}} {{if .Header.MetaDesc}}{{end}} diff --git a/templates/panel_analytics_agent_views.html b/templates/panel_analytics_agent_views.html index f256cd59..b1c33117 100644 --- a/templates/panel_analytics_agent_views.html +++ b/templates/panel_analytics_agent_views.html @@ -6,14 +6,7 @@
{{.FriendlyAgent}}{{lang "panel_statistics_views_head_suffix"}} - + {{template "panel_analytics_time_range.html" . }}
diff --git a/templates/panel_analytics_agents.html b/templates/panel_analytics_agents.html index 9194ef07..0975f416 100644 --- a/templates/panel_analytics_agents.html +++ b/templates/panel_analytics_agents.html @@ -6,14 +6,7 @@
{{lang "panel_statistics_user_agents_head"}} - + {{template "panel_analytics_time_range.html" . }}
diff --git a/templates/panel_analytics_forum_views.html b/templates/panel_analytics_forum_views.html index fc27f224..f262849b 100644 --- a/templates/panel_analytics_forum_views.html +++ b/templates/panel_analytics_forum_views.html @@ -6,14 +6,7 @@
{{.FriendlyAgent}}{{lang "panel_statistics_views_head_suffix"}} - + {{template "panel_analytics_time_range.html" . }}
diff --git a/templates/panel_analytics_forums.html b/templates/panel_analytics_forums.html index c5da95fc..c525cc08 100644 --- a/templates/panel_analytics_forums.html +++ b/templates/panel_analytics_forums.html @@ -6,14 +6,7 @@
{{lang "panel_statistics_forums_head"}} - + {{template "panel_analytics_time_range.html" . }}
diff --git a/templates/panel_analytics_lang_views.html b/templates/panel_analytics_lang_views.html index 343aadec..ea98e48d 100644 --- a/templates/panel_analytics_lang_views.html +++ b/templates/panel_analytics_lang_views.html @@ -6,14 +6,7 @@
{{.FriendlyAgent}}{{lang "panel_statistics_views_head_suffix"}} - + {{template "panel_analytics_time_range.html" . }}
diff --git a/templates/panel_analytics_langs.html b/templates/panel_analytics_langs.html index f98f7165..bf221840 100644 --- a/templates/panel_analytics_langs.html +++ b/templates/panel_analytics_langs.html @@ -6,14 +6,7 @@
{{lang "panel_statistics_languages_head"}} - + {{template "panel_analytics_time_range.html" . }}
diff --git a/templates/panel_analytics_posts.html b/templates/panel_analytics_posts.html index 2c04734f..dbc4bd19 100644 --- a/templates/panel_analytics_posts.html +++ b/templates/panel_analytics_posts.html @@ -6,14 +6,7 @@
{{lang "panel_statistics_post_counts_head"}} - + {{template "panel_analytics_time_range.html" . }}
diff --git a/templates/panel_analytics_referrer_views.html b/templates/panel_analytics_referrer_views.html index fad6fdca..a74c3f22 100644 --- a/templates/panel_analytics_referrer_views.html +++ b/templates/panel_analytics_referrer_views.html @@ -6,14 +6,7 @@
{{.Agent}}{{lang "panel_statistics_views_head_suffix"}} - + {{template "panel_analytics_time_range.html" . }}
diff --git a/templates/panel_analytics_referrers.html b/templates/panel_analytics_referrers.html index aee2e402..bc10af6e 100644 --- a/templates/panel_analytics_referrers.html +++ b/templates/panel_analytics_referrers.html @@ -6,14 +6,7 @@
{{lang "panel_statistics_referrers_head"}} - + {{template "panel_analytics_time_range.html" . }}
diff --git a/templates/panel_analytics_route_views.html b/templates/panel_analytics_route_views.html index 21c9b3c5..4387629b 100644 --- a/templates/panel_analytics_route_views.html +++ b/templates/panel_analytics_route_views.html @@ -6,14 +6,7 @@
{{.Route}}{{lang "panel_statistics_views_head_suffix"}} - + {{template "panel_analytics_time_range.html" . }}
diff --git a/templates/panel_analytics_routes.html b/templates/panel_analytics_routes.html index 43491aaf..09af23b6 100644 --- a/templates/panel_analytics_routes.html +++ b/templates/panel_analytics_routes.html @@ -6,14 +6,7 @@
{{lang "panel_statistics_routes_head"}} - + {{template "panel_analytics_time_range.html" . }}
diff --git a/templates/panel_analytics_system_views.html b/templates/panel_analytics_system_views.html index 23679039..b7dfc6f7 100644 --- a/templates/panel_analytics_system_views.html +++ b/templates/panel_analytics_system_views.html @@ -6,14 +6,7 @@
{{.FriendlyAgent}}{{lang "panel_statistics_views_head_suffix"}} - + {{template "panel_analytics_time_range.html" . }}
diff --git a/templates/panel_analytics_systems.html b/templates/panel_analytics_systems.html index 7c3ba309..54b6b574 100644 --- a/templates/panel_analytics_systems.html +++ b/templates/panel_analytics_systems.html @@ -6,14 +6,7 @@
{{lang "panel_statistics_operating_systems_head"}} - + {{template "panel_analytics_time_range.html" . }}
diff --git a/templates/panel_analytics_time_range.html b/templates/panel_analytics_time_range.html new file mode 100644 index 00000000..170494f2 --- /dev/null +++ b/templates/panel_analytics_time_range.html @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/templates/panel_analytics_topics.html b/templates/panel_analytics_topics.html index d6fcb162..3a06e73d 100644 --- a/templates/panel_analytics_topics.html +++ b/templates/panel_analytics_topics.html @@ -6,14 +6,7 @@
{{lang "panel_statistics_topic_counts_head"}} - + {{template "panel_analytics_time_range.html" . }}
diff --git a/templates/panel_analytics_views.html b/templates/panel_analytics_views.html index 2e9a8f9e..4c56150c 100644 --- a/templates/panel_analytics_views.html +++ b/templates/panel_analytics_views.html @@ -6,14 +6,7 @@

{{lang "panel_statistics_requests_head"}}

- + {{template "panel_analytics_time_range.html" . }}
diff --git a/templates/panel_inner_menu.html b/templates/panel_inner_menu.html index bbb06b71..bdc5d943 100644 --- a/templates/panel_inner_menu.html +++ b/templates/panel_inner_menu.html @@ -26,7 +26,7 @@
{{if eq .Zone "themes"}} - + {{end}} {{end}}
diff --git a/templates/panel_reglogs.html b/templates/panel_reglogs.html index be86c2a9..6a1d2bcf 100644 --- a/templates/panel_reglogs.html +++ b/templates/panel_reglogs.html @@ -7,7 +7,7 @@
{{range .Logs}} -
+
{{if not .Success}}{{lang "panel_logs_registration_attempt"}}: {{end}}{{.Username}} ({{lang "panel_logs_registration_email"}}: {{.Email}}){{if .ParsedReason}} ({{lang "panel_logs_registration_reason"}}: {{.ParsedReason}}){{end}} {{if $.CurrentUser.Perms.ViewIPs}}
{{.IPAddress}}{{end}} diff --git a/templates/panel_themes_menus.html b/templates/panel_themes_menus.html index 5c402f1d..65ac2c0f 100644 --- a/templates/panel_themes_menus.html +++ b/templates/panel_themes_menus.html @@ -5,7 +5,7 @@

{{lang "panel_themes_menus_head"}}

-
+
{{range .ItemList}}
{{if .Name}}{{.Name}} - {{end}}#{{.ID}} diff --git a/templates/panel_themes_widgets.html b/templates/panel_themes_widgets.html new file mode 100644 index 00000000..43f90613 --- /dev/null +++ b/templates/panel_themes_widgets.html @@ -0,0 +1,62 @@ +{{/** +type Widget struct { + Enabled bool + Location string // Coming Soon: overview, topics, topic / topic_view, forums, forum, global + Position int + Body string + Side string + Type string + Literal bool +} +**/}} +{{template "header.html" . }} +
+{{template "panel_menu.html" . }} +
+
+

{{lang "panel_themes_widgets_head"}}

+
+ {{range $name, $dock := .Docks}} + {{if $dock}} +
+

{{$name}}

+
+
+ {{range $widget := $dock}} +
+ +
+
+ + {{template "panel_themes_widgets_widget.html" $widget }} +
+
+
+ {{end}} + +
+ {{end}} + {{end}} +
+
+ +
+
+ + + {{template "panel_themes_widgets_widget.html" .BlankWidget }} +
+
+
+
+
+
+{{template "footer.html" . }} \ No newline at end of file diff --git a/templates/panel_themes_widgets_widget.html b/templates/panel_themes_widgets_widget.html new file mode 100644 index 00000000..1419ebd0 --- /dev/null +++ b/templates/panel_themes_widgets_widget.html @@ -0,0 +1,55 @@ +
+ +
+ +
+
+
+ +
+ +
+
+ +
+ +
+ +
+ +
+
+ +
+
+
+ + + + +
+
\ No newline at end of file diff --git a/templates/topic_alt_posts.html b/templates/topic_alt_posts.html index e954cc19..c8e206b4 100644 --- a/templates/topic_alt_posts.html +++ b/templates/topic_alt_posts.html @@ -36,7 +36,7 @@ {{end}} {{if $.CurrentUser.Perms.DeleteReply}}{{end}} {{if $.CurrentUser.Perms.ViewIPs}}{{end}} - + {{end}}
diff --git a/templates/topic_posts.html b/templates/topic_posts.html index 0616ffb2..d127375c 100644 --- a/templates/topic_posts.html +++ b/templates/topic_posts.html @@ -13,15 +13,15 @@    - {{if $.CurrentUser.Perms.LikeItem}}{{if .Liked}}{{else}}{{end}}{{end}} + {{if $.CurrentUser.Perms.LikeItem}}{{if .Liked}}{{else}}{{end}}{{end}} {{if not $.Topic.IsClosed or $.CurrentUser.Perms.CloseTopic}} {{if $.CurrentUser.Perms.EditReply}}{{end}} {{end}} {{if $.CurrentUser.Perms.DeleteReply}}{{end}} - {{if $.CurrentUser.Perms.ViewIPs}}{{end}} - + {{if $.CurrentUser.Perms.ViewIPs}}{{end}} + diff --git a/templates/topics_topic.html b/templates/topics_topic.html index 989464dc..892ba08d 100644 --- a/templates/topics_topic.html +++ b/templates/topics_topic.html @@ -1,7 +1,7 @@
- {{.Creator.Name}}'s Avatar + {{.Creator.Name}}'s Avatar {{.Title}} {{if .ForumName}}{{.ForumName}}{{end}}
{{.Creator.Name}} @@ -24,7 +24,7 @@
- {{.LastUser.Name}}'s Avatar + {{.LastUser.Name}}'s Avatar {{.LastUser.Name}}
{{reltime .LastReplyAt}} diff --git a/templates/widget_online.html b/templates/widget_online.html new file mode 100644 index 00000000..9ec56375 --- /dev/null +++ b/templates/widget_online.html @@ -0,0 +1,12 @@ +
+

{{.Name}}

+
+
+ {{if lt .UserCount 30}} + {{range .Users}}
+ {{.Name}}'s Avatar + {{.Name}} +
+ {{else}}
{{lang "widget.online_none_online"}}
{{end}} + {{else}}
{{langf "widget.online_some_online" .UserCount}}
{{end}} +
\ No newline at end of file diff --git a/templates/widget_search_and_filter.html b/templates/widget_search_and_filter.html new file mode 100644 index 00000000..a78531cb --- /dev/null +++ b/templates/widget_search_and_filter.html @@ -0,0 +1,7 @@ + +
+{{range .Forums}}
{{.Name}}
+{{end}} +
\ No newline at end of file diff --git a/themes/cosora/public/main.css b/themes/cosora/public/main.css index 5422d268..7e1c8324 100644 --- a/themes/cosora/public/main.css +++ b/themes/cosora/public/main.css @@ -236,12 +236,16 @@ ul { .rowhead:not(:first-child), .opthead:not(:first-child), .colstack_head:not(:first-child) { margin-top: 8px; } -.rowhead h1, .opthead h1, .colstack_head h1 { +.rowhead h1, .opthead h1, .colstack_head h1, +.rowhead h2, .opthead h2, .colstack_head h2 { font-size: 19px; font-weight: normal; color: var(--lightened-primary-text-color); display: inline-block; } +.rowhead h2, .opthead h2, .colstack_head h2 { + font-size: 17px; +} .colstack_head a h1 { color: var(--primary-link-color); } @@ -251,7 +255,7 @@ ul { .colstack_head h1 { font-size: 18px; } -h1, h3 { +h1, h2, h3 { -webkit-margin-before: 0; -webkit-margin-after: 0; margin-block-start: 0; @@ -534,7 +538,12 @@ input[type=checkbox]:checked + label .sel { .pollinput:not(:only-child):not(:first-child) { margin-bottom: 5px; } -.auto_hide, .show_on_edit:not(.edit_opened), .hide_on_edit.edit_opened, .link_select:not(.link_opened) { +.auto_hide, +.show_on_edit:not(.edit_opened), +.hide_on_edit.edit_opened, +.show_on_block_edit:not(.edit_opened), +.hide_on_block_edit.edit_opened, +.link_select:not(.link_opened) { display: none; } diff --git a/themes/cosora/public/panel.css b/themes/cosora/public/panel.css index 0b5a014d..9d21e1ca 100644 --- a/themes/cosora/public/panel.css +++ b/themes/cosora/public/panel.css @@ -309,6 +309,29 @@ } */ +.widget_normal { + display: flex; + width: 100%; +} +#widgetTmpl { + display: none; +} +.widget_disabled { + display: none; +} +.bg_red .widget_disabled { + display: inline; +} +.wtypes .formrow { + display: none; +} +.wtype_about .w_about, .wtype_simple .w_simple, .wtype_wol .w_wol, .wtype_default .w_default { + display: block; +} +.panel_widgets { + margin-bottom: 18px; +} + .pageset { margin-left: 16px; } diff --git a/themes/nox/public/acc_panel_common.css b/themes/nox/public/acc_panel_common.css index f9f5c7c4..94aed3c0 100644 --- a/themes/nox/public/acc_panel_common.css +++ b/themes/nox/public/acc_panel_common.css @@ -42,6 +42,9 @@ .to_right { margin-left: auto; } +.bg_red { + background-color: rgb(88,68,68) !important; +} @media (max-width: 420px) { .colstack { diff --git a/themes/nox/public/main.css b/themes/nox/public/main.css index f47b34b6..f49ab36c 100644 --- a/themes/nox/public/main.css +++ b/themes/nox/public/main.css @@ -170,11 +170,29 @@ li a { .sidebar .rowblock:not(.topic_list):not(.rowhead):not(.opthead) .rowitem { margin-left: 12px; } +.sidebar .search { + margin-left: 12px; +} +.widget_search:first-child { + margin-top: 36px; +} +.widget_search input { + width: 100%; + height: 30px; + margin-left: 0px; +} +.filter_list { + margin-top: 5px; +} .colstack_right .colstack_item:not(.colstack_head):not(.rowhead) .rowitem { border-radius: 3px; background-color: #444444; padding: 16px; } +.filter_item { + margin-bottom: 5px; + padding: 4px; +} .colstack_right .colstack_item:not(.colstack_head):not(.rowhead) .rowitem:not(:last-child) { margin-bottom: 8px; } @@ -261,6 +279,10 @@ h1, h2, h3, h4, h5 { .sidebar .rowhead h1 { font-size: 20px; } +.sidebar .rowhead:not(:first-child) h1 { + margin-top: 12px; + font-size: 19px; +} h2 { font-size: 18px; @@ -335,7 +357,14 @@ h2 { .more_topic_block_active { display: block; } -.hide_ajax_topic, .auto_hide, .show_on_edit:not(.edit_opened), .hide_on_edit.edit_opened, .link_select:not(.link_opened) { + +.hide_ajax_topic, +.auto_hide, +.show_on_edit:not(.edit_opened), +.hide_on_edit.edit_opened, +.show_on_block_edit:not(.edit_opened), +.hide_on_block_edit.edit_opened, +.link_select:not(.link_opened) { display: none !important; } @@ -866,7 +895,7 @@ input[type=checkbox]:checked + label .sel { display: flex; } -.rowlist.bgavatars, .micro_grid { +.rowlist.bgavatars:not(.not_grid), .micro_grid { display: grid; /*grid-gap: 16px; grid-row-gap: 8px;*/ @@ -888,6 +917,9 @@ input[type=checkbox]:checked + label .sel { margin-bottom: 10px; padding: 16px; } +.rowlist.not_grid .rowitem { + flex-direction: row; +} .rowlist.bgavatars .bgsub, .rowlist.bgavatars .rowTitle { margin-left: auto; margin-right: auto; @@ -901,6 +933,15 @@ input[type=checkbox]:checked + label .sel { font-size: 18px; margin-top: 4px; } +.rowlist.bgavatars.not_grid .bgsub { + height: 36px; + width: 36px; + margin-left: 8px; + margin-right: 12px; +} +.rowlist.bgavatars.not_grid .rowTitle { + margin-left: 0px; +} .ip_search_block { margin-bottom: 12px; diff --git a/themes/nox/public/panel.css b/themes/nox/public/panel.css index bb350d6d..c8c76f12 100644 --- a/themes/nox/public/panel.css +++ b/themes/nox/public/panel.css @@ -36,12 +36,21 @@ .colstack_right .colstack_head { margin-bottom: 5px; } +.colstack_right .colstack_head + .colstack_head:not(:first-child) { + margin-top: 5px; +} .colstack_right .colstack_head h1 { font-size: 21px; } .colstack_right .colstack_item.the_form, .colstack_right .colstack_item:not(.colstack_head):not(.rowhead) .rowitem { background-color: #444444; } +.colstack_right .colstack_head.colstack_sub_head:not(:first-child) { + margin-top: 6px; +} +.colstack_head + .colstack_head.colstack_sub_head:not(:first-child) { + margin-top: 2px; +} .rowitem { display: flex; } @@ -258,6 +267,39 @@ button, .formbutton, .panel_right_button:not(.has_inner_button), #panel_users .p margin-bottom: auto; } +.widget_normal { + display: flex; + width: 100%; +} +.bg_red.in_edit.widget_item { + background-color: #444444 !important; +} +.widget_item .form_button_row .rowitem { + display: flex; +} +.widget_edit .form_button_row .formitem a { + display: inline; +} +.colstack_right .colstack_item.the_form, .colstack_right .colstack_item:not(.colstack_head):not(.rowhead) .rowitem.widget_new { + padding-top: 12px; + padding-bottom: 12px; +} +#widgetTmpl { + display: none; +} +.widget_disabled { + display: none; +} +.bg_red .widget_disabled { + display: inline; +} +.wtypes .formrow { + display: none; +} +.wtype_about .w_about, .wtype_simple .w_simple, .wtype_wol .w_wol, .wtype_default .w_default { + display: block; +} + #panel_debug .grid_stat:not(.grid_stat_head) { margin-bottom: 5px; } diff --git a/themes/shadow/public/main.css b/themes/shadow/public/main.css index 07fcb1d2..b6ed0285 100644 --- a/themes/shadow/public/main.css +++ b/themes/shadow/public/main.css @@ -179,16 +179,18 @@ a { margin-top: 8px; padding: 12px; } -.rowitem h1 { +.rowitem h1, .rowitem h2 { font-size: 16px; - font-weight: normal; + display: inline; +} +h1, h2, h3 { -webkit-margin-before: 0; -webkit-margin-after: 0; margin-block-start: 0; margin-block-end: 0; margin-top: 0px; margin-bottom: 0px; - display: inline; + font-weight: normal; } .rowsmall { font-size: 12px; @@ -208,7 +210,13 @@ a { float: left; width: calc(70% - 24px); } -.colstack_left:empty, .colstack_right:empty, .show_on_edit:not(.edit_opened), .hide_on_edit.edit_opened, .link_select:not(.link_opened) { +.colstack_left:empty, +.colstack_right:empty, +.show_on_edit:not(.edit_opened), +.hide_on_edit.edit_opened, +.show_on_block_edit:not(.edit_opened), +.hide_on_block_edit.edit_opened, +.link_select:not(.link_opened) { display: none; } diff --git a/themes/shadow/public/panel.css b/themes/shadow/public/panel.css index b37931a5..954b8959 100644 --- a/themes/shadow/public/panel.css +++ b/themes/shadow/public/panel.css @@ -103,6 +103,22 @@ stroke: hsl(359,98%,43%) !important; } +#widgetTmpl { + display: none; +} +.widget_disabled { + display: none; +} +.bg_red .widget_disabled { + display: inline; +} +.wtypes .formrow { + display: none; +} +.wtype_about .w_about, .wtype_simple .w_simple, .wtype_wol .w_wol, .wtype_default .w_default { + display: block; +} + .pageset { margin-left: 0px; margin-bottom: 0px; diff --git a/themes/tempra-simple/public/main.css b/themes/tempra-simple/public/main.css index 43fa8a37..eebb61c5 100644 --- a/themes/tempra-simple/public/main.css +++ b/themes/tempra-simple/public/main.css @@ -249,7 +249,8 @@ li a { margin-top: -3px; margin-bottom: -2px; } -.rowhead h1, .colstack_head h1 { +.rowhead h1, .colstack_head h1, +.rowhead h2, .colstack_head h2 { -webkit-margin-before: 0; -webkit-margin-after: 0; margin-block-start: 0; @@ -743,7 +744,14 @@ button.username { .mention { font-weight: bold; } -.show_on_edit:not(.edit_opened), .hide_on_edit.edit_opened, .auto_hide, .hide_on_big, .show_on_mobile, .link_select:not(.link_opened) { +.show_on_edit:not(.edit_opened), +.hide_on_edit.edit_opened, +.show_on_block_edit:not(.edit_opened), +.hide_on_block_edit.edit_opened, +.auto_hide, +.hide_on_big, +.show_on_mobile, +.link_select:not(.link_opened) { display: none; } diff --git a/themes/tempra-simple/public/panel.css b/themes/tempra-simple/public/panel.css index 7d03acdf..602643e1 100644 --- a/themes/tempra-simple/public/panel.css +++ b/themes/tempra-simple/public/panel.css @@ -162,4 +162,20 @@ background-color: white; border: 1px solid hsl(0,0%,85%); +} + +#widgetTmpl { + display: none; +} +.widget_disabled { + display: none; +} +.bg_red .widget_disabled { + display: inline; +} +.wtypes .formrow { + display: none; +} +.wtype_about .w_about, .wtype_simple .w_simple, .wtype_wol .w_wol, .wtype_default .w_default { + display: block; } \ No newline at end of file