Skip to content
Open
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
114 changes: 114 additions & 0 deletions mail_composer_template_search/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
=============================
Mail Composer Template Search
=============================

..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:3ecf8791c31badd700b3c89946e2cd7786cd3dfdcd8bd2ae5c4cbde254478f6a
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png
:target: https://odoo-community.org/page/development-status
:alt: Alpha
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fsocial-lightgray.png?logo=github
:target: https://github.com/OCA/social/tree/18.0/mail_composer_template_search
:alt: OCA/social
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/social-18-0/social-18-0-mail_composer_template_search
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/social&target_branch=18.0
:alt: Try me on Runboat

|badge1| |badge2| |badge3| |badge4| |badge5|

This module enhances the Odoo mail composer's template selector by
adding an inline search bar. This allows users to quickly filter and
select templates without having to click "Search More..." or scroll
through a long list.

Key Features
~~~~~~~~~~~~

- **Inline Search**: Type directly in the template dropdown to filter.
- **Smart Filtering**: Case-insensitive search on template names.
- **Keyboard Navigation**: Use Arrow Up/Down and Enter to select
templates.
- **Debounced Search**: Optimized performance with a 300ms debounce.

.. IMPORTANT::
This is an alpha version, the data model and design can change at any time without warning.
Only for development or testing purpose, do not use in production.
`More details on development status <https://odoo-community.org/page/development-status>`_

**Table of contents**

.. contents::
:local:

Usage
=====

1. Open the mail composer (e.g., from an Opportunity, Partner, or
Purchase Order).
2. Click on the template selector dropdown.
3. Start typing in the new search bar at the top of the dropdown.
4. The list of templates will filter automatically as you type.
5. Use **Arrow Keys** to navigate and **Enter** to select, or simply
click the desired template.

Bug Tracker
===========

Bugs are tracked on `GitHub Issues <https://github.com/OCA/social/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback <https://github.com/OCA/social/issues/new?body=module:%20mail_composer_template_search%0Aversion:%2018.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.

Do not contact contributors directly about support or help with technical issues.

Credits
=======

Authors
-------

* Heliconia Solutions Pvt. Ltd.

Contributors
------------

- `Heliconia Solutions Pvt. Ltd. <https://www.heliconia.io>`__

- Bhavesh Heliconia

Maintainers
-----------

This module is maintained by the OCA.

.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org

OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.

.. |maintainer-Bhavesh Heliconia| image:: https://github.com/Bhavesh Heliconia.png?size=40px
:target: https://github.com/Bhavesh Heliconia
:alt: Bhavesh Heliconia

Current `maintainer <https://odoo-community.org/page/maintainer-role>`__:

|maintainer-Bhavesh Heliconia|

This module is part of the `OCA/social <https://github.com/OCA/social/tree/18.0/mail_composer_template_search>`_ project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
2 changes: 2 additions & 0 deletions mail_composer_template_search/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Copyright 2026 Heliconia Solutions Pvt. Ltd.
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
28 changes: 28 additions & 0 deletions mail_composer_template_search/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Copyright 2026 Heliconia Solutions Pvt. Ltd.
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).

