From 35b61822a8b12ebbdb455495479df6f2e13db03e Mon Sep 17 00:00:00 2001 From: Gorash Date: Tue, 20 Aug 2019 06:23:06 +0000 Subject: [PATCH] [REV] wev_editor, website: revert saas-12.2 editor This commit reverts the saas-12.2 editor by putting back the editor of Odoo 12. The saas-12.2 editor was an intermediate work between the previous editor of Odoo 12 and the new one slated for Odoo 13. It was unstable but it was supposed to be replaced by the new editor of version 13. However, since the new editor has been postponed to Odoo 14, the saas-12.2 one would have been staying in Odoo 13, which would have been a nightmare to maintain. To avoid this outcome, we chose to put back the 12.0 editor in its place. This has the particular advantage that Odoo 13 will share the same bugfixes as Odoo 12 and 11 as they all run under the same core editor, while the saas-12.2 one would have been an entirely different beast to maintain. This is a partial revert of f2969923. Part of PR 35677. Co-authored-by: Nicolas Bayet Co-authored-by: Antoine Guenet Co-authored-by: Christophe Matthieu Co-authored-by: David Monjoie --- addons/web_editor/static/src/js/base.js | 157 ++ addons/web_editor/static/src/js/common/ace.js | 270 +- .../web_editor/static/src/js/editor/editor.js | 286 ++ addons/web_editor/static/src/js/editor/rte.js | 749 +++++ .../static/src/js/editor/rte.summernote.js | 1277 +++++++++ .../snippets.editor.js | 324 +-- .../snippets.options.js | 103 +- .../static/src/js/editor/summernote.js | 2452 +++++++++++++++++ .../transcoder.js} | 107 +- addons/web_editor/static/src/js/tours/rte.js | 325 +++ .../static/src/js/wysiwyg/options.js | 6 - .../static/src/js/wysiwyg/plugin/abstract.js | 222 -- .../static/src/js/wysiwyg/plugin/bullet.js | 571 ---- .../static/src/js/wysiwyg/plugin/buttons.js | 88 - .../static/src/js/wysiwyg/plugin/codeview.js | 60 - .../static/src/js/wysiwyg/plugin/dropzone.js | 229 -- .../static/src/js/wysiwyg/plugin/editor.js | 189 -- .../static/src/js/wysiwyg/plugin/font.js | 567 ---- .../src/js/wysiwyg/plugin/font_buttons.js | 31 - .../src/js/wysiwyg/plugin/help_dialog.js | 27 - .../static/src/js/wysiwyg/plugin/helper.js | 1948 ------------- .../static/src/js/wysiwyg/plugin/hint.js | 139 - .../static/src/js/wysiwyg/plugin/history.js | 87 - .../static/src/js/wysiwyg/plugin/keyboard.js | 1237 --------- .../static/src/js/wysiwyg/plugin/link.js | 493 ---- .../static/src/js/wysiwyg/plugin/media.js | 1288 --------- .../static/src/js/wysiwyg/plugin/plugins.js | 78 - .../static/src/js/wysiwyg/plugin/table.js | 244 -- .../static/src/js/wysiwyg/plugin/text.js | 811 ------ .../static/src/js/wysiwyg/plugin/toolbar.js | 57 - .../static/src/js/wysiwyg/plugin/transform.js | 85 - .../src/js/wysiwyg/plugin/unbreakable.js | 364 --- .../static/src/js/wysiwyg/plugin_registry.js | 43 - .../static/src/js/wysiwyg/translation.js | 161 -- .../static/src/js/wysiwyg/wysiwyg_iframe.js | 234 -- .../js/wysiwyg_snippets/wysiwyg_snippets.js | 291 -- addons/web_editor/views/editor.xml | 102 +- addons/website/static/src/js/editor/editor.js | 18 + .../static/src/js/editor/rte.summernote.js | 59 + addons/website/views/website_templates.xml | 8 +- 40 files changed, 5678 insertions(+), 10109 deletions(-) create mode 100644 addons/web_editor/static/src/js/base.js create mode 100644 addons/web_editor/static/src/js/editor/editor.js create mode 100644 addons/web_editor/static/src/js/editor/rte.js create mode 100644 addons/web_editor/static/src/js/editor/rte.summernote.js rename addons/web_editor/static/src/js/{wysiwyg_snippets => editor}/snippets.editor.js (84%) rename addons/web_editor/static/src/js/{wysiwyg_snippets => editor}/snippets.options.js (93%) create mode 100644 addons/web_editor/static/src/js/editor/summernote.js rename addons/web_editor/static/src/js/{backend/convert_inline.js => editor/transcoder.js} (83%) create mode 100644 addons/web_editor/static/src/js/tours/rte.js delete mode 100644 addons/web_editor/static/src/js/wysiwyg/options.js delete mode 100644 addons/web_editor/static/src/js/wysiwyg/plugin/abstract.js delete mode 100644 addons/web_editor/static/src/js/wysiwyg/plugin/bullet.js delete mode 100644 addons/web_editor/static/src/js/wysiwyg/plugin/buttons.js delete mode 100644 addons/web_editor/static/src/js/wysiwyg/plugin/codeview.js delete mode 100644 addons/web_editor/static/src/js/wysiwyg/plugin/dropzone.js delete mode 100644 addons/web_editor/static/src/js/wysiwyg/plugin/editor.js delete mode 100644 addons/web_editor/static/src/js/wysiwyg/plugin/font.js delete mode 100644 addons/web_editor/static/src/js/wysiwyg/plugin/font_buttons.js delete mode 100644 addons/web_editor/static/src/js/wysiwyg/plugin/help_dialog.js delete mode 100644 addons/web_editor/static/src/js/wysiwyg/plugin/helper.js delete mode 100644 addons/web_editor/static/src/js/wysiwyg/plugin/hint.js delete mode 100644 addons/web_editor/static/src/js/wysiwyg/plugin/history.js delete mode 100644 addons/web_editor/static/src/js/wysiwyg/plugin/keyboard.js delete mode 100644 addons/web_editor/static/src/js/wysiwyg/plugin/link.js delete mode 100644 addons/web_editor/static/src/js/wysiwyg/plugin/media.js delete mode 100644 addons/web_editor/static/src/js/wysiwyg/plugin/plugins.js delete mode 100644 addons/web_editor/static/src/js/wysiwyg/plugin/table.js delete mode 100644 addons/web_editor/static/src/js/wysiwyg/plugin/text.js delete mode 100644 addons/web_editor/static/src/js/wysiwyg/plugin/toolbar.js delete mode 100644 addons/web_editor/static/src/js/wysiwyg/plugin/transform.js delete mode 100644 addons/web_editor/static/src/js/wysiwyg/plugin/unbreakable.js delete mode 100644 addons/web_editor/static/src/js/wysiwyg/plugin_registry.js delete mode 100644 addons/web_editor/static/src/js/wysiwyg/translation.js delete mode 100644 addons/web_editor/static/src/js/wysiwyg/wysiwyg_iframe.js delete mode 100644 addons/web_editor/static/src/js/wysiwyg_snippets/wysiwyg_snippets.js create mode 100644 addons/website/static/src/js/editor/editor.js create mode 100644 addons/website/static/src/js/editor/rte.summernote.js diff --git a/addons/web_editor/static/src/js/base.js b/addons/web_editor/static/src/js/base.js new file mode 100644 index 00000000000..d69cace3100 --- /dev/null +++ b/addons/web_editor/static/src/js/base.js @@ -0,0 +1,157 @@ +odoo.define('web_editor.base', function (require) { +'use strict'; + +var ajax = require('web.ajax'); +var session = require('web.session'); + +var domReady = $.Deferred(); +$(domReady.resolve.bind(domReady)); + +return { + /** + * Retrieves all the CSS rules which match the given parser (Regex). + * + * @param {Regex} filter + * @returns {Object[]} Array of CSS rules descriptions (objects). A rule is + * defined by 3 values: 'selector', 'css' and 'names'. 'selector' + * is a string which contains the whole selector, 'css' is a string + * which contains the css properties and 'names' is an array of the + * first captured groups for each selector part. E.g.: if the + * filter is set to match .fa-* rules and capture the icon names, + * the rule: + * '.fa-alias1::before, .fa-alias2::before { hello: world; }' + * will be retrieved as + * { + * selector: '.fa-alias1::before, .fa-alias2::before', + * css: 'hello: world;', + * names: ['.fa-alias1', '.fa-alias2'], + * } + */ + cacheCssSelectors: {}, + getCssSelectors: function (filter) { + if (this.cacheCssSelectors[filter]) { + return this.cacheCssSelectors[filter]; + } + this.cacheCssSelectors[filter] = []; + var sheets = document.styleSheets; + for (var i = 0; i < sheets.length; i++) { + var rules; + try { + // try...catch because Firefox not able to enumerate + // document.styleSheets[].cssRules[] for cross-domain + // stylesheets. + rules = sheets[i].rules || sheets[i].cssRules; + } catch (e) { + console.warn("Can't read the css rules of: " + sheets[i].href, e); + continue; + } + if (!rules) { + continue; + } + + for (var r = 0 ; r < rules.length ; r++) { + var selectorText = rules[r].selectorText; + if (!selectorText) { + continue; + } + var selectors = selectorText.split(/\s*,\s*/); + var data = null; + for (var s = 0; s < selectors.length; s++) { + var match = selectors[s].trim().match(filter); + if (!match) { + continue; + } + if (!data) { + data = { + selector: match[0], + css: rules[r].cssText.replace(/(^.*\{\s*)|(\s*\}\s*$)/g, ''), + names: [match[1]] + }; + } else { + data.selector += (', ' + match[0]); + data.names.push(match[1]); + } + } + if (data) { + this.cacheCssSelectors[filter].push(data); + } + } + } + return this.cacheCssSelectors[filter]; + }, + /** + * List of font icons to load by editor. The icons are displayed in the media + * editor and identified like font and image (can be colored, spinned, resized + * with fa classes). + * To add font, push a new object {base, parser} + * + * - base: class who appear on all fonts + * - parser: regular expression used to select all font in css stylesheets + * + * @type Array + */ + fontIcons: [{base: 'fa', parser: /\.(fa-(?:\w|-)+)::?before/i}], + /** + * Searches the fonts described by the @see fontIcons variable. + */ + computeFonts: _.once(function () { + var self = this; + _.each(this.fontIcons, function (data) { + data.cssData = self.getCssSelectors(data.parser); + data.alias = _.flatten(_.map(data.cssData, _.property('names'))); + }); + }), + /** + * If a widget needs to be instantiated on page loading, it needs to wait + * for appropriate resources to be loaded. This function returns a Deferred + * which is resolved when the dom is ready, the session is bound + * (translations loaded) and the XML is loaded. This should however not be + * necessary anymore as widgets should not be parentless and should then be + * instantiated (directly or not) by the page main component (webclient, + * website root, editor bar, ...). The DOM will be ready then, the main + * component is in charge of waiting for the session and the XML can be + * lazy loaded thanks to the @see Widget.xmlDependencies key. + * + * @returns {Deferred} + */ + ready: function () { + return $.when(domReady, session.is_bound, ajax.loadXML()); + }, +}; +}); + +//============================================================================== + +odoo.define('web_editor.context', function (require) { +'use strict'; + +function getContext(context) { + var html = document.documentElement; + return _.extend({ + lang: (html.getAttribute('lang') || 'en_US').replace('-', '_'), + }, context || {}); +} +function getExtraContext(context) { + var html = document.documentElement; + return _.extend(getContext(), { + editable: !!(html.dataset.editable || $('[data-oe-model]').length), // temporary hack, this should be done in python + translatable: !!html.dataset.translatable, + edit_translations: !!html.dataset.edit_translations, + }, context || {}); +} + +return { + get: getContext, + getExtra: getExtraContext, +}; +}); + +//============================================================================== + +odoo.define('web_editor.ready', function (require) { +'use strict'; + +var base = require('web_editor.base'); + +return base.ready(); +}); diff --git a/addons/web_editor/static/src/js/common/ace.js b/addons/web_editor/static/src/js/common/ace.js index dc2d78a53c7..37c21f2aa36 100644 --- a/addons/web_editor/static/src/js/common/ace.js +++ b/addons/web_editor/static/src/js/common/ace.js @@ -2,12 +2,13 @@ odoo.define('web_editor.ace', function (require) { 'use strict'; var ajax = require('web.ajax'); -var config = require('web.config'); var concurrency = require('web.concurrency'); var core = require('web.core'); var Dialog = require('web.Dialog'); var Widget = require('web.Widget'); +var weContext = require('web_editor.context'); var localStorage = require('web.local_storage'); +var session = require('web.session'); var _t = core._t; @@ -119,22 +120,20 @@ var ViewEditor = Widget.extend({ jsLibs: [ '/web/static/lib/ace/ace.js', [ - '/web/static/lib/ace/javascript_highlight_rules.js', '/web/static/lib/ace/mode-xml.js', '/web/static/lib/ace/mode-scss.js', - '/web/static/lib/ace/mode-js.js', '/web/static/lib/ace/theme-monokai.js' ] ], events: { 'click .o_ace_type_switcher_choice': '_onTypeChoice', 'change .o_res_list': '_onResChange', - 'click .o_ace_filter': '_onFilterChange', + 'click .js_include_bundles': '_onIncludeBundlesChange', + 'click .js_include_all_scss': '_onIncludeAllSCSSChange', 'click button[data-action=save]': '_onSaveClick', 'click button[data-action=reset]': '_onResetClick', 'click button[data-action=format]': '_onFormatClick', 'click button[data-action=close]': '_onCloseClick', - 'click #ace-view-id > .alert-warning .close': '_onCloseWarningClick' }, /** @@ -152,35 +151,30 @@ var ViewEditor = Widget.extend({ * @param {string} [options.position=right] * @param {boolean} [options.doNotLoadViews=false] * @param {boolean} [options.doNotLoadSCSS=false] - * @param {boolean} [options.doNotLoadJS=false] * @param {boolean} [options.includeBundles=false] - * @param {string} [options.filesFilter=custom] + * @param {boolean} [options.includeAllSCSS=false] * @param {string[]} [options.defaultBundlesRestriction] */ init: function (parent, viewKey, options) { this._super.apply(this, arguments); - this.context = options.context; - this.viewKey = viewKey; this.options = _.defaults({}, options, { position: 'right', doNotLoadViews: false, doNotLoadSCSS: false, - doNotLoadJS: false, includeBundles: false, - filesFilter: 'custom', + includeAllSCSS: false, defaultBundlesRestriction: [], }); - this.resources = {xml: {}, scss: {}, js: {}}; - this.editingSessions = {xml: {}, scss: {}, js: {}}; + this.resources = {xml: {}, scss: {}}; + this.editingSessions = {xml: {}, scss: {}}; this.currentType = 'xml'; // Alias this.views = this.resources.xml; this.scss = this.resources.scss; - this.js = this.resources.js; }, /** * Loads everything the ace library needs to work. @@ -189,10 +183,11 @@ var ViewEditor = Widget.extend({ * @override */ willStart: function () { - return Promise.all([ + return $.when( this._super.apply(this, arguments), + ajax.loadLibs(this), this._loadResources() - ]); + ); }, /** * Initializes the library and initial view once the DOM is ready. It also @@ -208,13 +203,11 @@ var ViewEditor = Widget.extend({ this.$lists = { xml: this.$('#ace-view-list'), - scss: this.$('#ace-scss-list'), - js: this.$('#ace-js-list'), + scss: this.$('#ace-scss-list') }; this.$includeBundlesArea = this.$('.oe_include_bundles'); this.$includeAllSCSSArea = this.$('.o_include_all_scss'); this.$viewID = this.$('#ace-view-id > span'); - this.$warningMessage = this.$('#ace-view-id > .alert-warning'); this.$formatButton = this.$('button[data-action=format]'); this.$resetButton = this.$('button[data-action=reset]'); @@ -234,24 +227,12 @@ var ViewEditor = Widget.extend({ var initType; if (this.options.initialResID) { initResID = this.options.initialResID; - if (_.isString(initResID) && initResID[0] === '/') { - if (_.str.endsWith(initResID, '.scss')) { - initType = 'scss'; - } else { - initType = 'js'; - } - } else { - initType = 'xml'; - } + initType = (_.isString(initResID) && initResID[0] === '/') ? 'scss' : 'xml'; } else { if (!this.options.doNotLoadSCSS) { initResID = this.sortedSCSS[0][1][0].url; // first bundle, scss files, first one initType = 'scss'; } - if (!this.options.doNotLoadJS) { - initResID = this.sortedJS[0][1][0].url; // first bundle, js files, first one - initType = 'js'; - } if (!this.options.doNotLoadViews) { if (typeof this.viewKey === "number") { initResID = this.viewKey; @@ -339,7 +320,7 @@ var ViewEditor = Widget.extend({ * Initializes a text editor for the specified resource. * * @private - * @param {integer|string} resID - the ID/URL of the view/scss/js file + * @param {integer|string} resID - the ID/URL of the view/scss file * @param {string} [type] (default to the currently selected one) * @returns {ace.EditSession} */ @@ -359,13 +340,13 @@ var ViewEditor = Widget.extend({ return editingSession; }, /** - * Forces the view/scss/js file identified by its ID/URL to be displayed in the + * Forces the view/scss file identified by its ID/URL to be displayed in the * editor. The method will update the resource select DOM element as well if * necessary. * * @private * @param {integer|string} resID - * @param {string} [type] - the type of resource (either 'xml', 'scss' or 'js') + * @param {string} [type] - the type of resource (either 'xml' or 'scss') */ _displayResource: function (resID, type) { if (type) { @@ -378,29 +359,14 @@ var ViewEditor = Widget.extend({ } this.aceEditor.setSession(editingSession); - var isCustomized = false; if (this.currentType === 'xml') { this.$viewID.text(_.str.sprintf(_t("Template ID: %s"), this.views[resID].key)); - } else if (this.currentType === 'scss') { - isCustomized = this.scss[resID].customized; - this.$viewID.text(_.str.sprintf(_t("SCSS file: %s"), resID)); } else { - isCustomized = this.js[resID].customized; - this.$viewID.text(_.str.sprintf(_t("JS file: %s"), resID)); + this.$viewID.text(_.str.sprintf(_t("SCSS file: %s"), resID)); } this.$lists[this.currentType].select2('val', resID); - this.$resetButton.toggleClass('d-none', this.currentType === 'xml' || !isCustomized); - - // TODO the warning message is always shown for XML templates but: - // 1) We have to implement a way to be able to reset XML templates - // otherwise the warning message is not accurate - // 2) We should be able to detect if the XML template is customized to - // not show the warning in that case - this.$warningMessage.toggleClass('d-none', - this.currentType !== 'xml' && (resID.indexOf('/user_custom_') >= 0 || isCustomized)); - - this.aceEditor.resize(true); + this.$resetButton.toggleClass('d-none', this.currentType === 'xml' || !this.scss[resID].customized); }, /** * Formats the current resource being vizualized. @@ -432,15 +398,14 @@ var ViewEditor = Widget.extend({ * is loading the activate views, index them and build their hierarchy. * * @private - * @returns {Promise} + * @returns {Deferred} */ _loadResources: function () { // Reset resources - this.resources = {xml: {}, scss: {}, js: {}}; - this.editingSessions = {xml: {}, scss: {}, js: {}}; + this.resources = {xml: {}, scss: {}}; + this.editingSessions = {xml: {}, scss: {}}; this.views = this.resources.xml; this.scss = this.resources.scss; - this.js = this.resources.js; // Load resources return this._rpc({ @@ -449,15 +414,12 @@ var ViewEditor = Widget.extend({ key: this.viewKey, get_views: !this.options.doNotLoadViews, get_scss: !this.options.doNotLoadSCSS, - get_js: !this.options.doNotLoadJS, bundles: this.options.includeBundles, - bundles_restriction: this.options.filesFilter === 'all' ? [] : this.options.defaultBundlesRestriction, - only_user_custom_files: this.options.filesFilter === 'custom', + bundles_restriction: this.options.includeAllSCSS ? [] : this.options.defaultBundlesRestriction, }, }).then((function (resources) { _processViews.call(this, resources.views || []); - _processJSorSCSS.call(this, resources.scss || [], 'scss'); - _processJSorSCSS.call(this, resources.js || [], 'js'); + _processSCSS.call(this, resources.scss || []); }).bind(this)); function _processViews(views) { @@ -498,24 +460,20 @@ var ViewEditor = Widget.extend({ }); } - function _processJSorSCSS(data, type) { - // The received scss or js data is already sorted by bundle and DOM order - if (type === 'scss') { - this.sortedSCSS = data; - } else { - this.sortedJS = data; - } + function _processSCSS(scss) { + // The received scss data is already sorted by bundle and DOM order + this.sortedSCSS = scss; // Store the URL ungrouped by bundle and use the URL as key (resource ID) - var resources = type === 'scss' ? this.scss : this.js; - _.each(data, function (bundleInfos) { + var self = this; + _.each(scss, function (bundleInfos) { _.each(bundleInfos[1], function (info) { info.bundle_xmlid = bundleInfos[0].xmlid; }); - _.extend(resources, _.indexBy(bundleInfos[1], 'url')); + _.extend(self.scss, _.indexBy(bundleInfos[1], 'url')); }); } }, /** - * Forces the view/scss/js file identified by its ID/URL to be reset to the way + * Forces the view/scss file identified by its ID/URL to be reset to the way * it was before the user started editing it. * * @todo views reset is not supported yet @@ -523,57 +481,59 @@ var ViewEditor = Widget.extend({ * @private * @param {integer|string} [resID] (default to the currently selected one) * @param {string} [type] (default to the currently selected one) - * @returns {Promise} + * @returns {Deferred} */ _resetResource: function (resID, type) { resID = resID || this._getSelectedResource(); type = type || this.currentType; if (this.currentType === 'xml') { - return Promise.reject(_t("Reseting views is not supported yet")); + return $.Defered().reject(_t("Reseting views is not supported yet")); } else { - var resource = type === 'scss' ? this.scss[resID] : this.js[resID]; return this._rpc({ - route: '/web_editor/reset_asset', + route: '/web_editor/reset_scss', params: { url: resID, - bundle_xmlid: resource.bundle_xmlid, + bundle_xmlid: this.scss[resID].bundle_xmlid, }, }); } }, /** - * Saves a unique SCSS or JS file. + * Saves an unique SCSS file. * * @private * @param {Object} session - contains the 'id' (url) and the 'text' of the - * SCSS or JS file to save. - * @return {Promise} status indicates if the save is finished or if an + * SCSS file to save. + * @return {Deferred} status indicates if the save is finished or if an * error occured. */ - _saveSCSSorJS: function (session) { + _saveSCSS: function (session) { + var def = $.Deferred(); + var self = this; - var sessionIdEndsWithJS = _.string.endsWith(session.id, '.js'); - var bundleXmlID = sessionIdEndsWithJS ? this.js[session.id].bundle_xmlid : this.scss[session.id].bundle_xmlid; - var fileType = sessionIdEndsWithJS ? 'js' : 'scss'; - return self._rpc({ - route: '/web_editor/save_asset', + this._rpc({ + route: '/web_editor/save_scss', params: { url: session.id, - bundle_xmlid: bundleXmlID, + bundle_xmlid: this.scss[session.id].bundle_xmlid, content: session.text, - file_type: fileType, }, }).then(function () { - self._toggleDirtyInfo(session.id, fileType, false); + self._toggleDirtyInfo(session.id, 'scss', false); + def.resolve(); + }, function (source, error) { + def.reject(session, error); }); + + return def; }, /** * Saves every resource that has been modified. If one cannot be saved, none * is saved and an error message is displayed. * * @private - * @return {Promise} status indicates if the save is finished or if an + * @return {Deferred} status indicates if the save is finished or if an * error occured. */ _saveResources: function () { @@ -602,7 +562,7 @@ var ViewEditor = Widget.extend({ } } }).bind(this)); - if (errorFound) return Promise.reject(errorFound); + if (errorFound) return $.Deferred().reject(errorFound); var defs = []; var mutex = new concurrency.Mutex(); @@ -611,15 +571,13 @@ var ViewEditor = Widget.extend({ _toSave = _.sortBy(_toSave, 'id').reverse(); _.each(_toSave, function (session) { defs.push(mutex.exec(function () { - return (type === 'xml' ? self._saveView(session) : self._saveSCSSorJS(session)); + return (type === 'xml' ? self._saveView(session) : self._saveSCSS(session)); })); }); }).bind(this)); - var self = this; - return Promise.all(defs).guardedCatch(function (results) { - var error = results[1]; - Dialog.alert(self, '', { + return $.when.apply($, defs).fail((function (session, error) { + Dialog.alert(this, '', { title: _t("Server error"), $content: $('
').html( _t("A server error occured. Please check you correctly signed in and that the file you are saving is correctly formatted.") @@ -627,32 +585,34 @@ var ViewEditor = Widget.extend({ + error ) }); - }); + }).bind(this)); }, /** * Saves an unique XML view. * * @private * @param {Object} session - the 'id' and the 'text' of the view to save. - * @returns {Promise} status indicates if the save is finished or if an + * @returns {Deferred} status indicates if the save is finished or if an * error occured. */ _saveView: function (session) { + var def = $.Deferred(); + var self = this; - return new Promise(function (resolve, reject) { - self._rpc({ - model: 'ir.ui.view', - method: 'write', - args: [[session.id], {arch: session.text}], - }, { - noContextKeys: 'lang', - }).then(function () { - self._toggleDirtyInfo(session.id, 'xml', false); - resolve(); - }, function (source, error) { - reject(session, error); - }); + this._rpc({ + model: 'ir.ui.view', + method: 'write', + args: [[session.id], {arch: session.text}], + }, { + noContextKeys: 'lang', + }).then(function () { + self._toggleDirtyInfo(session.id, 'xml', false); + def.resolve(); + }, function (source, error) { + def.reject(session, error); }); + + return def; }, /** * Shows a line which produced an error. Red color is added to the editor, @@ -704,11 +664,11 @@ var ViewEditor = Widget.extend({ } }, /** - * Switches to the SCSS, XML or JS edition. Calling this method will adapt all + * Switches to the SCSS or XML edition. Calling this method will adapt all * DOM elements to keep the editor consistent. * * @private - * @param {string} type - either 'xml', 'scss' or 'js' + * @param {string} type - either 'xml' or 'scss' */ _switchType: function (type) { this.currentType = type; @@ -716,10 +676,9 @@ var ViewEditor = Widget.extend({ _.each(this.$lists, function ($list, _type) { $list.toggleClass('d-none', type !== _type); }); this.$lists[type].change(); - this.$includeBundlesArea.toggleClass('d-none', this.currentType !== 'xml' || !config.isDebug()); - this.$includeAllSCSSArea.toggleClass('d-none', this.currentType !== 'scss' || !config.isDebug()); - this.$includeAllSCSSArea.find('[data-value="restricted"]').toggleClass('d-none', this.options.defaultBundlesRestriction.length === 0); - this.$formatButton.toggleClass('d-none', this.currentType !== 'xml'); + this.$includeBundlesArea.toggleClass('d-none', this.currentType === 'scss' || !session.debug); + this.$includeAllSCSSArea.toggleClass('d-none', this.currentType === 'xml' || !session.debug || this.options.defaultBundlesRestriction.length === 0); + this.$formatButton.toggleClass('d-none', this.currentType === 'scss'); }, /** * Updates the select option DOM element associated with a particular resID @@ -764,10 +723,21 @@ var ViewEditor = Widget.extend({ }); this.$lists.scss.empty(); - _populateList(this.sortedSCSS, this.$lists.scss, 5); - - this.$lists.js.empty(); - _populateList(this.sortedJS, this.$lists.js, 3); + _.each(this.sortedSCSS, function (bundleInfos) { + var $optgroup = $('', { + label: bundleInfos[0].name, + }).appendTo(self.$lists.scss); + _.each(bundleInfos[1], function (scssInfo) { + var name = scssInfo.url.substring(_.lastIndexOf(scssInfo.url, '/') + 1, scssInfo.url.length - 5); + $optgroup.append($('', { - label: bundleInfos[0].name, - }).appendTo($list); - _.each(bundleInfos[1], function (dataInfo) { - var name = dataInfo.url.substring(_.lastIndexOf(dataInfo.url, '/') + 1, dataInfo.url.length - lettersToRemove); - $optgroup.append($('