feat: Add initial frontend components and their corresponding build assets, along with generated API types and configuration.
Some checks failed
Build and Release / build-and-release (push) Failing after 7m50s

This commit is contained in:
Bryan1029384756
2026-02-11 06:24:33 -06:00
parent cb4361da1a
commit c472f0ee2d
369 changed files with 1423 additions and 395 deletions

View File

@@ -19,6 +19,7 @@ import type * as members from "../members.js";
import type * as messages from "../messages.js";
import type * as presence from "../presence.js";
import type * as reactions from "../reactions.js";
import type * as readState from "../readState.js";
import type * as roles from "../roles.js";
import type * as typing from "../typing.js";
import type * as voice from "../voice.js";
@@ -42,6 +43,7 @@ declare const fullApi: ApiFromModules<{
messages: typeof messages;
presence: typeof presence;
reactions: typeof reactions;
readState: typeof readState;
roles: typeof roles;
typing: typeof typing;
voice: typeof voice;

View File

@@ -7,7 +7,8 @@ type TableWithChannelIndex =
| "channelKeys"
| "dmParticipants"
| "typingIndicators"
| "voiceStates";
| "voiceStates"
| "channelReadState";
async function deleteByChannel(
ctx: GenericMutationCtx<DataModel>,
@@ -175,6 +176,7 @@ export const remove = mutation({
await deleteByChannel(ctx, "dmParticipants", args.id);
await deleteByChannel(ctx, "typingIndicators", args.id);
await deleteByChannel(ctx, "voiceStates", args.id);
await deleteByChannel(ctx, "channelReadState", args.id);
await ctx.db.delete(args.id);

113
convex/readState.ts Normal file
View File

@@ -0,0 +1,113 @@
import { query, mutation } from "./_generated/server";
import { v } from "convex/values";
// Get read state for a single channel
export const getReadState = query({
args: {
userId: v.id("userProfiles"),
channelId: v.id("channels"),
},
returns: v.union(
v.object({
lastReadTimestamp: v.number(),
}),
v.null()
),
handler: async (ctx, args) => {
const state = await ctx.db
.query("channelReadState")
.withIndex("by_user_and_channel", (q) =>
q.eq("userId", args.userId).eq("channelId", args.channelId)
)
.unique();
if (!state) return null;
return { lastReadTimestamp: state.lastReadTimestamp };
},
});
// Mark a channel as read up to a given timestamp
export const markRead = mutation({
args: {
userId: v.id("userProfiles"),
channelId: v.id("channels"),
lastReadTimestamp: v.number(),
},
returns: v.null(),
handler: async (ctx, args) => {
const existing = await ctx.db
.query("channelReadState")
.withIndex("by_user_and_channel", (q) =>
q.eq("userId", args.userId).eq("channelId", args.channelId)
)
.unique();
if (existing) {
// Only update if timestamp is newer
if (args.lastReadTimestamp > existing.lastReadTimestamp) {
await ctx.db.patch(existing._id, {
lastReadTimestamp: args.lastReadTimestamp,
});
}
} else {
await ctx.db.insert("channelReadState", {
userId: args.userId,
channelId: args.channelId,
lastReadTimestamp: args.lastReadTimestamp,
});
}
return null;
},
});
// Get all read states for a user (used by Sidebar)
export const getAllReadStates = query({
args: {
userId: v.id("userProfiles"),
},
returns: v.array(
v.object({
channelId: v.id("channels"),
lastReadTimestamp: v.number(),
})
),
handler: async (ctx, args) => {
const states = await ctx.db
.query("channelReadState")
.withIndex("by_user", (q) => q.eq("userId", args.userId))
.collect();
return states.map((s) => ({
channelId: s.channelId,
lastReadTimestamp: s.lastReadTimestamp,
}));
},
});
// Get the latest message timestamp per channel (used by Sidebar)
export const getLatestMessageTimestamps = query({
args: {
channelIds: v.array(v.id("channels")),
},
returns: v.array(
v.object({
channelId: v.id("channels"),
latestTimestamp: v.number(),
})
),
handler: async (ctx, args) => {
const results = [];
for (const channelId of args.channelIds) {
const latestMsg = await ctx.db
.query("messages")
.withIndex("by_channel", (q) => q.eq("channelId", channelId))
.order("desc")
.first();
if (latestMsg) {
results.push({
channelId,
latestTimestamp: latestMsg._creationTime,
});
}
}
return results;
},
});

View File

@@ -106,4 +106,13 @@ export default defineSchema({
})
.index("by_channel", ["channelId"])
.index("by_user", ["userId"]),
channelReadState: defineTable({
userId: v.id("userProfiles"),
channelId: v.id("channels"),
lastReadTimestamp: v.number(),
})
.index("by_user", ["userId"])
.index("by_channel", ["channelId"])
.index("by_user_and_channel", ["userId", "channelId"]),
});