diff --git a/ajax.js b/ajax.js index 1a4bae3..a927b43 100644 --- a/ajax.js +++ b/ajax.js @@ -68,10 +68,26 @@ function xhrParseJsonResponse(xhr) { return xhr; } +function withParams(url, queryParams) { + let sep = "?"; + for (let paramName of Object.getOwnPropertyNames(queryParams)) { + const paramValue = queryParams[paramName]; + if (!paramValue) continue; + url += `${sep}${encodeURIComponent(paramName)}`; + if (paramValue !== true) { + url += `=${encodeURIComponent(paramValue)}`; + } + sep = "&"; + } +} + function getJson(url, options={}) { + if (!options.queryParams) options.queryParams = {}; if (!options.headers) options.headers = {}; options.headers["Accept"] = MIME_JSON; + const urlWithParams = withParams(url, options.queryParams); + return new Promise((resolve, reject) => { let xhr = xhrInitForPromise(resolve, reject, url, "GET", options.headers); xhr.send(); diff --git a/assets/main.css b/assets/main.css index 58537ca..dea2475 100644 --- a/assets/main.css +++ b/assets/main.css @@ -42,3 +42,29 @@ h1 img { #login_server_row td, #login_server_row th { padding-bottom: 1em; } + +.post { + margin: 1em 0; + display: grid; + grid-template-rows: auto auto; + grid-template-columns: auto auto; +} + +.post .author { + grid-row: 1; + grid-column: 1; + color: #888; +} + +.post .create_at { + text-align: right; + grid-row: 1; + grid-column: 2; + color: #888; +} + +.post .message { + grid-row: 2; + grid-column-start: 1; + grid-column-end: 3; +} diff --git a/main.js b/main.js index 6c6a632..4dca271 100644 --- a/main.js +++ b/main.js @@ -54,16 +54,16 @@ class MattermostApi { return this._endpoint; } - async get(path, token) { + async get(path, token, queryParams) { const headers = token ? {"Authorization": `Bearer ${token}`} : {}; - const response = await ajax.getJson(`${this._endpoint}${path}`, {headers}); + const response = await ajax.getJson(`${this._endpoint}${path}`, {headers, queryParams}); if (!response.ok) { throw response; } return response; } - async post(path, data, token) { + async post(path, token, data) { const headers = token ? {"Authorization": `Bearer ${token}`} : {}; const response = await ajax.postJson(`${this._endpoint}${path}`, data, {headers}); if (!response.ok) { @@ -81,7 +81,7 @@ class MattermostClient { } async logIn(login_id, password) { - const response = await this.api.post("/users/login", {login_id, password}); + const response = await this.api.post("/users/login", undefined, {login_id, password}); const token = response.getResponseHeader("Token"); if (!token) { throw Error("No Token header in response to log in request"); @@ -95,7 +95,7 @@ class MattermostClient { if (!stored || !stored.token) { throw Error("No token stored"); } - const response = await this.api.post("/users/logout", undefined, stored.token); + const response = await this.api.post("/users/logout", stored.token); // Verify that the token is now invalidated let tokenWorks; @@ -137,6 +137,14 @@ class MattermostClient { const response = await this.api.get(`/users/me/teams/${team_id}/channels`, stored.token); return response.responseJson; } + + async channelPosts(channel_id, beforePost=null, afterPost=null, since=null) { + const stored = this.storage.get(this.api.id); + const response = await this.api.get(`/channels/${channel_id}/posts`, stored.token, { + before: beforePost, after: afterPost, since + }); + return response.responseJson; + } } @@ -231,7 +239,7 @@ function populateChannelList() { a.innerText = `${channel.type} ${team.name}/${channel.name}`; break; } - a.addEventListener("click", () => switchToChannel(team.id, channel.id)); + a.addEventListener("click", () => switchToChannel(client, team.id, channel.id)); li.appendChild(a); nodes.push(li); } @@ -248,6 +256,42 @@ function populateChannelList() { } populateChannelList(); +function populateChannelContents(contents) { + byId("channel_contents").innerHTML = ""; + + let nodes = []; + for (let id of contents.order) { + const post = contents.posts[id]; + + const isThreadReply = !!post.parent_id; + + const messageDiv = document.createElement("div"); + messageDiv.className = "message"; + messageDiv.innerText = post.message; + + const createAt = new Date(post.create_at); + const createAtDiv = document.createElement("time"); + createAtDiv.className = "create_at"; + createAtDiv.innerText = createAt.toLocaleString("nl-BE"); + createAtDiv.dateTime = createAt.toISOString(); + + const authorDiv = document.createElement("div"); + authorDiv.className = "author"; + authorDiv.innerText = `Auteur: ${post.user_id}`; + + const postDiv = document.createElement("div"); + postDiv.className = "post"; + postDiv.dataset["postid"] = id; + postDiv.appendChild(authorDiv); + postDiv.appendChild(createAtDiv); + postDiv.appendChild(messageDiv); + + nodes.unshift(postDiv); + } + + byId("channel_contents").append(...nodes); +} + function logIn() { const client = createClient(byId("login_server").value); @@ -289,8 +333,20 @@ function logOut(endpoint, button) { }); } -function switchToChannel(team_id, channel_id) { +function switchToChannel(client, team_id, channel_id) { + byId("channel_contents").innerText = "Loading…"; window.location = "#channel_contents"; + + client.channelPosts(channel_id) + .then(response => { + console.info(`Got channel contents of ${channel_id}`); + populateChannelContents(response); + window.location = "#channel_contents"; + //}) + //.catch(error => { + //console.error(error); + //byId("channel_contents").innerText = `Failed to get channel contents:\n${error.message}`; + }); } byId("login_button").addEventListener("click", logIn);