Implement notes folder management, including ACL settings

This commit is contained in:
Thomas Bruederli 2014-04-02 14:43:56 +02:00
parent 6b22e05444
commit 684964282a
8 changed files with 466 additions and 8 deletions

View file

@ -71,14 +71,14 @@ class kolab_notes extends rcube_plugin
$this->register_action('fetch', array($this, 'notes_fetch'));
$this->register_action('get', array($this, 'note_record'));
$this->register_action('action', array($this, 'note_action'));
$this->register_action('list', array($this, 'list_action'));
}
if (!$this->rc->output->ajax_call && !$this->rc->output->env['framed']) {
if (!$this->rc->output->ajax_call && (!$this->rc->output->env['framed'] || $args['action'] == 'folder-acl')) {
require_once($this->home . '/kolab_notes_ui.php');
$this->ui = new kolab_notes_ui($this);
$this->ui->init();
}
}
/**
@ -516,6 +516,89 @@ class kolab_notes extends rcube_plugin
return $folder->delete($note['uid'], $force);
}
/**
* Handler for client requests to list (aka folder) actions
*/
public function list_action()
{
$action = rcube_utils::get_input_value('_do', RCUBE_INPUT_GPC);
$list = rcube_utils::get_input_value('_list', RCUBE_INPUT_GPC, true);
$success = $update_cmd = false;
switch ($action) {
case 'form-new':
case 'form-edit':
$this->_read_lists();
echo $this->ui->list_editform($action, $this->lists[$list['id']], $this->folders[$list['id']]);
exit;
case 'new':
$list['type'] = 'note';
$list['subscribed'] = true;
$folder = kolab_storage::folder_update($list);
if ($folder === false) {
$save_error = $this->gettext(kolab_storage::$last_error);
}
else {
$success = true;
$update_cmd = 'plugin.update_list';
$list['id'] = kolab_storage::folder_id($folder);
$list['_reload'] = true;
}
break;
case 'edit':
$this->_read_lists();
$oldparent = $this->lists[$list['id']]['parentfolder'];
$newfolder = kolab_storage::folder_update($list);
if ($newfolder === false) {
$save_error = $this->gettext(kolab_storage::$last_error);
}
else {
$success = true;
$update_cmd = 'plugin.update_list';
$list['newid'] = kolab_storage::folder_id($newfolder);
$list['_reload'] = $list['parent'] != $oldparent;
// compose the new display name
$delim = $this->rc->get_storage()->get_hierarchy_delimiter();
$path_imap = explode($delim, $newfolder);
$list['name'] = kolab_storage::object_name($newfolder);
$list['editname'] = rcube_charset::convert(array_pop($path_imap), 'UTF7-IMAP');
$list['listname'] = str_repeat('   ', count($path_imap)) . '» ' . $list['editname'];
}
break;
case 'delete':
$this->_read_lists();
$folder = $this->folders[$list['id']];
if ($folder && kolab_storage::folder_delete($folder->name)) {
$success = true;
$update_cmd = 'plugin.destroy_list';
}
else {
$save_error = $this->gettext(kolab_storage::$last_error);
}
break;
}
$this->rc->output->command('plugin.unlock_saving');
if ($success) {
$this->rc->output->show_message('successfullysaved', 'confirmation');
if ($update_cmd) {
$this->rc->output->command($update_cmd, $list);
}
}
else {
$error_msg = $this->gettext('errorsaving') . ($save_error ? ': ' . $save_error :'');
$this->rc->output->show_message($error_msg, 'error');
}
}
/**
* Determine whether the given note is HTML formatted
*/

View file

