feat: merge alex version
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import localforage from "localforage";
|
||||
import { User } from "../../types/user";
|
||||
import { ContactStatus, User } from "../../types/user";
|
||||
import clearTable from "./clear.handler";
|
||||
|
||||
export default async function handler({ operation, data, payload }) {
|
||||
@@ -18,6 +18,17 @@ export default async function handler({ operation, data, payload }) {
|
||||
await table.setItems(arr);
|
||||
}
|
||||
break;
|
||||
case "updateContactStatus":
|
||||
{
|
||||
const tmp = payload as { uid: number, status: ContactStatus } | { uid: number, status: ContactStatus }[];
|
||||
const arr = Array.isArray(tmp) ? tmp : [tmp];
|
||||
const opts = arr.map(({ uid, status }) => {
|
||||
return { key: uid + "", value: { ...data.byId[uid], status } };
|
||||
});
|
||||
|
||||
await table.setItems(opts);
|
||||
}
|
||||
break;
|
||||
case "updateUsersByLogs":
|
||||
{
|
||||
const changeLogs = payload;
|
||||
|
||||
@@ -102,8 +102,13 @@ const baseQueryWithTokenCheck = async (args: any, api: any, extraOptions: any) =
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 403:
|
||||
toast.error("Request Not Allowed");
|
||||
case 403: {
|
||||
const whiteList403 = ["sendMsg"];
|
||||
if (!whiteList403.includes(api.endpoint)) {
|
||||
toast.error("Request Not Allowed");
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case 404:
|
||||
{
|
||||
|
||||
@@ -117,6 +117,25 @@ export const channelApi = createApi({
|
||||
}
|
||||
}
|
||||
}),
|
||||
createPrivateInviteLink: builder.query<string, number | void>({
|
||||
query: (gid) => ({
|
||||
headers: {
|
||||
"content-type": "text/plain",
|
||||
accept: "text/plain"
|
||||
},
|
||||
// 七天过期
|
||||
url: `/group/create_invite_private_magic_link?expired_in=604800&max_times=1&gid=${gid}`,
|
||||
responseHandler: "text"
|
||||
}),
|
||||
transformResponse: (link: string) => {
|
||||
// 确保http开头
|
||||
const _link = link.startsWith("http") ? link : `http://${link}`;
|
||||
// return _link;
|
||||
// 替换掉域名
|
||||
const invite = new URL(_link);
|
||||
return `${location.origin}${invite.pathname}${invite.hash}${invite.search}`;
|
||||
}
|
||||
}),
|
||||
removeChannel: builder.query<void, number>({
|
||||
query: (id) => ({
|
||||
url: `/group/${id}`,
|
||||
@@ -186,6 +205,13 @@ export const channelApi = createApi({
|
||||
body: members
|
||||
})
|
||||
}),
|
||||
joinPrivateChannel: builder.mutation<Channel, { magic_token: string }>({
|
||||
query: (body) => ({
|
||||
url: `/user/join_private`,
|
||||
method: "POST",
|
||||
body
|
||||
})
|
||||
}),
|
||||
updateIcon: builder.mutation<void, { gid: number; image: File }>({
|
||||
query: ({ gid, image }) => ({
|
||||
headers: {
|
||||
@@ -216,6 +242,8 @@ export const {
|
||||
useChangeChannelTypeMutation,
|
||||
useLazyLeaveChannelQuery,
|
||||
useLazyCreateInviteLinkQuery,
|
||||
useJoinPrivateChannelMutation,
|
||||
useLazyCreatePrivateInviteLinkQuery,
|
||||
useCreateInviteLinkQuery,
|
||||
useGetChannelQuery,
|
||||
useLazyGetChannelQuery,
|
||||
|
||||
@@ -3,7 +3,7 @@ import { batch } from "react-redux";
|
||||
import { ContentTypes } from "../config";
|
||||
import { addChannelMsg, removeChannelMsg } from "../slices/message.channel";
|
||||
import { addUserMsg, removeUserMsg } from "../slices/message.user";
|
||||
import { addMessage, removeMessage } from "../slices/message";
|
||||
import { addMessage, removeMessage, updateMessage } from "../slices/message";
|
||||
// import { ContentType } from "../../types/message";
|
||||
|
||||
export const onMessageSendStarted = async (
|
||||
@@ -57,10 +57,16 @@ export const onMessageSendStarted = async (
|
||||
dispatch(removeMessage(ts));
|
||||
}, 300);
|
||||
// dispatch(removePendingMessage({ id, mid:ts, type: from }));
|
||||
} catch {
|
||||
toast.error("Send Message Failed");
|
||||
dispatch(removeContextMessage({ id, mid: ts }));
|
||||
dispatch(removeMessage(ts));
|
||||
} catch (error) {
|
||||
if (error?.error?.status == 403) {
|
||||
// 403 means blocked
|
||||
// toast.error(`Failed to send, blocked maybe.`,);
|
||||
dispatch(updateMessage({ mid: ts, failed: true }));
|
||||
} else {
|
||||
toast.error(`Send Message Failed ${JSON.stringify(error)}`,);
|
||||
dispatch(removeContextMessage({ id, mid: ts }));
|
||||
dispatch(removeMessage(ts));
|
||||
}
|
||||
// patchResult.undo();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2,18 +2,19 @@ import { createApi } from "@reduxjs/toolkit/query/react";
|
||||
// import toast from "react-hot-toast";
|
||||
import baseQuery from "./base.query";
|
||||
import { updateAutoDeleteSetting, updateMute } from "../slices/footprint";
|
||||
import { fillUsers } from "../slices/users";
|
||||
import { fillUsers, updateContactStatus as updateStatus } from "../slices/users";
|
||||
import BASE_URL, { ContentTypes } from "../config";
|
||||
import { onMessageSendStarted } from "./handlers";
|
||||
import { AutoDeleteMsgDTO, BotAPIKey, User, UserCreateDTO, UserDTO, UserForAdmin, UserForAdminDTO } from "../../types/user";
|
||||
import { AutoDeleteMsgDTO, BotAPIKey, ContactAction, ContactResponse, ContactStatus, User, UserCreateDTO, UserDTO, UserForAdmin, UserForAdminDTO } from "../../types/user";
|
||||
import { ContentTypeKey, MuteDTO } from "../../types/message";
|
||||
import { RootState } from "../store";
|
||||
|
||||
export const userApi = createApi({
|
||||
reducerPath: "userApi",
|
||||
baseQuery,
|
||||
endpoints: (builder) => ({
|
||||
getUsers: builder.query<User[], void>({
|
||||
query: () => ({ url: `user` }),
|
||||
query: () => ({ url: `/user` }),
|
||||
transformResponse: (data: User[]) => {
|
||||
return data.map((user) => {
|
||||
return {
|
||||
@@ -25,12 +26,38 @@ export const userApi = createApi({
|
||||
};
|
||||
});
|
||||
},
|
||||
async onQueryStarted(data, { dispatch, queryFulfilled, getState }) {
|
||||
try {
|
||||
const { data: users } = await queryFulfilled;
|
||||
const { authData: { user: loginUser } } = getState() as RootState;
|
||||
dispatch(fillUsers(users.map((u) => {
|
||||
const status = loginUser?.uid == u.uid ? "added" : "";
|
||||
return {
|
||||
...u,
|
||||
status
|
||||
};
|
||||
})));
|
||||
} catch {
|
||||
console.log("get user list error");
|
||||
}
|
||||
}
|
||||
}),
|
||||
getContacts: builder.query<ContactResponse[], void>({
|
||||
query: () => ({ url: `/user/contacts` }),
|
||||
async onQueryStarted(data, { dispatch, queryFulfilled }) {
|
||||
try {
|
||||
const { data: users } = await queryFulfilled;
|
||||
dispatch(fillUsers(users));
|
||||
const payloads = users.map((c) => {
|
||||
const uid = c.target_uid;
|
||||
const status = c.contact_info.status;
|
||||
return {
|
||||
uid,
|
||||
status
|
||||
};
|
||||
});
|
||||
dispatch(updateStatus(payloads));
|
||||
} catch {
|
||||
console.log("get user list error");
|
||||
console.log("get contact list error");
|
||||
}
|
||||
}
|
||||
}),
|
||||
@@ -44,6 +71,27 @@ export const userApi = createApi({
|
||||
method: "POST"
|
||||
})
|
||||
}),
|
||||
searchUser: builder.mutation<User, { search_type: "id" | "email", keyword: string }>({
|
||||
query: (input) => ({
|
||||
url: `/user/search`,
|
||||
body: input,
|
||||
method: "POST"
|
||||
})
|
||||
}),
|
||||
pinChat: builder.mutation<void, { uid: number } | { gid: number }>({
|
||||
query: (data) => ({
|
||||
url: `/user/pin_chat`,
|
||||
method: "POST",
|
||||
body: { target: data }
|
||||
})
|
||||
}),
|
||||
unpinChat: builder.mutation<void, { uid: number } | { gid: number }>({
|
||||
query: (data) => ({
|
||||
url: `/user/unpin_chat`,
|
||||
method: "POST",
|
||||
body: { target: data }
|
||||
})
|
||||
}),
|
||||
updateUser: builder.mutation<UserForAdmin, UserForAdminDTO>({
|
||||
query: ({ id, ...rest }) => ({
|
||||
url: `/admin/user/${id}`,
|
||||
@@ -75,6 +123,28 @@ export const userApi = createApi({
|
||||
}
|
||||
}),
|
||||
|
||||
updateContactStatus: builder.mutation<void, { action: ContactAction, target_uid: number }>({
|
||||
query: (payload) => ({
|
||||
url: `/user/update_contact_status`,
|
||||
method: "POST",
|
||||
body: payload
|
||||
}),
|
||||
async onQueryStarted(data, { dispatch, queryFulfilled }) {
|
||||
const map = {
|
||||
"add": "added",
|
||||
"block": "blocked",
|
||||
"remove": "",
|
||||
"unblock": "",
|
||||
};
|
||||
try {
|
||||
await queryFulfilled;
|
||||
const status = map[data.action] as ContactStatus;
|
||||
dispatch(updateStatus({ uid: data.target_uid, status }));
|
||||
} catch (error) {
|
||||
console.log("update mute failed", error);
|
||||
}
|
||||
}
|
||||
}),
|
||||
updateMuteSetting: builder.mutation<void, MuteDTO>({
|
||||
query: (data) => ({
|
||||
url: `/user/mute`,
|
||||
@@ -152,6 +222,7 @@ export const userApi = createApi({
|
||||
body: type == "file" ? JSON.stringify(content) : content
|
||||
}),
|
||||
async onQueryStarted(param1, param2) {
|
||||
// @ts-ignore
|
||||
await onMessageSendStarted.call(this, param1, param2, "user");
|
||||
}
|
||||
}),
|
||||
@@ -160,6 +231,7 @@ export const userApi = createApi({
|
||||
});
|
||||
|
||||
export const {
|
||||
useLazyGetUsersQuery,
|
||||
useGetUserByAdminQuery,
|
||||
useUpdateAvatarByAdminMutation,
|
||||
useUpdateAutoDeleteMsgMutation,
|
||||
@@ -169,10 +241,13 @@ export const {
|
||||
useLazyDeleteUserQuery,
|
||||
useUpdateInfoMutation,
|
||||
useUpdateAvatarMutation,
|
||||
useGetUsersQuery,
|
||||
useLazyGetUsersQuery,
|
||||
useLazyGetContactsQuery,
|
||||
useSendMsgMutation,
|
||||
useCreateBotAPIKeyMutation,
|
||||
useLazyDeleteBotAPIKeyQuery,
|
||||
useGetBotAPIKeysQuery
|
||||
useGetBotAPIKeysQuery,
|
||||
useSearchUserMutation,
|
||||
useUpdateContactStatusMutation,
|
||||
usePinChatMutation,
|
||||
useUnpinChatMutation
|
||||
} = userApi;
|
||||
|
||||
@@ -53,7 +53,7 @@ const authDataSlice = createSlice({
|
||||
const { initialized = true, user, token, refresh_token, expired_in = 0 } = payload;
|
||||
const { uid, create_by } = user;
|
||||
state.initialized = initialized;
|
||||
state.user = { ...state.user, ...user };
|
||||
state.user = { ...state.user, ...user, status: "added" };
|
||||
state.guest = create_by == "guest";
|
||||
state.token = token;
|
||||
state.refreshToken = refresh_token;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
import { MuteDTO } from "../../types/message";
|
||||
import { OG } from "../../types/resource";
|
||||
import { AutoDeleteMessageSettingDTO, AutoDeleteMsgForGroup, AutoDeleteMsgForUser, AutoDeleteSettingForChannels, AutoDeleteSettingForUsers } from "../../types/sse";
|
||||
import { AutoDeleteMessageSettingDTO, AutoDeleteMsgForGroup, AutoDeleteMsgForUser, AutoDeleteSettingForChannels, AutoDeleteSettingForUsers, PinChat, PinChatTarget } from "../../types/sse";
|
||||
import { resetAuthData } from "./auth.data";
|
||||
|
||||
type ChannelAside = "members" | "voice" | "voice_fullscreen" | null;
|
||||
@@ -20,6 +20,7 @@ export interface State {
|
||||
autoDeleteMsgChannels: AutoDeleteMsgForGroup[];
|
||||
channelAsides: { [cid: number]: ChannelAside };
|
||||
dmAsides: { [uid: number]: DMAside };
|
||||
pinChats: PinChat[];
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +37,8 @@ const initialState: State = {
|
||||
autoDeleteMsgUsers: [],
|
||||
autoDeleteMsgChannels: [],
|
||||
channelAsides: {},
|
||||
dmAsides: {}
|
||||
dmAsides: {},
|
||||
pinChats: []
|
||||
};
|
||||
|
||||
const footprintSlice = createSlice({
|
||||
@@ -60,7 +62,8 @@ const footprintSlice = createSlice({
|
||||
autoDeleteMsgUsers = [],
|
||||
autoDeleteMsgChannels = [],
|
||||
channelAsides = {},
|
||||
dmAsides = {}
|
||||
dmAsides = {},
|
||||
pinChats = []
|
||||
} = action.payload;
|
||||
return {
|
||||
og,
|
||||
@@ -75,7 +78,8 @@ const footprintSlice = createSlice({
|
||||
autoDeleteMsgUsers,
|
||||
autoDeleteMsgChannels,
|
||||
channelAsides,
|
||||
dmAsides
|
||||
dmAsides,
|
||||
pinChats
|
||||
};
|
||||
},
|
||||
updateUsersVersion(state, action: PayloadAction<number>) {
|
||||
@@ -164,6 +168,22 @@ const footprintSlice = createSlice({
|
||||
}
|
||||
});
|
||||
},
|
||||
upsertPinChats(state, action: PayloadAction<{ pins: PinChat[], override?: boolean }>) {
|
||||
const { pins, override = false } = action.payload;
|
||||
if (override) {
|
||||
state.pinChats = pins;
|
||||
} else {
|
||||
state.pinChats = [...pins, ...state.pinChats];
|
||||
}
|
||||
},
|
||||
removePinChats(state, action: PayloadAction<PinChatTarget[]>) {
|
||||
const pins = action.payload;
|
||||
state.pinChats = state.pinChats.filter(pin => {
|
||||
const key = "uid" in pin.target ? "uid" : "gid";
|
||||
// @ts-ignore
|
||||
return !pins.some(p => p[key] == pin.target[key]);
|
||||
});
|
||||
},
|
||||
upsertOG(state, action: PayloadAction<{ key: string, value: OG }>) {
|
||||
const { key, value } = action.payload;
|
||||
state.og[key] = value;
|
||||
@@ -228,7 +248,9 @@ export const {
|
||||
updateHistoryMark,
|
||||
upsertOG,
|
||||
updateChannelVisibleAside,
|
||||
updateDMVisibleAside
|
||||
updateDMVisibleAside,
|
||||
upsertPinChats,
|
||||
removePinChats
|
||||
} = footprintSlice.actions;
|
||||
|
||||
export default footprintSlice.reducer;
|
||||
|
||||
@@ -22,6 +22,7 @@ export interface MessagePayload {
|
||||
thumbnail?: string;
|
||||
edited?: boolean | number;
|
||||
reply_mid?: number;
|
||||
failed?: boolean;
|
||||
}
|
||||
export interface State {
|
||||
[key: number]: MessagePayload;
|
||||
|
||||
@@ -9,8 +9,6 @@ export interface StoredServer extends Server {
|
||||
link: string;
|
||||
expire: number;
|
||||
};
|
||||
show_user_online_status: boolean,
|
||||
webclient_auto_update: boolean
|
||||
}
|
||||
const initialState: StoredServer = {
|
||||
version: "",
|
||||
@@ -23,7 +21,7 @@ const initialState: StoredServer = {
|
||||
expire: 0
|
||||
},
|
||||
show_user_online_status: false,
|
||||
webclient_auto_update: true
|
||||
webclient_auto_update: true,
|
||||
};
|
||||
|
||||
const serverSlice = createSlice({
|
||||
@@ -45,7 +43,7 @@ const serverSlice = createSlice({
|
||||
name = "",
|
||||
description = "",
|
||||
show_user_online_status = true,
|
||||
webclient_auto_update = true
|
||||
webclient_auto_update = true,
|
||||
} = action.payload || {};
|
||||
return { version, upgraded, name, logo, description, inviteLink, show_user_online_status, webclient_auto_update };
|
||||
},
|
||||
|
||||
+12
-4
@@ -1,11 +1,11 @@
|
||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
import { isNull, omitBy } from "lodash";
|
||||
import BASE_URL from "../config";
|
||||
import { User } from "../../types/user";
|
||||
import { Contact, ContactStatus } from "../../types/user";
|
||||
import { UserLog, UserState } from "../../types/sse";
|
||||
|
||||
type DMAside = "voice" | null;
|
||||
export interface StoredUser extends User {
|
||||
export interface StoredUser extends Contact {
|
||||
online?: boolean;
|
||||
voice?: boolean;
|
||||
avatar?: string;
|
||||
@@ -16,7 +16,6 @@ export interface State {
|
||||
ids: number[];
|
||||
byId: { [id: number]: StoredUser };
|
||||
}
|
||||
|
||||
const initialState: State = {
|
||||
ids: [],
|
||||
byId: {}
|
||||
@@ -69,6 +68,7 @@ const usersSlice = createSlice({
|
||||
? ""
|
||||
: `${BASE_URL}/resource/avatar?uid=${uid}&t=${rest.avatar_updated_at}`,
|
||||
create_by: "", // todo: missing properties create_by
|
||||
status: state.byId[uid]?.status ?? "",
|
||||
...rest
|
||||
};
|
||||
const idx = state.ids.findIndex((i) => i == uid);
|
||||
@@ -98,9 +98,17 @@ const usersSlice = createSlice({
|
||||
state.byId[uid]!.online = online;
|
||||
}
|
||||
});
|
||||
},
|
||||
updateContactStatus(state, action: PayloadAction<{ uid: number, status: ContactStatus } | { uid: number, status: ContactStatus }[]>) {
|
||||
const arr = Array.isArray(action.payload) ? action.payload : [action.payload];
|
||||
arr.forEach((data) => {
|
||||
if (state.byId[data.uid]) {
|
||||
state.byId[data.uid]!.status = data.status;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export const { resetUsers, fillUsers, updateUsersByLogs, updateUsersStatus } = usersSlice.actions;
|
||||
export const { updateContactStatus, resetUsers, fillUsers, updateUsersByLogs, updateUsersStatus } = usersSlice.actions;
|
||||
export default usersSlice.reducer;
|
||||
|
||||
Reference in New Issue
Block a user