diff --git a/index.html b/index.html
index 2dab188..8a3fc4a 100644
--- a/index.html
+++ b/index.html
@@ -63,6 +63,7 @@
+
diff --git a/js/controller.js b/js/controller.js
index 761d930..bdd9f00 100644
--- a/js/controller.js
+++ b/js/controller.js
@@ -62,33 +62,32 @@ function logOut(endpoint, button) {
}
function channelNameElements(team, channel) {
+ const teamName = team ? team.name : "";
+ const inTeam = teamName ? " in team " + teamName : "";
let icon = "";
- let teamName = team.name;
let channelName = channel.name;
let title = "";
switch (channel.type) {
case "O": // Public channel
- title = `${channel.name} in team ${team.name} (public)`;
+ title = `${channel.name}${inTeam} (public)`;
break;
case "P": // Private channel
icon = "🔒";
- title = `${channel.name} in team ${team.name} (private)`;
+ title = `${channel.name}${inTeam} (private)`;
break;
case "D": // Direct message
return undefined; // XXX Because they clutter the list
- teamName = "";
channelName = `👤 ...`;
title = `Direct message`;
break;
case "G": // Group chat
- teamName = "";
channelName = `👥 ${channel.display_name}`;
title = `Group chat with ${channel.display_name}`;
break;
default: // Unsupported
icon = channel.type;
- title = `${channel.name} in team ${team.name} (type ${channel.type})`;
+ title = `${channel.name}${inTeam} (type ${channel.type})`;
break;
}
@@ -210,16 +209,19 @@ function checkKeyPress(event) {
}
pubsub.subscribe("MESSAGES_NEW", post => {
- if (!window.hasFocus) {
- return;
- }
-
const curChan = currentChannel();
- if (post.endpoint === curChan.endpoint && post.channel_id === curChan.channel_id) {
- mm_client.getOrCreate(post.endpoint).markChannelRead(post.channel_id);
+ const client = mm_client.getOrCreate(post.endpoint);
+ 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", () => {
const curChan = currentChannel();
if (!curChan.channel_id) return;
diff --git a/js/mm_client.js b/js/mm_client.js
index 3d68377..6fa8c64 100644
--- a/js/mm_client.js
+++ b/js/mm_client.js
@@ -9,6 +9,8 @@ class MattermostClient {
const creds = this.credentials.get(this.endpoint);
this.token = creds ? creds.token : null;
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) {
@@ -56,11 +58,16 @@ class MattermostClient {
start() {
assert(this.token);
- let _ = this.getUsers();
- return this.userMe().then(data => {
- this.me = data;
- this.websocket();
- });
+ const users = this.getUsers();
+ const me = this.userMe().then(data => {this.me = data;});
+ const channels = this.channelStore.get();
+
+ return Promise.all([
+ users,
+ me,
+ channels
+ ])
+ .then(() => {this.websocket();});
}
async loggedIn() {
diff --git a/js/pubsub.js b/js/pubsub.js
index 79ae073..1241fee 100644
--- a/js/pubsub.js
+++ b/js/pubsub.js
@@ -3,8 +3,9 @@ const pubsub = (function() { "use strict";
const topics = [
"MESSAGES_NEW", // {endpoint, channel_id, create_at, user_id, message}
"MESSAGES_CHANGED",
- "CHANNELS_NEW",
- "CHANNELS_CHANGED",
+ "CHANNELS_RELOADED", //
+ "CHANNEL_UPDATED", // {endpoint, channel}
+ "CHANNEL_UNREAD_UPDATED", // {endpoint, channel_id, unread, mentions}
"USERS_NEW",
"USERS_CHANGED",
"CHANNEL_MEMBERS_NEW",
diff --git a/js/store.js b/js/store.js
new file mode 100644
index 0000000..0a28b2e
--- /dev/null
+++ b/js/store.js
@@ -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};
+
+})();
diff --git a/js/util.js b/js/util.js
index 2c7aaf0..1059127 100644
--- a/js/util.js
+++ b/js/util.js
@@ -71,6 +71,23 @@ function arrayToHashmap(array, key) {
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) {
return string.slice(0, start.length) == start;
diff --git a/js/view/sidebar.js b/js/view/sidebar.js
index 9f52ec6..fea0ccc 100644
--- a/js/view/sidebar.js
+++ b/js/view/sidebar.js
@@ -21,49 +21,42 @@ function populateServerSelectionList() {
function populateChannelList() {
async function addChannelItems(client) {
- const teams = await client.myTeams();
+ const {teams, channels, unread} = await client.channelStore.get();
- for (let team of teams) {
- let nodes = [];
- const [channels, unreadsList] = await Promise.all([
- client.myChannels(team.id),
- client.getUnread(team.id)
- ]);
+ let nodes = [];
- 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 chanUnreads = channel["total_msg_count"] - unreads[channel.id]["msg_count"];
- const chanMentions = unreads[channel.id]["mention_count"];
+ const chanUnread = unread[channel.id].unread;
+ const chanMentions = unread[channel.id].mentions;
- const li = document.createElement("li");
- const a = document.createElement("a");
- a.href = "javascript:void(0)";
- const titleAndElements = channelNameElements(team, channel);
- if (!titleAndElements) continue;
- a.title = titleAndElements[0];
- a.append(...titleAndElements[1]);
+ const li = document.createElement("li");
+ const a = document.createElement("a");
+ a.href = "javascript:void(0)";
+ const titleAndElements = channelNameElements(team, channel);
+ if (!titleAndElements) continue;
+ a.title = titleAndElements[0];
+ 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.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.dataset["id"] = channel.id;
- li.dataset["server"] = client.endpoint;
- li.dataset["unreads"] = chanUnreads;
- li.dataset["mentions"] = chanMentions;
+ li.appendChild(a);
+ li.dataset["id"] = channel.id;
+ li.dataset["server"] = client.endpoint;
- if (chanUnreads > 0) li.className = "unread";
- nodes.push(li);
- }
- byId("channel_list").append(...nodes);
+ if (chanUnread > 0) li.className = "unread";
+ nodes.push(li);
}
+ byId("channel_list").append(...nodes);
}
byId("channel_list").innerHTML = "";
@@ -73,18 +66,7 @@ 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;
+function updateUnreadCount(el, unread, mentions) {
let msgCountGem = el.querySelector('.msg_count_gem');
if (mentions > 0) {
msgCountGem.style.display = "inline-block";
@@ -92,29 +74,21 @@ function setUnreadCount(el, unreads, mentions) {
} else {
msgCountGem.style.display = "none";
}
- if (unreads > 0) {
+ if (unread > 0) {
addClass(el, "unread");
} else {
removeClass(el, "unread");
}
}
-
-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 => {
+pubsub.subscribe("CHANNEL_UNREAD_UPDATED", ({endpoint, channel_id, unread, mentions}) => {
for (let el of byId("channel_list").childNodes) {
- if (el.dataset["server"] == channel.endpoint && el.dataset["id"] == channel.channel_id) {
- setUnreadCount(el, 0, 0);
+ if (el.dataset["server"] == endpoint && el.dataset["id"] == channel_id) {
+ updateUnreadCount(el, unread, mentions);
}
}
});
+
+pubsub.subscribe("CHANNELS_RELOADED", () => {
+ populateChannelList();
+});