Merge branch '227-manage-blocks-mutes' into 'develop'

Transition to MastoAPI: Mute & Block and Add Mutes management tab

See merge request pleroma/pleroma-fe!611
This commit is contained in:
HJ 2019-03-24 17:15:21 +00:00
commit 01d0531699
14 changed files with 128 additions and 91 deletions

View file

@ -97,6 +97,7 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => {
copyInstanceOption('showInstanceSpecificPanel') copyInstanceOption('showInstanceSpecificPanel')
copyInstanceOption('scopeOptionsEnabled') copyInstanceOption('scopeOptionsEnabled')
copyInstanceOption('formattingOptionsEnabled') copyInstanceOption('formattingOptionsEnabled')
copyInstanceOption('hideMutedPosts')
copyInstanceOption('collapseMessageWithSubject') copyInstanceOption('collapseMessageWithSubject')
copyInstanceOption('loginMethod') copyInstanceOption('loginMethod')
copyInstanceOption('scopeCopy') copyInstanceOption('scopeCopy')

View file

@ -1,6 +1,6 @@
<template> <template>
<basic-user-card :user="user"> <basic-user-card :user="user">
<template slot="secondary-area"> <div class="mute-card-content-container">
<button class="btn btn-default" @click="unmuteUser" :disabled="progress" v-if="muted"> <button class="btn btn-default" @click="unmuteUser" :disabled="progress" v-if="muted">
<template v-if="progress"> <template v-if="progress">
{{ $t('user_card.unmute_progress') }} {{ $t('user_card.unmute_progress') }}
@ -17,8 +17,18 @@
{{ $t('user_card.mute') }} {{ $t('user_card.mute') }}
</template> </template>
</button> </button>
</template> </div>
</basic-user-card> </basic-user-card>
</template> </template>
<script src="./mute_card.js"></script> <script src="./mute_card.js"></script>
<style lang="scss">
.mute-card-content-container {
margin-top: 0.5em;
text-align: right;
button {
width: 10em;
}
}
</style>

View file

@ -47,6 +47,11 @@ const settings = {
pauseOnUnfocusedLocal: user.pauseOnUnfocused, pauseOnUnfocusedLocal: user.pauseOnUnfocused,
hoverPreviewLocal: user.hoverPreview, hoverPreviewLocal: user.hoverPreview,
hideMutedPostsLocal: typeof user.hideMutedPosts === 'undefined'
? instance.hideMutedPosts
: user.hideMutedPosts,
hideMutedPostsDefault: this.$t('settings.values.' + instance.hideMutedPosts),
collapseMessageWithSubjectLocal: typeof user.collapseMessageWithSubject === 'undefined' collapseMessageWithSubjectLocal: typeof user.collapseMessageWithSubject === 'undefined'
? instance.collapseMessageWithSubject ? instance.collapseMessageWithSubject
: user.collapseMessageWithSubject, : user.collapseMessageWithSubject,
@ -177,6 +182,9 @@ const settings = {
value = filter(value.split('\n'), (word) => trim(word).length > 0) value = filter(value.split('\n'), (word) => trim(word).length > 0)
this.$store.dispatch('setOption', { name: 'muteWords', value }) this.$store.dispatch('setOption', { name: 'muteWords', value })
}, },
hideMutedPostsLocal (value) {
this.$store.dispatch('setOption', { name: 'hideMutedPosts', value })
},
collapseMessageWithSubjectLocal (value) { collapseMessageWithSubjectLocal (value) {
this.$store.dispatch('setOption', { name: 'collapseMessageWithSubject', value }) this.$store.dispatch('setOption', { name: 'collapseMessageWithSubject', value })
}, },

View file

@ -36,6 +36,10 @@
<div class="setting-item"> <div class="setting-item">
<h2>{{$t('nav.timeline')}}</h2> <h2>{{$t('nav.timeline')}}</h2>
<ul class="setting-list"> <ul class="setting-list">
<li>
<input type="checkbox" id="hideMutedPosts" v-model="hideMutedPostsLocal">
<label for="hideMutedPosts">{{$t('settings.hide_muted_posts')}} {{$t('settings.instance_default', { value: hideMutedPostsDefault })}}</label>
</li>
<li> <li>
<input type="checkbox" id="collapseMessageWithSubject" v-model="collapseMessageWithSubjectLocal"> <input type="checkbox" id="collapseMessageWithSubject" v-model="collapseMessageWithSubjectLocal">
<label for="collapseMessageWithSubject"> <label for="collapseMessageWithSubject">

View file

@ -121,24 +121,16 @@ export default {
}) })
}, },
blockUser () { blockUser () {
const store = this.$store this.$store.dispatch('blockUser', this.user.id)
store.state.api.backendInteractor.blockUser(this.user.id)
.then((blockedUser) => {
store.commit('addNewUsers', [blockedUser])
store.commit('removeStatus', { timeline: 'friends', userId: this.user.id })
store.commit('removeStatus', { timeline: 'public', userId: this.user.id })
store.commit('removeStatus', { timeline: 'publicAndExternal', userId: this.user.id })
})
}, },
unblockUser () { unblockUser () {
const store = this.$store this.$store.dispatch('unblockUser', this.user.id)
store.state.api.backendInteractor.unblockUser(this.user.id)
.then((unblockedUser) => store.commit('addNewUsers', [unblockedUser]))
}, },
toggleMute () { muteUser () {
const store = this.$store this.$store.dispatch('muteUser', this.user.id)
store.commit('setMuted', {user: this.user, muted: !this.user.muted}) },
store.state.api.backendInteractor.setUserMute(this.user) unmuteUser () {
this.$store.dispatch('unmuteUser', this.user.id)
}, },
setProfileView (v) { setProfileView (v) {
if (this.switcher) { if (this.switcher) {

View file

@ -74,12 +74,12 @@
</div> </div>
<div class='mute' v-if='isOtherUser && loggedIn'> <div class='mute' v-if='isOtherUser && loggedIn'>
<span v-if='user.muted'> <span v-if='user.muted'>
<button @click="toggleMute" class="pressed"> <button @click="unmuteUser" class="pressed">
{{ $t('user_card.muted') }} {{ $t('user_card.muted') }}
</button> </button>
</span> </span>
<span v-if='!user.muted'> <span v-if='!user.muted'>
<button @click="toggleMute"> <button @click="muteUser">
{{ $t('user_card.mute') }} {{ $t('user_card.mute') }}
</button> </button>
</span> </span>

View file

@ -192,6 +192,12 @@
<template slot="empty">{{$t('settings.no_blocks')}}</template> <template slot="empty">{{$t('settings.no_blocks')}}</template>
</block-list> </block-list>
</div> </div>
<div :label="$t('settings.mutes_tab')">
<mute-list :refresh="true">
<template slot="empty">{{$t('settings.no_mutes')}}</template>
</mute-list>
</div>
</tab-switcher> </tab-switcher>
</div> </div>
</div> </div>

View file

@ -153,6 +153,7 @@
"general": "General", "general": "General",
"hide_attachments_in_convo": "Hide attachments in conversations", "hide_attachments_in_convo": "Hide attachments in conversations",
"hide_attachments_in_tl": "Hide attachments in timeline", "hide_attachments_in_tl": "Hide attachments in timeline",
"hide_muted_posts": "Hide posts of muted users",
"max_thumbnails": "Maximum amount of thumbnails per post", "max_thumbnails": "Maximum amount of thumbnails per post",
"hide_isp": "Hide instance-specific panel", "hide_isp": "Hide instance-specific panel",
"preload_images": "Preload images", "preload_images": "Preload images",

View file

@ -5,6 +5,7 @@ const browserLocale = (window.navigator.language || 'en').split('-')[0]
const defaultState = { const defaultState = {
colors: {}, colors: {},
hideMutedPosts: undefined, // instance default
collapseMessageWithSubject: undefined, // instance default collapseMessageWithSubject: undefined, // instance default
hideAttachments: false, hideAttachments: false,
hideAttachmentsInConv: false, hideAttachmentsInConv: false,

View file

@ -18,6 +18,7 @@ const defaultState = {
scopeOptionsEnabled: true, scopeOptionsEnabled: true,
formattingOptionsEnabled: false, formattingOptionsEnabled: false,
alwaysShowSubjectInput: true, alwaysShowSubjectInput: true,
hideMutedPosts: false,
collapseMessageWithSubject: false, collapseMessageWithSubject: false,
hidePostStats: false, hidePostStats: false,
hideUserStats: false, hideUserStats: false,

View file

@ -102,10 +102,20 @@ export const mutations = {
} }
}) })
}, },
saveBlocks (state, blockIds) { updateBlocks (state, blockedUsers) {
// Reset statusnet_blocking of all fetched users
each(state.users, (user) => { user.statusnet_blocking = false })
each(blockedUsers, (user) => mergeOrAdd(state.users, state.usersObject, user))
},
saveBlockIds (state, blockIds) {
state.currentUser.blockIds = blockIds state.currentUser.blockIds = blockIds
}, },
saveMutes (state, muteIds) { updateMutes (state, mutedUsers) {
// Reset muted of all fetched users
each(state.users, (user) => { user.muted = false })
each(mutedUsers, (user) => mergeOrAdd(state.users, state.usersObject, user))
},
saveMuteIds (state, muteIds) {
state.currentUser.muteIds = muteIds state.currentUser.muteIds = muteIds
}, },
setUserForStatus (state, status) { setUserForStatus (state, status) {
@ -172,34 +182,39 @@ const users = {
fetchBlocks (store) { fetchBlocks (store) {
return store.rootState.api.backendInteractor.fetchBlocks() return store.rootState.api.backendInteractor.fetchBlocks()
.then((blocks) => { .then((blocks) => {
store.commit('saveBlocks', map(blocks, 'id')) store.commit('saveBlockIds', map(blocks, 'id'))
store.commit('addNewUsers', blocks) store.commit('updateBlocks', blocks)
return blocks return blocks
}) })
}, },
blockUser (store, id) { blockUser (store, userId) {
return store.rootState.api.backendInteractor.blockUser(id) return store.rootState.api.backendInteractor.blockUser(userId)
.then((user) => store.commit('addNewUsers', [user])) .then((relationship) => {
store.commit('updateUserRelationship', [relationship])
store.commit('removeStatus', { timeline: 'friends', userId })
store.commit('removeStatus', { timeline: 'public', userId })
store.commit('removeStatus', { timeline: 'publicAndExternal', userId })
})
}, },
unblockUser (store, id) { unblockUser (store, id) {
return store.rootState.api.backendInteractor.unblockUser(id) return store.rootState.api.backendInteractor.unblockUser(id)
.then((user) => store.commit('addNewUsers', [user])) .then((relationship) => store.commit('updateUserRelationship', [relationship]))
}, },
fetchMutes (store) { fetchMutes (store) {
return store.rootState.api.backendInteractor.fetchMutes() return store.rootState.api.backendInteractor.fetchMutes()
.then((mutedUsers) => { .then((mutes) => {
each(mutedUsers, (user) => { user.muted = true }) store.commit('updateMutes', mutes)
store.commit('addNewUsers', mutedUsers) store.commit('saveMuteIds', map(mutes, 'id'))
store.commit('saveMutes', map(mutedUsers, 'id')) return mutes
}) })
}, },
muteUser (store, id) { muteUser (store, id) {
return store.state.api.backendInteractor.setUserMute({ id, muted: true }) return store.rootState.api.backendInteractor.muteUser(id)
.then((user) => store.commit('addNewUsers', [user])) .then((relationship) => store.commit('updateUserRelationship', [relationship]))
}, },
unmuteUser (store, id) { unmuteUser (store, id) {
return store.state.api.backendInteractor.setUserMute({ id, muted: false }) return store.rootState.api.backendInteractor.unmuteUser(id)
.then((user) => store.commit('addNewUsers', [user])) .then((relationship) => store.commit('updateUserRelationship', [relationship]))
}, },
addFriends ({ rootState, commit }, fetchBy) { addFriends ({ rootState, commit }, fetchBy) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {

View file

@ -18,10 +18,8 @@ const MENTIONS_URL = '/api/statuses/mentions.json'
const DM_TIMELINE_URL = '/api/statuses/dm_timeline.json' const DM_TIMELINE_URL = '/api/statuses/dm_timeline.json'
const FOLLOWERS_URL = '/api/statuses/followers.json' const FOLLOWERS_URL = '/api/statuses/followers.json'
const FRIENDS_URL = '/api/statuses/friends.json' const FRIENDS_URL = '/api/statuses/friends.json'
const BLOCKS_URL = '/api/statuses/blocks.json'
const FOLLOWING_URL = '/api/friendships/create.json' const FOLLOWING_URL = '/api/friendships/create.json'
const UNFOLLOWING_URL = '/api/friendships/destroy.json' const UNFOLLOWING_URL = '/api/friendships/destroy.json'
const QVITTER_USER_PREF_URL = '/api/qvitter/set_profile_pref.json'
const REGISTRATION_URL = '/api/account/register.json' const REGISTRATION_URL = '/api/account/register.json'
const AVATAR_UPDATE_URL = '/api/qvitter/update_avatar.json' const AVATAR_UPDATE_URL = '/api/qvitter/update_avatar.json'
const BG_UPDATE_URL = '/api/qvitter/update_background_image.json' const BG_UPDATE_URL = '/api/qvitter/update_background_image.json'
@ -30,8 +28,6 @@ const PROFILE_UPDATE_URL = '/api/account/update_profile.json'
const EXTERNAL_PROFILE_URL = '/api/externalprofile/show.json' const EXTERNAL_PROFILE_URL = '/api/externalprofile/show.json'
const QVITTER_USER_NOTIFICATIONS_URL = '/api/qvitter/statuses/notifications.json' const QVITTER_USER_NOTIFICATIONS_URL = '/api/qvitter/statuses/notifications.json'
const QVITTER_USER_NOTIFICATIONS_READ_URL = '/api/qvitter/statuses/notifications/read.json' const QVITTER_USER_NOTIFICATIONS_READ_URL = '/api/qvitter/statuses/notifications/read.json'
const BLOCKING_URL = '/api/blocks/create.json'
const UNBLOCKING_URL = '/api/blocks/destroy.json'
const FOLLOW_IMPORT_URL = '/api/pleroma/follow_import' const FOLLOW_IMPORT_URL = '/api/pleroma/follow_import'
const DELETE_ACCOUNT_URL = '/api/pleroma/delete_account' const DELETE_ACCOUNT_URL = '/api/pleroma/delete_account'
const CHANGE_PASSWORD_URL = '/api/pleroma/change_password' const CHANGE_PASSWORD_URL = '/api/pleroma/change_password'
@ -44,6 +40,12 @@ const MASTODON_USER_FAVORITES_TIMELINE_URL = '/api/v1/favourites'
const MASTODON_USER_URL = '/api/v1/accounts' const MASTODON_USER_URL = '/api/v1/accounts'
const MASTODON_USER_RELATIONSHIPS_URL = '/api/v1/accounts/relationships' const MASTODON_USER_RELATIONSHIPS_URL = '/api/v1/accounts/relationships'
const MASTODON_USER_TIMELINE_URL = id => `/api/v1/accounts/${id}/statuses` const MASTODON_USER_TIMELINE_URL = id => `/api/v1/accounts/${id}/statuses`
const MASTODON_USER_BLOCKS_URL = '/api/v1/blocks/'
const MASTODON_USER_MUTES_URL = '/api/v1/mutes/'
const MASTODON_BLOCK_USER_URL = id => `/api/v1/accounts/${id}/block`
const MASTODON_UNBLOCK_USER_URL = id => `/api/v1/accounts/${id}/unblock`
const MASTODON_MUTE_USER_URL = id => `/api/v1/accounts/${id}/mute`
const MASTODON_UNMUTE_USER_URL = id => `/api/v1/accounts/${id}/unmute`
import { each, map } from 'lodash' import { each, map } from 'lodash'
import { parseStatus, parseUser, parseNotification } from '../entity_normalizer/entity_normalizer.service.js' import { parseStatus, parseUser, parseNotification } from '../entity_normalizer/entity_normalizer.service.js'
@ -60,6 +62,19 @@ let fetch = (url, options) => {
return oldfetch(fullUrl, options) return oldfetch(fullUrl, options)
} }
const promisedRequest = (url, options) => {
return fetch(url, options)
.then((response) => {
return new Promise((resolve, reject) => response.json()
.then((json) => {
if (!response.ok) {
return reject(new StatusCodeError(response.status, json, { url, options }, response))
}
return resolve(json)
}))
})
}
// Params // Params
// cropH // cropH
// cropW // cropW
@ -212,16 +227,14 @@ const unfollowUser = ({id, credentials}) => {
} }
const blockUser = ({id, credentials}) => { const blockUser = ({id, credentials}) => {
let url = `${BLOCKING_URL}?user_id=${id}` return fetch(MASTODON_BLOCK_USER_URL(id), {
return fetch(url, {
headers: authHeaders(credentials), headers: authHeaders(credentials),
method: 'POST' method: 'POST'
}).then((data) => data.json()) }).then((data) => data.json())
} }
const unblockUser = ({id, credentials}) => { const unblockUser = ({id, credentials}) => {
let url = `${UNBLOCKING_URL}?user_id=${id}` return fetch(MASTODON_UNBLOCK_USER_URL(id), {
return fetch(url, {
headers: authHeaders(credentials), headers: authHeaders(credentials),
method: 'POST' method: 'POST'
}).then((data) => data.json()) }).then((data) => data.json())
@ -245,16 +258,7 @@ const denyUser = ({id, credentials}) => {
const fetchUser = ({id, credentials}) => { const fetchUser = ({id, credentials}) => {
let url = `${MASTODON_USER_URL}/${id}` let url = `${MASTODON_USER_URL}/${id}`
return fetch(url, { headers: authHeaders(credentials) }) return promisedRequest(url, { headers: authHeaders(credentials) })
.then((response) => {
return new Promise((resolve, reject) => response.json()
.then((json) => {
if (!response.ok) {
return reject(new StatusCodeError(response.status, json, { url }, response))
}
return resolve(json)
}))
})
.then((data) => parseUser(data)) .then((data) => parseUser(data))
} }
@ -338,23 +342,7 @@ const fetchStatus = ({id, credentials}) => {
.then((data) => parseStatus(data)) .then((data) => parseStatus(data))
} }
const setUserMute = ({id, credentials, muted = true}) => { const fetchTimeline = ({timeline, credentials, since = false, until = false, userId = false, tag = false, withMuted = false}) => {
const form = new FormData()
const muteInteger = muted ? 1 : 0
form.append('namespace', 'qvitter')
form.append('data', muteInteger)
form.append('topic', `mute:${id}`)
return fetch(QVITTER_USER_PREF_URL, {
method: 'POST',
headers: authHeaders(credentials),
body: form
})
}
const fetchTimeline = ({timeline, credentials, since = false, until = false, userId = false, tag = false}) => {
const timelineUrls = { const timelineUrls = {
public: PUBLIC_TIMELINE_URL, public: PUBLIC_TIMELINE_URL,
friends: FRIENDS_TIMELINE_URL, friends: FRIENDS_TIMELINE_URL,
@ -390,6 +378,7 @@ const fetchTimeline = ({timeline, credentials, since = false, until = false, use
} }
params.push(['count', 20]) params.push(['count', 20])
params.push(['with_muted', withMuted])
const queryString = map(params, (param) => `${param[0]}=${param[1]}`).join('&') const queryString = map(params, (param) => `${param[0]}=${param[1]}`).join('&')
url += `?${queryString}` url += `?${queryString}`
@ -538,24 +527,29 @@ const changePassword = ({credentials, password, newPassword, newPasswordConfirma
} }
const fetchMutes = ({credentials}) => { const fetchMutes = ({credentials}) => {
const url = '/api/qvitter/mutes.json' return promisedRequest(MASTODON_USER_MUTES_URL, { headers: authHeaders(credentials) })
.then((users) => users.map(parseUser))
return fetch(url, {
headers: authHeaders(credentials)
}).then((data) => data.json())
} }
const fetchBlocks = ({page, credentials}) => { const muteUser = ({id, credentials}) => {
return fetch(BLOCKS_URL, { return promisedRequest(MASTODON_MUTE_USER_URL(id), {
headers: authHeaders(credentials) headers: authHeaders(credentials),
}).then((data) => { method: 'POST'
if (data.ok) {
return data.json()
}
throw new Error('Error fetching blocks', data)
}) })
} }
const unmuteUser = ({id, credentials}) => {
return promisedRequest(MASTODON_UNMUTE_USER_URL(id), {
headers: authHeaders(credentials),
method: 'POST'
})
}
const fetchBlocks = ({credentials}) => {
return promisedRequest(MASTODON_USER_BLOCKS_URL, { headers: authHeaders(credentials) })
.then((users) => users.map(parseUser))
}
const fetchOAuthTokens = ({credentials}) => { const fetchOAuthTokens = ({credentials}) => {
const url = '/api/oauth_tokens.json' const url = '/api/oauth_tokens.json'
@ -618,8 +612,9 @@ const apiService = {
deleteStatus, deleteStatus,
uploadMedia, uploadMedia,
fetchAllFollowing, fetchAllFollowing,
setUserMute,
fetchMutes, fetchMutes,
muteUser,
unmuteUser,
fetchBlocks, fetchBlocks,
fetchOAuthTokens, fetchOAuthTokens,
revokeOAuthToken, revokeOAuthToken,

View file

@ -62,12 +62,10 @@ const backendInteractorService = (credentials) => {
return timelineFetcherService.startFetching({timeline, store, credentials, userId, tag}) return timelineFetcherService.startFetching({timeline, store, credentials, userId, tag})
} }
const setUserMute = ({id, muted = true}) => {
return apiService.setUserMute({id, muted, credentials})
}
const fetchMutes = () => apiService.fetchMutes({credentials}) const fetchMutes = () => apiService.fetchMutes({credentials})
const fetchBlocks = (params) => apiService.fetchBlocks({credentials, ...params}) const muteUser = (id) => apiService.muteUser({credentials, id})
const unmuteUser = (id) => apiService.unmuteUser({credentials, id})
const fetchBlocks = () => apiService.fetchBlocks({credentials})
const fetchFollowRequests = () => apiService.fetchFollowRequests({credentials}) const fetchFollowRequests = () => apiService.fetchFollowRequests({credentials})
const fetchOAuthTokens = () => apiService.fetchOAuthTokens({credentials}) const fetchOAuthTokens = () => apiService.fetchOAuthTokens({credentials})
const revokeOAuthToken = (id) => apiService.revokeOAuthToken({id, credentials}) const revokeOAuthToken = (id) => apiService.revokeOAuthToken({id, credentials})
@ -100,8 +98,9 @@ const backendInteractorService = (credentials) => {
fetchAllFollowing, fetchAllFollowing,
verifyCredentials: apiService.verifyCredentials, verifyCredentials: apiService.verifyCredentials,
startFetching, startFetching,
setUserMute,
fetchMutes, fetchMutes,
muteUser,
unmuteUser,
fetchBlocks, fetchBlocks,
fetchOAuthTokens, fetchOAuthTokens,
revokeOAuthToken, revokeOAuthToken,

View file

@ -19,6 +19,9 @@ const fetchAndUpdate = ({store, credentials, timeline = 'friends', older = false
const args = { timeline, credentials } const args = { timeline, credentials }
const rootState = store.rootState || store.state const rootState = store.rootState || store.state
const timelineData = rootState.statuses.timelines[camelCase(timeline)] const timelineData = rootState.statuses.timelines[camelCase(timeline)]
const hideMutedPosts = typeof rootState.config.hideMutedPosts === 'undefined'
? rootState.instance.hideMutedPosts
: rootState.config.hideMutedPosts
if (older) { if (older) {
args['until'] = until || timelineData.minId args['until'] = until || timelineData.minId
@ -28,6 +31,7 @@ const fetchAndUpdate = ({store, credentials, timeline = 'friends', older = false
args['userId'] = userId args['userId'] = userId
args['tag'] = tag args['tag'] = tag
args['withMuted'] = !hideMutedPosts
const numStatusesBeforeFetch = timelineData.statuses.length const numStatusesBeforeFetch = timelineData.statuses.length