Merge branch 'develop' into 'feature/users_timeline'

# Conflicts:
#   src/modules/users.js
This commit is contained in:
lambadalambda 2017-01-01 12:20:29 -05:00
commit a35e9ce3e0
11 changed files with 136 additions and 36 deletions

View File

@ -0,0 +1,17 @@
const DeleteButton = {
props: [ 'status' ],
methods: {
deleteStatus () {
const confirmed = confirm('Do you really want to delete this status?')
if (confirmed) {
this.$store.dispatch('deleteStatus', { id: this.status.id })
}
}
},
computed: {
currentUser () { return this.$store.state.users.currentUser },
canDelete () { return this.currentUser.rights.delete_others_notice || this.status.user.id == this.currentUser.id }
}
}
export default DeleteButton

View File

@ -0,0 +1,20 @@
<template>
<div v-if="canDelete">
<a href="#" v-on:click.prevent="deleteStatus()">
<i class='fa icon-cancel delete-status'></i>
</a>
</div>
</template>
<script src="./delete_button.js" ></script>
<style lang='scss'>
@import '../../_variables.scss';
.icon-cancel,.delete-status {
cursor: pointer;
&:hover {
color: $red;
}
}
</style>

View File

@ -1,6 +1,7 @@
import Attachment from '../attachment/attachment.vue' import Attachment from '../attachment/attachment.vue'
import FavoriteButton from '../favorite_button/favorite_button.vue' import FavoriteButton from '../favorite_button/favorite_button.vue'
import RetweetButton from '../retweet_button/retweet_button.vue' import RetweetButton from '../retweet_button/retweet_button.vue'
import DeleteButton from '../delete_button/delete_button.vue'
import PostStatusForm from '../post_status_form/post_status_form.vue' import PostStatusForm from '../post_status_form/post_status_form.vue'
const Status = { const Status = {
@ -20,25 +21,18 @@ const Status = {
}, },
loggedIn () { loggedIn () {
return !!this.$store.state.users.currentUser return !!this.$store.state.users.currentUser
}, }
deleted () { return this.statusoid.deleted },
canDelete () { return this.statusoid.user.id === this.$store.state.users.currentUser.id }
}, },
components: { components: {
Attachment, Attachment,
FavoriteButton, FavoriteButton,
RetweetButton, RetweetButton,
DeleteButton,
PostStatusForm PostStatusForm
}, },
methods: { methods: {
toggleReplying () { toggleReplying () {
this.replying = !this.replying this.replying = !this.replying
},
deleteStatus () {
const confirmed = confirm('Do you really want to delete this status?')
if (confirmed) {
this.$store.dispatch('deleteStatus', { id: this.status.id })
}
} }
} }
} }

View File

@ -52,11 +52,7 @@
</div> </div>
<retweet-button :status=status></retweet-button> <retweet-button :status=status></retweet-button>
<favorite-button :status=status></favorite-button> <favorite-button :status=status></favorite-button>
<div v-if="canDelete"> <delete-button :status=status></delete-button>
<a href="#" v-on:click.prevent="deleteStatus">
<i class='fa icon-cancel delete-status'></i>
</a>
</div>
</div> </div>
<post-status-form v-if="replying" :reply-to="status.id" :attentions="status.attentions" :repliedUser="status.user" v-on:posted="toggleReplying"></post-status-form> <post-status-form v-if="replying" :reply-to="status.id" :attentions="status.attentions" :repliedUser="status.user" v-on:posted="toggleReplying"></post-status-form>
@ -130,11 +126,4 @@
.status-el:last-child .status { .status-el:last-child .status {
border: none border: none
} }
.icon-cancel,.delete-status {
cursor: pointer;
&:hover {
color: $red;
}
}
</style> </style>

View File

