You can now re-order forums by dragging them in the Forum Manager.
Added some visual and textual hints to make it clearer that Menu Items and Forums can be dragged. Added a hint to flush the page after pushing the <head> Added the notice client template and pushNotice client function. Used a pointer instead of a struct for AnalyticsTimeRange in the analytics routes. Caught a potential missing error check in InitPhrases. Use struct{} instead of bool in some of the user mapping maps for WebSockets to save space. Added the buildUserExprs function to eliminate a bit of duplication. Fixed a typo in ForumsEdit where it referenced a non-existent notice phrase. Client hooks can now sort of return things. Panel phrases are now fetched by init.js, but only in the control panel. Reduced the number of unused phrases loaded in both the front-end and the control panel. Plugin hyperdrive should handle Gzip better now. Added the panel.ForumsOrderSubmit route. Added the panel_hints_reorder phrase. Moved the panel_forums phrases into the panel. namespace. Added the panel.forums_order_updated phrase. Renamed the panel_themes_menus_item_edit_button_aria phrase to panel_themes_menus_items_edit_button_aria Renamed the panel_themes_menus_item_delete_button_aria phrase to panel_themes_menus_items_delete_button_aria Added the panel_themes_menus_items_update_button phrase. You will need to run the patcher / updater for this commit.
This commit is contained in:
parent
2964cd767d
commit
27a4a74840
|
@ -182,6 +182,7 @@ func createTables(adapter qgen.Adapter) error {
|
||||||
tblColumn{"name", "varchar", 100, false, false, ""},
|
tblColumn{"name", "varchar", 100, false, false, ""},
|
||||||
tblColumn{"desc", "varchar", 200, false, false, ""},
|
tblColumn{"desc", "varchar", 200, false, false, ""},
|
||||||
tblColumn{"active", "boolean", 0, false, false, "1"},
|
tblColumn{"active", "boolean", 0, false, false, "1"},
|
||||||
|
tblColumn{"order", "int", 0, false, false, "0"},
|
||||||
tblColumn{"topicCount", "int", 0, false, false, "0"},
|
tblColumn{"topicCount", "int", 0, false, false, "0"},
|
||||||
tblColumn{"preset", "varchar", 100, false, false, "''"},
|
tblColumn{"preset", "varchar", 100, false, false, "''"},
|
||||||
tblColumn{"parentID", "int", 0, false, false, "0"},
|
tblColumn{"parentID", "int", 0, false, false, "0"},
|
||||||
|
@ -427,6 +428,7 @@ func createTables(adapter qgen.Adapter) error {
|
||||||
[]tblColumn{
|
[]tblColumn{
|
||||||
tblColumn{"uname", "varchar", 180, false, false, ""},
|
tblColumn{"uname", "varchar", 180, false, false, ""},
|
||||||
tblColumn{"default", "boolean", 0, false, false, "0"},
|
tblColumn{"default", "boolean", 0, false, false, "0"},
|
||||||
|
//tblColumn{"profileUserVars", "text", 0, false, false, "''"},
|
||||||
},
|
},
|
||||||
[]tblKey{
|
[]tblKey{
|
||||||
tblKey{"uname", "unique"},
|
tblKey{"uname", "unique"},
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package common
|
package common
|
||||||
|
|
||||||
//import "fmt"
|
|
||||||
import (
|
import (
|
||||||
|
//"log"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"errors"
|
"errors"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -28,6 +28,7 @@ type Forum struct {
|
||||||
Name string
|
Name string
|
||||||
Desc string
|
Desc string
|
||||||
Active bool
|
Active bool
|
||||||
|
Order int
|
||||||
Preset string
|
Preset string
|
||||||
ParentID int
|
ParentID int
|
||||||
ParentType string
|
ParentType string
|
||||||
|
@ -135,9 +136,23 @@ func (sf SortForum) Len() int {
|
||||||
func (sf SortForum) Swap(i, j int) {
|
func (sf SortForum) Swap(i, j int) {
|
||||||
sf[i], sf[j] = sf[j], sf[i]
|
sf[i], sf[j] = sf[j], sf[i]
|
||||||
}
|
}
|
||||||
|
/*func (sf SortForum) Less(i,j int) bool {
|
||||||
|
l := sf.less(i,j)
|
||||||
|
if l {
|
||||||
|
log.Printf("%s is less than %s. order: %d. id: %d.",sf[i].Name, sf[j].Name, sf[i].Order, sf[i].ID)
|
||||||
|
} else {
|
||||||
|
log.Printf("%s is not less than %s. order: %d. id: %d.",sf[i].Name, sf[j].Name, sf[i].Order, sf[i].ID)
|
||||||
|
}
|
||||||
|
return l
|
||||||
|
}*/
|
||||||
func (sf SortForum) Less(i, j int) bool {
|
func (sf SortForum) Less(i, j int) bool {
|
||||||
|
if sf[i].Order < sf[j].Order {
|
||||||
|
return true
|
||||||
|
} else if sf[i].Order == sf[j].Order {
|
||||||
return sf[i].ID < sf[j].ID
|
return sf[i].ID < sf[j].ID
|
||||||
}
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// ! Don't use this outside of tests and possibly template_init.go
|
// ! Don't use this outside of tests and possibly template_init.go
|
||||||
func BlankForum(fid int, link string, name string, desc string, active bool, preset string, parentID int, parentType string, topicCount int) *Forum {
|
func BlankForum(fid int, link string, name string, desc string, active bool, preset string, parentID int, parentType string, topicCount int) *Forum {
|
||||||
|
|
|
@ -42,6 +42,7 @@ type ForumStore interface {
|
||||||
//GetChildren(parentID int, parentType string) ([]*Forum,error)
|
//GetChildren(parentID int, parentType string) ([]*Forum,error)
|
||||||
//GetFirstChild(parentID int, parentType string) (*Forum,error)
|
//GetFirstChild(parentID int, parentType string) (*Forum,error)
|
||||||
Create(forumName string, forumDesc string, active bool, preset string) (int, error)
|
Create(forumName string, forumDesc string, active bool, preset string) (int, error)
|
||||||
|
UpdateOrder(updateMap map[int]int) error
|
||||||
|
|
||||||
GlobalCount() int
|
GlobalCount() int
|
||||||
}
|
}
|
||||||
|
@ -66,6 +67,7 @@ type MemoryForumStore struct {
|
||||||
updateCache *sql.Stmt
|
updateCache *sql.Stmt
|
||||||
addTopics *sql.Stmt
|
addTopics *sql.Stmt
|
||||||
removeTopics *sql.Stmt
|
removeTopics *sql.Stmt
|
||||||
|
updateOrder *sql.Stmt
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMemoryForumStore gives you a new instance of MemoryForumStore
|
// NewMemoryForumStore gives you a new instance of MemoryForumStore
|
||||||
|
@ -73,17 +75,19 @@ func NewMemoryForumStore() (*MemoryForumStore, error) {
|
||||||
acc := qgen.NewAcc()
|
acc := qgen.NewAcc()
|
||||||
// TODO: Do a proper delete
|
// TODO: Do a proper delete
|
||||||
return &MemoryForumStore{
|
return &MemoryForumStore{
|
||||||
get: acc.Select("forums").Columns("name, desc, active, preset, parentID, parentType, topicCount, lastTopicID, lastReplyerID").Where("fid = ?").Prepare(),
|
get: acc.Select("forums").Columns("name, desc, active, order, preset, parentID, parentType, topicCount, lastTopicID, lastReplyerID").Where("fid = ?").Prepare(),
|
||||||
getAll: acc.Select("forums").Columns("fid, name, desc, active, preset, parentID, parentType, topicCount, lastTopicID, lastReplyerID").Orderby("fid ASC").Prepare(),
|
getAll: acc.Select("forums").Columns("fid, name, desc, active, order, preset, parentID, parentType, topicCount, lastTopicID, lastReplyerID").Orderby("order ASC, fid ASC").Prepare(),
|
||||||
delete: acc.Update("forums").Set("name= '', active = 0").Where("fid = ?").Prepare(),
|
delete: acc.Update("forums").Set("name= '', active = 0").Where("fid = ?").Prepare(),
|
||||||
create: acc.Insert("forums").Columns("name, desc, active, preset").Fields("?,?,?,?").Prepare(),
|
create: acc.Insert("forums").Columns("name, desc, active, preset").Fields("?,?,?,?").Prepare(),
|
||||||
count: acc.Count("forums").Where("name != ''").Prepare(),
|
count: acc.Count("forums").Where("name != ''").Prepare(),
|
||||||
updateCache: acc.Update("forums").Set("lastTopicID = ?, lastReplyerID = ?").Where("fid = ?").Prepare(),
|
updateCache: acc.Update("forums").Set("lastTopicID = ?, lastReplyerID = ?").Where("fid = ?").Prepare(),
|
||||||
addTopics: acc.Update("forums").Set("topicCount = topicCount + ?").Where("fid = ?").Prepare(),
|
addTopics: acc.Update("forums").Set("topicCount = topicCount + ?").Where("fid = ?").Prepare(),
|
||||||
removeTopics: acc.Update("forums").Set("topicCount = topicCount - ?").Where("fid = ?").Prepare(),
|
removeTopics: acc.Update("forums").Set("topicCount = topicCount - ?").Where("fid = ?").Prepare(),
|
||||||
|
updateOrder: acc.Update("forums").Set("order = ?").Where("fid = ?").Prepare(),
|
||||||
}, acc.FirstError()
|
}, acc.FirstError()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Rename to ReloadAll?
|
||||||
// TODO: Add support for subforums
|
// TODO: Add support for subforums
|
||||||
func (mfs *MemoryForumStore) LoadForums() error {
|
func (mfs *MemoryForumStore) LoadForums() error {
|
||||||
var forumView []*Forum
|
var forumView []*Forum
|
||||||
|
@ -103,7 +107,7 @@ func (mfs *MemoryForumStore) LoadForums() error {
|
||||||
var i = 0
|
var i = 0
|
||||||
for ; rows.Next(); i++ {
|
for ; rows.Next(); i++ {
|
||||||
forum := &Forum{ID: 0, Active: true, Preset: "all"}
|
forum := &Forum{ID: 0, Active: true, Preset: "all"}
|
||||||
err = rows.Scan(&forum.ID, &forum.Name, &forum.Desc, &forum.Active, &forum.Preset, &forum.ParentID, &forum.ParentType, &forum.TopicCount, &forum.LastTopicID, &forum.LastReplyerID)
|
err = rows.Scan(&forum.ID, &forum.Name, &forum.Desc, &forum.Active, &forum.Order, &forum.Preset, &forum.ParentID, &forum.ParentType, &forum.TopicCount, &forum.LastTopicID, &forum.LastReplyerID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -161,7 +165,7 @@ func (mfs *MemoryForumStore) Get(id int) (*Forum, error) {
|
||||||
fint, ok := mfs.forums.Load(id)
|
fint, ok := mfs.forums.Load(id)
|
||||||
if !ok || fint.(*Forum).Name == "" {
|
if !ok || fint.(*Forum).Name == "" {
|
||||||
var forum = &Forum{ID: id}
|
var forum = &Forum{ID: id}
|
||||||
err := mfs.get.QueryRow(id).Scan(&forum.Name, &forum.Desc, &forum.Active, &forum.Preset, &forum.ParentID, &forum.ParentType, &forum.TopicCount, &forum.LastTopicID, &forum.LastReplyerID)
|
err := mfs.get.QueryRow(id).Scan(&forum.Name, &forum.Desc, &forum.Active, &forum.Order, &forum.Preset, &forum.ParentID, &forum.ParentType, &forum.TopicCount, &forum.LastTopicID, &forum.LastReplyerID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return forum, err
|
return forum, err
|
||||||
}
|
}
|
||||||
|
@ -178,7 +182,7 @@ func (mfs *MemoryForumStore) Get(id int) (*Forum, error) {
|
||||||
|
|
||||||
func (mfs *MemoryForumStore) BypassGet(id int) (*Forum, error) {
|
func (mfs *MemoryForumStore) BypassGet(id int) (*Forum, error) {
|
||||||
var forum = &Forum{ID: id}
|
var forum = &Forum{ID: id}
|
||||||
err := mfs.get.QueryRow(id).Scan(&forum.Name, &forum.Desc, &forum.Active, &forum.Preset, &forum.ParentID, &forum.ParentType, &forum.TopicCount, &forum.LastTopicID, &forum.LastReplyerID)
|
err := mfs.get.QueryRow(id).Scan(&forum.Name, &forum.Desc, &forum.Active, &forum.Order, &forum.Preset, &forum.ParentID, &forum.ParentType, &forum.TopicCount, &forum.LastTopicID, &forum.LastReplyerID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -206,7 +210,7 @@ func (mfs *MemoryForumStore) BulkGetCopy(ids []int) (forums []Forum, err error)
|
||||||
|
|
||||||
func (mfs *MemoryForumStore) Reload(id int) error {
|
func (mfs *MemoryForumStore) Reload(id int) error {
|
||||||
var forum = &Forum{ID: id}
|
var forum = &Forum{ID: id}
|
||||||
err := mfs.get.QueryRow(id).Scan(&forum.Name, &forum.Desc, &forum.Active, &forum.Preset, &forum.ParentID, &forum.ParentType, &forum.TopicCount, &forum.LastTopicID, &forum.LastReplyerID)
|
err := mfs.get.QueryRow(id).Scan(&forum.Name, &forum.Desc, &forum.Active, &forum.Order, &forum.Preset, &forum.ParentID, &forum.ParentType, &forum.TopicCount, &forum.LastTopicID, &forum.LastReplyerID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -348,6 +352,17 @@ func (mfs *MemoryForumStore) Create(forumName string, forumDesc string, active b
|
||||||
return fid, nil
|
return fid, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Make this atomic, maybe with a transaction?
|
||||||
|
func (s *MemoryForumStore) UpdateOrder(updateMap map[int]int) error {
|
||||||
|
for fid, order := range updateMap {
|
||||||
|
_, err := s.updateOrder.Exec(order, fid)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s.LoadForums()
|
||||||
|
}
|
||||||
|
|
||||||
// ! Might be slightly inaccurate, if the sync.Map is constantly shifting and churning, but it'll stabilise eventually. Also, slow. Don't use this on every request x.x
|
// ! Might be slightly inaccurate, if the sync.Map is constantly shifting and churning, but it'll stabilise eventually. Also, slow. Don't use this on every request x.x
|
||||||
// Length returns the number of forums in the memory cache
|
// Length returns the number of forums in the memory cache
|
||||||
func (mfs *MemoryForumStore) Length() (length int) {
|
func (mfs *MemoryForumStore) Length() (length int) {
|
||||||
|
|
|
@ -39,6 +39,7 @@ type LevelPhrases struct {
|
||||||
type LanguagePack struct {
|
type LanguagePack struct {
|
||||||
Name string
|
Name string
|
||||||
IsoCode string
|
IsoCode string
|
||||||
|
//LastUpdated string
|
||||||
|
|
||||||
// Should we use a sync map or a struct for these? It would be nice, if we could keep all the phrases consistent.
|
// 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
|
Levels LevelPhrases
|
||||||
|
@ -70,6 +71,9 @@ func InitPhrases(lang string) error {
|
||||||
if f.IsDir() {
|
if f.IsDir() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
data, err := ioutil.ReadFile(path)
|
data, err := ioutil.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -158,6 +158,7 @@ func panelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (header
|
||||||
header.AddPreScriptAsync("template_" + name + tname + ".js")
|
header.AddPreScriptAsync("template_" + name + tname + ".js")
|
||||||
}
|
}
|
||||||
addPreScript("alert")
|
addPreScript("alert")
|
||||||
|
addPreScript("notice")
|
||||||
|
|
||||||
return header, stats, nil
|
return header, stats, nil
|
||||||
}
|
}
|
||||||
|
@ -267,6 +268,7 @@ func PrepResources(user *User, header *Header, theme *Theme) {
|
||||||
addPreScript("topics_topic")
|
addPreScript("topics_topic")
|
||||||
addPreScript("paginator")
|
addPreScript("paginator")
|
||||||
addPreScript("alert")
|
addPreScript("alert")
|
||||||
|
addPreScript("notice")
|
||||||
if user.Loggedin {
|
if user.Loggedin {
|
||||||
addPreScript("topic_c_edit_post")
|
addPreScript("topic_c_edit_post")
|
||||||
addPreScript("topic_c_attach_item")
|
addPreScript("topic_c_attach_item")
|
||||||
|
|
|
@ -481,6 +481,8 @@ func compileJSTemplates(wg *sync.WaitGroup, c *tmpl.CTemplateSet, themeName stri
|
||||||
|
|
||||||
tmpls.AddStd("topic_c_attach_item", "common.TopicCAttachItem", TopicCAttachItem{ID: 1, ImgSrc: "", Path: "", FullPath: ""})
|
tmpls.AddStd("topic_c_attach_item", "common.TopicCAttachItem", TopicCAttachItem{ID: 1, ImgSrc: "", Path: "", FullPath: ""})
|
||||||
|
|
||||||
|
tmpls.AddStd("notice", "string", "nonono")
|
||||||
|
|
||||||
var dirPrefix = "./tmpl_client/"
|
var dirPrefix = "./tmpl_client/"
|
||||||
var writeTemplate = func(name string, content string) {
|
var writeTemplate = func(name string, content string) {
|
||||||
log.Print("Writing template '" + name + "'")
|
log.Print("Writing template '" + name + "'")
|
||||||
|
@ -711,6 +713,10 @@ func initDefaultTmplFuncMap() {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmap["flush"] = func() interface{} {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
DefaultTemplateFuncMap = fmap
|
DefaultTemplateFuncMap = fmap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -74,6 +74,7 @@ type CTemplateSet struct {
|
||||||
|
|
||||||
logger *log.Logger
|
logger *log.Logger
|
||||||
loggerf *os.File
|
loggerf *os.File
|
||||||
|
lang string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCTemplateSet(in string) *CTemplateSet {
|
func NewCTemplateSet(in string) *CTemplateSet {
|
||||||
|
@ -112,9 +113,11 @@ func NewCTemplateSet(in string) *CTemplateSet {
|
||||||
"scope": true,
|
"scope": true,
|
||||||
"dyntmpl": true,
|
"dyntmpl": true,
|
||||||
"index": true,
|
"index": true,
|
||||||
|
"flush": true,
|
||||||
},
|
},
|
||||||
logger: log.New(f, "", log.LstdFlags),
|
logger: log.New(f, "", log.LstdFlags),
|
||||||
loggerf: f,
|
loggerf: f,
|
||||||
|
lang:in,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -445,6 +448,16 @@ func (c *CTemplateSet) compile(name string, content string, expects string, expe
|
||||||
return errors.New("invalid page struct value")
|
return errors.New("invalid page struct value")
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
if c.lang == "normal" {
|
||||||
|
fout += `var iw http.ResponseWriter
|
||||||
|
gzw, ok := w.(common.GzipResponseWriter)
|
||||||
|
if ok {
|
||||||
|
iw = gzw.ResponseWriter
|
||||||
|
}
|
||||||
|
_ = iw
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
if len(c.langIndexToName) > 0 {
|
if len(c.langIndexToName) > 0 {
|
||||||
fout += "var plist = phrases.GetTmplPhrasesBytes(" + fname + "_tmpl_phrase_id)\n"
|
fout += "var plist = phrases.GetTmplPhrasesBytes(" + fname + "_tmpl_phrase_id)\n"
|
||||||
}
|
}
|
||||||
|
@ -587,16 +600,7 @@ func (c *CTemplateSet) compileSwitch(con CContext, node parse.Node) {
|
||||||
c.detail("Expression:", expr)
|
c.detail("Expression:", expr)
|
||||||
// Simple member / guest optimisation for now
|
// Simple member / guest optimisation for now
|
||||||
// TODO: Expand upon this
|
// TODO: Expand upon this
|
||||||
var userExprs = []string{
|
userExprs, negUserExprs := buildUserExprs(con.RootHolder)
|
||||||
con.RootHolder + ".CurrentUser.Loggedin",
|
|
||||||
con.RootHolder + ".CurrentUser.IsSuperMod",
|
|
||||||
con.RootHolder + ".CurrentUser.IsAdmin",
|
|
||||||
}
|
|
||||||
var negUserExprs = []string{
|
|
||||||
"!" + con.RootHolder + ".CurrentUser.Loggedin",
|
|
||||||
"!" + con.RootHolder + ".CurrentUser.IsSuperMod",
|
|
||||||
"!" + con.RootHolder + ".CurrentUser.IsAdmin",
|
|
||||||
}
|
|
||||||
if c.guestOnly {
|
if c.guestOnly {
|
||||||
c.detail("optimising away member branch")
|
c.detail("optimising away member branch")
|
||||||
if inSlice(userExprs, expr) {
|
if inSlice(userExprs, expr) {
|
||||||
|
@ -1170,6 +1174,16 @@ ArgLoop:
|
||||||
out += "if err != nil {\nreturn err\n}\n}\n"
|
out += "if err != nil {\nreturn err\n}\n}\n"
|
||||||
literal = true
|
literal = true
|
||||||
break ArgLoop
|
break ArgLoop
|
||||||
|
case "flush":
|
||||||
|
if c.lang == "js" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out = "if fl, ok := iw.(http.Flusher); ok {\n"
|
||||||
|
out += "fl.Flush()\n"
|
||||||
|
out += "}\n"
|
||||||
|
literal = true
|
||||||
|
c.importMap["net/http"] = "net/http"
|
||||||
|
break ArgLoop
|
||||||
default:
|
default:
|
||||||
c.detail("Variable!")
|
c.detail("Variable!")
|
||||||
if len(node.Args) > (pos + 1) {
|
if len(node.Args) > (pos + 1) {
|
||||||
|
@ -1391,6 +1405,20 @@ func (c *CTemplateSet) retCall(name string, params ...interface{}) {
|
||||||
c.detail("returned from " + name + " => (" + pstr + ")")
|
c.detail("returned from " + name + " => (" + pstr + ")")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func buildUserExprs(holder string) ([]string,[]string) {
|
||||||
|
var userExprs = []string{
|
||||||
|
holder + ".CurrentUser.Loggedin",
|
||||||
|
holder + ".CurrentUser.IsSuperMod",
|
||||||
|
holder + ".CurrentUser.IsAdmin",
|
||||||
|
}
|
||||||
|
var negUserExprs = []string{
|
||||||
|
"!" + holder + ".CurrentUser.Loggedin",
|
||||||
|
"!" + holder + ".CurrentUser.IsSuperMod",
|
||||||
|
"!" + holder + ".CurrentUser.IsAdmin",
|
||||||
|
}
|
||||||
|
return userExprs, negUserExprs
|
||||||
|
}
|
||||||
|
|
||||||
func (c *CTemplateSet) compileVarSub(con CContext, varname string, val reflect.Value, assLines string, onEnd func(string) string) {
|
func (c *CTemplateSet) compileVarSub(con CContext, varname string, val reflect.Value, assLines string, onEnd func(string) string) {
|
||||||
c.dumpCall("compileVarSub", con, varname, val, assLines, onEnd)
|
c.dumpCall("compileVarSub", con, varname, val, assLines, onEnd)
|
||||||
defer c.retCall("compileVarSub")
|
defer c.retCall("compileVarSub")
|
||||||
|
@ -1438,16 +1466,7 @@ func (c *CTemplateSet) compileVarSub(con CContext, varname string, val reflect.V
|
||||||
// TODO: Take c.memberOnly into account
|
// TODO: Take c.memberOnly into account
|
||||||
// TODO: Make this a template fragment so more optimisations can be applied to this
|
// TODO: Make this a template fragment so more optimisations can be applied to this
|
||||||
// TODO: De-duplicate this logic
|
// TODO: De-duplicate this logic
|
||||||
var userExprs = []string{
|
userExprs, negUserExprs := buildUserExprs(con.RootHolder)
|
||||||
con.RootHolder + ".CurrentUser.Loggedin",
|
|
||||||
con.RootHolder + ".CurrentUser.IsSuperMod",
|
|
||||||
con.RootHolder + ".CurrentUser.IsAdmin",
|
|
||||||
}
|
|
||||||
var negUserExprs = []string{
|
|
||||||
"!" + con.RootHolder + ".CurrentUser.Loggedin",
|
|
||||||
"!" + con.RootHolder + ".CurrentUser.IsSuperMod",
|
|
||||||
"!" + con.RootHolder + ".CurrentUser.IsAdmin",
|
|
||||||
}
|
|
||||||
if c.guestOnly {
|
if c.guestOnly {
|
||||||
c.detail("optimising away member branch")
|
c.detail("optimising away member branch")
|
||||||
if inSlice(userExprs, varname) {
|
if inSlice(userExprs, varname) {
|
||||||
|
|
|
@ -33,8 +33,8 @@ var errWsNouser = errors.New("This user isn't connected via WebSockets")
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
adminStatsWatchers = make(map[*websocket.Conn]*WSUser)
|
adminStatsWatchers = make(map[*websocket.Conn]*WSUser)
|
||||||
topicListWatchers = make(map[*WSUser]bool)
|
topicListWatchers = make(map[*WSUser]struct{})
|
||||||
topicWatchers = make(map[int]map[*WSUser]bool)
|
topicWatchers = make(map[int]map[*WSUser]struct{})
|
||||||
}
|
}
|
||||||
|
|
||||||
//easyjson:json
|
//easyjson:json
|
||||||
|
@ -130,7 +130,7 @@ func wsPageResponses(wsUser *WSUser, conn *websocket.Conn, page string) {
|
||||||
// TODO: Optimise this to reduce the amount of contention
|
// TODO: Optimise this to reduce the amount of contention
|
||||||
case page == "/topics/":
|
case page == "/topics/":
|
||||||
topicListMutex.Lock()
|
topicListMutex.Lock()
|
||||||
topicListWatchers[wsUser] = true
|
topicListWatchers[wsUser] = struct{}{}
|
||||||
topicListMutex.Unlock()
|
topicListMutex.Unlock()
|
||||||
// TODO: Evict from page when permissions change? Or check user perms every-time before sending data?
|
// TODO: Evict from page when permissions change? Or check user perms every-time before sending data?
|
||||||
case strings.HasPrefix(page, "/topic/"):
|
case strings.HasPrefix(page, "/topic/"):
|
||||||
|
@ -169,9 +169,9 @@ func wsPageResponses(wsUser *WSUser, conn *websocket.Conn, page string) {
|
||||||
topicMutex.Lock()
|
topicMutex.Lock()
|
||||||
_, ok := topicWatchers[topic.ID]
|
_, ok := topicWatchers[topic.ID]
|
||||||
if !ok {
|
if !ok {
|
||||||
topicWatchers[topic.ID] = make(map[*WSUser]bool)
|
topicWatchers[topic.ID] = make(map[*WSUser]struct{})
|
||||||
}
|
}
|
||||||
topicWatchers[topic.ID][wsUser] = true
|
topicWatchers[topic.ID][wsUser] = struct{}{}
|
||||||
topicMutex.Unlock()
|
topicMutex.Unlock()
|
||||||
case page == "/panel/":
|
case page == "/panel/":
|
||||||
if !wsUser.User.IsSuperMod {
|
if !wsUser.User.IsSuperMod {
|
||||||
|
@ -243,9 +243,9 @@ func wsLeavePage(wsUser *WSUser, conn *websocket.Conn, page string) {
|
||||||
|
|
||||||
// TODO: Abstract this
|
// TODO: Abstract this
|
||||||
// TODO: Use odd-even sharding
|
// TODO: Use odd-even sharding
|
||||||
var topicListWatchers map[*WSUser]bool
|
var topicListWatchers map[*WSUser]struct{}
|
||||||
var topicListMutex sync.RWMutex
|
var topicListMutex sync.RWMutex
|
||||||
var topicWatchers map[int]map[*WSUser]bool // map[tid]watchers
|
var topicWatchers map[int]map[*WSUser]struct{} // map[tid]watchers
|
||||||
var topicMutex sync.RWMutex
|
var topicMutex sync.RWMutex
|
||||||
var adminStatsWatchers map[*websocket.Conn]*WSUser
|
var adminStatsWatchers map[*websocket.Conn]*WSUser
|
||||||
var adminStatsMutex sync.RWMutex
|
var adminStatsMutex sync.RWMutex
|
||||||
|
|
|
@ -35,6 +35,7 @@ func deactivateHdrive(plugin *c.Plugin) {
|
||||||
|
|
||||||
type Hyperspace struct {
|
type Hyperspace struct {
|
||||||
topicList atomic.Value
|
topicList atomic.Value
|
||||||
|
gzipTopicList atomic.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
func newHyperspace() *Hyperspace {
|
func newHyperspace() *Hyperspace {
|
||||||
|
@ -48,10 +49,7 @@ func tickHdriveWol(args ...interface{}) (skip bool, rerr c.RouteError) {
|
||||||
return tickHdrive(args)
|
return tickHdrive(args)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Find a better way of doing this
|
func dummyReqHdrive() http.ResponseWriter {
|
||||||
func tickHdrive(args ...interface{}) (skip bool, rerr c.RouteError) {
|
|
||||||
c.DebugLog("Refueling...")
|
|
||||||
w := httptest.NewRecorder()
|
|
||||||
req := httptest.NewRequest("get", "/topics/", bytes.NewReader(nil))
|
req := httptest.NewRequest("get", "/topics/", bytes.NewReader(nil))
|
||||||
user := c.GuestUser
|
user := c.GuestUser
|
||||||
|
|
||||||
|
@ -68,17 +66,48 @@ func tickHdrive(args ...interface{}) (skip bool, rerr c.RouteError) {
|
||||||
}
|
}
|
||||||
if w.Code != 200 {
|
if w.Code != 200 {
|
||||||
c.LogWarning(err)
|
c.LogWarning(err)
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return w
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Find a better way of doing this
|
||||||
|
func tickHdrive(args ...interface{}) (skip bool, rerr c.RouteError) {
|
||||||
|
c.DebugLog("Refueling...")
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
dummyReqHdrive(w)
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
buf.ReadFrom(w.Result().Body)
|
buf.ReadFrom(w.Result().Body)
|
||||||
hyperspace.topicList.Store(buf.Bytes())
|
hyperspace.topicList.Store(buf.Bytes())
|
||||||
|
|
||||||
|
w = httptest.NewRecorder()
|
||||||
|
w.Header().Set("Content-Encoding", "gzip")
|
||||||
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
gz := gzip.NewWriter(w)
|
||||||
|
w = c.GzipResponseWriter{Writer: gz, ResponseWriter: w}
|
||||||
|
|
||||||
|
dummyReqHdrive(w)
|
||||||
|
buf = new(bytes.Buffer)
|
||||||
|
buf.ReadFrom(w.Result().Body)
|
||||||
|
hyperspace.gzipTopicList.Store(buf.Bytes())
|
||||||
|
|
||||||
|
if w.Header().Get("Content-Encoding") == "gzip" {
|
||||||
|
gz.Close()
|
||||||
|
}
|
||||||
|
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func jumpHdrive(args ...interface{}) (skip bool, rerr c.RouteError) {
|
func jumpHdrive(args ...interface{}) (skip bool, rerr c.RouteError) {
|
||||||
tList := hyperspace.topicList.Load().([]byte)
|
var tList []byte
|
||||||
|
w := args[0].(http.ResponseWriter)
|
||||||
|
_, ok := w.(c.GzipResponseWriter)
|
||||||
|
if ok {
|
||||||
|
tList = hyperspace.gzipTopicList.Load().([]byte)
|
||||||
|
} else {
|
||||||
|
tList = hyperspace.topicList.Load().([]byte)
|
||||||
|
}
|
||||||
if len(tList) == 0 {
|
if len(tList) == 0 {
|
||||||
c.DebugLog("no topiclist in hyperspace")
|
c.DebugLog("no topiclist in hyperspace")
|
||||||
return false, nil
|
return false, nil
|
||||||
|
@ -101,7 +130,6 @@ func jumpHdrive(args ...interface{}) (skip bool, rerr c.RouteError) {
|
||||||
//c.DebugLog
|
//c.DebugLog
|
||||||
c.DebugLog("Successful jump")
|
c.DebugLog("Successful jump")
|
||||||
|
|
||||||
w := args[0].(http.ResponseWriter)
|
|
||||||
header := args[3].(*c.Header)
|
header := args[3].(*c.Header)
|
||||||
routes.FootHeaders(w, header)
|
routes.FootHeaders(w, header)
|
||||||
w.Write(tList)
|
w.Write(tList)
|
||||||
|
|
749
gen_router.go
749
gen_router.go
File diff suppressed because it is too large
Load Diff
|
@ -715,6 +715,8 @@
|
||||||
"option_yes":"Yes",
|
"option_yes":"Yes",
|
||||||
"option_no":"No",
|
"option_no":"No",
|
||||||
|
|
||||||
|
"panel_hints_reorder":"Drag to change the order",
|
||||||
|
|
||||||
"panel_back_to_site":"Back to Site",
|
"panel_back_to_site":"Back to Site",
|
||||||
"panel_welcome":"Welcome ",
|
"panel_welcome":"Welcome ",
|
||||||
"panel_menu_head":"Control Panel",
|
"panel_menu_head":"Control Panel",
|
||||||
|
@ -769,22 +771,24 @@
|
||||||
"panel_user_group":"Group",
|
"panel_user_group":"Group",
|
||||||
"panel_user_update_button":"Update User",
|
"panel_user_update_button":"Update User",
|
||||||
|
|
||||||
"panel_forums_head":"Forums",
|
"panel.forums_head":"Forums",
|
||||||
"panel_forums_hidden":"Hidden",
|
"panel.forums_hidden":"Hidden",
|
||||||
"panel_forums_edit_button_tooltip":"Edit Forum",
|
"panel.forums_edit_button_tooltip":"Edit Forum",
|
||||||
"panel_forums_edit_button_aria":"Edit Forum",
|
"panel.forums_edit_button_aria":"Edit Forum",
|
||||||
"panel_forums_update_button":"Update",
|
"panel.forums_update_button":"Update",
|
||||||
"panel_forums_delete_button_tooltip":"Delete Forum",
|
"panel.forums_delete_button_tooltip":"Delete Forum",
|
||||||
"panel_forums_delete_button_aria":"Delete Forum",
|
"panel.forums_delete_button_aria":"Delete Forum",
|
||||||
"panel_forums_full_edit_button":"Full Edit",
|
"panel.forums_full_edit_button":"Full Edit",
|
||||||
"panel_forums_create_head":"Add Forum",
|
"panel.forums_create_head":"Add Forum",
|
||||||
"panel_forums_create_name_label":"Name",
|
"panel.forums_create_name_label":"Name",
|
||||||
"panel_forums_create_name":"Super Secret Forum",
|
"panel.forums_create_name":"Super Secret Forum",
|
||||||
"panel_forums_create_description_label":"Description",
|
"panel.forums_create_description_label":"Description",
|
||||||
"panel_forums_create_description":"Where all the super secret stuff happens",
|
"panel.forums_create_description":"Where all the super secret stuff happens",
|
||||||
"panel_forums_active_label":"Active",
|
"panel.forums_active_label":"Active",
|
||||||
"panel_forums_preset_label":"Preset",
|
"panel.forums_preset_label":"Preset",
|
||||||
"panel_forums_create_button":"Add Forum",
|
"panel.forums_create_button":"Add Forum",
|
||||||
|
"panel.forums_update_order_button":"Update Order",
|
||||||
|
"panel.forums_order_updated":"The forums have been successfully updated",
|
||||||
|
|
||||||
"panel_forum_head_suffix":" Forum",
|
"panel_forum_head_suffix":" Forum",
|
||||||
"panel_forum_name":"Name",
|
"panel_forum_name":"Name",
|
||||||
|
@ -942,8 +946,9 @@
|
||||||
"panel_themes_menus_head":"Menus",
|
"panel_themes_menus_head":"Menus",
|
||||||
"panel_themes_menus_main":"Main Menu",
|
"panel_themes_menus_main":"Main Menu",
|
||||||
"panel_themes_menus_items_head":"Menu Items",
|
"panel_themes_menus_items_head":"Menu Items",
|
||||||
"panel_themes_menus_item_edit_button_aria":"Edit menu item",
|
"panel_themes_menus_items_edit_button_aria":"Edit menu item",
|
||||||
"panel_themes_menus_item_delete_button_aria":"Delete menu item",
|
"panel_themes_menus_items_delete_button_aria":"Delete menu item",
|
||||||
|
"panel_themes_menus_items_update_button":"Update Order",
|
||||||
|
|
||||||
"panel_themes_menus_edit_head":"Menu Editor",
|
"panel_themes_menus_edit_head":"Menu Editor",
|
||||||
"panel_themes_menus_create_head":"Create Menu Item",
|
"panel_themes_menus_create_head":"Create Menu Item",
|
||||||
|
|
|
@ -30,6 +30,7 @@ func init() {
|
||||||
addPatch(15, patch15)
|
addPatch(15, patch15)
|
||||||
addPatch(16, patch16)
|
addPatch(16, patch16)
|
||||||
addPatch(17, patch17)
|
addPatch(17, patch17)
|
||||||
|
addPatch(18, patch18)
|
||||||
}
|
}
|
||||||
|
|
||||||
func patch0(scanner *bufio.Scanner) (err error) {
|
func patch0(scanner *bufio.Scanner) (err error) {
|
||||||
|
@ -588,3 +589,7 @@ func patch17(scanner *bufio.Scanner) error {
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func patch18(scanner *bufio.Scanner) error {
|
||||||
|
return execStmt(qgen.Builder.AddColumn("forums", tblColumn{"order", "int", 0, false, false, "0"}, nil))
|
||||||
|
}
|
|
@ -14,6 +14,14 @@ var wsBackoff = 0;
|
||||||
// Topic move
|
// Topic move
|
||||||
var forumToMoveTo = 0;
|
var forumToMoveTo = 0;
|
||||||
|
|
||||||
|
function pushNotice(msg) {
|
||||||
|
let aBox = document.getElementsByClassName("alertbox")[0];
|
||||||
|
let div = document.createElement('div');
|
||||||
|
div.innerHTML = Template_notice(msg).trim();
|
||||||
|
aBox.appendChild(div);
|
||||||
|
runInitHook("after_notice");
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Write a friendlier error handler which uses a .notice or something, we could have a specialised one for alerts
|
// TODO: Write a friendlier error handler which uses a .notice or something, we could have a specialised one for alerts
|
||||||
function ajaxError(xhr,status,errstr) {
|
function ajaxError(xhr,status,errstr) {
|
||||||
console.log("The AJAX request failed");
|
console.log("The AJAX request failed");
|
||||||
|
|
|
@ -28,9 +28,9 @@ function runHook(name, ...args) {
|
||||||
console.log("Running hook '"+name+"'");
|
console.log("Running hook '"+name+"'");
|
||||||
|
|
||||||
let hook = hooks[name];
|
let hook = hooks[name];
|
||||||
for (const index in hook) {
|
let ret;
|
||||||
hook[index](...args);
|
for (const index in hook) ret = hook[index](...args);
|
||||||
}
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
function addHook(name, callback) {
|
function addHook(name, callback) {
|
||||||
|
@ -40,15 +40,14 @@ function addHook(name, callback) {
|
||||||
|
|
||||||
// InitHooks are slightly special, as if they are run, then any adds after the initial run will run immediately, this is to deal with the async nature of script loads
|
// InitHooks are slightly special, as if they are run, then any adds after the initial run will run immediately, this is to deal with the async nature of script loads
|
||||||
function runInitHook(name, ...args) {
|
function runInitHook(name, ...args) {
|
||||||
runHook(name,...args);
|
let ret = runHook(name,...args);
|
||||||
ranInitHooks[name] = true;
|
ranInitHooks[name] = true;
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
function addInitHook(name, callback) {
|
function addInitHook(name, callback) {
|
||||||
addHook(name, callback);
|
addHook(name, callback);
|
||||||
if(name in ranInitHooks) {
|
if(name in ranInitHooks) callback();
|
||||||
callback();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Temporary hack for templates
|
// Temporary hack for templates
|
||||||
|
@ -175,14 +174,14 @@ function RelativeTime(date) {
|
||||||
return date;
|
return date;
|
||||||
}
|
}
|
||||||
|
|
||||||
function initPhrases(loggedIn) {
|
function initPhrases(loggedIn, panel = false) {
|
||||||
console.log("in initPhrases")
|
console.log("in initPhrases")
|
||||||
console.log("tmlInits:",tmplInits)
|
console.log("tmlInits:",tmplInits)
|
||||||
let e = "";
|
let e = "";
|
||||||
if(loggedIn) {
|
if(loggedIn && !panel) e = ",topic_list,topic";
|
||||||
e = ",topic"
|
else if(panel) e = ",analytics,panel"; // TODO: Request phrases for just one section of the control panel?
|
||||||
}
|
else e = ",topic_list";
|
||||||
fetchPhrases("status,topic_list,alerts,paginator,analytics"+e) // TODO: Break this up?
|
fetchPhrases("status,alerts,paginator"+e) // TODO: Break this up?
|
||||||
}
|
}
|
||||||
|
|
||||||
function fetchPhrases(plist) {
|
function fetchPhrases(plist) {
|
||||||
|
@ -219,15 +218,18 @@ function fetchPhrases(plist) {
|
||||||
(() => {
|
(() => {
|
||||||
runInitHook("pre_iife");
|
runInitHook("pre_iife");
|
||||||
let loggedIn = document.head.querySelector("[property='x-loggedin']").content == "true";
|
let loggedIn = document.head.querySelector("[property='x-loggedin']").content == "true";
|
||||||
|
let panel = window.location.pathname.startsWith("/panel/");
|
||||||
|
|
||||||
if(!window.location.pathname.startsWith("/panel/")) {
|
let toLoad = 1;
|
||||||
let toLoad = 2;
|
|
||||||
// TODO: Shunt this into loggedIn if there aren't any search and filter widgets?
|
// TODO: Shunt this into loggedIn if there aren't any search and filter widgets?
|
||||||
let q = (f) => {
|
let q = (f) => {
|
||||||
toLoad--;
|
toLoad--;
|
||||||
if(toLoad===0) initPhrases(loggedIn);
|
if(toLoad===0) initPhrases(loggedIn,panel);
|
||||||
if(f) throw("template function not found");
|
if(f) throw("template function not found");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if(!panel) {
|
||||||
|
toLoad += 2;
|
||||||
if(loggedIn) {
|
if(loggedIn) {
|
||||||
toLoad += 2;
|
toLoad += 2;
|
||||||
notifyOnScriptW("template_topic_c_edit_post", () => q(!Template_topic_c_edit_post));
|
notifyOnScriptW("template_topic_c_edit_post", () => q(!Template_topic_c_edit_post));
|
||||||
|
@ -235,9 +237,8 @@ function fetchPhrases(plist) {
|
||||||
}
|
}
|
||||||
notifyOnScriptW("template_topics_topic", () => q(!Template_topics_topic));
|
notifyOnScriptW("template_topics_topic", () => q(!Template_topics_topic));
|
||||||
notifyOnScriptW("template_paginator", () => q(!Template_paginator));
|
notifyOnScriptW("template_paginator", () => q(!Template_paginator));
|
||||||
} else {
|
|
||||||
initPhrases(false);
|
|
||||||
}
|
}
|
||||||
|
notifyOnScriptW("template_notice", () => q(!Template_notice));
|
||||||
|
|
||||||
if(loggedIn) {
|
if(loggedIn) {
|
||||||
fetch("/api/me/")
|
fetch("/api/me/")
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
(() => {
|
||||||
|
addInitHook("end_init", () => {
|
||||||
|
|
||||||
|
formVars = {
|
||||||
|
'forum_active': ['Hide','Show'],
|
||||||
|
'forum_preset': ['all','announce','members','staff','admins','archive','custom']
|
||||||
|
};
|
||||||
|
var forums = {};
|
||||||
|
let items = document.getElementsByClassName("panel_forum_item");
|
||||||
|
for(let i = 0; item = items[i]; i++) forums[i] = item.getAttribute("data-fid");
|
||||||
|
console.log("forums:",forums);
|
||||||
|
|
||||||
|
Sortable.create(document.getElementById("panel_forums"), {
|
||||||
|
sort: true,
|
||||||
|
onEnd: (evt) => {
|
||||||
|
console.log("pre forums: ", forums)
|
||||||
|
console.log("evt: ", evt)
|
||||||
|
let oldFid = forums[evt.newIndex];
|
||||||
|
forums[evt.oldIndex] = oldFid;
|
||||||
|
let newFid = evt.item.getAttribute("data-fid");
|
||||||
|
console.log("newFid: ", newFid);
|
||||||
|
forums[evt.newIndex] = newFid;
|
||||||
|
console.log("post forums: ", forums);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById("panel_forums_order_button").addEventListener("click", () => {
|
||||||
|
let req = new XMLHttpRequest();
|
||||||
|
if(!req) {
|
||||||
|
console.log("Failed to create request");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
req.onreadystatechange = () => {
|
||||||
|
try {
|
||||||
|
if(req.readyState!==XMLHttpRequest.DONE) return;
|
||||||
|
// TODO: Signal the error with a notice
|
||||||
|
if(req.status!==200) return;
|
||||||
|
|
||||||
|
let resp = JSON.parse(req.responseText);
|
||||||
|
console.log("resp: ", resp);
|
||||||
|
// TODO: Should we move other notices into TmplPhrases like this one?
|
||||||
|
pushNotice(phraseBox["panel"]["panel.forums_order_updated"]);
|
||||||
|
if(resp.success==1) return;
|
||||||
|
} catch(ex) {
|
||||||
|
console.error("exception: ", ex)
|
||||||
|
}
|
||||||
|
console.trace();
|
||||||
|
}
|
||||||
|
// ? - Is encodeURIComponent the right function for this?
|
||||||
|
req.open("POST","/panel/forums/order/edit/submit/?session=" + encodeURIComponent(me.User.Session));
|
||||||
|
req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
|
||||||
|
let items = "";
|
||||||
|
for(let i = 0; item = forums[i];i++) items += item+",";
|
||||||
|
if(items.length > 0) items = items.slice(0,-1);
|
||||||
|
req.send("js=1&items={"+items+"}");
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
})();
|
|
@ -151,6 +151,7 @@ func panelRoutes() *RouteGroup {
|
||||||
Action("panel.ForumsCreateSubmit", "/panel/forums/create/"),
|
Action("panel.ForumsCreateSubmit", "/panel/forums/create/"),
|
||||||
Action("panel.ForumsDelete", "/panel/forums/delete/", "extraData"),
|
Action("panel.ForumsDelete", "/panel/forums/delete/", "extraData"),
|
||||||
Action("panel.ForumsDeleteSubmit", "/panel/forums/delete/submit/", "extraData"),
|
Action("panel.ForumsDeleteSubmit", "/panel/forums/delete/submit/", "extraData"),
|
||||||
|
Action("panel.ForumsOrderSubmit", "/panel/forums/order/edit/submit/"),
|
||||||
View("panel.ForumsEdit", "/panel/forums/edit/", "extraData"),
|
View("panel.ForumsEdit", "/panel/forums/edit/", "extraData"),
|
||||||
Action("panel.ForumsEditSubmit", "/panel/forums/edit/submit/", "extraData"),
|
Action("panel.ForumsEditSubmit", "/panel/forums/edit/submit/", "extraData"),
|
||||||
Action("panel.ForumsEditPermsSubmit", "/panel/forums/edit/perms/submit/", "extraData"),
|
Action("panel.ForumsEditPermsSubmit", "/panel/forums/edit/perms/submit/", "extraData"),
|
||||||
|
|
20
routes.go
20
routes.go
|
@ -145,6 +145,8 @@ var phraseWhitelist = []string{
|
||||||
"alerts",
|
"alerts",
|
||||||
"paginator",
|
"paginator",
|
||||||
"analytics",
|
"analytics",
|
||||||
|
|
||||||
|
"panel", // We're going to handle this specially below as this is a security boundary
|
||||||
}
|
}
|
||||||
|
|
||||||
func routeAPIPhrases(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError {
|
func routeAPIPhrases(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError {
|
||||||
|
@ -199,13 +201,22 @@ func routeAPIPhrases(w http.ResponseWriter, r *http.Request, user c.User) c.Rout
|
||||||
var ok = false
|
var ok = false
|
||||||
for _, item := range phraseWhitelist {
|
for _, item := range phraseWhitelist {
|
||||||
if strings.HasPrefix(positive, item) {
|
if strings.HasPrefix(positive, item) {
|
||||||
|
// TODO: Break this down into smaller security boundaries based on control panel sections?
|
||||||
|
if strings.HasPrefix(positive,"panel") {
|
||||||
|
if user.IsSuperMod {
|
||||||
ok = true
|
ok = true
|
||||||
|
w.Header().Set("Cache-Control", "private")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ok = true
|
||||||
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !ok {
|
if !ok {
|
||||||
return c.PreErrorJS("Outside of phrase prefix whitelist", w, r)
|
return c.PreErrorJS("Outside of phrase prefix whitelist", w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
pPhrases, ok := phrases.GetTmplPhrasesByPrefix(positive)
|
pPhrases, ok := phrases.GetTmplPhrasesByPrefix(positive)
|
||||||
if !ok {
|
if !ok {
|
||||||
return c.PreErrorJS("No such prefix", w, r)
|
return c.PreErrorJS("No such prefix", w, r)
|
||||||
|
@ -219,13 +230,22 @@ func routeAPIPhrases(w http.ResponseWriter, r *http.Request, user c.User) c.Rout
|
||||||
var ok = false
|
var ok = false
|
||||||
for _, item := range phraseWhitelist {
|
for _, item := range phraseWhitelist {
|
||||||
if strings.HasPrefix(positives[0], item) {
|
if strings.HasPrefix(positives[0], item) {
|
||||||
|
// TODO: Break this down into smaller security boundaries based on control panel sections?
|
||||||
|
if strings.HasPrefix(positives[0],"panel") {
|
||||||
|
if user.IsSuperMod {
|
||||||
ok = true
|
ok = true
|
||||||
|
w.Header().Set("Cache-Control", "private")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ok = true
|
||||||
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !ok {
|
if !ok {
|
||||||
return c.PreErrorJS("Outside of phrase prefix whitelist", w, r)
|
return c.PreErrorJS("Outside of phrase prefix whitelist", w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
pPhrases, ok := phrases.GetTmplPhrasesByPrefix(positives[0])
|
pPhrases, ok := phrases.GetTmplPhrasesByPrefix(positives[0])
|
||||||
if !ok {
|
if !ok {
|
||||||
return c.PreErrorJS("No such prefix", w, r)
|
return c.PreErrorJS("No such prefix", w, r)
|
||||||
|
|
|
@ -22,7 +22,8 @@ type AnalyticsTimeRange struct {
|
||||||
Range string
|
Range string
|
||||||
}
|
}
|
||||||
|
|
||||||
func analyticsTimeRange(rawTimeRange string) (timeRange AnalyticsTimeRange, err error) {
|
func analyticsTimeRange(rawTimeRange string) (*AnalyticsTimeRange, error) {
|
||||||
|
timeRange := &AnalyticsTimeRange{}
|
||||||
timeRange.Quantity = 6
|
timeRange.Quantity = 6
|
||||||
timeRange.Unit = "hour"
|
timeRange.Unit = "hour"
|
||||||
timeRange.Slices = 12
|
timeRange.Slices = 12
|
||||||
|
@ -78,7 +79,7 @@ func analyticsTimeRange(rawTimeRange string) (timeRange AnalyticsTimeRange, err
|
||||||
return timeRange, nil
|
return timeRange, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func analyticsTimeRangeToLabelList(timeRange AnalyticsTimeRange) (revLabelList []int64, labelList []int64, viewMap map[int64]int64) {
|
func analyticsTimeRangeToLabelList(timeRange *AnalyticsTimeRange) (revLabelList []int64, labelList []int64, viewMap map[int64]int64) {
|
||||||
viewMap = make(map[int64]int64)
|
viewMap = make(map[int64]int64)
|
||||||
var currentTime = time.Now().Unix()
|
var currentTime = time.Now().Unix()
|
||||||
for i := 1; i <= timeRange.Slices; i++ {
|
for i := 1; i <= timeRange.Slices; i++ {
|
||||||
|
|
|
@ -19,6 +19,8 @@ func Forums(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError {
|
||||||
if !user.Perms.ManageForums {
|
if !user.Perms.ManageForums {
|
||||||
return c.NoPermissions(w, r, user)
|
return c.NoPermissions(w, r, user)
|
||||||
}
|
}
|
||||||
|
basePage.Header.AddScript("Sortable-1.4.0/Sortable.min.js")
|
||||||
|
basePage.Header.AddScriptAsync("panel_forums.js")
|
||||||
|
|
||||||
// TODO: Paginate this?
|
// TODO: Paginate this?
|
||||||
var forumList []interface{}
|
var forumList []interface{}
|
||||||
|
@ -130,6 +132,31 @@ func ForumsDeleteSubmit(w http.ResponseWriter, r *http.Request, user c.User, sfi
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ForumsOrderSubmit(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError {
|
||||||
|
_, ferr := c.SimplePanelUserCheck(w, r, &user)
|
||||||
|
if ferr != nil {
|
||||||
|
return ferr
|
||||||
|
}
|
||||||
|
isJs := (r.PostFormValue("js") == "1")
|
||||||
|
if !user.Perms.ManageForums {
|
||||||
|
return c.NoPermissionsJSQ(w, r, user, isJs)
|
||||||
|
}
|
||||||
|
sitems := strings.TrimSuffix(strings.TrimPrefix(r.PostFormValue("items"), "{"), "}")
|
||||||
|
//fmt.Printf("sitems: %+v\n", sitems)
|
||||||
|
|
||||||
|
var updateMap = make(map[int]int)
|
||||||
|
for index, sfid := range strings.Split(sitems, ",") {
|
||||||
|
fid, err := strconv.Atoi(sfid)
|
||||||
|
if err != nil {
|
||||||
|
return c.LocalErrorJSQ("Invalid integer in forum list", w, r, user, isJs)
|
||||||
|
}
|
||||||
|
updateMap[fid] = index
|
||||||
|
}
|
||||||
|
c.Forums.UpdateOrder(updateMap)
|
||||||
|
|
||||||
|
return successRedirect("/panel/forums/", w, r, isJs)
|
||||||
|
}
|
||||||
|
|
||||||
func ForumsEdit(w http.ResponseWriter, r *http.Request, user c.User, sfid string) c.RouteError {
|
func ForumsEdit(w http.ResponseWriter, r *http.Request, user c.User, sfid string) c.RouteError {
|
||||||
basePage, ferr := buildBasePage(w, r, &user, "edit_forum", "forums")
|
basePage, ferr := buildBasePage(w, r, &user, "edit_forum", "forums")
|
||||||
if ferr != nil {
|
if ferr != nil {
|
||||||
|
@ -333,7 +360,7 @@ func ForumsEditPermsAdvance(w http.ResponseWriter, r *http.Request, user c.User,
|
||||||
addNameLangToggle("MoveTopic", forumPerms.MoveTopic)
|
addNameLangToggle("MoveTopic", forumPerms.MoveTopic)
|
||||||
|
|
||||||
if r.FormValue("updated") == "1" {
|
if r.FormValue("updated") == "1" {
|
||||||
basePage.AddNotice("panel_forums_perms_updated")
|
basePage.AddNotice("panel_forum_perms_updated")
|
||||||
}
|
}
|
||||||
|
|
||||||
pi := c.PanelEditForumGroupPage{basePage, forum.ID, gid, forum.Name, forum.Desc, forum.Active, forum.Preset, formattedPermList}
|
pi := c.PanelEditForumGroupPage{basePage, forum.ID, gid, forum.Name, forum.Desc, forum.Active, forum.Preset, formattedPermList}
|
||||||
|
|
|
@ -3,6 +3,7 @@ CREATE TABLE [forums] (
|
||||||
[name] nvarchar (100) not null,
|
[name] nvarchar (100) not null,
|
||||||
[desc] nvarchar (200) not null,
|
[desc] nvarchar (200) not null,
|
||||||
[active] bit DEFAULT 1 not null,
|
[active] bit DEFAULT 1 not null,
|
||||||
|
[order] int DEFAULT 0 not null,
|
||||||
[topicCount] int DEFAULT 0 not null,
|
[topicCount] int DEFAULT 0 not null,
|
||||||
[preset] nvarchar (100) DEFAULT '' not null,
|
[preset] nvarchar (100) DEFAULT '' not null,
|
||||||
[parentID] int DEFAULT 0 not null,
|
[parentID] int DEFAULT 0 not null,
|
||||||
|
|
|
@ -3,6 +3,7 @@ CREATE TABLE `forums` (
|
||||||
`name` varchar(100) not null,
|
`name` varchar(100) not null,
|
||||||
`desc` varchar(200) not null,
|
`desc` varchar(200) not null,
|
||||||
`active` boolean DEFAULT 1 not null,
|
`active` boolean DEFAULT 1 not null,
|
||||||
|
`order` int DEFAULT 0 not null,
|
||||||
`topicCount` int DEFAULT 0 not null,
|
`topicCount` int DEFAULT 0 not null,
|
||||||
`preset` varchar(100) DEFAULT '' not null,
|
`preset` varchar(100) DEFAULT '' not null,
|
||||||
`parentID` int DEFAULT 0 not null,
|
`parentID` int DEFAULT 0 not null,
|
||||||
|
|
|
@ -3,6 +3,7 @@ CREATE TABLE "forums" (
|
||||||
`name` varchar (100) not null,
|
`name` varchar (100) not null,
|
||||||
`desc` varchar (200) not null,
|
`desc` varchar (200) not null,
|
||||||
`active` boolean DEFAULT 1 not null,
|
`active` boolean DEFAULT 1 not null,
|
||||||
|
`order` int DEFAULT 0 not null,
|
||||||
`topicCount` int DEFAULT 0 not null,
|
`topicCount` int DEFAULT 0 not null,
|
||||||
`preset` varchar (100) DEFAULT '' not null,
|
`preset` varchar (100) DEFAULT '' not null,
|
||||||
`parentID` int DEFAULT 0 not null,
|
`parentID` int DEFAULT 0 not null,
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
{{range .Header.PreScriptsAsync}}
|
{{range .Header.PreScriptsAsync}}
|
||||||
<script async type="text/javascript" src="/static/{{.}}"></script>{{end}}
|
<script async type="text/javascript" src="/static/{{.}}"></script>{{end}}
|
||||||
<meta property="x-loggedin" content="{{.CurrentUser.Loggedin}}" />
|
<meta property="x-loggedin" content="{{.CurrentUser.Loggedin}}" />
|
||||||
<script type="text/javascript" src="/static/init.js?i=3"></script>
|
<script type="text/javascript" src="/static/init.js?i=4"></script>
|
||||||
{{range .Header.ScriptsAsync}}
|
{{range .Header.ScriptsAsync}}
|
||||||
<script async type="text/javascript" src="/static/{{.}}"></script>{{end}}
|
<script async type="text/javascript" src="/static/{{.}}"></script>{{end}}
|
||||||
<script type="text/javascript" src="/static/jquery-3.1.1.min.js"></script>
|
<script type="text/javascript" src="/static/jquery-3.1.1.min.js"></script>
|
||||||
|
@ -25,7 +25,7 @@
|
||||||
{{if .GoogSiteVerify}}<meta name="google-site-verification" content="{{.GoogSiteVerify}}" />{{end}}
|
{{if .GoogSiteVerify}}<meta name="google-site-verification" content="{{.GoogSiteVerify}}" />{{end}}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
{{if not .CurrentUser.IsSuperMod}}<style>.supermod_only { display: none !important; }</style>{{end}}
|
{{if not .CurrentUser.IsSuperMod}}<style>.supermod_only { display: none !important; }</style>{{end}}{{flush}}
|
||||||
<div id="container" class="container">
|
<div id="container" class="container">
|
||||||
{{/**<!--<div class="navrow">-->**/}}
|
{{/**<!--<div class="navrow">-->**/}}
|
||||||
<div class="left_of_nav">{{dock "leftOfNav" .Header }}</div>
|
<div class="left_of_nav">{{dock "leftOfNav" .Header }}</div>
|
||||||
|
|
|
@ -2,60 +2,63 @@
|
||||||
|
|
||||||
<div class="colstack panel_stack">
|
<div class="colstack panel_stack">
|
||||||
{{template "panel_menu.html" . }}
|
{{template "panel_menu.html" . }}
|
||||||
<script>var formVars = {
|
|
||||||
'forum_active': ['Hide','Show'],
|
|
||||||
'forum_preset': ['all','announce','members','staff','admins','archive','custom']};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<main class="colstack_right">
|
<main class="colstack_right">
|
||||||
{{template "panel_before_head.html" . }}
|
{{template "panel_before_head.html" . }}
|
||||||
<div class="colstack_item colstack_head">
|
<div class="colstack_item colstack_head">
|
||||||
<div class="rowitem"><h1>{{lang "panel_forums_head"}}</h1></div>
|
<div class="rowitem">
|
||||||
|
<h1>{{lang "panel.forums_head"}}</h1>
|
||||||
|
<h2 class="hguide">{{lang "panel_hints_reorder"}}</h2>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="panel_forums" class="colstack_item rowlist">
|
<div id="panel_forums" class="colstack_item rowlist">
|
||||||
{{range .ItemList}}
|
{{range .ItemList}}
|
||||||
<div class="rowitem editable_parent">
|
<div data-fid="{{.ID}}" class="rowitem editable_parent panel_forum_item{{if not .Desc}} forum_no_desc{{end}}">
|
||||||
|
<span class="grip"></span>
|
||||||
<span id="panel_forums_left_box">
|
<span id="panel_forums_left_box">
|
||||||
{{/** TODO: Make sure the forum_active_name class is set and unset when the activity status of this forum is changed **/}}
|
{{/** TODO: Make sure the forum_active_name class is set and unset when the activity status of this forum is changed **/}}
|
||||||
<a data-field="forum_name" data-type="text" class="editable_block forum_name{{if not .Active}} forum_active_name{{end}}">{{.Name}}</a>
|
<a data-field="forum_name" data-type="text" class="editable_block forum_name{{if not .Active}} forum_active_name{{end}}">{{.Name}}</a>
|
||||||
<br /><span data-field="forum_desc" data-type="text" class="editable_block forum_desc rowsmall">{{.Desc}}</span>
|
<br /><span data-field="forum_desc" data-type="text" class="editable_block forum_desc rowsmall">{{.Desc}}</span>
|
||||||
</span>
|
</span>
|
||||||
<span class="panel_floater">
|
<span class="panel_floater">
|
||||||
<span data-field="forum_active" data-type="list" class="panel_tag editable_block forum_active {{if .Active}}forum_active_Show" data-value="1{{else}}forum_active_Hide" data-value="0{{end}}" title="{{lang "panel_forums_hidden"}}"></span>
|
<span data-field="forum_active" data-type="list" class="panel_tag editable_block forum_active forum_active_{{if .Active}}Show" data-value="1{{else}}Hide" data-value="0{{end}}" title="{{lang "panel.forums_hidden"}}"></span>
|
||||||
<span data-field="forum_preset" data-type="list" data-value="{{.Preset}}" class="panel_tag editable_block forum_preset forum_preset_{{.Preset}}" title="{{.PresetLang}}"></span>
|
<span data-field="forum_preset" data-type="list" data-value="{{.Preset}}" class="panel_tag editable_block forum_preset forum_preset_{{.Preset}}" title="{{.PresetLang}}"></span>
|
||||||
</span>
|
</span>
|
||||||
<span class="panel_buttons">
|
<span class="panel_buttons">
|
||||||
<a class="panel_tag edit_fields hide_on_edit panel_right_button edit_button" title="{{lang "panel_forums_edit_button_tooltip"}}" aria-label="{{lang "panel_forums_edit_button_aria"}}"></a>
|
<a class="panel_tag edit_fields hide_on_edit panel_right_button edit_button" title="{{lang "panel.forums_edit_button_tooltip"}}" aria-label="{{lang "panel.forums_edit_button_aria"}}"></a>
|
||||||
<a class="panel_right_button has_inner_button show_on_edit" href="/panel/forums/edit/submit/{{.ID}}"><button class='panel_tag submit_edit' type='submit'>{{lang "panel_forums_update_button"}}</button></a>
|
<a class="panel_right_button has_inner_button show_on_edit" href="/panel/forums/edit/submit/{{.ID}}"><button class='panel_tag submit_edit' type='submit'>{{lang "panel.forums_update_button"}}</button></a>
|
||||||
{{if gt .ID 1}}<a href="/panel/forums/delete/{{.ID}}?session={{$.CurrentUser.Session}}" class="panel_tag panel_right_button hide_on_edit delete_button" title="{{lang "panel_forums_delete_button_tooltip"}}" aria-label="{{lang "panel_forums_delete_button_aria"}}"></a>{{end}}
|
{{if gt .ID 1}}<a href="/panel/forums/delete/{{.ID}}?session={{$.CurrentUser.Session}}" class="panel_tag panel_right_button hide_on_edit delete_button" title="{{lang "panel.forums_delete_button_tooltip"}}" aria-label="{{lang "panel.forums_delete_button_aria"}}"></a>{{end}}
|
||||||
<a href="/panel/forums/edit/{{.ID}}" class="panel_tag panel_right_button has_inner_button show_on_edit"><button>{{lang "panel_forums_full_edit_button"}}</button></a>
|
<a href="/panel/forums/edit/{{.ID}}" class="panel_tag panel_right_button has_inner_button show_on_edit"><button>{{lang "panel.forums_full_edit_button"}}</button></a>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="colstack_item rowlist panel_submitrow">
|
||||||
|
<div class="rowitem"><button id="panel_forums_order_button" class="formbutton">{{lang "panel.forums_update_order_button"}}</button></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="colstack_item colstack_head">
|
<div class="colstack_item colstack_head">
|
||||||
<div class="rowitem"><h1>{{lang "panel_forums_create_head"}}</h1></div>
|
<div class="rowitem"><h1>{{lang "panel.forums_create_head"}}</h1></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="colstack_item the_form">
|
<div class="colstack_item the_form">
|
||||||
<form action="/panel/forums/create/?session={{.CurrentUser.Session}}" method="post">
|
<form action="/panel/forums/create/?session={{.CurrentUser.Session}}" method="post">
|
||||||
<div class="formrow">
|
<div class="formrow">
|
||||||
<div class="formitem formlabel"><a>{{lang "panel_forums_create_name_label"}}</a></div>
|
<div class="formitem formlabel"><a>{{lang "panel.forums_create_name_label"}}</a></div>
|
||||||
<div class="formitem"><input name="forum-name" type="text" placeholder="{{lang "panel_forums_create_name"}}" /></div>
|
<div class="formitem"><input name="forum-name" type="text" placeholder="{{lang "panel.forums_create_name"}}" /></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="formrow">
|
<div class="formrow">
|
||||||
<div class="formitem formlabel"><a>{{lang "panel_forums_create_description_label"}}</a></div>
|
<div class="formitem formlabel"><a>{{lang "panel.forums_create_description_label"}}</a></div>
|
||||||
<div class="formitem"><input name="forum-desc" type="text" placeholder="{{lang "panel_forums_create_description"}}" /></div>
|
<div class="formitem"><input name="forum-desc" type="text" placeholder="{{lang "panel.forums_create_description"}}" /></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="formrow">
|
<div class="formrow">
|
||||||
<div class="formitem formlabel"><a>{{lang "panel_forums_active_label"}}</a></div>
|
<div class="formitem formlabel"><a>{{lang "panel.forums_active_label"}}</a></div>
|
||||||
<div class="formitem"><select name="forum-active">
|
<div class="formitem"><select name="forum-active">
|
||||||
<option selected value="1">{{lang "option_yes"}}</option>
|
<option selected value="1">{{lang "option_yes"}}</option>
|
||||||
<option value="0">{{lang "option_no"}}</option>
|
<option value="0">{{lang "option_no"}}</option>
|
||||||
</select></div>
|
</select></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="formrow">
|
<div class="formrow">
|
||||||
<div class="formitem formlabel"><a>{{lang "panel_forums_preset_label"}}</a></div>
|
<div class="formitem formlabel"><a>{{lang "panel.forums_preset_label"}}</a></div>
|
||||||
<div class="formitem"><select name="forum-preset">
|
<div class="formitem"><select name="forum-preset">
|
||||||
<option selected value="all">{{lang "panel_preset_everyone"}}</option>
|
<option selected value="all">{{lang "panel_preset_everyone"}}</option>
|
||||||
<option value="announce">{{lang "panel_preset_announcements"}}</option>
|
<option value="announce">{{lang "panel_preset_announcements"}}</option>
|
||||||
|
@ -67,7 +70,7 @@
|
||||||
</select></div>
|
</select></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="formrow">
|
<div class="formrow">
|
||||||
<div class="formitem"><button name="panel-button" class="formbutton">{{lang "panel_forums_create_button"}}</button></div>
|
<div class="formitem"><button name="panel-button" class="formbutton">{{lang "panel.forums_create_button"}}</button></div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -4,20 +4,24 @@
|
||||||
<main class="colstack_right">
|
<main class="colstack_right">
|
||||||
{{template "panel_before_head.html" . }}
|
{{template "panel_before_head.html" . }}
|
||||||
<div class="colstack_item colstack_head">
|
<div class="colstack_item colstack_head">
|
||||||
<div class="rowitem"><h1>{{lang "panel_themes_menus_items_head"}}</h1></div>
|
<div class="rowitem">
|
||||||
|
<h1>{{lang "panel_themes_menus_items_head"}}</h1>
|
||||||
|
<h2 class="hguide">{{lang "panel_hints_reorder"}}</h2>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="panel_menu_item_holder" class="colstack_item rowlist">
|
<div id="panel_menu_item_holder" class="colstack_item rowlist">
|
||||||
{{range .ItemList}}
|
{{range .ItemList}}
|
||||||
<div class="panel_menu_item rowitem panel_compactrow editable_parent" data-miid="{{.ID}}">
|
<div class="panel_menu_item rowitem panel_compactrow editable_parent" data-miid="{{.ID}}">
|
||||||
|
<span class="grip"></span>
|
||||||
<a href="/panel/themes/menus/item/edit/{{.ID}}" class="editable_block panel_upshift">{{.Name}}</a>
|
<a href="/panel/themes/menus/item/edit/{{.ID}}" class="editable_block panel_upshift">{{.Name}}</a>
|
||||||
<span class="panel_buttons">
|
<span class="panel_buttons">
|
||||||
<a href="/panel/themes/menus/item/edit/{{.ID}}" class="panel_tag panel_right_button edit_button" aria-label="{{lang "panel_themes_menus_item_edit_button_aria"}}"></a>
|
<a href="/panel/themes/menus/item/edit/{{.ID}}" class="panel_tag panel_right_button edit_button" aria-label="{{lang "panel_themes_menus_items_edit_button_aria"}}"></a>
|
||||||
<a href="/panel/themes/menus/item/delete/submit/{{.ID}}?session={{$.CurrentUser.Session}}" class="panel_tag panel_right_button delete_button" aria-label="{{lang "panel_themes_menus_item_delete_button_aria"}}"></a>
|
<a href="/panel/themes/menus/item/delete/submit/{{.ID}}?session={{$.CurrentUser.Session}}" class="panel_tag panel_right_button delete_button" aria-label="{{lang "panel_themes_menus_items_delete_button_aria"}}"></a>
|
||||||
</span>
|
</span>
|
||||||
</div>{{end}}
|
</div>{{end}}
|
||||||
</div>
|
</div>
|
||||||
<div class="colstack_item rowlist panel_submitrow">
|
<div class="colstack_item rowlist panel_submitrow">
|
||||||
<div class="rowitem"><button id="panel_menu_items_order_button" class="formbutton">{{lang "panel_themes_menus_edit_update_button"}}</button></div>
|
<div class="rowitem"><button id="panel_menu_items_order_button" class="formbutton">{{lang "panel_themes_menus_items_update_button"}}</button></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="colstack_item colstack_head">
|
<div class="colstack_item colstack_head">
|
||||||
<div class="rowitem"><h1>{{lang "panel_themes_menus_create_head"}}</h1></div>
|
<div class="rowitem"><h1>{{lang "panel_themes_menus_create_head"}}</h1></div>
|
||||||
|
@ -80,10 +84,8 @@
|
||||||
// TODO: Move this into a JS file to reduce the number of possible problems
|
// TODO: Move this into a JS file to reduce the number of possible problems
|
||||||
var menuItems = {};
|
var menuItems = {};
|
||||||
let items = document.getElementsByClassName("panel_menu_item");
|
let items = document.getElementsByClassName("panel_menu_item");
|
||||||
for(let i = 0; item = items[i];i++) {
|
for(let i = 0; item = items[i]; i++) menuItems[i] = item.getAttribute("data-miid");
|
||||||
let miid = item.getAttribute("data-miid");
|
|
||||||
menuItems[i] = miid;
|
|
||||||
}
|
|
||||||
Sortable.create(document.getElementById("panel_menu_item_holder"), {
|
Sortable.create(document.getElementById("panel_menu_item_holder"), {
|
||||||
sort: true,
|
sort: true,
|
||||||
onEnd: (evt) => {
|
onEnd: (evt) => {
|
||||||
|
@ -92,11 +94,12 @@ Sortable.create(document.getElementById("panel_menu_item_holder"), {
|
||||||
let oldMiid = menuItems[evt.newIndex];
|
let oldMiid = menuItems[evt.newIndex];
|
||||||
menuItems[evt.oldIndex] = oldMiid;
|
menuItems[evt.oldIndex] = oldMiid;
|
||||||
let newMiid = evt.item.getAttribute("data-miid");
|
let newMiid = evt.item.getAttribute("data-miid");
|
||||||
console.log("newMiid: ", newMiid)
|
console.log("newMiid: ", newMiid);
|
||||||
menuItems[evt.newIndex] = newMiid;
|
menuItems[evt.newIndex] = newMiid;
|
||||||
console.log("post menuItems: ", menuItems)
|
console.log("post menuItems: ", menuItems);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById("panel_menu_items_order_button").addEventListener("click", () => {
|
document.getElementById("panel_menu_items_order_button").addEventListener("click", () => {
|
||||||
let req = new XMLHttpRequest();
|
let req = new XMLHttpRequest();
|
||||||
if(!req) {
|
if(!req) {
|
||||||
|
@ -105,18 +108,13 @@ document.getElementById("panel_menu_items_order_button").addEventListener("click
|
||||||
}
|
}
|
||||||
req.onreadystatechange = () => {
|
req.onreadystatechange = () => {
|
||||||
try {
|
try {
|
||||||
if(req.readyState!==XMLHttpRequest.DONE) {
|
if(req.readyState!==XMLHttpRequest.DONE) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
// TODO: Signal the error with a notice
|
// TODO: Signal the error with a notice
|
||||||
if(req.status===200) {
|
if(req.status===200) {
|
||||||
let resp = JSON.parse(req.responseText);
|
let resp = JSON.parse(req.responseText);
|
||||||
console.log("resp: ", resp);
|
console.log("resp: ", resp);
|
||||||
if(resp.success==1) {
|
|
||||||
// TODO: Have a successfully updated notice
|
// TODO: Have a successfully updated notice
|
||||||
console.log("success");
|
if(resp.success==1) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch(ex) {
|
} catch(ex) {
|
||||||
console.error("exception: ", ex)
|
console.error("exception: ", ex)
|
||||||
|
@ -127,12 +125,8 @@ document.getElementById("panel_menu_items_order_button").addEventListener("click
|
||||||
req.open("POST","/panel/themes/menus/item/order/edit/submit/{{.MenuID}}?session=" + encodeURIComponent(me.User.Session));
|
req.open("POST","/panel/themes/menus/item/order/edit/submit/{{.MenuID}}?session=" + encodeURIComponent(me.User.Session));
|
||||||
req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
|
req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
|
||||||
let items = "";
|
let items = "";
|
||||||
for(let i = 0; item = menuItems[i];i++) {
|
for(let i = 0; item = menuItems[i];i++) items += item+",";
|
||||||
items += item+",";
|
if(items.length > 0) items = items.slice(0,-1);
|
||||||
}
|
|
||||||
if(items.length > 0) {
|
|
||||||
items = items.slice(0,-1);
|
|
||||||
}
|
|
||||||
req.send("js=1&items={"+items+"}");
|
req.send("js=1&items={"+items+"}");
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -62,6 +62,12 @@
|
||||||
/*margin-top: -4px;*/
|
/*margin-top: -4px;*/
|
||||||
margin-bottom: 14px;
|
margin-bottom: 14px;
|
||||||
}
|
}
|
||||||
|
.colstack_right .colstack_head .rowitem {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.colstack_right .colstack_head h1 + h2.hguide {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
.footer {
|
.footer {
|
||||||
margin-top: 0px;
|
margin-top: 0px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -358,6 +358,11 @@ h2 {
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
}
|
}
|
||||||
|
.rowhead h2, .colstack_head h2 {
|
||||||
|
margin-top: 0px;
|
||||||
|
margin-bottom: 0px;
|
||||||
|
margin-left: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
.topic_create_form {
|
.topic_create_form {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
@ -30,6 +30,17 @@ function noxMenuBind() {
|
||||||
}
|
}
|
||||||
|
|
||||||
(() => {
|
(() => {
|
||||||
|
function moveAlerts() {
|
||||||
|
// Move the alerts above the first header
|
||||||
|
let colSel = $(".colstack_right .colstack_head:first");
|
||||||
|
let colSelAlt = $(".colstack_right .colstack_item:first");
|
||||||
|
let colSelAltAlt = $(".colstack_right .coldyn_block:first");
|
||||||
|
if(colSel.length > 0) $('.alert').insertBefore(colSel);
|
||||||
|
else if (colSelAlt.length > 0) $('.alert').insertBefore(colSelAlt);
|
||||||
|
else if (colSelAltAlt.length > 0) $('.alert').insertBefore(colSelAltAlt);
|
||||||
|
else $('.alert').insertAfter(".rowhead:first");
|
||||||
|
}
|
||||||
|
|
||||||
addInitHook("after_update_alert_list", (alertCount) => {
|
addInitHook("after_update_alert_list", (alertCount) => {
|
||||||
console.log("misc.js");
|
console.log("misc.js");
|
||||||
console.log("alertCount:",alertCount);
|
console.log("alertCount:",alertCount);
|
||||||
|
@ -57,15 +68,7 @@ function noxMenuBind() {
|
||||||
|
|
||||||
$(window).resize(() => noxMenuBind());
|
$(window).resize(() => noxMenuBind());
|
||||||
noxMenuBind();
|
noxMenuBind();
|
||||||
|
moveAlerts();
|
||||||
// Move the alerts above the first header
|
|
||||||
let colSel = $(".colstack_right .colstack_head:first");
|
|
||||||
let colSelAlt = $(".colstack_right .colstack_item:first");
|
|
||||||
let colSelAltAlt = $(".colstack_right .coldyn_block:first");
|
|
||||||
if(colSel.length > 0) $('.alert').insertBefore(colSel);
|
|
||||||
else if (colSelAlt.length > 0) $('.alert').insertBefore(colSelAlt);
|
|
||||||
else if (colSelAltAlt.length > 0) $('.alert').insertBefore(colSelAltAlt);
|
|
||||||
else $('.alert').insertAfter(".rowhead:first");
|
|
||||||
|
|
||||||
$(".menu_hamburger").click(function() {
|
$(".menu_hamburger").click(function() {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
@ -78,4 +81,6 @@ function noxMenuBind() {
|
||||||
|
|
||||||
$(document).click(() => $(".more_menu").removeClass("more_menu_selected"));
|
$(document).click(() => $(".more_menu").removeClass("more_menu_selected"));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addInitHook("after_notice", moveAlerts);
|
||||||
})();
|
})();
|
|
@ -79,6 +79,10 @@
|
||||||
.colstack_right .colstack_head h1 {
|
.colstack_right .colstack_head h1 {
|
||||||
font-size: 21px;
|
font-size: 21px;
|
||||||
}
|
}
|
||||||
|
.colstack_right .colstack_head h1 + h2.hguide {
|
||||||
|
margin-left: auto;
|
||||||
|
font-size: 17px;
|
||||||
|
}
|
||||||
.colstack_right .colstack_item.the_form, .colstack_right .colstack_item:not(.colstack_head):not(.rowhead) .rowitem {
|
.colstack_right .colstack_item.the_form, .colstack_right .colstack_item:not(.colstack_head):not(.rowhead) .rowitem {
|
||||||
background-color: #444444;
|
background-color: #444444;
|
||||||
}
|
}
|
||||||
|
@ -292,6 +296,35 @@ button, .formbutton, .panel_right_button:not(.has_inner_button), #panel_users .p
|
||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
span.grip {
|
||||||
|
content: '....';
|
||||||
|
width: 20px;
|
||||||
|
display: inline-block;
|
||||||
|
overflow: hidden;
|
||||||
|
line-height: 5px;
|
||||||
|
padding: 3px 4px;
|
||||||
|
cursor: move;
|
||||||
|
vertical-align: middle;
|
||||||
|
margin-top: -16px;
|
||||||
|
margin-right: 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-family: sans-serif;
|
||||||
|
letter-spacing: -3px;
|
||||||
|
color: #888888;
|
||||||
|
text-shadow: 1px 0 1px black;
|
||||||
|
margin-left: -12px;
|
||||||
|
height: 100%;
|
||||||
|
font-size: 40px;
|
||||||
|
margin-bottom: -4px;
|
||||||
|
line-height: 8px;
|
||||||
|
}
|
||||||
|
span.grip::after {
|
||||||
|
content: '... ... ... ... ... ... ...';
|
||||||
|
}
|
||||||
|
.forum_no_desc span.grip, .panel_menu_item span.grip {
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
.panel_plugin_meta {
|
.panel_plugin_meta {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
|
@ -11,6 +11,9 @@
|
||||||
.colstack_head .rowitem a h1 {
|
.colstack_head .rowitem a h1 {
|
||||||
margin-right: 0px;
|
margin-right: 0px;
|
||||||
}
|
}
|
||||||
|
.rowitem h2.hguide {
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
.rowlist .tag-mini {
|
.rowlist .tag-mini {
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
|
|
|
@ -10,6 +10,12 @@
|
||||||
.submenu a {
|
.submenu a {
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
}
|
}
|
||||||
|
/*.colstack_right .colstack_head .rowitem {
|
||||||
|
display: flex;
|
||||||
|
}*/
|
||||||
|
.colstack_right .colstack_head h1 + h2.hguide {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.edit_button:before {
|
.edit_button:before {
|
||||||
content: "{{lang "panel_edit_button_text" . }}";
|
content: "{{lang "panel_edit_button_text" . }}";
|
||||||
|
|
Loading…
Reference in New Issue