Added functions to manage taks lists/folders (aka resources)

This commit is contained in:
Thomas Bruederli 2012-06-21 21:59:47 +02:00
parent dc3fa5cb4b
commit 92b2bbead7
10 changed files with 429 additions and 86 deletions

View file

@ -75,6 +75,7 @@ class tasklist_database_driver extends tasklist_driver
while ($result && ($arr = $this->rc->db->fetch_assoc($result))) {
$arr['showalarms'] = intval($arr['showalarms']);
$arr['active'] = !in_array($arr['id'], $hidden);
$arr['editable'] = true;
$this->lists[$arr['id']] = $arr;
$list_ids[] = $this->rc->db->quote($arr['id']);
}
@ -133,7 +134,7 @@ class tasklist_database_driver extends tasklist_driver
$query = $this->rc->db->query(
"UPDATE " . $this->db_lists . "
SET name=?, color=?, showalarms=?
WHERE calendar_id=?
WHERE tasklist_id=?
AND user_id=?",
$prop['name'],
$prop['color'],
@ -288,7 +289,8 @@ class tasklist_database_driver extends tasklist_driver
"SELECT * FROM " . $this->db_tasks . "
WHERE tasklist_id IN (%s)
AND del=0
%s",
%s
ORDER BY parent_id, task_id ASC",
join(',', $list_ids),
$sql_add
),
@ -409,6 +411,11 @@ class tasklist_database_driver extends tasklist_driver
$sql_set[] = $this->rc->db->quote_identifier($col) . '=' . (empty($prop[$col]) ? 'NULL' : $this->rc->db->quote($prop[$col]));
}
// moved from another list
if ($prop['_fromlist'] && ($newlist = $prop['list'])) {
$sql_set[] = 'tasklist_id=' . $this->rc->db->quote($newlist);
}
$query = $this->rc->db->query(sprintf(
"UPDATE " . $this->db_tasks . "
SET changed=%s %s

View file

@ -69,14 +69,27 @@ class tasklist_kolab_driver extends tasklist_driver
asort($names, SORT_LOCALE_STRING);
$delim = $this->rc->get_storage()->get_hierarchy_delimiter();
$listnames = array();
foreach ($names as $utf7name => $name) {
$folder = $this->folders[$utf7name];
$path_imap = explode($delim, $name);
$editname = array_pop($path_imap); // pop off raw name part
$path_imap = join($delim, $path_imap);
$name = kolab_storage::folder_displayname(kolab_storage::object_name($utf7name), $listnames);
$tasklist = array(
'id' => kolab_storage::folder_id($utf7name),
'name' => kolab_storage::object_name($utf7name),
'name' => $name,
'editname' => $editname,
'color' => 'CC0000',
'showalarms' => false,
'active' => 1, #$folder->is_subscribed(kolab_storage::SERVERSIDE_SUBSCRIPTION),
'editable' => true,
'active' => $folder->is_subscribed(kolab_storage::SERVERSIDE_SUBSCRIPTION),
'parentfolder' => $path_imap,
);
$this->lists[$tasklist['id']] = $tasklist;
$this->folders[$tasklist['id']] = $folder;
@ -108,9 +121,19 @@ class tasklist_kolab_driver extends tasklist_driver
*/
public function create_list($prop)
{
$prop['type'] = 'task';
$prop['subscribed'] = kolab_storage::SERVERSIDE_SUBSCRIPTION; // subscribe to folder by default
$folder = kolab_storage::folder_update($prop);
if ($folder === false) {
$this->last_error = kolab_storage::$last_error;
return false;
}
// create ID
return kolab_storage::folder_id($folder);
}
/**
* Update properties of an existing tasklist
*
@ -123,6 +146,20 @@ class tasklist_kolab_driver extends tasklist_driver
*/
public function edit_list($prop)
{
if ($prop['id'] && ($folder = $this->folders[$prop['id']])) {
$prop['oldname'] = $folder->name;
$prop['type'] = 'task';
$newfolder = kolab_storage::folder_update($prop);
if ($newfolder === false) {
$this->last_error = kolab_storage::$last_error;
return false;
}
// create ID
return kolab_storage::folder_id($newfolder);
}
return false;
}
@ -136,6 +173,9 @@ class tasklist_kolab_driver extends tasklist_driver
*/
public function subscribe_list($prop)
{
if ($prop['id'] && ($folder = $this->folders[$prop['id']])) {
return $folder->subscribe($prop['active'], kolab_storage::SERVERSIDE_SUBSCRIPTION);
}
return false;
}
@ -148,6 +188,13 @@ class tasklist_kolab_driver extends tasklist_driver
*/
public function remove_list($prop)
{
if ($prop['id'] && ($folder = $this->folders[$prop['id']])) {
if (kolab_storage::folder_delete($folder->name))
return true;
else
$this->last_error = kolab_storage::$last_error;
}
return false;
}
@ -419,4 +466,19 @@ class tasklist_kolab_driver extends tasklist_driver
}
/**
*
*/
public function tasklist_edit_form($formfields)
{
$select = kolab_storage::folder_selector('task', array('name' => 'parent', 'id' => 'edit-parentfolder'), null);
$formfields['parent'] = array(
'id' => 'edit-parentfolder',
'label' => $this->plugin->gettext('parentfolder'),
'value' => $select->show(''),
);
return parent::tasklist_edit_form($formfields);
}
}

View file

@ -181,4 +181,23 @@ abstract class tasklist_driver
return $rcmail->config->get('tasklist_categories', array());
}
/**
* Build the edit/create form for lists.
* This gives the drivers the opportunity to add more list properties
*
* @param array List with form fields to be rendered
* @return string HTML content of the form
*/
public function tasklist_edit_form($formfields)
{
$html = '';
foreach ($formfields as $prop => $field) {
$html .= html::div('form-section',
html::label($field['id'], $field['label']) .
$field['value']);
}
return $html;
}
}

