pull/7403/merge
Aleksander Machniak 4 years ago committed by GitHub
commit aed8f7e46f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -36,28 +36,26 @@
},
{
"lib": "tinymce",
"version": "4.8.2",
"version": "5.3.0",
"url": "https://download.tiny.cloud/tinymce/community/tinymce_$v.zip",
"dest": "program/js",
"sha1": "d7fced05acdeeb78299585ea9909b0de2b3d759d",
"sha1": "74f51b87eebcc33de2bfded54c0e255c7ba7e0df",
"license": "LGPL",
"copyright": "Copyright (c) 1999-2015 Ephox Corp. All rights reserved",
"copyright": "Copyright (c) Tiny Technologies, Inc. All rights reserved",
"rm": "program/js/tinymce",
"map": {
"js/tinymce": "tinymce"
},
"omit": [
"tinymce/license.txt",
"tinymce/jquery.tinymce.min.js"
],
"addlicense": [
"tinymce/tinymce.min.js"
"tinymce/jquery.tinymce.min.js",
"tinymce/themes/mobile"
]
},
{
"lib": "tinymce-langs",
"version": "4.8.2",
"url": "https://www.tiny.cloud/docs-4x/language/tinymce4x_languages.zip",
"version": "5.3.0",
"url": "https://www.tiny.cloud/tinymce-services-azure/1/i18n/download?langs=ar,hy,az,eu,be,bs,bg_BG,ca,zh_CN,zh_TW,hr,cs,cs_CZ,da,nl,en_CA,en_GB,eo,et,fo,fi,fr_FR,fr_CH,gd,gl,ka_GE,de,de_AT,el,he_IL,hi_IN,hu_HU,is_IS,id,ga,it,ja,kab,km_KH,ko_KR,ku,ku_IQ,lv,lt,lb,mk_MK,ml_IN,nb_NO,oc,fa,fa_IR,pl,pt_BR,pt_PT,ro,ru,sk,sl_SI,es,es_MX,sv_SE,tg,ta,ta_IN,tt,th_TH,tr,tr_TR,ug,uk,uk_UA,vi,vi_VN,cy&v=$v&extension=.zip",
"dest": "program/js/tinymce"
},
{

@ -3,7 +3,7 @@
"type": "roundcube-plugin",
"description": "Plugin that adds emoticons support.",
"license": "GPLv3+",
"version": "2.0",
"version": "3.0",
"authors": [
{
"name": "Thomas Bruederli",

@ -1,15 +1,15 @@
<?php
/**
* Emoticons
* Emoticons.
*
* Plugin to replace emoticons in plain text message body with real icons.
* Plugin to replace emoticons in plain text message body with real emoji.
* Also it enables emoticons in HTML compose editor. Both features are optional.
*
* @license GNU GPLv3+
* @author Thomas Bruederli
* @author Aleksander Machniak
* @website http://roundcube.net
* @website https://roundcube.net
*/
class emoticons extends rcube_plugin
{
@ -24,8 +24,6 @@ class emoticons extends rcube_plugin
$rcube = rcube::get_instance();
$this->add_hook('message_part_after', array($this, 'message_part_after'));
$this->add_hook('message_outgoing_body', array($this, 'message_outgoing_body'));
$this->add_hook('html2text', array($this, 'html2text'));
$this->add_hook('html_editor', array($this, 'html_editor'));
if ($rcube->task == 'settings') {
@ -35,8 +33,8 @@ class emoticons extends rcube_plugin
}
/**
* 'message_part_after' hook handler to replace common plain text emoticons
* with emoticon images (<img>)
* 'message_part_after' hook handler to replace common
* plain text emoticons with emoji
*/
function message_part_after($args)
{
@ -48,65 +46,7 @@ class emoticons extends rcube_plugin
return $args;
}
require_once __DIR__ . '/emoticons_engine.php';
$args['body'] = emoticons_engine::text2icons($args['body']);
}
return $args;
}
/**
* 'message_outgoing_body' hook handler to replace image emoticons from TinyMCE
* editor with image attachments.
*/
function message_outgoing_body($args)
{
if ($args['type'] == 'html') {
$this->load_config();
$rcube = rcube::get_instance();
if (!$rcube->config->get('emoticons_compose', true)) {
return $args;
}
require_once __DIR__ . '/emoticons_engine.php';
// look for "emoticon" images from TinyMCE and change their src paths to
// be file paths on the server instead of URL paths.
$images = emoticons_engine::replace($args['body']);
// add these images as attachments to the MIME message
foreach ($images as $img_name => $img_file) {
$args['message']->addHTMLImage($img_file, 'image/gif', '', true, $img_name);
}
}
return $args;
}
/**
* 'html2text' hook handler to replace image emoticons from TinyMCE
* editor with plain text emoticons.
*
* This is executed on html2text action, i.e. when switching from HTML to text
* in compose window (or similar place). Also when generating alternative
* text/plain part.
*/
function html2text($args)
{
$rcube = rcube::get_instance();
if ($rcube->action == 'html2text' || $rcube->action == 'send') {
$this->load_config();
if (!$rcube->config->get('emoticons_compose', true)) {
return $args;
}
require_once __DIR__ . '/emoticons_engine.php';
$args['body'] = emoticons_engine::icons2text($args['body']);
$args['body'] = self::text2icons($args['body']);
}
return $args;
@ -170,16 +110,58 @@ class emoticons extends rcube_plugin
*/
function preferences_save($args)
{
$rcube = rcube::get_instance();
$dont_override = $rcube->config->get('dont_override', array());
if ($args['section'] == 'mailview' && !in_array('emoticons_display', $dont_override)) {
$args['prefs']['emoticons_display'] = rcube_utils::get_input_value('_emoticons_display', rcube_utils::INPUT_POST) ? true : false;
if ($args['section'] == 'mailview') {
$args['prefs']['emoticons_display'] = !empty(rcube_utils::get_input_value('_emoticons_display', rcube_utils::INPUT_POST));
}
else if ($args['section'] == 'compose' && !in_array('emoticons_compose', $dont_override)) {
$args['prefs']['emoticons_compose'] = rcube_utils::get_input_value('_emoticons_compose', rcube_utils::INPUT_POST) ? true : false;
else if ($args['section'] == 'compose') {
$args['prefs']['emoticons_compose'] = !empty(rcube_utils::get_input_value('_emoticons_compose', rcube_utils::INPUT_POST));
}
return $args;
}
/**
* Replace common plain text emoticons with emoji
*
* @param string $text Text
*
* @return string Converted text
*/
protected static function text2icons($text)
{
// This is a lookbehind assertion which will exclude html entities
// E.g. situation when ";)" in "&quot;)" shouldn't be replaced by the icon
// It's so long because of assertion format restrictions
$entity = '(?<!&'
. '[a-zA-Z0-9]{2}' . '|' . '#[0-9]{2}' . '|'
. '[a-zA-Z0-9]{3}' . '|' . '#[0-9]{3}' . '|'
. '[a-zA-Z0-9]{4}' . '|' . '#[0-9]{4}' . '|'
. '[a-zA-Z0-9]{5}' . '|'
. '[a-zA-Z0-9]{6}' . '|'
. '[a-zA-Z0-9]{7}'
. ')';
// map of emoticon replacements
$map = array(
'/(?<!mailto):-?D/' => self::ico_tag('1f603', ':D' ), // laugh
'/:-?\(/' => self::ico_tag('1f626', ':(' ), // frown
'/'.$entity.';-?\)/' => self::ico_tag('1f609', ';)' ), // wink
'/8-?\)/' => self::ico_tag('1f60e', '8)' ), // cool
'/(?<!mailto):-?O/i' => self::ico_tag('1f62e', ':O' ), // surprised
'/(?<!mailto):-?P/i' => self::ico_tag('1f61b', ':P' ), // tongue out
'/(?<!mailto):-?@/i' => self::ico_tag('1f631', ':-@' ), // yell
'/O:-?\)/i' => self::ico_tag('1f607', 'O:-)' ), // innocent
'/(?<!O):-?\)/' => self::ico_tag('1f60a', ':-)' ), // smile
'/(?<!mailto):-?\$/' => self::ico_tag('1f633', ':-$' ), // embarrassed
'/(?<!mailto):-?\*/i' => self::ico_tag('1f48b', ':-*' ), // kiss
'/(?<!mailto):-?S/i' => self::ico_tag('1f615', ':-S' ), // undecided
);
return preg_replace(array_keys($map), array_values($map), $text);
}
protected static function ico_tag($ico, $title)
{
return html::span(array('title' => $title), "&#x{$ico};");
}
}

@ -1,152 +0,0 @@
<?php
/**
* @license GNU GPLv3+
* @author Thomas Bruederli
* @author Aleksander Machniak
*/
class emoticons_engine
{
const IMG_PATH = 'program/js/tinymce/plugins/emoticons/img/';
/**
* Replaces TinyMCE's emoticon images with plain-text representation
*
* @param string $html HTML content
*
* @return string HTML content
*/
public static function icons2text($html)
{
$emoticons = array(
'8-)' => 'smiley-cool',
':-#' => 'smiley-foot-in-mouth',
':-*' => 'smiley-kiss',
':-X' => 'smiley-sealed',
':-P' => 'smiley-tongue-out',
':-@' => 'smiley-yell',
":'(" => 'smiley-cry',
':-(' => 'smiley-frown',
':-D' => 'smiley-laughing',
':-)' => 'smiley-smile',
':-S' => 'smiley-undecided',
':-$' => 'smiley-embarassed',
'O:-)' => 'smiley-innocent',
':-|' => 'smiley-money-mouth',
':-O' => 'smiley-surprised',
';-)' => 'smiley-wink',
);
foreach ($emoticons as $idx => $file) {
// <img title="Cry" src="http://.../program/js/tinymce/plugins/emoticons/img/smiley-cry.gif" border="0" alt="Cry" />
$file = preg_quote(self::IMG_PATH . $file . '.gif', '/');
$search[] = '/<img (title="[a-z ]+" )?src="[^"]+' . $file . '"[^>]+\/>/i';
$replace[] = $idx;
}
return preg_replace($search, $replace, $html);
}
/**
* Replace common plain text emoticons with empticon <img> tags
*
* @param string $text Text
*
* @return string Converted text
*/
public static function text2icons($text)
{
// This is a lookbehind assertion which will exclude html entities
// E.g. situation when ";)" in "&quot;)" shouldn't be replaced by the icon
// It's so long because of assertion format restrictions
$entity = '(?<!&'
. '[a-zA-Z0-9]{2}' . '|' . '#[0-9]{2}' . '|'
. '[a-zA-Z0-9]{3}' . '|' . '#[0-9]{3}' . '|'
. '[a-zA-Z0-9]{4}' . '|' . '#[0-9]{4}' . '|'
. '[a-zA-Z0-9]{5}' . '|'
. '[a-zA-Z0-9]{6}' . '|'
. '[a-zA-Z0-9]{7}'
. ')';
// map of emoticon replacements
$map = array(
'/(?<!mailto):D/' => self::img_tag('smiley-laughing.gif', ':D' ),
'/:-D/' => self::img_tag('smiley-laughing.gif', ':-D' ),
'/:\(/' => self::img_tag('smiley-frown.gif', ':(' ),
'/:-\(/' => self::img_tag('smiley-frown.gif', ':-(' ),
'/'.$entity.';\)/' => self::img_tag('smiley-wink.gif', ';)' ),
'/'.$entity.';-\)/' => self::img_tag('smiley-wink.gif', ';-)' ),
'/8\)/' => self::img_tag('smiley-cool.gif', '8)' ),
'/8-\)/' => self::img_tag('smiley-cool.gif', '8-)' ),
'/(?<!mailto):O/i' => self::img_tag('smiley-surprised.gif', ':O' ),
'/(?<!mailto):-O/i' => self::img_tag('smiley-surprised.gif', ':-O' ),
'/(?<!mailto):P/i' => self::img_tag('smiley-tongue-out.gif', ':P' ),
'/(?<!mailto):-P/i' => self::img_tag('smiley-tongue-out.gif', ':-P' ),
'/(?<!mailto):@/i' => self::img_tag('smiley-yell.gif', ':@' ),
'/(?<!mailto):-@/i' => self::img_tag('smiley-yell.gif', ':-@' ),
'/O:\)/i' => self::img_tag('smiley-innocent.gif', 'O:)' ),
'/O:-\)/i' => self::img_tag('smiley-innocent.gif', 'O:-)' ),
'/(?<!O):\)/' => self::img_tag('smiley-smile.gif', ':)' ),
'/(?<!O):-\)/' => self::img_tag('smiley-smile.gif', ':-)' ),
'/(?<!mailto):\$/' => self::img_tag('smiley-embarassed.gif', ':$' ),
'/(?<!mailto):-\$/' => self::img_tag('smiley-embarassed.gif', ':-$' ),
'/(?<!mailto):\*/i' => self::img_tag('smiley-kiss.gif', ':*' ),
'/(?<!mailto):-\*/i' => self::img_tag('smiley-kiss.gif', ':-*' ),
'/(?<!mailto):S/i' => self::img_tag('smiley-undecided.gif', ':S' ),
'/(?<!mailto):-S/i' => self::img_tag('smiley-undecided.gif', ':-S' ),
);
return preg_replace(array_keys($map), array_values($map), $text);
}
protected static function img_tag($ico, $title)
{
return html::img(array('src' => './' . self::IMG_PATH . $ico, 'title' => $title));
}
/**
* Replace emoticon icons <img> 'src' attribute, so it can
* be replaced with real file by Mail_Mime.
*
* @param string &$html HTML content
*
* @return array List of image files
*/
public static function replace(&$html)
{
// Replace this:
// <img src="http[s]://.../tinymce/plugins/emoticons/img/smiley-cool.gif" ... />
// with this:
// <img src="/path/on/server/.../tinymce/plugins/emoticons/img/smiley-cool.gif" ... />
$rcube = rcube::get_instance();
$assets_dir = $rcube->config->get('assets_dir');
$path = unslashify($assets_dir ?: INSTALL_PATH) . '/' . self::IMG_PATH;
$offset = 0;
$images = array();
// remove any null-byte characters before parsing
$html = preg_replace('/\x00/', '', $html);
if (preg_match_all('# src=[\'"]([^\'"]+)#', $html, $matches, PREG_OFFSET_CAPTURE)) {
foreach ($matches[1] as $m) {
// find emoticon image tags
if (preg_match('#'. self::IMG_PATH . '(.*)$#', $m[0], $imatches)) {
$image_name = $imatches[1];
// sanitize image name so resulting attachment doesn't leave images dir
$image_name = preg_replace('/[^a-zA-Z0-9_\.\-]/i', '', $image_name);
$image_file = $path . $image_name;
// Add the same image only once
$images[$image_name] = $image_file;
$html = substr_replace($html, $image_file, $m[1] + $offset, strlen($m[0]));
$offset += strlen($image_file) - strlen($m[0]);
}
}
}
return $images;
}
}

@ -17,5 +17,3 @@
$labels = array();
$labels['emoticonsdisplay'] = 'Display emoticons in plain text messages';
$labels['emoticonscompose'] = 'Enable emoticons';
?>

@ -1,48 +0,0 @@
<?php
class EmoticonsEngine extends PHPUnit\Framework\TestCase
{
function setUp()
{
include_once __DIR__ . '/../emoticons_engine.php';
}
/**
* text2icons() method tests
*/
function test_text2icons()
{
$map = array(
':D' => array('smiley-laughing.gif', ':D' ),
':-D' => array('smiley-laughing.gif', ':-D' ),
':(' => array('smiley-frown.gif', ':(' ),
':-(' => array('smiley-frown.gif', ':-(' ),
'8)' => array('smiley-cool.gif', '8)' ),
'8-)' => array('smiley-cool.gif', '8-)' ),
':O' => array('smiley-surprised.gif', ':O' ),
':-O' => array('smiley-surprised.gif', ':-O' ),
':P' => array('smiley-tongue-out.gif', ':P' ),
':-P' => array('smiley-tongue-out.gif', ':-P' ),
':@' => array('smiley-yell.gif', ':@' ),
':-@' => array('smiley-yell.gif', ':-@' ),
'O:)' => array('smiley-innocent.gif', 'O:)' ),
'O:-)' => array('smiley-innocent.gif', 'O:-)' ),
':)' => array('smiley-smile.gif', ':)' ),
':-)' => array('smiley-smile.gif', ':-)' ),
':$' => array('smiley-embarassed.gif', ':$' ),
':-$' => array('smiley-embarassed.gif', ':-$' ),
':*' => array('smiley-kiss.gif', ':*' ),
':-*' => array('smiley-kiss.gif', ':-*' ),
':S' => array('smiley-undecided.gif', ':S' ),
':-S' => array('smiley-undecided.gif', ':-S' ),
);
foreach ($map as $body => $expected) {
$result = emoticons_engine::text2icons($body);
$this->assertRegExp('/' . preg_quote($expected[0], '/') . '/', $result);
$this->assertRegExp('/title="' . preg_quote($expected[1], '/') . '"/', $result);
}
}
}

@ -2138,8 +2138,22 @@ class rcmail extends rcube
}
}
$this->output->add_label('selectimage', 'addimage', 'selectmedia', 'addmedia', 'close');
$font_family = $this->output->get_env('default_font');
$font_size = $this->output->get_env('default_font_size');
$style = array();
if ($font_family) {
$style[] = "font-family: $font_family;";
}
if ($font_size) {
$style[] = "font-size: $font_size;";
}
if (!empty($style)) {
$config['content_style'] = "body {" . implode(' ', $style) . "}";
}
$this->output->set_env('editor_config', $config);
$this->output->add_label('selectimage', 'addimage', 'selectmedia', 'addmedia', 'close');
if ($path = $this->config->get('media_browser_css_location', 'program/resources/tinymce/browser.css')) {
if ($path != 'none' && ($path = $this->find_asset($path))) {

@ -39,13 +39,17 @@ function rcube_text_editor(config, id)
abs_url = location.href.replace(/[?#].*$/, '').replace(/\/$/, ''),
conf = {
selector: '#' + ($('#' + id).is('.mce_editor') ? id : 'fake-editor-id'),
cache_suffix: 's=4080200',
theme: 'modern',
cache_suffix: 's=5030000',
theme: 'silver',
language: config.lang,
content_css: rcmail.assets_path(config.content_css),
content_style: config.content_style,
menubar: false,
statusbar: false,
toolbar_items_size: 'small',
// toolbar_sticky: true, // does not work in scrollable element: https://github.com/tinymce/tinymce/issues/5227
toolbar_drawer: 'sliding',
toolbar: 'bold italic underline | alignleft aligncenter alignright alignjustify'
+ ' | fontselect fontsizeselect | forecolor backcolor',
extended_valid_elements: 'font[face|size|color|style],span[id|class|align|style]',
fontsize_formats: '8pt 9pt 10pt 11pt 12pt 14pt 18pt 24pt 36pt',
// Allow style tag, have to be allowed inside body/div/blockquote (#7088)
@ -55,10 +59,18 @@ function rcube_text_editor(config, id)
convert_urls: false, // #1486944
image_description: false,
paste_webkit_style: "color font-size font-family",
automatic_uploads: false, // allows to paste images
paste_data_images: true,
// Note: We disable contextmenu options specifically for browser_spellcheck:true.
// Otherwise user would have to use Right-Click with CTRL to get to
// the browser's spellchecker options. Should you disable browser_spellcheck
// you can enable other contextmenu options (by removing these options below).
browser_spellcheck: true,
contextmenu: 'spellchecker',
anchor_bottom: false,
anchor_top: false
anchor_top: false,
file_picker_types: 'image media',
file_picker_callback: function(callback, value, meta) { ref.file_picker_callback(callback, value, meta); }
};
// register spellchecker for plain text editor
@ -84,29 +96,22 @@ function rcube_text_editor(config, id)
// minimal editor
if (config.mode == 'identity') {
conf.toolbar += ' | charmap hr link unlink image code $extra';
$.extend(conf, {
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 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'
plugins: 'autolink charmap code hr image link paste tabfocus',
file_picker_types: 'image'
});
}
// full-featured editor
else {
$.extend(conf, {
plugins: 'autolink charmap code colorpicker directionality link lists image media nonbreaking'
+ ' paste table tabfocus textcolor searchreplace spellchecker',
toolbar: 'bold italic underline | alignleft aligncenter alignright alignjustify'
+ ' | bullist numlist outdent indent ltr rtl blockquote | forecolor backcolor | fontselect fontsizeselect'
conf.toolbar += ' | bullist numlist outdent indent ltr rtl blockquote'
+ ' | link unlink table | $extra charmap image media | code searchreplace undo redo',
$.extend(conf, {
plugins: 'autolink charmap code directionality link lists image media nonbreaking'
+ ' paste table tabfocus searchreplace spellchecker',
spellchecker_rpc_url: abs_url + '/?_task=utils&_action=spell_html&_remote=1',
spellchecker_language: rcmail.env.spell_lang,
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 media'
min_height: 400,
});
}
@ -176,7 +181,7 @@ function rcube_text_editor(config, id)
if (rcmail.env.action == 'compose') {
var area = $('#' + this.id),
height = $('div.mce-toolbar-grp', area.parent()).first().height();
height = $('div.tox-toolbar__group', area.parent()).first().height();
// the editor might be still not fully loaded, making the editing area
// inaccessible, wait and try again (#1490310)
@ -184,19 +189,9 @@ function rcube_text_editor(config, id)
return setTimeout(function () { ref.init_callback(editor); }, 300);
}
var css = {},
elem = rcube_find_object('_from'),
var elem = rcube_find_object('_from'),
fe = rcmail.env.compose_focus_elem;
if (rcmail.env.default_font)
css['font-family'] = rcmail.env.default_font;
if (rcmail.env.default_font_size)
css['font-size'] = rcmail.env.default_font_size;
if (css['font-family'] || css['font-size'])
$(this.editor.getBody()).css(css);
if (elem && elem.type == 'select-one') {
// insert signature (only for the first time)
if (!rcmail.env.identities_initialized)
@ -654,21 +649,27 @@ function rcube_text_editor(config, id)
};
// image selector
this.file_browser_callback = function(field_name, url, type)
this.file_picker_callback = function(callback, value, meta)
{
var i, button, elem, cancel, dialog, fn, hint, list = [],
type = meta.filetype,
form = $('.upload-form').clone();
// open image selector dialog
this.editor.windowManager.open({
title: rcmail.get_label('select' + type),
width: 500,
html: '<div id="image-selector" class="image-selector file-upload"><ul id="image-selector-list" class="attachmentslist"></ul></div>',
buttons: [{text: rcmail.get_label('close'), onclick: function() { ref.file_browser_close(); }}]
body: {
type: 'panel',
items: [{
type: 'htmlpanel',
html: '<div id="image-selector" class="image-selector file-upload"><ul id="image-selector-list" class="attachmentslist"></ul></div>',
}]
},
buttons: [{type: 'cancel', text: rcmail.get_label('close'), onclick: function() { ref.file_picker_close(); }}]
});
rcmail.env.file_browser_field = field_name;
rcmail.env.file_browser_type = type;
rcmail.env.file_picker_callback = callback;
rcmail.env.file_picker_type = type;
dialog = $('#image-selector');
@ -681,14 +682,17 @@ function rcube_text_editor(config, id)
.text(rcmail.get_label('add' + type))
.focus();
if (!button.is('.btn'))
button.addClass('tox-button');
// fill images list with available images
for (i in rcmail.env.attachments) {
if (elem = ref.file_browser_entry(i, rcmail.env.attachments[i])) {
if (elem = ref.file_picker_entry(i, rcmail.env.attachments[i])) {
list.push(elem);
}
}
cancel = dialog.parent().parent().find('button').last().parent();
cancel = dialog.parents('.tox-dialog').find('button').last();
// Add custom Tab key handlers, tabindex does not work
list = $('#image-selector-list').append(list).on('keydown', 'li', function(e) {
@ -754,7 +758,7 @@ function rcube_text_editor(config, id)
rcmail.env['file_dialog_event+' + type] = true;
rcmail.addEventListener('fileuploaded', function(attr) {
var elem;
if (elem = ref.file_browser_entry(attr.name, attr.attachment)) {
if (elem = ref.file_picker_entry(attr.name, attr.attachment)) {
list.prepend(elem);
elem.focus();
}
@ -765,23 +769,19 @@ function rcube_text_editor(config, id)
};
// close file browser window
this.file_browser_close = function(url)
this.file_picker_close = function(url)
{
var input = $('#' + rcmail.env.file_browser_field);
if (url)
input.val(url);
this.editor.windowManager.close();
input.focus();
if (url)
rcmail.env.file_picker_callback(url);
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)
this.file_picker_entry = function(file_id, file)
{
if (!file.complete || !file.mimetype) {
return;
@ -793,7 +793,7 @@ function rcube_text_editor(config, id)
var rx, img_src;
switch (rcmail.env.file_browser_type) {
switch (rcmail.env.file_picker_type) {
case 'image':
rx = /^image\//i;
break;
@ -817,10 +817,10 @@ function rcube_text_editor(config, id)
.data('url', href)
.append($('<span class="img">').append(img))
.append($('<span class="name">').text(file.name))
.click(function() { ref.file_browser_close($(this).data('url')); })
.click(function() { ref.file_picker_close($(this).data('url')); })
.keydown(function(e) {
if (e.which == 13) {
ref.file_browser_close($(this).data('url'));
ref.file_picker_close($(this).data('url'));
}
});
}

@ -1,20 +1,17 @@
/* This file contains the CSS data for media file selector of TinyMCE */
#image-selector {
margin: 10px;
margin-bottom: 30px;
padding-bottom: 85px;
border: 1px solid transparent;
}
#image-selector.droptarget.hover,
#image-selector.droptarget.active {
border: 1px solid #019bc6;
box-shadow: 0 0 3px 2px rgba(71,135,177, 0.5);
}
#image-selector.droptarget.hover {
background-color: #d9ecf4;
box-shadow: 0 0 5px 2px rgba(71,135,177, 0.9);
}
#image-selector form {
@ -29,36 +26,29 @@
text-align: center;
}
#image-selector a.button {
color: #525252;
text-decoration: none;
font-size: 11px;
}
#image-selector .upload-form {
text-align: center;
margin-bottom: 1rem;
}
#image-selector .upload-form button {
padding: 4px 8px;
border: 1px solid #c0c0c0;
}
#image-selector-list {
overflow-x: hidden;
overflow-y: auto;
margin-left: 0;
padding: 0;
height: 250px;
}
#image-selector-list li {
line-height: 80px;
padding: 2px;
padding: 3px;
padding-left: 5px;
cursor: pointer;
overflow: hidden;
text-overflow: ellipsis;
background: none;
display:flex;
align-items: center;
margin-bottom: 1px;
}
#image-selector-list li:hover,
@ -72,7 +62,6 @@
}
#image-selector-list li span.name {
vertical-align: middle;
font-weight: bold;
padding-left: 10px;
}
@ -80,8 +69,14 @@
#image-selector-list li span.img {
height: 80px;
width: 80px;
min-width: 80px;
text-align: center;
display: inline-block;
overflow: hidden;
line-height: 80px;
border-radius: 5px;
display: flex;
align-items: center;
justify-content: center;
background: #fff;
border: 1px solid #ddd;
}

@ -1567,42 +1567,33 @@ table.quota-info td.root {
}
/********** TinyMCE styles **********/
.mce-btn-small button
{
height: 22px;
}
.mce-btn-small i
{
line-height: 16px !important;
vertical-align: text-top !important;
div.tox .tox-toolbar,
div.tox .tox-toolbar__overflow,
div.tox .tox-toolbar__primary {
background-color: #f0f0f0;
}
.mce-combobox button
{
padding: 6px 8px !important;
div.tox .tox-toolbar__primary {
border: 0;
}
.mce-tinymce
{
border-radius: 0 !important;
{
div.tox div.tox-dialog-wrap__backdrop {
background: #aaa;
opacity: .3;
}
.mce-panel.mce-toolbar-grp
{
border: 0 !important;
div.tox div.tox-dialog {
box-shadow: 1px 1px 18px #666;
border-width: 0;
}
#image-selector-form.droptarget {
#image-selector.droptarget {
background: url(images/filedrop.png) center bottom no-repeat;
}
#image-selector-form.droptarget.hover
{
#image-selector.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;
}
/** PGP key import dialog **/

@ -577,7 +577,7 @@ resize_compose_body: function()
h = div.height() - 2,
x = bw.ie || bw.opera ? 4 : 0;
$('#compose-body_ifr').width(w + 6).height(h - 1 - $('div.mce-toolbar').height());
$('#compose-body_ifr').width(w + 6).height(h - 1 - $('div.tox-toolbar').height());
$('#compose-body').width(w-x).height(h);
$('#googie_edit_layer').width(w).height(h);
},

@ -1414,15 +1414,11 @@ div.hide-headers
border: 1px solid #999;
}
#compose-body-div .mce-tinymce {
#compose-body-div .tox-tinymce {
border: 0 !important;
width: 100% !important;
}
.mce-top-part::before {
box-shadow: none !important;
}
#compose-div .boxlistcontent
{
bottom: 23px;
@ -1764,10 +1760,6 @@ input.from_address
position: absolute;
}
#image-selector {
padding-bottom: 0 !important;
}
/**** Styles for widescreen (3-column) view ****/
.widescreen #mailview-top {

@ -202,8 +202,11 @@
@color-datepicker-active-background: @color-main;
// HTML editor
@color-editor-disabled-mask: fadeout(lighten(@color-black, 85), 10);
// Image tools
@color-image-tools: #fff;
@color-image-tools-background: fadeout(@color-main, 60%);
@color-image-tools-hover: fadeout(@color-main, 50%);

@ -148,6 +148,7 @@ html.touch .popover {
max-width: initial;
margin: 0;
height: auto;
z-index: 1300; // above TinyMCE dialogs
.popover-header {
border-radius: .25rem .25rem 0 0 !important;

File diff suppressed because it is too large Load Diff

@ -482,20 +482,6 @@ function rcube_elastic_ui()
.addEventListener('clonerow', pretty_checkbox_fix)
.addEventListener('init', init);
// Add styling for TinyMCE editor popups
// We need to use MutationObserver, as TinyMCE does not provide any events for this
if (window.MutationObserver && window.tinymce) {
var callback = function(list) {
$.each(list, function() {
$.each(this.addedNodes, function() {
tinymce_style(this);
});
});
};
(new MutationObserver(callback)).observe(document.body, {childList: true});
}
// Create floating action button(s)
if ((layout.list.length || layout.content.length) && is_mobile()) {
var fabuttons = [];
@ -1086,86 +1072,6 @@ function rcube_elastic_ui()
$('select:not([multiple])', context).each(function() { pretty_select(this); });
};
/**
* Detects if the element is TinyMCE dialog/menu
* and adds Elastic styling to it
*/
function tinymce_style(elem)
{
// TinyMCE dialog widnows
if ($(elem).is('.mce-window')) {
var body = $(elem).find('.mce-window-body'),
foot = $(elem).find('.mce-foot > .mce-container-body');
// Apply basic forms style
if (body.length) {
bootstrap_style(body[0]);
}
body.find('button').filter(function() { return $(this).parent('.mce-btn').length > 0; }).removeClass('btn btn-secondary');
// Fix icons in Find and Replace dialog footer
if (foot.children('.mce-widget').length === 5) {
foot.addClass('mce-search-foot');
}
// Apply some form structure fixes and helper classes
$(elem).find('.mce-charmap').parent().parent().addClass('mce-charmap-dialog');
$(elem).find('.mce-combobox').each(function() {
if (!$(this).children('.mce-btn').length) {
$(this).addClass('mce-combobox-fake');
}
});
$(elem).find('.mce-form > .mce-container-body').each(function() {
if ($(this).children('.mce-formitem').length > 4) {
$(this).addClass('mce-form-split');
}
});
$(elem).find('.mce-form').next(':not(.mce-formitem)').addClass('mce-form');
// Fix dialog height (e.g. Table properties dialog)
if (!is_mobile()) {
var offset, max_height = 0, height = body.height();
$(elem).find('.mce-form').each(function() {
max_height = Math.max(max_height, $(this).height());
});
if (height < max_height) {
max_height += (body.find('.mce-tabs').height() || 0) + 25;
body.height(max_height);
$(elem).height($(elem).height() + (max_height - height));
$(elem).css('top', ($(window).height() - $(elem).height())/2 + 'px');
}
}
}
// TinyMCE menus on mobile
else if ($(elem).is('.mce-menu')) {
$(elem).prepend(
$('<h3 class="popover-header">').append(
$('<a class="button icon "' + 'cancel' + '">')
.text(rcmail.gettext('close'))
.on('click', function() { $(document.body).click(); })));
if (window.MutationObserver) {
var callback = function() {
if (mode != 'phone') {
return;
}
if (!$('.mce-menu:visible').length) {
$('div.mce-overlay').click();
}
else if (!$('div.mce-overlay').length) {
$('<div>').attr('class', 'popover-overlay mce-overlay')
.appendTo('body')
.click(function() { $(this).remove(); });
}
};
(new MutationObserver(callback)).observe(elem, {attributes: true});
}
}
};
/**
* Initializes popup menus
*/
@ -1368,15 +1274,8 @@ function rcube_elastic_ui()
o.config.plugins += ' autoresize';
if (is_touch()) {
// Make the toolbar icons bigger
o.config.toolbar_items_size = null;
// Use minimalistic toolbar
o.config.toolbar = 'undo redo | insert | styleselect';
if (o.config.plugins.match(/emoticons/)) {
o.config.toolbar += ' emoticons';
}
o.config.toolbar = 'undo redo | link image styleselect';
}
if (rcmail.task == 'mail' && rcmail.env.action == 'compose') {
@ -1396,8 +1295,8 @@ function rcube_elastic_ui()
// Keep the editor toolbar on top of the screen on scroll
form.on('scroll', function() {
var container = $('.mce-container-body', form),
toolbar = $('.mce-top-part', container),
var container = $('.tox-editor-container', form),
toolbar = container.find('.tox-toolbar-overlord'),
editor_offset = container.offset(),
header_top = form.offset().top;
@ -1410,16 +1309,17 @@ function rcube_elastic_ui()
});
$(window).resize(function() { form.trigger('scroll'); });
}
if (is_editor) {
o.config.toolbar = 'plaintext | ' + o.config.toolbar;
// Use setup_callback, we can't use editor-load event
o.config.setup_callback = function(ed) {
ed.addButton('plaintext', {
ed.ui.registry.addButton('plaintext', {
tooltip: rcmail.gettext('plaintoggle'),
icon: 'plaintext',
onclick: function(e) {
icon: 'close',
onAction: function(e) {
if (rcmail.command('toggle-editor', {id: ed.id, html: false}, '', e.originalEvent)) {
$('#' + ed.id).parent().removeClass('ishtml');
}
@ -1428,6 +1328,43 @@ function rcube_elastic_ui()
};
}
// Add styling for TinyMCE dialogs
onload.push(function(ed) {
ed.on('OpenWindow', function(e) {
var dialog = $('.tox-dialog:last')[0],
callback = function(e) {
var body = $(dialog).find('.tox-dialog__body'),
foot = $(dialog).find('.tox-dialog__footer'),
buttons = foot.find('button');
if (!e) {
// Fix icons in Find and Replace dialog footer
if (buttons.length === 4) {
body.closest('.tox-dialog').addClass('tox-search-dialog');
}
// Switch Save and Cancel buttons order
else if (buttons.length == 2) {
buttons.first().insertAfter(buttons[1]);
}
// TODO: Styling form elements does not work well because of
// https://github.com/tinymce/tinymce/issues/4867
// also https://github.com/tinymce/tinymce/issues/4869
}
body.find('select').each(function() { pretty_select(this); });
body.find('.tox-checkbox > input').each(function() { pretty_checkbox(this); });
};
// TODO: Maybe some day we'll not have to use MutationObserver
// https://github.com/tinymce/tinymce/issues/4869
if (window.MutationObserver) {
(new MutationObserver(callback)).observe($('.tox-dialog__body-content', dialog)[0], {childList: true});
}
callback();
});
});
rcmail.addEventListener('editor-load', function(e) {
$.each(onload, function() { this(e.ref.editor); });
});

@ -1305,7 +1305,6 @@ body.status-flagged .flag-icon {
overflow: auto;
}
#image-selector.droptarget,
#compose-attachments.droptarget {
background-image: url(images/filedrop.png);
background-position: center bottom;
@ -1340,19 +1339,11 @@ body.status-flagged .flag-icon {
padding-bottom: 8px;
}
#composebodycontainer .mce-tinymce {
#composebodycontainer .tox-tinymce {
border: 0 !important;
margin-top: 1px;
}
#composebodycontainer .mce-panel {
border-color: #ddd !important;
}
#composebody_toolbargroup {
border-bottom: 1px solid #ddd;
}
#uploadform a.iconlink {
margin-left: 1em;
text-indent: -5000px;

@ -1681,11 +1681,6 @@ table.propform td.title {
white-space: nowrap;
}
table.propform .mceLayout td {
padding: 0;
border-bottom: 0;
}
ul.proplist {
list-style: none;
margin: 0;
@ -3057,47 +3052,32 @@ ul.toolbarmenu li span.copy {
padding: 0.5em 1em;
}
#image-selector-form.droptarget {
background: url(images/filedrop.png) center bottom no-repeat;
}
/** Common TinyMCE fixes **/
.mce-btn-small:not(.mce-active) {
background: transparent !important;
}
.mce-btn-small:not(.mce-active):hover {
background: white !important;
}
.mce-btn-small .mce-ico {
display: inline; /* for old Firefox */
#image-selector.droptarget {
background: url(images/filedrop.png) center bottom no-repeat;
}
.mce-btn-small i {
line-height: 16px !important;
vertical-align: text-top !important;
div.tox .tox-toolbar,
div.tox .tox-toolbar__overflow,
div.tox .tox-toolbar__primary {
background-color: #f0f0f0;
}
_:not(), _:-moz-handler-blocked, .mozilla .mce-btn-small i {
line-height: 20px !important;
div.tox .tox-toolbar__primary {
border: 0;
}
.mce-top-part::before {
box-shadow: none !important;
div.tox div.tox-dialog-wrap__backdrop {
background: #aaa;
opacity: .3;
}
.mce-textbox {
border-radius: 0;
box-shadow: none;
div.tox div.tox-dialog {
box-shadow: 1px 1px 18px #666;
border-width: 0;
}
button.mce-close,
.mce-btn button,
.mce-textbox:focus {
box-shadow: none;
outline: none;
}
.mce-menu {
z-index: 65537 !important;

@ -600,8 +600,8 @@ function rcube_mail_ui()
h = body.parent().height() - 8;
body.width(w).height(h);
$('#composebodycontainer > div').width(w+8);
$('#composebody_ifr').height(h + 4 - $('div.mce-toolbar').height());
$('#composebodycontainer > div').width(w+7);
$('#composebody_ifr').height(h + 4 - $('div.tox-toolbar').height());
$('#googie_edit_layer').width(w).height(h);
// $('#composebodycontainer')[(btns ? 'addClass' : 'removeClass')]('buttons');
// $('#composeformbuttons')[(btns ? 'show' : 'hide')]();

@ -53,8 +53,8 @@ class HtmlEditor extends Component
return [
'@plain-toolbar' => '.editor-toolbar',
'@plain-body' => 'textarea',
'@html-editor' => '.mce-tinymce',
'@html-toolbar' => '.mce-tinymce .mce-toolbar',
'@html-editor' => '.tox-tinymce',
'@html-toolbar' => '.tox-tinymce .tox-editor-header',
'@html-body' => 'iframe',
];
}
@ -84,10 +84,10 @@ class HtmlEditor extends Component
if ($accept_warning) {
$browser->waitForDialog()->acceptDialog();
}
$browser->waitFor('@html-body');
$browser->waitFor('@html-body')->waitFor('@html-toolbar');
}
else {
$browser->click('@html-toolbar .mce-i-plaintext');
$browser->click('.tox-toolbar__primary .tox-toolbar__group:first-child button');
if ($accept_warning) {
$browser->waitForDialog()->acceptDialog();
}

@ -64,7 +64,6 @@
<file>./../plugins/database_attachments/tests/DatabaseAttachments.php</file>
<file>./../plugins/debug_logger/tests/DebugLogger.php</file>
<file>./../plugins/emoticons/tests/Emoticons.php</file>
<file>./../plugins/emoticons/tests/EmoticonsEngine.php</file>
<file>./../plugins/enigma/tests/Enigma.php</file>
<file>./../plugins/example_addressbook/tests/ExampleAddressbook.php</file>
<file>./../plugins/filesystem_attachments/tests/FilesystemAttachments.php</file>

Loading…
Cancel
Save