Implement domain mutes v2

This commit is contained in:
Shpuld Shpludson 2020-01-15 20:22:54 +00:00
parent e3de2964cc
commit 7a013ac393
7 changed files with 265 additions and 49 deletions

View file

@ -0,0 +1,15 @@
import ProgressButton from '../progress_button/progress_button.vue'
const DomainMuteCard = {
props: ['domain'],
components: {
ProgressButton
},
methods: {
unmuteDomain () {
return this.$store.dispatch('unmuteDomain', this.domain)
}
}
}
export default DomainMuteCard

View file

@ -0,0 +1,38 @@
<template>
<div class="domain-mute-card">
<div class="domain-mute-card-domain">
{{ domain }}
</div>
<ProgressButton
:click="unmuteDomain"
class="btn btn-default"
>
{{ $t('domain_mute_card.unmute') }}
<template slot="progress">
{{ $t('domain_mute_card.unmute_progress') }}
</template>
</ProgressButton>
</div>
</template>
<script src="./domain_mute_card.js"></script>
<style lang="scss">
.domain-mute-card {
flex: 1 0;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.6em 1em 0.6em 0;
&-domain {
margin-right: 1em;
overflow: hidden;
text-overflow: ellipsis;
}
button {
width: 10em;
}
}
</style>

View file

