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

* upstream/develop: (29 commits)
  update api services
  use native scrollbar
  refactor using List component
  remove transition css
  update api endpoint url
  rewrite checkbox component
  fix panel title overflow issue in mobile
  fix double scrollbar display bug in mobile
  prevent parent scroll
  use custom scrollbar
  modal style improvements
  Update promisedRequest helper to support json payload
  Improve mobile layout
  Add a css class to the checkbox indicator
  update copy
  update generic error message
  add translations
  add error message
  reset modal state if api request is completed
  add processing state and close modal after api request is completed
  ...
This commit is contained in:
Henry Jameson 2019-05-05 11:42:42 +03:00
commit 680cb7eda7
26 changed files with 552 additions and 77 deletions

View file

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

View file

@ -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;
@ -793,4 +794,4 @@ nav {
background-color: var(--lightBg, $fallback--fg); background-color: var(--lightBg, $fallback--fg);
} }
} }
} }

View file

@ -46,6 +46,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>

View file

@ -1,10 +1,10 @@
import UserAvatar from '../user_avatar/user_avatar.vue' import UserAvatar from '../user_avatar/user_avatar.vue'
const AvatarList = { const AvatarList = {
props: ['avatars'], props: ['users'],
computed: { computed: {
slicedAvatars () { slicedUsers () {
return this.avatars ? this.avatars.slice(0, 15) : [] return this.users ? this.users.slice(0, 15) : []
} }
}, },
components: { components: {

View file

@ -1,7 +1,7 @@
<template> <template>
<div class="avatars"> <div class="avatars">
<div class="avatars-item" v-for="avatar in slicedAvatars"> <div class="avatars-item" v-for="user in slicedUsers">
<UserAvatar :src="avatar.profile_image_url" class="avatar-small" /> <UserAvatar :user="user" class="avatar-small" />
</div> </div>
</div> </div>
</template> </template>

View file

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

View file

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

View file

@ -1,25 +1,26 @@
<template> <template>
<nav class='nav-bar container' id="nav"> <div>
<div class='mobile-inner-nav' @click="scrollToTop()"> <nav class='nav-bar container' id="nav">
<div class='item'> <div class='mobile-inner-nav' @click="scrollToTop()">
<a href="#" class="mobile-nav-button" @click.stop.prevent="toggleMobileSidebar()"> <div class='item'>
<i class="button-icon icon-menu"></i> <a href="#" class="mobile-nav-button" @click.stop.prevent="toggleMobileSidebar()">
</a> <i class="button-icon icon-menu"></i>
<router-link class="site-name" :to="{ name: 'root' }" active-class="home">{{sitename}}</router-link> </a>
<router-link class="site-name" :to="{ name: 'root' }" active-class="home">{{sitename}}</router-link>
</div>
<div class='item right'>
<a class="mobile-nav-button" v-if="currentUser" href="#" @click.stop.prevent="openMobileNotifications()">
<i class="button-icon icon-bell-alt"></i>
<div class="alert-dot" v-if="unseenNotificationsCount"></div>
</a>
</div>
</div> </div>
<div class='item right'> </nav>
<a class="mobile-nav-button" v-if="currentUser" href="#" @click.stop.prevent="openMobileNotifications()">
<i class="button-icon icon-bell-alt"></i>
<div class="alert-dot" v-if="unseenNotificationsCount"></div>
</a>
</div>
</div>
<SideDrawer ref="sideDrawer" :logout="logout"/>
<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%);

View file

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

View file

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

View file

@ -263,13 +263,13 @@ const Status = {
} }
return this.status.summary_html + '<br />' + this.status.statusnet_html return this.status.summary_html + '<br />' + this.status.statusnet_html
}, },
combinedFavsAndRepeatsAvatars () { combinedFavsAndRepeatsUsers () {
// Use the status from the global status repository since favs and repeats are saved in it // Use the status from the global status repository since favs and repeats are saved in it
const combinedAvatars = [].concat( const combinedUsers = [].concat(
this.statusFromGlobalRepository.favoritedBy, this.statusFromGlobalRepository.favoritedBy,
this.statusFromGlobalRepository.rebloggedBy this.statusFromGlobalRepository.rebloggedBy
) )
return uniqBy(combinedAvatars, 'id') return uniqBy(combinedUsers, 'id')
} }
}, },
components: { components: {

View file

@ -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>
@ -134,7 +139,7 @@
</div> </div>
<transition name="fade"> <transition name="fade">
<div class="favs-repeated-users" v-if="combinedFavsAndRepeatsAvatars.length > 0 && isFocused"> <div class="favs-repeated-users" v-if="combinedFavsAndRepeatsUsers.length > 0 && isFocused">
<div class="stats"> <div class="stats">
<div class="stat-count" v-if="statusFromGlobalRepository.rebloggedBy && statusFromGlobalRepository.rebloggedBy.length > 0"> <div class="stat-count" v-if="statusFromGlobalRepository.rebloggedBy && statusFromGlobalRepository.rebloggedBy.length > 0">
<a class="stat-title">{{ $t('status.repeats') }}</a> <a class="stat-title">{{ $t('status.repeats') }}</a>
@ -145,7 +150,7 @@
<div class="stat-number">{{ statusFromGlobalRepository.favoritedBy.length }}</div> <div class="stat-number">{{ statusFromGlobalRepository.favoritedBy.length }}</div>
</div> </div>
<div class="avatar-row"> <div class="avatar-row">
<AvatarList :avatars='combinedFavsAndRepeatsAvatars'></AvatarList> <AvatarList :users="combinedFavsAndRepeatsUsers"></AvatarList>
</div> </div>
</div> </div>
</div> </div>

View file

@ -2,7 +2,7 @@ import StillImage from '../still-image/still-image.vue'
const UserAvatar = { const UserAvatar = {
props: [ props: [
'src', 'user',
'betterShadow', 'betterShadow',
'compact' 'compact'
], ],

View file

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

View file

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

View file

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

View 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

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

View file

@ -420,6 +420,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...",
@ -452,6 +453,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"

View file

@ -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,37 @@
"muted": "Silenciado", "muted": "Silenciado",
"per_day": "por día", "per_day": "por día",
"remote_follow": "Seguir", "remote_follow": "Seguir",
"statuses": "Estados" "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."
}, },
"who_to_follow": { "who_to_follow": {
"more": "Más", "more": "Más",

View file

@ -2,6 +2,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",
"gopher": "Gopher", "gopher": "Gopher",
@ -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 ses producha pendent limportacion daqueste 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 destatuts" "no_statuses": "Cap destatuts"
}, },
"status": { "status": {
"favorites": "Li a agradat",
"repeats": "A repetit",
"reply_to": "Respond a", "reply_to": "Respond a",
"replies_list": "Responsas:" "replies_list": "Responsas:"
}, },
@ -461,4 +477,4 @@
"TiB": "Tio" "TiB": "Tio"
} }
} }
} }

