Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F85825809
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Authored By
Unknown
Size
41 KB
Referenced Files
None
Subscribers
None
View Options
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
Details
Attached
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)
Attached To
Mode
rPUFE pleroma-fe-upstream
Attached
Detach File
Event Timeline