changeset 403:65c53ec5e777

Bulma: complete redesign: This is a WIP full redesign of the whole web UI. Only chat is usable at the moment, many things are broken. The design uses the new Bulma v1+, has a look closer to industry standards, paves the way for a dark theme, and should be easy to use. It's a basis for coming features such as UI/UX for threads. rel 457
author Goffi <goffi@goffi.org>
date Fri, 11 Apr 2025 21:32:05 +0200
parents 2bbcb7da56bc
children 66f98ee041d8
files sat_templates/templates/bulma/base/base.html sat_templates/templates/bulma/chat/chat.html sat_templates/templates/bulma/chat/direct_messages.html sat_templates/templates/bulma/chat/extra_menu.html sat_templates/templates/bulma/chat/groups_search_item.html sat_templates/templates/bulma/chat/message.html sat_templates/templates/bulma/chat/new_chat_dialog.html sat_templates/templates/bulma/chat/occupant_item.html sat_templates/templates/bulma/chat/reactions.html sat_templates/templates/bulma/chat/search_item.html sat_templates/templates/bulma/components/avatar.html sat_templates/templates/bulma/components/collapsible_card.html sat_templates/templates/bulma/dialogs/modal.html sat_templates/templates/bulma/dialogs/notification.html sat_templates/templates/bulma/login/login.html sat_templates/templates/bulma/static/chat.css sat_templates/templates/bulma/static/styles.css
diffstat 17 files changed, 692 insertions(+), 239 deletions(-) [+]
line wrap: on
line diff
--- a/sat_templates/templates/bulma/base/base.html	Sat Oct 26 22:53:26 2024 +0200
+++ b/sat_templates/templates/bulma/base/base.html	Fri Apr 11 21:32:05 2025 +0200
@@ -93,10 +93,14 @@
             <div class="modal-background"></div>
             <div class="modal-content">
                 <div class="notification has-text-centered">
-                    <span class="icon is-large">
-                        <i class="icon-loading icon_animate_spin"></i>
-                    </span>
-                    {% trans %}page is loading, please wait…{% endtrans %}
+                    <div class="is-flex is-align-items-center is-justify-content-center">
+                        <span class="icon is-large">
+                            <i class="fa-solid fa-spinner fa-spin fa-xl"></i>
+                        </span>
+                        <span class="">
+                        {% trans %}page is loading, please wait…{% endtrans %}
+                        </span>
+                    </div>
                 </div>
             </div>
         </div>
@@ -107,7 +111,7 @@
         {% endblock main_menu %}
     {% endif %}
     {% block body_wrapper %}
-    <div id="body" class="container">
+    <div id="body">
         {% if breadcrumbs and not no_breadcrumps and breadcrumbs|length > 1 %}
             <nav class="breadcrumb is-medium" aria-label="breadcrumbs">
                 <ul>
--- a/sat_templates/templates/bulma/chat/chat.html	Sat Oct 26 22:53:26 2024 +0200
+++ b/sat_templates/templates/bulma/chat/chat.html	Fri Apr 11 21:32:05 2025 +0200
@@ -1,76 +1,160 @@
-{% if not embedded %}{% extends 'base/base.html' %}{% endif %}
+{% if not embedded %}
+    {% set loading_screen = true %}
+    {% extends 'base/base.html' %}
+{% endif %}
+{% from 'components/collapsible_card.html' import collapsible_card %}
 {% block title %}{{ target_jid }} - {{ super() }}{% endblock %}
 {% block body %}
     {{ icon_defs(
-    "lock-open", "lock-filled", "ok", "pencil", "dot-3", "share", "attach",
-    "paper-plane-empty", "doc", "download", "wrench", "eye", "smile", "plus-circled",
-    "quote-left", "trash-empty"
+   "share-nodes", "ellipsis","regular face-smile", "quote-left", "paperclip", "pencil", "regular trash-can"
     ) }}
 
