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

* upstream/develop: (26 commits)
  added condition to check for logined user
  fix gradients and minor artifacts
  keep track of new instance options
  fix old MR
  oof
  get rid of slots
  added hide_network option, fixed properties naming
  Fix fetching new users, add storing local users in usersObjects with their screen_name as well as id, so that they could be fetched zero-state with screen-name link.
  Refactor arrays to individual options
  Reset enableFollowsExport to true after 2 sec when an export file is available to download
  Write a unit test for fileSizeFormatService
  I am dumb
  Handle errors from server
  Moved upload errors in user_settings to an array. Moved upload error strings to its separate section in i18n
  Avatar, background, banner filesize errors
  Count in binary bytes and remove i18 from file size format service
  Add a space between filesizes
  Lint
  Add file size formating
  Fix formating
  ...
This commit is contained in:
Henry Jameson 2018-12-14 01:16:54 +03:00
commit 8d345bbad9
22 changed files with 295 additions and 88 deletions

View file

@ -228,24 +228,23 @@ i[class*=icon-] {
padding: 0 10px 0 10px;
}
.gaps {
margin: -1em 0 0 -1em;
}
.item {
flex: 1;
line-height: 50px;
height: 50px;
overflow: hidden;
display: flex;
flex-wrap: wrap;
.nav-icon {
font-size: 1.1em;
margin-left: 0.4em;
}
}
.gaps > .item {
padding: 1em 0 0 1em;
&.right {
justify-content: right;
padding-right: 20px;
}
}
.auto-size {
@ -293,8 +292,6 @@ nav {
}
.inner-nav {
padding-left: 20px;
padding-right: 20px;
display: flex;
align-items: center;
flex-basis: 970px;
@ -452,6 +449,23 @@ nav {
color: var(--faint, $fallback--faint);
box-shadow: 0px 0px 4px rgba(0,0,0,.6);
box-shadow: var(--topBarShadow);
.back-button {
display: block;
max-width: 99px;
transition-property: opacity, max-width;
transition-duration: 300ms;
transition-timing-function: ease-out;
i {
margin: 0 1em;
}
&.hidden {
opacity: 0;
max-width: 20px;
}
}
}
.fade-enter-active, .fade-leave-active {
@ -486,6 +500,7 @@ nav {
display: none;
width: 100%;
height: 46px;
button {
display: block;
flex: 1;
@ -499,6 +514,16 @@ nav {
body {
overflow-y: scroll;
}
nav {
.back-button {
display: none;
}
.site-name {
padding-left: 20px;
}
}
.sidebar-bounds {
overflow: hidden;
max-height: 100vh;
@ -591,11 +616,6 @@ nav {
}
}
.item.right {
text-align: right;
padding-right: 20px;
}
.visibility-tray {
font-size: 1.2em;
padding: 3px;

View file

@ -7,7 +7,10 @@
</div>
<div class='inner-nav'>
<div class='item'>
<router-link :to="{ name: 'root'}">{{sitename}}</router-link>
<router-link class="back-button" @click.native="activatePanel('timeline')" :to="{ name: 'root' }" active-class="hidden">
<i class="icon-left-open" :title="$t('nav.back')"></i>
</router-link>
<router-link class="site-name" :to="{ name: 'root' }" active-class="home">{{sitename}}</router-link>
</div>
<div class='item right'>
<user-finder class="nav-icon"></user-finder>

View file

@ -21,13 +21,21 @@ const afterStoreSetup = ({ store, i18n }) => {
window.fetch('/api/statusnet/config.json')
.then((res) => res.json())
.then((data) => {
const { name, closed: registrationClosed, textlimit, server, vapidPublicKey } = data.site
const { name, closed: registrationClosed, textlimit, uploadlimit, server, vapidPublicKey } = data.site
store.dispatch('setInstanceOption', { name: 'name', value: name })
store.dispatch('setInstanceOption', { name: 'registrationOpen', value: (registrationClosed === '0') })
store.dispatch('setInstanceOption', { name: 'textlimit', value: parseInt(textlimit) })
store.dispatch('setInstanceOption', { name: 'uploadlimit', value: parseInt(uploadlimit.uploadlimit) })
store.dispatch('setInstanceOption', { name: 'avatarlimit', value: parseInt(uploadlimit.avatarlimit) })
store.dispatch('setInstanceOption', { name: 'backgroundlimit', value: parseInt(uploadlimit.backgroundlimit) })
store.dispatch('setInstanceOption', { name: 'bannerlimit', value: parseInt(uploadlimit.bannerlimit) })
store.dispatch('setInstanceOption', { name: 'server', value: server })
if (data.nsfwCensorImage) {
store.dispatch('setInstanceOption', { name: 'nsfwCensorImage', value: data.nsfwCensorImage })
}
if (vapidPublicKey) {
store.dispatch('setInstanceOption', { name: 'vapidPublicKey', value: vapidPublicKey })
}

View file

@ -11,7 +11,7 @@ const Attachment = {
],
data () {
return {
nsfwImage,
nsfwImage: this.$store.state.config.nsfwCensorImage || nsfwImage,
hideNsfwLocal: this.$store.state.config.hideNsfw,
preloadImage: this.$store.state.config.preloadImage,
loopVideo: this.$store.state.config.loopVideo,

View file

@ -1,5 +1,6 @@
/* eslint-env browser */
import statusPosterService from '../../services/status_poster/status_poster.service.js'
import fileSizeFormatService from '../../services/file_size_format/file_size_format.js'
const mediaUpload = {
mounted () {
@ -21,6 +22,12 @@ const mediaUpload = {
uploadFile (file) {
const self = this
const store = this.$store
if (file.size > store.state.instance.uploadlimit) {
const filesize = fileSizeFormatService.fileSizeFormat(file.size)
const allowedsize = fileSizeFormatService.fileSizeFormat(store.state.instance.uploadlimit)
self.$emit('upload-failed', 'file_too_big', {filesize: filesize.num, filesizeunit: filesize.unit, allowedsize: allowedsize.num, allowedsizeunit: allowedsize.unit})
return
}
const formData = new FormData()
formData.append('media', file)
@ -32,7 +39,7 @@ const mediaUpload = {
self.$emit('uploaded', fileData)
self.uploading = false
}, (error) => { // eslint-disable-line handle-callback-err
self.$emit('upload-failed')
self.$emit('upload-failed', 'default')
self.uploading = false
})
},

View file

@ -259,6 +259,11 @@ const PostStatusForm = {
let index = this.newStatus.files.indexOf(fileInfo)
this.newStatus.files.splice(index, 1)
},
uploadFailed (errString, templateArgs) {
templateArgs = templateArgs || {}
this.error = this.$t('upload.error.base') + ' ' + this.$t('upload.error.' + errString, templateArgs)
this.enableSubmit()
},
disableSubmit () {
this.submitDisabled = true
},

View file

@ -64,7 +64,7 @@
</div>
</div>
<div class='form-bottom'>
<media-upload @uploading="disableSubmit" @uploaded="addMediaFile" @upload-failed="enableSubmit" :drop-files="dropFiles"></media-upload>
<media-upload @uploading="disableSubmit" @uploaded="addMediaFile" @upload-failed="uploadFailed" :drop-files="dropFiles"></media-upload>
<p v-if="isOverLengthLimit" class="error">{{ charactersLeft }}</p>
<p class="faint" v-else-if="hasStatusLengthLimit">{{ charactersLeft }}</p>

View file

@ -2,6 +2,7 @@ import Status from '../status/status.vue'
import timelineFetcher from '../../services/timeline_fetcher/timeline_fetcher.service.js'
import StatusOrConversation from '../status_or_conversation/status_or_conversation.vue'
import UserCard from '../user_card/user_card.vue'
import { throttle } from 'lodash'
const Timeline = {
props: [
@ -88,7 +89,7 @@ const Timeline = {
this.paused = false
}
},
fetchOlderStatuses () {
fetchOlderStatuses: throttle(function () {
const store = this.$store
const credentials = store.state.users.currentUser.credentials
store.commit('setLoading', { timeline: this.timelineName, value: true })
@ -101,7 +102,7 @@ const Timeline = {
userId: this.userId,
tag: this.tag
}).then(() => store.commit('setLoading', { timeline: this.timelineName, value: false }))
},
}, 1000, this),
fetchFollowers () {
const id = this.userId
this.$store.state.api.backendInteractor.fetchFollowers({ id })

View file

@ -14,6 +14,9 @@ const UserCard = {
components: {
UserCardContent
},
computed: {
currentUser () { return this.$store.state.users.currentUser }
},
methods: {
toggleUserExpanded () {
this.userExpanded = !this.userExpanded

View file

@ -10,13 +10,13 @@
<div :title="user.name" v-if="user.name_html" class="user-name">
<span v-html="user.name_html"></span>
<span class="follows-you" v-if="!userExpanded && showFollows && user.follows_you">
{{ $t('user_card.follows_you') }}
{{ currentUser.id == user.id ? $t('user_card.its_you') : $t('user_card.follows_you') }}
</span>
</div>
<div :title="user.name" v-else class="user-name">
{{ user.name }}
<span class="follows-you" v-if="!userExpanded && showFollows && user.follows_you">
{{ $t('user_card.follows_you') }}
{{ currentUser.id == user.id ? $t('user_card.its_you') : $t('user_card.follows_you') }}
</span>
</div>
<router-link class='user-screen-name' :to="{ name: 'user-profile', params: { id: user.id } }">

View file

@ -20,10 +20,20 @@ export default {
if (color) {
const rgb = (typeof color === 'string') ? hex2rgb(color) : color
const tintColor = `rgba(${Math.floor(rgb.r)}, ${Math.floor(rgb.g)}, ${Math.floor(rgb.b)}, .5)`
const gradient = [
[tintColor, this.hideBio ? '60%' : ''],
this.hideBio ? [
color, '100%'
] : [
tintColor, ''
]
].map(_ => _.join(' ')).join(', ')
return {
backgroundColor: `rgb(${Math.floor(rgb.r * 0.53)}, ${Math.floor(rgb.g * 0.56)}, ${Math.floor(rgb.b * 0.59)})`,
backgroundImage: [
`linear-gradient(to bottom, ${tintColor}, ${tintColor})`,
`linear-gradient(to bottom, ${gradient})`,
`url(${this.user.cover_photo})`
].join(', ')
}

View file

@ -90,7 +90,7 @@
</div>
</div>
</div>
<div class="panel-body profile-panel-body" v-if="switcher">
<div class="panel-body profile-panel-body" v-if="!hideBio">
<div v-if="!hideUserStatsLocal || switcher" class="user-counts" :class="{clickable: switcher}">
<div class="user-count" v-on:click.prevent="setProfileView('statuses')" :class="{selected: selected === 'statuses'}">
<h5>{{ $t('user_card.statuses') }}</h5>
@ -122,6 +122,9 @@
border-radius: var(--panelRadius, $fallback--panelRadius);
overflow: hidden;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
.panel-heading {
padding: 0.6em 0em;
text-align: center;

View file

@ -1,12 +1,12 @@
<template>
<span class="user-finder-container">
<div class="user-finder-container">
<i class="icon-spin4 user-finder-icon animate-spin-slow" v-if="loading" />
<a href="#" v-if="hidden" :title="$t('finder.find_user')" ><i class="icon-user-plus user-finder-icon" @click.prevent.stop="toggleHidden" /></a>
<span v-else>
<input class="user-finder-input" @keyup.enter="findUser(username)" v-model="username" :placeholder="$t('finder.find_user')" id="user-finder-input" type="text"/>
<i class="icon-cancel user-finder-icon" @click.prevent.stop="toggleHidden"/>
</span>
</span>
</div>
</template>
<script src="./user_finder.js"></script>
@ -15,7 +15,6 @@
@import '../../_variables.scss';
.user-finder-container {
height: 29px;
max-width: 100%;
}

View file

@ -3,6 +3,16 @@
<div v-if="user" class="user-profile panel panel-default">
<user-card-content :user="user" :switcher="true" :selected="timeline.viewing"></user-card-content>
</div>
<div v-else class="panel user-profile-placeholder">
<div class="panel-heading">
<div class="title">
{{ $t('settings.profile_tab') }}
</div>
</div>
<div class="panel-body">
<i class="icon-spin3 animate-spin"></i>
</div>
</div>
<Timeline :title="$t('user_profile.timeline_title')" :timeline="timeline" :timeline-name="'user'" :user-id="userId"/>
</div>
</template>
@ -21,4 +31,12 @@
align-items: stretch;
}
}
.user-profile-placeholder {
.panel-body {
display: flex;
justify-content: center;
align-items: middle;
padding: 7em;
}
}
</style>

View file

@ -1,20 +1,30 @@
import TabSwitcher from '../tab_switcher/tab_switcher.jsx'
import StyleSwitcher from '../style_switcher/style_switcher.vue'
import fileSizeFormatService from '../../services/file_size_format/file_size_format.js'
const UserSettings = {
data () {
return {
newname: this.$store.state.users.currentUser.name,
newbio: this.$store.state.users.currentUser.description,
newlocked: this.$store.state.users.currentUser.locked,
newnorichtext: this.$store.state.users.currentUser.no_rich_text,
newdefaultScope: this.$store.state.users.currentUser.default_scope,
newName: this.$store.state.users.currentUser.name,
newBio: this.$store.state.users.currentUser.description,
newLocked: this.$store.state.users.currentUser.locked,
newNoRichText: this.$store.state.users.currentUser.no_rich_text,
newDefaultScope: this.$store.state.users.currentUser.default_scope,
newHideNetwork: this.$store.state.users.currentUser.hide_network,
followList: null,
followImportError: false,
followsImported: false,
enableFollowsExport: true,
uploading: [ false, false, false, false ],
previews: [ null, null, null ],
avatarUploading: false,
bannerUploading: false,
backgroundUploading: false,
followListUploading: false,
avatarPreview: null,
bannerPreview: null,
backgroundPreview: null,
avatarUploadError: null,
bannerUploadError: null,
backgroundUploadError: null,
deletingAccount: false,
deleteAccountConfirmPasswordInput: '',
deleteAccountError: false,
@ -40,48 +50,67 @@ const UserSettings = {
},
vis () {
return {
public: { selected: this.newdefaultScope === 'public' },
unlisted: { selected: this.newdefaultScope === 'unlisted' },
private: { selected: this.newdefaultScope === 'private' },
direct: { selected: this.newdefaultScope === 'direct' }
public: { selected: this.newDefaultScope === 'public' },
unlisted: { selected: this.newDefaultScope === 'unlisted' },
private: { selected: this.newDefaultScope === 'private' },
direct: { selected: this.newDefaultScope === 'direct' }
}
}
},
methods: {
updateProfile () {
const name = this.newname
const description = this.newbio
const locked = this.newlocked
const description = this.newBio
const locked = this.newLocked
// Backend notation.
/* eslint-disable camelcase */
const default_scope = this.newdefaultScope
const no_rich_text = this.newnorichtext
this.$store.state.api.backendInteractor.updateProfile({params: {name, description, locked, default_scope, no_rich_text}}).then((user) => {
if (!user.error) {
this.$store.commit('addNewUsers', [user])
this.$store.commit('setCurrentUser', user)
}
})
const default_scope = this.newDefaultScope
const no_rich_text = this.newNoRichText
const hide_network = this.newHideNetwork
/* eslint-enable camelcase */
this.$store.state.api.backendInteractor
.updateProfile({
params: {
name,
description,
locked,
// Backend notation.
/* eslint-disable camelcase */
default_scope,
no_rich_text,
hide_network
/* eslint-enable camelcase */
}}).then((user) => {
if (!user.error) {
this.$store.commit('addNewUsers', [user])
this.$store.commit('setCurrentUser', user)
}
})
},
changeVis (visibility) {
this.newdefaultScope = visibility
this.newDefaultScope = visibility
},
uploadFile (slot, e) {
const file = e.target.files[0]
if (!file) { return }
if (file.size > this.$store.state.instance[slot + 'limit']) {
const filesize = fileSizeFormatService.fileSizeFormat(file.size)
const allowedsize = fileSizeFormatService.fileSizeFormat(this.$store.state.instance[slot + 'limit'])
this[slot + 'UploadError'] = this.$t('upload.error.base') + ' ' + this.$t('upload.error.file_too_big', {filesize: filesize.num, filesizeunit: filesize.unit, allowedsize: allowedsize.num, allowedsizeunit: allowedsize.unit})
return
}
// eslint-disable-next-line no-undef
const reader = new FileReader()
reader.onload = ({target}) => {
const img = target.result
this.previews[slot] = img
this.$forceUpdate() // just changing the array with the index doesn't update the view
this[slot + 'Preview'] = img
}
reader.readAsDataURL(file)
},
submitAvatar () {
if (!this.previews[0]) { return }
if (!this.avatarPreview) { return }
let img = this.previews[0]
let img = this.avatarPreview
// eslint-disable-next-line no-undef
let imginfo = new Image()
let cropX, cropY, cropW, cropH
@ -97,20 +126,25 @@ const UserSettings = {
cropX = Math.floor((imginfo.width - imginfo.height) / 2)
cropW = imginfo.height
}
this.uploading[0] = true
this.avatarUploading = true
this.$store.state.api.backendInteractor.updateAvatar({params: {img, cropX, cropY, cropW, cropH}}).then((user) => {
if (!user.error) {
this.$store.commit('addNewUsers', [user])
this.$store.commit('setCurrentUser', user)
this.previews[0] = null
this.avatarPreview = null
} else {
this.avatarUploadError = this.$t('upload.error.base') + user.error
}
this.uploading[0] = false
this.avatarUploading = false
})
},
clearUploadError (slot) {
this[slot + 'UploadError'] = null
},
submitBanner () {
if (!this.previews[1]) { return }
if (!this.bannerPreview) { return }
let banner = this.previews[1]
let banner = this.bannerPreview
// eslint-disable-next-line no-undef
let imginfo = new Image()
/* eslint-disable camelcase */
@ -120,22 +154,24 @@ const UserSettings = {
height = imginfo.height
offset_top = 0
offset_left = 0
this.uploading[1] = true
this.bannerUploading = true
this.$store.state.api.backendInteractor.updateBanner({params: {banner, offset_top, offset_left, width, height}}).then((data) => {
if (!data.error) {
let clone = JSON.parse(JSON.stringify(this.$store.state.users.currentUser))
clone.cover_photo = data.url
this.$store.commit('addNewUsers', [clone])
this.$store.commit('setCurrentUser', clone)
this.previews[1] = null
this.bannerPreview = null
} else {
this.bannerUploadError = this.$t('upload.error.base') + data.error
}
this.uploading[1] = false
this.bannerUploading = false
})
/* eslint-enable camelcase */
},
submitBg () {
if (!this.previews[2]) { return }
let img = this.previews[2]
if (!this.backgroundPreview) { return }
let img = this.backgroundPreview
// eslint-disable-next-line no-undef
let imginfo = new Image()
let cropX, cropY, cropW, cropH
@ -144,20 +180,22 @@ const UserSettings = {
cropY = 0
cropW = imginfo.width
cropH = imginfo.width
this.uploading[2] = true
this.backgroundUploading = true
this.$store.state.api.backendInteractor.updateBg({params: {img, cropX, cropY, cropW, cropH}}).then((data) => {
if (!data.error) {
let clone = JSON.parse(JSON.stringify(this.$store.state.users.currentUser))
clone.background_image = data.url
this.$store.commit('addNewUsers', [clone])
this.$store.commit('setCurrentUser', clone)
this.previews[2] = null
this.backgroundPreview = null
} else {
this.backgroundUploadError = this.$t('upload.error.base') + data.error
}
this.uploading[2] = false
this.backgroundUploading = false
})
},
importFollows () {
this.uploading[3] = true
this.followListUploading = true
const followList = this.followList
this.$store.state.api.backendInteractor.followImport({params: followList})
.then((status) => {
@ -166,7 +204,7 @@ const UserSettings = {
} else {
this.followImportError = true
}
this.uploading[3] = false
this.followListUploading = false
})
},
/* This function takes an Array of Users
@ -198,6 +236,7 @@ const UserSettings = {
.fetchFriends({id: this.$store.state.users.currentUser.id})
.then((friendList) => {
this.exportPeople(friendList, 'friends.csv')
setTimeout(() => { this.enableFollowsExport = true }, 2000)
})
},
followListChange () {

View file

@ -9,11 +9,11 @@
<div class="setting-item" >
<h2>{{$t('settings.name_bio')}}</h2>
<p>{{$t('settings.name')}}</p>
<input class='name-changer' id='username' v-model="newname"></input>
<input class='name-changer' id='username' v-model="newName"></input>
<p>{{$t('settings.bio')}}</p>
<textarea class="bio" v-model="newbio"></textarea>
<textarea class="bio" v-model="newBio"></textarea>
<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>
</p>
<div v-if="scopeOptionsEnabled">
@ -26,47 +26,63 @@
</div>
</div>
<p>
<input type="checkbox" v-model="newnorichtext" id="account-no-rich-text">
<input type="checkbox" v-model="newNoRichText" id="account-no-rich-text">
<label for="account-no-rich-text">{{$t('settings.no_rich_text_description')}}</label>
</p>
<button :disabled='newname.length <= 0' class="btn btn-default" @click="updateProfile">{{$t('general.submit')}}</button>
<p>
<input type="checkbox" v-model="newHideNetwork" id="account-hide-network">
<label for="account-no-rich-text">{{$t('settings.hide_network_description')}}</label>
</p>
<button :disabled='newName.length <= 0' class="btn btn-default" @click="updateProfile">{{$t('general.submit')}}</button>
</div>
<div class="setting-item">
<h2>{{$t('settings.avatar')}}</h2>
<p>{{$t('settings.current_avatar')}}</p>
<img :src="user.profile_image_url_original" class="old-avatar"></img>
<p>{{$t('settings.set_new_avatar')}}</p>
<img class="new-avatar" v-bind:src="previews[0]" v-if="previews[0]">
<img class="new-avatar" v-bind:src="avatarPreview" v-if="avatarPreview">
</img>
<div>
<input type="file" @change="uploadFile(0, $event)" ></input>
<input type="file" @change="uploadFile('avatar', $event)" ></input>
</div>
<i class="icon-spin4 animate-spin" v-if="avatarUploading"></i>
<button class="btn btn-default" v-else-if="avatarPreview" @click="submitAvatar">{{$t('general.submit')}}</button>
<div class='alert error' v-if="avatarUploadError">
Error: {{ avatarUploadError }}
<i class="icon-cancel" @click="clearUploadError('avatar')"></i>
</div>
<i class="icon-spin4 animate-spin" v-if="uploading[0]"></i>
<button class="btn btn-default" v-else-if="previews[0]" @click="submitAvatar">{{$t('general.submit')}}</button>
</div>
<div class="setting-item">
<h2>{{$t('settings.profile_banner')}}</h2>
<p>{{$t('settings.current_profile_banner')}}</p>
<img :src="user.cover_photo" class="banner"></img>
<p>{{$t('settings.set_new_profile_banner')}}</p>
<img class="banner" v-bind:src="previews[1]" v-if="previews[1]">
<img class="banner" v-bind:src="bannerPreview" v-if="bannerPreview">
</img>
<div>
<input type="file" @change="uploadFile(1, $event)" ></input>
<input type="file" @change="uploadFile('banner', $event)" ></input>
</div>
<i class=" icon-spin4 animate-spin uploading" v-if="bannerUploading"></i>
<button class="btn btn-default" v-else-if="bannerPreview" @click="submitBanner">{{$t('general.submit')}}</button>
<div class='alert error' v-if="bannerUploadError">
Error: {{ bannerUploadError }}
<i class="icon-cancel" @click="clearUploadError('banner')"></i>
</div>
<i class=" icon-spin4 animate-spin uploading" v-if="uploading[1]"></i>
<button class="btn btn-default" v-else-if="previews[1]" @click="submitBanner">{{$t('general.submit')}}</button>
</div>
<div class="setting-item">
<h2>{{$t('settings.profile_background')}}</h2>
<p>{{$t('settings.set_new_profile_background')}}</p>
<img class="bg" v-bind:src="previews[2]" v-if="previews[2]">
<img class="bg" v-bind:src="backgroundPreview" v-if="backgroundPreview">
</img>
<div>
<input type="file" @change="uploadFile(2, $event)" ></input>
<input type="file" @change="uploadFile('background', $event)" ></input>
</div>
<i class=" icon-spin4 animate-spin uploading" v-if="backgroundUploading"></i>
<button class="btn btn-default" v-else-if="backgroundPreview" @click="submitBg">{{$t('general.submit')}}</button>
<div class='alert error' v-if="backgroundUploadError">
Error: {{ backgroundUploadError }}
<i class="icon-cancel" @click="clearUploadError('background')"></i>
</div>
<i class=" icon-spin4 animate-spin uploading" v-if="uploading[2]"></i>
<button class="btn btn-default" v-else-if="previews[2]" @click="submitBg">{{$t('general.submit')}}</button>
</div>
</div>
@ -113,7 +129,7 @@
<form v-model="followImportForm">
<input type="file" ref="followlist" v-on:change="followListChange"></input>
</form>
<i class=" icon-spin4 animate-spin uploading" v-if="uploading[3]"></i>
<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>

View file

@ -29,6 +29,7 @@
"username": "Username"
},
"nav": {
"back": "Back",
"chat": "Local Chat",
"friend_requests": "Follow Requests",
"mentions": "Mentions",
@ -151,6 +152,7 @@
"notification_visibility_mentions": "Mentions",
"notification_visibility_repeats": "Repeats",
"no_rich_text_description": "Strip rich text formatting from all posts",
"hide_network_description": "Don't show who I'm following and who's following me",
"nsfw_clickthrough": "Enable clickthrough NSFW attachment hiding",
"panelRadius": "Panels",
"pause_on_unfocused": "Pause streaming when tab is not focused",
@ -322,6 +324,7 @@
"followers": "Followers",
"following": "Following!",
"follows_you": "Follows you!",
"its_you": "It's you!",
"mute": "Mute",
"muted": "Muted",
"per_day": "per day",
@ -341,5 +344,19 @@
"reply": "Reply",
"favorite": "Favorite",
"user_settings": "User Settings"
},
"upload":{
"error": {
"base": "Upload failed.",
"file_too_big": "File too big [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
"default": "Try again later"
},
"file_size_units": {
"B": "B",
"KiB": "KiB",
"MiB": "MiB",
"GiB": "GiB",
"TiB": "TiB"
}
}
}

View file

@ -19,6 +19,7 @@
"username": "Имя пользователя"
},
"nav": {
"back": "Назад",
"chat": "Локальный чат",
"mentions": "Упоминания",
"public_tl": "Публичная лента",
@ -126,6 +127,7 @@
"notification_visibility_mentions": "Упоминания",
"notification_visibility_repeats": "Повторы",
"no_rich_text_description": "Убрать форматирование из всех постов",
"hide_network_description": "Не показывать кого я читаю и кто меня читает",
"nsfw_clickthrough": "Включить скрытие NSFW вложений",
"panelRadius": "Панели",
"pause_on_unfocused": "Приостановить загрузку когда вкладка не в фокусе",

View file

@ -25,6 +25,8 @@ const defaultState = {
scopeCopy: true,
subjectLineBehavior: 'email',
loginMethod: 'password',
nsfwCensorImage: undefined,
vapidPublicKey: undefined,
// Nasty stuff
pleromaBackend: true,

View file

@ -17,6 +17,9 @@ export const mergeOrAdd = (arr, obj, item) => {
// This is a new item, prepare it
arr.push(item)
obj[item.id] = item
if (item.screen_name && !item.screen_name.includes('@')) {
obj[item.screen_name] = item
}
return { item, new: true }
}
}
@ -87,7 +90,7 @@ const users = {
actions: {
fetchUser (store, id) {
store.rootState.api.backendInteractor.fetchUser({ id })
.then((user) => store.commit('addNewUsers', user))
.then((user) => store.commit('addNewUsers', [user]))
},
registerPushNotifications (store) {
const token = store.state.currentUser.credentials

View file

@ -0,0 +1,17 @@
const fileSizeFormat = (num) => {
var exponent
var unit
var units = ['B', 'KiB', 'MiB', 'GiB', 'TiB']
if (num < 1) {
return num + ' ' + units[0]
}
exponent = Math.min(Math.floor(Math.log(num) / Math.log(1024)), units.length - 1)
num = (num / Math.pow(1024, exponent)).toFixed(2) * 1
unit = units[exponent]
return {num: num, unit: unit}
}
const fileSizeFormatService = {
fileSizeFormat
}
export default fileSizeFormatService

View file

@ -0,0 +1,34 @@
import fileSizeFormatService from '../../../../../src/services/file_size_format/file_size_format.js'
describe('fileSizeFormat', () => {
it('Formats file size', () => {
const values = [1, 1024, 1048576, 1073741824, 1099511627776]
const expected = [
{
num: 1,
unit: 'B'
},
{
num: 1,
unit: 'KiB'
},
{
num: 1,
unit: 'MiB'
},
{
num: 1,
unit: 'GiB'
},
{
num: 1,
unit: 'TiB'
}
]
var res = []
for (var value in values) {
res.push(fileSizeFormatService.fileSizeFormat(values[value]))
}
expect(res).to.eql(expected)
})
})