diff options
-rw-r--r-- | index.js | 26 | ||||
-rw-r--r-- | static/index.html | 2 | ||||
-rw-r--r-- | static/style.css | 52 |
3 files changed, 53 insertions, 27 deletions
diff --git a/index.js b/index.js index dae7762..d5de68d 100644 --- a/index.js +++ b/index.js @@ -3,7 +3,7 @@ const {promises: {readFile}, readFileSync, createReadStream, createWriteStream} const {createServer} = require('node:http') const {createHash, randomBytes} = require('node:crypto') const Readline = require('node:readline'); -const { stdin, stdout } = require('node:process'); +const {stdin, stdout} = require('node:process'); const {EventEmitter} = require('node:events') const readline = Readline.createInterface({ @@ -176,7 +176,7 @@ async function handleGetPage(req, res) { currentUsers[userId] = nonce setTimeout(() => { if (currentUsers[userId] === nonce) { - chatEvents.emit('event', {type: 'part', userId, args: {nick}}) + chatEvents.emit('event', {timestamp: Date.now(), type: 'part', userId, args: {nick}}) delete currentUsers[userId] } }, 3000) @@ -187,7 +187,7 @@ async function handleGetPage(req, res) { res.on('finish', onDisconnect) if (!(userId in currentUsers)) { - chatEvents.emit('event', {type: 'join', userId, args: {nick}}) + chatEvents.emit('event', {timestamp: Date.now(), type: 'join', userId, args: {nick}}) } currentUsers[userId] = true } @@ -219,7 +219,7 @@ async function handleMessagePost(req, res) { const messageId = randomBytes(20).toString('hex').slice(0, 7) if (content) - chatEvents.emit('event', {type: 'message', userId, args: {messageId, content, nick}}) + chatEvents.emit('event', {timestamp: Date.now(), type: 'message', userId, args: {messageId, content, nick}}) res.writeHead(303, {location: '/'}).end() } @@ -231,7 +231,7 @@ async function handleDeletePost(req, res) { const userId = getUserId(pass) if (messageId) - chatEvents.emit('event', {type: 'delete', userId, args: {messageId}}) + chatEvents.emit('event', {timestamp: Date.now(), type: 'delete', userId, args: {messageId}}) res.writeHead(303, {location: '/'}).end() } @@ -244,7 +244,7 @@ async function handleEditPost(req, res) { const userId = getUserId(pass) if (messageId && content) - chatEvents.emit('event', {type: 'edit', userId, args: {messageId, content}}) + chatEvents.emit('event', {timestamp: Date.now(), type: 'edit', userId, args: {messageId, content}}) res.writeHead(303, {location: '/'}).end() } @@ -262,16 +262,17 @@ async function handleSavePost(req, res) { writeCookie(res, 'semi-pass', newPass) if ((oldNick && oldNick !== newNick)) - chatEvents.emit('event', {type: 'nickChange', userId: oldUserId, args: {oldNick, newNick, oldUserId, newUserId}}) + chatEvents.emit('event', {timestamp: Date.now(), type: 'nickChange', userId: oldUserId, args: {oldNick, newNick, oldUserId, newUserId}}) res.writeHead(303, {location: '/'}).end() } -function renderEvent({type, userId: eventUserId, args}, {userId: currentUserId, live}) { +function renderEvent({type, timestamp, userId: eventUserId, args}, {userId: currentUserId, live}) { switch (type) { case 'message': return ` <div class="message${(currentUserId === eventUserId) ? ' currentUser' : ''}" data-user-id="${sanitizeText(eventUserId)}" data-message-id="${sanitizeText(args.messageId)}"> + ${timestamp ? `<span class="time" title="(all times in UTC)">${(new Date(timestamp)).toLocaleTimeString('en-US', {timeZone: 'UTC', hour12: false, hour: '2-digit', minute: '2-digit'})}</span>`: ''} <span class="nick" title="${sanitizeText(eventUserId)}">${sanitizeText(args.nick)}</span> <span class="content"><span class="orig">${sanitizeText(args.content)}</span></span> ${(currentUserId === eventUserId) ? `<div class="actions"> @@ -327,19 +328,22 @@ function renderEvent({type, userId: eventUserId, args}, {userId: currentUserId, case 'nickChange': return ` <div class="nickChange"> - (<span class="nick" title="${sanitizeText(args.oldUserId)}">${sanitizeText(args.oldNick)}</span> changed name to <span class="new" title="${sanitizeText(args.newUserId)}">${sanitizeText(args.newNick)}</span>) + ${timestamp ? `<span class="time" title="(all times in UTC)">${(new Date(timestamp)).toLocaleTimeString('en-US', {timeZone: 'UTC', hour12: false, hour: '2-digit', minute: '2-digit'})}</span>`: ''} + <span class="sysmessage">(<span class="nick" title="${sanitizeText(args.oldUserId)}">${sanitizeText(args.oldNick)}</span> changed name to <span class="new" title="${sanitizeText(args.newUserId)}">${sanitizeText(args.newNick)}</span>)</span> </div>` case 'join': return ` <div class="join"> - (<span class="nick" title="${sanitizeText(eventUserId)}">${sanitizeText(args.nick)}</span> joined the chat) + ${timestamp ? `<span class="time" title="(all times in UTC)">${(new Date(timestamp)).toLocaleTimeString('en-US', {timeZone: 'UTC', hour12: false, hour: '2-digit', minute: '2-digit'})}</span>`: ''} + <span class="sysmessage">(<span class="nick" title="${sanitizeText(eventUserId)}">${sanitizeText(args.nick)}</span> joined the chat)</span> </div>` case 'part': return ` <div class="part"> - (<span class="nick" title="${sanitizeText(eventUserId)}">${sanitizeText(args.nick)}</span> left the chat) + ${timestamp ? `<span class="time" title="(all times in UTC)">${(new Date(timestamp)).toLocaleTimeString('en-US', {timeZone: 'UTC', hour12: false, hour: '2-digit', minute: '2-digit'})}</span>`: ''} + <span class="sysmessage">(<span class="nick" title="${sanitizeText(eventUserId)}">${sanitizeText(args.nick)}</span> left the chat)</span> </div>` } } diff --git a/static/index.html b/static/index.html index b411bb2..77718dc 100644 --- a/static/index.html +++ b/static/index.html @@ -16,7 +16,7 @@ </form> </div> <div id="footer"> - <p>created by tempest-vi</p> + <p>created by tempest</p> <button type="button" popovertarget="settings_modal" popovertargetaction="toggle">Settings</button> <dialog popover id="settings_modal"> <iframe width="100%" height="100%" style="border: none;" src="/settings"></iframe> diff --git a/static/style.css b/static/style.css index 9957ac9..7f50b68 100644 --- a/static/style.css +++ b/static/style.css @@ -23,17 +23,25 @@ body { body > div#messages { order: -1; display: grid; - grid-template-columns: fit-content(120px) 1fr fit-content(80px); + grid-template-columns: fit-content(60px) fit-content(120px) 1fr fit-content(80px); } .message { display: contents; } -.message .nick { +.message .time { + grid-column: 1 / 2; padding: 4px 8px; padding-left: 16px; - grid-column: 1 / 2; + display: flex; + flex-direction: row; + align-items: center; +} + +.message .nick { + padding: 4px 8px; + grid-column: 2 / 3; overflow-x: hidden; text-wrap: nowrap; text-overflow: ellipsis; @@ -42,13 +50,13 @@ body > div#messages { } .message .content { - grid-column: 2 / 4; + grid-column: 3 / 5; padding: 4px 8px; align-content: center; } .message:has(.actions) .content { - grid-column: 2 / 3; + grid-column: 3 / 4; } .message .actions { @@ -57,7 +65,7 @@ body > div#messages { flex-direction: row; justify-content: end; align-items: center; - grid-column: 3 / 4; + grid-column: 4 / 5; align-content: center; } @@ -89,20 +97,34 @@ body > div#messages { /* background: #def6ff; */ } - & > .nick { + & > .time{ border-left: solid 6px #88dbfb; padding-left: 10px; } } -.nickChange, -.join, -.part { - grid-column: 1 / 4; - opacity: .4; - padding: 4px 16px; - background: #d3d3d342; - font-size: .95em; +#messages *:has(> .sysmessage) { + display: contents; + + .time { + grid-column: 1 / 2; + background: #d3d3d342; + font-size: .95em; + padding: 4px 8px; + padding-left: 16px; + opacity: .4; + display: flex; + flex-direction: row; + align-items: center; + } + + .sysmessage { + grid-column: 2 / 5; + background: #d3d3d342; + font-size: .95em; + padding: 4px 8px; + opacity: .4; + } } #chat { |