Merge remote-tracking branch 'upstream/develop' into shigusegubu
* upstream/develop: remove & add a comment show ellipsis for long user name and screen name use default_scope parameter use json content type clean up refactoring add “export blocks” feature fix wrong function binding make reusable Exporter component add “block import” feature change api function name make Importer component reusable add uploading icon css move formData generating logic to api.service split out follow’s importer as a separate component Update avatar uploading Switch to mastoapi for updating user profile Switch to mastoapi for updating banner Switch to mastoapi for updating avatar
This commit is contained in:
commit
5d274ea908
13 changed files with 302 additions and 215 deletions
|
@ -44,14 +44,15 @@
|
||||||
width: 16px;
|
width: 16px;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&-value {
|
&-user-name-value,
|
||||||
display: inline-block;
|
&-screen-name {
|
||||||
max-width: 100%;
|
display: inline-block;
|
||||||
overflow: hidden;
|
max-width: 100%;
|
||||||
white-space: nowrap;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
white-space: nowrap;
|
||||||
}
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-expanded-content {
|
&-expanded-content {
|
||||||
|
|
48
src/components/exporter/exporter.js
Normal file
48
src/components/exporter/exporter.js
Normal file
|
@ -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
|
20
src/components/exporter/exporter.vue
Normal file
20
src/components/exporter/exporter.vue
Normal file
|
@ -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>
|
|
@ -70,22 +70,10 @@ const ImageCropper = {
|
||||||
this.dataUrl = undefined
|
this.dataUrl = undefined
|
||||||
this.$emit('close')
|
this.$emit('close')
|
||||||
},
|
},
|
||||||
submit () {
|
submit (cropping = true) {
|
||||||
this.submitting = true
|
this.submitting = true
|
||||||
this.avatarUploadError = null
|
this.avatarUploadError = null
|
||||||
this.submitHandler(this.cropper, this.file)
|
this.submitHandler(cropping && 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)
|
|
||||||
.then(() => this.destroy())
|
.then(() => this.destroy())
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.submitError = err
|
this.submitError = err
|
||||||
|
|
|
@ -5,9 +5,9 @@
|
||||||
<img ref="img" :src="dataUrl" alt="" @load.stop="createCropper" />
|
<img ref="img" :src="dataUrl" alt="" @load.stop="createCropper" />
|
||||||
</div>
|
</div>
|
||||||
<div class="image-cropper-buttons-wrapper">
|
<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="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>
|
<i class="icon-spin4 animate-spin" v-if="submitting"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="alert error" v-if="submitError">
|
<div class="alert error" v-if="submitError">
|
||||||
|
|
53
src/components/importer/importer.js
Normal file
53
src/components/importer/importer.js
Normal file
|
@ -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
|
28
src/components/importer/importer.vue
Normal file
28
src/components/importer/importer.vue
Normal file
|
@ -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>
|
|
@ -31,6 +31,10 @@
|
||||||
&-item-inner {
|
&-item-inner {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-item-selected-inner {
|
&-item-selected-inner {
|
||||||
|
|
|
@ -13,6 +13,8 @@ import SelectableList from '../selectable_list/selectable_list.vue'
|
||||||
import ProgressButton from '../progress_button/progress_button.vue'
|
import ProgressButton from '../progress_button/progress_button.vue'
|
||||||
import EmojiInput from '../emoji-input/emoji-input.vue'
|
import EmojiInput from '../emoji-input/emoji-input.vue'
|
||||||
import Autosuggest from '../autosuggest/autosuggest.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 withSubscription from '../../hocs/with_subscription/with_subscription'
|
||||||
import userSearchApi from '../../services/new_api/user_search.js'
|
import userSearchApi from '../../services/new_api/user_search.js'
|
||||||
|
|
||||||
|
@ -40,14 +42,9 @@ const UserSettings = {
|
||||||
hideFollowers: this.$store.state.users.currentUser.hide_followers,
|
hideFollowers: this.$store.state.users.currentUser.hide_followers,
|
||||||
showRole: this.$store.state.users.currentUser.show_role,
|
showRole: this.$store.state.users.currentUser.show_role,
|
||||||
role: this.$store.state.users.currentUser.role,
|
role: this.$store.state.users.currentUser.role,
|
||||||
followList: null,
|
|
||||||
followImportError: false,
|
|
||||||
followsImported: false,
|
|
||||||
enableFollowsExport: true,
|
|
||||||
pickAvatarBtnVisible: true,
|
pickAvatarBtnVisible: true,
|
||||||
bannerUploading: false,
|
bannerUploading: false,
|
||||||
backgroundUploading: false,
|
backgroundUploading: false,
|
||||||
followListUploading: false,
|
|
||||||
bannerPreview: null,
|
bannerPreview: null,
|
||||||
backgroundPreview: null,
|
backgroundPreview: null,
|
||||||
bannerUploadError: null,
|
bannerUploadError: null,
|
||||||
|
@ -75,7 +72,9 @@ const UserSettings = {
|
||||||
Autosuggest,
|
Autosuggest,
|
||||||
BlockCard,
|
BlockCard,
|
||||||
MuteCard,
|
MuteCard,
|
||||||
ProgressButton
|
ProgressButton,
|
||||||
|
Importer,
|
||||||
|
Exporter
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
user () {
|
user () {
|
||||||
|
@ -110,37 +109,23 @@ const UserSettings = {
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
updateProfile () {
|
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
|
this.$store.state.api.backendInteractor
|
||||||
.updateProfile({
|
.updateProfile({
|
||||||
params: {
|
params: {
|
||||||
name,
|
note: this.newBio,
|
||||||
description,
|
locked: this.newLocked,
|
||||||
locked,
|
|
||||||
// Backend notation.
|
// Backend notation.
|
||||||
/* eslint-disable camelcase */
|
/* eslint-disable camelcase */
|
||||||
default_scope,
|
display_name: this.newName,
|
||||||
no_rich_text,
|
default_scope: this.newDefaultScope,
|
||||||
hide_follows,
|
no_rich_text: this.newNoRichText,
|
||||||
hide_followers,
|
hide_follows: this.hideFollows,
|
||||||
show_role
|
hide_followers: this.hideFollowers,
|
||||||
|
show_role: this.showRole
|
||||||
/* eslint-enable camelcase */
|
/* eslint-enable camelcase */
|
||||||
}}).then((user) => {
|
}}).then((user) => {
|
||||||
if (!user.error) {
|
this.$store.commit('addNewUsers', [user])
|
||||||
this.$store.commit('addNewUsers', [user])
|
this.$store.commit('setCurrentUser', user)
|
||||||
this.$store.commit('setCurrentUser', user)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
changeVis (visibility) {
|
changeVis (visibility) {
|
||||||
|
@ -160,23 +145,29 @@ const UserSettings = {
|
||||||
reader.onload = ({target}) => {
|
reader.onload = ({target}) => {
|
||||||
const img = target.result
|
const img = target.result
|
||||||
this[slot + 'Preview'] = img
|
this[slot + 'Preview'] = img
|
||||||
|
this[slot] = file
|
||||||
}
|
}
|
||||||
reader.readAsDataURL(file)
|
reader.readAsDataURL(file)
|
||||||
},
|
},
|
||||||
submitAvatar (cropper, file) {
|
submitAvatar (cropper, file) {
|
||||||
let img
|
const that = this
|
||||||
if (cropper) {
|
return new Promise((resolve, reject) => {
|
||||||
img = cropper.getCroppedCanvas().toDataURL(file.type)
|
function updateAvatar (avatar) {
|
||||||
} else {
|
that.$store.state.api.backendInteractor.updateAvatar({ avatar })
|
||||||
img = file
|
.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 (cropper) {
|
||||||
if (!user.error) {
|
cropper.getCroppedCanvas().toBlob(updateAvatar, file.type)
|
||||||
this.$store.commit('addNewUsers', [user])
|
|
||||||
this.$store.commit('setCurrentUser', user)
|
|
||||||
} else {
|
} else {
|
||||||
throw new Error(this.$t('upload.error.base') + user.error)
|
updateAvatar(file)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
@ -186,30 +177,17 @@ const UserSettings = {
|
||||||
submitBanner () {
|
submitBanner () {
|
||||||
if (!this.bannerPreview) { return }
|
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.bannerUploading = true
|
||||||
this.$store.state.api.backendInteractor.updateBanner({params: {banner, offset_top, offset_left, width, height}}).then((data) => {
|
this.$store.state.api.backendInteractor.updateBanner({banner: this.banner})
|
||||||
if (!data.error) {
|
.then((user) => {
|
||||||
let clone = JSON.parse(JSON.stringify(this.$store.state.users.currentUser))
|
this.$store.commit('addNewUsers', [user])
|
||||||
clone.cover_photo = data.url
|
this.$store.commit('setCurrentUser', user)
|
||||||
this.$store.commit('addNewUsers', [clone])
|
|
||||||
this.$store.commit('setCurrentUser', clone)
|
|
||||||
this.bannerPreview = null
|
this.bannerPreview = null
|
||||||
} else {
|
})
|
||||||
this.bannerUploadError = this.$t('upload.error.base') + data.error
|
.catch((err) => {
|
||||||
}
|
this.bannerUploadError = this.$t('upload.error.base') + ' ' + err.message
|
||||||
this.bannerUploading = false
|
})
|
||||||
})
|
.then(() => { this.bannerUploading = false })
|
||||||
/* eslint-enable camelcase */
|
|
||||||
},
|
},
|
||||||
submitBg () {
|
submitBg () {
|
||||||
if (!this.backgroundPreview) { return }
|
if (!this.backgroundPreview) { return }
|
||||||
|
@ -236,62 +214,41 @@ const UserSettings = {
|
||||||
this.backgroundUploading = false
|
this.backgroundUploading = false
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
importFollows () {
|
importFollows (file) {
|
||||||
this.followListUploading = true
|
return this.$store.state.api.backendInteractor.importFollows(file)
|
||||||
const followList = this.followList
|
|
||||||
this.$store.state.api.backendInteractor.followImport({params: followList})
|
|
||||||
.then((status) => {
|
.then((status) => {
|
||||||
if (status) {
|
if (!status) {
|
||||||
this.followsImported = true
|
throw new Error('failed')
|
||||||
} else {
|
|
||||||
this.followImportError = true
|
|
||||||
}
|
}
|
||||||
this.followListUploading = false
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
/* This function takes an Array of Users
|
importBlocks (file) {
|
||||||
* and outputs a file with all the addresses for the user to download
|
return this.$store.state.api.backendInteractor.importBlocks(file)
|
||||||
*/
|
.then((status) => {
|
||||||
exportPeople (users, filename) {
|
if (!status) {
|
||||||
// Get all the friends addresses
|
throw new Error('failed')
|
||||||
var UserAddresses = users.map(function (user) {
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
generateExportableUsersContent (users) {
|
||||||
|
// Get addresses
|
||||||
|
return users.map((user) => {
|
||||||
// check is it's a local user
|
// check is it's a local user
|
||||||
if (user && user.is_local) {
|
if (user && user.is_local) {
|
||||||
// append the instance address
|
// append the instance address
|
||||||
// eslint-disable-next-line no-undef
|
// eslint-disable-next-line no-undef
|
||||||
user.screen_name += '@' + location.hostname
|
return user.screen_name + '@' + location.hostname
|
||||||
}
|
}
|
||||||
return user.screen_name
|
return user.screen_name
|
||||||
}).join('\n')
|
}).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 () {
|
getFollowsContent () {
|
||||||
this.enableFollowsExport = false
|
return this.$store.state.api.backendInteractor.exportFriends({ id: this.$store.state.users.currentUser.id })
|
||||||
this.$store.state.api.backendInteractor
|
.then(this.generateExportableUsersContent)
|
||||||
.exportFriends({
|
|
||||||
id: this.$store.state.users.currentUser.id
|
|
||||||
})
|
|
||||||
.then((friendList) => {
|
|
||||||
this.exportPeople(friendList, 'friends.csv')
|
|
||||||
setTimeout(() => { this.enableFollowsExport = true }, 2000)
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
followListChange () {
|
getBlocksContent () {
|
||||||
// eslint-disable-next-line no-undef
|
return this.$store.state.api.backendInteractor.fetchBlocks()
|
||||||
let formData = new FormData()
|
.then(this.generateExportableUsersContent)
|
||||||
formData.append('list', this.$refs.followlist.files[0])
|
|
||||||
this.followList = formData
|
|
||||||
},
|
|
||||||
dismissImported () {
|
|
||||||
this.followsImported = false
|
|
||||||
this.followImportError = false
|
|
||||||
},
|
},
|
||||||
confirmDelete () {
|
confirmDelete () {
|
||||||
this.deletingAccount = true
|
this.deletingAccount = true
|
||||||
|
|
|
@ -171,26 +171,20 @@
|
||||||
<div class="setting-item">
|
<div class="setting-item">
|
||||||
<h2>{{$t('settings.follow_import')}}</h2>
|
<h2>{{$t('settings.follow_import')}}</h2>
|
||||||
<p>{{$t('settings.import_followers_from_a_csv_file')}}</p>
|
<p>{{$t('settings.import_followers_from_a_csv_file')}}</p>
|
||||||
<form>
|
<Importer :submitHandler="importFollows" :successMessage="$t('settings.follows_imported')" :errorMessage="$t('settings.follow_import_error')" />
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="setting-item" v-if="enableFollowsExport">
|
<div class="setting-item">
|
||||||
<h2>{{$t('settings.follow_export')}}</h2>
|
<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>
|
||||||
<div class="setting-item" v-else>
|
<div class="setting-item">
|
||||||
<h2>{{$t('settings.follow_export_processing')}}</h2>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,10 @@
|
||||||
"chat": {
|
"chat": {
|
||||||
"title": "Chat"
|
"title": "Chat"
|
||||||
},
|
},
|
||||||
|
"exporter": {
|
||||||
|
"export": "Export",
|
||||||
|
"processing": "Processing, you'll soon be asked to download your file"
|
||||||
|
},
|
||||||
"features_panel": {
|
"features_panel": {
|
||||||
"chat": "Chat",
|
"chat": "Chat",
|
||||||
"gopher": "Gopher",
|
"gopher": "Gopher",
|
||||||
|
@ -31,6 +35,11 @@
|
||||||
"save_without_cropping": "Save without cropping",
|
"save_without_cropping": "Save without cropping",
|
||||||
"cancel": "Cancel"
|
"cancel": "Cancel"
|
||||||
},
|
},
|
||||||
|
"importer": {
|
||||||
|
"submit": "Submit",
|
||||||
|
"success": "Imported successfully.",
|
||||||
|
"error": "An error occured while importing this file."
|
||||||
|
},
|
||||||
"login": {
|
"login": {
|
||||||
"login": "Log in",
|
"login": "Log in",
|
||||||
"description": "Log in with OAuth",
|
"description": "Log in with OAuth",
|
||||||
|
@ -126,6 +135,11 @@
|
||||||
"avatarRadius": "Avatars",
|
"avatarRadius": "Avatars",
|
||||||
"background": "Background",
|
"background": "Background",
|
||||||
"bio": "Bio",
|
"bio": "Bio",
|
||||||
|
"block_export": "Block export",
|
||||||
|
"block_export_button": "Export your blocks to a csv file",
|
||||||
|
"block_import": "Block import",
|
||||||
|
"block_import_error": "Error importing blocks",
|
||||||
|
"blocks_imported": "Blocks imported! Processing them will take a while.",
|
||||||
"blocks_tab": "Blocks",
|
"blocks_tab": "Blocks",
|
||||||
"btnRadius": "Buttons",
|
"btnRadius": "Buttons",
|
||||||
"cBlue": "Blue (Reply, follow)",
|
"cBlue": "Blue (Reply, follow)",
|
||||||
|
@ -153,7 +167,6 @@
|
||||||
"filtering_explanation": "All statuses containing these words will be muted, one per line",
|
"filtering_explanation": "All statuses containing these words will be muted, one per line",
|
||||||
"follow_export": "Follow export",
|
"follow_export": "Follow export",
|
||||||
"follow_export_button": "Export your follows to a csv file",
|
"follow_export_button": "Export your follows to a csv file",
|
||||||
"follow_export_processing": "Processing, you'll soon be asked to download your file",
|
|
||||||
"follow_import": "Follow import",
|
"follow_import": "Follow import",
|
||||||
"follow_import_error": "Error importing followers",
|
"follow_import_error": "Error importing followers",
|
||||||
"follows_imported": "Follows imported! Processing them will take a while.",
|
"follows_imported": "Follows imported! Processing them will take a while.",
|
||||||
|
@ -169,6 +182,7 @@
|
||||||
"hide_post_stats": "Hide post statistics (e.g. the number of favorites)",
|
"hide_post_stats": "Hide post statistics (e.g. the number of favorites)",
|
||||||
"hide_user_stats": "Hide user statistics (e.g. the number of followers)",
|
"hide_user_stats": "Hide user statistics (e.g. the number of followers)",
|
||||||
"hide_filtered_statuses": "Hide filtered statuses",
|
"hide_filtered_statuses": "Hide filtered statuses",
|
||||||
|
"import_blocks_from_a_csv_file": "Import blocks from a csv file",
|
||||||
"import_followers_from_a_csv_file": "Import follows from a csv file",
|
"import_followers_from_a_csv_file": "Import follows from a csv file",
|
||||||
"import_theme": "Load preset",
|
"import_theme": "Load preset",
|
||||||
"inputRadius": "Input fields",
|
"inputRadius": "Input fields",
|
||||||
|
|
|
@ -3,12 +3,10 @@ const LOGIN_URL = '/api/account/verify_credentials.json'
|
||||||
const ALL_FOLLOWING_URL = '/api/qvitter/allfollowing'
|
const ALL_FOLLOWING_URL = '/api/qvitter/allfollowing'
|
||||||
const MENTIONS_URL = '/api/statuses/mentions.json'
|
const MENTIONS_URL = '/api/statuses/mentions.json'
|
||||||
const REGISTRATION_URL = '/api/account/register.json'
|
const REGISTRATION_URL = '/api/account/register.json'
|
||||||
const AVATAR_UPDATE_URL = '/api/qvitter/update_avatar.json'
|
|
||||||
const BG_UPDATE_URL = '/api/qvitter/update_background_image.json'
|
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 EXTERNAL_PROFILE_URL = '/api/externalprofile/show.json'
|
||||||
const QVITTER_USER_NOTIFICATIONS_READ_URL = '/api/qvitter/statuses/notifications/read.json'
|
const QVITTER_USER_NOTIFICATIONS_READ_URL = '/api/qvitter/statuses/notifications/read.json'
|
||||||
|
const BLOCKS_IMPORT_URL = '/api/pleroma/blocks_import'
|
||||||
const FOLLOW_IMPORT_URL = '/api/pleroma/follow_import'
|
const FOLLOW_IMPORT_URL = '/api/pleroma/follow_import'
|
||||||
const DELETE_ACCOUNT_URL = '/api/pleroma/delete_account'
|
const DELETE_ACCOUNT_URL = '/api/pleroma/delete_account'
|
||||||
const CHANGE_PASSWORD_URL = '/api/pleroma/change_password'
|
const CHANGE_PASSWORD_URL = '/api/pleroma/change_password'
|
||||||
|
@ -49,6 +47,7 @@ const MASTODON_MUTE_USER_URL = id => `/api/v1/accounts/${id}/mute`
|
||||||
const MASTODON_UNMUTE_USER_URL = id => `/api/v1/accounts/${id}/unmute`
|
const MASTODON_UNMUTE_USER_URL = id => `/api/v1/accounts/${id}/unmute`
|
||||||
const MASTODON_POST_STATUS_URL = '/api/v1/statuses'
|
const MASTODON_POST_STATUS_URL = '/api/v1/statuses'
|
||||||
const MASTODON_MEDIA_UPLOAD_URL = '/api/v1/media'
|
const MASTODON_MEDIA_UPLOAD_URL = '/api/v1/media'
|
||||||
|
const MASTODON_PROFILE_UPDATE_URL = '/api/v1/accounts/update_credentials'
|
||||||
|
|
||||||
import { each, map, concat, last } from 'lodash'
|
import { each, map, concat, last } from 'lodash'
|
||||||
import { parseStatus, parseUser, parseNotification, parseAttachment } from '../entity_normalizer/entity_normalizer.service.js'
|
import { parseStatus, parseUser, parseNotification, parseAttachment } from '../entity_normalizer/entity_normalizer.service.js'
|
||||||
|
@ -78,28 +77,16 @@ const promisedRequest = (url, options) => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Params
|
const updateAvatar = ({credentials, avatar}) => {
|
||||||
// cropH
|
|
||||||
// cropW
|
|
||||||
// cropX
|
|
||||||
// cropY
|
|
||||||
// img (base 64 encodend data url)
|
|
||||||
const updateAvatar = ({credentials, params}) => {
|
|
||||||
let url = AVATAR_UPDATE_URL
|
|
||||||
|
|
||||||
const form = new FormData()
|
const form = new FormData()
|
||||||
|
form.append('avatar', avatar)
|
||||||
each(params, (value, key) => {
|
return fetch(MASTODON_PROFILE_UPDATE_URL, {
|
||||||
if (value) {
|
|
||||||
form.append(key, value)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return fetch(url, {
|
|
||||||
headers: authHeaders(credentials),
|
headers: authHeaders(credentials),
|
||||||
method: 'POST',
|
method: 'PATCH',
|
||||||
body: form
|
body: form
|
||||||
}).then((data) => data.json())
|
})
|
||||||
|
.then((data) => data.json())
|
||||||
|
.then((data) => parseUser(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateBg = ({credentials, params}) => {
|
const updateBg = ({credentials, params}) => {
|
||||||
|
@ -120,52 +107,29 @@ const updateBg = ({credentials, params}) => {
|
||||||
}).then((data) => data.json())
|
}).then((data) => data.json())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Params
|
const updateBanner = ({credentials, banner}) => {
|
||||||
// height
|
|
||||||
// width
|
|
||||||
// offset_left
|
|
||||||
// offset_top
|
|
||||||
// banner (base 64 encodend data url)
|
|
||||||
const updateBanner = ({credentials, params}) => {
|
|
||||||
let url = BANNER_UPDATE_URL
|
|
||||||
|
|
||||||
const form = new FormData()
|
const form = new FormData()
|
||||||
|
form.append('header', banner)
|
||||||
each(params, (value, key) => {
|
return fetch(MASTODON_PROFILE_UPDATE_URL, {
|
||||||
if (value) {
|
|
||||||
form.append(key, value)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return fetch(url, {
|
|
||||||
headers: authHeaders(credentials),
|
headers: authHeaders(credentials),
|
||||||
method: 'POST',
|
method: 'PATCH',
|
||||||
body: form
|
body: form
|
||||||
}).then((data) => data.json())
|
})
|
||||||
|
.then((data) => data.json())
|
||||||
|
.then((data) => parseUser(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Params
|
|
||||||
// name
|
|
||||||
// url
|
|
||||||
// location
|
|
||||||
// description
|
|
||||||
const updateProfile = ({credentials, params}) => {
|
const updateProfile = ({credentials, params}) => {
|
||||||
// Always include these fields, because they might be empty or false
|
return promisedRequest(MASTODON_PROFILE_UPDATE_URL, {
|
||||||
const fields = ['description', 'locked', 'no_rich_text', 'hide_follows', 'hide_followers', 'show_role']
|
headers: {
|
||||||
let url = PROFILE_UPDATE_URL
|
'Accept': 'application/json',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
const form = new FormData()
|
...authHeaders(credentials)
|
||||||
|
},
|
||||||
each(params, (value, key) => {
|
method: 'PATCH',
|
||||||
if (fields.includes(key) || value) {
|
body: JSON.stringify(params)
|
||||||
form.append(key, value)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
return fetch(url, {
|
.then((data) => parseUser(data))
|
||||||
headers: authHeaders(credentials),
|
|
||||||
method: 'POST',
|
|
||||||
body: form
|
|
||||||
}).then((data) => data.json())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Params needed:
|
// Params needed:
|
||||||
|
@ -634,9 +598,22 @@ const uploadMedia = ({formData, credentials}) => {
|
||||||
.then((data) => parseAttachment(data))
|
.then((data) => parseAttachment(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
const followImport = ({params, credentials}) => {
|
const importBlocks = ({file, credentials}) => {
|
||||||
|
const formData = new FormData()
|
||||||
|
formData.append('list', file)
|
||||||
|
return fetch(BLOCKS_IMPORT_URL, {
|
||||||
|
body: formData,
|
||||||
|
method: 'POST',
|
||||||
|
headers: authHeaders(credentials)
|
||||||
|
})
|
||||||
|
.then((response) => response.ok)
|
||||||
|
}
|
||||||
|
|
||||||
|
const importFollows = ({file, credentials}) => {
|
||||||
|
const formData = new FormData()
|
||||||
|
formData.append('list', file)
|
||||||
return fetch(FOLLOW_IMPORT_URL, {
|
return fetch(FOLLOW_IMPORT_URL, {
|
||||||
body: params,
|
body: formData,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: authHeaders(credentials)
|
headers: authHeaders(credentials)
|
||||||
})
|
})
|
||||||
|
@ -776,7 +753,8 @@ const apiService = {
|
||||||
updateProfile,
|
updateProfile,
|
||||||
updateBanner,
|
updateBanner,
|
||||||
externalProfile,
|
externalProfile,
|
||||||
followImport,
|
importBlocks,
|
||||||
|
importFollows,
|
||||||
deleteAccount,
|
deleteAccount,
|
||||||
changePassword,
|
changePassword,
|
||||||
fetchFollowRequests,
|
fetchFollowRequests,
|
||||||
|
|
|
@ -101,13 +101,14 @@ const backendInteractorService = (credentials) => {
|
||||||
|
|
||||||
const getCaptcha = () => apiService.getCaptcha()
|
const getCaptcha = () => apiService.getCaptcha()
|
||||||
const register = (params) => apiService.register(params)
|
const register = (params) => apiService.register(params)
|
||||||
const updateAvatar = ({params}) => apiService.updateAvatar({credentials, params})
|
const updateAvatar = ({avatar}) => apiService.updateAvatar({credentials, avatar})
|
||||||
const updateBg = ({params}) => apiService.updateBg({credentials, params})
|
const updateBg = ({params}) => apiService.updateBg({credentials, params})
|
||||||
const updateBanner = ({params}) => apiService.updateBanner({credentials, params})
|
const updateBanner = ({banner}) => apiService.updateBanner({credentials, banner})
|
||||||
const updateProfile = ({params}) => apiService.updateProfile({credentials, params})
|
const updateProfile = ({params}) => apiService.updateProfile({credentials, params})
|
||||||
|
|
||||||
const externalProfile = (profileUrl) => apiService.externalProfile({profileUrl, credentials})
|
const externalProfile = (profileUrl) => apiService.externalProfile({profileUrl, credentials})
|
||||||
const followImport = ({params}) => apiService.followImport({params, credentials})
|
const importBlocks = (file) => apiService.importBlocks({file, credentials})
|
||||||
|
const importFollows = (file) => apiService.importFollows({file, credentials})
|
||||||
|
|
||||||
const deleteAccount = ({password}) => apiService.deleteAccount({credentials, password})
|
const deleteAccount = ({password}) => apiService.deleteAccount({credentials, password})
|
||||||
const changePassword = ({password, newPassword, newPasswordConfirmation}) => apiService.changePassword({credentials, password, newPassword, newPasswordConfirmation})
|
const changePassword = ({password, newPassword, newPasswordConfirmation}) => apiService.changePassword({credentials, password, newPassword, newPasswordConfirmation})
|
||||||
|
@ -147,7 +148,8 @@ const backendInteractorService = (credentials) => {
|
||||||
updateBanner,
|
updateBanner,
|
||||||
updateProfile,
|
updateProfile,
|
||||||
externalProfile,
|
externalProfile,
|
||||||
followImport,
|
importBlocks,
|
||||||
|
importFollows,
|
||||||
deleteAccount,
|
deleteAccount,
|
||||||
changePassword,
|
changePassword,
|
||||||
fetchFollowRequests,
|
fetchFollowRequests,
|
||||||
|
|
Loading…
Add table
Reference in a new issue