Implement new folder navigation for notes module (#3056)
This commit is contained in:
parent
e26c4836bc
commit
7a85b2590e
8 changed files with 517 additions and 135 deletions
|
@ -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');
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
../../../kolab_addressbook/skins/larry/folder_icons.png
|
|
@ -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 |
|
@ -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>
|
||||
|
|
Loading…
Add table
Reference in a new issue