diff --git a/common/menu_store.go b/common/menu_store.go index c36a6c36..a706be1c 100644 --- a/common/menu_store.go +++ b/common/menu_store.go @@ -59,7 +59,7 @@ func (store *DefaultMenuStore) Load(mid int) error { if err != nil { return err } - hold := &MenuListHolder{mlist, make(map[int]menuTmpl)} + hold := &MenuListHolder{mid, mlist, make(map[int]menuTmpl)} err = hold.Preparse() if err != nil { return err diff --git a/common/menus.go b/common/menus.go index db1f0d80..0297694c 100644 --- a/common/menus.go +++ b/common/menus.go @@ -14,6 +14,7 @@ import ( type MenuItemList []MenuItem type MenuListHolder struct { + MenuID int List MenuItemList Variations map[int]menuTmpl // 0 = Guest Menu, 1 = Member Menu, 2 = Super Mod Menu, 3 = Admin Menu } @@ -43,8 +44,12 @@ type MenuItem struct { AdminOnly bool } +// TODO: Move the menu item stuff to it's own file type MenuItemStmts struct { - update *sql.Stmt + update *sql.Stmt + insert *sql.Stmt + delete *sql.Stmt + updateOrder *sql.Stmt } var menuItemStmts MenuItemStmts @@ -52,7 +57,10 @@ var menuItemStmts MenuItemStmts func init() { DbInits.Add(func(acc *qgen.Accumulator) error { menuItemStmts = MenuItemStmts{ - update: acc.Update("menu_items").Set("name = ?, htmlID = ?, cssClass = ?, position = ?, path = ?, aria = ?, tooltip = ?, tmplName = ?, guestOnly = ?, memberOnly = ?, staffOnly = ?, adminOnly = ?").Where("miid = ?").Prepare(), + update: acc.Update("menu_items").Set("name = ?, htmlID = ?, cssClass = ?, position = ?, path = ?, aria = ?, tooltip = ?, tmplName = ?, guestOnly = ?, memberOnly = ?, staffOnly = ?, adminOnly = ?").Where("miid = ?").Prepare(), + insert: acc.Insert("menu_items").Columns("mid, name, htmlID, cssClass, position, path, aria, tooltip, tmplName, guestOnly, memberOnly, staffOnly, adminOnly").Fields("?,?,?,?,?,?,?,?,?,?,?,?,?").Prepare(), + delete: acc.Delete("menu_items").Where("miid = ?").Prepare(), + updateOrder: acc.Update("menu_items").Set("order = ?").Where("miid = ?").Prepare(), } return acc.FirstError() }) @@ -64,6 +72,23 @@ func (item MenuItem) Commit() error { return err } +func (item MenuItem) Create() (int, error) { + res, err := menuItemStmts.insert.Exec(item.MenuID, item.Name, item.HTMLID, item.CSSClass, item.Position, item.Path, item.Aria, item.Tooltip, item.TmplName, item.GuestOnly, item.MemberOnly, item.SuperModOnly, item.AdminOnly) + if err != nil { + return 0, err + } + Menus.Load(item.MenuID) + + miid64, err := res.LastInsertId() + return int(miid64), err +} + +func (item MenuItem) Delete() error { + _, err := menuItemStmts.delete.Exec(item.ID) + Menus.Load(item.MenuID) + return err +} + func (hold *MenuListHolder) LoadTmpl(name string) (menuTmpl MenuTmpl, err error) { data, err := ioutil.ReadFile("./templates/" + name + ".html") if err != nil { @@ -72,6 +97,18 @@ func (hold *MenuListHolder) LoadTmpl(name string) (menuTmpl MenuTmpl, err error) return hold.Parse(name, data), nil } +// TODO: Make this atomic, maybe with a transaction or store the order on the menu itself? +func (hold *MenuListHolder) UpdateOrder(updateMap map[int]int) error { + for miid, order := range updateMap { + _, err := menuItemStmts.updateOrder.Exec(order, miid) + if err != nil { + return err + } + } + Menus.Load(hold.MenuID) + return nil +} + func (hold *MenuListHolder) LoadTmpls() (tmpls map[string]MenuTmpl, err error) { tmpls = make(map[string]MenuTmpl) var loadTmpl = func(name string) error { diff --git a/common/pages.go b/common/pages.go index 9c920ada..ca55505c 100644 --- a/common/pages.go +++ b/common/pages.go @@ -276,7 +276,7 @@ type PanelMenuPage struct { *Header Stats PanelStats Zone string - ID int + MenuID int ItemList []MenuItem } diff --git a/common/user.go b/common/user.go index 556ac2a4..0b8b0492 100644 --- a/common/user.go +++ b/common/user.go @@ -1,7 +1,7 @@ /* * * Gosora User File -* Copyright Azareal 2017 - 2018 +* Copyright Azareal 2017 - 2019 * */ package common @@ -88,7 +88,7 @@ func init() { activate: acc.SimpleUpdate("users", "active = 1", where), changeGroup: acc.SimpleUpdate("users", "group = ?", where), // TODO: Implement user_count for users_groups here delete: acc.SimpleDelete("users", where), - setAvatar: acc.SimpleUpdate("users", "avatar = ?", where), + setAvatar: acc.Update("users").Set("avatar = ?").Where(where).Prepare(), setUsername: acc.Update("users").Set("name = ?").Where(where).Prepare(), incrementTopics: acc.SimpleUpdate("users", "topics = topics + ?", where), updateLevel: acc.SimpleUpdate("users", "level = ?", where), @@ -101,7 +101,7 @@ func init() { //recalcLastLiked: acc... updateLastIP: acc.SimpleUpdate("users", "last_ip = ?", where), - setPassword: acc.SimpleUpdate("users", "password = ?, salt = ?", where), + setPassword: acc.Update("users").Set("password = ?, salt = ?").Where(where).Prepare(), } return acc.FirstError() }) @@ -244,9 +244,7 @@ func (user *User) ChangeName(username string) (err error) { } func (user *User) ChangeAvatar(avatar string) (err error) { - _, err = userStmts.setAvatar.Exec(avatar, user.ID) - user.CacheRemove() - return err + return user.bindStmt(userStmts.setAvatar, avatar) } func (user *User) ChangeGroup(group int) (err error) { diff --git a/config_default.noparse b/config_default.noparse index 3203f775..dc8ac451 100644 --- a/config_default.noparse +++ b/config_default.noparse @@ -1,8 +1,8 @@ package main -import "./common" +import "../common" -func init() { +func Config() { // Site Info common.Site.ShortName = "Ts" // This should be less than three letters to fit in the navbar common.Site.Name = "Test Site" diff --git a/gen_router.go b/gen_router.go index 4c5f0015..23427049 100644 --- a/gen_router.go +++ b/gen_router.go @@ -53,6 +53,9 @@ var RouteMap = map[string]interface{}{ "routePanelThemesMenusEdit": routePanelThemesMenusEdit, "routePanelThemesMenuItemEdit": routePanelThemesMenuItemEdit, "routePanelThemesMenuItemEditSubmit": routePanelThemesMenuItemEditSubmit, + "routePanelThemesMenuItemCreateSubmit": routePanelThemesMenuItemCreateSubmit, + "routePanelThemesMenuItemDeleteSubmit": routePanelThemesMenuItemDeleteSubmit, + "routePanelThemesMenuItemOrderSubmit": routePanelThemesMenuItemOrderSubmit, "routePanelPlugins": routePanelPlugins, "routePanelPluginsActivate": routePanelPluginsActivate, "routePanelPluginsDeactivate": routePanelPluginsDeactivate, @@ -166,81 +169,84 @@ var routeMapEnum = map[string]int{ "routePanelThemesMenusEdit": 31, "routePanelThemesMenuItemEdit": 32, "routePanelThemesMenuItemEditSubmit": 33, - "routePanelPlugins": 34, - "routePanelPluginsActivate": 35, - "routePanelPluginsDeactivate": 36, - "routePanelPluginsInstall": 37, - "routePanelUsers": 38, - "routePanelUsersEdit": 39, - "routePanelUsersEditSubmit": 40, - "routePanelAnalyticsViews": 41, - "routePanelAnalyticsRoutes": 42, - "routePanelAnalyticsAgents": 43, - "routePanelAnalyticsSystems": 44, - "routePanelAnalyticsLanguages": 45, - "routePanelAnalyticsReferrers": 46, - "routePanelAnalyticsRouteViews": 47, - "routePanelAnalyticsAgentViews": 48, - "routePanelAnalyticsForumViews": 49, - "routePanelAnalyticsSystemViews": 50, - "routePanelAnalyticsLanguageViews": 51, - "routePanelAnalyticsReferrerViews": 52, - "routePanelAnalyticsPosts": 53, - "routePanelAnalyticsTopics": 54, - "routePanelAnalyticsForums": 55, - "routePanelGroups": 56, - "routePanelGroupsEdit": 57, - "routePanelGroupsEditPerms": 58, - "routePanelGroupsEditSubmit": 59, - "routePanelGroupsEditPermsSubmit": 60, - "routePanelGroupsCreateSubmit": 61, - "routePanelBackups": 62, - "routePanelLogsMod": 63, - "routePanelDebug": 64, - "routePanelDashboard": 65, - "routes.AccountEditCritical": 66, - "routeAccountEditCriticalSubmit": 67, - "routeAccountEditAvatar": 68, - "routeAccountEditAvatarSubmit": 69, - "routeAccountEditUsername": 70, - "routeAccountEditUsernameSubmit": 71, - "routeAccountEditEmail": 72, - "routeAccountEditEmailTokenSubmit": 73, - "routes.ViewProfile": 74, - "routes.BanUserSubmit": 75, - "routes.UnbanUser": 76, - "routes.ActivateUser": 77, - "routes.IPSearch": 78, - "routes.CreateTopicSubmit": 79, - "routes.EditTopicSubmit": 80, - "routes.DeleteTopicSubmit": 81, - "routes.StickTopicSubmit": 82, - "routes.UnstickTopicSubmit": 83, - "routes.LockTopicSubmit": 84, - "routes.UnlockTopicSubmit": 85, - "routes.MoveTopicSubmit": 86, - "routeLikeTopicSubmit": 87, - "routes.ViewTopic": 88, - "routes.CreateReplySubmit": 89, - "routes.ReplyEditSubmit": 90, - "routes.ReplyDeleteSubmit": 91, - "routeReplyLikeSubmit": 92, - "routeProfileReplyCreateSubmit": 93, - "routes.ProfileReplyEditSubmit": 94, - "routes.ProfileReplyDeleteSubmit": 95, - "routes.PollVote": 96, - "routes.PollResults": 97, - "routes.AccountLogin": 98, - "routes.AccountRegister": 99, - "routeLogout": 100, - "routes.AccountLoginSubmit": 101, - "routes.AccountRegisterSubmit": 102, - "routeDynamic": 103, - "routeUploads": 104, - "routes.StaticFile": 105, - "routes.RobotsTxt": 106, - "routes.SitemapXml": 107, - "BadRoute": 108, + "routePanelThemesMenuItemCreateSubmit": 34, + "routePanelThemesMenuItemDeleteSubmit": 35, + "routePanelThemesMenuItemOrderSubmit": 36, + "routePanelPlugins": 37, + "routePanelPluginsActivate": 38, + "routePanelPluginsDeactivate": 39, + "routePanelPluginsInstall": 40, + "routePanelUsers": 41, + "routePanelUsersEdit": 42, + "routePanelUsersEditSubmit": 43, + "routePanelAnalyticsViews": 44, + "routePanelAnalyticsRoutes": 45, + "routePanelAnalyticsAgents": 46, + "routePanelAnalyticsSystems": 47, + "routePanelAnalyticsLanguages": 48, + "routePanelAnalyticsReferrers": 49, + "routePanelAnalyticsRouteViews": 50, + "routePanelAnalyticsAgentViews": 51, + "routePanelAnalyticsForumViews": 52, + "routePanelAnalyticsSystemViews": 53, + "routePanelAnalyticsLanguageViews": 54, + "routePanelAnalyticsReferrerViews": 55, + "routePanelAnalyticsPosts": 56, + "routePanelAnalyticsTopics": 57, + "routePanelAnalyticsForums": 58, + "routePanelGroups": 59, + "routePanelGroupsEdit": 60, + "routePanelGroupsEditPerms": 61, + "routePanelGroupsEditSubmit": 62, + "routePanelGroupsEditPermsSubmit": 63, + "routePanelGroupsCreateSubmit": 64, + "routePanelBackups": 65, + "routePanelLogsMod": 66, + "routePanelDebug": 67, + "routePanelDashboard": 68, + "routes.AccountEditCritical": 69, + "routeAccountEditCriticalSubmit": 70, + "routeAccountEditAvatar": 71, + "routeAccountEditAvatarSubmit": 72, + "routeAccountEditUsername": 73, + "routeAccountEditUsernameSubmit": 74, + "routeAccountEditEmail": 75, + "routeAccountEditEmailTokenSubmit": 76, + "routes.ViewProfile": 77, + "routes.BanUserSubmit": 78, + "routes.UnbanUser": 79, + "routes.ActivateUser": 80, + "routes.IPSearch": 81, + "routes.CreateTopicSubmit": 82, + "routes.EditTopicSubmit": 83, + "routes.DeleteTopicSubmit": 84, + "routes.StickTopicSubmit": 85, + "routes.UnstickTopicSubmit": 86, + "routes.LockTopicSubmit": 87, + "routes.UnlockTopicSubmit": 88, + "routes.MoveTopicSubmit": 89, + "routeLikeTopicSubmit": 90, + "routes.ViewTopic": 91, + "routes.CreateReplySubmit": 92, + "routes.ReplyEditSubmit": 93, + "routes.ReplyDeleteSubmit": 94, + "routeReplyLikeSubmit": 95, + "routeProfileReplyCreateSubmit": 96, + "routes.ProfileReplyEditSubmit": 97, + "routes.ProfileReplyDeleteSubmit": 98, + "routes.PollVote": 99, + "routes.PollResults": 100, + "routes.AccountLogin": 101, + "routes.AccountRegister": 102, + "routeLogout": 103, + "routes.AccountLoginSubmit": 104, + "routes.AccountRegisterSubmit": 105, + "routeDynamic": 106, + "routeUploads": 107, + "routes.StaticFile": 108, + "routes.RobotsTxt": 109, + "routes.SitemapXml": 110, + "BadRoute": 111, } var reverseRouteMapEnum = map[int]string{ 0: "routeAPI", @@ -277,81 +283,84 @@ var reverseRouteMapEnum = map[int]string{ 31: "routePanelThemesMenusEdit", 32: "routePanelThemesMenuItemEdit", 33: "routePanelThemesMenuItemEditSubmit", - 34: "routePanelPlugins", - 35: "routePanelPluginsActivate", - 36: "routePanelPluginsDeactivate", - 37: "routePanelPluginsInstall", - 38: "routePanelUsers", - 39: "routePanelUsersEdit", - 40: "routePanelUsersEditSubmit", - 41: "routePanelAnalyticsViews", - 42: "routePanelAnalyticsRoutes", - 43: "routePanelAnalyticsAgents", - 44: "routePanelAnalyticsSystems", - 45: "routePanelAnalyticsLanguages", - 46: "routePanelAnalyticsReferrers", - 47: "routePanelAnalyticsRouteViews", - 48: "routePanelAnalyticsAgentViews", - 49: "routePanelAnalyticsForumViews", - 50: "routePanelAnalyticsSystemViews", - 51: "routePanelAnalyticsLanguageViews", - 52: "routePanelAnalyticsReferrerViews", - 53: "routePanelAnalyticsPosts", - 54: "routePanelAnalyticsTopics", - 55: "routePanelAnalyticsForums", - 56: "routePanelGroups", - 57: "routePanelGroupsEdit", - 58: "routePanelGroupsEditPerms", - 59: "routePanelGroupsEditSubmit", - 60: "routePanelGroupsEditPermsSubmit", - 61: "routePanelGroupsCreateSubmit", - 62: "routePanelBackups", - 63: "routePanelLogsMod", - 64: "routePanelDebug", - 65: "routePanelDashboard", - 66: "routes.AccountEditCritical", - 67: "routeAccountEditCriticalSubmit", - 68: "routeAccountEditAvatar", - 69: "routeAccountEditAvatarSubmit", - 70: "routeAccountEditUsername", - 71: "routeAccountEditUsernameSubmit", - 72: "routeAccountEditEmail", - 73: "routeAccountEditEmailTokenSubmit", - 74: "routes.ViewProfile", - 75: "routes.BanUserSubmit", - 76: "routes.UnbanUser", - 77: "routes.ActivateUser", - 78: "routes.IPSearch", - 79: "routes.CreateTopicSubmit", - 80: "routes.EditTopicSubmit", - 81: "routes.DeleteTopicSubmit", - 82: "routes.StickTopicSubmit", - 83: "routes.UnstickTopicSubmit", - 84: "routes.LockTopicSubmit", - 85: "routes.UnlockTopicSubmit", - 86: "routes.MoveTopicSubmit", - 87: "routeLikeTopicSubmit", - 88: "routes.ViewTopic", - 89: "routes.CreateReplySubmit", - 90: "routes.ReplyEditSubmit", - 91: "routes.ReplyDeleteSubmit", - 92: "routeReplyLikeSubmit", - 93: "routeProfileReplyCreateSubmit", - 94: "routes.ProfileReplyEditSubmit", - 95: "routes.ProfileReplyDeleteSubmit", - 96: "routes.PollVote", - 97: "routes.PollResults", - 98: "routes.AccountLogin", - 99: "routes.AccountRegister", - 100: "routeLogout", - 101: "routes.AccountLoginSubmit", - 102: "routes.AccountRegisterSubmit", - 103: "routeDynamic", - 104: "routeUploads", - 105: "routes.StaticFile", - 106: "routes.RobotsTxt", - 107: "routes.SitemapXml", - 108: "BadRoute", + 34: "routePanelThemesMenuItemCreateSubmit", + 35: "routePanelThemesMenuItemDeleteSubmit", + 36: "routePanelThemesMenuItemOrderSubmit", + 37: "routePanelPlugins", + 38: "routePanelPluginsActivate", + 39: "routePanelPluginsDeactivate", + 40: "routePanelPluginsInstall", + 41: "routePanelUsers", + 42: "routePanelUsersEdit", + 43: "routePanelUsersEditSubmit", + 44: "routePanelAnalyticsViews", + 45: "routePanelAnalyticsRoutes", + 46: "routePanelAnalyticsAgents", + 47: "routePanelAnalyticsSystems", + 48: "routePanelAnalyticsLanguages", + 49: "routePanelAnalyticsReferrers", + 50: "routePanelAnalyticsRouteViews", + 51: "routePanelAnalyticsAgentViews", + 52: "routePanelAnalyticsForumViews", + 53: "routePanelAnalyticsSystemViews", + 54: "routePanelAnalyticsLanguageViews", + 55: "routePanelAnalyticsReferrerViews", + 56: "routePanelAnalyticsPosts", + 57: "routePanelAnalyticsTopics", + 58: "routePanelAnalyticsForums", + 59: "routePanelGroups", + 60: "routePanelGroupsEdit", + 61: "routePanelGroupsEditPerms", + 62: "routePanelGroupsEditSubmit", + 63: "routePanelGroupsEditPermsSubmit", + 64: "routePanelGroupsCreateSubmit", + 65: "routePanelBackups", + 66: "routePanelLogsMod", + 67: "routePanelDebug", + 68: "routePanelDashboard", + 69: "routes.AccountEditCritical", + 70: "routeAccountEditCriticalSubmit", + 71: "routeAccountEditAvatar", + 72: "routeAccountEditAvatarSubmit", + 73: "routeAccountEditUsername", + 74: "routeAccountEditUsernameSubmit", + 75: "routeAccountEditEmail", + 76: "routeAccountEditEmailTokenSubmit", + 77: "routes.ViewProfile", + 78: "routes.BanUserSubmit", + 79: "routes.UnbanUser", + 80: "routes.ActivateUser", + 81: "routes.IPSearch", + 82: "routes.CreateTopicSubmit", + 83: "routes.EditTopicSubmit", + 84: "routes.DeleteTopicSubmit", + 85: "routes.StickTopicSubmit", + 86: "routes.UnstickTopicSubmit", + 87: "routes.LockTopicSubmit", + 88: "routes.UnlockTopicSubmit", + 89: "routes.MoveTopicSubmit", + 90: "routeLikeTopicSubmit", + 91: "routes.ViewTopic", + 92: "routes.CreateReplySubmit", + 93: "routes.ReplyEditSubmit", + 94: "routes.ReplyDeleteSubmit", + 95: "routeReplyLikeSubmit", + 96: "routeProfileReplyCreateSubmit", + 97: "routes.ProfileReplyEditSubmit", + 98: "routes.ProfileReplyDeleteSubmit", + 99: "routes.PollVote", + 100: "routes.PollResults", + 101: "routes.AccountLogin", + 102: "routes.AccountRegister", + 103: "routeLogout", + 104: "routes.AccountLoginSubmit", + 105: "routes.AccountRegisterSubmit", + 106: "routeDynamic", + 107: "routeUploads", + 108: "routes.StaticFile", + 109: "routes.RobotsTxt", + 110: "routes.SitemapXml", + 111: "BadRoute", } var osMapEnum = map[string]int{ "unknown": 0, @@ -647,7 +656,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { counters.GlobalViewCounter.Bump() if prefix == "/static" { - counters.RouteViewCounter.Bump(105) + counters.RouteViewCounter.Bump(108) req.URL.Path += extraData routes.StaticFile(w, req) return @@ -1068,8 +1077,35 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { counters.RouteViewCounter.Bump(33) err = routePanelThemesMenuItemEditSubmit(w,req,user,extraData) - case "/panel/plugins/": + case "/panel/themes/menus/item/create/submit/": + err = common.NoSessionMismatch(w,req,user) + if err != nil { + router.handleError(err,w,req,user) + return + } + counters.RouteViewCounter.Bump(34) + err = routePanelThemesMenuItemCreateSubmit(w,req,user) + case "/panel/themes/menus/item/delete/submit/": + err = common.NoSessionMismatch(w,req,user) + if err != nil { + router.handleError(err,w,req,user) + return + } + + counters.RouteViewCounter.Bump(35) + err = routePanelThemesMenuItemDeleteSubmit(w,req,user,extraData) + case "/panel/themes/menus/item/order/edit/submit/": + err = common.NoSessionMismatch(w,req,user) + if err != nil { + router.handleError(err,w,req,user) + return + } + + counters.RouteViewCounter.Bump(36) + err = routePanelThemesMenuItemOrderSubmit(w,req,user,extraData) + case "/panel/plugins/": + counters.RouteViewCounter.Bump(37) err = routePanelPlugins(w,req,user) case "/panel/plugins/activate/": err = common.NoSessionMismatch(w,req,user) @@ -1078,7 +1114,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - counters.RouteViewCounter.Bump(35) + counters.RouteViewCounter.Bump(38) err = routePanelPluginsActivate(w,req,user,extraData) case "/panel/plugins/deactivate/": err = common.NoSessionMismatch(w,req,user) @@ -1087,7 +1123,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - counters.RouteViewCounter.Bump(36) + counters.RouteViewCounter.Bump(39) err = routePanelPluginsDeactivate(w,req,user,extraData) case "/panel/plugins/install/": err = common.NoSessionMismatch(w,req,user) @@ -1096,13 +1132,13 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - counters.RouteViewCounter.Bump(37) + counters.RouteViewCounter.Bump(40) err = routePanelPluginsInstall(w,req,user,extraData) case "/panel/users/": - counters.RouteViewCounter.Bump(38) + counters.RouteViewCounter.Bump(41) err = routePanelUsers(w,req,user) case "/panel/users/edit/": - counters.RouteViewCounter.Bump(39) + counters.RouteViewCounter.Bump(42) err = routePanelUsersEdit(w,req,user,extraData) case "/panel/users/edit/submit/": err = common.NoSessionMismatch(w,req,user) @@ -1111,7 +1147,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - counters.RouteViewCounter.Bump(40) + counters.RouteViewCounter.Bump(43) err = routePanelUsersEditSubmit(w,req,user,extraData) case "/panel/analytics/views/": err = common.ParseForm(w,req,user) @@ -1120,7 +1156,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - counters.RouteViewCounter.Bump(41) + counters.RouteViewCounter.Bump(44) err = routePanelAnalyticsViews(w,req,user) case "/panel/analytics/routes/": err = common.ParseForm(w,req,user) @@ -1129,7 +1165,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - counters.RouteViewCounter.Bump(42) + counters.RouteViewCounter.Bump(45) err = routePanelAnalyticsRoutes(w,req,user) case "/panel/analytics/agents/": err = common.ParseForm(w,req,user) @@ -1138,7 +1174,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - counters.RouteViewCounter.Bump(43) + counters.RouteViewCounter.Bump(46) err = routePanelAnalyticsAgents(w,req,user) case "/panel/analytics/systems/": err = common.ParseForm(w,req,user) @@ -1147,7 +1183,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - counters.RouteViewCounter.Bump(44) + counters.RouteViewCounter.Bump(47) err = routePanelAnalyticsSystems(w,req,user) case "/panel/analytics/langs/": err = common.ParseForm(w,req,user) @@ -1156,7 +1192,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - counters.RouteViewCounter.Bump(45) + counters.RouteViewCounter.Bump(48) err = routePanelAnalyticsLanguages(w,req,user) case "/panel/analytics/referrers/": err = common.ParseForm(w,req,user) @@ -1165,25 +1201,25 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - counters.RouteViewCounter.Bump(46) + counters.RouteViewCounter.Bump(49) err = routePanelAnalyticsReferrers(w,req,user) case "/panel/analytics/route/": - counters.RouteViewCounter.Bump(47) + counters.RouteViewCounter.Bump(50) err = routePanelAnalyticsRouteViews(w,req,user,extraData) case "/panel/analytics/agent/": - counters.RouteViewCounter.Bump(48) + counters.RouteViewCounter.Bump(51) err = routePanelAnalyticsAgentViews(w,req,user,extraData) case "/panel/analytics/forum/": - counters.RouteViewCounter.Bump(49) + counters.RouteViewCounter.Bump(52) err = routePanelAnalyticsForumViews(w,req,user,extraData) case "/panel/analytics/system/": - counters.RouteViewCounter.Bump(50) + counters.RouteViewCounter.Bump(53) err = routePanelAnalyticsSystemViews(w,req,user,extraData) case "/panel/analytics/lang/": - counters.RouteViewCounter.Bump(51) + counters.RouteViewCounter.Bump(54) err = routePanelAnalyticsLanguageViews(w,req,user,extraData) case "/panel/analytics/referrer/": - counters.RouteViewCounter.Bump(52) + counters.RouteViewCounter.Bump(55) err = routePanelAnalyticsReferrerViews(w,req,user,extraData) case "/panel/analytics/posts/": err = common.ParseForm(w,req,user) @@ -1192,7 +1228,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - counters.RouteViewCounter.Bump(53) + counters.RouteViewCounter.Bump(56) err = routePanelAnalyticsPosts(w,req,user) case "/panel/analytics/topics/": err = common.ParseForm(w,req,user) @@ -1201,7 +1237,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - counters.RouteViewCounter.Bump(54) + counters.RouteViewCounter.Bump(57) err = routePanelAnalyticsTopics(w,req,user) case "/panel/analytics/forums/": err = common.ParseForm(w,req,user) @@ -1210,16 +1246,16 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - counters.RouteViewCounter.Bump(55) + counters.RouteViewCounter.Bump(58) err = routePanelAnalyticsForums(w,req,user) case "/panel/groups/": - counters.RouteViewCounter.Bump(56) + counters.RouteViewCounter.Bump(59) err = routePanelGroups(w,req,user) case "/panel/groups/edit/": - counters.RouteViewCounter.Bump(57) + counters.RouteViewCounter.Bump(60) err = routePanelGroupsEdit(w,req,user,extraData) case "/panel/groups/edit/perms/": - counters.RouteViewCounter.Bump(58) + counters.RouteViewCounter.Bump(61) err = routePanelGroupsEditPerms(w,req,user,extraData) case "/panel/groups/edit/submit/": err = common.NoSessionMismatch(w,req,user) @@ -1228,7 +1264,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - counters.RouteViewCounter.Bump(59) + counters.RouteViewCounter.Bump(62) err = routePanelGroupsEditSubmit(w,req,user,extraData) case "/panel/groups/edit/perms/submit/": err = common.NoSessionMismatch(w,req,user) @@ -1237,7 +1273,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - counters.RouteViewCounter.Bump(60) + counters.RouteViewCounter.Bump(63) err = routePanelGroupsEditPermsSubmit(w,req,user,extraData) case "/panel/groups/create/": err = common.NoSessionMismatch(w,req,user) @@ -1246,7 +1282,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - counters.RouteViewCounter.Bump(61) + counters.RouteViewCounter.Bump(64) err = routePanelGroupsCreateSubmit(w,req,user) case "/panel/backups/": err = common.SuperAdminOnly(w,req,user) @@ -1255,10 +1291,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - counters.RouteViewCounter.Bump(62) + counters.RouteViewCounter.Bump(65) err = routePanelBackups(w,req,user,extraData) case "/panel/logs/mod/": - counters.RouteViewCounter.Bump(63) + counters.RouteViewCounter.Bump(66) err = routePanelLogsMod(w,req,user) case "/panel/debug/": err = common.AdminOnly(w,req,user) @@ -1267,10 +1303,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - counters.RouteViewCounter.Bump(64) + counters.RouteViewCounter.Bump(67) err = routePanelDebug(w,req,user) default: - counters.RouteViewCounter.Bump(65) + counters.RouteViewCounter.Bump(68) err = routePanelDashboard(w,req,user) } if err != nil { @@ -1285,7 +1321,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - counters.RouteViewCounter.Bump(66) + counters.RouteViewCounter.Bump(69) err = routes.AccountEditCritical(w,req,user) case "/user/edit/critical/submit/": err = common.NoSessionMismatch(w,req,user) @@ -1300,7 +1336,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - counters.RouteViewCounter.Bump(67) + counters.RouteViewCounter.Bump(70) err = routeAccountEditCriticalSubmit(w,req,user) case "/user/edit/avatar/": err = common.MemberOnly(w,req,user) @@ -1309,7 +1345,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - counters.RouteViewCounter.Bump(68) + counters.RouteViewCounter.Bump(71) err = routeAccountEditAvatar(w,req,user) case "/user/edit/avatar/submit/": err = common.MemberOnly(w,req,user) @@ -1329,7 +1365,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - counters.RouteViewCounter.Bump(69) + counters.RouteViewCounter.Bump(72) err = routeAccountEditAvatarSubmit(w,req,user) case "/user/edit/username/": err = common.MemberOnly(w,req,user) @@ -1338,7 +1374,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - counters.RouteViewCounter.Bump(70) + counters.RouteViewCounter.Bump(73) err = routeAccountEditUsername(w,req,user) case "/user/edit/username/submit/": err = common.NoSessionMismatch(w,req,user) @@ -1353,7 +1389,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - counters.RouteViewCounter.Bump(71) + counters.RouteViewCounter.Bump(74) err = routeAccountEditUsernameSubmit(w,req,user) case "/user/edit/email/": err = common.MemberOnly(w,req,user) @@ -1362,7 +1398,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - counters.RouteViewCounter.Bump(72) + counters.RouteViewCounter.Bump(75) err = routeAccountEditEmail(w,req,user) case "/user/edit/token/": err = common.NoSessionMismatch(w,req,user) @@ -1377,11 +1413,11 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - counters.RouteViewCounter.Bump(73) + counters.RouteViewCounter.Bump(76) err = routeAccountEditEmailTokenSubmit(w,req,user,extraData) default: req.URL.Path += extraData - counters.RouteViewCounter.Bump(74) + counters.RouteViewCounter.Bump(77) err = routes.ViewProfile(w,req,user) } if err != nil { @@ -1402,7 +1438,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - counters.RouteViewCounter.Bump(75) + counters.RouteViewCounter.Bump(78) err = routes.BanUserSubmit(w,req,user,extraData) case "/users/unban/": err = common.NoSessionMismatch(w,req,user) @@ -1417,7 +1453,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - counters.RouteViewCounter.Bump(76) + counters.RouteViewCounter.Bump(79) err = routes.UnbanUser(w,req,user,extraData) case "/users/activate/": err = common.NoSessionMismatch(w,req,user) @@ -1432,7 +1468,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - counters.RouteViewCounter.Bump(77) + counters.RouteViewCounter.Bump(80) err = routes.ActivateUser(w,req,user,extraData) case "/users/ips/": err = common.MemberOnly(w,req,user) @@ -1441,7 +1477,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - counters.RouteViewCounter.Bump(78) + counters.RouteViewCounter.Bump(81) err = routes.IPSearch(w,req,user) } if err != nil { @@ -1467,7 +1503,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - counters.RouteViewCounter.Bump(79) + counters.RouteViewCounter.Bump(82) err = routes.CreateTopicSubmit(w,req,user) case "/topic/edit/submit/": err = common.NoSessionMismatch(w,req,user) @@ -1482,7 +1518,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - counters.RouteViewCounter.Bump(80) + counters.RouteViewCounter.Bump(83) err = routes.EditTopicSubmit(w,req,user,extraData) case "/topic/delete/submit/": err = common.NoSessionMismatch(w,req,user) @@ -1498,7 +1534,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { } req.URL.Path += extraData - counters.RouteViewCounter.Bump(81) + counters.RouteViewCounter.Bump(84) err = routes.DeleteTopicSubmit(w,req,user) case "/topic/stick/submit/": err = common.NoSessionMismatch(w,req,user) @@ -1513,7 +1549,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - counters.RouteViewCounter.Bump(82) + counters.RouteViewCounter.Bump(85) err = routes.StickTopicSubmit(w,req,user,extraData) case "/topic/unstick/submit/": err = common.NoSessionMismatch(w,req,user) @@ -1528,7 +1564,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - counters.RouteViewCounter.Bump(83) + counters.RouteViewCounter.Bump(86) err = routes.UnstickTopicSubmit(w,req,user,extraData) case "/topic/lock/submit/": err = common.NoSessionMismatch(w,req,user) @@ -1544,7 +1580,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { } req.URL.Path += extraData - counters.RouteViewCounter.Bump(84) + counters.RouteViewCounter.Bump(87) err = routes.LockTopicSubmit(w,req,user) case "/topic/unlock/submit/": err = common.NoSessionMismatch(w,req,user) @@ -1559,7 +1595,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - counters.RouteViewCounter.Bump(85) + counters.RouteViewCounter.Bump(88) err = routes.UnlockTopicSubmit(w,req,user,extraData) case "/topic/move/submit/": err = common.NoSessionMismatch(w,req,user) @@ -1574,7 +1610,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - counters.RouteViewCounter.Bump(86) + counters.RouteViewCounter.Bump(89) err = routes.MoveTopicSubmit(w,req,user,extraData) case "/topic/like/submit/": err = common.NoSessionMismatch(w,req,user) @@ -1595,10 +1631,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - counters.RouteViewCounter.Bump(87) + counters.RouteViewCounter.Bump(90) err = routeLikeTopicSubmit(w,req,user,extraData) default: - counters.RouteViewCounter.Bump(88) + counters.RouteViewCounter.Bump(91) err = routes.ViewTopic(w,req,user, extraData) } if err != nil { @@ -1624,7 +1660,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - counters.RouteViewCounter.Bump(89) + counters.RouteViewCounter.Bump(92) err = routes.CreateReplySubmit(w,req,user) case "/reply/edit/submit/": err = common.NoSessionMismatch(w,req,user) @@ -1639,7 +1675,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - counters.RouteViewCounter.Bump(90) + counters.RouteViewCounter.Bump(93) err = routes.ReplyEditSubmit(w,req,user,extraData) case "/reply/delete/submit/": err = common.NoSessionMismatch(w,req,user) @@ -1654,7 +1690,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - counters.RouteViewCounter.Bump(91) + counters.RouteViewCounter.Bump(94) err = routes.ReplyDeleteSubmit(w,req,user,extraData) case "/reply/like/submit/": err = common.NoSessionMismatch(w,req,user) @@ -1675,7 +1711,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - counters.RouteViewCounter.Bump(92) + counters.RouteViewCounter.Bump(95) err = routeReplyLikeSubmit(w,req,user,extraData) } if err != nil { @@ -1696,7 +1732,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - counters.RouteViewCounter.Bump(93) + counters.RouteViewCounter.Bump(96) err = routeProfileReplyCreateSubmit(w,req,user) case "/profile/reply/edit/submit/": err = common.NoSessionMismatch(w,req,user) @@ -1711,7 +1747,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - counters.RouteViewCounter.Bump(94) + counters.RouteViewCounter.Bump(97) err = routes.ProfileReplyEditSubmit(w,req,user,extraData) case "/profile/reply/delete/submit/": err = common.NoSessionMismatch(w,req,user) @@ -1726,7 +1762,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - counters.RouteViewCounter.Bump(95) + counters.RouteViewCounter.Bump(98) err = routes.ProfileReplyDeleteSubmit(w,req,user,extraData) } if err != nil { @@ -1747,10 +1783,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - counters.RouteViewCounter.Bump(96) + counters.RouteViewCounter.Bump(99) err = routes.PollVote(w,req,user,extraData) case "/poll/results/": - counters.RouteViewCounter.Bump(97) + counters.RouteViewCounter.Bump(100) err = routes.PollResults(w,req,user,extraData) } if err != nil { @@ -1759,10 +1795,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { case "/accounts": switch(req.URL.Path) { case "/accounts/login/": - counters.RouteViewCounter.Bump(98) + counters.RouteViewCounter.Bump(101) err = routes.AccountLogin(w,req,user) case "/accounts/create/": - counters.RouteViewCounter.Bump(99) + counters.RouteViewCounter.Bump(102) err = routes.AccountRegister(w,req,user) case "/accounts/logout/": err = common.NoSessionMismatch(w,req,user) @@ -1777,7 +1813,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - counters.RouteViewCounter.Bump(100) + counters.RouteViewCounter.Bump(103) err = routeLogout(w,req,user) case "/accounts/login/submit/": err = common.ParseForm(w,req,user) @@ -1786,7 +1822,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - counters.RouteViewCounter.Bump(101) + counters.RouteViewCounter.Bump(104) err = routes.AccountLoginSubmit(w,req,user) case "/accounts/create/submit/": err = common.ParseForm(w,req,user) @@ -1795,7 +1831,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - counters.RouteViewCounter.Bump(102) + counters.RouteViewCounter.Bump(105) err = routes.AccountRegisterSubmit(w,req,user) } if err != nil { @@ -1812,7 +1848,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { common.NotFound(w,req,nil) return } - counters.RouteViewCounter.Bump(104) + counters.RouteViewCounter.Bump(107) req.URL.Path += extraData // TODO: Find a way to propagate errors up from this? router.UploadHandler(w,req) // TODO: Count these views @@ -1821,14 +1857,14 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { // TODO: Add support for favicons and robots.txt files switch(extraData) { case "robots.txt": - counters.RouteViewCounter.Bump(106) + counters.RouteViewCounter.Bump(109) err = routes.RobotsTxt(w,req) if err != nil { router.handleError(err,w,req,user) } return /*case "sitemap.xml": - counters.RouteViewCounter.Bump(107) + counters.RouteViewCounter.Bump(110) err = routes.SitemapXml(w,req) if err != nil { router.handleError(err,w,req,user) @@ -1857,7 +1893,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { router.RUnlock() if ok { - counters.RouteViewCounter.Bump(103) // TODO: Be more specific about *which* dynamic route it is + counters.RouteViewCounter.Bump(106) // TODO: Be more specific about *which* dynamic route it is req.URL.Path += extraData err = handle(w,req,user) if err != nil { @@ -1872,7 +1908,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { } else { router.DumpRequest(req,"Bad Route") } - counters.RouteViewCounter.Bump(108) + counters.RouteViewCounter.Bump(111) common.NotFound(w,req,nil) } } diff --git a/langs/english.json b/langs/english.json index 8092128e..29dfce23 100644 --- a/langs/english.json +++ b/langs/english.json @@ -701,15 +701,21 @@ "panel_themes_make_default":"Make Default", "panel_themes_menus_head":"Menus", + "panel_themes_menus_item_edit_button_aria":"Edit menu item", + "panel_themes_menus_item_delete_button_aria":"Delete menu item", "panel_themes_menus_edit_head":"Menu Editor", + "panel_themes_menus_create_head":"Create Menu Item", "panel_themes_menus_name":"Name", + "panel_themes_menus_name_placeholder":"Item Name", "panel_themes_menus_htmlid":"HTML ID", "panel_themes_menus_cssclass":"CSS Class", "panel_themes_menus_position":"Position", "panel_themes_menus_path":"Path", "panel_themes_menus_aria":"Aria", + "panel_themes_menus_aria_placeholder":"Short description for helping those with poor vision", "panel_themes_menus_tooltip":"Tooltip", + "panel_themes_menus_tooltip_placeholder":"A tooltip shown when you hover over it", "panel_themes_menus_tmplname":"Template", "panel_themes_menus_permissions":"Who Can See", "panel_themes_menus_everyone": "Everyone", @@ -718,6 +724,7 @@ "panel_themes_menus_supermodonly":"Super Mods", "panel_themes_menus_adminonly":"Admins", "panel_themes_menus_edit_update_button":"Update", + "panel_themes_menus_create_button":"Create", "panel_settings_head":"Settings", "panel_setting_head":"Edit Setting", diff --git a/panel_routes.go b/panel_routes.go index 3c9d1e88..cbb510ba 100644 --- a/panel_routes.go +++ b/panel_routes.go @@ -2417,6 +2417,7 @@ func routePanelThemesMenusEdit(w http.ResponseWriter, r *http.Request, user comm } // TODO: Something like Menu #1 for the title? header.Title = common.GetTitlePhrase("panel_themes_menus_edit") + header.AddScript("Sortable-1.4.0/Sortable.min.js") mid, err := strconv.Atoi(smid) if err != nil { @@ -2481,29 +2482,7 @@ func routePanelThemesMenuItemEdit(w http.ResponseWriter, r *http.Request, user c return panelRenderTemplate("panel_themes_menus_item_edit", w, r, user, &pi) } -func routePanelThemesMenuItemEditSubmit(w http.ResponseWriter, r *http.Request, user common.User, sitemID string) common.RouteError { - _, ferr := common.SimplePanelUserCheck(w, r, &user) - if ferr != nil { - return ferr - } - isJs := (r.PostFormValue("js") == "1") - if !user.Perms.ManageThemes { - return common.NoPermissionsJSQ(w, r, user, isJs) - } - - itemID, err := strconv.Atoi(sitemID) - if err != nil { - return common.LocalErrorJSQ("Invalid integer", w, r, user, isJs) - } - - menuItem, err := common.Menus.ItemStore().Get(itemID) - if err == ErrNoRows { - return common.LocalErrorJSQ("This item doesn't exist.", w, r, user, isJs) - } else if err != nil { - return common.InternalErrorJSQ(err, w, r, isJs) - } - //menuItem = menuItem.Copy() // If we switch this for a pointer, we might need this as a scratchpad - +func routePanelThemesMenuItemSetters(r *http.Request, menuItem common.MenuItem) common.MenuItem { var getItem = func(name string) string { return html.EscapeString(strings.Replace(r.PostFormValue("item-"+name), "\n", "", -1)) } @@ -2519,8 +2498,7 @@ func routePanelThemesMenuItemEditSubmit(w http.ResponseWriter, r *http.Request, menuItem.Tooltip = getItem("tooltip") menuItem.TmplName = getItem("tmplname") - var perms = getItem("permissions") - switch perms { + switch getItem("permissions") { case "everyone": menuItem.GuestOnly = false menuItem.MemberOnly = false @@ -2547,6 +2525,32 @@ func routePanelThemesMenuItemEditSubmit(w http.ResponseWriter, r *http.Request, menuItem.SuperModOnly = true menuItem.AdminOnly = true } + return menuItem +} + +func routePanelThemesMenuItemEditSubmit(w http.ResponseWriter, r *http.Request, user common.User, sitemID string) common.RouteError { + _, ferr := common.SimplePanelUserCheck(w, r, &user) + if ferr != nil { + return ferr + } + isJs := (r.PostFormValue("js") == "1") + if !user.Perms.ManageThemes { + return common.NoPermissionsJSQ(w, r, user, isJs) + } + + itemID, err := strconv.Atoi(sitemID) + if err != nil { + return common.LocalErrorJSQ("Invalid integer", w, r, user, isJs) + } + + menuItem, err := common.Menus.ItemStore().Get(itemID) + if err == ErrNoRows { + return common.LocalErrorJSQ("This item doesn't exist.", w, r, user, isJs) + } else if err != nil { + return common.InternalErrorJSQ(err, w, r, isJs) + } + //menuItem = menuItem.Copy() // If we switch this for a pointer, we might need this as a scratchpad + menuItem = routePanelThemesMenuItemSetters(r, menuItem) err = menuItem.Commit() if err != nil { @@ -2555,6 +2559,100 @@ func routePanelThemesMenuItemEditSubmit(w http.ResponseWriter, r *http.Request, return panelSuccessRedirect("/panel/themes/menus/item/edit/"+strconv.Itoa(itemID), w, r, isJs) } +func routePanelThemesMenuItemCreateSubmit(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { + _, ferr := common.SimplePanelUserCheck(w, r, &user) + if ferr != nil { + return ferr + } + + isJs := (r.PostFormValue("js") == "1") + if !user.Perms.ManageThemes { + return common.NoPermissionsJSQ(w, r, user, isJs) + } + smenuID := r.PostFormValue("mid") + if smenuID == "" { + return common.LocalErrorJSQ("No menuID provided", w, r, user, isJs) + } + menuID, err := strconv.Atoi(smenuID) + if err != nil { + return common.LocalErrorJSQ("Invalid integer", w, r, user, isJs) + } + + menuItem := common.MenuItem{MenuID: menuID} + menuItem = routePanelThemesMenuItemSetters(r, menuItem) + itemID, err := menuItem.Create() + if err != nil { + return common.InternalErrorJSQ(err, w, r, isJs) + } + return panelSuccessRedirect("/panel/themes/menus/item/edit/"+strconv.Itoa(itemID), w, r, isJs) +} + +func routePanelThemesMenuItemDeleteSubmit(w http.ResponseWriter, r *http.Request, user common.User, sitemID string) common.RouteError { + _, ferr := common.SimplePanelUserCheck(w, r, &user) + if ferr != nil { + return ferr + } + isJs := (r.PostFormValue("js") == "1") + if !user.Perms.ManageThemes { + return common.NoPermissionsJSQ(w, r, user, isJs) + } + + itemID, err := strconv.Atoi(sitemID) + if err != nil { + return common.LocalErrorJSQ("Invalid integer", w, r, user, isJs) + } + menuItem, err := common.Menus.ItemStore().Get(itemID) + if err == ErrNoRows { + return common.LocalErrorJSQ("This item doesn't exist.", w, r, user, isJs) + } else if err != nil { + return common.InternalErrorJSQ(err, w, r, isJs) + } + //menuItem = menuItem.Copy() // If we switch this for a pointer, we might need this as a scratchpad + + err = menuItem.Delete() + if err != nil { + return common.InternalErrorJSQ(err, w, r, isJs) + } + return panelSuccessRedirect("/panel/themes/menus/", w, r, isJs) +} + +func routePanelThemesMenuItemOrderSubmit(w http.ResponseWriter, r *http.Request, user common.User, smid string) common.RouteError { + _, ferr := common.SimplePanelUserCheck(w, r, &user) + if ferr != nil { + return ferr + } + isJs := (r.PostFormValue("js") == "1") + if !user.Perms.ManageThemes { + return common.NoPermissionsJSQ(w, r, user, isJs) + } + + mid, err := strconv.Atoi(smid) + if err != nil { + return common.LocalErrorJSQ("Invalid integer", w, r, user, isJs) + } + menuHold, err := common.Menus.Get(mid) + if err == ErrNoRows { + return common.LocalErrorJSQ("Can't find menu", w, r, user, isJs) + } else if err != nil { + return common.InternalErrorJSQ(err, w, r, isJs) + } + + sitems := strings.TrimSuffix(strings.TrimPrefix(r.PostFormValue("items"), "{"), "}") + fmt.Printf("sitems: %+v\n", sitems) + + var updateMap = make(map[int]int) + for index, smiid := range strings.Split(sitems, ",") { + miid, err := strconv.Atoi(smiid) + if err != nil { + return common.LocalErrorJSQ("Invalid integer in menu item list", w, r, user, isJs) + } + updateMap[miid] = index + } + menuHold.UpdateOrder(updateMap) + + return panelSuccessRedirect("/panel/themes/menus/edit/"+strconv.Itoa(mid), w, r, isJs) +} + func routePanelBackups(w http.ResponseWriter, r *http.Request, user common.User, backupURL string) common.RouteError { headerVars, stats, ferr := common.PanelUserCheck(w, r, &user) if ferr != nil { diff --git a/public/Sortable-1.4.0/.editorconfig b/public/Sortable-1.4.0/.editorconfig new file mode 100644 index 00000000..b0d7fd91 --- /dev/null +++ b/public/Sortable-1.4.0/.editorconfig @@ -0,0 +1,12 @@ +# editorconfig.org +root = true + +[*] +indent_style = tab +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false diff --git a/public/Sortable-1.4.0/.gitignore b/public/Sortable-1.4.0/.gitignore new file mode 100644 index 00000000..434cfa9b --- /dev/null +++ b/public/Sortable-1.4.0/.gitignore @@ -0,0 +1,5 @@ +node_modules +mock.png +.*.sw* +.build* +jquery.fn.* diff --git a/public/Sortable-1.4.0/.jshintrc b/public/Sortable-1.4.0/.jshintrc new file mode 100644 index 00000000..3f67a098 --- /dev/null +++ b/public/Sortable-1.4.0/.jshintrc @@ -0,0 +1,24 @@ +{ + "strict": true, + "newcap": false, + "node": true, + "expr": true, + "supernew": true, + "laxbreak": true, + "white": true, + "globals": { + "define": true, + "test": true, + "expect": true, + "module": true, + "asyncTest": true, + "start": true, + "ok": true, + "equal": true, + "notEqual": true, + "deepEqual": true, + "window": true, + "document": true, + "performance": true + } +} diff --git a/public/Sortable-1.4.0/CONTRIBUTING.md b/public/Sortable-1.4.0/CONTRIBUTING.md new file mode 100644 index 00000000..34ff6cdc --- /dev/null +++ b/public/Sortable-1.4.0/CONTRIBUTING.md @@ -0,0 +1,23 @@ +# Contribution Guidelines + +### Issue + + 1. Try [dev](https://github.com/RubaXa/Sortable/tree/dev/)-branch, perhaps the problem has been solved; + 2. [Use the search](https://github.com/RubaXa/Sortable/search?q=problem), maybe already have an answer; + 3. If not found, create example on [jsbin.com (draft)](http://jsbin.com/zunibaxada/1/edit?html,js,output) and describe the problem. + +--- + +### Pull Request + + 1. Before PR run `grunt`; + 2. Only into [dev](https://github.com/RubaXa/Sortable/tree/dev/)-branch. + +### Setup + + Pieced together from [gruntjs](http://gruntjs.com/getting-started) + + 1. Fork repo on [github](https://github.com) + 2. Clone locally + 3. from local repro ```npm install``` + 4. Install grunt-cli globally ```sudo -H npm install -g grunt-cli``` diff --git a/public/Sortable-1.4.0/README.md b/public/Sortable-1.4.0/README.md new file mode 100644 index 00000000..41d4751c --- /dev/null +++ b/public/Sortable-1.4.0/README.md @@ -0,0 +1,713 @@ +# Sortable +Sortable is a minimalist JavaScript library for reorderable drag-and-drop lists. + +Demo: http://rubaxa.github.io/Sortable/ + + +## Features + + * Supports touch devices and [modern](http://caniuse.com/#search=drag) browsers (including IE9) + * Can drag from one list to another or within the same list + * CSS animation when moving items + * Supports drag handles *and selectable text* (better than voidberg's html5sortable) + * Smart auto-scrolling + * Built using native HTML5 drag and drop API + * Supports [Meteor](meteor/README.md), [AngularJS](#ng), [React](#react) and [Polymer](#polymer) + * Supports any CSS library, e.g. [Bootstrap](#bs) + * Simple API + * [CDN](#cdn) + * No jQuery (but there is [support](#jq)) + + +
+ + +### Articles + * [Sortable v1.0 — New capabilities](https://github.com/RubaXa/Sortable/wiki/Sortable-v1.0-—-New-capabilities/) (December 22, 2014) + * [Sorting with the help of HTML5 Drag'n'Drop API](https://github.com/RubaXa/Sortable/wiki/Sorting-with-the-help-of-HTML5-Drag'n'Drop-API/) (December 23, 2013) + + +
+ + +### Usage +```html + +``` + +```js +var el = document.getElementById('items'); +var sortable = Sortable.create(el); +``` + +You can use any element for the list and its elements, not just `ul`/`li`. Here is an [example with `div`s](http://jsbin.com/luxero/2/edit?html,js,output). + + +--- + + +### Options +```js +var sortable = new Sortable(el, { + group: "name", // or { name: "...", pull: [true, false, clone], put: [true, false, array] } + sort: true, // sorting inside list + delay: 0, // time in milliseconds to define when the sorting should start + disabled: false, // Disables the sortable if set to true. + store: null, // @see Store + animation: 150, // ms, animation speed moving items when sorting, `0` — without animation + handle: ".my-handle", // Drag handle selector within list items + filter: ".ignore-elements", // Selectors that do not lead to dragging (String or Function) + draggable: ".item", // Specifies which items inside the element should be sortable + ghostClass: "sortable-ghost", // Class name for the drop placeholder + chosenClass: "sortable-chosen", // Class name for the chosen item + dataIdAttr: 'data-id', + + forceFallback: false, // ignore the HTML5 DnD behaviour and force the fallback to kick in + fallbackClass: "sortable-fallback" // Class name for the cloned DOM Element when using forceFallback + fallbackOnBody: false // Appends the cloned DOM Element into the Document's Body + + scroll: true, // or HTMLElement + scrollSensitivity: 30, // px, how near the mouse must be to an edge to start scrolling. + scrollSpeed: 10, // px + + setData: function (dataTransfer, dragEl) { + dataTransfer.setData('Text', dragEl.textContent); + }, + + // dragging started + onStart: function (/**Event*/evt) { + evt.oldIndex; // element index within parent + }, + + // dragging ended + onEnd: function (/**Event*/evt) { + evt.oldIndex; // element's old index within parent + evt.newIndex; // element's new index within parent + }, + + // Element is dropped into the list from another list + onAdd: function (/**Event*/evt) { + var itemEl = evt.item; // dragged HTMLElement + evt.from; // previous list + // + indexes from onEnd + }, + + // Changed sorting within list + onUpdate: function (/**Event*/evt) { + var itemEl = evt.item; // dragged HTMLElement + // + indexes from onEnd + }, + + // Called by any change to the list (add / update / remove) + onSort: function (/**Event*/evt) { + // same properties as onUpdate + }, + + // Element is removed from the list into another list + onRemove: function (/**Event*/evt) { + // same properties as onUpdate + }, + + // Attempt to drag a filtered element + onFilter: function (/**Event*/evt) { + var itemEl = evt.item; // HTMLElement receiving the `mousedown|tapstart` event. + }, + + // Event when you move an item in the list or between lists + onMove: function (/**Event*/evt) { + // Example: http://jsbin.com/tuyafe/1/edit?js,output + evt.dragged; // dragged HTMLElement + evt.draggedRect; // TextRectangle {left, top, right и bottom} + evt.related; // HTMLElement on which have guided + evt.relatedRect; // TextRectangle + // return false; — for cancel + } +}); +``` + + +--- + + +#### `group` option +To drag elements from one list into another, both lists must have the same `group` value. +You can also define whether lists can give away, give and keep a copy (`clone`), and receive elements. + + * name: `String` — group name + * pull: `true|false|'clone'` — ability to move from the list. `clone` — copy the item, rather than move. + * put: `true|false|["foo", "bar"]` — whether elements can be added from other lists, or an array of group names from which elements can be taken. Demo: http://jsbin.com/naduvo/2/edit?html,js,output + + +--- + + +#### `sort` option +Sorting inside list. + +Demo: http://jsbin.com/xizeh/2/edit?html,js,output + + +--- + + +#### `delay` option +Time in milliseconds to define when the sorting should start. + +Demo: http://jsbin.com/xizeh/4/edit?html,js,output + + +--- + + +#### `disabled` options +Disables the sortable if set to `true`. + +Demo: http://jsbin.com/xiloqu/1/edit?html,js,output + +```js +var sortable = Sortable.create(list); + +document.getElementById("switcher").onclick = function () { + var state = sortable.option("disabled"); // get + + sortable.option("disabled", !state); // set +}; +``` + + +--- + + +#### `handle` option +To make list items draggable, Sortable disables text selection by the user. +That's not always desirable. To allow text selection, define a drag handler, +which is an area of every list element that allows it to be dragged around. + +Demo: http://jsbin.com/newize/1/edit?html,js,output + +```js +Sortable.create(el, { + handle: ".my-handle" +}); +``` + +```html + +``` + +```css +.my-handle { + cursor: move; + cursor: -webkit-grabbing; +} +``` + + +--- + + +#### `filter` option + + +```js +Sortable.create(list, { + filter: ".js-remove, .js-edit", + onFilter: function (evt) { + var item = evt.item, + ctrl = evt.target; + + if (Sortable.utils.is(ctrl, ".js-remove")) { // Click on remove button + item.parentNode.removeChild(item); // remove sortable item + } + else if (Sortable.utils.is(ctrl, ".js-edit")) { // Click on edit link + // ... + } + } +}) +``` + + +--- + + +#### `ghostClass` option +Class name for the drop placeholder (default `sortable-ghost`). + +Demo: http://jsbin.com/hunifu/1/edit?css,js,output + +```css +.ghost { + opacity: 0.4; +} +``` + +```js +Sortable.create(list, { + ghostClass: "ghost" +}); +``` + + +--- + + +#### `chosenClass` option +Class name for the chosen item (default `sortable-chosen`). + +Demo: http://jsbin.com/hunifu/edit?html,css,js,output + +```css +.chosen { + color: #fff; + background-color: #c00; +} +``` + +```js +Sortable.create(list, { + delay: 500, + chosenClass: "chosen" +}); +``` + + +--- + + +#### `forceFallback` option +If set to `true`, the Fallback for non HTML5 Browser will be used, even if we are using an HTML5 Browser. +This gives us the possiblity to test the behaviour for older Browsers even in newer Browser, or make the Drag 'n Drop feel more consistent between Desktop , Mobile and old Browsers. + +On top of that, the Fallback always generates a copy of that DOM Element and appends the class `fallbackClass` definied in the options. This behaviour controls the look of this 'dragged' Element. + +Demo: http://jsbin.com/pucurizace/edit?html,css,js,output + + +--- + + +#### `scroll` option +If set to `true`, the page (or sortable-area) scrolls when coming to an edge. + +Demo: + - `window`: http://jsbin.com/boqugumiqi/1/edit?html,js,output + - `overflow: hidden`: http://jsbin.com/kohamakiwi/1/edit?html,js,output + + +--- + + +#### `scrollSensitivity` option +Defines how near the mouse must be to an edge to start scrolling. + + +--- + + +#### `scrollSpeed` option +The speed at which the window should scroll once the mouse pointer gets within the `scrollSensitivity` distance. + + +--- + + + +### Support AngularJS +Include [ng-sortable.js](ng-sortable.js) + +Demo: http://jsbin.com/naduvo/1/edit?html,js,output + +```html +
+ + + + + +
+``` + + +```js +angular.module('myApp', ['ng-sortable']) + .controller('demo', ['$scope', function ($scope) { + $scope.items = ['item 1', 'item 2']; + $scope.foo = ['foo 1', '..']; + $scope.bar = ['bar 1', '..']; + $scope.barConfig = { + group: 'foobar', + animation: 150, + onSort: function (/** ngSortEvent */evt){ + // @see https://github.com/RubaXa/Sortable/blob/master/ng-sortable.js#L18-L24 + } + }; + }]); +``` + + +--- + + + +### Support React +Include [react-sortable-mixin.js](react-sortable-mixin.js). +See [more options](react-sortable-mixin.js#L26). + + +```jsx +var SortableList = React.createClass({ + mixins: [SortableMixin], + + getInitialState: function() { + return { + items: ['Mixin', 'Sortable'] + }; + }, + + handleSort: function (/** Event */evt) { /*..*/ }, + + render: function() { + return + } +}); + +React.render(, document.body); + + +// +// Groups +// +var AllUsers = React.createClass({ + mixins: [SortableMixin], + + sortableOptions: { + ref: "user", + group: "shared", + model: "users" + }, + + getInitialState: function() { + return { users: ['Abbi', 'Adela', 'Bud', 'Cate', 'Davis', 'Eric']; }; + }, + + render: function() { + return ( +

Users

+ + ); + } +}); + +var ApprovedUsers = React.createClass({ + mixins: [SortableMixin], + sortableOptions: { group: "shared" }, + + getInitialState: function() { + return { items: ['Hal', 'Judy']; }; + }, + + render: function() { + return + } +}); + +React.render(
+ +
+ +
, document.body); +``` + + +--- + + + +### Support KnockoutJS +Include [knockout-sortable.js](knockout-sortable.js) + +```html +
+ +
+ +
+ +
+``` + +Using this bindingHandler sorts the observableArray when the user sorts the HTMLElements. + +The sortable/draggable bindingHandlers supports the same syntax as Knockouts built in [template](http://knockoutjs.com/documentation/template-binding.html) binding except for the `data` option, meaning that you could supply the name of a template or specify a separate templateEngine. The difference between the sortable and draggable handlers is that the draggable has the sortable `group` option set to `{pull:'clone',put: false}` and the `sort` option set to false by default (overridable). + +Other attributes are: +* options: an object that contains settings for the underlaying sortable, ie `group`,`handle`, events etc. +* collection: if your `foreach` array is a computed then you would supply the underlaying observableArray that you would like to sort here. + + +--- + + +### Support Polymer +```html + + + + + + +``` + +### Method + + +##### option(name:`String`[, value:`*`]):`*` +Get or set the option. + + + +##### closest(el:`String`[, selector:`HTMLElement`]):`HTMLElement|null` +For each element in the set, get the first element that matches the selector by testing the element itself and traversing up through its ancestors in the DOM tree. + + +##### toArray():`String[]` +Serializes the sortable's item `data-id`'s (`dataIdAttr` option) into an array of string. + + +##### sort(order:`String[]`) +Sorts the elements according to the array. + +```js +var order = sortable.toArray(); +sortable.sort(order.reverse()); // apply +``` + + +##### save() +Save the current sorting (see [store](#store)) + + +##### destroy() +Removes the sortable functionality completely. + + +--- + + + +### Store +Saving and restoring of the sort. + +```html +
    +
  • order
  • +
  • save
  • +
  • restore
  • +
+``` + +```js +Sortable.create(el, { + group: "localStorage-example", + store: { + /** + * Get the order of elements. Called once during initialization. + * @param {Sortable} sortable + * @returns {Array} + */ + get: function (sortable) { + var order = localStorage.getItem(sortable.options.group); + return order ? order.split('|') : []; + }, + + /** + * Save the order of elements. Called onEnd (when the item is dropped). + * @param {Sortable} sortable + */ + set: function (sortable) { + var order = sortable.toArray(); + localStorage.setItem(sortable.options.group, order.join('|')); + } + } +}) +``` + + +--- + + + +### Bootstrap +Demo: http://jsbin.com/luxero/2/edit?html,js,output + +```html + + + + + + + + + +
    +
  • This is Sortable
  • +
  • It works with Bootstrap...
  • +
  • ...out of the box.
  • +
  • It has support for touch devices.
  • +
  • Just drag some elements around.
  • +
+ + +``` + + +--- + + +### Static methods & properties + + + +##### Sortable.create(el:`HTMLElement`[, options:`Object`]):`Sortable` +Create new instance. + + +--- + + +##### Sortable.active:`Sortable` +Link to the active instance. + + +--- + + +##### Sortable.utils +* on(el`:HTMLElement`, event`:String`, fn`:Function`) — attach an event handler function +* off(el`:HTMLElement`, event`:String`, fn`:Function`) — remove an event handler +* css(el`:HTMLElement`)`:Object` — get the values of all the CSS properties +* css(el`:HTMLElement`, prop`:String`)`:Mixed` — get the value of style properties +* css(el`:HTMLElement`, prop`:String`, value`:String`) — set one CSS properties +* css(el`:HTMLElement`, props`:Object`) — set more CSS properties +* find(ctx`:HTMLElement`, tagName`:String`[, iterator`:Function`])`:Array` — get elements by tag name +* bind(ctx`:Mixed`, fn`:Function`)`:Function` — Takes a function and returns a new one that will always have a particular context +* is(el`:HTMLElement`, selector`:String`)`:Boolean` — check the current matched set of elements against a selector +* closest(el`:HTMLElement`, selector`:String`[, ctx`:HTMLElement`])`:HTMLElement|Null` — for each element in the set, get the first element that matches the selector by testing the element itself and traversing up through its ancestors in the DOM tree +* toggleClass(el`:HTMLElement`, name`:String`, state`:Boolean`) — add or remove one classes from each element + + +--- + + + +### CDN + +```html + + + + + + + + + + +``` + + +--- + + + +### jQuery compatibility +To assemble plugin for jQuery, perform the following steps: + +```bash + cd Sortable + npm install + grunt jquery +``` + +Now you can use `jquery.fn.sortable.js`:
+(or `jquery.fn.sortable.min.js` if you run `grunt jquery:min`) + +```js + $("#list").sortable({ /* options */ }); // init + + $("#list").sortable("widget"); // get Sortable instance + + $("#list").sortable("destroy"); // destroy Sortable instance + + $("#list").sortable("{method-name}"); // call an instance method + + $("#list").sortable("{method-name}", "foo", "bar"); // call an instance method with parameters +``` + +And `grunt jquery:mySortableFunc` → `jquery.fn.mySortableFunc.js` + +--- + + +### Contributing (Issue/PR) + +Please, [read this](CONTRIBUTING.md). + + +--- + + +## MIT LICENSE +Copyright 2013-2015 Lebedev Konstantin +http://rubaxa.github.io/Sortable/ + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/public/Sortable-1.4.0/Sortable.js b/public/Sortable-1.4.0/Sortable.js new file mode 100644 index 00000000..d767780f --- /dev/null +++ b/public/Sortable-1.4.0/Sortable.js @@ -0,0 +1,1249 @@ +/**! + * Sortable + * @author RubaXa + * @license MIT + */ + + +(function (factory) { + "use strict"; + + if (typeof define === "function" && define.amd) { + define(factory); + } + else if (typeof module != "undefined" && typeof module.exports != "undefined") { + module.exports = factory(); + } + else if (typeof Package !== "undefined") { + Sortable = factory(); // export for Meteor.js + } + else { + /* jshint sub:true */ + window["Sortable"] = factory(); + } +})(function () { + "use strict"; + + var dragEl, + parentEl, + ghostEl, + cloneEl, + rootEl, + nextEl, + + scrollEl, + scrollParentEl, + + lastEl, + lastCSS, + lastParentCSS, + + oldIndex, + newIndex, + + activeGroup, + autoScroll = {}, + + tapEvt, + touchEvt, + + moved, + + /** @const */ + RSPACE = /\s+/g, + + expando = 'Sortable' + (new Date).getTime(), + + win = window, + document = win.document, + parseInt = win.parseInt, + + supportDraggable = !!('draggable' in document.createElement('div')), + supportCssPointerEvents = (function (el) { + el = document.createElement('x'); + el.style.cssText = 'pointer-events:auto'; + return el.style.pointerEvents === 'auto'; + })(), + + _silent = false, + + abs = Math.abs, + slice = [].slice, + + touchDragOverListeners = [], + + _autoScroll = _throttle(function (/**Event*/evt, /**Object*/options, /**HTMLElement*/rootEl) { + // Bug: https://bugzilla.mozilla.org/show_bug.cgi?id=505521 + if (rootEl && options.scroll) { + var el, + rect, + sens = options.scrollSensitivity, + speed = options.scrollSpeed, + + x = evt.clientX, + y = evt.clientY, + + winWidth = window.innerWidth, + winHeight = window.innerHeight, + + vx, + vy + ; + + // Delect scrollEl + if (scrollParentEl !== rootEl) { + scrollEl = options.scroll; + scrollParentEl = rootEl; + + if (scrollEl === true) { + scrollEl = rootEl; + + do { + if ((scrollEl.offsetWidth < scrollEl.scrollWidth) || + (scrollEl.offsetHeight < scrollEl.scrollHeight) + ) { + break; + } + /* jshint boss:true */ + } while (scrollEl = scrollEl.parentNode); + } + } + + if (scrollEl) { + el = scrollEl; + rect = scrollEl.getBoundingClientRect(); + vx = (abs(rect.right - x) <= sens) - (abs(rect.left - x) <= sens); + vy = (abs(rect.bottom - y) <= sens) - (abs(rect.top - y) <= sens); + } + + + if (!(vx || vy)) { + vx = (winWidth - x <= sens) - (x <= sens); + vy = (winHeight - y <= sens) - (y <= sens); + + /* jshint expr:true */ + (vx || vy) && (el = win); + } + + + if (autoScroll.vx !== vx || autoScroll.vy !== vy || autoScroll.el !== el) { + autoScroll.el = el; + autoScroll.vx = vx; + autoScroll.vy = vy; + + clearInterval(autoScroll.pid); + + if (el) { + autoScroll.pid = setInterval(function () { + if (el === win) { + win.scrollTo(win.pageXOffset + vx * speed, win.pageYOffset + vy * speed); + } else { + vy && (el.scrollTop += vy * speed); + vx && (el.scrollLeft += vx * speed); + } + }, 24); + } + } + } + }, 30), + + _prepareGroup = function (options) { + var group = options.group; + + if (!group || typeof group != 'object') { + group = options.group = {name: group}; + } + + ['pull', 'put'].forEach(function (key) { + if (!(key in group)) { + group[key] = true; + } + }); + + options.groups = ' ' + group.name + (group.put.join ? ' ' + group.put.join(' ') : '') + ' '; + } + ; + + + + /** + * @class Sortable + * @param {HTMLElement} el + * @param {Object} [options] + */ + function Sortable(el, options) { + if (!(el && el.nodeType && el.nodeType === 1)) { + throw 'Sortable: `el` must be HTMLElement, and not ' + {}.toString.call(el); + } + + this.el = el; // root element + this.options = options = _extend({}, options); + + + // Export instance + el[expando] = this; + + + // Default options + var defaults = { + group: Math.random(), + sort: true, + disabled: false, + store: null, + handle: null, + scroll: true, + scrollSensitivity: 30, + scrollSpeed: 10, + draggable: /[uo]l/i.test(el.nodeName) ? 'li' : '>*', + ghostClass: 'sortable-ghost', + chosenClass: 'sortable-chosen', + ignore: 'a, img', + filter: null, + animation: 0, + setData: function (dataTransfer, dragEl) { + dataTransfer.setData('Text', dragEl.textContent); + }, + dropBubble: false, + dragoverBubble: false, + dataIdAttr: 'data-id', + delay: 0, + forceFallback: false, + fallbackClass: 'sortable-fallback', + fallbackOnBody: false + }; + + + // Set default options + for (var name in defaults) { + !(name in options) && (options[name] = defaults[name]); + } + + _prepareGroup(options); + + // Bind all private methods + for (var fn in this) { + if (fn.charAt(0) === '_') { + this[fn] = this[fn].bind(this); + } + } + + // Setup drag mode + this.nativeDraggable = options.forceFallback ? false : supportDraggable; + + // Bind events + _on(el, 'mousedown', this._onTapStart); + _on(el, 'touchstart', this._onTapStart); + + if (this.nativeDraggable) { + _on(el, 'dragover', this); + _on(el, 'dragenter', this); + } + + touchDragOverListeners.push(this._onDragOver); + + // Restore sorting + options.store && this.sort(options.store.get(this)); + } + + + Sortable.prototype = /** @lends Sortable.prototype */ { + constructor: Sortable, + + _onTapStart: function (/** Event|TouchEvent */evt) { + var _this = this, + el = this.el, + options = this.options, + type = evt.type, + touch = evt.touches && evt.touches[0], + target = (touch || evt).target, + originalTarget = target, + filter = options.filter; + + + if (type === 'mousedown' && evt.button !== 0 || options.disabled) { + return; // only left button or enabled + } + + target = _closest(target, options.draggable, el); + + if (!target) { + return; + } + + // get the index of the dragged element within its parent + oldIndex = _index(target); + + // Check filter + if (typeof filter === 'function') { + if (filter.call(this, evt, target, this)) { + _dispatchEvent(_this, originalTarget, 'filter', target, el, oldIndex); + evt.preventDefault(); + return; // cancel dnd + } + } + else if (filter) { + filter = filter.split(',').some(function (criteria) { + criteria = _closest(originalTarget, criteria.trim(), el); + + if (criteria) { + _dispatchEvent(_this, criteria, 'filter', target, el, oldIndex); + return true; + } + }); + + if (filter) { + evt.preventDefault(); + return; // cancel dnd + } + } + + + if (options.handle && !_closest(originalTarget, options.handle, el)) { + return; + } + + + // Prepare `dragstart` + this._prepareDragStart(evt, touch, target); + }, + + _prepareDragStart: function (/** Event */evt, /** Touch */touch, /** HTMLElement */target) { + var _this = this, + el = _this.el, + options = _this.options, + ownerDocument = el.ownerDocument, + dragStartFn; + + if (target && !dragEl && (target.parentNode === el)) { + tapEvt = evt; + + rootEl = el; + dragEl = target; + parentEl = dragEl.parentNode; + nextEl = dragEl.nextSibling; + activeGroup = options.group; + + dragStartFn = function () { + // Delayed drag has been triggered + // we can re-enable the events: touchmove/mousemove + _this._disableDelayedDrag(); + + // Make the element draggable + dragEl.draggable = true; + + // Chosen item + _toggleClass(dragEl, _this.options.chosenClass, true); + + // Bind the events: dragstart/dragend + _this._triggerDragStart(touch); + }; + + // Disable "draggable" + options.ignore.split(',').forEach(function (criteria) { + _find(dragEl, criteria.trim(), _disableDraggable); + }); + + _on(ownerDocument, 'mouseup', _this._onDrop); + _on(ownerDocument, 'touchend', _this._onDrop); + _on(ownerDocument, 'touchcancel', _this._onDrop); + + if (options.delay) { + // If the user moves the pointer or let go the click or touch + // before the delay has been reached: + // disable the delayed drag + _on(ownerDocument, 'mouseup', _this._disableDelayedDrag); + _on(ownerDocument, 'touchend', _this._disableDelayedDrag); + _on(ownerDocument, 'touchcancel', _this._disableDelayedDrag); + _on(ownerDocument, 'mousemove', _this._disableDelayedDrag); + _on(ownerDocument, 'touchmove', _this._disableDelayedDrag); + + _this._dragStartTimer = setTimeout(dragStartFn, options.delay); + } else { + dragStartFn(); + } + } + }, + + _disableDelayedDrag: function () { + var ownerDocument = this.el.ownerDocument; + + clearTimeout(this._dragStartTimer); + _off(ownerDocument, 'mouseup', this._disableDelayedDrag); + _off(ownerDocument, 'touchend', this._disableDelayedDrag); + _off(ownerDocument, 'touchcancel', this._disableDelayedDrag); + _off(ownerDocument, 'mousemove', this._disableDelayedDrag); + _off(ownerDocument, 'touchmove', this._disableDelayedDrag); + }, + + _triggerDragStart: function (/** Touch */touch) { + if (touch) { + // Touch device support + tapEvt = { + target: dragEl, + clientX: touch.clientX, + clientY: touch.clientY + }; + + this._onDragStart(tapEvt, 'touch'); + } + else if (!this.nativeDraggable) { + this._onDragStart(tapEvt, true); + } + else { + _on(dragEl, 'dragend', this); + _on(rootEl, 'dragstart', this._onDragStart); + } + + try { + if (document.selection) { + document.selection.empty(); + } else { + window.getSelection().removeAllRanges(); + } + } catch (err) { + } + }, + + _dragStarted: function () { + if (rootEl && dragEl) { + // Apply effect + _toggleClass(dragEl, this.options.ghostClass, true); + + Sortable.active = this; + + // Drag start event + _dispatchEvent(this, rootEl, 'start', dragEl, rootEl, oldIndex); + } + }, + + _emulateDragOver: function () { + if (touchEvt) { + if (this._lastX === touchEvt.clientX && this._lastY === touchEvt.clientY) { + return; + } + + this._lastX = touchEvt.clientX; + this._lastY = touchEvt.clientY; + + if (!supportCssPointerEvents) { + _css(ghostEl, 'display', 'none'); + } + + var target = document.elementFromPoint(touchEvt.clientX, touchEvt.clientY), + parent = target, + groupName = ' ' + this.options.group.name + '', + i = touchDragOverListeners.length; + + if (parent) { + do { + if (parent[expando] && parent[expando].options.groups.indexOf(groupName) > -1) { + while (i--) { + touchDragOverListeners[i]({ + clientX: touchEvt.clientX, + clientY: touchEvt.clientY, + target: target, + rootEl: parent + }); + } + + break; + } + + target = parent; // store last element + } + /* jshint boss:true */ + while (parent = parent.parentNode); + } + + if (!supportCssPointerEvents) { + _css(ghostEl, 'display', ''); + } + } + }, + + + _onTouchMove: function (/**TouchEvent*/evt) { + if (tapEvt) { + // only set the status to dragging, when we are actually dragging + if (!Sortable.active) { + this._dragStarted(); + } + + // as well as creating the ghost element on the document body + this._appendGhost(); + + var touch = evt.touches ? evt.touches[0] : evt, + dx = touch.clientX - tapEvt.clientX, + dy = touch.clientY - tapEvt.clientY, + translate3d = evt.touches ? 'translate3d(' + dx + 'px,' + dy + 'px,0)' : 'translate(' + dx + 'px,' + dy + 'px)'; + + moved = true; + touchEvt = touch; + + _css(ghostEl, 'webkitTransform', translate3d); + _css(ghostEl, 'mozTransform', translate3d); + _css(ghostEl, 'msTransform', translate3d); + _css(ghostEl, 'transform', translate3d); + + evt.preventDefault(); + } + }, + + _appendGhost: function () { + if (!ghostEl) { + var rect = dragEl.getBoundingClientRect(), + css = _css(dragEl), + options = this.options, + ghostRect; + + ghostEl = dragEl.cloneNode(true); + + _toggleClass(ghostEl, options.ghostClass, false); + _toggleClass(ghostEl, options.fallbackClass, true); + + _css(ghostEl, 'top', rect.top - parseInt(css.marginTop, 10)); + _css(ghostEl, 'left', rect.left - parseInt(css.marginLeft, 10)); + _css(ghostEl, 'width', rect.width); + _css(ghostEl, 'height', rect.height); + _css(ghostEl, 'opacity', '0.8'); + _css(ghostEl, 'position', 'fixed'); + _css(ghostEl, 'zIndex', '100000'); + _css(ghostEl, 'pointerEvents', 'none'); + + options.fallbackOnBody && document.body.appendChild(ghostEl) || rootEl.appendChild(ghostEl); + + // Fixing dimensions. + ghostRect = ghostEl.getBoundingClientRect(); + _css(ghostEl, 'width', rect.width * 2 - ghostRect.width); + _css(ghostEl, 'height', rect.height * 2 - ghostRect.height); + } + }, + + _onDragStart: function (/**Event*/evt, /**boolean*/useFallback) { + var dataTransfer = evt.dataTransfer, + options = this.options; + + this._offUpEvents(); + + if (activeGroup.pull == 'clone') { + cloneEl = dragEl.cloneNode(true); + _css(cloneEl, 'display', 'none'); + rootEl.insertBefore(cloneEl, dragEl); + } + + if (useFallback) { + + if (useFallback === 'touch') { + // Bind touch events + _on(document, 'touchmove', this._onTouchMove); + _on(document, 'touchend', this._onDrop); + _on(document, 'touchcancel', this._onDrop); + } else { + // Old brwoser + _on(document, 'mousemove', this._onTouchMove); + _on(document, 'mouseup', this._onDrop); + } + + this._loopId = setInterval(this._emulateDragOver, 50); + } + else { + if (dataTransfer) { + dataTransfer.effectAllowed = 'move'; + options.setData && options.setData.call(this, dataTransfer, dragEl); + } + + _on(document, 'drop', this); + setTimeout(this._dragStarted, 0); + } + }, + + _onDragOver: function (/**Event*/evt) { + var el = this.el, + target, + dragRect, + revert, + options = this.options, + group = options.group, + groupPut = group.put, + isOwner = (activeGroup === group), + canSort = options.sort; + + if (evt.preventDefault !== void 0) { + evt.preventDefault(); + !options.dragoverBubble && evt.stopPropagation(); + } + + moved = true; + + if (activeGroup && !options.disabled && + (isOwner + ? canSort || (revert = !rootEl.contains(dragEl)) // Reverting item into the original list + : activeGroup.pull && groupPut && ( + (activeGroup.name === group.name) || // by Name + (groupPut.indexOf && ~groupPut.indexOf(activeGroup.name)) // by Array + ) + ) && + (evt.rootEl === void 0 || evt.rootEl === this.el) // touch fallback + ) { + // Smart auto-scrolling + _autoScroll(evt, options, this.el); + + if (_silent) { + return; + } + + target = _closest(evt.target, options.draggable, el); + dragRect = dragEl.getBoundingClientRect(); + + if (revert) { + _cloneHide(true); + + if (cloneEl || nextEl) { + rootEl.insertBefore(dragEl, cloneEl || nextEl); + } + else if (!canSort) { + rootEl.appendChild(dragEl); + } + + return; + } + + + if ((el.children.length === 0) || (el.children[0] === ghostEl) || + (el === evt.target) && (target = _ghostIsLast(el, evt)) + ) { + + if (target) { + if (target.animated) { + return; + } + + targetRect = target.getBoundingClientRect(); + } + + _cloneHide(isOwner); + + if (_onMove(rootEl, el, dragEl, dragRect, target, targetRect) !== false) { + if (!dragEl.contains(el)) { + el.appendChild(dragEl); + parentEl = el; // actualization + } + + this._animate(dragRect, dragEl); + target && this._animate(targetRect, target); + } + } + else if (target && !target.animated && target !== dragEl && (target.parentNode[expando] !== void 0)) { + if (lastEl !== target) { + lastEl = target; + lastCSS = _css(target); + lastParentCSS = _css(target.parentNode); + } + + + var targetRect = target.getBoundingClientRect(), + width = targetRect.right - targetRect.left, + height = targetRect.bottom - targetRect.top, + floating = /left|right|inline/.test(lastCSS.cssFloat + lastCSS.display) + || (lastParentCSS.display == 'flex' && lastParentCSS['flex-direction'].indexOf('row') === 0), + isWide = (target.offsetWidth > dragEl.offsetWidth), + isLong = (target.offsetHeight > dragEl.offsetHeight), + halfway = (floating ? (evt.clientX - targetRect.left) / width : (evt.clientY - targetRect.top) / height) > 0.5, + nextSibling = target.nextElementSibling, + moveVector = _onMove(rootEl, el, dragEl, dragRect, target, targetRect), + after + ; + + if (moveVector !== false) { + _silent = true; + setTimeout(_unsilent, 30); + + _cloneHide(isOwner); + + if (moveVector === 1 || moveVector === -1) { + after = (moveVector === 1); + } + else if (floating) { + var elTop = dragEl.offsetTop, + tgTop = target.offsetTop; + + if (elTop === tgTop) { + after = (target.previousElementSibling === dragEl) && !isWide || halfway && isWide; + } else { + after = tgTop > elTop; + } + } else { + after = (nextSibling !== dragEl) && !isLong || halfway && isLong; + } + + if (!dragEl.contains(el)) { + if (after && !nextSibling) { + el.appendChild(dragEl); + } else { + target.parentNode.insertBefore(dragEl, after ? nextSibling : target); + } + } + + parentEl = dragEl.parentNode; // actualization + + this._animate(dragRect, dragEl); + this._animate(targetRect, target); + } + } + } + }, + + _animate: function (prevRect, target) { + var ms = this.options.animation; + + if (ms) { + var currentRect = target.getBoundingClientRect(); + + _css(target, 'transition', 'none'); + _css(target, 'transform', 'translate3d(' + + (prevRect.left - currentRect.left) + 'px,' + + (prevRect.top - currentRect.top) + 'px,0)' + ); + + target.offsetWidth; // repaint + + _css(target, 'transition', 'all ' + ms + 'ms'); + _css(target, 'transform', 'translate3d(0,0,0)'); + + clearTimeout(target.animated); + target.animated = setTimeout(function () { + _css(target, 'transition', ''); + _css(target, 'transform', ''); + target.animated = false; + }, ms); + } + }, + + _offUpEvents: function () { + var ownerDocument = this.el.ownerDocument; + + _off(document, 'touchmove', this._onTouchMove); + _off(ownerDocument, 'mouseup', this._onDrop); + _off(ownerDocument, 'touchend', this._onDrop); + _off(ownerDocument, 'touchcancel', this._onDrop); + }, + + _onDrop: function (/**Event*/evt) { + var el = this.el, + options = this.options; + + clearInterval(this._loopId); + clearInterval(autoScroll.pid); + clearTimeout(this._dragStartTimer); + + // Unbind events + _off(document, 'mousemove', this._onTouchMove); + + if (this.nativeDraggable) { + _off(document, 'drop', this); + _off(el, 'dragstart', this._onDragStart); + } + + this._offUpEvents(); + + if (evt) { + if (moved) { + evt.preventDefault(); + !options.dropBubble && evt.stopPropagation(); + } + + ghostEl && ghostEl.parentNode.removeChild(ghostEl); + + if (dragEl) { + if (this.nativeDraggable) { + _off(dragEl, 'dragend', this); + } + + _disableDraggable(dragEl); + + // Remove class's + _toggleClass(dragEl, this.options.ghostClass, false); + _toggleClass(dragEl, this.options.chosenClass, false); + + if (rootEl !== parentEl) { + newIndex = _index(dragEl); + + if (newIndex >= 0) { + // drag from one list and drop into another + _dispatchEvent(null, parentEl, 'sort', dragEl, rootEl, oldIndex, newIndex); + _dispatchEvent(this, rootEl, 'sort', dragEl, rootEl, oldIndex, newIndex); + + // Add event + _dispatchEvent(null, parentEl, 'add', dragEl, rootEl, oldIndex, newIndex); + + // Remove event + _dispatchEvent(this, rootEl, 'remove', dragEl, rootEl, oldIndex, newIndex); + } + } + else { + // Remove clone + cloneEl && cloneEl.parentNode.removeChild(cloneEl); + + if (dragEl.nextSibling !== nextEl) { + // Get the index of the dragged element within its parent + newIndex = _index(dragEl); + + if (newIndex >= 0) { + // drag & drop within the same list + _dispatchEvent(this, rootEl, 'update', dragEl, rootEl, oldIndex, newIndex); + _dispatchEvent(this, rootEl, 'sort', dragEl, rootEl, oldIndex, newIndex); + } + } + } + + if (Sortable.active) { + if (newIndex === null || newIndex === -1) { + newIndex = oldIndex; + } + + _dispatchEvent(this, rootEl, 'end', dragEl, rootEl, oldIndex, newIndex); + + // Save sorting + this.save(); + } + } + + // Nulling + rootEl = + dragEl = + parentEl = + ghostEl = + nextEl = + cloneEl = + + scrollEl = + scrollParentEl = + + tapEvt = + touchEvt = + + moved = + newIndex = + + lastEl = + lastCSS = + + activeGroup = + Sortable.active = null; + } + }, + + + handleEvent: function (/**Event*/evt) { + var type = evt.type; + + if (type === 'dragover' || type === 'dragenter') { + if (dragEl) { + this._onDragOver(evt); + _globalDragOver(evt); + } + } + else if (type === 'drop' || type === 'dragend') { + this._onDrop(evt); + } + }, + + + /** + * Serializes the item into an array of string. + * @returns {String[]} + */ + toArray: function () { + var order = [], + el, + children = this.el.children, + i = 0, + n = children.length, + options = this.options; + + for (; i < n; i++) { + el = children[i]; + if (_closest(el, options.draggable, this.el)) { + order.push(el.getAttribute(options.dataIdAttr) || _generateId(el)); + } + } + + return order; + }, + + + /** + * Sorts the elements according to the array. + * @param {String[]} order order of the items + */ + sort: function (order) { + var items = {}, rootEl = this.el; + + this.toArray().forEach(function (id, i) { + var el = rootEl.children[i]; + + if (_closest(el, this.options.draggable, rootEl)) { + items[id] = el; + } + }, this); + + order.forEach(function (id) { + if (items[id]) { + rootEl.removeChild(items[id]); + rootEl.appendChild(items[id]); + } + }); + }, + + + /** + * Save the current sorting + */ + save: function () { + var store = this.options.store; + store && store.set(this); + }, + + + /** + * For each element in the set, get the first element that matches the selector by testing the element itself and traversing up through its ancestors in the DOM tree. + * @param {HTMLElement} el + * @param {String} [selector] default: `options.draggable` + * @returns {HTMLElement|null} + */ + closest: function (el, selector) { + return _closest(el, selector || this.options.draggable, this.el); + }, + + + /** + * Set/get option + * @param {string} name + * @param {*} [value] + * @returns {*} + */ + option: function (name, value) { + var options = this.options; + + if (value === void 0) { + return options[name]; + } else { + options[name] = value; + + if (name === 'group') { + _prepareGroup(options); + } + } + }, + + + /** + * Destroy + */ + destroy: function () { + var el = this.el; + + el[expando] = null; + + _off(el, 'mousedown', this._onTapStart); + _off(el, 'touchstart', this._onTapStart); + + if (this.nativeDraggable) { + _off(el, 'dragover', this); + _off(el, 'dragenter', this); + } + + // Remove draggable attributes + Array.prototype.forEach.call(el.querySelectorAll('[draggable]'), function (el) { + el.removeAttribute('draggable'); + }); + + touchDragOverListeners.splice(touchDragOverListeners.indexOf(this._onDragOver), 1); + + this._onDrop(); + + this.el = el = null; + } + }; + + + function _cloneHide(state) { + if (cloneEl && (cloneEl.state !== state)) { + _css(cloneEl, 'display', state ? 'none' : ''); + !state && cloneEl.state && rootEl.insertBefore(cloneEl, dragEl); + cloneEl.state = state; + } + } + + + function _closest(/**HTMLElement*/el, /**String*/selector, /**HTMLElement*/ctx) { + if (el) { + ctx = ctx || document; + selector = selector.split('.'); + + var tag = selector.shift().toUpperCase(), + re = new RegExp('\\s(' + selector.join('|') + ')(?=\\s)', 'g'); + + do { + if ( + (tag === '>*' && el.parentNode === ctx) || ( + (tag === '' || el.nodeName.toUpperCase() == tag) && + (!selector.length || ((' ' + el.className + ' ').match(re) || []).length == selector.length) + ) + ) { + return el; + } + } + while (el !== ctx && (el = el.parentNode)); + } + + return null; + } + + + function _globalDragOver(/**Event*/evt) { + if (evt.dataTransfer) { + evt.dataTransfer.dropEffect = 'move'; + } + evt.preventDefault(); + } + + + function _on(el, event, fn) { + el.addEventListener(event, fn, false); + } + + + function _off(el, event, fn) { + el.removeEventListener(event, fn, false); + } + + + function _toggleClass(el, name, state) { + if (el) { + if (el.classList) { + el.classList[state ? 'add' : 'remove'](name); + } + else { + var className = (' ' + el.className + ' ').replace(RSPACE, ' ').replace(' ' + name + ' ', ' '); + el.className = (className + (state ? ' ' + name : '')).replace(RSPACE, ' '); + } + } + } + + + function _css(el, prop, val) { + var style = el && el.style; + + if (style) { + if (val === void 0) { + if (document.defaultView && document.defaultView.getComputedStyle) { + val = document.defaultView.getComputedStyle(el, ''); + } + else if (el.currentStyle) { + val = el.currentStyle; + } + + return prop === void 0 ? val : val[prop]; + } + else { + if (!(prop in style)) { + prop = '-webkit-' + prop; + } + + style[prop] = val + (typeof val === 'string' ? '' : 'px'); + } + } + } + + + function _find(ctx, tagName, iterator) { + if (ctx) { + var list = ctx.getElementsByTagName(tagName), i = 0, n = list.length; + + if (iterator) { + for (; i < n; i++) { + iterator(list[i], i); + } + } + + return list; + } + + return []; + } + + + + function _dispatchEvent(sortable, rootEl, name, targetEl, fromEl, startIndex, newIndex) { + var evt = document.createEvent('Event'), + options = (sortable || rootEl[expando]).options, + onName = 'on' + name.charAt(0).toUpperCase() + name.substr(1); + + evt.initEvent(name, true, true); + + evt.to = rootEl; + evt.from = fromEl || rootEl; + evt.item = targetEl || rootEl; + evt.clone = cloneEl; + + evt.oldIndex = startIndex; + evt.newIndex = newIndex; + + rootEl.dispatchEvent(evt); + + if (options[onName]) { + options[onName].call(sortable, evt); + } + } + + + function _onMove(fromEl, toEl, dragEl, dragRect, targetEl, targetRect) { + var evt, + sortable = fromEl[expando], + onMoveFn = sortable.options.onMove, + retVal; + + evt = document.createEvent('Event'); + evt.initEvent('move', true, true); + + evt.to = toEl; + evt.from = fromEl; + evt.dragged = dragEl; + evt.draggedRect = dragRect; + evt.related = targetEl || toEl; + evt.relatedRect = targetRect || toEl.getBoundingClientRect(); + + fromEl.dispatchEvent(evt); + + if (onMoveFn) { + retVal = onMoveFn.call(sortable, evt); + } + + return retVal; + } + + + function _disableDraggable(el) { + el.draggable = false; + } + + + function _unsilent() { + _silent = false; + } + + + /** @returns {HTMLElement|false} */ + function _ghostIsLast(el, evt) { + var lastEl = el.lastElementChild, + rect = lastEl.getBoundingClientRect(); + + return ((evt.clientY - (rect.top + rect.height) > 5) || (evt.clientX - (rect.right + rect.width) > 5)) && lastEl; // min delta + } + + + /** + * Generate id + * @param {HTMLElement} el + * @returns {String} + * @private + */ + function _generateId(el) { + var str = el.tagName + el.className + el.src + el.href + el.textContent, + i = str.length, + sum = 0; + + while (i--) { + sum += str.charCodeAt(i); + } + + return sum.toString(36); + } + + /** + * Returns the index of an element within its parent + * @param {HTMLElement} el + * @return {number} + */ + function _index(el) { + var index = 0; + + if (!el || !el.parentNode) { + return -1; + } + + while (el && (el = el.previousElementSibling)) { + if (el.nodeName.toUpperCase() !== 'TEMPLATE') { + index++; + } + } + + return index; + } + + function _throttle(callback, ms) { + var args, _this; + + return function () { + if (args === void 0) { + args = arguments; + _this = this; + + setTimeout(function () { + if (args.length === 1) { + callback.call(_this, args[0]); + } else { + callback.apply(_this, args); + } + + args = void 0; + }, ms); + } + }; + } + + function _extend(dst, src) { + if (dst && src) { + for (var key in src) { + if (src.hasOwnProperty(key)) { + dst[key] = src[key]; + } + } + } + + return dst; + } + + + // Export utils + Sortable.utils = { + on: _on, + off: _off, + css: _css, + find: _find, + is: function (el, selector) { + return !!_closest(el, selector, el); + }, + extend: _extend, + throttle: _throttle, + closest: _closest, + toggleClass: _toggleClass, + index: _index + }; + + + /** + * Create sortable instance + * @param {HTMLElement} el + * @param {Object} [options] + */ + Sortable.create = function (el, options) { + return new Sortable(el, options); + }; + + + // Export + Sortable.version = '1.3.0'; + return Sortable; +}); diff --git a/public/Sortable-1.4.0/Sortable.min.js b/public/Sortable-1.4.0/Sortable.min.js new file mode 100644 index 00000000..bef7fa3a --- /dev/null +++ b/public/Sortable-1.4.0/Sortable.min.js @@ -0,0 +1,2 @@ +/*! Sortable 1.3.0 - MIT | git://github.com/rubaxa/Sortable.git */ +!function(a){"use strict";"function"==typeof define&&define.amd?define(a):"undefined"!=typeof module&&"undefined"!=typeof module.exports?module.exports=a():"undefined"!=typeof Package?Sortable=a():window.Sortable=a()}(function(){"use strict";function a(a,b){if(!a||!a.nodeType||1!==a.nodeType)throw"Sortable: `el` must be HTMLElement, and not "+{}.toString.call(a);this.el=a,this.options=b=r({},b),a[L]=this;var c={group:Math.random(),sort:!0,disabled:!1,store:null,handle:null,scroll:!0,scrollSensitivity:30,scrollSpeed:10,draggable:/[uo]l/i.test(a.nodeName)?"li":">*",ghostClass:"sortable-ghost",chosenClass:"sortable-chosen",ignore:"a, img",filter:null,animation:0,setData:function(a,b){a.setData("Text",b.textContent)},dropBubble:!1,dragoverBubble:!1,dataIdAttr:"data-id",delay:0,forceFallback:!1,fallbackClass:"sortable-fallback",fallbackOnBody:!1};for(var d in c)!(d in b)&&(b[d]=c[d]);V(b);for(var f in this)"_"===f.charAt(0)&&(this[f]=this[f].bind(this));this.nativeDraggable=b.forceFallback?!1:P,e(a,"mousedown",this._onTapStart),e(a,"touchstart",this._onTapStart),this.nativeDraggable&&(e(a,"dragover",this),e(a,"dragenter",this)),T.push(this._onDragOver),b.store&&this.sort(b.store.get(this))}function b(a){v&&v.state!==a&&(h(v,"display",a?"none":""),!a&&v.state&&w.insertBefore(v,s),v.state=a)}function c(a,b,c){if(a){c=c||N,b=b.split(".");var d=b.shift().toUpperCase(),e=new RegExp("\\s("+b.join("|")+")(?=\\s)","g");do if(">*"===d&&a.parentNode===c||(""===d||a.nodeName.toUpperCase()==d)&&(!b.length||((" "+a.className+" ").match(e)||[]).length==b.length))return a;while(a!==c&&(a=a.parentNode))}return null}function d(a){a.dataTransfer&&(a.dataTransfer.dropEffect="move"),a.preventDefault()}function e(a,b,c){a.addEventListener(b,c,!1)}function f(a,b,c){a.removeEventListener(b,c,!1)}function g(a,b,c){if(a)if(a.classList)a.classList[c?"add":"remove"](b);else{var d=(" "+a.className+" ").replace(K," ").replace(" "+b+" "," ");a.className=(d+(c?" "+b:"")).replace(K," ")}}function h(a,b,c){var d=a&&a.style;if(d){if(void 0===c)return N.defaultView&&N.defaultView.getComputedStyle?c=N.defaultView.getComputedStyle(a,""):a.currentStyle&&(c=a.currentStyle),void 0===b?c:c[b];b in d||(b="-webkit-"+b),d[b]=c+("string"==typeof c?"":"px")}}function i(a,b,c){if(a){var d=a.getElementsByTagName(b),e=0,f=d.length;if(c)for(;f>e;e++)c(d[e],e);return d}return[]}function j(a,b,c,d,e,f,g){var h=N.createEvent("Event"),i=(a||b[L]).options,j="on"+c.charAt(0).toUpperCase()+c.substr(1);h.initEvent(c,!0,!0),h.to=b,h.from=e||b,h.item=d||b,h.clone=v,h.oldIndex=f,h.newIndex=g,b.dispatchEvent(h),i[j]&&i[j].call(a,h)}function k(a,b,c,d,e,f){var g,h,i=a[L],j=i.options.onMove;return g=N.createEvent("Event"),g.initEvent("move",!0,!0),g.to=b,g.from=a,g.dragged=c,g.draggedRect=d,g.related=e||b,g.relatedRect=f||b.getBoundingClientRect(),a.dispatchEvent(g),j&&(h=j.call(i,g)),h}function l(a){a.draggable=!1}function m(){R=!1}function n(a,b){var c=a.lastElementChild,d=c.getBoundingClientRect();return(b.clientY-(d.top+d.height)>5||b.clientX-(d.right+d.width)>5)&&c}function o(a){for(var b=a.tagName+a.className+a.src+a.href+a.textContent,c=b.length,d=0;c--;)d+=b.charCodeAt(c);return d.toString(36)}function p(a){var b=0;if(!a||!a.parentNode)return-1;for(;a&&(a=a.previousElementSibling);)"TEMPLATE"!==a.nodeName.toUpperCase()&&b++;return b}function q(a,b){var c,d;return function(){void 0===c&&(c=arguments,d=this,setTimeout(function(){1===c.length?a.call(d,c[0]):a.apply(d,c),c=void 0},b))}}function r(a,b){if(a&&b)for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c]);return a}var s,t,u,v,w,x,y,z,A,B,C,D,E,F,G,H,I,J={},K=/\s+/g,L="Sortable"+(new Date).getTime(),M=window,N=M.document,O=M.parseInt,P=!!("draggable"in N.createElement("div")),Q=function(a){return a=N.createElement("x"),a.style.cssText="pointer-events:auto","auto"===a.style.pointerEvents}(),R=!1,S=Math.abs,T=([].slice,[]),U=q(function(a,b,c){if(c&&b.scroll){var d,e,f,g,h=b.scrollSensitivity,i=b.scrollSpeed,j=a.clientX,k=a.clientY,l=window.innerWidth,m=window.innerHeight;if(z!==c&&(y=b.scroll,z=c,y===!0)){y=c;do if(y.offsetWidth=l-j)-(h>=j),g=(h>=m-k)-(h>=k),(f||g)&&(d=M)),(J.vx!==f||J.vy!==g||J.el!==d)&&(J.el=d,J.vx=f,J.vy=g,clearInterval(J.pid),d&&(J.pid=setInterval(function(){d===M?M.scrollTo(M.pageXOffset+f*i,M.pageYOffset+g*i):(g&&(d.scrollTop+=g*i),f&&(d.scrollLeft+=f*i))},24)))}},30),V=function(a){var b=a.group;b&&"object"==typeof b||(b=a.group={name:b}),["pull","put"].forEach(function(a){a in b||(b[a]=!0)}),a.groups=" "+b.name+(b.put.join?" "+b.put.join(" "):"")+" "};return a.prototype={constructor:a,_onTapStart:function(a){var b=this,d=this.el,e=this.options,f=a.type,g=a.touches&&a.touches[0],h=(g||a).target,i=h,k=e.filter;if(!("mousedown"===f&&0!==a.button||e.disabled)&&(h=c(h,e.draggable,d))){if(D=p(h),"function"==typeof k){if(k.call(this,a,h,this))return j(b,i,"filter",h,d,D),void a.preventDefault()}else if(k&&(k=k.split(",").some(function(a){return a=c(i,a.trim(),d),a?(j(b,a,"filter",h,d,D),!0):void 0})))return void a.preventDefault();(!e.handle||c(i,e.handle,d))&&this._prepareDragStart(a,g,h)}},_prepareDragStart:function(a,b,c){var d,f=this,h=f.el,j=f.options,k=h.ownerDocument;c&&!s&&c.parentNode===h&&(G=a,w=h,s=c,t=s.parentNode,x=s.nextSibling,F=j.group,d=function(){f._disableDelayedDrag(),s.draggable=!0,g(s,f.options.chosenClass,!0),f._triggerDragStart(b)},j.ignore.split(",").forEach(function(a){i(s,a.trim(),l)}),e(k,"mouseup",f._onDrop),e(k,"touchend",f._onDrop),e(k,"touchcancel",f._onDrop),j.delay?(e(k,"mouseup",f._disableDelayedDrag),e(k,"touchend",f._disableDelayedDrag),e(k,"touchcancel",f._disableDelayedDrag),e(k,"mousemove",f._disableDelayedDrag),e(k,"touchmove",f._disableDelayedDrag),f._dragStartTimer=setTimeout(d,j.delay)):d())},_disableDelayedDrag:function(){var a=this.el.ownerDocument;clearTimeout(this._dragStartTimer),f(a,"mouseup",this._disableDelayedDrag),f(a,"touchend",this._disableDelayedDrag),f(a,"touchcancel",this._disableDelayedDrag),f(a,"mousemove",this._disableDelayedDrag),f(a,"touchmove",this._disableDelayedDrag)},_triggerDragStart:function(a){a?(G={target:s,clientX:a.clientX,clientY:a.clientY},this._onDragStart(G,"touch")):this.nativeDraggable?(e(s,"dragend",this),e(w,"dragstart",this._onDragStart)):this._onDragStart(G,!0);try{N.selection?N.selection.empty():window.getSelection().removeAllRanges()}catch(b){}},_dragStarted:function(){w&&s&&(g(s,this.options.ghostClass,!0),a.active=this,j(this,w,"start",s,w,D))},_emulateDragOver:function(){if(H){if(this._lastX===H.clientX&&this._lastY===H.clientY)return;this._lastX=H.clientX,this._lastY=H.clientY,Q||h(u,"display","none");var a=N.elementFromPoint(H.clientX,H.clientY),b=a,c=" "+this.options.group.name,d=T.length;if(b)do{if(b[L]&&b[L].options.groups.indexOf(c)>-1){for(;d--;)T[d]({clientX:H.clientX,clientY:H.clientY,target:a,rootEl:b});break}a=b}while(b=b.parentNode);Q||h(u,"display","")}},_onTouchMove:function(b){if(G){a.active||this._dragStarted(),this._appendGhost();var c=b.touches?b.touches[0]:b,d=c.clientX-G.clientX,e=c.clientY-G.clientY,f=b.touches?"translate3d("+d+"px,"+e+"px,0)":"translate("+d+"px,"+e+"px)";I=!0,H=c,h(u,"webkitTransform",f),h(u,"mozTransform",f),h(u,"msTransform",f),h(u,"transform",f),b.preventDefault()}},_appendGhost:function(){if(!u){var a,b=s.getBoundingClientRect(),c=h(s);u=s.cloneNode(!0),g(u,this.options.ghostClass,!1),g(u,this.options.fallbackClass,!0),h(u,"top",b.top-O(c.marginTop,10)),h(u,"left",b.left-O(c.marginLeft,10)),h(u,"width",b.width),h(u,"height",b.height),h(u,"opacity","0.8"),h(u,"position","fixed"),h(u,"zIndex","100000"),h(u,"pointerEvents","none"),this.options.fallbackOnBody&&N.body.appendChild(u)||w.appendChild(u),a=u.getBoundingClientRect(),h(u,"width",2*b.width-a.width),h(u,"height",2*b.height-a.height)}},_onDragStart:function(a,b){var c=a.dataTransfer,d=this.options;this._offUpEvents(),"clone"==F.pull&&(v=s.cloneNode(!0),h(v,"display","none"),w.insertBefore(v,s)),b?("touch"===b?(e(N,"touchmove",this._onTouchMove),e(N,"touchend",this._onDrop),e(N,"touchcancel",this._onDrop)):(e(N,"mousemove",this._onTouchMove),e(N,"mouseup",this._onDrop)),this._loopId=setInterval(this._emulateDragOver,50)):(c&&(c.effectAllowed="move",d.setData&&d.setData.call(this,c,s)),e(N,"drop",this),setTimeout(this._dragStarted,0))},_onDragOver:function(a){var d,e,f,g=this.el,i=this.options,j=i.group,l=j.put,o=F===j,p=i.sort;if(void 0!==a.preventDefault&&(a.preventDefault(),!i.dragoverBubble&&a.stopPropagation()),I=!0,F&&!i.disabled&&(o?p||(f=!w.contains(s)):F.pull&&l&&(F.name===j.name||l.indexOf&&~l.indexOf(F.name)))&&(void 0===a.rootEl||a.rootEl===this.el)){if(U(a,i,this.el),R)return;if(d=c(a.target,i.draggable,g),e=s.getBoundingClientRect(),f)return b(!0),void(v||x?w.insertBefore(s,v||x):p||w.appendChild(s));if(0===g.children.length||g.children[0]===u||g===a.target&&(d=n(g,a))){if(d){if(d.animated)return;r=d.getBoundingClientRect()}b(o),k(w,g,s,e,d,r)!==!1&&(s.contains(g)||(g.appendChild(s),t=g),this._animate(e,s),d&&this._animate(r,d))}else if(d&&!d.animated&&d!==s&&void 0!==d.parentNode[L]){A!==d&&(A=d,B=h(d),C=h(d.parentNode));var q,r=d.getBoundingClientRect(),y=r.right-r.left,z=r.bottom-r.top,D=/left|right|inline/.test(B.cssFloat+B.display)||"flex"==C.display&&0===C["flex-direction"].indexOf("row"),E=d.offsetWidth>s.offsetWidth,G=d.offsetHeight>s.offsetHeight,H=(D?(a.clientX-r.left)/y:(a.clientY-r.top)/z)>.5,J=d.nextElementSibling,K=k(w,g,s,e,d,r);if(K!==!1){if(R=!0,setTimeout(m,30),b(o),1===K||-1===K)q=1===K;else if(D){var M=s.offsetTop,N=d.offsetTop;q=M===N?d.previousElementSibling===s&&!E||H&&E:N>M}else q=J!==s&&!G||H&&G;s.contains(g)||(q&&!J?g.appendChild(s):d.parentNode.insertBefore(s,q?J:d)),t=s.parentNode,this._animate(e,s),this._animate(r,d)}}}},_animate:function(a,b){var c=this.options.animation;if(c){var d=b.getBoundingClientRect();h(b,"transition","none"),h(b,"transform","translate3d("+(a.left-d.left)+"px,"+(a.top-d.top)+"px,0)"),b.offsetWidth,h(b,"transition","all "+c+"ms"),h(b,"transform","translate3d(0,0,0)"),clearTimeout(b.animated),b.animated=setTimeout(function(){h(b,"transition",""),h(b,"transform",""),b.animated=!1},c)}},_offUpEvents:function(){var a=this.el.ownerDocument;f(N,"touchmove",this._onTouchMove),f(a,"mouseup",this._onDrop),f(a,"touchend",this._onDrop),f(a,"touchcancel",this._onDrop)},_onDrop:function(b){var c=this.el,d=this.options;clearInterval(this._loopId),clearInterval(J.pid),clearTimeout(this._dragStartTimer),f(N,"mousemove",this._onTouchMove),this.nativeDraggable&&(f(N,"drop",this),f(c,"dragstart",this._onDragStart)),this._offUpEvents(),b&&(I&&(b.preventDefault(),!d.dropBubble&&b.stopPropagation()),u&&u.parentNode.removeChild(u),s&&(this.nativeDraggable&&f(s,"dragend",this),l(s),g(s,this.options.ghostClass,!1),g(s,this.options.chosenClass,!1),w!==t?(E=p(s),E>=0&&(j(null,t,"sort",s,w,D,E),j(this,w,"sort",s,w,D,E),j(null,t,"add",s,w,D,E),j(this,w,"remove",s,w,D,E))):(v&&v.parentNode.removeChild(v),s.nextSibling!==x&&(E=p(s),E>=0&&(j(this,w,"update",s,w,D,E),j(this,w,"sort",s,w,D,E)))),a.active&&((null===E||-1===E)&&(E=D),j(this,w,"end",s,w,D,E),this.save())),w=s=t=u=x=v=y=z=G=H=I=E=A=B=F=a.active=null)},handleEvent:function(a){var b=a.type;"dragover"===b||"dragenter"===b?s&&(this._onDragOver(a),d(a)):("drop"===b||"dragend"===b)&&this._onDrop(a)},toArray:function(){for(var a,b=[],d=this.el.children,e=0,f=d.length,g=this.options;f>e;e++)a=d[e],c(a,g.draggable,this.el)&&b.push(a.getAttribute(g.dataIdAttr)||o(a));return b},sort:function(a){var b={},d=this.el;this.toArray().forEach(function(a,e){var f=d.children[e];c(f,this.options.draggable,d)&&(b[a]=f)},this),a.forEach(function(a){b[a]&&(d.removeChild(b[a]),d.appendChild(b[a]))})},save:function(){var a=this.options.store;a&&a.set(this)},closest:function(a,b){return c(a,b||this.options.draggable,this.el)},option:function(a,b){var c=this.options;return void 0===b?c[a]:(c[a]=b,void("group"===a&&V(c)))},destroy:function(){var a=this.el;a[L]=null,f(a,"mousedown",this._onTapStart),f(a,"touchstart",this._onTapStart),this.nativeDraggable&&(f(a,"dragover",this),f(a,"dragenter",this)),Array.prototype.forEach.call(a.querySelectorAll("[draggable]"),function(a){a.removeAttribute("draggable")}),T.splice(T.indexOf(this._onDragOver),1),this._onDrop(),this.el=a=null}},a.utils={on:e,off:f,css:h,find:i,is:function(a,b){return!!c(a,b,a)},extend:r,throttle:q,closest:c,toggleClass:g,index:p},a.create=function(b,c){return new a(b,c)},a.version="1.3.0",a}); \ No newline at end of file diff --git a/public/Sortable-1.4.0/bower.json b/public/Sortable-1.4.0/bower.json new file mode 100644 index 00000000..c934c0ec --- /dev/null +++ b/public/Sortable-1.4.0/bower.json @@ -0,0 +1,35 @@ +{ + "name": "Sortable", + "main": [ + "Sortable.js", + "ng-sortable.js", + "knockout-sortable.js", + "react-sortable-mixin.js" + ], + "homepage": "http://rubaxa.github.io/Sortable/", + "authors": [ + "RubaXa " + ], + "description": "Minimalist library for reorderable drag-and-drop lists on modern browsers and touch devices. No jQuery.", + "keywords": [ + "sortable", + "reorder", + "list", + "html5", + "drag", + "and", + "drop", + "dnd", + "web-components" + ], + "license": "MIT", + "ignore": [ + "node_modules", + "bower_components", + "test", + "tests" + ], + "dependencies": { + "polymer": "Polymer/polymer#~1.1.4", + } +} diff --git a/public/Sortable-1.4.0/component.json b/public/Sortable-1.4.0/component.json new file mode 100644 index 00000000..20bfb914 --- /dev/null +++ b/public/Sortable-1.4.0/component.json @@ -0,0 +1,32 @@ +{ + "name": "Sortable", + "main": "Sortable.js", + "version": "1.3.0", + "homepage": "http://rubaxa.github.io/Sortable/", + "repo": "RubaXa/Sortable", + "authors": [ + "RubaXa " + ], + "description": "Minimalist library for reorderable drag-and-drop lists on modern browsers and touch devices. No jQuery.", + "keywords": [ + "sortable", + "reorder", + "list", + "html5", + "drag", + "and", + "drop", + "dnd" + ], + "license": "MIT", + "ignore": [ + "node_modules", + "bower_components", + "test", + "tests" + ], + + "scripts": [ + "Sortable.js" + ] +} diff --git a/public/Sortable-1.4.0/jquery.binding.js b/public/Sortable-1.4.0/jquery.binding.js new file mode 100644 index 00000000..9e9f4b93 --- /dev/null +++ b/public/Sortable-1.4.0/jquery.binding.js @@ -0,0 +1,61 @@ +/** + * jQuery plugin for Sortable + * @author RubaXa + * @license MIT + */ +(function (factory) { + "use strict"; + + if (typeof define === "function" && define.amd) { + define(["jquery"], factory); + } + else { + /* jshint sub:true */ + factory(jQuery); + } +})(function ($) { + "use strict"; + + + /* CODE */ + + + /** + * jQuery plugin for Sortable + * @param {Object|String} options + * @param {..*} [args] + * @returns {jQuery|*} + */ + $.fn.sortable = function (options) { + var retVal, + args = arguments; + + this.each(function () { + var $el = $(this), + sortable = $el.data('sortable'); + + if (!sortable && (options instanceof Object || !options)) { + sortable = new Sortable(this, options); + $el.data('sortable', sortable); + } + + if (sortable) { + if (options === 'widget') { + return sortable; + } + else if (options === 'destroy') { + sortable.destroy(); + $el.removeData('sortable'); + } + else if (typeof sortable[options] === 'function') { + retVal = sortable[options].apply(sortable, [].slice.call(args, 1)); + } + else if (options in sortable.options) { + retVal = sortable.option.apply(sortable, args); + } + } + }); + + return (retVal === void 0) ? this : retVal; + }; +}); diff --git a/public/Sortable-1.4.0/package.json b/public/Sortable-1.4.0/package.json new file mode 100644 index 00000000..a13735f8 --- /dev/null +++ b/public/Sortable-1.4.0/package.json @@ -0,0 +1,41 @@ +{ + "name": "sortablejs", + "exportName": "Sortable", + "version": "1.4.0", + "devDependencies": { + "grunt": "*", + "grunt-version": "*", + "grunt-exec": "*", + "grunt-contrib-jshint": "0.9.2", + "grunt-contrib-uglify": "*", + "spacejam": "*" + }, + "description": "Minimalist JavaScript library for reorderable drag-and-drop lists on modern browsers and touch devices. No jQuery. Supports AngularJS and any CSS library, e.g. Bootstrap.", + "main": "Sortable.js", + "scripts": { + "test": "grunt" + }, + "repository": { + "type": "git", + "url": "git://github.com/rubaxa/Sortable.git" + }, + "keywords": [ + "sortable", + "reorder", + "drag", + "meteor", + "angular", + "ng-sortable", + "react", + "mixin" + ], + "author": "Konstantin Lebedev ", + "license": "MIT", + "spm": { + "main": "Sortable.js", + "ignore": [ + "meteor", + "st" + ] + } +} diff --git a/router_gen/routes.go b/router_gen/routes.go index 69711903..cd6ad140 100644 --- a/router_gen/routes.go +++ b/router_gen/routes.go @@ -157,6 +157,9 @@ func buildPanelRoutes() { View("routePanelThemesMenusEdit", "/panel/themes/menus/edit/", "extraData"), View("routePanelThemesMenuItemEdit", "/panel/themes/menus/item/edit/", "extraData"), Action("routePanelThemesMenuItemEditSubmit", "/panel/themes/menus/item/edit/submit/", "extraData"), + Action("routePanelThemesMenuItemCreateSubmit", "/panel/themes/menus/item/create/submit/"), + Action("routePanelThemesMenuItemDeleteSubmit", "/panel/themes/menus/item/delete/submit/", "extraData"), + Action("routePanelThemesMenuItemOrderSubmit", "/panel/themes/menus/item/order/edit/submit/", "extraData"), View("routePanelPlugins", "/panel/plugins/"), Action("routePanelPluginsActivate", "/panel/plugins/activate/", "extraData"), diff --git a/templates/account_menu.html b/templates/account_menu.html index fd5dc0ec..c9a5526c 100644 --- a/templates/account_menu.html +++ b/templates/account_menu.html @@ -9,7 +9,7 @@ - + {{/** TODO: Add an alerts page with pagination to go through alerts which either don't fit in the alerts drop-down or which have already been dismissed. Bear in mind though that dismissed alerts older than two weeks might be purged to save space and to speed up the database **/}} diff --git a/templates/panel_themes_menus_items.html b/templates/panel_themes_menus_items.html index c695ee4e..bdc1ffc4 100644 --- a/templates/panel_themes_menus_items.html +++ b/templates/panel_themes_menus_items.html @@ -1,6 +1,6 @@ {{template "header.html" . }}
-