plugins/kolab_files

Aleksander Machniak machniak at kolabsys.com
Thu Feb 28 16:02:24 CET 2013


 plugins/kolab_files/config.inc.php.dist                       |    9 
 plugins/kolab_files/kolab_files.js                            |  494 ++++++++--
 plugins/kolab_files/kolab_files.php                           |   21 
 plugins/kolab_files/lib/kolab_files_engine.php                |  298 +++++-
 plugins/kolab_files/localization/en_US.inc                    |   21 
 plugins/kolab_files/skins/larry/images/buttons.png            |binary
 plugins/kolab_files/skins/larry/style.css                     |  287 +++--
 plugins/kolab_files/skins/larry/templates/compose_plugin.html |   12 
 plugins/kolab_files/skins/larry/templates/files.html          |  103 ++
 plugins/kolab_files/skins/larry/templates/message_plugin.html |   16 
 plugins/kolab_files/skins/larry/ui.js                         |  106 +-
 11 files changed, 1140 insertions(+), 227 deletions(-)

New commits:
commit 5a3c6f9ecd421f07dafb02e976d699d0c7bb2bf5
Author: Aleksander Machniak <machniak at kolabsys.com>
Date:   Thu Feb 28 16:01:56 2013 +0100

    Added main Files task interface and a lot of css improvements

diff --git a/plugins/kolab_files/config.inc.php.dist b/plugins/kolab_files/config.inc.php.dist
index fcad5a5..29ec6f0 100644
--- a/plugins/kolab_files/config.inc.php.dist
+++ b/plugins/kolab_files/config.inc.php.dist
@@ -3,4 +3,13 @@
 // 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 3198802..677731f 100644
--- a/plugins/kolab_files/kolab_files.js
+++ b/plugins/kolab_files/kolab_files.js
@@ -12,16 +12,34 @@ window.rcmail && rcmail.addEventListener('init', function() {
       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);
@@ -30,6 +48,42 @@ window.rcmail && rcmail.addEventListener('init', function() {
 
     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()
@@ -46,8 +100,22 @@ function kolab_files_init()
     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 = {};
@@ -68,19 +136,22 @@ function kolab_directory_selector_dialog()
 
   // 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()
@@ -118,28 +189,199 @@ function kolab_files_selector_dialog()
 
   // 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()
 {
 /*
@@ -175,44 +417,30 @@ function kolab_files_ui()
     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)
@@ -221,42 +449,39 @@ function kolab_files_ui()
       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)
@@ -275,31 +500,55 @@ function kolab_files_ui()
     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]));
     });
   };
 
@@ -313,7 +562,7 @@ function kolab_files_ui()
   // 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');
   };
 
@@ -324,16 +573,31 @@ function kolab_files_ui()
       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});
@@ -342,13 +606,87 @@ function kolab_files_ui()
       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 4304513..cd82da9 100644
--- a/plugins/kolab_files/kolab_files.php
+++ b/plugins/kolab_files/kolab_files.php
@@ -24,6 +24,9 @@
 
 class kolab_files extends rcube_plugin
 {
+    // all task excluding 'login' and 'logout'
+    public $task = '?(?!login|logout).*';
+
     public $rc;
     public $home;
     private $engine;
@@ -34,18 +37,18 @@ class kolab_files extends rcube_plugin
 
         // 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();
     }
 
     /**
diff --git a/plugins/kolab_files/lib/kolab_files_engine.php b/plugins/kolab_files/lib/kolab_files_engine.php
index addd9d9..53a2a4f 100644
--- a/plugins/kolab_files/lib/kolab_files_engine.php
+++ b/plugins/kolab_files/lib/kolab_files_engine.php
@@ -27,6 +27,7 @@ class kolab_files_engine
     private $plugin;
     private $rc;
     private $timeout = 60;
+    private $sort_cols = array('name', 'mtime', 'size');
 
     /**
      * Class constructor
@@ -44,16 +45,9 @@ class kolab_files_engine
     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';
             }
@@ -61,16 +55,42 @@ class kolab_files_engine
                 $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));
+            }
         }
     }
 
@@ -79,8 +99,17 @@ class kolab_files_engine
      */
     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/');
@@ -109,6 +138,8 @@ class kolab_files_engine
             $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;
@@ -138,15 +169,171 @@ class kolab_files_engine
         // 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
@@ -248,6 +435,61 @@ class kolab_files_engine
         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
      */
@@ -323,10 +565,12 @@ class kolab_files_engine
         }
 
         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
@@ -475,12 +719,12 @@ class kolab_files_engine
                 $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);
         }
 
diff --git a/plugins/kolab_files/localization/en_US.inc b/plugins/kolab_files/localization/en_US.inc
index 18c3d23..20e4284 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 0000000..58c0a29
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 463c220..f037d63 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 a94418f..1e4df79 100644
--- a/plugins/kolab_files/skins/larry/templates/compose_plugin.html
+++ b/plugins/kolab_files/skins/larry/templates/compose_plugin.html
@@ -5,10 +5,14 @@
         <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 0000000..c91f64b
--- /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 36f8e6f..50cf7cf 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 75d0d92..75199ce 100644
--- a/plugins/kolab_files/skins/larry/ui.js
+++ b/plugins/kolab_files/skins/larry/ui.js
@@ -1,18 +1,29 @@
+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');
@@ -29,13 +40,86 @@ function kolab_directory_create()
   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);
+};





More information about the commits mailing list