Rich chat messages (reactions, replies, files, images); RSS poll countdown; Speakers' Corner directory page updates
- Chat: extract reactions, quoted replies, file/image data in _normalize_item
- Chat: render emoji reaction pills, reply-quote blocks, inline image previews, file blocks with Accept/Download
- Chat: reply UI (hover → set reply → preview bar above compose → send with quotedItemId)
- Chat: emoji picker strip (6 quick-react emojis) on message hover
- Chat: POST /react and POST /file/{id}/receive and GET /file/{id}/download endpoints
- Chat: file decryption via core.chat_read_file (native libsimplex FFI), served with correct MIME type
- List: RSS bot cards show live next-poll countdown (ticks every second via status API poll_next field)
- Directory: rename SimpleXXX → Speakers' Corner Online Directory throughout
- Directory: add hero banner image, About page link, QR popout, title hyperlink
- Directory: new about.html — Online Safety Act, Digital ID, 65k arrests stat, community rules
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -105,6 +105,12 @@
|
||||
onclick="this.textContent='Stopping…'">Stop</button>
|
||||
</div>
|
||||
</div>
|
||||
{% if tab == 'rss-bots' %}
|
||||
<div style="margin-top:8px;font-size:12px;color:var(--muted);">
|
||||
<i class="fa-solid fa-clock"></i>
|
||||
<span id="poll-{{ p.id }}">{{ '—' if not p.running else 'Loading…' }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if p.address %}
|
||||
<div onclick="event.stopPropagation()" style="margin-top:10px;">{{ ui.linkbox(p.address, 'p' ~ p.id) }}</div>
|
||||
{% endif %}
|
||||
@@ -304,13 +310,42 @@
|
||||
</dialog>
|
||||
|
||||
<script>
|
||||
const _pollNextMap = {}; // profile_id → poll_next epoch (seconds)
|
||||
|
||||
function fmtCountdown(secs) {
|
||||
if (secs <= 0) return 'polling now…';
|
||||
const h = Math.floor(secs / 3600);
|
||||
const m = Math.floor((secs % 3600) / 60);
|
||||
const s = Math.floor(secs % 60);
|
||||
if (h > 0) return `in ${h}h ${m}m`;
|
||||
if (m > 0) return `in ${m}m ${s}s`;
|
||||
return `in ${s}s`;
|
||||
}
|
||||
|
||||
function tickPolls() {
|
||||
const now = Date.now() / 1000;
|
||||
for (const [id, pollNext] of Object.entries(_pollNextMap)) {
|
||||
const el = document.getElementById('poll-' + id);
|
||||
if (!el) continue;
|
||||
el.textContent = pollNext > 0 ? fmtCountdown(pollNext - now) : '—';
|
||||
}
|
||||
}
|
||||
setInterval(tickPolls, 1000);
|
||||
|
||||
function updateStatus(id, event) {
|
||||
try {
|
||||
const data = JSON.parse(event.detail.xhr.responseText);
|
||||
const badge = document.getElementById('status-' + id);
|
||||
if (!badge) return;
|
||||
badge.textContent = data.running ? 'running' : 'stopped';
|
||||
badge.className = 'badge ' + (data.running ? 'badge-green' : 'badge-red');
|
||||
if (badge) {
|
||||
badge.textContent = data.running ? 'running' : 'stopped';
|
||||
badge.className = 'badge ' + (data.running ? 'badge-green' : 'badge-red');
|
||||
}
|
||||
if (data.poll_next !== undefined) {
|
||||
_pollNextMap[id] = data.running ? data.poll_next : 0;
|
||||
const el = document.getElementById('poll-' + id);
|
||||
if (el) el.textContent = data.running && data.poll_next > 0
|
||||
? fmtCountdown(data.poll_next - Date.now() / 1000) : '—';
|
||||
}
|
||||
} catch(e) {}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user