Open and join collaborative sessions (T724, T725), Editing read-only documents (T757)

This commit is contained in:
Aleksander Machniak 2015-11-17 14:35:13 +01:00
parent 2ad06d8870
commit c323706a9b
6 changed files with 255 additions and 74 deletions

View file

@ -6,7 +6,7 @@
* @licstart The following is the entire license notice for the
* JavaScript code in this file.
*
* Copyright (C) 2011, Kolab Systems AG <contact@kolabsys.com>
* Copyright (C) 2011-2015, Kolab Systems AG <contact@kolabsys.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@ -125,6 +125,14 @@ window.rcmail && window.files_api && rcmail.addEventListener('init', function()
display_message: function(label, type) { return rcmail.display_message('kolab_files.' + label, type); },
gettext: function(label) { return rcmail.get_label('kolab_files.' + label); }
});
if (rcmail.env.action == 'open') {
// initialize folders list (for dialogs)
file_api.folder_list();
// get ongoing sessions
file_api.request('folder_info', {folder: file_api.file_path(rcmail.env.file), sessions: 1}, 'folder_info_response');
}
}
else {
file_api.folder_list();
@ -395,7 +403,7 @@ function kolab_files_folder_create_dialog()
$('form', dialog).submit(kolab_dialog_submit_handler);
// build parent selector
kolab_files_folder_select_element(select, {empty: true});
file_api.folder_select_element(select, {empty: true, writable: true});
};
// folder edit dialog
@ -440,7 +448,7 @@ function kolab_files_folder_edit_dialog()
$('form', dialog).submit(kolab_dialog_submit_handler);
// build parent selector
kolab_files_folder_select_element(select, {selected: path, empty: true});
file_api.folder_select_element(select, {selected: path, empty: true});
};
// folder mounting dialog
@ -504,6 +512,92 @@ function kolab_files_folder_mount_dialog()
});
};
// file edit dialog
function kolab_files_file_edit_dialog(file, sessions, readonly)
{
var content = [], items = [], height = 300;
dialog = $('#files-file-edit-dialog'),
buttons = {}, name = file_api.file_name(file),
title = rcmail.gettext('kolab_files.editfiledialog'),
mainaction = rcmail.gettext('kolab_files.select'),
item_fn = function(id, txt, classes) {
return $('<label>').attr('class', 'session' + (classes ? ' ' + classes : ''))
.append($('<input>').attr({name: 'opt', value: id, type: 'radio'})).append($('<span>').text(txt));
},
select_fn = function(dlg) {
var input = $('input:checked', dialog), id = input.val();
if (dlg)
kolab_dialog_close(dlg);
if (readonly && (id == 0 || !input.length))
return kolab_files_file_create_dialog(file);
// @todo: invitations
rcmail.files_edit(id ? id : true);
};
// Create sessions selection
if (sessions && sessions.length) {
items.push($('<div>').text(rcmail.gettext('kolab_files.editfilesessions')));
// first display owned sessions, then invited, other at the end
$.each(sessions, function() {
if (this.is_owner) {
var txt = rcmail.gettext('kolab_files.ownedsession');
items.push(item_fn(this.id, txt, 'owner'));
}
});
if (items.length == 1)
items.push(item_fn(0, rcmail.gettext('kolab_files.newsession' + (readonly ? 'ro' : ''))));
$.each(sessions, function() {
if (this.is_invited) {
var txt = rcmail.gettext('kolab_files.invitedsession').replace('$user', this.owner);
items.push(item_fn(this.id, txt, 'invited'));
}
});
$.each(sessions, function() {
if (!this.is_owner && !this.is_invited) {
var txt = rcmail.gettext('kolab_files.joinsession').replace('$user', this.owner);
items.push(item_fn(this.id, txt));
}
});
// check the first option
$('input', items[1]).attr('checked', true);
$('div', dialog).html(items);
// if there's only one session and it's owned, skip the dialog
if (!readonly && items.length == 2 && $('input:checked', dialog).parent().is('.owner'))
return select_fn();
}
// no ongoing session, folder is readonly warning
else {
title = rcmail.gettext('kolab_files.editfilerotitle');
height = 150;
$('div', dialog).text(rcmail.gettext('kolab_files.editfilero'));
mainaction = rcmail.gettext('kolab_files.create');
}
buttons[mainaction] = function() { select_fn(this); };
buttons[rcmail.gettext('kolab_files.cancel')] = function () {
kolab_dialog_close(this);
};
// show dialog window
kolab_dialog_show(dialog, {
title: title,
buttons: buttons,
button_classes: ['mainaction'],
minHeight: height - 100,
height: height
});
};
// file rename dialog
function kolab_files_file_rename_dialog(file)
{
@ -511,7 +605,7 @@ function kolab_files_file_rename_dialog(file)
buttons = {}, name = file_api.file_name(file)
input = $('input[name="name"]', dialog).val(name);
buttons[rcmail.gettext('kolab_files.save')] = function () {
buttons[rcmail.gettext('kolab_files.save')] = function() {
var folder = file_api.file_path(file), name = input.val();
if (!name)
@ -524,7 +618,7 @@ function kolab_files_file_rename_dialog(file)
kolab_dialog_close(this);
};
buttons[rcmail.gettext('kolab_files.cancel')] = function () {
buttons[rcmail.gettext('kolab_files.cancel')] = function() {
kolab_dialog_close(this);
};
@ -541,16 +635,16 @@ function kolab_files_file_rename_dialog(file)
});
};
// file creation dialog
function kolab_files_file_create_dialog()
// file creation (or cloning) dialog
function kolab_files_file_create_dialog(file)
{
var dialog = $('#files-file-create-dialog'),
buttons = {},
var buttons = {}, action = file ? 'copy' : 'create',
dialog = $('#files-file-create-dialog'),
type_select = $('select[name="type"]', dialog),
select = $('select[name="parent"]', dialog).html(''),
input = $('input[name="name"]', dialog).val(''),
create_func = function (dialog, editaction) {
var folder = select.val(), type = type_select.val(), name = input.val();
create_func = function(dialog, editaction) {
var sel, folder = select.val(), type = type_select.val(), name = input.val();
if (!name || !folder)
return;
@ -561,17 +655,36 @@ function kolab_files_file_create_dialog()
name = folder + file_api.env.directory_separator + name;
file_api.file_create(name, type, editaction);
// get type of cloned file
if (file) {
if (rcmail.env.file_data)
type = rcmail.env.file_data.type;
else {
sel = rcmail.file_list.get_selection();
type = $('#rcmrow' + sel[0]).data('type');
}
}
file_api.file_create(name, type, editaction, file);
kolab_dialog_close(dialog);
};
buttons[rcmail.gettext('kolab_files.createandedit')] = function () {
buttons[rcmail.gettext('kolab_files.' + action + 'andedit')] = function() {
create_func(this, true);
};
buttons[rcmail.gettext('kolab_files.create')] = function () {
create_func(this);
};
buttons[rcmail.gettext('kolab_files.cancel')] = function () {
if (action == 'create') {
buttons[rcmail.gettext('kolab_files.create')] = function() {
create_func(this);
};
type_select.parent('tr').show();
}
else {
input.val(file_api.file_name(file));
type_select.parent('tr').hide();
}
buttons[rcmail.gettext('kolab_files.cancel')] = function() {
kolab_dialog_close(this);
};
@ -580,37 +693,15 @@ function kolab_files_file_create_dialog()
// show dialog window
kolab_dialog_show(dialog, {
title: rcmail.gettext('kolab_files.createfile'),
title: rcmail.gettext('kolab_files.' + action + 'file'),
buttons: buttons,
button_classes: ['mainaction']
button_classes: ['mainaction'],
minHeight: 150,
height: 250
});
// build folder selector
kolab_files_folder_select_element(select);
};
// builds folder selector options
function kolab_files_folder_select_element(select, params)
{
var options = [],
selected = params && params.selected ? params.selected : file_api.env.folder;
if (params && params.empty)
options.push($('<option>').val('').text('---'));
$.each(file_api.env.folders, function(i, f) {
var n, name = escapeHTML(f.name);
for (n=0; n<f.depth; n++)
name = '&nbsp;&nbsp;&nbsp;' + name;
options.push($('<option>').val(i).html(name));
});
select.empty().append(options);
if (selected)
select.val(selected);
file_api.folder_select_element(select, {writable: true});
};
function kolab_dialog_show(content, params, onopen)
@ -826,11 +917,11 @@ function kolab_files_list_select(list)
// get file mimetype
var elem = $('tr.selected', list.list),
type = elem.data('type'),
file = elem.data('file');
folder = file_api.file_path(elem.data('file'));
rcmail.env.viewer = file_api.file_type_supported(type, rcmail.env.files_caps);
if (!file_api.is_writable(file.replace(/\/[^/]+$/, '')))
if (!file_api.is_writable(folder))
rcmail.enable_command('files-delete', 'files-rename', false);
}
else
@ -1152,21 +1243,40 @@ rcube_webmail.prototype.files_open = function()
};
// enable file editor
rcube_webmail.prototype.files_edit = function()
rcube_webmail.prototype.files_edit = function(session)
{
if (this.file_editor) {
var files, readonly, sessions, file = this.env.file,
params = {action: 'edit'};
if (!file && !this.env.action) {
files = kolab_files_selected();
if (files.length == 1)
file = files[0];
}
// check if the folder is read-only or there are ongoing sessions
// in such cases display dialog for the user to decide what to do
if (!session) {
sessions = file_api.file_sessions(file);
if (sessions.length || (readonly = !file_api.is_writable(file_api.file_path(file)))) {
kolab_files_file_edit_dialog(file, sessions, readonly);
return;
}
}
else if (session !== true)
params.session = session;
if (this.file_editor && !session) {
this.file_editor.enable();
this.enable_command('files-save', true);
}
else if (this.env.file) {
var viewer = file_api.file_type_supported(this.env.file_data.type, this.env.files_caps);
file_api.file_open(this.env.file, viewer, 'edit', true);
params.local = true;
file_api.file_open(file, viewer, params);
}
else if (!this.env.action) {
var files = kolab_files_selected();
if (files.length == 1)
file_api.file_open(files[0], this.env.viewer, 'edit');
else if (file) {
file_api.file_open(file, this.env.viewer, params);
}
};
@ -1270,7 +1380,6 @@ rcube_webmail.prototype.folder_mount = function()
function kolab_files_ui()
{
this.requests = {};
this.sessions = {};
this.uploads = [];
/*
@ -1435,7 +1544,8 @@ function kolab_files_ui()
// this.folder_list_tree(this.env.folders);
// handle authentication errors on external sources
this.folder_list_auth_errors(response.result);
if (elem.length)
this.folder_list_auth_errors(response.result);
};
this.folder_select = function(folder)
@ -2212,7 +2322,7 @@ function kolab_files_ui()
return;
if (response.result.sessions)
this.sessions[response.folder] = response.result.sessions;
this.sessions[response.result.folder] = response.result.sessions;
// update files list with document session info
$.each(file_api.env.file_list || [], function(i, file) {
@ -2432,14 +2542,21 @@ function kolab_files_ui()
}
};
// file(s) create request
this.file_create = function(file, type, edit)
// file(s) create (or clone) request
this.file_create = function(file, type, edit, cloneof)
{
this.file_create_edit_file = edit ? file : null;
this.file_create_edit_type = edit ? type : null;
this.file_create_folder = this.file_path(file);
this.req = this.set_busy(true, 'kolab_files.filecreating');
this.request('file_create', {file: file, 'content-type': type, content: ''}, 'file_create_response');
if (cloneof) {
this.req = this.set_busy(true, 'kolab_files.filecopying');
this.request('file_copy', {file: cloneof, 'new': file}, 'file_create_response');
}
else {
this.req = this.set_busy(true, 'kolab_files.filecreating');
this.request('file_create', {file: file, 'content-type': type, content: ''}, 'file_create_response');
}
};
// file(s) create response handler
@ -2449,12 +2566,13 @@ function kolab_files_ui()
return;
// @TODO: we could update metadata instead
this.file_list();
if (this.file_create_folder == this.env.folder)
this.file_list();
// open the file for editing if editable
if (this.file_create_edit_file) {
var viewer = this.file_type_supported(this.file_create_edit_type, rcmail.env.files_caps);
this.file_open(this.file_create_edit_file, viewer, 'edit');
this.file_open(this.file_create_edit_file, viewer, {action: 'edit'});
}
};
@ -2736,14 +2854,24 @@ function kolab_files_ui()
};
// open file in new window, using file API viewer
this.file_open = function(file, viewer, action, local)
this.file_open = function(file, viewer, params)
{
var href = '?' + $.param({_task: 'files', _action: action || 'open', file: file, viewer: viewer || 0});
var args = {
_task: 'files',
_action: params && params.action ? params.action : 'open',
_file: file,
_viewer: viewer || 0,
};
if (params && params.session)
args._session = params.session;
if (rcmail.env.extwin)
href += '&_extwin=1';
args._extwin = 1;
if (local)
href = '?' + $.param(args);
if (params && params.local)
location.href = href;
else
rcmail.open_window(href, false, true);

View file

@ -144,6 +144,7 @@ class kolab_files_engine
'file-search-form' => array($this, 'file_search_form'),
'file-rename-form' => array($this, 'file_rename_form'),
'file-create-form' => array($this, 'file_create_form'),
'file-edit-dialog' => array($this, 'file_edit_dialog'),
'filelist' => array($this, 'file_list'),
'filequotadisplay' => array($this, 'quota_display'),
));
@ -328,6 +329,19 @@ class kolab_files_engine
);
}
/**
* Template object for file edit dialog/warnings
*/
public function file_edit_dialog($attrib)
{
$this->plugin->add_label('select', 'create', 'cancel', 'editfiledialog', 'editfilesessions',
'newsession', 'ownedsession', 'invitedsession', 'joinsession', 'editfilero', 'editfilerotitle',
'newsessionro'
);
return '<div></div>';
}
/**
* Template object for file_rename form
*/
@ -398,7 +412,8 @@ class kolab_files_engine
$out = $this->rc->output->form_tag($attrib, $out);
}
$this->plugin->add_label('create', 'cancel', 'filecreating', 'createfile', 'createandedit');
$this->plugin->add_label('create', 'cancel', 'filecreating', 'createfile', 'createandedit',
'copyfile', 'copyandedit');
$this->rc->output->add_gui_object('file-create-form', $attrib['id']);
$this->rc->output->set_env('file_extensions', $types);
@ -893,7 +908,9 @@ class kolab_files_engine
protected function action_open()
{
$this->rc->output->set_env('files_caps', $_SESSION['kolab_files_caps']);
$this->file_opener(intval($_GET['viewer']) & ~4);
$this->rc->output->set_env('file_mimetypes', $this->get_mimetypes());
$this->file_opener(intval($_GET['_viewer']) & ~4);
}
/**
@ -901,7 +918,7 @@ class kolab_files_engine
*/
protected function action_edit()
{
$this->file_opener(intval($_GET['viewer']));
$this->file_opener(intval($_GET['_viewer']));
}
/**
@ -1191,14 +1208,16 @@ class kolab_files_engine
*/
protected function file_opener($viewer)
{
$file = rcube_utils::get_input_value('file', rcube_utils::INPUT_GET);
$file = rcube_utils::get_input_value('_file', rcube_utils::INPUT_GET);
$session = rcube_utils::get_input_value('_session', rcube_utils::INPUT_GET);
// get file info
$token = $this->get_api_token();
$request = $this->get_request(array(
'method' => 'file_info',
'file' => $file,
'viewer' => $viewer,
'method' => 'file_info',
'file' => $file,
'viewer' => $viewer,
'session' => $session,
), $token);
// send request to the API

View file

@ -46,6 +46,8 @@ $labels['printfile'] = 'Print file';
$labels['renamefile'] = 'Rename a file';
$labels['createfile'] = 'Create a file';
$labels['createandedit'] = 'Create and Edit';
$labels['copyfile'] = 'Copy a file';
$labels['copyandedit'] = 'Copy and Edit';
$labels['documenttitle'] = 'Title:';
$labels['collection_audio'] = 'Audio';
@ -89,6 +91,17 @@ $labels['fileoverwrite'] = 'Overwrite';
$labels['fileoverwriteall'] = 'Overwrite all';
$labels['filemoveconfirm'] = 'This action is going to overwrite the destination file: <b>$file</b>.';
$labels['editfiledialog'] = 'Confirm editing action';
$labels['editfilesessions'] = 'There are ongoing sessions on the selected file. Please, select the action you want to take.';
$labels['newsession'] = 'Create a new session';
$labels['newsessionro'] = 'Create a new session (copy the file into read-write location)';
$labels['ownedsession'] = 'Continue your existing session';
$labels['invitedsession'] = 'Join the session of $user';
$labels['joinsession'] = 'Request an invitation from $user';
$labels['editfilero'] = 'This file is read-only. Do you want to create and edit a copy of the file?';
$labels['editfilerotitle'] = 'Read-only file';
$labels['select'] = 'Select';
$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.';
@ -109,6 +122,7 @@ $labels['arialabelattachmentoptions'] = 'Attachment save options';
$labels['arialabelfilesavedialog'] = 'File(s) saving dialog';
$labels['arialabelfileprops'] = 'File properties';
$labels['arialabelfilecontent'] = 'File content';
$labels['arialabelfileeditdialog'] = 'File editing dialog';
$labels['type.plain'] = 'Plain Text Document';
$labels['type.vndoasisopendocumenttext'] = 'Text Document (ODF)';

View file

@ -396,6 +396,7 @@
#files-compose-dialog,
#files-file-rename-dialog,
#files-file-create-dialog,
#files-file-edit-dialog,
#files-folder-mount-dialog,
#files-folder-auth-dialog,
#files-folder-create-dialog,
@ -463,6 +464,11 @@
cursor: pointer;
}
#files-file-edit-dialog label {
display: block;
height: 20px;
}
a.filesaveall {
display: inline-block;
margin-top: .5em;

View file

@ -35,7 +35,17 @@
</div>
<div id="files-file-create-dialog" role="dialog" aria-labelledby="aria-label-filecreateform" aria-hidden="true">
<h3 id="aria-label-filecreateform" class="voice"><roundcube:label name="kolab_files.arialabelfilecreateform" /></h3>
<roundcube:object name="file-create-form" />
</div>
<div id="files-file-edit-dialog" role="dialog" aria-labelledby="aria-label-fileeditdialog" aria-hidden="true">
<h3 id="aria-label-fileeditdialog" class="voice"><roundcube:label name="kolab_files.arialabelfileeditdialog" /></h3>
<roundcube:object name="file-edit-dialog">
</div>
<roundcube:include file="/includes/footer.html" />
<script type="text/javascript">
kolab_files_ui_init();
</script>

View file

@ -105,6 +105,10 @@
<h3 id="aria-label-folderauthform" class="voice"><roundcube:label name="kolab_files.arialabelfolderauthform" /></h3>
<roundcube:object name="folder-auth-options">
</div>
<div id="files-file-edit-dialog" role="dialog" aria-labelledby="aria-label-fileeditdialog" aria-hidden="true">
<h3 id="aria-label-fileeditdialog" class="voice"><roundcube:label name="kolab_files.arialabelfileeditdialog" /></h3>
<roundcube:object name="file-edit-dialog">
</div>
<div id="listoptions" class="propform popupdialog" data-editable="true" role="dialog" aria-labelledby="aria-label-listoptions" aria-hidden="true">
<h3 id="aria-label-listoptions" class="voice"><roundcube:label name="kolab_files.arialabellistoptions" /></h3>