Implement new folder navigation for notes module (#3056)

This commit is contained in:
Thomas Bruederli 2014-06-23 10:37:56 +02:00
parent e26c4836bc
commit 7a85b2590e
8 changed files with 517 additions and 135 deletions

View file

@ -106,6 +106,9 @@ class kolab_notes extends rcube_plugin
if (!$this->rc->output->ajax_call && (!$this->rc->output->env['framed'] || in_array($args['action'], array('folder-acl','dialog-ui')))) {
$this->load_ui();
}
// notes use fully encoded identifiers
kolab_storage::$encode_ids = true;
}
/**
@ -155,63 +158,9 @@ class kolab_notes extends rcube_plugin
}
$delim = $this->rc->get_storage()->get_hierarchy_delimiter();
$listnames = array();
// include virtual folders for a full folder tree
if (!$this->rc->output->ajax_call && in_array($this->rc->action, array('index','')))
$folders = kolab_storage::folder_hierarchy($folders);
foreach ($folders as $folder) {
$utf7name = $folder->name;
$path_imap = explode($delim, $utf7name);
$editname = rcube_charset::convert(array_pop($path_imap), 'UTF7-IMAP'); // pop off raw name part
$path_imap = join($delim, $path_imap);
$fullname = $folder->get_name();
$listname = kolab_storage::folder_displayname($fullname, $listnames);
// special handling for virtual folders
if ($folder->virtual) {
$list_id = kolab_storage::folder_id($utf7name);
$this->lists[$list_id] = array(
'id' => $list_id,
'name' => $fullname,
'listname' => $listname,
'virtual' => true,
'editable' => false,
);
continue;
}
if ($folder->get_namespace() == 'personal') {
$norename = false;
$readonly = false;
$alarms = true;
}
else {
$alarms = false;
$readonly = true;
if (($rights = $folder->get_myrights()) && !PEAR::isError($rights)) {
if (strpos($rights, 'i') !== false)
$readonly = false;
}
$info = $folder->get_folder_info();
$norename = $readonly || $info['norename'] || $info['protected'];
}
$list_id = kolab_storage::folder_id($utf7name);
$item = array(
'id' => $list_id,
'name' => $fullname,
'listname' => $listname,
'editname' => $editname,
'editable' => !$readonly,
'norename' => $norename,
'parentfolder' => $path_imap,
'default' => $folder->default,
'class_name' => trim($folder->get_namespace() . ($folder->default ? ' default' : '')),
);
$item = $this->folder_props($folder, $delim);
$this->lists[$item['id']] = $item;
$this->folders[$item['id']] = $folder;
$this->folders[$folder->name] = $folder;
@ -221,7 +170,7 @@ class kolab_notes extends rcube_plugin
/**
* Get a list of available folders from this source
*/
public function get_lists()
public function get_lists(&$tree = null)
{
$this->_read_lists();
@ -233,9 +182,192 @@ class kolab_notes extends rcube_plugin
}
}
return $this->lists;
$folders = array();
foreach ($this->lists as $id => $list) {
if (!empty($this->folders[$id])) {
$folders[] = $this->folders[$id];
}
}
// include virtual folders for a full folder tree
if (!is_null($tree)) {
$folders = kolab_storage::folder_hierarchy($folders, $tree);
}
$delim = $this->rc->get_storage()->get_hierarchy_delimiter();
$lists = array();
foreach ($folders as $folder) {
$list_id = $folder->id;
$imap_path = explode($delim, $folder->name);
// find parent
do {
array_pop($imap_path);
$parent_id = kolab_storage::folder_id(join($delim, $imap_path));
}
while (count($imap_path) > 1 && !$this->folders[$parent_id]);
// restore "real" parent ID
if ($parent_id && !$this->folders[$parent_id]) {
$parent_id = kolab_storage::folder_id($folder->get_parent());
}
$fullname = $folder->get_name();
$listname = $folder->get_foldername();
// special handling for virtual folders
if ($folder instanceof kolab_storage_folder_user) {
$lists[$list_id] = array(
'id' => $list_id,
'name' => $fullname,
'listname' => $listname,
'title' => $folder->get_owner(),
'virtual' => true,
'editable' => false,
'group' => 'other virtual',
'class' => 'user',
'parent' => $parent_id,
);
}
else if ($folder->virtual) {
$lists[$list_id] = array(
'id' => $list_id,
'name' => kolab_storage::object_name($fullname),
'listname' => $listname,
'virtual' => true,
'editable' => false,
'group' => $folder->get_namespace(),
'parent' => $parent_id,
);
}
else {
if (!$this->lists[$list_id]) {
$this->lists[$list_id] = $this->folder_props($folder, $delim);
$this->folders[$list_id] = $folder;
}
$this->lists[$list_id]['parent'] = $parent_id;
$lists[$list_id] = $this->lists[$list_id];
}
}
return $lists;
}
/**
* Search for shared or otherwise not listed folders the user has access
*
* @param string Search string
* @param string Section/source to search
* @return array List of notes folders
*/
protected function search_lists($query, $source)
{
if (!kolab_storage::setup()) {
return array();
}
$this->search_more_results = false;
$this->lists = $this->folders = array();
$delim = $this->rc->get_storage()->get_hierarchy_delimiter();
// find unsubscribed IMAP folders that have "event" type
if ($source == 'folders') {
foreach ((array)kolab_storage::search_folders('note', $query, array('other')) as $folder) {
$this->folders[$folder->id] = $folder;
$this->lists[$folder->id] = $this->folder_props($folder, $delim);
}
}
// search other user's namespace via LDAP
else if ($source == 'users') {
$limit = $this->rc->config->get('autocomplete_max', 15) * 2; // we have slightly more space, so display twice the number
foreach (kolab_storage::search_users($query, 0, array(), $limit * 10) as $user) {
$folders = array();
// search for tasks folders shared by this user
foreach (kolab_storage::list_user_folders($user, 'note', false) as $foldername) {
$folders[] = new kolab_storage_folder($foldername, 'note');
}
if (count($folders)) {
$userfolder = new kolab_storage_folder_user($user['kolabtargetfolder'], '', $user);
$this->folders[$userfolder->id] = $userfolder;
$this->lists[$userfolder->id] = $this->folder_props($userfolder, $delim, array());
foreach ($folders as $folder) {
$this->folders[$folder->id] = $folder;
$this->lists[$folder->id] = $this->folder_props($folder, $delim, array());
$count++;
}
}
if ($count >= $limit) {
$this->search_more_results = true;
break;
}
}
}
return $this->get_lists();
}
/**
* Derive list properties from the given kolab_storage_folder object
*/
protected function folder_props($folder, $delim)
{
if ($folder->get_namespace() == 'personal') {
$norename = false;
$readonly = false;
$alarms = true;
}
else {
$alarms = false;
$readonly = true;
if (($rights = $folder->get_myrights()) && !PEAR::isError($rights)) {
if (strpos($rights, 'i') !== false)
$readonly = false;
}
$info = $folder->get_folder_info();
$norename = $readonly || $info['norename'] || $info['protected'];
}
$list_id = $folder->id;
return array(
'id' => $list_id,
'name' => $folder->get_name(),
'listname' => $folder->get_foldername(),
'editname' => $folder->get_foldername(),
'editable' => !$readonly,
'norename' => $norename,
'parentfolder' => $folder->get_parent(),
'subscribed' => (bool)$folder->is_subscribed(),
'default' => $folder->default,
'group' => $folder->default ? 'default' : $folder->get_namespace(),
'class' => trim($folder->get_namespace() . ($folder->default ? ' default' : '')),
);
}
/**
* Get the kolab_calendar instance for the given calendar ID
*
* @param string List identifier (encoded imap folder name)
* @return object kolab_storage_folder Object nor null if list doesn't exist
*/
public function get_folder($id)
{
// create list and folder instance if necesary
if (!$this->lists[$id]) {
$folder = kolab_storage::get_folder(kolab_storage::id_decode($id));
if ($folder->type) {
$this->folders[$id] = $folder;
$this->lists[$id] = $this->folder_props($folder, $this->rc->get_storage()->get_hierarchy_delimiter());
}
}
return $this->folders[$id];
}
/******* UI functions ********/
@ -325,7 +457,7 @@ class kolab_notes extends rcube_plugin
}
$this->_read_lists();
if ($folder = $this->folders[$list_id]) {
if ($folder = $this->get_folder($list_id)) {
foreach ($folder->select($query) as $record) {
// post-filter search results
if (strlen($search)) {
@ -393,7 +525,7 @@ class kolab_notes extends rcube_plugin
$this->_read_lists();
if ($list_id) {
if ($folder = $this->folders[$list_id]) {
if ($folder = $this->get_folder($list_id)) {
return $folder->get_object($uid);
}
}
@ -514,11 +646,11 @@ class kolab_notes extends rcube_plugin
$this->_read_lists();
$list_id = $note['list'];
if (!$list_id || !($folder = $this->folders[$list_id]))
if (!$list_id || !($folder = $this->get_folder($list_id)))
return false;
// moved from another folder
if ($note['_fromlist'] && ($fromfolder = $this->folders[$note['_fromlist']])) {
if ($note['_fromlist'] && ($fromfolder = $this->get_folder($note['_fromlist']))) {
if (!$fromfolder->move($note['uid'], $folder->name))
return false;
@ -566,8 +698,8 @@ class kolab_notes extends rcube_plugin
function move_note($note, $list_id)
{
$this->_read_lists();
$tofolder = $this->folders[$list_id];
$fromfolder = $this->folders[$note['list']];
$tofolder = $this->get_folder($list_id);
$fromfolder = $this->get_folder($note['list']);
if ($fromfolder && $tofolder) {
return $fromfolder->move($note['uid'], $tofolder->name);
@ -588,7 +720,7 @@ class kolab_notes extends rcube_plugin
$this->_read_lists();
$list_id = $note['list'];
if (!$list_id || !($folder = $this->folders[$list_id]))
if (!$list_id || !($folder = $this->get_folder($list_id)))
return false;
return $folder->delete($note['uid'], $force);
@ -603,6 +735,10 @@ class kolab_notes extends rcube_plugin
$list = rcube_utils::get_input_value('_list', RCUBE_INPUT_GPC, true);
$success = $update_cmd = false;
if (empty($action)) {
$action = rcube_utils::get_input_value('action', RCUBE_INPUT_GPC);
}
switch ($action) {
case 'form-new':
case 'form-edit':
@ -651,7 +787,7 @@ class kolab_notes extends rcube_plugin
case 'delete':
$this->_read_lists();
$folder = $this->folders[$list['id']];
$folder = $this->get_folder($list['id']);
if ($folder && kolab_storage::folder_delete($folder->name)) {
$success = true;
$update_cmd = 'plugin.destroy_list';
@ -660,6 +796,39 @@ class kolab_notes extends rcube_plugin
$save_error = $this->gettext(kolab_storage::$last_error);
}
break;
case 'search':
$this->load_ui();
$results = array();
foreach ((array)$this->search_lists(rcube_utils::get_input_value('q', RCUBE_INPUT_GPC), rcube_utils::get_input_value('source', RCUBE_INPUT_GPC)) as $id => $prop) {
$editname = $prop['editname'];
unset($prop['editname']); // force full name to be displayed
// let the UI generate HTML and CSS representation for this calendar
$html = $this->ui->folder_list_item($id, $prop, $jsenv);
$prop += (array)$jsenv[$id];
$prop['editname'] = $editname;
$prop['html'] = $html;
$results[] = $prop;
}
// report more results available
if ($this->driver->search_more_results) {
$this->rc->output->show_message('autocompletemore', 'info');
}
$this->rc->output->command('multi_thread_http_response', $results, rcube_utils::get_input_value('_reqid', RCUBE_INPUT_GPC));
return;
case 'subscribe':
$success = false;
if ($list['id'] && ($folder = $this->get_folder($list['id']))) {
if (isset($list['permanent']))
$success |= $folder->subscribe(intval($list['permanent']));
if (isset($list['active']))
$success |= $folder->activate(intval($list['active']));
}
break;
}
$this->rc->output->command('plugin.unlock_saving');

View file

@ -57,6 +57,11 @@ class kolab_notes_ui
$this->plugin->include_script('notes.js');
$this->plugin->include_script('jquery.tagedit.js');
// include kolab folderlist widget if available
if (is_readable($this->plugin->api->dir . 'libkolab/js/folderlist.js')) {
$this->plugin->api->include_script('libkolab/js/folderlist.js');
}
$this->plugin->include_stylesheet($this->plugin->local_skin_path() . '/tagedit.css');
// load config options and user prefs relevant for the UI
@ -109,44 +114,110 @@ class kolab_notes_ui
$select = new html_select($attrib);
}
$tree = $attrib['type'] != 'select' ? true : null;
$lists = $this->plugin->get_lists($tree);
$jsenv = array();
$items = '';
foreach ($this->plugin->get_lists() as $prop) {
unset($prop['user_id']);
$id = $prop['id'];
$class = '';
if (!$prop['virtual'])
$jsenv[$id] = $prop;
if (is_object($tree)) {
$html = $this->folder_tree_html($tree, $lists, $jsenv, $attrib);
}
else {
$html = '';
foreach ($lists as $prop) {
unset($prop['user_id']);
$id = $prop['id'];
if ($attrib['type'] == 'select') {
if ($prop['editable']) {
$select->add($prop['name'], $prop['id']);
if ($attrib['type'] == 'select') {
if ($prop['editable']) {
$select->add($prop['name'], $prop['id']);
}
}
else {
$html .= html::tag('li', array('id' => 'rcmliknb' . rcube_utils::html_identifier($id), 'class' => $prop['group']),
$this->folder_list_item($id, $prop, $jsenv)
);
}
}
else {
$html_id = rcube_utils::html_identifier($id);
$title = $prop['name'] != $prop['listname'] ? html_entity_decode($prop['name'], ENT_COMPAT, RCMAIL_CHARSET) : '';
if ($prop['virtual'])
$class .= ' virtual';
else if (!$prop['editable'])
$class .= ' readonly';
if ($prop['class_name'])
$class .= ' '.$prop['class_name'];
$attr = $prop['virtual'] ? array('tabindex' => '0') : array('href' => $this->rc->url(array('_list' => $id)));
$items .= html::tag('li', array('id' => 'rcmliknb' . $html_id, 'class' => trim($class)),
html::a($attr + array('class' => 'listname', 'title' => $title), $prop['listname']) .
html::span(array('class' => 'count'), '')
);
}
}
$this->rc->output->set_env('kolab_notebooks', $jsenv);
$this->rc->output->add_gui_object('notebooks', $attrib['id']);
return $attrib['type'] == 'select' ? $select->show() : html::tag('ul', $attrib, $items, html::$common_attrib);
return $attrib['type'] == 'select' ? $select->show() : html::tag('ul', $attrib, $html, html::$common_attrib);
}
/**
* Return html for a structured list <ul> for the folder tree
*/
public function folder_tree_html($node, $data, &$jsenv, $attrib)
{
$out = '';
foreach ($node->children as $folder) {
$id = $folder->id;
$prop = $data[$id];
$is_collapsed = false; // TODO: determine this somehow?
$content = $this->folder_list_item($id, $prop, $jsenv, $attrib['activeonly']);
if (!empty($folder->children)) {
$content .= html::tag('ul', array('style' => ($is_collapsed ? "display:none;" : null)),
$this->folder_tree_html($folder, $data, $jsenv, $attrib));
}
if (strlen($content)) {
$out .= html::tag('li', array(
'id' => 'rcmliknb' . rcube_utils::html_identifier($id),
'class' => $prop['group'] . ($prop['virtual'] ? ' virtual' : ''),
),
$content);
}
}
return $out;
}
/**
* Helper method to build a tasklist item (HTML content and js data)
*/
public function folder_list_item($id, $prop, &$jsenv, $activeonly = false)
{
if (!$prop['virtual']) {
unset($prop['user_id']);
$jsenv[$id] = $prop;
}
$classes = array('folder');
if ($prop['virtual']) {
$classes[] = 'virtual';
}
else if (!$prop['editable']) {
$classes[] = 'readonly';
}
if ($prop['subscribed']) {
$classes[] = 'subscribed';
}
if ($prop['class']) {
$classes[] = $prop['class'];
}
$title = $prop['title'] ?: ($prop['name'] != $prop['listname'] || strlen($prop['name']) > 25 ?
html_entity_decode($prop['name'], ENT_COMPAT, RCMAIL_CHARSET) : '');
$label_id = 'nl:' . $id;
$attr = $prop['virtual'] ? array('tabindex' => '0') : array('href' => $this->rc->url(array('_list' => $id)));
return html::div(join(' ', $classes),
html::a($attr + array('class' => 'listname', 'title' => $title, 'id' => $label_id), $prop['listname'] ?: $prop['name']) .
($prop['virtual'] ? '' :
html::tag('input', array('type' => 'checkbox', 'name' => '_list[]', 'value' => $id, 'checked' => $prop['active'], 'aria-labelledby' => $label_id)) .
html::span('handle', '') .
(isset($prop['subscribed']) ?
html::a(array('href' => '#', 'class' => 'subscribed', 'title' => $this->plugin->gettext('foldersubscribe'), 'role' => 'checkbox', 'aria-checked' => $prop['subscribed'] ? 'true' : 'false'), ' ') :
''
)
)
);
return '';
}
public function listing($attrib)

View file

@ -28,6 +28,11 @@ $labels['unsavedchanges'] = 'Unsaved Changes!';
$labels['appendnote'] = 'Add a Note';
$labels['editnote'] = 'Edit Note';
$labels['savein'] = 'Save in';
$labels['foldersubscribe'] = 'List permanently';
$labels['findnotebooks'] = 'Find notebooks...';
$labels['listsearchresults'] = 'Additional notebooks';
$labels['nrnotebooksfound'] = '$nr notebooks found';
$labels['nonotebooksfound'] = 'No notebooks found';
$labels['savingdata'] = 'Saving data...';
$labels['recordnotfound'] = 'Record not found';
@ -48,3 +53,4 @@ $labels['arialabelnotessortmenu'] = 'Notes list sorting options';
$labels['arialabelnotesoptionsmenu'] = 'Notebook actions menu';
$labels['arialabelnotebookform'] = 'Notebook properties';
$labels['arialabelmessagereferences'] = 'Linked email messages';
$labels['arialabelfolderearchform'] = 'Notebooks search form';

View file

@ -86,14 +86,21 @@ function rcube_kolab_notes_ui(settings)
// initialize folder selectors
var li, id;
for (id in me.notebooks) {
if (me.notebooks[id].editable && (!settings.selected_list || (me.notebooks[id].active && !me.notebooks[me.selected_list].active))) {
if (me.notebooks[id].editable && !settings.selected_list) {
settings.selected_list = id;
}
}
notebookslist = new rcube_treelist_widget(rcmail.gui_objects.notebooks, {
var widget_class = window.kolab_folderlist || rcube_treelist_widget;
notebookslist = new widget_class(rcmail.gui_objects.notebooks, {
id_prefix: 'rcmliknb',
save_state: true,
selectable: true,
keyboard: false,
searchbox: '#notebooksearch',
search_action: 'notes/list',
search_sources: [ 'folders', 'users' ],
search_title: rcmail.gettext('listsearchresults','kolab_notes'),
check_droptarget: function(node) {
var list = me.notebooks[node.id];
return !node.virtual && list.editable && node.id != me.selected_list;
@ -112,8 +119,30 @@ function rcube_kolab_notes_ui(settings)
});
}
});
notebookslist.addEventListener('subscribe', function(p) {
var list;
if ((list = me.notebooks[p.id])) {
list.subscribed = p.subscribed || false;
rcmail.http_post('list', { _do:'subscribe', _list:{ id:p.id, permanent:list.subscribed?1:0 } });
}
});
notebookslist.addEventListener('insert-item', function(p) {
var list = p.data;
if (list && list.id && !list.virtual) {
me.notebooks[list.id] = list;
var prop = { id:p.id, active:list.active?1:0 };
if (list.subscribed) prop.permanent = 1;
rcmail.http_post('list', { _do:'subscribe', _list:prop });
}
});
notebookslist.addEventListener('search-complete', function(data) {
if (data.length)
rcmail.display_message(rcmail.gettext('nrnotebooksfound','kolab_notes').replace('$nr', data.length), 'voice');
else
rcmail.display_message(rcmail.gettext('nonotebooksfound','kolab_notes'), 'info');
});
$(rcmail.gui_objects.notebooks).on('click', 'li a', function(e) {
$(rcmail.gui_objects.notebooks).on('click', 'div.folder > a.listname', function(e) {
var id = String($(this).closest('li').attr('id')).replace(/^rcmliknb/, '');
notebookslist.select(id);
e.preventDefault();

View file

@ -1 +0,0 @@
../../../kolab_addressbook/skins/larry/folder_icons.png

View file

@ -293,71 +293,141 @@
bottom: 0;
}
.notesview #notebooks li {
margin: 0;
height: 20px;
padding: 6px 8px 2px 6px;
display: block;
position: relative;
white-space: nowrap;
.notesview #notebooksbox .scroller {
top: 34px;
}
.notesview #notebooks li.virtual {
height: 12px;
.notesview #notebooks li {
margin: 0;
display: block;
position: relative;
}
.notesview #notebooks li > div.folder {
position: relative;
padding: 0;
height: 28px;
}
.notesview #notebooks li.virtual > div.folder {
height: 22px;
}
.notesview #notebooks li .listname {
display: block;
position: absolute;
top: 1px;
left: 2px;
right: 6px;
height: 19px;
cursor: default;
padding: 4px 26px 2px 6px;
margin: 0;
height: 24px;
padding-bottom: 0;
padding-right: 26px;
color: #004458;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.notesview #notebooks li.virtual .listname {
.notesview #notebooks li.virtual > div > .listname {
color: #aaa;
top: 0;
padding: 1px 8px;
height: 18px;
padding-top: 3px;
}
.notesview #notebooks li.readonly,
.notesview #notebooks li.shared,
.notesview #notebooks li.other {
background-image: url('folder_icons.png');
background-position: right -1000px;
background-repeat: no-repeat;
.notesview #notebooksbox .treelist li a.subscribed {
display: inline-block;
position: absolute;
top: 6px;
right: 5px;
height: 16px;
width: 16px;
padding: 0;
background: url(sprites.png) -100px 0 no-repeat;
overflow: hidden;
text-indent: -5000px;
cursor: pointer;
}
.notesview #notebooks li.readonly {
background-position: 98% -21px;
.notesview #notebooksbox .treelist div > a.subscribed:focus,
.notesview #notebooksbox .treelist div:hover > a.subscribed {
background-position: 2px -266px;
}
.notesview #notebooks li.other {
background-position: 98% -52px;
.notesview #notebooksbox .treelist div.subscribed a.subscribed {
background-position: -16px -266px;
}
.notesview #notebooks li.other.readonly {
background-position: 98% -77px;
.notesview #notebooksbox .treelist li a.subscribed:focus {
border-radius: 3px;
outline: 2px solid rgba(30,150,192, 0.5);
}
.notesview #notebooks li.shared {
background-position: 98% -103px;
.notesview #notebooksbox .treelist input {
position: absolute;
top: 4px;
left: 14px;
}
.notesview #notebooks li.shared.readonly {
background-position: 98% -130px;
.notesview #notebooks input {
display: none;
}
.notesview #notebooks li.other.readonly .listname,
.notesview #notebooks li.shared.readonly .listname {
padding-right: 36px;
.notesview #notebooksbox .searchresults a.listname {
padding-left: 36px;
}
.notesview #notebooks div.folder span.handle {
display: inline-block;
position: absolute;
top: 6px;
right: 26px;
height: 16px;
width: 30px;
padding: 0;
background: url('sprites.png') right -1000px no-repeat;
}
.notesview #notebooks div.readonly span.handle {
background-position: right -192px;
}
.notesview #notebooks div.other span.handle {
background-position: right -210px;
}
.notesview #notebooks div.other.readonly span.handle {
background-position: right -228px;
}
.notesview #notebooks div.shared span.handle {
background-position: right -246px;
}
.notesview #notebooks div.other .listname,
.notesview #notebooks div.shared .listname,
.notesview #notebooks div.readonly .listname {
padding-right: 46px;
}
.notesview #notebooks div.other.readonly .listname {
padding-right: 56px;
}
.notesview #notebooksbox .searchresults {
background: #b0ccd7;
margin-top: 8px;
}
.notesview #notebooksbox .searchresults .boxtitle {
background: none;
padding: 2px 8px 2px 8px;
}
.notesview #notebooksbox .boxtitle a.iconbutton.search {
position: absolute;
top: 8px;
right: 8px;
width: 16px;
cursor: pointer;
background-position: -2px -317px;
}
.notesview .uidialog .tabbed {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

View file

@ -38,7 +38,18 @@
</div>
<div id="notebooksbox" class="uibox listbox" role="navigation" aria-labelledby="aria-label-notebooks">
<h2 class="boxtitle" id="aria-label-notebooks"><roundcube:label name="kolab_notes.lists" /></h2>
<h2 class="boxtitle" id="aria-label-notebooks"><roundcube:label name="kolab_notes.lists" />
<a href="#notebooks" class="iconbutton search" title="<roundcube:label name='kolab_notes.findnotebooks' />" tabindex="0"><roundcube:label name="kolab_notes.findnotebooks" /></a>
</h2>
<div class="listsearchbox" style="display:none">
<div class="searchbox" role="search" aria-labelledby="aria-label-notebooksearchform" aria-controls="kolabnoteslist">
<h3 id="aria-label-notebooksearchform" class="voice"><roundcube:label name="kolab_notes.arialabelfolderearchform" /></h3>
<label for="notebooksearch" class="voice"><roundcube:label name="kolab_notes.searchterms" /></label>
<input type="text" name="q" id="notebooksearch" placeholder="<roundcube:label name='kolab_notes.findnotebooks' />" />
<a class="iconbutton searchicon"></a>
<roundcube:button command="reset-listsearch" id="notebooksearch-reset" class="iconbutton reset" title="resetsearch" label="resetsearch" />
</div>
</div>
<div class="scroller withfooter">
<roundcube:object name="plugin.notebooks" id="notebooks" class="listing treelist" />
</div>
@ -152,6 +163,33 @@ $(document).ready(function(e){
$(window).resize(function(e){
layout_view();
});
// animation to unfold list search box
$('#notebooksbox .boxtitle a.search').click(function(e){
var box = $('#notebooksbox .listsearchbox'),
dir = box.is(':visible') ? -1 : 1;
box.slideToggle({
duration: 160,
progress: function(animation, progress) {
if (dir < 0) progress = 1 - progress;
$('#notebooksbox .scroller').css('top', (34 + 34 * progress) + 'px');
},
complete: function() {
box.toggleClass('expanded');
if (box.is(':visible')) {
box.find('input[type=text]').focus();
}
else {
$('#notebooksearch-reset').click();
}
// TODO: save state in localStorage
}
});
return false;
});
});
</script>