Merge branch 'develop' into fedi-absturztau-be

This commit is contained in:
Absturztaube 2020-11-23 09:21:56 +01:00
commit a6822d26bb
26 changed files with 193 additions and 118 deletions

View file

@ -3,17 +3,35 @@ 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/). The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased] ## [Unreleased]
### Fixed
- Fixed the occasional bug where screen would scroll 1px when typing into a reply form
- Fixed timeline errors locking timelines
- Fixed missing highlighted border in expanded conversations
### 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
### Added ### Added
- New option to optimize timeline rendering to make the site more responsive (enabled by default) - 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 - New instance option `logoLeft` to move logo to the left side in desktop nav bar
- Import/export a muted users - Import/export a muted users
- Proper handling of deletes when using websocket streaming - 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 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
- Added the NSFW alert to link previews
### Fixed ### 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 clicking NSFW hider through status popover
- Fixed chat-view back button being hard to click - 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 - Fixed fresh chat notifications being cleared immediately while leaving the chat view and not having time to actually see the messages
@ -29,6 +47,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Logo is now clickable - Logo is now clickable
- Changed default logo to SVG version - 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 ## [2.1.1] - 2020-09-08
### Changed ### Changed
- Polls will be hidden with status content if "Collapse posts with subjects" is enabled and the post is collapsed. - Polls will be hidden with status content if "Collapse posts with subjects" is enabled and the post is collapsed.
@ -154,8 +179,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Ability to change user's email - Ability to change user's email
- About page - About page
- Added remote user redirect - Added remote user redirect
- Bookmarks
### Changed ### 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 - 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 ### Fixed
- improved hotkey behavior on autocomplete popup - improved hotkey behavior on autocomplete popup

View file

@ -3,7 +3,6 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1,user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=1,user-scalable=no">
<title>Pleroma</title>
<!--server-generated-meta--> <!--server-generated-meta-->
<link rel="icon" type="image/png" href="/favicon.png"> <link rel="icon" type="image/png" href="/favicon.png">
</head> </head>

View file