@ -31,6 +31,8 @@ class kolab_notes_ui
$this->plugin->include_stylesheet($this->plugin->local_skin_path() . '/notes.css');
$this->plugin->register_action('folder-acl', array($this, 'folder_acl'));
$this->ready = true;
}
@ -58,6 +60,10 @@ class kolab_notes_ui
// TODO: load config options and user prefs relevant for the UI
$settings = array();
if (!empty($_REQUEST['_list'])) {
$settings['selected_list'] = rcube_utils::get_input_value('_list', RCUBE_INPUT_GPC);
}
// TinyMCE uses two-letter lang codes, with exception of Chinese
$lang = strtolower($_SESSION['language']);
$lang = strpos($lang, 'zh_') === 0 ? str_replace('_', '-', $lang) : substr($lang, 0, 2);
@ -74,6 +80,8 @@ class kolab_notes_ui
);
$this->rc->output->set_env('kolab_notes_settings', $settings);
$this->rc->output->add_label('save','cancel');
}
public function folders($attrib)
@ -162,5 +170,138 @@ class kolab_notes_ui
return html::div($attrib, $html);
}
/**
* Render edit for notes lists (folders)
*/
public function list_editform($action, $list, $folder)
{
if (is_object($folder)) {
$folder_name = $folder->name; // UTF7
}
else {
$folder_name = '';
}
$hidden_fields[] = array('name' => 'oldname', 'value' => $folder_name);
$storage = $this->rc->get_storage();
$delim = $storage->get_hierarchy_delimiter();
$form = array();
if (strlen($folder_name)) {
$options = $storage->folder_info($folder_name);
$path_imap = explode($delim, $folder_name);
array_pop($path_imap); // pop off name part
$path_imap = implode($path_imap, $delim);
}
else {
$path_imap = '';
$options = array();
}
// General tab
$form['properties'] = array(
'name' => $this->rc->gettext('properties'),
'fields' => array(),
);
// folder name (default field)
$input_name = new html_inputfield(array('name' => 'name', 'id' => 'noteslist-name', 'size' => 20));
$form['properties']['fields']['name'] = array(
'label' => $this->plugin->gettext('listname'),
'value' => $input_name->show($list['editname'], array('disabled' => ($options['norename'] || $options['protected']))),
'id' => 'folder-name',
);
// prevent user from moving folder
if (!empty($options) && ($options['norename'] || $options['protected'])) {
$hidden_fields[] = array('name' => 'parent', 'value' => $path_imap);
}
else {
$select = kolab_storage::folder_selector('note', array('name' => 'parent'), $folder_name);
$form['properties']['fields']['path'] = array(
'label' => $this->plugin->gettext('parentfolder'),
'value' => $select->show(strlen($folder_name) ? $path_imap : ''),
);
}
// add folder ACL tab
if ($action != 'form-new') {
$form['sharing'] = array(
'name' => Q($this->plugin->gettext('tabsharing')),
'content' => html::tag('iframe', array(
'src' => $this->rc->url(array('_action' => 'folder-acl', '_folder' => $folder_name, 'framed' => 1)),
'width' => '100%',
'height' => 280,
'border' => 0,
'style' => 'border:0'),
'')
);
}
$form_html = '';
if (is_array($hidden_fields)) {
foreach ($hidden_fields as $field) {
$hiddenfield = new html_hiddenfield($field);
$form_html .= $hiddenfield->show() . "\n";
}
}
// create form output
foreach ($form as $tab) {
if (is_array($tab['fields']) && empty($tab['content'])) {
$table = new html_table(array('cols' => 2));
foreach ($tab['fields'] as $col => $colprop) {
$colprop['id'] = '_'.$col;
$label = !empty($colprop['label']) ? $colprop['label'] : $this->plugin->gettext($col);
$table->add('title', html::label($colprop['id'], Q($label)));
$table->add(null, $colprop['value']);
}
$content = $table->show();
}
else {
$content = $tab['content'];
}
if (!empty($content)) {
$form_html .= html::tag('fieldset', null, html::tag('legend', null, Q($tab['name'])) . $content) . "\n";
}
}
return html::tag('form', array('action' => "#", 'method' => "post", 'id' => "noteslistpropform"), $form_html);
}
/**
* Handler to render ACL form for a notes folder
*/
public function folder_acl()
{
$this->plugin->require_plugin('acl');
$this->rc->output->add_handler('folderacl', array($this, 'folder_acl_form'));
$this->rc->output->send('kolab_notes.kolabacl');
}
/**
* Handler for ACL form template object
*/
public function folder_acl_form()
{
$folder = rcube_utils::get_input_value('_folder', RCUBE_INPUT_GPC);
if (strlen($folder)) {
$storage = $this->rc->get_storage();
$options = $storage->folder_info($folder);
// get sharing UI from acl plugin
$acl = $this->rc->plugins->exec_hook('folder_form',
array('form' => array(), 'options' => $options, 'name' => $folder));
}
return $acl['form']['sharing']['content'] ?: html::div('hint', $this->plugin->gettext('aclnorights'));
}
}

View file

@ -12,8 +12,16 @@ $labels['removetag'] = 'Remove tag';
$labels['created'] = 'Created';
$labels['changed'] = 'Last Modified';
$labels['now'] = 'Now';
$labels['createlist'] = 'New Notebook';
$labels['editlist'] = 'Edit Notebook';
$labels['listname'] = 'Name';
$labels['tabsharing'] = 'Sharing';
$labels['savingdata'] = 'Saving data...';
$labels['recordnotfound'] = 'Record not found';
$labels['entertitle'] = 'Please enter a title for this note!';
$labels['deletenotesconfirm'] = 'Do you really want to delete the selected notes?';
$labels['deletenotebookconfirm'] = 'Do you really want to delete this notebook with all its notes? This action cannot be undone.';
$labels['invalidlistproperties'] = 'Invalid notebook properties! Please set a valid name.';
$labels['aclnorights'] = 'You do not have administrator rights for this notebook.';

