Merge branch '227-bulk-delete' into 'develop'
Add "bulk mute/unmute/block/unblock" feature See merge request pleroma/pleroma-fe!733
This commit is contained in:
commit
ed0f10e9ee
20 changed files with 433 additions and 136 deletions
|
@ -30,7 +30,6 @@
|
||||||
"v-click-outside": "^2.1.1",
|
"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-i18n": "^7.3.2",
|
"vue-i18n": "^7.3.2",
|
||||||
"vue-popperjs": "^2.0.3",
|
"vue-popperjs": "^2.0.3",
|
||||||
"vue-router": "^3.0.1",
|
"vue-router": "^3.0.1",
|
||||||
|
|
|
@ -24,19 +24,11 @@
|
||||||
<script src="./basic_user_card.js"></script>
|
<script src="./basic_user_card.js"></script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../../_variables.scss';
|
|
||||||
|
|
||||||
.basic-user-card {
|
.basic-user-card {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1 0;
|
flex: 1 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding-top: 0.6em;
|
padding: 0.6em 1em;
|
||||||
padding-right: 1em;
|
|
||||||
padding-bottom: 0.6em;
|
|
||||||
padding-left: 1em;
|
|
||||||
border-bottom: 1px solid;
|
|
||||||
border-bottom-color: $fallback--border;
|
|
||||||
border-bottom-color: var(--border, $fallback--border);
|
|
||||||
|
|
||||||
&-collapsed-content {
|
&-collapsed-content {
|
||||||
margin-left: 0.7em;
|
margin-left: 0.7em;
|
||||||
|
|
75
src/components/checkbox/checkbox.vue
Normal file
75
src/components/checkbox/checkbox.vue
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
<template>
|
||||||
|
<label class="checkbox">
|
||||||
|
<input type="checkbox" :checked="checked" @change="$emit('change', $event.target.checked)" :indeterminate.prop="indeterminate">
|
||||||
|
<i class="checkbox-indicator" />
|
||||||
|
<span v-if="!!$slots.default"><slot></slot></span>
|
||||||
|
</label>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
model: {
|
||||||
|
prop: 'checked',
|
||||||
|
event: 'change'
|
||||||
|
},
|
||||||
|
props: ['checked', 'indeterminate']
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@import '../../_variables.scss';
|
||||||
|
|
||||||
|
.checkbox {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
padding-left: 1.2em;
|
||||||
|
min-height: 1.2em;
|
||||||
|
|
||||||
|
&-indicator::before {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
display: block;
|
||||||
|
content: '✔';
|
||||||
|
transition: color 200ms;
|
||||||
|
width: 1.1em;
|
||||||
|
height: 1.1em;
|
||||||
|
border-radius: $fallback--checkboxRadius;
|
||||||
|
border-radius: var(--checkboxRadius, $fallback--checkboxRadius);
|
||||||
|
box-shadow: 0px 0px 2px black inset;
|
||||||
|
box-shadow: var(--inputShadow);
|
||||||
|
background-color: $fallback--fg;
|
||||||
|
background-color: var(--input, $fallback--fg);
|
||||||
|
vertical-align: top;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1.1em;
|
||||||
|
font-size: 1.1em;
|
||||||
|
color: transparent;
|
||||||
|
overflow: hidden;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=checkbox] {
|
||||||
|
display: none;
|
||||||
|
|
||||||
|
&:checked + .checkbox-indicator::before {
|
||||||
|
color: $fallback--text;
|
||||||
|
color: var(--text, $fallback--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:indeterminate + .checkbox-indicator::before {
|
||||||
|
content: '–';
|
||||||
|
color: $fallback--text;
|
||||||
|
color: var(--text, $fallback--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled + .checkbox-indicator::before {
|
||||||
|
opacity: .5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& > span {
|
||||||
|
margin-left: .5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -4,7 +4,7 @@
|
||||||
{{$t('nav.friend_requests')}}
|
{{$t('nav.friend_requests')}}
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<FollowRequestCard v-for="request in requests" :key="request.id" :user="request"/>
|
<FollowRequestCard v-for="request in requests" :key="request.id" :user="request" class="list-item"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
42
src/components/list/list.vue
Normal file
42
src/components/list/list.vue
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
<template>
|
||||||
|
<div class="list">
|
||||||
|
<div v-for="item in items" class="list-item" :key="getKey(item)">
|
||||||
|
<slot name="item" :item="item" />
|
||||||
|
</div>
|
||||||
|
<div class="list-empty-content faint" v-if="items.length === 0 && !!$slots.empty">
|
||||||
|
<slot name="empty" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
items: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
getKey: {
|
||||||
|
type: Function,
|
||||||
|
default: item => item.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@import '../../_variables.scss';
|
||||||
|
|
||||||
|
.list {
|
||||||
|
&-item:not(:last-child) {
|
||||||
|
border-bottom: 1px solid;
|
||||||
|
border-bottom-color: $fallback--border;
|
||||||
|
border-bottom-color: var(--border, $fallback--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
&-empty-content {
|
||||||
|
text-align: center;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
35
src/components/progress_button/progress_button.vue
Normal file
35
src/components/progress_button/progress_button.vue
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
<template>
|
||||||
|
<button :disabled="progress || disabled" @click="onClick">
|
||||||
|
<template v-if="progress">
|
||||||
|
<slot name="progress" />
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<slot />
|
||||||
|
</template>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
disabled: {
|
||||||
|
type: Boolean
|
||||||
|
},
|
||||||
|
click: { // click event handler. Must return a promise
|
||||||
|
type: Function,
|
||||||
|
default: () => Promise.resolve()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
progress: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onClick () {
|
||||||
|
this.progress = true
|
||||||
|
this.click().then(() => { this.progress = false })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
66
src/components/selectable_list/selectable_list.js
Normal file
66
src/components/selectable_list/selectable_list.js
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
import List from '../list/list.vue'
|
||||||
|
import Checkbox from '../checkbox/checkbox.vue'
|
||||||
|
|
||||||
|
const SelectableList = {
|
||||||
|
components: {
|
||||||
|
List,
|
||||||
|
Checkbox
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
items: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
getKey: {
|
||||||
|
type: Function,
|
||||||
|
default: item => item.id
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
selected: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
allKeys () {
|
||||||
|
return this.items.map(this.getKey)
|
||||||
|
},
|
||||||
|
filteredSelected () {
|
||||||
|
return this.allKeys.filter(key => this.selected.indexOf(key) !== -1)
|
||||||
|
},
|
||||||
|
allSelected () {
|
||||||
|
return this.filteredSelected.length === this.items.length
|
||||||
|
},
|
||||||
|
noneSelected () {
|
||||||
|
return this.filteredSelected.length === 0
|
||||||
|
},
|
||||||
|
someSelected () {
|
||||||
|
return !this.allSelected && !this.noneSelected
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
isSelected (item) {
|
||||||
|
return this.filteredSelected.indexOf(this.getKey(item)) !== -1
|
||||||
|
},
|
||||||
|
toggle (checked, item) {
|
||||||
|
const key = this.getKey(item)
|
||||||
|
const oldChecked = this.isSelected(key)
|
||||||
|
if (checked !== oldChecked) {
|
||||||
|
if (checked) {
|
||||||
|
this.selected.push(key)
|
||||||
|
} else {
|
||||||
|
this.selected.splice(this.selected.indexOf(key), 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
toggleAll (value) {
|
||||||
|
if (value) {
|
||||||
|
this.selected = this.allKeys.slice(0)
|
||||||
|
} else {
|
||||||
|
this.selected = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SelectableList
|
59
src/components/selectable_list/selectable_list.vue
Normal file
59
src/components/selectable_list/selectable_list.vue
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
<template>
|
||||||
|
<div class="selectable-list">
|
||||||
|
<div class="selectable-list-header" v-if="items.length > 0">
|
||||||
|
<div class="selectable-list-checkbox-wrapper">
|
||||||
|
<Checkbox :checked="allSelected" @change="toggleAll" :indeterminate="someSelected">{{ $t('selectable_list.select_all') }}</Checkbox>
|
||||||
|
</div>
|
||||||
|
<div class="selectable-list-header-actions">
|
||||||
|
<slot name="header" :selected="filteredSelected" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<List :items="items" :getKey="getKey">
|
||||||
|
<template slot="item" slot-scope="{item}">
|
||||||
|
<div class="selectable-list-item-inner" :class="{ 'selectable-list-item-selected-inner': isSelected(item) }">
|
||||||
|
<div class="selectable-list-checkbox-wrapper">
|
||||||
|
<Checkbox :checked="isSelected(item)" @change="checked => toggle(checked, item)" />
|
||||||
|
</div>
|
||||||
|
<slot name="item" :item="item" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template slot="empty"><slot name="empty" /></template>
|
||||||
|
</List>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./selectable_list.js"></script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@import '../../_variables.scss';
|
||||||
|
|
||||||
|
.selectable-list {
|
||||||
|
&-item-inner {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-item-selected-inner {
|
||||||
|
background-color: $fallback--lightBg;
|
||||||
|
background-color: var(--lightBg, $fallback--lightBg);
|
||||||
|
}
|
||||||
|
|
||||||
|
&-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.6em 0;
|
||||||
|
border-bottom: 2px solid;
|
||||||
|
border-bottom-color: $fallback--border;
|
||||||
|
border-bottom-color: var(--border, $fallback--border);
|
||||||
|
|
||||||
|
&-actions {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-checkbox-wrapper {
|
||||||
|
padding: 0 10px;
|
||||||
|
flex: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,33 +1,26 @@
|
||||||
import { compose } from 'vue-compose'
|
|
||||||
import get from 'lodash/get'
|
import get from 'lodash/get'
|
||||||
import UserCard from '../user_card/user_card.vue'
|
import UserCard from '../user_card/user_card.vue'
|
||||||
import FollowCard from '../follow_card/follow_card.vue'
|
import FollowCard from '../follow_card/follow_card.vue'
|
||||||
import Timeline from '../timeline/timeline.vue'
|
import Timeline from '../timeline/timeline.vue'
|
||||||
import ModerationTools from '../moderation_tools/moderation_tools.vue'
|
import ModerationTools from '../moderation_tools/moderation_tools.vue'
|
||||||
|
import List from '../list/list.vue'
|
||||||
import withLoadMore from '../../hocs/with_load_more/with_load_more'
|
import withLoadMore from '../../hocs/with_load_more/with_load_more'
|
||||||
import withList from '../../hocs/with_list/with_list'
|
|
||||||
|
|
||||||
const FollowerList = compose(
|
const FollowerList = withLoadMore({
|
||||||
withLoadMore({
|
fetch: (props, $store) => $store.dispatch('fetchFollowers', props.userId),
|
||||||
fetch: (props, $store) => $store.dispatch('fetchFollowers', props.userId),
|
select: (props, $store) => get($store.getters.findUser(props.userId), 'followerIds', []).map(id => $store.getters.findUser(id)),
|
||||||
select: (props, $store) => get($store.getters.findUser(props.userId), 'followerIds', []).map(id => $store.getters.findUser(id)),
|
destroy: (props, $store) => $store.dispatch('clearFollowers', props.userId),
|
||||||
destory: (props, $store) => $store.dispatch('clearFollowers', props.userId),
|
childPropName: 'items',
|
||||||
childPropName: 'entries',
|
additionalPropNames: ['userId']
|
||||||
additionalPropNames: ['userId']
|
})(List)
|
||||||
}),
|
|
||||||
withList({ getEntryProps: user => ({ user }) })
|
|
||||||
)(FollowCard)
|
|
||||||
|
|
||||||
const FriendList = compose(
|
const FriendList = withLoadMore({
|
||||||
withLoadMore({
|
fetch: (props, $store) => $store.dispatch('fetchFriends', props.userId),
|
||||||
fetch: (props, $store) => $store.dispatch('fetchFriends', props.userId),
|
select: (props, $store) => get($store.getters.findUser(props.userId), 'friendIds', []).map(id => $store.getters.findUser(id)),
|
||||||
select: (props, $store) => get($store.getters.findUser(props.userId), 'friendIds', []).map(id => $store.getters.findUser(id)),
|
destroy: (props, $store) => $store.dispatch('clearFriends', props.userId),
|
||||||
destory: (props, $store) => $store.dispatch('clearFriends', props.userId),
|
childPropName: 'items',
|
||||||
childPropName: 'entries',
|
additionalPropNames: ['userId']
|
||||||
additionalPropNames: ['userId']
|
})(List)
|
||||||
}),
|
|
||||||
withList({ getEntryProps: user => ({ user }) })
|
|
||||||
)(FollowCard)
|
|
||||||
|
|
||||||
const UserProfile = {
|
const UserProfile = {
|
||||||
data () {
|
data () {
|
||||||
|
@ -134,7 +127,8 @@ const UserProfile = {
|
||||||
Timeline,
|
Timeline,
|
||||||
FollowerList,
|
FollowerList,
|
||||||
FriendList,
|
FriendList,
|
||||||
ModerationTools
|
ModerationTools,
|
||||||
|
FollowCard
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,10 +14,18 @@
|
||||||
:user-id="userId"
|
:user-id="userId"
|
||||||
/>
|
/>
|
||||||
<div :label="$t('user_card.followees')" v-if="followsTabVisible" :disabled="!user.friends_count">
|
<div :label="$t('user_card.followees')" v-if="followsTabVisible" :disabled="!user.friends_count">
|
||||||
<FriendList :userId="userId" />
|
<FriendList :userId="userId">
|
||||||
|
<template slot="item" slot-scope="{item}">
|
||||||
|
<FollowCard :user="item" />
|
||||||
|
</template>
|
||||||
|
</FriendList>
|
||||||
</div>
|
</div>
|
||||||
<div :label="$t('user_card.followers')" v-if="followersTabVisible" :disabled="!user.followers_count">
|
<div :label="$t('user_card.followers')" v-if="followersTabVisible" :disabled="!user.followers_count">
|
||||||
<FollowerList :userId="userId" :entryProps="{noFollowsYou: isUs}" />
|
<FollowerList :userId="userId">
|
||||||
|
<template slot="item" slot-scope="{item}">
|
||||||
|
<FollowCard :user="item" :noFollowsYou="isUs" />
|
||||||
|
</template>
|
||||||
|
</FollowerList>
|
||||||
</div>
|
</div>
|
||||||
<Timeline
|
<Timeline
|
||||||
:label="$t('user_card.media')"
|
:label="$t('user_card.media')"
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
<i class="icon-spin3 animate-spin"/>
|
<i class="icon-spin3 animate-spin"/>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="panel-body">
|
<div v-else class="panel-body">
|
||||||
<FollowCard v-for="user in users" :key="user.id" :user="user"/>
|
<FollowCard v-for="user in users" :key="user.id" :user="user" class="list-item"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
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 map from 'lodash/map'
|
||||||
|
@ -10,29 +9,24 @@ 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 SelectableList from '../selectable_list/selectable_list.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'
|
||||||
import Autosuggest from '../autosuggest/autosuggest.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 userSearchApi from '../../services/new_api/user_search.js'
|
import userSearchApi from '../../services/new_api/user_search.js'
|
||||||
|
|
||||||
const BlockList = compose(
|
const BlockList = withSubscription({
|
||||||
withSubscription({
|
fetch: (props, $store) => $store.dispatch('fetchBlocks'),
|
||||||
fetch: (props, $store) => $store.dispatch('fetchBlocks'),
|
select: (props, $store) => get($store.state.users.currentUser, 'blockIds', []),
|
||||||
select: (props, $store) => get($store.state.users.currentUser, 'blockIds', []),
|
childPropName: 'items'
|
||||||
childPropName: 'entries'
|
})(SelectableList)
|
||||||
}),
|
|
||||||
withList({ getEntryProps: userId => ({ userId }) })
|
|
||||||
)(BlockCard)
|
|
||||||
|
|
||||||
const MuteList = compose(
|
const MuteList = withSubscription({
|
||||||
withSubscription({
|
fetch: (props, $store) => $store.dispatch('fetchMutes'),
|
||||||
fetch: (props, $store) => $store.dispatch('fetchMutes'),
|
select: (props, $store) => get($store.state.users.currentUser, 'muteIds', []),
|
||||||
select: (props, $store) => get($store.state.users.currentUser, 'muteIds', []),
|
childPropName: 'items'
|
||||||
childPropName: 'entries'
|
})(SelectableList)
|
||||||
}),
|
|
||||||
withList({ getEntryProps: userId => ({ userId }) })
|
|
||||||
)(MuteCard)
|
|
||||||
|
|
||||||
const UserSettings = {
|
const UserSettings = {
|
||||||
data () {
|
data () {
|
||||||
|
@ -80,7 +74,8 @@ const UserSettings = {
|
||||||
EmojiInput,
|
EmojiInput,
|
||||||
Autosuggest,
|
Autosuggest,
|
||||||
BlockCard,
|
BlockCard,
|
||||||
MuteCard
|
MuteCard,
|
||||||
|
ProgressButton
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
user () {
|
user () {
|
||||||
|
@ -360,6 +355,21 @@ const UserSettings = {
|
||||||
this.$store.dispatch('addNewUsers', users)
|
this.$store.dispatch('addNewUsers', users)
|
||||||
return map(users, 'id')
|
return map(users, 'id')
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
blockUsers (ids) {
|
||||||
|
return this.$store.dispatch('blockUsers', ids)
|
||||||
|
},
|
||||||
|
unblockUsers (ids) {
|
||||||
|
return this.$store.dispatch('unblockUsers', ids)
|
||||||
|
},
|
||||||
|
muteUsers (ids) {
|
||||||
|
return this.$store.dispatch('muteUsers', ids)
|
||||||
|
},
|
||||||
|
unmuteUsers (ids) {
|
||||||
|
return this.$store.dispatch('unmuteUsers', ids)
|
||||||
|
},
|
||||||
|
identity (value) {
|
||||||
|
return value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -200,9 +200,22 @@
|
||||||
<BlockCard slot-scope="row" :userId="row.item"/>
|
<BlockCard slot-scope="row" :userId="row.item"/>
|
||||||
</Autosuggest>
|
</Autosuggest>
|
||||||
</div>
|
</div>
|
||||||
<block-list :refresh="true">
|
<BlockList :refresh="true" :getKey="identity">
|
||||||
|
<template slot="header" slot-scope="{selected}">
|
||||||
|
<div class="profile-edit-bulk-actions">
|
||||||
|
<ProgressButton class="btn btn-default" v-if="selected.length > 0" :click="() => blockUsers(selected)">
|
||||||
|
{{ $t('user_card.block') }}
|
||||||
|
<template slot="progress">{{ $t('user_card.block_progress') }}</template>
|
||||||
|
</ProgressButton>
|
||||||
|
<ProgressButton class="btn btn-default" v-if="selected.length > 0" :click="() => unblockUsers(selected)">
|
||||||
|
{{ $t('user_card.unblock') }}
|
||||||
|
<template slot="progress">{{ $t('user_card.unblock_progress') }}</template>
|
||||||
|
</ProgressButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template slot="item" slot-scope="{item}"><BlockCard :userId="item" /></template>
|
||||||
<template slot="empty">{{$t('settings.no_blocks')}}</template>
|
<template slot="empty">{{$t('settings.no_blocks')}}</template>
|
||||||
</block-list>
|
</BlockList>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div :label="$t('settings.mutes_tab')">
|
<div :label="$t('settings.mutes_tab')">
|
||||||
|
@ -211,9 +224,22 @@
|
||||||
<MuteCard slot-scope="row" :userId="row.item"/>
|
<MuteCard slot-scope="row" :userId="row.item"/>
|
||||||
</Autosuggest>
|
</Autosuggest>
|
||||||
</div>
|
</div>
|
||||||
<mute-list :refresh="true">
|
<MuteList :refresh="true" :getKey="identity">
|
||||||
|
<template slot="header" slot-scope="{selected}">
|
||||||
|
<div class="profile-edit-bulk-actions">
|
||||||
|
<ProgressButton class="btn btn-default" v-if="selected.length > 0" :click="() => muteUsers(selected)">
|
||||||
|
{{ $t('user_card.mute') }}
|
||||||
|
<template slot="progress">{{ $t('user_card.mute_progress') }}</template>
|
||||||
|
</ProgressButton>
|
||||||
|
<ProgressButton class="btn btn-default" v-if="selected.length > 0" :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 :userId="item" /></template>
|
||||||
<template slot="empty">{{$t('settings.no_mutes')}}</template>
|
<template slot="empty">{{$t('settings.no_mutes')}}</template>
|
||||||
</mute-list>
|
</MuteList>
|
||||||
</div>
|
</div>
|
||||||
</tab-switcher>
|
</tab-switcher>
|
||||||
</div>
|
</div>
|
||||||
|
@ -276,5 +302,15 @@
|
||||||
&-usersearch-wrapper {
|
&-usersearch-wrapper {
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&-bulk-actions {
|
||||||
|
text-align: right;
|
||||||
|
padding: 0 1em;
|
||||||
|
min-height: 28px;
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 10em;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
{{$t('who_to_follow.who_to_follow')}}
|
{{$t('who_to_follow.who_to_follow')}}
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<FollowCard v-for="user in users" :key="user.id" :user="user"/>
|
<FollowCard v-for="user in users" :key="user.id" :user="user" class="list-item"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,40 +0,0 @@
|
||||||
import Vue from 'vue'
|
|
||||||
import map from 'lodash/map'
|
|
||||||
import isEmpty from 'lodash/isEmpty'
|
|
||||||
import './with_list.scss'
|
|
||||||
|
|
||||||
const defaultEntryPropsGetter = entry => ({ entry })
|
|
||||||
const defaultKeyGetter = entry => entry.id
|
|
||||||
|
|
||||||
const withList = ({
|
|
||||||
getEntryProps = defaultEntryPropsGetter, // function to accept entry and index values and return props to be passed into the item component
|
|
||||||
getKey = defaultKeyGetter // funciton to accept entry and index values and return key prop value
|
|
||||||
}) => (ItemComponent) => (
|
|
||||||
Vue.component('withList', {
|
|
||||||
props: [
|
|
||||||
'entries', // array of entry
|
|
||||||
'entryProps', // additional props to be passed into each entry
|
|
||||||
'entryListeners' // additional event listeners to be passed into each entry
|
|
||||||
],
|
|
||||||
render (createElement) {
|
|
||||||
return (
|
|
||||||
<div class="with-list">
|
|
||||||
{map(this.entries, (entry, index) => {
|
|
||||||
const props = {
|
|
||||||
key: getKey(entry, index),
|
|
||||||
props: {
|
|
||||||
...this.$props.entryProps,
|
|
||||||
...getEntryProps(entry, index)
|
|
||||||
},
|
|
||||||
on: this.$props.entryListeners
|
|
||||||
}
|
|
||||||
return <ItemComponent {...props} />
|
|
||||||
})}
|
|
||||||
{isEmpty(this.entries) && this.$slots.empty && <div class="with-list-empty-content faint">{this.$slots.empty}</div>}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
export default withList
|
|
|
@ -1,6 +0,0 @@
|
||||||
.with-list {
|
|
||||||
&-empty-content {
|
|
||||||
text-align: center;
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +1,13 @@
|
||||||
|
|
||||||
|
@import '../../_variables.scss';
|
||||||
|
|
||||||
.with-load-more {
|
.with-load-more {
|
||||||
&-footer {
|
&-footer {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
border-top: 1px solid;
|
||||||
|
border-top-color: $fallback--border;
|
||||||
|
border-top-color: var(--border, $fallback--border);
|
||||||
|
|
||||||
.error {
|
.error {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
|
|
@ -112,6 +112,9 @@
|
||||||
"password_confirmation_match": "should be the same as password"
|
"password_confirmation_match": "should be the same as password"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"selectable_list": {
|
||||||
|
"select_all": "Select all"
|
||||||
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"app_name": "App name",
|
"app_name": "App name",
|
||||||
"attachmentRadius": "Attachments",
|
"attachmentRadius": "Attachments",
|
||||||
|
|
|
@ -32,6 +32,35 @@ const getNotificationPermission = () => {
|
||||||
return Promise.resolve(Notification.permission)
|
return Promise.resolve(Notification.permission)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const blockUser = (store, id) => {
|
||||||
|
return store.rootState.api.backendInteractor.blockUser(id)
|
||||||
|
.then((relationship) => {
|
||||||
|
store.commit('updateUserRelationship', [relationship])
|
||||||
|
store.commit('addBlockId', id)
|
||||||
|
store.commit('removeStatus', { timeline: 'friends', userId: id })
|
||||||
|
store.commit('removeStatus', { timeline: 'public', userId: id })
|
||||||
|
store.commit('removeStatus', { timeline: 'publicAndExternal', userId: id })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const unblockUser = (store, id) => {
|
||||||
|
return store.rootState.api.backendInteractor.unblockUser(id)
|
||||||
|
.then((relationship) => store.commit('updateUserRelationship', [relationship]))
|
||||||
|
}
|
||||||
|
|
||||||
|
const muteUser = (store, id) => {
|
||||||
|
return store.rootState.api.backendInteractor.muteUser(id)
|
||||||
|
.then((relationship) => {
|
||||||
|
store.commit('updateUserRelationship', [relationship])
|
||||||
|
store.commit('addMuteId', id)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const unmuteUser = (store, id) => {
|
||||||
|
return store.rootState.api.backendInteractor.unmuteUser(id)
|
||||||
|
.then((relationship) => store.commit('updateUserRelationship', [relationship]))
|
||||||
|
}
|
||||||
|
|
||||||
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]
|
||||||
|
@ -206,19 +235,17 @@ const users = {
|
||||||
return blocks
|
return blocks
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
blockUser (store, userId) {
|
blockUser (store, id) {
|
||||||
return store.rootState.api.backendInteractor.blockUser(userId)
|
return blockUser(store, id)
|
||||||
.then((relationship) => {
|
|
||||||
store.commit('updateUserRelationship', [relationship])
|
|
||||||
store.commit('addBlockId', userId)
|
|
||||||
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 unblockUser(store, id)
|
||||||
.then((relationship) => store.commit('updateUserRelationship', [relationship]))
|
},
|
||||||
|
blockUsers (store, ids = []) {
|
||||||
|
return Promise.all(ids.map(id => blockUser(store, id)))
|
||||||
|
},
|
||||||
|
unblockUsers (store, ids = []) {
|
||||||
|
return Promise.all(ids.map(id => unblockUser(store, id)))
|
||||||
},
|
},
|
||||||
fetchMutes (store) {
|
fetchMutes (store) {
|
||||||
return store.rootState.api.backendInteractor.fetchMutes()
|
return store.rootState.api.backendInteractor.fetchMutes()
|
||||||
|
@ -229,15 +256,16 @@ const users = {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
muteUser (store, id) {
|
muteUser (store, id) {
|
||||||
return store.rootState.api.backendInteractor.muteUser(id)
|
return muteUser(store, id)
|
||||||
.then((relationship) => {
|
|
||||||
store.commit('updateUserRelationship', [relationship])
|
|
||||||
store.commit('addMuteId', id)
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
unmuteUser (store, id) {
|
unmuteUser (store, id) {
|
||||||
return store.rootState.api.backendInteractor.unmuteUser(id)
|
return unmuteUser(store, id)
|
||||||
.then((relationship) => store.commit('updateUserRelationship', [relationship]))
|
},
|
||||||
|
muteUsers (store, ids = []) {
|
||||||
|
return Promise.all(ids.map(id => muteUser(store, id)))
|
||||||
|
},
|
||||||
|
unmuteUsers (store, ids = []) {
|
||||||
|
return Promise.all(ids.map(id => unmuteUser(store, id)))
|
||||||
},
|
},
|
||||||
fetchFriends ({ rootState, commit }, id) {
|
fetchFriends ({ rootState, commit }, id) {
|
||||||
const user = rootState.users.usersObject[id]
|
const user = rootState.users.usersObject[id]
|
||||||
|
|
10
yarn.lock
10
yarn.lock
|
@ -6430,16 +6430,6 @@ vue-chat-scroll@^1.2.1:
|
||||||
version "1.3.5"
|
version "1.3.5"
|
||||||
resolved "https://registry.yarnpkg.com/vue-chat-scroll/-/vue-chat-scroll-1.3.5.tgz#a5ee5bae5058f614818a96eac5ee3be4394a2f68"
|
resolved "https://registry.yarnpkg.com/vue-chat-scroll/-/vue-chat-scroll-1.3.5.tgz#a5ee5bae5058f614818a96eac5ee3be4394a2f68"
|
||||||
|
|
||||||
vue-compose@^0.7.1:
|
|
||||||
version "0.7.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/vue-compose/-/vue-compose-0.7.1.tgz#1c11c4cd5e2c8f2743b03fce8ab43d78aabc20b3"
|
|
||||||
dependencies:
|
|
||||||
vue-hoc "0.x.x"
|
|
||||||
|
|
||||||
vue-hoc@0.x.x:
|
|
||||||
version "0.4.7"
|
|
||||||
resolved "https://registry.yarnpkg.com/vue-hoc/-/vue-hoc-0.4.7.tgz#4d3322ba89b8b0e42b19045ef536c21d948a4fac"
|
|
||||||
|
|
||||||
vue-hot-reload-api@^2.0.11:
|
vue-hot-reload-api@^2.0.11:
|
||||||
version "2.3.1"
|
version "2.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.1.tgz#b2d3d95402a811602380783ea4f566eb875569a2"
|
resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.1.tgz#b2d3d95402a811602380783ea4f566eb875569a2"
|
||||||
|
|
Loading…
Reference in a new issue