Add channel store

This commit is contained in:
Midgard 2022-06-17 23:04:16 +02:00
parent e63d8f0305
commit c3a24ee300
Signed by: midgard
GPG key ID: 511C112F1331BBB4
7 changed files with 189 additions and 81 deletions

View file

@ -63,6 +63,7 @@
<script type="text/javascript" src="/js/util.js"></script> <script type="text/javascript" src="/js/util.js"></script>
<script type="text/javascript" src="/js/pubsub.js"></script> <script type="text/javascript" src="/js/pubsub.js"></script>
<script type="text/javascript" src="/js/localstorage_credentials.js"></script> <script type="text/javascript" src="/js/localstorage_credentials.js"></script>
<script type="text/javascript" src="/js/store.js"></script>
<script type="text/javascript" src="/js/mm_client.js"></script> <script type="text/javascript" src="/js/mm_client.js"></script>
<script type="text/javascript" src="/js/view/messages.js"></script> <script type="text/javascript" src="/js/view/messages.js"></script>
<script type="text/javascript" src="/js/view/sidebar.js"></script> <script type="text/javascript" src="/js/view/sidebar.js"></script>

View file

@ -62,33 +62,32 @@ function logOut(endpoint, button) {
} }
function channelNameElements(team, channel) { function channelNameElements(team, channel) {
const teamName = team ? team.name : "";
const inTeam = teamName ? " in team " + teamName : "";
let icon = ""; let icon = "";
let teamName = team.name;
let channelName = channel.name; let channelName = channel.name;
let title = ""; let title = "";
switch (channel.type) { switch (channel.type) {
case "O": // Public channel case "O": // Public channel
title = `${channel.name} in team ${team.name} (public)`; title = `${channel.name}${inTeam} (public)`;
break; break;
case "P": // Private channel case "P": // Private channel
icon = "🔒"; icon = "🔒";
title = `${channel.name} in team ${team.name} (private)`; title = `${channel.name}${inTeam} (private)`;
break; break;
case "D": // Direct message case "D": // Direct message
return undefined; // XXX Because they clutter the list return undefined; // XXX Because they clutter the list
teamName = "";
channelName = `👤 ...`; channelName = `👤 ...`;
title = `Direct message`; title = `Direct message`;
break; break;
case "G": // Group chat case "G": // Group chat
teamName = "";
channelName = `👥 ${channel.display_name}`; channelName = `👥 ${channel.display_name}`;
title = `Group chat with ${channel.display_name}`; title = `Group chat with ${channel.display_name}`;
break; break;
default: // Unsupported default: // Unsupported
icon = channel.type; icon = channel.type;
title = `${channel.name} in team ${team.name} (type ${channel.type})`; title = `${channel.name}${inTeam} (type ${channel.type})`;
break; break;
} }
@ -210,16 +209,19 @@ function checkKeyPress(event) {
} }
pubsub.subscribe("MESSAGES_NEW", post => { pubsub.subscribe("MESSAGES_NEW", post => {
if (!window.hasFocus) {
return;
}
const curChan = currentChannel(); const curChan = currentChannel();
if (post.endpoint === curChan.endpoint && post.channel_id === curChan.channel_id) { const client = mm_client.getOrCreate(post.endpoint);
mm_client.getOrCreate(post.endpoint).markChannelRead(post.channel_id); if (window.hasFocus && post.endpoint === curChan.endpoint && post.channel_id === curChan.channel_id) {
client.markChannelRead(post.channel_id);
} else {
client.channelStore.increaseUnread(post);
} }
}); });
pubsub.subscribe("CHANNEL_READ", ({endpoint, channel_id}) => {
mm_client.getOrCreate(endpoint).channelStore.setUnread(channel_id, 0, 0);
});
pubsub.subscribe("WINDOW_FOCUSED", () => { pubsub.subscribe("WINDOW_FOCUSED", () => {
const curChan = currentChannel(); const curChan = currentChannel();
if (!curChan.channel_id) return; if (!curChan.channel_id) return;

View file

@ -9,6 +9,8 @@ class MattermostClient {
const creds = this.credentials.get(this.endpoint); const creds = this.credentials.get(this.endpoint);
this.token = creds ? creds.token : null; this.token = creds ? creds.token : null;
console.info(`Created MattermostClient for ${this.endpoint}, ${this.token ? "found token" : "did not find token"}`); console.info(`Created MattermostClient for ${this.endpoint}, ${this.token ? "found token" : "did not find token"}`);
this.channelStore = new store.ChannelStore(this);
} }
async get(path, queryParams) { async get(path, queryParams) {
@ -56,11 +58,16 @@ class MattermostClient {
start() { start() {
assert(this.token); assert(this.token);
let _ = this.getUsers(); const users = this.getUsers();
return this.userMe().then(data => { const me = this.userMe().then(data => {this.me = data;});
this.me = data; const channels = this.channelStore.get();
this.websocket();
}); return Promise.all([
users,
me,
channels
])
.then(() => {this.websocket();});
} }
async loggedIn() { async loggedIn() {

View file

@ -3,8 +3,9 @@ const pubsub = (function() { "use strict";
const topics = [ const topics = [
"MESSAGES_NEW", // {endpoint, channel_id, create_at, user_id, message} "MESSAGES_NEW", // {endpoint, channel_id, create_at, user_id, message}
"MESSAGES_CHANGED", "MESSAGES_CHANGED",
"CHANNELS_NEW", "CHANNELS_RELOADED", //
"CHANNELS_CHANGED", "CHANNEL_UPDATED", // {endpoint, channel}
"CHANNEL_UNREAD_UPDATED", // {endpoint, channel_id, unread, mentions}
"USERS_NEW", "USERS_NEW",
"USERS_CHANGED", "USERS_CHANGED",
"CHANNEL_MEMBERS_NEW", "CHANNEL_MEMBERS_NEW",

106
js/store.js Normal file
View file

@ -0,0 +1,106 @@
const store = (function() { "use strict";
class ChannelStore {
constructor(client) {
this.client = client;
this._channels = null;
this._unread = null;
this._promise = null;
this._fetching = false;
}
fetch() {
if (this._fetching) {
return;
}
this._fetching = true;
this._channels = null;
this._unread = null;
const teams = this.client.myTeams();
const channels = teams
.then(teams => Promise.all(teams.map(team => this.client.myChannels(team.id))))
.then(teams => teams.map(channels => arrayToHashmap(channels, "id")))
.then(flattenHashmaps);
const unread = teams
.then(teams => Promise.all(teams.map(team => this.client.getUnread(team.id))))
.then(flattenArrays);
function processUnread(channels, unread) {
// In the Mattermost API, not the number of *unread* messages but the number of *read*
// messages is returned, so we have to subtract that from the number of total messages.
let result = Object.create(null);
for (let x of unread) {
if (!(x["channel_id"] in channels)) continue;
let object = Object.create(null);
object.unread = channels[x["channel_id"]]["total_msg_count"] - x["msg_count"];
object.mentions = x["mention_count"];
result[x["channel_id"]] = object;
}
return result;
}
this._promise = Promise.all([teams, channels, unread])
.then(([teams, channels, unread]) => {
this._teams = arrayToHashmap(teams, "id");
this._channels = channels;
this._unread = processUnread(channels, unread);
this._fetching = false;
pubsub.publish("CHANNELS_RELOADED");
});
}
async get() {
if (this._promise === null) {
this.fetch();
}
await this._promise;
return {teams: this._teams, channels: this._channels, unread: this._unread};
}
async updateChannel(channel) {
if (this._promise === null) {
// Data does not exist yet and is not being requested
return;
}
await this._promise;
this._channels[channel["id"]] = channel;
}
async increaseUnread(post) {
if (this._promise === null) {
// Data does not exist yet and is not being requested
return;
}
await this._promise;
// TODO Check if post is mention and increase mentions counter if it is
this._unread[post["channel_id"]].unread += 1;
const unread = this._unread[post["channel_id"]].unread;
const mentions = this._unread[post["channel_id"]].mentions;
pubsub.publish("CHANNEL_UNREAD_UPDATED", {endpoint: this.client.endpoint, channel_id: post["channel_id"], unread, mentions});
}
async setUnread(channel_id, unread, mentions) {
if (this._promise === null) {
// Data does not exist yet and is not being requested
return;
}
await this._promise;
this._unread[channel_id].unread = unread;
this._unread[channel_id].mentions = mentions;
pubsub.publish("CHANNEL_UNREAD_UPDATED", {endpoint: this.client.endpoint, channel_id, unread, mentions});
}
}
return {ChannelStore};
})();

View file

@ -71,6 +71,23 @@ function arrayToHashmap(array, key) {
return result; return result;
} }
function flattenHashmaps(hashmaps) {
let result = Object.create(null);
for (let x of hashmaps) {
Object.assign(result, x);
}
return result;
}
function flattenArrays(arrays) {
let result = [];
for (let x of arrays) {
Object.assign(result, x);
Array.prototype.push.apply(result, x)
}
return result;
}
function startsWith(string, start) { function startsWith(string, start) {
return string.slice(0, start.length) == start; return string.slice(0, start.length) == start;

View file

@ -21,49 +21,42 @@ function populateServerSelectionList() {
function populateChannelList() { function populateChannelList() {
async function addChannelItems(client) { async function addChannelItems(client) {
const teams = await client.myTeams(); const {teams, channels, unread} = await client.channelStore.get();
for (let team of teams) { let nodes = [];
let nodes = [];
const [channels, unreadsList] = await Promise.all([
client.myChannels(team.id),
client.getUnread(team.id)
]);
const unreads = arrayToHashmap(unreadsList, "channel_id"); for (let channel_id in channels) {
const channel = channels[channel_id];
const team = teams[channel["team_id"]];
for (let channel of channels) { const chanUnread = unread[channel.id].unread;
const chanUnreads = channel["total_msg_count"] - unreads[channel.id]["msg_count"]; const chanMentions = unread[channel.id].mentions;
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)";
const titleAndElements = channelNameElements(team, channel); const titleAndElements = channelNameElements(team, channel);
if (!titleAndElements) continue; if (!titleAndElements) continue;
a.title = titleAndElements[0]; a.title = titleAndElements[0];
a.append(...titleAndElements[1]); a.append(...titleAndElements[1]);
a.append(document.createTextNode(" ")); a.append(document.createTextNode(" "));
const msgCountGem = document.createElement("span"); const msgCountGem = document.createElement("span");
msgCountGem.className = "msg_count_gem"; msgCountGem.className = "msg_count_gem";
msgCountGem.innerText = chanMentions; msgCountGem.innerText = chanMentions;
if (chanMentions > 0) msgCountGem.style.display = "inline-block"; if (chanMentions > 0) msgCountGem.style.display = "inline-block";
a.append(msgCountGem); 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"; if (chanUnread > 0) li.className = "unread";
nodes.push(li); nodes.push(li);
}
byId("channel_list").append(...nodes);
} }
byId("channel_list").append(...nodes);
} }
byId("channel_list").innerHTML = ""; byId("channel_list").innerHTML = "";
@ -73,18 +66,7 @@ function populateChannelList() {
} }
} }
function increaseUnreadCount(el, post) { function updateUnreadCount(el, unread, mentions) {
// 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'); let msgCountGem = el.querySelector('.msg_count_gem');
if (mentions > 0) { if (mentions > 0) {
msgCountGem.style.display = "inline-block"; msgCountGem.style.display = "inline-block";
@ -92,29 +74,21 @@ function setUnreadCount(el, unreads, mentions) {
} else { } else {
msgCountGem.style.display = "none"; msgCountGem.style.display = "none";
} }
if (unreads > 0) { if (unread > 0) {
addClass(el, "unread"); addClass(el, "unread");
} else { } else {
removeClass(el, "unread"); removeClass(el, "unread");
} }
} }
pubsub.subscribe("CHANNEL_UNREAD_UPDATED", ({endpoint, channel_id, unread, mentions}) => {
pubsub.subscribe("MESSAGES_NEW", post => {
const curChan = currentChannel();
if (!(post.endpoint === curChan.endpoint && post.channel_id === curChan.channel_id)) {
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) { for (let el of byId("channel_list").childNodes) {
if (el.dataset["server"] == channel.endpoint && el.dataset["id"] == channel.channel_id) { if (el.dataset["server"] == endpoint && el.dataset["id"] == channel_id) {
setUnreadCount(el, 0, 0); updateUnreadCount(el, unread, mentions);
} }
} }
}); });
pubsub.subscribe("CHANNELS_RELOADED", () => {
populateChannelList();
});