[IMP] web_editor, mass_mailing, website: reorganize Powerbox items

This reorganizes the Powerbox items under different category names, in a
more preferable order.

task-2901697

Part-of: odoo/odoo#96287
This commit is contained in:
Antoine Guenet
2022-08-08 14:17:00 +02:00
parent 9154e707df
commit 8e1f9fffc8
5 changed files with 141 additions and 131 deletions
+5 -4
View File
@@ -37,9 +37,10 @@ const MassMailingWysiwyg = Wysiwyg.extend({
/**
* @override
*/
_getCommands: function () {
const commands = this._super();
const linkCommands = commands.filter(command => command.title === 'Link' || command.title === 'Button');
_getPowerboxOptions: function () {
const options = this._super();
const {commands} = options;
const linkCommands = commands.filter(command => command.name === 'Link' || command.name === 'Button');
for (const linkCommand of linkCommands) {
// Don't open the dialog: use the link tools.
linkCommand.callback = () => this.toggleLinkTools({forceDialog: false});
@@ -55,7 +56,7 @@ const MassMailingWysiwyg = Wysiwyg.extend({
}
}
}
return commands;
return {...options, commands};
},
/**
* @override
@@ -1797,50 +1797,15 @@ export class OdooEditor extends EventTarget {
});
const mainCategories = [
{ name: 'Format', priority: 30 },
{ name: 'Structure', priority: 20 },
{ name: 'Widgets', priority: 10 },
{ name: this.options._t('Structure'), priority: 70 },
{ name: this.options._t('Format'), priority: 60 },
{ name: this.options._t('Widgets'), priority: 30 },
];
const mainCommands = [
{
category: this.options._t('Basic blocks'),
name: this.options._t('Heading 1'),
description: this.options._t('Big section heading.'),
fontawesome: 'fa-header',
callback: () => {
this.execCommand('setTag', 'H1');
},
},
{
category: this.options._t('Basic blocks'),
name: this.options._t('Heading 2'),
description: this.options._t('Medium section heading.'),
fontawesome: 'fa-header',
callback: () => {
this.execCommand('setTag', 'H2');
},
},
{
category: this.options._t('Basic blocks'),
name: this.options._t('Heading 3'),
description: this.options._t('Small section heading.'),
fontawesome: 'fa-header',
callback: () => {
this.execCommand('setTag', 'H3');
},
},
{
category: this.options._t('Basic blocks'),
name: this.options._t('Text'),
description: this.options._t('Paragraph block.'),
fontawesome: 'fa-paragraph',
callback: () => {
this.execCommand('setTag', 'P');
},
},
{
category: this.options._t('Basic blocks'),
category: this.options._t('Structure'),
name: this.options._t('Bulleted list'),
priority: 110,
description: this.options._t('Create a simple bulleted list.'),
fontawesome: 'fa-list-ul',
callback: () => {
@@ -1848,8 +1813,9 @@ export class OdooEditor extends EventTarget {
},
},
{
category: this.options._t('Basic blocks'),
category: this.options._t('Structure'),
name: this.options._t('Numbered list'),
priority: 100,
description: this.options._t('Create a list with numbering.'),
fontawesome: 'fa-list-ol',
callback: () => {
@@ -1857,8 +1823,9 @@ export class OdooEditor extends EventTarget {
},
},
{
category: this.options._t('Basic blocks'),
category: this.options._t('Structure'),
name: this.options._t('Checklist'),
priority: 90,
description: this.options._t('Track tasks with a checklist.'),
fontawesome: 'fa-check-square-o',
callback: () => {
@@ -1866,17 +1833,9 @@ export class OdooEditor extends EventTarget {
},
},
{
category: this.options._t('Basic blocks'),
name: this.options._t('Separator'),
description: this.options._t('Insert an horizontal rule separator.'),
fontawesome: 'fa-minus',
callback: () => {
this.execCommand('insertHorizontalRule');
},
},
{
category: this.options._t('Basic blocks'),
category: this.options._t('Structure'),
name: this.options._t('Table'),
priority: 80,
description: this.options._t('Insert a table.'),
fontawesome: 'fa-table',
callback: () => {
@@ -1884,8 +1843,49 @@ export class OdooEditor extends EventTarget {
},
},
{
category: this.options._t('Basic blocks'),
category: this.options._t('Structure'),
name: this.options._t('Separator'),
priority: 40,
description: this.options._t('Insert an horizontal rule separator.'),
fontawesome: 'fa-minus',
callback: () => {
this.execCommand('insertHorizontalRule');
},
},
{
category: this.options._t('Format'),
name: this.options._t('Heading 1'),
priority: 50,
description: this.options._t('Big section heading.'),
fontawesome: 'fa-header',
callback: () => {
this.execCommand('setTag', 'H1');
},
},
{
category: this.options._t('Format'),
name: this.options._t('Heading 2'),
priority: 40,
description: this.options._t('Medium section heading.'),
fontawesome: 'fa-header',
callback: () => {
this.execCommand('setTag', 'H2');
},
},
{
category: this.options._t('Format'),
name: this.options._t('Heading 3'),
priority: 30,
description: this.options._t('Small section heading.'),
fontawesome: 'fa-header',
callback: () => {
this.execCommand('setTag', 'H3');
},
},
{
category: this.options._t('Format'),
name: this.options._t('Switch direction'),
priority: 20,
description: this.options._t('Switch the text\'s direction.'),
fontawesome: 'fa-exchange',
callback: () => {
@@ -1893,9 +1893,20 @@ export class OdooEditor extends EventTarget {
},
},
{
category: 'Widgets',
name: '3 Stars',
description: 'Insert a rating over 3 stars.',
category: this.options._t('Format'),
name: this.options._t('Text'),
priority: 10,
description: this.options._t('Paragraph block.'),
fontawesome: 'fa-paragraph',
callback: () => {
this.execCommand('setTag', 'P');
},
},
{
category: this.options._t('Widgets'),
name: this.options._t('3 Stars'),
priority: 20,
description: this.options._t('Insert a rating over 3 stars.'),
fontawesome: 'fa-star-o',
callback: () => {
let html = '\u200B<span contenteditable="false" class="o_stars o_three_stars">';
@@ -1905,9 +1916,10 @@ export class OdooEditor extends EventTarget {
},
},
{
category: 'Widgets',
name: '5 Stars',
description: 'Insert a rating over 5 stars.',
category: this.options._t('Widgets'),
name: this.options._t('5 Stars'),
priority: 10,
description: this.options._t('Insert a rating over 5 stars.'),
fontawesome: 'fa-star',
callback: () => {
let html = '\u200B<span contenteditable="false" class="o_stars o_five_stars">';
@@ -89,14 +89,23 @@ export class Powerbox {
* @param {Array<{name: string, priority: number}} [categories=this.categories]
*/
open(commands=this.commands, categories=this.categories) {
const order = (a, b) => b.priority - a.priority || a.name.localeCompare(b.name);
if (this.onOpen) {
this.onOpen();
}
// Remove duplicate category names, keeping only last declared version.
// Remove duplicate category names, keeping only last declared version,
// and order them.
categories = [...categories].reverse().filter((category, index, cats) => (
cats.findIndex(cat => cat.name === category.name) === index
));
commands = this._getCurrentCommands(this._orderByPriority(commands), this._orderByPriority(categories));
)).sort(order);
// Apply optional filters to disable commands, then order them.
for (let filter of this.commandFilters) {
commands = filter(commands);
}
commands = commands.filter(command => !command.isDisabled || !command.isDisabled()).sort(order);
commands = this._groupCommands(commands, categories).flatMap(group => group[1]);
this._context = {
commands, categories, filteredCommands: commands, selectedCommand: undefined,
initialTarget: this.editable, initialValue: this.editable.textContent,
@@ -213,52 +222,6 @@ export class Powerbox {
}
this.close();
};
/**
* Filter an array of commands based on the given term using fuzzy matching.
*
* @private
* @param {PowerboxCommand[]} commands
* @param {string} term
* @returns {PowerboxCommand[]}
*/
_filter(commands, term) {
term = term.toLowerCase().replaceAll(/\s/g, '\\s').replaceAll('\u200B', '');
if (term.length) {
const regex = new RegExp(term.split('').map(char => char.replace(REGEX_RESERVED_CHARS, '\\$&')).join('.*'));
return commands.filter(command => `${command.category} ${command.name}`.toLowerCase().match(regex));
} else {
return commands;
}
}
/**
* Take a list of commands, filter them based on `commandFilters` and
* `isDisabled`) and return the remaining commands, ordered so that commands
* that belong to the same category are grouped together.
* Note: `commandFilters` is on the Powerbox instance and allows the
* filtering of several commands at once (eg, a whole category), while
* `isDisabled` is on a specific command and is made to target that command.
*
* @see commandFilters {Array<() => PowerboxCommand[]} return commands that can be used.
* @see command.isDisabled {() => boolean} return true if the command is disabled.
* @private
* @param {PowerboxCommand[]} commands
* @param {Array<{name: string, priority: number}} categories
* @returns {PowerboxCommand[]}
*/
_getCurrentCommands(commands, categories) {
/**
* Some available commands may need to be disabled in certain
* situations. i.e.: in a knowledge article, prevent the usage of the
* /template command inside a /template block.
*/
for (let filter of this.commandFilters) {
commands = filter(commands);
}
// Do not show disabled commands.
commands = commands.filter(command => !command.isDisabled || !command.isDisabled());
// Reorder the commands based on their categories' priorities.
return this._groupCommands(commands, categories).flatMap(group => group[1]);
}
/**
* Takes a list of commands and returns an object whose keys are all
* existing category names and whose values are each of these categories'
@@ -293,7 +256,7 @@ export class Powerbox {
* @returns {PowerboxCommand[] | Array<{name: string, priority: number}}
*/
_orderByPriority(commandsOrCategories) {
return commandsOrCategories.sort((a, b) => a.priority - b.priority || a.name.localeCompare(b.name));
return [...commandsOrCategories].sort((a, b) => b.priority - a.priority || a.name.localeCompare(b.name));
}
/**
* Recompute the Powerbox's position base on the selection in the document.
@@ -360,9 +323,15 @@ export class Powerbox {
if (this._context.lastText.match(/\s/)) {
this.close();
} else {
this._context.filteredCommands = this._context.lastText === ''
? this._context.commands
: this._filter(this._context.commands, this._context.lastText);
const term = this._context.lastText.toLowerCase().replaceAll(/\s/g, '\\s').replaceAll('\u200B', '');
if (term.length) {
const regex = new RegExp(term.split('').map(char => char.replace(REGEX_RESERVED_CHARS, '\\$&')).join('.*'));
this._context.filteredCommands = this._context.commands.filter(command => (
`${command.category} ${command.name}`.toLowerCase().match(regex)
));
} else {
this._context.filteredCommands = this._context.commands;
}
this._render(this._context.filteredCommands, this._context.categories);
}
}
@@ -118,7 +118,7 @@ const Wysiwyg = Widget.extend({
this.toolbar = new Toolbar(this, this.options.toolbarTemplate);
await this.toolbar.appendTo(document.createElement('void'));
const commands = this._getCommands();
const powerboxOptions = this._getPowerboxOptions();
let editorCollaborationOptions;
if (
@@ -182,7 +182,8 @@ const Wysiwyg = Widget.extend({
this.linkTools = undefined;
}
},
commands: commands,
commands: powerboxOptions.commands,
categories: powerboxOptions.categories,
plugins: options.editorPlugins,
direction: localization.direction || 'ltr',
}, editorCollaborationOptions));
@@ -1845,12 +1846,14 @@ const Wysiwyg = Widget.extend({
});
}
},
_getCommands: function () {
const options = this._editorOptions();
_getPowerboxOptions: function () {
const editorOptions = this._editorOptions();
const categories = [];
const commands = [
{
category: _t('Basic blocks'),
category: _t('Structure'),
name: _t('Quote'),
priority: 30,
description: _t('Add a blockquote section.'),
fontawesome: 'fa-quote-right',
callback: () => {
@@ -1858,8 +1861,9 @@ const Wysiwyg = Widget.extend({
},
},
{
category: _t('Basic blocks'),
category: _t('Structure'),
name: _t('Code'),
priority: 20,
description: _t('Add a code section.'),
fontawesome: 'fa-code',
callback: () => {
@@ -1883,11 +1887,13 @@ const Wysiwyg = Widget.extend({
},
},
];
if (options.allowCommandLink) {
if (editorOptions.allowCommandLink) {
categories.push({ name: 'Navigation', priority: 40 });
commands.push(
{
category: _t('Navigation'),
name: _t('Link'),
priority: 40,
description: _t('Add a link.'),
fontawesome: 'fa-link',
callback: () => {
@@ -1897,6 +1903,7 @@ const Wysiwyg = Widget.extend({
{
category: _t('Navigation'),
name: _t('Button'),
priority: 30,
description: _t('Add a button.'),
fontawesome: 'fa-link',
callback: () => {
@@ -1909,10 +1916,14 @@ const Wysiwyg = Widget.extend({
},
);
}
if (options.allowCommandImage) {
if (editorOptions.allowCommandImage || editorOptions.allowCommandVideo) {
categories.push({ name: 'Media', priority: 50 });
}
if (editorOptions.allowCommandImage) {
commands.push({
category: _t('Media'),
name: _t('Image'),
priority: 40,
description: _t('Insert an image.'),
fontawesome: 'fa-file-image-o',
callback: () => {
@@ -1920,10 +1931,11 @@ const Wysiwyg = Widget.extend({
},
});
}
if (options.allowCommandVideo) {
if (editorOptions.allowCommandVideo) {
commands.push({
category: _t('Media'),
name: _t('Video'),
priority: 30,
description: _t('Insert a video.'),
fontawesome: 'fa-file-video-o',
callback: () => {
@@ -1931,10 +1943,13 @@ const Wysiwyg = Widget.extend({
},
});
}
if (options.powerboxCommands) {
commands.push(...options.powerboxCommands);
if (editorOptions.powerboxCategories) {
categories.push(...editorOptions.powerboxCategories);
}
return commands;
if (editorOptions.powerboxCommands) {
commands.push(...editorOptions.powerboxCommands);
}
return {commands, categories};
},
/**
@@ -187,6 +187,7 @@ export class WysiwygAdapterComponent extends ComponentAdapter {
* @return {Object} Params to pass to the wysiwyg widget.
*/
get _wysiwygParams() {
const powerboxItems = this._getSnippetsPowerboxItems();
return {
snippets: 'website.snippets',
recordInfo: {
@@ -206,7 +207,8 @@ export class WysiwygAdapterComponent extends ComponentAdapter {
document: this.websiteService.pageDocument,
sideAttach: true,
isWebsite: true, // If set to true, it will trigger isolated behaviours in website patches. (.include)
powerboxCommands: this._getSnippetsCommands(),
powerboxCommands: powerboxItems[0],
powerboxCategories: powerboxItems[1],
bindLinkTool: true,
showEmptyElementHint: false,
getReadOnlyAreas: this._getReadOnlyAreas.bind(this),
@@ -406,7 +408,7 @@ export class WysiwygAdapterComponent extends ComponentAdapter {
*
* @private
*/
_getSnippetsCommands() {
_getSnippetsPowerboxItems() {
const snippetCommandCallback = (selector) => {
const $separatorBody = $(selector);
const $clonedBody = $separatorBody.clone().removeClass('oe_snippet_body');
@@ -417,10 +419,11 @@ export class WysiwygAdapterComponent extends ComponentAdapter {
this.widget.snippetsMenu.callPostSnippetDrop($clonedBody);
}
};
return [
const commands = [
{
category: 'Website',
name: 'Alert',
priority: 100,
description: 'Insert an alert snippet.',
fontawesome: 'fa-info',
callback: () => {
@@ -430,6 +433,7 @@ export class WysiwygAdapterComponent extends ComponentAdapter {
{
category: 'Website',
name: 'Rating',
priority: 90,
description: 'Insert a rating snippet.',
fontawesome: 'fa-star-half-o',
callback: () => {
@@ -439,6 +443,7 @@ export class WysiwygAdapterComponent extends ComponentAdapter {
{
category: 'Website',
name: 'Card',
priority: 80,
description: 'Insert a card snippet.',
fontawesome: 'fa-sticky-note',
callback: () => {
@@ -448,6 +453,7 @@ export class WysiwygAdapterComponent extends ComponentAdapter {
{
category: 'Website',
name: 'Share',
priority: 70,
description: 'Insert a share snippet.',
fontawesome: 'fa-share-square-o',
callback: () => {
@@ -457,6 +463,7 @@ export class WysiwygAdapterComponent extends ComponentAdapter {
{
category: 'Website',
name: 'Text Highlight',
priority: 60,
description: 'Insert a text Highlight snippet.',
fontawesome: 'fa-sticky-note',
callback: () => {
@@ -466,6 +473,7 @@ export class WysiwygAdapterComponent extends ComponentAdapter {
{
category: 'Website',
name: 'Chart',
priority: 50,
description: 'Insert a chart snippet.',
fontawesome: 'fa-bar-chart',
callback: () => {
@@ -475,6 +483,7 @@ export class WysiwygAdapterComponent extends ComponentAdapter {
{
category: 'Website',
name: 'Progress Bar',
priority: 40,
description: 'Insert a progress bar snippet.',
fontawesome: 'fa-spinner',
callback: () => {
@@ -484,6 +493,7 @@ export class WysiwygAdapterComponent extends ComponentAdapter {
{
category: 'Website',
name: 'Badge',
priority: 30,
description: 'Insert a badge snippet.',
fontawesome: 'fa-tags',
callback: () => {
@@ -493,6 +503,7 @@ export class WysiwygAdapterComponent extends ComponentAdapter {
{
category: 'Website',
name: 'Blockquote',
priority: 20,
description: 'Insert a blockquote snippet.',
fontawesome: 'fa-quote-left',
callback: () => {
@@ -502,6 +513,7 @@ export class WysiwygAdapterComponent extends ComponentAdapter {
{
category: 'Website',
name: 'Separator',
priority: 10,
description: 'Insert an horizontal separator sippet.',
fontawesome: 'fa-minus',
callback: () => {
@@ -509,6 +521,7 @@ export class WysiwygAdapterComponent extends ComponentAdapter {
},
},
];
return [commands, [{ name: 'Website', priority: 20 }]];
}
/**
* @returns {boolean} true if the page has been altered.