feat: GIF search (Klipy), stickers, emoji picker, and bug fixes
- Add GIF search powered by Klipy API with correct response mapping (file.sm/hd tiers, not flat files structure) - Add sticker system: packs, upload with auto-downscale, send in messages - Add tabbed InputPopover with emoji, GIF, and sticker pickers - Fix GIF API key migration race condition (column-add loop vs rename) - Fix masked API key corruption on settings save (server + client guards) - Fix sticker packs 403 (reversed isMember parameter order) - Fix emoji picker not filling popover width (perLine 8→9, CSS 100%) - Add error logging for Klipy API failures
This commit is contained in:
@@ -38,6 +38,7 @@ interface ChatState {
|
||||
clearAllMessages: () => void;
|
||||
loadMoreMessages: (channelId: string) => Promise<boolean>;
|
||||
sendMessage: (channelId: string, content: string, attachmentIds?: string[]) => Promise<void>;
|
||||
sendStickerMessage: (channelId: string, stickerId: string) => Promise<void>;
|
||||
editMessage: (messageId: string, content: string, channelId: string) => Promise<void>;
|
||||
deleteMessage: (messageId: string, channelId: string) => Promise<void>;
|
||||
addMessage: (channelId: string, message: MessageWithUser) => void;
|
||||
@@ -289,6 +290,45 @@ export const useChatStore = create<ChatState>((set, get) => ({
|
||||
}
|
||||
},
|
||||
|
||||
sendStickerMessage: async (channelId: string, stickerId: string) => {
|
||||
const isDm = isDmChannel(channelId);
|
||||
const currentUser = useAuthStore.getState().user;
|
||||
const origin = getChannelOrigin(channelId);
|
||||
const client = getApiForOrigin(origin);
|
||||
|
||||
// Optimistic message
|
||||
const tempId = `temp_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
||||
if (currentUser) {
|
||||
const optimisticMessage: MessageWithUser = {
|
||||
id: tempId,
|
||||
channelId: isDm ? '' : channelId,
|
||||
userId: currentUser.id,
|
||||
content: null,
|
||||
replyToId: null,
|
||||
editedAt: null,
|
||||
createdAt: Date.now(),
|
||||
user: currentUser,
|
||||
attachments: [],
|
||||
reactions: [],
|
||||
stickerId,
|
||||
};
|
||||
if (isDm) {
|
||||
(optimisticMessage as any).dmChannelId = channelId;
|
||||
}
|
||||
get().addMessage(channelId, optimisticMessage);
|
||||
}
|
||||
|
||||
try {
|
||||
if (isDm) {
|
||||
await client.dm.sendMessage(channelId, { stickerId });
|
||||
} else {
|
||||
await client.channels.sendMessage(channelId, { content: '', stickerId });
|
||||
}
|
||||
} catch {
|
||||
get().removeMessage(tempId, channelId);
|
||||
}
|
||||
},
|
||||
|
||||
editMessage: async (messageId: string, content: string, channelId: string) => {
|
||||
const isDm = isDmChannel(channelId);
|
||||
const origin = getChannelOrigin(channelId);
|
||||
|
||||
@@ -6,10 +6,12 @@ interface SettingsState {
|
||||
streamingLimits: InstanceStreamingLimits | null;
|
||||
instanceSettings: InstanceAdminSettings | null;
|
||||
isAdmin: boolean;
|
||||
gifEnabled: boolean;
|
||||
fetchStreamingLimits: () => Promise<void>;
|
||||
updateStreamingLimits: (limits: Partial<InstanceStreamingLimits>) => Promise<void>;
|
||||
fetchInstanceSettings: () => Promise<void>;
|
||||
updateInstanceSettings: (data: Partial<InstanceAdminSettings>) => Promise<void>;
|
||||
fetchGifEnabled: () => Promise<void>;
|
||||
setIsAdmin: (isAdmin: boolean) => void;
|
||||
}
|
||||
|
||||
@@ -32,6 +34,7 @@ export const useSettingsStore = create<SettingsState>((set) => ({
|
||||
streamingLimits: null,
|
||||
instanceSettings: null,
|
||||
isAdmin: false,
|
||||
gifEnabled: false,
|
||||
|
||||
fetchStreamingLimits: async () => {
|
||||
try {
|
||||
@@ -70,5 +73,14 @@ export const useSettingsStore = create<SettingsState>((set) => ({
|
||||
}
|
||||
},
|
||||
|
||||
fetchGifEnabled: async () => {
|
||||
try {
|
||||
const { enabled } = await api.gif.enabled();
|
||||
set({ gifEnabled: enabled });
|
||||
} catch {
|
||||
set({ gifEnabled: false });
|
||||
}
|
||||
},
|
||||
|
||||
setIsAdmin: (isAdmin: boolean) => set({ isAdmin }),
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user