diff --git a/CHANGELOG.md b/CHANGELOG.md index eb6a4599..6fe57ba5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Changed - Display 'people voted' instead of 'votes' for multi-choice polls +- Changed the "Timelines" link in side panel to toggle show all timeline options inside the panel +- Renamed "Timeline" to "Home Timeline" to be more clear - Optimized chat to not get horrible performance after keeping the same chat open for a long time - When opening emoji picker or react picker, it automatically focuses the search field - Language picker now uses native language names @@ -35,6 +37,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Fixed - Follows/Followers tabs on user profiles now display the content properly. - Handle punycode in screen names +- Fixed local dev mode having non-functional websockets in some cases +- Show notices for websocket events (errors, abnormal closures, reconnections) +- Fix not being able to re-enable websocket until page refresh +- Fix annoying issue where timeline might have few posts when streaming is enabled ### Changed - Don't filter own posts when they hit your wordfilter diff --git a/config/index.js b/config/index.js index ccec4196..7cb87c3b 100644 --- a/config/index.js +++ b/config/index.js @@ -3,6 +3,11 @@ const path = require('path') let settings = {} try { settings = require('./local.json') + if (settings.target && settings.target.endsWith('/')) { + // replacing trailing slash since it can conflict with some apis + // and that's how actual BE reports its url + settings.target = settings.target.replace(/\/$/, '') + } console.log('Using local dev server settings (/config/local.json):') console.log(JSON.stringify(settings, null, 2)) } catch (e) { diff --git a/src/App.scss b/src/App.scss index 2c7a4c6a..86508672 100644 --- a/src/App.scss +++ b/src/App.scss @@ -706,6 +706,15 @@ nav { color: var(--alertWarningPanelText, $fallback--text); } } + + &.success { + background-color: var(--alertSuccess, $fallback--alertWarning); + color: var(--alertSuccessText, $fallback--text); + + .panel-heading & { + color: var(--alertSuccessPanelText, $fallback--text); + } + } } .faint { diff --git a/src/components/global_notice_list/global_notice_list.vue b/src/components/global_notice_list/global_notice_list.vue index 049e23db..a45f4586 100644 --- a/src/components/global_notice_list/global_notice_list.vue +++ b/src/components/global_notice_list/global_notice_list.vue @@ -71,6 +71,14 @@ } } + .global-success { + background-color: var(--alertPopupSuccess, $fallback--cGreen); + color: var(--alertPopupSuccessText, $fallback--text); + .svg-inline--fa { + color: var(--alertPopupSuccessText, $fallback--text); + } + } + .global-info { background-color: var(--alertPopupNeutral, $fallback--fg); color: var(--alertPopupNeutralText, $fallback--text); diff --git a/src/components/nav_panel/nav_panel.js b/src/components/nav_panel/nav_panel.js index add22a97..4b7fa488 100644 --- a/src/components/nav_panel/nav_panel.js +++ b/src/components/nav_panel/nav_panel.js @@ -1,4 +1,4 @@ -import { timelineNames } from '../timeline_menu/timeline_menu.js' +import TimelineMenuContent from '../timeline_menu/timeline_menu_content.vue' import { mapState, mapGetters } from 'vuex' import { library } from '@fortawesome/fontawesome-svg-core' @@ -7,10 +7,12 @@ import { faGlobe, faBookmark, faEnvelope, - faHome, + faChevronDown, + faChevronUp, faComments, faBell, - faInfoCircle + faInfoCircle, + faStream } from '@fortawesome/free-solid-svg-icons' library.add( @@ -18,10 +20,12 @@ library.add( faGlobe, faBookmark, faEnvelope, - faHome, + faChevronDown, + faChevronUp, faComments, faBell, - faInfoCircle + faInfoCircle, + faStream ) const NavPanel = { @@ -30,19 +34,23 @@ const NavPanel = { this.$store.dispatch('startFetchingFollowRequests') } }, + components: { + TimelineMenuContent + }, + data () { + return { + showTimelines: false + } + }, + methods: { + toggleTimelines () { + this.showTimelines = !this.showTimelines + } + }, computed: { - onTimelineRoute () { - return !!timelineNames()[this.$route.name] - }, compactNavPanel () { return this.$store.getters.mergedConfig.compactNavPanel || false }, - timelinesRoute () { - if (this.$store.state.interface.lastTimeline) { - return this.$store.state.interface.lastTimeline - } - return this.currentUser ? 'friends' : 'public-timeline' - }, ...mapState({ currentUser: state => state.users.currentUser, followRequestCount: state => state.api.followRequests.length, diff --git a/src/components/nav_panel/nav_panel.vue b/src/components/nav_panel/nav_panel.vue index 00807d4c..0071e7c2 100644 --- a/src/components/nav_panel/nav_panel.vue +++ b/src/components/nav_panel/nav_panel.vue @@ -6,19 +6,33 @@
diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue index 44baa1ab..a34b17f4 100644 --- a/src/components/post_status_form/post_status_form.vue +++ b/src/components/post_status_form/post_status_form.vue @@ -272,7 +272,7 @@ disabled class="btn button-default" > - {{ $t('general.submit') }} + {{ $t('post_status.post') }}
- {{ $t('general.submit') }} + {{ $t('registration.register') }}
diff --git a/src/components/settings_modal/tabs/notifications_tab.vue b/src/components/settings_modal/tabs/notifications_tab.vue index 8f8fe48e..7e0568ea 100644 --- a/src/components/settings_modal/tabs/notifications_tab.vue +++ b/src/components/settings_modal/tabs/notifications_tab.vue @@ -24,7 +24,7 @@ class="btn button-default" @click="updateNotificationSettings" > - {{ $t('general.submit') }} + {{ $t('settings.save') }} diff --git a/src/components/settings_modal/tabs/profile_tab.vue b/src/components/settings_modal/tabs/profile_tab.vue index 175a0219..bb3c301d 100644 --- a/src/components/settings_modal/tabs/profile_tab.vue +++ b/src/components/settings_modal/tabs/profile_tab.vue @@ -153,7 +153,7 @@ class="btn button-default" @click="updateProfile" > - {{ $t('general.submit') }} + {{ $t('settings.save') }}
@@ -227,7 +227,7 @@ class="btn button-default" @click="submitBanner(banner)" > - {{ $t('general.submit') }} + {{ $t('settings.save') }}
@@ -266,7 +266,7 @@ class="btn button-default" @click="submitBackground(background)" > - {{ $t('general.submit') }} + {{ $t('settings.save') }}
diff --git a/src/components/settings_modal/tabs/security_tab/security_tab.vue b/src/components/settings_modal/tabs/security_tab/security_tab.vue index 56bea1f4..275d4616 100644 --- a/src/components/settings_modal/tabs/security_tab/security_tab.vue +++ b/src/components/settings_modal/tabs/security_tab/security_tab.vue @@ -22,7 +22,7 @@ class="btn button-default" @click="changeEmail" > - {{ $t('general.submit') }} + {{ $t('settings.save') }}

