added features and fixes

This commit is contained in:
2026-07-02 15:34:00 +00:00
parent eb5b7d55a4
commit 89f8381e2d
30 changed files with 1718 additions and 206 deletions
+116 -3
View File
@@ -10,6 +10,12 @@ export interface MessageEmbed {
site_name?: string;
}
export interface Reaction {
emoji: string;
count: number;
users: string[];
}
export interface Message {
id: string;
channel_id: string;
@@ -19,6 +25,8 @@ export interface Message {
content: string;
reply_to?: string | null;
embeds?: MessageEmbed[];
pinned: boolean;
reactions?: Reaction[];
created_at: string;
edited_at: string | null;
}
@@ -29,6 +37,7 @@ export interface SearchResultMessage extends Message {
export interface MessageState {
messagesByChannel: Record<string, Message[]>;
pinnedMessagesByChannel: Record<string, Message[]>;
searchResultsByChannel: Record<string, SearchResultMessage[]>;
selectedMessageIds: Record<string, Set<string>>; // ponytail: per-channel bulk selection
isLoading: boolean;
@@ -39,9 +48,14 @@ export interface MessageState {
fetchOlderMessages: (channelId: string) => Promise<void>;
sendMessage: (channelId: string, content: string, replyTo?: string) => Promise<Message>;
searchMessages: (channelId: string, query: string) => Promise<SearchResultMessage[]>;
pinMessage: (channelId: string, messageId: string) => Promise<void>;
unpinMessage: (channelId: string, messageId: string) => Promise<void>;
fetchPinnedMessages: (channelId: string) => Promise<Message[]>;
addMessage: (message: Message) => void;
updateMessage: (message: Message) => void;
removeMessage: (channelId: string, messageId: string) => void;
addReaction: (channelId: string, messageId: string, emoji: string, userId: string) => void;
removeReaction: (channelId: string, messageId: string, emoji: string, userId: string) => void;
bulkDeleteMessages: (channelId: string, messageIds: string[]) => Promise<{ deleted: number }>;
toggleSelectedMessage: (channelId: string, messageId: string) => void;
clearSelectedMessages: (channelId: string) => void;
@@ -49,6 +63,7 @@ export interface MessageState {
export const useMessageStore = create<MessageState>((set, get) => ({
messagesByChannel: {},
pinnedMessagesByChannel: {},
searchResultsByChannel: {},
selectedMessageIds: {},
isLoading: false,
@@ -139,6 +154,28 @@ export const useMessageStore = create<MessageState>((set, get) => ({
return message;
},
pinMessage: async (channelId, messageId) => {
const updated = await api.put<Message>(`/channels/${channelId}/messages/${messageId}/pin`, {});
get().updateMessage(updated);
},
unpinMessage: async (channelId, messageId) => {
const updated = await api.delete<Message>(`/channels/${channelId}/messages/${messageId}/pin`);
get().updateMessage(updated);
},
fetchPinnedMessages: async (channelId) => {
const pinned = await api.get<Message[]>(`/channels/${channelId}/messages/pinned`);
const list = Array.isArray(pinned) ? pinned : [];
set((state) => ({
pinnedMessagesByChannel: {
...state.pinnedMessagesByChannel,
[channelId]: list,
},
}));
return list;
},
addMessage: (message) =>
set((state) => {
const list = state.messagesByChannel[message.channel_id] || [];
@@ -156,12 +193,35 @@ export const useMessageStore = create<MessageState>((set, get) => ({
updateMessage: (message) =>
set((state) => {
const list = state.messagesByChannel[message.channel_id] || [];
const updatedList = list.map((m) =>
m.id === message.id ? message : m,
);
const pinnedList = state.pinnedMessagesByChannel[message.channel_id] || [];
let updatedPinned = [...pinnedList];
if (message.pinned) {
if (!pinnedList.some((m) => m.id === message.id)) {
updatedPinned = [message, ...pinnedList].sort(
(a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()
);
} else {
updatedPinned = pinnedList.map((m) => (m.id === message.id ? message : m));
}
} else {
updatedPinned = pinnedList.filter((m) => m.id !== message.id);
}
if (updatedPinned.length > 5) {
updatedPinned = updatedPinned.slice(0, 5);
}
return {
messagesByChannel: {
...state.messagesByChannel,
[message.channel_id]: list.map((m) =>
m.id === message.id ? message : m,
),
[message.channel_id]: updatedList,
},
pinnedMessagesByChannel: {
...state.pinnedMessagesByChannel,
[message.channel_id]: updatedPinned,
},
};
}),
@@ -169,11 +229,64 @@ export const useMessageStore = create<MessageState>((set, get) => ({
removeMessage: (channelId, messageId) =>
set((state) => {
const list = state.messagesByChannel[channelId] || [];
const pinnedList = state.pinnedMessagesByChannel[channelId] || [];
return {
messagesByChannel: {
...state.messagesByChannel,
[channelId]: list.filter((m) => m.id !== messageId),
},
pinnedMessagesByChannel: {
...state.pinnedMessagesByChannel,
[channelId]: pinnedList.filter((m) => m.id !== messageId),
},
};
}),
addReaction: (channelId, messageId, emoji, userId) =>
set((state) => {
const list = state.messagesByChannel[channelId] || [];
const updatedList = list.map((m) => {
if (m.id !== messageId) return m;
const reactions = m.reactions ? [...m.reactions] : [];
const existing = reactions.find((r) => r.emoji === emoji);
if (existing) {
if (!existing.users.includes(userId)) {
existing.users = [...existing.users, userId];
existing.count = existing.users.length;
}
} else {
reactions.push({ emoji, count: 1, users: [userId] });
}
return { ...m, reactions };
});
return {
messagesByChannel: {
...state.messagesByChannel,
[channelId]: updatedList,
},
};
}),
removeReaction: (channelId, messageId, emoji, userId) =>
set((state) => {
const list = state.messagesByChannel[channelId] || [];
const updatedList = list.map((m) => {
if (m.id !== messageId) return m;
if (!m.reactions) return m;
const reactions = m.reactions
.map((r) => {
if (r.emoji !== emoji) return r;
const users = r.users.filter((id) => id !== userId);
return { ...r, users, count: users.length };
})
.filter((r) => r.count > 0);
return { ...m, reactions };
});
return {
messagesByChannel: {
...state.messagesByChannel,
[channelId]: updatedList,
},
};
}),