Merge branch 'develop' into brendenbice1222/pleroma-fe-issues/pleroma-fe-202-show-boosted-users
This commit is contained in:
commit
d417945427
41 changed files with 767 additions and 275 deletions
52
src/components/autosuggest/autosuggest.js
Normal file
52
src/components/autosuggest/autosuggest.js
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
45
src/components/autosuggest/autosuggest.vue
Normal file
45
src/components/autosuggest/autosuggest.vue
Normal 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>
|
|
@ -24,19 +24,11 @@
|
|||
<script src="./basic_user_card.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
|
||||
.basic-user-card {
|
||||
display: flex;
|
||||
flex: 1 0;
|
||||
margin: 0;
|
||||
padding-top: 0.6em;
|
||||
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);
|
||||
padding: 0.6em 1em;
|
||||
|
||||
&-collapsed-content {
|
||||
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')}}
|
||||
</div>
|
||||
<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>
|
||||
</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>
|
|
@ -23,7 +23,7 @@ const Notification = {
|
|||
return generateProfileLink(user.id, user.screen_name, this.$store.state.instance.restrictedNicknames)
|
||||
},
|
||||
getUser (notification) {
|
||||
return this.$store.state.users.usersObject[notification.action.user.id]
|
||||
return this.$store.state.users.usersObject[notification.from_profile.id]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
|
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>
|
|
@ -42,9 +42,7 @@
|
|||
</li>
|
||||
<li>
|
||||
<input type="checkbox" id="collapseMessageWithSubject" v-model="collapseMessageWithSubjectLocal">
|
||||
<label for="collapseMessageWithSubject">
|
||||
{{$t('settings.collapse_subject')}} {{$t('settings.instance_default', { value: collapseMessageWithSubjectDefault })}}
|
||||
</label>
|
||||
<label for="collapseMessageWithSubject">{{$t('settings.collapse_subject')}} {{$t('settings.instance_default', { value: collapseMessageWithSubjectDefault })}}</label>
|
||||
</li>
|
||||
<li>
|
||||
<input type="checkbox" id="streaming" v-model="streamingLocal">
|
||||
|
@ -330,6 +328,7 @@
|
|||
|
||||
textarea {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
<div :title="user.name" class='user-name' v-if="user.name_html" v-html="user.name_html"></div>
|
||||
<div :title="user.name" class='user-name' v-else>{{user.name}}</div>
|
||||
<router-link :to="{ name: 'user-settings' }" v-if="!isOtherUser">
|
||||
<i class="button-icon icon-pencil usersettings" :title="$t('tool_tip.user_settings')"></i>
|
||||
<i class="button-icon icon-wrench usersettings" :title="$t('tool_tip.user_settings')"></i>
|
||||
</router-link>
|
||||
<a :href="user.statusnet_profile_url" target="_blank" v-if="isOtherUser && !user.is_local">
|
||||
<i class="icon-link-ext usersettings"></i>
|
||||
|
@ -162,7 +162,7 @@
|
|||
max-width: 100%;
|
||||
max-height: 400px;
|
||||
|
||||
.emoji {
|
||||
&.emoji {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
|
|
|
@ -1,48 +1,37 @@
|
|||
import { compose } from 'vue-compose'
|
||||
import get from 'lodash/get'
|
||||
import UserCard from '../user_card/user_card.vue'
|
||||
import FollowCard from '../follow_card/follow_card.vue'
|
||||
import Timeline from '../timeline/timeline.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 withList from '../../hocs/with_list/with_list'
|
||||
|
||||
const FollowerList = compose(
|
||||
withLoadMore({
|
||||
fetch: (props, $store) => $store.dispatch('addFollowers', props.userId),
|
||||
select: (props, $store) => get($store.getters.findUser(props.userId), 'followers', []),
|
||||
destory: (props, $store) => $store.dispatch('clearFollowers', props.userId),
|
||||
childPropName: 'entries',
|
||||
additionalPropNames: ['userId']
|
||||
}),
|
||||
withList({ getEntryProps: user => ({ user }) })
|
||||
)(FollowCard)
|
||||
const FollowerList = withLoadMore({
|
||||
fetch: (props, $store) => $store.dispatch('fetchFollowers', props.userId),
|
||||
select: (props, $store) => get($store.getters.findUser(props.userId), 'followerIds', []).map(id => $store.getters.findUser(id)),
|
||||
destroy: (props, $store) => $store.dispatch('clearFollowers', props.userId),
|
||||
childPropName: 'items',
|
||||
additionalPropNames: ['userId']
|
||||
})(List)
|
||||
|
||||
const FriendList = compose(
|
||||
withLoadMore({
|
||||
fetch: (props, $store) => $store.dispatch('addFriends', props.userId),
|
||||
select: (props, $store) => get($store.getters.findUser(props.userId), 'friends', []),
|
||||
destory: (props, $store) => $store.dispatch('clearFriends', props.userId),
|
||||
childPropName: 'entries',
|
||||
additionalPropNames: ['userId']
|
||||
}),
|
||||
withList({ getEntryProps: user => ({ user }) })
|
||||
)(FollowCard)
|
||||
const FriendList = withLoadMore({
|
||||
fetch: (props, $store) => $store.dispatch('fetchFriends', props.userId),
|
||||
select: (props, $store) => get($store.getters.findUser(props.userId), 'friendIds', []).map(id => $store.getters.findUser(id)),
|
||||
destroy: (props, $store) => $store.dispatch('clearFriends', props.userId),
|
||||
childPropName: 'items',
|
||||
additionalPropNames: ['userId']
|
||||
})(List)
|
||||
|
||||
const UserProfile = {
|
||||
data () {
|
||||
return {
|
||||
error: false,
|
||||
fetchedUserId: null
|
||||
userId: null
|
||||
}
|
||||
},
|
||||
created () {
|
||||
if (!this.user.id) {
|
||||
this.fetchUserId()
|
||||
.then(() => this.startUp())
|
||||
} else {
|
||||
this.startUp()
|
||||
}
|
||||
const routeParams = this.$route.params
|
||||
this.load(routeParams.name || routeParams.id)
|
||||
},
|
||||
destroyed () {
|
||||
this.cleanUp()
|
||||
|
@ -57,26 +46,12 @@ const UserProfile = {
|
|||
media () {
|
||||
return this.$store.state.statuses.timelines.media
|
||||
},
|
||||
userId () {
|
||||
return this.$route.params.id || this.user.id || this.fetchedUserId
|
||||
},
|
||||
userName () {
|
||||
return this.$route.params.name || this.user.screen_name
|
||||
},
|
||||
isUs () {
|
||||
return this.userId && this.$store.state.users.currentUser.id &&
|
||||
this.userId === this.$store.state.users.currentUser.id
|
||||
},
|
||||
userInStore () {
|
||||
const routeParams = this.$route.params
|
||||
// This needs fetchedUserId so that computed will be refreshed when user is fetched
|
||||
return this.$store.getters.findUser(this.fetchedUserId || routeParams.name || routeParams.id)
|
||||
},
|
||||
user () {
|
||||
if (this.userInStore) {
|
||||
return this.userInStore
|
||||
}
|
||||
return {}
|
||||
return this.$store.getters.findUser(this.userId)
|
||||
},
|
||||
isExternal () {
|
||||
return this.$route.name === 'external-user-profile'
|
||||
|
@ -89,39 +64,36 @@ const UserProfile = {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
startFetchFavorites () {
|
||||
if (this.isUs) {
|
||||
this.$store.dispatch('startFetchingTimeline', { timeline: 'favorites', userId: this.userId })
|
||||
}
|
||||
},
|
||||
fetchUserId () {
|
||||
let fetchPromise
|
||||
if (this.userId && !this.$route.params.name) {
|
||||
fetchPromise = this.$store.dispatch('fetchUser', this.userId)
|
||||
load (userNameOrId) {
|
||||
// Check if user data is already loaded in store
|
||||
const user = this.$store.getters.findUser(userNameOrId)
|
||||
if (user) {
|
||||
this.userId = user.id
|
||||
this.fetchTimelines()
|
||||
} else {
|
||||
fetchPromise = this.$store.dispatch('fetchUser', this.userName)
|
||||
this.$store.dispatch('fetchUser', userNameOrId)
|
||||
.then(({ id }) => {
|
||||
this.fetchedUserId = id
|
||||
this.userId = id
|
||||
this.fetchTimelines()
|
||||
})
|
||||
.catch((reason) => {
|
||||
const errorMessage = get(reason, 'error.error')
|
||||
if (errorMessage === 'No user with such user_id') { // Known error
|
||||
this.error = this.$t('user_profile.profile_does_not_exist')
|
||||
} else if (errorMessage) {
|
||||
this.error = errorMessage
|
||||
} else {
|
||||
this.error = this.$t('user_profile.profile_loading_error')
|
||||
}
|
||||
})
|
||||
}
|
||||
return fetchPromise
|
||||
.catch((reason) => {
|
||||
const errorMessage = get(reason, 'error.error')
|
||||
if (errorMessage === 'No user with such user_id') { // Known error
|
||||
this.error = this.$t('user_profile.profile_does_not_exist')
|
||||
} else if (errorMessage) {
|
||||
this.error = errorMessage
|
||||
} else {
|
||||
this.error = this.$t('user_profile.profile_loading_error')
|
||||
}
|
||||
})
|
||||
.then(() => this.startUp())
|
||||
},
|
||||
startUp () {
|
||||
if (this.userId) {
|
||||
this.$store.dispatch('startFetchingTimeline', { timeline: 'user', userId: this.userId })
|
||||
this.$store.dispatch('startFetchingTimeline', { timeline: 'media', userId: this.userId })
|
||||
this.startFetchFavorites()
|
||||
fetchTimelines () {
|
||||
const userId = this.userId
|
||||
this.$store.dispatch('startFetchingTimeline', { timeline: 'user', userId })
|
||||
this.$store.dispatch('startFetchingTimeline', { timeline: 'media', userId })
|
||||
if (this.isUs) {
|
||||
this.$store.dispatch('startFetchingTimeline', { timeline: 'favorites', userId })
|
||||
}
|
||||
},
|
||||
cleanUp () {
|
||||
|
@ -134,18 +106,16 @@ const UserProfile = {
|
|||
}
|
||||
},
|
||||
watch: {
|
||||
// userId can be undefined if we don't know it yet
|
||||
userId (newVal) {
|
||||
'$route.params.id': function (newVal) {
|
||||
if (newVal) {
|
||||
this.cleanUp()
|
||||
this.startUp()
|
||||
this.load(newVal)
|
||||
}
|
||||
},
|
||||
userName () {
|
||||
if (this.$route.params.name) {
|
||||
this.fetchUserId()
|
||||
'$route.params.name': function (newVal) {
|
||||
if (newVal) {
|
||||
this.cleanUp()
|
||||
this.startUp()
|
||||
this.load(newVal)
|
||||
}
|
||||
},
|
||||
$route () {
|
||||
|
@ -157,7 +127,8 @@ const UserProfile = {
|
|||
Timeline,
|
||||
FollowerList,
|
||||
FriendList,
|
||||
ModerationTools
|
||||
ModerationTools,
|
||||
FollowCard
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div>
|
||||
<div v-if="user.id" class="user-profile panel panel-default">
|
||||
<div v-if="user" class="user-profile panel panel-default">
|
||||
<UserCard :user="user" :switcher="true" :selected="timeline.viewing" rounded="top"/>
|
||||
<tab-switcher :renderOnlyFocused="true" ref="tabSwitcher">
|
||||
<Timeline
|
||||
|
@ -14,10 +14,18 @@
|
|||
:user-id="userId"
|
||||
/>
|
||||
<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 :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>
|
||||
<Timeline
|
||||
:label="$t('user_card.media')"
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<i class="icon-spin3 animate-spin"/>
|
||||
</div>
|
||||
<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>
|
||||
</template>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { compose } from 'vue-compose'
|
||||
import unescape from 'lodash/unescape'
|
||||
import get from 'lodash/get'
|
||||
import map from 'lodash/map'
|
||||
import reject from 'lodash/reject'
|
||||
import TabSwitcher from '../tab_switcher/tab_switcher.js'
|
||||
import ImageCropper from '../image_cropper/image_cropper.vue'
|
||||
import StyleSwitcher from '../style_switcher/style_switcher.vue'
|
||||
|
@ -8,27 +9,24 @@ import ScopeSelector from '../scope_selector/scope_selector.vue'
|
|||
import fileSizeFormatService from '../../services/file_size_format/file_size_format.js'
|
||||
import BlockCard from '../block_card/block_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 Autosuggest from '../autosuggest/autosuggest.vue'
|
||||
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'
|
||||
|
||||
const BlockList = compose(
|
||||
withSubscription({
|
||||
fetch: (props, $store) => $store.dispatch('fetchBlocks'),
|
||||
select: (props, $store) => get($store.state.users.currentUser, 'blockIds', []),
|
||||
childPropName: 'entries'
|
||||
}),
|
||||
withList({ getEntryProps: userId => ({ userId }) })
|
||||
)(BlockCard)
|
||||
const BlockList = withSubscription({
|
||||
fetch: (props, $store) => $store.dispatch('fetchBlocks'),
|
||||
select: (props, $store) => get($store.state.users.currentUser, 'blockIds', []),
|
||||
childPropName: 'items'
|
||||
})(SelectableList)
|
||||
|
||||
const MuteList = compose(
|
||||
withSubscription({
|
||||
fetch: (props, $store) => $store.dispatch('fetchMutes'),
|
||||
select: (props, $store) => get($store.state.users.currentUser, 'muteIds', []),
|
||||
childPropName: 'entries'
|
||||
}),
|
||||
withList({ getEntryProps: userId => ({ userId }) })
|
||||
)(MuteCard)
|
||||
const MuteList = withSubscription({
|
||||
fetch: (props, $store) => $store.dispatch('fetchMutes'),
|
||||
select: (props, $store) => get($store.state.users.currentUser, 'muteIds', []),
|
||||
childPropName: 'items'
|
||||
})(SelectableList)
|
||||
|
||||
const UserSettings = {
|
||||
data () {
|
||||
|
@ -73,7 +71,11 @@ const UserSettings = {
|
|||
ImageCropper,
|
||||
BlockList,
|
||||
MuteList,
|
||||
EmojiInput
|
||||
EmojiInput,
|
||||
Autosuggest,
|
||||
BlockCard,
|
||||
MuteCard,
|
||||
ProgressButton
|
||||
},
|
||||
computed: {
|
||||
user () {
|
||||
|
@ -334,6 +336,40 @@ const UserSettings = {
|
|||
if (window.confirm(`${this.$i18n.t('settings.revoke_token')}?`)) {
|
||||
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')
|
||||
})
|
||||
},
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
<div class="setting-item" >
|
||||
<h2>{{$t('settings.name_bio')}}</h2>
|
||||
<p>{{$t('settings.name')}}</p>
|
||||
<EmojiInput
|
||||
<EmojiInput
|
||||
type="text"
|
||||
v-model="newName"
|
||||
id="username"
|
||||
|
@ -195,15 +195,51 @@
|
|||
</div>
|
||||
|
||||
<div :label="$t('settings.blocks_tab')">
|
||||
<block-list :refresh="true">
|
||||
<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>
|
||||
<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>
|
||||
</block-list>
|
||||
</BlockList>
|
||||
</div>
|
||||
|
||||
<div :label="$t('settings.mutes_tab')">
|
||||
<mute-list :refresh="true">
|
||||
<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>
|
||||
<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>
|
||||
</mute-list>
|
||||
</MuteList>
|
||||
</div>
|
||||
</tab-switcher>
|
||||
</div>
|
||||
|
@ -262,5 +298,19 @@
|
|||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
&-usersearch-wrapper {
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
&-bulk-actions {
|
||||
text-align: right;
|
||||
padding: 0 1em;
|
||||
min-height: 28px;
|
||||
|
||||
button {
|
||||
width: 10em;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
{{$t('who_to_follow.who_to_follow')}}
|
||||
</div>
|
||||
<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>
|
||||
</template>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue