diff --git a/assets/main.css b/assets/main.css index f449903..5e50106 100644 --- a/assets/main.css +++ b/assets/main.css @@ -215,15 +215,25 @@ ul#server_selection_list { margin-bottom: 0.5em; } +.date_separator { + font-weight: bold; + margin-top: 0.3em; + font-size: 75%; + text-align: center; + color: #555; + border-top: 1px solid #ccc; +} + .post { - padding: 0.1em; - margin: 0.6em 0 0.2em; + padding: 0.2em; + margin-top: 0.3em; display: grid; - grid-template-rows: auto auto; + grid-template-rows: auto auto auto; grid-template-columns: auto auto; + min-height: 3ex; } .post.same_author { - margin-top: 0.2em; + margin-top: 0; } .post:first-child { margin-top: 8px; @@ -234,6 +244,9 @@ ul#server_selection_list { .post:hover { background-color: #e4e4e4; } +.post:not(.show_time) { + position: relative; +} .post .author { grid-row: 1; @@ -242,7 +255,7 @@ ul#server_selection_list { font-weight: bold; align-self: end; } -.post.same_author .author { +.post.same_author:not(.show_time) .author { display: none; } @@ -254,8 +267,16 @@ ul#server_selection_list { color: #888; align-self: end; } -.post.same_author .create_at { +.post:not(.show_time) .create_at { display: none; + position: absolute; + background-color: #e4e4e4; + padding: 0 0.2em; + right: 0; + top: 0; +} +.post:not(.show_time):hover .create_at { + display: block; } .post .message { @@ -264,8 +285,15 @@ ul#server_selection_list { grid-column-start: 1; grid-column-end: 3; } +.post.special .message { + color: #555; + font-size: 75%; +} .post ul.attachments { + grid-row: 3; + grid-column-start: 1; + grid-column-end: 3; margin: 0; padding: 0; list-style: none; diff --git a/js/util.js b/js/util.js index 1697ba9..2c7aaf0 100644 --- a/js/util.js +++ b/js/util.js @@ -90,8 +90,9 @@ function toTimeZone(date, timezoneString) { return new Date(date.toLocaleString("en-US", {timeZone: timezoneString})); } -function formatDdddMmYy(date) { - return `${date.getFullYear()}-${padLeft(date.getMonth() + 1, 2, "0")}-${padLeft(date.getDate(), 2, "0")}`; +function formatYyyyMmDdWeekday(date) { + const weekday = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"][date.getDay()]; + return `${date.getFullYear()}-${padLeft(date.getMonth() + 1, 2, "0")}-${padLeft(date.getDate(), 2, "0")}, ${weekday}`; } function formatHhMm(date) { diff --git a/js/view/messages.js b/js/view/messages.js index e52842d..87f1635 100644 --- a/js/view/messages.js +++ b/js/view/messages.js @@ -8,25 +8,63 @@ function transformMessageText(message) { } +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); - let createAt = new Date(post.create_at); - if (client.me.timezone.useAutomaticTimezone !== "true") { - createAt = toTimeZone(createAt, client.me.timezone.manualTimezone); - } 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); + let createAtText = formatHhMm(createAtLocalized); createAtDiv.title = createAt.toString(); createAtDiv.innerText = createAtText; createAtDiv.dateTime = createAt.toISOString(); @@ -36,8 +74,6 @@ async function createMessageElement(client, post, lastTime, lastAuthor) { authorDiv.className = "author"; authorDiv.innerText = authorName; - const postDiv = document.createElement("div"); - postDiv.className = "post"; if (lastAuthor === post.user_id) { postDiv.className += " same_author"; } @@ -72,7 +108,9 @@ async function createMessageElement(client, post, lastTime, lastAuthor) { postDiv.appendChild(attachmentsUl); } - return {postDiv, lastTime, lastAuthor}; + nodes.push(postDiv); + + return {nodes, lastTime, lastAuthor}; } @@ -84,7 +122,7 @@ async function populateChannelContents(client, channel, contents) { let nodes = []; for (let id of contents.order) { result = await createMessageElement(client, contents.posts[id], result.lastTime, result.lastAuthor); - nodes.push(result.postDiv); + nodes.push(...result.nodes); } byId("channel_contents").dataset["server"] = client.endpoint; @@ -106,7 +144,7 @@ async function addMessage(client, post) { ); byId("channel_contents").dataset["lastTime"] = result.lastTime.getTime(); byId("channel_contents").dataset["lastAuthor"] = result.lastAuthor; - byId("channel_contents").appendChild(result.postDiv); + byId("channel_contents").append(...result.nodes); if (shouldScroll) { scrollToBottom();