{{ $t('settings.changed_email') }} @@ -60,7 +60,7 @@ class="btn button-default" @click="changePassword" > - {{ $t('general.submit') }} + {{ $t('settings.save') }}

{{ $t('settings.changed_password') }} @@ -133,7 +133,7 @@ class="btn button-default" @click="confirmDelete" > - {{ $t('general.submit') }} + {{ $t('settings.save') }} diff --git a/src/components/timeline_menu/timeline_menu.js b/src/components/timeline_menu/timeline_menu.js index 8d6a58b1..bab51e75 100644 --- a/src/components/timeline_menu/timeline_menu.js +++ b/src/components/timeline_menu/timeline_menu.js @@ -1,29 +1,17 @@ import Popover from '../popover/popover.vue' -import { mapState } from 'vuex' +import TimelineMenuContent from './timeline_menu_content.vue' import { library } from '@fortawesome/fontawesome-svg-core' import { - faUsers, - faGlobe, - faBookmark, - faEnvelope, - faHome, faChevronDown } from '@fortawesome/free-solid-svg-icons' -library.add( - faUsers, - faGlobe, - faBookmark, - faEnvelope, - faHome, - faChevronDown -) +library.add(faChevronDown) // Route -> i18n key mapping, exported and not in the computed // because nav panel benefits from the same information. export const timelineNames = () => { return { - 'friends': 'nav.timeline', + 'friends': 'nav.home_timeline', 'bookmarks': 'nav.bookmarks', 'dms': 'nav.dms', 'public-timeline': 'nav.public_tl', @@ -33,7 +21,8 @@ export const timelineNames = () => { const TimelineMenu = { components: { - Popover + Popover, + TimelineMenuContent }, data () { return { @@ -41,9 +30,6 @@ const TimelineMenu = { } }, created () { - if (this.currentUser && this.currentUser.locked) { - this.$store.dispatch('startFetchingFollowRequests') - } if (timelineNames()[this.$route.name]) { this.$store.dispatch('setLastTimeline', this.$route.name) } @@ -75,13 +61,6 @@ const TimelineMenu = { const i18nkey = timelineNames()[this.$route.name] return i18nkey ? this.$t(i18nkey) : route } - }, - computed: { - ...mapState({ - currentUser: state => state.users.currentUser, - privateMode: state => state.instance.private, - federating: state => state.instance.federating - }) } } diff --git a/src/components/timeline_menu/timeline_menu.vue b/src/components/timeline_menu/timeline_menu.vue index 3c86842b..22dc3432 100644 --- a/src/components/timeline_menu/timeline_menu.vue +++ b/src/components/timeline_menu/timeline_menu.vue @@ -13,53 +13,7 @@ slot="content" class="timeline-menu-popover panel panel-default" > -

