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/js/editor.js | 51 +++++++++++++++++++++++++-------------------------- 1 file changed, 25 insertions(+), 26 deletions(-) (limited to 'program/js/editor.js') diff --git a/program/js/editor.js b/program/js/editor.js index df3d41240..632ff9918 100644 --- a/program/js/editor.js +++ b/program/js/editor.js @@ -18,51 +18,50 @@ function rcmail_editor_init(config) { var ret, conf = { - mode: 'textareas', - editor_selector: 'mce_editor', + selector: '.mce_editor', apply_source_formatting: true, - theme: 'advanced', + theme: 'modern', language: config.lang, content_css: config.skin_path + '/editor_content.css', - theme_advanced_toolbar_location: 'top', - theme_advanced_toolbar_align: 'left', - theme_advanced_buttons3: '', - theme_advanced_statusbar_location: 'none', + menubar: false, + statusbar: false, extended_valid_elements: 'font[face|size|color|style],span[id|class|align|style]', relative_urls: false, remove_script_host: false, gecko_spellcheck: true, convert_urls: false, // #1486944 - external_image_list: window.rcmail_editor_images, + external_image_list: window.rcmail_editor_images, //TODO rc_client: rcmail }; if (config.mode == 'identity') $.extend(conf, { - plugins: 'paste,tabfocus', - theme_advanced_buttons1: 'bold,italic,underline,strikethrough,justifyleft,justifycenter,justifyright,justifyfull,separator,outdent,indent,charmap,hr,link,unlink,code,forecolor', - theme_advanced_buttons2: 'fontselect,fontsizeselect' + plugins: ['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 $.extend(conf, { - plugins: 'paste,emotions,media,nonbreaking,table,searchreplace,visualchars,directionality,inlinepopups,tabfocus' + (config.spellcheck ? ',spellchecker' : ''), - theme_advanced_buttons1: 'bold,italic,underline,|,justifyleft,justifycenter,justifyright,justifyfull,|,bullist,numlist,outdent,indent,ltr,rtl,blockquote,|,forecolor,backcolor,fontselect,fontsizeselect', - theme_advanced_buttons2: 'link,unlink,table,|,emotions,charmap,image,media,|,code,search,undo,redo', - spellchecker_languages: (rcmail.env.spellcheck_langs ? rcmail.env.spellcheck_langs : 'Dansk=da,Deutsch=de,+English=en,Espanol=es,Francais=fr,Italiano=it,Nederlands=nl,Polski=pl,Portugues=pt,Suomi=fi,Svenska=sv'), + plugins: ['charmap code directionality emoticons link image media nonbreaking paste table tabfocus textcolor searchreplace' + (config.spellcheck ? ' spellchecker' : '')], + toolbar: 'bold italic underline | alignleft aligncenter alignright alignjustify' + + ' | bullist numlist outdent indent ltr rtl blockquote | forecolor backcolor | fontselect fontsizeselect' + + ' | link unlink table | emoticons charmap image media | code searchreplace undo redo', +// spellchecker_languages: (rcmail.env.spellcheck_langs ? rcmail.env.spellcheck_langs : 'Dansk=da,Deutsch=de,+English=en,Espanol=es,Francais=fr,Italiano=it,Nederlands=nl,Polski=pl,Portugues=pt,Suomi=fi,Svenska=sv'), spellchecker_rpc_url: '?_task=utils&_action=spell_html&_remote=1', spellchecker_enable_learn_rpc: config.spelldict, - accessibility_focus: false, - oninit: 'rcmail_editor_callback' + accessibility_focus: false }); - // add handler for spellcheck button state update conf.setup = function(ed) { - ed.onSetProgressState.add(function(ed, active) { - if (!active) + ed.on('init', rcmail_editor_callback); + // add handler for spellcheck button state update + ed.on('SetProgressState', function(args) { + if (!args.active) rcmail.spellcheck_state(); }); - ed.onKeyPress.add(function(ed, e) { - rcmail.compose_type_activity++; + ed.on('keypress', function() { + rcmail.compose_type_activity++; }); } } @@ -71,7 +70,7 @@ function rcmail_editor_init(config) if (window.rcmail_editor_settings) $.extend(conf, window.rcmail_editor_settings); - tinyMCE.init(conf); + tinymce.init(conf); } // react to real individual tinyMCE editor init @@ -88,7 +87,7 @@ function rcmail_editor_callback() css['font-size'] = rcmail.env.default_font_size; if (css['font-family'] || css['font-size']) - $(tinyMCE.get(rcmail.env.composebody).getBody()).css(css); + $(tinymce.get(rcmail.env.composebody).getBody()).css(css); if (elem && elem.type == 'select-one') { rcmail.change_identity(elem); @@ -108,11 +107,11 @@ function rcmail_editor_callback() $(window).resize(); } -// set tabIndex on tinyMCE editor +// set tabIndex on tinymce editor function rcmail_editor_tabindex(focus) { if (rcmail.env.task == 'mail') { - var editor = tinyMCE.get(rcmail.env.composebody); + var editor = tinymce.get(rcmail.env.composebody); if (editor) { var textarea = editor.getElement(); var node = editor.getContentAreaContainer().childNodes[0]; -- cgit v1.2.3 From 89d6ce68463b92641946551b07ec49b023d56d90 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Thu, 1 May 2014 11:48:29 +0200 Subject: Update to TinyMCE 4.x --- program/js/app.js | 5 ++ program/js/editor.js | 94 +++++++++++++--------------- program/lib/Roundcube/rcube_spellchecker.php | 13 +++- program/steps/mail/compose.inc | 2 +- program/steps/mail/sendmail.inc | 7 ++- program/steps/utils/spell_html.inc | 30 ++++----- skins/classic/common.css | 4 ++ skins/classic/functions.js | 7 +-- skins/larry/ui.js | 2 +- 9 files changed, 84 insertions(+), 80 deletions(-) (limited to 'program/js/editor.js') diff --git a/program/js/app.js b/program/js/app.js index 1baedac33..0588eb45b 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -3358,8 +3358,11 @@ function rcube_webmail() { this.stop_spellchecking(); + var flag = $('[name="_is_html"]'); + if (props.mode == 'html') { this.plain2html($('#'+props.id).val(), props.id); + flag.val(1); tinymce.execCommand('mceAddEditor', false, props.id); if (this.env.default_font) @@ -3376,6 +3379,8 @@ function rcube_webmail() } this.html2plain(existingHtml, props.id); } + + flag.val(0); tinymce.execCommand('mceRemoveEditor', false, props.id); } diff --git a/program/js/editor.js b/program/js/editor.js index 632ff9918..8a6e88433 100644 --- a/program/js/editor.js +++ b/program/js/editor.js @@ -19,7 +19,6 @@ function rcmail_editor_init(config) { var ret, conf = { selector: '.mce_editor', - apply_source_formatting: true, theme: 'modern', language: config.lang, content_css: config.skin_path + '/editor_content.css', @@ -28,36 +27,37 @@ function rcmail_editor_init(config) extended_valid_elements: 'font[face|size|color|style],span[id|class|align|style]', relative_urls: false, remove_script_host: false, - gecko_spellcheck: true, convert_urls: false, // #1486944 - external_image_list: window.rcmail_editor_images, //TODO - rc_client: rcmail + image_list: window.rcmail_editor_images, + image_description: false, + paste_webkit_style: "color font-size font-family", + paste_data_images: true }; if (config.mode == 'identity') $.extend(conf, { plugins: ['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' + + ' | outdent indent charmap hr link unlink code forecolor' + + ' | fontselect fontsizeselect' }); else { // mail compose $.extend(conf, { - plugins: ['charmap code directionality emoticons link image media nonbreaking paste table tabfocus textcolor searchreplace' + (config.spellcheck ? ' spellchecker' : '')], + plugins: ['charmap code directionality emoticons link image media nonbreaking' + + ' paste table tabfocus textcolor searchreplace' + (config.spellcheck ? ' spellchecker' : '')], toolbar: 'bold italic underline | alignleft aligncenter alignright alignjustify' - + ' | bullist numlist outdent indent ltr rtl blockquote | forecolor backcolor | fontselect fontsizeselect' - + ' | link unlink table | emoticons charmap image media | code searchreplace undo redo', -// spellchecker_languages: (rcmail.env.spellcheck_langs ? rcmail.env.spellcheck_langs : 'Dansk=da,Deutsch=de,+English=en,Espanol=es,Francais=fr,Italiano=it,Nederlands=nl,Polski=pl,Portugues=pt,Suomi=fi,Svenska=sv'), - spellchecker_rpc_url: '?_task=utils&_action=spell_html&_remote=1', - spellchecker_enable_learn_rpc: config.spelldict, + + ' | bullist numlist outdent indent ltr rtl blockquote | forecolor backcolor | fontselect fontsizeselect' + + ' | link unlink table | emoticons charmap image media | code searchreplace undo redo', + spellchecker_rpc_url: '../../../../../?_task=utils&_action=spell_html&_remote=1', + spellchecker_enable_learn_rpc: config.spelldict, //TODO accessibility_focus: false }); conf.setup = function(ed) { ed.on('init', rcmail_editor_callback); // add handler for spellcheck button state update - ed.on('SetProgressState', function(args) { - if (!args.active) + ed.on('ProgressState', function(args) { + if (!args.state) rcmail.spellcheck_state(); }); ed.on('keypress', function() { @@ -90,7 +90,10 @@ function rcmail_editor_callback() $(tinymce.get(rcmail.env.composebody).getBody()).css(css); if (elem && elem.type == 'select-one') { - rcmail.change_identity(elem); + // insert signature (only for the first time) + if (!rcmail.env.identities_initialized) + rcmail.change_identity(elem); + // Focus previously focused element if (fe && fe.id != rcmail.env.composebody) { // use setTimeout() for IE9 (#1488541) @@ -103,8 +106,8 @@ function rcmail_editor_callback() // 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 using default skin) - $(window).resize(); + // Trigger resize (needed for proper editor resizing in some browsers) + window.setTimeout(function() { $(window).resize(); }, 100); } // set tabIndex on tinymce editor @@ -113,8 +116,9 @@ function rcmail_editor_tabindex(focus) if (rcmail.env.task == 'mail') { var editor = tinymce.get(rcmail.env.composebody); if (editor) { - var textarea = editor.getElement(); - var node = editor.getContentAreaContainer().childNodes[0]; + var textarea = editor.getElement(), + node = editor.getContentAreaContainer().childNodes[0]; + if (textarea && node) node.tabIndex = textarea.tabIndex; if (focus) @@ -124,49 +128,37 @@ function rcmail_editor_tabindex(focus) } // switch html/plain mode -function rcmail_toggle_editor(select, textAreaId, flagElement) +function rcmail_toggle_editor(select, textAreaId) { - var flag, ishtml; - - if (select.tagName != 'SELECT') - ishtml = select.checked; - else - ishtml = select.value == 'html'; - - var res = rcmail.command('toggle-editor', {id:textAreaId, mode:ishtml?'html':'plain'}); - - if (ishtml) { - // #1486593 - setTimeout("rcmail_editor_tabindex(true);", 500); - if (flagElement && (flag = rcube_find_object(flagElement))) - flag.value = '1'; - } - else if (res) { - if (flagElement && (flag = rcube_find_object(flagElement))) - flag.value = '0'; + var ishtml = select.tagName != 'SELECT' ? select.checked : select.value == 'html', + res = rcmail.command('toggle-editor', {id: textAreaId, mode: ishtml ? 'html' : 'plain'}); - if (rcmail.env.composebody) - rcube_find_object(rcmail.env.composebody).focus(); - } - else { // !res + 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(); + } } -// editor callbeck for images listing -function rcmail_editor_images() +// editor callback for images listing +function rcmail_editor_images(callback) { - var i, files = rcmail.env.attachments, list = []; + var i, file, list = []; - for (i in files) { - att = files[i]; - if (att.complete && att.mimetype.startsWith('image/')) { - list.push([att.name, rcmail.env.comm_path+'&_id='+rcmail.env.compose_id+'&_action=display-attachment&_file='+i]); + 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}); } } - return list; -}; + callback(list); +} diff --git a/program/lib/Roundcube/rcube_spellchecker.php b/program/lib/Roundcube/rcube_spellchecker.php index 5b77bda02..3d15eb660 100644 --- a/program/lib/Roundcube/rcube_spellchecker.php +++ b/program/lib/Roundcube/rcube_spellchecker.php @@ -226,7 +226,18 @@ class rcube_spellchecker else { $word = mb_substr($this->content, $item[1], $item[2], RCUBE_CHARSET); } - $result[$word] = is_array($item[4]) ? implode("\t", $item[4]) : $item[4]; + + if (is_array($item[4])) { + $suggestions = $item[4]; + } + else if (empty($item[4])) { + $suggestions = array(); + } + else { + $suggestions = explode("\t", $item[4]); + } + + $result[$word] = $suggestions; } return $result; diff --git a/program/steps/mail/compose.inc b/program/steps/mail/compose.inc index db001d54e..db485fda8 100644 --- a/program/steps/mail/compose.inc +++ b/program/steps/mail/compose.inc @@ -1675,7 +1675,7 @@ function rcmail_editor_selector($attrib) if (empty($attrib['name'])) $attrib['name'] = 'editorSelect'; - $attrib['onchange'] = "return rcmail_toggle_editor(this, '".$attrib['editorid']."', '_is_html')"; + $attrib['onchange'] = "return rcmail_toggle_editor(this, '".$attrib['editorid']."')"; $select = new html_select($attrib); diff --git a/program/steps/mail/sendmail.inc b/program/steps/mail/sendmail.inc index 005c25427..5bddc249a 100644 --- a/program/steps/mail/sendmail.inc +++ b/program/steps/mail/sendmail.inc @@ -273,9 +273,10 @@ if ($isHtml) { } // append doctype and html/body wrappers - $message_body = '' - . "\r\n\r\n" - . $message_body; + $bstyle = !empty($bstyle) ? (" style='" . implode($bstyle, '; ') . "'") : ''; + $message_body = '' + . '' + . "\r\n" . $message_body; } if (!$savedraft) { diff --git a/program/steps/utils/spell_html.inc b/program/steps/utils/spell_html.inc index 27b14acef..b31d6ed86 100644 --- a/program/steps/utils/spell_html.inc +++ b/program/steps/utils/spell_html.inc @@ -19,31 +19,23 @@ +-----------------------------------------------------------------------+ */ -// read input data -$data = file_get_contents('php://input'); -// Decode JSON input -$request = json_decode($data, true); +$lang = rcube_utils::get_input_value('lang', rcube_utils::INPUT_POST); +$data = rcube_utils::get_input_value('text', rcube_utils::INPUT_POST); +$data = html_entity_decode($data, ENT_QUOTES, RCUBE_CHARSET); $result = array(); -$lang = $request['params'][0]; -$data = $request['params'][1]; -$data = implode("\n", (array) $data); - -$result['id'] = $request['id']; - $spellchecker = new rcube_spellchecker($lang); -if ($request['method'] == 'checkWords') { - $result['result'] = $spellchecker->get_words($data); -} -else if ($request['method'] == 'getSuggestions') { - $result['result'] = $spellchecker->get_suggestions($data); -} -else if ($request['method'] == 'learnWord') { +if ($method == 'learnWord') { $spellchecker->add_word($data); $result['result'] = true; } +else if ($data) { + if (!$spellchecker->check($data)) { + $result['words'] = $spellchecker->get(); + } +} if ($error = $spellchecker->error()) { rcube::raise_error(array('code' => 500, 'type' => 'php', @@ -51,12 +43,12 @@ if ($error = $spellchecker->error()) { 'message' => sprintf("Spell check engine error: " . $error)), true, false); - echo '{"error":{"errstr":"' . addslashes($error) . '","errfile":"","errline":null,"errcontext":"","level":"FATAL"}}'; + echo json_encode(array('error' => $error)); exit; } // send output -header("Content-Type: text/xml; charset=".RCUBE_CHARSET); +header("Content-Type: application/json; charset=".RCUBE_CHARSET); echo json_encode($result); exit; diff --git a/skins/classic/common.css b/skins/classic/common.css index 10bc91c09..390522fe8 100644 --- a/skins/classic/common.css +++ b/skins/classic/common.css @@ -597,6 +597,10 @@ img.uploading height: 16px; } +.mce-btn button { + height: 28px; +} + /***** common table settings ******/ table.records-table thead tr td diff --git a/skins/classic/functions.js b/skins/classic/functions.js index 07df6391c..aaaf393e7 100644 --- a/skins/classic/functions.js +++ b/skins/classic/functions.js @@ -543,10 +543,9 @@ resize_compose_body: function() w = div.width() - 2, h = div.height(), x = bw.ie || bw.opera ? 4 : 0; - $('#compose-body_tbl').width((w+3)+'px').height(''); - $('#compose-body_ifr').width((w+3)+'px').height((h-54)+'px'); - $('#compose-body').width((w-x)+'px').height(h+'px'); - $('#googie_edit_layer').height(h+'px'); + $('#compose-body_ifr').width(w+3).height(h-2 - $('div.mce-toolbar').height()); + $('#compose-body').width(w-x).height(h); + $('#googie_edit_layer').height(h); }, resize_compose_body_ev: function() diff --git a/skins/larry/ui.js b/skins/larry/ui.js index a173ffd1d..c85c37257 100644 --- a/skins/larry/ui.js +++ b/skins/larry/ui.js @@ -528,7 +528,7 @@ function rcube_mail_ui() body.width(w).height(h); $('#composebodycontainer > div').width(w+8).css('margin-top', '1px'); - $('#composebody_ifr').height(h + 4 - $('.mce-toolbar-grp').height()); + $('#composebody_ifr').height(h + 4 - $('div.mce-toolbar').height()); $('#googie_edit_layer').height(h - 8); // $('#composebodycontainer')[(btns ? 'addClass' : 'removeClass')]('buttons'); // $('#composeformbuttons')[(btns ? 'show' : 'hide')](); -- cgit v1.2.3 From ccb41747d994b0ba1ce0a2b9dd6456fccff247c3 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Mon, 5 May 2014 15:27:08 +0200 Subject: Make TinyMCE4 buttons smaller --- program/js/editor.js | 1 + skins/classic/common.css | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) (limited to 'program/js/editor.js') diff --git a/program/js/editor.js b/program/js/editor.js index 8a6e88433..b2d13f965 100644 --- a/program/js/editor.js +++ b/program/js/editor.js @@ -24,6 +24,7 @@ function rcmail_editor_init(config) content_css: config.skin_path + '/editor_content.css', menubar: false, statusbar: false, + toolbar_items_size: 'small', extended_valid_elements: 'font[face|size|color|style],span[id|class|align|style]', relative_urls: false, remove_script_host: false, diff --git a/skins/classic/common.css b/skins/classic/common.css index 390522fe8..bad2114c8 100644 --- a/skins/classic/common.css +++ b/skins/classic/common.css @@ -597,8 +597,13 @@ img.uploading height: 16px; } -.mce-btn button { - height: 28px; +.mce-btn-small button { + height: 22px; +} + +.mce-btn-small i { + line-height: 16px !important; + vertical-align: text-top !important; } /***** common table settings ******/ -- cgit v1.2.3 From 4d02384dcaba8806113a5554ea2faf2c011d2f4e Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Mon, 5 May 2014 15:46:29 +0200 Subject: Fix Spell button with TinyMCE4 --- program/js/app.js | 14 +++++++------- program/js/editor.js | 7 ++++--- 2 files changed, 11 insertions(+), 10 deletions(-) (limited to 'program/js/editor.js') diff --git a/program/js/app.js b/program/js/app.js index 0588eb45b..43dba1402 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -3540,7 +3540,7 @@ function rcube_webmail() var ed; if (window.tinymce && (ed = tinymce.get(this.env.composebody))) { - if (ed.plugins && ed.plugins.spellchecker && ed.plugins.spellchecker.active) + if (ed.plugins && ed.plugins.spellchecker && this.env.spellcheck_active) ed.execCommand('mceSpellCheck'); } else if (ed = this.env.spellcheck) { @@ -3555,8 +3555,8 @@ function rcube_webmail() { var ed, active; - if (window.tinymce && (ed = tinymce.get(this.env.composebody)) && ed.plugins && ed.plugins.spellchecker) - active = ed.plugins.spellchecker.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'; @@ -3571,8 +3571,8 @@ function rcube_webmail() { var ed; - if (window.tinymce && (ed = tinymce.get(this.env.composebody)) && ed.plugins && ed.plugins.spellchecker) - return ed.plugins.spellchecker.selectedLang; + 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; }; @@ -3581,8 +3581,8 @@ function rcube_webmail() { var ed; - if (window.tinymce && (ed = tinymce.get(this.env.composebody)) && ed.plugins) - ed.plugins.spellchecker.selectedLang = lang; + if (window.tinymce && (ed = tinymce.get(this.env.composebody))) + ed.settings.spellchecker_language = lang; else if (this.env.spellcheck) this.env.spellcheck.setCurrentLanguage(lang); }; diff --git a/program/js/editor.js b/program/js/editor.js index b2d13f965..014f61e1c 100644 --- a/program/js/editor.js +++ b/program/js/editor.js @@ -51,15 +51,16 @@ function rcmail_editor_init(config) + ' | link unlink table | emoticons charmap image media | code searchreplace undo redo', 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 }); conf.setup = function(ed) { ed.on('init', rcmail_editor_callback); // add handler for spellcheck button state update - ed.on('ProgressState', function(args) { - if (!args.state) - rcmail.spellcheck_state(); + ed.on('SpellcheckStart SpellcheckEnd', function(args) { + rcmail.env.spellcheck_active = args.type == 'spellcheckstart'; + rcmail.spellcheck_state(); }); ed.on('keypress', function() { rcmail.compose_type_activity++; -- 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/js/editor.js') 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 8763170d224408bb01998d0a3393d2eacfc781ad Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Wed, 28 May 2014 15:35:07 +0200 Subject: TinyMCE4: small improvements --- program/js/editor.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'program/js/editor.js') diff --git a/program/js/editor.js b/program/js/editor.js index 91e8c6d75..6cd68ccaf 100644 --- a/program/js/editor.js +++ b/program/js/editor.js @@ -49,7 +49,6 @@ function rcmail_editor_init(config) + ' | bullist numlist outdent indent ltr rtl blockquote | forecolor backcolor | fontselect fontsizeselect' + ' | link unlink table | emoticons charmap image media | code searchreplace undo redo', 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, file_browser_callback: rcmail_file_browser_callback, @@ -182,7 +181,7 @@ function rcmail_file_browser_callback(field_name, url, type, win) } // add hint about max file size (in dialog footer) - $('div.mce-abs-end', dialog.getEl()).append($('
    ').text($('#uploadform div.hint').text())); + $('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))); -- cgit v1.2.3