Add attachments and author names
This commit is contained in:
parent
c0fbb51ddf
commit
84eaae9fa9
8 changed files with 133 additions and 20 deletions
|
@ -214,7 +214,10 @@ ul#server_selection_list {
|
|||
.post .author {
|
||||
grid-row: 1;
|
||||
grid-column: 1;
|
||||
color: #888;
|
||||
font-size: 80%;
|
||||
}
|
||||
.post.same_author .author {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.post .create_at {
|
||||
|
@ -223,6 +226,9 @@ ul#server_selection_list {
|
|||
grid-column: 2;
|
||||
color: #888;
|
||||
}
|
||||
.post.same_author .create_at {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.post .message {
|
||||
grid-row: 2;
|
||||
|
|
|
@ -60,6 +60,9 @@ function xhrParseJsonResponse(xhr) {
|
|||
}
|
||||
|
||||
function withParams(url, queryParams) {
|
||||
console.debug(url, queryParams);
|
||||
if (!queryParams) return url;
|
||||
|
||||
let sep = "?";
|
||||
for (let paramName of Object.getOwnPropertyNames(queryParams)) {
|
||||
const paramValue = queryParams[paramName];
|
||||
|
@ -70,6 +73,9 @@ function withParams(url, queryParams) {
|
|||
}
|
||||
sep = "&";
|
||||
}
|
||||
|
||||
console.debug(url);
|
||||
return url;
|
||||
}
|
||||
|
||||
function getJson(url, options={}) {
|
||||
|
@ -80,7 +86,7 @@ function getJson(url, options={}) {
|
|||
const urlWithParams = withParams(url, options.queryParams);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let xhr = xhrInitForPromise(resolve, reject, url, "GET", options.headers);
|
||||
let xhr = xhrInitForPromise(resolve, reject, urlWithParams, "GET", options.headers);
|
||||
xhr.send();
|
||||
}).then(xhrParseJsonResponse);
|
||||
}
|
||||
|
|
|
@ -128,7 +128,7 @@ function switchToChannel(client, team, channel) {
|
|||
client.channelPosts(channel.id)
|
||||
.then(response => {
|
||||
console.info(`Got channel contents of ${channel.id} (${channel.name})`);
|
||||
populateChannelContents(response);
|
||||
populateChannelContents(client, response);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
|
|
|
@ -10,7 +10,7 @@ function key_for(endpoint) {
|
|||
return {
|
||||
getServers() {
|
||||
let servers = [];
|
||||
for (var i = 0; i < window.localStorage.length; i++) {
|
||||
for (let i = 0; i < window.localStorage.length; i++) {
|
||||
const key = window.localStorage.key(i);
|
||||
const matches = key.match(RE_SERVER_ITEM);
|
||||
if (matches) {
|
||||
|
|
17
js/main.js
17
js/main.js
|
@ -6,5 +6,22 @@ byId("login_no_button").addEventListener("click", e => { e.stopPropagation(); e.
|
|||
|
||||
updateComposeHeight();
|
||||
checkScrolledToBottom();
|
||||
|
||||
populateServerSelectionList();
|
||||
|
||||
function fetchUsernames(endpoint) {
|
||||
console.debug(endpoint);
|
||||
const client = createClient(endpoint).users();
|
||||
return client;
|
||||
}
|
||||
|
||||
let users = {};
|
||||
Promise.all(localstorage_credentials.getServers().map(server => server.endpoint).map(fetchUsernames)).then(server_user_data => {
|
||||
for (let user_data of server_user_data) {
|
||||
for (let id of Object.getOwnPropertyNames(user_data)) {
|
||||
const user = user_data[id];
|
||||
users[user.id] = user;
|
||||
}
|
||||
}
|
||||
populateChannelList();
|
||||
});
|
||||
|
|
|
@ -10,6 +10,10 @@ class MattermostApi {
|
|||
return this._endpoint;
|
||||
}
|
||||
|
||||
get endpoint() {
|
||||
return this._endpoint;
|
||||
}
|
||||
|
||||
async get(path, token, queryParams) {
|
||||
const headers = token ? {"Authorization": `Bearer ${token}`} : {};
|
||||
const response = await ajax.getJson(`${this._endpoint}${path}`, {headers, queryParams});
|
||||
|
@ -39,12 +43,38 @@ class MattermostClient {
|
|||
console.info(`Created MattermostClient for ${this.api.id}, ${this.token ? "found token" : "did not find token"}`);
|
||||
}
|
||||
|
||||
async authenticatedGet(url, queryParams) {
|
||||
async authedGet(url, queryParams) {
|
||||
assert(this.token, "logged in");
|
||||
const response = await this.api.get(url, this.token, queryParams);
|
||||
return response.responseJson;
|
||||
}
|
||||
|
||||
async authedPaginatedGet(url, queryParams, perPage=200) {
|
||||
assert(this.token, "logged in");
|
||||
let data = [];
|
||||
|
||||
let params = {page: 0, per_page: perPage};
|
||||
if (queryParams) {
|
||||
extend(params, queryParams);
|
||||
}
|
||||
|
||||
let loadMore = true;
|
||||
while (loadMore) {
|
||||
if (params.page > 100) {
|
||||
throw new Error("Requesting more than 100 pages, something looks wrong");
|
||||
}
|
||||
|
||||
const response = await this.api.get(url, this.token, params);
|
||||
console.log(response);
|
||||
data = data.concat(response.responseJson);
|
||||
|
||||
loadMore = response.responseJson.length > 0;
|
||||
params.page++;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
async loggedIn() {
|
||||
if (!this.token) {
|
||||
return false;
|
||||
|
@ -62,7 +92,7 @@ class MattermostClient {
|
|||
}
|
||||
|
||||
async logIn(login_id, password) {
|
||||
if (this.token && await this.tokenWorks()) {
|
||||
if (await this.loggedIn()) {
|
||||
throw Error("Already logged in on this server");
|
||||
}
|
||||
|
||||
|
@ -81,7 +111,7 @@ class MattermostClient {
|
|||
const response = await this.api.post("/users/logout", this.token);
|
||||
|
||||
// Verify that the token is now invalidated
|
||||
if (await loggedIn()) {
|
||||
if (await this.loggedIn()) {
|
||||
throw new Error("Failed to log out: token still works after trying to log out");
|
||||
}
|
||||
|
||||
|
@ -92,24 +122,33 @@ class MattermostClient {
|
|||
|
||||
user(user_id) {
|
||||
assertIsMattermostId(user_id);
|
||||
return this.authenticatedGet(`/users/${user_id}`);
|
||||
return this.authedGet(`/users/${user_id}`);
|
||||
}
|
||||
userMe() { return this.authenticatedGet("/users/me"); }
|
||||
myTeams() { return this.authenticatedGet("/users/me/teams"); }
|
||||
userMe() { return this.authedGet("/users/me"); }
|
||||
myTeams() { return this.authedGet("/users/me/teams"); }
|
||||
|
||||
myChannels(team_id) {
|
||||
assertIsMattermostId(team_id);
|
||||
return this.authenticatedGet(`/users/me/teams/${team_id}/channels`);
|
||||
return this.authedGet(`/users/me/teams/${team_id}/channels`);
|
||||
}
|
||||
|
||||
channelPosts(channel_id, beforePost=null, afterPost=null, since=null) {
|
||||
assertIsMattermostId(channel_id);
|
||||
assertIsNullOrMattermostId(beforePost);
|
||||
assertIsNullOrMattermostId(afterPost);
|
||||
return this.authenticatedGet(`/channels/${channel_id}/posts`, {
|
||||
return this.authedGet(`/channels/${channel_id}/posts`, {
|
||||
before: beforePost, after: afterPost, since
|
||||
});
|
||||
}
|
||||
|
||||
users() {
|
||||
return this.authedPaginatedGet("/users", {});
|
||||
}
|
||||
|
||||
async filePublicLink(file_id) {
|
||||
const response = await this.authedGet(`/files/${file_id}/link`, {});
|
||||
return response.link;
|
||||
}
|
||||
}
|
||||
|
||||
return {MattermostApi, MattermostClient};
|
||||
|
|
10
js/util.js
10
js/util.js
|
@ -45,6 +45,16 @@ function thisToArg(f) {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extend an object in-place with own properties of a second one
|
||||
*/
|
||||
function extend(obj1, obj2) {
|
||||
for (let key in Object.getOwnPropertyNames(obj1)) {
|
||||
obj1[key] = obj2[key];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class AssertionError extends Error {}
|
||||
/**
|
||||
* Throw an AssertionError if the first argument is not true
|
||||
|
|
|
@ -24,12 +24,10 @@ function populateChannelList() {
|
|||
const client = createClient(endpoint);
|
||||
|
||||
const teams = await client.myTeams();
|
||||
console.log(teams);
|
||||
|
||||
for (let team of teams) {
|
||||
let nodes = [];
|
||||
const channels = await client.myChannels(team.id);
|
||||
console.log(channels);
|
||||
for (let channel of channels) {
|
||||
const li = document.createElement("li");
|
||||
const a = document.createElement("a");
|
||||
|
@ -58,9 +56,10 @@ function populateChannelList() {
|
|||
}
|
||||
|
||||
|
||||
function populateChannelContents(contents) {
|
||||
function populateChannelContents(client, contents) {
|
||||
byId("channel_contents").innerHTML = "";
|
||||
|
||||
let lastAuthor = null;
|
||||
let nodes = [];
|
||||
for (let id of contents.order) {
|
||||
const post = contents.posts[id];
|
||||
|
@ -77,22 +76,59 @@ function populateChannelContents(contents) {
|
|||
createAtDiv.innerText = createAt.toLocaleString("nl-BE");
|
||||
createAtDiv.dateTime = createAt.toISOString();
|
||||
|
||||
const authorName = users[post.user_id] ? users[post.user_id].username : post.user_id;
|
||||
const authorDiv = document.createElement("div");
|
||||
authorDiv.className = "author";
|
||||
authorDiv.innerText = `Auteur: ${post.user_id}`;
|
||||
authorDiv.innerText = `@${authorName}`;
|
||||
|
||||
const postDiv = document.createElement("div");
|
||||
postDiv.className = "post";
|
||||
if (lastAuthor === post.user_id) {
|
||||
postDiv.className += " same_author";
|
||||
}
|
||||
lastAuthor = post.user_id;
|
||||
|
||||
postDiv.dataset["id"] = id;
|
||||
postDiv.appendChild(authorDiv);
|
||||
postDiv.appendChild(createAtDiv);
|
||||
postDiv.appendChild(messageDiv);
|
||||
|
||||
if ((post.metadata.files || []).length > 0) {
|
||||
const attachmentsUl = document.createElement("ul");
|
||||
attachmentsUl.className = "attachments";
|
||||
for (let file of post.metadata.files || []) {
|
||||
const attachmentLi = document.createElement("li");
|
||||
attachmentLi.dataset["id"] = file.id;
|
||||
|
||||
const attachmentA = document.createElement("a");
|
||||
client.filePublicLink(file.id).then(link => attachmentA.href = link);
|
||||
attachmentA.target = "_blank";
|
||||
attachmentA.innerText = file.name;
|
||||
|
||||
if (file.mini_preview) {
|
||||
const attachmentImg = document.createElement("img");
|
||||
attachmentImg.src = `data:image/jpeg;base64,${file.mini_preview}`;
|
||||
attachmentA.appendChild(attachmentImg);
|
||||
}
|
||||
|
||||
attachmentLi.appendChild(attachmentA);
|
||||
attachmentsUl.appendChild(attachmentLi);
|
||||
}
|
||||
postDiv.appendChild(attachmentsUl);
|
||||
}
|
||||
|
||||
nodes.unshift(postDiv);
|
||||
}
|
||||
|
||||
byId("channel_contents").append(...nodes);
|
||||
checkScrolledToBottom();
|
||||
scrollToBottom();
|
||||
}
|
||||
|
||||
|
||||
function scrollToBottom() {
|
||||
const el = byId("channel_contents_wrapper");
|
||||
el.scrollTop = el.scrollHeight;
|
||||
el.className = "";
|
||||
}
|
||||
|
||||
|
||||
|
@ -106,8 +142,7 @@ function checkScrolledToBottom() {
|
|||
const el = byId("channel_contents_wrapper");
|
||||
|
||||
const scrolledTo = el.clientHeight + el.scrollTop;
|
||||
const scrollHeight = el.scrollHeight
|
||||
const atBottom = scrolledTo >= scrollHeight;
|
||||
const atBottom = scrolledTo >= el.scrollHeight;
|
||||
el.className = atBottom ? "" : "not-at-bottom";
|
||||
}
|
||||
byId("channel_contents_wrapper").addEventListener("scroll", checkScrolledToBottom);
|
||||
|
|
Loading…
Reference in a new issue