Page MenuHomePhorge

No OneTemporary

Authored By
Unknown
Size
41 KB
Referenced Files
None
Subscribers
None
diff --git a/src/components/registration/registration.js b/src/components/registration/registration.js
index ea07c662b7..1dbc998596 100644
--- a/src/components/registration/registration.js
+++ b/src/components/registration/registration.js
@@ -1,162 +1,203 @@
import useVuelidate from '@vuelidate/core'
import { required, requiredIf, sameAs } from '@vuelidate/validators'
-import { mapState as mapPiniaState } from 'pinia'
-import { mapActions, mapState } from 'vuex'
+import { mapActions, mapState } from 'pinia'
import InterfaceLanguageSwitcher from 'src/components/interface_language_switcher/interface_language_switcher.vue'
import TermsOfServicePanel from 'src/components/terms_of_service_panel/terms_of_service_panel.vue'
import localeService from '../../services/locale/locale.service.js'
import { useInstanceStore } from 'src/stores/instance.js'
+import { useOAuthStore } from 'src/stores/oauth.js'
+import { useUsersStore } from 'src/stores/users.js'
+import { getCaptcha, register } from 'src/api/public.js'
import { DAY } from 'src/services/date_utils/date_utils.js'
const registration = {
setup() {
return { v$: useVuelidate() }
},
data: () => ({
user: {
email: '',
fullname: '',
username: '',
password: '',
confirm: '',
birthday: '',
reason: '',
language: [''],
},
+ signUpPending: false,
+ signUpErrors: [],
+ signUpNotice: {},
captcha: {},
}),
components: {
InterfaceLanguageSwitcher,
TermsOfServicePanel,
},
validations() {
return {
user: {
email: { required: requiredIf(() => this.accountActivationRequired) },
username: { required },
fullname: { required },
password: { required },
confirm: {
required,
sameAs: sameAs(this.user.password),
},
birthday: {
required: requiredIf(() => this.birthdayRequired),
maxValue: (value) => {
return (
!this.birthdayRequired ||
new Date(value).getTime() <= this.birthdayMin.getTime()
)
},
},
reason: { required: requiredIf(() => this.accountApprovalRequired) },
language: {},
},
}
},
created() {
- if ((!this.registrationOpen && !this.token) || this.signedIn) {
+ if ((!this.registrationOpen && !this.token) || this.loggedIn) {
this.$router.push({ name: 'root' })
}
this.setCaptcha()
},
computed: {
token() {
return this.$route.params.token
},
bioPlaceholder() {
return this.replaceNewlines(this.$t('registration.bio_placeholder'))
},
reasonPlaceholder() {
return this.replaceNewlines(this.$t('registration.reason_placeholder'))
},
birthdayMin() {
const minAge = this.birthdayMinAge
const today = new Date()
today.setUTCMilliseconds(0)
today.setUTCSeconds(0)
today.setUTCMinutes(0)
today.setUTCHours(0)
const minDate = new Date()
minDate.setTime(today.getTime() - minAge * DAY)
return minDate
},
birthdayMinAttr() {
return this.birthdayMin.toJSON().replace(/T.+$/, '')
},
birthdayMinFormatted() {
const browserLocale = localeService.internalToBrowserLocale(
this.$i18n.locale,
)
return (
this.user.birthday &&
new Date(Date.parse(this.birthdayMin)).toLocaleDateString(
browserLocale,
{ timeZone: 'UTC', day: 'numeric', month: 'long', year: 'numeric' },
)
)
},
- ...mapPiniaState(useInstanceStore, {
+ hasSignUpNotice(state) {
+ return this.signUpNotice.message
+ },
+ ...mapState(useInstanceStore, {
registrationOpen: (store) => store.registrationOpen,
embeddedToS: (store) => store.embeddedToS,
termsOfService: (store) => store.tos,
accountActivationRequired: (store) => store.accountActivationRequired,
accountApprovalRequired: (store) => store.accountApprovalRequired,
birthdayRequired: (store) => store.birthdayRequired,
birthdayMinAge: (store) => store.birthdayMinAge,
}),
- ...mapState({
- signedIn: (state) => !!state.users.currentUser,
- isPending: (state) => state.users.signUpPending,
- serverValidationErrors: (state) => state.users.signUpErrors,
- signUpNotice: (state) => state.users.signUpNotice,
- hasSignUpNotice: (state) => !!state.users.signUpNotice.message,
- }),
+ ...mapState(useUsersStore, ['loggedIn']),
},
methods: {
- ...mapActions(['signUp', 'getCaptcha']),
+ ...mapActions(useUsersStore, ['loginUser']),
+ getCaptcha(store) {
+ return getCaptcha({
+ credentials: useOAuthStore().token,
+ }).then(({ data }) => data)
+ },
+ async signUp(userInfo) {
+ const oauthStore = useOAuthStore()
+
+ this.signUpPending = true
+ this.signUpErrors = []
+ this.signUpNotice = {}
+
+ try {
+ const token = await oauthStore.ensureAppToken()
+ const { data } = await register({
+ credentials: token,
+ params: { ...userInfo },
+ })
+
+ if (data.access_token) {
+ this.signUpPending = false
+ oauthStore.setToken(data.access_token)
+ await this.loginUser(data.access_token)
+ return 'ok'
+ } else {
+ // Request succeeded, but user cannot login yet.
+ this.signUpErrors = []
+ this.signUpNotice = data
+ return 'request_sent'
+ }
+ } catch (e) {
+ const errors = e.message
+ this.signUpErrors = errors
+ this.signUpNotice = {}
+ throw e
+ } finally {
+ this.signUpPending = false
+ }
+ },
async submit() {
this.user.nickname = this.user.username
this.user.token = this.token
this.user.captcha_solution = this.captcha.solution
this.user.captcha_token = this.captcha.token
this.user.captcha_answer_data = this.captcha.answer_data
if (this.user.language) {
this.user.language = localeService.internalToBackendLocaleMulti(
this.user.language.filter(Boolean),
)
}
this.v$.$touch()
if (!this.v$.$invalid) {
try {
const status = await this.signUp(this.user)
if (status === 'ok') {
this.$router.push({ name: 'friends' })
}
// If status is not 'ok' (i.e. it needs further actions to be done
// before you can login), display sign up notice, do not switch anywhere
} catch (error) {
console.warn('Registration failed: ', error)
this.setCaptcha()
}
}
},
setCaptcha() {
this.getCaptcha().then((cpt) => {
this.captcha = cpt
})
},
replaceNewlines(str) {
return str.replaceAll(/\s*\n\s*/g, ' \n')
},
},
}
export default registration
diff --git a/src/components/registration/registration.vue b/src/components/registration/registration.vue
index b969a416ee..0abcfe6566 100644
--- a/src/components/registration/registration.vue
+++ b/src/components/registration/registration.vue
@@ -1,430 +1,430 @@
<template>
<div class="column-inner">
<TermsOfServicePanel v-if="!hasSignUpNotice && !embeddedToS" />
<div class="settings panel panel-default">
<div class="panel-heading">
<h1 class="title">
{{ $t('registration.registration') }}
</h1>
</div>
<div
v-if="!hasSignUpNotice"
class="panel-body"
>
<form
class="registration-form"
@submit.prevent="submit(user)"
>
<div class="container">
<div class="text-fields">
<div
class="form-group"
:class="{ 'form-group--error': v$.user.username.$error }"
>
<label
class="form--label"
for="sign-up-username"
>{{ $t('login.username') }}</label>
<input
id="sign-up-username"
v-model.trim="v$.user.username.$model"
- :disabled="isPending"
+ :disabled="signUpPending"
class="input form-control"
:aria-required="true"
:placeholder="$t('registration.username_placeholder')"
>
</div>
<div
v-if="v$.user.username.$dirty"
class="form-error"
>
<ul>
<li v-if="!v$.user.username.required">
<span>{{ $t('registration.validations.username_required') }}</span>
</li>
</ul>
</div>
<div
class="form-group"
:class="{ 'form-group--error': v$.user.fullname.$error }"
>
<label
class="form--label"
for="sign-up-fullname"
>{{ $t('registration.fullname') }}</label>
<input
id="sign-up-fullname"
v-model.trim="v$.user.fullname.$model"
- :disabled="isPending"
+ :disabled="signUpPending"
class="input form-control"
:aria-required="true"
:placeholder="$t('registration.fullname_placeholder')"
>
</div>
<div
v-if="v$.user.fullname.$dirty"
class="form-error"
>
<ul>
<li v-if="!v$.user.fullname.required">
<span>{{ $t('registration.validations.fullname_required') }}</span>
</li>
</ul>
</div>
<div
class="form-group"
:class="{ 'form-group--error': v$.user.email.$error }"
>
<label
class="form--label"
for="email"
>{{ accountActivationRequired ? $t('registration.email') : $t('registration.email_optional') }}</label>
<input
id="email"
v-model="v$.user.email.$model"
- :disabled="isPending"
+ :disabled="signUpPending"
class="input form-control"
type="email"
:aria-required="accountActivationRequired"
>
</div>
<div
v-if="v$.user.email.$dirty"
class="form-error"
>
<ul>
<li v-if="!v$.user.email.required">
<span>{{ $t('registration.validations.email_required') }}</span>
</li>
</ul>
</div>
<div class="form-group">
<label
class="form--label"
for="bio"
>{{ $t('registration.bio_optional') }}</label>
<textarea
id="bio"
v-model="user.bio"
- :disabled="isPending"
+ :disabled="signUpPending"
class="input form-control"
:placeholder="bioPlaceholder"
/>
</div>
<div
class="form-group"
:class="{ 'form-group--error': v$.user.password.$error }"
>
<label
class="form--label"
for="sign-up-password"
>{{ $t('login.password') }}</label>
<input
id="sign-up-password"
v-model="user.password"
- :disabled="isPending"
+ :disabled="signUpPending"
class="input form-control"
type="password"
:aria-required="true"
>
</div>
<div
v-if="v$.user.password.$dirty"
class="form-error"
>
<ul>
<li v-if="!v$.user.password.required">
<span>{{ $t('registration.validations.password_required') }}</span>
</li>
</ul>
</div>
<div
class="form-group"
:class="{ 'form-group--error': v$.user.confirm.$error }"
>
<label
class="form--label"
for="sign-up-password-confirmation"
>{{ $t('registration.password_confirm') }}</label>
<input
id="sign-up-password-confirmation"
v-model="user.confirm"
- :disabled="isPending"
+ :disabled="signUpPending"
class="input form-control"
type="password"
:aria-required="true"
>
</div>
<div
v-if="v$.user.confirm.$dirty"
class="form-error"
>
<ul>
<li v-if="v$.user.confirm.required.$invalid">
<span>{{ $t('registration.validations.password_confirmation_required') }}</span>
</li>
<li v-if="v$.user.confirm.sameAs.$invalid">
<span>{{ $t('registration.validations.password_confirmation_match') }}</span>
</li>
</ul>
</div>
<div
class="form-group"
:class="{ 'form-group--error': v$.user.birthday.$error }"
>
<label
class="form--label"
for="sign-up-birthday"
>
{{ birthdayRequired ? $t('registration.birthday') : $t('registration.birthday_optional') }}
</label>
<input
id="sign-up-birthday"
v-model="user.birthday"
- :disabled="isPending"
+ :disabled="signUpPending"
class="input form-control"
type="date"
:max="birthdayRequired ? birthdayMinAttr : undefined"
:aria-required="birthdayRequired"
>
</div>
<div
v-if="v$.user.birthday.$dirty"
class="form-error"
>
<ul>
<li v-if="v$.user.birthday.required.$invalid">
<span>{{ $t('registration.validations.birthday_required') }}</span>
</li>
<li v-if="v$.user.birthday.maxValue.$invalid">
<span>{{ $t('registration.validations.birthday_min_age', { date: birthdayMinFormatted }) }}</span>
</li>
</ul>
</div>
<div
class="form-group"
:class="{ 'form-group--error': v$.user.language.$error }"
>
<interface-language-switcher
for="email-language"
:prompt-text="$t('registration.email_language')"
:language="v$.user.language.$model"
:set-language="val => v$.user.language.$model = val"
@click.stop.prevent
/>
</div>
<div
v-if="accountApprovalRequired"
class="form-group"
>
<label
class="form--label"
for="reason"
>{{ $t('registration.reason') }}</label>
<textarea
id="reason"
v-model="user.reason"
- :disabled="isPending"
+ :disabled="signUpPending"
class="input form-control"
:placeholder="reasonPlaceholder"
/>
</div>
<div
v-if="captcha.type != 'none'"
id="captcha-group"
class="form-group"
>
<label
class="form--label"
for="captcha-label"
>{{ $t('registration.captcha') }}</label>
<template v-if="['kocaptcha', 'native'].includes(captcha.type)">
<img
:src="captcha.url"
@click="setCaptcha"
>
<sub>{{ $t('registration.new_captcha') }}</sub>
<input
id="captcha-answer"
v-model="captcha.solution"
- :disabled="isPending"
+ :disabled="signUpPending"
class="input form-control"
type="text"
autocomplete="off"
autocorrect="off"
autocapitalize="off"
spellcheck="false"
>
</template>
</div>
<div
v-if="token"
class="form-group"
>
<label for="token">{{ $t('registration.token') }}</label>
<input
id="token"
v-model="token"
disabled="true"
class="input form-control"
type="text"
>
</div>
<div class="form-group">
<button
- :disabled="isPending"
+ :disabled="signUpPending"
type="submit"
class="btn button-default"
>
{{ $t('registration.register') }}
</button>
</div>
</div>
<!-- eslint-disable vue/no-v-html -->
<div
v-if="embeddedToS"
class="terms-of-service"
v-html="termsOfService"
/>
<!-- eslint-enable vue/no-v-html -->
</div>
<div
v-if="serverValidationErrors.length"
class="form-group"
>
<div class="alert error">
<span
- v-for="error in serverValidationErrors"
+ v-for="error in signUpErrors"
:key="error"
>{{ error }}</span>
</div>
</div>
</form>
</div>
<div v-else>
<p class="registration-notice">
{{ signUpNotice.message }}
</p>
</div>
</div>
</div>
</template>
<script src="./registration.js"></script>
<style lang="scss">
.registration-form {
display: flex;
flex-direction: column;
margin: 1em;
.container {
display: flex;
flex-direction: row;
> * {
min-width: 0;
}
}
.terms-of-service {
flex: 0 1 50%;
margin: 0.6em 0 0 0.8em;
}
.text-fields {
margin-top: 0.6em;
flex: 1 0;
display: flex;
flex-direction: column;
}
textarea {
min-height: 100px;
resize: vertical;
}
.form-group {
display: flex;
flex-direction: column;
padding: 0.3em 0;
line-height: 2;
margin-bottom: 1em;
}
.form-group--error {
animation-name: shakeError;
animation-duration: 0.6s;
animation-timing-function: ease-in-out;
}
.form-group--error .form--label {
color: var(--cRed);
}
.form-error {
margin-top: -0.7em;
text-align: left;
span {
font-size: 0.85em;
}
}
.form-error ul {
list-style: none;
padding: 0 0 0 5px;
margin-top: 0;
li::before {
content: "• ";
}
}
form textarea {
line-height: 1;
resize: vertical;
}
.captcha {
max-width: 350px;
margin-bottom: 0.4em;
}
.btn {
margin-top: 0.6em;
height: 2em;
}
.error {
text-align: center;
}
}
.registration-notice {
margin: 0.6em;
}
@media all and (width <= 800px) {
.registration-form .container {
flex-direction: column-reverse;
.terms-of-service {
margin: 0;
}
}
}
</style>
diff --git a/src/stores/users.js b/src/stores/users.js
new file mode 100644
index 0000000000..7d12555696
--- /dev/null
+++ b/src/stores/users.js
@@ -0,0 +1,671 @@
+import Cookies from 'js-cookie'
+import { last, map } from 'lodash'
+import { defineStore } from 'pinia'
+
+import {
+ registerPushNotifications,
+ unregisterPushNotifications,
+} from '../services/sw/sw.js'
+import {
+ windowHeight,
+ windowWidth,
+} from '../services/window_utils/window_utils'
+
+import { useAnnouncementsStore } from 'src/stores/announcements.js'
+import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders.js'
+import { useChatsStore } from 'src/stores/chats.js'
+import { useEmojiStore } from 'src/stores/emoji.js'
+import { useInstanceStore } from 'src/stores/instance.js'
+import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
+import { useInterfaceStore } from 'src/stores/interface.js'
+import { useListsStore } from 'src/stores/lists.js'
+import { useMergedConfigStore } from 'src/stores/merged_config.js'
+import { useOAuthStore } from 'src/stores/oauth.js'
+import { useSyncConfigStore } from 'src/stores/sync_config.js'
+import { useUserHighlightStore } from 'src/stores/user_highlight.js'
+
+import { revokeToken } from 'src/api/oauth.js'
+import {
+ fetchFollowers,
+ fetchFriends,
+ fetchUser,
+ fetchUserByName,
+ searchUsers,
+ verifyCredentials,
+} from 'src/api/public.js'
+import {
+ blockUser,
+ editUserNote,
+ fetchBlocks,
+ fetchDomainMutes,
+ fetchMutes,
+ fetchUserInLists,
+ fetchUserRelationship,
+ followUser,
+ muteDomain,
+ muteUser,
+ removeUserFromFollowers,
+ unblockUser,
+ unmuteDomain,
+ unmuteUser,
+} from 'src/api/user.js'
+
+const getNotificationPermission = async () => {
+ const Notification = window.Notification
+
+ if (!Notification) return null
+ if (Notification.permission === 'default')
+ return Notification.requestPermission()
+ return Notification.permission
+}
+
+export const defaultState = {
+ loggingIn: false,
+ lastLoginName: null,
+ currentUser: null,
+ users: new Map(),
+ usersByName: new Map(),
+ usersByURL: new Map(),
+ relationships: new Map(),
+}
+
+export const useUsersStore = defineStore('users', {
+ state: defaultState,
+ getters: {
+ loggedIn: (state) => !!state.currentUser,
+ findUser: (state) => (query) => {
+ return state.users.get(query)
+ },
+ findUserByName: (state) => (query) => {
+ return state.usersByName.get(query.toLowerCase())
+ },
+ findUserByUrl: (state) => (query) => {
+ return state.usersByURL.get(query.toLowerCase())
+ },
+ relationship: (state) => (id) => {
+ const rel = id && state.relationships.get(id)
+ return rel || { id, loading: true }
+ },
+ },
+ actions: {
+ tagUser({ user: { id }, tag }) {
+ const user = this.users.get(id)
+ user.tags.add(tag)
+ },
+ untagUser({ user: { id }, tag }) {
+ const user = this.users.get(id)
+ user.tags.delete(tag)
+ },
+ updateRight({ user: { id }, right, value }) {
+ const user = this.users.get(id)
+ const newRights = user.rights
+ newRights[right] = value
+ user.rights = newRights
+ },
+ async updateUserAdminData({ user }) {
+ const localUser = await this.fetchUserIfMissing(user.id)
+
+ localUser.adminData = user
+ localUser.deactivated = !user.is_active
+ localUser.tags = new Set(user.tags)
+ },
+ setCurrentUser(user) {
+ this.lastLoginName = user.screen_name
+ this.currentUser = user
+ },
+ clearCurrentUser() {
+ this.currentUser = null
+ this.lastLoginName = null
+ },
+ saveFriendIds({ id, friendIds }) {
+ const user = this.users.get(id)
+ user.friendIds = [...new Set([...(user.friendIds || []), ...friendIds])]
+ },
+ saveFollowerIds({ id, followerIds }) {
+ const user = this.users.get(id)
+ user.followerIds = [...new Set([user.followerIds || [], ...followerIds])]
+ },
+ // Because frontend doesn't have a reason to keep these stuff in memory
+ // outside of viewing someones user profile.
+ clearFriends(userId) {
+ const user = this.users.get(userId)
+ if (user) {
+ user.friendIds = []
+ }
+ },
+ clearFollowers(userId) {
+ const user = this.users.get(userId)
+ if (user) {
+ user.followerIds = []
+ }
+ },
+ addNewUsers(users, timestamp) {
+ users.forEach((user) => {
+ const existing = users.get(user.id) ?? {}
+
+ const { relationship, ...old } = existing
+ const { relationshop, ...neu } = user
+ const newUser = { ...old, ...neu }
+
+ this.users.set(user.id, newUser)
+ this.usersByName.set(user.screen_name.toLowerCase(), newUser)
+ this.usersByURL.set(user.url.toLowerCase(), newUser)
+ })
+ },
+ updateUserRelationship(relationships) {
+ relationships.forEach((relationship) => {
+ this.relationships[relationship.id] = relationship
+ })
+ },
+ updateUserInLists({ id, inLists }) {
+ this.users.get(id).inLists = inLists
+ },
+ saveBlockIds(blockIds) {
+ this.currentUser.blockIds = blockIds
+ },
+ addBlockId(blockId) {
+ if (this.currentUser.blockIds.includes(blockId)) {
+ this.currentUser.blockIds.push(blockId)
+ }
+ },
+ setBlockIdsMaxId(blockIdsMaxId) {
+ this.currentUser.blockIdsMaxId = blockIdsMaxId
+ },
+ saveMuteIds(muteIds) {
+ this.currentUser.muteIds = muteIds
+ },
+ setMuteIdsMaxId(muteIdsMaxId) {
+ this.currentUser.muteIdsMaxId = muteIdsMaxId
+ },
+ addMuteId(muteId) {
+ if (this.currentUser.muteIds.includes(muteId)) {
+ this.currentUser.muteIds.push(muteId)
+ }
+ },
+ saveDomainMutes(domainMutes) {
+ this.currentUser.domainMutes = domainMutes
+ },
+ addDomainMute(domain) {
+ if (this.currentUser.domainMutes.includes(domain)) {
+ this.currentUser.domainMutes.push(domain)
+ }
+ },
+ removeDomainMute(domain) {
+ const index = this.currentUser.domainMutes.indexOf(domain)
+ if (index !== -1) {
+ this.currentUser.domainMutes.splice(index, 1)
+ }
+ },
+ setPinnedToUser(status) {
+ const user = this.users.get(status.user.id)
+ user.pinnedStatusIds = user.pinnedStatusIds || []
+ const index = user.pinnedStatusIds.indexOf(status.id)
+
+ if (status.pinned && index === -1) {
+ user.pinnedStatusIds.push(status.id)
+ } else if (!status.pinned && index !== -1) {
+ user.pinnedStatusIds.splice(index, 1)
+ }
+ },
+ setUserForStatus(status) {
+ status.user = this.users.get(status.user.id)
+ },
+ setUserForNotification(notification) {
+ if (notification.type !== 'follow') {
+ notification.action.user = this.users.get(notification.action.user.id)
+ }
+ notification.from_profile = this.users.get(notification.from_profile.id)
+ },
+ async fetchUserIfMissing(id) {
+ const user = this.findUser(id)
+ if (!user) {
+ return this.fetchUser(id)
+ } else {
+ return user
+ }
+ },
+ fetchUser(id) {
+ return fetchUser({
+ id,
+ credentials: useOAuthStore().token,
+ })
+ .then(({ data: user }) => {
+ this.addNewUsers([user])
+ return user
+ })
+ .catch((error) => {
+ if (error.statusCode === 404) {
+ console.warn(`User ${id} not found`)
+ } else {
+ throw error
+ }
+ })
+ },
+ fetchUserByName(name) {
+ return fetchUserByName({
+ name,
+ credentials: useOAuthStore().token,
+ }).then(({ data: user }) => {
+ this.addNewUsers([user])
+ return user
+ })
+ },
+ fetchUserRelationship(id) {
+ if (this.currentUser) {
+ fetchUserRelationship({
+ id,
+ credentials: useOAuthStore().token,
+ }).then(({ data: relationships }) =>
+ this.updateUserRelationship(relationships),
+ )
+ }
+ },
+ fetchUserInLists(id) {
+ if (this.currentUser) {
+ fetchUserInLists({
+ id,
+ credentials: useOAuthStore().token,
+ }).then(({ data: inLists }) => this.updateUserInLists({ id, inLists }))
+ }
+ },
+ fetchBlocks(args) {
+ const { reset } = args || {}
+
+ const maxId = this.currentUser.blockIdsMaxId
+ return fetchBlocks({
+ maxId,
+ credentials: useOAuthStore().token,
+ }).then(({ data: blocks }) => {
+ if (reset) {
+ this.saveBlockIds(blocks.map(({ id }) => id))
+ } else {
+ blocks.forEach(({ id }) => this.addBlockId(id))
+ }
+ if (blocks.length) {
+ this.setBlockIdsMaxId(last(blocks).id)
+ }
+ this.addNewUsers(blocks)
+ return blocks
+ })
+ },
+ blockUser(id, expiresIn = 0) {
+ const store = window.vuex
+
+ const predictedRelationship = this.relationships[id] || { id }
+ this.updateUserRelationship([predictedRelationship])
+ this.addBlockId(id)
+
+ return blockUser({ id, expiresIn }).then(({ data: relationship }) => {
+ this.updateUserRelationship([relationship])
+ this.addBlockId(id)
+
+ store.commit('removeStatus', { timeline: 'friends', userId: id })
+ store.commit('removeStatus', { timeline: 'public', userId: id })
+ store.commit('removeStatus', {
+ timeline: 'publicAndExternal',
+ userId: id,
+ })
+ })
+ },
+ unblockUser(id) {
+ return unblockUser({ id }).then(({ data: relationship }) =>
+ this.updateUserRelationship([relationship]),
+ )
+ },
+ removeUserFromFollowers(id) {
+ return removeUserFromFollowers({ id }).then((relationship) =>
+ this.updateUserRelationship([relationship]),
+ )
+ },
+ blockUsers(data = []) {
+ return Promise.all(data.map((d) => this.blockUser(d)))
+ },
+ unblockUsers(data = []) {
+ return Promise.all(data.map((d) => unblockUser(d)))
+ },
+ editUserNote({ id, comment }) {
+ return editUserNote({ id, comment }).then((relationship) =>
+ this.updateUserRelationship([relationship]),
+ )
+ },
+ fetchMutes(args) {
+ const { reset } = args || {}
+
+ const maxId = this.currentUser.muteIdsMaxId
+ return fetchMutes({
+ maxId,
+ credentials: useOAuthStore().token,
+ }).then(({ data: mutes }) => {
+ if (reset) {
+ this.saveMuteIds(mutes.map(({ id }) => id))
+ } else {
+ mutes.forEach(({ id }) => this.addMuteId(id))
+ }
+ if (mutes.length) {
+ this.setMuteIdsMaxId(last(mutes).id)
+ }
+ this.addNewUsers(mutes)
+ return mutes
+ })
+ },
+ muteUser(id, expiresIn = 0) {
+ const predictedRelationship = this.relationships[id] || { id }
+ this.updateUserRelationship([predictedRelationship])
+ this.addMuteId(id)
+
+ return muteUser({
+ id,
+ expiresIn,
+ credentials: useOAuthStore().token,
+ }).then(({ data: relationship }) => {
+ this.updateUserRelationship([relationship])
+ this.addMuteId(id)
+ })
+ },
+ unmuteUser(id) {
+ const predictedRelationship = this.relationships[id] || { id }
+ predictedRelationship.muting = false
+ this.updateUserRelationship([predictedRelationship])
+
+ return unmuteUser({ id }).then(({ data: relationship }) =>
+ this.updateUserRelationship([relationship]),
+ )
+ },
+ hideReblogs(id) {
+ return followUser({
+ id,
+ reblogs: false,
+ credentials: useOAuthStore().token,
+ }).then(({ data: relationship }) =>
+ this.updateUserRelationship([relationship]),
+ )
+ },
+ showReblogs(id) {
+ return followUser({
+ id,
+ reblogs: true,
+ credentials: useOAuthStore().token,
+ }).then(({ data: relationship }) =>
+ this.updateUserRelationship([relationship]),
+ )
+ },
+ muteUsers(data = []) {
+ return Promise.all(data.map((d) => this.muteUser(d)))
+ },
+ unmuteUsers(ids = []) {
+ return Promise.all(ids.map((d) => this.unmuteUser(d)))
+ },
+ fetchDomainMutes() {
+ return fetchDomainMutes({
+ credentials: useOAuthStore().token,
+ }).then(({ data: domainMutes }) => {
+ this.saveDomainMutes(domainMutes)
+ return domainMutes
+ })
+ },
+ muteDomain(domain) {
+ return muteDomain({
+ domain,
+ credentials: useOAuthStore().token,
+ }).then(() => this.addDomainMute(domain))
+ },
+ unmuteDomain(domain) {
+ return unmuteDomain({
+ domain,
+ credentials: useOAuthStore().token,
+ }).then(() => this.removeDomainMute(domain))
+ },
+ muteDomains(domains = []) {
+ return Promise.all(domains.map((domain) => this.muteDomain(domain)))
+ },
+ unmuteDomains(domain = []) {
+ return Promise.all(domain.map((domain) => this.unmuteDomain(domain)))
+ },
+ fetchFriends(id) {
+ const user = this.users.get(id)
+ const maxId = last(user.friendIds)
+ return fetchFriends({
+ id,
+ maxId,
+ credentials: useOAuthStore().token,
+ }).then(({ data: friends }) => {
+ this.addNewUsers(friends)
+ this.saveFriendIds({ id, friendIds: map(friends, 'id') })
+ return friends
+ })
+ },
+ fetchFollowers(id) {
+ const user = this.users.get(id)
+ const maxId = last(user.followerIds)
+ return fetchFollowers({
+ id,
+ maxId,
+ credentials: useOAuthStore().token,
+ }).then(({ data: followers }) => {
+ this.addNewUsers(followers)
+ this.saveFollowerIds({ id, followerIds: map(followers, 'id') })
+ return followers
+ })
+ },
+ subscribeUser(id) {
+ return followUser({
+ id,
+ notify: true,
+ credentials: useOAuthStore().token,
+ }).then(({ data: relationship }) =>
+ this.updateUserRelationship([relationship]),
+ )
+ },
+ unsubscribeUser(id) {
+ return followUser({
+ id,
+ notify: false,
+ credentials: useOAuthStore().token,
+ }).then(({ data: relationship }) =>
+ this.updateUserRelationship([relationship]),
+ )
+ },
+ registerPushNotifications() {
+ const token = this.currentUser.credentials
+ const vapidPublicKey = useInstanceStore().vapidPublicKey
+ const isEnabled = useMergedConfigStore().mergedConfig.webPushNotifications
+ const notificationVisibility =
+ useMergedConfigStore().mergedConfig.notificationVisibility
+
+ registerPushNotifications(
+ isEnabled,
+ vapidPublicKey,
+ token,
+ notificationVisibility,
+ )
+ },
+ unregisterPushNotifications() {
+ const token = this.currentUser.credentials
+
+ unregisterPushNotifications(token)
+ },
+ searchUsers({ query }) {
+ return searchUsers({
+ query,
+ credentials: useOAuthStore().token,
+ }).then(({ data: users }) => {
+ this.addNewUsers(users)
+ return users
+ })
+ },
+
+ logout() {
+ const store = window.vuex
+ const oauth = useOAuthStore()
+
+ // NOTE: No need to verify the app still exists, because if it doesn't,
+ // the token will be invalid too
+ return oauth
+ .ensureApp()
+ .then((app) => {
+ const params = {
+ app,
+ instance: useInstanceStore().server,
+ token: oauth.userToken,
+ }
+
+ return revokeToken(params)
+ })
+ .then(() => {
+ this.clearCurrentUser()
+ store.dispatch('disconnectFromSocket')
+ store.dispatch('stopFetchingTimeline', 'friends')
+ store.dispatch('stopFetchingNotifications')
+ useListsStore().stopFetching()
+ useBookmarkFoldersStore().stopFetching()
+ store.dispatch('stopFetchingFollowRequests')
+ store.commit('clearNotifications')
+ store.commit('resetStatuses')
+ useChatsStore().resetChats()
+ oauth.clearToken()
+ Cookies.remove('__Host-pleroma_key', { path: '/' })
+ useInterfaceStore().setLastTimeline('public-timeline')
+ useInterfaceStore().setLayoutWidth(windowWidth())
+ useInterfaceStore().setLayoutHeight(windowHeight())
+ })
+ },
+ loginUser(accessToken) {
+ return new Promise((resolve, reject) => {
+ const store = window.vuex
+ const dispatch = store.dispatch
+
+ this.loggingIn = true
+
+ verifyCredentials({
+ credentials: useOAuthStore().token,
+ })
+ .then(({ data: user }) => {
+ // user.credentials = userCredentials
+ user.credentials = accessToken
+ user.blockIds = []
+ user.muteIds = []
+ user.domainMutes = []
+ this.setCurrentUser(user)
+
+ useSyncConfigStore()
+ .initSyncConfig(user)
+ .then(() => {
+ useInterfaceStore()
+ .applyTheme()
+ .catch((e) => {
+ console.error('Error setting theme', e)
+ })
+ })
+ useUserHighlightStore().initUserHighlight(user)
+ this.addNewUsers([user])
+
+ useEmojiStore().fetchEmoji()
+
+ getNotificationPermission().then((permission) =>
+ useInterfaceStore().setNotificationPermission(permission),
+ )
+
+ // Do server-side storage migrations
+
+ // Debug snippet to clean up storage and reset migrations
+ /*
+ // Reset wordfilter
+ Object.keys(
+ useSyncConfigStore().prefsStorage.simple.muteFilters
+ ).forEach(key => {
+ useSyncConfigStore().unsetSimplePrefAndSave({ path: 'muteFilters.' + key, value: null })
+ })
+
+ // Reset flag to 0 to re-run migrations
+ useSyncConfigStore().setFlag({ flag: 'configMigration', value: 0 })
+ /**/
+
+ if (user.token) {
+ dispatch('setWsToken', user.token)
+
+ // Initialize the shout socket.
+ dispatch('initializeSocket')
+ }
+
+ const startPolling = () => {
+ // Start getting fresh posts.
+ dispatch('startFetchingTimeline', { timeline: 'friends' })
+
+ // Start fetching notifications
+ dispatch('startFetchingNotifications')
+
+ if (useInstanceCapabilitiesStore().pleromaChatMessagesAvailable) {
+ // Start fetching chats
+ dispatch('startFetchingChats')
+ }
+ }
+
+ useListsStore().startFetching()
+ useBookmarkFoldersStore().startFetching()
+
+ if (user.locked) {
+ dispatch('startFetchingFollowRequests')
+ }
+
+ if (useMergedConfigStore().mergedConfig.useStreamingApi) {
+ dispatch('fetchTimeline', {
+ timeline: 'friends',
+ sinceId: null,
+ })
+ dispatch('fetchNotifications', { sinceId: null })
+ dispatch('enableMastoSockets', true)
+ .catch((error) => {
+ console.error(
+ 'Failed initializing MastoAPI Streaming socket',
+ error,
+ )
+ })
+ .then(() => {
+ dispatch('fetchChats', { latest: true })
+ setTimeout(
+ () => dispatch('setNotificationsSilence', false),
+ 10000,
+ )
+ })
+ } else {
+ startPolling()
+ }
+
+ // Start fetching things that don't need to block the UI
+ useAnnouncementsStore().startFetchingAnnouncements()
+
+ this.fetchMutes()
+ dispatch('loadDrafts')
+
+ useInterfaceStore().setLayoutWidth(windowWidth())
+ useInterfaceStore().setLayoutHeight(windowHeight())
+
+ // Fetch our friends
+ fetchFriends({ id: user.id }).then(({ data: friends }) =>
+ this.addNewUsers(friends),
+ )
+ this.loggingIn = false
+ resolve()
+ })
+ .catch((error) => {
+ console.error(error)
+
+ // Authentication failed
+ this.loggingIn = false
+
+ // remove authentication token on client/authentication errors
+ if ([400, 401, 403, 422].includes(error.statusCode)) {
+ useOAuthStore().clearToken()
+ }
+
+ this.loggingIn = false
+ if (error.tatusCode === 401) {
+ throw new Error('Wrong username or password', error)
+ } else {
+ throw new Error('An error occurred, please try again', error)
+ }
+ })
+ })
+ },
+ },
+})

File Metadata

Mime Type
text/x-diff
Expires
Fri, Oct 30, 3:10 AM (1 d, 5 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1799302
Default Alt Text
(41 KB)

Event Timeline