forked from AkkomaGang/akkoma-fe
Merge remote-tracking branch 'upstream/develop' into webpack-4-dart-sass
* upstream/develop: (116 commits) Fix small mistake in Polish translation link interaction avatars to the user profile Use more clear explanation in the scope notice, make sure the hide button doesn't overlap with text in notice. use backendInteractor refactor api service functions using new helper clean up update favorite number earlier update status interaction upon retweet action response sync up favoritedBy with favorite/unfavorite action do not regenerate status object reduce needless calculation Move scope visibility notice to the status form, make it dismissible Revert "eliminate expandable prop in favor of inConversation" status attention doesn’t have relationship entities make it short fix wrong inlineExpanded expanded is always false, eliminate it eliminate expandable prop in favor of inConversation fix conversationId comparision bug using integer format Display additional scope description above the status form for mobile users. ...
This commit is contained in:
commit
750dca4a10
50 changed files with 1195 additions and 372 deletions
|
@ -10,6 +10,7 @@ import MediaModal from './components/media_modal/media_modal.vue'
|
||||||
import SideDrawer from './components/side_drawer/side_drawer.vue'
|
import SideDrawer from './components/side_drawer/side_drawer.vue'
|
||||||
import MobilePostStatusModal from './components/mobile_post_status_modal/mobile_post_status_modal.vue'
|
import MobilePostStatusModal from './components/mobile_post_status_modal/mobile_post_status_modal.vue'
|
||||||
import MobileNav from './components/mobile_nav/mobile_nav.vue'
|
import MobileNav from './components/mobile_nav/mobile_nav.vue'
|
||||||
|
import UserReportingModal from './components/user_reporting_modal/user_reporting_modal.vue'
|
||||||
import { windowWidth } from './services/window_utils/window_utils'
|
import { windowWidth } from './services/window_utils/window_utils'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -26,7 +27,8 @@ export default {
|
||||||
MediaModal,
|
MediaModal,
|
||||||
SideDrawer,
|
SideDrawer,
|
||||||
MobilePostStatusModal,
|
MobilePostStatusModal,
|
||||||
MobileNav
|
MobileNav,
|
||||||
|
UserReportingModal
|
||||||
},
|
},
|
||||||
data: () => ({
|
data: () => ({
|
||||||
mobileActivePanel: 'timeline',
|
mobileActivePanel: 'timeline',
|
||||||
|
|
14
src/App.scss
14
src/App.scss
|
@ -379,6 +379,7 @@ main-router {
|
||||||
|
|
||||||
.panel-heading {
|
.panel-heading {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex: none;
|
||||||
border-radius: $fallback--panelRadius $fallback--panelRadius 0 0;
|
border-radius: $fallback--panelRadius $fallback--panelRadius 0 0;
|
||||||
border-radius: var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius) 0 0;
|
border-radius: var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius) 0 0;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
|
@ -647,6 +648,19 @@ nav {
|
||||||
border-radius: var(--inputRadius, $fallback--inputRadius);
|
border-radius: var(--inputRadius, $fallback--inputRadius);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.notice-dismissible {
|
||||||
|
padding-right: 4rem;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.dismiss {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
padding: .5em;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes modal-background-fadein {
|
@keyframes modal-background-fadein {
|
||||||
from {
|
from {
|
||||||
background-color: rgba(0, 0, 0, 0);
|
background-color: rgba(0, 0, 0, 0);
|
||||||
|
|
|
@ -18,12 +18,13 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
<div v-if="" class="container" id="content">
|
<div class="container" id="content">
|
||||||
<div class="sidebar-flexer mobile-hidden" v-if="!isMobileLayout">
|
<div class="sidebar-flexer mobile-hidden">
|
||||||
<div class="sidebar-bounds">
|
<div class="sidebar-bounds">
|
||||||
<div class="sidebar-scroller">
|
<div class="sidebar-scroller">
|
||||||
<div class="sidebar">
|
<div class="sidebar">
|
||||||
<user-panel></user-panel>
|
<user-panel></user-panel>
|
||||||
|
<div v-if="!isMobileLayout">
|
||||||
<nav-panel></nav-panel>
|
<nav-panel></nav-panel>
|
||||||
<instance-specific-panel v-if="showInstanceSpecificPanel"></instance-specific-panel>
|
<instance-specific-panel v-if="showInstanceSpecificPanel"></instance-specific-panel>
|
||||||
<features-panel v-if="!currentUser && showFeaturesPanel"></features-panel>
|
<features-panel v-if="!currentUser && showFeaturesPanel"></features-panel>
|
||||||
|
@ -33,6 +34,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div class="main">
|
<div class="main">
|
||||||
<div v-if="!currentUser" class="login-hint panel panel-default">
|
<div v-if="!currentUser" class="login-hint panel panel-default">
|
||||||
<router-link :to="{ name: 'login' }" class="panel-body">
|
<router-link :to="{ name: 'login' }" class="panel-body">
|
||||||
|
@ -46,6 +48,7 @@
|
||||||
<media-modal></media-modal>
|
<media-modal></media-modal>
|
||||||
</div>
|
</div>
|
||||||
<chat-panel :floating="true" v-if="currentUser && chat" class="floating-chat mobile-hidden"></chat-panel>
|
<chat-panel :floating="true" v-if="currentUser && chat" class="floating-chat mobile-hidden"></chat-panel>
|
||||||
|
<UserReportingModal />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
21
src/components/avatar_list/avatar_list.js
Normal file
21
src/components/avatar_list/avatar_list.js
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import UserAvatar from '../user_avatar/user_avatar.vue'
|
||||||
|
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
|
||||||
|
|
||||||
|
const AvatarList = {
|
||||||
|
props: ['users'],
|
||||||
|
computed: {
|
||||||
|
slicedUsers () {
|
||||||
|
return this.users ? this.users.slice(0, 15) : []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
UserAvatar
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
userProfileLink (user) {
|
||||||
|
return generateProfileLink(user.id, user.screen_name, this.$store.state.instance.restrictedNicknames)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AvatarList
|
38
src/components/avatar_list/avatar_list.vue
Normal file
38
src/components/avatar_list/avatar_list.vue
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
<template>
|
||||||
|
<div class="avatars">
|
||||||
|
<router-link :to="userProfileLink(user)" class="avatars-item" v-for="user in slicedUsers">
|
||||||
|
<UserAvatar :user="user" class="avatar-small" />
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./avatar_list.js" ></script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@import '../../_variables.scss';
|
||||||
|
|
||||||
|
.avatars {
|
||||||
|
display: flex;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
// For hiding overflowing elements
|
||||||
|
flex-wrap: wrap;
|
||||||
|
height: 24px;
|
||||||
|
|
||||||
|
.avatars-item {
|
||||||
|
margin: 0 0 5px 5px;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
padding-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-small {
|
||||||
|
border-radius: $fallback--avatarAltRadius;
|
||||||
|
border-radius: var(--avatarAltRadius, $fallback--avatarAltRadius);
|
||||||
|
height: 24px;
|
||||||
|
width: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,7 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="basic-user-card">
|
<div class="basic-user-card">
|
||||||
<router-link :to="userProfileLink(user)">
|
<router-link :to="userProfileLink(user)">
|
||||||
<UserAvatar class="avatar" @click.prevent.native="toggleUserExpanded" :src="user.profile_image_url"/>
|
<UserAvatar
|
||||||
|
class="avatar"
|
||||||
|
:user="user"
|
||||||
|
@click.prevent.native="toggleUserExpanded"
|
||||||
|
/>
|
||||||
</router-link>
|
</router-link>
|
||||||
<div class="basic-user-card-expanded-content" v-if="userExpanded">
|
<div class="basic-user-card-expanded-content" v-if="userExpanded">
|
||||||
<UserCard :user="user" :rounded="true" :bordered="true"/>
|
<UserCard :user="user" :rounded="true" :bordered="true"/>
|
||||||
|
@ -44,15 +48,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;
|
||||||
|
|
|
@ -139,6 +139,7 @@ const conversation = {
|
||||||
},
|
},
|
||||||
setHighlight (id) {
|
setHighlight (id) {
|
||||||
this.highlight = id
|
this.highlight = id
|
||||||
|
this.$store.dispatch('fetchFavsAndRepeats', id)
|
||||||
},
|
},
|
||||||
getHighlight () {
|
getHighlight () {
|
||||||
return this.isExpanded ? this.highlight : null
|
return this.isExpanded ? this.highlight : null
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
@goto="setHighlight"
|
@goto="setHighlight"
|
||||||
@toggleExpanded="toggleExpanded"
|
@toggleExpanded="toggleExpanded"
|
||||||
:key="status.id"
|
:key="status.id"
|
||||||
:inlineExpanded="collapsable"
|
:inlineExpanded="collapsable && isExpanded"
|
||||||
:statusoid="status"
|
:statusoid="status"
|
||||||
:expandable='!isExpanded'
|
:expandable='!isExpanded'
|
||||||
:focused="focused(status.id)"
|
:focused="focused(status.id)"
|
||||||
|
|
48
src/components/exporter/exporter.js
Normal file
48
src/components/exporter/exporter.js
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
const Exporter = {
|
||||||
|
props: {
|
||||||
|
getContent: {
|
||||||
|
type: Function,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
filename: {
|
||||||
|
type: String,
|
||||||
|
default: 'export.csv'
|
||||||
|
},
|
||||||
|
exportButtonLabel: {
|
||||||
|
type: String,
|
||||||
|
default () {
|
||||||
|
return this.$t('exporter.export')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
processingMessage: {
|
||||||
|
type: String,
|
||||||
|
default () {
|
||||||
|
return this.$t('exporter.processing')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
processing: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
process () {
|
||||||
|
this.processing = true
|
||||||
|
this.getContent()
|
||||||
|
.then((content) => {
|
||||||
|
const fileToDownload = document.createElement('a')
|
||||||
|
fileToDownload.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(content))
|
||||||
|
fileToDownload.setAttribute('download', this.filename)
|
||||||
|
fileToDownload.style.display = 'none'
|
||||||
|
document.body.appendChild(fileToDownload)
|
||||||
|
fileToDownload.click()
|
||||||
|
document.body.removeChild(fileToDownload)
|
||||||
|
// Add delay before hiding processing state since browser takes some time to handle file download
|
||||||
|
setTimeout(() => { this.processing = false }, 2000)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Exporter
|
20
src/components/exporter/exporter.vue
Normal file
20
src/components/exporter/exporter.vue
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<template>
|
||||||
|
<div class="exporter">
|
||||||
|
<div v-if="processing">
|
||||||
|
<i class="icon-spin4 animate-spin exporter-processing"></i>
|
||||||
|
<span>{{processingMessage}}</span>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-default" @click="process" v-else>{{exportButtonLabel}}</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./exporter.js"></script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.exporter {
|
||||||
|
&-processing {
|
||||||
|
font-size: 1.5em;
|
||||||
|
margin: 0.25em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -70,22 +70,10 @@ const ImageCropper = {
|
||||||
this.dataUrl = undefined
|
this.dataUrl = undefined
|
||||||
this.$emit('close')
|
this.$emit('close')
|
||||||
},
|
},
|
||||||
submit () {
|
submit (cropping = true) {
|
||||||
this.submitting = true
|
this.submitting = true
|
||||||
this.avatarUploadError = null
|
this.avatarUploadError = null
|
||||||
this.submitHandler(this.cropper, this.file)
|
this.submitHandler(cropping && this.cropper, this.file)
|
||||||
.then(() => this.destroy())
|
|
||||||
.catch((err) => {
|
|
||||||
this.submitError = err
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
this.submitting = false
|
|
||||||
})
|
|
||||||
},
|
|
||||||
submitWithoutCropping () {
|
|
||||||
this.submitting = true
|
|
||||||
this.avatarUploadError = null
|
|
||||||
this.submitHandler(false, this.dataUrl)
|
|
||||||
.then(() => this.destroy())
|
.then(() => this.destroy())
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.submitError = err
|
this.submitError = err
|
||||||
|
|
|
@ -5,9 +5,9 @@
|
||||||
<img ref="img" :src="dataUrl" alt="" @load.stop="createCropper" />
|
<img ref="img" :src="dataUrl" alt="" @load.stop="createCropper" />
|
||||||
</div>
|
</div>
|
||||||
<div class="image-cropper-buttons-wrapper">
|
<div class="image-cropper-buttons-wrapper">
|
||||||
<button class="btn" type="button" :disabled="submitting" @click="submit" v-text="saveText"></button>
|
<button class="btn" type="button" :disabled="submitting" @click="submit()" v-text="saveText"></button>
|
||||||
<button class="btn" type="button" :disabled="submitting" @click="destroy" v-text="cancelText"></button>
|
<button class="btn" type="button" :disabled="submitting" @click="destroy" v-text="cancelText"></button>
|
||||||
<button class="btn" type="button" :disabled="submitting" @click="submitWithoutCropping" v-text="saveWithoutCroppingText"></button>
|
<button class="btn" type="button" :disabled="submitting" @click="submit(false)" v-text="saveWithoutCroppingText"></button>
|
||||||
<i class="icon-spin4 animate-spin" v-if="submitting"></i>
|
<i class="icon-spin4 animate-spin" v-if="submitting"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="alert error" v-if="submitError">
|
<div class="alert error" v-if="submitError">
|
||||||
|
|
53
src/components/importer/importer.js
Normal file
53
src/components/importer/importer.js
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
const Importer = {
|
||||||
|
props: {
|
||||||
|
submitHandler: {
|
||||||
|
type: Function,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
submitButtonLabel: {
|
||||||
|
type: String,
|
||||||
|
default () {
|
||||||
|
return this.$t('importer.submit')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
successMessage: {
|
||||||
|
type: String,
|
||||||
|
default () {
|
||||||
|
return this.$t('importer.success')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
errorMessage: {
|
||||||
|
type: String,
|
||||||
|
default () {
|
||||||
|
return this.$t('importer.error')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
file: null,
|
||||||
|
error: false,
|
||||||
|
success: false,
|
||||||
|
submitting: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
change () {
|
||||||
|
this.file = this.$refs.input.files[0]
|
||||||
|
},
|
||||||
|
submit () {
|
||||||
|
this.dismiss()
|
||||||
|
this.submitting = true
|
||||||
|
this.submitHandler(this.file)
|
||||||
|
.then(() => { this.success = true })
|
||||||
|
.catch(() => { this.error = true })
|
||||||
|
.finally(() => { this.submitting = false })
|
||||||
|
},
|
||||||
|
dismiss () {
|
||||||
|
this.success = false
|
||||||
|
this.error = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Importer
|
28
src/components/importer/importer.vue
Normal file
28
src/components/importer/importer.vue
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
<template>
|
||||||
|
<div class="importer">
|
||||||
|
<form>
|
||||||
|
<input type="file" ref="input" v-on:change="change" />
|
||||||
|
</form>
|
||||||
|
<i class="icon-spin4 animate-spin importer-uploading" v-if="submitting"></i>
|
||||||
|
<button class="btn btn-default" v-else @click="submit">{{submitButtonLabel}}</button>
|
||||||
|
<div v-if="success">
|
||||||
|
<i class="icon-cross" @click="dismiss"></i>
|
||||||
|
<p>{{successMessage}}</p>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="error">
|
||||||
|
<i class="icon-cross" @click="dismiss"></i>
|
||||||
|
<p>{{errorMessage}}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./importer.js"></script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.importer {
|
||||||
|
&-uploading {
|
||||||
|
font-size: 1.5em;
|
||||||
|
margin: 0.25em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -33,6 +33,8 @@
|
||||||
@import '../../_variables.scss';
|
@import '../../_variables.scss';
|
||||||
|
|
||||||
.media-modal-view {
|
.media-modal-view {
|
||||||
|
z-index: 1001;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
.modal-view-button-arrow {
|
.modal-view-button-arrow {
|
||||||
opacity: 0.75;
|
opacity: 0.75;
|
||||||
|
|
|
@ -63,6 +63,11 @@ const MobileNav = {
|
||||||
},
|
},
|
||||||
markNotificationsAsSeen () {
|
markNotificationsAsSeen () {
|
||||||
this.$refs.notifications.markAsSeen()
|
this.$refs.notifications.markAsSeen()
|
||||||
|
},
|
||||||
|
onScroll ({ target: { scrollTop, clientHeight, scrollHeight } }) {
|
||||||
|
if (this.$store.state.config.autoLoad && scrollTop + clientHeight >= scrollHeight) {
|
||||||
|
this.$refs.notifications.fetchOlderNotifications()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
|
<div>
|
||||||
<nav class='nav-bar container' id="nav">
|
<nav class='nav-bar container' id="nav">
|
||||||
<div class='mobile-inner-nav' @click="scrollToTop()">
|
<div class='mobile-inner-nav' @click="scrollToTop()">
|
||||||
<div class='item'>
|
<div class='item'>
|
||||||
|
@ -14,12 +15,12 @@
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<SideDrawer ref="sideDrawer" :logout="logout"/>
|
</nav>
|
||||||
<div v-if="currentUser"
|
<div v-if="currentUser"
|
||||||
class="mobile-notifications-drawer"
|
class="mobile-notifications-drawer"
|
||||||
:class="{ 'closed': !notificationsOpen }"
|
:class="{ 'closed': !notificationsOpen }"
|
||||||
@touchstart="notificationsTouchStart"
|
@touchstart.stop="notificationsTouchStart"
|
||||||
@touchmove="notificationsTouchMove"
|
@touchmove.stop="notificationsTouchMove"
|
||||||
>
|
>
|
||||||
<div class="mobile-notifications-header">
|
<div class="mobile-notifications-header">
|
||||||
<span class="title">{{$t('notifications.notifications')}}</span>
|
<span class="title">{{$t('notifications.notifications')}}</span>
|
||||||
|
@ -27,12 +28,13 @@
|
||||||
<i class="button-icon icon-cancel"/>
|
<i class="button-icon icon-cancel"/>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="currentUser" class="mobile-notifications">
|
<div class="mobile-notifications" @scroll="onScroll">
|
||||||
<Notifications ref="notifications" noHeading="true"/>
|
<Notifications ref="notifications" noHeading="true"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<SideDrawer ref="sideDrawer" :logout="logout"/>
|
||||||
<MobilePostStatusModal />
|
<MobilePostStatusModal />
|
||||||
</nav>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script src="./mobile_nav.js"></script>
|
<script src="./mobile_nav.js"></script>
|
||||||
|
@ -79,6 +81,8 @@
|
||||||
transition-property: transform;
|
transition-property: transform;
|
||||||
transition-duration: 0.25s;
|
transition-duration: 0.25s;
|
||||||
transform: translateX(0);
|
transform: translateX(0);
|
||||||
|
z-index: 1001;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
|
||||||
&.closed {
|
&.closed {
|
||||||
transform: translateX(100%);
|
transform: translateX(100%);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import PostStatusForm from '../post_status_form/post_status_form.vue'
|
import PostStatusForm from '../post_status_form/post_status_form.vue'
|
||||||
import { throttle } from 'lodash'
|
import { debounce } from 'lodash'
|
||||||
|
|
||||||
const MobilePostStatusModal = {
|
const MobilePostStatusModal = {
|
||||||
components: {
|
components: {
|
||||||
|
@ -16,11 +16,15 @@ const MobilePostStatusModal = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created () {
|
created () {
|
||||||
window.addEventListener('scroll', this.handleScroll)
|
if (this.autohideFloatingPostButton) {
|
||||||
|
this.activateFloatingPostButtonAutohide()
|
||||||
|
}
|
||||||
window.addEventListener('resize', this.handleOSK)
|
window.addEventListener('resize', this.handleOSK)
|
||||||
},
|
},
|
||||||
destroyed () {
|
destroyed () {
|
||||||
window.removeEventListener('scroll', this.handleScroll)
|
if (this.autohideFloatingPostButton) {
|
||||||
|
this.deactivateFloatingPostButtonAutohide()
|
||||||
|
}
|
||||||
window.removeEventListener('resize', this.handleOSK)
|
window.removeEventListener('resize', this.handleOSK)
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -28,10 +32,30 @@ const MobilePostStatusModal = {
|
||||||
return this.$store.state.users.currentUser
|
return this.$store.state.users.currentUser
|
||||||
},
|
},
|
||||||
isHidden () {
|
isHidden () {
|
||||||
return this.hidden || this.inputActive
|
return this.autohideFloatingPostButton && (this.hidden || this.inputActive)
|
||||||
|
},
|
||||||
|
autohideFloatingPostButton () {
|
||||||
|
return !!this.$store.state.config.autohideFloatingPostButton
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
autohideFloatingPostButton: function (isEnabled) {
|
||||||
|
if (isEnabled) {
|
||||||
|
this.activateFloatingPostButtonAutohide()
|
||||||
|
} else {
|
||||||
|
this.deactivateFloatingPostButtonAutohide()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
activateFloatingPostButtonAutohide () {
|
||||||
|
window.addEventListener('scroll', this.handleScrollStart)
|
||||||
|
window.addEventListener('scroll', this.handleScrollEnd)
|
||||||
|
},
|
||||||
|
deactivateFloatingPostButtonAutohide () {
|
||||||
|
window.removeEventListener('scroll', this.handleScrollStart)
|
||||||
|
window.removeEventListener('scroll', this.handleScrollEnd)
|
||||||
|
},
|
||||||
openPostForm () {
|
openPostForm () {
|
||||||
this.postFormOpen = true
|
this.postFormOpen = true
|
||||||
this.hidden = true
|
this.hidden = true
|
||||||
|
@ -65,26 +89,19 @@ const MobilePostStatusModal = {
|
||||||
this.inputActive = false
|
this.inputActive = false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handleScroll: throttle(function () {
|
handleScrollStart: debounce(function () {
|
||||||
const scrollAmount = window.scrollY - this.oldScrollPos
|
if (window.scrollY > this.oldScrollPos) {
|
||||||
const scrollingDown = scrollAmount > 0
|
this.hidden = true
|
||||||
|
} else {
|
||||||
if (scrollingDown !== this.scrollingDown) {
|
|
||||||
this.amountScrolled = 0
|
|
||||||
this.scrollingDown = scrollingDown
|
|
||||||
if (!scrollingDown) {
|
|
||||||
this.hidden = false
|
this.hidden = false
|
||||||
}
|
}
|
||||||
} else if (scrollingDown) {
|
|
||||||
this.amountScrolled += scrollAmount
|
|
||||||
if (this.amountScrolled > 100 && !this.hidden) {
|
|
||||||
this.hidden = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.oldScrollPos = window.scrollY
|
this.oldScrollPos = window.scrollY
|
||||||
this.scrollingDown = scrollingDown
|
}, 100, {leading: true, trailing: false}),
|
||||||
}, 100)
|
|
||||||
|
handleScrollEnd: debounce(function () {
|
||||||
|
this.hidden = false
|
||||||
|
this.oldScrollPos = window.scrollY
|
||||||
|
}, 100, {leading: false, trailing: true})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
>
|
>
|
||||||
<div class="post-form-modal-panel panel" @click.stop="">
|
<div class="post-form-modal-panel panel" @click.stop="">
|
||||||
<div class="panel-heading">{{$t('post_status.new_status')}}</div>
|
<div class="panel-heading">{{$t('post_status.new_status')}}</div>
|
||||||
<PostStatusForm class="panel-body" @posted="closePostForm"/>
|
<PostStatusForm class="panel-body" @posted="closePostForm" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
</status>
|
</status>
|
||||||
<div class="non-mention" :class="[userClass, { highlighted: userStyle }]" :style="[ userStyle ]" v-else>
|
<div class="non-mention" :class="[userClass, { highlighted: userStyle }]" :style="[ userStyle ]" v-else>
|
||||||
<a class='avatar-container' :href="notification.from_profile.statusnet_profile_url" @click.stop.prevent.capture="toggleUserExpanded">
|
<a class='avatar-container' :href="notification.from_profile.statusnet_profile_url" @click.stop.prevent.capture="toggleUserExpanded">
|
||||||
<UserAvatar :compact="true" :betterShadow="betterShadow" :src="notification.from_profile.profile_image_url_original" />
|
<UserAvatar :compact="true" :betterShadow="betterShadow" :user="notification.from_profile"/>
|
||||||
</a>
|
</a>
|
||||||
<div class='notification-right'>
|
<div class='notification-right'>
|
||||||
<UserCard :user="getUser(notification)" :rounded="true" :bordered="true" v-if="userExpanded" />
|
<UserCard :user="getUser(notification)" :rounded="true" :bordered="true" v-if="userExpanded" />
|
||||||
|
|
|
@ -52,6 +52,10 @@ const Notifications = {
|
||||||
this.$store.dispatch('markNotificationsAsSeen')
|
this.$store.dispatch('markNotificationsAsSeen')
|
||||||
},
|
},
|
||||||
fetchOlderNotifications () {
|
fetchOlderNotifications () {
|
||||||
|
if (this.loading) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const store = this.$store
|
const store = this.$store
|
||||||
const credentials = store.state.users.currentUser.credentials
|
const credentials = store.state.users.currentUser.credentials
|
||||||
store.commit('setNotificationsLoading', { value: true })
|
store.commit('setNotificationsLoading', { value: true })
|
||||||
|
|
|
@ -182,6 +182,9 @@ const PostStatusForm = {
|
||||||
},
|
},
|
||||||
safeDMEnabled () {
|
safeDMEnabled () {
|
||||||
return this.$store.state.instance.safeDM
|
return this.$store.state.instance.safeDM
|
||||||
|
},
|
||||||
|
hideScopeNotice () {
|
||||||
|
return this.$store.state.config.hideScopeNotice
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -338,6 +341,9 @@ const PostStatusForm = {
|
||||||
},
|
},
|
||||||
changeVis (visibility) {
|
changeVis (visibility) {
|
||||||
this.newStatus.visibility = visibility
|
this.newStatus.visibility = visibility
|
||||||
|
},
|
||||||
|
dismissScopeNotice () {
|
||||||
|
this.$store.dispatch('setOption', { name: 'hideScopeNotice', value: true })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,25 @@
|
||||||
class="visibility-notice">
|
class="visibility-notice">
|
||||||
<router-link :to="{ name: 'user-settings' }">{{ $t('post_status.account_not_locked_warning_link') }}</router-link>
|
<router-link :to="{ name: 'user-settings' }">{{ $t('post_status.account_not_locked_warning_link') }}</router-link>
|
||||||
</i18n>
|
</i18n>
|
||||||
<p v-if="newStatus.visibility === 'direct'" class="visibility-notice">
|
<p v-if="!hideScopeNotice && newStatus.visibility === 'public'" class="visibility-notice notice-dismissible">
|
||||||
|
<span>{{ $t('post_status.scope_notice.public') }}</span>
|
||||||
|
<a v-on:click.prevent="dismissScopeNotice()" class="button-icon dismiss">
|
||||||
|
<i class='icon-cancel'></i>
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<p v-else-if="!hideScopeNotice && newStatus.visibility === 'unlisted'" class="visibility-notice notice-dismissible">
|
||||||
|
<span>{{ $t('post_status.scope_notice.unlisted') }}</span>
|
||||||
|
<a v-on:click.prevent="dismissScopeNotice()" class="button-icon dismiss">
|
||||||
|
<i class='icon-cancel'></i>
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<p v-else-if="!hideScopeNotice && newStatus.visibility === 'private' && $store.state.users.currentUser.locked" class="visibility-notice notice-dismissible">
|
||||||
|
<span>{{ $t('post_status.scope_notice.private') }}</span>
|
||||||
|
<a v-on:click.prevent="dismissScopeNotice()" class="button-icon dismiss">
|
||||||
|
<i class='icon-cancel'></i>
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<p v-else-if="newStatus.visibility === 'direct'" class="visibility-notice">
|
||||||
<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>
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -46,6 +46,7 @@ const settings = {
|
||||||
streamingLocal: user.streaming,
|
streamingLocal: user.streaming,
|
||||||
pauseOnUnfocusedLocal: user.pauseOnUnfocused,
|
pauseOnUnfocusedLocal: user.pauseOnUnfocused,
|
||||||
hoverPreviewLocal: user.hoverPreview,
|
hoverPreviewLocal: user.hoverPreview,
|
||||||
|
autohideFloatingPostButtonLocal: user.autohideFloatingPostButton,
|
||||||
|
|
||||||
hideMutedPostsLocal: typeof user.hideMutedPosts === 'undefined'
|
hideMutedPostsLocal: typeof user.hideMutedPosts === 'undefined'
|
||||||
? instance.hideMutedPosts
|
? instance.hideMutedPosts
|
||||||
|
@ -183,6 +184,9 @@ const settings = {
|
||||||
hoverPreviewLocal (value) {
|
hoverPreviewLocal (value) {
|
||||||
this.$store.dispatch('setOption', { name: 'hoverPreview', value })
|
this.$store.dispatch('setOption', { name: 'hoverPreview', value })
|
||||||
},
|
},
|
||||||
|
autohideFloatingPostButtonLocal (value) {
|
||||||
|
this.$store.dispatch('setOption', { name: 'autohideFloatingPostButton', value })
|
||||||
|
},
|
||||||
muteWordsString (value) {
|
muteWordsString (value) {
|
||||||
value = filter(value.split('\n'), (word) => trim(word).length > 0)
|
value = filter(value.split('\n'), (word) => trim(word).length > 0)
|
||||||
this.$store.dispatch('setOption', { name: 'muteWords', value })
|
this.$store.dispatch('setOption', { name: 'muteWords', value })
|
||||||
|
|
|
@ -122,6 +122,10 @@
|
||||||
{{$t('settings.minimal_scopes_mode')}} {{$t('settings.instance_default', { value: minimalScopesModeDefault })}}
|
{{$t('settings.minimal_scopes_mode')}} {{$t('settings.instance_default', { value: minimalScopesModeDefault })}}
|
||||||
</label>
|
</label>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<input type="checkbox" id="autohideFloatingPostButton" v-model="autohideFloatingPostButtonLocal">
|
||||||
|
<label for="autohideFloatingPostButton">{{$t('settings.autohide_floating_post_button')}}</label>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -7,11 +7,12 @@ import UserCard from '../user_card/user_card.vue'
|
||||||
import UserAvatar from '../user_avatar/user_avatar.vue'
|
import UserAvatar from '../user_avatar/user_avatar.vue'
|
||||||
import Gallery from '../gallery/gallery.vue'
|
import Gallery from '../gallery/gallery.vue'
|
||||||
import LinkPreview from '../link-preview/link-preview.vue'
|
import LinkPreview from '../link-preview/link-preview.vue'
|
||||||
|
import AvatarList from '../avatar_list/avatar_list.vue'
|
||||||
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
|
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
|
||||||
import fileType from 'src/services/file_type/file_type.service'
|
import fileType from 'src/services/file_type/file_type.service'
|
||||||
import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js'
|
import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js'
|
||||||
import { mentionMatchesUrl, extractTagFromUrl } from 'src/services/matcher/matcher.service.js'
|
import { mentionMatchesUrl, extractTagFromUrl } from 'src/services/matcher/matcher.service.js'
|
||||||
import { filter, find, unescape } from 'lodash'
|
import { filter, find, unescape, uniqBy } from 'lodash'
|
||||||
|
|
||||||
const Status = {
|
const Status = {
|
||||||
name: 'Status',
|
name: 'Status',
|
||||||
|
@ -30,7 +31,6 @@ const Status = {
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
replying: false,
|
replying: false,
|
||||||
expanded: false,
|
|
||||||
unmuted: false,
|
unmuted: false,
|
||||||
userExpanded: false,
|
userExpanded: false,
|
||||||
preview: null,
|
preview: null,
|
||||||
|
@ -97,6 +97,10 @@ const Status = {
|
||||||
return this.statusoid
|
return this.statusoid
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
statusFromGlobalRepository () {
|
||||||
|
// NOTE: Consider to replace status with statusFromGlobalRepository
|
||||||
|
return this.$store.state.statuses.allStatusesObject[this.status.id]
|
||||||
|
},
|
||||||
loggedIn () {
|
loggedIn () {
|
||||||
return !!this.$store.state.users.currentUser
|
return !!this.$store.state.users.currentUser
|
||||||
},
|
},
|
||||||
|
@ -156,7 +160,7 @@ const Status = {
|
||||||
if (this.$store.state.config.replyVisibility === 'all') {
|
if (this.$store.state.config.replyVisibility === 'all') {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (this.inlineExpanded || this.expanded || this.inConversation || !this.isReply) {
|
if (this.inConversation || !this.isReply) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (this.status.user.id === this.$store.state.users.currentUser.id) {
|
if (this.status.user.id === this.$store.state.users.currentUser.id) {
|
||||||
|
@ -170,7 +174,7 @@ const Status = {
|
||||||
if (this.status.user.id === this.status.attentions[i].id) {
|
if (this.status.user.id === this.status.attentions[i].id) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if (checkFollowing && this.status.attentions[i].following) {
|
if (checkFollowing && this.$store.getters.findUser(this.status.attentions[i].id).following) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (this.status.attentions[i].id === this.$store.state.users.currentUser.id) {
|
if (this.status.attentions[i].id === this.$store.state.users.currentUser.id) {
|
||||||
|
@ -257,6 +261,14 @@ const Status = {
|
||||||
return this.status.statusnet_html
|
return this.status.statusnet_html
|
||||||
}
|
}
|
||||||
return this.status.summary_html + '<br />' + this.status.statusnet_html
|
return this.status.summary_html + '<br />' + this.status.statusnet_html
|
||||||
|
},
|
||||||
|
combinedFavsAndRepeatsUsers () {
|
||||||
|
// Use the status from the global status repository since favs and repeats are saved in it
|
||||||
|
const combinedUsers = [].concat(
|
||||||
|
this.statusFromGlobalRepository.favoritedBy,
|
||||||
|
this.statusFromGlobalRepository.rebloggedBy
|
||||||
|
)
|
||||||
|
return uniqBy(combinedUsers, 'id')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
|
@ -268,7 +280,8 @@ const Status = {
|
||||||
UserCard,
|
UserCard,
|
||||||
UserAvatar,
|
UserAvatar,
|
||||||
Gallery,
|
Gallery,
|
||||||
LinkPreview
|
LinkPreview,
|
||||||
|
AvatarList
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
visibilityIcon (visibility) {
|
visibilityIcon (visibility) {
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div v-if="retweet && !noHeading && !inConversation" :class="[repeaterClass, { highlighted: repeaterStyle }]" :style="[repeaterStyle]" class="media container retweet-info">
|
<div v-if="retweet && !noHeading && !inConversation" :class="[repeaterClass, { highlighted: repeaterStyle }]" :style="[repeaterStyle]" class="media container retweet-info">
|
||||||
<UserAvatar class="media-left" v-if="retweet" :betterShadow="betterShadow" :src="statusoid.user.profile_image_url_original"/>
|
<UserAvatar class="media-left" v-if="retweet" :betterShadow="betterShadow" :user="statusoid.user"/>
|
||||||
<div class="media-body faint">
|
<div class="media-body faint">
|
||||||
<span class="user-name">
|
<span class="user-name">
|
||||||
<router-link v-if="retweeterHtml" :to="retweeterProfileLink" v-html="retweeterHtml"/>
|
<router-link v-if="retweeterHtml" :to="retweeterProfileLink" v-html="retweeterHtml"/>
|
||||||
|
@ -27,7 +27,7 @@
|
||||||
<div :class="[userClass, { highlighted: userStyle, 'is-retweet': retweet && !inConversation }]" :style="[ userStyle ]" class="media status">
|
<div :class="[userClass, { highlighted: userStyle, 'is-retweet': retweet && !inConversation }]" :style="[ userStyle ]" class="media status">
|
||||||
<div v-if="!noHeading" class="media-left">
|
<div v-if="!noHeading" class="media-left">
|
||||||
<router-link :to="userProfileLink" @click.stop.prevent.capture.native="toggleUserExpanded">
|
<router-link :to="userProfileLink" @click.stop.prevent.capture.native="toggleUserExpanded">
|
||||||
<UserAvatar :compact="compact" :betterShadow="betterShadow" :src="status.user.profile_image_url_original"/>
|
<UserAvatar :compact="compact" :betterShadow="betterShadow" :user="status.user"/>
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
<div class="status-body">
|
<div class="status-body">
|
||||||
|
@ -91,8 +91,13 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="showPreview" class="status-preview-container">
|
<div v-if="showPreview" class="status-preview-container">
|
||||||
<status class="status-preview" v-if="preview" :isPreview="true" :statusoid="preview" :compact=true></status>
|
<status class="status-preview"
|
||||||
<div class="status-preview status-preview-loading" v-else>
|
v-if="preview"
|
||||||
|
:isPreview="true"
|
||||||
|
:statusoid="preview"
|
||||||
|
:compact=true
|
||||||
|
/>
|
||||||
|
<div v-else class="status-preview status-preview-loading">
|
||||||
<i class="icon-spin4 animate-spin"></i>
|
<i class="icon-spin4 animate-spin"></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -133,6 +138,24 @@
|
||||||
<link-preview :card="status.card" :size="attachmentSize" :nsfw="nsfwClickthrough" />
|
<link-preview :card="status.card" :size="attachmentSize" :nsfw="nsfwClickthrough" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<transition name="fade">
|
||||||
|
<div class="favs-repeated-users" v-if="isFocused && combinedFavsAndRepeatsUsers.length > 0">
|
||||||
|
<div class="stats">
|
||||||
|
<div class="stat-count" v-if="statusFromGlobalRepository.rebloggedBy && statusFromGlobalRepository.rebloggedBy.length > 0">
|
||||||
|
<a class="stat-title">{{ $t('status.repeats') }}</a>
|
||||||
|
<div class="stat-number">{{ statusFromGlobalRepository.rebloggedBy.length }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-count" v-if="statusFromGlobalRepository.favoritedBy && statusFromGlobalRepository.favoritedBy.length > 0">
|
||||||
|
<a class="stat-title">{{ $t('status.favorites') }}</a>
|
||||||
|
<div class="stat-number">{{ statusFromGlobalRepository.favoritedBy.length }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="avatar-row">
|
||||||
|
<AvatarList :users="combinedFavsAndRepeatsUsers"></AvatarList>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
|
||||||
<div v-if="!noHeading && !isPreview" class='status-actions media-body'>
|
<div v-if="!noHeading && !isPreview" class='status-actions media-body'>
|
||||||
<div v-if="loggedIn">
|
<div v-if="loggedIn">
|
||||||
<i class="button-icon icon-reply" v-on:click.prevent="toggleReplying" :title="$t('tool_tip.reply')" :class="{'icon-reply-active': replying}"></i>
|
<i class="button-icon icon-reply" v-on:click.prevent="toggleReplying" :title="$t('tool_tip.reply')" :class="{'icon-reply-active': replying}"></i>
|
||||||
|
@ -604,6 +627,58 @@ a.unmute {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.timeline > {
|
||||||
|
.status-el:last-child {
|
||||||
|
border-radius: 0 0 $fallback--panelRadius $fallback--panelRadius;
|
||||||
|
border-radius: 0 0 var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius);
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.favs-repeated-users {
|
||||||
|
margin-top: $status-margin;
|
||||||
|
|
||||||
|
.stats {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
line-height: 1em;
|
||||||
|
|
||||||
|
.stat-count {
|
||||||
|
margin-right: $status-margin;
|
||||||
|
|
||||||
|
.stat-title {
|
||||||
|
color: var(--faint, $fallback--faint);
|
||||||
|
font-size: 12px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-number {
|
||||||
|
font-weight: bolder;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-row {
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
width: 1px;
|
||||||
|
left: 0;
|
||||||
|
background-color: var(--faint, $fallback--faint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media all and (max-width: 800px) {
|
@media all and (max-width: 800px) {
|
||||||
.status-el {
|
.status-el {
|
||||||
.retweet-info {
|
.retweet-info {
|
||||||
|
|
|
@ -2,7 +2,7 @@ import StillImage from '../still-image/still-image.vue'
|
||||||
|
|
||||||
const UserAvatar = {
|
const UserAvatar = {
|
||||||
props: [
|
props: [
|
||||||
'src',
|
'user',
|
||||||
'betterShadow',
|
'betterShadow',
|
||||||
'compact'
|
'compact'
|
||||||
],
|
],
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
<StillImage
|
<StillImage
|
||||||
class="avatar"
|
class="avatar"
|
||||||
|
:alt="user.screen_name"
|
||||||
|
:title="user.screen_name"
|
||||||
|
:src="user.profile_image_url_original"
|
||||||
:class="{ 'avatar-compact': compact, 'better-shadow': betterShadow }"
|
:class="{ 'avatar-compact': compact, 'better-shadow': betterShadow }"
|
||||||
:src="imgSrc"
|
|
||||||
:imageLoadError="imageLoadError"
|
:imageLoadError="imageLoadError"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -151,6 +151,9 @@ export default {
|
||||||
},
|
},
|
||||||
userProfileLink (user) {
|
userProfileLink (user) {
|
||||||
return generateProfileLink(user.id, user.screen_name, this.$store.state.instance.restrictedNicknames)
|
return generateProfileLink(user.id, user.screen_name, this.$store.state.instance.restrictedNicknames)
|
||||||
|
},
|
||||||
|
reportUser () {
|
||||||
|
this.$store.dispatch('openUserReportingModal', this.user.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<div class='user-info'>
|
<div class='user-info'>
|
||||||
<div class='container'>
|
<div class='container'>
|
||||||
<router-link :to="userProfileLink(user)">
|
<router-link :to="userProfileLink(user)">
|
||||||
<UserAvatar :betterShadow="betterShadow" :src="user.profile_image_url_original"/>
|
<UserAvatar :betterShadow="betterShadow" :user="user"/>
|
||||||
</router-link>
|
</router-link>
|
||||||
<div class="name-and-screen-name">
|
<div class="name-and-screen-name">
|
||||||
<div class="top-line">
|
<div class="top-line">
|
||||||
|
@ -99,8 +99,14 @@
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<ModerationTools :user='user' v-if='loggedIn.role === "admin"'>
|
<div class='block' v-if='isOtherUser && loggedIn'>
|
||||||
</ModerationTools>
|
<span>
|
||||||
|
<button @click="reportUser">
|
||||||
|
{{ $t('user_card.report') }}
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<ModerationTools :user='user' v-if='loggedIn.role === "admin"'/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
106
src/components/user_reporting_modal/user_reporting_modal.js
Normal file
106
src/components/user_reporting_modal/user_reporting_modal.js
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
|
||||||
|
import Status from '../status/status.vue'
|
||||||
|
import List from '../list/list.vue'
|
||||||
|
import Checkbox from '../checkbox/checkbox.vue'
|
||||||
|
|
||||||
|
const UserReportingModal = {
|
||||||
|
components: {
|
||||||
|
Status,
|
||||||
|
List,
|
||||||
|
Checkbox
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
comment: '',
|
||||||
|
forward: false,
|
||||||
|
statusIdsToReport: [],
|
||||||
|
processing: false,
|
||||||
|
error: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isLoggedIn () {
|
||||||
|
return !!this.$store.state.users.currentUser
|
||||||
|
},
|
||||||
|
isOpen () {
|
||||||
|
return this.isLoggedIn && this.$store.state.reports.modalActivated
|
||||||
|
},
|
||||||
|
userId () {
|
||||||
|
return this.$store.state.reports.userId
|
||||||
|
},
|
||||||
|
user () {
|
||||||
|
return this.$store.getters.findUser(this.userId)
|
||||||
|
},
|
||||||
|
remoteInstance () {
|
||||||
|
return !this.user.is_local && this.user.screen_name.substr(this.user.screen_name.indexOf('@') + 1)
|
||||||
|
},
|
||||||
|
statuses () {
|
||||||
|
return this.$store.state.reports.statuses
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
userId: 'resetState'
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
resetState () {
|
||||||
|
// Reset state
|
||||||
|
this.comment = ''
|
||||||
|
this.forward = false
|
||||||
|
this.statusIdsToReport = []
|
||||||
|
this.processing = false
|
||||||
|
this.error = false
|
||||||
|
},
|
||||||
|
closeModal () {
|
||||||
|
this.$store.dispatch('closeUserReportingModal')
|
||||||
|
},
|
||||||
|
reportUser () {
|
||||||
|
this.processing = true
|
||||||
|
this.error = false
|
||||||
|
const params = {
|
||||||
|
userId: this.userId,
|
||||||
|
comment: this.comment,
|
||||||
|
forward: this.forward,
|
||||||
|
statusIds: this.statusIdsToReport
|
||||||
|
}
|
||||||
|
this.$store.state.api.backendInteractor.reportUser(params)
|
||||||
|
.then(() => {
|
||||||
|
this.processing = false
|
||||||
|
this.resetState()
|
||||||
|
this.closeModal()
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
this.processing = false
|
||||||
|
this.error = true
|
||||||
|
})
|
||||||
|
},
|
||||||
|
clearError () {
|
||||||
|
this.error = false
|
||||||
|
},
|
||||||
|
isChecked (statusId) {
|
||||||
|
return this.statusIdsToReport.indexOf(statusId) !== -1
|
||||||
|
},
|
||||||
|
toggleStatus (checked, statusId) {
|
||||||
|
if (checked === this.isChecked(statusId)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (checked) {
|
||||||
|
this.statusIdsToReport.push(statusId)
|
||||||
|
} else {
|
||||||
|
this.statusIdsToReport.splice(this.statusIdsToReport.indexOf(statusId), 1)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
resize (e) {
|
||||||
|
const target = e.target || e
|
||||||
|
if (!(target instanceof window.Element)) { return }
|
||||||
|
// Auto is needed to make textbox shrink when removing lines
|
||||||
|
target.style.height = 'auto'
|
||||||
|
target.style.height = `${target.scrollHeight}px`
|
||||||
|
if (target.value === '') {
|
||||||
|
target.style.height = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UserReportingModal
|
157
src/components/user_reporting_modal/user_reporting_modal.vue
Normal file
157
src/components/user_reporting_modal/user_reporting_modal.vue
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
<template>
|
||||||
|
<div class="modal-view" @click="closeModal" v-if="isOpen">
|
||||||
|
<div class="user-reporting-panel panel" @click.stop="">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<div class="title">{{$t('user_reporting.title', [user.screen_name])}}</div>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<div class="user-reporting-panel-left">
|
||||||
|
<div>
|
||||||
|
<p>{{$t('user_reporting.add_comment_description')}}</p>
|
||||||
|
<textarea
|
||||||
|
v-model="comment"
|
||||||
|
class="form-control"
|
||||||
|
:placeholder="$t('user_reporting.additional_comments')"
|
||||||
|
rows="1"
|
||||||
|
@input="resize"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-if="!user.is_local">
|
||||||
|
<p>{{$t('user_reporting.forward_description')}}</p>
|
||||||
|
<Checkbox v-model="forward">{{$t('user_reporting.forward_to', [remoteInstance])}}</Checkbox>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button class="btn btn-default" @click="reportUser" :disabled="processing">{{$t('user_reporting.submit')}}</button>
|
||||||
|
<div class="alert error" v-if="error">
|
||||||
|
{{$t('user_reporting.generic_error')}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="user-reporting-panel-right">
|
||||||
|
<List :items="statuses">
|
||||||
|
<template slot="item" slot-scope="{item}">
|
||||||
|
<div class="status-fadein user-reporting-panel-sitem">
|
||||||
|
<Status :inConversation="false" :focused="false" :statusoid="item" />
|
||||||
|
<Checkbox :checked="isChecked(item.id)" @change="checked => toggleStatus(checked, item.id)" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</List>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./user_reporting_modal.js"></script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@import '../../_variables.scss';
|
||||||
|
|
||||||
|
.user-reporting-panel {
|
||||||
|
width: 90vw;
|
||||||
|
max-width: 700px;
|
||||||
|
min-height: 20vh;
|
||||||
|
max-height: 80vh;
|
||||||
|
|
||||||
|
.panel-heading {
|
||||||
|
.title {
|
||||||
|
text-align: center;
|
||||||
|
// TODO: Consider making these as default of panel
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column-reverse;
|
||||||
|
border-top: 1px solid;
|
||||||
|
border-color: $fallback--border;
|
||||||
|
border-color: var(--border, $fallback--border);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-left {
|
||||||
|
padding: 1.1em 0.7em 0.7em;
|
||||||
|
line-height: 1.4em;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
margin-bottom: 1em;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea.form-control {
|
||||||
|
line-height: 16px;
|
||||||
|
resize: none;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: min-height 200ms 100ms;
|
||||||
|
min-height: 44px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
min-width: 10em;
|
||||||
|
padding: 0 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert {
|
||||||
|
margin: 1em 0 0 0;
|
||||||
|
line-height: 1.3em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-right {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-sitem {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
> .status-el {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .checkbox {
|
||||||
|
margin: 0.75em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media all and (min-width: 801px) {
|
||||||
|
.panel-body {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-left {
|
||||||
|
width: 50%;
|
||||||
|
max-width: 320px;
|
||||||
|
border-right: 1px solid;
|
||||||
|
border-color: $fallback--border;
|
||||||
|
border-color: var(--border, $fallback--border);
|
||||||
|
padding: 1.1em;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
margin-bottom: 2em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-right {
|
||||||
|
width: 50%;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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",
|
||||||
|
@ -85,6 +94,11 @@
|
||||||
"direct_warning_to_all": "This post will be visible to all the mentioned users.",
|
"direct_warning_to_all": "This post will be visible to all the mentioned users.",
|
||||||
"direct_warning_to_first_only": "This post will only be visible to the mentioned users at the beginning of the message.",
|
"direct_warning_to_first_only": "This post will only be visible to the mentioned users at the beginning of the message.",
|
||||||
"posting": "Posting",
|
"posting": "Posting",
|
||||||
|
"scope_notice": {
|
||||||
|
"public": "This post will be visible to everyone",
|
||||||
|
"private": "This post will be visible to your followers only",
|
||||||
|
"unlisted": "This post will not be visible in Public Timeline and The Whole Known Network"
|
||||||
|
},
|
||||||
"scope": {
|
"scope": {
|
||||||
"direct": "Direct - Post to mentioned users only",
|
"direct": "Direct - Post to mentioned users only",
|
||||||
"private": "Followers-only - Post to followers only",
|
"private": "Followers-only - Post to followers only",
|
||||||
|
@ -126,6 +140,11 @@
|
||||||
"avatarRadius": "Avatars",
|
"avatarRadius": "Avatars",
|
||||||
"background": "Background",
|
"background": "Background",
|
||||||
"bio": "Bio",
|
"bio": "Bio",
|
||||||
|
"block_export": "Block export",
|
||||||
|
"block_export_button": "Export your blocks to a csv file",
|
||||||
|
"block_import": "Block import",
|
||||||
|
"block_import_error": "Error importing blocks",
|
||||||
|
"blocks_imported": "Blocks imported! Processing them will take a while.",
|
||||||
"blocks_tab": "Blocks",
|
"blocks_tab": "Blocks",
|
||||||
"btnRadius": "Buttons",
|
"btnRadius": "Buttons",
|
||||||
"cBlue": "Blue (Reply, follow)",
|
"cBlue": "Blue (Reply, follow)",
|
||||||
|
@ -153,7 +172,6 @@
|
||||||
"filtering_explanation": "All statuses containing these words will be muted, one per line",
|
"filtering_explanation": "All statuses containing these words will be muted, one per line",
|
||||||
"follow_export": "Follow export",
|
"follow_export": "Follow export",
|
||||||
"follow_export_button": "Export your follows to a csv file",
|
"follow_export_button": "Export your follows to a csv file",
|
||||||
"follow_export_processing": "Processing, you'll soon be asked to download your file",
|
|
||||||
"follow_import": "Follow import",
|
"follow_import": "Follow import",
|
||||||
"follow_import_error": "Error importing followers",
|
"follow_import_error": "Error importing followers",
|
||||||
"follows_imported": "Follows imported! Processing them will take a while.",
|
"follows_imported": "Follows imported! Processing them will take a while.",
|
||||||
|
@ -169,6 +187,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",
|
||||||
|
@ -219,6 +238,7 @@
|
||||||
"reply_visibility_all": "Show all replies",
|
"reply_visibility_all": "Show all replies",
|
||||||
"reply_visibility_following": "Only show replies directed at me or users I'm following",
|
"reply_visibility_following": "Only show replies directed at me or users I'm following",
|
||||||
"reply_visibility_self": "Only show replies directed at me",
|
"reply_visibility_self": "Only show replies directed at me",
|
||||||
|
"autohide_floating_post_button": "Automatically hide New Post button (mobile)",
|
||||||
"saving_err": "Error saving settings",
|
"saving_err": "Error saving settings",
|
||||||
"saving_ok": "Settings saved",
|
"saving_ok": "Settings saved",
|
||||||
"search_user_to_block": "Search whom you want to block",
|
"search_user_to_block": "Search whom you want to block",
|
||||||
|
@ -380,6 +400,8 @@
|
||||||
"no_statuses": "No statuses"
|
"no_statuses": "No statuses"
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
|
"favorites": "Favorites",
|
||||||
|
"repeats": "Repeats",
|
||||||
"reply_to": "Reply to",
|
"reply_to": "Reply to",
|
||||||
"replies_list": "Replies:"
|
"replies_list": "Replies:"
|
||||||
},
|
},
|
||||||
|
@ -404,6 +426,7 @@
|
||||||
"muted": "Muted",
|
"muted": "Muted",
|
||||||
"per_day": "per day",
|
"per_day": "per day",
|
||||||
"remote_follow": "Remote follow",
|
"remote_follow": "Remote follow",
|
||||||
|
"report": "Report",
|
||||||
"statuses": "Statuses",
|
"statuses": "Statuses",
|
||||||
"unblock": "Unblock",
|
"unblock": "Unblock",
|
||||||
"unblock_progress": "Unblocking...",
|
"unblock_progress": "Unblocking...",
|
||||||
|
@ -436,6 +459,15 @@
|
||||||
"profile_does_not_exist": "Sorry, this profile does not exist.",
|
"profile_does_not_exist": "Sorry, this profile does not exist.",
|
||||||
"profile_loading_error": "Sorry, there was an error loading this profile."
|
"profile_loading_error": "Sorry, there was an error loading this profile."
|
||||||
},
|
},
|
||||||
|
"user_reporting": {
|
||||||
|
"title": "Reporting {0}",
|
||||||
|
"add_comment_description": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
|
||||||
|
"additional_comments": "Additional comments",
|
||||||
|
"forward_description": "The account is from another server. Send a copy of the report there as well?",
|
||||||
|
"forward_to": "Forward to {0}",
|
||||||
|
"submit": "Submit",
|
||||||
|
"generic_error": "An error occurred while processing your request."
|
||||||
|
},
|
||||||
"who_to_follow": {
|
"who_to_follow": {
|
||||||
"more": "More",
|
"more": "More",
|
||||||
"who_to_follow": "Who to follow"
|
"who_to_follow": "Who to follow"
|
||||||
|
|
116
src/i18n/es.json
116
src/i18n/es.json
|
@ -2,6 +2,10 @@
|
||||||
"chat": {
|
"chat": {
|
||||||
"title": "Chat"
|
"title": "Chat"
|
||||||
},
|
},
|
||||||
|
"exporter": {
|
||||||
|
"export": "Exportar",
|
||||||
|
"processing": "Procesando. Pronto se te pedirá que descargues tu archivo"
|
||||||
|
},
|
||||||
"features_panel": {
|
"features_panel": {
|
||||||
"chat": "Chat",
|
"chat": "Chat",
|
||||||
"gopher": "Gopher",
|
"gopher": "Gopher",
|
||||||
|
@ -19,7 +23,22 @@
|
||||||
"apply": "Aplicar",
|
"apply": "Aplicar",
|
||||||
"submit": "Enviar",
|
"submit": "Enviar",
|
||||||
"more": "Más",
|
"more": "Más",
|
||||||
"generic_error": "Ha ocurrido un error"
|
"generic_error": "Ha ocurrido un error",
|
||||||
|
"optional": "opcional",
|
||||||
|
"show_more": "Mostrar más",
|
||||||
|
"show_less": "Mostrar menos",
|
||||||
|
"cancel": "Cancelar"
|
||||||
|
},
|
||||||
|
"image_cropper": {
|
||||||
|
"crop_picture": "Recortar la foto",
|
||||||
|
"save": "Guardar",
|
||||||
|
"save_without_cropping": "Guardar sin recortar",
|
||||||
|
"cancel": "Cancelar"
|
||||||
|
},
|
||||||
|
"importer": {
|
||||||
|
"submit": "Enviar",
|
||||||
|
"success": "Importado con éxito",
|
||||||
|
"error": "Se ha producido un error al importar el archivo."
|
||||||
},
|
},
|
||||||
"login": {
|
"login": {
|
||||||
"login": "Identificación",
|
"login": "Identificación",
|
||||||
|
@ -30,6 +49,10 @@
|
||||||
"register": "Registrar",
|
"register": "Registrar",
|
||||||
"username": "Usuario",
|
"username": "Usuario",
|
||||||
"hint": "Inicia sesión para unirte a la discusión"
|
"hint": "Inicia sesión para unirte a la discusión"
|
||||||
|
},
|
||||||
|
"media_modal": {
|
||||||
|
"previous": "Anterior",
|
||||||
|
"next": "Siguiente"
|
||||||
},
|
},
|
||||||
"nav": {
|
"nav": {
|
||||||
"about": "Sobre",
|
"about": "Sobre",
|
||||||
|
@ -61,15 +84,19 @@
|
||||||
"account_not_locked_warning_link": "bloqueada",
|
"account_not_locked_warning_link": "bloqueada",
|
||||||
"attachments_sensitive": "Contenido sensible",
|
"attachments_sensitive": "Contenido sensible",
|
||||||
"content_type": {
|
"content_type": {
|
||||||
"text/plain": "Texto Plano"
|
"text/plain": "Texto Plano",
|
||||||
|
"text/html": "HTML",
|
||||||
|
"text/markdown": "Markdown",
|
||||||
|
"text/bbcode": "BBCode"
|
||||||
},
|
},
|
||||||
"content_warning": "Tema (opcional)",
|
"content_warning": "Tema (opcional)",
|
||||||
"default": "Acabo de aterrizar en L.A.",
|
"default": "Acabo de aterrizar en L.A.",
|
||||||
"direct_warning": "Esta entrada solo será visible para los usuarios mencionados.",
|
"direct_warning": "Esta publicación solo será visible para los usuarios mencionados.",
|
||||||
|
"direct_warning_to_first_only": "Esta publicación solo será visible para los usuarios mencionados al comienzo del mensaje.",
|
||||||
"posting": "Publicando",
|
"posting": "Publicando",
|
||||||
"scope": {
|
"scope": {
|
||||||
"direct": "Directo - Solo para los usuarios mencionados.",
|
"direct": "Directo - Solo para los usuarios mencionados.",
|
||||||
"private": "Solo-Seguidores - Solo tus seguidores leeran la entrada",
|
"private": "Solo-Seguidores - Solo tus seguidores leeran la publicación",
|
||||||
"public": "Público - Entradas visibles en las Líneas Temporales Públicas",
|
"public": "Público - Entradas visibles en las Líneas Temporales Públicas",
|
||||||
"unlisted": "Sin Listar - Entradas no visibles en las Líneas Temporales Públicas"
|
"unlisted": "Sin Listar - Entradas no visibles en las Líneas Temporales Públicas"
|
||||||
}
|
}
|
||||||
|
@ -83,6 +110,9 @@
|
||||||
"token": "Token de invitación",
|
"token": "Token de invitación",
|
||||||
"captcha": "CAPTCHA",
|
"captcha": "CAPTCHA",
|
||||||
"new_captcha": "Click en la imagen para obtener un nuevo captca",
|
"new_captcha": "Click en la imagen para obtener un nuevo captca",
|
||||||
|
"username_placeholder": "p.ej. lain",
|
||||||
|
"fullname_placeholder": "p.ej. Lain Iwakura",
|
||||||
|
"bio_placeholder": "e.g.\nHola, soy un ejemplo.\nAquí puedes poner algo representativo tuyo... o no.",
|
||||||
"validations": {
|
"validations": {
|
||||||
"username_required": "no puede estar vacío",
|
"username_required": "no puede estar vacío",
|
||||||
"fullname_required": "no puede estar vacío",
|
"fullname_required": "no puede estar vacío",
|
||||||
|
@ -91,8 +121,12 @@
|
||||||
"password_confirmation_required": "no puede estar vacío",
|
"password_confirmation_required": "no puede estar vacío",
|
||||||
"password_confirmation_match": "la contraseña no coincide"
|
"password_confirmation_match": "la contraseña no coincide"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"selectable_list": {
|
||||||
|
"select_all": "Seleccionarlo todo"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
|
"app_name": "Nombre de la aplicación",
|
||||||
"attachmentRadius": "Adjuntos",
|
"attachmentRadius": "Adjuntos",
|
||||||
"attachments": "Adjuntos",
|
"attachments": "Adjuntos",
|
||||||
"autoload": "Activar carga automática al llegar al final de la página",
|
"autoload": "Activar carga automática al llegar al final de la página",
|
||||||
|
@ -101,6 +135,12 @@
|
||||||
"avatarRadius": "Avatares",
|
"avatarRadius": "Avatares",
|
||||||
"background": "Fondo",
|
"background": "Fondo",
|
||||||
"bio": "Biografía",
|
"bio": "Biografía",
|
||||||
|
"block_export": "Exportar usuarios bloqueados",
|
||||||
|
"block_export_button": "Exporta la lista de tus usarios bloqueados a un archivo csv",
|
||||||
|
"block_import": "Importar usuarios bloqueados",
|
||||||
|
"block_import_error": "Error importando la lista de usuarios bloqueados",
|
||||||
|
"blocks_imported": "¡Lista de usuarios bloqueados importada! El procesado puede tardar un poco.",
|
||||||
|
"blocks_tab": "Bloqueados",
|
||||||
"btnRadius": "Botones",
|
"btnRadius": "Botones",
|
||||||
"cBlue": "Azul (Responder, seguir)",
|
"cBlue": "Azul (Responder, seguir)",
|
||||||
"cGreen": "Verde (Retweet)",
|
"cGreen": "Verde (Retweet)",
|
||||||
|
@ -127,7 +167,6 @@
|
||||||
"filtering_explanation": "Todos los estados que contengan estas palabras serán silenciados, una por línea",
|
"filtering_explanation": "Todos los estados que contengan estas palabras serán silenciados, una por línea",
|
||||||
"follow_export": "Exportar personas que tú sigues",
|
"follow_export": "Exportar personas que tú sigues",
|
||||||
"follow_export_button": "Exporta tus seguidores a un archivo csv",
|
"follow_export_button": "Exporta tus seguidores a un archivo csv",
|
||||||
"follow_export_processing": "Procesando, en breve se te preguntará para guardar el archivo",
|
|
||||||
"follow_import": "Importar personas que tú sigues",
|
"follow_import": "Importar personas que tú sigues",
|
||||||
"follow_import_error": "Error al importal el archivo",
|
"follow_import_error": "Error al importal el archivo",
|
||||||
"follows_imported": "¡Importado! Procesarlos llevará tiempo.",
|
"follows_imported": "¡Importado! Procesarlos llevará tiempo.",
|
||||||
|
@ -135,12 +174,15 @@
|
||||||
"general": "General",
|
"general": "General",
|
||||||
"hide_attachments_in_convo": "Ocultar adjuntos en las conversaciones",
|
"hide_attachments_in_convo": "Ocultar adjuntos en las conversaciones",
|
||||||
"hide_attachments_in_tl": "Ocultar adjuntos en la línea temporal",
|
"hide_attachments_in_tl": "Ocultar adjuntos en la línea temporal",
|
||||||
|
"hide_muted_posts": "Ocultar las publicaciones de los usuarios silenciados",
|
||||||
|
"max_thumbnails": "Cantidad máxima de miniaturas por publicación",
|
||||||
"hide_isp": "Ocultar el panel específico de la instancia",
|
"hide_isp": "Ocultar el panel específico de la instancia",
|
||||||
"preload_images": "Precargar las imágenes",
|
"preload_images": "Precargar las imágenes",
|
||||||
"use_one_click_nsfw": "Abrir los adjuntos NSFW con un solo click.",
|
"use_one_click_nsfw": "Abrir los adjuntos NSFW con un solo click.",
|
||||||
"hide_post_stats": "Ocultar las estadísticas de las entradas (p.ej. el número de favoritos)",
|
"hide_post_stats": "Ocultar las estadísticas de las entradas (p.ej. el número de favoritos)",
|
||||||
"hide_user_stats": "Ocultar las estadísticas del usuario (p.ej. el número de seguidores)",
|
"hide_user_stats": "Ocultar las estadísticas del usuario (p.ej. el número de seguidores)",
|
||||||
"hide_filtered_statuses": "Ocultar estados filtrados",
|
"hide_filtered_statuses": "Ocultar estados filtrados",
|
||||||
|
"import_blocks_from_a_csv_file": "Importar lista de usuarios bloqueados dese un archivo csv",
|
||||||
"import_followers_from_a_csv_file": "Importar personas que tú sigues a partir de un archivo csv",
|
"import_followers_from_a_csv_file": "Importar personas que tú sigues a partir de un archivo csv",
|
||||||
"import_theme": "Importar tema",
|
"import_theme": "Importar tema",
|
||||||
"inputRadius": "Campos de entrada",
|
"inputRadius": "Campos de entrada",
|
||||||
|
@ -155,6 +197,7 @@
|
||||||
"lock_account_description": "Restringir el acceso a tu cuenta solo a seguidores admitidos",
|
"lock_account_description": "Restringir el acceso a tu cuenta solo a seguidores admitidos",
|
||||||
"loop_video": "Vídeos en bucle",
|
"loop_video": "Vídeos en bucle",
|
||||||
"loop_video_silent_only": "Bucle solo en vídeos sin sonido (p.ej. \"gifs\" de Mastodon)",
|
"loop_video_silent_only": "Bucle solo en vídeos sin sonido (p.ej. \"gifs\" de Mastodon)",
|
||||||
|
"mutes_tab": "Silenciados",
|
||||||
"play_videos_in_modal": "Reproducir los vídeos directamente en el visor de medios",
|
"play_videos_in_modal": "Reproducir los vídeos directamente en el visor de medios",
|
||||||
"use_contain_fit": "No recortar los adjuntos en miniaturas",
|
"use_contain_fit": "No recortar los adjuntos en miniaturas",
|
||||||
"name": "Nombre",
|
"name": "Nombre",
|
||||||
|
@ -166,6 +209,8 @@
|
||||||
"notification_visibility_mentions": "Menciones",
|
"notification_visibility_mentions": "Menciones",
|
||||||
"notification_visibility_repeats": "Repeticiones (Repeats)",
|
"notification_visibility_repeats": "Repeticiones (Repeats)",
|
||||||
"no_rich_text_description": "Eliminar el formato de texto enriquecido de todas las entradas",
|
"no_rich_text_description": "Eliminar el formato de texto enriquecido de todas las entradas",
|
||||||
|
"no_blocks": "No hay usuarios bloqueados",
|
||||||
|
"no_mutes": "No hay usuarios sinlenciados",
|
||||||
"hide_follows_description": "No mostrar a quién sigo",
|
"hide_follows_description": "No mostrar a quién sigo",
|
||||||
"hide_followers_description": "No mostrar quién me sigue",
|
"hide_followers_description": "No mostrar quién me sigue",
|
||||||
"show_admin_badge": "Mostrar la placa de administrador en mi perfil",
|
"show_admin_badge": "Mostrar la placa de administrador en mi perfil",
|
||||||
|
@ -190,8 +235,11 @@
|
||||||
"reply_visibility_self": "Solo mostrar réplicas para mí",
|
"reply_visibility_self": "Solo mostrar réplicas para mí",
|
||||||
"saving_err": "Error al guardar los ajustes",
|
"saving_err": "Error al guardar los ajustes",
|
||||||
"saving_ok": "Ajustes guardados",
|
"saving_ok": "Ajustes guardados",
|
||||||
|
"search_user_to_block": "Buscar usuarios a bloquear",
|
||||||
|
"search_user_to_mute": "Buscar usuarios a silenciar",
|
||||||
"security_tab": "Seguridad",
|
"security_tab": "Seguridad",
|
||||||
"scope_copy": "Copiar la visibilidad cuando contestamos (En los mensajes directos (MDs) siempre se copia)",
|
"scope_copy": "Copiar la visibilidad de la publicación cuando contestamos (En los mensajes directos (MDs) siempre se copia)",
|
||||||
|
"minimal_scopes_mode": "Minimizar las opciones de publicación",
|
||||||
"set_new_avatar": "Cambiar avatar",
|
"set_new_avatar": "Cambiar avatar",
|
||||||
"set_new_profile_background": "Cambiar fondo del perfil",
|
"set_new_profile_background": "Cambiar fondo del perfil",
|
||||||
"set_new_profile_banner": "Cambiar cabecera del perfil",
|
"set_new_profile_banner": "Cambiar cabecera del perfil",
|
||||||
|
@ -210,6 +258,7 @@
|
||||||
"theme_help_v2_1": "También puede invalidar los colores y la opacidad de ciertos componentes si activa la casilla de verificación, use el botón \"Borrar todo\" para deshacer los cambios.",
|
"theme_help_v2_1": "También puede invalidar los colores y la opacidad de ciertos componentes si activa la casilla de verificación, use el botón \"Borrar todo\" para deshacer los cambios.",
|
||||||
"theme_help_v2_2": "Los iconos debajo de algunas entradas son indicadores de contraste de fondo/texto, desplace el ratón para obtener información detallada. Tenga en cuenta que cuando se utilizan indicadores de contraste de transparencia se muestra el peor caso posible.",
|
"theme_help_v2_2": "Los iconos debajo de algunas entradas son indicadores de contraste de fondo/texto, desplace el ratón para obtener información detallada. Tenga en cuenta que cuando se utilizan indicadores de contraste de transparencia se muestra el peor caso posible.",
|
||||||
"tooltipRadius": "Información/alertas",
|
"tooltipRadius": "Información/alertas",
|
||||||
|
"upload_a_photo": "Subir una foto",
|
||||||
"user_settings": "Ajustes de Usuario",
|
"user_settings": "Ajustes de Usuario",
|
||||||
"values": {
|
"values": {
|
||||||
"false": "no",
|
"false": "no",
|
||||||
|
@ -325,6 +374,11 @@
|
||||||
"checkbox": "He revisado los términos y condiciones",
|
"checkbox": "He revisado los términos y condiciones",
|
||||||
"link": "un bonito enlace"
|
"link": "un bonito enlace"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"title": "Versión",
|
||||||
|
"backend_version": "Versión del Backend",
|
||||||
|
"frontend_version": "Versión del Frontend"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"timeline": {
|
"timeline": {
|
||||||
|
@ -336,7 +390,14 @@
|
||||||
"repeated": "repetida",
|
"repeated": "repetida",
|
||||||
"show_new": "Mostrar lo nuevo",
|
"show_new": "Mostrar lo nuevo",
|
||||||
"up_to_date": "Actualizado",
|
"up_to_date": "Actualizado",
|
||||||
"no_more_statuses": "No hay más estados"
|
"no_more_statuses": "No hay más estados",
|
||||||
|
"no_statuses": "Sin estados"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"favorites": "Favoritos",
|
||||||
|
"repeats": "Repetidos",
|
||||||
|
"reply_to": "Responder a",
|
||||||
|
"replies_list": "Respuestas:"
|
||||||
},
|
},
|
||||||
"user_card": {
|
"user_card": {
|
||||||
"approve": "Aprovar",
|
"approve": "Aprovar",
|
||||||
|
@ -359,10 +420,47 @@
|
||||||
"muted": "Silenciado",
|
"muted": "Silenciado",
|
||||||
"per_day": "por día",
|
"per_day": "por día",
|
||||||
"remote_follow": "Seguir",
|
"remote_follow": "Seguir",
|
||||||
"statuses": "Estados"
|
"report": "Reportar",
|
||||||
|
"statuses": "Estados",
|
||||||
|
"unblock": "Desbloquear",
|
||||||
|
"unblock_progress": "Desbloqueando...",
|
||||||
|
"block_progress": "Bloqueando...",
|
||||||
|
"unmute": "Desenmudecer",
|
||||||
|
"unmute_progress": "Sesenmudeciendo...",
|
||||||
|
"mute_progress": "Silenciando...",
|
||||||
|
"admin_menu": {
|
||||||
|
"moderation": "Moderación",
|
||||||
|
"grant_admin": "Conceder permisos de Administrador",
|
||||||
|
"revoke_admin": "Revocar permisos de Administrador",
|
||||||
|
"grant_moderator": "Conceder permisos de Moderador",
|
||||||
|
"revoke_moderator": "Revocar permisos de Moderador",
|
||||||
|
"activate_account": "Activar cuenta",
|
||||||
|
"deactivate_account": "Desactivar cuenta",
|
||||||
|
"delete_account": "Borrar cuenta",
|
||||||
|
"force_nsfw": "Marcar todas las publicaciones como NSFW (no es seguro/apropiado para el trabajo)",
|
||||||
|
"strip_media": "Eliminar archivos multimedia de las publicaciones",
|
||||||
|
"force_unlisted": "Forzar que se publique en el modo -Sin Listar-",
|
||||||
|
"sandbox": "Forzar que se publique solo para tus seguidores",
|
||||||
|
"disable_remote_subscription": "No permitir que usuarios de instancias remotas te siga.",
|
||||||
|
"disable_any_subscription": "No permitir que ningún usuario te siga",
|
||||||
|
"quarantine": "No permitir publicaciones de usuarios de instancias remotas",
|
||||||
|
"delete_user": "Borrar usuario",
|
||||||
|
"delete_user_confirmation": "¿Estás completamente seguro? Esta acción no se puede deshacer."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"user_profile": {
|
"user_profile": {
|
||||||
"timeline_title": "Linea temporal del usuario"
|
"timeline_title": "Linea temporal del usuario",
|
||||||
|
"profile_does_not_exist": "Lo sentimos, este perfil no existe.",
|
||||||
|
"profile_loading_error": "Lo sentimos, hubo un error al cargar este perfil."
|
||||||
|
},
|
||||||
|
"user_reporting": {
|
||||||
|
"title": "Reportando a {0}",
|
||||||
|
"add_comment_description": "El informe será enviado a los moderadores de su instancia. Puedes proporcionar una explicación de por qué estás reportando esta cuenta a continuación:",
|
||||||
|
"additional_comments": "Comentarios adicionales",
|
||||||
|
"forward_description": "La cuenta es de otro servidor. ¿Enviar una copia del informe allí también?",
|
||||||
|
"forward_to": "Reenviar a {0}",
|
||||||
|
"submit": "Enviar",
|
||||||
|
"generic_error": "Se produjo un error al procesar la solicitud."
|
||||||
},
|
},
|
||||||
"who_to_follow": {
|
"who_to_follow": {
|
||||||
"more": "Más",
|
"more": "Más",
|
||||||
|
|
|
@ -222,6 +222,8 @@
|
||||||
"no_more_statuses": "Ei enempää viestejä"
|
"no_more_statuses": "Ei enempää viestejä"
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
|
"favorites": "Tykkäykset",
|
||||||
|
"repeats": "Toistot",
|
||||||
"reply_to": "Vastaus",
|
"reply_to": "Vastaus",
|
||||||
"replies_list": "Vastaukset:"
|
"replies_list": "Vastaukset:"
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
{
|
{
|
||||||
"chat": {
|
"chat": {
|
||||||
"title": "Messatjariá"
|
"title": "Messatjariá"
|
||||||
|
},
|
||||||
|
"exporter": {
|
||||||
|
"export": "Exportar",
|
||||||
|
"processing": "Tractament, vos demandarem lèu de telecargar lo fichièr"
|
||||||
},
|
},
|
||||||
"features_panel": {
|
"features_panel": {
|
||||||
"chat": "Chat",
|
"chat": "Chat",
|
||||||
|
@ -30,6 +34,11 @@
|
||||||
"save": "Salvar",
|
"save": "Salvar",
|
||||||
"save_without_cropping": "Salvar sens talhada",
|
"save_without_cropping": "Salvar sens talhada",
|
||||||
"cancel": "Anullar"
|
"cancel": "Anullar"
|
||||||
|
},
|
||||||
|
"importer": {
|
||||||
|
"submit": "Mandar",
|
||||||
|
"success": "Corrèctament importat.",
|
||||||
|
"error": "Una error s’es producha pendent l’importacion d’aqueste fichièr."
|
||||||
},
|
},
|
||||||
"login": {
|
"login": {
|
||||||
"login": "Connexion",
|
"login": "Connexion",
|
||||||
|
@ -126,6 +135,11 @@
|
||||||
"avatarRadius": "Avatars",
|
"avatarRadius": "Avatars",
|
||||||
"background": "Rèire plan",
|
"background": "Rèire plan",
|
||||||
"bio": "Biografia",
|
"bio": "Biografia",
|
||||||
|
"block_export": "Exportar los blocatges",
|
||||||
|
"block_export_button": "Exportar los blocatges dins un fichièr csv",
|
||||||
|
"block_import": "Impòrt de blocatges",
|
||||||
|
"block_import_error": "Error en importar los blocatges",
|
||||||
|
"blocks_imported": "Blocatges importats ! Lo tractament tardarà un pauc.",
|
||||||
"blocks_tab": "Blocatges",
|
"blocks_tab": "Blocatges",
|
||||||
"btnRadius": "Botons",
|
"btnRadius": "Botons",
|
||||||
"cBlue": "Blau (Respondre, seguir)",
|
"cBlue": "Blau (Respondre, seguir)",
|
||||||
|
@ -153,7 +167,6 @@
|
||||||
"filtering_explanation": "Totes los estatuts amb aqueles mots seràn en silenci, un mot per linha",
|
"filtering_explanation": "Totes los estatuts amb aqueles mots seràn en silenci, un mot per linha",
|
||||||
"follow_export": "Exportar los abonaments",
|
"follow_export": "Exportar los abonaments",
|
||||||
"follow_export_button": "Exportar vòstres abonaments dins un fichièr csv",
|
"follow_export_button": "Exportar vòstres abonaments dins un fichièr csv",
|
||||||
"follow_export_processing": "Tractament, vos demandarem lèu de telecargar lo fichièr",
|
|
||||||
"follow_import": "Importar los abonaments",
|
"follow_import": "Importar los abonaments",
|
||||||
"follow_import_error": "Error en important los seguidors",
|
"follow_import_error": "Error en important los seguidors",
|
||||||
"follows_imported": "Seguidors importats. Lo tractament pòt trigar una estona.",
|
"follows_imported": "Seguidors importats. Lo tractament pòt trigar una estona.",
|
||||||
|
@ -188,6 +201,7 @@
|
||||||
"use_contain_fit": "Talhar pas las pèças juntas per las vinhetas",
|
"use_contain_fit": "Talhar pas las pèças juntas per las vinhetas",
|
||||||
"name": "Nom",
|
"name": "Nom",
|
||||||
"name_bio": "Nom & Bio",
|
"name_bio": "Nom & Bio",
|
||||||
|
|
||||||
"new_password": "Nòu senhal",
|
"new_password": "Nòu senhal",
|
||||||
"notification_visibility_follows": "Abonaments",
|
"notification_visibility_follows": "Abonaments",
|
||||||
"notification_visibility_likes": "Aimar",
|
"notification_visibility_likes": "Aimar",
|
||||||
|
@ -380,6 +394,8 @@
|
||||||
"no_statuses": "Cap d’estatuts"
|
"no_statuses": "Cap d’estatuts"
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
|
"favorites": "Li a agradat",
|
||||||
|
"repeats": "A repetit",
|
||||||
"reply_to": "Respond a",
|
"reply_to": "Respond a",
|
||||||
"replies_list": "Responsas :"
|
"replies_list": "Responsas :"
|
||||||
},
|
},
|
||||||
|
|
|
@ -363,7 +363,7 @@
|
||||||
"error_fetching": "Błąd pobierania",
|
"error_fetching": "Błąd pobierania",
|
||||||
"load_older": "Załaduj starsze statusy",
|
"load_older": "Załaduj starsze statusy",
|
||||||
"no_retweet_hint": "Wpis oznaczony jako tylko dla obserwujących lub bezpośredni nie może zostać powtórzony",
|
"no_retweet_hint": "Wpis oznaczony jako tylko dla obserwujących lub bezpośredni nie może zostać powtórzony",
|
||||||
"repeated": "powtórzono",
|
"repeated": "powtórzył(-a)",
|
||||||
"show_new": "Pokaż nowe",
|
"show_new": "Pokaż nowe",
|
||||||
"up_to_date": "Na bieżąco",
|
"up_to_date": "Na bieżąco",
|
||||||
"no_more_statuses": "Brak kolejnych statusów",
|
"no_more_statuses": "Brak kolejnych statusów",
|
||||||
|
|
|
@ -42,8 +42,13 @@
|
||||||
"attachments_sensitive": "Вложения содержат чувствительный контент",
|
"attachments_sensitive": "Вложения содержат чувствительный контент",
|
||||||
"content_warning": "Тема (не обязательно)",
|
"content_warning": "Тема (не обязательно)",
|
||||||
"default": "Что нового?",
|
"default": "Что нового?",
|
||||||
"direct_warning": "Этот пост будет видет только упомянутым пользователям",
|
"direct_warning": "Этот пост будет виден только упомянутым пользователям",
|
||||||
"posting": "Отправляется",
|
"posting": "Отправляется",
|
||||||
|
"scope_notice": {
|
||||||
|
"public": "Этот пост будет виден всем",
|
||||||
|
"private": "Этот пост будет виден только вашим подписчикам",
|
||||||
|
"unlisted": "Этот пост не будет виден в публичной и федеративной ленте"
|
||||||
|
},
|
||||||
"scope": {
|
"scope": {
|
||||||
"direct": "Личное - этот пост видят только те кто в нём упомянут",
|
"direct": "Личное - этот пост видят только те кто в нём упомянут",
|
||||||
"private": "Для подписчиков - этот пост видят только подписчики",
|
"private": "Для подписчиков - этот пост видят только подписчики",
|
||||||
|
@ -152,6 +157,7 @@
|
||||||
"reply_visibility_all": "Показывать все ответы",
|
"reply_visibility_all": "Показывать все ответы",
|
||||||
"reply_visibility_following": "Показывать только ответы мне и тех на кого я подписан",
|
"reply_visibility_following": "Показывать только ответы мне и тех на кого я подписан",
|
||||||
"reply_visibility_self": "Показывать только ответы мне",
|
"reply_visibility_self": "Показывать только ответы мне",
|
||||||
|
"autohide_floating_post_button": "Автоматически скрывать кнопку постинга (в мобильной версии)",
|
||||||
"saving_err": "Не удалось сохранить настройки",
|
"saving_err": "Не удалось сохранить настройки",
|
||||||
"saving_ok": "Сохранено",
|
"saving_ok": "Сохранено",
|
||||||
"security_tab": "Безопасность",
|
"security_tab": "Безопасность",
|
||||||
|
|
|
@ -12,6 +12,7 @@ import chatModule from './modules/chat.js'
|
||||||
import oauthModule from './modules/oauth.js'
|
import oauthModule from './modules/oauth.js'
|
||||||
import mediaViewerModule from './modules/media_viewer.js'
|
import mediaViewerModule from './modules/media_viewer.js'
|
||||||
import oauthTokensModule from './modules/oauth_tokens.js'
|
import oauthTokensModule from './modules/oauth_tokens.js'
|
||||||
|
import reportsModule from './modules/reports.js'
|
||||||
|
|
||||||
import VueTimeago from 'vue-timeago'
|
import VueTimeago from 'vue-timeago'
|
||||||
import VueI18n from 'vue-i18n'
|
import VueI18n from 'vue-i18n'
|
||||||
|
@ -75,7 +76,8 @@ const persistedStateOptions = {
|
||||||
chat: chatModule,
|
chat: chatModule,
|
||||||
oauth: oauthModule,
|
oauth: oauthModule,
|
||||||
mediaViewer: mediaViewerModule,
|
mediaViewer: mediaViewerModule,
|
||||||
oauthTokens: oauthTokensModule
|
oauthTokens: oauthTokensModule,
|
||||||
|
reports: reportsModule
|
||||||
},
|
},
|
||||||
plugins: [persistedState, pushNotifications],
|
plugins: [persistedState, pushNotifications],
|
||||||
strict: false // Socket modifies itself, let's ignore this for now.
|
strict: false // Socket modifies itself, let's ignore this for now.
|
||||||
|
|
|
@ -17,6 +17,7 @@ const defaultState = {
|
||||||
autoLoad: true,
|
autoLoad: true,
|
||||||
streaming: false,
|
streaming: false,
|
||||||
hoverPreview: true,
|
hoverPreview: true,
|
||||||
|
autohideFloatingPostButton: false,
|
||||||
pauseOnUnfocused: true,
|
pauseOnUnfocused: true,
|
||||||
stopGifs: false,
|
stopGifs: false,
|
||||||
replyVisibility: 'all',
|
replyVisibility: 'all',
|
||||||
|
@ -30,6 +31,7 @@ const defaultState = {
|
||||||
muteWords: [],
|
muteWords: [],
|
||||||
highlight: {},
|
highlight: {},
|
||||||
interfaceLanguage: browserLocale,
|
interfaceLanguage: browserLocale,
|
||||||
|
hideScopeNotice: false,
|
||||||
scopeCopy: undefined, // instance default
|
scopeCopy: undefined, // instance default
|
||||||
subjectLineBehavior: undefined, // instance default
|
subjectLineBehavior: undefined, // instance default
|
||||||
alwaysShowSubjectInput: undefined, // instance default
|
alwaysShowSubjectInput: undefined, // instance default
|
||||||
|
|
30
src/modules/reports.js
Normal file
30
src/modules/reports.js
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import filter from 'lodash/filter'
|
||||||
|
|
||||||
|
const reports = {
|
||||||
|
state: {
|
||||||
|
userId: null,
|
||||||
|
statuses: [],
|
||||||
|
modalActivated: false
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
openUserReportingModal (state, { userId, statuses }) {
|
||||||
|
state.userId = userId
|
||||||
|
state.statuses = statuses
|
||||||
|
state.modalActivated = true
|
||||||
|
},
|
||||||
|
closeUserReportingModal (state) {
|
||||||
|
state.modalActivated = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
openUserReportingModal ({ rootState, commit }, userId) {
|
||||||
|
const statuses = filter(rootState.statuses.allStatuses, status => status.user.id === userId)
|
||||||
|
commit('openUserReportingModal', { userId, statuses })
|
||||||
|
},
|
||||||
|
closeUserReportingModal ({ commit }) {
|
||||||
|
commit('closeUserReportingModal')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default reports
|
|
@ -1,4 +1,4 @@
|
||||||
import { remove, slice, each, find, maxBy, minBy, merge, first, last, isArray, omitBy } from 'lodash'
|
import { remove, slice, each, findIndex, find, maxBy, minBy, merge, first, last, isArray, omitBy } from 'lodash'
|
||||||
import { set } from 'vue'
|
import { set } from 'vue'
|
||||||
import apiService from '../services/api/api.service.js'
|
import apiService from '../services/api/api.service.js'
|
||||||
// import parse from '../services/status_parser/status_parser.js'
|
// import parse from '../services/status_parser/status_parser.js'
|
||||||
|
@ -402,12 +402,27 @@ export const mutations = {
|
||||||
},
|
},
|
||||||
setFavorited (state, { status, value }) {
|
setFavorited (state, { status, value }) {
|
||||||
const newStatus = state.allStatusesObject[status.id]
|
const newStatus = state.allStatusesObject[status.id]
|
||||||
|
|
||||||
|
if (newStatus.favorited !== value) {
|
||||||
|
if (value) {
|
||||||
|
newStatus.fave_num++
|
||||||
|
} else {
|
||||||
|
newStatus.fave_num--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
newStatus.favorited = value
|
newStatus.favorited = value
|
||||||
},
|
},
|
||||||
setFavoritedConfirm (state, { status }) {
|
setFavoritedConfirm (state, { status, user }) {
|
||||||
const newStatus = state.allStatusesObject[status.id]
|
const newStatus = state.allStatusesObject[status.id]
|
||||||
newStatus.favorited = status.favorited
|
newStatus.favorited = status.favorited
|
||||||
newStatus.fave_num = status.fave_num
|
newStatus.fave_num = status.fave_num
|
||||||
|
const index = findIndex(newStatus.favoritedBy, { id: user.id })
|
||||||
|
if (index !== -1 && !newStatus.favorited) {
|
||||||
|
newStatus.favoritedBy.splice(index, 1)
|
||||||
|
} else if (index === -1 && newStatus.favorited) {
|
||||||
|
newStatus.favoritedBy.push(user)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
setRetweeted (state, { status, value }) {
|
setRetweeted (state, { status, value }) {
|
||||||
const newStatus = state.allStatusesObject[status.id]
|
const newStatus = state.allStatusesObject[status.id]
|
||||||
|
@ -422,6 +437,17 @@ export const mutations = {
|
||||||
|
|
||||||
newStatus.repeated = value
|
newStatus.repeated = value
|
||||||
},
|
},
|
||||||
|
setRetweetedConfirm (state, { status, user }) {
|
||||||
|
const newStatus = state.allStatusesObject[status.id]
|
||||||
|
newStatus.repeated = status.repeated
|
||||||
|
newStatus.repeat_num = status.repeat_num
|
||||||
|
const index = findIndex(newStatus.rebloggedBy, { id: user.id })
|
||||||
|
if (index !== -1 && !newStatus.repeated) {
|
||||||
|
newStatus.rebloggedBy.splice(index, 1)
|
||||||
|
} else if (index === -1 && newStatus.repeated) {
|
||||||
|
newStatus.rebloggedBy.push(user)
|
||||||
|
}
|
||||||
|
},
|
||||||
setDeleted (state, { status }) {
|
setDeleted (state, { status }) {
|
||||||
const newStatus = state.allStatusesObject[status.id]
|
const newStatus = state.allStatusesObject[status.id]
|
||||||
newStatus.deleted = true
|
newStatus.deleted = true
|
||||||
|
@ -459,6 +485,11 @@ export const mutations = {
|
||||||
},
|
},
|
||||||
queueFlush (state, { timeline, id }) {
|
queueFlush (state, { timeline, id }) {
|
||||||
state.timelines[timeline].flushMarker = id
|
state.timelines[timeline].flushMarker = id
|
||||||
|
},
|
||||||
|
addFavsAndRepeats (state, { id, favoritedByUsers, rebloggedByUsers }) {
|
||||||
|
const newStatus = state.allStatusesObject[id]
|
||||||
|
newStatus.favoritedBy = favoritedByUsers.filter(_ => _)
|
||||||
|
newStatus.rebloggedBy = rebloggedByUsers.filter(_ => _)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -493,27 +524,26 @@ const statuses = {
|
||||||
favorite ({ rootState, commit }, status) {
|
favorite ({ rootState, commit }, status) {
|
||||||
// Optimistic favoriting...
|
// Optimistic favoriting...
|
||||||
commit('setFavorited', { status, value: true })
|
commit('setFavorited', { status, value: true })
|
||||||
apiService.favorite({ id: status.id, credentials: rootState.users.currentUser.credentials })
|
rootState.api.backendInteractor.favorite(status.id)
|
||||||
.then(status => {
|
.then(status => commit('setFavoritedConfirm', { status, user: rootState.users.currentUser }))
|
||||||
commit('setFavoritedConfirm', { status })
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
unfavorite ({ rootState, commit }, status) {
|
unfavorite ({ rootState, commit }, status) {
|
||||||
// Optimistic favoriting...
|
// Optimistic unfavoriting...
|
||||||
commit('setFavorited', { status, value: false })
|
commit('setFavorited', { status, value: false })
|
||||||
apiService.unfavorite({ id: status.id, credentials: rootState.users.currentUser.credentials })
|
rootState.api.backendInteractor.unfavorite(status.id)
|
||||||
.then(status => {
|
.then(status => commit('setFavoritedConfirm', { status, user: rootState.users.currentUser }))
|
||||||
commit('setFavoritedConfirm', { status })
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
retweet ({ rootState, commit }, status) {
|
retweet ({ rootState, commit }, status) {
|
||||||
// Optimistic retweeting...
|
// Optimistic retweeting...
|
||||||
commit('setRetweeted', { status, value: true })
|
commit('setRetweeted', { status, value: true })
|
||||||
apiService.retweet({ id: status.id, credentials: rootState.users.currentUser.credentials })
|
rootState.api.backendInteractor.retweet(status.id)
|
||||||
|
.then(status => commit('setRetweetedConfirm', { status: status.retweeted_status, user: rootState.users.currentUser }))
|
||||||
},
|
},
|
||||||
unretweet ({ rootState, commit }, status) {
|
unretweet ({ rootState, commit }, status) {
|
||||||
|
// Optimistic unretweeting...
|
||||||
commit('setRetweeted', { status, value: false })
|
commit('setRetweeted', { status, value: false })
|
||||||
apiService.unretweet({ id: status.id, credentials: rootState.users.currentUser.credentials })
|
rootState.api.backendInteractor.unretweet(status.id)
|
||||||
|
.then(status => commit('setRetweetedConfirm', { status, user: rootState.users.currentUser }))
|
||||||
},
|
},
|
||||||
queueFlush ({ rootState, commit }, { timeline, id }) {
|
queueFlush ({ rootState, commit }, { timeline, id }) {
|
||||||
commit('queueFlush', { timeline, id })
|
commit('queueFlush', { timeline, id })
|
||||||
|
@ -524,6 +554,14 @@ const statuses = {
|
||||||
id: rootState.statuses.notifications.maxId,
|
id: rootState.statuses.notifications.maxId,
|
||||||
credentials: rootState.users.currentUser.credentials
|
credentials: rootState.users.currentUser.credentials
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
fetchFavsAndRepeats ({ rootState, commit }, id) {
|
||||||
|
Promise.all([
|
||||||
|
rootState.api.backendInteractor.fetchFavoritedByUsers(id),
|
||||||
|
rootState.api.backendInteractor.fetchRebloggedByUsers(id)
|
||||||
|
]).then(([favoritedByUsers, rebloggedByUsers]) =>
|
||||||
|
commit('addFavsAndRepeats', { id, favoritedByUsers, rebloggedByUsers })
|
||||||
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mutations
|
mutations
|
||||||
|
|
|
@ -3,12 +3,10 @@ const LOGIN_URL = '/api/account/verify_credentials.json'
|
||||||
const ALL_FOLLOWING_URL = '/api/qvitter/allfollowing'
|
const ALL_FOLLOWING_URL = '/api/qvitter/allfollowing'
|
||||||
const MENTIONS_URL = '/api/statuses/mentions.json'
|
const MENTIONS_URL = '/api/statuses/mentions.json'
|
||||||
const REGISTRATION_URL = '/api/account/register.json'
|
const REGISTRATION_URL = '/api/account/register.json'
|
||||||
const AVATAR_UPDATE_URL = '/api/qvitter/update_avatar.json'
|
|
||||||
const BG_UPDATE_URL = '/api/qvitter/update_background_image.json'
|
const BG_UPDATE_URL = '/api/qvitter/update_background_image.json'
|
||||||
const BANNER_UPDATE_URL = '/api/account/update_profile_banner.json'
|
|
||||||
const PROFILE_UPDATE_URL = '/api/account/update_profile.json'
|
|
||||||
const EXTERNAL_PROFILE_URL = '/api/externalprofile/show.json'
|
const EXTERNAL_PROFILE_URL = '/api/externalprofile/show.json'
|
||||||
const QVITTER_USER_NOTIFICATIONS_READ_URL = '/api/qvitter/statuses/notifications/read.json'
|
const QVITTER_USER_NOTIFICATIONS_READ_URL = '/api/qvitter/statuses/notifications/read.json'
|
||||||
|
const BLOCKS_IMPORT_URL = '/api/pleroma/blocks_import'
|
||||||
const FOLLOW_IMPORT_URL = '/api/pleroma/follow_import'
|
const FOLLOW_IMPORT_URL = '/api/pleroma/follow_import'
|
||||||
const DELETE_ACCOUNT_URL = '/api/pleroma/delete_account'
|
const DELETE_ACCOUNT_URL = '/api/pleroma/delete_account'
|
||||||
const CHANGE_PASSWORD_URL = '/api/pleroma/change_password'
|
const CHANGE_PASSWORD_URL = '/api/pleroma/change_password'
|
||||||
|
@ -49,6 +47,10 @@ const MASTODON_MUTE_USER_URL = id => `/api/v1/accounts/${id}/mute`
|
||||||
const MASTODON_UNMUTE_USER_URL = id => `/api/v1/accounts/${id}/unmute`
|
const MASTODON_UNMUTE_USER_URL = id => `/api/v1/accounts/${id}/unmute`
|
||||||
const MASTODON_POST_STATUS_URL = '/api/v1/statuses'
|
const MASTODON_POST_STATUS_URL = '/api/v1/statuses'
|
||||||
const MASTODON_MEDIA_UPLOAD_URL = '/api/v1/media'
|
const MASTODON_MEDIA_UPLOAD_URL = '/api/v1/media'
|
||||||
|
const MASTODON_STATUS_FAVORITEDBY_URL = id => `/api/v1/statuses/${id}/favourited_by`
|
||||||
|
const MASTODON_STATUS_REBLOGGEDBY_URL = id => `/api/v1/statuses/${id}/reblogged_by`
|
||||||
|
const MASTODON_PROFILE_UPDATE_URL = '/api/v1/accounts/update_credentials'
|
||||||
|
const MASTODON_REPORT_USER_URL = '/api/v1/reports'
|
||||||
|
|
||||||
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'
|
||||||
|
@ -65,7 +67,24 @@ let fetch = (url, options) => {
|
||||||
return oldfetch(fullUrl, options)
|
return oldfetch(fullUrl, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
const promisedRequest = (url, options) => {
|
const promisedRequest = ({ method, url, payload, credentials, headers = {} }) => {
|
||||||
|
const options = {
|
||||||
|
method,
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...headers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (payload) {
|
||||||
|
options.body = JSON.stringify(payload)
|
||||||
|
}
|
||||||
|
if (credentials) {
|
||||||
|
options.headers = {
|
||||||
|
...options.headers,
|
||||||
|
...authHeaders(credentials)
|
||||||
|
}
|
||||||
|
}
|
||||||
return fetch(url, options)
|
return fetch(url, options)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
return new Promise((resolve, reject) => response.json()
|
return new Promise((resolve, reject) => response.json()
|
||||||
|
@ -78,28 +97,16 @@ const promisedRequest = (url, options) => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Params
|
const updateAvatar = ({credentials, avatar}) => {
|
||||||
// cropH
|
|
||||||
// cropW
|
|
||||||
// cropX
|
|
||||||
// cropY
|
|
||||||
// img (base 64 encodend data url)
|
|
||||||
const updateAvatar = ({credentials, params}) => {
|
|
||||||
let url = AVATAR_UPDATE_URL
|
|
||||||
|
|
||||||
const form = new FormData()
|
const form = new FormData()
|
||||||
|
form.append('avatar', avatar)
|
||||||
each(params, (value, key) => {
|
return fetch(MASTODON_PROFILE_UPDATE_URL, {
|
||||||
if (value) {
|
|
||||||
form.append(key, value)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return fetch(url, {
|
|
||||||
headers: authHeaders(credentials),
|
headers: authHeaders(credentials),
|
||||||
method: 'POST',
|
method: 'PATCH',
|
||||||
body: form
|
body: form
|
||||||
}).then((data) => data.json())
|
})
|
||||||
|
.then((data) => data.json())
|
||||||
|
.then((data) => parseUser(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateBg = ({credentials, params}) => {
|
const updateBg = ({credentials, params}) => {
|
||||||
|
@ -120,52 +127,26 @@ 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({
|
||||||
const fields = ['description', 'locked', 'no_rich_text', 'hide_follows', 'hide_followers', 'show_role']
|
url: MASTODON_PROFILE_UPDATE_URL,
|
||||||
let url = PROFILE_UPDATE_URL
|
method: 'PATCH',
|
||||||
|
payload: params,
|
||||||
const form = new FormData()
|
credentials
|
||||||
|
|
||||||
each(params, (value, key) => {
|
|
||||||
if (fields.includes(key) || value) {
|
|
||||||
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:
|
||||||
|
@ -261,7 +242,7 @@ const denyUser = ({id, credentials}) => {
|
||||||
|
|
||||||
const fetchUser = ({id, credentials}) => {
|
const fetchUser = ({id, credentials}) => {
|
||||||
let url = `${MASTODON_USER_URL}/${id}`
|
let url = `${MASTODON_USER_URL}/${id}`
|
||||||
return promisedRequest(url, { headers: authHeaders(credentials) })
|
return promisedRequest({ url, credentials })
|
||||||
.then((data) => parseUser(data))
|
.then((data) => parseUser(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -525,62 +506,22 @@ const verifyCredentials = (user) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const favorite = ({ id, credentials }) => {
|
const favorite = ({ id, credentials }) => {
|
||||||
return fetch(MASTODON_FAVORITE_URL(id), {
|
return promisedRequest({ url: MASTODON_FAVORITE_URL(id), method: 'POST', credentials })
|
||||||
headers: authHeaders(credentials),
|
|
||||||
method: 'POST'
|
|
||||||
})
|
|
||||||
.then(response => {
|
|
||||||
if (response.ok) {
|
|
||||||
return response.json()
|
|
||||||
} else {
|
|
||||||
throw new Error('Error favoriting post')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then((data) => parseStatus(data))
|
.then((data) => parseStatus(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
const unfavorite = ({ id, credentials }) => {
|
const unfavorite = ({ id, credentials }) => {
|
||||||
return fetch(MASTODON_UNFAVORITE_URL(id), {
|
return promisedRequest({ url: MASTODON_UNFAVORITE_URL(id), method: 'POST', credentials })
|
||||||
headers: authHeaders(credentials),
|
|
||||||
method: 'POST'
|
|
||||||
})
|
|
||||||
.then(response => {
|
|
||||||
if (response.ok) {
|
|
||||||
return response.json()
|
|
||||||
} else {
|
|
||||||
throw new Error('Error removing favorite')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then((data) => parseStatus(data))
|
.then((data) => parseStatus(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
const retweet = ({ id, credentials }) => {
|
const retweet = ({ id, credentials }) => {
|
||||||
return fetch(MASTODON_RETWEET_URL(id), {
|
return promisedRequest({ url: MASTODON_RETWEET_URL(id), method: 'POST', credentials })
|
||||||
headers: authHeaders(credentials),
|
|
||||||
method: 'POST'
|
|
||||||
})
|
|
||||||
.then(response => {
|
|
||||||
if (response.ok) {
|
|
||||||
return response.json()
|
|
||||||
} else {
|
|
||||||
throw new Error('Error repeating post')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then((data) => parseStatus(data))
|
.then((data) => parseStatus(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
const unretweet = ({ id, credentials }) => {
|
const unretweet = ({ id, credentials }) => {
|
||||||
return fetch(MASTODON_UNRETWEET_URL(id), {
|
return promisedRequest({ url: MASTODON_UNRETWEET_URL(id), method: 'POST', credentials })
|
||||||
headers: authHeaders(credentials),
|
|
||||||
method: 'POST'
|
|
||||||
})
|
|
||||||
.then(response => {
|
|
||||||
if (response.ok) {
|
|
||||||
return response.json()
|
|
||||||
} else {
|
|
||||||
throw new Error('Error removing repeat')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then((data) => parseStatus(data))
|
.then((data) => parseStatus(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -634,9 +575,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)
|
||||||
})
|
})
|
||||||
|
@ -672,26 +626,20 @@ const changePassword = ({credentials, password, newPassword, newPasswordConfirma
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchMutes = ({credentials}) => {
|
const fetchMutes = ({credentials}) => {
|
||||||
return promisedRequest(MASTODON_USER_MUTES_URL, { headers: authHeaders(credentials) })
|
return promisedRequest({ url: MASTODON_USER_MUTES_URL, credentials })
|
||||||
.then((users) => users.map(parseUser))
|
.then((users) => users.map(parseUser))
|
||||||
}
|
}
|
||||||
|
|
||||||
const muteUser = ({id, credentials}) => {
|
const muteUser = ({id, credentials}) => {
|
||||||
return promisedRequest(MASTODON_MUTE_USER_URL(id), {
|
return promisedRequest({ url: MASTODON_MUTE_USER_URL(id), credentials, method: 'POST' })
|
||||||
headers: authHeaders(credentials),
|
|
||||||
method: 'POST'
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const unmuteUser = ({id, credentials}) => {
|
const unmuteUser = ({id, credentials}) => {
|
||||||
return promisedRequest(MASTODON_UNMUTE_USER_URL(id), {
|
return promisedRequest({ url: MASTODON_UNMUTE_USER_URL(id), credentials, method: 'POST' })
|
||||||
headers: authHeaders(credentials),
|
|
||||||
method: 'POST'
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchBlocks = ({credentials}) => {
|
const fetchBlocks = ({credentials}) => {
|
||||||
return promisedRequest(MASTODON_USER_BLOCKS_URL, { headers: authHeaders(credentials) })
|
return promisedRequest({ url: MASTODON_USER_BLOCKS_URL, credentials })
|
||||||
.then((users) => users.map(parseUser))
|
.then((users) => users.map(parseUser))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -735,6 +683,28 @@ const markNotificationsAsSeen = ({id, credentials}) => {
|
||||||
}).then((data) => data.json())
|
}).then((data) => data.json())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fetchFavoritedByUsers = ({id}) => {
|
||||||
|
return promisedRequest({ url: MASTODON_STATUS_FAVORITEDBY_URL(id) }).then((users) => users.map(parseUser))
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchRebloggedByUsers = ({id}) => {
|
||||||
|
return promisedRequest({ url: MASTODON_STATUS_REBLOGGEDBY_URL(id) }).then((users) => users.map(parseUser))
|
||||||
|
}
|
||||||
|
|
||||||
|
const reportUser = ({credentials, userId, statusIds, comment, forward}) => {
|
||||||
|
return promisedRequest({
|
||||||
|
url: MASTODON_REPORT_USER_URL,
|
||||||
|
method: 'POST',
|
||||||
|
payload: {
|
||||||
|
'account_id': userId,
|
||||||
|
'status_ids': statusIds,
|
||||||
|
comment,
|
||||||
|
forward
|
||||||
|
},
|
||||||
|
credentials
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const apiService = {
|
const apiService = {
|
||||||
verifyCredentials,
|
verifyCredentials,
|
||||||
fetchTimeline,
|
fetchTimeline,
|
||||||
|
@ -776,14 +746,18 @@ const apiService = {
|
||||||
updateProfile,
|
updateProfile,
|
||||||
updateBanner,
|
updateBanner,
|
||||||
externalProfile,
|
externalProfile,
|
||||||
followImport,
|
importBlocks,
|
||||||
|
importFollows,
|
||||||
deleteAccount,
|
deleteAccount,
|
||||||
changePassword,
|
changePassword,
|
||||||
fetchFollowRequests,
|
fetchFollowRequests,
|
||||||
approveUser,
|
approveUser,
|
||||||
denyUser,
|
denyUser,
|
||||||
suggestions,
|
suggestions,
|
||||||
markNotificationsAsSeen
|
markNotificationsAsSeen,
|
||||||
|
fetchFavoritedByUsers,
|
||||||
|
fetchRebloggedByUsers,
|
||||||
|
reportUser
|
||||||
}
|
}
|
||||||
|
|
||||||
export default apiService
|
export default apiService
|
||||||
|
|
|
@ -101,17 +101,27 @@ 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})
|
||||||
|
|
||||||
|
const fetchFavoritedByUsers = (id) => apiService.fetchFavoritedByUsers({id})
|
||||||
|
const fetchRebloggedByUsers = (id) => apiService.fetchRebloggedByUsers({id})
|
||||||
|
const reportUser = (params) => apiService.reportUser({credentials, ...params})
|
||||||
|
|
||||||
|
const favorite = (id) => apiService.favorite({id, credentials})
|
||||||
|
const unfavorite = (id) => apiService.unfavorite({id, credentials})
|
||||||
|
const retweet = (id) => apiService.retweet({id, credentials})
|
||||||
|
const unretweet = (id) => apiService.unretweet({id, credentials})
|
||||||
|
|
||||||
const backendInteractorServiceInstance = {
|
const backendInteractorServiceInstance = {
|
||||||
fetchStatus,
|
fetchStatus,
|
||||||
fetchConversation,
|
fetchConversation,
|
||||||
|
@ -147,12 +157,20 @@ const backendInteractorService = (credentials) => {
|
||||||
updateBanner,
|
updateBanner,
|
||||||
updateProfile,
|
updateProfile,
|
||||||
externalProfile,
|
externalProfile,
|
||||||
followImport,
|
importBlocks,
|
||||||
|
importFollows,
|
||||||
deleteAccount,
|
deleteAccount,
|
||||||
changePassword,
|
changePassword,
|
||||||
fetchFollowRequests,
|
fetchFollowRequests,
|
||||||
approveUser,
|
approveUser,
|
||||||
denyUser
|
denyUser,
|
||||||
|
fetchFavoritedByUsers,
|
||||||
|
fetchRebloggedByUsers,
|
||||||
|
reportUser,
|
||||||
|
favorite,
|
||||||
|
unfavorite,
|
||||||
|
retweet,
|
||||||
|
unretweet
|
||||||
}
|
}
|
||||||
|
|
||||||
return backendInteractorServiceInstance
|
return backendInteractorServiceInstance
|
||||||
|
|
|
@ -275,6 +275,9 @@ export const parseStatus = (data) => {
|
||||||
output.retweeted_status = parseStatus(retweetedStatus)
|
output.retweeted_status = parseStatus(retweetedStatus)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
output.favoritedBy = []
|
||||||
|
output.rebloggedBy = []
|
||||||
|
|
||||||
return output
|
return output
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -306,7 +309,7 @@ export const parseNotification = (data) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
output.created_at = new Date(data.created_at)
|
output.created_at = new Date(data.created_at)
|
||||||
output.id = data.id
|
output.id = parseInt(data.id)
|
||||||
|
|
||||||
return output
|
return output
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue