feathermost/js/view/messages.js
Midgard 68f87f4048
Fix "not scrolled to bottom" glow when zoomed
Allow 1px difference in isScrolledToBottom to allow for small
differences that can occur when the page zoom has been set to e.g. 110%
2022-06-17 14:03:27 +02:00

197 lines
5.6 KiB
JavaScript

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);
}
});