summaryrefslogtreecommitdiff
path: root/addons/website/static/src/js/editor
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2019-09-13 18:07:59 +0200
committerChristophe Simonis <chs@odoo.com>2019-09-13 18:07:59 +0200
commit6c948c36bbf72dd896e0bff03aa7fbddadcbb0cd (patch)
treef8620905c011735d8a3c340c46d1417a54a75972 /addons/website/static/src/js/editor
parent48503fcf08eac6fed12e080d4f501f9bbc8d257d (diff)
parent197474e5b9ad43ab2d587a98136a1a809ce9a2e7 (diff)
[MERGE] forward port branch saas-12.5 up to 197474e5b9a
Diffstat (limited to 'addons/website/static/src/js/editor')
-rw-r--r--addons/website/static/src/js/editor/editor_menu.js2
-rw-r--r--addons/website/static/src/js/editor/snippets.options.js76
-rw-r--r--addons/website/static/src/js/editor/wysiwyg_multizone.js142
3 files changed, 183 insertions, 37 deletions
diff --git a/addons/website/static/src/js/editor/editor_menu.js b/addons/website/static/src/js/editor/editor_menu.js
index 72c2e6bee81..87422efdb1f 100644
--- a/addons/website/static/src/js/editor/editor_menu.js
+++ b/addons/website/static/src/js/editor/editor_menu.js
@@ -113,7 +113,7 @@ var EditorMenu = Widget.extend({
save: function (reload) {
var self = this;
this.trigger_up('edition_will_stopped');
- return this.wysiwyg.save(true).then(function (result) {
+ return this.wysiwyg.save(false).then(function (result) {
var $wrapwrap = $('#wrapwrap');
self.editable($wrapwrap).removeClass('o_editable');
if (result.isDirty && reload !== false) {
diff --git a/addons/website/static/src/js/editor/snippets.options.js b/addons/website/static/src/js/editor/snippets.options.js
index d659677c478..baa1d8c5a7c 100644
--- a/addons/website/static/src/js/editor/snippets.options.js
+++ b/addons/website/static/src/js/editor/snippets.options.js
@@ -1297,35 +1297,51 @@ options.registry.gallery = options.Class.extend({
* @override
*/
_setActive: function () {
- this._super();
- var classes = _.uniq((this.$target.attr('class').replace(/(^|\s)o_/g, ' ') || '').split(/\s+/));
- this.$el.find('[data-mode]')
- .removeClass('active')
- .filter('[data-mode="' + classes.join('"], [data-mode="') + '"]').addClass('active');
- var mode = this.$el.find('[data-mode].active').data('mode');
+ this._super(...arguments);
- classes = _.uniq((this.$('img:first').attr('class') || '').split(/\s+/));
- this.$el.find('[data-styling]')
+ var activeModeSelectors = [];
+ for (const className of this.$target[0].classList) {
+ if (className.startsWith('o_')) {
+ activeModeSelectors.push('[data-mode="' + className.substring(2) + '"]');
+ }
+ }
+ var activeMode = this.$el.find('[data-mode]')
.removeClass('active')
- .filter('[data-styling="' + classes.join('"], [data-styling="') + '"]').addClass('active');
-
- this.$el.find('[data-interval]').removeClass('active')
- .filter('[data-interval='+this.$target.find('.carousel:first').attr('data-interval')+']')
+ .filter(activeModeSelectors.join(', '))
+ .addClass('active')
+ .data('mode');
+
+ var carousel = this.$target[0].querySelector('.carousel');
+ var activeInterval = carousel ? carousel.dataset.interval : undefined;
+ var $intervalOptions = this.$el.find('[data-interval]');
+ $intervalOptions.removeClass('active')
+ .filter('[data-interval="' + activeInterval + '"]')
.addClass('active');
-
- var interval = this.$target.find('.carousel:first').attr('data-interval');
- this.$el.find('[data-interval]')
- .removeClass('active')
- .filter('[data-interval=' + interval + ']').addClass('active');
+ $intervalOptions.closest('we-collapse-area')[0]
+ .classList.toggle('d-none', activeMode !== 'slideshow');
var columns = this._getColumns();
- this.$el.find('[data-columns]')
- .removeClass('active')
- .filter('[data-columns=' + columns + ']').addClass('active');
-
- this.$el.find('[data-columns]:first, [data-select-class="spc-none"]')
- .parent().parent().toggle(['grid', 'masonry'].indexOf(mode) !== -1);
- this.$el.find('[data-interval]:first').parent().parent().toggle(mode === 'slideshow');
+ var $columnOptions = this.$el.find('[data-columns]');
+ $columnOptions.removeClass('active')
+ .filter('[data-columns="' + columns + '"]')
+ .addClass('active');
+ $columnOptions.closest('we-collapse-area')[0]
+ .classList.toggle('d-none', !(activeMode === 'grid' || activeMode === 'masonry'));
+
+ this.el.querySelector('.o_w_image_spacing_option')
+ .classList.toggle('d-none', activeMode === 'slideshow');
+
+ var $stylingOptions = this.$el.find('[data-styling]');
+ $stylingOptions.removeClass('active');
+ var img = this.$target[0].querySelector('img');
+ if (img) {
+ var activeStyleSelectors = [];
+ for (const className of img.classList) {
+ activeStyleSelectors.push('[data-styling="' + className + '"]');
+ }
+ $stylingOptions.filter(activeStyleSelectors.join(', '))
+ .addClass('active');
+ }
},
});
@@ -1410,7 +1426,7 @@ options.registry.topMenuTransparency = options.Class.extend({
enabled = value;
},
});
- this.$el.find('[data-transparent]').addBack('[data-transparent]').toggleClass('active', !!enabled);
+ this.$el.find('[data-transparent]').toggleClass('active', !!enabled);
},
});
@@ -1465,7 +1481,6 @@ options.registry.topMenuColor = options.registry.colorpicker.extend({
*/
options.registry.anchorName = options.Class.extend({
xmlDependencies: ['/website/static/src/xml/website.editor.xml'],
- preventChildPropagation: true,
//--------------------------------------------------------------------------
// Public
@@ -1493,6 +1508,13 @@ options.registry.anchorName = options.Class.extend({
click: function () {
var $input = this.$('.o_input_anchor_name');
var anchorName = $input.val().trim().replace(/\s/g, '_');
+ if (self.$target[0].id === anchorName) {
+ // If the chosen anchor name is already the one used by the
+ // element, close the dialog and do nothing else
+ this.close();
+ return;
+ }
+
var isValid = /^[\w-]+$/.test(anchorName);
var alreadyExists = isValid && $('#' + anchorName).length > 0;
var anchorOK = isValid && !alreadyExists;
@@ -1520,7 +1542,7 @@ options.registry.anchorName = options.Class.extend({
});
}
new Dialog(this, {
- title: _t("Anchor Name"),
+ title: _t("Link Anchor"),
$content: $(qweb.render('website.dialog.anchorName', {
currentAnchor: this.$target.attr('id'),
})),
diff --git a/addons/website/static/src/js/editor/wysiwyg_multizone.js b/addons/website/static/src/js/editor/wysiwyg_multizone.js
index 202257857d2..9ed31d3afe5 100644
--- a/addons/website/static/src/js/editor/wysiwyg_multizone.js
+++ b/addons/website/static/src/js/editor/wysiwyg_multizone.js
@@ -1,7 +1,38 @@
odoo.define('web_editor.wysiwyg.multizone', function (require) {
'use strict';
+
var Wysiwyg = require('web_editor.wysiwyg');
+var snippetsEditor = require('web_editor.snippet.editor');
+/**
+ * Show/hide the dropdowns associated to the given toggles and allows to wait
+ * for when it is fully shown/hidden.
+ *
+ * Note: this also takes care of the fact the 'toggle' method of bootstrap does
+ * not properly work in all cases.
+ *
+ * @param {jQuery} $toggles
+ * @param {boolean} [show]
+ * @returns {Promise<jQuery>}
+ */
+function toggleDropdown($toggles, show) {
+ return Promise.all(_.map($toggles, toggle => {
+ var $toggle = $(toggle);
+ var $dropdown = $toggle.parent();
+ var shown = $dropdown.hasClass('show');
+ if (shown === show) {
+ return;
+ }
+ var toShow = !shown;
+ return new Promise(resolve => {
+ $dropdown.one(
+ toShow ? 'shown.bs.dropdown' : 'hidden.bs.dropdown',
+ () => resolve()
+ );
+ $toggle.dropdown(toShow ? 'show' : 'hide');
+ });
+ })).then(() => $toggles);
+}
/**
* HtmlEditor
@@ -13,7 +44,6 @@ var Wysiwyg = require('web_editor.wysiwyg');
*
*/
var WysiwygMultizone = Wysiwyg.extend({
-
/**
* @override
*/
@@ -24,7 +54,35 @@ var WysiwygMultizone = Wysiwyg.extend({
var outerHTML = this._getEscapedElement($el).prop('outerHTML');
return self._saveElement(outerHTML, self.options.recordInfo, $el[0]);
};
- return this._super();
+
+ // Mega menu initialization: handle dropdown openings by hand
+ var $megaMenuToggles = this.$('.o_mega_menu_toggle');
+ $megaMenuToggles.removeAttr('data-toggle').dropdown('dispose');
+ $megaMenuToggles.on('click.wysiwyg_multizone', ev => {
+ var $toggle = $(ev.currentTarget);
+
+ // Each time we toggle a dropdown, we will destroy the dropdown
+ // behavior afterwards to keep manual control of it
+ var dispose = ($els => $els.dropdown('dispose'));
+
+ // First hide all other mega menus
+ toggleDropdown($megaMenuToggles.not($toggle), false).then(dispose);
+
+ // Then toggle the clicked one
+ toggleDropdown($toggle)
+ .then(dispose)
+ .then($el => {
+ var isShown = $el.parent().hasClass('show');
+ this.editor.snippetsMenu.toggleMegaMenuSnippets(isShown);
+ });
+ });
+
+ return this._super.apply(this, arguments).then(() => {
+ // Showing Mega Menu snippets if one dropdown is already opened
+ if (this.$('.o_mega_menu').hasClass('show')) {
+ this.editor.snippetsMenu.toggleMegaMenuSnippets(true);
+ }
+ });
},
/**
* @override
@@ -32,20 +90,26 @@ var WysiwygMultizone = Wysiwyg.extend({
*/
save: function () {
if (this.isDirty()) {
- return this.editor.save().then(function() {
- return {isDirty: true};
- });
+ return this._restoreMegaMenus()
+ .then(() => this.editor.save(false))
+ .then(() => ({isDirty: true}));
} else {
return {isDirty: false};
}
},
-
+ /**
+ * @override
+ */
+ destroy: function () {
+ this._restoreMegaMenus();
+ this._super.apply(this, arguments);
+ },
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
- _getEditableArea: function() {
+ _getEditableArea: function () {
return $(':o_editable');
},
/**
@@ -59,8 +123,9 @@ var WysiwygMultizone = Wysiwyg.extend({
* page element)
*/
_saveElement: function (outerHTML, recordInfo, editable) {
+ var promises = [];
var $el = $(editable);
- return this._rpc({
+ promises.push(this._rpc({
model: 'ir.ui.view',
method: 'save',
args: [
@@ -69,7 +134,66 @@ var WysiwygMultizone = Wysiwyg.extend({
$el.data('oe-xpath') || null,
],
context: recordInfo.context,
- });
+ }));
+ if ($el.data('oe-field') === 'mega_menu_content') {
+ // On top of saving the mega menu content like any other field
+ // content, we must save the custom classes that were set on the
+ // menu itself.
+ // FIXME normally removing the 'show' class should not be necessary here
+ // TODO check that editor classes are removed here as well
+ var classes = _.without($el.attr('class').split(' '), 'dropdown-menu', 'o_mega_menu', 'show');
+ promises.push(this._rpc({
+ model: 'website.menu',
+ method: 'write',
+ args: [
+ [parseInt($el.data('oe-id'))],
+ {
+ 'mega_menu_classes': classes.join(' '),
+ },
+ ],
+ }));
+ }
+ return Promise.all(promises);
+ },
+ /**
+ * Restores mega menu behaviors and closes them (important to do before
+ * saving otherwise they would be saved opened).
+ *
+ * @private
+ * @returns {Promise}
+ */
+ _restoreMegaMenus: function () {
+ var $megaMenuToggles = this.$('.o_mega_menu_toggle');
+ $megaMenuToggles.off('.wysiwyg_multizone')
+ .attr('data-toggle', 'dropdown')
+ .dropdown({});
+ return toggleDropdown($megaMenuToggles, false);
+ },
+});
+
+snippetsEditor.Class.include({
+ /**
+ * @private
+ * @param {boolean} show
+ */
+ toggleMegaMenuSnippets: function (show) {
+ setTimeout(() => this._activateSnippet(false));
+ this.$('#snippet_mega_menu').toggleClass('d-none', !show);
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * @override
+ */
+ _insertDropzone: function ($hook) {
+ var $hookParent = $hook.parent();
+ var $dropzone = this._super(...arguments);
+ $dropzone.attr('data-editor-message', $hookParent.attr('data-editor-message'));
+ $dropzone.attr('data-editor-sub-message', $hookParent.attr('data-editor-sub-message'));
+ return $dropzone;
},
});