{
"name": "Mail Composer Template Search",
"version": "18.0.1.0.0",
"category": "Social",
"summary": "Adds an inline search bar to the mail composer template "
"selector dropdown",
"author": "Heliconia Solutions Pvt. Ltd., Odoo Community Association (OCA)",
"website": "https://github.com/OCA/social",
"license": "AGPL-3",
"maintainers": ["Bhavesh Heliconia"],
"development_status": "Alpha",
"depends": [
"mail",
],
"assets": {
"web.assets_backend": [
"mail_composer_template_search/static/src/js/mail_composer_template_selector.esm.js",
"mail_composer_template_search/static/src/xml/mail_composer_template_selector.xml",
"mail_composer_template_search/static/src/scss/mail_composer_template_selector.scss",
],
},
"installable": True,
"application": False,
"auto_install": False,
}
3 changes: 3 additions & 0 deletions mail_composer_template_search/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[build-system]
requires = ["whool"]
build-backend = "whool.buildapi"
2 changes: 2 additions & 0 deletions mail_composer_template_search/readme/CONTRIBUTORS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- [Heliconia Solutions Pvt. Ltd.](https://www.heliconia.io)
- Bhavesh Heliconia
9 changes: 9 additions & 0 deletions mail_composer_template_search/readme/DESCRIPTION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
This module enhances the Odoo mail composer's template selector by adding an
inline search bar. This allows users to quickly filter and select templates
without having to click "Search More..." or scroll through a long list.

### Key Features
* **Inline Search**: Type directly in the template dropdown to filter.
* **Smart Filtering**: Case-insensitive search on template names.
* **Keyboard Navigation**: Use Arrow Up/Down and Enter to select templates.
* **Debounced Search**: Optimized performance with a 300ms debounce.
6 changes: 6 additions & 0 deletions mail_composer_template_search/readme/USAGE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
1. Open the mail composer (e.g., from an Opportunity, Partner, or Purchase Order).
2. Click on the template selector dropdown.
3. Start typing in the new search bar at the top of the dropdown.
4. The list of templates will filter automatically as you type.
5. Use **Arrow Keys** to navigate and **Enter** to select, or simply click the
desired template.
6 changes: 6 additions & 0 deletions mail_composer_template_search/static/description/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<section class="oe_container">
<div class="oe_row oe_spaced">
<h2 class="oe_slogan">Mail Composer Template Search</h2>
<h3 class="oe_slogan">Search mail templates directly from the composer dropdown</h3>
</div>
</section>
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
/* Copyright 2026 Heliconia Solutions Pvt. Ltd.
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). */

/* global document, setTimeout, clearTimeout */

/** @odoo-module **/

import {onMounted, onWillUnmount} from "@odoo/owl";
import {MailComposerTemplateSelector} from "@mail/core/web/mail_composer_template_selector";
import {patch} from "@web/core/utils/patch";

let searchTimeout = null;

patch(MailComposerTemplateSelector.prototype, {
setup() {
super.setup(...arguments);
this.state.searchQuery = "";
this.state.searchTemplates = null;
this.state.isSearching = false;
this.state.highlightedIndex = -1;

// Auto-focus the search input when the dropdown opens
this._handleDropdownOpen = (ev) => {
if (ev.target.closest(".mail-composer-template-dropdown-btn")) {
setTimeout(() => {
const input = document.querySelector(".mail-template-search-input");
if (input) {
input.focus();
}
}, 50);
}
};

onMounted(() => {
document.addEventListener("click", this._handleDropdownOpen, true);
});

onWillUnmount(() => {
document.removeEventListener("click", this._handleDropdownOpen, true);
});
},

_resetSearchState() {
this.state.searchQuery = "";
this.state.searchTemplates = null;
this.state.isSearching = false;
this.state.highlightedIndex = -1;
},

async onLoadTemplate(template) {
this._resetSearchState();
await super.onLoadTemplate(template);
},

get displayedTemplates() {
if (this.state.searchQuery && this.state.searchTemplates !== null) {
return this.state.searchTemplates;
}
return this.state.templates || [];
},

get showSearchMore() {
if (this.state.searchQuery) {
return false;
}
return (this.state.templates || []).length >= this.limit;
},

_applyHighlight(el) {
const menu = el.closest(".mail-composer-template-dropdown");
if (!menu) return;
// Get only the insert-template DropdownItems (skip Save/Delete sections)
const allItems = menu.querySelectorAll(":scope > .o-dropdown-item");
const templates = this.displayedTemplates;
// The template items are the first N items in the dropdown
for (let i = 0; i < allItems.length && i < templates.length; i++) {
if (i === this.state.highlightedIndex) {
allItems[i].style.backgroundColor = "#017e84";
allItems[i].style.color = "white";
} else {
allItems[i].style.backgroundColor = "";
allItems[i].style.color = "";
}
}
},

_clearHighlight(el) {
const menu = el.closest(".mail-composer-template-dropdown");
if (!menu) return;
const allItems = menu.querySelectorAll(":scope > .o-dropdown-item");
allItems.forEach((item) => {
item.style.backgroundColor = "";
item.style.color = "";
});
},

onSearchInput(ev) {
const rawValue = ev.target.value;
const query = rawValue.trim();
this.state.searchQuery = rawValue;
this.state.highlightedIndex = -1;
this._clearHighlight(ev.target);

if (searchTimeout) {
clearTimeout(searchTimeout);
}

if (!query) {
this.state.searchTemplates = null;
this.state.isSearching = false;
return;
}

this.state.isSearching = true;

searchTimeout = setTimeout(async () => {
try {
const fields = ["display_name"];
const domain = [
["model", "=", this.props.record.data.render_model],
["name", "ilike", query],
];
const results = await this.orm.searchRead(
"mail.template",
domain,
fields,
{limit: 50}
);
if (this.state.searchQuery.trim() === query) {
this.state.searchTemplates = results;
this.state.isSearching = false;
}
} catch {
this.state.isSearching = false;
}
}, 300);
},

onSearchKeydown(ev) {
// Stop ALL event propagation so the Dropdown component
// does not intercept Space, Arrow keys, Enter, etc.
ev.stopPropagation();

const templates = this.displayedTemplates;

if (ev.key === "ArrowDown") {
ev.preventDefault();
if (templates.length > 0) {
this.state.highlightedIndex = Math.min(
this.state.highlightedIndex + 1,
templates.length - 1
);
// Use setTimeout to apply after OWL re-render
setTimeout(() => this._applyHighlight(ev.target), 0);
}
} else if (ev.key === "ArrowUp") {
ev.preventDefault();
if (templates.length > 0) {
this.state.highlightedIndex = Math.max(
this.state.highlightedIndex - 1,
0
);
setTimeout(() => this._applyHighlight(ev.target), 0);
}
} else if (ev.key === "Enter") {
ev.preventDefault();
const idx = this.state.highlightedIndex;
if (idx >= 0 && idx < templates.length) {
// Click the actual dropdown item to trigger both selection and close
const menu = ev.target.closest(".mail-composer-template-dropdown");
if (menu) {
const items = menu.querySelectorAll(":scope > .o-dropdown-item");
if (items[idx]) {
items[idx].click();
}
}
}
}
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.mail-template-search-container {
.mail-template-search-input {
font-size: 0.85rem;
&::placeholder {
color: #999;
font-style: italic;
}
}
}
Loading
Loading