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 @@
- {{ $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