2 commits - plugins/libcalendaring plugins/tasklist

Thomas Brüderli bruederli at kolabsys.com
Wed Aug 13 11:08:06 CEST 2014


 plugins/libcalendaring/libcalendaring.js                 |    4 
 plugins/tasklist/config.inc.php.dist                     |    9 +
 plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php |    9 -
 plugins/tasklist/localization/en_US.inc                  |    8 +
 plugins/tasklist/skins/larry/sprites.png                 |binary
 plugins/tasklist/skins/larry/tasklist.css                |   28 +++-
 plugins/tasklist/skins/larry/templates/mainview.html     |   27 +++-
 plugins/tasklist/tasklist.js                             |   98 ++++++++++++++-
 plugins/tasklist/tasklist.php                            |   30 ++--
 plugins/tasklist/tasklist_ui.php                         |    2 
 10 files changed, 176 insertions(+), 39 deletions(-)

New commits:
commit b6b12069df7f9c692e79a0a8f42147bd74dcc63b
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Wed Aug 13 11:07:06 2014 +0200

    Implement client-side and user-adjustable sorting of tasks (#3259)

diff --git a/plugins/tasklist/config.inc.php.dist b/plugins/tasklist/config.inc.php.dist
index 2fc5e28..399344c 100644
--- a/plugins/tasklist/config.inc.php.dist
+++ b/plugins/tasklist/config.inc.php.dist
@@ -1,4 +1,11 @@
 <?php
 
-$rcmail_config['tasklist_driver'] = 'kolab';
+// backend type (database, kolab)
+$config['tasklist_driver'] = 'kolab';
+
+// default sorting order of tasks listing (auto, datetime, startdatetime, flagged, complete, changed)
+$config['tasklist_sort_col'] = '';
+
+// default sorting order for tasks listing (asc or desc)
+$config['tasklist_sort_order'] = 'asc';
 
diff --git a/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php b/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php
index a3e6181..662311c 100644
--- a/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php
+++ b/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php
@@ -794,8 +794,11 @@ class tasklist_kolab_driver extends tasklist_driver
             if (!$record['start']->_dateonly)
                 $task['starttime'] = $start->format('H:i');
         }
-        if (is_a($record['dtstamp'], 'DateTime')) {
-            $task['changed'] = $record['dtstamp'];
+        if (is_a($record['changed'], 'DateTime')) {
+            $task['changed'] = $record['changed'];
+        }
+        if (is_a($record['created'], 'DateTime')) {
+            $task['created'] = $record['created'];
         }
 
         if ($record['valarms']) {
@@ -912,7 +915,7 @@ class tasklist_kolab_driver extends tasklist_driver
             $object['sequence'] = $old['sequence'];
         }
 
-        unset($object['tempid'], $object['raw'], $object['list'], $object['flagged'], $object['tags']);
+        unset($object['tempid'], $object['raw'], $object['list'], $object['flagged'], $object['tags'], $object['created']);
         return $object;
     }
 
diff --git a/plugins/tasklist/localization/en_US.inc b/plugins/tasklist/localization/en_US.inc
index 3fd045a..4132327 100644
--- a/plugins/tasklist/localization/en_US.inc
+++ b/plugins/tasklist/localization/en_US.inc
@@ -34,10 +34,13 @@ $labels['status-in-process'] = 'In process';
 $labels['status-completed'] = 'Completed';
 $labels['status-cancelled'] = 'Cancelled';
 $labels['assignedto'] = 'Assigned to';
+$labels['created'] = 'Created';
+$labels['changed'] = 'Last Modified';
 
 $labels['all'] = 'All';
 $labels['flagged'] = 'Flagged';
 $labels['complete'] = 'Complete';
+$labels['completeness'] = 'Progress';
 $labels['overdue'] = 'Overdue';
 $labels['today'] = 'Today';
 $labels['tomorrow'] = 'Tomorrow';
@@ -49,6 +52,7 @@ $labels['mytasks'] = 'My tasks';
 $labels['mytaskstitle'] = 'Tasks assigned to you';
 $labels['nodate'] = 'no date';
 $labels['removetag'] = 'Remove';
+$labels['auto'] = 'Auto';
 
 $labels['taskdetails'] = 'Details';
 $labels['newtask'] = 'New Task';
@@ -74,7 +78,7 @@ $labels['listactions'] = 'List options...';
 $labels['listname'] = 'Name';
 $labels['showalarms'] = 'Show reminders';
 $labels['import'] = 'Import';
-$labels['viewoptions'] = 'View options';
+$labels['viewactions'] = 'View actions';
 $labels['focusview'] = 'View only this list';
 
 // date words
