aboutsummaryrefslogtreecommitdiff
path: root/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'src/components')
-rw-r--r--src/components/basic_user_card/basic_user_card.vue15
-rw-r--r--src/components/exporter/exporter.js48
-rw-r--r--src/components/exporter/exporter.vue20
-rw-r--r--src/components/follow_card/follow_card.js15
-rw-r--r--src/components/follow_card/follow_card.vue60
-rw-r--r--src/components/image_cropper/image_cropper.js16
-rw-r--r--src/components/image_cropper/image_cropper.vue4
-rw-r--r--src/components/importer/importer.js53
-rw-r--r--src/components/importer/importer.vue28
-rw-r--r--src/components/selectable_list/selectable_list.vue4
-rw-r--r--src/components/user_search/user_search.js14
-rw-r--r--src/components/user_settings/user_settings.js169
-rw-r--r--src/components/user_settings/user_settings.vue28
13 files changed, 286 insertions, 188 deletions
diff --git a/src/components/basic_user_card/basic_user_card.vue b/src/components/basic_user_card/basic_user_card.vue
index 48de6678..25f1fb2a 100644
--- a/src/components/basic_user_card/basic_user_card.vue
+++ b/src/components/basic_user_card/basic_user_card.vue
@@ -44,14 +44,15 @@
width: 16px;
vertical-align: middle;
}
+ }
- &-value {
- display: inline-block;
- max-width: 100%;
- overflow: hidden;
- white-space: nowrap;
- text-overflow: ellipsis;
- }
+ &-user-name-value,
+ &-screen-name {
+ display: inline-block;
+ max-width: 100%;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
}
&-expanded-content {
diff --git a/src/components/exporter/exporter.js b/src/components/exporter/exporter.js
new file mode 100644
index 00000000..8f507416
--- /dev/null
+++ b/src/components/exporter/exporter.js
@@ -0,0 +1,48 @@
+const Exporter = {
+ props: {
+ getContent: {
+ type: Function,
+ required: true
+ },
+ filename: {
+ type: String,
+ default: 'export.csv'
+ },
+ exportButtonLabel: {
+ type: String,
+ default () {
+ return this.$t('exporter.export')
+ }
+ },
+ processingMessage: {
+ type: String,
+ default () {
+ return this.$t('exporter.processing')
+ }
+ }
+ },
+ data () {
+ return {
+ processing: false
+ }
+ },
+ methods: {
+ process () {
+ this.processing = true
+ this.getContent()
+ .then((content) => {
+ const fileToDownload = document.createElement('a')
+ fileToDownload.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(content))
+ fileToDownload.setAttribute('download', this.filename)
+ fileToDownload.style.display = 'none'
+ document.body.appendChild(fileToDownload)
+ fileToDownload.click()
+ document.body.removeChild(fileToDownload)
+ // Add delay before hiding processing state since browser takes some time to handle file download
+ setTimeout(() => { this.processing = false }, 2000)
+ })
+ }
+ }
+}
+
+export default Exporter
diff --git a/src/components/exporter/exporter.vue b/src/components/exporter/exporter.vue
new file mode 100644
index 00000000..f22e579e
--- /dev/null
+++ b/src/components/exporter/exporter.vue
@@ -0,0 +1,20 @@
+<template>
+ <div class="exporter">
+ <div v-if="processing">
+ <i class="icon-spin4 animate-spin exporter-processing"></i>
+ <span>{{processingMessage}}</span>
+ </div>
+ <button class="btn btn-default" @click="process" v-else>{{exportButtonLabel}}</button>
+ </div>
+</template>
+
+<script src="./exporter.js"></script>
+
+<style lang="scss">
+.exporter {
+ &-processing {
+ font-size: 1.5em;
+ margin: 0.25em;
+ }
+}
+</style>
diff --git a/src/components/follow_card/follow_card.js b/src/components/follow_card/follow_card.js
index ac4e265a..dc4a0d41 100644
--- a/src/components/follow_card/follow_card.js
+++ b/src/components/follow_card/follow_card.js
@@ -10,8 +10,7 @@ const FollowCard = {
data () {
return {
inProgress: false,
- requestSent: false,
- updated: false
+ requestSent: false
}
},
components: {
@@ -19,10 +18,8 @@ const FollowCard = {
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
+ isMe () {
+ return this.$store.state.users.currentUser.id === this.user.id
},
loggedIn () {
return this.$store.state.users.currentUser
@@ -31,17 +28,15 @@ const FollowCard = {
methods: {
followUser () {
this.inProgress = true
- requestFollow(this.user, this.$store).then(({ sent, updated }) => {
+ requestFollow(this.user, this.$store).then(({ sent }) => {
this.inProgress = false
this.requestSent = sent
- this.updated = updated
})
},
unfollowUser () {
this.inProgress = true
- requestUnfollow(this.user, this.$store).then(({ updated }) => {
+ requestUnfollow(this.user, this.$store).then(() => {
this.inProgress = false
- this.updated = updated
})
}
}
diff --git a/src/components/follow_card/follow_card.vue b/src/components/follow_card/follow_card.vue
index 9f314fd3..94e2836f 100644
--- a/src/components/follow_card/follow_card.vue
+++ b/src/components/follow_card/follow_card.vue
@@ -4,34 +4,38 @@
<span class="faint" v-if="!noFollowsYou && user.follows_you">
{{ isMe ? $t('user_card.its_you') : $t('user_card.follows_you') }}
</span>
- <div class="follow-card-follow-button" v-if="showFollow && !loggedIn">
- <RemoteFollow :user="user" />
- </div>
- <button
- v-if="showFollow && loggedIn"
- class="btn btn-default follow-card-follow-button"
- @click="followUser"
- :disabled="inProgress"
- :title="requestSent ? $t('user_card.follow_again') : ''"
- >
- <template v-if="inProgress">
- {{ $t('user_card.follow_progress') }}
- </template>
- <template v-else-if="requestSent">
- {{ $t('user_card.follow_sent') }}
- </template>
- <template v-else>
- {{ $t('user_card.follow') }}
- </template>
- </button>
- <button v-if="following" class="btn btn-default follow-card-follow-button pressed" @click="unfollowUser" :disabled="inProgress">
- <template v-if="inProgress">
- {{ $t('user_card.follow_progress') }}
- </template>
- <template v-else>
- {{ $t('user_card.follow_unfollow') }}
- </template>
- </button>
+ <template v-if="!loggedIn">
+ <div class="follow-card-follow-button" v-if="!user.following">
+ <RemoteFollow :user="user" />
+ </div>
+ </template>
+ <template v-else>
+ <button
+ v-if="!user.following"
+ class="btn btn-default follow-card-follow-button"
+ @click="followUser"
+ :disabled="inProgress"
+ :title="requestSent ? $t('user_card.follow_again') : ''"
+ >
+ <template v-if="inProgress">
+ {{ $t('user_card.follow_progress') }}
+ </template>
+ <template v-else-if="requestSent">
+ {{ $t('user_card.follow_sent') }}
+ </template>
+ <template v-else>
+ {{ $t('user_card.follow') }}
+ </template>
+ </button>
+ <button v-else class="btn btn-default follow-card-follow-button pressed" @click="unfollowUser" :disabled="inProgress">
+ <template v-if="inProgress">
+ {{ $t('user_card.follow_progress') }}
+ </template>
+ <template v-else>
+ {{ $t('user_card.follow_unfollow') }}
+ </template>
+ </button>
+ </template>
</div>
</basic-user-card>
</template>
diff --git a/src/components/image_cropper/image_cropper.js b/src/components/image_cropper/image_cropper.js
index 5ba8f04e..01361e25 100644
--- a/src/components/image_cropper/image_cropper.js
+++ b/src/components/image_cropper/image_cropper.js
@@ -70,22 +70,10 @@ const ImageCropper = {
this.dataUrl = undefined
this.$emit('close')
},
- submit () {
+ submit (cropping = true) {
this.submitting = true
this.avatarUploadError = null
- this.submitHandler(this.cropper, this.file)
- .then(() => this.destroy())
- .catch((err) => {
- this.submitError = err
- })
- .finally(() => {
- this.submitting = false
- })
- },
- submitWithoutCropping () {
- this.submitting = true
- this.avatarUploadError = null
- this.submitHandler(false, this.dataUrl)
+ this.submitHandler(cropping && this.cropper, this.file)
.then(() => this.destroy())
.catch((err) => {
this.submitError = err
diff --git a/src/components/image_cropper/image_cropper.vue b/src/components/image_cropper/image_cropper.vue
index 129e6f46..d2b86e9e 100644
--- a/src/components/image_cropper/image_cropper.vue
+++ b/src/components/image_cropper/image_cropper.vue
@@ -5,9 +5,9 @@
<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="submit()" v-text="saveText"></button>
<button class="btn" type="button" :disabled="submitting" @click="destroy" v-text="cancelText"></button>
- <button class="btn" type="button" :disabled="submitting" @click="submitWithoutCropping" v-text="saveWithoutCroppingText"></button>
+ <button class="btn" type="button" :disabled="submitting" @click="submit(false)" v-text="saveWithoutCroppingText"></button>
<i class="icon-spin4 animate-spin" v-if="submitting"></i>
</div>
<div class="alert error" v-if="submitError">
diff --git a/src/components/importer/importer.js b/src/components/importer/importer.js
new file mode 100644
index 00000000..c5f9e4d2
--- /dev/null
+++ b/src/components/importer/importer.js
@@ -0,0 +1,53 @@
+const Importer = {
+ props: {
+ submitHandler: {
+ type: Function,
+ required: true
+ },
+ submitButtonLabel: {
+ type: String,
+ default () {
+ return this.$t('importer.submit')
+ }
+ },
+ successMessage: {
+ type: String,
+ default () {
+ return this.$t('importer.success')
+ }
+ },
+ errorMessage: {
+ type: String,
+ default () {
+ return this.$t('importer.error')
+ }
+ }
+ },
+ data () {
+ return {
+ file: null,
+ error: false,
+ success: false,
+ submitting: false
+ }
+ },
+ methods: {
+ change () {
+ this.file = this.$refs.input.files[0]
+ },
+ submit () {
+ this.dismiss()
+ this.submitting = true
+ this.submitHandler(this.file)
+ .then(() => { this.success = true })
+ .catch(() => { this.error = true })
+ .finally(() => { this.submitting = false })
+ },
+ dismiss () {
+ this.success = false
+ this.error = false
+ }
+ }
+}
+
+export default Importer
diff --git a/src/components/importer/importer.vue b/src/components/importer/importer.vue
new file mode 100644
index 00000000..0c5aa93d
--- /dev/null
+++ b/src/components/importer/importer.vue
@@ -0,0 +1,28 @@
+<template>
+ <div class="importer">
+ <form>
+ <input type="file" ref="input" v-on:change="change" />
+ </form>
+ <i class="icon-spin4 animate-spin importer-uploading" v-if="submitting"></i>
+ <button class="btn btn-default" v-else @click="submit">{{submitButtonLabel}}</button>
+ <div v-if="success">
+ <i class="icon-cross" @click="dismiss"></i>
+ <p>{{successMessage}}</p>
+ </div>
+ <div v-else-if="error">
+ <i class="icon-cross" @click="dismiss"></i>
+ <p>{{errorMessage}}</p>
+ </div>
+ </div>
+</template>
+
+<script src="./importer.js"></script>
+
+<style lang="scss">
+.importer {
+ &-uploading {
+ font-size: 1.5em;
+ margin: 0.25em;
+ }
+}
+</style>
diff --git a/src/components/selectable_list/selectable_list.vue b/src/components/selectable_list/selectable_list.vue
index ba1e5266..3f16c921 100644
--- a/src/components/selectable_list/selectable_list.vue
+++ b/src/components/selectable_list/selectable_list.vue
@@ -31,6 +31,10 @@
&-item-inner {
display: flex;
align-items: center;
+
+ > * {
+ min-width: 0;
+ }
}
&-item-selected-inner {
diff --git a/src/components/user_search/user_search.js b/src/components/user_search/user_search.js
index 55040826..62dafdf1 100644
--- a/src/components/user_search/user_search.js
+++ b/src/components/user_search/user_search.js
@@ -1,5 +1,6 @@
import FollowCard from '../follow_card/follow_card.vue'
-import userSearchApi from '../../services/new_api/user_search.js'
+import map from 'lodash/map'
+
const userSearch = {
components: {
FollowCard
@@ -10,10 +11,15 @@ const userSearch = {
data () {
return {
username: '',
- users: [],
+ userIds: [],
loading: false
}
},
+ computed: {
+ users () {
+ return this.userIds.map(userId => this.$store.getters.findUser(userId))
+ }
+ },
mounted () {
this.search(this.query)
},
@@ -33,10 +39,10 @@ const userSearch = {
return
}
this.loading = true
- userSearchApi.search({query, store: this.$store})
+ this.$store.dispatch('searchUsers', query)
.then((res) => {
this.loading = false
- this.users = res
+ this.userIds = map(res, 'id')
})
}
}
diff --git a/src/components/user_settings/user_settings.js b/src/components/user_settings/user_settings.js
index e88ee612..2418450c 100644
--- a/src/components/user_settings/user_settings.js
+++ b/src/components/user_settings/user_settings.js
@@ -13,6 +13,8 @@ import SelectableList from '../selectable_list/selectable_list.vue'
import ProgressButton from '../progress_button/progress_button.vue'
import EmojiInput from '../emoji-input/emoji-input.vue'
import Autosuggest from '../autosuggest/autosuggest.vue'
+import Importer from '../importer/importer.vue'
+import Exporter from '../exporter/exporter.vue'
import withSubscription from '../../hocs/with_subscription/with_subscription'
import userSearchApi from '../../services/new_api/user_search.js'
@@ -40,14 +42,9 @@ const UserSettings = {
hideFollowers: this.$store.state.users.currentUser.hide_followers,
showRole: this.$store.state.users.currentUser.show_role,
role: this.$store.state.users.currentUser.role,
- followList: null,
- followImportError: false,
- followsImported: false,
- enableFollowsExport: true,
pickAvatarBtnVisible: true,
bannerUploading: false,
backgroundUploading: false,
- followListUploading: false,
bannerPreview: null,
backgroundPreview: null,
bannerUploadError: null,
@@ -75,7 +72,9 @@ const UserSettings = {
Autosuggest,
BlockCard,
MuteCard,
- ProgressButton
+ ProgressButton,
+ Importer,
+ Exporter
},
computed: {
user () {
@@ -110,37 +109,23 @@ const UserSettings = {
},
methods: {
updateProfile () {
- const name = this.newName
- const description = this.newBio
- const locked = this.newLocked
- // Backend notation.
- /* eslint-disable camelcase */
- const default_scope = this.newDefaultScope
- const no_rich_text = this.newNoRichText
- const hide_follows = this.hideFollows
- const hide_followers = this.hideFollowers
- const show_role = this.showRole
-
- /* eslint-enable camelcase */
this.$store.state.api.backendInteractor
.updateProfile({
params: {
- name,
- description,
- locked,
+ note: this.newBio,
+ locked: this.newLocked,
// Backend notation.
/* eslint-disable camelcase */
- default_scope,
- no_rich_text,
- hide_follows,
- hide_followers,
- show_role
+ display_name: this.newName,
+ default_scope: this.newDefaultScope,
+ no_rich_text: this.newNoRichText,
+ hide_follows: this.hideFollows,
+ hide_followers: this.hideFollowers,
+ show_role: this.showRole
/* eslint-enable camelcase */
}}).then((user) => {
- if (!user.error) {
- this.$store.commit('addNewUsers', [user])
- this.$store.commit('setCurrentUser', user)
- }
+ this.$store.commit('addNewUsers', [user])
+ this.$store.commit('setCurrentUser', user)
})
},
changeVis (visibility) {
@@ -160,23 +145,29 @@ const UserSettings = {
reader.onload = ({target}) => {
const img = target.result
this[slot + 'Preview'] = img
+ this[slot] = file
}
reader.readAsDataURL(file)
},
submitAvatar (cropper, file) {
- let img
- if (cropper) {
- img = cropper.getCroppedCanvas().toDataURL(file.type)
- } else {
- img = file
- }
+ const that = this
+ return new Promise((resolve, reject) => {
+ function updateAvatar (avatar) {
+ that.$store.state.api.backendInteractor.updateAvatar({ avatar })
+ .then((user) => {
+ that.$store.commit('addNewUsers', [user])
+ that.$store.commit('setCurrentUser', user)
+ resolve()
+ })
+ .catch((err) => {
+ reject(new Error(that.$t('upload.error.base') + ' ' + err.message))
+ })
+ }
- return this.$store.state.api.backendInteractor.updateAvatar({ params: { img } }).then((user) => {
- if (!user.error) {
- this.$store.commit('addNewUsers', [user])
- this.$store.commit('setCurrentUser', user)
+ if (cropper) {
+ cropper.getCroppedCanvas().toBlob(updateAvatar, file.type)
} else {
- throw new Error(this.$t('upload.error.base') + user.error)
+ updateAvatar(file)
}
})
},
@@ -186,30 +177,17 @@ const UserSettings = {
submitBanner () {
if (!this.bannerPreview) { return }
- let banner = this.bannerPreview
- // eslint-disable-next-line no-undef
- let imginfo = new Image()
- /* eslint-disable camelcase */
- let offset_top, offset_left, width, height
- imginfo.src = banner
- width = imginfo.width
- height = imginfo.height
- offset_top = 0
- offset_left = 0
this.bannerUploading = true
- this.$store.state.api.backendInteractor.updateBanner({params: {banner, offset_top, offset_left, width, height}}).then((data) => {
- if (!data.error) {
- let clone = JSON.parse(JSON.stringify(this.$store.state.users.currentUser))
- clone.cover_photo = data.url
- this.$store.commit('addNewUsers', [clone])
- this.$store.commit('setCurrentUser', clone)
+ this.$store.state.api.backendInteractor.updateBanner({banner: this.banner})
+ .then((user) => {
+ this.$store.commit('addNewUsers', [user])
+ this.$store.commit('setCurrentUser', user)
this.bannerPreview = null
- } else {
- this.bannerUploadError = this.$t('upload.error.base') + data.error
- }
- this.bannerUploading = false
- })
- /* eslint-enable camelcase */
+ })
+ .catch((err) => {
+ this.bannerUploadError = this.$t('upload.error.base') + ' ' + err.message
+ })
+ .then(() => { this.bannerUploading = false })
},
submitBg () {
if (!this.backgroundPreview) { return }
@@ -236,62 +214,41 @@ const UserSettings = {
this.backgroundUploading = false
})
},
- importFollows () {
- this.followListUploading = true
- const followList = this.followList
- this.$store.state.api.backendInteractor.followImport({params: followList})
+ importFollows (file) {
+ return this.$store.state.api.backendInteractor.importFollows(file)
.then((status) => {
- if (status) {
- this.followsImported = true
- } else {
- this.followImportError = true
+ if (!status) {
+ throw new Error('failed')
+ }
+ })
+ },
+ importBlocks (file) {
+ return this.$store.state.api.backendInteractor.importBlocks(file)
+ .then((status) => {
+ if (!status) {
+ throw new Error('failed')
}
- this.followListUploading = false
})
},
- /* This function takes an Array of Users
- * and outputs a file with all the addresses for the user to download
- */
- exportPeople (users, filename) {
- // Get all the friends addresses
- var UserAddresses = users.map(function (user) {
+ generateExportableUsersContent (users) {
+ // Get addresses
+ return users.map((user) => {
// check is it's a local user
if (user && user.is_local) {
// append the instance address
// eslint-disable-next-line no-undef
- user.screen_name += '@' + location.hostname
+ return user.screen_name + '@' + location.hostname
}
return user.screen_name
}).join('\n')
- // Make the user download the file
- var fileToDownload = document.createElement('a')
- fileToDownload.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(UserAddresses))
- fileToDownload.setAttribute('download', filename)
- fileToDownload.style.display = 'none'
- document.body.appendChild(fileToDownload)
- fileToDownload.click()
- document.body.removeChild(fileToDownload)
},
- exportFollows () {
- this.enableFollowsExport = false
- this.$store.state.api.backendInteractor
- .exportFriends({
- id: this.$store.state.users.currentUser.id
- })
- .then((friendList) => {
- this.exportPeople(friendList, 'friends.csv')
- setTimeout(() => { this.enableFollowsExport = true }, 2000)
- })
- },
- followListChange () {
- // eslint-disable-next-line no-undef
- let formData = new FormData()
- formData.append('list', this.$refs.followlist.files[0])
- this.followList = formData
+ getFollowsContent () {
+ return this.$store.state.api.backendInteractor.exportFriends({ id: this.$store.state.users.currentUser.id })
+ .then(this.generateExportableUsersContent)
},
- dismissImported () {
- this.followsImported = false
- this.followImportError = false
+ getBlocksContent () {
+ return this.$store.state.api.backendInteractor.fetchBlocks()
+ .then(this.generateExportableUsersContent)
},
confirmDelete () {
this.deletingAccount = true
diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue
index d68e68fa..8a94f0b8 100644
--- a/src/components/user_settings/user_settings.vue
+++ b/src/components/user_settings/user_settings.vue
@@ -171,26 +171,20 @@
<div class="setting-item">
<h2>{{$t('settings.follow_import')}}</h2>
<p>{{$t('settings.import_followers_from_a_csv_file')}}</p>
- <form>
- <input type="file" ref="followlist" v-on:change="followListChange" />
- </form>
- <i class=" icon-spin4 animate-spin uploading" v-if="followListUploading"></i>
- <button class="btn btn-default" v-else @click="importFollows">{{$t('general.submit')}}</button>
- <div v-if="followsImported">
- <i class="icon-cross" @click="dismissImported"></i>
- <p>{{$t('settings.follows_imported')}}</p>
- </div>
- <div v-else-if="followImportError">
- <i class="icon-cross" @click="dismissImported"></i>
- <p>{{$t('settings.follow_import_error')}}</p>
- </div>
+ <Importer :submitHandler="importFollows" :successMessage="$t('settings.follows_imported')" :errorMessage="$t('settings.follow_import_error')" />
</div>
- <div class="setting-item" v-if="enableFollowsExport">
+ <div class="setting-item">
<h2>{{$t('settings.follow_export')}}</h2>
- <button class="btn btn-default" @click="exportFollows">{{$t('settings.follow_export_button')}}</button>
+ <Exporter :getContent="getFollowsContent" filename="friends.csv" :exportButtonLabel="$t('settings.follow_export_button')" />
</div>
- <div class="setting-item" v-else>
- <h2>{{$t('settings.follow_export_processing')}}</h2>
+ <div class="setting-item">
+ <h2>{{$t('settings.block_import')}}</h2>
+ <p>{{$t('settings.import_blocks_from_a_csv_file')}}</p>
+ <Importer :submitHandler="importBlocks" :successMessage="$t('settings.blocks_imported')" :errorMessage="$t('settings.block_import_error')" />
+ </div>
+ <div class="setting-item">
+ <h2>{{$t('settings.block_export')}}</h2>
+ <Exporter :getContent="getBlocksContent" filename="blocks.csv" :exportButtonLabel="$t('settings.block_export_button')" />
</div>
</div>