Render calendar folders as a searchable treelist widget
This commit is contained in:
parent
d2d831b775
commit
00b1c7631b
7 changed files with 283 additions and 114 deletions
|
@ -51,6 +51,7 @@ function rcube_calendar_ui(settings)
|
||||||
var ignore_click = false;
|
var ignore_click = false;
|
||||||
var event_defaults = { free_busy:'busy', alarms:'' };
|
var event_defaults = { free_busy:'busy', alarms:'' };
|
||||||
var event_attendees = [];
|
var event_attendees = [];
|
||||||
|
var calendars_list;
|
||||||
var attendees_list;
|
var attendees_list;
|
||||||
var resources_list;
|
var resources_list;
|
||||||
var resources_treelist;
|
var resources_treelist;
|
||||||
|
@ -2658,15 +2659,10 @@ function rcube_calendar_ui(settings)
|
||||||
// mark the given calendar folder as selected
|
// mark the given calendar folder as selected
|
||||||
this.select_calendar = function(id)
|
this.select_calendar = function(id)
|
||||||
{
|
{
|
||||||
var prefix = 'rcmlical';
|
calendars_list.select(id);
|
||||||
|
|
||||||
$(rcmail.gui_objects.calendarslist).find('li.selected')
|
|
||||||
.removeClass('selected').addClass('unfocused');
|
|
||||||
$('#' + prefix + id, rcmail.gui_objects.calendarslist)
|
|
||||||
.removeClass('unfocused').addClass('selected');
|
|
||||||
|
|
||||||
// trigger event hook
|
// trigger event hook
|
||||||
rcmail.triggerEvent('selectfolder', { folder:name, prefix:prefix });
|
rcmail.triggerEvent('selectfolder', { folder:id, prefix:'rcmlical' });
|
||||||
|
|
||||||
this.selected_calendar = id;
|
this.selected_calendar = id;
|
||||||
};
|
};
|
||||||
|
@ -2703,35 +2699,8 @@ function rcube_calendar_ui(settings)
|
||||||
event_sources.push(this.calendars[id]);
|
event_sources.push(this.calendars[id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// init event handler on calendar list checkbox
|
// check active calendars
|
||||||
if ((li = rcube_find_object('rcmlical' + id))) {
|
$('#rcmlical'+id+' > .calendar input').data('id', id).get(0).checked = active;
|
||||||
$('#'+li.id+' input').click(function(e){
|
|
||||||
var id = $(this).data('id');
|
|
||||||
if (me.calendars[id]) { // add or remove event source on click
|
|
||||||
var action;
|
|
||||||
if (this.checked) {
|
|
||||||
action = 'addEventSource';
|
|
||||||
me.calendars[id].active = true;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
action = 'removeEventSource';
|
|
||||||
me.calendars[id].active = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// add/remove event source
|
|
||||||
fc.fullCalendar(action, me.calendars[id]);
|
|
||||||
rcmail.http_post('calendar', { action:'subscribe', c:{ id:id, active:me.calendars[id].active?1:0 } });
|
|
||||||
}
|
|
||||||
}).data('id', id).get(0).checked = active;
|
|
||||||
|
|
||||||
$(li).click(function(e){
|
|
||||||
me.select_calendar($(this).data('id'));
|
|
||||||
rcmail.enable_command('calendar-edit', true);
|
|
||||||
rcmail.enable_command('calendar-remove', 'calendar-showurl', true);
|
|
||||||
})
|
|
||||||
.dblclick(function(){ me.calendar_edit_dialog(me.calendars[me.selected_calendar]); })
|
|
||||||
.data('id', id);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!cal.readonly && !this.selected_calendar) {
|
if (!cal.readonly && !this.selected_calendar) {
|
||||||
this.selected_calendar = id;
|
this.selected_calendar = id;
|
||||||
|
@ -2739,6 +2708,49 @@ function rcube_calendar_ui(settings)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// initialize treelist widget that controls the calendars list
|
||||||
|
calendars_list = new rcube_treelist_widget(rcmail.gui_objects.calendarslist, {
|
||||||
|
id_prefix: 'rcmlical',
|
||||||
|
selectable: true,
|
||||||
|
searchbox: '#calendarlistsearch'
|
||||||
|
});
|
||||||
|
calendars_list.addEventListener('select', function(node){
|
||||||
|
me.select_calendar(node.id);
|
||||||
|
rcmail.enable_command('calendar-edit', 'calendar-showurl', true);
|
||||||
|
rcmail.enable_command('calendar-remove', !me.calendars[node.id].readonly);
|
||||||
|
});
|
||||||
|
calendars_list.addEventListener('search', function(search){
|
||||||
|
console.log(search);
|
||||||
|
});
|
||||||
|
|
||||||
|
// init (delegate) event handler on calendar list checkboxes
|
||||||
|
$(rcmail.gui_objects.calendarslist).on('click', 'input[type=checkbox]', function(e){
|
||||||
|
var id = $(this).data('id');
|
||||||
|
if (me.calendars[id]) { // add or remove event source on click
|
||||||
|
var action;
|
||||||
|
if (this.checked) {
|
||||||
|
action = 'addEventSource';
|
||||||
|
me.calendars[id].active = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
action = 'removeEventSource';
|
||||||
|
me.calendars[id].active = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// add/remove event source
|
||||||
|
fc.fullCalendar(action, me.calendars[id]);
|
||||||
|
rcmail.http_post('calendar', { action:'subscribe', c:{ id:id, active:me.calendars[id].active?1:0 } });
|
||||||
|
|
||||||
|
e.stopPropagation();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// register dbl-click handler to open calendar edit dialog
|
||||||
|
$(rcmail.gui_objects.calendarslist).on('dblclick', ':not(.virtual) > .calname', function(e){
|
||||||
|
var id = $(this).closest('li').attr('id').replace(/^rcmlical/, '');
|
||||||
|
me.calendar_edit_dialog(me.calendars[id]);
|
||||||
|
});
|
||||||
|
|
||||||
// select default calendar
|
// select default calendar
|
||||||
if (settings.default_calendar && this.calendars[settings.default_calendar] && !this.calendars[settings.default_calendar].readonly)
|
if (settings.default_calendar && this.calendars[settings.default_calendar] && !this.calendars[settings.default_calendar].readonly)
|
||||||
this.selected_calendar = settings.default_calendar;
|
this.selected_calendar = settings.default_calendar;
|
||||||
|
|
|
@ -88,16 +88,16 @@ class kolab_driver extends calendar_driver
|
||||||
return $this->calendars;
|
return $this->calendars;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a list of available calendars from this source
|
* Get a list of available calendars from this source
|
||||||
*
|
*
|
||||||
* @param bool $active Return only active calendars
|
* @param bool $active Return only active calendars
|
||||||
* @param bool $personal Return only personal calendars
|
* @param bool $personal Return only personal calendars
|
||||||
|
* @param object $tree Reference to hierarchical folder tree object
|
||||||
*
|
*
|
||||||
* @return array List of calendars
|
* @return array List of calendars
|
||||||
*/
|
*/
|
||||||
public function list_calendars($active = false, $personal = false)
|
public function list_calendars($active = false, $personal = false, &$tree = null)
|
||||||
{
|
{
|
||||||
// attempt to create a default calendar for this user
|
// attempt to create a default calendar for this user
|
||||||
if (!$this->has_writeable) {
|
if (!$this->has_writeable) {
|
||||||
|
@ -112,7 +112,7 @@ class kolab_driver extends calendar_driver
|
||||||
|
|
||||||
// include virtual folders for a full folder tree
|
// include virtual folders for a full folder tree
|
||||||
if (!$active && !$personal && !$this->rc->output->ajax_call && in_array($this->rc->action, array('index','')))
|
if (!$active && !$personal && !$this->rc->output->ajax_call && in_array($this->rc->action, array('index','')))
|
||||||
$folders = kolab_storage::folder_hierarchy($folders);
|
$folders = kolab_storage::folder_hierarchy($folders, $tree);
|
||||||
|
|
||||||
foreach ($folders as $id => $cal) {
|
foreach ($folders as $id => $cal) {
|
||||||
$fullname = $cal->get_name();
|
$fullname = $cal->get_name();
|
||||||
|
@ -124,6 +124,7 @@ class kolab_driver extends calendar_driver
|
||||||
'id' => $cal->id,
|
'id' => $cal->id,
|
||||||
'name' => $fullname,
|
'name' => $fullname,
|
||||||
'listname' => $listname,
|
'listname' => $listname,
|
||||||
|
'editname' => $cal->get_foldername(),
|
||||||
'virtual' => true,
|
'virtual' => true,
|
||||||
'readonly' => true,
|
'readonly' => true,
|
||||||
);
|
);
|
||||||
|
@ -1106,6 +1107,11 @@ class kolab_driver extends calendar_driver
|
||||||
*/
|
*/
|
||||||
public function calendar_form($action, $calendar, $formfields)
|
public function calendar_form($action, $calendar, $formfields)
|
||||||
{
|
{
|
||||||
|
// show default dialog for birthday calendar
|
||||||
|
if ($calendar['id'] == self::BIRTHDAY_CALENDAR_ID) {
|
||||||
|
return parent::calendar_form($action, $calendar, $formfields);
|
||||||
|
}
|
||||||
|
|
||||||
if ($calendar['id'] && ($cal = $this->calendars[$calendar['id']])) {
|
if ($calendar['id'] && ($cal = $this->calendars[$calendar['id']])) {
|
||||||
$folder = $cal->get_realname(); // UTF7
|
$folder = $cal->get_realname(); // UTF7
|
||||||
$color = $cal->get_color();
|
$color = $cal->get_color();
|
||||||
|
|
|
@ -187,45 +187,108 @@ class calendar_ui
|
||||||
*/
|
*/
|
||||||
function calendar_list($attrib = array())
|
function calendar_list($attrib = array())
|
||||||
{
|
{
|
||||||
$calendars = $this->cal->driver->list_calendars();
|
$html = '';
|
||||||
|
$jsenv = array();
|
||||||
|
$calendars = $this->cal->driver->list_calendars(false, false, $tree);
|
||||||
|
|
||||||
|
// walk folder tree
|
||||||
|
if (is_object($tree)) {
|
||||||
|
$html = $this->list_tree_html($tree, $calendars, $jsenv, $attrib);
|
||||||
|
|
||||||
|
// append birthdays calendar which isn't part of $tree
|
||||||
|
if ($bdaycal = $calendars[calendar_driver::BIRTHDAY_CALENDAR_ID]) {
|
||||||
|
$calendars = array(calendar_driver::BIRTHDAY_CALENDAR_ID => $bdaycal);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$calendars = array(); // clear array for flat listing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// fall-back to flat folder listing
|
||||||
|
$attrib['class'] .= ' flat';
|
||||||
|
}
|
||||||
|
|
||||||
$li = '';
|
|
||||||
foreach ((array)$calendars as $id => $prop) {
|
foreach ((array)$calendars as $id => $prop) {
|
||||||
if ($attrib['activeonly'] && !$prop['active'])
|
if ($attrib['activeonly'] && !$prop['active'])
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
unset($prop['user_id']);
|
$html .= html::tag('li', array('id' => 'rcmlical' . rcube_utils::html_identifier($id)),
|
||||||
$prop['alarms'] = $this->cal->driver->alarms;
|
$content = $this->calendar_list_item($id, $prop, $jsenv)
|
||||||
$prop['attendees'] = $this->cal->driver->attendees;
|
);
|
||||||
$prop['freebusy'] = $this->cal->driver->freebusy;
|
|
||||||
$prop['attachments'] = $this->cal->driver->attachments;
|
|
||||||
$prop['undelete'] = $this->cal->driver->undelete;
|
|
||||||
$prop['feedurl'] = $this->cal->get_url(array('_cal' => $this->cal->ical_feed_hash($id) . '.ics', 'action' => 'feed'));
|
|
||||||
|
|
||||||
if (!$prop['virtual'])
|
|
||||||
$jsenv[$id] = $prop;
|
|
||||||
|
|
||||||
$html_id = html_identifier($id);
|
|
||||||
$class = 'cal-' . asciiwords($id, true);
|
|
||||||
$title = $prop['name'] != $prop['listname'] ? html_entity_decode($prop['name'], ENT_COMPAT, RCMAIL_CHARSET) : '';
|
|
||||||
|
|
||||||
if ($prop['virtual'])
|
|
||||||
$class .= ' virtual';
|
|
||||||
else if ($prop['readonly'])
|
|
||||||
$class .= ' readonly';
|
|
||||||
if ($prop['class_name'])
|
|
||||||
$class .= ' '.$prop['class_name'];
|
|
||||||
|
|
||||||
$li .= html::tag('li', array('id' => 'rcmlical' . $html_id, 'class' => $class),
|
|
||||||
($prop['virtual'] ? '' : html::tag('input', array('type' => 'checkbox', 'name' => '_cal[]', 'value' => $id, 'checked' => $prop['active']), '') .
|
|
||||||
html::span('handle', ' ')) .
|
|
||||||
html::span(array('class' => 'calname', 'title' => $title), $prop['listname']));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->rc->output->set_env('calendars', $jsenv);
|
$this->rc->output->set_env('calendars', $jsenv);
|
||||||
$this->rc->output->add_gui_object('calendarslist', $attrib['id']);
|
$this->rc->output->add_gui_object('calendarslist', $attrib['id']);
|
||||||
|
|
||||||
return html::tag('ul', $attrib, $li, html::$common_attrib);
|
return html::tag('ul', $attrib, $html, html::$common_attrib);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return html for a structured list <ul> for the mailbox tree
|
||||||
|
*/
|
||||||
|
public function list_tree_html(&$node, &$data, &$jsenv, $attrib)
|
||||||
|
{
|
||||||
|
$out = '';
|
||||||
|
foreach ($node->children as $folder) {
|
||||||
|
$id = $folder->id;
|
||||||
|
$prop = $data[$id];
|
||||||
|
|
||||||
|
$content = $this->calendar_list_item($id, $prop, $jsenv);
|
||||||
|
|
||||||
|
if (!empty($folder->children)) {
|
||||||
|
$content .= html::tag('ul', array('style' => ($is_collapsed ? "display:none;" : null)),
|
||||||
|
$this->list_tree_html($folder, $data, $jsenv, $attrib));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strlen($content)) {
|
||||||
|
$out .= html::tag('li', array(
|
||||||
|
'id' => 'rcmlical' . rcube_utils::html_identifier($id),
|
||||||
|
'class' => $prop['virtual'] ? 'virtual' : '',
|
||||||
|
),
|
||||||
|
$content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method to build a calendar list item (HTML content and js data)
|
||||||
|
*/
|
||||||
|
protected function calendar_list_item($id, $prop, &$jsenv)
|
||||||
|
{
|
||||||
|
unset($prop['user_id']);
|
||||||
|
$prop['alarms'] = $this->cal->driver->alarms;
|
||||||
|
$prop['attendees'] = $this->cal->driver->attendees;
|
||||||
|
$prop['freebusy'] = $this->cal->driver->freebusy;
|
||||||
|
$prop['attachments'] = $this->cal->driver->attachments;
|
||||||
|
$prop['undelete'] = $this->cal->driver->undelete;
|
||||||
|
$prop['feedurl'] = $this->cal->get_url(array('_cal' => $this->cal->ical_feed_hash($id) . '.ics', 'action' => 'feed'));
|
||||||
|
|
||||||
|
if (!$prop['virtual'])
|
||||||
|
$jsenv[$id] = $prop;
|
||||||
|
|
||||||
|
$class = 'calendar cal-' . asciiwords($id, true);
|
||||||
|
$title = $prop['name'] != $prop['listname'] ? html_entity_decode($prop['name'], ENT_COMPAT, RCMAIL_CHARSET) : '';
|
||||||
|
$is_collapsed = false; // TODO: determine this somehow?
|
||||||
|
|
||||||
|
if ($prop['virtual'])
|
||||||
|
$class = 'folder virtual';
|
||||||
|
else if ($prop['readonly'])
|
||||||
|
$class .= ' readonly';
|
||||||
|
if ($prop['class_name'])
|
||||||
|
$class .= ' '.$prop['class_name'];
|
||||||
|
|
||||||
|
$content = '';
|
||||||
|
if (!$attrib['activeonly'] || $prop['active']) {
|
||||||
|
$content = html::div($class,
|
||||||
|
($prop['virtual'] ? '' : html::tag('input', array('type' => 'checkbox', 'name' => '_cal[]', 'value' => $id, 'checked' => $prop['active']), '') .
|
||||||
|
html::span('handle', ' ')) .
|
||||||
|
html::span(array('class' => 'calname', 'title' => $title), $prop['editname'] ? Q($prop['editname']) : $prop['listname'])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $content;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -164,45 +164,64 @@ pre {
|
||||||
right: 0;
|
right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#calendarslist li {
|
#calendars .scroller {
|
||||||
margin: 0;
|
top: 68px;
|
||||||
height: 20px;
|
|
||||||
padding: 6px 8px 2px;
|
|
||||||
display: block;
|
|
||||||
position: relative;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#calendarslist li.virtual {
|
#calendarslist li {
|
||||||
height: 12px;
|
margin: 0;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
#calendarslist li label {
|
#calendarslist li label {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#calendarslist li div.folder,
|
||||||
|
#calendarslist li div.calendar {
|
||||||
|
position: relative;
|
||||||
|
height: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#calendarslist li div.virtual {
|
||||||
|
height: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#calendarslist li span.calname {
|
#calendarslist li span.calname {
|
||||||
display: block;
|
display: block;
|
||||||
|
padding: 0px 30px 2px 2px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 6px;
|
top: 7px;
|
||||||
left: 26px;
|
left: 38px;
|
||||||
right: 24px;
|
right: 22px;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
background: url(images/calendars.png) right 20px no-repeat;
|
background: url(images/calendars.png) right 20px no-repeat;
|
||||||
padding-bottom: 2px;
|
|
||||||
padding-right: 30px;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
color: #004458;
|
color: #004458;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#calendarslist li div.virtual > span.calname {
|
||||||
|
color: #aaa;
|
||||||
|
top: 4px;
|
||||||
|
left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#calendarslist.flat li span.calname {
|
||||||
|
left: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
#calendarslist li span.handle {
|
#calendarslist li span.handle {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
top: 8px;
|
||||||
|
right: 6px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border-radius: 7px;
|
|
||||||
margin-right: 6px;
|
|
||||||
width: 10px;
|
width: 10px;
|
||||||
height: 10px;
|
height: 10px;
|
||||||
|
border-radius: 7px;
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
border: 1px solid rgba(0, 0, 0, 0.5);
|
border: 1px solid rgba(0, 0, 0, 0.5);
|
||||||
-webkit-box-shadow: inset 0px 0 1px 1px rgba(0, 0, 0, 0.3);
|
-webkit-box-shadow: inset 0px 0 1px 1px rgba(0, 0, 0, 0.3);
|
||||||
|
@ -212,43 +231,65 @@ pre {
|
||||||
|
|
||||||
#calendarslist li input {
|
#calendarslist li input {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 4px;
|
top: 5px;
|
||||||
right: 5px;
|
left: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#calendarslist li div.treetoggle {
|
||||||
|
top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#calendarslist li.virtual div.treetoggle {
|
||||||
|
top: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#calendarslist.flat li input {
|
||||||
|
left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#calendarslist ul li div.folder,
|
||||||
|
#calendarslist ul li div.calendar {
|
||||||
|
margin-left: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#calendarslist ul ul li div.folder,
|
||||||
|
#calendarslist ul ul li div.calendar {
|
||||||
|
margin-left: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#calendarslist ul ul ul li div.folder,
|
||||||
|
#calendarslist ul ul ul li div.calendar {
|
||||||
|
margin-left: 48px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#calendarslist li.selected {
|
#calendarslist li.selected {
|
||||||
background-color: #c7e3ef;
|
background-color: #c7e3ef;
|
||||||
}
|
}
|
||||||
|
|
||||||
#calendarslist li.selected span.calname {
|
#calendarslist li.selected > span.calname {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
#calendarslist li.readonly span.calname {
|
#calendarslist div.readonly span.calname {
|
||||||
background-position: right -20px;
|
background-position: right -20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#calendarslist li.other span.calname {
|
#calendarslist div.other span.calname {
|
||||||
background-position: right -38px;
|
background-position: right -38px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#calendarslist li.other.readonly span.calname {
|
#calendarslist div.other.readonly span.calname {
|
||||||
background-position: right -56px;
|
background-position: right -56px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#calendarslist li.shared span.calname {
|
#calendarslist div.shared span.calname {
|
||||||
background-position: right -74px;
|
background-position: right -74px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#calendarslist li.shared.readonly span.calname {
|
#calendarslist div.shared.readonly span.calname {
|
||||||
background-position: right -92px;
|
background-position: right -92px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#calendarslist li.virtual span.calname {
|
|
||||||
color: #aaa;
|
|
||||||
top: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#calfeedurl,
|
#calfeedurl,
|
||||||
#caldavurl {
|
#caldavurl {
|
||||||
width: 98%;
|
width: 98%;
|
||||||
|
|
|
@ -23,8 +23,15 @@
|
||||||
|
|
||||||
<div id="calendars" class="uibox listbox" style="visibility:hidden">
|
<div id="calendars" class="uibox listbox" style="visibility:hidden">
|
||||||
<h2 class="boxtitle"><roundcube:label name="calendar.calendars" /></h2>
|
<h2 class="boxtitle"><roundcube:label name="calendar.calendars" /></h2>
|
||||||
|
<div class="listsearchbox">
|
||||||
|
<div class="searchbox">
|
||||||
|
<input type="text" name="q" id="calendarlistsearch" />
|
||||||
|
<a class="iconbutton searchicon"></a>
|
||||||
|
<roundcube:button command="reset-listsearch" id="calendarlistsearch-reset" class="iconbutton reset" title="resetsearch" content="x" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="scroller withfooter">
|
<div class="scroller withfooter">
|
||||||
<roundcube:object name="plugin.calendar_list" id="calendarslist" class="listing" />
|
<roundcube:object name="plugin.calendar_list" id="calendarslist" class="treelist listing" />
|
||||||
</div>
|
</div>
|
||||||
<div class="boxfooter">
|
<div class="boxfooter">
|
||||||
<roundcube:button command="calendar-create" type="link" title="calendar.createcalendar" class="listbutton add disabled" classAct="listbutton add" innerClass="inner" content="+" /><roundcube:button name="calendaroptionslink" id="calendaroptionsmenulink" type="link" title="moreactions" class="listbutton groupactions" onclick="UI.show_popup('calendaroptionsmenu', undefined, { above:true });return false" innerClass="inner" content="⚙" />
|
<roundcube:button command="calendar-create" type="link" title="calendar.createcalendar" class="listbutton add disabled" classAct="listbutton add" innerClass="inner" content="+" /><roundcube:button name="calendaroptionslink" id="calendaroptionsmenulink" type="link" title="moreactions" class="listbutton groupactions" onclick="UI.show_popup('calendaroptionsmenu', undefined, { above:true });return false" innerClass="inner" content="⚙" />
|
||||||
|
|
|
@ -756,43 +756,54 @@ class kolab_storage
|
||||||
* Check the folder tree and add the missing parents as virtual folders
|
* Check the folder tree and add the missing parents as virtual folders
|
||||||
*
|
*
|
||||||
* @param array $folders Folders list
|
* @param array $folders Folders list
|
||||||
|
* @param object $tree Reference to the root node of the folder tree
|
||||||
*
|
*
|
||||||
* @return array Folders list
|
* @return array Flat folders list
|
||||||
*/
|
*/
|
||||||
public static function folder_hierarchy($folders)
|
public static function folder_hierarchy($folders, &$tree)
|
||||||
{
|
{
|
||||||
$_folders = array();
|
$_folders = array();
|
||||||
$existing = array_map(function($folder){ return $folder->get_name(); }, $folders);
|
|
||||||
$delim = rcube::get_instance()->get_storage()->get_hierarchy_delimiter();
|
$delim = rcube::get_instance()->get_storage()->get_hierarchy_delimiter();
|
||||||
|
$tree = new virtual_kolab_storage_folder('', '<root>', ''); // create tree root
|
||||||
|
$refs = array('' => $tree);
|
||||||
|
|
||||||
foreach ($folders as $idx => $folder) {
|
foreach ($folders as $idx => $folder) {
|
||||||
$path = explode($delim, $folder->name);
|
$path = explode($delim, $folder->name);
|
||||||
array_pop($path);
|
array_pop($path);
|
||||||
|
$folder->parent = join($delim, $path);
|
||||||
|
$folder->children = array(); // reset list
|
||||||
|
|
||||||
// skip top folders or ones with a custom displayname
|
// skip top folders or ones with a custom displayname
|
||||||
if (count($path) <= 1 || kolab_storage::custom_displayname($folder->name)) {
|
if (count($path) < 1 || kolab_storage::custom_displayname($folder->name)) {
|
||||||
|
$tree->children[] = $folder;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$parents = array();
|
$parents = array();
|
||||||
|
$depth = $folder->get_namespace() == 'personal' ? 1 : 2;
|
||||||
|
|
||||||
while (count($path) > 1 && ($parent = join($delim, $path))) {
|
while (count($path) >= $depth && ($parent = join($delim, $path))) {
|
||||||
$name = kolab_storage::object_name($parent, $folder->get_namespace());
|
|
||||||
if (!in_array($name, $existing)) {
|
|
||||||
$parents[$parent] = new virtual_kolab_storage_folder($parent, $name, $folder->get_namespace());
|
|
||||||
$existing[] = $name;
|
|
||||||
}
|
|
||||||
|
|
||||||
array_pop($path);
|
array_pop($path);
|
||||||
|
$name = kolab_storage::object_name($parent, $folder->get_namespace());
|
||||||
|
if (!$refs[$parent]) {
|
||||||
|
$refs[$parent] = new virtual_kolab_storage_folder($parent, $name, $folder->get_namespace(), join($delim, $path));
|
||||||
|
$parents[] = $refs[$parent];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!empty($parents)) {
|
if (!empty($parents)) {
|
||||||
$parents = array_reverse(array_values($parents));
|
$parents = array_reverse($parents);
|
||||||
foreach ($parents as $parent) {
|
foreach ($parents as $parent) {
|
||||||
|
$parent_node = $refs[$parent->parent] ?: $tree;
|
||||||
|
$parent_node->children[] = $parent;
|
||||||
$_folders[] = $parent;
|
$_folders[] = $parent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$parent_node = $refs[$folder->parent] ?: $tree;
|
||||||
|
$parent_node->children[] = $folder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$refs[$folder->name] = $folder;
|
||||||
$_folders[] = $folder;
|
$_folders[] = $folder;
|
||||||
unset($folders[$idx]);
|
unset($folders[$idx]);
|
||||||
}
|
}
|
||||||
|
@ -1164,13 +1175,18 @@ class virtual_kolab_storage_folder
|
||||||
public $id;
|
public $id;
|
||||||
public $name;
|
public $name;
|
||||||
public $namespace;
|
public $namespace;
|
||||||
|
public $parent = '';
|
||||||
|
public $children = array();
|
||||||
public $virtual = true;
|
public $virtual = true;
|
||||||
|
protected $displayname;
|
||||||
|
|
||||||
public function __construct($realname, $name, $ns)
|
public function __construct($name, $dispname, $ns, $parent = '')
|
||||||
{
|
{
|
||||||
$this->id = kolab_storage::folder_id($realname);
|
$this->id = kolab_storage::folder_id($name);
|
||||||
$this->name = $name;
|
$this->name = $name;
|
||||||
$this->namespace = $ns;
|
$this->namespace = $ns;
|
||||||
|
$this->parent = $parent;
|
||||||
|
$this->displayname = $dispname;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function get_namespace()
|
public function get_namespace()
|
||||||
|
@ -1181,6 +1197,12 @@ class virtual_kolab_storage_folder
|
||||||
public function get_name()
|
public function get_name()
|
||||||
{
|
{
|
||||||
// this is already kolab_storage::object_name() result
|
// this is already kolab_storage::object_name() result
|
||||||
return $this->name;
|
return $this->displayname;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_foldername()
|
||||||
|
{
|
||||||
|
$parts = explode('/', $this->name);
|
||||||
|
return rcube_charset::convert(end($parts), 'UTF7-IMAP');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,12 @@ class kolab_storage_folder
|
||||||
*/
|
*/
|
||||||
public $cache;
|
public $cache;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of direct child folders
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
public $children = array();
|
||||||
|
|
||||||
private $type_annotation;
|
private $type_annotation;
|
||||||
private $namespace;
|
private $namespace;
|
||||||
private $imap;
|
private $imap;
|
||||||
|
@ -217,6 +223,18 @@ class kolab_storage_folder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getter for the top-end folder name (not the entire path)
|
||||||
|
*
|
||||||
|
* @return string Name of this folder
|
||||||
|
*/
|
||||||
|
public function get_foldername()
|
||||||
|
{
|
||||||
|
$parts = explode('/', $this->name);
|
||||||
|
return rcube_charset::convert(end($parts), 'UTF7-IMAP');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the color value stored in metadata
|
* Get the color value stored in metadata
|
||||||
*
|
*
|
||||||
|
|
Loading…
Add table
Reference in a new issue