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}