Email Resent (Bounce) feature (#4985)

pull/5859/head
Aleksander Machniak 7 years ago
parent 16f84cc9c0
commit 1b2d3c0ac2

@ -1,6 +1,7 @@
CHANGELOG Roundcube Webmail
===========================
- Email Resent (Bounce) feature (#4985)
- Various improvements for templating engine and skin behaviours
- Support conditional include
- Support for 'link' objects

@ -659,9 +659,9 @@ class rcmail_sendmail
/**
* Parse and cleanup email address input (and count addresses)
*
* @param string Address input
* @param boolean Do count recipients (saved in global $RECIPIENT_COUNT)
* @param boolean Validate addresses (errors saved in global $EMAIL_FORMAT_ERROR)
* @param string $mailto Address input
* @param boolean $count Do count recipients (count saved in $this->parse_data['RECIPIENT_COUNT'])
* @param boolean $check Validate addresses (errors saved in $this->parse_data['INVALID_EMAIL'])
*
* @return string Canonical recipients string (comma separated)
*/
@ -872,8 +872,6 @@ class rcmail_sendmail
*/
protected function compose_header_from($attrib)
{
global $MESSAGE, $OUTPUT, $RCMAIL, $COMPOSE;
// pass the following attributes to the form class
$field_attrib = array('name' => '_from');
foreach ($attrib as $attr => $value) {
@ -975,9 +973,9 @@ class rcmail_sendmail
$fvalue = rcube_utils::get_input_value('_' . $header, rcube_utils::INPUT_POST, true);
$charset = $this->rcmail->output->charset;
}
else if (!empty($COMPOSE['param'][$header])) {
$fvalue = $COMPOSE['param'][$header];
$charset = $RCMAIL->output->charset;
else if (!empty($this->data['param'][$header])) {
$fvalue = $this->data['param'][$header];
$charset = $this->rcmail->output->charset;
}
else if ($mode == self::MODE_REPLY) {
// get recipent address(es) out of the message headers
@ -1403,6 +1401,12 @@ class rcmail_sendmail
'composeformhead' => array($this, 'form_head'),
));
// add some labels to client
$this->rcmail->output->add_label('nosubject', 'nosenderwarning', 'norecipientwarning',
'nosubjectwarning', 'cancel', 'nobodywarning', 'notsentwarning', 'savingmessage',
'sendingmessage', 'searching', 'disclosedrecipwarning', 'disclosedreciptitle',
'bccinstead', 'nosubjecttitle');
$this->rcmail->output->set_env('max_disclosed_recipients', (int) $this->rcmail->config->get('max_disclosed_recipients', 5));
}

@ -257,7 +257,7 @@ function rcube_webmail()
this.set_button_titles();
this.env.message_commands = ['show', 'reply', 'reply-all', 'reply-list',
'move', 'copy', 'delete', 'open', 'mark', 'edit', 'viewsource',
'move', 'copy', 'delete', 'open', 'mark', 'edit', 'viewsource', 'bounce',
'print', 'load-attachment', 'download-attachment', 'show-headers', 'hide-headers', 'download',
'forward', 'forward-inline', 'forward-attachment', 'change-format'];
@ -352,6 +352,10 @@ function rcube_webmail()
// init message compose form
this.init_messageform();
}
else if (this.env.action == 'bounce') {
this.init_messageform_inputs();
this.enable_command('identities', true);
}
else if (this.env.action == 'get') {
this.enable_command('download', true);
@ -369,7 +373,7 @@ function rcube_webmail()
if (this.env.is_message) {
this.enable_command('reply', 'reply-all', 'edit', 'viewsource',
'forward', 'forward-inline', 'forward-attachment', true);
'forward', 'forward-inline', 'forward-attachment', 'bounce', true);
if (this.env.list_post)
this.enable_command('reply-list', true);
}
@ -1407,7 +1411,7 @@ function rcube_webmail()
case 'identities':
case 'responses':
case 'folders':
this.goto_url('settings/' + command);
this.goto_url('settings/' + command, {_framed: 0});
break;
case 'undo':
@ -4099,6 +4103,48 @@ function rcube_webmail()
return state;
};
// Display "bounce message" dialog
this.bounce = function(props, obj, event)
{
// get message uid and folder
var uid = this.get_single_uid(),
url = this.url('bounce', {_framed: 1, _uid: uid, _mbox: this.get_message_mailbox(uid)}),
dialog = $('<iframe>').attr('src', url),
submit_func = function() {
var post = {},
rc = dialog[0].contentWindow.rcmail,
form = rc.gui_objects.messageform
if (typeof form != 'object')
return false;
if (!rc.check_compose_address_fields(false, false, form))
return false;
$.each($(form).serializeArray(), function() { post[this.name] = this.value; });
post._uid = rc.env.uid;
post._mbox = rc.env.mailbox;
delete post._action;
delete post._task;
if (post._to || post._cc || post._bcc) {
ref.http_post('bounce', post, ref.set_busy(true, 'sendingmessage'));
return true;
}
};
this.hide_menu('forwardmenu');
this.simple_dialog(dialog, this.gettext('bouncemsg'), submit_func, {
button: 'bounce',
width: 400,
height: 300
});
return true;
};
/*********************************************************/
/********* login form methods *********/
@ -4145,13 +4191,13 @@ function rcube_webmail()
if (!this.gui_objects.messageform)
return false;
var i, elem, pos, input_from = $("[name='_from']"),
var elem, pos,
input_from = $("[name='_from']"),
input_to = $("[name='_to']"),
input_subject = $("input[name='_subject']"),
input_message = $("[name='_message']").get(0),
html_mode = $("input[name='_is_html']").val() == '1',
ac_fields = ['cc', 'bcc', 'replyto', 'followupto'],
ac_props, opener_rc = this.opener();
opener_rc = this.opener();
// close compose step in opener
if (opener_rc && opener_rc.env.action == 'compose') {
@ -4164,20 +4210,6 @@ function rcube_webmail()
this.env.opened_extwin = true;
}
// configure parallel autocompletion
if (this.env.autocomplete_threads > 0) {
ac_props = {
threads: this.env.autocomplete_threads,
sources: this.env.autocomplete_sources
};
}
// init live search events
this.init_address_input_events(input_to, ac_props);
for (i in ac_fields) {
this.init_address_input_events($("[name='_"+ac_fields[i]+"']"), ac_props);
}
if (!html_mode) {
pos = this.env.top_posting && this.env.compose_mode ? 0 : input_message.value.length;
@ -4209,8 +4241,7 @@ function rcube_webmail()
else if (input_message)
elem = input_message;
// focus first empty element (need to be visible on IE8)
$(elem).filter(':visible').focus();
this.init_messageform_inputs(elem);
this.env.compose_focus_elem = document.activeElement;
@ -4221,6 +4252,31 @@ function rcube_webmail()
this.auto_save_start();
};
// init autocomplete events on compose form inputs
this.init_messageform_inputs = function(focused)
{
var i, ac_props,
input_to = $("[name='_to']"),
ac_fields = ['cc', 'bcc', 'replyto', 'followupto'];
// configure parallel autocompletion
if (this.env.autocomplete_threads > 0) {
ac_props = {
threads: this.env.autocomplete_threads,
sources: this.env.autocomplete_sources
};
}
// init live search events
this.init_address_input_events(input_to, ac_props);
for (i in ac_fields) {
this.init_address_input_events($("[name='_"+ac_fields[i]+"']"), ac_props);
}
// focus first empty element
$(focused || input_to).focus();
};
this.compose_restore_dialog = function(j, html_mode)
{
var i, key, formdata, index = this.local_storage_get_item('compose.index', []);
@ -4400,14 +4456,82 @@ function rcube_webmail()
// checks the input fields before sending a message
this.check_compose_input = function(cmd, skip_recipients_checks)
{
var key,
input_subject = $("[name='_subjec']");
// check if all files has been uploaded
for (key in this.env.attachments) {
if (typeof this.env.attachments[key] === 'object' && !this.env.attachments[key].complete) {
alert(this.get_label('notuploadedwarning'));
return false;
}
}
if (!this.check_compose_address_fields(cmd, skip_recipients_checks))
return false;
// display localized warning for missing subject
if (!this.env.nosubject_warned && input_subject.val() == '') {
var prompt_value = $('<input>').attr({type: 'text', size: 40}),
myprompt = $('<div class="prompt">')
.append($('<div class="message">').text(this.get_label('nosubjectwarning')))
.append(prompt_value),
save_func = function() {
input_subject.val(prompt_value.val());
dialog.dialog('close');
ref.command(cmd, { nocheck:true }); // repeat command which triggered this
};
dialog = this.show_popup_dialog(
myprompt,
this.get_label('nosubjecttitle'),
[{
text: this.get_label('sendmessage'),
'class': 'mainaction send',
click: function() { save_func(); }
}, {
text: this.get_label('cancel'),
'class': 'cancel',
click: function() {
input_subject.focus();
dialog.dialog('close');
}
}],
{dialogClass: 'warning'}
);
prompt_value.select().keydown(function(e) {
if (e.which == 13) save_func();
});
this.env.nosubject_warned = true;
return false;
}
// check for empty body (only possible if not mailvelope encrypted)
if (!this.mailvelope_editor && !this.editor.get_content() && !confirm(this.get_label('nobodywarning'))) {
this.editor.focus();
return false;
}
// move body from html editor to textarea (just to be sure, #1485860)
this.editor.save();
return true;
};
this.check_compose_address_fields = function(cmd, skip_recipients_checks, form)
{
if (!form)
form = window;
// check input fields
var key, recipients, dialog,
limit = this.env.max_disclosed_recipients,
input_to = $("[name='_to']"),
input_cc = $("[name='_cc']"),
input_bcc = $("[name='_bcc']"),
input_from = $("[name='_from']"),
input_subject = $("[name='_subject']"),
input_to = $("[name='_to']", form),
input_cc = $("[name='_cc']", form),
input_bcc = $("[name='_bcc']", form),
input_from = $("[name='_from']", form),
get_recipients = function(fields) {
fields = $.map(fields, function(v) {
v = $.trim(v.val());
@ -4431,14 +4555,6 @@ function rcube_webmail()
return false;
}
// check if all files has been uploaded
for (key in this.env.attachments) {
if (typeof this.env.attachments[key] === 'object' && !this.env.attachments[key].complete) {
alert(this.get_label('notuploadedwarning'));
return false;
}
}
// check disclosed recipients limit
if (limit && !skip_recipients_checks && !this.env.disclosed_recipients_warned
&& rcube_check_email(recipients = get_recipients([input_to, input_cc]), true, true) > limit
@ -4452,7 +4568,8 @@ function rcube_webmail()
}
dialog.dialog('close');
ref.check_compose_input(cmd, true);
if (cmd)
ref.check_compose_input(cmd, true);
};
dialog = this.show_popup_dialog(
@ -4477,53 +4594,6 @@ function rcube_webmail()
return false;
}
// display localized warning for missing subject
if (!this.env.nosubject_warned && input_subject.val() == '') {
var prompt_value = $('<input>').attr({type: 'text', size: 40}),
myprompt = $('<div class="prompt">')
.append($('<div class="message">').text(this.get_label('nosubjectwarning')))
.append(prompt_value),
save_func = function() {
input_subject.val(prompt_value.val());
dialog.dialog('close');
ref.command(cmd, { nocheck:true }); // repeat command which triggered this
};
dialog = this.show_popup_dialog(
myprompt,
this.get_label('nosubjecttitle'),
[{
text: this.get_label('sendmessage'),
'class': 'mainaction send',
click: function() { save_func(); }
}, {
text: this.get_label('cancel'),
'class': 'cancel',
click: function() {
input_subject.focus();
dialog.dialog('close');
}
}],
{dialogClass: 'warning'}
);
prompt_value.select().keydown(function(e) {
if (e.which == 13) save_func();
});
this.env.nosubject_warned = true;
return false;
}
// check for empty body (only possible if not mailvelope encrypted)
if (!this.mailvelope_editor && !this.editor.get_content() && !confirm(this.get_label('nobodywarning'))) {
this.editor.focus();
return false;
}
// move body from html editor to textarea (just to be sure, #1485860)
this.editor.save();
return true;
};
@ -8286,6 +8356,12 @@ function rcube_webmail()
url = url.replace(/\_task=[a-z0-9_-]+/, '_task=' + RegExp.$1);
}
// force _framed=0
if (query._framed === 0) {
url = url.replace('&_framed=1', '');
query._framed = null;
}
// remove undefined values
for (k in query) {
if (query[k] !== undefined && query[k] !== null)

@ -139,6 +139,8 @@ $labels['forward'] = 'Forward';
$labels['forwardinline'] = 'Forward inline';
$labels['forwardattachment'] = 'Forward as attachment';
$labels['forwardmessage'] = 'Forward the message';
$labels['bouncemsg'] = 'Resend (bounce)';
$labels['bounce'] = 'Resend';
$labels['deletemessage'] = 'Delete message';
$labels['movemessagetotrash'] = 'Move message to trash';
$labels['printmessage'] = 'Print this message';

@ -0,0 +1,250 @@
<?php
/**
+-----------------------------------------------------------------------+
| program/steps/mail/bounce.inc |
| |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2017, The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Bounce/resend an email message |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
| Author: Aleksander Machniak <alec@alec.pl> |
+-----------------------------------------------------------------------+
*/
/**
* Mail_mime wrapper to handle mail bounce
*
* @FIXME: For some reason this class definition
* cannot be put at the end of the file
*/
class rcmail_bounce_mail extends Mail_mime
{
/**
* Constructor function
*/
public function __construct($params = array())
{
// To make the code simpler always use delay_file_io=true
$params['delay_file_io'] = true;
$params['eol'] = "\r\n";
parent::__construct($params);
}
/**
* Returns/Sets message headers
*/
public function headers($headers = array(), $overwrite = false, $skip_content = false)
{
// headers() wrapper that returns Resent-Cc, Resent-Bcc instead of Cc,Bcc
// it's also called to re-add Resent-Bcc after it has been sent (to store in Sent)
if (array_key_exists('Bcc', $headers)) {
$this->build_params['bounce_headers']['Resent-Bcc'] = $headers['Bcc'];
}
foreach ($this->build_params['bounce_headers'] as $key => $val) {
$headers[str_replace('Resent-', '', $key)] = $val;
}
return $headers;
}
/**
* Returns all message headers as string
*/
public function txtHeaders($headers = array(), $overwrite = false, $skip_content = false)
{
// i.e. add Resent-* headers on top of the original message head
$this->init_message();
$result = array();
foreach ($this->build_params['bounce_headers'] as $name => $value) {
$key = str_replace('Resent-', '', $name);
// txtHeaders() can be used to unset Bcc header
if (array_key_exists($key, $headers)) {
$value = $headers[$key];
$this->build_params['bounce_headers']['Resent-'.$key] = $value;
}
if ($value) {
$result[] = "$name: $value";
}
}
$result = implode($this->build_params['eol'], $result);
if (strlen($this->bounce_head)) {
$result .= $this->build_params['eol'] . $this->bounce_head;
}
return $result;
}
/**
* Save the message body to a file (if delay_file_io=true)
*/
public function saveMessageBody($file, $params = null)
{
$this->init_message();
// this will be called only once, so let just move the file
rename($this->bounce_body, $file);
$this->bounce_head = null;
}
protected function init_message()
{
if ($this->bounce_head !== null) {
return;
}
$rcmail = rcmail::get_instance();
$storage = $rcmail->get_storage();
$message = $this->build_params['bounce_message'];
$temp_dir = unslashify($rcmail->config->get('temp_dir'));
$path = tempnam($temp_dir, 'rcmBounce');
// We'll write the body to the file and the headers to a variable
if ($fp = fopen($path, 'w')) {
stream_filter_register('bounce_source', 'rcmail_bounce_stream_filter');
stream_filter_append($fp, 'bounce_source');
// message part
if ($message->context) {
$message->get_part_body($message->context, false, 0, $fp);
}
// complete message
else {
$storage->set_folder($message->folder);
$storage->get_raw_body($message->uid, $fp);
}
fclose($fp);
$this->bounce_head = rcmail_bounce_stream_filter::$headers;
$this->bounce_body = $path;
}
}
}
/**
* Stream filter to remove message headers from the streamed
* message source (and store them in a variable)
*/
class rcmail_bounce_stream_filter extends php_user_filter
{
public static $headers;
protected $in_body = false;
public function onCreate()
{
self::$headers = '';
}
public function filter($in, $out, &$consumed, $closing)
{
while ($bucket = stream_bucket_make_writeable($in)) {
if (!$this->in_body) {
self::$headers .= $bucket->data;
if (($pos = strpos(self::$headers, "\r\n\r\n")) === false) {
continue;
}
$bucket->data = substr(self::$headers, $pos + 4);
$bucket->datalen = strlen($bucket->data);
self::$headers = substr(self::$headers, 0, $pos);
$this->in_body = true;
}
$consumed += $bucket->datalen;
stream_bucket_append($out, $bucket);
}
return PSFS_PASS_ON;
}
}
$msg_uid = rcube_utils::get_input_value('_uid', rcube_utils::INPUT_GP);
$msg_folder = rcube_utils::get_input_value('_mbox', rcube_utils::INPUT_GP, true);
$MESSAGE = new rcube_message($msg_uid, $msg_folder);
if (!$MESSAGE->headers) {
$OUTPUT->show_message('messageopenerror', 'error');
$OUTPUT->send('iframe');
}
// Display Bounce form
if (empty($_POST)) {
if (!empty($MESSAGE->headers->charset)) {
$RCMAIL->storage->set_charset($MESSAGE->headers->charset);
}
// Initialize helper class to build the UI
$SENDMAIL = new rcmail_sendmail(
array('mode' => rcmail_sendmail::MODE_FORWARD),
array('message' => $MESSAGE)
);
$OUTPUT->set_env('mailbox', $msg_folder);
$OUTPUT->set_env('uid', $msg_uid);
$OUTPUT->send('bounce');
}
// Initialize helper class to send the message
$SENDMAIL = new rcmail_sendmail(array('mode' => rcmail_sendmail::MODE_FORWARD), array(
'sendmail' => true,
'error_handler' => function() use ($OUTPUT) {
call_user_func_array(array($OUTPUT, 'show_message'), func_get_args());
$OUTPUT->send('iframe');
}
));
// Handle the form input
$input_headers = $SENDMAIL->headers_input();
// Set Resent-* headers, these will be added on top of the bounced message
$headers = array_filter(array(
// 'Received' => $input_headers['Received'],
'Resent-From' => $input_headers['From'],
'Resent-To' => $input_headers['To'],
'Resent-Cc' => $input_headers['Cc'],
'Resent-Bcc' => $input_headers['Bcc'],
'Resent-Date' => $input_headers['Date'],
'Resent-Message-ID' => $input_headers['Message-ID'],
));
// Create the bounce message
$BOUNCE = new rcmail_bounce_mail(array(
'bounce_message' => $MESSAGE,
'bounce_headers' => $headers,
));
// Send the bounce message
$SENDMAIL->deliver_message($BOUNCE);
// Save in Sent (if requested)
$saved = $SENDMAIL->save_message($BOUNCE);
if (!$saved && strlen($SENDMAIL->options['store_target'])) {
$RCMAIL->display_server_error('errorsaving');
}
$OUTPUT->show_message('messagesent', 'confirmation', null, false);
$OUTPUT->send('iframe');

@ -74,16 +74,15 @@ if (!is_array($COMPOSE)) {
// add some labels to client
$OUTPUT->add_label('nosubject', 'nosenderwarning', 'norecipientwarning', 'nosubjectwarning', 'cancel',
'nobodywarning', 'notsentwarning', 'notuploadedwarning', 'savingmessage', 'sendingmessage',
'messagesaved', 'converting', 'editorwarning', 'searching', 'uploading', 'uploadingmany',
$OUTPUT->add_label('notuploadedwarning', 'savingmessage',
'messagesaved', 'converting', 'editorwarning', 'uploading', 'uploadingmany',
'fileuploaderror', 'sendmessage', 'newresponse', 'responsename', 'responsetext', 'save',
'savingresponse', 'restoresavedcomposedata', 'restoremessage', 'delete', 'restore', 'ignore',
'selectimportfile', 'messageissent', 'loadingdata', 'nopubkeyfor', 'nopubkeyforsender',
'encryptnoattachments','encryptedsendialog','searchpubkeyservers', 'importpubkeys',
'encryptpubkeysfound', 'search', 'close', 'import', 'keyid', 'keylength', 'keyexpired',
'keyrevoked', 'keyimportsuccess', 'keyservererror', 'attaching', 'namex', 'attachmentrename',
'disclosedrecipwarning', 'disclosedreciptitle', 'bccinstead', 'nosubjecttitle');
'keyrevoked', 'keyimportsuccess', 'keyservererror', 'attaching', 'namex', 'attachmentrename'
);
$OUTPUT->set_pagetitle($RCMAIL->gettext('compose'));

@ -110,7 +110,8 @@ if (empty($RCMAIL->action) || $RCMAIL->action == 'list') {
'copy', 'move', 'quota', 'replyall', 'replylist', 'stillsearching',
'flagged', 'unflagged', 'unread', 'deleted', 'replied', 'forwarded',
'priority', 'withattachment', 'fileuploaderror', 'mark', 'markallread',
'folders-cur', 'folders-sub', 'folders-all', 'cancel');
'folders-cur', 'folders-sub', 'folders-all', 'cancel', 'bounce', 'bouncemsg',
'sendingmessage');
}
}

