Files
VRCX/src/components/dialogs/GroupDialog/GroupMemberModerationDialog.vue

1865 lines
85 KiB
Vue

<template>
<el-dialog
class="x-dialog"
v-model="groupMemberModeration.visible"
:title="t('dialog.group_member_moderation.header')"
append-to-body
width="90vw">
<div>
<h3>{{ groupMemberModeration.groupRef.name }}</h3>
<el-tabs style="height: 100%">
<el-tab-pane :label="t('dialog.group_member_moderation.members')">
<div style="margin-top: 10px">
<el-button
type="default"
size="small"
:icon="Refresh"
:loading="isGroupMembersLoading"
circle
@click="loadAllGroupMembers" />
<span style="font-size: 14px; margin-left: 5px; margin-right: 5px">
{{ groupMemberModerationTable.data.length }}/{{
groupMemberModeration.groupRef.memberCount
}}
</span>
<div style="float: right; margin-top: 5px">
<span style="margin-right: 5px">{{ t('dialog.group.members.sort_by') }}</span>
<DropdownMenu>
<DropdownMenuTrigger
as-child
:disabled="
Boolean(
isGroupMembersLoading ||
memberSearch.length ||
!hasGroupPermission(groupMemberModeration.groupRef, 'group-bans-manage')
)
">
<el-button
size="small"
:disabled="
Boolean(
isGroupMembersLoading ||
memberSearch.length ||
!hasGroupPermission(groupMemberModeration.groupRef, 'group-bans-manage')
)
"
@click.stop>
<span>
{{ t(memberSortOrder.name) }}
<el-icon style="margin-left: 5px"><ArrowDown /></el-icon>
</span>
</el-button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem
v-for="item in groupDialogSortingOptions"
:key="item.name"
@click="setGroupMemberSortOrder(item)">
{{ t(item.name) }}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<span style="margin-right: 5px">{{ t('dialog.group.members.filter') }}</span>
<DropdownMenu>
<DropdownMenuTrigger
as-child
:disabled="
Boolean(
isGroupMembersLoading ||
memberSearch.length ||
!hasGroupPermission(groupMemberModeration.groupRef, 'group-bans-manage')
)
">
<el-button
size="small"
:disabled="
Boolean(
isGroupMembersLoading ||
memberSearch.length ||
!hasGroupPermission(groupMemberModeration.groupRef, 'group-bans-manage')
)
"
@click.stop>
<span>
{{ t(memberFilter.name) }}
<el-icon style="margin-left: 5px"><ArrowDown /></el-icon>
</span>
</el-button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem
v-for="item in groupDialogFilterOptions"
:key="item.name"
@click="setGroupMemberFilter(item)">
{{ t(item.name) }}
</DropdownMenuItem>
<template v-for="role in groupMemberModeration.groupRef.roles" :key="role.name">
<DropdownMenuItem v-if="!role.defaultRole" @click="setGroupMemberFilter(role)">
{{ t(role.name) }}
</DropdownMenuItem>
</template>
</DropdownMenuContent>
</DropdownMenu>
</div>
<el-input
v-model="memberSearch"
:disabled="!hasGroupPermission(groupMemberModeration.groupRef, 'group-bans-manage')"
clearable
size="small"
:placeholder="t('dialog.group.members.search')"
style="margin-top: 10px; margin-bottom: 10px"
@input="groupMembersSearch"></el-input>
<br />
<el-button size="small" @click="selectAllGroupMembers">{{
t('dialog.group_member_moderation.select_all')
}}</el-button>
<DataTable
v-if="groupMemberModerationTable.data.length"
v-bind="groupMemberModerationTable"
style="margin-top: 10px">
<el-table-column width="55" prop="$selected">
<template #default="scope">
<el-button text size="small" @click.stop>
<el-checkbox
v-model="scope.row.$selected"
@change="
groupMemberModerationTableSelectionChange(scope.row)
"></el-checkbox>
</el-button>
</template>
</el-table-column>
<el-table-column
:label="t('dialog.group_member_moderation.avatar')"
width="70"
prop="photo">
<template #default="scope">
<el-popover placement="right" :width="500" trigger="hover">
<template #reference>
<img
:src="userImage(scope.row.user)"
class="friends-list-avatar"
loading="lazy" />
</template>
<img
:src="userImageFull(scope.row.user)"
:class="['friends-list-avatar', 'x-popover-image']"
style="cursor: pointer"
@click="showFullscreenImageDialog(userImageFull(scope.row.user))"
loading="lazy" />
</el-popover>
</template>
</el-table-column>
<el-table-column
:label="t('dialog.group_member_moderation.display_name')"
width="160"
prop="$displayName"
sortable>
<template #default="scope">
<span style="cursor: pointer" @click="showUserDialog(scope.row.userId)">
<span
v-if="randomUserColours"
:style="{ color: scope.row.user.$userColour }"
v-text="scope.row.user.displayName"></span>
<span v-else v-text="scope.row.user?.displayName"></span>
</span>
</template>
</el-table-column>
<el-table-column :label="t('dialog.group_member_moderation.roles')" prop="roleIds" sortable>
<template #default="scope">
<template v-for="(roleId, index) in scope.row.roleIds" :key="roleId">
<template
v-for="(role, rIndex) in groupMemberModeration.groupRef.roles"
:key="roleId + rIndex">
<span v-if="role?.id === roleId"
>{{ role.name
}}<span v-if="index < scope.row.roleIds.length - 1">, </span></span
></template
>
</template>
</template>
</el-table-column>
<el-table-column
:label="t('dialog.group_member_moderation.notes')"
prop="managerNotes"
sortable>
<template #default="scope">
<span @click.stop v-text="scope.row.managerNotes"></span>
</template>
</el-table-column>
<el-table-column
:label="t('dialog.group_member_moderation.joined_at')"
width="170"
prop="joinedAt"
sortable>
<template #default="scope">
<span>{{ formatDateFilter(scope.row.joinedAt, 'long') }}</span>
</template>
</el-table-column>
<el-table-column
:label="t('dialog.group_member_moderation.visibility')"
width="120"
prop="visibility"
sortable>
<template #default="scope">
<span v-text="scope.row.visibility"></span>
</template>
</el-table-column>
</DataTable>
</div>
</el-tab-pane>
<el-tab-pane
:label="t('dialog.group_member_moderation.bans')"
:disabled="!hasGroupPermission(groupMemberModeration.groupRef, 'group-bans-manage')">
<div style="margin-top: 10px">
<el-button
type="default"
size="small"
:icon="Refresh"
:loading="isGroupMembersLoading"
circle
@click="getAllGroupBans(groupMemberModeration.id)"></el-button>
<span style="font-size: 14px; margin-left: 5px; margin-right: 5px">{{
groupBansModerationTable.data.length
}}</span>
<br />
<el-input
v-model="groupBansModerationTable.filters[0].value"
clearable
size="small"
:placeholder="t('dialog.group.members.search')"
style="margin-top: 10px; margin-bottom: 10px"></el-input>
<br />
<el-button size="small" @click="selectAllGroupBans">{{
t('dialog.group_member_moderation.select_all')
}}</el-button>
<DataTable v-bind="groupBansModerationTable" style="margin-top: 10px">
<el-table-column width="55" prop="$selected">
<template #default="scope">
<el-button text size="small" @click.stop>
<el-checkbox
v-model="scope.row.$selected"
@change="
groupMemberModerationTableSelectionChange(scope.row)
"></el-checkbox>
</el-button>
</template>
</el-table-column>
<el-table-column
:label="t('dialog.group_member_moderation.avatar')"
width="70"
prop="photo">
<template #default="scope">
<el-popover placement="right" :width="500" trigger="hover">
<template #reference>
<img
:src="userImage(scope.row.user)"
class="friends-list-avatar"
loading="lazy" />
</template>
<img
:src="userImageFull(scope.row.user)"
:class="['friends-list-avatar', 'x-popover-image']"
style="cursor: pointer"
@click="showFullscreenImageDialog(userImageFull(scope.row.user))"
loading="lazy" />
</el-popover>
</template>
</el-table-column>
<el-table-column
:label="t('dialog.group_member_moderation.display_name')"
width="160"
prop="$displayName"
sortable>
<template #default="scope">
<span style="cursor: pointer" @click="showUserDialog(scope.row.userId)">
<span
v-if="randomUserColours"
:style="{ color: scope.row.user?.$userColour }"
v-text="scope.row.user?.displayName"></span>
<span v-else v-text="scope.row.user?.displayName"></span>
</span>
</template>
</el-table-column>
<el-table-column :label="t('dialog.group_member_moderation.roles')" prop="roleIds" sortable>
<template #default="scope">
<template v-for="(roleId, index) in scope.row.roleIds" :key="roleId">
<span
v-for="(role, rIndex) in groupMemberModeration.groupRef.roles"
v-if="role.id === roleId"
:key="rIndex + roleId"
>{{ role.name }}</span
>
<span v-if="index < scope.row.roleIds.length - 1">, </span>
</template>
</template>
</el-table-column>
<el-table-column
:label="t('dialog.group_member_moderation.notes')"
prop="managerNotes"
sortable>
<template #default="scope">
<span @click.stop v-text="scope.row.managerNotes"></span>
</template>
</el-table-column>
<el-table-column
:label="t('dialog.group_member_moderation.joined_at')"
width="170"
prop="joinedAt"
sortable>
<template #default="scope">
<span>{{ formatDateFilter(scope.row.joinedAt, 'long') }}</span>
</template>
</el-table-column>
<el-table-column
:label="t('dialog.group_member_moderation.banned_at')"
width="170"
prop="bannedAt"
sortable>
<template #default="scope">
<span>{{ formatDateFilter(scope.row.bannedAt, 'long') }}</span>
</template>
</el-table-column>
</DataTable>
</div>
</el-tab-pane>
<el-tab-pane
:label="t('dialog.group_member_moderation.invites')"
:disabled="!hasGroupPermission(groupMemberModeration.groupRef, 'group-invites-manage')">
<div style="margin-top: 10px">
<el-button
type="default"
size="small"
:icon="Refresh"
:loading="isGroupMembersLoading"
circle
@click="getAllGroupInvitesAndJoinRequests(groupMemberModeration.id)"></el-button>
<br />
<el-tabs>
<el-tab-pane>
<template #label>
<span style="font-weight: bold; font-size: 16px">{{
t('dialog.group_member_moderation.sent_invites')
}}</span>
<span style="color: #909399; font-size: 12px; margin-left: 5px">{{
groupInvitesModerationTable.data.length
}}</span>
</template>
<el-button size="small" @click="selectAllGroupInvites">{{
t('dialog.group_member_moderation.select_all')
}}</el-button>
<DataTable v-bind="groupInvitesModerationTable" style="margin-top: 10px">
<el-table-column width="55" prop="$selected">
<template #default="scope">
<el-button text size="small" @click.stop>
<el-checkbox
v-model="scope.row.$selected"
@change="
groupMemberModerationTableSelectionChange(scope.row)
"></el-checkbox>
</el-button>
</template>
</el-table-column>
<el-table-column
:label="t('dialog.group_member_moderation.avatar')"
width="70"
prop="photo">
<template #default="scope">
<el-popover placement="right" :width="500" trigger="hover">
<template #reference>
<img
:src="userImage(scope.row.user)"
class="friends-list-avatar"
loading="lazy" />
</template>
<img
:src="userImageFull(scope.row.user)"
:class="['friends-list-avatar', 'x-popover-image']"
style="cursor: pointer"
@click="showFullscreenImageDialog(userImageFull(scope.row.user))"
loading="lazy" />
</el-popover>
</template>
</el-table-column>
<el-table-column
:label="t('dialog.group_member_moderation.display_name')"
width="160"
prop="$displayName"
sortable>
<template #default="scope">
<span style="cursor: pointer" @click="showUserDialog(scope.row.userId)">
<span
v-if="randomUserColours"
:style="{ color: scope.row.user.$userColour }"
v-text="scope.row.user.displayName"></span>
<span v-else v-text="scope.row.user?.displayName"></span>
</span>
</template>
</el-table-column>
<el-table-column
:label="t('dialog.group_member_moderation.notes')"
prop="managerNotes"
sortable>
<template #default="scope">
<span @click.stop v-text="scope.row.managerNotes"></span>
</template>
</el-table-column>
</DataTable>
<br />
<el-button
:disabled="
Boolean(
progressCurrent ||
!hasGroupPermission(groupMemberModeration.groupRef, 'group-invites-manage')
)
"
@click="groupMembersDeleteSentInvite"
>{{ t('dialog.group_member_moderation.delete_sent_invite') }}</el-button
>
</el-tab-pane>
<el-tab-pane>
<template #label>
<span style="font-weight: bold; font-size: 16px">{{
t('dialog.group_member_moderation.join_requests')
}}</span>
<span style="color: #909399; font-size: 12px; margin-left: 5px">{{
groupJoinRequestsModerationTable.data.length
}}</span>
</template>
<el-button size="small" @click="selectAllGroupJoinRequests">{{
t('dialog.group_member_moderation.select_all')
}}</el-button>
<DataTable v-bind="groupJoinRequestsModerationTable" style="margin-top: 10px">
<el-table-column width="55" prop="$selected">
<template #default="scope">
<el-button text size="small" @click.stop>
<el-checkbox
v-model="scope.row.$selected"
@change="
groupMemberModerationTableSelectionChange(scope.row)
"></el-checkbox>
</el-button>
</template>
</el-table-column>
<el-table-column
:label="t('dialog.group_member_moderation.avatar')"
width="70"
prop="photo">
<template #default="scope">
<el-popover placement="right" :width="500" trigger="hover">
<template #reference>
<img
:src="userImage(scope.row.user)"
class="friends-list-avatar"
loading="lazy" />
</template>
<img
:src="userImageFull(scope.row.user)"
:class="['friends-list-avatar', 'x-popover-image']"
style="cursor: pointer"
@click="showFullscreenImageDialog(userImageFull(scope.row.user))"
loading="lazy" />
</el-popover>
</template>
</el-table-column>
<el-table-column
:label="t('dialog.group_member_moderation.display_name')"
width="160"
prop="$displayName"
sortable>
<template #default="scope">
<span style="cursor: pointer" @click="showUserDialog(scope.row.userId)">
<span
v-if="randomUserColours"
:style="{ color: scope.row.user.$userColour }"
v-text="scope.row.user.displayName"></span>
<span v-else v-text="scope.row.user?.displayName"></span>
</span>
</template>
</el-table-column>
<el-table-column
:label="t('dialog.group_member_moderation.notes')"
prop="managerNotes"
sortable>
<template #default="scope">
<span @click.stop v-text="scope.row.managerNotes"></span>
</template>
</el-table-column>
</DataTable>
<br />
<el-button
:disabled="
Boolean(
progressCurrent ||
!hasGroupPermission(groupMemberModeration.groupRef, 'group-invites-manage')
)
"
@click="groupMembersAcceptInviteRequest"
>{{ t('dialog.group_member_moderation.accept_join_requests') }}</el-button
>
<el-button
:disabled="
Boolean(
progressCurrent ||
!hasGroupPermission(groupMemberModeration.groupRef, 'group-invites-manage')
)
"
@click="groupMembersRejectInviteRequest"
>{{ t('dialog.group_member_moderation.reject_join_requests') }}</el-button
>
<el-button
:disabled="
Boolean(
progressCurrent ||
!hasGroupPermission(groupMemberModeration.groupRef, 'group-invites-manage')
)
"
@click="groupMembersBlockJoinRequest"
>{{ t('dialog.group_member_moderation.block_join_requests') }}</el-button
>
</el-tab-pane>
<el-tab-pane>
<template #label>
<span style="font-weight: bold; font-size: 16px">{{
t('dialog.group_member_moderation.blocked_requests')
}}</span>
<span style="color: #909399; font-size: 12px; margin-left: 5px">{{
groupBlockedModerationTable.data.length
}}</span>
</template>
<el-button size="small" @click="selectAllGroupBlocked">{{
t('dialog.group_member_moderation.select_all')
}}</el-button>
<DataTable v-bind="groupBlockedModerationTable" style="margin-top: 10px">
<el-table-column width="55" prop="$selected">
<template #default="scope">
<el-button text size="small" @click.stop>
<el-checkbox
v-model="scope.row.$selected"
@change="
groupMemberModerationTableSelectionChange(scope.row)
"></el-checkbox>
</el-button>
</template>
</el-table-column>
<el-table-column
:label="t('dialog.group_member_moderation.avatar')"
width="70"
prop="photo">
<template #default="scope">
<el-popover placement="right" :width="500" trigger="hover">
<template #reference>
<img
:src="userImage(scope.row.user)"
class="friends-list-avatar"
loading="lazy" />
</template>
<img
:src="userImageFull(scope.row.user)"
:class="['friends-list-avatar', 'x-popover-image']"
style="cursor: pointer"
@click="showFullscreenImageDialog(userImageFull(scope.row.user))"
loading="lazy" />
</el-popover>
</template>
</el-table-column>
<el-table-column
:label="t('dialog.group_member_moderation.display_name')"
width="160"
prop="$displayName"
sortable>
<template #default="scope">
<span style="cursor: pointer" @click="showUserDialog(scope.row.userId)">
<span
v-if="randomUserColours"
:style="{ color: scope.row.user.$userColour }"
v-text="scope.row.user.displayName"></span>
<span v-else v-text="scope.row.user?.displayName"></span>
</span>
</template>
</el-table-column>
<el-table-column
:label="t('dialog.group_member_moderation.notes')"
prop="managerNotes"
sortable>
<template #default="scope">
<span @click.stop v-text="scope.row.managerNotes"></span>
</template>
</el-table-column>
</DataTable>
<br />
<el-button
:disabled="
Boolean(
progressCurrent ||
!hasGroupPermission(groupMemberModeration.groupRef, 'group-invites-manage')
)
"
@click="groupMembersDeleteBlockedRequest"
>{{ t('dialog.group_member_moderation.delete_blocked_requests') }}</el-button
>
</el-tab-pane>
</el-tabs>
</div>
</el-tab-pane>
<el-tab-pane
:label="t('dialog.group_member_moderation.logs')"
:disabled="!hasGroupPermission(groupMemberModeration.groupRef, 'group-audit-view')">
<div style="margin-top: 10px">
<el-button
type="default"
size="small"
:icon="Refresh"
:loading="isGroupMembersLoading"
circle
@click="getAllGroupLogs(groupMemberModeration.id)"></el-button>
<span style="font-size: 14px; margin-left: 5px; margin-right: 5px">{{
groupLogsModerationTable.data.length
}}</span>
<br />
<div style="display: flex; justify-content: space-between; align-items: center">
<div>
<Select v-model="selectedAuditLogTypes" multiple>
<SelectTrigger style="margin: 10px 0; width: 250px">
<SelectValue :placeholder="t('dialog.group_member_moderation.filter_type')" />
</SelectTrigger>
<SelectContent>
<SelectItem
v-for="type in groupMemberModeration.auditLogTypes"
:key="type"
:value="type">
{{ getAuditLogTypeName(type) }}
</SelectItem>
</SelectContent>
</Select>
<el-input
v-model="groupLogsModerationTable.filters[0].value"
:placeholder="t('dialog.group_member_moderation.search_placeholder')"
style="display: inline-block; width: 150px; margin: 10px"
clearable></el-input>
</div>
<div>
<el-button @click="showGroupLogsExportDialog">{{
t('dialog.group_member_moderation.export_logs')
}}</el-button>
</div>
</div>
<br />
<DataTable v-bind="groupLogsModerationTable" style="margin-top: 10px">
<el-table-column
:label="t('dialog.group_member_moderation.created_at')"
width="170"
prop="created_at"
sortable>
<template #default="scope">
<span>{{ formatDateFilter(scope.row.created_at, 'long') }}</span>
</template>
</el-table-column>
<el-table-column
:label="t('dialog.group_member_moderation.type')"
width="190"
prop="eventType"
sortable>
<template #default="scope">
<span v-text="scope.row.eventType"></span>
</template>
</el-table-column>
<el-table-column
:label="t('dialog.group_member_moderation.display_name')"
width="160"
prop="actorDisplayName"
sortable>
<template #default="scope">
<span style="cursor: pointer" @click="showUserDialog(scope.row.actorId)">
<span v-text="scope.row.actorDisplayName"></span>
</span>
</template>
</el-table-column>
<el-table-column
:label="t('dialog.group_member_moderation.description')"
prop="description">
<template #default="scope">
<Location
v-if="scope.row?.targetId.startsWith('wrld_')"
:location="scope.row.targetId" />
<span v-text="scope.row.description"></span>
</template>
</el-table-column>
<el-table-column :label="t('dialog.group_member_moderation.data')" prop="data">
<template #default="scope">
<span v-if="Object.keys(scope.row.data).length">{{
JSON.stringify(scope.row.data)
}}</span>
<span v-else></span>
</template>
</el-table-column>
</DataTable>
</div>
</el-tab-pane>
</el-tabs>
<br />
<br />
<span class="name">{{ t('dialog.group_member_moderation.user_id') }}</span>
<br />
<el-input
v-model="selectUserId"
size="small"
style="margin-top: 5px; width: 340px"
:placeholder="t('dialog.group_member_moderation.user_id_placeholder')"
clearable></el-input>
<el-button
size="small"
style="margin-top: 5px; margin-left: 5px"
:disabled="!selectUserId"
@click="selectGroupMemberUserId"
>{{ t('dialog.group_member_moderation.select_user') }}</el-button
>
<br />
<br />
<span class="name">{{ t('dialog.group_member_moderation.selected_users') }}</span>
<el-button
type="default"
size="small"
:icon="Delete"
circle
style="margin-left: 5px"
@click="clearSelectedGroupMembers"></el-button>
<br />
<Badge
v-for="user in selectedUsersArray"
:key="user.id"
variant="outline"
style="margin-right: 5px; margin-top: 5px">
<TooltipWrapper v-if="user.membershipStatus !== 'member'" side="top">
<template #content>
<span>{{ t('dialog.group_member_moderation.user_isnt_in_group') }}</span>
</template>
<el-icon style="margin-left: 3px; display: inline-block"><Warning /></el-icon>
</TooltipWrapper>
<span v-text="user.user?.displayName || user.userId" style="font-weight: bold; margin-left: 5px"></span>
<button
type="button"
style="
margin-left: 6px;
border: none;
background: transparent;
padding: 0;
display: inline-flex;
align-items: center;
color: inherit;
cursor: pointer;
"
@click="deleteSelectedGroupMember(user)">
<i class="ri-close-line" style="font-size: 12px; line-height: 1"></i>
</button>
</Badge>
<br />
<br />
<span class="name">{{ t('dialog.group_member_moderation.notes') }}</span>
<el-input
v-model="note"
class="extra"
type="textarea"
:rows="2"
:autosize="{ minRows: 1, maxRows: 20 }"
:placeholder="t('dialog.group_member_moderation.note_placeholder')"
size="small"
resize="none"
style="margin-top: 5px"></el-input>
<br />
<br />
<span class="name">{{ t('dialog.group_member_moderation.selected_roles') }}</span>
<br />
<Select v-model="selectedRoles" multiple>
<SelectTrigger style="margin-top: 5px">
<SelectValue :placeholder="t('dialog.group_member_moderation.choose_roles_placeholder')" />
</SelectTrigger>
<SelectContent>
<SelectItem v-for="role in groupMemberModeration.groupRef.roles" :key="role.id" :value="role.id">
{{ role.name }}
</SelectItem>
</SelectContent>
</Select>
<br />
<br />
<span class="name">{{ t('dialog.group_member_moderation.actions') }}</span>
<br />
<el-button
:disabled="
Boolean(
!selectedRoles.length ||
progressCurrent ||
!hasGroupPermission(groupMemberModeration.groupRef, 'group-roles-assign')
)
"
@click="groupMembersAddRoles"
>{{ t('dialog.group_member_moderation.add_roles') }}</el-button
>
<el-button
:disabled="
Boolean(
!selectedRoles.length ||
progressCurrent ||
!hasGroupPermission(groupMemberModeration.groupRef, 'group-roles-assign')
)
"
@click="groupMembersRemoveRoles"
>{{ t('dialog.group_member_moderation.remove_roles') }}</el-button
>
<el-button
:disabled="
Boolean(
progressCurrent || !hasGroupPermission(groupMemberModeration.groupRef, 'group-members-manage')
)
"
@click="groupMembersSaveNote"
>{{ t('dialog.group_member_moderation.save_note') }}</el-button
>
<el-button
:disabled="
Boolean(
progressCurrent || !hasGroupPermission(groupMemberModeration.groupRef, 'group-members-remove')
)
"
@click="groupMembersKick"
>{{ t('dialog.group_member_moderation.kick') }}</el-button
>
<el-button
:disabled="
Boolean(progressCurrent || !hasGroupPermission(groupMemberModeration.groupRef, 'group-bans-manage'))
"
@click="groupMembersBan"
>{{ t('dialog.group_member_moderation.ban') }}</el-button
>
<el-button
:disabled="
Boolean(progressCurrent || !hasGroupPermission(groupMemberModeration.groupRef, 'group-bans-manage'))
"
@click="groupMembersUnban"
>{{ t('dialog.group_member_moderation.unban') }}</el-button
>
<span v-if="progressCurrent" style="margin-top: 10px">
<el-icon class="is-loading" style="margin-left: 5px; margin-right: 5px"><Loading /></el-icon>
{{ t('dialog.group_member_moderation.progress') }} {{ progressCurrent }}/{{ progressTotal }}
</span>
<el-button v-if="progressCurrent" style="margin-left: 5px" @click="progressTotal = 0">{{
t('dialog.group_member_moderation.cancel')
}}</el-button>
</div>
<group-member-moderation-export-dialog
:is-group-logs-export-dialog-visible="isGroupLogsExportDialogVisible"
:group-logs-moderation-table="groupLogsModerationTable" />
</el-dialog>
</template>
<script setup>
import { ArrowDown, Delete, Loading, Refresh, Warning } from '@element-plus/icons-vue';
import { reactive, ref, watch } from 'vue';
import { storeToRefs } from 'pinia';
import { toast } from 'vue-sonner';
import { useI18n } from 'vue-i18n';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../ui/select';
import { debounce, formatDateFilter, hasGroupPermission, userImage, userImageFull } from '../../../shared/utils';
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '../../ui/dropdown-menu';
import { useAppearanceSettingsStore, useGalleryStore, useGroupStore, useUserStore } from '../../../stores';
import { groupDialogFilterOptions, groupDialogSortingOptions } from '../../../shared/constants';
import { groupRequest, userRequest } from '../../../api';
import { Badge } from '../../ui/badge';
import GroupMemberModerationExportDialog from './GroupMemberModerationExportDialog.vue';
import * as workerTimers from 'worker-timers';
const { randomUserColours } = storeToRefs(useAppearanceSettingsStore());
const { showUserDialog } = useUserStore();
const { currentUser } = storeToRefs(useUserStore());
const { groupDialog, groupMemberModeration } = storeToRefs(useGroupStore());
const { applyGroupMember, handleGroupMember, handleGroupMemberProps } = useGroupStore();
const { showFullscreenImageDialog } = useGalleryStore();
const { t } = useI18n();
const selectedUsers = reactive({});
const selectedUsersArray = ref([]);
const isGroupMembersLoading = ref(false);
const isGroupMembersDone = ref(false);
const memberFilter = ref({
id: null,
name: 'dialog.group.members.filters.everyone'
});
const memberSortOrder = ref({
id: '',
name: 'dialog.group.members.sorting.joined_at_desc',
value: 'joinedAt:desc'
});
const members = ref([]);
const memberSearch = ref('');
let loadMoreGroupMembersParams = ref({
n: 100,
offset: 0,
groupId: '',
sort: 'joinedAt:desc',
roleId: ''
});
function setSelectedUsers(usersId, user) {
if (!user) {
return;
}
selectedUsers[usersId] = user;
selectedUsersArray.value = Object.values(selectedUsers);
}
function deselectedUsers(userId, isAll = false) {
if (isAll) {
for (const id in selectedUsers) {
if (Object.prototype.hasOwnProperty.call(selectedUsers, id)) {
delete selectedUsers[id];
}
}
} else {
if (Object.prototype.hasOwnProperty.call(selectedUsers, userId)) {
delete selectedUsers[userId];
}
}
selectedUsersArray.value = Object.values(selectedUsers);
}
function groupMemberModerationTableSelectionChange(row) {
if (row.$selected && !selectedUsers[row.userId]) {
setSelectedUsers(row.userId, row);
} else if (!row.$selected && selectedUsers[row.userId]) {
deselectedUsers(row.userId);
}
}
const groupInvitesModerationTable = reactive({
data: [],
tableProps: { stripe: true, size: 'small' },
pageSize: 15,
paginationProps: {
layout: 'sizes,prev,pager,next,total'
}
});
const groupJoinRequestsModerationTable = reactive({
data: [],
tableProps: { stripe: true, size: 'small' },
pageSize: 15,
paginationProps: {
layout: 'sizes,prev,pager,next,total'
}
});
const groupBlockedModerationTable = reactive({
data: [],
tableProps: { stripe: true, size: 'small' },
pageSize: 15,
paginationProps: {
small: true,
layout: 'sizes,prev,pager,next,total'
}
});
const groupLogsModerationTable = reactive({
data: [],
filters: [{ prop: ['description'], value: '' }],
tableProps: { stripe: true, size: 'small' },
pageSize: 15,
paginationProps: {
layout: 'sizes,prev,pager,next,total'
}
});
const groupBansModerationTable = reactive({
data: [],
filters: [{ prop: ['$displayName'], value: '' }],
tableProps: { stripe: true, size: 'small' },
pageSize: 15,
paginationProps: {
layout: 'sizes,prev,pager,next,total'
}
});
const groupMemberModerationTable = reactive({
data: [],
tableProps: { stripe: true, size: 'small' },
pageSize: 15,
paginationProps: {
layout: 'sizes,prev,pager,next,total'
}
});
function deselectGroupMember(userId) {
const deselectInTable = (tableData) => {
if (userId) {
const row = tableData.find((item) => item.userId === userId);
if (row) {
row.$selected = false;
}
} else {
tableData.forEach((row) => {
if (row.$selected) {
row.$selected = false;
}
});
}
};
deselectInTable(groupMemberModerationTable.data);
deselectInTable(groupBansModerationTable.data);
deselectInTable(groupInvitesModerationTable.data);
deselectInTable(groupJoinRequestsModerationTable.data);
deselectInTable(groupBlockedModerationTable.data);
}
const selectUserId = ref('');
const progressCurrent = ref(0);
const progressTotal = ref(0);
const selectedRoles = ref([]);
const selectedAuditLogTypes = ref([]);
const note = ref('');
const isGroupLogsExportDialogVisible = ref(false);
watch(
() => groupMemberModeration.value.visible,
(newVal) => {
if (newVal) {
groupMemberModerationTable.data = [];
groupBansModerationTable.data = [];
groupInvitesModerationTable.data = [];
groupJoinRequestsModerationTable.data = [];
groupBlockedModerationTable.data = [];
groupLogsModerationTable.data = [];
Object.assign(selectedUsers, {});
selectedUsersArray.value = [];
selectUserId.value = '';
selectedRoles.value = [];
note.value = '';
if (groupMemberModeration.value.openWithUserId) {
addGroupMemberToSelection(groupMemberModeration.value.openWithUserId);
}
}
}
);
function handleGroupMemberRoleChange(args) {
if (groupDialog.value.id === args.params.groupId) {
groupDialog.value.members.forEach((member) => {
if (member.userId === args.params.userId) {
member.roleIds = args.json;
return true;
}
});
}
}
async function groupMembersDeleteSentInvite() {
const D = groupMemberModeration.value;
const users = [...selectedUsersArray.value];
const memberCount = users.length;
progressTotal.value = memberCount;
let allSuccess = true;
for (let i = 0; i < memberCount; i++) {
if (!progressTotal.value) {
break;
}
const user = users[i];
progressCurrent.value = i + 1;
if (user.userId === currentUser.value.id) {
continue;
}
console.log(`Deleting group invite ${user.userId} ${i + 1}/${memberCount}`);
try {
await groupRequest.deleteSentGroupInvite({
groupId: D.id,
userId: user.userId
});
} catch (err) {
console.error(err);
toast.error(`Failed to delete group invites: ${err}`);
allSuccess = false;
}
}
if (allSuccess) {
toast.success(`Deleted ${memberCount} group invites`);
}
progressCurrent.value = 0;
progressTotal.value = 0;
getAllGroupInvites(D.id);
}
function selectAllGroupMembers() {
groupMemberModerationTable.data.forEach((row) => {
row.$selected = true;
setSelectedUsers(row.userId, row);
});
}
async function getAllGroupBans(groupId) {
groupBansModerationTable.data = [];
const params = { groupId, n: 100, offset: 0 };
const count = 50; // 5000 max
isGroupMembersLoading.value = true;
const fetchedBans = [];
try {
for (let i = 0; i < count; i++) {
const args = await groupRequest.getGroupBans(params);
if (args && args.json) {
if (groupMemberModeration.value.id !== args.params.groupId) {
continue;
}
args.json.forEach((json) => {
const ref = applyGroupMember(json);
fetchedBans.push(ref);
});
if (args.json.length < params.n) {
break;
}
params.offset += params.n;
} else {
break;
}
}
groupBansModerationTable.data = fetchedBans;
} catch {
toast.error('Failed to get group bans');
} finally {
isGroupMembersLoading.value = false;
}
}
function selectAllGroupBans() {
groupBansModerationTable.data.forEach((row) => {
row.$selected = true;
setSelectedUsers(row.userId, row);
});
}
async function groupMembersBan() {
const D = groupMemberModeration.value;
const users = [...selectedUsersArray.value];
const memberCount = users.length;
progressTotal.value = memberCount;
for (let i = 0; i < memberCount; i++) {
if (!progressTotal.value) break;
const user = users[i];
progressCurrent.value = i + 1;
if (user.userId === currentUser.value.id) continue;
console.log(`Banning ${user.userId} ${i + 1}/${memberCount}`);
try {
await groupRequest.banGroupMember({ groupId: D.id, userId: user.userId });
} catch (err) {
console.error(err);
toast.error(`Failed to ban group member: ${err}`);
}
}
toast.success(`Banned ${memberCount} group members`);
progressCurrent.value = 0;
progressTotal.value = 0;
getAllGroupBans(D.id);
deselectedUsers(null, true);
}
async function groupMembersUnban() {
const D = groupMemberModeration.value;
const users = [...selectedUsersArray.value];
const memberCount = users.length;
progressTotal.value = memberCount;
let allSuccess = true;
for (let i = 0; i < memberCount; i++) {
if (!progressTotal.value) break;
const user = users[i];
progressCurrent.value = i + 1;
if (user.userId === currentUser.value.id) continue;
console.log(`Unbanning ${user.userId} ${i + 1}/${memberCount}`);
try {
await groupRequest.unbanGroupMember({ groupId: D.id, userId: user.userId });
} catch (err) {
console.error(err);
toast.error(`Failed to unban group member: ${err}`);
allSuccess = false;
}
}
if (allSuccess) {
toast.success(`Unbanned ${memberCount} group members`);
}
progressCurrent.value = 0;
progressTotal.value = 0;
getAllGroupBans(D.id);
deselectedUsers(null, true);
}
async function groupMembersKick() {
const D = groupMemberModeration.value;
const users = [...selectedUsersArray.value];
const memberCount = users.length;
progressTotal.value = memberCount;
let allSuccess = true;
for (let i = 0; i < memberCount; i++) {
if (!progressTotal.value) break;
const user = users[i];
progressCurrent.value = i + 1;
if (user.userId === currentUser.value.id) continue;
console.log(`Kicking ${user.userId} ${i + 1}/${memberCount}`);
try {
await groupRequest.kickGroupMember({ groupId: D.id, userId: user.userId });
} catch (err) {
console.error(err);
toast.error(`Failed to kick group member: ${err}`);
allSuccess = false;
}
}
if (allSuccess) {
toast.success(`Kicked ${memberCount} group members`);
}
progressCurrent.value = 0;
progressTotal.value = 0;
deselectedUsers(null, true);
loadAllGroupMembers();
}
async function groupMembersSaveNote() {
const D = groupMemberModeration.value;
const users = [...selectedUsersArray.value];
const memberCount = users.length;
progressTotal.value = memberCount;
const noteToSave = note.value;
let allSuccess = true;
for (let i = 0; i < memberCount; i++) {
if (!progressTotal.value) break;
const user = users[i];
progressCurrent.value = i + 1;
if (user.managerNotes === noteToSave) {
continue;
}
console.log(`Setting note ${noteToSave} for ${user.userId} ${i + 1}/${memberCount}`);
try {
const args = await groupRequest.setGroupMemberProps(user.userId, D.id, { managerNotes: noteToSave });
handleGroupMemberProps(args);
} catch (err) {
console.error(err);
toast.error(`Failed to set group member note for ${err}`);
allSuccess = false;
}
}
if (allSuccess) {
toast.success(`Saved notes for ${memberCount} group members`);
}
progressCurrent.value = 0;
progressTotal.value = 0;
deselectedUsers(null, true);
}
async function groupMembersRemoveRoles() {
const D = groupMemberModeration.value;
const users = [...selectedUsersArray.value];
const rolesToRemoveSet = new Set(selectedRoles.value);
const memberCount = users.length;
progressTotal.value = memberCount;
let allSuccess = true;
for (let i = 0; i < memberCount; i++) {
if (!progressTotal.value) break;
const user = users[i];
progressCurrent.value = i + 1;
const currentRoles = new Set(user.roleIds || []);
const rolesToRemoveForUser = [];
rolesToRemoveSet.forEach((roleId) => {
if (currentRoles.has(roleId)) {
rolesToRemoveForUser.push(roleId);
}
});
if (!rolesToRemoveForUser.length) continue;
for (const roleId of rolesToRemoveForUser) {
console.log(`Removing role ${roleId} from ${user.userId} ${i + 1}/${memberCount}`);
try {
const args = await groupRequest.removeGroupMemberRole({
groupId: D.id,
userId: user.userId,
roleId
});
handleGroupMemberRoleChange(args);
} catch (err) {
console.error(err);
toast.error(`Failed to remove group member roles: ${err}`);
allSuccess = false;
}
}
}
if (allSuccess) {
toast.success(`Roles removed`);
}
progressCurrent.value = 0;
progressTotal.value = 0;
deselectedUsers(null, true);
}
async function groupMembersAddRoles() {
const D = groupMemberModeration.value;
const users = [...selectedUsersArray.value];
const rolesToAddSet = new Set(selectedRoles.value);
const memberCount = users.length;
progressTotal.value = memberCount;
let allSuccess = true;
for (let i = 0; i < memberCount; i++) {
if (!progressTotal.value) break;
const user = users[i];
progressCurrent.value = i + 1;
const currentRoles = new Set(user.roleIds || []);
const rolesToAddForUser = [];
rolesToAddSet.forEach((roleId) => {
if (!currentRoles.has(roleId)) {
rolesToAddForUser.push(roleId);
}
});
if (!rolesToAddForUser.length) continue;
for (const roleId of rolesToAddForUser) {
console.log(`Adding role: ${roleId} to ${user.userId} ${i + 1}/${memberCount}`);
try {
const args = await groupRequest.addGroupMemberRole({ groupId: D.id, userId: user.userId, roleId });
handleGroupMemberRoleChange(args);
} catch (err) {
console.error(err);
toast.error(`Failed to add group member roles: ${err}`);
allSuccess = false;
}
}
}
if (allSuccess) {
toast.success(`Added group member roles`);
}
progressCurrent.value = 0;
progressTotal.value = 0;
deselectedUsers(null, true);
}
function deleteSelectedGroupMember(user) {
deselectedUsers(user.userId);
deselectGroupMember(user.userId);
}
function clearSelectedGroupMembers() {
deselectedUsers(null, true);
deselectGroupMember();
}
async function selectGroupMemberUserId() {
const userIdInput = selectUserId.value;
if (!userIdInput) return;
const regexUserId = /usr_[0-9A-Fa-f]{8}-([0-9A-Fa-f]{4}-){3}[0-9A-Fa-f]{12}/g;
let match;
const userIdList = new Set();
while ((match = regexUserId.exec(userIdInput)) !== null) {
userIdList.add(match[0]);
}
if (userIdList.size === 0) {
// for those users missing the usr_ prefix
userIdList.add(userIdInput);
}
const promises = [];
userIdList.forEach((userId) => {
promises.push(addGroupMemberToSelection(userId));
});
await Promise.allSettled(promises);
selectUserId.value = '';
}
async function addGroupMemberToSelection(userId) {
const D = groupMemberModeration.value;
// fetch member if there is one
// banned members don't have a user object
let member = {};
const memberArgs = await groupRequest.getGroupMember({ groupId: D.id, userId });
if (memberArgs && memberArgs.json) {
member = applyGroupMember(memberArgs.json);
}
if (member && member.user) {
setSelectedUsers(member.userId, member);
return;
}
const userArgs = await userRequest.getCachedUser({
userId
});
member.userId = userArgs.json.id;
member.user = userArgs.json;
member.displayName = userArgs.json.displayName;
setSelectedUsers(member.userId, member);
}
async function getAllGroupLogs(groupId) {
groupLogsModerationTable.data = [];
const params = { groupId, n: 100, offset: 0 };
if (selectedAuditLogTypes.value.length) {
params.eventTypes = selectedAuditLogTypes.value;
}
const count = 50; // 5000 max
isGroupMembersLoading.value = true;
try {
for (let i = 0; i < count; i++) {
const args = await groupRequest.getGroupLogs(params);
if (args) {
if (groupMemberModeration.value.id !== args.params.groupId) {
continue;
}
for (const json of args.json.results) {
const existsInData = groupLogsModerationTable.data.some((dataItem) => dataItem.id === json.id);
if (!existsInData) {
groupLogsModerationTable.data.push(json);
}
}
}
params.offset += params.n;
if (!args.json.hasNext) {
break;
}
}
} catch {
toast.error('Failed to get group logs');
} finally {
isGroupMembersLoading.value = false;
}
}
async function groupMembersDeleteBlockedRequest() {
const D = groupMemberModeration.value;
const users = [...selectedUsersArray.value];
const memberCount = users.length;
progressTotal.value = memberCount;
let allSuccess = true;
for (let i = 0; i < memberCount; i++) {
if (!progressTotal.value) break;
const user = users[i];
progressCurrent.value = i + 1;
if (user.userId === currentUser.value.id) {
continue;
}
console.log(`Deleting blocked group request for ${user.userId} ${i + 1}/${memberCount}`);
try {
await groupRequest.deleteBlockedGroupRequest({ groupId: D.id, userId: user.userId });
} catch (err) {
console.error(err);
toast.error(`Failed to delete blocked group requests: ${err}`);
allSuccess = false;
}
}
if (allSuccess) {
toast.success(`Deleted ${memberCount} blocked group requests`);
}
progressCurrent.value = 0;
progressTotal.value = 0;
getAllGroupInvitesAndJoinRequests(D.id);
deselectedUsers(null, true);
}
async function groupMembersBlockJoinRequest() {
const D = groupMemberModeration.value;
const users = [...selectedUsersArray.value];
const memberCount = users.length;
progressTotal.value = memberCount;
let allSuccess = true;
for (let i = 0; i < memberCount; i++) {
if (!progressTotal.value) break;
const user = users[i];
progressCurrent.value = i + 1;
if (user.userId === currentUser.value.id) continue;
console.log(`Blocking group join request from ${user.userId} ${i + 1}/${memberCount}`);
try {
await groupRequest.blockGroupInviteRequest({ groupId: D.id, userId: user.userId });
} catch (err) {
console.error(err);
toast.error(`Failed to block group join requests: ${err}`);
allSuccess = false;
}
}
if (allSuccess) {
toast.success(`Blocked ${memberCount} group join requests`);
}
progressCurrent.value = 0;
progressTotal.value = 0;
getAllGroupInvitesAndJoinRequests(D.id);
deselectedUsers(null, true);
}
async function groupMembersRejectInviteRequest() {
const D = groupMemberModeration.value;
const users = [...selectedUsersArray.value];
const memberCount = users.length;
progressTotal.value = memberCount;
let allSuccess = true;
for (let i = 0; i < memberCount; i++) {
if (!progressTotal.value) break;
const user = users[i];
progressCurrent.value = i + 1;
if (user.userId === currentUser.value.id) continue;
console.log(`Rejecting group join request from ${user.userId} ${i + 1}/${memberCount}`);
try {
await groupRequest.rejectGroupInviteRequest({ groupId: D.id, userId: user.userId });
} catch (err) {
console.error(err);
toast.error(`Failed to reject group join requests: ${err}`);
allSuccess = false;
}
}
if (allSuccess) {
toast.success(`Rejected ${memberCount} group join requests`);
}
progressCurrent.value = 0;
progressTotal.value = 0;
getAllGroupInvitesAndJoinRequests(D.id);
deselectedUsers(null, true);
}
async function groupMembersAcceptInviteRequest() {
const D = groupMemberModeration.value;
const users = [...selectedUsersArray.value];
const memberCount = users.length;
progressTotal.value = memberCount;
let allSuccess = true;
for (let i = 0; i < memberCount; i++) {
if (!progressTotal.value) break;
const user = users[i];
progressCurrent.value = i + 1;
if (user.userId === currentUser.value.id) continue;
console.log(`Accepting group join request from ${user.userId} ${i + 1}/${memberCount}`);
try {
await groupRequest.acceptGroupInviteRequest({ groupId: D.id, userId: user.userId });
} catch (err) {
console.error(err);
toast.error(`Failed to accept group join requests: ${err}`);
allSuccess = false;
}
}
if (allSuccess) {
toast.success(`Accepted ${memberCount} group join requests`);
}
progressCurrent.value = 0;
progressTotal.value = 0;
getAllGroupInvitesAndJoinRequests(D.id);
deselectedUsers(null, true);
}
async function getAllGroupInvitesAndJoinRequests(groupId) {
try {
await Promise.all([
getAllGroupInvites(groupId),
getAllGroupJoinRequests(groupId),
getAllGroupBlockedRequests(groupId)
]);
} catch (error) {
console.error('Error fetching group invites/requests:', error);
}
}
async function getAllGroupBlockedRequests(groupId) {
groupBlockedModerationTable.data = [];
const params = { groupId, n: 100, offset: 0, blocked: true };
const count = 50; // 5000
isGroupMembersLoading.value = true;
try {
for (let i = 0; i < count; i++) {
const args = await groupRequest.getGroupJoinRequests(params);
if (groupMemberModeration.value.id !== args.params.groupId) {
return;
}
const targetTable = args.params.blocked
? groupBlockedModerationTable
: groupJoinRequestsModerationTable;
for (const json of args.json) {
const ref = applyGroupMember(json);
targetTable.data.push(ref);
}
params.offset += params.n;
if (args.json.length < params.n) {
break;
}
}
} catch {
toast.error('Failed to get group join requests');
} finally {
isGroupMembersLoading.value = false;
}
}
async function getAllGroupJoinRequests(groupId) {
groupJoinRequestsModerationTable.data = [];
const params = { groupId, n: 100, offset: 0, blocked: false };
const count = 50; // 5000 max
isGroupMembersLoading.value = true;
try {
for (let i = 0; i < count; i++) {
const args = await groupRequest.getGroupJoinRequests(params);
if (groupMemberModeration.value.id !== args.params.groupId) {
return;
}
const targetTable = args.params.blocked
? groupBlockedModerationTable
: groupJoinRequestsModerationTable;
for (const json of args.json) {
const ref = applyGroupMember(json);
targetTable.data.push(ref);
}
params.offset += params.n;
if (args.json.length < params.n) {
break;
}
}
} catch {
toast.error('Failed to get group join requests');
} finally {
isGroupMembersLoading.value = false;
}
}
async function getAllGroupInvites(groupId) {
groupInvitesModerationTable.data = [];
const params = { groupId, n: 100, offset: 0 };
const count = 50; // 5000 max
isGroupMembersLoading.value = true;
try {
for (let i = 0; i < count; i++) {
const args = await groupRequest.getGroupInvites(params);
if (args) {
if (groupMemberModeration.value.id !== args.params.groupId) {
return;
}
for (const json of args.json) {
const ref = applyGroupMember(json);
groupInvitesModerationTable.data.push(ref);
}
}
params.offset += params.n;
if (args.json.length < params.n) {
break;
}
if (!groupMemberModeration.value.visible) {
break;
}
}
} catch {
toast.error('Failed to get group invites');
} finally {
isGroupMembersLoading.value = false;
}
}
function selectAllGroupInvites() {
groupInvitesModerationTable.data.forEach((row) => {
row.$selected = true;
setSelectedUsers(row.userId, row);
});
}
function selectAllGroupJoinRequests() {
groupJoinRequestsModerationTable.data.forEach((row) => {
row.$selected = true;
setSelectedUsers(row.userId, row);
});
}
function selectAllGroupBlocked() {
groupBlockedModerationTable.data.forEach((row) => {
row.$selected = true;
setSelectedUsers(row.userId, row);
});
}
function showGroupLogsExportDialog() {
isGroupLogsExportDialogVisible.value = true;
}
function getAuditLogTypeName(auditLogType) {
if (!auditLogType) return '';
return auditLogType
.replace('group.', '')
.replace(/\./g, ' ')
.replace(/\b\w/g, (l) => l.toUpperCase());
}
function groupMembersSearch() {
if (memberSearch.value.length < 3) {
groupMemberModerationTable.data = [];
isGroupMembersLoading.value = false;
return;
}
isGroupMembersLoading.value = true;
debounce(groupMembersSearchDebounced, 200)();
}
function groupMembersSearchDebounced() {
const groupId = groupMemberModeration.value.id;
const search = memberSearch.value;
groupMemberModerationTable.data = [];
if (memberSearch.value.length < 3) {
return;
}
isGroupMembersLoading.value = true;
groupRequest
.getGroupMembersSearch({
groupId,
query: search,
n: 100,
offset: 0
})
.then((args) => {
for (const json of args.json.results) {
handleGroupMember({
json,
params: {
groupId: args.params.groupId
}
});
}
if (groupId === args.params.groupId) {
groupMemberModerationTable.data = args.json.results.map((member) => ({
...member,
$selected: Boolean(selectedUsers[member.userId])
}));
}
})
.finally(() => {
isGroupMembersLoading.value = false;
});
}
async function getGroupMembers() {
members.value = [];
isGroupMembersDone.value = false;
loadMoreGroupMembersParams.value = {
sort: 'joinedAt:desc',
roleId: '',
n: 100,
offset: 0,
groupId: groupMemberModeration.value.id
};
if (memberSortOrder.value.value) {
loadMoreGroupMembersParams.value.sort = memberSortOrder.value.value;
}
if (memberFilter.value.id !== null) {
loadMoreGroupMembersParams.value.roleId = memberFilter.value.id;
}
await groupRequest
.getGroupMember({
groupId: groupMemberModeration.value.id,
userId: currentUser.value.id
})
.then((args) => {
args.ref = applyGroupMember(args.json);
if (args.json) {
args.json.user = currentUser.value;
if (memberFilter.value.id === null) {
// when filtered by role don't include self
members.value.push(args.json);
}
}
return args;
});
await loadMoreGroupMembers();
}
async function loadMoreGroupMembers() {
if (isGroupMembersDone.value || isGroupMembersLoading.value) {
return;
}
const params = loadMoreGroupMembersParams.value;
if (params.roleId === '') {
delete params.roleId;
}
memberSearch.value = '';
isGroupMembersLoading.value = true;
await groupRequest
.getGroupMembers(params)
.finally(() => {
isGroupMembersLoading.value = false;
})
.then((args) => {
for (const json of args.json) {
handleGroupMember({
json,
params: {
groupId: args.params.groupId
}
});
}
for (let i = 0; i < args.json.length; i++) {
const member = args.json[i];
if (member.userId === currentUser.value.id) {
if (members.value.length > 0 && members.value[0].userId === currentUser.value.id) {
// remove duplicate and keep sort order
members.value.splice(0, 1);
}
break;
}
}
if (args.json.length < params.n) {
isGroupMembersDone.value = true;
}
members.value = [...members.value, ...args.json];
groupMemberModerationTable.data = members.value.map((member) => ({
...member,
$selected: Boolean(selectedUsers[member.userId])
}));
params.offset += params.n;
return args;
})
.catch((err) => {
isGroupMembersDone.value = true;
throw err;
});
}
async function setGroupMemberSortOrder(sortOrder) {
if (memberSortOrder.value === sortOrder) {
return;
}
memberSortOrder.value = sortOrder;
await getGroupMembers();
}
async function loadAllGroupMembers() {
if (isGroupMembersLoading.value) {
return;
}
await getGroupMembers();
while (groupMemberModeration.value.visible && !isGroupMembersDone.value) {
isGroupMembersLoading.value = true;
await new Promise((resolve) => {
workerTimers.setTimeout(resolve, 1000);
});
isGroupMembersLoading.value = false;
await loadMoreGroupMembers();
}
}
async function setGroupMemberFilter(filter) {
if (memberFilter.value === filter) {
return;
}
memberFilter.value = filter;
await getGroupMembers();
}
</script>