main f6d8405

messagesInternal.ts RawHistoryFolder

1import { internalQuery, internalMutation } from './_generated/server';
2import { v } from 'convex/values';
3
4// Get messages by chat room ID (internal)
5export const getByRoom = internalQuery({
6	args: {
7		roomId: v.id('chatRooms'),
8		limit: v.optional(v.number()),
9		beforeTimestamp: v.optional(v.number())
10	},
11	handler: async (ctx, args) => {
12		const limit = args.limit ?? 100;
13
14		const allMessages = await ctx.db
15			.query('messages')
16			.withIndex('by_chatRoom', (q) => q.eq('chatRoomId', args.roomId))
17			.collect();
18
19		// Filter by timestamp if provided
20		let filtered = allMessages;
21		if (args.beforeTimestamp) {
22			filtered = allMessages.filter((m) => m.timestamp < args.beforeTimestamp!);
23		}
24
25		// Sort by timestamp descending and limit
26		filtered.sort((a, b) => b.timestamp - a.timestamp);
27		const messages = filtered.slice(0, limit);
28
29		return {
30			messages,
31			hasMore: filtered.length > limit
32		};
33	}
34});
35
36// Send message (internal)
37export const send = internalMutation({
38	args: {
39		chatRoomId: v.id('chatRooms'),
40		senderId: v.id('users'),
41		content: v.string(),
42		type: v.optional(v.string()),
43		styleData: v.optional(v.string()),
44		hasFormatting: v.optional(v.boolean()),
45		timestamp: v.optional(v.number())
46	},
47	handler: async (ctx, args) => {
48		// Verify room exists
49		const room = await ctx.db.get(args.chatRoomId);
50		if (!room) {
51			throw new Error('Chat room not found');
52		}
53
54		// Verify user exists
55		const user = await ctx.db.get(args.senderId);
56		if (!user) {
57			throw new Error('User not found');
58		}
59
60		// Direct rooms are restricted to their two members
61		if (room.type === 'direct' && !(room.memberIds ?? []).includes(args.senderId)) {
62			throw new Error('Not a member of this conversation');
63		}
64
65		const messageId = await ctx.db.insert('messages', {
66			chatRoomId: args.chatRoomId,
67			senderId: args.senderId,
68			content: args.content,
69			type: args.type ?? 'chat',
70			timestamp: args.timestamp ?? Date.now(),
71			styleData: args.styleData,
72			hasFormatting: args.hasFormatting ?? false
73		});
74
75		return await ctx.db.get(messageId);
76	}
77});
78
79// Get recent messages across all rooms (internal) - for SSE polling
80export const getRecent = internalQuery({
81	args: {
82		sinceTimestamp: v.number(),
83		limit: v.optional(v.number())
84	},
85	handler: async (ctx, args) => {
86		const limit = args.limit ?? 50;
87
88		const messages = await ctx.db
89			.query('messages')
90			.withIndex('by_timestamp')
91			.filter((q) => q.gt(q.field('timestamp'), args.sinceTimestamp))
92			.take(limit);
93
94		return messages;
95	}
96});

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 3

Tags 8

↑↓ moveenter openesc close