View file

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

30
src/modules/reports.js Normal file
View 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

View file

@ -50,6 +50,7 @@ const MASTODON_MEDIA_UPLOAD_URL = '/api/v1/media'
const MASTODON_STATUS_FAVORITEDBY_URL = id => `/api/v1/statuses/${id}/favourited_by` const MASTODON_STATUS_FAVORITEDBY_URL = id => `/api/v1/statuses/${id}/favourited_by`
const MASTODON_STATUS_REBLOGGEDBY_URL = id => `/api/v1/statuses/${id}/reblogged_by` const MASTODON_STATUS_REBLOGGEDBY_URL = id => `/api/v1/statuses/${id}/reblogged_by`
const MASTODON_PROFILE_UPDATE_URL = '/api/v1/accounts/update_credentials' 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'
@ -66,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()
@ -122,14 +140,11 @@ const updateBanner = ({credentials, banner}) => {
} }
const updateProfile = ({credentials, params}) => { const updateProfile = ({credentials, params}) => {
return promisedRequest(MASTODON_PROFILE_UPDATE_URL, { return promisedRequest({
headers: { url: MASTODON_PROFILE_UPDATE_URL,
'Accept': 'application/json',
'Content-Type': 'application/json',
...authHeaders(credentials)
},
method: 'PATCH', method: 'PATCH',
body: JSON.stringify(params) payload: params,
credentials
}) })
.then((data) => parseUser(data)) .then((data) => parseUser(data))
} }
@ -227,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))
} }
@ -651,26 +666,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))
} }
@ -715,11 +724,25 @@ const markNotificationsAsSeen = ({id, credentials}) => {
} }
const fetchFavoritedByUsers = ({id}) => { const fetchFavoritedByUsers = ({id}) => {
return promisedRequest(MASTODON_STATUS_FAVORITEDBY_URL(id)).then((users) => users.map(parseUser)) return promisedRequest({ url: MASTODON_STATUS_FAVORITEDBY_URL(id) }).then((users) => users.map(parseUser))
} }
const fetchRebloggedByUsers = ({id}) => { const fetchRebloggedByUsers = ({id}) => {
return promisedRequest(MASTODON_STATUS_REBLOGGEDBY_URL(id)).then((users) => users.map(parseUser)) 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 = {
@ -773,7 +796,8 @@ const apiService = {
suggestions, suggestions,
markNotificationsAsSeen, markNotificationsAsSeen,
fetchFavoritedByUsers, fetchFavoritedByUsers,
fetchRebloggedByUsers fetchRebloggedByUsers,
reportUser
} }
export default apiService export default apiService

View file

@ -115,6 +115,7 @@ const backendInteractorService = (credentials) => {
const fetchFavoritedByUsers = (id) => apiService.fetchFavoritedByUsers({id}) const fetchFavoritedByUsers = (id) => apiService.fetchFavoritedByUsers({id})
const fetchRebloggedByUsers = (id) => apiService.fetchRebloggedByUsers({id}) const fetchRebloggedByUsers = (id) => apiService.fetchRebloggedByUsers({id})
const reportUser = (params) => apiService.reportUser({credentials, ...params})
const backendInteractorServiceInstance = { const backendInteractorServiceInstance = {
fetchStatus, fetchStatus,
@ -159,7 +160,8 @@ const backendInteractorService = (credentials) => {
approveUser, approveUser,
denyUser, denyUser,
fetchFavoritedByUsers, fetchFavoritedByUsers,
fetchRebloggedByUsers fetchRebloggedByUsers,
reportUser
} }
return backendInteractorServiceInstance return backendInteractorServiceInstance

View file

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