feat: orphaned data cleanup, file deletion on message/account removal, and group DM improvements
- Add fileCleanup utility to delete uploaded files from disk on message/account deletion - Add migration to clean orphaned DM channels, attachments, reactions, read states, and stale moderator refs - Add FK constraints on dm_message_id in attachments and dm_reactions schema - Make bans.bannedBy and voiceRestrictions.moderatorId nullable for deleted moderators - Transfer group DM ownership on member leave or account deletion - Fully clean up orphaned DM channels (zero members) including files - Add "Leave Group" context menu for group DMs in ChannelSidebar - Add leaveDm action to spaceStore
This commit is contained in:
@@ -15,6 +15,7 @@ import { AudioManager } from '../../audio/AudioManager';
|
||||
import { hasPermissionBit, PermissionBits } from '../../utils/permissions';
|
||||
import { parseFederatedUsername, isSelf } from '../../utils/identity';
|
||||
import { joinVoiceChannel, broadcastVoiceStatus, broadcastDeafenViaLiveKit } from '../../utils/voice';
|
||||
import { ContextMenu } from '../ui/ContextMenu';
|
||||
|
||||
export function ChannelSidebar() {
|
||||
const spaces = useSpaceStore((s) => s.spaces);
|
||||
@@ -208,7 +209,7 @@ export function ChannelSidebar() {
|
||||
? otherMembers.map(m => m.displayName ?? parseFederatedUsername(m.username).baseName).join(', ')
|
||||
: otherMembers[0]?.displayName ?? otherMembers[0]?.username;
|
||||
|
||||
return (
|
||||
const dmItem = (
|
||||
<div
|
||||
key={dm.id}
|
||||
onClick={() => handleChannelClick(dm.id)}
|
||||
@@ -281,6 +282,36 @@ export function ChannelSidebar() {
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
if (isGroup) {
|
||||
return (
|
||||
<ContextMenu
|
||||
key={dm.id}
|
||||
items={[
|
||||
{
|
||||
label: 'Leave Group',
|
||||
danger: true,
|
||||
icon: (
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M10.09 15.59L11.5 17l5-5-5-5-1.41 1.41L12.67 11H3v2h9.67l-2.58 2.59zM19 3H5a2 2 0 00-2 2v4h2V5h14v14H5v-4H3v4a2 2 0 002 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z" />
|
||||
</svg>
|
||||
),
|
||||
onClick: () => {
|
||||
if (currentChannelId === dm.id) {
|
||||
navigate('/channels/@me');
|
||||
setCurrentChannel(null);
|
||||
}
|
||||
useSpaceStore.getState().leaveDm(dm.id);
|
||||
},
|
||||
},
|
||||
]}
|
||||
>
|
||||
{dmItem}
|
||||
</ContextMenu>
|
||||
);
|
||||
}
|
||||
|
||||
return dmItem;
|
||||
})}
|
||||
{dmChannels.length === 0 && (
|
||||
<p className="px-2 py-4 text-[13px] text-txt-tertiary italic opacity-60">No DM conversations yet.</p>
|
||||
|
||||
@@ -46,6 +46,7 @@ interface SpaceState {
|
||||
addDmMember: (dmChannelId: string, user: User) => void;
|
||||
removeDmMember: (dmChannelId: string, userId: string) => void;
|
||||
closeDm: (id: string) => Promise<void>;
|
||||
leaveDm: (id: string) => Promise<void>;
|
||||
loadSpaces: () => Promise<void>;
|
||||
loadSpaceDetail: (spaceId: string) => Promise<void>;
|
||||
loadDmChannels: () => Promise<void>;
|
||||
@@ -130,6 +131,15 @@ export const useSpaceStore = create<SpaceState>((set, get) => ({
|
||||
}));
|
||||
},
|
||||
|
||||
leaveDm: async (id) => {
|
||||
const origin = get().channelOriginMap.get(id) || '';
|
||||
const targetApi = getApiForOrigin(origin);
|
||||
await targetApi.dm.leave(id);
|
||||
set((state) => ({
|
||||
dmChannels: state.dmChannels.filter(c => c.id !== id)
|
||||
}));
|
||||
},
|
||||
|
||||
loadSpaces: async () => {
|
||||
try {
|
||||
const spaces =await api.spaces.list();
|
||||
|
||||
Reference in New Issue
Block a user