Implement full notes display and editing from mail view

This commit is contained in:
Thomas Bruederli 2014-04-14 18:25:23 +02:00
parent e0bc40f160
commit f0ecee1e58
6 changed files with 272 additions and 52 deletions

View file

@ -34,6 +34,7 @@ class kolab_notes extends rcube_plugin
private $lists;
private $folders;
private $cache = array();
private $message_notes = array();
/**
* Required startup method of a Roundcube plugin
@ -68,6 +69,8 @@ class kolab_notes extends rcube_plugin
$this->rc->load_language($_SESSION['language'], array('notes.notes' => $this->gettext('navtitle'))); // add label for task title
if ($args['task'] == 'notes') {
$this->add_hook('storage_init', array($this, 'storage_init'));
// register task actions
$this->register_action('index', array($this, 'notes_view'));
$this->register_action('fetch', array($this, 'notes_fetch'));
@ -77,6 +80,8 @@ class kolab_notes extends rcube_plugin
$this->register_action('dialog-ui', array($this, 'dialog_view'));
}
else if ($args['task'] == 'mail') {
$this->add_hook('storage_init', array($this, 'storage_init'));
$this->add_hook('message_load', array($this, 'mail_message_load'));
$this->add_hook('message_compose', array($this, 'mail_message_compose'));
$this->add_hook('template_object_messagebody', array($this, 'mail_messagebody_html'));
@ -93,18 +98,35 @@ class kolab_notes extends rcube_plugin
))),
'messagemenu');
$this->api->output->add_label('kolab_notes.appendnote', 'save', 'cancel');
$this->api->output->add_label('kolab_notes.appendnote', 'kolab_notes.editnote', 'kolab_notes.deletenotesconfirm', 'kolab_notes.entertitle', 'save', 'delete', 'cancel', 'close');
$this->include_script('notes_mail.js');
}
}
if (!$this->rc->output->ajax_call && (!$this->rc->output->env['framed'] || in_array($args['action'], array('folder-acl','dialog-ui')))) {
require_once($this->home . '/kolab_notes_ui.php');
$this->ui = new kolab_notes_ui($this);
$this->ui->init();
$this->load_ui();
}
}
/**
* Hook into IMAP FETCH HEADER.FIELDS command and request MESSAGE-ID
*/
public function storage_init($p)
{
$p['fetch_headers'] = trim($p['fetch_headers'] . ' MESSAGE-ID');
return $p;
}
/**
* Load and initialize UI class
*/
private function load_ui()
{
require_once($this->home . '/kolab_notes_ui.php');
$this->ui = new kolab_notes_ui($this);
$this->ui->init();
}
/**
* Read available calendars for the current user and store them internally
*/
@ -205,8 +227,8 @@ class kolab_notes extends rcube_plugin
// attempt to create a default folder for this user
if (empty($this->lists)) {
#if ($this->create_list(array('name' => 'Tasks', 'color' => '0000CC', 'default' => true)))
# $this->_read_lists(true);
$folder = kolab_storage::folder_update(array('name' => 'Notes', 'type' => 'note', 'default' => true, 'subscribed' => true));
$this->_read_lists(true);
}
return $this->lists;
@ -234,10 +256,10 @@ class kolab_notes extends rcube_plugin
// resolve message reference
if ($msgref = rcube_utils::get_input_value('_msg', RCUBE_INPUT_GPC, true)) {
$storage = $this->rc->get_storage();
$storage->set_options(array('all_headers' => true));
list($uid, $folder) = explode('-', $msgref, 2);
if ($message = $storage->get_message_headers($msgref)) {
$this->rc->output->set_env('kolab_notes_template', array(
'_from_mail' => true,
'title' => $message->get('subject'),
'links' => array(array(
'uri' => $this->get_message_uri($message, $folder),
@ -346,7 +368,7 @@ class kolab_notes extends rcube_plugin
// encode for client use
if (is_array($data)) {
$this->_client_encode($data);
$this->_client_encode($data, true);
}
$this->rc->output->command('plugin.render_note', $data);
@ -393,7 +415,7 @@ class kolab_notes extends rcube_plugin
/**
* Helper method to encode the given note record for use in the client
*/
private function _client_encode(&$note)
private function _client_encode(&$note, $resolve = false)
{
foreach ($note as $key => $prop) {
if ($key[0] == '_' || $key == 'x-custom') {
@ -413,6 +435,14 @@ class kolab_notes extends rcube_plugin
$note['html'] = $this->_wash_html($note['description']);
}
// resolve message links
if (is_array($note['links'])) {
$me = $this;
$note['links'] = array_map(function($link) use ($me, $resolve){
return $me->get_message_reference($link, $resolve) ?: array('uri' => $link);
}, $note['links']);
}
return $note;
}
@ -680,11 +710,47 @@ class kolab_notes extends rcube_plugin
}
/**
* Handler for 'messagebody_html' hooks
* Lookup backend storage and find notes tagged with the given message-ID
*/
public function mail_message_load($p)
{
$this->message = $p['object'];
$this->message_notes = array();
// TODO: only query for notes if message was flagged with $KolabNotes ?
$message_id = trim($p['object']->headers->messageID, '<> ');
if ($message_id && $p['object']->uid) {
$query = array(array('tags','=','ref:' . $message_id));
foreach (kolab_storage::select($query, 'note') as $record) {
$record['list'] = kolab_storage::folder_id($record['_mailbox']);
$this->message_notes[] = $record;
}
}
}
/**
* Handler for 'messagebody_html' hook
*/
public function mail_messagebody_html($args)
{
$html = '';
foreach ($this->message_notes as $note) {
$html .= html::a(array(
'href' => $this->rc->url(array('task' => 'notes', '_list' => $note['list'], '_id' => $note['uid'])),
'class' => 'kolabnotesref',
'rel' => $note['uid'] . '@' . $note['list'],
'target' => '_blank',
), Q($note['title']));
}
// prepend note links to message body
if ($html) {
$this->load_ui();
$args['content'] = html::div('kolabmessagenotes', $html) . $args['content'];
}
return $args;
}
/**
@ -714,7 +780,7 @@ class kolab_notes extends rcube_plugin
'Subject' => $note['title'],
'Date' => $note['changed']->format('r'),
));
console($note);
if ($this->is_html($note)) {
$message->setHTMLBody($note['description']);
@ -735,9 +801,79 @@ class kolab_notes extends rcube_plugin
return $message->getMessage();
}
/**
* Build a URI representing the given message reference
*/
private function get_message_uri($headers, $folder)
{
return sprintf('imap://%s/%s#%s', $headers->folder ?: $folder, $headers->uid, urlencode($headers->messageID));
return sprintf('imap://%s/%s?message-id=%s&subject=%s',
$headers->folder ?: $folder,
$headers->uid,
urlencode($headers->messageID),
urlencode($headers->get('subject'))
);
}
/**
* Extract message reference components from the given URI
*/
private function parse_message_uri($uri)
{
$url = parse_url($uri);
if ($url['scheme'] == 'imap') {
parse_str($url['query'], $param);
$linkref = array(
'uri' => $uri,
'message_id' => $param['message-id'] ?: urldecode($url['fragment']),
'subject' => $param['subject'],
);
$path = explode('/', $url['host'] . $url['path']);
$linkref['msguid'] = array_pop($path);
$linkref['folder'] = join('/', $path);
return $linkref;
}
return false;
}
/**
* Resolve the email message reference from the given URI
*/
public function get_message_reference($uri, $resolve = false)
{
if ($linkref = $this->parse_message_uri($uri)) {
// fetch message subject
if ($resolve || empty($linkref['subject'])) {
$imap = $this->rc->get_storage();
if (!($message = $imap->get_message_headers($linrkef['msguid'], $linkref['folder']))) {
// try to find message using the message_id fragment
$index = $imap->search_once($imap->list_folders_subscribed('', '*', 'mail', null, true), 'HEADER MESSAGE-ID ' . $linkref['message_id']);
if ($index->count()) {
$uid = reset($index->get());
$folder = $index->get_parameters('MAILBOX');
if ($message = $imap->get_message_headers($uid, $folder)) {
// replace metadata
$linkref['subject'] = $message->get('subject');
$linkref['msguid'] = $message->uid;
$linkref['folder'] = $message->folder;
$linkref['uri'] = $this->get_message_uri($message, $folder);
}
}
}
}
$linkref['mailurl'] = $this->rc->url(array(
'task' => 'mail',
'action' => 'show',
'mbox' => $linkref['folder'],
'uid' => $linkref['msguid'],
'rel' => 'note',
));
}
return $linkref;
}
/**
@ -749,6 +885,14 @@ class kolab_notes extends rcube_plugin
// TODO: handle attachments
// convert link references into simple URIs
if (array_key_exists('links', $note)) {
$object['links'] = array_map(function($link){ return is_array($link) ? $link['uri'] : strval($link); }, $note['links']);
}
else {
$object['links'] = $old['links'];
}
// clean up HTML content
$object['description'] = $this->_wash_html($note['description']);
$is_html = true;

View file

@ -24,7 +24,8 @@ $labels['tabsharing'] = 'Sharing';
$labels['discard'] = 'Discard';
$labels['abort'] = 'Abort';
$labels['unsavedchanges'] = 'Unsaved Changes!';
$labels['appendnote'] = 'Add a note';
$labels['appendnote'] = 'Add a Note';
$labels['editnote'] = 'Edit Note';
$labels['savein'] = 'Save in';
$labels['savingdata'] = 'Saving data...';

View file

@ -236,6 +236,7 @@ function rcube_kolab_notes_ui(settings)
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;
@ -259,19 +260,24 @@ function rcube_kolab_notes_ui(settings)
}
init_editor();
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);
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;
@ -319,16 +325,7 @@ function rcube_kolab_notes_ui(settings)
// register click handler for message links
$(rcmail.gui_objects.notesattachmentslist).on('click', 'li a.messagelink', function(){
var uri = $(this).attr('rel');
if (uri && uri.match(/imap:\/\/.+/)) {
var path = uri.split('#')[0].substr(7).split('/'),
uid = path.pop(),
folder = path.join('/');
if (folder && uid) {
rcmail.open_window(rcmail.url('mail/show', { _mbox: folder, _uid: uid, _rel: 'note' }));
}
}
rcmail.open_window(this.href);
return false;
});
}
@ -715,13 +712,14 @@ function rcube_kolab_notes_ui(settings)
return;
}
var list = me.notebooks[data.list] || me.notebooks[me.selected_list];
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();
}
@ -753,15 +751,24 @@ function rcube_kolab_notes_ui(settings)
if (data.links) {
$.each(data.links, function(i, link){
$('<li>').addClass('link')
var li = $('<li>').addClass('link')
.addClass('message eml')
.append($('<a>')
.attr('href', '#show')
.attr('rel', link.uri)
.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);
}
*/
});
}
@ -770,6 +777,9 @@ function rcube_kolab_notes_ui(settings)
.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);
@ -781,7 +791,7 @@ function rcube_kolab_notes_ui(settings)
html = text2html(html);
}
var node, editor = tinyMCE.get('notecontent');
var node, editor = tinyMCE.get('notecontent'), is_html = false;
if (!readonly && editor) {
$(rcmail.gui_objects.notesdetailview).hide();
$(rcmail.gui_objects.noteseditform).show();
@ -795,13 +805,17 @@ function rcube_kolab_notes_ui(settings)
$('.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' })
me.selected_note.description = editor.getContent({ format:'html' });
is_html = true;
}
else {
$(rcmail.gui_objects.noteseditform).hide();
$(rcmail.gui_objects.notesdetailview).html(html).show();
}
// notify subscribers
rcmail.triggerEvent('kolab_notes_render', { data:data, readonly:readonly, html:is_html });
// Trigger resize (needed for proper editor resizing)
$(window).resize();
}
@ -1010,6 +1024,11 @@ function rcube_kolab_notes_ui(settings)
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'));
@ -1032,11 +1051,12 @@ function rcube_kolab_notes_ui(settings)
*/
function get_save_data()
{
var editor = tinyMCE.get('notecontent');
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: me.selected_note.list || me.selected_list,
list: listselect.length ? listselect.val() : me.selected_note.list || me.selected_list,
uid: me.selected_note.uid,
categories: []
};
@ -1068,7 +1088,8 @@ function rcube_kolab_notes_ui(settings)
return savedata.title != me.selected_note.title
|| savedata.description != me.selected_note.description
|| savedata.categories.join(',') != (me.selected_note.categories || []).join(',');
|| savedata.categories.join(',') != (me.selected_note.categories || []).join(',')
|| savedata.list != me.selected_note.list;
}
/**

View file

@ -103,7 +103,7 @@
}
.notesview .toolbarmenu.iconized .selected span.icon {
background: url(sprites.png) -5px -109px no-repeat;
background: url(sprites.png) -4px -110px no-repeat;
}
.notesview #notedetailsbox {
@ -137,16 +137,18 @@
width: 100%;
}
.notesdialog #noteform {
bottom: 30px;
}
.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 {
@ -266,12 +268,16 @@
position: absolute;
left: 0;
right: 0;
bottom: 0;
bottom: 41px;
height: 27px;
background: #fff;
padding-left: 10px;
}
.notesdialog #notereferences {
bottom: 0;
}
.notesview #notebooks li {
margin: 0;
height: 20px;
@ -369,4 +375,42 @@
-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;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

View file

@ -60,6 +60,9 @@
<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" />
</div>
<div class="footerleft formbuttons">
<roundcube:button command="save" type="input" class="button mainaction" label="save" />
</div>
@ -115,9 +118,16 @@ $(document).ready(function(e){
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();