Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F256858
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
77 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/plugins/kolab_files/config.inc.php.dist b/plugins/kolab_files/config.inc.php.dist
index fcad5a5c..29ec6f09 100644
--- a/plugins/kolab_files/config.inc.php.dist
+++ b/plugins/kolab_files/config.inc.php.dist
@@ -1,6 +1,15 @@
<?php
// URL of kolab-chwala installation
$rcmail_config['kolab_files_url'] = 'https://localhost/kolab-chwala/public_html';
+// List of files list columns. Available are: name, size, mtime, type
+$rcmail_config['kolab_files_list_cols'] = array('name', 'mtime', 'size');
+
+// Name of the column to sort files list by
+$rcmail_config['kolab_files_sort_col'] = 'name';
+
+// Order of the files list sort
+$rcmail_config['kolab_files_sort_order'] = 'asc';
+
?>
diff --git a/plugins/kolab_files/kolab_files.js b/plugins/kolab_files/kolab_files.js
index 3198802d..677731fd 100644
--- a/plugins/kolab_files/kolab_files.js
+++ b/plugins/kolab_files/kolab_files.js
@@ -1,354 +1,692 @@
/**
* Kolab files plugin
*
* @version @package_version@
* @author Aleksander Machniak <alec@alec.pl>
*/
window.rcmail && rcmail.addEventListener('init', function() {
if (rcmail.task == 'mail') {
// mail compose
if (rcmail.env.action == 'compose') {
var elem = $('#compose-attachments > div'),
input = $('<input class="button" type="button">');
- input.val(rcmail.gettext('kolab_files.fromcloud'))
- .click(function() { kolab_files_selector_dialog(); })
- .appendTo(elem);
+ input.val(rcmail.gettext('kolab_files.fromcloud'))
+ .click(function() { kolab_files_selector_dialog(); })
+ .appendTo(elem);
+
+ if (rcmail.gui_objects.filelist) {
+ rcmail.file_list = new rcube_list_widget(rcmail.gui_objects.filelist, {
+ multiselect: true,
+// draggable: true,
+ keyboard: true,
+ column_movable: false,
+ dblclick_time: rcmail.dblclick_time
+ });
+ rcmail.file_list.addEventListener('select', function(o) { kolab_files_list_select(o); });
+ rcmail.file_list.addEventListener('listupdate', function(e) { rcmail.triggerEvent('listupdate', e); });
+
+ rcmail.gui_objects.filelist.parentNode.onmousedown = function(e){ return kolab_files_click_on_list(e); };
+ rcmail.enable_command('files-sort', 'files-search', 'files-search-reset', true);
+
+ rcmail.file_list.init();
+ kolab_files_list_coltypes();
+ }
}
// mail preview
else if (rcmail.env.action == 'show' || rcmail.env.action == 'preview') {
var attachment_list = $('#attachment-list');
if ($('li', attachment_list).length) {
- var link = $('<a href="#">')
+ var link = $('<a href="#" class="button filesaveall">')
.text(rcmail.gettext('kolab_files.saveall'))
.click(function() { kolab_directory_selector_dialog(); })
.appendTo(attachment_list);
}
}
kolab_files_init();
}
+ else if (rcmail.task == 'files') {
+ if (rcmail.gui_objects.filelist) {
+ rcmail.file_list = new rcube_list_widget(rcmail.gui_objects.filelist, {
+ multiselect: true,
+ draggable: true,
+ keyboard: true,
+ column_movable: rcmail.env.col_movable,
+ dblclick_time: rcmail.dblclick_time
+ });
+/*
+ rcmail.file_list.row_init = function(o){ kolab_files_init_file_row(o); };
+ rcmail.file_list.addEventListener('dblclick', function(o){ p.msglist_dbl_click(o); });
+ rcmail.file_list.addEventListener('click', function(o){ p.msglist_click(o); });
+ rcmail.file_list.addEventListener('keypress', function(o){ p.msglist_keypress(o); });
+*/
+ rcmail.file_list.addEventListener('select', function(o){ kolab_files_list_select(o); });
+/*
+ rcmail.file_list.addEventListener('dragstart', function(o){ p.drag_start(o); });
+ rcmail.file_list.addEventListener('dragmove', function(e){ p.drag_move(e); });
+ rcmail.file_list.addEventListener('dragend', function(e){ p.drag_end(e); });
+*/
+ rcmail.file_list.addEventListener('column_replace', function(e){ kolab_files_set_coltypes(e); });
+ rcmail.file_list.addEventListener('listupdate', function(e){ rcmail.triggerEvent('listupdate', e); });
+
+// document.onmouseup = function(e){ return p.doc_mouse_up(e); };
+ rcmail.gui_objects.filelist.parentNode.onmousedown = function(e){ return kolab_files_click_on_list(e); };
+
+ rcmail.enable_command('menu-open', 'menu-save', 'files-sort', 'files-search', 'files-search-reset', true);
+
+ rcmail.file_list.init();
+ kolab_files_list_coltypes();
+ }
+
+ kolab_files_init();
+ file_api.folder_list();
+ }
});
function kolab_files_init()
{
if (window.file_api)
return;
// Initialize application object (don't change var name!)
file_api = $.extend(new files_api(), new kolab_files_ui());
file_api.set_env({
token: kolab_files_token(),
url: rcmail.env.files_url,
sort_column: 'name',
sort_reverse: 0
});
+
+ file_api.translations = rcmail.labels;
};
+function kolab_files_token()
+{
+ // consider the token from parent window more reliable (fresher) than in framed window
+ // it's because keep-alive is not requested in frames
+ return (window.parent && parent.rcmail && parent.rcmail.env.files_token) || rcmail.env.files_token;
+};
+
+
+/**********************************************************/
+/********* Plugin functionality in other tasks **********/
+/**********************************************************/
+
function kolab_directory_selector_dialog()
{
var dialog = $('#files-dialog'), buttons = {};
buttons[rcmail.gettext('kolab_files.save')] = function () {
var lock = rcmail.set_busy(true, 'saving');
rcmail.http_post('plugin.kolab_files', {
act: 'saveall',
source: rcmail.env.mailbox,
uid: rcmail.env.uid,
dest: file_api.env.folder
}, lock);
$('#files-dialog').dialog('destroy').hide();
};
buttons[rcmail.gettext('kolab_files.cancel')] = function () {
$('#files-dialog').dialog('destroy').hide();
};
// show dialog window
dialog.dialog({
- modal: false,
+ modal: true,
resizable: !bw.ie6,
closeOnEscape: (!bw.ie6 && !bw.ie7), // disable for performance reasons
title: rcmail.gettext('kolab_files.saveall'),
// close: function() { rcmail.dialog_close(); },
buttons: buttons,
- minWidth: 400,
+ minWidth: 250,
minHeight: 300,
- height: 300,
- width: 350
+ height: 350,
+ width: 300
}).show();
- file_api.folder_selector();
+ if (!rcmail.env.folders_loaded) {
+ file_api.folder_list();
+ rcmail.env.folders_loaded = true;
+ }
};
function kolab_files_selector_dialog()
{
var dialog = $('#files-compose-dialog'), buttons = {};
buttons[rcmail.gettext('kolab_files.attachsel')] = function () {
var list = [];
$('#filelist tr.selected').each(function() {
list.push($(this).data('file'));
});
$('#files-compose-dialog').dialog('destroy').hide();
if (list.length) {
// display upload indicator and cancel button
var content = '<span>' + rcmail.get_label('uploading' + (list.length > 1 ? 'many' : '')) + '</span>',
id = new Date().getTime();
rcmail.add2attachment_list(id, { name:'', html:content, classname:'uploading', complete:false });
// send request
rcmail.http_post('plugin.kolab_files', {
act: 'attach',
folder: file_api.env.folder,
files: list,
id: rcmail.env.compose_id,
uploadid: id
});
}
};
buttons[rcmail.gettext('kolab_files.cancel')] = function () {
$('#files-compose-dialog').dialog('destroy').hide();
};
// show dialog window
dialog.dialog({
- modal: false,
+ modal: true,
resizable: !bw.ie6,
closeOnEscape: (!bw.ie6 && !bw.ie7), // disable for performance reasons
title: rcmail.gettext('kolab_files.selectfiles'),
// close: function() { rcmail.dialog_close(); },
buttons: buttons,
- minWidth: 400,
+ minWidth: 500,
minHeight: 300,
- width: 600,
- height: 400
+ width: 700,
+ height: 500
}).show();
- file_api.folder_selector();
+ if (!rcmail.env.files_loaded) {
+ file_api.folder_list();
+ rcmail.env.files_loaded = true;
+ }
+ else
+ rcmail.file_list.clear_selection();
};
-function kolab_files_token()
+
+/***********************************************************/
+/********** Main functionality **********/
+/***********************************************************/
+
+// for reordering column array (Konqueror workaround)
+// and for setting some message list global variables
+kolab_files_list_coltypes = function()
{
- // consider the token from parent window more reliable (fresher) than in framed window
- // it's because keep-alive is not requested in frames
- return (window.parent && parent.rcmail && parent.rcmail.env.files_token) || rcmail.env.files_token;
+ var n, list = rcmail.file_list;
+
+ rcmail.env.subject_col = null;
+
+ if ((n = $.inArray('name', rcmail.env.coltypes)) >= 0) {
+ rcmail.env.subject_col = n;
+ list.subject_col = n;
+ }
+
+ list.init_header();
};
+kolab_files_set_list_options = function(cols, sort_col, sort_order)
+{
+ var update = 0, i, idx, name, newcols = [], oldcols = rcmail.env.coltypes;
+
+ if (sort_col === undefined)
+ sort_col = rcmail.env.sort_col;
+ if (!sort_order)
+ sort_order = rcmail.env.sort_order;
+
+ if (rcmail.env.sort_col != sort_col || rcmail.env.sort_order != sort_order) {
+ update = 1;
+ rcmail.set_list_sorting(sort_col, sort_order);
+ }
+
+ if (cols && cols.length) {
+ // make sure new columns are added at the end of the list
+ for (i=0; i<oldcols.length; i++) {
+ name = oldcols[i];
+ idx = $.inArray(name, cols);
+ if (idx != -1) {
+ newcols.push(name);
+ delete cols[idx];
+ }
+ }
+ for (i=0; i<cols.length; i++)
+ if (cols[i])
+ newcols.push(cols[i]);
+
+ if (newcols.join() != oldcols.join()) {
+ update += 2;
+ oldcols = newcols;
+ }
+ }
+
+ if (update == 1)
+ file_api.file_list({sort: sort_col, reverse: sort_order == 'DESC'});
+ else if (update) {
+ rcmail.http_post('files/prefs', {
+ kolab_files_list_cols: oldcols,
+ kolab_files_sort_col: sort_col,
+ kolab_files_sort_order: sort_order
+ }, rcmail.set_busy(true, 'loading'));
+ }
+};
+
+kolab_files_set_coltypes = function(list)
+{
+ var i, found, name, cols = list.list.tHead.rows[0].cells;
+
+ rcmail.env.coltypes = [];
+
+ for (i=0; i<cols.length; i++)
+ if (cols[i].id && cols[i].id.match(/^rcm/)) {
+ name = cols[i].id.replace(/^rcm/, '');
+ rcmail.env.coltypes.push(name);
+ }
+
+// if ((found = $.inArray('name', rcmail.env.coltypes)) >= 0)
+// rcmail.env.subject_col = found;
+ rcmail.env.subject_col = list.subject_col;
+
+ rcmail.http_post('files/prefs', {kolab_files_list_cols: rcmail.env.coltypes});
+};
+
+kolab_files_click_on_list = function(e)
+{
+ if (rcmail.gui_objects.qsearchbox)
+ rcmail.gui_objects.qsearchbox.blur();
+
+ if (rcmail.file_list)
+ rcmail.file_list.focus();
+
+ return true;
+};
+
+kolab_files_list_select = function(list)
+{
+ var selected = list.selection.length;
+// this.enable_command(this.env.message_commands, selected != null);
+
+ // Multi-message commands
+// this.enable_command('delete', 'moveto', 'copy', list.selection.length > 0);
+
+ // reset all-pages-selection
+// if (list.selection.length && list.selection.length != list.rowcount)
+// rcmail.select_all_mode = false;
+};
+
+rcube_webmail.prototype.files_sort = function(props)
+{
+ var params = {},
+ sort_order = this.env.sort_order,
+ sort_col = !this.env.disabled_sort_col ? props : this.env.sort_col;
+
+ if (!this.env.disabled_sort_order)
+ sort_order = this.env.sort_col == sort_col && sort_order == 'ASC' ? 'DESC' : 'ASC';
+
+ // set table header and update env
+ this.set_list_sorting(sort_col, sort_order);
+
+ this.http_post('files/prefs', {kolab_files_sort_col: sort_col, kolab_files_sort_order: sort_order});
+
+ params.sort = sort_col;
+ params.reverse = sort_order == 'DESC';
+
+ file_api.file_list(params);
+};
+
+rcube_webmail.prototype.files_search = function()
+{
+ var value = $(this.gui_objects.filesearchbox).val();
+
+ if (value)
+ file_api.file_search(value);
+ else
+ file_api.file_search_reset();
+};
+
+rcube_webmail.prototype.files_search_reset = function()
+{
+ $(this.gui_objects.filesearchbox).val('');
+
+ file_api.file_search_reset();
+};
+
+rcube_webmail.prototype.files_folder_delete = function()
+{
+ if (confirm(this.get_label('deletefolderconfirm')))
+ file_api.folder_delete(file_api.env.folder);
+};
+
+rcube_webmail.prototype.files_upload = function(form)
+{
+ if (form)
+ file_api.file_upload(form);
+};
+
+rcube_webmail.prototype.files_list_update = function(head)
+{
+ var list = this.file_list;
+
+ list.clear();
+ $('thead', list.list).html(head);
+ kolab_files_list_coltypes();
+ file_api.file_list();
+};
+
+
+/**********************************************************/
+/********* Files API handler **********/
+/**********************************************************/
+
function kolab_files_ui()
{
/*
// Called on "session expired" session
this.logout = function(response) {};
// called when a request timed out
this.request_timed_out = function() {};
// called on start of the request
this.set_request_time = function() {};
// called on request response
this.update_request_time = function() {};
*/
// set state
this.set_busy = function(a, message)
{
if (this.req)
rcmail.hide_message(this.req);
return rcmail.set_busy(a, message);
};
// displays error message
this.display_message = function(label)
{
return rcmail.display_message(this.t(label));
};
this.http_error = function(request, status, err)
{
rcmail.http_error(request, status, err);
};
- this.file_list = function(params)
- {
- if (rcmail.task != 'kolab_files')
- this.file_selector(params);
- };
-
- this.folder_list = function(params)
- {
- if (rcmail.task != 'kolab_files')
- this.folder_selector(params);
- };
-
- this.folder_selector = function()
+ this.folder_list = function()
{
this.req = this.set_busy(true, 'loading');
- this.get('folder_list', {}, 'folder_selector_response');
+ this.get('folder_list', {}, 'folder_list_response');
};
// folder list response handler
- this.folder_selector_response = function(response)
+ this.folder_list_response = function(response)
{
if (!this.response(response))
return;
- var first, elem = $('#files-folder-selector'),
- table = $('<table>');
+ var first, elem = $('#files-folder-list'),
+ list = $('<ul class="listing"></ul>');
- elem.html('').append(table);
+ elem.html('').append(list);
this.env.folders = this.folder_list_parse(response.result);
- table.empty();
-
$.each(this.env.folders, function(i, f) {
- var row = $('<tr><td><span class="branch"></span><span class="name"></span></td></tr>'),
- span = $('span.name', row);
+ var row = $('<li class="mailbox"><span class="branch"></span><a></a></li>'),
+ link = $('a', row);
- span.text(f.name);
+ link.text(f.name);
row.attr('id', f.id);
if (f.depth)
$('span.branch', row).width(15 * f.depth);
if (f.virtual)
row.addClass('virtual');
else
- span.click(function() { file_api.selector_select(i); });
+ link.click(function() { file_api.folder_select(i); });
-// if (i == file_api.env.folder)
-// row.addClass('selected');
-
- table.append(row);
+ list.append(row);
if (!first)
first = i;
});
// select first folder?
-// if (first)
-// this.selector_select(first);
+ if (this.env.folder || first)
+ this.folder_select(this.env.folder ? this.env.folder : first);
// add tree icons
this.folder_list_tree(this.env.folders);
};
- this.selector_select = function(i)
+ this.folder_select = function(i)
{
- var list = $('#files-folder-selector > table');
- $('tr.selected', list).removeClass('selected');
+ var list = $('#files-folder-list > ul');
+ $('li.selected', list).removeClass('selected');
$('#' + this.env.folders[i].id, list).addClass('selected');
this.env.folder = i;
+ rcmail.enable_command('files-folder-delete', 'files-upload', true);
+
// list files in selected folder
- if (rcmail.env.action == 'compose') {
- this.file_selector();
- }
+ this.file_list();
};
- this.file_selector = function(params)
+ this.file_list = function(params)
{
- if (!this.env.folder)
+ if (!this.env.folder || !rcmail.gui_objects.filelist)
return;
if (!params)
params = {};
params.folder = this.env.folder;
if (params.sort == undefined)
params.sort = this.env.sort_col;
if (params.reverse == undefined)
params.reverse = this.env.sort_reverse;
if (params.search == undefined)
params.search = this.env.search;
this.env.sort_col = params.sort;
this.env.sort_reverse = params.reverse;
this.req = this.set_busy(true, 'loading');
- this.get('file_list', params, 'file_selector_response');
+
+ rcmail.file_list.clear();
+
+ this.get('file_list', params, 'file_list_response');
};
// file list response handler
- this.file_selector_response = function(response)
+ this.file_list_response = function(response)
{
if (!this.response(response))
return;
- var table = $('#filelist');
+ var i = 0, table = $('#filelist');
$('tbody', table).empty();
$.each(response.result, function(key, data) {
- var row = $('<tr><td class="filename"></td>'
- + /* '<td class="filemtime"></td>' */ '<td class="filesize"></td></tr>'),
- link = $('<span></span>').text(key);
+ var c, row = '', col;
+
+ i++;
+
+ for (c in rcmail.env.coltypes) {
+ c = rcmail.env.coltypes[c];
+ if (c == 'name') {
+ if (rcmail.env.task == 'files')
+ col = '<td class="name">' + key + '</td>';
+ else
+ col = '<td class="name filename ' + file_api.file_type_class(data.type) + '">'
+ + '<span>' + key + '</span></td>';
+ }
+ else if (c == 'mtime')
+ col = '<td class="mtime">' + data.mtime + '</td>';
+ else if (c == 'size')
+ col = '<td class="size">' + file_api.file_size(data.size) + '</td>';
+ else if (c == 'options')
+ col = '<td class="filename ' + file_api.file_type_class(data.type) + '">'
+ + '<span class="drop"><a href="#" onclick="kolab_files_file_menu(' + i + ')"></a></span></td>';
+ else
+ col = '<td class="' + c + '"></td>';
+
+ row += col;
+ }
- $('td.filename', row).addClass(file_api.file_type_class(data.type)).append(link);
-// $('td.filemtime', row).text(data.mtime);
- $('td.filesize', row).text(file_api.file_size(data.size));
- row.attr('data-file', urlencode(key))
- .click(function(e) { file_api.file_select(e, this); });
+ row = $('<tr>')
+ .html(row)
+ .attr({id: 'rcmrow' + i, 'data-file': urlencode(key)});
- table.append(row);
+// table.append(row);
+ rcmail.file_list.insert_row(row.get([0]));
});
};
this.file_select = function(e, row)
{
var table = $('#filelist');
// $('tr.selected', table).removeClass('selected');
$(row).addClass('selected');
};
// folder create request
this.folder_create = function(folder)
{
- this.req = this.set_busy(true, 'creating');
+ this.req = this.set_busy(true, 'kolab_files.foldercreating');
this.get('folder_create', {folder: folder}, 'folder_create_response');
};
// folder create response handler
this.folder_create_response = function(response)
{
if (!this.response(response))
return;
// refresh folders list
- if (rcmail.task == 'kolab_files')
- this.folder_list();
- else
- this.folder_selector();
+ this.folder_list();
};
- this.search = function()
+ // folder delete request
+ this.folder_delete = function(folder)
{
- var value = $(rcmail.gui_objects.filesearchbox).val();
+ this.req = this.set_busy(true, 'folderdeleting');
+ this.get('folder_delete', {folder: folder}, 'folder_delete_response');
+ };
+
+ // folder delete response handler
+ this.folder_delete_response = function(response)
+ {
+ if (!this.response(response))
+ return;
+
+ this.env.folder = null;
+ rcmail.enable_command('files-folder-delete', 'files-folder-rename', false);
+
+ // refresh folders list
+ this.folder_list();
+ };
+ this.file_search = function(value)
+ {
if (value) {
this.env.search = {name: value};
this.file_list({search: this.env.search});
}
else
this.search_reset();
};
- this.search_reset = function()
+ this.file_search_reset = function()
{
- $(rcmail.gui_objects.filesearchbox).val('');
-
if (this.env.search) {
this.env.search = null;
this.file_list();
}
};
+
+ // file upload request
+ this.file_upload = function(form)
+ {
+ var form = $(form),
+ field = $('input[type=file]', form).get(0),
+ files = field.files ? field.files.length : field.value ? 1 : 0;
+
+ if (files) {
+ // submit form and read server response
+ this.async_upload_form(form, 'file_create', function(event) {
+ var doc, response;
+ try {
+ doc = this.contentDocument ? this.contentDocument : this.contentWindow.document;
+ response = doc.body.innerHTML;
+ // response may be wrapped in <pre> tag
+ if (response.slice(0, 5).toLowerCase() == '<pre>' && response.slice(-6).toLowerCase() == '</pre>') {
+ response = doc.body.firstChild.firstChild.nodeValue;
+ }
+ response = eval('(' + response + ')');
+ } catch (err) {
+ response = {status: 'ERROR'};
+ }
+
+ rcmail.hide_message(event.data.ts);
+
+ // refresh the list on upload success
+ if (file_api.response_parse(response))
+ file_api.file_list();
+ });
+ }
+ };
+
+ // post the given form to a hidden iframe
+ this.async_upload_form = function(form, action, onload)
+ {
+ var ts = rcmail.display_message(rcmail.get_label('kolab_files.uploading'), 'loading', 1000),
+ frame_name = 'fileupload'+ts;
+/*
+ // upload progress support
+ if (this.env.upload_progress_name) {
+ var fname = this.env.upload_progress_name,
+ field = $('input[name='+fname+']', form);
+
+ if (!field.length) {
+ field = $('<input>').attr({type: 'hidden', name: fname});
+ field.prependTo(form);
+ }
+ field.val(ts);
+ }
+*/
+ // have to do it this way for IE
+ // otherwise the form will be posted to a new window
+ if (document.all) {
+ var html = '<iframe id="'+frame_name+'" name="'+frame_name+'"'
+ + ' src="program/resources/blank.gif" style="width:0;height:0;visibility:hidden;"></iframe>';
+ document.body.insertAdjacentHTML('BeforeEnd', html);
+ }
+ // for standards-compliant browsers
+ else
+ $('<iframe>')
+ .attr({name: frame_name, id: frame_name})
+ .css({border: 'none', width: 0, height: 0, visibility: 'hidden'})
+ .appendTo(document.body);
+
+ // handle upload errors, parsing iframe content in onload
+ $('#'+frame_name).on('load', {ts:ts}, onload);
+
+ $(form).attr({
+ target: frame_name,
+ action: this.env.url + this.url(action, {folder: this.env.folder, token: this.env.token, uploadid:ts}),
+ method: 'POST'
+ }).attr(form.encoding ? 'encoding' : 'enctype', 'multipart/form-data')
+ .submit();
+ };
+
};
diff --git a/plugins/kolab_files/kolab_files.php b/plugins/kolab_files/kolab_files.php
index 43045132..cd82da99 100644
--- a/plugins/kolab_files/kolab_files.php
+++ b/plugins/kolab_files/kolab_files.php
@@ -1,110 +1,113 @@
<?php
/**
* Kolab files storage
*
* @version @package_version@
* @author Aleksander Machniak <machniak@kolabsys.com>
*
* Copyright (C) 2013, 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
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class kolab_files extends rcube_plugin
{
+ // all task excluding 'login' and 'logout'
+ public $task = '?(?!login|logout).*';
+
public $rc;
public $home;
private $engine;
public function init()
{
$this->rc = rcmail::get_instance();
// Register hooks
$this->add_hook('keep_alive', array($this, 'keep_alive'));
- // Plugin actions
+
+ // Plugin actions for other tasks
$this->register_action('plugin.kolab_files', array($this, 'actions'));
- $ui_actions = array(
- 'mail/compose',
- 'mail/preview',
- 'mail/show',
- );
+ // Register task
+ $this->register_task('files');
- if (in_array($this->rc->task . '/' . $this->rc->action, $ui_actions)) {
- $this->ui();
- }
+ // Register plugin task actions
+ $this->register_action('index', array($this, 'actions'));
+ $this->register_action('prefs', array($this, 'actions'));
+
+ $this->ui();
}
/**
* Creates kolab_files_engine instance
*/
private function engine()
{
if ($this->engine === null) {
$this->load_config();
$url = $this->rc->config->get('kolab_files_url');
if (!$url) {
return $this->engine = false;
}
require_once $this->home . DIRECTORY_SEPARATOR . 'lib' . DIRECTORY_SEPARATOR . 'kolab_files_engine.php';
$this->engine = new kolab_files_engine($this, $url);
}
return $this->engine;
}
/**
* Adds elements of files API user interface
*/
private function ui()
{
if ($this->rc->output->type != 'html') {
return;
}
if ($engine = $this->engine()) {
$engine->ui();
}
}
/**
* Keep_alive hook handler
*/
public function keep_alive($args)
{
// Here we are refreshing API session, so when we need it
// the session will be active
if ($engine = $this->engine()) {
$this->rc->output->set_env('files_token', $engine->get_api_token());
}
return $args;
}
/**
* Engine actions handler
*/
public function actions()
{
if ($engine = $this->engine()) {
$engine->actions();
}
}
}
diff --git a/plugins/kolab_files/lib/kolab_files_engine.php b/plugins/kolab_files/lib/kolab_files_engine.php
index addd9d91..53a2a4f8 100644
--- a/plugins/kolab_files/lib/kolab_files_engine.php
+++ b/plugins/kolab_files/lib/kolab_files_engine.php
@@ -1,491 +1,735 @@
<?php
/**
* Kolab files storage engine
*
* @version @package_version@
* @author Aleksander Machniak <machniak@kolabsys.com>
*
* Copyright (C) 2013, 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
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class kolab_files_engine
{
private $plugin;
private $rc;
private $timeout = 60;
+ private $sort_cols = array('name', 'mtime', 'size');
/**
* Class constructor
*/
public function __construct($plugin, $url)
{
$this->url = $url;
$this->plugin = $plugin;
$this->rc = $plugin->rc;
}
/**
* User interface initialization
*/
public function ui()
{
$this->plugin->add_texts('localization/', true);
- $this->rc->output->set_env('files_url', $this->url . '/api/');
- $this->rc->output->set_env('files_token', $this->get_api_token());
- $this->plugin->include_stylesheet($this->plugin->local_skin_path().'/style.css');
- $this->plugin->include_stylesheet($this->url . '/skins/default/images/mimetypes/style.css');
- $this->plugin->include_script($this->url . '/js/files_api.js');
- $this->plugin->include_script('kolab_files.js');
-
- // add dialogs
- if ($this->rc->task = 'mail') {
+ // set templates of Files UI and widgets
+ if ($this->rc->task == 'mail') {
if ($this->rc->action == 'compose') {
$template = 'compose_plugin';
}
else if ($this->rc->action == 'show' || $this->rc->action == 'preview') {
$template = 'message_plugin';
}
}
+ else if ($this->rc->task == 'files') {
+ $template = 'files';
+ }
+
+ // add taskbar button
+ if (empty($_REQUEST['framed'])) {
+ $this->plugin->add_button(array(
+ 'command' => 'files',
+ 'class' => 'button-files',
+ 'classsel' => 'button-files button-selected',
+ 'innerclass' => 'button-inner',
+ 'label' => 'kolab_files.files',
+ ), 'taskbar');
+ }
+
+ $this->plugin->include_stylesheet($this->plugin->local_skin_path().'/style.css');
if (!empty($template)) {
- // register template objects
- $this->rc->output->add_handlers(array(
- 'folder-create-form' => array($this, 'folder_create_form'),
- 'file-search-form' => array($this, 'file_search_form'),
- ));
- // add dialog content at the end of page body
- $this->rc->output->add_footer(
- $this->rc->output->parse('kolab_files.' . $template, false, false));
+ $this->plugin->include_stylesheet($this->url . '/skins/default/images/mimetypes/style.css');
+ $this->plugin->include_script($this->url . '/js/files_api.js');
+ $this->plugin->include_script('kolab_files.js');
+ $this->rc->output->set_env('files_url', $this->url . '/api/');
+ $this->rc->output->set_env('files_token', $this->get_api_token());
+
+ if ($this->rc->task != 'files') {
+ // register template objects for dialogs
+ $this->rc->output->add_handlers(array(
+ 'folder-create-form' => array($this, 'folder_create_form'),
+ 'file-search-form' => array($this, 'file_search_form'),
+ 'filelist' => array($this, 'file_list'),
+ ));
+
+ // add dialog content at the end of page body
+ $this->rc->output->add_footer(
+ $this->rc->output->parse('kolab_files.' . $template, false, false));
+ }
}
}
/**
* Engine actions handler
*/
public function actions()
{
- $action = $_POST['act'];
- $method = 'action_' . $action;
+ if ($this->rc->task == 'files' && $this->rc->action) {
+ $action = $this->rc->action;
+ }
+ else if ($this->rc->task != 'files' && $_POST['act']) {
+ $action = $_POST['act'];
+ }
+ else {
+ $action = 'index';
+ }
+
+ $method = 'action_' . str_replace('-', '_', $action);
if (method_exists($this, $method)) {
$this->plugin->add_texts('localization/');
$this->{$method}();
}
}
/**
* Template object for folder creation form in "Save as" dialog
*/
public function folder_create_form($attrib)
{
$attrib['name'] = 'folder-create-form';
if (empty($attrib['id'])) {
$attrib['id'] = 'folder-create-form';
}
$input_name = new html_inputfield(array('name' => 'folder_name'));
$out = $input_name->show();
// $input_parent = new html_checkbox(array('name' => 'folder_parent', 'checked' => true, 'value' => 1));
// $out .= html::label(null, $input_parent->show() . $this->plugin->gettext('assubfolder'));
// add form tag around text field
if (empty($attrib['form'])) {
$out = $this->rc->output->form_tag($attrib, $out);
}
+ $this->rc->output->add_label('kolab_files.foldercreating');
+
$this->rc->output->add_gui_object('folder-create-form', $attrib['id']);
return $out;
}
/**
* Template object for file search form in "From cloud" dialog
*/
public function file_search_form($attrib)
{
$attrib['name'] = '_q';
if (empty($attrib['id'])) {
$attrib['id'] = 'filesearchbox';
}
if ($attrib['type'] == 'search' && !$this->rc->output->browser->khtml) {
unset($attrib['type'], $attrib['results']);
}
$input_q = new html_inputfield($attrib);
$out = $input_q->show();
// add some labels to client
$this->rc->output->add_label('searching');
$this->rc->output->add_gui_object('filesearchbox', $attrib['id']);
// add form tag around text field
if (empty($attrib['form'])) {
$out = $this->rc->output->form_tag(array(
- 'name' => "filesearchform",
- 'onsubmit' => "file_api.search(); return false",
- 'style' => "display:inline"),
- $out);
+ 'name' => "filesearchform",
+ 'onsubmit' => rcmail_output::JS_OBJECT_NAME . ".command('files-search'); return false",
+ ), $out);
}
return $out;
}
+ /**
+ * Template object for files list
+ */
+ public function file_list($attrib)
+ {
+// $this->rc->output->add_label('');
+
+ // define list of cols to be displayed based on parameter or config
+ if (empty($attrib['columns'])) {
+ $list_cols = $this->rc->config->get('kolab_files_list_cols');
+ $dont_override = $this->rc->config->get('dont_override');
+ $a_show_cols = is_array($list_cols) ? $list_cols : array('name');
+ $this->rc->output->set_env('col_movable', !in_array('kolab_files_list_cols', (array)$dont_override));
+ }
+ else {
+ $a_show_cols = preg_split('/[\s,;]+/', strip_quotes($attrib['columns']));
+ }
+
+
+ // make sure 'name' and 'options' column is present
+ if (!in_array('name', $a_show_cols)) {
+ array_unshift($a_show_cols, 'name');
+ }
+ if (!in_array('options', $a_show_cols)) {
+ array_unshift($a_show_cols, 'options');
+ }
+
+ $attrib['columns'] = $a_show_cols;
+
+ // save some variables for use in ajax list
+ $_SESSION['kolab_files_list_attrib'] = $attrib;
+
+ // For list in dialog(s) remove all option-like columns
+ if ($this->rc->task != 'files') {
+ $a_show_cols = array_intersect($a_show_cols, $this->sort_cols);
+ }
+
+ // set default sort col/order to session
+ if (!isset($_SESSION['kolab_files_sort_col']))
+ $_SESSION['kolab_files_sort_col'] = $this->rc->config->get('kolab_files_sort_col') ?: 'name';
+ if (!isset($_SESSION['kolab_files_sort_order']))
+ $_SESSION['kolab_files_sort_order'] = strtoupper($this->rc->config->get('kolab_files_sort_order') ?: 'asc');
+
+ // set client env
+ $this->rc->output->add_gui_object('filelist', $attrib['id']);
+ $this->rc->output->set_env('sort_col', $_SESSION['kolab_files_sort_col']);
+ $this->rc->output->set_env('sort_order', $_SESSION['kolab_files_sort_order']);
+ $this->rc->output->set_env('coltypes', $a_show_cols);
+
+ $this->rc->output->include_script('list.js');
+
+ $thead = '';
+ foreach ($this->file_list_head($attrib, $a_show_cols) as $cell) {
+ $thead .= html::tag('td', array('class' => $cell['className'], 'id' => $cell['id']), $cell['html']);
+ }
+
+ return html::tag('table', $attrib,
+ html::tag('thead', null, html::tag('tr', null, $thead)) . html::tag('tbody', null, ''),
+ array('style', 'class', 'id', 'cellpadding', 'cellspacing', 'border', 'summary'));
+ }
+
+ /**
+ * Creates <THEAD> for message list table
+ */
+ protected function file_list_head($attrib, $a_show_cols)
+ {
+ $skin_path = $_SESSION['skin_path'];
+ $image_tag = html::img(array('src' => "%s%s", 'alt' => "%s"));
+
+ // check to see if we have some settings for sorting
+ $sort_col = $_SESSION['kolab_files_sort_col'];
+ $sort_order = $_SESSION['kolab_files_sort_order'];
+
+ $dont_override = (array)$this->rc->config->get('dont_override');
+ $disabled_sort = in_array('message_sort_col', $dont_override);
+ $disabled_order = in_array('message_sort_order', $dont_override);
+
+ $this->rc->output->set_env('disabled_sort_col', $disabled_sort);
+ $this->rc->output->set_env('disabled_sort_order', $disabled_order);
+
+ // define sortable columns
+ if ($disabled_sort)
+ $a_sort_cols = $sort_col && !$disabled_order ? array($sort_col) : array();
+ else
+ $a_sort_cols = $this->sort_cols;
+
+ if (!empty($attrib['optionsmenuicon'])) {
+ $onclick = 'return ' . JS_OBJECT_NAME . ".command('menu-open', 'filelistmenu')";
+ if ($attrib['optionsmenuicon'] === true || $attrib['optionsmenuicon'] == 'true')
+ $list_menu = html::div(array('onclick' => $onclick, 'class' => 'listmenu',
+ 'id' => 'listmenulink', 'title' => $this->rc->gettext('listoptions')));
+ else
+ $list_menu = html::a(array('href' => '#', 'onclick' => $onclick),
+ html::img(array('src' => $skin_path . $attrib['optionsmenuicon'],
+ 'id' => 'listmenulink', 'title' => $this->rc->gettext('listoptions'))));
+ }
+ else {
+ $list_menu = '';
+ }
+
+ $cells = array();
+
+ foreach ($a_show_cols as $col) {
+ // get column name
+ switch ($col) {
+/*
+ case 'status':
+ $col_name = '<span class="' . $col .'"> </span>';
+ break;
+*/
+ case 'options':
+ $col_name = $list_menu;
+ break;
+ default:
+ $col_name = Q($this->plugin->gettext($col));
+ }
+
+ // make sort links
+ if (in_array($col, $a_sort_cols))
+ $col_name = html::a(array('href'=>"#sort", 'onclick' => 'return '.JS_OBJECT_NAME.".command('files-sort','".$col."',this)", 'title' => rcube_label('sortby')), $col_name);
+ else if ($col_name[0] != '<')
+ $col_name = '<span class="' . $col .'">' . $col_name . '</span>';
+
+ $sort_class = $col == $sort_col && !$disabled_order ? " sorted$sort_order" : '';
+ $class_name = $col.$sort_class;
+
+ // put it all together
+ $cells[] = array('className' => $class_name, 'id' => "rcm$col", 'html' => $col_name);
+ }
+
+ return $cells;
+ }
+
+ /**
+ * Update files list object
+ */
+ protected function file_list_update($prefs)
+ {
+ $attrib = $_SESSION['kolab_files_list_attrib'];
+
+ if (!empty($prefs['kolab_files_list_cols'])) {
+ $attrib['columns'] = $prefs['kolab_files_list_cols'];
+ $_SESSION['kolab_files_list_attrib'] = $attrib;
+ }
+
+ $a_show_cols = $attrib['columns'];
+ $head = '';
+
+ foreach ($this->file_list_head($attrib, $a_show_cols) as $cell) {
+ $head .= html::tag('td', array('class' => $cell['className'], 'id' => $cell['id']), $cell['html']);
+ }
+
+ $head = html::tag('tr', null, $head);
+
+ $this->rc->output->set_env('coltypes', $a_show_cols);
+ $this->rc->output->command('files_list_update', $head);
+ }
/**
* Get API token for current user session, authenticate if needed
*/
protected function get_api_token()
{
$token = $_SESSION['kolab_files_token'];
$time = $_SESSION['kolab_files_time'];
if ($token && time() - $this->timeout < $time) {
return $token;
}
if (!($request = $this->get_request())) {
return $token;
}
try {
$url = $request->getUrl();
// Send ping request
if ($token) {
$url->setQueryVariables(array('method' => 'ping'));
$request->setUrl($url);
$response = $request->send();
$status = $response->getStatus();
if ($status == 200 && ($body = json_decode($response->getBody(), true))) {
if ($body['status'] == 'OK') {
$_SESSION['kolab_files_time'] = time();
return $token;
}
}
}
// Go with authenticate request
$url->setQueryVariables(array('method' => 'authenticate'));
$request->setUrl($url);
$request->setAuth($this->rc->user->get_username(), $this->rc->decrypt($_SESSION['password']));
$response = $request->send();
$status = $response->getStatus();
if ($status == 200 && ($body = json_decode($response->getBody(), true))) {
$token = $body['result']['token'];
if ($token) {
$_SESSION['kolab_files_token'] = $token;
$_SESSION['kolab_files_time'] = time();
}
}
else {
throw new Exception(sprintf("Authenticate error (Status: %d)", $status));
}
}
catch (Exception $e) {
rcube::raise_error($e, true, false);
}
return $token;
}
/**
* Initialize HTTP_Request object
*/
protected function get_request()
{
$url = $this->url . '/api/';
if (!$this->request) {
require_once 'HTTP/Request2.php';
try {
$request = new HTTP_Request2();
$request->setConfig(array(
'store_body' => true,
'follow_redirects' => true,
'ssl_verify_peer' => $this->rc->config->get('kolab_ssl_verify_peer', true),
));
$this->request = $request;
}
catch (Exception $e) {
rcube::raise_error($e, true, true);
}
}
if ($this->request) {
// cleanup
try {
$this->request->setBody('');
$this->request->setUrl($url);
$this->request->setMethod(HTTP_Request2::METHOD_GET);
}
catch (Exception $e) {
rcube::raise_error($e, true, true);
}
}
return $this->request;
}
+ protected function action_index()
+ {
+ // register template objects
+ $this->rc->output->add_handlers(array(
+// 'folderlist' => array($this, 'folder_list'),
+ 'filelist' => array($this, 'file_list'),
+ 'file-search-form' => array($this, 'file_search_form'),
+ ));
+
+
+ $this->rc->output->add_label('deletefolderconfirm', 'folderdeleting',
+ 'kolab_files.foldercreating', 'kolab_files.uploading');
+
+ $this->rc->output->set_pagetitle($this->plugin->gettext('files'));
+ $this->rc->output->send('kolab_files.files');
+ }
+
+ /**
+ * Handler for preferences save action
+ */
+ protected function action_prefs()
+ {
+ $dont_override = (array)$this->rc->config->get('dont_override');
+ $prefs = array();
+ $opts = array(
+ 'kolab_files_sort_col' => true,
+ 'kolab_files_sort_order' => true,
+ 'kolab_files_list_cols' => false,
+ );
+
+ foreach ($opts as $o => $sess) {
+ if (isset($_POST[$o]) && !in_array($o, $dont_override)) {
+ $prefs[$o] = rcube_utils::get_input_value($o, rcube_utils::INPUT_POST);
+ if ($sess) {
+ $_SESSION[$o] = $prefs[$o];
+ }
+
+ if ($o == 'kolab_files_list_cols') {
+ $update_list = true;
+ }
+ }
+ }
+
+ // save preference values
+ if (!empty($prefs)) {
+ $this->rc->user->save_prefs($prefs);
+ }
+
+ if (!empty($update_list)) {
+ $this->file_list_update($prefs);
+ }
+
+ $this->rc->output->send();
+ }
+
/**
* Handler for "save all attachments into cloud" action
*/
protected function action_saveall()
{
$source = rcube_utils::get_input_value('source', rcube_utils::INPUT_POST);
$uid = rcube_utils::get_input_value('uid', rcube_utils::INPUT_POST);
$dest = rcube_utils::get_input_value('dest', rcube_utils::INPUT_POST);
$temp_dir = unslashify($this->rc->config->get('temp_dir'));
$storage = $this->rc->get_storage();
$message = new rcube_message($uid);
$request = $this->get_request();
$url = $request->getUrl();
$files = array();
$errors = array();
$request->setMethod(HTTP_Request2::METHOD_POST);
$request->setHeader('X-Session-Token', $this->get_api_token());
$url->setQueryVariables(array('method' => 'file_create', 'folder' => $dest));
$request->setUrl($url);
// @TODO: handle error
// @TODO: implement file upload using file URI instead of body upload
foreach ($message->attachments as $attach_prop) {
$filename = rcmail_attachment_name($attach_prop, true);
$path = tempnam($temp_dir, 'rcmAttmnt');
// save attachment to file
if ($fp = fopen($path, 'w+')) {
$message->get_part_content($attach_prop->mime_id, $fp, true);
}
else {
$errors[] = true;
rcube::raise_error(array(
'code' => 500, 'type' => 'php', 'line' => __LINE__, 'file' => __FILE__,
'message' => "Unable to save attachment into file $path"),
true, false);
continue;
}
fclose($fp);
// send request to the API
try {
$request->setBody('');
$request->addUpload('file[]', $path, $filename, $attach_prop->mimetype);
$response = $request->send();
$status = $response->getStatus();
$body = @json_decode($response->getBody(), true);
if ($status == 200 && $body['status'] == 'OK') {
$files[] = $filename;
}
else {
throw new Exception($body['reason']);
}
}
catch (Exception $e) {
unlink($path);
$errors[] = $e->getMessage();
rcube::raise_error(array(
'code' => 500, 'type' => 'php', 'line' => __LINE__, 'file' => __FILE__,
'message' => $e->getMessage()),
true, false);
continue;
}
// clean up
unlink($path);
$request->setBody('');
}
if ($count = count($files)) {
- $this->rc->output->show_message($this->plugin->gettext('saveallnotice', array('n' => $count)), 'confirmation');
+ $msg = $this->plugin->gettext(array('name' => 'saveallnotice', 'vars' => array('n' => $count)));
+ $this->rc->output->show_message($msg, 'confirmation');
}
if ($count = count($errors)) {
- $this->rc->output->show_message($this->plugin->gettext('saveallerror', array('n' => $count)), 'error');
+ $msg = $this->plugin->gettext(array('name' => 'saveallerror', 'vars' => array('n' => $count)));
+ $this->rc->output->show_message($msg, 'error');
}
// @TODO: update quota indicator, make this optional in case files aren't stored in IMAP
$this->rc->output->send();
}
/**
* Handler for "add attachments from the cloud" action
*/
protected function action_attach()
{
$folder = rcube_utils::get_input_value('folder', rcube_utils::INPUT_POST);
$files = rcube_utils::get_input_value('files', rcube_utils::INPUT_POST);
$uploadid = rcube_utils::get_input_value('uploadid', rcube_utils::INPUT_POST);
$COMPOSE_ID = rcube_utils::get_input_value('id', rcube_utils::INPUT_POST);
$COMPOSE = null;
$errors = array();
if ($COMPOSE_ID && $_SESSION['compose_data_'.$COMPOSE_ID]) {
$COMPOSE =& $_SESSION['compose_data_'.$COMPOSE_ID];
}
if (!$COMPOSE) {
die("Invalid session var!");
}
// attachment upload action
if (!is_array($COMPOSE['attachments'])) {
$COMPOSE['attachments'] = array();
}
// clear all stored output properties (like scripts and env vars)
$this->rc->output->reset();
$temp_dir = unslashify($this->rc->config->get('temp_dir'));
$request = $this->get_request();
$url = $request->getUrl();
// Use observer object to store HTTP response into a file
require_once $this->plugin->home . DIRECTORY_SEPARATOR . 'lib' . DIRECTORY_SEPARATOR . 'kolab_files_observer.php';
$observer = new kolab_files_observer();
$request->setHeader('X-Session-Token', $this->get_api_token());
// download files from the API and attach them
foreach ($files as $file) {
// decode filename
$file = urldecode($file);
// get file information
try {
$url->setQueryVariables(array('method' => 'file_info', 'folder' => $folder, 'file' => $file));
$request->setUrl($url);
$response = $request->send();
$status = $response->getStatus();
$body = @json_decode($response->getBody(), true);
if ($status == 200 && $body['status'] == 'OK') {
$file_params = $body['result'];
}
else {
throw new Exception($body['reason']);
}
}
catch (Exception $e) {
$errors[] = $e->getMessage();
rcube::raise_error(array(
'code' => 500, 'type' => 'php', 'line' => __LINE__, 'file' => __FILE__,
'message' => $e->getMessage()),
true, false);
continue;
}
// set location of downloaded file
$path = tempnam($temp_dir, 'rcmAttmnt');
$observer->set_file($path);
// download file
try {
$url->setQueryVariables(array('method' => 'file_get', 'folder' => $folder, 'file' => $file));
$request->setUrl($url);
$request->attach($observer);
$response = $request->send();
$status = $response->getStatus();
$response->getBody(); // returns nothing
$request->detach($observer);
if ($status != 200 || !file_exists($path)) {
throw new Exception("Unable to save file");
}
}
catch (Exception $e) {
$errors[] = $e->getMessage();
rcube::raise_error(array(
'code' => 500, 'type' => 'php', 'line' => __LINE__, 'file' => __FILE__,
'message' => $e->getMessage()),
true, false);
continue;
}
$attachment = array(
'path' => $path,
'size' => $file_params['size'],
'name' => $file_params['name'],
'mimetype' => $file_params['type'],
'group' => $COMPOSE_ID,
);
$attachment = $this->rc->plugins->exec_hook('attachment_save', $attachment);
if ($attachment['status'] && !$attachment['abort']) {
$id = $attachment['id'];
// store new attachment in session
unset($attachment['status'], $attachment['abort']);
$COMPOSE['attachments'][$id] = $attachment;
if (($icon = $COMPOSE['deleteicon']) && is_file($icon)) {
$button = html::img(array(
'src' => $icon,
'alt' => $this->rc->gettext('delete')
));
}
else {
$button = Q($this->rc->gettext('delete'));
}
$content = html::a(array(
'href' => "#delete",
'onclick' => sprintf("return %s.command('remove-attachment','rcmfile%s', this)", JS_OBJECT_NAME, $id),
'title' => $this->rc->gettext('delete'),
'class' => 'delete',
), $button);
$content .= Q($attachment['name']);
$this->rc->output->command('add2attachment_list', "rcmfile$id", array(
'html' => $content,
'name' => $attachment['name'],
'mimetype' => $attachment['mimetype'],
'classname' => rcmail_filetype2classname($attachment['mimetype'], $attachment['name']),
'complete' => true), $uploadid);
}
else if ($attachment['error']) {
$errors[] = $attachment['error'];
}
else {
- $errors[] = $this->rc->gettext('fileuploaderror');
+ $errors[] = $this->plugin->gettext('attacherror');
}
}
if (!empty($errors)) {
- $this->rc->output->command('display_message', "Failed to attach file(s) from cloud", 'error');
+ $this->rc->output->command('display_message', $this->plugin->gettext('attacherror'), 'error');
$this->rc->output->command('remove_from_attachment_list', $uploadid);
}
// send html page with JS calls as response
$this->rc->output->command('auto_save_start', false);
$this->rc->output->send();
}
}
diff --git a/plugins/kolab_files/localization/en_US.inc b/plugins/kolab_files/localization/en_US.inc
index 18c3d23d..20e4284c 100644
--- a/plugins/kolab_files/localization/en_US.inc
+++ b/plugins/kolab_files/localization/en_US.inc
@@ -1,10 +1,31 @@
<?php
+$labels['files'] = 'Files';
$labels['saveall'] = 'Save all to cloud...';
$labels['save'] = 'Save';
$labels['cancel'] = 'Cancel';
$labels['fromcloud'] = 'From cloud...';
$labels['selectfiles'] = 'Select file(s) to attach...';
$labels['attachsel'] = 'Attach selected';
+$labels['foldercreate'] = 'Create folder';
+$labels['folderrename'] = 'Rename folder';
+$labels['folderdelete'] = 'Delete folder';
+
+$labels['name'] = 'Name';
+$labels['mtime'] = 'Modified';
+
+$labels['upload'] = 'Upload';
+$labels['uploadfile'] = 'Upload file(s)';
+$labels['get'] = 'Download';
+$labels['getfile'] = 'Download file';
+$labels['view'] = 'View';
+$labels['viewfile'] = 'View file';
+$labels['deletefile'] = 'Delete selected file(s)';
+
+$labels['uploading'] = 'Uploading file(s)...';
+$labels['foldercreating'] = 'Creating folder...';
+$labels['saveallnotice'] = 'Successfully saved $n file(s).';
+$labels['saveallerror'] = 'Saving $n file(s) failed.';
+$labels['attacherror'] = 'Failed to attach file(s) from the cloud';
?>
diff --git a/plugins/kolab_files/skins/larry/images/buttons.png b/plugins/kolab_files/skins/larry/images/buttons.png
new file mode 100644
index 00000000..58c0a293
Binary files /dev/null and b/plugins/kolab_files/skins/larry/images/buttons.png differ
diff --git a/plugins/kolab_files/skins/larry/style.css b/plugins/kolab_files/skins/larry/style.css
index 463c220a..f037d63a 100644
--- a/plugins/kolab_files/skins/larry/style.css
+++ b/plugins/kolab_files/skins/larry/style.css
@@ -1,161 +1,266 @@
-#files-dialog,
-#files-compose-dialog,
-#files-folder-create {
- display: none;
+/* Taskbar button */
+#taskbar a.button-files span.button-inner
+{
+ background: url(images/buttons.png) 0 0 no-repeat;
+ height: 22px;
}
-#files-folder-selector {
+#taskbar a.button-files:hover span.button-inner,
+#taskbar a.button-files.button-selected span.button-inner
+{
+ background: url(images/buttons.png) 0 -26px no-repeat;
+ height: 22px;
+}
+
+
+/* Files main interface */
+#filestoolbar {
position: absolute;
- top: 5px;
- bottom: 5px;
+ height: 40px;
left: 0;
- right: 0;
- background-color: #f0f0f0;
- padding: 2px;
+ top: -6px;
+ z-index: 10;
}
-#files-compose-dialog #files-folder-selector {
- right: auto;
- width: 190px;
- top: 45px;
+#filestoolbar a.button {
+ background-image: url(images/buttons.png);
+}
+
+#filestoolbar a.button.upload {
+ background-position: center -52px;
+}
+
+#filestoolbar a.button.get {
+ background-position: center -94px;
+}
+
+#filestoolbar a.button.view {
+ background-position: center -131px;
}
-#files-dialog #files-folder-selector {
- bottom: 40px;
+#filestoolbar a.button.delete {
+ background-image: url(../../../../skins/larry/images/buttons.png);
}
-#files-file-selector {
+#filestoolbar form {
+ display: inline;
+}
+
+#folderlistbox {
position: absolute;
- top: 45px;
- bottom: 5px;
- left: 200px;
- right: 0;
- background-color: #f0f0f0;
- padding: 2px;
- overflow: auto;
+ top: 42px;
+ left: 0;
+ width: 220px;
+ bottom: 0;
}
-#files-dialog #files-folder-footer {
+#filelistcontainer {
position: absolute;
- height: 30px;
+ top: 42px;
+ left: 232px;
+ right: 0;
bottom: 0;
+ overflow: auto;
}
-#files-dialog #files-folder-footer form {
- display: inline;
+#files-folder-list ul li a {
+ background: url(../../../../skins/larry/images/listicons.png) 6px 3px no-repeat;
+ padding-left: 32px;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ display: inline-block;
+ width: auto;
+ overflow: hidden;
}
-#files-folder-selector table {
- width: 100%;
- border-spacing: 0;
+#files-folder-list ul li span.branch {
+ display: inline-block;
}
-#files-folder-selector table td span.name {
- background: url(images/folder.png) 0 0 no-repeat;
- height: 18px;
- padding-left: 20px;
- margin-left: 3px;
- cursor: pointer;
+#files-folder-list ul li:first-child {
+ border-radius: 4px 4px 0 0;
+ border-top: 0;
}
-#files-folder-selector table tr.selected td span.name {
- font-weight: bold;
+#filelist thead tr td {
+ padding: 0;
}
-#files-folder-selector table tr.virtual td span.name {
- color: #bbb;
- cursor: default;
+#filelist tbody tr td {
+ padding: 2px 7px;
+ height: 18px;
}
-#files-compose-dialog #searchmenulink {
- width: 15px;
+#filelist tr td.size {
+ width: 60px;
+ text-align: right;
}
-#files-compose-dialog #quicksearchbar {
- top: 10px;
- right: 5px;
+#filelist thead tr td.size {
+ text-align: left;
}
-#files-compose-dialog #searchreset {
- cursor: pointer;
+#filelist tr td.mtime {
+ width: 125px;
}
-#filelist table {
- width: 100%;
- border-spacing: 0;
+#filelist tr td.options {
+ width: 26px;
}
-#filelist td {
+#filelist thead tr td.subject,
+#filelist tbody tr td.subject {
+ width: 99%;
white-space: nowrap;
- cursor: default;
}
-#filelist td.filename {
- width: 98%;
- height: 20px;
- padding: 0 4px;
+#filelist thead tr td.sortedASC a,
+#filelist thead tr td.sortedDESC a {
+ color: #004458;
+ text-decoration: underline;
+ background: url(../../../../skins/larry/images/listicons.png) right -912px no-repeat;
}
-#filelist td.filesize {
- text-align: right;
+#filelist thead tr td.sortedASC a {
+ background-position: right -944px;
+}
+
+#filelist td img {
+ vertical-align: middle;
+ display: inline-block;
+}
+
+#filelist tr td.options div.listmenu,
+#filelist tr td.flag span.flagged,
+#filelist tr td.flag span.unflagged,
+#filelist tr td.flag span.unflagged:hover {
+ display: inline-block;
+ vertical-align: middle;
+ height: 18px;
+ width: 20px;
+ padding: 0;
+ background: url(../../../../skins/larry/images/listicons.png) -100px 0 no-repeat;
+}
+
+#filelist thead tr td.options div.listmenu {
+ background-position: 0 -976px;
+ cursor: pointer;
+ width: 26px;
+}
+
+#filelist thead tr td.options {
+ padding: 0 3px;
+}
+
+#filelist td.filename {
+ padding: 0 4px;
}
#filelist tbody td.filename span {
background: url(images/unknown.png) 0 0 no-repeat;
- padding: 0 0 0 20px;
+ padding: 0 0 0 16px;
height: 16px;
}
+#filelist tbody td.filename span.drop a {
+ display: inline-block;
+ width: 12px;
+ height: 12px;
+ background: url(../../../../skins/larry/images/buttons.png) -20px -1575px no-repeat;
+}
+
+/*
#filelist tbody td.filename span input {
padding: 0 2px;
height: 18px;
}
-/*
-#filelist thead td {
- cursor: pointer;
+*/
+
+
+/* plugin dialogs */
+
+#files-dialog,
+#files-compose-dialog {
+ display: none;
}
-#filelist thead td.sorted {
- padding-right: 16px;
- text-decoration: underline;
- background: url(images/buttons.png) right -120px no-repeat;
+#files-compose-dialog #folderlistbox {
+ right: auto;
+ width: 190px;
+ top: 45px;
+ bottom: 5px;
+ box-shadow: none;
}
-#filelist thead td.sorted.reverse {
- background-position: right -140px;
+#files-dialog #folderlistbox {
+ bottom: 5px;
+ top: 5px;
+ left: 0;
+ right: 0;
+ width: auto;
+ box-shadow: none;
+}
+
+#files-compose-dialog #filelistcontainer {
+ position: absolute;
+ top: 45px;
+ bottom: 5px;
+ left: 200px;
+ right: 0;
+ box-shadow: none;
}
-*/
-#filelist tbody tr.selected td {
- background-color: #d9ecf4;
+#files-folder-create {
+ background-color: white;
+ padding: 10px;
+ bottom: 10px;
+ left: 5px;
+ top: auto;
+ z-index: 1001; /* for use in modal dialog window */
}
-/***** tree indicators *****/
+#folder-create-form {
+ margin-bottom: 10px;
+}
-td span.branch span
-{
- float: left;
- height: 16px;
+/*
+#files-folder-list table {
+ width: 100%;
+ border-spacing: 0;
}
-td span.branch span.tree
-{
- height: 17px;
+#files-folder-list table td span.name {
+ background: url(images/folder.png) 0 0 no-repeat;
+ height: 18px;
+ padding-left: 20px;
+ margin-left: 3px;
+ cursor: pointer;
+}
+
+#files-folder-list table tr.selected td span.name {
+ font-weight: bold;
+}
+
+#files-folder-list table tr.virtual td span.name {
+ color: #bbb;
+ cursor: default;
+}
+*/
+#files-compose-dialog #searchmenulink {
width: 15px;
- background: url(images/tree.gif) 0 0 no-repeat;
}
-td span.branch span.l1
-{
- background-position: 0px 0px; /* L */
+#files-compose-dialog #quicksearchbar {
+ top: 10px;
+ right: 5px;
}
-td span.branch span.l2
-{
- background-position: -30px 0px; /* | */
+#files-compose-dialog #searchreset {
+ cursor: pointer;
}
-td span.branch span.l3
-{
- background-position: -15px 0px; /* |- */
+a.filesaveall {
+ display: inline-block;
+ margin-top: .5em;
+ padding: 3px 5px 4px 5px;
}
diff --git a/plugins/kolab_files/skins/larry/templates/compose_plugin.html b/plugins/kolab_files/skins/larry/templates/compose_plugin.html
index a94418f9..1e4df79c 100644
--- a/plugins/kolab_files/skins/larry/templates/compose_plugin.html
+++ b/plugins/kolab_files/skins/larry/templates/compose_plugin.html
@@ -1,14 +1,18 @@
<div id="files-compose-dialog" class="uidialog">
<div id="quicksearchbar" class="searchbox">
<roundcube:object name="file-search-form" id="filesearchbox" />
<!--
<roundcube:button name="searchmenulink" id="searchmenulink" class="iconbutton searchoptions" onclick="UI.show_popup('searchmenu');return false" title="searchmod" content=" " />
-->
<a id="searchmenulink" class="iconbutton searchoptions"> </a>
- <a id="searchreset" class="iconbutton reset" title="<roundcube:label name="resetsearch"/>" onclick="file_api.search_reset()"> </a>
+ <roundcube:button command="files-search-reset" id="searchreset" class="iconbutton reset" title="resetsearch" content=" " />
</div>
- <div id="files-folder-selector"></div>
- <div id="files-file-selector">
- <table id="filelist"><tbody></tbody></table>
+
+ <div id="folderlistbox" class="uibox listbox">
+ <div id="files-folder-list" class="scroller"></div>
+ </div>
+
+ <div id="filelistcontainer" class="boxlistcontent uibox">
+ <roundcube:object name="filelist" id="filelist" class="records-table sortheader" />
</div>
</div>
diff --git a/plugins/kolab_files/skins/larry/templates/files.html b/plugins/kolab_files/skins/larry/templates/files.html
new file mode 100644
index 00000000..c91f64bb
--- /dev/null
+++ b/plugins/kolab_files/skins/larry/templates/files.html
@@ -0,0 +1,103 @@
+<roundcube:object name="doctype" value="html5" />
+<html>
+<head>
+<title><roundcube:object name="pagetitle" /></title>
+<roundcube:include file="/includes/links.html" />
+<!--
+<link rel="stylesheet" type="text/css" href="/settings.css" />
+-->
+<script src="plugins/kolab_files/skins/larry/ui.js" type="text/javascript"></script>
+</head>
+<body class="files noscroll">
+
+<roundcube:include file="/includes/header.html" />
+
+<div id="mainscreen">
+
+<div id="filestoolbar" class="toolbar">
+ <form id="filesuploadform">
+ <roundcube:button command="files-upload" type="link" class="button upload disabled" classAct="button upload" classSel="button upload pressed" label="kolab_files.upload" title="kolab_files.uploadfile" />
+ </form>
+ <roundcube:button command="files-get" type="link" class="button get disabled" classAct="button get" classSel="button get pressed" label="kolab_files.get" title="kolab_files.getfile" />
+ <roundcube:button command="files-view" type="link" class="button view disabled" classAct="button view" classSel="button delete pressed" label="kolab_files.view" title="kolab_files.viewfile" />
+ <roundcube:button command="files-delete" type="link" class="button delete disabled" classAct="button delete" classSel="button delete pressed" label="delete" title="kolab_files.deletefile" />
+</div>
+
+<div id="quicksearchbar" class="quicksearchbox">
+ <roundcube:object name="file-search-form" id="quicksearchbox" />
+<!--
+ <roundcube:button name="searchmenulink" id="searchmenulink" class="iconbutton searchoptions" onclick="UI.show_popup('searchmenu');return false" title="searchmod" content=" " />
+-->
+ <a id="searchmenulink" class="iconbutton searchoptions"> </a>
+ <roundcube:button command="files-search-reset" id="searchreset" class="iconbutton reset" title="resetsearch" content=" " />
+</div>
+
+<div id="folderlistbox" class="uibox listbox">
+ <div id="files-folder-list" class="scroller withfooter">
+ </div>
+ <div id="folderlist-footer" class="boxfooter">
+ <roundcube:button name="folder-create" type="link" title="kolab_files.foldercreate" class="listbutton add" classAct="listbutton add" innerClass="inner" content="+" onclick="kolab_files_folder_create()" /><roundcube:button name="folderoptions" id="folderoptionslink" type="link" title="moreactions" class="listbutton groupactions" onclick="UI.show_popup('folderoptions', undefined, {above: 1});return false" innerClass="inner" content="⚙" />
+ </div>
+</div>
+
+<div id="filelistcontainer" class="boxlistcontent uibox">
+ <roundcube:object name="filelist" id="filelist" class="records-table sortheader" optionsmenuIcon="true" />
+ <roundcube:object name="message" id="message" class="statusbar" />
+</div>
+
+</div>
+
+<div id="folderoptions" class="popupmenu">
+ <ul id="folderoptionsmenu" class="toolbarmenu">
+ <li><roundcube:button command="files-folder-edit" label="edit" classAct="active" /></li>
+ <li><roundcube:button command="files-folder-delete" label="delete" classAct="active" /></li>
+ <li><roundcube:button command="folders" task="settings" type="link" label="managefolders" classAct="active" /></li>
+ <roundcube:container name="filesfolderoptions" id="folderoptionsmenu" />
+ </ul>
+</div>
+
+<div id="listoptions" class="propform popupdialog">
+<roundcube:if condition="!in_array('kolab_files_list_cols', (array)config:dont_override)" />
+ <fieldset class="floating">
+ <legend><roundcube:label name="listcolumns" /></legend>
+ <ul class="proplist">
+ <li><label class="disabled"><input type="checkbox" name="list_col[]" value="options" checked="checked" disabled="disabled" /> <span><roundcube:label name="options" /></span></label></li>
+ <li><label class="disabled"><input type="checkbox" name="list_col[]" value="name" checked="checked" disabled="disabled" /> <span><roundcube:label name="kolab_files.name" /></span></label></li>
+ <li><label><input type="checkbox" name="list_col[]" value="mtime" /> <span><roundcube:label name="kolab_files.mtime" /></span></label></li>
+ <li><label><input type="checkbox" name="list_col[]" value="size" /> <span><roundcube:label name="size" /></span></label></li>
+ </ul>
+ </fieldset>
+ <roundcube:endif />
+ <roundcube:if condition="!in_array('kolab_files_sort_col', (array)config:dont_override)" />
+ <fieldset class="floating">
+ <legend><roundcube:label name="listsorting" /></legend>
+ <ul class="proplist">
+ <li><label><input type="radio" name="sort_col" value="name" /> <span><roundcube:label name="kolab_files.name" /></span></label></li>
+ <li><label><input type="radio" name="sort_col" value="mtime" /> <span><roundcube:label name="kolab_files.mtime" /></span></label></li>
+ <li><label><input type="radio" name="sort_col" value="size" /> <span><roundcube:label name="size" /></span></label></li>
+ </ul>
+ </fieldset>
+ <roundcube:endif />
+ <roundcube:if condition="!in_array('kolab_files_sort_order', (array)config:dont_override)" />
+ <fieldset class="floating">
+ <legend><roundcube:label name="listorder" /></legend>
+ <ul class="proplist">
+ <li><label><input type="radio" name="sort_ord" value="ASC" /> <span><roundcube:label name="asc" /></span></label></li>
+ <li><label><input type="radio" name="sort_ord" value="DESC" /> <span><roundcube:label name="desc" /></span></label></li>
+ </ul>
+ </fieldset>
+ <roundcube:endif />
+ <br style="clear:both" />
+ <div class="formbuttons">
+ <roundcube:button command="menu-save" id="listmenusave" type="input" class="button mainaction" label="save" />
+ <roundcube:button command="menu-open" id="listmenucancel" type="input" class="button" label="cancel" />
+ </div>
+</div>
+
+<roundcube:include file="/includes/footer.html" />
+<script type="text/javascript">
+kolab_files_ui_init();
+</script>
+
+</body>
+</html>
diff --git a/plugins/kolab_files/skins/larry/templates/message_plugin.html b/plugins/kolab_files/skins/larry/templates/message_plugin.html
index 36f8e6ff..50cf7cf6 100644
--- a/plugins/kolab_files/skins/larry/templates/message_plugin.html
+++ b/plugins/kolab_files/skins/larry/templates/message_plugin.html
@@ -1,12 +1,14 @@
<div id="files-dialog" class="uidialog">
- <div id="files-folder-selector"></div>
- <div id="files-folder-footer">
- <input id="folder-create-start-button" onclick="kolab_files_folder_form()" type="button" value="<roundcube:label name="kolab_files.foldercreate" />">
- <div id="files-folder-create">
- <roundcube:object name="folder-create-form" />
- <input id="folder-create-save-button" onclick="kolab_directory_create()" type="button" class="button mainaction" value="<roundcube:label name="create" />">
- <input id="folder-create-cancel-button" onclick="kolab_directory_cancel()" type="button" class="button" value="<roundcube:label name="cancel" />">
+ <div id="folderlistbox" class="uibox listbox">
+ <div id="files-folder-list" class="scroller withfooter"></div>
+ <div id="folderlist-footer" class="boxfooter">
+ <roundcube:button name="foldercreatelink" id="foldercreatelink" type="link" onclick="kolab_files_folder_form()" title="createfolder" class="listbutton add" classAct="listbutton add" innerClass="inner" content="+" />
</div>
</div>
+ <div id="files-folder-create" class="popupmenu">
+ <roundcube:object name="folder-create-form" />
+ <input id="folder-create-save-button" onclick="kolab_directory_create()" type="button" class="button mainaction" value="<roundcube:label name="create" />">
+ <input id="folder-create-cancel-button" onclick="kolab_directory_cancel()" type="button" class="button" value="<roundcube:label name="cancel" />">
+ </div>
</div>
<script src="plugins/kolab_files/skins/larry/ui.js" type="text/javascript"></script>
diff --git a/plugins/kolab_files/skins/larry/ui.js b/plugins/kolab_files/skins/larry/ui.js
index 75d0d922..75199ce1 100644
--- a/plugins/kolab_files/skins/larry/ui.js
+++ b/plugins/kolab_files/skins/larry/ui.js
@@ -1,41 +1,125 @@
+function kolab_files_ui_init()
+{
+ var filesviewsplit = new rcube_splitter({ id:'filesviewsplitter', p1:'#folderlistbox', p2:'#filelistcontainer',
+ orientation:'v', relative:true, start:226, min:150, size:12 }).init();
+
+ $(document).ready(function() {
+ rcmail.addEventListener('menu-open', kolab_files_show_listoptions);
+ rcmail.addEventListener('menu-save', kolab_files_save_listoptions);
+ });
+
+ kolab_files_upload_input('#filestoolbar a.upload');
+};
+
function kolab_files_folder_form(link)
{
- var form = $('#files-folder-create'),
- link = $('#folder-create-start-button');
+ var form = $('#files-folder-create');
- link.hide();
form.show();
- $('input[name="folder_name"]', form).val('').focus();
-}
+ $('form > input[name="folder_name"]', form).val('').focus();
+};
function kolab_directory_create()
{
var folder = '',
- form = $('#files-folder-create'),
+ form = $('#folder-create-form'),
name = $('input[name="folder_name"]', form).val(),
parent = file_api.env.folder;
// parent = $('input[name="folder_parent"]', form).is(':checked');
if (!name)
return;
if (parent && file_api.env.folder)
folder = file_api.env.folder + file_api.env.directory_separator;
folder += name;
kolab_directory_cancel();
file_api.folder_create(folder);
// todo: select created folder
-}
+};
function kolab_directory_cancel()
{
- var form = $('#files-folder-create'),
- link = $('#folder-create-start-button');
+ var form = $('#files-folder-create');
- link.show();
form.hide();
-}
+};
+
+function kolab_files_show_listoptions()
+{
+ var $dialog = $('#listoptions');
+
+ // close the dialog
+ if ($dialog.is(':visible')) {
+ $dialog.dialog('close');
+ return;
+ }
+
+ // set form values
+ $('input[name="sort_col"][value="'+rcmail.env.sort_col+'"]').prop('checked', true);
+ $('input[name="sort_ord"][value="DESC"]').prop('checked', rcmail.env.sort_order == 'DESC');
+ $('input[name="sort_ord"][value="ASC"]').prop('checked', rcmail.env.sort_order != 'DESC');
+
+ // set checkboxes
+ $('input[name="list_col[]"]').each(function() {
+ $(this).prop('checked', $.inArray(this.value, rcmail.env.coltypes) != -1);
+ });
+
+ $dialog.dialog({
+ modal: true,
+ resizable: false,
+ closeOnEscape: true,
+ title: null,
+ close: function() {
+ $dialog.dialog('destroy').hide();
+ },
+ width: 650
+ }).show();
+};
+
+function kolab_files_save_listoptions()
+{
+ $('#listoptions').dialog('close');
+
+ var sort = $('input[name="sort_col"]:checked').val(),
+ ord = $('input[name="sort_ord"]:checked').val(),
+ cols = $('input[name="list_col[]"]:checked')
+ .map(function(){ return this.value; }).get();
+
+ kolab_files_set_list_options(cols, sort, ord);
+};
+
+
+function kolab_files_upload_input(button)
+{
+ var link = $(button),
+ file = $('<input>'),
+ offset = link.offset();
+
+ file.attr({name: 'file[]', type: 'file', multiple: 'multiple', size: 5})
+ .change(function() { rcmail.files_upload('#filesuploadform'); })
+ // opacity:0 does the trick, display/visibility doesn't work
+ .css({opacity: 0, cursor: 'pointer', position: 'absolute'});
+
+ // In FF we need to move the browser file-input's button under the cursor
+ // Thanks to the size attribute above we know the length of the input field
+ if (bw.mz)
+ file.css({marginLeft: '-75px'});
+
+ // Note: now, I observe problem with cursor style on FF < 4 only
+ link.css({overflow: 'hidden', cursor: 'pointer'})
+ // place button under the cursor
+ .mousemove(function(e) {
+ if (rcmail.commands['files-upload'])
+ file.css({top: (e.pageY - offset.top - 10) + 'px', left: (e.pageX - offset.left - 10) + 'px'});
+ // move the input away if button is disabled
+ else
+ file.css({top: '1000px', left: '1000px'});
+ })
+ .attr('onclick', '') // remove default button action
+ .append(file);
+};
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Tue, Jun 10, 1:47 AM (1 d, 10 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
196914
Default Alt Text
(77 KB)
Attached To
Mode
R14 roundcubemail-plugins-kolab
Attached
Detach File
Event Timeline
Log In to Comment