diff options
| -rw-r--r-- | CHANGELOG.md | 8 | ||||
| -rw-r--r-- | src/boot/after_store.js | 3 | ||||
| -rw-r--r-- | src/components/notifications/notifications.js | 3 | ||||
| -rw-r--r-- | src/modules/users.js | 6 | ||||
| -rw-r--r-- | src/services/entity_normalizer/entity_normalizer.service.js | 12 | ||||
| -rw-r--r-- | src/services/favicon_service/favicon_service.js | 61 |
6 files changed, 87 insertions, 6 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 18c1cf3b..22a7af9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,13 +6,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] ### Fixed -- Fixed regression in react popup alignment and overflowing - 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 + + +## [2.2.1] - 2020-11-11 +### Fixed +- Fixed regression in react popup alignment and overflowing ## [2.2.0] - 2020-11-06 @@ -22,6 +27,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/modules/users.js b/src/modules/users.js index 9245db5c..655db4c7 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/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 |
