From 46765e848362129bb2d0fc34b2047e6cb2555258 Mon Sep 17 00:00:00 2001 From: Simon Gardling Date: Thu, 30 Apr 2026 04:25:07 -0400 Subject: [PATCH 6/6] feat(messages): Show 'This message was deleted.' placeholder Upstream hides the whole MessageItem when is-deleted is true via a top-level visible bind on the template root. Replace that with a Signal-Desktop-style behaviour: the row stays in the timeline, but the bubble's regular content (header, quote, attachments, label, popover trigger) and any reactions are hidden, and a single italic-dim placeholder label takes their place. The two pieces of imperative state set in code (media_overlay and the floating timestamp_img indicator over media-only messages) are reset in a small setup_deleted helper that subscribes to is-deleted, since they are not reachable through bindings. --- data/resources/style.css | 6 ++++++ data/resources/ui/message_item.blp | 29 +++++++++++++++++++++---- src/gui/message_item.rs | 34 ++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 4 deletions(-) diff --git a/data/resources/style.css b/data/resources/style.css index 1c0cdfd..3b0de9a 100644 --- a/data/resources/style.css +++ b/data/resources/style.css @@ -9,6 +9,12 @@ background-color: @bubble_bg_color; } +/* Deletion placeholder shown in place of remotely-deleted messages. */ +.deleted-message { + font-style: italic; + opacity: 0.6; +} + .message-input-bar { border-top: 1px solid @borders; } diff --git a/data/resources/ui/message_item.blp b/data/resources/ui/message_item.blp index ba3fd23..49002a5 100644 --- a/data/resources/ui/message_item.blp +++ b/data/resources/ui/message_item.blp @@ -71,8 +71,6 @@ template $FlMessageItem: $ContextMenuBin { "message-item", ] - visible: bind $not(template.message as <$FlTextMessage>.is-deleted) as ; - Grid { column-spacing: 12; row-spacing: 12; @@ -149,11 +147,32 @@ template $FlMessageItem: $ContextMenuBin { "message-bubble", ] + // Deletion placeholder "This message was deleted." — visible only when + // the message has been remotely deleted; hides the rest of the bubble's + // content and any reactions/attachments via the .deleted CSS class on + // the message-item. + Label deleted_label { + styles [ + "deleted-message", + ] + + label: _("This message was deleted."); + visible: bind template.message as <$FlTextMessage>.is-deleted; + halign: start; + xalign: 0; + + layout { + row: 0; + column: 0; + } + } + Label header { styles [ "heading", ] + visible: bind $not(template.message as <$FlTextMessage>.is-deleted) as ; label: bind template.message as <$FlTextMessage>.sender as <$FlContact>.title; hexpand: true; halign: start; @@ -177,7 +196,7 @@ template $FlMessageItem: $ContextMenuBin { "quote", ] - visible: bind $is_some(template.message as <$FlTextMessage>.quote) as ; + visible: bind $and($is_some(template.message as <$FlTextMessage>.quote) as , $not(template.message as <$FlTextMessage>.is-deleted) as ) as ; Label { styles [ @@ -219,6 +238,7 @@ template $FlMessageItem: $ContextMenuBin { } Box box_attachments { + visible: bind $not(template.message as <$FlTextMessage>.is-deleted) as ; layout { row: 2; column: 0; @@ -276,6 +296,7 @@ template $FlMessageItem: $ContextMenuBin { } $FlMessageLabel label_message { + visible: bind $not(template.message as <$FlTextMessage>.is-deleted) as ; label: bind $markup_urls(template.message as <$FlTextMessage>.body) as ; attributes: bind template.message as <$FlTextMessage>.message-attributes; @@ -306,7 +327,7 @@ template $FlMessageItem: $ContextMenuBin { ] label: bind $fix_emoji(template.message as <$FlTextMessage>.reactions) as ; - visible: bind template.has-reaction; + visible: bind $and(template.has-reaction, $not(template.message as <$FlTextMessage>.is-deleted) as ) as ; wrap-mode: word; justify: left; vexpand: false; diff --git a/src/gui/message_item.rs b/src/gui/message_item.rs index d88306f..33479da 100644 --- a/src/gui/message_item.rs +++ b/src/gui/message_item.rs @@ -35,6 +35,7 @@ impl MessageItem { s.setup_requires_attention(); s.setup_pending_and_error(); s.setup_selection(); + s.setup_deleted(); s } @@ -325,6 +326,39 @@ impl MessageItem { message.notify("requires-attention"); } + /// Reflect the message's `is-deleted` state in the UI. + /// + /// Upstream simply hid the row entirely; we instead keep the row but + /// show a `"This message was deleted."` placeholder (handled in the + /// blueprint) and clean up the bits that the deletion pseudo-message + /// can't reach via the bind layer: the media overlay (whose visibility + /// is set imperatively in `set_message`) and the standalone + /// `timestamp_img` indicator that floats over media-only messages. + pub fn setup_deleted(&self) { + let message = self.message(); + let apply = clone!( + #[weak(rename_to = s)] + self, + move || { + if s.message().is_deleted() { + s.add_css_class("deleted"); + s.imp().media_overlay.set_visible(false); + s.imp().timestamp_img.set_visible(false); + } else { + // Symmetric reset for any future code path that flips + // is-deleted back off (e.g. an unsend/restore flow). + // Today nothing does, but the asymmetry is fragile. + s.remove_css_class("deleted"); + } + } + ); + self.track_notify_local(&message, "is-deleted", { + let apply = apply.clone(); + move |_, _| apply() + }); + apply(); + } + pub fn setup_pending_and_error(&self) { let message = self.message(); message.connect_notify_local( -- 2.53.0