aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorHJ <30-hj@users.noreply.git.pleroma.social>2020-01-03 09:05:48 +0000
committerHJ <30-hj@users.noreply.git.pleroma.social>2020-01-03 09:05:48 +0000
commit215662afdee3935f66e5fbd73260e2039f5216de (patch)
tree817b578e53b884c4b5facd63c166b522f59a8978 /src
parentc3e7806acbd32483eab497a347f947158ab8febf (diff)
parent1a82a00a2b45041c35b72ab16ee34c120341b652 (diff)
Merge branch 'develop' into 'fix-move-type-notification'
# Conflicts: # static/fontello.json
Diffstat (limited to 'src')
-rw-r--r--src/App.js4
-rw-r--r--src/App.scss13
-rw-r--r--src/App.vue2
-rw-r--r--src/boot/after_store.js10
-rw-r--r--src/components/follow_request_card/follow_request_card.js4
-rw-r--r--src/components/login_form/login_form.js2
-rw-r--r--src/components/mfa_form/recovery_form.js11
-rw-r--r--src/components/mfa_form/totp_form.js11
-rw-r--r--src/components/mobile_nav/mobile_nav.js1
-rw-r--r--src/components/mobile_nav/mobile_nav.vue1
-rw-r--r--src/components/moderation_tools/moderation_tools.js21
-rw-r--r--src/components/nav_panel/nav_panel.js20
-rw-r--r--src/components/nav_panel/nav_panel.vue22
-rw-r--r--src/components/notifications/notifications.js5
-rw-r--r--src/components/public_and_external_timeline/public_and_external_timeline.js2
-rw-r--r--src/components/public_timeline/public_timeline.js2
-rw-r--r--src/components/registration/registration.vue2
-rw-r--r--src/components/settings/settings.js18
-rw-r--r--src/components/settings/settings.vue9
-rw-r--r--src/components/side_drawer/side_drawer.js9
-rw-r--r--src/components/side_drawer/side_drawer.vue49
-rw-r--r--src/components/sticker_picker/sticker_picker.vue34
-rw-r--r--src/components/tag_timeline/tag_timeline.js2
-rw-r--r--src/components/timeline/timeline.js7
-rw-r--r--src/components/timeline/timeline.vue32
-rw-r--r--src/components/user_profile/user_profile.js6
-rw-r--r--src/components/user_reporting_modal/user_reporting_modal.js2
-rw-r--r--src/components/user_settings/mfa.js2
-rw-r--r--src/components/user_settings/user_settings.js8
-rw-r--r--src/hocs/with_load_more/with_load_more.js4
-rw-r--r--src/hocs/with_subscription/with_subscription.js4
-rw-r--r--src/i18n/en.json2
-rw-r--r--src/i18n/ja_easy.json43
-rw-r--r--src/i18n/ru.json2
-rw-r--r--src/modules/api.js140
-rw-r--r--src/modules/auth_flow.js8
-rw-r--r--src/modules/config.js1
-rw-r--r--src/modules/instance.js1
-rw-r--r--src/modules/oauth_tokens.js2
-rw-r--r--src/modules/polls.js4
-rw-r--r--src/modules/statuses.js33
-rw-r--r--src/modules/users.js52
-rw-r--r--src/services/api/api.service.js154
-rw-r--r--src/services/backend_interactor_service/backend_interactor_service.js247
-rw-r--r--src/services/follow_manipulate/follow_manipulate.js2
-rw-r--r--src/services/new_api/mfa.js12
-rw-r--r--src/services/new_api/oauth.js4
-rw-r--r--src/services/timeline_fetcher/timeline_fetcher.service.js5
48 files changed, 606 insertions, 425 deletions
diff --git a/src/App.js b/src/App.js
index 04a40e30..61b5eec1 100644
--- a/src/App.js
+++ b/src/App.js
@@ -90,6 +90,7 @@ export default {
},
sitename () { return this.$store.state.instance.name },
chat () { return this.$store.state.chat.channel.state === 'joined' },
+ hideSitename () { return this.$store.state.instance.hideSitename },
suggestionsEnabled () { return this.$store.state.instance.suggestionsEnabled },
showInstanceSpecificPanel () {
return this.$store.state.instance.showInstanceSpecificPanel &&
@@ -97,7 +98,8 @@ export default {
this.$store.state.instance.instanceSpecificPanelContent
},
showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel },
- isMobileLayout () { return this.$store.state.interface.mobileLayout }
+ isMobileLayout () { return this.$store.state.interface.mobileLayout },
+ privateMode () { return this.$store.state.instance.private }
},
methods: {
scrollToTop () {
diff --git a/src/App.scss b/src/App.scss
index 925913f2..754ca62e 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -870,3 +870,16 @@ nav {
transform: rotate(359deg);
}
}
+
+.new-status-notification {
+ position:relative;
+ margin-top: -1px;
+ font-size: 1.1em;
+ border-width: 1px 0 0 0;
+ border-style: solid;
+ border-color: var(--border, $fallback--border);
+ padding: 10px;
+ z-index: 1;
+ background-color: $fallback--fg;
+ background-color: var(--panel, $fallback--fg);
+}
diff --git a/src/App.vue b/src/App.vue
index dbe842ec..1b1c2648 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -31,6 +31,7 @@
</div>
<div class="item">
<router-link
+ v-if="!hideSitename"
class="site-name"
:to="{ name: 'root' }"
active-class="home"
@@ -40,6 +41,7 @@
</div>
<div class="item right">
<search-bar
+ v-if="currentUser || !privateMode"
class="nav-icon mobile-hidden"
@toggled="onSearchBarToggled"
@click.stop.native
diff --git a/src/boot/after_store.js b/src/boot/after_store.js
index 226b67d8..228a0497 100644
--- a/src/boot/after_store.js
+++ b/src/boot/after_store.js
@@ -108,6 +108,7 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => {
copyInstanceOption('alwaysShowSubjectInput')
copyInstanceOption('noAttachmentLinks')
copyInstanceOption('showFeaturesPanel')
+ copyInstanceOption('hideSitename')
return store.dispatch('setTheme', config['theme'])
}
@@ -218,12 +219,21 @@ const getNodeInfo = async ({ store }) => {
store.dispatch('setInstanceOption', { name: 'backendVersion', value: software.version })
store.dispatch('setInstanceOption', { name: 'pleromaBackend', value: software.name === 'pleroma' })
+ const priv = metadata.private
+ store.dispatch('setInstanceOption', { name: 'private', value: priv })
+
const frontendVersion = window.___pleromafe_commit_hash
store.dispatch('setInstanceOption', { name: 'frontendVersion', value: frontendVersion })
store.dispatch('setInstanceOption', { name: 'tagPolicyAvailable', value: metadata.federation.mrf_policies.includes('TagPolicy') })
const federation = metadata.federation
store.dispatch('setInstanceOption', { name: 'federationPolicy', value: federation })
+ store.dispatch('setInstanceOption', {
+ name: 'federating',
+ value: typeof federation.enabled === 'undefined'
+ ? true
+ : federation.enabled
+ })
const accounts = metadata.staffAccounts
await resolveStaffAccounts({ store, accounts })
diff --git a/src/components/follow_request_card/follow_request_card.js b/src/components/follow_request_card/follow_request_card.js
index 1a00a1c1..a8931787 100644
--- a/src/components/follow_request_card/follow_request_card.js
+++ b/src/components/follow_request_card/follow_request_card.js
@@ -7,11 +7,11 @@ const FollowRequestCard = {
},
methods: {
approveUser () {
- this.$store.state.api.backendInteractor.approveUser(this.user.id)
+ this.$store.state.api.backendInteractor.approveUser({ id: this.user.id })
this.$store.dispatch('removeFollowRequest', this.user)
},
denyUser () {
- this.$store.state.api.backendInteractor.denyUser(this.user.id)
+ this.$store.state.api.backendInteractor.denyUser({ id: this.user.id })
this.$store.dispatch('removeFollowRequest', this.user)
}
}
diff --git a/src/components/login_form/login_form.js b/src/components/login_form/login_form.js
index 0b574a04..0d8f1da6 100644
--- a/src/components/login_form/login_form.js
+++ b/src/components/login_form/login_form.js
@@ -58,7 +58,7 @@ const LoginForm = {
).then((result) => {
if (result.error) {
if (result.error === 'mfa_required') {
- this.requireMFA({ app: app, settings: result })
+ this.requireMFA({ settings: result })
} else if (result.identifier === 'password_reset_required') {
this.$router.push({ name: 'password-reset', params: { passwordResetRequested: true } })
} else {
diff --git a/src/components/mfa_form/recovery_form.js b/src/components/mfa_form/recovery_form.js
index 7a3cc22d..b25c65dd 100644
--- a/src/components/mfa_form/recovery_form.js
+++ b/src/components/mfa_form/recovery_form.js
@@ -8,18 +8,23 @@ export default {
}),
computed: {
...mapGetters({
- authApp: 'authFlow/app',
authSettings: 'authFlow/settings'
}),
- ...mapState({ instance: 'instance' })
+ ...mapState({
+ instance: 'instance',
+ oauth: 'oauth'
+ })
},
methods: {
...mapMutations('authFlow', ['requireTOTP', 'abortMFA']),
...mapActions({ login: 'authFlow/login' }),
clearError () { this.error = false },
submit () {
+ const { clientId, clientSecret } = this.oauth
+
const data = {
- app: this.authApp,
+ clientId,
+ clientSecret,
instance: this.instance.server,
mfaToken: this.authSettings.mfa_token,
code: this.code
diff --git a/src/components/mfa_form/totp_form.js b/src/components/mfa_form/totp_form.js
index 778bf8dc..b774f2d0 100644
--- a/src/components/mfa_form/totp_form.js
+++ b/src/components/mfa_form/totp_form.js
@@ -7,18 +7,23 @@ export default {
}),
computed: {
...mapGetters({
- authApp: 'authFlow/app',
authSettings: 'authFlow/settings'
}),
- ...mapState({ instance: 'instance' })
+ ...mapState({
+ instance: 'instance',
+ oauth: 'oauth'
+ })
},
methods: {
...mapMutations('authFlow', ['requireRecovery', 'abortMFA']),
...mapActions({ login: 'authFlow/login' }),
clearError () { this.error = false },
submit () {
+ const { clientId, clientSecret } = this.oauth
+
const data = {
- app: this.authApp,
+ clientId,
+ clientSecret,
instance: this.instance.server,
mfaToken: this.authSettings.mfa_token,
code: this.code
diff --git a/src/components/mobile_nav/mobile_nav.js b/src/components/mobile_nav/mobile_nav.js
index 5a90c31f..c1166a0c 100644
--- a/src/components/mobile_nav/mobile_nav.js
+++ b/src/components/mobile_nav/mobile_nav.js
@@ -29,6 +29,7 @@ const MobileNav = {
unseenNotificationsCount () {
return this.unseenNotifications.length
},
+ hideSitename () { return this.$store.state.instance.hideSitename },
sitename () { return this.$store.state.instance.name }
},
methods: {
diff --git a/src/components/mobile_nav/mobile_nav.vue b/src/components/mobile_nav/mobile_nav.vue
index d1c24e56..51f1d636 100644
--- a/src/components/mobile_nav/mobile_nav.vue
+++ b/src/components/mobile_nav/mobile_nav.vue
@@ -17,6 +17,7 @@
<i class="button-icon icon-menu" />
</a>
<router-link
+ v-if="!hideSitename"
class="site-name"
:to="{ name: 'root' }"
active-class="home"
diff --git a/src/components/moderation_tools/moderation_tools.js b/src/components/moderation_tools/moderation_tools.js
index 8aadc8c5..757166ed 100644
--- a/src/components/moderation_tools/moderation_tools.js
+++ b/src/components/moderation_tools/moderation_tools.js
@@ -45,12 +45,12 @@ const ModerationTools = {
toggleTag (tag) {
const store = this.$store
if (this.tagsSet.has(tag)) {
- store.state.api.backendInteractor.untagUser(this.user, tag).then(response => {
+ store.state.api.backendInteractor.untagUser({ user: this.user, tag }).then(response => {
if (!response.ok) { return }
store.commit('untagUser', { user: this.user, tag })
})
} else {
- store.state.api.backendInteractor.tagUser(this.user, tag).then(response => {
+ store.state.api.backendInteractor.tagUser({ user: this.user, tag }).then(response => {
if (!response.ok) { return }
store.commit('tagUser', { user: this.user, tag })
})
@@ -59,24 +59,19 @@ const ModerationTools = {
toggleRight (right) {
const store = this.$store
if (this.user.rights[right]) {
- store.state.api.backendInteractor.deleteRight(this.user, right).then(response => {
+ store.state.api.backendInteractor.deleteRight({ user: this.user, right }).then(response => {
if (!response.ok) { return }
- store.commit('updateRight', { user: this.user, right: right, value: false })
+ store.commit('updateRight', { user: this.user, right, value: false })
})
} else {
- store.state.api.backendInteractor.addRight(this.user, right).then(response => {
+ store.state.api.backendInteractor.addRight({ user: this.user, right }).then(response => {
if (!response.ok) { return }
- store.commit('updateRight', { user: this.user, right: right, value: true })
+ store.commit('updateRight', { user: this.user, right, value: true })
})
}
},
toggleActivationStatus () {
- const store = this.$store
- const status = !!this.user.deactivated
- store.state.api.backendInteractor.setActivationStatus(this.user, status).then(response => {
- if (!response.ok) { return }
- store.commit('updateActivationStatus', { user: this.user, status: status })
- })
+ this.$store.dispatch('toggleActivationStatus', { user: this.user })
},
deleteUserDialog (show) {
this.showDeleteUserDialog = show
@@ -85,7 +80,7 @@ const ModerationTools = {
const store = this.$store
const user = this.user
const { id, name } = user
- store.state.api.backendInteractor.deleteUser(user)
+ store.state.api.backendInteractor.deleteUser({ user })
.then(e => {
this.$store.dispatch('markStatusesAsDeleted', status => user.id === status.user.id)
const isProfile = this.$route.name === 'external-user-profile' || this.$route.name === 'user-profile'
diff --git a/src/components/nav_panel/nav_panel.js b/src/components/nav_panel/nav_panel.js
index 7f783acb..d9268585 100644
--- a/src/components/nav_panel/nav_panel.js
+++ b/src/components/nav_panel/nav_panel.js
@@ -1,20 +1,18 @@
+import { mapState } from 'vuex'
+
const NavPanel = {
created () {
if (this.currentUser && this.currentUser.locked) {
this.$store.dispatch('startFetchingFollowRequest')
}
},
- computed: {
- currentUser () {
- return this.$store.state.users.currentUser
- },
- chat () {
- return this.$store.state.chat.channel
- },
- followRequestCount () {
- return this.$store.state.api.followRequests.length
- }
- }
+ computed: mapState({
+ currentUser: state => state.users.currentUser,
+ chat: state => state.chat.channel,
+ followRequestCount: state => state.api.followRequests.length,
+ privateMode: state => state.instance.private,
+ federating: state => state.instance.federating
+ })
}
export default NavPanel
diff --git a/src/components/nav_panel/nav_panel.vue b/src/components/nav_panel/nav_panel.vue
index 28589bb1..034259d9 100644
--- a/src/components/nav_panel/nav_panel.vue
+++ b/src/components/nav_panel/nav_panel.vue
@@ -4,22 +4,22 @@
<ul>
<li v-if="currentUser">
<router-link :to="{ name: 'friends' }">
- {{ $t("nav.timeline") }}
+ <i class="button-icon icon-home-2" /> {{ $t("nav.timeline") }}
</router-link>
</li>
<li v-if="currentUser">
<router-link :to="{ name: 'interactions', params: { username: currentUser.screen_name } }">
- {{ $t("nav.interactions") }}
+ <i class="button-icon icon-bell-alt" /> {{ $t("nav.interactions") }}
</router-link>
</li>
<li v-if="currentUser">
<router-link :to="{ name: 'dms', params: { username: currentUser.screen_name } }">
- {{ $t("nav.dms") }}
+ <i class="button-icon icon-mail-alt" /> {{ $t("nav.dms") }}
</router-link>
</li>
<li v-if="currentUser && currentUser.locked">
<router-link :to="{ name: 'friend-requests' }">
- {{ $t("nav.friend_requests") }}
+ <i class="button-icon icon-user-plus" /> {{ $t("nav.friend_requests") }}
<span
v-if="followRequestCount > 0"
class="badge follow-request-count"
@@ -28,19 +28,19 @@
</span>
</router-link>
</li>
- <li>
+ <li v-if="currentUser || !privateMode">
<router-link :to="{ name: 'public-timeline' }">
- {{ $t("nav.public_tl") }}
+ <i class="button-icon icon-users" /> {{ $t("nav.public_tl") }}
</router-link>
</li>
- <li>
+ <li v-if="federating && !privateMode">
<router-link :to="{ name: 'public-external-timeline' }">
- {{ $t("nav.twkn") }}
+ <i class="button-icon icon-globe" /> {{ $t("nav.twkn") }}
</router-link>
</li>
<li>
<router-link :to="{ name: 'about' }">
- {{ $t("nav.about") }}
+ <i class="button-icon icon-info-circled" /> {{ $t("nav.about") }}
</router-link>
</li>
</ul>
@@ -113,4 +113,8 @@
}
}
}
+
+.nav-panel .button-icon:before {
+ width: 1.1em;
+}
</style>
diff --git a/src/components/notifications/notifications.js b/src/components/notifications/notifications.js
index 6c4054fd..a89c0cdc 100644
--- a/src/components/notifications/notifications.js
+++ b/src/components/notifications/notifications.js
@@ -47,6 +47,11 @@ const Notifications = {
components: {
Notification
},
+ created () {
+ const { dispatch } = this.$store
+
+ dispatch('fetchAndUpdateNotifications')
+ },
watch: {
unseenCount (count) {
if (count > 0) {
diff --git a/src/components/public_and_external_timeline/public_and_external_timeline.js b/src/components/public_and_external_timeline/public_and_external_timeline.js
index f614c13b..cbd4491b 100644
--- a/src/components/public_and_external_timeline/public_and_external_timeline.js
+++ b/src/components/public_and_external_timeline/public_and_external_timeline.js
@@ -10,7 +10,7 @@ const PublicAndExternalTimeline = {
this.$store.dispatch('startFetchingTimeline', { timeline: 'publicAndExternal' })
},
destroyed () {
- this.$store.dispatch('stopFetching', 'publicAndExternal')
+ this.$store.dispatch('stopFetchingTimeline', 'publicAndExternal')
}
}
diff --git a/src/components/public_timeline/public_timeline.js b/src/components/public_timeline/public_timeline.js
index 8976a99c..66c40d3a 100644
--- a/src/components/public_timeline/public_timeline.js
+++ b/src/components/public_timeline/public_timeline.js
@@ -10,7 +10,7 @@ const PublicTimeline = {
this.$store.dispatch('startFetchingTimeline', { timeline: 'public' })
},
destroyed () {
- this.$store.dispatch('stopFetching', 'public')
+ this.$store.dispatch('stopFetchingTimeline', 'public')
}
}
diff --git a/src/components/registration/registration.vue b/src/components/registration/registration.vue
index 5bb06a4f..222b67a8 100644
--- a/src/components/registration/registration.vue
+++ b/src/components/registration/registration.vue
@@ -172,7 +172,7 @@
for="captcha-label"
>{{ $t('captcha') }}</label>
- <template v-if="captcha.type == 'kocaptcha'">
+ <template v-if="['kocaptcha', 'native'].includes(captcha.type)">
<img
:src="captcha.url"
@click="setCaptcha"
diff --git a/src/components/settings/settings.js b/src/components/settings/settings.js
index c49083f9..31a9e9be 100644
--- a/src/components/settings/settings.js
+++ b/src/components/settings/settings.js
@@ -84,7 +84,7 @@ const settings = {
}
}])
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}),
- // Special cases (need to transform values)
+ // Special cases (need to transform values or perform actions first)
muteWordsString: {
get () { return this.$store.getters.mergedConfig.muteWords.join('\n') },
set (value) {
@@ -93,6 +93,22 @@ const settings = {
value: filter(value.split('\n'), (word) => trim(word).length > 0)
})
}
+ },
+ useStreamingApi: {
+ get () { return this.$store.getters.mergedConfig.useStreamingApi },
+ set (value) {
+ const promise = value
+ ? this.$store.dispatch('enableMastoSockets')
+ : this.$store.dispatch('disableMastoSockets')
+
+ promise.then(() => {
+ this.$store.dispatch('setOption', { name: 'useStreamingApi', value })
+ }).catch((e) => {
+ console.error('Failed starting MastoAPI Streaming socket', e)
+ this.$store.dispatch('disableMastoSockets')
+ this.$store.dispatch('setOption', { name: 'useStreamingApi', value: false })
+ })
+ }
}
},
// Updating nested properties
diff --git a/src/components/settings/settings.vue b/src/components/settings/settings.vue
index 31329e82..cef492f3 100644
--- a/src/components/settings/settings.vue
+++ b/src/components/settings/settings.vue
@@ -74,6 +74,15 @@
</ul>
</li>
<li>
+ <Checkbox v-model="useStreamingApi">
+ {{ $t('settings.useStreamingApi') }}
+ <br/>
+ <small>
+ {{ $t('settings.useStreamingApiWarning') }}
+ </small>
+ </Checkbox>
+ </li>
+ <li>
<Checkbox v-model="autoLoad">
{{ $t('settings.autoload') }}
</Checkbox>
diff --git a/src/components/side_drawer/side_drawer.js b/src/components/side_drawer/side_drawer.js
index 0188cf3e..2534eb8f 100644
--- a/src/components/side_drawer/side_drawer.js
+++ b/src/components/side_drawer/side_drawer.js
@@ -33,11 +33,20 @@ const SideDrawer = {
logo () {
return this.$store.state.instance.logo
},
+ hideSitename () {
+ return this.$store.state.instance.hideSitename
+ },
sitename () {
return this.$store.state.instance.name
},
followRequestCount () {
return this.$store.state.api.followRequests.length
+ },
+ privateMode () {
+ return this.$store.state.instance.private
+ },
+ federating () {
+ return this.$store.state.instance.federating
}
},
methods: {
diff --git a/src/components/side_drawer/side_drawer.vue b/src/components/side_drawer/side_drawer.vue
index 214b8e0c..3fba9058 100644
--- a/src/components/side_drawer/side_drawer.vue
+++ b/src/components/side_drawer/side_drawer.vue
@@ -27,7 +27,7 @@
class="side-drawer-logo-wrapper"
>
<img :src="logo">
- <span>{{ sitename }}</span>
+ <span v-if="!hideSitename">{{ sitename }}</span>
</div>
</div>
<ul>
@@ -36,7 +36,7 @@
@click="toggleDrawer"
>
<router-link :to="{ name: 'login' }">
- {{ $t("login.login") }}
+ <i class="button-icon icon-login" /> {{ $t("login.login") }}
</router-link>
</li>
<li
@@ -44,7 +44,7 @@
@click="toggleDrawer"
>
<router-link :to="{ name: 'dms', params: { username: currentUser.screen_name } }">
- {{ $t("nav.dms") }}
+ <i class="button-icon icon-mail-alt" /> {{ $t("nav.dms") }}
</router-link>
</li>
<li
@@ -52,7 +52,7 @@
@click="toggleDrawer"
>
<router-link :to="{ name: 'interactions', params: { username: currentUser.screen_name } }">
- {{ $t("nav.interactions") }}
+ <i class="button-icon icon-bell-alt" /> {{ $t("nav.interactions") }}
</router-link>
</li>
</ul>
@@ -62,7 +62,7 @@
@click="toggleDrawer"
>
<router-link :to="{ name: 'friends' }">
- {{ $t("nav.timeline") }}
+ <i class="button-icon icon-home-2" /> {{ $t("nav.timeline") }}
</router-link>
</li>
<li
@@ -70,7 +70,7 @@
@click="toggleDrawer"
>
<router-link to="/friend-requests">
- {{ $t("nav.friend_requests") }}
+ <i class="button-icon icon-user-plus" /> {{ $t("nav.friend_requests") }}
<span
v-if="followRequestCount > 0"
class="badge follow-request-count"
@@ -79,14 +79,20 @@
</span>
</router-link>
</li>
- <li @click="toggleDrawer">
+ <li
+ v-if="currentUser || !privateMode"
+ @click="toggleDrawer"
+ >
<router-link to="/main/public">
- {{ $t("nav.public_tl") }}
+ <i class="button-icon icon-users" /> {{ $t("nav.public_tl") }}
</router-link>
</li>
- <li @click="toggleDrawer">
+ <li
+ v-if="federating && !privateMode"
+ @click="toggleDrawer"
+ >
<router-link to="/main/all">
- {{ $t("nav.twkn") }}
+ <i class="button-icon icon-globe" /> {{ $t("nav.twkn") }}
</router-link>
</li>
<li
@@ -94,14 +100,17 @@
@click="toggleDrawer"
>
<router-link :to="{ name: 'chat' }">
- {{ $t("nav.chat") }}
+ <i class="button-icon icon-chat" /> {{ $t("nav.chat") }}
</router-link>
</li>
</ul>
<ul>
- <li @click="toggleDrawer">
+ <li
+ v-if="currentUser || !privateMode"
+ @click="toggleDrawer"
+ >
<router-link :to="{ name: 'search' }">
- {{ $t("nav.search") }}
+ <i class="button-icon icon-search" /> {{ $t("nav.search") }}
</router-link>
</li>
<li
@@ -109,17 +118,17 @@
@click="toggleDrawer"
>
<router-link :to="{ name: 'who-to-follow' }">
- {{ $t("nav.who_to_follow") }}
+ <i class="button-icon icon-user-plus" /> {{ $t("nav.who_to_follow") }}
</router-link>
</li>
<li @click="toggleDrawer">
<router-link :to="{ name: 'settings' }">
- {{ $t("settings.settings") }}
+ <i class="button-icon icon-cog" /> {{ $t("settings.settings") }}
</router-link>
</li>
<li @click="toggleDrawer">
<router-link :to="{ name: 'about'}">
- {{ $t("nav.about") }}
+ <i class="button-icon icon-info-circled" /> {{ $t("nav.about") }}
</router-link>
</li>
<li
@@ -130,7 +139,7 @@
href="/pleroma/admin/#/login-pleroma"
target="_blank"
>
- {{ $t("nav.administration") }}
+ <i class="button-icon icon-gauge" /> {{ $t("nav.administration") }}
</a>
</li>
<li
@@ -141,7 +150,7 @@
href="#"
@click="doLogout"
>
- {{ $t("login.logout") }}
+ <i class="button-icon icon-logout" /> {{ $t("login.logout") }}
</a>
</li>
</ul>
@@ -215,6 +224,10 @@
box-shadow: var(--panelShadow);
background-color: $fallback--bg;
background-color: var(--bg, $fallback--bg);
+
+ .button-icon:before {
+ width: 1.1em;
+ }
}
.side-drawer-logo-wrapper {
diff --git a/src/components/sticker_picker/sticker_picker.vue b/src/components/sticker_picker/sticker_picker.vue
index 323855b9..3863908a 100644
--- a/src/components/sticker_picker/sticker_picker.vue
+++ b/src/components/sticker_picker/sticker_picker.vue
@@ -36,23 +36,23 @@
.sticker-picker {
width: 100%;
- position: relative;
- .tab-switcher {
- position: absolute;
- top: 0;
- bottom: 0;
- left: 0;
- right: 0;
- }
- .sticker-picker-content {
- .sticker {
- display: inline-block;
- width: 20%;
- height: 20%;
- img {
- width: 100%;
- &:hover {
- filter: drop-shadow(0 0 5px var(--link, $fallback--link));
+ .contents {
+ min-height: 250px;
+ .sticker-picker-content {
+ display: flex;
+ flex-wrap: wrap;
+ padding: 0 4px;
+ .sticker {
+ display: flex;
+ flex: 1 1 auto;
+ margin: 4px;
+ width: 56px;
+ height: 56px;
+ img {
+ height: 100%;
+ &:hover {
+ filter: drop-shadow(0 0 5px var(--link, $fallback--link));
+ }
}
}
}
diff --git a/src/components/tag_timeline/tag_timeline.js b/src/components/tag_timeline/tag_timeline.js
index 458eb1c5..400c6a4b 100644
--- a/src/components/tag_timeline/tag_timeline.js
+++ b/src/components/tag_timeline/tag_timeline.js
@@ -19,7 +19,7 @@ const TagTimeline = {
}
},
destroyed () {
- this.$store.dispatch('stopFetching', 'tag')
+ this.$store.dispatch('stopFetchingTimeline', 'tag')
}
}
diff --git a/src/components/timeline/timeline.js b/src/components/timeline/timeline.js
index 27a9a55e..9a53acd6 100644
--- a/src/components/timeline/timeline.js
+++ b/src/components/timeline/timeline.js
@@ -36,7 +36,12 @@ const Timeline = {
}
},
computed: {
- timelineError () { return this.$store.state.statuses.error },
+ timelineError () {
+ return this.$store.state.statuses.error
+ },
+ errorData () {
+ return this.$store.state.statuses.errorData
+ },
newStatusCount () {
return this.timeline.newStatusCount
},
diff --git a/src/components/timeline/timeline.vue b/src/components/timeline/timeline.vue
index 93f6f570..9777bd0c 100644
--- a/src/components/timeline/timeline.vue
+++ b/src/components/timeline/timeline.vue
@@ -11,15 +11,22 @@
>
{{ $t('timeline.error_fetching') }}
</div>
+ <div
+ v-else-if="errorData"
+ class="loadmore-error alert error"
+ @click.prevent
+ >
+ {{ errorData.statusText }}
+ </div>
<button
- v-if="timeline.newStatusCount > 0 && !timelineError"
+ v-if="timeline.newStatusCount > 0 && !timelineError && !errorData"
class="loadmore-button"
@click.prevent="showNewStatuses"
>
{{ $t('timeline.show_new') }}{{ newStatusCountStr }}
</button>
<div
- v-if="!timeline.newStatusCount > 0 && !timelineError"
+ v-if="!timeline.newStatusCount > 0 && !timelineError && !errorData"
class="loadmore-text faint"
@click.prevent
>
@@ -67,12 +74,18 @@
{{ $t('timeline.no_more_statuses') }}
</div>
<a
- v-else-if="!timeline.loading"
+ v-else-if="!timeline.loading && !errorData"
href="#"
@click.prevent="fetchOlderStatuses()"
>
<div class="new-status-notification text-center panel-footer">{{ $t('timeline.load_older') }}</div>
</a>
+ <a
+ v-else-if="errorData"
+ href="#"
+ >
+ <div class="new-status-notification text-center panel-footer">{{ errorData.error }}</div>
+ </a>
<div
v-else
class="new-status-notification text-center panel-footer"
@@ -93,17 +106,4 @@
opacity: 1;
}
}
-
-.new-status-notification {
- position:relative;
- margin-top: -1px;
- font-size: 1.1em;
- border-width: 1px 0 0 0;
- border-style: solid;
- border-color: var(--border, $fallback--border);
- padding: 10px;
- z-index: 1;
- background-color: $fallback--fg;
- background-color: var(--panel, $fallback--fg);
-}
</style>
diff --git a/src/components/user_profile/user_profile.js b/src/components/user_profile/user_profile.js
index 00055707..9558a0bd 100644
--- a/src/components/user_profile/user_profile.js
+++ b/src/components/user_profile/user_profile.js
@@ -112,9 +112,9 @@ const UserProfile = {
}
},
stopFetching () {
- this.$store.dispatch('stopFetching', 'user')
- this.$store.dispatch('stopFetching', 'favorites')
- this.$store.dispatch('stopFetching', 'media')
+ this.$store.dispatch('stopFetchingTimeline', 'user')
+ this.$store.dispatch('stopFetchingTimeline', 'favorites')
+ this.$store.dispatch('stopFetchingTimeline', 'media')
},
switchUser (userNameOrId) {
this.stopFetching()
diff --git a/src/components/user_reporting_modal/user_reporting_modal.js b/src/components/user_reporting_modal/user_reporting_modal.js
index 833fa98a..38cf117b 100644
--- a/src/components/user_reporting_modal/user_reporting_modal.js
+++ b/src/components/user_reporting_modal/user_reporting_modal.js
@@ -64,7 +64,7 @@ const UserReportingModal = {
forward: this.forward,
statusIds: this.statusIdsToReport
}
- this.$store.state.api.backendInteractor.reportUser(params)
+ this.$store.state.api.backendInteractor.reportUser({ ...params })
.then(() => {
this.processing = false
this.resetState()
diff --git a/src/components/user_settings/mfa.js b/src/components/user_settings/mfa.js
index 3090138a..abf37062 100644
--- a/src/components/user_settings/mfa.js
+++ b/src/components/user_settings/mfa.js
@@ -139,7 +139,7 @@ const Mfa = {
// fetch settings from server
async fetchSettings () {
- let result = await this.backendInteractor.fetchSettingsMFA()
+ let result = await this.backendInteractor.settingsMFA()
if (result.error) return
this.settings = result.settings
this.settings.available = true
diff --git a/src/components/user_settings/user_settings.js b/src/components/user_settings/user_settings.js
index 3fdc5340..d5d671e4 100644
--- a/src/components/user_settings/user_settings.js
+++ b/src/components/user_settings/user_settings.js
@@ -242,7 +242,7 @@ const UserSettings = {
})
},
importFollows (file) {
- return this.$store.state.api.backendInteractor.importFollows(file)
+ return this.$store.state.api.backendInteractor.importFollows({ file })
.then((status) => {
if (!status) {
throw new Error('failed')
@@ -250,7 +250,7 @@ const UserSettings = {
})
},
importBlocks (file) {
- return this.$store.state.api.backendInteractor.importBlocks(file)
+ return this.$store.state.api.backendInteractor.importBlocks({ file })
.then((status) => {
if (!status) {
throw new Error('failed')
@@ -297,7 +297,7 @@ const UserSettings = {
newPassword: this.changePasswordInputs[1],
newPasswordConfirmation: this.changePasswordInputs[2]
}
- this.$store.state.api.backendInteractor.changePassword(params)
+ this.$store.state.api.backendInteractor.changePassword({ params })
.then((res) => {
if (res.status === 'success') {
this.changedPassword = true
@@ -314,7 +314,7 @@ const UserSettings = {
email: this.newEmail,
password: this.changeEmailPassword
}
- this.$store.state.api.backendInteractor.changeEmail(params)
+ this.$store.state.api.backendInteractor.changeEmail({ params })
.then((res) => {
if (res.status === 'success') {
this.changedEmail = true
diff --git a/src/hocs/with_load_more/with_load_more.js b/src/hocs/with_load_more/with_load_more.js
index 1e1b2a74..6142f513 100644
--- a/src/hocs/with_load_more/with_load_more.js
+++ b/src/hocs/with_load_more/with_load_more.js
@@ -65,7 +65,7 @@ const withLoadMore = ({
}
}
},
- render (createElement) {
+ render (h) {
const props = {
props: {
...this.$props,
@@ -74,7 +74,7 @@ const withLoadMore = ({
on: this.$listeners,
scopedSlots: this.$scopedSlots
}
- const children = Object.entries(this.$slots).map(([key, value]) => createElement('template', { slot: key }, value))
+ const children = Object.entries(this.$slots).map(([key, value]) => h('template', { slot: key }, value))
return (
<div class="with-load-more">
<WrappedComponent {...props}>
diff --git a/src/hocs/with_subscription/with_subscription.js b/src/hocs/with_subscription/with_subscription.js
index 91fc4cca..1775adcb 100644
--- a/src/hocs/with_subscription/with_subscription.js
+++ b/src/hocs/with_subscription/with_subscription.js
@@ -49,7 +49,7 @@ const withSubscription = ({
}
}
},
- render (createElement) {
+ render (h) {
if (!this.error && !this.loading) {
const props = {
props: {
@@ -59,7 +59,7 @@ const withSubscription = ({
on: this.$listeners,
scopedSlots: this.$scopedSlots
}
- const children = Object.entries(this.$slots).map(([key, value]) => createElement('template', { slot: key }, value))
+ const children = Object.entries(this.$slots).map(([key, value]) => h('template', { slot: key }, value))
return (
<div class="with-subscription">
<WrappedComponent {...props}>
diff --git a/src/i18n/en.json b/src/i18n/en.json
index effc139f..75d66b9f 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -361,6 +361,8 @@
"post_status_content_type": "Post status content type",
"stop_gifs": "Play-on-hover GIFs",
"streaming": "Enable automatic streaming of new posts when scrolled to the top",
+ "useStreamingApi": "Receive posts and notifications real-time",
+ "useStreamingApiWarning": "(Not recommended, experimental, known to skip posts)",
"text": "Text",
"theme": "Theme",
"theme_help": "Use hex color codes (#rrggbb) to customize your color theme.",
diff --git a/src/i18n/ja_easy.json b/src/i18n/ja_easy.json
index 592a7257..be447f1c 100644
--- a/src/i18n/ja_easy.json
+++ b/src/i18n/ja_easy.json
@@ -1,4 +1,23 @@
{
+ "about": {
+ "staff": "スタッフ",
+ "federation": "フェデレーション",
+ "mrf_policies": "ゆうこうなMRFポリシー",
+ "mrf_policies_desc": "MRFポリシーは、このインスタンスのフェデレーションのふるまいを、いじります。これらのMRFポリシーがゆうこうになっています:",
+ "mrf_policy_simple": "インスタンスのポリシー",
+ "mrf_policy_simple_accept": "うけいれ",
+ "mrf_policy_simple_accept_desc": "このインスンスは、これらのインスタンスからのメッセージのみをうけいれます:",
+ "mrf_policy_simple_reject": "おことわり",
+ "mrf_policy_simple_reject_desc": "このインスタンスは、これらのインスタンスからのメッセージをうけいれません:",
+ "mrf_policy_simple_quarantine": "けんえき",
+ "mrf_policy_simple_quarantine_desc": "このインスタンスは、これらのインスタンスに、パブリックなとうこうのみを、おくります:",
+ "mrf_policy_simple_ftl_removal": "「つながっているすべてのネットワーク」タイムラインからのぞく",
+ "mrf_policy_simple_ftl_removal_desc": "このインスタンスは、つながっているすべてのネットワーク」タイムラインから、これらのインスタンスを、とりのぞきます:",
+ "mrf_policy_simple_media_removal": "メディアをのぞく",
+ "mrf_policy_simple_media_removal_desc": "このインスタンスは、これらのインスタンスからおくられてきたメディアを、とりのぞきます:",
+ "mrf_policy_simple_media_nsfw": "メディアをすべてセンシティブにする",
+ "mrf_policy_simple_media_nsfw_desc": "このインスタンスは、これらのインスタンスからおくられてきたメディアを、すべて、センシティブにマークします:"
+ },
"chat": {
"title": "チャット"
},
@@ -68,6 +87,7 @@
},
"nav": {
"about": "これはなに?",
+ "administration": "アドミニストレーション",
"back": "もどる",
"chat": "ローカルチャット",
"friend_requests": "フォローリクエスト",
@@ -113,7 +133,9 @@
"search_emoji": "えもじをさがす",
"add_emoji": "えもじをうちこむ",
"custom": "カスタムえもじ",
- "unicode": "ユニコードえもじ"
+ "unicode": "ユニコードえもじ",
+ "load_all_hint": "はじめの {saneAmount} このえもじだけがロードされています。すべてのえもじをロードすると、パフォーマンスがわるくなるかもしれません。",
+ "load_all": "すべてのえもじをロード ({emojiAmount} こあります)"
},
"stickers": {
"add_sticker": "ステッカーをふやす"
@@ -173,6 +195,11 @@
"password_confirmation_match": "パスワードがちがいます"
}
},
+ "remote_user_resolver": {
+ "remote_user_resolver": "リモートユーザーリゾルバー",
+ "searching_for": "さがしています:",
+ "error": "みつかりませんでした。"
+ },
"selectable_list": {
"select_all": "すべてえらぶ"
},
@@ -220,6 +247,9 @@
"cGreen": "リピート",
"cOrange": "おきにいり",
"cRed": "キャンセル",
+ "change_email": "メールアドレスをかえる",
+ "change_email_error": "メールアドレスをかえようとしましたが、なにかがおかしいです。",
+ "changed_email": "メールアドレスをかえることができました!",
"change_password": "パスワードをかえる",
"change_password_error": "パスワードをかえることが、できなかったかもしれません。",
"changed_password": "パスワードが、かわりました!",
@@ -279,6 +309,7 @@
"use_contain_fit": "がぞうのサムネイルを、きりぬかない",
"name": "なまえ",
"name_bio": "なまえとプロフィール",
+ "new_email": "あたらしいメールアドレス",
"new_password": "あたらしいパスワード",
"notification_visibility": "ひょうじするつうち",
"notification_visibility_follows": "フォロー",
@@ -344,6 +375,8 @@
"false": "いいえ",
"true": "はい"
},
+ "fun": "おたのしみ",
+ "greentext": "ミームやじるし",
"notifications": "つうち",
"notification_setting": "つうちをうけとる:",
"notification_setting_follows": "あなたがフォローしているひとから",
@@ -391,6 +424,7 @@
"_tab_label": "くわしく",
"alert": "アラートのバックグラウンド",
"alert_error": "エラー",
+ "alert_warning": "けいこく",
"badge": "バッジのバックグラウンド",
"badge_notification": "つうち",
"panel_header": "パネルヘッダー",
@@ -542,6 +576,7 @@
"followers": "フォロワー",
"following": "フォローしています!",
"follows_you": "フォローされました!",
+ "hidden": "かくされています",
"its_you": "これはあなたです!",
"media": "メディア",
"mention": "メンション",
@@ -559,6 +594,8 @@
"unmute": "ミュートをやめる",
"unmute_progress": "ミュートをとりけしています...",
"mute_progress": "ミュートしています...",
+ "hide_repeats": "リピートをかくす",
+ "show_repeats": "リピートをみる",
"admin_menu": {
"moderation": "モデレーション",
"grant_admin": "アドミンにする",
@@ -634,6 +671,8 @@
"return_home": "ホームページにもどる",
"not_found": "そのメールアドレスまたはユーザーめいを、みつけることができませんでした。",
"too_many_requests": "パスワードリセットを、ためすことが、おおすぎます。しばらくしてから、ためしてください。",
- "password_reset_disabled": "このインスタンスでは、パスワードリセットは、できません。インスタンスのアドミニストレーターに、おといあわせください。"
+ "password_reset_disabled": "このインスタンスでは、パスワードリセットは、できません。インスタンスのアドミニストレーターに、おといあわせください。",
+ "password_reset_required": "ログインするには、パスワードをリセットしてください。",
+ "password_reset_required_but_mailer_is_disabled": "あなたはパスワードのリセットがひつようです。しかし、まずいことに、このインスタンスでは、パスワードのリセットができなくなっています。このインスタンスのアドミニストレーターに、おといあわせください。"
}
}
diff --git a/src/i18n/ru.json b/src/i18n/ru.json
index 19e10f1e..4cb2d497 100644
--- a/src/i18n/ru.json
+++ b/src/i18n/ru.json
@@ -219,6 +219,8 @@
"subject_input_always_show": "Всегда показывать поле ввода темы",
"stop_gifs": "Проигрывать GIF анимации только при наведении",
"streaming": "Включить автоматическую загрузку новых сообщений при прокрутке вверх",
+ "useStreamingApi": "Получать сообщения и уведомления в реальном времени",
+ "useStreamingApiWarning": "(Не рекомендуется, экспериментально, сообщения могут пропадать)",
"text": "Текст",
"theme": "Тема",
"theme_help": "Используйте шестнадцатеричные коды цветов (#rrggbb) для настройки темы.",
diff --git a/src/modules/api.js b/src/modules/api.js
index 1293e3c8..9c296275 100644
--- a/src/modules/api.js
+++ b/src/modules/api.js
@@ -6,6 +6,7 @@ const api = {
backendInteractor: backendInteractorService(),
fetchers: {},
socket: null,
+ mastoUserSocket: null,
followRequests: []
},
mutations: {
@@ -15,7 +16,8 @@ const api = {
addFetcher (state, { fetcherName, fetcher }) {
state.fetchers[fetcherName] = fetcher
},
- removeFetcher (state, { fetcherName }) {
+ removeFetcher (state, { fetcherName, fetcher }) {
+ window.clearInterval(fetcher)
delete state.fetchers[fetcherName]
},
setWsToken (state, token) {
@@ -29,32 +31,134 @@ const api = {
}
},
actions: {
- startFetchingTimeline (store, { timeline = 'friends', tag = false, userId = false }) {
- // Don't start fetching if we already are.
+ // Global MastoAPI socket control, in future should disable ALL sockets/(re)start relevant sockets
+ enableMastoSockets (store) {
+ const { state, dispatch } = store
+ if (state.mastoUserSocket) return
+ return dispatch('startMastoUserSocket')
+ },
+ disableMastoSockets (store) {
+ const { state, dispatch } = store
+ if (!state.mastoUserSocket) return
+ return dispatch('stopMastoUserSocket')
+ },
+
+ // MastoAPI 'User' sockets
+ startMastoUserSocket (store) {
+ return new Promise((resolve, reject) => {
+ try {
+ const { state, dispatch, rootState } = store
+ const timelineData = rootState.statuses.timelines.friends
+ state.mastoUserSocket = state.backendInteractor.startUserSocket({ store })
+ state.mastoUserSocket.addEventListener(
+ 'message',
+ ({ detail: message }) => {
+ if (!message) return // pings
+ if (message.event === 'notification') {
+ dispatch('addNewNotifications', {
+ notifications: [message.notification],
+ older: false
+ })
+ } else if (message.event === 'update') {
+ dispatch('addNewStatuses', {
+ statuses: [message.status],
+ userId: false,
+ showImmediately: timelineData.visibleStatuses.length === 0,
+ timeline: 'friends'
+ })
+ }
+ }
+ )
+ state.mastoUserSocket.addEventListener('error', ({ detail: error }) => {
+ console.error('Error in MastoAPI websocket:', error)
+ })
+ state.mastoUserSocket.addEventListener('close', ({ detail: closeEvent }) => {
+ const ignoreCodes = new Set([
+ 1000, // Normal (intended) closure
+ 1001 // Going away
+ ])
+ const { code } = closeEvent
+ if (ignoreCodes.has(code)) {
+ console.debug(`Not restarting socket becasue of closure code ${code} is in ignore list`)
+ } else {
+ console.warn(`MastoAPI websocket disconnected, restarting. CloseEvent code: ${code}`)
+ dispatch('startFetchingTimeline', { timeline: 'friends' })
+ dispatch('startFetchingNotifications')
+ dispatch('restartMastoUserSocket')
+ }
+ })
+ resolve()
+ } catch (e) {
+ reject(e)
+ }
+ })
+ },
+ 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')
+ })
+ },
+ stopMastoUserSocket ({ state, dispatch }) {
+ dispatch('startFetchingTimeline', { timeline: 'friends' })
+ dispatch('startFetchingNotifications')
+ console.log(state.mastoUserSocket)
+ state.mastoUserSocket.close()
+ },
+
+ // Timelines
+ startFetchingTimeline (store, {
+ timeline = 'friends',
+ tag = false,
+ userId = false
+ }) {
if (store.state.fetchers[timeline]) return
- const fetcher = store.state.backendInteractor.startFetchingTimeline({ timeline, store, userId, tag })
+ const fetcher = store.state.backendInteractor.startFetchingTimeline({
+ timeline, store, userId, tag
+ })
store.commit('addFetcher', { fetcherName: timeline, fetcher })
},
- startFetchingNotifications (store) {
- // Don't start fetching if we already are.
- if (store.state.fetchers['notifications']) return
+ stopFetchingTimeline (store, timeline) {
+ const fetcher = store.state.fetchers[timeline]
+ if (!fetcher) return
+ store.commit('removeFetcher', { fetcherName: timeline, fetcher })
+ },
+ // Notifications
+ startFetchingNotifications (store) {
+ if (store.state.fetchers.notifications) return
const fetcher = store.state.backendInteractor.startFetchingNotifications({ store })
store.commit('addFetcher', { fetcherName: 'notifications', fetcher })
},
- startFetchingFollowRequest (store) {
- // Don't start fetching if we already are.
- if (store.state.fetchers['followRequest']) return
+ stopFetchingNotifications (store) {
+ const fetcher = store.state.fetchers.notifications
+ if (!fetcher) return
+ store.commit('removeFetcher', { fetcherName: 'notifications', fetcher })
+ },
+ fetchAndUpdateNotifications (store) {
+ store.state.backendInteractor.fetchAndUpdateNotifications({ store })
+ },
- const fetcher = store.state.backendInteractor.startFetchingFollowRequest({ store })
- store.commit('addFetcher', { fetcherName: 'followRequest', fetcher })
+ // Follow requests
+ startFetchingFollowRequests (store) {
+ if (store.state.fetchers['followRequests']) return
+ const fetcher = store.state.backendInteractor.startFetchingFollowRequests({ store })
+ store.commit('addFetcher', { fetcherName: 'followRequests', fetcher })
},
- stopFetching (store, fetcherName) {
- const fetcher = store.state.fetchers[fetcherName]
- window.clearInterval(fetcher)
- store.commit('removeFetcher', { fetcherName })
+ stopFetchingFollowRequests (store) {
+ const fetcher = store.state.fetchers.followRequests
+ if (!fetcher) return
+ store.commit('removeFetcher', { fetcherName: 'followRequests', fetcher })
+ },
+ removeFollowRequest (store, request) {
+ let requests = store.state.followRequests.filter((it) => it !== request)
+ store.commit('setFollowRequests', requests)
},
+
+ // Pleroma websocket
setWsToken (store, token) {
store.commit('setWsToken', token)
},
@@ -72,10 +176,6 @@ const api = {
disconnectFromSocket ({ commit, state }) {
state.socket && state.socket.disconnect()
commit('setSocket', null)
- },
- removeFollowRequest (store, request) {
- let requests = store.state.followRequests.filter((it) => it !== request)
- store.commit('setFollowRequests', requests)
}
}
}
diff --git a/src/modules/auth_flow.js b/src/modules/auth_flow.js
index d0a90feb..956d40e8 100644
--- a/src/modules/auth_flow.js
+++ b/src/modules/auth_flow.js
@@ -7,7 +7,6 @@ const RECOVERY_STRATEGY = 'recovery'
// initial state
const state = {
- app: null,
settings: {},
strategy: PASSWORD_STRATEGY,
initStrategy: PASSWORD_STRATEGY // default strategy from config
@@ -16,14 +15,10 @@ const state = {
const resetState = (state) => {
state.strategy = state.initStrategy
state.settings = {}
- state.app = null
}
// getters
const getters = {
- app: (state, getters) => {
- return state.app
- },
settings: (state, getters) => {
return state.settings
},
@@ -55,9 +50,8 @@ const mutations = {
requireToken (state) {
state.strategy = TOKEN_STRATEGY
},
- requireMFA (state, { app, settings }) {
+ requireMFA (state, { settings }) {
state.settings = settings
- state.app = app
state.strategy = TOTP_STRATEGY // default strategy of MFA
},
requireRecovery (state) {
diff --git a/src/modules/config.js b/src/modules/config.js
index cc47c848..de9f041b 100644
--- a/src/modules/config.js
+++ b/src/modules/config.js
@@ -36,6 +36,7 @@ export const defaultState = {
highlight: {},
interfaceLanguage: browserLocale,
hideScopeNotice: false,
+ useStreamingApi: false,
scopeCopy: undefined, // instance default
subjectLineBehavior: undefined, // instance default
alwaysShowSubjectInput: undefined, // instance default
diff --git a/src/modules/instance.js b/src/modules/instance.js
index 96f14ed5..625323b9 100644
--- a/src/modules/instance.js
+++ b/src/modules/instance.js
@@ -27,6 +27,7 @@ const defaultState = {
scopeCopy: true,
subjectLineBehavior: 'email',
postContentType: 'text/plain',
+ hideSitename: false,
nsfwCensorImage: undefined,
vapidPublicKey: undefined,
noAttachmentLinks: false,
diff --git a/src/modules/oauth_tokens.js b/src/modules/oauth_tokens.js
index 0159a3f1..907cae4a 100644
--- a/src/modules/oauth_tokens.js
+++ b/src/modules/oauth_tokens.js
@@ -9,7 +9,7 @@ const oauthTokens = {
})
},
revokeToken ({ rootState, commit, state }, id) {
- rootState.api.backendInteractor.revokeOAuthToken(id).then((response) => {
+ rootState.api.backendInteractor.revokeOAuthToken({ id }).then((response) => {
if (response.status === 201) {
commit('swapTokens', state.tokens.filter(token => token.id !== id))
}
diff --git a/src/modules/polls.js b/src/modules/polls.js
index e6158b63..92b89a06 100644
--- a/src/modules/polls.js
+++ b/src/modules/polls.js
@@ -40,7 +40,7 @@ const polls = {
commit('mergeOrAddPoll', poll)
},
updateTrackedPoll ({ rootState, dispatch, commit }, pollId) {
- rootState.api.backendInteractor.fetchPoll(pollId).then(poll => {
+ rootState.api.backendInteractor.fetchPoll({ pollId }).then(poll => {
setTimeout(() => {
if (rootState.polls.trackedPolls[pollId]) {
dispatch('updateTrackedPoll', pollId)
@@ -59,7 +59,7 @@ const polls = {
commit('untrackPoll', pollId)
},
votePoll ({ rootState, commit }, { id, pollId, choices }) {
- return rootState.api.backendInteractor.vote(pollId, choices).then(poll => {
+ return rootState.api.backendInteractor.vote({ pollId, choices }).then(poll => {
commit('mergeOrAddPoll', poll)
return poll
})
diff --git a/src/modules/statuses.js b/src/modules/statuses.js
index f3fc0f21..16dae8ce 100644
--- a/src/modules/statuses.js
+++ b/src/modules/statuses.js
@@ -38,6 +38,7 @@ export const defaultState = () => ({
notifications: emptyNotifications(),
favorites: new Set(),
error: false,
+ errorData: null,
timelines: {
mentions: emptyTl(),
public: emptyTl(),
@@ -483,6 +484,9 @@ export const mutations = {
setError (state, { value }) {
state.error = value
},
+ setErrorData (state, { value }) {
+ state.errorData = value
+ },
setNotificationsLoading (state, { value }) {
state.notifications.loading = value
},
@@ -532,6 +536,9 @@ const statuses = {
setError ({ rootState, commit }, { value }) {
commit('setError', { value })
},
+ setErrorData ({ rootState, commit }, { value }) {
+ commit('setErrorData', { value })
+ },
setNotificationsLoading ({ rootState, commit }, { value }) {
commit('setNotificationsLoading', { value })
},
@@ -555,45 +562,45 @@ const statuses = {
favorite ({ rootState, commit }, status) {
// Optimistic favoriting...
commit('setFavorited', { status, value: true })
- rootState.api.backendInteractor.favorite(status.id)
+ rootState.api.backendInteractor.favorite({ id: status.id })
.then(status => commit('setFavoritedConfirm', { status, user: rootState.users.currentUser }))
},
unfavorite ({ rootState, commit }, status) {
// Optimistic unfavoriting...
commit('setFavorited', { status, value: false })
- rootState.api.backendInteractor.unfavorite(status.id)
+ rootState.api.backendInteractor.unfavorite({ id: status.id })
.then(status => commit('setFavoritedConfirm', { status, user: rootState.users.currentUser }))
},
fetchPinnedStatuses ({ rootState, dispatch }, userId) {
- rootState.api.backendInteractor.fetchPinnedStatuses(userId)
+ rootState.api.backendInteractor.fetchPinnedStatuses({ id: userId })
.then(statuses => dispatch('addNewStatuses', { statuses, timeline: 'user', userId, showImmediately: true, noIdUpdate: true }))
},
pinStatus ({ rootState, dispatch }, statusId) {
- return rootState.api.backendInteractor.pinOwnStatus(statusId)
+ return rootState.api.backendInteractor.pinOwnStatus({ id: statusId })
.then((status) => dispatch('addNewStatuses', { statuses: [status] }))
},
unpinStatus ({ rootState, dispatch }, statusId) {
- rootState.api.backendInteractor.unpinOwnStatus(statusId)
+ rootState.api.backendInteractor.unpinOwnStatus({ id: statusId })
.then((status) => dispatch('addNewStatuses', { statuses: [status] }))
},
muteConversation ({ rootState, commit }, statusId) {
- return rootState.api.backendInteractor.muteConversation(statusId)
+ return rootState.api.backendInteractor.muteConversation({ id: statusId })
.then((status) => commit('setMutedStatus', status))
},
unmuteConversation ({ rootState, commit }, statusId) {
- return rootState.api.backendInteractor.unmuteConversation(statusId)
+ return rootState.api.backendInteractor.unmuteConversation({ id: statusId })
.then((status) => commit('setMutedStatus', status))
},
retweet ({ rootState, commit }, status) {
// Optimistic retweeting...
commit('setRetweeted', { status, value: true })
- rootState.api.backendInteractor.retweet(status.id)
+ rootState.api.backendInteractor.retweet({ id: status.id })
.then(status => commit('setRetweetedConfirm', { status: status.retweeted_status, user: rootState.users.currentUser }))
},
unretweet ({ rootState, commit }, status) {
// Optimistic unretweeting...
commit('setRetweeted', { status, value: false })
- rootState.api.backendInteractor.unretweet(status.id)
+ rootState.api.backendInteractor.unretweet({ id: status.id })
.then(status => commit('setRetweetedConfirm', { status, user: rootState.users.currentUser }))
},
queueFlush ({ rootState, commit }, { timeline, id }) {
@@ -608,19 +615,19 @@ const statuses = {
},
fetchFavsAndRepeats ({ rootState, commit }, id) {
Promise.all([
- rootState.api.backendInteractor.fetchFavoritedByUsers(id),
- rootState.api.backendInteractor.fetchRebloggedByUsers(id)
+ rootState.api.backendInteractor.fetchFavoritedByUsers({ id }),
+ rootState.api.backendInteractor.fetchRebloggedByUsers({ id })
]).then(([favoritedByUsers, rebloggedByUsers]) => {
commit('addFavs', { id, favoritedByUsers, currentUser: rootState.users.currentUser })
commit('addRepeats', { id, rebloggedByUsers, currentUser: rootState.users.currentUser })
})
},
fetchFavs ({ rootState, commit }, id) {
- rootState.api.backendInteractor.fetchFavoritedByUsers(id)
+ rootState.api.backendInteractor.fetchFavoritedByUsers({ id })
.then(favoritedByUsers => commit('addFavs', { id, favoritedByUsers, currentUser: rootState.users.currentUser }))
},
fetchRepeats ({ rootState, commit }, id) {
- rootState.api.backendInteractor.fetchRebloggedByUsers(id)
+ rootState.api.backendInteractor.fetchRebloggedByUsers({ id })
.then(rebloggedByUsers => commit('addRepeats', { id, rebloggedByUsers, currentUser: rootState.users.currentUser }))
},
search (store, { q, resolve, limit, offset, following }) {
diff --git a/src/modules/users.js b/src/modules/users.js
index f209b23b..84fa0255 100644
--- a/src/modules/users.js
+++ b/src/modules/users.js
@@ -32,7 +32,7 @@ const getNotificationPermission = () => {
}
const blockUser = (store, id) => {
- return store.rootState.api.backendInteractor.blockUser(id)
+ return store.rootState.api.backendInteractor.blockUser({ id })
.then((relationship) => {
store.commit('updateUserRelationship', [relationship])
store.commit('addBlockId', id)
@@ -43,12 +43,12 @@ const blockUser = (store, id) => {
}
const unblockUser = (store, id) => {
- return store.rootState.api.backendInteractor.unblockUser(id)
+ return store.rootState.api.backendInteractor.unblockUser({ id })
.then((relationship) => store.commit('updateUserRelationship', [relationship]))
}
const muteUser = (store, id) => {
- return store.rootState.api.backendInteractor.muteUser(id)
+ return store.rootState.api.backendInteractor.muteUser({ id })
.then((relationship) => {
store.commit('updateUserRelationship', [relationship])
store.commit('addMuteId', id)
@@ -56,7 +56,7 @@ const muteUser = (store, id) => {
}
const unmuteUser = (store, id) => {
- return store.rootState.api.backendInteractor.unmuteUser(id)
+ return store.rootState.api.backendInteractor.unmuteUser({ id })
.then((relationship) => store.commit('updateUserRelationship', [relationship]))
}
@@ -95,9 +95,9 @@ export const mutations = {
newRights[right] = value
set(user, 'rights', newRights)
},
- updateActivationStatus (state, { user: { id }, status }) {
+ updateActivationStatus (state, { user: { id }, deactivated }) {
const user = state.usersObject[id]
- set(user, 'deactivated', !status)
+ set(user, 'deactivated', deactivated)
},
setCurrentUser (state, user) {
state.lastLoginName = user.screen_name
@@ -324,13 +324,18 @@ const users = {
commit('clearFollowers', userId)
},
subscribeUser ({ rootState, commit }, id) {
- return rootState.api.backendInteractor.subscribeUser(id)
+ return rootState.api.backendInteractor.subscribeUser({ id })
.then((relationship) => commit('updateUserRelationship', [relationship]))
},
unsubscribeUser ({ rootState, commit }, id) {
- return rootState.api.backendInteractor.unsubscribeUser(id)
+ return rootState.api.backendInteractor.unsubscribeUser({ id })
.then((relationship) => commit('updateUserRelationship', [relationship]))
},
+ toggleActivationStatus ({ rootState, commit }, user) {
+ const api = user.deactivated ? rootState.api.backendInteractor.activateUser : rootState.api.backendInteractor.deactivateUser
+ api(user)
+ .then(({ deactivated }) => commit('updateActivationStatus', { user, deactivated }))
+ },
registerPushNotifications (store) {
const token = store.state.currentUser.credentials
const vapidPublicKey = store.rootState.instance.vapidPublicKey
@@ -384,7 +389,7 @@ const users = {
})
},
searchUsers (store, query) {
- return store.rootState.api.backendInteractor.searchUsers(query)
+ return store.rootState.api.backendInteractor.searchUsers({ query })
.then((users) => {
store.commit('addNewUsers', users)
return users
@@ -396,7 +401,7 @@ const users = {
let rootState = store.rootState
try {
- let data = await rootState.api.backendInteractor.register(userInfo)
+ let data = await rootState.api.backendInteractor.register({ ...userInfo })
store.commit('signUpSuccess')
store.commit('setToken', data.access_token)
store.dispatch('loginUser', data.access_token)
@@ -433,10 +438,10 @@ const users = {
store.commit('clearCurrentUser')
store.dispatch('disconnectFromSocket')
store.commit('clearToken')
- store.dispatch('stopFetching', 'friends')
+ store.dispatch('stopFetchingTimeline', 'friends')
store.commit('setBackendInteractor', backendInteractorService(store.getters.getToken()))
- store.dispatch('stopFetching', 'notifications')
- store.dispatch('stopFetching', 'followRequest')
+ store.dispatch('stopFetchingNotifications')
+ store.dispatch('stopFetchingFollowRequests')
store.commit('clearNotifications')
store.commit('resetStatuses')
})
@@ -471,11 +476,24 @@ const users = {
store.dispatch('initializeSocket')
}
- // Start getting fresh posts.
- store.dispatch('startFetchingTimeline', { timeline: 'friends' })
+ const startPolling = () => {
+ // Start getting fresh posts.
+ store.dispatch('startFetchingTimeline', { timeline: 'friends' })
+
+ // Start fetching notifications
+ store.dispatch('startFetchingNotifications')
+ }
- // Start fetching notifications
- store.dispatch('startFetchingNotifications')
+ if (store.getters.mergedConfig.useStreamingApi) {
+ store.dispatch('enableMastoSockets').catch((error) => {
+ console.error('Failed initializing MastoAPI Streaming socket', error)
+ startPolling()
+ }).then(() => {
+ setTimeout(() => store.dispatch('setNotificationsSilence', false), 10000)
+ })
+ } else {
+ startPolling()
+ }
// Get user mutes
store.dispatch('fetchMutes')
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index 8f5eb416..ef0267aa 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -1,4 +1,4 @@
-import { each, map, concat, last } from 'lodash'
+import { each, map, concat, last, get } from 'lodash'
import { parseStatus, parseUser, parseNotification, parseAttachment } from '../entity_normalizer/entity_normalizer.service.js'
import 'whatwg-fetch'
import { RegistrationError, StatusCodeError } from '../errors/errors'
@@ -12,7 +12,8 @@ const CHANGE_EMAIL_URL = '/api/pleroma/change_email'
const CHANGE_PASSWORD_URL = '/api/pleroma/change_password'
const TAG_USER_URL = '/api/pleroma/admin/users/tag'
const PERMISSION_GROUP_URL = (screenName, right) => `/api/pleroma/admin/users/${screenName}/permission_group/${right}`
-const ACTIVATION_STATUS_URL = screenName => `/api/pleroma/admin/users/${screenName}/activation_status`
+const ACTIVATE_USER_URL = '/api/pleroma/admin/users/activate'
+const DEACTIVATE_USER_URL = '/api/pleroma/admin/users/deactivate'
const ADMIN_USERS_URL = '/api/pleroma/admin/users'
const SUGGESTIONS_URL = '/api/v1/suggestions'
const NOTIFICATION_SETTINGS_URL = '/api/pleroma/notification_settings'
@@ -22,7 +23,7 @@ const MFA_BACKUP_CODES_URL = '/api/pleroma/accounts/mfa/backup_codes'
const MFA_SETUP_OTP_URL = '/api/pleroma/accounts/mfa/setup/totp'
const MFA_CONFIRM_OTP_URL = '/api/pleroma/accounts/mfa/confirm/totp'
-const MFA_DISABLE_OTP_URL = '/api/pleroma/account/mfa/totp'
+const MFA_DISABLE_OTP_URL = '/api/pleroma/accounts/mfa/totp'
const MASTODON_LOGIN_URL = '/api/v1/accounts/verify_credentials'
const MASTODON_REGISTRATION_URL = '/api/v1/accounts'
@@ -71,6 +72,7 @@ const MASTODON_MUTE_CONVERSATION = id => `/api/v1/statuses/${id}/mute`
const MASTODON_UNMUTE_CONVERSATION = id => `/api/v1/statuses/${id}/unmute`
const MASTODON_SEARCH_2 = `/api/v2/search`
const MASTODON_USER_SEARCH_URL = '/api/v1/accounts/search'
+const MASTODON_STREAMING = '/api/v1/streaming'
const oldfetch = window.fetch
@@ -450,20 +452,26 @@ const deleteRight = ({ right, credentials, ...user }) => {
})
}
-const setActivationStatus = ({ status, credentials, ...user }) => {
- const screenName = user.screen_name
- const body = {
- status: status
- }
-
- const headers = authHeaders(credentials)
- headers['Content-Type'] = 'application/json'
+const activateUser = ({ credentials, user: { screen_name: nickname } }) => {
+ return promisedRequest({
+ url: ACTIVATE_USER_URL,
+ method: 'PATCH',
+ credentials,
+ payload: {
+ nicknames: [nickname]
+ }
+ }).then(response => get(response, 'users.0'))
+}
- return fetch(ACTIVATION_STATUS_URL(screenName), {
- method: 'PUT',
- headers: headers,
- body: JSON.stringify(body)
- })
+const deactivateUser = ({ credentials, user: { screen_name: nickname } }) => {
+ return promisedRequest({
+ url: DEACTIVATE_USER_URL,
+ method: 'PATCH',
+ credentials,
+ payload: {
+ nicknames: [nickname]
+ }
+ }).then(response => get(response, 'users.0'))
}
const deleteUser = ({ credentials, ...user }) => {
@@ -529,16 +537,24 @@ const fetchTimeline = ({
const queryString = map(params, (param) => `${param[0]}=${param[1]}`).join('&')
url += `?${queryString}`
-
+ let status = ''
+ let statusText = ''
return fetch(url, { headers: authHeaders(credentials) })
.then((data) => {
- if (data.ok) {
+ status = data.status
+ statusText = data.statusText
+ return data
+ })
+ .then((data) => data.json())
+ .then((data) => {
+ if (!data.error) {
+ return data.map(isNotifications ? parseNotification : parseStatus)
+ } else {
+ data.status = status
+ data.statusText = statusText
return data
}
- throw new Error('Error fetching timeline', data)
})
- .then((data) => data.json())
- .then((data) => data.map(isNotifications ? parseNotification : parseStatus))
}
const fetchPinnedStatuses = ({ id, credentials }) => {
@@ -932,6 +948,99 @@ const search2 = ({ credentials, q, resolve, limit, offset, following }) => {
})
}
+export const getMastodonSocketURI = ({ credentials, stream, args = {} }) => {
+ return Object.entries({
+ ...(credentials
+ ? { access_token: credentials }
+ : {}
+ ),
+ stream,
+ ...args
+ }).reduce((acc, [key, val]) => {
+ return acc + `${key}=${val}&`
+ }, MASTODON_STREAMING + '?')
+}
+
+const MASTODON_STREAMING_EVENTS = new Set([
+ 'update',
+ 'notification',
+ 'delete',
+ 'filters_changed'
+])
+
+// A thin wrapper around WebSocket API that allows adding a pre-processor to it
+// Uses EventTarget and a CustomEvent to proxy events
+export const ProcessedWS = ({
+ url,
+ preprocessor = handleMastoWS,
+ id = 'Unknown'
+}) => {
+ const eventTarget = new EventTarget()
+ const socket = new WebSocket(url)
+ if (!socket) throw new Error(`Failed to create socket ${id}`)
+ const proxy = (original, eventName, processor = a => a) => {
+ original.addEventListener(eventName, (eventData) => {
+ eventTarget.dispatchEvent(new CustomEvent(
+ eventName,
+ { detail: processor(eventData) }
+ ))
+ })
+ }
+ socket.addEventListener('open', (wsEvent) => {
+ console.debug(`[WS][${id}] Socket connected`, wsEvent)
+ })
+ socket.addEventListener('error', (wsEvent) => {
+ console.debug(`[WS][${id}] Socket errored`, wsEvent)
+ })
+ socket.addEventListener('close', (wsEvent) => {
+ console.debug(
+ `[WS][${id}] Socket disconnected with code ${wsEvent.code}`,
+ wsEvent
+ )
+ })
+ // Commented code reason: very spammy, uncomment to enable message debug logging
+ /*
+ socket.addEventListener('message', (wsEvent) => {
+ console.debug(
+ `[WS][${id}] Message received`,
+ wsEvent
+ )
+ })
+ /**/
+
+ proxy(socket, 'open')
+ proxy(socket, 'close')
+ proxy(socket, 'message', preprocessor)
+ proxy(socket, 'error')
+
+ // 1000 = Normal Closure
+ eventTarget.close = () => { socket.close(1000, 'Shutting down socket') }
+
+ return eventTarget
+}
+
+export const handleMastoWS = (wsEvent) => {
+ const { data } = wsEvent
+ if (!data) return
+ const parsedEvent = JSON.parse(data)
+ const { event, payload } = parsedEvent
+ if (MASTODON_STREAMING_EVENTS.has(event)) {
+ // MastoBE and PleromaBE both send payload for delete as a PLAIN string
+ if (event === 'delete') {
+ return { event, id: payload }
+ }
+ const data = payload ? JSON.parse(payload) : null
+ if (event === 'update') {
+ return { event, status: parseStatus(data) }
+ } else if (event === 'notification') {
+ return { event, notification: parseNotification(data) }
+ }
+ } else {
+ console.warn('Unknown event', wsEvent)
+ return null
+ }
+}
+
const apiService = {
verifyCredentials,
fetchTimeline,
@@ -971,7 +1080,8 @@ const apiService = {
deleteUser,
addRight,
deleteRight,
- setActivationStatus,
+ activateUser,
+ deactivateUser,
register,
getCaptcha,
updateAvatar,
diff --git a/src/services/backend_interactor_service/backend_interactor_service.js b/src/services/backend_interactor_service/backend_interactor_service.js
index c16bd1f1..b7372ed0 100644
--- a/src/services/backend_interactor_service/backend_interactor_service.js
+++ b/src/services/backend_interactor_service/backend_interactor_service.js
@@ -1,230 +1,39 @@
-import apiService from '../api/api.service.js'
+import apiService, { getMastodonSocketURI, ProcessedWS } from '../api/api.service.js'
import timelineFetcherService 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 => {
- const fetchStatus = ({ id }) => {
- return apiService.fetchStatus({ id, credentials })
- }
-
- const fetchConversation = ({ id }) => {
- return apiService.fetchConversation({ id, credentials })
- }
-
- const fetchFriends = ({ id, maxId, sinceId, limit }) => {
- return apiService.fetchFriends({ id, maxId, sinceId, limit, credentials })
- }
-
- const exportFriends = ({ id }) => {
- return apiService.exportFriends({ id, credentials })
- }
-
- const fetchFollowers = ({ id, maxId, sinceId, limit }) => {
- return apiService.fetchFollowers({ id, maxId, sinceId, limit, credentials })
- }
-
- const fetchUser = ({ id }) => {
- return apiService.fetchUser({ id, credentials })
- }
-
- const fetchUserRelationship = ({ id }) => {
- return apiService.fetchUserRelationship({ id, credentials })
- }
-
- const followUser = ({ id, reblogs }) => {
- return apiService.followUser({ credentials, id, reblogs })
- }
-
- const unfollowUser = (id) => {
- return apiService.unfollowUser({ credentials, id })
- }
-
- const blockUser = (id) => {
- return apiService.blockUser({ credentials, id })
- }
-
- const unblockUser = (id) => {
- return apiService.unblockUser({ credentials, id })
- }
-
- const approveUser = (id) => {
- return apiService.approveUser({ credentials, id })
- }
-
- const denyUser = (id) => {
- return apiService.denyUser({ credentials, id })
- }
-
- const startFetchingTimeline = ({ timeline, store, userId = false, tag }) => {
+const backendInteractorService = credentials => ({
+ startFetchingTimeline ({ timeline, store, userId = false, tag }) {
return timelineFetcherService.startFetching({ timeline, store, credentials, userId, tag })
- }
+ },
- const startFetchingNotifications = ({ store }) => {
+ startFetchingNotifications ({ store }) {
return notificationsFetcher.startFetching({ store, credentials })
- }
-
- const startFetchingFollowRequest = ({ store }) => {
- return followRequestFetcher.startFetching({ store, credentials })
- }
-
- // eslint-disable-next-line camelcase
- const tagUser = ({ screen_name }, tag) => {
- return apiService.tagUser({ screen_name, tag, credentials })
- }
+ },
- // eslint-disable-next-line camelcase
- const untagUser = ({ screen_name }, tag) => {
- return apiService.untagUser({ screen_name, tag, credentials })
- }
+ fetchAndUpdateNotifications ({ store }) {
+ return notificationsFetcher.fetchAndUpdate({ store, credentials })
+ },
- // eslint-disable-next-line camelcase
- const addRight = ({ screen_name }, right) => {
- return apiService.addRight({ screen_name, right, credentials })
- }
-
- // eslint-disable-next-line camelcase
- const deleteRight = ({ screen_name }, right) => {
- return apiService.deleteRight({ screen_name, right, credentials })
- }
-
- // eslint-disable-next-line camelcase
- const setActivationStatus = ({ screen_name }, status) => {
- return apiService.setActivationStatus({ screen_name, status, credentials })
- }
-
- // eslint-disable-next-line camelcase
- const deleteUser = ({ screen_name }) => {
- return apiService.deleteUser({ screen_name, credentials })
- }
-
- const vote = (pollId, choices) => {
- return apiService.vote({ credentials, pollId, choices })
- }
-
- const fetchPoll = (pollId) => {
- return apiService.fetchPoll({ credentials, pollId })
- }
-
- const updateNotificationSettings = ({ settings }) => {
- return apiService.updateNotificationSettings({ credentials, settings })
- }
-
- const fetchMutes = () => apiService.fetchMutes({ credentials })
- const muteUser = (id) => apiService.muteUser({ credentials, id })
- const unmuteUser = (id) => apiService.unmuteUser({ credentials, id })
- const subscribeUser = (id) => apiService.subscribeUser({ credentials, id })
- const unsubscribeUser = (id) => apiService.unsubscribeUser({ credentials, id })
- const fetchBlocks = () => apiService.fetchBlocks({ credentials })
- const fetchOAuthTokens = () => apiService.fetchOAuthTokens({ credentials })
- const revokeOAuthToken = (id) => apiService.revokeOAuthToken({ id, credentials })
- const fetchPinnedStatuses = (id) => apiService.fetchPinnedStatuses({ credentials, id })
- const pinOwnStatus = (id) => apiService.pinOwnStatus({ credentials, id })
- const unpinOwnStatus = (id) => apiService.unpinOwnStatus({ credentials, id })
- const muteConversation = (id) => apiService.muteConversation({ credentials, id })
- const unmuteConversation = (id) => apiService.unmuteConversation({ credentials, id })
-
- const getCaptcha = () => apiService.getCaptcha()
- const register = (params) => apiService.register({ credentials, params })
- const updateAvatar = ({ avatar }) => apiService.updateAvatar({ credentials, avatar })
- const updateBg = ({ background }) => apiService.updateBg({ credentials, background })
- const updateBanner = ({ banner }) => apiService.updateBanner({ credentials, banner })
- const updateProfile = ({ params }) => apiService.updateProfile({ credentials, params })
-
- const importBlocks = (file) => apiService.importBlocks({ file, credentials })
- const importFollows = (file) => apiService.importFollows({ file, credentials })
-
- const deleteAccount = ({ password }) => apiService.deleteAccount({ credentials, password })
- const changeEmail = ({ email, password }) => apiService.changeEmail({ credentials, email, password })
- const changePassword = ({ password, newPassword, newPasswordConfirmation }) =>
- apiService.changePassword({ credentials, password, newPassword, newPasswordConfirmation })
-
- const fetchSettingsMFA = () => apiService.settingsMFA({ credentials })
- const generateMfaBackupCodes = () => apiService.generateMfaBackupCodes({ credentials })
- const mfaSetupOTP = () => apiService.mfaSetupOTP({ credentials })
- const mfaConfirmOTP = ({ password, token }) => apiService.mfaConfirmOTP({ credentials, password, token })
- const mfaDisableOTP = ({ password }) => apiService.mfaDisableOTP({ credentials, password })
-
- const fetchFavoritedByUsers = (id) => apiService.fetchFavoritedByUsers({ id })
- const fetchRebloggedByUsers = (id) => apiService.fetchRebloggedByUsers({ id })
- const reportUser = (params) => apiService.reportUser({ credentials, ...params })
-
- const favorite = (id) => apiService.favorite({ id, credentials })
- const unfavorite = (id) => apiService.unfavorite({ id, credentials })
- const retweet = (id) => apiService.retweet({ id, credentials })
- const unretweet = (id) => apiService.unretweet({ id, credentials })
- const search2 = ({ q, resolve, limit, offset, following }) =>
- apiService.search2({ credentials, q, resolve, limit, offset, following })
- const searchUsers = (query) => apiService.searchUsers({ query, credentials })
-
- const backendInteractorServiceInstance = {
- fetchStatus,
- fetchConversation,
- fetchFriends,
- exportFriends,
- fetchFollowers,
- followUser,
- unfollowUser,
- blockUser,
- unblockUser,
- fetchUser,
- fetchUserRelationship,
- verifyCredentials: apiService.verifyCredentials,
- startFetchingTimeline,
- startFetchingNotifications,
- startFetchingFollowRequest,
- fetchMutes,
- muteUser,
- unmuteUser,
- subscribeUser,
- unsubscribeUser,
- fetchBlocks,
- fetchOAuthTokens,
- revokeOAuthToken,
- fetchPinnedStatuses,
- pinOwnStatus,
- unpinOwnStatus,
- muteConversation,
- unmuteConversation,
- tagUser,
- untagUser,
- addRight,
- deleteRight,
- deleteUser,
- setActivationStatus,
- register,
- getCaptcha,
- updateAvatar,
- updateBg,
- updateBanner,
- updateProfile,
- importBlocks,
- importFollows,
- deleteAccount,
- changeEmail,
- changePassword,
- fetchSettingsMFA,
- generateMfaBackupCodes,
- mfaSetupOTP,
- mfaConfirmOTP,
- mfaDisableOTP,
- approveUser,
- denyUser,
- vote,
- fetchPoll,
- fetchFavoritedByUsers,
- fetchRebloggedByUsers,
- reportUser,
- favorite,
- unfavorite,
- retweet,
- unretweet,
- updateNotificationSettings,
- search2,
- searchUsers
- }
-
- return backendInteractorServiceInstance
-}
+ startFetchingFollowRequest ({ store }) {
+ return followRequestFetcher.startFetching({ store, credentials })
+ },
+
+ startUserSocket ({ store }) {
+ const serv = store.rootState.instance.server.replace('http', 'ws')
+ const url = serv + getMastodonSocketURI({ credentials, stream: 'user' })
+ return ProcessedWS({ url, id: 'User' })
+ },
+
+ ...Object.entries(apiService).reduce((acc, [key, func]) => {
+ return {
+ ...acc,
+ [key]: (args) => func({ credentials, ...args })
+ }
+ }, {}),
+
+ verifyCredentials: apiService.verifyCredentials
+})
export default backendInteractorService
diff --git a/src/services/follow_manipulate/follow_manipulate.js b/src/services/follow_manipulate/follow_manipulate.js
index 598cb5f7..29b38a0f 100644
--- a/src/services/follow_manipulate/follow_manipulate.js
+++ b/src/services/follow_manipulate/follow_manipulate.js
@@ -39,7 +39,7 @@ export const requestFollow = (user, store) => new Promise((resolve, reject) => {
})
export const requestUnfollow = (user, store) => new Promise((resolve, reject) => {
- store.state.api.backendInteractor.unfollowUser(user.id)
+ store.state.api.backendInteractor.unfollowUser({ id: user.id })
.then((updated) => {
store.commit('updateUserRelationship', [updated])
resolve({
diff --git a/src/services/new_api/mfa.js b/src/services/new_api/mfa.js
index cbba06d5..c944667c 100644
--- a/src/services/new_api/mfa.js
+++ b/src/services/new_api/mfa.js
@@ -1,9 +1,9 @@
-const verifyOTPCode = ({ app, instance, mfaToken, code }) => {
+const verifyOTPCode = ({ clientId, clientSecret, instance, mfaToken, code }) => {
const url = `${instance}/oauth/mfa/challenge`
const form = new window.FormData()
- form.append('client_id', app.client_id)
- form.append('client_secret', app.client_secret)
+ form.append('client_id', clientId)
+ form.append('client_secret', clientSecret)
form.append('mfa_token', mfaToken)
form.append('code', code)
form.append('challenge_type', 'totp')
@@ -14,12 +14,12 @@ const verifyOTPCode = ({ app, instance, mfaToken, code }) => {
}).then((data) => data.json())
}
-const verifyRecoveryCode = ({ app, instance, mfaToken, code }) => {
+const verifyRecoveryCode = ({ clientId, clientSecret, instance, mfaToken, code }) => {
const url = `${instance}/oauth/mfa/challenge`
const form = new window.FormData()
- form.append('client_id', app.client_id)
- form.append('client_secret', app.client_secret)
+ form.append('client_id', clientId)
+ form.append('client_secret', clientSecret)
form.append('mfa_token', mfaToken)
form.append('code', code)
form.append('challenge_type', 'recovery')
diff --git a/src/services/new_api/oauth.js b/src/services/new_api/oauth.js
index d0d18c03..3c8e64bd 100644
--- a/src/services/new_api/oauth.js
+++ b/src/services/new_api/oauth.js
@@ -12,7 +12,7 @@ export const getOrCreateApp = ({ clientId, clientSecret, instance, commit }) =>
form.append('client_name', `PleromaFE_${window.___pleromafe_commit_hash}_${(new Date()).toISOString()}`)
form.append('redirect_uris', REDIRECT_URI)
- form.append('scopes', 'read write follow')
+ form.append('scopes', 'read write follow push admin')
return window.fetch(url, {
method: 'POST',
@@ -28,7 +28,7 @@ const login = ({ instance, clientId }) => {
response_type: 'code',
client_id: clientId,
redirect_uri: REDIRECT_URI,
- scope: 'read write follow'
+ scope: 'read write follow push admin'
}
const dataString = reduce(data, (acc, v, k) => {
diff --git a/src/services/timeline_fetcher/timeline_fetcher.service.js b/src/services/timeline_fetcher/timeline_fetcher.service.js
index 9eb30c2d..c6b28ad5 100644
--- a/src/services/timeline_fetcher/timeline_fetcher.service.js
+++ b/src/services/timeline_fetcher/timeline_fetcher.service.js
@@ -6,6 +6,7 @@ const update = ({ store, statuses, timeline, showImmediately, userId }) => {
const ccTimeline = camelCase(timeline)
store.dispatch('setError', { value: false })
+ store.dispatch('setErrorData', { value: null })
store.dispatch('addNewStatuses', {
timeline: ccTimeline,
@@ -45,6 +46,10 @@ const fetchAndUpdate = ({
return apiService.fetchTimeline(args)
.then((statuses) => {
+ if (statuses.error) {
+ store.dispatch('setErrorData', { value: statuses })
+ return
+ }
if (!older && statuses.length >= 20 && !timelineData.loading && numStatusesBeforeFetch > 0) {
store.dispatch('queueFlush', { timeline: timeline, id: timelineData.maxId })
}