feathermost/js/view/messages.js
Midgard f7344dc63f
Improve when time is shown and improve post UI
This commit introduces a heuristic for when to show time, introduces a
date separator and tweaks the margins of posts.
2022-06-11 18:15:31 +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 >= 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);
}
});