@@ -172,3 +176,5 @@ $labels['itipresponseerror'] = 'Failed to send the response to this task assignm
 $labels['itipinvalidrequest'] = 'This invitation is no longer valid';
 $labels['sentresponseto'] = 'Successfully sent assignment response to $mailto';
 $labels['successremoval'] = 'The task has been deleted successfully.';
+
+$labels['arialabelsortmenu'] = 'Tasks sorting options';
diff --git a/plugins/tasklist/skins/larry/sprites.png b/plugins/tasklist/skins/larry/sprites.png
index 1446573..fecbd58 100644
Binary files a/plugins/tasklist/skins/larry/sprites.png and b/plugins/tasklist/skins/larry/sprites.png differ
diff --git a/plugins/tasklist/skins/larry/tasklist.css b/plugins/tasklist/skins/larry/tasklist.css
index 3b0e403..824f605 100644
--- a/plugins/tasklist/skins/larry/tasklist.css
+++ b/plugins/tasklist/skins/larry/tasklist.css
@@ -507,13 +507,14 @@ body.tasklist.attachmentwin #mainscreen {
 	cursor: pointer;
 }
 
-.buttonbar-right .listmenu .inner {
-	display: inline-block;
-	height: 18px;
-	width: 20px;
+.buttonbar-right a.iconbutton {
 	padding: 0;
-	background: url(sprites.png) 0 -237px no-repeat;
-	text-indent: -5000px;
+	background-image: url(sprites.png);
+	background-position: 0 -238px;
+}
+
+.buttonbar-right a.iconbutton.sorting {
+	background-position: -18px -347px;
 }
 
 #thelist {
@@ -728,7 +729,8 @@ body.tasklist.attachmentwin #mainscreen {
 
 ul.toolbarmenu li span.add,
 ul.toolbarmenu li span.expand,
-ul.toolbarmenu li span.collapse {
+ul.toolbarmenu li span.collapse,
+ul.toolbarmenu.iconized .selected span.icon {
 	background-image: url(sprites.png);
 }
 
@@ -748,6 +750,14 @@ ul.toolbarmenu li span.delete {
 	background-position: 0 -1508px;
 }
 
+ul.toolbarmenu.iconized .selected span.icon {
+	background-position: 0 -324px;
+}
+
+ul.toolbarmenu .sortcol.by-auto a {
+	font-style: italic;
+}
+
 .taskitem-draghelper {
 /*
 	width: 32px;
@@ -975,6 +985,10 @@ div.form-section {
 	margin-bottom: 0.3em;
 }
 
+.tasklistview div.form-section span.task-text + label {
+	margin-left: 2em;
+}
+
 label.block {
 	display: block;
 	margin-bottom: 0.3em;
diff --git a/plugins/tasklist/skins/larry/templates/mainview.html b/plugins/tasklist/skins/larry/templates/mainview.html
index 5f6831f..124af6b 100644
--- a/plugins/tasklist/skins/larry/templates/mainview.html
+++ b/plugins/tasklist/skins/larry/templates/mainview.html
@@ -97,12 +97,31 @@
 			</ul>
 
 			<div class="buttonbar-right">
-				<roundcube:button name="taskviewmenulink" id="taskviewmenulink" type="link" title="tasklist.viewoptions" class="listmenu viewoptions" onclick="return UI.toggle_popup('taskviewmenu',event)" innerClass="inner" label="tasklist.viewoptions" aria-haspopup="true" aria-expanded="false" aria-owns="taskviewmenu-menu" />
+				<roundcube:button name="taskviewactionslink" id="taskviewactionslink" type="link" title="tasklist.viewactions" class="iconbutton viewactions" onclick="return UI.toggle_popup('taskviewactions',event)" label="tasklist.viewactions" aria-haspopup="true" aria-expanded="false" aria-owns="taskviewactions-menu" />
+				<roundcube:button name="taskviewsortmenulink" id="taskviewsortmenulink" type="link" title="sortby" class="iconbutton sorting" onclick="return UI.toggle_popup('taskviewsortmenu',event)" label="sortby" aria-haspopup="true" aria-expanded="false" aria-owns="taskviewsortmenu-menu" />
 			</div>
 
-			<div id="taskviewmenu" class="popupmenu" aria-hidden="true">
-				<h3 id="aria-label-taskviewmenu" class="voice"><roundcube:label name="tasklist.viewoptions" /></h3>
-				<ul class="toolbarmenu" id="taskviewmenu-menu" role="menu" aria-labelledby="aria-label-taskviewmenu">
+			<div id="taskviewsortmenu" class="popupmenu" aria-hidden="true" data-align="right">
+				<h3 id="aria-label-taskviewsortmenu" class="voice"><roundcube:label name="tasklist.arialabelsortmenu" /></h3>
+				<ul class="toolbarmenu iconized" id="taskviewsortmenu-menu" role="menu" aria-labelledby="aria-label-taskviewsortmenu">
+					<ul role="radiogroup" aria-label="<roundcube:label name='sortby' />">
+						<li><roundcube:button command="list-sort" prop="auto" type="link" label="tasklist.auto" role="radio" aria-checked="false" class="sortcol by-auto icon active" innerclass="icon" /></li>
+						<li><roundcube:button command="list-sort" prop="datetime" type="link" label="tasklist.datetime" role="radio" aria-checked="false" class="sortcol by-datetime icon active" innerclass="icon" /></li>
+						<li><roundcube:button command="list-sort" prop="startdatetime" type="link" label="tasklist.start" role="radio" aria-checked="false" class="sortcol by-startdatetime icon active" innerclass="icon" /></li>
+						<li><roundcube:button command="list-sort" prop="flagged" type="link" label="tasklist.flagged" role="radio" aria-checked="false" class="sortcol by-flagged icon active" innerclass="icon" /></li>
+						<li><roundcube:button command="list-sort" prop="complete" type="link" label="tasklist.completeness" role="radio" aria-checked="false" class="sortcol by-complete icon active" innerclass="icon" /></li>
+						<li><roundcube:button command="list-sort" prop="changed" type="link" label="tasklist.changed" role="radio" aria-checked="false" class="sortcol by-changed icon active" innerclass="icon" /></li>
+					</ul>
+					<li role="separator" class="separator"><label id="aria-label-taskviewsortorder"><roundcube:label name="listorder" /></label></li>
+					<ul role="radiogroup" aria-labelledby="aria-label-taskviewsortorder">
+						<li><roundcube:button command="list-order" prop="asc" type="link" label="sortasc" role="radio" aria-checked="false" class="sortorder asc icon" classAct="icon sortorder asc active" innerclass="icon" /></li>
+						<li><roundcube:button command="list-order" prop="desc" type="link" label="sortdesc" role="radio" aria-checked="false" class="sortorder desc icon" classAct="icon sortorder desc active" innerclass="icon" /></li>
+					</ul>
+				</ul>
+			</div>
+			<div id="taskviewactions" class="popupmenu" aria-hidden="true" data-align="right">
+				<h3 id="aria-label-taskviewactions" class="voice"><roundcube:label name="tasklist.viewactions" /></h3>
+				<ul class="toolbarmenu" id="taskviewactions-menu" role="menu" aria-labelledby="aria-label-taskviewactions">
 					<li role="menuitem"><roundcube:button command="expand-all" label="expand-all" class="icon" classAct="icon active" innerclass="icon expand" /></li>
 					<li role="menuitem"><roundcube:button command="collapse-all" label="collapse-all" class="icon" classAct="icon active" innerclass="icon collapse" /></li>
 				</ul>
diff --git a/plugins/tasklist/tasklist.js b/plugins/tasklist/tasklist.js
index 76e6313..91a0537 100644
--- a/plugins/tasklist/tasklist.js
+++ b/plugins/tasklist/tasklist.js
@@ -256,6 +256,13 @@ function rcube_tasklist_ui(settings)
             setTimeout(fetch_counts, 200);
         });
 
+        rcmail.register_command('list-sort', list_set_sort, true);
+        rcmail.register_command('list-order', list_set_order, (settings.sort_col || 'auto') != 'auto');
+
+        $('#taskviewsortmenu .by-' + (settings.sort_col || 'auto')).attr('aria-checked', 'true').addClass('selected');
+        $('#taskviewsortmenu .sortorder.' + (settings.sort_order || 'asc')).attr('aria-checked', 'true').addClass('selected');
+
+
         // start loading tasks
         fetch_counts();
         list_tasks();
@@ -743,6 +750,9 @@ function rcube_tasklist_ui(settings)
                 listdata[listdata[id].parent_id].children.push(id);
         }
 
+        // sort index before rendering
+        listindex.sort(function(a, b) { return task_cmp(listdata[a], listdata[b]); });
+
         append_tags(response.tags || []);
         render_tasklist();
 
@@ -1016,6 +1026,14 @@ function rcube_tasklist_ui(settings)
             }
         }
 
+        // copy _depth property from old rec or derive from parent
+        if (rec.parent_id && listdata[rec.parent_id]) {
+            rec._depth = (listdata[rec.parent_id]._depth || 0) + 1;
+        }
+        else if (oldrec) {
+            rec._depth = oldrec._depth || 0;
+        }
+
         if (list.active || rec.tempid) {
             if (!filter || match_filter(rec, {}))
                 render_task(rec, oldid);
@@ -1294,10 +1312,33 @@ function rcube_tasklist_ui(settings)
      */
     function task_cmp(a, b)
     {
-        var d = is_complete(a) - is_complete(b);
-        if (!d) d = (b._hasdate-0) - (a._hasdate-0);
-        if (!d) d = (a.datetime||99999999999) - (b.datetime||99999999999);
-        return d;
+        // sort by hierarchy level first
+        if ((a._depth || 0) != (b._depth || 0))
+            return a._depth - b._depth;
+
+        var p, alt, inv = 1, c = is_complete(a) - is_complete(b), d = c;
+
+        // completed tasks always move to the end
+        if (c != 0)
+            return c;
+
+        // custom sorting
+        if (settings.sort_col && settings.sort_col != 'auto') {
+            alt = settings.sort_col == 'datetime' || settings.sort_col == 'startdatetime' ? 99999999999 : 0
+            d = (a[settings.sort_col]||alt) - (b[settings.sort_col]||alt);
+            inv = settings.sort_order == 'desc' ? -1 : 1;
+        }
+        // default sorting (auto)
+        else {
+            if (!d) d = (b._hasdate-0) - (a._hasdate-0);
+            if (!d) d = (a.datetime||99999999999) - (b.datetime||99999999999);
+        }
+
+        // fall-back to created/changed date
+        if (!d) d = (a.created||0) - (b.created||0);
+        if (!d) d = (a.changed||0) - (b.changed||0);
+
+        return d * inv;
     }
 
     /**
@@ -1688,6 +1729,12 @@ function rcube_tasklist_ui(settings)
             $('#task-recurrence').hide();
         }
 
+        if (rec.created || rec.changed) {
+            $('#task-created-changed .task-created').html(Q(rec.created_ || rcmail.gettext('unknown','tasklist')))
+            $('#task-created-changed .task-changed').html(Q(rec.changed_ || rcmail.gettext('unknown','tasklist')))
+            $('#task-created-changed').show()
+        }
+
         // build attachments list
         $('#task-attachments').hide();
         if ($.isArray(rec.attachments)) {
@@ -2179,12 +2226,12 @@ function rcube_tasklist_ui(settings)
     /**
      *
      */
-    var remove_attachment = function(elem, id)
+    function remove_attachment(elem, id)
     {
         $(elem.parentNode).hide();
         me.selected_task.deleted_attachments.push(id);
         delete rcmail.env.attachments[id];
-    };
+    }
 
     /**
      *
@@ -2364,6 +2411,45 @@ function rcube_tasklist_ui(settings)
     }
 
     /**
+     * Change tasks list sorting
+     */
+    function list_set_sort(col)
+    {
+        if (settings.sort_col != col) {
+          settings.sort_col = col;
+          $('#taskviewsortmenu .sortcol').attr('aria-checked', 'false').removeClass('selected')
+              .filter('.by-' + col).attr('aria-checked', 'true').addClass('selected');
+
+          // re-sort list index and re-render list
+          listindex.sort(function(a, b) { return task_cmp(listdata[a], listdata[b]); });
+          render_tasklist();
+
+          rcmail.enable_command('list-order', settings.sort_col != 'auto');
+          $('#taskviewsortmenu .sortorder').removeClass('selected').filter('[aria-checked=true]').addClass('selected');
+
+          rcmail.save_pref({ name: 'tasklist_sort_col', value: (col == 'auto' ? '' : col) });
+        }
+    }
+
+    /**
+     * Change tasks list sort order
+     */
+    function list_set_order(order)
+    {
+        if (settings.sort_order != order) {
+          settings.sort_order = order;
+          $('#taskviewsortmenu .sortorder').attr('aria-checked', 'false').removeClass('selected')
+              .filter('.' + order).attr('aria-checked', 'true').addClass('selected');
+
+          // re-sort list index and re-render list
+          listindex.sort(function(a, b) { return task_cmp(listdata[a], listdata[b]); });
+          render_tasklist();
+
+          rcmail.save_pref({ name: 'tasklist_sort_order', value: order });
+        }
+    }
+
+    /**
      *
      */
     function list_edit_dialog(id)
diff --git a/plugins/tasklist/tasklist.php b/plugins/tasklist/tasklist.php
index 35c59ee..7894b10 100644
--- a/plugins/tasklist/tasklist.php
+++ b/plugins/tasklist/tasklist.php
@@ -51,6 +51,8 @@ class tasklist extends rcube_plugin
     );
 
     public $task = '?(?!login|logout).*';
+    public $allowed_prefs = array('tasklist_sort_col','tasklist_sort_order');
+
     public $rc;
     public $lib;
     public $driver;
@@ -959,9 +961,8 @@ class tasklist extends rcube_plugin
                 $data[] = $rec;
         }
 
