Merge branch 'develop' of git.pleroma.social:pleroma/pleroma-fe into develop

This commit is contained in:
Maksim Pechnikov 2020-01-06 15:47:36 +03:00
commit 2810f10d14
32 changed files with 409 additions and 316 deletions

View file

@ -7,11 +7,11 @@ const FollowRequestCard = {
}, },
methods: { methods: {
approveUser () { approveUser () {
this.$store.state.api.backendInteractor.approveUser(this.user.id) this.$store.state.api.backendInteractor.approveUser({ id: this.user.id })
this.$store.dispatch('removeFollowRequest', this.user) this.$store.dispatch('removeFollowRequest', this.user)
}, },
denyUser () { denyUser () {
this.$store.state.api.backendInteractor.denyUser(this.user.id) this.$store.state.api.backendInteractor.denyUser({ id: this.user.id })
this.$store.dispatch('removeFollowRequest', this.user) this.$store.dispatch('removeFollowRequest', this.user)
} }
} }

View file

@ -3,7 +3,8 @@ import Notifications from '../notifications/notifications.vue'
const tabModeDict = { const tabModeDict = {
mentions: ['mention'], mentions: ['mention'],
'likes+repeats': ['repeat', 'like'], 'likes+repeats': ['repeat', 'like'],
follows: ['follow'] follows: ['follow'],
moves: ['move']
} }
const Interactions = { const Interactions = {

View file

@ -21,6 +21,10 @@
key="follows" key="follows"
:label="$t('interactions.follows')" :label="$t('interactions.follows')"
/> />
<span
key="moves"
:label="$t('interactions.moves')"
/>
</tab-switcher> </tab-switcher>
<Notifications <Notifications
ref="notifications" ref="notifications"

View file

@ -45,12 +45,12 @@ const ModerationTools = {
toggleTag (tag) { toggleTag (tag) {
const store = this.$store const store = this.$store
if (this.tagsSet.has(tag)) { if (this.tagsSet.has(tag)) {
store.state.api.backendInteractor.untagUser(this.user, tag).then(response => { store.state.api.backendInteractor.untagUser({ user: this.user, tag }).then(response => {
if (!response.ok) { return } if (!response.ok) { return }
store.commit('untagUser', { user: this.user, tag }) store.commit('untagUser', { user: this.user, tag })
}) })
} else { } else {
store.state.api.backendInteractor.tagUser(this.user, tag).then(response => { store.state.api.backendInteractor.tagUser({ user: this.user, tag }).then(response => {
if (!response.ok) { return } if (!response.ok) { return }
store.commit('tagUser', { user: this.user, tag }) store.commit('tagUser', { user: this.user, tag })
}) })
@ -59,19 +59,19 @@ const ModerationTools = {
toggleRight (right) { toggleRight (right) {
const store = this.$store const store = this.$store
if (this.user.rights[right]) { if (this.user.rights[right]) {
store.state.api.backendInteractor.deleteRight(this.user, right).then(response => { store.state.api.backendInteractor.deleteRight({ user: this.user, right }).then(response => {
if (!response.ok) { return } if (!response.ok) { return }
store.commit('updateRight', { user: this.user, right: right, value: false }) store.commit('updateRight', { user: this.user, right, value: false })
}) })
} else { } else {
store.state.api.backendInteractor.addRight(this.user, right).then(response => { store.state.api.backendInteractor.addRight({ user: this.user, right }).then(response => {
if (!response.ok) { return } if (!response.ok) { return }
store.commit('updateRight', { user: this.user, right: right, value: true }) store.commit('updateRight', { user: this.user, right, value: true })
}) })
} }
}, },
toggleActivationStatus () { toggleActivationStatus () {
this.$store.dispatch('toggleActivationStatus', this.user) this.$store.dispatch('toggleActivationStatus', { user: this.user })
}, },
deleteUserDialog (show) { deleteUserDialog (show) {
this.showDeleteUserDialog = show this.showDeleteUserDialog = show
@ -80,7 +80,7 @@ const ModerationTools = {
const store = this.$store const store = this.$store
const user = this.user const user = this.user
const { id, name } = user const { id, name } = user
store.state.api.backendInteractor.deleteUser(user) store.state.api.backendInteractor.deleteUser({ user })
.then(e => { .then(e => {
this.$store.dispatch('markStatusesAsDeleted', status => user.id === status.user.id) this.$store.dispatch('markStatusesAsDeleted', status => user.id === status.user.id)
const isProfile = this.$route.name === 'external-user-profile' || this.$route.name === 'user-profile' const isProfile = this.$route.name === 'external-user-profile' || this.$route.name === 'user-profile'

View file

@ -43,18 +43,18 @@ const Notification = {
const user = this.notification.from_profile const user = this.notification.from_profile
return highlightStyle(highlight[user.screen_name]) return highlightStyle(highlight[user.screen_name])
}, },
userInStore () {
return this.$store.getters.findUser(this.notification.from_profile.id)
},
user () { user () {
if (this.userInStore) { return this.$store.getters.findUser(this.notification.from_profile.id)
return this.userInStore
}
return this.notification.from_profile
}, },
userProfileLink () { userProfileLink () {
return this.generateUserProfileLink(this.user) return this.generateUserProfileLink(this.user)
}, },
targetUser () {
return this.$store.getters.findUser(this.notification.target.id)
},
targetUserProfileLink () {
return this.generateUserProfileLink(this.targetUser)
},
needMute () { needMute () {
return this.user.muted return this.user.muted
} }

View file

@ -74,9 +74,13 @@
<i class="fa icon-user-plus lit" /> <i class="fa icon-user-plus lit" />
<small>{{ $t('notifications.followed_you') }}</small> <small>{{ $t('notifications.followed_you') }}</small>
</span> </span>
<span v-if="notification.type === 'move'">
<i class="fa icon-arrow-curved lit" />
<small>{{ $t('notifications.migrated_to') }}</small>
</span>
</div> </div>
<div <div
v-if="notification.type === 'follow'" v-if="notification.type === 'follow' || notification.type === 'move'"
class="timeago" class="timeago"
> >
<span class="faint"> <span class="faint">
@ -115,6 +119,14 @@
@{{ notification.from_profile.screen_name }} @{{ notification.from_profile.screen_name }}
</router-link> </router-link>
</div> </div>
<div
v-else-if="notification.type === 'move'"
class="move-text"
>
<router-link :to="targetUserProfileLink">
@{{ notification.target.screen_name }}
</router-link>
</div>
<template v-else> <template v-else>
<status <status
class="faint" class="faint"

View file

@ -47,6 +47,11 @@ const Notifications = {
components: { components: {
Notification Notification
}, },
created () {
const { dispatch } = this.$store
dispatch('fetchAndUpdateNotifications')
},
watch: { watch: {
unseenCount (count) { unseenCount (count) {
if (count > 0) { if (count > 0) {

View file

@ -76,7 +76,7 @@
} }
} }
.follow-text { .follow-text, .move-text {
padding: 0.5em 0; padding: 0.5em 0;
} }
@ -151,6 +151,11 @@
color: var(--cOrange, $fallback--cOrange); color: var(--cOrange, $fallback--cOrange);
} }
.icon-arrow-curved.lit {
color: $fallback--cBlue;
color: var(--cBlue, $fallback--cBlue);
}
.status-content { .status-content {
margin: 0; margin: 0;
max-height: 300px; max-height: 300px;

View file

@ -10,7 +10,7 @@ const PublicAndExternalTimeline = {
this.$store.dispatch('startFetchingTimeline', { timeline: 'publicAndExternal' }) this.$store.dispatch('startFetchingTimeline', { timeline: 'publicAndExternal' })
}, },
destroyed () { destroyed () {
this.$store.dispatch('stopFetching', 'publicAndExternal') this.$store.dispatch('stopFetchingTimeline', 'publicAndExternal')
} }
} }

View file

@ -10,7 +10,7 @@ const PublicTimeline = {
this.$store.dispatch('startFetchingTimeline', { timeline: 'public' }) this.$store.dispatch('startFetchingTimeline', { timeline: 'public' })
}, },
destroyed () { destroyed () {
this.$store.dispatch('stopFetching', 'public') this.$store.dispatch('stopFetchingTimeline', 'public')
} }
} }

View file

@ -84,7 +84,7 @@ const settings = {
} }
}]) }])
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}), .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}),
// Special cases (need to transform values) // Special cases (need to transform values or perform actions first)
muteWordsString: { muteWordsString: {
get () { return this.$store.getters.mergedConfig.muteWords.join('\n') }, get () { return this.$store.getters.mergedConfig.muteWords.join('\n') },
set (value) { set (value) {
@ -93,6 +93,22 @@ const settings = {
value: filter(value.split('\n'), (word) => trim(word).length > 0) value: filter(value.split('\n'), (word) => trim(word).length > 0)
}) })
} }
},
useStreamingApi: {
get () { return this.$store.getters.mergedConfig.useStreamingApi },
set (value) {
const promise = value
? this.$store.dispatch('enableMastoSockets')
: this.$store.dispatch('disableMastoSockets')
promise.then(() => {
this.$store.dispatch('setOption', { name: 'useStreamingApi', value })
}).catch((e) => {
console.error('Failed starting MastoAPI Streaming socket', e)
this.$store.dispatch('disableMastoSockets')
this.$store.dispatch('setOption', { name: 'useStreamingApi', value: false })
})
}
} }
}, },
// Updating nested properties // Updating nested properties

