add @ mentions in chat

This commit is contained in:
JCEEE
2026-09-09 20:57:25 +01:00
parent 3f95d50d2e
commit 8a02640cdc
9 changed files with 296 additions and 40 deletions
+58 -6
View File
@@ -1,3 +1,5 @@
import { sendMentionEmail } from '../email';
export type ChatActor = {
id: string;
type: 'admin' | 'member';
@@ -5,19 +7,33 @@ export type ChatActor = {
color: string;
};
// Everyone in the family (children + parents) for the @mention picker.
export async function chatMe(pb: any, famId: string, actor: ChatActor) {
return { famId, actor };
const users = await pb
.collection('users')
.getFullList({ filter: `famId = '${famId}'` })
.catch(() => []);
return {
famId,
actor,
members: users.map((m: any) => ({
id: m.id,
name: m.name || '',
color: m.color || '#6366f1',
role: m.role === 'parent' ? 'parent' : 'child'
}))
};
}
export async function send(
pb: any,
famId: string,
actor: ChatActor,
body: { content?: string; clientId?: string }
body: { content?: string; clientId?: string; siteUrl?: string }
) {
const content = (body.content || '').trim();
if (!content) throw new Error('content required');
return pb.collection('messages').create({
const record = await pb.collection('messages').create({
famId,
authorType: actor.type,
authorId: actor.id,
@@ -27,14 +43,50 @@ export async function send(
createdAt: new Date().toISOString(),
clientId: body.clientId ? String(body.clientId).slice(0, 64) : ''
});
// Email any @mentioned parents (their in-app toast is client-side, via the
// same mention tokens). Fire-and-forget — never fail the message itself.
notifyMentionedParents(pb, famId, actor, record, body.siteUrl || '').catch(() => {});
return record;
}
export async function typing(
// Scan the message for `@Parent Name` tokens (case-insensitive) and email every
// mentioned parent who isn't the sender, with a link back to the family site.
async function notifyMentionedParents(
pb: any,
famId: string,
actor: ChatActor,
body: { typing?: boolean }
message: any,
siteUrl: string
) {
const content = String(message.content || '');
const parents = await pb
.collection('users')
.getFullList({ filter: `famId = '${famId}' && role = 'parent'` })
.catch(() => []);
const mentioned = parents.filter((u: any) => {
if (u.id === actor.id || !u.email) return false;
const name = (u.name || '').trim().toLowerCase();
return !!name && content.toLowerCase().includes('@' + name);
});
if (!mentioned.length) return;
const fam = await pb
.collection('fams')
.getOne(famId)
.catch(() => null);
const dashboardUrl = `${siteUrl}/${fam?.slug || famId}`;
for (const parent of mentioned) {
await sendMentionEmail({
to: parent.email,
parentName: parent.name || 'there',
authorName: message.authorName || 'Family member',
message: content,
dashboardUrl
});
}
}
export async function typing(pb: any, famId: string, actor: ChatActor, body: { typing?: boolean }) {
const existing = await pb.collection('chat_typing').getFullList({
filter: `famId = '${famId}' && actorId = '${actor.id}' && actorType = '${actor.type}'`
});
@@ -52,4 +104,4 @@ export async function typing(
await pb.collection('chat_typing').create(row);
}
return { ok: true };
}
}