Ignore:
File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/views/pages/messages.ejs

    r439f095 re64a49b  
    22  <%- include('../partials/fedi-tabs', { active: 'berichten' }) %>
    33  <h1 class="msg-title"><%= t('msg.title') %></h1>
    4   <% if (typeof success !== 'undefined' && success) { %><div class="alert alert-success"><%= success === 'guardian_accepted' ? t('msg.guard_accepted') : (success === 'guardian_rejected' ? t('msg.guard_rejected') : (success === 'wave_sent' ? t('msg.wave_sent') : success)) %></div><% } %>
    5   <% if (typeof error !== 'undefined' && error) { %><div class="alert alert-error"><%= error === 'guardianship' ? t('msg.guard_failed') : error %></div><% } %>
     4  <% if (typeof success !== 'undefined' && success) { %><div class="alert alert-success"><%= success === 'guardian_accepted' ? t('msg.guard_accepted') : (success === 'guardian_rejected' ? t('msg.guard_rejected') : (success === 'wave_sent' ? t('msg.wave_sent') : (success === 'reply_sent' ? t('msg.reply_sent') : success))) %></div><% } %>
     5  <% if (typeof error !== 'undefined' && error) { %><div class="alert alert-error"><%= error === 'guardianship' ? t('msg.guard_failed') : (error === 'reply_empty' ? t('msg.reply_empty') : ((error === 'reply_failed' || error === 'reply_target') ? t('msg.reply_failed') : error)) %></div><% } %>
    66
    77  <%# FEP-633c: a pending guardianship offer is a special message: the kid
     
    2525
    2626  <div class="msg-filters" role="tablist" aria-label="<%= t('msg.title') %>">
     27    <%# Vier chips, geen zes: Berichten, Gesprekken en Verzonden gingen op in
     28        een enkele Gesprekken-view, waarin verzonden en ontvangen in dezelfde
     29        draad staan. Activiteit en Moderatie blijven wat ze waren. %>
    2730    <button type="button" class="msg-chip is-on" data-show="all"><%= t('msg.filter_all') %></button>
    28     <button type="button" class="msg-chip" data-show="msgs"><%= t('msg.filter_msgs') %></button>
    2931    <button type="button" class="msg-chip" data-show="conv"><%= t('msg.filter_conv') %></button>
    3032    <button type="button" class="msg-chip" data-show="act"><%= t('msg.filter_act') %></button>
    3133    <button type="button" class="msg-chip" data-show="mod"><%= t('msg.filter_mod') %></button>
    32     <button type="button" class="msg-chip" data-show="sent"><%= t('msg.filter_sent') %></button>
    3334  </div>
    3435  <div class="msg-search">
     
    5960</div>
    6061
    61 <script>
    62 (function () {
    63   if (window.__msgWired) return; window.__msgWired = true;
    64 
    65   // Filtering: kind chip AND free-text search, combined in JS (search over the
    66   // sender and the rendered message text). Empty search + "all" = show all.
    67   var list = document.querySelector('.msg-list');
    68   var noMatch = document.querySelector('.msg-nomatch');
    69   var q = document.getElementById('msg-q');
    70   var kind = 'all';
    71   var items = [];
    72   function indexItem(li) {
    73     // Index once: sender + message body + linked post title + poll text.
    74     var body = li.querySelector('.msg-content');
    75     var post = li.querySelector('.msg-post');
    76     var poll = li.querySelector('.msg-poll');
    77     li._search = ((li.getAttribute('data-who') || '') + ' ' +
    78       (body ? body.textContent : '') + ' ' + (post ? post.textContent : '') + ' ' +
    79       (poll ? poll.textContent : '')).toLowerCase();
    80   }
    81   // Re-collect + index; called on load and after each "Load more" append so new
    82   // items join the filter/search (and inherit the active chip via apply()).
    83   function reindex() {
    84     items = list ? Array.prototype.slice.call(list.querySelectorAll('.msg-item')) : [];
    85     items.forEach(function (li) { if (!li._search) indexItem(li); });
    86   }
    87   reindex();
    88   function apply() {
    89     if (!list) return;
    90     var term = (q && q.value || '').trim().toLowerCase();
    91     var shown = 0;
    92     items.forEach(function (li) {
    93       var ok = (kind === 'all' || li.getAttribute('data-kind') === kind) &&
    94         (!term || li._search.indexOf(term) !== -1);
    95       li.style.display = ok ? '' : 'none';
    96       if (ok) shown++;
    97     });
    98     if (noMatch) noMatch.hidden = shown !== 0;
    99   }
    100   document.addEventListener('click', function (e) {
    101     var chip = e.target.closest('.msg-chip'); if (!chip) return;
    102     kind = chip.getAttribute('data-show');
    103     document.querySelectorAll('.msg-chip').forEach(function (c) { c.classList.toggle('is-on', c === chip); });
    104     apply();
    105   });
    106   if (q) q.addEventListener('input', apply);
    107   // After a "Load more" append (htmx), index the new rows and re-apply the filter.
    108   document.body.addEventListener('htmx:afterSettle', function (e) {
    109     if (e.target && e.target.id === 'msg-list') { reindex(); apply(); }
    110   });
    111 
    112   var a = document.getElementById('fedi-bm-btn');
    113   if (a) {
    114     a.setAttribute('href', "javascript:void(window.open('" + location.origin + "/authorize_interaction?uri='+encodeURIComponent(window.location.href)))");
    115     a.addEventListener('click', function (e) { e.preventDefault(); a.classList.add('nudge'); setTimeout(function(){ a.classList.remove('nudge'); }, 600); });
    116   }
    117 })();
    118 </script>
     62<%# Filteren, zoeken en het in-/uitklappen zitten in assets/js/mod/messages.js.
     63    Inline script hier wordt door de CSP geweigerd zodra je deze pagina via een
     64    link BINNEN de site opent -- zie shaer-0i6. De shell laadt de module op
     65    body[data-js]; deze pagina vraagt erom met pageJs (routes/posts.js). %>
    11966
    12067<style>
     
    13885
    13986  .msg-list { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: .55rem; }
     87
    14088
    14189  .msg-item { display: flex; gap: .8rem; padding: .85rem .95rem; border-radius: 14px; align-items: flex-start;
     
    14896    background: color-mix(in srgb, var(--accent, #06c) 4%, transparent); }
    14997  .msg-sent { border-left: 3px solid color-mix(in srgb, var(--accent, #06c) 55%, transparent); }
     98
     99  /* ── Gesprekken ──────────────────────────────────────────────────────
     100     NA .msg-item, en dat is geen smaak: .msg-item zet display:flex, en bij
     101     gelijke specificiteit wint de laatste regel. Stond dit ervoor, dan kwam de
     102     draadkop NAAST de bubbels te staan in plaats van erboven. */
     103  .msg-thread { display: block; }
     104  .msg-thread-head { display: flex; align-items: center; gap: .4rem; flex-wrap: wrap; margin-bottom: .4rem; }
     105  /* De tegenpartij is een labeltje bij het gesprek, geen kop erboven: een
     106     kleine verzonken uitsparing, zodat de bubbels eronder de ruimte krijgen.
     107     Het aantal zit in dezelfde uitsparing in plaats van ernaast. */
     108  /* Ook de knop die het gesprek in- en uitklapt. Een button erft geen font en
     109     geen kleur, dus die staan hier expliciet; zonder dat wordt het ineens
     110     systeemblauw in 13px. */
     111  .msg-thread-who { display: inline-flex; align-items: center; gap: .3rem; min-width: 0;
     112    font: inherit; font-size: .78em; line-height: 1.5; font-weight: 500;
     113    padding: .05rem .5rem; border-radius: 999px; border: 0; cursor: pointer;
     114    color: color-mix(in srgb, var(--ink, #000) 62%, transparent);
     115    background: color-mix(in srgb, var(--ink, #000) 5%, transparent);
     116    box-shadow: inset 0 1px 2px color-mix(in srgb, var(--ink, #000) 9%, transparent);
     117    max-width: 100%; overflow: hidden; white-space: nowrap;
     118    /* Ruimer aanraakvlak dan de tekst: een chip van 18px hoog is op een telefoon
     119       niet te raken. De uitsparing blijft klein, het doel wordt groter. */
     120    min-height: 32px; }
     121  .msg-thread-who:hover { background: color-mix(in srgb, var(--ink, #000) 9%, transparent); }
     122  .msg-thread-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
     123  .msg-thread-chevron { width: 12px; height: 12px; flex: none; opacity: .55;
     124    transition: transform .18s ease; }
     125  /* Ingeklapt: de bubbels en het antwoordveld verdwijnen, de uitsparing met de
     126     naam en het AANTAL blijft staan -- dat aantal is juist ingeklapt het meest
     127     waard. */
     128  .msg-thread.is-collapsed .msg-thread-msgs,
     129  .msg-thread.is-collapsed .msg-thread-foot { display: none; }
     130  /* Tijdens het zoeken wint de treffer van de dichtgeklapte stand. */
     131  .msg-thread.is-collapsed.is-search-open .msg-thread-msgs { display: flex; }
     132  .msg-thread.is-collapsed.is-search-open .msg-thread-foot { display: flex; }
     133  .msg-thread.is-collapsed .msg-thread-chevron { transform: rotate(-90deg); }
     134  .msg-thread.is-collapsed .msg-thread-head { margin-bottom: 0; }
     135  @media (prefers-reduced-motion: reduce) { .msg-thread-chevron { transition: none; } }
     136  .msg-thread-count { font-variant-numeric: tabular-nums; opacity: .75; flex: 0 0 auto;
     137    padding-inline-start: .3rem; border-inline-start: 1px solid color-mix(in srgb, var(--ink, #000) 14%, transparent); }
     138  .msg-thread-post { display: inline-flex; align-items: center; gap: .3rem; font-size: .85em;
     139    margin-inline-start: auto; min-width: 0; max-width: 100%;
     140    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
     141  .msg-thread-post svg { width: 13px; height: 13px; flex: none; }
     142  .msg-thread-msgs { list-style: none; margin: 0; padding: 0 0 0 .7rem; display: flex; flex-direction: column; gap: .5rem;
     143    border-inline-start: 2px solid color-mix(in srgb, var(--ink, #000) 10%, transparent); }
     144  .msg-sub { display: flex; gap: .6rem; align-items: flex-start; min-width: 0; }
     145  .msg-sub > .msg-body { min-width: 0; }
     146  /* Jouw eigen bijdrage onderscheidt zich met een tint en de accentlijn die
     147     .msg-sent al had -- niet met row-reverse: dat duwde de avatar buiten de
     148     kaart zodra de regel smaller werd dan zijn inhoud. */
     149  .msg-sub.msg-sent { border-radius: 10px; padding: .3rem .45rem;
     150    background: color-mix(in srgb, var(--accent, #06c) 5%, transparent); }
     151  /* Een lange naam of tijd mag de bubbel niet uit de kaart duwen. */
     152  .msg-sub .msg-line { flex-wrap: wrap; }
     153
     154  /* De voet van een gesprek: antwoorden en zwaaien naast elkaar, ingeklapt.
     155     Een open editor onder elk gesprek maakt de lijst weer onleesbaar -- precies
     156     wat deze weergave moest oplossen. */
     157  .msg-thread-foot { display: flex; align-items: flex-start; gap: .5rem; margin-top: .5rem; flex-wrap: wrap; }
     158  .msg-reply { flex: 1 1 12rem; min-width: 0; }
     159  .msg-reply > summary { cursor: pointer; font-size: .82rem; color: var(--ink-soft, #888); }
     160  .msg-reply[open] > summary { margin-bottom: .4rem; }
     161  .msg-reply-form { min-width: 0; }
     162  .msg-wave button { line-height: 1.2; }
     163
     164  @media (max-width: 560px) {
     165    /* Op een telefoon is 40px avatar per bubbel puur verlies: de naam staat er
     166       al naast, en de inhoud heeft de breedte harder nodig. */
     167    .msg-sub > .msg-av { display: none; }
     168    .msg-thread-msgs { padding-inline-start: .55rem; }
     169    .msg-thread-post { margin-inline-start: 0; }
     170    .msg-item { padding: .7rem .6rem; gap: .6rem; }
     171  }
    150172
    151173  .msg-av { position: relative; flex: 0 0 40px; width: 40px; height: 40px; border-radius: 50%;
Note: See TracChangeset for help on using the changeset viewer.