diff --git a/spec/converter-spec.js b/spec/converter-spec.js index 770cb1982..5c8cad47c 100644 --- a/spec/converter-spec.js +++ b/spec/converter-spec.js @@ -61,7 +61,7 @@ describe('Template converter', function() { }, { optionalName: 'simpleBlock', templateMode: 'show', - html: '
' + html: '
' }]; expect(parseData.templates).toEqual(expectedTemplates); diff --git a/src/js/bindings/scrollfix.js b/src/js/bindings/scrollfix.js index 7084e7204..4999cc2a3 100644 --- a/src/js/bindings/scrollfix.js +++ b/src/js/bindings/scrollfix.js @@ -22,6 +22,7 @@ var render = function() { timeout = undefined; + // For Tinymce 4.x if (typeof tinymce.activeEditor !== 'undefined' && tinymce.activeEditor !== null && typeof tinymce.activeEditor.theme !== 'undefined' && tinymce.activeEditor.theme !== null && typeof tinymce.activeEditor.theme.panel !== 'undefined' && tinymce.activeEditor.theme.panel !== null) { @@ -43,6 +44,19 @@ var render = function() { } } + + // For Tinymce 5.x and 6.0.x + if (typeof tinymce.activeEditor !== 'undefined' && tinymce.activeEditor !== null && + typeof tinymce.activeEditor.container !== 'undefined' && tinymce.activeEditor.container !== null && + typeof tinymce.activeEditor.ui !== 'undefined' && tinymce.activeEditor.ui !== null) { + + // this is not null when the toolbar is visible + if (tinymce.activeEditor.container.offsetParent !== null) { + // nodeChanged updates the toolbar position but doesn't move it around the editable (on top or bottom) according to the best placement, while ui.show does. + // tinymce.activeEditor.nodeChanged(); + tinymce.activeEditor.ui.show(); + } + } }; ko.bindingHandlers.wysiwygScrollfix = { diff --git a/src/js/bindings/wysiwygs.js b/src/js/bindings/wysiwygs.js index d3dfd204a..49cda0144 100644 --- a/src/js/bindings/wysiwygs.js +++ b/src/js/bindings/wysiwygs.js @@ -228,9 +228,11 @@ var _catchingFire = function(event, args) { // also, maybe we should use the "raw" only for the "before SetContent" and instead read the "non-raw" content (the raw content sometimes have data- attributes and too many ending
in the code) ko.bindingHandlers.wysiwyg = { debug: false, - // please note that setting getContentOptions to "{}" improves (clean ups) the html output generated by tinymce, but also introduces a bug in Firefox: https://github.com/voidlabs/mosaico/issues/446 - // by keeping raw the output is still broken in Firefox but empty

tags are rendered 0px height. - getContentOptions: { format: 'raw' }, + // We used to have a "getContentOptions" with a default value "{ format: 'raw' }" used to read the content from TinyMCE + // We used it to try to move to a more clean output (passing "{}" instead of raw), but this introduced issues with Firefox + // https://github.com/voidlabs/mosaico/issues/446 + // We now don't use this option anymore: the options are decided internally depending on the mode (inline/single line vs block/multiline). + // getContentOptions: { format: 'raw' }, useTarget: false, currentIndex: 0, standardOptions: {}, @@ -244,14 +246,14 @@ ko.bindingHandlers.wysiwyg = { toolbar1: 'bold italic forecolor backcolor hr styleselect removeformat | link unlink | pastetext code', //toolbar1: "bold italic | forecolor backcolor | link unlink | hr | pastetext code", // | newsletter_profile newsletter_optlink newsletter_unsubscribe newsletter_showlink"; //toolbar2: "formatselect fontselect fontsizeselect | alignleft aligncenter alignright alignjustify | bullist numlist", - plugins: ["link hr paste lists textcolor code"], + plugins: ["link", "hr", "paste", "lists", "textcolor", "code"], // valid_elements: 'strong/b,em/i,*[*]', // extended_valid_elements: 'strong/b,em/i,*[*]', // Removed: image fullscreen contextmenu // download custom: // jquery version con legacyoutput, anchor, code, importcss, link, paste, textcolor, hr, lists }, - init: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) { + init: function(element, valueAccessor, allBindings, viewModel, bindingContext) { // TODO ugly, but works... ko.bindingHandlers.focusable.init(element); @@ -284,7 +286,12 @@ ko.bindingHandlers.wysiwyg = { if (!ko.isObservable(value)) throw "Wysiwyg binding called with non observable"; if (element.nodeType === 8) throw "Wysiwyg binding called on virtual node, ignoring...." + element.innerHTML; - var fullEditor = element.tagName == 'DIV' || element.tagName == 'TD'; + var fullEditor = true; + + var editorStyle = allBindings['has']('wysiwygStyle') ? allBindings.get('wysiwygStyle') : false; + if (editorStyle == 'singleline') fullEditor = false; + else if (editorStyle === false) fullEditor = element.tagName == 'DIV' || element.tagName == 'TD'; + var isSubscriberChange = false; var thisEditor; var isEditorChange = false; @@ -296,18 +303,32 @@ ko.bindingHandlers.wysiwyg = { plugins: ["paste"], toolbar1: "bold italic", toolbar2: "", - // we have to disable preview_styles otherwise tinymce push inline every style he things will be applied and this makes the style menu to inherit color/font-family and more. + // we have to disable preview_styles otherwise tinymce push inline every style he thinks will be applied and + // this makes the style menu to inherit color/font-family and more. preview_styles: false, paste_as_text: true, language: 'en', schema: "html5", - extended_valid_elements: 'strong/b,em/i,*[*]', + + // 2022-05 remove *[*] from the extended_valid_elements to let tinymce do content filtering and, for example, + // protect from XSS. + // extended_valid_elements: 'strong/b,em/i,*[*]', + extended_valid_elements: 'strong/b,em/i', menubar: false, skin: 'gray-flat', + + // 2022-05: we found that 'raw' format is mainly needed for "single line" (inline, not block multiline) editing + // NOTE: this is not a tinymce option! + // set "ko.bindingHandlers.wysiwyg.fullOptions._use_raw_format" to "true" to fallback to mosaico 0.17 behaviour + _use_raw_format: fullEditor ? false : true, + // 2018-03-07: the force_*_newlines are not effective. force_root_block is the property dealing with newlines, now. // force_br_newlines: !fullEditor, // we force BR as newline when NOT in full editor // force_p_newlines: fullEditor, + // 2022-05: tinymce 6 dropped support for forced_root_block false or empty. Using 'x' or another unknown tag is a + // workaround but then further handling is needed if you want the enter to create
newslines (like shift-enter). forced_root_block: fullEditor ? 'p' : '', + init_instance_callback : function(editor) { if (doDebug) console.debug("Editor for selector", selectorId, "is now initialized."); if (ko.bindingHandlers.wysiwyg.initializingClass) { @@ -351,7 +372,7 @@ ko.bindingHandlers.wysiwyg = { // not emptied and full of tags used by tinymce as workaround. // In future we'll probably change the default to "non raw", but at this time we keep this as an option // in order to keep backward compatibility. - value(editor.getContent(ko.bindingHandlers.wysiwyg.getContentOptions)); + value(editor.getContent(editor.getParam('_use_raw_format') ? { format: 'raw' } : {})); } catch (e) { console.warn("Unexpected error setting content value for", selectorId, e); } finally { @@ -383,10 +404,14 @@ ko.bindingHandlers.wysiwyg = { }); } - // NOTE: this fixes issue with "leading spaces" in default content that were lost during initialization. - editor.on('BeforeSetContent', function(args) { - if (args.initial) args.format = 'raw'; - }); + // 2022-05-04: use format raw only for inline contents (the ones with no force_root_block) + // for better compatibility with Tinymce 4.7+,5+ + if (editor.getParam('_use_raw_format')) { + // NOTE: this fixes issue with "leading spaces" in default content that were lost during initialization. + editor.on('BeforeSetContent', function(args) { + if (args.initial) args.format = 'raw'; + }); + } // 20180307: Newer TinyMCE versions (4.7.x for sure, maybe early versions too) stopped accepting ENTER on single paragraph elements // We try to use the "force_br_newlines : true," in non full version (see options) @@ -420,6 +445,13 @@ ko.bindingHandlers.wysiwyg = { ko.utils.extend(options, ko.bindingHandlers.wysiwyg.standardOptions); if (fullEditor) ko.utils.extend(options, ko.bindingHandlers.wysiwyg.fullOptions); + // this way you can have custom editing styles + // default ones are: singleline and multiline + // everyone already inherit "standardOptions" + every non "singleline" style inherit the "fullOptions" + if (ko.bindingHandlers.wysiwyg[editorStyle+'Options']) { + ko.utils.extend(options, ko.bindingHandlers.wysiwyg[editorStyle+'Options']); + } + // we have to put initialization in a settimeout, otherwise switching from "1" to "2" columns blocks // will start the new editors before disposing the old ones and IDs get temporarily duplicated. // using setTimeout the dispose/create order is correct on every browser tested. @@ -442,7 +474,8 @@ ko.bindingHandlers.wysiwyg = { // we failed setting contents in other ways... // $(element).html(content); if (typeof thisEditor !== 'undefined') { - thisEditor.setContent(content, { format: 'raw' }); + // 2022-05-04 changed so to use format raw only for single line editor + thisEditor.setContent(content, options._use_raw_format ? { format: 'raw' } : {}); } else { ko.utils.setHtml(element, content); } diff --git a/src/js/converter/parser.js b/src/js/converter/parser.js index 1fda1b82a..5b8b98a97 100644 --- a/src/js/converter/parser.js +++ b/src/js/converter/parser.js @@ -233,8 +233,29 @@ var processBlock = function(element, defs, themeUpdater, blockPusher, templateUr newBinding += "wysiwygOrHtml: " + modelBindValue; - if (domutils.getLowerTagName(element) == 'td') { - var wrappingDiv = $('

')[0]; + var lowerTagName = domutils.getLowerTagName(element); + + var editorStyle = domutils.getAttribute(element, 'data-ko-editor-style'); + if (editorStyle) { + domutils.removeAttribute(element, 'data-ko-editor-style'); + } else if (lowerTagName == 'div' || lowerTagName == 'td') { + editorStyle = 'multiline'; + } else { + editorStyle = 'singleline'; + } + + newBinding += ", wysiwygStyle: '"+editorStyle+"'"; + + // 2022-05-04: we now always use a wrapping DIV for every element (but a DIV). + // In past we only used the wrapping div for td elements (because it didn't work in IE10-IE11) + // https://github.com/voidlabs/mosaico/issues/11 + // but we found that every element but divs have contenteditable/tinymce issues. + // We stuck to tinymce 4.5.x for long time because of tinymce issue with editing spans. + if (lowerTagName !== 'div') { + var wrappingDivAttrs = editorStyle == 'singleline' ? + ' style="display: inline-block;"' : + ' style="width: 100%; height: 100%"'; + var wrappingDiv = $('
')[0]; domutils.setAttribute(wrappingDiv, 'data-bind', newBinding); var newContent = domutils.getInnerHtml($('
').append(wrappingDiv)); domutils.setContent(element, newContent);