-        // sort tasks according to their hierarchy level and due date
+        // assign hierarchy level indicators for later sorting
         array_walk($data, array($this, 'task_walk_tree'));
-        usort($data, array($this, 'task_sort_cmp'));
 
         return $data;
     }
@@ -974,7 +975,18 @@ class tasklist extends rcube_plugin
         $rec['mask'] = $this->filter_mask($rec);
         $rec['flagged'] = intval($rec['flagged']);
         $rec['complete'] = floatval($rec['complete']);
-        $rec['changed'] = is_object($rec['changed']) ? $rec['changed']->format('U') : null;
+
+        if (is_object($rec['created'])) {
+            $rec['created_'] = $this->rc->format_date($rec['created']);
+            $rec['created'] = $rec['created']->format('U');
+        }
+        if (is_object($rec['changed'])) {
+            $rec['changed_'] = $this->rc->format_date($rec['changed']);
+            $rec['changed'] = $rec['changed']->format('U');
+        }
+        else {
+            $rec['changed'] = null;
+        }
 
         if ($rec['date']) {
             try {
@@ -1046,18 +1058,6 @@ class tasklist extends rcube_plugin
     }
 
     /**
-     * Compare function for task list sorting.
-     * Nested tasks need to be sorted to the end.
-     */
-    private function task_sort_cmp($a, $b)
-    {
-        $d = $a['_depth'] - $b['_depth'];
-        if (!$d) $d = $b['_hasdate'] - $a['_hasdate'];
-        if (!$d) $d = $a['datetime'] - $b['datetime'];
-        return $d;
-    }
-
-    /**
      * Compute the filter mask of the given task
      *
      * @param array Hash array with Task record properties
diff --git a/plugins/tasklist/tasklist_ui.php b/plugins/tasklist/tasklist_ui.php
index 5b10df4..f3a2bb6 100644
--- a/plugins/tasklist/tasklist_ui.php
+++ b/plugins/tasklist/tasklist_ui.php
@@ -72,6 +72,8 @@ class tasklist_ui
         $settings = array();
 
         $settings['invite_shared'] = (int)$this->rc->config->get('calendar_allow_invite_shared', 0);
+        $settings['sort_col']      = $this->rc->config->get('tasklist_sort_col', '');
+        $settings['sort_order']    = $this->rc->config->get('tasklist_sort_order', 'asc');
 
         // get user identity to create default attendee
         foreach ($this->rc->user->list_identities() as $rec) {


commit aa63f121c855484da75bab9de64c16388f653384
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Tue Aug 12 18:43:31 2014 +0200

    Fix link regex and replacement

diff --git a/plugins/libcalendaring/libcalendaring.js b/plugins/libcalendaring/libcalendaring.js
index ce09755..6f20ab7 100644
--- a/plugins/libcalendaring/libcalendaring.js
+++ b/plugins/libcalendaring/libcalendaring.js
@@ -303,11 +303,11 @@ function rcube_libcalendaring(settings)
         // simple link parser (similar to rcube_string_replacer class in PHP)
         var utf_domain = '[^?&@"\'/\\(\\)\\s\\r\\t\\n]+\\.([^\x00-\x2f\x3b-\x40\x5b-\x60\x7b-\x7f]{2,}|xn--[a-z0-9]{2,})';
         var url1 = '.:;,', url2 = 'a-z0-9%=#@+?&/_~\\[\\]-';
-        var link_pattern = new RegExp('([hf]t+ps?://)('+utf_domain+'(['+url1+']?['+url2+']+)*)?', 'ig');
+        var link_pattern = new RegExp('([hf]t+ps?://)('+utf_domain+'(['+url1+']?['+url2+']+)*)', 'ig');
         var mailto_pattern = new RegExp('([^\\s\\n\\(\\);]+@'+utf_domain+')', 'ig');
         var link_replace = function(matches, p1, p2) {
           var title = '', text = p2;
-          if (p2.length > 55) {
+          if (p2 && p2.length > 55) {
             text = p2.substr(0, 45) + '...' + p2.substr(-8);
             title = p1 + p2;
           }




More information about the commits mailing list