-
+
diff --git a/addons/web/static/src/core/utils/colors.js b/addons/web/static/src/core/utils/colors.js
new file mode 100644
index 00000000000..a06357dcfe7
--- /dev/null
+++ b/addons/web/static/src/core/utils/colors.js
@@ -0,0 +1,248 @@
+/** @odoo-module **/
+
+/**
+ * Converts RGB color components to HSL components.
+ *
+ * @static
+ * @param {integer} r - [0, 255]
+ * @param {integer} g - [0, 255]
+ * @param {integer} b - [0, 255]
+ * @returns {Object|false}
+ * - hue [0, 360[ (float)
+ * - saturation [0, 100] (float)
+ * - lightness [0, 100] (float)
+ */
+export function convertRgbToHsl(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;
+ return {
+ hue: hue < 0 ? hue + 360 : hue,
+ saturation: saturation * 100,
+ lightness: lightness * 100,
+ };
+};
+/**
+ * Converts HSL color components to RGB components.
+ *
+ * @static
+ * @param {number} h - [0, 360[ (float)
+ * @param {number} s - [0, 100] (float)
+ * @param {number} l - [0, 100] (float)
+ * @returns {Object|false}
+ * - red [0, 255] (integer)
+ * - green [0, 255] (integer)
+ * - blue [0, 255] (integer)
+ */
+export function convertHslToRgb(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 = Math.round((chroma + lightnessAdjustment) * precision);
+ secondComponent = Math.round((secondComponent + lightnessAdjustment) * precision);
+ lightnessAdjustment = Math.round((lightnessAdjustment) * precision);
+ 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,
+ };
+ }
+ return false;
+};
+/**
+ * Converts RGBA color components to a normalized CSS color: if the opacity
+ * is invalid or equal to 100, a hex is returned; otherwise a rgba() css color
+ * is returned.
+ *
+ * Those choice have multiple reason:
+ * - A hex color is more common to c/c from other utilities on the web and is
+ * also shorter than rgb() css colors
+ * - Opacity in hexadecimal notations is not supported on all browsers and is
+ * also less common to use.
+ *
+ * @static
+ * @param {integer} r - [0, 255]
+ * @param {integer} g - [0, 255]
+ * @param {integer} b - [0, 255]
+ * @param {float} a - [0, 100]
+ * @returns {string}
+ */
+export function convertRgbaToCSSColor(r, g, b, a) {
+ 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;
+ }
+ if (typeof (a) !== 'number' || isNaN(a) || a < 0 || Math.abs(a - 100) < Number.EPSILON) {
+ const rr = r < 16 ? '0' + r.toString(16) : r.toString(16);
+ const gg = g < 16 ? '0' + g.toString(16) : g.toString(16);
+ const bb = b < 16 ? '0' + b.toString(16) : b.toString(16);
+ return (`#${rr}${gg}${bb}`).toUpperCase();
+ }
+ return `rgba(${r}, ${g}, ${b}, ${parseFloat((a / 100.0).toFixed(3))})`;
+};
+/**
+ * Converts a CSS color (rgb(), rgba(), hexadecimal) to RGBA color components.
+ *
+ * Note: we don't support using and displaying hexadecimal color with opacity
+ * but this method allows to receive one and returns the correct opacity value.
+ *
+ * @static
+ * @param {string} cssColor - hexadecimal code or rgb() or rgba()
+ * @returns {Object|false}
+ * - red [0, 255] (integer)
+ * - green [0, 255] (integer)
+ * - blue [0, 255] (integer)
+ * - opacity [0, 100.0] (float)
+ */
+export function convertCSSColorToRgba(cssColor) {
+ // Check if cssColor is a rgba() or rgb() color
+ const rgba = cssColor.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+(?:\.\d+)?))?\)$/);
+ if (rgba) {
+ if (rgba[4] === undefined) {
+ rgba[4] = 1;
+ }
+ return {
+ red: parseInt(rgba[1]),
+ green: parseInt(rgba[2]),
+ blue: parseInt(rgba[3]),
+ opacity: Math.round(parseFloat(rgba[4]) * 100),
+ };
+ }
+
+ // Otherwise, check if cssColor is an hexadecimal code color
+ if (/^#([0-9A-F]{6}|[0-9A-F]{8})$/i.test(cssColor)) {
+ return {
+ red: parseInt(cssColor.substr(1, 2), 16),
+ green: parseInt(cssColor.substr(3, 2), 16),
+ blue: parseInt(cssColor.substr(5, 2), 16),
+ opacity: (cssColor.length === 9 ? (parseInt(cssColor.substr(7, 2), 16) / 255) : 1) * 100,
+ };
+ }
+
+ // TODO maybe implement a support for receiving css color like 'red' or
+ // 'transparent' (which are now considered non-css color by isCSSColor...)
+ // Note: however, if ever implemented be careful of 'white'/'black' which
+ // actually are color names for our color system...
+
+ return false;
+};
+/**
+ * Converts a CSS color (rgb(), rgba(), hexadecimal) to a normalized version
+ * of the same color (@see convertRgbaToCSSColor).
+ *
+ * Normalized color can be safely compared using string comparison.
+ *
+ * @static
+ * @param {string} cssColor - hexadecimal code or rgb() or rgba()
+ * @returns {string} - the normalized css color or the given css color if it
+ * failed to be normalized
+ */
+export function normalizeCSSColor(cssColor) {
+ const rgba = convertCSSColorToRgba(cssColor);
+ if (!rgba) {
+ return cssColor;
+ }
+ return convertRgbaToCSSColor(rgba.red, rgba.green, rgba.blue, rgba.opacity);
+};
+/**
+ * Checks if a given string is a css color.
+ *
+ * @static
+ * @param {string} cssColor
+ * @returns {boolean}
+ */
+export function isCSSColor(cssColor) {
+ return convertCSSColorToRgba(cssColor) !== false;
+};
+/**
+ * Mixes two colors by applying a weighted average of their red, green and blue
+ * components.
+ *
+ * @static
+ * @param {string} cssColor1 - hexadecimal code or rgb() or rgba()
+ * @param {string} cssColor2 - hexadecimal code or rgb() or rgba()
+ * @param {number} weight - a number between 0 and 1
+ * @returns {string} - mixed color in hexadecimal format
+ */
+export function mixCssColors(cssColor1, cssColor2, weight) {
+ const rgba1 = convertCSSColorToRgba(cssColor1);
+ const rgba2 = convertCSSColorToRgba(cssColor2);
+ const rgb1 = [rgba1.red, rgba1.green, rgba1.blue];
+ const rgb2 = [rgba2.red, rgba2.green, rgba2.blue];
+ const [r, g, b] = rgb1.map((_, idx) => Math.round(rgb2[idx] + (rgb1[idx] - rgb2[idx]) * weight));
+ return convertRgbaToCSSColor(r, g, b);
+};
diff --git a/addons/web/static/src/legacy/js/widgets/colorpicker.js b/addons/web/static/src/legacy/js/widgets/colorpicker.js
deleted file mode 100644
index c87eb8a2544..00000000000
--- a/addons/web/static/src/legacy/js/widgets/colorpicker.js
+++ /dev/null
@@ -1,756 +0,0 @@
-/** @odoo-module alias=web.Colorpicker **/
-
-import core from "web.core";
-import utils from "web.utils";
-import Dialog from "web.Dialog";
-import Widget from "web.Widget";
-import { uniqueId } from "@web/core/utils/functions";
-import { debounce, throttleForAnimation } from "@web/core/utils/timing";
-
-var _t = core._t;
-
-var ColorpickerWidget = Widget.extend({
- template: 'Colorpicker',
- events: {
- 'click': '_onClick',
- 'keypress': '_onKeypress',
- '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']
- * @param {string} [options.noTransparency=false]
- * @param {boolean} [options.stopClickPropagation=false]
- */
- init: function (parent, options) {
- this._super(...arguments);
- options = options || {};
-
- this.pickerFlag = false;
- this.sliderFlag = false;
- this.opacitySliderFlag = false;
- this.colorComponents = {};
- this.uniqueId = uniqueId("colorpicker");
- this.selectedHexValue = '';
-
- this.options = Object.assign({}, 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 = ColorpickerWidget.convertCSSColorToRgba(defaultColor);
- if (rgba) {
- this._updateRgba(rgba.red, rgba.green, rgba.blue, rgba.opacity);
- }
-
- // Pre-fill the inputs. This is because on safari, the baseline for empty
- // input is not the baseline of where the text would be, but the bottom
- // of the input itself. (see https://bugs.webkit.org/show_bug.cgi?id=142968)
- // This will cause the first _updateUI to alter the layout of the colorpicker
- // which will change its height. Changing the height of an element inside of
- // the callback to a ResizeObserver observing it will cause an error
- // (ResizeObserver loop completed with undelivered notifications) that cannot
- // be caught, which will open the crash manager. Prefilling the inputs sets
- // the baseline correctly from the start so the layout doesn't change.
- Object.entries(this.colorComponents).forEach(([component, value]) => {
- const input = this.el.querySelector(`.o_${component}_input`);
- if (input) {
- input.value = value;
- }
- });
- const resizeObserver = new window.ResizeObserver(() => {
- this._updateUI();
- });
- resizeObserver.observe(this.el);
-
- // Need to be bound on all documents to work in all possible cases (we
- // have to be able to start dragging/moving from the colorpicker to
- // anywhere on the screen, crossing iframes).
- this.$documents = $([window.top, ...Array.from(window.top.frames).filter(frame => {
- try {
- const document = frame.document;
- return !!document;
- } catch {
- // We cannot access the document (cross origin).
- return false;
- }
- })].map(w => w.document));
- this.throttleOnMouseMove = throttleForAnimation((ev) => {
- this._onMouseMovePicker(ev);
- this._onMouseMoveSlider(ev);
- this._onMouseMoveOpacitySlider(ev);
- });
- this.$documents.on(`mousemove.${this.uniqueId}`, this.throttleOnMouseMove);
- this.$documents.on(`mouseup.${this.uniqueId}`, debounce((ev) => {
- if (this.pickerFlag || this.sliderFlag || this.opacitySliderFlag) {
- this._colorSelected();
- }
- this.pickerFlag = false;
- this.sliderFlag = false;
- this.opacitySliderFlag = false;
- }, 10));
-
- this.previewActive = true;
- return this._super.apply(this, arguments);
- },
- /**
- * @override
- */
- destroy: function () {
- this._super.apply(this, arguments);
- if (this.throttleOnMouseMove) {
- this.$documents.off(`.${this.uniqueId}`);
- this.throttleOnMouseMove.cancel();
- }
- },
- /**
- * Sets the currently selected color
- *
- * @param {string} color rgb[a]
- */
- setSelectedColor: function (color) {
- var rgba = ColorpickerWidget.convertCSSColorToRgba(color);
- if (rgba) {
- const oldPreviewActive = this.previewActive;
- this.previewActive = false;
- this._updateRgba(rgba.red, rgba.green, rgba.blue, rgba.opacity);
- this.previewActive = oldPreviewActive;
- this._updateUI();
- }
- },
-
- //--------------------------------------------------------------------------
- // Private
- //--------------------------------------------------------------------------
-
- /**
- * Updates input values, color preview, picker and slider pointer positions.
- *
- * @private
- */
- _updateUI: function () {
- var self = this;
-
- // Update inputs
- for (const [color, value] of Object.entries(this.colorComponents)) {
- self.$(`.o_${color}_input`).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",
- `hsl(${this.colorComponents.hue}, 100%, 50%)`
- );
- 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));
-
- if (! this.options.noTransparency) {
- // 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 = ColorpickerWidget.convertCSSColorToRgba(hex);
- if (!rgb) {
- return;
- }
- Object.assign(this.colorComponents,
- {hex: hex},
- rgb,
- ColorpickerWidget.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) {
- // Remove full transparency in case some lightness is added
- const opacity = a || this.colorComponents.opacity;
- if (opacity < 0.1 && (r > 0.1 || g > 0.1 || b > 0.1)) {
- a = 100;
- }
-
- // We update the hexadecimal code by transforming into a css color and
- // ignoring the opacity (we don't display opacity component in hexa as
- // not supported on all browsers)
- var hex = ColorpickerWidget.convertRgbaToCSSColor(r, g, b);
- if (!hex) {
- return;
- }
- Object.assign(this.colorComponents,
- {red: r, green: g, blue: b},
- a === undefined ? {} : {opacity: a},
- {hex: hex},
- ColorpickerWidget.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) {
- // Remove full transparency in case some lightness is added
- let a = this.colorComponents.opacity;
- if (a < 0.1 && l > 0.1) {
- a = 100;
- }
-
- var rgb = ColorpickerWidget.convertHslToRgb(h, s, l);
- if (!rgb) {
- return;
- }
- // We receive an hexa as we ignore the opacity
- const hex = ColorpickerWidget.convertRgbaToCSSColor(rgb.red, rgb.green, rgb.blue);
- Object.assign(this.colorComponents,
- {hue: h, saturation: s, lightness: l},
- rgb,
- {hex: hex},
- {opacity: a},
- );
- this._updateCssColor();
- },
- /**
- * Updates color opacity.
- *
- * @private
- * @param {integer} a
- */
- _updateOpacity: function (a) {
- if (a < 0 || a > 100) {
- return;
- }
- Object.assign(this.colorComponents,
- {opacity: a}
- );
- this._updateCssColor();
- },
- /**
- * Trigger an event to annonce that the widget value has changed
- *
- * @private
- */
- _colorSelected: function () {
- this.trigger_up('colorpicker_select', this.colorComponents);
- },
- /**
- * Updates css color representation.
- *
- * @private
- */
- _updateCssColor: function () {
- const r = this.colorComponents.red;
- const g = this.colorComponents.green;
- const b = this.colorComponents.blue;
- const a = this.colorComponents.opacity;
- Object.assign(this.colorComponents,
- {cssColor: ColorpickerWidget.convertRgbaToCSSColor(r, g, b, a)}
- );
- if (this.previewActive) {
- this.trigger_up('colorpicker_preview', this.colorComponents);
- }
- },
-
- //--------------------------------------------------------------------------
- // Handlers
- //--------------------------------------------------------------------------
-
- /**
- * @private
- * @param {Event} ev
- */
- _onKeypress: function (ev) {
- if (ev.charCode === $.ui.keyCode.ENTER) {
- if (ev.target.tagName === 'INPUT') {
- this._onChangeInputs(ev);
- }
- ev.preventDefault();
- this.trigger_up('enter_key_color_colorpicker');
- }
- },
- /**
- * @param {Event} ev
- */
- _onClick: function (ev) {
- if (this.options.stopClickPropagation) {
- ev.stopPropagation();
- }
- ev.originalEvent.__isColorpickerClick = true;
- $(ev.target).find('> .o_opacity_pointer, > .o_slider_pointer, > .o_picker_pointer').addBack('.o_opacity_pointer, .o_slider_pointer, .o_picker_pointer').focus();
- if (ev.target.dataset.colorMethod === 'hex' && !this.selectedHexValue) {
- ev.target.select();
- this.selectedHexValue = ev.target.value;
- return;
- }
- this.selectedHexValue = '';
- },
- /**
- * Updates color when the user starts clicking on the picker.
- *
- * @private
- * @param {Event} ev
- */
- _onMouseDownPicker: function (ev) {
- this.pickerFlag = true;
- ev.preventDefault();
- 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;
- ev.preventDefault();
- 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;
- ev.preventDefault();
- this._onMouseMoveOpacitySlider(ev);
- },
- /**
- * Updates opacity value on mouse drag over opacity slider.
- *
- * @private
- * @param {Event} ev
- */
- _onMouseMoveOpacitySlider: function (ev) {
- if (!this.opacitySliderFlag || this.options.noTransparency) {
- 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();
- this._colorSelected();
- },
-});
-
-//--------------------------------------------------------------------------
-// Static
-//--------------------------------------------------------------------------
-
-/**
- * Converts RGB color components to HSL components.
- *
- * @static
- * @param {integer} r - [0, 255]
- * @param {integer} g - [0, 255]
- * @param {integer} b - [0, 255]
- * @returns {Object|false}
- * - hue [0, 360[ (float)
- * - saturation [0, 100] (float)
- * - lightness [0, 100] (float)
- */
-ColorpickerWidget.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;
- return {
- hue: hue < 0 ? hue + 360 : hue,
- saturation: saturation * 100,
- lightness: lightness * 100,
- };
-};
-/**
- * Converts HSL color components to RGB components.
- *
- * @static
- * @param {number} h - [0, 360[ (float)
- * @param {number} s - [0, 100] (float)
- * @param {number} l - [0, 100] (float)
- * @returns {Object|false}
- * - red [0, 255] (integer)
- * - green [0, 255] (integer)
- * - blue [0, 255] (integer)
- */
-ColorpickerWidget.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 = Math.round((chroma + lightnessAdjustment) * precision);
- secondComponent = Math.round((secondComponent + lightnessAdjustment) * precision);
- lightnessAdjustment = Math.round((lightnessAdjustment) * precision);
- 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,
- };
- }
- return false;
-};
-/**
- * Converts RGBA color components to a normalized CSS color: if the opacity
- * is invalid or equal to 100, a hex is returned; otherwise a rgba() css color
- * is returned.
- *
- * Those choice have multiple reason:
- * - A hex color is more common to c/c from other utilities on the web and is
- * also shorter than rgb() css colors
- * - Opacity in hexadecimal notations is not supported on all browsers and is
- * also less common to use.
- *
- * @static
- * @param {integer} r - [0, 255]
- * @param {integer} g - [0, 255]
- * @param {integer} b - [0, 255]
- * @param {float} a - [0, 100]
- * @returns {string}
- */
-ColorpickerWidget.convertRgbaToCSSColor = function (r, g, b, a) {
- 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;
- }
- if (typeof (a) !== 'number' || isNaN(a) || a < 0 || Math.abs(a - 100) < Number.EPSILON) {
- const rr = r < 16 ? '0' + r.toString(16) : r.toString(16);
- const gg = g < 16 ? '0' + g.toString(16) : g.toString(16);
- const bb = b < 16 ? '0' + b.toString(16) : b.toString(16);
- return (`#${rr}${gg}${bb}`).toUpperCase();
- }
- return `rgba(${r}, ${g}, ${b}, ${parseFloat((a / 100.0).toFixed(3))})`;
-};
-/**
- * Converts a CSS color (rgb(), rgba(), hexadecimal) to RGBA color components.
- *
- * Note: we don't support using and displaying hexadecimal color with opacity
- * but this method allows to receive one and returns the correct opacity value.
- *
- * @static
- * @param {string} cssColor - hexadecimal code or rgb() or rgba()
- * @returns {Object|false}
- * - red [0, 255] (integer)
- * - green [0, 255] (integer)
- * - blue [0, 255] (integer)
- * - opacity [0, 100.0] (float)
- */
-ColorpickerWidget.convertCSSColorToRgba = function (cssColor) {
- // Check if cssColor is a rgba() or rgb() color
- const rgba = cssColor.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+(?:\.\d+)?))?\)$/);
- if (rgba) {
- if (rgba[4] === undefined) {
- rgba[4] = 1;
- }
- return {
- red: parseInt(rgba[1]),
- green: parseInt(rgba[2]),
- blue: parseInt(rgba[3]),
- opacity: Math.round(parseFloat(rgba[4]) * 100),
- };
- }
-
- // Otherwise, check if cssColor is an hexadecimal code color
- if (/^#([0-9A-F]{6}|[0-9A-F]{8})$/i.test(cssColor)) {
- return {
- red: parseInt(cssColor.substr(1, 2), 16),
- green: parseInt(cssColor.substr(3, 2), 16),
- blue: parseInt(cssColor.substr(5, 2), 16),
- opacity: (cssColor.length === 9 ? (parseInt(cssColor.substr(7, 2), 16) / 255) : 1) * 100,
- };
- }
-
- // TODO maybe implement a support for receiving css color like 'red' or
- // 'transparent' (which are now considered non-css color by isCSSColor...)
- // Note: however, if ever implemented be careful of 'white'/'black' which
- // actually are color names for our color system...
-
- return false;
-};
-/**
- * Converts a CSS color (rgb(), rgba(), hexadecimal) to a normalized version
- * of the same color (@see convertRgbaToCSSColor).
- *
- * Normalized color can be safely compared using string comparison.
- *
- * @static
- * @param {string} cssColor - hexadecimal code or rgb() or rgba()
- * @returns {string} - the normalized css color or the given css color if it
- * failed to be normalized
- */
-ColorpickerWidget.normalizeCSSColor = function (cssColor) {
- const rgba = ColorpickerWidget.convertCSSColorToRgba(cssColor);
- if (!rgba) {
- return cssColor;
- }
- return ColorpickerWidget.convertRgbaToCSSColor(rgba.red, rgba.green, rgba.blue, rgba.opacity);
-};
-/**
- * Checks if a given string is a css color.
- *
- * @static
- * @param {string} cssColor
- * @returns {boolean}
- */
-ColorpickerWidget.isCSSColor = function (cssColor) {
- return ColorpickerWidget.convertCSSColorToRgba(cssColor) !== false;
-};
-
-/**
- * Mixes two colors by applying a weighted average of their red, green and blue
- * components.
- *
- * @static
- * @param {string} cssColor1 - hexadecimal code or rgb() or rgba()
- * @param {string} cssColor2 - hexadecimal code or rgb() or rgba()
- * @param {number} weight - a number between 0 and 1
- * @returns {string} - mixed color in hexadecimal format
- */
-ColorpickerWidget.mixCssColors = function (cssColor1, cssColor2, weight) {
- const rgba1 = ColorpickerWidget.convertCSSColorToRgba(cssColor1);
- const rgba2 = ColorpickerWidget.convertCSSColorToRgba(cssColor2);
- const rgb1 = [rgba1.red, rgba1.green, rgba1.blue];
- const rgb2 = [rgba2.red, rgba2.green, rgba2.blue];
- const [r, g, b] = rgb1.map((_, idx) => Math.round(rgb2[idx] + (rgb1[idx] - rgb2[idx]) * weight));
- return ColorpickerWidget.convertRgbaToCSSColor(r, g, b);
-};
-
-const ColorpickerDialog = Dialog.extend({
- /**
- * @override
- */
- init: function (parent, options) {
- this.options = options || {};
- this._super(parent, Object.assign({
- size: 'small',
- title: _t('Pick a color'),
- buttons: [
- {text: _t('Choose'), classes: 'btn-primary', close: true, click: this._onFinalPick.bind(this)},
- {text: _t('Discard'), close: true},
- ],
- }, this.options));
- },
- /**
- * @override
- */
- start: function () {
- const proms = [this._super(...arguments)];
- this.colorPicker = new ColorpickerWidget(this, Object.assign({
- colorPreview: true,
- }, this.options));
- proms.push(this.colorPicker.appendTo(this.$el));
- return Promise.all(proms);
- },
-
- //--------------------------------------------------------------------------
- // Handlers
- //--------------------------------------------------------------------------
-
- /**
- * @private
- */
- _onFinalPick: function () {
- this.trigger_up('colorpicker:saved', this.colorPicker.colorComponents);
- },
-});
-
-export default {
- ColorpickerDialog: ColorpickerDialog,
- ColorpickerWidget: ColorpickerWidget,
-};
diff --git a/addons/web/static/src/legacy/js/widgets/colorpicker_dialog.js b/addons/web/static/src/legacy/js/widgets/colorpicker_dialog.js
new file mode 100644
index 00000000000..ddffd55debc
--- /dev/null
+++ b/addons/web/static/src/legacy/js/widgets/colorpicker_dialog.js
@@ -0,0 +1,47 @@
+/** @odoo-module **/
+
+import { _t } from "@web/core/l10n/translation";
+import { ComponentWrapper } from "web.OwlCompatibility";
+
+import Dialog from "web.Dialog";
+import { Colorpicker } from '@web/core/colorpicker/colorpicker';
+
+export const ColorpickerDialog = Dialog.extend({
+ /**
+ * @override
+ */
+ init: function (parent, options) {
+ this.options = options || {};
+ this._super(parent, Object.assign({
+ size: 'small',
+ title: _t('Pick a color'),
+ buttons: [
+ {text: _t('Choose'), classes: 'btn-primary', close: true, click: this._onFinalPick.bind(this)},
+ {text: _t('Discard'), close: true},
+ ],
+ }, this.options));
+ },
+ /**
+ * @override
+ */
+ start: function () {
+ const proms = [this._super(...arguments)];
+ this.colorpicker = new ComponentWrapper(this, Colorpicker, {
+ colorPreview: true,
+ });
+ this._colorpickerComponent = this.colorpicker.node.component;
+ proms.push(this.colorpicker.mount(this.$el[0]));
+ return Promise.all(proms);
+ },
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * @private
+ */
+ _onFinalPick: function () {
+ this.trigger_up('colorpicker:saved', this._colorpickerComponent.colorComponents);
+ },
+});
diff --git a/addons/web_editor/__manifest__.py b/addons/web_editor/__manifest__.py
index 885bcadf425..0b497d6f32f 100644
--- a/addons/web_editor/__manifest__.py
+++ b/addons/web_editor/__manifest__.py
@@ -22,6 +22,23 @@ Odoo Web Editor widget.
# MAIN BUNDLES
#----------------------------------------------------------------------
+ 'web_editor.assets_legacy_wysiwyg': [
+ 'web_editor/static/src/js/editor/snippets.editor.js',
+ 'web_editor/static/src/js/editor/snippets.options.js',
+ ],
+ 'web_editor.wysiwyg_iframe_editor_assets': [
+ ('include', 'web.assets_common'),
+ ('include', 'web_editor.assets_wysiwyg'),
+ ('include', 'web_editor.assets_legacy_wysiwyg'),
+ ],
+ 'web_editor.assets_media_dialog': [
+ 'web_editor/static/src/components/**/*',
+ ],
+ 'web_editor.assets_tests_styles': [
+ ('include', 'web._assets_helpers'),
+ 'web_editor/static/src/js/editor/odoo-editor/src/base_style.scss',
+ 'web_editor/static/src/js/editor/odoo-editor/src/checklist.scss',
+ ],
'web_editor.assets_wysiwyg': [
# lib
'web_editor/static/lib/cropperjs/cropper.css',
@@ -53,6 +70,8 @@ Odoo Web Editor widget.
# utils
'web_editor/static/src/js/wysiwyg/linkDialogCommand.js',
'web_editor/static/src/js/wysiwyg/PeerToPeer.js',
+ 'web_editor/static/src/js/wysiwyg/conflict_dialog.js',
+ 'web_editor/static/src/js/wysiwyg/conflict_dialog.xml',
# odoo utils
('include', 'web._assets_helpers'),
@@ -73,13 +92,11 @@ Odoo Web Editor widget.
# widgets & plugins
'web_editor/static/src/js/wysiwyg/widgets/**/*',
- 'web_editor/static/src/js/editor/snippets.editor.js',
'web_editor/static/src/js/editor/toolbar.js',
- 'web_editor/static/src/js/editor/snippets.options.js',
# Launcher
+ 'web_editor/static/src/js/wysiwyg/wysiwyg_jquery_extention.js',
'web_editor/static/src/js/wysiwyg/wysiwyg.js',
- 'web_editor/static/src/js/wysiwyg/widget.js',
'web_editor/static/src/js/wysiwyg/wysiwyg_iframe.js',
'web_editor/static/src/xml/editor.xml',
@@ -89,14 +106,6 @@ Odoo Web Editor widget.
'web_editor/static/src/xml/wysiwyg.xml',
'web_editor/static/src/xml/wysiwyg_colorpicker.xml',
],
- 'web_editor.assets_media_dialog': [
- 'web_editor/static/src/components/**/*',
- ],
- 'web_editor.assets_tests_styles': [
- ('include', 'web._assets_helpers'),
- 'web_editor/static/src/js/editor/odoo-editor/src/base_style.scss',
- 'web_editor/static/src/js/editor/odoo-editor/src/checklist.scss',
- ],
'web.assets_common': [
'web_editor/static/src/js/editor/odoo-editor/src/base_style.scss',
'web_editor/static/lib/vkbeautify/**/*',
@@ -116,10 +125,11 @@ Odoo Web Editor widget.
'web_editor/static/src/scss/web_editor.common.scss',
'web_editor/static/src/scss/web_editor.backend.scss',
- 'web_editor/static/src/js/wysiwyg/dialog.js',
'web_editor/static/src/js/frontend/loader.js',
'web_editor/static/src/js/backend/**/*',
'web_editor/static/src/xml/backend.xml',
+
+ ('include', 'web_editor.assets_wysiwyg'),
],
"web.dark_mode_assets_backend": [
'web_editor/static/src/scss/odoo-editor/powerbox.dark.scss',
@@ -144,8 +154,8 @@ Odoo Web Editor widget.
'web_editor/static/src/scss/web_editor.common.scss',
'web_editor/static/src/scss/web_editor.frontend.scss',
- 'web_editor/static/src/js/wysiwyg/dialog.js',
'web_editor/static/src/js/frontend/loader.js',
+ 'web_editor/static/src/js/frontend/loadWysiwygFromTextarea.js',
],
#----------------------------------------------------------------------
@@ -172,7 +182,7 @@ Odoo Web Editor widget.
# ----------------------------------------------------------------------
'web.qunit_suite_tests': [
- ('include', 'web_editor.assets_wysiwyg'),
+ ('include', 'web_editor.assets_legacy_wysiwyg'),
'web_editor/static/tests/**/*',
'web_editor/static/src/js/editor/odoo-editor/test/utils.js'
diff --git a/addons/web_editor/static/src/components/emoji_popover/emoji_popover.js b/addons/web_editor/static/src/components/emoji_popover/emoji_popover.js
deleted file mode 100644
index 76555dc01c6..00000000000
--- a/addons/web_editor/static/src/components/emoji_popover/emoji_popover.js
+++ /dev/null
@@ -1,19 +0,0 @@
-/** @odoo-module */
-
-import { useWowlService } from '@web/legacy/utils';
-import { Component, onRendered, xml } from "@odoo/owl";
-import { EmojiPicker } from '@web/core/emoji_picker/emoji_picker';
-
-export class EmojiPickerWrapper extends Component {
- setup() {
- this.popover = useWowlService('popover');
-
- onRendered(() => {
- this.popover.add(this.props.targetEl, EmojiPicker, {
- onSelect: this.props.onSelect,
- }, { position: `bottom-${this.props.position}`});
- });
- }
-}
-
-EmojiPickerWrapper.template = xml``;
diff --git a/addons/web_editor/static/src/components/media_dialog/media_dialog.js b/addons/web_editor/static/src/components/media_dialog/media_dialog.js
index 06150f1e780..9ca7649ad78 100644
--- a/addons/web_editor/static/src/components/media_dialog/media_dialog.js
+++ b/addons/web_editor/static/src/components/media_dialog/media_dialog.js
@@ -1,7 +1,6 @@
/** @odoo-module **/
import { useService } from '@web/core/utils/hooks';
-import { useWowlService } from '@web/legacy/utils';
import { Dialog } from '@web/core/dialog/dialog';
import { Notebook } from '@web/core/notebook/notebook';
import { ImageSelector } from './image_selector';
@@ -9,7 +8,7 @@ import { DocumentSelector } from './document_selector';
import { IconSelector } from './icon_selector';
import { VideoSelector } from './video_selector';
-import { Component, useState, onRendered, xml } from "@odoo/owl";
+import { Component, useState } from "@odoo/owl";
export const TABS = {
IMAGES: {
@@ -236,16 +235,3 @@ MediaDialog.components = {
Dialog,
Notebook,
};
-
-export class MediaDialogWrapper extends Component {
- setup() {
- this.dialogs = useWowlService('dialog');
-
- onRendered(() => {
- this.dialogs.add(MediaDialog, this.props, {
- onClose: this.props.close,
- });
- });
- }
-}
-MediaDialogWrapper.template = xml``;
diff --git a/addons/web_editor/static/src/components/media_dialog/media_dialog_wrapper.js b/addons/web_editor/static/src/components/media_dialog/media_dialog_wrapper.js
new file mode 100644
index 00000000000..319718babd5
--- /dev/null
+++ b/addons/web_editor/static/src/components/media_dialog/media_dialog_wrapper.js
@@ -0,0 +1,19 @@
+
+/** @odoo-module **/
+
+import { useWowlService } from '@web/legacy/utils';
+import { Component, onRendered, xml } from "@odoo/owl";
+import { MediaDialog } from "./media_dialog";
+
+export class MediaDialogWrapper extends Component {
+ setup() {
+ this.dialogs = useWowlService('dialog');
+
+ onRendered(() => {
+ this.dialogs.add(MediaDialog, this.props, {
+ onClose: this.props.close,
+ });
+ });
+ }
+}
+MediaDialogWrapper.template = xml``;
diff --git a/addons/web_editor/static/src/components/media_dialog/video_selector.js b/addons/web_editor/static/src/components/media_dialog/video_selector.js
index e6088583b79..89e96dfb594 100644
--- a/addons/web_editor/static/src/components/media_dialog/video_selector.js
+++ b/addons/web_editor/static/src/components/media_dialog/video_selector.js
@@ -2,7 +2,6 @@
import { useService } from '@web/core/utils/hooks';
import { debounce } from '@web/core/utils/timing';
-import { qweb } from 'web.core';
import { Component, useState, useRef, onMounted, onWillStart } from "@odoo/owl";
@@ -195,9 +194,15 @@ export class VideoSelector extends Component {
*/
static createElements(selectedMedia) {
return selectedMedia.map(video => {
- const template = document.createElement('template');
- template.innerHTML = qweb.render('web_editor.videoWrapper', { src: video.src });
- return template.content.firstChild;
+ const div = document.createElement('div');
+ div.dataset.oeExpression = video.src;
+ div.innerHTML = `
+
+
+
+ `;
+ div.querySelector('iframe').src = video.src;
+ return div;
});
}
}
diff --git a/addons/web_editor/static/src/components/media_dialog/video_selector.xml b/addons/web_editor/static/src/components/media_dialog/video_selector.xml
index ece78946bff..b0a2424a4a5 100644
--- a/addons/web_editor/static/src/components/media_dialog/video_selector.xml
+++ b/addons/web_editor/static/src/components/media_dialog/video_selector.xml
@@ -1,10 +1,5 @@
-
diff --git a/addons/web_editor/static/src/js/backend/html_field.js b/addons/web_editor/static/src/js/backend/html_field.js
index 6e4cded8a17..07654d0d200 100644
--- a/addons/web_editor/static/src/js/backend/html_field.js
+++ b/addons/web_editor/static/src/js/backend/html_field.js
@@ -1,16 +1,11 @@
/** @odoo-module **/
-import legacyEnv from 'web.commonEnv';
-import { ComponentAdapter } from 'web.OwlCompatibility';
import { registry } from "@web/core/registry";
import { _lt } from "@web/core/l10n/translation";
import { standardFieldProps } from "@web/views/fields/standard_field_props";
-import { getWysiwygClass } from 'web_editor.loader';
import { QWebPlugin } from '@web_editor/js/backend/QWebPlugin';
import { TranslationButton } from "@web/views/fields/translation_button";
import { useDynamicPlaceholder } from "@web/views/fields/dynamic_placeholder_hook";
-import { QWeb } from 'web.core';
-import ajax from 'web.ajax';
import {
useBus,
useService,
@@ -22,11 +17,10 @@ import {
getRangePosition
} from '@web_editor/js/editor/odoo-editor/src/utils/utils';
import { toInline } from 'web_editor.convertInline';
-import { loadJS } from '@web/core/assets';
+import { loadJS, getBundle } from '@web/core/assets';
import {
Component,
useRef,
- useSubEnv,
useState,
onWillStart,
onMounted,
@@ -35,46 +29,31 @@ import {
onWillUnmount,
} from "@odoo/owl";
import { uniqueId } from '@web/core/utils/functions';
-
-export class HtmlFieldWysiwygAdapterComponent extends ComponentAdapter {
- setup() {
- super.setup();
- useSubEnv(legacyEnv);
-
- let started = false;
- onMounted(() => {
- if (!started) {
- this.props.startWysiwyg(this.widget);
- started = true;
- }
- });
- }
-
- updateWidget(newProps) {
- const lastValue = String(this.props.widgetArgs[0].value || '');
- const lastRecordInfo = this.props.widgetArgs[0].recordInfo;
- const lastCollaborationChannel = this.props.widgetArgs[0].collaborationChannel;
- const newValue = String(newProps.widgetArgs[0].value || '');
- const newRecordInfo = newProps.widgetArgs[0].recordInfo;
- const newCollaborationChannel = newProps.widgetArgs[0].collaborationChannel;
-
- if (
- (
- stripHistoryIds(newValue) !== stripHistoryIds(newProps.editingValue) &&
- stripHistoryIds(lastValue) !== stripHistoryIds(newValue)
- ) ||
- JSON.stringify(lastRecordInfo) !== JSON.stringify(newRecordInfo) ||
- JSON.stringify(lastCollaborationChannel) !== JSON.stringify(newCollaborationChannel)
- )
- {
- this.widget.resetEditor(newValue, newProps.widgetArgs[0]);
- this.env.onWysiwygReset && this.env.onWysiwygReset();
- }
- }
- renderWidget() {}
-}
+import { Wysiwyg, stripHistoryIds } from '../wysiwyg/wysiwyg';
export class HtmlField extends Component {
+ static template = "web_editor.HtmlField";
+ static components = {
+ TranslationButton,
+ Wysiwyg,
+ };
+ static defaultProps = { dynamicPlaceholder: false };
+ static props = {
+ ...standardFieldProps,
+ placeholder: { type: String, optional: true },
+ codeview: { type: Boolean, optional: true },
+ isCollaborative: { type: Boolean, optional: true },
+ dynamicPlaceholder: { type: Boolean, optional: true, default: false },
+ dynamicPlaceholderModelReferenceField: { type: String, optional: true },
+ cssReadonlyAssetId: { type: String, optional: true },
+ cssEditAssetId: { type: String, optional: true },
+ isInlineStyle: { type: Boolean, optional: true },
+ sandboxedPreview: {type: Boolean, optional: true},
+ wrapper: { type: String, optional: true },
+ wysiwygOptions: { type: Object },
+ hasReadonlyModifiers: { type: Boolean, optional: true },
+ };
+
setup() {
this.containsComplexHTML = this.computeContainsComplexHTML();
this.sandboxedPreview = this.props.sandboxedPreview || this.containsComplexHTML;
@@ -109,13 +88,12 @@ export class HtmlField extends Component {
this._onUpdateIframeId = "onLoad_" + uniqueId("FieldHtml");
onWillStart(async () => {
- this.Wysiwyg = await this._getWysiwygClass();
if (this.props.cssReadonlyAssetId) {
- this.cssReadonlyAsset = await ajax.loadAsset(this.props.cssReadonlyAssetId);
+ this.cssReadonlyAsset = await getBundle(this.props.cssReadonlyAssetId);
}
if (this.props.cssEditAssetId || this.props.isInlineStyle) {
await loadJS('/web_editor/static/lib/html2canvas.js');
- this.cssEditAsset = await ajax.loadAsset(this.props.cssEditAssetId || 'web_editor.assets_edit_html_field');
+ await getBundle(this.props.cssEditAssetId || 'web_editor.assets_edit_html_field');
}
});
this._lastRecordInfo = {
@@ -235,7 +213,7 @@ export class HtmlField extends Component {
}
}
- const wysiwygOptions = { ...this.props.wysiwygOptions };
+ const wysiwygOptions = {...this.props.wysiwygOptions};
const { sanitize_tags, sanitize } = this.props.record.fields[this.props.name];
if (sanitize_tags || (sanitize_tags === undefined && sanitize)) {
wysiwygOptions.allowCommandVideo = false; // Tag-sanitized fields remove videos.
@@ -288,14 +266,6 @@ export class HtmlField extends Component {
}
return selectionIsInForbidenSnippet ? commands.filter((o) => o.title !== "Dynamic Placeholder") : commands;
}
- get translationButtonWrapperStyle() {
- return `
- font-size: 15px;
- position: absolute;
- right: ${this.props.codeview ? '40px' : '5px'};
- top: 5px;
- `;
- }
getEditingValue () {
const codeViewEl = this._getCodeViewEl();
@@ -335,7 +305,7 @@ export class HtmlField extends Component {
`);
- this.wysiwyg.toolbar.$el.append($codeviewButtonToolbar);
+ this.wysiwyg.toolbarEl.append($codeviewButtonToolbar[0]);
$codeviewButtonToolbar.click(this.toggleCodeView.bind(this));
}
this.wysiwyg.odooEditor.addEventListener("historyStep", () =>
@@ -353,6 +323,7 @@ export class HtmlField extends Component {
if (this.wysiwyg) {
this.wysiwyg.odooEditor.observerUnactive('toggleCodeView');
if (this.state.showCodeView) {
+ this.wysiwyg.$editable.remove();
this.wysiwyg.odooEditor.toolbarHide();
const value = this.wysiwyg.getValue();
this.props.record.update({ [this.props.name]: value });
@@ -447,9 +418,6 @@ export class HtmlField extends Component {
}
this.iframePromise = new Promise((resolve) => {
let value = this.props.record.data[this.props.name];
- if (this.props.wrapper) {
- value = this._wrap(value);
- }
// this bug only appears on some computers with some chrome version.
let avoidDoubleLoad = 0;
@@ -494,15 +462,14 @@ export class HtmlField extends Component {
'