From 6fa5b437a48485927e5d90abe061ee723f3b45c2 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Mon, 20 Jan 2014 18:46:28 +0100 Subject: Update to TinyMCE 4.x --- program/include/rcmail.php | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) (limited to 'program/include/rcmail.php') diff --git a/program/include/rcmail.php b/program/include/rcmail.php index a927b7946..e297b24bc 100644 --- a/program/include/rcmail.php +++ b/program/include/rcmail.php @@ -1774,17 +1774,20 @@ class rcmail extends rcube return; } - $lang = strtolower($_SESSION['language']); + $lang_codes = array($_SESSION['language']); - // TinyMCE uses two-letter lang codes, with exception of Chinese - if (strpos($lang, 'zh_') === 0) { - $lang = str_replace('_', '-', $lang); + if ($pos = strpos($_SESSION['language'], '_')) { + $lang_codes[] = substr($_SESSION['language'], 0, $pos); } - else { - $lang = substr($lang, 0, 2); + + foreach ($lang_codes as $code) { + if (file_exists(INSTALL_PATH . 'program/js/tinymce/langs/'.$code.'.js')) { + $lang = $code; + break; + } } - if (!file_exists(INSTALL_PATH . 'program/js/tiny_mce/langs/'.$lang.'.js')) { + if (empty($lang)) { $lang = 'en'; } @@ -1796,7 +1799,7 @@ class rcmail extends rcube 'spelldict' => intval($this->config->get('spellcheck_dictionary')) )); - $this->output->include_script('tiny_mce/tiny_mce.js'); + $this->output->include_script('tinymce/tinymce.min.js'); $this->output->include_script('editor.js'); $this->output->add_script("rcmail_editor_init($script)", 'docready'); } @@ -1830,8 +1833,8 @@ class rcmail extends rcube ); foreach ($emoticons as $idx => $file) { - // Cry - $search[] = '/]+\/>/i'; + // Cry + $search[] = '/]+\/>/i'; $replace[] = $idx; } -- cgit v1.2.3 From b21f8bd3ef368563f2debf4e0debfb319ce763ea Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Tue, 27 May 2014 20:08:36 +0200 Subject: Implemented image selector dialog for TinyMCE, css fixes in classic skin for TinyMCE4 --- program/include/rcmail.php | 1 + program/js/app.js | 15 ++-- program/js/editor.js | 154 ++++++++++++++++++++++++++++++++-- program/localization/en_US/labels.inc | 2 + program/steps/mail/attachments.inc | 43 +++++++++- skins/classic/common.css | 127 ++++++++++++++++++++++++++-- skins/classic/images/filedrop.png | Bin 0 -> 605 bytes skins/classic/mail.css | 7 -- skins/larry/mail.css | 1 + skins/larry/styles.css | 93 ++++++++++++++++++++ skins/larry/ui.js | 2 +- 11 files changed, 413 insertions(+), 32 deletions(-) create mode 100644 skins/classic/images/filedrop.png (limited to 'program/include/rcmail.php') diff --git a/program/include/rcmail.php b/program/include/rcmail.php index e297b24bc..ef63db9da 100644 --- a/program/include/rcmail.php +++ b/program/include/rcmail.php @@ -1799,6 +1799,7 @@ class rcmail extends rcube 'spelldict' => intval($this->config->get('spellcheck_dictionary')) )); + $this->output->add_label('selectimage', 'addimage'); $this->output->include_script('tinymce/tinymce.min.js'); $this->output->include_script('editor.js'); $this->output->add_script("rcmail_editor_init($script)", 'docready'); diff --git a/program/js/app.js b/program/js/app.js index 43dba1402..b0b2cb15c 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -539,12 +539,12 @@ function rcube_webmail() // execute all foreign onload scripts // @deprecated - for (var i in this.onloads) { - if (typeof this.onloads[i] === 'string') - eval(this.onloads[i]); - else if (typeof this.onloads[i] === 'function') - this.onloads[i](); - } + for (n in this.onloads) { + if (typeof this.onloads[n] === 'string') + eval(this.onloads[n]); + else if (typeof this.onloads[n] === 'function') + this.onloads[n](); + } // start keep-alive and refresh intervals this.start_refresh(); @@ -4010,6 +4010,9 @@ function rcube_webmail() // called from upload page this.add2attachment_list = function(name, att, upload_id) { + if (upload_id) + this.triggerEvent('fileuploaded', {name: name, attachment: att, id: upload_id}); + if (!this.gui_objects.attachmentlist) return false; diff --git a/program/js/editor.js b/program/js/editor.js index 014f61e1c..91e8c6d75 100644 --- a/program/js/editor.js +++ b/program/js/editor.js @@ -29,7 +29,6 @@ function rcmail_editor_init(config) relative_urls: false, remove_script_host: false, convert_urls: false, // #1486944 - image_list: window.rcmail_editor_images, image_description: false, paste_webkit_style: "color font-size font-family", paste_data_images: true @@ -52,7 +51,10 @@ function rcmail_editor_init(config) spellchecker_rpc_url: '../../../../../?_task=utils&_action=spell_html&_remote=1', spellchecker_enable_learn_rpc: config.spelldict, //TODO spellchecker_language: rcmail.env.spell_lang, - accessibility_focus: false + accessibility_focus: false, + file_browser_callback: rcmail_file_browser_callback, + // @todo: support more than image (types: file, image, media) + file_browser_callback_types: 'image' }); conf.setup = function(ed) { @@ -150,17 +152,151 @@ function rcmail_toggle_editor(select, textAreaId) } } -// editor callback for images listing -function rcmail_editor_images(callback) +// image selector +function rcmail_file_browser_callback(field_name, url, type, win) { - var i, file, list = []; + var i, elem, dialog, list = [], editor = tinyMCE.activeEditor; + // open image selector dialog + dialog = editor.windowManager.open({ + title: rcmail.gettext('select' + type), + width: 500, + height: 300, + html: '
    ' + + '
    ', + buttons: [{text: 'Cancel', onclick: function() { rcmail_file_browser_close(); }}] + }); + + rcmail.env.file_browser_field = field_name; + rcmail.env.file_browser_type = type; + + // fill images list with available images for (i in rcmail.env.attachments) { - file = rcmail.env.attachments[i]; - if (file.complete && file.mimetype.startsWith('image/')) { - list.push({title: file.name, value: rcmail.env.comm_path+'&_id='+rcmail.env.compose_id+'&_action=display-attachment&_file='+i}); + if (elem = rcmail_file_browser_entry(i, rcmail.env.attachments[i])) { + list.push(elem); } } - callback(list); + if (list.length) { + $('#image-selector-list > ul').append(list); + } + + // add hint about max file size (in dialog footer) + $('div.mce-abs-end', dialog.getEl()).append($('
    ').text($('#uploadform div.hint').text())); + + // enable (smart) upload button + elem = $('#image-upload-button').append($('').text(rcmail.gettext('add' + type))); + hack_file_input(elem, rcmail.gui_objects.uploadform); + + // enable drag-n-drop area + if (rcmail.gui_objects.filedrop && rcmail.env.filedrop && ((window.XMLHttpRequest && XMLHttpRequest.prototype && XMLHttpRequest.prototype.sendAsBinary) || window.FormData)) { + rcmail.env.old_file_drop = rcmail.gui_objects.filedrop; + rcmail.gui_objects.filedrop = $('#image-selector-form'); + rcmail.gui_objects.filedrop.addClass('droptarget') + .bind('dragover dragleave', function(e) { + e.preventDefault(); + e.stopPropagation(); + $(this)[(e.type == 'dragover' ? 'addClass' : 'removeClass')]('hover'); + }) + .get(0).addEventListener('drop', function(e) { return rcmail.file_dropped(e); }, false); + } + + // register handler for successful file upload + if (!rcmail.env.file_dialog_event) { + rcmail.env.file_dialog_event = true; + rcmail.addEventListener('fileuploaded', function(attr) { + var elem; + if (elem = rcmail_file_browser_entry(attr.name, attr.attachment)) { + $('#image-selector-list > ul').prepend(elem); + } + }); + } +} + +// close file browser window +function rcmail_file_browser_close(url) +{ + if (url) + $('#' + rcmail.env.file_browser_field).val(url); + + tinyMCE.activeEditor.windowManager.close(); + + if (rcmail.env.old_file_drop) + rcmail.gui_objects.filedrop = rcmail.env.old_file_drop; +} + +// creates file browser entry +function rcmail_file_browser_entry(file_id, file) +{ + if (!file.complete || !file.mimetype) { + return; + } + + if (file.mimetype.startsWith('image/')) { + var href = rcmail.env.comm_path+'&_id='+rcmail.env.compose_id+'&_action=display-attachment&_file='+file_id, + img = $('').attr({title: file.name, src: href + '&_thumbnail=1'}); + + return $('
  • ').data('url', href) + .append($('').append(img)) + .append($('').text(file.name)) + .click(function() { rcmail_file_browser_close($(this).data('url')); }); + } +} + +// create smart files upload button +function hack_file_input(elem, clone_form) +{ + var link = $(elem), + file = $(''), + form = $('
    ').attr({method: 'post', enctype: 'multipart/form-data'}), + offset = link.offset(); + + // clone existing upload form + if (clone_form) { + file.attr('name', $('input[type="file"]', clone_form).attr('name')); + form.attr('action', $(clone_form).attr('action')) + .append($('').attr({type: 'hidden', name: '_token', value: rcmail.env.request_token})); + } + + function move_file_input(e) { + file.css({top: (e.pageY - offset.top - 10) + 'px', left: (e.pageX - offset.left - 10) + 'px'}); + } + + file.attr({type: 'file', multiple: 'multiple', size: 5, title: ''}) + .change(function() { rcmail.upload_file(form, 'upload'); }) + .click(function() { setTimeout(function() { link.mouseleave(); }, 20); }) + // opacity:0 does the trick, display/visibility doesn't work + .css({opacity: 0, cursor: 'pointer', position: 'relative', outline: 'none'}) + .appendTo(form); + + // In FF and IE we need to move the browser file-input's button under the cursor + // Thanks to the size attribute above we know the length of the input field + if (navigator.userAgent.match(/Firefox|MSIE/)) + file.css({marginLeft: '-80px'}); + + // Note: now, I observe problem with cursor style on FF < 4 only + link.css({overflow: 'hidden', cursor: 'pointer'}) + .mouseenter(function() { this.__active = true; }) + // place button under the cursor + .mousemove(function(e) { + if (this.__active) + move_file_input(e); + // move the input away if button is disabled + else + $(this).mouseleave(); + }) + .mouseleave(function() { + file.css({top: '-10000px', left: '-10000px'}); + this.__active = false; + }) + .click(function(e) { + // forward click if mouse-enter event was missed + if (!this.__active) { + this.__active = true; + move_file_input(e); + file.trigger(e); + } + }) + .mouseleave() + .append(form); } diff --git a/program/localization/en_US/labels.inc b/program/localization/en_US/labels.inc index 61890a642..4b1efb5eb 100644 --- a/program/localization/en_US/labels.inc +++ b/program/localization/en_US/labels.inc @@ -226,6 +226,8 @@ $labels['returnreceipt'] = 'Return receipt'; $labels['dsn'] = 'Delivery status notification'; $labels['mailreplyintro'] = 'On $date, $sender wrote:'; $labels['originalmessage'] = 'Original Message'; +$labels['selectimage'] = 'Select image'; +$labels['addimage'] = 'Add image'; $labels['editidents'] = 'Edit identities'; $labels['spellcheck'] = 'Spell'; diff --git a/program/steps/mail/attachments.inc b/program/steps/mail/attachments.inc index 85bc36cac..c8b7f9517 100644 --- a/program/steps/mail/attachments.inc +++ b/program/steps/mail/attachments.inc @@ -60,7 +60,7 @@ if ($RCMAIL->action=='remove-attachment') { exit; } -if ($RCMAIL->action=='display-attachment') { +if ($RCMAIL->action == 'display-attachment') { $id = 'undefined'; if (preg_match('/^rcmfile(\w+)$/', $_GET['_file'], $regs)) { @@ -76,6 +76,47 @@ if ($RCMAIL->action=='display-attachment') { $attachment['size'] = $attachment['data'] ? strlen($attachment['data']) : @filesize($attachment['path']); } + // generate image thumbnail for file browser in HTML editor + if (!empty($_GET['_thumbnail'])) { + $temp_dir = $RCMAIL->config->get('temp_dir'); + $thumbnail_size = 80; + list(,$ext) = explode('/', $attachment['mimetype']); + $mimetype = $attachment['mimetype']; + $file_ident = $attachment['id'] . ':' . $attachment['mimetype'] . ':' . $attachment['size']; + $cache_basename = $temp_dir . '/' . md5($file_ident . ':' . $RCMAIL->user->ID . ':' . $thumbnail_size); + $cache_file = $cache_basename . '.' . $ext; + + // render thumbnail image if not done yet + if (!is_file($cache_file)) { + if (!$attachment['path']) { + $orig_name = $filename = $cache_basename . '.orig.' . $ext; + file_put_contents($orig_name, $attachment['data']); + } + else { + $filename = $attachment['path']; + } + + $image = new rcube_image($filename); + if ($imgtype = $image->resize($thumbnail_size, $cache_file, true)) { + $mimetype = 'image/' . $imgtype; + + if ($orig_name) { + unlink($orig_name); + } + } + } + + if (is_file($cache_file)) { + // cache for 1h + $RCMAIL->output->future_expire_header(3600); + header('Content-Type: ' . $mimetype); + header('Content-Length: ' . filesize($cache_file)); + + readfile($cache_file); + exit; + } + } + header('Content-Type: ' . $attachment['mimetype']); header('Content-Length: ' . $attachment['size']); diff --git a/skins/classic/common.css b/skins/classic/common.css index bad2114c8..3ebe82dab 100644 --- a/skins/classic/common.css +++ b/skins/classic/common.css @@ -597,14 +597,6 @@ img.uploading height: 16px; } -.mce-btn-small button { - height: 22px; -} - -.mce-btn-small i { - line-height: 16px !important; - vertical-align: text-top !important; -} /***** common table settings ******/ @@ -1122,3 +1114,122 @@ fieldset.tabbed .quota_text_high { color: white; } .quota_text_mid { color: #666; } .quota_text_low { color: #666; } + + +/********** TinyMCE styles **********/ +.mce-btn-small button +{ + height: 22px; +} + +.mce-btn-small i +{ + line-height: 16px !important; + vertical-align: text-top !important; +} + +.mce-combobox button +{ + padding: 6px 8px !important; +} + +.mce-tinymce, +.mce-panel.mce-toolbar-grp +{ + border: 0 !important; +} + +#image-selector-list +{ + position: absolute; + top: 0; + left: 0; + right: 152px; + height: 100%; + overflow-x: hidden; + overflow-y: auto; +} + +#image-selector-form +{ + position: absolute; + top: 0; + bottom: 0; + right: 0; + width: 150px; + border: 0; + border: 1px solid #FFF; + border-left: 1px solid #DDD; + background: url(images/filedrop.png) center bottom no-repeat; + text-align: center; + padding-top: 10px; +} + +#image-upload-button +{ + width: 80%; + height: 30px; +} + +#image-upload-button span +{ + position: absolute; + width: 100%; + text-align: center; + line-height: 30px; +} + +#image-selector-list li +{ + line-height: 80px; + padding: 2px 0 2px 3px; + cursor: pointer; + overflow: hidden; + text-overflow: ellipsis; +} + +#image-selector-list li:hover +{ + background-color: #F0F0F0; +} + +#image-selector-list ul li img +{ + vertical-align: middle; + max-height: 80px; +} + +#image-selector-list ul li span.name +{ + vertical-align: middle; + font-weight: bold; + padding-left: 10px; + line-height: 80px; + vertical-align: middle; +} + +#image-selector-list ul li span.img +{ + width: 80px; + text-align: center; + display: inline-block; + overflow: hidden; + line-height: 80px; + vertical-align: middle; +} + +#image-selector-form.droptarget.hover +{ + background-color: #F0F0EE; + box-shadow: 0 0 5px 0 #999; + -moz-box-shadow: 0 0 5px 0 #999; + -o-box-shadow: 0 0 5px 0 #999; +} + +div.mce-abs-end div.hint +{ + line-height: 50px; + padding-left: 10px; + color: #999; + text-shadow: 0 1px 1px #FFF; +} diff --git a/skins/classic/images/filedrop.png b/skins/classic/images/filedrop.png new file mode 100644 index 000000000..d4d455bdf Binary files /dev/null and b/skins/classic/images/filedrop.png differ diff --git a/skins/classic/mail.css b/skins/classic/mail.css index a640c0817..f6459627f 100644 --- a/skins/classic/mail.css +++ b/skins/classic/mail.css @@ -1520,13 +1520,6 @@ div.hide-headers outline: none; } -.mce-container.mce-panel -{ - border: none; - border-bottom: 1px solid #ccc; - background-image: none; -} - #compose-headers { width: 100%; diff --git a/skins/larry/mail.css b/skins/larry/mail.css index 3f8a3905e..ca29c79b4 100644 --- a/skins/larry/mail.css +++ b/skins/larry/mail.css @@ -1550,6 +1550,7 @@ div.message-partheaders .headers-table td.header { #composebodycontainer .mce-tinymce { border: 0 !important; + margin-top: 1px; } #composebodycontainer .mce-panel { diff --git a/skins/larry/styles.css b/skins/larry/styles.css index 4cdf0f0f3..5bade1c0c 100644 --- a/skins/larry/styles.css +++ b/skins/larry/styles.css @@ -2599,3 +2599,96 @@ fieldset.tab { padding: 0; margin-left: 0; } + +/*** image selector in HTML editor ***/ +#image-selector-list { + position: absolute; + top: 0; + left: 0; + right: 152px; + height: 100%; + overflow-x: hidden; + overflow-y: auto; +} + +#image-selector-form { + position: absolute; + top: 0; + bottom: 0; + right: 0; + width: 150px; + border: 0; + border: 1px solid #FFF; + border-left: 1px solid #DDD; + background: url(images/filedrop.png) center bottom no-repeat; + text-align: center; + padding-top: 10px; +} + +#image-upload-button { + width: 80%; + height: 30px; +} + +#image-upload-button span { + position: absolute; + width: 100%; + text-align: center; + line-height: 30px; +} + +#image-selector-list li { + line-height: 80px; + padding: 2px 0 2px 3px; + cursor: pointer; + overflow: hidden; + text-overflow: ellipsis; +} + +#image-selector-list li:hover { + background-color: #F0F0F0; +} + +#image-selector-list ul li img { + vertical-align: middle; + max-height: 80px; +} + +#image-selector-list ul li span.name { + vertical-align: middle; + font-weight: bold; + padding-left: 10px; +} + +#image-selector-list ul li span.img { + height: 80px; + width: 80px; + text-align: center; + display: inline-block; + overflow: hidden; + line-height: 80px; +} + +#image-selector-form.droptarget.hover, +#image-selector-form.droptarget.active { + border: 1px solid #019bc6; + box-shadow: 0 0 3px 2px rgba(71,135,177, 0.5); + -moz-box-shadow: 0 0 3px 2px rgba(71,135,177, 0.5); + -webkit-box-shadow: 0 0 3px 2px rgba(71,135,177, 0.5); + -o-box-shadow: 0 0 3px 2px rgba(71,135,177, 0.5); +} + +#image-selector-form.droptarget.hover { + background-color: #d9ecf4; + box-shadow: 0 0 5px 2px rgba(71,135,177, 0.9); + -moz-box-shadow: 0 0 5px 2px rgba(71,135,177, 0.9); + -webkit-box-shadow: 0 0 5px 2px rgba(71,135,177, 0.9); + -o-box-shadow: 0 0 5px 2px rgba(71,135,177, 0.9); +} + +div.mce-abs-end div.hint { + line-height: 50px; + padding-left: 10px; + color: #999; + text-shadow: 0 1px 1px #FFF; +} diff --git a/skins/larry/ui.js b/skins/larry/ui.js index c85c37257..5ae3962d0 100644 --- a/skins/larry/ui.js +++ b/skins/larry/ui.js @@ -527,7 +527,7 @@ function rcube_mail_ui() h = body.parent().height() - 8; body.width(w).height(h); - $('#composebodycontainer > div').width(w+8).css('margin-top', '1px'); + $('#composebodycontainer > div').width(w+8); $('#composebody_ifr').height(h + 4 - $('div.mce-toolbar').height()); $('#googie_edit_layer').height(h - 8); // $('#composebodycontainer')[(btns ? 'addClass' : 'removeClass')]('buttons'); -- cgit v1.2.3 From 646b64107a578d228a23d5b82923fb794fdb9c55 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Fri, 30 May 2014 08:32:06 +0200 Subject: Implemented Text Editor widget that integrates all operations on textareas including HTML editor and spellchecking --- program/include/rcmail.php | 6 +- program/js/app.js | 374 +++------------ program/js/editor.js | 762 ++++++++++++++++++++++--------- program/steps/mail/compose.inc | 8 +- program/steps/mail/sendmail.inc | 2 +- program/steps/settings/edit_identity.inc | 2 +- 6 files changed, 619 insertions(+), 535 deletions(-) (limited to 'program/include/rcmail.php') diff --git a/program/include/rcmail.php b/program/include/rcmail.php index 7a952cfe3..9639422ed 100644 --- a/program/include/rcmail.php +++ b/program/include/rcmail.php @@ -1786,18 +1786,18 @@ class rcmail extends rcube $lang = 'en'; } - $script = json_encode(array( + $config = array( 'mode' => $mode, 'lang' => $lang, 'skin_path' => $this->output->get_skin_path(), 'spellcheck' => intval($this->config->get('enable_spellcheck')), 'spelldict' => intval($this->config->get('spellcheck_dictionary')) - )); + ); $this->output->add_label('selectimage', 'addimage'); + $this->output->set_env('editor_config', $config); $this->output->include_script('tinymce/tinymce.min.js'); $this->output->include_script('editor.js'); - $this->output->add_script("rcmail_editor_init($script)", 'docready'); } /** diff --git a/program/js/app.js b/program/js/app.js index 8c1fcb44b..fe5815b3b 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -286,12 +286,17 @@ function rcube_webmail() // add more commands (not enabled) $.merge(this.env.compose_commands, ['add-recipient', 'firstpage', 'previouspage', 'nextpage', 'lastpage']); - if (this.env.spellcheck) { - this.env.spellcheck.spelling_state_observer = function(s) { ref.spellcheck_state(); }; + if (window.googie) { + this.env.editor_config.spellchecker = googie; + this.env.editor_config.spellcheck_observer = function(s) { ref.spellcheck_state(); }; + this.env.compose_commands.push('spellcheck') this.enable_command('spellcheck', true); } + // initialize HTML editor + this.editor_init(this.env.editor_config, this.env.composebody); + // init canned response functions if (this.gui_objects.responseslist) { $('a.insertresponse', this.gui_objects.responseslist) @@ -426,6 +431,9 @@ function rcube_webmail() else if (this.env.action == 'edit-identity' || this.env.action == 'add-identity') { this.enable_command('save', 'edit', 'toggle-editor', true); this.enable_command('delete', this.env.identities_level < 2); + + // initialize HTML editor + this.editor_init(this.env.editor_config, 'rcmfd_signature'); } else if (this.env.action == 'folders') { this.enable_command('subscribe', 'unsubscribe', 'create-folder', 'rename-folder', true); @@ -1040,17 +1048,11 @@ function rcube_webmail() case 'spellcheck': if (this.spellcheck_state()) { - this.stop_spellchecking(); + this.editor.spellcheck_stop(); } else { - if (window.tinymce && tinymce.get(this.env.composebody)) { - tinymce.execCommand('mceSpellCheck', true); - } - else if (this.env.spellcheck && this.env.spellcheck.spellCheck) { - this.env.spellcheck.spellCheck(); - } + this.editor.spellcheck_start(); } - this.spellcheck_state(); break; case 'savedraft': @@ -1272,7 +1274,7 @@ function rcube_webmail() default: var func = command.replace(/-/g, '_'); if (this[func] && typeof this[func] === 'function') { - ret = this[func](props, obj); + ret = this[func](props, obj, event); } break; } @@ -3158,7 +3160,7 @@ function rcube_webmail() if (!this.gui_objects.messageform) return false; - var input_from = $("[name='_from']"), + var i, input_from = $("[name='_from']"), input_to = $("[name='_to']"), input_subject = $("input[name='_subject']"), input_message = $("[name='_message']").get(0), @@ -3187,7 +3189,7 @@ function rcube_webmail() // init live search events this.init_address_input_events(input_to, ac_props); - for (var i in ac_fields) { + for (i in ac_fields) { this.init_address_input_events($("[name='_"+ac_fields[i]+"']"), ac_props); } @@ -3202,10 +3204,11 @@ function rcube_webmail() // check for locally stored compose data if (window.localStorage) { - var index = this.local_storage_get_item('compose.index', []); + var key, formdata, index = this.local_storage_get_item('compose.index', []); - for (var key, i = 0; i < index.length; i++) { - key = index[i], formdata = this.local_storage_get_item('compose.' + key, null, true); + for (i = 0; i < index.length; i++) { + key = index[i]; + formdata = this.local_storage_get_item('compose.' + key, null, true); if (!formdata) { continue; } @@ -3356,12 +3359,11 @@ function rcube_webmail() this.check_compose_input = function(cmd) { // check input fields - var ed, input_to = $("[name='_to']"), + var input_to = $("[name='_to']"), input_cc = $("[name='_cc']"), input_bcc = $("[name='_bcc']"), input_from = $("[name='_from']"), - input_subject = $("[name='_subject']"), - input_message = $("[name='_message']"); + input_subject = $("[name='_subject']"); // check sender (if have no identities) if (input_from.prop('type') == 'text' && !rcube_check_email(input_from.val(), true)) { @@ -3388,10 +3390,12 @@ function rcube_webmail() // display localized warning for missing subject if (input_subject.val() == '') { - var myprompt = $('
    ').html('
    ' + this.get_label('nosubjectwarning') + '
    ').appendTo(document.body); - var prompt_value = $('').attr('type', 'text').attr('size', 30).appendTo(myprompt).val(this.get_label('nosubject')); + var buttons = {}, + myprompt = $('
    ').html('
    ' + this.get_label('nosubjectwarning') + '
    ') + .appendTo(document.body), + prompt_value = $('').attr({type: 'text', size: 30}).val(this.get_label('nosubject')) + .appendTo(myprompt); - var buttons = {}; buttons[this.get_label('cancel')] = function(){ input_subject.focus(); $(this).dialog('close'); @@ -3408,100 +3412,31 @@ function rcube_webmail() buttons: buttons, close: function(event, ui) { $(this).remove() } }); + prompt_value.select(); return false; } - // Apply spellcheck changes if spell checker is active - this.stop_spellchecking(); - - if (window.tinymce) - ed = tinymce.get(this.env.composebody); - // check for empty body - if (!ed && input_message.val() == '' && !confirm(this.get_label('nobodywarning'))) { - input_message.focus(); + if (!this.editor.get_content() && !confirm(this.get_label('nobodywarning'))) { + this.editor.focus(); return false; } - else if (ed) { - if (!ed.getContent() && !confirm(this.get_label('nobodywarning'))) { - ed.focus(); - return false; - } - // move body from html editor to textarea (just to be sure, #1485860) - tinymce.triggerSave(); - } + + // move body from html editor to textarea (just to be sure, #1485860) + this.editor.save(); return true; }; - this.toggle_editor = function(props) + this.toggle_editor = function(props, obj, e) { - this.stop_spellchecking(); - - var ed, curr, content, result, - // these non-printable chars are not removed on text2html and html2text - // we can use them as temp signature replacement - sig_mark = "\u0002\u0003", - input = $('#' + props.id), - signature = this.env.identity ? this.env.signatures[this.env.identity] : null, - is_sig = signature && signature.text && signature.text.length > 1; - - if (props.mode == 'html') { - content = input.val(); - - // replace current text signature with temp mark - if (is_sig) - content = content.replace(signature.text, sig_mark); + // @todo: this should work also with many editors on page + var result = this.editor.toggle(props.html); - // convert to html - result = this.plain2html(content, function(data) { - // replace signature mark with html version of the signature - if (is_sig) - data = data.replace(sig_mark, '
    ' + signature.html + '
    '); - - input.val(data); - tinyMCE.execCommand('mceAddEditor', false, props.id); - - if (ref.env.default_font) - setTimeout(function() { - $(tinyMCE.get(props.id).getBody()).css('font-family', ref.env.default_font); - }, 500); - }); - } - else { - ed = tinyMCE.get(props.id); - - if (is_sig) { - // get current version of signature, we'll need it in - // case of html2text conversion abort - if (curr = ed.dom.get('_rc_sig')) - curr = curr.innerHTML; - - // replace current signature with some non-printable characters - // we use non-printable characters, because this replacement - // is visible to the user - // doing this after getContent() would be hard - ed.dom.setHTML('_rc_sig', sig_mark); - } - - // get html content - content = ed.getContent(); - - // convert html to text - result = this.html2plain(content, function(data) { - tinyMCE.execCommand('mceRemoveEditor', false, props.id); - - // replace signture mark with text version of the signature - if (is_sig) - data = data.replace(sig_mark, "\n" + signature.text); - - input.val(data); - }); - - // bring back current signature - if (!result && curr) - ed.dom.setHTML('_rc_sig', curr); + if (!result && e) { + // fix selector value if operation failed + $(e.target).filter('select').val(props.html ? 'plain' : 'html'); } return result; @@ -3510,30 +3445,11 @@ function rcube_webmail() this.insert_response = function(key) { var insert = this.env.textresponses[key] ? this.env.textresponses[key].text : null; + if (!insert) return false; - // insert into tinymce editor - if ($("input[name='_is_html']").val() == '1') { - var editor = tinymce.get(this.env.composebody); - editor.getWin().focus(); // correct focus in IE & Chrome - editor.selection.setContent(this.quote_html(insert).replace(/\r?\n/g, '
    '), { format:'text' }); - } - // replace selection in compose textarea - else { - var textarea = rcube_find_object(this.env.composebody), - selection = $(textarea).is(':focus') ? this.get_input_selection(textarea) : { start:0, end:0 }, - inp_value = textarea.value; - pre = inp_value.substring(0, selection.start), - end = inp_value.substring(selection.end, inp_value.length); - - // insert response text - textarea.value = pre + insert + end; - - // set caret after inserted text - this.set_caret_pos(textarea, selection.start + insert.length); - textarea.focus(); - } + this.editor.replace(insert); }; /** @@ -3541,42 +3457,8 @@ function rcube_webmail() */ this.save_response = function() { - var sigstart, text = '', strip = false; - - // get selected text from tinymce editor - if ($("input[name='_is_html']").val() == '1') { - var editor = tinymce.get(this.env.composebody); - editor.getWin().focus(); // correct focus in IE & Chrome - text = editor.selection.getContent({ format:'text' }); - - if (!text) { - text = editor.getContent({ format:'text' }); - strip = true; - } - } - // get selected text from compose textarea - else { - var textarea = rcube_find_object(this.env.composebody), sigstart; - if (textarea && $(textarea).is(':focus')) { - text = this.get_input_selection(textarea).text; - } - - if (!text && textarea) { - text = textarea.value; - strip = true; - } - } - - // strip off signature - if (strip) { - sigstart = text.indexOf('-- \n'); - if (sigstart > 0) { - text = text.substring(0, sigstart); - } - } - // show dialog to enter a name and to modify the text to be saved - var buttons = {}, + var buttons = {}, text = this.editor.get_content(true, true), html = '' + '
    ' + '
    ' + @@ -3655,30 +3537,10 @@ function rcube_webmail() return false; }; - this.stop_spellchecking = function() - { - var ed; - - if (window.tinymce && (ed = tinymce.get(this.env.composebody))) { - if (ed.plugins && ed.plugins.spellchecker && this.env.spellcheck_active) - ed.execCommand('mceSpellCheck'); - } - else if (ed = this.env.spellcheck) { - if (ed.state && ed.state != 'ready' && ed.state != 'no_error_found') - $(ed.spell_span).trigger('click'); - } - - this.spellcheck_state(); - }; - + // updates spellchecker buttons on state change this.spellcheck_state = function() { - var ed, active; - - if (window.tinymce && (ed = tinymce.get(this.env.composebody))) - active = this.env.spellcheck_active; - else if ((ed = this.env.spellcheck) && ed.state) - active = ed.state != 'ready' && ed.state != 'no_error_found'; + var active = this.editor.spellcheck_state(); if (this.buttons.spellcheck) $('#'+this.buttons.spellcheck[0].id)[active ? 'addClass' : 'removeClass']('selected'); @@ -3689,41 +3551,19 @@ function rcube_webmail() // get selected language this.spellcheck_lang = function() { - var ed; - - if (window.tinymce && (ed = tinymce.get(this.env.composebody))) - return ed.settings.spellchecker_language || this.env.spell_lang; - else if (this.env.spellcheck) - return GOOGIE_CUR_LANG; + return this.editor.get_language(); }; this.spellcheck_lang_set = function(lang) { - var ed; - - if (window.tinymce && (ed = tinymce.get(this.env.composebody))) - ed.settings.spellchecker_language = lang; - else if (this.env.spellcheck) - this.env.spellcheck.setCurrentLanguage(lang); + this.editor.set_language(lang); }; // resume spellchecking, highlight provided mispellings without new ajax request - this.spellcheck_resume = function(ishtml, data) + this.spellcheck_resume = function(data) { - if (ishtml) { - var ed = tinymce.get(this.env.composebody); - ed.settings.spellchecker_callback = function(name, text, done, error) { done(data); }; - ed.execCommand('mceSpellCheck'); - ed.settings.spellchecker_callback = null; - } - else { - var sp = this.env.spellcheck; - sp.prepare(false, true); - sp.processData(data); - } - - this.spellcheck_state(); - } + this.editor.spellcheck_resume(data); + }; this.set_draft_id = function(id) { @@ -3783,16 +3623,13 @@ function rcube_webmail() this.compose_field_hash = function(save) { // check input fields - var ed, i, id, val, str = '', hash_fields = ['to', 'cc', 'bcc', 'subject']; + var i, id, val, str = '', hash_fields = ['to', 'cc', 'bcc', 'subject']; for (i=0; i= 0) - message = message.substring(0, p) + message.substring(p+sig.length, message.length); - } - // add the new signature string - if (show_sig && this.env.signatures && this.env.signatures[id]) { - sig = this.env.signatures[id].text; - sig = sig.replace(/\r\n/g, '\n'); - - if (this.env.top_posting) { - if (p >= 0) { // in place of removed signature - message = message.substring(0, p) + sig + message.substring(p, message.length); - cursor_pos = p - 1; - } - else if (!message) { // empty message - cursor_pos = 0; - message = '\n\n' + sig; - } - else if (pos = this.get_caret_pos(input_message.get(0))) { // at cursor position - message = message.substring(0, pos) + '\n' + sig + '\n\n' + message.substring(pos, message.length); - cursor_pos = pos; - } - else { // on top - cursor_pos = 0; - message = '\n\n' + sig + '\n\n' + message.replace(/^[\r\n]+/, ''); - } - } - else { - message = message.replace(/[\r\n]+$/, ''); - cursor_pos = !this.env.top_posting && message.length ? message.length+1 : 0; - message += '\n\n' + sig; - } - } - else - cursor_pos = this.env.top_posting ? 0 : message.length; - - input_message.val(message); - - // move cursor before the signature - this.set_caret_pos(input_message.get(0), cursor_pos); - } - else if (show_sig && this.env.signatures) { // html - var editor = tinymce.get(this.env.composebody), - sigElem = editor.dom.get('_rc_sig'); - - // Append the signature as a div within the body - if (!sigElem) { - var body = editor.getBody(), - doc = editor.getDoc(); - - sigElem = doc.createElement('div'); - sigElem.setAttribute('id', '_rc_sig'); - - if (this.env.top_posting) { - // if no existing sig and top posting then insert at caret pos - editor.getWin().focus(); // correct focus in IE & Chrome - - var node = editor.selection.getNode(); - if (node.nodeName == 'BODY') { - // no real focus, insert at start - body.insertBefore(sigElem, body.firstChild); - body.insertBefore(doc.createElement('br'), body.firstChild); - } - else { - body.insertBefore(sigElem, node.nextSibling); - body.insertBefore(doc.createElement('br'), node.nextSibling); - } - } - else { - body.appendChild(sigElem); - } - } - - if (this.env.signatures[id]) - sigElem.innerHTML = this.env.signatures[id].html; - } - + this.editor.change_signature(id, show_sig); this.env.identity = id; this.triggerEvent('change_identity'); return true; @@ -6864,6 +6607,12 @@ function rcube_webmail() element.css({left: left + 'px', top: top + 'px'}); }; + // initialize HTML editor + this.editor_init = function(config, id) + { + this.editor = new rcube_text_editor(config, id); + }; + /********************************************************/ /********* html to text conversion functions *********/ @@ -7890,7 +7639,6 @@ function rcube_webmail() { return localStorage.removeItem(this.get_local_storage_prefix() + key); }; - } // end object rcube_webmail diff --git a/program/js/editor.js b/program/js/editor.js index 280eb2ae1..6540bd56a 100644 --- a/program/js/editor.js +++ b/program/js/editor.js @@ -26,13 +26,18 @@ * for the JavaScript code in this file. * * @author Eric Stadtherr + * @author Aleksander Machniak */ -// Initialize HTML editor -function rcmail_editor_init(config) +/** + * Roundcube Text Editor Widget class + * @constructor + */ +function rcube_text_editor(config, id) { - var ret, conf = { - selector: '.mce_editor', + var ref = this, + conf = { + selector: '#' + ($('#' + id).is('.mce_editor') ? id : 'fake-editor-id'), theme: 'modern', language: config.lang, content_css: config.skin_path + '/editor_content.css?v2', @@ -48,14 +53,26 @@ function rcmail_editor_init(config) paste_data_images: true }; - if (config.mode == 'identity') + // register spellchecker for plain text editor + this.spellcheck_observer = function() {}; + if (config.spellchecker) { + this.spellchecker = config.spellchecker; + if (config.spellcheck_observer) { + this.spellchecker.spelling_state_observer = this.spellcheck_observer = config.spellcheck_observer; + } + } + + // minimal editor + if (config.mode == 'identity') { $.extend(conf, { plugins: ['autolink charmap code hr link paste tabfocus textcolor'], toolbar: 'bold italic underline alignleft aligncenter alignright alignjustify' + ' | outdent indent charmap hr link unlink code forecolor' + ' | fontselect fontsizeselect' }); - else { // mail compose + } + // full-featured editor + else { $.extend(conf, { plugins: ['autolink charmap code directionality emoticons link image media nonbreaking' + ' paste table tabfocus textcolor searchreplace' + (config.spellcheck ? ' spellchecker' : '')], @@ -65,251 +82,572 @@ function rcmail_editor_init(config) spellchecker_rpc_url: '../../../../../?_task=utils&_action=spell_html&_remote=1', spellchecker_language: rcmail.env.spell_lang, accessibility_focus: false, - file_browser_callback: rcmail_file_browser_callback, + file_browser_callback: function(name, url, type, win) { ref.file_browser_callback(name, url, type); }, // @todo: support more than image (types: file, image, media) file_browser_callback_types: 'image' }); - - conf.setup = function(ed) { - ed.on('init', rcmail_editor_callback); - // add handler for spellcheck button state update - ed.on('SpellcheckStart SpellcheckEnd', function(args) { - rcmail.env.spellcheck_active = args.type == 'spellcheckstart'; - rcmail.spellcheck_state(); - }); - ed.on('keypress', function() { - rcmail.compose_type_activity++; - }); - } } // support external configuration settings e.g. from skin if (window.rcmail_editor_settings) $.extend(conf, window.rcmail_editor_settings); + conf.setup = function(ed) { + ed.on('init', function(ed) { ref.init_callback(ed); }); + // add handler for spellcheck button state update + ed.on('SpellcheckStart SpellcheckEnd', function(args) { + ref.spellcheck_active = args.type == 'spellcheckstart'; + ref.spellcheck_observer(); + }); + ed.on('keypress', function() { + rcmail.compose_type_activity++; + }); + }; + + // textarea identifier + this.id = id; + // reference to active editor (if in HTML mode) + this.editor = null; + tinymce.init(conf); -} -// react to real individual tinyMCE editor init -function rcmail_editor_callback() -{ - var css = {}, - elem = rcube_find_object('_from'), - fe = rcmail.env.compose_focus_elem; + // react to real individual tinyMCE editor init + this.init_callback = function(event) + { + this.editor = event.target; + + if (rcmail.env.action != 'compose') { + return; + } - if (rcmail.env.default_font) - css['font-family'] = rcmail.env.default_font; + var css = {}, + elem = rcube_find_object('_from'), + fe = rcmail.env.compose_focus_elem; - if (rcmail.env.default_font_size) - css['font-size'] = rcmail.env.default_font_size; + if (rcmail.env.default_font) + css['font-family'] = rcmail.env.default_font; - if (css['font-family'] || css['font-size']) - $(tinymce.get(rcmail.env.composebody).getBody()).css(css); + if (rcmail.env.default_font_size) + css['font-size'] = rcmail.env.default_font_size; - if (elem && elem.type == 'select-one') { - // insert signature (only for the first time) - if (!rcmail.env.identities_initialized) - rcmail.change_identity(elem); + if (css['font-family'] || css['font-size']) + $(this.editor.getBody()).css(css); - // Focus previously focused element - if (fe && fe.id != rcmail.env.composebody) { - // use setTimeout() for IE9 (#1488541) - window.setTimeout(function() { - window.focus(); // for WebKit (#1486674) - fe.focus(); - }, 10); + if (elem && elem.type == 'select-one') { + // insert signature (only for the first time) + if (!rcmail.env.identities_initialized) + rcmail.change_identity(elem); + + // Focus previously focused element + if (fe && fe.id != this.id) { + // use setTimeout() for IE9 (#1488541) + window.setTimeout(function() { + window.focus(); // for WebKit (#1486674) + fe.focus(); + }, 10); + } } - } - // set tabIndex and set focus to element that was focused before - rcmail_editor_tabindex(fe && fe.id == rcmail.env.composebody); - // Trigger resize (needed for proper editor resizing in some browsers) - window.setTimeout(function() { $(window).resize(); }, 100); -} + // set tabIndex and set focus to element that was focused before + this.tabindex(fe && fe.id == this.id); + // Trigger resize (needed for proper editor resizing in some browsers) + window.setTimeout(function() { $(window).resize(); }, 100); + }; -// set tabIndex on tinymce editor -function rcmail_editor_tabindex(focus) -{ - if (rcmail.env.task == 'mail') { - var editor = tinymce.get(rcmail.env.composebody); - if (editor) { - var textarea = editor.getElement(), - node = editor.getContentAreaContainer().childNodes[0]; + // set tabIndex on tinymce editor + this.tabindex = function(focus) + { + if (rcmail.env.task == 'mail' && this.editor) { + var textarea = this.editor.getElement(), + node = this.editor.getContentAreaContainer().childNodes[0]; if (textarea && node) node.tabIndex = textarea.tabIndex; if (focus) - editor.getBody().focus(); + this.editor.getBody().focus(); } - } -} + }; + + // switch html/plain mode + this.toggle = function(ishtml) + { + var curr, content, result, + // these non-printable chars are not removed on text2html and html2text + // we can use them as temp signature replacement + sig_mark = "\u0002\u0003", + input = $('#' + this.id), + signature = rcmail.env.identity ? rcmail.env.signatures[rcmail.env.identity] : null, + is_sig = signature && signature.text && signature.text.length > 1; + + // apply spellcheck changes if spell checker is active + this.spellcheck_stop(); + + if (ishtml) { + content = input.val(); + + // replace current text signature with temp mark + if (is_sig) + content = content.replace(signature.text, sig_mark); + + // convert to html + result = rcmail.plain2html(content, function(data) { + // replace signature mark with html version of the signature + if (is_sig) + data = data.replace(sig_mark, '
    ' + signature.html + '
    '); + + input.val(data); + tinymce.execCommand('mceAddEditor', false, ref.id); + + setTimeout(function() { + if (ref.editor) { + if (rcmail.env.default_font) + $(ref.editor.getBody()).css('font-family', rcmail.env.default_font); + // #1486593 + ref.tabindex(true); + } + }, 500); + }); + } + else if (this.editor) { + if (is_sig) { + // get current version of signature, we'll need it in + // case of html2text conversion abort + if (curr = this.editor.dom.get('_rc_sig')) + curr = curr.innerHTML; + + // replace current signature with some non-printable characters + // we use non-printable characters, because this replacement + // is visible to the user + // doing this after getContent() would be hard + this.editor.dom.setHTML('_rc_sig', sig_mark); + } -// switch html/plain mode -function rcmail_toggle_editor(select, textAreaId) -{ - var ishtml = select.tagName != 'SELECT' ? select.checked : select.value == 'html', - res = rcmail.command('toggle-editor', {id: textAreaId, mode: ishtml ? 'html' : 'plain'}); - - if (!res) { - if (select.tagName == 'SELECT') - select.value = 'html'; - else if (select.tagName == 'INPUT') - select.checked = true; - } - else if (ishtml) { - // #1486593 - setTimeout("rcmail_editor_tabindex(true);", 500); - } - else if (rcmail.env.composebody) { - rcube_find_object(rcmail.env.composebody).focus(); - } -} + // get html content + content = this.editor.getContent(); -// image selector -function rcmail_file_browser_callback(field_name, url, type, win) -{ - var i, elem, dialog, list = [], editor = tinyMCE.activeEditor; - - // open image selector dialog - dialog = editor.windowManager.open({ - title: rcmail.gettext('select' + type), - width: 500, - height: 300, - html: '
      ' - + '
      ', - buttons: [{text: 'Cancel', onclick: function() { rcmail_file_browser_close(); }}] - }); - - rcmail.env.file_browser_field = field_name; - rcmail.env.file_browser_type = type; - - // fill images list with available images - for (i in rcmail.env.attachments) { - if (elem = rcmail_file_browser_entry(i, rcmail.env.attachments[i])) { - list.push(elem); + // convert html to text + result = rcmail.html2plain(content, function(data) { + tinymce.execCommand('mceRemoveEditor', false, ref.id); + ref.editor = null; + + // replace signture mark with text version of the signature + if (is_sig) + data = data.replace(sig_mark, "\n" + signature.text); + + input.val(data).focus(); + }); + + // bring back current signature + if (!result && curr) + this.editor.dom.setHTML('_rc_sig', curr); } - } - if (list.length) { - $('#image-selector-list > ul').append(list); - } + return result; + }; - // add hint about max file size (in dialog footer) - $('div.mce-abs-end', dialog.getEl()).append($('
      ').text($('div.hint', rcmail.gui_objects.uploadform).text())); - - // enable (smart) upload button - elem = $('#image-upload-button').append($('').text(rcmail.gettext('add' + type))); - hack_file_input(elem, rcmail.gui_objects.uploadform); - - // enable drag-n-drop area - if (rcmail.gui_objects.filedrop && rcmail.env.filedrop && ((window.XMLHttpRequest && XMLHttpRequest.prototype && XMLHttpRequest.prototype.sendAsBinary) || window.FormData)) { - rcmail.env.old_file_drop = rcmail.gui_objects.filedrop; - rcmail.gui_objects.filedrop = $('#image-selector-form'); - rcmail.gui_objects.filedrop.addClass('droptarget') - .bind('dragover dragleave', function(e) { - e.preventDefault(); - e.stopPropagation(); - $(this)[(e.type == 'dragover' ? 'addClass' : 'removeClass')]('hover'); - }) - .get(0).addEventListener('drop', function(e) { return rcmail.file_dropped(e); }, false); - } + // start spellchecker + this.spellcheck_start = function() + { + if (this.editor) { + tinymce.execCommand('mceSpellCheck', true); + this.spellcheck_observer(); + } + else if (this.spellchecker && this.spellchecker.spellCheck) { + this.spellchecker.spellCheck(); + } + }; - // register handler for successful file upload - if (!rcmail.env.file_dialog_event) { - rcmail.env.file_dialog_event = true; - rcmail.addEventListener('fileuploaded', function(attr) { - var elem; - if (elem = rcmail_file_browser_entry(attr.name, attr.attachment)) { - $('#image-selector-list > ul').prepend(elem); - } - }); - } -} + // stop spellchecker + this.spellcheck_stop = function() + { + var ed = this.editor; -// close file browser window -function rcmail_file_browser_close(url) -{ - if (url) - $('#' + rcmail.env.file_browser_field).val(url); + if (ed) { + if (ed.plugins && ed.plugins.spellchecker && this.spellcheck_active) + ed.execCommand('mceSpellCheck'); + this.spellcheck_observer(); + } + else if (ed = this.spellchecker) { + if (ed.state && ed.state != 'ready' && ed.state != 'no_error_found') + $(ed.spell_span).trigger('click'); + } + }; + + // spellchecker state + this.spellcheck_state = function() + { + var ed; + + if (this.editor) + return this.spellcheck_active; + else if ((ed = this.spellchecker) && ed.state) + return ed.state != 'ready' && ed.state != 'no_error_found'; + }; + + // resume spellchecking, highlight provided mispellings without new ajax request + this.spellcheck_resume = function(data) + { + var ed = this.editor; + + if (ed) { + ed.settings.spellchecker_callback = function(name, text, done, error) { done(data); }; + ed.execCommand('mceSpellCheck'); + ed.settings.spellchecker_callback = null; + + this.spellcheck_observer(); + } + else if (ed = this.spellchecker) { + ed.prepare(false, true); + ed.processData(data); + } + }; - tinyMCE.activeEditor.windowManager.close(); + // get selected (spellcheker) language + this.get_language = function() + { + if (this.editor) { + return this.editor.settings.spellchecker_language || rcmail.env.spell_lang; + } + else if (this.spellchecker) { + return GOOGIE_CUR_LANG; + } + }; - if (rcmail.env.old_file_drop) - rcmail.gui_objects.filedrop = rcmail.env.old_file_drop; -} + // set language for spellchecking + this.set_language = function(lang) + { + var ed = this.editor; -// creates file browser entry -function rcmail_file_browser_entry(file_id, file) -{ - if (!file.complete || !file.mimetype) { - return; - } + if (ed) { + ed.settings.spellchecker_language = lang; + } + if (ed = this.spellchecker) { + ed.setCurrentLanguage(lang); + } + }; - if (file.mimetype.startsWith('image/')) { - var href = rcmail.env.comm_path+'&_id='+rcmail.env.compose_id+'&_action=display-attachment&_file='+file_id, - img = $('').attr({title: file.name, src: href + '&_thumbnail=1'}); + // replace selection with text snippet + this.replace = function(text) + { + var ed = this.editor; - return $('
    • ').data('url', href) - .append($('').append(img)) - .append($('').text(file.name)) - .click(function() { rcmail_file_browser_close($(this).data('url')); }); - } -} + // insert into tinymce editor + if (ed) { + ed.getWin().focus(); // correct focus in IE & Chrome + ed.selection.setContent(rcmail.quote_html(text).replace(/\r?\n/g, '
      '), { format:'text' }); + } + // replace selection in compose textarea + else if (ed = rcube_find_object(this.id)) { + var selection = $(ed).is(':focus') ? rcmail.get_input_selection(ed) : { start:0, end:0 }, + inp_value = ed.value; + pre = inp_value.substring(0, selection.start), + end = inp_value.substring(selection.end, inp_value.length); + + // insert response text + ed.value = pre + text + end; + + // set caret after inserted text + rcmail.set_caret_pos(ed, selection.start + text.length); + ed.focus(); + } + }; + + // get selected text (if no selection returns all text) from the editor + this.get_content = function(selected, plain) + { + // apply spellcheck changes if spell checker is active + this.spellcheck_stop(); + + var sigstart, ed = this.editor, + format = plain ? 'text' : 'html', + text = '', strip = false; + + // get selected text from tinymce editor + if (ed) { + ed.getWin().focus(); // correct focus in IE & Chrome + if (selected) + text = ed.selection.getContent({format: format}); + + if (!text) { + text = ed.getContent({format: format}); + strip = true; + } + } + // get selected text from compose textarea + else if (ed = rcube_find_object(this.id)) { + if (selected && $(ed).is(':focus')) { + text = rcmail.get_input_selection(ed).text; + } -// create smart files upload button -function hack_file_input(elem, clone_form) -{ - var link = $(elem), - file = $(''), - form = $('').attr({method: 'post', enctype: 'multipart/form-data'}), - offset = link.offset(); - - // clone existing upload form - if (clone_form) { - file.attr('name', $('input[type="file"]', clone_form).attr('name')); - form.attr('action', $(clone_form).attr('action')) - .append($('').attr({type: 'hidden', name: '_token', value: rcmail.env.request_token})); - } + if (!text) { + text = ed.value; + strip = true; + } + } - function move_file_input(e) { - file.css({top: (e.pageY - offset.top - 10) + 'px', left: (e.pageX - offset.left - 10) + 'px'}); - } + // strip off signature + if (strip) { + sigstart = text.indexOf('-- \n'); + if (sigstart > 0) { + text = text.substring(0, sigstart); + } + } - file.attr({type: 'file', multiple: 'multiple', size: 5, title: ''}) - .change(function() { rcmail.upload_file(form, 'upload'); }) - .click(function() { setTimeout(function() { link.mouseleave(); }, 20); }) - // opacity:0 does the trick, display/visibility doesn't work - .css({opacity: 0, cursor: 'pointer', position: 'relative', outline: 'none'}) - .appendTo(form); - - // In FF and IE we need to move the browser file-input's button under the cursor - // Thanks to the size attribute above we know the length of the input field - if (navigator.userAgent.match(/Firefox|MSIE/)) - file.css({marginLeft: '-80px'}); - - // Note: now, I observe problem with cursor style on FF < 4 only - link.css({overflow: 'hidden', cursor: 'pointer'}) - .mouseenter(function() { this.__active = true; }) - // place button under the cursor - .mousemove(function(e) { - if (this.__active) - move_file_input(e); - // move the input away if button is disabled + return text; + }; + + // change user signature text + this.change_signature = function(id, show_sig) + { + var cursor_pos, p = -1, + input_message = $('#' + this.id), + message = input_message.val(), + sig = rcmail.env.identity; + + if (!this.editor) { // plain text mode + // remove the 'old' signature + if (show_sig && sig && rcmail.env.signatures && rcmail.env.signatures[sig]) { + sig = rcmail.env.signatures[sig].text; + sig = sig.replace(/\r\n/g, '\n'); + + p = rcmail.env.top_posting ? message.indexOf(sig) : message.lastIndexOf(sig); + if (p >= 0) + message = message.substring(0, p) + message.substring(p+sig.length, message.length); + } + // add the new signature string + if (show_sig && rcmail.env.signatures && rcmail.env.signatures[id]) { + sig = rcmail.env.signatures[id].text; + sig = sig.replace(/\r\n/g, '\n'); + + if (rcmail.env.top_posting) { + if (p >= 0) { // in place of removed signature + message = message.substring(0, p) + sig + message.substring(p, message.length); + cursor_pos = p - 1; + } + else if (!message) { // empty message + cursor_pos = 0; + message = '\n\n' + sig; + } + else if (pos = rcmail.get_caret_pos(input_message.get(0))) { // at cursor position + message = message.substring(0, pos) + '\n' + sig + '\n\n' + message.substring(pos, message.length); + cursor_pos = pos; + } + else { // on top + cursor_pos = 0; + message = '\n\n' + sig + '\n\n' + message.replace(/^[\r\n]+/, ''); + } + } + else { + message = message.replace(/[\r\n]+$/, ''); + cursor_pos = !rcmail.env.top_posting && message.length ? message.length+1 : 0; + message += '\n\n' + sig; + } + } else - $(this).mouseleave(); - }) - .mouseleave(function() { - file.css({top: '-10000px', left: '-10000px'}); - this.__active = false; - }) - .click(function(e) { - // forward click if mouse-enter event was missed - if (!this.__active) { - this.__active = true; - move_file_input(e); - file.trigger(e); + cursor_pos = rcmail.env.top_posting ? 0 : message.length; + + input_message.val(message); + + // move cursor before the signature + rcmail.set_caret_pos(input_message.get(0), cursor_pos); + } + else if (show_sig && rcmail.env.signatures) { // html + var sigElem = this.editor.dom.get('_rc_sig'); + + // Append the signature as a div within the body + if (!sigElem) { + var body = this.editor.getBody(), + doc = this.editor.getDoc(); + + sigElem = doc.createElement('div'); + sigElem.setAttribute('id', '_rc_sig'); + + if (rcmail.env.top_posting) { + // if no existing sig and top posting then insert at caret pos + this.editor.getWin().focus(); // correct focus in IE & Chrome + + var node = this.editor.selection.getNode(); + if (node.nodeName == 'BODY') { + // no real focus, insert at start + body.insertBefore(sigElem, body.firstChild); + body.insertBefore(doc.createElement('br'), body.firstChild); + } + else { + body.insertBefore(sigElem, node.nextSibling); + body.insertBefore(doc.createElement('br'), node.nextSibling); + } + } + else { + body.appendChild(sigElem); + } + } + + if (rcmail.env.signatures[id]) { + sigElem.innerHTML = rcmail.env.signatures[id].html; } - }) - .mouseleave() - .append(form); + } + }; + + // trigger content save + this.save = function() + { + if (this.editor) { + this.editor.save(); + } + }; + + // focus the editing area + this.focus = function() + { + (this.editor || rcube_find_object(this.id)).focus(); + }; + + // image selector + this.file_browser_callback = function(field_name, url, type) + { + var i, elem, dialog, list = []; + + // open image selector dialog + dialog = this.editor.windowManager.open({ + title: rcmail.gettext('select' + type), + width: 500, + height: 300, + html: '
        ' + + '
        ', + buttons: [{text: 'Cancel', onclick: function() { ref.file_browser_close(); }}] + }); + + rcmail.env.file_browser_field = field_name; + rcmail.env.file_browser_type = type; + + // fill images list with available images + for (i in rcmail.env.attachments) { + if (elem = ref.file_browser_entry(i, rcmail.env.attachments[i])) { + list.push(elem); + } + } + + if (list.length) { + $('#image-selector-list > ul').append(list); + } + + // add hint about max file size (in dialog footer) + $('div.mce-abs-end', dialog.getEl()).append($('
        ').text($('div.hint', rcmail.gui_objects.uploadform).text())); + + // enable (smart) upload button + elem = $('#image-upload-button').append($('').text(rcmail.gettext('add' + type))); + this.hack_file_input(elem, rcmail.gui_objects.uploadform); + + // enable drag-n-drop area + if (rcmail.gui_objects.filedrop && rcmail.env.filedrop && ((window.XMLHttpRequest && XMLHttpRequest.prototype && XMLHttpRequest.prototype.sendAsBinary) || window.FormData)) { + rcmail.env.old_file_drop = rcmail.gui_objects.filedrop; + rcmail.gui_objects.filedrop = $('#image-selector-form'); + rcmail.gui_objects.filedrop.addClass('droptarget') + .bind('dragover dragleave', function(e) { + e.preventDefault(); + e.stopPropagation(); + $(this)[(e.type == 'dragover' ? 'addClass' : 'removeClass')]('hover'); + }) + .get(0).addEventListener('drop', function(e) { return rcmail.file_dropped(e); }, false); + } + + // register handler for successful file upload + if (!rcmail.env.file_dialog_event) { + rcmail.env.file_dialog_event = true; + rcmail.addEventListener('fileuploaded', function(attr) { + var elem; + if (elem = ref.file_browser_entry(attr.name, attr.attachment)) { + $('#image-selector-list > ul').prepend(elem); + } + }); + } + }; + + // close file browser window + this.file_browser_close = function(url) + { + if (url) + $('#' + rcmail.env.file_browser_field).val(url); + + this.editor.windowManager.close(); + + if (rcmail.env.old_file_drop) + rcmail.gui_objects.filedrop = rcmail.env.old_file_drop; + }; + + // creates file browser entry + this.file_browser_entry = function(file_id, file) + { + if (!file.complete || !file.mimetype) { + return; + } + + if (file.mimetype.startsWith('image/')) { + var href = rcmail.env.comm_path+'&_id='+rcmail.env.compose_id+'&_action=display-attachment&_file='+file_id, + img = $('').attr({title: file.name, src: href + '&_thumbnail=1'}); + + return $('
      • ').data('url', href) + .append($('').append(img)) + .append($('').text(file.name)) + .click(function() { ref.file_browser_close($(this).data('url')); }); + } + }; + + // create smart files upload button + this.hack_file_input = function(elem, clone_form) + { + var link = $(elem), + file = $(''), + form = $('').attr({method: 'post', enctype: 'multipart/form-data'}), + offset = link.offset(); + + // clone existing upload form + if (clone_form) { + file.attr('name', $('input[type="file"]', clone_form).attr('name')); + form.attr('action', $(clone_form).attr('action')) + .append($('').attr({type: 'hidden', name: '_token', value: rcmail.env.request_token})); + } + + function move_file_input(e) { + file.css({top: (e.pageY - offset.top - 10) + 'px', left: (e.pageX - offset.left - 10) + 'px'}); + } + + file.attr({type: 'file', multiple: 'multiple', size: 5, title: ''}) + .change(function() { rcmail.upload_file(form, 'upload'); }) + .click(function() { setTimeout(function() { link.mouseleave(); }, 20); }) + // opacity:0 does the trick, display/visibility doesn't work + .css({opacity: 0, cursor: 'pointer', position: 'relative', outline: 'none'}) + .appendTo(form); + + // In FF and IE we need to move the browser file-input's button under the cursor + // Thanks to the size attribute above we know the length of the input field + if (navigator.userAgent.match(/Firefox|MSIE/)) + file.css({marginLeft: '-80px'}); + + // Note: now, I observe problem with cursor style on FF < 4 only + link.css({overflow: 'hidden', cursor: 'pointer'}) + .mouseenter(function() { this.__active = true; }) + // place button under the cursor + .mousemove(function(e) { + if (this.__active) + move_file_input(e); + // move the input away if button is disabled + else + $(this).mouseleave(); + }) + .mouseleave(function() { + file.css({top: '-10000px', left: '-10000px'}); + this.__active = false; + }) + .click(function(e) { + // forward click if mouse-enter event was missed + if (!this.__active) { + this.__active = true; + move_file_input(e); + file.trigger(e); + } + }) + .mouseleave() + .append(form); + }; } diff --git a/program/steps/mail/compose.inc b/program/steps/mail/compose.inc index c44ba434a..6baf6e79a 100644 --- a/program/steps/mail/compose.inc +++ b/program/steps/mail/compose.inc @@ -951,8 +951,7 @@ function rcmail_compose_body($attrib) "googie.setLanguages(%s);\n". "googie.setCurrentLanguage('%s');\n". "googie.setDecoration(false);\n". - "googie.decorateTextarea('%s');\n". - "%s.set_env('spellcheck', googie);", + "googie.decorateTextarea('%s');\n", $RCMAIL->output->get_skin_path(), $RCMAIL->url(array('_task' => 'utils', '_action' => 'spell', '_remote' => 1)), !empty($dictionary) ? 'true' : 'false', @@ -964,8 +963,7 @@ function rcmail_compose_body($attrib) rcube::JQ(rcube::Q($RCMAIL->gettext('addtodict'))), rcube_output::json_serialize($spellcheck_langs), $lang, - $attrib['id'], - rcmail_output::JS_OBJECT_NAME), 'foot'); + $attrib['id']), 'foot'); $OUTPUT->add_label('checking'); $OUTPUT->set_env('spellcheck_langs', join(',', $editor_lang_set)); @@ -1704,7 +1702,7 @@ function rcmail_editor_selector($attrib) if (empty($attrib['name'])) $attrib['name'] = 'editorSelect'; - $attrib['onchange'] = "return rcmail_toggle_editor(this, '".$attrib['editorid']."')"; + $attrib['onchange'] = "return rcmail.command('toggle-editor', {id: '".$attrib['editorid']."', html: this.value == 'html'}, '', event)"; $select = new html_select($attrib); diff --git a/program/steps/mail/sendmail.inc b/program/steps/mail/sendmail.inc index 7ae03e522..4737ce368 100644 --- a/program/steps/mail/sendmail.inc +++ b/program/steps/mail/sendmail.inc @@ -320,7 +320,7 @@ if (!$savedraft) { } $OUTPUT->show_message('mispellingsfound', 'error'); - $OUTPUT->command('spellcheck_resume', $isHtml, $result); + $OUTPUT->command('spellcheck_resume', $result); $OUTPUT->send('iframe'); } } diff --git a/program/steps/settings/edit_identity.inc b/program/steps/settings/edit_identity.inc index 3f7b6a58a..e43a7bb60 100644 --- a/program/steps/settings/edit_identity.inc +++ b/program/steps/settings/edit_identity.inc @@ -96,7 +96,7 @@ function rcube_identity_form($attrib) 'spellcheck' => true), 'html_signature' => array('type' => 'checkbox', 'label' => $RCMAIL->gettext('htmlsignature'), - 'onclick' => 'return rcmail_toggle_editor(this, \'rcmfd_signature\');'), + 'onclick' => 'return rcmail.command(\'toggle-editor\', {id: \'rcmfd_signature\', html: this.checked}, \'\', event)'), )) ); -- cgit v1.2.3 From b408e0bc532e6023248c6671c5cef52d1c06f3f3 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Fri, 30 May 2014 10:53:19 +0200 Subject: Display a warning if popup window was blocked (#1489618) --- CHANGELOG | 1 + program/include/rcmail.php | 3 ++- program/js/app.js | 10 +++++++--- program/localization/en_US/messages.inc | 1 + 4 files changed, 11 insertions(+), 4 deletions(-) (limited to 'program/include/rcmail.php') diff --git a/CHANGELOG b/CHANGELOG index c9e3f386b..b0cca3a19 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ CHANGELOG Roundcube Webmail =========================== +- Display a warning if popup window was blocked (#1489618) - Remove (was: ...) from message subject on reply (#1489375) - Update to TinyMCE 4.0 (#1489057) - Enable autolink plugin in TinyMCE (#1488845) diff --git a/program/include/rcmail.php b/program/include/rcmail.php index 9639422ed..a6ba18312 100644 --- a/program/include/rcmail.php +++ b/program/include/rcmail.php @@ -428,7 +428,8 @@ class rcmail extends rcube } // add some basic labels to client - $this->output->add_label('loading', 'servererror', 'connerror', 'requesttimedout', 'refreshing'); + $this->output->add_label('loading', 'servererror', 'connerror', 'requesttimedout', + 'refreshing', 'windowopenerror'); return $this->output; } diff --git a/program/js/app.js b/program/js/app.js index 914bb0278..cd0737e6d 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -685,9 +685,6 @@ function rcube_webmail() form.target = win.name; form.submit(); } - else { - // this.display_message(this.get_label('windowopenerror'), 'error'); - } } else { this.open_window(this.env.permaurl, true); @@ -1780,6 +1777,13 @@ function rcube_webmail() +(toolbar ? ',toolbar=yes,menubar=yes,status=yes' : ',toolbar=no,menubar=no,status=no')); } + // detect popup blocker (#1489618) + // don't care this might not work with all browsers + if (!extwin || extwin.closed) { + this.display_message(this.get_label('windowopenerror'), 'warning'); + return; + } + // write loading... message to empty windows if (!url && extwin.document) { extwin.document.write('' + this.get_label('loading') + ''); diff --git a/program/localization/en_US/messages.inc b/program/localization/en_US/messages.inc index 0d0a6916c..d4fbd6148 100644 --- a/program/localization/en_US/messages.inc +++ b/program/localization/en_US/messages.inc @@ -26,6 +26,7 @@ $messages['servererror'] = 'Server Error!'; $messages['servererrormsg'] = 'Server Error: $msg'; $messages['connerror'] = 'Connection Error (Failed to reach the server)!'; $messages['dberror'] = 'Database Error!'; +$messages['windowopenerror'] = 'The popup window was blocked!'; $messages['requesttimedout'] = 'Request timed out'; $messages['errorreadonly'] = 'Unable to perform operation. Folder is read-only.'; $messages['errornoperm'] = 'Unable to perform operation. Permission denied.'; -- cgit v1.2.3 From d58c39126f6e1754e29b6f3bbc01f0f6a3ea2581 Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Mon, 2 Jun 2014 16:35:12 +0200 Subject: Some more improvemements on content structure, text representation and keyboard navigation within the mail view --- program/include/rcmail.php | 2 +- program/include/rcmail_output_html.php | 4 ++-- program/js/app.js | 35 ++++++++++++++++++++++++------- program/js/list.js | 3 +-- program/localization/en_US/labels.inc | 3 +++ program/steps/mail/list_contacts.inc | 2 +- program/steps/mail/search_contacts.inc | 2 +- skins/classic/mail.css | 1 + skins/larry/styles.css | 6 ++++++ skins/larry/templates/compose.html | 10 ++++----- skins/larry/templates/mail.html | 1 + skins/larry/templates/messagepart.html | 4 ++-- skins/larry/templates/messagepreview.html | 4 ++-- 13 files changed, 54 insertions(+), 23 deletions(-) (limited to 'program/include/rcmail.php') diff --git a/program/include/rcmail.php b/program/include/rcmail.php index cc70739cf..8e66e85e7 100644 --- a/program/include/rcmail.php +++ b/program/include/rcmail.php @@ -1501,7 +1501,7 @@ class rcmail extends rcube $html_name = $this->Q($foldername) . ($unread ? html::span('unreadcount', sprintf($attrib['unreadwrap'], $unread)) : ''); $link_attrib = $folder['virtual'] ? array() : array( 'href' => $this->url(array('_mbox' => $folder['id'])), - 'onclick' => sprintf("return %s.command('list','%s',this)", rcmail_output::JS_OBJECT_NAME, $js_name), + 'onclick' => sprintf("return %s.command('list','%s',this,event)", rcmail_output::JS_OBJECT_NAME, $js_name), 'rel' => $folder['id'], 'title' => $title, ); diff --git a/program/include/rcmail_output_html.php b/program/include/rcmail_output_html.php index 1fa0376fb..e8b5c9828 100644 --- a/program/include/rcmail_output_html.php +++ b/program/include/rcmail_output_html.php @@ -859,10 +859,10 @@ EOF; } // localize title and summary attributes - if (!empty($attrib['title']) && $this->app->text_exists($attrib['title'])) { + if ($command != 'button' && !empty($attrib['title']) && $this->app->text_exists($attrib['title'])) { $attrib['title'] = $this->app->gettext($attrib['title']); } - if (!empty($attrib['summary']) && $this->app->text_exists($attrib['summary'])) { + if ($command != 'button' && !empty($attrib['summary']) && $this->app->text_exists($attrib['summary'])) { $attrib['summary'] = $this->app->gettext($attrib['summary']); } diff --git a/program/js/app.js b/program/js/app.js index bf163c614..e7f9d02b6 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -342,7 +342,16 @@ function rcube_webmail() .addEventListener('initrow', function(o) { ref.triggerEvent('insertrow', { cid:o.uid, row:o }); }) .addEventListener('select', function(o) { ref.compose_recipient_select(o); }) .addEventListener('dblclick', function(o) { ref.compose_add_recipient('to'); }) - .addEventListener('keypress', function(o) { if (o.key_pressed == o.ENTER_KEY) ref.compose_add_recipient('to'); }) + .addEventListener('keypress', function(o) { + if (o.key_pressed == o.ENTER_KEY) { + if (!ref.compose_add_recipient('to')) { + // execute link action on if not a recipient entry + if (o.last_selected && String(o.last_selected).charAt(0) == 'G') { + $(o.rows[o.last_selected].obj).find('a').first().click(); + } + } + } + }) .init(); } @@ -602,7 +611,7 @@ function rcube_webmail() { var ret, uid, cid, url, flag, aborted = false; - if (obj && obj.blur) + if (obj && obj.blur && !(event || rcube_event.is_keyboard(event))) obj.blur(); // do nothing if interface is locked by other command (with exception for searching reset) @@ -1647,9 +1656,12 @@ function rcube_webmail() { // Helper method to move focus to the next/prev active menu item var focus_menu_item = function(dir) { - var obj, mod = dir < 0 ? 'prevAll' : 'nextAll', limit = dir < 0 ? 'last' : 'first'; + var obj, item, mod = dir < 0 ? 'prevAll' : 'nextAll', limit = dir < 0 ? 'last' : 'first'; if (ref.focused_menu && (obj = $('#'+ref.focused_menu))) { - return obj.find(':focus').closest('li')[mod](':has(:not([aria-disabled=true]))').find('a,input')[limit]().focus().length; + item = obj.find(':focus').closest('li')[mod](':has(:not([aria-disabled=true]))').find('a,input')[limit](); + if (!item.length) + item = obj.find(':focus').closest('ul')[mod](':has(:not([aria-disabled=true]))').find('a,input')[limit](); + return item.focus().length; } return 0; @@ -2402,7 +2414,6 @@ function rcube_webmail() this.clear_message_list = function() { this.env.messages = {}; - this.last_selected = 0; this.show_contentframe(false); if (this.message_list) @@ -3490,6 +3501,8 @@ function rcube_webmail() input.val(oldval + recipients.join(delim + ' ') + delim + ' '); this.triggerEvent('add-recipient', { field:field, recipients:recipients }); } + + return recipients.length; }; // checks the input fields before sending a message @@ -7405,7 +7418,8 @@ function rcube_webmail() this.enable_command('set-listmode', this.env.threads && !is_multifolder); if ((response.action == 'list' || response.action == 'search') && this.message_list) { - this.message_list.focus(); + if (this.message_list.rowcount > 0) + this.message_list.focus(); this.msglist_select(this.message_list); this.triggerEvent('listupdate', { folder:this.env.mailbox, rowcount:this.message_list.rowcount }); } @@ -7417,11 +7431,18 @@ function rcube_webmail() this.enable_command('search-create', this.env.source == ''); this.enable_command('search-delete', this.env.search_id); this.update_group_commands(); - this.contact_list.focus(); + if (this.contact_list.rowcount > 0) + this.contact_list.focus(); this.triggerEvent('listupdate', { folder:this.env.source, rowcount:this.contact_list.rowcount }); } } break; + + case 'list-contacts': + case 'search-contacts': + if (this.contact_list && this.contact_list.rowcount > 0) + this.contact_list.focus(); + break; } if (response.unlock) diff --git a/program/js/list.js b/program/js/list.js index 0bf5d568b..65d4a9260 100644 --- a/program/js/list.js +++ b/program/js/list.js @@ -274,11 +274,10 @@ clear: function(sel) this.rows = {}; this.rowcount = 0; + this.last_selected = 0; if (sel) this.clear_selection(); - else - this.last_selected = 0; // reset scroll position (in Opera) if (this.frame) diff --git a/program/localization/en_US/labels.inc b/program/localization/en_US/labels.inc index 0b5ff8c5a..874f19be2 100644 --- a/program/localization/en_US/labels.inc +++ b/program/localization/en_US/labels.inc @@ -591,11 +591,14 @@ $labels['arialabelmessagelistoptions'] = 'Message list display and sorting optio $labels['arialabelmailimportdialog'] = 'Message import dialog'; $labels['arialabelmessagenav'] = 'Message navigation'; $labels['arialabelmessagebody'] = 'Message Body'; +$labels['arialabelmessageactions'] = 'Message actions'; $labels['arialabelcontactquicksearch'] = 'Contacts search form'; $labels['arialabelcontactsearchbox'] = 'Contact search input'; $labels['arialabelmessageheaders'] = 'Message headers'; $labels['arialabelcomposeoptions'] = 'Composition options'; $labels['arialabelresponsesmenu'] = 'Canned responses menu'; $labels['arialabelattachmentuploadform'] = 'Attachment upload form'; +$labels['arialabelattachmentpreview'] = 'Attachment preview'; +$labels['ariasummarycomposecontacts'] = 'List of contacts and groups to select as recipients'; ?> diff --git a/program/steps/mail/list_contacts.inc b/program/steps/mail/list_contacts.inc index 0ee81135b..4f17beffd 100644 --- a/program/steps/mail/list_contacts.inc +++ b/program/steps/mail/list_contacts.inc @@ -110,7 +110,7 @@ else if (!empty($result) && $result->count > 0) { $keyname = $row['_type'] == 'group' ? 'contactgroup' : 'contact'; $OUTPUT->command('add_contact_row', $row_id, array( - $keyname => html::span(array('title' => $email), rcube::Q($name ? $name : $email) . + $keyname => html::a(array('title' => $email), rcube::Q($name ? $name : $email) . ($name && count($emails) > 1 ? ' ' . html::span('email', rcube::Q($email)) : '') )), $classname); } diff --git a/program/steps/mail/search_contacts.inc b/program/steps/mail/search_contacts.inc index d56581695..ccef32dd2 100644 --- a/program/steps/mail/search_contacts.inc +++ b/program/steps/mail/search_contacts.inc @@ -87,7 +87,7 @@ if (!empty($result) && $result->count > 0) { $row_id = $row['ID'].'-'.$i; $jsresult[$row_id] = format_email_recipient($email, $name); $OUTPUT->command('add_contact_row', $row_id, array( - 'contact' => html::span(array('title' => $email), rcube::Q($name ? $name : $email) . + 'contact' => html::a(array('title' => $email), rcube::Q($name ? $name : $email) . ($name && count($emails) > 1 ? ' ' . html::span('email', rcube::Q($email)) : '') )), 'person'); } diff --git a/skins/classic/mail.css b/skins/classic/mail.css index 0a4653a3a..3310ac54f 100644 --- a/skins/classic/mail.css +++ b/skins/classic/mail.css @@ -780,6 +780,7 @@ table.messagelist.fixedcopy -o-text-overflow: ellipsis; border-bottom: 1px solid #EBEBEB; cursor: default; + outline: none; } .messagelist tbody tr td a diff --git a/skins/larry/styles.css b/skins/larry/styles.css index d7ad74c01..add47324d 100644 --- a/skins/larry/styles.css +++ b/skins/larry/styles.css @@ -1187,6 +1187,12 @@ a.iconlink.upload { outline: none; } +.listing tbody td a { + color: #376572; + text-shadow: 0px 1px 1px #fff; + text-decoration: none; +} + .webkit .listing tbody td { height: 14px; } diff --git a/skins/larry/templates/compose.html b/skins/larry/templates/compose.html index 87993ed70..c643b36f5 100644 --- a/skins/larry/templates/compose.html +++ b/skins/larry/templates/compose.html @@ -52,7 +52,7 @@
      • - +
        @@ -93,25 +93,25 @@ - x + - x + - x + - x + diff --git a/skins/larry/templates/mail.html b/skins/larry/templates/mail.html index 92b01e019..cf8ff2f7d 100644 --- a/skins/larry/templates/mail.html +++ b/skins/larry/templates/mail.html @@ -75,6 +75,7 @@ diff --git a/skins/larry/templates/messagepart.html b/skins/larry/templates/messagepart.html index 2df9c7b77..edf275f6e 100644 --- a/skins/larry/templates/messagepart.html +++ b/skins/larry/templates/messagepart.html @@ -29,9 +29,9 @@
        -

        Attachment preview

        +

        - +
        diff --git a/skins/larry/templates/messagepreview.html b/skins/larry/templates/messagepreview.html index 97efdf3e7..82ecd7a43 100644 --- a/skins/larry/templates/messagepreview.html +++ b/skins/larry/templates/messagepreview.html @@ -10,7 +10,7 @@
        -

        Message Body

        +

        -- cgit v1.2.3 From 77043f8469ba8e7b64af5b0192b0297ded5c615f Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Tue, 3 Jun 2014 19:13:54 +0200 Subject: Apply accessibility improvements to the settings section --- plugins/managesieve/skins/larry/managesieve.css | 8 +- program/include/rcmail.php | 7 +- program/localization/en_US/labels.inc | 4 + program/steps/settings/edit_folder.inc | 3 +- program/steps/settings/func.inc | 2 +- program/steps/settings/responses.inc | 2 +- skins/larry/includes/footer.html | 1 + skins/larry/includes/settingstabs.html | 8 +- skins/larry/settings.css | 101 ++++++++++++------------ skins/larry/templates/addressbook.html | 4 +- skins/larry/templates/folders.html | 25 +++--- skins/larry/templates/identities.html | 12 +-- skins/larry/templates/importcontacts.html | 2 +- skins/larry/templates/responses.html | 12 +-- skins/larry/templates/settings.html | 11 ++- skins/larry/ui.js | 4 +- 16 files changed, 117 insertions(+), 89 deletions(-) (limited to 'program/include/rcmail.php') diff --git a/plugins/managesieve/skins/larry/managesieve.css b/plugins/managesieve/skins/larry/managesieve.css index 1f954caf2..2172c60b2 100644 --- a/plugins/managesieve/skins/larry/managesieve.css +++ b/plugins/managesieve/skins/larry/managesieve.css @@ -417,11 +417,13 @@ body.iframe.mail #filter-form /* vacation form */ -#settings-sections span.vacation a { - background: url(images/vacation_icons.png) no-repeat 7px 1px; +#settings-sections .vacation a { + background-image: url(images/vacation_icons.png); + background-repeat: no-repeat; + background-position: 7px 1px; } -#settings-sections span.vacation.selected a { +#settings-sections .vacation.selected a { background-position: 7px -23px; } diff --git a/program/include/rcmail.php b/program/include/rcmail.php index 8e66e85e7..5a5d4cb83 100644 --- a/program/include/rcmail.php +++ b/program/include/rcmail.php @@ -1088,14 +1088,17 @@ class rcmail extends rcube } else { foreach ($table_data as $row_data) { - $class = !empty($row_data['class']) ? $row_data['class'] : ''; + $class = !empty($row_data['class']) ? $row_data['class'] : null; + if (!empty($attrib['rowclass'])) + $class = trim($class . ' ' . $attrib['rowclass']); $rowid = 'rcmrow' . rcube_utils::html_identifier($row_data[$id_col]); $table->add_row(array('id' => $rowid, 'class' => $class)); // format each col foreach ($a_show_cols as $col) { - $table->add($col, $this->Q(is_array($row_data[$col]) ? $row_data[$col][0] : $row_data[$col])); + $val = is_array($row_data[$col]) ? $row_data[$col][0] : $row_data[$col]; + $table->add($col, empty($attrib['ishtml']) ? $this->Q($val) : $val); } } } diff --git a/program/localization/en_US/labels.inc b/program/localization/en_US/labels.inc index 2b44ac188..9d3863497 100644 --- a/program/localization/en_US/labels.inc +++ b/program/localization/en_US/labels.inc @@ -474,6 +474,7 @@ $labels['miscfolding'] = 'RFC 2047/2231 (MS Outlook)'; $labels['2047folding'] = 'Full RFC 2047 (other)'; $labels['force7bit'] = 'Use MIME encoding for 8-bit characters'; $labels['advancedoptions'] = 'Advanced options'; +$labels['toggleadvancedoptions'] = 'Toggle advanced options'; $labels['focusonnewmessage'] = 'Focus browser window on new message'; $labels['checkallfolders'] = 'Check all folders for new messages'; $labels['displaynext'] = 'After message delete/move display the next message'; @@ -605,6 +606,9 @@ $labels['arialabelattachmentpreview'] = 'Attachment preview'; $labels['ariasummarycomposecontacts'] = 'List of contacts and groups to select as recipients'; $labels['arialabelcontactexportoptions'] = 'Contact export options'; $labels['arialabelabookgroupoptions'] = 'Addressbook/group options'; +$labels['arialabelpreferencesform'] = 'Preferences form'; +$labels['arialabelidentityeditfrom'] = 'Identity edit form'; +$labels['arialabelresonseeditfrom'] = 'Response edit form'; $labels['helplistnavigation'] = 'List keyboard navigation'; $labels['helplistkeyboardnavigation'] = "Arrows up/down: Move row focus/selection. diff --git a/program/steps/settings/edit_folder.inc b/program/steps/settings/edit_folder.inc index 6b7bd08d2..c61ac6da9 100644 --- a/program/steps/settings/edit_folder.inc +++ b/program/steps/settings/edit_folder.inc @@ -132,6 +132,7 @@ function rcmail_folder_form($attrib) } $select = $RCMAIL->folder_selector(array( + 'id' => '_parent', 'name' => '_parent', 'noselection' => '---', 'realnames' => false, @@ -155,7 +156,7 @@ function rcmail_folder_form($attrib) // Settings: threading if ($threading_supported && ($mbox_imap == 'INBOX' || (!$options['noselect'] && !$options['is_root']))) { - $select = new html_select(array('name' => '_viewmode', 'id' => '_listmode')); + $select = new html_select(array('name' => '_viewmode', 'id' => '_viewmode')); $select->add($RCMAIL->gettext('list'), 0); $select->add($RCMAIL->gettext('threads'), 1); diff --git a/program/steps/settings/func.inc b/program/steps/settings/func.inc index bccd9caa8..5da01b757 100644 --- a/program/steps/settings/func.inc +++ b/program/steps/settings/func.inc @@ -343,7 +343,7 @@ function rcmail_user_prefs($current = null) if (is_array($meta) && $meta['name']) { $skinname = $meta['name']; $author_link = $meta['url'] ? html::a(array('href' => $meta['url'], 'target' => '_blank'), rcube::Q($meta['author'])) : rcube::Q($meta['author']); - $license_link = $meta['license-url'] ? html::a(array('href' => $meta['license-url'], 'target' => '_blank'), rcube::Q($meta['license'])) : rcube::Q($meta['license']); + $license_link = $meta['license-url'] ? html::a(array('href' => $meta['license-url'], 'target' => '_blank', 'tabindex' => '-1'), rcube::Q($meta['license'])) : rcube::Q($meta['license']); } $skinnames[] = mb_strtolower($skinname); diff --git a/program/steps/settings/responses.inc b/program/steps/settings/responses.inc index 06093b3b8..ddd1924fe 100644 --- a/program/steps/settings/responses.inc +++ b/program/steps/settings/responses.inc @@ -95,7 +95,7 @@ function rcmail_responses_list($attrib) { global $RCMAIL, $OUTPUT; - $attrib += array('id' => 'rcmresponseslist', 'tagname' => 'table', 'cols' => 1); + $attrib += array('id' => 'rcmresponseslist', 'tagname' => 'table'); $plugin = $RCMAIL->plugins->exec_hook('responses_list', array( 'list' => $RCMAIL->get_compose_responses(true), diff --git a/skins/larry/includes/footer.html b/skins/larry/includes/footer.html index f421ec5b0..6cd3e62d1 100644 --- a/skins/larry/includes/footer.html +++ b/skins/larry/includes/footer.html @@ -6,6 +6,7 @@ var UI = new rcube_mail_ui(); $(document).ready(function(){ UI.set('errortitle', ''); + UI.set('toggleoptions', ''); UI.init(); }); diff --git a/skins/larry/includes/settingstabs.html b/skins/larry/includes/settingstabs.html index e62695848..d43e8f075 100644 --- a/skins/larry/includes/settingstabs.html +++ b/skins/larry/includes/settingstabs.html @@ -1,7 +1,9 @@ -
        -

        + diff --git a/skins/larry/settings.css b/skins/larry/settings.css index 675ef9973..0517f3c3a 100644 --- a/skins/larry/settings.css +++ b/skins/larry/settings.css @@ -70,7 +70,7 @@ width: 20px; height: 18px; background: url('images/listicons.png') 0 -1157px no-repeat; - text-indent: 1000px; + text-indent: -5000px; overflow: hidden; } @@ -78,9 +78,10 @@ background-position: -24px -1137px; } -#sections-table tbody td.section, -#settings-sections span.listitem a, -#settings-sections span.tablink a { +#sections-table td.section, +#sections-table .listitem span, +#settings-sections .listitem a, +#settings-sections .tablink a { padding-left: 36px; background-image: url(images/listicons.png); background-position: -100px 0; @@ -88,120 +89,120 @@ } /* note: support span.tablink because this is used by plugins */ -#settings-sections span.listitem a, -#settings-sections span.tablink a { +#settings-sections .listitem a, +#settings-sections .tablink a { background-position: 6px -862px; } -#settings-sections span.selected a, -#settings-sections span.tablink.selected a { +#settings-sections .selected a, +#settings-sections .tablink.selected a { background-position: 6px -887px; } -#settings-sections span.preferences a { +#settings-sections .preferences a { background-position: 6px -431px; } -#settings-sections span.preferences.selected a { +#settings-sections .preferences.selected a { background-position: 6px -455px; } -#settings-sections span.folders a, -#sections-table #rcmrowfolders td.section { +#settings-sections .folders a, +#sections-table #rcmrowfolders .section { background-position: 6px 2px; } -#settings-sections span.folders.selected a, -#sections-table #rcmrowfolders.selected td.section { +#settings-sections .folders.selected a, +#sections-table #rcmrowfolders.selected .section { background-position: 6px -22px; } -#settings-sections span.identities a { +#settings-sections .identities a { background-position: 6px -478px; } -#settings-sections span.identities.selected a { +#settings-sections .identities.selected a { background-position: 6px -502px; } -#settings-sections span.filter a { +#settings-sections .filter a { background-position: 6px -1746px; } -#settings-sections span.filter.selected a { +#settings-sections .filter.selected a { background-position: 6px -1770px; } -#settings-sections span.password a { +#settings-sections .password a { background-position: 6px -1795px; } -#settings-sections span.password.selected a { +#settings-sections .password.selected a { background-position: 6px -1819px; } -#settings-sections span.responses a { +#settings-sections .responses a { background-position: 6px -1972px; } -#settings-sections span.responses.selected a { +#settings-sections .responses.selected a { background-position: 6px -1996px; } -#sections-table #rcmrowgeneral td.section { - background-position: 6px -573px; +#sections-table #rcmrowgeneral .section { + background-position: 4px -573px; } -#sections-table #rcmrowgeneral.selected td.section { - background-position: 6px -598px; +#sections-table #rcmrowgeneral.selected .section { + background-position: 4px -598px; } -#sections-table #rcmrowmailbox td.section { - background-position: 6px -621px; +#sections-table #rcmrowmailbox .section { + background-position: 4px -621px; } -#sections-table #rcmrowmailbox.selected td.section { - background-position: 6px -646px; +#sections-table #rcmrowmailbox.selected .section { + background-position: 4px -646px; } -#sections-table #rcmrowcompose td.section { - background-position: 6px -670px; +#sections-table #rcmrowcompose .section { + background-position: 4px -670px; } -#sections-table #rcmrowcompose.selected td.section { - background-position: 6px -695px; +#sections-table #rcmrowcompose.selected .section { + background-position: 4px -695px; } -#sections-table #rcmrowmailview td.section { - background-position: 6px -718px; +#sections-table #rcmrowmailview .section { + background-position: 4px -718px; } -#sections-table #rcmrowmailview.selected td.section { - background-position: 6px -742px; +#sections-table #rcmrowmailview.selected .section { + background-position: 4px -742px; } -#sections-table #rcmrowaddressbook td.section { - background-position: 6px -766px; +#sections-table #rcmrowaddressbook .section { + background-position: 4px -766px; } -#sections-table #rcmrowaddressbook.selected td.section { - background-position: 6px -791px; +#sections-table #rcmrowaddressbook.selected .section { + background-position: 4px -791px; } -#sections-table #rcmrowserver td.section { - background-position: 6px -814px; +#sections-table #rcmrowserver .section { + background-position: 4px -814px; } -#sections-table #rcmrowserver.selected td.section { - background-position: 6px -838px; +#sections-table #rcmrowserver.selected .section { + background-position: 4px -838px; } -#sections-table #rcmrowcalendar td.section { - background-position: 6px -526px; +#sections-table #rcmrowcalendar .section { + background-position: 4px -526px; } -#sections-table #rcmrowcalendar.selected td.section { - background-position: 6px -550px; +#sections-table #rcmrowcalendar.selected .section { + background-position: 4px -550px; } #folderslist, diff --git a/skins/larry/templates/addressbook.html b/skins/larry/templates/addressbook.html index c9aa5acad..e1101e648 100644 --- a/skins/larry/templates/addressbook.html +++ b/skins/larry/templates/addressbook.html @@ -84,10 +84,10 @@
        -
        +
        -
        +
        diff --git a/skins/larry/templates/folders.html b/skins/larry/templates/folders.html index 56396bf1d..ffb0a7ee1 100644 --- a/skins/larry/templates/folders.html +++ b/skins/larry/templates/folders.html @@ -10,9 +10,11 @@
        +

        :

        + -
        +

        @@ -20,11 +22,22 @@
        + + +
        @@ -37,14 +50,6 @@
        -
        -
          -
        • -
        • - -
        -
        - diff --git a/skins/larry/templates/identities.html b/skins/larry/templates/identities.html index e3d2cc842..91f7f8f71 100644 --- a/skins/larry/templates/identities.html +++ b/skins/larry/templates/identities.html @@ -10,23 +10,25 @@
        +

        :

        + -
        +
        -

        +

        - +
        - +
        - +
        diff --git a/skins/larry/templates/importcontacts.html b/skins/larry/templates/importcontacts.html index a670d0354..2bc1d4a26 100644 --- a/skins/larry/templates/importcontacts.html +++ b/skins/larry/templates/importcontacts.html @@ -15,7 +15,7 @@
        -

        +

        diff --git a/skins/larry/templates/responses.html b/skins/larry/templates/responses.html index 8e6884539..503ed2177 100644 --- a/skins/larry/templates/responses.html +++ b/skins/larry/templates/responses.html @@ -10,23 +10,25 @@
        +

        :

        + -
        +
        -

        +

        - +
        - +
        - +
        diff --git a/skins/larry/templates/settings.html b/skins/larry/templates/settings.html index 08df7686e..406b9c9b3 100644 --- a/skins/larry/templates/settings.html +++ b/skins/larry/templates/settings.html @@ -10,19 +10,22 @@
        +

        :

        +
        -
        + -
        +
        - +
        diff --git a/skins/larry/ui.js b/skins/larry/ui.js index ff0b2e41a..6385e73b1 100644 --- a/skins/larry/ui.js +++ b/skins/larry/ui.js @@ -273,7 +273,9 @@ function rcube_mail_ui() orientation:'v', relative:true, start:266, min:180, size:12 }).init(); } else if (rcmail.env.action == 'edit-prefs') { - $('') + $('') + .text(env.toggleoptions) + .attr('title', env.toggleoptions) .addClass('advanced-toggle') .appendTo('#preferences-details fieldset.advanced legend'); -- cgit v1.2.3 From c5bfe69e2199d6dc92136d5e1ebcc9cdeb180bf5 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Wed, 4 Jun 2014 18:42:57 +0200 Subject: Improved video support, all tinymce related resources moved to program/js/tinymce/roundcube dir --- program/include/rcmail.php | 3 +- program/js/editor.js | 24 ++++++-- program/js/tinymce/roundcube/browser.css | 91 ++++++++++++++++++++++++++++++ program/js/tinymce/roundcube/content.css | 26 +++++++++ program/js/tinymce/roundcube/video.png | Bin 0 -> 999 bytes program/lib/Roundcube/rcube_washtml.php | 6 +- program/localization/en_US/labels.inc | 2 + program/steps/mail/sendmail.inc | 4 +- skins/classic/common.css | 86 ----------------------------- skins/classic/editor_content.css | 26 --------- skins/larry/editor_content.css | 26 --------- skins/larry/styles.css | 92 ------------------------------- 12 files changed, 148 insertions(+), 238 deletions(-) create mode 100644 program/js/tinymce/roundcube/browser.css create mode 100644 program/js/tinymce/roundcube/content.css create mode 100644 program/js/tinymce/roundcube/video.png delete mode 100644 skins/classic/editor_content.css delete mode 100644 skins/larry/editor_content.css (limited to 'program/include/rcmail.php') diff --git a/program/include/rcmail.php b/program/include/rcmail.php index a6ba18312..a9e717b86 100644 --- a/program/include/rcmail.php +++ b/program/include/rcmail.php @@ -1795,8 +1795,9 @@ class rcmail extends rcube 'spelldict' => intval($this->config->get('spellcheck_dictionary')) ); - $this->output->add_label('selectimage', 'addimage'); + $this->output->add_label('selectimage', 'addimage', 'selectmedia', 'addmedia'); $this->output->set_env('editor_config', $config); + $this->output->include_css('program/js/tinymce/roundcube/browser.css'); $this->output->include_script('tinymce/tinymce.min.js'); $this->output->include_script('editor.js'); } diff --git a/program/js/editor.js b/program/js/editor.js index 6540bd56a..c5ceddd0f 100644 --- a/program/js/editor.js +++ b/program/js/editor.js @@ -40,7 +40,7 @@ function rcube_text_editor(config, id) selector: '#' + ($('#' + id).is('.mce_editor') ? id : 'fake-editor-id'), theme: 'modern', language: config.lang, - content_css: config.skin_path + '/editor_content.css?v2', + content_css: 'program/js/tinymce/roundcube/content.css?v1', menubar: false, statusbar: false, toolbar_items_size: 'small', @@ -84,7 +84,7 @@ function rcube_text_editor(config, id) accessibility_focus: false, file_browser_callback: function(name, url, type, win) { ref.file_browser_callback(name, url, type); }, // @todo: support more than image (types: file, image, media) - file_browser_callback_types: 'image' + file_browser_callback_types: 'image media' }); } @@ -582,9 +582,25 @@ function rcube_text_editor(config, id) return; } - if (file.mimetype.startsWith('image/')) { + var rx, img_src; + + switch (rcmail.env.file_browser_type) { + case 'image': + rx = /^image\//i; + break; + + case 'media': + rx = /^video\//i; + img_src = 'program/js/tinymce/roundcube/video.png'; + break; + + default: + return; + } + + if (rx.test(file.mimetype)) { var href = rcmail.env.comm_path+'&_id='+rcmail.env.compose_id+'&_action=display-attachment&_file='+file_id, - img = $('').attr({title: file.name, src: href + '&_thumbnail=1'}); + img = $('').attr({title: file.name, src: img_src ? img_src : href + '&_thumbnail=1'}); return $('
      • ').data('url', href) .append($('').append(img)) diff --git a/program/js/tinymce/roundcube/browser.css b/program/js/tinymce/roundcube/browser.css new file mode 100644 index 000000000..1787226f3 --- /dev/null +++ b/program/js/tinymce/roundcube/browser.css @@ -0,0 +1,91 @@ +/* This file contains the CSS data for media file selector of TinyMCE */ +#image-selector-list { + position: absolute; + top: 0; + left: 0; + right: 152px; + height: 100%; + overflow-x: hidden; + overflow-y: auto; +} + +#image-selector-form { + position: absolute; + top: 0; + bottom: 0; + right: 0; + width: 150px; + border: 0; + border: 1px solid #FFF; + border-left: 1px solid #DDD; + text-align: center; + padding-top: 10px; +} + +#image-upload-button { + width: 80%; + height: 30px; +} + +#image-upload-button span { + position: absolute; + width: 100%; + text-align: center; + line-height: 30px; +} + +#image-selector-list li { + line-height: 80px; + padding: 2px 0 2px 3px; + cursor: pointer; + overflow: hidden; + text-overflow: ellipsis; +} + +#image-selector-list li:hover { + background-color: #F0F0F0; +} + +#image-selector-list ul li img { + vertical-align: middle; + max-height: 80px; +} + +#image-selector-list ul li span.name { + vertical-align: middle; + font-weight: bold; + padding-left: 10px; +} + +#image-selector-list ul li span.img { + height: 80px; + width: 80px; + text-align: center; + display: inline-block; + overflow: hidden; + line-height: 80px; +} + +#image-selector-form.droptarget.hover, +#image-selector-form.droptarget.active { + border: 1px solid #019bc6; + box-shadow: 0 0 3px 2px rgba(71,135,177, 0.5); + -moz-box-shadow: 0 0 3px 2px rgba(71,135,177, 0.5); + -webkit-box-shadow: 0 0 3px 2px rgba(71,135,177, 0.5); + -o-box-shadow: 0 0 3px 2px rgba(71,135,177, 0.5); +} + +#image-selector-form.droptarget.hover { + background-color: #d9ecf4; + box-shadow: 0 0 5px 2px rgba(71,135,177, 0.9); + -moz-box-shadow: 0 0 5px 2px rgba(71,135,177, 0.9); + -webkit-box-shadow: 0 0 5px 2px rgba(71,135,177, 0.9); + -o-box-shadow: 0 0 5px 2px rgba(71,135,177, 0.9); +} + +div.mce-abs-end div.hint { + line-height: 50px; + padding-left: 10px; + color: #999; + text-shadow: 0 1px 1px #FFF; +} diff --git a/program/js/tinymce/roundcube/content.css b/program/js/tinymce/roundcube/content.css new file mode 100644 index 000000000..67480ab77 --- /dev/null +++ b/program/js/tinymce/roundcube/content.css @@ -0,0 +1,26 @@ +/* This file contains the CSS data for the editable area(iframe) of TinyMCE */ + +body, td, pre { + font-family: "Lucida Grande", Verdana, Arial, Helvetica, sans-serif; + font-size: 12px; +} + +body { + background-color: #FFFFFF; + margin-left: 4px; + margin-right: 4px; + margin-top: 2px; +} + +div.pre { + margin: 0; + padding: 0; + font-family: monospace; +} + +blockquote +{ + border-left: #1010ff 2px solid; + margin: 0; + padding: 0 0.4em; +} diff --git a/program/js/tinymce/roundcube/video.png b/program/js/tinymce/roundcube/video.png new file mode 100644 index 000000000..faf657000 Binary files /dev/null and b/program/js/tinymce/roundcube/video.png differ diff --git a/program/lib/Roundcube/rcube_washtml.php b/program/lib/Roundcube/rcube_washtml.php index 984294376..b93d3b117 100644 --- a/program/lib/Roundcube/rcube_washtml.php +++ b/program/lib/Roundcube/rcube_washtml.php @@ -95,6 +95,7 @@ class rcube_washtml 'ins', 'label', 'legend', 'li', 'map', 'menu', 'nobr', 'ol', 'p', 'pre', 'q', 's', 'samp', 'small', 'span', 'strike', 'strong', 'sub', 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr', 'tt', 'u', 'ul', 'var', 'wbr', 'img', + 'video', 'source', // form elements 'button', 'input', 'textarea', 'select', 'option', 'optgroup' ); @@ -246,7 +247,10 @@ class rcube_washtml $quot = strpos($style, '"') !== false ? "'" : '"'; $t .= ' style=' . $quot . $style . $quot; } - else if ($key == 'background' || ($key == 'src' && strtolower($node->tagName) == 'img')) { //check tagName anyway + else if ($key == 'background' + || ($key == 'src' && preg_match('/^(img|source)$/i', $node->tagName)) + || ($key == 'poster' && strtolower($node->tagName) == 'video') + ) { if (($src = $this->config['cid_map'][$value]) || ($src = $this->config['cid_map'][$this->config['base_url'].$value]) ) { diff --git a/program/localization/en_US/labels.inc b/program/localization/en_US/labels.inc index 20992e50e..2da99105f 100644 --- a/program/localization/en_US/labels.inc +++ b/program/localization/en_US/labels.inc @@ -232,6 +232,8 @@ $labels['mailreplyintro'] = 'On $date, $sender wrote:'; $labels['originalmessage'] = 'Original Message'; $labels['selectimage'] = 'Select image'; $labels['addimage'] = 'Add image'; +$labels['selectmedia'] = 'Select movie'; +$labels['addmedia'] = 'Add movie'; $labels['editidents'] = 'Edit identities'; $labels['spellcheck'] = 'Spell'; diff --git a/program/steps/mail/sendmail.inc b/program/steps/mail/sendmail.inc index 4737ce368..b70b18b6b 100644 --- a/program/steps/mail/sendmail.inc +++ b/program/steps/mail/sendmail.inc @@ -428,7 +428,7 @@ if (is_array($COMPOSE['attachments'])) { $attachment = $RCMAIL->plugins->exec_hook('attachment_get', $attachment); if ($isHtml) { - $dispurl = '/\ssrc\s*=\s*[\'"]*\S+display-attachment\S+file=rcmfile' + $dispurl = '/\s(poster|src)\s*=\s*[\'"]*\S+display-attachment\S+file=rcmfile' . preg_quote($attachment['id']) . '[\s\'"]*/'; $message_body = $MAIL_MIME->getHTMLBody(); $is_inline = preg_match($dispurl, $message_body); @@ -449,7 +449,7 @@ if (is_array($COMPOSE['attachments'])) { $cid .= '@localhost'; } - $message_body = preg_replace($dispurl, ' src="cid:' . $cid . '" ', $message_body); + $message_body = preg_replace($dispurl, ' \\1="cid:' . $cid . '" ', $message_body); $MAIL_MIME->setHTMLBody($message_body); diff --git a/skins/classic/common.css b/skins/classic/common.css index bdc7501f3..813df9ed3 100644 --- a/skins/classic/common.css +++ b/skins/classic/common.css @@ -1246,84 +1246,6 @@ fieldset.tabbed border: 0 !important; } -#image-selector-list -{ - position: absolute; - top: 0; - left: 0; - right: 152px; - height: 100%; - overflow-x: hidden; - overflow-y: auto; -} - -#image-selector-form -{ - position: absolute; - top: 0; - bottom: 0; - right: 0; - width: 150px; - border: 0; - border: 1px solid #FFF; - border-left: 1px solid #DDD; - text-align: center; - padding-top: 10px; -} - -#image-upload-button -{ - width: 80%; - height: 30px; -} - -#image-upload-button span -{ - position: absolute; - width: 100%; - text-align: center; - line-height: 30px; -} - -#image-selector-list li -{ - line-height: 80px; - padding: 2px 0 2px 3px; - cursor: pointer; - overflow: hidden; - text-overflow: ellipsis; -} - -#image-selector-list li:hover -{ - background-color: #F0F0F0; -} - -#image-selector-list ul li img -{ - vertical-align: middle; - max-height: 80px; -} - -#image-selector-list ul li span.name -{ - vertical-align: middle; - font-weight: bold; - padding-left: 10px; - line-height: 80px; - vertical-align: middle; -} - -#image-selector-list ul li span.img -{ - width: 80px; - text-align: center; - display: inline-block; - overflow: hidden; - line-height: 80px; - vertical-align: middle; -} - #image-selector-form.droptarget { background: url(images/filedrop.png) center bottom no-repeat; } @@ -1335,11 +1257,3 @@ fieldset.tabbed -moz-box-shadow: 0 0 5px 0 #999; -o-box-shadow: 0 0 5px 0 #999; } - -div.mce-abs-end div.hint -{ - line-height: 50px; - padding-left: 10px; - color: #999; - text-shadow: 0 1px 1px #FFF; -} diff --git a/skins/classic/editor_content.css b/skins/classic/editor_content.css deleted file mode 100644 index 67480ab77..000000000 --- a/skins/classic/editor_content.css +++ /dev/null @@ -1,26 +0,0 @@ -/* This file contains the CSS data for the editable area(iframe) of TinyMCE */ - -body, td, pre { - font-family: "Lucida Grande", Verdana, Arial, Helvetica, sans-serif; - font-size: 12px; -} - -body { - background-color: #FFFFFF; - margin-left: 4px; - margin-right: 4px; - margin-top: 2px; -} - -div.pre { - margin: 0; - padding: 0; - font-family: monospace; -} - -blockquote -{ - border-left: #1010ff 2px solid; - margin: 0; - padding: 0 0.4em; -} diff --git a/skins/larry/editor_content.css b/skins/larry/editor_content.css deleted file mode 100644 index 67480ab77..000000000 --- a/skins/larry/editor_content.css +++ /dev/null @@ -1,26 +0,0 @@ -/* This file contains the CSS data for the editable area(iframe) of TinyMCE */ - -body, td, pre { - font-family: "Lucida Grande", Verdana, Arial, Helvetica, sans-serif; - font-size: 12px; -} - -body { - background-color: #FFFFFF; - margin-left: 4px; - margin-right: 4px; - margin-top: 2px; -} - -div.pre { - margin: 0; - padding: 0; - font-family: monospace; -} - -blockquote -{ - border-left: #1010ff 2px solid; - margin: 0; - padding: 0 0.4em; -} diff --git a/skins/larry/styles.css b/skins/larry/styles.css index 6eceb772e..d1e45012b 100644 --- a/skins/larry/styles.css +++ b/skins/larry/styles.css @@ -2721,98 +2721,6 @@ fieldset.tab { margin-left: 0; } -/*** image selector in HTML editor ***/ -#image-selector-list { - position: absolute; - top: 0; - left: 0; - right: 152px; - height: 100%; - overflow-x: hidden; - overflow-y: auto; -} - -#image-selector-form { - position: absolute; - top: 0; - bottom: 0; - right: 0; - width: 150px; - border: 0; - border: 1px solid #FFF; - border-left: 1px solid #DDD; - text-align: center; - padding-top: 10px; -} - -#image-upload-button { - width: 80%; - height: 30px; -} - -#image-upload-button span { - position: absolute; - width: 100%; - text-align: center; - line-height: 30px; -} - -#image-selector-list li { - line-height: 80px; - padding: 2px 0 2px 3px; - cursor: pointer; - overflow: hidden; - text-overflow: ellipsis; -} - -#image-selector-list li:hover { - background-color: #F0F0F0; -} - -#image-selector-list ul li img { - vertical-align: middle; - max-height: 80px; -} - -#image-selector-list ul li span.name { - vertical-align: middle; - font-weight: bold; - padding-left: 10px; -} - -#image-selector-list ul li span.img { - height: 80px; - width: 80px; - text-align: center; - display: inline-block; - overflow: hidden; - line-height: 80px; -} - #image-selector-form.droptarget { background: url(images/filedrop.png) center bottom no-repeat; } - -#image-selector-form.droptarget.hover, -#image-selector-form.droptarget.active { - border: 1px solid #019bc6; - box-shadow: 0 0 3px 2px rgba(71,135,177, 0.5); - -moz-box-shadow: 0 0 3px 2px rgba(71,135,177, 0.5); - -webkit-box-shadow: 0 0 3px 2px rgba(71,135,177, 0.5); - -o-box-shadow: 0 0 3px 2px rgba(71,135,177, 0.5); -} - -#image-selector-form.droptarget.hover { - background-color: #d9ecf4; - box-shadow: 0 0 5px 2px rgba(71,135,177, 0.9); - -moz-box-shadow: 0 0 5px 2px rgba(71,135,177, 0.9); - -webkit-box-shadow: 0 0 5px 2px rgba(71,135,177, 0.9); - -o-box-shadow: 0 0 5px 2px rgba(71,135,177, 0.9); -} - -div.mce-abs-end div.hint { - line-height: 50px; - padding-left: 10px; - color: #999; - text-shadow: 0 1px 1px #FFF; -} -- cgit v1.2.3 From 4a9a0e1f0fc1977e067806e49b7ce5fa8c47dab5 Mon Sep 17 00:00:00 2001 From: David Carter Date: Fri, 6 Jun 2014 11:29:40 +0100 Subject: The following: program/steps/mail/compose.inc :: rcmail_store_target_selection() program/steps/settings/edit_folder.inc :: rcmail_folder_form() both try to localise mailbox names. Push the logic down into the folder_selector() method which can use: $this->config->get('show_real_foldernames') to decide the correct default behaviour. Clients functions and methods can still override by adding 'realnames' named parameter to the folder_selector() call. The obvious example is the Settings -> Preferences -> Special Folders screen. --- program/include/rcmail.php | 3 ++- program/steps/settings/edit_folder.inc | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'program/include/rcmail.php') diff --git a/program/include/rcmail.php b/program/include/rcmail.php index 1a227927e..f4689215c 100644 --- a/program/include/rcmail.php +++ b/program/include/rcmail.php @@ -1325,7 +1325,8 @@ class rcmail extends rcube */ public function folder_selector($p = array()) { - $p += array('maxlength' => 100, 'realnames' => false, 'is_escaped' => true); + $realnames = $this->config->get('show_real_foldernames'); + $p += array('maxlength' => 100, 'realnames' => $realnames, 'is_escaped' => true); $a_mailboxes = array(); $storage = $this->get_storage(); diff --git a/program/steps/settings/edit_folder.inc b/program/steps/settings/edit_folder.inc index c61ac6da9..30a187fc4 100644 --- a/program/steps/settings/edit_folder.inc +++ b/program/steps/settings/edit_folder.inc @@ -135,7 +135,6 @@ function rcmail_folder_form($attrib) 'id' => '_parent', 'name' => '_parent', 'noselection' => '---', - 'realnames' => false, 'maxlength' => 150, 'unsubscribed' => true, 'skip_noinferiors' => true, -- cgit v1.2.3 From 6fa1a0da1f0902f10be8fc4eb24180f8e3453c17 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Tue, 24 Jun 2014 19:16:18 +0200 Subject: Extend get_quota() so it's possible to specify GETQUOTAROOT folder and return full quota info (including all roots and types, e.g. MESSAGE) - for future use --- program/include/rcmail.php | 1 + program/lib/Roundcube/rcube_imap.php | 7 ++- program/lib/Roundcube/rcube_imap_generic.php | 91 +++++++++++++++------------- program/lib/Roundcube/rcube_storage.php | 4 +- 4 files changed, 57 insertions(+), 46 deletions(-) (limited to 'program/include/rcmail.php') diff --git a/program/include/rcmail.php b/program/include/rcmail.php index f4689215c..29ed66a43 100644 --- a/program/include/rcmail.php +++ b/program/include/rcmail.php @@ -1678,6 +1678,7 @@ class rcmail extends rcube $quota = $this->storage->get_quota(); $quota = $this->plugins->exec_hook('quota', $quota); + unset($quota['abort']); $quota_result = (array) $quota; $quota_result['type'] = isset($_SESSION['quota_display']) ? $_SESSION['quota_display'] : ''; diff --git a/program/lib/Roundcube/rcube_imap.php b/program/lib/Roundcube/rcube_imap.php index 109886f8d..858db7b5b 100644 --- a/program/lib/Roundcube/rcube_imap.php +++ b/program/lib/Roundcube/rcube_imap.php @@ -3067,14 +3067,15 @@ class rcube_imap extends rcube_storage /** * Get mailbox quota information - * added by Nuny + * + * @param string $folder Folder name * * @return mixed Quota info or False if not supported */ - public function get_quota() + public function get_quota($folder = null) { if ($this->get_capability('QUOTA') && $this->check_connection()) { - return $this->conn->getQuota(); + return $this->conn->getQuota($folder); } return false; diff --git a/program/lib/Roundcube/rcube_imap_generic.php b/program/lib/Roundcube/rcube_imap_generic.php index 7b9ba2ec8..032506412 100644 --- a/program/lib/Roundcube/rcube_imap_generic.php +++ b/program/lib/Roundcube/rcube_imap_generic.php @@ -2873,59 +2873,66 @@ class rcube_imap_generic /** * Returns QUOTA information * + * @param string $mailbox Mailbox name + * * @return array Quota information */ - function getQuota() - { - /* - * GETQUOTAROOT "INBOX" - * QUOTAROOT INBOX user/rchijiiwa1 - * QUOTA user/rchijiiwa1 (STORAGE 654 9765) - * OK Completed - */ - $result = false; - $quota_lines = array(); - $key = $this->nextTag(); - $command = $key . ' GETQUOTAROOT INBOX'; - - // get line(s) containing quota info - if ($this->putLine($command)) { - do { - $line = rtrim($this->readLine(5000)); - if (preg_match('/^\* QUOTA /', $line)) { - $quota_lines[] = $line; - } - } while (!$this->startsWith($line, $key, true, true)); - } - else { - $this->setError(self::ERROR_COMMAND, "Unable to send command: $command"); + function getQuota($mailbox = null) + { + if ($mailbox === null || $mailbox === '') { + $mailbox = 'INBOX'; } - // return false if not found, parse if found + // a0001 GETQUOTAROOT INBOX + // * QUOTAROOT INBOX user/sample + // * QUOTA user/sample (STORAGE 654 9765) + // a0001 OK Completed + + list($code, $response) = $this->execute('GETQUOTAROOT', array($this->escape($mailbox))); + + $result = false; $min_free = PHP_INT_MAX; - foreach ($quota_lines as $key => $quota_line) { - $quota_line = str_replace(array('(', ')'), '', $quota_line); - $parts = explode(' ', $quota_line); - $storage_part = array_search('STORAGE', $parts); + $all = array(); - if (!$storage_part) { - continue; - } + if ($code == self::ERROR_OK) { + foreach (explode("\n", $response) as $line) { + if (preg_match('/^\* QUOTA /', $line)) { + list(, , $quota_root) = $this->tokenizeResponse($line, 3); + + while ($line) { + list($type, $used, $total) = $this->tokenizeResponse($line, 1); + $type = strtolower($type); + + if ($type && $total) { + $all[$quota_root][$type]['used'] = intval($used); + $all[$quota_root][$type]['total'] = intval($total); + } + } + + if (empty($all[$quota_root]['storage'])) { + continue; + } - $used = intval($parts[$storage_part+1]); - $total = intval($parts[$storage_part+2]); - $free = $total - $used; + $used = $all[$quota_root]['storage']['used']; + $total = $all[$quota_root]['storage']['total']; + $free = $total - $used; - // return lowest available space from all quotas - if ($free < $min_free) { - $min_free = $free; - $result['used'] = $used; - $result['total'] = $total; - $result['percent'] = min(100, round(($used/max(1,$total))*100)); - $result['free'] = 100 - $result['percent']; + // calculate lowest available space from all storage quotas + if ($free < $min_free) { + $min_free = $free; + $result['used'] = $used; + $result['total'] = $total; + $result['percent'] = min(100, round(($used/max(1,$total))*100)); + $result['free'] = 100 - $result['percent']; + } + } } } + if (!empty($result)) { + $result['all'] = $all; + } + return $result; } diff --git a/program/lib/Roundcube/rcube_storage.php b/program/lib/Roundcube/rcube_storage.php index c1293961c..ccb28c680 100644 --- a/program/lib/Roundcube/rcube_storage.php +++ b/program/lib/Roundcube/rcube_storage.php @@ -918,9 +918,11 @@ abstract class rcube_storage /** * Get mailbox quota information. * + * @param string $folder Folder name + * * @return mixed Quota info or False if not supported */ - abstract function get_quota(); + abstract function get_quota($folder = null); /* ----------------------------------------- -- cgit v1.2.3 From c5f06896d4db5f2479b5988cb2cea6ef0fa80cad Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Thu, 26 Jun 2014 12:32:52 +0200 Subject: Display full quota information in popup (#1485769, #1486604) --- CHANGELOG | 1 + program/include/rcmail.php | 41 +++++++++++++++++++++++++++++++++-- program/localization/en_US/labels.inc | 5 +++++ skins/classic/common.css | 29 +++++++++++++++++++++++++ skins/classic/functions.js | 12 ++++++++++ skins/larry/styles.css | 24 ++++++++++++++++++++ skins/larry/ui.js | 15 +++++++++++-- 7 files changed, 123 insertions(+), 4 deletions(-) (limited to 'program/include/rcmail.php') diff --git a/CHANGELOG b/CHANGELOG index 2239fcf74..6ff89c2d7 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ CHANGELOG Roundcube Webmail =========================== +- Display full quota information in popup (#1485769, #1486604) - Mail compose: Selecting contact inserts recipient to previously focused input - to/cc/bcc accordingly (#1489684) - Add option to set default message list mode - default_list_mode (#1487312) - Close "no subject" prompt with Enter key (#1489580) diff --git a/program/include/rcmail.php b/program/include/rcmail.php index 29ed66a43..cfdd1816e 100644 --- a/program/include/rcmail.php +++ b/program/include/rcmail.php @@ -1678,7 +1678,6 @@ class rcmail extends rcube $quota = $this->storage->get_quota(); $quota = $this->plugins->exec_hook('quota', $quota); - unset($quota['abort']); $quota_result = (array) $quota; $quota_result['type'] = isset($_SESSION['quota_display']) ? $_SESSION['quota_display'] : ''; @@ -1697,7 +1696,39 @@ class rcmail extends rcube $quota_result['width'] = $attrib['width']; } if ($attrib['height']) { - $quota_result['height'] = $attrib['height']; + $quota_result['height'] = $attrib['height']; + } + + // build a table of quota types/roots info + if (($root_cnt = count($quota_result['all'])) > 1 || count($quota_result['all'][key($quota_result['all'])]) > 1) { + $table = new html_table(array('cols' => 3, 'class' => 'quota-info')); + + $table->add_header(null, self::Q($this->gettext('quotatype'))); + $table->add_header(null, self::Q($this->gettext('quotatotal'))); + $table->add_header(null, self::Q($this->gettext('quotaused'))); + + foreach ($quota_result['all'] as $root => $data) { + if ($root_cnt > 1 && $root) { + $table->add(array('colspan' => 3, 'class' => 'root'), self::Q($root)); + } + + if ($storage = $data['storage']) { + $percent = min(100, round(($storage['used']/max(1,$storage['total']))*100)); + + $table->add('name', self::Q($this->gettext('quotastorage'))); + $table->add(null, $this->show_bytes($storage['total'] * 1024)); + $table->add(null, sprintf('%s (%.0f%%)', $this->show_bytes($storage['used'] * 1024), $percent)); + } + if ($message = $data['message']) { + $percent = min(100, round(($message['used']/max(1,$message['total']))*100)); + + $table->add('name', self::Q($this->gettext('quotamessage'))); + $table->add(null, intval($message['total'])); + $table->add(null, sprintf('%d (%.0f%%)', $message['used'], $percent)); + } + } + + $quota_result['table'] = $table->show(); } } else { @@ -1706,6 +1737,12 @@ class rcmail extends rcube $quota_result['percent'] = 0; } + // cleanup + unset($quota_result['abort']); + if (empty($quota_result['table'])) { + unset($quota_result['all']); + } + return $quota_result; } diff --git a/program/localization/en_US/labels.inc b/program/localization/en_US/labels.inc index ba8fe2e40..1e179cbcf 100644 --- a/program/localization/en_US/labels.inc +++ b/program/localization/en_US/labels.inc @@ -208,6 +208,11 @@ $labels['importmessages'] = 'Import messages'; $labels['quota'] = 'Disk usage'; $labels['unknown'] = 'unknown'; $labels['unlimited'] = 'unlimited'; +$labels['quotatype'] = 'Quota type'; +$labels['quotatotal'] = 'Limit'; +$labels['quotaused'] = 'Used'; +$labels['quotastorage'] = 'Disk space'; +$labels['quotamessage'] = 'Messages count'; $labels['quicksearch'] = 'Quick search'; $labels['resetsearch'] = 'Reset search'; diff --git a/skins/classic/common.css b/skins/classic/common.css index 273defe19..6e9986165 100644 --- a/skins/classic/common.css +++ b/skins/classic/common.css @@ -1236,6 +1236,35 @@ fieldset.tabbed .quota_text_mid { color: #666; } .quota_text_low { color: #666; } +table.quota-info { + border-spacing: 0; + border-collapse: collapse; + table-layout: fixed; + margin: 2px; +} + +table.quota-info td, +table.quota-info th { + border: 1px solid #999; + padding: 2px 3px; + text-align: center; + min-width: 80px; + color: #333; + font-size: 11px; +} + +table.quota-info th { + font-weight: bold; + background-color: #ddd; +} + +table.quota-info td.name { + text-align: left; +} + +table.quota-info td.root { + font-style: italic; +} /********** TinyMCE styles **********/ .mce-btn-small button diff --git a/skins/classic/functions.js b/skins/classic/functions.js index 4fef61151..7f2b8b4fb 100644 --- a/skins/classic/functions.js +++ b/skins/classic/functions.js @@ -879,6 +879,18 @@ function fit_string_to_size(str, elem, len) function update_quota(data) { percent_indicator(rcmail.gui_objects.quotadisplay, data); + + if (data.table) { + var menu = $('#quotamenu'); + + if (!menu.length) + menu = $('
        ').appendTo($('body')); + + menu.html(data.table); + $('#quotaimg').css('cursor', 'pointer').off('click').on('click', function(e) { + return rcmail.command('menu-open', 'quotamenu', e.target, e); + }); + } }; // percent (quota) indicator diff --git a/skins/larry/styles.css b/skins/larry/styles.css index e4a5c6799..5e3eae2b3 100644 --- a/skins/larry/styles.css +++ b/skins/larry/styles.css @@ -2277,6 +2277,30 @@ a.iconbutton:focus, background: url(images/quota.png) -100px 0 no-repeat; } +table.quota-info { + border-spacing: 0; + border-collapse: collapse; + table-layout: fixed; + margin: 5px; +} + +table.quota-info td, +table.quota-info th { + color: white; + border: 1px solid lightgrey; + padding: 2px 3px; + text-align: center; + min-width: 80px; +} + +table.quota-info td.name { + text-align: left; +} + +table.quota-info td.root { + font-style: italic; +} + /*** popup menus ***/ .popupmenu, diff --git a/skins/larry/ui.js b/skins/larry/ui.js index 11125c389..f67b42250 100644 --- a/skins/larry/ui.js +++ b/skins/larry/ui.js @@ -515,14 +515,25 @@ function rcube_mail_ui() function update_quota(p) { - var step = 24, step_count = 20, + var element = $('#quotadisplay'), menu = $('#quotamenu'), + step = 24, step_count = 20, y = p.total ? Math.ceil(p.percent / 100 * step_count) * step : 0; // never show full-circle if quota is close to 100% but below. if (p.total && y == step * step_count && p.percent < 100) y -= step; - $('#quotadisplay').css('background-position', '0 -'+y+'px'); + element.css('background-position', '0 -' + y + 'px'); + + if (p.table) { + if (!menu.length) + menu = $('
        ').appendTo($('body')); + + menu.html(p.table); + element.css('cursor', 'pointer').off('click').on('click', function(e) { + return rcmail.command('menu-open', 'quotamenu', e.target, e); + }); + } } -- cgit v1.2.3 From 6d5a1b9e8f426d5ddc7c5bf2840a25859ab9d9e1 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Sat, 28 Jun 2014 10:07:44 +0200 Subject: Get rid of some rcube_config::all() calls --- program/include/rcmail.php | 35 ++++++++++++++++++++--------------- program/lib/Roundcube/rcube.php | 10 +++++++--- 2 files changed, 27 insertions(+), 18 deletions(-) (limited to 'program/include/rcmail.php') diff --git a/program/include/rcmail.php b/program/include/rcmail.php index cfdd1816e..0151020c7 100644 --- a/program/include/rcmail.php +++ b/program/include/rcmail.php @@ -730,14 +730,16 @@ class rcmail extends rcube */ public function logout_actions() { - $config = $this->config->all(); - $storage = $this->get_storage(); + $storage = $this->get_storage(); + $logout_expunge = $this->config->get('logout_expunge'); + $logout_purge = $this->config->get('logout_purge'); + $trash_mbox = $this->config->get('trash_mbox'); - if ($config['logout_purge'] && !empty($config['trash_mbox'])) { - $storage->clear_folder($config['trash_mbox']); + if ($logout_purge && !empty($trash_mbox)) { + $storage->clear_folder($trash_mbox); } - if ($config['logout_expunge']) { + if ($logout_expunge) { $storage->expunge_folder('INBOX'); } @@ -887,12 +889,15 @@ class rcmail extends rcube $prefix = $this->storage->get_namespace('prefix'); $prefix_len = strlen($prefix); - if (!$prefix_len) + if (!$prefix_len) { return; + } - $prefs = $this->config->all(); - if (!empty($prefs['namespace_fixed'])) + if ($this->config->get('namespace_fixed')) { return; + } + + $prefs = array(); // Build namespace prefix regexp $ns = $this->storage->get_namespace(); @@ -912,16 +917,16 @@ class rcmail extends rcube // Fix preferences $opts = array('drafts_mbox', 'junk_mbox', 'sent_mbox', 'trash_mbox', 'archive_mbox'); foreach ($opts as $opt) { - if ($value = $prefs[$opt]) { + if ($value = $this->config->get($opt)) { if ($value != 'INBOX' && !preg_match($regexp, $value)) { $prefs[$opt] = $prefix.$value; } } } - if (!empty($prefs['search_mods'])) { + if (($search_mods = $this->config->get('search_mods')) && !empty($search_mods)) { $folders = array(); - foreach ($prefs['search_mods'] as $idx => $value) { + foreach ($search_mods as $idx => $value) { if ($idx != 'INBOX' && $idx != '*' && !preg_match($regexp, $idx)) { $idx = $prefix.$idx; } @@ -931,9 +936,9 @@ class rcmail extends rcube $prefs['search_mods'] = $folders; } - if (!empty($prefs['message_threading'])) { + if (($threading = $this->config->get('message_threading')) && !empty($threading)) { $folders = array(); - foreach ($prefs['message_threading'] as $idx => $value) { + foreach ($threading as $idx => $value) { if ($idx != 'INBOX' && !preg_match($regexp, $idx)) { $idx = $prefix.$idx; } @@ -943,8 +948,8 @@ class rcmail extends rcube $prefs['message_threading'] = $folders; } - if (!empty($prefs['collapsed_folders'])) { - $folders = explode('&&', $prefs['collapsed_folders']); + if ($collapsed = $this->config->get('collapsed_folders')) { + $folders = explode('&&', $collapsed); $count = count($folders); $folders_str = ''; diff --git a/program/lib/Roundcube/rcube.php b/program/lib/Roundcube/rcube.php index cf54c3c12..5f55414e6 100644 --- a/program/lib/Roundcube/rcube.php +++ b/program/lib/Roundcube/rcube.php @@ -172,9 +172,13 @@ class rcube public function get_dbh() { if (!$this->db) { - $config_all = $this->config->all(); - $this->db = rcube_db::factory($config_all['db_dsnw'], $config_all['db_dsnr'], $config_all['db_persistent']); - $this->db->set_debug((bool)$config_all['sql_debug']); + $this->db = rcube_db::factory( + $this->config->get('db_dsnw'), + $this->config->get('db_dsnr'), + $this->config->get('db_persistent') + ); + + $this->db->set_debug((bool)$this->config->get('sql_debug')); } return $this->db; -- cgit v1.2.3 From 3cc1afa1c2f30bfebb30146795e50172947b4b5f Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Sun, 29 Jun 2014 16:35:18 +0200 Subject: Support images in HTML signatures (#1488676) This enables image button and file browser in html editor for signatures --- CHANGELOG | 1 + config/defaults.inc.php | 4 ++ program/include/rcmail.php | 79 +++++++++++++++++++++++++++++++- program/js/app.js | 15 +++--- program/js/editor.js | 16 +++++-- program/steps/mail/attachments.inc | 63 +------------------------ program/steps/settings/edit_identity.inc | 10 ++++ program/steps/settings/func.inc | 1 + program/steps/settings/save_identity.inc | 34 +++++++++++++- 9 files changed, 149 insertions(+), 74 deletions(-) (limited to 'program/include/rcmail.php') diff --git a/CHANGELOG b/CHANGELOG index e4a02ad4f..b73ee3f19 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ CHANGELOG Roundcube Webmail =========================== +- Support images in HTML signatures (#1488676) - Display full quota information in popup (#1485769, #1486604) - Mail compose: Selecting contact inserts recipient to previously focused input - to/cc/bcc accordingly (#1489684) - Add option to set default message list mode - default_list_mode (#1487312) diff --git a/config/defaults.inc.php b/config/defaults.inc.php index 71a2e4277..e7cb1e3aa 100644 --- a/config/defaults.inc.php +++ b/config/defaults.inc.php @@ -481,6 +481,10 @@ $config['mdn_use_from'] = false; // 4 - one identity with possibility to edit only signature $config['identities_level'] = 0; +// Maximum size of uploaded image in kilobytes +// Images (in html signatures) are stored in database as data URIs +$config['identity_image_size'] = 64; + // Mimetypes supported by the browser. // attachments of these types will open in a preview window // either a comma-separated list or an array: 'text/plain,text/html,text/xml,image/jpeg,image/gif,image/png,application/pdf' diff --git a/program/include/rcmail.php b/program/include/rcmail.php index 0151020c7..54d0d88e6 100644 --- a/program/include/rcmail.php +++ b/program/include/rcmail.php @@ -1944,8 +1944,10 @@ class rcmail extends rcube /** * Initializes file uploading interface. + * + * @param $int Optional maximum file size in bytes */ - public function upload_init() + public function upload_init($max_size = null) { // Enable upload progress bar if ($seconds = $this->config->get('upload_progress')) { @@ -1973,6 +1975,10 @@ class rcmail extends rcube $max_filesize = $max_postsize; } + if ($max_size && $max_size < $max_filesize) { + $max_filesize = $max_size; + } + $this->output->set_env('max_filesize', $max_filesize); $max_filesize = $this->show_bytes($max_filesize); $this->output->set_env('filesizeerror', $this->gettext(array( @@ -1981,6 +1987,77 @@ class rcmail extends rcube return $max_filesize; } + /** + * Outputs uploaded file content (with image thumbnails support + * + * @param array $file Upload file data + */ + public function display_uploaded_file($file) + { + if (empty($file)) { + return; + } + + $file = $this->plugins->exec_hook('attachment_display', $file); + + if ($file['status']) { + if (empty($file['size'])) { + $file['size'] = $file['data'] ? strlen($file['data']) : @filesize($file['path']); + } + + // generate image thumbnail for file browser in HTML editor + if (!empty($_GET['_thumbnail'])) { + $temp_dir = $this->config->get('temp_dir'); + $thumbnail_size = 80; + list(,$ext) = explode('/', $file['mimetype']); + $mimetype = $file['mimetype']; + $file_ident = $file['id'] . ':' . $file['mimetype'] . ':' . $file['size']; + $cache_basename = $temp_dir . '/' . md5($file_ident . ':' . $this->user->ID . ':' . $thumbnail_size); + $cache_file = $cache_basename . '.' . $ext; + + // render thumbnail image if not done yet + if (!is_file($cache_file)) { + if (!$file['path']) { + $orig_name = $filename = $cache_basename . '.orig.' . $ext; + file_put_contents($orig_name, $file['data']); + } + else { + $filename = $file['path']; + } + + $image = new rcube_image($filename); + if ($imgtype = $image->resize($thumbnail_size, $cache_file, true)) { + $mimetype = 'image/' . $imgtype; + + if ($orig_name) { + unlink($orig_name); + } + } + } + + if (is_file($cache_file)) { + // cache for 1h + $this->output->future_expire_header(3600); + header('Content-Type: ' . $mimetype); + header('Content-Length: ' . filesize($cache_file)); + + readfile($cache_file); + exit; + } + } + + header('Content-Type: ' . $file['mimetype']); + header('Content-Length: ' . $file['size']); + + if ($file['data']) { + echo $file['data']; + } + else if ($file['path']) { + readfile($file['path']); + } + } + } + /** * Initializes client-side autocompletion. */ diff --git a/program/js/app.js b/program/js/app.js index 8f2065635..3b5ff0422 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -4083,6 +4083,14 @@ function rcube_webmail() if (upload_id) this.triggerEvent('fileuploaded', {name: name, attachment: att, id: upload_id}); + if (!this.env.attachments) + this.env.attachments = {}; + + if (upload_id && this.env.attachments[upload_id]) + delete this.env.attachments[upload_id]; + + this.env.attachments[name] = att; + if (!this.gui_objects.attachmentlist) return false; @@ -4112,11 +4120,6 @@ function rcube_webmail() var tabindex = $(this.gui_objects.attachmentlist).attr('data-tabindex') || '0'; li.find('a').attr('tabindex', tabindex); - if (upload_id && this.env.attachments[upload_id]) - delete this.env.attachments[upload_id]; - - this.env.attachments[name] = att; - return true; }; @@ -7563,7 +7566,7 @@ function rcube_webmail() $(form).attr({ target: frame_name, - action: this.url(action, { _id:this.env.compose_id||'', _uploadid:ts }), + action: this.url(action, {_id: this.env.compose_id || '', _uploadid: ts, _from: this.env.action}), method: 'POST'}) .attr(form.encoding ? 'encoding' : 'enctype', 'multipart/form-data') .submit(); diff --git a/program/js/editor.js b/program/js/editor.js index dfd3e27ea..0dd8fef9a 100644 --- a/program/js/editor.js +++ b/program/js/editor.js @@ -65,10 +65,12 @@ function rcube_text_editor(config, id) // minimal editor if (config.mode == 'identity') { $.extend(conf, { - plugins: 'autolink charmap code colorpicker hr link paste tabfocus textcolor', + plugins: 'autolink charmap code colorpicker hr image link paste tabfocus textcolor', toolbar: 'bold italic underline alignleft aligncenter alignright alignjustify' - + ' | outdent indent charmap hr link unlink code forecolor' - + ' | fontselect fontsizeselect' + + ' | outdent indent charmap hr link unlink image code forecolor' + + ' | fontselect fontsizeselect', + file_browser_callback: function(name, url, type, win) { ref.file_browser_callback(name, url, type); }, + file_browser_callback_types: 'image' }); } // full-featured editor @@ -610,6 +612,8 @@ function rcube_text_editor(config, id) } }); } + + // @todo: upload progress indicator }; // close file browser window @@ -652,7 +656,9 @@ function rcube_text_editor(config, id) } if (rx.test(file.mimetype)) { - var href = rcmail.env.comm_path+'&_id='+rcmail.env.compose_id+'&_action=display-attachment&_file='+file_id, + var path = rcmail.env.comm_path + '&_from=' + rcmail.env.action, + action = rcmail.env.compose_id ? '&_id=' + rcmail.env.compose_id + '&_action=display-attachment' : '&_action=upload-display', + href = path + action + '&_file=' + file_id, img = $('').attr({title: file.name, src: img_src ? img_src : href + '&_thumbnail=1'}); return $('
      • ').attr({tabindex: 0}) @@ -686,7 +692,7 @@ function rcube_text_editor(config, id) this.hack_file_input = function(elem, clone_form) { var link = $(elem), - file = $(''), + file = $('').attr('name', '_files[]'), form = $('').attr({method: 'post', enctype: 'multipart/form-data'}), offset = link.offset(); diff --git a/program/steps/mail/attachments.inc b/program/steps/mail/attachments.inc index fd122c5c1..5eaa655e3 100644 --- a/program/steps/mail/attachments.inc +++ b/program/steps/mail/attachments.inc @@ -38,7 +38,7 @@ if (!$COMPOSE) { // remove an attachment -if ($RCMAIL->action=='remove-attachment') { +if ($RCMAIL->action == 'remove-attachment') { $id = 'undefined'; if (preg_match('/^rcmfile(\w+)$/', $_POST['_file'], $regs)) { @@ -67,66 +67,7 @@ if ($RCMAIL->action == 'display-attachment') { $id = $regs[1]; } - if ($attachment = $COMPOSE['attachments'][$id]) { - $attachment = $RCMAIL->plugins->exec_hook('attachment_display', $attachment); - } - - if ($attachment['status']) { - if (empty($attachment['size'])) { - $attachment['size'] = $attachment['data'] ? strlen($attachment['data']) : @filesize($attachment['path']); - } - - // generate image thumbnail for file browser in HTML editor - if (!empty($_GET['_thumbnail'])) { - $temp_dir = $RCMAIL->config->get('temp_dir'); - $thumbnail_size = 80; - list(,$ext) = explode('/', $attachment['mimetype']); - $mimetype = $attachment['mimetype']; - $file_ident = $attachment['id'] . ':' . $attachment['mimetype'] . ':' . $attachment['size']; - $cache_basename = $temp_dir . '/' . md5($file_ident . ':' . $RCMAIL->user->ID . ':' . $thumbnail_size); - $cache_file = $cache_basename . '.' . $ext; - - // render thumbnail image if not done yet - if (!is_file($cache_file)) { - if (!$attachment['path']) { - $orig_name = $filename = $cache_basename . '.orig.' . $ext; - file_put_contents($orig_name, $attachment['data']); - } - else { - $filename = $attachment['path']; - } - - $image = new rcube_image($filename); - if ($imgtype = $image->resize($thumbnail_size, $cache_file, true)) { - $mimetype = 'image/' . $imgtype; - - if ($orig_name) { - unlink($orig_name); - } - } - } - - if (is_file($cache_file)) { - // cache for 1h - $RCMAIL->output->future_expire_header(3600); - header('Content-Type: ' . $mimetype); - header('Content-Length: ' . filesize($cache_file)); - - readfile($cache_file); - exit; - } - } - - header('Content-Type: ' . $attachment['mimetype']); - header('Content-Length: ' . $attachment['size']); - - if ($attachment['data']) { - echo $attachment['data']; - } - else if ($attachment['path']) { - readfile($attachment['path']); - } - } + $RCMAIL->display_uploaded_file($COMPOSE['attachments'][$id]); exit; } diff --git a/program/steps/settings/edit_identity.inc b/program/steps/settings/edit_identity.inc index 20f822027..34fe9798f 100644 --- a/program/steps/settings/edit_identity.inc +++ b/program/steps/settings/edit_identity.inc @@ -176,5 +176,15 @@ function rcube_identity_form($attrib) $out .= $form_end; + // add image upload form + $max_filesize = $RCMAIL->upload_init($RCMAIL->config->get('identity_image_size', 64) * 1024); + $upload_form_id = 'identityImageUpload'; + + $out .= '' + . html::div('hint', $RCMAIL->gettext(array('name' => 'maxuploadsize', 'vars' => array('size' => $max_filesize)))) + . '
      • '; + + $RCMAIL->output->add_gui_object('uploadform', $upload_form_id); + return $out; } diff --git a/program/steps/settings/func.inc b/program/steps/settings/func.inc index 89103ee44..7ccbfa4a5 100644 --- a/program/steps/settings/func.inc +++ b/program/steps/settings/func.inc @@ -44,6 +44,7 @@ $RCMAIL->register_action_map(array( 'add-response' => 'edit_response.inc', 'save-response' => 'edit_response.inc', 'delete-response' => 'responses.inc', + 'upload-display' => 'upload.inc', )); diff --git a/program/steps/settings/save_identity.inc b/program/steps/settings/save_identity.inc index 77245b988..de0c84c91 100644 --- a/program/steps/settings/save_identity.inc +++ b/program/steps/settings/save_identity.inc @@ -79,8 +79,11 @@ foreach ($email_checks as $email) { } } -// XSS protection in HTML signature (#1489251) if (!empty($save_data['signature']) && !empty($save_data['html_signature'])) { + // replace uploaded images with data URIs + $save_data['signature'] = rcmail_attach_images($save_data['signature']); + + // XSS protection in HTML signature (#1489251) $save_data['signature'] = rcmail_wash_html($save_data['signature']); // clear POST data of signature, we want to use safe content @@ -190,6 +193,35 @@ else { } +/** + * Attach uploaded images into signature as data URIs + */ +function rcmail_attach_images($html) +{ + global $RCMAIL; + + $offset = 0; + $regexp = '/\s(poster|src)\s*=\s*[\'"]*\S+upload-display\S+file=rcmfile([0-9]+)[\s\'"]*/'; + + while (preg_match($regexp, $html, $matches, 0, $offset)) { + $file_id = $matches[2]; + $data_uri = ' '; + + if ($file_id && ($file = $_SESSION['identity']['files'][$file_id])) { + $file = $RCMAIL->plugins->exec_hook('attachment_get', $file); + + $data_uri .= 'src="data:' . $file['mimetype'] . ';base64,'; + $data_uri .= base64_encode($file['data'] ? $file['data'] : file_get_contents($file['path'])); + $data_uri .= '" '; + } + + $html = str_replace($matches[0], $data_uri, $html); + $offset += strlen($data_uri) - strlen($matches[0]) + 1; + } + + return $html; +} + /** * Sanity checks/cleanups on HTML body of signature */ -- cgit v1.2.3 From b8bcca7033b3d10eb7da4b7a1f9987ee9e25a45b Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Mon, 30 Jun 2014 12:26:15 +0200 Subject: Display quota information for current folder not INBOX only (#1487993) --- CHANGELOG | 1 + program/include/rcmail.php | 30 ++++++++++++++++++++++-------- program/steps/mail/check_recent.inc | 5 +++-- program/steps/mail/copy.inc | 16 ++++++++++------ program/steps/mail/folders.inc | 4 ++-- program/steps/mail/list.inc | 9 ++++++++- program/steps/mail/move_del.inc | 15 +++++++++------ program/steps/mail/search.inc | 16 ++++++++++++---- program/steps/settings/edit_folder.inc | 4 ++++ program/steps/settings/folders.inc | 4 ++-- 10 files changed, 73 insertions(+), 31 deletions(-) (limited to 'program/include/rcmail.php') diff --git a/CHANGELOG b/CHANGELOG index b73ee3f19..4730642ef 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ CHANGELOG Roundcube Webmail =========================== +- Display quota information for current folder not INBOX only (#1487993) - Support images in HTML signatures (#1488676) - Display full quota information in popup (#1485769, #1486604) - Mail compose: Selecting contact inserts recipient to previously focused input - to/cc/bcc accordingly (#1489684) diff --git a/program/include/rcmail.php b/program/include/rcmail.php index 54d0d88e6..b105eb892 100644 --- a/program/include/rcmail.php +++ b/program/include/rcmail.php @@ -1678,9 +1678,9 @@ class rcmail extends rcube } - public function quota_content($attrib = null) + public function quota_content($attrib = null, $folder = null) { - $quota = $this->storage->get_quota(); + $quota = $this->storage->get_quota($folder); $quota = $this->plugins->exec_hook('quota', $quota); $quota_result = (array) $quota; @@ -1748,6 +1748,10 @@ class rcmail extends rcube unset($quota_result['all']); } + if ($folder !== null && $folder !== '') { + $quota_result['folder'] = $folder; + } + return $quota_result; } @@ -2170,11 +2174,13 @@ class rcmail extends rcube /** * Returns message UID(s) and IMAP folder(s) from GET/POST data * - * @param string UID value to decode - * @param string Default mailbox value (if not encoded in UIDs) + * @param string UID value to decode + * @param string Default mailbox value (if not encoded in UIDs) + * @param bool Will be set to True if multi-folder request + * * @return array List of message UIDs per folder */ - public static function get_uids($uids = null, $mbox = null) + public static function get_uids($uids = null, $mbox = null, &$is_multifolder = false) { // message UID (or comma-separated list of IDs) is provided in // the form of -[,-]* @@ -2191,6 +2197,7 @@ class rcmail extends rcube // special case: * if ($_uid == '*' && is_object($_SESSION['search'][1]) && $_SESSION['search'][1]->multi) { + $is_multifolder = true; // extract the full list of UIDs per folder from the search set foreach ($_SESSION['search'][1]->sets as $subset) { $mbox = $subset->get_parameters('MAILBOX'); @@ -2204,12 +2211,19 @@ class rcmail extends rcube // create a per-folder UIDs array foreach ((array)$_uid as $uid) { list($uid, $mbox) = explode('-', $uid, 2); - if (!strlen($mbox)) + if (!strlen($mbox)) { $mbox = $_mbox; - if ($uid == '*') + } + else { + $is_multifolder = true; + } + + if ($uid == '*') { $result[$mbox] = $uid; - else + } + else { $result[$mbox][] = $uid; + } } } diff --git a/program/steps/mail/check_recent.inc b/program/steps/mail/check_recent.inc index 70f4c03a6..b95819415 100644 --- a/program/steps/mail/check_recent.inc +++ b/program/steps/mail/check_recent.inc @@ -77,12 +77,13 @@ foreach ($a_mailboxes as $mbox_name) { if ($search_request && isset($_SESSION['search'])) { unset($search_request); // only do this once $_SESSION['search'] = $RCMAIL->storage->refresh_search(); - if ($_SESSION['search'][1]->multi) + if ($_SESSION['search'][1]->multi) { $mbox_name = ''; + } } if (!empty($_POST['_quota'])) { - $OUTPUT->command('set_quota', $RCMAIL->quota_content()); + $OUTPUT->command('set_quota', $RCMAIL->quota_content(null, $mbox_name)); } $OUTPUT->set_env('exists', $RCMAIL->storage->count($mbox_name, 'EXISTS', true)); diff --git a/program/steps/mail/copy.inc b/program/steps/mail/copy.inc index 86586d34d..585310d47 100644 --- a/program/steps/mail/copy.inc +++ b/program/steps/mail/copy.inc @@ -24,15 +24,19 @@ if (!$OUTPUT->ajax_call) { return; } -// move messages +// copy messages if (!empty($_POST['_uid']) && strlen($_POST['_target_mbox'])) { - $target = rcube_utils::get_input_value('_target_mbox', rcube_utils::INPUT_POST, true); + $target = rcube_utils::get_input_value('_target_mbox', rcube_utils::INPUT_POST, true); + $sources = array(); - foreach (rcmail::get_uids() as $mbox => $uids) { - if ($mbox === $target) + foreach (rcmail::get_uids(null, null, $multifolder) as $mbox => $uids) { + if ($mbox === $target) { $copied++; - else + } + else { $copied += (int)$RCMAIL->storage->copy_message($uids, $target, $mbox); + $sources[] = $mbox; + } } if (!$copied) { @@ -47,7 +51,7 @@ if (!empty($_POST['_uid']) && strlen($_POST['_target_mbox'])) { rcmail_send_unread_count($target, true); - $OUTPUT->command('set_quota', $RCMAIL->quota_content()); + $OUTPUT->command('set_quota', $RCMAIL->quota_content(null, $multifolder ? $sources[0] : 'INBOX')); } // unknown action or missing query param else { diff --git a/program/steps/mail/folders.inc b/program/steps/mail/folders.inc index 519a41fdd..49bf25377 100644 --- a/program/steps/mail/folders.inc +++ b/program/steps/mail/folders.inc @@ -35,7 +35,7 @@ if ($RCMAIL->action == 'expunge') { $OUTPUT->show_message('folderexpunged', 'confirmation'); if (!empty($_REQUEST['_reload'])) { - $OUTPUT->command('set_quota', $RCMAIL->quota_content()); + $OUTPUT->command('set_quota', $RCMAIL->quota_content(null, $mbox)); $OUTPUT->command('message_list.clear'); $RCMAIL->action = 'list'; return; @@ -69,7 +69,7 @@ else if ($RCMAIL->action == 'purge') { $OUTPUT->command('message_list.clear'); $OUTPUT->command('set_rowcount', rcmail_get_messagecount_text(), $mbox); $OUTPUT->command('set_unread_count', $mbox, 0); - $OUTPUT->command('set_quota', $RCMAIL->quota_content()); + $OUTPUT->command('set_quota', $RCMAIL->quota_content(null, $mbox)); rcmail_set_unseen_count($mbox, 0); // set trash folder state diff --git a/program/steps/mail/list.inc b/program/steps/mail/list.inc index 496c95146..2dcc40d17 100644 --- a/program/steps/mail/list.inc +++ b/program/steps/mail/list.inc @@ -68,6 +68,8 @@ if ($_SESSION['search_filter'] && $_SESSION['search_filter'] != 'ALL') { $OUTPUT->set_env('search_request', $search_request); $OUTPUT->set_env('search_filter', $_SESSION['search_filter']); + + $multifolder = is_a($_SESSION['search'][1], 'rcube_result_multifolder'); } // fetch message headers @@ -96,12 +98,13 @@ rcmail_send_unread_count($mbox_name, !empty($_REQUEST['_refresh']), $unseen); // update message count display $pages = ceil($count/$RCMAIL->storage->get_pagesize()); +$page = $count ? $RCMAIL->storage->get_page() : 1; $exists = $RCMAIL->storage->count($mbox_name, 'EXISTS', true); $OUTPUT->set_env('messagecount', $count); $OUTPUT->set_env('pagecount', $pages); $OUTPUT->set_env('threading', $threading); -$OUTPUT->set_env('current_page', $count ? $RCMAIL->storage->get_page() : 1); +$OUTPUT->set_env('current_page', $page); $OUTPUT->set_env('exists', $exists); $OUTPUT->command('set_rowcount', rcmail_get_messagecount_text($count), $mbox_name); @@ -143,5 +146,9 @@ if ($mbox_name === $RCMAIL->config->get('trash_mbox')) { $OUTPUT->command('set_trash_count', $exists); } +if ($page == 1) { + $OUTPUT->command('set_quota', $RCMAIL->quota_content(null, $multifolder ? 'INBOX' : $mbox_name)); +} + // send response $OUTPUT->send(); diff --git a/program/steps/mail/move_del.inc b/program/steps/mail/move_del.inc index d98d49d1f..81d476b2e 100644 --- a/program/steps/mail/move_del.inc +++ b/program/steps/mail/move_del.inc @@ -27,6 +27,7 @@ if (!$OUTPUT->ajax_call) $threading = (bool) $RCMAIL->storage->get_threading(); $old_count = $RCMAIL->storage->count(NULL, $threading ? 'THREADS' : 'ALL'); $old_pages = ceil($old_count / $RCMAIL->storage->get_pagesize()); +$sources = array(); $trash = $RCMAIL->config->get('trash_mbox'); @@ -36,12 +37,13 @@ if ($RCMAIL->action == 'move' && !empty($_POST['_uid']) && strlen($_POST['_targe $trash = $RCMAIL->config->get('trash_mbox'); $success = true; - foreach (rcmail::get_uids() as $mbox => $uids) { + foreach (rcmail::get_uids(null, null, multifolder) as $mbox => $uids) { if ($mbox === $target) { $count += count($uids); } else if ($RCMAIL->storage->move_message($uids, $target, $mbox)) { $count += count($uids); + $sources[] = $mbox; } else { $success = false; @@ -69,10 +71,11 @@ if ($RCMAIL->action == 'move' && !empty($_POST['_uid']) && strlen($_POST['_targe } } // delete messages -else if ($RCMAIL->action=='delete' && !empty($_POST['_uid'])) { - foreach (rcmail::get_uids() as $mbox => $uids) { - $del += (int)$RCMAIL->storage->delete_message($uids, $mbox); - $count += count($uids); +else if ($RCMAIL->action == 'delete' && !empty($_POST['_uid'])) { + foreach (rcmail::get_uids(null, null, $multifolder) as $mbox => $uids) { + $del += (int)$RCMAIL->storage->delete_message($uids, $mbox); + $count += count($uids); + $sources[] = $mbox; } if (!$del) { @@ -146,7 +149,7 @@ else { rcmail_send_unread_count($target, true); } - $OUTPUT->command('set_quota', $RCMAIL->quota_content()); + $OUTPUT->command('set_quota', $RCMAIL->quota_content(null, $multifolder ? $sources[0] : 'INBOX')); $OUTPUT->command('set_rowcount', rcmail_get_messagecount_text($msg_count), $mbox); if ($threading) { diff --git a/program/steps/mail/search.inc b/program/steps/mail/search.inc index 4aa22e14b..b50593480 100644 --- a/program/steps/mail/search.inc +++ b/program/steps/mail/search.inc @@ -127,8 +127,9 @@ if ($search_str) { } else if ($scope == 'sub') { $mboxes = $RCMAIL->storage->list_folders_subscribed($mbox, '*', 'mail'); - if ($mbox != 'INBOX' && $mboxes[0] == 'INBOX') + if ($mbox != 'INBOX' && $mboxes[0] == 'INBOX') { array_shift($mboxes); + } } $result = $RCMAIL->storage->search($mboxes, $search_str, $imap_charset, $sort_column); @@ -144,7 +145,7 @@ if ($search_str) { $_SESSION['last_text_search'] = $str; } $_SESSION['search_request'] = $search_request; -$_SESSION['search_scope'] = $scope; +$_SESSION['search_scope'] = $scope; // Get the headers @@ -181,10 +182,13 @@ else if ($result->incomplete) { else { $OUTPUT->show_message('searchnomatch', 'notice'); $OUTPUT->set_env('multifolder_listing', (bool)$result->multi); - if ($result->multi && $scope == 'all') + if ($result->multi && $scope == 'all') { $OUTPUT->command('select_folder', ''); + } } +$OUTPUT->set_pagetitle($RCMAIL->gettext(array('name' => 'searchfor', 'vars' => array('q' => $str)))); + // update message count display $OUTPUT->set_env('search_request', $search_str ? $search_request : ''); $OUTPUT->set_env('search_filter', $_SESSION['search_filter']); @@ -193,5 +197,9 @@ $OUTPUT->set_env('messagecount', $count); $OUTPUT->set_env('pagecount', ceil($count/$RCMAIL->storage->get_pagesize())); $OUTPUT->set_env('exists', $mbox === null ? 0 : $RCMAIL->storage->count($mbox, 'EXISTS')); $OUTPUT->command('set_rowcount', rcmail_get_messagecount_text($count, 1), $mbox); -$OUTPUT->set_pagetitle($RCMAIL->gettext(array('name' => 'searchfor', 'vars' => array('q' => $str)))); + +if (!$result->incomplete) { + $OUTPUT->command('set_quota', $RCMAIL->quota_content(null, $result->multi ? 'INBOX' : $mbox)); +} + $OUTPUT->send(); diff --git a/program/steps/settings/edit_folder.inc b/program/steps/settings/edit_folder.inc index 87a45fa8d..51f4d8d72 100644 --- a/program/steps/settings/edit_folder.inc +++ b/program/steps/settings/edit_folder.inc @@ -290,6 +290,10 @@ function rcmail_folder_form($attrib) $RCMAIL->output->set_env('messagecount', (int) $msgcount); + if ($mbox_imap !== null && empty($_POST)) { + $RCMAIL->output->command('parent.set_quota', $RCMAIL->quota_content(null, $mbox_imap)); + } + return $out; } diff --git a/program/steps/settings/folders.inc b/program/steps/settings/folders.inc index 8b74a59e1..33b2b0624 100644 --- a/program/steps/settings/folders.inc +++ b/program/steps/settings/folders.inc @@ -134,7 +134,7 @@ else if ($RCMAIL->action == 'purge') { $success = $STORAGE->delete_message('*', $mbox); $delete = true; } - // copy to Trash + // move to Trash else { $success = $STORAGE->move_message('1:*', $trash_mbox, $mbox); $delete = false; @@ -144,7 +144,7 @@ else if ($RCMAIL->action == 'purge') { $OUTPUT->set_env('messagecount', 0); if ($delete) { $OUTPUT->show_message('folderpurged', 'confirmation'); - $OUTPUT->command('set_quota', $RCMAIL->quota_content()); + $OUTPUT->command('set_quota', $RCMAIL->quota_content(null, $mbox)); } else { $OUTPUT->show_message('messagemoved', 'confirmation'); -- cgit v1.2.3 From 5312b71126c0d1286a2d1c0788dd2636679a1746 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Mon, 30 Jun 2014 12:33:07 +0200 Subject: Allways return current folder from quota_content() --- program/include/rcmail.php | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) (limited to 'program/include/rcmail.php') diff --git a/program/include/rcmail.php b/program/include/rcmail.php index b105eb892..ed5fedb4a 100644 --- a/program/include/rcmail.php +++ b/program/include/rcmail.php @@ -1684,7 +1684,8 @@ class rcmail extends rcube $quota = $this->plugins->exec_hook('quota', $quota); $quota_result = (array) $quota; - $quota_result['type'] = isset($_SESSION['quota_display']) ? $_SESSION['quota_display'] : ''; + $quota_result['type'] = isset($_SESSION['quota_display']) ? $_SESSION['quota_display'] : ''; + $quota_result['folder'] = $folder !== null && $folder !== '' ? $folder : 'INBOX'; if ($quota['total'] > 0) { if (!isset($quota['percent'])) { @@ -1748,10 +1749,6 @@ class rcmail extends rcube unset($quota_result['all']); } - if ($folder !== null && $folder !== '') { - $quota_result['folder'] = $folder; - } - return $quota_result; } -- cgit v1.2.3 From d4783319a086b09eb50ac6ad5da93eb1eeb771ca Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Mon, 28 Jul 2014 09:39:37 +0200 Subject: Set 'compose_extwin' env property on every step; accept a list of URL parameters for the 'compose' command --- program/include/rcmail.php | 3 +++ program/js/app.js | 8 ++++++-- program/steps/addressbook/func.inc | 1 - program/steps/addressbook/show.inc | 1 - program/steps/mail/func.inc | 2 +- program/steps/mail/show.inc | 2 +- 6 files changed, 11 insertions(+), 6 deletions(-) (limited to 'program/include/rcmail.php') diff --git a/program/include/rcmail.php b/program/include/rcmail.php index ed5fedb4a..ceb369af8 100644 --- a/program/include/rcmail.php +++ b/program/include/rcmail.php @@ -427,6 +427,9 @@ class rcmail extends rcube $this->output->set_env('user_id', $this->user->get_hash()); } + // set compose mode for all tasks (message compose step can be triggered from everywhere) + $this->output->set_env('compose_extwin', $this->config->get('compose_extwin',false)); + // add some basic labels to client $this->output->add_label('loading', 'servererror', 'connerror', 'requesttimedout', 'refreshing', 'windowopenerror'); diff --git a/program/js/app.js b/program/js/app.js index e00479f90..b4e11b9d8 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -1052,7 +1052,7 @@ function rcube_webmail() if (this.task == 'mail') { url._mbox = this.env.mailbox; if (props) - url._to = props; + url._to = props; // also send search request so we can go back to search result after message is sent if (this.env.search_request) url._search = this.env.search_request; @@ -1080,8 +1080,12 @@ function rcube_webmail() break; } } - else if (props) + else if (props && typeof props == 'string') { url._to = props; + } + else if (props && typeof props == 'object') { + $.extend(url, props); + } this.open_compose_step(url); break; diff --git a/program/steps/addressbook/func.inc b/program/steps/addressbook/func.inc index f56e07169..d4c57cc9d 100644 --- a/program/steps/addressbook/func.inc +++ b/program/steps/addressbook/func.inc @@ -81,7 +81,6 @@ if (!$RCMAIL->action && !$OUTPUT->ajax_call) { $OUTPUT->set_env('search_mods', $search_mods); $OUTPUT->set_env('address_sources', $js_list); $OUTPUT->set_env('writable_source', $writeable); - $OUTPUT->set_env('compose_extwin', $RCMAIL->config->get('compose_extwin',false)); $OUTPUT->set_pagetitle($RCMAIL->gettext('addressbook')); $_SESSION['addressbooks_count'] = $count; diff --git a/program/steps/addressbook/show.inc b/program/steps/addressbook/show.inc index 4471ea658..8f357cd96 100644 --- a/program/steps/addressbook/show.inc +++ b/program/steps/addressbook/show.inc @@ -32,7 +32,6 @@ $SOURCE_ID = $source; if ($cid && ($record = $CONTACTS->get_record($cid, true))) { $OUTPUT->set_env('readonly', $CONTACTS->readonly || $record['readonly']); $OUTPUT->set_env('cid', $record['ID']); - $OUTPUT->set_env('compose_extwin', $RCMAIL->config->get('compose_extwin',false)); } // get address book name (for display) diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc index 77f11725f..8774c08fe 100644 --- a/program/steps/mail/func.inc +++ b/program/steps/mail/func.inc @@ -110,7 +110,7 @@ if (empty($RCMAIL->action) || $RCMAIL->action == 'list') { // set configuration $RCMAIL->set_env_config(array('delete_junk', 'flag_for_deletion', 'read_when_deleted', - 'skip_deleted', 'display_next', 'message_extwin', 'compose_extwin', 'forward_attachment')); + 'skip_deleted', 'display_next', 'message_extwin', 'forward_attachment')); if (!$OUTPUT->ajax_call) { $OUTPUT->add_label('checkingmail', 'deletemessage', 'movemessagetotrash', diff --git a/program/steps/mail/show.inc b/program/steps/mail/show.inc index e6ab4891b..4b2d78d31 100644 --- a/program/steps/mail/show.inc +++ b/program/steps/mail/show.inc @@ -80,7 +80,7 @@ if ($uid) { // set configuration $RCMAIL->set_env_config(array('delete_junk', 'flag_for_deletion', 'read_when_deleted', - 'skip_deleted', 'display_next', 'compose_extwin', 'forward_attachment')); + 'skip_deleted', 'display_next', 'forward_attachment')); // set special folders foreach (array('drafts', 'trash', 'junk') as $mbox) { -- cgit v1.2.3 From 06fdaf88cb1a355e445294beba4a89d0209ac71e Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Wed, 13 Aug 2014 19:15:12 +0200 Subject: Extend rcmail::url() to produce absolute and fully qualified URLs --- program/include/rcmail.php | 45 +++++++++++++++++++----- tests/RcmailFunc.php | 85 ++++++++++++++++++++++++++++++++++++++++++++++ tests/phpunit.xml | 1 + 3 files changed, 123 insertions(+), 8 deletions(-) create mode 100644 tests/RcmailFunc.php (limited to 'program/include/rcmail.php') diff --git a/program/include/rcmail.php b/program/include/rcmail.php index ceb369af8..bb2346f5e 100644 --- a/program/include/rcmail.php +++ b/program/include/rcmail.php @@ -789,11 +789,13 @@ class rcmail extends rcube /** * Build a valid URL to this instance of Roundcube * - * @param mixed Either a string with the action or url parameters as key-value pairs + * @param mixed Either a string with the action or url parameters as key-value pairs + * @param boolean Build an URL absolute to document root + * @param boolean Create fully qualified URL including http(s):// and hostname * * @return string Valid application URL */ - public function url($p) + public function url($p, $absolute = false, $full = false) { if (!is_array($p)) { if (strpos($p, 'http') === 0) { @@ -803,14 +805,15 @@ class rcmail extends rcube $p = array('_action' => @func_get_arg(0)); } - $task = $p['_task'] ? $p['_task'] : ($p['task'] ? $p['task'] : $this->task); - $p['_task'] = $task; - unset($p['task']); + $pre = array(); + $task = $p['_task'] ?: ($p['task'] ?: $this->task); + $pre['_task'] = $task; + unset($p['task'], $p['_task']); - $url = './' . $this->filename; + $url = $this->filename; $delm = '?'; - foreach (array_reverse($p) as $key => $val) { + foreach (array_merge($pre, $p) as $key => $val) { if ($val !== '' && $val !== null) { $par = $key[0] == '_' ? $key : '_'.$key; $url .= $delm.urlencode($par).'='.urlencode($val); @@ -818,7 +821,33 @@ class rcmail extends rcube } } - return $url; + if ($absolute || $full) { + $prefix = ''; + + // prepend protocol://hostname:port + if ($full) { + $schema = 'http'; + $default_port = 80; + if (rcube_utils::https_check()) { + $schema = 'https'; + $default_port = 443; + } + $prefix = $schema . '://' . preg_replace('/:\d+$/', '', $_SERVER['HTTP_HOST']); + if ($_SERVER['SERVER_PORT'] != $default_port) { + $prefix .= ':' . $_SERVER['SERVER_PORT']; + } + } + + // add base path to this Roundcube installation + $base_path = preg_replace('![^/]+$!', '', strval($_SERVER['SCRIPT_NAME'])); + if ($base_path == '') $base_path = '/'; + $prefix .= $base_path; + } + else { + $prefix = './'; + } + + return $prefix . $url; } /** diff --git a/tests/RcmailFunc.php b/tests/RcmailFunc.php new file mode 100644 index 000000000..09b54b22c --- /dev/null +++ b/tests/RcmailFunc.php @@ -0,0 +1,85 @@ +filename = ''; + } + + /** + * Class constructor + */ + function test_class() + { + $object = rcmail::get_instance(); + $this->assertInstanceOf('rcmail', $object, "Class singleton"); + } + + /** + * Test rcmail::url() + */ + function test_url() + { + $rcmail = rcmail::get_instance(); + $this->assertEquals( + './?_task=cli&_action=test', + $rcmail->url('test'), + "Action only" + ); + $this->assertEquals( + './?_task=cli&_action=test&_a=AA', + $rcmail->url(array('action' => 'test', 'a' => 'AA')), + "Unprefixed parameters" + ); + $this->assertEquals( + './?_task=cli&_action=test&_b=BB', + $rcmail->url(array('_action' => 'test', '_b' => 'BB', '_c' => null)), + "Prefixed parameters (skip empty)" + ); + $this->assertEquals( + '/sub/?_task=cli&_action=test&_mode=ABS', + $rcmail->url(array('_action' => 'test', '_mode' => 'ABS'), true), + "Absolute URL" + ); + + $this->assertEquals( + 'https://mail.example.org/sub/?_task=calendar&_action=test&_mode=FQ', + $rcmail->url(array('task' => 'calendar', '_action' => 'test', '_mode' => 'FQ'), true, true), + "Fully Qualified URL" + ); + + // with different SCRIPT_NAME values + $_SERVER['SCRIPT_NAME'] = 'index.php'; + $this->assertEquals( + '/?_task=cli&_action=test&_mode=ABS', + $rcmail->url(array('_action' => 'test', '_mode' => 'ABS'), true), + "Absolute URL (root)" + ); + $_SERVER['SCRIPT_NAME'] = ''; + $this->assertEquals( + '/?_task=cli&_action=test&_mode=ABS', + $rcmail->url(array('_action' => 'test', '_mode' => 'ABS'), true), + "Absolute URL (root)" + ); + + $_SERVER['HTTPS'] = false; + $_SERVER['SERVER_PORT'] = '8080'; + $this->assertEquals( + 'http://mail.example.org:8080/?_task=cli&_action=test&_mode=ABS', + $rcmail->url(array('_action' => 'test', '_mode' => 'ABS'), true, true), + "Full URL with port" + ); + } +} diff --git a/tests/phpunit.xml b/tests/phpunit.xml index 4d50ad6a0..5c27d0e0d 100644 --- a/tests/phpunit.xml +++ b/tests/phpunit.xml @@ -46,6 +46,7 @@ Framework/VCard.php Framework/Washtml.php MailFunc.php + RcmailFunc.php ./../plugins/acl/tests/Acl.php -- cgit v1.2.3 From e35eab5f946fbdca688040da61d33a1a4153f968 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Fri, 22 Aug 2014 08:52:29 +0200 Subject: Fix comm_path update on task switch (#1490041) --- CHANGELOG | 1 + program/include/rcmail.php | 5 +++++ 2 files changed, 6 insertions(+) (limited to 'program/include/rcmail.php') diff --git a/CHANGELOG b/CHANGELOG index d6606eb16..f2dcaded7 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -57,6 +57,7 @@ CHANGELOG Roundcube Webmail - Fix incorrect thumbnail rotation with GD and exif orientation data (#1490029) - Fix contacts list update after adding/deleting/moving a contact (#1490028, #1490033) - Fix handling of email addresses with quoted domain part (#1490040) +- Fix comm_path update on task switch (#1490041) RELEASE 1.0.2 ------------- diff --git a/program/include/rcmail.php b/program/include/rcmail.php index bb2346f5e..0c41c1598 100644 --- a/program/include/rcmail.php +++ b/program/include/rcmail.php @@ -147,8 +147,13 @@ class rcmail extends rcube $this->task = $task; $this->comm_path = $this->url(array('task' => $this->task)); + if (!empty($_REQUEST['_framed'])) { + $this->comm_path .= '&_framed=1'; + } + if ($this->output) { $this->output->set_env('task', $this->task); + $this->output->set_env('comm_path', $this->comm_path); } } -- cgit v1.2.3 From 75bbada03b0e616248ec3458d1a6ee98bfc03659 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Sun, 24 Aug 2014 11:23:33 +0200 Subject: Remove code for PHP<5.3, use PHP_VERSION_ID instead of version_compare() for version checks --- program/include/rcmail.php | 2 +- program/lib/Roundcube/rcube_utils.php | 11 ----------- program/lib/Roundcube/rcube_washtml.php | 2 +- 3 files changed, 2 insertions(+), 13 deletions(-) (limited to 'program/include/rcmail.php') diff --git a/program/include/rcmail.php b/program/include/rcmail.php index 0c41c1598..52b53e9d9 100644 --- a/program/include/rcmail.php +++ b/program/include/rcmail.php @@ -173,7 +173,7 @@ class rcmail extends rcube setlocale(LC_ALL, $lang . '.utf8', $lang . '.UTF-8', 'en_US.utf8', 'en_US.UTF-8'); // workaround for http://bugs.php.net/bug.php?id=18556 - if (version_compare(PHP_VERSION, '5.5.0', '<') && in_array($lang, array('tr_TR', 'ku', 'az_AZ'))) { + if (PHP_VERSION_ID < 50500 && in_array($lang, array('tr_TR', 'ku', 'az_AZ'))) { setlocale(LC_CTYPE, 'en_US.utf8', 'en_US.UTF-8'); } } diff --git a/program/lib/Roundcube/rcube_utils.php b/program/lib/Roundcube/rcube_utils.php index 39e27fc7f..86d9eb2cc 100644 --- a/program/lib/Roundcube/rcube_utils.php +++ b/program/lib/Roundcube/rcube_utils.php @@ -119,17 +119,6 @@ class rcube_utils return true; } - if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN' && version_compare(PHP_VERSION, '5.3.0', '<')) { - $lookup = array(); - @exec("nslookup -type=MX " . escapeshellarg($domain_part) . " 2>&1", $lookup); - foreach ($lookup as $line) { - if (strpos($line, 'MX preference')) { - return true; - } - } - return false; - } - // find MX record(s) if (!function_exists('getmxrr') || getmxrr($domain_part, $mx_records)) { return true; diff --git a/program/lib/Roundcube/rcube_washtml.php b/program/lib/Roundcube/rcube_washtml.php index b93d3b117..97ab56cdf 100644 --- a/program/lib/Roundcube/rcube_washtml.php +++ b/program/lib/Roundcube/rcube_washtml.php @@ -378,7 +378,7 @@ class rcube_washtml $this->max_nesting_level = (int) @ini_get('xdebug.max_nesting_level'); // Use optimizations if supported - if (version_compare(PHP_VERSION, '5.4.0', '>=')) { + if (PHP_VERSION_ID >= 50400) { @$node->loadHTML($html, LIBXML_PARSEHUGE | LIBXML_COMPACT); } else { -- cgit v1.2.3 From 5f58127eae9ed8c54c190506e11af13e8ba57170 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Sun, 24 Aug 2014 11:43:12 +0200 Subject: Added rcube_utils::resolve_url() --- program/include/rcmail.php | 21 ++++++--------------- program/lib/Roundcube/rcube_utils.php | 30 ++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 15 deletions(-) (limited to 'program/include/rcmail.php') diff --git a/program/include/rcmail.php b/program/include/rcmail.php index 52b53e9d9..ece0606ae 100644 --- a/program/include/rcmail.php +++ b/program/include/rcmail.php @@ -827,26 +827,17 @@ class rcmail extends rcube } if ($absolute || $full) { - $prefix = ''; + // add base path to this Roundcube installation + $base_path = preg_replace('![^/]+$!', '', strval($_SERVER['SCRIPT_NAME'])); + if ($base_path == '') $base_path = '/'; + $prefix = $base_path; // prepend protocol://hostname:port if ($full) { - $schema = 'http'; - $default_port = 80; - if (rcube_utils::https_check()) { - $schema = 'https'; - $default_port = 443; - } - $prefix = $schema . '://' . preg_replace('/:\d+$/', '', $_SERVER['HTTP_HOST']); - if ($_SERVER['SERVER_PORT'] != $default_port) { - $prefix .= ':' . $_SERVER['SERVER_PORT']; - } + $prefix = rcube_utils::resolve_url($prefix); } - // add base path to this Roundcube installation - $base_path = preg_replace('![^/]+$!', '', strval($_SERVER['SCRIPT_NAME'])); - if ($base_path == '') $base_path = '/'; - $prefix .= $base_path; + $prefix = rtrim($prefix, '/') . '/'; } else { $prefix = './'; diff --git a/program/lib/Roundcube/rcube_utils.php b/program/lib/Roundcube/rcube_utils.php index 86d9eb2cc..ef303f8c1 100644 --- a/program/lib/Roundcube/rcube_utils.php +++ b/program/lib/Roundcube/rcube_utils.php @@ -1071,4 +1071,34 @@ class rcube_utils return $path[0] == DIRECTORY_SEPARATOR; } } + + /** + * Resolve relative URL + * + * @param string $url Relative URL + * + * @return string Absolute URL + */ + public static function resolve_url($url) + { + // prepend protocol://hostname:port + if (!preg_match('|^https?://|', $url)) { + $schema = 'http'; + $default_port = 80; + + if (self::https_check()) { + $schema = 'https'; + $default_port = 443; + } + + $prefix = $schema . '://' . preg_replace('/:\d+$/', '', $_SERVER['HTTP_HOST']); + if ($_SERVER['SERVER_PORT'] != $default_port) { + $prefix .= ':' . $_SERVER['SERVER_PORT']; + } + + $url = $prefix . ($url[0] == '/' ? '' : '/') . $url; + } + + return $url; + } } -- cgit v1.2.3