diff options
Diffstat (limited to 'src/modules')
| -rw-r--r-- | src/modules/api.js | 106 | ||||
| -rw-r--r-- | src/modules/chats.js | 6 | ||||
| -rw-r--r-- | src/modules/config.js | 55 | ||||
| -rw-r--r-- | src/modules/instance.js | 19 | ||||
| -rw-r--r-- | src/modules/media_viewer.js | 9 | ||||
| -rw-r--r-- | src/modules/shout.js (renamed from src/modules/chat.js) | 6 | ||||
| -rw-r--r-- | src/modules/statuses.js | 29 | ||||
| -rw-r--r-- | src/modules/users.js | 14 |
8 files changed, 190 insertions, 54 deletions
diff --git a/src/modules/api.js b/src/modules/api.js index 08485a30..54f94356 100644 --- a/src/modules/api.js +++ b/src/modules/api.js @@ -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 }) { diff --git a/src/modules/chats.js b/src/modules/chats.js index 0a373d88..69d683bd 100644 --- a/src/modules/chats.js +++ b/src/modules/chats.js @@ -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]) } } } diff --git a/src/modules/config.js b/src/modules/config.js index e591a506..825535cd 100644 --- a/src/modules/config.js +++ b/src/modules/config.js @@ -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 }) }, diff --git a/src/modules/instance.js b/src/modules/instance.js index 411b1caa..1abd784f 100644 --- a/src/modules/instance.js +++ b/src/modules/instance.js @@ -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') } diff --git a/src/modules/media_viewer.js b/src/modules/media_viewer.js index 721c25e6..ebcba01d 100644 --- a/src/modules/media_viewer.js +++ b/src/modules/media_viewer.js @@ -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') diff --git a/src/modules/chat.js b/src/modules/shout.js index c798549d..507a4d83 100644 --- a/src/modules/chat.js +++ b/src/modules/shout.js @@ -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 diff --git a/src/modules/statuses.js b/src/modules/statuses.js index d5343cd4..cb2a8d46 100644 --- a/src/modules/statuses.js +++ b/src/modules/statuses.js @@ -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 diff --git a/src/modules/users.js b/src/modules/users.js index 655db4c7..05ff44d5 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -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) |
