function populateServerSelectionList() { const servers = localstorage_credentials.getServers(); let nodes = []; for (let server of servers) { const li = document.createElement("li"); const endpoint = humanReadableEndpoint(server.endpoint); li.innerText = `${server.login_id}@${endpoint} `; const logoutButton = document.createElement("button"); logoutButton.className = "logout"; logoutButton.innerText = "Log out"; logoutButton.addEventListener("click", e => logOut(server.endpoint, e.currentTarget)); li.appendChild(logoutButton); nodes.push(li); } byId("server_selection_list").innerHTML = ""; byId("server_selection_list").append(...nodes); } function populateChannelList() { async function addChannelItems(client) { const teams = await client.myTeams(); for (let team of teams) { let nodes = []; const channels = await client.myChannels(team.id); for (let channel of channels) { 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.addEventListener("click", () => switchToChannel(client, team, channel)); li.appendChild(a); li.dataset["id"] = channel.id; nodes.push(li); } byId("channel_list").append(...nodes); } } byId("channel_list").innerHTML = ""; const endpoints = localstorage_credentials.getServers().map(server => server.endpoint); for (let client of mm_client.getMultiple(endpoints)) { addChannelItems(client); } } function transformMessageText(message) { return message.split(/(?<=:|\W|^)(:[a-z0-9_-]+:)(?=:|\W|$)/).map( (x, i) => i % 2 ? emoji[x.slice(1, -1)] || x : x ); } async function createMessageElement(client, post, lastTime, lastAuthor) { const users = await client.getUsers(); const isThreadReply = !!post.parent_id; const messageDiv = document.createElement("div"); messageDiv.className = "message"; messageDiv.innerText = transformMessageText(post.message); const createAt = new Date(post.create_at); const createAtDiv = document.createElement("time"); createAtDiv.className = "create_at"; const sim = dateSimilarity(lastTime, createAt); lastTime = createAt; let createAtText = ""; if (sim < DATE_SIMILARITY.date) createAtText += formatDdddMmYy(createAt); if (sim < DATE_SIMILARITY.minutes) createAtText += " " + formatHhMm(createAt); createAtDiv.title = createAt.toString(); createAtDiv.innerText = createAtText; 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 = 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"] = post.id; postDiv.appendChild(authorDiv); postDiv.appendChild(createAtDiv); postDiv.appendChild(messageDiv); if (post.metadata && (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); } return {postDiv, lastTime, lastAuthor}; } async function populateChannelContents(client, channel, contents) { byId("channel_contents").innerHTML = ""; let result = {lastAuthor: null, lastTime: null, postDiv: null}; let nodes = []; for (let id of contents.order) { result = await createMessageElement(client, contents.posts[id], result.lastTime, result.lastAuthor); nodes.push(result.postDiv); } byId("channel_contents").dataset["server"] = client.endpoint; byId("channel_contents").dataset["id"] = channel.id; byId("channel_contents").dataset["lastTime"] = result.lastTime.getTime(); byId("channel_contents").dataset["lastAuthor"] = result.lastAuthor; byId("channel_contents").append(...nodes); scrollToBottom(); } async function addMessage(client, post) { const shouldScroll = isScrolledToBottom(); const result = await createMessageElement( client, post, new Date(1 * (byId("channel_contents").dataset["lastTime"])), byId("channel_contents").dataset["lastAuthor"] ); console.log(result); byId("channel_contents").dataset["lastTime"] = result.lastTime.getTime(); byId("channel_contents").dataset["lastAuthor"] = result.lastAuthor; byId("channel_contents").appendChild(result.postDiv); if (shouldScroll) { scrollToBottom(); } } function scrollToBottom() { const el = byId("channel_contents_wrapper"); el.scrollTop = el.scrollHeight; el.className = ""; } function updateComposeHeight() { byId("compose").style.height = ""; byId("compose").style.height = (byId("compose").scrollHeight + 1) + "px"; } byId("compose").addEventListener("input", updateComposeHeight); function isScrolledToBottom() { const el = byId("channel_contents_wrapper"); const scrolledTo = el.clientHeight + el.scrollTop; return scrolledTo >= el.scrollHeight; } function checkScrolledToBottom() { byId("channel_contents_wrapper").className = isScrolledToBottom() ? "" : "not-at-bottom"; } byId("channel_contents_wrapper").addEventListener("scroll", checkScrolledToBottom); pubsub.subscribe("MESSAGES_NEW", post => { if ( post.endpoint === byId("channel_contents").dataset["server"] && post.channel_id === byId("channel_contents").dataset["id"] ) { addMessage(mm_client.get(post.endpoint), post); } });