|
@@ -194,6 +194,8 @@
|
|
|
|
|
|
|
|
// -------- messages --------
|
|
// -------- messages --------
|
|
|
|
|
|
|
|
|
|
+ const baseTitle = document.title;
|
|
|
|
|
+
|
|
|
function reloadAllThreads() {
|
|
function reloadAllThreads() {
|
|
|
return Promise.all($$('.thread').map(reloadThread));
|
|
return Promise.all($$('.thread').map(reloadThread));
|
|
|
}
|
|
}
|
|
@@ -202,6 +204,7 @@
|
|
|
const slug = thread.dataset.vendor;
|
|
const slug = thread.dataset.vendor;
|
|
|
const list = thread.querySelector('.thread-list');
|
|
const list = thread.querySelector('.thread-list');
|
|
|
const since = list.dataset.maxId || 0;
|
|
const since = list.dataset.maxId || 0;
|
|
|
|
|
+ const isInitialLoad = (since === '0' || since === 0);
|
|
|
return get('bin/messages_list.php', { vendor: slug, since })
|
|
return get('bin/messages_list.php', { vendor: slug, since })
|
|
|
.then(r => r.text().then(html => ({
|
|
.then(r => r.text().then(html => ({
|
|
|
html,
|
|
html,
|
|
@@ -209,9 +212,14 @@
|
|
|
})))
|
|
})))
|
|
|
.then(({ html, maxId }) => {
|
|
.then(({ html, maxId }) => {
|
|
|
if (html.trim()) {
|
|
if (html.trim()) {
|
|
|
- if (since === '0' || since === 0) list.innerHTML = '';
|
|
|
|
|
- list.insertAdjacentHTML('beforeend', html);
|
|
|
|
|
- list.scrollTop = list.scrollHeight;
|
|
|
|
|
|
|
+ if (isInitialLoad) list.innerHTML = '';
|
|
|
|
|
+ const incomingClass = PDQ.audience === 'ICG' ? 'msg-vendor' : 'msg-icg';
|
|
|
|
|
+ const counter = document.createElement('div');
|
|
|
|
|
+ counter.innerHTML = html;
|
|
|
|
|
+ const incomingCount = counter.querySelectorAll('.' + incomingClass).length;
|
|
|
|
|
+ list.insertAdjacentHTML('afterbegin', html);
|
|
|
|
|
+ list.scrollTop = 0;
|
|
|
|
|
+ if (!isInitialLoad && incomingCount > 0) markUnread(thread, incomingCount);
|
|
|
}
|
|
}
|
|
|
list.dataset.maxId = maxId;
|
|
list.dataset.maxId = maxId;
|
|
|
const empty = list.querySelector('.msg-empty');
|
|
const empty = list.querySelector('.msg-empty');
|
|
@@ -219,6 +227,44 @@
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // Flag a thread as having unread messages: a badge on its header and a
|
|
|
|
|
+ // count in the page title, since the auto-scrolled message box alone is
|
|
|
|
|
+ // easy to miss if the user isn't already looking at it.
|
|
|
|
|
+ function markUnread(thread, count) {
|
|
|
|
|
+ const current = parseInt(thread.dataset.unread || '0', 10);
|
|
|
|
|
+ thread.dataset.unread = current + count;
|
|
|
|
|
+ thread.classList.add('thread-unread');
|
|
|
|
|
+ let badge = thread.querySelector('.unread-badge');
|
|
|
|
|
+ if (!badge) {
|
|
|
|
|
+ badge = document.createElement('span');
|
|
|
|
|
+ badge.className = 'unread-badge';
|
|
|
|
|
+ thread.querySelector('h2').appendChild(badge);
|
|
|
|
|
+ }
|
|
|
|
|
+ badge.textContent = thread.dataset.unread;
|
|
|
|
|
+ updateTitle();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function clearUnread(thread) {
|
|
|
|
|
+ if (!thread.dataset.unread || thread.dataset.unread === '0') return;
|
|
|
|
|
+ thread.dataset.unread = '0';
|
|
|
|
|
+ thread.classList.remove('thread-unread');
|
|
|
|
|
+ const badge = thread.querySelector('.unread-badge');
|
|
|
|
|
+ if (badge) badge.remove();
|
|
|
|
|
+ updateTitle();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function updateTitle() {
|
|
|
|
|
+ const total = $$('.thread').reduce((sum, t) => sum + parseInt(t.dataset.unread || '0', 10), 0);
|
|
|
|
|
+ document.title = total > 0 ? '(' + total + ') ' + baseTitle : baseTitle;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function wireUnreadClearing() {
|
|
|
|
|
+ $$('.thread').forEach(thread => {
|
|
|
|
|
+ thread.addEventListener('click', () => clearUnread(thread));
|
|
|
|
|
+ thread.addEventListener('focusin', () => clearUnread(thread));
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
function wireCompose() {
|
|
function wireCompose() {
|
|
|
$$('.thread').forEach(thread => {
|
|
$$('.thread').forEach(thread => {
|
|
|
const form = thread.querySelector('.thread-compose');
|
|
const form = thread.querySelector('.thread-compose');
|
|
@@ -240,9 +286,9 @@
|
|
|
const list = thread.querySelector('.thread-list');
|
|
const list = thread.querySelector('.thread-list');
|
|
|
const empty = list.querySelector('.msg-empty');
|
|
const empty = list.querySelector('.msg-empty');
|
|
|
if (empty) empty.remove();
|
|
if (empty) empty.remove();
|
|
|
- list.insertAdjacentHTML('beforeend', res.body);
|
|
|
|
|
|
|
+ list.insertAdjacentHTML('afterbegin', res.body);
|
|
|
list.dataset.maxId = res.maxId || list.dataset.maxId;
|
|
list.dataset.maxId = res.maxId || list.dataset.maxId;
|
|
|
- list.scrollTop = list.scrollHeight;
|
|
|
|
|
|
|
+ list.scrollTop = 0;
|
|
|
})
|
|
})
|
|
|
.catch(err => {
|
|
.catch(err => {
|
|
|
input.disabled = false;
|
|
input.disabled = false;
|
|
@@ -403,6 +449,7 @@
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
|
wireAddJob();
|
|
wireAddJob();
|
|
|
wireCompose();
|
|
wireCompose();
|
|
|
|
|
+ wireUnreadClearing();
|
|
|
reloadTable();
|
|
reloadTable();
|
|
|
reloadAllThreads();
|
|
reloadAllThreads();
|
|
|
const interval = PDQ.pollMs || 180000;
|
|
const interval = PDQ.pollMs || 180000;
|