feat: remark
This commit is contained in:
+1
-1
@@ -12,7 +12,7 @@ const prices: Price[] = [
|
||||
},
|
||||
];
|
||||
const official_dev = `https://dev.voce.chat`;
|
||||
const local_dev = `https://dev.voce.chat`;
|
||||
const local_dev = `http://localhost:3000`;
|
||||
// const local_dev = `http://chat.jcdl369.top:3009`;
|
||||
const dev_origin = process.env.REACT_APP_OFFICIAL_DEMO ? official_dev : local_dev;
|
||||
|
||||
|
||||
+53
-44
@@ -11,7 +11,8 @@ import {
|
||||
UserCreateDTO,
|
||||
UserDTO,
|
||||
UserForAdmin,
|
||||
UserForAdminDTO
|
||||
UserForAdminDTO,
|
||||
UserRemarkDTO,
|
||||
} from "@/types/user";
|
||||
import BASE_URL, { ContentTypes } from "../config";
|
||||
import { updateAutoDeleteSetting, updateMute } from "../slices/footprint";
|
||||
@@ -36,7 +37,7 @@ export const userApi = createApi({
|
||||
avatar:
|
||||
user.avatar_updated_at == 0
|
||||
? ""
|
||||
: `${BASE_URL}/resource/avatar?uid=${user.uid}&t=${user.avatar_updated_at}`
|
||||
: `${BASE_URL}/resource/avatar?uid=${user.uid}&t=${user.avatar_updated_at}`,
|
||||
};
|
||||
});
|
||||
},
|
||||
@@ -44,7 +45,7 @@ export const userApi = createApi({
|
||||
try {
|
||||
const { data: users } = await queryFulfilled;
|
||||
const {
|
||||
authData: { user: loginUser }
|
||||
authData: { user: loginUser },
|
||||
} = getState() as RootState;
|
||||
dispatch(
|
||||
fillUsers(
|
||||
@@ -52,7 +53,7 @@ export const userApi = createApi({
|
||||
const status = loginUser?.uid == u.uid ? "added" : "";
|
||||
return {
|
||||
...u,
|
||||
status
|
||||
status,
|
||||
};
|
||||
})
|
||||
)
|
||||
@@ -60,7 +61,7 @@ export const userApi = createApi({
|
||||
} catch {
|
||||
console.log("get user list error");
|
||||
}
|
||||
}
|
||||
},
|
||||
}),
|
||||
getContacts: builder.query<ContactResponse[], void>({
|
||||
query: () => ({ url: `/user/contacts` }),
|
||||
@@ -72,59 +73,66 @@ export const userApi = createApi({
|
||||
const status = c.contact_info.status;
|
||||
return {
|
||||
uid,
|
||||
status
|
||||
status,
|
||||
};
|
||||
});
|
||||
dispatch(updateStatus(payloads));
|
||||
} catch {
|
||||
console.log("get contact list error");
|
||||
}
|
||||
}
|
||||
},
|
||||
}),
|
||||
deleteUser: builder.query<void, number>({
|
||||
query: (uid) => ({ url: `/admin/user/${uid}`, method: "DELETE" })
|
||||
query: (uid) => ({ url: `/admin/user/${uid}`, method: "DELETE" }),
|
||||
}),
|
||||
createUser: builder.mutation<UserForAdmin, UserCreateDTO>({
|
||||
query: (data) => ({
|
||||
url: `/admin/user`,
|
||||
body: data,
|
||||
method: "POST"
|
||||
})
|
||||
method: "POST",
|
||||
}),
|
||||
}),
|
||||
searchUser: builder.mutation<User, { search_type: "id" | "email" | "name"; keyword: string }>({
|
||||
query: (input) => ({
|
||||
url: `/user/search`,
|
||||
body: input,
|
||||
method: "POST"
|
||||
})
|
||||
method: "POST",
|
||||
}),
|
||||
}),
|
||||
pinChat: builder.mutation<void, { uid: number } | { gid: number }>({
|
||||
query: (data) => ({
|
||||
url: `/user/pin_chat`,
|
||||
method: "POST",
|
||||
body: { target: data }
|
||||
})
|
||||
body: { target: data },
|
||||
}),
|
||||
}),
|
||||
unpinChat: builder.mutation<void, { uid: number } | { gid: number }>({
|
||||
query: (data) => ({
|
||||
url: `/user/unpin_chat`,
|
||||
method: "POST",
|
||||
body: { target: data }
|
||||
})
|
||||
body: { target: data },
|
||||
}),
|
||||
}),
|
||||
updateUser: builder.mutation<UserForAdmin, UserForAdminDTO>({
|
||||
query: ({ id, ...rest }) => ({
|
||||
url: `/admin/user/${id}`,
|
||||
body: rest,
|
||||
method: "PUT"
|
||||
})
|
||||
method: "PUT",
|
||||
}),
|
||||
}),
|
||||
updateRemark: builder.mutation<void, UserRemarkDTO>({
|
||||
query: (data) => ({
|
||||
url: `/user/contact_remark`,
|
||||
body: data,
|
||||
method: "PUT",
|
||||
}),
|
||||
}),
|
||||
|
||||
updateAutoDeleteMsg: builder.mutation<void, AutoDeleteMsgDTO>({
|
||||
query: (data) => ({
|
||||
url: `/user/burn-after-reading`,
|
||||
body: data,
|
||||
method: "POST"
|
||||
method: "POST",
|
||||
}),
|
||||
async onQueryStarted(data, { dispatch, queryFulfilled }) {
|
||||
try {
|
||||
@@ -140,21 +148,21 @@ export const userApi = createApi({
|
||||
} catch {
|
||||
console.log("update auto delete message setting error");
|
||||
}
|
||||
}
|
||||
},
|
||||
}),
|
||||
|
||||
updateContactStatus: builder.mutation<void, { action: ContactAction; target_uid: number }>({
|
||||
query: (payload) => ({
|
||||
url: `/user/update_contact_status`,
|
||||
method: "POST",
|
||||
body: payload
|
||||
body: payload,
|
||||
}),
|
||||
async onQueryStarted(data, { dispatch, queryFulfilled }) {
|
||||
const map = {
|
||||
add: "added",
|
||||
block: "blocked",
|
||||
remove: "",
|
||||
unblock: ""
|
||||
unblock: "",
|
||||
};
|
||||
try {
|
||||
await queryFulfilled;
|
||||
@@ -163,13 +171,13 @@ export const userApi = createApi({
|
||||
} catch (error) {
|
||||
console.log("update mute failed", error);
|
||||
}
|
||||
}
|
||||
},
|
||||
}),
|
||||
updateMuteSetting: builder.mutation<void, MuteDTO>({
|
||||
query: (data) => ({
|
||||
url: `/user/mute`,
|
||||
method: "POST",
|
||||
body: data
|
||||
body: data,
|
||||
}),
|
||||
async onQueryStarted(data, { dispatch, queryFulfilled }) {
|
||||
try {
|
||||
@@ -178,51 +186,51 @@ export const userApi = createApi({
|
||||
} catch (error) {
|
||||
console.log("update mute failed", error);
|
||||
}
|
||||
}
|
||||
},
|
||||
}),
|
||||
updateAvatar: builder.mutation<void, File>({
|
||||
query: (data) => ({
|
||||
headers: {
|
||||
"content-type": "image/png"
|
||||
"content-type": "image/png",
|
||||
},
|
||||
url: `/user/avatar`,
|
||||
method: "POST",
|
||||
body: data
|
||||
})
|
||||
body: data,
|
||||
}),
|
||||
}),
|
||||
updateAvatarByAdmin: builder.mutation<void, { uid: number; file: File }>({
|
||||
query: ({ uid, file }) => ({
|
||||
headers: {
|
||||
"content-type": "image/png"
|
||||
"content-type": "image/png",
|
||||
},
|
||||
url: `/admin/user/${uid}/avatar`,
|
||||
method: "POST",
|
||||
body: file
|
||||
})
|
||||
body: file,
|
||||
}),
|
||||
}),
|
||||
getUserByAdmin: builder.query<UserForAdmin, number>({
|
||||
query: (uid) => ({ url: `/admin/user/${uid}` })
|
||||
query: (uid) => ({ url: `/admin/user/${uid}` }),
|
||||
}),
|
||||
// bot operations
|
||||
createBotAPIKey: builder.mutation<void, { uid: number; name: string }>({
|
||||
query: ({ uid, name }) => ({
|
||||
url: `/admin/user/bot-api-key/${uid}`,
|
||||
method: "POST",
|
||||
body: { name }
|
||||
})
|
||||
body: { name },
|
||||
}),
|
||||
}),
|
||||
getBotAPIKeys: builder.query<BotAPIKey[], number>({
|
||||
query: (uid) => ({ url: `/admin/user/bot-api-key/${uid}` })
|
||||
query: (uid) => ({ url: `/admin/user/bot-api-key/${uid}` }),
|
||||
}),
|
||||
deleteBotAPIKey: builder.query<void, { uid: number; kid: number }>({
|
||||
query: ({ uid, kid }) => ({ url: `/admin/user/bot-api-key/${uid}/${kid}`, method: "DELETE" })
|
||||
query: ({ uid, kid }) => ({ url: `/admin/user/bot-api-key/${uid}/${kid}`, method: "DELETE" }),
|
||||
}),
|
||||
// bot operations end
|
||||
updateInfo: builder.mutation<User, UserDTO>({
|
||||
query: (data) => ({
|
||||
url: `/user`,
|
||||
method: "PUT",
|
||||
body: data
|
||||
body: data,
|
||||
}),
|
||||
async onQueryStarted(params, { dispatch, queryFulfilled }) {
|
||||
try {
|
||||
@@ -231,7 +239,7 @@ export const userApi = createApi({
|
||||
} catch (error) {
|
||||
console.log("update login user failed", error);
|
||||
}
|
||||
}
|
||||
},
|
||||
}),
|
||||
sendMsg: builder.mutation<
|
||||
number,
|
||||
@@ -240,21 +248,22 @@ export const userApi = createApi({
|
||||
query: ({ id, content, type = "text", properties = "" }) => ({
|
||||
headers: {
|
||||
"content-type": ContentTypes[type],
|
||||
"X-Properties": properties ? encodeBase64(JSON.stringify(properties)) : ""
|
||||
"X-Properties": properties ? encodeBase64(JSON.stringify(properties)) : "",
|
||||
},
|
||||
url: `/user/${id}/send`,
|
||||
method: "POST",
|
||||
body: type == "file" ? JSON.stringify(content) : content
|
||||
body: type == "file" ? JSON.stringify(content) : content,
|
||||
}),
|
||||
async onQueryStarted(param1, param2) {
|
||||
// @ts-ignore
|
||||
await onMessageSendStarted.call(this, param1, param2, "user");
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
export const {
|
||||
useUpdateRemarkMutation,
|
||||
useLazyGetUsersQuery,
|
||||
useGetUserByAdminQuery,
|
||||
useUpdateAvatarByAdminMutation,
|
||||
@@ -273,5 +282,5 @@ export const {
|
||||
useSearchUserMutation,
|
||||
useUpdateContactStatusMutation,
|
||||
usePinChatMutation,
|
||||
useUnpinChatMutation
|
||||
useUnpinChatMutation,
|
||||
} = userApi;
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
AutoDeleteSettingForChannels,
|
||||
AutoDeleteSettingForUsers,
|
||||
PinChat,
|
||||
PinChatTarget
|
||||
PinChatTarget,
|
||||
} from "@/types/sse";
|
||||
import { resetAuthData } from "./auth.data";
|
||||
|
||||
@@ -31,6 +31,7 @@ export interface State {
|
||||
channelAsides: { [cid: number]: ChannelAside };
|
||||
dmAsides: { [uid: number]: DMAside };
|
||||
pinChats: PinChat[];
|
||||
remarkMap: Record<number, string>;
|
||||
}
|
||||
|
||||
const initialState: State = {
|
||||
@@ -47,7 +48,8 @@ const initialState: State = {
|
||||
autoDeleteMsgChannels: [],
|
||||
channelAsides: {},
|
||||
dmAsides: {},
|
||||
pinChats: []
|
||||
pinChats: [],
|
||||
remarkMap: {},
|
||||
};
|
||||
|
||||
const footprintSlice = createSlice({
|
||||
@@ -72,7 +74,8 @@ const footprintSlice = createSlice({
|
||||
autoDeleteMsgChannels = [],
|
||||
channelAsides = {},
|
||||
dmAsides = {},
|
||||
pinChats = []
|
||||
pinChats = [],
|
||||
remarkMap = {},
|
||||
} = action.payload;
|
||||
// 初始化全局变量
|
||||
window.USERS_VERSION = usersVersion;
|
||||
@@ -91,7 +94,8 @@ const footprintSlice = createSlice({
|
||||
autoDeleteMsgChannels,
|
||||
channelAsides,
|
||||
dmAsides,
|
||||
pinChats
|
||||
pinChats,
|
||||
remarkMap,
|
||||
};
|
||||
},
|
||||
updateUsersVersion(state, action: PayloadAction<number>) {
|
||||
@@ -100,7 +104,7 @@ const footprintSlice = createSlice({
|
||||
},
|
||||
updateAfterMid(state, action: PayloadAction<number>) {
|
||||
const newMid = action.payload;
|
||||
// 如果新mid小于已有的afterMid,则不必更新
|
||||
// 如果新 mid 小于已有的 afterMid,则不必更新
|
||||
if (state.afterMid < newMid) {
|
||||
state.afterMid = action.payload;
|
||||
window.AFTER_MID = action.payload;
|
||||
@@ -220,6 +224,10 @@ const footprintSlice = createSlice({
|
||||
state.readUsers[uid] = mid;
|
||||
});
|
||||
},
|
||||
updateRemarkByUid(state, action: PayloadAction<{ uid: number; remark: string }>) {
|
||||
const { uid, remark } = action.payload;
|
||||
state.remarkMap[uid] = remark;
|
||||
},
|
||||
updateReadChannels(state, action: PayloadAction<{ gid: number; mid: number }[] | undefined>) {
|
||||
const reads = action.payload || [];
|
||||
if (reads.length == 0) return;
|
||||
@@ -234,11 +242,11 @@ const footprintSlice = createSlice({
|
||||
updateDMVisibleAside(state, action: PayloadAction<{ id: number; aside: DMAside }>) {
|
||||
const { id, aside } = action.payload;
|
||||
state.dmAsides[id] = aside;
|
||||
}
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder.addCase(resetAuthData, (state) => {
|
||||
// 如果有aside是voice的,就把它关掉
|
||||
// 如果有 aside 是 voice 的,就把它关掉
|
||||
Object.keys(state.channelAsides).forEach((channel_id: string) => {
|
||||
if (state.channelAsides[+channel_id] === "voice") {
|
||||
state.channelAsides[+channel_id] = null;
|
||||
@@ -250,7 +258,7 @@ const footprintSlice = createSlice({
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export const {
|
||||
@@ -267,7 +275,8 @@ export const {
|
||||
updateChannelVisibleAside,
|
||||
updateDMVisibleAside,
|
||||
upsertPinChats,
|
||||
removePinChats
|
||||
removePinChats,
|
||||
updateRemarkByUid,
|
||||
} = footprintSlice.actions;
|
||||
|
||||
export default footprintSlice.reducer;
|
||||
|
||||
@@ -18,6 +18,7 @@ import User from "../User";
|
||||
import { shallowEqual } from "react-redux";
|
||||
// import ViewPassword from "./ViewPassword";
|
||||
import UpdatePassword from "./UpdatePassword";
|
||||
import NameWithRemark from "../NameWithRemark";
|
||||
|
||||
interface Props {
|
||||
cid?: number;
|
||||
@@ -38,9 +39,9 @@ const MemberList: FC<Props> = ({ cid }) => {
|
||||
canCopyEmail,
|
||||
removeFromChannel,
|
||||
removeUser,
|
||||
showEmailInChannel
|
||||
showEmailInChannel,
|
||||
} = useUserOperation({
|
||||
cid
|
||||
cid,
|
||||
});
|
||||
const [updateUser, { isSuccess: updateSuccess }] = useUpdateUserMutation();
|
||||
|
||||
@@ -53,7 +54,7 @@ const MemberList: FC<Props> = ({ cid }) => {
|
||||
const handleToggleRole = ({
|
||||
ignore = false,
|
||||
uid,
|
||||
isAdmin = true
|
||||
isAdmin = true,
|
||||
}: {
|
||||
ignore: boolean;
|
||||
uid: number;
|
||||
@@ -95,7 +96,7 @@ const MemberList: FC<Props> = ({ cid }) => {
|
||||
<User compact uid={uid} interactive={false} />
|
||||
<div className="flex flex-col">
|
||||
<span className="font-bold text-sm text-gray-600 dark:text-white flex items-center gap-1">
|
||||
{name} {owner && <IconOwner />}
|
||||
<NameWithRemark name={name} uid={uid} /> {owner && <IconOwner />}
|
||||
</span>
|
||||
{showEmailInChannel && (
|
||||
<span className="hidden md:block text-xs text-gray-500 dark:text-slate-50">
|
||||
@@ -117,7 +118,7 @@ const MemberList: FC<Props> = ({ cid }) => {
|
||||
onClick={handleToggleRole.bind(null, {
|
||||
ignore: is_admin,
|
||||
uid,
|
||||
isAdmin: true
|
||||
isAdmin: true,
|
||||
})}
|
||||
>
|
||||
{t("admin")}
|
||||
@@ -128,7 +129,7 @@ const MemberList: FC<Props> = ({ cid }) => {
|
||||
onClick={handleToggleRole.bind(null, {
|
||||
ignore: !is_admin,
|
||||
uid,
|
||||
isAdmin: false
|
||||
isAdmin: false,
|
||||
})}
|
||||
>
|
||||
{t("user")}
|
||||
|
||||
@@ -20,6 +20,7 @@ import renderContent from "./renderContent";
|
||||
import Reply from "./Reply";
|
||||
import useInView from "./useInView";
|
||||
import { shallowEqual } from "react-redux";
|
||||
import NameWithRemark from "../NameWithRemark";
|
||||
|
||||
interface IProps {
|
||||
readOnly?: boolean;
|
||||
@@ -35,7 +36,7 @@ const Message: FC<IProps> = ({
|
||||
mid,
|
||||
context = "dm",
|
||||
updateReadIndex,
|
||||
read = true
|
||||
read = true,
|
||||
}) => {
|
||||
const { visible: contextMenuVisible, handleContextMenuEvent, hideContextMenu } = useContextMenu();
|
||||
const inViewRef = useInView<HTMLDivElement>();
|
||||
@@ -80,7 +81,7 @@ const Message: FC<IProps> = ({
|
||||
edited,
|
||||
properties,
|
||||
expires_in = 0,
|
||||
failed = false
|
||||
failed = false,
|
||||
} = message;
|
||||
|
||||
const reactions = reactionMessageData[mid];
|
||||
@@ -159,7 +160,13 @@ const Message: FC<IProps> = ({
|
||||
<div
|
||||
className={clsx(`flex items-center gap-2 font-semibold`, isSelf && "flex-row-reverse")}
|
||||
>
|
||||
<span className="text-primary-500 text-sm">{currUser?.name || "Deleted User"}</span>
|
||||
<span className="text-primary-500 text-sm">
|
||||
{currUser?.name ? (
|
||||
<NameWithRemark uid={currUser.uid} showName={false} name={currUser.name} />
|
||||
) : (
|
||||
"Deleted User"
|
||||
)}
|
||||
</span>
|
||||
{currUser?.is_admin && <IconAdmin />}
|
||||
<Tooltip
|
||||
delay={200}
|
||||
@@ -201,7 +208,7 @@ const Message: FC<IProps> = ({
|
||||
content,
|
||||
thumbnail,
|
||||
download,
|
||||
edited
|
||||
edited,
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { useAppSelector } from "../app/store";
|
||||
|
||||
type Props = {
|
||||
uid: number;
|
||||
name?: string;
|
||||
showName?: boolean;
|
||||
};
|
||||
|
||||
const NameWithRemark = ({ uid, name = "", showName = true }: Props) => {
|
||||
const remark = useAppSelector((store) => store.footprint.remarkMap[uid]);
|
||||
if (!remark) return name;
|
||||
return (
|
||||
<>
|
||||
{remark} {showName ? `(${name})` : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default NameWithRemark;
|
||||
@@ -13,6 +13,7 @@ import IconMore from "@/assets/icons/more.svg";
|
||||
import Avatar from "../Avatar";
|
||||
import ContextMenu, { Item } from "../ContextMenu";
|
||||
import { shallowEqual } from "react-redux";
|
||||
import Remark from "./remark";
|
||||
|
||||
interface Props {
|
||||
uid: number;
|
||||
@@ -35,7 +36,7 @@ const Profile: FC<Props> = ({ uid, type = "embed", cid }) => {
|
||||
removeUser,
|
||||
isAdmin,
|
||||
canUpdateRole,
|
||||
updateRole
|
||||
updateRole,
|
||||
} = useUserOperation({ uid, cid });
|
||||
const data = useAppSelector((store) => store.users.byId[uid], shallowEqual);
|
||||
if (!data) return null;
|
||||
@@ -43,7 +44,7 @@ const Profile: FC<Props> = ({ uid, type = "embed", cid }) => {
|
||||
const {
|
||||
name,
|
||||
email,
|
||||
avatar
|
||||
avatar,
|
||||
// introduction = "This guy has nothing to introduce",
|
||||
} = data;
|
||||
const isCard = type == "card";
|
||||
@@ -64,6 +65,7 @@ const Profile: FC<Props> = ({ uid, type = "embed", cid }) => {
|
||||
src={avatar}
|
||||
name={name}
|
||||
/>
|
||||
<Remark uid={uid} />
|
||||
<h2 className="text-lg select-text font-bold text-gray-900 dark:text-white">
|
||||
{name} {canDM && <span className="font-normal text-gray-500">#{uid}</span>}
|
||||
</h2>
|
||||
@@ -101,11 +103,11 @@ const Profile: FC<Props> = ({ uid, type = "embed", cid }) => {
|
||||
agoraEnabled &&
|
||||
type == "card" && {
|
||||
title: t("call"),
|
||||
handler: startCall
|
||||
handler: startCall,
|
||||
},
|
||||
canCopyEmail && {
|
||||
title: t("copy_email"),
|
||||
handler: copyEmail
|
||||
handler: copyEmail,
|
||||
},
|
||||
canUpdateRole && {
|
||||
title: t("roles"),
|
||||
@@ -114,25 +116,25 @@ const Profile: FC<Props> = ({ uid, type = "embed", cid }) => {
|
||||
{
|
||||
title: t("set_normal"),
|
||||
checked: !isAdmin,
|
||||
handler: updateRole
|
||||
handler: updateRole,
|
||||
},
|
||||
{
|
||||
title: t("set_admin"),
|
||||
checked: isAdmin,
|
||||
handler: updateRole
|
||||
}
|
||||
]
|
||||
handler: updateRole,
|
||||
},
|
||||
],
|
||||
},
|
||||
canRemoveFromChannel && {
|
||||
title: t("remove_from_channel"),
|
||||
danger: true,
|
||||
handler: removeFromChannel
|
||||
handler: removeFromChannel,
|
||||
},
|
||||
canRemoveFromServer && {
|
||||
title: t("remove"),
|
||||
handler: removeUser,
|
||||
danger: true
|
||||
}
|
||||
danger: true,
|
||||
},
|
||||
].filter(Boolean) as Item[]
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import React, { ChangeEvent, useState } from "react";
|
||||
import { useAppSelector } from "../../app/store";
|
||||
import Input from "../styled/Input";
|
||||
import IconEdit from "@/assets/icons/edit.svg";
|
||||
import StyledButton from "../styled/Button";
|
||||
import { useUpdateRemarkMutation } from "../../app/services/user";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import ServerVersionChecker from "../ServerVersionChecker";
|
||||
|
||||
type Props = {
|
||||
uid: number;
|
||||
};
|
||||
|
||||
const Remark = ({ uid }: Props) => {
|
||||
const { t } = useTranslation("chat");
|
||||
const { t: ct } = useTranslation("common", { keyPrefix: "action" });
|
||||
const [updateRemark, { isLoading }] = useUpdateRemarkMutation();
|
||||
const [editMode, setEditMode] = useState(false);
|
||||
const remark = useAppSelector((store) => store.footprint.remarkMap[uid] || "");
|
||||
const [input, setInput] = useState(remark);
|
||||
const handleChange = (evt: ChangeEvent<HTMLInputElement>) => {
|
||||
setInput(evt.target.value);
|
||||
};
|
||||
const handleOK = async () => {
|
||||
const { error } = await updateRemark({ contact_uid: uid, remark: input });
|
||||
if (!error) {
|
||||
setEditMode(false);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<ServerVersionChecker empty version="0.5.0">
|
||||
<div className="flex items-center gap-1 text-white py-2">
|
||||
{editMode ? (
|
||||
<div className="flex flex-col gap-2">
|
||||
<Input
|
||||
onChange={handleChange}
|
||||
value={input}
|
||||
placeholder={t("remark_placeholder")}
|
||||
className="small"
|
||||
/>
|
||||
<div className="flex items-center gap-2">
|
||||
<StyledButton disabled={isLoading} onClick={handleOK} className="small">
|
||||
{ct("yes")}
|
||||
</StyledButton>
|
||||
<StyledButton onClick={setEditMode.bind(null, false)} className="small ghost">
|
||||
{ct("cancel")}
|
||||
</StyledButton>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center gap-1">
|
||||
<span>{remark}</span>
|
||||
<button className="flex items-center gap-1" onClick={setEditMode.bind(null, true)}>
|
||||
{!remark && t("remark")}
|
||||
<IconEdit />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ServerVersionChecker>
|
||||
);
|
||||
};
|
||||
|
||||
export default Remark;
|
||||
@@ -12,6 +12,7 @@ import Profile from "../Profile";
|
||||
import ContextMenu from "./ContextMenu";
|
||||
import { shallowEqual } from "react-redux";
|
||||
import { cn } from "@/utils";
|
||||
import NameWithRemark from "../NameWithRemark";
|
||||
|
||||
interface Props extends React.HTMLAttributes<HTMLDivElement> {
|
||||
uid: number;
|
||||
@@ -111,7 +112,7 @@ const User: FC<Props> = ({
|
||||
</div>
|
||||
{!compact && (
|
||||
<span className={nameClass} title={curr?.name}>
|
||||
{curr?.name}
|
||||
<NameWithRemark uid={uid} name={curr?.name || ""} />
|
||||
</span>
|
||||
)}
|
||||
{!compact && curr.is_admin && !curr.is_bot && <IconAdmin />}
|
||||
@@ -155,7 +156,7 @@ const User: FC<Props> = ({
|
||||
</div>
|
||||
{!compact && (
|
||||
<span className={nameClass} title={curr?.name}>
|
||||
{curr?.name}
|
||||
<NameWithRemark uid={uid} name={curr?.name || ""} />
|
||||
</span>
|
||||
)}
|
||||
{!compact && curr.is_admin && !curr.is_bot && <IconAdmin />}
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
fillChannels,
|
||||
removeChannel,
|
||||
updateChannel,
|
||||
updatePinMessage
|
||||
updatePinMessage,
|
||||
} from "@/app/slices/channels";
|
||||
import {
|
||||
removePinChats,
|
||||
@@ -20,14 +20,15 @@ import {
|
||||
updateMute,
|
||||
updateReadChannels,
|
||||
updateReadUsers,
|
||||
updateRemarkByUid,
|
||||
updateUsersVersion,
|
||||
upsertPinChats
|
||||
upsertPinChats,
|
||||
} from "@/app/slices/footprint";
|
||||
import { resetMessage } from "@/app/slices/message";
|
||||
import {
|
||||
clearChannelMessage,
|
||||
removeChannelSession,
|
||||
resetChannelMsg
|
||||
resetChannelMsg,
|
||||
} from "@/app/slices/message.channel";
|
||||
import { resetFileMessage } from "@/app/slices/message.file";
|
||||
import { resetReactionMessage } from "@/app/slices/message.reaction";
|
||||
@@ -41,7 +42,7 @@ import {
|
||||
ServerEvent,
|
||||
UserSettingsChangedEvent,
|
||||
UserSettingsEvent,
|
||||
UsersStateEvent
|
||||
UsersStateEvent,
|
||||
} from "@/types/sse";
|
||||
import { getLocalAuthData, isElectronContext } from "@/utils";
|
||||
import chatMessageHandler from "./chat.handler";
|
||||
@@ -123,7 +124,7 @@ export default function useStreaming() {
|
||||
users_version?: string;
|
||||
} = {
|
||||
limit: "500",
|
||||
"api-key": _token
|
||||
"api-key": _token,
|
||||
};
|
||||
// 如果 afterMid 是 0,则不传该参数
|
||||
if (window.AFTER_MID) {
|
||||
@@ -168,6 +169,11 @@ export default function useStreaming() {
|
||||
case "heartbeat":
|
||||
keepAlive();
|
||||
break;
|
||||
case "user_remark": {
|
||||
const { contact_uid, remark } = data;
|
||||
dispatch(updateRemarkByUid({ uid: contact_uid, remark }));
|
||||
break;
|
||||
}
|
||||
case "message_cleared": {
|
||||
dispatch(resetFootprint());
|
||||
dispatch(resetChannelMsg());
|
||||
@@ -192,7 +198,7 @@ export default function useStreaming() {
|
||||
const transformed = {
|
||||
name: organization_name,
|
||||
description: organization_description,
|
||||
...tmp
|
||||
...tmp,
|
||||
};
|
||||
const purified = omitBy(transformed, isNull);
|
||||
dispatch(updateInfo(purified));
|
||||
@@ -409,7 +415,7 @@ export default function useStreaming() {
|
||||
ready,
|
||||
loginUid,
|
||||
readUsers,
|
||||
readChannels
|
||||
readChannels,
|
||||
});
|
||||
break;
|
||||
}
|
||||
@@ -493,6 +499,6 @@ export default function useStreaming() {
|
||||
|
||||
return {
|
||||
startStreaming,
|
||||
stopStreaming
|
||||
stopStreaming,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import IconVoicing from "@/assets/icons/voicing.svg";
|
||||
import getUnreadCount, { renderPreviewMessage } from "../utils";
|
||||
import ContextMenu from "./ContextMenu";
|
||||
import { shallowEqual } from "react-redux";
|
||||
import NameWithRemark from "../../../components/NameWithRemark";
|
||||
|
||||
interface IProps {
|
||||
type?: ChatContext;
|
||||
@@ -33,7 +34,7 @@ const Session: FC<IProps> = ({
|
||||
id,
|
||||
mid,
|
||||
setDeleteChannelId,
|
||||
setInviteChannelId
|
||||
setInviteChannelId,
|
||||
}) => {
|
||||
const navPath = type == "dm" ? `/chat/dm/${id}` : `/chat/channel/${id}`;
|
||||
// const { pathname } = useLocation();
|
||||
@@ -56,8 +57,8 @@ const Session: FC<IProps> = ({
|
||||
}
|
||||
},
|
||||
collect: (monitor) => ({
|
||||
isActive: monitor.canDrop() && monitor.isOver()
|
||||
})
|
||||
isActive: monitor.canDrop() && monitor.isOver(),
|
||||
}),
|
||||
}),
|
||||
[type, id]
|
||||
);
|
||||
@@ -108,7 +109,7 @@ const Session: FC<IProps> = ({
|
||||
mids,
|
||||
readIndex,
|
||||
messageData,
|
||||
loginUid
|
||||
loginUid,
|
||||
});
|
||||
const isVoicing =
|
||||
type == "channel"
|
||||
@@ -167,7 +168,7 @@ const Session: FC<IProps> = ({
|
||||
!previewMsg.created_at && "max-w-[190px]"
|
||||
)}
|
||||
>
|
||||
{name}
|
||||
{type == "dm" ? <NameWithRemark uid={id} showName={false} name={name} /> : name}
|
||||
</i>
|
||||
{!is_public && <IconLock className="dark:fill-gray-400" />}
|
||||
</span>
|
||||
|
||||
+8
-1
@@ -195,6 +195,12 @@ interface GroupChangedEvent {
|
||||
avatar_updated_at: number;
|
||||
}
|
||||
|
||||
interface UserRemark {
|
||||
type: "user_remark";
|
||||
contact_uid: number;
|
||||
remark: string;
|
||||
}
|
||||
|
||||
interface PinnedMessageUpdatedEvent {
|
||||
type: "pinned_message_updated";
|
||||
gid: number;
|
||||
@@ -248,4 +254,5 @@ export type ServerEvent =
|
||||
| GroupClearEvent
|
||||
| UserCallEvent
|
||||
| ServerConfigChangedEvent
|
||||
| MessageClearedEvent;
|
||||
| MessageClearedEvent
|
||||
| UserRemark;
|
||||
|
||||
@@ -57,6 +57,10 @@ export interface UserForAdmin extends User {
|
||||
status: UserStatus;
|
||||
online_devices: UserDevice[];
|
||||
}
|
||||
export interface UserRemarkDTO {
|
||||
contact_uid: number;
|
||||
remark: string;
|
||||
}
|
||||
export interface UserForAdminDTO extends Partial<UserForAdmin> {
|
||||
id?: number;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user