forked from AkkomaGang/akkoma-fe
Merge branch '220-import-export-blocks-mutes' into 'develop'
Allow import/export of blocks See merge request pleroma/pleroma-fe!717
This commit is contained in:
commit
d94fdd0617
9 changed files with 224 additions and 72 deletions
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>
|
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>
|
|
@ -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 () {
|
||||
|
@ -236,62 +235,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')
|
||||
}
|
||||
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) {
|
||||
importBlocks (file) {
|
||||
return this.$store.state.api.backendInteractor.importBlocks(file)
|
||||
.then((status) => {
|
||||
if (!status) {
|
||||
throw new Error('failed')
|
||||
}
|
||||
})
|
||||
},
|
||||
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)
|
||||
})
|
||||
getFollowsContent () {
|
||||
return this.$store.state.api.backendInteractor.exportFriends({ id: this.$store.state.users.currentUser.id })
|
||||
.then(this.generateExportableUsersContent)
|
||||
},
|
||||
followListChange () {
|
||||
// eslint-disable-next-line no-undef
|
||||
let formData = new FormData()
|
||||
formData.append('list', this.$refs.followlist.files[0])
|
||||
this.followList = formData
|
||||
},
|
||||
dismissImported () {
|
||||
this.followsImported = false
|
||||
this.followImportError = false
|
||||
getBlocksContent () {
|
||||
return this.$store.state.api.backendInteractor.fetchBlocks()
|
||||
.then(this.generateExportableUsersContent)
|
||||
},
|
||||
confirmDelete () {
|
||||
this.deletingAccount = true
|
||||
|
|
|
@ -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>
|
||||
<Importer :submitHandler="importFollows" :successMessage="$t('settings.follows_imported')" :errorMessage="$t('settings.follow_import_error')" />
|
||||
</div>
|
||||
<div v-else-if="followImportError">
|
||||
<i class="icon-cross" @click="dismissImported"></i>
|
||||
<p>{{$t('settings.follow_import_error')}}</p>
|
||||
</div>
|
||||
</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>
|
||||
|
||||
|
|
|
@ -2,6 +2,10 @@
|
|||
"chat": {
|
||||
"title": "Chat"
|
||||
},
|
||||
"exporter": {
|
||||
"export": "Export",
|
||||
"processing": "Processing, you'll soon be asked to download your file"
|
||||
},
|
||||
"features_panel": {
|
||||
"chat": "Chat",
|
||||
"gopher": "Gopher",
|
||||
|
@ -31,6 +35,11 @@
|
|||
"save_without_cropping": "Save without cropping",
|
||||
"cancel": "Cancel"
|
||||
},
|
||||
"importer": {
|
||||
"submit": "Submit",
|
||||
"success": "Imported successfully.",
|
||||
"error": "An error occured while importing this file."
|
||||
},
|
||||
"login": {
|
||||
"login": "Log in",
|
||||
"description": "Log in with OAuth",
|
||||
|
@ -126,6 +135,11 @@
|
|||
"avatarRadius": "Avatars",
|
||||
"background": "Background",
|
||||
"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",
|
||||
"btnRadius": "Buttons",
|
||||
"cBlue": "Blue (Reply, follow)",
|
||||
|
@ -153,7 +167,6 @@
|
|||
"filtering_explanation": "All statuses containing these words will be muted, one per line",
|
||||
"follow_export": "Follow export",
|
||||
"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_error": "Error importing followers",
|
||||
"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_user_stats": "Hide user statistics (e.g. the number of followers)",
|
||||
"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_theme": "Load preset",
|
||||
"inputRadius": "Input fields",
|
||||
|
|
|
@ -9,6 +9,7 @@ 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 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 DELETE_ACCOUNT_URL = '/api/pleroma/delete_account'
|
||||
const CHANGE_PASSWORD_URL = '/api/pleroma/change_password'
|
||||
|
@ -634,9 +635,22 @@ const uploadMedia = ({formData, credentials}) => {
|
|||
.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, {
|
||||
body: params,
|
||||
body: formData,
|
||||
method: 'POST',
|
||||
headers: authHeaders(credentials)
|
||||
})
|
||||
|
@ -776,7 +790,8 @@ const apiService = {
|
|||
updateProfile,
|
||||
updateBanner,
|
||||
externalProfile,
|
||||
followImport,
|
||||
importBlocks,
|
||||
importFollows,
|
||||
deleteAccount,
|
||||
changePassword,
|
||||
fetchFollowRequests,
|
||||
|
|
|
@ -107,7 +107,8 @@ const backendInteractorService = (credentials) => {
|
|||
const updateProfile = ({params}) => apiService.updateProfile({credentials, params})
|
||||
|
||||
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 changePassword = ({password, newPassword, newPasswordConfirmation}) => apiService.changePassword({credentials, password, newPassword, newPasswordConfirmation})
|
||||
|
@ -147,7 +148,8 @@ const backendInteractorService = (credentials) => {
|
|||
updateBanner,
|
||||
updateProfile,
|
||||
externalProfile,
|
||||
followImport,
|
||||
importBlocks,
|
||||
importFollows,
|
||||
deleteAccount,
|
||||
changePassword,
|
||||
fetchFollowRequests,
|
||||
|
|
Loading…
Reference in a new issue