Merge remote-tracking branch 'upstream/develop' into shigusegubu

* upstream/develop:
  review
  review
  Update emoji-input.js
  Misc fixes: Fix uploads stretching on chrome, fix warnings in console
  apply font smoothing in webkit and firefox
  fix user search
  Apply suggestion to src/services/backend_interactor_service/backend_interactor_service.js
  properly position the caret after replacement
  Apply suggestion to src/services/api/api.service.js
  fix MFA crashing on user-settings page
  fixup! Removed formattingOptionsEnabled in favor of relying on BE-provided list of accepted formatting options
  getting and setting user background via MastoAPI
  Removed formattingOptionsEnabled in favor of relying on BE-provided list of accepted formatting options
  fix small annoyance
  fixed some bugs i found, also cleaned up some stuff + documentation
  self-review
  Linting
This commit is contained in:
Henry Jameson 2019-06-18 22:27:15 +03:00
commit cda2d6cb20
23 changed files with 214 additions and 162 deletions

View file

@ -1,5 +1,8 @@
# v1.0 # v1.0
## Removed features/radically changed behavior ## Removed features/radically changed behavior
### formattingOptionsEnabled
as of !833 `formattingOptionsEnabled` is no longer available and instead FE check for available post formatting options and enables formatting control if there's more than one option.
### minimalScopesMode ### minimalScopesMode
As of !633, `scopeOptions` is no longer available and instead is changed for `minimalScopesMode` (default: `false`) As of !633, `scopeOptions` is no longer available and instead is changed for `minimalScopesMode` (default: `false`)

View file