View file

@ -31,6 +31,13 @@ $labels['save'] = 'Speichern';
$labels['cancel'] = 'Abbrechen';
$labels['addsubtask'] = 'Neue Teilaufgabe';
$labels['editlist'] = 'Ressource bearbeiten';
$labels['createlist'] = 'Neue Ressource';
$labels['listactions'] = 'Ressourcenoptionen...';
$labels['listname'] = 'Name';
$labels['showalarms'] = 'Erinnerungen anzeigen';
$labels['import'] = 'Importieren';
// date words
$labels['on'] = 'am';
$labels['at'] = 'um';

View file

@ -31,6 +31,13 @@ $labels['save'] = 'Save';
$labels['cancel'] = 'Cancel';
$labels['addsubtask'] = 'Add subtask';
$labels['editlist'] = 'Edit resource';
$labels['createlist'] = 'Add resource';
$labels['listactions'] = 'Resource options...';
$labels['listname'] = 'Name';
$labels['showalarms'] = 'Show alarms';
$labels['import'] = 'Import';
// date words
$labels['on'] = 'on';
$labels['at'] = 'at';

View file

@ -64,10 +64,6 @@ body.tasklistview #searchmenulink {
border-radius: 0 0 4px 4px;
}
#taskselector li.selected {
background-color: #c7e3ef;
}
#taskselector li.overdue a {
color: #b72a2a;
font-weight: bold;
@ -160,14 +156,6 @@ body.tasklistview #searchmenulink {
right: 5px;
}
#tasklists li.selected {
background-color: #c7e3ef;
}
#tasklists li.selected span.calname {
font-weight: bold;
}
#mainview-right {
position: absolute;
top: 0;
@ -461,6 +449,11 @@ ul.toolbarmenu li span.delete {
color: #999;
}
#task-parent-title {
position: relative;
top: -0.6em;
}
a.morelink {
font-size: 90%;
color: #0069a6;

View file

@ -74,7 +74,19 @@
</ul>
</div>
<div id="tasklistoptionsmenu" class="popupmenu">
<ul class="toolbarmenu">
<li><roundcube:button command="list-edit" label="edit" classAct="active" /></li>
<li><roundcube:button command="list-remove" label="delete" classAct="active" /></li>
<li><roundcube:button command="list-import" label="tasklist.import" classAct="active" /></li>
<roundcube:if condition="env:tasklist_driver == 'kolab'" />
<li><roundcube:button command="folders" task="settings" type="link" label="managefolders" classAct="active" /></li>
<roundcube:endif />
</ul>
</div>
<div id="taskshow">
<div class="form-section" id="task-parent-title"></div>
<div class="form-section">
<h2 id="task-title"></h2>
</div>
@ -125,6 +137,10 @@
</form>
</div>
<div id="tasklistform" class="uidialog">
<roundcube:object name="plugin.tasklist_editform" />
</div>
<script type="text/javascript">
// UI startup

View file

@ -49,7 +49,6 @@ function rcube_tasklist(settings)
var selector = 'all';
var filtermask = FILTER_MASK_ALL;
var idcount = 0;
var selected_list;
var saving_lock;
var ui_loading;
var taskcounts = {};
@ -77,6 +76,7 @@ function rcube_tasklist(settings)
/* public members */
this.tasklists = rcmail.env.tasklists;
this.selected_task;
this.selected_list;
/* public methods */
this.init = init;
@ -85,6 +85,8 @@ function rcube_tasklist(settings)
this.add_childtask = add_childtask;
this.quicksearch = quicksearch;
this.reset_search = reset_search;
this.list_remove = list_remove;
this.list_edit_dialog = list_edit_dialog;
/**
@ -92,16 +94,25 @@ function rcube_tasklist(settings)
*/
function init()
{
// select the first task list
for (var s in me.tasklists) {
selected_list = s;
break;
};
// sinitialize task list selectors
for (var id in me.tasklists) {
if ((li = rcmail.get_folder_li(id, 'rcmlitasklist'))) {
init_tasklist_li(li, id);
}
if (!me.tasklists.readonly && !me.selected_list) {
me.selected_list = id;
rcmail.enable_command('addtask', true);
$(li).click();
}
}
// register server callbacks
rcmail.addEventListener('plugin.data_ready', data_ready);
rcmail.addEventListener('plugin.refresh_task', update_taskitem);
rcmail.addEventListener('plugin.update_counts', update_counts);
rcmail.addEventListener('plugin.insert_tasklist', insert_list);
rcmail.addEventListener('plugin.update_tasklist', update_list);
rcmail.addEventListener('plugin.reload_data', function(){ list_tasks(null); });
rcmail.addEventListener('plugin.unlock_saving', function(p){ rcmail.set_busy(false, null, saving_lock); });
@ -121,7 +132,7 @@ function rcube_tasklist(settings)
var tasktext = this.elements.text.value;
var rec = { id:-(++idcount), title:tasktext, readonly:true, mask:0, complete:0 };
save_task({ tempid:rec.id, raw:tasktext, list:selected_list }, 'new');
save_task({ tempid:rec.id, raw:tasktext, list:me.selected_list }, 'new');
render_task(rec);
// clear form
@ -255,7 +266,11 @@ function rcube_tasklist(settings)
*/
function fetch_counts()
{
rcmail.http_request('counts');
var active = active_lists();
if (active.length)
rcmail.http_request('counts', { lists:active.join(',') });
else
update_counts({});
}
/**
@ -268,8 +283,13 @@ function rcube_tasklist(settings)
selector = sel;
}
var active = active_lists();
if (active.length) {
ui_loading = rcmail.set_busy(true, 'loading');
rcmail.http_request('fetch', { filter:filtermask, q:search_query }, true);
rcmail.http_request('fetch', { filter:filtermask, lists:active.join(','), q:search_query }, true);
}
else
data_ready([]);
$('#taskselector li.selected').removeClass('selected');
$('#taskselector li.'+selector).addClass('selected');
@ -373,7 +393,7 @@ function rcube_tasklist(settings)
div.addClass('nodate');
if ((rec.mask & FILTER_MASK_OVERDUE))
div.addClass('overdue');
console.log(replace)
var li, parent;
if (replace && (li = $('li[rel="'+replace+'"]', rcmail.gui_objects.resultlist)) && li.length) {
li.children('div.taskhead').first().replaceWith(div);
@ -545,6 +565,7 @@ console.log(replace)
me.selected_task = rec;
// fill dialog data
$('#task-parent-title').html(Q(rec.parent_title || '')+' &raquo;').css('display', rec.parent_title ? 'block' : 'none');
$('#task-title').html(Q(rec.title || ''));
$('#task-description').html(text2html(rec.description || '', 300, 6))[(rec.description ? 'show' : 'hide')]();
$('#task-date')[(rec.date ? 'show' : 'hide')]().children('.task-text').html(Q(rec.date || rcmail.gettext('nodate','tasklist')));
@ -590,7 +611,7 @@ console.log(replace)
$dialog = $('<div>'),
editform = $('#taskedit'),
list = rec.list && me.tasklists[rec.list] ? me.tasklists[rec.list] :
(selected_list ? me.tasklists[selected_list] : { editable: action=='new' });
(me.selected_list ? me.tasklists[me.selected_list] : { editable: action=='new' });
if (list.readonly || (action == 'edit' && (!rec || rec.readonly || rec.temp)))
return false;
@ -604,7 +625,7 @@ console.log(replace)
var rectime = $('#edit-time').val(rec.time || '');
var complete = $('#edit-completeness').val((rec.complete || 0) * 100);
completeness_slider.slider('value', complete.val());
var tasklist = $('#edit-tasklist').val(rec.list || 0);
var tasklist = $('#edit-tasklist').val(rec.list || 0); // .prop('disabled', rec.parent_id ? true : false);
$('#edit-nodate').unbind('click').click(function(){
recdate.val('');
@ -697,6 +718,108 @@ console.log(replace)
return true;
}
/**
*
*/
function list_edit_dialog(id)
{
var list = me.tasklists[id],
$dialog = $('#tasklistform').dialog('close');
editform = $('#tasklisteditform');
if (!list)
list = { name:'', editable:true, showalarms:true };
// fill edit form
var name = $('#edit-tasklistame').prop('disabled', !list.editable).val(list.editname || list.name),
alarms = $('#edit-showalarms').prop('checked', list.showalarms).get(0),
parent = $('#edit-parentfolder').val(list.parentfolder);
// dialog buttons
var buttons = {};
buttons[rcmail.gettext('save','tasklist')] = function() {
// do some input validation
if (!name.val() || name.val().length < 2) {
alert(rcmail.gettext('invalidlistproperties', 'tasklist'));
name.select();
return;
}
// post data to server
var data = editform.serializeJSON();
if (list.id)
data.id = list.id;
if (alarms)
data.showalarms = alarms.checked ? 1 : 0;
if (parent.length)
data.parentfolder = $('option:selected', parent).val();
saving_lock = rcmail.set_busy(true, 'tasklist.savingdata');
rcmail.http_post('tasklist', { action:(list.id ? 'edit' : 'new'), l:data });
$dialog.dialog('close');
};
buttons[rcmail.gettext('cancel','tasklist')] = function() {
$dialog.dialog('close');
};
// open jquery UI dialog
$dialog.dialog({
modal: true,
resizable: true,
closeOnEscape: false,
title: rcmail.gettext((list.id ? 'editlist' : 'createlist'), 'tasklist'),
close: function() { $dialog.dialog('destroy').hide(); },
buttons: buttons,
minWidth: 400,
width: 420
}).show();
}
/**
*
*/
function list_remove(id)
{
var list = me.tasklists[id];
if (list && !list.readonly) {
alert('To be implemented')
}
}
/**
*
*/
function insert_list(prop)
{
console.log(prop)
var li = $('<li>').attr('id', 'rcmlitasklist'+prop.id)
.append('<input type="checkbox" name="_list[]" value="'+prop.id+'" checked="checked" />')
.append('<span class="handle">&nbsp;</span>')
.append('<span class="listname">'+Q(prop.name)+'</span>');
$(rcmail.gui_objects.folderlist).append(li);
init_tasklist_li(li.get(0), prop.id);
me.tasklists[prop.id] = prop;
}
/**
*
*/
function update_list(prop)
{
var id = prop.oldid || prop.id,
li = rcmail.get_folder_li(id, 'rcmlitasklist');
if (me.tasklists[id] && li) {
delete me.tasklists[id];
me.tasklists[prop.id] = prop;
$(li).data('id', prop.id);
$('#'+li.id+' input').data('id', prop.id);
$('.listname', li).html(Q(prop.name));
}
}
/**
* Execute search
*/
@ -828,10 +951,13 @@ console.log(replace)
*/
function clear_popups(e)
{
var count = 0;
var count = 0, target = e.target;
if (target && target.className == 'inner')
target = e.target.parentNode;
$('.popupmenu:visible').each(function(i, elem){
var menu = $(elem);
if (!menu.data('sticky') || !target_overlaps(e.target, elem)) {
var menu = $(elem), id = elem.id;
if (target.id != id+'link' && (!menu.data('sticky') || !target_overlaps(e.target, elem))) {
menu.hide();
count++;
}
@ -852,9 +978,58 @@ console.log(replace)
return false;
}
/**
*
*/
function active_lists()
{
var active = [];
for (var id in me.tasklists) {
if (me.tasklists[id].active)
active.push(id);
}
return active;
}
/**
* Register event handlers on a tasklist (folder) item
*/
function init_tasklist_li(li, id)
{
$('#'+li.id+' input').click(function(e){
var id = $(this).data('id');
if (me.tasklists[id]) { // add or remove event source on click
me.tasklists[id].active = this.checked;
fetch_counts();
list_tasks(null);
rcmail.http_post('tasklist', { action:'subscribe', l:{ id:id, active:me.tasklists[id].active?1:0 } });
}
}).data('id', id).get(0).checked = me.tasklists[id].active || false;
$(li).click(function(e){
var id = $(this).data('id');
rcmail.select_folder(id, 'rcmlitasklist');
rcmail.enable_command('list-edit', 'list-remove', 'import', !me.tasklists[id].readonly);
me.selected_list = id;
})
// .dblclick(function(){ list_edit_dialog(me.selected_list); })
.data('id', id);
}
}
// extend jQuery
(function($){
$.fn.serializeJSON = function(){
var json = {};
jQuery.map($(this).serializeArray(), function(n, i) {
json[n['name']] = n['value'];
});
return json;
};
})(jQuery);
/* tasklist plugin UI initialization */
var rctasks;
window.rcmail && rcmail.addEventListener('init', function(evt) {
@ -862,8 +1037,12 @@ window.rcmail && rcmail.addEventListener('init', function(evt) {
rctasks = new rcube_tasklist(rcmail.env.tasklist_settings);
// register button commands
//rcmail.register_command('addtask', function(){ tasks.add_task(); }, true);
//rcmail.register_command('print', function(){ tasks.print_list(); }, true);
//rcmail.register_command('addtask', function(){ rctasks.add_task(); }, true);
//rcmail.register_command('print', function(){ rctasks.print_list(); }, true);
rcmail.register_command('list-create', function(){ rctasks.list_edit_dialog(null); }, true);
rcmail.register_command('list-edit', function(){ rctasks.list_edit_dialog(rctasks.selected_list); }, false);
rcmail.register_command('list-remove', function(){ rctasks.list_remove(rctasks.selected_list); }, false);
rcmail.register_command('search', function(){ rctasks.quicksearch(); }, true);
rcmail.register_command('reset-search', function(){ rctasks.reset_search(); }, true);

View file

@ -273,14 +273,39 @@ class tasklist extends rcube_plugin
$list = get_input_value('l', RCUBE_INPUT_POST, true);
$success = false;
switch ($action) {
if (isset($list['showalarms']))
$list['showalarms'] = intval($list['showalarms']);
switch ($action) {
case 'new':
$list += array('showalarms' => true, 'active' => true);
if ($insert_id = $this->driver->create_list($list)) {
$list['id'] = $insert_id;
$this->rc->output->command('plugin.insert_tasklist', $list);
$success = true;
}
break;
case 'edit':
if ($newid = $this->driver->edit_list($list)) {
$list['oldid'] = $list['id'];
$list['id'] = $newid;
$this->rc->output->command('plugin.update_tasklist', $list);
$success = true;
}
break;
case 'subscribe':
$success = $this->driver->subscribe_list($list);
break;
}
if ($success)
$this->rc->output->show_message('successfullysaved', 'confirmation');
else
$this->rc->output->show_message('tasklist.errorsaving', 'error');
$this->rc->output->command('plugin.unlock_saving');
}
/**
@ -314,7 +339,7 @@ class tasklist extends rcube_plugin
$f = intval(get_input_value('filter', RCUBE_INPUT_GPC));
$search = get_input_value('q', RCUBE_INPUT_GPC);
$filter = array('mask' => $f, 'search' => $search);
$lists = null;
$lists = get_input_value('lists', RCUBE_INPUT_GPC);;
// convert magic date filters into a real date range
switch ($f) {
@ -347,10 +372,9 @@ class tasklist extends rcube_plugin
}
$data = $this->task_tree = $this->tasks_childs = array();
$data = $this->task_tree = $this->task_titles = array();
foreach ($this->driver->list_tasks($filter, $lists) as $rec) {
if ($rec['parent_id']) {
$this->tasks_childs[$rec['parent_id']]++;
$this->task_tree[$rec['id']] = $rec['parent_id'];
}
$this->encode_task($rec);
@ -391,17 +415,17 @@ class tasklist extends rcube_plugin
$rec['_hasdate'] = 0;
}
if ($this->tasks_childs[$rec['id']])
$rec['_haschilds'] = $this->tasks_childs[$rec['id']];
if (!isset($rec['_depth'])) {
$rec['_depth'] = 0;
$parent_id = $this->task_tree[$rec['id']];
while ($parent_id) {
$rec['_depth']++;
$rec['parent_title'] = $this->task_titles[$parent_id];
$parent_id = $this->task_tree[$parent_id];
}
}
$this->task_titles[$rec['id']] = $rec['title'];
}
/**

View file

@ -66,6 +66,7 @@ class tasklist_ui
$this->plugin->register_handler('plugin.category_select', array($this, 'category_select'));
$this->plugin->register_handler('plugin.searchform', array($this->rc->output, 'search_form'));
$this->plugin->register_handler('plugin.quickaddform', array($this, 'quickadd_form'));
$this->plugin->register_handler('plugin.tasklist_editform', array($this, 'tasklist_editform'));
$this->plugin->register_handler('plugin.tasks', array($this, 'tasks_resultview'));
$this->plugin->include_script('tasklist.js');
@ -108,7 +109,7 @@ class tasklist_ui
$class .= ' '.$prop['class_name'];
$li .= html::tag('li', array('id' => 'rcmlitasklist' . $html_id, 'class' => $class),
html::tag('input', array('type' => 'checkbox', 'name' => '_list[]', 'value' => $id, 'checked' => $prop['active'], 'disabled' => true)) .
html::tag('input', array('type' => 'checkbox', 'name' => '_list[]', 'value' => $id, 'checked' => $prop['active'])) .
html::span('handle', '&nbsp;') .
html::span('listname', Q($prop['name'])));
}
@ -135,6 +136,34 @@ class tasklist_ui
return $select->show(null);
}
function tasklist_editform($attrib = array())
{
$fields = array(
'name' => array(
'id' => 'edit-tasklistame',
'label' => $this->plugin->gettext('listname'),
'value' => html::tag('input', array('id' => 'edit-tasklistame', 'name' => 'name', 'type' => 'text', 'class' => 'text', 'size' => 40)),
),
/*
'color' => array(
'id' => 'edit-color',
'label' => $this->plugin->gettext('color'),
'value' => html::tag('input', array('id' => 'edit-color', 'name' => 'color', 'type' => 'text', 'class' => 'text colorpicker', 'size' => 6)),
),
'showalarms' => array(
'id' => 'edit-showalarms',
'label' => $this->plugin->gettext('showalarms'),
'value' => html::tag('input', array('id' => 'edit-showalarms', 'name' => 'color', 'type' => 'checkbox')),
),
*/
);
return html::tag('form', array('action' => "#", 'method' => "post", 'id' => 'tasklisteditform'),
$this->plugin->driver->tasklist_edit_form($fields)
);
}
/**
* Render a HTML select box to select a task category
*/