Page MenuHomePhorge

No OneTemporary

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 .'">&nbsp;</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="&#9881;" />
+ </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

Mime Type
text/x-diff
Expires
Tue, Jun 10, 1:47 AM (1 d, 16 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
196914
Default Alt Text
(77 KB)

Event Timeline