[MERGE] web_editor, *: new widgets system for editor left panel
This is a first step towards more complex behaviors in the left panel and more possibilities with standard XML options. The main goal is to use widgets for internal option components (checkbox, button, input, colorpicker, ...) as a way to receive a value for an option method (selectClass, selectStyle, ...) while the two parts of this system were both handled by the option before. For example, now, a developer can say: "I want a colorpicker to style the border-color of this elements" or "I want the combination of two inputs to be set as a data-example of this element" without writing any JS. See sub-commits for details. In preparation of task-2122935 closes odoo/odoo#40282 Signed-off-by: Quentin Smetz (qsm) <qsm@odoo.com>
This commit is contained in:
@@ -807,8 +807,12 @@
|
||||
data-selector=".note-editable > div:not(.o_layout), .note-editable .oe_structure > div, td, th"
|
||||
data-exclude=".o_mail_no_resize, .o_mail_no_options"/>
|
||||
|
||||
<div data-js="colorpicker" string="Background Color"
|
||||
data-selector=".note-editable > div:not(.o_layout), .note-editable .oe_structure > div, td, th"
|
||||
data-exclude=".o_mail_no_colorpicker, .o_mail_no_options"/>
|
||||
<div data-selector=".note-editable > div:not(.o_layout), .note-editable .oe_structure > div, td, th"
|
||||
data-exclude=".o_mail_no_colorpicker, .o_mail_no_options">
|
||||
<we-colorpicker string="Background Color"
|
||||
data-select-style="true"
|
||||
data-css-property="background-color"
|
||||
data-color-prefix="bg-"/>
|
||||
</div>
|
||||
</template>
|
||||
</odoo>
|
||||
|
||||
@@ -15,7 +15,7 @@ var ColorpickerDialog = Dialog.extend({
|
||||
'mousedown .o_color_pick_area': '_onMouseDownPicker',
|
||||
'mousedown .o_color_slider': '_onMouseDownSlider',
|
||||
'mousedown .o_opacity_slider': '_onMouseDownOpacitySlider',
|
||||
'change .o_color_picker_inputs' : '_onChangeInputs',
|
||||
'change .o_color_picker_inputs': '_onChangeInputs',
|
||||
}),
|
||||
|
||||
/**
|
||||
@@ -76,7 +76,7 @@ var ColorpickerDialog = Dialog.extend({
|
||||
this.$opacitySliderPointer = this.$('.o_opacity_pointer');
|
||||
|
||||
var defaultColor = this.options.defaultColor || '#FF0000';
|
||||
var rgba = ColorpickerDialog.convertColorToRgba(defaultColor);
|
||||
var rgba = ColorpickerDialog.convertCSSColorToRgba(defaultColor);
|
||||
if (rgba) {
|
||||
this._updateRgba(rgba.red, rgba.green, rgba.blue, rgba.opacity);
|
||||
}
|
||||
@@ -143,7 +143,7 @@ var ColorpickerDialog = Dialog.extend({
|
||||
* @param {string} hex - hexadecimal code
|
||||
*/
|
||||
_updateHex: function (hex) {
|
||||
var rgb = ColorpickerDialog.convertHexToRgba(hex);
|
||||
var rgb = ColorpickerDialog.convertCSSColorToRgba(hex);
|
||||
if (!rgb) {
|
||||
return;
|
||||
}
|
||||
@@ -164,14 +164,17 @@ var ColorpickerDialog = Dialog.extend({
|
||||
* @param {integer} [a]
|
||||
*/
|
||||
_updateRgba: function (r, g, b, a) {
|
||||
var hex = ColorpickerDialog.convertRgbToHex(r, g, b);
|
||||
// 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 = ColorpickerDialog.convertRgbaToCSSColor(r, g, b);
|
||||
if (!hex) {
|
||||
return;
|
||||
}
|
||||
_.extend(this.colorComponents,
|
||||
{red: r, green: g, blue: b},
|
||||
a === undefined ? {} : {opacity: a},
|
||||
hex,
|
||||
{hex: hex},
|
||||
ColorpickerDialog.convertRgbToHsl(r, g, b)
|
||||
);
|
||||
this._updateCssColor();
|
||||
@@ -189,10 +192,12 @@ var ColorpickerDialog = Dialog.extend({
|
||||
if (!rgb) {
|
||||
return;
|
||||
}
|
||||
// We receive an hexa as we ignore the opacity
|
||||
const hex = ColorpickerDialog.convertRgbaToCSSColor(rgb.red, rgb.green, rgb.blue);
|
||||
_.extend(this.colorComponents,
|
||||
{hue: h, saturation: s, lightness: l},
|
||||
rgb,
|
||||
ColorpickerDialog.convertRgbToHex(rgb.red, rgb.green, rgb.blue)
|
||||
{hex: hex}
|
||||
);
|
||||
this._updateCssColor();
|
||||
},
|
||||
@@ -217,8 +222,12 @@ var ColorpickerDialog = Dialog.extend({
|
||||
* @private
|
||||
*/
|
||||
_updateCssColor: function () {
|
||||
const r = this.colorComponents.red;
|
||||
const g = this.colorComponents.green;
|
||||
const b = this.colorComponents.blue;
|
||||
const a = this.colorComponents.opacity;
|
||||
_.extend(this.colorComponents,
|
||||
{cssColor: ColorpickerDialog.formatColor(this.colorComponents)}
|
||||
{cssColor: ColorpickerDialog.convertRgbaToCSSColor(r, g, b, a)}
|
||||
);
|
||||
},
|
||||
|
||||
@@ -359,84 +368,21 @@ var ColorpickerDialog = Dialog.extend({
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Converts any color to string RGBA.
|
||||
* Converts RGB color components to HSL components.
|
||||
*
|
||||
* @static
|
||||
* @param {Object|string} color
|
||||
* @returns {string} rgba (red, green, blue, opacity)
|
||||
*/
|
||||
ColorpickerDialog.formatColor = function (color) {
|
||||
if (typeof color === 'string') {
|
||||
color = ColorpickerDialog.convertColorToRgba(color);
|
||||
}
|
||||
if (!color) {
|
||||
return '';
|
||||
}
|
||||
if (color.opacity === 100) {
|
||||
return ColorpickerDialog.convertRgbToHex(color.red, color.green, color.blue).hex;
|
||||
}
|
||||
return _.str.sprintf('rgba(%s, %s, %s, %s)',
|
||||
color.red,
|
||||
color.green,
|
||||
color.blue,
|
||||
color.opacity / 100
|
||||
);
|
||||
};
|
||||
/**
|
||||
* Converts any color to code to RGBA.
|
||||
*
|
||||
* @static
|
||||
* @param {string} color
|
||||
* @returns {Object|false} contains red, green, blue, opacity
|
||||
*/
|
||||
ColorpickerDialog.convertColorToRgba = function (color) {
|
||||
var rgba = color.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),
|
||||
};
|
||||
} else {
|
||||
return ColorpickerDialog.convertHexToRgba(color);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Converts Hexadecimal code to RGB.
|
||||
*
|
||||
* @static
|
||||
* @param {string} hex - hexadecimal code
|
||||
* @returns {Object|false} contains red, green and blue
|
||||
*/
|
||||
ColorpickerDialog.convertHexToRgba = function (hex) {
|
||||
if (!/^#([0-9A-F]{6}|[0-9A-F]{8})$/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),
|
||||
opacity: (hex.length === 9 ? (parseInt(hex.substr(7, 2), 16) / 255) : 1) * 100,
|
||||
};
|
||||
};
|
||||
/**
|
||||
* Converts RGB color to HSL.
|
||||
*
|
||||
* @static
|
||||
* @param {integer} r
|
||||
* @param {integer} g
|
||||
* @param {integer} b
|
||||
* @returns {Object|false} contains hue, saturation and lightness
|
||||
* @param {integer} r - [0, 255]
|
||||
* @param {integer} g - [0, 255]
|
||||
* @param {integer} b - [0, 255]
|
||||
* @returns {Object|false}
|
||||
* - hue [0, 360[
|
||||
* - saturation [0, 100]
|
||||
* - lightness [0, 100]
|
||||
*/
|
||||
ColorpickerDialog.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) {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -471,20 +417,24 @@ ColorpickerDialog.convertRgbToHsl = function (r, g, b) {
|
||||
};
|
||||
};
|
||||
/**
|
||||
* Converts HSL color to RGB.
|
||||
* Converts HSL color components to RGB components.
|
||||
*
|
||||
* @static
|
||||
* @param {integer} h
|
||||
* @param {integer} s
|
||||
* @param {integer} l
|
||||
* @returns {Object|false} contains red, green and blue
|
||||
* @param {integer} h - [0, 360[
|
||||
* @param {integer} s - [0, 100]
|
||||
* @param {integer} l - [0, 100]
|
||||
* @returns {Object|false}
|
||||
* - red [0, 255]
|
||||
* - green [0, 255]
|
||||
* - blue [0, 255]
|
||||
*/
|
||||
ColorpickerDialog.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) {
|
||||
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;
|
||||
@@ -537,28 +487,111 @@ ColorpickerDialog.convertHslToRgb = function (h, s, l) {
|
||||
blue: secondComponent,
|
||||
};
|
||||
}
|
||||
return false;
|
||||
};
|
||||
/**
|
||||
* Converts RGB color to Hexadecimal code.
|
||||
* 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
|
||||
* @param {integer} g
|
||||
* @param {integer} b
|
||||
* @returns {Object|false} contains hexadecimal code
|
||||
* @param {integer} r - [0, 255]
|
||||
* @param {integer} g - [0, 255]
|
||||
* @param {integer} b - [0, 255]
|
||||
* @param {float} a - [0, 100]
|
||||
* @returns {string}
|
||||
*/
|
||||
ColorpickerDialog.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) {
|
||||
ColorpickerDialog.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;
|
||||
}
|
||||
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)
|
||||
};
|
||||
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]
|
||||
* - green [0, 255]
|
||||
* - blue [0, 255]
|
||||
* - opacity [0, 100.0]
|
||||
*/
|
||||
ColorpickerDialog.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...)
|
||||
|
||||
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
|
||||
*/
|
||||
ColorpickerDialog.normalizeCSSColor = function (cssColor) {
|
||||
const rgba = ColorpickerDialog.convertCSSColorToRgba(cssColor);
|
||||
if (!rgba) {
|
||||
return cssColor;
|
||||
}
|
||||
return ColorpickerDialog.convertRgbaToCSSColor(rgba.red, rgba.green, rgba.blue, rgba.opacity);
|
||||
};
|
||||
/**
|
||||
* Checks if a given string is a css color.
|
||||
*
|
||||
* @static
|
||||
* @param {string} cssColor
|
||||
* @returns {boolean}
|
||||
*/
|
||||
ColorpickerDialog.isCSSColor = function (cssColor) {
|
||||
return ColorpickerDialog.convertCSSColorToRgba(cssColor) !== false;
|
||||
};
|
||||
|
||||
return ColorpickerDialog;
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
odoo.define('web_editor.utils', function (require) {
|
||||
'use strict';
|
||||
|
||||
const ColorpickerDialog = require('web.ColorpickerDialog');
|
||||
|
||||
/**
|
||||
* window.getComputedStyle cannot work properly with CSS shortcuts (like
|
||||
* 'border-width' which is a shortcut for the top + right + bottom + left border
|
||||
* widths. If an option wants to customize such a shortcut, it should be listed
|
||||
* here with the non-shortcuts property it stands for, in order.
|
||||
*
|
||||
* @type {Object<string[]>}
|
||||
*/
|
||||
const CSS_SHORTHANDS = {
|
||||
'border-width': ['border-top-width', 'border-right-width', 'border-bottom-width', 'border-left-width'],
|
||||
'border-radius': ['border-top-left-radius', 'border-top-right-radius', 'border-bottom-right-radius', 'border-bottom-left-radius'],
|
||||
'border-color': ['border-top-color', 'border-right-color', 'border-bottom-color', 'border-left-color'],
|
||||
};
|
||||
/**
|
||||
* Key-value mapping to list converters from an unit A to an unit B.
|
||||
* - The key is a string in the format '$1-$2' where $1 is the CSS symbol of
|
||||
* unit A and $2 is the CSS symbol of unit B.
|
||||
* - The value is a function that converts the received value (expressed in
|
||||
* unit A) to another value expressed in unit B. Two other parameters is
|
||||
* received: the css property on which the unit applies and the jQuery element
|
||||
* on which that css property may change.
|
||||
*/
|
||||
const CSS_UNITS_CONVERSION = {
|
||||
's-ms': () => 1000,
|
||||
'ms-s': () => 0.001,
|
||||
'rem-px': () => _computePxByRem(),
|
||||
'px-rem': () => _computePxByRem(true),
|
||||
};
|
||||
|
||||
/**
|
||||
* Computes the number of "px" needed to make a "rem" unit. Subsequent calls
|
||||
* returns the cached computed value.
|
||||
*
|
||||
* @param {boolean} [toRem=false]
|
||||
* @returns {float} - number of px by rem if 'toRem' is false
|
||||
* - the inverse otherwise
|
||||
*/
|
||||
function _computePxByRem(toRem) {
|
||||
if (_computePxByRem.PX_BY_REM === undefined) {
|
||||
const htmlStyle = window.getComputedStyle(document.documentElement);
|
||||
_computePxByRem.PX_BY_REM = parseFloat(htmlStyle['font-size']);
|
||||
}
|
||||
return toRem ? (1 / _computePxByRem.PX_BY_REM) : _computePxByRem.PX_BY_REM;
|
||||
}
|
||||
/**
|
||||
* Converts the given (value + unit) string to a numeric value expressed in
|
||||
* the other given css unit.
|
||||
*
|
||||
* e.g. fct('400ms', 's') -> 0.4
|
||||
*
|
||||
* @param {string} value
|
||||
* @param {string} unitTo
|
||||
* @param {string} [cssProp] - the css property on which the unit applies
|
||||
* @param {jQuery} [$target] - the jQuery element on which that css property
|
||||
* may change
|
||||
* @returns {number}
|
||||
*/
|
||||
function _convertValueToUnit(value, unitTo, cssProp, $target) {
|
||||
const m = _getNumericAndUnit(value);
|
||||
if (!m) {
|
||||
return NaN;
|
||||
}
|
||||
const numValue = parseFloat(m[0]);
|
||||
const valueUnit = m[1];
|
||||
return _convertNumericToUnit(numValue, valueUnit, unitTo, cssProp, $target);
|
||||
}
|
||||
/**
|
||||
* Converts the given numeric value expressed in the given css unit into
|
||||
* the corresponding numeric value expressed in the other given css unit.
|
||||
*
|
||||
* e.g. fct(400, 'ms', 's') -> 0.4
|
||||
*
|
||||
* @param {number} value
|
||||
* @param {string} unitFrom
|
||||
* @param {string} unitTo
|
||||
* @param {string} [cssProp] - the css property on which the unit applies
|
||||
* @param {jQuery} [$target] - the jQuery element on which that css property
|
||||
* may change
|
||||
* @returns {number}
|
||||
*/
|
||||
function _convertNumericToUnit(value, unitFrom, unitTo, cssProp, $target) {
|
||||
if (Math.abs(value) < Number.EPSILON || unitFrom === unitTo) {
|
||||
return value;
|
||||
}
|
||||
const converter = CSS_UNITS_CONVERSION[`${unitFrom}-${unitTo}`];
|
||||
if (converter === undefined) {
|
||||
throw new Error(`Cannot convert '${unitFrom}' units into '${unitTo}' units !`);
|
||||
}
|
||||
return value * converter(cssProp, $target);
|
||||
}
|
||||
/**
|
||||
* Returns the numeric value and unit of a css value.
|
||||
*
|
||||
* e.g. fct('400ms') -> [400, 'ms']
|
||||
*
|
||||
* @param {string} value
|
||||
* @returns {Array|null}
|
||||
*/
|
||||
function _getNumericAndUnit(value) {
|
||||
const m = value.trim().match(/^([0-9.]+)(\w*)$/);
|
||||
if (!m) {
|
||||
return null;
|
||||
}
|
||||
return [m[1], m[2]];
|
||||
}
|
||||
/**
|
||||
* Checks if two css values are equal.
|
||||
*
|
||||
* @param {string} value1
|
||||
* @param {string} value2
|
||||
* @param {string} [cssProp] - the css property on which the unit applies
|
||||
* @param {jQuery} [$target] - the jQuery element on which that css property
|
||||
* may change
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function _areCssValuesEqual(value1, value2, cssProp, $target) {
|
||||
// If not colors, they will be left untouched
|
||||
value1 = ColorpickerDialog.normalizeCSSColor(value1);
|
||||
value2 = ColorpickerDialog.normalizeCSSColor(value2);
|
||||
|
||||
// String comparison first
|
||||
if (value1 === value2) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Convert the second value in the unit of the first one and compare
|
||||
// floating values
|
||||
const data = _getNumericAndUnit(value1);
|
||||
if (!data) {
|
||||
return false;
|
||||
}
|
||||
const numValue1 = data[0];
|
||||
const numValue2 = _convertValueToUnit(value2, data[1], cssProp, $target);
|
||||
return (Math.abs(numValue1 - numValue2) < Number.EPSILON);
|
||||
}
|
||||
|
||||
return {
|
||||
CSS_SHORTHANDS: CSS_SHORTHANDS,
|
||||
CSS_UNITS_CONVERSION: CSS_UNITS_CONVERSION,
|
||||
computePxByRem: _computePxByRem,
|
||||
convertValueToUnit: _convertValueToUnit,
|
||||
convertNumericToUnit: _convertNumericToUnit,
|
||||
getNumericAndUnit: _getNumericAndUnit,
|
||||
areCssValuesEqual: _areCssValuesEqual,
|
||||
};
|
||||
});
|
||||
@@ -767,6 +767,7 @@ return {
|
||||
odoo.define('web_editor.rte.summernote_custom_colors', function (require) {
|
||||
'use strict';
|
||||
|
||||
// These colors are already normalized as per normalizeCSSColor in web.ColorpickerDialog
|
||||
return [
|
||||
['#000000', '#424242', '#636363', '#9C9C94', '#CEC6CE', '#EFEFEF', '#F7F7F7', '#FFFFFF'],
|
||||
['#FF0000', '#FF9C00', '#FFFF00', '#00FF00', '#00FFFF', '#0000FF', '#9C00FF', '#FF00FF'],
|
||||
|
||||
@@ -4,6 +4,7 @@ odoo.define('web_editor.rte.summernote', function (require) {
|
||||
var Class = require('web.Class');
|
||||
const concurrency = require('web.concurrency');
|
||||
var core = require('web.core');
|
||||
const ColorpickerDialog = require('web.ColorpickerDialog');
|
||||
var ColorPaletteWidget = require('web_editor.ColorPalette').ColorPaletteWidget;
|
||||
var mixins = require('web.mixins');
|
||||
var fonts = require('wysiwyg.fonts');
|
||||
@@ -50,13 +51,17 @@ renderer.createPalette = function ($container, options) {
|
||||
const targetNode = r.sc;
|
||||
const targetElement = targetNode.nodeType === Node.ELEMENT_NODE ? targetNode : targetNode.parentNode;
|
||||
colorpicker = new ColorPaletteWidget(parent, {
|
||||
colorPrefix: eventName === "foreColor" ? 'text-' : 'bg-',
|
||||
excluded: ['transparent_grayscale'],
|
||||
$editable: rte.Class.prototype.editable(), // Our parent is the root widget, we can't retrieve the editable section from it...
|
||||
selectedColor: $(targetElement).css(eventName === "foreColor" ? 'color' : 'backgroundColor'),
|
||||
targetClasses: [...targetElement.classList],
|
||||
});
|
||||
colorpicker.on('color_picked custom_color_picked', null, ev => applyColor(ev.data.target, eventName, ev.data.cssColor));
|
||||
colorpicker.on('color_picked', null, ev => {
|
||||
let color = ev.data.color;
|
||||
if (!ColorpickerDialog.isCSSColor(color)) {
|
||||
color = (eventName === "foreColor" ? 'text-' : 'bg-') + color;
|
||||
}
|
||||
applyColor(ev.data.target, eventName, color);
|
||||
});
|
||||
colorpicker.on('color_reset', null, ev => applyColor(ev.data.target, eventName, 'inherit'));
|
||||
return colorpicker.replace(hookEl).then(() => {
|
||||
if (oldColorpicker) {
|
||||
|
||||
@@ -144,6 +144,15 @@ var SnippetEditor = Widget.extend({
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Checks whether the snippet options are shown or not.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
areOptionsShown: function () {
|
||||
const lastIndex = this._customize$Elements.length - 1;
|
||||
return !!this._customize$Elements[lastIndex].parent().length;
|
||||
},
|
||||
/**
|
||||
* Notifies all the associated snippet options that the snippet has just
|
||||
* been dropped in the page.
|
||||
@@ -165,6 +174,17 @@ var SnippetEditor = Widget.extend({
|
||||
option.cleanForSave();
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Closes all widgets of all options.
|
||||
*/
|
||||
closeWidgets: function () {
|
||||
if (!this.areOptionsShown()) {
|
||||
return;
|
||||
}
|
||||
Object.keys(this.styles).forEach(key => {
|
||||
this.styles[key].closeWidgets();
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Makes the editor overlay cover the associated snippet.
|
||||
*/
|
||||
@@ -288,9 +308,7 @@ var SnippetEditor = Widget.extend({
|
||||
return;
|
||||
}
|
||||
|
||||
var lastIndex = this._customize$Elements.length - 1;
|
||||
var optionsAlreadyShown = !!this._customize$Elements[lastIndex].parent().length;
|
||||
if (optionsAlreadyShown === show) {
|
||||
if (this.areOptionsShown() === show) {
|
||||
return;
|
||||
}
|
||||
this.trigger_up('update_customize_elements', {
|
||||
@@ -301,6 +319,7 @@ var SnippetEditor = Widget.extend({
|
||||
var styles = _.values(editor.styles);
|
||||
_.sortBy(styles, '__order').forEach(style => {
|
||||
if (show) {
|
||||
style.updateUI();
|
||||
style.onFocus();
|
||||
} else {
|
||||
style.onBlur();
|
||||
@@ -321,62 +340,6 @@ var SnippetEditor = Widget.extend({
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Transforms option UI description into actual DOM.
|
||||
*
|
||||
* @private
|
||||
* @param {jQuery} $el
|
||||
*/
|
||||
_createOptionUI: function ($el) {
|
||||
const $optionSection = $(core.qweb.render('web_editor.customize_block_option'));
|
||||
$optionSection.append($el);
|
||||
const uiEl = $optionSection[0];
|
||||
|
||||
// Build group first as their internal components will be built after
|
||||
uiEl.querySelectorAll('we-row').forEach(el => {
|
||||
const infos = this._extraInfoFromDescriptionElement(el);
|
||||
const groupEl = options.Class.prototype.buildRowElement(infos.title, infos.options);
|
||||
el.parentNode.insertBefore(groupEl, el);
|
||||
el.parentNode.removeChild(el);
|
||||
});
|
||||
|
||||
// Build standard components
|
||||
uiEl.querySelectorAll('we-select').forEach(el => {
|
||||
const infos = this._extraInfoFromDescriptionElement(el);
|
||||
const selectEl = options.Class.prototype.buildSelectElement(infos.title, infos.options);
|
||||
el.parentNode.insertBefore(selectEl, el);
|
||||
el.parentNode.removeChild(el);
|
||||
});
|
||||
uiEl.querySelectorAll('we-checkbox').forEach(el => {
|
||||
const infos = this._extraInfoFromDescriptionElement(el);
|
||||
const checkboxEl = options.Class.prototype.buildCheckboxElement(infos.title, infos.options);
|
||||
el.parentNode.insertBefore(checkboxEl, el);
|
||||
el.parentNode.removeChild(el);
|
||||
});
|
||||
uiEl.querySelectorAll('we-input').forEach(el => {
|
||||
const infos = this._extraInfoFromDescriptionElement(el);
|
||||
const inputEl = options.Class.prototype.buildInputElement(infos.title, infos.options);
|
||||
el.parentNode.insertBefore(inputEl, el);
|
||||
el.parentNode.removeChild(el);
|
||||
});
|
||||
|
||||
return $optionSection;
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {HTMLElement} el
|
||||
* @returns {Object}
|
||||
*/
|
||||
_extraInfoFromDescriptionElement: function (el) {
|
||||
return {
|
||||
title: el.getAttribute('string'),
|
||||
options: {
|
||||
classes: el.classList,
|
||||
dataAttributes: el.dataset,
|
||||
childNodes: [...el.childNodes],
|
||||
},
|
||||
};
|
||||
},
|
||||
/**
|
||||
* DOMElements have a default name which appears in the overlay when they
|
||||
* are being edited. This method retrieves this name; it can be defined
|
||||
@@ -418,7 +381,8 @@ var SnippetEditor = Widget.extend({
|
||||
var $optionsSection = $(core.qweb.render('web_editor.customize_block_options_section', {
|
||||
name: this._getName(),
|
||||
})).data('editor', this);
|
||||
$optionsSection.find('we-button-group').contents().each((i, node) => {
|
||||
const $optionsSectionBtnGroup = $optionsSection.find('we-button-group');
|
||||
$optionsSectionBtnGroup.contents().each((i, node) => {
|
||||
if (node.nodeType === Node.TEXT_NODE) {
|
||||
node.parentNode.removeChild(node);
|
||||
}
|
||||
@@ -447,13 +411,15 @@ var SnippetEditor = Widget.extend({
|
||||
}
|
||||
|
||||
var optionName = val.option;
|
||||
|
||||
var $ui = val.$el.children().clone(true);
|
||||
var option = new (options.registry[optionName] || options.Class)(
|
||||
this,
|
||||
val.$el.children(),
|
||||
val.base_target ? this.$target.find(val.base_target).eq(0) : this.$target,
|
||||
this.$el,
|
||||
_.extend({snippetName: this._getName()}, val.data),
|
||||
_.extend({
|
||||
optionName: optionName,
|
||||
snippetName: this._getName(),
|
||||
}, val.data),
|
||||
this.options
|
||||
);
|
||||
var key = optionName || _.uniqueId('option');
|
||||
@@ -465,8 +431,7 @@ var SnippetEditor = Widget.extend({
|
||||
}
|
||||
this.styles[key] = option;
|
||||
option.__order = i++;
|
||||
const $option = this._createOptionUI($ui).addClass('snippet-option-' + optionName);
|
||||
return option.attachTo($option);
|
||||
return option.appendTo(document.createDocumentFragment());
|
||||
});
|
||||
|
||||
this.isTargetMovable = (this.selectorSiblings.length > 0 || this.selectorChildren.length > 0);
|
||||
@@ -476,8 +441,8 @@ var SnippetEditor = Widget.extend({
|
||||
return Promise.all(defs).then(() => {
|
||||
const options = _.sortBy(this.styles, '__order');
|
||||
options.forEach(option => {
|
||||
if (option.isTopOption()) {
|
||||
$optionsSection.find('we-button-group').first().prepend(option.$el);
|
||||
if (option.isTopOption) {
|
||||
$optionsSectionBtnGroup.prepend(option.$el);
|
||||
} else {
|
||||
$optionsSection.append(option.$el);
|
||||
}
|
||||
@@ -720,7 +685,6 @@ var SnippetsMenu = Widget.extend({
|
||||
id: 'oe_snippets',
|
||||
cacheSnippetTemplate: {},
|
||||
events: {
|
||||
'click we-select': '_onOptionTogglerClick',
|
||||
'click .o_install_btn': '_onInstallBtnClick',
|
||||
},
|
||||
custom_events: {
|
||||
@@ -738,6 +702,7 @@ var SnippetsMenu = Widget.extend({
|
||||
'hide_overlay': '_onHideOverlay',
|
||||
'block_preview_overlays': '_onBlockPreviewOverlays',
|
||||
'unblock_preview_overlays': '_onUnblockPreviewOverlays',
|
||||
'user_value_widget_opening': '_onUserValueWidgetOpening',
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -1722,21 +1687,6 @@ var SnippetsMenu = Widget.extend({
|
||||
_onCleanForSaveDemand: function (ev) {
|
||||
this.cleanForSave();
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {*} ev
|
||||
*/
|
||||
_onOptionTogglerClick: function (ev) {
|
||||
var togglerEl = ev.currentTarget;
|
||||
if (togglerEl.tagName !== 'WE-TOGGLER') {
|
||||
togglerEl = togglerEl.querySelector('we-toggler');
|
||||
}
|
||||
|
||||
var $hierarchyTogglers = $(togglerEl).parents('we-select').children('we-toggler');
|
||||
this.$('we-select').children('we-toggler').not($hierarchyTogglers).removeClass('active');
|
||||
$hierarchyTogglers.not(togglerEl).addClass('active');
|
||||
togglerEl.classList.toggle('active');
|
||||
},
|
||||
/**
|
||||
* Called when the overlay dimensions/positions should be recomputed.
|
||||
*
|
||||
@@ -1890,6 +1840,13 @@ var SnippetsMenu = Widget.extend({
|
||||
this.customizePanel.classList.toggle('d-none', !customize);
|
||||
this.$('.o_we_customize_snippet_btn').toggleClass('active', customize);
|
||||
},
|
||||
/**
|
||||
* Called when an user value widget is being opened -> close all the other
|
||||
* user value widgets of all editors.
|
||||
*/
|
||||
_onUserValueWidgetOpening: function () {
|
||||
this.snippetEditors.forEach(editor => editor.closeWidgets());
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@ odoo.define('web_editor.ColorPalette', function (require) {
|
||||
'use strict';
|
||||
|
||||
const core = require('web.core');
|
||||
const ColorPickerDialog = require('web.ColorpickerDialog');
|
||||
const ColorpickerDialog = require('web.ColorpickerDialog');
|
||||
const Dialog = require('web.Dialog');
|
||||
const Widget = require('web.Widget');
|
||||
const summernoteCustomColors = require('web_editor.rte.summernote_custom_colors');
|
||||
@@ -27,8 +27,6 @@ const ColorPaletteWidget = Widget.extend({
|
||||
*
|
||||
* @param {Object} [options]
|
||||
* @param {string} [options.selectedColor] The class or css attribute color selected by default.
|
||||
* @param {string[]} [options.targetClasses]
|
||||
* @param {string} [options.colorPrefix='bg-'] Used for the class based colors (theme).
|
||||
* @param {boolean} [options.resetButton=true] Whether to display or not the reset button.
|
||||
* @param {string[]} [options.excluded=[]] Sections not to display.
|
||||
* @param {string[]} [options.excludeSectionOf] Extra section to exclude: the one containing the named color.
|
||||
@@ -36,17 +34,20 @@ const ColorPaletteWidget = Widget.extend({
|
||||
*/
|
||||
init: function (parent, options) {
|
||||
this._super.apply(this, arguments);
|
||||
this.summernoteCustomColorsArray = [].concat(...summernoteCustomColors).map((color) => this._convertHexToCssRgba(color));
|
||||
this.summernoteCustomColorsArray = [].concat(...summernoteCustomColors);
|
||||
this.options = _.extend({
|
||||
selectedColor: false,
|
||||
targetClasses: [],
|
||||
colorPrefix: 'bg-',
|
||||
resetButton: true,
|
||||
excluded: [],
|
||||
excludeSectionOf: null,
|
||||
$editable: $(),
|
||||
}, options || {});
|
||||
|
||||
const selectedColor = this.options.selectedColor;
|
||||
if (selectedColor) {
|
||||
this.options.selectedColor = ColorpickerDialog.normalizeCSSColor(selectedColor);
|
||||
}
|
||||
|
||||
this.trigger_up('request_editable', {callback: val => this.options.$editable = val});
|
||||
},
|
||||
/**
|
||||
@@ -107,7 +108,7 @@ const ColorPaletteWidget = Widget.extend({
|
||||
}
|
||||
|
||||
// Render custom colors
|
||||
this._buildCustomColor();
|
||||
this._buildCustomColors();
|
||||
|
||||
// TODO refactor in master
|
||||
// The primary and secondary are hardcoded here (but marked as hidden)
|
||||
@@ -119,31 +120,26 @@ const ColorPaletteWidget = Widget.extend({
|
||||
this.$el.append($('<button/>', {'class': 'd-none', 'data-color': 'secondary'}));
|
||||
|
||||
// Compute class colors
|
||||
this.classes = [];
|
||||
this.colorNames = [];
|
||||
const style = window.getComputedStyle(document.documentElement);
|
||||
this.el.querySelectorAll('button[data-color]').forEach(elem => {
|
||||
const color = elem.dataset.color;
|
||||
const colorName = elem.dataset.color;
|
||||
const color = ColorpickerDialog.normalizeCSSColor(style.getPropertyValue('--' + colorName).trim());
|
||||
const $color = $(elem);
|
||||
$color.addClass('bg-' + color);
|
||||
const className = this.options.colorPrefix + color;
|
||||
if (this.options.targetClasses.includes(className)) {
|
||||
$color.addClass('bg-' + colorName);
|
||||
if (this.options.selectedColor && (this.options.selectedColor === colorName || this.options.selectedColor === color)) {
|
||||
$color.addClass('selected');
|
||||
}
|
||||
this.classes.push(className);
|
||||
this.colorNames.push(colorName);
|
||||
});
|
||||
|
||||
return res;
|
||||
},
|
||||
/**
|
||||
* Return a list of the color classes used in the color palette
|
||||
* Return a list of the color names used in the color palette
|
||||
*/
|
||||
getClasses: function () {
|
||||
return this.classes;
|
||||
},
|
||||
/**
|
||||
* Reloads the color palette to get other custom colors on the page
|
||||
*/
|
||||
reloadColorPalette: function () {
|
||||
this._buildCustomColor();
|
||||
getColorNames: function () {
|
||||
return this.colorNames;
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -153,41 +149,47 @@ const ColorPaletteWidget = Widget.extend({
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_buildCustomColor: function () {
|
||||
const existingColors = new Set(this.summernoteCustomColorsArray.concat(Array.from(this.el.querySelectorAll('.o_custom_color')).map(el => el.style.backgroundColor)));
|
||||
if (!this.options.excluded.includes('custom')) {
|
||||
Array.from(this.options.$editable.find('[style*="color"]')).forEach(el => {
|
||||
for (const colorProp of ['color', 'backgroundColor']) {
|
||||
const color = el.style[colorProp];
|
||||
if (color && !existingColors.has(color)) {
|
||||
this._addCustomColorButton(color);
|
||||
existingColors.add(color);
|
||||
}
|
||||
}
|
||||
});
|
||||
_buildCustomColors: function () {
|
||||
if (this.options.excluded.includes('custom')) {
|
||||
return;
|
||||
}
|
||||
const existingColors = new Set(this.summernoteCustomColorsArray.concat(
|
||||
[...this.el.querySelectorAll('.o_custom_color')].map(el => {
|
||||
return ColorpickerDialog.normalizeCSSColor(el.style.backgroundColor);
|
||||
})
|
||||
));
|
||||
_.each(this.options.$editable.find('[style*="color"]'), el => {
|
||||
for (const colorProp of ['color', 'backgroundColor']) {
|
||||
const color = ColorpickerDialog.normalizeCSSColor(el.style[colorProp]);
|
||||
if (color && !existingColors.has(color)) {
|
||||
this._addCustomColorButton(color);
|
||||
existingColors.add(color);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Add a custom button in the coresponding section.
|
||||
*
|
||||
* @private
|
||||
* @param {string} color Css rgb(a) string
|
||||
* @param {Array<string>} classes Classes added to the button
|
||||
* @param {string} color
|
||||
* @param {string[]} classes - classes added to the button
|
||||
* @returns {jQuery}
|
||||
*/
|
||||
_addCustomColorButton: function (color, classes = []) {
|
||||
classes.push('o_custom_color');
|
||||
this._createColorButton(color, classes).appendTo(this.$('.o_colorpicker_section[data-name="theme"]'));
|
||||
const $themeSection = this.$('.o_colorpicker_section[data-name="theme"]');
|
||||
return this._createColorButton(color, classes).appendTo($themeSection);
|
||||
},
|
||||
/**
|
||||
* Return a color button.
|
||||
*
|
||||
* @param {string} color Css rgb(a) string
|
||||
* @param {Array<string>} classes Classes added to the button
|
||||
* @param {string} color
|
||||
* @param {string[]} classes - classes added to the button
|
||||
* @returns {jQuery}
|
||||
*/
|
||||
_createColorButton: function (color, classes) {
|
||||
const cssColor = this._convertHexToCssRgba(color);
|
||||
const selectedColor = this.options.selectedColor && this._convertHexToCssRgba(this.options.selectedColor);
|
||||
if (selectedColor === cssColor) {
|
||||
if (this.options.selectedColor === color) { // both colors are already normalized
|
||||
classes.push('selected');
|
||||
}
|
||||
return $('<button/>', {
|
||||
@@ -196,33 +198,19 @@ const ColorPaletteWidget = Widget.extend({
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Return the hexadecimal css color as an rgb(a) css color: rgb((r, g, b) | rgba(r, g, b, a)
|
||||
* Gets normalized information about a color button.
|
||||
*
|
||||
* @private
|
||||
* @param {string} hex Css hex string (#FFFFFF(FF))
|
||||
* @param {HTMLElement} buttonEl
|
||||
* @returns {Object}
|
||||
*/
|
||||
_convertHexToCssRgba: function (hex) {
|
||||
if (!/^#([0-9A-F]{6}|[0-9A-F]{8})$/i.test(hex)) {
|
||||
return hex;
|
||||
}
|
||||
|
||||
const red = parseInt(hex.substr(1, 2), 16);
|
||||
const blue = parseInt(hex.substr(3, 2), 16);
|
||||
const green = parseInt(hex.substr(5, 2), 16);
|
||||
if (hex.length === 9) {
|
||||
return _.str.sprintf('rgba(%s, %s, %s, %s)',
|
||||
red,
|
||||
blue,
|
||||
green,
|
||||
parseInt(hex.substr(7, 2), 16)
|
||||
);
|
||||
} else {
|
||||
return _.str.sprintf('rgb(%s, %s, %s)',
|
||||
red,
|
||||
blue,
|
||||
green
|
||||
);
|
||||
}
|
||||
_getButtonInfo: function (buttonEl) {
|
||||
const bgColor = buttonEl.style.backgroundColor;
|
||||
const color = bgColor ? ColorpickerDialog.normalizeCSSColor(bgColor) : buttonEl.dataset.color;
|
||||
return {
|
||||
color: color,
|
||||
target: buttonEl,
|
||||
};
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -236,14 +224,10 @@ const ColorPaletteWidget = Widget.extend({
|
||||
* @param {Event} ev
|
||||
*/
|
||||
_onColorButtonClick: function (ev) {
|
||||
const target = ev.currentTarget;
|
||||
const buttonEl = ev.currentTarget;
|
||||
this.$('button.selected').removeClass('selected');
|
||||
$(target).addClass('selected');
|
||||
this.trigger_up('color_picked', {
|
||||
cssColor: target.style.backgroundColor || this.options.colorPrefix + target.dataset.color,
|
||||
isClass: !!target.dataset.color,
|
||||
target: ev.target,
|
||||
});
|
||||
$(buttonEl).addClass('selected');
|
||||
this.trigger_up('color_picked', this._getButtonInfo(buttonEl));
|
||||
},
|
||||
/**
|
||||
* Called when a color button is entered.
|
||||
@@ -253,12 +237,7 @@ const ColorPaletteWidget = Widget.extend({
|
||||
*/
|
||||
_onColorButtonEnter: function (ev) {
|
||||
ev.stopPropagation();
|
||||
const target = ev.currentTarget;
|
||||
this.trigger_up('color_hover', {
|
||||
cssColor: target.style.backgroundColor || this.options.colorPrefix + target.dataset.color,
|
||||
isClass: !!target.dataset.color,
|
||||
target: ev.target,
|
||||
});
|
||||
this.trigger_up('color_hover', this._getButtonInfo(ev.currentTarget));
|
||||
},
|
||||
/**
|
||||
* Called when a color button is left.
|
||||
@@ -268,19 +247,16 @@ const ColorPaletteWidget = Widget.extend({
|
||||
*/
|
||||
_onColorButtonLeave: function (ev) {
|
||||
ev.stopPropagation();
|
||||
let params = {
|
||||
cssColor: '',
|
||||
isClass: false,
|
||||
target: ev.target,
|
||||
};
|
||||
const selected = this.el.querySelector('button.selected');
|
||||
let params = null;
|
||||
if (selected) {
|
||||
params = this._getButtonInfo(selected);
|
||||
} else {
|
||||
params = {
|
||||
cssColor: selected.style.backgroundColor || this.options.colorPrefix + selected.dataset.color,
|
||||
isClass: !!selected.dataset.color,
|
||||
target: ev.target,
|
||||
color: '',
|
||||
};
|
||||
}
|
||||
params.target = ev.target;
|
||||
this.trigger_up('color_leave', params);
|
||||
},
|
||||
/**
|
||||
@@ -302,17 +278,16 @@ const ColorPaletteWidget = Widget.extend({
|
||||
* @param {Event} ev
|
||||
*/
|
||||
_onCustomColorButtonClick: async function (ev) {
|
||||
const originalEvent = ev;
|
||||
const $selected = this.$('button.selected');
|
||||
const colorpicker = new ColorPickerDialog(this, {
|
||||
const target = ev.target;
|
||||
const $selected = this.$('button.selected').removeClass('selected');
|
||||
const colorpicker = new ColorpickerDialog(this, {
|
||||
defaultColor: this.options.defaultColor || $selected.css('background-color'),
|
||||
});
|
||||
colorpicker.on('colorpicker:saved', this, ev => {
|
||||
$selected.removeClass('selected');
|
||||
this._addCustomColorButton(ev.data.cssColor, ['selected']);
|
||||
this.trigger_up('custom_color_picked', {
|
||||
cssColor: ev.data.cssColor,
|
||||
target: originalEvent.target,
|
||||
this.trigger_up('color_picked', {
|
||||
color: ev.data.cssColor,
|
||||
target: target,
|
||||
});
|
||||
});
|
||||
colorpicker.open();
|
||||
|
||||
@@ -2,6 +2,12 @@
|
||||
/// This file regroups basic style rules for web_editor enable page edition and backend utils.
|
||||
///
|
||||
|
||||
:root {
|
||||
@each $color, $value in $grays {
|
||||
--#{$color}: #{$value};
|
||||
}
|
||||
}
|
||||
|
||||
html, body {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
@@ -297,26 +297,6 @@ body.editor_enable.editor_has_snippets {
|
||||
@include we-icon-button('\f107');
|
||||
}
|
||||
|
||||
we-row {
|
||||
display: block;
|
||||
|
||||
> div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
> * {
|
||||
flex: 1 1 auto;
|
||||
|
||||
+ * {
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
}
|
||||
> .fa, > .fas, > .fab, > .far {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
we-select {
|
||||
position: relative;
|
||||
display: flex;
|
||||
@@ -374,13 +354,8 @@ body.editor_enable.editor_has_snippets {
|
||||
}
|
||||
|
||||
we-input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@extend we-row.o_we_inline;
|
||||
|
||||
> we-title {
|
||||
min-width: 60%;
|
||||
margin-right: 5%;
|
||||
}
|
||||
> input {
|
||||
flex: 1 1 auto;
|
||||
width: 0;
|
||||
@@ -398,6 +373,50 @@ body.editor_enable.editor_has_snippets {
|
||||
}
|
||||
}
|
||||
|
||||
we-row {
|
||||
position: relative;
|
||||
display: block;
|
||||
|
||||
> div {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
> * {
|
||||
flex: 1 1 auto;
|
||||
|
||||
+ * {
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
}
|
||||
> .fa, > .fas, > .fab, > .far {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
&.o_we_inline {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
> we-title {
|
||||
min-width: 60%;
|
||||
margin-right: 5%;
|
||||
}
|
||||
}
|
||||
|
||||
we-select {
|
||||
position: static;
|
||||
}
|
||||
|
||||
we-input {
|
||||
flex-wrap: wrap;
|
||||
|
||||
> we-title {
|
||||
min-width: 95%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
// FIXME temporary fix for m2o option for example
|
||||
position: static !important;
|
||||
|
||||
@@ -31,9 +31,6 @@
|
||||
</we-title>
|
||||
</we-customizeblock-options>
|
||||
</t>
|
||||
<t t-name="web_editor.customize_block_option">
|
||||
<we-customizeblock-option/>
|
||||
</t>
|
||||
|
||||
<!-- options -->
|
||||
<div t-name="web_editor.snippet.option.colorpicker" class="colorpicker">
|
||||
|
||||
@@ -78,7 +78,9 @@ MockServer.include({
|
||||
' <div data-js="content" data-selector=".s_hr, .test_option_all" ' +
|
||||
'data-drop-in=".note-editable" data-drop-near="p, h1, h2, h3, blockquote, .s_hr"></div>' +
|
||||
' <div data-js="sizing_y" data-selector=".s_hr, .test_option_all"></div>' +
|
||||
' <div data-js="colorpicker" string="Background Color" data-selector=".test_option_all"></div>' +
|
||||
' <div data-selector=".test_option_all">' +
|
||||
' <we-colorpicker string="Background Color" data-select-style="true" data-css-property="background-color" data-color-prefix="bg-"/>' +
|
||||
' </div>' +
|
||||
' <div data-js="background" data-selector=".test_option_all">' +
|
||||
' <we-button data-choose-image="true" data-no-preview="true"><i class="far fa-image"/> Background Image</we-button>' +
|
||||
' </div>' +
|
||||
@@ -245,7 +247,9 @@ function wysiwygData(data) {
|
||||
' <div data-js="content" data-selector=".s_hr, .test_option_all" ' +
|
||||
'data-drop-in=".note-editable" data-drop-near="p, h1, h2, h3, blockquote, .s_hr"></div>' +
|
||||
' <div data-js="sizing_y" data-selector=".s_hr, .test_option_all"></div>' +
|
||||
' <div data-js="colorpicker" string="Background Color" data-selector=".test_option_all"></div>' +
|
||||
' <div data-selector=".test_option_all">' +
|
||||
' <we-colorpicker string="Background Color" data-select-style="true" data-css-property="background-color" data-color-prefix="bg-"/>' +
|
||||
' </div>' +
|
||||
' <div data-js="background" data-selector=".test_option_all">' +
|
||||
' <we-button data-choose-image="true" data-no-preview="true"><i class="far fa-image"/> Background Image</we-button>' +
|
||||
' </div>' +
|
||||
|
||||
@@ -131,6 +131,7 @@
|
||||
<xpath expr="script[last()]" position="after">
|
||||
<script type="text/javascript" src="/web_editor/static/lib/vkbeautify/vkbeautify.0.99.00.beta.js"/>
|
||||
<script type="text/javascript" src="/web_editor/static/src/js/common/ace.js"/>
|
||||
<script type="text/javascript" src="/web_editor/static/src/js/common/utils.js"/>
|
||||
<script type="text/javascript" src="/web_editor/static/src/js/wysiwyg/root.js"/>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
@@ -881,7 +881,7 @@ registry.gallery = publicWidget.Widget.extend({
|
||||
srcs: urls,
|
||||
index: idx,
|
||||
dim: dimensions,
|
||||
interval: milliseconds,
|
||||
interval: milliseconds || 0,
|
||||
id: _.uniqueId('slideshow_'),
|
||||
}));
|
||||
$modal.modal({
|
||||
|
||||
@@ -9,8 +9,21 @@ var options = require('web_editor.snippets.options');
|
||||
var _t = core._t;
|
||||
var qweb = core.qweb;
|
||||
|
||||
// TODO should we refresh public widgets for all option changes by default ?
|
||||
options.Class.include({
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Options
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
selectDataAttribute: function (previewMode, widgetValue, params) {
|
||||
this._super(...arguments);
|
||||
this._refreshPublicWidgets();
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -66,19 +79,20 @@ options.registry.background.include({
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_setActive: function () {
|
||||
this._super(...arguments);
|
||||
if (this._hasBgvideo()) {
|
||||
this.$el.find('[data-choose-image]').addClass('active');
|
||||
_computeWidgetState: function (methodName, params) {
|
||||
if (methodName === 'chooseImage') {
|
||||
return this._hasBgvideo() ? 'true' : '';
|
||||
}
|
||||
return this._super(...arguments);
|
||||
},
|
||||
/**
|
||||
* Updates the background video used by the snippet.
|
||||
*
|
||||
* @private
|
||||
* @see this.selectClass for parameters
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_setBgVideo: function (previewMode, value) {
|
||||
_setBgVideo: async function (previewMode, value) {
|
||||
this.$('> .o_bg_video_container').toggleClass('d-none', previewMode === true);
|
||||
|
||||
if (previewMode !== false) {
|
||||
@@ -93,7 +107,7 @@ options.registry.background.include({
|
||||
delete target.dataset.bgVideoSrc;
|
||||
}
|
||||
this._refreshPublicWidgets();
|
||||
this._updateUI();
|
||||
await this.updateUI();
|
||||
},
|
||||
/**
|
||||
* Returns whether the current target has a background video or not.
|
||||
@@ -122,15 +136,16 @@ options.registry.background.include({
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_onSaveMediaDialog: function (data) {
|
||||
_onSaveMediaDialog: async function (data) {
|
||||
if (!data.bgVideoSrc) {
|
||||
this._setBgVideo(false);
|
||||
this._super(...arguments);
|
||||
return;
|
||||
const _super = this._super.bind(this);
|
||||
const args = arguments;
|
||||
await this._setBgVideo(false);
|
||||
return _super(...args);
|
||||
}
|
||||
// if the user chose a video, only add the video without removing the
|
||||
// background
|
||||
this._setBgVideo(false, data.bgVideoSrc);
|
||||
await this._setBgVideo(false, data.bgVideoSrc);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -309,6 +324,8 @@ options.registry.Carousel = options.Class.extend({
|
||||
});
|
||||
|
||||
options.registry.CarouselItem = options.Class.extend({
|
||||
isTopOption: true,
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
@@ -333,18 +350,18 @@ options.registry.CarouselItem = options.Class.extend({
|
||||
this._super(...arguments);
|
||||
this.$carousel.off('.carousel_item_option');
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
isTopOption: function () {
|
||||
return true;
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Updates the slide counter.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
onFocus: function () {
|
||||
updateUI: function () {
|
||||
this._super(...arguments);
|
||||
const $items = this.$carousel.find('.carousel-item');
|
||||
const $activeSlide = $items.filter('.active');
|
||||
const updatedText = ` (${$activeSlide.index() + 1}/${$items.length})`;
|
||||
@@ -400,8 +417,8 @@ options.registry.CarouselItem = options.Class.extend({
|
||||
*
|
||||
* @see this.selectClass for parameters
|
||||
*/
|
||||
slide: function (previewMode, value) {
|
||||
switch (value) {
|
||||
slide: function (previewMode, widgetValue, params) {
|
||||
switch (widgetValue) {
|
||||
case 'left':
|
||||
this.$controls.filter('.carousel-control-prev')[0].click();
|
||||
break;
|
||||
@@ -442,7 +459,7 @@ options.registry.navTabs = options.Class.extend({
|
||||
*
|
||||
* @see this.selectClass for parameters
|
||||
*/
|
||||
addTab: function (previewMode, value, $opt) {
|
||||
addTab: function (previewMode, widgetValue, params) {
|
||||
var $activeItem = this.$navLinks.filter('.active').parent();
|
||||
var $activePane = this.$tabPanes.filter('.active');
|
||||
|
||||
@@ -461,7 +478,7 @@ options.registry.navTabs = options.Class.extend({
|
||||
*
|
||||
* @see this.selectClass for parameters
|
||||
*/
|
||||
removeTab: function (previewMode, value, $opt) {
|
||||
removeTab: function (previewMode, widgetValue, params) {
|
||||
var self = this;
|
||||
|
||||
var $activeLink = this.$navLinks.filter('.active');
|
||||
@@ -472,11 +489,23 @@ options.registry.navTabs = options.Class.extend({
|
||||
$activeLink.parent().remove();
|
||||
$activePane.remove();
|
||||
self._findLinksAndPanes();
|
||||
self._updateUI(); // TODO forced to do this because we do not return deferred for options
|
||||
self.updateUI(); // TODO forced to do this because we do not return deferred for options
|
||||
});
|
||||
$next.tab('show');
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
updateUI: async function () {
|
||||
await this._super(...arguments);
|
||||
this.$el.filter('[data-remove-tab]').toggleClass('d-none', this.$tabPanes.length <= 2);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -496,7 +525,7 @@ options.registry.navTabs = options.Class.extend({
|
||||
* @private
|
||||
*/
|
||||
_generateUniqueIDs: function () {
|
||||
for (var i = 0 ; i < this.$navLinks.length ; i++) {
|
||||
for (var i = 0; i < this.$navLinks.length; i++) {
|
||||
var id = _.now() + '_' + _.uniqueId();
|
||||
var idLink = 'nav_tabs_link_' + id;
|
||||
var idContent = 'nav_tabs_content_' + id;
|
||||
@@ -511,14 +540,6 @@ options.registry.navTabs = options.Class.extend({
|
||||
});
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @override
|
||||
*/
|
||||
_updateUI: function () {
|
||||
this._super.apply(this, arguments);
|
||||
this.$el.filter('[data-remove-tab]').toggleClass('d-none', this.$tabPanes.length <= 2);
|
||||
},
|
||||
});
|
||||
|
||||
options.registry.sizing_x = options.registry.sizing.extend({
|
||||
@@ -547,8 +568,8 @@ options.registry.sizing_x = options.registry.sizing.extend({
|
||||
var gridE = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
|
||||
var gridW = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
|
||||
this.grid = {
|
||||
e: [_.map(gridE, function (v) { return 'col-lg-' + v; }), _.map(gridE, function (v) { return width/12*v; }), 'width'],
|
||||
w: [_.map(gridW, function (v) { return 'offset-lg-' + v; }), _.map(gridW, function (v) { return width/12*v; }), 'margin-left'],
|
||||
e: [_.map(gridE, v => ('col-lg-' + v)), _.map(gridE, v => width / 12 * v), 'width'],
|
||||
w: [_.map(gridW, v => ('offset-lg-' + v)), _.map(gridW, v => width / 12 * v), 'margin-left'],
|
||||
};
|
||||
return this.grid;
|
||||
},
|
||||
@@ -569,7 +590,7 @@ options.registry.sizing_x = options.registry.sizing.extend({
|
||||
colSize = 1;
|
||||
offset = beginOffset + beginCol - 1;
|
||||
}
|
||||
this.$target.attr('class',this.$target.attr('class').replace(/\s*(offset-xl-|offset-lg-|col-lg-)([0-9-]+)/g, ''));
|
||||
this.$target.attr('class', this.$target.attr('class').replace(/\s*(offset-xl-|offset-lg-|col-lg-)([0-9-]+)/g, ''));
|
||||
|
||||
this.$target.addClass('col-lg-' + (colSize > 12 ? 12 : colSize));
|
||||
if (offset > 0) {
|
||||
@@ -591,14 +612,24 @@ options.registry.layout_column = options.Class.extend({
|
||||
*
|
||||
* @see this.selectClass for parameters
|
||||
*/
|
||||
selectCount: function (previewMode, value, $opt) {
|
||||
this._updateColumnCount(value - this.$target.children().length);
|
||||
selectCount: function (previewMode, widgetValue, params) {
|
||||
const nbColumns = parseInt(widgetValue);
|
||||
this._updateColumnCount(nbColumns - this.$target.children().length);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_computeWidgetState: function (methodName, params) {
|
||||
if (methodName === 'selectCount') {
|
||||
return '' + this.$target.children().length;
|
||||
}
|
||||
return this._super(...arguments);
|
||||
},
|
||||
/**
|
||||
* Adds new columns which are clones of the last column or removes the
|
||||
* last x columns.
|
||||
@@ -615,7 +646,7 @@ options.registry.layout_column = options.Class.extend({
|
||||
|
||||
if (count > 0) {
|
||||
var $lastColumn = this.$target.children().last();
|
||||
for (var i = 0 ; i < count ; i++) {
|
||||
for (var i = 0; i < count; i++) {
|
||||
$lastColumn.clone().insertAfter($lastColumn);
|
||||
}
|
||||
} else {
|
||||
@@ -648,14 +679,6 @@ options.registry.layout_column = options.Class.extend({
|
||||
$columns.first().addClass('offset-lg-' + colOffset);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_setActive: function () {
|
||||
this._super.apply(this, arguments);
|
||||
this.$el.find('[data-select-count]').removeClass('active')
|
||||
.filter('[data-select-count=' + this.$target.children().length + ']').addClass('active');
|
||||
},
|
||||
});
|
||||
|
||||
options.registry.parallax = options.Class.extend({
|
||||
@@ -690,33 +713,6 @@ options.registry.parallax = options.Class.extend({
|
||||
onMove: function () {
|
||||
this._refreshPublicWidgets();
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Options
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Changes the scrolling speed of the parallax effect.
|
||||
*
|
||||
* @see this.selectClass for parameters
|
||||
*/
|
||||
scroll: function (previewMode, value) {
|
||||
this.$target.attr('data-scroll-background-ratio', value);
|
||||
this._refreshPublicWidgets();
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_setActive: function () {
|
||||
this._super.apply(this, arguments);
|
||||
this.$el.find('[data-scroll]').removeClass('active')
|
||||
.filter('[data-scroll="' + (this.$target.attr('data-scroll-background-ratio') || 0) + '"]').addClass('active');
|
||||
},
|
||||
});
|
||||
|
||||
var FacebookPageDialog = weWidgets.Dialog.extend({
|
||||
@@ -760,7 +756,7 @@ var FacebookPageDialog = weWidgets.Dialog.extend({
|
||||
*/
|
||||
_renderPreview: function () {
|
||||
var self = this;
|
||||
var match = this.fbData.href.match(/^(?:https?:\/\/)?(?:www\.)?(?:fb|facebook)\.com\/(?:([\w.]+)|[^/?#]+-([0-9]{15,16}))(?:$|[\/?# ])/);
|
||||
var match = this.fbData.href.match(/^(?:https?:\/\/)?(?:www\.)?(?:fb|facebook)\.com\/(?:([\w.]+)|[^/?#]+-([0-9]{15,16}))(?:$|[/?# ])/);
|
||||
if (match) {
|
||||
// Check if the page exists on Facebook or not
|
||||
$.ajax({
|
||||
@@ -815,7 +811,7 @@ var FacebookPageDialog = weWidgets.Dialog.extend({
|
||||
_onOptionChange: function () {
|
||||
var self = this;
|
||||
// Update values in fbData
|
||||
this.fbData.tabs = _.map(this.$('.o_facebook_tabs input:checked'), function (tab) { return tab.name; }).join(',');
|
||||
this.fbData.tabs = _.map(this.$('.o_facebook_tabs input:checked'), tab => tab.name).join(',');
|
||||
this.fbData.href = this.$('.o_facebook_page_url').val();
|
||||
_.each(this.$('.o_facebook_options input'), function (el) {
|
||||
self.fbData[el.name] = $(el).prop('checked');
|
||||
@@ -942,7 +938,7 @@ options.registry.ul = options.Class.extend({
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
toggleClass: function () {
|
||||
selectClass: function () {
|
||||
this._super.apply(this, arguments);
|
||||
|
||||
this.trigger_up('widgets_stop_request', {
|
||||
@@ -952,8 +948,8 @@ options.registry.ul = options.Class.extend({
|
||||
this.$target.find('.o_ul_toggle_self, .o_ul_toggle_next').remove();
|
||||
this.$target.find('li:has(>ul,>ol)').map(function () {
|
||||
// get if the li contain a text label
|
||||
var texts = _.filter(_.toArray(this.childNodes), function (a) { return a.nodeType === 3;});
|
||||
if (!texts.length || !texts.reduce(function (a,b) { return a.textContent + b.textContent;}).match(/\S/)) {
|
||||
var texts = _.filter(_.toArray(this.childNodes), a => (a.nodeType === 3));
|
||||
if (!texts.length || !texts.reduce((a, b) => (a.textContent + b.textContent)).match(/\S/)) {
|
||||
return;
|
||||
}
|
||||
$(this).children('ul,ol').addClass('o_close');
|
||||
@@ -962,7 +958,7 @@ options.registry.ul = options.Class.extend({
|
||||
.prepend('<a href="#" class="o_ul_toggle_self fa" />');
|
||||
var $li = this.$target.find('li:has(+li:not(>.o_ul_toggle_self)>ul, +li:not(>.o_ul_toggle_self)>ol)');
|
||||
$li.css('list-style', this.$target.hasClass('o_ul_folded') ? 'none' : '');
|
||||
$li.map(function () { return $(this).children()[0] || this; })
|
||||
$li.map((i, el) => ($(el).children()[0] || el))
|
||||
.prepend('<a href="#" class="o_ul_toggle_next fa" />');
|
||||
$li.removeClass('o_open').next().addClass('o_close');
|
||||
this.$target.find('li').removeClass('o_open');
|
||||
@@ -1003,7 +999,7 @@ options.registry.collapse = options.Class.extend({
|
||||
var $panel = this.$target.find('.collapse').removeData('bs.collapse');
|
||||
if ($panel.attr('aria-expanded') === 'true') {
|
||||
$panel.closest('.accordion').find('.collapse[aria-expanded="true"]')
|
||||
.filter(function () {return this !== $panel[0];})
|
||||
.filter((i, el) => (el !== $panel[0]))
|
||||
.collapse('hide')
|
||||
.one('hidden.bs.collapse', function () {
|
||||
$panel.trigger('shown.bs.collapse');
|
||||
@@ -1031,20 +1027,20 @@ options.registry.collapse = options.Class.extend({
|
||||
tablist_id = 'myCollapse' + time;
|
||||
$tablist.attr('id', tablist_id);
|
||||
}
|
||||
$tab.attr('data-parent', '#'+tablist_id);
|
||||
$tab.data('parent', '#'+tablist_id);
|
||||
$tab.attr('data-parent', '#' + tablist_id);
|
||||
$tab.data('parent', '#' + tablist_id);
|
||||
|
||||
// link to the collapse
|
||||
var $panel = this.$target.find('.collapse');
|
||||
var panel_id = $panel.attr('id');
|
||||
if (!panel_id) {
|
||||
while ($('#'+(panel_id = 'myCollapseTab' + time)).length) {
|
||||
while ($('#' + (panel_id = 'myCollapseTab' + time)).length) {
|
||||
time++;
|
||||
}
|
||||
$panel.attr('id', panel_id);
|
||||
}
|
||||
$tab.attr('data-target', '#'+panel_id);
|
||||
$tab.data('target', '#'+panel_id);
|
||||
$tab.attr('data-target', '#' + panel_id);
|
||||
$tab.data('target', '#' + panel_id);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1125,16 +1121,16 @@ options.registry.gallery = options.Class.extend({
|
||||
var lastImage = _.last(this._getImages());
|
||||
var index = lastImage ? this._getIndex(lastImage) : -1;
|
||||
dialog.on('save', this, function (attachments) {
|
||||
for (var i = 0 ; i < attachments.length; i++) {
|
||||
for (var i = 0; i < attachments.length; i++) {
|
||||
$('<img/>', {
|
||||
class: 'img img-fluid',
|
||||
src: attachments[i].image_src,
|
||||
'data-index': ++index,
|
||||
}).appendTo($container);
|
||||
}
|
||||
self._reset();
|
||||
self.trigger_up('cover_update');
|
||||
this._updateUI();
|
||||
this.mode('reset', this.getMode());
|
||||
this.trigger_up('cover_update');
|
||||
this.updateUI();
|
||||
});
|
||||
dialog.open();
|
||||
},
|
||||
@@ -1144,11 +1140,11 @@ options.registry.gallery = options.Class.extend({
|
||||
*
|
||||
* @see this.selectClass for parameters
|
||||
*/
|
||||
columns: function (previewMode, value) {
|
||||
this.$target.attr('data-columns', value);
|
||||
columns: function (previewMode, widgetValue, params) {
|
||||
const nbColumns = parseInt(widgetValue || '1');
|
||||
this.$target.attr('data-columns', nbColumns);
|
||||
|
||||
var $activeMode = this.$el.find('.active[data-mode]');
|
||||
this.mode(null, $activeMode.data('mode'), $activeMode);
|
||||
this.mode(previewMode, this.getMode(), {}); // TODO improve
|
||||
},
|
||||
/**
|
||||
* Get the image target's layout mode (slideshow, masonry, grid or nomode).
|
||||
@@ -1193,8 +1189,8 @@ options.registry.gallery = options.Class.extend({
|
||||
* Allows to changes the interval of automatic slideshow (not active in
|
||||
* edit mode).
|
||||
*/
|
||||
interval: function (previewMode, value) {
|
||||
this.$target.find('.carousel:first').attr('data-interval', value);
|
||||
interval: function (previewMode, widgetValue) {
|
||||
this.$target.find('.carousel:first').attr('data-interval', widgetValue || '0');
|
||||
},
|
||||
/**
|
||||
* Displays the images with the "masonry" layout.
|
||||
@@ -1237,12 +1233,13 @@ options.registry.gallery = options.Class.extend({
|
||||
*
|
||||
* @see this.selectClass for parameters
|
||||
*/
|
||||
mode: function (previewMode, value, $opt) {
|
||||
mode: function (previewMode, widgetValue, params) {
|
||||
widgetValue = widgetValue || 'slideshow'; // FIXME should not be needed
|
||||
this.$target.css('height', '');
|
||||
this[value]();
|
||||
this[widgetValue]();
|
||||
this.$target
|
||||
.removeClass('o_nomode o_masonry o_grid o_slideshow')
|
||||
.addClass('o_' + value);
|
||||
.addClass('o_' + widgetValue);
|
||||
this.trigger_up('cover_update');
|
||||
},
|
||||
/**
|
||||
@@ -1293,10 +1290,10 @@ options.registry.gallery = options.Class.extend({
|
||||
});
|
||||
var currentInterval = this.$target.find('.carousel:first').attr('data-interval');
|
||||
var params = {
|
||||
srcs : urls,
|
||||
srcs: urls,
|
||||
index: 0,
|
||||
title: "",
|
||||
interval : currentInterval || this.$target.data('interval') || 0,
|
||||
interval: currentInterval || this.$target.data('interval') || 0,
|
||||
id: 'slideshow_' + new Date().getTime(),
|
||||
userStyle: imgStyle,
|
||||
},
|
||||
@@ -1333,11 +1330,11 @@ options.registry.gallery = options.Class.extend({
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
notify: function (name, data) {
|
||||
this._super.apply(this, arguments);
|
||||
notify: async function (name, data) {
|
||||
await this._super(...arguments);
|
||||
if (name === 'image_removed') {
|
||||
data.$image.remove(); // Force the removal of the image before reset
|
||||
this._reset();
|
||||
this.mode('reset', this.getMode());
|
||||
} else if (name === 'image_index_request') {
|
||||
var imgs = this._getImages();
|
||||
var position = _.indexOf(imgs, data.$image[0]);
|
||||
@@ -1362,14 +1359,67 @@ options.registry.gallery = options.Class.extend({
|
||||
// indexes were not the same as positions.
|
||||
$(img).attr('data-index', index);
|
||||
});
|
||||
this._reset();
|
||||
this.mode('reset', this.getMode());
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
updateUI: async function () {
|
||||
await this._super(...arguments);
|
||||
|
||||
this.$el.find('[data-interval]').closest('we-select')[0]
|
||||
.classList.toggle('d-none', this.activeMode !== 'slideshow');
|
||||
|
||||
this.$el.find('[data-columns]').closest('we-select')[0]
|
||||
.classList.toggle('d-none', !(this.activeMode === 'grid' || this.activeMode === 'masonry'));
|
||||
|
||||
this.el.querySelector('.o_w_image_spacing_option')
|
||||
.classList.toggle('d-none', this.activeMode === 'slideshow');
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_computeWidgetState: function (methodName, params) {
|
||||
switch (methodName) {
|
||||
case 'mode': {
|
||||
let activeModeName = 'slideshow';
|
||||
for (const modeName of params.possibleValues) {
|
||||
if (this.$target.hasClass(`o_${modeName}`)) {
|
||||
activeModeName = modeName;
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.activeMode = activeModeName;
|
||||
return activeModeName;
|
||||
}
|
||||
case 'interval': {
|
||||
const carousel = this.$target[0].querySelector('.carousel');
|
||||
return (carousel && carousel.dataset.interval || '0');
|
||||
}
|
||||
case 'columns': {
|
||||
return `${this._getColumns()}`;
|
||||
}
|
||||
case 'styling': {
|
||||
const img = this.$target[0].querySelector('img');
|
||||
if (!img) {
|
||||
return '';
|
||||
}
|
||||
for (const className of params.possibleValues) {
|
||||
if (className && img.classList.contains(className)) {
|
||||
return className;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
return this._super(...arguments);
|
||||
},
|
||||
/**
|
||||
* Returns the images, sorted by index.
|
||||
*
|
||||
@@ -1415,61 +1465,6 @@ options.registry.gallery = options.Class.extend({
|
||||
$container.empty().append($content);
|
||||
return $container;
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_setActive: function () {
|
||||
this._super(...arguments);
|
||||
|
||||
var activeModeSelectors = [];
|
||||
for (const className of this.$target[0].classList) {
|
||||
if (className.startsWith('o_')) {
|
||||
activeModeSelectors.push('[data-mode="' + className.substring(2) + '"]');
|
||||
}
|
||||
}
|
||||
var activeMode = this.$el.find('[data-mode]')
|
||||
.removeClass('active')
|
||||
.filter(activeModeSelectors.join(', '))
|
||||
.addClass('active')
|
||||
.data('mode');
|
||||
|
||||
var carousel = this.$target[0].querySelector('.carousel');
|
||||
var activeInterval = carousel ? (carousel.dataset.interval || 0) : undefined;
|
||||
var $intervalOptions = this.$el.find('[data-interval]');
|
||||
$intervalOptions.removeClass('active')
|
||||
.filter('[data-interval="' + activeInterval + '"]')
|
||||
.addClass('active');
|
||||
$intervalOptions.closest('we-select')[0]
|
||||
.classList.toggle('d-none', activeMode !== 'slideshow');
|
||||
|
||||
var columns = this._getColumns();
|
||||
var $columnOptions = this.$el.find('[data-columns]');
|
||||
$columnOptions.removeClass('active')
|
||||
.filter('[data-columns="' + columns + '"]')
|
||||
.addClass('active');
|
||||
$columnOptions.closest('we-select')[0]
|
||||
.classList.toggle('d-none', !(activeMode === 'grid' || activeMode === 'masonry'));
|
||||
|
||||
this.el.querySelector('.o_w_image_spacing_option')
|
||||
.classList.toggle('d-none', activeMode === 'slideshow');
|
||||
|
||||
var $stylingOptions = this.$el.find('[data-styling]');
|
||||
$stylingOptions.removeClass('active');
|
||||
var img = this.$target[0].querySelector('img');
|
||||
var activeStyleSelectors = [];
|
||||
if (img) {
|
||||
for (const className of img.classList) {
|
||||
activeStyleSelectors.push('[data-styling="' + className + '"]');
|
||||
}
|
||||
}
|
||||
var $toEnable = activeStyleSelectors.length
|
||||
? $stylingOptions.filter(activeStyleSelectors.join(', '))
|
||||
: null;
|
||||
if (!$toEnable || !$toEnable.length) {
|
||||
$toEnable = $stylingOptions.first();
|
||||
}
|
||||
$toEnable.addClass('active');
|
||||
},
|
||||
});
|
||||
|
||||
options.registry.gallery_img = options.Class.extend({
|
||||
@@ -1497,14 +1492,14 @@ options.registry.gallery_img = options.Class.extend({
|
||||
*
|
||||
* @see this.selectClass for parameters
|
||||
*/
|
||||
position: function (previewMode, value) {
|
||||
position: function (previewMode, widgetValue, params) {
|
||||
this.trigger_up('deactivate_snippet');
|
||||
this.trigger_up('option_update', {
|
||||
optionName: 'gallery',
|
||||
name: 'image_index_request',
|
||||
data: {
|
||||
$image: this.$target,
|
||||
position: value,
|
||||
position: widgetValue,
|
||||
},
|
||||
});
|
||||
},
|
||||
@@ -1521,7 +1516,7 @@ options.registry.topMenuTransparency = options.Class.extend({
|
||||
*
|
||||
* @see this.selectClass for params
|
||||
*/
|
||||
transparent: function (previewMode, value, $opt) {
|
||||
transparent: function (previewMode, widgetValue, params) {
|
||||
var self = this;
|
||||
this.trigger_up('action_demand', {
|
||||
actionName: 'toggle_page_option',
|
||||
@@ -1542,20 +1537,21 @@ options.registry.topMenuTransparency = options.Class.extend({
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_setActive: function () {
|
||||
this._super.apply(this, arguments);
|
||||
|
||||
this.trigger_up('action_demand', {
|
||||
actionName: 'get_page_option',
|
||||
params: ['header_overlay'],
|
||||
onSuccess: value => {
|
||||
this.$el.find('[data-transparent]').toggleClass('active', !!value);
|
||||
},
|
||||
});
|
||||
_computeWidgetState: function (methodName, params) {
|
||||
if (methodName === 'transparent') {
|
||||
return new Promise(resolve => {
|
||||
this.trigger_up('action_demand', {
|
||||
actionName: 'get_page_option',
|
||||
params: ['header_overlay'],
|
||||
onSuccess: v => resolve(v ? 'true' : ''),
|
||||
});
|
||||
});
|
||||
}
|
||||
return this._super(...arguments);
|
||||
},
|
||||
});
|
||||
|
||||
options.registry.topMenuColor = options.registry.colorpicker.extend({
|
||||
options.registry.topMenuColor = options.Class.extend({
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
@@ -1567,10 +1563,16 @@ options.registry.topMenuColor = options.registry.colorpicker.extend({
|
||||
});
|
||||
return def;
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
onFocus: function () {
|
||||
updateUI: function () {
|
||||
this._super(...arguments);
|
||||
this.trigger_up('action_demand', {
|
||||
actionName: 'get_page_option',
|
||||
params: ['header_overlay'],
|
||||
@@ -1604,6 +1606,7 @@ options.registry.topMenuColor = options.registry.colorpicker.extend({
|
||||
*/
|
||||
options.registry.anchor = options.Class.extend({
|
||||
xmlDependencies: ['/website/static/src/xml/website.editor.xml'],
|
||||
isTopOption: true,
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
@@ -1627,12 +1630,6 @@ options.registry.anchor = options.Class.extend({
|
||||
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
isTopOption: function () {
|
||||
return true;
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
@@ -1646,7 +1643,7 @@ options.registry.anchor = options.Class.extend({
|
||||
/**
|
||||
* @see this.selectClass for parameters
|
||||
*/
|
||||
openAnchorDialog: function (previewMode, value, $opt) {
|
||||
openAnchorDialog: function (previewMode, widgetValue, params) {
|
||||
var self = this;
|
||||
var buttons = [{
|
||||
text: _t("Save & copy"),
|
||||
@@ -1773,14 +1770,14 @@ options.registry.CoverProperties = options.Class.extend({
|
||||
/**
|
||||
* @see this.selectClass for parameters
|
||||
*/
|
||||
clear: function (previewMode, value, $opt) {
|
||||
this.selectClass(previewMode, '', $());
|
||||
clear: function (previewMode, widgetValue, params) {
|
||||
this.$target.removeClass('o_record_has_cover');
|
||||
this.$image.css('background-image', '');
|
||||
},
|
||||
/**
|
||||
* @see this.selectClass for parameters
|
||||
*/
|
||||
change: function (previewMode, value, $opt) {
|
||||
change: function (previewMode, widgetValue, params) {
|
||||
var $image = $('<img/>');
|
||||
var background = this.$image.css('background-image');
|
||||
if (background && background !== 'none') {
|
||||
@@ -1796,25 +1793,24 @@ options.registry.CoverProperties = options.Class.extend({
|
||||
var src = image.src;
|
||||
this.$image.css('background-image', src ? ('url(' + src + ')') : '');
|
||||
if (!this.$target.hasClass('o_record_has_cover')) {
|
||||
var $opt = this.$el.find('.o_record_cover_opt_size_default[data-select-class]');
|
||||
this.selectClass(previewMode, $opt.data('selectClass'), $opt);
|
||||
this.$el.find('.o_record_cover_opt_size_default[data-select-class]').click();
|
||||
}
|
||||
this._updateUI();
|
||||
this.updateUI();
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @see this.selectClass for parameters
|
||||
*/
|
||||
filterValue: function (previewMode, value, $opt) {
|
||||
this.$filter.css('opacity', value);
|
||||
filterValue: function (previewMode, widgetValue, params) {
|
||||
this.$filter.css('opacity', widgetValue || 0);
|
||||
},
|
||||
/**
|
||||
* @see this.selectClass for parameters
|
||||
*/
|
||||
filterColor: function (previewMode, value, $opt) {
|
||||
filterColor: function (previewMode, widgetValue, params) {
|
||||
this.$filter.removeClass(this.filterColorClasses);
|
||||
if (value) {
|
||||
this.$filter.addClass(value);
|
||||
if (widgetValue) {
|
||||
this.$filter.addClass(widgetValue);
|
||||
}
|
||||
|
||||
var $firstVisibleFilterOpt = this.$filterValueOpts.eq(1);
|
||||
@@ -1824,16 +1820,16 @@ options.registry.CoverProperties = options.Class.extend({
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @override
|
||||
*/
|
||||
_setActive: function () {
|
||||
this._super.apply(this, arguments);
|
||||
updateUI: async function () {
|
||||
await this._super(...arguments);
|
||||
|
||||
// Only show options which are useful to the current cover
|
||||
_.each(this.$el.children(), el => {
|
||||
var $el = $(el);
|
||||
|
||||
@@ -1850,27 +1846,39 @@ options.registry.CoverProperties = options.Class.extend({
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.$el.find('[data-clear]').toggleClass('d-none', !this.$target.hasClass('o_record_has_cover'));
|
||||
|
||||
this.$filterValueOpts.removeClass('active');
|
||||
this.$filterColorOpts.removeClass('active');
|
||||
|
||||
var activeFilterValue = this.$filterValueOpts
|
||||
.filter((i, el) => {
|
||||
return (parseFloat($(el).data('filterValue')).toFixed(1) === parseFloat(this.$filter.css('opacity')).toFixed(1));
|
||||
}).addClass('active').data('filterValue');
|
||||
|
||||
var activeFilterColor = this.$filterColorOpts
|
||||
.filter((i, el) => {
|
||||
return this.$filter.hasClass($(el).data('filterColor'));
|
||||
}).addClass('active').data('filterColor');
|
||||
|
||||
// Update saving dataset
|
||||
this.$target[0].dataset.coverClass = this.$el.find('.active[data-cover-opt="size"]').data('selectClass') || '';
|
||||
this.$target[0].dataset.textSizeClass = this.$el.find('.active[data-cover-opt="text_size"]').data('selectClass') || '';
|
||||
this.$target[0].dataset.textAlignClass = this.$el.find('.active[data-cover-opt="text_align"]').data('selectClass') || '';
|
||||
this.$target[0].dataset.filterValue = activeFilterValue || 0.0;
|
||||
this.$target[0].dataset.filterColor = activeFilterColor || '';
|
||||
this.$target[0].dataset.filterValue = this.$filterValueOpts.filter('.active').data('filterValue') || 0.0;
|
||||
this.$target[0].dataset.filterColor = this.$filterColorOpts.filter('.active').data('filterColor') || '';
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_computeWidgetState: function (methodName, params) {
|
||||
switch (methodName) {
|
||||
case 'filterValue': {
|
||||
return parseFloat(this.$filter.css('opacity')).toFixed(1);
|
||||
}
|
||||
case 'filterColor': {
|
||||
const classes = this.filterColorClasses.split(' ');
|
||||
for (const className of classes) {
|
||||
if (this.$filter.hasClass(className)) {
|
||||
return className;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
return this._super(...arguments);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1894,35 +1902,6 @@ options.registry.SectionStretch = options.Class.extend({
|
||||
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Options
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @see this.selectClass for parameters
|
||||
*/
|
||||
toggleContainerFluid: function (previewMode, value, $opt) {
|
||||
var isFluid = this.$el.find('[data-toggle-container-fluid]').hasClass('active');
|
||||
this.$target.toggleClass('container', !isFluid)
|
||||
.toggleClass('container-fluid', isFluid);
|
||||
if (previewMode !== 'reset') {
|
||||
this.$target.toggleClass('container').toggleClass('container-fluid');
|
||||
}
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_setActive: function () {
|
||||
this._super.apply(this, arguments);
|
||||
var isFluid = this.$target.hasClass('container-fluid');
|
||||
this.$el.find('[data-toggle-container-fluid]').toggleClass('active', isFluid);
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -1938,10 +1917,6 @@ options.registry.SnippetMove = options.Class.extend({
|
||||
$overlayArea.prepend($buttons[0]);
|
||||
$overlayArea.append($buttons[1]);
|
||||
|
||||
// TODO this is kinda hack but not worth a complex system while no
|
||||
// other use case is implemented.
|
||||
$buttons.on('click', this._onOptionSelection.bind(this));
|
||||
|
||||
return this._super(...arguments);
|
||||
},
|
||||
|
||||
@@ -1954,8 +1929,8 @@ options.registry.SnippetMove = options.Class.extend({
|
||||
*
|
||||
* @see this.selectClass for parameters
|
||||
*/
|
||||
moveSnippet: function (previewMode, value, $opt) {
|
||||
switch (value) {
|
||||
moveSnippet: function (previewMode, widgetValue, params) {
|
||||
switch (widgetValue) {
|
||||
case 'prev':
|
||||
this.$target.prev().before(this.$target);
|
||||
break;
|
||||
|
||||
@@ -8,6 +8,7 @@ var Widget = require('web.Widget');
|
||||
var weWidgets = require('wysiwyg.widgets');
|
||||
var websiteNavbarData = require('website.navbar');
|
||||
var ColorPaletteDialog = require('web_editor.ColorPalette').ColorPaletteDialog;
|
||||
const ColorpickerDialog = require('web.ColorpickerDialog');
|
||||
|
||||
var _t = core._t;
|
||||
|
||||
@@ -518,13 +519,14 @@ var ThemeCustomizeDialog = Dialog.extend({
|
||||
var colorpicker = new ColorPaletteDialog(self, {
|
||||
resetButton: false,
|
||||
defaultColor: $color.css('background-color'),
|
||||
excluded: ['transparent_grayscale'],
|
||||
excludeSectionOf: colorName,
|
||||
});
|
||||
var chosenColor = undefined;
|
||||
colorpicker.on('color_picked custom_color_picked', self, function (ev) {
|
||||
colorpicker.on('color_picked', self, function (ev) {
|
||||
ev.stopPropagation();
|
||||
chosenColor = ev.data.cssColor;
|
||||
chosenColor = ev.data.color;
|
||||
if (!ColorpickerDialog.isCSSColor(chosenColor)) {
|
||||
chosenColor = ColorpickerDialog.normalizeCSSColor(self.style.getPropertyValue('--' + chosenColor).trim());
|
||||
}
|
||||
colorpicker.close();
|
||||
});
|
||||
colorpicker.on('closed', self, function (ev) {
|
||||
|
||||
@@ -982,25 +982,39 @@
|
||||
<t t-call="web_editor.snippet_options"/>
|
||||
|
||||
<!-- COLOR | .s_three_columns | .s_comparisons -->
|
||||
<div data-js="colorpicker" string="Color"
|
||||
data-selector=".s_three_columns .row > div, .s_comparisons .row > div, .s_tabs .row > div"
|
||||
data-target=".card"/>
|
||||
<div data-selector=".s_three_columns .row > div, .s_comparisons .row > div, .s_tabs .row > div"
|
||||
data-target=".card">
|
||||
<we-colorpicker string="Color"
|
||||
data-select-style="true"
|
||||
data-css-property="background-color"
|
||||
data-color-prefix="bg-"/>
|
||||
</div>
|
||||
|
||||
<!-- COLOR | .s_hr -->
|
||||
<div data-js="colorpicker" string="Color"
|
||||
data-selector=".s_hr"
|
||||
data-target="hr"
|
||||
data-color-prefix="border-"
|
||||
data-palette-exclude="transparent_grayscale, common, custom"/>
|
||||
<div data-selector=".s_hr"
|
||||
data-target="hr">
|
||||
<we-colorpicker string="Color"
|
||||
data-select-style="true"
|
||||
data-css-property="border-color"
|
||||
data-color-prefix="border-"
|
||||
data-excluded="transparent_grayscale"/>
|
||||
</div>
|
||||
|
||||
<!-- COLOR | .s_cards -->
|
||||
<div data-js="colorpicker" string="Color"
|
||||
data-selector=".s_card, .accordion .card"/>
|
||||
<div data-selector=".s_card, .accordion .card">
|
||||
<we-colorpicker string="Color"
|
||||
data-select-style="true"
|
||||
data-css-property="background-color"
|
||||
data-color-prefix="bg-"/>
|
||||
</div>
|
||||
|
||||
<!-- COLOR | .s_alert -->
|
||||
<div data-js="colorpicker" string="Color"
|
||||
data-selector=".s_alert"
|
||||
data-color-prefix="alert-"/>
|
||||
<div data-selector=".s_alert">
|
||||
<we-colorpicker string="Color"
|
||||
data-select-style="true"
|
||||
data-css-property="background-color"
|
||||
data-color-prefix="alert-"/>
|
||||
</div>
|
||||
|
||||
<!-- STYLES | .s_title -->
|
||||
<div data-selector=".s_title" data-target="[class*='s_title_']">
|
||||
@@ -1108,7 +1122,7 @@
|
||||
<we-divider/>
|
||||
<we-button data-select-class="s_carousel_default">Default</we-button>
|
||||
</we-select>
|
||||
<we-input string="Speed" data-set-data-attribute="interval" data-unit="s" data-save-unit="ms" data-step="0.1"/>
|
||||
<we-input string="Speed" data-select-data-attribute="0s" data-attribute-name="interval" data-unit="s" data-save-unit="ms" data-step="0.1"/>
|
||||
</div>
|
||||
|
||||
<div data-js="CarouselItem"
|
||||
@@ -1238,46 +1252,62 @@
|
||||
<we-button class="fas fa-fw fa-expand-arrows-alt" title="Background Position"
|
||||
data-background-position-overlay="true" data-no-preview="true"/>
|
||||
</we-row>
|
||||
<we-input string="Background width" data-set-style="background-size" data-default-value="auto"/>
|
||||
<we-input string="Background height" data-set-style="background-size" data-default-value="auto"/>
|
||||
<we-multi data-css-property="background-size">
|
||||
<we-input string="Width" data-select-style="auto" data-unit="px"/>
|
||||
<we-input string="Height" data-select-style="auto" data-unit="px"/>
|
||||
</we-multi>
|
||||
</div>
|
||||
|
||||
<!-- Parallax -->
|
||||
<div data-js="parallax" data-selector=".parallax">
|
||||
<we-select string="Scroll Speed">
|
||||
<we-button data-scroll="0">No-scroll</we-button>
|
||||
<we-select string="Scroll Speed" data-attribute-name="scrollBackgroundRatio" data-attribute-default-value="0">
|
||||
<we-button data-select-data-attribute="0">No-scroll</we-button>
|
||||
<we-divider/>
|
||||
<we-button data-scroll="1">Fixed</we-button>
|
||||
<we-button data-select-data-attribute="1">Fixed</we-button>
|
||||
<we-divider/>
|
||||
<we-button data-scroll="0.6">Very Slow</we-button>
|
||||
<we-button data-scroll="1.2">Slow</we-button>
|
||||
<we-button data-scroll="1.6">Fast</we-button>
|
||||
<we-button data-scroll="2">Very Fast</we-button>
|
||||
<we-button data-select-data-attribute="0.6">Very Slow</we-button>
|
||||
<we-button data-select-data-attribute="1.2">Slow</we-button>
|
||||
<we-button data-select-data-attribute="1.6">Fast</we-button>
|
||||
<we-button data-select-data-attribute="2">Very Fast</we-button>
|
||||
</we-select>
|
||||
</div>
|
||||
|
||||
<!-- Color | Section -->
|
||||
<div id="so_main_colorpicker" string="Background Color"
|
||||
data-js="colorpicker"
|
||||
data-selector="section, .carousel-item"
|
||||
data-exclude=".parallax, .o_gallery .carousel-item"/>
|
||||
<div id="so_main_colorpicker"
|
||||
data-selector="section, .carousel-item"
|
||||
data-exclude=".parallax, .o_gallery .carousel-item">
|
||||
<we-colorpicker string="Background Color"
|
||||
data-select-style="true"
|
||||
data-css-property="background-color"
|
||||
data-color-prefix="bg-"/>
|
||||
</div>
|
||||
|
||||
<!-- FILTER | .s_parallax -->
|
||||
<div data-js="colorpicker" string="Filter"
|
||||
data-selector=".parallax"
|
||||
data-palette-exclude="common, theme, custom"
|
||||
data-palette-default="transparent_grayscale"/>
|
||||
<div data-selector=".parallax">
|
||||
<we-colorpicker string="Filter"
|
||||
data-select-style="true"
|
||||
data-css-property="background-color"
|
||||
data-color-prefix="bg-"
|
||||
data-excluded="common, theme, custom"/>
|
||||
</div>
|
||||
|
||||
<!-- Color | Columns -->
|
||||
<div data-js="colorpicker" string="Background Color"
|
||||
data-selector="section .row > div"
|
||||
data-exclude=".s_col_no_bgcolor, .s_col_no_bgcolor.row > div, .o_gallery .row > div"/>
|
||||
<div data-selector="section .row > div"
|
||||
data-exclude=".s_col_no_bgcolor, .s_col_no_bgcolor.row > div, .o_gallery .row > div">
|
||||
<we-colorpicker string="Background Color"
|
||||
data-select-style="true"
|
||||
data-css-property="background-color"
|
||||
data-color-prefix="bg-"/>
|
||||
</div>
|
||||
|
||||
<div data-selector="section .row > div"
|
||||
data-exclude=".s_col_no_bgcolor, .s_col_no_bgcolor.row > div, .o_gallery .row > div">
|
||||
<we-input string="Border" data-set-style="border-width" data-extra-class="border"/>
|
||||
<we-input string="Rounded" data-set-style="border-radius" data-extra-class="rounded"/>
|
||||
<we-checkbox string="Shadow" data-toggle-class="shadow"/>
|
||||
<we-row string="Border" class="o_we_inline">
|
||||
<we-colorpicker data-select-style="true" data-css-property="border-color" data-color-prefix="border-"/>
|
||||
<we-input data-select-style="0" data-css-property="border-width" data-unit="px" data-extra-class="border"/>
|
||||
</we-row>
|
||||
<we-input string="Rounded" data-select-style="0" data-css-property="border-radius" data-unit="px" data-extra-class="rounded"/>
|
||||
<we-checkbox string="Shadow" data-select-class="shadow"/>
|
||||
</div>
|
||||
|
||||
<div data-js="sizing_y"
|
||||
@@ -1301,7 +1331,7 @@
|
||||
|
||||
<div data-js="ul"
|
||||
data-selector=":not(li) > ul:has(ul,ol), :not(li) > ol:has(ul,ol)">
|
||||
<we-checkbox string="Folded list" data-toggle-class="o_ul_folded"/>
|
||||
<we-checkbox string="Folded list" data-select-class="o_ul_folded"/>
|
||||
</div>
|
||||
|
||||
<div data-js="menu_data"
|
||||
@@ -1319,11 +1349,14 @@
|
||||
<we-checkbox string="Transparent" data-transparent="true" data-no-preview="true"/>
|
||||
</div>
|
||||
|
||||
<div data-js="topMenuColor" string="Background Color"
|
||||
data-selector="[data-main-object^='website.page('] #wrapwrap > header"
|
||||
data-palette-exclude="theme,common, custom"
|
||||
data-palette-default="transparent_grayscale"
|
||||
data-no-check="true">
|
||||
<div data-js="topMenuColor"
|
||||
data-selector="[data-main-object^='website.page('] #wrapwrap > header"
|
||||
data-no-check="true">
|
||||
<we-colorpicker string="Background Color"
|
||||
data-select-style="true"
|
||||
data-css-property="background-color"
|
||||
data-color-prefix="bg-"
|
||||
data-excluded="theme, common, custom"/>
|
||||
</div>
|
||||
|
||||
<!-- Anchor Name -->
|
||||
@@ -1391,7 +1424,7 @@
|
||||
<div data-js="SectionStretch"
|
||||
data-selector="section"
|
||||
data-target="> .container, > .container-fluid">
|
||||
<we-checkbox string="Stretch" data-toggle-container-fluid=""/>
|
||||
<we-checkbox string="Stretch" data-select-class="container|container-fluid" data-no-preview="true"/>
|
||||
</div>
|
||||
</template>
|
||||
</odoo>
|
||||
|
||||
@@ -1,29 +1,10 @@
|
||||
odoo.define('website_blog.s_latest_posts_editor', function (require) {
|
||||
'use strict';
|
||||
|
||||
var core = require('web.core');
|
||||
var sOptions = require('web_editor.snippets.options');
|
||||
var wUtils = require('website.utils');
|
||||
|
||||
var _t = core._t;
|
||||
|
||||
sOptions.registry.js_get_posts_limit = sOptions.Class.extend({
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Options
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @see this.selectClass for parameters
|
||||
*/
|
||||
postsLimit: function (previewMode, value, $opt) {
|
||||
value = parseInt(value);
|
||||
this.$target.attr('data-posts-limit', value).data('postsLimit', value);
|
||||
this.trigger_up('widgets_start_request', {
|
||||
editableMode: true,
|
||||
$target: this.$target,
|
||||
});
|
||||
},
|
||||
sOptions.registry.js_get_posts_selectBlog = sOptions.Class.extend({
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
@@ -32,69 +13,20 @@ sOptions.registry.js_get_posts_limit = sOptions.Class.extend({
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_setActive: function () {
|
||||
this._super.apply(this, arguments);
|
||||
var activeLimit = this.$target.data('postsLimit') || 3;
|
||||
|
||||
this.$el.find('[data-posts-limit]').removeClass('active');
|
||||
this.$el.find('[data-posts-limit=' + activeLimit + ']').addClass('active');
|
||||
},
|
||||
});
|
||||
|
||||
sOptions.registry.js_get_posts_selectBlog = sOptions.Class.extend({
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
start: function () {
|
||||
var def = this._rpc({
|
||||
_renderCustomXML: function (uiFragment) {
|
||||
return this._rpc({
|
||||
model: 'blog.blog',
|
||||
method: 'search_read',
|
||||
args: [wUtils.websiteDomain(this), ['name']],
|
||||
}).then(blogs => {
|
||||
var allBlogsEl = this.el.querySelector('[data-filter-by-blog-id="0"]');
|
||||
var menuEl = allBlogsEl.parentNode;
|
||||
const menuEl = uiFragment.querySelector('[name="blog_selection"]');
|
||||
for (const blog of blogs) {
|
||||
let el = allBlogsEl.cloneNode();
|
||||
el.dataset.filterByBlogId = blog.id;
|
||||
const el = document.createElement('we-button');
|
||||
el.dataset.selectDataAttribute = blog.id;
|
||||
el.textContent = blog.name;
|
||||
menuEl.appendChild(el);
|
||||
}
|
||||
this._updateUI();
|
||||
});
|
||||
|
||||
return Promise.all([this._super.apply(this, arguments), def]);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Options
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @see this.selectClass for parameters
|
||||
*/
|
||||
filterByBlogId: function (previewMode, value, $opt) {
|
||||
value = parseInt(value);
|
||||
this.$target.attr('data-filter-by-blog-id', value).data('filterByBlogId', value);
|
||||
this.trigger_up('widgets_start_request', {
|
||||
editableMode: true,
|
||||
$target: this.$target,
|
||||
});
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_setActive: function () {
|
||||
this._super.apply(this, arguments);
|
||||
|
||||
var activeBlogId = this.$target.data('filterByBlogId') || 0;
|
||||
|
||||
this.$el.find('[data-filter-by-blog-id]').removeClass('active');
|
||||
this.$el.find('[data-filter-by-blog-id=' + activeBlogId + ']').addClass('active');
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -111,8 +111,8 @@ options.registry.CoverProperties.include({
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_updateUI: function () {
|
||||
this._super(...arguments);
|
||||
updateUI: async function () {
|
||||
await this._super(...arguments);
|
||||
var isRegularCover = this.$target.is('.o_wblog_post_page_cover_regular');
|
||||
var $coverFull = this.$el.find('[data-select-class*="cover_full"]');
|
||||
var $coverMid = this.$el.find('[data-select-class*="cover_mid"]');
|
||||
|
||||
@@ -11,24 +11,24 @@
|
||||
<template id="s_latest_posts_options" inherit_id="website.snippet_options">
|
||||
<xpath expr="//div[@data-js='background']" position="before">
|
||||
<div data-js="js_get_posts_selectBlog" data-selector=".s_latest_posts, .s_latest_posts_big_picture" data-target=".js_get_posts" data-no-check="true">
|
||||
<we-select string="Choose a blog" data-no-preview="true">
|
||||
<we-button data-filter-by-blog-id="0">All blogs</we-button>
|
||||
<we-select string="Choose a blog" data-no-preview="true" name="blog_selection" data-attribute-name="filterByBlogId">
|
||||
<we-button data-select-data-attribute="0">All blogs</we-button>
|
||||
<!-- the blog list will be generated in js -->
|
||||
</we-select>
|
||||
</div>
|
||||
|
||||
<div data-js="js_get_posts_limit" data-selector=".s_latest_posts, .s_latest_posts_big_picture" data-target=".js_get_posts" data-no-check="true">
|
||||
<we-select string="Posts limit" data-no-preview="true">
|
||||
<we-button data-posts-limit="1">1 post max</we-button>
|
||||
<we-button data-posts-limit="2">2 posts max</we-button>
|
||||
<we-button data-posts-limit="3">3 posts max</we-button>
|
||||
<we-button data-posts-limit="4">4 posts max</we-button>
|
||||
<we-button data-posts-limit="5">5 posts max</we-button>
|
||||
<we-button data-posts-limit="6">6 posts max</we-button>
|
||||
<we-button data-posts-limit="7">7 posts max</we-button>
|
||||
<we-button data-posts-limit="8">8 posts max</we-button>
|
||||
<we-button data-posts-limit="9">9 posts max</we-button>
|
||||
<we-button data-posts-limit="10">10 posts max</we-button>
|
||||
<div data-selector=".s_latest_posts, .s_latest_posts_big_picture" data-target=".js_get_posts" data-no-check="true">
|
||||
<we-select string="Posts limit" data-no-preview="true" data-attribute-name="postsLimit" data-attribute-default-value="3">
|
||||
<we-button data-select-data-attribute="1">1 post max</we-button>
|
||||
<we-button data-select-data-attribute="2">2 posts max</we-button>
|
||||
<we-button data-select-data-attribute="3">3 posts max</we-button>
|
||||
<we-button data-select-data-attribute="4">4 posts max</we-button>
|
||||
<we-button data-select-data-attribute="5">5 posts max</we-button>
|
||||
<we-button data-select-data-attribute="6">6 posts max</we-button>
|
||||
<we-button data-select-data-attribute="7">7 posts max</we-button>
|
||||
<we-button data-select-data-attribute="8">8 posts max</we-button>
|
||||
<we-button data-select-data-attribute="9">9 posts max</we-button>
|
||||
<we-button data-select-data-attribute="10">10 posts max</we-button>
|
||||
</we-select>
|
||||
</div>
|
||||
</xpath>
|
||||
|
||||
@@ -91,7 +91,7 @@ odoo.define('website_form_editor', function (require) {
|
||||
},
|
||||
|
||||
// Choose a model modal
|
||||
website_form_model_modal: function (previewMode, value, $li) {
|
||||
website_form_model_modal: function (previewMode, widgetValue, params) {
|
||||
var self = this;
|
||||
this._rpc({
|
||||
model: "ir.model",
|
||||
@@ -224,7 +224,7 @@ odoo.define('website_form_editor', function (require) {
|
||||
},
|
||||
|
||||
// Choose a field modal
|
||||
website_form_field_modal: function (previewMode, value, $li) {
|
||||
website_form_field_modal: function (previewMode, widgetValue, params) {
|
||||
var self = this;
|
||||
|
||||
this.fields().then(function (fields) {
|
||||
@@ -261,13 +261,13 @@ odoo.define('website_form_editor', function (require) {
|
||||
},
|
||||
|
||||
// Create a custom field
|
||||
website_form_custom_field: function (previewMode, value, $li) {
|
||||
var default_field_name = 'Custom ' + $li.text();
|
||||
website_form_custom_field: function (previewMode, widgetValue, params) {
|
||||
var default_field_name = 'Custom ' + this.el.querySelector(`[data-website_form_custom_field="${widgetValue}"]`).textContent; // TODO improve
|
||||
this.append_field({
|
||||
name: default_field_name,
|
||||
string: default_field_name,
|
||||
custom: true,
|
||||
type: value,
|
||||
type: widgetValue,
|
||||
// Default values for x2many fields
|
||||
records: [
|
||||
{
|
||||
@@ -302,7 +302,7 @@ odoo.define('website_form_editor', function (require) {
|
||||
},
|
||||
|
||||
// Re-render the field and replace the current one
|
||||
// website_form_editor_field_reset: function(previewMode, value, $li) {
|
||||
// website_form_editor_field_reset: function(previewMode, widgetValue, params) {
|
||||
// var self = this;
|
||||
// var target_field_name = this.$target.find('.col-form-label').attr('for');
|
||||
// this.fields().then(function(fields){
|
||||
@@ -476,9 +476,9 @@ odoo.define('website_form_editor', function (require) {
|
||||
/**
|
||||
* @see this.selectClass for parameters
|
||||
*/
|
||||
website_form_choice_field_display: function (previewMode, value, $opt) {
|
||||
this.$target.toggleClass('o_website_form_flex_fw', value === 'vertical');
|
||||
this.$target[0].dataset.display = value;
|
||||
website_form_choice_field_display: function (previewMode, widgetValue, params) {
|
||||
this.$target.toggleClass('o_website_form_flex_fw', widgetValue === 'vertical');
|
||||
this.$target[0].dataset.display = widgetValue;
|
||||
},
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
@@ -488,12 +488,11 @@ odoo.define('website_form_editor', function (require) {
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_setActive: function () {
|
||||
this._super(...arguments);
|
||||
this.$el.find('[data-website_form_choice_field_display]')
|
||||
.removeClass('active')
|
||||
.filter('[data-website_form_choice_field_display=' + this.$target.attr('data-display') + ']')
|
||||
.addClass('active');
|
||||
_computeWidgetState: function (methodName, params) {
|
||||
if (methodName === 'website_form_choice_field_display') {
|
||||
return this.$target.attr('data-display');
|
||||
}
|
||||
return this._super(...arguments);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -502,7 +501,7 @@ odoo.define('website_form_editor', function (require) {
|
||||
xmlDependencies: ['/website_form/static/src/xml/website_form_editor.xml'],
|
||||
|
||||
// Option to toggle inputs required attribute
|
||||
website_form_field_require: function (previewMode, value, $li) {
|
||||
website_form_field_require: function (previewMode, widgetValue, params) {
|
||||
this.$target.find('.o_website_form_input').each(function (index, input) {
|
||||
input.required = !input.required;
|
||||
});
|
||||
|
||||
@@ -115,7 +115,7 @@ odoo.define('website_form_editor.tour', function(require) {
|
||||
},
|
||||
{
|
||||
content: "Click on Hidden",
|
||||
trigger: "[data-toggle-class='o_website_form_field_hidden']"
|
||||
trigger: "[data-select-class='o_website_form_field_hidden']"
|
||||
},
|
||||
{
|
||||
content: "Check the resulting field",
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
<!-- Field -->
|
||||
<div data-js='website_form_editor' data-selector=".form-field" data-drop-near=".form-field">
|
||||
<we-checkbox string="Hidden" data-no-preview="true"
|
||||
data-toggle-class="o_website_form_field_hidden"/>
|
||||
data-select-class="o_website_form_field_hidden"/>
|
||||
</div>
|
||||
|
||||
<!-- Add an option to display radio buttons / checkboxes horizontally or vertically -->
|
||||
@@ -72,7 +72,7 @@
|
||||
<div data-js='website_form_editor_field' data-selector=".form-field:not(.o_website_form_required)">
|
||||
<we-checkbox string="Required" data-no-preview="true"
|
||||
data-website_form_field_require=""
|
||||
data-toggle-class="o_website_form_required_custom"/>
|
||||
data-select-class="o_website_form_required_custom"/>
|
||||
</div>
|
||||
|
||||
<!-- Remove the duplicate options of model fields -->
|
||||
|
||||
@@ -113,8 +113,8 @@ options.registry.WebsiteSaleGridLayout = options.Class.extend({
|
||||
/**
|
||||
* @see this.selectClass for params
|
||||
*/
|
||||
setPpg: function (previewMode, value, $opt) {
|
||||
const ppg = parseInt($opt.find('input').val());
|
||||
setPpg: function (previewMode, widgetValue, params) {
|
||||
const ppg = parseInt(widgetValue);
|
||||
if (!ppg || ppg < 1) {
|
||||
return false;
|
||||
}
|
||||
@@ -129,12 +129,12 @@ options.registry.WebsiteSaleGridLayout = options.Class.extend({
|
||||
/**
|
||||
* @see this.selectClass for params
|
||||
*/
|
||||
setPpr: function (previewMode, value, $opt) {
|
||||
this.ppr = parseInt(value);
|
||||
setPpr: function (previewMode, widgetValue, params) {
|
||||
this.ppr = parseInt(widgetValue);
|
||||
this._rpc({
|
||||
route: '/shop/change_ppr',
|
||||
params: {
|
||||
'ppr': value,
|
||||
'ppr': this.ppr,
|
||||
},
|
||||
}).then(reload);
|
||||
},
|
||||
@@ -146,18 +146,16 @@ options.registry.WebsiteSaleGridLayout = options.Class.extend({
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_setActive: function () {
|
||||
this._super(...arguments);
|
||||
|
||||
this.$el.find('[data-set-ppr]')
|
||||
.removeClass('active')
|
||||
.filter((i, el) => {
|
||||
var nbColumns = parseInt($(el).data('setPpr'));
|
||||
return nbColumns === this.ppr;
|
||||
})
|
||||
.addClass('active');
|
||||
|
||||
this.$el.find('[data-set-ppg] > input').val(this.ppg);
|
||||
_computeWidgetState: function (methodName, params) {
|
||||
switch (methodName) {
|
||||
case 'setPpg': {
|
||||
return this.ppg;
|
||||
}
|
||||
case 'setPpr': {
|
||||
return this.ppr;
|
||||
}
|
||||
}
|
||||
return this._super(...arguments);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -172,29 +170,11 @@ options.registry.WebsiteSaleProductsItem = options.Class.extend({
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
start: function () {
|
||||
willStart: function () {
|
||||
this.ppr = this.$target.closest('[data-ppr]').data('ppr');
|
||||
this.productTemplateID = parseInt(this.$target.find('[data-oe-model="product.template"]').data('oe-id'));
|
||||
|
||||
var defs = [this._super.apply(this, arguments)];
|
||||
|
||||
defs.push(this._rpc({
|
||||
model: 'product.style',
|
||||
method: 'search_read',
|
||||
}).then(data => {
|
||||
var $menu = this.$el.find('[name="style"]');
|
||||
for (var k in data) {
|
||||
$menu.append(this.buildCheckboxElement(data[k]['name'], {
|
||||
dataAttributes: {
|
||||
'style': data[k]['id'],
|
||||
'toggleClass': data[k]['html_class'],
|
||||
},
|
||||
}));
|
||||
}
|
||||
this._updateUI();
|
||||
}));
|
||||
|
||||
return $.when.apply($, defs);
|
||||
return this._super(...arguments);
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
@@ -212,37 +192,37 @@ options.registry.WebsiteSaleProductsItem = options.Class.extend({
|
||||
/**
|
||||
* @see this.selectClass for params
|
||||
*/
|
||||
style: function (previewMode, value, $opt) {
|
||||
style: function (previewMode, widgetValue, params) {
|
||||
this._rpc({
|
||||
route: '/shop/change_styles',
|
||||
params: {
|
||||
'id': this.productTemplateID,
|
||||
'style_id': value,
|
||||
'style_id': params.possibleValues[params.possibleValues.length - 1],
|
||||
},
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @see this.selectClass for params
|
||||
*/
|
||||
changeSequence: function (previewMode, value, $opt) {
|
||||
changeSequence: function (previewMode, widgetValue, params) {
|
||||
this._rpc({
|
||||
route: '/shop/change_sequence',
|
||||
params: {
|
||||
id: this.productTemplateID,
|
||||
sequence: value,
|
||||
sequence: widgetValue,
|
||||
},
|
||||
}).then(reload);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_updateUI: function () {
|
||||
this._super.apply(this, arguments);
|
||||
updateUI: async function () {
|
||||
await this._super.apply(this, arguments);
|
||||
|
||||
var sizeX = parseInt(this.$target.attr('colspan') || 1);
|
||||
var sizeY = parseInt(this.$target.attr('rowspan') || 1);
|
||||
@@ -255,6 +235,37 @@ options.registry.WebsiteSaleProductsItem = options.Class.extend({
|
||||
$size.find('tr td:nth-child(n + ' + parseInt(this.ppr + 1) + ')').hide();
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_renderCustomWidgets: async function (uiFragment) {
|
||||
const checkboxes = [];
|
||||
return this._rpc({
|
||||
model: 'product.style',
|
||||
method: 'search_read',
|
||||
}).then(async data => {
|
||||
for (var k in data) {
|
||||
const checkboxWidget = this._registerUserValueWidget('we-checkbox', this, data[k]['name'], {
|
||||
dataAttributes: {
|
||||
'style': data[k]['id'],
|
||||
'selectClass': data[k]['html_class'],
|
||||
},
|
||||
});
|
||||
checkboxes.push(checkboxWidget);
|
||||
await checkboxWidget.appendTo(document.createDocumentFragment());
|
||||
}
|
||||
}).then(() => {
|
||||
const menuEl = uiFragment.querySelector('[name="style"]');
|
||||
for (const checkbox of checkboxes) {
|
||||
menuEl.appendChild(checkbox.el);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -277,8 +288,8 @@ options.registry.WebsiteSaleProductsItem = options.Class.extend({
|
||||
_onTableItemMouseEnter: function (ev) {
|
||||
var $td = $(ev.currentTarget);
|
||||
var $table = $td.closest("table");
|
||||
var x = $td.index()+1;
|
||||
var y = $td.parent().index()+1;
|
||||
var x = $td.index() + 1;
|
||||
var y = $td.parent().index() + 1;
|
||||
|
||||
var tr = [];
|
||||
for (var yi = 0; yi < y; yi++) {
|
||||
@@ -334,7 +345,7 @@ options.registry.ProductsSearchBar = options.Class.extend({
|
||||
/**
|
||||
* @see this.selectClass for parameters
|
||||
*/
|
||||
openSearchbarSettings: function (previewMode, value, $opt) {
|
||||
openSearchbarSettings: function (previewMode, widgetValue, params) {
|
||||
var self = this;
|
||||
new Dialog(this, {
|
||||
title: _t("Products Search Bar"),
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
<div data-js="WebsiteSaleGridLayout"
|
||||
data-selector="#products_grid .o_wsale_products_grid_table_wrapper > table"
|
||||
data-no-check="true">
|
||||
<we-input string="Number of products" data-set-ppg="" data-unit="" data-no-preview="true"/>
|
||||
<we-input string="Number of products" data-set-ppg="" data-no-preview="true"/>
|
||||
<we-select string="Number of Columns" class="o_wsale_ppr_submenu" data-no-preview="true">
|
||||
<we-button data-set-ppr="2">2</we-button>
|
||||
<we-button data-set-ppr="3">3</we-button>
|
||||
|
||||
Reference in New Issue
Block a user