137 lines
5.2 KiB
HTML
137 lines
5.2 KiB
HTML
|
|
{% load static %}
|
||
|
|
{# ─────────────────────────────────────────────────────────────────────── #}
|
||
|
|
{# _buddy_panel.html — bottom-left handshake btn + slide-out recipient #}
|
||
|
|
{# field for the share-post async flow. Mirror of #id_kit_btn (bottom- #}
|
||
|
|
{# right). Included by post.html only. #}
|
||
|
|
{# #}
|
||
|
|
{# Spec lives in functional_tests/test_buddy_btn.py — write it red-first. #}
|
||
|
|
{# Run: #}
|
||
|
|
{# python src/manage.py test functional_tests.test_buddy_btn #}
|
||
|
|
{# ─────────────────────────────────────────────────────────────────────── #}
|
||
|
|
|
||
|
|
<button id="id_buddy_btn" type="button" aria-label="Share with a buddy">
|
||
|
|
<i class="fa-solid fa-handshake"></i>
|
||
|
|
</button>
|
||
|
|
|
||
|
|
<div id="id_buddy_panel" data-share-url="{% url 'billboard:share_post' post.id %}">
|
||
|
|
<input id="id_recipient"
|
||
|
|
name="recipient"
|
||
|
|
type="email"
|
||
|
|
placeholder="friend@example.com"
|
||
|
|
autocomplete="off">
|
||
|
|
<button id="id_buddy_ok" type="button" class="btn btn-confirm">OK</button>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<script>
|
||
|
|
(function () {
|
||
|
|
'use strict';
|
||
|
|
|
||
|
|
var btn = document.getElementById('id_buddy_btn');
|
||
|
|
var panel = document.getElementById('id_buddy_panel');
|
||
|
|
var input = document.getElementById('id_recipient');
|
||
|
|
var ok = document.getElementById('id_buddy_ok');
|
||
|
|
var html = document.documentElement;
|
||
|
|
if (!btn || !panel || !input || !ok) return;
|
||
|
|
|
||
|
|
function _csrf() {
|
||
|
|
var m = document.cookie.match(/csrftoken=([^;]+)/);
|
||
|
|
return m ? m[1] : '';
|
||
|
|
}
|
||
|
|
|
||
|
|
function _open() {
|
||
|
|
html.classList.add('buddy-open');
|
||
|
|
btn.classList.add('active');
|
||
|
|
// small delay before focus so the slide-out animation can play
|
||
|
|
setTimeout(function () { input.focus(); }, 60);
|
||
|
|
}
|
||
|
|
|
||
|
|
function _close(opts) {
|
||
|
|
opts = opts || {};
|
||
|
|
html.classList.remove('buddy-open');
|
||
|
|
btn.classList.remove('active');
|
||
|
|
if (opts.clear !== false) input.value = '';
|
||
|
|
}
|
||
|
|
|
||
|
|
btn.addEventListener('click', function () {
|
||
|
|
if (html.classList.contains('buddy-open')) {
|
||
|
|
_close();
|
||
|
|
} else {
|
||
|
|
_open();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// Escape closes the panel, clears the field
|
||
|
|
document.addEventListener('keydown', function (e) {
|
||
|
|
if (e.key === 'Escape' && html.classList.contains('buddy-open')) _close();
|
||
|
|
});
|
||
|
|
|
||
|
|
// Click-outside dismiss — same pattern as game-kit.js
|
||
|
|
document.addEventListener('click', function (e) {
|
||
|
|
if (!html.classList.contains('buddy-open')) return;
|
||
|
|
if (panel.contains(e.target)) return;
|
||
|
|
if (e.target === btn || btn.contains(e.target)) return;
|
||
|
|
_close();
|
||
|
|
});
|
||
|
|
|
||
|
|
// OK → POST share-post async; reuses the C3.b response handling so the
|
||
|
|
// recipient chip + brief banner + post-table line append all light up.
|
||
|
|
function _appendLine(text) {
|
||
|
|
var table = document.getElementById('id_post_table');
|
||
|
|
if (!table) return;
|
||
|
|
var n = table.querySelectorAll('tr').length + 1;
|
||
|
|
var tr = document.createElement('tr');
|
||
|
|
var td = document.createElement('td');
|
||
|
|
td.textContent = n + '. ' + text;
|
||
|
|
tr.appendChild(td);
|
||
|
|
table.appendChild(tr);
|
||
|
|
}
|
||
|
|
|
||
|
|
function _appendRecipientChip(displayName) {
|
||
|
|
var box = document.getElementById('id_post_recipients');
|
||
|
|
if (!box || !displayName) return;
|
||
|
|
var span = document.createElement('span');
|
||
|
|
span.className = 'post-recipient';
|
||
|
|
span.textContent = displayName;
|
||
|
|
box.appendChild(document.createTextNode(' '));
|
||
|
|
box.appendChild(span);
|
||
|
|
}
|
||
|
|
|
||
|
|
ok.addEventListener('click', function () {
|
||
|
|
var email = input.value.trim();
|
||
|
|
if (!email) return;
|
||
|
|
|
||
|
|
var fd = new FormData();
|
||
|
|
fd.set('recipient', email);
|
||
|
|
|
||
|
|
fetch(panel.dataset.shareUrl, {
|
||
|
|
method: 'POST',
|
||
|
|
credentials: 'same-origin',
|
||
|
|
headers: {
|
||
|
|
'Accept': 'application/json',
|
||
|
|
'X-CSRFToken': _csrf(),
|
||
|
|
},
|
||
|
|
body: fd,
|
||
|
|
})
|
||
|
|
.then(function (r) { return r.ok ? r.json() : Promise.reject(r.status); })
|
||
|
|
.then(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);
|
||
|
|
_close({ clear: true });
|
||
|
|
})
|
||
|
|
.catch(function () {
|
||
|
|
// swallow — privacy-safe response shape means even an
|
||
|
|
// unregistered recipient is a 200; only network/5xx land here.
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
// Submit-on-Enter inside the input mirrors clicking OK
|
||
|
|
input.addEventListener('keydown', function (e) {
|
||
|
|
if (e.key === 'Enter') {
|
||
|
|
e.preventDefault();
|
||
|
|
ok.click();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}());
|
||
|
|
</script>
|