diff --git a/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php b/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php index e44b2c6a..3ee1100e 100644 --- a/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php +++ b/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php @@ -1237,6 +1237,25 @@ class tasklist_kolab_driver extends tasklist_driver return $this->_convert_message_uri($uri); } + /** + * Find tasks assigned to a specified message + * + * @see tasklist_driver::get_message_related_tasks() + */ + public function get_message_related_tasks($headers, $folder) + { + $config = kolab_storage_config::get_instance(); + $result = $config->get_message_relations($headers, $folder, 'task'); + + foreach ($result as $idx => $rec) { + $task = $this->_to_rcube_task($rec); + $task['list'] = kolab_storage::folder_id($rec['_mailbox']); + $result[$idx] = $task; + } + + return $result; + } + /** * */ diff --git a/plugins/tasklist/drivers/tasklist_driver.php b/plugins/tasklist/drivers/tasklist_driver.php index 2102d219..791d2ab4 100644 --- a/plugins/tasklist/drivers/tasklist_driver.php +++ b/plugins/tasklist/drivers/tasklist_driver.php @@ -290,8 +290,8 @@ abstract class tasklist_driver /** * Build a URI representing the given message reference * - * @param object rcube_message_header Instance holding the message headers - * @param string IMAP folder the message resides in + * @param object $headers rcube_message_header instance holding the message headers + * @param string $folder IMAP folder the message resides in * * @return string An URI referencing the given IMAP message */ @@ -301,6 +301,20 @@ abstract class tasklist_driver return false; } + /** + * Find tasks assigned to a specified message + * + * @param object $message rcube_message_header instance + * @param string $folder IMAP folder the message resides in + * + * @param array List of linked task objects + */ + public function get_message_related_tasks($headers, $folder) + { + // to be implemented by the derived classes + return array(); + } + /** * Helper method to determine whether the given task is considered "complete" * diff --git a/plugins/tasklist/skins/larry/buttons.png b/plugins/tasklist/skins/larry/buttons.png index 62baed67..7f1a2b63 100644 Binary files a/plugins/tasklist/skins/larry/buttons.png and b/plugins/tasklist/skins/larry/buttons.png differ diff --git a/plugins/tasklist/skins/larry/tasklist.css b/plugins/tasklist/skins/larry/tasklist.css index bf53e6b8..832197db 100644 --- a/plugins/tasklist/skins/larry/tasklist.css +++ b/plugins/tasklist/skins/larry/tasklist.css @@ -1337,6 +1337,46 @@ div.tasklist-invitebox .rsvp-status.accepted, background-position: 2px -220px; } +div.messagetasklinks { + margin: 8px 8px; + padding: 4px 8px; + border: 1px solid #dfdfdf; + background: #fafafa; + border-radius: 4px; +} + +div.messagetasklinks span.messagetaskref { + position: relative; + display: inline-block; + margin-right: 1em; + padding-right: 24px; +} + +div.messagetasklinks a.messagetasklink { + position: relative; + display: inline-block; + color: #333; + font-weight: bold; + padding: 3px 0 2px 22px; + text-shadow: 0px 1px 1px #fff; + text-decoration: none; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 16em; + background: url(buttons.png) -6px -115px no-repeat; +} + +div.messagetasklinks span.messagetaskref.complete a.messagetasklink { + text-decoration: line-through; +} + +div.messagetasklinks span.messagetaskref input.complete { + position: absolute; + top: 1px; + right: 2px; +} + /** Special hacks for IE7 **/ /** They need to be in this file to also affect the task-create dialog embedded in mail view **/ diff --git a/plugins/tasklist/tasklist.js b/plugins/tasklist/tasklist.js index 3cadd57a..5ec467d0 100644 --- a/plugins/tasklist/tasklist.js +++ b/plugins/tasklist/tasklist.js @@ -137,6 +137,11 @@ function rcube_tasklist_ui(settings) { // initialize task list selectors for (var id in me.tasklists) { + if (settings.selected_list && me.tasklists[settings.selected_list] && !me.tasklists[settings.selected_list].active) { + me.tasklists[settings.selected_list].active = true; + me.selected_list = settings.selected_list; + $(rcmail.gui_objects.tasklistslist).find("input[value='"+settings.selected_list+"']").prop('checked', true); + } if (me.tasklists[id].editable && (!me.selected_list || me.tasklists[id].default || (me.tasklists[id].active && !me.tasklists[me.selected_list].active))) { me.selected_list = id; } @@ -269,10 +274,9 @@ function rcube_tasklist_ui(settings) $('#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(); + list_tasks(settings.selected_filter); // register event handlers for UI elements $('#taskselector a').click(function(e) { @@ -769,6 +773,19 @@ function rcube_tasklist_ui(settings) append_tags(response.tags || []); render_tasklist(); + // show selected task dialog + if (settings.selected_id) { + if (listdata[settings.selected_id]) { + task_show_dialog(settings.selected_id); + delete settings.selected_id; + } + + // remove _id from window location + if (window.history.replaceState) { + window.history.replaceState({}, document.title, rcmail.url('', { _list: me.selected_list })); + } + } + rcmail.set_busy(false, 'loading', ui_loading); } diff --git a/plugins/tasklist/tasklist.php b/plugins/tasklist/tasklist.php index 9acded6d..82b36343 100644 --- a/plugins/tasklist/tasklist.php +++ b/plugins/tasklist/tasklist.php @@ -61,6 +61,7 @@ class tasklist extends rcube_plugin public $home; // declare public to be used in other classes private $collapsed_tasks = array(); + private $message_tasks = array(); private $itip; private $ical; @@ -125,6 +126,9 @@ class tasklist extends rcube_plugin } else if ($args['task'] == 'mail') { if ($args['action'] == 'show' || $args['action'] == 'preview') { + if ($this->rc->config->get('tasklist_mail_embed', true)) { + $this->add_hook('message_load', array($this, 'mail_message_load')); + } $this->add_hook('template_object_messagebody', array($this, 'mail_messagebody_html')); } @@ -204,7 +208,8 @@ class tasklist extends rcube_plugin $success = $refresh = false; // force notify if hidden + active - if ((int)$this->rc->config->get('calendar_itip_send_option', 3) === 1 && empty($rec['_reportpartstat'])) + $itip_send_option = (int)$this->rc->config->get('calendar_itip_send_option', 3); + if ($itip_send_option === 1 && empty($rec['_reportpartstat'])) $rec['_notify'] = 1; switch ($action) { @@ -220,6 +225,24 @@ class tasklist extends rcube_plugin } break; + case 'complete': + $complete = intval(rcube_utils::get_input_value('complete', rcube_utils::INPUT_POST)); + if (!($rec = $this->driver->get_task($rec))) { + break; + } + + $rec['status'] = $complete ? 'COMPLETED' : ($rec['complete'] > 0 ? 'IN-PROCESS' : 'NEEDS-ACTION'); + + // sent itip notifications if enabled (no user interaction here) + if (($itip_send_option & 1)) { + if ($this->is_attendee($rec)) { + $rec['_reportpartstat'] = $rec['status']; + } + else if ($this->is_organizer($rec)) { + $rec['_notify'] = 1; + } + } + case 'edit': $rec = $this->prepare_task($rec); $clone = $this->handle_recurrence($rec, $this->driver->get_task($rec)); @@ -1437,7 +1460,36 @@ class tasklist extends rcube_plugin } } - // prepend event boxes to message body + // list linked tasks + $links = array(); + foreach ($this->message_tasks as $task) { + $checkbox = new html_checkbox(array( + 'name' => 'completed', + 'class' => 'complete', + 'title' => $this->gettext('complete'), + 'data-list' => $task['list'], + )); + $complete = $this->driver->is_complete($task); + $links[] = html::span('messagetaskref' . ($complete ? ' complete' : ''), + $checkbox->show($complete ? $task['uid'] : null, array('value' => $task['uid'])) . ' ' . + html::a(array( + 'href' => $this->rc->url(array( + 'task' => 'tasks', + 'list' => $task['list'], + 'id' => $task['uid'], + 'complete' => $complete?1:null, + )), + 'class' => 'messagetasklink', + 'rel' => $task['uid'] . '@' . $task['list'], + 'target' => '_blank', + ), Q($task['title'])) + ); + } + if (count($links)) { + $html .= html::div('messagetasklinks', join("\n", $links)); + } + + // prepend iTip/relation boxes to message body if ($html) { $this->load_ui(); $this->ui->init(); @@ -1465,6 +1517,17 @@ class tasklist extends rcube_plugin return $p; } + /** + * Lookup backend storage and find notes associated with the given message + */ + public function mail_message_load($p) + { + if (!$p['object']->headers->others['x-kolab-type']) { + $this->load_driver(); + $this->message_tasks = $this->driver->get_message_related_tasks($p['object']->headers, $p['object']->folder); + } + } + /** * Load iCalendar functions */ diff --git a/plugins/tasklist/tasklist_base.js b/plugins/tasklist/tasklist_base.js index e399c537..b5ea06e9 100644 --- a/plugins/tasklist/tasklist_base.js +++ b/plugins/tasklist/tasklist_base.js @@ -30,6 +30,7 @@ function rcube_tasklist(settings) /* private vars */ var ui_loaded = false; var me = this; + var mywin = window; /* public members */ this.ui; @@ -79,6 +80,18 @@ function rcube_tasklist(settings) function mail2task_dialog(prop) { this.ui.edit_task(null, 'new', prop); + rcmail.addEventListener('responseaftertask', refresh_mailview); + } + + /** + * Reload the mail view/preview to update the tasks listing + */ + function refresh_mailview(e) + { + var win = rcmail.env.contentframe ? rcmail.get_frame_window(rcmail.env.contentframe) : mywin; + if (win && e.response.action == 'task') { + win.location.reload(); + } } // handler for attachment-save-tasklist commands @@ -95,6 +108,21 @@ function rcube_tasklist(settings) } } + // register event handlers on linked task items in message view + // the checkbox allows to mark a task as complete + if (rcmail.env.action == 'show' || rcmail.env.action == 'preview') { + $('div.messagetasklinks input.complete').click(function(e) { + var $this = $(this); + $(this).closest('.messagetaskref').toggleClass('complete'); + + // submit change to server + rcmail.http_post('tasks/task', { + action: 'complete', + t: { id:this.value, list:$this.attr('data-list') }, + complete: this.checked?1:0 + }, rcmail.set_busy(true, 'tasklist.savingdata')); + }); + } } /* tasklist plugin initialization (for email task) */ diff --git a/plugins/tasklist/tasklist_ui.php b/plugins/tasklist/tasklist_ui.php index b7bb4d43..5451e2cd 100644 --- a/plugins/tasklist/tasklist_ui.php +++ b/plugins/tasklist/tasklist_ui.php @@ -92,6 +92,23 @@ class tasklist_ui 'emails' => ';' . strtolower(join(';', $identity['emails'])) ); + if ($list = rcube_utils::get_input_value('_list', rcube_utils::INPUT_GPC)) { + $settings['selected_list'] = $list; + } + if ($list && ($id = rcube_utils::get_input_value('_id', rcube_utils::INPUT_GPC))) { + $settings['selected_id'] = $id; + + // check if the referenced task is completed + $task = $this->plugin->driver->get_task(array('id' => $id, 'list' => $list)); + console($id, $task); + if ($task && $this->plugin->driver->is_complete($task)) { + $settings['selected_filter'] = 'complete'; + } + } + else if ($filter = rcube_utils::get_input_value('_filter', rcube_utils::INPUT_GPC)) { + $settings['selected_filter'] = $filter; + } + return $settings; }