@ -99,7 +99,8 @@ if ($uid) {
if (!$OUTPUT->ajax_call) {
$OUTPUT->add_label('checkingmail', 'deletemessage', 'movemessagetotrash',
'movingmessage', 'deletingmessage', 'markingmessage', 'replyall', 'replylist');
'movingmessage', 'deletingmessage', 'markingmessage', 'replyall', 'replylist',
'bounce', 'bouncemsg', 'sendingmessage');
}
// check for unset disposition notification

@ -1037,6 +1037,7 @@ function rcube_init_mail_ui()
.addEventListener('menu-open', 'menu_open', rcmail_ui)
.addEventListener('aftersend-attachment', 'uploadmenu', rcmail_ui)
.addEventListener('aftertoggle-editor', 'resize_compose_body_ev', rcmail_ui)
.addEventListener('afterbounce', function(){ rcmail_ui.show_popup('forwardmenu', false); })
.gui_object('dragmenu', 'dragmenu');
if (rcmail.gui_objects.mailboxlist) {

@ -30,6 +30,7 @@
<ul id="forwardmenumenu">
<roundcube:button type="link-menuitem" command="forward-inline" label="forwardinline" prop="sub" classAct="forwardlink active" class="forwardlink" />
<roundcube:button type="link-menuitem" command="forward-attachment" label="forwardattachment" prop="sub" classAct="forwardattachmentlink active" class="forwardattachmentlink" />
<roundcube:button type="link-menuitem" command="bounce" label="bounce" prop="sub" classAct="bouncelink active" class="bouncelink" />
<roundcube:container name="forwardmenu" id="forwardmenumenu" />
</ul>
</div>

@ -369,6 +369,7 @@
{
position: absolute;
right: 0;
left: 0;
top: 205px;
bottom: 0px;
border: 1px solid #999999;
@ -1490,6 +1491,10 @@ input.from_address
display: none;
}
#bounceheaders td.editfield {
width: 95%;
}
#compose-editorfooter
{
position: absolute;

@ -0,0 +1,42 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title><roundcube:object name="pagetitle" /></title>
<roundcube:include file="/includes/links.html" />
</head>
<body class="iframe">
<div class="compose-headers-div" id="bounceheaders" role="region" aria-labelledby="aria-label-composeheaders">
<h2 id="aria-label-composeheaders" class="voice"><roundcube:label name="arialabelmessageheaders" /></h2>
<roundcube:object name="composeFormHead" role="main" />
<table id="compose-headers"><tbody>
<tr>
<td class="title"><label for="_from"><roundcube:label name="from" /></label></td>
<td class="editfield formlinks">
<roundcube:object name="composeHeaders" part="from" form="form" id="_from" tabindex="1" />
<a href="#identities" onclick="return rcmail.command('identities')" tabindex="1"><roundcube:label name="editidents" /></a>
</td>
</tr>
<tr>
<td class="title top"><label for="_to"><roundcube:label name="to" /></label></td>
<td class="editfield"><roundcube:object name="composeHeaders" part="to" form="form" id="_to" cols="70" rows="1" tabindex="1" /></td>
</tr>
<tr>
<td class="title top"><label for="_cc"><roundcube:label name="cc" /></label></td>
<td class="editfield"><roundcube:object name="composeHeaders" part="cc" form="form" id="_cc" cols="70" rows="1" tabindex="1" /></td>
</tr>
<tr>
<td class="title top"><label for="_bcc"><roundcube:label name="bcc" /></label></td>
<td class="editfield"><roundcube:object name="composeHeaders" part="bcc" form="form" id="_bcc" cols="70" rows="1" tabindex="1" /></td>
</tr>
<roundcube:if condition="!config:no_save_sent_messages" />
<tr><td colspan="2" class="bounceopts">
<label><roundcube:label name="savesentmessagein" /> <roundcube:object name="storetarget" maxlength="30" style="max-width:12em" tabindex="1" /></label>
</td></tr>
<roundcube:endif />
</tbody></table>
</form>
</div>
</body>
</html>

@ -23,6 +23,7 @@
<ul id="forwardmenu-menu" class="toolbarmenu" role="menu" aria-labelledby="aria-label-forwardmenu">
<roundcube:button type="link-menuitem" command="forward-inline" label="forwardinline" prop="sub" classAct="forwardlink active" class="forwardlink" />
<roundcube:button type="link-menuitem" command="forward-attachment" label="forwardattachment" prop="sub" classAct="forwardattachmentlink active" class="forwardattachmentlink" />
<roundcube:button type="link-menuitem" command="bounce" label="bounce" prop="sub" classAct="bouncelink active" class="bouncelink" />
<roundcube:container name="forwardmenu" id="forwardmenu-menu" />
</ul>
</div>

@ -1164,9 +1164,16 @@ div.message-partheaders .headers-table td.header {
}
.compose-headers td.title {
width: 11%;
width: 10%;
white-space: nowrap;
padding-left: 6px;
min-width: 60px;
line-height: 16px;
}
.compose-headers td.top {
vertical-align: top;
padding-top: 5px;
}
.compose-headers td.title label {
@ -1193,17 +1200,16 @@ div.message-partheaders .headers-table td.header {
padding: 0 4px;
}
.compose-headers td.top {
vertical-align: top;
padding-top: 10px;
}
.compose-headers td textarea,
.compose-headers td input {
width: 100%;
resize: none;
}
.compose-headers td.bounceopts {
padding-top: 20px;
}
#compose-cc, #compose-bcc, #compose-replyto, #compose-followupto {
display: none;
}

@ -0,0 +1,46 @@
<roundcube:object name="doctype" value="html5" />
<html>
<head>
<title><roundcube:object name="pagetitle" /></title>
<roundcube:include file="/includes/links.html" />
</head>
<body class="iframe fullheight">
<h1 class="voice"><roundcube:object name="pagetitle" /></h1>
<div class="boxcontent" id="bounceheaders" role="region" aria-labelledby="aria-label-composeheaders">
<h2 id="aria-label-composeheaders" class="voice"><roundcube:label name="arialabelmessageheaders" /></h2>
<roundcube:object name="composeFormHead" role="main" />
<table class="headers-table compose-headers"><tbody>
<tr>
<td class="title"><label for="_from"><roundcube:label name="from" /></label></td>
<td class="editfield">
<roundcube:object name="composeHeaders" part="from" form="form" id="_from" tabindex="1" />
<a href="#identities" onclick="return rcmail.command('identities')" class="iconlink edit" tabindex="1"><roundcube:label name="editidents" /></a>
</td>
</tr>
<tr>
<td class="title top"><label for="_to"><roundcube:label name="to" /></label></td>
<td class="editfield"><roundcube:object name="composeHeaders" part="to" form="form" id="_to" cols="70" rows="1" tabindex="1" /></td>
</tr>
<tr>
<td class="title top"><label for="_cc"><roundcube:label name="cc" /></label></td>
<td class="editfield"><roundcube:object name="composeHeaders" part="cc" form="form" id="_cc" cols="70" rows="1" tabindex="1" /></td>
</tr>
<tr>
<td class="title top"><label for="_bcc"><roundcube:label name="bcc" /></label></td>
<td class="editfield"><roundcube:object name="composeHeaders" part="bcc" form="form" id="_bcc" cols="70" rows="1" tabindex="1" /></td>
</tr>
<roundcube:if condition="!config:no_save_sent_messages" />
<tr><td colspan="2" class="bounceopts">
<label><roundcube:label name="savesentmessagein" /> <roundcube:object name="storetarget" maxlength="30" style="max-width:12em" tabindex="1" /></label>
</td></tr>
<roundcube:endif />
</tbody></table>
</form>
</div>
<roundcube:include file="/includes/footer.html" />
</body>
</html>

@ -194,16 +194,7 @@ function rcube_mail_ui()
$('#responseslist a.insertresponse')[(e.active?'removeClass':'addClass')]('active');
});
// Show input elements with non-empty value
var f, v, field, fields = ['cc', 'bcc', 'replyto', 'followupto'];
for (f=0; f < fields.length; f++) {
v = fields[f]; field = $('#_'+v);
if (field.length) {
field.on('change', {v: v}, function(e) { if (this.value) show_header_row(e.data.v, true); });
if (field.val() != '')
show_header_row(v, true);
}
}
init_compose_editfields();
$('#composeoptionstoggle').click(function(e){
var expanded = $('#composeoptions').toggle().is(':visible');
@ -219,10 +210,6 @@ function rcube_mail_ui()
$('#composeoptionstoggle').click();
}
// adjust hight when textarea starts to scroll
$("textarea[name='_to'], textarea[name='_cc'], textarea[name='_bcc']").change(function(e){ adjust_compose_editfields(this); }).change();
rcmail.addEventListener('autocomplete_insert', function(p){ adjust_compose_editfields(p.field); });
// toggle compose options if opened in new window and they were visible before
var opener_rc = rcmail.opener();
if (opener_rc && opener_rc.env.action == 'compose' && $('#composeoptionstoggle', opener.document).hasClass('remove'))
@ -236,6 +223,9 @@ function rcube_mail_ui()
attachmentmenu_append(this);
});
}
else if (rcmail.env.action == 'bounce') {
init_compose_editfields();
}
else if (rcmail.env.action == 'list' || !rcmail.env.action) {
mail_layout();
@ -565,6 +555,24 @@ function rcube_mail_ui()
// STUB
}
function init_compose_editfields()
{
// Show input elements with non-empty value
var f, v, field, fields = ['cc', 'bcc', 'replyto', 'followupto'];
for (f=0; f < fields.length; f++) {
v = fields[f]; field = $('#_'+v);
if (field.length) {
field.on('change', {v: v}, function(e) { if (this.value) show_header_row(e.data.v, true); });
if (field.val() != '')
show_header_row(v, true);
}
}
// adjust hight when textarea starts to scroll
$("textarea[name='_to'], textarea[name='_cc'], textarea[name='_bcc']").change(function(e){ adjust_compose_editfields(this); }).change();
rcmail.addEventListener('autocomplete_insert', function(p){ adjust_compose_editfields(p.field); });
}
function adjust_compose_editfields(elem)
{
if (elem.nodeName == 'TEXTAREA') {
@ -582,7 +590,10 @@ function rcube_mail_ui()
form = $('#compose-content'),
bottom = $('#composeview-bottom'),
w, h, bh, ovflw, btns = 0,
minheight = 300,
minheight = 300;
if (!form.length)
return;
bh = form.height() - bottom.position().top;
ovflw = minheight - bh;

Loading…
Cancel
Save