Group member moderation dialog, group member search

This commit is contained in:
Natsumi
2023-10-27 23:39:07 +13:00
parent 6ba1856a7a
commit 091b559272
3 changed files with 776 additions and 31 deletions

View File

@@ -26901,10 +26901,11 @@ speechSynthesis.getVoices();
groupId: string,
params: {
visibility: string,
isSubscribedToAnnouncements: bool
isSubscribedToAnnouncements: bool,
managerNotes: string
}
*/
API.setGroupProps = function (userId, groupId, params) {
API.setGroupMemberProps = function (userId, groupId, params) {
return this.call(`groups/${groupId}/members/${userId}`, {
method: 'PUT',
params
@@ -26915,12 +26916,15 @@ speechSynthesis.getVoices();
groupId,
params
};
this.$emit('GROUP:PROPS', args);
this.$emit('GROUP:MEMBER:PROPS', args);
return args;
});
};
API.$on('GROUP:PROPS', function (args) {
API.$on('GROUP:MEMBER:PROPS', function (args) {
if (args.userId !== this.currentUser.id) {
return;
}
var json = args.json;
json.$memberId = json.id;
json.id = json.groupId;
@@ -26945,6 +26949,113 @@ speechSynthesis.getVoices();
});
});
API.$on('GROUP:MEMBER:PROPS', function (args) {
if ($app.groupDialog.id === args.json.groupId) {
for (var i = 0; i < $app.groupDialog.members.length; ++i) {
var member = $app.groupDialog.members[i];
if (member.userId === args.json.userId) {
Object.assign(member, this.applyGroupMember(args.json));
break;
}
}
for (
var i = 0;
i < $app.groupDialog.memberSearchResults.length;
++i
) {
var member = $app.groupDialog.memberSearchResults[i];
if (member.userId === args.json.userId) {
Object.assign(member, this.applyGroupMember(args.json));
break;
}
}
}
if (
$app.groupMemberModeration.visible &&
$app.groupMemberModeration.id === args.json.groupId
) {
// force redraw table
$app.groupMembersSearch();
}
});
/*
params: {
userId: string,
groupId: string,
roleId: string
}
*/
API.addGroupMemberRole = function (params) {
return this.call(
`groups/${params.groupId}/members/${params.userId}/roles/${params.roleId}`,
{
method: 'PUT'
}
).then((json) => {
var args = {
json,
params
};
this.$emit('GROUP:MEMBER:ROLE:CHANGE', args);
return args;
});
};
/*
params: {
userId: string,
groupId: string,
roleId: string
}
*/
API.removeGroupMemberRole = function (params) {
return this.call(
`groups/${params.groupId}/members/${params.userId}/roles/${params.roleId}`,
{
method: 'DELETE'
}
).then((json) => {
var args = {
json,
params
};
this.$emit('GROUP:MEMBER:ROLE:CHANGE', args);
return args;
});
};
API.$on('GROUP:MEMBER:ROLE:CHANGE', function (args) {
if ($app.groupDialog.id === args.params.groupId) {
for (var i = 0; i < $app.groupDialog.members.length; ++i) {
var member = $app.groupDialog.members[i];
if (member.userId === args.params.userId) {
member.roleIds = args.json;
break;
}
}
for (
var i = 0;
i < $app.groupDialog.memberSearchResults.length;
++i
) {
var member = $app.groupDialog.memberSearchResults[i];
if (member.userId === args.params.userId) {
member.roleIds = args.json;
break;
}
}
}
if (
$app.groupMemberModeration.visible &&
$app.groupMemberModeration.id === args.params.groupId
) {
// force redraw table
$app.groupMembersSearch();
}
});
/*
params: {
groupId: string
@@ -27065,6 +27176,39 @@ speechSynthesis.getVoices();
args.ref = this.applyGroupMember(args.json);
});
/*
params: {
groupId: string,
query: string,
n: number,
offset: number
}
*/
API.getGroupMembersSearch = function (params) {
return this.call(`groups/${params.groupId}/members/search`, {
method: 'GET',
params
}).then((json) => {
var args = {
json,
params
};
this.$emit('GROUP:MEMBERS:SEARCH', args);
return args;
});
};
API.$on('GROUP:MEMBERS:SEARCH', function (args) {
for (var json of args.json.results) {
this.$emit('GROUP:MEMBER', {
json,
params: {
groupId: args.params.groupId
}
});
}
});
/*
params: {
groupId: string,
@@ -27087,6 +27231,47 @@ speechSynthesis.getVoices();
});
};
/*
params: {
groupId: string,
userId: string
}
*/
API.kickGroupMember = function (params) {
return this.call(`groups/${params.groupId}/members/${params.userId}`, {
method: 'DELETE'
}).then((json) => {
var args = {
json,
params
};
this.$emit('GROUP:MEMBER:KICK', args);
return args;
});
};
/*
params: {
groupId: string,
userId: string
}
*/
API.banGroupMember = function (params) {
return this.call(`groups/${params.groupId}/bans`, {
method: 'POST',
params: {
userId: params.userId
}
}).then((json) => {
var args = {
json,
params
};
this.$emit('GROUP:MEMBER:BAN', args);
return args;
});
};
/*
params: {
groupId: string
@@ -27397,6 +27582,8 @@ speechSynthesis.getVoices();
posts: [],
postsFiltered: [],
members: [],
memberSearch: '',
memberSearchResults: [],
instances: [],
memberRoles: [],
memberFilter: $app.data.groupDialogFilterOptions.everyone,
@@ -27409,6 +27596,12 @@ speechSynthesis.getVoices();
if (!groupId) {
return;
}
if (
this.groupMemberModeration.visible &&
this.groupMemberModeration.id !== groupId
) {
this.groupMemberModeration.visible = false;
}
this.$nextTick(() => adjustDialogZ(this.$refs.groupDialog.$el));
var D = this.groupDialog;
D.visible = true;
@@ -27422,6 +27615,8 @@ speechSynthesis.getVoices();
D.postsFiltered = [];
D.instances = [];
D.memberRoles = [];
D.memberSearch = '';
D.memberSearchResults = [];
if (this.groupDialogLastGallery !== groupId) {
D.galleries = {};
}
@@ -27553,6 +27748,9 @@ speechSynthesis.getVoices();
case 'Refresh':
this.showGroupDialog(D.id);
break;
case 'Moderation Tools':
this.showGroupMemberModerationDialog(D.id);
break;
case 'Leave Group':
this.leaveGroup(D.id);
break;
@@ -27664,7 +27862,7 @@ speechSynthesis.getVoices();
};
$app.methods.setGroupVisibility = function (groupId, visibility) {
return API.setGroupProps(API.currentUser.id, groupId, {
return API.setGroupMemberProps(API.currentUser.id, groupId, {
visibility
}).then((args) => {
this.$message({
@@ -27676,7 +27874,7 @@ speechSynthesis.getVoices();
};
$app.methods.setGroupSubscription = function (groupId, subscribe) {
return API.setGroupProps(API.currentUser.id, groupId, {
return API.setGroupMemberProps(API.currentUser.id, groupId, {
isSubscribedToAnnouncements: subscribe
}).then((args) => {
this.$message({
@@ -27710,6 +27908,13 @@ speechSynthesis.getVoices();
};
$app.methods.onGroupJoined = function (groupId) {
if (
this.groupMemberModeration.visible &&
this.groupMemberModeration.id === groupId
) {
// ignore this event if we were the one to trigger it
return;
}
if (this.groupDialog.visible && this.groupDialog.id === groupId) {
this.showGroupDialog(groupId);
}
@@ -27732,6 +27937,60 @@ speechSynthesis.getVoices();
API.currentUserGroups.delete(groupId);
};
// group search
$app.methods.groupMembersSearchDebounce = function () {
var D = this.groupDialog;
var search = D.memberSearch;
D.memberSearchResults = [];
if (!search || search.length < 3) {
this.setGroupMemberModerationTable(D.members);
return;
}
this.isGroupMembersLoading = true;
API.getGroupMembersSearch({
groupId: D.id,
query: search,
n: 100,
offset: 0
})
.then((args) => {
if (D.id === args.params.groupId) {
D.memberSearchResults = args.json.results;
this.setGroupMemberModerationTable(args.json.results);
}
})
.finally(() => {
this.isGroupMembersLoading = false;
});
};
$app.data.groupMembersSearchTimer = null;
$app.data.groupMembersSearchPending = false;
$app.methods.groupMembersSearch = function () {
if (this.groupMembersSearchTimer) {
this.groupMembersSearchPending = true;
} else {
this.groupMembersSearchExecute();
this.groupMembersSearchTimer = setTimeout(() => {
if (this.groupMembersSearchPending) {
this.groupMembersSearchExecute();
}
this.groupMembersSearchTimer = null;
}, 500);
}
};
$app.methods.groupMembersSearchExecute = function () {
try {
this.groupMembersSearchDebounce();
} catch (err) {
console.error(err);
}
this.groupMembersSearchTimer = null;
this.groupMembersSearchPending = false;
};
// group posts
$app.methods.updateGroupPostSearch = function () {
@@ -27796,6 +28055,7 @@ speechSynthesis.getVoices();
}
var D = this.groupDialog;
var params = this.loadMoreGroupMembersParams;
D.memberSearch = '';
this.isGroupMembersLoading = true;
await API.getGroupMembers(params)
.finally(() => {
@@ -27819,6 +28079,7 @@ speechSynthesis.getVoices();
this.isGroupMembersDone = true;
}
D.members = [...D.members, ...args.json];
this.setGroupMemberModerationTable(D.members);
params.offset += params.n;
return args;
})
@@ -28785,9 +29046,346 @@ speechSynthesis.getVoices();
);
};
// #endregion
// #region | Dialog: group member moderation
$app.data.groupMemberModeration = {
visible: false,
loading: false,
id: '',
groupRef: {},
note: '',
selectedUsers: new Map(),
selectedUsersArray: [],
selectedRoles: [],
progressCurrent: 0,
progressTotal: 0
};
$app.data.groupMemberModerationTable = {
data: [],
tableProps: {
stripe: true,
size: 'mini'
},
pageSize: $app.data.tablePageSize,
paginationProps: {
small: true,
layout: 'sizes,prev,pager,next,total',
pageSizes: [10, 15, 25, 50, 100]
},
key: 0
};
$app.methods.setGroupMemberModerationTable = function (data) {
if (!this.groupMemberModeration.visible) {
return;
}
for (var i = 0; i < data.length; i++) {
var member = data[i];
member.$selected = this.groupMemberModeration.selectedUsers.has(
member.userId
);
}
this.groupMemberModerationTable.data = data;
// force redraw
this.groupMemberModerationTable.key++;
};
$app.methods.showGroupMemberModerationDialog = function (groupId) {
this.$nextTick(() =>
adjustDialogZ(this.$refs.groupMemberModeration.$el)
);
if (groupId !== this.groupDialog.id) {
return;
}
var D = this.groupMemberModeration;
D.id = groupId;
D.selectedUsers.clear();
D.selectedUsersArray = [];
D.selectedRoles = [];
D.groupRef = {};
API.getCachedGroup({ groupId }).then((args) => {
D.groupRef = args.ref;
});
this.groupMemberModerationTable.key = 0;
D.visible = true;
this.setGroupMemberModerationTable(this.groupDialog.members);
};
$app.methods.groupMemberModerationTableSelectionChange = function (row) {
var D = this.groupMemberModeration;
if (row.$selected && !D.selectedUsers.has(row.userId)) {
D.selectedUsers.set(row.userId, row);
} else if (!row.$selected && D.selectedUsers.has(row.userId)) {
D.selectedUsers.delete(row.userId);
}
D.selectedUsersArray = Array.from(D.selectedUsers.values());
// force redraw
this.groupMemberModerationTable.key++;
};
$app.methods.deleteSelectedGroupMember = function (user) {
var D = this.groupMemberModeration;
D.selectedUsers.delete(user.userId);
D.selectedUsersArray = Array.from(D.selectedUsers.values());
for (var i = 0; i < this.groupMemberModerationTable.data.length; i++) {
var row = this.groupMemberModerationTable.data[i];
if (row.userId === user.userId) {
row.$selected = false;
break;
}
}
// force redraw
this.groupMemberModerationTable.key++;
};
$app.methods.clearSelectedGroupMembers = function () {
var D = this.groupMemberModeration;
D.selectedUsers.clear();
D.selectedUsersArray = [];
for (var i = 0; i < this.groupMemberModerationTable.data.length; i++) {
var row = this.groupMemberModerationTable.data[i];
row.$selected = false;
}
// force redraw
this.groupMemberModerationTable.key++;
};
$app.methods.selectAllGroupMembers = function () {
var D = this.groupMemberModeration;
for (var i = 0; i < this.groupMemberModerationTable.data.length; i++) {
var row = this.groupMemberModerationTable.data[i];
row.$selected = true;
D.selectedUsers.set(row.userId, row);
}
D.selectedUsersArray = Array.from(D.selectedUsers.values());
// force redraw
this.groupMemberModerationTable.key++;
};
$app.methods.groupMembersKick = async function () {
var D = this.groupMemberModeration;
var memberCount = D.selectedUsersArray.length;
D.progressTotal = memberCount;
try {
for (var i = 0; i < memberCount; i++) {
if (!D.visible || !D.progressTotal) {
break;
}
var user = D.selectedUsersArray[i];
D.progressCurrent = i + 1;
if (user.userId === API.currentUser.id) {
continue;
}
await API.kickGroupMember({
groupId: D.id,
userId: user.userId
});
console.log(`Kicking ${user.userId} ${i + 1}/${memberCount}`);
}
} catch (err) {
console.error(err);
this.$message({
message: `Failed to kick group member: ${err}`,
type: 'error'
});
} finally {
D.progressCurrent = 0;
D.progressTotal = 0;
}
};
$app.methods.groupMembersBan = async function () {
var D = this.groupMemberModeration;
var memberCount = D.selectedUsersArray.length;
D.progressTotal = memberCount;
try {
for (var i = 0; i < memberCount; i++) {
if (!D.visible || !D.progressTotal) {
break;
}
var user = D.selectedUsersArray[i];
D.progressCurrent = i + 1;
if (user.userId === API.currentUser.id) {
continue;
}
await API.banGroupMember({
groupId: D.id,
userId: user.userId
});
console.log(`Banning ${user.userId} ${i + 1}/${memberCount}`);
}
} catch (err) {
console.error(err);
this.$message({
message: `Failed to ban group member: ${err}`,
type: 'error'
});
} finally {
D.progressCurrent = 0;
D.progressTotal = 0;
}
};
$app.methods.groupMembersSaveNote = async function () {
var D = this.groupMemberModeration;
var memberCount = D.selectedUsersArray.length;
D.progressTotal = memberCount;
try {
for (var i = 0; i < memberCount; i++) {
if (!D.visible || !D.progressTotal) {
break;
}
var user = D.selectedUsersArray[i];
D.progressCurrent = i + 1;
if (user.managerNotes === D.note) {
continue;
}
await API.setGroupMemberProps(user.userId, D.id, {
managerNotes: D.note
});
console.log(
`Setting note ${D.note} ${user.userId} ${
i + 1
}/${memberCount}`
);
}
this.$message({
message: 'Note saved',
type: 'success'
});
} catch (err) {
console.error(err);
this.$message({
message: `Failed to set group member note: ${err}`,
type: 'error'
});
} finally {
D.progressCurrent = 0;
D.progressTotal = 0;
}
};
$app.methods.groupMembersAddRoles = async function () {
var D = this.groupMemberModeration;
var memberCount = D.selectedUsersArray.length;
D.progressTotal = memberCount;
try {
for (var i = 0; i < memberCount; i++) {
if (!D.visible || !D.progressTotal) {
break;
}
var user = D.selectedUsersArray[i];
D.progressCurrent = i + 1;
var rolesToAdd = [];
D.selectedRoles.forEach((roleId) => {
if (!user.roleIds.includes(roleId)) {
rolesToAdd.push(roleId);
}
});
if (!rolesToAdd.length) {
continue;
}
for (var j = 0; j < rolesToAdd.length; j++) {
var roleId = rolesToAdd[j];
console.log(
`Adding role: ${roleId} ${user.userId} ${
i + 1
}/${memberCount}`
);
await API.addGroupMemberRole({
groupId: D.id,
userId: user.userId,
roleId
});
}
}
this.$message({
message: 'Added group member roles',
type: 'success'
});
} catch (err) {
console.error(err);
this.$message({
message: `Failed to add group member roles: ${err}`,
type: 'error'
});
} finally {
D.progressCurrent = 0;
D.progressTotal = 0;
}
};
$app.methods.groupMembersRemoveRoles = async function () {
var D = this.groupMemberModeration;
var memberCount = D.selectedUsersArray.length;
D.progressTotal = memberCount;
try {
for (var i = 0; i < memberCount; i++) {
if (!D.visible || !D.progressTotal) {
break;
}
var user = D.selectedUsersArray[i];
D.progressCurrent = i + 1;
var rolesToRemove = [];
D.selectedRoles.forEach((roleId) => {
if (user.roleIds.includes(roleId)) {
rolesToRemove.push(roleId);
}
});
if (!rolesToRemove.length) {
continue;
}
for (var j = 0; j < rolesToRemove.length; j++) {
var roleId = rolesToRemove[j];
console.log(
`Removing role ${roleId} ${user.userId} ${
i + 1
}/${memberCount}`
);
await API.removeGroupMemberRole({
groupId: D.id,
userId: user.userId,
roleId
});
}
}
this.$message({
message: 'Roles removed',
type: 'success'
});
} catch (err) {
console.error(err);
this.$message({
message: `Failed to remove group member roles: ${err}`,
type: 'error'
});
} finally {
D.progressCurrent = 0;
D.progressTotal = 0;
}
};
// #endregion
$app = new Vue($app);
window.$app = $app;
})();
// #endregion
// // #endregion
// // #region | Dialog: templateDialog
// $app.data.templateDialog = {
// visible: false,
// };
// $app.methods.showTemplateDialog = function () {
// this.$nextTick(() => adjustDialogZ(this.$refs.templateDialog.$el));
// var D = this.templateDialog;
// D.visible = true;
// };
// // #endregion

View File

@@ -441,11 +441,11 @@ html
.detail
span.name(v-if="userDialog.unFriended") {{ $t('dialog.user.info.unfriended') }}
el-tooltip(v-if="!hideTooltips" placement="top" style="margin-left:5px" :content="$t('dialog.user.info.accuracy_notice')")
i.el-icon-warning
span.name(v-else) {{ $t('dialog.user.info.friended') }}
el-tooltip(v-if="!hideTooltips" placement="top" style="margin-left:5px" :content="$t('dialog.user.info.accuracy_notice')")
i.el-icon-warning
span.extra {{ userDialog.dateFriended | formatDate('long') }}
i.el-icon-warning
span.name(v-else) {{ $t('dialog.user.info.friended') }}
el-tooltip(v-if="!hideTooltips" placement="top" style="margin-left:5px" :content="$t('dialog.user.info.accuracy_notice')")
i.el-icon-warning
span.extra {{ userDialog.dateFriended | formatDate('long') }}
template(v-if="API.currentUser.id === userDialog.id")
.x-friend-item(@click="toggleAvatarCopying")
.detail
@@ -985,6 +985,8 @@ html
el-dropdown-item(v-if="groupDialog.ref.myMember.isSubscribedToAnnouncements" icon="el-icon-close" command="Unsubscribe To Announcements" divided) {{ $t('dialog.group.actions.unsubscribe') }}
el-dropdown-item(v-else icon="el-icon-check" command="Subscribe To Announcements" divided) {{ $t('dialog.group.actions.subscribe') }}
el-dropdown-item(v-if="hasGroupPermission(groupDialog.ref, 'group-invites-manage')" icon="el-icon-message" command="Invite To Group") {{ $t('dialog.group.actions.invite_to_group') }}
template(v-if="hasGroupPermission(groupDialog.ref, 'group-members-manage')")
el-dropdown-item(icon="el-icon-s-operation" command="Moderation Tools") {{ $t('dialog.group.actions.moderation_tools') }}
template(v-if="groupDialog.ref.myMember && groupDialog.ref.privacy === 'default'")
el-dropdown-item(icon="el-icon-view" command="Visibility Everyone" divided) #[i.el-icon-check(v-if="groupDialog.ref.myMember.visibility === 'visible'")] {{ $t('dialog.group.actions.visibility_everyone') }}
el-dropdown-item(icon="el-icon-view" command="Visibility Friends") #[i.el-icon-check(v-if="groupDialog.ref.myMember.visibility === 'friends'")] {{ $t('dialog.group.actions.visibility_friends') }}
@@ -1134,25 +1136,52 @@ html
template(v-if="groupDialog.visible")
span(v-if="hasGroupPermission(groupDialog.ref, 'group-members-viewall')" style="font-weight:bold;font-size:16px") {{ $t('dialog.group.members.all_members') }}
span(v-else style="font-weight:bold;font-size:16px") {{ $t('dialog.group.members.friends_only') }}
br
el-button(type="default" @click="loadAllGroupMembers" size="mini" icon="el-icon-refresh" :loading="isGroupMembersLoading" circle)
el-button(type="default" @click="downloadAndSaveJson(`${groupDialog.id}_members`, groupDialog.members)" size="mini" icon="el-icon-download" circle style="margin-left:5px")
span(style="font-size:14px;margin-left:5px;margin-right:5px") {{ groupDialog.members.length }}/{{ groupDialog.ref.memberCount }}
div(v-if="hasGroupPermission(groupDialog.ref, 'group-members-manage')" style="float:right")
span(style="margin-right:5px") {{ $t('dialog.group.members.sort_by') }}
el-dropdown(@click.native.stop trigger="click" size="small" style="margin-right:5px" :disabled="isGroupMembersLoading")
el-button(size="mini")
span {{ groupDialog.memberSortOrder.name }} #[i.el-icon-arrow-down.el-icon--right]
el-dropdown-menu(#default="dropdown")
el-dropdown-item(v-for="(item) in groupDialogSortingOptions" v-text="item.name" @click.native="setGroupMemberSortOrder(item)")
span(style="margin-right:5px") {{ $t('dialog.group.members.filter') }}
el-dropdown(@click.native.stop trigger="click" size="small" style="margin-right:5px" :disabled="isGroupMembersLoading")
el-button(size="mini")
span {{ groupDialog.memberFilter.name }} #[i.el-icon-arrow-down.el-icon--right]
el-dropdown-menu(#default="dropdown")
el-dropdown-item(v-for="(item) in groupDialogFilterOptions" v-text="item.name" @click.native="setGroupMemberFilter(item)")
el-dropdown-item(v-for="(item) in groupDialog.memberRoles" v-text="item.name" @click.native="setGroupMemberFilter(item)")
ul.infinite-list.x-friend-list(v-if="groupDialog.members.length > 0" v-infinite-scroll="loadMoreGroupMembers" style="margin-top:10px;overflow:auto;max-height:250px;min-width:130px")
div(style="margin-top:10px")
el-button(type="default" @click="loadAllGroupMembers" size="mini" icon="el-icon-refresh" :loading="isGroupMembersLoading" circle)
el-button(type="default" @click="downloadAndSaveJson(`${groupDialog.id}_members`, groupDialog.members)" size="mini" icon="el-icon-download" circle style="margin-left:5px")
span(v-if="groupDialog.memberSearch.length" style="font-size:14px;margin-left:5px;margin-right:5px") {{ groupDialog.memberSearchResults.length }}/{{ groupDialog.ref.memberCount }}
span(v-else style="font-size:14px;margin-left:5px;margin-right:5px") {{ groupDialog.members.length }}/{{ groupDialog.ref.memberCount }}
div(v-if="hasGroupPermission(groupDialog.ref, 'group-members-manage')" style="float:right")
span(style="margin-right:5px") {{ $t('dialog.group.members.sort_by') }}
el-dropdown(@click.native.stop trigger="click" size="small" style="margin-right:5px" :disabled="isGroupMembersLoading || groupDialog.memberSearch.length")
el-button(size="mini")
span {{ groupDialog.memberSortOrder.name }} #[i.el-icon-arrow-down.el-icon--right]
el-dropdown-menu(#default="dropdown")
el-dropdown-item(v-for="(item) in groupDialogSortingOptions" v-text="item.name" @click.native="setGroupMemberSortOrder(item)")
span(style="margin-right:5px") {{ $t('dialog.group.members.filter') }}
el-dropdown(@click.native.stop trigger="click" size="small" style="margin-right:5px" :disabled="isGroupMembersLoading || groupDialog.memberSearch.length")
el-button(size="mini")
span {{ groupDialog.memberFilter.name }} #[i.el-icon-arrow-down.el-icon--right]
el-dropdown-menu(#default="dropdown")
el-dropdown-item(v-for="(item) in groupDialogFilterOptions" v-text="item.name" @click.native="setGroupMemberFilter(item)")
el-dropdown-item(v-for="(item) in groupDialog.ref.roles" v-text="item.name" @click.native="setGroupMemberFilter(item)")
el-input(v-model="groupDialog.memberSearch" @input="groupMembersSearch" clearable size="mini" :placeholder="$t('dialog.group.members.search')" style="margin-top:10px;margin-bottom:10px")
.x-friend-list(v-if="groupDialog.memberSearch.length" v-loading="isGroupMembersLoading" style="margin-top:10px;overflow:auto;max-height:250px;min-width:130px")
.x-friend-item(v-for="user in groupDialog.memberSearchResults" :key="user.id" @click="showUserDialog(user.userId)" class="x-friend-item-border")
.avatar
img(v-lazy="userImage(user.user)")
.detail
span.name(v-text="user.user.displayName" :style="{'color':user.user.$userColour}")
span.extra
template(v-if="hasGroupPermission(groupDialog.ref, 'group-members-manage')")
el-tooltip(v-if="user.isRepresenting" placement="top" :content="$t('dialog.group.members.representing')")
i.el-icon-collection-tag(style="margin-right:5px")
el-tooltip(v-if="user.visibility !== 'visible'" placement="top")
template(#content)
span {{ $t('dialog.group.members.visibility') }} {{ user.visibility }}
i.el-icon-view(style="margin-right:5px")
el-tooltip(v-if="!user.isSubscribedToAnnouncements" placement="top" :content="$t('dialog.group.members.unsubscribed_announcements')")
i.el-icon-chat-line-square(style="margin-right:5px")
el-tooltip(v-if="user.managerNotes" placement="top")
template(#content)
span {{ $t('dialog.group.members.manager_notes') }}
br
span {{ user.managerNotes }}
i.el-icon-edit-outline(style="margin-right:5px")
template(v-for="roleId in user.roleIds" :key="roleId")
span(v-for="(role, rIndex) in groupDialog.ref.roles" :key="rIndex" v-if="role.id === roleId" v-text="role.name")
span(v-if="user.roleIds.indexOf(roleId) < user.roleIds.length - 1") ,&nbsp;
ul.infinite-list.x-friend-list(v-else-if="groupDialog.members.length > 0" v-infinite-scroll="loadMoreGroupMembers" style="margin-top:10px;overflow:auto;max-height:250px;min-width:130px")
li.infinite-list-item.x-friend-item(v-for="user in groupDialog.members" :key="user.id" @click="showUserDialog(user.userId)" class="x-friend-item-border")
.avatar
img(v-lazy="userImage(user.user)")
@@ -1168,6 +1197,12 @@ html
i.el-icon-view(style="margin-right:5px")
el-tooltip(v-if="!user.isSubscribedToAnnouncements" placement="top" :content="$t('dialog.group.members.unsubscribed_announcements')")
i.el-icon-chat-line-square(style="margin-right:5px")
el-tooltip(v-if="user.managerNotes" placement="top")
template(#content)
span {{ $t('dialog.group.members.manager_notes') }}
br
span {{ user.managerNotes }}
i.el-icon-edit-outline(style="margin-right:5px")
template(v-for="roleId in user.roleIds" :key="roleId")
span(v-for="(role, rIndex) in groupDialog.ref.roles" :key="rIndex" v-if="role.id === roleId" v-text="role.name")
span(v-if="user.roleIds.indexOf(roleId) < user.roleIds.length - 1") ,&nbsp;
@@ -2799,6 +2834,91 @@ html
el-tooltip(placement="top" :content="$t('dialog.registry_backup.delete')" :disabled="hideTooltips")
el-button(type="text" icon="el-icon-delete" size="mini" @click="deleteVrcRegistryBackup(scope.row)")
//- dialog: group moderation
el-dialog.x-dialog(:before-close="beforeDialogClose" @mousedown.native="dialogMouseDown" @mouseup.native="dialogMouseUp" ref="groupMemberModeration" :visible.sync="groupMemberModeration.visible" :title="$t('dialog.group_member_moderation.header')" width="90vw")
div(v-if="groupMemberModeration.visible")
h3(v-text="groupMemberModeration.groupRef.name")
div(style="margin-top:10px")
el-button(type="default" @click="loadAllGroupMembers" size="mini" icon="el-icon-refresh" :loading="isGroupMembersLoading" circle)
span(style="font-size:14px;margin-left:5px;margin-right:5px") {{ groupMemberModerationTable.data.length }}/{{ groupMemberModeration.groupRef.memberCount }}
div(style="float:right;margin-top:5px")
span(style="margin-right:5px") {{ $t('dialog.group.members.sort_by') }}
el-dropdown(@click.native.stop trigger="click" size="small" style="margin-right:5px" :disabled="isGroupMembersLoading || groupDialog.memberSearch.length")
el-button(size="mini")
span {{ groupDialog.memberSortOrder.name }} #[i.el-icon-arrow-down.el-icon--right]
el-dropdown-menu(#default="dropdown")
el-dropdown-item(v-for="(item) in groupDialogSortingOptions" v-text="item.name" @click.native="setGroupMemberSortOrder(item)")
span(style="margin-right:5px") {{ $t('dialog.group.members.filter') }}
el-dropdown(@click.native.stop trigger="click" size="small" style="margin-right:5px" :disabled="isGroupMembersLoading || groupDialog.memberSearch.length")
el-button(size="mini")
span {{ groupDialog.memberFilter.name }} #[i.el-icon-arrow-down.el-icon--right]
el-dropdown-menu(#default="dropdown")
el-dropdown-item(v-for="(item) in groupDialogFilterOptions" v-text="item.name" @click.native="setGroupMemberFilter(item)")
el-dropdown-item(v-for="(item) in groupDialog.ref.roles" v-text="item.name" @click.native="setGroupMemberFilter(item)")
el-input(v-model="groupDialog.memberSearch" @input="groupMembersSearch" clearable size="mini" :placeholder="$t('dialog.group.members.search')" style="margin-top:10px;margin-bottom:10px")
br
el-button(size="small" @click="selectAllGroupMembers") {{ $t('dialog.group_member_moderation.select_all') }}
data-tables(v-bind="groupMemberModerationTable" style="margin-top:10px")
el-table-column(width="55" prop="$selected" :key="groupMemberModerationTable.key")
template(v-once #default="scope")
el-button(type="text" size="mini" @click.stop)
el-checkbox(v-model="scope.row.$selected" @change="groupMemberModerationTableSelectionChange(scope.row)")
el-table-column(:label="$t('dialog.group_member_moderation.avatar')" width="70" prop="photo")
template(v-once #default="scope")
el-popover(placement="right" height="500px" trigger="hover")
img.friends-list-avatar(slot="reference" v-lazy="userImage(scope.row.user)")
img.friends-list-avatar(v-lazy="userImageFull(scope.row.user)" style="height:500px;cursor:pointer" @click="showFullscreenImageDialog(userImageFull(scope.row.user))")
el-table-column(:label="$t('dialog.group_member_moderation.display_name')" width="160" prop="displayName" sortable :sort-method="(a, b) => sortAlphabetically(a.user, b.user, 'displayName')")
template(v-once #default="scope")
span(style="cursor:pointer" @click="showUserDialog(scope.row.userId)")
span(v-if="randomUserColours" v-text="scope.row.user.displayName" :style="{'color':scope.row.user.$userColour}")
span(v-else v-text="scope.row.user.displayName")
el-table-column(:label="$t('dialog.group_member_moderation.roles')" prop="roleIds" sortable)
template(v-once #default="scope")
template(v-for="roleId in scope.row.roleIds" :key="roleId")
span(v-for="(role, rIndex) in groupMemberModeration.groupRef.roles" :key="rIndex" v-if="role.id === roleId" v-text="role.name")
span(v-if="scope.row.roleIds.indexOf(roleId) < scope.row.roleIds.length - 1") ,&nbsp;
el-table-column(:label="$t('dialog.group_member_moderation.notes')" prop="managerNotes" sortable)
template(v-once #default="scope")
span(v-text="scope.row.managerNotes" @click.stop)
el-table-column(:label="$t('dialog.group_member_moderation.joined_at')" width="170" prop="joinedAt" sortable)
template(v-once #default="scope")
span {{ scope.row.joinedAt | formatDate('long') }}
el-table-column(:label="$t('dialog.group_member_moderation.visibility')" width="120" prop="visibility" sortable)
template(v-once #default="scope")
span(v-text="scope.row.visibility")
br
span.name {{ $t('dialog.group_member_moderation.selected_users') }}
el-button(type="default" @click="clearSelectedGroupMembers" size="mini" icon="el-icon-delete" circle style="margin-left:5px")
br
el-tag(v-for="user in groupMemberModeration.selectedUsersArray" type="info" disable-transitions="true" :key="user.id" style="margin-right:5px;margin-top:5px" closable @close="deleteSelectedGroupMember(user)")
span {{ user.user.displayName }}
br
br
span.name {{ $t('dialog.group_member_moderation.notes') }}
el-input.extra(v-model="groupMemberModeration.note" type="textarea" :rows="2" :autosize="{ minRows: 1, maxRows: 20 }" :placeholder="$t('dialog.group_member_moderation.note_placeholder')" size="mini" resize="none")
br
span.name {{ $t('dialog.group_member_moderation.selected_roles') }}
br
el-select(v-model="groupMemberModeration.selectedRoles" clearable multiple :placeholder="$t('dialog.group_member_moderation.choose_roles_placeholder')" filterable style="margin-top:5px")
el-option-group(:label="$t('dialog.group_member_moderation.roles')")
el-option.x-friend-item(v-for="role in groupMemberModeration.groupRef.roles" :key="role.id" :label="role.name" :value="role.id" style="height:auto")
.detail
span.name(v-text="role.name")
br
span.name {{ $t('dialog.group_member_moderation.actions') }}
br
el-button(@click="groupMembersAddRoles" :disabled="!groupMemberModeration.selectedRoles.length") {{ $t('dialog.group_member_moderation.add_roles') }}
el-button(@click="groupMembersRemoveRoles" :disabled="!groupMemberModeration.selectedRoles.length") {{ $t('dialog.group_member_moderation.remove_roles') }}
el-button(@click="groupMembersSaveNote" :disabled="groupMemberModeration.progressCurrent") {{ $t('dialog.group_member_moderation.save_note') }}
el-button(@click="groupMembersKick" :disabled="groupMemberModeration.progressCurrent") {{ $t('dialog.group_member_moderation.kick') }}
el-button(@click="groupMembersBan" :disabled="groupMemberModeration.progressCurrent") {{ $t('dialog.group_member_moderation.ban') }}
span(v-if="groupMemberModeration.progressCurrent" style="margin-top:10px") #[i.el-icon-loading(style="margin-left:5px;margin-right:5px")] {{ $t('dialog.group_member_moderation.progress') }} {{ groupMemberModeration.progressCurrent }}/{{ groupMemberModeration.progressTotal }}
el-button(v-if="groupMemberModeration.progressCurrent" @click="groupMemberModeration.progressTotal = 0" style="margin-left:5px") {{ $t('dialog.group_member_moderation.cancel') }}
//- el-dialog.x-dialog(:before-close="beforeDialogClose" @mousedown.native="dialogMouseDown" @mouseup.native="dialogMouseUp" ref="templateDialog" :visible.sync="templateDialog.visible" :title="$t('dialog.template_dialog.header')" width="450px")
//- dialog: open source software notice
el-dialog.x-dialog(:before-close="beforeDialogClose" @mousedown.native="dialogMouseDown" @mouseup.native="dialogMouseUp" :visible.sync="ossDialog" :title="$t('dialog.open_source.header')" width="650px")
div(v-if="ossDialog" style="height:350px;overflow:hidden scroll;word-break:break-all")

View File

@@ -802,6 +802,7 @@
"visibility_everyone": "Visibility Everyone",
"visibility_friends": "Visibility Friends",
"visibility_hidden": "Visibility Hidden",
"moderation_tools": "Moderation Tools",
"leave": "Leave Group"
},
"info": {
@@ -850,7 +851,9 @@
},
"unsubscribed_announcements": "Unsubscribed from announcements",
"visibility": "Visibility:",
"representing": "Representing"
"representing": "Representing",
"manager_notes": "Manager Notes:",
"search": "Search"
},
"gallery": {
"header": "Photos"
@@ -1246,6 +1249,30 @@
"action": "Action",
"auto_backup": "Weekly Auto Backup",
"restore_prompt": "VRCX has noticed auto backup of VRC registry settings is enabled but this computer dosn't have any, if you'd like to restore from backup you can do so from here."
},
"group_member_moderation": {
"header": "Group Member Moderation",
"notes": "Manager Note",
"note_placeholder": "Click to add a note",
"actions": "Actions",
"kick": "Kick",
"ban": "Ban",
"save_note": "Save Note",
"group_members": "Group Members",
"progress": "Progress:",
"display_name": "Display Name",
"visibility": "Visibility",
"avatar": "Avatar",
"joined_at": "Joined At",
"note": "Note",
"roles": "Roles",
"selected_users": "Selected Users",
"select_all": "Select All",
"cancel": "Cancel",
"choose_roles_placeholder": "Choose Roles",
"selected_roles": "Selected Roles",
"remove_roles": "Remove Roles",
"add_roles": "Add Roles"
}
},
"prompt": {