diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/App.vue | 1 | ||||
| -rw-r--r-- | src/components/search_bar/search_bar.js | 5 | ||||
| -rw-r--r-- | src/components/timeline/timeline.js | 24 | ||||
| -rw-r--r-- | src/components/timeline/timeline.vue | 26 | ||||
| -rw-r--r-- | src/components/user_card/user_card.js | 10 | ||||
| -rw-r--r-- | src/components/user_card/user_card.vue | 48 | ||||
| -rw-r--r-- | src/components/user_profile/user_profile.vue | 16 | ||||
| -rw-r--r-- | src/i18n/es.json | 3 | ||||
| -rw-r--r-- | src/modules/users.js | 17 | ||||
| -rw-r--r-- | src/services/api/api.service.js | 9 | ||||
| -rw-r--r-- | src/services/entity_normalizer/entity_normalizer.service.js | 2 | ||||
| -rw-r--r-- | src/services/errors/errors.js | 35 |
12 files changed, 153 insertions, 43 deletions
diff --git a/src/App.vue b/src/App.vue index be4d1f75..bf6e62e2 100644 --- a/src/App.vue +++ b/src/App.vue @@ -41,6 +41,7 @@ <search-bar class="nav-icon mobile-hidden" @toggled="onSearchBarToggled" + @click.stop.native /> <router-link class="mobile-hidden" diff --git a/src/components/search_bar/search_bar.js b/src/components/search_bar/search_bar.js index b8a792ee..d7d85676 100644 --- a/src/components/search_bar/search_bar.js +++ b/src/components/search_bar/search_bar.js @@ -20,6 +20,11 @@ const SearchBar = { toggleHidden () { this.hidden = !this.hidden this.$emit('toggled', this.hidden) + this.$nextTick(() => { + if (!this.hidden) { + this.$refs.searchInput.focus() + } + }) } } } diff --git a/src/components/timeline/timeline.js b/src/components/timeline/timeline.js index 5e24bd15..aac3869f 100644 --- a/src/components/timeline/timeline.js +++ b/src/components/timeline/timeline.js @@ -1,7 +1,20 @@ import Status from '../status/status.vue' import timelineFetcher from '../../services/timeline_fetcher/timeline_fetcher.service.js' import Conversation from '../conversation/conversation.vue' -import { throttle } from 'lodash' +import { throttle, keyBy } from 'lodash' + +export const getExcludedStatusIdsByPinning = (statuses, pinnedStatusIds) => { + const ids = [] + if (pinnedStatusIds && pinnedStatusIds.length > 0) { + for (let status of statuses) { + if (!pinnedStatusIds.includes(status.id)) { + break + } + ids.push(status.id) + } + } + return ids +} const Timeline = { props: [ @@ -11,7 +24,8 @@ const Timeline = { 'userId', 'tag', 'embedded', - 'count' + 'count', + 'pinnedStatusIds' ], data () { return { @@ -39,6 +53,12 @@ const Timeline = { body: ['timeline-body'].concat(!this.embedded ? ['panel-body'] : []), footer: ['timeline-footer'].concat(!this.embedded ? ['panel-footer'] : []) } + }, + // id map of statuses which need to be hidden in the main list due to pinning logic + excludedStatusIdsObject () { + const ids = getExcludedStatusIdsByPinning(this.timeline.visibleStatuses, this.pinnedStatusIds) + // Convert id array to object + return keyBy(ids, id => id) } }, components: { diff --git a/src/components/timeline/timeline.vue b/src/components/timeline/timeline.vue index 1fc52083..0cb4b3ef 100644 --- a/src/components/timeline/timeline.vue +++ b/src/components/timeline/timeline.vue @@ -28,13 +28,25 @@ </div> <div :class="classes.body"> <div class="timeline"> - <conversation - v-for="status in timeline.visibleStatuses" - :key="status.id" - class="status-fadein" - :statusoid="status" - :collapsable="true" - /> + <template v-for="statusId in pinnedStatusIds"> + <conversation + v-if="timeline.statusesObject[statusId]" + :key="statusId + '-pinned'" + class="status-fadein" + :statusoid="timeline.statusesObject[statusId]" + :collapsable="true" + :show-pinned="true" + /> + </template> + <template v-for="status in timeline.visibleStatuses"> + <conversation + v-if="!excludedStatusIdsObject[status.id]" + :key="status.id" + class="status-fadein" + :statusoid="status" + :collapsable="true" + /> + </template> </div> </div> <div :class="classes.footer"> diff --git a/src/components/user_card/user_card.js b/src/components/user_card/user_card.js index e019ebbd..82d3b835 100644 --- a/src/components/user_card/user_card.js +++ b/src/components/user_card/user_card.js @@ -7,7 +7,7 @@ import { requestFollow, requestUnfollow } from '../../services/follow_manipulate import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' export default { - props: [ 'user', 'switcher', 'selected', 'hideBio', 'rounded', 'bordered' ], + props: [ 'user', 'switcher', 'selected', 'hideBio', 'rounded', 'bordered', 'allowZoomingAvatar' ], data () { return { followRequestInProgress: false, @@ -162,6 +162,14 @@ export default { }, reportUser () { this.$store.dispatch('openUserReportingModal', this.user.id) + }, + zoomAvatar () { + const attachment = { + url: this.user.profile_image_url_original, + mimetype: 'image' + } + this.$store.dispatch('setMedia', [attachment]) + this.$store.dispatch('setCurrent', attachment) } } } diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue index 9e142480..fc18e240 100644 --- a/src/components/user_card/user_card.vue +++ b/src/components/user_card/user_card.vue @@ -7,7 +7,23 @@ <div class="panel-heading"> <div class="user-info"> <div class="container"> - <router-link :to="userProfileLink(user)"> + <a + v-if="allowZoomingAvatar" + class="user-info-avatar-link" + @click="zoomAvatar" + > + <UserAvatar + :better-shadow="betterShadow" + :user="user" + /> + <div class="user-info-avatar-link-overlay"> + <i class="button-icon icon-zoom-in" /> + </div> + </a> + <router-link + v-else + :to="userProfileLink(user)" + > <UserAvatar :better-shadow="betterShadow" :user="user" @@ -351,6 +367,7 @@ .container { padding: 16px 0 6px; display: flex; + align-items: flex-start; max-height: 56px; .avatar { @@ -372,6 +389,35 @@ } } + &-avatar-link { + position: relative; + cursor: pointer; + + &-overlay { + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.3); + display: flex; + justify-content: center; + align-items: center; + border-radius: $fallback--avatarRadius; + border-radius: var(--avatarRadius, $fallback--avatarRadius); + opacity: 0; + transition: opacity .2s ease; + + i { + color: #FFF; + } + } + + &:hover &-overlay { + opacity: 1; + } + } + .usersettings { color: $fallback--lightText; color: var(--lightText, $fallback--lightText); diff --git a/src/components/user_profile/user_profile.vue b/src/components/user_profile/user_profile.vue index 4ea0a869..cffa28f1 100644 --- a/src/components/user_profile/user_profile.vue +++ b/src/components/user_profile/user_profile.vue @@ -8,6 +8,7 @@ :user="user" :switcher="true" :selected="timeline.viewing" + :allow-zooming-avatar="true" rounded="top" /> <tab-switcher @@ -15,25 +16,14 @@ :render-only-focused="true" > <div :label="$t('user_card.statuses')"> - <div class="timeline"> - <template v-for="statusId in user.pinnedStatuseIds"> - <Conversation - v-if="timeline.statusesObject[statusId]" - :key="statusId" - class="status-fadein" - :statusoid="timeline.statusesObject[statusId]" - :collapsable="true" - :show-pinned="true" - /> - </template> - </div> <Timeline :count="user.statuses_count" :embedded="true" :title="$t('user_profile.timeline_title')" :timeline="timeline" - :timeline-name="'user'" + timeline-name="user" :user-id="userId" + :pinned-status-ids="user.pinnedStatusIds" /> </div> <div diff --git a/src/i18n/es.json b/src/i18n/es.json index bf87937a..f88e90f7 100644 --- a/src/i18n/es.json +++ b/src/i18n/es.json @@ -106,6 +106,9 @@ "expired": "La encuesta terminó hace {0}", "not_enough_options": "Muy pocas opciones únicas en la encuesta" }, + "stickers": { + "add_sticker": "Añadir Pegatina" + }, "interactions": { "favs_repeats": "Favoritos y Repetidos", "follows": "Nuevos seguidores", diff --git a/src/modules/users.js b/src/modules/users.js index 57d3a3e3..4d78aef5 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -3,7 +3,6 @@ import oauthApi from '../services/new_api/oauth.js' import { compact, map, each, merge, last, concat, uniq } from 'lodash' import { set } from 'vue' import { registerPushNotifications, unregisterPushNotifications } from '../services/push/push.js' -import { humanizeErrors } from './errors' // TODO: Unify with mergeOrAdd in statuses.js export const mergeOrAdd = (arr, obj, item) => { @@ -167,11 +166,11 @@ export const mutations = { }, setPinned (state, status) { const user = state.usersObject[status.user.id] - const index = user.pinnedStatuseIds.indexOf(status.id) + const index = user.pinnedStatusIds.indexOf(status.id) if (status.pinned && index === -1) { - user.pinnedStatuseIds.push(status.id) + user.pinnedStatusIds.push(status.id) } else if (!status.pinned && index !== -1) { - user.pinnedStatuseIds.splice(index, 1) + user.pinnedStatusIds.splice(index, 1) } }, setUserForStatus (state, status) { @@ -382,16 +381,8 @@ const users = { store.dispatch('loginUser', data.access_token) } catch (e) { let errors = e.message - // replace ap_id with username - if (typeof errors === 'object') { - if (errors.ap_id) { - errors.username = errors.ap_id - delete errors.ap_id - } - errors = humanizeErrors(errors) - } store.commit('signUpFailure', errors) - throw Error(errors) + throw e } }, async getCaptcha (store) { diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 1f6e1ebd..083d4f4f 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -1,7 +1,7 @@ import { each, map, concat, last } from 'lodash' import { parseStatus, parseUser, parseNotification, parseAttachment } from '../entity_normalizer/entity_normalizer.service.js' import 'whatwg-fetch' -import { StatusCodeError } from '../errors/errors' +import { RegistrationError, StatusCodeError } from '../errors/errors' /* eslint-env browser */ const EXTERNAL_PROFILE_URL = '/api/externalprofile/show.json' @@ -201,12 +201,11 @@ const register = ({ params, credentials }) => { ...rest }) }) - .then((response) => [response.ok, response]) - .then(([ok, response]) => { - if (ok) { + .then((response) => { + if (response.ok) { return response.json() } else { - return response.json().then((error) => { throw new Error(error) }) + return response.json().then((error) => { throw new RegistrationError(error) }) } }) } diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js index a79c6f07..c8474302 100644 --- a/src/services/entity_normalizer/entity_normalizer.service.js +++ b/src/services/entity_normalizer/entity_normalizer.service.js @@ -152,7 +152,7 @@ export const parseUser = (data) => { output.statuses_count = data.statuses_count output.friendIds = [] output.followerIds = [] - output.pinnedStatuseIds = [] + output.pinnedStatusIds = [] if (data.pleroma) { output.follow_request_count = data.pleroma.follow_request_count diff --git a/src/services/errors/errors.js b/src/services/errors/errors.js index 548f3c68..590552da 100644 --- a/src/services/errors/errors.js +++ b/src/services/errors/errors.js @@ -1,3 +1,5 @@ +import { humanizeErrors } from '../../modules/errors' + export function StatusCodeError (statusCode, body, options, response) { this.name = 'StatusCodeError' this.statusCode = statusCode @@ -12,3 +14,36 @@ export function StatusCodeError (statusCode, body, options, response) { } StatusCodeError.prototype = Object.create(Error.prototype) StatusCodeError.prototype.constructor = StatusCodeError + +export class RegistrationError extends Error { + constructor (error) { + super() + if (Error.captureStackTrace) { + Error.captureStackTrace(this) + } + + try { + // the error is probably a JSON object with a single key, "errors", whose value is another JSON object containing the real errors + if (typeof error === 'string') { + error = JSON.parse(error) + if (error.hasOwnProperty('error')) { + error = JSON.parse(error.error) + } + } + + if (typeof error === 'object') { + // replace ap_id with username + if (error.ap_id) { + error.username = error.ap_id + delete error.ap_id + } + this.message = humanizeErrors(error) + } else { + this.message = error + } + } catch (e) { + // can't parse it, so just treat it like a string + this.message = error + } + } +} |
