resolve merge conflicts and update fixes
This commit is contained in:
commit
7f05730e9b
25 changed files with 411 additions and 182 deletions
|
@ -20,6 +20,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
|
||||
### Changed
|
||||
- Display 'people voted' instead of 'votes' for multi-choice polls
|
||||
- Changed the "Timelines" link in side panel to toggle show all timeline options inside the panel
|
||||
- Renamed "Timeline" to "Home Timeline" to be more clear
|
||||
- Optimized chat to not get horrible performance after keeping the same chat open for a long time
|
||||
- When opening emoji picker or react picker, it automatically focuses the search field
|
||||
- Language picker now uses native language names
|
||||
|
@ -35,6 +37,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
### Fixed
|
||||
- Follows/Followers tabs on user profiles now display the content properly.
|
||||
- Handle punycode in screen names
|
||||
- Fixed local dev mode having non-functional websockets in some cases
|
||||
- Show notices for websocket events (errors, abnormal closures, reconnections)
|
||||
- Fix not being able to re-enable websocket until page refresh
|
||||
- Fix annoying issue where timeline might have few posts when streaming is enabled
|
||||
|
||||
### Changed
|
||||
- Don't filter own posts when they hit your wordfilter
|
||||
|
|
|
@ -3,6 +3,11 @@ const path = require('path')
|
|||
let settings = {}
|
||||
try {
|
||||
settings = require('./local.json')
|
||||
if (settings.target && settings.target.endsWith('/')) {
|
||||
// replacing trailing slash since it can conflict with some apis
|
||||
// and that's how actual BE reports its url
|
||||
settings.target = settings.target.replace(/\/$/, '')
|
||||
}
|
||||
console.log('Using local dev server settings (/config/local.json):')
|
||||
console.log(JSON.stringify(settings, null, 2))
|
||||
} catch (e) {
|
||||
|
|
|
@ -706,6 +706,15 @@ nav {
|
|||
color: var(--alertWarningPanelText, $fallback--text);
|
||||
}
|
||||
}
|
||||
|
||||
&.success {
|
||||
background-color: var(--alertSuccess, $fallback--alertWarning);
|
||||
color: var(--alertSuccessText, $fallback--text);
|
||||
|
||||
.panel-heading & {
|
||||
color: var(--alertSuccessPanelText, $fallback--text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.faint {
|
||||
|
|
|
@ -71,6 +71,14 @@
|
|||
}
|
||||
}
|
||||
|
||||
.global-success {
|
||||
background-color: var(--alertPopupSuccess, $fallback--cGreen);
|
||||
color: var(--alertPopupSuccessText, $fallback--text);
|
||||
.svg-inline--fa {
|
||||
color: var(--alertPopupSuccessText, $fallback--text);
|
||||
}
|
||||
}
|
||||
|
||||
.global-info {
|
||||
background-color: var(--alertPopupNeutral, $fallback--fg);
|
||||
color: var(--alertPopupNeutralText, $fallback--text);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { timelineNames } from '../timeline_menu/timeline_menu.js'
|
||||
import TimelineMenuContent from '../timeline_menu/timeline_menu_content.vue'
|
||||
import { mapState, mapGetters } from 'vuex'
|
||||
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
|
@ -7,10 +7,12 @@ import {
|
|||
faGlobe,
|
||||
faBookmark,
|
||||
faEnvelope,
|
||||
faHome,
|
||||
faChevronDown,
|
||||
faChevronUp,
|
||||
faComments,
|
||||
faBell,
|
||||
faInfoCircle
|
||||
faInfoCircle,
|
||||
faStream
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
library.add(
|
||||
|
@ -18,10 +20,12 @@ library.add(
|
|||
faGlobe,
|
||||
faBookmark,
|
||||
faEnvelope,
|
||||
faHome,
|
||||
faChevronDown,
|
||||
faChevronUp,
|
||||
faComments,
|
||||
faBell,
|
||||
faInfoCircle
|
||||
faInfoCircle,
|
||||
faStream
|
||||
)
|
||||
|
||||
const NavPanel = {
|
||||
|
@ -30,19 +34,23 @@ const NavPanel = {
|
|||
this.$store.dispatch('startFetchingFollowRequests')
|
||||
}
|
||||
},
|
||||
components: {
|
||||
TimelineMenuContent
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
showTimelines: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggleTimelines () {
|
||||
this.showTimelines = !this.showTimelines
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
onTimelineRoute () {
|
||||
return !!timelineNames()[this.$route.name]
|
||||
},
|
||||
compactNavPanel () {
|
||||
return this.$store.getters.mergedConfig.compactNavPanel || false
|
||||
},
|
||||
timelinesRoute () {
|
||||
if (this.$store.state.interface.lastTimeline) {
|
||||
return this.$store.state.interface.lastTimeline
|
||||
}
|
||||
return this.currentUser ? 'friends' : 'public-timeline'
|
||||
},
|
||||
...mapState({
|
||||
currentUser: state => state.users.currentUser,
|
||||
followRequestCount: state => state.api.followRequests.length,
|
||||
|
|
|
@ -6,19 +6,33 @@
|
|||
<div class="panel panel-default">
|
||||
<ul>
|
||||
<li v-if="currentUser || !privateMode">
|
||||
<router-link
|
||||
:to="{ name: timelinesRoute }"
|
||||
:class="onTimelineRoute && 'router-link-active'"
|
||||
<button
|
||||
class="button-unstyled menu-item"
|
||||
@click="toggleTimelines"
|
||||
>
|
||||
<FAIcon
|
||||
fixed-width
|
||||
class="fa-scale-110"
|
||||
icon="home"
|
||||
icon="stream"
|
||||
/>{{ $t("nav.timelines") }}
|
||||
</router-link>
|
||||
<FAIcon
|
||||
class="timelines-chevron"
|
||||
fixed-width
|
||||
:icon="showTimelines ? 'chevron-up' : 'chevron-down'"
|
||||
/>
|
||||
</button>
|
||||
<div
|
||||
v-show="showTimelines"
|
||||
class="timelines-background"
|
||||
>
|
||||
<TimelineMenuContent class="timelines" />
|
||||
</div>
|
||||
</li>
|
||||
<li v-if="currentUser">
|
||||
<router-link :to="{ name: 'interactions', params: { username: currentUser.screen_name } }">
|
||||
<router-link
|
||||
class="menu-item"
|
||||
:to="{ name: 'interactions', params: { username: currentUser.screen_name } }"
|
||||
>
|
||||
<FAIcon
|
||||
fixed-width
|
||||
class="fa-scale-110"
|
||||
|
@ -27,7 +41,10 @@
|
|||
</router-link>
|
||||
</li>
|
||||
<li v-if="currentUser && pleromaChatMessagesAvailable">
|
||||
<router-link :to="{ name: 'chats', params: { username: currentUser.screen_name } }">
|
||||
<router-link
|
||||
class="menu-item"
|
||||
:to="{ name: 'chats', params: { username: currentUser.screen_name } }"
|
||||
>
|
||||
<div
|
||||
v-if="unreadChatCount"
|
||||
class="badge badge-notification"
|
||||
|
@ -42,7 +59,10 @@
|
|||
</router-link>
|
||||
</li>
|
||||
<li v-if="currentUser && currentUser.locked">
|
||||
<router-link :to="{ name: 'friend-requests' }">
|
||||
<router-link
|
||||
class="menu-item"
|
||||
:to="{ name: 'friend-requests' }"
|
||||
>
|
||||
<FAIcon
|
||||
fixed-width
|
||||
class="fa-scale-110"
|
||||
|
@ -57,7 +77,10 @@
|
|||
</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link :to="{ name: 'about' }">
|
||||
<router-link
|
||||
class="menu-item"
|
||||
:to="{ name: 'about' }"
|
||||
>
|
||||
<FAIcon
|
||||
fixed-width
|
||||
class="fa-scale-110"
|
||||
|
@ -94,14 +117,14 @@
|
|||
border-color: var(--border, $fallback--border);
|
||||
padding: 0;
|
||||
|
||||
&:first-child a {
|
||||
&:first-child .menu-item {
|
||||
border-top-right-radius: $fallback--panelRadius;
|
||||
border-top-right-radius: var(--panelRadius, $fallback--panelRadius);
|
||||
border-top-left-radius: $fallback--panelRadius;
|
||||
border-top-left-radius: var(--panelRadius, $fallback--panelRadius);
|
||||
}
|
||||
|
||||
&:last-child a {
|
||||
&:last-child .menu-item {
|
||||
border-bottom-right-radius: $fallback--panelRadius;
|
||||
border-bottom-right-radius: var(--panelRadius, $fallback--panelRadius);
|
||||
border-bottom-left-radius: $fallback--panelRadius;
|
||||
|
@ -113,13 +136,15 @@
|
|||
border: none;
|
||||
}
|
||||
|
||||
a {
|
||||
.menu-item {
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
align-items: stretch;
|
||||
height: 3.5em;
|
||||
line-height: 3.5em;
|
||||
padding: 0 1em;
|
||||
width: 100%;
|
||||
color: $fallback--link;
|
||||
color: var(--link, $fallback--link);
|
||||
|
||||
&:hover {
|
||||
background-color: $fallback--lightBg;
|
||||
|
@ -149,6 +174,25 @@
|
|||
}
|
||||
}
|
||||
|
||||
.timelines-chevron {
|
||||
margin-left: 0.8em;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.timelines-background {
|
||||
padding: 0 0 0 0.6em;
|
||||
background-color: $fallback--lightBg;
|
||||
background-color: var(--selectedMenu, $fallback--lightBg);
|
||||
border-top: 1px solid;
|
||||
border-color: $fallback--border;
|
||||
border-color: var(--border, $fallback--border);
|
||||
}
|
||||
|
||||
.timelines {
|
||||
background-color: $fallback--bg;
|
||||
background-color: var(--bg, $fallback--bg);
|
||||
}
|
||||
|
||||
.fa-scale-110 {
|
||||
margin-right: 0.8em;
|
||||
}
|
||||
|
@ -161,7 +205,7 @@
|
|||
|
||||
&.compact {
|
||||
.panel {
|
||||
height: 40px;
|
||||
overflow: visible;
|
||||
|
||||
ul > li:hover > a:not(.router-link-active) > .button-icon {
|
||||
color: var(--selectedMenuText,#b9b9ba);
|
||||
|
@ -187,11 +231,21 @@
|
|||
}
|
||||
}
|
||||
|
||||
a {
|
||||
.timelines-chevron {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.timelines-background {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
a, button {
|
||||
font-size: 0;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
position: relative;
|
||||
padding-top: 7px;
|
||||
padding-bottom: 7px;
|
||||
}
|
||||
|
||||
.button-icon, svg.svg-inline--fa {
|
||||
|
|
|
@ -35,11 +35,6 @@ const Notifications = {
|
|||
seenToDisplayCount: DEFAULT_SEEN_TO_DISPLAY_COUNT
|
||||
}
|
||||
},
|
||||
created () {
|
||||
const store = this.$store
|
||||
const credentials = store.state.users.currentUser.credentials
|
||||
notificationsFetcher.fetchAndUpdate({ store, credentials })
|
||||
},
|
||||
computed: {
|
||||
mainClass () {
|
||||
return this.minimalMode ? '' : 'panel panel-default'
|
||||
|
|
|
@ -53,7 +53,7 @@
|
|||
type="submit"
|
||||
class="btn button-default btn-block"
|
||||
>
|
||||
{{ $t('general.submit') }}
|
||||
{{ $t('settings.save') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -272,7 +272,7 @@
|
|||
disabled
|
||||
class="btn button-default"
|
||||
>
|
||||
{{ $t('general.submit') }}
|
||||
{{ $t('post_status.post') }}
|
||||
</button>
|
||||
<!-- touchstart is used to keep the OSK at the same position after a message send -->
|
||||
<button
|
||||
|
@ -282,7 +282,7 @@
|
|||
@touchstart.stop.prevent="postStatus($event, newStatus)"
|
||||
@click.stop.prevent="postStatus($event, newStatus)"
|
||||
>
|
||||
{{ $t('general.submit') }}
|
||||
{{ $t('post_status.post') }}
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
|
|
|
@ -230,7 +230,7 @@
|
|||
type="submit"
|
||||
class="btn button-default"
|
||||
>
|
||||
{{ $t('general.submit') }}
|
||||
{{ $t('registration.register') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
class="btn button-default"
|
||||
@click="updateNotificationSettings"
|
||||
>
|
||||
{{ $t('general.submit') }}
|
||||
{{ $t('settings.save') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -153,7 +153,7 @@
|
|||
class="btn button-default"
|
||||
@click="updateProfile"
|
||||
>
|
||||
{{ $t('general.submit') }}
|
||||
{{ $t('settings.save') }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
|
@ -227,7 +227,7 @@
|
|||
class="btn button-default"
|
||||
@click="submitBanner(banner)"
|
||||
>
|
||||
{{ $t('general.submit') }}
|
||||
{{ $t('settings.save') }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
|
@ -266,7 +266,7 @@
|
|||
class="btn button-default"
|
||||
@click="submitBackground(background)"
|
||||
>
|
||||
{{ $t('general.submit') }}
|
||||
{{ $t('settings.save') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
class="btn button-default"
|
||||
@click="changeEmail"
|
||||
>
|
||||
{{ $t('general.submit') }}
|
||||
{{ $t('settings.save') }}
|
||||
</button>
|
||||
<p v-if="changedEmail">
|
||||
{{ $t('settings.changed_email') }}
|
||||
|
@ -60,7 +60,7 @@
|
|||
class="btn button-default"
|
||||
@click="changePassword"
|
||||
>
|
||||
{{ $t('general.submit') }}
|
||||
{{ $t('settings.save') }}
|
||||
</button>
|
||||
<p v-if="changedPassword">
|
||||
{{ $t('settings.changed_password') }}
|
||||
|
@ -133,7 +133,7 @@
|
|||
class="btn button-default"
|
||||
@click="confirmDelete"
|
||||
>
|
||||
{{ $t('general.submit') }}
|
||||
{{ $t('settings.save') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,29 +1,17 @@
|
|||
import Popover from '../popover/popover.vue'
|
||||
import { mapState } from 'vuex'
|
||||
import TimelineMenuContent from './timeline_menu_content.vue'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faUsers,
|
||||
faGlobe,
|
||||
faBookmark,
|
||||
faEnvelope,
|
||||
faHome,
|
||||
faChevronDown
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
library.add(
|
||||
faUsers,
|
||||
faGlobe,
|
||||
faBookmark,
|
||||
faEnvelope,
|
||||
faHome,
|
||||
faChevronDown
|
||||
)
|
||||
library.add(faChevronDown)
|
||||
|
||||
// Route -> i18n key mapping, exported and not in the computed
|
||||
// because nav panel benefits from the same information.
|
||||
export const timelineNames = () => {
|
||||
return {
|
||||
'friends': 'nav.timeline',
|
||||
'friends': 'nav.home_timeline',
|
||||
'bookmarks': 'nav.bookmarks',
|
||||
'dms': 'nav.dms',
|
||||
'public-timeline': 'nav.public_tl',
|
||||
|
@ -33,7 +21,8 @@ export const timelineNames = () => {
|
|||
|
||||
const TimelineMenu = {
|
||||
components: {
|
||||
Popover
|
||||
Popover,
|
||||
TimelineMenuContent
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
|
@ -41,9 +30,6 @@ const TimelineMenu = {
|
|||
}
|
||||
},
|
||||
created () {
|
||||
if (this.currentUser && this.currentUser.locked) {
|
||||
this.$store.dispatch('startFetchingFollowRequests')
|
||||
}
|
||||
if (timelineNames()[this.$route.name]) {
|
||||
this.$store.dispatch('setLastTimeline', this.$route.name)
|
||||
}
|
||||
|
@ -75,13 +61,6 @@ const TimelineMenu = {
|
|||
const i18nkey = timelineNames()[this.$route.name]
|
||||
return i18nkey ? this.$t(i18nkey) : route
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
currentUser: state => state.users.currentUser,
|
||||
privateMode: state => state.instance.private,
|
||||
federating: state => state.instance.federating
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,53 +13,7 @@
|
|||
slot="content"
|
||||
class="timeline-menu-popover panel panel-default"
|
||||
>
|
||||
<ul>
|
||||
<li v-if="currentUser">
|
||||
<router-link :to="{ name: 'friends' }">
|
||||
<FAIcon
|
||||
fixed-width
|
||||
class="fa-scale-110 fa-old-padding "
|
||||
icon="home"
|
||||
/>{{ $t("nav.timeline") }}
|
||||
</router-link>
|
||||
</li>
|
||||
<li v-if="currentUser">
|
||||
<router-link :to="{ name: 'bookmarks'}">
|
||||
<FAIcon
|
||||
fixed-width
|
||||
class="fa-scale-110 fa-old-padding "
|
||||
icon="bookmark"
|
||||
/>{{ $t("nav.bookmarks") }}
|
||||
</router-link>
|
||||
</li>
|
||||
<li v-if="currentUser">
|
||||
<router-link :to="{ name: 'dms', params: { username: currentUser.screen_name } }">
|
||||
<FAIcon
|
||||
fixed-width
|
||||
class="fa-scale-110 fa-old-padding "
|
||||
icon="envelope"
|
||||
/>{{ $t("nav.dms") }}
|
||||
</router-link>
|
||||
</li>
|
||||
<li v-if="currentUser || !privateMode">
|
||||
<router-link :to="{ name: 'public-timeline' }">
|
||||
<FAIcon
|
||||
fixed-width
|
||||
class="fa-scale-110 fa-old-padding "
|
||||
icon="users"
|
||||
/>{{ $t("nav.public_tl") }}
|
||||
</router-link>
|
||||
</li>
|
||||
<li v-if="federating && (currentUser || !privateMode)">
|
||||
<router-link :to="{ name: 'public-external-timeline' }">
|
||||
<FAIcon
|
||||
fixed-width
|
||||
class="fa-scale-110 fa-old-padding "
|
||||
icon="globe"
|
||||
/>{{ $t("nav.twkn") }}
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
<TimelineMenuContent />
|
||||
</div>
|
||||
<div
|
||||
slot="trigger"
|
||||
|
|
29
src/components/timeline_menu/timeline_menu_content.js
Normal file
29
src/components/timeline_menu/timeline_menu_content.js
Normal file
|
@ -0,0 +1,29 @@
|
|||
import { mapState } from 'vuex'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faUsers,
|
||||
faGlobe,
|
||||
faBookmark,
|
||||
faEnvelope,
|
||||
faHome
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
library.add(
|
||||
faUsers,
|
||||
faGlobe,
|
||||
faBookmark,
|
||||
faEnvelope,
|
||||
faHome
|
||||
)
|
||||
|
||||
const TimelineMenuContent = {
|
||||
computed: {
|
||||
...mapState({
|
||||
currentUser: state => state.users.currentUser,
|
||||
privateMode: state => state.instance.private,
|
||||
federating: state => state.instance.federating
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default TimelineMenuContent
|
66
src/components/timeline_menu/timeline_menu_content.vue
Normal file
66
src/components/timeline_menu/timeline_menu_content.vue
Normal file
|
@ -0,0 +1,66 @@
|
|||
<template>
|
||||
<ul>
|
||||
<li v-if="currentUser">
|
||||
<router-link
|
||||
class="menu-item"
|
||||
:to="{ name: 'friends' }"
|
||||
>
|
||||
<FAIcon
|
||||
fixed-width
|
||||
class="fa-scale-110 fa-old-padding "
|
||||
icon="home"
|
||||
/>{{ $t("nav.home_timeline") }}
|
||||
</router-link>
|
||||
</li>
|
||||
<li v-if="currentUser || !privateMode">
|
||||
<router-link
|
||||
class="menu-item"
|
||||
:to="{ name: 'public-timeline' }"
|
||||
>
|
||||
<FAIcon
|
||||
fixed-width
|
||||
class="fa-scale-110 fa-old-padding "
|
||||
icon="users"
|
||||
/>{{ $t("nav.public_tl") }}
|
||||
</router-link>
|
||||
</li>
|
||||
<li v-if="federating && (currentUser || !privateMode)">
|
||||
<router-link
|
||||
class="menu-item"
|
||||
:to="{ name: 'public-external-timeline' }"
|
||||
>
|
||||
<FAIcon
|
||||
fixed-width
|
||||
class="fa-scale-110 fa-old-padding "
|
||||
icon="globe"
|
||||
/>{{ $t("nav.twkn") }}
|
||||
</router-link>
|
||||
</li>
|
||||
<li v-if="currentUser">
|
||||
<router-link
|
||||
class="menu-item"
|
||||
:to="{ name: 'bookmarks'}"
|
||||
>
|
||||
<FAIcon
|
||||
fixed-width
|
||||
class="fa-scale-110 fa-old-padding "
|
||||
icon="bookmark"
|
||||
/>{{ $t("nav.bookmarks") }}
|
||||
</router-link>
|
||||
</li>
|
||||
<li v-if="currentUser">
|
||||
<router-link
|
||||
class="menu-item"
|
||||
:to="{ name: 'dms', params: { username: currentUser.screen_name } }"
|
||||
>
|
||||
<FAIcon
|
||||
fixed-width
|
||||
class="fa-scale-110 fa-old-padding "
|
||||
icon="envelope"
|
||||
/>{{ $t("nav.dms") }}
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<script src="./timeline_menu_content.js" ></script>
|
|
@ -3,27 +3,27 @@
|
|||
"mrf": {
|
||||
"federation": "Federation",
|
||||
"keyword": {
|
||||
"keyword_policies": "Keyword Policies",
|
||||
"keyword_policies": "Keyword policies",
|
||||
"ftl_removal": "Removal from \"The Whole Known Network\" Timeline",
|
||||
"reject": "Reject",
|
||||
"replace": "Replace",
|
||||
"is_replaced_by": "→"
|
||||
},
|
||||
"mrf_policies": "Enabled MRF Policies",
|
||||
"mrf_policies": "Enabled MRF policies",
|
||||
"mrf_policies_desc": "MRF policies manipulate the federation behaviour of the instance. The following policies are enabled:",
|
||||
"simple": {
|
||||
"simple_policies": "Instance-specific Policies",
|
||||
"simple_policies": "Instance-specific policies",
|
||||
"accept": "Accept",
|
||||
"accept_desc": "This instance only accepts messages from the following instances:",
|
||||
"reject": "Reject",
|
||||
"reject_desc": "This instance will not accept messages from the following instances:",
|
||||
"quarantine": "Quarantine",
|
||||
"quarantine_desc": "This instance will send only public posts to the following instances:",
|
||||
"ftl_removal": "Removal from \"The Whole Known Network\" Timeline",
|
||||
"ftl_removal_desc": "This instance removes these instances from \"The Whole Known Network\" timeline:",
|
||||
"ftl_removal": "Removal from \"Known Network\" Timeline",
|
||||
"ftl_removal_desc": "This instance removes these instances from \"Known Network\" timeline:",
|
||||
"media_removal": "Media Removal",
|
||||
"media_removal_desc": "This instance removes media from posts on the following instances:",
|
||||
"media_nsfw": "Media Force-set As Sensitive",
|
||||
"media_nsfw": "Media force-set as sensitive",
|
||||
"media_nsfw_desc": "This instance forces media to be set sensitive in posts on the following instances:"
|
||||
}
|
||||
},
|
||||
|
@ -118,12 +118,13 @@
|
|||
"about": "About",
|
||||
"administration": "Administration",
|
||||
"back": "Back",
|
||||
"friend_requests": "Follow Requests",
|
||||
"friend_requests": "Follow requests",
|
||||
"mentions": "Mentions",
|
||||
"interactions": "Interactions",
|
||||
"dms": "Direct Messages",
|
||||
"public_tl": "Public Timeline",
|
||||
"dms": "Direct messages",
|
||||
"public_tl": "Public timeline",
|
||||
"timeline": "Timeline",
|
||||
"home_timeline": "Home timeline",
|
||||
"twkn": "Known Network",
|
||||
"bookmarks": "Bookmarks",
|
||||
"user_search": "User Search",
|
||||
|
@ -148,8 +149,8 @@
|
|||
"reacted_with": "reacted with {0}"
|
||||
},
|
||||
"polls": {
|
||||
"add_poll": "Add Poll",
|
||||
"add_option": "Add Option",
|
||||
"add_poll": "Add poll",
|
||||
"add_option": "Add option",
|
||||
"option": "Option",
|
||||
"votes": "votes",
|
||||
"people_voted_count": "{count} person voted | {count} people voted",
|
||||
|
@ -178,7 +179,7 @@
|
|||
"storage_unavailable": "Pleroma could not access browser storage. Your login or your local settings won't be saved and you might encounter unexpected issues. Try enabling cookies."
|
||||
},
|
||||
"interactions": {
|
||||
"favs_repeats": "Repeats and Favorites",
|
||||
"favs_repeats": "Repeats and favorites",
|
||||
"follows": "New follows",
|
||||
"moves": "User migrates",
|
||||
"load_older": "Load older interactions"
|
||||
|
@ -200,6 +201,7 @@
|
|||
"direct_warning_to_all": "This post will be visible to all the mentioned users.",
|
||||
"direct_warning_to_first_only": "This post will only be visible to the mentioned users at the beginning of the message.",
|
||||
"posting": "Posting",
|
||||
"post": "Post",
|
||||
"preview": "Preview",
|
||||
"preview_empty": "Empty",
|
||||
"empty_status_error": "Can't post an empty status with no files",
|
||||
|
@ -210,10 +212,10 @@
|
|||
"unlisted": "This post will not be visible in Public Timeline and The Whole Known Network"
|
||||
},
|
||||
"scope": {
|
||||
"direct": "Direct - Post to mentioned users only",
|
||||
"private": "Followers-only - Post to followers only",
|
||||
"public": "Public - Post to public timelines",
|
||||
"unlisted": "Unlisted - Do not post to public timelines"
|
||||
"direct": "Direct - post to mentioned users only",
|
||||
"private": "Followers-only - post to followers only",
|
||||
"public": "Public - post to public timelines",
|
||||
"unlisted": "Unlisted - do not post to public timelines"
|
||||
}
|
||||
},
|
||||
"registration": {
|
||||
|
@ -230,6 +232,7 @@
|
|||
"bio_placeholder": "e.g.\nHi, I'm Lain.\nI’m an anime girl living in suburban Japan. You may know me from the Wired.",
|
||||
"reason": "Reason to register",
|
||||
"reason_placeholder": "This instance approves registrations manually.\nLet the administration know why you want to register.",
|
||||
"register": "Register",
|
||||
"validations": {
|
||||
"username_required": "cannot be left blank",
|
||||
"fullname_required": "cannot be left blank",
|
||||
|
@ -249,6 +252,7 @@
|
|||
},
|
||||
"settings": {
|
||||
"app_name": "App name",
|
||||
"save": "Save changes",
|
||||
"security": "Security",
|
||||
"setting_changed": "Setting is different from default",
|
||||
"enter_current_password_to_confirm": "Enter your current password to confirm your identity",
|
||||
|
@ -277,7 +281,7 @@
|
|||
"attachmentRadius": "Attachments",
|
||||
"attachments": "Attachments",
|
||||
"avatar": "Avatar",
|
||||
"avatarAltRadius": "Avatars (Notifications)",
|
||||
"avatarAltRadius": "Avatars (notifications)",
|
||||
"avatarRadius": "Avatars",
|
||||
"background": "Background",
|
||||
"bio": "Bio",
|
||||
|
@ -299,10 +303,10 @@
|
|||
"cGreen": "Green (Retweet)",
|
||||
"cOrange": "Orange (Favorite)",
|
||||
"cRed": "Red (Cancel)",
|
||||
"change_email": "Change Email",
|
||||
"change_email": "Change email",
|
||||
"change_email_error": "There was an issue changing your email.",
|
||||
"changed_email": "Email changed successfully!",
|
||||
"change_password": "Change Password",
|
||||
"change_password": "Change password",
|
||||
"change_password_error": "There was an issue changing your password.",
|
||||
"changed_password": "Password changed successfully!",
|
||||
"chatMessageRadius": "Chat message",
|
||||
|
@ -311,9 +315,9 @@
|
|||
"confirm_new_password": "Confirm new password",
|
||||
"current_password": "Current password",
|
||||
"mutes_and_blocks": "Mutes and Blocks",
|
||||
"data_import_export_tab": "Data Import / Export",
|
||||
"data_import_export_tab": "Data import / export",
|
||||
"default_vis": "Default visibility scope",
|
||||
"delete_account": "Delete Account",
|
||||
"delete_account": "Delete account",
|
||||
"delete_account_description": "Permanently delete your data and deactivate your account.",
|
||||
"delete_account_error": "There was an issue deleting your account. If this persists please contact your instance administrator.",
|
||||
"delete_account_instructions": "Type your password in the input below to confirm account deletion.",
|
||||
|
@ -369,14 +373,14 @@
|
|||
"play_videos_in_modal": "Play videos in a popup frame",
|
||||
"profile_fields": {
|
||||
"label": "Profile metadata",
|
||||
"add_field": "Add Field",
|
||||
"add_field": "Add field",
|
||||
"name": "Label",
|
||||
"value": "Content"
|
||||
},
|
||||
"use_contain_fit": "Don't crop the attachment in thumbnails",
|
||||
"name": "Name",
|
||||
"name_bio": "Name & Bio",
|
||||
"new_email": "New Email",
|
||||
"name_bio": "Name & bio",
|
||||
"new_email": "New email",
|
||||
"new_password": "New password",
|
||||
"notification_visibility": "Types of notifications to show",
|
||||
"notification_visibility_follows": "Follows",
|
||||
|
@ -392,19 +396,19 @@
|
|||
"hide_followers_description": "Don't show who's following me",
|
||||
"hide_follows_count_description": "Don't show follow count",
|
||||
"hide_followers_count_description": "Don't show follower count",
|
||||
"show_admin_badge": "Show Admin badge in my profile",
|
||||
"show_moderator_badge": "Show Moderator badge in my profile",
|
||||
"show_admin_badge": "Show \"Admin\" badge in my profile",
|
||||
"show_moderator_badge": "Show \"Moderator\" badge in my profile",
|
||||
"nsfw_clickthrough": "Enable clickthrough attachment and link preview image hiding for NSFW statuses",
|
||||
"oauth_tokens": "OAuth tokens",
|
||||
"token": "Token",
|
||||
"refresh_token": "Refresh Token",
|
||||
"valid_until": "Valid Until",
|
||||
"refresh_token": "Refresh token",
|
||||
"valid_until": "Valid until",
|
||||
"revoke_token": "Revoke",
|
||||
"panelRadius": "Panels",
|
||||
"pause_on_unfocused": "Pause streaming when tab is not focused",
|
||||
"presets": "Presets",
|
||||
"profile_background": "Profile Background",
|
||||
"profile_banner": "Profile Banner",
|
||||
"profile_background": "Profile background",
|
||||
"profile_banner": "Profile banner",
|
||||
"profile_tab": "Profile",
|
||||
"radii_help": "Set up interface edge rounding (in pixels)",
|
||||
"replies_in_timeline": "Replies in timeline",
|
||||
|
@ -616,8 +620,8 @@
|
|||
},
|
||||
"version": {
|
||||
"title": "Version",
|
||||
"backend_version": "Backend Version",
|
||||
"frontend_version": "Frontend Version"
|
||||
"backend_version": "Backend version",
|
||||
"frontend_version": "Frontend version"
|
||||
}
|
||||
},
|
||||
"time": {
|
||||
|
@ -665,7 +669,9 @@
|
|||
"reload": "Reload",
|
||||
"up_to_date": "Up-to-date",
|
||||
"no_more_statuses": "No more statuses",
|
||||
"no_statuses": "No statuses"
|
||||
"no_statuses": "No statuses",
|
||||
"socket_reconnected": "Realtime connection established",
|
||||
"socket_broke": "Realtime connection lost: CloseEvent code {0}"
|
||||
},
|
||||
"status": {
|
||||
"favorites": "Favorites",
|
||||
|
@ -758,7 +764,7 @@
|
|||
}
|
||||
},
|
||||
"user_profile": {
|
||||
"timeline_title": "User Timeline",
|
||||
"timeline_title": "User timeline",
|
||||
"profile_does_not_exist": "Sorry, this profile does not exist.",
|
||||
"profile_loading_error": "Sorry, there was an error loading this profile."
|
||||
},
|
||||
|
@ -776,7 +782,7 @@
|
|||
"who_to_follow": "Who to follow"
|
||||
},
|
||||
"tool_tip": {
|
||||
"media_upload": "Upload Media",
|
||||
"media_upload": "Upload media",
|
||||
"repeat": "Repeat",
|
||||
"reply": "Reply",
|
||||
"favorite": "Favorite",
|
||||
|
|
|
@ -3,8 +3,11 @@ import { WSConnectionStatus } from '../services/api/api.service.js'
|
|||
import { maybeShowChatNotification } from '../services/chat_utils/chat_utils.js'
|
||||
import { Socket } from 'phoenix'
|
||||
|
||||
const retryTimeout = (multiplier) => 1000 * multiplier
|
||||
|
||||
const api = {
|
||||
state: {
|
||||
retryMultiplier: 1,
|
||||
backendInteractor: backendInteractorService(),
|
||||
fetchers: {},
|
||||
socket: null,
|
||||
|
@ -34,18 +37,43 @@ const api = {
|
|||
},
|
||||
setMastoUserSocketStatus (state, value) {
|
||||
state.mastoUserSocketStatus = value
|
||||
},
|
||||
incrementRetryMultiplier (state) {
|
||||
state.retryMultiplier = Math.max(++state.retryMultiplier, 3)
|
||||
},
|
||||
resetRetryMultiplier (state) {
|
||||
state.retryMultiplier = 1
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
// Global MastoAPI socket control, in future should disable ALL sockets/(re)start relevant sockets
|
||||
enableMastoSockets (store) {
|
||||
const { state, dispatch } = store
|
||||
if (state.mastoUserSocket) return
|
||||
/**
|
||||
* Global MastoAPI socket control, in future should disable ALL sockets/(re)start relevant sockets
|
||||
*
|
||||
* @param {Boolean} [initial] - whether this enabling happened at boot time or not
|
||||
*/
|
||||
enableMastoSockets (store, initial) {
|
||||
const { state, dispatch, commit } = store
|
||||
// Do not initialize unless nonexistent or closed
|
||||
if (
|
||||
state.mastoUserSocket &&
|
||||
![
|
||||
WebSocket.CLOSED,
|
||||
WebSocket.CLOSING
|
||||
].includes(state.mastoUserSocket.getState())
|
||||
) {
|
||||
return
|
||||
}
|
||||
if (initial) {
|
||||
commit('setMastoUserSocketStatus', WSConnectionStatus.STARTING_INITIAL)
|
||||
} else {
|
||||
commit('setMastoUserSocketStatus', WSConnectionStatus.STARTING)
|
||||
}
|
||||
return dispatch('startMastoUserSocket')
|
||||
},
|
||||
disableMastoSockets (store) {
|
||||
const { state, dispatch } = store
|
||||
const { state, dispatch, commit } = store
|
||||
if (!state.mastoUserSocket) return
|
||||
commit('setMastoUserSocketStatus', WSConnectionStatus.DISABLED)
|
||||
return dispatch('stopMastoUserSocket')
|
||||
},
|
||||
|
||||
|
@ -91,11 +119,29 @@ const api = {
|
|||
}
|
||||
)
|
||||
state.mastoUserSocket.addEventListener('open', () => {
|
||||
// Do not show notification when we just opened up the page
|
||||
if (state.mastoUserSocketStatus !== WSConnectionStatus.STARTING_INITIAL) {
|
||||
dispatch('pushGlobalNotice', {
|
||||
level: 'success',
|
||||
messageKey: 'timeline.socket_reconnected',
|
||||
timeout: 5000
|
||||
})
|
||||
}
|
||||
// Stop polling if we were errored or disabled
|
||||
if (new Set([
|
||||
WSConnectionStatus.ERROR,
|
||||
WSConnectionStatus.DISABLED
|
||||
]).has(state.mastoUserSocketStatus)) {
|
||||
dispatch('stopFetchingTimeline', { timeline: 'friends' })
|
||||
dispatch('stopFetchingNotifications')
|
||||
dispatch('stopFetchingChats')
|
||||
}
|
||||
commit('resetRetryMultiplier')
|
||||
commit('setMastoUserSocketStatus', WSConnectionStatus.JOINED)
|
||||
})
|
||||
state.mastoUserSocket.addEventListener('error', ({ detail: error }) => {
|
||||
console.error('Error in MastoAPI websocket:', error)
|
||||
commit('setMastoUserSocketStatus', WSConnectionStatus.ERROR)
|
||||
// TODO is this needed?
|
||||
dispatch('clearOpenedChats')
|
||||
})
|
||||
state.mastoUserSocket.addEventListener('close', ({ detail: closeEvent }) => {
|
||||
|
@ -106,14 +152,26 @@ const api = {
|
|||
const { code } = closeEvent
|
||||
if (ignoreCodes.has(code)) {
|
||||
console.debug(`Not restarting socket becasue of closure code ${code} is in ignore list`)
|
||||
commit('setMastoUserSocketStatus', WSConnectionStatus.CLOSED)
|
||||
} else {
|
||||
console.warn(`MastoAPI websocket disconnected, restarting. CloseEvent code: ${code}`)
|
||||
dispatch('startFetchingTimeline', { timeline: 'friends' })
|
||||
dispatch('startFetchingNotifications')
|
||||
dispatch('startFetchingChats')
|
||||
dispatch('restartMastoUserSocket')
|
||||
setTimeout(() => {
|
||||
dispatch('startMastoUserSocket')
|
||||
}, retryTimeout(state.retryMultiplier))
|
||||
commit('incrementRetryMultiplier')
|
||||
if (state.mastoUserSocketStatus !== WSConnectionStatus.ERROR) {
|
||||
dispatch('startFetchingTimeline', { timeline: 'friends' })
|
||||
dispatch('startFetchingNotifications')
|
||||
dispatch('startFetchingChats')
|
||||
dispatch('pushGlobalNotice', {
|
||||
level: 'error',
|
||||
messageKey: 'timeline.socket_broke',
|
||||
messageArgs: [code],
|
||||
timeout: 5000
|
||||
})
|
||||
}
|
||||
commit('setMastoUserSocketStatus', WSConnectionStatus.ERROR)
|
||||
}
|
||||
commit('setMastoUserSocketStatus', WSConnectionStatus.CLOSED)
|
||||
dispatch('clearOpenedChats')
|
||||
})
|
||||
resolve()
|
||||
|
@ -122,15 +180,6 @@ const api = {
|
|||
}
|
||||
})
|
||||
},
|
||||
restartMastoUserSocket ({ dispatch }) {
|
||||
// This basically starts MastoAPI user socket and stops conventional
|
||||
// fetchers when connection reestablished
|
||||
return dispatch('startMastoUserSocket').then(() => {
|
||||
dispatch('stopFetchingTimeline', { timeline: 'friends' })
|
||||
dispatch('stopFetchingNotifications')
|
||||
dispatch('stopFetchingChats')
|
||||
})
|
||||
},
|
||||
stopMastoUserSocket ({ state, dispatch }) {
|
||||
dispatch('startFetchingTimeline', { timeline: 'friends' })
|
||||
dispatch('startFetchingNotifications')
|
||||
|
@ -156,6 +205,13 @@ const api = {
|
|||
if (!fetcher) return
|
||||
store.commit('removeFetcher', { fetcherName: timeline, fetcher })
|
||||
},
|
||||
fetchTimeline (store, timeline, { ...rest }) {
|
||||
store.state.backendInteractor.fetchTimeline({
|
||||
store,
|
||||
timeline,
|
||||
...rest
|
||||
})
|
||||
},
|
||||
|
||||
// Notifications
|
||||
startFetchingNotifications (store) {
|
||||
|
@ -168,6 +224,12 @@ const api = {
|
|||
if (!fetcher) return
|
||||
store.commit('removeFetcher', { fetcherName: 'notifications', fetcher })
|
||||
},
|
||||
fetchNotifications (store, { ...rest }) {
|
||||
store.state.backendInteractor.fetchNotifications({
|
||||
store,
|
||||
...rest
|
||||
})
|
||||
},
|
||||
|
||||
// Follow requests
|
||||
startFetchingFollowRequests (store) {
|
||||
|
|
|
@ -553,9 +553,10 @@ const users = {
|
|||
}
|
||||
|
||||
if (store.getters.mergedConfig.useStreamingApi) {
|
||||
store.dispatch('enableMastoSockets').catch((error) => {
|
||||
store.dispatch('fetchTimeline', 'friends', { since: null })
|
||||
store.dispatch('fetchNotifications', { since: null })
|
||||
store.dispatch('enableMastoSockets', true).catch((error) => {
|
||||
console.error('Failed initializing MastoAPI Streaming socket', error)
|
||||
startPolling()
|
||||
}).then(() => {
|
||||
store.dispatch('fetchChats', { latest: true })
|
||||
setTimeout(() => store.dispatch('setNotificationsSilence', false), 10000)
|
||||
|
|
|
@ -1152,6 +1152,7 @@ export const ProcessedWS = ({
|
|||
|
||||
// 1000 = Normal Closure
|
||||
eventTarget.close = () => { socket.close(1000, 'Shutting down socket') }
|
||||
eventTarget.getState = () => socket.readyState
|
||||
|
||||
return eventTarget
|
||||
}
|
||||
|
@ -1183,7 +1184,10 @@ export const handleMastoWS = (wsEvent) => {
|
|||
export const WSConnectionStatus = Object.freeze({
|
||||
'JOINED': 1,
|
||||
'CLOSED': 2,
|
||||
'ERROR': 3
|
||||
'ERROR': 3,
|
||||
'DISABLED': 4,
|
||||
'STARTING': 5,
|
||||
'STARTING_INITIAL': 6
|
||||
})
|
||||
|
||||
const chats = ({ credentials }) => {
|
||||
|
|
|
@ -1,17 +1,25 @@
|
|||
import apiService, { getMastodonSocketURI, ProcessedWS } from '../api/api.service.js'
|
||||
import timelineFetcherService from '../timeline_fetcher/timeline_fetcher.service.js'
|
||||
import timelineFetcher from '../timeline_fetcher/timeline_fetcher.service.js'
|
||||
import notificationsFetcher from '../notifications_fetcher/notifications_fetcher.service.js'
|
||||
import followRequestFetcher from '../../services/follow_request_fetcher/follow_request_fetcher.service'
|
||||
|
||||
const backendInteractorService = credentials => ({
|
||||
startFetchingTimeline ({ timeline, store, userId = false, tag }) {
|
||||
return timelineFetcherService.startFetching({ timeline, store, credentials, userId, tag })
|
||||
return timelineFetcher.startFetching({ timeline, store, credentials, userId, tag })
|
||||
},
|
||||
|
||||
fetchTimeline (args) {
|
||||
return timelineFetcher.fetchAndUpdate({ ...args, credentials })
|
||||
},
|
||||
|
||||
startFetchingNotifications ({ store }) {
|
||||
return notificationsFetcher.startFetching({ store, credentials })
|
||||
},
|
||||
|
||||
fetchNotifications (args) {
|
||||
return notificationsFetcher.fetchAndUpdate({ ...args, credentials })
|
||||
},
|
||||
|
||||
startFetchingFollowRequests ({ store }) {
|
||||
return followRequestFetcher.startFetching({ store, credentials })
|
||||
},
|
||||
|
|
|
@ -5,7 +5,7 @@ const update = ({ store, notifications, older }) => {
|
|||
store.dispatch('addNewNotifications', { notifications, older })
|
||||
}
|
||||
|
||||
const fetchAndUpdate = ({ store, credentials, older = false }) => {
|
||||
const fetchAndUpdate = ({ store, credentials, older = false, since }) => {
|
||||
const args = { credentials }
|
||||
const { getters } = store
|
||||
const rootState = store.rootState || store.state
|
||||
|
@ -22,8 +22,10 @@ const fetchAndUpdate = ({ store, credentials, older = false }) => {
|
|||
return fetchNotifications({ store, args, older })
|
||||
} else {
|
||||
// fetch new notifications
|
||||
if (timelineData.maxId !== Number.POSITIVE_INFINITY) {
|
||||
if (since === undefined && timelineData.maxId !== Number.POSITIVE_INFINITY) {
|
||||
args['since'] = timelineData.maxId
|
||||
} else if (since !== null) {
|
||||
args['since'] = since
|
||||
}
|
||||
const result = fetchNotifications({ store, args, older })
|
||||
|
||||
|
|
|
@ -616,6 +616,23 @@ export const SLOT_INHERITANCE = {
|
|||
textColor: true
|
||||
},
|
||||
|
||||
alertSuccess: {
|
||||
depends: ['cGreen'],
|
||||
opacity: 'alert'
|
||||
},
|
||||
alertSuccessText: {
|
||||
depends: ['text'],
|
||||
layer: 'alert',
|
||||
variant: 'alertSuccess',
|
||||
textColor: true
|
||||
},
|
||||
alertSuccessPanelText: {
|
||||
depends: ['panelText'],
|
||||
layer: 'alertPanel',
|
||||
variant: 'alertSuccess',
|
||||
textColor: true
|
||||
},
|
||||
|
||||
alertNeutral: {
|
||||
depends: ['text'],
|
||||
opacity: 'alert'
|
||||
|
@ -656,6 +673,17 @@ export const SLOT_INHERITANCE = {
|
|||
textColor: true
|
||||
},
|
||||
|
||||
alertPopupSuccess: {
|
||||
depends: ['alertSuccess'],
|
||||
opacity: 'alertPopup'
|
||||
},
|
||||
alertPopupSuccessText: {
|
||||
depends: ['alertSuccessText'],
|
||||
layer: 'popover',
|
||||
variant: 'alertPopupSuccess',
|
||||
textColor: true
|
||||
},
|
||||
|
||||
alertPopupNeutral: {
|
||||
depends: ['alertNeutral'],
|
||||
opacity: 'alertPopup'
|
||||
|
|
|
@ -23,7 +23,8 @@ const fetchAndUpdate = ({
|
|||
showImmediately = false,
|
||||
userId = false,
|
||||
tag = false,
|
||||
until
|
||||
until,
|
||||
since
|
||||
}) => {
|
||||
const args = { timeline, credentials }
|
||||
const rootState = store.rootState || store.state
|
||||
|
@ -35,7 +36,11 @@ const fetchAndUpdate = ({
|
|||
if (older) {
|
||||
args['until'] = until || timelineData.minId
|
||||
} else {
|
||||
args['since'] = timelineData.maxId
|
||||
if (since === undefined) {
|
||||
args['since'] = timelineData.maxId
|
||||
} else if (since !== null) {
|
||||
args['since'] = since
|
||||
}
|
||||
}
|
||||
|
||||
args['userId'] = userId
|
||||
|
|
Loading…
Reference in a new issue