From acba2815810b6989df8ae0c5f7df7bd2b5610b7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20H=C3=B6=C3=9Fl?= Date: Sat, 16 Mar 2013 17:02:14 +0100 Subject: [PATCH] Sorting agenda items with nestedSortable. --- AUTHORS | 1 + THANKS | 6 +- docs/de/chapter7_3.rst | 2 +- extras/benchmark/bench.py | 1 - openslides/agenda/forms.py | 14 +- openslides/agenda/static/javascript/agenda.js | 18 +- .../agenda/static/javascript/agenda_sort.js | 66 + .../javascript/jquery.mjs.nestedSortable.js | 429 +++++ .../agenda/static/styles/agenda_sort.css | 78 + .../agenda/templates/agenda/item_row.html | 95 +- .../agenda/templates/agenda/overview.html | 75 +- openslides/static/javascript/tabledrag.js | 1431 ----------------- openslides/static/styles/tabledrag.css | 49 - 13 files changed, 664 insertions(+), 1601 deletions(-) create mode 100644 openslides/agenda/static/javascript/agenda_sort.js create mode 100644 openslides/agenda/static/javascript/jquery.mjs.nestedSortable.js create mode 100644 openslides/agenda/static/styles/agenda_sort.css delete mode 100644 openslides/static/javascript/tabledrag.js delete mode 100644 openslides/static/styles/tabledrag.css diff --git a/AUTHORS b/AUTHORS index b1c6e1f79..d4b545959 100644 --- a/AUTHORS +++ b/AUTHORS @@ -8,3 +8,4 @@ Authors of OpenSlides in chronological order of first contribution: Moira Brülisauer (French translation) Alexis Roussel (French translation) Stefan Frauenknecht + Tobias Hößl diff --git a/THANKS b/THANKS index f0ed4bea3..8472b5382 100644 --- a/THANKS +++ b/THANKS @@ -22,8 +22,8 @@ OpenSlides uses parts of the following projects: * ReportLab -* Drupal (tabledrag function) - - * Ubuntu TrueType Font + +* nestedSortable jQuery Plugin + diff --git a/docs/de/chapter7_3.rst b/docs/de/chapter7_3.rst index 9356c3d2d..5541d07c6 100644 --- a/docs/de/chapter7_3.rst +++ b/docs/de/chapter7_3.rst @@ -9,7 +9,7 @@ Dieses Tutorial ist noch nicht fertiggestellt. Wenn Sie Interesse haben, uns zu .. TODO, ggf. nach oben verschieben -.. Das Aussehen von Openslides wird durch die folgenden Dateien beeinflusst: base.css, tabledrag.css, agenda.css, beamer.css +.. Das Aussehen von Openslides wird durch die folgenden Dateien beeinflusst: base.css, agenda.css, projector.css .. Die Dateien liegen im Unterverzeichnis "/static/styles/" des Installationsverzeichnisses. diff --git a/extras/benchmark/bench.py b/extras/benchmark/bench.py index 31e886d01..48b6ed5fe 100755 --- a/extras/benchmark/bench.py +++ b/extras/benchmark/bench.py @@ -37,7 +37,6 @@ URL_SETS = { "agenda": [ "/agenda/", "/static/styles/base.css", - "/static/styles/tabledrag.css", "/static/javascript/utils.js", "/static/styles/agenda.css", "/static/javascript/jquery.min.js", diff --git a/openslides/agenda/forms.py b/openslides/agenda/forms.py index 0ee615f65..38f4ae81a 100644 --- a/openslides/agenda/forms.py +++ b/openslides/agenda/forms.py @@ -40,20 +40,12 @@ class ItemForm(forms.ModelForm, CssClassMixin): exclude = ('closed', 'weight', 'related_sid') -def gen_weight_choices(): - """ - Creates a list of tuples (n, n) for n from -49 to 50. - """ - return zip(*(range(-50, 51), range(-50, 51))) - - -class ItemOrderForm(CssClassMixin, forms.Form): +class ItemOrderForm(forms.Form, CssClassMixin): """ Form to change the order of the items. """ - weight = forms.ChoiceField( - choices=gen_weight_choices(), - widget=forms.Select(attrs={'class': 'menu-weight'})) + weight = forms.IntegerField( + widget=forms.HiddenInput(attrs={'class': 'menu-weight'})) self = forms.IntegerField( widget=forms.HiddenInput(attrs={'class': 'menu-mlid'})) parent = forms.IntegerField( diff --git a/openslides/agenda/static/javascript/agenda.js b/openslides/agenda/static/javascript/agenda.js index 35cb30765..ea89df59c 100644 --- a/openslides/agenda/static/javascript/agenda.js +++ b/openslides/agenda/static/javascript/agenda.js @@ -5,28 +5,18 @@ * :license: GNU GPL, see LICENSE for more details. */ -function hideLine(object) { - if (object == []) { - return; - } - object.hide(); - id = object.children('td.tabledrag-hide').children('input.menu-mlid').attr('value'); - $('.menu-plid[value=\'' + id + '\']').parent().parent().each(function() { - hideLine($(this)); - }); -} function hideClosedSlides(hide) { if (hide) { $('#hidelink').attr('title', 'show'); $('#hidelink').removeClass('hide').addClass('show'); - $('.close_link .icon-checked-new').parent().parent().parent().each(function() { - hideLine($(this)); + $('.close_link .icon-checked-new').each(function() { + $(this).parents("li").first().hide(); }); - hidden = $('#menu-overview tr:hidden').size(); + var hidden = $(".agenda_list li:hidden").length; $('#hiddencount').text(interpolate(gettext(', of which %s are hidden.'), [hidden])); } else { - $('#menu-overview tr').show(); + $('.agenda_list li').show(); $('#hidelink').attr('title','hide'); $('#hidelink').removeClass('show').addClass('hide'); $('#hiddencount').text(''); diff --git a/openslides/agenda/static/javascript/agenda_sort.js b/openslides/agenda/static/javascript/agenda_sort.js new file mode 100644 index 000000000..5b4a4cf4b --- /dev/null +++ b/openslides/agenda/static/javascript/agenda_sort.js @@ -0,0 +1,66 @@ + +$(function() { + var $agenda_list = $('ol.agenda_list'); + + var rebuildOpenersClosers = function ( ) { + $agenda_list.find("li").each(function() { + var $li = $(this); + if ($li.find("> ol").length > 0) $li.find("> div .opener_closer").show(); + else $li.find("> div .opener_closer").hide(); + }); + } + + var rebuildNesting = function( $root, level, parent_id ) { + var $children = $root.find('> li'), + curr_weight = -50; + + $children.each(function() { + var $child = $(this), + $curr_element = $child.find('> div'), + my_id = $curr_element.find('.menu-mlid').val(); + + $curr_element.find('.menu-plid').val(parent_id); + $curr_element.find('.menu-weight').val(curr_weight); + curr_weight++; + + $child.find('> ol').each(function() { + rebuildNesting( $(this), level + 1, my_id ); + }); + + }); + + }; + + if ($agenda_list.hasClass("sortable")) $agenda_list.nestedSortable({ + forcePlaceholderSize: true, + handle: 'div', + helper: 'clone', + items: 'li', + opacity: .6, + placeholder: 'placeholder', + revert: 250, + tabSize: 25, + tolerance: 'pointer', + toleranceElement: '> div', + isTree: true, + expandOnHover: 700, + startCollapsed: true, + update: function (event, ui) { + var $this = $(this); + rebuildNesting($this, 0, 0); + $('#changed-order-message').show(); + rebuildOpenersClosers(); + } + }); + + rebuildOpenersClosers(); + + $agenda_list.find(".opener_closer .opener").click(function(ev) { + ev.preventDefault(); + $(this).parents("li").first().removeClass("closed"); + }); + $agenda_list.find(".opener_closer .closer").click(function(ev) { + ev.preventDefault(); + $(this).parents("li").first().addClass("closed"); + }); +}); diff --git a/openslides/agenda/static/javascript/jquery.mjs.nestedSortable.js b/openslides/agenda/static/javascript/jquery.mjs.nestedSortable.js new file mode 100644 index 000000000..e6c0d6245 --- /dev/null +++ b/openslides/agenda/static/javascript/jquery.mjs.nestedSortable.js @@ -0,0 +1,429 @@ +/* + * jQuery UI Nested Sortable + * v 1.3.5 / 21 jun 2012 + * http://mjsarfatti.com/code/nestedSortable + * + * Depends on: + * jquery.ui.sortable.js 1.8+ + * + * Copyright (c) 2010-2012 Manuele J Sarfatti + * Licensed under the MIT License + * http://www.opensource.org/licenses/mit-license.php + */ + +(function($) { + + $.widget("mjs.nestedSortable", $.extend({}, $.ui.sortable.prototype, { + + options: { + tabSize: 20, + disableNesting: 'mjs-nestedSortable-no-nesting', + errorClass: 'mjs-nestedSortable-error', + doNotClear: false, + listType: 'ol', + maxLevels: 0, + protectRoot: false, + rootID: null, + rtl: false, + isAllowed: function(item, parent) { return true; } + }, + + _create: function() { + this.element.data('sortable', this.element.data('nestedSortable')); + + if (!this.element.is(this.options.listType)) + throw new Error('nestedSortable: Please check the listType option is set to your actual list type'); + + return $.ui.sortable.prototype._create.apply(this, arguments); + }, + + destroy: function() { + this.element + .removeData("nestedSortable") + .unbind(".nestedSortable"); + return $.ui.sortable.prototype.destroy.apply(this, arguments); + }, + + _mouseDrag: function(event) { + + //Compute the helpers position + this.position = this._generatePosition(event); + this.positionAbs = this._convertPositionTo("absolute"); + + if (!this.lastPositionAbs) { + this.lastPositionAbs = this.positionAbs; + } + + var o = this.options; + + //Do scrolling + if(this.options.scroll) { + var scrolled = false; + if(this.scrollParent[0] != document && this.scrollParent[0].tagName != 'HTML') { + + if((this.overflowOffset.top + this.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity) + this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop + o.scrollSpeed; + else if(event.pageY - this.overflowOffset.top < o.scrollSensitivity) + this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop - o.scrollSpeed; + + if((this.overflowOffset.left + this.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity) + this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft + o.scrollSpeed; + else if(event.pageX - this.overflowOffset.left < o.scrollSensitivity) + this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft - o.scrollSpeed; + + } else { + + if(event.pageY - $(document).scrollTop() < o.scrollSensitivity) + scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed); + else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity) + scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed); + + if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity) + scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed); + else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity) + scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed); + + } + + if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour) + $.ui.ddmanager.prepareOffsets(this, event); + } + + //Regenerate the absolute position used for position checks + this.positionAbs = this._convertPositionTo("absolute"); + + // Find the top offset before rearrangement, + var previousTopOffset = this.placeholder.offset().top; + + //Set the helper position + if(!this.options.axis || this.options.axis != "y") this.helper[0].style.left = this.position.left+'px'; + if(!this.options.axis || this.options.axis != "x") this.helper[0].style.top = this.position.top+'px'; + + //Rearrange + for (var i = this.items.length - 1; i >= 0; i--) { + + //Cache variables and intersection, continue if no intersection + var item = this.items[i], itemElement = item.item[0], intersection = this._intersectsWithPointer(item); + if (!intersection) continue; + + if(itemElement != this.currentItem[0] //cannot intersect with itself + && this.placeholder[intersection == 1 ? "next" : "prev"]()[0] != itemElement //no useless actions that have been done before + && !$.contains(this.placeholder[0], itemElement) //no action if the item moved is the parent of the item checked + && (this.options.type == 'semi-dynamic' ? !$.contains(this.element[0], itemElement) : true) + //&& itemElement.parentNode == this.placeholder[0].parentNode // only rearrange items within the same container + ) { + + $(itemElement).mouseenter(); + + this.direction = intersection == 1 ? "down" : "up"; + + if (this.options.tolerance == "pointer" || this._intersectsWithSides(item)) { + $(itemElement).mouseleave(); + this._rearrange(event, item); + } else { + break; + } + + // Clear emtpy ul's/ol's + this._clearEmpty(itemElement); + + this._trigger("change", event, this._uiHash()); + break; + } + } + + var parentItem = (this.placeholder[0].parentNode.parentNode && + $(this.placeholder[0].parentNode.parentNode).closest('.ui-sortable').length) + ? $(this.placeholder[0].parentNode.parentNode) + : null, + level = this._getLevel(this.placeholder), + childLevels = this._getChildLevels(this.helper); + + // To find the previous sibling in the list, keep backtracking until we hit a valid list item. + var previousItem = this.placeholder[0].previousSibling ? $(this.placeholder[0].previousSibling) : null; + if (previousItem != null) { + while (previousItem[0].nodeName.toLowerCase() != 'li' || previousItem[0] == this.currentItem[0] || previousItem[0] == this.helper[0]) { + if (previousItem[0].previousSibling) { + previousItem = $(previousItem[0].previousSibling); + } else { + previousItem = null; + break; + } + } + } + + // To find the next sibling in the list, keep stepping forward until we hit a valid list item. + var nextItem = this.placeholder[0].nextSibling ? $(this.placeholder[0].nextSibling) : null; + if (nextItem != null) { + while (nextItem[0].nodeName.toLowerCase() != 'li' || nextItem[0] == this.currentItem[0] || nextItem[0] == this.helper[0]) { + if (nextItem[0].nextSibling) { + nextItem = $(nextItem[0].nextSibling); + } else { + nextItem = null; + break; + } + } + } + + var newList = document.createElement(o.listType); + + this.beyondMaxLevels = 0; + + // If the item is moved to the left, send it to its parent's level unless there are siblings below it. + if (parentItem != null && nextItem == null && + (o.rtl && (this.positionAbs.left + this.helper.outerWidth() > parentItem.offset().left + parentItem.outerWidth()) || + !o.rtl && (this.positionAbs.left < parentItem.offset().left))) { + parentItem.after(this.placeholder[0]); + this._clearEmpty(parentItem[0]); + this._trigger("change", event, this._uiHash()); + } + // If the item is below a sibling and is moved to the right, make it a child of that sibling. + else if (previousItem != null && + (o.rtl && (this.positionAbs.left + this.helper.outerWidth() < previousItem.offset().left + previousItem.outerWidth() - o.tabSize) || + !o.rtl && (this.positionAbs.left > previousItem.offset().left + o.tabSize))) { + this._isAllowed(previousItem, level, level+childLevels+1); + if (!previousItem.children(o.listType).length) { + previousItem[0].appendChild(newList); + } + // If this item is being moved from the top, add it to the top of the list. + if (previousTopOffset && (previousTopOffset <= previousItem.offset().top)) { + previousItem.children(o.listType).prepend(this.placeholder); + } + // Otherwise, add it to the bottom of the list. + else { + previousItem.children(o.listType)[0].appendChild(this.placeholder[0]); + } + this._trigger("change", event, this._uiHash()); + } + else { + this._isAllowed(parentItem, level, level+childLevels); + } + + //Post events to containers + this._contactContainers(event); + + //Interconnect with droppables + if($.ui.ddmanager) $.ui.ddmanager.drag(this, event); + + //Call callbacks + this._trigger('sort', event, this._uiHash()); + + this.lastPositionAbs = this.positionAbs; + return false; + + }, + + _mouseStop: function(event, noPropagation) { + + // If the item is in a position not allowed, send it back + if (this.beyondMaxLevels) { + + this.placeholder.removeClass(this.options.errorClass); + + if (this.domPosition.prev) { + $(this.domPosition.prev).after(this.placeholder); + } else { + $(this.domPosition.parent).prepend(this.placeholder); + } + + this._trigger("revert", event, this._uiHash()); + + } + + // Clean last empty ul/ol + for (var i = this.items.length - 1; i >= 0; i--) { + var item = this.items[i].item[0]; + this._clearEmpty(item); + } + + $.ui.sortable.prototype._mouseStop.apply(this, arguments); + + }, + + serialize: function(options) { + + var o = $.extend({}, this.options, options), + items = this._getItemsAsjQuery(o && o.connected), + str = []; + + $(items).each(function() { + var res = ($(o.item || this).attr(o.attribute || 'id') || '') + .match(o.expression || (/(.+)[-=_](.+)/)), + pid = ($(o.item || this).parent(o.listType) + .parent(o.items) + .attr(o.attribute || 'id') || '') + .match(o.expression || (/(.+)[-=_](.+)/)); + + if (res) { + str.push(((o.key || res[1]) + '[' + (o.key && o.expression ? res[1] : res[2]) + ']') + + '=' + + (pid ? (o.key && o.expression ? pid[1] : pid[2]) : o.rootID)); + } + }); + + if(!str.length && o.key) { + str.push(o.key + '='); + } + + return str.join('&'); + + }, + + toHierarchy: function(options) { + + var o = $.extend({}, this.options, options), + sDepth = o.startDepthCount || 0, + ret = []; + + $(this.element).children(o.items).each(function () { + var level = _recursiveItems(this); + ret.push(level); + }); + + return ret; + + function _recursiveItems(item) { + var id = ($(item).attr(o.attribute || 'id') || '').match(o.expression || (/(.+)[-=_](.+)/)); + if (id) { + var currentItem = {"id" : id[2]}; + if ($(item).children(o.listType).children(o.items).length > 0) { + currentItem.children = []; + $(item).children(o.listType).children(o.items).each(function() { + var level = _recursiveItems(this); + currentItem.children.push(level); + }); + } + return currentItem; + } + } + }, + + toArray: function(options) { + + var o = $.extend({}, this.options, options), + sDepth = o.startDepthCount || 0, + ret = [], + left = 2; + + ret.push({ + "item_id": o.rootID, + "parent_id": 'none', + "depth": sDepth, + "left": '1', + "right": ($(o.items, this.element).length + 1) * 2 + }); + + $(this.element).children(o.items).each(function () { + left = _recursiveArray(this, sDepth + 1, left); + }); + + ret = ret.sort(function(a,b){ return (a.left - b.left); }); + + return ret; + + function _recursiveArray(item, depth, left) { + + var right = left + 1, + id, + pid; + + if ($(item).children(o.listType).children(o.items).length > 0) { + depth ++; + $(item).children(o.listType).children(o.items).each(function () { + right = _recursiveArray($(this), depth, right); + }); + depth --; + } + + id = ($(item).attr(o.attribute || 'id')).match(o.expression || (/(.+)[-=_](.+)/)); + + if (depth === sDepth + 1) { + pid = o.rootID; + } else { + var parentItem = ($(item).parent(o.listType) + .parent(o.items) + .attr(o.attribute || 'id')) + .match(o.expression || (/(.+)[-=_](.+)/)); + pid = parentItem[2]; + } + + if (id) { + ret.push({"item_id": id[2], "parent_id": pid, "depth": depth, "left": left, "right": right}); + } + + left = right + 1; + return left; + } + + }, + + _clearEmpty: function(item) { + + var emptyList = $(item).children(this.options.listType); + if (emptyList.length && !emptyList.children().length && !this.options.doNotClear) { + emptyList.remove(); + } + + }, + + _getLevel: function(item) { + + var level = 1; + + if (this.options.listType) { + var list = item.closest(this.options.listType); + while (list && list.length > 0 && + !list.is('.ui-sortable')) { + level++; + list = list.parent().closest(this.options.listType); + } + } + + return level; + }, + + _getChildLevels: function(parent, depth) { + var self = this, + o = this.options, + result = 0; + depth = depth || 0; + + $(parent).children(o.listType).children(o.items).each(function (index, child) { + result = Math.max(self._getChildLevels(child, depth + 1), result); + }); + + return depth ? result + 1 : result; + }, + + _isAllowed: function(parentItem, level, levels) { + var o = this.options, + isRoot = $(this.domPosition.parent).hasClass('ui-sortable') ? true : false, + maxLevels = this.placeholder.closest('.ui-sortable').nestedSortable('option', 'maxLevels'); // this takes into account the maxLevels set to the recipient list + + // Is the root protected? + // Are we trying to nest under a no-nest? + // Are we nesting too deep? + if (!o.isAllowed(this.currentItem, parentItem) || + parentItem && parentItem.hasClass(o.disableNesting) || + o.protectRoot && (parentItem == null && !isRoot || isRoot && level > 1)) { + this.placeholder.addClass(o.errorClass); + if (maxLevels < levels && maxLevels != 0) { + this.beyondMaxLevels = levels - maxLevels; + } else { + this.beyondMaxLevels = 1; + } + } else { + if (maxLevels < levels && maxLevels != 0) { + this.placeholder.addClass(o.errorClass); + this.beyondMaxLevels = levels - maxLevels; + } else { + this.placeholder.removeClass(o.errorClass); + this.beyondMaxLevels = 0; + } + } + } + + })); + + $.mjs.nestedSortable.prototype.options = $.extend({}, $.ui.sortable.prototype.options, $.mjs.nestedSortable.prototype.options); +})(jQuery); diff --git a/openslides/agenda/static/styles/agenda_sort.css b/openslides/agenda/static/styles/agenda_sort.css new file mode 100644 index 000000000..36d231b2f --- /dev/null +++ b/openslides/agenda/static/styles/agenda_sort.css @@ -0,0 +1,78 @@ +ol { + margin: 0; + padding: 0; + padding-left: 30px; +} + +ol.agenda_list, ol.agenda_list ol { + margin: 0 0 0 25px; + padding: 0; + list-style-type: none; +} + +ol.agenda_list { + margin: 0; +} + +.agenda_list li { + margin: 5px 0 0 0; + padding: 0; +} + +.agenda_list li > div { + border: 1px solid #d4d4d4; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; + border-color: #D4D4D4 #D4D4D4 #BCBCBC; + padding: 6px; + margin: 0; + background: #f6f6f6; + background: -moz-linear-gradient(top, #ffffff 0%, #f6f6f6 47%, #ededed 100%); + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ffffff), color-stop(47%,#f6f6f6), color-stop(100%,#ededed)); + background: -webkit-linear-gradient(top, #ffffff 0%,#f6f6f6 47%,#ededed 100%); + background: -o-linear-gradient(top, #ffffff 0%,#f6f6f6 47%,#ededed 100%); + background: -ms-linear-gradient(top, #ffffff 0%,#f6f6f6 47%,#ededed 100%); + background: linear-gradient(to bottom, #ffffff 0%,#f6f6f6 47%,#ededed 100%); + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#ededed',GradientType=0 ); + overflow: auto; +} +.agenda_list.sortable li > div { + cursor: move; +} +.disclose { + cursor: pointer; + width: 10px; + display: none; +} + +.placeholder { + outline: 1px dashed #4183C4; +} + +.agenda_list .openclose { width: 40px; float: left; min-height: 1px; } +.agenda_list .duration { width: 92px; float: right; min-height: 1px; padding-left: 5px; } +.agenda_list .manage { width: 150px; float: right; min-height: 1px; padding-left: 5px; } +.agenda_list .title { float: left; } +.agenda_list .optional { float: left; } +.agenda_list .opener_closer { float: left; margin-right: 10px; } +.agenda_list .optional { float: right; width: 208px; padding-left: 8px;} + +#menu-overview .manage { width: 150px; } +#menu-overview .duration { width: 75px; } +#menu-overview .optional { width: 200px; } + +@media screen and (max-width: 1000px) { + #menu-overview .optional { width: 150px; } + .agenda_list .optional { float: right; width: 158px; padding-left: 8px;} +} + +@media screen and (max-width: 800px) { + #menu-overview .optional { width: 100px; } + .agenda_list .optional { float: right; width: 108px; padding-left: 8px;} +} +.agenda_list .opener, .agenda_list .closer { } +.agenda_list .opener { display: none; } +.agenda_list li.closed ol { display: none; } +.agenda_list li.closed .closer { display: none; } +.agenda_list li.closed > div .opener { display: inline-block; } diff --git a/openslides/agenda/templates/agenda/item_row.html b/openslides/agenda/templates/agenda/item_row.html index 386f00ee8..0b82a1bd7 100644 --- a/openslides/agenda/templates/agenda/item_row.html +++ b/openslides/agenda/templates/agenda/item_row.html @@ -1,70 +1,71 @@ {% load i18n %} {% load tags %} - - {% if perms.agenda.can_manage_agenda %} - - - - {% else %} - - - - {% endif %} - - - {% for p in item.get_ancestors %} -
 
- {% endfor %} - {% if perms.agenda.can_manage_agenda %} -
- {% endif %} - {% if item.type == item.ORGANIZATIONAL_ITEM %}[{% endif %}{{ item }}{% if item.type == item.ORGANIZATIONAL_ITEM %}]{% endif %} - {{ item.get_title_supplement|safe }} - - {% if perms.agenda.can_manage_agenda %} - - {{ item.comment|first_line }} - - {% endif %} - {% if perms.agenda.can_see_orga_items %} - - {% if item.duration %} - {{ item.duration }}h {% if start and end %}{% endif %} - {% endif %} - - {% endif %} +
{% if perms.agenda.can_manage_agenda or perms.projector.can_manage_projector %} - +
{% if perms.projector.can_manage_projector %} - - + + {% endif %} {% if perms.agenda.can_manage_agenda %} - + - + - {% if not item.is_leaf_node %} - + {% if not node.is_leaf_node %} + {% endif %} {% endif %} - +
{% endif %} - - {% with form=item.weight_form %} + + {% if perms.agenda.can_see_orga_items %} +
+ {% if node.duration %} + {{ node.duration }}h {% if start and end %}{% endif %} + {% endif %} +
+ {% endif %} + + {% if perms.agenda.can_manage_agenda %} +
+ {{ node.comment|first_line }} +
+ {% endif %} + +
+ + +
+ +
+ {% if perms.agenda.can_manage_agenda %} + + + + {% else %} + + + + {% endif %} +
+ +
+ {% with form=node.weight_form %} {{ form.weight }} {{ form.self }} {{ form.parent }} {% endwith %} - - - + {% if node.type == node.ORGANIZATIONAL_ITEM %}[{% endif %}{{ node }}{% if node.type == node.ORGANIZATIONAL_ITEM %}]{% endif %} + {{ node.get_title_supplement|safe }} +
+
diff --git a/openslides/agenda/templates/agenda/overview.html b/openslides/agenda/templates/agenda/overview.html index 06557bac5..793a31be7 100644 --- a/openslides/agenda/templates/agenda/overview.html +++ b/openslides/agenda/templates/agenda/overview.html @@ -8,8 +8,8 @@ {% block title %}{{ block.super }} – {% trans "Agenda" %}{% endblock %} {% block header %} - + {% endblock %} {% block javascript %} @@ -17,26 +17,11 @@ {% if perms.agenda.can_manage_agenda %} + - - + {% endif %} + {% endblock %} {% block content %} @@ -60,7 +45,7 @@ PDF - + {% if perms.agenda.can_see_orga_items %} {% if start and end %} @@ -84,55 +69,57 @@
- - + + {% if perms.agenda.can_manage_agenda %} - + {% endif %} {% if perms.agenda.can_see_orga_items %} - + {% endif %} {% if perms.agenda.can_manage_agenda or perms.projector.can_manage_projector %} - + {% endif %} - - - + {% if perms.agenda.can_manage_agenda %} {% endif %} {% if perms.agenda.can_see_orga_items %} - + {% endif %} {% if perms.agenda.can_manage_agenda or perms.projector.can_manage_projector %} - {% endif %} - {% if items %} - {% for item in items %} - - {% include "agenda/item_row.html" %} - - {% endfor %} - {% else %} - - - - {% endif %} + {% if items %} +
    + {% recursetree items %} +
  1. + {% include "agenda/item_row.html" %} + {% if not node.is_leaf_node %} +
      + {{ children }} +
    + {% endif %} +
  2. + {% endrecursetree %} +
+ {% else %} +
{% trans "No items available." %}
+ {% endif %} {% endblock %} diff --git a/openslides/static/javascript/tabledrag.js b/openslides/static/javascript/tabledrag.js deleted file mode 100644 index 5d486ac57..000000000 --- a/openslides/static/javascript/tabledrag.js +++ /dev/null @@ -1,1431 +0,0 @@ -// $Id: tabledrag.js,v 1.42 2010/09/11 00:03:41 webchick Exp $ -var Drupal = Drupal || { 'settings': {}, 'behaviors': {}, 'locale': {} }; - -// Important OpenSlides note: -// This line is comment out to use jquery's 'hide' function for notification in agenda overview. -// jQuery.noConflict(); - -(function ($) { - -/** - * Attach all registered behaviors to a page element. - * - * Behaviors are event-triggered actions that attach to page elements, enhancing - * default non-Javascript UIs. Behaviors are registered in the Drupal.behaviors - * object using the method 'attach' and optionally also 'detach' as follows: - * @code - * Drupal.behaviors.behaviorName = { - * attach: function (context, settings) { - * ... - * }, - * detach: function (context, settings, trigger) { - * ... - * } - * }; - * @endcode - * - * Drupal.attachBehaviors is added below to the jQuery ready event and so - * runs on initial page load. Developers implementing AHAH/AJAX in their - * solutions should also call this function after new page content has been - * loaded, feeding in an element to be processed, in order to attach all - * behaviors to the new content. - * - * Behaviors should use - * @code - * $(selector).once('behavior-name', function () { - * ... - * }); - * @endcode - * to ensure the behavior is attached only once to a given element. (Doing so - * enables the reprocessing of given elements, which may be needed on occasion - * despite the ability to limit behavior attachment to a particular element.) - * - * @param context - * An element to attach behaviors to. If none is given, the document element - * is used. - * @param settings - * An object containing settings for the current context. If none given, the - * global Drupal.settings object is used. - */ -Drupal.attachBehaviors = function (context, settings) { - context = context || document; - settings = settings || Drupal.settings; - // Execute all of them. - $.each(Drupal.behaviors, function () { - if ($.isFunction(this.attach)) { - this.attach(context, settings); - } - }); -}; - -/** - * Detach registered behaviors from a page element. - * - * Developers implementing AHAH/AJAX in their solutions should call this - * function before page content is about to be removed, feeding in an element - * to be processed, in order to allow special behaviors to detach from the - * content. - * - * Such implementations should look for the class name that was added in their - * corresponding Drupal.behaviors.behaviorName.attach implementation, i.e. - * behaviorName-processed, to ensure the behavior is detached only from - * previously processed elements. - * - * @param context - * An element to detach behaviors from. If none is given, the document element - * is used. - * @param settings - * An object containing settings for the current context. If none given, the - * global Drupal.settings object is used. - * @param trigger - * A string containing what's causing the behaviors to be detached. The - * possible triggers are: - * - unload: (default) The context element is being removed from the DOM. - * - move: The element is about to be moved within the DOM (for example, - * during a tabledrag row swap). After the move is completed, - * Drupal.attachBehaviors() is called, so that the behavior can undo - * whatever it did in response to the move. Many behaviors won't need to - * do anything simply in response to the element being moved, but because - * IFRAME elements reload their "src" when being moved within the DOM, - * behaviors bound to IFRAME elements (like WYSIWYG editors) may need to - * take some action. - * - serialize: When an AJAX form is submitted, this is called with the - * form as the context. This provides every behavior within the form an - * opportunity to ensure that the field elements have correct content - * in them before the form is serialized. The canonical use-case is so - * that WYSIWYG editors can update the hidden textarea to which they are - * bound. - * - * @see Drupal.attachBehaviors - */ -Drupal.detachBehaviors = function (context, settings, trigger) { - context = context || document; - settings = settings || Drupal.settings; - trigger = trigger || 'unload'; - // Execute all of them. - $.each(Drupal.behaviors, function () { - if ($.isFunction(this.detach)) { - this.detach(context, settings, trigger); - } - }); -}; - - -/** - * Translate strings to the page language or a given language. - * - * See the documentation of the server-side t() function for further details. - * - * @param str - * A string containing the English string to translate. - * @param args - * An object of replacements pairs to make after translation. Incidences - * of any key in this array are replaced with the corresponding value. - * Based on the first character of the key, the value is escaped and/or themed: - * - !variable: inserted as is - * - @variable: escape plain text to HTML (Drupal.checkPlain) - * - %variable: escape text and theme as a placeholder for user-submitted - * content (checkPlain + Drupal.theme('placeholder')) - * @return - * The translated string. - */ -Drupal.t = function (str, args) { - // Fetch the localized version of the string. - if (Drupal.locale.strings && Drupal.locale.strings[str]) { - str = Drupal.locale.strings[str]; - } - - if (args) { - // Transform arguments before inserting them. - for (var key in args) { - switch (key.charAt(0)) { - // Escaped only. - case '@': - args[key] = Drupal.checkPlain(args[key]); - break; - // Pass-through. - case '!': - break; - // Escaped and placeholder. - case '%': - default: - args[key] = Drupal.theme('placeholder', args[key]); - break; - } - str = str.replace(key, args[key]); - } - } - return str; -}; - - - -/** - * Generate the themed representation of a Drupal object. - * - * All requests for themed output must go through this function. It examines - * the request and routes it to the appropriate theme function. If the current - * theme does not provide an override function, the generic theme function is - * called. - * - * For example, to retrieve the HTML for text that should be emphasized and - * displayed as a placeholder inside a sentence, call - * Drupal.theme('placeholder', text). - * - * @param func - * The name of the theme function to call. - * @param ... - * Additional arguments to pass along to the theme function. - * @return - * Any data the theme function returns. This could be a plain HTML string, - * but also a complex object. - */ -Drupal.theme = function (func) { - var args = Array.prototype.slice.apply(arguments, [1]); - - return (Drupal.theme[func] || Drupal.theme.prototype[func]).apply(this, args); -}; - -/** - * Freeze the current body height (as minimum height). Used to prevent - * unnecessary upwards scrolling when doing DOM manipulations. - */ -Drupal.freezeHeight = function () { - Drupal.unfreezeHeight(); - $('
').css({ - position: 'absolute', - top: '0px', - left: '0px', - width: '1px', - height: $('body').css('height') - }).appendTo('body'); -}; - -/** - * Unfreeze the body height. - */ -Drupal.unfreezeHeight = function () { - $('#freeze-height').remove(); -}; - - - - -/** - * Additions to jQuery.support. - */ -$(function () { - /** - * Boolean indicating whether or not position:fixed is supported. - */ - if (jQuery.support.positionFixed === undefined) { - var el = $('
').appendTo(document.body); - jQuery.support.positionFixed = el[0].offsetTop === 10; - el.remove(); - } -}); - -//Attach all behaviors. -$(function () { - Drupal.attachBehaviors(document, Drupal.settings); -}); - -/** - * The default themes. - */ -Drupal.theme.prototype = { - - /** - * Formats text for emphasized display in a placeholder inside a sentence. - * - * @param str - * The text to format (plain-text). - * @return - * The formatted text (html). - */ - placeholder: function (str) { - return '' + Drupal.checkPlain(str) + ''; - } -}; - -})(jQuery); - -(function ($) { - -/** - * Drag and drop table rows with field manipulation. - * - * Using the drupal_add_tabledrag() function, any table with weights or parent - * relationships may be made into draggable tables. Columns containing a field - * may optionally be hidden, providing a better user experience. - * - * Created tableDrag instances may be modified with custom behaviors by - * overriding the .onDrag, .onDrop, .row.onSwap, and .row.onIndent methods. - * See blocks.js for an example of adding additional functionality to tableDrag. - */ -Drupal.behaviors.tableDrag = { - attach: function (context, settings) { - for (var base in settings.tableDrag) { - $('#' + base, context).once('tabledrag', function () { - // Create the new tableDrag instance. Save in the Drupal variable - // to allow other scripts access to the object. - Drupal.tableDrag[base] = new Drupal.tableDrag(this, settings.tableDrag[base]); - }); - } - } -}; - -/** - * Constructor for the tableDrag object. Provides table and field manipulation. - * - * @param table - * DOM object for the table to be made draggable. - * @param tableSettings - * Settings for the table added via drupal_add_dragtable(). - */ -Drupal.tableDrag = function (table, tableSettings) { - var self = this; - - // Required object variables. - this.table = table; - this.tableSettings = tableSettings; - this.dragObject = null; // Used to hold information about a current drag operation. - this.rowObject = null; // Provides operations for row manipulation. - this.oldRowElement = null; // Remember the previous element. - this.oldY = 0; // Used to determine up or down direction from last mouse move. - this.changed = false; // Whether anything in the entire table has changed. - this.maxDepth = 0; // Maximum amount of allowed parenting. - this.rtl = $(this.table).css('direction') == 'rtl' ? -1 : 1; // Direction of the table. - - // Configure the scroll settings. - this.scrollSettings = { amount: 4, interval: 50, trigger: 70 }; - this.scrollInterval = null; - this.scrollY = 0; - this.windowHeight = 0; - - // Check this table's settings to see if there are parent relationships in - // this table. For efficiency, large sections of code can be skipped if we - // don't need to track horizontal movement and indentations. - this.indentEnabled = false; - for (var group in tableSettings) { - for (var n in tableSettings[group]) { - if (tableSettings[group][n].relationship == 'parent') { - this.indentEnabled = true; - } - if (tableSettings[group][n].limit > 0) { - this.maxDepth = tableSettings[group][n].limit; - } - } - } - if (this.indentEnabled) { - this.indentCount = 1; // Total width of indents, set in makeDraggable. - // Find the width of indentations to measure mouse movements against. - // Because the table doesn't need to start with any indentations, we - // manually append 2 indentations in the first draggable row, measure - // the offset, then remove. - var indent = Drupal.theme('tableDragIndentation'); - var testRow = $('').addClass('draggable').appendTo(table); - var testCell = $('').appendTo(testRow).prepend(indent).prepend(indent); - this.indentAmount = $('.indentation', testCell).get(1).offsetLeft - $('.indentation', testCell).get(0).offsetLeft; - testRow.remove(); - } - - // Make each applicable row draggable. - // Match immediate children of the parent element to allow nesting. - $('> tr.draggable, > tbody > tr.draggable', table).each(function () { self.makeDraggable(this); }); - - // Add a link before the table for users to show or hide weight columns. - $(table).before($('') - .attr('title', Drupal.t('Re-order rows by numerical weight instead of dragging.')) - .click(function () { - if ($.cookie('Drupal.tableDrag.showWeight') == 1) { - self.hideColumns(); - } - else { - self.showColumns(); - } - return false; - }) - .wrap('
') - .parent() - ); - - // Initialize the specified columns (for example, weight or parent columns) - // to show or hide according to user preference. This aids accessibility - // so that, e.g., screen reader users can choose to enter weight values and - // manipulate form elements directly, rather than using drag-and-drop.. - self.initColumns(); - - // Add mouse bindings to the document. The self variable is passed along - // as event handlers do not have direct access to the tableDrag object. - $(document).bind('mousemove', function (event) { return self.dragRow(event, self); }); - $(document).bind('mouseup', function (event) { return self.dropRow(event, self); }); -}; - -/** - * Initialize columns containing form elements to be hidden by default, - * according to the settings for this tableDrag instance. - * - * Identify and mark each cell with a CSS class so we can easily toggle - * show/hide it. Finally, hide columns if user does not have a - * 'Drupal.tableDrag.showWeight' cookie. - */ -Drupal.tableDrag.prototype.initColumns = function () { - for (var group in this.tableSettings) { - // Find the first field in this group. - for (var d in this.tableSettings[group]) { - var field = $('.' + this.tableSettings[group][d].target + ':first', this.table); - if (field.size() && this.tableSettings[group][d].hidden) { - var hidden = this.tableSettings[group][d].hidden; - var cell = field.parents('td:eq(1)'); - break; - } - } - - // Mark the column containing this field so it can be hidden. - if (hidden && cell[0] && cell.css('display') != 'none') { - // Add 1 to our indexes. The nth-child selector is 1 based, not 0 based. - // Match immediate children of the parent element to allow nesting. - var columnIndex = $('> td', cell.parent()).index(cell.get(0)) + 1; - var headerIndex = $('> td:not(:hidden)', cell.parent()).index(cell.get(0)) + 1; - $('> thead > tr, > tbody > tr, > tr', this.table).each(function (){ - var row = $(this); - var parentTag = row.parent().get(0).tagName.toLowerCase(); - var index = (parentTag == 'thead') ? headerIndex : columnIndex; - - // Adjust the index to take into account colspans. - row.children().each(function (n) { - if (n < index) { - index -= (this.colSpan && this.colSpan > 1) ? this.colSpan - 1 : 0; - } - }); - if (index > 0) { - cell = row.children(':nth-child(' + index + ')'); - if (cell[0].colSpan > 1) { - // If this cell has a colspan, mark it so we can reduce the colspan. - $(cell[0]).addClass('tabledrag-has-colspan'); - } - else { - // Mark this cell so we can hide it. - $(cell[0]).addClass('tabledrag-hide'); - } - } - }); - } - } - - // Now hide cells and reduce colspans unless cookie indicates previous choice. - // Set a cookie if it is not already present. - if ($.cookie('Drupal.tableDrag.showWeight') === null) { - $.cookie('Drupal.tableDrag.showWeight', 0, { - path: Drupal.settings.basePath, - // The cookie expires in one year. - expires: 365 - }); - this.hideColumns(); - } - // Check cookie value and show/hide weight columns accordingly. - else { - if ($.cookie('Drupal.tableDrag.showWeight') == 1) { - this.showColumns(); - } - else { - this.hideColumns(); - } - } -}; - -/** - * Hide the columns containing weight/parent form elements. - * Undo showColumns(). - */ -Drupal.tableDrag.prototype.hideColumns = function () { - // Hide weight/parent cells and headers. - $('.tabledrag-hide', 'table.tabledrag-processed').css('display', 'none'); - // Show TableDrag handles. - $('.tabledrag-handle', 'table.tabledrag-processed').css('display', ''); - // Reduce the colspan of any effected multi-span columns. - $('.tabledrag-has-colspan', 'table.tabledrag-processed').each(function () { - this.colSpan = this.colSpan - 1; - }); - // Change link text. -// $('.tabledrag-toggle-weight').text(Drupal.t('Show row weights')); - // Change cookie. - $.cookie('Drupal.tableDrag.showWeight', 0, { - path: Drupal.settings.basePath, - // The cookie expires in one year. - expires: 365 - }); -}; - -/** - * Show the columns containing weight/parent form elements - * Undo hideColumns(). - */ -Drupal.tableDrag.prototype.showColumns = function () { - // Show weight/parent cells and headers. - $('.tabledrag-hide', 'table.tabledrag-processed').css('display', ''); - // Hide TableDrag handles. - $('.tabledrag-handle', 'table.tabledrag-processed').css('display', 'none'); - // Increase the colspan for any columns where it was previously reduced. - $('.tabledrag-has-colspan', 'table.tabledrag-processed').each(function () { - this.colSpan = this.colSpan + 1; - }); - // Change link text. - $('.tabledrag-toggle-weight').text(Drupal.t('Hide row weights')); - // Change cookie. - $.cookie('Drupal.tableDrag.showWeight', 1, { - path: Drupal.settings.basePath, - // The cookie expires in one year. - expires: 365 - }); -}; - -/** - * Find the target used within a particular row and group. - */ -Drupal.tableDrag.prototype.rowSettings = function (group, row) { - var field = $('.' + group, row); - for (var delta in this.tableSettings[group]) { - var targetClass = this.tableSettings[group][delta].target; - if (field.is('.' + targetClass)) { - // Return a copy of the row settings. - var rowSettings = {}; - for (var n in this.tableSettings[group][delta]) { - rowSettings[n] = this.tableSettings[group][delta][n]; - } - return rowSettings; - } - } -}; - -/** - * Take an item and add event handlers to make it become draggable. - */ -Drupal.tableDrag.prototype.makeDraggable = function (item) { - var self = this; - - // Create the handle. - var handle = $('
 
').attr('title', Drupal.t('Drag to re-order')); - // Insert the handle after indentations (if any). - if ($('td:eq(1) .indentation:last', item).length) { - $('td:eq(1) .indentation:last', item).after(handle); - // Update the total width of indentation in this entire table. - self.indentCount = Math.max($('.indentation', item).size(), self.indentCount); - } - else { - $('td:eq(1)', item).prepend(handle); - } - - // Add hover action for the handle. - handle.hover(function () { - self.dragObject == null ? $(this).addClass('tabledrag-handle-hover') : null; - }, function () { - self.dragObject == null ? $(this).removeClass('tabledrag-handle-hover') : null; - }); - - // Add the mousedown action for the handle. - handle.mousedown(function (event) { - // Create a new dragObject recording the event information. - self.dragObject = {}; - self.dragObject.initMouseOffset = self.getMouseOffset(item, event); - self.dragObject.initMouseCoords = self.mouseCoords(event); - if (self.indentEnabled) { - self.dragObject.indentMousePos = self.dragObject.initMouseCoords; - } - - // If there's a lingering row object from the keyboard, remove its focus. - if (self.rowObject) { - $('a.tabledrag-handle', self.rowObject.element).blur(); - } - - // Create a new rowObject for manipulation of this row. - self.rowObject = new self.row(item, 'mouse', self.indentEnabled, self.maxDepth, true); - - // Save the position of the table. - self.table.topY = $(self.table).offset().top; - self.table.bottomY = self.table.topY + self.table.offsetHeight; - - // Add classes to the handle and row. - $(this).addClass('tabledrag-handle-hover'); - $(item).addClass('drag'); - - // Set the document to use the move cursor during drag. - $('body').addClass('drag'); - if (self.oldRowElement) { - $(self.oldRowElement).removeClass('drag-previous'); - } - - // Hack for IE6 that flickers uncontrollably if select lists are moved. - if (navigator.userAgent.indexOf('MSIE 6.') != -1) { - $('select', this.table).css('display', 'none'); - } - - // Hack for Konqueror, prevent the blur handler from firing. - // Konqueror always gives links focus, even after returning false on mousedown. - self.safeBlur = false; - - // Call optional placeholder function. - self.onDrag(); - return false; - }); - - // Prevent the anchor tag from jumping us to the top of the page. - handle.click(function () { - return false; - }); - - // Similar to the hover event, add a class when the handle is focused. - handle.focus(function () { - $(this).addClass('tabledrag-handle-hover'); - self.safeBlur = true; - }); - - // Remove the handle class on blur and fire the same function as a mouseup. - handle.blur(function (event) { - $(this).removeClass('tabledrag-handle-hover'); - if (self.rowObject && self.safeBlur) { - self.dropRow(event, self); - } - }); - - // Add arrow-key support to the handle. - handle.keydown(function (event) { - // If a rowObject doesn't yet exist and this isn't the tab key. - if (event.keyCode != 9 && !self.rowObject) { - self.rowObject = new self.row(item, 'keyboard', self.indentEnabled, self.maxDepth, true); - } - - var keyChange = false; - switch (event.keyCode) { - case 37: // Left arrow. - case 63234: // Safari left arrow. - keyChange = true; - self.rowObject.indent(-1 * self.rtl); - break; - case 38: // Up arrow. - case 63232: // Safari up arrow. - var previousRow = $(self.rowObject.element).prev('tr').get(0); - while (previousRow && $(previousRow).is(':hidden')) { - previousRow = $(previousRow).prev('tr').get(0); - } - if (previousRow) { - self.safeBlur = false; // Do not allow the onBlur cleanup. - self.rowObject.direction = 'up'; - keyChange = true; - - if ($(item).is('.tabledrag-root')) { - // Swap with the previous top-level row. - var groupHeight = 0; - while (previousRow && $('.indentation', previousRow).size()) { - previousRow = $(previousRow).prev('tr').get(0); - groupHeight += $(previousRow).is(':hidden') ? 0 : previousRow.offsetHeight; - } - if (previousRow) { - self.rowObject.swap('before', previousRow); - // No need to check for indentation, 0 is the only valid one. - window.scrollBy(0, -groupHeight); - } - } - else if (self.table.tBodies[0].rows[0] != previousRow || $(previousRow).is('.draggable')) { - // Swap with the previous row (unless previous row is the first one - // and undraggable). - self.rowObject.swap('before', previousRow); - self.rowObject.interval = null; - self.rowObject.indent(0); - window.scrollBy(0, -parseInt(item.offsetHeight, 10)); - } - handle.get(0).focus(); // Regain focus after the DOM manipulation. - } - break; - case 39: // Right arrow. - case 63235: // Safari right arrow. - keyChange = true; - self.rowObject.indent(1 * self.rtl); - break; - case 40: // Down arrow. - case 63233: // Safari down arrow. - var nextRow = $(self.rowObject.group).filter(':last').next('tr').get(0); - while (nextRow && $(nextRow).is(':hidden')) { - nextRow = $(nextRow).next('tr').get(0); - } - if (nextRow) { - self.safeBlur = false; // Do not allow the onBlur cleanup. - self.rowObject.direction = 'down'; - keyChange = true; - - if ($(item).is('.tabledrag-root')) { - // Swap with the next group (necessarily a top-level one). - var groupHeight = 0; - nextGroup = new self.row(nextRow, 'keyboard', self.indentEnabled, self.maxDepth, false); - if (nextGroup) { - $(nextGroup.group).each(function () { - groupHeight += $(this).is(':hidden') ? 0 : this.offsetHeight; - }); - nextGroupRow = $(nextGroup.group).filter(':last').get(0); - self.rowObject.swap('after', nextGroupRow); - // No need to check for indentation, 0 is the only valid one. - window.scrollBy(0, parseInt(groupHeight, 10)); - } - } - else { - // Swap with the next row. - self.rowObject.swap('after', nextRow); - self.rowObject.interval = null; - self.rowObject.indent(0); - window.scrollBy(0, parseInt(item.offsetHeight, 10)); - } - handle.get(0).focus(); // Regain focus after the DOM manipulation. - } - break; - } - - if (self.rowObject && self.rowObject.changed == true) { - $(item).addClass('drag'); - if (self.oldRowElement) { - $(self.oldRowElement).removeClass('drag-previous'); - } - self.oldRowElement = item; - self.restripeTable(); - self.onDrag(); - } - - // Returning false if we have an arrow key to prevent scrolling. - if (keyChange) { - return false; - } - }); - - // Compatibility addition, return false on keypress to prevent unwanted scrolling. - // IE and Safari will suppress scrolling on keydown, but all other browsers - // need to return false on keypress. http://www.quirksmode.org/js/keys.html - handle.keypress(function (event) { - switch (event.keyCode) { - case 37: // Left arrow. - case 38: // Up arrow. - case 39: // Right arrow. - case 40: // Down arrow. - return false; - } - }); -}; - -/** - * Mousemove event handler, bound to document. - */ -Drupal.tableDrag.prototype.dragRow = function (event, self) { - if (self.dragObject) { - self.currentMouseCoords = self.mouseCoords(event); - - var y = self.currentMouseCoords.y - self.dragObject.initMouseOffset.y; - var x = self.currentMouseCoords.x - self.dragObject.initMouseOffset.x; - - // Check for row swapping and vertical scrolling. - if (y != self.oldY) { - self.rowObject.direction = y > self.oldY ? 'down' : 'up'; - self.oldY = y; // Update the old value. - - // Check if the window should be scrolled (and how fast). - var scrollAmount = self.checkScroll(self.currentMouseCoords.y); - // Stop any current scrolling. - clearInterval(self.scrollInterval); - // Continue scrolling if the mouse has moved in the scroll direction. - if (scrollAmount > 0 && self.rowObject.direction == 'down' || scrollAmount < 0 && self.rowObject.direction == 'up') { - self.setScroll(scrollAmount); - } - - // If we have a valid target, perform the swap and restripe the table. - var currentRow = self.findDropTargetRow(x, y); - if (currentRow) { - if (self.rowObject.direction == 'down') { - self.rowObject.swap('after', currentRow, self); - } - else { - self.rowObject.swap('before', currentRow, self); - } - self.restripeTable(); - } - } - - // Similar to row swapping, handle indentations. - if (self.indentEnabled) { - var xDiff = self.currentMouseCoords.x - self.dragObject.indentMousePos.x; - // Set the number of indentations the mouse has been moved left or right. - var indentDiff = Math.round(xDiff / self.indentAmount * self.rtl); - // Indent the row with our estimated diff, which may be further - // restricted according to the rows around this row. - var indentChange = self.rowObject.indent(indentDiff); - // Update table and mouse indentations. - self.dragObject.indentMousePos.x += self.indentAmount * indentChange * self.rtl; - self.indentCount = Math.max(self.indentCount, self.rowObject.indents); - } - - return false; - } -}; - -/** - * Mouseup event handler, bound to document. - * Blur event handler, bound to drag handle for keyboard support. - */ -Drupal.tableDrag.prototype.dropRow = function (event, self) { - // Drop row functionality shared between mouseup and blur events. - if (self.rowObject != null) { - var droppedRow = self.rowObject.element; - // The row is already in the right place so we just release it. - if (self.rowObject.changed == true) { - // Update the fields in the dropped row. - self.updateFields(droppedRow); - - // If a setting exists for affecting the entire group, update all the - // fields in the entire dragged group. - for (var group in self.tableSettings) { - var rowSettings = self.rowSettings(group, droppedRow); - if (rowSettings.relationship == 'group') { - for (var n in self.rowObject.children) { - self.updateField(self.rowObject.children[n], group); - } - } - } - - self.rowObject.markChanged(); - if (self.changed == false) { - $(Drupal.theme('tableDragChangedWarning')).insertBefore(self.table).hide().fadeIn('slow'); - self.changed = true; - } - } - - if (self.indentEnabled) { - self.rowObject.removeIndentClasses(); - } - if (self.oldRowElement) { - $(self.oldRowElement).removeClass('drag-previous'); - } - $(droppedRow).removeClass('drag').addClass('drag-previous'); - self.oldRowElement = droppedRow; - self.onDrop(); - self.rowObject = null; - } - - // Functionality specific only to mouseup event. - if (self.dragObject != null) { - $('.tabledrag-handle', droppedRow).removeClass('tabledrag-handle-hover'); - - self.dragObject = null; - $('body').removeClass('drag'); - clearInterval(self.scrollInterval); - - // Hack for IE6 that flickers uncontrollably if select lists are moved. - if (navigator.userAgent.indexOf('MSIE 6.') != -1) { - $('select', this.table).css('display', 'block'); - } - } -}; - -/** - * Get the mouse coordinates from the event (allowing for browser differences). - */ -Drupal.tableDrag.prototype.mouseCoords = function (event) { - if (event.pageX || event.pageY) { - return { x: event.pageX, y: event.pageY }; - } - return { - x: event.clientX + document.body.scrollLeft - document.body.clientLeft, - y: event.clientY + document.body.scrollTop - document.body.clientTop - }; -}; - -/** - * Given a target element and a mouse event, get the mouse offset from that - * element. To do this we need the element's position and the mouse position. - */ -Drupal.tableDrag.prototype.getMouseOffset = function (target, event) { - var docPos = $(target).offset(); - var mousePos = this.mouseCoords(event); - return { x: mousePos.x - docPos.left, y: mousePos.y - docPos.top }; -}; - -/** - * Find the row the mouse is currently over. This row is then taken and swapped - * with the one being dragged. - * - * @param x - * The x coordinate of the mouse on the page (not the screen). - * @param y - * The y coordinate of the mouse on the page (not the screen). - */ -Drupal.tableDrag.prototype.findDropTargetRow = function (x, y) { - var rows = $(this.table.tBodies[0].rows).not(':hidden'); - for (var n = 0; n < rows.length; n++) { - var row = rows[n]; - var indentDiff = 0; - var rowY = $(row).offset().top; - // Because Safari does not report offsetHeight on table rows, but does on - // table cells, grab the firstChild of the row and use that instead. - // http://jacob.peargrove.com/blog/2006/technical/table-row-offsettop-bug-in-safari. - if (row.offsetHeight == 0) { - var rowHeight = parseInt(row.firstChild.offsetHeight, 10) / 2; - } - // Other browsers. - else { - var rowHeight = parseInt(row.offsetHeight, 10) / 2; - } - - // Because we always insert before, we need to offset the height a bit. - if ((y > (rowY - rowHeight)) && (y < (rowY + rowHeight))) { - if (this.indentEnabled) { - // Check that this row is not a child of the row being dragged. - for (var n in this.rowObject.group) { - if (this.rowObject.group[n] == row) { - return null; - } - } - } - else { - // Do not allow a row to be swapped with itself. - if (row == this.rowObject.element) { - return null; - } - } - - // Check that swapping with this row is allowed. - if (!this.rowObject.isValidSwap(row)) { - return null; - } - - // We may have found the row the mouse just passed over, but it doesn't - // take into account hidden rows. Skip backwards until we find a draggable - // row. - while ($(row).is(':hidden') && $(row).prev('tr').is(':hidden')) { - row = $(row).prev('tr').get(0); - } - return row; - } - } - return null; -}; - -/** - * After the row is dropped, update the table fields according to the settings - * set for this table. - * - * @param changedRow - * DOM object for the row that was just dropped. - */ -Drupal.tableDrag.prototype.updateFields = function (changedRow) { - for (var group in this.tableSettings) { - // Each group may have a different setting for relationship, so we find - // the source rows for each separately. - this.updateField(changedRow, group); - } -}; - -/** - * After the row is dropped, update a single table field according to specific - * settings. - * - * @param changedRow - * DOM object for the row that was just dropped. - * @param group - * The settings group on which field updates will occur. - */ -Drupal.tableDrag.prototype.updateField = function (changedRow, group) { - var rowSettings = this.rowSettings(group, changedRow); - - // Set the row as its own target. - if (rowSettings.relationship == 'self' || rowSettings.relationship == 'group') { - var sourceRow = changedRow; - } - // Siblings are easy, check previous and next rows. - else if (rowSettings.relationship == 'sibling') { - var previousRow = $(changedRow).prev('tr').get(0); - var nextRow = $(changedRow).next('tr').get(0); - var sourceRow = changedRow; - if ($(previousRow).is('.draggable') && $('.' + group, previousRow).length) { - if (this.indentEnabled) { - if ($('.indentations', previousRow).size() == $('.indentations', changedRow)) { - sourceRow = previousRow; - } - } - else { - sourceRow = previousRow; - } - } - else if ($(nextRow).is('.draggable') && $('.' + group, nextRow).length) { - if (this.indentEnabled) { - if ($('.indentations', nextRow).size() == $('.indentations', changedRow)) { - sourceRow = nextRow; - } - } - else { - sourceRow = nextRow; - } - } - } - // Parents, look up the tree until we find a field not in this group. - // Go up as many parents as indentations in the changed row. - else if (rowSettings.relationship == 'parent') { - var previousRow = $(changedRow).prev('tr'); - while (previousRow.length && $('.indentation', previousRow).length >= this.rowObject.indents) { - previousRow = previousRow.prev('tr'); - } - // If we found a row. - if (previousRow.length) { - sourceRow = previousRow[0]; - } - // Otherwise we went all the way to the left of the table without finding - // a parent, meaning this item has been placed at the root level. - else { - // Use the first row in the table as source, because it's guaranteed to - // be at the root level. Find the first item, then compare this row - // against it as a sibling. - sourceRow = $(this.table).find('tr.draggable:first').get(0); - if (sourceRow == this.rowObject.element) { - sourceRow = $(this.rowObject.group[this.rowObject.group.length - 1]).next('tr.draggable').get(0); - } - var useSibling = true; - } - } - - // Because we may have moved the row from one category to another, - // take a look at our sibling and borrow its sources and targets. - this.copyDragClasses(sourceRow, changedRow, group); - rowSettings = this.rowSettings(group, changedRow); - - // In the case that we're looking for a parent, but the row is at the top - // of the tree, copy our sibling's values. - if (useSibling) { - rowSettings.relationship = 'sibling'; - rowSettings.source = rowSettings.target; - } - - var targetClass = '.' + rowSettings.target; - var targetElement = $(targetClass, changedRow).get(0); - - // Check if a target element exists in this row. - if (targetElement) { - var sourceClass = '.' + rowSettings.source; - var sourceElement = $(sourceClass, sourceRow).get(0); - switch (rowSettings.action) { - case 'depth': - // Get the depth of the target row. - targetElement.value = $('.indentation', $(sourceElement).parents('tr:first')).size(); - break; - case 'match': - // Update the value. - targetElement.value = sourceElement.value; - break; - case 'order': - var siblings = this.rowObject.findSiblings(rowSettings); - if ($(targetElement).is('select')) { - // Get a list of acceptable values. - var values = []; - $('option', targetElement).each(function () { - values.push(this.value); - }); - var maxVal = values[values.length - 1]; - // Populate the values in the siblings. - $(targetClass, siblings).each(function () { - // If there are more items than possible values, assign the maximum value to the row. - if (values.length > 0) { - this.value = values.shift(); - } - else { - this.value = maxVal; - } - }); - } - else { - // Assume a numeric input field. - var weight = parseInt($(targetClass, siblings[0]).val(), 10) || 0; - $(targetClass, siblings).each(function () { - this.value = weight; - weight++; - }); - } - break; - } - } -}; - -/** - * Copy all special tableDrag classes from one row's form elements to a - * different one, removing any special classes that the destination row - * may have had. - */ -Drupal.tableDrag.prototype.copyDragClasses = function (sourceRow, targetRow, group) { - var sourceElement = $('.' + group, sourceRow); - var targetElement = $('.' + group, targetRow); - if (sourceElement.length && targetElement.length) { - targetElement[0].className = sourceElement[0].className; - } -}; - -Drupal.tableDrag.prototype.checkScroll = function (cursorY) { - var de = document.documentElement; - var b = document.body; - - var windowHeight = this.windowHeight = window.innerHeight || (de.clientHeight && de.clientWidth != 0 ? de.clientHeight : b.offsetHeight); - var scrollY = this.scrollY = (document.all ? (!de.scrollTop ? b.scrollTop : de.scrollTop) : (window.pageYOffset ? window.pageYOffset : window.scrollY)); - var trigger = this.scrollSettings.trigger; - var delta = 0; - - // Return a scroll speed relative to the edge of the screen. - if (cursorY - scrollY > windowHeight - trigger) { - delta = trigger / (windowHeight + scrollY - cursorY); - delta = (delta > 0 && delta < trigger) ? delta : trigger; - return delta * this.scrollSettings.amount; - } - else if (cursorY - scrollY < trigger) { - delta = trigger / (cursorY - scrollY); - delta = (delta > 0 && delta < trigger) ? delta : trigger; - return -delta * this.scrollSettings.amount; - } -}; - -Drupal.tableDrag.prototype.setScroll = function (scrollAmount) { - var self = this; - - this.scrollInterval = setInterval(function () { - // Update the scroll values stored in the object. - self.checkScroll(self.currentMouseCoords.y); - var aboveTable = self.scrollY > self.table.topY; - var belowTable = self.scrollY + self.windowHeight < self.table.bottomY; - if (scrollAmount > 0 && belowTable || scrollAmount < 0 && aboveTable) { - window.scrollBy(0, scrollAmount); - } - }, this.scrollSettings.interval); -}; - -Drupal.tableDrag.prototype.restripeTable = function () { - // :even and :odd are reversed because jQuery counts from 0 and - // we count from 1, so we're out of sync. - // Match immediate children of the parent element to allow nesting. - $('> tbody > tr.draggable:visible, > tr.draggable:visible', this.table) - .removeClass('odd even') - .filter(':odd').addClass('even').end() - .filter(':even').addClass('odd'); -}; - -/** - * Stub function. Allows a custom handler when a row begins dragging. - */ -Drupal.tableDrag.prototype.onDrag = function () { - return null; -}; - -/** - * Stub function. Allows a custom handler when a row is dropped. - */ -Drupal.tableDrag.prototype.onDrop = function () { - return null; -}; - -/** - * Constructor to make a new object to manipulate a table row. - * - * @param tableRow - * The DOM element for the table row we will be manipulating. - * @param method - * The method in which this row is being moved. Either 'keyboard' or 'mouse'. - * @param indentEnabled - * Whether the containing table uses indentations. Used for optimizations. - * @param maxDepth - * The maximum amount of indentations this row may contain. - * @param addClasses - * Whether we want to add classes to this row to indicate child relationships. - */ -Drupal.tableDrag.prototype.row = function (tableRow, method, indentEnabled, maxDepth, addClasses) { - this.element = tableRow; - this.method = method; - this.group = [tableRow]; - this.groupDepth = $('.indentation', tableRow).size(); - this.changed = false; - this.table = $(tableRow).parents('table:first').get(0); - this.indentEnabled = indentEnabled; - this.maxDepth = maxDepth; - this.direction = ''; // Direction the row is being moved. - - if (this.indentEnabled) { - this.indents = $('.indentation', tableRow).size(); - this.children = this.findChildren(addClasses); - this.group = $.merge(this.group, this.children); - // Find the depth of this entire group. - for (var n = 0; n < this.group.length; n++) { - this.groupDepth = Math.max($('.indentation', this.group[n]).size(), this.groupDepth); - } - } -}; - -/** - * Find all children of rowObject by indentation. - * - * @param addClasses - * Whether we want to add classes to this row to indicate child relationships. - */ -Drupal.tableDrag.prototype.row.prototype.findChildren = function (addClasses) { - var parentIndentation = this.indents; - var currentRow = $(this.element, this.table).next('tr.draggable'); - var rows = []; - var child = 0; - while (currentRow.length) { - var rowIndentation = $('.indentation', currentRow).length; - // A greater indentation indicates this is a child. - if (rowIndentation > parentIndentation) { - child++; - rows.push(currentRow[0]); - if (addClasses) { - $('.indentation', currentRow).each(function (indentNum) { - if (child == 1 && (indentNum == parentIndentation)) { - $(this).addClass('tree-child-first'); - } - if (indentNum == parentIndentation) { - $(this).addClass('tree-child'); - } - else if (indentNum > parentIndentation) { - $(this).addClass('tree-child-horizontal'); - } - }); - } - } - else { - break; - } - currentRow = currentRow.next('tr.draggable'); - } - if (addClasses && rows.length) { - $('.indentation:nth-child(' + (parentIndentation + 1) + ')', rows[rows.length - 1]).addClass('tree-child-last'); - } - return rows; -}; - -/** - * Ensure that two rows are allowed to be swapped. - * - * @param row - * DOM object for the row being considered for swapping. - */ -Drupal.tableDrag.prototype.row.prototype.isValidSwap = function (row) { - if (this.indentEnabled) { - var prevRow, nextRow; - if (this.direction == 'down') { - prevRow = row; - nextRow = $(row).next('tr').get(0); - } - else { - prevRow = $(row).prev('tr').get(0); - nextRow = row; - } - this.interval = this.validIndentInterval(prevRow, nextRow); - - // We have an invalid swap if the valid indentations interval is empty. - if (this.interval.min > this.interval.max) { - return false; - } - } - - // Do not let an un-draggable first row have anything put before it. - if (this.table.tBodies[0].rows[0] == row && $(row).is(':not(.draggable)')) { - return false; - } - - return true; -}; - -/** - * Perform the swap between two rows. - * - * @param position - * Whether the swap will occur 'before' or 'after' the given row. - * @param row - * DOM element what will be swapped with the row group. - */ -Drupal.tableDrag.prototype.row.prototype.swap = function (position, row) { - Drupal.detachBehaviors(this.group, Drupal.settings, 'move'); - $(row)[position](this.group); - Drupal.attachBehaviors(this.group, Drupal.settings); - this.changed = true; - this.onSwap(row); -}; - -/** - * Determine the valid indentations interval for the row at a given position - * in the table. - * - * @param prevRow - * DOM object for the row before the tested position - * (or null for first position in the table). - * @param nextRow - * DOM object for the row after the tested position - * (or null for last position in the table). - */ -Drupal.tableDrag.prototype.row.prototype.validIndentInterval = function (prevRow, nextRow) { - var minIndent, maxIndent; - - // Minimum indentation: - // Do not orphan the next row. - minIndent = nextRow ? $('.indentation', nextRow).size() : 0; - - // Maximum indentation: - if (!prevRow || $(prevRow).is(':not(.draggable)') || $(this.element).is('.tabledrag-root')) { - // Do not indent: - // - the first row in the table, - // - rows dragged below a non-draggable row, - // - 'root' rows. - maxIndent = 0; - } - else { - // Do not go deeper than as a child of the previous row. - maxIndent = $('.indentation', prevRow).size() + ($(prevRow).is('.tabledrag-leaf') ? 0 : 1); - // Limit by the maximum allowed depth for the table. - if (this.maxDepth) { - maxIndent = Math.min(maxIndent, this.maxDepth - (this.groupDepth - this.indents)); - } - } - - return { 'min': minIndent, 'max': maxIndent }; -}; - -/** - * Indent a row within the legal bounds of the table. - * - * @param indentDiff - * The number of additional indentations proposed for the row (can be - * positive or negative). This number will be adjusted to nearest valid - * indentation level for the row. - */ -Drupal.tableDrag.prototype.row.prototype.indent = function (indentDiff) { - // Determine the valid indentations interval if not available yet. - if (!this.interval) { - prevRow = $(this.element).prev('tr').get(0); - nextRow = $(this.group).filter(':last').next('tr').get(0); - this.interval = this.validIndentInterval(prevRow, nextRow); - } - - // Adjust to the nearest valid indentation. - var indent = this.indents + indentDiff; - indent = Math.max(indent, this.interval.min); - indent = Math.min(indent, this.interval.max); - indentDiff = indent - this.indents; - - for (var n = 1; n <= Math.abs(indentDiff); n++) { - // Add or remove indentations. - if (indentDiff < 0) { - $('.indentation:first', this.group).remove(); - this.indents--; - } - else { - $('td:eq(1)', this.group).prepend(Drupal.theme('tableDragIndentation')); - this.indents++; - } - } - if (indentDiff) { - // Update indentation for this row. - this.changed = true; - this.groupDepth += indentDiff; - this.onIndent(); - } - - return indentDiff; -}; - -/** - * Find all siblings for a row, either according to its subgroup or indentation. - * Note that the passed in row is included in the list of siblings. - * - * @param settings - * The field settings we're using to identify what constitutes a sibling. - */ -Drupal.tableDrag.prototype.row.prototype.findSiblings = function (rowSettings) { - var siblings = []; - var directions = ['prev', 'next']; - var rowIndentation = this.indents; - for (var d = 0; d < directions.length; d++) { - var checkRow = $(this.element)[directions[d]](); - while (checkRow.length) { - // Check that the sibling contains a similar target field. - if ($('.' + rowSettings.target, checkRow)) { - // Either add immediately if this is a flat table, or check to ensure - // that this row has the same level of indentation. - if (this.indentEnabled) { - var checkRowIndentation = $('.indentation', checkRow).length; - } - - if (!(this.indentEnabled) || (checkRowIndentation == rowIndentation)) { - siblings.push(checkRow[0]); - } - else if (checkRowIndentation < rowIndentation) { - // No need to keep looking for siblings when we get to a parent. - break; - } - } - else { - break; - } - checkRow = $(checkRow)[directions[d]](); - } - // Since siblings are added in reverse order for previous, reverse the - // completed list of previous siblings. Add the current row and continue. - if (directions[d] == 'prev') { - siblings.reverse(); - siblings.push(this.element); - } - } - return siblings; -}; - -/** - * Remove indentation helper classes from the current row group. - */ -Drupal.tableDrag.prototype.row.prototype.removeIndentClasses = function () { - for (var n in this.children) { - $('.indentation', this.children[n]) - .removeClass('tree-child') - .removeClass('tree-child-first') - .removeClass('tree-child-last') - .removeClass('tree-child-horizontal'); - } -}; - -/** - * Add an asterisk or other marker to the changed row. - */ -Drupal.tableDrag.prototype.row.prototype.markChanged = function () { - var marker = Drupal.theme('tableDragChangedMarker'); - var cell = $('td:eq(1)', this.element); - if ($('span.tabledrag-changed', cell).length == 0) { - cell.append(marker); - } -}; - -/** - * Stub function. Allows a custom handler when a row is indented. - */ -Drupal.tableDrag.prototype.row.prototype.onIndent = function () { - return null; -}; - -/** - * Stub function. Allows a custom handler when a row is swapped. - */ -Drupal.tableDrag.prototype.row.prototype.onSwap = function (swappedRow) { - return null; -}; - -Drupal.theme.prototype.tableDragChangedMarker = function () { - return '*'; -}; - -Drupal.theme.prototype.tableDragIndentation = function () { - return '
 
'; -}; - -Drupal.theme.prototype.tableDragChangedWarning = function () { - $('#changed-order-message').show("fast"); - - //message = gettext('Changes made in this table will not be saved until the form is submitted.'); - //return '
' + - // Drupal.theme('tableDragChangedMarker') + ' ' + message + '
'; -}; - -})(jQuery); diff --git a/openslides/static/styles/tabledrag.css b/openslides/static/styles/tabledrag.css deleted file mode 100644 index ed8946288..000000000 --- a/openslides/static/styles/tabledrag.css +++ /dev/null @@ -1,49 +0,0 @@ -/** - * TableDrag behavior. - * - * @see tabledrag.js - */ -body.drag { - cursor: move; -} -.draggable a.tabledrag-handle { - cursor: move; - float: left; /* LTR */ - padding-right: 0.5em ; /* LTR */ - text-decoration: none; -} -.draggable .tabledrag-changed { - padding-right: 0.2em; -} -a.tabledrag-handle:hover { - text-decoration: none; -} -a.tabledrag-handle .handle { - background: url("../img/draggable.png") no-repeat scroll 0 0 transparent; - height: 13px; - margin-top: 4px; - width: 13px; -} -a.tabledrag-handle-hover .handle { - background-position: 0 -20px; -} -div.indentation { - float: left; /* LTR */ - height: 1.7em; - margin: -0.4em 0.2em -0.4em -0.4em; /* LTR */ - padding: 0.42em 0 0.42em 0.6em; /* LTR */ - width: 10px; -} -div.tree-child { - background: url(../img/tree.png) no-repeat 11px center; /* LTR */ -} -div.tree-child-last { - background: url(../img/tree-bottom.png) no-repeat 11px center; /* LTR */ -} -div.tree-child-horizontal { - background: url(../img/tree.png) no-repeat -11px center; -} -.tabledrag-toggle-weight-wrapper { - text-align: right; /* LTR */ - display: none; -}