@ -6,6 +6,24 @@
<span class="glyphicon glyphicon-user"></span> <span class="glyphicon glyphicon-user"></span>
<div class='user-name'>{{user.name}}</div> <div class='user-name'>{{user.name}}</div>
<div class='user-screen-name'>@{{user.screen_name}}</div> <div class='user-screen-name'>@{{user.screen_name}}</div>
<div v-if="isOtherUser" class="following-info">
<div v-if="user.follows_you" class="following">
Follows you!
</div>
<div class="followed">
<span v-if="user.following">
Following them!
<button @click="unfollowUser">
Unfollow!
</button>
</span>
<span v-if="!user.following" >
<button @click="followUser">
Follow!
</button>
</span>
</div>
</div>
</div> </div>
</div> </div>
<div class="panel-body"> <div class="panel-body">
@ -37,6 +55,21 @@
color: `#${this.user.profile_link_color}`, color: `#${this.user.profile_link_color}`,
'background-image': `url(${this.user.cover_photo})` 'background-image': `url(${this.user.cover_photo})`
} }
},
isOtherUser () {
return this.user !== this.$store.state.users.currentUser
}
},
methods: {
followUser () {
const store = this.$store
store.state.api.backendInteractor.followUser(this.user.id)
.then((followedUser) => store.commit('addNewUsers', [followedUser]))
},
unfollowUser () {
const store = this.$store
store.state.api.backendInteractor.unfollowUser(this.user.id)
.then((unfollowedUser) => store.commit('addNewUsers', [unfollowedUser]))
} }
} }
} }

View File

@ -14,3 +14,20 @@
</template> </template>
<script src="./user_profile.js"></script> <script src="./user_profile.js"></script>
<style lang="scss">
.user-profile {
flex: 2;
flex-basis: 500px;
}
.user-info {
.following-info {
display: flex;
div {
flex: 1;
}
}
}
</style>

View File

