[IMP] web_editor, website: generalize colorpicker

Currently we have:
- a colorpicker for background color of snippets
- a colorpicker for foreground and background text (topbar)
- a colorpicker for menu/footer/navbar/etc
These 3 are separate colorpickers with different options.

After this Commit
All 3 will use the same color palette wich allow you to select
theme color, common colors, custom color and transparent filter.

Part of https://github.com/odoo/odoo/pull/38959
maintask-2066614
task-2087383
This commit is contained in:
fja-odoo
2019-11-18 09:56:40 +00:00
committed by qsm-odoo
parent 2006959761
commit 22c5fc5dd2
12 changed files with 513 additions and 317 deletions
@@ -18,6 +18,7 @@ var EditorMenuBar = Widget.extend({
'click button[data-action=cancel]': '_onCancelClick',
},
custom_events: {
request_editable: '_onRequestEditable',
request_history_undo_record: '_onHistoryUndoRecordRequest',
request_save: '_onSaveRequest',
},
@@ -324,6 +325,13 @@ var EditorMenuBar = Widget.extend({
ev.stopPropagation();
this.save(ev.data.reload).then(ev.data.onSuccess, ev.data.onFailure);
},
/**
* @private
* @param {OdooEvent} ev
*/
_onRequestEditable: function (ev) {
ev.data.callback(this.rte.editable());
},
});
return {
@@ -1,30 +1,17 @@
odoo.define('web_editor.rte.summernote', function (require) {
'use strict';
var ajax = require('web.ajax');
var Class = require('web.Class');
const concurrency = require('web.concurrency');
var core = require('web.core');
var ColorpickerDialog = require('web.ColorpickerDialog');
var ColorPaletteWidget = require('web_editor.ColorPalette').ColorPaletteWidget;
var mixins = require('web.mixins');
var fonts = require('wysiwyg.fonts');
var weContext = require('web_editor.context');
var rte = require('web_editor.rte');
var weWidgets = require('wysiwyg.widgets');
var QWeb = core.qweb;
var _t = core._t;
ajax.jsonRpc('/web/dataset/call_kw', 'call', {
'model': 'ir.ui.view',
'method': 'read_template',
'args': ['web_editor.colorpicker'],
'kwargs': {
'context': weContext.get(),
}
}).then(function (data) {
QWeb.add_template('<templates>' + data + '</templates>');
});
// Summernote Lib (neek change to make accessible: method and object)
var dom = $.summernote.core.dom;
var range = $.summernote.core.range;
@@ -35,106 +22,53 @@ var tplButton = renderer.getTemplate().button;
var tplIconButton = renderer.getTemplate().iconButton;
var tplDropdown = renderer.getTemplate().dropdown;
function _rgbToHex(cssColor) {
var rgba = cssColor.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+(?:\.\d+)?))?\)$/);
if (!rgba) {
return cssColor;
}
if (rgba[4]) {
return cssColor;
}
var hex = ColorpickerDialog.convertRgbToHex(
parseInt(rgba[1]),
parseInt(rgba[2]),
parseInt(rgba[3])
);
if (!hex) {
return cssColor; // TODO handle error
}
return hex.hex.toUpperCase();
}
var applyColor = function (target, eventName, color) {
var layoutInfo = dom.makeLayoutInfo(target);
$.summernote.pluginEvents[eventName](undefined, eventHandler.modules.editor, layoutInfo, color);
};
// Update and change the popovers content, and add history button
var fn_createPalette = renderer.createPalette;
renderer.createPalette = function ($container, options) {
fn_createPalette.call(this, $container, options);
const $dropdownContent = $container.find(".colorPalette");
// The editor's root widget can be website or web's root widget and cannot be properly retrieved...
const parent = odoo.__DEBUG__.services['root.widget'];
_.each($dropdownContent, elem => {
const eventName = elem.dataset.eventName;
let colorpicker = null;
const mutex = new concurrency.MutexedDropPrevious();
const $dropdown = $(elem).closest('.btn-group, .dropdown');
let manualOpening = false;
$dropdown.on('show.bs.dropdown', () => {
if (manualOpening) {
return true;
}
mutex.exec(() => {
const oldColorpicker = colorpicker;
const hookEl = oldColorpicker ? oldColorpicker.el : elem;
if (!QWeb.has_template('web_editor.colorpicker')) {
return;
}
var $clpicker = $(QWeb.render('web_editor.colorpicker'));
var groups;
if ($clpicker.is("colorpicker")) {
groups = _.map($clpicker.find('[data-name="theme"]'), function (el) {
return $(el).find("button").empty();
const r = range.create();
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_reset', null, ev => applyColor(ev.data.target, eventName, 'inherit'));
return colorpicker.replace(hookEl).then(() => {
if (oldColorpicker) {
oldColorpicker.destroy();
}
manualOpening = true;
$dropdown.children('.dropdown-toggle').dropdown('show');
manualOpening = false;
});
});
return false;
});
} else {
groups = [$clpicker.find("button").empty()];
}
var html = "<h6 class='mt-2'>" + _t("Theme colors") + "</h6>" + _.map(groups, function ($group) {
var $row = $("<div/>", {"class": "note-color-row mb8"}).append($group);
var $after_breaks = $row.find(".o_small + :not(.o_small)");
if ($after_breaks.length === 0) {
$after_breaks = $row.find(":nth-child(8n+9)");
}
$after_breaks.addClass("o_clear");
return $row[0].outerHTML;
}).join("") + "<h6 class='mt-2'>" + _t("Common colors") + "</h6>";
var $palettes = $container.find(".note-color .note-color-palette");
$palettes.prepend(html);
// Find the custom colors which are used in the page and add them to the color palette
var colors = [];
var $editable = window.__EditorMenuBar_$editable || $();
_.each($editable.find('[style*="color"]'), function (element) {
if (element.style.color) {
colors.push(element.style.color);
}
if (element.style.backgroundColor) {
colors.push(element.style.backgroundColor);
}
});
var $customColorPalettes = $container.find('.note-color .note-custom-color-palette').append($('<div/>', {class: "note-color-row"}));
var $customColorRows = $customColorPalettes.find('.note-color-row');
_.each(_.uniq(colors), function (color) {
var hexColor = _rgbToHex(color);
if (_.indexOf(_.flatten(options.colors), hexColor) < 0) {
// Create button for used custom color for backColor and foreColor both and add them into palette
$customColorRows.append('<button type="button" class="o_custom_color" data-color="' + color + '" style="background-color:' + color + ';" />');
}
});
$palettes.push.apply($palettes, $customColorPalettes);
var $forePalette = $palettes.filter(":even").closest('li').addClass('note-palette');
var $bgPalette = $palettes.filter(":odd").closest('li').addClass('note-palette');
$forePalette.add($bgPalette).find('.note-color-reset').remove();
$forePalette.children().each(function () {
var $reset = $('<div class="note-color-reset" data-event="foreColor" data-value="inherit"></div>').text(_t('Reset to default'));
$(this).prepend($reset);
})
$bgPalette.children().each(function () {
var $reset = $('<div class="note-color-reset" data-event="backColor" data-value="inherit"></div>').text(_t('Reset to default'));
$(this).prepend($reset);
})
var $fore = $forePalette.find("button:not(.note-color-btn)").addClass("note-color-btn");
$fore.each(function () {
var $el = $(this);
var className = $el.hasClass('o_custom_color') ? $el.data('color') : 'text-' + $el.data('color');
$el.attr('data-event', 'foreColor').attr('data-value', className).addClass($el.hasClass('o_custom_color') ? '' : 'bg-' + $el.data('color'));
});
var $bg = $bgPalette.find("button:not(.note-color-btn)").addClass("note-color-btn");
$bg.each(function () {
var $el = $(this);
var className = $el.hasClass('o_custom_color') ? $el.data('color') : 'bg-' + $el.data('color');
$el.attr('data-event', 'backColor').attr('data-value', className).addClass($el.hasClass('o_custom_color') ? '' : className);
});
};
@@ -291,8 +225,6 @@ eventHandler.modules.popover.button.update = function ($container, oStyle) {
$(oStyle.image).addClass('o_we_selected_image');
if (dom.isImgFont(oStyle.image)) {
$container.find('[data-event="customColor"][data-value="foreColor"]').attr('data-color', $(oStyle.image).css('color'));
$container.find('[data-event="customColor"][data-value="backColor"]').attr('data-color', $(oStyle.image).css('background-color'));
$container.find('.note-fore-color-preview > button').css('border-bottom-color', $(oStyle.image).css('color'));
$container.find('.note-back-color-preview > button').css('border-bottom-color', $(oStyle.image).css('background-color'));
@@ -335,8 +267,6 @@ eventHandler.modules.popover.button.update = function ($container, oStyle) {
$(oStyle.image).trigger('attributes_change');
} else {
$container.find('[data-event="customColor"][data-value="foreColor"]').attr('data-color', oStyle.color);
$container.find('[data-event="customColor"][data-value="backColor"]').attr('data-color', oStyle['background-color']);
$container.find('.note-fore-color-preview > button').css('border-bottom-color', oStyle.color);
$container.find('.note-back-color-preview > button').css('border-bottom-color', oStyle['background-color']);
}
@@ -505,25 +435,6 @@ $.summernote.pluginEvents.alt = function (event, editor, layoutInfo, sorted) {
media: $selection.data('target'),
});
};
$.summernote.pluginEvents.customColor = function (event, editor, layoutInfo, customColor) {
var defaultColor = event.target.dataset.color;
core.bus.trigger('color_picker_dialog_demand', {
color: defaultColor === 'rgba(0, 0, 0, 0)' ? 'rgb(255, 0, 0)' : defaultColor,
onSave: function (color) {
var $palettes = $(event.currentTarget).find('.note-custom-color-palette > .note-color-row')
.append(('<button type="button" class="note-color-btn" data-value="' + color + '" style="background-color:' + color + ';" />'));
$palettes.filter(':odd').find('button:not([data-event])').attr('data-event', 'backColor');
$palettes.filter(':even').find('button:not([data-event])').attr('data-event', 'foreColor');
if (customColor === 'foreColor') {
$(event.currentTarget).find('.note-fore-color-preview > button').css('border-bottom-color', color);
$.summernote.pluginEvents.foreColor(event, editor, layoutInfo, color);
} else {
$(event.currentTarget).find('.note-back-color-preview > button').css('border-bottom-color', color);
$.summernote.pluginEvents.backColor(event, editor, layoutInfo, color);
}
}
});
};
$.summernote.pluginEvents.cropImage = function (event, editor, layoutInfo, sorted) {
var $editable = layoutInfo.editable();
var $selection = layoutInfo.handle().find('.note-control-selection');
@@ -1142,7 +1053,6 @@ var SummernoteManager = Class.extend(mixins.EventDispatcherMixin, {
this.setParent(parent);
core.bus.on('alt_dialog_demand', this, this._onAltDialogDemand);
core.bus.on('color_picker_dialog_demand', this, this._onColorPickerDialogDemand);
core.bus.on('crop_image_dialog_demand', this, this._onCropImageDialogDemand);
core.bus.on('link_dialog_demand', this, this._onLinkDialogDemand);
core.bus.on('media_dialog_demand', this, this._onMediaDialogDemand);
@@ -1154,7 +1064,6 @@ var SummernoteManager = Class.extend(mixins.EventDispatcherMixin, {
mixins.EventDispatcherMixin.destroy.call(this);
core.bus.off('alt_dialog_demand', this, this._onAltDialogDemand);
core.bus.off('color_picker_dialog_demand', this, this._onColorPickerDialogDemand);
core.bus.off('crop_image_dialog_demand', this, this._onCropImageDialogDemand);
core.bus.off('link_dialog_demand', this, this._onLinkDialogDemand);
core.bus.off('media_dialog_demand', this, this._onMediaDialogDemand);
@@ -1187,27 +1096,6 @@ var SummernoteManager = Class.extend(mixins.EventDispatcherMixin, {
}
altDialog.open();
},
/**
* Called when a demand to open a color picker dialog is received on the bus.
*
* @private
* @param {Object} data
*/
_onColorPickerDialogDemand: function (data) {
if (data.__alreadyDone) {
return;
}
data.__alreadyDone = true;
var colorpicker = new ColorpickerDialog(this, {
defaultColor: data.color,
});
if (data.onSave) {
colorpicker.on('colorpicker:saved', this, function (ev) {
data.onSave(ev.data.cssColor);
});
}
colorpicker.open();
},
/**
* Called when a demand to open a crop dialog is received on the bus.
*
@@ -2,10 +2,11 @@ odoo.define('web_editor.snippets.options', function (require) {
'use strict';
var core = require('web.core');
var ColorPaletteWidget = require('web_editor.ColorPalette').ColorPaletteWidget;
var Dialog = require('web.Dialog');
var rte = require('web_editor.rte');
var Widget = require('web.Widget');
var weWidgets = require('wysiwyg.widgets');
var summernoteCustomColors = require('web_editor.rte.summernote_custom_colors');
var qweb = core.qweb;
var _t = core._t;
@@ -1146,93 +1147,57 @@ registry['sizing_y'] = registry.sizing.extend({
*/
registry.colorpicker = SnippetOption.extend({
xmlDependencies: ['/web_editor/static/src/xml/snippets.xml'],
events: _.extend({}, SnippetOption.prototype.events || {}, {
'click .colorpicker button': '_onColorButtonClick',
'mouseenter .colorpicker button': '_onColorButtonEnter',
'mouseleave .colorpicker button': '_onColorButtonLeave',
'click .note-color-reset': '_onColorResetButtonClick',
}),
colorPrefix: 'bg-',
custom_events: {
color_picked: '_onColorPicked',
custom_color_picked: '_onCustomColor',
color_hover: '_onColorHovered',
color_leave: '_onColorLeft',
color_reset: '_onColorReset',
},
/**
* @override
*/
start: function () {
var self = this;
var res = this._super.apply(this, arguments);
const options = {
selectedColor: this.$target.css('background-color'),
targetClasses: [...this.$target[0].classList],
};
const proms = [this._super(...arguments)];
if (this.data.paletteExclude) {
options.excluded = this.data.paletteExclude.replace(/ /g, '').split(',');
}
if (this.data.colorPrefix) {
this.colorPrefix = this.data.colorPrefix;
options.colorPrefix = this.data.colorPrefix;
}
this.colorPalette = new ColorPaletteWidget(this, options);
proms.push(this.colorPalette.appendTo(this.$el.find('we-collapse')));
if (!this.$el.find('.colorpicker').length) {
// TODO remove old UI's code that survived
var $pt = $(qweb.render('web_editor.snippet.option.colorpicker'));
var $clpicker = $(qweb.render('web_editor.colorpicker'));
return Promise.all(proms);
},
_.each($clpicker.find('.o_colorpicker_section'), function (elem) {
$(elem).prepend("<div>" + elem.dataset.display + "</div>");
});
onFocus: function () {
this.colorPalette.reloadColorPalette();
},
// Retrieve excluded palettes list
var excluded = [];
if (this.data.paletteExclude) {
excluded = this.data.paletteExclude.replace(/ /g, '').split(',');
}
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
// Remove excluded palettes
_.each(excluded, function (exc) {
$clpicker.find('[data-name="' + exc + '"]').remove();
});
// Add common colors to palettes if not excluded
if (!('common' in excluded)) {
var $commonColorSection = $clpicker.find('[data-name="common"]');
_.each(summernoteCustomColors, function (colorRow, i) {
var $div = $('<div/>', {class: 'clearfix'}).appendTo($commonColorSection);
if (i === 0) {
// Ignore the summernote gray palette and use ours
return;
}
_.each(colorRow, function (color) {
$div.append('<button class="o_custom_color" style="background-color: ' + color + '" />');
});
});
}
$pt.find('.o_colorpicker_section_tabs').append($clpicker);
this.$el.find('we-collapse').append($pt);
/**
* Change the color of the targeted background
*
* @private
* @param {string} cssColor
* @param {boolean} isClass
*/
_changeTargetColor: function (cssColor, isClass) {
this.$target[0].classList.remove(...this.colorPalette.getClasses());
if (isClass) {
this.$target.addClass(cssColor);
} else {
this.$target.css('background-color', cssColor);
}
// TODO refactor in master
// The primary and secondary are hardcoded here (but marked as hidden)
// so they can be removed from snippets when selecting another color.
// Normally, the chosable colors do not contain them, which prevents
// them to be removed. Indeed, normally, the 'alpha' and 'beta' colors
// (which are the same) are displayed instead... but not for all themes.
var $colorpicker = this.$el.find('.colorpicker');
$colorpicker.append($('<button/>', {'class': 'd-none', 'data-color': 'primary'}));
$colorpicker.append($('<button/>', {'class': 'd-none', 'data-color': 'secondary'}));
var classes = [];
this.$el.find('.colorpicker button').each(function () {
var $color = $(this);
var color = $color.data('color');
if (!color) {
return;
}
$color.addClass('bg-' + color);
var className = self.colorPrefix + color;
if (self.$target.hasClass(className)) {
$color.addClass('selected');
}
classes.push(className);
});
this.classes = classes.join(' ');
return res;
},
//--------------------------------------------------------------------------
@@ -1245,28 +1210,26 @@ registry.colorpicker = SnippetOption.extend({
* @private
* @param {Event} ev
*/
_onColorButtonClick: function (ev) {
this.$el.find('.colorpicker button.selected').removeClass('selected');
$(ev.currentTarget).addClass('selected');
_onColorPicked: function () {
this.$target.closest('.o_editable').trigger('content_changed');
this.$target.trigger('background-color-event', false);
},
/**
* Called when a custom color is selected
* @param {*} ev
*/
_onCustomColor: function (ev) {
this._changeTargetColor(ev.data.cssColor);
this._onColorPicked();
},
/**
* Called when a color button is entered -> preview the background color.
*
* @private
* @param {Event} ev
*/
_onColorButtonEnter: function (ev) {
this.$target.removeClass(this.classes);
var color = $(ev.currentTarget).data('color');
if (color) {
this.$target.addClass(this.colorPrefix + color);
} else if ($(ev.target).hasClass('o_custom_color')) {
this.$target
.removeClass(this.classes)
.css('background-color', ev.currentTarget.style.backgroundColor);
}
_onColorHovered: function (ev) {
this._changeTargetColor(ev.data.cssColor, ev.data.isClass);
this.$target.trigger('background-color-event', true);
},
/**
@@ -1275,17 +1238,8 @@ registry.colorpicker = SnippetOption.extend({
* @private
* @param {Event} ev
*/
_onColorButtonLeave: function (ev) {
this.$target.removeClass(this.classes);
this.$target.css('background-color', '');
var $selected = this.$el.find('.colorpicker button.selected');
if ($selected.length) {
if ($selected.data('color')) {
this.$target.addClass(this.colorPrefix + $selected.data('color'));
} else if ($selected.hasClass('o_custom_color')) {
this.$target.css('background-color', $selected.css('background-color'));
}
}
_onColorLeft: function (ev) {
this._changeTargetColor(ev.data.cssColor, ev.data.isClass);
this.$target.trigger('background-color-event', 'reset');
},
/**
@@ -1294,10 +1248,9 @@ registry.colorpicker = SnippetOption.extend({
*
* @private
*/
_onColorResetButtonClick: function () {
this.$target.removeClass(this.classes).css('background-color', '');
_onColorReset: function () {
this._targetColorChange('');
this.$target.trigger('content_changed');
this.$el.find('.colorpicker button.selected').removeClass('selected');
},
});
@@ -983,33 +983,15 @@ renderer.tplButtonInfo.color = function (lang, options) {
event: 'color',
value: '{"backColor":"#B35E9B"}'
});
var foreColorItems = [
'<li><div class="btn-group flex-column">',
'<div class="note-color-palette" data-target-event="foreColor"></div>',
'<h6 class="note-custom-color mt8" data-event="customColor" data-value="foreColor" title="' + lang.color.custom + '">',
lang.color.custom + '</h6>',
'<div class="note-custom-color-palette" data-target-event="foreColor"></div>',
'</div></li>',
];
var backColorItems = [
'<li><div class="btn-group flex-column">',
'<div class="note-color-reset" data-event="backColor" data-value="inherit" title="' + lang.color.transparent + '">',
lang.color.setTransparent + '</div>',
'<div class="note-color-palette" data-target-event="backColor"></div>',
'<h6 class="note-custom-color mt8" data-event="customColor" data-value="backColor" title="' + lang.color.custom + '">',
lang.color.custom + '</h6>',
'<div class="note-custom-color-palette" data-target-event="backColor"></div>',
'</div></li>',
];
var foreColorButton = renderer.getTemplate().button(foreColorButtonLabel, {
className: 'note-fore-color-preview mx-1',
title: lang.color.foreground,
dropdown: renderer.getTemplate().dropdown(foreColorItems)
dropdown: renderer.getTemplate().dropdown('<li><div data-event-name="foreColor" class="colorPalette"/></li>'),
});
var backColorButton = renderer.getTemplate().button(backColorButtonLabel, {
className: 'note-back-color-preview mx-1',
title: lang.color.background,
dropdown: renderer.getTemplate().dropdown(backColorItems)
dropdown: renderer.getTemplate().dropdown('<li><div data-event-name="backColor" class="colorPalette"/></li>'),
});
return recentColorButton + foreColorButton + backColorButton;
};
@@ -0,0 +1,342 @@
odoo.define('web_editor.ColorPalette', function (require) {
'use strict';
const core = require('web.core');
const ColorPickerDialog = require('web.ColorpickerDialog');
const Dialog = require('web.Dialog');
const Widget = require('web.Widget');
const summernoteCustomColors = require('web_editor.rte.summernote_custom_colors');
const qweb = core.qweb;
const _t = core._t;
let templatePromise;
const ColorPaletteWidget = Widget.extend({
xmlDependencies: ['/web_editor/static/src/xml/snippets.xml'],
template: 'web_editor.snippet.option.colorpicker',
events: {
'click button': '_onColorButtonClick',
'mouseenter button': '_onColorButtonEnter',
'mouseleave button': '_onColorButtonLeave',
'click .o_colorpicker_reset': '_onColorResetButtonClick',
'click .o_add_custom_color': '_onCustomColorButtonClick',
},
/**
* @override
*
* @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 {JQuery} [options.$editable=$()] Editable content from which the custom colors are retrieved.
*/
init: function (parent, options) {
this._super.apply(this, arguments);
this.summernoteCustomColorsArray = [].concat(...summernoteCustomColors).map((color) => this._convertHexToCssRgba(color));
this.options = _.extend({
selectedColor: false,
targetClasses: [],
colorPrefix: 'bg-',
resetButton: true,
excluded: [],
$editable: $(),
}, options || {});
this.trigger_up('request_editable', {callback: val => this.options.$editable = val});
},
/**
* @override
*/
willStart: async function () {
const res = this._super.apply(this, arguments);
// We can call the colorPalette multiple times but only need 1 rpc
if (!templatePromise && !qweb.has_template('web_editor.colorpicker')) {
templatePromise = this._rpc({
model: 'ir.ui.view',
method: 'read_template',
args: ['web_editor.colorpicker'],
}).then(template => {
return qweb.add_template('<templates>' + template + '</templates>');
});
}
await templatePromise;
return res;
},
/**
* @override
*/
start: function () {
const res = this._super.apply(this, arguments);
const $wrapper = this.$('.o_colorpicker_section_tabs');
$(qweb.render('web_editor.colorpicker')).appendTo($wrapper);
this.el.querySelectorAll('.o_colorpicker_section').forEach(elem => {
$(elem).prepend('<div>' + (elem.dataset.display || '') + '</div>');
});
if (this.options.resetButton) {
this.$('.o_colorpicker_reset').removeClass('d-none');
}
// Remove excluded palettes
_.each(this.options.excluded, function (exc) {
$wrapper.find('[data-name="' + exc + '"]').remove();
});
// Render common colors
if (!this.options.excluded.includes('common')) {
const $commonColorSection = this.$('[data-name="common"]');
summernoteCustomColors.forEach((colorRow, i) => {
if (i === 0) {
return; // Ignore the summernote gray palette and use ours
}
const $div = $('<div/>', {class: 'clearfix'}).appendTo($commonColorSection);
colorRow.forEach(color => {
$div.append(this._createColorButton(color, ['o_common_color']));
});
});
}
// Render custom colors
this._buildCustomColor();
// TODO refactor in master
// The primary and secondary are hardcoded here (but marked as hidden)
// so they can be removed from snippets when selecting another color.
// Normally, the chosable colors do not contain them, which prevents
// them to be removed. Indeed, normally, the 'alpha' and 'beta' colors
// (which are the same) are displayed instead... but not for all themes.
this.$el.append($('<button/>', {'class': 'd-none', 'data-color': 'primary'}));
this.$el.append($('<button/>', {'class': 'd-none', 'data-color': 'secondary'}));
// Compute class colors
this.classes = [];
this.el.querySelectorAll('button[data-color]').forEach(elem => {
const color = elem.dataset.color;
const $color = $(elem);
$color.addClass('bg-' + color);
const className = this.options.colorPrefix + color;
if (this.options.targetClasses.includes(className)) {
$color.addClass('selected');
}
this.classes.push(className);
});
return res;
},
/**
* Return a list of the color classes 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();
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* @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 => {
const color = el.style.color || el.style.backgroundColor;
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
*/
_addCustomColorButton: function (color, classes = []) {
classes.push('o_custom_color');
this._createColorButton(color, classes).appendTo(this.$('.o_colorpicker_section[data-name="theme"]'));
},
/**
* Return a color button.
*
* @param {string} color Css rgb(a) string
* @param {Array<string>} classes Classes added to the button
*/
_createColorButton: function (color, classes) {
const cssColor = this._convertHexToCssRgba(color);
const selectedColor = this.options.selectedColor && this._convertHexToCssRgba(this.options.selectedColor);
if (selectedColor === cssColor) {
classes.push('selected');
}
return $('<button/>', {
class: classes.join(' '),
style: 'background-color:' + color + ';',
});
},
/**
* Return the hexadecimal css color as an rgb(a) css color: rgb((r, g, b) | rgba(r, g, b, a)
*
* @private
* @param {string} hex Css hex string (#FFFFFF(FF))
*/
_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
);
}
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
/**
* Called when a color button is clicked.
*
* @private
* @param {Event} ev
*/
_onColorButtonClick: function (ev) {
const target = 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,
});
},
/**
* Called when a color button is entered.
*
* @private
* @param {Event} ev
*/
_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,
});
},
/**
* Called when a color button is left.
*
* @private
* @param {Event} ev
*/
_onColorButtonLeave: function (ev) {
ev.stopPropagation();
let params = {
cssColor: '',
isClass: false,
target: ev.target,
};
const selected = this.el.querySelector('button.selected');
if (selected) {
params = {
cssColor: selected.style.backgroundColor || this.options.colorPrefix + selected.dataset.color,
isClass: !!selected.dataset.color,
target: ev.target,
};
}
this.trigger_up('color_leave', params);
},
/**
* Called when the color reset button is clicked.
*
* @private
* @param {Event} ev
*/
_onColorResetButtonClick: function (ev) {
this.$('button.selected').removeClass('selected');
this.trigger_up('color_reset', {
target: ev.target,
});
},
/**
* Called when the custom color button is clicked.
*
* @private
* @param {Event} ev
*/
_onCustomColorButtonClick: async function (ev) {
const originalEvent = ev;
const $selected = this.$('button.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,
});
});
colorpicker.open();
},
});
const ColorPaletteDialog = Dialog.extend({
/**
* @override
*/
init: function (parent, options) {
this.options = options;
this._super(parent, {
size: 'small',
title: _t('Pick a color'),
});
},
/**
* @override
*/
start: function () {
const proms = [this._super(...arguments)];
const colorPalette = new ColorPaletteWidget(this, this.options);
proms.push(colorPalette.appendTo(this.$el));
return Promise.all(proms);
},
});
return {
ColorPaletteWidget: ColorPaletteWidget,
ColorPaletteDialog: ColorPaletteDialog,
};
});
@@ -303,12 +303,14 @@ $o-theme-color-palette-number: 1;
// Greyscale transparent colours
$o-transparent-grays: (
'black-15': rgba(black, 0.15),
'black-25': rgba(black, 0.25),
'black-50': rgba(black, 0.5),
'black-75': rgba(black, 0.75),
'white-25': rgba(white, 0.25),
'white-50': rgba(white, 0.5),
'white-75': rgba(white, 0.75),
'white-85': rgba(white, 0.85),
) !default;
$o-gray-color-palettes: (
(
@@ -479,7 +479,7 @@ body.editor_enable.editor_has_snippets {
.o_colorpicker_section {
> :first-child {
margin: 8px 0 4px;
margin: 4px 0;
}
button {
position: relative;
@@ -508,11 +508,13 @@ body.editor_enable.editor_has_snippets {
}
}
&[data-name="transparent_grayscale"] button {
@include o-alpha-button-preview;
&[data-name="transparent_grayscale"], &[data-name="theme"] {
button {
@include o-alpha-button-preview;
&::before, &::after {
box-shadow: inherit;
&::before, &::after {
box-shadow: inherit;
}
}
}
}
@@ -37,12 +37,16 @@
<!-- options -->
<div t-name="web_editor.snippet.option.colorpicker" class="colorpicker">
<div class="btn-group palette-reset">
<div class="note-color-reset" data-event="foreColor" data-value="inherit" title="None" role="img" aria-label="No color">
<i class="fa fa-ban"/>
</div>
</div>
<div class="o_colorpicker_sections px-1 pb8">
<div class="o_colorpicker_section">
<a class="btn w-50 float-left o_colorpicker_reset d-none">
<i class="fa fa-ban"/> Reset
</a>
<a class="btn w-50 o_add_custom_color" data-name="custom">
<i class="fa fa-plus"/> Custom
</a>
</div>
<div class="tab-content o_colorpicker_section_tabs"/>
</div>
</div>
@@ -150,12 +150,20 @@ QUnit.module('web_editor', {}, function () {
assert.strictEqual(range.sc, pText,
"should select the text");
await testUtils.dom.click($field.find('.note-toolbar .note-back-color-preview button:first'));
async function openColorpicker(selector) {
const $colorpicker = $field.find(selector);
const openingProm = new Promise(resolve => {
$colorpicker.one('shown.bs.dropdown', () => resolve());
});
await testUtils.dom.click($colorpicker.find('button:first'));
return openingProm;
}
await openColorpicker('.note-toolbar .note-back-color-preview');
assert.ok($field.find('.note-back-color-preview').hasClass('show'),
"should display the color picker");
await testUtils.dom.click($field.find('.note-toolbar .note-back-color-preview button[data-value="#00FFFF"]'));
await testUtils.dom.click($field.find('.note-toolbar .note-back-color-preview button[style="background-color:#00FFFF;"]'));
assert.ok(!$field.find('.note-back-color-preview').hasClass('show'),
"should close the color picker");
@@ -180,8 +188,8 @@ QUnit.module('web_editor', {}, function () {
Wysiwyg.setRange(fontContent, 5, pText, 2);
// text is selected
await testUtils.dom.click($field.find('.note-toolbar .note-back-color-preview button:first'));
await testUtils.dom.click($field.find('.note-toolbar .note-back-color-preview button[data-value="bg-gamma"]'));
await openColorpicker('.note-toolbar .note-back-color-preview');
await testUtils.dom.click($field.find('.note-toolbar .note-back-color-preview button.bg-gamma'));
assert.strictEqual($field.find('.note-editable').html(),
'<p>t<font style="background-color: rgb(0, 255, 255);">oto t</font><font style="" class="bg-gamma">oto&nbsp;</font><font class="bg-gamma" style="">to</font>to</p><p>tata</p>',
@@ -329,7 +337,7 @@ QUnit.module('web_editor', {}, function () {
// text is selected
await testUtils.dom.click($field.find('.note-toolbar .note-back-color-preview button:first'));
await testUtils.dom.click($field.find('.note-toolbar .note-back-color-preview button[data-value="bg-gamma"]'));
await testUtils.dom.click($field.find('.note-toolbar .note-back-color-preview button.bg-gamma'));
await testUtils.form.clickSave(form);
+21 -17
View File
@@ -47,6 +47,7 @@
<script type="text/javascript" src="/web_editor/static/src/js/wysiwyg/widgets/media.js"/>
<script type="text/javascript" src="/web_editor/static/src/js/wysiwyg/widgets/dialog.js"/>
<script type="text/javascript" src="/web_editor/static/src/js/wysiwyg/widgets/alt_dialog.js"/>
<script type="text/javascript" src="/web_editor/static/src/js/wysiwyg/widgets/color_palette.js"/>
<script type="text/javascript" src="/web_editor/static/src/js/wysiwyg/widgets/crop_dialog.js"/>
<script type="text/javascript" src="/web_editor/static/src/js/wysiwyg/widgets/link_dialog.js"/>
<script type="text/javascript" src="/web_editor/static/src/js/wysiwyg/widgets/media_dialog.js"/>
@@ -204,38 +205,41 @@
Sections should contain a set of button with a data-color attribute. When selecting such a button to
edit style, the "bg-x" class or the "text-x" will be automatically added.
If a color is no longer used you need to add the d-none class to it and not remove it from this file !!
Else you will no longer be able to use them.
-->
<template id="web_editor.colorpicker" name="Color-Picker">
<colorpicker>
<div class="o_colorpicker_section" data-name="theme" data-display="Theme Colors" data-icon-class="fa fa-flask">
<div class="o_colorpicker_section" data-name="theme">
<button data-color="alpha"></button>
<button data-color="beta"></button>
<button data-color="gamma"></button>
<button data-color="delta"></button>
<button data-color="epsilon"></button>
</div>
<div class="o_colorpicker_section" data-name="transparent_grayscale" data-display="Transparent Colors" data-icon-class="fa fa-eye-slash">
<button class="o_btn_transparent"></button>
<div class="o_colorpicker_section" data-name="common">
<button data-color="black"></button>
<button data-color="900"></button>
<button data-color="800"></button>
<button data-color="700" class="d-none"></button>
<button data-color="600"></button>
<button data-color="500" class="d-none"></button>
<button data-color="400"></button>
<button data-color="300" class="d-none"></button>
<button data-color="200"></button>
<button data-color="100"></button>
<button data-color="white"></button>
</div>
<div class="o_colorpicker_section" data-name="transparent_grayscale" data-display="Opacity Filters" data-icon-class="fa fa-eye-slash">
<button data-color="black-15"></button>
<button data-color="black-25"></button>
<button data-color="black-50"></button>
<button data-color="black-75"></button>
<button data-color="white-25"></button>
<button data-color="white-50"></button>
<button data-color="white-75"></button>
</div>
<div class="o_colorpicker_section" data-name="common" data-display="Common Colors" data-icon-class="fa fa-paint-brush">
<button data-color="black"></button>
<button data-color="white"></button>
<div class="clearfix"/>
<button data-color="900"></button>
<button data-color="800"></button>
<button data-color="700"></button>
<button data-color="600"></button>
<button data-color="500"></button>
<button data-color="400" class="d-none"></button><!-- Hide this one to be able to use it but not display it -->
<button data-color="300"></button>
<button data-color="200"></button>
<button data-color="100"></button>
<button data-color="white-85"></button>
</div>
</colorpicker>
</template>
@@ -6,8 +6,8 @@ var core = require('web.core');
var Dialog = require('web.Dialog');
var Widget = require('web.Widget');
var weWidgets = require('wysiwyg.widgets');
var ColorpickerDialog = require('web.ColorpickerDialog');
var websiteNavbarData = require('website.navbar');
var ColorPaletteDialog = require('web_editor.ColorPalette').ColorPaletteDialog;
var _t = core._t;
@@ -515,13 +515,16 @@ var ThemeCustomizeDialog = Dialog.extend({
var colorType = $color.data('colorType');
return new Promise(function (resolve, reject) {
var colorpicker = new ColorpickerDialog(self, {
var colorpicker = new ColorPaletteDialog(self, {
resetButton: false,
defaultColor: $color.css('background-color'),
excluded: ['transparent_grayscale'],
});
var chosenColor = undefined;
colorpicker.on('colorpicker:saved', self, function (ev) {
colorpicker.on('color_picked custom_color_picked', self, function (ev) {
ev.stopPropagation();
chosenColor = ev.data.cssColor;
colorpicker.close();
});
colorpicker.on('closed', self, function (ev) {
if (chosenColor === undefined) {
+3 -3
View File
@@ -996,7 +996,7 @@
data-selector=".s_hr"
data-target="hr"
data-color-prefix="border-"
data-palette-exclude="transparent_grayscale, common">
data-palette-exclude="transparent_grayscale, common, custom">
<we-collapse-area>
<we-toggler>Color</we-toggler>
<we-collapse/>
@@ -1276,7 +1276,7 @@
<!-- FILTER | .s_parallax -->
<div data-js="colorpicker"
data-selector=".parallax"
data-palette-exclude="common, theme"
data-palette-exclude="common, theme, custom"
data-palette-default="transparent_grayscale">
<we-collapse-area>
<we-toggler>Filter</we-toggler>
@@ -1342,7 +1342,7 @@
<div data-js="topMenuColor"
data-selector="[data-main-object^='website.page('] #wrapwrap > header"
data-palette-exclude="theme,common"
data-palette-exclude="theme,common, custom"
data-palette-default="transparent_grayscale"
data-no-check="true">
<we-collapse-area>