Partial invitations handling, participants autocompletion (T726, T729)

This commit is contained in:
Aleksander Machniak 2015-11-19 21:10:41 +01:00
parent 9d32f5cbae
commit 48ddb0eea4
5 changed files with 277 additions and 16 deletions

View file

@ -1125,7 +1125,9 @@ function manticore_init()
var info = rcmail.env.file_data;
rcmail.enable_command('document-save', 'document-export', true);
rcmail.enable_command('files-close', info && info.session && info.session.is_owner);
if (info && info.session && info.session.is_owner)
rcmail.enable_command('files-close', 'document-editors', true);
};
rcube_webmail.prototype.document_save = function()
@ -1138,6 +1140,144 @@ rcube_webmail.prototype.document_export = function(type)
manticore.export(type || 'odt');
};
rcube_webmail.prototype.document_editors = function()
{
var info = rcmail.env.file_data;
if (!info || !info.session || !info.session.is_owner)
return;
kolab_files_editors_dialog(info.session);
};
// close editing session
rcube_webmail.prototype.files_close = function()
{
// @todo: check document "unsaved changes" state and display a warning
file_api.document_delete(this.env.file_data.session.id);
};
// document editors management dialog
function kolab_files_editors_dialog(session)
{
var ac_props, items = [], buttons = {},
dialog = $('#document-editors-dialog');
// always add the session organizer
items.push(kolab_files_attendee_record(session.owner, 'organizer'));
$.each(session.invitations || [], function() {
items.push(kolab_files_attendee_record(this.user, this.status));
});
$('table > tbody', dialog).html(items);
buttons[rcmail.gettext('kolab_files.close')] = function() {
kolab_dialog_close(this);
};
// show dialog window
kolab_dialog_show(dialog, {
title: rcmail.gettext('kolab_files.manageeditors'),
buttons: buttons,
button_classes: ['mainaction']
});
if (!rcmail.env.editors_dialog) {
rcmail.env.editors_dialog = dialog;
// init attendees autocompletion
if (rcmail.env.autocomplete_threads > 0) {
ac_props = {
threads: rcmail.env.autocomplete_threads,
sources: rcmail.env.autocomplete_sources
};
}
rcmail.init_address_input_events($('#invitation-editor-name'), ac_props);
rcmail.addEventListener('autocomplete_insert', function(e) {
var success = false;
if (e.field.name == 'participant') {
success = kolab_files_add_attendees(e.insert, 'invited', e.data && e.data.type == 'group' ? 'GROUP' : 'INDIVIDUAL');
}
if (e.field && success) {
e.field.value = '';
}
});
$('#invitation-editor-add').click(function() {
var input = $('#invitation-editor-name');
rcmail.ksearch_blur();
if (kolab_files_add_attendees(input.val(), 'invited', 'INDIVIDUAL')) {
input.val('');
}
});
}
};
// add the given list of participants
function kolab_files_add_attendees(names)
{
var i, item, email, name, attendees = {}, counter = 0;
names = file_api.explode_quoted_string(names.replace(/,\s*$/, ''), ',');
// parse name/email pairs
for (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) {
attendees[email] = {user: email, name: name};
counter++;
}
else {
alert(rcmail.gettext('noemailwarning'));
}
}
// remove already existing entries
if (counter) {
if (attendees[rcmail.env.file_data.session.owner]) {
delete attendees[this.user];
counter--;
}
$.each(rcmail.env.file_data.session.invitations || [], function() {
if (this.user in attendees) {
delete attendees[this.user];
counter--;
}
});
}
if (counter)
file_api.document_invite(rcmail.env.file_data.session.id, attendees);
};
function kolab_files_attendee_record(user, status)
{
return $('<tr>').attr('class', 'invitation' + (status ? ' ' + status : ''))
.append($('<td class="name">').text(user))
.append($('<td class="status">').text(rcmail.gettext('kolab_files.status' + status)))
.append($('<td class="options">'));
// @todo: delete and accept button
};
/***********************************************************/
/********** Commands **********/
@ -1283,13 +1423,7 @@ rcube_webmail.prototype.files_edit = function(session)
}
};
// close editing session
rcube_webmail.prototype.files_close = function()
{
// @todo: check document "unsaved changes" state and display a warning
file_api.document_delete(this.env.file_data.session.id);
};
// save changes to the file
rcube_webmail.prototype.files_save = function()
{
if (!this.file_editor)
@ -2935,6 +3069,36 @@ function kolab_files_ui()
// @todo: force sessions info update
};
// Invite document session participants
this.document_invite = function(id, attendees)
{
var list = [];
// expect attendees to be email => name hash
$.each(attendees || {}, function() { list.push(this); });
if (list.length) {
this.req = this.set_busy(true, 'kolab_files.documentinviting');
this.request('document_invite', {id: id, users: list}, 'document_invite_response');
}
};
// document invite response handler
this.document_invite_response = function(response)
{
if (!this.response(response))
return;
var info = rcmail.env.file_data,
table = $('#document-editors-dialog table > tbody');
$.each(response.list || {}, function() {
table.appned(kolab_files_attendee_record(this.user, this.status));
if (info.session && info.session.invitations)
info.session.invitations.push($.merge({status: 'invited'}, this));
});
};
// handle auth errors on folder list
this.folder_list_auth_errors = function(result)
{

View file

@ -147,6 +147,7 @@ class kolab_files_engine
'file-edit-dialog' => array($this, 'file_edit_dialog'),
'filelist' => array($this, 'file_list'),
'filequotadisplay' => array($this, 'quota_display'),
'document-editors-dialog' => array($this, 'document_editors_dialog'),
));
if ($this->rc->task != 'files') {
@ -342,6 +343,37 @@ class kolab_files_engine
return '<div></div>';
}
/**
* Template object for dcument editors dialog
*/
public function document_editors_dialog($attrib)
{
$table = new html_table(array('cols' => 3, 'border' => 0, 'cellpadding' => 0, 'class' => 'records-table'));
$table->add_header('username', $this->plugin->gettext('participant'));
$table->add_header('status', $this->plugin->gettext('status'));
$table->add_header('options', null);
$input = new html_inputfield(array('name' => 'participant', 'id' => 'invitation-editor-name', 'size' => 30));
$textarea = new html_textarea(array('name' => 'comment', 'id' => 'invitation-comment',
'rows' => 4, 'cols' => 55, 'title' => $this->plugin->gettext('invitationtexttitle')));
$button = new html_inputfield(array('type' => 'button', 'class' => 'button', 'id' => 'invitation-editor-add', 'value' => $this->plugin->gettext('addparticipant')));
$this->plugin->add_label('close', 'manageeditors', 'statusorganizer', 'statusaccepted',
'statusinvited', 'statusdeclined', 'statusrequested'
);
// initialize attendees autocompletion
$this->rc->autocomplete_init();
return '<div>' . $table->show() . html::div(null,
html::div(null, $input->show() . " " . $button->show())
. html::p('attendees-commentbox', html::label(null,
$this->plugin->gettext('invitationtextlabel') . $textarea->show())
)
) . '</div>';
}
/**
* Template object for file_rename form
*/