@ -47,6 +47,8 @@ body {
color: var(--text, $fallback--text); color: var(--text, $fallback--text);
max-width: 100vw; max-width: 100vw;
overflow-x: hidden; overflow-x: hidden;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
} }
a { a {

View file

@ -100,7 +100,6 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => {
copyInstanceOption('redirectRootLogin') copyInstanceOption('redirectRootLogin')
copyInstanceOption('showInstanceSpecificPanel') copyInstanceOption('showInstanceSpecificPanel')
copyInstanceOption('minimalScopesMode') copyInstanceOption('minimalScopesMode')
copyInstanceOption('formattingOptionsEnabled')
copyInstanceOption('hideMutedPosts') copyInstanceOption('hideMutedPosts')
copyInstanceOption('collapseMessageWithSubject') copyInstanceOption('collapseMessageWithSubject')
copyInstanceOption('scopeCopy') copyInstanceOption('scopeCopy')

View file

@ -68,6 +68,7 @@
max-height: 200px; max-height: 200px;
max-width: 100%; max-width: 100%;
display: flex; display: flex;
align-items: center;
video { video {
max-width: 100%; max-width: 100%;
} }

View file

@ -1,6 +1,11 @@
<template> <template>
<div class="avatars"> <div class="avatars">
<router-link :to="userProfileLink(user)" class="avatars-item" v-for="user in slicedUsers"> <router-link
:to="userProfileLink(user)"
class="avatars-item"
v-for="user in slicedUsers"
:key="user.id"
>
<UserAvatar :user="user" class="avatar-small" /> <UserAvatar :user="user" class="avatar-small" />
</router-link> </router-link>
</div> </div>

View file

@ -139,6 +139,7 @@ const conversation = {
return (this.isExpanded) && id === this.status.id return (this.isExpanded) && id === this.status.id
}, },
setHighlight (id) { setHighlight (id) {
if (!id) return
this.highlight = id this.highlight = id
this.$store.dispatch('fetchFavsAndRepeats', id) this.$store.dispatch('fetchFavsAndRepeats', id)
}, },

View file

@ -1,31 +1,65 @@
import Completion from '../../services/completion/completion.js' import Completion from '../../services/completion/completion.js'
import { take } from 'lodash' import { take } from 'lodash'
/**
* EmojiInput - augmented inputs for emoji and autocomplete support in inputs
* without having to give up the comfort of <input/> and <textarea/> elements
*
* Intended usage is:
* <EmojiInput v-model="something">
* <input v-model="something"/>
* </EmojiInput>
*
* Works only with <input> and <textarea>. Intended to use with only one nested
* input. It will find first input or textarea and work with that, multiple
* nested children not tested. You HAVE TO duplicate v-model for both
* <emoji-input> and <input>/<textarea> otherwise it will not work.
*
* Be prepared for CSS troubles though because it still wraps component in a div
* while TRYING to make it look like nothing happened, but it could break stuff.
*/
const EmojiInput = { const EmojiInput = {
props: [ props: {
'placeholder', suggest: {
'suggest', /**
'value', * suggest: function (input: String) => Suggestion[]
'type', *
'classname' * Function that takes input string which takes string (textAtCaret)
], * and returns an array of Suggestions
*
* Suggestion is an object containing following properties:
* displayText: string. Main display text, what actual suggestion
* represents (user's screen name/emoji shortcode)
* replacement: string. Text that should replace the textAtCaret
* detailText: string, optional. Subtitle text, providing additional info
* if present (user's nickname)
* imageUrl: string, optional. Image to display alongside with suggestion,
* currently if no image is provided, replacement will be used (for
* unicode emojis)
*
* TODO: make it asynchronous when adding proper server-provided user
* suggestions
*
* For commonly used suggestors (emoji, users, both) use suggestor.js
*/
required: true,
type: Function
},
value: {
/**
* Used for v-model
*/
required: true,
type: String
}
},
data () { data () {
return { return {
input: undefined, input: undefined,
highlighted: 0, highlighted: 0,
caret: 0, caret: 0,
focused: false, focused: false
popupOptions: {
placement: 'bottom-start',
trigger: 'hover',
// See: https://github.com/RobinCK/vue-popper/issues/63
'delay-on-mouse-over': 9999999,
'delay-on-mouse-out': 9999999,
modifiers: {
arrow: { enabled: true },
offset: { offset: '0, 5px' }
}
}
} }
}, },
computed: { computed: {
@ -64,22 +98,22 @@ const EmojiInput = {
if (!input) return if (!input) return
this.input = input this.input = input
this.resize() this.resize()
input.elm.addEventListener('transitionend', this.onTransition)
input.elm.addEventListener('blur', this.onBlur) input.elm.addEventListener('blur', this.onBlur)
input.elm.addEventListener('focus', this.onFocus) input.elm.addEventListener('focus', this.onFocus)
input.elm.addEventListener('paste', this.onPaste) input.elm.addEventListener('paste', this.onPaste)
input.elm.addEventListener('keyup', this.onKeyUp) input.elm.addEventListener('keyup', this.onKeyUp)
input.elm.addEventListener('keydown', this.onKeyDown) input.elm.addEventListener('keydown', this.onKeyDown)
input.elm.addEventListener('transitionend', this.onTransition)
}, },
unmounted () { unmounted () {
const { input } = this const { input } = this
if (input) { if (input) {
input.elm.removeEventListener('transitionend', this.onTransition)
input.elm.removeEventListener('blur', this.onBlur) input.elm.removeEventListener('blur', this.onBlur)
input.elm.removeEventListener('focus', this.onFocus) input.elm.removeEventListener('focus', this.onFocus)
input.elm.removeEventListener('paste', this.onPaste) input.elm.removeEventListener('paste', this.onPaste)
input.elm.removeEventListener('keyup', this.onKeyUp) input.elm.removeEventListener('keyup', this.onKeyUp)
input.elm.removeEventListener('keydown', this.onKeyDown) input.elm.removeEventListener('keydown', this.onKeyDown)
input.elm.removeEventListener('transitionend', this.onTransition)
} }
}, },
methods: { methods: {
@ -96,8 +130,16 @@ const EmojiInput = {
const replacement = suggestion.replacement const replacement = suggestion.replacement
const newValue = Completion.replaceWord(this.value, this.wordAtCaret, replacement) const newValue = Completion.replaceWord(this.value, this.wordAtCaret, replacement)
this.$emit('input', newValue) this.$emit('input', newValue)
this.caret = 0
this.highlighted = 0 this.highlighted = 0
const position = this.wordAtCaret.start + replacement.length
this.$nextTick(function () {
// Re-focus inputbox after clicking suggestion
this.input.elm.focus()
// Set selection right after the replacement instead of the very end
this.input.elm.setSelectionRange(position, position)
this.caret = position
})
e.preventDefault() e.preventDefault()
} }
}, },
@ -126,29 +168,33 @@ const EmojiInput = {
} }
}, },
onTransition (e) { onTransition (e) {
this.resize(e) this.resize()
}, },
onBlur (e) { onBlur (e) {
this.focused = false // Clicking on any suggestion removes focus from autocomplete,
this.setCaret(e) // preventing click handler ever executing.
this.resize(e) setTimeout(() => {
this.focused = false
this.setCaret(e)
this.resize()
}, 200)
}, },
onFocus (e) { onFocus (e) {
this.focused = true this.focused = true
this.setCaret(e) this.setCaret(e)
this.resize(e) this.resize()
}, },
onKeyUp (e) { onKeyUp (e) {
this.setCaret(e) this.setCaret(e)
this.resize(e) this.resize()
}, },
onPaste (e) { onPaste (e) {
this.setCaret(e) this.setCaret(e)
this.resize(e) this.resize()
}, },
onKeyDown (e) { onKeyDown (e) {
this.setCaret(e) this.setCaret(e)
this.resize(e) this.resize()
const { ctrlKey, shiftKey, key } = e const { ctrlKey, shiftKey, key } = e
if (key === 'Tab') { if (key === 'Tab') {
@ -172,7 +218,7 @@ const EmojiInput = {
onInput (e) { onInput (e) {
this.$emit('input', e.target.value) this.$emit('input', e.target.value)
}, },
setCaret ({ target: { selectionStart, value } }) { setCaret ({ target: { selectionStart } }) {
this.caret = selectionStart this.caret = selectionStart
}, },
resize () { resize () {

View file

@ -6,7 +6,7 @@
<div <div
v-for="(suggestion, index) in suggestions" v-for="(suggestion, index) in suggestions"
:key="index" :key="index"
@click.stop.prevent="replace(suggestion.replacement)" @click.stop.prevent="replaceText"
class="autocomplete-item" class="autocomplete-item"
:class="{ highlighted: suggestion.highlighted }" :class="{ highlighted: suggestion.highlighted }"
> >

View file

@ -1,70 +1,79 @@
export default function suggest (data) { /**
return input => { * suggest - generates a suggestor function to be used by emoji-input
const firstChar = input[0] * data: object providing source information for specific types of suggestions:
if (firstChar === ':' && data.emoji) { * data.emoji - optional, an array of all emoji available i.e.
return suggestEmoji(data.emoji)(input) * (state.instance.emoji + state.instance.customEmoji)
} * data.users - optional, an array of all known users
if (firstChar === '@' && data.users) { *
return suggestUsers(data.users)(input) * Depending on data present one or both (or none) can be present, so if field
} * doesn't support user linking you can just provide only emoji.
return [] */
export default data => input => {
const firstChar = input[0]
if (firstChar === ':' && data.emoji) {
return suggestEmoji(data.emoji)(input)
} }
if (firstChar === '@' && data.users) {
return suggestUsers(data.users)(input)
}
return []
} }
function suggestEmoji (emojis) { export const suggestEmoji = emojis => input => {
return input => { const noPrefix = input.toLowerCase().substr(1)
const noPrefix = input.toLowerCase().substr(1) return emojis
return emojis .filter(({ displayText }) => displayText.toLowerCase().startsWith(noPrefix))
.filter(({ displayText }) => displayText.toLowerCase().startsWith(noPrefix)) .sort((a, b) => {
.sort((a, b) => {
let aScore = 0
let bScore = 0
// Make custom emojis a priority
aScore += Number(!!a.imageUrl) * 10
bScore += Number(!!b.imageUrl) * 10
// Sort alphabetically
const alphabetically = a.displayText > b.displayText ? 1 : -1
return bScore - aScore + alphabetically
})
}
}
function suggestUsers (users) {
return input => {
const noPrefix = input.toLowerCase().substr(1)
return users.filter(
user =>
user.screen_name.toLowerCase().startsWith(noPrefix) ||
user.name.toLowerCase().startsWith(noPrefix)
/* eslint-disable camelcase */
).slice(0, 20).sort((a, b) => {
let aScore = 0 let aScore = 0
let bScore = 0 let bScore = 0
// Matches on screen name (i.e. user@instance) makes a priority // Make custom emojis a priority
aScore += a.screen_name.toLowerCase().startsWith(noPrefix) * 2 aScore += a.imageUrl ? 10 : 0
bScore += b.screen_name.toLowerCase().startsWith(noPrefix) * 2 bScore += b.imageUrl ? 10 : 0
// Matches on name takes second priority // Sort alphabetically
aScore += a.name.toLowerCase().startsWith(noPrefix) const alphabetically = a.displayText > b.displayText ? 1 : -1
bScore += b.name.toLowerCase().startsWith(noPrefix)
const diff = bScore * 10 - aScore * 10 return bScore - aScore + alphabetically
})
// Then sort alphabetically }
const nameAlphabetically = a.name > b.name ? 1 : -1
const screenNameAlphabetically = a.screen_name > b.screen_name ? 1 : -1 export const suggestUsers = users => input => {
const noPrefix = input.toLowerCase().substr(1)
return diff + nameAlphabetically + screenNameAlphabetically return users.filter(
}).map(({ screen_name, name, profile_image_url_original }) => ({ user =>
displayText: screen_name, user.screen_name.toLowerCase().startsWith(noPrefix) ||
detailText: name, user.name.toLowerCase().startsWith(noPrefix)
imageUrl: profile_image_url_original,
replacement: '@' + screen_name /* taking only 20 results so that sorting is a bit cheaper, we display
})) * only 5 anyway. could be inaccurate, but we ideally we should query
/* eslint-enable camelcase */ * backend anyway
} */
).slice(0, 20).sort((a, b) => {
let aScore = 0
let bScore = 0
// Matches on screen name (i.e. user@instance) makes a priority
aScore += a.screen_name.toLowerCase().startsWith(noPrefix) ? 2 : 0
bScore += b.screen_name.toLowerCase().startsWith(noPrefix) ? 2 : 0
// Matches on name takes second priority
aScore += a.name.toLowerCase().startsWith(noPrefix) ? 1 : 0
bScore += b.name.toLowerCase().startsWith(noPrefix) ? 1 : 0
const diff = (bScore - aScore) * 10
// Then sort alphabetically
const nameAlphabetically = a.name > b.name ? 1 : -1
const screenNameAlphabetically = a.screen_name > b.screen_name ? 1 : -1
return diff + nameAlphabetically + screenNameAlphabetically
/* eslint-disable camelcase */
}).map(({ screen_name, name, profile_image_url_original }) => ({
displayText: screen_name,
detailText: name,
imageUrl: profile_image_url_original,
replacement: '@' + screen_name + ' '
}))
/* eslint-enable camelcase */
} }

View file

@ -147,9 +147,6 @@ const PostStatusForm = {
return true return true
} }
}, },
formattingOptionsEnabled () {
return this.$store.state.instance.formattingOptionsEnabled
},
postFormats () { postFormats () {
return this.$store.state.instance.postFormats || [] return this.$store.state.instance.postFormats || []
}, },

View file

@ -31,7 +31,7 @@
<span v-if="safeDMEnabled">{{ $t('post_status.direct_warning_to_first_only') }}</span> <span v-if="safeDMEnabled">{{ $t('post_status.direct_warning_to_first_only') }}</span>
<span v-else>{{ $t('post_status.direct_warning_to_all') }}</span> <span v-else>{{ $t('post_status.direct_warning_to_all') }}</span>
</p> </p>
<emoji-input <EmojiInput
v-if="newStatus.spoilerText || alwaysShowSubject" v-if="newStatus.spoilerText || alwaysShowSubject"
:suggest="emojiSuggestor" :suggest="emojiSuggestor"
v-model="newStatus.spoilerText" v-model="newStatus.spoilerText"
@ -44,8 +44,8 @@
v-model="newStatus.spoilerText" v-model="newStatus.spoilerText"
class="form-post-subject" class="form-post-subject"
/> />
</emoji-input> </EmojiInput>
<emoji-input <EmojiInput
:suggest="emojiUserSuggestor" :suggest="emojiUserSuggestor"
v-model="newStatus.status" v-model="newStatus.status"
class="form-control" class="form-control"
@ -65,9 +65,9 @@
class="form-post-body" class="form-post-body"
> >
</textarea> </textarea>
</emoji-input> </EmojiInput>
<div class="visibility-tray"> <div class="visibility-tray">
<div class="text-format" v-if="formattingOptionsEnabled"> <div class="text-format" v-if="postFormats.length > 1">
<label for="post-content-type" class="select"> <label for="post-content-type" class="select">
<select id="post-content-type" v-model="newStatus.contentType" class="form-control"> <select id="post-content-type" v-model="newStatus.contentType" class="form-control">
<option v-for="postFormat in postFormats" :key="postFormat" :value="postFormat"> <option v-for="postFormat in postFormats" :key="postFormat" :value="postFormat">
@ -77,6 +77,11 @@
<i class="icon-down-open"></i> <i class="icon-down-open"></i>
</label> </label>
</div> </div>
<div class="text-format" v-if="postFormats.length === 1">
<span class="only-format">
{{$t(`post_status.content_type["${postFormats[0]}"]`)}}
</span>
</div>
<scope-selector <scope-selector
:showAll="showAllScopes" :showAll="showAllScopes"
@ -167,6 +172,14 @@
} }
} }
.text-format {
.only-format {
color: $fallback--faint;
color: var(--faint, $fallback--faint);
}
}
.error { .error {
text-align: center; text-align: center;
} }

View file

@ -102,7 +102,7 @@
</label> </label>
</div> </div>
</li> </li>
<li> <li v-if="postFormats.length > 0">
<div> <div>
{{$t('settings.post_status_content_type')}} {{$t('settings.post_status_content_type')}}
<label for="postContentType" class="select"> <label for="postContentType" class="select">

View file

@ -78,6 +78,8 @@ const Timeline = {
}, },
methods: { methods: {
handleShortKey (e) { handleShortKey (e) {
// Ignore when input fields are focused
if (['textarea', 'input'].includes(e.target.tagName.toLowerCase())) return
if (e.key === '.') this.showNewStatuses() if (e.key === '.') this.showNewStatuses()
}, },
showNewStatuses () { showNewStatuses () {

View file

@ -7,6 +7,7 @@ import { mapState } from 'vuex'
const Mfa = { const Mfa = {
data: () => ({ data: () => ({
settings: { // current settings of MFA settings: { // current settings of MFA
available: false,
enabled: false, enabled: false,
totp: false totp: false
}, },
@ -139,7 +140,9 @@ const Mfa = {
// fetch settings from server // fetch settings from server
async fetchSettings () { async fetchSettings () {
let result = await this.backendInteractor.fetchSettingsMFA() let result = await this.backendInteractor.fetchSettingsMFA()
if (result.error) return
this.settings = result.settings this.settings = result.settings
this.settings.available = true
return result return result
} }
}, },

View file

@ -1,5 +1,5 @@
<template> <template>
<div class="setting-item mfa-settings" v-if="readyInit"> <div class="setting-item mfa-settings" v-if="readyInit && settings.available">
<div class="mfa-heading"> <div class="mfa-heading">
<h2>{{$t('settings.mfa.title')}}</h2> <h2>{{$t('settings.mfa.title')}}</h2>

View file

@ -47,7 +47,9 @@ const UserSettings = {
pickAvatarBtnVisible: true, pickAvatarBtnVisible: true,
bannerUploading: false, bannerUploading: false,
backgroundUploading: false, backgroundUploading: false,
banner: null,
bannerPreview: null, bannerPreview: null,
background: null,
backgroundPreview: null, backgroundPreview: null,
bannerUploadError: null, bannerUploadError: null,
backgroundUploadError: null, backgroundUploadError: null,
@ -214,22 +216,12 @@ const UserSettings = {
}, },
submitBg () { submitBg () {
if (!this.backgroundPreview) { return } if (!this.backgroundPreview) { return }
let img = this.backgroundPreview let background = this.background
// eslint-disable-next-line no-undef
let imginfo = new Image()
let cropX, cropY, cropW, cropH
imginfo.src = img
cropX = 0
cropY = 0
cropW = imginfo.width
cropH = imginfo.width
this.backgroundUploading = true this.backgroundUploading = true
this.$store.state.api.backendInteractor.updateBg({params: {img, cropX, cropY, cropW, cropH}}).then((data) => { this.$store.state.api.backendInteractor.updateBg({ background }).then((data) => {
if (!data.error) { if (!data.error) {
let clone = JSON.parse(JSON.stringify(this.$store.state.users.currentUser)) this.$store.commit('addNewUsers', [data])
clone.background_image = data.url this.$store.commit('setCurrentUser', data)
this.$store.commit('addNewUsers', [clone])
this.$store.commit('setCurrentUser', clone)
this.backgroundPreview = null this.backgroundPreview = null
} else { } else {
this.backgroundUploadError = this.$t('upload.error.base') + data.error this.backgroundUploadError = this.$t('upload.error.base') + data.error

View file

@ -22,20 +22,20 @@
<div class="setting-item" > <div class="setting-item" >
<h2>{{$t('settings.name_bio')}}</h2> <h2>{{$t('settings.name_bio')}}</h2>
<p>{{$t('settings.name')}}</p> <p>{{$t('settings.name')}}</p>
<emoji-input :suggest="emojiSuggestor" v-model="newName"> <EmojiInput :suggest="emojiSuggestor" v-model="newName">
<input <input
v-model="newName" v-model="newName"
id="username" id="username"
classname="name-changer" classname="name-changer"
/> />
</emoji-input> </EmojiInput>
<p>{{$t('settings.bio')}}</p> <p>{{$t('settings.bio')}}</p>
<emoji-input :suggest="emojiUserSuggestor" v-model="newBio"> <EmojiInput :suggest="emojiUserSuggestor" v-model="newBio">
<textarea <textarea
v-model="newBio" v-model="newBio"
classname="bio" classname="bio"
/> />
</emoji-input> </EmojiInput>
<p> <p>
<input type="checkbox" v-model="newLocked" id="account-locked"> <input type="checkbox" v-model="newLocked" id="account-locked">
<label for="account-locked">{{$t('settings.lock_account_description')}}</label> <label for="account-locked">{{$t('settings.lock_account_description')}}</label>

View file

@ -16,7 +16,6 @@ const defaultState = {
redirectRootNoLogin: '/main/all', redirectRootNoLogin: '/main/all',
redirectRootLogin: '/main/friends', redirectRootLogin: '/main/friends',
showInstanceSpecificPanel: false, showInstanceSpecificPanel: false,
formattingOptionsEnabled: false,
alwaysShowSubjectInput: true, alwaysShowSubjectInput: true,
hideMutedPosts: false, hideMutedPosts: false,
collapseMessageWithSubject: false, collapseMessageWithSubject: false,

View file

@ -356,7 +356,13 @@ const users = {
}, },
searchUsers (store, query) { searchUsers (store, query) {
// TODO: Move userSearch api into api.service // TODO: Move userSearch api into api.service
return userSearchApi.search({query, store: { state: store.rootState }}) return userSearchApi.search({
query,
store: {
state: store.rootState,
getters: store.rootGetters
}
})
.then((users) => { .then((users) => {
store.commit('addNewUsers', users) store.commit('addNewUsers', users)
return users return users

View file

@ -1,5 +1,4 @@
/* eslint-env browser */ /* eslint-env browser */
const BG_UPDATE_URL = '/api/qvitter/update_background_image.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 BLOCKS_IMPORT_URL = '/api/pleroma/blocks_import'
@ -25,7 +24,6 @@ const MFA_DISABLE_OTP_URL = '/api/pleroma/profile/mfa/totp'
const MASTODON_LOGIN_URL = '/api/v1/accounts/verify_credentials' const MASTODON_LOGIN_URL = '/api/v1/accounts/verify_credentials'
const MASTODON_REGISTRATION_URL = '/api/v1/accounts' const MASTODON_REGISTRATION_URL = '/api/v1/accounts'
const GET_BACKGROUND_HACK = '/api/account/verify_credentials.json'
const MASTODON_USER_FAVORITES_TIMELINE_URL = '/api/v1/favourites' const MASTODON_USER_FAVORITES_TIMELINE_URL = '/api/v1/favourites'
const MASTODON_USER_NOTIFICATIONS_URL = '/api/v1/notifications' const MASTODON_USER_NOTIFICATIONS_URL = '/api/v1/notifications'
const MASTODON_FAVORITE_URL = id => `/api/v1/statuses/${id}/favourite` const MASTODON_FAVORITE_URL = id => `/api/v1/statuses/${id}/favourite`
@ -133,22 +131,16 @@ const updateAvatar = ({credentials, avatar}) => {
.then((data) => parseUser(data)) .then((data) => parseUser(data))
} }
const updateBg = ({credentials, params}) => { const updateBg = ({ credentials, background }) => {
let url = BG_UPDATE_URL
const form = new FormData() const form = new FormData()
form.append('pleroma_background_image', background)
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 updateBanner = ({credentials, banner}) => { const updateBanner = ({credentials, banner}) => {
@ -544,26 +536,6 @@ const verifyCredentials = (user) => {
} }
}) })
.then((data) => data.error ? data : parseUser(data)) .then((data) => data.error ? data : parseUser(data))
.then((mastoUser) => {
// REMOVE WHEN BE SUPPORTS background_image
return fetch(GET_BACKGROUND_HACK, {
method: 'POST',
headers: authHeaders(user)
})
.then((response) => {
if (response.ok) {
return response.json()
} else {
return {}
}
})
/* eslint-disable camelcase */
.then(({ background_image }) => ({
...mastoUser,
background_image
}))
/* eslint-enable camelcase */
})
} }
const favorite = ({ id, credentials }) => { const favorite = ({ id, credentials }) => {

View file

@ -105,7 +105,7 @@ const backendInteractorService = (credentials) => {
const getCaptcha = () => apiService.getCaptcha() const getCaptcha = () => apiService.getCaptcha()
const register = (params) => apiService.register({ credentials, params }) const register = (params) => apiService.register({ credentials, params })
const updateAvatar = ({avatar}) => apiService.updateAvatar({credentials, avatar}) const updateAvatar = ({avatar}) => apiService.updateAvatar({credentials, avatar})
const updateBg = ({params}) => apiService.updateBg({credentials, params}) const updateBg = ({ background }) => apiService.updateBg({ credentials, background })
const updateBanner = ({banner}) => apiService.updateBanner({credentials, banner}) const updateBanner = ({banner}) => apiService.updateBanner({credentials, banner})
const updateProfile = ({params}) => apiService.updateProfile({credentials, params}) const updateProfile = ({params}) => apiService.updateProfile({credentials, params})

View file

@ -60,6 +60,9 @@ export const parseUser = (data) => {
if (data.pleroma) { if (data.pleroma) {
const relationship = data.pleroma.relationship const relationship = data.pleroma.relationship
output.background_image = data.pleroma.background_image
output.token = data.pleroma.chat_token
if (relationship) { if (relationship) {
output.follows_you = relationship.followed_by output.follows_you = relationship.followed_by
output.following = relationship.following output.following = relationship.following

View file

@ -9,7 +9,6 @@
"redirectRootLogin": "/main/friends", "redirectRootLogin": "/main/friends",
"chatDisabled": false, "chatDisabled": false,
"showInstanceSpecificPanel": false, "showInstanceSpecificPanel": false,
"formattingOptionsEnabled": false,
"collapseMessageWithSubject": false, "collapseMessageWithSubject": false,
"scopeCopy": true, "scopeCopy": true,
"subjectLineBehavior": "noop", "subjectLineBehavior": "noop",