Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F117759450
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
84 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/plugins/kolab_notes/kolab_notes_ui.php b/plugins/kolab_notes/kolab_notes_ui.php
index fa2fe5a7..143bc54b 100644
--- a/plugins/kolab_notes/kolab_notes_ui.php
+++ b/plugins/kolab_notes/kolab_notes_ui.php
@@ -1,352 +1,353 @@
<?php
class kolab_notes_ui
{
private $rc;
private $plugin;
private $ready = false;
function __construct($plugin)
{
$this->plugin = $plugin;
$this->rc = $plugin->rc;
}
/**
* Calendar UI initialization and requests handlers
*/
public function init()
{
if ($this->ready) // already done
return;
// add taskbar button
$this->plugin->add_button(array(
'command' => 'notes',
'class' => 'button-notes',
'classsel' => 'button-notes button-selected',
'innerclass' => 'button-inner',
'label' => 'kolab_notes.navtitle',
), 'taskbar');
$this->plugin->include_stylesheet($this->plugin->local_skin_path() . '/notes.css');
$this->plugin->register_action('print', array($this, 'print_template'));
$this->plugin->register_action('folder-acl', array($this, 'folder_acl'));
$this->ready = true;
}
/**
* Register handler methods for the template engine
*/
public function init_templates()
{
$this->plugin->register_handler('plugin.tagslist', array($this, 'tagslist'));
$this->plugin->register_handler('plugin.notebooks', array($this, 'folders'));
#$this->plugin->register_handler('plugin.folders_select', array($this, 'folders_select'));
$this->plugin->register_handler('plugin.searchform', array($this->rc->output, 'search_form'));
$this->plugin->register_handler('plugin.listing', array($this, 'listing'));
$this->plugin->register_handler('plugin.editform', array($this, 'editform'));
$this->plugin->register_handler('plugin.notetitle', array($this, 'notetitle'));
$this->plugin->register_handler('plugin.detailview', array($this, 'detailview'));
$this->plugin->register_handler('plugin.attachments_list', array($this, 'attachments_list'));
$this->rc->output->include_script('list.js');
$this->rc->output->include_script('treelist.js');
$this->plugin->include_script('notes.js');
$this->plugin->include_script('jquery.tagedit.js');
$this->plugin->include_stylesheet($this->plugin->local_skin_path() . '/tagedit.css');
// load config options and user prefs relevant for the UI
$settings = array(
'sort_col' => $this->rc->config->get('kolab_notes_sort_col', 'changed'),
'print_template' => $this->rc->url('print'),
);
if ($list = rcube_utils::get_input_value('_list', RCUBE_INPUT_GPC)) {
$settings['selected_list'] = $list;
}
if ($uid = rcube_utils::get_input_value('_id', RCUBE_INPUT_GPC)) {
$settings['selected_uid'] = $uid;
}
$lang_codes = array($_SESSION['language']);
if ($pos = strpos($_SESSION['language'], '_')) {
$lang_codes[] = substr($_SESSION['language'], 0, $pos);
}
foreach ($lang_codes as $code) {
if (file_exists(INSTALL_PATH . "program/js/tinymce/langs/$code.js")) {
$lang = $code;
break;
}
}
if (empty($lang)) {
$lang = 'en';
}
$settings['editor'] = array(
'lang' => $lang,
'spellcheck' => intval($this->rc->config->get('enable_spellcheck')),
'spelldict' => intval($this->rc->config->get('spellcheck_dictionary'))
);
$this->rc->output->set_env('kolab_notes_settings', $settings);
$this->rc->output->add_label('save','cancel');
}
public function folders($attrib)
{
$attrib += array('id' => 'rcmkolabnotebooks');
if ($attrib['type'] == 'select') {
$attrib['is_escaped'] = true;
$select = new html_select($attrib);
}
$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 ($attrib['type'] == 'select') {
if ($prop['editable']) {
$select->add($prop['name'], $prop['id']);
}
}
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::span(array('class' => 'listname', 'title' => $title), $prop['listname']) .
+ 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);
}
public function listing($attrib)
{
$attrib += array('id' => 'rcmkolabnoteslist');
$this->rc->output->add_gui_object('noteslist', $attrib['id']);
return html::tag('ul', $attrib, '', html::$common_attrib);
}
public function tagslist($attrib)
{
$attrib += array('id' => 'rcmkolabnotestagslist');
$this->rc->output->add_gui_object('notestagslist', $attrib['id']);
return html::tag('ul', $attrib, '', html::$common_attrib);
}
public function editform($attrib)
{
$attrib += array('action' => '#', 'id' => 'rcmkolabnoteseditform');
$this->rc->output->add_gui_object('noteseditform', $attrib['id']);
$this->rc->output->include_script('tinymce/tinymce.min.js');
- $textarea = new html_textarea(array('name' => 'content', 'id' => 'notecontent', 'cols' => 60, 'rows' => 20, 'tabindex' => 3));
+ $textarea = new html_textarea(array('name' => 'content', 'id' => 'notecontent', 'cols' => 60, 'rows' => 20, 'tabindex' => 0));
return html::tag('form', $attrib, $textarea->show(), array_merge(html::$common_attrib, array('action')));
}
public function detailview($attrib)
{
$attrib += array('id' => 'rcmkolabnotesdetailview');
$this->rc->output->add_gui_object('notesdetailview', $attrib['id']);
return html::div($attrib, '');
}
public function notetitle($attrib)
{
$attrib += array('id' => 'rcmkolabnotestitle');
$this->rc->output->add_gui_object('noteviewtitle', $attrib['id']);
- $summary = new html_inputfield(array('name' => 'summary', 'class' => 'notetitle inline-edit', 'size' => 60, 'tabindex' => 1));
+ $summary = new html_inputfield(array('name' => 'summary', 'class' => 'notetitle inline-edit', 'size' => 60, 'tabindex' => 0));
$html = $summary->show();
$html .= html::div(array('class' => 'tagline tagedit', 'style' => 'display:none'), ' ');
$html .= html::div(array('class' => 'dates', 'style' => 'display:none'),
html::label(array(), $this->plugin->gettext('created')) .
html::span('notecreated', '') .
html::label(array(), $this->plugin->gettext('changed')) .
html::span('notechanged', '')
);
return html::div($attrib, $html);
}
public function attachments_list($attrib)
{
$attrib += array('id' => 'rcmkolabnotesattachmentslist');
$this->rc->output->add_gui_object('notesattachmentslist', $attrib['id']);
return html::tag('ul', $attrib, '', html::$common_attrib);
}
/**
* 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',
+ 'id' => 'noteslist-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);
+ $select = kolab_storage::folder_selector('note', array('name' => 'parent', 'id' => 'parent-folder'), $folder_name);
$form['properties']['fields']['path'] = array(
'label' => $this->plugin->gettext('parentfolder'),
'value' => $select->show(strlen($folder_name) ? $path_imap : ''),
+ 'id' => 'parent-folder',
);
}
// 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'));
}
/**
* Render the template for printing with placeholders
*/
public function print_template()
{
$this->rc->output->reset(true);
echo $this->rc->output->parse('kolab_notes.print', false, false);
exit;
}
}
diff --git a/plugins/kolab_notes/localization/en_US.inc b/plugins/kolab_notes/localization/en_US.inc
index 71a26cd4..74e65377 100644
--- a/plugins/kolab_notes/localization/en_US.inc
+++ b/plugins/kolab_notes/localization/en_US.inc
@@ -1,48 +1,50 @@
<?php
$labels = array();
$labels['navtitle'] = 'Notes';
$labels['tags'] = 'Tags';
$labels['lists'] = 'Notebooks';
$labels['notes'] = 'Notes';
$labels['create'] = 'New Note';
$labels['createnote'] = 'Create a new note';
$labels['send'] = 'Send';
$labels['sendnote'] = 'Send note by email';
$labels['newnote'] = 'New Note';
$labels['notags'] = 'No tags';
$labels['removetag'] = 'Remove tag';
$labels['created'] = 'Created';
$labels['changed'] = 'Last Modified';
$labels['title'] = 'Title';
$labels['now'] = 'Now';
$labels['sortby'] = 'Sort by';
$labels['newnotebook'] = 'Create a new notebook';
$labels['addnotebook'] = 'Add notebook';
$labels['editlist'] = 'Edit Notebook';
$labels['listname'] = 'Name';
$labels['tabsharing'] = 'Sharing';
$labels['discard'] = 'Discard';
$labels['abort'] = 'Abort';
$labels['unsavedchanges'] = 'Unsaved Changes!';
$labels['appendnote'] = 'Add a Note';
$labels['editnote'] = 'Edit Note';
$labels['savein'] = 'Save in';
$labels['savingdata'] = 'Saving data...';
$labels['recordnotfound'] = 'Record not found';
+$labels['norecordsfound'] = 'No notes found';
$labels['nochanges'] = 'No changes to be saved';
$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['discardunsavedchanges'] = 'The current note has not yet been saved. Discard the changes?';
$labels['invalidlistproperties'] = 'Invalid notebook properties! Please set a valid name.';
$labels['entertitle'] = 'Please enter a title for this note.';
$labels['aclnorights'] = 'You do not have administrator rights for this notebook.';
$labels['arialabelnoteslist'] = 'List of notes';
$labels['arialabelnotesearchform'] = 'Notes search form';
$labels['arialabelnotesquicksearchbox'] = 'Notes search input';
$labels['arialabelnotessortmenu'] = 'Notes list sorting options';
$labels['arialabelnotesoptionsmenu'] = 'Notebook actions menu';
$labels['arialabelnotebookform'] = 'Notebook properties';
+$labels['arialabelmessagereferences'] = 'Linked email messages';
diff --git a/plugins/kolab_notes/notes.js b/plugins/kolab_notes/notes.js
index c52e0b92..5f29ba46 100644
--- a/plugins/kolab_notes/notes.js
+++ b/plugins/kolab_notes/notes.js
@@ -1,1348 +1,1375 @@
/**
* Client scripts for the Kolab Notes plugin
*
* @author Thomas Bruederli <bruederli@kolabsys.com>
*
* @licstart The following is the entire license notice for the
* JavaScript code in this file.
*
* Copyright (C) 2014, Kolab Systems AG <contact@kolabsys.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @licend The above is the entire license notice
* for the JavaScript code in this file.
*/
function rcube_kolab_notes_ui(settings)
{
/* private vars */
var ui_loading = false;
var saving_lock;
var search_query;
var folder_drop_target;
var notebookslist;
var noteslist;
var notesdata = {};
var tagsfilter = [];
var tags = [];
var search_request;
var search_query;
var tag_draghelper;
+ var render_no_focus;
var me = this;
/* public members */
this.selected_list;
this.selected_note;
this.notebooks = rcmail.env.kolab_notebooks || {};
/**
* initialize the notes UI
*/
function init()
{
// register button commands
rcmail.register_command('createnote', function(){
warn_unsaved_changes(function(){ edit_note(null, 'new'); })
}, 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('list-sort', list_set_sort, true);
rcmail.register_command('save', save_note, true);
rcmail.register_command('delete', delete_notes, false);
rcmail.register_command('search', quicksearch, true);
rcmail.register_command('reset-search', reset_search, true);
rcmail.register_command('sendnote', send_note, false);
rcmail.register_command('print', print_note, false);
// register server callbacks
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);
}
if (rcmail.gui_objects.noteseditform) {
rcmail.lock_form(rcmail.gui_objects.noteseditform, false);
}
});
// 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))) {
settings.selected_list = id;
}
}
notebookslist = new rcube_treelist_widget(rcmail.gui_objects.notebooks, {
id_prefix: 'rcmliknb',
selectable: true,
check_droptarget: function(node) {
var list = me.notebooks[node.id];
return !node.virtual && list.editable && node.id != me.selected_list;
}
});
notebookslist.addEventListener('select', function(node) {
var id = node.id;
if (me.notebooks[id] && id != me.selected_list) {
warn_unsaved_changes(function(){
rcmail.enable_command('createnote', 'list-edit', 'list-remove', me.notebooks[id].editable);
fetch_notes(id); // sets me.selected_list
},
function(){
// restore previous selection
notebookslist.select(me.selected_list);
});
}
});
+ $(rcmail.gui_objects.notebooks).on('click', 'li a', function(e) {
+ var id = String($(this).closest('li').attr('id')).replace(/^rcmliknb/, '');
+ notebookslist.select(id);
+ e.preventDefault();
+ return false;
+ });
+
// register dbl-click handler to open list edit dialog
- $(rcmail.gui_objects.notebooks).on('dblclick', 'li:not(.virtual)', function(e){
- var id = String(this.id).replace(/^rcmliknb/, '');
+ $(rcmail.gui_objects.notebooks).on('dblclick', 'li:not(.virtual) a', function(e) {
+ var id = String($(this).closest('li').attr('id')).replace(/^rcmliknb/, '');
if (me.notebooks[id] && me.notebooks[id].editable) {
list_edit_dialog(id);
}
// clear text selection (from dbl-clicking)
var sel = window.getSelection ? window.getSelection() : document.selection;
if (sel && sel.removeAllRanges) {
sel.removeAllRanges();
}
else if (sel && sel.empty) {
sel.empty();
}
e.preventDefault();
return false;
});
// initialize notes list widget
if (rcmail.gui_objects.noteslist) {
noteslist = new rcube_list_widget(rcmail.gui_objects.noteslist,
{ multiselect:true, draggable:true, keyboard:true });
noteslist.addEventListener('select', function(list) {
+ render_no_focus = rcube_event._last_keyboard_event && $(list.list).has(rcube_event._last_keyboard_event.target);
var selection_changed = list.selection.length != 1 || !me.selected_note || list.selection[0] != me.selected_note.id;
selection_changed && warn_unsaved_changes(function(){
var note;
if (noteslist.selection.length == 1 && (note = notesdata[noteslist.selection[0]])) {
edit_note(note.uid, 'edit');
}
else {
reset_view();
}
},
function(){
// TODO: previous restore selection
list.select(me.selected_note.id);
});
rcmail.enable_command('delete', me.notebooks[me.selected_list] && me.notebooks[me.selected_list].editable && list.selection.length > 0);
rcmail.enable_command('sendnote', list.selection.length > 0);
rcmail.enable_command('print', list.selection.length == 1);
})
.addEventListener('dragstart', function(e) {
folder_drop_target = null;
notebookslist.drag_start();
})
.addEventListener('dragmove', function(e) {
folder_drop_target = notebookslist.intersects(rcube_event.get_mouse_pos(e), true);
})
.addEventListener('dragend', function(e) {
notebookslist.drag_end();
// move dragged notes to this folder
if (folder_drop_target) {
noteslist.draglayer.hide();
move_notes(folder_drop_target);
noteslist.clear_selection();
reset_view();
}
folder_drop_target = null;
})
.init().focus();
}
if (settings.sort_col) {
$('#notessortmenu a.by-' + settings.sort_col).addClass('selected');
}
// click-handler on tags list
- $(rcmail.gui_objects.notestagslist).on('click', function(e){
+ $(rcmail.gui_objects.notestagslist).on('click', 'li', function(e){
var item = e.target.nodeName == 'LI' ? $(e.target) : $(e.target).closest('li'),
tag = item.data('value');
if (!tag)
return false;
// reset selection on regular clicks
var index = $.inArray(tag, tagsfilter);
var shift = e.shiftKey || e.ctrlKey || e.metaKey;
if (!shift) {
if (tagsfilter.length > 1)
index = -1;
- $('li', this).removeClass('selected');
+ $('li', rcmail.gui_objects.notestagslist).removeClass('selected').attr('aria-checked', 'false');
tagsfilter = [];
}
// add tag to filter
if (index < 0) {
- item.addClass('selected');
+ item.addClass('selected').attr('aria-checked', 'true');
tagsfilter.push(tag);
}
else if (shift) {
- item.removeClass('selected');
+ item.removeClass('selected').attr('aria-checked', 'false');
var a = tagsfilter.slice(0,index);
tagsfilter = a.concat(tagsfilter.slice(index+1));
}
filter_notes();
// clear text selection in IE after shift+click
if (shift && document.selection)
document.selection.empty();
e.preventDefault();
return false;
})
+ .on('keypress', 'li', function(e) {
+ if (e.keyCode == 13) {
+ $(this).trigger('click', { pointerType:'keyboard' });
+ }
+ })
.mousedown(function(e){
// disable content selection with the mouse
e.preventDefault();
return false;
});
init_editor();
if (settings.selected_list) {
notebookslist.select(settings.selected_list)
}
}
this.init = init;
/**
*
*/
function init_dialog()
{
rcmail.register_command('save', save_note, true);
rcmail.addEventListener('plugin.render_note', render_note);
rcmail.addEventListener('plugin.update_note', function(data){
data.id = rcmail.html_identifier_encode(data.uid);
notesdata[data.id] = data;
render_note(data);
});
rcmail.addEventListener('plugin.unlock_saving', function(){
if (saving_lock) {
rcmail.set_busy(false, null, saving_lock);
}
if (rcmail.gui_objects.noteseditform) {
rcmail.lock_form(rcmail.gui_objects.noteseditform, false);
}
});
var id;
for (id in me.notebooks) {
if (me.notebooks[id].editable) {
me.selected_list = id;
break;
}
}
init_editor();
if (settings.selected_uid) {
me.selected_list = settings.selected_list;
edit_note(settings.selected_uid);
}
else {
setTimeout(function(){
me.selected_note = $.extend({
list: me.selected_list,
uid: null,
title: rcmail.gettext('newnote','kolab_notes'),
description: '',
categories: [],
created: rcmail.gettext('now', 'kolab_notes'),
changed: rcmail.gettext('now', 'kolab_notes')
}, rcmail.env.kolab_notes_template || {});
render_note(me.selected_note);
}, 100);
}
}
this.init_dialog = init_dialog;
/**
* initialize tinyMCE editor
*/
function init_editor()
{
var editor_conf = {
selector: '#notecontent',
theme: 'modern',
language: settings.editor.lang,
content_css: 'program/js/tinymce/roundcube/content.css?v1',
plugins: 'autolink charmap code link paste tabfocus searchreplace table textcolor',
toolbar: 'bold italic underline | alignleft aligncenter alignright alignjustify '
+ '| bullist numlist outdent indent blockquote | forecolor backcolor fontselect fontsizeselect '
+ '| link unlink table charmap | code searchreplace undo redo',
menubar: false,
statusbar: false,
toolbar_items_size: 'small',
extended_valid_elements: 'font[face|size|color|style],span[id|class|align|style]',
relative_urls: false,
remove_script_host: false,
convert_urls: false,
image_description: false,
paste_webkit_style: "color font-size font-family",
paste_data_images: true,
//spellchecker_rpc_url: '../../../../../?_task=utils&_action=spell_html&_remote=1',
//spellchecker_language: rcmail.env.spell_lang,
accessibility_focus: false,
+ tabfocus_elements: [':prev','btn-save-note'],
setup: function(ed) {
// make links open on shift-click
ed.on('click', function(e) {
var link = $(e.target).closest('a');
if (link.length && e.shiftKey) {
if (!bw.mz) window.open(link.get(0).href, '_blank');
return false;
}
});
}
};
// support external configuration settings e.g. from skin
if (window.rcmail_editor_settings)
$.extend(editor_conf, window.rcmail_editor_settings);
tinyMCE.init(editor_conf);
// register click handler for message links
$(rcmail.gui_objects.notesattachmentslist).on('click', 'li a.messagelink', function(){
rcmail.open_window(this.href);
return false;
});
}
/**
* Quote HTML entities
*/
function Q(str)
{
return String(str).replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
}
/**
* Trim whitespace off the given string
*/
function trim(str)
{
return String(str).replace(/\s+$/, '').replace(/^\s+/, '');
}
/**
*
*/
function edit_note(uid, action)
{
if (!uid) {
if (noteslist)
noteslist.clear_selection();
me.selected_note = {
list: me.selected_list,
uid: null,
title: rcmail.gettext('newnote','kolab_notes'),
description: '',
categories: [],
created: rcmail.gettext('now', 'kolab_notes'),
changed: rcmail.gettext('now', 'kolab_notes')
}
render_note(me.selected_note);
rcmail.enable_command('print', true);
}
else {
ui_loading = rcmail.set_busy(true, 'loading');
rcmail.http_request('get', { _list:me.selected_list, _id:uid }, true);
}
}
/**
*
*/
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'),
+ title: rcmail.gettext((list.id ? 'editlist' : 'newnotebook'), '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;
}
}
}
}
/**
* Change notes list sort order
*/
function list_set_sort(col)
{
if (settings.sort_col != col) {
settings.sort_col = col;
$('#notessortmenu a').removeClass('selected').filter('.by-' + col).addClass('selected');
rcmail.save_pref({ name: 'kolab_notes_sort_col', value: col });
// re-sort table in DOM
$(noteslist.tbody).children().sortElements(function(la, lb){
var a_id = String(la.id).replace(/^rcmrow/, ''),
b_id = String(lb.id).replace(/^rcmrow/, ''),
a = notesdata[a_id],
b = notesdata[b_id];
if (!a || !b) {
return 0;
}
else if (settings.sort_col == 'title') {
return String(a.title).toLowerCase() > String(b.title).toLowerCase() ? 1 : -1;
}
else {
return b.changed_ - a.changed_;
}
});
}
}
/**
* Execute search
*/
function quicksearch()
{
var q;
if (rcmail.gui_objects.qsearchbox && (q = rcmail.gui_objects.qsearchbox.value)) {
var id = 'search-'+q;
// ignore if query didn't change
if (search_request == id)
return;
warn_unsaved_changes(function(){
search_request = id;
search_query = q;
fetch_notes();
},
function(){
reset_search();
});
}
else { // empty search input equals reset
reset_search();
}
}
/**
* Reset search and get back to normal listing
*/
function reset_search()
{
$(rcmail.gui_objects.qsearchbox).val('');
if (search_request) {
search_request = search_query = null;
fetch_notes();
}
}
/**
*
*/
function fetch_notes(id)
{
if (rcmail.busy)
return;
if (id && id != me.selected_list) {
me.selected_list = id;
}
ui_loading = rcmail.set_busy(true, 'loading');
rcmail.http_request('fetch', { _list:me.selected_list, _q:search_query }, true);
reset_view();
noteslist.clear(true);
notesdata = {};
tagsfilter = [];
update_state();
}
function filter_notes()
{
// tagsfilter
var note, tr, match;
for (var id in noteslist.rows) {
tr = noteslist.rows[id].obj;
note = notesdata[id];
match = note.categories && note.categories.length;
for (var i=0; match && note && i < tagsfilter.length; i++) {
if ($.inArray(tagsfilter[i], note.categories) < 0)
match = false;
}
if (match || !tagsfilter.length) {
$(tr).show();
}
else {
$(tr).hide();
}
if (me.selected_note && me.selected_note.uid == note.uid && !match) {
warn_unsaved_changes(function(){
me.selected_note = null;
noteslist.clear_selection();
}, function(){
tagsfilter = [];
filter_notes();
update_tagcloud();
});
}
}
}
/**
*
*/
function data_ready(data)
{
data.data.sort(function(a,b){
if (settings.sort_col == 'title') {
return String(a.title).toLowerCase() > String(b.title).toLowerCase() ? 1 : -1;
}
else {
return b.changed_ - a.changed_;
}
});
var i, id, rec;
for (i=0; data.data && i < data.data.length; i++) {
rec = data.data[i];
rec.id = rcmail.html_identifier_encode(rec.uid);
noteslist.insert_row({
id: 'rcmrow' + rec.id,
cols: [
{ className:'title', innerHTML:Q(rec.title) },
{ className:'date', innerHTML:Q(rec.changed || '') }
]
});
notesdata[rec.id] = rec;
}
render_tagslist(data.tags || [], !data.search)
rcmail.set_busy(false, 'loading', ui_loading);
// select the single result
if (data.data.length == 1) {
noteslist.select(data.data[0].id);
}
else if (settings.selected_uid) {
noteslist.select(rcmail.html_identifier_encode(settings.selected_uid));
delete settings.selected_uid;
}
else if (me.selected_note && notesdata[me.selected_note.id]) {
noteslist.select(me.selected_note.id);
}
+ else if (!data.data.length) {
+ console.log(data);
+ rcmail.display_message(rcmail.gettext('norecordsfound','kolab_notes'), 'info');
+ }
}
/**
*
*/
function render_note(data)
{
rcmail.set_busy(false, 'loading', ui_loading);
if (!data) {
rcmail.display_message(rcmail.get_label('recordnotfound', 'kolab_notes'), 'error');
return;
}
var list = me.notebooks[data.list] || me.notebooks[me.selected_list] || {};
content = $('#notecontent').val(data.description),
readonly = data.readonly || !list.editable,
attachmentslist = $(rcmail.gui_objects.notesattachmentslist).html('');
$('.notetitle', rcmail.gui_objects.noteviewtitle).val(data.title).prop('disabled', readonly);
$('.dates .notecreated', rcmail.gui_objects.noteviewtitle).html(Q(data.created || ''));
$('.dates .notechanged', rcmail.gui_objects.noteviewtitle).html(Q(data.changed || ''));
$(rcmail.gui_objects.notebooks).filter('select').val(list.id);
if (data.created || data.changed) {
$('.dates', rcmail.gui_objects.noteviewtitle).show();
}
// tag-edit line
var tagline = $('.tagline', rcmail.gui_objects.noteviewtitle).empty().show();
$.each(typeof data.categories == 'object' && data.categories.length ? data.categories : [''], function(i,val){
$('<input>')
.attr('name', 'tags[]')
- .attr('tabindex', '2')
+ .attr('tabindex', '0')
.addClass('tag')
.val(val)
.appendTo(tagline);
});
if (!data.categories || !data.categories.length) {
$('<span>').addClass('placeholder').html(rcmail.gettext('notags', 'kolab_notes')).appendTo(tagline);
}
$('.tagline input.tag', rcmail.gui_objects.noteviewtitle).tagedit({
animSpeed: 100,
allowEdit: false,
allowAdd: !readonly,
allowDelete: !readonly,
checkNewEntriesCaseSensitive: false,
autocompleteOptions: { source: tags, minLength: 0, noCheck: true },
texts: { removeLinkTitle: rcmail.gettext('removetag', 'kolab_notes') }
});
if (data.links) {
$.each(data.links, function(i, link){
var li = $('<li>').addClass('link')
.addClass('message eml')
.append($('<a>')
.attr('href', link.mailurl)
.addClass('messagelink')
.html(Q(link.subject || link.message_id || link.uri))
)
.appendTo(attachmentslist);
/*
if (!readonly && !data._from_mail) {
$('<a>')
.attr('href', '#delete')
.attr('title', rcmail.gettext('delete'))
.addClass('delete')
.html(rcmail.gettext('delete'))
.appendTo(li);
}
*/
});
}
if (!readonly) {
$('.tagedit-list', rcmail.gui_objects.noteviewtitle)
.on('click', function(){ $('.tagline .placeholder').hide(); });
}
if (!data.list)
data.list = list.id;
me.selected_note = data;
me.selected_note.id = rcmail.html_identifier_encode(data.uid);
rcmail.enable_command('save', list.editable && !data.readonly);
var html = data.html || data.description;
// convert plain text to HTML and make URLs clickable
if (!data.html || !html.match(/<(html|body)/)) {
html = text2html(html);
}
var node, editor = tinyMCE.get('notecontent'), is_html = false;
if (!readonly && editor) {
$(rcmail.gui_objects.notesdetailview).hide();
$(rcmail.gui_objects.noteseditform).show();
editor.setContent(html);
node = editor.getContentAreaContainer().childNodes[0];
if (node) node.tabIndex = content.get(0).tabIndex;
- if (me.selected_note.uid)
- editor.getBody().focus();
+ if (me.selected_note.uid) {
+ if (!render_no_focus)
+ editor.getBody().focus();
+ }
else
$('.notetitle', rcmail.gui_objects.noteviewtitle).focus().select();
// read possibly re-formatted content back from editor for later comparison
me.selected_note.description = editor.getContent({ format:'html' });
is_html = true;
}
else {
$(rcmail.gui_objects.noteseditform).hide();
$(rcmail.gui_objects.notesdetailview).html(html).show();
}
+ render_no_focus = false;
+
// notify subscribers
rcmail.triggerEvent('kolab_notes_render', { data:data, readonly:readonly, html:is_html });
if (rcmail.is_framed())
parent.rcmail.triggerEvent('kolab_notes_render', { data:data, readonly:readonly, html:is_html });
// Trigger resize (needed for proper editor resizing)
$(window).resize();
}
/**
* Convert the given plain text to HTML contents to be displayed in editor
*/
function text2html(str)
{
// simple link parser (similar to rcube_string_replacer class in PHP)
var utf_domain = '[^?&@"\'/\\(\\)\\s\\r\\t\\n]+\\.([^\x00-\x2f\x3b-\x40\x5b-\x60\x7b-\x7f]{2,}|xn--[a-z0-9]{2,})',
url1 = '.:;,', url2 = 'a-z0-9%=#@+?&/_~\\[\\]-',
link_pattern = new RegExp('([hf]t+ps?://|www.)('+utf_domain+'(['+url1+']?['+url2+']+)*)?', 'ig'),
link_replace = function(matches, p1, p2) {
var url = (p1 == 'www.' ? 'http://' : '') + p1 + p2;
return '<a href="' + url + '" class="x-templink">' + p1 + p2 + '</a>';
};
return '<pre>' + Q(str).replace(link_pattern, link_replace) + '</pre>';
}
/**
* Open a new window to print the currently selected note
*/
function print_note()
{
var printwin, data;
if (me.selected_note && (printwin = rcmail.open_window(settings.print_template))) {
data = get_save_data();
$(printwin).load(function(){
printwin.document.title = data.title;
$('#notetitle', printwin.document).html(Q(data.title));
$('#notebody', printwin.document).html(data.description);
$('#notetags', printwin.document).html('<span class="tag">' + data.categories.join('</span><span class="tag">') + '</span>');
$('#notecreated', printwin.document).html(Q(me.selected_note.created));
$('#notechanged', printwin.document).html(Q(me.selected_note.changed));
printwin.print();
});
}
}
/**
* Redirect to message compose screen with UIDs of notes to be appended
*/
function send_note()
{
var uids = [];
for (var rec, i=0; i < noteslist.selection.length; i++) {
if (rec = notesdata[noteslist.selection[i]]) {
uids.push(rec.uid);
// TODO: check if rec.uid == me.selected_note.uid and unsaved changes
}
}
if (uids.length) {
rcmail.goto_url('mail/compose', { _with_notes: uids.join(','), _notes_list: me.selected_list }, true);
}
}
/**
*
*/
function render_tagslist(newtags, replace)
{
if (replace) {
tags = newtags;
}
else {
var append = [];
for (var i=0; i < newtags.length; i++) {
if ($.inArray(newtags[i], tags) < 0)
append.push(newtags[i]);
}
if (!append.length) {
update_tagcloud();
return; // nothing to be added
}
tags = tags.concat(append);
}
// sort tags first
tags.sort(function(a,b){
return a.toLowerCase() > b.toLowerCase() ? 1 : -1;
})
var widget = $(rcmail.gui_objects.notestagslist).html('');
// append tags to tag cloud
$.each(tags, function(i, tag){
- li = $('<li>').attr('rel', tag).data('value', tag)
+ li = $('<li role="checkbox" aria-checked="false" tabindex="0"></li>')
+ .attr('rel', tag)
+ .data('value', tag)
.html(Q(tag) + '<span class="count"></span>')
.appendTo(widget)
.draggable({
addClasses: false,
revert: 'invalid',
revertDuration: 300,
helper: tag_draggable_helper,
start: tag_draggable_start,
appendTo: 'body',
cursor: 'pointer'
});
});
update_tagcloud();
}
/**
* Display the given counts to each tag and set those inactive which don't
* have any matching records in the current view.
*/
function update_tagcloud(counts)
{
// compute counts first by iterating over all visible task items
if (typeof counts == 'undefined') {
counts = {};
$.each(notesdata, function(id, rec){
for (var t, j=0; rec && rec.categories && j < rec.categories.length; j++) {
t = rec.categories[j];
if (typeof counts[t] == 'undefined')
counts[t] = 0;
counts[t]++;
}
});
}
$(rcmail.gui_objects.notestagslist).children('li').each(function(i,li){
var elem = $(li), tag = elem.attr('rel'),
count = counts[tag] || 0;
elem.children('.count').html(count+'');
if (count == 0) elem.addClass('inactive');
else elem.removeClass('inactive');
if (tagsfilter && tagsfilter.length && $.inArray(tag, tagsfilter)) {
- elem.addClass('selected');
+ elem.addClass('selected').attr('aria-checked', 'true');
}
else {
- elem.removeClass('selected');
+ elem.removeClass('selected').attr('aria-checked', 'false');
}
});
}
/**
* Callback from server after saving a note record
*/
function update_note(data)
{
data.id = rcmail.html_identifier_encode(data.uid);
var row, is_new = notesdata[data.id] == undefined
notesdata[data.id] = data;
if (is_new || me.selected_note && data.id == me.selected_note.id) {
render_note(data);
render_tagslist(data.categories || []);
}
else if (data.categories) {
render_tagslist(data.categories);
}
// add list item on top
if (is_new) {
noteslist.insert_row({
id: 'rcmrow' + data.id,
cols: [
{ className:'title', innerHTML:Q(data.title) },
{ className:'date', innerHTML:Q(data.changed || '') }
]
}, true);
noteslist.select(data.id);
}
// update list item
else if (row = noteslist.rows[data.id]) {
$('.title', row.obj).html(Q(data.title));
$('.date', row.obj).html(Q(data.changed || ''));
// TODO: move to top
}
}
/**
*
*/
function reset_view()
{
me.selected_note = null;
$('.notetitle', rcmail.gui_objects.noteviewtitle).val('');
$('.tagline, .dates', rcmail.gui_objects.noteviewtitle).hide();
$(rcmail.gui_objects.noteseditform).hide();
$(rcmail.gui_objects.notesdetailview).hide();
$(rcmail.gui_objects.notesattachmentslist).html('');
rcmail.enable_command('save', false);
}
/**
* Collect data from the edit form and submit it to the server
*/
function save_note()
{
if (!me.selected_note) {
return false;
}
var savedata = get_save_data();
// copy links as they're yet immutable
if (me.selected_note.links)
savedata.links = me.selected_note.links;
// add reference to old list if changed
if (me.selected_note.list && savedata.list != me.selected_note.list) {
savedata._fromlist = me.selected_note.list;
}
// do some input validation
if (savedata.title == '') {
alert(rcmail.gettext('entertitle', 'kolab_notes'));
$('.notetitle', rcmail.gui_objects.noteviewtitle).focus();
return false;
}
if (check_change_state(savedata)) {
rcmail.lock_form(rcmail.gui_objects.noteseditform, true);
saving_lock = rcmail.set_busy(true, 'kolab_notes.savingdata');
rcmail.http_post('action', { _data: savedata, _do: savedata.uid?'edit':'new' }, true);
}
else {
rcmail.display_message(rcmail.get_label('nochanges', 'kolab_notes'), 'info');
}
}
/**
* Collect updated note properties from edit form for saving
*/
function get_save_data()
{
var editor = tinyMCE.get('notecontent'),
listselect = $('option:selected', rcmail.gui_objects.notebooks);
var savedata = {
title: trim($('.notetitle', rcmail.gui_objects.noteviewtitle).val()),
description: editor ? editor.getContent({ format:'html' }) : $('#notecontent').val(),
list: listselect.length ? listselect.val() : me.selected_note.list || me.selected_list,
uid: me.selected_note.uid,
categories: []
};
// collect tags
$('.tagedit-list input[type="hidden"]', rcmail.gui_objects.noteviewtitle).each(function(i, elem){
if (elem.value)
savedata.categories.push(elem.value);
});
// including the "pending" one in the text box
var newtag = $('#tagedit-input').val();
if (newtag != '') {
savedata.categories.push(newtag);
}
return savedata;
}
/**
* Check if the currently edited note record was changed
*/
function check_change_state(data)
{
if (!me.selected_note || me.selected_note.readonly || !me.notebooks[me.selected_note.list || me.selected_list].editable) {
return false;
}
var savedata = data || get_save_data();
return savedata.title != me.selected_note.title
|| savedata.description != me.selected_note.description
|| savedata.categories.join(',') != (me.selected_note.categories || []).join(',')
|| savedata.list != me.selected_note.list;
}
/**
* Check for unsaved changes and warn the user
*/
function warn_unsaved_changes(ok, nok)
{
if (typeof ok != 'function')
ok = function(){ };
if (typeof nok != 'function')
nok = function(){ };
if (check_change_state()) {
var dialog, buttons = [];
buttons.push({
text: rcmail.gettext('discard', 'kolab_notes'),
click: function() {
dialog.dialog('close');
ok();
}
});
buttons.push({
text: rcmail.gettext('save'),
click: function() {
save_note();
dialog.dialog('close');
ok();
}
});
buttons.push({
text: rcmail.gettext('abort', 'kolab_notes'),
click: function() {
dialog.dialog('close');
nok();
}
});
var options = {
width: 460,
resizable: false,
closeOnEscape: false,
dialogClass: 'warning',
open: function(event, ui) {
$(this).parent().find('.ui-dialog-titlebar-close').hide();
- $(this).parent().find('.ui-button').first().addClass('mainaction').focus();
+ setTimeout(function(){
+ dialog.parent().find('.ui-button:visible').first().addClass('mainaction').focus();
+ }, 10);
}
};
// open jquery UI dialog
dialog = rcmail.show_popup_dialog(
rcmail.gettext('discardunsavedchanges', 'kolab_notes'),
rcmail.gettext('unsavedchanges', 'kolab_notes'),
buttons,
options
);
return false;
}
if (typeof ok == 'function') {
ok();
}
return true;
}
/**
*
*/
function delete_notes()
{
if (!noteslist.selection.length) {
return false;
}
if (confirm(rcmail.gettext('deletenotesconfirm','kolab_notes'))) {
var rec, id, uids = [];
for (var i=0; i < noteslist.selection.length; i++) {
id = noteslist.selection[i];
rec = notesdata[id];
if (rec) {
noteslist.remove_row(id);
uids.push(rec.uid);
delete notesdata[id];
}
}
saving_lock = rcmail.set_busy(true, 'kolab_notes.savingdata');
rcmail.http_post('action', { _data: { uid: uids.join(','), list: me.selected_list }, _do: 'delete' }, true);
reset_view();
update_tagcloud();
noteslist.clear_selection();
}
}
/**
*
*/
function move_notes(list_id)
{
var rec, id, uids = [];
for (var i=0; i < noteslist.selection.length; i++) {
id = noteslist.selection[i];
rec = notesdata[id];
if (rec) {
noteslist.remove_row(id);
uids.push(rec.uid);
delete notesdata[id];
}
}
if (uids.length) {
saving_lock = rcmail.set_busy(true, 'kolab_notes.savingdata');
rcmail.http_post('action', { _data: { uid: uids.join(','), list: me.selected_list, to: list_id }, _do: 'move' }, true);
}
}
/**
* update browser location to remember current view
*/
function update_state()
{
var query = { _list: me.selected_list }
if (settings.selected_uid) {
query._id = settings.selected_uid;
}
if (window.history.replaceState) {
window.history.replaceState({}, document.title, rcmail.url('', query));
}
}
/* Helper functions for drag & drop functionality of tags */
function tag_draggable_helper()
{
if (!tag_draghelper)
tag_draghelper = $('<div class="tag-draghelper"></div>');
else
tag_draghelper.html('');
$(this).clone().addClass('tag').appendTo(tag_draghelper);
return tag_draghelper;
}
function tag_draggable_start(event, ui)
{
// register notes list to receive drop events
$('li', rcmail.gui_objects.noteslist).droppable({
hoverClass: 'droptarget',
accept: tag_droppable_accept,
drop: tag_draggable_dropped,
addClasses: false
});
// allow to drop tags onto edit form title
$(rcmail.gui_objects.noteviewtitle).droppable({
drop: function(event, ui){
$('#tagedit-input').val(ui.draggable.data('value')).trigger('transformToTag');
},
addClasses: false
})
}
function tag_droppable_accept(draggable)
{
if (rcmail.busy)
return false;
var tag = draggable.data('value'),
drop_id = $(this).attr('id').replace(/^rcmrow/, ''),
drop_rec = notesdata[drop_id];
// target already has this tag assigned
if (!drop_rec || (drop_rec.categories && $.inArray(tag, drop_rec.categories) >= 0)) {
return false;
}
return true;
}
function tag_draggable_dropped(event, ui)
{
var drop_id = $(this).attr('id').replace(/^rcmrow/, ''),
tag = ui.draggable.data('value'),
rec = notesdata[drop_id],
savedata;
if (rec && rec.id) {
savedata = me.selected_note && rec.uid == me.selected_note.uid ? get_save_data() : $.extend({}, rec);
if (savedata.id) delete savedata.id;
if (savedata.html) delete savedata.html;
if (!savedata.categories)
savedata.categories = [];
savedata.categories.push(tag);
rcmail.lock_form(rcmail.gui_objects.noteseditform, true);
saving_lock = rcmail.set_busy(true, 'kolab_notes.savingdata');
rcmail.http_post('action', { _data: savedata, _do: 'edit' }, true);
}
}
}
// extend jQuery
// from http://james.padolsey.com/javascript/sorting-elements-with-jquery/
jQuery.fn.sortElements = (function(){
var sort = [].sort;
return function(comparator, getSortable) {
getSortable = getSortable || function(){ return this };
var last = null;
return sort.call(this, comparator).each(function(i){
// at this point the array is sorted, so we can just detach each one from wherever it is, and add it after the last
var node = $(getSortable.call(this));
var parent = node.parent();
if (last) last.after(node);
else parent.prepend(node);
last = node;
});
};
})();
/* notes plugin UI initialization */
var kolabnotes;
window.rcmail && rcmail.addEventListener('init', function(evt) {
kolabnotes = new rcube_kolab_notes_ui(rcmail.env.kolab_notes_settings);
if (rcmail.env.action == 'dialog-ui')
kolabnotes.init_dialog();
else
kolabnotes.init();
});
diff --git a/plugins/kolab_notes/skins/larry/notes.css b/plugins/kolab_notes/skins/larry/notes.css
index f85890ed..d57c2f83 100644
--- a/plugins/kolab_notes/skins/larry/notes.css
+++ b/plugins/kolab_notes/skins/larry/notes.css
@@ -1,420 +1,424 @@
/**
* Kolab Notes plugin styles for skin "Larry"
*
* Copyright (C) 2014, Kolab Systems AG <contact@kolabsys.com>
* Screendesign by FLINT / Büro für Gestaltung, bueroflint.com
*
* The contents are subject to the Creative Commons Attribution-ShareAlike
* License. It is allowed to copy, distribute, transmit and to adapt the work
* by keeping credits to the original autors in the README file.
* See http://creativecommons.org/licenses/by-sa/3.0/ for details.
*/
#taskbar a.button-notes span.button-inner {
background-image: url('sprites.png');
background-position: 0 0;
}
#taskbar a.button-notes:hover span.button-inner,
#taskbar a.button-notes.button-selected span.button-inner {
background-image: url('sprites.png');
background-position: 0 -26px;
}
.notesview #sidebar {
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 240px;
}
.notesview #notestoolbar {
position: absolute;
top: -6px;
left: 0;
width: 100%;
height: 40px;
white-space: nowrap;
}
.notesview #notestoolbar a.button.createnote {
background-image: url('sprites.png');
background-position: center -54px;
}
.notesview #notestoolbar a.button.sendnote {
background-position: left -650px;
}
.notesview #quicksearchbar {
top: 2px;
}
.notesview #searchmenulink {
width: 15px;
}
.notesview #mainview-right {
position: absolute;
bottom: 0;
right: 0;
top: 0;
left: 252px;
}
.notesview #tagsbox {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 240px;
}
.notesview #notebooksbox {
position: absolute;
top: 252px;
left: 0;
width: 100%;
bottom: 0px;
}
.notesview #noteslistbox {
position: absolute;
top: 0;
left: 0;
width: 240px;
bottom: 0px;
}
+.notesview #kolabnoteslist li {
+ border-left: 2px solid transparent;
+}
+
+.notesview #kolabnoteslist.focus li.focused {
+ border-left: 2px solid #739da8;
+}
+
.notesview #kolabnoteslist .title {
display: block;
- padding: 4px 8px;
+ padding: 4px 6px;
overflow: hidden;
text-overflow: ellipsis;
+ outline: none;
}
.notesview #kolabnoteslist .date {
display: block;
- padding: 0px 8px 4px 8px;
+ padding: 0px 6px 4px 6px;
color: #777;
font-weight: normal;
}
.notesview .boxpagenav a.icon.sortoptions {
background: url(sprites.png) center -93px no-repeat;
}
.notesview .toolbarmenu.iconized .selected span.icon {
background: url(sprites.png) -4px -110px no-repeat;
}
.notesview #notedetailsbox {
position: absolute;
top: 0;
left: 252px;
right: 0;
bottom: 0px;
}
.notesdialog #notedetailsbox {
left: 0;
}
.notesview #notedetailsbox .formbuttons {
position: absolute;
bottom: 0;
left: 0;
right: 0;
padding: 8px 12px;
background: #f9f9f9;
}
.notesview #noteform,
.notesview #notedetails {
display: none;
position: absolute;
top: 82px;
left: 0;
bottom: 41px;
width: 100%;
}
.notesview #notedetails {
padding: 8px;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
box-sizing: border-box;
border-bottom: 1px solid #dfdfdf;
}
.notesdialog #noteform,
.notesdialog #notedetails {
bottom: 30px;
}
.notesview #notedetails pre {
font-family: "Lucida Grande", Verdana, Arial, Helvetica, sans-serif;
font-size: 12px;
margin: 0;
}
.notesview #notecontent {
position: relative;
width: 100%;
height: 100%;
border: 0;
border-radius: 0;
padding: 8px 0 8px 8px;
resize: none;
font-family: "Lucida Grande", Verdana, Arial, Helvetica, sans-serif;
font-size: 12px;
outline: none;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
box-sizing: border-box;
-webkit-box-shadow: inset 0 0 2px 1px rgba(0,0,0, 0.2);
-moz-box-shadow: inset 0 0 2px 1px rgba(0,0,0, 0.2);
-o-box-shadow: inset 0 0 2px 1px rgba(0,0,0, 0.2);
box-shadow: inset 0 0 2px 1px rgba(0,0,0, 0.2);
}
.notesview #notecontent:active,
.notesview #notecontent:focus {
-webkit-box-shadow: inset 0 0 3px 2px rgba(71,135,177, 0.9);
-moz-box-shadow: inset 0 0 3px 2px rgba(71,135,177, 0.9);
-o-box-shadow: inset 0 0 3px 2px rgba(71,135,177, 0.9);
box-shadow: inset 0 0 3px 2px rgba(71,135,177, 0.9);
}
.notesview .mce-panel {
border: 0;
}
.notesview #notedetailstitle {
height: auto;
}
.notesview #notedetailstitle .tagedit-list,
.notesview #notedetailstitle input.inline-edit,
.notesview #notedetailstitle input.inline-edit:focus {
outline: none;
padding: 0;
margin: 0;
border: 0;
background: rgba(255,255,255,0.01);
-webkit-box-shadow: none;
-moz-box-shadow: none;
-o-box-shadow: none;
box-shadow: none;
}
.notesview #notedetailstitle input.notetitle,
.notesview #notedetailstitle input.notetitle:focus {
width: 100%;
font-size: 14px;
font-weight: bold;
color: #777;
}
.notesview #notedetailstitle .dates,
.notesview #notedetailstitle .tagline,
.notesdialog .notebookselect label {
color: #999;
font-weight: normal;
font-size: 0.9em;
margin-top: 6px;
}
.notesview #notedetailstitle .dates,
.notesview #notedetailstitle .notebookselect {
margin-top: 4px;
margin-bottom: 4px;
}
.notesview #notedetailstitle .tagline {
position: relative;
cursor: text;
+ margin: 6px -2px -2px -2px;
}
.notesview #notedetailstitle .tagline .placeholder {
position: absolute;
top: 4px;
left: 0;
z-index: 1;
}
.notesview #notedetailstitle .tagedit-list {
position: relative;
z-index: 2;
+ padding: 2px;
}
.notesview #notedetailstitle #tagedit-input {
background: none;
}
.notesview .tag-draghelper {
z-index: 1000;
}
.notesview #notedetailstitle .notecreated,
.notesview #notedetailstitle .notechanged {
display: inline-block;
padding-left: 0.4em;
padding-right: 2em;
color: #777;
}
.notesview #notereferences {
position: absolute;
left: 0;
right: 0;
bottom: 41px;
height: 27px;
background: #fff;
padding-left: 10px;
}
.notesdialog #notereferences {
bottom: 0;
}
.notesview #notebooks li {
margin: 0;
height: 20px;
padding: 6px 8px 2px 6px;
display: block;
position: relative;
white-space: nowrap;
}
.notesview #notebooks li.virtual {
height: 12px;
}
-.notesview #notebooks li span.listname {
+.notesview #notebooks li .listname {
display: block;
position: absolute;
- top: 7px;
- left: 9px;
+ top: 1px;
+ left: 2px;
right: 6px;
+ height: 19px;
cursor: default;
- padding-bottom: 2px;
- padding-right: 26px;
+ padding: 4px 26px 2px 6px;
color: #004458;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
-.notesview #notebooks li.virtual span.listname {
+.notesview #notebooks li.virtual .listname {
color: #aaa;
- top: 3px;
+ top: 0;
+ padding: 1px 8px;
}
.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 #notebooks li.readonly {
background-position: 98% -21px;
}
.notesview #notebooks li.other {
background-position: 98% -52px;
}
.notesview #notebooks li.other.readonly {
background-position: 98% -77px;
}
.notesview #notebooks li.shared {
background-position: 98% -103px;
}
.notesview #notebooks li.shared.readonly {
background-position: 98% -130px;
}
-.notesview #notebooks li.other.readonly span.listname,
-.notesview #notebooks li.shared.readonly span.listname {
+.notesview #notebooks li.other.readonly .listname,
+.notesview #notebooks li.shared.readonly .listname {
padding-right: 36px;
}
-.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;
+.notesview .uidialog .propform fieldset.ui-tabs-panel {
min-height: 290px;
}
.notesview .uidialog .propform #noteslist-name {
width: 20em;
}
.notesdialog #notesdialogheader {
height: auto;
}
#kolabnotesinlinegui {
border: 0;
padding: 0;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
box-sizing: border-box;
}
.notesview .attachmentslist li.message.eml {
display: inline-block;
background-image: url(sprites.png);
background-position: -6px -128px;
margin-right: 1em;
}
.notesview .attachmentslist li.message a.messagelink {
padding-left: 24px;
}
ul.toolbarmenu li .appendnote span.icon {
background-image: url(sprites.png);
background-position: -4px -170px;
}
div.kolabmessagenotes {
margin: 8px 8px;
padding: 4px 8px;
border: 1px solid #dfdfdf;
background: #fafafa;
border-radius: 4px;
}
div.kolabmessagenotes a.kolabnotesref {
display: inline-block;
color: #333;
font-weight: bold;
padding: 3px 15px 2px 22px;
text-shadow: 0px 1px 1px #fff;
text-decoration: none;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 12em;
background: url(sprites.png) -6px -151px no-repeat;
}
\ No newline at end of file
diff --git a/plugins/kolab_notes/skins/larry/templates/notes.html b/plugins/kolab_notes/skins/larry/templates/notes.html
index 2904cf0b..1647da33 100644
--- a/plugins/kolab_notes/skins/larry/templates/notes.html
+++ b/plugins/kolab_notes/skins/larry/templates/notes.html
@@ -1,159 +1,160 @@
<roundcube:object name="doctype" value="html5" />
<html>
<head>
<title><roundcube:object name="pagetitle" /></title>
<roundcube:include file="/includes/links.html" />
</head>
<body class="notesview noscroll">
<roundcube:include file="/includes/header.html" />
<div id="mainscreen">
<h1 class="voice"><roundcube:label name="kolab_notes.navtitle" /></h1>
<div id="notestoolbar" class="toolbar" role="toolbar" aria-labelledby="aria-label-toolbar">
<h2 id="aria-label-toolbar" class="voice"><roundcube:label name="arialabeltoolbar" /></h2>
<roundcube:button command="createnote" type="link" class="button createnote disabled" classAct="button createnote" classSel="button createnote pressed" label="kolab_notes.create" title="kolab_notes.createnote" />
<roundcube:button command="print" type="link" class="button print disabled" classAct="button print" classSel="button print pressed" label="print" title="print" />
<roundcube:button command="sendnote" type="link" class="button sendnote disabled" classAct="button sendnote" classSel="button sendnote pressed" label="kolab_notes.send" title="kolab_notes.sendnote" />
<roundcube:container name="toolbar" id="notestoolbar" />
</div>
<div id="quicksearchbar" role="search" aria-labelledby="aria-label-searchform">
<h2 id="aria-label-searchform" class="voice"><roundcube:label name="kolab_notes.arialabelnotesearchform" /></h2>
<label for="quicksearchbox" class="voice"><roundcube:label name="kolab_notes.arialabelnotesquicksearchbox" /></label>
<a id="searchmenulink" class="iconbutton searchoptions" > </a>
<roundcube:object name="plugin.searchform" id="quicksearchbox" />
<roundcube:button command="reset-search" id="searchreset" class="iconbutton reset" title="resetsearch" content=" " />
</div>
<div id="mainscreencontent">
<div id="sidebar">
<div id="tagsbox" class="uibox listbox">
- <h2 class="boxtitle"><roundcube:label name="kolab_notes.tags" id="taglist" /></h2>
+ <h2 class="boxtitle" id="aria-label-tagsbox"><roundcube:label name="kolab_notes.tags" id="taglist" /></h2>
<div class="scroller">
- <roundcube:object name="plugin.tagslist" id="tagslist" class="tagcloud" />
+ <roundcube:object name="plugin.tagslist" id="tagslist" class="tagcloud" role="region" aria-labelledby="aria-label-tagsbox" aria-controls="kolabnoteslist" />
</div>
</div>
- <div id="notebooksbox" class="uibox listbox">
- <h2 class="boxtitle"><roundcube:label name="kolab_notes.lists" /></h2>
+ <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>
<div class="scroller withfooter">
- <roundcube:object name="plugin.notebooks" id="notebooks" class="listing" />
+ <roundcube:object name="plugin.notebooks" id="notebooks" class="listing treelist" />
</div>
<div class="boxfooter">
- <roundcube:button command="list-create" type="link" title="kolab_notes.newnotebook" class="listbutton add disabled" classAct="listbutton add" innerClass="inner" label="kolab_notes.addnotebook" /><roundcube:button name="notesoptionslink" id="notesoptionsmenulink" type="link" title="kolab_notes.listactions" class="listbutton groupactions" onclick="return UI.toggle_popup('notesoptionsmenu', event)" innerClass="inner" content="⚙" aria-haspopup="true" aria-expanded="false" aria-owns="notesoptionsmenu-menu" />
+ <roundcube:button command="list-create" type="link" title="kolab_notes.newnotebook" class="listbutton add disabled" classAct="listbutton add" innerClass="inner" label="kolab_notes.addnotebook" /><roundcube:button name="notesoptionslink" id="notesoptionsmenulink" type="link" title="kolab_notes.listactions" class="listbutton groupactions" onclick="return UI.toggle_popup('notesoptionsmenu', event)" innerClass="inner" label="kolab_notes.listactions" aria-haspopup="true" aria-expanded="false" aria-owns="notesoptionsmenu-menu" />
</div>
</div>
+
+ <div id="notesoptionsmenu" class="popupmenu" aria-hidden="true">
+ <h3 id="aria-label-optionsmenu" class="voice"><roundcube:label name="kolab_notes.arialabelnotesoptionsmenu" /></h3>
+ <ul class="toolbarmenu" id="notesoptionsmenu-menu" role="menu" aria-labelledby="aria-label-optionsmenu">
+ <li role="menuitem"><roundcube:button command="list-edit" label="edit" classAct="active" /></li>
+ <li role="menuitem"><roundcube:button command="list-remove" label="delete" classAct="active" /></li>
+ <li role="menuitem"><roundcube:button command="folders" task="settings" type="link" label="managefolders" classAct="active" /></li>
+ </ul>
+ </div>
</div>
<div id="mainview-right">
<div id="noteslistbox" class="uibox listbox">
<h3 id="aria-label-noteslist" class="boxtitle"><roundcube:label name="kolab_notes.notes" /></h3>
<div class="scroller withfooter" aria-labelledby="aria-label-noteslist">
<roundcube:object name="plugin.listing" id="kolabnoteslist" class="listing" summary="kolab_notes.arialabelnoteslist" />
</div>
<div class="boxfooter">
<roundcube:button command="delete" type="link" title="delete" class="listbutton delete disabled" classAct="listbutton delete" innerClass="inner" label="delete" />
- <roundcube:object name="plugin.recordsCountDisplay" class="countdisplay" label="fromtoshort" />
+ <roundcube:object name="plugin.recordsCountDisplay" class="countdisplay" label="fromtoshort" aria-live="polite" aria-relevant="text" />
</div>
<div class="boxpagenav">
<roundcube:button name="notessortmenulink" id="notessortmenulink" type="link" title="kolab_notes.sortby" class="icon sortoptions" onclick="return UI.toggle_popup('notessortmenu', event)" innerClass="inner" content="v" aria-haspopup="true" aria-expanded="false" aria-owns="notessortmenu-menu" />
</div>
</div>
- <div id="notedetailsbox" class="uibox contentbox" role="complementary" aria-labelledby="aria-label-noteform">
+ <div id="notedetailsbox" class="uibox contentbox" role="main" aria-labelledby="aria-label-noteform">
<h3 id="aria-label-noteform" class="voice"><roundcube:label name="kolab_notes.arialabelnoteform" /></h3>
<roundcube:object name="plugin.notetitle" id="notedetailstitle" class="boxtitle" />
<roundcube:object name="plugin.editform" id="noteform" />
<roundcube:object name="plugin.detailview" id="notedetails" class="scroller" />
<div id="notereferences">
- <roundcube:object name="plugin.attachments_list" id="attachment-list" class="attachmentslist" />
+ <h3 id="aria-label-messagereferences" class="voice"><roundcube:label name="kolab_notes.arialabelmessagereferences" /></h3>
+ <roundcube:object name="plugin.attachments_list" id="attachment-list" class="attachmentslist" role="region" aria-labelledby="aria-label-messagereferences" />
</div>
<div class="footerleft formbuttons">
- <roundcube:button command="save" type="input" class="button mainaction" label="save" />
+ <roundcube:button command="save" type="input" class="button mainaction" label="save" id="btn-save-note" />
</div>
</div>
</div>
</div>
</div>
<roundcube:object name="message" id="messagestack" />
-<div id="notesoptionsmenu" class="popupmenu" aria-hidden="true">
- <h3 id="aria-label-optionsmenu" class="voice"><roundcube:label name="kolab_notes.arialabelnotesoptionsmenu" /></h3>
- <ul class="toolbarmenu" id="notesoptionsmenu-menu" role="menu" aria-labelledby="aria-label-optionsmenu">
- <li role="menuitem"><roundcube:button command="list-edit" label="edit" classAct="active" /></li>
- <li role="menuitem"><roundcube:button command="list-remove" label="delete" classAct="active" /></li>
- <li role="menuitem"><roundcube:button command="folders" task="settings" type="link" label="managefolders" classAct="active" /></li>
- </ul>
-</div>
-
<div id="notessortmenu" class="popupmenu" aria-hidden="true">
<h3 id="aria-label-sortmenu" class="voice"><roundcube:label name="kolab_notes.arialabelnotessortmenu" /></h3>
<ul class="toolbarmenu iconized" id="notessortmenu-menu" role="menu" aria-labelledby="aria-label-sortmenu">
<li role="menuitem"><roundcube:button command="list-sort" prop="changed" type="link" label="kolab_notes.changed" class="icon active by-changed" innerclass="icon" /></li>
<li role="menuitem"><roundcube:button command="list-sort" prop="title" type="link" label="kolab_notes.title" class="icon active by-title" innerclass="icon" /></li>
</ul>
</div>
<div id="notebookeditform" class="uidialog" aria-labelledby="aria-label-editform" aria-hidden="true" role="dialog">
<h3 id="aria-label-editform" class="voice"><roundcube:label name="kolab_notes.arialabelnotebookform" /></h3>
<roundcube:container name="notebookeditform" id="notebookeditform" />
<roundcube:label name="loading" />
</div>
<script type="text/javascript">
// UI startup
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:12, render:layout_view }).init();
new rcube_splitter({ id:'noteslistsplitter2', p1:'#noteslistbox', p2:'#notedetailsbox',
orientation:'v', relative:true, start:242, min:180, size:12, render:layout_view }).init();
new rcube_splitter({ id:'notesviewsplitterv', p1:'#tagsbox', p2:'#notebooksbox',
orientation:'h', relative:true, start:242, min:120, size:12, offset:4 }).init();
function layout_view()
{
var form = $('#noteform, #notedetails'),
content = $('#notecontent'),
header = $('#notedetailstitle'),
footer = $('#notereferences'),
w, h;
if ($('#attachment-list li').length)
footer.show();
else
footer.hide();
form.css('top', header.outerHeight()+'px');
form.css('bottom', ((footer.is(':visible') ? footer.outerHeight()+4 : 0) + 41) + 'px');
w = form.outerWidth();
h = form.outerHeight();
content.width(w).height(h);
$('#noteform > div.mce-tinymce').width(w);
$('#notecontent_ifr').width(w).height(h - 4 - $('div.mce-toolbar').height());
}
$(window).resize(function(e){
layout_view();
});
});
</script>
</body>
</html>
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Apr 4 2026, 10:15 AM (4 w, 3 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18747993
Default Alt Text
(84 KB)
Attached To
Mode
rRPK roundcubemail-plugins-kolab
Attached
Detach File
Event Timeline