feat(v1.8.0): Twitter-style conversation modal, light theme, major CSS/JS refactor

This commit is contained in:
SamiAhmed7777
2026-06-09 18:18:55 -07:00
parent 955e443274
commit ba00d0d382
9 changed files with 1330 additions and 932 deletions
+898 -619
View File
File diff suppressed because it is too large Load Diff
+76 -51
View File
@@ -10,6 +10,12 @@
function init() {
var feeds = document.querySelectorAll('[data-beep-feed]');
Array.prototype.forEach.call(feeds, initFeed);
// Delegate on document so dynamically injected content (thread modal,
// GIF picker, toasts) is handled too.
document.addEventListener('click', onClick);
document.addEventListener('input', onInput);
document.addEventListener('keydown', onKeydown);
document.addEventListener('keydown', onEscape);
}
var feedState = { offset: 0, loading: false, hasMore: true, currentSort: 'latest' };
@@ -20,9 +26,6 @@
initSortTabs(feed);
initMedia(compose, feed);
initInfiniteScroll(feed);
feed.addEventListener('click', onClick);
feed.addEventListener('input', onInput);
feed.addEventListener('keydown', onKeydown);
}
/* ---------- Infinite scroll ---------- */
@@ -119,7 +122,7 @@
if (empty) empty.remove();
// Reload feed to get the fully rendered post (includes media, quotes, polls)
reloadFeed(feed, 'recent');
reloadFeed(feed, feedState.currentSort);
input.value = '';
clearQuotePreview(compose);
@@ -134,7 +137,7 @@
var pending = compose._beepPendingImages;
var pendingVoice = compose._beepPendingVoice;
var uploadDone = function () {
reloadFeed(feed, 'latest');
reloadFeed(feed, feedState.currentSort);
};
var tasks = [];
@@ -405,6 +408,29 @@
closeLightbox();
return;
}
// Modal backdrop click closes
if (t.matches && t.matches('[data-beep-thread-modal]')) { closeThread(); return; }
if (t.matches && t.matches('[data-beep-gif-modal]')) { closeGifPicker(); return; }
// Click anywhere else on a feed post opens the conversation
var openPost = t.closest('[data-beep-open-thread]');
if (openPost && !t.closest('a, button, label, input, textarea, audio, video, .beep-media-grid-item, .beep-gif-embed, [data-beep-reply-form]')) {
// Don't hijack text selection
var sel = window.getSelection && window.getSelection();
if (sel && sel.toString()) return;
openThread(openPost.dataset.beepOpenThread);
}
}
function onEscape(e) {
if (e.key !== 'Escape') return;
var lightbox = document.querySelector('.beep-lightbox');
if (lightbox) { closeLightbox(); return; }
var gifModal = document.querySelector('[data-beep-gif-modal]:not([hidden])');
if (gifModal) { closeGifPicker(); return; }
var threadModal = document.querySelector('[data-beep-thread-modal]:not([hidden])');
if (threadModal) closeThread();
}
function onInput(e) {
@@ -423,12 +449,20 @@
}
function onKeydown(e) {
// Enter in the GIF search box runs the search
if (e.key === 'Enter' && e.target.matches && e.target.matches('[data-beep-gif-search]')) {
e.preventDefault();
var compose = document.querySelector('[data-beep-compose]');
searchGifs(e.target.value, compose);
return;
}
// Cmd/Ctrl+Enter submits beeps and replies
if (!((e.metaKey || e.ctrlKey) && e.key === 'Enter')) return;
var input = e.target.closest('textarea');
if (!input) return;
var wrap = input.closest('.beep-compose, .beep-reply-form');
if (!wrap) return;
var btn = wrap.querySelector('button');
var btn = wrap.querySelector('[data-beep-submit], [data-beep-reply-submit]');
if (btn && !btn.disabled) btn.click();
}
@@ -486,10 +520,16 @@
form.innerHTML = '<div class="beep-pending">Reply submitted, awaiting moderation.</div>';
return;
}
// Reload the full feed to get the properly rendered reply
input.value = '';
// Inside the conversation modal: refresh just the thread.
if (btn.closest('[data-beep-thread-modal]')) {
refreshThread();
return;
}
// Otherwise reload the feed with the current sort.
var feed = btn.closest('[data-beep-feed]');
if (feed) {
reloadFeed(feed, 'latest');
reloadFeed(feed, feedState.currentSort);
}
})
.catch(function () {
@@ -786,66 +826,48 @@
}
/* ---------- Thread / Conversation modal ---------- */
function openThread(postId) {
function openThread(postId, keepScroll) {
var modal = document.querySelector('[data-beep-thread-modal]');
var content = modal && modal.querySelector('[data-beep-thread-content]');
var loading = modal && modal.querySelector('.beep-thread-loading');
var loading = modal && modal.querySelector('[data-beep-thread-loading]');
if (!modal || !content) return;
modal.hidden = false;
if (loading) loading.style.display = 'block';
content.innerHTML = '';
modal._beepThreadId = postId;
if (!keepScroll) {
if (loading) loading.hidden = false;
content.innerHTML = '';
}
document.documentElement.style.overflow = 'hidden';
fetch(config.restUrl + 'thread/' + postId, {
fetch(config.restUrl + 'thread/' + postId + '/html', {
headers: { 'X-WP-Nonce': config.nonce },
credentials: 'same-origin'
})
.then(handleResponse)
.then(function(data) {
if (loading) loading.style.display = 'none';
// data can be an array directly or { thread: [...] }
var thread = Array.isArray(data) ? data : (data.thread || []);
renderThread(thread, content);
if (loading) loading.hidden = true;
content.innerHTML = data.html || '<div class="beep-thread-error">Nothing to show.</div>';
// Sync the reply count back to the feed card
if (typeof data.reply_count !== 'undefined') {
var feedPost = document.querySelector('.beep-list [data-beep-post="' + postId + '"] [data-beep-reply-count]');
if (feedPost) feedPost.textContent = data.reply_count > 0 ? data.reply_count : '';
}
})
.catch(function() {
if (loading) loading.textContent = 'Failed to load conversation.';
if (loading) loading.hidden = true;
content.innerHTML = '<div class="beep-thread-error">Failed to load conversation.</div>';
});
}
function refreshThread() {
var modal = document.querySelector('[data-beep-thread-modal]:not([hidden])');
if (modal && modal._beepThreadId) openThread(modal._beepThreadId, true);
}
function closeThread() {
var modal = document.querySelector('[data-beep-thread-modal]');
if (modal) modal.hidden = true;
}
function renderThread(thread, container) {
if (!thread || !thread.length) {
container.innerHTML = '<p style="padding:16px;color:var(--beep-muted);text-align:center;">No replies yet.</p>';
return;
}
var html = '';
thread.forEach(function(item) {
var indent = Math.min(item.depth, 5) * 24;
var author = item.author || {};
var avatar = escapeHtml(author.avatar || 'https://www.gravatar.com/avatar/?s=48&d=mp');
var name = escapeHtml(author.name || 'Unknown');
var username = escapeHtml(author.username || '');
var content_text = escapeHtml(item.content || '');
var time_ago = escapeHtml(item.time_ago || '');
var replyClass = item.depth > 0 ? 'beep-thread-reply' : 'beep-thread-root';
html += '<div class="beep-thread-item ' + replyClass + '" style="margin-left:' + indent + 'px" data-thread-id="' + (item.id || '') + '">';
html += '<div class="beep-thread-avatar"><img src="' + avatar + '" alt="' + name + '" width="40" height="40" style="border-radius:50%;vertical-align:top;"></div>';
html += '<div class="beep-thread-body">';
html += '<div class="beep-thread-meta">';
html += '<span class="beep-thread-name">' + name + '</span>';
html += '<span class="beep-thread-username">@' + username + '</span>';
html += '<span class="beep-thread-time">' + time_ago + '</span>';
html += '</div>';
html += '<div class="beep-thread-content-text">' + content_text + '</div>';
html += '<div class="beep-thread-actions">';
html += '<span class="beep-thread-reply-count">' + (item.reply_count || 0) + ' replies</span>';
html += '<span class="beep-thread-like-count">' + (item.like_count || 0) + ' likes</span>';
html += '</div></div></div>';
});
container.innerHTML = html;
document.documentElement.style.overflow = '';
}
/* ---------- Quote posts ---------- */
@@ -1085,10 +1107,12 @@
})
.then(handleResponse)
.then(function(data) {
// Inside the conversation modal: refresh just the thread.
if (btn.closest('[data-beep-thread-modal]')) { refreshThread(); return; }
var postEl = btn.closest('[data-beep-post]');
if (postEl) {
var list = postEl.closest('[data-beep-list]');
if (list) reloadFeed(list.closest('[data-beep-feed]'), 'latest');
if (list) reloadFeed(list.closest('[data-beep-feed]'), feedState.currentSort);
}
})
.catch(function() {
@@ -1124,3 +1148,4 @@
return res.json();
}
})();
/* Beep frontend — end */