Merge branch 'develop' of https://git.pleroma.social/pleroma/pleroma-fe into feat/report-notification
This commit is contained in:
commit
d0c4ad22cd
225 changed files with 11974 additions and 3981 deletions
|
|
@ -3,8 +3,11 @@ import { WSConnectionStatus } from '../services/api/api.service.js'
|
|||
import { maybeShowChatNotification } from '../services/chat_utils/chat_utils.js'
|
||||
import { Socket } from 'phoenix'
|
||||
|
||||
const retryTimeout = (multiplier) => 1000 * multiplier
|
||||
|
||||
const api = {
|
||||
state: {
|
||||
retryMultiplier: 1,
|
||||
backendInteractor: backendInteractorService(),
|
||||
fetchers: {},
|
||||
socket: null,
|
||||
|
|
@ -34,18 +37,43 @@ const api = {
|
|||
},
|
||||
setMastoUserSocketStatus (state, value) {
|
||||
state.mastoUserSocketStatus = value
|
||||
},
|
||||
incrementRetryMultiplier (state) {
|
||||
state.retryMultiplier = Math.max(++state.retryMultiplier, 3)
|
||||
},
|
||||
resetRetryMultiplier (state) {
|
||||
state.retryMultiplier = 1
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
// Global MastoAPI socket control, in future should disable ALL sockets/(re)start relevant sockets
|
||||
enableMastoSockets (store) {
|
||||
const { state, dispatch } = store
|
||||
if (state.mastoUserSocket) return
|
||||
/**
|
||||
* Global MastoAPI socket control, in future should disable ALL sockets/(re)start relevant sockets
|
||||
*
|
||||
* @param {Boolean} [initial] - whether this enabling happened at boot time or not
|
||||
*/
|
||||
enableMastoSockets (store, initial) {
|
||||
const { state, dispatch, commit } = store
|
||||
// Do not initialize unless nonexistent or closed
|
||||
if (
|
||||
state.mastoUserSocket &&
|
||||
![
|
||||
WebSocket.CLOSED,
|
||||
WebSocket.CLOSING
|
||||
].includes(state.mastoUserSocket.getState())
|
||||
) {
|
||||
return
|
||||
}
|
||||
if (initial) {
|
||||
commit('setMastoUserSocketStatus', WSConnectionStatus.STARTING_INITIAL)
|
||||
} else {
|
||||
commit('setMastoUserSocketStatus', WSConnectionStatus.STARTING)
|
||||
}
|
||||
return dispatch('startMastoUserSocket')
|
||||
},
|
||||
disableMastoSockets (store) {
|
||||
const { state, dispatch } = store
|
||||
const { state, dispatch, commit } = store
|
||||
if (!state.mastoUserSocket) return
|
||||
commit('setMastoUserSocketStatus', WSConnectionStatus.DISABLED)
|
||||
return dispatch('stopMastoUserSocket')
|
||||
},
|
||||
|
||||
|
|
@ -91,11 +119,29 @@ const api = {
|
|||
}
|
||||
)
|
||||
state.mastoUserSocket.addEventListener('open', () => {
|
||||
// Do not show notification when we just opened up the page
|
||||
if (state.mastoUserSocketStatus !== WSConnectionStatus.STARTING_INITIAL) {
|
||||
dispatch('pushGlobalNotice', {
|
||||
level: 'success',
|
||||
messageKey: 'timeline.socket_reconnected',
|
||||
timeout: 5000
|
||||
})
|
||||
}
|
||||
// Stop polling if we were errored or disabled
|
||||
if (new Set([
|
||||
WSConnectionStatus.ERROR,
|
||||
WSConnectionStatus.DISABLED
|
||||
]).has(state.mastoUserSocketStatus)) {
|
||||
dispatch('stopFetchingTimeline', { timeline: 'friends' })
|
||||
dispatch('stopFetchingNotifications')
|
||||
dispatch('stopFetchingChats')
|
||||
}
|
||||
commit('resetRetryMultiplier')
|
||||
commit('setMastoUserSocketStatus', WSConnectionStatus.JOINED)
|
||||
})
|
||||
state.mastoUserSocket.addEventListener('error', ({ detail: error }) => {
|
||||
console.error('Error in MastoAPI websocket:', error)
|
||||
commit('setMastoUserSocketStatus', WSConnectionStatus.ERROR)
|
||||
// TODO is this needed?
|
||||
dispatch('clearOpenedChats')
|
||||
})
|
||||
state.mastoUserSocket.addEventListener('close', ({ detail: closeEvent }) => {
|
||||
|
|
@ -106,14 +152,26 @@ const api = {
|
|||
const { code } = closeEvent
|
||||
if (ignoreCodes.has(code)) {
|
||||
console.debug(`Not restarting socket becasue of closure code ${code} is in ignore list`)
|
||||
commit('setMastoUserSocketStatus', WSConnectionStatus.CLOSED)
|
||||
} else {
|
||||
console.warn(`MastoAPI websocket disconnected, restarting. CloseEvent code: ${code}`)
|
||||
dispatch('startFetchingTimeline', { timeline: 'friends' })
|
||||
dispatch('startFetchingNotifications')
|
||||
dispatch('startFetchingChats')
|
||||
dispatch('restartMastoUserSocket')
|
||||
setTimeout(() => {
|
||||
dispatch('startMastoUserSocket')
|
||||
}, retryTimeout(state.retryMultiplier))
|
||||
commit('incrementRetryMultiplier')
|
||||
if (state.mastoUserSocketStatus !== WSConnectionStatus.ERROR) {
|
||||
dispatch('startFetchingTimeline', { timeline: 'friends' })
|
||||
dispatch('startFetchingNotifications')
|
||||
dispatch('startFetchingChats')
|
||||
dispatch('pushGlobalNotice', {
|
||||
level: 'error',
|
||||
messageKey: 'timeline.socket_broke',
|
||||
messageArgs: [code],
|
||||
timeout: 5000
|
||||
})
|
||||
}
|
||||
commit('setMastoUserSocketStatus', WSConnectionStatus.ERROR)
|
||||
}
|
||||
commit('setMastoUserSocketStatus', WSConnectionStatus.CLOSED)
|
||||
dispatch('clearOpenedChats')
|
||||
})
|
||||
resolve()
|
||||
|
|
@ -122,15 +180,6 @@ const api = {
|
|||
}
|
||||
})
|
||||
},
|
||||
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')
|
||||
dispatch('stopFetchingChats')
|
||||
})
|
||||
},
|
||||
stopMastoUserSocket ({ state, dispatch }) {
|
||||
dispatch('startFetchingTimeline', { timeline: 'friends' })
|
||||
dispatch('startFetchingNotifications')
|
||||
|
|
@ -156,6 +205,13 @@ const api = {
|
|||
if (!fetcher) return
|
||||
store.commit('removeFetcher', { fetcherName: timeline, fetcher })
|
||||
},
|
||||
fetchTimeline (store, timeline, { ...rest }) {
|
||||
store.state.backendInteractor.fetchTimeline({
|
||||
store,
|
||||
timeline,
|
||||
...rest
|
||||
})
|
||||
},
|
||||
|
||||
// Notifications
|
||||
startFetchingNotifications (store) {
|
||||
|
|
@ -168,6 +224,12 @@ const api = {
|
|||
if (!fetcher) return
|
||||
store.commit('removeFetcher', { fetcherName: 'notifications', fetcher })
|
||||
},
|
||||
fetchNotifications (store, { ...rest }) {
|
||||
store.state.backendInteractor.fetchNotifications({
|
||||
store,
|
||||
...rest
|
||||
})
|
||||
},
|
||||
|
||||
// Follow requests
|
||||
startFetchingFollowRequests (store) {
|
||||
|
|
@ -193,12 +255,12 @@ const api = {
|
|||
initializeSocket ({ dispatch, commit, state, rootState }) {
|
||||
// Set up websocket connection
|
||||
const token = state.wsToken
|
||||
if (rootState.instance.chatAvailable && typeof token !== 'undefined' && state.socket === null) {
|
||||
if (rootState.instance.shoutAvailable && typeof token !== 'undefined' && state.socket === null) {
|
||||
const socket = new Socket('/socket', { params: { token } })
|
||||
socket.connect()
|
||||
|
||||
commit('setSocket', socket)
|
||||
dispatch('initializeChat', socket)
|
||||
dispatch('initializeShout', socket)
|
||||
}
|
||||
},
|
||||
disconnectFromSocket ({ commit, state }) {
|
||||
|
|
|
|||
|
|
@ -115,6 +115,9 @@ const chats = {
|
|||
},
|
||||
handleMessageError ({ commit }, value) {
|
||||
commit('handleMessageError', { commit, ...value })
|
||||
},
|
||||
cullOlderMessages ({ commit }, chatId) {
|
||||
commit('cullOlderMessages', chatId)
|
||||
}
|
||||
},
|
||||
mutations: {
|
||||
|
|
@ -227,6 +230,9 @@ const chats = {
|
|||
handleMessageError (state, { chatId, fakeId, isRetry }) {
|
||||
const chatMessageService = state.openedChatMessageServices[chatId]
|
||||
chatService.handleMessageError(chatMessageService, fakeId, isRetry)
|
||||
},
|
||||
cullOlderMessages (state, chatId) {
|
||||
chatService.cullOlderMessages(state.openedChatMessageServices[chatId])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@ const browserLocale = (window.navigator.language || 'en').split('-')[0]
|
|||
*/
|
||||
export const multiChoiceProperties = [
|
||||
'postContentType',
|
||||
'subjectLineBehavior'
|
||||
'subjectLineBehavior',
|
||||
'mentionLinkDisplay' // short | full_for_remote | full
|
||||
]
|
||||
|
||||
export const defaultState = {
|
||||
|
|
@ -21,8 +22,11 @@ export const defaultState = {
|
|||
customThemeSource: undefined,
|
||||
hideISP: false,
|
||||
hideInstanceWallpaper: false,
|
||||
hideShoutbox: false,
|
||||
// bad name: actually hides posts of muted USERS
|
||||
hideMutedPosts: undefined, // instance default
|
||||
hideMutedThreads: undefined, // instance default
|
||||
hideWordFilteredPosts: undefined, // instance default
|
||||
collapseMessageWithSubject: undefined, // instance default
|
||||
padEmoji: true,
|
||||
hideAttachments: false,
|
||||
|
|
@ -34,6 +38,7 @@ export const defaultState = {
|
|||
loopVideoSilentOnly: true,
|
||||
streaming: false,
|
||||
emojiReactionsOnTimeline: true,
|
||||
alwaysShowNewPostButton: false,
|
||||
autohideFloatingPostButton: false,
|
||||
pauseOnUnfocused: true,
|
||||
stopGifs: false,
|
||||
|
|
@ -55,6 +60,7 @@ export const defaultState = {
|
|||
interfaceLanguage: browserLocale,
|
||||
hideScopeNotice: false,
|
||||
useStreamingApi: false,
|
||||
sidebarRight: undefined, // instance default
|
||||
scopeCopy: undefined, // instance default
|
||||
subjectLineBehavior: undefined, // instance default
|
||||
alwaysShowSubjectInput: undefined, // instance default
|
||||
|
|
@ -66,9 +72,17 @@ export const defaultState = {
|
|||
useOneClickNsfw: false,
|
||||
useContainFit: false,
|
||||
greentext: undefined, // instance default
|
||||
useAtIcon: undefined, // instance default
|
||||
mentionLinkDisplay: undefined, // instance default
|
||||
mentionLinkShowTooltip: undefined, // instance default
|
||||
mentionLinkShowAvatar: undefined, // instance default
|
||||
mentionLinkFadeDomain: undefined, // instance default
|
||||
mentionLinkShowYous: undefined, // instance default
|
||||
mentionLinkBoldenYou: undefined, // instance default
|
||||
hidePostStats: undefined, // instance default
|
||||
hideUserStats: undefined, // instance default
|
||||
virtualScrolling: undefined // instance default
|
||||
virtualScrolling: undefined, // instance default
|
||||
sensitiveByDefault: undefined // instance default
|
||||
}
|
||||
|
||||
// caching the instance default properties
|
||||
|
|
@ -77,18 +91,23 @@ export const instanceDefaultProperties = Object.entries(defaultState)
|
|||
.map(([key, value]) => key)
|
||||
|
||||
const config = {
|
||||
state: defaultState,
|
||||
state: { ...defaultState },
|
||||
getters: {
|
||||
mergedConfig (state, getters, rootState, rootGetters) {
|
||||
defaultConfig (state, getters, rootState, rootGetters) {
|
||||
const { instance } = rootState
|
||||
return {
|
||||
...state,
|
||||
...instanceDefaultProperties
|
||||
.map(key => [key, state[key] === undefined
|
||||
? instance[key]
|
||||
: state[key]
|
||||
])
|
||||
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {})
|
||||
...defaultState,
|
||||
...Object.fromEntries(
|
||||
instanceDefaultProperties.map(key => [key, instance[key]])
|
||||
)
|
||||
}
|
||||
},
|
||||
mergedConfig (state, getters, rootState, rootGetters) {
|
||||
const { defaultConfig } = rootGetters
|
||||
return {
|
||||
...defaultConfig,
|
||||
// Do not override with undefined
|
||||
...Object.fromEntries(Object.entries(state).filter(([k, v]) => v !== undefined))
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -106,6 +125,20 @@ const config = {
|
|||
}
|
||||
},
|
||||
actions: {
|
||||
loadSettings ({ dispatch }, data) {
|
||||
const knownKeys = new Set(Object.keys(defaultState))
|
||||
const presentKeys = new Set(Object.keys(data))
|
||||
const intersection = new Set()
|
||||
for (let elem of presentKeys) {
|
||||
if (knownKeys.has(elem)) {
|
||||
intersection.add(elem)
|
||||
}
|
||||
}
|
||||
|
||||
intersection.forEach(
|
||||
name => dispatch('setOption', { name, value: data[name] })
|
||||
)
|
||||
},
|
||||
setHighlight ({ commit, dispatch }, { user, color, type }) {
|
||||
commit('setHighlight', { user, color, type })
|
||||
},
|
||||
|
|
|
|||
|
|
@ -19,10 +19,19 @@ const defaultState = {
|
|||
defaultBanner: '/images/banner.png',
|
||||
background: '/static/aurora_borealis.jpg',
|
||||
collapseMessageWithSubject: false,
|
||||
disableChat: false,
|
||||
greentext: false,
|
||||
useAtIcon: false,
|
||||
mentionLinkDisplay: 'short',
|
||||
mentionLinkShowTooltip: true,
|
||||
mentionLinkShowAvatar: false,
|
||||
mentionLinkFadeDomain: true,
|
||||
mentionLinkShowYous: false,
|
||||
mentionLinkBoldenYou: true,
|
||||
hideFilteredStatuses: false,
|
||||
// bad name: actually hides posts of muted USERS
|
||||
hideMutedPosts: false,
|
||||
hideMutedThreads: true,
|
||||
hideWordFilteredPosts: false,
|
||||
hidePostStats: false,
|
||||
hideSitename: false,
|
||||
hideUserStats: false,
|
||||
|
|
@ -43,6 +52,7 @@ const defaultState = {
|
|||
subjectLineBehavior: 'email',
|
||||
theme: 'pleroma-dark',
|
||||
virtualScrolling: true,
|
||||
sensitiveByDefault: false,
|
||||
|
||||
// Nasty stuff
|
||||
customEmoji: [],
|
||||
|
|
@ -56,7 +66,7 @@ const defaultState = {
|
|||
knownDomains: [],
|
||||
|
||||
// Feature-set, apparently, not everything here is reported...
|
||||
chatAvailable: false,
|
||||
shoutAvailable: false,
|
||||
pleromaChatMessagesAvailable: false,
|
||||
gopherAvailable: false,
|
||||
mediaProxyAvailable: false,
|
||||
|
|
@ -97,6 +107,9 @@ const instance = {
|
|||
return instanceDefaultProperties
|
||||
.map(key => [key, state[key]])
|
||||
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {})
|
||||
},
|
||||
instanceDomain (state) {
|
||||
return new URL(state.server).hostname
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
|
|
@ -106,7 +119,7 @@ const instance = {
|
|||
case 'name':
|
||||
dispatch('setPageTitle')
|
||||
break
|
||||
case 'chatAvailable':
|
||||
case 'shoutAvailable':
|
||||
if (value) {
|
||||
dispatch('initializeSocket')
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import fileTypeService from '../services/file_type/file_type.service.js'
|
||||
const supportedTypes = new Set(['image', 'video', 'audio', 'flash'])
|
||||
|
||||
const mediaViewer = {
|
||||
state: {
|
||||
|
|
@ -10,7 +11,7 @@ const mediaViewer = {
|
|||
setMedia (state, media) {
|
||||
state.media = media
|
||||
},
|
||||
setCurrent (state, index) {
|
||||
setCurrentMedia (state, index) {
|
||||
state.activated = true
|
||||
state.currentIndex = index
|
||||
},
|
||||
|
|
@ -22,13 +23,13 @@ const mediaViewer = {
|
|||
setMedia ({ commit }, attachments) {
|
||||
const media = attachments.filter(attachment => {
|
||||
const type = fileTypeService.fileType(attachment.mimetype)
|
||||
return type === 'image' || type === 'video' || type === 'audio'
|
||||
return supportedTypes.has(type)
|
||||
})
|
||||
commit('setMedia', media)
|
||||
},
|
||||
setCurrent ({ commit, state }, current) {
|
||||
setCurrentMedia ({ commit, state }, current) {
|
||||
const index = state.media.indexOf(current)
|
||||
commit('setCurrent', index || 0)
|
||||
commit('setCurrentMedia', index || 0)
|
||||
},
|
||||
closeMediaViewer ({ commit }) {
|
||||
commit('close')
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
const chat = {
|
||||
const shout = {
|
||||
state: {
|
||||
messages: [],
|
||||
channel: { state: '' }
|
||||
|
|
@ -16,7 +16,7 @@ const chat = {
|
|||
}
|
||||
},
|
||||
actions: {
|
||||
initializeChat (store, socket) {
|
||||
initializeShout (store, socket) {
|
||||
const channel = socket.channel('chat:public')
|
||||
channel.on('new_msg', (msg) => {
|
||||
store.commit('addMessage', msg)
|
||||
|
|
@ -30,4 +30,4 @@ const chat = {
|
|||
}
|
||||
}
|
||||
|
||||
export default chat
|
||||
export default shout
|
||||
|
|
@ -13,7 +13,11 @@ import {
|
|||
omitBy
|
||||
} from 'lodash'
|
||||
import { set } from 'vue'
|
||||
import { isStatusNotification, maybeShowNotification } from '../services/notification_utils/notification_utils.js'
|
||||
import {
|
||||
isStatusNotification,
|
||||
isValidNotification,
|
||||
maybeShowNotification
|
||||
} from '../services/notification_utils/notification_utils.js'
|
||||
import apiService from '../services/api/api.service.js'
|
||||
|
||||
const emptyTl = (userId = 0) => ({
|
||||
|
|
@ -310,8 +314,24 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us
|
|||
}
|
||||
}
|
||||
|
||||
const updateNotificationsMinMaxId = (state, notification) => {
|
||||
state.notifications.maxId = notification.id > state.notifications.maxId
|
||||
? notification.id
|
||||
: state.notifications.maxId
|
||||
state.notifications.minId = notification.id < state.notifications.minId
|
||||
? notification.id
|
||||
: state.notifications.minId
|
||||
}
|
||||
|
||||
const addNewNotifications = (state, { dispatch, notifications, older, visibleNotificationTypes, rootGetters, newNotificationSideEffects }) => {
|
||||
each(notifications, (notification) => {
|
||||
// If invalid notification, update ids but don't add it to store
|
||||
if (!isValidNotification(notification)) {
|
||||
console.error('Invalid notification:', notification)
|
||||
updateNotificationsMinMaxId(state, notification)
|
||||
return
|
||||
}
|
||||
|
||||
if (isStatusNotification(notification.type)) {
|
||||
notification.action = addStatusToGlobalStorage(state, notification.action).item
|
||||
notification.status = notification.status && addStatusToGlobalStorage(state, notification.status).item
|
||||
|
|
@ -327,12 +347,7 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot
|
|||
|
||||
// Only add a new notification if we don't have one for the same action
|
||||
if (!state.notifications.idStore.hasOwnProperty(notification.id)) {
|
||||
state.notifications.maxId = notification.id > state.notifications.maxId
|
||||
? notification.id
|
||||
: state.notifications.maxId
|
||||
state.notifications.minId = notification.id < state.notifications.minId
|
||||
? notification.id
|
||||
: state.notifications.minId
|
||||
updateNotificationsMinMaxId(state, notification)
|
||||
|
||||
state.notifications.data.push(notification)
|
||||
state.notifications.idStore[notification.id] = notification
|
||||
|
|
|
|||
|
|
@ -246,6 +246,11 @@ export const getters = {
|
|||
}
|
||||
return result
|
||||
},
|
||||
findUserByUrl: state => query => {
|
||||
return state.users
|
||||
.find(u => u.statusnet_profile_url &&
|
||||
u.statusnet_profile_url.toLowerCase() === query.toLowerCase())
|
||||
},
|
||||
relationship: state => id => {
|
||||
const rel = id && state.relationships[id]
|
||||
return rel || { id, loading: true }
|
||||
|
|
@ -388,7 +393,7 @@ const users = {
|
|||
toggleActivationStatus ({ rootState, commit }, { user }) {
|
||||
const api = user.deactivated ? rootState.api.backendInteractor.activateUser : rootState.api.backendInteractor.deactivateUser
|
||||
api({ user })
|
||||
.then(({ deactivated }) => commit('updateActivationStatus', { user, deactivated }))
|
||||
.then((user) => { let deactivated = !user.is_active; commit('updateActivationStatus', { user, deactivated }) })
|
||||
},
|
||||
registerPushNotifications (store) {
|
||||
const token = store.state.currentUser.credentials
|
||||
|
|
@ -531,7 +536,7 @@ const users = {
|
|||
if (user.token) {
|
||||
store.dispatch('setWsToken', user.token)
|
||||
|
||||
// Initialize the chat socket.
|
||||
// Initialize the shout socket.
|
||||
store.dispatch('initializeSocket')
|
||||
}
|
||||
|
||||
|
|
@ -547,9 +552,10 @@ const users = {
|
|||
}
|
||||
|
||||
if (store.getters.mergedConfig.useStreamingApi) {
|
||||
store.dispatch('enableMastoSockets').catch((error) => {
|
||||
store.dispatch('fetchTimeline', 'friends', { since: null })
|
||||
store.dispatch('fetchNotifications', { since: null })
|
||||
store.dispatch('enableMastoSockets', true).catch((error) => {
|
||||
console.error('Failed initializing MastoAPI Streaming socket', error)
|
||||
startPolling()
|
||||
}).then(() => {
|
||||
store.dispatch('fetchChats', { latest: true })
|
||||
setTimeout(() => store.dispatch('setNotificationsSilence', false), 10000)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue