forked from AkkomaGang/admin-fe
Merge branch 'feature/do-not-show-users-with-null-nicknames' into 'develop'
Update displaying and managing accounts with invalid nicknames See merge request pleroma/admin-fe!133
This commit is contained in:
commit
cca3e01979
18 changed files with 226 additions and 131 deletions
|
@ -16,6 +16,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
- Add a dialog window with a confirmation when a remove button is clicked on the Settings page
|
||||
- Disable tab on the Settings page if there are no settings on this tab that can be changed in Admin FE
|
||||
- Settings that can't be altered in Admin FE are removed: HTTP Signatures settings, Federation publisher modules and Oban Repo
|
||||
- When rendering user's profile, statuses, reports and notes check if required properties exist
|
||||
- Remove ability to moderate users that don't have valid nickname
|
||||
|
||||
### Fixed
|
||||
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
const reports = [
|
||||
{ created_at: '2019-05-21T21:35:33.000Z', account: { display_name: 'Benjamin Fame', tags: [] }, actor: {}, state: 'open', id: '2', content: 'This is a report', statuses: [] },
|
||||
{ created_at: '2019-05-20T22:45:33.000Z', account: { display_name: 'Alice Pool', tags: [] }, actor: {}, state: 'resolved', id: '1', content: 'Please block this user', statuses: [] },
|
||||
{ created_at: '2019-05-18T13:01:33.000Z', account: { display_name: 'Nick Keys', tags: [] }, actor: {}, state: 'closed', id: '3', content: '', statuses: [] },
|
||||
{ created_at: '2019-05-21T21:35:33.000Z', account: { display_name: 'Benjamin Fame', tags: [] }, actor: {}, state: 'open', id: '5', content: 'This is a report', statuses: [] },
|
||||
{ created_at: '2019-05-20T22:45:33.000Z', account: { display_name: 'Alice Pool', tags: [] }, actor: {}, state: 'resolved', id: '7', content: 'Please block this user', statuses: [
|
||||
{ account: { display_name: 'Alice Pool', avatar: '' }, visibility: 'public', sensitive: false, id: '11', content: 'Hey!', url: '', created_at: '2019-05-10T21:35:33.000Z' },
|
||||
{ account: { display_name: 'Alice Pool', avatar: '' }, visibility: 'unlisted', sensitive: true, id: '10', content: 'Bye!', url: '', created_at: '2019-05-10T21:00:33.000Z' }
|
||||
{ created_at: '2019-05-21T21:35:33.000Z', account: { nickname: 'benjamin', tags: [] }, actor: {}, state: 'open', id: '2', content: 'This is a report', statuses: [] },
|
||||
{ created_at: '2019-05-20T22:45:33.000Z', account: { nickname: 'alice', tags: [] }, actor: {}, state: 'resolved', id: '1', content: 'Please block this user', statuses: [] },
|
||||
{ created_at: '2019-05-18T13:01:33.000Z', account: { nickname: 'nick_keys', tags: [] }, actor: {}, state: 'closed', id: '3', content: '', statuses: [] },
|
||||
{ created_at: '2019-05-21T21:35:33.000Z', account: { nickname: 'benjamin', tags: [] }, actor: {}, state: 'open', id: '5', content: 'This is a report', statuses: [] },
|
||||
{ created_at: '2019-05-20T22:45:33.000Z', account: { nickname: 'alice', tags: [] }, actor: {}, state: 'resolved', id: '7', content: 'Please block this user', statuses: [
|
||||
{ account: { nickname: 'alice', avatar: '' }, visibility: 'public', sensitive: false, id: '11', content: 'Hey!', url: '', created_at: '2019-05-10T21:35:33.000Z' },
|
||||
{ account: { nickname: 'alice', avatar: '' }, visibility: 'unlisted', sensitive: true, id: '10', content: 'Bye!', url: '', created_at: '2019-05-10T21:00:33.000Z' }
|
||||
] },
|
||||
{ created_at: '2019-05-18T13:01:33.000Z', account: { display_name: 'Nick Keys', tags: [] }, actor: {}, state: 'closed', id: '6', content: '', statuses: [] },
|
||||
{ created_at: '2019-05-18T13:01:33.000Z', account: { display_name: 'Nick Keys', tags: [] }, actor: {}, state: 'closed', id: '4', content: '', statuses: [] }
|
||||
{ created_at: '2019-05-18T13:01:33.000Z', account: { nickname: 'nick_keys', tags: [] }, actor: {}, state: 'closed', id: '6', content: '', statuses: [] },
|
||||
{ created_at: '2019-05-18T13:01:33.000Z', account: { nickname: 'nick_keys', tags: [] }, actor: {}, state: 'closed', id: '4', content: '', statuses: [] }
|
||||
]
|
||||
|
||||
export async function fetchReports(filter, page, pageSize, authHost, token) {
|
||||
|
|
|
@ -11,7 +11,7 @@ export async function fetchStatus(id, authHost, token) {
|
|||
account: {
|
||||
id: '9n1bySks25olxWrku0',
|
||||
avatar: 'http://localhost:4000/images/avi.png',
|
||||
display_name: 'dolin',
|
||||
nickname: 'dolin',
|
||||
tags: ['strip_media', 'sandbox', 'disable_any_subscription', 'force_nsfw'],
|
||||
url: 'http://localhost:4000/users/dolin'
|
||||
},
|
||||
|
@ -36,7 +36,7 @@ export async function fetchStatusesByInstance({ instance, authHost, token, pageS
|
|||
? [{
|
||||
'account': {
|
||||
'avatar': 'http://localhost:4000/images/avi.png',
|
||||
'display_name': 'sky',
|
||||
'nickname': 'sky',
|
||||
'url': 'http://localhost:4000/users/sky'
|
||||
},
|
||||
'content': 'A nice young couple contacted us from Brazil to decorate their newly acquired apartment.',
|
||||
|
@ -52,7 +52,7 @@ export async function fetchStatusesByInstance({ instance, authHost, token, pageS
|
|||
{
|
||||
'account': {
|
||||
'avatar': 'http://localhost:4000/images/avi.png',
|
||||
'display_name': 'sky',
|
||||
'nickname': 'sky',
|
||||
'url': 'http://localhost:4000/users/sky'
|
||||
},
|
||||
'content': 'A nice young couple contacted us from Brazil to decorate their newly acquired apartment.',
|
||||
|
@ -65,7 +65,7 @@ export async function fetchStatusesByInstance({ instance, authHost, token, pageS
|
|||
{
|
||||
'account': {
|
||||
'avatar': 'http://localhost:4000/images/avi.png',
|
||||
'display_name': 'sky',
|
||||
'nickname': 'sky',
|
||||
'url': 'http://localhost:4000/users/sky'
|
||||
},
|
||||
'content': 'the happiest man ever',
|
||||
|
|
|
@ -4,12 +4,12 @@ export let users = [
|
|||
{ active: false, deactivated: true, id: 'abc', nickname: 'john', local: true, external: false, roles: { admin: false, moderator: false }, tags: ['strip_media'] }
|
||||
]
|
||||
|
||||
const userProfile = { avatar: 'avatar.jpg', display_name: 'Allis', nickname: 'allis', id: '2', tags: [], roles: { admin: true, moderator: false }, local: true, external: false }
|
||||
const userProfile = { avatar: 'avatar.jpg', nickname: 'allis', id: '2', tags: [], roles: { admin: true, moderator: false }, local: true, external: false }
|
||||
|
||||
const userStatuses = [
|
||||
{ account: { id: '9n1bySks25olxWrku0', display_name: 'dolin' }, content: 'pizza makes everything better', id: '9vJOO3iFPyjNaEhJ5s', created_at: '2020-05-22T17:34:34.000Z', visibility: 'public' },
|
||||
{ account: { id: '9n1bySks25olxWrku0', display_name: 'dolin' }, content: 'pizza time', id: '9vJPD5XKOdzQ0bvGLY', created_at: '2020-05-22T17:34:34.000Z', visibility: 'public' },
|
||||
{ account: { id: '9n1bySks25olxWrku0', display_name: 'dolin' }, content: 'what is yout favorite pizza?', id: '9jop82OBXeFPYulVjM', created_at: '2020-05-22T17:34:34.000Z', visibility: 'public' }
|
||||
{ account: { id: '9n1bySks25olxWrku0', nickname: 'dolin' }, content: 'pizza makes everything better', id: '9vJOO3iFPyjNaEhJ5s', created_at: '2020-05-22T17:34:34.000Z', visibility: 'public' },
|
||||
{ account: { id: '9n1bySks25olxWrku0', nickname: 'dolin' }, content: 'pizza time', id: '9vJPD5XKOdzQ0bvGLY', created_at: '2020-05-22T17:34:34.000Z', visibility: 'public' },
|
||||
{ account: { id: '9n1bySks25olxWrku0', nickname: 'dolin' }, content: 'what is yout favorite pizza?', id: '9jop82OBXeFPYulVjM', created_at: '2020-05-22T17:34:34.000Z', visibility: 'public' }
|
||||
]
|
||||
|
||||
const filterUsers = (str) => {
|
||||
|
|
|
@ -5,16 +5,18 @@
|
|||
<div class="status-account-container">
|
||||
<div class="status-account">
|
||||
<el-checkbox v-if="showCheckbox" class="status-checkbox" @change="handleStatusSelection(account)"/>
|
||||
<router-link v-if="!account.deactivated && account.id" :to="{ name: 'UsersShow', params: { id: account.id }}" @click.native.stop>
|
||||
<router-link v-if="propertyExists(account, 'id')" :to="{ name: 'UsersShow', params: { id: account.id }}" @click.native.stop>
|
||||
<div class="status-card-header">
|
||||
<img :src="account.avatar" class="status-avatar-img">
|
||||
<h3 class="status-account-name">{{ account.display_name }}</h3>
|
||||
<img v-if="propertyExists(account, 'avatar')" :src="account.avatar" class="status-avatar-img">
|
||||
<span v-if="propertyExists(account, 'nickname')" class="status-account-name">{{ account.nickname }}</span>
|
||||
<span v-else>
|
||||
<span v-if="propertyExists(account, 'nickname')" class="status-account-name">
|
||||
{{ account.nickname }}
|
||||
</span>
|
||||
<span v-else class="status-account-name deactivated">({{ $t('users.invalidNickname') }})</span>
|
||||
</span>
|
||||
</div>
|
||||
</router-link>
|
||||
<span v-else>
|
||||
<h3 class="status-account-name">{{ account.display_name }}</h3>
|
||||
<h3 class="status-account-name deactivated"> (deactivated)</h3>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="status-actions">
|
||||
|
@ -228,6 +230,12 @@ export default {
|
|||
},
|
||||
parseTimestamp(timestamp) {
|
||||
return moment(timestamp).format('YYYY-MM-DD HH:mm')
|
||||
},
|
||||
propertyExists(account, property, _secondProperty) {
|
||||
if (_secondProperty) {
|
||||
return account[property] && account[_secondProperty]
|
||||
}
|
||||
return account[property]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -245,6 +253,11 @@ export default {
|
|||
.account:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.deactivated {
|
||||
color: gray;
|
||||
line-height: 28px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.image {
|
||||
width: 20%;
|
||||
img {
|
||||
|
@ -267,7 +280,8 @@ export default {
|
|||
.status-account-name {
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.status-body {
|
||||
display: flex;
|
||||
|
|
|
@ -238,7 +238,11 @@ export default {
|
|||
unconfirmedEmail: 'User didn\'t confirm the email',
|
||||
confirmAccount: 'Confirm account',
|
||||
confirmAccounts: 'Confirm accounts',
|
||||
resendConfirmation: 'Resend confirmation email'
|
||||
resendConfirmation: 'Resend confirmation email',
|
||||
invalidAccount: 'This account has invalid nickname and can\'t be modified',
|
||||
invalidNickname: 'invalid nickname',
|
||||
passwordResetTokenGenerated: 'Password reset token was generated:',
|
||||
linkToResetPassword: 'You can also use this link to reset password:'
|
||||
},
|
||||
statuses: {
|
||||
statuses: 'Statuses',
|
||||
|
|
|
@ -61,9 +61,8 @@ const reports = {
|
|||
const optimisticNote = {
|
||||
user: {
|
||||
avatar: rootState.user.avatar,
|
||||
display_name: rootState.user.name,
|
||||
url: `${rootState.user.authHost}/${rootState.user.name}`,
|
||||
acct: rootState.user.name
|
||||
nickname: rootState.user.name,
|
||||
id: rootState.user.id
|
||||
},
|
||||
content: content,
|
||||
created_at: new Date().getTime()
|
||||
|
|
|
@ -123,6 +123,10 @@ const users = {
|
|||
|
||||
dispatch('ApplyChanges', { updatedUsers, callApiFn, userId: _userId, statusId: _statusId })
|
||||
},
|
||||
ClearUsersState({ commit }) {
|
||||
commit('SET_SEARCH_QUERY', '')
|
||||
commit('SET_USERS_FILTERS', { local: false, external: false, active: false, deactivated: false })
|
||||
},
|
||||
async ClearFilters({ commit, dispatch, state }) {
|
||||
commit('CLEAR_USERS_FILTERS')
|
||||
dispatch('SearchUsers', { query: state.searchQuery, page: 1 })
|
||||
|
|
|
@ -2,25 +2,14 @@
|
|||
<el-card class="note-card">
|
||||
<div slot="header">
|
||||
<div class="note-header">
|
||||
<div class="note-actor-container">
|
||||
<div class="note-actor">
|
||||
<img :src="note.user.avatar" class="note-avatar-img">
|
||||
<h3 class="note-actor-name">{{ note.user.display_name }}</h3>
|
||||
</div>
|
||||
<a :href="note.user.url" target="_blank">
|
||||
@{{ note.user.display_name }}
|
||||
</a>
|
||||
<img v-if="propertyExists(note.user, 'avatar')" :src="note.user.avatar" class="note-avatar-img">
|
||||
<span v-if="propertyExists(note.user, 'nickname')" class="note-actor-name">{{ note.user.nickname }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<el-popconfirm
|
||||
title="Are you sure to delete this?"
|
||||
confirm-button-text="Yes"
|
||||
cancel-button-text="No"
|
||||
@onConfirm="handleNoteDeletion(note.id, report.id)">
|
||||
<el-button slot="reference" size="mini">
|
||||
<el-button size="mini" @click.native="handleNoteDeletion(note.id, report.id)">
|
||||
{{ $t('reports.deleteNote') }}
|
||||
</el-button>
|
||||
</el-popconfirm>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -47,11 +36,29 @@ export default {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
handleNoteDeletion(noteID, reportID) {
|
||||
this.$confirm('Are you sure you want to delete this note?', 'Warning', {
|
||||
confirmButtonText: 'OK',
|
||||
cancelButtonText: 'Cancel',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
this.$store.dispatch('DeleteReportNote', { noteID, reportID })
|
||||
this.$message({
|
||||
type: 'success',
|
||||
message: 'Delete completed'
|
||||
})
|
||||
}).catch(() => {
|
||||
this.$message({
|
||||
type: 'info',
|
||||
message: 'Delete canceled'
|
||||
})
|
||||
})
|
||||
},
|
||||
parseTimestamp(timestamp) {
|
||||
return moment(timestamp).format('YYYY-MM-DD HH:mm')
|
||||
},
|
||||
handleNoteDeletion(noteID, reportID) {
|
||||
this.$store.dispatch('DeleteReportNote', { noteID, reportID })
|
||||
propertyExists(account, property) {
|
||||
return account[property]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -76,7 +83,7 @@ export default {
|
|||
}
|
||||
.note-actor-name {
|
||||
margin: 0;
|
||||
height: 22px;
|
||||
height: 28px;
|
||||
}
|
||||
.note-avatar-img {
|
||||
width: 15px;
|
||||
|
@ -96,6 +103,10 @@ export default {
|
|||
.note-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 28px;
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@media only screen and (max-width:480px) {
|
||||
|
@ -105,14 +116,15 @@ export default {
|
|||
.note-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 80px;
|
||||
height: 65px;
|
||||
}
|
||||
.note-actor-container {
|
||||
.note-actor {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.note-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -10,9 +10,9 @@
|
|||
<el-card class="report">
|
||||
<div class="report-header-container">
|
||||
<div class="title-container">
|
||||
<h3 v-if="accountExists(report.account, 'display_name')" class="report-title">{{ $t('reports.reportOn') }} {{ report.account.display_name }}</h3>
|
||||
<h3 v-if="propertyExists(report.account, 'nickname')" class="report-title">{{ $t('reports.reportOn') }} {{ report.account.nickname }}</h3>
|
||||
<h3 v-else class="report-title">{{ $t('reports.report') }}</h3>
|
||||
<h5 class="id">{{ $t('reports.id') }}: {{ report.id }}</h5>
|
||||
<h5 v-if="propertyExists(report.account, 'id')" class="id">{{ $t('reports.id') }}: {{ report.id }}</h5>
|
||||
</div>
|
||||
<div>
|
||||
<el-tag :type="getStateType(report.state)" size="large" class="report-tag">{{ capitalizeFirstLetter(report.state) }}</el-tag>
|
||||
|
@ -24,26 +24,26 @@
|
|||
<el-dropdown-item v-if="report.state !== 'closed'" @click.native="changeReportState('closed', report.id)">{{ $t('reports.close') }}</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
<moderate-user-dropdown :account="report.account"/>
|
||||
<moderate-user-dropdown v-if="propertyExists(report.account, 'nickname')" :account="report.account"/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<el-divider class="divider"/>
|
||||
<span class="report-row-key">{{ $t('reports.account') }}:</span>
|
||||
<span v-if="accountExists(report.account, 'avatar') && accountExists(report.account, 'display_name')">
|
||||
<img
|
||||
v-if="propertyExists(report.account, 'avatar')"
|
||||
:src="report.account.avatar"
|
||||
alt="avatar"
|
||||
class="avatar-img">
|
||||
<a v-if="!report.account.deactivated" :href="report.account.url" target="_blank" class="account">
|
||||
<span>{{ report.account.display_name }}</span>
|
||||
<a v-if="propertyExists(report.account, 'url', 'nickname')" :href="report.account.url" target="_blank" class="account">
|
||||
<span class="report-account-name">{{ report.account.nickname }}</span>
|
||||
</a>
|
||||
<span v-else>
|
||||
{{ report.account.display_name }}
|
||||
<span class="deactivated"> (deactivated)</span>
|
||||
<span v-if="propertyExists(report.account, 'nickname')" class="report-account-name">
|
||||
{{ report.account.nickname }}
|
||||
</span>
|
||||
<span v-else class="report-account-name deactivated">({{ $t('users.invalidNickname') }})</span>
|
||||
</span>
|
||||
<span v-else class="deactivated">({{ $t('reports.notFound') }})</span>
|
||||
</div>
|
||||
<div v-if="report.content && report.content.length > 0">
|
||||
<el-divider class="divider"/>
|
||||
|
@ -54,22 +54,26 @@
|
|||
<div :style="showStatuses(report.statuses) ? '' : 'margin-bottom:15px'">
|
||||
<el-divider class="divider"/>
|
||||
<span class="report-row-key">{{ $t('reports.actor') }}:</span>
|
||||
<span v-if="accountExists(report.actor, 'avatar') && accountExists(report.actor, 'display_name')">
|
||||
<img
|
||||
v-if="propertyExists(report.actor, 'avatar')"
|
||||
:src="report.actor.avatar"
|
||||
alt="avatar"
|
||||
class="avatar-img">
|
||||
<a :href="report.actor.url" target="_blank" class="account">
|
||||
<span>{{ report.actor.display_name }}</span>
|
||||
<a v-if="propertyExists(report.actor, 'url', 'nickname')" :href="report.actor.url" target="_blank" class="account">
|
||||
<span class="report-account-name">{{ report.actor.nickname }}</span>
|
||||
</a>
|
||||
<span v-else>
|
||||
<span v-if="propertyExists(report.actor, 'nickname')" class="report-account-name">
|
||||
{{ report.actor.nickname }}
|
||||
</span>
|
||||
<span v-else class="report-account-name deactivated">({{ $t('users.invalidNickname') }})</span>
|
||||
</span>
|
||||
<span v-else class="deactivated">({{ $t('reports.notFound') }})</span>
|
||||
</div>
|
||||
<div v-if="showStatuses(report.statuses)" class="statuses">
|
||||
<el-collapse>
|
||||
<el-collapse-item :title="getStatusesTitle(report.statuses)">
|
||||
<div v-for="status in report.statuses" :key="status.id">
|
||||
<status :status="status" :account="status.account.display_name ? status.account : report.account" :show-checkbox="false" :page="currentPage"/>
|
||||
<status :status="status" :account="status.account.nickname ? status.account : report.account" :show-checkbox="false" :page="currentPage"/>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
|
@ -142,9 +146,6 @@ export default {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
accountExists(account, key) {
|
||||
return account[key]
|
||||
},
|
||||
changeReportState(state, id) {
|
||||
this.$store.dispatch('ChangeReportState', [{ state, id }])
|
||||
},
|
||||
|
@ -177,6 +178,12 @@ export default {
|
|||
parseTimestamp(timestamp) {
|
||||
return moment(timestamp).format('L HH:mm')
|
||||
},
|
||||
propertyExists(account, property, _secondProperty) {
|
||||
if (_secondProperty) {
|
||||
return account[property] && account[_secondProperty]
|
||||
}
|
||||
return account[property]
|
||||
},
|
||||
showStatuses(statuses = []) {
|
||||
return statuses.length > 0
|
||||
}
|
||||
|
@ -262,6 +269,10 @@ export default {
|
|||
font-style: italic;
|
||||
color: gray;
|
||||
}
|
||||
.report-account-name {
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.report-row-key {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
|
|
|
@ -2,13 +2,14 @@
|
|||
<div v-if="!loading" class="status-show-container">
|
||||
<header v-if="isDesktop || isTablet" class="user-page-header">
|
||||
<div class="avatar-name-container">
|
||||
<router-link :to="{ name: 'UsersShow', params: { id: user.id }}">
|
||||
<router-link v-if="propertyExists(user, 'id')" :to="{ name: 'UsersShow', params: { id: user.id }}">
|
||||
<div class="avatar-name-header">
|
||||
<el-avatar v-if="accountExists(user, 'avatar')" :src="user.avatar" size="large" />
|
||||
<h1 v-if="accountExists(user, 'display_name')">{{ user.display_name }}</h1>
|
||||
<el-avatar v-if="propertyExists(user, 'avatar')" :src="user.avatar" size="large" />
|
||||
<h1 v-if="propertyExists(user, 'nickname')">{{ user.nickname }}</h1>
|
||||
<h1 v-else class="invalid">({{ $t('users.invalidNickname') }})</h1>
|
||||
</div>
|
||||
</router-link>
|
||||
<a v-if="accountExists(user, 'url')" :href="user.url" target="_blank" class="account">
|
||||
<a v-if="propertyExists(user, 'url')" :href="user.url" target="_blank" class="account">
|
||||
<i class="el-icon-top-right" title="Open user in instance"/>
|
||||
</a>
|
||||
</div>
|
||||
|
@ -24,8 +25,8 @@
|
|||
<div v-if="isMobile" class="status-page-header-container">
|
||||
<header class="user-page-header">
|
||||
<div class="avatar-name-container">
|
||||
<el-avatar v-if="accountExists(user, 'avatar')" :src="user.avatar" size="large" />
|
||||
<h1 v-if="accountExists(user, 'display_name')">{{ user.display_name }}</h1>
|
||||
<el-avatar v-if="propertyExists(user, 'avatar')" :src="user.avatar" size="large" />
|
||||
<h1 v-if="propertyExists(user, 'nickname')">{{ user.nickname }}</h1>
|
||||
</div>
|
||||
<reboot-button/>
|
||||
</header>
|
||||
|
@ -41,7 +42,10 @@
|
|||
<status :status="status" :account="user" :show-checkbox="false" :godmode="showPrivate"/>
|
||||
</div>
|
||||
<div class="recent-statuses-container-show">
|
||||
<h2 class="recent-statuses">{{ $t('userProfile.recentStatuses') }} by {{ user.display_name }}</h2>
|
||||
<h2 v-if="propertyExists(user, 'nickname')" class="recent-statuses">
|
||||
{{ $t('userProfile.recentStatuses') }} by {{ user.nickname }}
|
||||
</h2>
|
||||
<h2 v-else class="recent-statuses">{{ $t('userProfile.recentStatuses') }}</h2>
|
||||
<el-checkbox v-model="showPrivate" class="show-private-statuses" @change="onTogglePrivate">
|
||||
{{ $t('statuses.showPrivateStatuses') }}
|
||||
</el-checkbox>
|
||||
|
@ -102,9 +106,6 @@ export default {
|
|||
this.$store.dispatch('FetchStatus', this.$route.params.id)
|
||||
},
|
||||
methods: {
|
||||
accountExists(account, key) {
|
||||
return account[key]
|
||||
},
|
||||
closeResetPasswordDialog() {
|
||||
this.resetPasswordDialogOpen = false
|
||||
this.$store.dispatch('RemovePasswordToken')
|
||||
|
@ -114,6 +115,9 @@ export default {
|
|||
},
|
||||
openResetPasswordDialog() {
|
||||
this.resetPasswordDialogOpen = true
|
||||
},
|
||||
propertyExists(account, property) {
|
||||
return account[property]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -134,6 +138,9 @@ export default {
|
|||
height: 40px;
|
||||
align-items: center;
|
||||
}
|
||||
.invalid {
|
||||
color: gray;
|
||||
}
|
||||
.no-statuses {
|
||||
margin-left: 28px;
|
||||
color: #606266;
|
||||
|
@ -176,6 +183,7 @@ export default {
|
|||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin: 22px 15px 22px 20px;
|
||||
padding: 0;
|
||||
align-items: center;
|
||||
h1 {
|
||||
display: inline;
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<template>
|
||||
<el-dropdown :hide-on-click="false" size="small" trigger="click" placement="top-start">
|
||||
<el-dropdown :hide-on-click="false" size="small" trigger="click" placement="top-start" @click.native.stop>
|
||||
<div>
|
||||
<span v-if="page === 'users'" class="el-dropdown-link">
|
||||
<el-button v-if="page === 'users'" type="text" class="el-dropdown-link">
|
||||
{{ $t('users.moderation') }}
|
||||
<i v-if="isDesktop" class="el-icon-arrow-down el-icon--right"/>
|
||||
</span>
|
||||
</el-button>
|
||||
<el-button v-if="page === 'userPage' || page === 'statusPage'" class="moderate-user-button">
|
||||
<span class="moderate-user-button-container">
|
||||
<span>
|
||||
|
|
|
@ -165,33 +165,33 @@ export default {
|
|||
}
|
||||
return {
|
||||
grantRight: (right) => () => {
|
||||
const filterUsersFn = user => user.local && !user.roles[right] && this.$store.state.user.id !== user.id
|
||||
const filterUsersFn = user => this.isLocalUser(user) && !user.roles[right] && this.$store.state.user.id !== user.id
|
||||
const addRightFn = async(users) => await this.$store.dispatch('AddRight', { users, right })
|
||||
const filtered = this.selectedUsers.filter(filterUsersFn)
|
||||
|
||||
applyAction(filtered, addRightFn)
|
||||
},
|
||||
revokeRight: (right) => () => {
|
||||
const filterUsersFn = user => user.local && user.roles[right] && this.$store.state.user.id !== user.id
|
||||
const filterUsersFn = user => this.isLocalUser(user) && user.roles[right] && this.$store.state.user.id !== user.id
|
||||
const deleteRightFn = async(users) => await this.$store.dispatch('DeleteRight', { users, right })
|
||||
const filtered = this.selectedUsers.filter(filterUsersFn)
|
||||
|
||||
applyAction(filtered, deleteRightFn)
|
||||
},
|
||||
activate: () => {
|
||||
const filtered = this.selectedUsers.filter(user => user.deactivated && this.$store.state.user.id !== user.id)
|
||||
const filtered = this.selectedUsers.filter(user => user.nickname && user.deactivated && this.$store.state.user.id !== user.id)
|
||||
const activateUsersFn = async(users) => await this.$store.dispatch('ActivateUsers', { users })
|
||||
|
||||
applyAction(filtered, activateUsersFn)
|
||||
},
|
||||
deactivate: () => {
|
||||
const filtered = this.selectedUsers.filter(user => !user.deactivated && this.$store.state.user.id !== user.id)
|
||||
const filtered = this.selectedUsers.filter(user => user.nickname && !user.deactivated && this.$store.state.user.id !== user.id)
|
||||
const deactivateUsersFn = async(users) => await this.$store.dispatch('DeactivateUsers', { users })
|
||||
|
||||
applyAction(filtered, deactivateUsersFn)
|
||||
},
|
||||
remove: () => {
|
||||
const filtered = this.selectedUsers.filter(user => this.$store.state.user.id !== user.id)
|
||||
const filtered = this.selectedUsers.filter(user => user.nickname && this.$store.state.user.id !== user.id)
|
||||
const deleteAccountFn = async(users) => await this.$store.dispatch('DeleteUsers', { users })
|
||||
|
||||
applyAction(filtered, deleteAccountFn)
|
||||
|
@ -199,40 +199,43 @@ export default {
|
|||
addTag: (tag) => () => {
|
||||
const filtered = this.selectedUsers.filter(user =>
|
||||
tag === 'disable_remote_subscription' || tag === 'disable_any_subscription'
|
||||
? user.local && !user.tags.includes(tag)
|
||||
: !user.tags.includes(tag))
|
||||
? this.isLocalUser(user) && !user.tags.includes(tag)
|
||||
: user.nickname && !user.tags.includes(tag))
|
||||
const addTagFn = async(users) => await this.$store.dispatch('AddTag', { users, tag })
|
||||
applyAction(filtered, addTagFn)
|
||||
},
|
||||
removeTag: (tag) => async() => {
|
||||
const filtered = this.selectedUsers.filter(user =>
|
||||
tag === 'disable_remote_subscription' || tag === 'disable_any_subscription'
|
||||
? user.local && user.tags.includes(tag)
|
||||
: user.tags.includes(tag))
|
||||
? this.isLocalUser(user) && user.tags.includes(tag)
|
||||
: user.nickname && user.tags.includes(tag))
|
||||
const removeTagFn = async(users) => await this.$store.dispatch('RemoveTag', { users, tag })
|
||||
|
||||
applyAction(filtered, removeTagFn)
|
||||
},
|
||||
requirePasswordReset: () => {
|
||||
const filtered = this.selectedUsers.filter(user => user.local)
|
||||
const filtered = this.selectedUsers.filter(user => this.isLocalUser(user))
|
||||
const requirePasswordResetFn = async(users) => await this.$store.dispatch('RequirePasswordReset', users)
|
||||
|
||||
applyAction(filtered, requirePasswordResetFn)
|
||||
},
|
||||
confirmAccounts: () => {
|
||||
const filtered = this.selectedUsers.filter(user => user.local && user.confirmation_pending)
|
||||
const filtered = this.selectedUsers.filter(user => this.isLocalUser(user) && user.confirmation_pending)
|
||||
const confirmAccountFn = async(users) => await this.$store.dispatch('ConfirmUsersEmail', { users })
|
||||
|
||||
applyAction(filtered, confirmAccountFn)
|
||||
},
|
||||
resendConfirmation: () => {
|
||||
const filtered = this.selectedUsers.filter(user => user.local && user.confirmation_pending)
|
||||
const filtered = this.selectedUsers.filter(user => this.isLocalUser(user) && user.confirmation_pending)
|
||||
const resendConfirmationFn = async(users) => await this.$store.dispatch('ResendConfirmationEmail', users)
|
||||
|
||||
applyAction(filtered, resendConfirmationFn)
|
||||
}
|
||||
}
|
||||
},
|
||||
isLocalUser(user) {
|
||||
return user.nickname && user.local
|
||||
},
|
||||
grantRightToMultipleUsers(right) {
|
||||
const { grantRight } = this.mappers()
|
||||
this.confirmMessage(
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
custom-class="password-reset-token-dialog"
|
||||
@close="closeResetPasswordDialog">
|
||||
<div>
|
||||
<p class="password-reset-token">Password reset token was generated: {{ passwordResetToken }}</p>
|
||||
<p>You can also use this link to reset password:
|
||||
<p class="password-reset-token">{{ $t('users.passwordResetTokenGenerated') }} {{ passwordResetToken }}</p>
|
||||
<p>{{ $t('users.linkToResetPassword') }}
|
||||
<a :href="passwordResetLink" target="_blank" class="reset-password-link">{{ passwordResetLink }}</a>
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
:data="users"
|
||||
row-key="id"
|
||||
style="width: 100%"
|
||||
@row-click="handleRowClick($event)"
|
||||
@selection-change="handleSelectionChange">
|
||||
<el-table-column
|
||||
v-if="isDesktop"
|
||||
|
@ -47,7 +48,7 @@
|
|||
<el-table-column :min-width="width" :label="$t('users.id')" prop="id" />
|
||||
<el-table-column :label="$t('users.name')" prop="nickname">
|
||||
<template slot-scope="scope">
|
||||
<router-link :to="{ name: 'UsersShow', params: { id: scope.row.id }}">{{ scope.row.nickname }}</router-link>
|
||||
{{ scope.row.nickname }}
|
||||
<el-tag v-if="isDesktop" type="info" size="mini">
|
||||
<span>{{ scope.row.local ? $t('users.local') : $t('users.external') }}</span>
|
||||
</el-tag>
|
||||
|
@ -75,9 +76,14 @@
|
|||
<el-table-column :label="$t('users.actions')" fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<moderation-dropdown
|
||||
v-if="propertyExists(scope.row, 'nickname')"
|
||||
:user="scope.row"
|
||||
:page="'users'"
|
||||
@open-reset-token-dialog="openResetPasswordDialog"/>
|
||||
<el-button v-else type="text" disabled>
|
||||
{{ $t('users.moderation') }}
|
||||
<i v-if="isDesktop" class="el-icon-arrow-down el-icon--right"/>
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
@ -132,12 +138,6 @@ export default {
|
|||
normalizedUsersCount() {
|
||||
return numeral(this.$store.state.users.totalUsersCount).format('0a')
|
||||
},
|
||||
users() {
|
||||
return this.$store.state.users.fetchedUsers
|
||||
},
|
||||
usersCount() {
|
||||
return this.$store.state.users.totalUsersCount
|
||||
},
|
||||
pageSize() {
|
||||
return this.$store.state.users.pageSize
|
||||
},
|
||||
|
@ -150,6 +150,12 @@ export default {
|
|||
isMobile() {
|
||||
return this.$store.state.app.device === 'mobile'
|
||||
},
|
||||
users() {
|
||||
return this.$store.state.users.fetchedUsers
|
||||
},
|
||||
usersCount() {
|
||||
return this.$store.state.users.totalUsersCount
|
||||
},
|
||||
width() {
|
||||
return this.isMobile ? 55 : false
|
||||
}
|
||||
|
@ -163,6 +169,9 @@ export default {
|
|||
this.$store.dispatch('NeedReboot')
|
||||
this.$store.dispatch('FetchUsers', { page: 1 })
|
||||
},
|
||||
destroyed() {
|
||||
this.$store.dispatch('ClearUsersState')
|
||||
},
|
||||
methods: {
|
||||
activationIcon(status) {
|
||||
return status ? 'el-icon-error' : 'el-icon-success'
|
||||
|
@ -189,12 +198,20 @@ export default {
|
|||
this.$store.dispatch('SearchUsers', { query: searchQuery, page })
|
||||
}
|
||||
},
|
||||
handleRowClick(row) {
|
||||
if (row.id) {
|
||||
this.$router.push({ name: 'UsersShow', params: { id: row.id }})
|
||||
}
|
||||
},
|
||||
handleSelectionChange(value) {
|
||||
this.$data.selectedUsers = value
|
||||
},
|
||||
openResetPasswordDialog() {
|
||||
this.resetPasswordDialogOpen = true
|
||||
},
|
||||
propertyExists(account, property) {
|
||||
return account[property]
|
||||
},
|
||||
showDeactivatedButton(id) {
|
||||
return this.$store.state.user.id !== id
|
||||
}
|
||||
|
@ -253,6 +270,9 @@ export default {
|
|||
margin: 10px 0 0 15px;
|
||||
height: 40px;
|
||||
}
|
||||
.el-table__row:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
.pagination {
|
||||
margin: 25px 0;
|
||||
text-align: center;
|
||||
|
|
|
@ -2,11 +2,13 @@
|
|||
<main v-if="!userProfileLoading">
|
||||
<header v-if="isDesktop || isTablet" class="user-page-header">
|
||||
<div class="avatar-name-container">
|
||||
<el-avatar :src="user.avatar" size="large" />
|
||||
<h1>{{ user.display_name }}</h1>
|
||||
<el-avatar v-if="propertyExists(user, 'avatar')" :src="user.avatar" size="large" />
|
||||
<h1 v-if="propertyExists(user, 'nickname')">{{ user.nickname }}</h1>
|
||||
<h1 v-else class="invalid">({{ $t('users.invalidNickname') }})</h1>
|
||||
</div>
|
||||
<div class="left-header-container">
|
||||
<moderation-dropdown
|
||||
v-if="propertyExists(user, 'nickname')"
|
||||
:user="user"
|
||||
:page="'userPage'"
|
||||
@open-reset-token-dialog="openResetPasswordDialog"/>
|
||||
|
@ -16,12 +18,14 @@
|
|||
<div v-if="isMobile" class="user-page-header-container">
|
||||
<header class="user-page-header">
|
||||
<div class="avatar-name-container">
|
||||
<el-avatar :src="user.avatar" size="large" />
|
||||
<h1>{{ user.display_name }}</h1>
|
||||
<el-avatar v-if="propertyExists(user, 'avatar')" :src="user.avatar" size="large" />
|
||||
<h1 v-if="propertyExists(user, 'nickname')">{{ user.nickname }}</h1>
|
||||
<h1 v-else class="invalid">({{ $t('users.invalidNickname') }})</h1>
|
||||
</div>
|
||||
<reboot-button/>
|
||||
</header>
|
||||
<moderation-dropdown
|
||||
v-if="propertyExists(user, 'nickname')"
|
||||
:user="user"
|
||||
:page="'userPage'"
|
||||
@open-reset-token-dialog="openResetPasswordDialog"/>
|
||||
|
@ -32,14 +36,11 @@
|
|||
<div class="user-profile-container">
|
||||
<el-card class="user-profile-card">
|
||||
<div class="el-table el-table--fit el-table--enable-row-hover el-table--enable-row-transition el-table--medium">
|
||||
<el-tag v-if="!propertyExists(user, 'nickname')" type="info" class="invalid-user-tag">
|
||||
{{ $t('users.invalidAccount') }}
|
||||
</el-tag>
|
||||
<table class="user-profile-table">
|
||||
<tbody>
|
||||
<tr class="el-table__row">
|
||||
<td>{{ $t('userProfile.nickname') }}</td>
|
||||
<td>
|
||||
{{ user.nickname }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="el-table__row">
|
||||
<td class="name-col">ID</td>
|
||||
<td class="value-col">
|
||||
|
@ -49,8 +50,8 @@
|
|||
<tr class="el-table__row">
|
||||
<td>{{ $t('userProfile.tags') }}</td>
|
||||
<td>
|
||||
<el-tag v-for="tag in user.tags" :key="tag" class="user-profile-tag">{{ humanizeTag(tag) }}</el-tag>
|
||||
<span v-if="user.tags.length === 0">—</span>
|
||||
<span v-if="user.tags.length === 0 || !propertyExists(user, 'tags')">—</span>
|
||||
<el-tag v-for="tag in user.tags" v-else :key="tag" class="user-profile-tag">{{ humanizeTag(tag) }}</el-tag>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="el-table__row">
|
||||
|
@ -62,7 +63,7 @@
|
|||
<el-tag v-if="user.roles.moderator" class="user-profile-tag">
|
||||
{{ $t('users.moderator') }}
|
||||
</el-tag>
|
||||
<span v-if="!user.roles.moderator && !user.roles.admin">—</span>
|
||||
<span v-if="!propertyExists(user, 'roles') || (!user.roles.moderator && !user.roles.admin)">—</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="el-table__row">
|
||||
|
@ -82,10 +83,11 @@
|
|||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<el-button icon="el-icon-lock" class="security-setting-button" @click="securitySettingsModalVisible = true">
|
||||
<el-button v-if="propertyExists(user, 'nickname')" icon="el-icon-lock" class="security-setting-button" @click="securitySettingsModalVisible = true">
|
||||
{{ $t('userProfile.securitySettings.securitySettings') }}
|
||||
</el-button>
|
||||
<SecuritySettingsModal
|
||||
v-if="propertyExists(user, 'nickname')"
|
||||
:user="user"
|
||||
:visible="securitySettingsModalVisible"
|
||||
@close="securitySettingsModalVisible = false" />
|
||||
|
@ -178,6 +180,9 @@ export default {
|
|||
},
|
||||
openResetPasswordDialog() {
|
||||
this.resetPasswordDialogOpen = true
|
||||
},
|
||||
propertyExists(account, property) {
|
||||
return account[property]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -203,6 +208,9 @@ table {
|
|||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.invalid {
|
||||
color: gray;
|
||||
}
|
||||
.el-table--border::after, .el-table--group::after, .el-table::before {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
@ -212,6 +220,14 @@ table {
|
|||
width: 100%;
|
||||
}
|
||||
}
|
||||
.invalid-user-tag {
|
||||
font-size: 14px;
|
||||
width: inherit;
|
||||
height: auto;
|
||||
text-align: center;
|
||||
word-wrap: break-word;
|
||||
white-space: normal;
|
||||
}
|
||||
.left-header-container {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
|
@ -270,7 +286,8 @@ table {
|
|||
.user-page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0 15px 0 20px;
|
||||
margin: 22px 15px 22px 20px;
|
||||
padding: 0;
|
||||
align-items: center;
|
||||
h1 {
|
||||
display: inline
|
||||
|
@ -286,6 +303,7 @@ table {
|
|||
}
|
||||
.user-profile-table {
|
||||
margin: 0;
|
||||
width: inherit;
|
||||
}
|
||||
.user-profile-tag {
|
||||
margin: 0 4px 4px 0;
|
||||
|
|
|
@ -73,7 +73,7 @@ describe('Statuses', () => {
|
|||
await flushPromises()
|
||||
|
||||
expect(wrapper.vm.selectedUsers.length).toEqual(1)
|
||||
expect(wrapper.vm.selectedUsers[0].display_name).toBe('sky')
|
||||
expect(wrapper.vm.selectedUsers[0].nickname).toBe('sky')
|
||||
done()
|
||||
})
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@ describe('Status show page', () => {
|
|||
|
||||
expect(wrapper.find('.status-container').isVisible()).toBe(true)
|
||||
expect(store.state.status.fetchedStatus.id).toBe('9vJOO3iFPyjNaEhJ5s')
|
||||
expect(store.state.status.fetchedStatus.account.display_name).toBe('dolin')
|
||||
expect(store.state.status.fetchedStatus.account.nickname).toBe('dolin')
|
||||
expect(store.state.userProfile.statuses.length).toEqual(3)
|
||||
done()
|
||||
})
|
||||
|
@ -81,7 +81,7 @@ describe('Status show page', () => {
|
|||
await flushPromises()
|
||||
|
||||
expect(wrapper.find('.status-card').exists()).toBe(true)
|
||||
expect(wrapper.find('router-link-stub h3').text()).toBe('dolin')
|
||||
expect(wrapper.find('router-link-stub span.status-account-name').text()).toBe('dolin')
|
||||
expect(wrapper.find('span.el-tag').text()).not.toBe('Sensitive')
|
||||
expect(wrapper.find('span.el-tag').text()).toBe('Public')
|
||||
expect(wrapper.find('button.status-actions-button').exists()).toBe(true)
|
||||
|
|
Loading…
Reference in a new issue