View file

@ -73,6 +73,15 @@
</li> </li>
</ul> </ul>
</li> </li>
<li>
<Checkbox v-model="useStreamingApi">
{{ $t('settings.useStreamingApi') }}
<br/>
<small>
{{ $t('settings.useStreamingApiWarning') }}
</small>
</Checkbox>
</li>
<li> <li>
<Checkbox v-model="autoLoad"> <Checkbox v-model="autoLoad">
{{ $t('settings.autoload') }} {{ $t('settings.autoload') }}
@ -314,6 +323,11 @@
{{ $t('settings.notification_visibility_mentions') }} {{ $t('settings.notification_visibility_mentions') }}
</Checkbox> </Checkbox>
</li> </li>
<li>
<Checkbox v-model="notificationVisibility.moves">
{{ $t('settings.notification_visibility_moves') }}
</Checkbox>
</li>
</ul> </ul>
</div> </div>
<div> <div>

View file

@ -19,7 +19,7 @@ const TagTimeline = {
} }
}, },
destroyed () { destroyed () {
this.$store.dispatch('stopFetching', 'tag') this.$store.dispatch('stopFetchingTimeline', 'tag')
} }
} }

View file

@ -112,9 +112,9 @@ const UserProfile = {
} }
}, },
stopFetching () { stopFetching () {
this.$store.dispatch('stopFetching', 'user') this.$store.dispatch('stopFetchingTimeline', 'user')
this.$store.dispatch('stopFetching', 'favorites') this.$store.dispatch('stopFetchingTimeline', 'favorites')
this.$store.dispatch('stopFetching', 'media') this.$store.dispatch('stopFetchingTimeline', 'media')
}, },
switchUser (userNameOrId) { switchUser (userNameOrId) {
this.stopFetching() this.stopFetching()

View file

@ -64,7 +64,7 @@ const UserReportingModal = {
forward: this.forward, forward: this.forward,
statusIds: this.statusIdsToReport statusIds: this.statusIdsToReport
} }
this.$store.state.api.backendInteractor.reportUser(params) this.$store.state.api.backendInteractor.reportUser({ ...params })
.then(() => { .then(() => {
this.processing = false this.processing = false
this.resetState() this.resetState()

View file

@ -139,7 +139,7 @@ const Mfa = {
// fetch settings from server // fetch settings from server
async fetchSettings () { async fetchSettings () {
let result = await this.backendInteractor.fetchSettingsMFA() let result = await this.backendInteractor.settingsMFA()
if (result.error) return if (result.error) return
this.settings = result.settings this.settings = result.settings
this.settings.available = true this.settings.available = true

View file

@ -242,7 +242,7 @@ const UserSettings = {
}) })
}, },
importFollows (file) { importFollows (file) {
return this.$store.state.api.backendInteractor.importFollows(file) return this.$store.state.api.backendInteractor.importFollows({ file })
.then((status) => { .then((status) => {
if (!status) { if (!status) {
throw new Error('failed') throw new Error('failed')
@ -250,7 +250,7 @@ const UserSettings = {
}) })
}, },
importBlocks (file) { importBlocks (file) {
return this.$store.state.api.backendInteractor.importBlocks(file) return this.$store.state.api.backendInteractor.importBlocks({ file })
.then((status) => { .then((status) => {
if (!status) { if (!status) {
throw new Error('failed') throw new Error('failed')
@ -297,7 +297,7 @@ const UserSettings = {
newPassword: this.changePasswordInputs[1], newPassword: this.changePasswordInputs[1],
newPasswordConfirmation: this.changePasswordInputs[2] newPasswordConfirmation: this.changePasswordInputs[2]
} }
this.$store.state.api.backendInteractor.changePassword(params) this.$store.state.api.backendInteractor.changePassword({ params })
.then((res) => { .then((res) => {
if (res.status === 'success') { if (res.status === 'success') {
this.changedPassword = true this.changedPassword = true
@ -314,7 +314,7 @@ const UserSettings = {
email: this.newEmail, email: this.newEmail,
password: this.changeEmailPassword password: this.changeEmailPassword
} }
this.$store.state.api.backendInteractor.changeEmail(params) this.$store.state.api.backendInteractor.changeEmail({ params })
.then((res) => { .then((res) => {
if (res.status === 'success') { if (res.status === 'success') {
this.changedEmail = true this.changedEmail = true

View file

@ -110,7 +110,8 @@
"notifications": "Notifications", "notifications": "Notifications",
"read": "Read!", "read": "Read!",
"repeated_you": "repeated your status", "repeated_you": "repeated your status",
"no_more_notifications": "No more notifications" "no_more_notifications": "No more notifications",
"migrated_to": "migrated to"
}, },
"polls": { "polls": {
"add_poll": "Add Poll", "add_poll": "Add Poll",
@ -140,6 +141,7 @@
"interactions": { "interactions": {
"favs_repeats": "Repeats and Favorites", "favs_repeats": "Repeats and Favorites",
"follows": "New follows", "follows": "New follows",
"moves": "User migrates",
"load_older": "Load older interactions" "load_older": "Load older interactions"
}, },
"post_status": { "post_status": {
@ -311,6 +313,7 @@
"notification_visibility_likes": "Likes", "notification_visibility_likes": "Likes",
"notification_visibility_mentions": "Mentions", "notification_visibility_mentions": "Mentions",
"notification_visibility_repeats": "Repeats", "notification_visibility_repeats": "Repeats",
"notification_visibility_moves": "User Migrates",
"no_rich_text_description": "Strip rich text formatting from all posts", "no_rich_text_description": "Strip rich text formatting from all posts",
"no_blocks": "No blocks", "no_blocks": "No blocks",
"no_mutes": "No mutes", "no_mutes": "No mutes",
@ -358,6 +361,8 @@
"post_status_content_type": "Post status content type", "post_status_content_type": "Post status content type",
"stop_gifs": "Play-on-hover GIFs", "stop_gifs": "Play-on-hover GIFs",
"streaming": "Enable automatic streaming of new posts when scrolled to the top", "streaming": "Enable automatic streaming of new posts when scrolled to the top",
"useStreamingApi": "Receive posts and notifications real-time",
"useStreamingApiWarning": "(Not recommended, experimental, known to skip posts)",
"text": "Text", "text": "Text",
"theme": "Theme", "theme": "Theme",
"theme_help": "Use hex color codes (#rrggbb) to customize your color theme.", "theme_help": "Use hex color codes (#rrggbb) to customize your color theme.",

View file

@ -219,6 +219,8 @@
"subject_input_always_show": "Всегда показывать поле ввода темы", "subject_input_always_show": "Всегда показывать поле ввода темы",
"stop_gifs": "Проигрывать GIF анимации только при наведении", "stop_gifs": "Проигрывать GIF анимации только при наведении",
"streaming": "Включить автоматическую загрузку новых сообщений при прокрутке вверх", "streaming": "Включить автоматическую загрузку новых сообщений при прокрутке вверх",
"useStreamingApi": "Получать сообщения и уведомления в реальном времени",
"useStreamingApiWarning": "(Не рекомендуется, экспериментально, сообщения могут пропадать)",
"text": "Текст", "text": "Текст",
"theme": "Тема", "theme": "Тема",
"theme_help": "Используйте шестнадцатеричные коды цветов (#rrggbb) для настройки темы.", "theme_help": "Используйте шестнадцатеричные коды цветов (#rrggbb) для настройки темы.",

View file

@ -6,6 +6,7 @@ const api = {
backendInteractor: backendInteractorService(), backendInteractor: backendInteractorService(),
fetchers: {}, fetchers: {},
socket: null, socket: null,
mastoUserSocket: null,
followRequests: [] followRequests: []
}, },
mutations: { mutations: {
@ -15,7 +16,8 @@ const api = {
addFetcher (state, { fetcherName, fetcher }) { addFetcher (state, { fetcherName, fetcher }) {
state.fetchers[fetcherName] = fetcher state.fetchers[fetcherName] = fetcher
}, },
removeFetcher (state, { fetcherName }) { removeFetcher (state, { fetcherName, fetcher }) {
window.clearInterval(fetcher)
delete state.fetchers[fetcherName] delete state.fetchers[fetcherName]
}, },
setWsToken (state, token) { setWsToken (state, token) {
@ -29,32 +31,134 @@ const api = {
} }
}, },
actions: { actions: {
startFetchingTimeline (store, { timeline = 'friends', tag = false, userId = false }) { // Global MastoAPI socket control, in future should disable ALL sockets/(re)start relevant sockets
// Don't start fetching if we already are. enableMastoSockets (store) {
const { state, dispatch } = store
if (state.mastoUserSocket) return
return dispatch('startMastoUserSocket')
},
disableMastoSockets (store) {
const { state, dispatch } = store
if (!state.mastoUserSocket) return
return dispatch('stopMastoUserSocket')
},
// MastoAPI 'User' sockets
startMastoUserSocket (store) {
return new Promise((resolve, reject) => {
try {
const { state, dispatch, rootState } = store
const timelineData = rootState.statuses.timelines.friends
state.mastoUserSocket = state.backendInteractor.startUserSocket({ store })
state.mastoUserSocket.addEventListener(
'message',
({ detail: message }) => {
if (!message) return // pings
if (message.event === 'notification') {
dispatch('addNewNotifications', {
notifications: [message.notification],
older: false
})
} else if (message.event === 'update') {
dispatch('addNewStatuses', {
statuses: [message.status],
userId: false,
showImmediately: timelineData.visibleStatuses.length === 0,
timeline: 'friends'
})
}
}
)
state.mastoUserSocket.addEventListener('error', ({ detail: error }) => {
console.error('Error in MastoAPI websocket:', error)
})
state.mastoUserSocket.addEventListener('close', ({ detail: closeEvent }) => {
const ignoreCodes = new Set([
1000, // Normal (intended) closure
1001 // Going away
])
const { code } = closeEvent
if (ignoreCodes.has(code)) {
console.debug(`Not restarting socket becasue of closure code ${code} is in ignore list`)
} else {
console.warn(`MastoAPI websocket disconnected, restarting. CloseEvent code: ${code}`)
dispatch('startFetchingTimeline', { timeline: 'friends' })
dispatch('startFetchingNotifications')
dispatch('restartMastoUserSocket')
}
})
resolve()
} catch (e) {
reject(e)
}
})
},
restartMastoUserSocket ({ dispatch }) {
// This basically starts MastoAPI user socket and stops conventional
// fetchers when connection reestablished
return dispatch('startMastoUserSocket').then(() => {
dispatch('stopFetchingTimeline', { timeline: 'friends' })
dispatch('stopFetchingNotifications')
})
},
stopMastoUserSocket ({ state, dispatch }) {
dispatch('startFetchingTimeline', { timeline: 'friends' })
dispatch('startFetchingNotifications')
console.log(state.mastoUserSocket)
state.mastoUserSocket.close()
},
// Timelines
startFetchingTimeline (store, {
timeline = 'friends',
tag = false,
userId = false
}) {
if (store.state.fetchers[timeline]) return if (store.state.fetchers[timeline]) return
const fetcher = store.state.backendInteractor.startFetchingTimeline({ timeline, store, userId, tag }) const fetcher = store.state.backendInteractor.startFetchingTimeline({
timeline, store, userId, tag
})
store.commit('addFetcher', { fetcherName: timeline, fetcher }) store.commit('addFetcher', { fetcherName: timeline, fetcher })
}, },
startFetchingNotifications (store) { stopFetchingTimeline (store, timeline) {
// Don't start fetching if we already are. const fetcher = store.state.fetchers[timeline]
if (store.state.fetchers['notifications']) return if (!fetcher) return
store.commit('removeFetcher', { fetcherName: timeline, fetcher })
},
// Notifications
startFetchingNotifications (store) {
if (store.state.fetchers.notifications) return
const fetcher = store.state.backendInteractor.startFetchingNotifications({ store }) const fetcher = store.state.backendInteractor.startFetchingNotifications({ store })
store.commit('addFetcher', { fetcherName: 'notifications', fetcher }) store.commit('addFetcher', { fetcherName: 'notifications', fetcher })
}, },
startFetchingFollowRequest (store) { stopFetchingNotifications (store) {
// Don't start fetching if we already are. const fetcher = store.state.fetchers.notifications
if (store.state.fetchers['followRequest']) return if (!fetcher) return
store.commit('removeFetcher', { fetcherName: 'notifications', fetcher })
},
fetchAndUpdateNotifications (store) {
store.state.backendInteractor.fetchAndUpdateNotifications({ store })
},
const fetcher = store.state.backendInteractor.startFetchingFollowRequest({ store }) // Follow requests
store.commit('addFetcher', { fetcherName: 'followRequest', fetcher }) startFetchingFollowRequests (store) {
if (store.state.fetchers['followRequests']) return
const fetcher = store.state.backendInteractor.startFetchingFollowRequests({ store })
store.commit('addFetcher', { fetcherName: 'followRequests', fetcher })
}, },
stopFetching (store, fetcherName) { stopFetchingFollowRequests (store) {
const fetcher = store.state.fetchers[fetcherName] const fetcher = store.state.fetchers.followRequests
window.clearInterval(fetcher) if (!fetcher) return
store.commit('removeFetcher', { fetcherName }) store.commit('removeFetcher', { fetcherName: 'followRequests', fetcher })
}, },
removeFollowRequest (store, request) {
let requests = store.state.followRequests.filter((it) => it !== request)
store.commit('setFollowRequests', requests)
},
// Pleroma websocket
setWsToken (store, token) { setWsToken (store, token) {
store.commit('setWsToken', token) store.commit('setWsToken', token)
}, },
@ -72,10 +176,6 @@ const api = {
disconnectFromSocket ({ commit, state }) { disconnectFromSocket ({ commit, state }) {
state.socket && state.socket.disconnect() state.socket && state.socket.disconnect()
commit('setSocket', null) commit('setSocket', null)
},
removeFollowRequest (store, request) {
let requests = store.state.followRequests.filter((it) => it !== request)
store.commit('setFollowRequests', requests)
} }
} }
} }

View file

@ -28,13 +28,15 @@ export const defaultState = {
follows: true, follows: true,
mentions: true, mentions: true,
likes: true, likes: true,
repeats: true repeats: true,
moves: true
}, },
webPushNotifications: false, webPushNotifications: false,
muteWords: [], muteWords: [],
highlight: {}, highlight: {},
interfaceLanguage: browserLocale, interfaceLanguage: browserLocale,
hideScopeNotice: false, hideScopeNotice: false,
useStreamingApi: false,
scopeCopy: undefined, // instance default scopeCopy: undefined, // instance default
subjectLineBehavior: undefined, // instance default subjectLineBehavior: undefined, // instance default
alwaysShowSubjectInput: undefined, // instance default alwaysShowSubjectInput: undefined, // instance default

View file

@ -9,7 +9,7 @@ const oauthTokens = {
}) })
}, },
revokeToken ({ rootState, commit, state }, id) { revokeToken ({ rootState, commit, state }, id) {
rootState.api.backendInteractor.revokeOAuthToken(id).then((response) => { rootState.api.backendInteractor.revokeOAuthToken({ id }).then((response) => {
if (response.status === 201) { if (response.status === 201) {
commit('swapTokens', state.tokens.filter(token => token.id !== id)) commit('swapTokens', state.tokens.filter(token => token.id !== id))
} }

View file

@ -40,7 +40,7 @@ const polls = {
commit('mergeOrAddPoll', poll) commit('mergeOrAddPoll', poll)
}, },
updateTrackedPoll ({ rootState, dispatch, commit }, pollId) { updateTrackedPoll ({ rootState, dispatch, commit }, pollId) {
rootState.api.backendInteractor.fetchPoll(pollId).then(poll => { rootState.api.backendInteractor.fetchPoll({ pollId }).then(poll => {
setTimeout(() => { setTimeout(() => {
if (rootState.polls.trackedPolls[pollId]) { if (rootState.polls.trackedPolls[pollId]) {
dispatch('updateTrackedPoll', pollId) dispatch('updateTrackedPoll', pollId)
@ -59,7 +59,7 @@ const polls = {
commit('untrackPoll', pollId) commit('untrackPoll', pollId)
}, },
votePoll ({ rootState, commit }, { id, pollId, choices }) { votePoll ({ rootState, commit }, { id, pollId, choices }) {
return rootState.api.backendInteractor.vote(pollId, choices).then(poll => { return rootState.api.backendInteractor.vote({ pollId, choices }).then(poll => {
commit('mergeOrAddPoll', poll) commit('mergeOrAddPoll', poll)
return poll return poll
}) })

View file

@ -67,7 +67,8 @@ const visibleNotificationTypes = (rootState) => {
rootState.config.notificationVisibility.likes && 'like', rootState.config.notificationVisibility.likes && 'like',
rootState.config.notificationVisibility.mentions && 'mention', rootState.config.notificationVisibility.mentions && 'mention',
rootState.config.notificationVisibility.repeats && 'repeat', rootState.config.notificationVisibility.repeats && 'repeat',
rootState.config.notificationVisibility.follows && 'follow' rootState.config.notificationVisibility.follows && 'follow',
rootState.config.notificationVisibility.moves && 'move'
].filter(_ => _) ].filter(_ => _)
} }
@ -306,7 +307,7 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us
const addNewNotifications = (state, { dispatch, notifications, older, visibleNotificationTypes, rootGetters }) => { const addNewNotifications = (state, { dispatch, notifications, older, visibleNotificationTypes, rootGetters }) => {
each(notifications, (notification) => { each(notifications, (notification) => {
if (notification.type !== 'follow') { if (notification.type !== 'follow' && notification.type !== 'move') {
notification.action = addStatusToGlobalStorage(state, notification.action).item notification.action = addStatusToGlobalStorage(state, notification.action).item
notification.status = notification.status && addStatusToGlobalStorage(state, notification.status).item notification.status = notification.status && addStatusToGlobalStorage(state, notification.status).item
} }
@ -339,6 +340,9 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot
case 'follow': case 'follow':
i18nString = 'followed_you' i18nString = 'followed_you'
break break
case 'move':
i18nString = 'migrated_to'
break
} }
if (i18nString) { if (i18nString) {
@ -558,45 +562,45 @@ const statuses = {
favorite ({ rootState, commit }, status) { favorite ({ rootState, commit }, status) {
// Optimistic favoriting... // Optimistic favoriting...
commit('setFavorited', { status, value: true }) commit('setFavorited', { status, value: true })
rootState.api.backendInteractor.favorite(status.id) rootState.api.backendInteractor.favorite({ id: status.id })
.then(status => commit('setFavoritedConfirm', { status, user: rootState.users.currentUser })) .then(status => commit('setFavoritedConfirm', { status, user: rootState.users.currentUser }))
}, },
unfavorite ({ rootState, commit }, status) { unfavorite ({ rootState, commit }, status) {
// Optimistic unfavoriting... // Optimistic unfavoriting...
commit('setFavorited', { status, value: false }) commit('setFavorited', { status, value: false })
rootState.api.backendInteractor.unfavorite(status.id) rootState.api.backendInteractor.unfavorite({ id: status.id })
.then(status => commit('setFavoritedConfirm', { status, user: rootState.users.currentUser })) .then(status => commit('setFavoritedConfirm', { status, user: rootState.users.currentUser }))
}, },
fetchPinnedStatuses ({ rootState, dispatch }, userId) { fetchPinnedStatuses ({ rootState, dispatch }, userId) {
rootState.api.backendInteractor.fetchPinnedStatuses(userId) rootState.api.backendInteractor.fetchPinnedStatuses({ id: userId })
.then(statuses => dispatch('addNewStatuses', { statuses, timeline: 'user', userId, showImmediately: true, noIdUpdate: true })) .then(statuses => dispatch('addNewStatuses', { statuses, timeline: 'user', userId, showImmediately: true, noIdUpdate: true }))
}, },
pinStatus ({ rootState, dispatch }, statusId) { pinStatus ({ rootState, dispatch }, statusId) {
return rootState.api.backendInteractor.pinOwnStatus(statusId) return rootState.api.backendInteractor.pinOwnStatus({ id: statusId })
.then((status) => dispatch('addNewStatuses', { statuses: [status] })) .then((status) => dispatch('addNewStatuses', { statuses: [status] }))
}, },
unpinStatus ({ rootState, dispatch }, statusId) { unpinStatus ({ rootState, dispatch }, statusId) {
rootState.api.backendInteractor.unpinOwnStatus(statusId) rootState.api.backendInteractor.unpinOwnStatus({ id: statusId })
.then((status) => dispatch('addNewStatuses', { statuses: [status] })) .then((status) => dispatch('addNewStatuses', { statuses: [status] }))
}, },
muteConversation ({ rootState, commit }, statusId) { muteConversation ({ rootState, commit }, statusId) {
return rootState.api.backendInteractor.muteConversation(statusId) return rootState.api.backendInteractor.muteConversation({ id: statusId })
.then((status) => commit('setMutedStatus', status)) .then((status) => commit('setMutedStatus', status))
}, },
unmuteConversation ({ rootState, commit }, statusId) { unmuteConversation ({ rootState, commit }, statusId) {
return rootState.api.backendInteractor.unmuteConversation(statusId) return rootState.api.backendInteractor.unmuteConversation({ id: statusId })
.then((status) => commit('setMutedStatus', status)) .then((status) => commit('setMutedStatus', status))
}, },
retweet ({ rootState, commit }, status) { retweet ({ rootState, commit }, status) {
// Optimistic retweeting... // Optimistic retweeting...
commit('setRetweeted', { status, value: true }) commit('setRetweeted', { status, value: true })
rootState.api.backendInteractor.retweet(status.id) rootState.api.backendInteractor.retweet({ id: status.id })
.then(status => commit('setRetweetedConfirm', { status: status.retweeted_status, user: rootState.users.currentUser })) .then(status => commit('setRetweetedConfirm', { status: status.retweeted_status, user: rootState.users.currentUser }))
}, },
unretweet ({ rootState, commit }, status) { unretweet ({ rootState, commit }, status) {
// Optimistic unretweeting... // Optimistic unretweeting...
commit('setRetweeted', { status, value: false }) commit('setRetweeted', { status, value: false })
rootState.api.backendInteractor.unretweet(status.id) rootState.api.backendInteractor.unretweet({ id: status.id })
.then(status => commit('setRetweetedConfirm', { status, user: rootState.users.currentUser })) .then(status => commit('setRetweetedConfirm', { status, user: rootState.users.currentUser }))
}, },
queueFlush ({ rootState, commit }, { timeline, id }) { queueFlush ({ rootState, commit }, { timeline, id }) {
@ -611,19 +615,19 @@ const statuses = {
}, },
fetchFavsAndRepeats ({ rootState, commit }, id) { fetchFavsAndRepeats ({ rootState, commit }, id) {
Promise.all([ Promise.all([
rootState.api.backendInteractor.fetchFavoritedByUsers(id), rootState.api.backendInteractor.fetchFavoritedByUsers({ id }),
rootState.api.backendInteractor.fetchRebloggedByUsers(id) rootState.api.backendInteractor.fetchRebloggedByUsers({ id })
]).then(([favoritedByUsers, rebloggedByUsers]) => { ]).then(([favoritedByUsers, rebloggedByUsers]) => {
commit('addFavs', { id, favoritedByUsers, currentUser: rootState.users.currentUser }) commit('addFavs', { id, favoritedByUsers, currentUser: rootState.users.currentUser })
commit('addRepeats', { id, rebloggedByUsers, currentUser: rootState.users.currentUser }) commit('addRepeats', { id, rebloggedByUsers, currentUser: rootState.users.currentUser })
}) })
}, },
fetchFavs ({ rootState, commit }, id) { fetchFavs ({ rootState, commit }, id) {
rootState.api.backendInteractor.fetchFavoritedByUsers(id) rootState.api.backendInteractor.fetchFavoritedByUsers({ id })
.then(favoritedByUsers => commit('addFavs', { id, favoritedByUsers, currentUser: rootState.users.currentUser })) .then(favoritedByUsers => commit('addFavs', { id, favoritedByUsers, currentUser: rootState.users.currentUser }))
}, },
fetchRepeats ({ rootState, commit }, id) { fetchRepeats ({ rootState, commit }, id) {
rootState.api.backendInteractor.fetchRebloggedByUsers(id) rootState.api.backendInteractor.fetchRebloggedByUsers({ id })
.then(rebloggedByUsers => commit('addRepeats', { id, rebloggedByUsers, currentUser: rootState.users.currentUser })) .then(rebloggedByUsers => commit('addRepeats', { id, rebloggedByUsers, currentUser: rootState.users.currentUser }))
}, },
search (store, { q, resolve, limit, offset, following }) { search (store, { q, resolve, limit, offset, following }) {

View file

@ -32,7 +32,7 @@ const getNotificationPermission = () => {
} }
const blockUser = (store, id) => { const blockUser = (store, id) => {
return store.rootState.api.backendInteractor.blockUser(id) return store.rootState.api.backendInteractor.blockUser({ id })
.then((relationship) => { .then((relationship) => {
store.commit('updateUserRelationship', [relationship]) store.commit('updateUserRelationship', [relationship])
store.commit('addBlockId', id) store.commit('addBlockId', id)
@ -43,12 +43,12 @@ const blockUser = (store, id) => {
} }
const unblockUser = (store, id) => { const unblockUser = (store, id) => {
return store.rootState.api.backendInteractor.unblockUser(id) return store.rootState.api.backendInteractor.unblockUser({ id })
.then((relationship) => store.commit('updateUserRelationship', [relationship])) .then((relationship) => store.commit('updateUserRelationship', [relationship]))
} }
const muteUser = (store, id) => { const muteUser = (store, id) => {
return store.rootState.api.backendInteractor.muteUser(id) return store.rootState.api.backendInteractor.muteUser({ id })
.then((relationship) => { .then((relationship) => {
store.commit('updateUserRelationship', [relationship]) store.commit('updateUserRelationship', [relationship])
store.commit('addMuteId', id) store.commit('addMuteId', id)
@ -56,7 +56,7 @@ const muteUser = (store, id) => {
} }
const unmuteUser = (store, id) => { const unmuteUser = (store, id) => {
return store.rootState.api.backendInteractor.unmuteUser(id) return store.rootState.api.backendInteractor.unmuteUser({ id })
.then((relationship) => store.commit('updateUserRelationship', [relationship])) .then((relationship) => store.commit('updateUserRelationship', [relationship]))
} }
@ -324,11 +324,11 @@ const users = {
commit('clearFollowers', userId) commit('clearFollowers', userId)
}, },
subscribeUser ({ rootState, commit }, id) { subscribeUser ({ rootState, commit }, id) {
return rootState.api.backendInteractor.subscribeUser(id) return rootState.api.backendInteractor.subscribeUser({ id })
.then((relationship) => commit('updateUserRelationship', [relationship])) .then((relationship) => commit('updateUserRelationship', [relationship]))
}, },
unsubscribeUser ({ rootState, commit }, id) { unsubscribeUser ({ rootState, commit }, id) {
return rootState.api.backendInteractor.unsubscribeUser(id) return rootState.api.backendInteractor.unsubscribeUser({ id })
.then((relationship) => commit('updateUserRelationship', [relationship])) .then((relationship) => commit('updateUserRelationship', [relationship]))
}, },
toggleActivationStatus ({ rootState, commit }, user) { toggleActivationStatus ({ rootState, commit }, user) {
@ -373,8 +373,10 @@ const users = {
}, },
addNewNotifications (store, { notifications }) { addNewNotifications (store, { notifications }) {
const users = map(notifications, 'from_profile') const users = map(notifications, 'from_profile')
const targetUsers = map(notifications, 'target')
const notificationIds = notifications.map(_ => _.id) const notificationIds = notifications.map(_ => _.id)
store.commit('addNewUsers', users) store.commit('addNewUsers', users)
store.commit('addNewUsers', targetUsers)
const notificationsObject = store.rootState.statuses.notifications.idStore const notificationsObject = store.rootState.statuses.notifications.idStore
const relevantNotifications = Object.entries(notificationsObject) const relevantNotifications = Object.entries(notificationsObject)
@ -387,7 +389,7 @@ const users = {
}) })
}, },
searchUsers (store, query) { searchUsers (store, query) {
return store.rootState.api.backendInteractor.searchUsers(query) return store.rootState.api.backendInteractor.searchUsers({ query })
.then((users) => { .then((users) => {
store.commit('addNewUsers', users) store.commit('addNewUsers', users)
return users return users
@ -399,7 +401,7 @@ const users = {
let rootState = store.rootState let rootState = store.rootState
try { try {
let data = await rootState.api.backendInteractor.register(userInfo) let data = await rootState.api.backendInteractor.register({ ...userInfo })
store.commit('signUpSuccess') store.commit('signUpSuccess')
store.commit('setToken', data.access_token) store.commit('setToken', data.access_token)
store.dispatch('loginUser', data.access_token) store.dispatch('loginUser', data.access_token)
@ -436,10 +438,10 @@ const users = {
store.commit('clearCurrentUser') store.commit('clearCurrentUser')
store.dispatch('disconnectFromSocket') store.dispatch('disconnectFromSocket')
store.commit('clearToken') store.commit('clearToken')
store.dispatch('stopFetching', 'friends') store.dispatch('stopFetchingTimeline', 'friends')
store.commit('setBackendInteractor', backendInteractorService(store.getters.getToken())) store.commit('setBackendInteractor', backendInteractorService(store.getters.getToken()))
store.dispatch('stopFetching', 'notifications') store.dispatch('stopFetchingNotifications')
store.dispatch('stopFetching', 'followRequest') store.dispatch('stopFetchingFollowRequests')
store.commit('clearNotifications') store.commit('clearNotifications')
store.commit('resetStatuses') store.commit('resetStatuses')
}) })
@ -474,11 +476,24 @@ const users = {
store.dispatch('initializeSocket') store.dispatch('initializeSocket')
} }
// Start getting fresh posts. const startPolling = () => {
store.dispatch('startFetchingTimeline', { timeline: 'friends' }) // Start getting fresh posts.
store.dispatch('startFetchingTimeline', { timeline: 'friends' })
// Start fetching notifications // Start fetching notifications
store.dispatch('startFetchingNotifications') store.dispatch('startFetchingNotifications')
}
if (store.getters.mergedConfig.useStreamingApi) {
store.dispatch('enableMastoSockets').catch((error) => {
console.error('Failed initializing MastoAPI Streaming socket', error)
startPolling()
}).then(() => {
setTimeout(() => store.dispatch('setNotificationsSilence', false), 10000)
})
} else {
startPolling()
}
// Get user mutes // Get user mutes
store.dispatch('fetchMutes') store.dispatch('fetchMutes')

View file

@ -72,6 +72,7 @@ const MASTODON_MUTE_CONVERSATION = id => `/api/v1/statuses/${id}/mute`
const MASTODON_UNMUTE_CONVERSATION = id => `/api/v1/statuses/${id}/unmute` const MASTODON_UNMUTE_CONVERSATION = id => `/api/v1/statuses/${id}/unmute`
const MASTODON_SEARCH_2 = `/api/v2/search` const MASTODON_SEARCH_2 = `/api/v2/search`
const MASTODON_USER_SEARCH_URL = '/api/v1/accounts/search' const MASTODON_USER_SEARCH_URL = '/api/v1/accounts/search'
const MASTODON_STREAMING = '/api/v1/streaming'
const oldfetch = window.fetch const oldfetch = window.fetch
@ -451,7 +452,7 @@ const deleteRight = ({ right, credentials, ...user }) => {
}) })
} }
const activateUser = ({ credentials, screen_name: nickname }) => { const activateUser = ({ credentials, user: { screen_name: nickname } }) => {
return promisedRequest({ return promisedRequest({
url: ACTIVATE_USER_URL, url: ACTIVATE_USER_URL,
method: 'PATCH', method: 'PATCH',
@ -462,7 +463,7 @@ const activateUser = ({ credentials, screen_name: nickname }) => {
}).then(response => get(response, 'users.0')) }).then(response => get(response, 'users.0'))
} }
const deactivateUser = ({ credentials, screen_name: nickname }) => { const deactivateUser = ({ credentials, user: { screen_name: nickname } }) => {
return promisedRequest({ return promisedRequest({
url: DEACTIVATE_USER_URL, url: DEACTIVATE_USER_URL,
method: 'PATCH', method: 'PATCH',
@ -947,6 +948,99 @@ const search2 = ({ credentials, q, resolve, limit, offset, following }) => {
}) })
} }
export const getMastodonSocketURI = ({ credentials, stream, args = {} }) => {
return Object.entries({
...(credentials
? { access_token: credentials }
: {}
),
stream,
...args
}).reduce((acc, [key, val]) => {
return acc + `${key}=${val}&`
}, MASTODON_STREAMING + '?')
}
const MASTODON_STREAMING_EVENTS = new Set([
'update',
'notification',
'delete',
'filters_changed'
])
// A thin wrapper around WebSocket API that allows adding a pre-processor to it
// Uses EventTarget and a CustomEvent to proxy events
export const ProcessedWS = ({
url,
preprocessor = handleMastoWS,
id = 'Unknown'
}) => {
const eventTarget = new EventTarget()
const socket = new WebSocket(url)
if (!socket) throw new Error(`Failed to create socket ${id}`)
const proxy = (original, eventName, processor = a => a) => {
original.addEventListener(eventName, (eventData) => {
eventTarget.dispatchEvent(new CustomEvent(
eventName,
{ detail: processor(eventData) }
))
})
}
socket.addEventListener('open', (wsEvent) => {
console.debug(`[WS][${id}] Socket connected`, wsEvent)
})
socket.addEventListener('error', (wsEvent) => {
console.debug(`[WS][${id}] Socket errored`, wsEvent)
})
socket.addEventListener('close', (wsEvent) => {
console.debug(
`[WS][${id}] Socket disconnected with code ${wsEvent.code}`,
wsEvent
)
})
// Commented code reason: very spammy, uncomment to enable message debug logging
/*
socket.addEventListener('message', (wsEvent) => {
console.debug(
`[WS][${id}] Message received`,
wsEvent
)
})
/**/
proxy(socket, 'open')
proxy(socket, 'close')
proxy(socket, 'message', preprocessor)
proxy(socket, 'error')
// 1000 = Normal Closure
eventTarget.close = () => { socket.close(1000, 'Shutting down socket') }
return eventTarget
}
export const handleMastoWS = (wsEvent) => {
const { data } = wsEvent
if (!data) return
const parsedEvent = JSON.parse(data)
const { event, payload } = parsedEvent
if (MASTODON_STREAMING_EVENTS.has(event)) {
// MastoBE and PleromaBE both send payload for delete as a PLAIN string
if (event === 'delete') {
return { event, id: payload }
}
const data = payload ? JSON.parse(payload) : null
if (event === 'update') {
return { event, status: parseStatus(data) }
} else if (event === 'notification') {
return { event, notification: parseNotification(data) }
}
} else {
console.warn('Unknown event', wsEvent)
return null
}
}
const apiService = { const apiService = {
verifyCredentials, verifyCredentials,
fetchTimeline, fetchTimeline,

View file

@ -1,236 +1,39 @@
import apiService from '../api/api.service.js' import apiService, { getMastodonSocketURI, ProcessedWS } from '../api/api.service.js'
import timelineFetcherService from '../timeline_fetcher/timeline_fetcher.service.js' import timelineFetcherService from '../timeline_fetcher/timeline_fetcher.service.js'
import notificationsFetcher from '../notifications_fetcher/notifications_fetcher.service.js' import notificationsFetcher from '../notifications_fetcher/notifications_fetcher.service.js'
import followRequestFetcher from '../../services/follow_request_fetcher/follow_request_fetcher.service' import followRequestFetcher from '../../services/follow_request_fetcher/follow_request_fetcher.service'
const backendInteractorService = credentials => { const backendInteractorService = credentials => ({
const fetchStatus = ({ id }) => { startFetchingTimeline ({ timeline, store, userId = false, tag }) {
return apiService.fetchStatus({ id, credentials })
}
const fetchConversation = ({ id }) => {
return apiService.fetchConversation({ id, 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, maxId, sinceId, limit }) => {
return apiService.fetchFollowers({ id, maxId, sinceId, limit, credentials })
}
const fetchUser = ({ id }) => {
return apiService.fetchUser({ id, credentials })
}
const fetchUserRelationship = ({ id }) => {
return apiService.fetchUserRelationship({ id, credentials })
}
const followUser = ({ id, reblogs }) => {
return apiService.followUser({ credentials, id, reblogs })
}
const unfollowUser = (id) => {
return apiService.unfollowUser({ credentials, id })
}
const blockUser = (id) => {
return apiService.blockUser({ credentials, id })
}
const unblockUser = (id) => {
return apiService.unblockUser({ credentials, id })
}
const approveUser = (id) => {
return apiService.approveUser({ credentials, id })
}
const denyUser = (id) => {
return apiService.denyUser({ credentials, id })
}
const startFetchingTimeline = ({ timeline, store, userId = false, tag }) => {
return timelineFetcherService.startFetching({ timeline, store, credentials, userId, tag }) return timelineFetcherService.startFetching({ timeline, store, credentials, userId, tag })
} },
const startFetchingNotifications = ({ store }) => { startFetchingNotifications ({ store }) {
return notificationsFetcher.startFetching({ store, credentials }) return notificationsFetcher.startFetching({ store, credentials })
} },
const startFetchingFollowRequest = ({ store }) => { fetchAndUpdateNotifications ({ store }) {
return notificationsFetcher.fetchAndUpdate({ store, credentials })
},
startFetchingFollowRequest ({ store }) {
return followRequestFetcher.startFetching({ store, credentials }) return followRequestFetcher.startFetching({ store, credentials })
} },
// eslint-disable-next-line camelcase startUserSocket ({ store }) {
const tagUser = ({ screen_name }, tag) => { const serv = store.rootState.instance.server.replace('http', 'ws')
return apiService.tagUser({ screen_name, tag, credentials }) const url = serv + getMastodonSocketURI({ credentials, stream: 'user' })
} return ProcessedWS({ url, id: 'User' })
},
// eslint-disable-next-line camelcase ...Object.entries(apiService).reduce((acc, [key, func]) => {
const untagUser = ({ screen_name }, tag) => { return {
return apiService.untagUser({ screen_name, tag, credentials }) ...acc,
} [key]: (args) => func({ credentials, ...args })
}
}, {}),
// eslint-disable-next-line camelcase verifyCredentials: apiService.verifyCredentials
const addRight = ({ screen_name }, right) => { })
return apiService.addRight({ screen_name, right, credentials })
}
// eslint-disable-next-line camelcase
const deleteRight = ({ screen_name }, right) => {
return apiService.deleteRight({ screen_name, right, credentials })
}
// eslint-disable-next-line camelcase
const activateUser = ({ screen_name }) => {
return apiService.activateUser({ screen_name, credentials })
}
// eslint-disable-next-line camelcase
const deactivateUser = ({ screen_name }) => {
return apiService.deactivateUser({ screen_name, credentials })
}
// eslint-disable-next-line camelcase
const deleteUser = ({ screen_name }) => {
return apiService.deleteUser({ screen_name, credentials })
}
const vote = (pollId, choices) => {
return apiService.vote({ credentials, pollId, choices })
}
const fetchPoll = (pollId) => {
return apiService.fetchPoll({ credentials, pollId })
}
const updateNotificationSettings = ({ settings }) => {
return apiService.updateNotificationSettings({ credentials, settings })
}
const fetchMutes = () => apiService.fetchMutes({ credentials })
const muteUser = (id) => apiService.muteUser({ credentials, id })
const unmuteUser = (id) => apiService.unmuteUser({ credentials, id })
const subscribeUser = (id) => apiService.subscribeUser({ credentials, id })
const unsubscribeUser = (id) => apiService.unsubscribeUser({ credentials, id })
const fetchBlocks = () => apiService.fetchBlocks({ credentials })
const fetchOAuthTokens = () => apiService.fetchOAuthTokens({ credentials })
const revokeOAuthToken = (id) => apiService.revokeOAuthToken({ id, credentials })
const fetchPinnedStatuses = (id) => apiService.fetchPinnedStatuses({ credentials, id })
const pinOwnStatus = (id) => apiService.pinOwnStatus({ credentials, id })
const unpinOwnStatus = (id) => apiService.unpinOwnStatus({ credentials, id })
const muteConversation = (id) => apiService.muteConversation({ credentials, id })
const unmuteConversation = (id) => apiService.unmuteConversation({ credentials, id })
const getCaptcha = () => apiService.getCaptcha()
const register = (params) => apiService.register({ credentials, params })
const updateAvatar = ({ avatar }) => apiService.updateAvatar({ credentials, avatar })
const updateBg = ({ background }) => apiService.updateBg({ credentials, background })
const updateBanner = ({ banner }) => apiService.updateBanner({ credentials, banner })
const updateProfile = ({ params }) => apiService.updateProfile({ credentials, params })
const importBlocks = (file) => apiService.importBlocks({ file, credentials })
const importFollows = (file) => apiService.importFollows({ file, credentials })
const deleteAccount = ({ password }) => apiService.deleteAccount({ credentials, password })
const changeEmail = ({ email, password }) => apiService.changeEmail({ credentials, email, password })
const changePassword = ({ password, newPassword, newPasswordConfirmation }) =>
apiService.changePassword({ credentials, password, newPassword, newPasswordConfirmation })
const fetchSettingsMFA = () => apiService.settingsMFA({ credentials })
const generateMfaBackupCodes = () => apiService.generateMfaBackupCodes({ credentials })
const mfaSetupOTP = () => apiService.mfaSetupOTP({ credentials })
const mfaConfirmOTP = ({ password, token }) => apiService.mfaConfirmOTP({ credentials, password, token })
const mfaDisableOTP = ({ password }) => apiService.mfaDisableOTP({ credentials, password })
const fetchFavoritedByUsers = (id) => apiService.fetchFavoritedByUsers({ id })
const fetchRebloggedByUsers = (id) => apiService.fetchRebloggedByUsers({ id })
const reportUser = (params) => apiService.reportUser({ credentials, ...params })
const favorite = (id) => apiService.favorite({ id, credentials })
const unfavorite = (id) => apiService.unfavorite({ id, credentials })
const retweet = (id) => apiService.retweet({ id, credentials })
const unretweet = (id) => apiService.unretweet({ id, credentials })
const search2 = ({ q, resolve, limit, offset, following }) =>
apiService.search2({ credentials, q, resolve, limit, offset, following })
const searchUsers = (query) => apiService.searchUsers({ query, credentials })
const backendInteractorServiceInstance = {
fetchStatus,
fetchConversation,
fetchFriends,
exportFriends,
fetchFollowers,
followUser,
unfollowUser,
blockUser,
unblockUser,
fetchUser,
fetchUserRelationship,
verifyCredentials: apiService.verifyCredentials,
startFetchingTimeline,
startFetchingNotifications,
startFetchingFollowRequest,
fetchMutes,
muteUser,
unmuteUser,
subscribeUser,
unsubscribeUser,
fetchBlocks,
fetchOAuthTokens,
revokeOAuthToken,
fetchPinnedStatuses,
pinOwnStatus,
unpinOwnStatus,
muteConversation,
unmuteConversation,
tagUser,
untagUser,
addRight,
deleteRight,
deleteUser,
activateUser,
deactivateUser,
register,
getCaptcha,
updateAvatar,
updateBg,
updateBanner,
updateProfile,
importBlocks,
importFollows,
deleteAccount,
changeEmail,
changePassword,
fetchSettingsMFA,
generateMfaBackupCodes,
mfaSetupOTP,
mfaConfirmOTP,
mfaDisableOTP,
approveUser,
denyUser,
vote,
fetchPoll,
fetchFavoritedByUsers,
fetchRebloggedByUsers,
reportUser,
favorite,
unfavorite,
retweet,
unretweet,
updateNotificationSettings,
search2,
searchUsers
}
return backendInteractorServiceInstance
}
export default backendInteractorService export default backendInteractorService

View file

@ -341,10 +341,13 @@ export const parseNotification = (data) => {
if (masto) { if (masto) {
output.type = mastoDict[data.type] || data.type output.type = mastoDict[data.type] || data.type
output.seen = data.pleroma.is_seen output.seen = data.pleroma.is_seen
output.status = output.type === 'follow' output.status = output.type === 'follow' || output.type === 'move'
? null ? null
: parseStatus(data.status) : parseStatus(data.status)
output.action = output.status // TODO: Refactor, this is unneeded output.action = output.status // TODO: Refactor, this is unneeded
output.target = output.type !== 'move'
? null
: parseUser(data.target)
output.from_profile = parseUser(data.account) output.from_profile = parseUser(data.account)
} else { } else {
const parsedNotice = parseStatus(data.notice) const parsedNotice = parseStatus(data.notice)

View file

@ -39,7 +39,7 @@ export const requestFollow = (user, store) => new Promise((resolve, reject) => {
}) })
export const requestUnfollow = (user, store) => new Promise((resolve, reject) => { export const requestUnfollow = (user, store) => new Promise((resolve, reject) => {
store.state.api.backendInteractor.unfollowUser(user.id) store.state.api.backendInteractor.unfollowUser({ id: user.id })
.then((updated) => { .then((updated) => {
store.commit('updateUserRelationship', [updated]) store.commit('updateUserRelationship', [updated])
resolve({ resolve({

View file

@ -6,7 +6,8 @@ export const visibleTypes = store => ([
store.state.config.notificationVisibility.likes && 'like', store.state.config.notificationVisibility.likes && 'like',
store.state.config.notificationVisibility.mentions && 'mention', store.state.config.notificationVisibility.mentions && 'mention',
store.state.config.notificationVisibility.repeats && 'repeat', store.state.config.notificationVisibility.repeats && 'repeat',
store.state.config.notificationVisibility.follows && 'follow' store.state.config.notificationVisibility.follows && 'follow',
store.state.config.notificationVisibility.moves && 'move'
].filter(_ => _)) ].filter(_ => _))
const sortById = (a, b) => { const sortById = (a, b) => {

View file

@ -65,7 +65,8 @@ function sendSubscriptionToBackEnd (subscription, token, notificationVisibility)
follow: notificationVisibility.follows, follow: notificationVisibility.follows,
favourite: notificationVisibility.likes, favourite: notificationVisibility.likes,
mention: notificationVisibility.mentions, mention: notificationVisibility.mentions,
reblog: notificationVisibility.repeats reblog: notificationVisibility.repeats,
move: notificationVisibility.moves
} }
} }
}) })

View file

@ -333,6 +333,12 @@
"css": "login", "css": "login",
"code": 59424, "code": 59424,
"src": "fontawesome" "src": "fontawesome"
},
{
"uid": "f3ebd6751c15a280af5cc5f4a764187d",
"css": "arrow-curved",
"code": 59426,
"src": "iconic"
} }
] ]
} }