summaryrefslogtreecommitdiff
path: root/plugins/kolab_notes
diff options
context:
space:
mode:
authorThomas Bruederli <bruederli@kolabsys.com>2014-04-02 12:43:56 (GMT)
committerThomas Bruederli <bruederli@kolabsys.com>2014-04-02 12:43:56 (GMT)
commit684964282acc9ed379049610044630da9bb61245 (patch)
tree470d87337f86df4f28dab177f2c1cb7563b424a1 /plugins/kolab_notes
parent6b22e05444609d0dc089480bb4166cf11d2d0f69 (diff)
downloadroundcubemail-plugins-kolab-684964282acc9ed379049610044630da9bb61245.tar.gz
Implement notes folder management, including ACL settings
Diffstat (limited to 'plugins/kolab_notes')
-rw-r--r--plugins/kolab_notes/kolab_notes.php87
-rw-r--r--plugins/kolab_notes/kolab_notes_ui.php141
-rw-r--r--plugins/kolab_notes/localization/en_US.inc8
-rw-r--r--plugins/kolab_notes/notes.js156
-rw-r--r--plugins/kolab_notes/skins/larry/editor.css29
-rw-r--r--plugins/kolab_notes/skins/larry/notes.css18
-rw-r--r--plugins/kolab_notes/skins/larry/templates/kolabacl.html26
-rw-r--r--plugins/kolab_notes/skins/larry/templates/notes.html9
8 files changed, 466 insertions, 8 deletions
diff --git a/plugins/kolab_notes/kolab_notes.php b/plugins/kolab_notes/kolab_notes.php
index 065029a..2dd3321 100644
--- a/plugins/kolab_notes/kolab_notes.php
+++ b/plugins/kolab_notes/kolab_notes.php
@@ -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();
}
-
}
/**
@@ -517,6 +517,89 @@ class kolab_notes extends rcube_plugin
}
/**
+ * 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('&nbsp;&nbsp;&nbsp;', count($path_imap)) . '&raquo; ' . $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
*/
private function is_html($note)
diff --git a/plugins/kolab_notes/kolab_notes_ui.php b/plugins/kolab_notes/kolab_notes_ui.php
index 36e228d..b6bc95e 100644
--- a/plugins/kolab_notes/kolab_notes_ui.php
+++ b/plugins/kolab_notes/kolab_notes_ui.php
@@ -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'));
+ }
+
}
diff --git a/plugins/kolab_notes/localization/en_US.inc b/plugins/kolab_notes/localization/en_US.inc
index 5905bbb..62eab8c 100644
--- a/plugins/kolab_notes/localization/en_US.inc
+++ b/plugins/kolab_notes/localization/en_US.inc
@@ -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.';
+
diff --git a/plugins/kolab_notes/notes.js b/plugins/kolab_notes/notes.js
index 0ccff15..2176706 100644
--- a/plugins/kolab_notes/notes.js
+++ b/plugins/kolab_notes/notes.js
@@ -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;
+ }
+ }
+ }
}
/**
diff --git a/plugins/kolab_notes/skins/larry/editor.css b/plugins/kolab_notes/skins/larry/editor.css
new file mode 100644
index 0000000..943b118
--- /dev/null
+++ b/plugins/kolab_notes/skins/larry/editor.css
@@ -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%;
+}
diff --git a/plugins/kolab_notes/skins/larry/notes.css b/plugins/kolab_notes/skins/larry/notes.css
index a137b01..32a3792 100644
--- a/plugins/kolab_notes/skins/larry/notes.css
+++ b/plugins/kolab_notes/skins/larry/notes.css
@@ -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;
+} \ No newline at end of file
diff --git a/plugins/kolab_notes/skins/larry/templates/kolabacl.html b/plugins/kolab_notes/skins/larry/templates/kolabacl.html
new file mode 100644
index 0000000..ed9b0c7
--- /dev/null
+++ b/plugins/kolab_notes/skins/larry/templates/kolabacl.html
@@ -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>
diff --git a/plugins/kolab_notes/skins/larry/templates/notes.html b/plugins/kolab_notes/skins/larry/templates/notes.html
index 98a8eaf..b5ff0c7 100644
--- a/plugins/kolab_notes/skins/larry/templates/notes.html
+++ b/plugins/kolab_notes/skins/larry/templates/notes.html
@@ -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',