Merge remote-tracking branch 'upstream/develop' into mastoapi/convos
* upstream/develop: (34 commits) Update attachment normalizer Add fallback for attachments uploaded via the other platforms Get correct mimetype through entity_normalizer Set default parameter Switch to mastoapi for posting status and uploading media Revert changes prevent text pasting if image is pasted remove border radius of suggested emojis #450 - dispatch login after saved state is loaded #448 - fix timeline fetch error when status text is null #451 - add class to username span No need to fetch mutes on load anymore 🙌 switch to mastoapi switch to mastoapi masto api sends muted property now No need to fetch user data using old api anymore 🎉 Switch to mastoapi Add comment Reset statusnet_blocking of all fetched users first while refreshing block list Add hideMutedPosts setting and wire up to post-returning endpoints ...
This commit is contained in:
		
						commit
						3b5fc88989
					
				
					 23 changed files with 185 additions and 149 deletions
				
			
		|  | @ -97,6 +97,7 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => { | |||
|   copyInstanceOption('showInstanceSpecificPanel') | ||||
|   copyInstanceOption('scopeOptionsEnabled') | ||||
|   copyInstanceOption('formattingOptionsEnabled') | ||||
|   copyInstanceOption('hideMutedPosts') | ||||
|   copyInstanceOption('collapseMessageWithSubject') | ||||
|   copyInstanceOption('loginMethod') | ||||
|   copyInstanceOption('scopeCopy') | ||||
|  |  | |||
|  | @ -8,8 +8,8 @@ | |||
|     </div> | ||||
|     <div class="basic-user-card-collapsed-content" v-else> | ||||
|       <div :title="user.name" class="basic-user-card-user-name"> | ||||
|         <span v-if="user.name_html" v-html="user.name_html"></span> | ||||
|         <span v-else>{{ user.name }}</span> | ||||
|         <span v-if="user.name_html" class="basic-user-card-user-name-value" v-html="user.name_html"></span> | ||||
|         <span v-else class="basic-user-card-user-name-value">{{ user.name }}</span> | ||||
|       </div> | ||||
|       <div> | ||||
|         <router-link class="basic-user-card-screen-name" :to="userProfileLink(user)"> | ||||
|  | @ -52,6 +52,14 @@ | |||
|       width: 16px; | ||||
|       vertical-align: middle; | ||||
|     } | ||||
| 
 | ||||
