diff --git a/addons/mass_mailing/static/src/js/mass_mailing_widget.js b/addons/mass_mailing/static/src/js/mass_mailing_widget.js
index 812aec0e909..a9705c7f1a1 100644
--- a/addons/mass_mailing/static/src/js/mass_mailing_widget.js
+++ b/addons/mass_mailing/static/src/js/mass_mailing_widget.js
@@ -92,35 +92,6 @@ var MassMailingFieldHtml = FieldHtml.extend({
// Private
//--------------------------------------------------------------------------
- /**
- * Add generateOptions options to change wysiwyg configuration:
- * - Remove table menu in toolbar
- * - Remove table popover
- * - Add rules for isEditableNode, the TD must content a not TABLE node as
- * children to be editable.
- *
- * @override
- */
- _getWysiwygOptions: function () {
- var options = this._super();
- options.generateOptions = function (options) {
- options.toolbar = _.filter(options.toolbar, function (item) {
- return item[0] !== 'table';
- });
- delete options.popover.table;
-
- var isEditableNode = options.isEditableNode;
- options.isEditableNode = function (node) {
- if (node.tagName === "TD" && !$(node).children('*:not(table):first').length) {
- return false;
- }
- return isEditableNode.call(this, node);
- };
-
- return options;
- };
- return options;
- },
/**
* Returns true if must force the user to choose a theme.
*
@@ -293,12 +264,12 @@ var MassMailingFieldHtml = FieldHtml.extend({
} else if ($old_layout.length) {
$contents = ($old_layout.hasClass('oe_structure') ? $old_layout : $old_layout.find('.oe_structure').first()).contents();
} else {
- $contents = this.$content.find('.note-editable').contents();
+ $contents = this.$content.find('.o_editable').contents();
}
$newWrapperContent.append($contents);
this._switchImages(themeParams, $newWrapperContent);
- this.$content.find('.note-editable').empty().append($newLayout);
+ this.$content.find('.o_editable').empty().append($newLayout);
$old_layout.remove();
if (firstChoice) {
@@ -326,6 +297,9 @@ var MassMailingFieldHtml = FieldHtml.extend({
if (this._isFromInline) {
this._fromInline();
}
+ if (this.snippetsLoaded) {
+ this._onSnippetsLoaded(this.snippetsLoaded);
+ }
this._super();
},
/**
@@ -334,6 +308,10 @@ var MassMailingFieldHtml = FieldHtml.extend({
*/
_onSnippetsLoaded: function (ev) {
var self = this;
+ if (!this.$content) {
+ this.snippetsLoaded = ev;
+ return;
+ }
var $snippetsSideBar = ev.data;
var $themes = $snippetsSideBar.find("#email_designer_themes").children();
var $snippets = $snippetsSideBar.find(".oe_snippet");
@@ -341,7 +319,6 @@ var MassMailingFieldHtml = FieldHtml.extend({
if (config.device.isMobile) {
$snippetsSideBar.hide();
- console.log(this.$content[0]);
this.$content.attr('style', 'padding-left: 0px !important');
}
diff --git a/addons/mass_mailing/static/src/scss/mass_mailing.ui.scss b/addons/mass_mailing/static/src/scss/mass_mailing.ui.scss
index 297b241070c..7aec2b165e9 100644
--- a/addons/mass_mailing/static/src/scss/mass_mailing.ui.scss
+++ b/addons/mass_mailing/static/src/scss/mass_mailing.ui.scss
@@ -35,17 +35,23 @@
.dropdown-menu {
position: absolute;
- bottom: -1px;
+ bottom: -2px;
top: -3px !important;
left: -5px !important;
- right: 3px !important;
+ right: -2px !important;
margin: 0;
border-radius: 0;
overflow: auto;
background-color: $o-wysiwyg-bg-color;
z-index: 1050;
- .dropdown-item {
+ &.show {
+ bottom: 31px !important;
+ top: -2px !important;
+ right: 3px !important;
+ }
+
+ .dropdown-item {
padding: 10px 10px;
&:first-child {
display: none;
@@ -182,6 +188,13 @@ body.o_force_mail_theme_choice {
overflow: initial;
}
+#web_editor-toolbars .popover .note-color {
+ .note-back-color-preview .dropdown-toggle,
+ .note-fore-color-preview .dropdown-toggle {
+ height: $o-wysiwyg-toolbar-height - 1;
+ }
+}
+
.oe_structure {
width: 100%;
}
diff --git a/addons/mass_mailing/static/src/scss/themes/theme_default.scss b/addons/mass_mailing/static/src/scss/themes/theme_default.scss
index 4d49a7ef22c..13bc9d56894 100644
--- a/addons/mass_mailing/static/src/scss/themes/theme_default.scss
+++ b/addons/mass_mailing/static/src/scss/themes/theme_default.scss
@@ -56,7 +56,6 @@ td:not([align]) {
> .o_mail_wrapper {
width: 100%;
- margin: 24px 0;
border-collapse: separate; // Allow the first and last td to have the
// same width
diff --git a/addons/mass_mailing/views/assets.xml b/addons/mass_mailing/views/assets.xml
index 46a6cfcb5dc..d2645ef6c14 100644
--- a/addons/mass_mailing/views/assets.xml
+++ b/addons/mass_mailing/views/assets.xml
@@ -32,9 +32,6 @@
-
-
-
diff --git a/addons/web_editor/static/src/js/editor/transcoder.js b/addons/web_editor/static/src/js/backend/convert_inline.js
similarity index 83%
rename from addons/web_editor/static/src/js/editor/transcoder.js
rename to addons/web_editor/static/src/js/backend/convert_inline.js
index 745e8492b11..4482a8f67ba 100644
--- a/addons/web_editor/static/src/js/editor/transcoder.js
+++ b/addons/web_editor/static/src/js/backend/convert_inline.js
@@ -1,9 +1,7 @@
-odoo.define('web_editor.transcoder', function (require) {
+odoo.define('web_editor.convertInline', function (require) {
'use strict';
-var base = require('web_editor.base');
-
-var rulesCache = [];
+var FieldHtml = require('web_editor.field.html');
/**
* Returns the css rules which applies on an element, tweaked so that they are
@@ -14,8 +12,11 @@ var rulesCache = [];
*/
function getMatchedCSSRules(a) {
var i, r, k;
+ var doc = a.ownerDocument;
+ var rulesCache = a.ownerDocument._rulesCache || (a.ownerDocument._rulesCache = []);
+
if (!rulesCache.length) {
- var sheets = document.styleSheets;
+ var sheets = doc.styleSheets;
for (i = sheets.length-1 ; i >= 0 ; i--) {
var rules;
// try...catch because browser may not able to enumerate rules for cross-domain sheets
@@ -187,11 +188,13 @@ function getMatchedCSSRules(a) {
* converted to images
*/
function fontToImg($editable) {
+ var fonts = odoo.__DEBUG__.services["wysiwyg.fonts"];
+
$editable.find('.fa').each(function () {
var $font = $(this);
var icon, content;
- _.find(base.fontIcons, function (font) {
- return _.find(base.getCssSelectors(font.parser), function (data) {
+ _.find(fonts.fontIcons, function (font) {
+ return _.find(fonts.getCssSelectors(font.parser), function (data) {
if ($font.is(data.selector.replace(/::?before/g, ''))) {
icon = data.names[0].split('-').shift();
content = data.css.match(/content:\s*['"]?(.)['"]?/)[1];
@@ -265,9 +268,6 @@ function applyOverDescendants(node, func) {
* @param {jQuery} $editable
*/
function classToStyle($editable) {
- if (!rulesCache.length) {
- getMatchedCSSRules($editable[0]);
- }
applyOverDescendants($editable[0], function (node) {
var $target = $(node);
var css = getMatchedCSSRules(node);
@@ -328,9 +328,7 @@ function styleToClass($editable) {
$(this).after($('a,img', this));
}).remove();
- getMatchedCSSRules($editable[0]);
-
- var $c = $('').appendTo(document.body);
+ var $c = $('').appendTo($editable[0].ownerDocument.body);
applyOverDescendants($editable[0], function (node) {
var $target = $(node);
@@ -342,7 +340,7 @@ function styleToClass($editable) {
}
});
css = ($c.attr('style', style).attr('style') || '').split(/\s*;\s*/);
- style = $target.attr('style') || '';
+ style = ($target.attr('style') || '').replace(/\s*:\s*/, ':').replace(/\s*;\s*/, ';');
_.each(css, function (v) {
style = style.replace(v, '');
});
@@ -364,13 +362,13 @@ function styleToClass($editable) {
* @param {jQuery} $editable
*/
function attachmentThumbnailToLinkImg($editable) {
- $editable.find('a[href*="/web/content/"][data-mimetype]:empty').each(function () {
+ $editable.find('a[href*="/web/content/"][data-mimetype]').filter(':empty, :containsExact( )').each(function () {
var $link = $(this);
var $img = $('
')
.attr('src', $link.css('background-image').replace(/(^url\(['"])|(['"]\)$)/g, ''))
.css('height', Math.max(1, $link.height()) + 'px')
.css('width', Math.max(1, $link.width()) + 'px');
- $link.append($img);
+ $link.prepend($img);
});
}
@@ -384,6 +382,83 @@ function linkImgToAttachmentThumbnail($editable) {
$editable.find('a[href*="/web/content/"][data-mimetype] > img').remove();
}
+
+//--------------------------------------------------------------------------
+//--------------------------------------------------------------------------
+
+
+FieldHtml.include({
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ /**
+ * @override
+ */
+ commitChanges: function () {
+ if (this.nodeOptions['style-inline'] && this.mode === "edit") {
+ this._toInline();
+ }
+ return this._super();
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * Converts CSS dependencies to CSS-independent HTML.
+ * - CSS display for attachment link -> real image
+ * - Font icons -> images
+ * - CSS styles -> inline styles
+ *
+ * @private
+ */
+ _toInline: function () {
+ var $editable = this.wysiwyg.getEditable();
+ var html = this.wysiwyg.getValue();
+ $editable.html(html);
+
+ attachmentThumbnailToLinkImg($editable);
+ fontToImg($editable);
+ classToStyle($editable);
+ this.wysiwyg.setValue($editable.html(), {
+ notifyChange: false,
+ });
+ },
+ /**
+ * Revert _toInline changes.
+ *
+ * @private
+ */
+ _fromInline: function () {
+ var $editable = this.wysiwyg.getEditable();
+ var html = this.wysiwyg.getValue();
+ $editable.html(html);
+
+ styleToClass($editable);
+ imgToFont($editable);
+ linkImgToAttachmentThumbnail($editable);
+ this.wysiwyg.setValue($editable.html(), {
+ notifyChange: false,
+ });
+ },
+
+ //--------------------------------------------------------------------------
+ // Handler
+ //--------------------------------------------------------------------------
+
+ /**
+ * @override
+ */
+ _onLoadWysiwyg: function () {
+ if (this.nodeOptions['style-inline'] && this.mode === "edit") {
+ this._fromInline();
+ }
+ this._super();
+ },
+});
+
return {
fontToImg: fontToImg,
imgToFont: imgToFont,
@@ -392,4 +467,4 @@ return {
attachmentThumbnailToLinkImg: attachmentThumbnailToLinkImg,
linkImgToAttachmentThumbnail: linkImgToAttachmentThumbnail,
};
-});
+});
\ No newline at end of file
diff --git a/addons/web_editor/static/src/js/backend/field_html.js b/addons/web_editor/static/src/js/backend/field_html.js
index f6d86c9020c..0be20743132 100644
--- a/addons/web_editor/static/src/js/backend/field_html.js
+++ b/addons/web_editor/static/src/js/backend/field_html.js
@@ -179,8 +179,6 @@ var FieldHtml = basic_fields.DebouncedField.extend(TranslatableFieldMixin, {
* @returns {Object}
*/
_getWysiwygOptions: function () {
- var iPhone = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
-
return Object.assign({}, this.nodeOptions, {
recordInfo: {
context: this.record.getContext(this.recordParams),
@@ -193,6 +191,7 @@ var FieldHtml = basic_fields.DebouncedField.extend(TranslatableFieldMixin, {
snippets: this.nodeOptions.snippets,
tabsize: 0,
+ height: 180,
generateOptions: function (options) {
var para = _.find(options.toolbar, function (item) {
return item[0] === 'para';
diff --git a/addons/web_editor/static/src/js/editor/editor.js b/addons/web_editor/static/src/js/editor/editor.js
index dc0f53c8e57..07947db56e4 100644
--- a/addons/web_editor/static/src/js/editor/editor.js
+++ b/addons/web_editor/static/src/js/editor/editor.js
@@ -6,6 +6,7 @@ var Widget = require('web.Widget');
var core = require('web.core');
var rte = require('web_editor.rte');
var snippetsEditor = require('web_editor.snippet.editor');
+var summernoteCustomColors = require('web_editor.rte.summernote_custom_colors');
var _t = core._t;
@@ -26,7 +27,7 @@ var EditorMenuBar = Widget.extend({
*
* @constructor
*/
- init: function (parent) {
+ init: function (parent, options) {
var self = this;
var res = this._super.apply(this, arguments);
var Editor = options.Editor || rte.Class;
@@ -48,8 +49,6 @@ var EditorMenuBar = Widget.extend({
var $editable = this.rte.editable();
window.__EditorMenuBar_$editable = $editable; // TODO remove this hack asap
-
- var options = this.getParent().params;
if (options.snippets) {
this.snippetsMenu = new snippetsEditor.Class(this, Object.assign({
$el: $editable,
@@ -185,6 +184,32 @@ var EditorMenuBar = Widget.extend({
// Private
//--------------------------------------------------------------------------
+ /**
+ * @private
+ */
+ _getDefaultConfig: function ($editable) {
+ return {
+ 'airMode' : true,
+ 'focus': false,
+ 'airPopover': [
+ ['style', ['style']],
+ ['font', ['bold', 'italic', 'underline', 'clear']],
+ ['fontsize', ['fontsize']],
+ ['color', ['color']],
+ ['para', ['ul', 'ol', 'paragraph']],
+ ['table', ['table']],
+ ['insert', ['link', 'picture']],
+ ['history', ['undo', 'redo']],
+ ],
+ 'styleWithSpan': false,
+ 'inlinemedia' : ['p'],
+ 'lang': 'odoo',
+ 'onChange': function (html, $editable) {
+ $editable.trigger('content_changed');
+ },
+ 'colors': summernoteCustomColors,
+ };
+ },
/**
* Reloads the page in non-editable mode, with the right scrolling.
*
diff --git a/addons/web_editor/static/src/js/editor/snippets.editor.js b/addons/web_editor/static/src/js/editor/snippets.editor.js
index 2f2e55a0084..4fba95f2490 100644
--- a/addons/web_editor/static/src/js/editor/snippets.editor.js
+++ b/addons/web_editor/static/src/js/editor/snippets.editor.js
@@ -67,7 +67,7 @@ var SnippetEditor = Widget.extend({
defs.push(this._initializeOptions());
// Initialize move/clone/remove buttons
- if (!this.options.isEditableNode(this.$target[0])) {
+ if (!this.$target.is(':o_editable')) {
this.$el.find('.oe_snippet_move, .oe_snippet_clone, .oe_snippet_remove').remove();
} else {
this.dropped = false;
@@ -1249,6 +1249,7 @@ var SnippetsMenu = Widget.extend({
this.$el.addClass('o_loaded');
$('body.editor_enable').addClass('editor_has_snippets');
+ this.trigger_up('snippets_loaded', self.$el);
},
/**
* Creates a snippet editor to associated to the given snippet. If the given
diff --git a/addons/web_editor/static/src/js/wysiwyg/root.js b/addons/web_editor/static/src/js/wysiwyg/root.js
index ab531d086f7..c2b3d703733 100644
--- a/addons/web_editor/static/src/js/wysiwyg/root.js
+++ b/addons/web_editor/static/src/js/wysiwyg/root.js
@@ -47,7 +47,7 @@ var WysiwygRoot = Widget.extend({
});
return instance.attachTo($target).then(function () {
- self.$editor = instance.$el;
+ self.$editor = instance.$editor || instance.$el;
});
});
},
diff --git a/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js b/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js
index cb0db917e54..d33f5455da4 100644
--- a/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js
+++ b/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js
@@ -2,7 +2,6 @@ odoo.define('web_editor.wysiwyg', function (require) {
'use strict';
var Widget = require('web.Widget');
var SummernoteManager = require('web_editor.rte.summernote');
-var transcoder = require('web_editor.transcoder');
var summernoteCustomColors = require('web_editor.rte.summernote_custom_colors');
var id = 0;
@@ -32,28 +31,26 @@ var Wysiwyg = Widget.extend({
},
},
/**
- * @params {Object} params
- * @params {Object} params.recordInfo
- * @params {Object} params.recordInfo.context
- * @params {String} [params.recordInfo.context]
- * @params {integer} [params.recordInfo.res_id]
- * @params {String} [params.recordInfo.data_res_model]
- * @params {integer} [params.recordInfo.data_res_id]
+ * @options {Object} options
+ * @options {Object} options.recordInfo
+ * @options {Object} options.recordInfo.context
+ * @options {String} [options.recordInfo.context]
+ * @options {integer} [options.recordInfo.res_id]
+ * @options {String} [options.recordInfo.data_res_model]
+ * @options {integer} [options.recordInfo.data_res_id]
* @see _onGetRecordInfo
* @see _getAttachmentsDomain in /wysiwyg/widgets/media.js
- * @params {Object} params.attachments
+ * @options {Object} options.attachments
* @see _onGetRecordInfo
* @see _getAttachmentsDomain in /wysiwyg/widgets/media.js (for attachmentIDs)
- * @params {function} params.generateOptions
+ * @options {function} options.generateOptions
* called with the summernote configuration object used before sending to summernote
* @see _editorOptions
**/
- init: function (parent, params) {
+ init: function (parent, options) {
this._super.apply(this, arguments);
- this.params = params;
- this.params.isEditableNode = function (node) {
- return $(node).is(':o_editable');
- };
+ this.id = ++id;
+ this.options = options;
},
/**
* Load assets and color picker template then call summernote API
@@ -64,14 +61,13 @@ var Wysiwyg = Widget.extend({
willStart: function () {
new SummernoteManager(this);
this.$target = this.$el;
- return this._super.apply(this, arguments);
+ return this._super();
},
/**
*
* @override
*/
start: function () {
- var self = this;
this.$target.wrap('');
this.$el = this.$target.parent();
var options = this._editorOptions();
@@ -80,7 +76,6 @@ var Wysiwyg = Widget.extend({
this.$editor.data('wysiwyg', this);
this.$editor.data('oe-model', options.recordInfo.res_model);
this.$editor.data('oe-id', options.recordInfo.res_id);
- var $wysiwyg = this.$editor.closest('odoo-wysiwyg-container');
$(document).on('mousedown', this._blur);
this._value = this.$target.html() || this.$target.val();
return this._super.apply(this, arguments).then(() => {
@@ -106,7 +101,7 @@ var Wysiwyg = Widget.extend({
* @returns {jQuery}
*/
getEditable: function () {
- console.log('getEditable');
+ return this.$editor;
},
/**
* Return true if the content has changed.
@@ -116,27 +111,6 @@ var Wysiwyg = Widget.extend({
isDirty: function () {
return this._value !== (this.$editor.html() || this.$editor.val());
},
- /**
- * Return true if the current node is unbreakable.
- * An unbreakable node can be removed or added but can't by split into
- * different nodes (for keypress and selection).
- * An unbreakable node can contain nodes that can be edited.
- *
- * @param {Node} node
- * @returns {Boolean}
- */
- isUnbreakableNode: function (node) {
- console.log('isUnbreakableNode');
- },
- /**
- * Return true if the current node is editable (for keypress and selection).
- *
- * @param {Node} node
- * @returns {Boolean}
- */
- isEditableNode: function (node) {
- console.log('isEditableNode');
- },
/**
* Set the focus on the element.
*/
@@ -193,12 +167,6 @@ var Wysiwyg = Widget.extend({
this.$target.html(value);
}
this.$editor.html(value);
- if (this.params['style-inline']) {
- transcoder.styleToClass(this.$editor);
- transcoder.imgToFont(this.$editor);
- transcoder.linkImgToAttachmentThumbnail(this.$editor);
- }
- this._value = (this.$target.html() || this.$target.val());
},
//--------------------------------------------------------------------------
// Private
@@ -246,8 +214,21 @@ Wysiwyg.getRange = function (node) {
eo: range.eo,
};
};
-Wysiwyg.setRange = function () {
- console.log('setRange');
+/**
+ * @param {Node} startNode
+ * @param {Number} startOffset
+ * @param {Node} endNode
+ * @param {Number} endOffset
+ */
+Wysiwyg.setRange = function (startNode, startOffset, endNode, endOffset) {
+ $(startNode).focus();
+ if (endNode) {
+ $.summernote.core.range.create(startNode, startOffset, endNode, endOffset).select();
+ } else {
+ $.summernote.core.range.create(startNode, startOffset).select();
+ }
+ // trigger for Unbreakable
+ $(startNode.tagName ? startNode : startNode.parentNode).trigger('wysiwyg.range');
};
/**
* @param {Node} node - dom node
diff --git a/addons/web_editor/static/src/js/wysiwyg/wysiwyg_iframe.js b/addons/web_editor/static/src/js/wysiwyg/wysiwyg_iframe.js
new file mode 100644
index 00000000000..64b7c6a8f8c
--- /dev/null
+++ b/addons/web_editor/static/src/js/wysiwyg/wysiwyg_iframe.js
@@ -0,0 +1,136 @@
+odoo.define('web_editor.wysiwyg.iframe', function (require) {
+'use strict';
+
+var Wysiwyg = require('web_editor.wysiwyg');
+var ajax = require('web.ajax');
+var core = require('web.core');
+
+var qweb = core.qweb;
+var promiseCommon;
+var promiseWysiwyg;
+
+
+/**
+ * Add option (inIframe) to load Wysiwyg in an iframe.
+ **/
+Wysiwyg.include({
+ /**
+ * Add options to load Wysiwyg in an iframe.
+ *
+ * @override
+ * @param {boolean} options.inIframe
+ **/
+ init: function (parent, options) {
+ this._super.apply(this, arguments);
+ if (this.options.inIframe) {
+ if (!this.options.iframeCssAssets) {
+ this.options.iframeCssAssets = 'web_editor.wysiwyg_iframe_css_assets';
+ }
+ this._onUpdateIframeId = 'onLoad_' + this.id;
+ }
+ },
+ /**
+ * Load assets to inject into iframe.
+ *
+ * @override
+ **/
+ willStart: function () {
+ if (!this.options.inIframe) {
+ return this._super();
+ }
+ var debug = odoo.debug;
+ odoo.debug = false;
+
+ var defAsset;
+ if (this.options.iframeCssAssets) {
+ defAsset = ajax.loadAsset(this.options.iframeCssAssets);
+ } else {
+ defAsset = Promise.resolve({
+ cssLibs: [],
+ cssContents: []
+ });
+ }
+
+ promiseWysiwyg = promiseWysiwyg || ajax.loadAsset('web_editor.wysiwyg_iframe_editor_assets');
+ odoo.debug = debug;
+
+ this.defAsset = Promise.all([promiseWysiwyg, defAsset]);
+
+ this.$target = this.$el;
+ return this.defAsset
+ .then(this._loadIframe.bind(this))
+ .then(this._super.bind(this));
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * Create iframe, inject css and create a link with the content,
+ * then inject the target inside.
+ *
+ * @private
+ * @returns {Promise}
+ */
+ _loadIframe: function () {
+ var self = this;
+ this.$iframe = $('