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