From 8e1f9fffc806ece650fcb09549cfcbbdd5e08369 Mon Sep 17 00:00:00 2001 From: Antoine Guenet Date: Tue, 19 Jul 2022 12:52:46 +0000 Subject: [PATCH] [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 --- addons/mass_mailing/static/src/js/wysiwyg.js | 9 +- .../js/editor/odoo-editor/src/OdooEditor.js | 130 ++++++++++-------- .../odoo-editor/src/powerbox/Powerbox.js | 75 +++------- .../static/src/js/wysiwyg/wysiwyg.js | 39 ++++-- .../wysiwyg_adapter/wysiwyg_adapter.js | 19 ++- 5 files changed, 141 insertions(+), 131 deletions(-) diff --git a/addons/mass_mailing/static/src/js/wysiwyg.js b/addons/mass_mailing/static/src/js/wysiwyg.js index 40efa7e7dfa..c86502fc05e 100644 --- a/addons/mass_mailing/static/src/js/wysiwyg.js +++ b/addons/mass_mailing/static/src/js/wysiwyg.js @@ -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 diff --git a/addons/web_editor/static/src/js/editor/odoo-editor/src/OdooEditor.js b/addons/web_editor/static/src/js/editor/odoo-editor/src/OdooEditor.js index bc6751469ae..ff1c8fd2a11 100644 --- a/addons/web_editor/static/src/js/editor/odoo-editor/src/OdooEditor.js +++ b/addons/web_editor/static/src/js/editor/odoo-editor/src/OdooEditor.js @@ -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'; @@ -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'; diff --git a/addons/web_editor/static/src/js/editor/odoo-editor/src/powerbox/Powerbox.js b/addons/web_editor/static/src/js/editor/odoo-editor/src/powerbox/Powerbox.js index 10281a86ffe..ff6af2443fa 100644 --- a/addons/web_editor/static/src/js/editor/odoo-editor/src/powerbox/Powerbox.js +++ b/addons/web_editor/static/src/js/editor/odoo-editor/src/powerbox/Powerbox.js @@ -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); } } diff --git a/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js b/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js index b8292177443..477b3ea0742 100644 --- a/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js +++ b/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js @@ -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}; }, /** diff --git a/addons/website/static/src/components/wysiwyg_adapter/wysiwyg_adapter.js b/addons/website/static/src/components/wysiwyg_adapter/wysiwyg_adapter.js index bde177d6c24..fde6edec486 100644 --- a/addons/website/static/src/components/wysiwyg_adapter/wysiwyg_adapter.js +++ b/addons/website/static/src/components/wysiwyg_adapter/wysiwyg_adapter.js @@ -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.