@ -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 backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
import { CURRENT_VERSION } from '../services/theme_data/theme_data.service.js' import { CURRENT_VERSION } from '../services/theme_data/theme_data.service.js'
import { applyTheme } from '../services/style_setter/style_setter.js' import { applyTheme } from '../services/style_setter/style_setter.js'
import FaviconService from '../services/favicon_service/favicon_service.js'
let staticInitialResults = null let staticInitialResults = null
@ -326,6 +327,8 @@ const afterStoreSetup = async ({ store, i18n }) => {
const width = windowWidth() const width = windowWidth()
store.dispatch('setMobileLayout', width <= 800) store.dispatch('setMobileLayout', width <= 800)
FaviconService.initFaviconService()
const overrides = window.___pleromafe_dev_overrides || {} const overrides = window.___pleromafe_dev_overrides || {}
const server = (typeof overrides.target !== 'undefined') ? overrides.target : window.location.origin const server = (typeof overrides.target !== 'undefined') ? overrides.target : window.location.origin
store.dispatch('setInstanceOption', { name: 'server', value: server }) store.dispatch('setInstanceOption', { name: 'server', value: server })

View file

@ -8,14 +8,16 @@ import {
faFile, faFile,
faMusic, faMusic,
faImage, faImage,
faVideo faVideo,
faPlayCircle
} from '@fortawesome/free-solid-svg-icons' } from '@fortawesome/free-solid-svg-icons'
library.add( library.add(
faFile, faFile,
faMusic, faMusic,
faImage, faImage,
faVideo faVideo,
faPlayCircle
) )
const Attachment = { const Attachment = {

View file

@ -57,13 +57,6 @@
} }
&.-expanded { &.-expanded {
.conversation-status {
border-color: $fallback--border;
border-color: var(--border, $fallback--border);
border-left-color: $fallback--cRed;
border-left-color: var(--cRed, $fallback--cRed);
}
.conversation-status:last-child { .conversation-status:last-child {
border-bottom: none; border-bottom: none;
border-radius: 0 0 $fallback--panelRadius $fallback--panelRadius; border-radius: 0 0 $fallback--panelRadius $fallback--panelRadius;

View file

@ -1,3 +1,5 @@
import { mapGetters } from 'vuex'
const LinkPreview = { const LinkPreview = {
name: 'LinkPreview', name: 'LinkPreview',
props: [ props: [
@ -15,11 +17,20 @@ const LinkPreview = {
// Currently BE shoudn't give cards if tagged NSFW, this is a bit paranoid // Currently BE shoudn't give cards if tagged NSFW, this is a bit paranoid
// as it makes sure to hide the image if somehow NSFW tagged preview can // as it makes sure to hide the image if somehow NSFW tagged preview can
// exist. // exist.
return this.card.image && !this.nsfw && this.size !== 'hide' return this.card.image && !this.censored && this.size !== 'hide'
},
censored () {
return this.nsfw && this.hideNsfwConfig
}, },
useDescription () { useDescription () {
return this.card.description && /\S/.test(this.card.description) return this.card.description && /\S/.test(this.card.description)
} },
hideNsfwConfig () {
return this.mergedConfig.hideNsfw
},
...mapGetters([
'mergedConfig'
])
}, },
created () { created () {
if (this.useImage) { if (this.useImage) {

View file

@ -9,12 +9,17 @@
<div <div
v-if="useImage && imageLoaded" v-if="useImage && imageLoaded"
class="card-image" class="card-image"
:class="{ 'small-image': size === 'small' }"
> >
<img :src="card.image"> <img :src="card.image">
</div> </div>
<div class="card-content"> <div class="card-content">
<span class="card-host faint">{{ card.provider_name }}</span> <span class="card-host faint">
<span
v-if="censored"
class="nsfw-alert alert warning"
>{{ $t('status.nsfw') }}</span>
{{ card.provider_name }}
</span>
<h4 class="card-title">{{ card.title }}</h4> <h4 class="card-title">{{ card.title }}</h4>
<p <p
v-if="useDescription" v-if="useDescription"
@ -50,10 +55,6 @@
} }
} }
.small-image {
width: 80px;
}
.card-content { .card-content {
max-height: 100%; max-height: 100%;
margin: 0.5em; margin: 0.5em;
@ -76,6 +77,10 @@
max-height: calc(1.2em * 3 - 1px); max-height: calc(1.2em * 3 - 1px);
} }
.nsfw-alert {
margin: 2em 0;
}
color: $fallback--text; color: $fallback--text;
color: var(--text, $fallback--text); color: var(--text, $fallback--text);
border-style: solid; border-style: solid;

View file

@ -6,6 +6,7 @@ import {
filteredNotificationsFromStore, filteredNotificationsFromStore,
unseenNotificationsFromStore unseenNotificationsFromStore
} from '../../services/notification_utils/notification_utils.js' } from '../../services/notification_utils/notification_utils.js'
import FaviconService from '../../services/favicon_service/favicon_service.js'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { faCircleNotch } from '@fortawesome/free-solid-svg-icons' import { faCircleNotch } from '@fortawesome/free-solid-svg-icons'
@ -75,8 +76,10 @@ const Notifications = {
watch: { watch: {
unseenCountTitle (count) { unseenCountTitle (count) {
if (count > 0) { if (count > 0) {
FaviconService.drawFaviconBadge()
this.$store.dispatch('setPageTitle', `(${count})`) this.$store.dispatch('setPageTitle', `(${count})`)
} else { } else {
FaviconService.clearFaviconBadge()
this.$store.dispatch('setPageTitle', '') this.$store.dispatch('setPageTitle', '')
} }
} }

View file

@ -15,13 +15,6 @@
class="badge badge-notification unseen-count" class="badge badge-notification unseen-count"
>{{ unseenCount }}</span> >{{ unseenCount }}</span>
</div> </div>
<div
v-if="error"
class="loadmore-error alert error"
@click.prevent
>
{{ $t('timeline.error_fetching') }}
</div>
<button <button
v-if="unseenCount" v-if="unseenCount"
class="read-button" class="read-button"

View file

@ -533,7 +533,7 @@ const PostStatusForm = {
!(isFormBiggerThanScroller && !(isFormBiggerThanScroller &&
this.$refs.textarea.selectionStart !== this.$refs.textarea.value.length) this.$refs.textarea.selectionStart !== this.$refs.textarea.value.length)
const totalDelta = shouldScrollToBottom ? bottomChangeDelta : 0 const totalDelta = shouldScrollToBottom ? bottomChangeDelta : 0
const targetScroll = currentScroll + totalDelta const targetScroll = Math.round(currentScroll + totalDelta)
if (scrollerRef === window) { if (scrollerRef === window) {
scrollerRef.scroll(0, targetScroll) scrollerRef.scroll(0, targetScroll)

View file

@ -4,6 +4,7 @@
placement="top" placement="top"
:offset="{ y: 5 }" :offset="{ y: 5 }"
class="react-button-popover" class="react-button-popover"
:bound-to="{ x: 'container' }"
> >
<div <div
slot="content" slot="content"
@ -37,12 +38,13 @@
<div class="reaction-bottom-fader" /> <div class="reaction-bottom-fader" />
</div> </div>
</div> </div>
<FAIcon <span slot="trigger">
slot="trigger" <FAIcon
class="fa-scale-110 fa-old-padding add-reaction-button" class="fa-scale-110 fa-old-padding add-reaction-button"
:icon="['far', 'smile-beam']" :icon="['far', 'smile-beam']"
:title="$t('tool_tip.add_reaction')" :title="$t('tool_tip.add_reaction')"
/> />
</span>
</Popover> </Popover>
</template> </template>

View file

@ -29,6 +29,8 @@ $status-margin: 0.75em;
&.-conversation { &.-conversation {
border-left-width: 4px; border-left-width: 4px;
border-left-style: solid; border-left-style: solid;
border-left-color: $fallback--cRed;
border-left-color: var(--cRed, $fallback--cRed);
} }
.gravestone { .gravestone {

View file

@ -50,17 +50,10 @@ const Timeline = {
TimelineMenu TimelineMenu
}, },
computed: { computed: {
timelineError () {
return this.$store.state.statuses.error
},
errorData () {
return this.$store.state.statuses.errorData
},
newStatusCount () { newStatusCount () {
return this.timeline.newStatusCount return this.timeline.newStatusCount
}, },
showLoadButton () { showLoadButton () {
if (this.timelineError || this.errorData) return false
return this.timeline.newStatusCount > 0 || this.timeline.flushMarker !== 0 return this.timeline.newStatusCount > 0 || this.timeline.flushMarker !== 0
}, },
loadButtonString () { loadButtonString () {
@ -171,11 +164,12 @@ const Timeline = {
userId: this.userId, userId: this.userId,
tag: this.tag tag: this.tag
}).then(({ statuses }) => { }).then(({ statuses }) => {
store.commit('setLoading', { timeline: this.timelineName, value: false })
if (statuses && statuses.length === 0) { if (statuses && statuses.length === 0) {
this.bottomedOut = true this.bottomedOut = true
} }
}) }).finally(() =>
store.commit('setLoading', { timeline: this.timelineName, value: false })
)
}, 1000, this), }, 1000, this),
determineVisibleStatuses () { determineVisibleStatuses () {
if (!this.$refs.timeline) return if (!this.$refs.timeline) return

View file

@ -2,22 +2,8 @@
<div :class="[classes.root, 'Timeline']"> <div :class="[classes.root, 'Timeline']">
<div :class="classes.header"> <div :class="classes.header">
<TimelineMenu v-if="!embedded" /> <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 <button
v-else-if="showLoadButton" v-if="showLoadButton"
class="loadmore-button" class="loadmore-button"
@click.prevent="showNewStatuses" @click.prevent="showNewStatuses"
> >
@ -76,18 +62,12 @@
{{ $t('timeline.no_more_statuses') }} {{ $t('timeline.no_more_statuses') }}
</div> </div>
<a <a
v-else-if="!timeline.loading && !errorData" v-else-if="!timeline.loading"
href="#" href="#"
@click.prevent="fetchOlderStatuses()" @click.prevent="fetchOlderStatuses()"
> >
<div class="new-status-notification text-center panel-footer">{{ $t('timeline.load_older') }}</div> <div class="new-status-notification text-center panel-footer">{{ $t('timeline.load_older') }}</div>
</a> </a>
<a
v-else-if="errorData"
href="#"
>
<div class="new-status-notification text-center panel-footer">{{ errorData.error }}</div>
</a>
<div <div
v-else v-else
class="new-status-notification text-center panel-footer" class="new-status-notification text-center panel-footer"

View file

@ -19,7 +19,7 @@ library.add(
faChevronDown 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. // because nav panel benefits from the same information.
export const timelineNames = () => { export const timelineNames = () => {
return { return {
@ -27,8 +27,7 @@ export const timelineNames = () => {
'bookmarks': 'nav.bookmarks', 'bookmarks': 'nav.bookmarks',
'dms': 'nav.dms', 'dms': 'nav.dms',
'public-timeline': 'nav.public_tl', 'public-timeline': 'nav.public_tl',
'public-external-timeline': 'nav.twkn', 'public-external-timeline': 'nav.twkn'
'tag-timeline': 'tag'
} }
} }

View file

@ -20,14 +20,13 @@
:key="index" :key="index"
class="user-profile-field" class="user-profile-field"
> >
<!-- eslint-disable vue/no-v-html -->
<dt <dt
:title="user.fields_text[index].name" :title="user.fields_text[index].name"
class="user-profile-field-name" class="user-profile-field-name"
@click.prevent="linkClicked" @click.prevent="linkClicked"
> v-html="field.name"
{{ field.name }} />
</dt>
<!-- eslint-disable vue/no-v-html -->
<dd <dd
:title="user.fields_text[index].value" :title="user.fields_text[index].value"
class="user-profile-field-value" class="user-profile-field-value"

View file

@ -1,6 +1,7 @@
<template> <template>
<video <video
class="video" class="video"
preload="metadata"
:src="attachment.url" :src="attachment.url"
:loop="loopVideo" :loop="loopVideo"
:controls="controls" :controls="controls"

View file

@ -130,6 +130,7 @@
}, },
"notifications": { "notifications": {
"broken_favorite": "Unknown status, searching for it…", "broken_favorite": "Unknown status, searching for it…",
"error": "Error fetching notifications: {0}",
"favorited_you": "favorited your status", "favorited_you": "favorited your status",
"followed_you": "followed you", "followed_you": "followed you",
"follow_request": "wants to follow you", "follow_request": "wants to follow you",
@ -377,7 +378,7 @@
"hide_followers_count_description": "Don't show follower count", "hide_followers_count_description": "Don't show follower count",
"show_admin_badge": "Show Admin badge in my profile", "show_admin_badge": "Show Admin badge in my profile",
"show_moderator_badge": "Show Moderator badge in my profile", "show_moderator_badge": "Show Moderator badge in my profile",
"nsfw_clickthrough": "Enable clickthrough NSFW attachment hiding", "nsfw_clickthrough": "Enable clickthrough attachment and link preview image hiding for NSFW statuses",
"oauth_tokens": "OAuth tokens", "oauth_tokens": "OAuth tokens",
"token": "Token", "token": "Token",
"refresh_token": "Refresh Token", "refresh_token": "Refresh Token",
@ -635,7 +636,7 @@
"timeline": { "timeline": {
"collapse": "Collapse", "collapse": "Collapse",
"conversation": "Conversation", "conversation": "Conversation",
"error_fetching": "Error fetching updates", "error": "Error fetching timeline: {0}",
"load_older": "Load older statuses", "load_older": "Load older statuses",
"no_retweet_hint": "Post is marked as followers-only or direct and cannot be repeated", "no_retweet_hint": "Post is marked as followers-only or direct and cannot be repeated",
"repeated": "repeated", "repeated": "repeated",
@ -667,7 +668,8 @@
"hide_full_subject": "Hide full subject", "hide_full_subject": "Hide full subject",
"show_content": "Show content", "show_content": "Show content",
"hide_content": "Hide content", "hide_content": "Hide content",
"status_deleted": "This post was deleted" "status_deleted": "This post was deleted",
"nsfw": "NSFW"
}, },
"user_card": { "user_card": {
"approve": "Approve", "approve": "Approve",

View file

@ -39,8 +39,7 @@ const emptyNotifications = () => ({
minId: Number.POSITIVE_INFINITY, minId: Number.POSITIVE_INFINITY,
data: [], data: [],
idStore: {}, idStore: {},
loading: false, loading: false
error: false
}) })
export const defaultState = () => ({ export const defaultState = () => ({
@ -50,8 +49,6 @@ export const defaultState = () => ({
maxId: 0, maxId: 0,
notifications: emptyNotifications(), notifications: emptyNotifications(),
favorites: new Set(), favorites: new Set(),
error: false,
errorData: null,
timelines: { timelines: {
mentions: emptyTl(), mentions: emptyTl(),
public: emptyTl(), public: emptyTl(),
@ -462,18 +459,9 @@ export const mutations = {
const newStatus = state.allStatusesObject[id] const newStatus = state.allStatusesObject[id]
newStatus.nsfw = nsfw newStatus.nsfw = nsfw
}, },
setError (state, { value }) {
state.error = value
},
setErrorData (state, { value }) {
state.errorData = value
},
setNotificationsLoading (state, { value }) { setNotificationsLoading (state, { value }) {
state.notifications.loading = value state.notifications.loading = value
}, },
setNotificationsError (state, { value }) {
state.notifications.error = value
},
setNotificationsSilence (state, { value }) { setNotificationsSilence (state, { value }) {
state.notifications.desktopNotificationSilence = value state.notifications.desktopNotificationSilence = value
}, },
@ -588,18 +576,9 @@ const statuses = {
} }
commit('addNewNotifications', { dispatch, notifications, older, rootGetters, newNotificationSideEffects }) 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 }) { setNotificationsLoading ({ rootState, commit }, { value }) {
commit('setNotificationsLoading', { value }) commit('setNotificationsLoading', { value })
}, },
setNotificationsError ({ rootState, commit }, { value }) {
commit('setNotificationsError', { value })
},
setNotificationsSilence ({ rootState, commit }, { value }) { setNotificationsSilence ({ rootState, commit }, { value }) {
commit('setNotificationsSilence', { value }) commit('setNotificationsSilence', { value })
}, },

View file

@ -137,11 +137,11 @@ export const mutations = {
}, },
saveFriendIds (state, { id, friendIds }) { saveFriendIds (state, { id, friendIds }) {
const user = state.usersObject[id] const user = state.usersObject[id]
user.friendIds = uniq(concat(user.friendIds, friendIds)) user.friendIds = uniq(concat(user.friendIds || [], friendIds))
}, },
saveFollowerIds (state, { id, followerIds }) { saveFollowerIds (state, { id, followerIds }) {
const user = state.usersObject[id] 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 // Because frontend doesn't have a reason to keep these stuff in memory
// outside of viewing someones user profile. // outside of viewing someones user profile.
@ -202,7 +202,9 @@ export const mutations = {
}, },
setPinnedToUser (state, status) { setPinnedToUser (state, status) {
const user = state.usersObject[status.user.id] const user = state.usersObject[status.user.id]
user.pinnedStatusIds = user.pinnedStatusIds || []
const index = user.pinnedStatusIds.indexOf(status.id) const index = user.pinnedStatusIds.indexOf(status.id)
if (status.pinned && index === -1) { if (status.pinned && index === -1) {
user.pinnedStatusIds.push(status.id) user.pinnedStatusIds.push(status.id)
} else if (!status.pinned && index !== -1) { } else if (!status.pinned && index !== -1) {

View file

@ -560,7 +560,7 @@ const fetchTimeline = ({
}) })
.then((data) => data.json()) .then((data) => data.json())
.then((data) => { .then((data) => {
if (!data.error) { if (!data.errors) {
return { data: data.map(isNotifications ? parseNotification : parseStatus), pagination } return { data: data.map(isNotifications ? parseNotification : parseStatus), pagination }
} else { } else {
data.status = status data.status = status

View file

@ -2,6 +2,15 @@ import escape from 'escape-html'
import parseLinkHeader from 'parse-link-header' import parseLinkHeader from 'parse-link-header'
import { isStatusNotification } from '../notification_utils/notification_utils.js' 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) => { const qvitterStatusType = (status) => {
if (status.is_post_verb) { if (status.is_post_verb) {
return 'status' return 'status'
@ -53,7 +62,7 @@ export const parseUser = (data) => {
output.fields = data.fields output.fields = data.fields
output.fields_html = data.fields.map(field => { output.fields_html = data.fields.map(field => {
return { return {
name: addEmojis(field.name, data.emojis), name: addEmojis(escape(field.name), data.emojis),
value: addEmojis(field.value, data.emojis) value: addEmojis(field.value, data.emojis)
} }
}) })
@ -173,9 +182,6 @@ export const parseUser = (data) => {
output.locked = data.locked output.locked = data.locked
output.followers_count = data.followers_count output.followers_count = data.followers_count
output.statuses_count = data.statuses_count output.statuses_count = data.statuses_count
output.friendIds = []
output.followerIds = []
output.pinnedStatusIds = []
if (data.pleroma) { if (data.pleroma) {
output.follow_request_count = data.pleroma.follow_request_count output.follow_request_count = data.pleroma.follow_request_count

View 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

View file

@ -2,7 +2,6 @@ import apiService from '../api/api.service.js'
import { promiseInterval } from '../promise_interval/promise_interval.js' import { promiseInterval } from '../promise_interval/promise_interval.js'
const update = ({ store, notifications, older }) => { const update = ({ store, notifications, older }) => {
store.dispatch('setNotificationsError', { value: false })
store.dispatch('addNewNotifications', { notifications, older }) store.dispatch('addNewNotifications', { notifications, older })
} }
@ -47,11 +46,22 @@ const fetchAndUpdate = ({ store, credentials, older = false }) => {
const fetchNotifications = ({ store, args, older }) => { const fetchNotifications = ({ store, args, older }) => {
return apiService.fetchTimeline(args) 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 }) update({ store, notifications, older })
return notifications 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 }) => { const startFetching = ({ credentials, store }) => {

View file

@ -6,9 +6,6 @@ import { promiseInterval } from '../promise_interval/promise_interval.js'
const update = ({ store, statuses, timeline, showImmediately, userId, pagination }) => { const update = ({ store, statuses, timeline, showImmediately, userId, pagination }) => {
const ccTimeline = camelCase(timeline) const ccTimeline = camelCase(timeline)
store.dispatch('setError', { value: false })
store.dispatch('setErrorData', { value: null })
store.dispatch('addNewStatuses', { store.dispatch('addNewStatuses', {
timeline: ccTimeline, timeline: ccTimeline,
userId, userId,
@ -52,9 +49,8 @@ const fetchAndUpdate = ({
return apiService.fetchTimeline(args) return apiService.fetchTimeline(args)
.then(response => { .then(response => {
if (response.error) { if (response.errors) {
store.dispatch('setErrorData', { value: response }) throw new Error(`${response.status} ${response.statusText}`)
return
} }
const { data: statuses, pagination } = response const { data: statuses, pagination } = response
@ -63,7 +59,15 @@ const fetchAndUpdate = ({
} }
update({ store, statuses, timeline, showImmediately, userId, pagination }) update({ store, statuses, timeline, showImmediately, userId, pagination })
return { statuses, 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 }) => { const startFetching = ({ timeline = 'friends', credentials, store, userId = false, tag = false }) => {

View file

@ -8,8 +8,7 @@ const localVue = createLocalVue()
localVue.use(Vuex) localVue.use(Vuex)
const mutations = { const mutations = {
clearTimeline: () => {}, clearTimeline: () => {}
setError: () => {}
} }
const actions = { const actions = {