details:   https://code.tryton.org/tryton/commit/80fddd54da71
branch:    default
user:      Cédric Krier <[email protected]>
date:      Thu Feb 05 16:12:46 2026 +0100
description:
        Fill blank state of empty notification menu

        Closes #14575
diffstat:

 sao/src/notification.js           |  132 ++++++++++++++++++++-----------------
 tryton/tryton/gui/notification.py |   74 +++++++++++---------
 2 files changed, 110 insertions(+), 96 deletions(-)

diffs (240 lines):

diff -r 8d0759bc0e9a -r 80fddd54da71 sao/src/notification.js
--- a/sao/src/notification.js   Sat Feb 14 09:49:11 2026 +0100
+++ b/sao/src/notification.js   Thu Feb 05 16:12:46 2026 +0100
@@ -40,74 +40,82 @@
             let Notification = new Sao.Model('res.notification');
             Notification.execute('get', []).done((notifications) => {
                 this.el.empty();
-                for (let notification of notifications) {
-                    let notification_item = jQuery('<div/>', {
-                    }).append(jQuery('<span/>', {
-                        'class': 'notification-label',
-                        'text': notification.label,
-                        'title': notification.label,
-                    })).append(jQuery('<span/>', {
-                        'class': 'notification-description',
-                        'text': notification.description,
-                        'title': notification.description,
-                    }));
-                    let link = jQuery('<a/>', {
-                        'role': 'menuitem',
-                        'href': '#',
-                    }).click((evt) => {
-                        evt.preventDefault();
-                        this.open(notification)
-                    }).append(notification_item);
-                    let li = jQuery('<li/>', {
-                        'class': 'notification-item',
+                if (!notifications.length) {
+                    jQuery('<li/>', {
+                        'class': 'dropdown-header',
                         'role': 'presentation',
-                    });
-                    let img = jQuery('<img/>', {
-                        'class': 'icon',
-                    });
-                    link.prepend(img);
-                    Sao.common.ICONFACTORY.get_icon_url(
-                        notification.icon || 'tryton-notification')
-                        .then(url => {
-                            img.attr('src', url);
-                            // Append only when the url is known to prevent
-                            // layout shifts
-                            li.append(link);
+                    }).text(Sao.i18n.gettext("No notifications at this time"))
+                    .appendTo(this.el);
+                } else {
+                    for (let notification of notifications) {
+                        let notification_item = jQuery('<div/>', {
+                        }).append(jQuery('<span/>', {
+                            'class': 'notification-label',
+                            'text': notification.label,
+                            'title': notification.label,
+                        })).append(jQuery('<span/>', {
+                            'class': 'notification-description',
+                            'text': notification.description,
+                            'title': notification.description,
+                        }));
+                        let link = jQuery('<a/>', {
+                            'role': 'menuitem',
+                            'href': '#',
+                        }).click((evt) => {
+                            evt.preventDefault();
+                            this.open(notification)
+                        }).append(notification_item);
+                        let li = jQuery('<li/>', {
+                            'class': 'notification-item',
+                            'role': 'presentation',
+                        });
+                        let img = jQuery('<img/>', {
+                            'class': 'icon',
                         });
-                    if (notification.unread) {
-                        li.addClass('notification-unread');
+                        link.prepend(img);
+                        Sao.common.ICONFACTORY.get_icon_url(
+                            notification.icon || 'tryton-notification')
+                            .then(url => {
+                                img.attr('src', url);
+                                // Append only when the url is known to prevent
+                                // layout shifts
+                                li.append(link);
+                            });
+                        if (notification.unread) {
+                            li.addClass('notification-unread');
+                        }
+                        this.el.append(li)
                     }
-                    this.el.append(li)
+                    this.el.append(
+                        jQuery('<li/>', {
+                            'role': 'presentation',
+                            'class': 'notification-item notification-action',
+                        }).append(jQuery('<a/>', {
+                            'role': 'menuitem',
+                            'href': '#',
+                            'title': Sao.i18n.gettext("All Notifications..."),
+                        }).append(jQuery('<span/>', {
+                            'class': 'caret',
+                        })).click((evt) => {
+                            evt.preventDefault();
+                            let params = {
+                                context: jQuery.extend(
+                                    {}, Sao.Session.current_session.context),
+                                domain: [
+                                    ['user', '=', 
Sao.Session.current_session.user_id]],
+                            };
+                            params.model = 'res.notification';
+                            Sao.Tab.create(params).done(() => {
+                                this.indicator.hide();
+                            });
+                        }))
+                    );
                 }
                 this.el.append(
                     jQuery('<li/>', {
-                        'role': 'presentation',
-                        'class': 'notification-item notification-action',
-                    }).append(jQuery('<a/>', {
-                        'role': 'menuitem',
-                        'href': '#',
-                        'title': Sao.i18n.gettext("All Notifications..."),
-                    }).append(jQuery('<span/>', {
-                        'class': 'caret',
-                    })).click((evt) => {
-                        evt.preventDefault();
-                        let params = {
-                            context: jQuery.extend({}, 
Sao.Session.current_session.context),
-                            domain: [['user', '=', 
Sao.Session.current_session.user_id]],
-                        };
-                        params.model = 'res.notification';
-                        Sao.Tab.create(params).done(() => {
-                            this.indicator.hide();
-                        });
-                    }))
-                );
-                if (notifications.length > 0) {
-                    this.el.append(
-                        jQuery('<li/>', {
-                            'role': 'separator',
-                            'class': 'divider',
-                        }));
-                }
+                        'role': 'separator',
+                        'class': 'divider',
+                    }));
                 let preferences_img = jQuery('<img/>', {
                     'class': 'icon',
                 });
diff -r 8d0759bc0e9a -r 80fddd54da71 tryton/tryton/gui/notification.py
--- a/tryton/tryton/gui/notification.py Sat Feb 14 09:49:11 2026 +0100
+++ b/tryton/tryton/gui/notification.py Thu Feb 05 16:12:46 2026 +0100
@@ -33,48 +33,54 @@
         notifications = rpc.execute(
             'model', 'res.notification', 'get', rpc.CONTEXT)
 
-        for notification in notifications:
-            item = Gtk.MenuItem()
-
-            hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6)
-            hbox.set_margin_top(4)
-            hbox.set_margin_bottom(4)
-            hbox.set_margin_start(6)
-            hbox.set_margin_end(6)
+        if not notifications:
+            item = Gtk.MenuItem(
+                label=_("No notifications at this time"))
+            item.set_sensitive(False)
+            menu.append(item)
+        else:
+            for notification in notifications:
+                item = Gtk.MenuItem()
 
-            img = common.IconFactory.get_image(
-                notification['icon'] or 'tryton-notification',
-                Gtk.IconSize.MENU)
-            hbox.pack_start(img, False, False, 0)
+                hbox = Gtk.Box(
+                    orientation=Gtk.Orientation.HORIZONTAL, spacing=6)
+                hbox.set_margin_top(4)
+                hbox.set_margin_bottom(4)
+                hbox.set_margin_start(6)
+                hbox.set_margin_end(6)
 
-            vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=2)
+                img = common.IconFactory.get_image(
+                    notification['icon'] or 'tryton-notification',
+                    Gtk.IconSize.MENU)
+                hbox.pack_start(img, False, False, 0)
 
-            label = Gtk.Label(notification['label'])
-            label.set_xalign(0)
-            vbox.pack_start(label, False, False, 0)
+                vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=2)
 
