feat: render system messages with HTML content and update UI styles for orange flag buttons in notifications

This commit is contained in:
kdeterme 2026-09-09 22:28:22 +02:00
parent ae540acacc
commit c79d9e6f85
4 changed files with 34 additions and 6 deletions

View file

@ -136,8 +136,12 @@
<!-- Aperçu du message nettoyé des tags @[Nom](u:id) --> <!-- Aperçu du message nettoyé des tags @[Nom](u:id) -->
{% if item.last_message %} {% if item.last_message %}
<p class="mb-2 small text-muted text-truncate mt-0.5" style="max-width: 780px;"> <p class="mb-2 small text-muted text-truncate mt-0.5" style="max-width: 780px;">
{% if item.last_message.is_system %}
<span class="text-secondary opacity-75 fst-italic">{{ item.last_message.content|clean_mentions_preview|safe }}</span>
{% else %}
<strong class="text-secondary">{{ item.last_message.author.get_full_name|default:item.last_message.author.username }}</strong> : <strong class="text-secondary">{{ item.last_message.author.get_full_name|default:item.last_message.author.username }}</strong> :
<span class="text-dark opacity-75">{{ item.last_message.content|clean_mentions_preview|truncatechars:140 }}</span> <span class="text-dark opacity-75">{{ item.last_message.content|clean_mentions_preview|truncatechars:140 }}</span>
{% endif %}
</p> </p>
{% else %} {% else %}
<p class="mb-2 small text-muted mt-0.5 fst-italic">{% translate "Aucun message encore." %}</p> <p class="mb-2 small text-muted mt-0.5 fst-italic">{% translate "Aucun message encore." %}</p>

View file

@ -370,8 +370,8 @@
<a href="?tab=discussions&flag=red" class="btn btn-sm {% if filter_flag == 'red' %}btn-danger text-white{% else %}btn-outline-danger{% endif %} rounded-pill px-2.5 py-0.5" style="font-size: 0.8rem;"> <a href="?tab=discussions&flag=red" class="btn btn-sm {% if filter_flag == 'red' %}btn-danger text-white{% else %}btn-outline-danger{% endif %} rounded-pill px-2.5 py-0.5" style="font-size: 0.8rem;">
<i class="bi bi-flag-fill me-1"></i>{% translate "Important" %} ({{ flag_counts.red|default:"0" }}) <i class="bi bi-flag-fill me-1"></i>{% translate "Important" %} ({{ flag_counts.red|default:"0" }})
</a> </a>
<a href="?tab=discussions&flag=orange" class="btn btn-sm {% if filter_flag == 'orange' %}btn-warning text-dark{% else %}btn-outline-warning text-dark{% endif %} rounded-pill px-2.5 py-0.5" style="font-size: 0.8rem;"> <a href="?tab=discussions&flag=orange" class="btn btn-sm {% if filter_flag == 'orange' %}btn-warning text-dark{% else %}btn-outline-warning text-dark{% endif %} rounded-pill px-2.5 py-0.5" style="font-size: 0.8rem; {% if filter_flag != 'orange' %}border-color: #fd7e14;{% endif %}">
<i class="bi bi-flag-fill me-1"></i>{% translate "En cours" %} ({{ flag_counts.orange|default:"0" }}) <i class="bi bi-flag-fill me-1" style="color: #fd7e14;"></i>{% translate "En cours" %} ({{ flag_counts.orange|default:"0" }})
</a> </a>
<a href="?tab=discussions&flag=green" class="btn btn-sm {% if filter_flag == 'green' %}btn-success text-white{% else %}btn-outline-success{% endif %} rounded-pill px-2.5 py-0.5" style="font-size: 0.8rem;"> <a href="?tab=discussions&flag=green" class="btn btn-sm {% if filter_flag == 'green' %}btn-success text-white{% else %}btn-outline-success{% endif %} rounded-pill px-2.5 py-0.5" style="font-size: 0.8rem;">
<i class="bi bi-flag-fill me-1"></i>{% translate "Traité" %} ({{ flag_counts.green|default:"0" }}) <i class="bi bi-flag-fill me-1"></i>{% translate "Traité" %} ({{ flag_counts.green|default:"0" }})
@ -441,8 +441,12 @@
<!-- Aperçu du message nettoyé des tags @[Nom](u:id) --> <!-- Aperçu du message nettoyé des tags @[Nom](u:id) -->
{% if item.last_message %} {% if item.last_message %}
<p class="mb-2 small text-muted text-truncate mt-0.5" style="max-width: 780px;"> <p class="mb-2 small text-muted text-truncate mt-0.5" style="max-width: 780px;">
{% if item.last_message.is_system %}
<span class="text-secondary opacity-75 fst-italic">{{ item.last_message.content|clean_mentions_preview|safe }}</span>
{% else %}
<strong class="text-secondary">{{ item.last_message.author.get_full_name|default:item.last_message.author.username }}</strong> : <strong class="text-secondary">{{ item.last_message.author.get_full_name|default:item.last_message.author.username }}</strong> :
<span class="text-dark opacity-75">{{ item.last_message.content|clean_mentions_preview|truncatechars:140 }}</span> <span class="text-dark opacity-75">{{ item.last_message.content|clean_mentions_preview|truncatechars:140 }}</span>
{% endif %}
</p> </p>
{% else %} {% else %}
<p class="mb-2 small text-muted mt-0.5 fst-italic">{% translate "Aucun message encore." %}</p> <p class="mb-2 small text-muted mt-0.5 fst-italic">{% translate "Aucun message encore." %}</p>

View file

