Add support for channel read status

This commit is contained in:
Midgard 2022-06-08 23:24:59 +02:00
parent 38b4e49dfb
commit 0cc0cb1c13
Signed by: midgard
GPG key ID: 511C112F1331BBB4
6 changed files with 125 additions and 7 deletions

View file

@ -78,6 +78,7 @@ h1 img {
border-right: 1px solid #aaa; border-right: 1px solid #aaa;
} }
#channel_list a { #channel_list a {
position: relative;
text-overflow: ellipsis; text-overflow: ellipsis;
overflow: hidden; overflow: hidden;
white-space: nowrap; white-space: nowrap;
@ -105,6 +106,23 @@ h1 img {
color: #888; color: #888;
margin: 0 1px; margin: 0 1px;
} }
#channel_list .unread {
font-weight: bold;
}
#channel_list a .msg_count_gem {
display: none;
position: absolute;
background-color: #444;
border-radius: 100%;
color: #ccc;
font-weight: normal;
font-size: 50%;
padding: 0 0.6em;
right: 0.5em;
top: 50%;
transform: translateY(-50%);
vertical-align: middle;
}
ul#channel_list, ul#server_selection_list { ul#channel_list, ul#server_selection_list {
list-style: none; list-style: none;

View file

@ -120,9 +120,9 @@ function channelNameElements(team, channel) {
function switchToChannel(client, team, channel) { function switchToChannel(client, team, channel) {
for (let el of byId("channel_list").childNodes) { for (let el of byId("channel_list").childNodes) {
if (el.dataset["server"] == client.endpoint && el.dataset["id"] == channel.id) { if (el.dataset["server"] == client.endpoint && el.dataset["id"] == channel.id) {
el.className = "active"; addClass(el, "active");
} else { } else {
el.className = ""; removeClass(el, "active");
} }
} }
@ -132,6 +132,14 @@ function switchToChannel(client, team, channel) {
console.info(`Got channel contents of ${channel.id} (${channel.name})`); console.info(`Got channel contents of ${channel.id} (${channel.name})`);
response.order.reverse(); response.order.reverse();
populateChannelContents(client, channel, response); populateChannelContents(client, channel, response);
markChannelAsRead(client, channel)
.then(_ => {
pubsub.publish("CHANNEL_READ", {
endpoint: client.endpoint,
channel_id: channel.id
});
});
}) })
.catch(error => { .catch(error => {
console.error(error); console.error(error);
@ -156,6 +164,10 @@ function sendMessage(endpoint, channel_id, message) {
//}); //});
} }
function markChannelAsRead(client, channel) {
return client.markChannelRead(channel.id);
}
function checkKeyPress(event) { function checkKeyPress(event) {
// Battle tested for many years in several browsers // Battle tested for many years in several browsers
if ((event.keyCode === event.DOM_VK_RETURN || event.keyCode === 13 || event.keyCode === 10 || event.key === "Enter" || event.keyIdentifier === "U+000A") && !event.shiftKey && !event.ctrlKey) { if ((event.keyCode === event.DOM_VK_RETURN || event.keyCode === 13 || event.keyCode === 10 || event.key === "Enter" || event.keyIdentifier === "U+000A") && !event.shiftKey && !event.ctrlKey) {

View file

@ -137,6 +137,17 @@ class MattermostClient {
}); });
} }
getUnread(team_id) {
return this.get(`/users/me/teams/${team_id}/channels/members`);
}
markChannelRead(channel_id) {
assertIsMattermostId(channel_id);
return this.post(`/channels/members/me/view`, {
channel_id: channel_id
});
}
writePost(channel_id, message) { writePost(channel_id, message) {
return this.post("/posts", { return this.post("/posts", {
"channel_id": channel_id, "channel_id": channel_id,

View file

@ -1,7 +1,7 @@
const pubsub = (function() { "use strict"; const pubsub = (function() { "use strict";
const topics = [ const topics = [
"MESSAGES_NEW", "MESSAGES_NEW", // {endpoint, channel_id, create_at, user_id, message}
"MESSAGES_CHANGED", "MESSAGES_CHANGED",
"CHANNELS_NEW", "CHANNELS_NEW",
"CHANNELS_CHANGED", "CHANNELS_CHANGED",
@ -9,6 +9,7 @@ const topics = [
"USERS_CHANGED", "USERS_CHANGED",
"CHANNEL_MEMBERS_NEW", "CHANNEL_MEMBERS_NEW",
"CHANNEL_MEMBERS_REMOVED", "CHANNEL_MEMBERS_REMOVED",
"CHANNEL_READ", // {endpoint, channel_id}
]; ];
let subscribers = Object.create(null); let subscribers = Object.create(null);

View file

@ -35,6 +35,14 @@ function byId(id, nullOk=false) {
} }
function removeClass(el, className) {
el.className = el.className.split(" ").filter(x => x !== className).join(" ");
}
function addClass(el, className) {
el.className = el.className.split(" ").filter(x => x !== className).join(" ") + ` ${className}`;
}
/** /**
* Wrap a function so that it receives `this` as first argument * Wrap a function so that it receives `this` as first argument
*/ */
@ -55,6 +63,15 @@ function extend(obj1, obj2) {
} }
function arrayToHashmap(array, key) {
let result = Object.create(null);
for (let x of array) {
result[x[key]] = x;
}
return result;
}
function padLeft(input, width, padding=" ") { function padLeft(input, width, padding=" ") {
text = input + ""; text = input + "";
while (text.length < width) { while (text.length < width) {

View file

@ -25,8 +25,17 @@ function populateChannelList() {
for (let team of teams) { for (let team of teams) {
let nodes = []; let nodes = [];
const channels = await client.myChannels(team.id); const [channels, unreadsList] = await Promise.all([
client.myChannels(team.id),
client.getUnread(team.id)
]);
const unreads = arrayToHashmap(unreadsList, "channel_id");
for (let channel of channels) { for (let channel of channels) {
const chanUnreads = channel["total_msg_count"] - unreads[channel.id]["msg_count"];
const chanMentions = unreads[channel.id]["mention_count"];
const li = document.createElement("li"); const li = document.createElement("li");
const a = document.createElement("a"); const a = document.createElement("a");
a.href = "javascript:void(0)"; a.href = "javascript:void(0)";
@ -35,11 +44,23 @@ function populateChannelList() {
a.title = titleAndElements[0]; a.title = titleAndElements[0];
a.append(...titleAndElements[1]); a.append(...titleAndElements[1]);
a.append(document.createTextNode(" "));
const msgCountGem = document.createElement("span");
msgCountGem.className = "msg_count_gem";
msgCountGem.innerText = chanMentions;
if (chanMentions > 0) msgCountGem.style.display = "inline-block";
a.append(msgCountGem);
a.addEventListener("click", () => switchToChannel(client, team, channel)); a.addEventListener("click", () => switchToChannel(client, team, channel));
li.appendChild(a); li.appendChild(a);
li.dataset["id"] = channel.id; li.dataset["id"] = channel.id;
li.dataset["server"] = client.endpoint; li.dataset["server"] = client.endpoint;
li.dataset["unreads"] = chanUnreads;
li.dataset["mentions"] = chanMentions;
if (chanUnreads > 0) li.className = "unread";
console.debug(`Channel ${channel.name} with ${chanUnreads} unreads`);
nodes.push(li); nodes.push(li);
} }
byId("channel_list").append(...nodes); byId("channel_list").append(...nodes);
@ -53,10 +74,48 @@ function populateChannelList() {
} }
} }
function increaseUnreadCount(el, post) {
// TODO Check if post is mention and increase mentions counter if it is
setUnreadCount(
el,
el.dataset["unreads"] * 1 + 1,
el.dataset["mentions"] * 1
);
}
function setUnreadCount(el, unreads, mentions) {
el.dataset["unreads"] = unreads;
el.dataset["mentions"] = mentions;
let msgCountGem = el.querySelector('.msg_count_gem');
if (mentions > 0) {
msgCountGem.style.display = "inline-block";
msgCountGem.innerText = mentions;
} else {
msgCountGem.style.display = "none";
}
if (unreads > 0) {
addClass(el, "unread");
} else {
removeClass(el, "unread");
}
}
pubsub.subscribe("MESSAGES_NEW", post => { pubsub.subscribe("MESSAGES_NEW", post => {
const chan = currentChannel(); const curChan = currentChannel();
if (!(post.endpoint === chan.endpoint && post.channel_id === chan.channel_id)) { if (!(post.endpoint === curChan.endpoint && post.channel_id === curChan.channel_id)) {
// TODO mark channel unread for (let el of byId("channel_list").childNodes) {
if (el.dataset["server"] == post.endpoint && el.dataset["id"] == post.channel_id) {
increaseUnreadCount(el, post);
}
}
}
});
pubsub.subscribe("CHANNEL_READ", channel => {
for (let el of byId("channel_list").childNodes) {
if (el.dataset["server"] == channel.endpoint && el.dataset["id"] == channel.channel_id) {
setUnreadCount(el, 0, 0);
}
} }
}); });