diff --git a/CHANGELOG.md b/CHANGELOG.md index 056a0881b..87123ddc4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,17 +3,29 @@ All notable changes to this project will be documented in this file. 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 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 ### Added - New option to optimize timeline rendering to make the site more responsive (enabled by default) - New instance option `logoLeft` to move logo to the left side in desktop nav bar - 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 chats list not updating its order when new messages come in -- Fixed chat messages sometimes getting lost when you receive a message at the same time - Fixed clicking NSFW hider through status popover - Fixed chat-view back button being hard to click - Fixed fresh chat notifications being cleared immediately while leaving the chat view and not having time to actually see the messages @@ -29,6 +41,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Logo is now clickable - Changed default logo to SVG version + +## [2.1.2] - 2020-09-17 +### Fixed +- Fixed chats list not updating its order when new messages come in +- Fixed chat messages sometimes getting lost when you receive a message at the same time + + ## [2.1.1] - 2020-09-08 ### Changed - Polls will be hidden with status content if "Collapse posts with subjects" is enabled and the post is collapsed. @@ -154,8 +173,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Ability to change user's email - About page - Added remote user redirect -- Bookmarks + ### Changed - changed the way fading effects for user profile/long statuses works, now uses css-mask instead of gradient background hacks which weren't exactly compatible with semi-transparent themes + ### Fixed - improved hotkey behavior on autocomplete popup diff --git a/index.html b/index.html index 545dff72c..58bd574bd 100644 --- a/index.html +++ b/index.html @@ -3,7 +3,6 @@ - Pleroma diff --git a/src/boot/after_store.js b/src/boot/after_store.js index 3cbbf020c..b472fcf64 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 4b479e135..492585631 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/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js index de583269d..3ff4a3c8e 100644 --- a/src/components/post_status_form/post_status_form.js +++ b/src/components/post_status_form/post_status_form.js @@ -531,7 +531,7 @@ const PostStatusForm = { !(isFormBiggerThanScroller && this.$refs.textarea.selectionStart !== this.$refs.textarea.value.length) const totalDelta = shouldScrollToBottom ? bottomChangeDelta : 0 - const targetScroll = currentScroll + totalDelta + const targetScroll = Math.round(currentScroll + totalDelta) if (scrollerRef === window) { scrollerRef.scroll(0, targetScroll) diff --git a/src/components/react_button/react_button.vue b/src/components/react_button/react_button.vue index 95d95b11d..e508a3e9c 100644 --- a/src/components/react_button/react_button.vue +++ b/src/components/react_button/react_button.vue @@ -4,6 +4,7 @@ placement="top" :offset="{ y: 5 }" class="react-button-popover" + :bound-to="{ x: 'container' }" >
- + + + diff --git a/src/components/user_profile/user_profile.vue b/src/components/user_profile/user_profile.vue index f1f518402..745e795de 100644 --- a/src/components/user_profile/user_profile.vue +++ b/src/components/user_profile/user_profile.vue @@ -20,14 +20,13 @@ :key="index" class="user-profile-field" > +
- {{ field.name }} -
- + v-html="field.name" + />
{ if (status.is_post_verb) { return 'status' @@ -53,7 +62,7 @@ export const parseUser = (data) => { output.fields = data.fields output.fields_html = data.fields.map(field => { return { - name: addEmojis(field.name, data.emojis), + name: addEmojis(escape(field.name), data.emojis), value: addEmojis(field.value, data.emojis) } }) @@ -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 000000000..d1ddee410 --- /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