Chat info
@@ -245,45 +284,36 @@ export function ChatInfoPanel({ chatId, open, onClose }: Props) {
{members.map((member) => {
const user = memberUsers[member.user_id];
+ const isSelf = member.user_id === me?.id;
+ const canOpenMemberMenu =
+ canManageMembers &&
+ !isSelf &&
+ (myRole === "owner" || (myRole === "admin" && member.role === "member"));
return (
-
-
{user?.name || `user #${member.user_id}`}
+
+ {canOpenMemberMenu ?
Right click for actions
: null}
+
);
})}
@@ -358,37 +388,74 @@ export function ChatInfoPanel({ chatId, open, onClose }: Props) {
setAttachmentsTab("media")}
+ className={`rounded px-2 py-1 ${attachmentsTab === "all" ? "bg-sky-500/30 text-sky-200" : "text-slate-300"}`}
+ onClick={() => setAttachmentsTab("all")}
type="button"
>
- Media
+ All
setAttachmentsTab("files")}
+ className={`rounded px-2 py-1 ${attachmentsTab === "photos" ? "bg-sky-500/30 text-sky-200" : "text-slate-300"}`}
+ onClick={() => setAttachmentsTab("photos")}
type="button"
>
- Files
+ Photos
+
+ setAttachmentsTab("videos")}
+ type="button"
+ >
+ Videos
+
+ setAttachmentsTab("audio")}
+ type="button"
+ >
+ Audio
+
+ setAttachmentsTab("voice")}
+ type="button"
+ >
+ Voice
+
+ setAttachmentsTab("links")}
+ type="button"
+ >
+ Links
{attachmentsLoading ?
Loading attachments...
: null}
{!attachmentsLoading && attachments.length === 0 ?
No attachments yet
: null}
- {!attachmentsLoading && attachmentsTab === "media" ? (
+ {!attachmentsLoading && (attachmentsTab === "all" || attachmentsTab === "photos" || attachmentsTab === "videos") ? (
<>
- {mediaAttachments.slice(0, 90).map((item) => (
+ {(attachmentsTab === "photos" ? photoAttachments : attachmentsTab === "videos" ? videoAttachments : [...photoAttachments, ...videoAttachments])
+ .slice(0, 120)
+ .map((item) => (
window.open(item.file_url, "_blank", "noopener,noreferrer")}
+ onClick={() => {
+ const mediaItems = [...photoAttachments, ...videoAttachments]
+ .sort((a, b) => b.id - a.id)
+ .map((it) => ({ url: it.file_url, type: it.file_type.startsWith("video/") ? "video" as const : "image" as const, messageId: it.message_id }));
+ const idx = mediaItems.findIndex((it) => it.url === item.file_url && it.messageId === item.message_id);
+ setMediaViewer({ items: mediaItems, index: idx >= 0 ? idx : 0 });
+ }}
onContextMenu={(event) => {
event.preventDefault();
setAttachmentCtx({
x: Math.min(event.clientX + 4, window.innerWidth - 190),
y: Math.min(event.clientY + 4, window.innerHeight - 120),
- url: item.file_url
+ url: item.file_url,
+ messageId: item.message_id,
});
}}
type="button"
@@ -403,19 +470,23 @@ export function ChatInfoPanel({ chatId, open, onClose }: Props) {
>
) : null}
- {!attachmentsLoading && attachmentsTab === "files" ? (
+ {!attachmentsLoading && (attachmentsTab === "all" || attachmentsTab === "audio" || attachmentsTab === "voice") ? (
- {fileAttachments.slice(0, 100).map((item) => (
+ {(attachmentsTab === "audio" ? audioAttachments : attachmentsTab === "voice" ? voiceAttachments : allAttachmentItems)
+ .filter((item) => !item.file_type.startsWith("image/") && !item.file_type.startsWith("video/"))
+ .slice(0, 120)
+ .map((item) => (
window.open(item.file_url, "_blank", "noopener,noreferrer")}
+ onClick={() => jumpToMessage(item.message_id)}
onContextMenu={(event) => {
event.preventDefault();
setAttachmentCtx({
x: Math.min(event.clientX + 4, window.innerWidth - 190),
y: Math.min(event.clientY + 4, window.innerHeight - 120),
- url: item.file_url
+ url: item.file_url,
+ messageId: item.message_id,
});
}}
type="button"
@@ -428,6 +499,31 @@ export function ChatInfoPanel({ chatId, open, onClose }: Props) {
))}
) : null}
+ {!attachmentsLoading && attachmentsTab === "links" ? (
+
+ {linkItems.length === 0 ?
No links found
: null}
+ {linkItems.map((item, index) => (
+
jumpToMessage(item.messageId)}
+ onContextMenu={(event) => {
+ event.preventDefault();
+ setAttachmentCtx({
+ x: Math.min(event.clientX + 4, window.innerWidth - 190),
+ y: Math.min(event.clientY + 4, window.innerHeight - 120),
+ url: item.url,
+ messageId: item.messageId,
+ });
+ }}
+ type="button"
+ >
+ {item.url}
+ {new Date(item.createdAt).toLocaleString()}
+
+ ))}
+
+ ) : null}
{chat.type === "private" && !chat.is_saved && chat.counterpart_user_id ? (
@@ -504,6 +600,142 @@ export function ChatInfoPanel({ chatId, open, onClose }: Props) {
>
Copy link
+