View file

@ -36,8 +36,8 @@ function rcube_kolab_notes_ui(settings)
var me = this;
/* public members */
this.selected_list;
this.selected_note;
this.selected_list = settings.selected_list;
this.selected_note = settings.selected_id;
this.notebooks = rcmail.env.kolab_notebooks || {};
/**
@ -47,7 +47,7 @@ function rcube_kolab_notes_ui(settings)
{
// register button commands
rcmail.register_command('createnote', function(){ edit_note(null, 'new'); }, false);
rcmail.register_command('list-create', function(){ list_edit_dialog(null); }, false);
rcmail.register_command('list-create', function(){ list_edit_dialog(null); }, true);
rcmail.register_command('list-edit', function(){ list_edit_dialog(me.selected_list); }, false);
rcmail.register_command('list-remove', function(){ list_remove(me.selected_list); }, false);
rcmail.register_command('save', save_note, true);
@ -59,6 +59,8 @@ function rcube_kolab_notes_ui(settings)
rcmail.addEventListener('plugin.data_ready', data_ready);
rcmail.addEventListener('plugin.render_note', render_note);
rcmail.addEventListener('plugin.update_note', update_note);
rcmail.addEventListener('plugin.update_list', list_update);
rcmail.addEventListener('plugin.destroy_list', list_destroy);
rcmail.addEventListener('plugin.unlock_saving', function(){
if (saving_lock) {
rcmail.set_busy(false, null, saving_lock);
@ -80,7 +82,8 @@ function rcube_kolab_notes_ui(settings)
id_prefix: 'rcmliknb',
selectable: true,
check_droptarget: function(node) {
return !node.virtual && node.id != me.selected_list;
var list = me.notebooks[node.id];
return !node.virtual && list.editable && node.id != me.selected_list;
}
});
notebookslist.addEventListener('select', function(node) {
@ -264,15 +267,156 @@ function rcube_kolab_notes_ui(settings)
*/
function list_edit_dialog(id)
{
if (!rcmail.gui_containers.notebookeditform) {
return false;
}
// close show dialog first
var $dialog = rcmail.gui_containers.notebookeditform;
if ($dialog.is(':ui-dialog')) {
$dialog.dialog('close');
}
var list = me.notebooks[id] || { name:'', editable:true };
var form, name;
$dialog.html(rcmail.get_label('loading'));
$.ajax({
type: 'GET',
dataType: 'html',
url: rcmail.url('list'),
data: { _do: (list.id ? 'form-edit' : 'form-new'), _list: { id: list.id } },
success: function(data) {
$dialog.html(data);
rcmail.triggerEvent('kolab_notes_editform_load', list);
// resize and reposition dialog window
form = $('#noteslistpropform');
var win = $(window), w = win.width(), h = win.height();
$dialog.dialog('option', { height: Math.min(h-20, form.height()+130), width: Math.min(w-20, form.width()+50) })
.dialog('option', 'position', ['center', 'center']); // only works in a separate call (!?)
name = $('#noteslist-name').prop('disabled', !list.editable).val(list.editname || list.name);
name.select();
}
});
// dialog buttons
var buttons = {};
buttons[rcmail.gettext('save')] = function() {
// form is not loaded
if (!form || !form.length)
return;
// do some input validation
if (!name.val() || name.val().length < 2) {
alert(rcmail.gettext('invalidlistproperties', 'kolab_notes'));
name.select();
return;
}
// post data to server
var data = form.serializeJSON();
if (list.id)
data.id = list.id;
saving_lock = rcmail.set_busy(true, 'kolab_notes.savingdata');
rcmail.http_post('list', { _do: (list.id ? 'edit' : 'new'), _list: data });
$dialog.dialog('close');
};
buttons[rcmail.gettext('cancel')] = function() {
$dialog.dialog('close');
};
// open jquery UI dialog
$dialog.dialog({
modal: true,
resizable: true,
closeOnEscape: false,
title: rcmail.gettext((list.id ? 'editlist' : 'createlist'), 'kolab_notes'),
open: function() {
$dialog.parent().find('.ui-dialog-buttonset .ui-button').first().addClass('mainaction');
},
close: function() {
$dialog.html('').dialog('destroy').hide();
},
buttons: buttons,
minWidth: 480,
width: 640,
}).show();
}
/**
* Callback from server after changing list properties
*/
function list_update(prop)
{
if (prop._reload) {
rcmail.redirect(rcmail.url('', { _list: (prop.newid || prop.id) }));
}
else if (prop.newid && prop.newid != prop.id) {
var book = $.extend({}, me.notebooks[prop.id]);
book.id = prop.newid;
book.name = prop.name;
book.listname = prop.listname;
book.editname = prop.editname || prop.name;
me.notebooks[prop.newid] = book;
delete me.notebooks[prop.id];
// update treelist item
var li = $(notebookslist.get_item(prop.id));
$('.listname', li).html(prop.listname);
notebookslist.update(prop.id, { id:book.id, html:li.html() });
// link all loaded note records to the new list id
if (me.selected_list == prop.id) {
me.selected_list = prop.newid;
for (var k in notesdata) {
if (notesdata[k].list == prop.id) {
notesdata[k].list = book.id;
}
}
notebookslist.select(prop.newid);
}
}
}
/**
*
*/
function list_remove(id)
{
var list = me.notebooks[id];
if (list && confirm(rcmail.gettext('deletenotebookconfirm', 'kolab_notes'))) {
saving_lock = rcmail.set_busy(true, 'kolab_notes.savingdata');
rcmail.http_post('list', { _do: 'delete', _list: { id: list.id } });
}
}
/**
* Callback from server on list delete command
*/
function list_destroy(prop)
{
if (!me.notebooks[prop.id]) {
return;
}
notebookslist.remove(prop.id);
delete me.notebooks[prop.id];
if (me.selected_list == prop.id) {
for (id in me.notebooks) {
if (me.notebooks[id]) {
notebookslist.select(id);
break;
}
}
}
}
/**

View file

@ -0,0 +1,29 @@
/* This file contains the CSS data for the editable area(iframe) of TinyMCE */
body, td, pre {
font-family: "Lucida Grande", Verdana, Arial, Helvetica, sans-serif;
font-size: 12px;
}
body {
background-color: #FFF;
margin: 6px;
}
pre
{
margin: 0;
padding: 0;
white-space: -moz-pre-wrap !important;
white-space: pre-wrap !important;
white-space: pre;
word-wrap: break-word; /* IE (and Safari) */
}
blockquote
{
padding-left: 5px;
border-left: #1010ff 2px solid;
margin-left: 5px;
width: 100%;
}