-    {# TODO: this should be done in a more generic way in dialog module #}
-    <div id="modal" class="modal">
-        <div class="modal-background"></div>
-        <div class="modal-content">
-            <p class="image">
-            <img id="modal-image" src="" alt="">
-            </p>
-        </div>
-        <button class="modal-close is-large" aria-label="close"></button>
-    </div>
+
 
-    <div id="chat-panel" class="is-flex is-flex-direction-column is-justify-content-space-between is-align-items-stretch pt-4"> <!-- calculate height depending on your navbar size -->
-        <!-- Header -->
-        <div id="header" class="box p-4 is-flex is-justify-content-space-between is-align-items-center is-flex-shrink-0">
-            {% if subject is defined %}
-                <h2 id="room-subject" class="title is-5 has-text-weight-bold">{{- subject|urlize(nofollow=true,target='_blank') -}}</h2>
-            {% endif %}
+    <div class="columns is-gapless chat-container">
+        <!-- Left Panel -->
+        <div class="column is-2-desktop is-3-tablet chat-sidebar has-background-dark" id="left_panel">
+            <aside class="menu pl-4">
+                <button class="button is-ghost has-text-grey-light mb-2 pl-1 is-align-items-center is-justify-content-start is-fullwidth" id="new_chat_btn" aria-label="Start new discussion">
+                    <span class="icon">
+                        <i class="fas fa-circle-plus"></i>
+                    </span>
+                    <span class="pt-1">Start New Chat</span>
+                </button>
+                <p class="menu-label has-text-grey-light">
+                    {% trans %}Rooms{% endtrans %}
+                </p>
+                <ul class="menu-list">
+                    {% for room_address, room_info in bookmarks.items() %}
+                        <li>
+                            <a {{ {'href': chat_url + "/" + room_address}|xmlattr}} class="chat-jid has-text-white"><span class="icon"><i class="fas fa-hashtag"></i></span> {{ room_info.get('name', room_address.split('@')[0]) }}</a>
+                        </li>
+                    {% endfor %}
+                </ul>
+                <p class="menu-label has-text-grey-light">
+                    Direct Messages
+                </p>
+                <div id="direct-messages"></div>
+            </aside>
         </div>
 
-        <!-- Messages -->
-        <div id="messages" class="box p-4 has-background-white mb-4 is-flex-grow-1 is-flex-direction-column-reverse is-overflow-auto">
-            {% for msg in messages %}
-                {% set current_date = msg.timestamp|date_fmt('full', date_only=True) %}
-                {% if loop.changed(current_date) %}
-                    <div class="separator">
-                        <hr class="has-background-light" />
-                        <span class="has-text-grey is-size-7">{{current_date}}</span>
-                        <hr class="has-background-light" />
+        <!-- Main Chat Area -->
+        <div class="column is-8-desktop is-6-tablet chat-main" id="main_panel">
+                <div class="chat-header">
+                    <div class="level is-mobile">
+                        <div class="level-left">
+                            <div class="level-item">
+                                <button class="button is-small" id="left_panel-toggle" aria-label="Toggle left panel">
+                                    <span class="icon"><i class="fas fa-bars"></i></span>
+                                </button>
+                            </div>
+                            <div class="level-item">
+                                <h1 class="title is-5 mb-0">{{target_jid}}</h1>
+                            </div>
+                        </div>
+                        <div class="level-right">
+                            <div class="level-item">
+                                <div class="field has-addons">
+                                    <div class="control">
+                                        <input class="input is-small" type="text" placeholder="Search">
+                                    </div>
+                                    <div class="control">
+                                        <button class="button is-info is-small">
+                                            <span class="icon is-small">
+                                                <i class="fas fa-search"></i>
+                                            </span>
+                                        </button>
+                                    </div>
+                                </div>
+                            </div>
+                            <div class="level-item">
+                                <button class="button is-small">
+                                    <span class="icon"><i class="fas fa-cog"></i></span>
+                                </button>
+                            </div>
+                            <div class="level-item {{ "is-hidden" if chat_type != "group" }}">
+                                <button class="button is-small" id="right_panel-toggle">
+                                    <span class="icon"><i class="fas fa-info-circle"></i></span>
+                                </button>
+                            </div>
+                        </div>
                     </div>
-                {% endif %}
-                {% include 'chat/message.html' %}
-            {% endfor %}
+                </div>
+
+                <!-- Messages -->
+                <div id="messages">
+                    {% for msg in messages %}
+                        {% set current_date = msg.timestamp|date_fmt('full', date_only=True) %}
+                        {% if loop.changed(current_date) %}
+                            <div class="separator">
+                                <hr class="has-background-light" />
+                                <span class="has-text-grey is-size-7">{{current_date}}</span>
+                                <hr class="has-background-light" />
+                            </div>
+                        {% endif %}
+                        {% include 'chat/message.html' %}
+                    {% endfor %}
+                </div>
+
+            <!-- Attachments -->
+            <div id="attachments" class="box has-background-white is-flex is-flex-grow-0 is-flex-shrink-0 is-align-items-center is-overflow-auto is-contracted">
+                {% for file in attachments %}
+                    {% include 'chat/attachment_preview.html' %}
+                {% endfor %}
+            </div>
+
+            <div id="message_input" class="chat-input">
+                <div class="field has-addons">
+                    <div class="control">
+                        <button id="attach-button" class="button">
+                            <span class="icon is-small">
+                            {{ icon('paperclip') }}
+                            </span>
+                        </button>
+                        <input id="file-input" type="file" multiple="true" style="display: none" />
+                    </div>
+                    <div class="control is-expanded">
+                     <textarea id="message_input_area" class="textarea" name="message_input_area" type="text" rows="1" placeholder="{{_("Type your message…")}}"></textarea>
+                    </div>
+                    <div class="control">
+                        <button class="button is-primary">
+                            <span class="icon is-small">
+                                <i class="fas fa-paper-plane"></i>
+                            </span>
+                            <span class="is-hidden-mobile">Send</span>
+                        </button>
+                    </div>
+                </div>
+            </div>
+
         </div>
 
-    <!-- Attachments -->
-    <div id="attachments" class="box has-background-white is-flex is-flex-grow-0 is-flex-shrink-0 is-align-items-center is-overflow-auto is-contracted">
-        {% for file in attachments %}
-            {% include 'chat/attachment_preview.html' %}
-        {% endfor %}
-    </div>
+
+        <!-- Right Panel -->
+
+        <div class="column is-2-desktop is-3-tablet chat-details {{ 'is-hidden' if chat_type != 'group' }}" id="right_panel">
+            <div class="box is-shadowless has-background-white-bis is-full-height">
+                <h3 class="title is-5 has-text-centered mb-4">Room Details</h3>
+
+                {% if subject is defined %}
+                    {% call collapsible_card("Subject", icon="fas fa-quote-left") %}
+                    {{subject}}
+                    {% endcall %}
+                {% endif %}
 
-    <!-- Input -->
-    <div id="input-area" class="field has-addons py-4 is-flex-shrink-0">
-        <p class="control is-expanded">
-            <textarea id="message_input" class="textarea" name="message" type="text" rows="1" placeholder="{{_("enter your message")}}"></textarea>
-        </p>
-        <div class="control">
-            <button class="button" id="attach_button">
-                <span class="icon is-small">
-                    {{ icon('attach', cls='has-text-info') }}
-                </span>
-            </button>
-            <input id="file_input" type="file" multiple="true" style="display: none" />
+                <div class="card">
+                    <div class="card-header has-background-info-light">
+                        <p class="card-header-title has-text-info-dark">
+                        <span class="icon"><i class="fas fa-users"></i></span>
+                        <span>Members (<span id="occupants-count">0</span>)</span>
+                        </p>
+                    </div>
+                    <div class="card-content py-3 px-4" id="group-occupants">
+                    </div>
+                </div>
+            </div>
         </div>
-        <div class="control">
-            <button class="button" id="send_button">
-                <span class="icon is-small">
-                    {{ icon('paper-plane-empty') }}
-                </span>
-            </button>
-        </div>
-    </div>
 
-    </div>
 {% endblock body %}
 
 {% block footer %}{% endblock footer %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/chat/direct_messages.html	Fri Apr 11 21:32:05 2025 +0200
@@ -0,0 +1,7 @@
+
+<ul class="menu-list">
+    {% for contact_jid, contact_data in roster.items() %}
+        {%- set name = identities[contact_jid].nicknames[0] | default(contact_jid) -%}
+        <li><a {{ {'href': chat_url + '/' + contact_jid}|xmlattr}} class="has-background-inherit has-text-white"><span class="icon"><i class="fas fa-circle has-text-success"></i></span>{{name}}</a></li>
+    {% endfor %}
+</ul>
--- a/sat_templates/templates/bulma/chat/extra_menu.html	Sat Oct 26 22:53:26 2024 +0200
+++ b/sat_templates/templates/bulma/chat/extra_menu.html	Fri Apr 11 21:32:05 2025 +0200
@@ -1,4 +1,4 @@
-<div class="extra-menu-popup">
+<div class="menu extra-menu-popup">
     <ul class="menu-list">
         <li class="action_quote">
             <a href="#" class="">
@@ -15,7 +15,7 @@
         {% if retract %}
             <li class="action_retract">
                 <a href="#" class="">
-                    {{ icon('trash-empty', cls='icon is-small has-text-danger') }} {% trans %}Retract{% endtrans %}
+                    {{ icon('regular trash-can', cls='icon is-small has-text-danger') }} {% trans %}Retract{% endtrans %}
                 </a>
             </li>
         {% endif %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/chat/groups_search_item.html	Fri Apr 11 21:32:05 2025 +0200
@@ -0,0 +1,28 @@
+{% import 'components/avatar.html' as avatar with context %}
+
+
+
+<div class="search-item box py-2 px-3 mb-2 is-unselectable is-clickable" {{ {'data-entity': item.entity}|xmlattr }}>
+    <div class="is-flex is-align-items-center">
+        <div class="mr-2">
+            {{ avatar.avatar(item.entity) }}
+        </div>
+        <div class="is-flex-grow-1">
+            <p class="mb-0">{{item.name if item.name else item.entity}}</p>
+            <p class="has-text-grey is-size-7">{{item.entity}}</p>
+        </div>
+        <div>
+            {% if item.occupants %}
+                <span class="tag is-info is-light">
+                    <span class="icon-text">
+                        <span class="icon">
+                            <i class="fa-solid fa-user"></i>
+                        </span>
+                        <span>23</span>
+                    </span>
+                </span>
+            {% endif %}
+        </div>
+    </div>
+</div>
+
--- a/sat_templates/templates/bulma/chat/message.html	Sat Oct 26 22:53:26 2024 +0200
+++ b/sat_templates/templates/bulma/chat/message.html	Fri Apr 11 21:32:05 2025 +0200
@@ -8,74 +8,74 @@
 
 <div
     id="{{msg.id}}"
-    class="media is-chat-message msg_{{msg.type}} {{'own_msg' if own_msg}} {{ 'is-retracted' if msg.extra.retracted }}"
+    class="media chat-message msg_{{msg.type}} {{'own_msg' if own_msg}} {{ 'is-retracted' if msg.extra.retracted }}"
     role="listitem"
     aria-label="{{ 'Retracted Message' if msg.extra.retracted else 'Chat Message' }}"
     {% if msg.extra.editions %}data-editions='{{msg.extra.editions|tojson}}'{% endif %}
     >
     {%- if msg.type != "info" %}
         {%- set author = identities[msg.from_].nicknames[0] | default(msg.from_) -%}
-        <figure class="media-left pt-1">
-            {{ avatar.avatar(msg.from_, "is-32x32") }}
+        <figure class="media-left">
+            {{ avatar.avatar(msg.from_) }}
         </figure>
     {% endif -%}
     <div class="media-content is-relative">
-        <div class="content">
             {%- if msg.type != "info" %}
-                <nav class="level is-mobile is-marginless is-size-7 is-not-selectable">
-                    <div class="level-left has-text-weight-bold">
-                        <div class="level-item">
-                            <span class="author" id="msg_author_{{msg.id}}">{{author}}</span>
-                        </div>
+
+                <div>
+
+                    <span class="author" id="msg_author_{{msg.id}}">{{author}}</span>
+                    <span class="date" id="msg_date_{{msg.id}}">{{(msg.extra.updated or msg.timestamp)|date_fmt('short', tz_name=tz_name)}}</span>
+                    <span id="status_icons_{{msg.id}}" class="status-icons level-item has-padding-left">
+                        {% if msg.extra.editions %}
+                            {{ icon('pencil', cls='icon is-small message-editions') }}
+                        {% endif %}
+                        {% if msg.encrypted %}
+                            {{ icon('lock-filled', cls='icon is-small has-text-success') }}
+                        {% else %}
+                            {{ icon('lock-open', cls='icon is-small has-text-danger') }}
+                        {% endif %}
+                        {% if msg.received %}
+                            {{ icon('ok', cls='icon is-small has-text-link') }}
+                        {% endif %}
+                        {% if msg.edited %}
+                            {{ icon('pencil', cls='icon is-small has-text-info') }}
+                        {% endif %}
+                    </span>
+                </div>
+
+            {% endif -%}
+            <div class="message-core">
+                <p class="message-body {{ "has-text-info" if msg.type=="info" or msg.extra.retracted }} m-0" id="message-body_{{msg.id}}">
+                   {%- if msg.extra.retracted %}
+                       {% trans %}This message has been retracted.{% endtrans %}
+                   {% else %}
+                       {{- msg.html or (msg.text|e|urlize|safe) -}}
+                   {% endif -%}
+                </p>
+
+                <div class="url-previews is-hidden">
+                    <div class="icon-container"></div>
+                </div>
+
+                <div
+                   id="actions_{{msg.id}}"
+                   class="level message-actions mb-0 mt-1 {{ "is-hidden" if msg.extra.retracted }}"
+                   >
+                   <div class="level-left"></div>
+                    <div class="level-right">
+                        {# {{ icon('share-nodes', cls='icon is-small action-button', id='msg_share_' + msg.id) }} #}
+                        {{ icon('regular face-smile', cls='icon is-small action-button reaction-button', id='msg_actions_' + msg.id) }}
+                        {{ icon('ellipsis', cls='icon is-small action-button extra-button', id='msg_actions_' + msg.id) }}
                     </div>
-                    <div class="level-right is-italic">
-                        <div class="level-item">
-                            <span class="date" id="msg_date_{{msg.id}}">{{(msg.extra.updated or msg.timestamp)|date_fmt('short', tz_name=tz_name)}}</span>
-                            <div id="status_icons_{{msg.id}}" class="status-icons level-item has-padding-left">
-                                {% if msg.extra.editions %}
-                                    {{ icon('pencil', cls='icon is-small message-editions') }}
-                                {% endif %}
-                                {% if msg.encrypted %}
-                                    {{ icon('lock-filled', cls='icon is-small has-text-success') }}
-                                {% else %}
-                                    {{ icon('lock-open', cls='icon is-small has-text-danger') }}
-                                {% endif %}
-                                {% if msg.received %}
-                                    {{ icon('ok', cls='icon is-small has-text-link') }}
-                                {% endif %}
-                                {% if msg.edited %}
-                                    {{ icon('pencil', cls='icon is-small has-text-info') }}
-                                {% endif %}
-                            </div>
-                        </div>
-                    </div>
-                </nav>
-            {% endif -%}
-            <p class="msg_body has-whitespace-pre-wrap {{ "has-text-info" if msg.type=="info" or msg.extra.retracted }} m-0" id="msg_body_{{msg.id}}">
-               {%- if msg.extra.retracted %}
-                   {% trans %}This message has been retracted.{% endtrans %}
-               {% else %}
-                   {{- msg.html or (msg.text|e|urlize|safe) -}}
-               {% endif -%}
-            </p>
+                </div>
 
-            <div class="url-previews is-hidden">
-                <div class="icon-container"></div>
-            </div>
-
-            <div
-               id="actions_{{msg.id}}"
-               class="level is-mobile actions mb-0 {{ "is-hidden" if msg.extra.retracted }}"
-               >
-                {#{{ icon('share', cls='icon is-small action-button', id='msg_share_{{msg.id}}') }} #}
-                {{ icon('smile', cls='icon is-small action-button reaction-button', id='msg_actions_{{msg.id}}') }}
-                {{ icon('dot-3', cls='icon is-small action-button extra-button', id='msg_actions_{{msg.id}}') }}
-            </div>
-            <div id="msg_reactions_{{msg.id}}" style="margin-top: 0.5em;">
-                {% if msg.extra.reactions is defined %}
-                    {% set reactions = msg.extra.get("reactions") %}
-                    {% include 'chat/reactions.html' %}
-                {% endif %}
+                <div id="msg_reactions_{{msg.id}}">
+                    {% if msg.extra.reactions is defined %}
+                        {% set reactions = msg.extra.get("reactions") %}
+                        {% include 'chat/reactions.html' %}
+                    {% endif %}
+                </div>
             </div>
         {% if msg.attachments %}
             <div class="message-attachments pt-2">
@@ -106,6 +106,5 @@
                 {%- endfor %}
             </div>
         {% endif %}
-        </div>
     </div>
 </div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/chat/new_chat_dialog.html	Fri Apr 11 21:32:05 2025 +0200
@@ -0,0 +1,125 @@
+{% import 'components/avatar.html' as avatar with context %}
+
+<div class="modal-card jid-search-dialog">
+    <header class="modal-card-head">
+        <p class="modal-card-title">Start New Chat</p>
+        <button class="delete action_close"></button>
+    </header>
+    <section class="modal-card-body">
+
+        <div class="tabs is-boxed mt-4">
+            <ul>
+                <li class="is-active" data-tab="direct">
+                    <a>
+                        <span class="icon has-text-info">
+                            <i class="fa-solid fa-user"></i>
+                        </span>
+                        <span>Persons</span>
+                        <span class="tag is-info is-light ml-2 direct-count">0</span>
+                    </a>
+                </li>
+                <li data-tab="groups">
+                    <a>
+                        <span class="icon has-text-success">
+                            <i class="fa-solid fa-users"></i>
+                        </span>
+                        <span>Group Chats</span>
+                    </a>
+                </li>
+            </ul>
+        </div>
+
+        <!-- Direct Messages Tab -->
+        <div class="direct-content pt-4">
+            <div class="field">
+                <label class="label">Direct contact search.</label>
+                <p class="help">Use names, JIDs, or keywords.</p>
+                <div class="control has-icons-left has-icons-right">
+                    <input class="input search-input" type="search"
+                           placeholder="Search contacts…">
+                    <span class="icon is-small is-left">
+                        <i class="fa-solid fa-magnifying-glass"></i>
+                    </span>
+                    <span class="icon is-right action_clear_search">
+                        <a role="button" class="delete is-small" aria-label="Clear search"></a>
+                    </span>
+                </div>
+            </div>
+            <div class="direct-items">
+            </div>
+        </div>
+
+        <!-- Group Chats Tab -->
+        <div class="groups-content is-hidden pt-4">
+            <div class="field">
+                <label class="label">Room search.</label>
+                <p class="help">Use names, JIDs, or keywords.</p>
+                <div class="control has-icons-left has-icons-right">
+                    <input class="input search-input" type="search"
+                           placeholder="Search rooms…">
+                    <span class="icon is-small is-left">
+                        <i class="fa-solid fa-magnifying-glass"></i>
+                    </span>
+                    <span class="icon is-right action_clear_search">
+                        <a role="button" class="delete is-small" aria-label="Clear search"></a>
+                    </span>
+                </div>
+            </div>
+            <div class="is-flex is-justify-content-space-between mb-4">
+                <button class="button is-success is-light action_new_room">
+                    <span class="icon">
+                        <i class="fa-solid fa-plus"></i>
+                    </span>
+                    <span>Create New Room</span>
+                </button>
+            </div>
+
+            <!-- New Room Form (initially hidden) -->
+            <div class="box is-hidden mb-4 panel_new_room">
+                <div class="field">
+                    <label class="label">Room Name</label>
+                    <div class="control has-icons-left">
+                        <input class="input input-room-name" type="text" placeholder="Enter room name">
+                        <span class="icon is-small is-left">
+                            <i class="fa-solid fa-hashtag"></i>
+                        </span>
+                    </div>
+                </div>
+                <div class="field">
+                    <label class="label">Room Type</label>
+                    <div class="control">
+                        <div class="select is-fullwidth">
+                            <select>
+                                <option>Public Room</option>
+                                {#
+                                FIXME: only public room is supported for now, need to
+                                    check config option to use.
+                                <option>Private Room</option>
+                                #}
+                            </select>
+                        </div>
+                    </div>
+                </div>
+                <div class="field is-grouped">
+                    <div class="control">
+                        <button class="button is-success is-small action_create_room">Create Room</button>
+                    </div>
+                </div>
+                <div class="notification is-danger error-message is-hidden mt-4">
+                    <button class="delete action_hide_error_message"></button>
+                    <p></p>
+                </div>
+            </div>
+
+            <!-- Search Result -->
+            <div class="groups-items"></div>
+        </div>
+    </section>
+    <footer class="modal-card-foot is-justify-content-flex-end">
+        <button class="button action_close">Cancel</button>
+        <button class="button is-primary ml-2 action_ok" disabled>Start Chat</button>
+    </footer>
+</div>
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/chat/occupant_item.html	Fri Apr 11 21:32:05 2025 +0200
@@ -0,0 +1,10 @@
+{% import 'components/avatar.html' as avatar with context %}
+
+<div class="is-flex is-align-items-center">
+    <div class="mr-2">
+        {{ avatar.avatar(item.entity) }}
+    </div>
+    <div class="is-flex-grow-1">
+        <p class="mb-0">{{nick}}</p>
+    </div>
+</div>
--- a/sat_templates/templates/bulma/chat/reactions.html	Sat Oct 26 22:53:26 2024 +0200
+++ b/sat_templates/templates/bulma/chat/reactions.html	Fri Apr 11 21:32:05 2025 +0200
@@ -9,7 +9,7 @@
     {% for emoji, jids in reactions.items() %}
         {% set own_reaction = local_jid in jids %}
         <div
-                class="reaction box is-shadowless is-inline-flex is-align-items-center my-1 ml-0 mr-2 px-2 py-1 is-size-6 has-background-light has-border is-not-selectable {{ 'own-reaction' if own_reaction }}"
+                class="reaction is-inline-flex is-align-items-center {{ 'own-reaction' if own_reaction }}"
                 data-jids='{{jids|tojson}}'
                 >
             <span class="emoji has-text-weight-semibold">{{ emoji }}</span>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/chat/search_item.html	Fri Apr 11 21:32:05 2025 +0200
@@ -0,0 +1,20 @@
+{% import 'components/avatar.html' as avatar with context %}
+
+
+
+<div class="search-item box py-2 px-3 mb-2 is-unselectable is-clickable" {{ {'data-entity': item.entity}|xmlattr }}>
+    <div class="is-flex is-align-items-center">
+        <div class="mr-2">
+            {{ avatar.avatar(item.entity) }}
+        </div>
+        <div class="is-flex-grow-1">
+            <p class="mb-0">{{identities[item.entity].nicknames[0] if identities[item.entity].nicknames else item.entity}}</p>
+            <p class="has-text-grey is-size-7">{{item.entity}}</p>
+        </div>
+        {#
+        <div>
+            <span class="tag is-success is-light">Online</span>
+        </div>
+        #}
+    </div>
+</div>
--- a/sat_templates/templates/bulma/components/avatar.html	Sat Oct 26 22:53:26 2024 +0200
+++ b/sat_templates/templates/bulma/components/avatar.html	Fri Apr 11 21:32:05 2025 +0200
@@ -1,13 +1,13 @@
-{% macro avatar(jid, class="is-48x48") %}
+{% macro avatar(jid, class="") %}
     {%- if identities is defined -%}
         {% set identity = identities.get(jid) %}
         {%- if identity and identity.avatar.filename %}
-            <div class="is-avatar image {{class}}">
+            <div class="avatar image {{class}}">
                 <img src="/cache/common/{{identity.avatar.filename}}">
             </div>
         {% else %}
             {% set nick = identity.nicknames[0] if identity.nicknames else jid %}
-            <span class="is-avatar image {{class}} is-flex has-items-centered has-text-weight-bold is-size-3 {{class}}">{{nick|first|upper}}</span>
+            <span class="avatar {{class}} is-flex is-align-items-center is-justify-content-center has-text-weight-bold is-family-code {{class}}">{{nick|initials}}</span>
         {%- endif -%}
     {%- endif -%}
 {% endmacro %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/components/collapsible_card.html	Fri Apr 11 21:32:05 2025 +0200
@@ -0,0 +1,24 @@
+{% macro collapsible_card(title, header_color='has-background-primary-light', icon=none) %}
+    <div class="card collapsible-card">
+        <div class="card-header collapsible-header {{ header_color }}">
+            <p class="card-header-title">
+                {% if icon %}
+                    <span class="icon is-small mr-2">
+                        <i class="fas fa-{{ icon }}"></i>
+                    </span>
+                {% endif %}
+                {{ title }}
+                <span class="icon is-small ml-2 collapsible-icon">
+                    <i class="fas fa-chevron-down"></i>
+                </span>
+            </p>
+        </div>
+        <div class="collapsible-content">
+            <div class="card-content">
+                <div class="content">
+                    {{ caller() }}
+                </div>
+            </div>
+        </div>
+    </div>
+{% endmacro %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/dialogs/modal.html	Fri Apr 11 21:32:05 2025 +0200
@@ -0,0 +1,8 @@
+<div class="modal is-active">
+    <div class="modal-background"></div>
+    <div class="modal-content">
+    </div>
+    {% if closable %}
+        <button class="modal-close is-large" aria-label="close"></button>
+    {% endif %}
+</div>
--- a/sat_templates/templates/bulma/dialogs/notification.html	Sat Oct 26 22:53:26 2024 +0200
+++ b/sat_templates/templates/bulma/dialogs/notification.html	Fri Apr 11 21:32:05 2025 +0200
@@ -1,5 +1,5 @@
 <div class="main_notification columns is-centered">
-    <div class="column is-narrow has-items-centered">
+    <div class="column has-items-centered">
         {% set level_map={
             'success': 'is-success',
             'info': 'is-info',
--- a/sat_templates/templates/bulma/login/login.html	Sat Oct 26 22:53:26 2024 +0200
+++ b/sat_templates/templates/bulma/login/login.html	Fri Apr 11 21:32:05 2025 +0200
@@ -5,7 +5,7 @@
 {% block title %}{{C.APP_NAME}} login{% endblock %}
 
 {% block body %}
-{{ icon_defs('group') }}
+{{ icon_defs('users') }}
 <section class="section">
     <div class="columns is-vcentered is-centered">
         <div class="column is-narrow is-hidden-touch">
--- a/sat_templates/templates/bulma/static/chat.css	Sat Oct 26 22:53:26 2024 +0200
+++ b/sat_templates/templates/bulma/static/chat.css	Fri Apr 11 21:32:05 2025 +0200
@@ -1,66 +1,220 @@
-#chat-panel {
-    height: calc(100vh - 3.25rem);
+html, body, #body {
+    height: 100%;
+    overflow: hidden;
+}
+
+
+.chat-main {
+    /* Base colors */
+    --color-background: #ffffff;
+    --color-text: #4a4a4a;
+
+    /* Message colors */
+    --message-background: #f5f5f5;
+    --message-hover-background: #e8e8e8;
+
+    /* Reaction colors */
+    --reaction-background: #ffffff;
+    --reaction-hover-background: #f5f5f5;
+    --reaction-border: #dbdbdb;
+
+    /* User reaction colors */
+    --reaction-user-background: #eef6fc;
+    --reaction-user-hover-background: #e3f1fc;
+    --reaction-user-border: #3273dc;
+
+    /* Spacing */
+    --reaction-padding: 0.25rem 0.5rem;
+    --reaction-gap: 0.5rem;
+    --reaction-margin-top: 0.5rem;
+
+    /* Typography */
+    --reaction-font-size: 0.875rem;
+    --reaction-counter-font-size: 0.75rem;
+}
+
+[data-theme="dark"] .chat-main {
+    --color-background: #2c2c2c;
+    --color-text: #ffffff;
+
+    --message-background: #363636;
+    --message-hover-background: #404040;
+
+    --reaction-background: #2c2c2c;
+    --reaction-hover-background: #404040;
+    --reaction-border: #4a4a4a;
+
+    --reaction-user-background: #1a3b5c;
+    --reaction-user-hover-background: #234875;
+    --reaction-user-border: #3273dc;
+}
+
+.chat-container {
+    height: 100%;
+    overflow-y: auto;
+}
+
+.chat-main {
+    height: 100%;
+    display: flex;
+    flex-direction: column;
+}
+
+.chat-header {
+    border-bottom: 1px solid #dbdbdb;
+    padding: 0.5rem 1rem;
+}
+
+
+/* Sidebar toggle transitions */
+.chat-sidebar, .chat-details {
+    transition: transform 0.3s ease, max-width 0.3s ease, opacity 0.3s ease;
+    overflow-x: hidden;
+}
+
+.chat-sidebar.is-collapsed {
+    transform: translateX(-100%);
+    max-width: 0;
+    padding: 0;
+    margin: 0;
+    opacity: 0;
+}
+
+.chat-details.is-collapsed {
+    transform: translateX(100%);
+    max-width: 0;
+    padding: 0;
+    margin: 0;
+    opacity: 0;
+}
+
+.chat-main.is-expanded-left {
+    margin-left: 0;
 }
 
+.chat-main.is-expanded-right {
+    margin-right: 0;
+}
+
+.columns.is-gapless.chat-container {
+    flex-wrap: nowrap;
+}
+
+.author {
+    font-weight: bold;
+}
 #messages {
+    flex-grow: 1;
     overflow-y: auto;
+    padding: 1rem;
 }
 
+#left_panel a {
+    background-color: inherit;
+}
+
+#left_panel a:hover {
+    background-color: #444;
+}
+
+.chat-input {
+    border-top: 1px solid #dbdbdb;
+    padding: 1rem;
+}
+
+.chat-input button {
+    height: 100%;
+}
+
+.chat-message {
+    max-width: 95%;
+}
+
+.message-core {
+    margin-bottom: 1rem;
+    padding: 0.75rem;
+    border-radius: 0.5rem;
+    overflow-wrap: anywhere;
+    background-color: #f5f5f5;
+    transition: background-color 0.2s, border-left 0.2s;
+}
+.message-core:hover, .message-core.has-popup-focus {
+    background-color: #e8e8e8;
+    border-left: 4px solid #3273dc;
+}
+
+.message-actions {
+    display: none;
+}
+
+.message-core:hover .message-actions,
+.message-core.has-popup-focus .message-actions
+{
+    display: flex;
+}
+
+.reactions-container {
+    gap: var(--reaction-gap);
+    margin-top: var(--reaction-margin-top);
+}
+.reaction {
+    padding: var(--reaction-padding);
+    border-radius: 1rem;
+    background-color: var(--reaction-background);
+    font-size: var(--reaction-font-size);
+    cursor: pointer;
+    transition: all 0.2s ease;
+    border: 1px solid var(--reaction-border);
+    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+
+.reaction:hover {
+    background-color: var(--reaction-hover-background);
+}
+
+.reaction .counter {
+    margin-left: 0.25rem;
+    font-size: var(--reaction-counter-font-size);
+    color: var(--color-text);
+}
+
+.reaction.own-reaction {
+    background-color: var(--reaction-user-background);
+    border-color: var(--reaction-user-border);
+}
+
+.reaction.own-reaction:hover {
+    background-color: var(--reaction-user-hover-background);
+}
+
+
 #attachments {
     overflow-x: auto;
     white-space: nowrap;
 }
 
-#message_input {
-    resize: None;
-    max-height: 20rem;
-    min-height: 48px;
-}
-
-/* Input "edit" mode */
-#message_input.mode_edit {
-    background-color: #d1ecf1;
-}
-
-.is-chat-message {
-    transition: all 0.3s ease;
-}
-
-.is-chat-message:hover, .is-chat-message.has-popup-focus {
+.message-core:hover, .message-core.has-popup-focus {
     background-color: #f5f5f5;
     box-shadow: 0 8px 16px 0 rgba(0,0,0,0.2);
 }
 
-.media.is-chat-message.is-retracted {
-    padding: 0.5em;
-    border-left: 3px solid #ff3860; /* Reddish border for distinction */
-    background-color: #f5f5f5; /* Light grey background */
-    color: #636363; /* Slightly darker text for better readability */
+.message-body {
+    white-space: pre-wrap;
+    overflow-wrap: break-word;
 }
 
 .message-attachment {
     max-width: 20rem;
 }
-
 .status-icons {
-    padding-bottom: 0.4rem;
-}
-
-.actions {
-    position: absolute;
-    right: 5px;
-    bottom: 5px;
-    opacity: 0;
-}
-
-.is-chat-message:hover .actions, .chat-message-highlight .actions {
-    opacity: 1;
+    display: inline;
 }
 
 .action-button {
     box-sizing: border-box;
     margin: 0 0.2rem;
     user-select: none;
+    cursor: pointer;
     z-index: 10;
 }
 
@@ -70,34 +224,6 @@
     transition: box-shadow 0.3s ease;
 }
 
-.has-padding-left {
-    padding-left: 0.5em;
-}
-
-#attach_button, #send_button {
-    height: 100%;
-}
-
-/* URL previews */
-
-.url-preview-iframe-container {
-  position: relative;
-  padding-bottom: 56.25%;
-  height: 0;
-  overflow: hidden;
-  max-width: 100%;
-  background: #000;
-}
-
-.url-preview-iframe {
-  position: absolute;
-  top: 0;
-  left: 0;
-  width: 100%;
-  height: 100%;
-  border: 0;
-}
-
 /* Attachments */
 
 #attachments {
@@ -125,48 +251,3 @@
     width: 9rem;
     height: 6rem;
 }
-
-.thumbnail-container {
-    width: 150px;
-    height: 80px;
-    overflow: hidden;
-    position: relative;
-}
-.icon {
-    width: 60px;
-    height: 60px;
-}
-
-.thumbnail-image {
-    position: absolute;
-    left: 50%;
-    top: 50%;
-    height: 100%;
-    width: auto;
-    transform: translate(-50%, -50%);
-}
-
-.attachment-name {
-    text-overflow: ellipsis;
-    overflow: hidden;
-    white-space: nowrap;
-    max-width: 100%;
-    font-size: 0.8em;
-    letter-spacing: 0.02em;
-    text-align: center;
-    margin-top: auto;
-}
-
-.thumbnail-image:empty {
-    left: 50%;
-    top: 50%;
-    width: 8rem;
-    height: auto;
-    transform: translate(-50%, -50%);
-}
-
-.attachment-delete-button {
-    position: absolute;
-    top: -10px;
-    right: -10px;
-}
--- a/sat_templates/templates/bulma/static/styles.css	Sat Oct 26 22:53:26 2024 +0200
+++ b/sat_templates/templates/bulma/static/styles.css	Fri Apr 11 21:32:05 2025 +0200
@@ -1,7 +1,26 @@
+:root {
+  --selected-item-bg: deepskyblue;
+}
+
 html, body {
 
+    height: 100vh;
+    overflow: hidden;
+}
+
+body {
+    display: flex;
+    flex-direction: column;
+}
+
+#body {
+    display: flex;
+    flex-direction: column;
+    overflow: auto;
+}
+
+.is-full-height {
     height: 100%;
-    overflow: hidden;
 }
 
 .navbar-item {
@@ -12,3 +31,47 @@
 .menu-item {
     padding-top: 0.225em;
 }
+
+.avatar {
+    width: 40px;
+    height: 40px;
+    border-radius: 50%;
+    background-color: #3273dc;
+    color: #fff;
+}
+
+.avatar.image {
+    background-color: #ccc;
+    padding: 3px;
+}
+
+div.jid-search-dialog {
+    min-height: 80%;
+}
+
+div.search-item.is-selected {
+    background-color: var(--selected-item-bg);
+}
+
+
+.collapsible-content {
+    overflow: hidden;
+    max-height: 0;
+    opacity: 1;
+    transition:
+        max-height 0.3s ease-out,
+        opacity 0.2s linear;
+}
+
+.collapsible-header.collapsed .collapsible-icon {
+    transform: rotate(-90deg);
+}
+
+/* Animation */
+.collapsible-header.collapsed + .collapsible-content {
+    opacity: 0;
+}
+
+.collapsible-icon {
+    transition: transform 0.2s ease-out;
+}