View file

@ -49,6 +49,7 @@ $labels['createandedit'] = 'Create and Edit';
$labels['copyfile'] = 'Copy a file';
$labels['copyandedit'] = 'Copy and Edit';
$labels['documenttitle'] = 'Title:';
$labels['close'] = 'Close';
$labels['collection_audio'] = 'Audio';
$labels['collection_video'] = 'Video';
@ -104,6 +105,18 @@ $labels['select'] = 'Select';
$labels['terminatesession'] = 'Terminate the session';
$labels['terminate'] = 'Terminate';
$labels['sessionterminating'] = 'Terminating the session...';
$labels['manageeditors'] = 'Invite to document';
$labels['participant'] = 'Participant';
$labels['status'] = 'Status';
$labels['addparticipant'] = 'Add participant';
$labels['delparticipant'] = 'Remove participant';
$labels['invitationtexttitle'] = 'This comment will be attached to the invitation/notification message sent to the participant';
$labels['invitationtextlabel'] = 'Invitation/notification comment';
$labels['statusorganizer'] = 'Organizer';
$labels['statusinvited'] = 'Invited';
$labels['statusaccepted'] = 'Accepted';
$labels['statusdeclined'] = 'Declined';
$labels['statusrequested'] = 'Requested';
$labels['storepasswords'] = 'remember password';
$labels['storepasswordsdesc'] = 'Stored passwords will be encrypted. Enable this if you do not want to be asked for the password on every login or you want this storage to be available via WebDAV.';

