Start implementing Assignments (Attendees) tab for tasks (#1165)
This commit is contained in:
parent
5b26d69e20
commit
c0da6448fb
5 changed files with 552 additions and 30 deletions
|
@ -58,6 +58,7 @@ $labels['taskactions'] = 'Task options...';
|
||||||
|
|
||||||
$labels['tabsummary'] = 'Summary';
|
$labels['tabsummary'] = 'Summary';
|
||||||
$labels['tabrecurrence'] = 'Recurrence';
|
$labels['tabrecurrence'] = 'Recurrence';
|
||||||
|
$labels['tabassignments'] = 'Assignments';
|
||||||
$labels['tabattachments'] = 'Attachments';
|
$labels['tabattachments'] = 'Attachments';
|
||||||
$labels['tabsharing'] = 'Sharing';
|
$labels['tabsharing'] = 'Sharing';
|
||||||
|
|
||||||
|
@ -96,3 +97,38 @@ $labels['arialabellistsearchform'] = 'Tasklists search form';
|
||||||
$labels['arialabeltaskselector'] = 'List mode';
|
$labels['arialabeltaskselector'] = 'List mode';
|
||||||
$labels['arialabeltasklisting'] = 'Tasks listing';
|
$labels['arialabeltasklisting'] = 'Tasks listing';
|
||||||
|
|
||||||
|
// attendees
|
||||||
|
$labels['attendee'] = 'Participant';
|
||||||
|
$labels['role'] = 'Role';
|
||||||
|
$labels['availability'] = 'Avail.';
|
||||||
|
$labels['confirmstate'] = 'Status';
|
||||||
|
$labels['addattendee'] = 'Add participant';
|
||||||
|
$labels['roleorganizer'] = 'Organizer';
|
||||||
|
$labels['rolerequired'] = 'Required';
|
||||||
|
$labels['roleoptional'] = 'Optional';
|
||||||
|
$labels['rolechair'] = 'Chair';
|
||||||
|
$labels['rolenonparticipant'] = 'Absent';
|
||||||
|
$labels['sendinvitations'] = 'Send invitations';
|
||||||
|
$labels['sendnotifications'] = 'Notify participants about modifications';
|
||||||
|
$labels['sendcancellation'] = 'Notify participants about task cancellation';
|
||||||
|
$labels['invitationsubject'] = 'You\'ve been invited to "$title"';
|
||||||
|
$labels['invitationmailbody'] = "*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees\n\nPlease find attached an iCalendar file with all the task details which you can import to your tasks application.";
|
||||||
|
$labels['invitationattendlinks'] = "In case your email client doesn't support iTip requests you can use the following link to either accept or decline this invitation:\n\$url";
|
||||||
|
$labels['eventupdatesubject'] = '"$title" has been updated';
|
||||||
|
$labels['eventupdatesubjectempty'] = 'A task that concerns you has been updated';
|
||||||
|
$labels['eventupdatemailbody'] = "*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees\n\nPlease find attached an iCalendar file with the updated task details which you can import to your tasks application.";
|
||||||
|
$labels['eventcancelsubject'] = '"$title" has been canceled';
|
||||||
|
$labels['eventcancelmailbody'] = "*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees\n\nThe task has been cancelled by \$organizer.\n\nPlease find attached an iCalendar file with the updated task details.";
|
||||||
|
|
||||||
|
// 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\nWhen: \$date\n\nInvitees: \$attendees";
|
||||||
|
$labels['itipmailbodytentative'] = "\$sender has tentatively accepted the assignment to the following task:\n\n*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees";
|
||||||
|
$labels['itipmailbodydeclined'] = "\$sender has declined the assignment to the following task:\n\n*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees";
|
||||||
|
$labels['itipmailbodycancel'] = "\$sender has rejected your assignment to the following task:\n\n*\$title*\n\nWhen: \$date";
|
||||||
|
|
||||||
|
$labels['itipdeclineevent'] = 'Do you want to decline your assignment to this task?';
|
||||||
|
$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 participants';
|
||||||
|
|
|
@ -849,6 +849,84 @@ a.morelink:hover {
|
||||||
border-bottom: 2px solid #fafafa;
|
border-bottom: 2px solid #fafafa;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.edit-attendees-table {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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: 4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-attendees-table th.options,
|
||||||
|
.edit-attendees-table td.options {
|
||||||
|
width: 16px;
|
||||||
|
padding: 2px 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-attendees-table th.sendmail,
|
||||||
|
.edit-attendees-table td.sendmail {
|
||||||
|
width: 44px;
|
||||||
|
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 {
|
#taskedit-attachments {
|
||||||
margin: 0.6em 0;
|
margin: 0.6em 0;
|
||||||
}
|
}
|
||||||
|
@ -954,4 +1032,3 @@ label.block {
|
||||||
html.ie7 #taskedit-completeness-slider {
|
html.ie7 #taskedit-completeness-slider {
|
||||||
display: inline;
|
display: inline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<div id="taskedit" class="uidialog uidialog-tabbed">
|
<div id="taskedit" class="uidialog uidialog-tabbed">
|
||||||
<form id="taskeditform" action="#" method="post" enctype="multipart/form-data">
|
<form id="taskeditform" action="#" method="post" enctype="multipart/form-data">
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="#taskedit-panel-main"><roundcube:label name="tasklist.tabsummary" /></a></li><li><a href="#taskedit-panel-recurrence"><roundcube:label name="tasklist.tabrecurrence" /></a></li><li id="taskedit-tab-attachments"><a href="#taskedit-panel-attachments"><roundcube:label name="tasklist.tabattachments" /></a></li>
|
<li><a href="#taskedit-panel-main"><roundcube:label name="tasklist.tabsummary" /></a></li><li><a href="#taskedit-panel-recurrence"><roundcube:label name="tasklist.tabrecurrence" /></a></li><li id="edit-tab-attendees"><a href="#taskedit-panel-attendees"><roundcube:label name="tasklist.tabassignments" /></a></li><li id="taskedit-tab-attachments"><a href="#taskedit-panel-attachments"><roundcube:label name="tasklist.tabattachments" /></a></li>
|
||||||
</ul>
|
</ul>
|
||||||
<!-- basic info -->
|
<!-- basic info -->
|
||||||
<div id="taskedit-panel-main">
|
<div id="taskedit-panel-main">
|
||||||
|
@ -79,6 +79,13 @@
|
||||||
<roundcube:object name="plugin.recurrence_form" part="rdate" class="form-section" />
|
<roundcube:object name="plugin.recurrence_form" part="rdate" class="form-section" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- attendees list (assignments) -->
|
||||||
|
<div id="taskedit-panel-attendees">
|
||||||
|
<h3 id="aria-label-attendeestable" class="voice"><roundcube:label name="tasklist.arialabeleventassignments" /></h3>
|
||||||
|
<roundcube:object name="plugin.attendees_list" id="edit-attendees-table" class="records-table edit-attendees-table" coltitle="attendee" aria-labelledby="aria-label-attendeestable" />
|
||||||
|
<roundcube:object name="plugin.attendees_form" id="edit-attendees-form" />
|
||||||
|
<roundcube:include file="/templates/freebusylegend.html" />
|
||||||
|
</div>
|
||||||
<!-- attachments list (with upload form) -->
|
<!-- attachments list (with upload form) -->
|
||||||
<div id="taskedit-panel-attachments">
|
<div id="taskedit-panel-attachments">
|
||||||
<div id="taskedit-attachments">
|
<div id="taskedit-attachments">
|
||||||
|
@ -91,4 +98,5 @@
|
||||||
<roundcube:object name="plugin.filedroparea" id="taskedit-tab-2" />
|
<roundcube:object name="plugin.filedroparea" id="taskedit-tab-2" />
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
<roundcube:object name="plugin.edit_attendees_notify" id="edit-attendees-notify" class="taskedit-dialog-message" style="display:none" />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -82,6 +82,9 @@ function rcube_tasklist_ui(settings)
|
||||||
var tasklists_widget;
|
var tasklists_widget;
|
||||||
var focused_task;
|
var focused_task;
|
||||||
var focused_subclass;
|
var focused_subclass;
|
||||||
|
var task_attendees = [];
|
||||||
|
var attendees_list;
|
||||||
|
// var resources_list;
|
||||||
var me = this;
|
var me = this;
|
||||||
|
|
||||||
// general datepicker settings
|
// general datepicker settings
|
||||||
|
@ -541,6 +544,34 @@ function rcube_tasklist_ui(settings)
|
||||||
if (sel) $(sel).val('');
|
if (sel) $(sel).val('');
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// init attendees autocompletion
|
||||||
|
var ac_props;
|
||||||
|
// parallel autocompletion
|
||||||
|
if (rcmail.env.autocomplete_threads > 0) {
|
||||||
|
ac_props = {
|
||||||
|
threads: rcmail.env.autocomplete_threads,
|
||||||
|
sources: rcmail.env.autocomplete_sources
|
||||||
|
};
|
||||||
|
}
|
||||||
|
rcmail.init_address_input_events($('#edit-attendee-name'), ac_props);
|
||||||
|
rcmail.addEventListener('autocomplete_insert', function(e){
|
||||||
|
if (e.field.name == 'participant') {
|
||||||
|
$('#edit-attendee-add').click();
|
||||||
|
}
|
||||||
|
// else if (e.field.name == 'resource' && e.data && e.data.email) {
|
||||||
|
// add_attendee($.extend(e.data, { role:'REQ-PARTICIPANT', status:'NEEDS-ACTION', cutype:'RESOURCE' }));
|
||||||
|
// e.field.value = '';
|
||||||
|
// }
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#edit-attendee-add').click(function(){
|
||||||
|
var input = $('#edit-attendee-name');
|
||||||
|
rcmail.ksearch_blur();
|
||||||
|
if (add_attendees(input.val(), { role:'REQ-PARTICIPANT', status:'NEEDS-ACTION', cutype:'INDIVIDUAL' })) {
|
||||||
|
input.val('');
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1298,6 +1329,177 @@ function rcube_tasklist_ui(settings)
|
||||||
scroll_timer = window.setTimeout(function(){ tasklist_drag_scroll(container, dir); }, scroll_speed);
|
scroll_timer = window.setTimeout(function(){ tasklist_drag_scroll(container, dir); }, scroll_speed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check if the task has 'real' attendees, excluding the current user
|
||||||
|
var has_attendees = function(task)
|
||||||
|
{
|
||||||
|
return !!(task.attendees && task.attendees.length && (task.attendees.length > 1 || String(task.attendees[0].email).toLowerCase() != settings.identity.email));
|
||||||
|
};
|
||||||
|
|
||||||
|
// check if the current user is an attendee of this task
|
||||||
|
var is_attendee = function(task, role, email)
|
||||||
|
{
|
||||||
|
var i, emails = email ? ';' + email.toLowerCase() : settings.identity.emails;
|
||||||
|
|
||||||
|
for (i=0; task.attendees && i < task.attendees.length; i++) {
|
||||||
|
if ((!role || task.attendees[i].role == role) && task.attendees[i].email && emails.indexOf(';'+task.attendees[i].email.toLowerCase()) >= 0)
|
||||||
|
return task.attendees[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// check if the current user is the organizer
|
||||||
|
var is_organizer = function(task, email)
|
||||||
|
{
|
||||||
|
return is_attendee(task, 'ORGANIZER', email) || !task.id;
|
||||||
|
};
|
||||||
|
|
||||||
|
// add the given list of participants
|
||||||
|
var add_attendees = function(names, params)
|
||||||
|
{
|
||||||
|
names = explode_quoted_string(names.replace(/,\s*$/, ''), ',');
|
||||||
|
|
||||||
|
// parse name/email pairs
|
||||||
|
var item, email, name, success = false;
|
||||||
|
for (var i=0; i < names.length; i++) {
|
||||||
|
email = name = '';
|
||||||
|
item = $.trim(names[i]);
|
||||||
|
|
||||||
|
if (!item.length) {
|
||||||
|
continue;
|
||||||
|
} // address in brackets without name (do nothing)
|
||||||
|
else if (item.match(/^<[^@]+@[^>]+>$/)) {
|
||||||
|
email = item.replace(/[<>]/g, '');
|
||||||
|
} // address without brackets and without name (add brackets)
|
||||||
|
else if (rcube_check_email(item)) {
|
||||||
|
email = item;
|
||||||
|
} // address with name
|
||||||
|
else if (item.match(/([^\s<@]+@[^>]+)>*$/)) {
|
||||||
|
email = RegExp.$1;
|
||||||
|
name = item.replace(email, '').replace(/^["\s<>]+/, '').replace(/["\s<>]+$/, '');
|
||||||
|
}
|
||||||
|
if (email) {
|
||||||
|
add_attendee($.extend({ email:email, name:name }, params));
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
alert(rcmail.gettext('noemailwarning'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
|
};
|
||||||
|
|
||||||
|
// add the given attendee to the list
|
||||||
|
var add_attendee = function(data, readonly)
|
||||||
|
{
|
||||||
|
if (!me.selected_task)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// check for dupes...
|
||||||
|
var exists = false;
|
||||||
|
$.each(task_attendees, function(i, v) { exists |= (v.email == data.email); });
|
||||||
|
if (exists)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var list = me.selected_task && me.tasklists[me.selected_task.list] ? me.tasklists[me.selected_task.list] : me.tasklists[me.selected_list];
|
||||||
|
|
||||||
|
var dispname = Q(data.name || data.email);
|
||||||
|
if (data.email)
|
||||||
|
dispname = '<a href="mailto:' + data.email + '" title="' + Q(data.email) + '" class="mailtolink" data-cutype="' + data.cutype + '">' + dispname + '</a>';
|
||||||
|
|
||||||
|
// role selection
|
||||||
|
var organizer = data.role == 'ORGANIZER';
|
||||||
|
var opts = {};
|
||||||
|
if (organizer)
|
||||||
|
opts.ORGANIZER = rcmail.gettext('.roleorganizer');
|
||||||
|
opts['REQ-PARTICIPANT'] = rcmail.gettext('tasklist.rolerequired');
|
||||||
|
opts['OPT-PARTICIPANT'] = rcmail.gettext('tasklist.roleoptional');
|
||||||
|
opts['NON-PARTICIPANT'] = rcmail.gettext('tasklist.rolenonparticipant');
|
||||||
|
|
||||||
|
if (data.cutype != 'RESOURCE')
|
||||||
|
opts['CHAIR'] = rcmail.gettext('tasklist.rolechair');
|
||||||
|
|
||||||
|
if (organizer && !readonly)
|
||||||
|
dispname = rcmail.env['identities-selector'];
|
||||||
|
|
||||||
|
var select = '<select class="edit-attendee-role"' + (organizer || readonly ? ' disabled="true"' : '') + ' aria-label="' + rcmail.gettext('role','tasklist') + '">';
|
||||||
|
for (var r in opts)
|
||||||
|
select += '<option value="'+ r +'" class="' + r.toLowerCase() + '"' + (data.role == r ? ' selected="selected"' : '') +'>' + Q(opts[r]) + '</option>';
|
||||||
|
select += '</select>';
|
||||||
|
|
||||||
|
// availability
|
||||||
|
var avail = data.email ? 'loading' : 'unknown';
|
||||||
|
|
||||||
|
// delete icon
|
||||||
|
var icon = rcmail.env.deleteicon ? '<img src="' + rcmail.env.deleteicon + '" alt="" />' : rcmail.gettext('delete');
|
||||||
|
var dellink = '<a href="#delete" class="iconlink delete deletelink" title="' + Q(rcmail.gettext('delete')) + '">' + icon + '</a>';
|
||||||
|
var tooltip = data.status || '';
|
||||||
|
|
||||||
|
// send invitation checkbox
|
||||||
|
var invbox = '<input type="checkbox" class="edit-attendee-reply" value="' + Q(data.email) +'" title="' + Q(rcmail.gettext('tasklist.sendinvitations')) + '" '
|
||||||
|
+ (!data.noreply ? 'checked="checked" ' : '') + '/>';
|
||||||
|
|
||||||
|
if (data['delegated-to'])
|
||||||
|
tooltip = rcmail.gettext('delegatedto', 'tasklist') + data['delegated-to'];
|
||||||
|
else if (data['delegated-from'])
|
||||||
|
tooltip = rcmail.gettext('delegatedfrom', 'tasklist') + data['delegated-from'];
|
||||||
|
|
||||||
|
var html = '<td class="role">' + select + '</td>' +
|
||||||
|
'<td class="name">' + dispname + '</td>' +
|
||||||
|
// '<td class="availability"><img src="./program/resources/blank.gif" class="availabilityicon ' + avail + '" data-email="' + data.email + '" alt="" /></td>' +
|
||||||
|
'<td class="confirmstate"><span class="' + String(data.status).toLowerCase() + '" title="' + Q(tooltip) + '">' + Q(data.status || '') + '</span></td>' +
|
||||||
|
(data.cutype != 'RESOURCE' ? '<td class="sendmail">' + (organizer || readonly || !invbox ? '' : invbox) + '</td>' : '') +
|
||||||
|
'<td class="options">' + (organizer || readonly ? '' : dellink) + '</td>';
|
||||||
|
|
||||||
|
var table = rcmail.env.tasklist_resources && data.cutype == 'RESOURCE' ? resources_list : attendees_list;
|
||||||
|
var tr = $('<tr>')
|
||||||
|
.addClass(String(data.role).toLowerCase())
|
||||||
|
.html(html)
|
||||||
|
.appendTo(table);
|
||||||
|
|
||||||
|
tr.find('a.deletelink').click({ id:(data.email || data.name) }, function(e) { remove_attendee(this, e.data.id); return false; });
|
||||||
|
tr.find('a.mailtolink').click(task_attendee_click);
|
||||||
|
tr.find('input.edit-attendee-reply').click(function() {
|
||||||
|
var enabled = $('#edit-attendees-invite:checked').length || $('input.edit-attendee-reply:checked').length;
|
||||||
|
$('p.attendees-commentbox')[enabled ? 'show' : 'hide']();
|
||||||
|
});
|
||||||
|
|
||||||
|
// select organizer identity
|
||||||
|
if (data.identity_id)
|
||||||
|
$('#edit-identities-list').val(data.identity_id);
|
||||||
|
|
||||||
|
// check free-busy status
|
||||||
|
// if (avail == 'loading') {
|
||||||
|
// check_freebusy_status(tr.find('img.availabilityicon'), data.email, me.selected_task);
|
||||||
|
// }
|
||||||
|
|
||||||
|
task_attendees.push(data);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// event handler for clicks on an attendee link
|
||||||
|
var task_attendee_click = function(e)
|
||||||
|
{
|
||||||
|
var cutype = $(this).attr('data-cutype'),
|
||||||
|
mailto = this.href.substr(7);
|
||||||
|
|
||||||
|
if (rcmail.env.calendar_resources && cutype == 'RESOURCE') {
|
||||||
|
event_resources_dialog(mailto);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
rcmail.redirect(rcmail.url('mail/compose', { _to:mailto }));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// remove an attendee from the list
|
||||||
|
var remove_attendee = function(elem, id)
|
||||||
|
{
|
||||||
|
$(elem).closest('tr').remove();
|
||||||
|
task_attendees = $.grep(task_attendees, function(data) { return (data.name != id && data.email != id) });
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show task details in a dialog
|
* Show task details in a dialog
|
||||||
*/
|
*/
|
||||||
|
@ -1442,6 +1644,12 @@ function rcube_tasklist_ui(settings)
|
||||||
completeness_slider.slider('value', complete.val());
|
completeness_slider.slider('value', complete.val());
|
||||||
var taskstatus = $('#taskedit-status').val(rec.status || '');
|
var taskstatus = $('#taskedit-status').val(rec.status || '');
|
||||||
var tasklist = $('#taskedit-tasklist').val(rec.list || me.selected_list).prop('disabled', rec.parent_id ? true : false);
|
var tasklist = $('#taskedit-tasklist').val(rec.list || me.selected_list).prop('disabled', rec.parent_id ? true : false);
|
||||||
|
var notify = $('#edit-attendees-donotify').get(0);
|
||||||
|
var invite = $('#edit-attendees-invite').get(0);
|
||||||
|
var comment = $('#edit-attendees-comment');
|
||||||
|
|
||||||
|
notify.checked = has_attendees(rec);
|
||||||
|
invite.checked = true;
|
||||||
|
|
||||||
// tag-edit line
|
// tag-edit line
|
||||||
var tagline = $(rcmail.gui_objects.edittagline).empty();
|
var tagline = $(rcmail.gui_objects.edittagline).empty();
|
||||||
|
@ -1468,6 +1676,49 @@ function rcube_tasklist_ui(settings)
|
||||||
// set recurrence
|
// set recurrence
|
||||||
me.set_recurrence_edit(rec);
|
me.set_recurrence_edit(rec);
|
||||||
|
|
||||||
|
// init attendees tab
|
||||||
|
var organizer = !rec.attendees || is_organizer(rec),
|
||||||
|
allow_invitations = organizer || (rec.owner && rec.owner == 'anonymous') || settings.invite_shared;
|
||||||
|
|
||||||
|
task_attendees = [];
|
||||||
|
attendees_list = $('#edit-attendees-table > tbody').html('');
|
||||||
|
//resources_list = $('#edit-resources-table > tbody').html('');
|
||||||
|
$('#edit-attendees-notify')[(notify.checked && allow_invitations ? 'show' : 'hide')]();
|
||||||
|
$('#edit-localchanges-warning')[(has_attendees(rec) && !(allow_invitations || (rec.owner && is_organizer(rec, rec.owner))) ? 'show' : 'hide')]();
|
||||||
|
|
||||||
|
var load_attendees_tab = function()
|
||||||
|
{
|
||||||
|
var j, data, reply_selected = 0;
|
||||||
|
if (rec.attendees) {
|
||||||
|
for (j=0; j < rec.attendees.length; j++) {
|
||||||
|
data = rec.attendees[j];
|
||||||
|
add_attendee(data, !allow_invitations);
|
||||||
|
if (allow_invitations && data.role != 'ORGANIZER' && !data.noreply) {
|
||||||
|
reply_selected++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure comment box is visible if at least one attendee has reply enabled
|
||||||
|
// or global "send invitations" checkbox is checked
|
||||||
|
if (reply_selected || $('#edit-attendees-invite:checked').length) {
|
||||||
|
$('p.attendees-commentbox').show();
|
||||||
|
}
|
||||||
|
|
||||||
|
// select the correct organizer identity
|
||||||
|
var identity_id = 0;
|
||||||
|
$.each(settings.identities, function(i,v) {
|
||||||
|
if (organizer && v == organizer.email) {
|
||||||
|
identity_id = i;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#edit-identities-list').val(identity_id);
|
||||||
|
$('#edit-attendees-form')[(allow_invitations?'show':'hide')]();
|
||||||
|
// $('#edit-attendee-schedule')[(tasklist.freebusy?'show':'hide')]();
|
||||||
|
};
|
||||||
|
|
||||||
// attachments
|
// attachments
|
||||||
rcmail.enable_command('remove-attachment', list.editable);
|
rcmail.enable_command('remove-attachment', list.editable);
|
||||||
me.selected_task.deleted_attachments = [];
|
me.selected_task.deleted_attachments = [];
|
||||||
|
@ -1491,23 +1742,26 @@ function rcube_tasklist_ui(settings)
|
||||||
// define dialog buttons
|
// define dialog buttons
|
||||||
var buttons = {};
|
var buttons = {};
|
||||||
buttons[rcmail.gettext('save', 'tasklist')] = function() {
|
buttons[rcmail.gettext('save', 'tasklist')] = function() {
|
||||||
|
var data = me.selected_task;
|
||||||
|
|
||||||
// copy form field contents into task object to save
|
// copy form field contents into task object to save
|
||||||
$.each({ title:title, description:description, date:recdate, time:rectime, startdate:recstartdate, starttime:recstarttime, status:taskstatus, list:tasklist }, function(key,input){
|
$.each({ title:title, description:description, date:recdate, time:rectime, startdate:recstartdate, starttime:recstarttime, status:taskstatus, list:tasklist }, function(key,input){
|
||||||
me.selected_task[key] = input.val();
|
data[key] = input.val();
|
||||||
});
|
});
|
||||||
me.selected_task.tags = [];
|
data.tags = [];
|
||||||
me.selected_task.attachments = [];
|
data.attachments = [];
|
||||||
me.selected_task.valarms = me.serialize_alarms('#taskedit-alarms');
|
data.attendees = task_attendees;
|
||||||
me.selected_task.recurrence = me.serialize_recurrence(rectime.val());
|
data.valarms = me.serialize_alarms('#taskedit-alarms');
|
||||||
|
data.recurrence = me.serialize_recurrence(rectime.val());
|
||||||
|
|
||||||
// do some basic input validation
|
// do some basic input validation
|
||||||
if (!me.selected_task.title || !me.selected_task.title.length) {
|
if (!data.title || !data.title.length) {
|
||||||
title.focus();
|
title.focus();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else if (me.selected_task.startdate && me.selected_task.date) {
|
else if (data.startdate && data.date) {
|
||||||
var startdate = $.datepicker.parseDate(datepicker_settings.dateFormat, me.selected_task.startdate, datepicker_settings);
|
var startdate = $.datepicker.parseDate(datepicker_settings.dateFormat, data.startdate, datepicker_settings);
|
||||||
var duedate = $.datepicker.parseDate(datepicker_settings.dateFormat, me.selected_task.date, datepicker_settings);
|
var duedate = $.datepicker.parseDate(datepicker_settings.dateFormat, data.date, datepicker_settings);
|
||||||
if (startdate > duedate) {
|
if (startdate > duedate) {
|
||||||
alert(rcmail.gettext('invalidstartduedates', 'tasklist'));
|
alert(rcmail.gettext('invalidstartduedates', 'tasklist'));
|
||||||
return false;
|
return false;
|
||||||
|
@ -1517,36 +1771,74 @@ function rcube_tasklist_ui(settings)
|
||||||
// collect tags
|
// collect tags
|
||||||
$('input[type="hidden"]', rcmail.gui_objects.edittagline).each(function(i,elem) {
|
$('input[type="hidden"]', rcmail.gui_objects.edittagline).each(function(i,elem) {
|
||||||
if (elem.value)
|
if (elem.value)
|
||||||
me.selected_task.tags.push(elem.value);
|
data.tags.push(elem.value);
|
||||||
});
|
});
|
||||||
// including the "pending" one in the text box
|
// including the "pending" one in the text box
|
||||||
var newtag = $('#tagedit-input').val();
|
var newtag = $('#tagedit-input').val();
|
||||||
if (newtag != '') {
|
if (newtag != '') {
|
||||||
me.selected_task.tags.push(newtag);
|
data.tags.push(newtag);
|
||||||
}
|
}
|
||||||
|
|
||||||
// uploaded attachments list
|
// uploaded attachments list
|
||||||
for (var i in rcmail.env.attachments) {
|
for (var i in rcmail.env.attachments) {
|
||||||
if (i.match(/^rcmfile(.+)/))
|
if (i.match(/^rcmfile(.+)/))
|
||||||
me.selected_task.attachments.push(RegExp.$1);
|
data.attachments.push(RegExp.$1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// task assigned to a new list
|
// task assigned to a new list
|
||||||
if (me.selected_task.list && listdata[id] && me.selected_task.list != listdata[id].list) {
|
if (data.list && listdata[id] && data.list != listdata[id].list) {
|
||||||
me.selected_task._fromlist = list.id;
|
data._fromlist = list.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
me.selected_task.complete = complete.val() / 100;
|
data.complete = complete.val() / 100;
|
||||||
if (isNaN(me.selected_task.complete))
|
if (isNaN(data.complete))
|
||||||
me.selected_task.complete = null;
|
data.complete = null;
|
||||||
|
|
||||||
if (!me.selected_task.list && list.id)
|
if (!data.list && list.id)
|
||||||
me.selected_task.list = list.id;
|
data.list = list.id;
|
||||||
|
|
||||||
if (!me.selected_task.tags.length)
|
if (!data.tags.length)
|
||||||
me.selected_task.tags = '';
|
data.tags = '';
|
||||||
|
|
||||||
if (save_task(me.selected_task, action))
|
// read attendee roles
|
||||||
|
$('select.edit-attendee-role').each(function(i, elem) {
|
||||||
|
if (data.attendees[i]) {
|
||||||
|
data.attendees[i].role = $(elem).val();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (organizer) {
|
||||||
|
data._identity = $('#edit-identities-list option:selected').val();
|
||||||
|
}
|
||||||
|
|
||||||
|
// don't submit attendees if only myself is added as organizer
|
||||||
|
if (data.attendees.length == 1 && data.attendees[0].role == 'ORGANIZER' && String(data.attendees[0].email).toLowerCase() == settings.identity.email) {
|
||||||
|
data.attendees = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// per-attendee notification suppression
|
||||||
|
var need_invitation = false;
|
||||||
|
if (allow_invitations) {
|
||||||
|
$.each(data.attendees, function (i, v) {
|
||||||
|
if (v.role != 'ORGANIZER') {
|
||||||
|
if ($('input.edit-attendee-reply[value="' + v.email + '"]').prop('checked')) {
|
||||||
|
need_invitation = true;
|
||||||
|
delete data.attendees[i]['noreply'];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
data.attendees[i].noreply = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// tell server to send notifications
|
||||||
|
if ((data.attendees.length || (rec.id && rec.attendees.length)) && allow_invitations && (notify.checked || invite.checked || need_invitation)) {
|
||||||
|
data._notify = 1;
|
||||||
|
data._comment = comment.val();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (save_task(data, action))
|
||||||
$dialog.dialog('close');
|
$dialog.dialog('close');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1581,8 +1873,10 @@ function rcube_tasklist_ui(settings)
|
||||||
|
|
||||||
// set dialog size according to content
|
// set dialog size according to content
|
||||||
me.dialog_resize($dialog.get(0), $dialog.height(), 580);
|
me.dialog_resize($dialog.get(0), $dialog.height(), 580);
|
||||||
}
|
|
||||||
|
|
||||||
|
if (tasklist.attendees)
|
||||||
|
window.setTimeout(load_attendees_tab, 1);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open a task attachment either in a browser window for inline view or download it
|
* Open a task attachment either in a browser window for inline view or download it
|
||||||
|
@ -2060,6 +2354,29 @@ function rcube_tasklist_ui(settings)
|
||||||
|
|
||||||
/**** Utility functions ****/
|
/**** Utility functions ****/
|
||||||
|
|
||||||
|
// same as str.split(delimiter) but it ignores delimiters within quoted strings
|
||||||
|
var explode_quoted_string = function(str, delimiter)
|
||||||
|
{
|
||||||
|
var result = [],
|
||||||
|
strlen = str.length,
|
||||||
|
q, p, i, char, last;
|
||||||
|
|
||||||
|
for (q = p = i = 0; i < strlen; i++) {
|
||||||
|
char = str.charAt(i);
|
||||||
|
if (char == '"' && last != '\\') {
|
||||||
|
q = !q;
|
||||||
|
}
|
||||||
|
else if (!q && char == delimiter) {
|
||||||
|
result.push(str.substring(p, i));
|
||||||
|
p = i + 1;
|
||||||
|
}
|
||||||
|
last = char;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.push(str.substr(p));
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clear any text selection
|
* Clear any text selection
|
||||||
* (text is probably selected when double-clicking somewhere)
|
* (text is probably selected when double-clicking somewhere)
|
||||||
|
@ -2205,7 +2522,7 @@ jQuery.unqiqueStrings = (function() {
|
||||||
var rctasks;
|
var rctasks;
|
||||||
window.rcmail && rcmail.addEventListener('init', function(evt) {
|
window.rcmail && rcmail.addEventListener('init', function(evt) {
|
||||||
|
|
||||||
rctasks = new rcube_tasklist_ui(rcmail.env.libcal_settings);
|
rctasks = new rcube_tasklist_ui($.extend(rcmail.env.tasklist_settings, rcmail.env.libcal_settings));
|
||||||
|
|
||||||
// register button commands
|
// register button commands
|
||||||
rcmail.register_command('newtask', function(){ rctasks.edit_task(null, 'new', {}); }, true);
|
rcmail.register_command('newtask', function(){ rctasks.edit_task(null, 'new', {}); }, true);
|
||||||
|
|
|
@ -55,11 +55,42 @@ class tasklist_ui
|
||||||
$this->plugin->include_script('tasklist_base.js');
|
$this->plugin->include_script('tasklist_base.js');
|
||||||
|
|
||||||
// copy config to client
|
// copy config to client
|
||||||
// $this->rc->output->set_env('tasklist_settings', $settings);
|
$this->rc->output->set_env('tasklist_settings', $this->load_settings());
|
||||||
|
|
||||||
|
// initialize attendees autocompletion
|
||||||
|
$this->rc->autocomplete_init();
|
||||||
|
|
||||||
$this->ready = true;
|
$this->ready = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
function load_settings()
|
||||||
|
{
|
||||||
|
$settings = array();
|
||||||
|
|
||||||
|
//$settings['invite_shared'] = (int)$this->rc->config->get('calendar_allow_invite_shared', $this->defaults['calendar_allow_invite_shared']);
|
||||||
|
|
||||||
|
// get user identity to create default attendee
|
||||||
|
foreach ($this->rc->user->list_identities() as $rec) {
|
||||||
|
if (!$identity)
|
||||||
|
$identity = $rec;
|
||||||
|
|
||||||
|
$identity['emails'][] = $rec['email'];
|
||||||
|
$settings['identities'][$rec['identity_id']] = $rec['email'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$identity['emails'][] = $this->rc->user->get_username();
|
||||||
|
$settings['identity'] = array(
|
||||||
|
'name' => $identity['name'],
|
||||||
|
'email' => strtolower($identity['email']),
|
||||||
|
'emails' => ';' . strtolower(join(';', $identity['emails']))
|
||||||
|
);
|
||||||
|
|
||||||
|
return $settings;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register handler methods for the template engine
|
* Register handler methods for the template engine
|
||||||
*/
|
*/
|
||||||
|
@ -78,6 +109,9 @@ class tasklist_ui
|
||||||
$this->plugin->register_handler('plugin.attachments_form', array($this, 'attachments_form'));
|
$this->plugin->register_handler('plugin.attachments_form', array($this, 'attachments_form'));
|
||||||
$this->plugin->register_handler('plugin.attachments_list', array($this, 'attachments_list'));
|
$this->plugin->register_handler('plugin.attachments_list', array($this, 'attachments_list'));
|
||||||
$this->plugin->register_handler('plugin.filedroparea', array($this, 'file_drop_area'));
|
$this->plugin->register_handler('plugin.filedroparea', array($this, 'file_drop_area'));
|
||||||
|
$this->plugin->register_handler('plugin.attendees_list', array($this, 'attendees_list'));
|
||||||
|
$this->plugin->register_handler('plugin.attendees_form', array($this, 'attendees_form'));
|
||||||
|
$this->plugin->register_handler('plugin.edit_attendees_notify', array($this, 'edit_attendees_notify'));
|
||||||
|
|
||||||
$this->plugin->include_script('jquery.tagedit.js');
|
$this->plugin->include_script('jquery.tagedit.js');
|
||||||
$this->plugin->include_script('tasklist.js');
|
$this->plugin->include_script('tasklist.js');
|
||||||
|
@ -375,4 +409,54 @@ class tasklist_ui
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
function attendees_list($attrib = array())
|
||||||
|
{
|
||||||
|
// add "noreply" checkbox to attendees table only
|
||||||
|
$invitations = strpos($attrib['id'], 'attend') !== false;
|
||||||
|
|
||||||
|
$invite = new html_checkbox(array('value' => 1, 'id' => 'edit-attendees-invite'));
|
||||||
|
$table = new html_table(array('cols' => 4 + intval($invitations), 'border' => 0, 'cellpadding' => 0, 'class' => 'rectable'));
|
||||||
|
|
||||||
|
$table->add_header('role', $this->plugin->gettext('role'));
|
||||||
|
$table->add_header('name', $this->plugin->gettext($attrib['coltitle'] ?: 'attendee'));
|
||||||
|
// $table->add_header('availability', $this->plugin->gettext('availability'));
|
||||||
|
$table->add_header('confirmstate', $this->plugin->gettext('confirmstate'));
|
||||||
|
if ($invitations) {
|
||||||
|
$table->add_header(array('class' => 'sendmail', 'title' => $this->plugin->gettext('sendinvitations')),
|
||||||
|
$invite->show(1) . html::label('edit-attendees-invite', $this->plugin->gettext('sendinvitations')));
|
||||||
|
}
|
||||||
|
$table->add_header('options', '');
|
||||||
|
|
||||||
|
return $table->show($attrib);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
function attendees_form($attrib = array())
|
||||||
|
{
|
||||||
|
$input = new html_inputfield(array('name' => 'participant', 'id' => 'edit-attendee-name', 'size' => 30));
|
||||||
|
$textarea = new html_textarea(array('name' => 'comment', 'id' => 'edit-attendees-comment',
|
||||||
|
'rows' => 4, 'cols' => 55, 'title' => $this->plugin->gettext('itipcommenttitle')));
|
||||||
|
|
||||||
|
return html::div($attrib,
|
||||||
|
html::div(null, $input->show() . " " .
|
||||||
|
html::tag('input', array('type' => 'button', 'class' => 'button', 'id' => 'edit-attendee-add', 'value' => $this->plugin->gettext('addattendee')))
|
||||||
|
// . " " . html::tag('input', array('type' => 'button', 'class' => 'button', 'id' => 'edit-attendee-schedule', 'value' => $this->plugin->gettext('scheduletime').'...'))
|
||||||
|
) .
|
||||||
|
html::p('attendees-commentbox', html::label(null, $this->plugin->gettext('itipcomment') . $textarea->show()))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
function edit_attendees_notify($attrib = array())
|
||||||
|
{
|
||||||
|
$checkbox = new html_checkbox(array('name' => '_notify', 'id' => 'edit-attendees-donotify', 'value' => 1));
|
||||||
|
return html::div($attrib, html::label(null, $checkbox->show(1) . ' ' . $this->plugin->gettext('sendnotifications')));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue