fix merge conflict
This commit is contained in:
commit
b1ab09b348
@ -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
|
||||
|
@ -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 })
|
||||
|
@ -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', '')
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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'
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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 })
|
||||
},
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
61
src/services/favicon_service/favicon_service.js
Normal file
61
src/services/favicon_service/favicon_service.js
Normal file
@ -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
|
@ -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 }) => {
|
||||
|
@ -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 }) => {
|
||||
|
@ -8,8 +8,7 @@ const localVue = createLocalVue()
|
||||
localVue.use(Vuex)
|
||||
|
||||
const mutations = {
|
||||
clearTimeline: () => {},
|
||||
setError: () => {}
|
||||
clearTimeline: () => {}
|
||||
}
|
||||
|
||||
const actions = {
|
||||
|
Loading…
Reference in New Issue
Block a user