diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php index 6c23741b..ae1c4cea 100644 --- a/plugins/calendar/calendar.php +++ b/plugins/calendar/calendar.php @@ -2341,6 +2341,7 @@ class calendar extends rcube_plugin } $html = ''; + $has_events = false; foreach ($this->ics_parts as $mime_id) { $part = $this->message->mime_parts[$mime_id]; $charset = $part->ctype_parameters['charset'] ? $part->ctype_parameters['charset'] : RCMAIL_CHARSET; @@ -2356,16 +2357,20 @@ class calendar extends rcube_plugin if ($event['_type'] != 'event') // skip non-event objects (#2928) continue; + $has_events = true; + // get prepared inline UI for this event object - $html .= html::div('calendar-invitebox', - $this->itip->mail_itip_inline_ui( - $event, - $this->ical->method, - $mime_id.':'.$idx, - 'calendar', - rcube_utils::anytodatetime($this->message->headers->date) - ) - ); + if ($this->ical->method) { + $html .= html::div('calendar-invitebox', + $this->itip->mail_itip_inline_ui( + $event, + $this->ical->method, + $mime_id.':'.$idx, + 'calendar', + rcube_utils::anytodatetime($this->message->headers->date) + ) + ); + } // limit listing if ($idx >= 3) @@ -2381,7 +2386,7 @@ class calendar extends rcube_plugin } // add "Save to calendar" button into attachment menu - if (!empty($this->ics_parts)) { + if ($has_events) { $this->add_button(array( 'id' => 'attachmentsavecal', 'name' => 'attachmentsavecal', diff --git a/plugins/libcalendaring/lib/libcalendaring_itip.php b/plugins/libcalendaring/lib/libcalendaring_itip.php index 074f87f6..93bdfee9 100644 --- a/plugins/libcalendaring/lib/libcalendaring_itip.php +++ b/plugins/libcalendaring/lib/libcalendaring_itip.php @@ -60,6 +60,11 @@ class libcalendaring_itip $this->rsvp_status = array_merge($this->rsvp_actions, array('delegated')); } + public function set_rsvp_status($status) + { + $this->rsvp_status = $status; + } + /** * Wrapper for rcube_plugin::gettext() * Checking for a label in different domains @@ -213,6 +218,14 @@ class libcalendaring_itip $event['attendees'] = $reply_attendees; } } + // set RSVP=TRUE for every attendee if not set + else if ($method == 'REQUEST') { + foreach ($event['attendees'] as $i => $attendee) { + if (!isset($attendee['rsvp'])) { + $event['attendees'][$i]['rsvp']= true; + } + } + } // compose multipart message using PEAR:Mail_Mime $message = new Mail_mime("\r\n"); @@ -236,9 +249,10 @@ class libcalendaring_itip $message->headers($headers); // attach ics file for this event - $ical = $this->plugin->get_ical(); + $ical = libcalendaring::get_ical(); $ics = $ical->export(array($event), $method, false, $method == 'REQUEST' && $this->plugin->driver ? array($this->plugin->driver, 'get_attachment_body') : false); - $message->addAttachment($ics, 'text/calendar', 'event.ics', false, '8bit', '', RCMAIL_CHARSET . "; method=" . $method); + $filename = $event['_type'] == 'task' ? 'todo.ics' : 'event.ics'; + $message->addAttachment($ics, 'text/calendar', $filename, false, '8bit', '', RCMAIL_CHARSET . "; method=" . $method); return $message; } @@ -313,9 +327,10 @@ class libcalendaring_itip $listed = false; foreach ($existing['attendees'] as $attendee) { if ($attendee['role'] != 'ORGANIZER' && strcasecmp($attendee['email'], $event['attendee']) == 0) { - if (in_array($status, array('ACCEPTED','TENTATIVE','DECLINED','DELEGATED'))) { - $html = html::div('rsvp-status ' . strtolower($status), $this->gettext(array( - 'name' => 'attendee'.strtolower($status), + $status_lc = strtolower($status); + if (in_array($status_lc, $this->rsvp_status)) { + $html = html::div('rsvp-status ' . $status_lc, $this->gettext(array( + 'name' => 'attendee' . $status_lc, 'vars' => array( 'delegatedto' => Q($attendee['delegated-to'] ?: '?'), ) @@ -532,15 +547,21 @@ class libcalendaring_itip } /** - * Render event details in a table + * Render event/task details in a table */ function itip_object_details_table($event, $title) { $table = new html_table(array('cols' => 2, 'border' => 0, 'class' => 'calendar-eventdetails')); $table->add('ititle', $title); $table->add('title', Q($event['title'])); - $table->add('label', $this->plugin->gettext('date'), $this->domain); - $table->add('date', Q($this->lib->event_date_text($event))); + if ($event['start'] && $event['end']) { + $table->add('label', $this->plugin->gettext('date'), $this->domain); + $table->add('date', Q($this->lib->event_date_text($event))); + } + else if ($event['due'] && $event['_type'] == 'task') { + $table->add('label', $this->plugin->gettext('date'), $this->domain); + $table->add('date', Q($this->lib->event_date_text($event))); + } if ($event['location']) { $table->add('label', $this->plugin->gettext('location'), $this->domain); $table->add('location', Q($event['location'])); diff --git a/plugins/libcalendaring/libcalendaring.php b/plugins/libcalendaring/libcalendaring.php index 0eef727c..a8b70cb3 100644 --- a/plugins/libcalendaring/libcalendaring.php +++ b/plugins/libcalendaring/libcalendaring.php @@ -247,11 +247,25 @@ class libcalendaring extends rcube_plugin */ public function event_date_text($event, $tzinfo = false) { - $fromto = ''; + $fromto = '--'; + + // handle task objects + if ($event['_type'] == 'task' && is_object($event['due'])) { + $date_format = $event['due']->_dateonly ? self::to_php_date_format($this->rc->config->get('calendar_date_format', $this->defaults['calendar_date_format'])) : null; + $fromto = $this->rc->format_date($event['due'], $date_format, false); + + // add timezone information + if ($fromto && $tzinfo && ($tzname = $this->timezone->getName())) { + $fromto .= ' (' . strtr($tzname, '_', ' ') . ')'; + } + + return $fromto; + } // abort if no valid event dates are given - if (!is_object($event['start']) || !is_a($event['start'], 'DateTime') || !is_object($event['end']) || !is_a($event['end'], 'DateTime')) + if (!is_object($event['start']) || !is_a($event['start'], 'DateTime') || !is_object($event['end']) || !is_a($event['end'], 'DateTime')) { return $fromto; + } $duration = $event['start']->diff($event['end'])->format('s'); diff --git a/plugins/libcalendaring/libvcalendar.php b/plugins/libcalendaring/libvcalendar.php index 855e0749..a89cec2b 100644 --- a/plugins/libcalendaring/libvcalendar.php +++ b/plugins/libcalendaring/libvcalendar.php @@ -567,7 +567,7 @@ class libvcalendar implements Iterator } // make organizer part of the attendees list for compatibility reasons - if (!empty($event['organizer']) && is_array($event['attendees'])) { + if (!empty($event['organizer']) && is_array($event['attendees']) && $event['_type'] == 'event') { array_unshift($event['attendees'], $event['organizer']); } diff --git a/plugins/libcalendaring/localization/en_US.inc b/plugins/libcalendaring/localization/en_US.inc index 26e4ae43..68ddab6b 100644 --- a/plugins/libcalendaring/localization/en_US.inc +++ b/plugins/libcalendaring/localization/en_US.inc @@ -87,6 +87,8 @@ $labels['itipobjectnotfound'] = 'The object referred by this message was not fou $labels['itipsubjectaccepted'] = '"$title" has been accepted by $name'; $labels['itipsubjecttentative'] = '"$title" has been tentatively accepted by $name'; $labels['itipsubjectdeclined'] = '"$title" has been declined by $name'; +$labels['itipsubjectin-process'] = '"$title" is in-process by $name'; +$labels['itipsubjectcompleted'] = '"$title" was completed by $name'; $labels['itipsubjectcancel'] = 'Your participation in "$title" has been cancelled'; $labels['itipnewattendee'] = 'This is a reply from a new participant'; @@ -100,18 +102,24 @@ $labels['youhaveaccepted'] = 'You have accepted this invitation'; $labels['youhavetentative'] = 'You have tentatively accepted this invitation'; $labels['youhavedeclined'] = 'You have declined this invitation'; $labels['youhavedelegated'] = 'You have delegated this invitation'; +$labels['youhavein-process'] = 'You are working on this assignment'; +$labels['youhavecompleted'] = 'You have completed this assignment'; $labels['youhaveneeds-action'] = 'Your response to this invitation is still pending'; $labels['youhavepreviouslyaccepted'] = 'You have previously accepted this invitation'; $labels['youhavepreviouslytentative'] = 'You have previously accepted this invitation tentatively'; $labels['youhavepreviouslydeclined'] = 'You have previously declined this invitation'; $labels['youhavepreviouslydelegated'] = 'You have previously delegated this invitation'; +$labels['youhavepreviouslyin-process'] = 'You have previously reported to work on this assignment'; +$labels['youhavepreviouslycompleted'] = 'You have previously completed this assignment'; $labels['youhavepreviouslyneeds-action'] = 'Your response to this invitation is still pending'; $labels['attendeeaccepted'] = 'Participant has accepted'; $labels['attendeetentative'] = 'Participant has tentatively accepted'; $labels['attendeedeclined'] = 'Participant has declined'; $labels['attendeedelegated'] = 'Participant has delegated to $delegatedto'; +$labels['attendeein-process'] = 'Participant is in-process'; +$labels['attendeecompleted'] = 'Participant has completed'; $labels['notanattendee'] = 'You\'re not listed as an attendee of this object'; $labels['outdatedinvitation'] = 'This invitation has been replaced by a newer version'; diff --git a/plugins/libkolab/lib/kolab_format_event.php b/plugins/libkolab/lib/kolab_format_event.php index 596d0da3..c233f444 100644 --- a/plugins/libkolab/lib/kolab_format_event.php +++ b/plugins/libkolab/lib/kolab_format_event.php @@ -26,7 +26,7 @@ class kolab_format_event extends kolab_format_xcal { public $CTYPEv2 = 'application/x-vnd.kolab.event'; - public static $scheduling_properties = array('start', 'end', 'allday', 'location', 'status', 'cancelled'); + public $scheduling_properties = array('start', 'end', 'allday', 'location', 'status', 'cancelled'); protected $objclass = 'Event'; protected $read_func = 'readEvent'; diff --git a/plugins/libkolab/lib/kolab_format_task.php b/plugins/libkolab/lib/kolab_format_task.php index ee0ca6a9..52744d45 100644 --- a/plugins/libkolab/lib/kolab_format_task.php +++ b/plugins/libkolab/lib/kolab_format_task.php @@ -26,7 +26,7 @@ class kolab_format_task extends kolab_format_xcal { public $CTYPEv2 = 'application/x-vnd.kolab.task'; - public static $scheduling_properties = array('start', 'due', 'summary', 'status'); + public $scheduling_properties = array('start', 'due', 'summary', 'status'); protected $objclass = 'Todo'; protected $read_func = 'readTodo'; diff --git a/plugins/libkolab/lib/kolab_format_xcal.php b/plugins/libkolab/lib/kolab_format_xcal.php index 6624b025..7d077b7c 100644 --- a/plugins/libkolab/lib/kolab_format_xcal.php +++ b/plugins/libkolab/lib/kolab_format_xcal.php @@ -29,7 +29,8 @@ abstract class kolab_format_xcal extends kolab_format public $CTYPE = 'application/calendar+xml'; public static $fulltext_cols = array('title', 'description', 'location', 'attendees:name', 'attendees:email', 'categories'); - public static $scheduling_properties = array('start', 'end', 'location'); + + public $scheduling_properties = array('start', 'end', 'location'); protected $sensitivity_map = array( 'public' => kolabformat::ClassPublic, @@ -315,7 +316,7 @@ abstract class kolab_format_xcal extends kolab_format // increment sequence when updating properties relevant for scheduling. // RFC 5545: "It is incremented [...] each time the Organizer makes a significant revision to the calendar component." // TODO: make the list of properties considered 'significant' for scheduling configurable - foreach (self::$scheduling_properties as $prop) { + foreach ($this->scheduling_properties as $prop) { $a = $old[$prop]; $b = $object[$prop]; if ($object['allday'] && ($prop == 'start' || $prop == 'end') && $a instanceof DateTime && $b instanceof DateTime) { diff --git a/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php b/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php index 1999a8e5..bcc5c06e 100644 --- a/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php +++ b/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php @@ -25,16 +25,17 @@ class tasklist_kolab_driver extends tasklist_driver { // features supported by the backend - public $alarms = false; + public $alarms = false; public $attachments = true; - public $undelete = false; // task undelete action + public $attendees = true; + public $undelete = false; // task undelete action public $alarm_types = array('DISPLAY','AUDIO'); private $rc; private $plugin; private $lists; private $folders = array(); - private $tasks = array(); + private $tasks = array(); /** @@ -772,6 +773,9 @@ class tasklist_kolab_driver extends tasklist_driver 'status' => $record['status'], 'parent_id' => $record['parent_id'], 'recurrence' => $record['recurrence'], + 'attendees' => $record['attendees'], + 'organizer' => $record['organizer'], + 'sequence' => $record['sequence'], ); // convert from DateTime to internal date format @@ -815,8 +819,8 @@ class tasklist_kolab_driver extends tasklist_driver } /** - * Convert the given task record into a data structure that can be passed to kolab_storage backend for saving - * (opposite of self::_to_rcube_event()) + * Convert the given task record into a data structure that can be passed to kolab_storage backend for saving + * (opposite of self::_to_rcube_event()) */ private function _from_rcube_task($task, $old = array()) { @@ -824,14 +828,14 @@ class tasklist_kolab_driver extends tasklist_driver $object['categories'] = (array)$task['tags']; if (!empty($task['date'])) { - $object['due'] = new DateTime($task['date'].' '.$task['time'], $this->plugin->timezone); + $object['due'] = rcube_utils::anytodatetime($task['date'].' '.$task['time'], $this->plugin->timezone); if (empty($task['time'])) $object['due']->_dateonly = true; unset($object['date']); } if (!empty($task['startdate'])) { - $object['start'] = new DateTime($task['startdate'].' '.$task['starttime'], $this->plugin->timezone); + $object['start'] = rcube_utils::anytodatetime($task['startdate'].' '.$task['starttime'], $this->plugin->timezone); if (empty($task['starttime'])) $object['start']->_dateonly = true; unset($object['startdate']); @@ -898,6 +902,14 @@ class tasklist_kolab_driver extends tasklist_driver unset($object['attachments']); } + // allow sequence increments if I'm the organizer + if ($this->plugin->is_organizer($object)) { + unset($object['sequence']); + } + else if (isset($old['sequence'])) { + $object['sequence'] = $old['sequence']; + } + unset($object['tempid'], $object['raw'], $object['list'], $object['flagged'], $object['tags']); return $object; } diff --git a/plugins/tasklist/drivers/tasklist_driver.php b/plugins/tasklist/drivers/tasklist_driver.php index eb7663e5..dd2e415f 100644 --- a/plugins/tasklist/drivers/tasklist_driver.php +++ b/plugins/tasklist/drivers/tasklist_driver.php @@ -72,6 +72,7 @@ abstract class tasklist_driver // features supported by the backend public $alarms = false; public $attachments = false; + public $attendees = false; public $undelete = false; // task undelete action public $sortable = false; public $alarm_types = array('DISPLAY'); diff --git a/plugins/tasklist/localization/en_US.inc b/plugins/tasklist/localization/en_US.inc index 9fd0e3eb..a7ec1ea3 100644 --- a/plugins/tasklist/localization/en_US.inc +++ b/plugins/tasklist/localization/en_US.inc @@ -33,6 +33,7 @@ $labels['status-needs-action'] = 'Needs action'; $labels['status-in-process'] = 'In process'; $labels['status-completed'] = 'Completed'; $labels['status-cancelled'] = 'Cancelled'; +$labels['assignedto'] = 'Assigned to'; $labels['all'] = 'All'; $labels['flagged'] = 'Flagged'; @@ -50,6 +51,7 @@ $labels['newtask'] = 'New Task'; $labels['edittask'] = 'Edit Task'; $labels['save'] = 'Save'; $labels['cancel'] = 'Cancel'; +$labels['saveandnotify'] = 'Save and Notify'; $labels['addsubtask'] = 'Add subtask'; $labels['deletetask'] = 'Delete task'; $labels['deletethisonly'] = 'Delete this task only'; @@ -58,6 +60,7 @@ $labels['taskactions'] = 'Task options...'; $labels['tabsummary'] = 'Summary'; $labels['tabrecurrence'] = 'Recurrence'; +$labels['tabassignments'] = 'Assignments'; $labels['tabattachments'] = 'Attachments'; $labels['tabsharing'] = 'Sharing'; @@ -76,7 +79,7 @@ $labels['at'] = 'at'; $labels['this'] = 'this'; $labels['next'] = 'next'; -// mesages +// messages $labels['savingdata'] = 'Saving data...'; $labels['errorsaving'] = 'Failed to save data.'; $labels['notasksfound'] = 'No tasks found for the given criteria'; @@ -86,6 +89,9 @@ $labels['deleteparenttasktconfirm'] = 'Do you really want to delete this task an $labels['deletelistconfirm'] = 'Do you really want to delete this list with all its tasks?'; $labels['deletelistconfirmrecursive'] = 'Do you really want to delete this list with all its sub-lists and tasks?'; $labels['aclnorights'] = 'You do not have administrator rights on this task list.'; +$labels['changetaskconfirm'] = 'Update task'; +$labels['changeconfirmnotifications'] = 'Do you want to notify the attendees about the modification?'; +$labels['partstatupdatenotification'] = 'Do you want to notify the organizer about the status change?'; // (hidden) titles and labels for accessibility annotations $labels['quickaddinput'] = 'New task date and title'; @@ -96,3 +102,68 @@ $labels['arialabellistsearchform'] = 'Tasklists search form'; $labels['arialabeltaskselector'] = 'List mode'; $labels['arialabeltasklisting'] = 'Tasks listing'; +// attendees +$labels['attendee'] = 'Assignee'; +$labels['role'] = 'Role'; +$labels['availability'] = 'Avail.'; +$labels['confirmstate'] = 'Status'; +$labels['addattendee'] = 'Add assignee'; +$labels['roleorganizer'] = 'Organizer'; +$labels['rolerequired'] = 'Required'; +$labels['roleoptional'] = 'Optional'; +$labels['rolechair'] = 'Chair'; +$labels['rolenonparticipant'] = 'Observer'; +$labels['sendinvitations'] = 'Send invitations'; +$labels['sendnotifications'] = 'Notify assignees about modifications'; +$labels['sendcancellation'] = 'Notify assignees about task cancellation'; +$labels['invitationsubject'] = 'You\'ve been assigned to "$title"'; +$labels['invitationmailbody'] = "*\$title*\n\nDue: \$date\n\nAssignees: \$attendees\n\nPlease find attached an iCalendar file with all the task details which you can import to your tasks application."; +$labels['itipupdatesubject'] = '"$title" has been updated'; +$labels['itipupdatesubjectempty'] = 'A task that concerns you has been updated'; +$labels['itipupdatemailbody'] = "*\$title*\n\nDue: \$date\n\nAssignees: \$attendees\n\nPlease find attached an iCalendar file with the updated task details which you can import to your tasks application."; +$labels['itipcancelsubject'] = '"$title" has been canceled'; +$labels['itipcancelmailbody'] = "*\$title*\n\nDue: \$date\n\nAssignees: \$attendees\n\nThe task has been cancelled by \$organizer.\n\nPlease find attached an iCalendar file with the updated task details."; +$labels['saveintasklist'] = 'save in '; + +// invitation handling (overrides labels from libcalendaring) +$labels['itipobjectnotfound'] = 'The task referred by this message was not found in your tasks list.'; + +$labels['itipmailbodyaccepted'] = "\$sender has accepted the assignment to the following task:\n\n*\$title*\n\nDue: \$date\n\nAssignees: \$attendees"; +$labels['itipmailbodytentative'] = "\$sender has tentatively accepted the assignment to the following task:\n\n*\$title*\n\nDue: \$date\n\nAssignees: \$attendees"; +$labels['itipmailbodydeclined'] = "\$sender has declined the assignment to the following task:\n\n*\$title*\n\nDue: \$date\n\nAssignees: \$attendees"; +$labels['itipmailbodycancel'] = "\$sender has rejected your assignment to the following task:\n\n*\$title*\n\nDue: \$date"; +$labels['itipmailbodyin-process'] = "\$sender has set the status of the following task to in-process:\n\n*\$title*\n\nDue: \$date"; +$labels['itipmailbodycompleted'] = "\$sender has completed the following task:\n\n*\$title*\n\nDue: \$date"; + +$labels['attendeeaccepted'] = 'Assignee has accepted'; +$labels['attendeetentative'] = 'Assignee has tentatively accepted'; +$labels['attendeedeclined'] = 'Assignee has declined'; +$labels['attendeedelegated'] = 'Assignee has delegated to $delegatedto'; +$labels['attendeein-process'] = 'Assignee is in-process'; +$labels['attendeecompleted'] = 'Assignee has completed'; + +$labels['itipdeclinetask'] = 'Decline your assignment to this task to the organizer'; +$labels['declinedeleteconfirm'] = 'Do you also want to delete this declined task from your tasks list?'; +$labels['itipcomment'] = 'Invitation/notification comment'; +$labels['itipcommenttitle'] = 'This comment will be attached to the invitation/notification message sent to assignees'; +$labels['itipsendsuccess'] = 'Invitation sent to assignees'; +$labels['errornotifying'] = 'Failed to send notifications to task assignees'; +$labels['removefromcalendar'] = 'Remove from my tasks'; + +$labels['andnmore'] = '$nr more...'; +$labels['delegatedto'] = 'Delegated to: '; +$labels['delegatedfrom'] = 'Delegated from: '; +$labels['savetotasklist'] = 'Save to tasks'; +$labels['comment'] = 'Comment'; +$labels['errorimportingtask'] = 'Failed to import task(s)'; +$labels['importwarningexists'] = 'A copy of this task already exists in your tasklist.'; +$labels['importsuccess'] = 'Successfully imported $nr tasks'; +$labels['newerversionexists'] = 'A newer version of this task already exists! Aborted.'; +$labels['nowritetasklistfound'] = 'No tasklist found to save the task'; +$labels['importedsuccessfully'] = 'The task was successfully added to \'$list\''; +$labels['updatedsuccessfully'] = 'The task was successfully updated in \'$list\''; +$labels['attendeupdateesuccess'] = 'Successfully updated the participant\'s status'; +$labels['itipresponseerror'] = 'Failed to send the response to this task assignment'; +$labels['itipinvalidrequest'] = 'This invitation is no longer valid'; +$labels['sentresponseto'] = 'Successfully sent assignment response to $mailto'; +$labels['successremoval'] = 'The task has been deleted successfully.'; diff --git a/plugins/tasklist/skins/larry/images/attendee-status.png b/plugins/tasklist/skins/larry/images/attendee-status.png new file mode 100644 index 00000000..5343e601 Binary files /dev/null and b/plugins/tasklist/skins/larry/images/attendee-status.png differ diff --git a/plugins/tasklist/skins/larry/images/badge_cancelled.png b/plugins/tasklist/skins/larry/images/badge_cancelled.png new file mode 100644 index 00000000..b89029e0 Binary files /dev/null and b/plugins/tasklist/skins/larry/images/badge_cancelled.png differ diff --git a/plugins/tasklist/skins/larry/images/loading_blue.gif b/plugins/tasklist/skins/larry/images/loading_blue.gif new file mode 100644 index 00000000..2ea6b19a Binary files /dev/null and b/plugins/tasklist/skins/larry/images/loading_blue.gif differ diff --git a/plugins/tasklist/skins/larry/images/sendinvitation.png b/plugins/tasklist/skins/larry/images/sendinvitation.png new file mode 100644 index 00000000..ecdaa091 Binary files /dev/null and b/plugins/tasklist/skins/larry/images/sendinvitation.png differ diff --git a/plugins/tasklist/skins/larry/images/tasklist.png b/plugins/tasklist/skins/larry/images/tasklist.png new file mode 100644 index 00000000..50ed6300 Binary files /dev/null and b/plugins/tasklist/skins/larry/images/tasklist.png differ diff --git a/plugins/tasklist/skins/larry/tasklist.css b/plugins/tasklist/skins/larry/tasklist.css index e93a3113..96b5e823 100644 --- a/plugins/tasklist/skins/larry/tasklist.css +++ b/plugins/tasklist/skins/larry/tasklist.css @@ -20,7 +20,8 @@ background-position: 0 -26px; } -ul.toolbarmenu li span.icon.taskadd { +ul.toolbarmenu li span.icon.taskadd, +#attachmentmenu li a.tasklistlink span.icon.taskadd { background-image: url(buttons.png); background-position: -4px -90px; } @@ -814,6 +815,10 @@ ul.toolbarmenu li span.delete { color: #999; } +#taskshow.status-cancelled { + background: url(images/badge_cancelled.png) top right no-repeat; +} + #task-parent-title { position: relative; top: -0.6em; @@ -849,6 +854,93 @@ a.morelink:hover { border-bottom: 2px solid #fafafa; } +.edit-attendees-table { + width: 100%; + margin-top: 0.5em; +} + +.edit-attendees-table tbody td { + padding: 4px 7px; +} + +.edit-attendees-table tbody tr:last-child td { + border-bottom: 0; +} + +.edit-attendees-table th.role, +.edit-attendees-table td.role { + width: 9em; +} + +.edit-attendees-table th.availability, +.edit-attendees-table td.availability, +.edit-attendees-table th.confirmstate, +.edit-attendees-table td.confirmstate { + width: 6em; +} + +.edit-attendees-table th.options, +.edit-attendees-table td.options { + width: 24px; + padding: 2px 4px; + text-align: right; +} + +.edit-attendees-table th.sendmail, +.edit-attendees-table td.sendmail { + width: 48px; + padding: 2px; +} + +.edit-attendees-table th.sendmail label { + display: inline-block; + position: relative; + top: 4px; + width: 24px; + height: 18px; + min-width: 24px; + padding: 0; + overflow: hidden; + text-indent: -5000px; + white-space: nowrap; + background: url(images/sendinvitation.png) 1px 0 no-repeat; +} + +.edit-attendees-table th.name, +.edit-attendees-table td.name { + width: auto; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.edit-attendees-table td.name select { + width: 100%; +} + +.edit-attendees-table a.deletelink { + display: inline-block; + width: 17px; + height: 17px; + padding: 0; + overflow: hidden; + text-indent: 1000px; +} + +#edit-attendees-form { + position: relative; + margin-top: 15px; +} + +#edit-attendees-form .attendees-invitebox { + text-align: right; + margin: 0; +} + +#edit-attendees-form .attendees-invitebox label { + padding-right: 3px; +} + #taskedit-attachments { margin: 0.6em 0; } @@ -857,10 +949,11 @@ a.morelink:hover { display: block; color: #333; font-weight: bold; - padding: 8px 4px 3px 30px; + padding: 3px 4px 3px 30px; text-shadow: 0px 1px 1px #fff; text-decoration: none; white-space: nowrap; + line-height: 20px; } #taskedit-attachments ul li a.file { @@ -876,7 +969,7 @@ a.morelink:hover { div.form-section { position: relative; margin-top: 0.2em; - margin-bottom: 0.8em; + margin-bottom: 0.5em; } .form-section label { @@ -891,6 +984,10 @@ label.block { margin-bottom: 0.3em; } +#task-description { + margin-bottom: 1em; +} + #taskedit-completeness-slider { display: inline-block; margin-left: 2em; @@ -935,6 +1032,57 @@ label.block { outline: none; } +.task-attendees span.attendee { + padding-right: 18px; + margin-right: 0.5em; + background: url(images/attendee-status.png) right 0 no-repeat; +} + +.task-attendees span.attendee a.mailtolink { + text-decoration: none; + white-space: nowrap; + outline: none; +} + +.task-attendees span.attendee a.mailtolink:hover { + text-decoration: underline; +} + +.task-attendees span.completed { + background-position: right -20px; +} + +.task-attendees span.declined { + background-position: right -40px; +} + +.task-attendees span.tentative { + background-position: right -60px; +} + +.task-attendees span.delegated { + background-position: right -180px; +} + +.task-attendees span.in-process { + background-position: right -200px; +} + +.task-attendees span.accepted { + background-position: right -220px; +} + +.task-attendees span.organizer { + background-position: right 100px; +} + +#all-task-attendees span.attendee { + display: block; + margin-bottom: 0.4em; + padding-bottom: 0.3em; + border-bottom: 1px solid #ddd; +} + .tasklistview .uidialog .tabbed { min-width: 600px; } @@ -947,6 +1095,128 @@ label.block { width: 20em; } +.task-dialog-message { + margin-top: 0.5em; + padding: 0.8em; + border: 1px solid #ffdf0e; + background-color: #fef893; +} + +.task-dialog-message .message, +.task-update-confirm .message { + margin-bottom: 0.5em; +} + +/* Invitation UI in mail */ + +.messagelist tbody .attachment span.ical { + display: inline-block; + vertical-align: middle; + height: 18px; + width: 20px; + padding: 0; + background: url(images/ical-attachment.png) 2px 1px no-repeat; +} + +div.tasklist-invitebox { + min-height: 20px; + margin: 5px 8px; + padding: 3px 6px 6px 34px; + border: 1px solid #ffdf0e; + background: url(images/tasklist.png) 6px 5px no-repeat #fef893; +} + +div.tasklist-invitebox td.ititle { + font-weight: bold; + padding-right: 0.5em; +} + +div.tasklist-invitebox td.label { + color: #666; + padding-right: 1em; +} + +#event-rsvp .rsvp-buttons, +div.tasklist-invitebox .itip-buttons div { + margin-top: 0.5em; +} + +#event-rsvp input.button, +div.tasklist-invitebox input.button { + font-weight: bold; + margin-right: 0.5em; +} + +div.tasklist-invitebox .folder-select { + font-weight: 10px; + margin-left: 1em; +} + +div.tasklist-invitebox .rsvp-status { + padding-left: 2px; +} + +div.tasklist-invitebox .rsvp-status.loading { + color: #666; + padding: 1px 0 2px 24px; + background: url(images/loading_blue.gif) top left no-repeat; +} + +div.tasklist-invitebox .rsvp-status.hint { + color: #666; + text-shadow: none; + font-style: italic; +} + +#event-partstat .changersvp, +.edit-attendees-table td.confirmstate span, +div.tasklist-invitebox .rsvp-status.declined, +div.tasklist-invitebox .rsvp-status.tentative, +div.tasklist-invitebox .rsvp-status.accepted, +div.tasklist-invitebox .rsvp-status.delegated, +div.tasklist-invitebox .rsvp-status.in-process, +div.tasklist-invitebox .rsvp-status.completed, +div.tasklist-invitebox .rsvp-status.needs-action { + padding: 0 0 1px 22px; + background: url(images/attendee-status.png) 2px -20px no-repeat; +} + +#event-partstat .changersvp.declined, +div.tasklist-invitebox .rsvp-status.declined, +.edit-attendees-table td.confirmstate span.declined { + background-position: 2px -40px; +} + +#event-partstat .changersvp.tentative, +div.tasklist-invitebox .rsvp-status.tentative, +.edit-attendees-table td.confirmstate span.tentative { + background-position: 2px -60px; +} + +#event-partstat .changersvp.delegated, +div.tasklist-invitebox .rsvp-status.delegated, +.edit-attendees-table td.confirmstate span.delegated { + background-position: 2px -180px; +} + +#event-partstat .changersvp.needs-action, +div.tasklist-invitebox .rsvp-status.needs-action, +.edit-attendees-table td.confirmstate span.needs-action { + background-position: 2px 0; +} + +#event-partstat .changersvp.in-process, +div.tasklist-invitebox .rsvp-status.in-process, +.edit-attendees-table td.confirmstate span.in-process { + background-position: 2px -200px; +} + +#event-partstat .changersvp.accepted, +div.tasklist-invitebox .rsvp-status.accepted, +.edit-attendees-table td.confirmstate span.accepted { + background-position: 2px -220px; +} + /** Special hacks for IE7 **/ /** They need to be in this file to also affect the task-create dialog embedded in mail view **/ @@ -954,4 +1224,3 @@ label.block { html.ie7 #taskedit-completeness-slider { display: inline; } - diff --git a/plugins/tasklist/skins/larry/templates/mainview.html b/plugins/tasklist/skins/larry/templates/mainview.html index 3003067a..727d31fc 100644 --- a/plugins/tasklist/skins/larry/templates/mainview.html +++ b/plugins/tasklist/skins/larry/templates/mainview.html @@ -156,6 +156,23 @@ +