diff --git a/plugins/kolab_files/kolab_files.js b/plugins/kolab_files/kolab_files.js index 2ed926fa..59f767fe 100644 --- a/plugins/kolab_files/kolab_files.js +++ b/plugins/kolab_files/kolab_files.js @@ -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 $('').attr('class', 'invitation' + (status ? ' ' + status : '')) + .append($('').text(user)) + .append($('').text(rcmail.gettext('kolab_files.status' + status))) + .append($('')); + // @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) { diff --git a/plugins/kolab_files/lib/kolab_files_engine.php b/plugins/kolab_files/lib/kolab_files_engine.php index 7954b1cb..efa70769 100644 --- a/plugins/kolab_files/lib/kolab_files_engine.php +++ b/plugins/kolab_files/lib/kolab_files_engine.php @@ -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 '
'; } + /** + * 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 '
' . $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()) + ) + ) . '
'; + } + /** * Template object for file_rename form */ diff --git a/plugins/kolab_files/localization/en_US.inc b/plugins/kolab_files/localization/en_US.inc index a95b6586..a2812d2b 100644 --- a/plugins/kolab_files/localization/en_US.inc +++ b/plugins/kolab_files/localization/en_US.inc @@ -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.'; diff --git a/plugins/kolab_files/skins/larry/style.css b/plugins/kolab_files/skins/larry/style.css index dd61b9f5..30bed293 100644 --- a/plugins/kolab_files/skins/larry/style.css +++ b/plugins/kolab_files/skins/larry/style.css @@ -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; +} diff --git a/plugins/kolab_files/skins/larry/templates/docedit.html b/plugins/kolab_files/skins/larry/templates/docedit.html index 4839d22a..cbcd3ef9 100644 --- a/plugins/kolab_files/skins/larry/templates/docedit.html +++ b/plugins/kolab_files/skins/larry/templates/docedit.html @@ -34,6 +34,7 @@

@@ -49,11 +50,12 @@

- +