diff options
| author | Christophe Matthieu <chm@odoo.com> | 2018-12-28 17:49:21 +0000 |
|---|---|---|
| committer | Christophe Matthieu <chm@odoo.com> | 2019-01-17 08:40:21 +0000 |
| commit | de444335b7da30a2ab3a4c5b92c9731a023f3f07 (patch) | |
| tree | 40ceda25aa7157ae0923cf567a448d685798e911 /addons/web_editor/static/src/js | |
| parent | 17c6d00531c55d0c392ddb6916bf1957bc490c1c (diff) | |
[REF] web_editor,*: Move and rename files to prepare refactoring
The refactoring of the wysiwyg editor and 'html' field allows us to
move 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.
Diffstat (limited to 'addons/web_editor/static/src/js')
11 files changed, 497 insertions, 721 deletions
diff --git a/addons/web_editor/static/src/js/editor/transcoder.js b/addons/web_editor/static/src/js/backend/convert_inline.js index 85903f6b4eb..85903f6b4eb 100644 --- a/addons/web_editor/static/src/js/editor/transcoder.js +++ b/addons/web_editor/static/src/js/backend/convert_inline.js diff --git a/addons/web_editor/static/src/js/backend/fields.js b/addons/web_editor/static/src/js/backend/field_html.js index 7742f49bcde..7742f49bcde 100644 --- a/addons/web_editor/static/src/js/backend/fields.js +++ b/addons/web_editor/static/src/js/backend/field_html.js diff --git a/addons/web_editor/static/src/js/widgets/ace.js b/addons/web_editor/static/src/js/common/ace.js index f3c47f30050..f3c47f30050 100644 --- a/addons/web_editor/static/src/js/widgets/ace.js +++ b/addons/web_editor/static/src/js/common/ace.js diff --git a/addons/web_editor/static/src/js/content/body_manager.js b/addons/web_editor/static/src/js/content/body_manager.js deleted file mode 100644 index 1045e94a65a..00000000000 --- a/addons/web_editor/static/src/js/content/body_manager.js +++ /dev/null @@ -1,60 +0,0 @@ -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/web_editor/static/src/js/editor/editor.js b/addons/web_editor/static/src/js/editor/editor.js deleted file mode 100644 index 63fa8c1c667..00000000000 --- a/addons/web_editor/static/src/js/editor/editor.js +++ /dev/null @@ -1,286 +0,0 @@ -odoo.define('web_editor.editor', function (require) { -'use strict'; - -var Dialog = require('web.Dialog'); -var Widget = require('web.Widget'); -var core = require('web.core'); -var rte = require('web_editor.rte'); -var snippetsEditor = require('web_editor.snippet.editor'); - -var _t = core._t; - -var EditorMenuBar = Widget.extend({ - template: 'web_editor.editorbar', - xmlDependencies: ['/web_editor/static/src/xml/editor.xml'], - events: { - 'click button[data-action=save]': '_onSaveClick', - 'click button[data-action=cancel]': '_onCancelClick', - }, - custom_events: { - request_history_undo_record: '_onHistoryUndoRecordRequest', - request_save: '_onSaveRequest', - }, - - /** - * Initializes RTE and snippets menu. - * - * @constructor - */ - init: function (parent) { - var self = this; - var res = this._super.apply(this, arguments); - this.rte = new rte.Class(this); - this.rte.on('rte:start', this, function () { - self.trigger('rte:start'); - }); - - // Snippets edition - var $editable = this.rte.editable(); - window.__EditorMenuBar_$editable = $editable; // TODO remove this hack asap - this.snippetsMenu = new snippetsEditor.Class(this, $editable); - - return res; - }, - /** - * @override - */ - start: function () { - var self = this; - var defs = [this._super.apply(this, arguments)]; - - core.bus.on('editor_save_request', this, this.save); - core.bus.on('editor_discard_request', this, this.cancel); - - $('.dropdown-toggle').dropdown(); - - $(document).on('keyup', function (event) { - if ((event.keyCode === 8 || event.keyCode === 46)) { - var $target = $(event.target).closest('.o_editable'); - if (!$target.is(':has(*:not(p):not(br))') && !$target.text().match(/\S/)) { - $target.empty(); - } - } - }); - $(document).on('click', '.note-editable', function (ev) { - ev.preventDefault(); - }); - $(document).on('submit', '.note-editable form .btn', function (ev) { - ev.preventDefault(); // Disable form submition in editable mode - }); - $(document).on('hide.bs.dropdown', '.dropdown', function (ev) { - // Prevent dropdown closing when a contenteditable children is focused - if (ev.originalEvent - && $(ev.target).has(ev.originalEvent.target).length - && $(ev.originalEvent.target).is('[contenteditable]')) { - ev.preventDefault(); - } - }); - - this.rte.start(); - - var flag = false; - window.onbeforeunload = function (event) { - if (rte.history.getEditableHasUndo().length && !flag) { - flag = true; - _.defer(function () { flag=false; }); - return _t('This document is not saved!'); - } - }; - - // Snippets menu - defs.push(this.snippetsMenu.insertAfter(this.$el)); - this.rte.editable().find('*').off('mousedown mouseup click'); - - return $.when.apply($, defs).then(function () { - self.trigger_up('edit_mode'); - }); - }, - /** - * @override - */ - destroy: function () { - this._super.apply(this, arguments); - core.bus.off('editor_save_request', this, this._onSaveRequest); - core.bus.off('editor_discard_request', this, this._onDiscardRequest); - }, - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - /** - * Asks the user if he really wants to discard its changes (if there are - * some of them), then simply reload the page if he wants to. - * - * @param {boolean} [reload=true] - * true if the page has to be reloaded when the user answers yes - * (do nothing otherwise but add this to allow class extension) - * @returns {Deferred} - */ - cancel: function (reload) { - var self = this; - var def = $.Deferred(); - if (!rte.history.getEditableHasUndo().length) { - def.resolve(); - } else { - var confirm = Dialog.confirm(this, _t("If you discard the current edition, all unsaved changes will be lost. You can cancel to return to the edition mode."), { - confirm_callback: def.resolve.bind(def), - }); - confirm.on('closed', def, def.reject); - } - return def.then(function () { - if (reload !== false) { - window.onbeforeunload = null; - return self._reload(); - } - }); - }, - /** - * Asks the snippets to clean themself, then saves the page, then reloads it - * if asked to. - * - * @param {boolean} [reload=true] - * true if the page has to be reloaded after the save - * @returns {Deferred} - */ - save: function (reload) { - var self = this; - var defs = []; - this.trigger_up('ready_to_save', {defs: defs}); - return $.when.apply($, defs).then(function () { - self.snippetsMenu.cleanForSave(); - return self._saveCroppedImages(); - }).then(function () { - return self.rte.save(); - }).then(function () { - if (reload !== false) { - return self._reload(); - } - }); - }, - - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- - - /** - * Reloads the page in non-editable mode, with the right scrolling. - * - * @private - * @returns {Deferred} (never resolved, the page is reloading anyway) - */ - _reload: function () { - window.location.hash = 'scrollTop=' + window.document.body.scrollTop; - if (window.location.search.indexOf('enable_editor') >= 0) { - window.location.href = window.location.href.replace(/&?enable_editor(=[^&]*)?/g, ''); - } else { - window.location.reload(true); - } - return $.Deferred(); - }, - /** - * @private - */ - _saveCroppedImages: function () { - var self = this; - var defs = _.map(this.rte.editable().find('.o_cropped_img_to_save'), function (croppedImg) { - var $croppedImg = $(croppedImg); - $croppedImg.removeClass('o_cropped_img_to_save'); - - var resModel = $croppedImg.data('crop:resModel'); - var resID = $croppedImg.data('crop:resID'); - var cropID = $croppedImg.data('crop:id'); - var mimetype = $croppedImg.data('crop:mimetype'); - var originalSrc = $croppedImg.data('crop:originalSrc'); - - var datas = $croppedImg.attr('src').split(',')[1]; - - if (!cropID) { - var name = originalSrc + '.crop'; - return self._rpc({ - model: 'ir.attachment', - method: 'create', - args: [{ - res_model: resModel, - res_id: resID, - name: name, - datas_fname: name, - datas: datas, - mimetype: mimetype, - url: originalSrc, // To save the original image that was cropped - }], - }).then(function (attachmentID) { - return self._rpc({ - model: 'ir.attachment', - method: 'generate_access_token', - args: [[attachmentID]], - }).then(function (access_token) { - $croppedImg.attr('src', '/web/image/' + attachmentID + '?access_token=' + access_token[0]); - }); - }); - } else { - return self._rpc({ - model: 'ir.attachment', - method: 'write', - args: [[cropID], {datas: datas}], - }); - } - }); - return $.when.apply($, defs); - }, - - //-------------------------------------------------------------------------- - // Handlers - //-------------------------------------------------------------------------- - - /** - * Called when the "Discard" button is clicked -> discards the changes. - * - * @private - */ - _onCancelClick: function () { - this.cancel(); - }, - /** - * Called when an element askes to record an history undo -> records it. - * - * @private - * @param {OdooEvent} ev - */ - _onHistoryUndoRecordRequest: function (ev) { - this.rte.historyRecordUndo(ev.data.$target, ev.data.event); - }, - /** - * Called when the "Save" button is clicked -> saves the changes. - * - * @private - */ - _onSaveClick: function () { - this.save(); - }, - /** - * Called when a discard request is received -> discard the page content - * changes. - * - * @private - * @param {OdooEvent} ev - */ - _onDiscardRequest: function (ev) { - this.cancel(ev.data.reload).then(ev.data.onSuccess, ev.data.onFailure); - }, - /** - * Called when a save request is received -> saves the page content. - * - * @private - * @param {OdooEvent} ev - */ - _onSaveRequest: function (ev) { - ev.stopPropagation(); - this.save(ev.data.reload).then(ev.data.onSuccess, ev.data.onFailure); - }, -}); - -return { - Class: EditorMenuBar, -}; -}); diff --git a/addons/web_editor/static/src/js/editor/translator.js b/addons/web_editor/static/src/js/editor/translator.js deleted file mode 100644 index c6cadb40562..00000000000 --- a/addons/web_editor/static/src/js/editor/translator.js +++ /dev/null @@ -1,375 +0,0 @@ -odoo.define('web_editor.translate', function (require) { -'use strict'; - -var core = require('web.core'); -var Dialog = require('web.Dialog'); -var localStorage = require('web.local_storage'); -var Widget = require('web.Widget'); -var weContext = require('web_editor.context'); -var rte = require('web_editor.rte'); -var weWidgets = require('web_editor.widget'); -var Dialog = require('web.Dialog'); - -var _t = core._t; - -var localStorageNoDialogKey = 'website_translator_nodialog'; - -var RTETranslatorWidget = rte.Class.extend({ - - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- - - /** - * If the element holds a translation, saves it. Otherwise, fallback to the - * standard saving but with the lang kept. - * - * @override - */ - _saveElement: function ($el, context, withLang) { - var self = this; - if ($el.data('oe-translation-id')) { - return this._rpc({ - model: 'ir.translation', - method: 'save_html', - args: [ - [+$el.data('oe-translation-id')], - this._getEscapedElement($el).html() - ], - context: context, - }).fail(function (error) { - Dialog.alert(null, error.data.message); - }); - } - return this._super($el, context, withLang === undefined ? true : withLang); - }, -}); - -var AttributeTranslateDialog = weWidgets.Dialog.extend({ - /** - * @constructor - */ - init: function (parent, options, node) { - this._super(parent, _.extend({ - title: _t("Translate Attribute"), - buttons: [ - {text: _t("Close"), classes: 'btn-primary', click: this.save} - ], - }, options || {})); - this.$target = $(node); - this.translation = $(node).data('translation'); - }, - /** - * @override - */ - start: function () { - var self = this; - var $group = $('<div/>', {class: 'form-group'}).appendTo(this.$el); - _.each(this.translation, function (node, attr) { - var $node = $(node); - var $label = $('<label class="col-form-label"></label>').text(attr); - var $input = $('<input class="form-control"/>').val($node.html()); - $input.on('change keyup', function () { - var value = $input.val(); - $node.html(value).trigger('change', node); - $node.data('$node').attr($node.data('attribute'), value).trigger('translate'); - self.trigger_up('rte_change', {target: node}); - }); - $group.append($label).append($input); - }); - return this._super.apply(this, arguments); - } -}); - -var TranslatorInfoDialog = Dialog.extend({ - template: 'web_editor.TranslatorInfoDialog', - xmlDependencies: Dialog.prototype.xmlDependencies.concat( - ['/web_editor/static/src/xml/translator.xml'] - ), - - /** - * @constructor - */ - init: function (parent, options) { - this._super(parent, _.extend({ - title: _t("Translation Info"), - buttons: [ - {text: _t("Ok, never show me this again"), classes: 'btn-primary', close: true, click: this._onStrongOk.bind(this)}, - {text: _t("Ok"), close: true} - ], - }, options || {})); - }, - - //-------------------------------------------------------------------------- - // Handlers - //-------------------------------------------------------------------------- - - /** - * Called when the "strong" ok is clicked -> adapt localstorage to make sure - * the dialog is never displayed again. - * - * @private - */ - _onStrongOk: function () { - localStorage.setItem(localStorageNoDialogKey, true); - }, -}); - -var TranslatorMenuBar = Widget.extend({ - template: 'web_editor.editorbar', - xmlDependencies: ['/web_editor/static/src/xml/editor.xml'], - events: { - 'click [data-action="save"]': '_onSaveClick', - 'click [data-action="cancel"]': '_onCancelClick', - }, - custom_events: { - 'rte_change': '_onRTEChange', - }, - - /** - * @constructor - */ - init: function (parent, $target, lang) { - this._super.apply(this, arguments); - - var $edit = $target.find('[data-oe-translation-id], [data-oe-model][data-oe-id][data-oe-field]'); - $edit.filter(':has([data-oe-translation-id], [data-oe-model][data-oe-id][data-oe-field])').attr('data-oe-readonly', true); - this.$target = $edit.not('[data-oe-readonly]'); - var attrs = ['placeholder', 'title', 'alt']; - _.each(attrs, function (attr) { - $target.find('['+attr+'*="data-oe-translation-id="]').filter(':empty, input, select, textarea, img').each(function () { - var $node = $(this); - var translation = $node.data('translation') || {}; - var trans = $node.attr(attr); - var match = trans.match(/<span [^>]*data-oe-translation-id="([0-9]+)"[^>]*>(.*)<\/span>/); - var $trans = $(trans).addClass('d-none o_editable o_editable_translatable_attribute').appendTo('body'); - $trans.data('$node', $node).data('attribute', attr); - translation[attr] = $trans[0]; - $node.attr(attr, match[2]); - - var select2 = $node.data('select2'); - if (select2) { - select2.blur(); - $node.on('translate', function () { - select2.blur(); - }); - $node = select2.container.find('input'); - } - $node.addClass('o_translatable_attribute').data('translation', translation); - }); - }); - this.$target_attr = $target.find('.o_translatable_attribute'); - this.$target_attribute = $('.o_editable_translatable_attribute'); - - this.lang = lang || weContext.get().lang; - - this.rte = new RTETranslatorWidget(this, this._getRTEConfig); - }, - /** - * @override - */ - start: function () { - this.$('#web_editor-toolbars').remove(); - - var flag = false; - window.onbeforeunload = function (event) { - if ($('.o_editable.o_dirty').length && !flag) { - flag = true; - setTimeout(function () { - flag = false; - }, 0); - return _t('This document is not saved!'); - } - }; - this.$target.addClass('o_editable'); - this.rte.start(); - this.translations = []; - this._markTranslatableNodes(); - this.$el.show(); // TODO seems useless - this.trigger('edit'); - - if (!localStorage.getItem(localStorageNoDialogKey)) { - new TranslatorInfoDialog(this).open(); - } - - return this._super.apply(this, arguments); - }, - /** - * @override - */ - destroy: function () { - this._cancel(); - this._super.apply(this, arguments); - }, - - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- - - /** - * Leaves translation mode by hiding the translator bar. - * - * @todo should be destroyed? - * @private - */ - _cancel: function () { - var self = this; - this.rte.cancel(); - this.$target.each(function () { - $(this).html(self._getTranlationObject(this).value); - }); - this._unmarkTranslatableNode(); - this.trigger('cancel'); - this.$el.hide(); - window.onbeforeunload = null; - }, - /** - * Returns the RTE summernote configuration for translation mode. - * - * @private - * @param {jQuery} $editable - */ - _getRTEConfig: function ($editable) { - return { - airMode : true, - focus: false, - airPopover: $editable.data('oe-model') ? [ - ['history', ['undo', 'redo']], - ] : [ - ['font', ['bold', 'italic', 'underline', 'clear']], - ['fontsize', ['fontsize']], - ['color', ['color']], - ['history', ['undo', 'redo']], - ], - styleWithSpan: false, - inlinemedia : ['p'], - lang: 'odoo', - onChange: function (html, $editable) { - $editable.trigger('content_changed'); - }, - }; - }, - /** - * @private - */ - _getTranlationObject: function (node) { - var $node = $(node); - var id = +$node.data('oe-translation-id'); - if (!id) { - id = $node.data('oe-model')+','+$node.data('oe-id')+','+$node.data('oe-field'); - } - var trans = _.find(this.translations, function (trans) { - return trans.id === id; - }); - if (!trans) { - this.translations.push(trans = {'id': id}); - } - return trans; - }, - /** - * @private - */ - _markTranslatableNodes: function () { - var self = this; - this.$target.add(this.$target_attribute).each(function () { - var $node = $(this); - var trans = self._getTranlationObject(this); - trans.value = (trans.value ? trans.value : $node.html() ).replace(/[ \t\n\r]+/, ' '); - }); - this.$target.parent().prependEvent('click.translator', function (ev) { - if (ev.ctrlKey || !$(ev.target).is(':o_editable')) { - return; - } - ev.preventDefault(); - ev.stopPropagation(); - }); - - // attributes - - this.$target_attr.each(function () { - var $node = $(this); - var translation = $node.data('translation'); - _.each(translation, function (node, attr) { - var trans = self._getTranlationObject(node); - trans.value = (trans.value ? trans.value : $node.html() ).replace(/[ \t\n\r]+/, ' '); - $node.attr('data-oe-translation-state', (trans.state || 'to_translate')); - }); - }); - - this.$target_attr.prependEvent('mousedown.translator click.translator mouseup.translator', function (ev) { - if (ev.ctrlKey) { - return; - } - ev.preventDefault(); - ev.stopPropagation(); - if (ev.type !== 'mousedown') { - return; - } - - new AttributeTranslateDialog(self, {}, ev.target).open(); - }); - }, - /** - * Saves the translation and reloads the page to leave edit mode. - * - * @private - * @returns {Deferred} (never resolved as the page is reloading anyway) - */ - _save: function () { - return this.rte.save(weContext.get({lang: this.lang})).then(function () { - window.location.href = window.location.href.replace(/&?edit_translations(=[^&]*)?/g, ''); - return $.Deferred(); - }); - }, - /** - * @private - */ - _unmarkTranslatableNode: function () { - this.$target.removeClass('o_editable').removeAttr('contentEditable'); - this.$target.parent().off('.translator'); - this.$target_attr.off('.translator'); - }, - - //-------------------------------------------------------------------------- - // Handlers - //-------------------------------------------------------------------------- - - /** - * Called when the "cancel" button is clicked -> undo changes and leaves - * edition. - * - * @private - */ - _onCancelClick: function () { - this._cancel(); - }, - /** - * Called when text is edited -> make sure text is not messed up and mark - * the element as dirty. - * - * @private - * @param {OdooEvent} ev - */ - _onRTEChange: function (ev) { - var $node = $(ev.data.target); - $node.find('p').each(function () { // remove <p/> element which might have been inserted because of copy-paste - var $p = $(this); - $p.after($p.html()).remove(); - }); - var trans = this._getTranlationObject($node[0]); - $node.toggleClass('o_dirty', trans.value !== $node.html().replace(/[ \t\n\r]+/, ' ')); - }, - /** - * Called when the "save" button is clicked -> saves the translations. - * - * @private - */ - _onSaveClick: function () { - this._save(); - }, -}); - -return { - Class: TranslatorMenuBar, -}; -}); diff --git a/addons/web_editor/static/src/js/base.js b/addons/web_editor/static/src/js/wysiwyg/fonts.js index d69cace3100..d69cace3100 100644 --- a/addons/web_editor/static/src/js/base.js +++ b/addons/web_editor/static/src/js/wysiwyg/fonts.js diff --git a/addons/web_editor/static/src/js/wysiwyg/widgets/colorpicker_dialog.js b/addons/web_editor/static/src/js/wysiwyg/widgets/colorpicker_dialog.js new file mode 100644 index 00000000000..e8ed888e1af --- /dev/null +++ b/addons/web_editor/static/src/js/wysiwyg/widgets/colorpicker_dialog.js @@ -0,0 +1,497 @@ +odoo.define('web.colorpicker', function (require) { +'use strict'; + +var core = require('web.core'); +var utils = require('web.utils'); +var Dialog = require('web.Dialog'); + +var _t = core._t; + +var Colorpicker = Dialog.extend({ + xmlDependencies: (Dialog.prototype.xmlDependencies || []) + .concat(['/web/static/src/xml/colorpicker.xml']), + + template: 'web.colorpicker', + events: _.extend({}, Dialog.prototype.events || {}, { + 'mousedown .o_color_pick_area': '_onMouseDownPicker', + 'mousedown .o_color_slider': '_onMouseDownSlider', + 'mousedown .o_opacity_slider': '_onMouseDownOpacitySlider', + 'change .o_color_picker_inputs' : '_onChangeInputs', + }), + + /** + * @constructor + * @param {Widget} parent + * @param {Object} [options] + * @param {string} [options.defaultColor='#FF0000'] + */ + init: function (parent, options) { + options = options || {}; + + this._super(parent, _.extend({ + size: 'medium', + title: _t('Pick a color'), + buttons: [ + {text: _t('Choose'), classes: 'btn-primary', close: true, click: this._onFinalPick.bind(this)}, + {text: _t('Discard'), close: true}, + ], + }, options)); + + this.pickerFlag = false; + this.sliderFlag = false; + this.opacitySliderFlag = false; + this.colorComponents = {}; + + var self = this; + var $body = $(document.body); + $body.on('mousemove.colorpicker', _.throttle(function (ev) { + self._onMouseMovePicker(ev); + self._onMouseMoveSlider(ev); + self._onMouseMoveOpacitySlider(ev); + }, 10)); + $body.on('mouseup.colorpicker', _.throttle(function (ev) { + self.pickerFlag = false; + self.sliderFlag = false; + self.opacitySliderFlag = false; + }, 10)); + + this.options = _.clone(options); + }, + /** + * @override + */ + start: function () { + this.$colorpickerArea = this.$('.o_color_pick_area'); + this.$colorpickerPointer = this.$('.o_picker_pointer'); + this.$colorSlider = this.$('.o_color_slider'); + this.$colorSliderPointer = this.$('.o_slider_pointer'); + this.$opacitySlider = this.$('.o_opacity_slider'); + this.$opacitySliderPointer = this.$('.o_opacity_pointer'); + + var defaultColor = this.options.defaultColor || '#FF0000'; + var rgba = defaultColor.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+(?:\.\d+)?))?\)$/); + if (rgba) { + if (rgba[4] === undefined) { + rgba[4] = 1; + } + this._updateRgba(parseInt(rgba[1]), parseInt(rgba[2]), parseInt(rgba[3]), Math.round(parseFloat(rgba[4] * 100))); + } else { + this._updateHex(defaultColor); + this._updateOpacity(100); + } + this.opened().then(this._updateUI.bind(this)); + + return this._super.apply(this, arguments); + }, + /** + * @override + */ + destroy: function () { + this._super.apply(this, arguments); + $(document.body).off('.colorpicker'); + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * Updates input values, color preview, picker and slider pointer positions. + * + * @private + */ + _updateUI: function () { + var self = this; + + // Update inputs + _.each(this.colorComponents, function (value, color) { + self.$(_.str.sprintf('.o_%s_input', color)).val(value); + }); + + // Update preview + this.$('.o_color_preview').css('background-color', this.colorComponents.cssColor); + + // Update picker area and picker pointer position + this.$colorpickerArea.css('background-color', _.str.sprintf('hsl(%s, 100%%, 50%%)', this.colorComponents.hue)); + var top = (100 - this.colorComponents.lightness) * this.$colorpickerArea.height() / 100; + var left = this.colorComponents.saturation * this.$colorpickerArea.width() / 100; + this.$colorpickerPointer.css({ + top: (top - 5) + 'px', + left: (left - 5) + 'px', + }); + + // Update color slider position + var height = this.$colorSlider.height(); + var y = this.colorComponents.hue * height / 360; + this.$colorSliderPointer.css('top', Math.round(y - 2)); + + // Update opacity slider position + var heightOpacity = this.$opacitySlider.height(); + var z = heightOpacity * (1 - this.colorComponents.opacity / 100.0); + this.$opacitySliderPointer.css('top', Math.round(z - 2)); + + // Add gradient color on opacity slider + this.$opacitySlider.css('background', 'linear-gradient(' + this.colorComponents.hex + ' 0%, transparent 100%)'); + }, + /** + * Updates colors according to given hex value. Opacity is left unchanged. + * + * @private + * @param {string} hex - hexadecimal code + */ + _updateHex: function (hex) { + var rgb = Colorpicker.prototype.convertHexToRgb(hex); + if (!rgb) { + return; + } + _.extend(this.colorComponents, + {hex: hex}, + rgb, + Colorpicker.prototype.convertRgbToHsl(rgb.red, rgb.green, rgb.blue) + ); + this._updateCssColor(); + }, + /** + * Updates colors according to given RGB values. + * + * @private + * @param {integer} r + * @param {integer} g + * @param {integer} b + * @param {integer} [a] + */ + _updateRgba: function (r, g, b, a) { + var hex = Colorpicker.prototype.convertRgbToHex(r, g, b); + if (!hex) { + return; + } + _.extend(this.colorComponents, + {red: r, green: g, blue: b}, + a === undefined ? {} : {opacity: a}, + hex, + Colorpicker.prototype.convertRgbToHsl(r, g, b) + ); + this._updateCssColor(); + }, + /** + * Updates colors according to given HSL values. + * + * @private + * @param {integer} h + * @param {integer} s + * @param {integer} l + */ + _updateHsl: function (h, s, l) { + var rgb = Colorpicker.prototype.convertHslToRgb(h, s, l); + if (!rgb) { + return; + } + _.extend(this.colorComponents, + {hue: h, saturation: s, lightness: l}, + rgb, + Colorpicker.prototype.convertRgbToHex(rgb.red, rgb.green, rgb.blue) + ); + this._updateCssColor(); + }, + /** + * Updates color opacity. + * + * @private + * @param {integer} a + */ + _updateOpacity: function (a) { + if (a < 0 || a > 100) { + return; + } + _.extend(this.colorComponents, + {opacity: a} + ); + this._updateCssColor(); + }, + /** + * Updates css color representation. + * + * @private + */ + _updateCssColor: function () { + var cssColor = this.colorComponents.hex; + if (this.colorComponents.opacity !== 100) { + cssColor = _.str.sprintf('rgba(%s, %s, %s, %s)', + this.colorComponents.red, + this.colorComponents.green, + this.colorComponents.blue, + this.colorComponents.opacity / 100 + ); + } + _.extend(this.colorComponents, + {cssColor: cssColor} + ); + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * Updates color when the user starts clicking on the picker. + * + * @private + * @param {Event} ev + */ + _onMouseDownPicker: function (ev) { + this.pickerFlag = true; + this._onMouseMovePicker(ev); + }, + /** + * Updates saturation and lightness values on mouse drag over picker. + * + * @private + * @param {Event} ev + */ + _onMouseMovePicker: function (ev) { + if (!this.pickerFlag) { + return; + } + + var offset = this.$colorpickerArea.offset(); + var top = ev.pageY - offset.top; + var left = ev.pageX - offset.left; + var saturation = Math.round(100 * left / this.$colorpickerArea.width()); + var lightness = Math.round(100 * (this.$colorpickerArea.height() - top) / this.$colorpickerArea.height()); + saturation = utils.confine(saturation, 0, 100); + lightness = utils.confine(lightness, 0, 100); + + this._updateHsl(this.colorComponents.hue, saturation, lightness); + this._updateUI(); + }, + /** + * Updates color when user starts clicking on slider. + * + * @private + * @param {Event} ev + */ + _onMouseDownSlider: function (ev) { + this.sliderFlag = true; + this._onMouseMoveSlider(ev); + }, + /** + * Updates hue value on mouse drag over slider. + * + * @private + * @param {Event} ev + */ + _onMouseMoveSlider: function (ev) { + if (!this.sliderFlag) { + return; + } + + var y = ev.pageY - this.$colorSlider.offset().top; + var hue = Math.round(360 * y / this.$colorSlider.height()); + hue = utils.confine(hue, 0, 360); + + this._updateHsl(hue, this.colorComponents.saturation, this.colorComponents.lightness); + this._updateUI(); + }, + /** + * Updates opacity when user starts clicking on opacity slider. + * + * @private + * @param {Event} ev + */ + _onMouseDownOpacitySlider: function (ev) { + this.opacitySliderFlag = true; + this._onMouseMoveOpacitySlider(ev); + }, + /** + * Updates opacity value on mouse drag over opacity slider. + * + * @private + * @param {Event} ev + */ + _onMouseMoveOpacitySlider: function (ev) { + if (!this.opacitySliderFlag) { + return; + } + + var y = ev.pageY - this.$opacitySlider.offset().top; + var opacity = Math.round(100 * (1 - y / this.$opacitySlider.height())); + opacity = utils.confine(opacity, 0, 100); + + this._updateOpacity(opacity); + this._updateUI(); + }, + /** + * Called when input value is changed -> Updates UI: Set picker and slider + * position and set colors. + * + * @private + * @param {Event} ev + */ + _onChangeInputs: function (ev) { + switch ($(ev.target).data('colorMethod')) { + case 'hex': + this._updateHex(this.$('.o_hex_input').val()); + break; + case 'rgb': + this._updateRgba( + parseInt(this.$('.o_red_input').val()), + parseInt(this.$('.o_green_input').val()), + parseInt(this.$('.o_blue_input').val()) + ); + break; + case 'hsl': + this._updateHsl( + parseInt(this.$('.o_hue_input').val()), + parseInt(this.$('.o_saturation_input').val()), + parseInt(this.$('.o_lightness_input').val()) + ); + break; + case 'opacity': + this._updateOpacity(parseInt(this.$('.o_opacity_input').val())); + break; + } + this._updateUI(); + }, + /** + * @private + */ + _onFinalPick: function () { + this.trigger_up('colorpicker:saved', this.colorComponents); + }, + + //-------------------------------------------------------------------------- + // Static + //-------------------------------------------------------------------------- + + /** + * Converts Hexadecimal code to RGB. + * + * @static + * @param {string} hex - hexadecimal code + * @returns {Object|false} contains red, green and blue + */ + convertHexToRgb: function (hex) { + if (!/^#[0-9A-F]{6}$/i.test(hex)) { + return false; + } + + return { + red: parseInt(hex.substr(1, 2), 16), + green: parseInt(hex.substr(3, 2), 16), + blue: parseInt(hex.substr(5, 2), 16), + }; + }, + /** + * Converts RGB color to HSL. + * + * @static + * @param {integer} r + * @param {integer} g + * @param {integer} b + * @returns {Object|false} contains hue, saturation and lightness + */ + convertRgbToHsl: function (r, g, b) { + if (typeof(r) !== 'number' || isNaN(r) || r < 0 || r > 255 || + typeof(g) !== 'number' || isNaN(g) || g < 0 || g > 255 || + typeof(b) !== 'number' || isNaN(b) || b < 0 || b > 255) { + return false; + } + + var red = r / 255; + var green = g / 255; + var blue = b / 255; + var maxColor = Math.max(red, green, blue); + var minColor = Math.min(red, green, blue); + var delta = maxColor - minColor; + var hue = 0; + var saturation = 0; + var lightness = (maxColor + minColor) / 2; + if (delta) { + if (maxColor === red) { + hue = (green - blue) / delta; + } + if (maxColor === green) { + hue = 2 + (blue - red) / delta; + } + if (maxColor === blue) { + hue = 4 + (red - green) / delta; + } + if (maxColor) { + saturation = delta / (1 - Math.abs(2 * lightness - 1)); + } + } + hue = 60 * hue | 0; + return { + hue: hue < 0 ? hue += 360 : hue, + saturation: (saturation * 100) | 0, + lightness: (lightness * 100) | 0, + }; + }, + /** + * Converts HSL color to RGB. + * + * @static + * @param {integer} h + * @param {integer} s + * @param {integer} l + * @returns {Object|false} contains red, green and blue + */ + convertHslToRgb: function (h, s, l) { + if (typeof(h) !== 'number' || isNaN(h) || h < 0 || h > 360 || + typeof(s) !== 'number' || isNaN(s) || s < 0 || s > 100 || + typeof(l) !== 'number' || isNaN(l) || l < 0 || l > 100) { + return false; + } + var huePrime = h / 60; + var saturation = s / 100; + var lightness = l / 100; + var chroma = saturation * (1 - Math.abs(2 * lightness - 1)); + var secondComponent = chroma * (1 - Math.abs(huePrime % 2 - 1)); + var lightnessAdjustment = lightness - chroma/2; + var precision = 255; + chroma = (chroma + lightnessAdjustment) * precision | 0; + secondComponent = (secondComponent + lightnessAdjustment) * precision | 0; + lightnessAdjustment = lightnessAdjustment * precision | 0; + if (huePrime >= 0 && huePrime < 1) { + return {red: chroma, green: secondComponent, blue: lightnessAdjustment}; + } + if (huePrime >= 1 && huePrime < 2) { + return {red: secondComponent, green: chroma, blue: lightnessAdjustment}; + } + if (huePrime >= 2 && huePrime < 3) { + return {red: lightnessAdjustment, green: chroma, blue: secondComponent}; + } + if (huePrime >= 3 && huePrime < 4) { + return {red: lightnessAdjustment, green: secondComponent, blue: chroma}; + } + if (huePrime >= 4 && huePrime < 5) { + return {red: secondComponent, green: lightnessAdjustment, blue: chroma}; + } + if (huePrime >= 5 && huePrime <= 6) { + return {red: chroma, green: lightnessAdjustment, blue: secondComponent}; + } + }, + /** + * Converts RGB color to Hexadecimal code. + * + * @static + * @param {integer} r + * @param {integer} g + * @param {integer} b + * @returns {Object|false} contains hexadecimal code + */ + convertRgbToHex: function (r, g, b) { + if (typeof(r) !== 'number' || isNaN(r) || r < 0 || r > 255 || + typeof(g) !== 'number' || isNaN(g) || g < 0 || g > 255 || + typeof(b) !== 'number' || isNaN(b) || b < 0 || b > 255) { + return false; + } + var red = r < 16 ? '0' + r.toString(16) : r.toString(16); + var green = g < 16 ? '0' + g.toString(16) : g.toString(16); + var blue = b < 16 ? '0' + b.toString(16) : b.toString(16); + return {hex: _.str.sprintf('#%s%s%s', red, green, blue)}; + }, +}); + +return Colorpicker; +}); diff --git a/addons/web_editor/static/src/js/widgets/widgets.js b/addons/web_editor/static/src/js/wysiwyg/widgets/media.js index 9dad13a0b2b..9dad13a0b2b 100644 --- a/addons/web_editor/static/src/js/widgets/widgets.js +++ b/addons/web_editor/static/src/js/wysiwyg/widgets/media.js diff --git a/addons/web_editor/static/src/js/editor/snippets.editor.js b/addons/web_editor/static/src/js/wysiwyg_snippets/snippets.editor.js index fe8876468b5..fe8876468b5 100644 --- a/addons/web_editor/static/src/js/editor/snippets.editor.js +++ b/addons/web_editor/static/src/js/wysiwyg_snippets/snippets.editor.js diff --git a/addons/web_editor/static/src/js/editor/snippets.options.js b/addons/web_editor/static/src/js/wysiwyg_snippets/snippets.options.js index 97d3777b95f..97d3777b95f 100644 --- a/addons/web_editor/static/src/js/editor/snippets.options.js +++ b/addons/web_editor/static/src/js/wysiwyg_snippets/snippets.options.js |
