[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:
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user