diff --git a/TODO.md b/TODO.md index ffc8397..8152f7c 100644 --- a/TODO.md +++ b/TODO.md @@ -14,7 +14,7 @@ A more complex version of this list is available [here](https://trello.com/b/kJw - [x] Notifications - [x] Make the damn textbox stop unfocusing on every message submit - [ ] Message context menus -- [ ] Message Timestamps +- [x] Message Timestamps - [x] Markdown Support - [x] More Secure database passwords - [x] Minio diff --git a/prodServer.ts b/prodServer.ts index fb602ed..2a1ebf7 100644 --- a/prodServer.ts +++ b/prodServer.ts @@ -20,13 +20,15 @@ io.on('connection', async (socket) => { if (msg.content !== '') { console.log(`\x1b[35m[ws:kit]\x1b[0m message from ${socket.id}: ${msg.content}`); // Store the message in the database - await db.sendMessage(msg.channel, msg.content, msg.id, uuidv4()); + const timestamp = new Date(); + await db.sendMessage(msg.channel, msg.content, msg.id, uuidv4(), timestamp); const sender = authdb.getUser(msg.id); io!.emit('message', { user: sender.username, message: msg.content, imageSrc: sender.image, channel: msg.channel, + timestamp: timestamp.getTime(), }); } }); diff --git a/src/lib/components/message.svelte b/src/lib/components/message.svelte index 82f31ec..c180950 100644 --- a/src/lib/components/message.svelte +++ b/src/lib/components/message.svelte @@ -2,7 +2,7 @@ import { type TypeMessage } from '$lib/types'; import Prose from '$lib/components/prose.svelte'; import renderMarkdown from '$lib/functions/renderMarkdown'; - const { message, imageSrc, user }: TypeMessage = $props(); + const { message, imageSrc, user, timestamp }: TypeMessage = $props();
@@ -12,7 +12,20 @@
-

{user}

+

+ {user} + ยท + {new Date(timestamp).toLocaleDateString('en-US', { + year: 'numeric', + month: 'short', + day: 'numeric', + hour12: true, + hour: 'numeric', + minute: 'numeric', + })} +

{@html renderMarkdown(message)}
diff --git a/src/lib/functions/clientWebsocket.svelte.ts b/src/lib/functions/clientWebsocket.svelte.ts index e32330e..dbeb397 100644 --- a/src/lib/functions/clientWebsocket.svelte.ts +++ b/src/lib/functions/clientWebsocket.svelte.ts @@ -34,6 +34,7 @@ class Websocket { message: newMsg.message, imageSrc: newMsg.imageSrc, user: newMsg.user, + timestamp: newMsg.timestamp, }, ...this.messages, ]; diff --git a/src/lib/functions/websocketConfig.ts b/src/lib/functions/websocketConfig.ts index eef4a03..ef53bf5 100644 --- a/src/lib/functions/websocketConfig.ts +++ b/src/lib/functions/websocketConfig.ts @@ -28,13 +28,15 @@ export function startupSocketIOServer(httpServer: HttpServer | null) { if (msg.content !== '') { console.log(`\x1b[35m[ws:kit]\x1b[0m message from ${socket.id}: ${msg.content}`); // Store the message in the database - await db.sendMessage(msg.channel, msg.content, msg.id, uuidv4()); + const timestamp = new Date(); + await db.sendMessage(msg.channel, msg.content, msg.id, uuidv4(), timestamp); const sender = authdb.getUser(msg.id); io!.emit('message', { user: sender.username, message: msg.content, imageSrc: sender.image, channel: msg.channel, + timestamp: timestamp.getTime(), }); } }); diff --git a/src/lib/server/db/index.ts b/src/lib/server/db/index.ts index f01b7a7..a3c0a73 100644 --- a/src/lib/server/db/index.ts +++ b/src/lib/server/db/index.ts @@ -6,6 +6,12 @@ interface Messages { error: Error | null; } +interface CassandraTimestamp { + low: number; + high: number; + unsigned: boolean; +} + function createDelay(ms: number) { return new Promise((res) => setTimeout(res, ms)); } @@ -69,16 +75,15 @@ class Db { } // Send message method - async sendMessage(channelName: string, content: string, sender: string, id: string) { + async sendMessage(channelName: string, content: string, sender: string, id: string, timestamp: Date) { try { - const now = new Date(); channelName = sanitizeChannelName(channelName); - await this.client.execute(`INSERT INTO channels.${channelName} (id, message_content, channel_name, timestamp, sender) VALUES (?, ?, ?, ?, ?)`, { + await this.client.execute(`INSERT INTO channels.${channelName} (id, message_content, channel_name, sender, timestamp) VALUES (?, ?, ?, ?, ?)`, { id, message_content: content, channel_name: channelName, - timestamp: now.getTime(), sender, + timestamp, }); } catch (e) { console.error(`Error storing message: ${e as Error}`); @@ -130,6 +135,13 @@ class Db { }; } } + + // Timestamp to Epoch method + tsEpoch(ts: CassandraTimestamp) { + const low = ts.low >>> 0; + const high = ts.high >>> 0; + return high * 2 ** 32 + low; + } } const db = new Db(); diff --git a/src/lib/types/index.ts b/src/lib/types/index.ts index e5ef4af..8a348ff 100644 --- a/src/lib/types/index.ts +++ b/src/lib/types/index.ts @@ -2,6 +2,7 @@ export interface TypeMessage { message: string; imageSrc: string; user: string; + timestamp: number; } export interface TypeFullMessage { @@ -9,4 +10,5 @@ export interface TypeFullMessage { message: string; imageSrc: string; user: string; + timestamp: number; } diff --git a/src/routes/(main)/channel/[channel]/+page.server.ts b/src/routes/(main)/channel/[channel]/+page.server.ts index dc7a5a7..11787d9 100644 --- a/src/routes/(main)/channel/[channel]/+page.server.ts +++ b/src/routes/(main)/channel/[channel]/+page.server.ts @@ -31,6 +31,7 @@ export async function load({ params, request }): Promise { user: sender.username, imageSrc: sender.image, channel: value.channel, + timestamp: db.tsEpoch(value.timestamp), }; }) : []; diff --git a/src/routes/(main)/channel/[channel]/+page.svelte b/src/routes/(main)/channel/[channel]/+page.svelte index f9b5031..a26764c 100644 --- a/src/routes/(main)/channel/[channel]/+page.svelte +++ b/src/routes/(main)/channel/[channel]/+page.svelte @@ -71,7 +71,7 @@ {#snippet message(messages: TypeMessage[])} {#each messages as message} - + {/each} {/snippet}