+
state.users.currentUser, + privateMode: state => state.instance.private, + federating: state => state.instance.federating + }) + } +} + +export default TimelineMenuContent diff --git a/src/components/timeline_menu/timeline_menu_content.vue b/src/components/timeline_menu/timeline_menu_content.vue new file mode 100644 index 00000000..bed1b679 --- /dev/null +++ b/src/components/timeline_menu/timeline_menu_content.vue @@ -0,0 +1,66 @@ + + + diff --git a/src/i18n/en.json b/src/i18n/en.json index bda4ab30..be90fb41 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -3,27 +3,27 @@ "mrf": { "federation": "Federation", "keyword": { - "keyword_policies": "Keyword Policies", + "keyword_policies": "Keyword policies", "ftl_removal": "Removal from \"The Whole Known Network\" Timeline", "reject": "Reject", "replace": "Replace", "is_replaced_by": "→" }, - "mrf_policies": "Enabled MRF Policies", + "mrf_policies": "Enabled MRF policies", "mrf_policies_desc": "MRF policies manipulate the federation behaviour of the instance. The following policies are enabled:", "simple": { - "simple_policies": "Instance-specific Policies", + "simple_policies": "Instance-specific policies", "accept": "Accept", "accept_desc": "This instance only accepts messages from the following instances:", "reject": "Reject", "reject_desc": "This instance will not accept messages from the following instances:", "quarantine": "Quarantine", "quarantine_desc": "This instance will send only public posts to the following instances:", - "ftl_removal": "Removal from \"The Whole Known Network\" Timeline", - "ftl_removal_desc": "This instance removes these instances from \"The Whole Known Network\" timeline:", + "ftl_removal": "Removal from \"Known Network\" Timeline", + "ftl_removal_desc": "This instance removes these instances from \"Known Network\" timeline:", "media_removal": "Media Removal", "media_removal_desc": "This instance removes media from posts on the following instances:", - "media_nsfw": "Media Force-set As Sensitive", + "media_nsfw": "Media force-set as sensitive", "media_nsfw_desc": "This instance forces media to be set sensitive in posts on the following instances:" } }, @@ -118,12 +118,13 @@ "about": "About", "administration": "Administration", "back": "Back", - "friend_requests": "Follow Requests", + "friend_requests": "Follow requests", "mentions": "Mentions", "interactions": "Interactions", - "dms": "Direct Messages", - "public_tl": "Public Timeline", + "dms": "Direct messages", + "public_tl": "Public timeline", "timeline": "Timeline", + "home_timeline": "Home timeline", "twkn": "Known Network", "bookmarks": "Bookmarks", "user_search": "User Search", @@ -148,8 +149,8 @@ "reacted_with": "reacted with {0}" }, "polls": { - "add_poll": "Add Poll", - "add_option": "Add Option", + "add_poll": "Add poll", + "add_option": "Add option", "option": "Option", "votes": "votes", "people_voted_count": "{count} person voted | {count} people voted", @@ -178,7 +179,7 @@ "storage_unavailable": "Pleroma could not access browser storage. Your login or your local settings won't be saved and you might encounter unexpected issues. Try enabling cookies." }, "interactions": { - "favs_repeats": "Repeats and Favorites", + "favs_repeats": "Repeats and favorites", "follows": "New follows", "moves": "User migrates", "load_older": "Load older interactions" @@ -200,6 +201,7 @@ "direct_warning_to_all": "This post will be visible to all the mentioned users.", "direct_warning_to_first_only": "This post will only be visible to the mentioned users at the beginning of the message.", "posting": "Posting", + "post": "Post", "preview": "Preview", "preview_empty": "Empty", "empty_status_error": "Can't post an empty status with no files", @@ -210,10 +212,10 @@ "unlisted": "This post will not be visible in Public Timeline and The Whole Known Network" }, "scope": { - "direct": "Direct - Post to mentioned users only", - "private": "Followers-only - Post to followers only", - "public": "Public - Post to public timelines", - "unlisted": "Unlisted - Do not post to public timelines" + "direct": "Direct - post to mentioned users only", + "private": "Followers-only - post to followers only", + "public": "Public - post to public timelines", + "unlisted": "Unlisted - do not post to public timelines" } }, "registration": { @@ -230,6 +232,7 @@ "bio_placeholder": "e.g.\nHi, I'm Lain.\nI’m an anime girl living in suburban Japan. You may know me from the Wired.", "reason": "Reason to register", "reason_placeholder": "This instance approves registrations manually.\nLet the administration know why you want to register.", + "register": "Register", "validations": { "username_required": "cannot be left blank", "fullname_required": "cannot be left blank", @@ -249,6 +252,7 @@ }, "settings": { "app_name": "App name", + "save": "Save changes", "security": "Security", "setting_changed": "Setting is different from default", "enter_current_password_to_confirm": "Enter your current password to confirm your identity", @@ -277,7 +281,7 @@ "attachmentRadius": "Attachments", "attachments": "Attachments", "avatar": "Avatar", - "avatarAltRadius": "Avatars (Notifications)", + "avatarAltRadius": "Avatars (notifications)", "avatarRadius": "Avatars", "background": "Background", "bio": "Bio", @@ -299,10 +303,10 @@ "cGreen": "Green (Retweet)", "cOrange": "Orange (Favorite)", "cRed": "Red (Cancel)", - "change_email": "Change Email", + "change_email": "Change email", "change_email_error": "There was an issue changing your email.", "changed_email": "Email changed successfully!", - "change_password": "Change Password", + "change_password": "Change password", "change_password_error": "There was an issue changing your password.", "changed_password": "Password changed successfully!", "chatMessageRadius": "Chat message", @@ -311,9 +315,9 @@ "confirm_new_password": "Confirm new password", "current_password": "Current password", "mutes_and_blocks": "Mutes and Blocks", - "data_import_export_tab": "Data Import / Export", + "data_import_export_tab": "Data import / export", "default_vis": "Default visibility scope", - "delete_account": "Delete Account", + "delete_account": "Delete account", "delete_account_description": "Permanently delete your data and deactivate your account.", "delete_account_error": "There was an issue deleting your account. If this persists please contact your instance administrator.", "delete_account_instructions": "Type your password in the input below to confirm account deletion.", @@ -369,14 +373,14 @@ "play_videos_in_modal": "Play videos in a popup frame", "profile_fields": { "label": "Profile metadata", - "add_field": "Add Field", + "add_field": "Add field", "name": "Label", "value": "Content" }, "use_contain_fit": "Don't crop the attachment in thumbnails", "name": "Name", - "name_bio": "Name & Bio", - "new_email": "New Email", + "name_bio": "Name & bio", + "new_email": "New email", "new_password": "New password", "notification_visibility": "Types of notifications to show", "notification_visibility_follows": "Follows", @@ -392,19 +396,19 @@ "hide_followers_description": "Don't show who's following me", "hide_follows_count_description": "Don't show follow count", "hide_followers_count_description": "Don't show follower count", - "show_admin_badge": "Show Admin badge in my profile", - "show_moderator_badge": "Show Moderator badge in my profile", + "show_admin_badge": "Show \"Admin\" badge in my profile", + "show_moderator_badge": "Show \"Moderator\" badge in my profile", "nsfw_clickthrough": "Enable clickthrough attachment and link preview image hiding for NSFW statuses", "oauth_tokens": "OAuth tokens", "token": "Token", - "refresh_token": "Refresh Token", - "valid_until": "Valid Until", + "refresh_token": "Refresh token", + "valid_until": "Valid until", "revoke_token": "Revoke", "panelRadius": "Panels", "pause_on_unfocused": "Pause streaming when tab is not focused", "presets": "Presets", - "profile_background": "Profile Background", - "profile_banner": "Profile Banner", + "profile_background": "Profile background", + "profile_banner": "Profile banner", "profile_tab": "Profile", "radii_help": "Set up interface edge rounding (in pixels)", "replies_in_timeline": "Replies in timeline", @@ -616,8 +620,8 @@ }, "version": { "title": "Version", - "backend_version": "Backend Version", - "frontend_version": "Frontend Version" + "backend_version": "Backend version", + "frontend_version": "Frontend version" } }, "time": { @@ -665,7 +669,9 @@ "reload": "Reload", "up_to_date": "Up-to-date", "no_more_statuses": "No more statuses", - "no_statuses": "No statuses" + "no_statuses": "No statuses", + "socket_reconnected": "Realtime connection established", + "socket_broke": "Realtime connection lost: CloseEvent code {0}" }, "status": { "favorites": "Favorites", @@ -758,7 +764,7 @@ } }, "user_profile": { - "timeline_title": "User Timeline", + "timeline_title": "User timeline", "profile_does_not_exist": "Sorry, this profile does not exist.", "profile_loading_error": "Sorry, there was an error loading this profile." }, @@ -776,7 +782,7 @@ "who_to_follow": "Who to follow" }, "tool_tip": { - "media_upload": "Upload Media", + "media_upload": "Upload media", "repeat": "Repeat", "reply": "Reply", "favorite": "Favorite", diff --git a/src/modules/api.js b/src/modules/api.js index 08485a30..8654b90a 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) { diff --git a/src/modules/users.js b/src/modules/users.js index 7018bbd8..88ecb3e5 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -553,9 +553,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) diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index f4483149..436b8b0a 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -1152,6 +1152,7 @@ export const ProcessedWS = ({ // 1000 = Normal Closure eventTarget.close = () => { socket.close(1000, 'Shutting down socket') } + eventTarget.getState = () => socket.readyState return eventTarget } @@ -1183,7 +1184,10 @@ export const handleMastoWS = (wsEvent) => { export const WSConnectionStatus = Object.freeze({ 'JOINED': 1, 'CLOSED': 2, - 'ERROR': 3 + 'ERROR': 3, + 'DISABLED': 4, + 'STARTING': 5, + 'STARTING_INITIAL': 6 }) const chats = ({ credentials }) => { diff --git a/src/services/backend_interactor_service/backend_interactor_service.js b/src/services/backend_interactor_service/backend_interactor_service.js index 45e6bd0e..4a40f5b5 100644 --- a/src/services/backend_interactor_service/backend_interactor_service.js +++ b/src/services/backend_interactor_service/backend_interactor_service.js @@ -1,17 +1,25 @@ import apiService, { getMastodonSocketURI, ProcessedWS } from '../api/api.service.js' -import timelineFetcherService from '../timeline_fetcher/timeline_fetcher.service.js' +import timelineFetcher from '../timeline_fetcher/timeline_fetcher.service.js' import notificationsFetcher from '../notifications_fetcher/notifications_fetcher.service.js' import followRequestFetcher from '../../services/follow_request_fetcher/follow_request_fetcher.service' const backendInteractorService = credentials => ({ startFetchingTimeline ({ timeline, store, userId = false, tag }) { - return timelineFetcherService.startFetching({ timeline, store, credentials, userId, tag }) + return timelineFetcher.startFetching({ timeline, store, credentials, userId, tag }) + }, + + fetchTimeline (args) { + return timelineFetcher.fetchAndUpdate({ ...args, credentials }) }, startFetchingNotifications ({ store }) { return notificationsFetcher.startFetching({ store, credentials }) }, + fetchNotifications (args) { + return notificationsFetcher.fetchAndUpdate({ ...args, credentials }) + }, + startFetchingFollowRequests ({ store }) { return followRequestFetcher.startFetching({ store, credentials }) }, diff --git a/src/services/notifications_fetcher/notifications_fetcher.service.js b/src/services/notifications_fetcher/notifications_fetcher.service.js index beeb167c..f83f871e 100644 --- a/src/services/notifications_fetcher/notifications_fetcher.service.js +++ b/src/services/notifications_fetcher/notifications_fetcher.service.js @@ -5,7 +5,7 @@ const update = ({ store, notifications, older }) => { store.dispatch('addNewNotifications', { notifications, older }) } -const fetchAndUpdate = ({ store, credentials, older = false }) => { +const fetchAndUpdate = ({ store, credentials, older = false, since }) => { const args = { credentials } const { getters } = store const rootState = store.rootState || store.state @@ -22,8 +22,10 @@ const fetchAndUpdate = ({ store, credentials, older = false }) => { return fetchNotifications({ store, args, older }) } else { // fetch new notifications - if (timelineData.maxId !== Number.POSITIVE_INFINITY) { + if (since === undefined && timelineData.maxId !== Number.POSITIVE_INFINITY) { args['since'] = timelineData.maxId + } else if (since !== null) { + args['since'] = since } const result = fetchNotifications({ store, args, older }) diff --git a/src/services/theme_data/pleromafe.js b/src/services/theme_data/pleromafe.js index bec1eebd..14aac975 100644 --- a/src/services/theme_data/pleromafe.js +++ b/src/services/theme_data/pleromafe.js @@ -616,6 +616,23 @@ export const SLOT_INHERITANCE = { textColor: true }, + alertSuccess: { + depends: ['cGreen'], + opacity: 'alert' + }, + alertSuccessText: { + depends: ['text'], + layer: 'alert', + variant: 'alertSuccess', + textColor: true + }, + alertSuccessPanelText: { + depends: ['panelText'], + layer: 'alertPanel', + variant: 'alertSuccess', + textColor: true + }, + alertNeutral: { depends: ['text'], opacity: 'alert' @@ -656,6 +673,17 @@ export const SLOT_INHERITANCE = { textColor: true }, + alertPopupSuccess: { + depends: ['alertSuccess'], + opacity: 'alertPopup' + }, + alertPopupSuccessText: { + depends: ['alertSuccessText'], + layer: 'popover', + variant: 'alertPopupSuccess', + textColor: true + }, + alertPopupNeutral: { depends: ['alertNeutral'], opacity: 'alertPopup' diff --git a/src/services/timeline_fetcher/timeline_fetcher.service.js b/src/services/timeline_fetcher/timeline_fetcher.service.js index 921df3ed..46bba41a 100644 --- a/src/services/timeline_fetcher/timeline_fetcher.service.js +++ b/src/services/timeline_fetcher/timeline_fetcher.service.js @@ -23,7 +23,8 @@ const fetchAndUpdate = ({ showImmediately = false, userId = false, tag = false, - until + until, + since }) => { const args = { timeline, credentials } const rootState = store.rootState || store.state @@ -35,7 +36,11 @@ const fetchAndUpdate = ({ if (older) { args['until'] = until || timelineData.minId } else { - args['since'] = timelineData.maxId + if (since === undefined) { + args['since'] = timelineData.maxId + } else if (since !== null) { + args['since'] = since + } } args['userId'] = userId