forked from AkkomaGang/akkoma-fe
Merge branch 'develop' into fedi-absturztau-be
This commit is contained in:
commit
a6822d26bb
26 changed files with 193 additions and 118 deletions
32
CHANGELOG.md
32
CHANGELOG.md
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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 })
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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', '')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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 })
|
||||||
},
|
},
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
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'
|
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 }) => {
|
||||||
|
|
|
@ -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 }) => {
|
||||||
|
|
|
@ -8,8 +8,7 @@ const localVue = createLocalVue()
|
||||||
localVue.use(Vuex)
|
localVue.use(Vuex)
|
||||||
|
|
||||||
const mutations = {
|
const mutations = {
|
||||||
clearTimeline: () => {},
|
clearTimeline: () => {}
|
||||||
setError: () => {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
|
|
Loading…
Reference in a new issue