Implement audit trail for notes (#4904)
This commit is contained in:
parent
dfa8e1e4de
commit
ae6ec80e44
7 changed files with 681 additions and 56 deletions
|
@ -8,7 +8,7 @@
|
|||
* @version @package_version@
|
||||
* @author Thomas Bruederli <bruederli@kolabsys.com>
|
||||
*
|
||||
* Copyright (C) 2014, Kolab Systems AG <contact@kolabsys.com>
|
||||
* Copyright (C) 2014-2015, 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
|
||||
|
@ -35,6 +35,7 @@ class kolab_notes extends rcube_plugin
|
|||
private $folders;
|
||||
private $cache = array();
|
||||
private $message_notes = array();
|
||||
private $bonnie_api = false;
|
||||
|
||||
/**
|
||||
* Required startup method of a Roundcube plugin
|
||||
|
@ -110,6 +111,11 @@ class kolab_notes extends rcube_plugin
|
|||
$this->load_ui();
|
||||
}
|
||||
|
||||
// get configuration for the Bonnie API
|
||||
if ($bonnie_config = $this->rc->config->get('kolab_bonnie_api', false)) {
|
||||
$this->bonnie_api = new kolab_bonnie_api($bonnie_config);
|
||||
}
|
||||
|
||||
// notes use fully encoded identifiers
|
||||
kolab_storage::$encode_ids = true;
|
||||
}
|
||||
|
@ -597,7 +603,7 @@ class kolab_notes extends rcube_plugin
|
|||
$action = rcube_utils::get_input_value('_do', rcube_utils::INPUT_POST);
|
||||
$note = rcube_utils::get_input_value('_data', rcube_utils::INPUT_POST, true);
|
||||
|
||||
$success = false;
|
||||
$success = $silent = false;
|
||||
switch ($action) {
|
||||
case 'new':
|
||||
$temp_id = $rec['tempid'];
|
||||
|
@ -630,13 +636,66 @@ class kolab_notes extends rcube_plugin
|
|||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'changelog':
|
||||
$data = $this->get_changelog($note);
|
||||
if (is_array($data) && !empty($data)) {
|
||||
$dtformat = $this->rc->config->get('date_format') . ' ' . $this->rc->config->get('time_format');
|
||||
array_walk($data, function(&$change) use ($lib, $dtformat) {
|
||||
if ($change['date']) {
|
||||
$dt = rcube_utils::anytodatetime($change['date']);
|
||||
if ($dt instanceof DateTime) {
|
||||
$change['date'] = $this->rc->format_date($dt, $dtformat);
|
||||
}
|
||||
}
|
||||
});
|
||||
$this->rc->output->command('plugin.note_render_changelog', $data);
|
||||
}
|
||||
else {
|
||||
$this->rc->output->command('plugin.note_render_changelog', false);
|
||||
}
|
||||
$silent = true;
|
||||
break;
|
||||
|
||||
case 'diff':
|
||||
$silent = true;
|
||||
$data = $this->get_diff($note, $note['rev1'], $note['rev2']);
|
||||
if (is_array($data)) {
|
||||
$this->rc->output->command('plugin.note_show_diff', $data);
|
||||
}
|
||||
else {
|
||||
$this->rc->output->command('display_message', $this->gettext('objectdiffnotavailable'), 'error');
|
||||
}
|
||||
break;
|
||||
|
||||
case 'show':
|
||||
if ($rec = $this->get_revison($note, $note['rev'])) {
|
||||
$this->rc->output->command('plugin.note_show_revision', $this->_client_encode($rec));
|
||||
}
|
||||
else {
|
||||
$this->rc->output->command('display_message', $this->gettext('objectnotfound'), 'error');
|
||||
}
|
||||
$silent = true;
|
||||
break;
|
||||
|
||||
case 'restore':
|
||||
if ($this->restore_revision($note, $note['rev'])) {
|
||||
$refresh = $this->get_note($note);
|
||||
$this->rc->output->command('display_message', $this->gettext(array('name' => 'objectrestoresuccess', 'vars' => array('rev' => $note['rev']))), 'confirmation');
|
||||
$this->rc->output->command('plugin.close_history_dialog');
|
||||
}
|
||||
else {
|
||||
$this->rc->output->command('display_message', $this->gettext('objectrestoreerror'), 'error');
|
||||
}
|
||||
$silent = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// show confirmation/error message
|
||||
if ($success) {
|
||||
$this->rc->output->show_message('successfullysaved', 'confirmation');
|
||||
}
|
||||
else {
|
||||
else if (!$silent) {
|
||||
$this->rc->output->show_message('errorsaving', 'error');
|
||||
}
|
||||
|
||||
|
@ -762,6 +821,192 @@ class kolab_notes extends rcube_plugin
|
|||
return $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a list of revisions for the given object
|
||||
*
|
||||
* @param array $note Hash array with note properties
|
||||
* @return array List of changes, each as a hash array
|
||||
*/
|
||||
public function get_changelog($note)
|
||||
{
|
||||
if (empty($this->bonnie_api)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
list($uid, $mailbox, $msguid) = $this->_resolve_note_identity($note);
|
||||
|
||||
$result = $uid && $mailbox ? $this->bonnie_api->changelog('note', $uid, $mailbox, $msguid) : null;
|
||||
if (is_array($result) && $result['uid'] == $uid) {
|
||||
return $result['changes'];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return full data of a specific revision of a note record
|
||||
*
|
||||
* @param mixed $note UID string or hash array with note properties
|
||||
* @param mixed $rev Revision number
|
||||
*
|
||||
* @return array Note object as hash array
|
||||
*/
|
||||
public function get_revison($note, $rev)
|
||||
{
|
||||
if (empty($this->bonnie_api)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
list($uid, $mailbox, $msguid) = $this->_resolve_note_identity($note);
|
||||
|
||||
// call Bonnie API
|
||||
$result = $this->bonnie_api->get('note', $uid, $rev, $mailbox, $msguid);
|
||||
if (is_array($result) && $result['uid'] == $uid && !empty($result['xml'])) {
|
||||
$format = kolab_format::factory('note');
|
||||
$format->load($result['xml']);
|
||||
$rec = $format->to_array();
|
||||
|
||||
if ($format->is_valid()) {
|
||||
$rec['rev'] = $result['rev'];
|
||||
return $rec;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of property changes beteen two revisions of a note object
|
||||
*
|
||||
* @param array $$note Hash array with note properties
|
||||
* @param mixed $rev Revisions: "from:to"
|
||||
*
|
||||
* @return array List of property changes, each as a hash array
|
||||
*/
|
||||
public function get_diff($note, $rev1, $rev2)
|
||||
{
|
||||
if (empty($this->bonnie_api)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
list($uid, $mailbox, $msguid) = $this->_resolve_note_identity($note);
|
||||
|
||||
// call Bonnie API
|
||||
$result = $this->bonnie_api->diff('note', $uid, $rev1, $rev2, $mailbox, $msguid);
|
||||
if (is_array($result) && $result['uid'] == $uid) {
|
||||
$result['rev1'] = $rev1;
|
||||
$result['rev2'] = $rev2;
|
||||
|
||||
// convert some properties, similar to self::_client_encode()
|
||||
$keymap = array(
|
||||
'summary' => 'title',
|
||||
'lastmodified-date' => 'changed',
|
||||
);
|
||||
|
||||
// map kolab object properties to keys and values the client expects
|
||||
array_walk($result['changes'], function(&$change, $i) use ($keymap) {
|
||||
if (array_key_exists($change['property'], $keymap)) {
|
||||
$change['property'] = $keymap[$change['property']];
|
||||
}
|
||||
|
||||
if ($change['property'] == 'created' || $change['property'] == 'changed') {
|
||||
if ($old_ = rcube_utils::anytodatetime($change['old'])) {
|
||||
$change['old_'] = $this->rc->format_date($old_);
|
||||
}
|
||||
if ($new_ = rcube_utils::anytodatetime($change['new'])) {
|
||||
$change['new_'] = $this->rc->format_date($new_);
|
||||
}
|
||||
}
|
||||
|
||||
// compute a nice diff of note contents
|
||||
if ($change['property'] == 'description') {
|
||||
$change['diff_'] = libkolab::html_diff($change['old'], $change['new']);
|
||||
if (!empty($change['diff_'])) {
|
||||
unset($change['old'], $change['new']);
|
||||
$change['diff_'] = preg_replace(array('!^.*<body[^>]*>!Uims','!</body>.*$!Uims'), '', $change['diff_']);
|
||||
$change['diff_'] = preg_replace("!</(p|li|span)>\n!", '</\\1>', $change['diff_']);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Command the backend to restore a certain revision of a note.
|
||||
* This shall replace the current object with an older version.
|
||||
*
|
||||
* @param array $note Hash array with note properties (id, list)
|
||||
* @param mixed $rev Revision number
|
||||
*
|
||||
* @return boolean True on success, False on failure
|
||||
*/
|
||||
public function restore_revision($note, $rev)
|
||||
{
|
||||
if (empty($this->bonnie_api)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
list($uid, $mailbox, $msguid) = $this->_resolve_note_identity($note);
|
||||
|
||||
$folder = $this->get_folder($note['list']);
|
||||
$success = false;
|
||||
|
||||
if ($folder && ($raw_msg = $this->bonnie_api->rawdata('note', $uid, $rev, $mailbox))) {
|
||||
$imap = $this->rc->get_storage();
|
||||
|
||||
// insert $raw_msg as new message
|
||||
if ($imap->save_message($folder->name, $raw_msg, null, false)) {
|
||||
$success = true;
|
||||
|
||||
// delete old revision from imap and cache
|
||||
$imap->delete_message($msguid, $folder->name);
|
||||
$folder->cache->set($msguid, false);
|
||||
$this->cache = array();
|
||||
}
|
||||
}
|
||||
|
||||
return $success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to resolved the given note identifier into uid and mailbox
|
||||
*
|
||||
* @return array (uid,mailbox,msguid) tuple
|
||||
*/
|
||||
private function _resolve_note_identity($note)
|
||||
{
|
||||
$mailbox = $msguid = null;
|
||||
|
||||
if (!is_array($note)) {
|
||||
$note = $this->get_note($note);
|
||||
}
|
||||
|
||||
if (is_array($note)) {
|
||||
$uid = $note['uid'] ?: $note['id'];
|
||||
$list = $note['list'];
|
||||
}
|
||||
else {
|
||||
return array(null, $mailbox, $msguid);
|
||||
}
|
||||
|
||||
if ($folder = $this->get_folder($list)) {
|
||||
$mailbox = $folder->get_mailbox_id();
|
||||
|
||||
// get object from storage in order to get the real object uid an msguid
|
||||
if ($rec = $folder->get_object($uid)) {
|
||||
$msguid = $rec['_msguid'];
|
||||
$uid = $rec['uid'];
|
||||
}
|
||||
}
|
||||
|
||||
return array($uid, $mailbox, $msguid);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handler for client requests to list (aka folder) actions
|
||||
*/
|
||||
|
|
|
@ -51,6 +51,7 @@ class kolab_notes_ui
|
|||
$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->plugin->register_handler('plugin.object_changelog_table', array('libkolab', 'object_changelog_table'));
|
||||
|
||||
$this->rc->output->include_script('list.js');
|
||||
$this->rc->output->include_script('treelist.js');
|
||||
|
@ -61,6 +62,7 @@ class kolab_notes_ui
|
|||
// include kolab folderlist widget if available
|
||||
if (in_array('libkolab', $this->plugin->api->loaded_plugins())) {
|
||||
$this->plugin->api->include_script('libkolab/js/folderlist.js');
|
||||
$this->plugin->api->include_script('libkolab/js/audittrail.js');
|
||||
}
|
||||
|
||||
// load config options and user prefs relevant for the UI
|
||||
|
@ -101,7 +103,7 @@ class kolab_notes_ui
|
|||
|
||||
$this->rc->output->set_env('kolab_notes_settings', $settings);
|
||||
|
||||
$this->rc->output->add_label('save','cancel','delete');
|
||||
$this->rc->output->add_label('save','cancel','delete','close');
|
||||
}
|
||||
|
||||
public function folders($attrib)
|
||||
|
|
|
@ -56,6 +56,24 @@ $labels['invalidlistproperties'] = 'Invalid notebook properties! Please set a va
|
|||
$labels['entertitle'] = 'Please enter a title for this note!';
|
||||
$labels['aclnorights'] = 'You do not have administrator rights for this notebook.';
|
||||
|
||||
// history dialog
|
||||
$labels['showhistory'] = 'Show History';
|
||||
$labels['compare'] = 'Compare';
|
||||
$labels['objectchangelog'] = 'Change History';
|
||||
$labels['objectdiff'] = 'Changes from $rev1 to $rev2';
|
||||
$labels['actionappend'] = 'Saved';
|
||||
$labels['actionmove'] = 'Moved';
|
||||
$labels['actiondelete'] = 'Deleted';
|
||||
$labels['showrevision'] = 'Show this version';
|
||||
$labels['restore'] = 'Restore this version';
|
||||
$labels['objectnotfound'] = 'Failed to load note data';
|
||||
$labels['objectchangelognotavailable'] = 'Change history is not available for this note';
|
||||
$labels['objectdiffnotavailable'] = 'No comparison possible for the selected revisions';
|
||||
$labels['revisionrestoreconfirm'] = 'Do you really want to restore revision $rev of this note? This will replace the current note with the old version.';
|
||||
$labels['objectrestoresuccess'] = 'Revision $rev successfully restored';
|
||||
$labels['objectrestoreerror'] = 'Failed to restore the old revision';
|
||||
|
||||
// (hidden) titles and labels for accessibility annotations
|
||||
$labels['arialabelnoteslist'] = 'List of notes';
|
||||
$labels['arialabelnotesearchform'] = 'Notes search form';
|
||||
$labels['arialabelnotesquicksearchbox'] = 'Notes search input';
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* @licstart The following is the entire license notice for the
|
||||
* JavaScript code in this file.
|
||||
*
|
||||
* Copyright (C) 2014, Kolab Systems AG <contact@kolabsys.com>
|
||||
* Copyright (C) 2014-2015, 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
|
||||
|
@ -68,6 +68,7 @@ function rcube_kolab_notes_ui(settings)
|
|||
rcmail.register_command('reset-search', reset_search, true);
|
||||
rcmail.register_command('sendnote', send_note, false);
|
||||
rcmail.register_command('print', print_note, false);
|
||||
rcmail.register_command('history', show_history_dialog, false);
|
||||
|
||||
// register server callbacks
|
||||
rcmail.addEventListener('plugin.data_ready', data_ready);
|
||||
|
@ -84,6 +85,11 @@ function rcube_kolab_notes_ui(settings)
|
|||
}
|
||||
});
|
||||
|
||||
rcmail.addEventListener('plugin.close_history_dialog', close_history_dialog);
|
||||
rcmail.addEventListener('plugin.note_render_changelog', render_changelog);
|
||||
rcmail.addEventListener('plugin.note_show_revision', render_revision);
|
||||
rcmail.addEventListener('plugin.note_show_diff', show_diff);
|
||||
|
||||
// initialize folder selectors
|
||||
if (settings.selected_list && !me.notebooks[settings.selected_list]) {
|
||||
settings.selected_list = null;
|
||||
|
@ -209,7 +215,7 @@ function rcube_kolab_notes_ui(settings)
|
|||
|
||||
rcmail.enable_command('delete', me.notebooks[me.selected_list] && has_permission(me.notebooks[me.selected_list], 'td') && list.selection.length > 0);
|
||||
rcmail.enable_command('sendnote', list.selection.length > 0);
|
||||
rcmail.enable_command('print', list.selection.length == 1);
|
||||
rcmail.enable_command('print', 'history', list.selection.length == 1);
|
||||
})
|
||||
.addEventListener('dragstart', function(e) {
|
||||
folder_drop_target = null;
|
||||
|
@ -828,7 +834,7 @@ function rcube_kolab_notes_ui(settings)
|
|||
/**
|
||||
*
|
||||
*/
|
||||
function render_note(data, retry)
|
||||
function render_note(data, container, temp, retry)
|
||||
{
|
||||
rcmail.set_busy(false, 'loading', ui_loading);
|
||||
|
||||
|
@ -837,20 +843,25 @@ function rcube_kolab_notes_ui(settings)
|
|||
return;
|
||||
}
|
||||
|
||||
if (!container) {
|
||||
container = rcmail.gui_containers['notedetailview'];
|
||||
}
|
||||
|
||||
var list = me.notebooks[data.list] || me.notebooks[me.selected_list] || { rights: 'lrs', editable: false };
|
||||
content = $('#notecontent').val(data.description),
|
||||
readonly = data.readonly || !(list.editable || !data.uid && has_permission(list,'i')),
|
||||
attachmentslist = $(rcmail.gui_objects.notesattachmentslist).html('');
|
||||
$('.notetitle', rcmail.gui_objects.noteviewtitle).val(data.title).prop('disabled', readonly).show();
|
||||
$('.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);
|
||||
attachmentslist = gui_object('notesattachmentslist', container).html(''),
|
||||
titlecontainer = container || rcmail.gui_objects.noteviewtitle;
|
||||
|
||||
$('.notetitle', titlecontainer).val(data.title).prop('disabled', readonly).show();
|
||||
$('.dates .notecreated', titlecontainer).html(Q(data.created || ''));
|
||||
$('.dates .notechanged', titlecontainer).html(Q(data.changed || ''));
|
||||
if (data.created || data.changed) {
|
||||
$('.dates', rcmail.gui_objects.noteviewtitle).show();
|
||||
$('.dates', titlecontainer).show();
|
||||
}
|
||||
|
||||
// tag-edit line
|
||||
var tagline = $('.tagline', rcmail.gui_objects.noteviewtitle).empty()[readonly?'addClass':'removeClass']('disabled').show();
|
||||
var tagline = $('.tagline', titlecontainer).empty()[readonly?'addClass':'removeClass']('disabled').show();
|
||||
$.each(typeof data.tags == 'object' && data.tags.length ? data.tags : [''], function(i,val) {
|
||||
$('<input>')
|
||||
.attr('name', 'tags[]')
|
||||
|
@ -867,7 +878,7 @@ function rcube_kolab_notes_ui(settings)
|
|||
.click(function(e) { $(this).parent().find('.tagedit-list').trigger('click'); });
|
||||
}
|
||||
|
||||
$('.tagline input.tag', rcmail.gui_objects.noteviewtitle).tagedit({
|
||||
$('.tagline input.tag', titlecontainer).tagedit({
|
||||
animSpeed: 100,
|
||||
allowEdit: false,
|
||||
allowAdd: !readonly,
|
||||
|
@ -904,7 +915,7 @@ function rcube_kolab_notes_ui(settings)
|
|||
}
|
||||
|
||||
if (!readonly) {
|
||||
$('.tagedit-list', rcmail.gui_objects.noteviewtitle)
|
||||
$('.tagedit-list', titlecontainer)
|
||||
.on('click', function(){ $('.tagline .placeholder').hide(); });
|
||||
}
|
||||
|
||||
|
@ -912,9 +923,13 @@ function rcube_kolab_notes_ui(settings)
|
|||
data.list = list.id;
|
||||
|
||||
data.readonly = readonly;
|
||||
me.selected_note = data;
|
||||
me.selected_note.id = rcmail.html_identifier_encode(data.uid);
|
||||
rcmail.enable_command('save', !readonly);
|
||||
|
||||
if (!temp) {
|
||||
$(rcmail.gui_objects.notebooks).filter('select').val(list.id);
|
||||
me.selected_note = data;
|
||||
me.selected_note.id = rcmail.html_identifier_encode(data.uid);
|
||||
rcmail.enable_command('save', !readonly);
|
||||
}
|
||||
|
||||
var html = data.html || data.description;
|
||||
|
||||
|
@ -930,15 +945,15 @@ function rcube_kolab_notes_ui(settings)
|
|||
if (!readonly && !editor && $('#notecontent').length && retry < 5) {
|
||||
// ... give it some more time
|
||||
setTimeout(function() {
|
||||
$(rcmail.gui_objects.noteseditform).show();
|
||||
render_note(data, retry+1);
|
||||
gui_object('noteseditform', container).show();
|
||||
render_note(data, container, temp, retry+1);
|
||||
}, 200);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!readonly && editor) {
|
||||
$(rcmail.gui_objects.notesdetailview).hide();
|
||||
$(rcmail.gui_objects.noteseditform).show();
|
||||
gui_object('notesdetailview', container).hide();
|
||||
gui_object('noteseditform', container).show();
|
||||
editor.setContent(html);
|
||||
node = editor.getContentAreaContainer().childNodes[0];
|
||||
if (node) node.tabIndex = content.get(0).tabIndex;
|
||||
|
@ -948,15 +963,15 @@ function rcube_kolab_notes_ui(settings)
|
|||
editor.getBody().focus();
|
||||
}
|
||||
else
|
||||
$('.notetitle', rcmail.gui_objects.noteviewtitle).focus().select();
|
||||
$('.notetitle', titlecontainer).focus().select();
|
||||
|
||||
// read possibly re-formatted content back from editor for later comparison
|
||||
me.selected_note.description = editor.getContent({ format:'html' }).replace(/^\s*(<p><\/p>\n*)?/, '');
|
||||
is_html = true;
|
||||
}
|
||||
else {
|
||||
$(rcmail.gui_objects.noteseditform).hide();
|
||||
$(rcmail.gui_objects.notesdetailview).html(html).show();
|
||||
gui_object('noteseditform', container).hide();
|
||||
gui_object('notesdetailview', container).html(html).show();
|
||||
}
|
||||
|
||||
render_no_focus = false;
|
||||
|
@ -970,6 +985,22 @@ function rcube_kolab_notes_ui(settings)
|
|||
$(window).resize();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function gui_object(name, container)
|
||||
{
|
||||
var elem = rcmail.gui_objects[name], selector = elem;
|
||||
if (elem && elem.className && container) {
|
||||
selector = '.' + String(elem.className).split(' ').join('.');
|
||||
}
|
||||
else if (elem && elem.id) {
|
||||
selector = '#' + elem.id;
|
||||
}
|
||||
|
||||
return $(selector, container);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given plain text to HTML contents to be displayed in editor
|
||||
*/
|
||||
|
@ -987,6 +1018,193 @@ function rcube_kolab_notes_ui(settings)
|
|||
return '<pre>' + Q(str).replace(link_pattern, link_replace) + '</pre>';
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function show_history_dialog()
|
||||
{
|
||||
var dialog, rec = me.selected_note;
|
||||
if (!rec || !rec.uid || !window.libkolab_audittrail) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// render dialog
|
||||
$dialog = libkolab_audittrail.object_history_dialog({
|
||||
module: 'kolab_notes',
|
||||
container: '#notehistory',
|
||||
title: rcmail.gettext('objectchangelog','kolab_notes') + ' - ' + rec.title,
|
||||
|
||||
// callback function for list actions
|
||||
listfunc: function(action, rev) {
|
||||
var rec = $dialog.data('rec');
|
||||
saving_lock = rcmail.set_busy(true, 'loading', saving_lock);
|
||||
rcmail.http_post('action', { _do: action, _data: { uid: rec.uid, list:rec.list, rev: rev } }, saving_lock);
|
||||
},
|
||||
|
||||
// callback function for comparing two object revisions
|
||||
comparefunc: function(rev1, rev2) {
|
||||
var rec = $dialog.data('rec');
|
||||
saving_lock = rcmail.set_busy(true, 'loading', saving_lock);
|
||||
rcmail.http_post('action', { _do: 'diff', _data: { uid: rec.uid, list: rec.list, rev1: rev1, rev2: rev2 } }, saving_lock);
|
||||
}
|
||||
});
|
||||
|
||||
$dialog.data('rec', rec);
|
||||
|
||||
// fetch changelog data
|
||||
saving_lock = rcmail.set_busy(true, 'loading', saving_lock);
|
||||
rcmail.http_post('action', { _do: 'changelog', _data: { uid: rec.uid, list: rec.list } }, saving_lock);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function render_changelog(data)
|
||||
{
|
||||
var $dialog = $('#notehistory'),
|
||||
rec = $dialog.data('rec');
|
||||
|
||||
if (data === false || !data.length || !event) {
|
||||
// display 'unavailable' message
|
||||
$('<div class="notfound-message note-dialog-message warning">' + rcmail.gettext('objectchangelognotavailable','kolab_notes') + '</div>')
|
||||
.insertBefore($dialog.find('.changelog-table').hide());
|
||||
return;
|
||||
}
|
||||
|
||||
data.module = 'kolab_notes';
|
||||
libkolab_audittrail.render_changelog(data, rec, me.notebooks[rec.list]);
|
||||
|
||||
// set dialog size according to content
|
||||
dialog_resize($dialog.get(0), $dialog.height(), 600);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function render_revision(data)
|
||||
{
|
||||
data.readonly = true;
|
||||
|
||||
// clone view and render data into a dialog
|
||||
var model = rcmail.gui_containers['notedetailview'],
|
||||
container = model.clone();
|
||||
|
||||
container
|
||||
.removeAttr('id style class role')
|
||||
.find('.mce-container').remove();
|
||||
|
||||
// reset custom styles
|
||||
container.children('div, form').removeAttr('id style');
|
||||
|
||||
// open jquery UI dialog
|
||||
container.dialog({
|
||||
modal: false,
|
||||
resizable: true,
|
||||
closeOnEscape: true,
|
||||
title: data.title + ' @ ' + data.rev,
|
||||
close: function() {
|
||||
container.dialog('destroy').remove();
|
||||
},
|
||||
buttons: [
|
||||
{
|
||||
text: rcmail.gettext('close'),
|
||||
click: function() { container.dialog('close'); },
|
||||
autofocus: true
|
||||
}
|
||||
],
|
||||
width: model.width(),
|
||||
height: model.height(),
|
||||
minWidth: 450,
|
||||
minHeight: 400,
|
||||
})
|
||||
.show();
|
||||
|
||||
render_note(data, container, true);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function show_diff(data)
|
||||
{
|
||||
var rec = me.selected_note,
|
||||
$dialog = $('#notediff');
|
||||
|
||||
$dialog.find('div.form-section, h2.note-title-new').hide().data('set', false);
|
||||
|
||||
// always show title
|
||||
$('.note-title', $dialog).text(rec.title).removeClass('diff-text-old').show();
|
||||
|
||||
// show each property change
|
||||
$.each(data.changes, function(i, change) {
|
||||
var prop = change.property, r2, html = false,
|
||||
row = $('div.note-' + prop, $dialog).first();
|
||||
|
||||
// special case: title
|
||||
if (prop == 'title') {
|
||||
$('.note-title', $dialog).addClass('diff-text-old').text(change['old'] || '--');
|
||||
$('.note-title-new', $dialog).text(change['new'] || '--').show();
|
||||
}
|
||||
|
||||
// no display container for this property
|
||||
if (!row.length) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (change.diff_) {
|
||||
row.children('.diff-text-diff').html(change.diff_);
|
||||
row.children('.diff-text-old, .diff-text-new').hide();
|
||||
}
|
||||
else {
|
||||
if (!html) {
|
||||
// escape HTML characters
|
||||
change.old_ = Q(change.old_ || change['old'] || '--')
|
||||
change.new_ = Q(change.new_ || change['new'] || '--')
|
||||
}
|
||||
row.children('.diff-text-old').html(change.old_ || change['old'] || '--').show();
|
||||
row.children('.diff-text-new').html(change.new_ || change['new'] || '--').show();
|
||||
}
|
||||
|
||||
row.show().data('set', true);
|
||||
});
|
||||
|
||||
// open jquery UI dialog
|
||||
$dialog.dialog({
|
||||
modal: false,
|
||||
resizable: true,
|
||||
closeOnEscape: true,
|
||||
title: rcmail.gettext('objectdiff','kolab_notes').replace('$rev1', data.rev1).replace('$rev2', data.rev2) + ' - ' + rec.title,
|
||||
open: function() {
|
||||
$dialog.attr('aria-hidden', 'false');
|
||||
},
|
||||
close: function() {
|
||||
$dialog.dialog('destroy').attr('aria-hidden', 'true').hide();
|
||||
},
|
||||
buttons: [
|
||||
{
|
||||
text: rcmail.gettext('close'),
|
||||
click: function() { $dialog.dialog('close'); },
|
||||
autofocus: true
|
||||
}
|
||||
],
|
||||
minWidth: 400,
|
||||
width: 480
|
||||
}).show();
|
||||
|
||||
// set dialog size according to content
|
||||
dialog_resize($dialog.get(0), $dialog.height(), rcmail.gui_containers.notedetailview.width() - 40);
|
||||
}
|
||||
|
||||
// close the event history dialog
|
||||
function close_history_dialog()
|
||||
{
|
||||
$('#notehistory, #notediff').each(function(i, elem) {
|
||||
var $dialog = $(elem);
|
||||
if ($dialog.is(':ui-dialog'))
|
||||
$dialog.dialog('close');
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
@ -1176,6 +1394,7 @@ function rcube_kolab_notes_ui(settings)
|
|||
*/
|
||||
function reset_view()
|
||||
{
|
||||
close_history_dialog();
|
||||
me.selected_note = null;
|
||||
$('.notetitle', rcmail.gui_objects.noteviewtitle).val('').hide();
|
||||
$('.tagline, .dates', rcmail.gui_objects.noteviewtitle).hide();
|
||||
|
@ -1491,6 +1710,14 @@ function rcube_kolab_notes_ui(settings)
|
|||
}
|
||||
}
|
||||
|
||||
// resize and reposition (center) the dialog window
|
||||
function dialog_resize(id, height, width)
|
||||
{
|
||||
var win = $(window), w = win.width(), h = win.height();
|
||||
$(id).dialog('option', { height: Math.min(h-20, height+130), width: Math.min(w-20, width+50) })
|
||||
.dialog('option', 'position', ['center', 'center']); // only works in a separate call (!?)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* Kolab Notes plugin styles for skin "Larry"
|
||||
*
|
||||
* Copyright (C) 2014, Kolab Systems AG <contact@kolabsys.com>
|
||||
* Copyright (C) 2014-2015, 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
|
||||
|
@ -147,8 +147,45 @@
|
|||
background: #f9f9f9;
|
||||
}
|
||||
|
||||
.notesview #noteform,
|
||||
.notesview #notedetails {
|
||||
.notesview #notedetailsbox .footerright {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.notesview #notedetailsbox .formbuttons:after {
|
||||
content: "";
|
||||
display: inline;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.notesview .btn-note-history {
|
||||
display: inline-block;
|
||||
padding: 2px;
|
||||
text-decoration: none;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.notesview .btn-note-history.active {
|
||||
visibility: visible;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.notesview .btn-note-history:before {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
top: 4px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-right: 4px;
|
||||
background: url('sprites.png') -1px -302px no-repeat;
|
||||
}
|
||||
|
||||
.notesview .btn-note-history.active:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.notesview .noteform,
|
||||
.notesview .notedetails {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 82px;
|
||||
|
@ -157,7 +194,7 @@
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
.notesview #notedetails {
|
||||
.notesview .notedetails {
|
||||
padding: 8px;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
|
@ -167,12 +204,12 @@
|
|||
background: #fff;
|
||||
}
|
||||
|
||||
.notesdialog #noteform,
|
||||
.notesdialog #notedetails {
|
||||
.notesdialog .noteform,
|
||||
.notesdialog .notedetails {
|
||||
bottom: 30px;
|
||||
}
|
||||
|
||||
.notesview #notedetails pre {
|
||||
.notesview .notedetails pre {
|
||||
font-family: "Lucida Grande", Verdana, Arial, Helvetica, sans-serif;
|
||||
font-size: 12px;
|
||||
margin: 0;
|
||||
|
@ -211,13 +248,35 @@
|
|||
border: 0;
|
||||
}
|
||||
|
||||
.notesview #notedetailstitle {
|
||||
.notesview .ui-dialog-content .noteform,
|
||||
.notesview .ui-dialog-content .notedetails,
|
||||
.notesview .ui-dialog-content .notereferences {
|
||||
position: relative;
|
||||
width: auto;
|
||||
height: auto;
|
||||
top: auto;
|
||||
bottom: auto;
|
||||
overflow: visible;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.notesview .notetitle {
|
||||
height: auto;
|
||||
min-height: 20px;
|
||||
}
|
||||
|
||||
.notesview #notedetailstitle .disabled .tagedit-list,
|
||||
.notesview #notedetailstitle input.inline-edit:disabled {
|
||||
.notesview .ui-dialog-content .notetitle {
|
||||
padding: 0 0 6px 0;
|
||||
margin-top: -6px;
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.notesview .ui-dialog-content .tagline {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.notesview .notetitle .disabled .tagedit-list,
|
||||
.notesview .notetitle input.inline-edit:disabled {
|
||||
outline: none;
|
||||
padding-left: 0;
|
||||
border: 0;
|
||||
|
@ -228,8 +287,8 @@
|
|||
box-shadow: none;
|
||||
}
|
||||
|
||||
.notesview #notedetailstitle input.notetitle,
|
||||
.notesview #notedetailstitle input.notetitle:focus {
|
||||
.notesview .notetitle input.notetitle,
|
||||
.notesview .notetitle input.notetitle:focus {
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
|
@ -240,8 +299,13 @@
|
|||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.notesview #notedetailstitle .dates,
|
||||
.notesview #notedetailstitle .tagline,
|
||||
.notesview .ui-dialog-content .formbuttons,
|
||||
.notesview .ui-dialog-content .notetitle input {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.notesview .notetitle .dates,
|
||||
.notesview .notetitle .tagline,
|
||||
.notesdialog .notebookselect label {
|
||||
color: #999;
|
||||
font-weight: normal;
|
||||
|
@ -253,24 +317,28 @@
|
|||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.notesview #notedetailstitle .tagline {
|
||||
.notesview .notetitle .tagline {
|
||||
position: relative;
|
||||
cursor: text;
|
||||
margin: 4px 0 0 0;
|
||||
}
|
||||
|
||||
.notesview #notedetailstitle .tagline.disabled {
|
||||
.notesview .notetitle .tagline.disabled {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.notesview #notedetailstitle .tagline .placeholder {
|
||||
.notesview .notetitle .tagline .placeholder {
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
left: 6px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.notesview #notedetailstitle .tagedit-list {
|
||||
.notesview .notetitle .tagline.disabled .placeholder {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.notesview .notetitle .tagedit-list {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
min-height: 32px;
|
||||
|
@ -282,15 +350,15 @@
|
|||
}
|
||||
|
||||
/* Firefox 3.6 */
|
||||
_:not(), _:-moz-handler-blocked, .notesview #notedetailstitle .tagedit-list {
|
||||
_:not(), _:-moz-handler-blocked, .notesview .notetitle .tagedit-list {
|
||||
min-height: 26px;
|
||||
}
|
||||
|
||||
.notesview #notedetailstitle .disabled .tagedit-list {
|
||||
.notesview .notetitle .disabled .tagedit-list {
|
||||
min-height: 26px;
|
||||
}
|
||||
|
||||
.notesview #notedetailstitle #tagedit-input {
|
||||
.notesview .notetitle #tagedit-input {
|
||||
background: none;
|
||||
}
|
||||
|
||||
|
@ -298,15 +366,15 @@ _:not(), _:-moz-handler-blocked, .notesview #notedetailstitle .tagedit-list {
|
|||
z-index: 1000;
|
||||
}
|
||||
|
||||
.notesview #notedetailstitle .notecreated,
|
||||
.notesview #notedetailstitle .notechanged {
|
||||
.notesview .notetitle .notecreated,
|
||||
.notesview .notetitle .notechanged {
|
||||
display: inline-block;
|
||||
padding-left: 0.4em;
|
||||
padding-right: 2em;
|
||||
color: #777;
|
||||
}
|
||||
|
||||
.notesview #notereferences {
|
||||
.notesview .notereferences {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
@ -316,10 +384,41 @@ _:not(), _:-moz-handler-blocked, .notesview #notedetailstitle .tagedit-list {
|
|||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.notesdialog #notereferences {
|
||||
.notesdialog .notereferences {
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.notesview #notediff .note-title,
|
||||
.notesview #notediff .note-title-new {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.notesview .note-title.diff-text-old {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.notesview .diff-text-diff del,
|
||||
.notesview .diff-text-diff ins {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.notesview .diff-text-old,
|
||||
.notesview .diff-text-diff del {
|
||||
background-color: #fdd;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.notesview .diff-text-new,
|
||||
.notesview .diff-text-diff ins,
|
||||
.notesview .diff-text-diff .diffmod img {
|
||||
background-color: #dfd;
|
||||
}
|
||||
|
||||
.notesview .diff-text-diff img {
|
||||
border: 1px solid #999;
|
||||
}
|
||||
|
||||
.notesview #notebooksbox .scroller {
|
||||
top: 34px;
|
||||
}
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 3 KiB After Width: | Height: | Size: 5.5 KiB |
|
@ -86,22 +86,56 @@
|
|||
|
||||
<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.notetitle" id="notedetailstitle" class="notetitle boxtitle" />
|
||||
<roundcube:object name="plugin.editform" id="noteform" class="noteform" />
|
||||
<roundcube:object name="plugin.detailview" id="notedetails" class="notedetails scroller" />
|
||||
<div id="notereferences" class="notereferences">
|
||||
<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">
|
||||
<div class="formbuttons">
|
||||
<roundcube:button command="save" type="input" class="button mainaction" label="save" id="btn-save-note" />
|
||||
<div class="footerright">
|
||||
<roundcube:if condition="config:kolab_bonnie_api" />
|
||||
<roundcube:button command="history" type="link" label="kolab_notes.showhistory" class="btn-note-history" classAct="btn-note-history active" />
|
||||
<roundcube:endif />
|
||||
</div>
|
||||
</div>
|
||||
<roundcube:container name="notedetailview" id="notedetailsbox" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<roundcube:if condition="config:kolab_bonnie_api" />
|
||||
<div id="notehistory" class="uidialog" aria-hidden="true">
|
||||
<roundcube:object name="plugin.object_changelog_table" class="records-table changelog-table" domain="calendar" />
|
||||
<div class="compare-button"><input type="button" class="button" value="↳ <roundcube:label name='kolab_notes.compare' />" /></div>
|
||||
</div>
|
||||
|
||||
<div id="notediff" class="uidialog contentbox" aria-hidden="true">
|
||||
<h2 class="note-title">Note Title</h2>
|
||||
<h2 class="note-title-new diff-text-new"></h2>
|
||||
|
||||
<div class="form-section note-tags">
|
||||
<span class="diff-text-old"></span> ⇢
|
||||
<span class="diff-text-new"></span>
|
||||
</div>
|
||||
|
||||
<div class="form-section note-description">
|
||||
<div class="diff-text-diff" style="white-space:pre-wrap"></div>
|
||||
<div class="diff-text-old"></div>
|
||||
<div class="diff-text-new"></div>
|
||||
</div>
|
||||
|
||||
<div class="form-section notereferences">
|
||||
<div class="diff-text-old"></div>
|
||||
<div class="diff-text-new"></div>
|
||||
</div>
|
||||
</div>
|
||||
<roundcube:endif />
|
||||
|
||||
<roundcube:object name="message" id="messagestack" />
|
||||
|
||||
<div id="notessortmenu" class="popupmenu" aria-hidden="true">
|
||||
|
|
Loading…
Add table
Reference in a new issue