From 642b0be0b2c82e7c9c1bb45c50e60b2d3b4090f8 Mon Sep 17 00:00:00 2001 From: Edijs Date: Sun, 17 Feb 2019 14:31:13 -0700 Subject: [PATCH 01/44] Bind a keyboard to show new status --- src/components/timeline/timeline.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/timeline/timeline.js b/src/components/timeline/timeline.js index 85e0a055..a8a3495a 100644 --- a/src/components/timeline/timeline.js +++ b/src/components/timeline/timeline.js @@ -67,6 +67,9 @@ const Timeline = { document.addEventListener('visibilitychange', this.handleVisibilityChange, false) this.unfocused = document.hidden } + window.addEventListener('keydown', e => { + if (e.key === '.') this.showNewStatuses() + }) }, destroyed () { window.removeEventListener('scroll', this.scrollLoad) From 0d2922afb3bcfcdcc439f158d147db1f08d995c7 Mon Sep 17 00:00:00 2001 From: Edijs Date: Sun, 17 Feb 2019 14:37:24 -0700 Subject: [PATCH 02/44] Remove event listener when component destroy --- src/components/timeline/timeline.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/timeline/timeline.js b/src/components/timeline/timeline.js index a8a3495a..d7f7029e 100644 --- a/src/components/timeline/timeline.js +++ b/src/components/timeline/timeline.js @@ -67,16 +67,18 @@ const Timeline = { document.addEventListener('visibilitychange', this.handleVisibilityChange, false) this.unfocused = document.hidden } - window.addEventListener('keydown', e => { - if (e.key === '.') this.showNewStatuses() - }) + window.addEventListener('keydown', this.handleShortKey) }, destroyed () { window.removeEventListener('scroll', this.scrollLoad) + window.removeEventListener('keydown', this.handleShortKey) if (typeof document.hidden !== 'undefined') document.removeEventListener('visibilitychange', this.handleVisibilityChange, false) this.$store.commit('setLoading', { timeline: this.timelineName, value: false }) }, methods: { + handleShortKey (e) { + if (e.key === '.') this.showNewStatuses() + }, showNewStatuses () { if (this.timeline.flushMarker !== 0) { this.$store.commit('clearTimeline', { timeline: this.timelineName }) From d032ce2758cb57ea34e1fea2d1804656965b635e Mon Sep 17 00:00:00 2001 From: Edijs Date: Mon, 18 Feb 2019 18:32:20 -0700 Subject: [PATCH 03/44] Validate if there are any new status to show --- src/components/timeline/timeline.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/timeline/timeline.js b/src/components/timeline/timeline.js index d7f7029e..06832898 100644 --- a/src/components/timeline/timeline.js +++ b/src/components/timeline/timeline.js @@ -80,6 +80,8 @@ const Timeline = { if (e.key === '.') this.showNewStatuses() }, showNewStatuses () { + if (this.newStatusCount === 0) return + if (this.timeline.flushMarker !== 0) { this.$store.commit('clearTimeline', { timeline: this.timelineName }) this.$store.commit('queueFlush', { timeline: this.timelineName, id: 0 }) From e687b58091bcedb6f3a56d94030fa312e51830d5 Mon Sep 17 00:00:00 2001 From: taehoon Date: Thu, 21 Feb 2019 13:32:47 -0500 Subject: [PATCH 04/44] Show error message when visit profile page of invalid user --- src/components/user_profile/user_profile.js | 8 ++++++++ src/components/user_profile/user_profile.vue | 3 ++- src/i18n/en.json | 3 ++- src/modules/users.js | 2 +- src/services/api/api.service.js | 6 ++++++ 5 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/components/user_profile/user_profile.js b/src/components/user_profile/user_profile.js index 37179ce1..44192e9a 100644 --- a/src/components/user_profile/user_profile.js +++ b/src/components/user_profile/user_profile.js @@ -4,6 +4,11 @@ import Timeline from '../timeline/timeline.vue' import FollowList from '../follow_list/follow_list.vue' const UserProfile = { + data () { + return { + error: false + } + }, created () { this.$store.commit('clearTimeline', { timeline: 'user' }) this.$store.commit('clearTimeline', { timeline: 'favorites' }) @@ -13,6 +18,9 @@ const UserProfile = { this.startFetchFavorites() if (!this.user.id) { this.$store.dispatch('fetchUser', this.fetchBy) + .catch(() => { + this.error = true + }) } }, destroyed () { diff --git a/src/components/user_profile/user_profile.vue b/src/components/user_profile/user_profile.vue index 09fb93de..ccebe20b 100644 --- a/src/components/user_profile/user_profile.vue +++ b/src/components/user_profile/user_profile.vue @@ -55,7 +55,8 @@
- + {{ $t('user_profile.profile_does_not_exist') }} +
diff --git a/src/i18n/en.json b/src/i18n/en.json index 64753f1d..c482ecb6 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -383,7 +383,8 @@ "mute_progress": "Muting..." }, "user_profile": { - "timeline_title": "User Timeline" + "timeline_title": "User Timeline", + "profile_does_not_exist": "Sorry, this profile does not exist." }, "who_to_follow": { "more": "More", diff --git a/src/modules/users.js b/src/modules/users.js index 77df7168..eabfe5ae 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -140,7 +140,7 @@ const users = { getters, actions: { fetchUser (store, id) { - store.rootState.api.backendInteractor.fetchUser({ id }) + return store.rootState.api.backendInteractor.fetchUser({ id }) .then((user) => store.commit('addNewUsers', [user])) }, fetchBlocks (store) { diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 3d2e8823..d8716596 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -244,6 +244,12 @@ const denyUser = ({id, credentials}) => { const fetchUser = ({id, credentials}) => { let url = `${USER_URL}?user_id=${id}` return fetch(url, { headers: authHeaders(credentials) }) + .then((data) => { + if (!data.ok) { + throw Error(data.statusText) + } + return data + }) .then((data) => data.json()) .then((data) => parseUser(data)) } From b78227456ea6b1a80cd85988d3ef91cb654a881c Mon Sep 17 00:00:00 2001 From: taehoon Date: Tue, 26 Feb 2019 12:26:04 -0500 Subject: [PATCH 05/44] Better error handling --- src/components/user_profile/user_profile.js | 12 ++++++++++-- src/components/user_profile/user_profile.vue | 2 +- src/i18n/en.json | 3 ++- src/services/api/api.service.js | 15 +++++++++------ src/services/errors/errors.js | 14 ++++++++++++++ 5 files changed, 36 insertions(+), 10 deletions(-) create mode 100644 src/services/errors/errors.js diff --git a/src/components/user_profile/user_profile.js b/src/components/user_profile/user_profile.js index 44192e9a..ebf6c61a 100644 --- a/src/components/user_profile/user_profile.js +++ b/src/components/user_profile/user_profile.js @@ -1,3 +1,4 @@ +import get from 'lodash/get' import UserCardContent from '../user_card_content/user_card_content.vue' import UserCard from '../user_card/user_card.vue' import Timeline from '../timeline/timeline.vue' @@ -18,8 +19,15 @@ const UserProfile = { this.startFetchFavorites() if (!this.user.id) { this.$store.dispatch('fetchUser', this.fetchBy) - .catch(() => { - this.error = true + .catch((reason) => { + const errorMessage = get(reason, 'error.error') + if (errorMessage === 'No user with such user_id') { // Known error + this.error = this.$t('user_profile.profile_does_not_exist') + } else if (errorMessage) { + this.error = errorMessage + } else { + this.error = this.$t('user_profile.profile_loading_error') + } }) } }, diff --git a/src/components/user_profile/user_profile.vue b/src/components/user_profile/user_profile.vue index ccebe20b..ba1a7760 100644 --- a/src/components/user_profile/user_profile.vue +++ b/src/components/user_profile/user_profile.vue @@ -55,7 +55,7 @@
- {{ $t('user_profile.profile_does_not_exist') }} + {{ error }}
diff --git a/src/i18n/en.json b/src/i18n/en.json index c482ecb6..9fdd1692 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -384,7 +384,8 @@ }, "user_profile": { "timeline_title": "User Timeline", - "profile_does_not_exist": "Sorry, this profile does not exist." + "profile_does_not_exist": "Sorry, this profile does not exist.", + "profile_loading_error": "Sorry, there was an error loading this profile." }, "who_to_follow": { "more": "More", diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index d8716596..fbe5afa0 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -47,6 +47,7 @@ const MASTODON_USER_FAVORITES_TIMELINE_URL = '/api/v1/favourites' import { each, map } from 'lodash' import { parseStatus, parseUser, parseNotification } from '../entity_normalizer/entity_normalizer.service.js' import 'whatwg-fetch' +import { StatusCodeError } from '../errors/errors' const oldfetch = window.fetch @@ -244,13 +245,15 @@ const denyUser = ({id, credentials}) => { const fetchUser = ({id, credentials}) => { let url = `${USER_URL}?user_id=${id}` return fetch(url, { headers: authHeaders(credentials) }) - .then((data) => { - if (!data.ok) { - throw Error(data.statusText) - } - return data + .then((response) => { + return new Promise((resolve, reject) => response.json() + .then((json) => { + if (!response.ok) { + return reject(new StatusCodeError(response.status, json, { url }, response)) + } + return resolve(json) + })) }) - .then((data) => data.json()) .then((data) => parseUser(data)) } diff --git a/src/services/errors/errors.js b/src/services/errors/errors.js new file mode 100644 index 00000000..548f3c68 --- /dev/null +++ b/src/services/errors/errors.js @@ -0,0 +1,14 @@ +export function StatusCodeError (statusCode, body, options, response) { + this.name = 'StatusCodeError' + this.statusCode = statusCode + this.message = statusCode + ' - ' + (JSON && JSON.stringify ? JSON.stringify(body) : body) + this.error = body // legacy attribute + this.options = options + this.response = response + + if (Error.captureStackTrace) { // required for non-V8 environments + Error.captureStackTrace(this) + } +} +StatusCodeError.prototype = Object.create(Error.prototype) +StatusCodeError.prototype.constructor = StatusCodeError From 95fb768b5a1200cd3e4317c54293b46aea9c8655 Mon Sep 17 00:00:00 2001 From: Shpuld Shpludson Date: Wed, 27 Feb 2019 14:38:58 +0000 Subject: [PATCH 06/44] Fix #399 Make max attachments configurable --- src/components/settings/settings.js | 5 +++++ src/components/settings/settings.vue | 8 ++++++++ src/components/status/status.js | 8 +++++--- src/i18n/en.json | 1 + src/i18n/fi.json | 1 + src/modules/config.js | 1 + 6 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/components/settings/settings.js b/src/components/settings/settings.js index 23c1acdb..6e2dff7b 100644 --- a/src/components/settings/settings.js +++ b/src/components/settings/settings.js @@ -12,6 +12,7 @@ const settings = { return { hideAttachmentsLocal: user.hideAttachments, hideAttachmentsInConvLocal: user.hideAttachmentsInConv, + maxThumbnails: user.maxThumbnails, hideNsfwLocal: user.hideNsfw, useOneClickNsfw: user.useOneClickNsfw, hideISPLocal: user.hideISP, @@ -186,6 +187,10 @@ const settings = { }, useContainFit (value) { this.$store.dispatch('setOption', { name: 'useContainFit', value }) + }, + maxThumbnails (value) { + value = this.maxThumbnails = Math.floor(Math.max(value, 0)) + this.$store.dispatch('setOption', { name: 'maxThumbnails', value }) } } } diff --git a/src/components/settings/settings.vue b/src/components/settings/settings.vue index f5e00995..16814f65 100644 --- a/src/components/settings/settings.vue +++ b/src/components/settings/settings.vue @@ -136,6 +136,10 @@ +
  • + + +
  • @@ -316,6 +320,10 @@ min-width: 10em; padding: 0 2em; } + + .number-input { + max-width: 6em; + } } .select-multiple { display: flex; diff --git a/src/components/status/status.js b/src/components/status/status.js index 0273a5be..fab2fe62 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -40,8 +40,7 @@ const Status = { expandingSubject: typeof this.$store.state.config.collapseMessageWithSubject === 'undefined' ? !this.$store.state.instance.collapseMessageWithSubject : !this.$store.state.config.collapseMessageWithSubject, - betterShadow: this.$store.state.interface.browserSupport.cssFilter, - maxAttachments: 9 + betterShadow: this.$store.state.interface.browserSupport.cssFilter } }, computed: { @@ -225,7 +224,7 @@ const Status = { attachmentSize () { if ((this.$store.state.config.hideAttachments && !this.inConversation) || (this.$store.state.config.hideAttachmentsInConv && this.inConversation) || - (this.status.attachments.length > this.maxAttachments)) { + (this.status.attachments.length > this.maxThumbnails)) { return 'hide' } else if (this.compact) { return 'small' @@ -249,6 +248,9 @@ const Status = { return this.status.attachments.filter( file => !fileType.fileMatchesSomeType(this.galleryTypes, file) ) + }, + maxThumbnails () { + return this.$store.state.config.maxThumbnails } }, components: { diff --git a/src/i18n/en.json b/src/i18n/en.json index 64753f1d..8e837d2f 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -149,6 +149,7 @@ "general": "General", "hide_attachments_in_convo": "Hide attachments in conversations", "hide_attachments_in_tl": "Hide attachments in timeline", + "max_thumbnails": "Maximum amount of thumbnails per post", "hide_isp": "Hide instance-specific panel", "preload_images": "Preload images", "use_one_click_nsfw": "Open NSFW attachments with just one click", diff --git a/src/i18n/fi.json b/src/i18n/fi.json index 5a0c1ea8..a8259ca8 100644 --- a/src/i18n/fi.json +++ b/src/i18n/fi.json @@ -133,6 +133,7 @@ "general": "Yleinen", "hide_attachments_in_convo": "Piilota liitteet keskusteluissa", "hide_attachments_in_tl": "Piilota liitteet aikajanalla", + "max_thumbnails": "Suurin sallittu määrä liitteitä esikatselussa", "hide_isp": "Piilota palvelimenkohtainen ruutu", "preload_images": "Esilataa kuvat", "use_one_click_nsfw": "Avaa NSFW-liitteet yhdellä painalluksella", diff --git a/src/modules/config.js b/src/modules/config.js index 71f71376..1c30c203 100644 --- a/src/modules/config.js +++ b/src/modules/config.js @@ -8,6 +8,7 @@ const defaultState = { collapseMessageWithSubject: undefined, // instance default hideAttachments: false, hideAttachmentsInConv: false, + maxThumbnails: 16, hideNsfw: true, preloadImage: true, loopVideo: true, From 7c6446a9dea2a221da643414e87a97d17336215f Mon Sep 17 00:00:00 2001 From: dave Date: Wed, 27 Feb 2019 14:38:10 -0500 Subject: [PATCH 07/44] #388: get follow request on a real-time basis --- .../follow_requests/follow_requests.js | 9 -------- src/components/nav_panel/nav_panel.js | 13 ++++++++++++ src/components/nav_panel/nav_panel.vue | 4 ++-- src/components/notifications/notifications.js | 1 + src/components/side_drawer/side_drawer.js | 3 +++ src/components/side_drawer/side_drawer.vue | 4 ++-- .../request_fetcher.service.js | 21 +++++++++++++++++++ 7 files changed, 42 insertions(+), 13 deletions(-) create mode 100644 src/services/notifications_fetcher/request_fetcher.service.js diff --git a/src/components/follow_requests/follow_requests.js b/src/components/follow_requests/follow_requests.js index 11a228aa..bf890530 100644 --- a/src/components/follow_requests/follow_requests.js +++ b/src/components/follow_requests/follow_requests.js @@ -4,19 +4,10 @@ const FollowRequests = { components: { UserCard }, - created () { - this.updateRequests() - }, computed: { requests () { return this.$store.state.api.followRequests } - }, - methods: { - updateRequests () { - this.$store.state.api.backendInteractor.fetchFollowRequests() - .then((requests) => { this.$store.commit('setFollowRequests', requests) }) - } } } diff --git a/src/components/nav_panel/nav_panel.js b/src/components/nav_panel/nav_panel.js index ea5d7ea4..c4034417 100644 --- a/src/components/nav_panel/nav_panel.js +++ b/src/components/nav_panel/nav_panel.js @@ -1,10 +1,23 @@ +import requestFetcher from '../../services/notifications_fetcher/request_fetcher.service.js' + const NavPanel = { + created () { + if (this.currentUser && this.currentUser.locked) { + const store = this.$store + const credentials = store.state.users.currentUser.credentials + + requestFetcher.startFetching({ store, credentials }) + } + }, computed: { currentUser () { return this.$store.state.users.currentUser }, chat () { return this.$store.state.chat.channel + }, + followRequestCount () { + return this.$store.state.api.followRequests.length } } } diff --git a/src/components/nav_panel/nav_panel.vue b/src/components/nav_panel/nav_panel.vue index 1a269adf..7a7212fb 100644 --- a/src/components/nav_panel/nav_panel.vue +++ b/src/components/nav_panel/nav_panel.vue @@ -20,8 +20,8 @@
  • {{ $t("nav.friend_requests")}} -
  • diff --git a/src/components/notifications/notifications.js b/src/components/notifications/notifications.js index 5e95631a..2c9a84be 100644 --- a/src/components/notifications/notifications.js +++ b/src/components/notifications/notifications.js @@ -1,5 +1,6 @@ import Notification from '../notification/notification.vue' import notificationsFetcher from '../../services/notifications_fetcher/notifications_fetcher.service.js' + import { notificationsFromStore, visibleNotificationsFromStore, diff --git a/src/components/side_drawer/side_drawer.js b/src/components/side_drawer/side_drawer.js index 40ffa1dd..b5c49059 100644 --- a/src/components/side_drawer/side_drawer.js +++ b/src/components/side_drawer/side_drawer.js @@ -32,6 +32,9 @@ const SideDrawer = { }, sitename () { return this.$store.state.instance.name + }, + followRequestCount () { + return this.$store.state.api.followRequests.length } }, methods: { diff --git a/src/components/side_drawer/side_drawer.vue b/src/components/side_drawer/side_drawer.vue index 8eca7b8c..6996380d 100644 --- a/src/components/side_drawer/side_drawer.vue +++ b/src/components/side_drawer/side_drawer.vue @@ -45,8 +45,8 @@
  • {{ $t("nav.friend_requests") }} - diff --git a/src/services/notifications_fetcher/request_fetcher.service.js b/src/services/notifications_fetcher/request_fetcher.service.js new file mode 100644 index 00000000..beb6c320 --- /dev/null +++ b/src/services/notifications_fetcher/request_fetcher.service.js @@ -0,0 +1,21 @@ +import apiService from '../api/api.service.js' + +const fetchAndUpdate = ({ store, credentials }) => { + return apiService.fetchFollowRequests({ credentials }) + .then((requests) => { + store.commit('setFollowRequests', requests) + }, () => {}) + .catch(() => {}) +} + +const startFetching = ({credentials, store}) => { + fetchAndUpdate({ credentials, store }) + const boundFetchAndUpdate = () => fetchAndUpdate({ credentials, store }) + return setInterval(boundFetchAndUpdate, 10000) +} + +const requestFetcher = { + startFetching +} + +export default requestFetcher From 9f1214555e3e759cc26b10144b12f5c3a2852710 Mon Sep 17 00:00:00 2001 From: dave Date: Wed, 27 Feb 2019 14:40:21 -0500 Subject: [PATCH 08/44] #388: remove empty line --- src/components/notifications/notifications.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/notifications/notifications.js b/src/components/notifications/notifications.js index 2c9a84be..5e95631a 100644 --- a/src/components/notifications/notifications.js +++ b/src/components/notifications/notifications.js @@ -1,6 +1,5 @@ import Notification from '../notification/notification.vue' import notificationsFetcher from '../../services/notifications_fetcher/notifications_fetcher.service.js' - import { notificationsFromStore, visibleNotificationsFromStore, From 2c7406d9a8d8e961b99286f317c6ed0a48427853 Mon Sep 17 00:00:00 2001 From: Maxim Filippov Date: Tue, 12 Feb 2019 21:53:59 +0300 Subject: [PATCH 09/44] Add OAuth Tokens management to settings --- src/components/user_settings/user_settings.js | 24 +++++++++++++ .../user_settings/user_settings.vue | 36 +++++++++++++++++++ src/i18n/ar.json | 5 +++ src/i18n/ca.json | 5 +++ src/i18n/de.json | 5 +++ src/i18n/en.json | 5 +++ src/i18n/es.json | 5 +++ src/i18n/fi.json | 5 +++ src/i18n/fr.json | 5 +++ src/i18n/ga.json | 5 +++ src/i18n/he.json | 5 +++ src/i18n/it.json | 5 +++ src/i18n/ja.json | 5 +++ src/i18n/ko.json | 5 +++ src/i18n/nb.json | 5 +++ src/i18n/nl.json | 5 +++ src/i18n/oc.json | 1 + src/i18n/pl.json | 5 +++ src/i18n/ru.json | 5 +++ src/i18n/zh.json | 5 +++ src/main.js | 4 ++- src/modules/oauth_tokens.js | 26 ++++++++++++++ src/services/api/api.service.js | 19 ++++++++++ .../backend_interactor_service.js | 4 +++ test/unit/specs/modules/users.spec.js | 10 ++++++ 25 files changed, 208 insertions(+), 1 deletion(-) create mode 100644 src/modules/oauth_tokens.js diff --git a/src/components/user_settings/user_settings.js b/src/components/user_settings/user_settings.js index 06e72112..c7dab754 100644 --- a/src/components/user_settings/user_settings.js +++ b/src/components/user_settings/user_settings.js @@ -1,6 +1,10 @@ +<<<<<<< HEAD import { compose } from 'vue-compose' import unescape from 'lodash/unescape' import get from 'lodash/get' +======= +import { unescape, truncate } from 'lodash' +>>>>>>> Add OAuth Tokens management to settings import TabSwitcher from '../tab_switcher/tab_switcher.js' import ImageCropper from '../image_cropper/image_cropper.vue' @@ -62,6 +66,9 @@ const UserSettings = { activeTab: 'profile' } }, + created () { + this.$store.dispatch('fetchTokens') + }, components: { StyleSwitcher, TabSwitcher, @@ -87,8 +94,20 @@ const UserSettings = { direct: { selected: this.newDefaultScope === 'direct' } } }, +<<<<<<< HEAD currentSaveStateNotice () { return this.$store.state.interface.settings.currentSaveStateNotice +======= + oauthTokens () { + return this.$store.state.oauthTokens.tokens.map(oauthToken => { + return { + id: oauthToken.id, + token: truncate(oauthToken.token, { length: 15 }), + refreshToken: truncate(oauthToken.refresh_token, { length: 15 }), + validUntil: new Date(oauthToken.valid_until).toLocaleDateString() + } + }) +>>>>>>> Add OAuth Tokens management to settings } }, methods: { @@ -308,6 +327,11 @@ const UserSettings = { logout () { this.$store.dispatch('logout') this.$router.replace('/') + }, + revokeToken (id) { + if(confirm('Are you sure?')) { + this.$store.dispatch('revokeToken', id) + } } } } diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue index 983cbda0..ac75ad86 100644 --- a/src/components/user_settings/user_settings.vue +++ b/src/components/user_settings/user_settings.vue @@ -121,6 +121,30 @@

    {{changePasswordError}}

    +
    +

    {{$t('settings.oauth_tokens')}}

    + + + + + + + + + + + + + + + + + +
    TokenRefresh TokenValid Until
    {{oauthToken.token}}{{oauthToken.refreshToken}}{{oauthToken.validUntil}} + +
    +
    +

    {{$t('settings.delete_account')}}

    {{$t('settings.delete_account_description')}}

    @@ -213,5 +237,17 @@ border-radius: $fallback--avatarRadius; border-radius: var(--avatarRadius, $fallback--avatarRadius); } + + .oauth-tokens { + width: 100%; + + th { + text-align: left; + } + + .actions { + text-align: right; + } + } } diff --git a/src/i18n/ar.json b/src/i18n/ar.json index ac7d0f1a..242dab78 100644 --- a/src/i18n/ar.json +++ b/src/i18n/ar.json @@ -134,6 +134,11 @@ "notification_visibility_mentions": "الإشارات", "notification_visibility_repeats": "", "nsfw_clickthrough": "", + "oauth_tokens": "رموز OAuth", + "token": "رمز", + "refresh_token": "رمز التحديث", + "valid_until": "صالح حتى", + "revoke_token": "سحب", "panelRadius": "", "pause_on_unfocused": "", "presets": "النماذج", diff --git a/src/i18n/ca.json b/src/i18n/ca.json index fa517e22..d2f285df 100644 --- a/src/i18n/ca.json +++ b/src/i18n/ca.json @@ -132,6 +132,11 @@ "notification_visibility_repeats": "Republica una entrada meva", "no_rich_text_description": "Neteja el formatat de text de totes les entrades", "nsfw_clickthrough": "Amaga el contingut NSFW darrer d'una imatge clicable", + "oauth_tokens": "Llistats OAuth", + "token": "Token", + "refresh_token": "Actualitza el token", + "valid_until": "Vàlid fins", + "revoke_token": "Revocar", "panelRadius": "Panells", "pause_on_unfocused": "Pausa la reproducció en continu quan la pestanya perdi el focus", "presets": "Temes", diff --git a/src/i18n/de.json b/src/i18n/de.json index d0bfba38..07d44348 100644 --- a/src/i18n/de.json +++ b/src/i18n/de.json @@ -159,6 +159,11 @@ "hide_follows_description": "Zeige nicht, wem ich folge", "hide_followers_description": "Zeige nicht, wer mir folgt", "nsfw_clickthrough": "Aktiviere ausblendbares Overlay für Anhänge, die als NSFW markiert sind", + "oauth_tokens": "OAuth-Token", + "token": "Zeichen", + "refresh_token": "Token aktualisieren", + "valid_until": "Gültig bis", + "revoke_token": "Widerrufen", "panelRadius": "Panel", "pause_on_unfocused": "Streaming pausieren, wenn das Tab nicht fokussiert ist", "presets": "Voreinstellungen", diff --git a/src/i18n/en.json b/src/i18n/en.json index 8e837d2f..0aeb70ab 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -189,6 +189,11 @@ "show_admin_badge": "Show Admin badge in my profile", "show_moderator_badge": "Show Moderator badge in my profile", "nsfw_clickthrough": "Enable clickthrough NSFW attachment hiding", + "oauth_tokens": "OAuth tokens", + "token": "Token", + "refresh_token": "Refresh Token", + "valid_until": "Valid Until", + "revoke_token": "Revoke", "panelRadius": "Panels", "pause_on_unfocused": "Pause streaming when tab is not focused", "presets": "Presets", diff --git a/src/i18n/es.json b/src/i18n/es.json index d14e7a31..167e8c42 100644 --- a/src/i18n/es.json +++ b/src/i18n/es.json @@ -171,6 +171,11 @@ "show_admin_badge": "Mostrar la placa de administrador en mi perfil", "show_moderator_badge": "Mostrar la placa de moderador en mi perfil", "nsfw_clickthrough": "Activar el clic para ocultar los adjuntos NSFW", + "oauth_tokens": "Tokens de OAuth", + "token": "Token", + "refresh_token": "Actualizar el token", + "valid_until": "Válido hasta", + "revoke_token": "Revocar", "panelRadius": "Paneles", "pause_on_unfocused": "Parar la transmisión cuando no estés en foco.", "presets": "Por defecto", diff --git a/src/i18n/fi.json b/src/i18n/fi.json index a8259ca8..c7a25fe1 100644 --- a/src/i18n/fi.json +++ b/src/i18n/fi.json @@ -166,6 +166,11 @@ "no_rich_text_description": "Älä näytä tekstin muotoilua.", "hide_network_description": "Älä näytä seurauksiani tai seuraajiani", "nsfw_clickthrough": "Piilota NSFW liitteet klikkauksen taakse", + "oauth_tokens": "OAuth-merkit", + "token": "Token", + "refresh_token": "Päivitä token", + "valid_until": "Voimassa asti", + "revoke_token": "Peruuttaa", "panelRadius": "Ruudut", "pause_on_unfocused": "Pysäytä automaattinen viestien näyttö välilehden ollessa pois fokuksesta", "presets": "Valmiit teemat", diff --git a/src/i18n/fr.json b/src/i18n/fr.json index 129b7d7c..1209556a 100644 --- a/src/i18n/fr.json +++ b/src/i18n/fr.json @@ -137,6 +137,11 @@ "notification_visibility_mentions": "Mentionnés", "notification_visibility_repeats": "Partages", "nsfw_clickthrough": "Masquer les images marquées comme contenu adulte ou sensible", + "oauth_tokens": "Jetons OAuth", + "token": "Jeton", + "refresh_token": "Refresh Token", + "valid_until": "Valable jusque", + "revoke_token": "Révoquer", "panelRadius": "Fenêtres", "pause_on_unfocused": "Suspendre le streaming lorsque l'onglet n'est pas centré", "presets": "Thèmes prédéfinis", diff --git a/src/i18n/ga.json b/src/i18n/ga.json index 64461202..5be9297a 100644 --- a/src/i18n/ga.json +++ b/src/i18n/ga.json @@ -134,6 +134,11 @@ "notification_visibility_repeats": "Atphostáil", "no_rich_text_description": "Bain formáidiú téacs saibhir ó gach post", "nsfw_clickthrough": "Cumasaigh an ceangaltán NSFW cliceáil ar an gcnaipe", + "oauth_tokens": "Tocanna OAuth", + "token": "Token", + "refresh_token": "Athnuachan Comórtas", + "valid_until": "Bailí Go dtí", + "revoke_token": "Athghairm", "panelRadius": "Painéil", "pause_on_unfocused": "Sruthú ar sos nuair a bhíonn an fócas caillte", "presets": "Réamhshocruithe", diff --git a/src/i18n/he.json b/src/i18n/he.json index 99ae9551..213e6170 100644 --- a/src/i18n/he.json +++ b/src/i18n/he.json @@ -129,6 +129,11 @@ "notification_visibility_mentions": "אזכורים", "notification_visibility_repeats": "חזרות", "nsfw_clickthrough": "החל החבאת צירופים לא בטוחים לצפיה בעת עבודה בעזרת לחיצת עכבר", + "oauth_tokens": "אסימוני OAuth", + "token": "אסימון", + "refresh_token": "רענון האסימון", + "valid_until": "בתוקף עד", + "revoke_token": "בטל", "panelRadius": "פאנלים", "pause_on_unfocused": "השהה זרימת הודעות כשהחלון לא בפוקוס", "presets": "ערכים קבועים מראש", diff --git a/src/i18n/it.json b/src/i18n/it.json index 8f69e7c1..385d21aa 100644 --- a/src/i18n/it.json +++ b/src/i18n/it.json @@ -93,6 +93,11 @@ "notification_visibility_mentions": "Menzioni", "notification_visibility_repeats": "Condivisioni", "no_rich_text_description": "Togli la formattazione del testo da tutti i post", + "oauth_tokens": "Token OAuth", + "token": "Token", + "refresh_token": "Aggiorna token", + "valid_until": "Valido fino a", + "revoke_token": "Revocare", "panelRadius": "Pannelli", "pause_on_unfocused": "Metti in pausa l'aggiornamento continuo quando la scheda non è in primo piano", "presets": "Valori predefiniti", diff --git a/src/i18n/ja.json b/src/i18n/ja.json index 7849aa20..b51fa7fd 100644 --- a/src/i18n/ja.json +++ b/src/i18n/ja.json @@ -171,6 +171,11 @@ "show_admin_badge": "アドミンのしるしをみる", "show_moderator_badge": "モデレーターのしるしをみる", "nsfw_clickthrough": "NSFWなファイルをかくす", + "oauth_tokens": "OAuthトークン", + "token": "トークン", + "refresh_token": "トークンを更新", + "valid_until": "まで有効", + "revoke_token": "取り消す", "panelRadius": "パネル", "pause_on_unfocused": "タブにフォーカスがないときストリーミングをとめる", "presets": "プリセット", diff --git a/src/i18n/ko.json b/src/i18n/ko.json index f9e4dfa3..336e464f 100644 --- a/src/i18n/ko.json +++ b/src/i18n/ko.json @@ -159,6 +159,11 @@ "hide_follows_description": "내가 팔로우하는 사람을 표시하지 않음", "hide_followers_description": "나를 따르는 사람을 보여주지 마라.", "nsfw_clickthrough": "NSFW 이미지 \"클릭해서 보이기\"를 활성화", + "oauth_tokens": "OAuth 토큰", + "token": "토큰", + "refresh_token": "토큰 새로 고침", + "valid_until": "까지 유효하다", + "revoke_token": "취소", "panelRadius": "패널", "pause_on_unfocused": "탭이 활성 상태가 아닐 때 스트리밍 멈추기", "presets": "프리셋", diff --git a/src/i18n/nb.json b/src/i18n/nb.json index 0f4dca58..39e054f7 100644 --- a/src/i18n/nb.json +++ b/src/i18n/nb.json @@ -132,6 +132,11 @@ "notification_visibility_repeats": "Gjentakelser", "no_rich_text_description": "Fjern all formatering fra statuser", "nsfw_clickthrough": "Krev trykk for å vise statuser som kan være upassende", + "oauth_tokens": "OAuth Tokens", + "token": "Pollett", + "refresh_token": "Refresh Token", + "valid_until": "Gyldig til", + "revoke_token": "Tilbakekall", "panelRadius": "Panel", "pause_on_unfocused": "Stopp henting av poster når vinduet ikke er i fokus", "presets": "Forhåndsdefinerte tema", diff --git a/src/i18n/nl.json b/src/i18n/nl.json index bb388a90..799e22b9 100644 --- a/src/i18n/nl.json +++ b/src/i18n/nl.json @@ -159,6 +159,11 @@ "no_rich_text_description": "Strip rich text formattering van alle posts", "hide_network_description": "Toon niet wie mij volgt en wie ik volg.", "nsfw_clickthrough": "Schakel doorklikbaar verbergen van NSFW bijlages in", + "oauth_tokens": "OAuth-tokens", + "token": "Token", + "refresh_token": "Token vernieuwen", + "valid_until": "Geldig tot", + "revoke_token": "Intrekken", "panelRadius": "Panelen", "pause_on_unfocused": "Pauzeer streamen wanneer de tab niet gefocused is", "presets": "Presets", diff --git a/src/i18n/oc.json b/src/i18n/oc.json index 2ce666c6..db66bb98 100644 --- a/src/i18n/oc.json +++ b/src/i18n/oc.json @@ -142,6 +142,7 @@ "notification_visibility_mentions": "Mencions", "notification_visibility_repeats": "Repeticions", "no_rich_text_description": "Netejar lo format tèxte de totas las publicacions", + "oauth_tokens": "Llistats OAuth", "pause_on_unfocused": "Pausar la difusion quand l’onglet es pas seleccionat", "profile_tab": "Perfil", "replies_in_timeline": "Responsas del flux", diff --git a/src/i18n/pl.json b/src/i18n/pl.json index a3952d4f..2e1d7488 100644 --- a/src/i18n/pl.json +++ b/src/i18n/pl.json @@ -86,6 +86,11 @@ "name_bio": "Imię i bio", "new_password": "Nowe hasło", "nsfw_clickthrough": "Włącz domyślne ukrywanie załączników o treści nieprzyzwoitej (NSFW)", + "oauth_tokens": "Tokeny OAuth", + "token": "Token", + "refresh_token": "Odśwież token", + "valid_until": "Ważne do", + "revoke_token": "Odwołać", "panelRadius": "Panele", "presets": "Gotowe motywy", "profile_background": "Tło profilu", diff --git a/src/i18n/ru.json b/src/i18n/ru.json index 4b0bd4b4..6799cc96 100644 --- a/src/i18n/ru.json +++ b/src/i18n/ru.json @@ -132,6 +132,11 @@ "show_admin_badge": "Показывать значок администратора в моем профиле", "show_moderator_badge": "Показывать значок модератора в моем профиле", "nsfw_clickthrough": "Включить скрытие NSFW вложений", + "oauth_tokens": "OAuth токены", + "token": "Токен", + "refresh_token": "Рефреш токен", + "valid_until": "Годен до", + "revoke_token": "Удалить", "panelRadius": "Панели", "pause_on_unfocused": "Приостановить загрузку когда вкладка не в фокусе", "presets": "Пресеты", diff --git a/src/i18n/zh.json b/src/i18n/zh.json index 7ad23c57..089a98e2 100644 --- a/src/i18n/zh.json +++ b/src/i18n/zh.json @@ -134,6 +134,11 @@ "notification_visibility_repeats": "转发", "no_rich_text_description": "不显示富文本格式", "nsfw_clickthrough": "将不和谐附件隐藏,点击才能打开", + "oauth_tokens": "OAuth令牌", + "token": "代币", + "refresh_token": "刷新令牌", + "valid_until": "有效期至", + "revoke_token": "撤消", "panelRadius": "面板", "pause_on_unfocused": "在离开页面时暂停时间线推送", "presets": "预置", diff --git a/src/main.js b/src/main.js index adeb0550..2844194e 100644 --- a/src/main.js +++ b/src/main.js @@ -11,6 +11,7 @@ import configModule from './modules/config.js' import chatModule from './modules/chat.js' import oauthModule from './modules/oauth.js' import mediaViewerModule from './modules/media_viewer.js' +import oauthTokensModule from './modules/oauth_tokens.js' import VueTimeago from 'vue-timeago' import VueI18n from 'vue-i18n' @@ -64,7 +65,8 @@ createPersistedState(persistedStateOptions).then((persistedState) => { config: configModule, chat: chatModule, oauth: oauthModule, - mediaViewer: mediaViewerModule + mediaViewer: mediaViewerModule, + oauthTokens: oauthTokensModule }, plugins: [persistedState, pushNotifications], strict: false // Socket modifies itself, let's ignore this for now. diff --git a/src/modules/oauth_tokens.js b/src/modules/oauth_tokens.js new file mode 100644 index 00000000..00ac1431 --- /dev/null +++ b/src/modules/oauth_tokens.js @@ -0,0 +1,26 @@ +const oauthTokens = { + state: { + tokens: [] + }, + actions: { + fetchTokens ({rootState, commit}) { + rootState.api.backendInteractor.fetchOAuthTokens().then((tokens) => { + commit('swapTokens', tokens) + }) + }, + revokeToken ({rootState, commit, state}, id) { + rootState.api.backendInteractor.revokeOAuthToken(id).then((response) => { + if (response.status === 201) { + commit('swapTokens', state.tokens.filter(token => token.id !== id)) + } + }) + } + }, + mutations: { + swapTokens (state, tokens) { + state.tokens = tokens + } + } +} + +export default oauthTokens diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 3d2e8823..7b04343d 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -531,6 +531,23 @@ const fetchBlocks = ({page, credentials}) => { }) } +const fetchOAuthTokens = ({credentials}) => { + const url = '/api/oauth_tokens.json' + + return fetch(url, { + headers: authHeaders(credentials) + }).then((data) => data.json()) +} + +const revokeOAuthToken = ({id, credentials}) => { + const url = `/api/oauth_tokens/${id}` + + return fetch(url, { + headers: authHeaders(credentials), + method: 'DELETE' + }) +} + const suggestions = ({credentials}) => { return fetch(SUGGESTIONS_URL, { headers: authHeaders(credentials) @@ -573,6 +590,8 @@ const apiService = { setUserMute, fetchMutes, fetchBlocks, + fetchOAuthTokens, + revokeOAuthToken, register, getCaptcha, updateAvatar, diff --git a/src/services/backend_interactor_service/backend_interactor_service.js b/src/services/backend_interactor_service/backend_interactor_service.js index 43c914d9..2278cd45 100644 --- a/src/services/backend_interactor_service/backend_interactor_service.js +++ b/src/services/backend_interactor_service/backend_interactor_service.js @@ -65,6 +65,8 @@ const backendInteractorService = (credentials) => { const fetchMutes = () => apiService.fetchMutes({credentials}) const fetchBlocks = (params) => apiService.fetchBlocks({credentials, ...params}) const fetchFollowRequests = () => apiService.fetchFollowRequests({credentials}) + const fetchOAuthTokens = () => apiService.fetchOAuthTokens({credentials}) + const revokeOAuthToken = (id) => apiService.revokeOAuthToken({id, credentials}) const getCaptcha = () => apiService.getCaptcha() const register = (params) => apiService.register(params) @@ -96,6 +98,8 @@ const backendInteractorService = (credentials) => { setUserMute, fetchMutes, fetchBlocks, + fetchOAuthTokens, + revokeOAuthToken, register, getCaptcha, updateAvatar, diff --git a/test/unit/specs/modules/users.spec.js b/test/unit/specs/modules/users.spec.js index 4d49ee24..96ae845b 100644 --- a/test/unit/specs/modules/users.spec.js +++ b/test/unit/specs/modules/users.spec.js @@ -32,6 +32,16 @@ describe('The users module', () => { expect(user.muted).to.eql(false) }) + + it('sets oauth tokens', () => { + const state = cloneDeep(defaultState) + const tokens = [{ id: 1, token: 'bar' }] + + mutations.addOAuthTokens(state, tokens) + + expect(state.oauthTokens).to.have.length(1) + expect(state.oauthTokens).to.eql(tokens) + }) }) describe('getUserByName', () => { From c10a15386ab4b58e8537af5b6289f59e902bb489 Mon Sep 17 00:00:00 2001 From: Maxim Filippov Date: Tue, 12 Feb 2019 21:56:43 +0300 Subject: [PATCH 10/44] Code style --- src/components/user_settings/user_settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/user_settings/user_settings.js b/src/components/user_settings/user_settings.js index c7dab754..5e972d82 100644 --- a/src/components/user_settings/user_settings.js +++ b/src/components/user_settings/user_settings.js @@ -329,7 +329,7 @@ const UserSettings = { this.$router.replace('/') }, revokeToken (id) { - if(confirm('Are you sure?')) { + if (window.confirm('Are you sure?')) { this.$store.dispatch('revokeToken', id) } } From b9082fb13f5fc16eeaa97b580dcfd3a11481d519 Mon Sep 17 00:00:00 2001 From: Maxim Filippov Date: Tue, 12 Feb 2019 22:02:29 +0300 Subject: [PATCH 11/44] Remove outdated test --- test/unit/specs/modules/users.spec.js | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/test/unit/specs/modules/users.spec.js b/test/unit/specs/modules/users.spec.js index 96ae845b..4d49ee24 100644 --- a/test/unit/specs/modules/users.spec.js +++ b/test/unit/specs/modules/users.spec.js @@ -32,16 +32,6 @@ describe('The users module', () => { expect(user.muted).to.eql(false) }) - - it('sets oauth tokens', () => { - const state = cloneDeep(defaultState) - const tokens = [{ id: 1, token: 'bar' }] - - mutations.addOAuthTokens(state, tokens) - - expect(state.oauthTokens).to.have.length(1) - expect(state.oauthTokens).to.eql(tokens) - }) }) describe('getUserByName', () => { From afbe524a2e7fa7002c4b1f5883e08b2d135a2eda Mon Sep 17 00:00:00 2001 From: Maxim Filippov Date: Tue, 12 Feb 2019 22:07:10 +0300 Subject: [PATCH 12/44] use translations --- src/components/user_settings/user_settings.vue | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue index ac75ad86..116c1335 100644 --- a/src/components/user_settings/user_settings.vue +++ b/src/components/user_settings/user_settings.vue @@ -126,9 +126,9 @@ - - - + + + @@ -138,7 +138,9 @@ From c71f411ad6593a496a041505f8a6642a4ae55eea Mon Sep 17 00:00:00 2001 From: Maxim Filippov Date: Mon, 18 Feb 2019 00:01:18 +0300 Subject: [PATCH 13/44] Show only "app_name" and "valid_until" (OAuth tokens table) --- src/components/user_settings/user_settings.js | 12 ++---------- src/components/user_settings/user_settings.vue | 6 ++---- src/i18n/en.json | 1 + 3 files changed, 5 insertions(+), 14 deletions(-) diff --git a/src/components/user_settings/user_settings.js b/src/components/user_settings/user_settings.js index 5e972d82..e7a961f1 100644 --- a/src/components/user_settings/user_settings.js +++ b/src/components/user_settings/user_settings.js @@ -1,11 +1,6 @@ -<<<<<<< HEAD import { compose } from 'vue-compose' import unescape from 'lodash/unescape' import get from 'lodash/get' -======= -import { unescape, truncate } from 'lodash' ->>>>>>> Add OAuth Tokens management to settings - import TabSwitcher from '../tab_switcher/tab_switcher.js' import ImageCropper from '../image_cropper/image_cropper.vue' import StyleSwitcher from '../style_switcher/style_switcher.vue' @@ -94,20 +89,17 @@ const UserSettings = { direct: { selected: this.newDefaultScope === 'direct' } } }, -<<<<<<< HEAD currentSaveStateNotice () { return this.$store.state.interface.settings.currentSaveStateNotice -======= + }, oauthTokens () { return this.$store.state.oauthTokens.tokens.map(oauthToken => { return { id: oauthToken.id, - token: truncate(oauthToken.token, { length: 15 }), - refreshToken: truncate(oauthToken.refresh_token, { length: 15 }), + appName: oauthToken.app_name, validUntil: new Date(oauthToken.valid_until).toLocaleDateString() } }) ->>>>>>> Add OAuth Tokens management to settings } }, methods: { diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue index 116c1335..a1123638 100644 --- a/src/components/user_settings/user_settings.vue +++ b/src/components/user_settings/user_settings.vue @@ -126,16 +126,14 @@
    TokenRefresh TokenValid Until{{$t('settings.token')}}{{$t('settings.refresh_token')}}{{$t('settings.valid_until')}}
    {{oauthToken.refreshToken}} {{oauthToken.validUntil}} - +
    - - + - - +
    {{$t('settings.token')}}{{$t('settings.refresh_token')}}{{$t('settings.app_name')}} {{$t('settings.valid_until')}}
    {{oauthToken.token}}{{oauthToken.refreshToken}}{{oauthToken.appName}} {{oauthToken.validUntil}}