aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/App.vue1
-rw-r--r--src/components/search_bar/search_bar.js5
-rw-r--r--src/components/timeline/timeline.js24
-rw-r--r--src/components/timeline/timeline.vue26
-rw-r--r--src/components/user_card/user_card.js10
-rw-r--r--src/components/user_card/user_card.vue48
-rw-r--r--src/components/user_profile/user_profile.vue16
-rw-r--r--src/i18n/es.json3
-rw-r--r--src/modules/users.js17
-rw-r--r--src/services/api/api.service.js9
-rw-r--r--src/services/entity_normalizer/entity_normalizer.service.js2
-rw-r--r--src/services/errors/errors.js35
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
+ }
+ }
+}