-            description = Gtk.Label(notification['description'])
-            description.set_xalign(0)
-            description.get_style_context().add_class("dim-label")
-            description.set_max_width_chars(30)
-            description.set_line_wrap(True)
-            description.set_line_wrap_mode(Pango.WrapMode.WORD_CHAR)
-            description.set_ellipsize(Pango.EllipsizeMode.END)
-            description.set_lines(2)
-            vbox.pack_start(description, False, False, 0)
+                label = Gtk.Label(notification['label'])
+                label.set_xalign(0)
+                vbox.pack_start(label, False, False, 0)
 
-            hbox.pack_start(vbox, True, True, 0)
-            item.add(hbox)
-
-            item.connect('activate', self.open, notification)
+                description = Gtk.Label(notification['description'])
+                description.set_xalign(0)
+                description.get_style_context().add_class("dim-label")
+                description.set_max_width_chars(30)
+                description.set_line_wrap(True)
+                description.set_line_wrap_mode(Pango.WrapMode.WORD_CHAR)
+                description.set_ellipsize(Pango.EllipsizeMode.END)
+                description.set_lines(2)
+                vbox.pack_start(description, False, False, 0)
 
-            if notification['unread']:
-                style = item.get_style_context()
-                style.add_class('unread-notification')
+                hbox.pack_start(vbox, True, True, 0)
+                item.add(hbox)
+
+                item.connect('activate', self.open, notification)
 
-            menu.append(item)
+                if notification['unread']:
+                    style = item.get_style_context()
+                    style.add_class('unread-notification')
 
-        if notifications:
+                menu.append(item)
+
             menu.append(Gtk.SeparatorMenuItem())
 
             def open_all_notifications(item):

Reply via email to