Merge branch 'develop' into 'brendenbice1222/pleroma-fe-issues/pleroma-fe-202-show-boosted-users'

# Conflicts:
#   src/services/api/api.service.js
This commit is contained in:
Shpuld Shpludson 2019-04-30 15:11:30 +00:00
commit b1bd5bd08e
22 changed files with 373 additions and 281 deletions

View file

@ -44,15 +44,16 @@
width: 16px; width: 16px;
vertical-align: middle; vertical-align: middle;
} }
}
&-value { &-user-name-value,
&-screen-name {
display: inline-block; display: inline-block;
max-width: 100%; max-width: 100%;
overflow: hidden; overflow: hidden;
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
}
&-expanded-content { &-expanded-content {
flex: 1; flex: 1;

View 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

View 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>

View file

@ -10,8 +10,7 @@ const FollowCard = {
data () { data () {
return { return {
inProgress: false, inProgress: false,
requestSent: false, requestSent: false
updated: false
} }
}, },
components: { components: {
@ -19,10 +18,8 @@ const FollowCard = {
RemoteFollow RemoteFollow
}, },
computed: { computed: {
isMe () { return this.$store.state.users.currentUser.id === this.user.id }, isMe () {
following () { return this.updated ? this.updated.following : this.user.following }, return this.$store.state.users.currentUser.id === this.user.id
showFollow () {
return !this.following || this.updated && !this.updated.following
}, },
loggedIn () { loggedIn () {
return this.$store.state.users.currentUser return this.$store.state.users.currentUser
@ -31,17 +28,15 @@ const FollowCard = {
methods: { methods: {
followUser () { followUser () {
this.inProgress = true this.inProgress = true
requestFollow(this.user, this.$store).then(({ sent, updated }) => { requestFollow(this.user, this.$store).then(({ sent }) => {
this.inProgress = false this.inProgress = false
this.requestSent = sent this.requestSent = sent
this.updated = updated
}) })
}, },
unfollowUser () { unfollowUser () {
this.inProgress = true this.inProgress = true
requestUnfollow(this.user, this.$store).then(({ updated }) => { requestUnfollow(this.user, this.$store).then(() => {
this.inProgress = false this.inProgress = false
this.updated = updated
}) })
} }
} }

View file

@ -4,11 +4,14 @@
<span class="faint" v-if="!noFollowsYou && user.follows_you"> <span class="faint" v-if="!noFollowsYou && user.follows_you">
{{ isMe ? $t('user_card.its_you') : $t('user_card.follows_you') }} {{ isMe ? $t('user_card.its_you') : $t('user_card.follows_you') }}
</span> </span>
<div class="follow-card-follow-button" v-if="showFollow && !loggedIn"> <template v-if="!loggedIn">
<div class="follow-card-follow-button" v-if="!user.following">
<RemoteFollow :user="user" /> <RemoteFollow :user="user" />
</div> </div>
</template>
<template v-else>
<button <button
v-if="showFollow && loggedIn" v-if="!user.following"
class="btn btn-default follow-card-follow-button" class="btn btn-default follow-card-follow-button"
@click="followUser" @click="followUser"
:disabled="inProgress" :disabled="inProgress"
@ -24,7 +27,7 @@
{{ $t('user_card.follow') }} {{ $t('user_card.follow') }}
</template> </template>
</button> </button>
<button v-if="following" class="btn btn-default follow-card-follow-button pressed" @click="unfollowUser" :disabled="inProgress"> <button v-else class="btn btn-default follow-card-follow-button pressed" @click="unfollowUser" :disabled="inProgress">
<template v-if="inProgress"> <template v-if="inProgress">
{{ $t('user_card.follow_progress') }} {{ $t('user_card.follow_progress') }}
</template> </template>
@ -32,6 +35,7 @@
{{ $t('user_card.follow_unfollow') }} {{ $t('user_card.follow_unfollow') }}
</template> </template>
</button> </button>
</template>
</div> </div>
</basic-user-card> </basic-user-card>
</template> </template>

View file

@ -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

View file

@ -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">

View 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

View 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>

View file

@ -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 {

View file

@ -1,5 +1,6 @@
import FollowCard from '../follow_card/follow_card.vue' import FollowCard from '../follow_card/follow_card.vue'
import userSearchApi from '../../services/new_api/user_search.js' import map from 'lodash/map'
const userSearch = { const userSearch = {
components: { components: {
FollowCard FollowCard
@ -10,10 +11,15 @@ const userSearch = {
data () { data () {
return { return {
username: '', username: '',
users: [], userIds: [],
loading: false loading: false
} }
}, },
computed: {
users () {
return this.userIds.map(userId => this.$store.getters.findUser(userId))
}
},
mounted () { mounted () {
this.search(this.query) this.search(this.query)
}, },
@ -33,10 +39,10 @@ const userSearch = {
return return
} }
this.loading = true this.loading = true
userSearchApi.search({query, store: this.$store}) this.$store.dispatch('searchUsers', query)
.then((res) => { .then((res) => {
this.loading = false this.loading = false
this.users = res this.userIds = map(res, 'id')
}) })
} }
} }

View file

@ -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
}
this.bannerUploading = false
}) })
/* eslint-enable camelcase */ .catch((err) => {
this.bannerUploadError = this.$t('upload.error.base') + ' ' + err.message
})
.then(() => { this.bannerUploading = false })
}, },
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

View file

@ -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>
<div v-else-if="followImportError"> <div class="setting-item">
<i class="icon-cross" @click="dismissImported"></i>
<p>{{$t('settings.follow_import_error')}}</p>
</div>
</div>
<div class="setting-item" v-if="enableFollowsExport">
<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>

View file

@ -73,7 +73,8 @@
"content_type": { "content_type": {
"text/plain": "Prostý text", "text/plain": "Prostý text",
"text/html": "HTML", "text/html": "HTML",
"text/markdown": "Markdown" "text/markdown": "Markdown",
"text/bbcode": "BBCode"
}, },
"content_warning": "Předmět (volitelný)", "content_warning": "Předmět (volitelný)",
"default": "Právě jsem přistál v L.A.", "default": "Právě jsem přistál v L.A.",

View file

@ -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",
@ -77,7 +86,8 @@
"content_type": { "content_type": {
"text/plain": "Plain text", "text/plain": "Plain text",
"text/html": "HTML", "text/html": "HTML",
"text/markdown": "Markdown" "text/markdown": "Markdown",
"text/bbcode": "BBCode"
}, },
"content_warning": "Subject (optional)", "content_warning": "Subject (optional)",
"default": "Just landed in L.A.", "default": "Just landed in L.A.",
@ -125,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)",
@ -152,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.",
@ -168,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",

View file

@ -77,7 +77,8 @@
"content_type": { "content_type": {
"text/plain": "Tèxte brut", "text/plain": "Tèxte brut",
"text/html": "HTML", "text/html": "HTML",
"text/markdown": "Markdown" "text/markdown": "Markdown",
"text/bbcode": "BBCode"
}, },
"content_warning": "Avís de contengut (opcional)", "content_warning": "Avís de contengut (opcional)",
"default": "Escrivètz aquí vòstre estatut.", "default": "Escrivètz aquí vòstre estatut.",

View file

@ -74,7 +74,8 @@
"content_type": { "content_type": {
"text/plain": "Czysty tekst", "text/plain": "Czysty tekst",
"text/html": "HTML", "text/html": "HTML",
"text/markdown": "Markdown" "text/markdown": "Markdown",
"text/bbcode": "BBCode"
}, },
"content_warning": "Temat (nieobowiązkowy)", "content_warning": "Temat (nieobowiązkowy)",
"default": "Właśnie wróciłem z kościoła", "default": "Właśnie wróciłem z kościoła",

View file

@ -1,4 +1,5 @@
import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js' import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
import userSearchApi from '../services/new_api/user_search.js'
import { compact, map, each, merge, last, concat, uniq } from 'lodash' import { compact, map, each, merge, last, concat, uniq } from 'lodash'
import { set } from 'vue' import { set } from 'vue'
import { registerPushNotifications, unregisterPushNotifications } from '../services/push/push.js' import { registerPushNotifications, unregisterPushNotifications } from '../services/push/push.js'
@ -341,6 +342,14 @@ const users = {
store.commit('setUserForNotification', notification) store.commit('setUserForNotification', notification)
}) })
}, },
searchUsers (store, query) {
// TODO: Move userSearch api into api.service
return userSearchApi.search({query, store: { state: store.rootState }})
.then((users) => {
store.commit('addNewUsers', users)
return users
})
},
async signUp (store, userInfo) { async signUp (store, userInfo) {
store.commit('signUpPending') store.commit('signUpPending')

View file

@ -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'
@ -51,6 +49,7 @@ 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_STATUS_FAVORITEDBY_URL = id => `/api/v1/statuses/${id}/favourited_by` const MASTODON_STATUS_FAVORITEDBY_URL = id => `/api/v1/statuses/${id}/favourited_by`
const MASTODON_STATUS_REBLOGGEDBY_URL = id => `/api/v1/statuses/${id}/reblogged_by` const MASTODON_STATUS_REBLOGGEDBY_URL = id => `/api/v1/statuses/${id}/reblogged_by`
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'
@ -80,28 +79,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}) => {
@ -122,52 +109,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:
@ -636,9 +600,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)
}) })
@ -786,7 +763,8 @@ const apiService = {
updateProfile, updateProfile,
updateBanner, updateBanner,
externalProfile, externalProfile,
followImport, importBlocks,
importFollows,
deleteAccount, deleteAccount,
changePassword, changePassword,
fetchFollowRequests, fetchFollowRequests,

View file

@ -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})
@ -150,7 +151,8 @@ const backendInteractorService = (credentials) => {
updateBanner, updateBanner,
updateProfile, updateProfile,
externalProfile, externalProfile,
followImport, importBlocks,
importFollows,
deleteAccount, deleteAccount,
changePassword, changePassword,
fetchFollowRequests, fetchFollowRequests,

View file

@ -195,6 +195,7 @@ export const parseStatus = (data) => {
output.summary = pleroma.spoiler_text ? data.pleroma.spoiler_text['text/plain'] : data.spoiler_text output.summary = pleroma.spoiler_text ? data.pleroma.spoiler_text['text/plain'] : data.spoiler_text
output.statusnet_conversation_id = data.pleroma.conversation_id output.statusnet_conversation_id = data.pleroma.conversation_id
output.is_local = pleroma.local output.is_local = pleroma.local
output.in_reply_to_screen_name = data.pleroma.in_reply_to_account_acct
} else { } else {
output.text = data.content output.text = data.content
output.summary = data.spoiler_text output.summary = data.spoiler_text
@ -204,8 +205,6 @@ export const parseStatus = (data) => {
output.in_reply_to_user_id = data.in_reply_to_account_id output.in_reply_to_user_id = data.in_reply_to_account_id
output.replies_count = data.replies_count output.replies_count = data.replies_count
// Missing!! fix in UI?
// output.in_reply_to_screen_name = ???
if (output.type === 'retweet') { if (output.type === 'retweet') {
output.retweeted_status = parseStatus(data.reblog) output.retweeted_status = parseStatus(data.reblog)
} }

View file

@ -23,18 +23,12 @@ export const requestFollow = (user, store) => new Promise((resolve, reject) => {
// For locked users we just mark it that we sent the follow request // For locked users we just mark it that we sent the follow request
if (updated.locked) { if (updated.locked) {
resolve({ resolve({ sent: true })
sent: true,
updated
})
} }
if (updated.following) { if (updated.following) {
// If we get result immediately, just stop. // If we get result immediately, just stop.
resolve({ resolve({ sent: false })
sent: false,
updated
})
} }
// But usually we don't get result immediately, so we ask server // But usually we don't get result immediately, so we ask server
@ -48,16 +42,10 @@ export const requestFollow = (user, store) => new Promise((resolve, reject) => {
.then((following) => { .then((following) => {
if (following) { if (following) {
// We confirmed and everything's good. // We confirmed and everything's good.
resolve({ resolve({ sent: false })
sent: false,
updated
})
} else { } else {
// If after all the tries, just treat it as if user is locked // If after all the tries, just treat it as if user is locked
resolve({ resolve({ sent: false })
sent: false,
updated
})
} }
}) })
}) })