Merge branch '227-quick-add' into 'develop'

Support user searching to mute/block directly in the mutes/blocks tab

See merge request pleroma/pleroma-fe!727
This commit is contained in:
Shpuld Shpludson 2019-04-15 18:21:52 +00:00
commit efa93d0829
9 changed files with 166 additions and 3 deletions

View file

@ -27,6 +27,7 @@
"popper.js": "^1.14.7", "popper.js": "^1.14.7",
"sanitize-html": "^1.13.0", "sanitize-html": "^1.13.0",
"sass-loader": "^4.0.2", "sass-loader": "^4.0.2",
"v-click-outside": "^2.1.1",
"vue": "^2.5.13", "vue": "^2.5.13",
"vue-chat-scroll": "^1.2.1", "vue-chat-scroll": "^1.2.1",
"vue-compose": "^0.7.1", "vue-compose": "^0.7.1",

View file

@ -0,0 +1,52 @@
const debounceMilliseconds = 500
export default {
props: {
query: { // function to query results and return a promise
type: Function,
required: true
},
filter: { // function to filter results in real time
type: Function
},
placeholder: {
type: String,
default: 'Search...'
}
},
data () {
return {
term: '',
timeout: null,
results: [],
resultsVisible: false
}
},
computed: {
filtered () {
return this.filter ? this.filter(this.results) : this.results
}
},
watch: {
term (val) {
this.fetchResults(val)
}
},
methods: {
fetchResults (term) {
clearTimeout(this.timeout)
this.timeout = setTimeout(() => {
this.results = []
if (term) {
this.query(term).then((results) => { this.results = results })
}
}, debounceMilliseconds)
},
onInputClick () {
this.resultsVisible = true
},
onClickOutside () {
this.resultsVisible = false
}
}
}

View file

@ -0,0 +1,45 @@
<template>
<div class="autosuggest" v-click-outside="onClickOutside">
<input v-model="term" :placeholder="placeholder" @click="onInputClick" class="autosuggest-input" />
<div class="autosuggest-results" v-if="resultsVisible && filtered.length > 0">
<slot v-for="item in filtered" :item="item" />
</div>
</div>
</template>
<script src="./autosuggest.js"></script>
<style lang="scss">
@import '../../_variables.scss';
.autosuggest {
position: relative;
&-input {
display: block;
width: 100%;
}
&-results {
position: absolute;
left: 0;
top: 100%;
right: 0;
max-height: 400px;
background-color: $fallback--lightBg;
background-color: var(--lightBg, $fallback--lightBg);
border-style: solid;
border-width: 1px;
border-color: $fallback--border;
border-color: var(--border, $fallback--border);
border-radius: $fallback--inputRadius;
border-radius: var(--inputRadius, $fallback--inputRadius);
border-top-left-radius: 0;
border-top-right-radius: 0;
box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.6);
box-shadow: var(--panelShadow);
overflow-y: auto;
z-index: 1;
}
}
</style>

View file

