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{"desc", "varchar", 200, false, false, ""},
|
||||
tblColumn{"active", "boolean", 0, false, false, "1"},
|
||||
tblColumn{"order", "int", 0, false, false, "0"},
|
||||
tblColumn{"topicCount", "int", 0, false, false, "0"},
|
||||
tblColumn{"preset", "varchar", 100, false, false, "''"},
|
||||
tblColumn{"parentID", "int", 0, false, false, "0"},
|
||||
|
@ -427,6 +428,7 @@ func createTables(adapter qgen.Adapter) error {
|
|||
[]tblColumn{
|
||||
tblColumn{"uname", "varchar", 180, false, false, ""},
|
||||
tblColumn{"default", "boolean", 0, false, false, "0"},
|
||||
//tblColumn{"profileUserVars", "text", 0, false, false, "''"},
|
||||
},
|
||||
[]tblKey{
|
||||
tblKey{"uname", "unique"},
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package common
|
||||
|
||||
//import "fmt"
|
||||
import (
|
||||
//"log"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"strconv"
|
||||
|
@ -28,6 +28,7 @@ type Forum struct {
|
|||
Name string
|
||||
Desc string
|
||||
Active bool
|
||||
Order int
|
||||
Preset string
|
||||
ParentID int
|
||||
ParentType string
|
||||
|
@ -135,9 +136,23 @@ func (sf SortForum) Len() int {
|
|||
func (sf SortForum) Swap(i, j int) {
|
||||
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 {
|
||||
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 false
|
||||
}
|
||||
|
||||
// ! 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 {
|
||||
|
|
|
@ -42,6 +42,7 @@ type ForumStore interface {
|
|||
//GetChildren(parentID int, parentType string) ([]*Forum,error)
|
||||
//GetFirstChild(parentID int, parentType string) (*Forum,error)
|
||||
Create(forumName string, forumDesc string, active bool, preset string) (int, error)
|
||||
UpdateOrder(updateMap map[int]int) error
|
||||
|
||||
GlobalCount() int
|
||||
}
|
||||
|
@ -66,6 +67,7 @@ type MemoryForumStore struct {
|
|||
updateCache *sql.Stmt
|
||||
addTopics *sql.Stmt
|
||||
removeTopics *sql.Stmt
|
||||
updateOrder *sql.Stmt
|
||||
}
|
||||
|
||||
// NewMemoryForumStore gives you a new instance of MemoryForumStore
|
||||
|
@ -73,17 +75,19 @@ func NewMemoryForumStore() (*MemoryForumStore, error) {
|
|||
acc := qgen.NewAcc()
|
||||
// TODO: Do a proper delete
|
||||
return &MemoryForumStore{
|
||||
get: acc.Select("forums").Columns("name, desc, active, 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(),
|
||||
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, order, preset, parentID, parentType, topicCount, lastTopicID, lastReplyerID").Orderby("order ASC, fid ASC").Prepare(),
|
||||
delete: acc.Update("forums").Set("name= '', active = 0").Where("fid = ?").Prepare(),
|
||||
create: acc.Insert("forums").Columns("name, desc, active, preset").Fields("?,?,?,?").Prepare(),
|
||||
count: acc.Count("forums").Where("name != ''").Prepare(),
|
||||
updateCache: acc.Update("forums").Set("lastTopicID = ?, lastReplyerID = ?").Where("fid = ?").Prepare(),
|
||||
addTopics: 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()
|
||||
}
|
||||
|
||||
// TODO: Rename to ReloadAll?
|
||||
// TODO: Add support for subforums
|
||||
func (mfs *MemoryForumStore) LoadForums() error {
|
||||
var forumView []*Forum
|
||||
|
@ -103,7 +107,7 @@ func (mfs *MemoryForumStore) LoadForums() error {
|
|||
var i = 0
|
||||
for ; rows.Next(); i++ {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
@ -161,7 +165,7 @@ func (mfs *MemoryForumStore) Get(id int) (*Forum, error) {
|
|||
fint, ok := mfs.forums.Load(id)
|
||||
if !ok || fint.(*Forum).Name == "" {
|
||||
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 {
|
||||
return forum, err
|
||||
}
|
||||
|
@ -178,7 +182,7 @@ func (mfs *MemoryForumStore) Get(id int) (*Forum, error) {
|
|||
|
||||
func (mfs *MemoryForumStore) BypassGet(id int) (*Forum, error) {
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -206,7 +210,7 @@ func (mfs *MemoryForumStore) BulkGetCopy(ids []int) (forums []Forum, err error)
|
|||
|
||||
func (mfs *MemoryForumStore) Reload(id int) error {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
@ -348,6 +352,17 @@ func (mfs *MemoryForumStore) Create(forumName string, forumDesc string, active b
|
|||
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
|
||||
// Length returns the number of forums in the memory cache
|
||||
func (mfs *MemoryForumStore) Length() (length int) {
|
||||
|
|
|
@ -39,6 +39,7 @@ type LevelPhrases struct {
|
|||
type LanguagePack struct {
|
||||
Name 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.
|
||||
Levels LevelPhrases
|
||||
|
@ -70,6 +71,9 @@ func InitPhrases(lang string) error {
|
|||
if f.IsDir() {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
|
|
|
@ -158,6 +158,7 @@ func panelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (header
|
|||
header.AddPreScriptAsync("template_" + name + tname + ".js")
|
||||
}
|
||||
addPreScript("alert")
|
||||
addPreScript("notice")
|
||||
|
||||
return header, stats, nil
|
||||
}
|
||||
|
@ -267,6 +268,7 @@ func PrepResources(user *User, header *Header, theme *Theme) {
|
|||
addPreScript("topics_topic")
|
||||
addPreScript("paginator")
|
||||
addPreScript("alert")
|
||||
addPreScript("notice")
|
||||
if user.Loggedin {
|
||||
addPreScript("topic_c_edit_post")
|
||||
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("notice", "string", "nonono")
|
||||
|
||||
var dirPrefix = "./tmpl_client/"
|
||||
var writeTemplate = func(name string, content string) {
|
||||
log.Print("Writing template '" + name + "'")
|
||||
|
@ -711,6 +713,10 @@ func initDefaultTmplFuncMap() {
|
|||
return ""
|
||||
}
|
||||
|
||||
fmap["flush"] = func() interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
DefaultTemplateFuncMap = fmap
|
||||
}
|
||||
|
||||
|
|
|
@ -74,6 +74,7 @@ type CTemplateSet struct {
|
|||
|
||||
logger *log.Logger
|
||||
loggerf *os.File
|
||||
lang string
|
||||
}
|
||||
|
||||
func NewCTemplateSet(in string) *CTemplateSet {
|
||||
|
@ -112,9 +113,11 @@ func NewCTemplateSet(in string) *CTemplateSet {
|
|||
"scope": true,
|
||||
"dyntmpl": true,
|
||||
"index": true,
|
||||
"flush": true,
|
||||
},
|
||||
logger: log.New(f, "", log.LstdFlags),
|
||||
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")
|
||||
}
|
||||
`
|
||||
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 {
|
||||
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)
|
||||
// Simple member / guest optimisation for now
|
||||
// TODO: Expand upon this
|
||||
var userExprs = []string{
|
||||
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",
|
||||
}
|
||||
userExprs, negUserExprs := buildUserExprs(con.RootHolder)
|
||||
if c.guestOnly {
|
||||
c.detail("optimising away member branch")
|
||||
if inSlice(userExprs, expr) {
|
||||
|
@ -1170,6 +1174,16 @@ ArgLoop:
|
|||
out += "if err != nil {\nreturn err\n}\n}\n"
|
||||
literal = true
|
||||
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:
|
||||
c.detail("Variable!")
|
||||
if len(node.Args) > (pos + 1) {
|
||||
|
@ -1391,6 +1405,20 @@ func (c *CTemplateSet) retCall(name string, params ...interface{}) {
|
|||
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) {
|
||||
c.dumpCall("compileVarSub", con, varname, val, assLines, onEnd)
|
||||
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: Make this a template fragment so more optimisations can be applied to this
|
||||
// TODO: De-duplicate this logic
|
||||
var userExprs = []string{
|
||||
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",
|
||||
}
|
||||
userExprs, negUserExprs := buildUserExprs(con.RootHolder)
|
||||
if c.guestOnly {
|
||||
c.detail("optimising away member branch")
|
||||
if inSlice(userExprs, varname) {
|
||||
|
|
|
@ -33,8 +33,8 @@ var errWsNouser = errors.New("This user isn't connected via WebSockets")
|
|||
|
||||
func init() {
|
||||
adminStatsWatchers = make(map[*websocket.Conn]*WSUser)
|
||||
topicListWatchers = make(map[*WSUser]bool)
|
||||
topicWatchers = make(map[int]map[*WSUser]bool)
|
||||
topicListWatchers = make(map[*WSUser]struct{})
|
||||
topicWatchers = make(map[int]map[*WSUser]struct{})
|
||||
}
|
||||
|
||||
//easyjson:json
|
||||
|
@ -130,7 +130,7 @@ func wsPageResponses(wsUser *WSUser, conn *websocket.Conn, page string) {
|
|||
// TODO: Optimise this to reduce the amount of contention
|
||||
case page == "/topics/":
|
||||
topicListMutex.Lock()
|
||||
topicListWatchers[wsUser] = true
|
||||
topicListWatchers[wsUser] = struct{}{}
|
||||
topicListMutex.Unlock()
|
||||
// TODO: Evict from page when permissions change? Or check user perms every-time before sending data?
|
||||
case strings.HasPrefix(page, "/topic/"):
|
||||
|
@ -169,9 +169,9 @@ func wsPageResponses(wsUser *WSUser, conn *websocket.Conn, page string) {
|
|||
topicMutex.Lock()
|
||||
_, ok := topicWatchers[topic.ID]
|
||||
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()
|
||||
case page == "/panel/":
|
||||
if !wsUser.User.IsSuperMod {
|
||||
|
@ -243,9 +243,9 @@ func wsLeavePage(wsUser *WSUser, conn *websocket.Conn, page string) {
|
|||
|
||||
// TODO: Abstract this
|
||||
// TODO: Use odd-even sharding
|
||||
var topicListWatchers map[*WSUser]bool
|
||||
var topicListWatchers map[*WSUser]struct{}
|
||||
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 adminStatsWatchers map[*websocket.Conn]*WSUser
|
||||
var adminStatsMutex sync.RWMutex
|
||||
|
|
|
@ -35,6 +35,7 @@ func deactivateHdrive(plugin *c.Plugin) {
|
|||
|
||||
type Hyperspace struct {
|
||||
topicList atomic.Value
|
||||
gzipTopicList atomic.Value
|
||||
}
|
||||
|
||||
func newHyperspace() *Hyperspace {
|
||||
|
@ -48,10 +49,7 @@ func tickHdriveWol(args ...interface{}) (skip bool, rerr c.RouteError) {
|
|||
return tickHdrive(args)
|
||||
}
|
||||
|
||||
// TODO: Find a better way of doing this
|
||||
func tickHdrive(args ...interface{}) (skip bool, rerr c.RouteError) {
|
||||
c.DebugLog("Refueling...")
|
||||
w := httptest.NewRecorder()
|
||||
func dummyReqHdrive() http.ResponseWriter {
|
||||
req := httptest.NewRequest("get", "/topics/", bytes.NewReader(nil))
|
||||
user := c.GuestUser
|
||||
|
||||
|
@ -68,17 +66,48 @@ func tickHdrive(args ...interface{}) (skip bool, rerr c.RouteError) {
|
|||
}
|
||||
if w.Code != 200 {
|
||||
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.ReadFrom(w.Result().Body)
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
c.DebugLog("no topiclist in hyperspace")
|
||||
return false, nil
|
||||
|
@ -101,7 +130,6 @@ func jumpHdrive(args ...interface{}) (skip bool, rerr c.RouteError) {
|
|||
//c.DebugLog
|
||||
c.DebugLog("Successful jump")
|
||||
|
||||
w := args[0].(http.ResponseWriter)
|
||||
header := args[3].(*c.Header)
|
||||
routes.FootHeaders(w, header)
|
||||
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_no":"No",
|
||||
|
||||
"panel_hints_reorder":"Drag to change the order",
|
||||
|
||||
"panel_back_to_site":"Back to Site",
|
||||
"panel_welcome":"Welcome ",
|
||||
"panel_menu_head":"Control Panel",
|
||||
|
@ -769,22 +771,24 @@
|
|||
"panel_user_group":"Group",
|
||||
"panel_user_update_button":"Update User",
|
||||
|
||||
"panel_forums_head":"Forums",
|
||||
"panel_forums_hidden":"Hidden",
|
||||
"panel_forums_edit_button_tooltip":"Edit Forum",
|
||||
"panel_forums_edit_button_aria":"Edit Forum",
|
||||
"panel_forums_update_button":"Update",
|
||||
"panel_forums_delete_button_tooltip":"Delete Forum",
|
||||
"panel_forums_delete_button_aria":"Delete Forum",
|
||||
"panel_forums_full_edit_button":"Full Edit",
|
||||
"panel_forums_create_head":"Add Forum",
|
||||
"panel_forums_create_name_label":"Name",
|
||||
"panel_forums_create_name":"Super Secret Forum",
|
||||
"panel_forums_create_description_label":"Description",
|
||||
"panel_forums_create_description":"Where all the super secret stuff happens",
|
||||
"panel_forums_active_label":"Active",
|
||||
"panel_forums_preset_label":"Preset",
|
||||
"panel_forums_create_button":"Add Forum",
|
||||
"panel.forums_head":"Forums",
|
||||
"panel.forums_hidden":"Hidden",
|
||||
"panel.forums_edit_button_tooltip":"Edit Forum",
|
||||
"panel.forums_edit_button_aria":"Edit Forum",
|
||||
"panel.forums_update_button":"Update",
|
||||
"panel.forums_delete_button_tooltip":"Delete Forum",
|
||||
"panel.forums_delete_button_aria":"Delete Forum",
|
||||
"panel.forums_full_edit_button":"Full Edit",
|
||||
"panel.forums_create_head":"Add Forum",
|
||||
"panel.forums_create_name_label":"Name",
|
||||
"panel.forums_create_name":"Super Secret Forum",
|
||||
"panel.forums_create_description_label":"Description",
|
||||
"panel.forums_create_description":"Where all the super secret stuff happens",
|
||||
"panel.forums_active_label":"Active",
|
||||
"panel.forums_preset_label":"Preset",
|
||||
"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_name":"Name",
|
||||
|
@ -942,8 +946,9 @@
|
|||
"panel_themes_menus_head":"Menus",
|
||||
"panel_themes_menus_main":"Main Menu",
|
||||
"panel_themes_menus_items_head":"Menu Items",
|
||||
"panel_themes_menus_item_edit_button_aria":"Edit menu item",
|
||||
"panel_themes_menus_item_delete_button_aria":"Delete menu item",
|
||||
"panel_themes_menus_items_edit_button_aria":"Edit 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_create_head":"Create Menu Item",
|
||||
|
|
|
@ -30,6 +30,7 @@ func init() {
|
|||
addPatch(15, patch15)
|
||||
addPatch(16, patch16)
|
||||
addPatch(17, patch17)
|
||||
addPatch(18, patch18)
|
||||
}
|
||||
|
||||
func patch0(scanner *bufio.Scanner) (err error) {
|
||||
|
@ -588,3 +589,7 @@ func patch17(scanner *bufio.Scanner) error {
|
|||
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
|
||||
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
|
||||
function ajaxError(xhr,status,errstr) {
|
||||
console.log("The AJAX request failed");
|
||||
|
|
|
@ -28,9 +28,9 @@ function runHook(name, ...args) {
|
|||
console.log("Running hook '"+name+"'");
|
||||
|
||||
let hook = hooks[name];
|
||||
for (const index in hook) {
|
||||
hook[index](...args);
|
||||
}
|
||||
let ret;
|
||||
for (const index in hook) ret = hook[index](...args);
|
||||
return ret;
|
||||
}
|
||||
|
||||
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
|
||||
function runInitHook(name, ...args) {
|
||||
runHook(name,...args);
|
||||
let ret = runHook(name,...args);
|
||||
ranInitHooks[name] = true;
|
||||
return ret;
|
||||
}
|
||||
|
||||
function addInitHook(name, callback) {
|
||||
addHook(name, callback);
|
||||
if(name in ranInitHooks) {
|
||||
callback();
|
||||
}
|
||||
if(name in ranInitHooks) callback();
|
||||
}
|
||||
|
||||
// Temporary hack for templates
|
||||
|
@ -175,14 +174,14 @@ function RelativeTime(date) {
|
|||
return date;
|
||||
}
|
||||
|
||||
function initPhrases(loggedIn) {
|
||||
function initPhrases(loggedIn, panel = false) {
|
||||
console.log("in initPhrases")
|
||||
console.log("tmlInits:",tmplInits)
|
||||
let e = "";
|
||||
if(loggedIn) {
|
||||
e = ",topic"
|
||||
}
|
||||
fetchPhrases("status,topic_list,alerts,paginator,analytics"+e) // TODO: Break this up?
|
||||
if(loggedIn && !panel) e = ",topic_list,topic";
|
||||
else if(panel) e = ",analytics,panel"; // TODO: Request phrases for just one section of the control panel?
|
||||
else e = ",topic_list";
|
||||
fetchPhrases("status,alerts,paginator"+e) // TODO: Break this up?
|
||||
}
|
||||
|
||||
function fetchPhrases(plist) {
|
||||
|
@ -219,15 +218,18 @@ function fetchPhrases(plist) {
|
|||
(() => {
|
||||
runInitHook("pre_iife");
|
||||
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 = 2;
|
||||
let toLoad = 1;
|
||||
// TODO: Shunt this into loggedIn if there aren't any search and filter widgets?
|
||||
let q = (f) => {
|
||||
toLoad--;
|
||||
if(toLoad===0) initPhrases(loggedIn);
|
||||
if(toLoad===0) initPhrases(loggedIn,panel);
|
||||
if(f) throw("template function not found");
|
||||
};
|
||||
|
||||
if(!panel) {
|
||||
toLoad += 2;
|
||||
if(loggedIn) {
|
||||
toLoad += 2;
|
||||
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_paginator", () => q(!Template_paginator));
|
||||
} else {
|
||||
initPhrases(false);
|
||||
}
|
||||
notifyOnScriptW("template_notice", () => q(!Template_notice));
|
||||
|
||||
if(loggedIn) {
|
||||
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.ForumsDelete", "/panel/forums/delete/", "extraData"),
|
||||
Action("panel.ForumsDeleteSubmit", "/panel/forums/delete/submit/", "extraData"),
|
||||
Action("panel.ForumsOrderSubmit", "/panel/forums/order/edit/submit/"),
|
||||
View("panel.ForumsEdit", "/panel/forums/edit/", "extraData"),
|
||||
Action("panel.ForumsEditSubmit", "/panel/forums/edit/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",
|
||||
"paginator",
|
||||
"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 {
|
||||
|
@ -199,13 +201,22 @@ func routeAPIPhrases(w http.ResponseWriter, r *http.Request, user c.User) c.Rout
|
|||
var ok = false
|
||||
for _, item := range phraseWhitelist {
|
||||
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
|
||||
w.Header().Set("Cache-Control", "private")
|
||||
}
|
||||
} else {
|
||||
ok = true
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
return c.PreErrorJS("Outside of phrase prefix whitelist", w, r)
|
||||
}
|
||||
|
||||
pPhrases, ok := phrases.GetTmplPhrasesByPrefix(positive)
|
||||
if !ok {
|
||||
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
|
||||
for _, item := range phraseWhitelist {
|
||||
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
|
||||
w.Header().Set("Cache-Control", "private")
|
||||
}
|
||||
} else {
|
||||
ok = true
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
return c.PreErrorJS("Outside of phrase prefix whitelist", w, r)
|
||||
}
|
||||
|
||||
pPhrases, ok := phrases.GetTmplPhrasesByPrefix(positives[0])
|
||||
if !ok {
|
||||
return c.PreErrorJS("No such prefix", w, r)
|
||||
|
|
|
@ -22,7 +22,8 @@ type AnalyticsTimeRange struct {
|
|||
Range string
|
||||
}
|
||||
|
||||
func analyticsTimeRange(rawTimeRange string) (timeRange AnalyticsTimeRange, err error) {
|
||||
func analyticsTimeRange(rawTimeRange string) (*AnalyticsTimeRange, error) {
|
||||
timeRange := &AnalyticsTimeRange{}
|
||||
timeRange.Quantity = 6
|
||||
timeRange.Unit = "hour"
|
||||
timeRange.Slices = 12
|
||||
|
@ -78,7 +79,7 @@ func analyticsTimeRange(rawTimeRange string) (timeRange AnalyticsTimeRange, err
|
|||
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)
|
||||
var currentTime = time.Now().Unix()
|
||||
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 {
|
||||
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?
|
||||
var forumList []interface{}
|
||||
|
@ -130,6 +132,31 @@ func ForumsDeleteSubmit(w http.ResponseWriter, r *http.Request, user c.User, sfi
|
|||
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 {
|
||||
basePage, ferr := buildBasePage(w, r, &user, "edit_forum", "forums")
|
||||
if ferr != nil {
|
||||
|
@ -333,7 +360,7 @@ func ForumsEditPermsAdvance(w http.ResponseWriter, r *http.Request, user c.User,
|
|||
addNameLangToggle("MoveTopic", forumPerms.MoveTopic)
|
||||
|
||||
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}
|
||||
|
|
|
@ -3,6 +3,7 @@ CREATE TABLE [forums] (
|
|||
[name] nvarchar (100) not null,
|
||||
[desc] nvarchar (200) not null,
|
||||
[active] bit DEFAULT 1 not null,
|
||||
[order] int DEFAULT 0 not null,
|
||||
[topicCount] int DEFAULT 0 not null,
|
||||
[preset] nvarchar (100) DEFAULT '' not null,
|
||||
[parentID] int DEFAULT 0 not null,
|
||||
|
|
|
@ -3,6 +3,7 @@ CREATE TABLE `forums` (
|
|||
`name` varchar(100) not null,
|
||||
`desc` varchar(200) not null,
|
||||
`active` boolean DEFAULT 1 not null,
|
||||
`order` int DEFAULT 0 not null,
|
||||
`topicCount` int DEFAULT 0 not null,
|
||||
`preset` varchar(100) DEFAULT '' not null,
|
||||
`parentID` int DEFAULT 0 not null,
|
||||
|
|
|
@ -3,6 +3,7 @@ CREATE TABLE "forums" (
|
|||
`name` varchar (100) not null,
|
||||
`desc` varchar (200) not null,
|
||||
`active` boolean DEFAULT 1 not null,
|
||||
`order` int DEFAULT 0 not null,
|
||||
`topicCount` int DEFAULT 0 not null,
|
||||
`preset` varchar (100) DEFAULT '' not null,
|
||||
`parentID` int DEFAULT 0 not null,
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
{{range .Header.PreScriptsAsync}}
|
||||
<script async type="text/javascript" src="/static/{{.}}"></script>{{end}}
|
||||
<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}}
|
||||
<script async type="text/javascript" src="/static/{{.}}"></script>{{end}}
|
||||
<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}}
|
||||
</head>
|
||||
<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 class="navrow">-->**/}}
|
||||
<div class="left_of_nav">{{dock "leftOfNav" .Header }}</div>
|
||||
|
|
|
@ -2,60 +2,63 @@
|
|||
|
||||
<div class="colstack panel_stack">
|
||||
{{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">
|
||||
{{template "panel_before_head.html" . }}
|
||||
<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 id="panel_forums" class="colstack_item rowlist">
|
||||
{{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">
|
||||
{{/** 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>
|
||||
<br /><span data-field="forum_desc" data-type="text" class="editable_block forum_desc rowsmall">{{.Desc}}</span>
|
||||
</span>
|
||||
<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>
|
||||
<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_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}}
|
||||
<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 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>
|
||||
{{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>
|
||||
</span>
|
||||
</div>
|
||||
{{end}}
|
||||
</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="rowitem"><h1>{{lang "panel_forums_create_head"}}</h1></div>
|
||||
<div class="rowitem"><h1>{{lang "panel.forums_create_head"}}</h1></div>
|
||||
</div>
|
||||
<div class="colstack_item the_form">
|
||||
<form action="/panel/forums/create/?session={{.CurrentUser.Session}}" method="post">
|
||||
<div class="formrow">
|
||||
<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 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>
|
||||
<div class="formrow">
|
||||
<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 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>
|
||||
<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">
|
||||
<option selected value="1">{{lang "option_yes"}}</option>
|
||||
<option value="0">{{lang "option_no"}}</option>
|
||||
</select></div>
|
||||
</div>
|
||||
<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">
|
||||
<option selected value="all">{{lang "panel_preset_everyone"}}</option>
|
||||
<option value="announce">{{lang "panel_preset_announcements"}}</option>
|
||||
|
@ -67,7 +70,7 @@
|
|||
</select></div>
|
||||
</div>
|
||||
<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>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
@ -4,20 +4,24 @@
|
|||
<main class="colstack_right">
|
||||
{{template "panel_before_head.html" . }}
|
||||
<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 id="panel_menu_item_holder" class="colstack_item rowlist">
|
||||
{{range .ItemList}}
|
||||
<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>
|
||||
<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/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/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_items_delete_button_aria"}}"></a>
|
||||
</span>
|
||||
</div>{{end}}
|
||||
</div>
|
||||
<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 class="colstack_item colstack_head">
|
||||
<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
|
||||
var menuItems = {};
|
||||
let items = document.getElementsByClassName("panel_menu_item");
|
||||
for(let i = 0; item = items[i];i++) {
|
||||
let miid = item.getAttribute("data-miid");
|
||||
menuItems[i] = miid;
|
||||
}
|
||||
for(let i = 0; item = items[i]; i++) menuItems[i] = item.getAttribute("data-miid");
|
||||
|
||||
Sortable.create(document.getElementById("panel_menu_item_holder"), {
|
||||
sort: true,
|
||||
onEnd: (evt) => {
|
||||
|
@ -92,11 +94,12 @@ Sortable.create(document.getElementById("panel_menu_item_holder"), {
|
|||
let oldMiid = menuItems[evt.newIndex];
|
||||
menuItems[evt.oldIndex] = oldMiid;
|
||||
let newMiid = evt.item.getAttribute("data-miid");
|
||||
console.log("newMiid: ", newMiid)
|
||||
console.log("newMiid: ", newMiid);
|
||||
menuItems[evt.newIndex] = newMiid;
|
||||
console.log("post menuItems: ", menuItems)
|
||||
console.log("post menuItems: ", menuItems);
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("panel_menu_items_order_button").addEventListener("click", () => {
|
||||
let req = new XMLHttpRequest();
|
||||
if(!req) {
|
||||
|
@ -105,18 +108,13 @@ document.getElementById("panel_menu_items_order_button").addEventListener("click
|
|||
}
|
||||
req.onreadystatechange = () => {
|
||||
try {
|
||||
if(req.readyState!==XMLHttpRequest.DONE) {
|
||||
return;
|
||||
}
|
||||
if(req.readyState!==XMLHttpRequest.DONE) return;
|
||||
// TODO: Signal the error with a notice
|
||||
if(req.status===200) {
|
||||
let resp = JSON.parse(req.responseText);
|
||||
console.log("resp: ", resp);
|
||||
if(resp.success==1) {
|
||||
// TODO: Have a successfully updated notice
|
||||
console.log("success");
|
||||
return;
|
||||
}
|
||||
if(resp.success==1) return;
|
||||
}
|
||||
} catch(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.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
|
||||
let items = "";
|
||||
for(let i = 0; item = menuItems[i];i++) {
|
||||
items += item+",";
|
||||
}
|
||||
if(items.length > 0) {
|
||||
items = items.slice(0,-1);
|
||||
}
|
||||
for(let i = 0; item = menuItems[i];i++) items += item+",";
|
||||
if(items.length > 0) items = items.slice(0,-1);
|
||||
req.send("js=1&items={"+items+"}");
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -62,6 +62,12 @@
|
|||
/*margin-top: -4px;*/
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.colstack_right .colstack_head .rowitem {
|
||||
display: flex;
|
||||
}
|
||||
.colstack_right .colstack_head h1 + h2.hguide {
|
||||
margin-left: auto;
|
||||
}
|
||||
.footer {
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
|
|
@ -358,6 +358,11 @@ h2 {
|
|||
margin-bottom: 8px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
.rowhead h2, .colstack_head h2 {
|
||||
margin-top: 0px;
|
||||
margin-bottom: 0px;
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
.topic_create_form {
|
||||
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) => {
|
||||
console.log("misc.js");
|
||||
console.log("alertCount:",alertCount);
|
||||
|
@ -57,15 +68,7 @@ function noxMenuBind() {
|
|||
|
||||
$(window).resize(() => noxMenuBind());
|
||||
noxMenuBind();
|
||||
|
||||
// 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");
|
||||
moveAlerts();
|
||||
|
||||
$(".menu_hamburger").click(function() {
|
||||
event.stopPropagation();
|
||||
|
@ -78,4 +81,6 @@ function noxMenuBind() {
|
|||
|
||||
$(document).click(() => $(".more_menu").removeClass("more_menu_selected"));
|
||||
});
|
||||
|
||||
addInitHook("after_notice", moveAlerts);
|
||||
})();
|
|
@ -79,6 +79,10 @@
|
|||
.colstack_right .colstack_head h1 {
|
||||
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 {
|
||||
background-color: #444444;
|
||||
}
|
||||
|
@ -292,6 +296,35 @@ button, .formbutton, .panel_right_button:not(.has_inner_button), #panel_users .p
|
|||
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 {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
|
@ -11,6 +11,9 @@
|
|||
.colstack_head .rowitem a h1 {
|
||||
margin-right: 0px;
|
||||
}
|
||||
.rowitem h2.hguide {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.rowlist .tag-mini {
|
||||
font-size: 10px;
|
||||
|
|
|
@ -10,6 +10,12 @@
|
|||
.submenu a {
|
||||
margin-left: 8px;
|
||||
}
|
||||
/*.colstack_right .colstack_head .rowitem {
|
||||
display: flex;
|
||||
}*/
|
||||
.colstack_right .colstack_head h1 + h2.hguide {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.edit_button:before {
|
||||
content: "{{lang "panel_edit_button_text" . }}";
|
||||
|
|
Loading…
Reference in New Issue