added features and fixes
This commit is contained in:
@@ -5,6 +5,7 @@ export interface Member {
|
||||
id: string;
|
||||
username: string;
|
||||
display_name: string;
|
||||
nickname?: string;
|
||||
avatar: string;
|
||||
status: "online" | "idle" | "dnd" | "offline";
|
||||
status_text: string;
|
||||
@@ -15,6 +16,7 @@ interface MemberState {
|
||||
isLoading: boolean;
|
||||
fetchMembers: (serverId: string) => Promise<void>;
|
||||
updateMemberStatus: (userId: string, status: Member["status"]) => void;
|
||||
setMemberNickname: (serverId: string, userId: string, nickname: string) => Promise<void>;
|
||||
}
|
||||
|
||||
export const useMemberStore = create<MemberState>((set) => ({
|
||||
@@ -46,4 +48,17 @@ export const useMemberStore = create<MemberState>((set) => ({
|
||||
}
|
||||
return { membersByServer: next };
|
||||
}),
|
||||
|
||||
setMemberNickname: async (serverId, userId, nickname) => {
|
||||
await api.patch(`/servers/${serverId}/members/${userId}`, { nickname });
|
||||
set((state) => {
|
||||
const list = state.membersByServer[serverId] || [];
|
||||
const updated = list.map((m) =>
|
||||
m.id === userId ? { ...m, nickname: nickname || undefined } : m
|
||||
);
|
||||
return {
|
||||
membersByServer: { ...state.membersByServer, [serverId]: updated },
|
||||
};
|
||||
});
|
||||
},
|
||||
}));
|
||||
|
||||
+116
-3
@@ -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,
|
||||
},
|
||||
};
|
||||
}),
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ export const useTypingStore = create<TypingState>((set, get) => ({
|
||||
[channel_id]: (state.typingUsers[channel_id] || []).filter(u => u.userId !== user_id),
|
||||
},
|
||||
}));
|
||||
}, 3000);
|
||||
}, 6000);
|
||||
|
||||
set(state => ({
|
||||
typingUsers: {
|
||||
|
||||
@@ -115,6 +115,8 @@ export const useWebSocketStore = create<WebSocketState>((set, get) => ({
|
||||
const addMessage = useMessageStore.getState().addMessage;
|
||||
const updateMessage = useMessageStore.getState().updateMessage;
|
||||
const removeMessage = useMessageStore.getState().removeMessage;
|
||||
const addReaction = useMessageStore.getState().addReaction;
|
||||
const removeReaction = useMessageStore.getState().removeReaction;
|
||||
const addChannel = useChannelStore.getState().addChannel;
|
||||
const updateChannel = useChannelStore.getState().updateChannel;
|
||||
const removeChannel = useChannelStore.getState().removeChannel;
|
||||
@@ -163,6 +165,29 @@ export const useWebSocketStore = create<WebSocketState>((set, get) => ({
|
||||
if (ids) removeMessage(ids.channel_id, ids.message_id);
|
||||
break;
|
||||
}
|
||||
case 'REACTION_ADD': {
|
||||
const channelId = typeof payload.channel_id === 'string' ? payload.channel_id : '';
|
||||
const messageId = typeof payload.message_id === 'string' ? payload.message_id : '';
|
||||
const reaction = isRecord(payload.reaction) ? payload.reaction : null;
|
||||
if (channelId && messageId && reaction) {
|
||||
const emoji = typeof reaction.emoji === 'string' ? reaction.emoji : '';
|
||||
const userId = typeof reaction.user_id === 'string' ? reaction.user_id : '';
|
||||
if (emoji && userId) {
|
||||
addReaction(channelId, messageId, emoji, userId);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'REACTION_REMOVE': {
|
||||
const channelId = typeof payload.channel_id === 'string' ? payload.channel_id : '';
|
||||
const messageId = typeof payload.message_id === 'string' ? payload.message_id : '';
|
||||
const emoji = typeof payload.emoji === 'string' ? payload.emoji : '';
|
||||
const userId = typeof payload.user_id === 'string' ? payload.user_id : '';
|
||||
if (channelId && messageId && emoji && userId) {
|
||||
removeReaction(channelId, messageId, emoji, userId);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'CHANNEL_CREATE': {
|
||||
if (isRecord(payload)) addChannel(payload as unknown as Channel);
|
||||
break;
|
||||
|
||||
Reference in New Issue
Block a user