@ -1,6 +1,8 @@
import { compose } from 'vue-compose' import { compose } from 'vue-compose'
import unescape from 'lodash/unescape' import unescape from 'lodash/unescape'
import get from 'lodash/get' import get from 'lodash/get'
import map from 'lodash/map'
import reject from 'lodash/reject'
import TabSwitcher from '../tab_switcher/tab_switcher.js' import TabSwitcher from '../tab_switcher/tab_switcher.js'
import ImageCropper from '../image_cropper/image_cropper.vue' import ImageCropper from '../image_cropper/image_cropper.vue'
import StyleSwitcher from '../style_switcher/style_switcher.vue' import StyleSwitcher from '../style_switcher/style_switcher.vue'
@ -9,8 +11,10 @@ import fileSizeFormatService from '../../services/file_size_format/file_size_for
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 EmojiInput from '../emoji-input/emoji-input.vue' import EmojiInput from '../emoji-input/emoji-input.vue'
import Autosuggest from '../autosuggest/autosuggest.vue'
import withSubscription from '../../hocs/with_subscription/with_subscription' import withSubscription from '../../hocs/with_subscription/with_subscription'
import withList from '../../hocs/with_list/with_list' import withList from '../../hocs/with_list/with_list'
import userSearchApi from '../../services/new_api/user_search.js'
const BlockList = compose( const BlockList = compose(
withSubscription({ withSubscription({
@ -73,7 +77,10 @@ const UserSettings = {
ImageCropper, ImageCropper,
BlockList, BlockList,
MuteList, MuteList,
EmojiInput EmojiInput,
Autosuggest,
BlockCard,
MuteCard
}, },
computed: { computed: {
user () { user () {
@ -334,6 +341,25 @@ const UserSettings = {
if (window.confirm(`${this.$i18n.t('settings.revoke_token')}?`)) { if (window.confirm(`${this.$i18n.t('settings.revoke_token')}?`)) {
this.$store.dispatch('revokeToken', id) this.$store.dispatch('revokeToken', id)
} }
},
filterUnblockedUsers (userIds) {
return reject(userIds, (userId) => {
const user = this.$store.getters.findUser(userId)
return !user || user.statusnet_blocking || user.id === this.$store.state.users.currentUser.id
})
},
filterUnMutedUsers (userIds) {
return reject(userIds, (userId) => {
const user = this.$store.getters.findUser(userId)
return !user || user.muted || user.id === this.$store.state.users.currentUser.id
})
},
queryUserIds (query) {
return userSearchApi.search({query, store: this.$store})
.then((users) => {
this.$store.dispatch('addNewUsers', users)
return map(users, 'id')
})
} }
} }
} }

View file

@ -22,7 +22,7 @@
<div class="setting-item" > <div class="setting-item" >
<h2>{{$t('settings.name_bio')}}</h2> <h2>{{$t('settings.name_bio')}}</h2>
<p>{{$t('settings.name')}}</p> <p>{{$t('settings.name')}}</p>
<EmojiInput <EmojiInput
type="text" type="text"
v-model="newName" v-model="newName"
id="username" id="username"
@ -195,12 +195,22 @@
</div> </div>
<div :label="$t('settings.blocks_tab')"> <div :label="$t('settings.blocks_tab')">
<div class="profile-edit-usersearch-wrapper">
<Autosuggest :filter="filterUnblockedUsers" :query="queryUserIds" :placeholder="$t('settings.search_user_to_block')">
<BlockCard slot-scope="row" :userId="row.item"/>
</Autosuggest>
</div>
<block-list :refresh="true"> <block-list :refresh="true">
<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')"> <div :label="$t('settings.mutes_tab')">
<div class="profile-edit-usersearch-wrapper">
<Autosuggest :filter="filterUnMutedUsers" :query="queryUserIds" :placeholder="$t('settings.search_user_to_mute')">
<MuteCard slot-scope="row" :userId="row.item"/>
</Autosuggest>
</div>
<mute-list :refresh="true"> <mute-list :refresh="true">
<template slot="empty">{{$t('settings.no_mutes')}}</template> <template slot="empty">{{$t('settings.no_mutes')}}</template>
</mute-list> </mute-list>
@ -262,5 +272,9 @@
text-align: right; text-align: right;
} }
} }
&-usersearch-wrapper {
padding: 1em;
}
} }
</style> </style>

View file

@ -217,6 +217,8 @@
"reply_visibility_self": "Only show replies directed at me", "reply_visibility_self": "Only show replies directed at me",
"saving_err": "Error saving settings", "saving_err": "Error saving settings",
"saving_ok": "Settings saved", "saving_ok": "Settings saved",
"search_user_to_block": "Search whom you want to block",
"search_user_to_mute": "Search whom you want to mute",
"security_tab": "Security", "security_tab": "Security",
"scope_copy": "Copy scope when replying (DMs are always copied)", "scope_copy": "Copy scope when replying (DMs are always copied)",
"minimal_scopes_mode": "Minimize post scope selection options", "minimal_scopes_mode": "Minimize post scope selection options",

View file

