aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/App.scss11
-rw-r--r--src/components/chat_panel/chat_panel.vue11
-rw-r--r--src/components/follow_list/follow_list.js7
-rw-r--r--src/components/follow_list/follow_list.vue3
-rw-r--r--src/components/image_cropper/image_cropper.js128
-rw-r--r--src/components/image_cropper/image_cropper.vue42
-rw-r--r--src/components/media_modal/media_modal.js51
-rw-r--r--src/components/media_modal/media_modal.vue81
-rw-r--r--src/components/nav_panel/nav_panel.vue11
-rw-r--r--src/components/notifications/notifications.scss3
-rw-r--r--src/components/post_status_form/post_status_form.vue8
-rw-r--r--src/components/settings/settings.vue14
-rw-r--r--src/components/side_drawer/side_drawer.vue4
-rw-r--r--src/components/status/status.vue2
-rw-r--r--src/components/user_card/user_card.js7
-rw-r--r--src/components/user_card/user_card.vue102
-rw-r--r--src/components/user_card_content/user_card_content.vue4
-rw-r--r--src/components/user_settings/user_settings.js36
-rw-r--r--src/components/user_settings/user_settings.vue25
-rw-r--r--src/i18n/en.json12
-rw-r--r--src/modules/users.js10
-rw-r--r--src/services/entity_normalizer/entity_normalizer.service.js3
22 files changed, 446 insertions, 129 deletions
diff --git a/src/App.scss b/src/App.scss
index 52484f59..7c6970c1 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -181,8 +181,7 @@ input, textarea, .select {
color: $fallback--text;
color: var(--text, $fallback--text);
}
- &:disabled,
- {
+ &:disabled {
&,
& + label,
& + label::before {
@@ -649,10 +648,6 @@ nav {
color: var(--lightText, $fallback--lightText);
}
- .text-format {
- float: right;
- }
-
div {
padding-top: 5px;
}
@@ -739,3 +734,7 @@ nav {
width: 100%;
}
}
+
+.btn.btn-default {
+ min-height: 28px;
+}
diff --git a/src/components/chat_panel/chat_panel.vue b/src/components/chat_panel/chat_panel.vue
index bf65efc5..b37469ac 100644
--- a/src/components/chat_panel/chat_panel.vue
+++ b/src/components/chat_panel/chat_panel.vue
@@ -3,8 +3,8 @@
<div class="panel panel-default">
<div class="panel-heading timeline-heading" :class="{ 'chat-heading': floating }" @click.stop.prevent="togglePanel">
<div class="title">
- {{$t('chat.title')}}
- <i class="icon-cancel" style="float: right;" v-if="floating"></i>
+ <span>{{$t('chat.title')}}</span>
+ <i class="icon-cancel" v-if="floating"></i>
</div>
</div>
<div class="chat-window" v-chat-scroll>
@@ -98,4 +98,11 @@
resize: none;
}
}
+
+.chat-panel {
+ .title {
+ display: flex;
+ justify-content: space-between;
+ }
+}
</style>
diff --git a/src/components/follow_list/follow_list.js b/src/components/follow_list/follow_list.js
index acdb216d..9777c87e 100644
--- a/src/components/follow_list/follow_list.js
+++ b/src/components/follow_list/follow_list.js
@@ -26,7 +26,9 @@ const FollowList = {
entries () {
return this.showFollowers ? this.user.followers : this.user.friends
},
- showActions () { return this.$store.state.users.currentUser.id === this.userId }
+ showFollowsYou () {
+ return !this.showFollowers || (this.showFollowers && this.userId !== this.$store.state.users.currentUser.id)
+ }
},
methods: {
fetchEntries () {
@@ -55,6 +57,9 @@ const FollowList = {
}
}
},
+ watch: {
+ 'user': 'fetchEntries'
+ },
components: {
UserCard
}
diff --git a/src/components/follow_list/follow_list.vue b/src/components/follow_list/follow_list.vue
index 7be2e7b7..27102edf 100644
--- a/src/components/follow_list/follow_list.vue
+++ b/src/components/follow_list/follow_list.vue
@@ -3,8 +3,7 @@
<user-card
v-for="entry in entries"
:key="entry.id" :user="entry"
- :showFollows="!showFollowers"
- :showActions="showActions"
+ :noFollowsYou="!showFollowsYou"
/>
<div class="text-center panel-footer">
<a v-if="error" @click="fetchEntries" class="alert error">
diff --git a/src/components/image_cropper/image_cropper.js b/src/components/image_cropper/image_cropper.js
new file mode 100644
index 00000000..990c0370
--- /dev/null
+++ b/src/components/image_cropper/image_cropper.js
@@ -0,0 +1,128 @@
+import Cropper from 'cropperjs'
+import 'cropperjs/dist/cropper.css'
+
+const ImageCropper = {
+ props: {
+ trigger: {
+ type: [String, window.Element],
+ required: true
+ },
+ submitHandler: {
+ type: Function,
+ required: true
+ },
+ cropperOptions: {
+ type: Object,
+ default () {
+ return {
+ aspectRatio: 1,
+ autoCropArea: 1,
+ viewMode: 1,
+ movable: false,
+ zoomable: false,
+ guides: false
+ }
+ }
+ },
+ mimes: {
+ type: String,
+ default: 'image/png, image/gif, image/jpeg, image/bmp, image/x-icon'
+ },
+ saveButtonLabel: {
+ type: String
+ },
+ cancelButtonLabel: {
+ type: String
+ }
+ },
+ data () {
+ return {
+ cropper: undefined,
+ dataUrl: undefined,
+ filename: undefined,
+ submitting: false,
+ submitError: null
+ }
+ },
+ computed: {
+ saveText () {
+ return this.saveButtonLabel || this.$t('image_cropper.save')
+ },
+ cancelText () {
+ return this.cancelButtonLabel || this.$t('image_cropper.cancel')
+ },
+ submitErrorMsg () {
+ return this.submitError && this.submitError instanceof Error ? this.submitError.toString() : this.submitError
+ }
+ },
+ methods: {
+ destroy () {
+ if (this.cropper) {
+ this.cropper.destroy()
+ }
+ this.$refs.input.value = ''
+ this.dataUrl = undefined
+ this.$emit('close')
+ },
+ submit () {
+ this.submitting = true
+ this.avatarUploadError = null
+ this.submitHandler(this.cropper, this.filename)
+ .then(() => this.destroy())
+ .catch((err) => {
+ this.submitError = err
+ })
+ .finally(() => {
+ this.submitting = false
+ })
+ },
+ pickImage () {
+ this.$refs.input.click()
+ },
+ createCropper () {
+ this.cropper = new Cropper(this.$refs.img, this.cropperOptions)
+ },
+ getTriggerDOM () {
+ return typeof this.trigger === 'object' ? this.trigger : document.querySelector(this.trigger)
+ },
+ readFile () {
+ const fileInput = this.$refs.input
+ if (fileInput.files != null && fileInput.files[0] != null) {
+ let reader = new window.FileReader()
+ reader.onload = (e) => {
+ this.dataUrl = e.target.result
+ this.$emit('open')
+ }
+ reader.readAsDataURL(fileInput.files[0])
+ this.filename = fileInput.files[0].name || 'unknown'
+ this.$emit('changed', fileInput.files[0], reader)
+ }
+ },
+ clearError () {
+ this.submitError = null
+ }
+ },
+ mounted () {
+ // listen for click event on trigger
+ const trigger = this.getTriggerDOM()
+ if (!trigger) {
+ this.$emit('error', 'No image make trigger found.', 'user')
+ } else {
+ trigger.addEventListener('click', this.pickImage)
+ }
+ // listen for input file changes
+ const fileInput = this.$refs.input
+ fileInput.addEventListener('change', this.readFile)
+ },
+ beforeDestroy: function () {
+ // remove the event listeners
+ const trigger = this.getTriggerDOM()
+ if (trigger) {
+ trigger.removeEventListener('click', this.pickImage)
+ }
+ const fileInput = this.$refs.input
+ fileInput.removeEventListener('change', this.readFile)
+ }
+}
+
+export default ImageCropper
diff --git a/src/components/image_cropper/image_cropper.vue b/src/components/image_cropper/image_cropper.vue
new file mode 100644
index 00000000..24a6f3bd
--- /dev/null
+++ b/src/components/image_cropper/image_cropper.vue
@@ -0,0 +1,42 @@
+<template>
+ <div class="image-cropper">
+ <div v-if="dataUrl">
+ <div class="image-cropper-image-container">
+ <img ref="img" :src="dataUrl" alt="" @load.stop="createCropper" />
+ </div>
+ <div class="image-cropper-buttons-wrapper">
+ <button class="btn" type="button" :disabled="submitting" @click="submit" v-text="saveText"></button>
+ <button class="btn" type="button" :disabled="submitting" @click="destroy" v-text="cancelText"></button>
+ <i class="icon-spin4 animate-spin" v-if="submitting"></i>
+ </div>
+ <div class="alert error" v-if="submitError">
+ {{submitErrorMsg}}
+ <i class="button-icon icon-cancel" @click="clearError"></i>
+ </div>
+ </div>
+ <input ref="input" type="file" class="image-cropper-img-input" :accept="mimes">
+ </div>
+</template>
+
+<script src="./image_cropper.js"></script>
+
+<style lang="scss">
+.image-cropper {
+ &-img-input {
+ display: none;
+ }
+
+ &-image-container {
+ position: relative;
+
+ img {
+ display: block;
+ max-width: 100%;
+ }
+ }
+
+ &-buttons-wrapper {
+ margin-top: 15px;
+ }
+}
+</style>
diff --git a/src/components/media_modal/media_modal.js b/src/components/media_modal/media_modal.js
index 14ae19d4..992d7129 100644
--- a/src/components/media_modal/media_modal.js
+++ b/src/components/media_modal/media_modal.js
@@ -11,27 +11,62 @@ const MediaModal = {
showing () {
return this.$store.state.mediaViewer.activated
},
+ media () {
+ return this.$store.state.mediaViewer.media
+ },
currentIndex () {
return this.$store.state.mediaViewer.currentIndex
},
currentMedia () {
- return this.$store.state.mediaViewer.media[this.currentIndex]
+ return this.media[this.currentIndex]
+ },
+ canNavigate () {
+ return this.media.length > 1
},
type () {
return this.currentMedia ? fileTypeService.fileType(this.currentMedia.mimetype) : null
}
},
- created () {
- document.addEventListener('keyup', e => {
- if (e.keyCode === 27 && this.showing) { // escape
- this.hide()
- }
- })
- },
methods: {
hide () {
this.$store.dispatch('closeMediaViewer')
+ },
+ goPrev () {
+ if (this.canNavigate) {
+ const prevIndex = this.currentIndex === 0 ? this.media.length - 1 : (this.currentIndex - 1)
+ this.$store.dispatch('setCurrent', this.media[prevIndex])
+ }
+ },
+ goNext () {
+ if (this.canNavigate) {
+ const nextIndex = this.currentIndex === this.media.length - 1 ? 0 : (this.currentIndex + 1)
+ this.$store.dispatch('setCurrent', this.media[nextIndex])
+ }
+ },
+ handleKeyupEvent (e) {
+ if (this.showing && e.keyCode === 27) { // escape
+ this.hide()
+ }
+ },
+ handleKeydownEvent (e) {
+ if (!this.showing) {
+ return
+ }
+
+ if (e.keyCode === 39) { // arrow right
+ this.goNext()
+ } else if (e.keyCode === 37) { // arrow left
+ this.goPrev()
+ }
}
+ },
+ mounted () {
+ document.addEventListener('keyup', this.handleKeyupEvent)
+ document.addEventListener('keydown', this.handleKeydownEvent)
+ },
+ destroyed () {
+ document.removeEventListener('keyup', this.handleKeyupEvent)
+ document.removeEventListener('keydown', this.handleKeydownEvent)
}
}
diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue
index 796d4e40..427bf12b 100644
--- a/src/components/media_modal/media_modal.vue
+++ b/src/components/media_modal/media_modal.vue
@@ -8,6 +8,22 @@
:controls="true"
@click.stop.native="">
</VideoAttachment>
+ <button
+ :title="$t('media_modal.previous')"
+ class="modal-view-button-arrow modal-view-button-arrow--prev"
+ v-if="canNavigate"
+ @click.stop.prevent="goPrev"
+ >
+ <i class="icon-left-open arrow-icon" />
+ </button>
+ <button
+ :title="$t('media_modal.next')"
+ class="modal-view-button-arrow modal-view-button-arrow--next"
+ v-if="canNavigate"
+ @click.stop.prevent="goNext"
+ >
+ <i class="icon-right-open arrow-icon" />
+ </button>
</div>
</template>
@@ -19,15 +35,29 @@
.modal-view {
z-index: 1000;
position: fixed;
- width: 100vw;
- height: 100vh;
top: 0;
left: 0;
+ right: 0;
+ bottom: 0;
display: flex;
justify-content: center;
align-items: center;
background-color: rgba(0, 0, 0, 0.5);
- cursor: pointer;
+
+ &:hover {
+ .modal-view-button-arrow {
+ opacity: 0.75;
+
+ &:focus,
+ &:hover {
+ outline: none;
+ box-shadow: none;
+ }
+ &:hover {
+ opacity: 1;
+ }
+ }
+ }
}
.modal-image {
@@ -35,4 +65,49 @@
max-height: 90%;
box-shadow: 0px 5px 15px 0 rgba(0, 0, 0, 0.5);
}
+
+.modal-view-button-arrow {
+ position: absolute;
+ display: block;
+ top: 50%;
+ margin-top: -50px;
+ width: 70px;
+ height: 100px;
+ border: 0;
+ padding: 0;
+ opacity: 0;
+ box-shadow: none;
+ background: none;
+ appearance: none;
+ overflow: visible;
+ cursor: pointer;
+ transition: opacity 333ms cubic-bezier(.4,0,.22,1);
+
+ .arrow-icon {
+ position: absolute;
+ top: 35px;
+ height: 30px;
+ width: 32px;
+ font-size: 14px;
+ line-height: 30px;
+ color: #FFF;
+ text-align: center;
+ background-color: rgba(0,0,0,.3);
+ }
+
+ &--prev {
+ left: 0;
+ .arrow-icon {
+ left: 6px;
+ }
+ }
+
+ &--next {
+ right: 0;
+ .arrow-icon {
+ right: 6px;
+ }
+ }
+}
+
</style>
diff --git a/src/components/nav_panel/nav_panel.vue b/src/components/nav_panel/nav_panel.vue
index 3aa0a793..1a269adf 100644
--- a/src/components/nav_panel/nav_panel.vue
+++ b/src/components/nav_panel/nav_panel.vue
@@ -19,7 +19,10 @@
</li>
<li v-if='currentUser && currentUser.locked'>
<router-link :to="{ name: 'friend-requests' }">
- {{ $t("nav.friend_requests") }}
+ {{ $t("nav.friend_requests")}}
+ <span v-if='currentUser.follow_request_count > 0' class="badge follow-request-count">
+ {{currentUser.follow_request_count}}
+ </span>
</router-link>
</li>
<li>
@@ -52,6 +55,12 @@
padding: 0;
}
+.follow-request-count {
+ margin: -6px 10px;
+ background-color: $fallback--bg;
+ background-color: var(--input, $fallback--faint);
+}
+
.nav-panel li {
border-bottom: 1px solid;
border-color: $fallback--border;
diff --git a/src/components/notifications/notifications.scss b/src/components/notifications/notifications.scss
index bc81d45c..b3364afc 100644
--- a/src/components/notifications/notifications.scss
+++ b/src/components/notifications/notifications.scss
@@ -103,6 +103,7 @@
flex: 1 1 0;
display: flex;
flex-wrap: nowrap;
+ justify-content: space-between;
.name-and-action {
flex: 1;
@@ -123,8 +124,8 @@
object-fit: contain
}
}
+
.timeago {
- float: right;
font-size: 12px;
}
diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue
index 6ed5d92e..b3cc0ce6 100644
--- a/src/components/post_status_form/post_status_form.vue
+++ b/src/components/post_status_form/post_status_form.vue
@@ -118,6 +118,14 @@
}
}
+.post-status-form {
+ .visibility-tray {
+ display: flex;
+ justify-content: space-between;
+ flex-direction: row-reverse;
+ }
+}
+
.post-status-form, .login {
.form-bottom {
display: flex;
diff --git a/src/components/settings/settings.vue b/src/components/settings/settings.vue
index e5f8fefb..f5e00995 100644
--- a/src/components/settings/settings.vue
+++ b/src/components/settings/settings.vue
@@ -311,20 +311,6 @@
color: $fallback--cRed;
}
- .old-avatar {
- width: 128px;
- border-radius: $fallback--avatarRadius;
- border-radius: var(--avatarRadius, $fallback--avatarRadius);
- }
-
- .new-avatar {
- object-fit: cover;
- width: 128px;
- height: 128px;
- border-radius: $fallback--avatarRadius;
- border-radius: var(--avatarRadius, $fallback--avatarRadius);
- }
-
.btn {
min-height: 28px;
min-width: 10em;
diff --git a/src/components/side_drawer/side_drawer.vue b/src/components/side_drawer/side_drawer.vue
index a6c6f237..8eca7b8c 100644
--- a/src/components/side_drawer/side_drawer.vue
+++ b/src/components/side_drawer/side_drawer.vue
@@ -45,6 +45,10 @@
<li v-if="currentUser && currentUser.locked" @click="toggleDrawer">
<router-link to='/friend-requests'>
{{ $t("nav.friend_requests") }}
+ <span v-if='currentUser.follow_request_count > 0' class="badge follow-request-count">
+ {{currentUser.follow_request_count}}
+ </span>
+
</router-link>
</li>
<li @click="toggleDrawer">
diff --git a/src/components/status/status.vue b/src/components/status/status.vue
index aae365d1..3fc5b486 100644
--- a/src/components/status/status.vue
+++ b/src/components/status/status.vue
@@ -554,7 +554,7 @@ a.unmute {
.timeline > {
.status-el:last-child {
- border-bottom-radius: 0 0 $fallback--panelRadius $fallback--panelRadius;;
+ border-radius: 0 0 $fallback--panelRadius $fallback--panelRadius;
border-radius: 0 0 var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius);
border-bottom: none;
}
diff --git a/src/components/user_card/user_card.js b/src/components/user_card/user_card.js
index a4c84716..28e22f09 100644
--- a/src/components/user_card/user_card.js
+++ b/src/components/user_card/user_card.js
@@ -6,9 +6,8 @@ import { requestFollow, requestUnfollow } from '../../services/follow_manipulate
const UserCard = {
props: [
'user',
- 'showFollows',
- 'showApproval',
- 'showActions'
+ 'noFollowsYou',
+ 'showApproval'
],
data () {
return {
@@ -26,7 +25,7 @@ const UserCard = {
currentUser () { return this.$store.state.users.currentUser },
following () { return this.updated ? this.updated.following : this.user.following },
showFollow () {
- return this.showActions && (!this.showFollows && !this.following || this.updated && !this.updated.following)
+ return !this.showApproval && (!this.following || this.updated && !this.updated.following)
}
},
methods: {
diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue
index 12960c02..ce4edb3c 100644
--- a/src/components/user_card/user_card.vue
+++ b/src/components/user_card/user_card.vue
@@ -1,27 +1,31 @@
<template>
<div class="card">
<router-link :to="userProfileLink(user)">
- <UserAvatar class="avatar" :compact="true" @click.prevent.native="toggleUserExpanded" :src="user.profile_image_url"/>
+ <UserAvatar class="avatar" @click.prevent.native="toggleUserExpanded" :src="user.profile_image_url"/>
</router-link>
- <div class="usercard" v-if="userExpanded">
- <user-card-content :user="user" :switcher="false"></user-card-content>
- </div>
- <div class="name-and-screen-name" v-else>
- <div :title="user.name" class="user-name">
- <span v-if="user.name_html" v-html="user.name_html"></span>
- <span v-else>{{ user.name }}</span>
- <span class="follows-you" v-if="!userExpanded && showFollows && user.follows_you">
+ <div class="user-card-main-content">
+ <div class="usercard" v-if="userExpanded">
+ <user-card-content :user="user" :switcher="false"></user-card-content>
+ </div>
+ <div class="name-and-screen-name" v-if="!userExpanded">
+ <div :title="user.name" class="user-name">
+ <span v-if="user.name_html" v-html="user.name_html"></span>
+ <span v-else>{{ user.name }}</span>
+ </div>
+ <div class="user-link-action">
+ <router-link class='user-screen-name' :to="userProfileLink(user)">
+ @{{user.screen_name}}
+ </router-link>
+ </div>
+ </div>
+ <div class="follow-box" v-if="!userExpanded">
+ <span class="faint" v-if="!noFollowsYou && user.follows_you">
{{ currentUser.id == user.id ? $t('user_card.its_you') : $t('user_card.follows_you') }}
</span>
- </div>
- <div class="user-link-action">
- <router-link class='user-screen-name' :to="userProfileLink(user)">
- @{{user.screen_name}}
- </router-link>
- <button
- v-if="showFollow"
- class="btn btn-default"
- @click="followUser"
+ <button
+ v-if="showFollow"
+ class="btn btn-default"
+ @click="followUser"
:disabled="followRequestInProgress"
:title="followRequestSent ? $t('user_card.follow_again') : ''"
>
@@ -35,7 +39,7 @@
{{ $t('user_card.follow') }}
</template>
</button>
- <button v-if="showActions && showFollows && following" class="btn btn-default" @click="unfollowUser" :disabled="followRequestInProgress">
+ <button v-if="following" class="btn btn-default pressed" @click="unfollowUser" :disabled="followRequestInProgress">
<template v-if="followRequestInProgress">
{{ $t('user_card.follow_progress') }}
</template>
@@ -44,10 +48,10 @@
</template>
</button>
</div>
- </div>
- <div class="approval" v-if="showApproval">
- <button class="btn btn-default" @click="approveUser">{{ $t('user_card.approve') }}</button>
- <button class="btn btn-default" @click="denyUser">{{ $t('user_card.deny') }}</button>
+ <div class="approval" v-if="showApproval">
+ <button class="btn btn-default" @click="approveUser">{{ $t('user_card.approve') }}</button>
+ <button class="btn btn-default" @click="denyUser">{{ $t('user_card.deny') }}</button>
+ </div>
</div>
</div>
</template>
@@ -57,15 +61,19 @@
<style lang="scss">
@import '../../_variables.scss';
-.name-and-screen-name {
+.user-card-main-content {
+ display: flex;
+ flex-direction: column;
+ flex: 1 1 100%;
margin-left: 0.7em;
- margin-top:0.0em;
+ min-width: 0;
+}
+
+.name-and-screen-name {
text-align: left;
width: 100%;
- .user-name {
- display: flex;
- justify-content: space-between;
+ .user-name {
img {
object-fit: contain;
height: 16px;
@@ -73,21 +81,14 @@
vertical-align: middle;
}
}
-
+
.user-link-action {
display: flex;
align-items: flex-start;
justify-content: space-between;
-
- button {
- margin-top: 3px;
- }
}
}
-.follows-you {
- margin-left: 2em;
-}
.card {
display: flex;
@@ -99,16 +100,31 @@
border-bottom: 1px solid;
margin: 0;
border-bottom-color: $fallback--border;
- border-bottom-color: var(--border, $fallback--border);
+ border-bottom-color: var(--border, $fallback--border);
.avatar {
padding: 0;
}
+
+ .follow-box {
+ text-align: center;
+ flex-shrink: 0;
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ flex-wrap: wrap;
+ line-height: 1.5em;
+
+ .btn {
+ margin-top: 0.5em;
+ margin-left: auto;
+ width: 10em;
+ }
+ }
}
.usercard {
width: fill-available;
- margin: 0.2em 0 0 0.7em;
border-radius: $fallback--panelRadius;
border-radius: var(--panelRadius, $fallback--panelRadius);
border-style: solid;
@@ -129,9 +145,15 @@
}
.approval {
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
button {
- width: 100%;
- margin-bottom: 0.5em;
+ margin-top: 0.5em;
+ margin-right: 0.5em;
+ flex: 1 1;
+ max-width: 12em;
+ min-width: 8em;
}
}
</style>
diff --git a/src/components/user_card_content/user_card_content.vue b/src/components/user_card_content/user_card_content.vue
index 7f9909c4..a3d24eb1 100644
--- a/src/components/user_card_content/user_card_content.vue
+++ b/src/components/user_card_content/user_card_content.vue
@@ -13,7 +13,7 @@
<router-link :to="{ name: 'user-settings' }" v-if="!isOtherUser">
<i class="button-icon icon-cog usersettings" :title="$t('tool_tip.user_settings')"></i>
</router-link>
- <a :href="user.statusnet_profile_url" target="_blank" v-if="isOtherUser">
+ <a :href="user.statusnet_profile_url" target="_blank" v-if="isOtherUser && !user.is_local">
<i class="icon-link-ext usersettings"></i>
</a>
</div>
@@ -386,6 +386,4 @@
}
}
-.floater {
-}
</style>
diff --git a/src/components/user_settings/user_settings.js b/src/components/user_settings/user_settings.js
index 3ad711c2..904060b2 100644
--- a/src/components/user_settings/user_settings.js
+++ b/src/components/user_settings/user_settings.js
@@ -1,6 +1,7 @@
import { unescape } from 'lodash'
import TabSwitcher from '../tab_switcher/tab_switcher.js'
+import ImageCropper from '../image_cropper/image_cropper.vue'
import StyleSwitcher from '../style_switcher/style_switcher.vue'
import fileSizeFormatService from '../../services/file_size_format/file_size_format.js'
@@ -20,14 +21,12 @@ const UserSettings = {
followImportError: false,
followsImported: false,
enableFollowsExport: true,
- avatarUploading: false,
+ pickAvatarBtnVisible: true,
bannerUploading: false,
backgroundUploading: false,
followListUploading: false,
- avatarPreview: null,
bannerPreview: null,
backgroundPreview: null,
- avatarUploadError: null,
bannerUploadError: null,
backgroundUploadError: null,
deletingAccount: false,
@@ -41,7 +40,8 @@ const UserSettings = {
},
components: {
StyleSwitcher,
- TabSwitcher
+ TabSwitcher,
+ ImageCropper
},
computed: {
user () {
@@ -120,35 +120,15 @@ const UserSettings = {
}
reader.readAsDataURL(file)
},
- submitAvatar () {
- if (!this.avatarPreview) { return }
-
- let img = this.avatarPreview
- // eslint-disable-next-line no-undef
- let imginfo = new Image()
- let cropX, cropY, cropW, cropH
- imginfo.src = img
- if (imginfo.height > imginfo.width) {
- cropX = 0
- cropW = imginfo.width
- cropY = Math.floor((imginfo.height - imginfo.width) / 2)
- cropH = imginfo.width
- } else {
- cropY = 0
- cropH = imginfo.height
- cropX = Math.floor((imginfo.width - imginfo.height) / 2)
- cropW = imginfo.height
- }
- this.avatarUploading = true
- this.$store.state.api.backendInteractor.updateAvatar({params: {img, cropX, cropY, cropW, cropH}}).then((user) => {
+ submitAvatar (cropper) {
+ const img = cropper.getCroppedCanvas().toDataURL('image/jpeg')
+ return this.$store.state.api.backendInteractor.updateAvatar({ params: { img } }).then((user) => {
if (!user.error) {
this.$store.commit('addNewUsers', [user])
this.$store.commit('setCurrentUser', user)
- this.avatarPreview = null
} else {
- this.avatarUploadError = this.$t('upload.error.base') + user.error
+ throw new Error(this.$t('upload.error.base') + user.error)
}
- this.avatarUploading = false
})
},
clearUploadError (slot) {
diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue
index d1770562..948b5c25 100644
--- a/src/components/user_settings/user_settings.vue
+++ b/src/components/user_settings/user_settings.vue
@@ -61,19 +61,10 @@
<h2>{{$t('settings.avatar')}}</h2>
<p class="visibility-notice">{{$t('settings.avatar_size_instruction')}}</p>
<p>{{$t('settings.current_avatar')}}</p>
- <img :src="user.profile_image_url_original" class="old-avatar"></img>
+ <img :src="user.profile_image_url_original" class="current-avatar"></img>
<p>{{$t('settings.set_new_avatar')}}</p>
- <img class="new-avatar" v-bind:src="avatarPreview" v-if="avatarPreview">
- </img>
- <div>
- <input type="file" @change="uploadFile('avatar', $event)" ></input>
- </div>
- <i class="icon-spin4 animate-spin" v-if="avatarUploading"></i>
- <button class="btn btn-default" v-else-if="avatarPreview" @click="submitAvatar">{{$t('general.submit')}}</button>
- <div class='alert error' v-if="avatarUploadError">
- Error: {{ avatarUploadError }}
- <i class="button-icon icon-cancel" @click="clearUploadError('avatar')"></i>
- </div>
+ <button class="btn" type="button" id="pick-avatar" v-show="pickAvatarBtnVisible">{{$t('settings.upload_a_photo')}}</button>
+ <image-cropper trigger="#pick-avatar" :submitHandler="submitAvatar" @open="pickAvatarBtnVisible=false" @close="pickAvatarBtnVisible=true" />
</div>
<div class="setting-item">
<h2>{{$t('settings.profile_banner')}}</h2>
@@ -180,6 +171,8 @@
</script>
<style lang="scss">
+@import '../../_variables.scss';
+
.profile-edit {
.bio {
margin: 0;
@@ -206,5 +199,13 @@
.bg {
max-width: 100%;
}
+
+ .current-avatar {
+ display: block;
+ width: 150px;
+ height: 150px;
+ border-radius: $fallback--avatarRadius;
+ border-radius: var(--avatarRadius, $fallback--avatarRadius);
+ }
}
</style>
diff --git a/src/i18n/en.json b/src/i18n/en.json
index c664fbfa..78e8fced 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -21,6 +21,11 @@
"more": "More",
"generic_error": "An error occured"
},
+ "image_cropper": {
+ "crop_picture": "Crop picture",
+ "save": "Save",
+ "cancel": "Cancel"
+ },
"login": {
"login": "Log in",
"description": "Log in with OAuth",
@@ -31,6 +36,10 @@
"username": "Username",
"hint": "Log in to join the discussion"
},
+ "media_modal": {
+ "previous": "Previous",
+ "next": "Next"
+ },
"nav": {
"about": "About",
"back": "Back",
@@ -206,6 +215,7 @@
"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.",
"tooltipRadius": "Tooltips/alerts",
+ "upload_a_photo": "Upload a photo",
"user_settings": "User Settings",
"values": {
"false": "no",
@@ -344,7 +354,7 @@
"follow_sent": "Request sent!",
"follow_progress": "Requesting…",
"follow_again": "Send request again?",
- "follow_unfollow": "Stop following",
+ "follow_unfollow": "Unfollow",
"followees": "Following",
"followers": "Followers",
"following": "Following!",
diff --git a/src/modules/users.js b/src/modules/users.js
index 4d56ec6f..000cfd72 100644
--- a/src/modules/users.js
+++ b/src/modules/users.js
@@ -231,8 +231,14 @@ const users = {
store.commit('setToken', result.access_token)
store.dispatch('loginUser', result.access_token)
} else {
- let data = await response.json()
- let errors = humanizeErrors(JSON.parse(data.error))
+ const data = await response.json()
+ let errors = JSON.parse(data.error)
+ // replace ap_id with username
+ if (errors.ap_id) {
+ errors.username = errors.ap_id
+ delete errors.ap_id
+ }
+ errors = humanizeErrors(errors)
store.commit('signUpFailure', errors)
throw Error(errors)
}
diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js
index 828c48f9..d20ce77f 100644
--- a/src/services/entity_normalizer/entity_normalizer.service.js
+++ b/src/services/entity_normalizer/entity_normalizer.service.js
@@ -117,6 +117,9 @@ export const parseUser = (data) => {
output.statuses_count = data.statuses_count
output.friends = []
output.followers = []
+ if (data.pleroma) {
+ output.follow_request_count = data.pleroma.follow_request_count
+ }
return output
}