From ee49409049430dedf042ddbeb73898f605664cd2 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Fri, 8 Mar 2019 00:35:30 +0200 Subject: [PATCH 01/45] Partially transitioned user data to MastoAPI. Added support for fetching relationship data. Upgraded code to be more resilient to nulls caused by missing data in either APIs --- src/components/user_card/user_card.js | 3 ++ src/components/user_profile/user_profile.js | 3 ++ src/modules/statuses.js | 6 ++-- src/modules/users.js | 32 +++++++++++++++---- src/services/api/api.service.js | 20 ++++++++++-- .../backend_interactor_service.js | 5 +++ 6 files changed, 59 insertions(+), 10 deletions(-) diff --git a/src/components/user_card/user_card.js b/src/components/user_card/user_card.js index 80d15a27..43a77f45 100644 --- a/src/components/user_card/user_card.js +++ b/src/components/user_card/user_card.js @@ -15,6 +15,9 @@ export default { betterShadow: this.$store.state.interface.browserSupport.cssFilter } }, + created () { + this.$store.dispatch('fetchUserRelationship', this.user.id) + }, computed: { classes () { return [{ diff --git a/src/components/user_profile/user_profile.js b/src/components/user_profile/user_profile.js index 54126514..345e7035 100644 --- a/src/components/user_profile/user_profile.js +++ b/src/components/user_profile/user_profile.js @@ -43,6 +43,7 @@ const UserProfile = { this.startFetchFavorites() if (!this.user.id) { this.$store.dispatch('fetchUser', this.fetchBy) + .then(() => this.$store.dispatch('fetchUserRelationship', this.fetchBy)) .catch((reason) => { const errorMessage = get(reason, 'error.error') if (errorMessage === 'No user with such user_id') { // Known error @@ -53,6 +54,8 @@ const UserProfile = { this.error = this.$t('user_profile.profile_loading_error') } }) + } else if (typeof this.user.following === 'undefined' || this.user.following === null) { + this.$store.dispatch('fetchUserRelationship', this.fetchBy) } }, destroyed () { diff --git a/src/modules/statuses.js b/src/modules/statuses.js index 7571b62a..2b0215f0 100644 --- a/src/modules/statuses.js +++ b/src/modules/statuses.js @@ -1,4 +1,4 @@ -import { remove, slice, each, find, maxBy, minBy, merge, first, last, isArray } from 'lodash' +import { remove, slice, each, find, maxBy, minBy, merge, first, last, isArray, omitBy } from 'lodash' import apiService from '../services/api/api.service.js' // import parse from '../services/status_parser/status_parser.js' @@ -72,7 +72,9 @@ const mergeOrAdd = (arr, obj, item) => { if (oldItem) { // We already have this, so only merge the new info. - merge(oldItem, item) + // We ignore null values to avoid overwriting existing properties with missing data + // we also skip 'used' because that is handled by users module + merge(oldItem, omitBy(item, (v, k) => v === null || k === 'user')) // Reactivity fix. oldItem.attachments.splice(oldItem.attachments.length) return {item: oldItem, new: false} diff --git a/src/modules/users.js b/src/modules/users.js index 4159964c..a81ed964 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -1,5 +1,5 @@ import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js' -import { compact, map, each, merge, find } from 'lodash' +import { compact, map, each, merge, find, omitBy } from 'lodash' import { set } from 'vue' import { registerPushNotifications, unregisterPushNotifications } from '../services/push/push.js' import oauthApi from '../services/new_api/oauth' @@ -11,7 +11,7 @@ export const mergeOrAdd = (arr, obj, item) => { const oldItem = obj[item.id] if (oldItem) { // We already have this, so only merge the new info. - merge(oldItem, item) + merge(oldItem, omitBy(item, _ => _ === null)) return { item: oldItem, new: false } } else { // This is a new item, prepare it @@ -39,7 +39,7 @@ export const mutations = { }, setCurrentUser (state, user) { state.lastLoginName = user.screen_name - state.currentUser = merge(state.currentUser || {}, user) + state.currentUser = merge(state.currentUser || {}, omitBy(user, _ => _ === null)) }, clearCurrentUser (state) { state.currentUser = false @@ -91,6 +91,16 @@ export const mutations = { addNewUsers (state, users) { each(users, (user) => mergeOrAdd(state.users, state.usersObject, user)) }, + updateUserRelationship (state, relationships) { + relationships.forEach((relationship) => { + const user = state.usersObject[relationship.id] + + user.follows_you = relationship.followed_by + user.following = relationship.following + user.muted = relationship.muting + user.statusnet_blocking = relationship.blocking + }) + }, saveBlocks (state, blockIds) { state.currentUser.blockIds = blockIds }, @@ -98,11 +108,17 @@ export const mutations = { state.currentUser.muteIds = muteIds }, setUserForStatus (state, status) { - status.user = state.usersObject[status.user.id] + // Not setting it again since it's already reactive if it has getters + if (!Object.getOwnPropertyDescriptor(status.user, 'id').get) { + status.user = state.usersObject[status.user.id] + } }, setUserForNotification (state, notification) { - notification.action.user = state.usersObject[notification.action.user.id] - notification.from_profile = state.usersObject[notification.action.user.id] + // Not setting it again since it's already reactive if it has getters + if (!Object.getOwnPropertyDescriptor(notification.action.user, 'id').get) { + notification.action.user = state.usersObject[notification.action.user.id] + notification.from_profile = state.usersObject[notification.action.user.id] + } }, setColor (state, { user: { id }, highlighted }) { const user = state.usersObject[id] @@ -149,6 +165,10 @@ const users = { return store.rootState.api.backendInteractor.fetchUser({ id }) .then((user) => store.commit('addNewUsers', [user])) }, + fetchUserRelationship (store, id) { + return store.rootState.api.backendInteractor.fetchUserRelationship({ id }) + .then((relationships) => store.commit('updateUserRelationship', relationships)) + }, fetchBlocks (store) { return store.rootState.api.backendInteractor.fetchBlocks() .then((blocks) => { diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 2de87026..d512b120 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -33,7 +33,6 @@ const QVITTER_USER_NOTIFICATIONS_URL = '/api/qvitter/statuses/notifications.json const QVITTER_USER_NOTIFICATIONS_READ_URL = '/api/qvitter/statuses/notifications/read.json' const BLOCKING_URL = '/api/blocks/create.json' const UNBLOCKING_URL = '/api/blocks/destroy.json' -const USER_URL = '/api/users/show.json' const FOLLOW_IMPORT_URL = '/api/pleroma/follow_import' const DELETE_ACCOUNT_URL = '/api/pleroma/delete_account' const CHANGE_PASSWORD_URL = '/api/pleroma/change_password' @@ -43,6 +42,8 @@ const DENY_USER_URL = '/api/pleroma/friendships/deny' const SUGGESTIONS_URL = '/api/v1/suggestions' const MASTODON_USER_FAVORITES_TIMELINE_URL = '/api/v1/favourites' +const MASTODON_USER_URL = '/api/v1/accounts/' +const MASTODON_USER_RELATIONSHIPS_URL = '/api/v1/accounts/relationships' import { each, map } from 'lodash' import { parseStatus, parseUser, parseNotification } from '../entity_normalizer/entity_normalizer.service.js' @@ -243,7 +244,7 @@ const denyUser = ({id, credentials}) => { } const fetchUser = ({id, credentials}) => { - let url = `${USER_URL}?user_id=${id}` + let url = `${MASTODON_USER_URL}/${id}` return fetch(url, { headers: authHeaders(credentials) }) .then((response) => { return new Promise((resolve, reject) => response.json() @@ -257,6 +258,20 @@ const fetchUser = ({id, credentials}) => { .then((data) => parseUser(data)) } +const fetchUserRelationship = ({id, credentials}) => { + let url = `${MASTODON_USER_RELATIONSHIPS_URL}/?id=${id}` + return fetch(url, { headers: authHeaders(credentials) }) + .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) + })) + }) +} + const fetchFriends = ({id, page, credentials}) => { let url = `${FRIENDS_URL}?user_id=${id}` if (page) { @@ -588,6 +603,7 @@ const apiService = { blockUser, unblockUser, fetchUser, + fetchUserRelationship, favorite, unfavorite, retweet, diff --git a/src/services/backend_interactor_service/backend_interactor_service.js b/src/services/backend_interactor_service/backend_interactor_service.js index 7e972d7b..cbd0b733 100644 --- a/src/services/backend_interactor_service/backend_interactor_service.js +++ b/src/services/backend_interactor_service/backend_interactor_service.js @@ -30,6 +30,10 @@ const backendInteractorService = (credentials) => { return apiService.fetchUser({id, credentials}) } + const fetchUserRelationship = ({id}) => { + return apiService.fetchUserRelationship({id, credentials}) + } + const followUser = (id) => { return apiService.followUser({credentials, id}) } @@ -92,6 +96,7 @@ const backendInteractorService = (credentials) => { blockUser, unblockUser, fetchUser, + fetchUserRelationship, fetchAllFollowing, verifyCredentials: apiService.verifyCredentials, startFetching, From 853e0bc26fc49c9f402fd482fb03082f32353485 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Fri, 8 Mar 2019 00:50:58 +0200 Subject: [PATCH 02/45] switch to mastoapi for user timeline --- src/services/api/api.service.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index d512b120..744c2f64 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -28,7 +28,6 @@ const BG_UPDATE_URL = '/api/qvitter/update_background_image.json' const BANNER_UPDATE_URL = '/api/account/update_profile_banner.json' const PROFILE_UPDATE_URL = '/api/account/update_profile.json' const EXTERNAL_PROFILE_URL = '/api/externalprofile/show.json' -const QVITTER_USER_TIMELINE_URL = '/api/qvitter/statuses/user_timeline.json' const QVITTER_USER_NOTIFICATIONS_URL = '/api/qvitter/statuses/notifications.json' const QVITTER_USER_NOTIFICATIONS_READ_URL = '/api/qvitter/statuses/notifications/read.json' const BLOCKING_URL = '/api/blocks/create.json' @@ -44,6 +43,7 @@ const SUGGESTIONS_URL = '/api/v1/suggestions' const MASTODON_USER_FAVORITES_TIMELINE_URL = '/api/v1/favourites' const MASTODON_USER_URL = '/api/v1/accounts/' const MASTODON_USER_RELATIONSHIPS_URL = '/api/v1/accounts/relationships' +const MASTODON_USER_TIMELINE_URL = id => `/api/v1/accounts/${id}/statuses` import { each, map } from 'lodash' import { parseStatus, parseUser, parseNotification } from '../entity_normalizer/entity_normalizer.service.js' @@ -362,8 +362,8 @@ const fetchTimeline = ({timeline, credentials, since = false, until = false, use dms: DM_TIMELINE_URL, notifications: QVITTER_USER_NOTIFICATIONS_URL, 'publicAndExternal': PUBLIC_AND_EXTERNAL_TIMELINE_URL, - user: QVITTER_USER_TIMELINE_URL, - media: QVITTER_USER_TIMELINE_URL, + user: MASTODON_USER_TIMELINE_URL, + media: MASTODON_USER_TIMELINE_URL, favorites: MASTODON_USER_FAVORITES_TIMELINE_URL, tag: TAG_TIMELINE_URL } @@ -372,6 +372,10 @@ const fetchTimeline = ({timeline, credentials, since = false, until = false, use let url = timelineUrls[timeline] + if (timeline === 'user' || timeline === 'media') { + url = url(userId) + } + if (since) { params.push(['since_id', since]) } From 4f3a220487c3c8b3596e5a8de7b65cc7c4f0c981 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Fri, 8 Mar 2019 22:40:57 +0200 Subject: [PATCH 03/45] Since BE doesn't support fetching user by screen name over MastoAPI we'll gonna just fetching it over QvitterAPI real quick :DDDDDDDDD --- src/components/block_card/block_card.js | 2 +- src/components/mute_card/mute_card.js | 2 +- src/components/user_profile/user_profile.js | 63 ++++++++----------- src/components/user_profile/user_profile.vue | 4 +- src/modules/statuses.js | 1 + src/modules/users.js | 12 ++-- src/services/api/api.service.js | 23 +++++-- .../backend_interactor_service.js | 5 ++ .../specs/components/user_profile.spec.js | 3 +- test/unit/specs/modules/users.spec.js | 6 +- 10 files changed, 65 insertions(+), 56 deletions(-) diff --git a/src/components/block_card/block_card.js b/src/components/block_card/block_card.js index 11fa27b4..c459ff1b 100644 --- a/src/components/block_card/block_card.js +++ b/src/components/block_card/block_card.js @@ -9,7 +9,7 @@ const BlockCard = { }, computed: { user () { - return this.$store.getters.userById(this.userId) + return this.$store.getters.findUser(this.userId) }, blocked () { return this.user.statusnet_blocking diff --git a/src/components/mute_card/mute_card.js b/src/components/mute_card/mute_card.js index 5dd0a9e5..65c9cfb5 100644 --- a/src/components/mute_card/mute_card.js +++ b/src/components/mute_card/mute_card.js @@ -9,7 +9,7 @@ const MuteCard = { }, computed: { user () { - return this.$store.getters.userById(this.userId) + return this.$store.getters.findUser(this.userId) }, muted () { return this.user.muted diff --git a/src/components/user_profile/user_profile.js b/src/components/user_profile/user_profile.js index 345e7035..4f920ae2 100644 --- a/src/components/user_profile/user_profile.js +++ b/src/components/user_profile/user_profile.js @@ -9,7 +9,7 @@ import withList from '../../hocs/with_list/with_list' const FollowerList = compose( withLoadMore({ fetch: (props, $store) => $store.dispatch('addFollowers', props.userId), - select: (props, $store) => get($store.getters.userById(props.userId), 'followers', []), + select: (props, $store) => get($store.getters.findUser(props.userId), 'followers', []), destory: (props, $store) => $store.dispatch('clearFollowers', props.userId), childPropName: 'entries', additionalPropNames: ['userId'] @@ -20,7 +20,7 @@ const FollowerList = compose( const FriendList = compose( withLoadMore({ fetch: (props, $store) => $store.dispatch('addFriends', props.userId), - select: (props, $store) => get($store.getters.userById(props.userId), 'friends', []), + select: (props, $store) => get($store.getters.findUser(props.userId), 'friends', []), destory: (props, $store) => $store.dispatch('clearFriends', props.userId), childPropName: 'entries', additionalPropNames: ['userId'] @@ -31,19 +31,22 @@ const FriendList = compose( const UserProfile = { data () { return { - error: false + error: false, + fetchedUserId: null } }, created () { - this.$store.commit('clearTimeline', { timeline: 'user' }) - this.$store.commit('clearTimeline', { timeline: 'favorites' }) - this.$store.commit('clearTimeline', { timeline: 'media' }) - this.$store.dispatch('startFetching', { timeline: 'user', userId: this.fetchBy }) - this.$store.dispatch('startFetching', { timeline: 'media', userId: this.fetchBy }) - this.startFetchFavorites() if (!this.user.id) { - this.$store.dispatch('fetchUser', this.fetchBy) - .then(() => this.$store.dispatch('fetchUserRelationship', this.fetchBy)) + let fetchPromise + if (this.userId) { + fetchPromise = this.$store.dispatch('fetchUser', this.userId) + } else { + fetchPromise = this.$store.dispatch('fetchUserByScreenName', this.userName) + .then(userId => { + this.fetchedUserId = userId + }) + } + fetchPromise .catch((reason) => { const errorMessage = get(reason, 'error.error') if (errorMessage === 'No user with such user_id') { // Known error @@ -54,8 +57,7 @@ const UserProfile = { this.error = this.$t('user_profile.profile_loading_error') } }) - } else if (typeof this.user.following === 'undefined' || this.user.following === null) { - this.$store.dispatch('fetchUserRelationship', this.fetchBy) + .then(() => this.startUp()) } }, destroyed () { @@ -72,7 +74,7 @@ const UserProfile = { return this.$store.state.statuses.timelines.media }, userId () { - return this.$route.params.id || this.user.id + return this.$route.params.id || this.user.id || this.fetchedUserId }, userName () { return this.$route.params.name || this.user.screen_name @@ -82,10 +84,8 @@ const UserProfile = { this.userId === this.$store.state.users.currentUser.id }, userInStore () { - if (this.isExternal) { - return this.$store.getters.userById(this.userId) - } - return this.$store.getters.userByName(this.userName) + const routeParams = this.$route.params + return this.$store.getters.findUser(routeParams.name || routeParams.iid) }, user () { if (this.timeline.statuses[0]) { @@ -96,9 +96,6 @@ const UserProfile = { } return {} }, - fetchBy () { - return this.isExternal ? this.userId : this.userName - }, isExternal () { return this.$route.name === 'external-user-profile' }, @@ -112,13 +109,13 @@ const UserProfile = { methods: { startFetchFavorites () { if (this.isUs) { - this.$store.dispatch('startFetching', { timeline: 'favorites', userId: this.fetchBy }) + this.$store.dispatch('startFetching', { timeline: 'favorites', userId: this.userId }) } }, startUp () { - this.$store.dispatch('startFetching', { timeline: 'user', userId: this.fetchBy }) - this.$store.dispatch('startFetching', { timeline: 'media', userId: this.fetchBy }) - + this.$store.dispatch('fetchUserRelationship', this.userId) + this.$store.dispatch('startFetching', { timeline: 'user', userId: this.userId }) + this.$store.dispatch('startFetching', { timeline: 'media', userId: this.userId }) this.startFetchFavorites() }, cleanUp () { @@ -131,19 +128,11 @@ const UserProfile = { } }, watch: { - userName () { - if (this.isExternal) { - return + userId (newVal, oldVal) { + if (newVal) { + this.cleanUp() + this.startUp() } - this.cleanUp() - this.startUp() - }, - userId () { - if (!this.isExternal) { - return - } - this.cleanUp() - this.startUp() }, $route () { this.$refs.tabSwitcher.activateTab(0)() diff --git a/src/components/user_profile/user_profile.vue b/src/components/user_profile/user_profile.vue index 7d4a8b1f..d449eb85 100644 --- a/src/components/user_profile/user_profile.vue +++ b/src/components/user_profile/user_profile.vue @@ -11,7 +11,7 @@ :title="$t('user_profile.timeline_title')" :timeline="timeline" :timeline-name="'user'" - :user-id="fetchBy" + :user-id="userId" />
@@ -25,7 +25,7 @@ :embedded="true" :title="$t('user_card.media')" timeline-name="media" :timeline="media" - :user-id="fetchBy" + :user-id="userId" /> id => - state.users.find(user => user.id === id), - userByName: state => name => - state.users.find(user => user.screen_name && - (user.screen_name.toLowerCase() === name.toLowerCase()) - ) + findUser: state => query => state.usersObject[query] } export const defaultState = { @@ -165,6 +160,11 @@ const users = { return store.rootState.api.backendInteractor.fetchUser({ id }) .then((user) => store.commit('addNewUsers', [user])) }, + fetchUserByScreenName (store, screenName) { + return store.rootState.api.backendInteractor.figureOutUserId({ screenName }) + .then((qvitterUserData) => store.rootState.api.backendInteractor.fetchUser({ id: qvitterUserData.id })) + .then((user) => store.commit('addNewUsers', [user]) || user.id) + }, fetchUserRelationship (store, id) { return store.rootState.api.backendInteractor.fetchUserRelationship({ id }) .then((relationships) => store.commit('updateUserRelationship', relationships)) diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 744c2f64..5a0aa2de 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -32,6 +32,7 @@ const QVITTER_USER_NOTIFICATIONS_URL = '/api/qvitter/statuses/notifications.json const QVITTER_USER_NOTIFICATIONS_READ_URL = '/api/qvitter/statuses/notifications/read.json' const BLOCKING_URL = '/api/blocks/create.json' const UNBLOCKING_URL = '/api/blocks/destroy.json' +const USER_URL = '/api/users/show.json' const FOLLOW_IMPORT_URL = '/api/pleroma/follow_import' const DELETE_ACCOUNT_URL = '/api/pleroma/delete_account' const CHANGE_PASSWORD_URL = '/api/pleroma/change_password' @@ -41,7 +42,7 @@ const DENY_USER_URL = '/api/pleroma/friendships/deny' const SUGGESTIONS_URL = '/api/v1/suggestions' const MASTODON_USER_FAVORITES_TIMELINE_URL = '/api/v1/favourites' -const MASTODON_USER_URL = '/api/v1/accounts/' +const MASTODON_USER_URL = '/api/v1/accounts' const MASTODON_USER_RELATIONSHIPS_URL = '/api/v1/accounts/relationships' const MASTODON_USER_TIMELINE_URL = id => `/api/v1/accounts/${id}/statuses` @@ -272,6 +273,22 @@ const fetchUserRelationship = ({id, credentials}) => { }) } +// TODO remove once MastoAPI supports screen_name in fetchUser one +const figureOutUserId = ({screenName, credentials}) => { + let url = `${USER_URL}/?user_id=${screenName}` + return fetch(url, { headers: authHeaders(credentials) }) + .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) => parseUser(data)) +} + const fetchFriends = ({id, page, credentials}) => { let url = `${FRIENDS_URL}?user_id=${id}` if (page) { @@ -382,9 +399,6 @@ const fetchTimeline = ({timeline, credentials, since = false, until = false, use if (until) { params.push(['max_id', until]) } - if (userId) { - params.push(['user_id', userId]) - } if (tag) { url += `/${tag}.json` } @@ -608,6 +622,7 @@ const apiService = { unblockUser, fetchUser, fetchUserRelationship, + figureOutUserId, favorite, unfavorite, retweet, diff --git a/src/services/backend_interactor_service/backend_interactor_service.js b/src/services/backend_interactor_service/backend_interactor_service.js index cbd0b733..48689167 100644 --- a/src/services/backend_interactor_service/backend_interactor_service.js +++ b/src/services/backend_interactor_service/backend_interactor_service.js @@ -26,6 +26,10 @@ const backendInteractorService = (credentials) => { return apiService.fetchAllFollowing({username, credentials}) } + const figureOutUserId = ({screenName}) => { + return apiService.figureOutUserId({screenName, credentials}) + } + const fetchUser = ({id}) => { return apiService.fetchUser({id, credentials}) } @@ -95,6 +99,7 @@ const backendInteractorService = (credentials) => { unfollowUser, blockUser, unblockUser, + figureOutUserId, fetchUser, fetchUserRelationship, fetchAllFollowing, diff --git a/test/unit/specs/components/user_profile.spec.js b/test/unit/specs/components/user_profile.spec.js index 41fd9cd0..1524c4eb 100644 --- a/test/unit/specs/components/user_profile.spec.js +++ b/test/unit/specs/components/user_profile.spec.js @@ -13,8 +13,7 @@ const mutations = { } const testGetters = { - userByName: state => getters.userByName(state.users), - userById: state => getters.userById(state.users) + findUser: state => getters.findUser(state.users) } const localUser = { diff --git a/test/unit/specs/modules/users.spec.js b/test/unit/specs/modules/users.spec.js index 4d49ee24..dae7e580 100644 --- a/test/unit/specs/modules/users.spec.js +++ b/test/unit/specs/modules/users.spec.js @@ -43,7 +43,7 @@ describe('The users module', () => { } const name = 'Guy' const expected = { screen_name: 'Guy', id: '1' } - expect(getters.userByName(state)(name)).to.eql(expected) + expect(getters.findUser(state)(name)).to.eql(expected) }) it('returns user with matching screen_name with different case', () => { @@ -54,7 +54,7 @@ describe('The users module', () => { } const name = 'Guy' const expected = { screen_name: 'guy', id: '1' } - expect(getters.userByName(state)(name)).to.eql(expected) + expect(getters.findUser(state)(name)).to.eql(expected) }) }) @@ -67,7 +67,7 @@ describe('The users module', () => { } const id = '1' const expected = { screen_name: 'Guy', id: '1' } - expect(getters.userById(state)(id)).to.eql(expected) + expect(getters.findUser(state)(id)).to.eql(expected) }) }) }) From 690c1dcd7ac09f3e6cd4ba5778dce5bd360ee68e Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Sat, 9 Mar 2019 01:19:56 +0200 Subject: [PATCH 04/45] revert some stuff, turns out it's actually breaking. Fixed some local user things --- src/components/user_profile/user_profile.js | 53 ++++++++++++--------- src/modules/users.js | 12 ++--- 2 files changed, 34 insertions(+), 31 deletions(-) diff --git a/src/components/user_profile/user_profile.js b/src/components/user_profile/user_profile.js index 4f920ae2..0f387f66 100644 --- a/src/components/user_profile/user_profile.js +++ b/src/components/user_profile/user_profile.js @@ -37,27 +37,7 @@ const UserProfile = { }, created () { if (!this.user.id) { - let fetchPromise - if (this.userId) { - fetchPromise = this.$store.dispatch('fetchUser', this.userId) - } else { - fetchPromise = this.$store.dispatch('fetchUserByScreenName', this.userName) - .then(userId => { - this.fetchedUserId = userId - }) - } - fetchPromise - .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') - } - }) - .then(() => this.startUp()) + this.fetchUserId() } }, destroyed () { @@ -112,8 +92,30 @@ const UserProfile = { this.$store.dispatch('startFetching', { timeline: 'favorites', userId: this.userId }) } }, + fetchUserId () { + let fetchPromise + if (this.userId && !this.$route.params.name) { + fetchPromise = this.$store.dispatch('fetchUser', this.userId) + } else { + fetchPromise = this.$store.dispatch('fetchUserByScreenName', this.userName) + .then(userId => { + this.fetchedUserId = userId + }) + } + fetchPromise + .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') + } + }) + .then(() => this.startUp()) + }, startUp () { - this.$store.dispatch('fetchUserRelationship', this.userId) this.$store.dispatch('startFetching', { timeline: 'user', userId: this.userId }) this.$store.dispatch('startFetching', { timeline: 'media', userId: this.userId }) this.startFetchFavorites() @@ -134,6 +136,13 @@ const UserProfile = { this.startUp() } }, + userName (newVal, oldVal) { + if (this.$route.params.name) { + this.fetchUserId() + this.cleanUp() + this.startUp() + } + }, $route () { this.$refs.tabSwitcher.activateTab(0)() } diff --git a/src/modules/users.js b/src/modules/users.js index 4e17ebf2..e4146c31 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -108,17 +108,11 @@ export const mutations = { state.currentUser.muteIds = muteIds }, setUserForStatus (state, status) { - // Not setting it again since it's already reactive if it has getters - if (!Object.getOwnPropertyDescriptor(status.user, 'id').get) { - status.user = state.usersObject[status.user.id] - } + status.user = state.usersObject[status.user.id] }, setUserForNotification (state, notification) { - // Not setting it again since it's already reactive if it has getters - if (!Object.getOwnPropertyDescriptor(notification.action.user, 'id').get) { - notification.action.user = state.usersObject[notification.action.user.id] - notification.from_profile = state.usersObject[notification.action.user.id] - } + notification.action.user = state.usersObject[notification.action.user.id] + notification.from_profile = state.usersObject[notification.action.user.id] }, setColor (state, { user: { id }, highlighted }) { const user = state.usersObject[id] From fe624f6114220320e1528981af83fb7ab39c0e67 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Sat, 9 Mar 2019 01:34:15 +0200 Subject: [PATCH 05/45] fix reply-to marker, also whoops console log --- src/components/status/status.js | 8 ++++---- src/modules/statuses.js | 1 - 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/components/status/status.js b/src/components/status/status.js index 9e18fe15..43572fe8 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -145,11 +145,11 @@ const Status = { return !!(this.status.in_reply_to_status_id && this.status.in_reply_to_user_id) }, replyToName () { - const user = this.$store.state.users.usersObject[this.status.in_reply_to_user_id] - if (user) { - return user.screen_name - } else { + const user = this.$store.getters.findUser(this.status.in_reply_to_user_id) + if (this.status.in_reply_to_screen_name) { return this.status.in_reply_to_screen_name + } else { + return user.screen_name } }, hideReply () { diff --git a/src/modules/statuses.js b/src/modules/statuses.js index 4ee75d48..2b0215f0 100644 --- a/src/modules/statuses.js +++ b/src/modules/statuses.js @@ -135,7 +135,6 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us // This makes sure that user timeline won't get data meant for other // user. I.e. opening different user profiles makes request which could // return data late after user already viewing different user profile - console.log('TIMEINLINE', timelineObject.userId) if ((timeline === 'user' || timeline === 'media') && timelineObject.userId !== userId) { return } From a02a74e9b9b999cd6c640c541dfbfcff3b2ebd9a Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Sat, 9 Mar 2019 01:36:35 +0200 Subject: [PATCH 06/45] attempt at fixing switching to user TL --- src/components/status/status.js | 4 ++-- src/components/user_profile/user_profile.js | 11 ++++++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/components/status/status.js b/src/components/status/status.js index 43572fe8..c90da6d4 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -145,11 +145,11 @@ const Status = { return !!(this.status.in_reply_to_status_id && this.status.in_reply_to_user_id) }, replyToName () { - const user = this.$store.getters.findUser(this.status.in_reply_to_user_id) if (this.status.in_reply_to_screen_name) { return this.status.in_reply_to_screen_name } else { - return user.screen_name + const user = this.$store.getters.findUser(this.status.in_reply_to_user_id) + return user && user.screen_name } }, hideReply () { diff --git a/src/components/user_profile/user_profile.js b/src/components/user_profile/user_profile.js index 0f387f66..dc9cdeeb 100644 --- a/src/components/user_profile/user_profile.js +++ b/src/components/user_profile/user_profile.js @@ -38,6 +38,9 @@ const UserProfile = { created () { if (!this.user.id) { this.fetchUserId() + .then(() => this.startUp()) + } else { + this.startUp() } }, destroyed () { @@ -116,9 +119,11 @@ const UserProfile = { .then(() => this.startUp()) }, startUp () { - this.$store.dispatch('startFetching', { timeline: 'user', userId: this.userId }) - this.$store.dispatch('startFetching', { timeline: 'media', userId: this.userId }) - this.startFetchFavorites() + if (this.userId) { + this.$store.dispatch('startFetching', { timeline: 'user', userId: this.userId }) + this.$store.dispatch('startFetching', { timeline: 'media', userId: this.userId }) + this.startFetchFavorites() + } }, cleanUp () { this.$store.dispatch('stopFetching', 'user') From 47211fb32cb18809912db6c939fdd3a17d3d4569 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Sat, 9 Mar 2019 02:15:35 +0200 Subject: [PATCH 07/45] emoji adder --- .../entity_normalizer.service.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js index d20ce77f..633bd3dc 100644 --- a/src/services/entity_normalizer/entity_normalizer.service.js +++ b/src/services/entity_normalizer/entity_normalizer.service.js @@ -40,10 +40,10 @@ export const parseUser = (data) => { } output.name = null // missing - output.name_html = data.display_name + output.name_html = addEmojis(data.display_name, data.emojis) output.description = null // missing - output.description_html = data.note + output.description_html = addEmojis(data.note, data.emojis) // Utilize avatar_static for gif avatars? output.profile_image_url = data.avatar @@ -142,6 +142,14 @@ const parseAttachment = (data) => { return output } +const addEmojis = (string, emojis) => { + return emojis.reduce((acc, emoji) => { + return acc.replace( + new RegExp(`:${emoji.shortcode}:`, 'g'), + `${emoji.shortcode}` + ) + }, string) +} export const parseStatus = (data) => { const output = {} @@ -157,7 +165,7 @@ export const parseStatus = (data) => { output.type = data.reblog ? 'retweet' : 'status' output.nsfw = data.sensitive - output.statusnet_html = data.content + output.statusnet_html = addEmojis(data.content, data.emojis) // Not exactly the same but works? output.text = data.content @@ -176,7 +184,7 @@ export const parseStatus = (data) => { } output.summary = data.spoiler_text - output.summary_html = data.spoiler_text + output.summary_html = addEmojis(data.spoiler_text, data.emojis) output.external_url = data.url // FIXME missing!! From f3a9200b7c17de04bbedf16cb1ac5d9681ab73f4 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Sat, 9 Mar 2019 02:47:20 +0200 Subject: [PATCH 08/45] some test fixes, disabled one test for now since logic now is even more async in general --- .../specs/components/user_profile.spec.js | 3 +- test/unit/specs/modules/users.spec.js | 31 +++++++------------ 2 files changed, 13 insertions(+), 21 deletions(-) diff --git a/test/unit/specs/components/user_profile.spec.js b/test/unit/specs/components/user_profile.spec.js index 1524c4eb..23e5ce20 100644 --- a/test/unit/specs/components/user_profile.spec.js +++ b/test/unit/specs/components/user_profile.spec.js @@ -160,7 +160,8 @@ const localProfileStore = new Vuex.Store({ } }) -describe('UserProfile', () => { +// It's a little bit more complicated now +describe.skip('UserProfile', () => { it('renders external profile', () => { const wrapper = mount(UserProfile, { localVue, diff --git a/test/unit/specs/modules/users.spec.js b/test/unit/specs/modules/users.spec.js index dae7e580..a7f18dce 100644 --- a/test/unit/specs/modules/users.spec.js +++ b/test/unit/specs/modules/users.spec.js @@ -34,36 +34,27 @@ describe('The users module', () => { }) }) - describe('getUserByName', () => { + describe('findUser', () => { it('returns user with matching screen_name', () => { + const user = { screen_name: 'Guy', id: '1' } const state = { - users: [ - { screen_name: 'Guy', id: '1' } - ] + usersObject: { + 1: user, + Guy: user + } } const name = 'Guy' const expected = { screen_name: 'Guy', id: '1' } expect(getters.findUser(state)(name)).to.eql(expected) }) - it('returns user with matching screen_name with different case', () => { - const state = { - users: [ - { screen_name: 'guy', id: '1' } - ] - } - const name = 'Guy' - const expected = { screen_name: 'guy', id: '1' } - expect(getters.findUser(state)(name)).to.eql(expected) - }) - }) - - describe('getUserById', () => { it('returns user with matching id', () => { + const user = { screen_name: 'Guy', id: '1' } const state = { - users: [ - { screen_name: 'Guy', id: '1' } - ] + usersObject: { + 1: user, + Guy: user + } } const id = '1' const expected = { screen_name: 'Guy', id: '1' } From 489f840d84b0057cf9ddddcb8dc594bfc5ad628f Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Sat, 9 Mar 2019 11:54:11 +0200 Subject: [PATCH 09/45] fix error --- src/components/user_profile/user_profile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/user_profile/user_profile.js b/src/components/user_profile/user_profile.js index dc9cdeeb..2d186bc5 100644 --- a/src/components/user_profile/user_profile.js +++ b/src/components/user_profile/user_profile.js @@ -105,7 +105,7 @@ const UserProfile = { this.fetchedUserId = userId }) } - fetchPromise + return fetchPromise .catch((reason) => { const errorMessage = get(reason, 'error.error') if (errorMessage === 'No user with such user_id') { // Known error From 068c9724e45fe801ecedbea491234d0b95695629 Mon Sep 17 00:00:00 2001 From: Edijs Date: Sun, 10 Mar 2019 16:58:12 -0700 Subject: [PATCH 10/45] Added new tab to display versions of BE/FE --- src/boot/after_store.js | 6 ++++++ src/components/settings/settings.js | 17 +++++++++++++++-- src/components/settings/settings.vue | 22 ++++++++++++++++++++++ src/i18n/en.json | 5 +++++ src/modules/instance.js | 6 +++++- src/services/version/version.service.js | 10 ++++++++++ 6 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 src/services/version/version.service.js diff --git a/src/boot/after_store.js b/src/boot/after_store.js index a8e2bf35..dfaf4d68 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -176,6 +176,12 @@ const afterStoreSetup = ({ store, i18n }) => { const suggestions = metadata.suggestions store.dispatch('setInstanceOption', { name: 'suggestionsEnabled', value: suggestions.enabled }) store.dispatch('setInstanceOption', { name: 'suggestionsWeb', value: suggestions.web }) + + const software = data.software + store.dispatch('setInstanceOption', { name: 'backendVersion', value: software.version }) + + const frontendVersion = window.___pleromafe_commit_hash + store.dispatch('setInstanceOption', { name: 'frontendVersion', value: frontendVersion }) }) } diff --git a/src/components/settings/settings.js b/src/components/settings/settings.js index 979457a5..d208ee5a 100644 --- a/src/components/settings/settings.js +++ b/src/components/settings/settings.js @@ -1,8 +1,10 @@ /* eslint-env browser */ +import { filter, trim } from 'lodash' + import TabSwitcher from '../tab_switcher/tab_switcher.js' import StyleSwitcher from '../style_switcher/style_switcher.vue' import InterfaceLanguageSwitcher from '../interface_language_switcher/interface_language_switcher.vue' -import { filter, trim } from 'lodash' +import { parseBackendVersionString, parseFrontendVersionString } from '../../services/version/version.service' const settings = { data () { @@ -78,7 +80,10 @@ const settings = { // Future spec, still not supported in Nightly 63 as of 08/2018 Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'audioTracks'), playVideosInModal: user.playVideosInModal, - useContainFit: user.useContainFit + useContainFit: user.useContainFit, + + backendVersion: instance.backendVersion, + frontendVersion: instance.frontendVersion } }, components: { @@ -98,6 +103,14 @@ const settings = { }, instanceSpecificPanelPresent () { return this.$store.state.instance.showInstanceSpecificPanel } }, + methods: { + parseBackendVersion (versionString) { + return parseBackendVersionString(versionString) + }, + parseFrontendVersion (versionString) { + return parseFrontendVersionString(versionString) + } + }, watch: { hideAttachmentsLocal (value) { this.$store.dispatch('setOption', { name: 'hideAttachments', value }) diff --git a/src/components/settings/settings.vue b/src/components/settings/settings.vue index d2346747..3ca7bdc0 100644 --- a/src/components/settings/settings.vue +++ b/src/components/settings/settings.vue @@ -261,6 +261,28 @@
+
+
+
    +
  • +

    {{$t('settings.version.backend_version')}}

    +
      +
    • +
      +
    • +
    +
  • +
  • +

    {{$t('settings.version.frontend_version')}}

    +
      +
    • +
      +
    • +
    +
  • +
+
+
diff --git a/src/i18n/en.json b/src/i18n/en.json index 01fe2fba..1fe201a4 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -347,6 +347,11 @@ "checkbox": "I have skimmed over terms and conditions", "link": "a nice lil' link" } + }, + "version": { + "title": "Version", + "backend_version": "Backend Version", + "frontend_version": "Frontend Version" } }, "timeline": { diff --git a/src/modules/instance.js b/src/modules/instance.js index 24c52f9c..155aa2eb 100644 --- a/src/modules/instance.js +++ b/src/modules/instance.js @@ -48,7 +48,11 @@ const defaultState = { // Html stuff instanceSpecificPanelContent: '', - tos: '' + tos: '', + + // Version Information + backendVersion: '', + frontendVersion: '' } const instance = { diff --git a/src/services/version/version.service.js b/src/services/version/version.service.js new file mode 100644 index 00000000..6c5036c1 --- /dev/null +++ b/src/services/version/version.service.js @@ -0,0 +1,10 @@ + +export const parseBackendVersionString = versionString => { + const regex = /(-g)(\w+)$/i + const replacer = '$1$2' + return versionString.replace(regex, replacer) +} + +export const parseFrontendVersionString = versionString => { + return `#${versionString}` +} From 06d39b62a8358c911b18f5acc378047035840465 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 11 Mar 2019 02:17:49 +0200 Subject: [PATCH 11/45] fixed tests, review fixes, now storing local users with downcase screen name for better compatibility --- src/components/user_profile/user_profile.js | 7 ++++--- src/modules/statuses.js | 2 +- src/modules/users.js | 4 ++-- test/unit/specs/components/user_profile.spec.js | 14 ++++++++++---- test/unit/specs/modules/users.spec.js | 4 ++-- 5 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/components/user_profile/user_profile.js b/src/components/user_profile/user_profile.js index 2d186bc5..a8dfce2f 100644 --- a/src/components/user_profile/user_profile.js +++ b/src/components/user_profile/user_profile.js @@ -68,7 +68,7 @@ const UserProfile = { }, userInStore () { const routeParams = this.$route.params - return this.$store.getters.findUser(routeParams.name || routeParams.iid) + return this.$store.getters.findUser(routeParams.name || routeParams.id) }, user () { if (this.timeline.statuses[0]) { @@ -135,13 +135,14 @@ const UserProfile = { } }, watch: { - userId (newVal, oldVal) { + // userId can be undefined if we don't know it yet + userId (newVal) { if (newVal) { this.cleanUp() this.startUp() } }, - userName (newVal, oldVal) { + userName () { if (this.$route.params.name) { this.fetchUserId() this.cleanUp() diff --git a/src/modules/statuses.js b/src/modules/statuses.js index 2b0215f0..ea1b2de0 100644 --- a/src/modules/statuses.js +++ b/src/modules/statuses.js @@ -73,7 +73,7 @@ const mergeOrAdd = (arr, obj, item) => { if (oldItem) { // We already have this, so only merge the new info. // We ignore null values to avoid overwriting existing properties with missing data - // we also skip 'used' because that is handled by users module + // we also skip 'user' because that is handled by users module merge(oldItem, omitBy(item, (v, k) => v === null || k === 'user')) // Reactivity fix. oldItem.attachments.splice(oldItem.attachments.length) diff --git a/src/modules/users.js b/src/modules/users.js index e4146c31..5eabb1ec 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -18,7 +18,7 @@ export const mergeOrAdd = (arr, obj, item) => { arr.push(item) obj[item.id] = item if (item.screen_name && !item.screen_name.includes('@')) { - obj[item.screen_name] = item + obj[item.screen_name.toLowerCase()] = item } return { item, new: true } } @@ -132,7 +132,7 @@ export const mutations = { } export const getters = { - findUser: state => query => state.usersObject[query] + findUser: state => query => state.usersObject[typeof query === 'string' ? query.toLowerCase() : query] } export const defaultState = { diff --git a/test/unit/specs/components/user_profile.spec.js b/test/unit/specs/components/user_profile.spec.js index 23e5ce20..847481f3 100644 --- a/test/unit/specs/components/user_profile.spec.js +++ b/test/unit/specs/components/user_profile.spec.js @@ -12,6 +12,11 @@ const mutations = { setError: () => {} } +const actions = { + fetchUser: () => {}, + fetchUserByScreenName: () => {} +} + const testGetters = { findUser: state => getters.findUser(state.users) } @@ -30,6 +35,7 @@ const extUser = { const externalProfileStore = new Vuex.Store({ mutations, + actions, getters: testGetters, state: { api: { @@ -88,7 +94,7 @@ const externalProfileStore = new Vuex.Store({ currentUser: { credentials: '' }, - usersObject: [extUser], + usersObject: { 100: extUser }, users: [extUser] } } @@ -96,6 +102,7 @@ const externalProfileStore = new Vuex.Store({ const localProfileStore = new Vuex.Store({ mutations, + actions, getters: testGetters, state: { api: { @@ -154,14 +161,13 @@ const localProfileStore = new Vuex.Store({ currentUser: { credentials: '' }, - usersObject: [localUser], + usersObject: { 100: localUser, 'testuser': localUser }, users: [localUser] } } }) -// It's a little bit more complicated now -describe.skip('UserProfile', () => { +describe('UserProfile', () => { it('renders external profile', () => { const wrapper = mount(UserProfile, { localVue, diff --git a/test/unit/specs/modules/users.spec.js b/test/unit/specs/modules/users.spec.js index a7f18dce..c8bc0ae7 100644 --- a/test/unit/specs/modules/users.spec.js +++ b/test/unit/specs/modules/users.spec.js @@ -40,7 +40,7 @@ describe('The users module', () => { const state = { usersObject: { 1: user, - Guy: user + guy: user } } const name = 'Guy' @@ -53,7 +53,7 @@ describe('The users module', () => { const state = { usersObject: { 1: user, - Guy: user + guy: user } } const id = '1' From 8952761370a9fc08a3f931940050ccbdd8df9767 Mon Sep 17 00:00:00 2001 From: Edijs Date: Sun, 10 Mar 2019 18:06:51 -0700 Subject: [PATCH 12/45] Version links to BE/FE --- src/components/settings/settings.js | 17 +++++++++-------- src/components/settings/settings.vue | 4 ++-- src/services/version/version.service.js | 12 ++++-------- 3 files changed, 15 insertions(+), 18 deletions(-) diff --git a/src/components/settings/settings.js b/src/components/settings/settings.js index d208ee5a..3579f6db 100644 --- a/src/components/settings/settings.js +++ b/src/components/settings/settings.js @@ -4,7 +4,10 @@ import { filter, trim } from 'lodash' import TabSwitcher from '../tab_switcher/tab_switcher.js' import StyleSwitcher from '../style_switcher/style_switcher.vue' import InterfaceLanguageSwitcher from '../interface_language_switcher/interface_language_switcher.vue' -import { parseBackendVersionString, parseFrontendVersionString } from '../../services/version/version.service' +import { extractCommit } from '../../services/version/version.service' + +const pleromaFeCommitUrl = 'https://git.pleroma.social/pleroma/pleroma-fe/commit/' +const pleromaBeCommitUrl = 'https://git.pleroma.social/pleroma/pleroma/commit/' const settings = { data () { @@ -101,14 +104,12 @@ const settings = { postFormats () { return this.$store.state.instance.postFormats || [] }, - instanceSpecificPanelPresent () { return this.$store.state.instance.showInstanceSpecificPanel } - }, - methods: { - parseBackendVersion (versionString) { - return parseBackendVersionString(versionString) + instanceSpecificPanelPresent () { return this.$store.state.instance.showInstanceSpecificPanel }, + frontendVersionLink () { + return pleromaFeCommitUrl + this.$store.state.instance.frontendVersion }, - parseFrontendVersion (versionString) { - return parseFrontendVersionString(versionString) + backendVersionLink () { + return pleromaBeCommitUrl + extractCommit(this.$store.state.instance.backendVersion) } }, watch: { diff --git a/src/components/settings/settings.vue b/src/components/settings/settings.vue index 3ca7bdc0..17f1f1a1 100644 --- a/src/components/settings/settings.vue +++ b/src/components/settings/settings.vue @@ -268,7 +268,7 @@

{{$t('settings.version.backend_version')}}

@@ -276,7 +276,7 @@

{{$t('settings.version.frontend_version')}}

diff --git a/src/services/version/version.service.js b/src/services/version/version.service.js index 6c5036c1..a750b0dd 100644 --- a/src/services/version/version.service.js +++ b/src/services/version/version.service.js @@ -1,10 +1,6 @@ -export const parseBackendVersionString = versionString => { - const regex = /(-g)(\w+)$/i - const replacer = '$1$2' - return versionString.replace(regex, replacer) -} - -export const parseFrontendVersionString = versionString => { - return `#${versionString}` +export const extractCommit = versionString => { + const regex = /-g(\w+)$/i + const matches = versionString.match(regex) + return matches ? matches[1] : '' } From 9c60934786f0ca11d64550b1f290e1e8d63d8b9d Mon Sep 17 00:00:00 2001 From: Edijs Date: Sun, 10 Mar 2019 18:13:01 -0700 Subject: [PATCH 13/45] Code refactoring --- src/components/settings/settings.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/settings/settings.js b/src/components/settings/settings.js index 3579f6db..b77c5197 100644 --- a/src/components/settings/settings.js +++ b/src/components/settings/settings.js @@ -106,10 +106,10 @@ const settings = { }, instanceSpecificPanelPresent () { return this.$store.state.instance.showInstanceSpecificPanel }, frontendVersionLink () { - return pleromaFeCommitUrl + this.$store.state.instance.frontendVersion + return pleromaFeCommitUrl + this.frontendVersion }, backendVersionLink () { - return pleromaBeCommitUrl + extractCommit(this.$store.state.instance.backendVersion) + return pleromaBeCommitUrl + extractCommit(this.backendVersion) } }, watch: { From 4efcda1b4137fa14659a586d4d8559dcdd0f479b Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 11 Mar 2019 22:41:08 +0200 Subject: [PATCH 14/45] Added some tests --- .../entity_normalizer.service.js | 2 +- .../entity_normalizer.spec.js | 75 ++++++++++++++++++- 2 files changed, 75 insertions(+), 2 deletions(-) diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js index 633bd3dc..b7e5711e 100644 --- a/src/services/entity_normalizer/entity_normalizer.service.js +++ b/src/services/entity_normalizer/entity_normalizer.service.js @@ -142,7 +142,7 @@ const parseAttachment = (data) => { return output } -const addEmojis = (string, emojis) => { +export const addEmojis = (string, emojis) => { return emojis.reduce((acc, emoji) => { return acc.replace( new RegExp(`:${emoji.shortcode}:`, 'g'), diff --git a/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js b/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js index 6245361c..2b0b0d6d 100644 --- a/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js +++ b/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js @@ -1,4 +1,4 @@ -import { parseStatus, parseUser, parseNotification } from '../../../../../src/services/entity_normalizer/entity_normalizer.service.js' +import { parseStatus, parseUser, parseNotification, addEmojis } from '../../../../../src/services/entity_normalizer/entity_normalizer.service.js' import mastoapidata from '../../../../fixtures/mastoapi.json' import qvitterapidata from '../../../../fixtures/statuses.json' @@ -143,6 +143,23 @@ const makeMockNotificationQvitter = (overrides = {}) => { }, overrides) } +const makeMockEmojiMasto = (overrides = [{}]) => { + return [ + Object.assign({ + shortcode: 'image', + static_url: 'https://example.com/image.png', + url: 'https://example.com/image.png', + visible_in_picker: false + }, overrides[0]), + Object.assign({ + shortcode: 'thinking', + static_url: 'https://example.com/think.png', + url: 'https://example.com/think.png', + visible_in_picker: false + }, overrides[1]) + ] +} + parseNotification parseUser parseStatus @@ -218,6 +235,22 @@ describe('API Entities normalizer', () => { expect(parsedRepeat).to.have.property('retweeted_status') expect(parsedRepeat).to.have.deep.property('retweeted_status.id', 'deadbeef') }) + + it('adds emojis to post content', () => { + const post = makeMockStatusMasto({ emojis: makeMockEmojiMasto(), content: 'Makes you think :thinking:' }) + + const parsedPost = parseStatus(post) + + expect(parsedPost).to.have.property('statusnet_html').that.contains(' { + const post = makeMockStatusMasto({ emojis: makeMockEmojiMasto(), spoiler_text: 'CW: 300 IQ :thinking:' }) + + const parsedPost = parseStatus(post) + + expect(parsedPost).to.have.property('summary_html').that.contains(' { expect(parseUser(local)).to.have.property('is_local', true) expect(parseUser(remote)).to.have.property('is_local', false) }) + + it('adds emojis to user name', () => { + const user = makeMockUserMasto({ emojis: makeMockEmojiMasto(), display_name: 'The :thinking: thinker' }) + + const parsedUser = parseUser(user) + + expect(parsedUser).to.have.property('name_html').that.contains(' { + const user = makeMockUserMasto({ emojis: makeMockEmojiMasto(), note: 'Hello i like to :thinking: a lot' }) + + const parsedUser = parseUser(user) + + expect(parsedUser).to.have.property('description_html').that.contains(' { expect(parseNotification(notif)).to.have.deep.property('from_profile.id', 'spurdo') }) }) + + describe('MastoAPI emoji adder', () => { + const emojis = makeMockEmojiMasto() + const imageHtml = 'image' + .replace(/"/g, '\'') + const thinkHtml = 'thinking' + .replace(/"/g, '\'') + + it('correctly replaces shortcodes in supplied string', () => { + const result = addEmojis('This post has :image: emoji and :thinking: emoji', emojis) + expect(result).to.include(thinkHtml) + expect(result).to.include(imageHtml) + }) + + it('handles consecutive emojis correctly', () => { + const result = addEmojis('Lelel emoji spam :thinking::thinking::thinking::thinking:', emojis) + expect(result).to.include(thinkHtml + thinkHtml + thinkHtml + thinkHtml) + }) + + it('Doesn\'t replace nonexistent emojis', () => { + const result = addEmojis('Admin add the :tenshi: emoji', emojis) + expect(result).to.equal('Admin add the :tenshi: emoji') + }) + }) }) From 130fb7ae1e037ad6fe3653e31185f76b4e0c281f Mon Sep 17 00:00:00 2001 From: dave Date: Mon, 11 Mar 2019 16:48:27 -0400 Subject: [PATCH 15/45] #434 - fix plain text issue --- src/i18n/ar.json | 2 +- src/i18n/ca.json | 2 +- src/i18n/cs.json | 2 +- src/i18n/de.json | 2 +- src/i18n/eo.json | 2 +- src/i18n/es.json | 2 +- src/i18n/fi.json | 2 +- src/i18n/fr.json | 2 +- src/i18n/ga.json | 2 +- src/i18n/he.json | 2 +- src/i18n/it.json | 2 +- src/i18n/ja.json | 2 +- src/i18n/ko.json | 2 +- src/i18n/nb.json | 2 +- src/i18n/nl.json | 2 +- src/i18n/oc.json | 2 +- src/i18n/pt.json | 2 +- src/i18n/zh.json | 2 +- 18 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/i18n/ar.json b/src/i18n/ar.json index 242dab78..72e3010f 100644 --- a/src/i18n/ar.json +++ b/src/i18n/ar.json @@ -49,7 +49,7 @@ "account_not_locked_warning_link": "مقفل", "attachments_sensitive": "اعتبر المرفقات كلها كمحتوى حساس", "content_type": { - "plain_text": "نص صافٍ" + "text/plain": "نص صافٍ" }, "content_warning": "الموضوع (اختياري)", "default": "وصلت للتوّ إلى لوس أنجلس.", diff --git a/src/i18n/ca.json b/src/i18n/ca.json index d2f285df..8fa3a88b 100644 --- a/src/i18n/ca.json +++ b/src/i18n/ca.json @@ -49,7 +49,7 @@ "account_not_locked_warning_link": "bloquejat", "attachments_sensitive": "Marca l'adjunt com a delicat", "content_type": { - "plain_text": "Text pla" + "text/plain": "Text pla" }, "content_warning": "Assumpte (opcional)", "default": "Em sento…", diff --git a/src/i18n/cs.json b/src/i18n/cs.json index 51e9d342..020092a6 100644 --- a/src/i18n/cs.json +++ b/src/i18n/cs.json @@ -71,7 +71,7 @@ "account_not_locked_warning_link": "uzamčen", "attachments_sensitive": "Označovat přílohy jako citlivé", "content_type": { - "plain_text": "Prostý text", + "text/plain": "Prostý text", "text/html": "HTML", "text/markdown": "Markdown" }, diff --git a/src/i18n/de.json b/src/i18n/de.json index 07d44348..fa9db16c 100644 --- a/src/i18n/de.json +++ b/src/i18n/de.json @@ -55,7 +55,7 @@ "account_not_locked_warning_link": "gesperrt", "attachments_sensitive": "Anhänge als heikel markieren", "content_type": { - "plain_text": "Nur Text" + "text/plain": "Nur Text" }, "content_warning": "Betreff (optional)", "default": "Sitze gerade im Hofbräuhaus.", diff --git a/src/i18n/eo.json b/src/i18n/eo.json index 34851a44..6c5b3a74 100644 --- a/src/i18n/eo.json +++ b/src/i18n/eo.json @@ -71,7 +71,7 @@ "account_not_locked_warning_link": "ŝlosita", "attachments_sensitive": "Marki kunsendaĵojn kiel konsternajn", "content_type": { - "plain_text": "Plata teksto" + "text/plain": "Plata teksto" }, "content_warning": "Temo (malnepra)", "default": "Ĵus alvenis al la Universala Kongreso!", diff --git a/src/i18n/es.json b/src/i18n/es.json index fe96dd08..a692eef9 100644 --- a/src/i18n/es.json +++ b/src/i18n/es.json @@ -61,7 +61,7 @@ "account_not_locked_warning_link": "bloqueada", "attachments_sensitive": "Contenido sensible", "content_type": { - "plain_text": "Texto Plano" + "text/plain": "Texto Plano" }, "content_warning": "Tema (opcional)", "default": "Acabo de aterrizar en L.A.", diff --git a/src/i18n/fi.json b/src/i18n/fi.json index 4f0ffb4b..fbe676cf 100644 --- a/src/i18n/fi.json +++ b/src/i18n/fi.json @@ -60,7 +60,7 @@ "account_not_locked_warning_link": "lukittu", "attachments_sensitive": "Merkkaa liitteet arkaluonteisiksi", "content_type": { - "plain_text": "Tavallinen teksti" + "text/plain": "Tavallinen teksti" }, "content_warning": "Aihe (valinnainen)", "default": "Tulin juuri saunasta.", diff --git a/src/i18n/fr.json b/src/i18n/fr.json index 1209556a..8f9f243e 100644 --- a/src/i18n/fr.json +++ b/src/i18n/fr.json @@ -51,7 +51,7 @@ "account_not_locked_warning_link": "verrouillé", "attachments_sensitive": "Marquer le média comme sensible", "content_type": { - "plain_text": "Texte brut" + "text/plain": "Texte brut" }, "content_warning": "Sujet (optionnel)", "default": "Écrivez ici votre prochain statut.", diff --git a/src/i18n/ga.json b/src/i18n/ga.json index 5be9297a..31250876 100644 --- a/src/i18n/ga.json +++ b/src/i18n/ga.json @@ -49,7 +49,7 @@ "account_not_locked_warning_link": "faoi glas", "attachments_sensitive": "Marcáil ceangaltán mar íogair", "content_type": { - "plain_text": "Gnáth-théacs" + "text/plain": "Gnáth-théacs" }, "content_warning": "Teideal (roghnach)", "default": "Lá iontach anseo i nGaillimh", diff --git a/src/i18n/he.json b/src/i18n/he.json index 213e6170..ea581e05 100644 --- a/src/i18n/he.json +++ b/src/i18n/he.json @@ -49,7 +49,7 @@ "account_not_locked_warning_link": "נעול", "attachments_sensitive": "סמן מסמכים מצורפים כלא בטוחים לצפייה", "content_type": { - "plain_text": "טקסט פשוט" + "text/plain": "טקסט פשוט" }, "content_warning": "נושא (נתון לבחירה)", "default": "הרגע נחת ב-ל.א.", diff --git a/src/i18n/it.json b/src/i18n/it.json index 385d21aa..f441292e 100644 --- a/src/i18n/it.json +++ b/src/i18n/it.json @@ -175,7 +175,7 @@ "account_not_locked_warning_link": "bloccato", "attachments_sensitive": "Segna allegati come sensibili", "content_type": { - "plain_text": "Testo normale" + "text/plain": "Testo normale" }, "content_warning": "Oggetto (facoltativo)", "default": "Appena atterrato in L.A.", diff --git a/src/i18n/ja.json b/src/i18n/ja.json index f39a5a7c..b77f5531 100644 --- a/src/i18n/ja.json +++ b/src/i18n/ja.json @@ -61,7 +61,7 @@ "account_not_locked_warning_link": "ロックされたアカウント", "attachments_sensitive": "ファイルをNSFWにする", "content_type": { - "plain_text": "プレーンテキスト" + "text/plain": "プレーンテキスト" }, "content_warning": "せつめい (かかなくてもよい)", "default": "はねだくうこうに、つきました。", diff --git a/src/i18n/ko.json b/src/i18n/ko.json index 336e464f..402a354c 100644 --- a/src/i18n/ko.json +++ b/src/i18n/ko.json @@ -56,7 +56,7 @@ "account_not_locked_warning_link": "잠김", "attachments_sensitive": "첨부물을 민감함으로 설정", "content_type": { - "plain_text": "평문" + "text/plain": "평문" }, "content_warning": "주제 (필수 아님)", "default": "LA에 도착!", diff --git a/src/i18n/nb.json b/src/i18n/nb.json index 39e054f7..298dc0b9 100644 --- a/src/i18n/nb.json +++ b/src/i18n/nb.json @@ -49,7 +49,7 @@ "account_not_locked_warning_link": "låst", "attachments_sensitive": "Merk vedlegg som sensitive", "content_type": { - "plain_text": "Klar tekst" + "text/plain": "Klar tekst" }, "content_warning": "Tema (valgfritt)", "default": "Landet akkurat i L.A.", diff --git a/src/i18n/nl.json b/src/i18n/nl.json index 799e22b9..7e2f0604 100644 --- a/src/i18n/nl.json +++ b/src/i18n/nl.json @@ -57,7 +57,7 @@ "account_not_locked_warning_link": "gesloten", "attachments_sensitive": "Markeer bijlage als gevoelig", "content_type": { - "plain_text": "Gewone tekst" + "text/plain": "Gewone tekst" }, "content_warning": "Onderwerp (optioneel)", "default": "Tijd voor een pauze!", diff --git a/src/i18n/oc.json b/src/i18n/oc.json index fd5ccc97..baac3d25 100644 --- a/src/i18n/oc.json +++ b/src/i18n/oc.json @@ -71,7 +71,7 @@ "account_not_locked_warning_link": "clavat", "attachments_sensitive": "Marcar las pèças juntas coma sensiblas", "content_type": { - "plain_text": "Tèxte brut" + "text/plain": "Tèxte brut" }, "content_warning": "Avís de contengut (opcional)", "default": "Escrivètz aquí vòstre estatut.", diff --git a/src/i18n/pt.json b/src/i18n/pt.json index cbc2c9a3..29ab995b 100644 --- a/src/i18n/pt.json +++ b/src/i18n/pt.json @@ -71,7 +71,7 @@ "account_not_locked_warning_link": "fechada", "attachments_sensitive": "Marcar anexos como sensíveis", "content_type": { - "plain_text": "Texto puro" + "text/plain": "Texto puro" }, "content_warning": "Assunto (opcional)", "default": "Acabei de chegar no Rio!", diff --git a/src/i18n/zh.json b/src/i18n/zh.json index 089a98e2..da6dae5f 100644 --- a/src/i18n/zh.json +++ b/src/i18n/zh.json @@ -49,7 +49,7 @@ "account_not_locked_warning_link": "上锁", "attachments_sensitive": "标记附件为敏感内容", "content_type": { - "plain_text": "纯文本" + "text/plain": "纯文本" }, "content_warning": "主题(可选)", "default": "刚刚抵达上海", From a6a162177b8347917494ddd18de9d239b15bd7fa Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 11 Mar 2019 23:03:55 +0200 Subject: [PATCH 16/45] instead of filtering nulls, let's just not have them in the first place --- src/modules/users.js | 6 +++--- .../entity_normalizer.service.js | 15 +++++++-------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/modules/users.js b/src/modules/users.js index 5eabb1ec..d04c7f0b 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -1,5 +1,5 @@ import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js' -import { compact, map, each, merge, find, omitBy } from 'lodash' +import { compact, map, each, merge, find } from 'lodash' import { set } from 'vue' import { registerPushNotifications, unregisterPushNotifications } from '../services/push/push.js' import oauthApi from '../services/new_api/oauth' @@ -11,7 +11,7 @@ export const mergeOrAdd = (arr, obj, item) => { const oldItem = obj[item.id] if (oldItem) { // We already have this, so only merge the new info. - merge(oldItem, omitBy(item, _ => _ === null)) + merge(oldItem, item) return { item: oldItem, new: false } } else { // This is a new item, prepare it @@ -39,7 +39,7 @@ export const mutations = { }, setCurrentUser (state, user) { state.lastLoginName = user.screen_name - state.currentUser = merge(state.currentUser || {}, omitBy(user, _ => _ === null)) + state.currentUser = merge(state.currentUser || {}, user) }, clearCurrentUser (state) { state.currentUser = false diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js index d20ce77f..7c840552 100644 --- a/src/services/entity_normalizer/entity_normalizer.service.js +++ b/src/services/entity_normalizer/entity_normalizer.service.js @@ -39,10 +39,10 @@ export const parseUser = (data) => { return output } - output.name = null // missing + // output.name = ??? missing output.name_html = data.display_name - output.description = null // missing + // output.description = ??? missing output.description_html = data.note // Utilize avatar_static for gif avatars? @@ -83,7 +83,7 @@ export const parseUser = (data) => { output.friends_count = data.friends_count - output.bot = null // missing + // output.bot = ??? missing output.statusnet_profile_url = data.statusnet_profile_url @@ -134,7 +134,7 @@ const parseAttachment = (data) => { output.meta = data.meta // not present in BE yet } else { output.mimetype = data.mimetype - output.meta = null // missing + // output.meta = ??? missing } output.url = data.url @@ -166,7 +166,7 @@ export const parseStatus = (data) => { output.in_reply_to_user_id = data.in_reply_to_account_id // Missing!! fix in UI? - output.in_reply_to_screen_name = null + // output.in_reply_to_screen_name = ??? // Not exactly the same but works output.statusnet_conversation_id = data.id @@ -179,8 +179,7 @@ export const parseStatus = (data) => { output.summary_html = data.spoiler_text output.external_url = data.url - // FIXME missing!! - output.is_local = false + // output.is_local = ??? missing } else { output.favorited = data.favorited output.fave_num = data.fave_num @@ -259,7 +258,7 @@ export const parseNotification = (data) => { if (masto) { output.type = mastoDict[data.type] || data.type - output.seen = null // missing + // output.seen = ??? missing output.status = parseStatus(data.status) output.action = output.status // not sure output.from_profile = parseUser(data.account) From 03f1b9525213daac521dea3fbdc1b7f70e5e0064 Mon Sep 17 00:00:00 2001 From: Rond Date: Mon, 11 Mar 2019 18:26:40 -0300 Subject: [PATCH 17/45] fixing typos and badly translated strings --- src/i18n/pt.json | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/i18n/pt.json b/src/i18n/pt.json index cbc2c9a3..3c2d2be4 100644 --- a/src/i18n/pt.json +++ b/src/i18n/pt.json @@ -51,7 +51,7 @@ "public_tl": "Linha do tempo pública", "timeline": "Linha do tempo", "twkn": "Toda a rede conhecida", - "user_search": "Busca de usuário", + "user_search": "Buscar usuários", "who_to_follow": "Quem seguir", "preferences": "Preferências" }, @@ -67,8 +67,8 @@ }, "post_status": { "new_status": "Postar novo status", - "account_not_locked_warning": "Sua conta não está {0}. Qualquer pessoa pode te seguir para ver seus posts restritos.", - "account_not_locked_warning_link": "fechada", + "account_not_locked_warning": "Sua conta não é {0}. Qualquer pessoa pode te seguir e ver seus posts privados (só para seguidores).", + "account_not_locked_warning_link": "restrita", "attachments_sensitive": "Marcar anexos como sensíveis", "content_type": { "plain_text": "Texto puro" @@ -115,7 +115,7 @@ "avatarRadius": "Avatares", "background": "Pano de Fundo", "bio": "Biografia", - "blocks_tab": "Blocos", + "blocks_tab": "Bloqueios", "btnRadius": "Botões", "cBlue": "Azul (Responder, seguir)", "cGreen": "Verde (Repetir)", @@ -125,7 +125,7 @@ "change_password_error": "Houve um erro ao modificar sua senha.", "changed_password": "Senha modificada com sucesso!", "collapse_subject": "Esconder posts com assunto", - "composing": "Escrevendo", + "composing": "Escrita", "confirm_new_password": "Confirmar nova senha", "current_avatar": "Seu avatar atual", "current_password": "Sua senha atual", @@ -139,7 +139,7 @@ "avatar_size_instruction": "O tamanho mínimo recomendado para imagens de avatar é 150x150 pixels.", "export_theme": "Salvar predefinições", "filtering": "Filtragem", - "filtering_explanation": "Todas as postagens contendo estas palavras serão silenciadas, uma por linha.", + "filtering_explanation": "Todas as postagens contendo estas palavras serão silenciadas; uma palavra por linha.", "follow_export": "Exportar quem você segue", "follow_export_button": "Exportar quem você segue para um arquivo CSV", "follow_export_processing": "Processando. Em breve você receberá a solicitação de download do arquivo", @@ -178,7 +178,7 @@ "name_bio": "Nome & Biografia", "new_password": "Nova senha", "notification_visibility": "Tipos de notificação para mostrar", - "notification_visibility_follows": "Seguidos", + "notification_visibility_follows": "Seguidas", "notification_visibility_likes": "Favoritos", "notification_visibility_mentions": "Menções", "notification_visibility_repeats": "Repetições", @@ -187,7 +187,7 @@ "no_mutes": "Sem silenciados", "hide_follows_description": "Não mostrar quem estou seguindo", "hide_followers_description": "Não mostrar quem me segue", - "show_admin_badge": "Mostrar distintivo de Administrador em meu perfil", + "show_admin_badge": "Mostrar título de Administrador em meu perfil", "show_moderator_badge": "Mostrar título de Moderador em meu perfil", "nsfw_clickthrough": "Habilitar clique para ocultar anexos sensíveis", "oauth_tokens": "Token OAuth", @@ -201,9 +201,9 @@ "profile_background": "Pano de fundo de perfil", "profile_banner": "Capa de perfil", "profile_tab": "Perfil", - "radii_help": "Arredondar arestas da interface (em píxeis)", + "radii_help": "Arredondar arestas da interface (em pixel)", "replies_in_timeline": "Respostas na linha do tempo", - "reply_link_preview": "Habilitar a pré-visualização de link de respostas ao passar o mouse.", + "reply_link_preview": "Habilitar a pré-visualização de de respostas ao passar o mouse.", "reply_visibility_all": "Mostrar todas as respostas", "reply_visibility_following": "Só mostrar respostas direcionadas a mim ou a usuários que sigo", "reply_visibility_self": "Só mostrar respostas direcionadas a mim", @@ -212,7 +212,7 @@ "security_tab": "Segurança", "scope_copy": "Copiar opções de privacidade ao responder (Mensagens diretas sempre copiam)", "set_new_avatar": "Alterar avatar", - "set_new_profile_background": "Alterar o plano de fundo de perfil", + "set_new_profile_background": "Alterar o pano de fundo de perfil", "set_new_profile_banner": "Alterar capa de perfil", "settings": "Configurações", "subject_input_always_show": "Sempre mostrar campo de assunto", @@ -220,9 +220,9 @@ "subject_line_email": "Como em email: \"re: assunto\"", "subject_line_mastodon": "Como o Mastodon: copiar como está", "subject_line_noop": "Não copiar", - "post_status_content_type": "Postar tipo de conteúdo do status", - "stop_gifs": "Reproduzir GIFs ao passar o cursor em cima", - "streaming": "Habilitar o fluxo automático de postagens quando ao topo da página", + "post_status_content_type": "Tipo de conteúdo do status", + "stop_gifs": "Reproduzir GIFs ao passar o cursor", + "streaming": "Habilitar o fluxo automático de postagens no topo da página", "text": "Texto", "theme": "Tema", "theme_help": "Use cores em código hexadecimal (#rrggbb) para personalizar seu esquema de cores.", @@ -235,7 +235,7 @@ "false": "não", "true": "sim" }, - "notifications": "Notifications", + "notifications": "Notificações", "enable_web_push_notifications": "Habilitar notificações web push", "style": { "switcher": { @@ -245,7 +245,7 @@ "keep_roundness": "Manter arredondado", "keep_fonts": "Manter fontes", "save_load_hint": "Manter as opções preserva as opções atuais ao selecionar ou carregar temas; também salva as opções ao exportar um tempo. Quanto todos os campos estiverem desmarcados, tudo será salvo ao exportar o tema.", - "reset": "Voltar ao padrão", + "reset": "Restaurar o padrão", "clear_all": "Limpar tudo", "clear_opacity": "Limpar opacidade" }, @@ -319,7 +319,7 @@ }, "fonts": { "_tab_label": "Fontes", - "help": "Selecionar fonte dos elementos da interface. Para fonte \"personalizada\" você deve entrar exatamente o nome da fonte no sistema.", + "help": "Selecione as fontes dos elementos da interface. Para fonte \"personalizada\" você deve inserir o mesmo nome da fonte no sistema.", "components": { "interface": "Interface", "input": "Campo de entrada", @@ -383,7 +383,7 @@ "mute": "Silenciar", "muted": "Silenciado", "per_day": "por dia", - "remote_follow": "Seguidor Remoto", + "remote_follow": "Seguir remotamente", "statuses": "Postagens", "unblock": "Desbloquear", "unblock_progress": "Desbloqueando...", From b9877a4323ab0025c14a8126eb2f6268a4bea6ac Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 11 Mar 2019 23:08:14 +0200 Subject: [PATCH 18/45] actually use embedded relationship if it's present --- .../entity_normalizer/entity_normalizer.service.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js index 7c840552..37c1f544 100644 --- a/src/services/entity_normalizer/entity_normalizer.service.js +++ b/src/services/entity_normalizer/entity_normalizer.service.js @@ -59,10 +59,13 @@ export const parseUser = (data) => { output.statusnet_profile_url = data.url if (data.pleroma) { - const pleroma = data.pleroma - output.follows_you = pleroma.follows_you - output.statusnet_blocking = pleroma.statusnet_blocking - output.muted = pleroma.muted + const relationship = data.pleroma.relationship + + if (relationship) { + output.follows_you = relationship.follows_you + output.statusnet_blocking = relationship.statusnet_blocking + output.muted = relationship.muted + } } // Missing, trying to recover From ce8b5fcd11aed0dedf8dd8c16190e9f1a826da2d Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 12 Mar 2019 21:49:03 +0200 Subject: [PATCH 19/45] fix embedded relationship card parsing --- .../entity_normalizer/entity_normalizer.service.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js index 37c1f544..9f9db563 100644 --- a/src/services/entity_normalizer/entity_normalizer.service.js +++ b/src/services/entity_normalizer/entity_normalizer.service.js @@ -62,9 +62,10 @@ export const parseUser = (data) => { const relationship = data.pleroma.relationship if (relationship) { - output.follows_you = relationship.follows_you - output.statusnet_blocking = relationship.statusnet_blocking - output.muted = relationship.muted + output.follows_you = relationship.followed_by + output.following = relationship.following + output.statusnet_blocking = relationship.blocking + output.muted = relationship.muting } } From 27cbe3ca658e3f9a40650f119854cd7d31094085 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 12 Mar 2019 22:10:22 +0200 Subject: [PATCH 20/45] =?UTF-8?q?=E3=83=AC=E3=82=A4=E3=83=B3=E3=81=9B?= =?UTF-8?q?=E3=82=93=E3=81=B1=E3=81=84=E3=81=AB=E3=82=B5=E3=83=B3=E3=82=AD?= =?UTF-8?q?=E3=83=A5=E3=83=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/user_profile/user_profile.js | 7 ++++--- src/modules/users.js | 10 ++++------ src/services/api/api.service.js | 18 ------------------ .../backend_interactor_service.js | 5 ----- 4 files changed, 8 insertions(+), 32 deletions(-) diff --git a/src/components/user_profile/user_profile.js b/src/components/user_profile/user_profile.js index a8dfce2f..216ac392 100644 --- a/src/components/user_profile/user_profile.js +++ b/src/components/user_profile/user_profile.js @@ -100,9 +100,10 @@ const UserProfile = { if (this.userId && !this.$route.params.name) { fetchPromise = this.$store.dispatch('fetchUser', this.userId) } else { - fetchPromise = this.$store.dispatch('fetchUserByScreenName', this.userName) - .then(userId => { - this.fetchedUserId = userId + fetchPromise = this.$store.dispatch('fetchUser', this.userName) + .then(({ id }) => { + console.log(arguments) + this.fetchedUserId = id }) } return fetchPromise diff --git a/src/modules/users.js b/src/modules/users.js index d04c7f0b..27114684 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -152,12 +152,10 @@ const users = { actions: { fetchUser (store, id) { return store.rootState.api.backendInteractor.fetchUser({ id }) - .then((user) => store.commit('addNewUsers', [user])) - }, - fetchUserByScreenName (store, screenName) { - return store.rootState.api.backendInteractor.figureOutUserId({ screenName }) - .then((qvitterUserData) => store.rootState.api.backendInteractor.fetchUser({ id: qvitterUserData.id })) - .then((user) => store.commit('addNewUsers', [user]) || user.id) + .then((user) => { + store.commit('addNewUsers', [user]) + return user + }) }, fetchUserRelationship (store, id) { return store.rootState.api.backendInteractor.fetchUserRelationship({ id }) diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 5a0aa2de..1c6703b7 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -32,7 +32,6 @@ const QVITTER_USER_NOTIFICATIONS_URL = '/api/qvitter/statuses/notifications.json const QVITTER_USER_NOTIFICATIONS_READ_URL = '/api/qvitter/statuses/notifications/read.json' const BLOCKING_URL = '/api/blocks/create.json' const UNBLOCKING_URL = '/api/blocks/destroy.json' -const USER_URL = '/api/users/show.json' const FOLLOW_IMPORT_URL = '/api/pleroma/follow_import' const DELETE_ACCOUNT_URL = '/api/pleroma/delete_account' const CHANGE_PASSWORD_URL = '/api/pleroma/change_password' @@ -273,22 +272,6 @@ const fetchUserRelationship = ({id, credentials}) => { }) } -// TODO remove once MastoAPI supports screen_name in fetchUser one -const figureOutUserId = ({screenName, credentials}) => { - let url = `${USER_URL}/?user_id=${screenName}` - return fetch(url, { headers: authHeaders(credentials) }) - .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) => parseUser(data)) -} - const fetchFriends = ({id, page, credentials}) => { let url = `${FRIENDS_URL}?user_id=${id}` if (page) { @@ -622,7 +605,6 @@ const apiService = { unblockUser, fetchUser, fetchUserRelationship, - figureOutUserId, favorite, unfavorite, retweet, diff --git a/src/services/backend_interactor_service/backend_interactor_service.js b/src/services/backend_interactor_service/backend_interactor_service.js index 48689167..cbd0b733 100644 --- a/src/services/backend_interactor_service/backend_interactor_service.js +++ b/src/services/backend_interactor_service/backend_interactor_service.js @@ -26,10 +26,6 @@ const backendInteractorService = (credentials) => { return apiService.fetchAllFollowing({username, credentials}) } - const figureOutUserId = ({screenName}) => { - return apiService.figureOutUserId({screenName, credentials}) - } - const fetchUser = ({id}) => { return apiService.fetchUser({id, credentials}) } @@ -99,7 +95,6 @@ const backendInteractorService = (credentials) => { unfollowUser, blockUser, unblockUser, - figureOutUserId, fetchUser, fetchUserRelationship, fetchAllFollowing, From 644eba87fe1d290bfce298a0b744375608a688f3 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 12 Mar 2019 22:14:41 +0200 Subject: [PATCH 21/45] whoops --- src/components/user_profile/user_profile.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/user_profile/user_profile.js b/src/components/user_profile/user_profile.js index 216ac392..1bf4a86d 100644 --- a/src/components/user_profile/user_profile.js +++ b/src/components/user_profile/user_profile.js @@ -102,7 +102,6 @@ const UserProfile = { } else { fetchPromise = this.$store.dispatch('fetchUser', this.userName) .then(({ id }) => { - console.log(arguments) this.fetchedUserId = id }) } From 1387dfb889f8b59d7790cf794cb6498a0f308607 Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 13 Mar 2019 11:29:45 +0100 Subject: [PATCH 22/45] Load persistedStated with async/await. --- src/main.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main.js b/src/main.js index a3265e3a..9ffc3727 100644 --- a/src/main.js +++ b/src/main.js @@ -53,9 +53,10 @@ const persistedStateOptions = { 'users.lastLoginName', 'oauth' ] -} +}; -createPersistedState(persistedStateOptions).then((persistedState) => { +(async () => { + const persistedState = await createPersistedState(persistedStateOptions) const store = new Vuex.Store({ modules: { interface: interfaceModule, @@ -75,7 +76,7 @@ createPersistedState(persistedStateOptions).then((persistedState) => { }) afterStoreSetup({ store, i18n }) -}) +})() // These are inlined by webpack's DefinePlugin /* eslint-disable */ From c030e622544346f6359e4df86bf20e503b0f2c3a Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 13 Mar 2019 11:57:30 +0100 Subject: [PATCH 23/45] afterStoreSetup: refactor. --- src/boot/after_store.js | 240 +++++++++++++++++++++------------------- 1 file changed, 128 insertions(+), 112 deletions(-) diff --git a/src/boot/after_store.js b/src/boot/after_store.js index cd88c188..d9706960 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -4,119 +4,137 @@ import routes from './routes' import App from '../App.vue' -const afterStoreSetup = ({ store, i18n }) => { - window.fetch('/api/statusnet/config.json') - .then((res) => res.json()) - .then((data) => { - const { name, closed: registrationClosed, textlimit, uploadlimit, server, vapidPublicKey } = data.site +const getStatusnetConfig = async ({ store }) => { + try { + const res = await window.fetch('/api/statusnet/config.json') + const data = await res.json() + const { name, closed: registrationClosed, textlimit, uploadlimit, server, vapidPublicKey } = data.site - store.dispatch('setInstanceOption', { name: 'name', value: name }) - store.dispatch('setInstanceOption', { name: 'registrationOpen', value: (registrationClosed === '0') }) - store.dispatch('setInstanceOption', { name: 'textlimit', value: parseInt(textlimit) }) - store.dispatch('setInstanceOption', { name: 'server', value: server }) + store.dispatch('setInstanceOption', { name: 'name', value: name }) + store.dispatch('setInstanceOption', { name: 'registrationOpen', value: (registrationClosed === '0') }) + store.dispatch('setInstanceOption', { name: 'textlimit', value: parseInt(textlimit) }) + store.dispatch('setInstanceOption', { name: 'server', value: server }) - // TODO: default values for this stuff, added if to not make it break on - // my dev config out of the box. - if (uploadlimit) { - store.dispatch('setInstanceOption', { name: 'uploadlimit', value: parseInt(uploadlimit.uploadlimit) }) - store.dispatch('setInstanceOption', { name: 'avatarlimit', value: parseInt(uploadlimit.avatarlimit) }) - store.dispatch('setInstanceOption', { name: 'backgroundlimit', value: parseInt(uploadlimit.backgroundlimit) }) - store.dispatch('setInstanceOption', { name: 'bannerlimit', value: parseInt(uploadlimit.bannerlimit) }) + // TODO: default values for this stuff, added if to not make it break on + // my dev config out of the box. + if (uploadlimit) { + store.dispatch('setInstanceOption', { name: 'uploadlimit', value: parseInt(uploadlimit.uploadlimit) }) + store.dispatch('setInstanceOption', { name: 'avatarlimit', value: parseInt(uploadlimit.avatarlimit) }) + store.dispatch('setInstanceOption', { name: 'backgroundlimit', value: parseInt(uploadlimit.backgroundlimit) }) + store.dispatch('setInstanceOption', { name: 'bannerlimit', value: parseInt(uploadlimit.bannerlimit) }) + } + + if (vapidPublicKey) { + store.dispatch('setInstanceOption', { name: 'vapidPublicKey', value: vapidPublicKey }) + } + + return data.site.pleromafe + } catch (error) { + console.error('Could not load statusnet config, potentially fatal') + console.error(error) + } +} + +const getStaticConfig = async () => { + try { + const res = await window.fetch('/static/config.json') + return res.json() + } catch (error) { + console.warn('Failed to load static/config.json, continuing without it.') + console.warn(error) + return {} + } +} + +const setSettings = async ({ apiConfig, staticConfig, store }) => { + const overrides = window.___pleromafe_dev_overrides || {} + const env = window.___pleromafe_mode.NODE_ENV + + // This takes static config and overrides properties that are present in apiConfig + let config = {} + if (overrides.staticConfigPreference && env === 'development') { + console.warn('OVERRIDING API CONFIG WITH STATIC CONFIG') + config = Object.assign({}, apiConfig, staticConfig) + } else { + config = Object.assign({}, staticConfig, apiConfig) + } + + const copyInstanceOption = (name) => { + store.dispatch('setInstanceOption', { name, value: config[name] }) + } + + copyInstanceOption('nsfwCensorImage') + copyInstanceOption('background') + copyInstanceOption('hidePostStats') + copyInstanceOption('hideUserStats') + copyInstanceOption('hideFilteredStatuses') + copyInstanceOption('logo') + + store.dispatch('setInstanceOption', { + name: 'logoMask', + value: typeof config.logoMask === 'undefined' + ? true + : config.logoMask + }) + + store.dispatch('setInstanceOption', { + name: 'logoMargin', + value: typeof config.logoMargin === 'undefined' + ? 0 + : config.logoMargin + }) + + copyInstanceOption('redirectRootNoLogin') + copyInstanceOption('redirectRootLogin') + copyInstanceOption('showInstanceSpecificPanel') + copyInstanceOption('scopeOptionsEnabled') + copyInstanceOption('formattingOptionsEnabled') + copyInstanceOption('collapseMessageWithSubject') + copyInstanceOption('loginMethod') + copyInstanceOption('scopeCopy') + copyInstanceOption('subjectLineBehavior') + copyInstanceOption('postContentType') + copyInstanceOption('alwaysShowSubjectInput') + copyInstanceOption('noAttachmentLinks') + copyInstanceOption('showFeaturesPanel') + + if ((config.chatDisabled)) { + store.dispatch('disableChat') + } else { + store.dispatch('initializeSocket') + } + + return store.dispatch('setTheme', config['theme']) +} + +const afterStoreSetup = async ({ store, i18n }) => { + const apiConfig = await getStatusnetConfig({ store }) + const staticConfig = await getStaticConfig() + await setSettings({ store, apiConfig, staticConfig }) + // Now we have the server settings and can try logging in + if (store.state.oauth.token) { + store.dispatch('loginUser', store.state.oauth.token) + } + + const router = new VueRouter({ + mode: 'history', + routes: routes(store), + scrollBehavior: (to, _from, savedPosition) => { + if (to.matched.some(m => m.meta.dontScroll)) { + return false } + return savedPosition || { x: 0, y: 0 } + } + }) - if (vapidPublicKey) { - store.dispatch('setInstanceOption', { name: 'vapidPublicKey', value: vapidPublicKey }) - } - - var apiConfig = data.site.pleromafe - - window.fetch('/static/config.json') - .then((res) => res.json()) - .catch((err) => { - console.warn('Failed to load static/config.json, continuing without it.') - console.warn(err) - return {} - }) - .then((staticConfig) => { - const overrides = window.___pleromafe_dev_overrides || {} - const env = window.___pleromafe_mode.NODE_ENV - - // This takes static config and overrides properties that are present in apiConfig - let config = {} - if (overrides.staticConfigPreference && env === 'development') { - console.warn('OVERRIDING API CONFIG WITH STATIC CONFIG') - config = Object.assign({}, apiConfig, staticConfig) - } else { - config = Object.assign({}, staticConfig, apiConfig) - } - - const copyInstanceOption = (name) => { - store.dispatch('setInstanceOption', {name, value: config[name]}) - } - - copyInstanceOption('nsfwCensorImage') - copyInstanceOption('background') - copyInstanceOption('hidePostStats') - copyInstanceOption('hideUserStats') - copyInstanceOption('hideFilteredStatuses') - copyInstanceOption('logo') - - store.dispatch('setInstanceOption', { - name: 'logoMask', - value: typeof config.logoMask === 'undefined' - ? true - : config.logoMask - }) - - store.dispatch('setInstanceOption', { - name: 'logoMargin', - value: typeof config.logoMargin === 'undefined' - ? 0 - : config.logoMargin - }) - - copyInstanceOption('redirectRootNoLogin') - copyInstanceOption('redirectRootLogin') - copyInstanceOption('showInstanceSpecificPanel') - copyInstanceOption('scopeOptionsEnabled') - copyInstanceOption('formattingOptionsEnabled') - copyInstanceOption('collapseMessageWithSubject') - copyInstanceOption('loginMethod') - copyInstanceOption('scopeCopy') - copyInstanceOption('subjectLineBehavior') - copyInstanceOption('postContentType') - copyInstanceOption('alwaysShowSubjectInput') - copyInstanceOption('noAttachmentLinks') - copyInstanceOption('showFeaturesPanel') - - if (config.chatDisabled) { - store.dispatch('disableChat') - } - - return store.dispatch('setTheme', config['theme']) - }) - .then(() => { - const router = new VueRouter({ - mode: 'history', - routes: routes(store), - scrollBehavior: (to, _from, savedPosition) => { - if (to.matched.some(m => m.meta.dontScroll)) { - return false - } - return savedPosition || { x: 0, y: 0 } - } - }) - - /* eslint-disable no-new */ - new Vue({ - router, - store, - i18n, - el: '#app', - render: h => h(App) - }) - }) - }) + /* eslint-disable no-new */ + new Vue({ + router, + store, + i18n, + el: '#app', + render: h => h(App) + }) window.fetch('/static/terms-of-service.html') .then((res) => res.text()) @@ -157,7 +175,7 @@ const afterStoreSetup = ({ store, i18n }) => { store.dispatch('setInstanceOption', { name: 'instanceSpecificPanelContent', value: html }) }) - window.fetch('/nodeinfo/2.0.json') + return window.fetch('/nodeinfo/2.0.json') .then((res) => res.json()) .then((data) => { const metadata = data.metadata @@ -167,8 +185,6 @@ const afterStoreSetup = ({ store, i18n }) => { store.dispatch('setInstanceOption', { name: 'chatAvailable', value: features.includes('chat') }) store.dispatch('setInstanceOption', { name: 'gopherAvailable', value: features.includes('gopher') }) - store.dispatch('setInstanceOption', { name: 'postFormats', value: metadata.postFormats }) - store.dispatch('setInstanceOption', { name: 'restrictedNicknames', value: metadata.restrictedNicknames }) const suggestions = metadata.suggestions From f535ccd92583819f48d32c381b234ab26508666d Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 13 Mar 2019 12:10:57 +0100 Subject: [PATCH 24/45] afterStoreSetup: refactor TOS and panel fetching, handle 404s. --- src/boot/after_store.js | 65 +++++++++++++++++++++++++++-------------- 1 file changed, 43 insertions(+), 22 deletions(-) diff --git a/src/boot/after_store.js b/src/boot/after_store.js index d9706960..c1073012 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -107,10 +107,43 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => { return store.dispatch('setTheme', config['theme']) } +const getTOS = async ({ store }) => { + try { + const res = await window.fetch('/static/terms-of-service.html') + if (res.ok) { + const html = await res.text() + store.dispatch('setInstanceOption', { name: 'tos', value: html }) + } else { + throw (res) + } + } catch (e) { + console.warn("Can't load TOS") + console.warn(e) + } +} + +const getInstancePanel = async ({ store }) => { + try { + const res = await window.fetch('/instance/panel.html') + if (res.ok) { + const html = await res.text() + store.dispatch('setInstanceOption', { name: 'instanceSpecificPanelContent', value: html }) + } else { + throw (res) + } + } catch (e) { + console.warn("Can't load instance panel") + console.log(e) + } +} + const afterStoreSetup = async ({ store, i18n }) => { const apiConfig = await getStatusnetConfig({ store }) const staticConfig = await getStaticConfig() await setSettings({ store, apiConfig, staticConfig }) + await getTOS({ store }) + await getInstancePanel({ store }) + // Now we have the server settings and can try logging in if (store.state.oauth.token) { store.dispatch('loginUser', store.state.oauth.token) @@ -127,21 +160,6 @@ const afterStoreSetup = async ({ store, i18n }) => { } }) - /* eslint-disable no-new */ - new Vue({ - router, - store, - i18n, - el: '#app', - render: h => h(App) - }) - - window.fetch('/static/terms-of-service.html') - .then((res) => res.text()) - .then((html) => { - store.dispatch('setInstanceOption', { name: 'tos', value: html }) - }) - window.fetch('/api/pleroma/emoji.json') .then( (res) => res.json() @@ -169,13 +187,7 @@ const afterStoreSetup = async ({ store, i18n }) => { store.dispatch('setInstanceOption', { name: 'emoji', value: emoji }) }) - window.fetch('/instance/panel.html') - .then((res) => res.text()) - .then((html) => { - store.dispatch('setInstanceOption', { name: 'instanceSpecificPanelContent', value: html }) - }) - - return window.fetch('/nodeinfo/2.0.json') + window.fetch('/nodeinfo/2.0.json') .then((res) => res.json()) .then((data) => { const metadata = data.metadata @@ -191,6 +203,15 @@ const afterStoreSetup = async ({ store, i18n }) => { store.dispatch('setInstanceOption', { name: 'suggestionsEnabled', value: suggestions.enabled }) store.dispatch('setInstanceOption', { name: 'suggestionsWeb', value: suggestions.web }) }) + + /* eslint-disable no-new */ + return new Vue({ + router, + store, + i18n, + el: '#app', + render: h => h(App) + }) } export default afterStoreSetup From 446785ddce5f78af7087a47d82f45633fc2f7da5 Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 13 Mar 2019 12:26:40 +0100 Subject: [PATCH 25/45] afterStoreSetup: Emoji and nodeinfo refactor. --- src/boot/after_store.js | 115 ++++++++++++++++++++++++---------------- 1 file changed, 70 insertions(+), 45 deletions(-) diff --git a/src/boot/after_store.js b/src/boot/after_store.js index c1073012..f9e14eb2 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -133,7 +133,73 @@ const getInstancePanel = async ({ store }) => { } } catch (e) { console.warn("Can't load instance panel") - console.log(e) + console.warn(e) + } +} + +const getStaticEmoji = async ({ store }) => { + try { + const res = await window.fetch('/static/emoji.json') + if (res.ok) { + const values = await res.json() + const emoji = Object.keys(values).map((key) => { + return { shortcode: key, image_url: false, 'utf': values[key] } + }) + store.dispatch('setInstanceOption', { name: 'emoji', value: emoji }) + } else { + throw (res) + } + } catch (e) { + console.warn("Can't load static emoji") + console.warn(e) + } +} + +// This is also used to indicate if we have a 'pleroma backend' or not. +// Somewhat weird, should probably be somewhere else. +const getCustomEmoji = async ({ store }) => { + try { + const res = await window.fetch('/api/pleroma/emoji.json') + if (res.ok) { + const values = await res.json() + const emoji = Object.keys(values).map((key) => { + return { shortcode: key, image_url: values[key] } + }) + store.dispatch('setInstanceOption', { name: 'customEmoji', value: emoji }) + store.dispatch('setInstanceOption', { name: 'pleromaBackend', value: true }) + } else { + throw (res) + } + } catch (e) { + store.dispatch('setInstanceOption', { name: 'pleromaBackend', value: false }) + console.warn("Can't load custom emojis, maybe not a Pleroma instance?") + console.warn(e) + } +} + +const getNodeInfo = async ({ store }) => { + try { + const res = await window.fetch('/nodeinfo/2.0.json') + if (res.ok) { + const data = await res.json() + const metadata = data.metadata + + const features = metadata.features + store.dispatch('setInstanceOption', { name: 'mediaProxyAvailable', value: features.includes('media_proxy') }) + store.dispatch('setInstanceOption', { name: 'chatAvailable', value: features.includes('chat') }) + store.dispatch('setInstanceOption', { name: 'gopherAvailable', value: features.includes('gopher') }) + + store.dispatch('setInstanceOption', { name: 'restrictedNicknames', value: metadata.restrictedNicknames }) + + const suggestions = metadata.suggestions + store.dispatch('setInstanceOption', { name: 'suggestionsEnabled', value: suggestions.enabled }) + store.dispatch('setInstanceOption', { name: 'suggestionsWeb', value: suggestions.web }) + } else { + throw (res) + } + } catch (e) { + console.warn('Could not load nodeinfo') + console.warn(e) } } @@ -143,6 +209,9 @@ const afterStoreSetup = async ({ store, i18n }) => { await setSettings({ store, apiConfig, staticConfig }) await getTOS({ store }) await getInstancePanel({ store }) + await getStaticEmoji({ store }) + await getCustomEmoji({ store }) + await getNodeInfo({ store }) // Now we have the server settings and can try logging in if (store.state.oauth.token) { @@ -160,50 +229,6 @@ const afterStoreSetup = async ({ store, i18n }) => { } }) - window.fetch('/api/pleroma/emoji.json') - .then( - (res) => res.json() - .then( - (values) => { - const emoji = Object.keys(values).map((key) => { - return { shortcode: key, image_url: values[key] } - }) - store.dispatch('setInstanceOption', { name: 'customEmoji', value: emoji }) - store.dispatch('setInstanceOption', { name: 'pleromaBackend', value: true }) - }, - (failure) => { - store.dispatch('setInstanceOption', { name: 'pleromaBackend', value: false }) - } - ), - (error) => console.log(error) - ) - - window.fetch('/static/emoji.json') - .then((res) => res.json()) - .then((values) => { - const emoji = Object.keys(values).map((key) => { - return { shortcode: key, image_url: false, 'utf': values[key] } - }) - store.dispatch('setInstanceOption', { name: 'emoji', value: emoji }) - }) - - window.fetch('/nodeinfo/2.0.json') - .then((res) => res.json()) - .then((data) => { - const metadata = data.metadata - - const features = metadata.features - store.dispatch('setInstanceOption', { name: 'mediaProxyAvailable', value: features.includes('media_proxy') }) - store.dispatch('setInstanceOption', { name: 'chatAvailable', value: features.includes('chat') }) - store.dispatch('setInstanceOption', { name: 'gopherAvailable', value: features.includes('gopher') }) - - store.dispatch('setInstanceOption', { name: 'restrictedNicknames', value: metadata.restrictedNicknames }) - - const suggestions = metadata.suggestions - store.dispatch('setInstanceOption', { name: 'suggestionsEnabled', value: suggestions.enabled }) - store.dispatch('setInstanceOption', { name: 'suggestionsWeb', value: suggestions.web }) - }) - /* eslint-disable no-new */ return new Vue({ router, From 48ac96cfc7c9c3328d7a18707f00a2fe1bc743a1 Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 13 Mar 2019 12:41:39 +0100 Subject: [PATCH 26/45] afterStoreSetup: Handle 404 cases. --- src/boot/after_store.js | 48 ++++++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/src/boot/after_store.js b/src/boot/after_store.js index f9e14eb2..a51f895e 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -7,28 +7,32 @@ import App from '../App.vue' const getStatusnetConfig = async ({ store }) => { try { const res = await window.fetch('/api/statusnet/config.json') - const data = await res.json() - const { name, closed: registrationClosed, textlimit, uploadlimit, server, vapidPublicKey } = data.site + if (res.ok) { + const data = await res.json() + const { name, closed: registrationClosed, textlimit, uploadlimit, server, vapidPublicKey } = data.site - store.dispatch('setInstanceOption', { name: 'name', value: name }) - store.dispatch('setInstanceOption', { name: 'registrationOpen', value: (registrationClosed === '0') }) - store.dispatch('setInstanceOption', { name: 'textlimit', value: parseInt(textlimit) }) - store.dispatch('setInstanceOption', { name: 'server', value: server }) + store.dispatch('setInstanceOption', { name: 'name', value: name }) + store.dispatch('setInstanceOption', { name: 'registrationOpen', value: (registrationClosed === '0') }) + store.dispatch('setInstanceOption', { name: 'textlimit', value: parseInt(textlimit) }) + store.dispatch('setInstanceOption', { name: 'server', value: server }) - // TODO: default values for this stuff, added if to not make it break on - // my dev config out of the box. - if (uploadlimit) { - store.dispatch('setInstanceOption', { name: 'uploadlimit', value: parseInt(uploadlimit.uploadlimit) }) - store.dispatch('setInstanceOption', { name: 'avatarlimit', value: parseInt(uploadlimit.avatarlimit) }) - store.dispatch('setInstanceOption', { name: 'backgroundlimit', value: parseInt(uploadlimit.backgroundlimit) }) - store.dispatch('setInstanceOption', { name: 'bannerlimit', value: parseInt(uploadlimit.bannerlimit) }) + // TODO: default values for this stuff, added if to not make it break on + // my dev config out of the box. + if (uploadlimit) { + store.dispatch('setInstanceOption', { name: 'uploadlimit', value: parseInt(uploadlimit.uploadlimit) }) + store.dispatch('setInstanceOption', { name: 'avatarlimit', value: parseInt(uploadlimit.avatarlimit) }) + store.dispatch('setInstanceOption', { name: 'backgroundlimit', value: parseInt(uploadlimit.backgroundlimit) }) + store.dispatch('setInstanceOption', { name: 'bannerlimit', value: parseInt(uploadlimit.bannerlimit) }) + } + + if (vapidPublicKey) { + store.dispatch('setInstanceOption', { name: 'vapidPublicKey', value: vapidPublicKey }) + } + + return data.site.pleromafe + } else { + throw (res) } - - if (vapidPublicKey) { - store.dispatch('setInstanceOption', { name: 'vapidPublicKey', value: vapidPublicKey }) - } - - return data.site.pleromafe } catch (error) { console.error('Could not load statusnet config, potentially fatal') console.error(error) @@ -38,7 +42,11 @@ const getStatusnetConfig = async ({ store }) => { const getStaticConfig = async () => { try { const res = await window.fetch('/static/config.json') - return res.json() + if (res.ok) { + return res.json() + } else { + throw (res) + } } catch (error) { console.warn('Failed to load static/config.json, continuing without it.') console.warn(error) From 5318227c378a9cd44baa4cc12dbb1935826c843b Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 13 Mar 2019 13:29:34 +0100 Subject: [PATCH 27/45] afterStoreSetup: Move log in and theme load to afterStoreSetup. --- src/boot/after_store.js | 10 ++++++++++ src/lib/persisted_state.js | 12 ------------ 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/boot/after_store.js b/src/boot/after_store.js index a51f895e..3d400ffa 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -212,6 +212,16 @@ const getNodeInfo = async ({ store }) => { } const afterStoreSetup = async ({ store, i18n }) => { + if (store.state.config.customTheme) { + // This is a hack to deal with async loading of config.json and themes + // See: style_setter.js, setPreset() + window.themeLoaded = true + store.dispatch('setOption', { + name: 'customTheme', + value: store.state.config.customTheme + }) + } + const apiConfig = await getStatusnetConfig({ store }) const staticConfig = await getStaticConfig() await setSettings({ store, apiConfig, staticConfig }) diff --git a/src/lib/persisted_state.js b/src/lib/persisted_state.js index e828a74b..7ab89c12 100644 --- a/src/lib/persisted_state.js +++ b/src/lib/persisted_state.js @@ -60,18 +60,6 @@ export default function createPersistedState ({ merge({}, store.state, savedState) ) } - if (store.state.config.customTheme) { - // This is a hack to deal with async loading of config.json and themes - // See: style_setter.js, setPreset() - window.themeLoaded = true - store.dispatch('setOption', { - name: 'customTheme', - value: store.state.config.customTheme - }) - } - if (store.state.oauth.token) { - store.dispatch('loginUser', store.state.oauth.token) - } loaded = true } catch (e) { console.log("Couldn't load state") From 885a3a77df84c21026b7d13ff368c3bebcf70561 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Thu, 14 Mar 2019 18:50:51 +0200 Subject: [PATCH 28/45] fix console error --- src/modules/users.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/modules/users.js b/src/modules/users.js index 27114684..2d23955b 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -94,11 +94,12 @@ export const mutations = { updateUserRelationship (state, relationships) { relationships.forEach((relationship) => { const user = state.usersObject[relationship.id] - - user.follows_you = relationship.followed_by - user.following = relationship.following - user.muted = relationship.muting - user.statusnet_blocking = relationship.blocking + if (user) { + user.follows_you = relationship.followed_by + user.following = relationship.following + user.muted = relationship.muting + user.statusnet_blocking = relationship.blocking + } }) }, saveBlocks (state, blockIds) { From 6420c93e983040807ea58277e5780038a656ef4d Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Thu, 14 Mar 2019 23:04:13 +0200 Subject: [PATCH 29/45] fix flake id users not fetching correctly --- src/components/user_profile/user_profile.js | 3 ++- src/modules/users.js | 9 ++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/components/user_profile/user_profile.js b/src/components/user_profile/user_profile.js index 1bf4a86d..82df4510 100644 --- a/src/components/user_profile/user_profile.js +++ b/src/components/user_profile/user_profile.js @@ -68,7 +68,8 @@ const UserProfile = { }, userInStore () { const routeParams = this.$route.params - return this.$store.getters.findUser(routeParams.name || routeParams.id) + // This needs fetchedUserId so that computed will be refreshed when user is fetched + return this.$store.getters.findUser(this.fetchedUserId || routeParams.name || routeParams.id) }, user () { if (this.timeline.statuses[0]) { diff --git a/src/modules/users.js b/src/modules/users.js index 2d23955b..fafe1a60 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -133,7 +133,14 @@ export const mutations = { } export const getters = { - findUser: state => query => state.usersObject[typeof query === 'string' ? query.toLowerCase() : query] + findUser: state => query => { + const result = state.usersObject[query] + // In case it's a screen_name, we can try searching case-insensitive + if (!result && typeof query === 'string') { + return state.usersObject[query.toLowerCase()] + } + return result + } } export const defaultState = { From 71c12fa3a58ff2b498ed5ba9d3fc3d58d016bd97 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Thu, 14 Mar 2019 23:04:30 +0200 Subject: [PATCH 30/45] fix user-card avatar falling into permament failed state --- src/components/user_avatar/user_avatar.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/components/user_avatar/user_avatar.js b/src/components/user_avatar/user_avatar.js index e513b993..e6fed3b5 100644 --- a/src/components/user_avatar/user_avatar.js +++ b/src/components/user_avatar/user_avatar.js @@ -23,6 +23,11 @@ const UserAvatar = { imageLoadError () { this.showPlaceholder = true } + }, + watch: { + src () { + this.showPlaceholder = false + } } } From f93d4a37546f12f4850fbe4a296dfa0a92813ddd Mon Sep 17 00:00:00 2001 From: William Pitcock Date: Thu, 14 Mar 2019 21:35:45 +0000 Subject: [PATCH 31/45] after store: fix setting postFormats field --- src/boot/after_store.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/boot/after_store.js b/src/boot/after_store.js index a51f895e..6e5e1d8b 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -198,6 +198,7 @@ const getNodeInfo = async ({ store }) => { store.dispatch('setInstanceOption', { name: 'gopherAvailable', value: features.includes('gopher') }) store.dispatch('setInstanceOption', { name: 'restrictedNicknames', value: metadata.restrictedNicknames }) + store.dispatch('setInstanceOption', { name: 'postFormats', value: metadata.postFormats }) const suggestions = metadata.suggestions store.dispatch('setInstanceOption', { name: 'suggestionsEnabled', value: suggestions.enabled }) From 6e60873a3d9c709689eb02c05460b25a73ea0718 Mon Sep 17 00:00:00 2001 From: taehoon Date: Tue, 12 Mar 2019 14:18:20 -0400 Subject: [PATCH 32/45] Switch to mastoapi for user search --- src/services/new_api/user_search.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/services/new_api/user_search.js b/src/services/new_api/user_search.js index ce7da88e..869afa9c 100644 --- a/src/services/new_api/user_search.js +++ b/src/services/new_api/user_search.js @@ -1,13 +1,16 @@ import utils from './utils.js' +import { parseUser } from '../entity_normalizer/entity_normalizer.service.js' const search = ({query, store}) => { return utils.request({ store, - url: '/api/pleroma/search_user', + url: '/api/v1/accounts/search', params: { - query + q: query } - }).then((data) => data.json()) + }) + .then((data) => data.json()) + .then((data) => data.map(parseUser)) } const UserSearch = { search From 7babbce5221080a00df42cefc3e2bb51373d38f9 Mon Sep 17 00:00:00 2001 From: Exilat Date: Fri, 15 Mar 2019 17:23:30 +0000 Subject: [PATCH 33/45] [i18n] Update oc.json --- src/i18n/oc.json | 147 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 131 insertions(+), 16 deletions(-) diff --git a/src/i18n/oc.json b/src/i18n/oc.json index baac3d25..ecc4df61 100644 --- a/src/i18n/oc.json +++ b/src/i18n/oc.json @@ -59,19 +59,21 @@ "broken_favorite": "Estatut desconegut, sèm a lo cercar...", "favorited_you": "a aimat vòstre estatut", "followed_you": "vos a seguit", - "load_older": "Cargar las notificaciones mai ancianas", + "load_older": "Cargar las notificacions mai ancianas", "notifications": "Notficacions", - "read": "Legit !", + "read": "Legit !", "repeated_you": "a repetit vòstre estatut", "no_more_notifications": "Pas mai de notificacions" }, "post_status": { "new_status": "Publicar d’estatuts novèls", - "account_not_locked_warning": "Vòstre compte es pas {0}. Qual que siá pòt vos seguir per veire vòstras publicacions destinadas pas qu'a vòstres seguidors.", + "account_not_locked_warning": "Vòstre compte es pas {0}. Qual que siá pòt vos seguir per veire vòstras publicacions destinadas pas qu’a vòstres seguidors.", "account_not_locked_warning_link": "clavat", "attachments_sensitive": "Marcar las pèças juntas coma sensiblas", "content_type": { - "text/plain": "Tèxte brut" + "text/plain": "Tèxte brut", + "text/html": "HTML", + "text/markdown": "Markdown" }, "content_warning": "Avís de contengut (opcional)", "default": "Escrivètz aquí vòstre estatut.", @@ -118,12 +120,12 @@ "blocks_tab": "Blocatges", "btnRadius": "Botons", "cBlue": "Blau (Respondre, seguir)", - "cGreen": "Verd (Repartajar)", + "cGreen": "Verd (Repertir)", "cOrange": "Irange (Aimar)", "cRed": "Roge (Anullar)", "change_password": "Cambiar lo senhal", "change_password_error": "Una error s’es producha en cambiant lo senhal.", - "changed_password": "Senhal corrèctament cambiat !", + "changed_password": "Senhal corrèctament cambiat !", "collapse_subject": "Replegar las publicacions amb de subjèctes", "composing": "Escritura", "confirm_new_password": "Confirmatz lo nòu senhal", @@ -134,7 +136,7 @@ "default_vis": "Nivèl de visibilitat per defaut", "delete_account": "Suprimir lo compte", "delete_account_description": "Suprimir vòstre compte e los messatges per sempre.", - "delete_account_error": "Una error s’es producha en suprimir lo compte. S’aquò ten d’arribar mercés de contactar vòstre administrador d’instància.", + "delete_account_error": "Una error s’es producha en suprimir lo compte. S’aquò ten d’arribar mercés de contactar vòstre administrator d’instància.", "delete_account_instructions": "Picatz vòstre senhal dins lo camp tèxte çai-jos per confirmar la supression del compte.", "avatar_size_instruction": "La talha minimum recomandada pels imatges d’avatar es 150x150 pixèls.", "export_theme": "Enregistrar la preconfiguracion", @@ -154,14 +156,14 @@ "hide_isp": "Amagar lo panèl especial instància", "preload_images": "Precargar los imatges", "use_one_click_nsfw": "Dobrir las pèças juntas NSFW amb un clic", - "hide_post_stats": "Amagar los estatistics de publicacion (ex. lo ombre de favorits)", + "hide_post_stats": "Amagar las estatisticas de publicacion (ex. lo nombre de favorits)", "hide_user_stats": "Amagar las estatisticas de l’utilizaire (ex. lo nombre de seguidors)", "hide_filtered_statuses": "Amagar los estatuts filtrats", "import_followers_from_a_csv_file": "Importar los seguidors d’un fichièr csv", "import_theme": "Cargar un tèma", "inputRadius": "Camps tèxte", "checkboxRadius": "Casas de marcar", - "instance_default": "(defaut : {value})", + "instance_default": "(defaut : {value})", "instance_default_simple": "(defaut)", "interface": "Interfàcia", "interfaceLanguage": "Lenga de l’interfàcia", @@ -172,7 +174,7 @@ "loop_video": "Bocla vidèo", "loop_video_silent_only": "Legir en bocla solament las vidèos sens son (coma los « Gifs » de Mastodon)", "mutes_tab": "Agamats", - "play_videos_in_modal": "Legir las vidèoas dirèctament dins la visualizaira mèdia", + "play_videos_in_modal": "Legir las vidèos dirèctament dins la visualizaira mèdia", "use_contain_fit": "Talhar pas las pèças juntas per las vinhetas", "name": "Nom", "name_bio": "Nom & Bio", @@ -223,7 +225,7 @@ "post_status_content_type": "Publicar lo tipe de contengut dels estatuts", "stop_gifs": "Lançar los GIFs al subrevòl", "streaming": "Activar lo cargament automatic dels novèls estatus en anar amont", - "text": "Tèxt", + "text": "Tèxte", "theme": "Tèma", "theme_help_v2_1": "You can also override certain component's colors and opacity by toggling the checkbox, use \"Clear all\" button to clear all overrides.", "theme_help_v2_2": "Icons underneath some entries are background/text contrast indicators, hover over for detailed info. Please keep in mind that when using transparency contrast indicators show the worst possible case.", @@ -234,6 +236,117 @@ "values": { "false": "non", "true": "òc" + }, + "notifications": "Notificacions", + "enable_web_push_notifications": "Activar las notificacions web push", + "style": { + "switcher": { + "keep_color": "Gardar las colors", + "keep_shadows": "Gardar las ombras", + "keep_opacity": "Gardar l’opacitat", + "keep_roundness": "Gardar la redondetat", + "keep_fonts": "Gardar las polissas", + "save_load_hint": "Las opcions « Gardar » permeton de servar las opcions configuradas actualament quand seleccionatz o cargatz un tèma, permeton tanben d’enregistrar aquelas opcions quand exportatz un tèma. Quand totas las casas son pas marcadas, l’exportacion de tèma o enregistrarà tot.", + "reset": "Restablir", + "clear_all": "O escafar tot", + "clear_opacity": "Escafar l’opacitat" + }, + "common": { + "color": "Color", + "opacity": "Opacitat", + "contrast": { + "hint": "Lo coeficient de contraste es de {ratio}. Dòna {level} {context}", + "level": { + "aa": "un nivèl AA minimum recomandat", + "aaa": "un nivèl AAA recomandat", + "bad": "pas un nivèl d’accessibilitat recomandat" + }, + "context": { + "18pt": "pel tèxte grand (18pt+)", + "text": "pel tèxte" + } + } + }, + "common_colors": { + "_tab_label": "Comun", + "main": "Colors comunas", + "foreground_hint": "Vejatz « Avançat » per mai de paramètres detalhats", + "rgbo": "Icònas, accents, badges" + }, + "advanced_colors": { + "_tab_label": "Avançat", + "alert": "Rèire plan d’alèrtas", + "alert_error": "Error", + "badge": "Rèire plan dels badges", + "badge_notification": "Notificacion", + "panel_header": "Bandièra del tablèu de bòrd", + "top_bar": "Barra amont", + "borders": "Caires", + "buttons": "Botons", + "inputs": "Camps tèxte", + "faint_text": "Tèxte descolorit" + }, + "radii": { + "_tab_label": "Redondetat" + }, + "shadows": { + "_tab_label": "Ombra e luminositat", + "component": "Compausant", + "override": "Subrecargar", + "shadow_id": "Ombra #{value}", + "blur": "Fosc", + "spread": "Espandiment", + "inset": "Incrustacion", + "hint": "Per las ombras podètz tanben utilizar --variable coma valor de color per emplegar una variable CSS3. Notatz que lo paramètre d’opacitat foncionarà pas dins aquel cas.", + "filter_hint": { + "always_drop_shadow": "Avertiment, aquel ombra utiliza totjorn {0} quand lo navigator es compatible.", + "drop_shadow_syntax": "{0} es pas compatible amb lo paramètre {1} e lo mot clau {2}.", + "avatar_inset": "Notatz que combinar d’ombras incrustadas e pas incrustadas pòt donar de resultats inesperats amb los avatars transparents.", + "spread_zero": "L’ombra amb un espandiment de > 0 apareisserà coma reglat a zèro", + "inset_classic": "L’ombra d’incrustacion utilizarà {0}" + }, + "components": { + "panel": "Tablèu", + "panelHeader": "Bandièra del tablèu", + "topBar": "Barra amont", + "avatar": "Utilizar l’avatar (vista perfil)", + "avatarStatus": "Avatar de l’utilizaire (afichatge publicacion)", + "popup": "Fenèstras sorgissentas e astúcias", + "button": "Boton", + "buttonHover": "Boton (en passar la mirga)", + "buttonPressed": "Boton (en quichar)", + "buttonPressedHover": "Boton (en quichar e passar)", + "input": "Camp tèxte" + } + }, + "fonts": { + "_tab_label": "Polissas", + "help": "Selecionatz la polissa d’utilizar pels elements de l’UI. Per « Personalizada » vos cal picar lo nom exacte tal coma apareis sul sistèma.", + "components": { + "interface": "Interfàcia", + "input": "Camps tèxte", + "post": "Tèxte de publicacion", + "postCode": "Tèxte Monospaced dins las publicacion (tèxte formatat)" + }, + "family": "Nom de la polissa", + "size": "Talha (en px)", + "weight": "Largor (gras)", + "custom": "Personalizada" + }, + "preview": { + "header": "Apercebut", + "content": "Contengut", + "error": "Error d’exemple", + "button": "Boton", + "text": "A tròç de mai de {0} e {1}", + "mono": "contengut", + "input": "arribada al país.", + "faint_link": "manual d’ajuda", + "fine_print": "Legissètz nòstre {0} per legir pas res d’util !", + "header_faint": "Va plan", + "checkbox": "Ai legit los tèrmes e condicions d’utilizacion", + "link": "un pichon ligam simpatic" + } } }, "timeline": { @@ -241,19 +354,21 @@ "conversation": "Conversacion", "error_fetching": "Error en cercant de mesas a jorn", "load_older": "Ne veire mai", + "no_retweet_hint": "Las publicacions marcadas pels seguidors solament o dirèctas se pòdon pas repetir", "repeated": "repetit", "show_new": "Ne veire mai", "up_to_date": "A jorn", - "no_retweet_hint": "La publicacion marcada coma pels seguidors solament o dirècte pòt pas èsser repetida" + "no_more_statuses": "Pas mai d’estatuts", + "no_statuses": "Cap d’estatuts" }, "status": { - "reply_to": "Respondre à", + "reply_to": "Respond a", "replies_list": "Responsas :" }, "user_card": { "approve": "Validar", "block": "Blocar", - "blocked": "Blocat !", + "blocked": "Blocat !", "deny": "Refusar", "favorites": "Favorits", "follow": "Seguir", @@ -263,8 +378,8 @@ "follow_unfollow": "Quitar de seguir", "followees": "Abonaments", "followers": "Seguidors", - "following": "Seguit !", - "follows_you": "Vos sèc !", + "following": "Seguit !", + "follows_you": "Vos sèc !", "its_you": "Sètz vos !", "media": "Mèdia", "mute": "Amagar", From 4cdfd5fb8678fae7dbba8b00b2a468d3ce385e08 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Sun, 17 Mar 2019 14:41:05 +0200 Subject: [PATCH 34/45] post-merge fixes --- src/services/entity_normalizer/entity_normalizer.service.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js index 02130dcf..e831963a 100644 --- a/src/services/entity_normalizer/entity_normalizer.service.js +++ b/src/services/entity_normalizer/entity_normalizer.service.js @@ -40,10 +40,10 @@ export const parseUser = (data) => { } // output.name = ??? missing - output.name_html = data.display_name + output.name_html = addEmojis(data.display_name, data.emojis) // output.description = ??? missing - output.description_html = data.note + output.description_html = addEmojis(data.note, data.emojis) // Utilize avatar_static for gif avatars? output.profile_image_url = data.avatar From 3108722eda845cd3a8172b95e554b0dc8c8e444d Mon Sep 17 00:00:00 2001 From: jasper Date: Mon, 18 Mar 2019 18:19:42 -0700 Subject: [PATCH 35/45] Add button to save without cropping --- src/components/image_cropper/image_cropper.js | 18 ++++++++++++++++++ src/components/image_cropper/image_cropper.vue | 7 ++++++- src/components/user_settings/user_settings.js | 8 +++++++- src/i18n/en.json | 1 + 4 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/components/image_cropper/image_cropper.js b/src/components/image_cropper/image_cropper.js index 49d51846..5ba8f04e 100644 --- a/src/components/image_cropper/image_cropper.js +++ b/src/components/image_cropper/image_cropper.js @@ -31,6 +31,9 @@ const ImageCropper = { saveButtonLabel: { type: String }, + saveWithoutCroppingButtonlabel: { + type: String + }, cancelButtonLabel: { type: String } @@ -48,6 +51,9 @@ const ImageCropper = { saveText () { return this.saveButtonLabel || this.$t('image_cropper.save') }, + saveWithoutCroppingText () { + return this.saveWithoutCroppingButtonlabel || this.$t('image_cropper.save_without_cropping') + }, cancelText () { return this.cancelButtonLabel || this.$t('image_cropper.cancel') }, @@ -76,6 +82,18 @@ const ImageCropper = { this.submitting = false }) }, + submitWithoutCropping () { + this.submitting = true + this.avatarUploadError = null + this.submitHandler(false, this.dataUrl) + .then(() => this.destroy()) + .catch((err) => { + this.submitError = err + }) + .finally(() => { + this.submitting = false + }) + }, pickImage () { this.$refs.input.click() }, diff --git a/src/components/image_cropper/image_cropper.vue b/src/components/image_cropper/image_cropper.vue index 24a6f3bd..129e6f46 100644 --- a/src/components/image_cropper/image_cropper.vue +++ b/src/components/image_cropper/image_cropper.vue @@ -7,6 +7,7 @@
+
@@ -36,7 +37,11 @@ } &-buttons-wrapper { - margin-top: 15px; + margin-top: 10px; + + button { + margin-top: 5px; + } } } diff --git a/src/components/user_settings/user_settings.js b/src/components/user_settings/user_settings.js index c0ab759c..72e7bb53 100644 --- a/src/components/user_settings/user_settings.js +++ b/src/components/user_settings/user_settings.js @@ -158,7 +158,13 @@ const UserSettings = { reader.readAsDataURL(file) }, submitAvatar (cropper, file) { - const img = cropper.getCroppedCanvas().toDataURL(file.type) + let img + if (cropper) { + img = cropper.getCroppedCanvas().toDataURL(file.type) + } else { + img = file + } + return this.$store.state.api.backendInteractor.updateAvatar({ params: { img } }).then((user) => { if (!user.error) { this.$store.commit('addNewUsers', [user]) diff --git a/src/i18n/en.json b/src/i18n/en.json index 01fe2fba..6db9ef05 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -25,6 +25,7 @@ "image_cropper": { "crop_picture": "Crop picture", "save": "Save", + "save_without_cropping": "Save without cropping", "cancel": "Cancel" }, "login": { From 07b8115a37a22b2a7d935154e8231c8a90f199d6 Mon Sep 17 00:00:00 2001 From: dave Date: Tue, 19 Mar 2019 14:36:27 -0400 Subject: [PATCH 36/45] #444 - show `remote follow` button when logged out --- src/components/follow_card/follow_card.js | 7 ++++- src/components/follow_card/follow_card.vue | 9 ++++--- src/components/remote_follow/remote_follow.js | 10 +++++++ .../remote_follow/remote_follow.vue | 26 +++++++++++++++++++ src/components/user_card/user_card.js | 4 ++- src/components/user_card/user_card.vue | 15 ++--------- 6 files changed, 53 insertions(+), 18 deletions(-) create mode 100644 src/components/remote_follow/remote_follow.js create mode 100644 src/components/remote_follow/remote_follow.vue diff --git a/src/components/follow_card/follow_card.js b/src/components/follow_card/follow_card.js index 425c9c3e..ac4e265a 100644 --- a/src/components/follow_card/follow_card.js +++ b/src/components/follow_card/follow_card.js @@ -1,4 +1,5 @@ import BasicUserCard from '../basic_user_card/basic_user_card.vue' +import RemoteFollow from '../remote_follow/remote_follow.vue' import { requestFollow, requestUnfollow } from '../../services/follow_manipulate/follow_manipulate' const FollowCard = { @@ -14,13 +15,17 @@ const FollowCard = { } }, components: { - BasicUserCard + BasicUserCard, + RemoteFollow }, computed: { isMe () { return this.$store.state.users.currentUser.id === this.user.id }, following () { return this.updated ? this.updated.following : this.user.following }, showFollow () { return !this.following || this.updated && !this.updated.following + }, + loggedIn () { + return this.$store.state.users.currentUser } }, methods: { diff --git a/src/components/follow_card/follow_card.vue b/src/components/follow_card/follow_card.vue index 6cb064eb..9bd21cfd 100644 --- a/src/components/follow_card/follow_card.vue +++ b/src/components/follow_card/follow_card.vue @@ -4,9 +4,12 @@ {{ isMe ? $t('user_card.its_you') : $t('user_card.follows_you') }} +
+ +
+ +
+ + + + + diff --git a/src/components/user_card/user_card.js b/src/components/user_card/user_card.js index 43a77f45..b07da675 100644 --- a/src/components/user_card/user_card.js +++ b/src/components/user_card/user_card.js @@ -1,4 +1,5 @@ import UserAvatar from '../user_avatar/user_avatar.vue' +import RemoteFollow from '../remote_follow/remote_follow.vue' import { hex2rgb } from '../../services/color_convert/color_convert.js' import { requestFollow, requestUnfollow } from '../../services/follow_manipulate/follow_manipulate' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' @@ -99,7 +100,8 @@ export default { } }, components: { - UserAvatar + UserAvatar, + RemoteFollow }, methods: { followUser () { diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue index 690e1bde..f4114e6e 100644 --- a/src/components/user_card/user_card.vue +++ b/src/components/user_card/user_card.vue @@ -84,14 +84,8 @@ -
-
- - - -
+
+
@@ -375,11 +369,6 @@ min-height: 28px; } - .remote-follow { - max-width: 220px; - min-height: 28px; - } - .follow { max-width: 220px; min-height: 28px; From 96c88b334c2bf19ed1ffc418f0603d49c5a71652 Mon Sep 17 00:00:00 2001 From: dave Date: Tue, 19 Mar 2019 14:41:50 -0400 Subject: [PATCH 37/45] #444 - remote follow clean up --- src/components/remote_follow/remote_follow.vue | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/remote_follow/remote_follow.vue b/src/components/remote_follow/remote_follow.vue index db361e49..fb2147bd 100644 --- a/src/components/remote_follow/remote_follow.vue +++ b/src/components/remote_follow/remote_follow.vue @@ -13,8 +13,6 @@