diff --git a/application/admin/command/Install/fastadmin.sql b/application/admin/command/Install/fastadmin.sql index cf8e91e4becc2f9b7a72859b23dca438f0c0746e..019126f07345f9d2e04cd76aea89bfdfe7e69fc5 100755 --- a/application/admin/command/Install/fastadmin.sql +++ b/application/admin/command/Install/fastadmin.sql @@ -26,6 +26,7 @@ CREATE TABLE `fa_admin` ( `loginip` varchar(50) DEFAULT NULL COMMENT '登录IP', `createtime` int(10) DEFAULT NULL COMMENT '创建时间', `updatetime` int(10) DEFAULT NULL COMMENT '更新时间', + `expiretime` int(10) DEFAULT NULL COMMENT '过期时间', `token` varchar(59) NOT NULL DEFAULT '' COMMENT 'Session标识', `status` varchar(30) NOT NULL DEFAULT 'normal' COMMENT '状态', PRIMARY KEY (`id`), @@ -36,7 +37,7 @@ CREATE TABLE `fa_admin` ( -- Records of fa_admin -- ---------------------------- BEGIN; -INSERT INTO `fa_admin` VALUES (1, 'admin', 'Admin', '075eaec83636846f51c152f29b98a2fd', 's4f3', '/assets/img/avatar.png', 'admin@fastadmin.net', 0, 1502029281, '127.0.0.1',1492186163, 1502029281, 'd3992c3b-5ecc-4ecb-9dc2-8997780fcadc', 'normal'); +INSERT INTO `fa_admin` VALUES (1, 'admin', 'Admin', '075eaec83636846f51c152f29b98a2fd', 's4f3', '/assets/img/avatar.png', 'admin@fastadmin.net', 0, 1502029281, '127.0.0.1',1492186163, 1502029281, 0, 'd3992c3b-5ecc-4ecb-9dc2-8997780fcadc', 'normal'); COMMIT; -- ---------------------------- @@ -99,6 +100,7 @@ CREATE TABLE `fa_auth_group` ( `rules` text NOT NULL COMMENT '规则ID', `createtime` int(10) DEFAULT NULL COMMENT '创建时间', `updatetime` int(10) DEFAULT NULL COMMENT '更新时间', + `expiretime` int(10) DEFAULT NULL COMMENT '过期时间', `status` varchar(30) NOT NULL DEFAULT '' COMMENT '状态', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='分组表'; @@ -107,11 +109,11 @@ CREATE TABLE `fa_auth_group` ( -- Records of fa_auth_group -- ---------------------------- BEGIN; -INSERT INTO `fa_auth_group` VALUES (1, 0, 'Admin group', '*', 1490883540, 149088354, 'normal'); -INSERT INTO `fa_auth_group` VALUES (2, 1, 'Second group', '13,14,16,15,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,40,41,42,43,44,45,46,47,48,49,50,55,56,57,58,59,60,61,62,63,64,65,1,9,10,11,7,6,8,2,4,5', 1490883540, 1505465692, 'normal'); -INSERT INTO `fa_auth_group` VALUES (3, 2, 'Third group', '1,4,9,10,11,13,14,15,16,17,40,41,42,43,44,45,46,47,48,49,50,55,56,57,58,59,60,61,62,63,64,65,5', 1490883540, 1502205322, 'normal'); -INSERT INTO `fa_auth_group` VALUES (4, 1, 'Second group 2', '1,4,13,14,15,16,17,55,56,57,58,59,60,61,62,63,64,65', 1490883540, 1502205350, 'normal'); -INSERT INTO `fa_auth_group` VALUES (5, 2, 'Third group 2', '1,2,6,7,8,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34', 1490883540, 1502205344, 'normal'); +INSERT INTO `fa_auth_group` VALUES (1, 0, 'Admin group', '*', 1490883540, 149088354, 0,'normal'); +INSERT INTO `fa_auth_group` VALUES (2, 1, 'Second group', '13,14,16,15,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,40,41,42,43,44,45,46,47,48,49,50,55,56,57,58,59,60,61,62,63,64,65,1,9,10,11,7,6,8,2,4,5', 1490883540, 1505465692, 0, 'normal'); +INSERT INTO `fa_auth_group` VALUES (3, 2, 'Third group', '1,4,9,10,11,13,14,15,16,17,40,41,42,43,44,45,46,47,48,49,50,55,56,57,58,59,60,61,62,63,64,65,5', 1490883540, 1502205322, 0, 'normal'); +INSERT INTO `fa_auth_group` VALUES (4, 1, 'Second group 2', '1,4,13,14,15,16,17,55,56,57,58,59,60,61,62,63,64,65', 1490883540, 1502205350, 0, 'normal'); +INSERT INTO `fa_auth_group` VALUES (5, 2, 'Third group 2', '1,2,6,7,8,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34', 1490883540, 1502205344, 0, 'normal'); COMMIT; -- ---------------------------- diff --git a/application/admin/controller/auth/Admin.php b/application/admin/controller/auth/Admin.php index a8d23606a3c8a6a2d7361e3f6861d4a319701e3c..d88001dd28f9ea8604aaae9573bb19132e6459e0 100644 --- a/application/admin/controller/auth/Admin.php +++ b/application/admin/controller/auth/Admin.php @@ -129,6 +129,7 @@ class Admin extends Backend $params['salt'] = Random::alnum(); $params['password'] = md5(md5($params['password']) . $params['salt']); $params['avatar'] = '/assets/img/avatar.png'; //设置新管理员默认头像。 + $params['expiretime'] = $params['expiretime']==0 ? 0 : strtotime($params['expiretime']); $result = $this->model->validate('Admin.add')->save($params); if ($result === false) { $this->error($this->model->getError()); @@ -174,6 +175,7 @@ class Admin extends Backend } else { unset($params['password'], $params['salt']); } + $params['expiretime'] = $params['expiretime']==0 ? 0 : strtotime($params['expiretime']); //这里需要针对username和email做唯一验证 $adminValidate = \think\Loader::validate('Admin'); $adminValidate->rule([ @@ -208,6 +210,7 @@ class Admin extends Backend foreach ($grouplist as $k => $v) { $groupids[] = $v['id']; } + $row['expiretime'] = $row['expiretime'] === 0 ? 0 : date("Y-m-d H:i:s", $row['expiretime']); $this->view->assign("row", $row); $this->view->assign("groupids", $groupids); return $this->view->fetch(); diff --git a/application/admin/controller/auth/Group.php b/application/admin/controller/auth/Group.php index 0e82c8d89d7ee5301cbc219820f4dfe85fdaffd9..22e0080e3b467877f3f8dd0bf533716247f173a5 100644 --- a/application/admin/controller/auth/Group.php +++ b/application/admin/controller/auth/Group.php @@ -111,6 +111,11 @@ class Group extends Backend // 如果当前组别不是超级管理员则需要过滤规则节点,不能超当前组别的权限 $rules = in_array('*', $currentrules) ? $rules : array_intersect($currentrules, $rules); $params['rules'] = implode(',', $rules); + //日期转时间戳 + $params['expiretime'] = empty($params['expiretime']) ? 0 : strtotime($params['expiretime']); + if (false === $params['expiretime']) { + $this->error(__('Invalid format of expire time')); + } if ($params) { $this->model->create($params); $this->success(); @@ -144,6 +149,11 @@ class Group extends Backend $this->error(__('The parent group can not be its own child or itself')); } $params['rules'] = explode(',', $params['rules']); + //日期转时间戳 + $params['expiretime'] = empty($params['expiretime']) ? 0 : strtotime($params['expiretime']); + if (false === $params['expiretime']) { + $this->error(__('Invalid format of expire time')); + } $parentmodel = model("AuthGroup")->get($params['pid']); if (!$parentmodel) { @@ -180,6 +190,7 @@ class Group extends Backend $this->error(); return; } + $row['expiretime'] = $row['expiretime'] === 0 ? 0 : date("Y-m-d H:i:s", $row['expiretime']); $this->view->assign("row", $row); return $this->view->fetch(); } diff --git a/application/admin/lang/zh-cn.php b/application/admin/lang/zh-cn.php index b18b5bf6660c52db49c989a84fb22563c5bbbe56..746c2db1bf0e872efabed1c98b50d2aca27b550b 100755 --- a/application/admin/lang/zh-cn.php +++ b/application/admin/lang/zh-cn.php @@ -96,6 +96,7 @@ return [ 'End time' => '结束时间', 'Create time' => '创建时间', 'Update time' => '更新时间', + 'Expire time' => '过期时间', 'Flag' => '标志', 'Drag to sort' => '拖动进行排序', 'Redirect now' => '立即跳转', diff --git a/application/admin/lang/zh-cn/auth/group.php b/application/admin/lang/zh-cn/auth/group.php index 3a63f5860e905d01015bd40e4f955146292d034e..dbf52e561e0e2fa9dbdc3899eee02f4e076d70df 100644 --- a/application/admin/lang/zh-cn/auth/group.php +++ b/application/admin/lang/zh-cn/auth/group.php @@ -9,4 +9,5 @@ return [ 'You can not delete group that contain child group and administrators' => '你不能删除含有子组和管理员的组', 'The parent group exceeds permission limit' => '父组别超出权限范围', 'The parent group can not be its own child or itself' => '父组别不能是它的子组别及本身', + 'Invalid format of expire time' => '过期时间格式错误', ]; diff --git a/application/admin/lang/zh-cn/index.php b/application/admin/lang/zh-cn/index.php index 04aa093560af0f00ab82d58fa8cda7ec177d5801..bf5cfcb37ec0cf50f07dca197c7dde7433ca9517 100644 --- a/application/admin/lang/zh-cn/index.php +++ b/application/admin/lang/zh-cn/index.php @@ -27,6 +27,7 @@ return [ 'Username is incorrect' => '用户名不正确', 'Password is incorrect' => '密码不正确', 'Admin is forbidden' => '管理员已经被禁止登录', + 'Admin is expired' => '管理员已过期', 'Please try again after 1 day' => '请于1天后再尝试登录', 'Login successful' => '登录成功!', 'Logout successful' => '退出成功!', diff --git a/application/admin/library/Auth.php b/application/admin/library/Auth.php index 73826e454721b7969522c847db96294cae905845..3ae2ac05aa88b85edced24aea30baadd4b57ef22 100644 --- a/application/admin/library/Auth.php +++ b/application/admin/library/Auth.php @@ -47,6 +47,10 @@ class Auth extends \fast\Auth $this->setError('Admin is forbidden'); return false; } + if ($admin['expiretime'] < time() && $admin['expiretime']!=0) { + $this->setError('Admin is expired'); + return false; + } if (Config::get('fastadmin.login_failure_retry') && $admin->loginfailure >= 10 && time() - $admin->updatetime < 86400) { $this->setError('Please try again after 1 day'); return false; @@ -282,7 +286,8 @@ class Auth extends \fast\Auth } } // 取出所有分组 - $groupList = \app\admin\model\AuthGroup::where(['status' => 'normal'])->select(); +// $groupList = \app\admin\model\AuthGroup::where(['status' => 'normal'])->select(); + $groupList = \app\admin\model\AuthGroup::all(); $objList = []; foreach ($groups as $k => $v) { if ($v['rules'] === '*') { diff --git a/application/admin/validate/Admin.php b/application/admin/validate/Admin.php index 5b4e4e8617f7b2edba1834b9a1b9b7aa8ea1cad5..76aa4f97c79844ed3e1f9bac19c88a40adfc1183 100644 --- a/application/admin/validate/Admin.php +++ b/application/admin/validate/Admin.php @@ -11,10 +11,11 @@ class Admin extends Validate * 验证规则 */ protected $rule = [ - 'username' => 'require|regex:\w{3,12}|unique:admin', + 'username' => 'require|max:50|unique:admin', 'nickname' => 'require', - 'password' => 'require|regex:\S{32}', + 'password' => 'require', 'email' => 'require|email|unique:admin,email', + 'expiretime' => 'require|integer' ]; /** @@ -33,8 +34,8 @@ class Admin extends Validate * 验证场景 */ protected $scene = [ - 'add' => ['username', 'email', 'nickname', 'password'], - 'edit' => ['username', 'email', 'nickname', 'password'], + 'add' => ['username', 'email', 'nickname', 'password', 'expiretime'], + 'edit' => ['username', 'email', 'nickname', 'expiretime'], ]; public function __construct(array $rules = [], $message = [], $field = []) @@ -45,11 +46,6 @@ class Admin extends Validate 'password' => __('Password'), 'email' => __('Email'), ]; - $this->message = array_merge($this->message, [ - 'username.regex' => __('Please input correct username'), - 'password.regex' => __('Please input correct password') - ]); parent::__construct($rules, $message, $field); } - } diff --git a/application/admin/view/auth/admin/add.html b/application/admin/view/auth/admin/add.html index 6f984d288b8271548c127b7ecc6e896e9f8ec594..b863dc91171ad753bf28303bb28285bd18259fbf 100644 --- a/application/admin/view/auth/admin/add.html +++ b/application/admin/view/auth/admin/add.html @@ -30,6 +30,17 @@ +
b?"-":"")+(a.numbers.output.thousandsSeparator?
-(n?p[0].substr(0,n)+a.numbers.output.thousandsSeparator:"")+p[0].substr(n).replace(/(\d{3})(?=\d)/g,"$1"+a.numbers.output.thousandsSeparator):p[0])+(p[1].length?a.numbers.output.decimalMark+p[1]:"")}}else f=k;!0===a.escape&&(f=escape(f));"function"===typeof a.onCellData&&(f=a.onCellData(r,d,e,f,m))}void 0!==q&&(q.type=m);return f}function Da(b){return 0 b?"-":"")+
+(a.numbers.output.thousandsSeparator?(n?p[0].substr(0,n)+a.numbers.output.thousandsSeparator:"")+p[0].substr(n).replace(/(\d{3})(?=\d)/g,"$1"+a.numbers.output.thousandsSeparator):p[0])+(p[1].length?a.numbers.output.decimalMark+p[1]:"")}}else f=k;!0===a.escape&&(f=escape(f));"function"===typeof a.onCellData&&(f=a.onCellData(t,d,e,f,m))}void 0!==q&&(q.type=m);return f}function Ca(b){return 0 "+a.tableName+" \r'+sa[v],h=a.mso.rtl?h+' "+a.tableName+" = expireTimestamp) {
+ result = Moment(parseInt(valueTimestamp) * 1000).format('YYYY-MM-DD HH:mm:ss');
+ } else {
+ result = '已过期';
+ color = 'danger';
+ }
+ }
+ return '' + result + '';
+ },
operate: function (value, row, index) {
var table = this.table;
// 操作配置
@@ -10741,387 +10772,387 @@ define('table',['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstr
return Table;
});
-// jQuery List DragSort v0.5.2
-// Website: http://dragsort.codeplex.com/
-// License: http://dragsort.codeplex.com/license
-
-(function($) {
-
- $.fn.dragsort = function(options) {
- if (options == "destroy") {
- $(this.selector).trigger("dragsort-uninit");
- return;
- }
-
- var opts = $.extend({}, $.fn.dragsort.defaults, options);
- var lists = [];
- var list = null, lastPos = null;
-
- this.each(function(i, cont) {
-
- //if list container is table, the browser automatically wraps rows in tbody if not specified so change list container to tbody so that children returns rows as user expected
- if ($(cont).is("table") && $(cont).children().size() == 1 && $(cont).children().is("tbody"))
- cont = $(cont).children().get(0);
-
- var newList = {
- draggedItem: null,
- placeHolderItem: null,
- pos: null,
- offset: null,
- offsetLimit: null,
- scroll: null,
- container: cont,
-
- init: function() {
- //set options to default values if not set
- opts.tagName = opts.tagName == "" ? ($(this.container).children().size() == 0 ? "li" : $(this.container).children().get(0).tagName.toLowerCase()) : opts.tagName;
- if (opts.itemSelector == "")
- opts.itemSelector = opts.tagName;
- if (opts.dragSelector == "")
- opts.dragSelector = opts.tagName;
- if (opts.placeHolderTemplate == "")
- opts.placeHolderTemplate = "<" + opts.tagName + "> " + opts.tagName + ">";
-
- //listidx allows reference back to correct list variable instance
- $(this.container).attr("data-listidx", i).mousedown(this.grabItem).bind("dragsort-uninit", this.uninit);
- this.styleDragHandlers(true);
- },
-
- uninit: function() {
- var list = lists[$(this).attr("data-listidx")];
- $(list.container).unbind("mousedown", list.grabItem).unbind("dragsort-uninit");
- list.styleDragHandlers(false);
- },
-
- getItems: function() {
- return $(this.container).children(opts.itemSelector);
- },
-
- styleDragHandlers: function(cursor) {
- this.getItems().map(function() { return $(this).is(opts.dragSelector) ? this : $(this).find(opts.dragSelector).get(); }).css("cursor", cursor ? "pointer" : "");
- },
-
- grabItem: function(e) {
- var list = lists[$(this).attr("data-listidx")];
- var item = $(e.target).closest("[data-listidx] > " + opts.tagName).get(0);
- var insideMoveableItem = list.getItems().filter(function() { return this == item; }).size() > 0;
-
- //if not left click or if clicked on excluded element (e.g. text box) or not a moveable list item return
- if (e.which != 1 || $(e.target).is(opts.dragSelectorExclude) || $(e.target).closest(opts.dragSelectorExclude).size() > 0 || !insideMoveableItem)
- return;
-
- //prevents selection, stops issue on Fx where dragging hyperlink doesn't work and on IE where it triggers mousemove even though mouse hasn't moved,
- //does also stop being able to click text boxes hence dragging on text boxes by default is disabled in dragSelectorExclude
- //e.preventDefault();
-
- //change cursor to move while dragging
- var dragHandle = e.target;
- while (!$(dragHandle).is(opts.dragSelector)) {
- if (dragHandle == this) return;
- dragHandle = dragHandle.parentNode;
- }
- $(dragHandle).attr("data-cursor", $(dragHandle).css("cursor"));
- $(dragHandle).css("cursor", "move");
-
- //on mousedown wait for movement of mouse before triggering dragsort script (dragStart) to allow clicking of hyperlinks to work
- var listElem = this;
- var trigger = function() {
- list.dragStart.call(listElem, e);
- $(list.container).unbind("mousemove", trigger);
- };
- $(list.container).mousemove(trigger).mouseup(function() { $(list.container).unbind("mousemove", trigger); $(dragHandle).css("cursor", $(dragHandle).attr("data-cursor")); });
- },
-
- dragStart: function(e) {
- if (list != null && list.draggedItem != null)
- list.dropItem();
-
- list = lists[$(this).attr("data-listidx")];
- list.draggedItem = $(e.target).closest("[data-listidx] > " + opts.tagName)
-
- //record current position so on dragend we know if the dragged item changed position or not, not using getItems to allow dragsort to restore dragged item to original location in relation to fixed items
- list.draggedItem.attr("data-origpos", $(this).attr("data-listidx") + "-" + $(list.container).children().index(list.draggedItem));
-
- //calculate mouse offset relative to draggedItem
- var mt = parseInt(list.draggedItem.css("marginTop"));
- var ml = parseInt(list.draggedItem.css("marginLeft"));
- list.offset = list.draggedItem.offset();
- list.offset.top = e.pageY - list.offset.top + (isNaN(mt) ? 0 : mt) - 1;
- list.offset.left = e.pageX - list.offset.left + (isNaN(ml) ? 0 : ml) - 1;
-
- //calculate box the dragged item can't be dragged outside of
- if (!opts.dragBetween) {
- var containerHeight = $(list.container).outerHeight() == 0 ? Math.max(1, Math.round(0.5 + list.getItems().size() * list.draggedItem.outerWidth() / $(list.container).outerWidth())) * list.draggedItem.outerHeight() : $(list.container).outerHeight();
- list.offsetLimit = $(list.container).offset();
- list.offsetLimit.right = list.offsetLimit.left + $(list.container).outerWidth() - list.draggedItem.outerWidth();
- list.offsetLimit.bottom = list.offsetLimit.top + containerHeight - list.draggedItem.outerHeight();
- }
-
- //create placeholder item
- var h = list.draggedItem.height();
- var w = list.draggedItem.width();
- if (opts.tagName == "tr") {
- list.draggedItem.children().each(function() { $(this).width($(this).width()); });
- list.placeHolderItem = list.draggedItem.clone().attr("data-placeholder", true);
- list.draggedItem.after(list.placeHolderItem);
- //list.placeHolderItem.children().each(function() { $(this).css({ borderWidth:0, width: $(this).width() + 1, height: $(this).height() + 1 }).html(" "); });
- list.placeHolderItem.children().each(function() { $(this).html(" "); });
- } else {
- list.draggedItem.after(opts.placeHolderTemplate);
- list.placeHolderItem = list.draggedItem.next().css({ height: h, width: w }).attr("data-placeholder", true);
- }
-
- if (opts.tagName == "td") {
- var listTable = list.draggedItem.closest("table").get(0);
- $("
"));g=P(a);a=V(a);c.each(f,function(){if(p>=this.s.r&&p<=this.e.r&&e>=this.s.c&&e<=this.e.c)for(var a=0;a<=this.e.c-this.s.c;++a)e++,b++});if(a||g)a=a||1,g=g||1,f.push({s:{r:p,c:e},e:{r:p+a-1,c:e+g-1}});1
"));g=O(a);a=U(a);c.each(f,function(){if(p>=this.s.r&&p<=this.e.r&&e>=this.s.c&&e<=this.e.c)for(var a=0;a<=this.e.c-this.s.c;++a)e++,b++});if(a||g)a=a||1,g=g||1,f.push({s:{r:p,c:e},e:{r:p+a-1,c:e+g-1}});1"+F(c,d,f)+" "}});0"));n+=">"+e+" "}});0 ");I+=""+F(c(" "+F(c,d,f)+" "}});0"));n+=">"+e+" "}});0 ");H+=""+F(c("
").appendTo("body").children().append(list.draggedItem);
- }
-
- //style draggedItem while dragging
- var orig = list.draggedItem.attr("style");
- list.draggedItem.attr("data-origstyle", orig ? orig : "");
- list.draggedItem.css({ position: "absolute", opacity: 0.8, "z-index": 999, height: h, width: w });
-
- //auto-scroll setup
- list.scroll = { moveX: 0, moveY: 0, maxX: $(document).width() - $(window).width(), maxY: $(document).height() - $(window).height() };
- list.scroll.scrollY = window.setInterval(function() {
- if (opts.scrollContainer != window) {
- $(opts.scrollContainer).scrollTop($(opts.scrollContainer).scrollTop() + list.scroll.moveY);
- return;
- }
- var t = $(opts.scrollContainer).scrollTop();
- if (list.scroll.moveY > 0 && t < list.scroll.maxY || list.scroll.moveY < 0 && t > 0) {
- $(opts.scrollContainer).scrollTop(t + list.scroll.moveY);
- list.draggedItem.css("top", list.draggedItem.offset().top + list.scroll.moveY + 1);
- }
- }, 10);
- list.scroll.scrollX = window.setInterval(function() {
- if (opts.scrollContainer != window) {
- $(opts.scrollContainer).scrollLeft($(opts.scrollContainer).scrollLeft() + list.scroll.moveX);
- return;
- }
- var l = $(opts.scrollContainer).scrollLeft();
- if (list.scroll.moveX > 0 && l < list.scroll.maxX || list.scroll.moveX < 0 && l > 0) {
- $(opts.scrollContainer).scrollLeft(l + list.scroll.moveX);
- list.draggedItem.css("left", list.draggedItem.offset().left + list.scroll.moveX + 1);
- }
- }, 10);
-
- //misc
- $(lists).each(function(i, l) { l.createDropTargets(); l.buildPositionTable(); });
- list.setPos(e.pageX, e.pageY);
- $(document).bind("mousemove", list.swapItems);
- $(document).bind("mouseup", list.dropItem);
- if (opts.scrollContainer != window)
- $(window).bind("wheel", list.wheel);
- },
-
- //set position of draggedItem
- setPos: function(x, y) {
- //remove mouse offset so mouse cursor remains in same place on draggedItem instead of top left corner
- var top = y - this.offset.top;
- var left = x - this.offset.left;
-
- //limit top, left to within box draggedItem can't be dragged outside of
- if (!opts.dragBetween) {
- top = Math.min(this.offsetLimit.bottom, Math.max(top, this.offsetLimit.top));
- left = Math.min(this.offsetLimit.right, Math.max(left, this.offsetLimit.left));
- }
-
- //adjust top & left calculations to parent offset
- var parent = this.draggedItem.offsetParent().not("body").offset(); //offsetParent returns body even when it's static, if not static offset is only factoring margin
- if (parent != null) {
- top -= parent.top;
- left -= parent.left;
- }
-
- //set x or y auto-scroll amount
- if (opts.scrollContainer == window) {
- y -= $(window).scrollTop();
- x -= $(window).scrollLeft();
- y = Math.max(0, y - $(window).height() + 5) + Math.min(0, y - 5);
- x = Math.max(0, x - $(window).width() + 5) + Math.min(0, x - 5);
- } else {
- var cont = $(opts.scrollContainer);
- var offset = cont.offset();
- y = Math.max(0, y - cont.height() - offset.top) + Math.min(0, y - offset.top);
- x = Math.max(0, x - cont.width() - offset.left) + Math.min(0, x - offset.left);
- }
-
- list.scroll.moveX = x == 0 ? 0 : x * opts.scrollSpeed / Math.abs(x);
- list.scroll.moveY = y == 0 ? 0 : y * opts.scrollSpeed / Math.abs(y);
-
- //move draggedItem to new mouse cursor location
- this.draggedItem.css({ top: top, left: left });
- },
-
- //if scroll container is a div allow mouse wheel to scroll div instead of window when mouse is hovering over
- wheel: function(e) {
- if (list && opts.scrollContainer != window) {
- var cont = $(opts.scrollContainer);
- var offset = cont.offset();
- e = e.originalEvent;
- if (e.clientX > offset.left && e.clientX < offset.left + cont.width() && e.clientY > offset.top && e.clientY < offset.top + cont.height()) {
- var deltaY = (e.deltaMode == 0 ? 1 : 10) * e.deltaY;
- cont.scrollTop(cont.scrollTop() + deltaY);
- e.preventDefault();
- }
- }
- },
-
- //build a table recording all the positions of the moveable list items
- buildPositionTable: function() {
- var pos = [];
- this.getItems().not([list.draggedItem[0], list.placeHolderItem[0]]).each(function(i) {
- var loc = $(this).offset();
- loc.right = loc.left + $(this).outerWidth();
- loc.bottom = loc.top + $(this).outerHeight();
- loc.elm = this;
- pos[i] = loc;
- });
- this.pos = pos;
- },
-
- dropItem: function() {
- if (list.draggedItem == null)
- return;
-
- //list.draggedItem.attr("style", "") doesn't work on IE8 and jQuery 1.5 or lower
- //list.draggedItem.removeAttr("style") doesn't work on chrome and jQuery 1.6 (works jQuery 1.5 or lower)
- var orig = list.draggedItem.attr("data-origstyle");
- list.draggedItem.attr("style", orig);
- if (orig == "")
- list.draggedItem.removeAttr("style");
- list.draggedItem.removeAttr("data-origstyle");
-
- list.styleDragHandlers(true);
-
- list.placeHolderItem.before(list.draggedItem);
- list.placeHolderItem.remove();
-
- $("[data-droptarget], .dragSortItem").remove();
-
- window.clearInterval(list.scroll.scrollY);
- window.clearInterval(list.scroll.scrollX);
-
- //if position changed call dragEnd
- if (list.draggedItem.attr("data-origpos") != $(lists).index(list) + "-" + $(list.container).children().index(list.draggedItem))
- if (opts.dragEnd.apply(list.draggedItem) == false) { //if dragEnd returns false revert order
- var pos = list.draggedItem.attr("data-origpos").split('-');
- var nextItem = $(lists[pos[0]].container).children().not(list.draggedItem).eq(pos[1]);
- if (nextItem.size() > 0)
- nextItem.before(list.draggedItem);
- else if (pos[1] == 0) //was the only item in list
- $(lists[pos[0]].container).prepend(list.draggedItem);
- else //was the last item in list
- $(lists[pos[0]].container).append(list.draggedItem);
- }
- list.draggedItem.removeAttr("data-origpos");
-
- list.draggedItem = null;
- $(document).unbind("mousemove", list.swapItems);
- $(document).unbind("mouseup", list.dropItem);
- if (opts.scrollContainer != window)
- $(window).unbind("wheel", list.wheel);
- return false;
- },
-
- //swap the draggedItem (represented visually by placeholder) with the list item the it has been dragged on top of
- swapItems: function(e) {
- if (list.draggedItem == null)
- return false;
-
- //move draggedItem to mouse location
- list.setPos(e.pageX, e.pageY);
-
- //retrieve list and item position mouse cursor is over
- var ei = list.findPos(e.pageX, e.pageY);
- var nlist = list;
- for (var i = 0; ei == -1 && opts.dragBetween && i < lists.length; i++) {
- ei = lists[i].findPos(e.pageX, e.pageY);
- nlist = lists[i];
- }
-
- //if not over another moveable list item return
- if (ei == -1)
- return false;
-
- //save fixed items locations
- var children = function() { return $(nlist.container).children().not(nlist.draggedItem); };
- var fixed = children().not(opts.itemSelector).each(function(i) { this.idx = children().index(this); });
-
- //if moving draggedItem up or left place placeHolder before list item the dragged item is hovering over otherwise place it after
- if (lastPos == null || lastPos.top > list.draggedItem.offset().top || lastPos.left > list.draggedItem.offset().left)
- $(nlist.pos[ei].elm).before(list.placeHolderItem);
- else
- $(nlist.pos[ei].elm).after(list.placeHolderItem);
-
- //restore fixed items location
- fixed.each(function() {
- var elm = children().eq(this.idx).get(0);
- if (this != elm && children().index(this) < this.idx)
- $(this).insertAfter(elm);
- else if (this != elm)
- $(this).insertBefore(elm);
- });
-
- //misc
- $(lists).each(function(i, l) { l.createDropTargets(); l.buildPositionTable(); });
- lastPos = list.draggedItem.offset();
- return false;
- },
-
- //returns the index of the list item the mouse is over
- findPos: function(x, y) {
- for (var i = 0; i < this.pos.length; i++) {
- if (this.pos[i].left < x && this.pos[i].right > x && this.pos[i].top < y && this.pos[i].bottom > y)
- return i;
- }
- return -1;
- },
-
- //create drop targets which are placeholders at the end of other lists to allow dragging straight to the last position
- createDropTargets: function() {
- if (!opts.dragBetween)
- return;
-
- $(lists).each(function() {
- var ph = $(this.container).find("[data-placeholder]");
- var dt = $(this.container).find("[data-droptarget]");
- if (ph.size() > 0 && dt.size() > 0)
- dt.remove();
- else if (ph.size() == 0 && dt.size() == 0) {
- if (opts.tagName == "td")
- $(opts.placeHolderTemplate).attr("data-droptarget", true).appendTo(this.container);
- else
- //list.placeHolderItem.clone().removeAttr("data-placeholder") crashes in IE7 and jquery 1.5.1 (doesn't in jquery 1.4.2 or IE8)
- $(this.container).append(list.placeHolderItem.removeAttr("data-placeholder").clone().attr("data-droptarget", true));
-
- list.placeHolderItem.attr("data-placeholder", true);
- }
- });
- }
- };
-
- newList.init();
- lists.push(newList);
- });
-
- return this;
- };
-
- $.fn.dragsort.defaults = {
- tagName:"",
- itemSelector: "",
- dragSelector: "",
- dragSelectorExclude: "input, textarea",
- dragEnd: function() { },
- dragBetween: false,
- placeHolderTemplate: "",
- scrollContainer: window,
- scrollSpeed: 5
- };
-
-})(jQuery);
+// jQuery List DragSort v0.5.2
+// Website: http://dragsort.codeplex.com/
+// License: http://dragsort.codeplex.com/license
+
+(function($) {
+
+ $.fn.dragsort = function(options) {
+ if (options == "destroy") {
+ $(this.selector).trigger("dragsort-uninit");
+ return;
+ }
+
+ var opts = $.extend({}, $.fn.dragsort.defaults, options);
+ var lists = [];
+ var list = null, lastPos = null;
+
+ this.each(function(i, cont) {
+
+ //if list container is table, the browser automatically wraps rows in tbody if not specified so change list container to tbody so that children returns rows as user expected
+ if ($(cont).is("table") && $(cont).children().size() == 1 && $(cont).children().is("tbody"))
+ cont = $(cont).children().get(0);
+
+ var newList = {
+ draggedItem: null,
+ placeHolderItem: null,
+ pos: null,
+ offset: null,
+ offsetLimit: null,
+ scroll: null,
+ container: cont,
+
+ init: function() {
+ //set options to default values if not set
+ opts.tagName = opts.tagName == "" ? ($(this.container).children().size() == 0 ? "li" : $(this.container).children().get(0).tagName.toLowerCase()) : opts.tagName;
+ if (opts.itemSelector == "")
+ opts.itemSelector = opts.tagName;
+ if (opts.dragSelector == "")
+ opts.dragSelector = opts.tagName;
+ if (opts.placeHolderTemplate == "")
+ opts.placeHolderTemplate = "<" + opts.tagName + "> " + opts.tagName + ">";
+
+ //listidx allows reference back to correct list variable instance
+ $(this.container).attr("data-listidx", i).mousedown(this.grabItem).bind("dragsort-uninit", this.uninit);
+ this.styleDragHandlers(true);
+ },
+
+ uninit: function() {
+ var list = lists[$(this).attr("data-listidx")];
+ $(list.container).unbind("mousedown", list.grabItem).unbind("dragsort-uninit");
+ list.styleDragHandlers(false);
+ },
+
+ getItems: function() {
+ return $(this.container).children(opts.itemSelector);
+ },
+
+ styleDragHandlers: function(cursor) {
+ this.getItems().map(function() { return $(this).is(opts.dragSelector) ? this : $(this).find(opts.dragSelector).get(); }).css("cursor", cursor ? "pointer" : "");
+ },
+
+ grabItem: function(e) {
+ var list = lists[$(this).attr("data-listidx")];
+ var item = $(e.target).closest("[data-listidx] > " + opts.tagName).get(0);
+ var insideMoveableItem = list.getItems().filter(function() { return this == item; }).size() > 0;
+
+ //if not left click or if clicked on excluded element (e.g. text box) or not a moveable list item return
+ if (e.which != 1 || $(e.target).is(opts.dragSelectorExclude) || $(e.target).closest(opts.dragSelectorExclude).size() > 0 || !insideMoveableItem)
+ return;
+
+ //prevents selection, stops issue on Fx where dragging hyperlink doesn't work and on IE where it triggers mousemove even though mouse hasn't moved,
+ //does also stop being able to click text boxes hence dragging on text boxes by default is disabled in dragSelectorExclude
+ //e.preventDefault();
+
+ //change cursor to move while dragging
+ var dragHandle = e.target;
+ while (!$(dragHandle).is(opts.dragSelector)) {
+ if (dragHandle == this) return;
+ dragHandle = dragHandle.parentNode;
+ }
+ $(dragHandle).attr("data-cursor", $(dragHandle).css("cursor"));
+ $(dragHandle).css("cursor", "move");
+
+ //on mousedown wait for movement of mouse before triggering dragsort script (dragStart) to allow clicking of hyperlinks to work
+ var listElem = this;
+ var trigger = function() {
+ list.dragStart.call(listElem, e);
+ $(list.container).unbind("mousemove", trigger);
+ };
+ $(list.container).mousemove(trigger).mouseup(function() { $(list.container).unbind("mousemove", trigger); $(dragHandle).css("cursor", $(dragHandle).attr("data-cursor")); });
+ },
+
+ dragStart: function(e) {
+ if (list != null && list.draggedItem != null)
+ list.dropItem();
+
+ list = lists[$(this).attr("data-listidx")];
+ list.draggedItem = $(e.target).closest("[data-listidx] > " + opts.tagName)
+
+ //record current position so on dragend we know if the dragged item changed position or not, not using getItems to allow dragsort to restore dragged item to original location in relation to fixed items
+ list.draggedItem.attr("data-origpos", $(this).attr("data-listidx") + "-" + $(list.container).children().index(list.draggedItem));
+
+ //calculate mouse offset relative to draggedItem
+ var mt = parseInt(list.draggedItem.css("marginTop"));
+ var ml = parseInt(list.draggedItem.css("marginLeft"));
+ list.offset = list.draggedItem.offset();
+ list.offset.top = e.pageY - list.offset.top + (isNaN(mt) ? 0 : mt) - 1;
+ list.offset.left = e.pageX - list.offset.left + (isNaN(ml) ? 0 : ml) - 1;
+
+ //calculate box the dragged item can't be dragged outside of
+ if (!opts.dragBetween) {
+ var containerHeight = $(list.container).outerHeight() == 0 ? Math.max(1, Math.round(0.5 + list.getItems().size() * list.draggedItem.outerWidth() / $(list.container).outerWidth())) * list.draggedItem.outerHeight() : $(list.container).outerHeight();
+ list.offsetLimit = $(list.container).offset();
+ list.offsetLimit.right = list.offsetLimit.left + $(list.container).outerWidth() - list.draggedItem.outerWidth();
+ list.offsetLimit.bottom = list.offsetLimit.top + containerHeight - list.draggedItem.outerHeight();
+ }
+
+ //create placeholder item
+ var h = list.draggedItem.height();
+ var w = list.draggedItem.width();
+ if (opts.tagName == "tr") {
+ list.draggedItem.children().each(function() { $(this).width($(this).width()); });
+ list.placeHolderItem = list.draggedItem.clone().attr("data-placeholder", true);
+ list.draggedItem.after(list.placeHolderItem);
+ //list.placeHolderItem.children().each(function() { $(this).css({ borderWidth:0, width: $(this).width() + 1, height: $(this).height() + 1 }).html(" "); });
+ list.placeHolderItem.children().each(function() { $(this).html(" "); });
+ } else {
+ list.draggedItem.after(opts.placeHolderTemplate);
+ list.placeHolderItem = list.draggedItem.next().css({ height: h, width: w }).attr("data-placeholder", true);
+ }
+
+ if (opts.tagName == "td") {
+ var listTable = list.draggedItem.closest("table").get(0);
+ $("
").appendTo("body").children().append(list.draggedItem);
+ }
+
+ //style draggedItem while dragging
+ var orig = list.draggedItem.attr("style");
+ list.draggedItem.attr("data-origstyle", orig ? orig : "");
+ list.draggedItem.css({ position: "absolute", opacity: 0.8, "z-index": 999, height: h, width: w });
+
+ //auto-scroll setup
+ list.scroll = { moveX: 0, moveY: 0, maxX: $(document).width() - $(window).width(), maxY: $(document).height() - $(window).height() };
+ list.scroll.scrollY = window.setInterval(function() {
+ if (opts.scrollContainer != window) {
+ $(opts.scrollContainer).scrollTop($(opts.scrollContainer).scrollTop() + list.scroll.moveY);
+ return;
+ }
+ var t = $(opts.scrollContainer).scrollTop();
+ if (list.scroll.moveY > 0 && t < list.scroll.maxY || list.scroll.moveY < 0 && t > 0) {
+ $(opts.scrollContainer).scrollTop(t + list.scroll.moveY);
+ list.draggedItem.css("top", list.draggedItem.offset().top + list.scroll.moveY + 1);
+ }
+ }, 10);
+ list.scroll.scrollX = window.setInterval(function() {
+ if (opts.scrollContainer != window) {
+ $(opts.scrollContainer).scrollLeft($(opts.scrollContainer).scrollLeft() + list.scroll.moveX);
+ return;
+ }
+ var l = $(opts.scrollContainer).scrollLeft();
+ if (list.scroll.moveX > 0 && l < list.scroll.maxX || list.scroll.moveX < 0 && l > 0) {
+ $(opts.scrollContainer).scrollLeft(l + list.scroll.moveX);
+ list.draggedItem.css("left", list.draggedItem.offset().left + list.scroll.moveX + 1);
+ }
+ }, 10);
+
+ //misc
+ $(lists).each(function(i, l) { l.createDropTargets(); l.buildPositionTable(); });
+ list.setPos(e.pageX, e.pageY);
+ $(document).bind("mousemove", list.swapItems);
+ $(document).bind("mouseup", list.dropItem);
+ if (opts.scrollContainer != window)
+ $(window).bind("wheel", list.wheel);
+ },
+
+ //set position of draggedItem
+ setPos: function(x, y) {
+ //remove mouse offset so mouse cursor remains in same place on draggedItem instead of top left corner
+ var top = y - this.offset.top;
+ var left = x - this.offset.left;
+
+ //limit top, left to within box draggedItem can't be dragged outside of
+ if (!opts.dragBetween) {
+ top = Math.min(this.offsetLimit.bottom, Math.max(top, this.offsetLimit.top));
+ left = Math.min(this.offsetLimit.right, Math.max(left, this.offsetLimit.left));
+ }
+
+ //adjust top & left calculations to parent offset
+ var parent = this.draggedItem.offsetParent().not("body").offset(); //offsetParent returns body even when it's static, if not static offset is only factoring margin
+ if (parent != null) {
+ top -= parent.top;
+ left -= parent.left;
+ }
+
+ //set x or y auto-scroll amount
+ if (opts.scrollContainer == window) {
+ y -= $(window).scrollTop();
+ x -= $(window).scrollLeft();
+ y = Math.max(0, y - $(window).height() + 5) + Math.min(0, y - 5);
+ x = Math.max(0, x - $(window).width() + 5) + Math.min(0, x - 5);
+ } else {
+ var cont = $(opts.scrollContainer);
+ var offset = cont.offset();
+ y = Math.max(0, y - cont.height() - offset.top) + Math.min(0, y - offset.top);
+ x = Math.max(0, x - cont.width() - offset.left) + Math.min(0, x - offset.left);
+ }
+
+ list.scroll.moveX = x == 0 ? 0 : x * opts.scrollSpeed / Math.abs(x);
+ list.scroll.moveY = y == 0 ? 0 : y * opts.scrollSpeed / Math.abs(y);
+
+ //move draggedItem to new mouse cursor location
+ this.draggedItem.css({ top: top, left: left });
+ },
+
+ //if scroll container is a div allow mouse wheel to scroll div instead of window when mouse is hovering over
+ wheel: function(e) {
+ if (list && opts.scrollContainer != window) {
+ var cont = $(opts.scrollContainer);
+ var offset = cont.offset();
+ e = e.originalEvent;
+ if (e.clientX > offset.left && e.clientX < offset.left + cont.width() && e.clientY > offset.top && e.clientY < offset.top + cont.height()) {
+ var deltaY = (e.deltaMode == 0 ? 1 : 10) * e.deltaY;
+ cont.scrollTop(cont.scrollTop() + deltaY);
+ e.preventDefault();
+ }
+ }
+ },
+
+ //build a table recording all the positions of the moveable list items
+ buildPositionTable: function() {
+ var pos = [];
+ this.getItems().not([list.draggedItem[0], list.placeHolderItem[0]]).each(function(i) {
+ var loc = $(this).offset();
+ loc.right = loc.left + $(this).outerWidth();
+ loc.bottom = loc.top + $(this).outerHeight();
+ loc.elm = this;
+ pos[i] = loc;
+ });
+ this.pos = pos;
+ },
+
+ dropItem: function() {
+ if (list.draggedItem == null)
+ return;
+
+ //list.draggedItem.attr("style", "") doesn't work on IE8 and jQuery 1.5 or lower
+ //list.draggedItem.removeAttr("style") doesn't work on chrome and jQuery 1.6 (works jQuery 1.5 or lower)
+ var orig = list.draggedItem.attr("data-origstyle");
+ list.draggedItem.attr("style", orig);
+ if (orig == "")
+ list.draggedItem.removeAttr("style");
+ list.draggedItem.removeAttr("data-origstyle");
+
+ list.styleDragHandlers(true);
+
+ list.placeHolderItem.before(list.draggedItem);
+ list.placeHolderItem.remove();
+
+ $("[data-droptarget], .dragSortItem").remove();
+
+ window.clearInterval(list.scroll.scrollY);
+ window.clearInterval(list.scroll.scrollX);
+
+ //if position changed call dragEnd
+ if (list.draggedItem.attr("data-origpos") != $(lists).index(list) + "-" + $(list.container).children().index(list.draggedItem))
+ if (opts.dragEnd.apply(list.draggedItem) == false) { //if dragEnd returns false revert order
+ var pos = list.draggedItem.attr("data-origpos").split('-');
+ var nextItem = $(lists[pos[0]].container).children().not(list.draggedItem).eq(pos[1]);
+ if (nextItem.size() > 0)
+ nextItem.before(list.draggedItem);
+ else if (pos[1] == 0) //was the only item in list
+ $(lists[pos[0]].container).prepend(list.draggedItem);
+ else //was the last item in list
+ $(lists[pos[0]].container).append(list.draggedItem);
+ }
+ list.draggedItem.removeAttr("data-origpos");
+
+ list.draggedItem = null;
+ $(document).unbind("mousemove", list.swapItems);
+ $(document).unbind("mouseup", list.dropItem);
+ if (opts.scrollContainer != window)
+ $(window).unbind("wheel", list.wheel);
+ return false;
+ },
+
+ //swap the draggedItem (represented visually by placeholder) with the list item the it has been dragged on top of
+ swapItems: function(e) {
+ if (list.draggedItem == null)
+ return false;
+
+ //move draggedItem to mouse location
+ list.setPos(e.pageX, e.pageY);
+
+ //retrieve list and item position mouse cursor is over
+ var ei = list.findPos(e.pageX, e.pageY);
+ var nlist = list;
+ for (var i = 0; ei == -1 && opts.dragBetween && i < lists.length; i++) {
+ ei = lists[i].findPos(e.pageX, e.pageY);
+ nlist = lists[i];
+ }
+
+ //if not over another moveable list item return
+ if (ei == -1)
+ return false;
+
+ //save fixed items locations
+ var children = function() { return $(nlist.container).children().not(nlist.draggedItem); };
+ var fixed = children().not(opts.itemSelector).each(function(i) { this.idx = children().index(this); });
+
+ //if moving draggedItem up or left place placeHolder before list item the dragged item is hovering over otherwise place it after
+ if (lastPos == null || lastPos.top > list.draggedItem.offset().top || lastPos.left > list.draggedItem.offset().left)
+ $(nlist.pos[ei].elm).before(list.placeHolderItem);
+ else
+ $(nlist.pos[ei].elm).after(list.placeHolderItem);
+
+ //restore fixed items location
+ fixed.each(function() {
+ var elm = children().eq(this.idx).get(0);
+ if (this != elm && children().index(this) < this.idx)
+ $(this).insertAfter(elm);
+ else if (this != elm)
+ $(this).insertBefore(elm);
+ });
+
+ //misc
+ $(lists).each(function(i, l) { l.createDropTargets(); l.buildPositionTable(); });
+ lastPos = list.draggedItem.offset();
+ return false;
+ },
+
+ //returns the index of the list item the mouse is over
+ findPos: function(x, y) {
+ for (var i = 0; i < this.pos.length; i++) {
+ if (this.pos[i].left < x && this.pos[i].right > x && this.pos[i].top < y && this.pos[i].bottom > y)
+ return i;
+ }
+ return -1;
+ },
+
+ //create drop targets which are placeholders at the end of other lists to allow dragging straight to the last position
+ createDropTargets: function() {
+ if (!opts.dragBetween)
+ return;
+
+ $(lists).each(function() {
+ var ph = $(this.container).find("[data-placeholder]");
+ var dt = $(this.container).find("[data-droptarget]");
+ if (ph.size() > 0 && dt.size() > 0)
+ dt.remove();
+ else if (ph.size() == 0 && dt.size() == 0) {
+ if (opts.tagName == "td")
+ $(opts.placeHolderTemplate).attr("data-droptarget", true).appendTo(this.container);
+ else
+ //list.placeHolderItem.clone().removeAttr("data-placeholder") crashes in IE7 and jquery 1.5.1 (doesn't in jquery 1.4.2 or IE8)
+ $(this.container).append(list.placeHolderItem.removeAttr("data-placeholder").clone().attr("data-droptarget", true));
+
+ list.placeHolderItem.attr("data-placeholder", true);
+ }
+ });
+ }
+ };
+
+ newList.init();
+ lists.push(newList);
+ });
+
+ return this;
+ };
+
+ $.fn.dragsort.defaults = {
+ tagName:"",
+ itemSelector: "",
+ dragSelector: "",
+ dragSelectorExclude: "input, textarea",
+ dragEnd: function() { },
+ dragBetween: false,
+ placeHolderTemplate: "",
+ scrollContainer: window,
+ scrollSpeed: 5
+ };
+
+})(jQuery);
define("dragsort", function(){});
@@ -11141,244 +11172,244 @@ define("drag", function(){});
;(function(d){d.fn.drop=function(i,e,h){var g=typeof i=="string"?i:"",f=d.isFunction(i)?i:d.isFunction(e)?e:null;if(g.indexOf("drop")!==0){g="drop"+g}h=(i==f?e:h)||{};return f?this.bind(g,h,f):this.trigger(g)};d.drop=function(e){e=e||{};b.multi=e.multi===true?Infinity:e.multi===false?1:!isNaN(e.multi)?e.multi:b.multi;b.delay=e.delay||b.delay;b.tolerance=d.isFunction(e.tolerance)?e.tolerance:e.tolerance===null?null:b.tolerance;b.mode=e.mode||b.mode||"intersect"};var c=d.event,a=c.special,b=d.event.special.drop={multi:1,delay:20,mode:"overlap",targets:[],datakey:"dropdata",noBubble:true,add:function(f){var e=d.data(this,b.datakey);e.related+=1},remove:function(){d.data(this,b.datakey).related-=1},setup:function(){if(d.data(this,b.datakey)){return}var e={related:0,active:[],anyactive:0,winner:0,location:{}};d.data(this,b.datakey,e);b.targets.push(this);return false},teardown:function(){var f=d.data(this,b.datakey)||{};if(f.related){return}d.removeData(this,b.datakey);var e=this;b.targets=d.grep(b.targets,function(g){return(g!==e)})},handler:function(g,e){var f,h;if(!e){return}switch(g.type){case"mousedown":case"touchstart":h=d(b.targets);if(typeof e.drop=="string"){h=h.filter(e.drop)}h.each(function(){var i=d.data(this,b.datakey);i.active=[];i.anyactive=0;i.winner=0});e.droppable=h;a.drag.hijack(g,"dropinit",e);break;case"mousemove":case"touchmove":b.event=g;if(!b.timer){b.tolerate(e)}break;case"mouseup":case"touchend":b.timer=clearTimeout(b.timer);if(e.propagates){a.drag.hijack(g,"drop",e);a.drag.hijack(g,"dropend",e)}break}},locate:function(k,h){var l=d.data(k,b.datakey),g=d(k),i=g.offset()||{},e=g.outerHeight(),j=g.outerWidth(),f={elem:k,width:j,height:e,top:i.top,left:i.left,right:i.left+j,bottom:i.top+e};if(l){l.location=f;l.index=h;l.elem=k}return f},contains:function(e,f){return((f[0]||f.left)>=e.left&&(f[0]||f.right)<=e.right&&(f[1]||f.top)>=e.top&&(f[1]||f.bottom)<=e.bottom)},modes:{intersect:function(f,e,g){return this.contains(g,[f.pageX,f.pageY])?1000000000:this.modes.overlap.apply(this,arguments)},overlap:function(f,e,g){return Math.max(0,Math.min(g.bottom,e.bottom)-Math.max(g.top,e.top))*Math.max(0,Math.min(g.right,e.right)-Math.max(g.left,e.left))},fit:function(f,e,g){return this.contains(g,e)?1:0},middle:function(f,e,g){return this.contains(g,[e.left+e.width*0.5,e.top+e.height*0.5])?1:0}},sort:function(f,e){return(e.winner-f.winner)||(f.index-e.index)},tolerate:function(q){var k,e,n,j,l,m,g,p=0,f,h=q.interactions.length,r=[b.event.pageX,b.event.pageY],o=b.tolerance||b.modes[b.mode];do{if(f=q.interactions[p]){if(!f){return}f.drop=[];l=[];m=f.droppable.length;if(o){n=b.locate(f.proxy)}k=0;do{if(g=f.droppable[k]){j=d.data(g,b.datakey);e=j.location;if(!e){continue}j.winner=o?o.call(b,b.event,n,e):b.contains(e,r)?1:0;l.push(j)}}while(++k