function transformMessageText(message) { return message .split(/(?<=:|\W|^)(:[a-z0-9_-]+:)(?=:|\W|$)/) .map((x, i) => i % 2 ? emoji[x.slice(1, -1)] || x : x) .join(""); } function shouldShowTime(lastShownTime, time) { const sim = dateSimilarity(lastShownTime, time); if (sim < DATE_SIMILARITY.date) { return {date: true, time: true}; } const minutesSinceLastShown = (time - lastShownTime) / 1000 / 60; if (minutesSinceLastShown >= 15) { return {date: false, time: true}; } return {date: false, time: false}; } async function createMessageElement(client, post, lastTime, lastAuthor) { let nodes = []; const users = await client.getUsers(); const isThreadReply = !!post.parent_id; const createAt = new Date(post.create_at); let createAtLocalized = createAt; let lastTimeLocalized = lastTime; try { if (client.me.timezone.useAutomaticTimezone !== "true") { createAtLocalized = toTimeZone(createAt, client.me.timezone.manualTimezone); lastTimeLocalized = toTimeZone(lastTime, client.me.timezone.manualTimezone); } } catch(e) {} const showTime = shouldShowTime(lastTimeLocalized, createAtLocalized); if (showTime.time) { lastTime = createAt; } const postDiv = document.createElement("div"); postDiv.className = "post"; if (post.type !== "") { postDiv.className += " special"; } if (showTime.date) { const dateElement = document.createElement("div"); dateElement.className = "date_separator"; dateElement.innerText = formatYyyyMmDdWeekday(createAtLocalized); nodes.push(dateElement); } if (showTime.time) { postDiv.className += " show_time"; } const messageDiv = document.createElement("div"); messageDiv.className = "message"; messageDiv.innerText = transformMessageText(post.message); const createAtDiv = document.createElement("time"); createAtDiv.className = "create_at"; let createAtText = formatHhMm(createAtLocalized); 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; 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); } nodes.push(postDiv); return {nodes, 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.nodes); } 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"] ); byId("channel_contents").dataset["lastTime"] = result.lastTime.getTime(); byId("channel_contents").dataset["lastAuthor"] = result.lastAuthor; byId("channel_contents").append(...result.nodes); 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 + 1 >= el.scrollHeight; } function checkScrolledToBottom() { if (isScrolledToBottom()) { removeClass(byId("channel_contents_wrapper"), "not-at-bottom"); } else { addClass(byId("channel_contents_wrapper"), "not-at-bottom"); } } byId("channel_contents_wrapper").addEventListener("scroll", checkScrolledToBottom); function currentChannel() { return { endpoint: byId("channel_contents").dataset["server"], channel_id: byId("channel_contents").dataset["id"] }; } pubsub.subscribe("MESSAGES_NEW", post => { const chan = currentChannel(); if (post.endpoint === chan.endpoint && post.channel_id === chan.channel_id) { addMessage(mm_client.getOrCreate(post.endpoint), post); } });