feat(v1.8.0): Twitter-style conversation modal, light theme, major CSS/JS refactor
This commit is contained in:
+898
-619
File diff suppressed because it is too large
Load Diff
+76
-51
@@ -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 */
|
||||
|
||||
Reference in New Issue
Block a user