Files
python-tdd/src/templates/apps/billboard/_partials/_bud_panel.html
Disco DeDisco db10f345e4
Some checks failed
ci/woodpecker/push/pyswiss Pipeline was successful
ci/woodpecker/push/main Pipeline failed
display_name → at_handle for every user-rendering point around the recent-activity surfaces: scroll.html actor <strong>, Most Recent Scroll applet actor <strong>, My Games row body actor prefix, My Scrolls row body actor prefix, My Buds page bud-name span, navbar identity, _bud_panel.html data-sharer-name (consumed by the dynamic post-line-author append on share success) — at_handle was always the right filter for these slots: it produces @<username> when the user has set one, falling back to the truncated email (which already carries an @) so we don't double-prefix; the _my_buds_applet_item.html row was already on at_handle from the 3-col sprint, so this commit just brings the rest of the surfaces in line; _navbar.html swap also drops the literal @ that prefixed {{ user|display_name }} — that literal predated at_handle + worked for users w. usernames (gave @disco) but produced @<email>@<domain> for users w. no username yet; navbar wait_to_be_logged_in(email) FT helper keeps working since the email still appears as a substring whether rendered as @disco@test.io (old, no username) or disco@test.io (new); _bud_add_panel.html's client-side _appendBudEntry JS gains an inline at_handle mirror — display.indexOf('@') >= 0 ? display : '@' + display — since the server's add_bud response packs username or email under the username key (semantic mismatch w. the key name but stable) so the JS has to detect the email case itself; test_bill_my_buds.py two .bud-name text assertions ("alice""@alice") updated for the new prefix; 931 ITs + targeted FT regression on test_bill_my_buds + test_core_navbar + test_core_login green
2026-05-13 01:09:43 -04:00

119 lines
5.4 KiB
HTML

{% load static %}
{% load lyric_extras %}
{# ─────────────────────────────────────────────────────────────────────── #}
{# _bud_panel.html — bottom-left handshake btn + slide-out recipient #}
{# field for the share-post async flow. Skeleton (open/close/POST + auto- #}
{# complete) owned by bud-btn.js; this partial wires the share-post #}
{# success callback: appends a Line to #id_post_table, fires Brief.show- #}
{# Banner, and updates the .post-header shared-with prose. #}
{# Included by post.html only. #}
{# #}
{# Spec lives in functional_tests/test_bud_btn.py. #}
{# ─────────────────────────────────────────────────────────────────────── #}
<button id="id_bud_btn" type="button" aria-label="Share with a bud">
<i class="fa-solid fa-handshake"></i>
</button>
<div id="id_bud_panel"
data-sharer-name="{% if request.user.is_authenticated %}{{ request.user|at_handle }}{% endif %}">
<input id="id_recipient"
name="recipient"
type="text"
placeholder="friend@example.com or username"
autocomplete="off">
<button id="id_bud_ok" type="button" class="btn btn-confirm">OK</button>
</div>
{# Autocomplete suggestions — sibling of #id_bud_panel because the panel #}
{# has overflow:hidden for its scaleX slide animation. #}
<div id="id_bud_suggestions" class="bud-suggestions" hidden></div>
<script src="{% static 'apps/billboard/bud-autocomplete.js' %}"></script>
<script src="{% static 'apps/billboard/bud-btn.js' %}"></script>
<script>
(function () {
'use strict';
var panel = document.getElementById('id_bud_panel');
// Author = the sharer (rendered server-side as data-sharer-name) so the
// appended Line matches the post-refresh state, where the persisted
// Line.author is request.user.
function _appendLine(text) {
var list = document.getElementById('id_post_table');
if (!list) return;
var li = document.createElement('li');
li.className = 'post-line';
var author = document.createElement('span');
author.className = 'post-line-author';
author.textContent = panel.dataset.sharerName || '';
var body = document.createElement('span');
body.className = 'post-line-text';
body.textContent = text;
var time = document.createElement('time');
time.className = 'post-line-time';
var now = new Date();
time.dateTime = now.toISOString();
time.textContent = now.toLocaleTimeString([], {hour: 'numeric', minute: '2-digit'});
li.appendChild(author);
li.appendChild(body);
li.appendChild(time);
var buffer = list.querySelector('.post-line-buffer');
if (buffer) list.insertBefore(li, buffer);
else list.appendChild(li);
}
// The shared-with header lives outside #id_bud_panel — two <p> siblings
// under .post-header. State transitions:
// 0 → 1+ recipients : "just me, X" → "shared between {chip}" + "& me, X"
// ≥1 → +1 recipients: append chip + ", " separator before existing.
// `userId` is stamped onto the chip as data-user-id so a later duplicate-
// share attempt can highlight this same element via .bud-duplicate-flash.
function _appendRecipientChip(displayName, userId) {
if (!displayName) return;
var header = document.querySelector('.post-page .post-header');
if (!header) return;
var existingRecipients = header.querySelector('.post-shared-recipients');
var selfLine = header.querySelector('.post-shared-self');
var chip = document.createElement('span');
chip.className = 'post-recipient';
chip.textContent = displayName;
if (userId) chip.dataset.userId = userId;
if (existingRecipients) {
existingRecipients.appendChild(document.createTextNode(', '));
existingRecipients.appendChild(chip);
return;
}
var recipientsLine = document.createElement('p');
recipientsLine.className = 'post-shared-recipients';
recipientsLine.appendChild(document.createTextNode('shared between '));
recipientsLine.appendChild(chip);
if (selfLine) {
header.insertBefore(recipientsLine, selfLine);
selfLine.textContent = selfLine.textContent.replace(/^just me,/, '& me,');
} else {
header.appendChild(recipientsLine);
}
}
bindBudBtn({
submitUrl: '{% url "billboard:share_post" post.id %}',
autocompleteUrl: '{% url "billboard:search_buds" %}',
onSuccess: function (data) {
if (data.line_text) _appendLine(data.line_text);
if (window.Brief && data.brief) Brief.showBanner(data.brief);
if (data.recipient_display) {
_appendRecipientChip(data.recipient_display, data.recipient_user_id);
}
},
duplicateTargetSelector: function (data) {
return '.post-recipient[data-user-id="' + data.recipient_user_id + '"]';
},
});
}());
</script>