@ -302,8 +302,8 @@
<a href="?tab=discussions&flag=red" class="btn btn-sm {% if filter_flag == 'red' %}btn-danger text-white{% else %}btn-outline-danger{% endif %} rounded-pill px-2.5 py-1 text-nowrap" style="font-size: 0.78rem;"> <a href="?tab=discussions&flag=red" class="btn btn-sm {% if filter_flag == 'red' %}btn-danger text-white{% else %}btn-outline-danger{% endif %} rounded-pill px-2.5 py-1 text-nowrap" style="font-size: 0.78rem;">
<i class="bi bi-flag-fill me-1"></i>{% translate "Important" %} ({{ flag_counts.red|default:"0" }}) <i class="bi bi-flag-fill me-1"></i>{% translate "Important" %} ({{ flag_counts.red|default:"0" }})
</a> </a>
<a href="?tab=discussions&flag=orange" class="btn btn-sm {% if filter_flag == 'orange' %}btn-warning text-dark{% else %}btn-outline-warning text-dark{% endif %} rounded-pill px-2.5 py-1 text-nowrap" style="font-size: 0.78rem;"> <a href="?tab=discussions&flag=orange" class="btn btn-sm {% if filter_flag == 'orange' %}btn-warning text-dark{% else %}btn-outline-warning text-dark{% endif %} rounded-pill px-2.5 py-1 text-nowrap" style="font-size: 0.78rem; {% if filter_flag != 'orange' %}border-color: #fd7e14;{% endif %}">
<i class="bi bi-flag-fill me-1"></i>{% translate "En cours" %} ({{ flag_counts.orange|default:"0" }}) <i class="bi bi-flag-fill me-1" style="color: #fd7e14;"></i>{% translate "En cours" %} ({{ flag_counts.orange|default:"0" }})
</a> </a>
<a href="?tab=discussions&flag=green" class="btn btn-sm {% if filter_flag == 'green' %}btn-success text-white{% else %}btn-outline-success{% endif %} rounded-pill px-2.5 py-1 text-nowrap" style="font-size: 0.78rem;"> <a href="?tab=discussions&flag=green" class="btn btn-sm {% if filter_flag == 'green' %}btn-success text-white{% else %}btn-outline-success{% endif %} rounded-pill px-2.5 py-1 text-nowrap" style="font-size: 0.78rem;">
<i class="bi bi-flag-fill me-1"></i>{% translate "Traité" %} ({{ flag_counts.green|default:"0" }}) <i class="bi bi-flag-fill me-1"></i>{% translate "Traité" %} ({{ flag_counts.green|default:"0" }})
@ -364,8 +364,12 @@
<!-- Aperçu dernier message --> <!-- Aperçu dernier message -->
{% if item.last_message %} {% if item.last_message %}
<p class="mb-2 text-muted text-truncate" style="font-size: 0.82rem;"> <p class="mb-2 text-muted text-truncate" style="font-size: 0.82rem;">
{% if item.last_message.is_system %}
<span class="text-secondary opacity-75 fst-italic">{{ item.last_message.content|clean_mentions_preview|safe }}</span>
{% else %}
<strong class="text-secondary">{{ item.last_message.author.get_full_name|default:item.last_message.author.username }}</strong> : <strong class="text-secondary">{{ item.last_message.author.get_full_name|default:item.last_message.author.username }}</strong> :
<span class="text-dark opacity-75">{{ item.last_message.content|clean_mentions_preview|truncatechars:90 }}</span> <span class="text-dark opacity-75">{{ item.last_message.content|clean_mentions_preview|truncatechars:90 }}</span>
{% endif %}
</p> </p>
{% else %} {% else %}
<p class="mb-2 text-muted fst-italic" style="font-size: 0.82rem;">{% translate "Aucun message encore." %}</p> <p class="mb-2 text-muted fst-italic" style="font-size: 0.82rem;">{% translate "Aucun message encore." %}</p>

View file

@ -622,9 +622,25 @@ class DiscussionFlagTests(TestCase):
self.client.force_login(self.user) self.client.force_login(self.user)
from notifications.models import Message from notifications.models import Message
Message.objects.create(discussion=self.discussion, author=self.user, content="Hello message") Message.objects.create(discussion=self.discussion, author=self.user, content="Hello message")
Message.objects.create(discussion=self.discussion, author=self.user, content="🚩 System flag", is_system=True) Message.objects.create(discussion=self.discussion, author=self.user, content="<i class=\"bi bi-flag-fill text-danger me-1\"></i> System flag", is_system=True)
url = reverse('notifications:discussion_detail', args=[self.discussion.pk]) url = reverse('notifications:discussion_detail', args=[self.discussion.pk])
res = self.client.get(url) res = self.client.get(url)
self.assertEqual(res.status_code, 200) self.assertEqual(res.status_code, 200)
self.assertContains(res, "Hello message") self.assertContains(res, "Hello message")
self.assertContains(res, "System flag") self.assertContains(res, "bi-flag-fill text-danger")
def test_inbox_renders_system_message_flag_icon_safely(self):
self.client.force_login(self.user)
from notifications.models import Message
Message.objects.create(
discussion=self.discussion,
author=self.user,
content='<i class="bi bi-flag-fill text-success me-1"></i> Alice Smith a défini le statut : Traité / Fait',
is_system=True,
)
url = reverse('notifications:inbox') + '?tab=discussions'
res = self.client.get(url)
self.assertEqual(res.status_code, 200)
# Icon should NOT be escaped as &lt;i
self.assertNotContains(res, '&lt;i class=')
self.assertContains(res, '<i class="bi bi-flag-fill text-success me-1"></i>')