@ -9,6 +9,7 @@ import ScopeSelector from '../scope_selector/scope_selector.vue'
import fileSizeFormatService from '../../services/file_size_format/file_size_format.js' import fileSizeFormatService from '../../services/file_size_format/file_size_format.js'
import BlockCard from '../block_card/block_card.vue' import BlockCard from '../block_card/block_card.vue'
import MuteCard from '../mute_card/mute_card.vue' import MuteCard from '../mute_card/mute_card.vue'
import DomainMuteCard from '../domain_mute_card/domain_mute_card.vue'
import SelectableList from '../selectable_list/selectable_list.vue' import SelectableList from '../selectable_list/selectable_list.vue'
import ProgressButton from '../progress_button/progress_button.vue' import ProgressButton from '../progress_button/progress_button.vue'
import EmojiInput from '../emoji_input/emoji_input.vue' import EmojiInput from '../emoji_input/emoji_input.vue'
@ -32,6 +33,12 @@ const MuteList = withSubscription({
childPropName: 'items' childPropName: 'items'
})(SelectableList) })(SelectableList)
const DomainMuteList = withSubscription({
fetch: (props, $store) => $store.dispatch('fetchDomainMutes'),
select: (props, $store) => get($store.state.users.currentUser, 'domainMutes', []),
childPropName: 'items'
})(SelectableList)
const UserSettings = { const UserSettings = {
data () { data () {
return { return {
@ -67,7 +74,8 @@ const UserSettings = {
changedPassword: false, changedPassword: false,
changePasswordError: false, changePasswordError: false,
activeTab: 'profile', activeTab: 'profile',
notificationSettings: this.$store.state.users.currentUser.notification_settings notificationSettings: this.$store.state.users.currentUser.notification_settings,
newDomainToMute: ''
} }
}, },
created () { created () {
@ -80,10 +88,12 @@ const UserSettings = {
ImageCropper, ImageCropper,
BlockList, BlockList,
MuteList, MuteList,
DomainMuteList,
EmojiInput, EmojiInput,
Autosuggest, Autosuggest,
BlockCard, BlockCard,
MuteCard, MuteCard,
DomainMuteCard,
ProgressButton, ProgressButton,
Importer, Importer,
Exporter, Exporter,
@ -365,6 +375,13 @@ const UserSettings = {
unmuteUsers (ids) { unmuteUsers (ids) {
return this.$store.dispatch('unmuteUsers', ids) return this.$store.dispatch('unmuteUsers', ids)
}, },
unmuteDomains (domains) {
return this.$store.dispatch('unmuteDomains', domains)
},
muteDomain () {
return this.$store.dispatch('muteDomain', this.newDomainToMute)
.then(() => { this.newDomainToMute = '' })
},
identity (value) { identity (value) {
return value return value
} }

View file

@ -509,59 +509,114 @@
</div> </div>
<div :label="$t('settings.mutes_tab')"> <div :label="$t('settings.mutes_tab')">
<div class="profile-edit-usersearch-wrapper"> <tab-switcher>
<Autosuggest <div label="Users">
:filter="filterUnMutedUsers" <div class="profile-edit-usersearch-wrapper">
:query="queryUserIds" <Autosuggest
:placeholder="$t('settings.search_user_to_mute')" :filter="filterUnMutedUsers"
> :query="queryUserIds"
<MuteCard :placeholder="$t('settings.search_user_to_mute')"
slot-scope="row"
:user-id="row.item"
/>
</Autosuggest>
</div>
<MuteList
:refresh="true"
:get-key="identity"
>
<template
slot="header"
slot-scope="{selected}"
>
<div class="profile-edit-bulk-actions">
<ProgressButton
v-if="selected.length > 0"
class="btn btn-default"
:click="() => muteUsers(selected)"
> >
{{ $t('user_card.mute') }} <MuteCard
<template slot="progress"> slot-scope="row"
{{ $t('user_card.mute_progress') }} :user-id="row.item"
</template> />
</ProgressButton> </Autosuggest>
<ProgressButton </div>
v-if="selected.length > 0" <MuteList
class="btn btn-default" :refresh="true"
:click="() => unmuteUsers(selected)" :get-key="identity"
>
<template
slot="header"
slot-scope="{selected}"
> >
{{ $t('user_card.unmute') }} <div class="profile-edit-bulk-actions">
<ProgressButton
v-if="selected.length > 0"
class="btn btn-default"
:click="() => muteUsers(selected)"
>
{{ $t('user_card.mute') }}
<template slot="progress">
{{ $t('user_card.mute_progress') }}
</template>
</ProgressButton>
<ProgressButton
v-if="selected.length > 0"
class="btn btn-default"
:click="() => unmuteUsers(selected)"
>
{{ $t('user_card.unmute') }}
<template slot="progress">
{{ $t('user_card.unmute_progress') }}
</template>
</ProgressButton>
</div>
</template>
<template
slot="item"
slot-scope="{item}"
>
<MuteCard :user-id="item" />
</template>
<template slot="empty">
{{ $t('settings.no_mutes') }}
</template>
</MuteList>
</div>
<div :label="$t('settings.domain_mutes')">
<div class="profile-edit-domain-mute-form">
<input
v-model="newDomainToMute"
:placeholder="$t('settings.type_domains_to_mute')"
type="text"
@keyup.enter="muteDomain"
>
<ProgressButton
class="btn btn-default"
:click="muteDomain"
>
{{ $t('domain_mute_card.mute') }}
<template slot="progress"> <template slot="progress">
{{ $t('user_card.unmute_progress') }} {{ $t('domain_mute_card.mute_progress') }}
</template> </template>
</ProgressButton> </ProgressButton>
</div> </div>
</template> <DomainMuteList
<template :refresh="true"
slot="item" :get-key="identity"
slot-scope="{item}" >
> <template
<MuteCard :user-id="item" /> slot="header"
</template> slot-scope="{selected}"
<template slot="empty"> >
{{ $t('settings.no_mutes') }} <div class="profile-edit-bulk-actions">
</template> <ProgressButton
</MuteList> v-if="selected.length > 0"
class="btn btn-default"
:click="() => unmuteDomains(selected)"
>
{{ $t('domain_mute_card.unmute') }}
<template slot="progress">
{{ $t('domain_mute_card.unmute_progress') }}
</template>
</ProgressButton>
</div>
</template>
<template
slot="item"
slot-scope="{item}"
>
<DomainMuteCard :domain="item" />
</template>
<template slot="empty">
{{ $t('settings.no_mutes') }}
</template>
</DomainMuteList>
</div>
</tab-switcher>
</div> </div>
</tab-switcher> </tab-switcher>
</div> </div>
@ -639,6 +694,18 @@
} }
} }
&-domain-mute-form {
padding: 1em;
display: flex;
flex-direction: column;
button {
align-self: flex-end;
margin-top: 1em;
width: 10em;
}
}
.setting-subitem { .setting-subitem {
margin-left: 1.75em; margin-left: 1.75em;
} }

View file

@ -21,6 +21,12 @@
"chat": { "chat": {
"title": "Chat" "title": "Chat"
}, },
"domain_mute_card": {
"mute": "Mute",
"mute_progress": "Muting...",
"unmute": "Unmute",
"unmute_progress": "Unmuting..."
},
"exporter": { "exporter": {
"export": "Export", "export": "Export",
"processing": "Processing, you'll soon be asked to download your file" "processing": "Processing, you'll soon be asked to download your file"
@ -264,6 +270,7 @@
"delete_account_error": "There was an issue deleting your account. If this persists please contact your instance administrator.", "delete_account_error": "There was an issue deleting your account. If this persists please contact your instance administrator.",
"delete_account_instructions": "Type your password in the input below to confirm account deletion.", "delete_account_instructions": "Type your password in the input below to confirm account deletion.",
"discoverable": "Allow discovery of this account in search results and other services", "discoverable": "Allow discovery of this account in search results and other services",
"domain_mutes": "Domains",
"avatar_size_instruction": "The recommended minimum size for avatar images is 150x150 pixels.", "avatar_size_instruction": "The recommended minimum size for avatar images is 150x150 pixels.",
"pad_emoji": "Pad emoji with spaces when adding from picker", "pad_emoji": "Pad emoji with spaces when adding from picker",
"export_theme": "Save preset", "export_theme": "Save preset",
@ -361,6 +368,7 @@
"post_status_content_type": "Post status content type", "post_status_content_type": "Post status content type",
"stop_gifs": "Play-on-hover GIFs", "stop_gifs": "Play-on-hover GIFs",
"streaming": "Enable automatic streaming of new posts when scrolled to the top", "streaming": "Enable automatic streaming of new posts when scrolled to the top",
"user_mutes": "Users",
"useStreamingApi": "Receive posts and notifications real-time", "useStreamingApi": "Receive posts and notifications real-time",
"useStreamingApiWarning": "(Not recommended, experimental, known to skip posts)", "useStreamingApiWarning": "(Not recommended, experimental, known to skip posts)",
"text": "Text", "text": "Text",
@ -369,6 +377,7 @@
"theme_help_v2_1": "You can also override certain component's colors and opacity by toggling the checkbox, use \"Clear all\" button to clear all overrides.", "theme_help_v2_1": "You can also override certain component's colors and opacity by toggling the checkbox, use \"Clear all\" button to clear all overrides.",
"theme_help_v2_2": "Icons underneath some entries are background/text contrast indicators, hover over for detailed info. Please keep in mind that when using transparency contrast indicators show the worst possible case.", "theme_help_v2_2": "Icons underneath some entries are background/text contrast indicators, hover over for detailed info. Please keep in mind that when using transparency contrast indicators show the worst possible case.",
"tooltipRadius": "Tooltips/alerts", "tooltipRadius": "Tooltips/alerts",
"type_domains_to_mute": "Type in domains to mute",
"upload_a_photo": "Upload a photo", "upload_a_photo": "Upload a photo",
"user_settings": "User Settings", "user_settings": "User Settings",
"values": { "values": {

View file

@ -72,6 +72,16 @@ const showReblogs = (store, userId) => {
.then((relationship) => store.commit('updateUserRelationship', [relationship])) .then((relationship) => store.commit('updateUserRelationship', [relationship]))
} }
const muteDomain = (store, domain) => {
return store.rootState.api.backendInteractor.muteDomain({ domain })
.then(() => store.commit('addDomainMute', domain))
}
const unmuteDomain = (store, domain) => {
return store.rootState.api.backendInteractor.unmuteDomain({ domain })
.then(() => store.commit('removeDomainMute', domain))
}
export const mutations = { export const mutations = {
setMuted (state, { user: { id }, muted }) { setMuted (state, { user: { id }, muted }) {
const user = state.usersObject[id] const user = state.usersObject[id]
@ -177,6 +187,20 @@ export const mutations = {
state.currentUser.muteIds.push(muteId) state.currentUser.muteIds.push(muteId)
} }
}, },
saveDomainMutes (state, domainMutes) {
state.currentUser.domainMutes = domainMutes
},
addDomainMute (state, domain) {
if (state.currentUser.domainMutes.indexOf(domain) === -1) {
state.currentUser.domainMutes.push(domain)
}
},
removeDomainMute (state, domain) {
const index = state.currentUser.domainMutes.indexOf(domain)
if (index !== -1) {
state.currentUser.domainMutes.splice(index, 1)
}
},
setPinnedToUser (state, status) { setPinnedToUser (state, status) {
const user = state.usersObject[status.user.id] const user = state.usersObject[status.user.id]
const index = user.pinnedStatusIds.indexOf(status.id) const index = user.pinnedStatusIds.indexOf(status.id)
@ -297,6 +321,25 @@ const users = {
unmuteUsers (store, ids = []) { unmuteUsers (store, ids = []) {
return Promise.all(ids.map(id => unmuteUser(store, id))) return Promise.all(ids.map(id => unmuteUser(store, id)))
}, },
fetchDomainMutes (store) {
return store.rootState.api.backendInteractor.fetchDomainMutes()
.then((domainMutes) => {
store.commit('saveDomainMutes', domainMutes)
return domainMutes
})
},
muteDomain (store, domain) {
return muteDomain(store, domain)
},
unmuteDomain (store, domain) {
return unmuteDomain(store, domain)
},
muteDomains (store, domains = []) {
return Promise.all(domains.map(domain => muteDomain(store, domain)))
},
unmuteDomains (store, domain = []) {
return Promise.all(domain.map(domain => unmuteDomain(store, domain)))
},
fetchFriends ({ rootState, commit }, id) { fetchFriends ({ rootState, commit }, id) {
const user = rootState.users.usersObject[id] const user = rootState.users.usersObject[id]
const maxId = last(user.friendIds) const maxId = last(user.friendIds)
@ -460,6 +503,7 @@ const users = {
user.credentials = accessToken user.credentials = accessToken
user.blockIds = [] user.blockIds = []
user.muteIds = [] user.muteIds = []
user.domainMutes = []
commit('setCurrentUser', user) commit('setCurrentUser', user)
commit('addNewUsers', [user]) commit('addNewUsers', [user])

View file

@ -72,6 +72,7 @@ const MASTODON_MUTE_CONVERSATION = id => `/api/v1/statuses/${id}/mute`
const MASTODON_UNMUTE_CONVERSATION = id => `/api/v1/statuses/${id}/unmute` const MASTODON_UNMUTE_CONVERSATION = id => `/api/v1/statuses/${id}/unmute`
const MASTODON_SEARCH_2 = `/api/v2/search` const MASTODON_SEARCH_2 = `/api/v2/search`
const MASTODON_USER_SEARCH_URL = '/api/v1/accounts/search' const MASTODON_USER_SEARCH_URL = '/api/v1/accounts/search'
const MASTODON_DOMAIN_BLOCKS_URL = '/api/v1/domain_blocks'
const MASTODON_STREAMING = '/api/v1/streaming' const MASTODON_STREAMING = '/api/v1/streaming'
const oldfetch = window.fetch const oldfetch = window.fetch
@ -948,6 +949,28 @@ const search2 = ({ credentials, q, resolve, limit, offset, following }) => {
}) })
} }
const fetchDomainMutes = ({ credentials }) => {
return promisedRequest({ url: MASTODON_DOMAIN_BLOCKS_URL, credentials })
}
const muteDomain = ({ domain, credentials }) => {
return promisedRequest({
url: MASTODON_DOMAIN_BLOCKS_URL,
method: 'POST',
payload: { domain },
credentials
})
}
const unmuteDomain = ({ domain, credentials }) => {
return promisedRequest({
url: MASTODON_DOMAIN_BLOCKS_URL,
method: 'DELETE',
payload: { domain },
credentials
})
}
export const getMastodonSocketURI = ({ credentials, stream, args = {} }) => { export const getMastodonSocketURI = ({ credentials, stream, args = {} }) => {
return Object.entries({ return Object.entries({
...(credentials ...(credentials
@ -1110,7 +1133,10 @@ const apiService = {
reportUser, reportUser,
updateNotificationSettings, updateNotificationSettings,
search2, search2,
searchUsers searchUsers,
fetchDomainMutes,
muteDomain,
unmuteDomain
} }
export default apiService export default apiService