summaryrefslogtreecommitdiff
path: root/addons/website/static/src/js/content
diff options
context:
space:
mode:
authorRobot Odoo <xmo+roboto@odoo.com>2019-01-17 19:17:22 +0100
committerGitHub <noreply@github.com>2019-01-17 19:17:22 +0100
commit22efdfbecc1e8c688000be98903a5a355d1ece8a (patch)
treee097625171b2b67ed5f5288ced67446608b9f633 /addons/website/static/src/js/content
parentf50fbc6d168f0d17fa1e932b4ecc968a92b02b65 (diff)
parenteb0ea8efbe932c140302e9f8da33a839bcd73146 (diff)
[IMP] web_editor,*: Refactoring the wysiwyg editor and 'html' field
_Task #1878502_ - The refactoring allows for the update of the _Summernote_ library. - A new structure was created by transforming all the plugins in the library using the odoo inheritance system. Plugins are easier to implement with the `AbstractPlugin` to add Odoo behaviors. - The cleanup allowed us to remove some of the code that was in `web_editor` but only used in `website` or in `mass_mailing`. Some parts are still in `web_editor` but will be moved at a later time. - The refactoring also had to be done in order to create a set of unit tests. Each behavior can be tested, including the behaviors performed as a consequence of keyboard interactions (for instance: Enter, Tab...). - From now on, the methods of the library (in this case _Summernote_) can no longer be called by other modules or files. Only the `wysiwyg` widgets can access it, to simplify the updating process. The `wysiwyg` object serves as an interface. A lot of tests are done, ensuring the consistency of outside behavior. - Depending on the options the snippets will be loaded or not, the editor will be in an iframe or not... all of this is transparent from the outside. - Regarding iframes, all controllers related to editing have been removed: the new API no longer needs them. This speeds up loading, eases testing and removes complexity for the same features. **PUBLIC FEATURES** There are several public methods on the `Wysiwyg` class: - `Wysiwyg.prepare (WidgetParent)` which returns a deferred resolved when the library (xml, lazy, assets...) is loaded. - `Wysiwyg.getRange (DOM)` returns the range (selection in the dom) - `Wysiwyg.setRange (startNode, startOffset, endNode, endOffset)` which creates a range (selection in the dom) - `Wysiwyg.setRangeFromNode (DOM, options)` that creates a range from an element (option available to select all, start or end) A jQuery selector was added: `:o_editable`, which indicates whether the current element is editable. That is, if it is contained in a tag with the attribute `contentEditable = "true"` or in a tag with the class `o_editable`. Several methods are also present: - `focusIn`: makes a focus and places the cursor at the beginning of the element - `focusInEnd`: makes a focus and places the cursor at the end of the element - `selectContent`: makes a focus and selects the content **HTML FIELD** The HTML field can receive different options: - `style-inline:` {boolean} transforms a class into an inline style when saving and vice versa when reading. - `no-attachment`: {boolean} prevents the use of attachments (in media dialog) - `cssEdit`: {xml_id} to use a template containing the css to loaded in an iframe when editing - `cssReadonly`: {xml_id} to use a template containing the css to load into an iframe when viewing in readonly - `snippets`: {xml_id} snippets template (can be used with or without `cssEdit`) - `wrapper`: {template} qweb static template (containing a tag: `id = "wrapper"`) that will include the content during editing (removed on save) **MASS MAILING** A widget was created for `mass mailing`. There are now two fields: `body_html` and `body_arch`. `body_arch` contains the code with the class without conversion into inline style, useful when editing and one with the inline style that is visible in readonly mode and sent by email. Advantage: no spreading errors, able to update css/theme, able to do more changes when converting to inline style so that a maximum of mail clients have an impeccable rendering. **REMAINING KNOW ISSUES** These will be fixed in a subsequent commit. - A few committed changes made during the development of this refactoring were lost in the process and should be reapplied. - Some minor (mostly range) issues remain with Firefox. -- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr closes odoo/odoo#29775
Diffstat (limited to 'addons/website/static/src/js/content')
-rw-r--r--addons/website/static/src/js/content/base.js28
-rw-r--r--addons/website/static/src/js/content/body_manager.js60
-rw-r--r--addons/website/static/src/js/content/context.js24
-rw-r--r--addons/website/static/src/js/content/ready.js7
-rw-r--r--addons/website/static/src/js/content/snippets.animation.js5
-rw-r--r--addons/website/static/src/js/content/website_root.js10
6 files changed, 131 insertions, 3 deletions
diff --git a/addons/website/static/src/js/content/base.js b/addons/website/static/src/js/content/base.js
new file mode 100644
index 00000000000..bc5aa9892d0
--- /dev/null
+++ b/addons/website/static/src/js/content/base.js
@@ -0,0 +1,28 @@
+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 {
+ /**
+ * If a widget needs to be instantiated on page loading, it needs to wait
+ * for appropriate resources in order to be loaded. This function returns a
+ * Deferred that 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's 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());
+ },
+};
+});
diff --git a/addons/website/static/src/js/content/body_manager.js b/addons/website/static/src/js/content/body_manager.js
new file mode 100644
index 00000000000..1045e94a65a
--- /dev/null
+++ b/addons/website/static/src/js/content/body_manager.js
@@ -0,0 +1,60 @@
+odoo.define('web_editor.BodyManager', function (require) {
+'use strict';
+
+var weContext = require('web_editor.context');
+var rootWidget = require('web_editor.root_widget');
+var ServiceProviderMixin = require('web.ServiceProviderMixin');
+var session = require('web.session');
+
+/**
+ * Element which is designed to be unique and that will be the top-most element
+ * in the widget hierarchy. So, all other widgets will be indirectly linked to
+ * this Class instance. Its main role will be to retrieve RPC demands from its
+ * children and handle them.
+ */
+var BodyManager = rootWidget.RootWidget.extend(ServiceProviderMixin, {
+ /**
+ * @constructor
+ */
+ init: function () {
+ this._super.apply(this, arguments);
+ ServiceProviderMixin.init.call(this);
+ },
+ /**
+ * @override
+ */
+ willStart: function () {
+ return $.when(
+ this._super.apply(this, arguments),
+ session.is_bound
+ );
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * Automatically add the web_editor context.
+ *
+ * @override
+ */
+ _call_service: function (event) {
+ if (event.data.service === 'ajax' && event.data.method === 'rpc') {
+ var route = event.data.args[0];
+ if (_.str.startsWith(route, '/web/dataset/call_kw/')) {
+ var params = event.data.args[1];
+ var options = event.data.args[2];
+ params.kwargs.context = _.extend({}, weContext.get(), params.kwargs.context || {});
+ if (options) {
+ params.kwargs.context = _.omit(params.kwargs.context, options.noContextKeys);
+ event.data.args[2] = _.omit(options, 'noContextKeys');
+ }
+ params.kwargs.context = JSON.parse(JSON.stringify(params.kwargs.context));
+ }
+ }
+ return ServiceProviderMixin._call_service.apply(this, arguments);
+ },
+});
+return BodyManager;
+});
diff --git a/addons/website/static/src/js/content/context.js b/addons/website/static/src/js/content/context.js
new file mode 100644
index 00000000000..b76e7cb403d
--- /dev/null
+++ b/addons/website/static/src/js/content/context.js
@@ -0,0 +1,24 @@
+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,
+};
+});
diff --git a/addons/website/static/src/js/content/ready.js b/addons/website/static/src/js/content/ready.js
new file mode 100644
index 00000000000..214e7c2c34f
--- /dev/null
+++ b/addons/website/static/src/js/content/ready.js
@@ -0,0 +1,7 @@
+odoo.define('web_editor.ready', function (require) {
+'use strict';
+
+var base = require('web_editor.base');
+
+return base.ready();
+});
diff --git a/addons/website/static/src/js/content/snippets.animation.js b/addons/website/static/src/js/content/snippets.animation.js
index 587cbca61d4..65729146dfb 100644
--- a/addons/website/static/src/js/content/snippets.animation.js
+++ b/addons/website/static/src/js/content/snippets.animation.js
@@ -494,7 +494,9 @@ var Animation = Widget.extend({
/**
* The registry object contains the list of available animations.
*/
-var registry = {};
+var Registry = require('web.Registry');
+var registryObject = new Registry();
+var registry = registryObject.map;
registry.slider = Animation.extend({
selector: '.carousel',
@@ -1108,5 +1110,6 @@ registry.anchorSlide = Animation.extend({
return {
Class: Animation,
registry: registry,
+ registryObject: registryObject,
};
});
diff --git a/addons/website/static/src/js/content/website_root.js b/addons/website/static/src/js/content/website_root.js
index cf4812b28b9..b9f3bfe25e9 100644
--- a/addons/website/static/src/js/content/website_root.js
+++ b/addons/website/static/src/js/content/website_root.js
@@ -56,6 +56,7 @@ var WebsiteRoot = BodyManager.extend({
init: function () {
this._super.apply(this, arguments);
this.animations = [];
+ sAnimation.registryObject.onAdd(this._startMissingAnimations.bind(this));
},
/**
* @override
@@ -141,7 +142,7 @@ var WebsiteRoot = BodyManager.extend({
_startAnimations: function (editableMode, $from) {
var self = this;
- editableMode = editableMode || false;
+ this.startInEditableMode = editableMode || false;
if ($from === undefined) {
$from = this.$('#wrapwrap');
}
@@ -153,7 +154,7 @@ var WebsiteRoot = BodyManager.extend({
var $target = $from.find(selector).addBack(selector);
var defs = _.map($target, function (el) {
- var animation = new Animation(self, editableMode);
+ var animation = new Animation(self, self.startInEditableMode);
self.animations.push(animation);
return animation.attachTo($(el));
});
@@ -161,6 +162,11 @@ var WebsiteRoot = BodyManager.extend({
});
return $.when.apply($, defs);
},
+ _startMissingAnimations: function () {
+ if (this.animations.length) {
+ this._startAnimations(this.startInEditableMode);
+ }
+ },
/**
* Destroys all animation instances. Especially needed before saving while
* in edition mode for example.