View file

@ -82,6 +82,10 @@
text-shadow: 0 1px 1px #eee;
}
ul.toolbarmenu li span.saveas {
background: url(images/buttons.png) -5px -253px no-repeat;
}
#document-title {
width: 200px;
}
@ -102,6 +106,15 @@
margin-left: 5px;
}
#collaborators a.button.add {
background: url(../../../../skins/larry/images/buttons.png) -5px -357px no-repeat;
min-width: 16px;
width: 16px;
height: 16px;
padding: 2px;
margin-top: 10px;
}
#quicksearchbar #filesearchmenulink {
position: absolute;
top: 5px;
@ -400,7 +413,8 @@
#files-folder-mount-dialog,
#files-folder-auth-dialog,
#files-folder-create-dialog,
#files-folder-edit-dialog {
#files-folder-edit-dialog,
#document-editors-dialog {
display: none;
}
@ -489,10 +503,6 @@ a.filesaveall {
width: 200px;
}
ul.toolbarmenu li span.saveas {
background: url(images/buttons.png) -5px -253px no-repeat;
}
table.propform td.source.selected {
background-color: #c7e3ef;
}
@ -542,3 +552,43 @@ table.propform td.source table.propform td {
.auth-options label {
vertical-align: middle;
}
#document-editors-dialog textarea {
width: 98%
}
#document-editors-dialog label {
display: block;
}
#document-editors-dialog table {
margin-top: 0.5em;
margin-bottom: 1em;
}
#document-editors-dialog table th.status {
width: 9em;
}
#document-editors-dialog table th.options {
width: 40px
}
#document-editors-dialog table td {
padding-top: 4px;
padding-bottom: 4px;
}
#document-editors-dialog table td.name {
overflow: hidden;
text-overflow: ellipsis;
width: auto;
}
#document-editors-dialog table tr:last-child td {
border-bottom: 0;
}
#document-editors-dialog table tr.organizer td {
color: #888;
}

View file

@ -34,6 +34,7 @@
<h2 id="aria-label-collaborators" class="voice"><roundcube:label name="kolab_files.arialabelcollaborators" /></h2>
<div id="collaborators" class="toolbar" role="toolbar" aria-labelledby="aria-label-collaborators">
<roundcube:button command="document-editors" type="link" class="button add disabled" classAct="button add" classSel="button add pressed" content=" " title="kolab_files.manageeditors" />
<div id="members"></div>
</div>
@ -49,11 +50,12 @@
<h3 id="aria-label-exportmenu" class="voice"><roundcube:label name="kolab_files.arialabelexportoptions" /></h3>
<ul id="exportmenu-menu" class="toolbarmenu" role="menu" aria-labelledby="aria-label-exportmenu"></ul>
</div>
<!--
<div id="editors-dialog" class="uidialog" data-editable="true" role="dialog" aria-labelledby="aria-label-doceditorsdialog" aria-hidden="true">
<div id="document-editors-dialog" class="uidialog" data-editable="true" role="dialog" aria-labelledby="aria-label-doceditorsdialog" aria-hidden="true">
<h3 id="aria-label-doceditorsdialog" class="voice"><roundcube:label name="kolab_files.arialabeldoceditorsdialog" /></h3>
<roundcube:object name="document-editors-dialog" />
</div>
-->
<roundcube:include file="/includes/footer.html" />
<script type="text/javascript">