Projects
Kolab:3.4
roundcubemail-plugins-kolab
Log In
Username
Password
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
Expand all
Collapse all
Changes of Revision 24
View file
debian.changelog
Changed
@@ -1,3 +1,9 @@ +roundcubemail-plugins-kolab (1:3.1.6-0~kolab2) unstable; urgency=low + + * include patches, similar to CentOS package + + -- Timotheus Pokorra <timotheus@pokorra.de> Mon, 04 Nov 2013 07:41:13 +0200 + roundcubemail-plugins-kolab (1:3.1.6-0~kolab1) unstable; urgency=low * New upstream version 3.1.6
View file
debian.tar.gz/debian.series
Added
@@ -0,0 +1,5 @@ +roundcubemail-plugins-kolab-3.1.6-task-enhancements.patch -p1 +roundcubemail-plugins-kolab-3.1.6-2353-ics-import-efficiency.patch -p1 +roundcubemail-plugins-kolab-3.1.6-2463-all-day-events-are-off-one-day.patch -p1 +roundcubemail-plugins-kolab-3.1.6-fix-replacement-of-alarm-values.patch -p1 +
View file
debian.tar.gz/patches
Added
+(directory)
View file
debian.tar.gz/patches/roundcubemail-plugins-kolab-3.1.6-2353-ics-import-efficiency.patch
Added
@@ -0,0 +1,744 @@ +diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php +index 685745d..e8e5ab2 100644 +--- a/plugins/calendar/calendar.php ++++ b/plugins/calendar/calendar.php +@@ -1003,7 +1003,7 @@ class calendar extends rcube_plugin + rcube_upload_progress(); + } + +- $calendar = get_input_value('calendar', RCUBE_INPUT_GPC); ++ @set_time_limit(0); + + // process uploaded file if there is no error + $err = $_FILES['_data']['error']; +@@ -1011,22 +1011,23 @@ class calendar extends rcube_plugin + if (!$err && $_FILES['_data']['tmp_name']) { + $calendar = get_input_value('calendar', RCUBE_INPUT_GPC); + $rangestart = $_REQUEST['_range'] ? date_create("now -" . intval($_REQUEST['_range']) . " months") : 0; +- $count = $errors = 0; +- +- try { +- $events = $this->get_ical()->import_from_file($_FILES['_data']['tmp_name'], 'UTF-8', true); +- } +- catch (Exception $e) { +- $errors = 1; +- $msg = $e->getMessage(); +- $events = array(); +- } ++ $user_email = $this->rc->user->get_username(); ++ ++ $ical = $this->get_ical(); ++ $errors = !$ical->fopen($_FILES['_data']['tmp_name']); ++ $count = $i = 0; ++ foreach ($ical as $event) { ++ // keep the browser connection alive on long import jobs ++ if (++$i > 100 && $i % 100 == 0) { ++ echo "<!-- -->"; ++ ob_flush(); ++ } + +- foreach ($events as $event) { + // TODO: correctly handle recurring events which start before $rangestart + if ($event['end'] < $rangestart && (!$event['recurrence'] || ($event['recurrence']['until'] && $event['recurrence']['until'] < $rangestart))) + continue; + ++ $event['_owner'] = $user_email; + $event['calendar'] = $calendar; + if ($this->driver->new_event($event)) { + $count++; +diff --git a/plugins/calendar/calendar_ui.js b/plugins/calendar/calendar_ui.js +index d5a4308..3ef1cb1 100644 +--- a/plugins/calendar/calendar_ui.js ++++ b/plugins/calendar/calendar_ui.js +@@ -1988,10 +1988,15 @@ function rcube_calendar_ui(settings) + rcmail.display_message(rcmail.get_label('importerror', 'calendar'), 'error'); + }); + +- // display upload indicator ++ // display upload indicator (with extended timeout) ++ var timeout = rcmail.env.request_timeout; ++ rcmail.env.request_timeout = 600; + me.import_succeeded = null; + me.saving_lock = rcmail.set_busy(true, 'uploading'); + $('.ui-dialog-buttonpane button', $dialog.parent()).button('disable'); ++ ++ // restore settings ++ rcmail.env.request_timeout = timeout; + } + }; + +diff --git a/plugins/calendar/drivers/kolab/kolab_calendar.php b/plugins/calendar/drivers/kolab/kolab_calendar.php +index 91c8d0c..bd42f56 100644 +--- a/plugins/calendar/drivers/kolab/kolab_calendar.php ++++ b/plugins/calendar/drivers/kolab/kolab_calendar.php +@@ -317,7 +317,7 @@ class kolab_calendar + } + else { + $event['id'] = $event['uid']; +- $this->events[$event['uid']] = $this->_to_rcube_event($object); ++ $this->events = array($event['uid'] => $this->_to_rcube_event($object)); + } + + return $saved; +diff --git a/plugins/libcalendaring/libvcalendar.php b/plugins/libcalendaring/libvcalendar.php +index 5e12d50..aa6dc4a 100644 +--- a/plugins/libcalendaring/libvcalendar.php ++++ b/plugins/libcalendaring/libvcalendar.php +@@ -37,13 +37,17 @@ if (!class_exists('\Sabre\VObject\Reader')) { + * and place the lib files in this plugin's lib directory + * + */ +-class libvcalendar ++class libvcalendar implements Iterator + { + private $timezone; + private $attach_uri = null; + private $prodid = '-//Roundcube//Roundcube libcalendaring//Sabre//Sabre VObject//EN'; + private $type_component_map = array('event' => 'VEVENT', 'task' => 'VTODO'); + private $attendee_keymap = array('name' => 'CN', 'status' => 'PARTSTAT', 'role' => 'ROLE', 'cutype' => 'CUTYPE', 'rsvp' => 'RSVP'); ++ private $iteratorkey = 0; ++ private $charset; ++ private $forward_exceptions; ++ private $fp; + + public $method; + public $agent = ''; +@@ -98,6 +102,13 @@ class libvcalendar + { + $this->method = ''; + $this->objects = array(); ++ $this->freebusy = array(); ++ $this->iteratorkey = 0; ++ ++ if ($this->fp) { ++ fclose($this->fp); ++ $this->fp = null; ++ } + } + + /** +@@ -108,25 +119,36 @@ class libvcalendar + * @param boolean True if parsing exceptions should be forwarded to the caller + * @return array List of events extracted from the input + */ +- public function import($vcal, $charset = 'UTF-8', $forward_exceptions = false) ++ public function import($vcal, $charset = 'UTF-8', $forward_exceptions = false, $memcheck = true) + { + // TODO: convert charset to UTF-8 if other + + try { ++ // estimate the memory usage and try to avoid fatal errors when allowed memory gets exhausted ++ if ($memcheck) { ++ $count = substr_count($vcal, 'BEGIN:VEVENT'); ++ $expected_memory = $count * 70*1024; // assume ~ 70K per event (empirically determined) ++ ++ if (!rcube_utils::mem_check($expected_memory)) { ++ throw new Exception("iCal file too big"); ++ } ++ } ++ + $vobject = VObject\Reader::read($vcal, VObject\Reader::OPTION_FORGIVING | VObject\Reader::OPTION_IGNORE_INVALID_LINES); + if ($vobject) + return $this->import_from_vobject($vobject); + } + catch (Exception $e) { +- rcube::raise_error(array( +- 'code' => 600, 'type' => 'php', +- 'file' => __FILE__, 'line' => __LINE__, +- 'message' => "iCal data parse error: " . $e->getMessage()), +- true, false); +- + if ($forward_exceptions) { + throw $e; + } ++ else { ++ rcube::raise_error(array( ++ 'code' => 600, 'type' => 'php', ++ 'file' => __FILE__, 'line' => __LINE__, ++ 'message' => "iCal data parse error: " . $e->getMessage()), ++ true, false); ++ } + } + + return array(); +@@ -142,17 +164,118 @@ class libvcalendar + */ + public function import_from_file($filepath, $charset = 'UTF-8', $forward_exceptions = false) + { +- $this->objects = array(); +- $fp = fopen($filepath, 'r'); ++ if ($this->fopen($filepath, $charset, $forward_exceptions)) { ++ while ($this->_parse_next(false)) { ++ // nop ++ } ++ ++ fclose($this->fp); ++ $this->fp = null; ++ } ++ ++ return $this->objects; ++ } ++ ++ /** ++ * Open a file to read iCalendar events sequentially ++ * ++ * @param string File path to read from ++ * @param string Input charset (from envelope) ++ * @param boolean True if parsing exceptions should be forwarded to the caller ++ * @return boolean True if file contents are considered valid ++ */ ++ public function fopen($filepath, $charset = 'UTF-8', $forward_exceptions = false) ++ { ++ $this->reset(); ++ ++ // just to be sure... ++ @ini_set('auto_detect_line_endings', true); ++ ++ $this->charset = $charset; ++ $this->forward_exceptions = $forward_exceptions; ++ $this->fp = fopen($filepath, 'r'); + + // check file content first +- $begin = fread($fp, 1024); ++ $begin = fread($this->fp, 1024); + if (!preg_match('/BEGIN:VCALENDAR/i', $begin)) { +- return $this->objects; ++ return false; ++ } ++ ++ // read vcalendar header (with timezone defintion) ++ $this->vhead = ''; ++ fseek($this->fp, 0); ++ while (($line = fgets($this->fp, 512)) !== false) { ++ if (preg_match('/BEGIN:(VEVENT|VTODO|VFREEBUSY)/i', $line)) ++ break; ++ $this->vhead .= $line; ++ } ++ fseek($this->fp, -strlen($line), SEEK_CUR); ++ ++ return $this->_parse_next(); ++ } ++ ++ /** ++ * Parse the next event/todo/freebusy object from the input file ++ */ ++ private function _parse_next($reset = true) ++ { ++ if ($reset) { ++ $this->iteratorkey = 0; ++ $this->objects = array(); ++ $this->freebusy = array(); ++ } ++ ++ $next = $this->_next_component(); ++ $buffer = $next; ++ ++ // load the next component(s) too, as they could contain recurrence exceptions ++ while (preg_match('/(RRULE|RECURRENCE-ID)[:;]/i', $next)) { ++ $next = $this->_next_component(); ++ $buffer .= $next; ++ } ++ ++ // parse the vevent block surrounded with the vcalendar heading ++ if (strlen($buffer) && preg_match('/BEGIN:(VEVENT|VTODO|VFREEBUSY)/i', $buffer)) { ++ try { ++ $this->import($this->vhead . $buffer . "END:VCALENDAR", $this->charset, true, false); ++ } ++ catch (Exception $e) { ++ if ($this->forward_exceptions) { ++ throw new VObject\ParseException($e->getMessage() . " in\n" . $buffer); ++ } ++ else { ++ // write the failing section to error log ++ rcube::raise_error(array( ++ 'code' => 600, 'type' => 'php', ++ 'file' => __FILE__, 'line' => __LINE__, ++ 'message' => $e->getMessage() . " in\n" . $buffer), ++ true, false); ++ } ++ ++ // advance to next ++ return $this->_parse_next($reset); ++ } ++ ++ return count($this->objects) > 0; + } +- fclose($fp); + +- return $this->import(file_get_contents($filepath), $charset, $forward_exceptions); ++ return false; ++ } ++ ++ /** ++ * Helper method to read the next calendar component from the file ++ */ ++ private function _next_component() ++ { ++ $buffer = ''; ++ while (($line = fgets($this->fp, 1024)) !== false) { ++ $buffer .= $line; ++ if (preg_match('/END:(VEVENT|VTODO|VFREEBUSY)/i', $line)) { ++ break; ++ } ++ } ++ ++ return $buffer; + } + + /** +@@ -163,7 +286,7 @@ class libvcalendar + */ + public function import_from_vobject($vobject) + { +- $this->objects = $this->freebusy = $seen = array(); ++ $seen = array(); + + if ($vobject->name == 'VCALENDAR') { + $this->method = strval($vobject->METHOD); +@@ -847,6 +970,41 @@ class libvcalendar + } + } + ++ ++ /*** Implement PHP 5 Iterator interface to make foreach work ***/ ++ ++ function current() ++ { ++ return $this->objects[$this->iteratorkey]; ++ } ++ ++ function key() ++ { ++ return $this->iteratorkey; ++ } ++ ++ function next() ++ { ++ $this->iteratorkey++; ++ ++ // read next chunk if we're reading from a file ++ if (!$this->objects[$this->iteratorkey] && $this->fp) { ++ $this->_parse_next(true); ++ } ++ ++ return $this->valid(); ++ } ++ ++ function rewind() ++ { ++ $this->iteratorkey = 0; ++ } ++ ++ function valid() ++ { ++ return !empty($this->objects[$this->iteratorkey]); ++ } ++ + } + + +diff --git a/plugins/libcalendaring/tests/libvcalendar.php b/plugins/libcalendaring/tests/libvcalendar.php +index 92a22a3..28be822 100644 +--- a/plugins/libcalendaring/tests/libvcalendar.php ++++ b/plugins/libcalendaring/tests/libvcalendar.php +@@ -167,6 +167,22 @@ class libvcalendar_test extends PHPUnit_Framework_TestCase + } + + /** ++ * @depends test_import_from_file ++ */ ++ function test_attachment() ++ { ++ $ical = new libvcalendar(); ++ ++ $events = $ical->import_from_file(__DIR__ . '/resources/attachment.ics', 'UTF-8'); ++ $event = $events[0]; ++ ++ $this->assertEquals(2, count($events)); ++ $this->assertEquals(1, count($event['attachments'])); ++ $this->assertEquals('image/png', $event['attachments'][0]['mimetype']); ++ $this->assertEquals('500px-Opensource.svg.png', $event['attachments'][0]['name']); ++ } ++ ++ /** + * @depends test_import + */ + function test_freebusy() +diff --git a/plugins/libcalendaring/tests/resources/attachment.ics b/plugins/libcalendaring/tests/resources/attachment.ics +new file mode 100644 +index 0000000..827cc76 +--- /dev/null ++++ b/plugins/libcalendaring/tests/resources/attachment.ics +@@ -0,0 +1,344 @@ ++BEGIN:VCALENDAR ++VERSION:2.0 ++PRODID:-//Roundcube//Roundcube libcalendaring 1.0-git//Sabre//Sabre VObject ++ 2.1.3//EN ++CALSCALE:GREGORIAN ++BEGIN:VEVENT ++UID:93B331265CF47061F888BC0A3F0FBD7F-FCBB6C4091F28CA0 ++DTSTAMP;VALUE=DATE-TIME:20131017T084408Z ++CREATED;VALUE=DATE-TIME:20131017T083610Z ++LAST-MODIFIED;VALUE=DATE-TIME:20131017T083610Z ++DTSTART;VALUE=DATE-TIME;TZID=Europe/Zurich:20131017T110000 ++DTEND;VALUE=DATE-TIME;TZID=Europe/Zurich:20131017T120000 ++SUMMARY:Event with attachment ++LOCATION:Test lab ++TRANSP:OPAQUE ++CLASS:PUBLIC ++ORGANIZER;CN=Bruederli:mailto:thomas.bruederli@example.org ++ATTACH;VALUE=BINARY;ENCODING=BASE64;FMTTYPE=image/png;X-LABEL=500px-Opensou ++ rce.svg.png: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 ++END:VEVENT ++ ++BEGIN:VEVENT ++CREATED:20130718T073555Z ++UID:B968B885-08FB-40E5-B89E-6DA05F26AA79 ++URL;VALUE=URI:http://en.wikipedia.org/wiki/Swiss_National_Day ++DTEND;VALUE=DATE:20130802 ++TRANSP:TRANSPARENT ++SUMMARY:Swiss National Day ++DTSTART;VALUE=DATE:20130801 ++DTSTAMP:20130718T074538Z ++SEQUENCE:2 ++DESCRIPTION:German: Schweizer Bundesfeier\nFrench: FĂȘte nationale Suisse ++ \nItalian: Festa nazionale svizzera\nRomansh: Fiasta naziunala Svizra ++END:VEVENT ++ ++END:VCALENDAR +diff --git a/plugins/libkolab/lib/kolab_format_xcal.php b/plugins/libkolab/lib/kolab_format_xcal.php +index 284a068..500dfa2 100644 +--- a/plugins/libkolab/lib/kolab_format_xcal.php ++++ b/plugins/libkolab/lib/kolab_format_xcal.php +@@ -373,7 +373,7 @@ abstract class kolab_format_xcal extends kolab_format + if ($object['alarms']) { + list($offset, $type) = explode(":", $object['alarms']); + +- if ($type == 'EMAIL') { // email alarms implicitly go to event owner ++ if ($type == 'EMAIL' && !empty($object['_owner'])) { // email alarms implicitly go to event owner + $recipients = new vectorcontactref; + $recipients->push(new ContactReference(ContactReference::EmailReference, $object['_owner'])); + $alarm = new Alarm($object['title'], strval($object['description']), $recipients); +diff --git a/plugins/libkolab/lib/kolab_storage_cache.php b/plugins/libkolab/lib/kolab_storage_cache.php +index a4fd34c..54b4e7a 100644 +--- a/plugins/libkolab/lib/kolab_storage_cache.php ++++ b/plugins/libkolab/lib/kolab_storage_cache.php +@@ -318,8 +318,8 @@ class kolab_storage_cache + } + + // keep a copy in memory for fast access +- $this->objects[$msguid] = $object; +- $this->uid2msg[$object['uid']] = $msguid; ++ $this->objects = array($msguid => $object); ++ $this->uid2msg = array($object['uid'] => $msguid); + } + +
View file
debian.tar.gz/patches/roundcubemail-plugins-kolab-3.1.6-2463-all-day-events-are-off-one-day.patch
Added
@@ -0,0 +1,51 @@ +commit 5924783b18b5c423e58b30730bfdee00ca6c85b0 +Author: Thomas Bruederli <bruederli@kolabsys.com> +Date: Fri Nov 1 11:23:42 2013 +0100 + + Don't set timezone on allday dates (#2463) + +diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php +index e8e5ab2..f08798e 100644 +--- a/plugins/calendar/calendar.php ++++ b/plugins/calendar/calendar.php +@@ -1217,7 +1217,7 @@ class calendar extends rcube_plugin + if ($event['recurrence']) { + $event['recurrence_text'] = $this->_recurrence_text($event['recurrence']); + if ($event['recurrence']['UNTIL']) +- $event['recurrence']['UNTIL'] = $this->lib->adjust_timezone($event['recurrence']['UNTIL'])->format('c'); ++ $event['recurrence']['UNTIL'] = $this->lib->adjust_timezone($event['recurrence']['UNTIL'], $event['allday'])->format('c'); + unset($event['recurrence']['EXCEPTIONS']); + } + +@@ -1248,8 +1248,8 @@ class calendar extends rcube_plugin + + return array( + '_id' => $event['calendar'] . ':' . $event['id'], // unique identifier for fullcalendar +- 'start' => $this->lib->adjust_timezone($event['start'])->format('c'), +- 'end' => $this->lib->adjust_timezone($event['end'])->format('c'), ++ 'start' => $this->lib->adjust_timezone($event['start'], $event['allday'])->format('c'), ++ 'end' => $this->lib->adjust_timezone($event['end'], $event['allday'])->format('c'), + // 'changed' might be empty for event recurrences (Bug #2185) + 'changed' => $event['changed'] ? $this->lib->adjust_timezone($event['changed'])->format('c') : null, + 'title' => strval($event['title']), +diff --git a/plugins/libcalendaring/libcalendaring.php b/plugins/libcalendaring/libcalendaring.php +index 47cf67e..4cd5782 100644 +--- a/plugins/libcalendaring/libcalendaring.php ++++ b/plugins/libcalendaring/libcalendaring.php +@@ -120,14 +120,14 @@ class libcalendaring extends rcube_plugin + * @param mixed Any kind of a date representation (DateTime object, string or unix timestamp) + * @return object DateTime object in user's timezone + */ +- public function adjust_timezone($dt) ++ public function adjust_timezone($dt, $dateonly = false) + { + if (is_numeric($dt)) + $dt = new DateTime('@'.$dt); + else if (is_string($dt)) + $dt = new DateTime($dt); + +- if ($dt instanceof DateTime) { ++ if ($dt instanceof DateTime && !($dt->_dateonly || $dateonly)) { + $dt->setTimezone($this->timezone); + } +
View file
debian.tar.gz/patches/roundcubemail-plugins-kolab-3.1.6-fix-replacement-of-alarm-values.patch
Added
@@ -0,0 +1,19 @@ +commit 2784e1a2dd79de0a13fe4cf75ce1d6ce8733d6a3 +Author: Thomas Bruederli <bruederli@kolabsys.com> +Date: Fri Nov 1 14:47:46 2013 +0100 + + Fix replacement of alarm values + +diff --git a/plugins/calendar/drivers/kolab/kolab_calendar.php b/plugins/calendar/drivers/kolab/kolab_calendar.php +index bd42f56..9eb70bd 100644 +--- a/plugins/calendar/drivers/kolab/kolab_calendar.php ++++ b/plugins/calendar/drivers/kolab/kolab_calendar.php +@@ -581,7 +581,7 @@ class kolab_calendar + + // The web client only supports DISPLAY type of alarms + if (!empty($record['alarms'])) +- $record['alarms'] = preg_replace('/:[A-Z]+$/', 'DISPLAY', $record['alarms']); ++ $record['alarms'] = preg_replace('/:[A-Z]+$/', ':DISPLAY', $record['alarms']); + + // remove empty recurrence array + if (empty($record['recurrence']))
View file
debian.tar.gz/patches/roundcubemail-plugins-kolab-3.1.6-task-enhancements.patch
Added
@@ -0,0 +1,1472 @@ +diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php +index f038caf..685745d 100644 +--- a/plugins/calendar/calendar.php ++++ b/plugins/calendar/calendar.php +@@ -126,6 +126,7 @@ class calendar extends rcube_plugin + $this->register_action('mailtoevent', array($this, 'mail_message2event')); + $this->register_action('inlineui', array($this, 'get_inline_ui')); + $this->register_action('check-recent', array($this, 'check_recent')); ++ $this->add_hook('refresh', array($this, 'refresh')); + + // remove undo information... + if ($undo = $_SESSION['calendar_event_undo']) { +@@ -162,6 +163,8 @@ class calendar extends rcube_plugin + 'innerclass' => 'icon calendar', + ))), + 'messagemenu'); ++ ++ $this->api->output->add_label('calendar.createfrommail'); + } + } + +@@ -919,6 +922,38 @@ class calendar extends rcube_plugin + } + + /** ++ * Handler for keep-alive requests ++ * This will check for updated data in active calendars and sync them to the client ++ */ ++ public function refresh($attr) ++ { ++ // refresh the entire calendar every 10th time to also sync deleted events ++ $refetch = rand(0,10) == 10; ++ ++ foreach ($this->driver->list_calendars(true) as $cal) { ++ if ($refetch) { ++ $this->rc->output->command('plugin.refresh_calendar', ++ array('source' => $cal['id'], 'refetch' => true)); ++ } ++ else { ++ $events = $this->driver->load_events( ++ get_input_value('start', RCUBE_INPUT_GET), ++ get_input_value('end', RCUBE_INPUT_GET), ++ get_input_value('q', RCUBE_INPUT_GET), ++ $cal['id'], ++ 1, ++ $attr['last'] ++ ); ++ ++ foreach ($events as $event) { ++ $this->rc->output->command('plugin.refresh_calendar', ++ array('source' => $cal['id'], 'update' => $this->_client_event($event))); ++ } ++ } ++ } ++ } ++ ++ /** + * Handler for pending_alarms plugin hook triggered by the calendar module on keep-alive requests. + * This will check for pending notifications and pass them to the client + */ +@@ -975,10 +1010,18 @@ class calendar extends rcube_plugin + + if (!$err && $_FILES['_data']['tmp_name']) { + $calendar = get_input_value('calendar', RCUBE_INPUT_GPC); +- $events = $this->get_ical()->import_from_file($_FILES['_data']['tmp_name']); +- +- $count = $errors = 0; + $rangestart = $_REQUEST['_range'] ? date_create("now -" . intval($_REQUEST['_range']) . " months") : 0; ++ $count = $errors = 0; ++ ++ try { ++ $events = $this->get_ical()->import_from_file($_FILES['_data']['tmp_name'], 'UTF-8', true); ++ } ++ catch (Exception $e) { ++ $errors = 1; ++ $msg = $e->getMessage(); ++ $events = array(); ++ } ++ + foreach ($events as $event) { + // TODO: correctly handle recurring events which start before $rangestart + if ($event['end'] < $rangestart && (!$event['recurrence'] || ($event['recurrence']['until'] && $event['recurrence']['until'] < $rangestart))) +@@ -1000,8 +1043,9 @@ class calendar extends rcube_plugin + $this->rc->output->command('display_message', $this->gettext('importnone'), 'notice'); + $this->rc->output->command('plugin.import_success', array('source' => $calendar)); + } +- else +- $this->rc->output->command('display_message', $this->gettext('importerror'), 'error'); ++ else { ++ $this->rc->output->command('plugin.import_error', array('message' => $this->gettext('importerror') . ($msg ? ': ' . $msg : ''))); ++ } + } + else { + if ($err == UPLOAD_ERR_INI_SIZE || $err == UPLOAD_ERR_FORM_SIZE) { +@@ -1012,7 +1056,7 @@ class calendar extends rcube_plugin + $msg = rcube_label('fileuploaderror'); + } + +- $this->rc->output->command('display_message', $msg, 'error'); ++ $this->rc->output->command('plugin.import_error', array('message' => $msg)); + $this->rc->output->command('plugin.unlock_saving', false); + } + +diff --git a/plugins/calendar/calendar_base.js b/plugins/calendar/calendar_base.js +index c60c89a..33fe9e4 100644 +--- a/plugins/calendar/calendar_base.js ++++ b/plugins/calendar/calendar_base.js +@@ -6,7 +6,7 @@ + * @author Thomas Bruederli <bruederli@kolabsys.com> + * + * Copyright (C) 2010, Lazlo Westerhof <hello@lazlo.me> +- * Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com> ++ * Copyright (C) 2013, Kolab Systems AG <contact@kolabsys.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as +@@ -36,10 +36,9 @@ function rcube_calendar(settings) + var me = this; + + // create new event from current mail message +- this.create_from_mail = function() ++ this.create_from_mail = function(uid) + { +- var uid; +- if ((uid = rcmail.get_single_uid())) { ++ if (uid || (uid = rcmail.get_single_uid())) { + // load calendar UI (scripts and edit dialog template) + if (!this.ui_loaded) { + $.when( +@@ -53,7 +52,7 @@ function rcube_calendar(settings) + + me.ui_loaded = true; + me.ui = new rcube_calendar_ui(me.settings); +- me.create_from_mail(); // start over ++ me.create_from_mail(uid); // start over + }); + return; + } +@@ -156,9 +155,6 @@ window.rcmail && rcmail.addEventListener('init', function(evt) { + + // register create-from-mail command to message_commands array + if (rcmail.env.task == 'mail') { +- // place link above 'view source' +- $('#messagemenu a.calendarlink').parent().insertBefore($('#messagemenu a.sourcelink').parent()); +- + rcmail.register_command('calendar-create-from-mail', function() { cal.create_from_mail() }); + rcmail.addEventListener('plugin.mail2event_dialog', function(p){ cal.mail2event_dialog(p) }); + rcmail.addEventListener('plugin.unlock_saving', function(p){ cal.ui && cal.ui.unlock_saving(); }); +@@ -169,6 +165,15 @@ window.rcmail && rcmail.addEventListener('init', function(evt) { + } + else + rcmail.enable_command('calendar-create-from-mail', true); ++ ++ // add contextmenu item ++ if (window.rcm_contextmenu_register_command) { ++ rcm_contextmenu_register_command( ++ 'calendar-create-from-mail', ++ function(cmd,el){ cal.create_from_mail() }, ++ 'calendar.createfrommail', ++ 'moveto'); ++ } + } + } + +diff --git a/plugins/calendar/calendar_ui.js b/plugins/calendar/calendar_ui.js +index 986113f..d5a4308 100644 +--- a/plugins/calendar/calendar_ui.js ++++ b/plugins/calendar/calendar_ui.js +@@ -1981,10 +1981,17 @@ function rcube_calendar_ui(settings) + if (form && form.elements._data.value) { + rcmail.async_upload_form(form, 'import_events', function(e) { + rcmail.set_busy(false, null, me.saving_lock); ++ $('.ui-dialog-buttonpane button', $dialog.parent()).button('enable'); ++ ++ // display error message if no sophisticated response from server arrived (e.g. iframe load error) ++ if (me.import_succeeded === null) ++ rcmail.display_message(rcmail.get_label('importerror', 'calendar'), 'error'); + }); + + // display upload indicator ++ me.import_succeeded = null; + me.saving_lock = rcmail.set_busy(true, 'uploading'); ++ $('.ui-dialog-buttonpane button', $dialog.parent()).button('disable'); + } + }; + +@@ -1999,6 +2006,7 @@ function rcube_calendar_ui(settings) + closeOnEscape: false, + title: rcmail.gettext('importevents', 'calendar'), + close: function() { ++ $('.ui-dialog-buttonpane button', $dialog.parent()).button('enable'); + $dialog.dialog("destroy").hide(); + }, + buttons: buttons, +@@ -2010,6 +2018,7 @@ function rcube_calendar_ui(settings) + // callback from server if import succeeded + this.import_success = function(p) + { ++ this.import_succeeded = true; + $("#eventsimport:ui-dialog").dialog('close'); + rcmail.set_busy(false, null, me.saving_lock); + rcmail.gui_objects.importform.reset(); +@@ -2018,6 +2027,13 @@ function rcube_calendar_ui(settings) + this.refresh(p); + }; + ++ // callback from server to report errors on import ++ this.import_error = function(p) ++ { ++ this.import_succeeded = false; ++ rcmail.display_message(p.message || rcmail.get_label('importerror', 'calendar'), 'error'); ++ } ++ + // show URL of the given calendar in a dialog box + this.showurl = function(calendar) + { +@@ -2087,6 +2103,20 @@ function rcube_calendar_ui(settings) + fc.fullCalendar('removeEvents', function(e){ return e.temp; }); + }; + ++ // modify query parameters for refresh requests ++ this.before_refresh = function(query) ++ { ++ var view = fc.fullCalendar('getView'); ++ ++ query.start = date2unixtime(view.visStart); ++ query.end = date2unixtime(view.visEnd); ++ ++ if (this.search_query) ++ query.q = this.search_query; ++ ++ return query; ++ }; ++ + + /*** event searching ***/ + +@@ -2780,6 +2810,8 @@ window.rcmail && rcmail.addEventListener('init', function(evt) { + rcmail.addEventListener('plugin.unlock_saving', function(p){ cal.unlock_saving(); }); + rcmail.addEventListener('plugin.refresh_calendar', function(p){ cal.refresh(p); }); + rcmail.addEventListener('plugin.import_success', function(p){ cal.import_success(p); }); ++ rcmail.addEventListener('plugin.import_error', function(p){ cal.import_error(p); }); ++ rcmail.addEventListener('requestrefresh', function(q){ return cal.before_refresh(q); }); + + // let's go + var cal = new rcube_calendar_ui($.extend(rcmail.env.calendar_settings, rcmail.env.libcal_settings)); +diff --git a/plugins/calendar/drivers/calendar_driver.php b/plugins/calendar/drivers/calendar_driver.php +index 52de901..c09d8b9 100644 +--- a/plugins/calendar/drivers/calendar_driver.php ++++ b/plugins/calendar/drivers/calendar_driver.php +@@ -236,9 +236,11 @@ abstract class calendar_driver + * @param integer Event's new end (unix timestamp) + * @param string Search query (optional) + * @param mixed List of calendar IDs to load events from (either as array or comma-separated string) ++ * @param boolean Include virtual/recurring events (optional) ++ * @param integer Only list events modified since this time (unix timestamp) + * @return array A list of event objects (see header of this file for struct of an event) + */ +- abstract function load_events($start, $end, $query = null, $calendars = null); ++ abstract function load_events($start, $end, $query = null, $calendars = null, $virtual = 1, $modifiedsince = null); + + /** + * Get a list of pending alarms to be displayed to the user +diff --git a/plugins/calendar/drivers/database/database_driver.php b/plugins/calendar/drivers/database/database_driver.php +index 8cd363c..a2cb903 100644 +--- a/plugins/calendar/drivers/database/database_driver.php ++++ b/plugins/calendar/drivers/database/database_driver.php +@@ -724,7 +724,7 @@ class database_driver extends calendar_driver + * + * @see calendar_driver::load_events() + */ +- public function load_events($start, $end, $query = null, $calendars = null) ++ public function load_events($start, $end, $query = null, $calendars = null, $virtual = 1, $modifiedsince = null) + { + if (empty($calendars)) + $calendars = array_keys($this->calendars); +@@ -742,6 +742,12 @@ class database_driver extends calendar_driver + $sql_add = 'AND (' . join(' OR ', $sql_query) . ')'; + } + ++ if (!$virtual) ++ $sql_arr .= ' AND e.recurrence_id = 0'; ++ ++ if ($modifiedsince) ++ $sql_add .= ' AND e.changed >= ' . $this->rc->db->quote(date('Y-m-d H:i:s', $modifiedsince)); ++ + $events = array(); + if (!empty($calendar_ids)) { + $result = $this->rc->db->query(sprintf( +diff --git a/plugins/calendar/drivers/kolab/kolab_driver.php b/plugins/calendar/drivers/kolab/kolab_driver.php +index b8171b7..0722d23 100644 +--- a/plugins/calendar/drivers/kolab/kolab_driver.php ++++ b/plugins/calendar/drivers/kolab/kolab_driver.php +@@ -733,20 +733,25 @@ class kolab_driver extends calendar_driver + * @param integer Event's new end (unix timestamp) + * @param string Search query (optional) + * @param mixed List of calendar IDs to load events from (either as array or comma-separated string) +- * @param boolean Strip virtual events (optional) ++ * @param boolean Include virtual events (optional) ++ * @param integer Only list events modified since this time (unix timestamp) + * @return array A list of event records + */ +- public function load_events($start, $end, $search = null, $calendars = null, $virtual = 1) ++ public function load_events($start, $end, $search = null, $calendars = null, $virtual = 1, $modifiedsince = null) + { + if ($calendars && is_string($calendars)) + $calendars = explode(',', $calendars); + ++ $query = array(); ++ if ($modifiedsince) ++ $query[] = array('changed', '>=', $modifiedsince); ++ + $events = $categories = array(); + foreach (array_keys($this->calendars) as $cid) { + if ($calendars && !in_array($cid, $calendars)) + continue; + +- $events = array_merge($events, $this->calendars[$cid]->list_events($start, $end, $search, $virtual)); ++ $events = array_merge($events, $this->calendars[$cid]->list_events($start, $end, $search, $virtual, $query)); + $categories += $this->calendars[$cid]->categories; + } + +diff --git a/plugins/tasklist/drivers/database/tasklist_database_driver.php b/plugins/tasklist/drivers/database/tasklist_database_driver.php +index 8ad776a..3c5ad38 100644 +--- a/plugins/tasklist/drivers/database/tasklist_database_driver.php ++++ b/plugins/tasklist/drivers/database/tasklist_database_driver.php +@@ -286,7 +286,7 @@ class tasklist_database_driver extends tasklist_driver + + if ($filter['mask'] & tasklist::FILTER_MASK_COMPLETE) + $sql_add .= ' AND complete=1'; +- else // don't show complete tasks by default ++ else if (empty($filter['since'])) // don't show complete tasks by default + $sql_add .= ' AND complete<1'; + + if ($filter['mask'] & tasklist::FILTER_MASK_FLAGGED) +@@ -301,6 +301,10 @@ class tasklist_database_driver extends tasklist_driver + $sql_add = 'AND (' . join(' OR ', $sql_query) . ')'; + } + ++ if ($filter['since'] && is_numeric($filter['since'])) { ++ $sql_add .= ' AND changed >= ' . $this->rc->db->quote(date('Y-m-d H:i:s', $filter['since'])); ++ } ++ + $tasks = array(); + if (!empty($list_ids)) { + $result = $this->rc->db->query(sprintf( +diff --git a/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php b/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php +index c630a41..fd3e5c3 100644 +--- a/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php ++++ b/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php +@@ -371,7 +371,7 @@ class tasklist_kolab_driver extends tasklist_driver + $query = array(); + if ($filter['mask'] & tasklist::FILTER_MASK_COMPLETE) + $query[] = array('tags','~','x-complete'); +- else ++ else if (empty($filter['since'])) + $query[] = array('tags','!~','x-complete'); + + // full text search (only works with cache enabled) +@@ -382,6 +382,10 @@ class tasklist_kolab_driver extends tasklist_driver + } + } + ++ if ($filter['since']) { ++ $query[] = array('changed', '>=', $filter['since']); ++ } ++ + foreach ($lists as $list_id) { + $folder = $this->folders[$list_id]; + foreach ((array)$folder->select($query) as $record) { +diff --git a/plugins/tasklist/localization/en_US.inc b/plugins/tasklist/localization/en_US.inc +index 57d6c3c..ba331be 100644 +--- a/plugins/tasklist/localization/en_US.inc ++++ b/plugins/tasklist/localization/en_US.inc +@@ -51,6 +51,8 @@ $labels['listactions'] = 'List options...'; + $labels['listname'] = 'Name'; + $labels['showalarms'] = 'Show alarms'; + $labels['import'] = 'Import'; ++$labels['viewoptions'] = 'View options'; ++$labels['focusview'] = 'View only this list'; + + // date words + $labels['on'] = 'on'; +diff --git a/plugins/tasklist/skins/larry/sprites.png b/plugins/tasklist/skins/larry/sprites.png +index 5224f6f..5c6b9fd 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 173704d..7a1df99 100644 +--- a/plugins/tasklist/skins/larry/tasklist.css ++++ b/plugins/tasklist/skins/larry/tasklist.css +@@ -66,7 +66,7 @@ body.attachmentwin #topnav .topright { + } + + #taskselector { +- margin: -4px 0 0; ++ margin: -4px 40px 0 0; + padding: 0; + } + +@@ -180,16 +180,46 @@ body.attachmentwin #topnav .topright { + #tagslist li { + display: inline-block; + color: #004458; +- margin-right: 0.5em; ++ padding-right: 0.2em; ++ margin-right: 0.3em; + margin-bottom: 0.4em; + min-width: 1.2em; + cursor: pointer; + } + ++#tagslist li.inactive { ++ color: #89b3be; ++ padding-right: 0.6em; ++ font-size: 80%; ++/* display: none; */ ++} ++ ++#tagslist li .count { ++ position: relative; ++ top: -1px; ++ margin-left: 5px; ++ padding: 0.15em 0.5em; ++ font-size: 80%; ++ font-weight: bold; ++ color: #59838e; ++ background: #c7e3ef; ++ box-shadow: inset 0 1px 1px 0 #b0ccd7; ++ -o-box-shadow: inset 0 1px 1px 0 #b0ccd7; ++ -webkit-box-shadow: inset 0 1px 1px 0 #b0ccd7; ++ -moz-box-shadow: inset 0 1px 1px 0 #b0ccd7; ++ border-color: #b0ccd7; ++ border-radius: 8px; ++} ++ ++.tag-draghelper .tag .count, ++#tagslist li.inactive .count { ++ display: none; ++} ++ + #tasklists li { + margin: 0; + height: 20px; +- padding: 6px 8px 2px; ++ padding: 6px 8px 2px 6px; + display: block; + position: relative; + white-space: nowrap; +@@ -206,10 +236,13 @@ body.attachmentwin #topnav .topright { + + #tasklists li span.listname { + display: block; ++ position: absolute; ++ top: 7px; ++ left: 26px; ++ right: 26px; + cursor: default; + padding-bottom: 2px; + padding-right: 30px; +- margin-right: 20px; + color: #004458; + overflow: hidden; + text-overflow: ellipsis; +@@ -218,7 +251,20 @@ body.attachmentwin #topnav .topright { + } + + #tasklists li span.handle { +- display: none; ++ display: inline-block; ++ width: 16px; ++ height: 16px; ++ margin-right: 4px; ++ background: url(sprites.png) -200px 0 no-repeat; ++ cursor: pointer; ++} ++ ++#tasklists li:hover span.handle { ++ background-position: -20px -101px; ++} ++ ++#tasklists li.focusview span.handle { ++ background-position: -2px -101px; + } + + #tasklists li.selected span.listname { +@@ -249,6 +295,11 @@ body.attachmentwin #topnav .topright { + color: #aaa; + } + ++#tasklists li.virtual span.handle { ++ background: none; ++ cursor: default; ++} ++ + #tasklists li input { + position: absolute; + top: 5px; +@@ -336,6 +387,27 @@ body.attachmentwin #topnav .topright { + background: -ms-linear-gradient(top, #eee 0%, #dfdfdf 100%); + background: linear-gradient(top, #eee 0%, #dfdfdf 100%); + border-bottom: 1px solid #ccc; ++ position: relative; ++} ++ ++#tasksview .buttonbar .buttonbar-right { ++ position: absolute; ++ top: 6px; ++ right: 8px; ++} ++ ++.buttonbar-right .listmenu { ++ display: inline-block; ++ cursor: pointer; ++} ++ ++.buttonbar-right .listmenu .inner { ++ display: inline-block; ++ height: 18px; ++ width: 20px; ++ padding: 0; ++ background: url(sprites.png) 0 -237px no-repeat; ++ text-indent: -5000px; + } + + #thelist { +@@ -456,6 +528,7 @@ body.attachmentwin #topnav .topright { + text-align: right; + } + ++.tag-draghelper .tag, + .taskhead .tags .tag { + font-size: 85%; + background: #d9ecf4; +@@ -465,6 +538,11 @@ body.attachmentwin #topnav .topright { + margin-right: 3px; + } + ++.tag-draghelper li.tag { ++ list-style: none; ++ font-size: 100%; ++} ++ + .taskhead .date { + position: absolute; + top: 4px; +@@ -547,9 +625,22 @@ body.attachmentwin #topnav .topright { + border-top: 1px solid #219de6; + } + +-ul.toolbarmenu li span.add { ++ul.toolbarmenu li span.add, ++ul.toolbarmenu li span.expand, ++ul.toolbarmenu li span.collapse { + background-image: url(sprites.png); +- background-position: 0 -100px; ++} ++ ++ul.toolbarmenu li span.add { ++ background-position: 0 -302px; ++} ++ ++ul.toolbarmenu li span.expand { ++ background-position: 0 -258px; ++} ++ ++ul.toolbarmenu li span.collapse { ++ background-position: 0 -280px; + } + + ul.toolbarmenu li span.delete { +@@ -806,6 +897,12 @@ label.block { + /* cursor: pointer; */ + } + ++.form-section span.tag-element.inherit { ++ color: #666; ++ background: #f2f2f2; ++ border-color: #ddd; ++} ++ + .tagedit-list li.tagedit-listelement-old a.tagedit-close, + .tagedit-list li.tagedit-listelement-old a.tagedit-break, + .tagedit-list li.tagedit-listelement-old a.tagedit-delete, +diff --git a/plugins/tasklist/skins/larry/templates/mainview.html b/plugins/tasklist/skins/larry/templates/mainview.html +index 9196d02..52463e0 100644 +--- a/plugins/tasklist/skins/larry/templates/mainview.html ++++ b/plugins/tasklist/skins/larry/templates/mainview.html +@@ -59,6 +59,10 @@ + <li class="nodate"><a href="#nodate"><roundcube:label name="tasklist.nodate" ucfirst="true" /></a></li> + <li class="complete"><a href="#complete"><roundcube:label name="tasklist.complete" /><span class="count"></span></a></li> + </ul> ++ ++ <div class="buttonbar-right"> ++ <roundcube:button name="taskviewmenulink" id="taskviewmenulink" type="link" title="tasklist.viewoptions" class="listmenu viewoptions" onclick="UI.show_popup('taskviewmenu');return false" innerClass="inner" content="⚙" /> ++ </div> + </div> + + <div class="scroller"> +@@ -92,6 +96,13 @@ + </ul> + </div> + ++<div id="taskviewmenu" class="popupmenu"> ++ <ul class="toolbarmenu"> ++ <li><roundcube:button command="expand-all" label="expand-all" class="icon" classAct="icon active" innerclass="icon expand" /></li> ++ <li><roundcube:button command="collapse-all" label="collapse-all" class="icon" classAct="icon active" innerclass="icon collapse" /></li> ++ </ul> ++</div> ++ + <div id="taskshow"> + <div class="form-section" id="task-parent-title"></div> + <div class="form-section"> +@@ -146,6 +157,8 @@ $(document).ready(function(e){ + UI.init(); + new rcube_splitter({ id:'taskviewsplitter', p1:'#sidebar', p2:'#mainview-right', + orientation:'v', relative:true, start:240, min:180, size:16, offset:2 }).init(); ++ new rcube_splitter({ id:'taskviewsplitterv', p1:'#tagsbox', p2:'#tasklistsbox', ++ orientation:'h', relative:true, start:242, min:120, size:16, offset:6 }).init(); + }); + + </script> +diff --git a/plugins/tasklist/tasklist.js b/plugins/tasklist/tasklist.js +index d76c9d1..25ab68d 100644 +--- a/plugins/tasklist/tasklist.js ++++ b/plugins/tasklist/tasklist.js +@@ -54,6 +54,7 @@ function rcube_tasklist_ui(settings) + var filtermask = FILTER_MASK_ALL; + var loadstate = { filter:-1, lists:'', search:null }; + var idcount = 0; ++ var focusview; + var saving_lock; + var ui_loading; + var taskcounts = {}; +@@ -64,6 +65,8 @@ function rcube_tasklist_ui(settings) + var search_request; + var search_query; + var completeness_slider; ++ var task_draghelper; ++ var tag_draghelper; + var me = this; + + // general datepicker settings +@@ -92,6 +95,7 @@ function rcube_tasklist_ui(settings) + this.add_childtask = add_childtask; + this.quicksearch = quicksearch; + this.reset_search = reset_search; ++ this.expand_collapse = expand_collapse; + this.list_remove = list_remove; + this.list_edit_dialog = list_edit_dialog; + this.unlock_saving = unlock_saving; +@@ -116,22 +120,30 @@ function rcube_tasklist_ui(settings) + init_tasklist_li(li, id); + } + +- if (me.tasklists[id].editable && !me.selected_list) { ++ if (me.tasklists[id].editable && (!me.selected_list || (me.tasklists[id].active && !me.tasklists[me.selected_list].active))) { + me.selected_list = id; +- rcmail.enable_command('addtask', true); +- $(li).click(); + } + } + ++ if (me.selected_list) { ++ rcmail.enable_command('addtask', true); ++ $(rcmail.get_folder_li(me.selected_list, 'rcmlitasklist')).click(); ++ } ++ + // register server callbacks + rcmail.addEventListener('plugin.data_ready', data_ready); +- rcmail.addEventListener('plugin.refresh_task', update_taskitem); ++ rcmail.addEventListener('plugin.update_task', update_taskitem); ++ rcmail.addEventListener('plugin.refresh_tasks', function(p) { update_taskitem(p, true); }); + rcmail.addEventListener('plugin.update_counts', update_counts); + rcmail.addEventListener('plugin.insert_tasklist', insert_list); + rcmail.addEventListener('plugin.update_tasklist', update_list); + rcmail.addEventListener('plugin.destroy_tasklist', destroy_list); +- rcmail.addEventListener('plugin.reload_data', function(){ list_tasks(null); }); + rcmail.addEventListener('plugin.unlock_saving', unlock_saving); ++ rcmail.addEventListener('requestrefresh', before_refresh); ++ rcmail.addEventListener('plugin.reload_data', function(){ ++ list_tasks(null, true); ++ setTimeout(fetch_counts, 200); ++ }); + + // start loading tasks + fetch_counts(); +@@ -335,7 +347,8 @@ function rcube_tasklist_ui(settings) + $(input).datepicker('widget').find('button.ui-datepicker-close') + .html(rcmail.gettext('nodate','tasklist')) + .attr('onclick', '') +- .click(function(e){ ++ .unbind('click') ++ .bind('click', function(e){ + $(input).datepicker('setDate', null).datepicker('hide'); + }); + }, 1); +@@ -393,7 +406,7 @@ function rcube_tasklist_ui(settings) + /** + * List tasks matching the given selector + */ +- function list_tasks(sel) ++ function list_tasks(sel, force) + { + if (rcmail.busy) + return; +@@ -405,7 +418,7 @@ function rcube_tasklist_ui(settings) + + var active = active_lists(), + basefilter = filtermask == FILTER_MASK_COMPLETE ? FILTER_MASK_COMPLETE : FILTER_MASK_ALL, +- reload = active.join(',') != loadstate.lists || basefilter != loadstate.filter || loadstate.search != search_query; ++ reload = force || active.join(',') != loadstate.lists || basefilter != loadstate.filter || loadstate.search != search_query; + + if (active.length && reload) { + ui_loading = rcmail.set_busy(true, 'loading'); +@@ -439,6 +452,19 @@ function rcube_tasklist_ui(settings) + } + + /** ++ * Modify query parameters for refresh requests ++ */ ++ function before_refresh(query) ++ { ++ query.filter = filtermask == FILTER_MASK_COMPLETE ? FILTER_MASK_COMPLETE : FILTER_MASK_ALL; ++ query.lists = active_lists().join(','); ++ if (search_query) ++ query.q = search_query; ++ ++ return query; ++ } ++ ++ /** + * Callback if task data from server is ready + */ + function data_ready(response) +@@ -459,8 +485,9 @@ function rcube_tasklist_ui(settings) + listdata[listdata[id].parent_id].children.push(id); + } + +- render_tasklist(); + append_tags(response.tags || []); ++ render_tasklist(); ++ + rcmail.set_busy(false, 'loading', ui_loading); + } + +@@ -473,6 +500,7 @@ function rcube_tasklist_ui(settings) + var id, rec, + count = 0, + cache = {}, ++ activetags = {}, + msgbox = $('#listmessagebox').hide(), + list = $(rcmail.gui_objects.resultlist).html(''); + +@@ -482,10 +510,19 @@ function rcube_tasklist_ui(settings) + if (match_filter(rec, cache)) { + render_task(rec); + count++; ++ ++ // keep a list of tags from all visible tasks ++ for (var t, j=0; rec.tags && j < rec.tags.length; j++) { ++ t = rec.tags[j]; ++ if (typeof activetags[t] == 'undefined') ++ activetags[t] = 0; ++ activetags[t]++; ++ } + } + } + + fix_tree_toggles(); ++ update_tagcloud(activetags); + + if (!count) + msgbox.html(rcmail.gettext('notasksfound','tasklist')).show(); +@@ -506,6 +543,30 @@ function rcube_tasklist_ui(settings) + } + + /** ++ * Expand/collapse all task items with childs ++ */ ++ function expand_collapse(expand) ++ { ++ var collapsed = !expand; ++ ++ $('.taskitem .childtasks')[(collapsed ? 'hide' : 'show')](); ++ $('.taskitem .childtoggle') ++ .removeClass(collapsed ? 'expanded' : 'collapsed') ++ .addClass(collapsed ? 'collapsed' : 'expanded') ++ .html(collapsed ? '▶' : '▼'); ++ ++ // store new toggle collapse states ++ var ids = []; ++ for (var id in listdata) { ++ if (listdata[id].children && listdata[id].children.length) ++ ids.push(id); ++ } ++ if (ids.length) { ++ rcmail.http_post('tasks/task', { action:'collapse', t:{ id:ids.join(',') }, collapsed:collapsed?1:0 }); ++ } ++ } ++ ++ /** + * + */ + function append_tags(taglist) +@@ -520,8 +581,19 @@ function rcube_tasklist_ui(settings) + + // append new tags to tag cloud + $.each(newtags, function(i, tag){ +- $('<li>').attr('rel', tag).data('value', tag).html(Q(tag)).appendTo(rcmail.gui_objects.tagslist); +- }); ++ $('<li>').attr('rel', tag).data('value', tag) ++ .html(Q(tag) + '<span class="count"></span>') ++ .appendTo(rcmail.gui_objects.tagslist) ++ .draggable({ ++ addClasses: false, ++ revert: 'invalid', ++ revertDuration: 300, ++ helper: tag_draggable_helper, ++ start: tag_draggable_start, ++ appendTo: 'body', ++ cursor: 'pointer' ++ }); ++ }); + + // re-sort tags list + $(rcmail.gui_objects.tagslist).children('li').sortElements(function(a,b){ +@@ -530,6 +602,90 @@ function rcube_tasklist_ui(settings) + } + + /** ++ * Display the given counts to each tag and set those inactive which don't ++ * have any matching tasks in the current view. ++ */ ++ function update_tagcloud(counts) ++ { ++ // compute counts first by iterating over all visible task items ++ if (typeof counts == 'undefined') { ++ counts = {}; ++ $('li.taskitem', rcmail.gui_objects.resultlist).each(function(i,li){ ++ var t, id = $(li).attr('rel'), ++ rec = listdata[id]; ++ for (var j=0; rec && rec.tags && j < rec.tags.length; j++) { ++ t = rec.tags[j]; ++ if (typeof counts[t] == 'undefined') ++ counts[t] = 0; ++ counts[t]++; ++ } ++ }); ++ } ++ ++ $(rcmail.gui_objects.tagslist).children('li').each(function(i,li){ ++ var elem = $(li), tag = elem.attr('rel'), ++ count = counts[tag] || 0; ++ ++ elem.children('.count').html(count+''); ++ if (count == 0) elem.addClass('inactive'); ++ else elem.removeClass('inactive'); ++ }); ++ } ++ ++ /* Helper functions for drag & drop functionality of tags */ ++ ++ function tag_draggable_helper() ++ { ++ if (!tag_draghelper) ++ tag_draghelper = $('<div class="tag-draghelper"></div>'); ++ else ++ tag_draghelper.html(''); ++ ++ $(this).clone().addClass('tag').appendTo(tag_draghelper); ++ return tag_draghelper; ++ } ++ ++ function tag_draggable_start(event, ui) ++ { ++ $('.taskhead').droppable({ ++ hoverClass: 'droptarget', ++ accept: tag_droppable_accept, ++ drop: tag_draggable_dropped, ++ addClasses: false ++ }); ++ } ++ ++ function tag_droppable_accept(draggable) ++ { ++ if (rcmail.busy) ++ return false; ++ ++ var tag = draggable.data('value'), ++ drop_id = $(this).data('id'), ++ drop_rec = listdata[drop_id]; ++ ++ // target already has this tag assigned ++ if (!drop_rec || (drop_rec.tags && $.inArray(tag, drop_rec.tags) >= 0)) { ++ return false; ++ } ++ ++ return true; ++ } ++ ++ function tag_draggable_dropped(event, ui) ++ { ++ var drop_id = $(this).data('id'), ++ tag = ui.draggable.data('value'), ++ rec = listdata[drop_id]; ++ ++ if (rec && rec.id) { ++ if (!rec.tags) rec.tags = []; ++ rec.tags.push(tag); ++ save_task(rec, 'edit'); ++ } ++ } ++ ++ /** + * + */ + function update_counts(counts) +@@ -553,11 +709,11 @@ function rcube_tasklist_ui(settings) + /** + * Callback from server to update a single task item + */ +- function update_taskitem(rec) ++ function update_taskitem(rec, filter) + { + // handle a list of task records + if ($.isArray(rec)) { +- $.each(rec, function(i,r){ update_taskitem(r); }); ++ $.each(rec, function(i,r){ update_taskitem(r, filter); }); + return; + } + +@@ -597,12 +753,16 @@ function rcube_tasklist_ui(settings) + } + } + +- if (list.active) +- render_task(rec, oldid); +- else ++ if (list.active || rec.tempid) { ++ if (!filter || match_filter(rec, {})) ++ render_task(rec, oldid); ++ } ++ else { + $('li[rel="'+id+'"]', rcmail.gui_objects.resultlist).remove(); ++ } + + append_tags(rec.tags || []); ++ update_tagcloud(); + fix_tree_toggles(); + } + +@@ -655,10 +815,10 @@ function rcube_tasklist_ui(settings) + revert: 'invalid', + addClasses: false, + cursorAt: { left:-10, top:12 }, +- helper: draggable_helper, ++ helper: task_draggable_helper, + appendTo: 'body', +- start: draggable_start, +- stop: draggable_stop, ++ start: task_draggable_start, ++ stop: task_draggable_stop, + revertDuration: 300 + }); + +@@ -703,7 +863,7 @@ function rcube_tasklist_ui(settings) + */ + function resort_task(rec, li, animated) + { +- var dir = 0, index, slice, next_li, next_id, next_rec; ++ var dir = 0, index, slice, cmp, next_li, next_id, next_rec, insert_after, past_myself; + + // animated moving + var insert_animated = function(li, before, after) { +@@ -729,33 +889,36 @@ function rcube_tasklist_ui(settings) + } + + // find the right place to insert the task item +- li.siblings().each(function(i, elem){ ++ li.parent().children('.taskitem').each(function(i, elem){ + next_li = $(elem); + next_id = next_li.attr('rel'); + next_rec = listdata[next_id]; + + if (next_id == rec.id) { +- next_li = null; ++ past_myself = true; + return 1; // continue + } + +- if (next_rec && task_cmp(rec, next_rec) > 0) { ++ cmp = next_rec ? task_cmp(rec, next_rec) : 0; ++ ++ if (cmp > 0 || (cmp == 0 && !past_myself)) { ++ insert_after = next_li; + return 1; // continue; + } +- else if (next_rec && next_li && task_cmp(rec, next_rec) < 0) { ++ else if (next_li && cmp < 0) { + if (animated) insert_animated(li, next_li); + else li.insertBefore(next_li); +- next_li = null; +- return false; ++ index = $.inArray(next_id, listindex); ++ return false; // break + } + }); + +- index = $.inArray(next_id, listindex); ++ if (insert_after) { ++ if (animated) insert_animated(li, null, insert_after); ++ else li.insertAfter(insert_after); + +- if (next_li) { +- if (animated) insert_animated(li, null, next_li); +- else li.insertAfter(next_li); +- index++; ++ next_id = insert_after.attr('rel'); ++ index = $.inArray(next_id, listindex); + } + + // insert into list index +@@ -799,20 +962,20 @@ function rcube_tasklist_ui(settings) + + /* Helper functions for drag & drop functionality */ + +- function draggable_helper() ++ function task_draggable_helper() + { +- if (!draghelper) +- draghelper = $('<div class="taskitem-draghelper">✔</div>'); ++ if (!task_draghelper) ++ task_draghelper = $('<div class="taskitem-draghelper">✔</div>'); + +- return draghelper; ++ return task_draghelper; + } + +- function draggable_start(event, ui) ++ function task_draggable_start(event, ui) + { + $('.taskhead, #rootdroppable, #'+rcmail.gui_objects.folderlist.id+' li').droppable({ + hoverClass: 'droptarget', +- accept: droppable_accept, +- drop: draggable_dropped, ++ accept: task_droppable_accept, ++ drop: task_draggable_dropped, + addClasses: false + }); + +@@ -820,13 +983,13 @@ function rcube_tasklist_ui(settings) + $('#rootdroppable').show(); + } + +- function draggable_stop(event, ui) ++ function task_draggable_stop(event, ui) + { + $(this).parent().removeClass('dragging'); + $('#rootdroppable').hide(); + } + +- function droppable_accept(draggable) ++ function task_droppable_accept(draggable) + { + if (rcmail.busy) + return false; +@@ -858,7 +1021,7 @@ function rcube_tasklist_ui(settings) + return true; + } + +- function draggable_dropped(event, ui) ++ function task_draggable_dropped(event, ui) + { + var drop_id = $(this).data('id'), + task_id = ui.draggable.data('id'), +@@ -921,10 +1084,23 @@ function rcube_tasklist_ui(settings) + $('#task-completeness .task-text').html(((rec.complete || 0) * 100) + '%'); + $('#task-list .task-text').html(Q(me.tasklists[rec.list] ? me.tasklists[rec.list].name : '')); + +- var taglist = $('#task-tags')[(rec.tags && rec.tags.length ? 'show' : 'hide')]().children('.task-text').empty(); ++ var itags = get_inherited_tags(rec); ++ var taglist = $('#task-tags')[(rec.tags && rec.tags.length || itags.length ? 'show' : 'hide')]().children('.task-text').empty(); + if (rec.tags && rec.tags.length) { + $.each(rec.tags, function(i,val){ +- $('<span>').addClass('tag-element').html(Q(val)).data('value', val).appendTo(taglist); ++ $('<span>').addClass('tag-element').html(Q(val)).appendTo(taglist); ++ }); ++ } ++ ++ // append inherited tags ++ if (itags.length) { ++ $.each(itags, function(i,val){ ++ if (!rec.tags || $.inArray(val, rec.tags) < 0) ++ $('<span>').addClass('tag-element inherit').html(Q(val)).appendTo(taglist); ++ }); ++ // re-sort tags list ++ $(taglist).children().sortElements(function(a,b){ ++ return $.text([a]).toLowerCase() > $.text([b]).toLowerCase() ? 1 : -1; + }); + } + +@@ -1012,7 +1188,7 @@ function rcube_tasklist_ui(settings) + var recstarttime = $('#taskedit-starttime').val(rec.starttime || ''); + var complete = $('#taskedit-completeness').val((rec.complete || 0) * 100); + completeness_slider.slider('value', complete.val()); +- var tasklist = $('#taskedit-tasklist').val(rec.list || 0).prop('disabled', rec.parent_id ? true : false); ++ var tasklist = $('#taskedit-tasklist').val(rec.list || me.selected_list).prop('disabled', rec.parent_id ? true : false); + + // tag-edit line + var tagline = $(rcmail.gui_objects.edittagline).empty(); +@@ -1102,10 +1278,16 @@ function rcube_tasklist_ui(settings) + } + } + ++ // collect tags + $('input[type="hidden"]', rcmail.gui_objects.edittagline).each(function(i,elem){ + if (elem.value) + me.selected_task.tags.push(elem.value); + }); ++ // including the "pending" one in the text box ++ var newtag = $('#tagedit-input').val(); ++ if (newtag != '') { ++ me.selected_task.tags.push(newtag); ++ } + + // serialize alarm settings + var alarm = $('#taskedit select.edit-alarm-type').val(); +@@ -1124,7 +1306,7 @@ function rcube_tasklist_ui(settings) + } + + // task assigned to a new list +- if (me.selected_task.list && me.selected_task.list != rec.list) { ++ if (me.selected_task.list && listdata[id] && me.selected_task.list != listdata[id].list) { + me.selected_task._fromlist = rec.list; + } + +@@ -1135,6 +1317,9 @@ function rcube_tasklist_ui(settings) + if (!me.selected_task.list && list.id) + me.selected_task.list = list.id; + ++ if (!me.selected_task.tags.length) ++ me.selected_task.tags = ''; ++ + if (save_task(me.selected_task, action)) + $dialog.dialog('close'); + }; +@@ -1367,12 +1552,17 @@ function rcube_tasklist_ui(settings) + return cache[rec.id]; + } + +- var match = !filtermask || (filtermask & rec.mask) > 0 ++ var match = !filtermask || (filtermask & rec.mask) > 0; ++ ++ // in focusview mode, only tasks from the selected list are allowed ++ if (focusview && rec.list != focusview) ++ match = false; + + if (match && tagsfilter.length) { + match = rec.tags && rec.tags.length; ++ var alltags = get_inherited_tags(rec).concat(rec.tags || []); + for (var i=0; match && i < tagsfilter.length; i++) { +- if ($.inArray(tagsfilter[i], rec.tags) < 0) ++ if ($.inArray(tagsfilter[i], alltags) < 0) + match = false; + } + } +@@ -1402,6 +1592,23 @@ function rcube_tasklist_ui(settings) + /** + * + */ ++ function get_inherited_tags(rec) ++ { ++ var parent_id, itags = []; ++ ++ if ((parent_id = rec.parent_id)) { ++ while (parent_id && listdata[parent_id]) { ++ itags = itags.concat(listdata[parent_id].tags || []); ++ parent_id = listdata[parent_id].parent_id; ++ } ++ } ++ ++ return $.unqiqueStrings(itags); ++ } ++ ++ /** ++ * ++ */ + function list_edit_dialog(id) + { + var list = me.tasklists[id], +@@ -1671,6 +1878,11 @@ function rcube_tasklist_ui(settings) + if (!this.checked) remove_tasks(id); + else list_tasks(null); + rcmail.http_post('tasklist', { action:'subscribe', l:{ id:id, active:me.tasklists[id].active?1:0 } }); ++ ++ // disable focusview ++ if (!this.checked && focusview == id) { ++ set_focusview(null); ++ } + } + }).data('id', id).get(0).checked = me.tasklists[id].active || false; + +@@ -1679,6 +1891,15 @@ function rcube_tasklist_ui(settings) + rcmail.select_folder(id, 'rcmlitasklist'); + rcmail.enable_command('list-edit', 'list-remove', 'list-import', me.tasklists[id].editable); + me.selected_list = id; ++ ++ // click on handle icon toggles focusview ++ if (e.target.className == 'handle') { ++ set_focusview(focusview == id ? null : id) ++ } ++ // disable focusview when selecting another list ++ else if (focusview && id != focusview) { ++ set_focusview(null); ++ } + }) + .dblclick(function(e){ + list_edit_dialog($(this).data('id')); +@@ -1688,6 +1909,31 @@ function rcube_tasklist_ui(settings) + .addClass(me.tasklists[id].editable ? null : 'readonly'); + } + ++ /** ++ * Enable/disable focusview mode for the given list ++ */ ++ function set_focusview(id) ++ { ++ if (focusview && focusview != id) ++ $(rcmail.get_folder_li(focusview, 'rcmlitasklist')).removeClass('focusview'); ++ ++ focusview = id; ++ ++ // activate list if necessary ++ if (focusview && !me.tasklists[id].active) { ++ $('input', rcmail.get_folder_li(id, 'rcmlitasklist')).get(0).checked = true; ++ me.tasklists[id].active = true; ++ fetch_counts(); ++ } ++ ++ // update list ++ list_tasks(null); ++ ++ if (focusview) { ++ $(rcmail.get_folder_li(focusview, 'rcmlitasklist')).addClass('focusview'); ++ } ++ } ++ + + // init dialog by default + init_taskedit(); +@@ -1714,6 +1960,22 @@ jQuery.fn.sortElements = (function(){ + }; + })(); + ++// equivalent to $.unique() but working on arrays of strings ++jQuery.unqiqueStrings = (function() { ++ return function(arr) { ++ var hash = {}, out = []; ++ ++ for (var i = 0; i < arr.length; i++) { ++ hash[arr[i]] = 0; ++ } ++ for (var val in hash) { ++ out.push(val); ++ } ++ ++ return out; ++ }; ++})(); ++ + + /* tasklist plugin UI initialization */ + var rctasks; +@@ -1731,6 +1993,8 @@ window.rcmail && rcmail.addEventListener('init', function(evt) { + + rcmail.register_command('search', function(){ rctasks.quicksearch(); }, true); + rcmail.register_command('reset-search', function(){ rctasks.reset_search(); }, true); ++ rcmail.register_command('expand-all', function(){ rctasks.expand_collapse(true); }, true); ++ rcmail.register_command('collapse-all', function(){ rctasks.expand_collapse(false); }, true); + + rctasks.init(); + }); +diff --git a/plugins/tasklist/tasklist.php b/plugins/tasklist/tasklist.php +index 6ebddc4..e77bccc 100644 +--- a/plugins/tasklist/tasklist.php ++++ b/plugins/tasklist/tasklist.php +@@ -90,6 +90,7 @@ class tasklist extends rcube_plugin + $this->register_action('mail2task', array($this, 'mail_message2task')); + $this->register_action('get-attachment', array($this, 'attachment_get')); + $this->register_action('upload', array($this, 'attachment_upload')); ++ $this->add_hook('refresh', array($this, 'refresh')); + + $this->collapsed_tasks = array_filter(explode(',', $this->rc->config->get('tasklist_collapsed_tasks', ''))); + } +@@ -112,6 +113,8 @@ class tasklist extends rcube_plugin + 'innerclass' => 'icon taskadd', + ))), + 'messagemenu'); ++ ++ $this->api->output->add_label('tasklist.createfrommail'); + } + } + +@@ -247,13 +250,15 @@ class tasklist extends rcube_plugin + break; + + case 'collapse': +- if (intval(get_input_value('collapsed', RCUBE_INPUT_GPC))) { +- $this->collapsed_tasks[] = $rec['id']; +- } +- else { +- $i = array_search($rec['id'], $this->collapsed_tasks); +- if ($i !== false) +- unset($this->collapsed_tasks[$i]); ++ foreach (explode(',', $rec['id']) as $rec_id) { ++ if (intval(get_input_value('collapsed', RCUBE_INPUT_GPC))) { ++ $this->collapsed_tasks[] = $rec_id; ++ } ++ else { ++ $i = array_search($rec_id, $this->collapsed_tasks); ++ if ($i !== false) ++ unset($this->collapsed_tasks[$i]); ++ } + } + + $this->rc->user->save_prefs(array('tasklist_collapsed_tasks' => join(',', array_unique($this->collapsed_tasks)))); +@@ -278,7 +283,7 @@ class tasklist extends rcube_plugin + foreach ($refresh as $i => $r) + $this->encode_task($refresh[$i]); + } +- $this->rc->output->command('plugin.refresh_task', $refresh); ++ $this->rc->output->command('plugin.update_task', $refresh); + } + } + +@@ -540,8 +545,17 @@ class tasklist extends rcube_plugin + + } + */ ++ $data = $this->tasks_data($this->driver->list_tasks($filter, $lists), $f, $tags); ++ $this->rc->output->command('plugin.data_ready', array('filter' => $f, 'lists' => $lists, 'search' => $search, 'data' => $data, 'tags' => array_values(array_unique($tags)))); ++ } ++ ++ /** ++ * Prepare and sort the given task records to be sent to the client ++ */ ++ private function tasks_data($records, $f, &$tags) ++ { + $data = $tags = $this->task_tree = $this->task_titles = array(); +- foreach ($this->driver->list_tasks($filter, $lists) as $rec) { ++ foreach ($records as $rec) { + if ($rec['parent_id']) { + $this->task_tree[$rec['id']] = $rec['parent_id']; + } +@@ -558,7 +572,7 @@ class tasklist extends rcube_plugin + array_walk($data, array($this, 'task_walk_tree')); + usort($data, array($this, 'task_sort_cmp')); + +- $this->rc->output->command('plugin.data_ready', array('filter' => $f, 'lists' => $lists, 'search' => $search, 'data' => $data, 'tags' => array_values(array_unique($tags)))); ++ return $data; + } + + /** +@@ -605,6 +619,10 @@ class tasklist extends rcube_plugin + $rec['attachments'][$k]['classname'] = rcube_utils::file2class($attachment['mimetype'], $attachment['name']); + } + ++ if (!is_array($rec['tags'])) ++ $rec['tags'] = (array)$rec['tags']; ++ sort($rec['tags'], SORT_LOCALE_STRING); ++ + if (in_array($rec['id'], $this->collapsed_tasks)) + $rec['collapsed'] = true; + +@@ -720,6 +738,34 @@ class tasklist extends rcube_plugin + exit; + } + ++ /** ++ * Handler for keep-alive requests ++ * This will check for updated data in active lists and sync them to the client ++ */ ++ public function refresh($attr) ++ { ++ // refresh the entire list every 10th time to also sync deleted items ++ if (rand(0,10) == 10) { ++ $this->rc->output->command('plugin.reload_data'); ++ return; ++ } ++ ++ $filter = array( ++ 'since' => $attr['last'], ++ 'search' => get_input_value('q', RCUBE_INPUT_GPC), ++ 'mask' => intval(get_input_value('filter', RCUBE_INPUT_GPC)) & self::FILTER_MASK_COMPLETE, ++ ); ++ $lists = get_input_value('lists', RCUBE_INPUT_GPC);; ++ ++ $updates = $this->driver->list_tasks($filter, $lists); ++ if (!empty($updates)) { ++ $this->rc->output->command('plugin.refresh_tasks', $this->tasks_data($updates, 255, $tags), true); ++ ++ // update counts ++ $counts = $this->driver->count_tasks($lists); ++ $this->rc->output->command('plugin.update_counts', $counts); ++ } ++ } + + /** + * Handler for pending_alarms plugin hook triggered by the calendar module on keep-alive requests. +diff --git a/plugins/tasklist/tasklist_base.js b/plugins/tasklist/tasklist_base.js +index e3a889c..f804c34 100644 +--- a/plugins/tasklist/tasklist_base.js ++++ b/plugins/tasklist/tasklist_base.js +@@ -4,7 +4,7 @@ + * @version @package_version@ + * @author Thomas Bruederli <bruederli@kolabsys.com> + * +- * Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com> ++ * Copyright (C) 2013, Kolab Systems AG <contact@kolabsys.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as +@@ -37,10 +37,9 @@ function rcube_tasklist(settings) + /** + * Open a new task dialog prefilled with contents from the currently selected mail message + */ +- function create_from_mail() ++ function create_from_mail(uid) + { +- var uid; +- if ((uid = rcmail.get_single_uid())) { ++ if (uid || (uid = rcmail.get_single_uid())) { + // load calendar UI (scripts and edit dialog template) + if (!ui_loaded) { + $.when( +@@ -53,7 +52,7 @@ function rcube_tasklist(settings) + + ui_loaded = true; + me.ui = new rcube_tasklist_ui(settings); +- create_from_mail(); // start over ++ create_from_mail(uid); // start over + }); + return; + } +@@ -90,5 +89,14 @@ window.rcmail && rcmail.env.task == 'mail' && rcmail.addEventListener('init', fu + rcmail.env.message_commands.push('tasklist-create-from-mail'); + else + rcmail.enable_command('tasklist-create-from-mail', true); ++ ++ // add contextmenu item ++ if (window.rcm_contextmenu_register_command) { ++ rcm_contextmenu_register_command( ++ 'tasklist-create-from-mail', ++ function(cmd,el){ tasks.create_from_mail() }, ++ 'tasklist.createfrommail', ++ 'moveto'); ++ } + }); + +diff --git a/plugins/tasklist/tasklist_ui.php b/plugins/tasklist/tasklist_ui.php +index dab9b12..21faba3 100644 +--- a/plugins/tasklist/tasklist_ui.php ++++ b/plugins/tasklist/tasklist_ui.php +@@ -117,7 +117,7 @@ class tasklist_ui + + $li .= html::tag('li', array('id' => 'rcmlitasklist' . $html_id, 'class' => $class), + ($prop['virtual'] ? '' : html::tag('input', array('type' => 'checkbox', 'name' => '_list[]', 'value' => $id, 'checked' => $prop['active']))) . +- html::span('handle', ' ') . ++ html::span(array('class' => 'handle', 'title' => $this->plugin->gettext('focusview')), ' ') . + html::span(array('class' => 'listname', 'title' => $title), $prop['listname'])); + } +
View file
roundcubemail-plugins-kolab.dsc
Changed
@@ -2,7 +2,7 @@ Source: roundcubemail-plugins-kolab Binary: roundcubemail-plugins-kolab Architecture: all -Version: 1:3.1.6-0~kolab1 +Version: 1:3.1.6-0~kolab2 Maintainer: Christoph Wickert <wickert@kolabsys.com> Uploaders: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen@kolabsys.com>, Paul Klos <kolab@klos2day.nl> Standards-Version: 3.9.3
Locations
Projects
Search
Status Monitor
Help
Open Build Service
OBS Manuals
API Documentation
OBS Portal
Reporting a Bug
Contact
Mailing List
Forums
Chat (IRC)
Twitter
Open Build Service (OBS)
is an
openSUSE project
.