View file

@ -83,6 +83,8 @@
.notesview #kolabnoteslist .title {
display: block;
padding: 4px 8px;
overflow: hidden;
text-overflow: ellipsis;
}
.notesview #kolabnoteslist .date {
@ -303,3 +305,19 @@
.notesview #notebooks li.selected > a {
background-color: transparent;
}
.notesview .uidialog .tabbed {
margin-top: -12px;
}
.notesview .uidialog .propform fieldset.tab {
display: block;
background: #efefef;
margin-top: 0.5em;
padding: 0.5em 1em;
min-height: 290px;
}
.notesview .uidialog .propform #noteslist-name {
width: 20em;
}

View file

@ -0,0 +1,26 @@
<roundcube:object name="doctype" value="html5" />
<html>
<head>
<title><roundcube:object name="pagetitle" /></title>
<roundcube:include file="/includes/links.html" />
<style type="text/css" media="screen">
body.aclform {
background: #efefef;
margin: 0;
}
body.aclform .hint {
margin: 1em;
}
</style>
</head>
<body class="iframe aclform">
<roundcube:object name="folderacl" />
<roundcube:include file="/includes/footer.html" />
</body>
</html>

View file

@ -74,6 +74,11 @@
</ul>
</div>
<div id="notebookeditform" class="uidialog">
<roundcube:container name="notebookeditform" id="notebookeditform" />
<roundcube:label name="loading" />
</div>
<script type="text/javascript">
@ -83,6 +88,10 @@ var UI = new rcube_mail_ui();
$(document).ready(function(e){
UI.init();
rcmail.addEventListener('kolab_notes_editform_load', function(e){
UI.init_tabs($('#notebookeditform > form').addClass('propform tabbed'));
})
new rcube_splitter({ id:'notesviewsplitter', p1:'#sidebar', p2:'#mainview-right',
orientation:'v', relative:true, start:240, min:180, size:16, offset:2, render:layout_view }).init();
new rcube_splitter({ id:'noteslistsplitter2', p1:'#noteslistbox', p2:'#notedetailsbox',