|     &-value { | ||||
|       display: inline-block; | ||||
|       max-width: 100%; | ||||
|       overflow: hidden; | ||||
|       white-space: nowrap; | ||||
|       text-overflow: ellipsis; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   &-expanded-content { | ||||
|  |  | |||
|  | @ -4,12 +4,12 @@ | |||
|       <span class="faint" v-if="!noFollowsYou && user.follows_you"> | ||||
|         {{ isMe ? $t('user_card.its_you') : $t('user_card.follows_you') }} | ||||
|       </span> | ||||
|       <div class="btn-follow" v-if="showFollow && !loggedIn"> | ||||
|       <div class="follow-card-follow-button" v-if="showFollow && !loggedIn"> | ||||
|         <RemoteFollow :user="user" /> | ||||
|       </div> | ||||
|       <button | ||||
|         v-if="showFollow && loggedIn" | ||||
|         class="btn btn-default btn-follow" | ||||
|         class="btn btn-default follow-card-follow-button" | ||||
|         @click="followUser" | ||||
|         :disabled="inProgress" | ||||
|         :title="requestSent ? $t('user_card.follow_again') : ''" | ||||
|  | @ -24,7 +24,7 @@ | |||
|           {{ $t('user_card.follow') }} | ||||
|         </template> | ||||
|       </button> | ||||
|       <button v-if="following" class="btn btn-default pressed" @click="unfollowUser" :disabled="inProgress"> | ||||
|       <button v-if="following" class="btn btn-default follow-card-follow-button pressed" @click="unfollowUser" :disabled="inProgress"> | ||||
|         <template v-if="inProgress"> | ||||
|           {{ $t('user_card.follow_progress') }} | ||||
|         </template> | ||||
|  | @ -39,15 +39,17 @@ | |||
| <script src="./follow_card.js"></script> | ||||
| 
 | ||||
| <style lang="scss"> | ||||
| .follow-card-content-container { | ||||
|   flex-shrink: 0; | ||||
|   display: flex; | ||||
|   flex-direction: row; | ||||
|   justify-content: space-between; | ||||
|   flex-wrap: wrap; | ||||
|   line-height: 1.5em; | ||||
| .follow-card { | ||||
|   &-content-container { | ||||
|     flex-shrink: 0; | ||||
|     display: flex; | ||||
|     flex-direction: row; | ||||
|     justify-content: space-between; | ||||
|     flex-wrap: wrap; | ||||
|     line-height: 1.5em; | ||||
|   } | ||||
| 
 | ||||
|   .btn-follow { | ||||
|   &-follow-button { | ||||
|     margin-top: 0.5em; | ||||
|     margin-left: auto; | ||||
|     width: 10em; | ||||
|  |  | |||
|  | @ -20,7 +20,7 @@ const mediaUpload = { | |||
|         return | ||||
|       } | ||||
|       const formData = new FormData() | ||||
|       formData.append('media', file) | ||||
|       formData.append('file', file) | ||||
| 
 | ||||
|       self.$emit('uploading') | ||||
|       self.uploading = true | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| <template> | ||||
|   <basic-user-card :user="user"> | ||||
|     <template slot="secondary-area"> | ||||
|     <div class="mute-card-content-container"> | ||||
|       <button class="btn btn-default" @click="unmuteUser" :disabled="progress" v-if="muted"> | ||||
|         <template v-if="progress"> | ||||
|           {{ $t('user_card.unmute_progress') }} | ||||
|  | @ -17,8 +17,18 @@ | |||
|           {{ $t('user_card.mute') }} | ||||
|         </template> | ||||
|       </button> | ||||
|     </template> | ||||
|     </div> | ||||
|   </basic-user-card> | ||||
| </template> | ||||
| 
 | ||||
| <script src="./mute_card.js"></script> | ||||
| 
 | ||||
| <style lang="scss"> | ||||
| .mute-card-content-container { | ||||
|   margin-top: 0.5em; | ||||
|   text-align: right; | ||||
|   button { | ||||
|     width: 10em; | ||||
|   } | ||||
| } | ||||
| </style> | ||||
|  |  | |||
|  | @ -296,6 +296,8 @@ const PostStatusForm = { | |||
|     }, | ||||
|     paste (e) { | ||||
|       if (e.clipboardData.files.length > 0) { | ||||
|         // prevent pasting of file as text
 | ||||
|         e.preventDefault() | ||||
|         // Strangely, files property gets emptied after event propagation
 | ||||
|         // Trying to wrap it in array doesn't work. Plus I doubt it's possible
 | ||||
|         // to hold more than one file in clipboard.
 | ||||
|  |  | |||
|  | @ -84,10 +84,10 @@ | |||
|         <div class="media-upload-wrapper" v-for="file in newStatus.files"> | ||||
|           <i class="fa button-icon icon-cancel" @click="removeMediaFile(file)"></i> | ||||
|           <div class="media-upload-container attachment"> | ||||
|             <img class="thumbnail media-upload" :src="file.image" v-if="type(file) === 'image'"></img> | ||||
|             <video v-if="type(file) === 'video'" :src="file.image" controls></video> | ||||
|             <audio v-if="type(file) === 'audio'" :src="file.image" controls></audio> | ||||
|             <a v-if="type(file) === 'unknown'" :href="file.image">{{file.url}}</a> | ||||
|             <img class="thumbnail media-upload" :src="file.url" v-if="type(file) === 'image'"></img> | ||||
|             <video v-if="type(file) === 'video'" :src="file.url" controls></video> | ||||
|             <audio v-if="type(file) === 'audio'" :src="file.url" controls></audio> | ||||
|             <a v-if="type(file) === 'unknown'" :href="file.url">{{file.url}}</a> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|  | @ -287,8 +287,6 @@ | |||
|     img { | ||||
|       width: 24px; | ||||
|       height: 24px; | ||||
|       border-radius: $fallback--avatarRadius; | ||||
|       border-radius: var(--avatarRadius, $fallback--avatarRadius); | ||||
|       object-fit: contain; | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -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 }) | ||||
|     }, | ||||
|  |  | |||
|  | @ -36,6 +36,10 @@ | |||
|         <div class="setting-item"> | ||||
|           <h2>{{$t('nav.timeline')}}</h2> | ||||
|           <ul class="setting-list"> | ||||
|             <li> | ||||
|               <input type="checkbox" id="hideMutedPosts" v-model="hideMutedPostsLocal"> | ||||
|               <label for="hideMutedPosts">{{$t('settings.hide_muted_posts')}} {{$t('settings.instance_default', { value: hideMutedPostsDefault })}}</label> | ||||
|             </li> | ||||
|             <li> | ||||
|               <input type="checkbox" id="collapseMessageWithSubject" v-model="collapseMessageWithSubjectLocal"> | ||||
|               <label for="collapseMessageWithSubject"> | ||||
|  |  | |||
|  | @ -121,24 +121,16 @@ 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) | ||||
|     }, | ||||
|     toggleMute () { | ||||
|       const store = this.$store | ||||
|       store.commit('setMuted', {user: this.user, muted: !this.user.muted}) | ||||
|       store.state.api.backendInteractor.setUserMute(this.user) | ||||
|     muteUser () { | ||||
|       this.$store.dispatch('muteUser', this.user.id) | ||||
|     }, | ||||
|     unmuteUser () { | ||||
|       this.$store.dispatch('unmuteUser', this.user.id) | ||||
|     }, | ||||
|     setProfileView (v) { | ||||
|       if (this.switcher) { | ||||
|  |  | |||
|  | @ -74,12 +74,12 @@ | |||
|         </div> | ||||
|         <div class='mute' v-if='isOtherUser && loggedIn'> | ||||
|           <span v-if='user.muted'> | ||||
|             <button @click="toggleMute" class="pressed"> | ||||
|             <button @click="unmuteUser" class="pressed"> | ||||
|               {{ $t('user_card.muted') }} | ||||
|             </button> | ||||
|           </span> | ||||
|           <span v-if='!user.muted'> | ||||
|             <button @click="toggleMute"> | ||||
|             <button @click="muteUser"> | ||||
|               {{ $t('user_card.mute') }} | ||||
|             </button> | ||||
|           </span> | ||||
|  |  | |||
|  | @ -192,6 +192,12 @@ | |||
|             <template slot="empty">{{$t('settings.no_blocks')}}</template> | ||||
|           </block-list> | ||||
|         </div> | ||||
| 
 | ||||
|         <div :label="$t('settings.mutes_tab')"> | ||||
|           <mute-list :refresh="true"> | ||||
|             <template slot="empty">{{$t('settings.no_mutes')}}</template> | ||||
|           </mute-list> | ||||
|         </div> | ||||
|       </tab-switcher> | ||||
|     </div> | ||||
|   </div> | ||||
|  |  | |||
|  | @ -153,6 +153,7 @@ | |||
|     "general": "General", | ||||
|     "hide_attachments_in_convo": "Hide attachments in conversations", | ||||
|     "hide_attachments_in_tl": "Hide attachments in timeline", | ||||
|     "hide_muted_posts": "Hide posts of muted users", | ||||
|     "max_thumbnails": "Maximum amount of thumbnails per post", | ||||
|     "hide_isp": "Hide instance-specific panel", | ||||
|     "preload_images": "Preload images", | ||||
|  |  | |||
|  | @ -60,6 +60,9 @@ 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") | ||||
|  |  | |||
|  | @ -5,6 +5,7 @@ const browserLocale = (window.navigator.language || 'en').split('-')[0] | |||
| 
 | ||||
| const defaultState = { | ||||
|   colors: {}, | ||||
|   hideMutedPosts: undefined, // instance default
 | ||||
|   collapseMessageWithSubject: undefined, // instance default
 | ||||
|   hideAttachments: false, | ||||
|   hideAttachmentsInConv: false, | ||||
|  |  | |||
|  | @ -18,6 +18,7 @@ const defaultState = { | |||
|   scopeOptionsEnabled: true, | ||||
|   formattingOptionsEnabled: false, | ||||
|   alwaysShowSubjectInput: true, | ||||
|   hideMutedPosts: false, | ||||
|   collapseMessageWithSubject: false, | ||||
|   hidePostStats: false, | ||||
|   hideUserStats: false, | ||||
|  |  | |||
|  | @ -102,10 +102,20 @@ export const mutations = { | |||
|       } | ||||
|     }) | ||||
|   }, | ||||
|   saveBlocks (state, blockIds) { | ||||
|   updateBlocks (state, blockedUsers) { | ||||
|     // Reset statusnet_blocking of all fetched users
 | ||||
|     each(state.users, (user) => { user.statusnet_blocking = false }) | ||||
|     each(blockedUsers, (user) => mergeOrAdd(state.users, state.usersObject, user)) | ||||
|   }, | ||||
|   saveBlockIds (state, blockIds) { | ||||
|     state.currentUser.blockIds = blockIds | ||||
|   }, | ||||
|   saveMutes (state, muteIds) { | ||||
|   updateMutes (state, mutedUsers) { | ||||
|     // Reset muted of all fetched users
 | ||||
|     each(state.users, (user) => { user.muted = false }) | ||||
|     each(mutedUsers, (user) => mergeOrAdd(state.users, state.usersObject, user)) | ||||
|   }, | ||||
|   saveMuteIds (state, muteIds) { | ||||
|     state.currentUser.muteIds = muteIds | ||||
|   }, | ||||
|   setUserForStatus (state, status) { | ||||
|  | @ -172,34 +182,39 @@ const users = { | |||
|     fetchBlocks (store) { | ||||
|       return store.rootState.api.backendInteractor.fetchBlocks() | ||||
|         .then((blocks) => { | ||||
|           store.commit('saveBlocks', map(blocks, 'id')) | ||||
|           store.commit('addNewUsers', blocks) | ||||
|           store.commit('saveBlockIds', map(blocks, 'id')) | ||||
|           store.commit('updateBlocks', blocks) | ||||
|           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((relationship) => { | ||||
|           store.commit('updateUserRelationship', [relationship]) | ||||
|           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) | ||||
|         .then((user) => store.commit('addNewUsers', [user])) | ||||
|         .then((relationship) => store.commit('updateUserRelationship', [relationship])) | ||||
|     }, | ||||
|     fetchMutes (store) { | ||||
|       return store.rootState.api.backendInteractor.fetchMutes() | ||||
|         .then((mutedUsers) => { | ||||
|           each(mutedUsers, (user) => { user.muted = true }) | ||||
|           store.commit('addNewUsers', mutedUsers) | ||||
|           store.commit('saveMutes', map(mutedUsers, 'id')) | ||||
|         .then((mutes) => { | ||||
|           store.commit('updateMutes', mutes) | ||||
|           store.commit('saveMuteIds', map(mutes, 'id')) | ||||
|           return mutes | ||||
|         }) | ||||
|     }, | ||||
|     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((relationship) => store.commit('updateUserRelationship', [relationship])) | ||||
|     }, | ||||
|     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((relationship) => store.commit('updateUserRelationship', [relationship])) | ||||
|     }, | ||||
|     addFriends ({ rootState, commit }, fetchBy) { | ||||
|       return new Promise((resolve, reject) => { | ||||
|  |  | |||
|  | @ -9,17 +9,13 @@ 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 MEDIA_UPLOAD_URL = '/api/statusnet/media/upload' | ||||
| 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' | ||||
| 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' | ||||
|  | @ -28,8 +24,6 @@ const PROFILE_UPDATE_URL = '/api/account/update_profile.json' | |||
| const EXTERNAL_PROFILE_URL = '/api/externalprofile/show.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 FOLLOW_IMPORT_URL = '/api/pleroma/follow_import' | ||||
| const DELETE_ACCOUNT_URL = '/api/pleroma/delete_account' | ||||
| const CHANGE_PASSWORD_URL = '/api/pleroma/change_password' | ||||
|  | @ -44,9 +38,17 @@ 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_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` | ||||
| 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' | ||||
| import { parseStatus, parseUser, parseNotification, parseAttachment } from '../entity_normalizer/entity_normalizer.service.js' | ||||
| import 'whatwg-fetch' | ||||
| import { StatusCodeError } from '../errors/errors' | ||||
| 
 | ||||
|  | @ -60,6 +62,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
 | ||||
|  | @ -212,16 +227,14 @@ const unfollowUser = ({id, credentials}) => { | |||
| } | ||||
| 
 | ||||
| const blockUser = ({id, credentials}) => { | ||||
|   let url = `${BLOCKING_URL}?user_id=${id}` | ||||
|   return fetch(url, { | ||||
|   return fetch(MASTODON_BLOCK_USER_URL(id), { | ||||
|     headers: authHeaders(credentials), | ||||
|     method: 'POST' | ||||
|   }).then((data) => data.json()) | ||||
| } | ||||
| 
 | ||||
| const unblockUser = ({id, credentials}) => { | ||||
|   let url = `${UNBLOCKING_URL}?user_id=${id}` | ||||
|   return fetch(url, { | ||||
|   return fetch(MASTODON_UNBLOCK_USER_URL(id), { | ||||
|     headers: authHeaders(credentials), | ||||
|     method: 'POST' | ||||
|   }).then((data) => data.json()) | ||||
|  | @ -245,16 +258,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)) | ||||
| } | ||||
| 
 | ||||
|  | @ -341,23 +345,7 @@ 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 fetchTimeline = ({timeline, credentials, since = false, until = false, userId = false, tag = false, withMuted = false}) => { | ||||
|   const timelineUrls = { | ||||
|     public: PUBLIC_TIMELINE_URL, | ||||
|     friends: FRIENDS_TIMELINE_URL, | ||||
|  | @ -393,6 +381,7 @@ const fetchTimeline = ({timeline, credentials, since = false, until = false, use | |||
|   } | ||||
| 
 | ||||
|   params.push(['count', 20]) | ||||
|   params.push(['with_muted', withMuted]) | ||||
| 
 | ||||
|   const queryString = map(params, (param) => `${param[0]}=${param[1]}`).join('&') | ||||
|   url += `?${queryString}` | ||||
|  | @ -453,23 +442,23 @@ 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) | ||||
|   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) | ||||
|  | @ -494,13 +483,13 @@ 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((data) => data.json()) | ||||
|     .then((data) => parseAttachment(data)) | ||||
| } | ||||
| 
 | ||||
| const followImport = ({params, credentials}) => { | ||||
|  | @ -541,24 +530,29 @@ const changePassword = ({credentials, password, newPassword, newPasswordConfirma | |||
| } | ||||
| 
 | ||||
| const fetchMutes = ({credentials}) => { | ||||
|   const url = '/api/qvitter/mutes.json' | ||||
| 
 | ||||
|   return fetch(url, { | ||||
|     headers: authHeaders(credentials) | ||||
|   }).then((data) => data.json()) | ||||
|   return promisedRequest(MASTODON_USER_MUTES_URL, { headers: authHeaders(credentials) }) | ||||
|     .then((users) => users.map(parseUser)) | ||||
| } | ||||
| 
 | ||||
| const fetchBlocks = ({page, credentials}) => { | ||||
|   return fetch(BLOCKS_URL, { | ||||
|     headers: authHeaders(credentials) | ||||
|   }).then((data) => { | ||||
|     if (data.ok) { | ||||
|       return data.json() | ||||
|     } | ||||
|     throw new Error('Error fetching blocks', data) | ||||
| const muteUser = ({id, credentials}) => { | ||||
|   return promisedRequest(MASTODON_MUTE_USER_URL(id), { | ||||
|     headers: authHeaders(credentials), | ||||
|     method: 'POST' | ||||
|   }) | ||||
| } | ||||
| 
 | ||||
| const unmuteUser = ({id, credentials}) => { | ||||
|   return promisedRequest(MASTODON_UNMUTE_USER_URL(id), { | ||||
|     headers: authHeaders(credentials), | ||||
|     method: 'POST' | ||||
|   }) | ||||
| } | ||||
| 
 | ||||
| const fetchBlocks = ({credentials}) => { | ||||
|   return promisedRequest(MASTODON_USER_BLOCKS_URL, { headers: authHeaders(credentials) }) | ||||
|     .then((users) => users.map(parseUser)) | ||||
| } | ||||
| 
 | ||||
| const fetchOAuthTokens = ({credentials}) => { | ||||
|   const url = '/api/oauth_tokens.json' | ||||
| 
 | ||||
|  | @ -621,8 +615,9 @@ const apiService = { | |||
|   deleteStatus, | ||||
|   uploadMedia, | ||||
|   fetchAllFollowing, | ||||
|   setUserMute, | ||||
|   fetchMutes, | ||||
|   muteUser, | ||||
|   unmuteUser, | ||||
|   fetchBlocks, | ||||
|   fetchOAuthTokens, | ||||
|   revokeOAuthToken, | ||||
|  |  | |||
|  | @ -62,12 +62,10 @@ 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 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}) | ||||
|  | @ -100,8 +98,9 @@ const backendInteractorService = (credentials) => { | |||
|     fetchAllFollowing, | ||||
|     verifyCredentials: apiService.verifyCredentials, | ||||
|     startFetching, | ||||
|     setUserMute, | ||||
|     fetchMutes, | ||||
|     muteUser, | ||||
|     unmuteUser, | ||||
|     fetchBlocks, | ||||
|     fetchOAuthTokens, | ||||
|     revokeOAuthToken, | ||||
|  |  | |||
|  | @ -128,14 +128,15 @@ 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 ? data.pleroma.mime_type : data.type | ||||
|     output.meta = data.meta // not present in BE yet
 | ||||
|     output.id = data.id | ||||
|   } else { | ||||
|     output.mimetype = data.mimetype | ||||
|     // output.meta = ??? missing
 | ||||
|  | @ -293,5 +294,5 @@ export const parseNotification = (data) => { | |||
| 
 | ||||
| const isNsfw = (status) => { | ||||
|   const nsfwRegex = /#nsfw/i | ||||
|   return (status.tags || []).includes('nsfw') || !!status.text.match(nsfwRegex) | ||||
|   return (status.tags || []).includes('nsfw') || !!(status.text || '').match(nsfwRegex) | ||||
| } | ||||
|  |  | |||
|  | @ -1,13 +1,16 @@ | |||
| import utils from './utils.js' | ||||
| import { parseUser } from '../entity_normalizer/entity_normalizer.service.js' | ||||
| 
 | ||||
| const search = ({query, store}) => { | ||||
|   return utils.request({ | ||||
|     store, | ||||
|     url: '/api/pleroma/search_user', | ||||
|     url: '/api/v1/accounts/search', | ||||
|     params: { | ||||
|       query | ||||
|       q: query | ||||
|     } | ||||
|   }).then((data) => data.json()) | ||||
|   }) | ||||
|   .then((data) => data.json()) | ||||
|   .then((data) => data.map(parseUser)) | ||||
| } | ||||
| const UserSearch = { | ||||
|   search | ||||
|  |  | |||
|  | @ -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 = { | ||||
|  |  | |||
|  | @ -19,6 +19,9 @@ const fetchAndUpdate = ({store, credentials, timeline = 'friends', older = false | |||
|   const args = { timeline, credentials } | ||||
|   const rootState = store.rootState || store.state | ||||
|   const timelineData = rootState.statuses.timelines[camelCase(timeline)] | ||||
|   const hideMutedPosts = typeof rootState.config.hideMutedPosts === 'undefined' | ||||
|     ? rootState.instance.hideMutedPosts | ||||
|     : rootState.config.hideMutedPosts | ||||
| 
 | ||||
|   if (older) { | ||||
|     args['until'] = until || timelineData.minId | ||||
|  | @ -28,6 +31,7 @@ const fetchAndUpdate = ({store, credentials, timeline = 'friends', older = false | |||
| 
 | ||||
|   args['userId'] = userId | ||||
|   args['tag'] = tag | ||||
|   args['withMuted'] = !hideMutedPosts | ||||
| 
 | ||||
|   const numStatusesBeforeFetch = timelineData.statuses.length | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue
	
	 Henry Jameson
						Henry Jameson