From 2e59ab738b6991a0e6b0a0b9a6dafba41e16c929 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Thu, 7 Mar 2019 19:49:41 +0200 Subject: [PATCH 01/68] updates normalizer for proper user handling and adds support for friends tl via mastoapi --- src/services/api/api.service.js | 4 ++-- .../entity_normalizer.service.js | 15 ++++++++++++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 2de87026e6..fe2cb1c8b4 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -1,6 +1,5 @@ /* eslint-env browser */ const LOGIN_URL = '/api/account/verify_credentials.json' -const FRIENDS_TIMELINE_URL = '/api/statuses/friends_timeline.json' const ALL_FOLLOWING_URL = '/api/qvitter/allfollowing' const PUBLIC_TIMELINE_URL = '/api/statuses/public_timeline.json' const PUBLIC_AND_EXTERNAL_TIMELINE_URL = '/api/statuses/public_and_external_timeline.json' @@ -43,6 +42,7 @@ const DENY_USER_URL = '/api/pleroma/friendships/deny' const SUGGESTIONS_URL = '/api/v1/suggestions' const MASTODON_USER_FAVORITES_TIMELINE_URL = '/api/v1/favourites' +const MASTODON_USER_HOME_TIMELINE_URL = '/api/v1/timelines/home' import { each, map } from 'lodash' import { parseStatus, parseUser, parseNotification } from '../entity_normalizer/entity_normalizer.service.js' @@ -342,7 +342,7 @@ const setUserMute = ({id, credentials, muted = true}) => { const fetchTimeline = ({timeline, credentials, since = false, until = false, userId = false, tag = false}) => { const timelineUrls = { public: PUBLIC_TIMELINE_URL, - friends: FRIENDS_TIMELINE_URL, + friends: MASTODON_USER_HOME_TIMELINE_URL, mentions: MENTIONS_URL, dms: DM_TIMELINE_URL, notifications: QVITTER_USER_NOTIFICATIONS_URL, diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js index d20ce77f77..70f6d693af 100644 --- a/src/services/entity_normalizer/entity_normalizer.service.js +++ b/src/services/entity_normalizer/entity_normalizer.service.js @@ -60,9 +60,18 @@ export const parseUser = (data) => { if (data.pleroma) { const pleroma = data.pleroma - output.follows_you = pleroma.follows_you - output.statusnet_blocking = pleroma.statusnet_blocking - output.muted = pleroma.muted + const relationship = pleroma.relationship + output.follows_you = relationship.followed_by + output.statusnet_blocking = relationship.blocking + output.muted = relationship.muting + output.following = relationship.following + + // Unused: + // domain_blocking + // endorsed + // muting_notifications + // requested + // showing_reblogs } // Missing, trying to recover From c038d0c12a8cc2af9fd3d3c6f9e338ee0d7c4765 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Thu, 7 Mar 2019 20:04:29 +0200 Subject: [PATCH 02/68] undo this change since BE returns empty object for relationship, add in a separate MR --- .../entity_normalizer.service.js | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js index 70f6d693af..d20ce77f77 100644 --- a/src/services/entity_normalizer/entity_normalizer.service.js +++ b/src/services/entity_normalizer/entity_normalizer.service.js @@ -60,18 +60,9 @@ export const parseUser = (data) => { if (data.pleroma) { const pleroma = data.pleroma - const relationship = pleroma.relationship - output.follows_you = relationship.followed_by - output.statusnet_blocking = relationship.blocking - output.muted = relationship.muting - output.following = relationship.following - - // Unused: - // domain_blocking - // endorsed - // muting_notifications - // requested - // showing_reblogs + output.follows_you = pleroma.follows_you + output.statusnet_blocking = pleroma.statusnet_blocking + output.muted = pleroma.muted } // Missing, trying to recover From 8522063b2c4a35f0926517a3b75376c81131dd79 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Thu, 7 Mar 2019 20:16:35 +0200 Subject: [PATCH 03/68] switch public and TWKN to MastoAPI --- src/services/api/api.service.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 2de87026e6..f9b556c0e7 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -2,8 +2,6 @@ const LOGIN_URL = '/api/account/verify_credentials.json' const FRIENDS_TIMELINE_URL = '/api/statuses/friends_timeline.json' const ALL_FOLLOWING_URL = '/api/qvitter/allfollowing' -const PUBLIC_TIMELINE_URL = '/api/statuses/public_timeline.json' -const PUBLIC_AND_EXTERNAL_TIMELINE_URL = '/api/statuses/public_and_external_timeline.json' const TAG_TIMELINE_URL = '/api/statusnet/tags/timeline' const FAVORITE_URL = '/api/favorites/create' const UNFAVORITE_URL = '/api/favorites/destroy' @@ -43,6 +41,7 @@ const DENY_USER_URL = '/api/pleroma/friendships/deny' const SUGGESTIONS_URL = '/api/v1/suggestions' const MASTODON_USER_FAVORITES_TIMELINE_URL = '/api/v1/favourites' +const MASTODON_PUBLIC_TIMELINE = '/api/v1/timelines/public' import { each, map } from 'lodash' import { parseStatus, parseUser, parseNotification } from '../entity_normalizer/entity_normalizer.service.js' @@ -341,12 +340,12 @@ const setUserMute = ({id, credentials, muted = true}) => { const fetchTimeline = ({timeline, credentials, since = false, until = false, userId = false, tag = false}) => { const timelineUrls = { - public: PUBLIC_TIMELINE_URL, + public: MASTODON_PUBLIC_TIMELINE, friends: FRIENDS_TIMELINE_URL, mentions: MENTIONS_URL, dms: DM_TIMELINE_URL, notifications: QVITTER_USER_NOTIFICATIONS_URL, - 'publicAndExternal': PUBLIC_AND_EXTERNAL_TIMELINE_URL, + 'publicAndExternal': MASTODON_PUBLIC_TIMELINE, user: QVITTER_USER_TIMELINE_URL, media: QVITTER_USER_TIMELINE_URL, favorites: MASTODON_USER_FAVORITES_TIMELINE_URL, @@ -372,6 +371,12 @@ const fetchTimeline = ({timeline, credentials, since = false, until = false, use if (timeline === 'media') { params.push(['only_media', 1]) } + if (timeline === 'public') { + params.push(['local', true]) + } + if (timeline === 'public' || timeline === 'publicAndExternal') { + params.push(['only_media', false]) + } params.push(['count', 20]) From 6e2946f35290380cbd9e2147a570469f1be18ab6 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Thu, 7 Mar 2019 20:21:07 +0200 Subject: [PATCH 04/68] switch direct messages to mastoapi --- src/services/api/api.service.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 2de87026e6..65d0916f2a 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -15,7 +15,6 @@ const STATUS_URL = '/api/statuses/show' const MEDIA_UPLOAD_URL = '/api/statusnet/media/upload' const CONVERSATION_URL = '/api/statusnet/conversation' const MENTIONS_URL = '/api/statuses/mentions.json' -const DM_TIMELINE_URL = '/api/statuses/dm_timeline.json' const FOLLOWERS_URL = '/api/statuses/followers.json' const FRIENDS_URL = '/api/statuses/friends.json' const BLOCKS_URL = '/api/statuses/blocks.json' @@ -43,6 +42,7 @@ const DENY_USER_URL = '/api/pleroma/friendships/deny' const SUGGESTIONS_URL = '/api/v1/suggestions' const MASTODON_USER_FAVORITES_TIMELINE_URL = '/api/v1/favourites' +const MASTODON_DIRECT_MESSAGES_TIMELINE_URL = '/api/v1/timelines/direct' import { each, map } from 'lodash' import { parseStatus, parseUser, parseNotification } from '../entity_normalizer/entity_normalizer.service.js' @@ -344,7 +344,7 @@ const fetchTimeline = ({timeline, credentials, since = false, until = false, use public: PUBLIC_TIMELINE_URL, friends: FRIENDS_TIMELINE_URL, mentions: MENTIONS_URL, - dms: DM_TIMELINE_URL, + dms: MASTODON_DIRECT_MESSAGES_TIMELINE_URL, notifications: QVITTER_USER_NOTIFICATIONS_URL, 'publicAndExternal': PUBLIC_AND_EXTERNAL_TIMELINE_URL, user: QVITTER_USER_TIMELINE_URL, From 49b0f0a04a74039a1b82fbde731828e599123e93 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Sat, 9 Mar 2019 18:33:49 +0200 Subject: [PATCH 05/68] Fetching convos via MastoAPI. Had to change conversation component a bit for better support, since MastoAPI doesn't have coversation ids --- src/components/conversation/conversation.js | 23 +++++++------- src/services/api/api.service.js | 35 ++++++++++++++------- 2 files changed, 35 insertions(+), 23 deletions(-) diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index 48b8aaaa51..fd4303ca5a 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -25,7 +25,8 @@ const sortAndFilterConversation = (conversation) => { const conversation = { data () { return { - highlight: null + highlight: null, + relevantIds: [] } }, props: [ @@ -48,9 +49,11 @@ const conversation = { return [] } - const conversationId = this.status.statusnet_conversation_id - const statuses = this.$store.state.statuses.allStatuses - const conversation = filter(statuses, { statusnet_conversation_id: conversationId }) + const statusesObject = this.$store.state.statuses.allStatusesObject + const conversation = this.relevantIds.reduce((acc, id) => { + acc.push(statusesObject[id]) + return acc + }, []) return sortAndFilterConversation(conversation) }, replies () { @@ -83,15 +86,13 @@ const conversation = { methods: { fetchConversation () { if (this.status) { - const conversationId = this.status.statusnet_conversation_id + const conversationId = this.status.id this.$store.state.api.backendInteractor.fetchConversation({id: conversationId}) - .then((statuses) => this.$store.dispatch('addNewStatuses', { statuses })) + .then((statuses) => { + this.$store.dispatch('addNewStatuses', { statuses }) + statuses.forEach(status => this.relevantIds.push(status.id)) + }) .then(() => this.setHighlight(this.statusId)) - } else { - const id = this.$route.params.id - this.$store.state.api.backendInteractor.fetchStatus({id}) - .then((status) => this.$store.dispatch('addNewStatuses', { statuses: [status] })) - .then(() => this.fetchConversation()) } }, getReplies (id) { diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 2de87026e6..a273f32e2b 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -11,9 +11,7 @@ const RETWEET_URL = '/api/statuses/retweet' const UNRETWEET_URL = '/api/statuses/unretweet' const STATUS_UPDATE_URL = '/api/statuses/update.json' const STATUS_DELETE_URL = '/api/statuses/destroy' -const STATUS_URL = '/api/statuses/show' const MEDIA_UPLOAD_URL = '/api/statusnet/media/upload' -const CONVERSATION_URL = '/api/statusnet/conversation' const MENTIONS_URL = '/api/statuses/mentions.json' const DM_TIMELINE_URL = '/api/statuses/dm_timeline.json' const FOLLOWERS_URL = '/api/statuses/followers.json' @@ -43,6 +41,8 @@ const DENY_USER_URL = '/api/pleroma/friendships/deny' const SUGGESTIONS_URL = '/api/v1/suggestions' const MASTODON_USER_FAVORITES_TIMELINE_URL = '/api/v1/favourites' +const MASTODON_STATUS_URL = id => `/api/v1/statuses/${id}` +const MASTODON_STATUS_CONTEXT_URL = id => `/api/v1/statuses/${id}/context` import { each, map } from 'lodash' import { parseStatus, parseUser, parseNotification } from '../entity_normalizer/entity_normalizer.service.js' @@ -298,20 +298,31 @@ const fetchFollowRequests = ({credentials}) => { } const fetchConversation = ({id, credentials}) => { - let url = `${CONVERSATION_URL}/${id}.json?count=100` - return fetch(url, { headers: authHeaders(credentials) }) - .then((data) => { - if (data.ok) { - return data - } - throw new Error('Error fetching timeline', data) - }) - .then((data) => data.json()) + let url = MASTODON_STATUS_URL(id) + let urlContext = MASTODON_STATUS_CONTEXT_URL(id) + return Promise.all([ + fetch(url, { headers: authHeaders(credentials) }) + .then((data) => { + if (data.ok) { + return data + } + throw new Error('Error fetching timeline', data) + }) + .then((data) => data.json()), + fetch(urlContext, { headers: authHeaders(credentials) }) + .then((data) => { + if (data.ok) { + return data + } + throw new Error('Error fetching timeline', data) + }) + .then((data) => data.json())]) + .then(([status, context]) => [...context.ancestors, status, ...context.descendants]) .then((data) => data.map(parseStatus)) } const fetchStatus = ({id, credentials}) => { - let url = `${STATUS_URL}/${id}.json` + let url = MASTODON_STATUS_URL(id) return fetch(url, { headers: authHeaders(credentials) }) .then((data) => { if (data.ok) { From 0b1506a4c00c1482dd31ee43b17ee673e9f57bde Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Sun, 10 Mar 2019 18:05:51 +0200 Subject: [PATCH 06/68] wip support for follower/following, a bit broken and with regression --- src/services/api/api.service.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 2de87026e6..9cee75b9ad 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -16,8 +16,6 @@ const MEDIA_UPLOAD_URL = '/api/statusnet/media/upload' const CONVERSATION_URL = '/api/statusnet/conversation' const MENTIONS_URL = '/api/statuses/mentions.json' const DM_TIMELINE_URL = '/api/statuses/dm_timeline.json' -const FOLLOWERS_URL = '/api/statuses/followers.json' -const FRIENDS_URL = '/api/statuses/friends.json' const BLOCKS_URL = '/api/statuses/blocks.json' const FOLLOWING_URL = '/api/friendships/create.json' const UNFOLLOWING_URL = '/api/friendships/destroy.json' @@ -43,6 +41,8 @@ const DENY_USER_URL = '/api/pleroma/friendships/deny' const SUGGESTIONS_URL = '/api/v1/suggestions' const MASTODON_USER_FAVORITES_TIMELINE_URL = '/api/v1/favourites' +const MASTODON_FOLLOWING_URL = id => `/api/v1/accounts/${id}/following` +const MASTODON_FOLLOWERS_URL = id => `/api/v1/accounts/${id}/followers` import { each, map } from 'lodash' import { parseStatus, parseUser, parseNotification } from '../entity_normalizer/entity_normalizer.service.js' @@ -258,9 +258,9 @@ const fetchUser = ({id, credentials}) => { } const fetchFriends = ({id, page, credentials}) => { - let url = `${FRIENDS_URL}?user_id=${id}` + let url = MASTODON_FOLLOWING_URL(id) if (page) { - url = url + `&page=${page}` + url = url + `?page=${page}` } return fetch(url, { headers: authHeaders(credentials) }) .then((data) => data.json()) @@ -268,16 +268,16 @@ const fetchFriends = ({id, page, credentials}) => { } const exportFriends = ({id, credentials}) => { - let url = `${FRIENDS_URL}?user_id=${id}&all=true` + let url = MASTODON_FOLLOWING_URL(id) + `?all=true` return fetch(url, { headers: authHeaders(credentials) }) .then((data) => data.json()) .then((data) => data.map(parseUser)) } const fetchFollowers = ({id, page, credentials}) => { - let url = `${FOLLOWERS_URL}?user_id=${id}` + let url = MASTODON_FOLLOWERS_URL(id) if (page) { - url = url + `&page=${page}` + url = url + `?page=${page}` } return fetch(url, { headers: authHeaders(credentials) }) .then((data) => data.json()) From 4a5aef8883d3e92a58e272857cdfb303da61feb2 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Sun, 10 Mar 2019 19:15:07 +0200 Subject: [PATCH 07/68] basic user and status actions implemented --- src/modules/statuses.js | 14 ------ src/services/api/api.service.js | 86 +++++++++++++++++++++------------ 2 files changed, 56 insertions(+), 44 deletions(-) diff --git a/src/modules/statuses.js b/src/modules/statuses.js index 7571b62abd..a1ba87c410 100644 --- a/src/modules/statuses.js +++ b/src/modules/statuses.js @@ -429,13 +429,6 @@ const statuses = { // Optimistic favoriting... commit('setFavorited', { status, value: true }) apiService.favorite({ id: status.id, credentials: rootState.users.currentUser.credentials }) - .then(response => { - if (response.ok) { - return response.json() - } else { - return {} - } - }) .then(status => { commit('setFavoritedConfirm', { status }) }) @@ -444,13 +437,6 @@ const statuses = { // Optimistic favoriting... commit('setFavorited', { status, value: false }) apiService.unfavorite({ id: status.id, credentials: rootState.users.currentUser.credentials }) - .then(response => { - if (response.ok) { - return response.json() - } else { - return {} - } - }) .then(status => { commit('setFavoritedConfirm', { status }) }) diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 2de87026e6..e2f44a2b86 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -5,12 +5,7 @@ const ALL_FOLLOWING_URL = '/api/qvitter/allfollowing' const PUBLIC_TIMELINE_URL = '/api/statuses/public_timeline.json' const PUBLIC_AND_EXTERNAL_TIMELINE_URL = '/api/statuses/public_and_external_timeline.json' const TAG_TIMELINE_URL = '/api/statusnet/tags/timeline' -const FAVORITE_URL = '/api/favorites/create' -const UNFAVORITE_URL = '/api/favorites/destroy' -const RETWEET_URL = '/api/statuses/retweet' -const UNRETWEET_URL = '/api/statuses/unretweet' const STATUS_UPDATE_URL = '/api/statuses/update.json' -const STATUS_DELETE_URL = '/api/statuses/destroy' const STATUS_URL = '/api/statuses/show' const MEDIA_UPLOAD_URL = '/api/statusnet/media/upload' const CONVERSATION_URL = '/api/statusnet/conversation' @@ -19,9 +14,6 @@ const DM_TIMELINE_URL = '/api/statuses/dm_timeline.json' const FOLLOWERS_URL = '/api/statuses/followers.json' const FRIENDS_URL = '/api/statuses/friends.json' const BLOCKS_URL = '/api/statuses/blocks.json' -const FOLLOWING_URL = '/api/friendships/create.json' -const UNFOLLOWING_URL = '/api/friendships/destroy.json' -const QVITTER_USER_PREF_URL = '/api/qvitter/set_profile_pref.json' const REGISTRATION_URL = '/api/account/register.json' const AVATAR_UPDATE_URL = '/api/qvitter/update_avatar.json' const BG_UPDATE_URL = '/api/qvitter/update_background_image.json' @@ -31,8 +23,6 @@ const EXTERNAL_PROFILE_URL = '/api/externalprofile/show.json' const QVITTER_USER_TIMELINE_URL = '/api/qvitter/statuses/user_timeline.json' const QVITTER_USER_NOTIFICATIONS_URL = '/api/qvitter/statuses/notifications.json' const QVITTER_USER_NOTIFICATIONS_READ_URL = '/api/qvitter/statuses/notifications/read.json' -const BLOCKING_URL = '/api/blocks/create.json' -const UNBLOCKING_URL = '/api/blocks/destroy.json' const USER_URL = '/api/users/show.json' const FOLLOW_IMPORT_URL = '/api/pleroma/follow_import' const DELETE_ACCOUNT_URL = '/api/pleroma/delete_account' @@ -43,6 +33,17 @@ const DENY_USER_URL = '/api/pleroma/friendships/deny' const SUGGESTIONS_URL = '/api/v1/suggestions' const MASTODON_USER_FAVORITES_TIMELINE_URL = '/api/v1/favourites' +const MASTODON_FAVORITE_URL = id => `/api/v1/statuses/${id}/favourite` +const MASTODON_UNFAVORITE_URL = id => `/api/v1/statuses/${id}/unfavourite` +const MASTODON_RETWEET_URL = id => `/api/v1/statuses/${id}/reblog` +const MASTODON_UNRETWEET_URL = id => `/api/v1/statuses/${id}/unreblog` +const MASTODON_DELETE_URL = id => `/api/v1/statuses/${id}` +const MASTODON_FOLLOW_URL = id => `/api/v1/accounts/${id}/follow` +const MASTODON_UNFOLLOW_URL = id => `/api/v1/accounts/${id}/unfollow` +const MASTODON_BLOCK_URL = id => `/api/v1/accounts/${id}/block` +const MASTODON_UNBLOCK_URL = id => `/api/v1/accounts/${id}/unblock` +const MASTODON_MUTE_URL = id => `/api/v1/accounts/${id}/mute` +const MASTODON_UNMUTE_URL = id => `/api/v1/accounts/${id}/unmute` import { each, map } from 'lodash' import { parseStatus, parseUser, parseNotification } from '../entity_normalizer/entity_normalizer.service.js' @@ -195,7 +196,7 @@ const externalProfile = ({profileUrl, credentials}) => { } const followUser = ({id, credentials}) => { - let url = `${FOLLOWING_URL}?user_id=${id}` + let url = MASTODON_FOLLOW_URL(id) return fetch(url, { headers: authHeaders(credentials), method: 'POST' @@ -203,7 +204,7 @@ const followUser = ({id, credentials}) => { } const unfollowUser = ({id, credentials}) => { - let url = `${UNFOLLOWING_URL}?user_id=${id}` + let url = MASTODON_UNFOLLOW_URL(id) return fetch(url, { headers: authHeaders(credentials), method: 'POST' @@ -211,7 +212,7 @@ const unfollowUser = ({id, credentials}) => { } const blockUser = ({id, credentials}) => { - let url = `${BLOCKING_URL}?user_id=${id}` + let url = MASTODON_BLOCK_URL(id) return fetch(url, { headers: authHeaders(credentials), method: 'POST' @@ -219,7 +220,7 @@ const blockUser = ({id, credentials}) => { } const unblockUser = ({id, credentials}) => { - let url = `${UNBLOCKING_URL}?user_id=${id}` + let url = MASTODON_UNBLOCK_URL(id) return fetch(url, { headers: authHeaders(credentials), method: 'POST' @@ -324,18 +325,11 @@ const fetchStatus = ({id, credentials}) => { } const setUserMute = ({id, credentials, muted = true}) => { - const form = new FormData() + const url = muted ? MASTODON_MUTE_URL(id) : MASTODON_UNMUTE_URL(id) - const muteInteger = muted ? 1 : 0 - - form.append('namespace', 'qvitter') - form.append('data', muteInteger) - form.append('topic', `mute:${id}`) - - return fetch(QVITTER_USER_PREF_URL, { + return fetch(url, { method: 'POST', - headers: authHeaders(credentials), - body: form + headers: authHeaders(credentials) }) } @@ -407,31 +401,63 @@ const verifyCredentials = (user) => { } const favorite = ({ id, credentials }) => { - return fetch(`${FAVORITE_URL}/${id}.json`, { + return fetch(MASTODON_FAVORITE_URL(id), { headers: authHeaders(credentials), method: 'POST' }) + .then(response => { + if (response.ok) { + return response.json() + } else { + return {} + } + }) + .then((data) => parseStatus(data)) } const unfavorite = ({ id, credentials }) => { - return fetch(`${UNFAVORITE_URL}/${id}.json`, { + return fetch(MASTODON_UNFAVORITE_URL(id), { headers: authHeaders(credentials), method: 'POST' }) + .then(response => { + if (response.ok) { + return response.json() + } else { + return {} + } + }) + .then((data) => parseStatus(data)) } const retweet = ({ id, credentials }) => { - return fetch(`${RETWEET_URL}/${id}.json`, { + return fetch(MASTODON_RETWEET_URL(id), { headers: authHeaders(credentials), method: 'POST' }) + .then(response => { + if (response.ok) { + return response.json() + } else { + return {} + } + }) + .then((data) => parseStatus(data)) } const unretweet = ({ id, credentials }) => { - return fetch(`${UNRETWEET_URL}/${id}.json`, { + return fetch(MASTODON_UNRETWEET_URL(id), { headers: authHeaders(credentials), method: 'POST' }) + .then(response => { + if (response.ok) { + return response.json() + } else { + return {} + } + }) + .then((data) => parseStatus(data)) } const postStatus = ({credentials, status, spoilerText, visibility, sensitive, mediaIds, inReplyToStatusId, contentType, noAttachmentLinks}) => { @@ -468,9 +494,9 @@ const postStatus = ({credentials, status, spoilerText, visibility, sensitive, me } const deleteStatus = ({ id, credentials }) => { - return fetch(`${STATUS_DELETE_URL}/${id}.json`, { + return fetch(MASTODON_DELETE_URL(id), { headers: authHeaders(credentials), - method: 'POST' + method: 'DELETE' }) } From 4f455eefe5af2914d7dece40e027ed35ec8a21b3 Mon Sep 17 00:00:00 2001 From: dave Date: Mon, 11 Mar 2019 10:52:28 -0400 Subject: [PATCH 08/68] #433: do not remove the reply dialog --- src/components/conversation/conversation.js | 6 +++++- src/components/conversation/conversation.vue | 15 +++++++++----- src/components/status/status.js | 6 +++--- .../status_or_conversation.js | 6 +++++- .../status_or_conversation.vue | 20 +++++++++++++++++-- 5 files changed, 41 insertions(+), 12 deletions(-) diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index 48b8aaaa51..95e484cda6 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -30,7 +30,8 @@ const conversation = { }, props: [ 'statusoid', - 'collapsable' + 'collapsable', + 'replying' ], computed: { status () { @@ -102,6 +103,9 @@ const conversation = { }, setHighlight (id) { this.highlight = id + }, + toggleReplying () { + this.$emit('toggleReplying') } } } diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index 5528fef659..42d009c90d 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -10,14 +10,19 @@
- + class="status-fadein" + />
diff --git a/src/components/status/status.js b/src/components/status/status.js index 9e18fe151c..20ca86a6b7 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -25,11 +25,11 @@ const Status = { 'replies', 'isPreview', 'noHeading', - 'inlineExpanded' + 'inlineExpanded', + 'replying' ], data () { return { - replying: false, expanded: false, unmuted: false, userExpanded: false, @@ -307,7 +307,7 @@ const Status = { } }, toggleReplying () { - this.replying = !this.replying + this.$emit('toggleReplying') }, gotoOriginal (id) { // only handled by conversation, not status_or_conversation diff --git a/src/components/status_or_conversation/status_or_conversation.js b/src/components/status_or_conversation/status_or_conversation.js index 441552ca62..749f76651f 100644 --- a/src/components/status_or_conversation/status_or_conversation.js +++ b/src/components/status_or_conversation/status_or_conversation.js @@ -5,7 +5,8 @@ const statusOrConversation = { props: ['statusoid'], data () { return { - expanded: false + expanded: false, + replying: false } }, components: { @@ -15,6 +16,9 @@ const statusOrConversation = { methods: { toggleExpanded () { this.expanded = !this.expanded + }, + toggleReplying () { + this.replying = !this.replying } } } diff --git a/src/components/status_or_conversation/status_or_conversation.vue b/src/components/status_or_conversation/status_or_conversation.vue index 9647d5eb49..43a60c3a06 100644 --- a/src/components/status_or_conversation/status_or_conversation.vue +++ b/src/components/status_or_conversation/status_or_conversation.vue @@ -1,7 +1,23 @@ From 63d7c7bd80cf8028cdefee99c1cb75614385f96b Mon Sep 17 00:00:00 2001 From: dave Date: Mon, 11 Mar 2019 16:24:37 -0400 Subject: [PATCH 09/68] #433: persistency of status form --- src/components/conversation/conversation.js | 36 +++++++++++++------ src/components/conversation/conversation.vue | 31 +++++++++++----- src/components/status/status.js | 7 ++-- .../status_or_conversation.js | 26 -------------- .../status_or_conversation.vue | 30 ---------------- src/components/timeline/timeline.js | 4 +-- src/components/timeline/timeline.vue | 8 ++++- 7 files changed, 60 insertions(+), 82 deletions(-) delete mode 100644 src/components/status_or_conversation/status_or_conversation.js delete mode 100644 src/components/status_or_conversation/status_or_conversation.vue diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index 95e484cda6..4cae0bdb7c 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -1,4 +1,4 @@ -import { reduce, filter } from 'lodash' +import { reduce, filter, findIndex } from 'lodash' import Status from '../status/status.vue' const sortById = (a, b) => { @@ -25,13 +25,13 @@ const sortAndFilterConversation = (conversation) => { const conversation = { data () { return { - highlight: null + highlight: null, + expanded: false } }, props: [ 'statusoid', - 'collapsable', - 'replying' + 'collapsable' ], computed: { status () { @@ -49,9 +49,18 @@ const conversation = { return [] } + if (!this.expanded) { + return [this.status] + } + const conversationId = this.status.statusnet_conversation_id const statuses = this.$store.state.statuses.allStatuses const conversation = filter(statuses, { statusnet_conversation_id: conversationId }) + + const statusIndex = findIndex(conversation, { id: this.statusId }) + if (statusIndex !== -1) { + conversation[statusIndex] = this.status + } return sortAndFilterConversation(conversation) }, replies () { @@ -75,11 +84,13 @@ const conversation = { components: { Status }, - created () { - this.fetchConversation() - }, watch: { - '$route': 'fetchConversation' + '$route': 'fetchConversation', + expanded (value) { + if (value) { + this.fetchConversation() + } + } }, methods: { fetchConversation () { @@ -99,13 +110,16 @@ const conversation = { return this.replies[id] || [] }, focused (id) { - return id === this.statusId + return this.expanded && id === this.statusId }, setHighlight (id) { this.highlight = id }, - toggleReplying () { - this.$emit('toggleReplying') + getHighlight () { + return this.expanded ? this.highlight : null + }, + toggleExpanded () { + this.expanded = !this.expanded } } } diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index 42d009c90d..b208d540d0 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -1,9 +1,9 @@ + + diff --git a/src/components/status/status.js b/src/components/status/status.js index 20ca86a6b7..8e489704ff 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -25,11 +25,11 @@ const Status = { 'replies', 'isPreview', 'noHeading', - 'inlineExpanded', - 'replying' + 'inlineExpanded' ], data () { return { + replying: false, expanded: false, unmuted: false, userExpanded: false, @@ -307,10 +307,9 @@ const Status = { } }, toggleReplying () { - this.$emit('toggleReplying') + this.replying = !this.replying }, gotoOriginal (id) { - // only handled by conversation, not status_or_conversation if (this.inConversation) { this.$emit('goto', id) } diff --git a/src/components/status_or_conversation/status_or_conversation.js b/src/components/status_or_conversation/status_or_conversation.js deleted file mode 100644 index 749f76651f..0000000000 --- a/src/components/status_or_conversation/status_or_conversation.js +++ /dev/null @@ -1,26 +0,0 @@ -import Status from '../status/status.vue' -import Conversation from '../conversation/conversation.vue' - -const statusOrConversation = { - props: ['statusoid'], - data () { - return { - expanded: false, - replying: false - } - }, - components: { - Status, - Conversation - }, - methods: { - toggleExpanded () { - this.expanded = !this.expanded - }, - toggleReplying () { - this.replying = !this.replying - } - } -} - -export default statusOrConversation diff --git a/src/components/status_or_conversation/status_or_conversation.vue b/src/components/status_or_conversation/status_or_conversation.vue deleted file mode 100644 index 43a60c3a06..0000000000 --- a/src/components/status_or_conversation/status_or_conversation.vue +++ /dev/null @@ -1,30 +0,0 @@ - - - - - diff --git a/src/components/timeline/timeline.js b/src/components/timeline/timeline.js index c45f89470d..1da7d5cc52 100644 --- a/src/components/timeline/timeline.js +++ b/src/components/timeline/timeline.js @@ -1,6 +1,6 @@ import Status from '../status/status.vue' import timelineFetcher from '../../services/timeline_fetcher/timeline_fetcher.service.js' -import StatusOrConversation from '../status_or_conversation/status_or_conversation.vue' +import Conversation from '../conversation/conversation.vue' import { throttle } from 'lodash' const Timeline = { @@ -43,7 +43,7 @@ const Timeline = { }, components: { Status, - StatusOrConversation + Conversation }, created () { const store = this.$store diff --git a/src/components/timeline/timeline.vue b/src/components/timeline/timeline.vue index 8f28d65c78..e0a34bd1b2 100644 --- a/src/components/timeline/timeline.vue +++ b/src/components/timeline/timeline.vue @@ -16,7 +16,13 @@
- +
From 543cc0d285246c3353a05ca0fdda9d511af46276 Mon Sep 17 00:00:00 2001 From: dave Date: Sun, 17 Mar 2019 16:59:08 -0400 Subject: [PATCH 10/68] #442 - clean up Bio placeholder text --- src/i18n/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/en.json b/src/i18n/en.json index 01fe2fba21..0bf8b4e953 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -97,7 +97,7 @@ "new_captcha": "Click the image to get a new captcha", "username_placeholder": "e.g. lain", "fullname_placeholder": "e.g. Lain Iwakura", - "bio_placeholder": "e.g.\nHi, I'm Lain\nI’m an anime girl living in suburban Japan. You may know me from the Wired.", + "bio_placeholder": "e.g. \nHi, I'm Lain. \nI’m an anime girl living in suburban Japan. You may know me from the Wired.", "validations": { "username_required": "cannot be left blank", "fullname_required": "cannot be left blank", From 5717d971829c54e4387b05725658e652b69a8b17 Mon Sep 17 00:00:00 2001 From: dave Date: Mon, 18 Mar 2019 10:35:13 -0400 Subject: [PATCH 11/68] #442 - update placeholder linebreak --- src/components/registration/registration.js | 3 +++ src/components/registration/registration.vue | 2 +- src/i18n/en.json | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/components/registration/registration.js b/src/components/registration/registration.js index ab6cd64d9e..8dc0042087 100644 --- a/src/components/registration/registration.js +++ b/src/components/registration/registration.js @@ -35,6 +35,9 @@ const registration = { }, computed: { token () { return this.$route.params.token }, + bioPlaceholder () { + return this.$t('registration.bio_placeholder').replace(/\s*\n\s*/g, ' \n') + }, ...mapState({ registrationOpen: (state) => state.instance.registrationOpen, signedIn: (state) => !!state.users.currentUser, diff --git a/src/components/registration/registration.vue b/src/components/registration/registration.vue index e22b308df0..110b27bfca 100644 --- a/src/components/registration/registration.vue +++ b/src/components/registration/registration.vue @@ -45,7 +45,7 @@
- +
diff --git a/src/i18n/en.json b/src/i18n/en.json index 0bf8b4e953..e5139a4e05 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -97,7 +97,7 @@ "new_captcha": "Click the image to get a new captcha", "username_placeholder": "e.g. lain", "fullname_placeholder": "e.g. Lain Iwakura", - "bio_placeholder": "e.g. \nHi, I'm Lain. \nI’m an anime girl living in suburban Japan. You may know me from the Wired.", + "bio_placeholder": "e.g.\nHi, I'm Lain.\nI’m an anime girl living in suburban Japan. You may know me from the Wired.", "validations": { "username_required": "cannot be left blank", "fullname_required": "cannot be left blank", From 3255950b0e9a16f2a477d606b91d90bed8a6cef7 Mon Sep 17 00:00:00 2001 From: taehoon Date: Sun, 24 Feb 2019 03:02:04 -0500 Subject: [PATCH 12/68] Add mute/unmute featrue and mutes management tab --- src/components/mute_card/mute_card.js | 2 +- src/components/user_card/user_card.js | 20 ++++---- src/components/user_card/user_card.vue | 4 +- .../user_settings/user_settings.vue | 6 +++ src/modules/users.js | 32 ++++++++----- src/services/api/api.service.js | 46 +++++++++++++++++-- .../backend_interactor_service.js | 6 ++- src/utils/url.js | 9 ++++ 8 files changed, 94 insertions(+), 31 deletions(-) create mode 100644 src/utils/url.js diff --git a/src/components/mute_card/mute_card.js b/src/components/mute_card/mute_card.js index 65c9cfb5b0..5ef17b60f8 100644 --- a/src/components/mute_card/mute_card.js +++ b/src/components/mute_card/mute_card.js @@ -12,7 +12,7 @@ const MuteCard = { return this.$store.getters.findUser(this.userId) }, muted () { - return this.user.muted + return this.user.mastodonMuted } }, components: { diff --git a/src/components/user_card/user_card.js b/src/components/user_card/user_card.js index b07da6752f..61b784febf 100644 --- a/src/components/user_card/user_card.js +++ b/src/components/user_card/user_card.js @@ -121,21 +121,19 @@ export default { }) }, blockUser () { - const store = this.$store - store.state.api.backendInteractor.blockUser(this.user.id) - .then((blockedUser) => { - store.commit('addNewUsers', [blockedUser]) - store.commit('removeStatus', { timeline: 'friends', userId: this.user.id }) - store.commit('removeStatus', { timeline: 'public', userId: this.user.id }) - store.commit('removeStatus', { timeline: 'publicAndExternal', userId: this.user.id }) - }) + this.$store.dispatch('blockUser', this.user.id) }, unblockUser () { - const store = this.$store - store.state.api.backendInteractor.unblockUser(this.user.id) - .then((unblockedUser) => store.commit('addNewUsers', [unblockedUser])) + this.$store.dispatch('unblockUser', this.user.id) + }, + muteUser () { // Mastodon Mute + this.$store.dispatch('muteUser', this.user.id) + }, + unmuteUser () { // Mastodon Unmute + this.$store.dispatch('unmuteUser', this.user.id) }, toggleMute () { + // TODO: Pleroma mute/unmute, Need to migrate to the Mastodon API const store = this.$store store.commit('setMuted', {user: this.user, muted: !this.user.muted}) store.state.api.backendInteractor.setUserMute(this.user) diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue index f4114e6e60..3259d1c5fe 100644 --- a/src/components/user_card/user_card.vue +++ b/src/components/user_card/user_card.vue @@ -74,12 +74,12 @@
- - diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue index a112363892..c9e68808a8 100644 --- a/src/components/user_settings/user_settings.vue +++ b/src/components/user_settings/user_settings.vue @@ -192,6 +192,12 @@
+ +
+ + + +
diff --git a/src/modules/users.js b/src/modules/users.js index 1fe12fc825..9c89f34a4c 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -177,9 +177,14 @@ const users = { return blocks }) }, - blockUser (store, id) { - return store.rootState.api.backendInteractor.blockUser(id) - .then((user) => store.commit('addNewUsers', [user])) + blockUser (store, userId) { + return store.rootState.api.backendInteractor.blockUser(userId) + .then((blockedUser) => { + store.commit('addNewUsers', [blockedUser]) + store.commit('removeStatus', { timeline: 'friends', userId }) + store.commit('removeStatus', { timeline: 'public', userId }) + store.commit('removeStatus', { timeline: 'publicAndExternal', userId }) + }) }, unblockUser (store, id) { return store.rootState.api.backendInteractor.unblockUser(id) @@ -188,18 +193,26 @@ const users = { fetchMutes (store) { return store.rootState.api.backendInteractor.fetchMutes() .then((mutedUsers) => { - each(mutedUsers, (user) => { user.muted = true }) + each(mutedUsers, (user) => { user.mastodonMuted = true }) store.commit('addNewUsers', mutedUsers) store.commit('saveMutes', map(mutedUsers, 'id')) }) }, muteUser (store, id) { - return store.state.api.backendInteractor.setUserMute({ id, muted: true }) - .then((user) => store.commit('addNewUsers', [user])) + return store.rootState.api.backendInteractor.muteUser(id) + .then(() => { + const user = store.rootState.users.usersObject[id] + set(user, 'mastodonMuted', true) + store.commit('addNewUsers', [user]) + }) }, unmuteUser (store, id) { - return store.state.api.backendInteractor.setUserMute({ id, muted: false }) - .then((user) => store.commit('addNewUsers', [user])) + return store.rootState.api.backendInteractor.unmuteUser(id) + .then(() => { + const user = store.rootState.users.usersObject[id] + set(user, 'mastodonMuted', false) + store.commit('addNewUsers', [user]) + }) }, addFriends ({ rootState, commit }, fetchBy) { return new Promise((resolve, reject) => { @@ -350,9 +363,6 @@ const users = { // Start getting fresh posts. store.dispatch('startFetching', { timeline: 'friends' }) - // Get user mutes - store.dispatch('fetchMutes') - // Fetch our friends store.rootState.api.backendInteractor.fetchFriends({ id: user.id }) .then((friends) => commit('addNewUsers', friends)) diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 176f1c18dc..92abf94bc4 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -1,3 +1,5 @@ +import { generateUrl } from '../../utils/url' + /* eslint-env browser */ const LOGIN_URL = '/api/account/verify_credentials.json' const FRIENDS_TIMELINE_URL = '/api/statuses/friends_timeline.json' @@ -19,6 +21,9 @@ const DM_TIMELINE_URL = '/api/statuses/dm_timeline.json' const FOLLOWERS_URL = '/api/statuses/followers.json' const FRIENDS_URL = '/api/statuses/friends.json' const BLOCKS_URL = '/api/statuses/blocks.json' +const MUTES_URL = '/api/v1/mutes.json' +const MUTING_URL = '/api/v1/accounts/:id/mute' +const UNMUTING_URL = '/api/v1/accounts/:id/unmute' const FOLLOWING_URL = '/api/friendships/create.json' const UNFOLLOWING_URL = '/api/friendships/destroy.json' const QVITTER_USER_PREF_URL = '/api/qvitter/set_profile_pref.json' @@ -538,14 +543,43 @@ const changePassword = ({credentials, password, newPassword, newPasswordConfirma } const fetchMutes = ({credentials}) => { - const url = '/api/qvitter/mutes.json' - - return fetch(url, { + return fetch(MUTES_URL, { headers: authHeaders(credentials) - }).then((data) => data.json()) + }).then((data) => { + if (data.ok) { + return data.json() + } + throw new Error('Error fetching mutes', data) + }) } -const fetchBlocks = ({page, credentials}) => { +const muteUser = ({id, credentials}) => { + const url = generateUrl(MUTING_URL, { id }) + return fetch(url, { + headers: authHeaders(credentials), + method: 'POST' + }).then((data) => { + if (data.ok) { + return data.json() + } + throw new Error('Error muting', data) + }) +} + +const unmuteUser = ({id, credentials}) => { + const url = generateUrl(UNMUTING_URL, { id }) + return fetch(url, { + headers: authHeaders(credentials), + method: 'POST' + }).then((data) => { + if (data.ok) { + return data.json() + } + throw new Error('Error unmuting', data) + }) +} + +const fetchBlocks = ({credentials}) => { return fetch(BLOCKS_URL, { headers: authHeaders(credentials) }).then((data) => { @@ -620,6 +654,8 @@ const apiService = { fetchAllFollowing, setUserMute, fetchMutes, + muteUser, + unmuteUser, fetchBlocks, fetchOAuthTokens, revokeOAuthToken, diff --git a/src/services/backend_interactor_service/backend_interactor_service.js b/src/services/backend_interactor_service/backend_interactor_service.js index cbd0b73389..674fb4a49d 100644 --- a/src/services/backend_interactor_service/backend_interactor_service.js +++ b/src/services/backend_interactor_service/backend_interactor_service.js @@ -67,7 +67,9 @@ const backendInteractorService = (credentials) => { } const fetchMutes = () => apiService.fetchMutes({credentials}) - const fetchBlocks = (params) => apiService.fetchBlocks({credentials, ...params}) + const muteUser = (id) => apiService.muteUser({credentials, id}) + const unmuteUser = (id) => apiService.unmuteUser({credentials, id}) + const fetchBlocks = () => apiService.fetchBlocks({credentials}) const fetchFollowRequests = () => apiService.fetchFollowRequests({credentials}) const fetchOAuthTokens = () => apiService.fetchOAuthTokens({credentials}) const revokeOAuthToken = (id) => apiService.revokeOAuthToken({id, credentials}) @@ -102,6 +104,8 @@ const backendInteractorService = (credentials) => { startFetching, setUserMute, fetchMutes, + muteUser, + unmuteUser, fetchBlocks, fetchOAuthTokens, revokeOAuthToken, diff --git a/src/utils/url.js b/src/utils/url.js new file mode 100644 index 0000000000..79ea7394bc --- /dev/null +++ b/src/utils/url.js @@ -0,0 +1,9 @@ +// Generate url based on template +// Example: /api/v1/accounts/:id/mute -> /api/v1/accounts/123/mute +export const generateUrl = (template, params = {}) => { + let url = template + Object.entries(params).forEach(([key, value]) => { + url = url.replace(':' + key, value) + }) + return url +} From f04cbc887595e842ab8b9dad6545f6e409e52ea1 Mon Sep 17 00:00:00 2001 From: taehoon Date: Sun, 24 Feb 2019 03:20:11 -0500 Subject: [PATCH 13/68] Add mute/unmute mutations --- src/modules/users.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/modules/users.js b/src/modules/users.js index 9c89f34a4c..f53245a245 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -108,6 +108,14 @@ export const mutations = { saveMutes (state, muteIds) { state.currentUser.muteIds = muteIds }, + muteUser (state, id) { + const user = state.usersObject[id] + set(user, 'mastodonMuted', true) + }, + unmuteUser (state, id) { + const user = state.usersObject[id] + set(user, 'mastodonMuted', false) + }, setUserForStatus (state, status) { status.user = state.usersObject[status.user.id] }, @@ -200,19 +208,11 @@ const users = { }, muteUser (store, id) { return store.rootState.api.backendInteractor.muteUser(id) - .then(() => { - const user = store.rootState.users.usersObject[id] - set(user, 'mastodonMuted', true) - store.commit('addNewUsers', [user]) - }) + .then(() => store.commit('muteUser', id)) }, unmuteUser (store, id) { return store.rootState.api.backendInteractor.unmuteUser(id) - .then(() => { - const user = store.rootState.users.usersObject[id] - set(user, 'mastodonMuted', false) - store.commit('addNewUsers', [user]) - }) + .then(() => store.commit('unmuteUser', id)) }, addFriends ({ rootState, commit }, fetchBy) { return new Promise((resolve, reject) => { From 859ed4f34feffd4a975e6d865f82bc57224025f9 Mon Sep 17 00:00:00 2001 From: taehoon Date: Sun, 24 Feb 2019 03:21:21 -0500 Subject: [PATCH 14/68] Fetct full data of muted users after fetchMutes api call --- src/modules/users.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/modules/users.js b/src/modules/users.js index f53245a245..af40be3d84 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -200,6 +200,11 @@ const users = { }, fetchMutes (store) { return store.rootState.api.backendInteractor.fetchMutes() + .then((mutes) => { + // fetchMutes api doesn't return full user data, let's fetch full user data using separate api calls + const promises = mutes.map(({ id }) => store.rootState.api.backendInteractor.fetchUser({ id })) + return Promise.all(promises) + }) .then((mutedUsers) => { each(mutedUsers, (user) => { user.mastodonMuted = true }) store.commit('addNewUsers', mutedUsers) From a6ce191cbcc34f309e9c4d4e3f7e3c11ed020821 Mon Sep 17 00:00:00 2001 From: taehoon Date: Thu, 28 Feb 2019 22:59:26 -0500 Subject: [PATCH 15/68] Update MuteCard ui --- src/components/mute_card/mute_card.vue | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/components/mute_card/mute_card.vue b/src/components/mute_card/mute_card.vue index e1bfe20be7..a4edff0368 100644 --- a/src/components/mute_card/mute_card.vue +++ b/src/components/mute_card/mute_card.vue @@ -1,6 +1,6 @@ - \ No newline at end of file + + + From 302310a653083bc82226cf0743d52fc02c277a8a Mon Sep 17 00:00:00 2001 From: taehoon Date: Fri, 1 Mar 2019 11:37:34 -0500 Subject: [PATCH 16/68] Remove old muting logic --- src/components/mute_card/mute_card.js | 2 +- src/components/user_card/user_card.js | 10 ++-------- src/modules/users.js | 12 +++++++++--- src/services/api/api.service.js | 18 ------------------ .../backend_interactor_service.js | 5 ----- 5 files changed, 12 insertions(+), 35 deletions(-) diff --git a/src/components/mute_card/mute_card.js b/src/components/mute_card/mute_card.js index 5ef17b60f8..65c9cfb5b0 100644 --- a/src/components/mute_card/mute_card.js +++ b/src/components/mute_card/mute_card.js @@ -12,7 +12,7 @@ const MuteCard = { return this.$store.getters.findUser(this.userId) }, muted () { - return this.user.mastodonMuted + return this.user.muted } }, components: { diff --git a/src/components/user_card/user_card.js b/src/components/user_card/user_card.js index 61b784febf..197c61d521 100644 --- a/src/components/user_card/user_card.js +++ b/src/components/user_card/user_card.js @@ -126,18 +126,12 @@ export default { unblockUser () { this.$store.dispatch('unblockUser', this.user.id) }, - muteUser () { // Mastodon Mute + muteUser () { this.$store.dispatch('muteUser', this.user.id) }, - unmuteUser () { // Mastodon Unmute + unmuteUser () { this.$store.dispatch('unmuteUser', this.user.id) }, - toggleMute () { - // TODO: Pleroma mute/unmute, Need to migrate to the Mastodon API - const store = this.$store - store.commit('setMuted', {user: this.user, muted: !this.user.muted}) - store.state.api.backendInteractor.setUserMute(this.user) - }, setProfileView (v) { if (this.switcher) { const store = this.$store diff --git a/src/modules/users.js b/src/modules/users.js index af40be3d84..5e53aafb48 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -110,11 +110,11 @@ export const mutations = { }, muteUser (state, id) { const user = state.usersObject[id] - set(user, 'mastodonMuted', true) + set(user, 'muted', true) }, unmuteUser (state, id) { const user = state.usersObject[id] - set(user, 'mastodonMuted', false) + set(user, 'muted', false) }, setUserForStatus (state, status) { status.user = state.usersObject[status.user.id] @@ -206,9 +206,10 @@ const users = { return Promise.all(promises) }) .then((mutedUsers) => { - each(mutedUsers, (user) => { user.mastodonMuted = true }) + each(mutedUsers, (user) => { user.muted = true }) store.commit('addNewUsers', mutedUsers) store.commit('saveMutes', map(mutedUsers, 'id')) + // TODO: Unset muted property of the rest users }) }, muteUser (store, id) { @@ -368,6 +369,11 @@ const users = { // Start getting fresh posts. store.dispatch('startFetching', { timeline: 'friends' }) + // Fetch mutes + // TODO: We should not show timeline until fetchMutes is resolved + // However, we can get rid of this logic totally if we can know user muted state from user object + store.dispatch('fetchMutes') + // Fetch our friends store.rootState.api.backendInteractor.fetchFriends({ id: user.id }) .then((friends) => commit('addNewUsers', friends)) diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 92abf94bc4..7da2758a6e 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -26,7 +26,6 @@ const MUTING_URL = '/api/v1/accounts/:id/mute' const UNMUTING_URL = '/api/v1/accounts/:id/unmute' const FOLLOWING_URL = '/api/friendships/create.json' const UNFOLLOWING_URL = '/api/friendships/destroy.json' -const QVITTER_USER_PREF_URL = '/api/qvitter/set_profile_pref.json' const REGISTRATION_URL = '/api/account/register.json' const AVATAR_UPDATE_URL = '/api/qvitter/update_avatar.json' const BG_UPDATE_URL = '/api/qvitter/update_background_image.json' @@ -343,22 +342,6 @@ const fetchStatus = ({id, credentials}) => { .then((data) => parseStatus(data)) } -const setUserMute = ({id, credentials, muted = true}) => { - const form = new FormData() - - const muteInteger = muted ? 1 : 0 - - form.append('namespace', 'qvitter') - form.append('data', muteInteger) - form.append('topic', `mute:${id}`) - - return fetch(QVITTER_USER_PREF_URL, { - method: 'POST', - headers: authHeaders(credentials), - body: form - }) -} - const fetchTimeline = ({timeline, credentials, since = false, until = false, userId = false, tag = false}) => { const timelineUrls = { public: PUBLIC_TIMELINE_URL, @@ -652,7 +635,6 @@ const apiService = { deleteStatus, uploadMedia, fetchAllFollowing, - setUserMute, fetchMutes, muteUser, unmuteUser, diff --git a/src/services/backend_interactor_service/backend_interactor_service.js b/src/services/backend_interactor_service/backend_interactor_service.js index 674fb4a49d..0f0bcddc41 100644 --- a/src/services/backend_interactor_service/backend_interactor_service.js +++ b/src/services/backend_interactor_service/backend_interactor_service.js @@ -62,10 +62,6 @@ const backendInteractorService = (credentials) => { return timelineFetcherService.startFetching({timeline, store, credentials, userId, tag}) } - const setUserMute = ({id, muted = true}) => { - return apiService.setUserMute({id, muted, credentials}) - } - const fetchMutes = () => apiService.fetchMutes({credentials}) const muteUser = (id) => apiService.muteUser({credentials, id}) const unmuteUser = (id) => apiService.unmuteUser({credentials, id}) @@ -102,7 +98,6 @@ const backendInteractorService = (credentials) => { fetchAllFollowing, verifyCredentials: apiService.verifyCredentials, startFetching, - setUserMute, fetchMutes, muteUser, unmuteUser, From 9b690209d056d82fa855b682ffeb74378128460e Mon Sep 17 00:00:00 2001 From: taehoon Date: Fri, 1 Mar 2019 13:30:01 -0500 Subject: [PATCH 17/68] Reset old mutes state after fetching new mutes data --- src/modules/users.js | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/modules/users.js b/src/modules/users.js index 5e53aafb48..668ebabdf9 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -102,10 +102,15 @@ export const mutations = { } }) }, - saveBlocks (state, blockIds) { + saveBlockIds (state, blockIds) { state.currentUser.blockIds = blockIds }, - saveMutes (state, muteIds) { + updateMutes (state, mutedUsers) { + each(state.users, (user) => { user.muted = false }) + const newUsers = map(mutedUsers, (user) => ({ ...user, muted: true })) + each(newUsers, (user) => mergeOrAdd(state.users, state.usersObject, user)) + }, + saveMuteIds (state, muteIds) { state.currentUser.muteIds = muteIds }, muteUser (state, id) { @@ -180,7 +185,7 @@ const users = { fetchBlocks (store) { return store.rootState.api.backendInteractor.fetchBlocks() .then((blocks) => { - store.commit('saveBlocks', map(blocks, 'id')) + store.commit('saveBlockIds', map(blocks, 'id')) store.commit('addNewUsers', blocks) return blocks }) @@ -205,11 +210,10 @@ const users = { const promises = mutes.map(({ id }) => store.rootState.api.backendInteractor.fetchUser({ id })) return Promise.all(promises) }) - .then((mutedUsers) => { - each(mutedUsers, (user) => { user.muted = true }) - store.commit('addNewUsers', mutedUsers) - store.commit('saveMutes', map(mutedUsers, 'id')) - // TODO: Unset muted property of the rest users + .then((mutes) => { + store.commit('updateMutes', mutes) + store.commit('saveMuteIds', map(mutes, 'id')) + return mutes }) }, muteUser (store, id) { From d65422a6a59e0126201f71d0ca42343c075da54e Mon Sep 17 00:00:00 2001 From: taehoon Date: Fri, 1 Mar 2019 22:47:07 -0500 Subject: [PATCH 18/68] Improve fetch error handling using a util --- src/services/api/api.service.js | 54 +++++++++++---------------------- 1 file changed, 18 insertions(+), 36 deletions(-) diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 7da2758a6e..b7fcf510dd 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -64,6 +64,19 @@ let fetch = (url, options) => { return oldfetch(fullUrl, options) } +const promisedRequest = (url, options) => { + return fetch(url, options) + .then((response) => { + return new Promise((resolve, reject) => response.json() + .then((json) => { + if (!response.ok) { + return reject(new StatusCodeError(response.status, json, { url, options }, response)) + } + return resolve(json) + })) + }) +} + // Params // cropH // cropW @@ -249,16 +262,7 @@ const denyUser = ({id, credentials}) => { const fetchUser = ({id, credentials}) => { let url = `${MASTODON_USER_URL}/${id}` - return fetch(url, { headers: authHeaders(credentials) }) - .then((response) => { - return new Promise((resolve, reject) => response.json() - .then((json) => { - if (!response.ok) { - return reject(new StatusCodeError(response.status, json, { url }, response)) - } - return resolve(json) - })) - }) + return promisedRequest(url, { headers: authHeaders(credentials) }) .then((data) => parseUser(data)) } @@ -526,50 +530,28 @@ const changePassword = ({credentials, password, newPassword, newPasswordConfirma } const fetchMutes = ({credentials}) => { - return fetch(MUTES_URL, { - headers: authHeaders(credentials) - }).then((data) => { - if (data.ok) { - return data.json() - } - throw new Error('Error fetching mutes', data) - }) + return promisedRequest(MUTES_URL, { headers: authHeaders(credentials) }) } const muteUser = ({id, credentials}) => { const url = generateUrl(MUTING_URL, { id }) - return fetch(url, { + return promisedRequest(url, { headers: authHeaders(credentials), method: 'POST' - }).then((data) => { - if (data.ok) { - return data.json() - } - throw new Error('Error muting', data) }) } const unmuteUser = ({id, credentials}) => { const url = generateUrl(UNMUTING_URL, { id }) - return fetch(url, { + return promisedRequest(url, { headers: authHeaders(credentials), method: 'POST' - }).then((data) => { - if (data.ok) { - return data.json() - } - throw new Error('Error unmuting', data) }) } const fetchBlocks = ({credentials}) => { - return fetch(BLOCKS_URL, { + return promisedRequest(BLOCKS_URL, { headers: authHeaders(credentials) - }).then((data) => { - if (data.ok) { - return data.json() - } - throw new Error('Error fetching blocks', data) }) } From 333afd213892087f5047fabdc2cc21b70ddcf76d Mon Sep 17 00:00:00 2001 From: taehoon Date: Fri, 1 Mar 2019 22:50:06 -0500 Subject: [PATCH 19/68] Minor code refactoring --- src/services/api/api.service.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index b7fcf510dd..14bc919f67 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -1,5 +1,3 @@ -import { generateUrl } from '../../utils/url' - /* eslint-env browser */ const LOGIN_URL = '/api/account/verify_credentials.json' const FRIENDS_TIMELINE_URL = '/api/statuses/friends_timeline.json' @@ -53,6 +51,7 @@ import { each, map } from 'lodash' import { parseStatus, parseUser, parseNotification } from '../entity_normalizer/entity_normalizer.service.js' import 'whatwg-fetch' import { StatusCodeError } from '../errors/errors' +import { generateUrl } from '../../utils/url' const oldfetch = window.fetch From 300259fd976bb4496c5e1cca0df9bd47a1947259 Mon Sep 17 00:00:00 2001 From: taehoon Date: Sat, 2 Mar 2019 06:47:05 -0500 Subject: [PATCH 20/68] Add todo comment --- src/modules/users.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/modules/users.js b/src/modules/users.js index 668ebabdf9..790463714d 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -107,6 +107,7 @@ export const mutations = { }, updateMutes (state, mutedUsers) { each(state.users, (user) => { user.muted = false }) + // TODO: Remove this line once we get muted property of user object from the api const newUsers = map(mutedUsers, (user) => ({ ...user, muted: true })) each(newUsers, (user) => mergeOrAdd(state.users, state.usersObject, user)) }, From 9857002bf5dc902302644e981712b611124a5845 Mon Sep 17 00:00:00 2001 From: taehoon Date: Sat, 2 Mar 2019 08:07:14 -0500 Subject: [PATCH 21/68] Add hideMutedPosts setting and wire up to post-returning endpoints --- src/boot/after_store.js | 1 + src/components/settings/settings.js | 8 ++++++++ src/components/settings/settings.vue | 4 ++++ src/i18n/en.json | 1 + src/modules/config.js | 1 + src/modules/instance.js | 1 + src/services/api/api.service.js | 3 ++- src/services/timeline_fetcher/timeline_fetcher.service.js | 4 ++++ 8 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/boot/after_store.js b/src/boot/after_store.js index a5f8c97875..f5e84cbc67 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -97,6 +97,7 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => { copyInstanceOption('showInstanceSpecificPanel') copyInstanceOption('scopeOptionsEnabled') copyInstanceOption('formattingOptionsEnabled') + copyInstanceOption('hideMutedPosts') copyInstanceOption('collapseMessageWithSubject') copyInstanceOption('loginMethod') copyInstanceOption('scopeCopy') diff --git a/src/components/settings/settings.js b/src/components/settings/settings.js index b77c5197b5..1d5f75ed64 100644 --- a/src/components/settings/settings.js +++ b/src/components/settings/settings.js @@ -47,6 +47,11 @@ const settings = { pauseOnUnfocusedLocal: user.pauseOnUnfocused, hoverPreviewLocal: user.hoverPreview, + hideMutedPostsLocal: typeof user.hideMutedPosts === 'undefined' + ? instance.hideMutedPosts + : user.hideMutedPosts, + hideMutedPostsDefault: this.$t('settings.values.' + instance.hideMutedPosts), + collapseMessageWithSubjectLocal: typeof user.collapseMessageWithSubject === 'undefined' ? instance.collapseMessageWithSubject : user.collapseMessageWithSubject, @@ -177,6 +182,9 @@ const settings = { value = filter(value.split('\n'), (word) => trim(word).length > 0) this.$store.dispatch('setOption', { name: 'muteWords', value }) }, + hideMutedPostsLocal (value) { + this.$store.dispatch('setOption', { name: 'hideMutedPosts', value }) + }, collapseMessageWithSubjectLocal (value) { this.$store.dispatch('setOption', { name: 'collapseMessageWithSubject', value }) }, diff --git a/src/components/settings/settings.vue b/src/components/settings/settings.vue index 17f1f1a1cb..33dad549cd 100644 --- a/src/components/settings/settings.vue +++ b/src/components/settings/settings.vue @@ -36,6 +36,10 @@

{{$t('nav.timeline')}}

    +
  • + + +
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 8586f993cb..a15cecaf7e 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -9,10 +9,8 @@ const FAVORITE_URL = '/api/favorites/create' const UNFAVORITE_URL = '/api/favorites/destroy' const RETWEET_URL = '/api/statuses/retweet' const UNRETWEET_URL = '/api/statuses/unretweet' -const STATUS_UPDATE_URL = '/api/statuses/update.json' const STATUS_DELETE_URL = '/api/statuses/destroy' const STATUS_URL = '/api/statuses/show' -const MEDIA_UPLOAD_URL = '/api/statusnet/media/upload' const CONVERSATION_URL = '/api/statusnet/conversation' const MENTIONS_URL = '/api/statuses/mentions.json' const DM_TIMELINE_URL = '/api/statuses/dm_timeline.json' @@ -46,6 +44,8 @@ const MASTODON_BLOCK_USER_URL = id => `/api/v1/accounts/${id}/block` const MASTODON_UNBLOCK_USER_URL = id => `/api/v1/accounts/${id}/unblock` const MASTODON_MUTE_USER_URL = id => `/api/v1/accounts/${id}/mute` const MASTODON_UNMUTE_USER_URL = id => `/api/v1/accounts/${id}/unmute` +const MASTODON_POST_STATUS_URL = '/api/v1/statuses' +const MASTODON_MEDIA_UPLOAD_URL = '/api/v1/media' import { each, map } from 'lodash' import { parseStatus, parseUser, parseNotification } from '../entity_normalizer/entity_normalizer.service.js' @@ -439,23 +439,25 @@ const unretweet = ({ id, credentials }) => { }) } -const postStatus = ({credentials, status, spoilerText, visibility, sensitive, mediaIds, inReplyToStatusId, contentType, noAttachmentLinks}) => { - const idsText = mediaIds.join(',') +const postStatus = ({credentials, status, spoilerText, visibility, sensitive, mediaIds, inReplyToStatusId, contentType}) => { const form = new FormData() form.append('status', status) form.append('source', 'Pleroma FE') - if (noAttachmentLinks) form.append('no_attachment_links', noAttachmentLinks) if (spoilerText) form.append('spoiler_text', spoilerText) if (visibility) form.append('visibility', visibility) if (sensitive) form.append('sensitive', sensitive) if (contentType) form.append('content_type', contentType) - form.append('media_ids', idsText) + if (mediaIds) { + mediaIds.forEach(val => { + form.append('media_ids[]', val) + }) + } if (inReplyToStatusId) { - form.append('in_reply_to_status_id', inReplyToStatusId) + form.append('in_reply_to_id', inReplyToStatusId) } - return fetch(STATUS_UPDATE_URL, { + return fetch(MASTODON_POST_STATUS_URL, { body: form, method: 'POST', headers: authHeaders(credentials) @@ -480,13 +482,12 @@ const deleteStatus = ({ id, credentials }) => { } const uploadMedia = ({formData, credentials}) => { - return fetch(MEDIA_UPLOAD_URL, { + return fetch(MASTODON_MEDIA_UPLOAD_URL, { body: formData, method: 'POST', headers: authHeaders(credentials) }) - .then((response) => response.text()) - .then((text) => (new DOMParser()).parseFromString(text, 'application/xml')) + .then((response) => response.json()) } const followImport = ({params, credentials}) => { diff --git a/src/services/status_poster/status_poster.service.js b/src/services/status_poster/status_poster.service.js index f1932bb613..e70b0f2648 100644 --- a/src/services/status_poster/status_poster.service.js +++ b/src/services/status_poster/status_poster.service.js @@ -4,7 +4,7 @@ import apiService from '../api/api.service.js' const postStatus = ({ store, status, spoilerText, visibility, sensitive, media = [], inReplyToStatusId = undefined, contentType = 'text/plain' }) => { const mediaIds = map(media, 'id') - return apiService.postStatus({credentials: store.state.users.currentUser.credentials, status, spoilerText, visibility, sensitive, mediaIds, inReplyToStatusId, contentType, noAttachmentLinks: store.state.instance.noAttachmentLinks}) + return apiService.postStatus({credentials: store.state.users.currentUser.credentials, status, spoilerText, visibility, sensitive, mediaIds, inReplyToStatusId, contentType}) .then((data) => { if (!data.error) { store.dispatch('addNewStatuses', { @@ -26,25 +26,7 @@ const postStatus = ({ store, status, spoilerText, visibility, sensitive, media = const uploadMedia = ({ store, formData }) => { const credentials = store.state.users.currentUser.credentials - return apiService.uploadMedia({ credentials, formData }).then((xml) => { - // Firefox and Chrome treat method differently... - let link = xml.getElementsByTagName('link') - - if (link.length === 0) { - link = xml.getElementsByTagName('atom:link') - } - - link = link[0] - - const mediaData = { - id: xml.getElementsByTagName('media_id')[0].textContent, - url: xml.getElementsByTagName('media_url')[0].textContent, - image: link.getAttribute('href'), - mimetype: link.getAttribute('type') - } - - return mediaData - }) + return apiService.uploadMedia({ credentials, formData }) } const statusPosterService = { From 966add1b2996b87019051d8c924edb4af0bafb80 Mon Sep 17 00:00:00 2001 From: taehoon Date: Sun, 17 Mar 2019 23:22:54 -0400 Subject: [PATCH 39/68] Set default parameter --- src/services/api/api.service.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index a15cecaf7e..079462f718 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -48,7 +48,7 @@ const MASTODON_POST_STATUS_URL = '/api/v1/statuses' const MASTODON_MEDIA_UPLOAD_URL = '/api/v1/media' import { each, map } from 'lodash' -import { parseStatus, parseUser, parseNotification } from '../entity_normalizer/entity_normalizer.service.js' +import { parseStatus, parseUser, parseNotification, parseAttachment } from '../entity_normalizer/entity_normalizer.service.js' import 'whatwg-fetch' import { StatusCodeError } from '../errors/errors' @@ -439,7 +439,7 @@ const unretweet = ({ id, credentials }) => { }) } -const postStatus = ({credentials, status, spoilerText, visibility, sensitive, mediaIds, inReplyToStatusId, contentType}) => { +const postStatus = ({credentials, status, spoilerText, visibility, sensitive, mediaIds = [], inReplyToStatusId, contentType}) => { const form = new FormData() form.append('status', status) @@ -448,11 +448,9 @@ const postStatus = ({credentials, status, spoilerText, visibility, sensitive, me if (visibility) form.append('visibility', visibility) if (sensitive) form.append('sensitive', sensitive) if (contentType) form.append('content_type', contentType) - if (mediaIds) { - mediaIds.forEach(val => { - form.append('media_ids[]', val) - }) - } + mediaIds.forEach(val => { + form.append('media_ids[]', val) + }) if (inReplyToStatusId) { form.append('in_reply_to_id', inReplyToStatusId) } @@ -487,7 +485,8 @@ const uploadMedia = ({formData, credentials}) => { method: 'POST', headers: authHeaders(credentials) }) - .then((response) => response.json()) + .then((data) => data.json()) + .then((data) => parseAttachment(data)) } const followImport = ({params, credentials}) => { From 909c315a44a5c6272f000325e5e9c8b33e75f873 Mon Sep 17 00:00:00 2001 From: taehoon Date: Sun, 17 Mar 2019 23:23:59 -0400 Subject: [PATCH 40/68] Get correct mimetype through entity_normalizer --- src/components/post_status_form/post_status_form.js | 2 +- src/services/entity_normalizer/entity_normalizer.service.js | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js index 142d9d9036..1f0df35a0f 100644 --- a/src/components/post_status_form/post_status_form.js +++ b/src/components/post_status_form/post_status_form.js @@ -292,7 +292,7 @@ const PostStatusForm = { this.submitDisabled = false }, type (fileInfo) { - return fileTypeService.fileType(fileInfo.pleroma.mime_type) + return fileTypeService.fileType(fileInfo.mimetype) }, paste (e) { if (e.clipboardData.files.length > 0) { diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js index 57a6adf926..0d653207a2 100644 --- a/src/services/entity_normalizer/entity_normalizer.service.js +++ b/src/services/entity_normalizer/entity_normalizer.service.js @@ -128,13 +128,12 @@ export const parseUser = (data) => { return output } -const parseAttachment = (data) => { +export const parseAttachment = (data) => { const output = {} const masto = !data.hasOwnProperty('oembed') if (masto) { - // Not exactly same... - output.mimetype = data.type + output.mimetype = data.pleroma.mime_type output.meta = data.meta // not present in BE yet } else { output.mimetype = data.mimetype From 6fdbc182ca722dbb12d92e4bf26e841ccda8303d Mon Sep 17 00:00:00 2001 From: taehoon Date: Sun, 17 Mar 2019 23:42:07 -0400 Subject: [PATCH 41/68] Add fallback for attachments uploaded via the other platforms --- src/services/entity_normalizer/entity_normalizer.service.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js index 0d653207a2..35c28ec06d 100644 --- a/src/services/entity_normalizer/entity_normalizer.service.js +++ b/src/services/entity_normalizer/entity_normalizer.service.js @@ -133,7 +133,8 @@ export const parseAttachment = (data) => { const masto = !data.hasOwnProperty('oembed') if (masto) { - output.mimetype = data.pleroma.mime_type + // Not exactly same... + output.mimetype = data.pleroma ? data.pleroma.mime_type : data.type output.meta = data.meta // not present in BE yet } else { output.mimetype = data.mimetype From 6a9159b25598d578018643673568bbcd06ea1e12 Mon Sep 17 00:00:00 2001 From: dave Date: Mon, 25 Mar 2019 10:50:09 -0400 Subject: [PATCH 42/68] #433 - fix broken conversation page --- src/components/conversation-page/conversation-page.vue | 6 +++++- src/components/conversation/conversation.js | 8 ++++++-- src/components/conversation/conversation.vue | 4 ++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/components/conversation-page/conversation-page.vue b/src/components/conversation-page/conversation-page.vue index b03eea2822..9e322cf5a5 100644 --- a/src/components/conversation-page/conversation-page.vue +++ b/src/components/conversation-page/conversation-page.vue @@ -1,5 +1,9 @@ diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index 4cae0bdb7c..5a9568a9f2 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -31,7 +31,8 @@ const conversation = { }, props: [ 'statusoid', - 'collapsable' + 'collapsable', + 'isPage' ], computed: { status () { @@ -49,7 +50,7 @@ const conversation = { return [] } - if (!this.expanded) { + if (!this.expanded && !this.isPage) { return [this.status] } @@ -79,6 +80,9 @@ const conversation = { i++ return result }, {}) + }, + isExpanded () { + return this.expanded || this.isPage } }, components: { diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index b208d540d0..3778cc1532 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -1,6 +1,6 @@ From 968e6c7fe8392970ddb93751cd8df551ac99c5f1 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 25 Mar 2019 21:04:52 +0200 Subject: [PATCH 48/68] correctly paginate on MastoAPI --- src/modules/users.js | 24 +++++++++---------- src/services/api/api.service.js | 24 ++++++++++++------- .../backend_interactor_service.js | 8 +++---- 3 files changed, 32 insertions(+), 24 deletions(-) diff --git a/src/modules/users.js b/src/modules/users.js index 5cfa128ee3..61d0546253 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -52,23 +52,23 @@ export const mutations = { state.loggingIn = false }, // TODO Clean after ourselves? - addFriends (state, { id, friends, page }) { + addFriends (state, { id, friends }) { const user = state.usersObject[id] each(friends, friend => { if (!find(user.friends, { id: friend.id })) { user.friends.push(friend) } }) - user.friendsPage = page + 1 + user.lastFriendId = friends.slice(-1)[0].id }, - addFollowers (state, { id, followers, page }) { + addFollowers (state, { id, followers }) { const user = state.usersObject[id] each(followers, follower => { if (!find(user.followers, { id: follower.id })) { user.followers.push(follower) } }) - user.followersPage = page + 1 + user.lastFollowerId = followers.slice(-1)[0].id }, // Because frontend doesn't have a reason to keep these stuff in memory // outside of viewing someones user profile. @@ -78,7 +78,7 @@ export const mutations = { return } user.friends = [] - user.friendsPage = 0 + user.lastFriendId = null }, clearFollowers (state, userId) { const user = state.usersObject[userId] @@ -86,7 +86,7 @@ export const mutations = { return } user.followers = [] - user.followersPage = 0 + user.lastFollowerId = null }, addNewUsers (state, users) { each(users, (user) => mergeOrAdd(state.users, state.usersObject, user)) @@ -219,10 +219,10 @@ const users = { addFriends ({ rootState, commit }, fetchBy) { return new Promise((resolve, reject) => { const user = rootState.users.usersObject[fetchBy] - const page = user.friendsPage || 1 - rootState.api.backendInteractor.fetchFriends({ id: user.id, page }) + const sinceId = user.lastFriendId + rootState.api.backendInteractor.fetchFriends({ id: user.id, sinceId }) .then((friends) => { - commit('addFriends', { id: user.id, friends, page }) + commit('addFriends', { id: user.id, friends }) resolve(friends) }).catch(() => { reject() @@ -231,10 +231,10 @@ const users = { }, addFollowers ({ rootState, commit }, fetchBy) { const user = rootState.users.usersObject[fetchBy] - const page = user.followersPage || 1 - return rootState.api.backendInteractor.fetchFollowers({ id: user.id, page }) + const sinceId = user.lastFollowerId + return rootState.api.backendInteractor.fetchFollowers({ id: user.id, sinceId }) .then((followers) => { - commit('addFollowers', { id: user.id, followers, page }) + commit('addFollowers', { id: user.id, followers }) return followers }) }, diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 2c5e9d60a1..a8de736c25 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -276,11 +276,15 @@ const fetchUserRelationship = ({id, credentials}) => { }) } -const fetchFriends = ({id, page, credentials}) => { +const fetchFriends = ({id, maxId, sinceId, limit = 20, credentials}) => { let url = MASTODON_FOLLOWING_URL(id) - if (page) { - url = url + `?page=${page}` - } + const args = [ + maxId && `max_id=${maxId}`, + sinceId && `max_id=${sinceId}`, + limit && `limit=${limit}` + ].filter(_ => _).join('&') + + url = url + (args ? '?' + args : '') return fetch(url, { headers: authHeaders(credentials) }) .then((data) => data.json()) .then((data) => data.map(parseUser)) @@ -293,11 +297,15 @@ const exportFriends = ({id, credentials}) => { .then((data) => data.map(parseUser)) } -const fetchFollowers = ({id, page, credentials}) => { +const fetchFollowers = ({id, maxId, sinceId, limit = 20, credentials}) => { let url = MASTODON_FOLLOWERS_URL(id) - if (page) { - url = url + `?page=${page}` - } + const args = [ + maxId && `max_id=${maxId}`, + sinceId && `max_id=${sinceId}`, + limit && `limit=${limit}` + ].filter(_ => _).join('&') + + url = url + (args ? '?' + args : '') return fetch(url, { headers: authHeaders(credentials) }) .then((data) => data.json()) .then((data) => data.map(parseUser)) diff --git a/src/services/backend_interactor_service/backend_interactor_service.js b/src/services/backend_interactor_service/backend_interactor_service.js index 0f0bcddc41..71e78d2f0c 100644 --- a/src/services/backend_interactor_service/backend_interactor_service.js +++ b/src/services/backend_interactor_service/backend_interactor_service.js @@ -10,16 +10,16 @@ const backendInteractorService = (credentials) => { return apiService.fetchConversation({id, credentials}) } - const fetchFriends = ({id, page}) => { - return apiService.fetchFriends({id, page, credentials}) + const fetchFriends = ({id, maxId, sinceId, limit}) => { + return apiService.fetchFriends({id, maxId, sinceId, limit, credentials}) } const exportFriends = ({id}) => { return apiService.exportFriends({id, credentials}) } - const fetchFollowers = ({id, page}) => { - return apiService.fetchFollowers({id, page, credentials}) + const fetchFollowers = ({id, maxId, sinceId, limit}) => { + return apiService.fetchFollowers({id, maxId, sinceId, limit, credentials}) } const fetchAllFollowing = ({username}) => { From c5ec4829a7a18b20a1525998ba27cc7af5a3da55 Mon Sep 17 00:00:00 2001 From: dave Date: Mon, 25 Mar 2019 15:10:45 -0400 Subject: [PATCH 49/68] #433 - consider page on focused --- src/components/conversation/conversation.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index f88875573d..94709fd86a 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -139,7 +139,7 @@ const conversation = { return this.replies[id] || [] }, focused (id) { - return this.expanded && id === this.statusId + return (this.expanded || this.isPage) && id === this.statusId }, setHighlight (id) { this.highlight = id From 82f077feb944388d0648be1fdace8db1108c5c45 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 25 Mar 2019 21:12:54 +0200 Subject: [PATCH 50/68] fixup! Merge remote-tracking branch 'upstream/develop' into mastoapi/actions --- src/services/api/api.service.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index eac151eb56..70aeb2a951 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -33,10 +33,6 @@ const MASTODON_UNRETWEET_URL = id => `/api/v1/statuses/${id}/unreblog` const MASTODON_DELETE_URL = id => `/api/v1/statuses/${id}` const MASTODON_FOLLOW_URL = id => `/api/v1/accounts/${id}/follow` const MASTODON_UNFOLLOW_URL = id => `/api/v1/accounts/${id}/unfollow` -const MASTODON_BLOCK_URL = id => `/api/v1/accounts/${id}/block` -const MASTODON_UNBLOCK_URL = id => `/api/v1/accounts/${id}/unblock` -const MASTODON_MUTE_URL = id => `/api/v1/accounts/${id}/mute` -const MASTODON_UNMUTE_URL = id => `/api/v1/accounts/${id}/unmute` const MASTODON_STATUS_URL = id => `/api/v1/statuses/${id}` const MASTODON_STATUS_CONTEXT_URL = id => `/api/v1/statuses/${id}/context` const MASTODON_USER_URL = '/api/v1/accounts' From d766059deef65ea8c46616b340f6038476b47edf Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 25 Mar 2019 21:19:24 +0200 Subject: [PATCH 51/68] correctly update relationship on follow/unfollow --- src/services/follow_manipulate/follow_manipulate.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/follow_manipulate/follow_manipulate.js b/src/services/follow_manipulate/follow_manipulate.js index 1e9bd67916..51dafe84ee 100644 --- a/src/services/follow_manipulate/follow_manipulate.js +++ b/src/services/follow_manipulate/follow_manipulate.js @@ -19,7 +19,7 @@ const fetchUser = (attempt, user, store) => new Promise((resolve, reject) => { export const requestFollow = (user, store) => new Promise((resolve, reject) => { store.state.api.backendInteractor.followUser(user.id) .then((updated) => { - store.commit('addNewUsers', [updated]) + store.commit('updateUserRelationship', [updated]) // For locked users we just mark it that we sent the follow request if (updated.locked) { @@ -66,7 +66,7 @@ export const requestFollow = (user, store) => new Promise((resolve, reject) => { export const requestUnfollow = (user, store) => new Promise((resolve, reject) => { store.state.api.backendInteractor.unfollowUser(user.id) .then((updated) => { - store.commit('addNewUsers', [updated]) + store.commit('updateUserRelationship', [updated]) resolve({ updated }) From 467647d5d715730a14d41737e4326696ff971cc2 Mon Sep 17 00:00:00 2001 From: dave Date: Mon, 25 Mar 2019 16:09:40 -0400 Subject: [PATCH 52/68] #433 - fix retweet abnormal behavior --- src/components/conversation/conversation.js | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index 94709fd86a..8ad1f44ddf 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -18,8 +18,13 @@ const sortById = (a, b) => { } } -const sortAndFilterConversation = (conversation) => { - conversation = filter(conversation, (status) => status.type !== 'retweet') +const sortAndFilterConversation = (conversation, statusoid) => { + if (statusoid.type === 'retweet') { + conversation = filter( + conversation, + (status) => (status.type === 'retweet' || status.id !== statusoid.retweeted_status.id) + ) + } return conversation.filter(_ => _).sort(sortById) } @@ -66,7 +71,7 @@ const conversation = { return [] } - if (!this.expanded && !this.isPage) { + if (!this.isExpanded) { return [this.status] } @@ -81,7 +86,7 @@ const conversation = { conversation[statusIndex] = this.status } - return sortAndFilterConversation(conversation) + return sortAndFilterConversation(conversation, this.status) }, replies () { let i = 1 @@ -139,7 +144,7 @@ const conversation = { return this.replies[id] || [] }, focused (id) { - return (this.expanded || this.isPage) && id === this.statusId + return (this.isExpanded) && id === this.status.id }, setHighlight (id) { this.highlight = id @@ -149,6 +154,9 @@ const conversation = { }, toggleExpanded () { this.expanded = !this.expanded + if (!this.expanded) { + this.setHighlight(null) + } } } } From e23c19468a4436454161fd315c6bfb79aef1b15d Mon Sep 17 00:00:00 2001 From: shpuld Date: Mon, 25 Mar 2019 22:44:58 +0200 Subject: [PATCH 53/68] somewhat functioning gesture service --- src/components/side_drawer/side_drawer.js | 21 +++--- .../gesture_service/gesture_service.js | 71 +++++++++++++++++++ 2 files changed, 81 insertions(+), 11 deletions(-) diff --git a/src/components/side_drawer/side_drawer.js b/src/components/side_drawer/side_drawer.js index ad3738d167..76c64fe053 100644 --- a/src/components/side_drawer/side_drawer.js +++ b/src/components/side_drawer/side_drawer.js @@ -1,17 +1,18 @@ import UserCard from '../user_card/user_card.vue' import { unseenNotificationsFromStore } from '../../services/notification_utils/notification_utils' - -// TODO: separate touch gesture stuff into their own utils if more components want them -const deltaCoord = (oldCoord, newCoord) => [newCoord[0] - oldCoord[0], newCoord[1] - oldCoord[1]] - -const touchEventCoord = e => ([e.touches[0].screenX, e.touches[0].screenY]) +import GestureService from '../../services/gesture_service/gesture_service' const SideDrawer = { props: [ 'logout' ], data: () => ({ closed: true, - touchCoord: [0, 0] + closeGesture: undefined }), + created () { + const cb = () => this.toggleDrawer() + this.closeGesture = GestureService.swipeGesture(GestureService.DIRECTION_LEFT, cb) + console.log(this.closeGesture) + }, components: { UserCard }, computed: { currentUser () { @@ -46,13 +47,11 @@ const SideDrawer = { this.toggleDrawer() }, touchStart (e) { - this.touchCoord = touchEventCoord(e) + console.log(this) + GestureService.beginSwipe(e, this.closeGesture) }, touchMove (e) { - const delta = deltaCoord(this.touchCoord, touchEventCoord(e)) - if (delta[0] < -30 && Math.abs(delta[1]) < Math.abs(delta[0]) && !this.closed) { - this.toggleDrawer() - } + GestureService.updateSwipe(e, this.closeGesture) } } } diff --git a/src/services/gesture_service/gesture_service.js b/src/services/gesture_service/gesture_service.js index e69de29bb2..2e39003c8c 100644 --- a/src/services/gesture_service/gesture_service.js +++ b/src/services/gesture_service/gesture_service.js @@ -0,0 +1,71 @@ + +const DIRECTION_LEFT = [-1, 0] +const DIRECTION_RIGHT = [1, 0] +const DIRECTION_UP = [0, -1] +const DIRECTION_DOWN = [0, 1] + +const deltaCoord = (oldCoord, newCoord) => [newCoord[0] - oldCoord[0], newCoord[1] - oldCoord[1]] + +const touchEventCoord = e => ([e.touches[0].screenX, e.touches[0].screenY]) + +const vectorLength = v => Math.sqrt(v[0] * v[0] + v[1] * v[1]) + +const perpendicular = v => [v[1], v[0]] + +const dotProduct = (v1, v2) => v1[0] * v2[0] + v1[1] * v2[1] + +const vectorFlatten = (v1, v2) => [v1[0] * v2[0], v1[1] * v2[1]] + +// direction: either use the constants above or an arbitrary 2d vector. +// threshold: how many Px to move from touch origin before checking if the +// callback should be called. +// divergentTolerance: a scalr for much of divergent direction we tolerate when +// above threshold. for example, with 1.0 we only call the callback if +// divergent component of delta is < 1.0 * direction component of delta. +const swipeGesture = (direction, onSwipe, threshold = 30, perpendicularTolerance = 1.0) => { + return { + direction, + onSwipe, + threshold, + perpendicularTolerance, + _startPos: [0, 0], + _swiping: false + } +} + +const beginSwipe = (event, gesture) => { + gesture._startPos = touchEventCoord(event) + gesture._swiping = true +} + +const updateSwipe = (event, gesture) => { + if (!gesture._swiping) return + // movement too small + const delta = deltaCoord(gesture._startPos, touchEventCoord(event)) + if (vectorLength(delta) < gesture.threshold) return + // movement is opposite from direction + if (dotProduct(delta, gesture.direction) < 0) return + // movement perpendicular to direction is too much + const towardsDir = vectorFlatten(gesture.direction, delta) + const perpendicularDir = perpendicular(gesture.direction) + const towardsPerpendicular = vectorFlatten(perpendicularDir, delta) + if ( + vectorLength(towardsDir) < + gesture.perpendicularTolerance * vectorLength(towardsPerpendicular) + ) return + + gesture.onSwipe() + gesture._swiping = false +} + +const GestureService = { + DIRECTION_LEFT, + DIRECTION_RIGHT, + DIRECTION_UP, + DIRECTION_DOWN, + swipeGesture, + beginSwipe, + updateSwipe +} + +export default GestureService From 982be05873879f1ecea2fcdc9a8aa3e33fbb2524 Mon Sep 17 00:00:00 2001 From: dave Date: Tue, 26 Mar 2019 11:40:34 -0400 Subject: [PATCH 54/68] #457 - update tag timeline endpoint to MastoAPI --- src/services/api/api.service.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 3a4f21a69b..384a11bcc4 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -4,7 +4,6 @@ const FRIENDS_TIMELINE_URL = '/api/statuses/friends_timeline.json' const ALL_FOLLOWING_URL = '/api/qvitter/allfollowing' const PUBLIC_TIMELINE_URL = '/api/statuses/public_timeline.json' const PUBLIC_AND_EXTERNAL_TIMELINE_URL = '/api/statuses/public_and_external_timeline.json' -const TAG_TIMELINE_URL = '/api/statusnet/tags/timeline' const FAVORITE_URL = '/api/favorites/create' const UNFAVORITE_URL = '/api/favorites/destroy' const RETWEET_URL = '/api/statuses/retweet' @@ -38,6 +37,7 @@ const MASTODON_STATUS_CONTEXT_URL = id => `/api/v1/statuses/${id}/context` const MASTODON_USER_URL = '/api/v1/accounts' const MASTODON_USER_RELATIONSHIPS_URL = '/api/v1/accounts/relationships' const MASTODON_USER_TIMELINE_URL = id => `/api/v1/accounts/${id}/statuses` +const MASTODON_TAG_TIMELINE_URL = tag => `/api/v1/timelines/tag/${tag}` const MASTODON_USER_BLOCKS_URL = '/api/v1/blocks/' const MASTODON_USER_MUTES_URL = '/api/v1/mutes/' const MASTODON_BLOCK_USER_URL = id => `/api/v1/accounts/${id}/block` @@ -356,7 +356,7 @@ const fetchTimeline = ({timeline, credentials, since = false, until = false, use user: MASTODON_USER_TIMELINE_URL, media: MASTODON_USER_TIMELINE_URL, favorites: MASTODON_USER_FAVORITES_TIMELINE_URL, - tag: TAG_TIMELINE_URL + tag: MASTODON_TAG_TIMELINE_URL } const isNotifications = timeline === 'notifications' const params = [] @@ -374,7 +374,7 @@ const fetchTimeline = ({timeline, credentials, since = false, until = false, use params.push(['max_id', until]) } if (tag) { - url += `/${tag}.json` + url = url(tag) } if (timeline === 'media') { params.push(['only_media', 1]) From c50e64f8eecd780246e3ac47c2a54164cfc28b8f Mon Sep 17 00:00:00 2001 From: shpuld Date: Tue, 26 Mar 2019 22:11:45 +0200 Subject: [PATCH 55/68] Add tests for gesture service, fix bug with perpendicular directions --- .../gesture_service/gesture_service.js | 15 ++- .../gesture_service/gesture_service.spec.js | 120 ++++++++++++++++++ 2 files changed, 129 insertions(+), 6 deletions(-) create mode 100644 test/unit/specs/services/gesture_service/gesture_service.spec.js diff --git a/src/services/gesture_service/gesture_service.js b/src/services/gesture_service/gesture_service.js index 2e39003c8c..efc0ca78f7 100644 --- a/src/services/gesture_service/gesture_service.js +++ b/src/services/gesture_service/gesture_service.js @@ -10,11 +10,14 @@ const touchEventCoord = e => ([e.touches[0].screenX, e.touches[0].screenY]) const vectorLength = v => Math.sqrt(v[0] * v[0] + v[1] * v[1]) -const perpendicular = v => [v[1], v[0]] +const perpendicular = v => [v[1], -v[0]] const dotProduct = (v1, v2) => v1[0] * v2[0] + v1[1] * v2[1] -const vectorFlatten = (v1, v2) => [v1[0] * v2[0], v1[1] * v2[1]] +const project = (v1, v2) => { + const scalar = (dotProduct(v1, v2) / dotProduct(v2, v2)) + return [scalar * v2[0], scalar * v2[1]] +} // direction: either use the constants above or an arbitrary 2d vector. // threshold: how many Px to move from touch origin before checking if the @@ -46,12 +49,12 @@ const updateSwipe = (event, gesture) => { // movement is opposite from direction if (dotProduct(delta, gesture.direction) < 0) return // movement perpendicular to direction is too much - const towardsDir = vectorFlatten(gesture.direction, delta) + const towardsDir = project(delta, gesture.direction) const perpendicularDir = perpendicular(gesture.direction) - const towardsPerpendicular = vectorFlatten(perpendicularDir, delta) + const towardsPerpendicular = project(delta, perpendicularDir) if ( - vectorLength(towardsDir) < - gesture.perpendicularTolerance * vectorLength(towardsPerpendicular) + vectorLength(towardsDir) * gesture.perpendicularTolerance < + vectorLength(towardsPerpendicular) ) return gesture.onSwipe() diff --git a/test/unit/specs/services/gesture_service/gesture_service.spec.js b/test/unit/specs/services/gesture_service/gesture_service.spec.js new file mode 100644 index 0000000000..4a1b009a25 --- /dev/null +++ b/test/unit/specs/services/gesture_service/gesture_service.spec.js @@ -0,0 +1,120 @@ +import GestureService from 'src/services/gesture_service/gesture_service.js' + +const mockTouchEvent = (x, y) => ({ + touches: [ + { + screenX: x, + screenY: y + } + ] +}) + +describe.only('GestureService', () => { + describe('swipeGesture', () => { + it('calls the callback on a successful swipe', () => { + let swiped = false + const callback = () => { swiped = true } + const gesture = GestureService.swipeGesture( + GestureService.DIRECTION_RIGHT, + callback + ) + + GestureService.beginSwipe(mockTouchEvent(100, 100), gesture) + GestureService.updateSwipe(mockTouchEvent(200, 100), gesture) + + expect(swiped).to.eql(true) + }) + + it('calls the callback only once per begin', () => { + let hits = 0 + const callback = () => { hits += 1 } + const gesture = GestureService.swipeGesture( + GestureService.DIRECTION_RIGHT, + callback + ) + + GestureService.beginSwipe(mockTouchEvent(100, 100), gesture) + GestureService.updateSwipe(mockTouchEvent(150, 100), gesture) + GestureService.updateSwipe(mockTouchEvent(200, 100), gesture) + + expect(hits).to.eql(1) + }) + + it('doesn\'t call the callback on an opposite swipe', () => { + let swiped = false + const callback = () => { swiped = true } + const gesture = GestureService.swipeGesture( + GestureService.DIRECTION_RIGHT, + callback + ) + + GestureService.beginSwipe(mockTouchEvent(100, 100), gesture) + GestureService.updateSwipe(mockTouchEvent(0, 100), gesture) + + expect(swiped).to.eql(false) + }) + + it('doesn\'t call the callback on a swipe below threshold', () => { + let swiped = false + const callback = () => { swiped = true } + const gesture = GestureService.swipeGesture( + GestureService.DIRECTION_RIGHT, + callback, + 100 + ) + + GestureService.beginSwipe(mockTouchEvent(100, 100), gesture) + GestureService.updateSwipe(mockTouchEvent(150, 100), gesture) + + expect(swiped).to.eql(false) + }) + + it('doesn\'t call the callback on a perpendicular swipe', () => { + let swiped = false + const callback = () => { swiped = true } + const gesture = GestureService.swipeGesture( + GestureService.DIRECTION_RIGHT, + callback, + 30, + 0.5 + ) + + GestureService.beginSwipe(mockTouchEvent(100, 100), gesture) + GestureService.updateSwipe(mockTouchEvent(150, 200), gesture) + + expect(swiped).to.eql(false) + }) + + it('calls the callback on perpendicular swipe if within tolerance', () => { + let swiped = false + const callback = () => { swiped = true } + const gesture = GestureService.swipeGesture( + GestureService.DIRECTION_RIGHT, + callback, + 30, + 2.0 + ) + + GestureService.beginSwipe(mockTouchEvent(100, 100), gesture) + GestureService.updateSwipe(mockTouchEvent(150, 150), gesture) + + expect(swiped).to.eql(true) + }) + + it('works with any arbitrary 2d directions', () => { + let swiped = false + const callback = () => { swiped = true } + const gesture = GestureService.swipeGesture( + [-1, -1], + callback, + 30, + 0.1 + ) + + GestureService.beginSwipe(mockTouchEvent(100, 100), gesture) + GestureService.updateSwipe(mockTouchEvent(60, 60), gesture) + + expect(swiped).to.eql(true) + }) + }) +}) From 2d05aef24b36d1c8be5cdad13ae69a2f0514c713 Mon Sep 17 00:00:00 2001 From: jasper Date: Tue, 26 Mar 2019 14:26:26 -0700 Subject: [PATCH 56/68] Add await to login action' --- src/boot/after_store.js | 2 +- src/lib/persisted_state.js | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/boot/after_store.js b/src/boot/after_store.js index f5e84cbc67..f5add8ade5 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -241,7 +241,7 @@ const afterStoreSetup = async ({ store, i18n }) => { // Now we have the server settings and can try logging in if (store.state.oauth.token) { - store.dispatch('loginUser', store.state.oauth.token) + await store.dispatch('loginUser', store.state.oauth.token) } const router = new VueRouter({ diff --git a/src/lib/persisted_state.js b/src/lib/persisted_state.js index 720ff706a3..7ab89c1235 100644 --- a/src/lib/persisted_state.js +++ b/src/lib/persisted_state.js @@ -60,9 +60,6 @@ export default function createPersistedState ({ merge({}, store.state, savedState) ) } - if (store.state.oauth.token) { - store.dispatch('loginUser', store.state.oauth.token) - } loaded = true } catch (e) { console.log("Couldn't load state") From 95459ff0bca7f095f0fdc01b38dd89626fe5c189 Mon Sep 17 00:00:00 2001 From: HJ <30-hj@users.noreply.git.pleroma.social> Date: Wed, 27 Mar 2019 08:47:54 +0000 Subject: [PATCH 57/68] Revert "Merge branch 'mastoapi/friends-tl' into 'develop'" This reverts merge request !647 --- src/services/api/api.service.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 4ebfb7b770..3a4f21a69b 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -1,5 +1,6 @@ /* eslint-env browser */ const LOGIN_URL = '/api/account/verify_credentials.json' +const FRIENDS_TIMELINE_URL = '/api/statuses/friends_timeline.json' const ALL_FOLLOWING_URL = '/api/qvitter/allfollowing' const PUBLIC_TIMELINE_URL = '/api/statuses/public_timeline.json' const PUBLIC_AND_EXTERNAL_TIMELINE_URL = '/api/statuses/public_and_external_timeline.json' @@ -32,7 +33,6 @@ const DENY_USER_URL = '/api/pleroma/friendships/deny' const SUGGESTIONS_URL = '/api/v1/suggestions' const MASTODON_USER_FAVORITES_TIMELINE_URL = '/api/v1/favourites' -const MASTODON_USER_HOME_TIMELINE_URL = '/api/v1/timelines/home' const MASTODON_STATUS_URL = id => `/api/v1/statuses/${id}` const MASTODON_STATUS_CONTEXT_URL = id => `/api/v1/statuses/${id}/context` const MASTODON_USER_URL = '/api/v1/accounts' @@ -348,7 +348,7 @@ const fetchStatus = ({id, credentials}) => { const fetchTimeline = ({timeline, credentials, since = false, until = false, userId = false, tag = false, withMuted = false}) => { const timelineUrls = { public: PUBLIC_TIMELINE_URL, - friends: MASTODON_USER_HOME_TIMELINE_URL, + friends: FRIENDS_TIMELINE_URL, mentions: MENTIONS_URL, dms: DM_TIMELINE_URL, notifications: QVITTER_USER_NOTIFICATIONS_URL, From b3a6bec17b18f554caa12d35e9e875adddc206db Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 27 Mar 2019 11:15:57 +0200 Subject: [PATCH 58/68] Revert "Merge branch 'revert-987b5162' into 'develop'" This reverts commit 96753e6a5febff60e0c2cb6fac13d5e7865f0a94, reversing changes made to 987b5162a7530979e9fa887a24311eb1cd480fc5. --- src/services/api/api.service.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 8d7f9e5d34..aabbe80f70 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -1,6 +1,5 @@ /* eslint-env browser */ const LOGIN_URL = '/api/account/verify_credentials.json' -const FRIENDS_TIMELINE_URL = '/api/statuses/friends_timeline.json' const ALL_FOLLOWING_URL = '/api/qvitter/allfollowing' const TAG_TIMELINE_URL = '/api/statusnet/tags/timeline' const FAVORITE_URL = '/api/favorites/create' @@ -32,6 +31,7 @@ const SUGGESTIONS_URL = '/api/v1/suggestions' const MASTODON_USER_FAVORITES_TIMELINE_URL = '/api/v1/favourites' const MASTODON_PUBLIC_TIMELINE = '/api/v1/timelines/public' +const MASTODON_USER_HOME_TIMELINE_URL = '/api/v1/timelines/home' const MASTODON_STATUS_URL = id => `/api/v1/statuses/${id}` const MASTODON_STATUS_CONTEXT_URL = id => `/api/v1/statuses/${id}/context` const MASTODON_USER_URL = '/api/v1/accounts' @@ -347,7 +347,7 @@ const fetchStatus = ({id, credentials}) => { const fetchTimeline = ({timeline, credentials, since = false, until = false, userId = false, tag = false, withMuted = false}) => { const timelineUrls = { public: MASTODON_PUBLIC_TIMELINE, - friends: FRIENDS_TIMELINE_URL, + friends: MASTODON_USER_HOME_TIMELINE_URL, mentions: MENTIONS_URL, dms: DM_TIMELINE_URL, notifications: QVITTER_USER_NOTIFICATIONS_URL, From 43e97e590c98d1f1bb500f96d2b604b968fbbbb3 Mon Sep 17 00:00:00 2001 From: dave Date: Wed, 27 Mar 2019 16:00:54 -0400 Subject: [PATCH 59/68] #433 - sort conversation for retweets and clean up --- src/components/conversation/conversation.js | 18 +++++------------- src/components/status/status.vue | 4 ++-- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index 8ad1f44ddf..5357b67f77 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -3,19 +3,9 @@ import { set } from 'vue' import Status from '../status/status.vue' const sortById = (a, b) => { - const seqA = Number(a.id) - const seqB = Number(b.id) - const isSeqA = !Number.isNaN(seqA) - const isSeqB = !Number.isNaN(seqB) - if (isSeqA && isSeqB) { - return seqA < seqB ? -1 : 1 - } else if (isSeqA && !isSeqB) { - return -1 - } else if (!isSeqA && isSeqB) { - return 1 - } else { - return a.id < b.id ? -1 : 1 - } + const idA = a.type === 'retweet' ? a.retweeted_status.id : a.id + const idB = b.type === 'retweet' ? b.retweeted_status.id : b.id + return idA < idB ? -1 : 1 } const sortAndFilterConversation = (conversation, statusoid) => { @@ -24,6 +14,8 @@ const sortAndFilterConversation = (conversation, statusoid) => { conversation, (status) => (status.type === 'retweet' || status.id !== statusoid.retweeted_status.id) ) + } else { + conversation = filter(conversation, (status) => status.type !== 'retweet') } return conversation.filter(_ => _).sort(sortById) } diff --git a/src/components/status/status.vue b/src/components/status/status.vue index 1f6d0325ee..da329deb08 100644 --- a/src/components/status/status.vue +++ b/src/components/status/status.vue @@ -12,7 +12,7 @@