@ -22,6 +22,7 @@ import pushNotifications from './lib/push_notifications_plugin.js'
import messages from './i18n/messages.js' import messages from './i18n/messages.js'
import VueChatScroll from 'vue-chat-scroll' import VueChatScroll from 'vue-chat-scroll'
import VueClickOutside from 'v-click-outside'
import afterStoreSetup from './boot/after_store.js' import afterStoreSetup from './boot/after_store.js'
@ -39,6 +40,7 @@ Vue.use(VueTimeago, {
}) })
Vue.use(VueI18n) Vue.use(VueI18n)
Vue.use(VueChatScroll) Vue.use(VueChatScroll)
Vue.use(VueClickOutside)
const i18n = new VueI18n({ const i18n = new VueI18n({
// By default, use the browser locale, we will update it if neccessary // By default, use the browser locale, we will update it if neccessary

View file

@ -132,6 +132,11 @@ export const mutations = {
saveBlockIds (state, blockIds) { saveBlockIds (state, blockIds) {
state.currentUser.blockIds = blockIds state.currentUser.blockIds = blockIds
}, },
addBlockId (state, blockId) {
if (state.currentUser.blockIds.indexOf(blockId) === -1) {
state.currentUser.blockIds.push(blockId)
}
},
updateMutes (state, mutedUsers) { updateMutes (state, mutedUsers) {
// Reset muted of all fetched users // Reset muted of all fetched users
each(state.users, (user) => { user.muted = false }) each(state.users, (user) => { user.muted = false })
@ -140,6 +145,11 @@ export const mutations = {
saveMuteIds (state, muteIds) { saveMuteIds (state, muteIds) {
state.currentUser.muteIds = muteIds state.currentUser.muteIds = muteIds
}, },
addMuteId (state, muteId) {
if (state.currentUser.muteIds.indexOf(muteId) === -1) {
state.currentUser.muteIds.push(muteId)
}
},
setUserForStatus (state, status) { setUserForStatus (state, status) {
status.user = state.usersObject[status.user.id] status.user = state.usersObject[status.user.id]
}, },
@ -215,6 +225,7 @@ const users = {
return store.rootState.api.backendInteractor.blockUser(userId) return store.rootState.api.backendInteractor.blockUser(userId)
.then((relationship) => { .then((relationship) => {
store.commit('updateUserRelationship', [relationship]) store.commit('updateUserRelationship', [relationship])
store.commit('addBlockId', userId)
store.commit('removeStatus', { timeline: 'friends', userId }) store.commit('removeStatus', { timeline: 'friends', userId })
store.commit('removeStatus', { timeline: 'public', userId }) store.commit('removeStatus', { timeline: 'public', userId })
store.commit('removeStatus', { timeline: 'publicAndExternal', userId }) store.commit('removeStatus', { timeline: 'publicAndExternal', userId })
@ -234,7 +245,10 @@ const users = {
}, },
muteUser (store, id) { muteUser (store, id) {
return store.rootState.api.backendInteractor.muteUser(id) return store.rootState.api.backendInteractor.muteUser(id)
.then((relationship) => store.commit('updateUserRelationship', [relationship])) .then((relationship) => {
store.commit('updateUserRelationship', [relationship])
store.commit('addMuteId', id)
})
}, },
unmuteUser (store, id) { unmuteUser (store, id) {
return store.rootState.api.backendInteractor.unmuteUser(id) return store.rootState.api.backendInteractor.unmuteUser(id)
@ -281,6 +295,9 @@ const users = {
unregisterPushNotifications(token) unregisterPushNotifications(token)
}, },
addNewUsers ({ commit }, users) {
commit('addNewUsers', users)
},
addNewStatuses (store, { statuses }) { addNewStatuses (store, { statuses }) {
const users = map(statuses, 'user') const users = map(statuses, 'user')
const retweetedUsers = compact(map(statuses, 'retweeted_status.user')) const retweetedUsers = compact(map(statuses, 'retweeted_status.user'))

View file

@ -6389,6 +6389,10 @@ uuid@^3.3.2:
version "3.3.2" version "3.3.2"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"
v-click-outside@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/v-click-outside/-/v-click-outside-2.1.1.tgz#5af80b68a1c82eac89c597890434fa3994b42ed1"
validate-npm-package-license@^3.0.1: validate-npm-package-license@^3.0.1:
version "3.0.4" version "3.0.4"
resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a"