diff --git a/frontend/app/src/components/UserPill.svelte b/frontend/app/src/components/UserPill.svelte index 8fa13f0274..7204c7a795 100644 --- a/frontend/app/src/components/UserPill.svelte +++ b/frontend/app/src/components/UserPill.svelte @@ -11,13 +11,15 @@ export let userOrGroup: UserOrUserGroup; $: avatarUrl = - userOrGroup.kind === "user_group" ? undefined : client.userAvatarUrl(userOrGroup); - $: userId = userOrGroup.kind === "user_group" ? undefined : userOrGroup.userId; + userOrGroup.kind === "user_group" || userOrGroup.kind === "everyone" + ? undefined + : client.userAvatarUrl(userOrGroup); + $: userId = client.userOrUserGroupId(userOrGroup); $: communityMembers = client.currentCommunityMembers; - $: name = userOrGroup.kind === "user_group" ? userOrGroup.name : userOrGroup.username; + $: name = client.userOrUserGroupName(userOrGroup); $: displayName = - userOrGroup.kind === "user_group" + userOrGroup.kind === "user_group" || userOrGroup.kind === "everyone" ? undefined : client.getDisplayName(userOrGroup, $communityMembers); diff --git a/frontend/app/src/components/home/CurrentChatSearchHeader.svelte b/frontend/app/src/components/home/CurrentChatSearchHeader.svelte index 4bc84c3146..377374d596 100644 --- a/frontend/app/src/components/home/CurrentChatSearchHeader.svelte +++ b/frontend/app/src/components/home/CurrentChatSearchHeader.svelte @@ -114,8 +114,7 @@ let expandedText = text.replace(/@([\w\d_]*)/g, (match, p1) => { const userOrGroup = client.lookupUserForMention(p1, true); if (userOrGroup !== undefined) { - if (userOrGroup.kind === "user_group") return ""; - mentionedSet.add(userOrGroup.userId); + mentionedSet.add(client.userOrUserGroupId(userOrGroup) ?? ""); return ""; } else { console.log( @@ -224,8 +223,7 @@ function mention(ev: CustomEvent): void { const userOrGroup = ev.detail; - const username = - userOrGroup.kind === "user_group" ? userOrGroup.name : userOrGroup.username; + const username = client.userOrUserGroupName(userOrGroup); const userLabel = `@${username}`; replaceTextWith(userLabel); diff --git a/frontend/app/src/components/home/Markdown.svelte b/frontend/app/src/components/home/Markdown.svelte index ddbfc376be..a6bf0a4cb1 100644 --- a/frontend/app/src/components/home/Markdown.svelte +++ b/frontend/app/src/components/home/Markdown.svelte @@ -44,6 +44,11 @@ }); } + function replaceEveryone(text: string): string { + if (!text.includes("@everyone")) return text; + return text.replace(/(^|[\s(){}\[\]])(@everyone)($|[\s(){}\[\]])/gm, "$1**$2**$3"); + } + function replaceDatetimes(text: string): string { return text.replace(/@DateTime\((\d+)\)/g, (_, p1) => { return client.toDatetimeString(new Date(Number(p1))); @@ -51,7 +56,9 @@ } $: { - let parsed = replaceUserGroupIds(replaceUserIds(replaceDatetimes(text)), $userGroups); + let parsed = replaceEveryone( + replaceUserGroupIds(replaceUserIds(replaceDatetimes(text)), $userGroups) + ); try { if (inline) { parsed = marked.parseInline(parsed, options) as string; diff --git a/frontend/app/src/components/home/MentionPicker.svelte b/frontend/app/src/components/home/MentionPicker.svelte index c20ee5556e..14eeeda745 100644 --- a/frontend/app/src/components/home/MentionPicker.svelte +++ b/frontend/app/src/components/home/MentionPicker.svelte @@ -40,6 +40,9 @@ prefixLower === undefined || (supportsUserGroups && userOrGroup.name.toLowerCase().startsWith(prefixLower)) ); + case "everyone": { + return prefixLower === undefined || userOrGroup.kind.startsWith(prefixLower); + } default: return ( (mentionSelf || userOrGroup.userId !== currentUser.userId) && @@ -62,7 +65,7 @@ onMount(() => { usersAndGroups = Object.values(client.getUserLookupForMentions()).sort( (a: UserOrUserGroup, b: UserOrUserGroup) => { - const order = { user_group: 1, user: 2, bot: 3 }; + const order = { everyone: 1, user_group: 2, user: 3, bot: 4 }; return order[a.kind] - order[b.kind]; } ); @@ -113,7 +116,7 @@ p.userId} items={filtered} let:item let:itemIndex> mention(item)}>
- {#if item.kind === "user_group"} + {#if item.kind === "user_group" || item.kind === "everyone"}
@@ -129,6 +132,10 @@ {item.name} + {:else if item.kind === "everyone"} + + {"everyone"} + {:else} {client.getDisplayName(item, $communityMembers)} diff --git a/frontend/app/src/components/home/MessageEntry.svelte b/frontend/app/src/components/home/MessageEntry.svelte index ff21c535e3..a299d598c6 100644 --- a/frontend/app/src/components/home/MessageEntry.svelte +++ b/frontend/app/src/components/home/MessageEntry.svelte @@ -260,6 +260,8 @@ switch (userOrGroup.kind) { case "user_group": return `@UserGroup(${userOrGroup.id})`; + case "everyone": + return "@everyone"; default: mentionedMap.set(userOrGroup.userId, userOrGroup); return `@UserId(${userOrGroup.userId})`; @@ -432,8 +434,7 @@ function mention(ev: CustomEvent): void { const userOrGroup = ev.detail; - const username = - userOrGroup.kind === "user_group" ? userOrGroup.name : userOrGroup.username; + const username = client.userOrUserGroupName(userOrGroup); const userLabel = `@${username}`; replaceTextWith(userLabel); diff --git a/frontend/openchat-client/src/openchat.ts b/frontend/openchat-client/src/openchat.ts index df1e76277c..9ce15aa5a1 100644 --- a/frontend/openchat-client/src/openchat.ts +++ b/frontend/openchat-client/src/openchat.ts @@ -349,6 +349,9 @@ import { toTitleCase, CommonResponses, defaultChatRules, + userOrUserGroupName, + userOrUserGroupId, + extractUserIdsFromMentions, } from "openchat-shared"; import { failedMessagesStore } from "./stores/failedMessages"; import { @@ -1203,6 +1206,9 @@ export class OpenChat extends OpenChatAgentWorker { formatMessageDate = formatMessageDate; userIdsFromEvents = userIdsFromEvents; missingUserIds = missingUserIds; + userOrUserGroupName = userOrUserGroupName; + userOrUserGroupId = userOrUserGroupId; + extractUserIdsFromMentions = extractUserIdsFromMentions; toRecord2 = toRecord2; toDatetimeString = toDatetimeString; groupBySender = groupBySender; @@ -4306,11 +4312,6 @@ export class OpenChat extends OpenChatAgentWorker { } } - // FIXME - this is duplicated - private extractUserIdsFromMentions(text: string): string[] { - return [...text.matchAll(/@UserId\(([\d\w-]+)\)/g)].map((m) => m[1]); - } - private userIdsFromChatSummaries(chats: ChatSummary[]): Set { const userIds = new Set(); chats.forEach((chat) => { @@ -4852,6 +4853,7 @@ export class OpenChat extends OpenChatAgentWorker { const userGroups = [...this._liveState.selectedCommunity.userGroups.values()]; userGroups.forEach((ug) => (lookup[ug.name.toLowerCase()] = ug)); } + lookup["everyone"] = { kind: "everyone" }; this._userLookupForMentions = lookup; } return this._userLookupForMentions; @@ -4865,6 +4867,7 @@ export class OpenChat extends OpenChatAgentWorker { switch (userOrGroup.kind) { case "user_group": + case "everyone": return userOrGroup; default: return includeSelf || userOrGroup.userId !== this.user.userId diff --git a/frontend/openchat-shared/src/domain/user/index.ts b/frontend/openchat-shared/src/domain/user/index.ts index 9be5e6ac64..a135878284 100644 --- a/frontend/openchat-shared/src/domain/user/index.ts +++ b/frontend/openchat-shared/src/domain/user/index.ts @@ -1,2 +1,2 @@ export * from "./user"; -export { extractUserIdsFromMentions, missingUserIds, userStatus } from "./user.utils"; +export { extractUserIdsFromMentions, missingUserIds, userStatus, userOrUserGroupName, userOrUserGroupId } from "./user.utils"; diff --git a/frontend/openchat-shared/src/domain/user/user.ts b/frontend/openchat-shared/src/domain/user/user.ts index cd2744b181..3b72a4c797 100644 --- a/frontend/openchat-shared/src/domain/user/user.ts +++ b/frontend/openchat-shared/src/domain/user/user.ts @@ -1,6 +1,16 @@ import type { DataContent } from "../data/data"; -export type UserOrUserGroup = UserSummary | UserGroupSummary; +export type UserOrUserGroup = UserSummary | UserGroupSummary | MentionEveryone; + +export type UserSummary = DataContent & { + kind: "user" | "bot"; + userId: string; + username: string; + displayName: string | undefined; + updated: bigint; + suspended: boolean; + diamond: boolean; +}; export type UserGroupSummary = { kind: "user_group"; @@ -9,6 +19,10 @@ export type UserGroupSummary = { id: number; }; +export type MentionEveryone = { + kind: "everyone"; +} + export type UserGroupDetails = { kind: "user_group"; members: Set; @@ -25,16 +39,6 @@ export type IdentityState = | "upgrading_user" | "upgrade_user"; -export type UserSummary = DataContent & { - kind: "user" | "bot"; - userId: string; - username: string; - displayName: string | undefined; - updated: bigint; - suspended: boolean; - diamond: boolean; -}; - export type UserLookup = Record; export type User = { diff --git a/frontend/openchat-shared/src/domain/user/user.utils.ts b/frontend/openchat-shared/src/domain/user/user.utils.ts index 5cf66e2a2e..93926a693f 100644 --- a/frontend/openchat-shared/src/domain/user/user.utils.ts +++ b/frontend/openchat-shared/src/domain/user/user.utils.ts @@ -1,5 +1,5 @@ import { ONLINE_THRESHOLD } from "../../constants"; -import type { UserLookup } from "./user"; +import type { UserLookup, UserOrUserGroup } from "./user"; import { UserStatus } from "./user"; export function userStatus(lastOnline: number | undefined, now: number): UserStatus { @@ -22,4 +22,21 @@ const mentionRegex = /@UserId\(([\d\w-]+)\)/g; export function extractUserIdsFromMentions(text: string): string[] { return [...text.matchAll(mentionRegex)].map((m) => m[1]); +} + +export function userOrUserGroupName(u: UserOrUserGroup): string { + switch (u.kind) { + case "user_group": return u.name; + case "everyone": return u.kind; + default: return u.username; + } +} + +export function userOrUserGroupId(u: UserOrUserGroup): string | undefined { + switch (u.kind) { + case "user": + case "bot": + return u.username; + default: return undefined; + } } \ No newline at end of file diff --git a/frontend/openchat-shared/src/index.ts b/frontend/openchat-shared/src/index.ts index 051b0131a7..987d906f6c 100644 --- a/frontend/openchat-shared/src/index.ts +++ b/frontend/openchat-shared/src/index.ts @@ -1,3 +1,2 @@ export * from "./domain"; -export * from "./utils"; -export { missingUserIds } from "./domain/user/user.utils"; +export * from "./utils"; \ No newline at end of file