Skip to content

Commit

Permalink
Merge pull request #644 from voidlabs/updated-tinymce-compatibility
Browse files Browse the repository at this point in the history
Attempt to support newer Tinymce releases
  • Loading branch information
bago authored May 12, 2022
2 parents 45ebf25 + 65fd8b4 commit 4190d1b
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 17 deletions.
2 changes: 1 addition & 1 deletion spec/converter-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ describe('Template converter', function() {
}, {
optionalName: 'simpleBlock',
templateMode: 'show',
html: '<div data-bind="attr: { id: id }"><div data-bind="wysiwygId: id()+\'_text\', wysiwygClick: function(obj, evt) { $root.selectItem(text, $data); return false }, clickBubble: false, wysiwygCss: { selecteditem: $root.isSelectedItem(text) }, scrollIntoView: $root.isSelectedItem(text), wysiwygOrHtml: text"></div></div>'
html: '<div data-bind="attr: { id: id }"><div data-bind="wysiwygId: id()+\'_text\', wysiwygClick: function(obj, evt) { $root.selectItem(text, $data); return false }, clickBubble: false, wysiwygCss: { selecteditem: $root.isSelectedItem(text) }, scrollIntoView: $root.isSelectedItem(text), wysiwygOrHtml: text, wysiwygStyle: \'multiline\'"></div></div>'
}];

expect(parseData.templates).toEqual(expectedTemplates);
Expand Down
14 changes: 14 additions & 0 deletions src/js/bindings/scrollfix.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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 = {
Expand Down
61 changes: 47 additions & 14 deletions src/js/bindings/wysiwygs.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 <br> 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 <p> 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: {},
Expand All @@ -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);

Expand Down Expand Up @@ -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;
Expand All @@ -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 <br> 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) {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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.
Expand All @@ -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);
}
Expand Down
25 changes: 23 additions & 2 deletions src/js/converter/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -233,8 +233,29 @@ var processBlock = function(element, defs, themeUpdater, blockPusher, templateUr

newBinding += "wysiwygOrHtml: " + modelBindValue;

if (domutils.getLowerTagName(element) == 'td') {
var wrappingDiv = $('<div data-ko-wrap="false" style="width: 100%; height: 100%"></div>')[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 = $('<div data-ko-wrap="false"'+wrappingDivAttrs+'></div>')[0];
domutils.setAttribute(wrappingDiv, 'data-bind', newBinding);
var newContent = domutils.getInnerHtml($('<div></div>').append(wrappingDiv));
domutils.setContent(element, newContent);
Expand Down

0 comments on commit 4190d1b

Please sign in to comment.