---- eventum-new/htdocs/ajax/order.php 2008-10-15 02:02:25.000000000 +0300
-+++ eventum-new/htdocs/ajax/order.php 2008-10-15 02:02:25.000000000 +0300
-@@ -0,0 +1,72 @@
+--- eventum-2.2/htdocs/ajax/order.php 1970-01-01 02:00:00.000000000 +0200
++++ eventum-2.2-order/htdocs/ajax/order.php 2009-10-12 22:10:36.429185594 +0300
+@@ -0,0 +1,69 @@
+<?
-+require_once(dirname(__FILE__) . '/../init.php');
-+require_once(APP_INC_PATH . "class.auth.php");
-+require_once(APP_INC_PATH . "class.issue.php");
++require_once dirname(__FILE__) . '/../../init.php';
+
+// check login
+if (!Auth::hasValidCookie(APP_COOKIE)) {
+}
+
+
-+
+if (!isset($_POST['before']) || !isset($_POST['after'])) {
+ exit;
+}
+ }
+ }
+}
---- eventum/htdocs/ajax/update.php 2008-10-15 01:46:20.000000000 +0300
-+++ eventum-new/htdocs/ajax/update.php 2008-10-15 02:02:25.000000000 +0300
-@@ -0,0 +1,30 @@
-+<?
-+require_once(dirname(__FILE__) . '/../init.php');
-+require_once(APP_INC_PATH . "class.auth.php");
-+require_once(APP_INC_PATH . "class.issue.php");
-+
-+// check login
-+if (!Auth::hasValidCookie(APP_COOKIE)) {
-+ exit;
+--- eventum-2.3.1/htdocs/css/style.css~ 2011-09-15 09:36:55.000000000 +0300
++++ eventum-2.3.1/htdocs/css/style.css 2011-09-15 09:38:23.668223576 +0300
+@@ -177,6 +177,24 @@
+ cursor: pointer;
+ }
+
++.tDnD_whileDrag td {
++ background-color: #ffffdd;
+}
-+
-+if (!is_numeric($_POST['issueID'])) {
-+ exit;
++.tDnD_whileDrag td {
++ border: 1px solid red;
+}
-+
-+switch ($_POST['fieldName']) {
-+ case 'expected_resolution_date':
-+ $day = (int)$_POST['day'];
-+ $month = (int)$_POST['month'];
-+ $year = (int)$_POST['year'];
-+ if (Issue::updateField($_POST['issueID'], $_POST['fieldName'], sprintf('%04d-%02d-%02d', $year, $month, $day)) !== -1) {
-+ echo Date_Helper::getSimpleDate(sprintf('%04d-%02d-%02d', $year, $month, $day), false);
-+ } else {
-+ echo 'update failed';
-+ }
-+ exit;
-+ break;
-+ default:
-+ die('object type not supported');
-+ break;
++.inline_date_pick {
++ cursor: pointer;
++}
++.custom_field {
++ cursor: pointer;
++}
++.showDragHandle {
++ cursor: move;
++ background-image: url(../images/updown2.gif);
++ background-repeat: no-repeat;
++ background-position: center center;
+}
---- eventum/lib/eventum/class.display_column.php 2008-10-15 01:46:20.000000000 +0300
-+++ eventum-new/lib/eventum/class.display_column.php 2008-10-15 02:02:25.000000000 +0300
-@@ -229,7 +229,10 @@
- ),
- "iss_expected_resolution_date" => array(
- "title" => ev_gettext("Expected Resolution Date")
-- )
-+ ),
-+ "isu_order" => array(
-+ "title" => ev_gettext("Order")
-+ ),
- )
- );
- return $columns[$page];
---- eventum/lib/eventum/class.issue.php 2008-10-15 01:46:20.000000000 +0300
-+++ eventum-new/lib/eventum/class.issue.php 2008-10-15 02:02:25.000000000 +0300
-@@ -1356,6 +1356,7 @@
- Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__);
- return -1;
- } else {
-+ self::moveOrderForAllUsers($issue_id, 1000);
- $prj_id = self::getProjectID($issue_id);
-
- // record the change
-@@ -1659,6 +1660,176 @@
- }
- }
-+ /**
-+ * Method used to update the a single detail field of a specific issue.
-+ *
-+ * @param integer $issue_id
-+ * @param string $field_name
-+ * @param string $field_value
-+ * @param string $field_type string or integer (for escape)
-+ * @return integer 1 on success, -1 otherwise
-+ */
-+ function updateField($issue_id, $field_name, $filed_value) {
+ ul.excerpts {
+ list-style: none;
+@@ -187,4 +205,4 @@
+ ul.excerpts ul {
+ list-style-type: none;
+ padding-left: 1em;
+-}
+\ No newline at end of file
++}
+--- eventum-2.2/htdocs/js/jquery/jquery.tablednd.js 1970-01-01 02:00:00.000000000 +0200
++++ eventum-2.2-order/htdocs/js/jquery/jquery.tablednd.js 2009-10-12 22:10:36.435851675 +0300
+@@ -0,0 +1,382 @@
++/**
++ * TableDnD plug-in for JQuery, allows you to drag and drop table rows
++ * You can set up various options to control how the system will work
++ * Copyright (c) Denis Howlett <denish@isocra.com>
++ * Licensed like jQuery, see http://docs.jquery.com/License.
++ *
++ * Configuration options:
++ *
++ * onDragStyle
++ * This is the style that is assigned to the row during drag. There are limitations to the styles that can be
++ * associated with a row (such as you can't assign a border--well you can, but it won't be
++ * displayed). (So instead consider using onDragClass.) The CSS style to apply is specified as
++ * a map (as used in the jQuery css(...) function).
++ * onDropStyle
++ * This is the style that is assigned to the row when it is dropped. As for onDragStyle, there are limitations
++ * to what you can do. Also this replaces the original style, so again consider using onDragClass which
++ * is simply added and then removed on drop.
++ * onDragClass
++ * This class is added for the duration of the drag and then removed when the row is dropped. It is more
++ * flexible than using onDragStyle since it can be inherited by the row cells and other content. The default
++ * is class is tDnD_whileDrag. So to use the default, simply customise this CSS class in your
++ * stylesheet.
++ * onDrop
++ * Pass a function that will be called when the row is dropped. The function takes 2 parameters: the table
++ * and the row that was dropped. You can work out the new order of the rows by using
++ * table.rows.
++ * onDragStart
++ * Pass a function that will be called when the user starts dragging. The function takes 2 parameters: the
++ * table and the row which the user has started to drag.
++ * onAllowDrop
++ * Pass a function that will be called as a row is over another row. If the function returns true, allow
++ * dropping on that row, otherwise not. The function takes 2 parameters: the dragged row and the row under
++ * the cursor. It returns a boolean: true allows the drop, false doesn't allow it.
++ * scrollAmount
++ * This is the number of pixels to scroll if the user moves the mouse cursor to the top or bottom of the
++ * window. The page should automatically scroll up or down as appropriate (tested in IE6, IE7, Safari, FF2,
++ * FF3 beta
++ * dragHandle
++ * This is the name of a class that you assign to one or more cells in each row that is draggable. If you
++ * specify this class, then you are responsible for setting cursor: move in the CSS and only these cells
++ * will have the drag behaviour. If you do not specify a dragHandle, then you get the old behaviour where
++ * the whole row is draggable.
++ *
++ * Other ways to control behaviour:
++ *
++ * Add class="nodrop" to any rows for which you don't want to allow dropping, and class="nodrag" to any rows
++ * that you don't want to be draggable.
++ *
++ * Inside the onDrop method you can also call $.tableDnD.serialize() this returns a string of the form
++ * <tableID>[]=<rowID1>&<tableID>[]=<rowID2> so that you can send this back to the server. The table must have
++ * an ID as must all the rows.
++ *
++ * Other methods:
++ *
++ * $("...").tableDnDUpdate()
++ * Will update all the matching tables, that is it will reapply the mousedown method to the rows (or handle cells).
++ * This is useful if you have updated the table rows using Ajax and you want to make the table draggable again.
++ * The table maintains the original configuration (so you don't have to specify it again).
++ *
++ * $("...").tableDnDSerialize()
++ * Will serialize and return the serialized string as above, but for each of the matching tables--so it can be
++ * called from anywhere and isn't dependent on the currentTable being set up correctly before calling
++ *
++ * Known problems:
++ * - Auto-scoll has some problems with IE7 (it scrolls even when it shouldn't), work-around: set scrollAmount to 0
++ *
++ * Version 0.2: 2008-02-20 First public version
++ * Version 0.3: 2008-02-07 Added onDragStart option
++ * Made the scroll amount configurable (default is 5 as before)
++ * Version 0.4: 2008-03-15 Changed the noDrag/noDrop attributes to nodrag/nodrop classes
++ * Added onAllowDrop to control dropping
++ * Fixed a bug which meant that you couldn't set the scroll amount in both directions
++ * Added serialize method
++ * Version 0.5: 2008-05-16 Changed so that if you specify a dragHandle class it doesn't make the whole row
++ * draggable
++ * Improved the serialize method to use a default (and settable) regular expression.
++ * Added tableDnDupate() and tableDnDSerialize() to be called when you are outside the table
++ */
++jQuery.tableDnD = {
++ /** Keep hold of the current table being dragged */
++ currentTable : null,
++ /** Keep hold of the current drag object if any */
++ dragObject: null,
++ /** The current mouse offset */
++ mouseOffset: null,
++ /** Remember the old value of Y so that we don't do too much processing */
++ oldY: 0,
+
-+ $issue_id = Misc::escapeInteger($issue_id);
++ /** Actually build the structure */
++ build: function(options) {
++ // Set up the defaults if any
+
-+ $usr_id = Auth::getUserID();
-+ $prj_id = self::getProjectID($issue_id);
++ this.each(function() {
++ // This is bound to each matching table, set up the defaults and override with user options
++ this.tableDnDConfig = jQuery.extend({
++ onDragStyle: null,
++ onDropStyle: null,
++ // Add in the default class for whileDragging
++ onDragClass: "tDnD_whileDrag",
++ onDrop: null,
++ onDragStart: null,
++ scrollAmount: 5,
++ serializeRegexp: /[^\-]*$/, // The regular expression to use to trim row IDs
++ serializeParamName: null, // If you want to specify another parameter name instead of the table ID
++ dragHandle: null // If you give the name of a class here, then only Cells with this class will be draggable
++ }, options || {});
++ // Now make the rows draggable
++ jQuery.tableDnD.makeDraggable(this);
++ });
+
-+ // get all of the 'current' information of this issue
-+ $current = self::getDetails($issue_id);
++ // Now we need to capture the mouse up and mouse move event
++ // We can use bind so that we don't interfere with other event handlers
++ jQuery(document)
++ .bind('mousemove', jQuery.tableDnD.mousemove)
++ .bind('mouseup', jQuery.tableDnD.mouseup);
+
-+ $stmt = "UPDATE
-+ " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "issue
-+ SET
-+ iss_updated_date='" . Date_Helper::getCurrentDateGMT() . "',
-+ iss_last_public_action_date='" . Date_Helper::getCurrentDateGMT() . "',
-+ iss_last_public_action_type='updated'";
++ // Don't break the chain
++ return this;
++ },
+
-+ switch ($field_name) {
-+ case 'category':
-+ $stmt .= ", iss_prc_id = " . Misc::escapeInteger($filed_value);
-+ break;
-+ case 'release':
-+ $stmt .= ", iss_pre_id = " . Misc::escapeInteger($filed_value);
-+ break;
-+ case 'expected_resolution_date':
-+ $stmt .= ", iss_expected_resolution_date = '" . Misc::escapeString($filed_value) . "'";
-+ break;
-+ case 'release':
-+ $stmt .= ", iss_pre_id = " . Misc::escapeInteger($filed_value);
-+ break;
-+ case 'priority':
-+ $stmt .= ", iss_pri_id = " . Misc::escapeInteger($filed_value);
-+ break;
-+ case 'status':
-+ $stmt .= ", iss_sta_id = " . Misc::escapeInteger($filed_value);
-+ break;
-+ case 'resolution':
-+ $stmt .= ", iss_res_id = " . Misc::escapeInteger($filed_value);
-+ break;
-+ case 'summary':
-+ $stmt .= ", iss_summary = '" . Misc::escapeString($filed_value) . "'";
-+ break;
-+ case 'description':
-+ $stmt .= ", iss_description = '" . Misc::escapeString($filed_value) . "'";
-+ break;
-+ case 'estimated_dev_time':
-+ $stmt .= ", iss_dev_time = '" . Misc::escapeString($filed_value) . "'";
-+ break;
-+ case 'percent_complete':
-+ $stmt .= ", iss_percent_complete = '" . Misc::escapeString($filed_value) . "'";
-+ break;
-+ case 'trigger_reminders':
-+ $stmt .= ", iss_trigger_reminders = " . Misc::escapeInteger($filed_value);
-+ break;
-+ case 'group':
-+ $stmt .= ", iss_grp_id = " . Misc::escapeInteger($filed_value);
-+ break;
-+ case 'private':
-+ $stmt .= ", iss_private = " . Misc::escapeInteger($filed_value);
-+ break;
-+ default:
-+ Error_Handler::logError("Unknown field name $field_name", __FILE__, __LINE__);
-+ return -1;
-+ break;
-+ }
++ /** This function makes all the rows on the table draggable apart from those marked as "NoDrag" */
++ makeDraggable: function(table) {
++ var config = table.tableDnDConfig;
++ if (table.tableDnDConfig.dragHandle) {
++ // We only need to add the event to the specified cells
++ var cells = jQuery("td."+table.tableDnDConfig.dragHandle, table);
++ cells.each(function() {
++ // The cell is bound to "this"
++ jQuery(this).mousedown(function(ev) {
++ jQuery.tableDnD.dragObject = this.parentNode;
++ jQuery.tableDnD.currentTable = table;
++ jQuery.tableDnD.mouseOffset = jQuery.tableDnD.getMouseOffset(this, ev);
++ if (config.onDragStart) {
++ // Call the onDrop method if there is one
++ config.onDragStart(table, this);
++ }
++ return false;
++ });
++ })
++ } else {
++ // For backwards compatibility, we add the event to the whole row
++ var rows = jQuery("tr", table); // get all the rows as a wrapped set
++ rows.each(function() {
++ // Iterate through each row, the row is bound to "this"
++ var row = jQuery(this);
++ if (! row.hasClass("nodrag")) {
++ row.mousedown(function(ev) {
++ if (ev.target.tagName == "TD") {
++ jQuery.tableDnD.dragObject = this;
++ jQuery.tableDnD.currentTable = table;
++ jQuery.tableDnD.mouseOffset = jQuery.tableDnD.getMouseOffset(this, ev);
++ if (config.onDragStart) {
++ // Call the onDrop method if there is one
++ config.onDragStart(table, this);
++ }
++ return false;
++ }
++ }).css("cursor", "move"); // Store the tableDnD object
++ }
++ });
++ }
++ },
+
-+ $stmt .= "
-+ WHERE
-+ iss_id=$issue_id";
++ updateTables: function() {
++ this.each(function() {
++ // this is now bound to each matching table
++ if (this.tableDnDConfig) {
++ jQuery.tableDnD.makeDraggable(this);
++ }
++ })
++ },
+
-+ $res = DB_Helper::getInstance()->query($stmt);
-+ if (PEAR::isError($res)) {
-+ Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__);
-+ return -1;
-+ } else {
-+ $new = array(
-+ 'category' => $current['iss_prc_id'],
-+ 'release' => $current['iss_pre_id'],
-+ 'expected_resolution_date' => $current['iss_expected_resolution_date'],
-+ 'release' => $current['iss_pre_id'],
-+ 'priority' => $current['iss_pri_id'],
-+ 'status' => $current['iss_sta_id'],
-+ 'resolution' => $current['iss_res_id'],
-+ 'summary' => $current['iss_summary'],
-+ 'description' => $current['iss_description'],
-+ 'estimated_dev_time' => $current['iss_dev_time'],
-+ 'percent_complete' => $current['iss_percent_complete'],
-+ 'trigger_reminders' => $current['iss_trigger_reminders'],
-+ 'group' => $current['iss_grp_id'],
-+ 'iss_private' => $current['private']
-+ );
-+ $new[$field_name] = $filed_value;
++ /** Get the mouse coordinates from the event (allowing for browser differences) */
++ mouseCoords: function(ev){
++ if(ev.pageX || ev.pageY){
++ return {x:ev.pageX, y:ev.pageY};
++ }
++ return {
++ x:ev.clientX + document.body.scrollLeft - document.body.clientLeft,
++ y:ev.clientY + document.body.scrollTop - document.body.clientTop
++ };
++ },
+
-+ // add change to the history (only for changes on specific fields?)
-+ $updated_fields = array();
-+ if ($field_name == 'expected_resolution_date' && $current["iss_expected_resolution_date"] != $filed_value) {
-+ $updated_fields["Expected Resolution Date"] = History::formatChanges($current["iss_expected_resolution_date"], $filed_value);
-+ }
-+ if ($field_name == 'category' && $current["iss_prc_id"] != $filed_value) {
-+ $updated_fields["Category"] = History::formatChanges(Category::getTitle($current["iss_prc_id"]), Category::getTitle($filed_value));
-+ }
-+ if ($field_name == 'release' && $current["iss_pre_id"] != $filed_value) {
-+ $updated_fields["Release"] = History::formatChanges(Release::getTitle($current["iss_pre_id"]), Release::getTitle($filed_value));
-+ }
-+ if ($field_name == 'priority' && $current["iss_pri_id"] != $filed_value) {
-+ $updated_fields["Priority"] = History::formatChanges(Priority::getTitle($current["iss_pri_id"]), Priority::getTitle($filed_value));
-+ Workflow::handlePriorityChange($prj_id, $issue_id, $usr_id, $current, $new);
-+ }
-+ if ($field_name == 'status' && $current["iss_sta_id"] != $filed_value) {
-+ // clear out the last-triggered-reminder flag when changing the status of an issue
-+ Reminder_Action::clearLastTriggered($issue_id);
++ /** 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 */
++ getMouseOffset: function(target, ev) {
++ ev = ev || window.event;
+
-+ // if old status was closed and new status is not, clear closed data from issue.
-+ $old_status_details = Status::getDetails($current['iss_sta_id']);
-+ if ($old_status_details['sta_is_closed'] == 1) {
-+ $new_status_details = Status::getDetails($filed_value);
-+ if ($new_status_details['sta_is_closed'] != 1) {
-+ self::clearClosed($issue_id);
-+ }
-+ }
-+ $updated_fields["Status"] = History::formatChanges(Status::getStatusTitle($current["iss_sta_id"]), Status::getStatusTitle($filed_value));
-+ }
-+ if ($field_name == 'resolution' && $current["iss_res_id"] != $filed_value) {
-+ $updated_fields["Resolution"] = History::formatChanges(Resolution::getTitle($current["iss_res_id"]), Resolution::getTitle($filed_value));
-+ }
-+ if ($field_name == 'estimated_dev_time' && $current["iss_dev_time"] != $filed_value) {
-+ $updated_fields["Estimated Dev. Time"] = History::formatChanges(Misc::getFormattedTime(($current["iss_dev_time"]*60)), Misc::getFormattedTime(($filed_value*60)));
-+ }
-+ if ($field_name == 'summary' && $current["iss_summary"] != $filed_value) {
-+ $updated_fields["Summary"] = '';
-+ }
-+ if ($field_name == 'description' && $current["iss_description"] != $filed_value) {
-+ $updated_fields["Description"] = '';
-+ }
-+ if ($field_name == 'private' && ($filed_value != $current['iss_private'])) {
-+ $updated_fields["Private"] = History::formatChanges(Misc::getBooleanDisplayValue($current['iss_private']), Misc::getBooleanDisplayValue($filed_value));
-+ }
-+ if (count($updated_fields) > 0) {
-+ // log the changes
-+ $changes = '';
-+ $i = 0;
-+ foreach ($updated_fields as $key => $value) {
-+ if ($i > 0) {
-+ $changes .= "; ";
-+ }
-+ if (($key != "Summary") && ($key != "Description")) {
-+ $changes .= "$key: $value";
++ var docPos = this.getPosition(target);
++ var mousePos = this.mouseCoords(ev);
++ return {x:mousePos.x - docPos.x, y:mousePos.y - docPos.y};
++ },
++
++ /** Get the position of an element by going up the DOM tree and adding up all the offsets */
++ getPosition: function(e){
++ var left = 0;
++ var top = 0;
++ /** Safari fix -- thanks to Luis Chato for this! */
++ if (e.offsetHeight == 0) {
++ /** Safari 2 doesn't correctly grab the offsetTop of a table row
++ this is detailed here:
++ http://jacob.peargrove.com/blog/2006/technical/table-row-offsettop-bug-in-safari/
++ the solution is likewise noted there, grab the offset of a table cell in the row - the firstChild.
++ note that firefox will return a text node as a first child, so designing a more thorough
++ solution may need to take that into account, for now this seems to work in firefox, safari, ie */
++ e = e.firstChild; // a table cell
++ }
++
++ while (e.offsetParent){
++ left += e.offsetLeft;
++ top += e.offsetTop;
++ e = e.offsetParent;
++ }
++
++ left += e.offsetLeft;
++ top += e.offsetTop;
++
++ return {x:left, y:top};
++ },
++
++ mousemove: function(ev) {
++ if (jQuery.tableDnD.dragObject == null) {
++ return;
++ }
++
++ var dragObj = jQuery(jQuery.tableDnD.dragObject);
++ var config = jQuery.tableDnD.currentTable.tableDnDConfig;
++ var mousePos = jQuery.tableDnD.mouseCoords(ev);
++ var y = mousePos.y - jQuery.tableDnD.mouseOffset.y;
++ //auto scroll the window
++ var yOffset = window.pageYOffset;
++ if (document.all) {
++ // Windows version
++ //yOffset=document.body.scrollTop;
++ if (typeof document.compatMode != 'undefined' &&
++ document.compatMode != 'BackCompat') {
++ yOffset = document.documentElement.scrollTop;
++ }
++ else if (typeof document.body != 'undefined') {
++ yOffset=document.body.scrollTop;
++ }
++
++ }
++
++ if (mousePos.y-yOffset < config.scrollAmount) {
++ window.scrollBy(0, -config.scrollAmount);
++ } else {
++ var windowHeight = window.innerHeight ? window.innerHeight
++ : document.documentElement.clientHeight ? document.documentElement.clientHeight : document.body.clientHeight;
++ if (windowHeight-(mousePos.y-yOffset) < config.scrollAmount) {
++ window.scrollBy(0, config.scrollAmount);
++ }
++ }
++
++
++ if (y != jQuery.tableDnD.oldY) {
++ // work out if we're going up or down...
++ var movingDown = y > jQuery.tableDnD.oldY;
++ // update the old value
++ jQuery.tableDnD.oldY = y;
++ // update the style to show we're dragging
++ if (config.onDragClass) {
++ dragObj.addClass(config.onDragClass);
++ } else {
++ dragObj.css(config.onDragStyle);
++ }
++ // If we're over a row then move the dragged row to there so that the user sees the
++ // effect dynamically
++ var currentRow = jQuery.tableDnD.findDropTargetRow(dragObj, y);
++ if (currentRow) {
++ // TODO worry about what happens when there are multiple TBODIES
++ if (movingDown && jQuery.tableDnD.dragObject != currentRow) {
++ jQuery.tableDnD.dragObject.parentNode.insertBefore(jQuery.tableDnD.dragObject, currentRow.nextSibling);
++ } else if (! movingDown && jQuery.tableDnD.dragObject != currentRow) {
++ jQuery.tableDnD.dragObject.parentNode.insertBefore(jQuery.tableDnD.dragObject, currentRow);
++ }
++ }
++ }
++
++ return false;
++ },
++
++ /** We're only worried about the y position really, because we can only move rows up and down */
++ findDropTargetRow: function(draggedRow, y) {
++ var rows = jQuery.tableDnD.currentTable.rows;
++ for (var i=0; i<rows.length; i++) {
++ var row = rows[i];
++ var rowY = this.getPosition(row).y;
++ var rowHeight = parseInt(row.offsetHeight)/2;
++ if (row.offsetHeight == 0) {
++ rowY = this.getPosition(row.firstChild).y;
++ rowHeight = parseInt(row.firstChild.offsetHeight)/2;
++ }
++ // Because we always have to insert before, we need to offset the height a bit
++ if ((y > rowY - rowHeight) && (y < (rowY + rowHeight))) {
++ // that's the row we're over
++ // If it's the same as the current row, ignore it
++ if (row == draggedRow) {return null;}
++ var config = jQuery.tableDnD.currentTable.tableDnDConfig;
++ if (config.onAllowDrop) {
++ if (config.onAllowDrop(draggedRow, row)) {
++ return row;
++ } else {
++ return null;
++ }
++ } else {
++ // If a row has nodrop class, then don't allow dropping (inspired by John Tarr and Famic)
++ var nodrop = jQuery(row).hasClass("nodrop");
++ if (! nodrop) {
++ return row;
+ } else {
-+ $changes .= "$key";
++ return null;
+ }
-+ $i++;
+ }
++ return row;
++ }
++ }
++ return null;
++ },
+
-+ History::add($issue_id, $usr_id, History::getTypeID('issue_updated'), "Issue updated ($changes) by " . User::getFullName($usr_id));
-+ // send notifications for the issue being updated
-+ Notification::notifyIssueUpdated($issue_id, $current, $new);
++ mouseup: function(e) {
++ if (jQuery.tableDnD.currentTable && jQuery.tableDnD.dragObject) {
++ var droppedRow = jQuery.tableDnD.dragObject;
++ var config = jQuery.tableDnD.currentTable.tableDnDConfig;
++ // If we have a dragObject, then we need to release it,
++ // The row will already have been moved to the right place so we just reset stuff
++ if (config.onDragClass) {
++ jQuery(droppedRow).removeClass(config.onDragClass);
++ } else {
++ jQuery(droppedRow).css(config.onDropStyle);
++ }
++ jQuery.tableDnD.dragObject = null;
++ if (config.onDrop) {
++ // Call the onDrop method if there is one
++ config.onDrop(jQuery.tableDnD.currentTable, droppedRow);
+ }
++ jQuery.tableDnD.currentTable = null; // let go of the table too
+ }
-+ return 1;
++ },
++
++ serialize: function() {
++ if (jQuery.tableDnD.currentTable) {
++ return jQuery.tableDnD.serializeTable(jQuery.tableDnD.currentTable);
++ } else {
++ return "Error: No Table id set, you need to set an id on your table and every row";
++ }
++ },
++
++ serializeTable: function(table) {
++ var result = "";
++ var tableId = table.id;
++ var rows = table.rows;
++ for (var i=0; i<rows.length; i++) {
++ if (result.length > 0) result += "&";
++ var rowId = rows[i].id;
++ if (rowId && rowId && table.tableDnDConfig && table.tableDnDConfig.serializeRegexp) {
++ rowId = rowId.match(table.tableDnDConfig.serializeRegexp)[0];
++ }
++
++ result += tableId + '[]=' + rowId;
++ }
++ return result;
++ },
++
++ serializeTables: function() {
++ var result = "";
++ this.each(function() {
++ // this is now bound to each matching table
++ result += jQuery.tableDnD.serializeTable(this);
++ });
++ return result;
+ }
+
++}
++
++jQuery.fn.extend(
++ {
++ tableDnD : jQuery.tableDnD.build,
++ tableDnDUpdate : jQuery.tableDnD.updateTables,
++ tableDnDSerialize: jQuery.tableDnD.serializeTables
++ }
++);
+\ No newline at end of file
+--- eventum-2.3.2/htdocs/list.php~ 2012-03-09 18:19:56.000000000 +0200
++++ eventum-2.3.2/htdocs/list.php 2012-03-09 18:32:43.998284397 +0200
+@@ -67,6 +67,11 @@
+ }
+ }
- /**
- * Move the issue to a new project
-@@ -1820,16 +1991,33 @@
++@$reorder_usr_id = $_REQUEST["reorder_user"];
++@$reorder_issue_id = $_REQUEST["reorder_source"];
++@$reorder_neworder = $_REQUEST["reorder_neworder"];
++Issue::reorderUserIssues($reorder_usr_id, $reorder_issue_id, $reorder_neworder);
++
+ if (!empty($_REQUEST['nosave'])) {
+ $options = Search::saveSearchParams(false);
+ } else {
+@@ -92,6 +97,24 @@
+ }
+ $assign_options += $users;
+
++// get the isu_order (assigned users) ordering user
++if (!empty($options["users"])) {
++ if ($options["users"] == -2) {
++ $isu_order_user = $usr_id;
++ } else
++ if ($options["users"] > 0) {
++ $isu_order_user = $options["users"];
++ } else {
++ unset($isu_order_user);
++ }
++} else {
++ unset($isu_order_user);
++}
++
++if (isset($isu_order_user)) {
++ $tpl->assign("isu_order_user", $isu_order_user);
++}
++
+ $list = Search::getListing($prj_id, $options, $pagerRow, $rows);
+ $tpl->assign("list", $list["list"]);
+ $tpl->assign("list_info", $list["info"]);
+--- eventum-2.2/lib/eventum/class.display_column.php 2009-09-14 18:07:55.000000000 +0300
++++ eventum-2.2-order/lib/eventum/class.display_column.php 2009-10-12 22:10:36.429185594 +0300
+@@ -230,7 +230,10 @@
+ ),
+ "iss_expected_resolution_date" => array(
+ "title" => ev_gettext("Expected Resolution Date")
+- )
++ ),
++ "isu_order" => array(
++ "title" => ev_gettext("Order")
++ ),
+ )
+ );
+ return $columns[$page];
+--- eventum-2.3.1/lib/eventum/class.issue.php~ 2011-09-15 09:36:55.000000000 +0300
++++ eventum-2.3.1/lib/eventum/class.issue.php 2011-09-15 09:42:02.844032474 +0300
+@@ -1374,6 +1374,7 @@
+ return -1;
+ }
+
++ self::moveOrderForAllUsers($issue_id, 1000);
+ $prj_id = self::getProjectID($issue_id);
+
+ // record the change
+@@ -1800,16 +1801,33 @@
{
$issue_id = Misc::escapeInteger($issue_id);
$assignee_usr_id = Misc::escapeInteger($assignee_usr_id);
+ $order = 1;
+ // move all orders down to free "order space" for this new association
-+ $stmt = "UPDATE
++ $stmt = "UPDATE
+ " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "issue_user
+ SET
+ isu_order = isu_order + 1
)";
$res = DB_Helper::getInstance()->query($stmt);
if (PEAR::isError($res)) {
-@@ -1844,6 +2032,78 @@
+@@ -1824,6 +1842,78 @@
}
}
+ " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "issue_user
+ WHERE
+ isu_iss_id IN ($issue_id)";
-+ if ($usr_id !== FALSE) {
++ if (!empty($usr_id)) {
+ $stmt.= " AND isu_usr_id IN ($usr_id)";
+ }
+ $stmt.= "ORDER BY isu_order";
/**
* Method used to delete all user assignments for a specific issue.
-@@ -1859,6 +2119,7 @@
+@@ -1839,6 +1929,7 @@
if (is_array($issue_id)) {
$issue_id = implode(", ", $issue_id);
}
$stmt = "DELETE FROM
" . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "issue_user
WHERE
-@@ -1871,6 +2132,7 @@
- if ($usr_id) {
- History::add($issue_id, $usr_id, History::getTypeID('user_all_unassociated'), 'Issue assignments removed by ' . User::getFullName($usr_id));
- }
-+ self::rearrangeDeleteUserAsssociationOrderList($deleted_order_list);
- return 1;
- }
- }
-@@ -1889,6 +2151,7 @@
+@@ -1869,6 +1960,7 @@
{
$issue_id = Misc::escapeInteger($issue_id);
$usr_id = Misc::escapeInteger($usr_id);
$stmt = "DELETE FROM
" . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "issue_user
WHERE
-@@ -1903,6 +2166,7 @@
- History::add($issue_id, Auth::getUserID(), History::getTypeID('user_unassociated'),
- User::getFullName($usr_id) . ' removed from issue by ' . User::getFullName(Auth::getUserID()));
- }
-+ self::rearrangeDeleteUserAssociationOrderList($delete_order_list);
- return 1;
+@@ -2020,6 +2021,7 @@
+ History::add($issue_id, Auth::getUserID(), History::getTypeID('user_unassociated'),
+ User::getFullName($usr_id) . ' removed from issue by ' . User::getFullName(Auth::getUserID()));
}
++ self::rearrangeDeleteUserAssociationOrderList($delete_order_list);
+ return 1;
}
-@@ -2379,6 +2643,11 @@
- {
- $sort_by = self::getParam('sort_by');
- $sort_order = self::getParam('sort_order');
-+ $users = self::getParam('users');
-+ if (empty($users) && ($sort_by == 'isu_order')) { // Sorting by isu_order is impossible when no user specified
-+ unset($sort_by);
-+ unset($sort_order);
-+ }
- $rows = self::getParam('rows');
- $hide_closed = self::getParam('hide_closed');
- if ($hide_closed === '') {
-@@ -2483,6 +2752,7 @@
- "last_action_date" => "desc",
- "usr_full_name" => "asc",
- "iss_expected_resolution_date" => "desc",
-+ "isu_order" => "desc",
- );
- foreach ($custom_fields as $fld_id => $fld_name) {
-@@ -3275,6 +3545,8 @@
+@@ -3253,6 +3352,8 @@
$ids = implode(", ", $ids);
$stmt = "SELECT
isu_iss_id,
usr_full_name
FROM
" . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "issue_user,
-@@ -3286,6 +3558,7 @@
+@@ -3264,6 +3365,7 @@
if (PEAR::isError($res)) {
Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__);
} else {
$t = array();
for ($i = 0; $i < count($res); $i++) {
if (!empty($t[$res[$i]['isu_iss_id']])) {
-@@ -3294,9 +3567,18 @@
+@@ -3272,9 +3374,18 @@
$t[$res[$i]['isu_iss_id']] = $res[$i]['usr_full_name'];
}
}
}
}
}
-@@ -4264,6 +4546,7 @@
+@@ -4247,6 +4358,7 @@
Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__);
return -1;
}
}
-@@ -4322,5 +4605,121 @@
- }
- return $returns[$msg_id];
+@@ -4346,4 +4458,120 @@
+ History::add($issue_id, Auth::getUserID(), History::getTypeID('user_associated'),
+ "Issue assignment to changed (" . History::formatChanges(join(', ', $old_assignee_names), join(', ', $assignee_names)) . ") by " . User::getFullName(Auth::getUserID()));
}
+
+ /**
+ }
+
}
+--- eventum-2.3.1/lib/eventum/class.search.php~ 2011-04-20 17:22:15.000000000 +0300
++++ eventum-2.3.1/lib/eventum/class.search.php 2011-04-20 17:44:34.498519260 +0300
+@@ -63,6 +63,12 @@
+ {
+ $sort_by = self::getParam('sort_by');
+ $sort_order = self::getParam('sort_order');
++ $users = self::getParam('users');
++ if (empty($users) && $sort_by === 'isu_order') {
++ // Sorting by isu_order is impossible when no user specified
++ unset($sort_by);
++ unset($sort_order);
++ }
+ $rows = self::getParam('rows');
+ $hide_closed = self::getParam('hide_closed');
+ if ($hide_closed === '') {
+@@ -174,6 +174,7 @@
+ "iss_expected_resolution_date" => "desc",
+ "pre_title" => "asc",
+ "assigned" => "asc",
++ "isu_order" => "desc",
+ );
---- eventum-r3749/htdocs/js/global.js~ 2008-10-15 02:03:48.000000000 +0300
-+++ eventum-r3749/htdocs/js/global.js 2008-10-15 02:06:00.000000000 +0300
-@@ -799,4 +799,39 @@
- });
- });
- });
-+
-+$(document).ready(function() {
-+ // dialog type calender isn't working in Konqueror beacuse it's not a supported browser for either jQuery or jQuery UI
-+ // http://groups.google.com/group/jquery-ui/browse_thread/thread/ea61238c34cb5f33/046837b02fb90b5c
-+ if (navigator.appName != 'Konqueror') {
-+ $(".inline_date_pick").click(function() {
-+ var masterObj = this;
-+ var masterObjPos = $(masterObj).offset();
-+ // offset gives uses top and left but datepicker needs pageX and pageY
-+ var masterObjPos = {pageX: masterObjPos.left, pageY: masterObjPos.top};
-+ $(this).datepicker(
-+ // we use dialog type calender so we won't haveto have a hidden element on the page
-+ 'dialog',
-+ // selected date
-+ masterObj.innerHTML,
-+ // onclick handler
-+ function (date, dteObj) {
-+ fieldName = masterObj.id.substr(0,masterObj.id.indexOf('|'));
-+ issueID = masterObj.id.substr(masterObj.id.indexOf('|')+1);
-+ $.post("/ajax/update.php", {fieldName: fieldName, issueID: issueID, day: dteObj.selectedDay, month: (dteObj.selectedMonth+1), year: dteObj.selectedYear}, function(data) {
-+ if (data.length > 0) {
-+ masterObj.innerHTML = data;
-+ }
-+ }, "text");
-+ },
-+ // config
-+ {dateFormat: 'dd M yy', duration: ""},
-+ // position of the datepicker calender - taken from div's offset
-+ masterObjPos
-+ );
-+ return false;
-+ });
-+ }
-+});
-+
- //-->
---- eventum/htdocs/list.php 2008-10-15 01:46:20.000000000 +0300
-+++ eventum-new/htdocs/list.php 2008-10-15 02:02:25.000000000 +0300
-@@ -67,6 +67,11 @@
- $profile['sort_by'] . "&sort_order=" . $profile['sort_order']);
- }
-
-+@$reorder_usr_id = $_REQUEST["reorder_user"];
-+@$reorder_issue_id = $_REQUEST["reorder_source"];
-+@$reorder_neworder = $_REQUEST["reorder_neworder"];
-+Issue::reorderUserIssues($reorder_usr_id, $reorder_issue_id, $reorder_neworder);
-+
- $options = Issue::saveSearchParams();
- $tpl->assign("options", $options);
- $tpl->assign("sorting", Issue::getSortingInfo($options));
-@@ -90,6 +95,21 @@
- }
- $assign_options += $users;
-
-+// get the isu_order (assignated users) ordering user
-+if (!empty($options["users"])) {
-+ if ($options["users"] == -2) {
-+ $isu_order_user = $usr_id;
-+ } else
-+ if ($options["users"] > 0) {
-+ $isu_order_user = $options["users"];
-+ } else {
-+ unset($isu_order_user);
-+ }
-+} else {
-+ unset($isu_order_user);
-+}
-+$tpl->assign("isu_order_user", $isu_order_user);
-+
- $list = Issue::getListing($prj_id, $options, $pagerRow, $rows);
- $tpl->assign("list", $list["list"]);
- $tpl->assign("list_info", $list["info"]);
---- eventum/templates/list.tpl.html 2008-10-15 01:46:20.000000000 +0300
-+++ eventum-new/templates/list.tpl.html 2008-10-15 02:02:25.000000000 +0300
-@@ -89,6 +89,28 @@
+ foreach ($custom_fields as $fld_id => $fld_name) {
+--- eventum-2.3.1/templates/header.tpl.html~ 2011-09-15 09:36:55.000000000 +0300
++++ eventum-2.3.1/templates/header.tpl.html 2011-09-15 09:43:49.318473817 +0300
+@@ -18,6 +18,7 @@
+ <script type="text/javascript" src="{$rel_url}js/jquery/form.js?c=9984"></script>
+ <script type="text/javascript" src="{$rel_url}js/jquery/blockui.js?c=eb13"></script>
+ <script type="text/javascript" src="{$rel_url}js/jquery/ui.datepicker.js?c=a911"></script>
++<script type="text/javascript" src="{$rel_url}js/jquery/jquery.tablednd.js"></script>
+ <script type="text/javascript" src="{$rel_url}js/validation.js?c=ad33"></script>
+ <script type="text/javascript" src="{$rel_url}js/browserSniffer.js?c=c046"></script>
+ <script type="text/javascript" src="{$rel_url}js/global.js?c=50d6"></script>
+--- eventum-2.2/templates/list.tpl.html 2009-09-14 18:07:55.000000000 +0300
++++ eventum-2.2-order/templates/list.tpl.html 2009-10-12 22:10:36.439185157 +0300
+@@ -92,6 +92,28 @@
f.target = '_popup';
f.submit();
}
function hideClosed(f)
{
if (f.hide_closed.checked) {
-@@ -150,6 +172,13 @@
+@@ -153,6 +175,13 @@
f.go.disabled = true;
}
}
//-->
</script>
{/literal}
-@@ -166,11 +195,11 @@
+@@ -169,11 +198,11 @@
<input type="hidden" name="cat" value="bulk_update">
<tr>
<td>
<td class="default">
<b>{t}Search Results{/t} ({$list_info.total_rows} {t}issues found{/t}{if $list_info.end_offset > 0}, {math equation="x + 1" x=$list_info.start_offset} - {$list_info.end_offset} {t}shown{/t}{/if})</b>
{include file="help_link.tpl.html" topic="list"}
-@@ -190,7 +219,7 @@
+@@ -193,7 +222,7 @@
</table>
</td>
</tr>
{if $current_role > $roles.developer}
<td width="1%">
<input type="button" value="{t}All{/t}" class="shortcut" onClick="javascript:toggleSelectAll(this.form, 'item[]');toggleBulkUpdate();">
-@@ -205,7 +234,7 @@
+@@ -208,7 +237,7 @@
{if $sorting.images[$fld_name_id] != ""}<a title="{t}sort by{/t} {$fld_title|escape:"html"}" href="{$sorting.links[$fld_name_id]}" class="white_link"><img border="0" src="{$sorting.images[$fld_name_id]}"></a>{/if}
</td>
{/foreach}
<td align="{$column.align|default:'center'}" class="default_white" nowrap {if $column.width != ''}width="{$column.width}"{/if}>
{if $field_name == 'iss_summary'}
<table cellspacing="0" cellpadding="1" width="100%">
-@@ -221,6 +250,9 @@
- </table>
- {elseif $sorting.links[$field_name] != ''}
- <a title="{t}sort by{/t} {$column.title}" href="{$sorting.links[$field_name]}" class="white_link">{$column.title}</a>
-+ {if $field_name == 'isu_order'}
-+ <br>{$users[$isu_order_user]}
-+ {/if}
- {if $sorting.images[$field_name] != ""}<a title="{t}sort by{/t} {$column.title}" href="{$sorting.links[$field_name]}" class="white_link"><img border="0" src="{$sorting.images[$field_name]}"></a>{/if}
- {else}
- {$column.title}
-@@ -229,19 +261,20 @@
+@@ -268,8 +268,9 @@
{/if}
{/foreach}
</tr>
{if $current_role > $roles.developer}
<td bgcolor="{$list[i].status_color}" width="1%" class="default" align="center"><input type="checkbox" name="item[]" value="{$list[i].iss_id}" onchange="toggleBulkUpdate();"></td>
{/if}
- {foreach from=$columns item=column key=field_name}
- {if $field_name == 'custom_fields'}
- {foreach from=$list[i].custom_field key=fld_id item=fld_value}
-- <td bgcolor="{$list[i].status_color}" align="{$column.align|default:'center'}" class="default">
-- {$fld_value|formatCustomValue:$fld_id:$list[i].iss_id}
-+ <td bgcolor="{$list[i].status_color}" align="{$column.align|default:'center'}" class="default custom_field" onclick="return updateCustomFields({$list[i].iss_id});">
-+ {$fld_value|formatCustomValue:$fld_id:$list[i].iss_id}
+@@ -280,8 +281,8 @@
+ {$fld_value|formatCustomValue:$fld_id:$list[i].iss_id}
</td>
{/foreach}
- {else}
+- <td bgcolor="{$list[i].status_color}" align="{$column.align|default:'center'}" class="default">
+ {elseif $field_name != 'isu_order' || $isu_order_user}
- <td bgcolor="{$list[i].status_color}" align="{$column.align|default:'center'}" class="default">
++ <td bgcolor="{$list[i].status_color}" align="{$column.align|default:'center'}" class="default{if $field_name == 'isu_order'} dragHandle{/if}">
{if $field_name == 'iss_id'}
<a href="view.php?id={$list[i].iss_id}" class="link" title="{t}view issue details{/t}">{$list[i].iss_id}</a>
-@@ -276,7 +309,7 @@
- {elseif $field_name == 'iss_percent_complete'}
- {$list[i].iss_percent_complete|escape:"html"}%
- {elseif $field_name == 'iss_expected_resolution_date'}
-- {$list[i].iss_expected_resolution_date|escape:"html"}
-+ <div class="inline_date_pick" id="expected_resolution_date|{$list[i].iss_id}">{$list[i].iss_expected_resolution_date|escape:"html"} </div>
- {elseif $field_name == 'iss_summary'}
- <a href="view.php?id={$list[i].iss_id}" class="link" title="{t}view issue details{/t}">{$list[i].iss_summary|escape:"html"}</a>
- {if $list[i].redeemed}
-@@ -285,6 +318,10 @@
+ {elseif $field_name == 'pri_rank'}
+@@ -288,6 +318,8 @@
{if $list[i].iss_private == 1}
<b>[Private]</b>
{/if}
+ {elseif $field_name == 'isu_order'}
-+ {if $list[i].assigned_users_order[$current_user_id]}
-+ <img src="{$rel_url}images/updown.gif" alt="move">
-+ {/if}
++ {if $options.sort_by == "isu_order" and $current_user_id == $isu_order_user} {/if}
{/if}
</td>
{/if}
-@@ -297,10 +334,11 @@
+@@ -300,10 +332,11 @@
</td>
</tr>
{/section}
- <tr bgcolor="{$cell_color}">
-+ </tbody>
++ </tbody>
+ <tr bgcolor="{$cell_color}" class="nodrag">
<td colspan="{$col_count}">
<table width="100%" cellspacing="0" cellpadding="0">
<td width="30%" nowrap>
{if $current_role > $roles.developer}
<input type="button" value="{t}All{/t}" class="shortcut" onClick="javascript:toggleSelectAll(this.form, 'item[]');">
-@@ -352,6 +390,29 @@
+@@ -355,6 +388,35 @@
</form>
</table>
<br />
+ alert(data);
+ }
+ }, "text");
-+ }
++ },
++ dragHandle: "dragHandle"
++});
++$("#issue_list_table tr").hover(function() {
++ $('#' + this.id + ' .dragHandle').addClass('showDragHandle');
++}, function() {
++ $('#' + this.id + ' .dragHandle').removeClass('showDragHandle');
+});
+{/literal}
+{/if}
+</script>
{include file="app_info.tpl.html"}
--{include file="footer.tpl.html"}
-+{include file="footer.tpl.html"}
-\ No newline at end of file
---- ./htdocs/js/jquery/jquery.tablednd.js (revision 0)
-+++ ./htdocs/js/jquery/jquery.tablednd.js (revision 0)
-@@ -0,0 +1,382 @@
-+/**
-+ * TableDnD plug-in for JQuery, allows you to drag and drop table rows
-+ * You can set up various options to control how the system will work
-+ * Copyright (c) Denis Howlett <denish@isocra.com>
-+ * Licensed like jQuery, see http://docs.jquery.com/License.
-+ *
-+ * Configuration options:
-+ *
-+ * onDragStyle
-+ * This is the style that is assigned to the row during drag. There are limitations to the styles that can be
-+ * associated with a row (such as you can't assign a border--well you can, but it won't be
-+ * displayed). (So instead consider using onDragClass.) The CSS style to apply is specified as
-+ * a map (as used in the jQuery css(...) function).
-+ * onDropStyle
-+ * This is the style that is assigned to the row when it is dropped. As for onDragStyle, there are limitations
-+ * to what you can do. Also this replaces the original style, so again consider using onDragClass which
-+ * is simply added and then removed on drop.
-+ * onDragClass
-+ * This class is added for the duration of the drag and then removed when the row is dropped. It is more
-+ * flexible than using onDragStyle since it can be inherited by the row cells and other content. The default
-+ * is class is tDnD_whileDrag. So to use the default, simply customise this CSS class in your
-+ * stylesheet.
-+ * onDrop
-+ * Pass a function that will be called when the row is dropped. The function takes 2 parameters: the table
-+ * and the row that was dropped. You can work out the new order of the rows by using
-+ * table.rows.
-+ * onDragStart
-+ * Pass a function that will be called when the user starts dragging. The function takes 2 parameters: the
-+ * table and the row which the user has started to drag.
-+ * onAllowDrop
-+ * Pass a function that will be called as a row is over another row. If the function returns true, allow
-+ * dropping on that row, otherwise not. The function takes 2 parameters: the dragged row and the row under
-+ * the cursor. It returns a boolean: true allows the drop, false doesn't allow it.
-+ * scrollAmount
-+ * This is the number of pixels to scroll if the user moves the mouse cursor to the top or bottom of the
-+ * window. The page should automatically scroll up or down as appropriate (tested in IE6, IE7, Safari, FF2,
-+ * FF3 beta
-+ * dragHandle
-+ * This is the name of a class that you assign to one or more cells in each row that is draggable. If you
-+ * specify this class, then you are responsible for setting cursor: move in the CSS and only these cells
-+ * will have the drag behaviour. If you do not specify a dragHandle, then you get the old behaviour where
-+ * the whole row is draggable.
-+ *
-+ * Other ways to control behaviour:
-+ *
-+ * Add class="nodrop" to any rows for which you don't want to allow dropping, and class="nodrag" to any rows
-+ * that you don't want to be draggable.
-+ *
-+ * Inside the onDrop method you can also call $.tableDnD.serialize() this returns a string of the form
-+ * <tableID>[]=<rowID1>&<tableID>[]=<rowID2> so that you can send this back to the server. The table must have
-+ * an ID as must all the rows.
-+ *
-+ * Other methods:
-+ *
-+ * $("...").tableDnDUpdate()
-+ * Will update all the matching tables, that is it will reapply the mousedown method to the rows (or handle cells).
-+ * This is useful if you have updated the table rows using Ajax and you want to make the table draggable again.
-+ * The table maintains the original configuration (so you don't have to specify it again).
-+ *
-+ * $("...").tableDnDSerialize()
-+ * Will serialize and return the serialized string as above, but for each of the matching tables--so it can be
-+ * called from anywhere and isn't dependent on the currentTable being set up correctly before calling
-+ *
-+ * Known problems:
-+ * - Auto-scoll has some problems with IE7 (it scrolls even when it shouldn't), work-around: set scrollAmount to 0
-+ *
-+ * Version 0.2: 2008-02-20 First public version
-+ * Version 0.3: 2008-02-07 Added onDragStart option
-+ * Made the scroll amount configurable (default is 5 as before)
-+ * Version 0.4: 2008-03-15 Changed the noDrag/noDrop attributes to nodrag/nodrop classes
-+ * Added onAllowDrop to control dropping
-+ * Fixed a bug which meant that you couldn't set the scroll amount in both directions
-+ * Added serialize method
-+ * Version 0.5: 2008-05-16 Changed so that if you specify a dragHandle class it doesn't make the whole row
-+ * draggable
-+ * Improved the serialize method to use a default (and settable) regular expression.
-+ * Added tableDnDupate() and tableDnDSerialize() to be called when you are outside the table
-+ */
-+jQuery.tableDnD = {
-+ /** Keep hold of the current table being dragged */
-+ currentTable : null,
-+ /** Keep hold of the current drag object if any */
-+ dragObject: null,
-+ /** The current mouse offset */
-+ mouseOffset: null,
-+ /** Remember the old value of Y so that we don't do too much processing */
-+ oldY: 0,
-+
-+ /** Actually build the structure */
-+ build: function(options) {
-+ // Set up the defaults if any
-+
-+ this.each(function() {
-+ // This is bound to each matching table, set up the defaults and override with user options
-+ this.tableDnDConfig = jQuery.extend({
-+ onDragStyle: null,
-+ onDropStyle: null,
-+ // Add in the default class for whileDragging
-+ onDragClass: "tDnD_whileDrag",
-+ onDrop: null,
-+ onDragStart: null,
-+ scrollAmount: 5,
-+ serializeRegexp: /[^\-]*$/, // The regular expression to use to trim row IDs
-+ serializeParamName: null, // If you want to specify another parameter name instead of the table ID
-+ dragHandle: null // If you give the name of a class here, then only Cells with this class will be draggable
-+ }, options || {});
-+ // Now make the rows draggable
-+ jQuery.tableDnD.makeDraggable(this);
-+ });
-+
-+ // Now we need to capture the mouse up and mouse move event
-+ // We can use bind so that we don't interfere with other event handlers
-+ jQuery(document)
-+ .bind('mousemove', jQuery.tableDnD.mousemove)
-+ .bind('mouseup', jQuery.tableDnD.mouseup);
-+
-+ // Don't break the chain
-+ return this;
-+ },
-+
-+ /** This function makes all the rows on the table draggable apart from those marked as "NoDrag" */
-+ makeDraggable: function(table) {
-+ var config = table.tableDnDConfig;
-+ if (table.tableDnDConfig.dragHandle) {
-+ // We only need to add the event to the specified cells
-+ var cells = jQuery("td."+table.tableDnDConfig.dragHandle, table);
-+ cells.each(function() {
-+ // The cell is bound to "this"
-+ jQuery(this).mousedown(function(ev) {
-+ jQuery.tableDnD.dragObject = this.parentNode;
-+ jQuery.tableDnD.currentTable = table;
-+ jQuery.tableDnD.mouseOffset = jQuery.tableDnD.getMouseOffset(this, ev);
-+ if (config.onDragStart) {
-+ // Call the onDrop method if there is one
-+ config.onDragStart(table, this);
-+ }
-+ return false;
-+ });
-+ })
-+ } else {
-+ // For backwards compatibility, we add the event to the whole row
-+ var rows = jQuery("tr", table); // get all the rows as a wrapped set
-+ rows.each(function() {
-+ // Iterate through each row, the row is bound to "this"
-+ var row = jQuery(this);
-+ if (! row.hasClass("nodrag")) {
-+ row.mousedown(function(ev) {
-+ if (ev.target.tagName == "TD") {
-+ jQuery.tableDnD.dragObject = this;
-+ jQuery.tableDnD.currentTable = table;
-+ jQuery.tableDnD.mouseOffset = jQuery.tableDnD.getMouseOffset(this, ev);
-+ if (config.onDragStart) {
-+ // Call the onDrop method if there is one
-+ config.onDragStart(table, this);
-+ }
-+ return false;
-+ }
-+ }).css("cursor", "move"); // Store the tableDnD object
-+ }
-+ });
-+ }
-+ },
-+
-+ updateTables: function() {
-+ this.each(function() {
-+ // this is now bound to each matching table
-+ if (this.tableDnDConfig) {
-+ jQuery.tableDnD.makeDraggable(this);
-+ }
-+ })
-+ },
-+
-+ /** Get the mouse coordinates from the event (allowing for browser differences) */
-+ mouseCoords: function(ev){
-+ if(ev.pageX || ev.pageY){
-+ return {x:ev.pageX, y:ev.pageY};
-+ }
-+ return {
-+ x:ev.clientX + document.body.scrollLeft - document.body.clientLeft,
-+ y:ev.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 */
-+ getMouseOffset: function(target, ev) {
-+ ev = ev || window.event;
-+
-+ var docPos = this.getPosition(target);
-+ var mousePos = this.mouseCoords(ev);
-+ return {x:mousePos.x - docPos.x, y:mousePos.y - docPos.y};
-+ },
-+
-+ /** Get the position of an element by going up the DOM tree and adding up all the offsets */
-+ getPosition: function(e){
-+ var left = 0;
-+ var top = 0;
-+ /** Safari fix -- thanks to Luis Chato for this! */
-+ if (e.offsetHeight == 0) {
-+ /** Safari 2 doesn't correctly grab the offsetTop of a table row
-+ this is detailed here:
-+ http://jacob.peargrove.com/blog/2006/technical/table-row-offsettop-bug-in-safari/
-+ the solution is likewise noted there, grab the offset of a table cell in the row - the firstChild.
-+ note that firefox will return a text node as a first child, so designing a more thorough
-+ solution may need to take that into account, for now this seems to work in firefox, safari, ie */
-+ e = e.firstChild; // a table cell
-+ }
-+
-+ while (e.offsetParent){
-+ left += e.offsetLeft;
-+ top += e.offsetTop;
-+ e = e.offsetParent;
-+ }
-+
-+ left += e.offsetLeft;
-+ top += e.offsetTop;
-+
-+ return {x:left, y:top};
-+ },
-+
-+ mousemove: function(ev) {
-+ if (jQuery.tableDnD.dragObject == null) {
-+ return;
-+ }
-+
-+ var dragObj = jQuery(jQuery.tableDnD.dragObject);
-+ var config = jQuery.tableDnD.currentTable.tableDnDConfig;
-+ var mousePos = jQuery.tableDnD.mouseCoords(ev);
-+ var y = mousePos.y - jQuery.tableDnD.mouseOffset.y;
-+ //auto scroll the window
-+ var yOffset = window.pageYOffset;
-+ if (document.all) {
-+ // Windows version
-+ //yOffset=document.body.scrollTop;
-+ if (typeof document.compatMode != 'undefined' &&
-+ document.compatMode != 'BackCompat') {
-+ yOffset = document.documentElement.scrollTop;
-+ }
-+ else if (typeof document.body != 'undefined') {
-+ yOffset=document.body.scrollTop;
-+ }
-+
-+ }
-+
-+ if (mousePos.y-yOffset < config.scrollAmount) {
-+ window.scrollBy(0, -config.scrollAmount);
-+ } else {
-+ var windowHeight = window.innerHeight ? window.innerHeight
-+ : document.documentElement.clientHeight ? document.documentElement.clientHeight : document.body.clientHeight;
-+ if (windowHeight-(mousePos.y-yOffset) < config.scrollAmount) {
-+ window.scrollBy(0, config.scrollAmount);
-+ }
-+ }
-+
-+
-+ if (y != jQuery.tableDnD.oldY) {
-+ // work out if we're going up or down...
-+ var movingDown = y > jQuery.tableDnD.oldY;
-+ // update the old value
-+ jQuery.tableDnD.oldY = y;
-+ // update the style to show we're dragging
-+ if (config.onDragClass) {
-+ dragObj.addClass(config.onDragClass);
-+ } else {
-+ dragObj.css(config.onDragStyle);
-+ }
-+ // If we're over a row then move the dragged row to there so that the user sees the
-+ // effect dynamically
-+ var currentRow = jQuery.tableDnD.findDropTargetRow(dragObj, y);
-+ if (currentRow) {
-+ // TODO worry about what happens when there are multiple TBODIES
-+ if (movingDown && jQuery.tableDnD.dragObject != currentRow) {
-+ jQuery.tableDnD.dragObject.parentNode.insertBefore(jQuery.tableDnD.dragObject, currentRow.nextSibling);
-+ } else if (! movingDown && jQuery.tableDnD.dragObject != currentRow) {
-+ jQuery.tableDnD.dragObject.parentNode.insertBefore(jQuery.tableDnD.dragObject, currentRow);
-+ }
-+ }
-+ }
-+
-+ return false;
-+ },
-+
-+ /** We're only worried about the y position really, because we can only move rows up and down */
-+ findDropTargetRow: function(draggedRow, y) {
-+ var rows = jQuery.tableDnD.currentTable.rows;
-+ for (var i=0; i<rows.length; i++) {
-+ var row = rows[i];
-+ var rowY = this.getPosition(row).y;
-+ var rowHeight = parseInt(row.offsetHeight)/2;
-+ if (row.offsetHeight == 0) {
-+ rowY = this.getPosition(row.firstChild).y;
-+ rowHeight = parseInt(row.firstChild.offsetHeight)/2;
-+ }
-+ // Because we always have to insert before, we need to offset the height a bit
-+ if ((y > rowY - rowHeight) && (y < (rowY + rowHeight))) {
-+ // that's the row we're over
-+ // If it's the same as the current row, ignore it
-+ if (row == draggedRow) {return null;}
-+ var config = jQuery.tableDnD.currentTable.tableDnDConfig;
-+ if (config.onAllowDrop) {
-+ if (config.onAllowDrop(draggedRow, row)) {
-+ return row;
-+ } else {
-+ return null;
-+ }
-+ } else {
-+ // If a row has nodrop class, then don't allow dropping (inspired by John Tarr and Famic)
-+ var nodrop = jQuery(row).hasClass("nodrop");
-+ if (! nodrop) {
-+ return row;
-+ } else {
-+ return null;
-+ }
-+ }
-+ return row;
-+ }
-+ }
-+ return null;
-+ },
-+
-+ mouseup: function(e) {
-+ if (jQuery.tableDnD.currentTable && jQuery.tableDnD.dragObject) {
-+ var droppedRow = jQuery.tableDnD.dragObject;
-+ var config = jQuery.tableDnD.currentTable.tableDnDConfig;
-+ // If we have a dragObject, then we need to release it,
-+ // The row will already have been moved to the right place so we just reset stuff
-+ if (config.onDragClass) {
-+ jQuery(droppedRow).removeClass(config.onDragClass);
-+ } else {
-+ jQuery(droppedRow).css(config.onDropStyle);
-+ }
-+ jQuery.tableDnD.dragObject = null;
-+ if (config.onDrop) {
-+ // Call the onDrop method if there is one
-+ config.onDrop(jQuery.tableDnD.currentTable, droppedRow);
-+ }
-+ jQuery.tableDnD.currentTable = null; // let go of the table too
-+ }
-+ },
-+
-+ serialize: function() {
-+ if (jQuery.tableDnD.currentTable) {
-+ return jQuery.tableDnD.serializeTable(jQuery.tableDnD.currentTable);
-+ } else {
-+ return "Error: No Table id set, you need to set an id on your table and every row";
-+ }
-+ },
-+
-+ serializeTable: function(table) {
-+ var result = "";
-+ var tableId = table.id;
-+ var rows = table.rows;
-+ for (var i=0; i<rows.length; i++) {
-+ if (result.length > 0) result += "&";
-+ var rowId = rows[i].id;
-+ if (rowId && rowId && table.tableDnDConfig && table.tableDnDConfig.serializeRegexp) {
-+ rowId = rowId.match(table.tableDnDConfig.serializeRegexp)[0];
-+ }
-+
-+ result += tableId + '[]=' + rowId;
-+ }
-+ return result;
-+ },
-+
-+ serializeTables: function() {
-+ var result = "";
-+ this.each(function() {
-+ // this is now bound to each matching table
-+ result += jQuery.tableDnD.serializeTable(this);
-+ });
-+ return result;
-+ }
-+
-+}
-+
-+jQuery.fn.extend(
-+ {
-+ tableDnD : jQuery.tableDnD.build,
-+ tableDnDUpdate : jQuery.tableDnD.updateTables,
-+ tableDnDSerialize: jQuery.tableDnD.serializeTables
-+ }
-+);
-\ No newline at end of file
---- eventum-2.2/templates/header.tpl.html~ 2009-06-30 02:07:28.000000000 +0300
-+++ eventum-2.2/templates/header.tpl.html 2009-06-30 02:07:49.981934267 +0300
-@@ -8,6 +8,7 @@
- <script type="text/javascript" src="{$rel_url}js/jquery/form.js?c=9984"></script>
- <script type="text/javascript" src="{$rel_url}js/jquery/blockui.js?c=eb13"></script>
- <script type="text/javascript" src="{$rel_url}js/jquery/ui.datepicker.js?c=a911"></script>
-+<script type="text/javascript" src="{$rel_url}js/jquery/jquery.tablednd.js"></script>
- <link rel="stylesheet" href="{$rel_url}js/jquery/ui.datepicker.css?c=5096">
- <script type="text/javascript">
- <!--
---- eventum-r3765/htdocs/css/style.css~ 2008-06-19 08:30:31.000000000 +0300
-+++ eventum-r3765/htdocs/css/style.css 2008-10-29 17:28:49.393768970 +0200
-@@ -172,4 +172,9 @@
- font-size: 70%;
- font-family: Verdana, Arial, Helvetica, sans-serif;
- padding: 10px;
--}
-\ No newline at end of file
-+}
-+
-+.tDnD_whileDrag td {
-+ background-color: #ffffdd;
-+ border: 1px solid red;
-+}
---- eventum~/htdocs/css/style.css 2008-11-06 16:54:30.000000000 +0200
-+++ eventum/htdocs/css/style.css 2008-11-06 16:20:50.038350957 +0200
-@@ -173,8 +173,21 @@
- font-family: Verdana, Arial, Helvetica, sans-serif;
- padding: 10px;
- }
--
- .tDnD_whileDrag td {
-- background-color: #ffffdd;
-- border: 1px solid red;
-+ background-color: #ffffdd;
-+}
-+.tDnD_whileDrag td {
-+ border: 1px solid red;
-+}
-+.inline_date_pick {
-+ cursor: pointer;
-+}
-+.custom_field {
-+ cursor: pointer;
-+}
-+.showDragHandle {
-+ cursor: move;
-+ background-image: url(../images/updown2.gif);
-+ background-repeat: no-repeat;
-+ background-position: center center;
- }
---- eventum~/templates/list.tpl.html 2008-11-06 16:54:30.000000000 +0200
-+++ eventum/templates/list.tpl.html 2008-11-11 11:22:36.206950437 +0200
-@@ -250,9 +250,6 @@
- </table>
- {elseif $sorting.links[$field_name] != ''}
- <a title="{t}sort by{/t} {$column.title}" href="{$sorting.links[$field_name]}" class="white_link">{$column.title}</a>
-- {if $field_name == 'isu_order'}
-- <br>{$users[$isu_order_user]}
-- {/if}
- {if $sorting.images[$field_name] != ""}<a title="{t}sort by{/t} {$column.title}" href="{$sorting.links[$field_name]}" class="white_link"><img border="0" src="{$sorting.images[$field_name]}"></a>{/if}
- {else}
- {$column.title}
-@@ -275,7 +272,7 @@
- </td>
- {/foreach}
- {elseif $field_name != 'isu_order' || $isu_order_user}
-- <td bgcolor="{$list[i].status_color}" align="{$column.align|default:'center'}" class="default">
-+ <td bgcolor="{$list[i].status_color}" align="{$column.align|default:'center'}" class="default{if $field_name == 'isu_order'} dragHandle{/if}">
- {if $field_name == 'iss_id'}
- <a href="view.php?id={$list[i].iss_id}" class="link" title="{t}view issue details{/t}">{$list[i].iss_id}</a>
- {elseif $field_name == 'pri_rank'}
-@@ -319,9 +316,7 @@
- <b>[Private]</b>
- {/if}
- {elseif $field_name == 'isu_order'}
-- {if $list[i].assigned_users_order[$current_user_id]}
-- <img src="{$rel_url}images/updown.gif" alt="move">
-- {/if}
-+ {if $options.sort_by == "isu_order" and $current_user_id == $isu_order_user} {/if}
- {/if}
- </td>
- {/if}
-@@ -334,7 +329,7 @@
- </td>
- </tr>
- {/section}
-- </tbody>
-+ </tbody>
- <tr bgcolor="{$cell_color}" class="nodrag">
- <td colspan="{$col_count}">
- <table width="100%" cellspacing="0" cellpadding="0">
-@@ -409,10 +404,16 @@
- alert(data);
- }
- }, "text");
-- }
-+ },
-+ dragHandle: "dragHandle"
-+});
-+$("#issue_list_table tr").hover(function() {
-+ $('#' + this.id + ' .dragHandle').addClass('showDragHandle');
-+}, function() {
-+ $('#' + this.id + ' .dragHandle').removeClass('showDragHandle');
- });
- {/literal}
- {/if}
- </script>
- {include file="app_info.tpl.html"}
--{include file="footer.tpl.html"}
-\ No newline at end of file
-+{include file="footer.tpl.html"}
---- eventum-r3776/upgrade/update-database.php 2008-11-18 01:10:18.806283202 +0200
-+++ eventum/upgrade/update-database.php 2008-11-18 01:15:20.703082655 +0200
-@@ -83,6 +83,7 @@
- 1 => '01_notes.php',
- 2 => '02_usr_alias.php',
- 3 => '03_prj_mail_aliases.php',
-+ 4 => '04_isu_order.php',
- );
-
- // sanity check. check that the version table exists.
---- /dev/null 2008-11-04 20:33:38.146691408 +0200
-+++ eventum/upgrade/patches/04_isu_order.php 2008-11-18 01:14:02.972104347 +0200
-@@ -0,0 +1,15 @@
-+<?php
-+
-+function db_patch_4() {
-+ $stmts = array();
-+
-+ $columns = db_getCol('DESC %TABLE_PREFIX%issue_user');
-+ if (in_array('isu_order', $columns)) {
-+ return $stmts;
-+ }
-+
-+ $stmts[] = "ALTER TABLE %TABLE_PREFIX%issue_user ADD isu_order int(11) NOT NULL DEFAULT '0' AFTER isu_assigned_date, ADD INDEX isu_order (isu_order)";
-+ $stmts[] = "UPDATE %TABLE_PREFIX%issue_user set isu_order=isu_iss_id";
-+
-+ return $stmts;
-+}
---- ../eventum-old2/htdocs/ajax/update.php 2008-10-13 11:14:23.673428821 +0300
-+++ ./htdocs/ajax/update.php 2008-11-19 15:05:54.490054961 +0200
-@@ -16,9 +16,17 @@
- case 'expected_resolution_date':
- $day = (int)$_POST['day'];
- $month = (int)$_POST['month'];
-- $year = (int)$_POST['year'];
-- if (Issue::updateField($_POST['issueID'], $_POST['fieldName'], sprintf('%04d-%02d-%02d', $year, $month, $day)) !== -1) {
-- echo Date_Helper::getSimpleDate(sprintf('%04d-%02d-%02d', $year, $month, $day), false);
-+ $year = (int)$_POST['year'];
-+ if ($day == 0 && $month == 1 && $year == 0) {
-+ // clear button
-+ $date = null;
-+ } else {
-+ $date = sprintf('%04d-%02d-%02d', $year, $month, $day);
-+ }
-+ if (Issue::updateField($_POST['issueID'], $_POST['fieldName'], $date) !== -1) {
-+ if (!is_null($date)) {
-+ echo Date_Helper::getSimpleDate(sprintf('%04d-%02d-%02d', $year, $month, $day), false);
-+ }
- } else {
- echo 'update failed';
- }
---- eventum_tar/htdocs/js/global.js 2008-11-06 16:54:30.000000000 +0200
-+++ ./htdocs/js/global.js 2008-11-19 15:06:21.820071605 +0200
-@@ -817,10 +817,15 @@
- function (date, dteObj) {
- fieldName = masterObj.id.substr(0,masterObj.id.indexOf('|'));
- issueID = masterObj.id.substr(masterObj.id.indexOf('|')+1);
-+ if (date == '') {
-+ // clear button
-+ dteObj.selectedDay = 0;
-+ dteObj.selectedMonth = 0;
-+ dteObj.selectedYear = 0;
-+ }
-+ //alertProperties(date);
- $.post("/ajax/update.php", {fieldName: fieldName, issueID: issueID, day: dteObj.selectedDay, month: (dteObj.selectedMonth+1), year: dteObj.selectedYear}, function(data) {
-- if (data.length > 0) {
-- masterObj.innerHTML = data;
-- }
-+ masterObj.innerHTML = data;
- }, "text");
- },
- // config
---- eventum-old2/lib/eventum/class.issue.php 2008-10-15 18:30:31.590172372 +0300
-+++ ./lib/eventum/class.issue.php 2008-11-19 15:04:43.710659328 +0200
-@@ -1694,7 +1694,11 @@
- $stmt .= ", iss_pre_id = " . Misc::escapeInteger($filed_value);
- break;
- case 'expected_resolution_date':
-- $stmt .= ", iss_expected_resolution_date = '" . Misc::escapeString($filed_value) . "'";
-+ if (is_null($filed_value)) {
-+ $stmt .= ", iss_expected_resolution_date = null";
-+ } else {
-+ $stmt .= ", iss_expected_resolution_date = '" . Misc::escapeString($filed_value) . "'";
-+ }
- break;
- case 'release':
- $stmt .= ", iss_pre_id = " . Misc::escapeInteger($filed_value);
---- eventum/lib/eventum/class.issue.php 2008-10-15 01:46:20.000000000 +0300
-+++ eventum-new/lib/eventum/class.issue.php 2008-10-15 02:02:25.000000000 +0300
-@@ -2050,7 +2050,7 @@
- " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "issue_user
- WHERE
- isu_iss_id IN ($issue_id)";
-- if ($usr_id !== FALSE) {
-+ if (!empty($usr_id)) {
- $stmt.= " AND isu_usr_id IN ($usr_id)";
- }
- $stmt.= "ORDER BY isu_order";
-@@ -2135,7 +2135,6 @@
- if ($usr_id) {
- History::add($issue_id, $usr_id, History::getTypeID('user_all_unassociated'), 'Issue assignments removed by ' . User::getFullName($usr_id));
- }
-- self::rearrangeDeleteUserAsssociationOrderList($deleted_order_list);
- return 1;
- }
- }
+ {include file="footer.tpl.html"}