From 7ed9d17ce745abc38a27d4994452a136357aba46 Mon Sep 17 00:00:00 2001 From: shpuld Date: Sun, 7 Jul 2019 23:02:09 +0300 Subject: Add thread muting to context menu of status --- src/services/api/api.service.js | 14 ++++++++++++++ .../backend_interactor_service.js | 4 ++++ .../entity_normalizer/entity_normalizer.service.js | 1 + 3 files changed, 19 insertions(+) (limited to 'src/services') diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 22db671e..5bce85a3 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -65,6 +65,8 @@ const MASTODON_PROFILE_UPDATE_URL = '/api/v1/accounts/update_credentials' const MASTODON_REPORT_USER_URL = '/api/v1/reports' const MASTODON_PIN_OWN_STATUS = id => `/api/v1/statuses/${id}/pin` const MASTODON_UNPIN_OWN_STATUS = id => `/api/v1/statuses/${id}/unpin` +const MASTODON_MUTE_CONVERSATION = id => `/api/v1/statuses/${id}/mute` +const MASTODON_UNMUTE_CONVERSATION = id => `/api/v1/statuses/${id}/unmute` const oldfetch = window.fetch @@ -244,6 +246,16 @@ const unpinOwnStatus = ({ id, credentials }) => { .then((data) => parseStatus(data)) } +const muteConversation = ({ id, credentials }) => { + return promisedRequest({ url: MASTODON_MUTE_CONVERSATION(id), credentials, method: 'POST' }) + .then((data) => parseStatus(data)) +} + +const unmuteConversation = ({ id, credentials }) => { + return promisedRequest({ url: MASTODON_UNMUTE_CONVERSATION(id), credentials, method: 'POST' }) + .then((data) => parseStatus(data)) +} + const blockUser = ({ id, credentials }) => { return fetch(MASTODON_BLOCK_USER_URL(id), { headers: authHeaders(credentials), @@ -850,6 +862,8 @@ const apiService = { unfollowUser, pinOwnStatus, unpinOwnStatus, + muteConversation, + unmuteConversation, blockUser, unblockUser, fetchUser, diff --git a/src/services/backend_interactor_service/backend_interactor_service.js b/src/services/backend_interactor_service/backend_interactor_service.js index 3550aafd..46c097a3 100644 --- a/src/services/backend_interactor_service/backend_interactor_service.js +++ b/src/services/backend_interactor_service/backend_interactor_service.js @@ -115,6 +115,8 @@ const backendInteractorService = 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 }) @@ -171,6 +173,8 @@ const backendInteractorService = credentials => { fetchPinnedStatuses, pinOwnStatus, unpinOwnStatus, + muteConversation, + unmuteConversation, tagUser, untagUser, addRight, diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js index df6747a6..6a5f1408 100644 --- a/src/services/entity_normalizer/entity_normalizer.service.js +++ b/src/services/entity_normalizer/entity_normalizer.service.js @@ -239,6 +239,7 @@ export const parseStatus = (data) => { output.external_url = data.url output.poll = data.poll output.pinned = data.pinned + output.muted = data.muted } else { output.favorited = data.favorited output.fave_num = data.fave_num -- cgit v1.2.3-70-g09d2 From 876c6de8066d9ac708bb0cd8e4d4c5e60f9502a6 Mon Sep 17 00:00:00 2001 From: taehoon Date: Fri, 19 Jul 2019 22:49:29 -0400 Subject: fix typos --- src/components/user_profile/user_profile.vue | 2 +- src/modules/users.js | 6 +++--- src/services/entity_normalizer/entity_normalizer.service.js | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) (limited to 'src/services') diff --git a/src/components/user_profile/user_profile.vue b/src/components/user_profile/user_profile.vue index fc745d1a..e862440e 100644 --- a/src/components/user_profile/user_profile.vue +++ b/src/components/user_profile/user_profile.vue @@ -22,7 +22,7 @@ :timeline="timeline" timeline-name="user" :user-id="userId" - :pinned-status-ids="user.pinnedStatuseIds" + :pinned-status-ids="user.pinnedStatusIds" />
{ output.statuses_count = data.statuses_count output.friendIds = [] output.followerIds = [] - output.pinnedStatuseIds = [] + output.pinnedStatusIds = [] if (data.pleroma) { output.follow_request_count = data.pleroma.follow_request_count -- cgit v1.2.3-70-g09d2 From 4fc27414d25d719991d2368740c5ffea67af907d Mon Sep 17 00:00:00 2001 From: Lee Starnes Date: Tue, 6 Aug 2019 18:03:31 +0000 Subject: Handle JSONified errors while registering Closes #617 --- src/modules/users.js | 11 +---------- src/services/api/api.service.js | 9 ++++----- src/services/errors/errors.js | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 15 deletions(-) (limited to 'src/services') diff --git a/src/modules/users.js b/src/modules/users.js index 10def9cd..4d78aef5 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -3,7 +3,6 @@ import oauthApi from '../services/new_api/oauth.js' import { compact, map, each, merge, last, concat, uniq } from 'lodash' import { set } from 'vue' import { registerPushNotifications, unregisterPushNotifications } from '../services/push/push.js' -import { humanizeErrors } from './errors' // TODO: Unify with mergeOrAdd in statuses.js export const mergeOrAdd = (arr, obj, item) => { @@ -382,16 +381,8 @@ const users = { store.dispatch('loginUser', data.access_token) } catch (e) { let errors = e.message - // replace ap_id with username - if (typeof errors === 'object') { - if (errors.ap_id) { - errors.username = errors.ap_id - delete errors.ap_id - } - errors = humanizeErrors(errors) - } store.commit('signUpFailure', errors) - throw Error(errors) + throw e } }, async getCaptcha (store) { diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index d4ad1c4e..84616dd1 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -1,7 +1,7 @@ import { each, map, concat, last } from 'lodash' import { parseStatus, parseUser, parseNotification, parseAttachment } from '../entity_normalizer/entity_normalizer.service.js' import 'whatwg-fetch' -import { StatusCodeError } from '../errors/errors' +import { RegistrationError, StatusCodeError } from '../errors/errors' /* eslint-env browser */ const EXTERNAL_PROFILE_URL = '/api/externalprofile/show.json' @@ -199,12 +199,11 @@ const register = ({ params, credentials }) => { ...rest }) }) - .then((response) => [response.ok, response]) - .then(([ok, response]) => { - if (ok) { + .then((response) => { + if (response.ok) { return response.json() } else { - return response.json().then((error) => { throw new Error(error) }) + return response.json().then((error) => { throw new RegistrationError(error) }) } }) } diff --git a/src/services/errors/errors.js b/src/services/errors/errors.js index 548f3c68..590552da 100644 --- a/src/services/errors/errors.js +++ b/src/services/errors/errors.js @@ -1,3 +1,5 @@ +import { humanizeErrors } from '../../modules/errors' + export function StatusCodeError (statusCode, body, options, response) { this.name = 'StatusCodeError' this.statusCode = statusCode @@ -12,3 +14,36 @@ export function StatusCodeError (statusCode, body, options, response) { } StatusCodeError.prototype = Object.create(Error.prototype) StatusCodeError.prototype.constructor = StatusCodeError + +export class RegistrationError extends Error { + constructor (error) { + super() + if (Error.captureStackTrace) { + Error.captureStackTrace(this) + } + + try { + // the error is probably a JSON object with a single key, "errors", whose value is another JSON object containing the real errors + if (typeof error === 'string') { + error = JSON.parse(error) + if (error.hasOwnProperty('error')) { + error = JSON.parse(error.error) + } + } + + if (typeof error === 'object') { + // replace ap_id with username + if (error.ap_id) { + error.username = error.ap_id + delete error.ap_id + } + this.message = humanizeErrors(error) + } else { + this.message = error + } + } catch (e) { + // can't parse it, so just treat it like a string + this.message = error + } + } +} -- cgit v1.2.3-70-g09d2 From 766bcc2a72bc9e6966c0ce174d1887ac6b8cc5c4 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Fri, 9 Aug 2019 14:18:46 +0300 Subject: Fix sent follow request detection This fixes `requestFollow` using the relationship instead of user object, resulting in `sent` always being false for locked users, and also removes assumptions about follow request being sent, instead relying on `requested` from user relationship. --- .../entity_normalizer/entity_normalizer.service.js | 1 + .../follow_manipulate/follow_manipulate.js | 30 ++++++++-------------- 2 files changed, 11 insertions(+), 20 deletions(-) (limited to 'src/services') diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js index c8474302..6cc1851d 100644 --- a/src/services/entity_normalizer/entity_normalizer.service.js +++ b/src/services/entity_normalizer/entity_normalizer.service.js @@ -65,6 +65,7 @@ export const parseUser = (data) => { if (relationship) { output.follows_you = relationship.followed_by + output.requested = relationship.requested output.following = relationship.following output.statusnet_blocking = relationship.blocking output.muted = relationship.muting diff --git a/src/services/follow_manipulate/follow_manipulate.js b/src/services/follow_manipulate/follow_manipulate.js index b2486e7c..3a953ab3 100644 --- a/src/services/follow_manipulate/follow_manipulate.js +++ b/src/services/follow_manipulate/follow_manipulate.js @@ -2,17 +2,17 @@ const fetchUser = (attempt, user, store) => new Promise((resolve, reject) => { setTimeout(() => { store.state.api.backendInteractor.fetchUser({ id: user.id }) .then((user) => store.commit('addNewUsers', [user])) - .then(() => resolve([user.following, attempt])) + .then(() => resolve([user.following, user.requested, user.locked, attempt])) .catch((e) => reject(e)) }, 500) -}).then(([following, attempt]) => { - if (!following && attempt <= 3) { +}).then(([following, sent, locked, attempt]) => { + if (!following && !locked && attempt <= 3) { // If we BE reports that we still not following that user - retry, // increment attempts by one return fetchUser(++attempt, user, store) } else { // If we run out of attempts, just return whatever status is. - return following + return sent } }) @@ -21,14 +21,10 @@ export const requestFollow = (user, store) => new Promise((resolve, reject) => { .then((updated) => { store.commit('updateUserRelationship', [updated]) - // For locked users we just mark it that we sent the follow request - if (updated.locked) { - resolve({ sent: true }) - } - - if (updated.following) { - // If we get result immediately, just stop. - resolve({ sent: false }) + if (updated.following || user.locked) { + // If we get result immediately or the account is locked, just stop. + resolve({ sent: updated.requested }) + return } // But usually we don't get result immediately, so we ask server @@ -39,14 +35,8 @@ export const requestFollow = (user, store) => new Promise((resolve, reject) => { // Recursive Promise, it will call itself up to 3 times. return fetchUser(1, user, store) - .then((following) => { - if (following) { - // We confirmed and everything's good. - resolve({ sent: false }) - } else { - // If after all the tries, just treat it as if user is locked - resolve({ sent: false }) - } + .then((sent) => { + resolve({ sent: sent }) }) }) }) -- cgit v1.2.3-70-g09d2 From e83b321ff296dc7c3d8eba52a416ecc13a54e141 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Fri, 9 Aug 2019 12:01:57 +0000 Subject: Apply suggestion to src/services/follow_manipulate/follow_manipulate.js --- src/services/follow_manipulate/follow_manipulate.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/services') diff --git a/src/services/follow_manipulate/follow_manipulate.js b/src/services/follow_manipulate/follow_manipulate.js index 3a953ab3..bd8ae979 100644 --- a/src/services/follow_manipulate/follow_manipulate.js +++ b/src/services/follow_manipulate/follow_manipulate.js @@ -36,7 +36,7 @@ export const requestFollow = (user, store) => new Promise((resolve, reject) => { return fetchUser(1, user, store) .then((sent) => { - resolve({ sent: sent }) + resolve({ sent }) }) }) }) -- cgit v1.2.3-70-g09d2 From 5f3ac6625fef52adbb1cda5e1bd03863ed128580 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Fri, 9 Aug 2019 12:25:58 +0000 Subject: Apply suggestion to src/services/follow_manipulate/follow_manipulate.js --- src/services/follow_manipulate/follow_manipulate.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/services') diff --git a/src/services/follow_manipulate/follow_manipulate.js b/src/services/follow_manipulate/follow_manipulate.js index bd8ae979..26733e26 100644 --- a/src/services/follow_manipulate/follow_manipulate.js +++ b/src/services/follow_manipulate/follow_manipulate.js @@ -21,7 +21,7 @@ export const requestFollow = (user, store) => new Promise((resolve, reject) => { .then((updated) => { store.commit('updateUserRelationship', [updated]) - if (updated.following || user.locked) { + if (updated.following || (user.locked && user.requested)) { // If we get result immediately or the account is locked, just stop. resolve({ sent: updated.requested }) return -- cgit v1.2.3-70-g09d2 From 114b5f6effa3de813e047ee6b702a674e6ad07d5 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Fri, 9 Aug 2019 12:26:58 +0000 Subject: Apply suggestion to src/services/follow_manipulate/follow_manipulate.js --- src/services/follow_manipulate/follow_manipulate.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/services') diff --git a/src/services/follow_manipulate/follow_manipulate.js b/src/services/follow_manipulate/follow_manipulate.js index 26733e26..529fdb9b 100644 --- a/src/services/follow_manipulate/follow_manipulate.js +++ b/src/services/follow_manipulate/follow_manipulate.js @@ -6,7 +6,7 @@ const fetchUser = (attempt, user, store) => new Promise((resolve, reject) => { .catch((e) => reject(e)) }, 500) }).then(([following, sent, locked, attempt]) => { - if (!following && !locked && attempt <= 3) { + if (!following && !(locked && sent) && attempt <= 3) { // If we BE reports that we still not following that user - retry, // increment attempts by one return fetchUser(++attempt, user, store) -- cgit v1.2.3-70-g09d2 From 8e3d6f5c2806d683e7cf2de84ce7ba38e137d64b Mon Sep 17 00:00:00 2001 From: rinpatch Date: Tue, 3 Sep 2019 13:38:52 +0300 Subject: Use mastodon api in follow requests --- src/services/api/api.service.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) (limited to 'src/services') diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 083d4f4f..4cf41e61 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -10,9 +10,6 @@ const BLOCKS_IMPORT_URL = '/api/pleroma/blocks_import' const FOLLOW_IMPORT_URL = '/api/pleroma/follow_import' const DELETE_ACCOUNT_URL = '/api/pleroma/delete_account' const CHANGE_PASSWORD_URL = '/api/pleroma/change_password' -const FOLLOW_REQUESTS_URL = '/api/pleroma/friend_requests' -const APPROVE_USER_URL = '/api/pleroma/friendships/approve' -const DENY_USER_URL = '/api/pleroma/friendships/deny' 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` @@ -40,6 +37,9 @@ const MASTODON_FOLLOW_URL = id => `/api/v1/accounts/${id}/follow` const MASTODON_UNFOLLOW_URL = id => `/api/v1/accounts/${id}/unfollow` const MASTODON_FOLLOWING_URL = id => `/api/v1/accounts/${id}/following` const MASTODON_FOLLOWERS_URL = id => `/api/v1/accounts/${id}/followers` +const MASTODON_FOLLOW_REQUESTS_URL = '/api/v1/follow_requests' +const MASTODON_APPROVE_USER_URL = id => `/api/v1/follow_requests/${id}/authorize` +const MASTODON_DENY_USER_URL = id => `/api/v1/follow_requests/${id}/reject` const MASTODON_DIRECT_MESSAGES_TIMELINE_URL = '/api/v1/timelines/direct' const MASTODON_PUBLIC_TIMELINE = '/api/v1/timelines/public' const MASTODON_USER_HOME_TIMELINE_URL = '/api/v1/timelines/home' @@ -279,7 +279,7 @@ const unblockUser = ({ id, credentials }) => { } const approveUser = ({ id, credentials }) => { - let url = `${APPROVE_USER_URL}?user_id=${id}` + let url = MASTODON_APPROVE_USER_URL(id) return fetch(url, { headers: authHeaders(credentials), method: 'POST' @@ -287,7 +287,7 @@ const approveUser = ({ id, credentials }) => { } const denyUser = ({ id, credentials }) => { - let url = `${DENY_USER_URL}?user_id=${id}` + let url = MASTODON_DENY_USER_URL(id) return fetch(url, { headers: authHeaders(credentials), method: 'POST' @@ -363,9 +363,10 @@ const fetchFollowers = ({ id, maxId, sinceId, limit = 20, credentials }) => { } const fetchFollowRequests = ({ credentials }) => { - const url = FOLLOW_REQUESTS_URL + const url = MASTODON_FOLLOW_REQUESTS_URL return fetch(url, { headers: authHeaders(credentials) }) .then((data) => data.json()) + .then((data) => data.map(parseUser)) } const fetchConversation = ({ id, credentials }) => { -- cgit v1.2.3-70-g09d2 From 457290e81ec9a37bf848f5d166fc77bf487e834d Mon Sep 17 00:00:00 2001 From: rinpatch Date: Tue, 3 Sep 2019 19:59:28 +0300 Subject: Replace `/api/externalprofile/show.json` with a MastoAPI equialent `/api/v1/accounts/:id` supports remote nicknames since pleroma!1622 --- src/components/who_to_follow/who_to_follow.js | 2 +- src/components/who_to_follow_panel/who_to_follow_panel.js | 2 +- src/services/api/api.service.js | 10 ---------- .../backend_interactor_service/backend_interactor_service.js | 3 --- 4 files changed, 2 insertions(+), 15 deletions(-) (limited to 'src/services') diff --git a/src/components/who_to_follow/who_to_follow.js b/src/components/who_to_follow/who_to_follow.js index 8fab6c4d..1aa3a4cd 100644 --- a/src/components/who_to_follow/who_to_follow.js +++ b/src/components/who_to_follow/who_to_follow.js @@ -26,7 +26,7 @@ const WhoToFollow = { } this.users.push(user) - this.$store.state.api.backendInteractor.externalProfile(user.screen_name) + this.$store.state.api.backendInteractor.fetchUser({ id: user.screen_name }) .then((externalUser) => { if (!externalUser.error) { this.$store.commit('addNewUsers', [externalUser]) diff --git a/src/components/who_to_follow_panel/who_to_follow_panel.js b/src/components/who_to_follow_panel/who_to_follow_panel.js index 7d01678b..dcb56106 100644 --- a/src/components/who_to_follow_panel/who_to_follow_panel.js +++ b/src/components/who_to_follow_panel/who_to_follow_panel.js @@ -13,7 +13,7 @@ function showWhoToFollow (panel, reply) { toFollow.img = img toFollow.name = name - panel.$store.state.api.backendInteractor.externalProfile(name) + panel.$store.state.api.backendInteractor.fetchUser({ id: name }) .then((externalUser) => { if (!externalUser.error) { panel.$store.commit('addNewUsers', [externalUser]) diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 4cf41e61..887d7d7a 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -4,7 +4,6 @@ import 'whatwg-fetch' import { RegistrationError, StatusCodeError } from '../errors/errors' /* eslint-env browser */ -const EXTERNAL_PROFILE_URL = '/api/externalprofile/show.json' const QVITTER_USER_NOTIFICATIONS_READ_URL = '/api/qvitter/statuses/notifications/read.json' const BLOCKS_IMPORT_URL = '/api/pleroma/blocks_import' const FOLLOW_IMPORT_URL = '/api/pleroma/follow_import' @@ -220,14 +219,6 @@ const authHeaders = (accessToken) => { } } -const externalProfile = ({ profileUrl, credentials }) => { - let url = `${EXTERNAL_PROFILE_URL}?profileurl=${profileUrl}` - return fetch(url, { - headers: authHeaders(credentials), - method: 'GET' - }).then((data) => data.json()) -} - const followUser = ({ id, credentials }) => { let url = MASTODON_FOLLOW_URL(id) return fetch(url, { @@ -966,7 +957,6 @@ const apiService = { updateBg, updateProfile, updateBanner, - externalProfile, importBlocks, importFollows, deleteAccount, diff --git a/src/services/backend_interactor_service/backend_interactor_service.js b/src/services/backend_interactor_service/backend_interactor_service.js index 846d9415..3c44a10c 100644 --- a/src/services/backend_interactor_service/backend_interactor_service.js +++ b/src/services/backend_interactor_service/backend_interactor_service.js @@ -127,8 +127,6 @@ const backendInteractorService = credentials => { const updateBanner = ({ banner }) => apiService.updateBanner({ credentials, banner }) const updateProfile = ({ params }) => apiService.updateProfile({ credentials, params }) - const externalProfile = (profileUrl) => apiService.externalProfile({ profileUrl, credentials }) - const importBlocks = (file) => apiService.importBlocks({ file, credentials }) const importFollows = (file) => apiService.importFollows({ file, credentials }) @@ -194,7 +192,6 @@ const backendInteractorService = credentials => { updateBg, updateBanner, updateProfile, - externalProfile, importBlocks, importFollows, deleteAccount, -- cgit v1.2.3-70-g09d2 From 8ee5abb1a532bcfb9c70f1dad8cdeefcaf31e59c Mon Sep 17 00:00:00 2001 From: Eugenij Date: Thu, 5 Sep 2019 11:23:28 +0000 Subject: Password reset page --- src/boot/after_store.js | 1 + src/boot/routes.js | 2 + src/components/login_form/login_form.vue | 5 + src/components/password_reset/password_reset.js | 62 ++++++++++++ src/components/password_reset/password_reset.vue | 116 +++++++++++++++++++++++ src/i18n/en.json | 11 +++ src/i18n/ru.json | 11 +++ src/services/new_api/password_reset.js | 18 ++++ 8 files changed, 226 insertions(+) create mode 100644 src/components/password_reset/password_reset.js create mode 100644 src/components/password_reset/password_reset.vue create mode 100644 src/services/new_api/password_reset.js (limited to 'src/services') diff --git a/src/boot/after_store.js b/src/boot/after_store.js index 5a94194c..5cb2acba 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -246,6 +246,7 @@ const getNodeInfo = async ({ store }) => { store.dispatch('setInstanceOption', { name: 'gopherAvailable', value: features.includes('gopher') }) store.dispatch('setInstanceOption', { name: 'pollsAvailable', value: features.includes('polls') }) store.dispatch('setInstanceOption', { name: 'pollLimits', value: metadata.pollLimits }) + store.dispatch('setInstanceOption', { name: 'mailerEnabled', value: metadata.mailerEnabled }) store.dispatch('setInstanceOption', { name: 'restrictedNicknames', value: metadata.restrictedNicknames }) store.dispatch('setInstanceOption', { name: 'postFormats', value: metadata.postFormats }) diff --git a/src/boot/routes.js b/src/boot/routes.js index 7dc4b2a5..cd02711c 100644 --- a/src/boot/routes.js +++ b/src/boot/routes.js @@ -9,6 +9,7 @@ import UserProfile from 'components/user_profile/user_profile.vue' import Search from 'components/search/search.vue' import Settings from 'components/settings/settings.vue' import Registration from 'components/registration/registration.vue' +import PasswordReset from 'components/password_reset/password_reset.vue' import UserSettings from 'components/user_settings/user_settings.vue' import FollowRequests from 'components/follow_requests/follow_requests.vue' import OAuthCallback from 'components/oauth_callback/oauth_callback.vue' @@ -46,6 +47,7 @@ export default (store) => { { name: 'dms', path: '/users/:username/dms', component: DMs, beforeEnter: validateAuthenticatedRoute }, { name: 'settings', path: '/settings', component: Settings }, { name: 'registration', path: '/registration', component: Registration }, + { name: 'password-reset', path: '/password-reset', component: PasswordReset }, { name: 'registration-token', path: '/registration/:token', component: Registration }, { name: 'friend-requests', path: '/friend-requests', component: FollowRequests, beforeEnter: validateAuthenticatedRoute }, { name: 'user-settings', path: '/user-settings', component: UserSettings, beforeEnter: validateAuthenticatedRoute }, diff --git a/src/components/login_form/login_form.vue b/src/components/login_form/login_form.vue index 3ec7fe0c..b4fdcefb 100644 --- a/src/components/login_form/login_form.vue +++ b/src/components/login_form/login_form.vue @@ -33,6 +33,11 @@ type="password" >
+
+ + {{ $t('password_reset.forgot_password') }} + +
({ + user: { + email: '' + }, + isPending: false, + success: false, + throttled: false, + error: null + }), + computed: { + ...mapState({ + signedIn: (state) => !!state.users.currentUser, + instance: state => state.instance + }), + mailerEnabled () { + return this.instance.mailerEnabled + } + }, + created () { + if (this.signedIn) { + this.$router.push({ name: 'root' }) + } + }, + methods: { + dismissError () { + this.error = null + }, + submit () { + this.isPending = true + const email = this.user.email + const instance = this.instance.server + + passwordResetApi({ instance, email }).then(({ status }) => { + this.isPending = false + this.user.email = '' + + if (status === 204) { + this.success = true + this.error = null + } else if (status === 404 || status === 400) { + this.error = this.$t('password_reset.not_found') + this.$nextTick(() => { + this.$refs.email.focus() + }) + } else if (status === 429) { + this.throttled = true + this.error = this.$t('password_reset.too_many_requests') + } + }).catch(() => { + this.isPending = false + this.user.email = '' + this.error = this.$t('general.generic_error') + }) + } + } +} + +export default passwordReset diff --git a/src/components/password_reset/password_reset.vue b/src/components/password_reset/password_reset.vue new file mode 100644 index 00000000..00474e95 --- /dev/null +++ b/src/components/password_reset/password_reset.vue @@ -0,0 +1,116 @@ + + + + diff --git a/src/i18n/en.json b/src/i18n/en.json index 6a9af55c..ddde471a 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -608,5 +608,16 @@ "person_talking": "{count} person talking", "people_talking": "{count} people talking", "no_results": "No results" + }, + "password_reset": { + "forgot_password": "Forgot password?", + "password_reset": "Password reset", + "instruction": "Enter your email address or username. We will send you a link to reset your password.", + "placeholder": "Your email or username", + "check_email": "Check your email for a link to reset your password.", + "return_home": "Return to the home page", + "not_found": "We couldn't find that email or username.", + "too_many_requests": "You have reached the limit of attempts, try again later.", + "password_reset_disabled": "Password reset is disabled. Please contact your instance administrator." } } diff --git a/src/i18n/ru.json b/src/i18n/ru.json index 90ed6664..3af65f40 100644 --- a/src/i18n/ru.json +++ b/src/i18n/ru.json @@ -389,5 +389,16 @@ "person_talking": "Популярно у {count} человека", "people_talking": "Популярно у {count} человек", "no_results": "Ничего не найдено" + }, + "password_reset": { + "forgot_password": "Забыли пароль?", + "password_reset": "Сброс пароля", + "instruction": "Введите ваш email или имя пользователя, и мы отправим вам ссылку для сброса пароля.", + "placeholder": "Ваш email или имя пользователя", + "check_email": "Проверьте ваш email и перейдите по ссылке для сброса пароля.", + "return_home": "Вернуться на главную страницу", + "not_found": "Мы не смогли найти аккаунт с таким email-ом или именем пользователя.", + "too_many_requests": "Вы исчерпали допустимое количество попыток, попробуйте позже.", + "password_reset_disabled": "Сброс пароля отключен. Cвяжитесь с администратором вашего сервера." } } diff --git a/src/services/new_api/password_reset.js b/src/services/new_api/password_reset.js new file mode 100644 index 00000000..43199625 --- /dev/null +++ b/src/services/new_api/password_reset.js @@ -0,0 +1,18 @@ +import { reduce } from 'lodash' + +const MASTODON_PASSWORD_RESET_URL = `/auth/password` + +const resetPassword = ({ instance, email }) => { + const params = { email } + const query = reduce(params, (acc, v, k) => { + const encoded = `${k}=${encodeURIComponent(v)}` + return `${acc}&${encoded}` + }, '') + const url = `${instance}${MASTODON_PASSWORD_RESET_URL}?${query}` + + return window.fetch(url, { + method: 'POST' + }) +} + +export default resetPassword -- cgit v1.2.3-70-g09d2