@ -107,7 +107,7 @@ const mergeOrAdd = (arr, item) => {
const sortTimeline = (timeline) => { const sortTimeline = (timeline) => {
timeline.visibleStatuses = sortBy(timeline.visibleStatuses, ({id}) => -id) timeline.visibleStatuses = sortBy(timeline.visibleStatuses, ({id}) => -id)
timeline.statuses = sortBy(timeline.statuses, ({id}) => -id) timeline.statuses = sortBy(timeline.statuses, ({id}) => -id)
timeline.minVisibleId = (last(timeline.statuses) || {}).id timeline.minVisibleId = (last(timeline.visibleStatuses) || {}).id
return timeline return timeline
} }

View File

@ -1,6 +1,6 @@
import timelineFetcher from '../services/timeline_fetcher/timeline_fetcher.service.js' import timelineFetcher from '../services/timeline_fetcher/timeline_fetcher.service.js'
import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js' import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
import { each, find, merge } from 'lodash' import { compact, map, each, find, merge } from 'lodash'
// TODO: Unify with mergeOrAdd in statuses.js // TODO: Unify with mergeOrAdd in statuses.js
export const mergeOrAdd = (arr, item) => { export const mergeOrAdd = (arr, item) => {
@ -43,14 +43,10 @@ const users = {
mutations, mutations,
actions: { actions: {
addNewStatuses (store, { statuses }) { addNewStatuses (store, { statuses }) {
const users = statuses.reduce((acc, status) => { const users = map(statuses, 'user')
const addUsers = [status.user] const retweetedUsers = compact(map(statuses, 'retweeted_status.user'))
if (status.retweeted_status) {
addUsers.push(status.retweeted_status.user)
}
return acc.concat(addUsers)
}, [])
store.commit('addNewUsers', users) store.commit('addNewUsers', users)
store.commit('addNewUsers', retweetedUsers)
}, },
loginUser (store, userCredentials) { loginUser (store, userCredentials) {
const commit = store.commit const commit = store.commit
@ -85,4 +81,4 @@ const users = {
} }
} }
export default users export default users

View File

@ -14,6 +14,9 @@ const MEDIA_UPLOAD_URL = '/api/statusnet/media/upload'
const CONVERSATION_URL = '/api/statusnet/conversation' const CONVERSATION_URL = '/api/statusnet/conversation'
const MENTIONS_URL = '/api/statuses/mentions.json' const MENTIONS_URL = '/api/statuses/mentions.json'
const FRIENDS_URL = '/api/statuses/friends.json' const FRIENDS_URL = '/api/statuses/friends.json'
const FOLLOWING_URL = '/api/friendships/create.json'
const UNFOLLOWING_URL = '/api/friendships/destroy.json'
// const USER_URL = '/api/users/show.json'
const oldfetch = window.fetch const oldfetch = window.fetch
@ -31,6 +34,22 @@ const authHeaders = (user) => {
} }
} }
const followUser = ({id, credentials}) => {
let url = `${FOLLOWING_URL}?user_id=${id}`
return fetch(url, {
headers: authHeaders(credentials),
method: 'POST'
}).then((data) => data.json())
}
const unfollowUser = ({id, credentials}) => {
let url = `${UNFOLLOWING_URL}?user_id=${id}`
return fetch(url, {
headers: authHeaders(credentials),
method: 'POST'
}).then((data) => data.json())
}
const fetchFriends = ({credentials}) => { const fetchFriends = ({credentials}) => {
return fetch(FRIENDS_URL, { headers: authHeaders(credentials) }) return fetch(FRIENDS_URL, { headers: authHeaders(credentials) })
.then((data) => data.json()) .then((data) => data.json())
@ -158,6 +177,8 @@ const apiService = {
fetchStatus, fetchStatus,
fetchMentions, fetchMentions,
fetchFriends, fetchFriends,
followUser,
unfollowUser,
favorite, favorite,
unfavorite, unfavorite,
retweet, retweet,

View File

@ -17,11 +17,21 @@ const backendInteractorService = (credentials) => {
return apiService.fetchFriends({credentials}) return apiService.fetchFriends({credentials})
} }
const followUser = (id) => {
return apiService.followUser({credentials, id})
}
const unfollowUser = (id) => {
return apiService.unfollowUser({credentials, id})
}
const backendInteractorServiceInstance = { const backendInteractorServiceInstance = {
fetchStatus, fetchStatus,
fetchConversation, fetchConversation,
fetchMentions, fetchMentions,
fetchFriends, fetchFriends,
followUser,
unfollowUser,
verifyCredentials: apiService.verifyCredentials verifyCredentials: apiService.verifyCredentials
} }

View File

@ -136,18 +136,21 @@ describe('The Statuses module', () => {
it('keeps a descending by id order in timeline.visibleStatuses and timeline.statuses', () => { it('keeps a descending by id order in timeline.visibleStatuses and timeline.statuses', () => {
const state = cloneDeep(defaultState) const state = cloneDeep(defaultState)
const status = makeMockStatus({id: 2}) const nonVisibleStatus = makeMockStatus({id: 1})
const statusTwo = makeMockStatus({id: 1}) const status = makeMockStatus({id: 3})
const statusThree = makeMockStatus({id: 3}) const statusTwo = makeMockStatus({id: 2})
const statusThree = makeMockStatus({id: 4})
mutations.addNewStatuses(state, { statuses: [nonVisibleStatus], showImmediately: false, timeline: 'public' })
mutations.addNewStatuses(state, { statuses: [status], showImmediately: true, timeline: 'public' }) mutations.addNewStatuses(state, { statuses: [status], showImmediately: true, timeline: 'public' })
mutations.addNewStatuses(state, { statuses: [statusTwo], showImmediately: true, timeline: 'public' }) mutations.addNewStatuses(state, { statuses: [statusTwo], showImmediately: true, timeline: 'public' })
expect(state.timelines.public.minVisibleId).to.equal(1) expect(state.timelines.public.minVisibleId).to.equal(2)
mutations.addNewStatuses(state, { statuses: [statusThree], showImmediately: true, timeline: 'public' }) mutations.addNewStatuses(state, { statuses: [statusThree], showImmediately: true, timeline: 'public' })
expect(state.timelines.public.statuses).to.eql([statusThree, status, statusTwo]) expect(state.timelines.public.statuses).to.eql([statusThree, status, statusTwo, nonVisibleStatus])
expect(state.timelines.public.visibleStatuses).to.eql([statusThree, status, statusTwo]) expect(state.timelines.public.visibleStatuses).to.eql([statusThree, status, statusTwo])
}) })