Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mailing List Subscribe Option #4191

Merged
merged 1 commit into from
Mar 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion addons/html_builder/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@

'html_builder/static/src/**/*',
('remove', 'html_builder/static/src/website_preview/**/*'),
('remove', 'html_builder/static/src/website_mass_mailing/**/*'),
('remove', 'html_builder/static/src/website_builder/plugins/website_edit_service.js'),
('remove', 'html_builder/static/src/interactions/**/*'),
],
Expand Down
11 changes: 9 additions & 2 deletions addons/html_builder/static/src/core/drop_zone_plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -297,9 +297,16 @@ export class DropZonePlugin extends Plugin {
insertMethod = "appendChild";
}
this.clearDropZone();
return (elementToAdd) => {
return async (elementToAdd) => {
// TODO: refactor if a new mutex system is implemented
target[insertMethod](elementToAdd);
this.dispatchTo("on_add_element_handlers", { elementToAdd: elementToAdd });
const proms = [];
for (const handler of this.getResource("on_add_element_handlers")) {
proms.push(handler({ elementToAdd: elementToAdd }));
}
this.services.ui.block();
await Promise.all(proms);
this.services.ui.unblock();
scrollToWindow(elementToAdd, { behavior: "smooth", offset: 50 });
this.dependencies.history.addStep();
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,7 @@ class BackgroundOptionPlugin extends Plugin {
applyFunDependOnSelectorAndExclude(
this.markColorLevel,
root,
coloredLevelBackgroundParam.selector,
coloredLevelBackgroundParam.exclude
coloredLevelBackgroundParam
);
}
}
Expand Down
27 changes: 25 additions & 2 deletions addons/html_builder/static/src/plugins/utils.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,33 @@
export function applyFunDependOnSelectorAndExclude(fn, rootEl, selector, exclude) {
export function applyFunDependOnSelectorAndExclude(fn, rootEl, { selector, exclude, applyTo }) {
const closestSelector = rootEl.closest(selector);
let editingEls = closestSelector ? [closestSelector] : [...rootEl.querySelectorAll(selector)];
if (exclude) {
editingEls = editingEls.filter((selectorEl) => !selectorEl.matches(exclude));
}
for (const editingEl of editingEls) {
fn(editingEl);
const targetEls = applyTo ? editingEl.querySelectorAll(applyTo) : [editingEl];
for (const targetEl of targetEls) {
fn(targetEl);
}
}
}

export async function applyAsyncFunDependOnSelectorAndExclude(
fn,
rootEl,
{ selector, exclude, applyTo }
) {
const closestSelector = rootEl.closest(selector);
let editingEls = closestSelector ? [closestSelector] : [...rootEl.querySelectorAll(selector)];
if (exclude) {
editingEls = editingEls.filter((selectorEl) => !selectorEl.matches(exclude));
}
const proms = [];
for (const editingEl of editingEls) {
const targetEls = applyTo ? editingEl.querySelectorAll(applyTo) : [editingEl];
for (const targetEl of targetEls) {
proms.push(fn(targetEl));
}
}
await Promise.all(proms);
}
10 changes: 5 additions & 5 deletions addons/html_builder/static/src/sidebar/block_tab.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,16 +68,16 @@ export class BlockTab extends Component {
onDrag: ({ element, x, y }) => {
this.dropzonePlugin.dragElement(element, x, y);
},
onDrop: ({ element, x, y }) => {
onDrop: async ({ element, x, y }) => {
const { height, width } = element.getClientRects()[0];

const position = { x, y, height, width };
const { category, snippet } = this.dragState;
if (category === "snippet_groups") {
this.openSnippetDialog(snippet, position);
await this.openSnippetDialog(snippet, position);
return;
}
const addElement = this.dropzonePlugin.getAddElement(position);
const addElement = await this.dropzonePlugin.getAddElement(position);
if (!addElement) {
return;
}
Expand All @@ -98,7 +98,7 @@ export class BlockTab extends Component {
return this.env.editor.shared.disableSnippets;
}

openSnippetDialog(snippet, position) {
async openSnippetDialog(snippet, position) {
if (snippet.moduleId) {
return;
}
Expand All @@ -107,7 +107,7 @@ export class BlockTab extends Component {
this.dropzonePlugin.getSelectors(snippet);
this.dropzonePlugin.displayDropZone(selectorSiblings, selectorChildren);
}
const addElement = this.dropzonePlugin.getAddElement(position);
const addElement = await this.dropzonePlugin.getAddElement(position);
if (!addElement) {
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ class ProcessStepsOptionPlugin extends Plugin {
// connectors even if there were no step added (e.g: a column of the
// snippet is being resized).
content_updated_handlers: (rootEl) =>
applyFunDependOnSelectorAndExclude(reloadConnectors, rootEl, this.selector),
applyFunDependOnSelectorAndExclude(reloadConnectors, rootEl, {
selector: this.selector,
}),
};
getActions() {
return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,11 +121,9 @@ class TableOfContentOptionPlugin extends Plugin {
for (const navbar of root.querySelectorAll(".s_table_of_content_navbar")) {
navbar.setAttribute("contenteditable", "false");
}
applyFunDependOnSelectorAndExclude(
this.updateTableOfContentNavbar.bind(this),
root,
".s_table_of_content_main"
);
applyFunDependOnSelectorAndExclude(this.updateTableOfContentNavbar.bind(this), root, {
selector: ".s_table_of_content_main",
});
}

updateTableOfContentNavbar(tableOfContentMain) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,7 @@ class WebsiteParallaxPlugin extends Plugin {
applyFunDependOnSelectorAndExclude(
this.removeParallax.bind(this),
rootEl,
backgroundOptionSelector.selector,
backgroundOptionSelector.exclude
backgroundOptionSelector
);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.o_enable_preview {
display: block !important;
}
.o_disable_preview {
display: none !important;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { onWillStart } from "@odoo/owl";
import { BaseOptionComponent } from "@html_builder/core/utils";

export class MailingListSubscribeOption extends BaseOptionComponent {
static template = "html_builder.MailingListSubscribeOption";
static props = {
fetchMailingLists: Function,
};

setup() {
this.mailingLists = [];
onWillStart(async () => {
this.mailingLists = await this.props.fetchMailingLists();
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">

<!-- TODO: form_opt dependency -->

<t t-name="html_builder.MailingListSubscribeOption">

<BuilderRow label.translate="Newsletter">
<BuilderSelect dataAttributeAction="'listId'">
<t t-foreach="mailingLists" t-as="item" t-key="item.id">
<BuilderSelectItem dataAttributeActionValue="item.id.toString()">
<t t-out = "item.name"/>
</BuilderSelectItem>
</t>
<t t-if="!mailingLists.length">
<BuilderSelectItem>None</BuilderSelectItem>
</t>
</BuilderSelect>
</BuilderRow>

<BuilderRow label.translate="Display Thanks Message">
<BuilderCheckbox action="'toggleThanksMessage'"/>
</BuilderRow>

</t>

<t t-name="html_builder.MailingListSubscribeFormOption">

<BuilderRow label.translate="Placeholder">
<BuilderTextInput attributeAction="'placeholder'" applyTo="'.s_newsletter_subscribe_form_input'"/>
</BuilderRow>

</t>

</templates>
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog";
import { Plugin } from "@html_editor/plugin";
import { registry } from "@web/core/registry";
import { user } from "@web/core/user";
import { _t } from "@web/core/l10n/translation";
import { NewsletterSubscribeCommonOption } from "./newsletter_subscribe_common_option";
import { getSelectorParams } from "@html_builder/utils/utils";
import {
applyFunDependOnSelectorAndExclude,
applyAsyncFunDependOnSelectorAndExclude,
} from "@html_builder/plugins/utils";

class MailingListSubscribeOptionPlugin extends Plugin {
static id = "mailingListSubscribeOption";
static dependencies = ["remove", "savePlugin"];
static shared = ["fetchMailingLists"];
resources = {
builder_actions: [
{
toggleThanksMessage: {
apply: ({ editingElement }) => {
this.setThanksMessageVisibility(editingElement, true);
},
clean: ({ editingElement }) => {
this.setThanksMessageVisibility(editingElement, false);
},
isApplied: ({ editingElement }) =>
editingElement
.querySelector(".js_subscribed_wrap")
.classList.contains("o_enable_preview"),
},
},
],
on_add_element_handlers: this.onAddElement.bind(this),
clean_for_save_handlers: this.cleanForSave.bind(this),
};

setup() {
this.mailingListSubscribeOptionSelectorParams = getSelectorParams(
this.getResource("builder_options"),
NewsletterSubscribeCommonOption
);
}

setThanksMessageVisibility(editingElement, isVisible) {
const toSubscribeEl = editingElement.querySelector(".js_subscribe_wrap");
const thanksMessageEl = editingElement.querySelector(".js_subscribed_wrap");
thanksMessageEl.classList.toggle("o_enable_preview", isVisible);
thanksMessageEl.classList.toggle("o_disable_preview", !isVisible);
toSubscribeEl.classList.toggle("o_enable_preview", !isVisible);
toSubscribeEl.classList.toggle("o_disable_preview", isVisible);
}

async onAddElement({ elementToAdd }) {
const proms = [];
for (const mailingListSubscribeOptionSelector of this
.mailingListSubscribeOptionSelectorParams) {
proms.push(
applyAsyncFunDependOnSelectorAndExclude(
this.addNewsletterListElement.bind(this),
elementToAdd,
mailingListSubscribeOptionSelector
)
);
}
await Promise.all(proms);
}

async addNewsletterListElement(elementToAdd) {
await this.fetchMailingLists();
if (this.mailingLists.length) {
elementToAdd.dataset.listId = this.mailingLists[0].id;
} else {
this.services.dialog.add(ConfirmationDialog, {
body: _t(
"No mailing list found, do you want to create a new one? This will save all your changes, are you sure you want to proceed?"
),
confirm: async () => {
await this.dependencies.savePlugin.save();
window.location.href =
"/odoo/action-mass_mailing.action_view_mass_mailing_lists";
},
cancel: () => {
this.dependencies.remove.removeElement(elementToAdd);
},
});
}
}

async fetchMailingLists() {
if (!this.mailingLists) {
const context = Object.assign({}, user.context, {
website_id: this.services.website.currentWebsite.id,
lang: this.services.website.currentWebsite.metadata.lang,
user_lang: user.context.lang,
});
const response = await this.services.orm.call(
"mailing.list",
"name_search",
["", [["is_public", "=", true]]],
{ context }
);
this.mailingLists = [];
for (const entry of response) {
this.mailingLists.push({ id: entry[0], name: entry[1] });
}
}
return this.mailingLists;
}

cleanForSave({ root }) {
for (const mailingListSubscribeOptionSelector of this
.mailingListSubscribeOptionSelectorParams) {
applyFunDependOnSelectorAndExclude(
this.removePreview.bind(this),
root,
mailingListSubscribeOptionSelector
);
}
}

removePreview(editingElement) {
const previewClasses = ["o_disable_preview", "o_enable_preview"];
const toCleanElsSelector = ".js_subscribe_wrap, .js_subscribed_wrap";
const toCleanEls = editingElement.querySelectorAll(toCleanElsSelector);
for (const toCleanEl of toCleanEls) {
toCleanEl.classList.remove(...previewClasses);
}
}
}

registry
.category("website-plugins")
.add(MailingListSubscribeOptionPlugin.id, MailingListSubscribeOptionPlugin);
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { BaseOptionComponent } from "@html_builder/core/utils";
import { MailingListSubscribeOption } from "./mailing_list_subscribe_option";
import { RecaptchaSubscribeOption } from "./recaptcha_subscribe_option";

export class NewsletterSubscribeCommonOption extends BaseOptionComponent {
static template = "html_builder.NewsletterSubscribeCommonOption";
static components = {
MailingListSubscribeOption,
RecaptchaSubscribeOption,
};
static props = {
fetchMailingLists: Function,
hasRecaptcha: Function,
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">

<t t-name="html_builder.NewsletterSubscribeCommonOption">

<MailingListSubscribeOption fetchMailingLists="props.fetchMailingLists"/>
<RecaptchaSubscribeOption hasRecaptcha="props.hasRecaptcha"/>

</t>

</templates>
Loading