From 467dadda412b48453879fb480cb04d920316e66c Mon Sep 17 00:00:00 2001 From: Nicolas Bayet Date: Wed, 11 Dec 2024 11:35:24 +0100 Subject: [PATCH] Use resources instead of registries --- .../static/src/builder/builder_actions.js | 72 -------- .../static/src/builder/builder_helpers.js | 23 +-- .../src/builder/core_builder_action_plugin.js | 81 +++++++++ .../src/builder/options/alert_option.js | 19 +- .../src/builder/options/alignment_option.js | 17 +- .../builder/options/block_alignment_option.js | 20 ++- .../src/builder/options/border_option.js | 21 ++- .../builder/options/content_width_option.js | 25 ++- .../src/builder/options/image_tool_option.js | 162 ++++++++++-------- .../src/builder/options/layout_option.js | 26 ++- .../options/section_background_option.js | 30 ++-- .../static/src/builder/options/size_option.js | 19 +- .../src/builder/options/visibility_option.js | 17 +- .../src/builder/options/width_option.js | 19 +- .../builder/plugins/builder_actions_plugin.js | 41 +++++ .../builder/plugins/builder_options_plugin.js | 8 +- .../static/src/builder/snippets_menu.js | 5 +- addons/html_builder/static/tests/helpers.js | 43 +++-- 18 files changed, 407 insertions(+), 241 deletions(-) delete mode 100644 addons/html_builder/static/src/builder/builder_actions.js create mode 100644 addons/html_builder/static/src/builder/core_builder_action_plugin.js create mode 100644 addons/html_builder/static/src/builder/plugins/builder_actions_plugin.js diff --git a/addons/html_builder/static/src/builder/builder_actions.js b/addons/html_builder/static/src/builder/builder_actions.js deleted file mode 100644 index 001220aab6d99..0000000000000 --- a/addons/html_builder/static/src/builder/builder_actions.js +++ /dev/null @@ -1,72 +0,0 @@ -import { registry } from "@web/core/registry"; - -registry.category("website-builder-actions").add("classAction", { - isActive: ({ editingElement, param: className }) => { - return editingElement.classList.contains(className); - }, - apply: ({ editingElement, param: className, value }) => { - editingElement.classList.add(className); - }, - clean: ({ editingElement, param: className, value }) => { - editingElement.classList.remove(className); - }, -}); - -const styleMap = { - borderWidth: { - getValue: (editingElement) => { - return parseInt( - getComputedStyle(editingElement).getPropertyValue("border-width") - ).toString(); - }, - apply: (editingElement, value) => { - const parsedValue = parseInt(value); - const hasBorderClass = editingElement.classList.contains("border"); - if (!parsedValue || parsedValue < 0) { - if (hasBorderClass) { - editingElement.classList.remove("border"); - } - } else { - if (!hasBorderClass) { - editingElement.classList.add("border"); - } - } - editingElement.style.setProperty("border-width", `${parsedValue}px`, "important"); - }, - }, -}; - -registry.category("website-builder-actions").add("styleAction", { - getValue: ({ editingElement, param: styleName }) => { - return styleMap[styleName]?.getValue(editingElement); - }, - apply: ({ editingElement, param: styleName, value }) => { - styleMap[styleName]?.apply(editingElement, value); - }, -}); - -registry.category("website-builder-actions").add("attributeAction", { - getValue: ({ editingElement, param: attributeName }) => { - return editingElement.getAttribute(attributeName); - }, - isActive: ({ editingElement, param: attributeName, value }) => { - if (value) { - return ( - editingElement.hasAttribute(attributeName) && - editingElement.getAttribute(attributeName) === value - ); - } else { - return !editingElement.hasAttribute(attributeName); - } - }, - apply: ({ editingElement, param: attributeName, value }) => { - if (value) { - editingElement.setAttribute(attributeName, value); - } else { - editingElement.removeAttribute(attributeName); - } - }, - clean: ({ editingElement, param: attributeName }) => { - editingElement.removeAttribute(attributeName); - }, -}); diff --git a/addons/html_builder/static/src/builder/builder_helpers.js b/addons/html_builder/static/src/builder/builder_helpers.js index 873e26aaa9417..4dd06df5b349a 100644 --- a/addons/html_builder/static/src/builder/builder_helpers.js +++ b/addons/html_builder/static/src/builder/builder_helpers.js @@ -1,6 +1,7 @@ import { isTextNode } from "@html_editor/utils/dom_info"; import { Component, + onWillDestroy, useComponent, useEffect, useEnv, @@ -8,9 +9,7 @@ import { useState, useSubEnv, xml, - onWillDestroy, } from "@odoo/owl"; -import { registry } from "@web/core/registry"; import { useBus } from "@web/core/utils/hooks"; export function useDomState(getState) { @@ -133,11 +132,10 @@ export function useDependencies(dependencies) { return isDependenciesVisible; } -const actionsRegistry = registry.category("website-builder-actions"); - export function useClickableWeWidget() { useWeComponent(); const comp = useComponent(); + const getAction = comp.env.editor.shared.builderActions.getAction; const call = comp.env.editor.shared.history.makePreviewableOperation(callActions); if ( comp.props.preview === false || @@ -154,7 +152,7 @@ export function useClickableWeWidget() { useBus(comp.env.actionBus, "BEFORE_CALL_ACTIONS", () => { for (const [actionId, actionParam, actionValue] of getActions()) { for (const editingElement of comp.env.getEditingElements()) { - actionsRegistry.get(actionId).clean?.({ + getAction(actionId).clean?.({ editingElement, param: actionParam, value: actionValue, @@ -168,13 +166,10 @@ export function useClickableWeWidget() { comp.env.actionBus?.trigger("BEFORE_CALL_ACTIONS"); for (const [actionId, actionParam, actionValue] of getActions()) { for (const editingElement of comp.env.getEditingElements()) { - actionsRegistry.get(actionId).apply({ + getAction(actionId).apply({ editingElement, param: actionParam, value: actionValue, - // todo: should not be necessary if the actions are registred - // through a plugin resource - editor: comp.env.editor, }); } } @@ -210,7 +205,7 @@ export function useClickableWeWidget() { return getActions().every(([actionId, actionParam, actionValue]) => { // TODO isActive === first editing el or all ? const editingElement = editingElements[0]; - return actionsRegistry.get(actionId).isActive?.({ + return getAction(actionId).isActive?.({ editingElement, param: actionParam, value: actionValue, @@ -226,17 +221,15 @@ export function useClickableWeWidget() { } export function useInputWeWidget() { const comp = useComponent(); + const getAction = comp.env.editor.shared.builderActions.getAction; const state = useDomState(getState); const applyValue = comp.env.editor.shared.history.makePreviewableOperation((value) => { for (const [actionId, actionParam] of getActions()) { for (const editingElement of comp.env.getEditingElements()) { - actionsRegistry.get(actionId).apply({ + getAction(actionId).apply({ editingElement, param: actionParam, value, - // todo: should not be necessary if the actions are registred - // through a plugin resource - editor: comp.env.editor, }); } } @@ -248,7 +241,7 @@ export function useInputWeWidget() { } const [actionId, actionParam] = getActions()[0]; return { - value: actionsRegistry.get(actionId).getValue({ + value: getAction(actionId).getValue({ editingElement, param: actionParam, }), diff --git a/addons/html_builder/static/src/builder/core_builder_action_plugin.js b/addons/html_builder/static/src/builder/core_builder_action_plugin.js new file mode 100644 index 0000000000000..dc4e92ce4700e --- /dev/null +++ b/addons/html_builder/static/src/builder/core_builder_action_plugin.js @@ -0,0 +1,81 @@ +import { Plugin } from "@html_editor/plugin"; +import { registry } from "@web/core/registry"; + +class CoreBuilderActionPlugin extends Plugin { + static id = "CoreBuilderAction"; + resources = { + builder_actions: actions, + }; +} +registry.category("website-plugins").add(CoreBuilderActionPlugin.id, CoreBuilderActionPlugin); + +const styleMap = { + borderWidth: { + getValue: (editingElement) => { + return parseInt( + getComputedStyle(editingElement).getPropertyValue("border-width") + ).toString(); + }, + apply: (editingElement, value) => { + const parsedValue = parseInt(value); + const hasBorderClass = editingElement.classList.contains("border"); + if (!parsedValue || parsedValue < 0) { + if (hasBorderClass) { + editingElement.classList.remove("border"); + } + } else { + if (!hasBorderClass) { + editingElement.classList.add("border"); + } + } + editingElement.style.setProperty("border-width", `${parsedValue}px`, "important"); + }, + }, +}; + +const actions = { + classAction: { + isActive: ({ editingElement, param: className }) => { + return editingElement.classList.contains(className); + }, + apply: ({ editingElement, param: className, value }) => { + editingElement.classList.add(className); + }, + clean: ({ editingElement, param: className, value }) => { + editingElement.classList.remove(className); + }, + }, + styleAction: { + getValue: ({ editingElement, param: styleName }) => { + return styleMap[styleName]?.getValue(editingElement); + }, + apply: ({ editingElement, param: styleName, value }) => { + styleMap[styleName]?.apply(editingElement, value); + }, + }, + attributeAction: { + getValue: ({ editingElement, param: attributeName }) => { + return editingElement.getAttribute(attributeName); + }, + isActive: ({ editingElement, param: attributeName, value }) => { + if (value) { + return ( + editingElement.hasAttribute(attributeName) && + editingElement.getAttribute(attributeName) === value + ); + } else { + return !editingElement.hasAttribute(attributeName); + } + }, + apply: ({ editingElement, param: attributeName, value }) => { + if (value) { + editingElement.setAttribute(attributeName, value); + } else { + editingElement.removeAttribute(attributeName); + } + }, + clean: ({ editingElement, param: attributeName }) => { + editingElement.removeAttribute(attributeName); + }, + }, +}; diff --git a/addons/html_builder/static/src/builder/options/alert_option.js b/addons/html_builder/static/src/builder/options/alert_option.js index 0646059342453..df9a1cb87a78f 100644 --- a/addons/html_builder/static/src/builder/options/alert_option.js +++ b/addons/html_builder/static/src/builder/options/alert_option.js @@ -1,7 +1,16 @@ +import { Plugin } from "@html_editor/plugin"; import { registry } from "@web/core/registry"; +import { withSequence } from "@html_editor/utils/resource"; -registry.category("sidebar-element-option").add("AlertOption", { - template: "html_builder.AlertOption", - selector: ".s_alert", - sequence: 5, -}); +class AlertOptionPlugin extends Plugin { + static id = "AlertOption"; + resources = { + builder_options: [ + withSequence(5, { + template: "html_builder.AlertOption", + selector: ".s_alert", + }), + ], + }; +} +registry.category("website-plugins").add(AlertOptionPlugin.id, AlertOptionPlugin); diff --git a/addons/html_builder/static/src/builder/options/alignment_option.js b/addons/html_builder/static/src/builder/options/alignment_option.js index d8675da98faf7..73fb26dc829b1 100644 --- a/addons/html_builder/static/src/builder/options/alignment_option.js +++ b/addons/html_builder/static/src/builder/options/alignment_option.js @@ -1,6 +1,15 @@ +import { Plugin } from "@html_editor/plugin"; import { registry } from "@web/core/registry"; -registry.category("sidebar-element-option").add("AlignmentOption", { - template: "html_builder.AlignmentOption", - selector: ".s_share, .s_text_highlight, .s_social_media, section", -}); +class AlignmentOptionPlugin extends Plugin { + static id = "AlignmentOption"; + resources = { + builder_options: [ + { + template: "html_builder.AlignmentOption", + selector: ".s_share, .s_text_highlight, .s_social_media", + }, + ], + }; +} +registry.category("website-plugins").add(AlignmentOptionPlugin.id, AlignmentOptionPlugin); diff --git a/addons/html_builder/static/src/builder/options/block_alignment_option.js b/addons/html_builder/static/src/builder/options/block_alignment_option.js index 1ab3ca273c7de..33b34cb3de66b 100644 --- a/addons/html_builder/static/src/builder/options/block_alignment_option.js +++ b/addons/html_builder/static/src/builder/options/block_alignment_option.js @@ -1,7 +1,17 @@ +import { Plugin } from "@html_editor/plugin"; import { registry } from "@web/core/registry"; +import { withSequence } from "@html_editor/utils/resource"; -registry.category("sidebar-element-option").add("BlockAlignmentOption", { - template: "html_builder.BlockAlignmentOption", - selector: ".s_alert, .s_blockquote, .s_text_highlight", - sequence: 30, -}); +class BlockAlignmentOptionPlugin extends Plugin { + static id = "BlockAlignmentOption"; + resources = { + builder_options: [ + withSequence(30, { + template: "html_builder.BlockAlignmentOption", + selector: ".s_alert, .s_blockquote, .s_text_highlight", + }), + ], + }; +} + +registry.category("website-plugins").add(BlockAlignmentOptionPlugin.id, BlockAlignmentOptionPlugin); diff --git a/addons/html_builder/static/src/builder/options/border_option.js b/addons/html_builder/static/src/builder/options/border_option.js index 0b74f307839d5..51cacb956bce2 100644 --- a/addons/html_builder/static/src/builder/options/border_option.js +++ b/addons/html_builder/static/src/builder/options/border_option.js @@ -1,11 +1,20 @@ +import { Plugin } from "@html_editor/plugin"; import { registry } from "@web/core/registry"; export const card_parent_handlers = ".s_three_columns .row > div, .s_comparisons .row > div, .s_cards_grid .row > div, .s_cards_soft .row > div, .s_product_list .row > div"; -registry.category("sidebar-element-option").add("BorderOption", { - template: "html_builder.BorderOption", - selector: "section .row > div", - exclude: `.s_col_no_bgcolor, .s_col_no_bgcolor.row > div, .s_image_gallery .row > div, .s_masonry_block .s_col_no_resize, .s_text_cover .row > .o_not_editable, ${card_parent_handlers}`, - // TODO add border shadow. -}); +class BorderOptionPlugin extends Plugin { + static id = "BorderOption"; + resources = { + builder_options: [ + { + template: "html_builder.BorderOption", + selector: "section .row > div", + exclude: `.s_col_no_bgcolor, .s_col_no_bgcolor.row > div, .s_image_gallery .row > div, .s_masonry_block .s_col_no_resize, .s_text_cover .row > .o_not_editable, ${card_parent_handlers}`, + // TODO add border shadow. + }, + ], + }; +} +registry.category("website-plugins").add(BorderOptionPlugin.id, BorderOptionPlugin); diff --git a/addons/html_builder/static/src/builder/options/content_width_option.js b/addons/html_builder/static/src/builder/options/content_width_option.js index 3f3304ca0b2a7..e0a1253e0633d 100644 --- a/addons/html_builder/static/src/builder/options/content_width_option.js +++ b/addons/html_builder/static/src/builder/options/content_width_option.js @@ -1,11 +1,18 @@ +import { Plugin } from "@html_editor/plugin"; import { registry } from "@web/core/registry"; -// TODO to import in html_builder - -registry.category("sidebar-element-option").add("ContentWidthOption", { - template: "html_builder.ContentWidthOption", - selector: "section, .s_carousel .carousel-item, .s_carousel_intro_item", - exclude: "[data-snippet] :not(.oe_structure) > [data-snippet]", - // TODO add target and remove applyTo in the template of ContentWidthOption ? - // target: "> .container, > .container-fluid, > .o_container_small", -}); +class ContentWidthOptionPlugin extends Plugin { + static id = "ContentWidthOption"; + resources = { + builder_options: [ + { + template: "html_builder.ContentWidthOption", + selector: "section, .s_carousel .carousel-item, .s_carousel_intro_item", + exclude: "[data-snippet] :not(.oe_structure) > [data-snippet]", + // TODO add target and remove applyTo in the template of ContentWidthOption ? + // target: "> .container, > .container-fluid, > .o_container_small", + }, + ], + }; +} +registry.category("website-plugins").add(ContentWidthOptionPlugin.id, ContentWidthOptionPlugin); diff --git a/addons/html_builder/static/src/builder/options/image_tool_option.js b/addons/html_builder/static/src/builder/options/image_tool_option.js index 1f1e8cc07e97e..0d7226d3733a8 100644 --- a/addons/html_builder/static/src/builder/options/image_tool_option.js +++ b/addons/html_builder/static/src/builder/options/image_tool_option.js @@ -6,82 +6,104 @@ import { registry } from "@web/core/registry"; import { defaultOptionComponents } from "../components/defaultComponents"; import { AddElementOption } from "./add_element_option"; import { SpacingOption } from "./spacing_option"; +import { Plugin } from "@html_editor/plugin"; -export class ImageToolOption extends Component { +class ImageToolOptionPlugin extends Plugin { + static id = "ImageToolOption"; + static dependencies = ["history", "userCommand"]; + resources = { + builder_options: { + OptionComponent: ImageToolOption, + selector: "img", + }, + builder_actions: this.getActions(), + }; + getActions() { + return { + cropImage: { + isActive: ({ editingElement }) => { + return editingElement.classList.contains("o_we_image_cropped"); + }, + apply: () => { + this.dependencies.userCommand.getCommand("cropImage").run(); + }, + }, + resetCrop: { + apply: async ({ editingElement }) => { + // todo: This seems quite heavy for a simple reset. Retrieve some + // metadata, to load the image crop, to call processImageCrop, just to + // reset the crop. We might want to simplify this. + await loadBundle("html_editor.assets_image_cropper"); + const croppedImage = editingElement; + + const container = document.createElement("div"); + container.style.display = "none"; + const originalImage = document.createElement("img"); + container.append(originalImage); + document.body.append(container); + + const mimetime = getImageMimetype(croppedImage); + await loadImage(croppedImage.dataset.originalSrc, originalImage); + let aspectRatio = croppedImage.dataset.aspectRatio || "0/0"; + let readyResolve; + const readyPromise = new Promise((resolve) => (readyResolve = resolve)); + const cropper = await activateCropper( + originalImage, + cropperAspectRatios[aspectRatio].value, + croppedImage.dataset, + { ready: readyResolve } + ); + await readyPromise; + cropper.reset(); + if (aspectRatio !== "0/0") { + aspectRatio = "0/0"; + cropper.setAspectRatio(0); + } + const newSrc = await processImageCrop( + croppedImage, + cropper, + mimetime, + aspectRatio + ); + container.remove(); + cropper.destroy(); + croppedImage.setAttribute("src", newSrc); + // todo: Should re-apply a shape if it was applied before. + this.dependencies.history.addStep(); + }, + }, + transformImage: { + isActive: ({ editingElement }) => { + return editingElement.matches(`[style*="transform"]`); + }, + apply: () => { + this.dependencies.userCommand.getCommand("transformImage").run(); + }, + }, + resetTransformImage: { + apply: ({ editingElement }) => { + editingElement.setAttribute( + "style", + (editingElement.getAttribute("style") || "").replace( + /[^;]*transform[\w:]*;?/g, + "" + ) + ); + this.dependencies.history.addStep(); + }, + }, + }; + } +} +registry.category("website-plugins").add(ImageToolOptionPlugin.id, ImageToolOptionPlugin); + +class ImageToolOption extends Component { static template = "html_builder.ImageToolOption"; static components = { ...defaultOptionComponents, SpacingOption, AddElementOption }; static props = {}; } -registry.category("sidebar-element-option").add("ImageToolOption", { - OptionComponent: ImageToolOption, - selector: "img", -}); - -registry.category("website-builder-actions").add("cropImage", { - isActive: ({ editingElement }) => { - return editingElement.classList.contains("o_we_image_cropped"); - }, - apply: ({ editor }) => { - editor.shared.userCommand.getCommand("cropImage").run(); - }, -}); -registry.category("website-builder-actions").add("resetCrop", { - apply: async ({ editingElement, editor }) => { - // todo: This seems quite heavy for a simple reset. Retrieve some - // metadata, to load the image crop, to call processImageCrop, just to - // reset the crop. We might want to simplify this. - await loadBundle("html_editor.assets_image_cropper"); - const croppedImage = editingElement; - - const container = document.createElement("div"); - container.style.display = "none"; - const originalImage = document.createElement("img"); - container.append(originalImage); - document.body.append(container); - - const mimetime = getImageMimetype(croppedImage); - await loadImage(croppedImage.dataset.originalSrc, originalImage); - let aspectRatio = croppedImage.dataset.aspectRatio || "0/0"; - let readyResolve; - const readyPromise = new Promise((resolve) => (readyResolve = resolve)); - const cropper = await activateCropper( - originalImage, - cropperAspectRatios[aspectRatio].value, - croppedImage.dataset, - { ready: readyResolve } - ); - await readyPromise; - cropper.reset(); - if (aspectRatio !== "0/0") { - aspectRatio = "0/0"; - cropper.setAspectRatio(0); - } - const newSrc = await processImageCrop(croppedImage, cropper, mimetime, aspectRatio); - container.remove(); - cropper.destroy(); - croppedImage.setAttribute("src", newSrc); - // todo: Should re-apply a shape if it was applied before. - editor.shared.history.addStep(); - }, -}); -registry.category("website-builder-actions").add("transformImage", { - isActive: ({ editingElement }) => { - return editingElement.matches(`[style*="transform"]`); - }, - apply: ({ editor }) => { - editor.shared.userCommand.getCommand("transformImage").run(); - }, -}); -registry.category("website-builder-actions").add("resetTransformImage", { - apply: ({ editingElement, editor }) => { - editingElement.setAttribute( - "style", - (editingElement.getAttribute("style") || "").replace(/[^;]*transform[\w:]*;?/g, "") - ); - editor.shared.history.addStep(); - }, -}); +// const getActions = (plugin) => (); /** * @private diff --git a/addons/html_builder/static/src/builder/options/layout_option.js b/addons/html_builder/static/src/builder/options/layout_option.js index 8a56fd7e816ce..b73cc963e866e 100644 --- a/addons/html_builder/static/src/builder/options/layout_option.js +++ b/addons/html_builder/static/src/builder/options/layout_option.js @@ -1,15 +1,31 @@ import { Component } from "@odoo/owl"; -import { registry } from "@web/core/registry"; import { defaultOptionComponents } from "../components/defaultComponents"; import { useDomState } from "../builder_helpers"; import { SpacingOption } from "./spacing_option"; import { AddElementOption } from "./add_element_option"; +import { Plugin } from "@html_editor/plugin"; +import { registry } from "@web/core/registry"; + +// TODO to import in html_builder import { convertToNormalColumn, reloadLazyImages, toggleGridMode, } from "@html_builder/builder/utils/grid_layout_utils"; +class LayoutOptionPlugin extends Plugin { + static id = "LayoutOption"; + resources = { + builder_options: { + OptionComponent: LayoutOption, + selector: "section, section.s_carousel_wrapper .carousel-item, .s_carousel_intro_item", + exclude: + ".s_dynamic, .s_dynamic_snippet_content, .s_dynamic_snippet_title, .s_masonry_block, .s_framed_intro, .s_features_grid, .s_media_list, .s_table_of_content, .s_process_steps, .s_image_gallery, .s_timeline, .s_pricelist_boxed, .s_quadrant, .s_pricelist_cafe, .s_faq_horizontal, .s_image_frame, .s_card_offset, .s_contact_info", + }, + }; +} +registry.category("website-plugins").add(LayoutOptionPlugin.id, LayoutOptionPlugin); + export class LayoutOption extends Component { static template = "html_builder.LayoutOption"; static components = { ...defaultOptionComponents, SpacingOption, AddElementOption }; @@ -67,11 +83,3 @@ export class LayoutOption extends Component { console.warn(`changeColumnCount:`, nbColumns); } } - -registry.category("sidebar-element-option").add("LayoutOption", { - OptionComponent: LayoutOption, - selector: "section, section.s_carousel_wrapper .carousel-item, .s_carousel_intro_item", - exclude: - ".s_dynamic, .s_dynamic_snippet_content, .s_dynamic_snippet_title, .s_masonry_block, .s_framed_intro, .s_features_grid, .s_media_list, .s_table_of_content, .s_process_steps, .s_image_gallery, .s_timeline, .s_pricelist_boxed, .s_quadrant, .s_pricelist_cafe, .s_faq_horizontal, .s_image_frame, .s_card_offset, .s_contact_info", - // TODO add target (applyTo) data-target="> *:has(> .row), > .s_allow_columns" -}); diff --git a/addons/html_builder/static/src/builder/options/section_background_option.js b/addons/html_builder/static/src/builder/options/section_background_option.js index 3930021eac290..602b2350933c5 100644 --- a/addons/html_builder/static/src/builder/options/section_background_option.js +++ b/addons/html_builder/static/src/builder/options/section_background_option.js @@ -1,19 +1,21 @@ -import { Component } from "@odoo/owl"; import { registry } from "@web/core/registry"; -import { defaultOptionComponents } from "../components/defaultComponents"; - -// TODO to import in html_builder - -export class SectionBackgroundOption extends Component { - static template = "html_builder.SectionBackgroundOption"; - static components = { ...defaultOptionComponents }; - static props = {}; -} +import { Plugin } from "@html_editor/plugin"; // todo: this is a naive implemenation. We should look at the current // implementations of backgrounds options for all targets instead of just // focusing on sections. -registry.category("sidebar-element-option").add("SectionBackgroundOption", { - OptionComponent: SectionBackgroundOption, - selector: "section", -}); +class SectionBackgroundOptionPlugin extends Plugin { + static id = "SectionBackgroundOption"; + resources = { + builder_options: [ + { + template: "html_builder.SectionBackgroundOption", + selector: "section", + }, + ], + }; +} + +registry + .category("website-plugins") + .add(SectionBackgroundOptionPlugin.id, SectionBackgroundOptionPlugin); diff --git a/addons/html_builder/static/src/builder/options/size_option.js b/addons/html_builder/static/src/builder/options/size_option.js index e9f6f4b74931a..f5c7acdd1bbfa 100644 --- a/addons/html_builder/static/src/builder/options/size_option.js +++ b/addons/html_builder/static/src/builder/options/size_option.js @@ -1,7 +1,16 @@ +import { withSequence } from "@html_editor/utils/resource"; +import { Plugin } from "@html_editor/plugin"; import { registry } from "@web/core/registry"; -registry.category("sidebar-element-option").add("SizeOption", { - template: "html_builder.SizeOption", - selector: ".s_alert", - sequence: 20, -}); +class SizeOptionPlugin extends Plugin { + static id = "SizeOption"; + resources = { + builder_options: [ + withSequence(20, { + template: "html_builder.SizeOption", + selector: ".s_alert", + }), + ], + }; +} +registry.category("website-plugins").add(SizeOptionPlugin.id, SizeOptionPlugin); diff --git a/addons/html_builder/static/src/builder/options/visibility_option.js b/addons/html_builder/static/src/builder/options/visibility_option.js index 28d36ddccb2f4..f5560ae4948d9 100644 --- a/addons/html_builder/static/src/builder/options/visibility_option.js +++ b/addons/html_builder/static/src/builder/options/visibility_option.js @@ -1,6 +1,15 @@ +import { Plugin } from "@html_editor/plugin"; import { registry } from "@web/core/registry"; -registry.category("sidebar-element-option").add("VisibilityOption", { - template: "html_builder.VisibilityOption", - selector: "section, .s_hr", -}); +class VisibilityOptionPlugin extends Plugin { + static id = "VisibilityOption"; + resources = { + builder_options: [ + { + template: "html_builder.VisibilityOption", + selector: "section, .s_hr", + }, + ], + }; +} +registry.category("website-plugins").add(VisibilityOptionPlugin.id, VisibilityOptionPlugin); diff --git a/addons/html_builder/static/src/builder/options/width_option.js b/addons/html_builder/static/src/builder/options/width_option.js index dddc9dd3c15a0..f6dc24a11c628 100644 --- a/addons/html_builder/static/src/builder/options/width_option.js +++ b/addons/html_builder/static/src/builder/options/width_option.js @@ -1,7 +1,16 @@ +import { Plugin } from "@html_editor/plugin"; +import { withSequence } from "@html_editor/utils/resource"; import { registry } from "@web/core/registry"; -registry.category("sidebar-element-option").add("WidthOption", { - template: "html_builder.WidthOption", - selector: ".s_alert, .s_blockquote, .s_text_highlight", - sequence: 10, -}); +class WidthOptionPlugin extends Plugin { + static id = "WidthOption"; + resources = { + builder_options: [ + withSequence(10, { + template: "html_builder.WidthOption", + selector: ".s_alert, .s_blockquote, .s_text_highlight", + }), + ], + }; +} +registry.category("website-plugins").add(WidthOptionPlugin.id, WidthOptionPlugin); diff --git a/addons/html_builder/static/src/builder/plugins/builder_actions_plugin.js b/addons/html_builder/static/src/builder/plugins/builder_actions_plugin.js new file mode 100644 index 0000000000000..fb1e8e186ecca --- /dev/null +++ b/addons/html_builder/static/src/builder/plugins/builder_actions_plugin.js @@ -0,0 +1,41 @@ +import { Plugin } from "@html_editor/plugin"; + +/** + * @typedef {Object} BuilderAction + * @property {string} id + * @property {Function} apply + * @property {Function} [isActive] + * @property {Function} [clean] + */ + +export class BuilderActionsPlugin extends Plugin { + static id = "builderActions"; + static shared = ["getAction"]; + + setup() { + this.actions = {}; + for (const actions of this.getResource("builder_actions")) { + for (const [actionId, action] of Object.entries(actions)) { + if (actionId in this.actions) { + throw new Error(`Duplicate builder action id: ${action.id}`); + } + this.actions[actionId] = { id: actionId, ...action }; + } + } + Object.freeze(this.actions); + } + + /** + * Get the action object for the given action ID. + * + * @param {string} actionId + * @returns {Object} + */ + getAction(actionId) { + const action = this.actions[actionId]; + if (!action) { + throw new Error(`Unknown builder action id: ${actionId}`); + } + return action; + } +} diff --git a/addons/html_builder/static/src/builder/plugins/builder_options_plugin.js b/addons/html_builder/static/src/builder/plugins/builder_options_plugin.js index ea1424605ab20..b26f9b564b2c2 100644 --- a/addons/html_builder/static/src/builder/plugins/builder_options_plugin.js +++ b/addons/html_builder/static/src/builder/plugins/builder_options_plugin.js @@ -1,5 +1,4 @@ import { Plugin } from "@html_editor/plugin"; -import { registry } from "@web/core/registry"; import { uniqueId } from "@web/core/utils/functions"; export class BuilderOptionsPlugin extends Plugin { @@ -11,12 +10,7 @@ export class BuilderOptionsPlugin extends Plugin { }; setup() { - // todo: use resources instead of registry - this.builderOptions = registry - .category("sidebar-element-option") - .getEntries() - .map(([id, option]) => ({ id, ...option })); - this.builderOptions.sort((a, b) => (a.sequence ?? 0) - (b.sequence ?? 0)); + this.builderOptions = this.getResource("builder_options"); this.addDomListener(this.editable, "pointerup", (e) => { this.updateContainers(e.target); }); diff --git a/addons/html_builder/static/src/builder/snippets_menu.js b/addons/html_builder/static/src/builder/snippets_menu.js index cfef6cd05dd4f..8b5f19bc21f73 100644 --- a/addons/html_builder/static/src/builder/snippets_menu.js +++ b/addons/html_builder/static/src/builder/snippets_menu.js @@ -28,9 +28,11 @@ import { addLoadingEffect as addButtonLoadingEffect } from "@web/core/utils/ui"; import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog"; import { useSetupAction } from "@web/search/action_hook"; import { closestElement } from "@html_editor/utils/dom_traversal"; +import { BuilderActionsPlugin } from "./plugins/builder_actions_plugin"; const BUILDER_PLUGIN = [ BuilderOptionsPlugin, + BuilderActionsPlugin, BuilderOverlayPlugin, DropZonePlugin, MediaWebsitePlugin, @@ -76,9 +78,10 @@ export class SnippetsMenu extends Component { const editorBus = new EventBus(); // TODO: maybe do a different config for the translate mode and the // "regular" mode. + const websitePlugins = registry.category("website-plugins").getAll(); this.editor = new Editor( { - Plugins: [...MAIN_PLUGINS, ...BUILDER_PLUGIN], + Plugins: [...MAIN_PLUGINS, ...BUILDER_PLUGIN, ...websitePlugins], onChange: () => { this.state.canUndo = this.editor.shared.history.canUndo(); this.state.canRedo = this.editor.shared.history.canRedo(); diff --git a/addons/html_builder/static/tests/helpers.js b/addons/html_builder/static/tests/helpers.js index fb23710ee90d5..0e910fb70fc80 100644 --- a/addons/html_builder/static/tests/helpers.js +++ b/addons/html_builder/static/tests/helpers.js @@ -17,6 +17,8 @@ import { registry } from "@web/core/registry"; import { uniqueId } from "@web/core/utils/functions"; import { WebClient } from "@web/webclient/webclient"; import { getWebsiteSnippets } from "./snippets_getter.hoot"; +import { Plugin } from "@html_editor/plugin"; +import { withSequence } from "@html_editor/utils/resource"; class Website extends models.Model { _name = "website"; @@ -131,30 +133,51 @@ export function getEditable(inWrap) { return `
${inWrap}
`; } -const actionsRegistry = registry.category("website-builder-actions"); - export function addOption({ selector, exclude, template, Component, sequence }) { - const optionId = uniqueId("test-option"); - registry.category("sidebar-element-option").add(optionId, { + const pluginId = uniqueId("test-option"); + const Class = makeOptionPlugin({ + pluginId, OptionComponent: Component, template, selector, exclude, sequence, }); + registry.category("website-plugins").add(pluginId, Class); after(() => { - registry.category("sidebar-element-option").remove(optionId); + registry.category("website-plugins").remove(pluginId); }); } +function makeOptionPlugin({ id, template, selector, sequence, OptionComponent }) { + const option = { + OptionComponent, + template, + selector, + }; + + const Class = { + [id]: class extends Plugin { + static id = id; + resources = { + builder_options: sequence ? withSequence(sequence, option) : option, + }; + }, + }[id]; + + return Class; +} export function addActionOption(actions = {}) { - for (const [name, action] of Object.entries(actions)) { - actionsRegistry.add(name, action); + const pluginId = uniqueId("test-action-plugin"); + class P extends Plugin { + static id = pluginId; + resources = { + builder_actions: actions, + }; } + registry.category("website-plugins").add(pluginId, P); after(() => { - for (const [name] of Object.entries(actions)) { - actionsRegistry.remove(name); - } + registry.category("website-plugins").remove(P); }); }