Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Attempt to support newer Tinymce releases #644

Merged
merged 1 commit into from
May 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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