diff options
| author | Shpuld Shpuldson <shp@cock.li> | 2020-11-19 12:38:35 +0200 |
|---|---|---|
| committer | Shpuld Shpuldson <shp@cock.li> | 2020-11-19 12:38:35 +0200 |
| commit | b1ab09b348014a8e70097158bda4d2e1226e4cb6 (patch) | |
| tree | 65740e91d81cbaaca444440a95713fdb2a46b849 | |
| parent | 419df9d44673bf1abe3cda7ba6cafee9dd6eaba4 (diff) | |
| parent | d770bab1b0e5d543547c59d86aef533f77ceafe2 (diff) | |
fix merge conflict
| -rw-r--r-- | CHANGELOG.md | 6 | ||||
| -rw-r--r-- | src/boot/after_store.js | 3 | ||||
| -rw-r--r-- | src/components/notifications/notifications.js | 3 | ||||
| -rw-r--r-- | src/components/notifications/notifications.vue | 7 | ||||
| -rw-r--r-- | src/components/timeline/timeline.js | 12 | ||||
| -rw-r--r-- | src/components/timeline/timeline.vue | 24 | ||||
| -rw-r--r-- | src/components/timeline_menu/timeline_menu.js | 5 | ||||
| -rw-r--r-- | src/i18n/en.json | 3 | ||||
| -rw-r--r-- | src/modules/statuses.js | 23 | ||||
| -rw-r--r-- | src/modules/users.js | 6 | ||||
| -rw-r--r-- | src/services/api/api.service.js | 2 | ||||
| -rw-r--r-- | src/services/entity_normalizer/entity_normalizer.service.js | 12 | ||||
| -rw-r--r-- | src/services/favicon_service/favicon_service.js | 61 | ||||
| -rw-r--r-- | src/services/notifications_fetcher/notifications_fetcher.service.js | 18 | ||||
| -rw-r--r-- | src/services/timeline_fetcher/timeline_fetcher.service.js | 18 | ||||
| -rw-r--r-- | test/unit/specs/components/user_profile.spec.js | 3 |
16 files changed, 123 insertions, 83 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 17fb1de8..2e225c00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] ### Fixed - Fixed the occasional bug where screen would scroll 1px when typing into a reply form +- Fixed timeline errors locking timelines + +### Changed +- Errors when fetching are now shown with popup errors instead of "Error fetching updates" in panel headers - Fixed custom emoji not working in profile field names +- Fixed pinned statuses not appearing in user profiles - Fixed username autocomplete being jumpy @@ -23,6 +28,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Import/export a muted users - Proper handling of deletes when using websocket streaming - Added optimistic chat message sending, so you can start writing next message before the previous one has been sent +- Added a small red badge to the favicon when there's unread notifications ### Fixed - Fixed clicking NSFW hider through status popover diff --git a/src/boot/after_store.js b/src/boot/after_store.js index 3cbbf020..b472fcf6 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -7,6 +7,7 @@ import { getOrCreateApp, getClientToken } from '../services/new_api/oauth.js' import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js' import { CURRENT_VERSION } from '../services/theme_data/theme_data.service.js' import { applyTheme } from '../services/style_setter/style_setter.js' +import FaviconService from '../services/favicon_service/favicon_service.js' let staticInitialResults = null @@ -326,6 +327,8 @@ const afterStoreSetup = async ({ store, i18n }) => { const width = windowWidth() store.dispatch('setMobileLayout', width <= 800) + FaviconService.initFaviconService() + const overrides = window.___pleromafe_dev_overrides || {} const server = (typeof overrides.target !== 'undefined') ? overrides.target : window.location.origin store.dispatch('setInstanceOption', { name: 'server', value: server }) diff --git a/src/components/notifications/notifications.js b/src/components/notifications/notifications.js index 4b479e13..49258563 100644 --- a/src/components/notifications/notifications.js +++ b/src/components/notifications/notifications.js @@ -6,6 +6,7 @@ import { filteredNotificationsFromStore, unseenNotificationsFromStore } from '../../services/notification_utils/notification_utils.js' +import FaviconService from '../../services/favicon_service/favicon_service.js' import { library } from '@fortawesome/fontawesome-svg-core' import { faCircleNotch } from '@fortawesome/free-solid-svg-icons' @@ -75,8 +76,10 @@ const Notifications = { watch: { unseenCountTitle (count) { if (count > 0) { + FaviconService.drawFaviconBadge() this.$store.dispatch('setPageTitle', `(${count})`) } else { + FaviconService.clearFaviconBadge() this.$store.dispatch('setPageTitle', '') } } diff --git a/src/components/notifications/notifications.vue b/src/components/notifications/notifications.vue index bd875cca..b976026e 100644 --- a/src/components/notifications/notifications.vue +++ b/src/components/notifications/notifications.vue @@ -15,13 +15,6 @@ class="badge badge-notification unseen-count" >{{ unseenCount }}</span> </div> - <div - v-if="error" - class="loadmore-error alert error" - @click.prevent - > - {{ $t('timeline.error_fetching') }} - </div> <button v-if="unseenCount" class="read-button" diff --git a/src/components/timeline/timeline.js b/src/components/timeline/timeline.js index cba46daf..665d195e 100644 --- a/src/components/timeline/timeline.js +++ b/src/components/timeline/timeline.js @@ -50,17 +50,10 @@ const Timeline = { TimelineMenu }, computed: { - timelineError () { - return this.$store.state.statuses.error - }, - errorData () { - return this.$store.state.statuses.errorData - }, newStatusCount () { return this.timeline.newStatusCount }, showLoadButton () { - if (this.timelineError || this.errorData) return false return this.timeline.newStatusCount > 0 || this.timeline.flushMarker !== 0 }, loadButtonString () { @@ -171,11 +164,12 @@ const Timeline = { userId: this.userId, tag: this.tag }).then(({ statuses }) => { - store.commit('setLoading', { timeline: this.timelineName, value: false }) if (statuses && statuses.length === 0) { this.bottomedOut = true } - }) + }).finally(() => + store.commit('setLoading', { timeline: this.timelineName, value: false }) + ) }, 1000, this), determineVisibleStatuses () { if (!this.$refs.timeline) return diff --git a/src/components/timeline/timeline.vue b/src/components/timeline/timeline.vue index 04859852..d4da2a87 100644 --- a/src/components/timeline/timeline.vue +++ b/src/components/timeline/timeline.vue @@ -2,22 +2,8 @@ <div :class="[classes.root, 'Timeline']"> <div :class="classes.header"> <TimelineMenu v-if="!embedded" /> - <div - v-if="timelineError" - class="loadmore-error alert error" - @click.prevent - > - {{ $t('timeline.error_fetching') }} - </div> - <div - v-else-if="errorData" - class="loadmore-error alert error" - @click.prevent - > - {{ errorData.statusText }} - </div> <button - v-else-if="showLoadButton" + v-if="showLoadButton" class="loadmore-button" @click.prevent="showNewStatuses" > @@ -76,18 +62,12 @@ {{ $t('timeline.no_more_statuses') }} </div> <a - v-else-if="!timeline.loading && !errorData" + v-else-if="!timeline.loading" href="#" @click.prevent="fetchOlderStatuses()" > <div class="new-status-notification text-center panel-footer">{{ $t('timeline.load_older') }}</div> </a> - <a - v-else-if="errorData" - href="#" - > - <div class="new-status-notification text-center panel-footer">{{ errorData.error }}</div> - </a> <div v-else class="new-status-notification text-center panel-footer" diff --git a/src/components/timeline_menu/timeline_menu.js b/src/components/timeline_menu/timeline_menu.js index 4ccd52b4..ef8a5813 100644 --- a/src/components/timeline_menu/timeline_menu.js +++ b/src/components/timeline_menu/timeline_menu.js @@ -19,7 +19,7 @@ library.add( faChevronDown ) -// Route -> i18n key mapping, exported andnot in the computed +// Route -> i18n key mapping, exported and not in the computed // because nav panel benefits from the same information. export const timelineNames = () => { return { @@ -27,8 +27,7 @@ export const timelineNames = () => { 'bookmarks': 'nav.bookmarks', 'dms': 'nav.dms', 'public-timeline': 'nav.public_tl', - 'public-external-timeline': 'nav.twkn', - 'tag-timeline': 'tag' + 'public-external-timeline': 'nav.twkn' } } diff --git a/src/i18n/en.json b/src/i18n/en.json index d3d57562..3f6ae372 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -130,6 +130,7 @@ }, "notifications": { "broken_favorite": "Unknown status, searching for it…", + "error": "Error fetching notifications: {0}", "favorited_you": "favorited your status", "followed_you": "followed you", "follow_request": "wants to follow you", @@ -634,7 +635,7 @@ "timeline": { "collapse": "Collapse", "conversation": "Conversation", - "error_fetching": "Error fetching updates", + "error": "Error fetching timeline: {0}", "load_older": "Load older statuses", "no_retweet_hint": "Post is marked as followers-only or direct and cannot be repeated", "repeated": "repeated", diff --git a/src/modules/statuses.js b/src/modules/statuses.js index e673141d..33c68c57 100644 --- a/src/modules/statuses.js +++ b/src/modules/statuses.js @@ -39,8 +39,7 @@ const emptyNotifications = () => ({ minId: Number.POSITIVE_INFINITY, data: [], idStore: {}, - loading: false, - error: false + loading: false }) export const defaultState = () => ({ @@ -50,8 +49,6 @@ export const defaultState = () => ({ maxId: 0, notifications: emptyNotifications(), favorites: new Set(), - error: false, - errorData: null, timelines: { mentions: emptyTl(), public: emptyTl(), @@ -462,18 +459,9 @@ export const mutations = { const newStatus = state.allStatusesObject[id] newStatus.nsfw = nsfw }, - setError (state, { value }) { - state.error = value - }, - setErrorData (state, { value }) { - state.errorData = value - }, setNotificationsLoading (state, { value }) { state.notifications.loading = value }, - setNotificationsError (state, { value }) { - state.notifications.error = value - }, setNotificationsSilence (state, { value }) { state.notifications.desktopNotificationSilence = value }, @@ -588,18 +576,9 @@ const statuses = { } commit('addNewNotifications', { dispatch, notifications, older, rootGetters, newNotificationSideEffects }) }, - setError ({ rootState, commit }, { value }) { - commit('setError', { value }) - }, - setErrorData ({ rootState, commit }, { value }) { - commit('setErrorData', { value }) - }, setNotificationsLoading ({ rootState, commit }, { value }) { commit('setNotificationsLoading', { value }) }, - setNotificationsError ({ rootState, commit }, { value }) { - commit('setNotificationsError', { value }) - }, setNotificationsSilence ({ rootState, commit }, { value }) { commit('setNotificationsSilence', { value }) }, diff --git a/src/modules/users.js b/src/modules/users.js index 13df9df4..4bdf3360 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -137,11 +137,11 @@ export const mutations = { }, saveFriendIds (state, { id, friendIds }) { const user = state.usersObject[id] - user.friendIds = uniq(concat(user.friendIds, friendIds)) + user.friendIds = uniq(concat(user.friendIds || [], friendIds)) }, saveFollowerIds (state, { id, followerIds }) { const user = state.usersObject[id] - user.followerIds = uniq(concat(user.followerIds, followerIds)) + user.followerIds = uniq(concat(user.followerIds || [], followerIds)) }, // Because frontend doesn't have a reason to keep these stuff in memory // outside of viewing someones user profile. @@ -202,7 +202,9 @@ export const mutations = { }, setPinnedToUser (state, status) { const user = state.usersObject[status.user.id] + user.pinnedStatusIds = user.pinnedStatusIds || [] const index = user.pinnedStatusIds.indexOf(status.id) + if (status.pinned && index === -1) { user.pinnedStatusIds.push(status.id) } else if (!status.pinned && index !== -1) { diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 22b5e8ba..8da933c4 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -560,7 +560,7 @@ const fetchTimeline = ({ }) .then((data) => data.json()) .then((data) => { - if (!data.error) { + if (!data.errors) { return { data: data.map(isNotifications ? parseNotification : parseStatus), pagination } } else { data.status = status diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js index a4c1a1bf..e3a52489 100644 --- a/src/services/entity_normalizer/entity_normalizer.service.js +++ b/src/services/entity_normalizer/entity_normalizer.service.js @@ -2,6 +2,15 @@ import escape from 'escape-html' import parseLinkHeader from 'parse-link-header' import { isStatusNotification } from '../notification_utils/notification_utils.js' +/** NOTICE! ** + * Do not initialize UI-generated data here. + * It will override existing data. + * + * i.e. user.pinnedStatusIds was set to [] here + * UI code would update it with data but upon next user fetch + * it would be reverted back to [] + */ + const qvitterStatusType = (status) => { if (status.is_post_verb) { return 'status' @@ -173,9 +182,6 @@ export const parseUser = (data) => { output.locked = data.locked output.followers_count = data.followers_count output.statuses_count = data.statuses_count - output.friendIds = [] - output.followerIds = [] - output.pinnedStatusIds = [] if (data.pleroma) { output.follow_request_count = data.pleroma.follow_request_count diff --git a/src/services/favicon_service/favicon_service.js b/src/services/favicon_service/favicon_service.js new file mode 100644 index 00000000..d1ddee41 --- /dev/null +++ b/src/services/favicon_service/favicon_service.js @@ -0,0 +1,61 @@ +import { find } from 'lodash' + +const createFaviconService = () => { + let favimg, favcanvas, favcontext, favicon + const faviconWidth = 128 + const faviconHeight = 128 + const badgeRadius = 32 + + const initFaviconService = () => { + const nodes = document.getElementsByTagName('link') + favicon = find(nodes, node => node.rel === 'icon') + if (favicon) { + favcanvas = document.createElement('canvas') + favcanvas.width = faviconWidth + favcanvas.height = faviconHeight + favimg = new Image() + favimg.src = favicon.href + favcontext = favcanvas.getContext('2d') + } + } + + const isImageLoaded = (img) => img.complete && img.naturalHeight !== 0 + + const clearFaviconBadge = () => { + if (!favimg || !favcontext || !favicon) return + + favcontext.clearRect(0, 0, faviconWidth, faviconHeight) + if (isImageLoaded(favimg)) { + favcontext.drawImage(favimg, 0, 0, favimg.width, favimg.height, 0, 0, faviconWidth, faviconHeight) + } + favicon.href = favcanvas.toDataURL('image/png') + } + + const drawFaviconBadge = () => { + if (!favimg || !favcontext || !favcontext) return + + clearFaviconBadge() + + const style = getComputedStyle(document.body) + const badgeColor = `${style.getPropertyValue('--badgeNotification') || 'rgb(240, 100, 100)'}` + + if (isImageLoaded(favimg)) { + favcontext.drawImage(favimg, 0, 0, favimg.width, favimg.height, 0, 0, faviconWidth, faviconHeight) + } + favcontext.fillStyle = badgeColor + favcontext.beginPath() + favcontext.arc(faviconWidth - badgeRadius, badgeRadius, badgeRadius, 0, 2 * Math.PI, false) + favcontext.fill() + favicon.href = favcanvas.toDataURL('image/png') + } + + return { + initFaviconService, + clearFaviconBadge, + drawFaviconBadge + } +} + +const FaviconService = createFaviconService() + +export default FaviconService diff --git a/src/services/notifications_fetcher/notifications_fetcher.service.js b/src/services/notifications_fetcher/notifications_fetcher.service.js index c908b644..beeb167c 100644 --- a/src/services/notifications_fetcher/notifications_fetcher.service.js +++ b/src/services/notifications_fetcher/notifications_fetcher.service.js @@ -2,7 +2,6 @@ import apiService from '../api/api.service.js' import { promiseInterval } from '../promise_interval/promise_interval.js' const update = ({ store, notifications, older }) => { - store.dispatch('setNotificationsError', { value: false }) store.dispatch('addNewNotifications', { notifications, older }) } @@ -47,11 +46,22 @@ const fetchAndUpdate = ({ store, credentials, older = false }) => { const fetchNotifications = ({ store, args, older }) => { return apiService.fetchTimeline(args) - .then(({ data: notifications }) => { + .then((response) => { + if (response.errors) { + throw new Error(`${response.status} ${response.statusText}`) + } + const notifications = response.data update({ store, notifications, older }) return notifications - }, () => store.dispatch('setNotificationsError', { value: true })) - .catch(() => store.dispatch('setNotificationsError', { value: true })) + }) + .catch((error) => { + store.dispatch('pushGlobalNotice', { + level: 'error', + messageKey: 'notifications.error', + messageArgs: [error.message], + timeout: 5000 + }) + }) } const startFetching = ({ credentials, store }) => { diff --git a/src/services/timeline_fetcher/timeline_fetcher.service.js b/src/services/timeline_fetcher/timeline_fetcher.service.js index 72ea4890..921df3ed 100644 --- a/src/services/timeline_fetcher/timeline_fetcher.service.js +++ b/src/services/timeline_fetcher/timeline_fetcher.service.js @@ -6,9 +6,6 @@ import { promiseInterval } from '../promise_interval/promise_interval.js' const update = ({ store, statuses, timeline, showImmediately, userId, pagination }) => { const ccTimeline = camelCase(timeline) - store.dispatch('setError', { value: false }) - store.dispatch('setErrorData', { value: null }) - store.dispatch('addNewStatuses', { timeline: ccTimeline, userId, @@ -52,9 +49,8 @@ const fetchAndUpdate = ({ return apiService.fetchTimeline(args) .then(response => { - if (response.error) { - store.dispatch('setErrorData', { value: response }) - return + if (response.errors) { + throw new Error(`${response.status} ${response.statusText}`) } const { data: statuses, pagination } = response @@ -63,7 +59,15 @@ const fetchAndUpdate = ({ } update({ store, statuses, timeline, showImmediately, userId, pagination }) return { statuses, pagination } - }, () => store.dispatch('setError', { value: true })) + }) + .catch((error) => { + store.dispatch('pushGlobalNotice', { + level: 'error', + messageKey: 'timeline.error', + messageArgs: [error.message], + timeout: 5000 + }) + }) } const startFetching = ({ timeline = 'friends', credentials, store, userId = false, tag = false }) => { diff --git a/test/unit/specs/components/user_profile.spec.js b/test/unit/specs/components/user_profile.spec.js index dcf066f9..80092b41 100644 --- a/test/unit/specs/components/user_profile.spec.js +++ b/test/unit/specs/components/user_profile.spec.js @@ -8,8 +8,7 @@ const localVue = createLocalVue() localVue.use(Vuex) const mutations = { - clearTimeline: () => {}, - setError: () => {} + clearTimeline: () => {} } const actions = { |
