Merge remote-tracking branch 'upstream/develop' into shigusegubu
* upstream/develop: (88 commits) Update font-size of username in UserCardContent component Re-do status header a bit, add more consistent spacing to status Fix JS error when no statuses returned Fix fetching error by tag #388: update naming properly Fix layout overflow issue Add a class to screen name Add back accidently removed logic Merge all slots of BasicUserCard into one Revert "Minor mobile layout improvement for BasicUserCard" Minor mobile layout improvement for BasicUserCard Use native filter function Shorten a classname Improve mobile layout of user card Update naming Add back some css Remove legacy class names in BasicUserCard Remove UserCard Migrate UserCard to FollowCard and FollowRequestCard Add FollowRequestCard component ...
This commit is contained in:
commit
a129ef2e07
78 changed files with 1402 additions and 606 deletions
|
|
@ -28,6 +28,7 @@
|
||||||
"sass-loader": "^4.0.2",
|
"sass-loader": "^4.0.2",
|
||||||
"vue": "^2.5.13",
|
"vue": "^2.5.13",
|
||||||
"vue-chat-scroll": "^1.2.1",
|
"vue-chat-scroll": "^1.2.1",
|
||||||
|
"vue-compose": "^0.7.1",
|
||||||
"vue-i18n": "^7.3.2",
|
"vue-i18n": "^7.3.2",
|
||||||
"vue-router": "^3.0.1",
|
"vue-router": "^3.0.1",
|
||||||
"vue-template-compiler": "^2.3.4",
|
"vue-template-compiler": "^2.3.4",
|
||||||
|
|
|
||||||
24
src/App.scss
24
src/App.scss
|
|
@ -628,6 +628,16 @@ nav {
|
||||||
color: $fallback--faint;
|
color: $fallback--faint;
|
||||||
color: var(--faint, $fallback--faint);
|
color: var(--faint, $fallback--faint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.faint-link {
|
||||||
|
color: $fallback--faint;
|
||||||
|
color: var(--faint, $fallback--faint);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media all and (min-width: 800px) {
|
@media all and (min-width: 800px) {
|
||||||
.logo {
|
.logo {
|
||||||
opacity: 1 !important;
|
opacity: 1 !important;
|
||||||
|
|
@ -661,6 +671,10 @@ nav {
|
||||||
border-radius: var(--inputRadius, $fallback--inputRadius);
|
border-radius: var(--inputRadius, $fallback--inputRadius);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button-icon {
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes shakeError {
|
@keyframes shakeError {
|
||||||
0% {
|
0% {
|
||||||
transform: translateX(0);
|
transform: translateX(0);
|
||||||
|
|
@ -705,16 +719,6 @@ nav {
|
||||||
margin: 0.5em 0 0.5em 0;
|
margin: 0.5em 0 0.5em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-icon {
|
|
||||||
font-size: 1.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status .status-actions {
|
|
||||||
div {
|
|
||||||
max-width: 4em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu-button {
|
.menu-button {
|
||||||
display: block;
|
display: block;
|
||||||
margin-right: 0.8em;
|
margin-right: 0.8em;
|
||||||
|
|
|
||||||
|
|
@ -88,7 +88,7 @@
|
||||||
|
|
||||||
.attachment {
|
.attachment {
|
||||||
position: relative;
|
position: relative;
|
||||||
margin: 0.5em 0.5em 0em 0em;
|
margin-top: 0.5em;
|
||||||
align-self: flex-start;
|
align-self: flex-start;
|
||||||
line-height: 0;
|
line-height: 0;
|
||||||
|
|
||||||
|
|
|
||||||
28
src/components/basic_user_card/basic_user_card.js
Normal file
28
src/components/basic_user_card/basic_user_card.js
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
import UserCardContent from '../user_card_content/user_card_content.vue'
|
||||||
|
import UserAvatar from '../user_avatar/user_avatar.vue'
|
||||||
|
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
|
||||||
|
|
||||||
|
const BasicUserCard = {
|
||||||
|
props: [
|
||||||
|
'user'
|
||||||
|
],
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
userExpanded: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
UserCardContent,
|
||||||
|
UserAvatar
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
toggleUserExpanded () {
|
||||||
|
this.userExpanded = !this.userExpanded
|
||||||
|
},
|
||||||
|
userProfileLink (user) {
|
||||||
|
return generateProfileLink(user.id, user.screen_name, this.$store.state.instance.restrictedNicknames)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BasicUserCard
|
||||||
79
src/components/basic_user_card/basic_user_card.vue
Normal file
79
src/components/basic_user_card/basic_user_card.vue
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
<template>
|
||||||
|
<div class="user-card">
|
||||||
|
<router-link :to="userProfileLink(user)">
|
||||||
|
<UserAvatar class="avatar" @click.prevent.native="toggleUserExpanded" :src="user.profile_image_url"/>
|
||||||
|
</router-link>
|
||||||
|
<div class="user-card-expanded-content" v-if="userExpanded">
|
||||||
|
<user-card-content :user="user" :switcher="false"></user-card-content>
|
||||||
|
</div>
|
||||||
|
<div class="user-card-collapsed-content" v-else>
|
||||||
|
<div :title="user.name" class="user-card-user-name">
|
||||||
|
<span v-if="user.name_html" v-html="user.name_html"></span>
|
||||||
|
<span v-else>{{ user.name }}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<router-link class="user-card-screen-name" :to="userProfileLink(user)">
|
||||||
|
@{{user.screen_name}}
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./basic_user_card.js"></script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@import '../../_variables.scss';
|
||||||
|
|
||||||
|
.user-card {
|
||||||
|
display: flex;
|
||||||
|
flex: 1 0;
|
||||||
|
padding-top: 0.6em;
|
||||||
|
padding-right: 1em;
|
||||||
|
padding-bottom: 0.6em;
|
||||||
|
padding-left: 1em;
|
||||||
|
border-bottom: 1px solid;
|
||||||
|
margin: 0;
|
||||||
|
border-bottom-color: $fallback--border;
|
||||||
|
border-bottom-color: var(--border, $fallback--border);
|
||||||
|
|
||||||
|
&-collapsed-content {
|
||||||
|
margin-left: 0.7em;
|
||||||
|
text-align: left;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-user-name {
|
||||||
|
img {
|
||||||
|
object-fit: contain;
|
||||||
|
height: 16px;
|
||||||
|
width: 16px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-expanded-content {
|
||||||
|
flex: 1;
|
||||||
|
margin-left: 0.7em;
|
||||||
|
border-radius: $fallback--panelRadius;
|
||||||
|
border-radius: var(--panelRadius, $fallback--panelRadius);
|
||||||
|
border-style: solid;
|
||||||
|
border-color: $fallback--border;
|
||||||
|
border-color: var(--border, $fallback--border);
|
||||||
|
border-width: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.panel-heading {
|
||||||
|
background: transparent;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
37
src/components/block_card/block_card.js
Normal file
37
src/components/block_card/block_card.js
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
import BasicUserCard from '../basic_user_card/basic_user_card.vue'
|
||||||
|
|
||||||
|
const BlockCard = {
|
||||||
|
props: ['userId'],
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
progress: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
user () {
|
||||||
|
return this.$store.getters.userById(this.userId)
|
||||||
|
},
|
||||||
|
blocked () {
|
||||||
|
return this.user.statusnet_blocking
|
||||||
|
}
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
BasicUserCard
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
unblockUser () {
|
||||||
|
this.progress = true
|
||||||
|
this.$store.dispatch('unblockUser', this.user.id).then(() => {
|
||||||
|
this.progress = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
blockUser () {
|
||||||
|
this.progress = true
|
||||||
|
this.$store.dispatch('blockUser', this.user.id).then(() => {
|
||||||
|
this.progress = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BlockCard
|
||||||
34
src/components/block_card/block_card.vue
Normal file
34
src/components/block_card/block_card.vue
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
<template>
|
||||||
|
<basic-user-card :user="user">
|
||||||
|
<div class="block-card-content-container">
|
||||||
|
<button class="btn btn-default" @click="unblockUser" :disabled="progress" v-if="blocked">
|
||||||
|
<template v-if="progress">
|
||||||
|
{{ $t('user_card.unblock_progress') }}
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
{{ $t('user_card.unblock') }}
|
||||||
|
</template>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-default" @click="blockUser" :disabled="progress" v-else>
|
||||||
|
<template v-if="progress">
|
||||||
|
{{ $t('user_card.block_progress') }}
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
{{ $t('user_card.block') }}
|
||||||
|
</template>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</basic-user-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./block_card.js"></script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.block-card-content-container {
|
||||||
|
margin-top: 0.5em;
|
||||||
|
text-align: right;
|
||||||
|
button {
|
||||||
|
width: 10em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
45
src/components/follow_card/follow_card.js
Normal file
45
src/components/follow_card/follow_card.js
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
import BasicUserCard from '../basic_user_card/basic_user_card.vue'
|
||||||
|
import { requestFollow, requestUnfollow } from '../../services/follow_manipulate/follow_manipulate'
|
||||||
|
|
||||||
|
const FollowCard = {
|
||||||
|
props: [
|
||||||
|
'user',
|
||||||
|
'noFollowsYou'
|
||||||
|
],
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
inProgress: false,
|
||||||
|
requestSent: false,
|
||||||
|
updated: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
BasicUserCard
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isMe () { return this.$store.state.users.currentUser.id === this.user.id },
|
||||||
|
following () { return this.updated ? this.updated.following : this.user.following },
|
||||||
|
showFollow () {
|
||||||
|
return !this.following || this.updated && !this.updated.following
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
followUser () {
|
||||||
|
this.inProgress = true
|
||||||
|
requestFollow(this.user, this.$store).then(({ sent, updated }) => {
|
||||||
|
this.inProgress = false
|
||||||
|
this.requestSent = sent
|
||||||
|
this.updated = updated
|
||||||
|
})
|
||||||
|
},
|
||||||
|
unfollowUser () {
|
||||||
|
this.inProgress = true
|
||||||
|
requestUnfollow(this.user, this.$store).then(({ updated }) => {
|
||||||
|
this.inProgress = false
|
||||||
|
this.updated = updated
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FollowCard
|
||||||
53
src/components/follow_card/follow_card.vue
Normal file
53
src/components/follow_card/follow_card.vue
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
<template>
|
||||||
|
<basic-user-card :user="user">
|
||||||
|
<div class="follow-card-content-container">
|
||||||
|
<span class="faint" v-if="!noFollowsYou && user.follows_you">
|
||||||
|
{{ isMe ? $t('user_card.its_you') : $t('user_card.follows_you') }}
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
v-if="showFollow"
|
||||||
|
class="btn btn-default"
|
||||||
|
@click="followUser"
|
||||||
|
:disabled="inProgress"
|
||||||
|
:title="requestSent ? $t('user_card.follow_again') : ''"
|
||||||
|
>
|
||||||
|
<template v-if="inProgress">
|
||||||
|
{{ $t('user_card.follow_progress') }}
|
||||||
|
</template>
|
||||||
|
<template v-else-if="requestSent">
|
||||||
|
{{ $t('user_card.follow_sent') }}
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
{{ $t('user_card.follow') }}
|
||||||
|
</template>
|
||||||
|
</button>
|
||||||
|
<button v-if="following" class="btn btn-default pressed" @click="unfollowUser" :disabled="inProgress">
|
||||||
|
<template v-if="inProgress">
|
||||||
|
{{ $t('user_card.follow_progress') }}
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
{{ $t('user_card.follow_unfollow') }}
|
||||||
|
</template>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</basic-user-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./follow_card.js"></script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.follow-card-content-container {
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
line-height: 1.5em;
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
margin-top: 0.5em;
|
||||||
|
margin-left: auto;
|
||||||
|
width: 10em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -1,68 +0,0 @@
|
||||||
import UserCard from '../user_card/user_card.vue'
|
|
||||||
|
|
||||||
const FollowList = {
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
loading: false,
|
|
||||||
bottomedOut: false,
|
|
||||||
error: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
props: ['userId', 'showFollowers'],
|
|
||||||
created () {
|
|
||||||
window.addEventListener('scroll', this.scrollLoad)
|
|
||||||
if (this.entries.length === 0) {
|
|
||||||
this.fetchEntries()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
destroyed () {
|
|
||||||
window.removeEventListener('scroll', this.scrollLoad)
|
|
||||||
this.$store.dispatch('clearFriendsAndFollowers', this.userId)
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
user () {
|
|
||||||
return this.$store.getters.userById(this.userId)
|
|
||||||
},
|
|
||||||
entries () {
|
|
||||||
return this.showFollowers ? this.user.followers : this.user.friends
|
|
||||||
},
|
|
||||||
showFollowsYou () {
|
|
||||||
return !this.showFollowers || (this.showFollowers && this.userId !== this.$store.state.users.currentUser.id)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
fetchEntries () {
|
|
||||||
if (!this.loading) {
|
|
||||||
const command = this.showFollowers ? 'addFollowers' : 'addFriends'
|
|
||||||
this.loading = true
|
|
||||||
this.$store.dispatch(command, this.userId).then(entries => {
|
|
||||||
this.error = false
|
|
||||||
this.loading = false
|
|
||||||
this.bottomedOut = entries.length === 0
|
|
||||||
}).catch(() => {
|
|
||||||
this.error = true
|
|
||||||
this.loading = false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
scrollLoad (e) {
|
|
||||||
const bodyBRect = document.body.getBoundingClientRect()
|
|
||||||
const height = Math.max(bodyBRect.height, -(bodyBRect.y))
|
|
||||||
if (this.loading === false &&
|
|
||||||
this.bottomedOut === false &&
|
|
||||||
this.$el.offsetHeight > 0 &&
|
|
||||||
(window.innerHeight + window.pageYOffset) >= (height - 750)
|
|
||||||
) {
|
|
||||||
this.fetchEntries()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
'user': 'fetchEntries'
|
|
||||||
},
|
|
||||||
components: {
|
|
||||||
UserCard
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default FollowList
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="follow-list">
|
|
||||||
<user-card
|
|
||||||
v-for="entry in entries"
|
|
||||||
:key="entry.id" :user="entry"
|
|
||||||
:noFollowsYou="!showFollowsYou"
|
|
||||||
/>
|
|
||||||
<div class="text-center panel-footer">
|
|
||||||
<a v-if="error" @click="fetchEntries" class="alert error">
|
|
||||||
{{$t('general.generic_error')}}
|
|
||||||
</a>
|
|
||||||
<i v-else-if="loading" class="icon-spin3 animate-spin"/>
|
|
||||||
<span v-else-if="bottomedOut"></span>
|
|
||||||
<a v-else @click="fetchEntries">{{$t('general.more')}}</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script src="./follow_list.js"></script>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
|
|
||||||
.follow-list {
|
|
||||||
.panel-footer {
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
20
src/components/follow_request_card/follow_request_card.js
Normal file
20
src/components/follow_request_card/follow_request_card.js
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
import BasicUserCard from '../basic_user_card/basic_user_card.vue'
|
||||||
|
|
||||||
|
const FollowRequestCard = {
|
||||||
|
props: ['user'],
|
||||||
|
components: {
|
||||||
|
BasicUserCard
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
approveUser () {
|
||||||
|
this.$store.state.api.backendInteractor.approveUser(this.user.id)
|
||||||
|
this.$store.dispatch('removeFollowRequest', this.user)
|
||||||
|
},
|
||||||
|
denyUser () {
|
||||||
|
this.$store.state.api.backendInteractor.denyUser(this.user.id)
|
||||||
|
this.$store.dispatch('removeFollowRequest', this.user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FollowRequestCard
|
||||||
29
src/components/follow_request_card/follow_request_card.vue
Normal file
29
src/components/follow_request_card/follow_request_card.vue
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
<template>
|
||||||
|
<basic-user-card :user="user">
|
||||||
|
<div class="follow-request-card-content-container">
|
||||||
|
<button class="btn btn-default" @click="approveUser">{{ $t('user_card.approve') }}</button>
|
||||||
|
<button class="btn btn-default" @click="denyUser">{{ $t('user_card.deny') }}</button>
|
||||||
|
</div>
|
||||||
|
</basic-user-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./follow_request_card.js"></script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.follow-request-card-content-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
button {
|
||||||
|
margin-top: 0.5em;
|
||||||
|
margin-right: 0.5em;
|
||||||
|
flex: 1 1;
|
||||||
|
max-width: 12em;
|
||||||
|
min-width: 8em;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -1,22 +1,13 @@
|
||||||
import UserCard from '../user_card/user_card.vue'
|
import FollowRequestCard from '../follow_request_card/follow_request_card.vue'
|
||||||
|
|
||||||
const FollowRequests = {
|
const FollowRequests = {
|
||||||
components: {
|
components: {
|
||||||
UserCard
|
FollowRequestCard
|
||||||
},
|
|
||||||
created () {
|
|
||||||
this.updateRequests()
|
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
requests () {
|
requests () {
|
||||||
return this.$store.state.api.followRequests
|
return this.$store.state.api.followRequests
|
||||||
}
|
}
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
updateRequests () {
|
|
||||||
this.$store.state.api.backendInteractor.fetchFollowRequests()
|
|
||||||
.then((requests) => { this.$store.commit('setFollowRequests', requests) })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
{{$t('nav.friend_requests')}}
|
{{$t('nav.friend_requests')}}
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<user-card v-for="request in requests" :key="request.id" :user="request" :showFollows="false" :showApproval="true"></user-card>
|
<FollowRequestCard v-for="request in requests" :key="request.id" :user="request"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,9 @@
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
// to make failed images a bit more noticeable on chromium
|
// to make failed images a bit more noticeable on chromium
|
||||||
min-width: 2em;
|
min-width: 2em;
|
||||||
|
&:last-child {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-attachment {
|
.image-attachment {
|
||||||
|
|
|
||||||
|
|
@ -24,10 +24,6 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
// TODO: clean up the random margins in attachments, this makes preview line
|
|
||||||
// up with attachments...
|
|
||||||
margin-right: 0.5em;
|
|
||||||
|
|
||||||
.card-image {
|
.card-image {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
width: 120px;
|
width: 120px;
|
||||||
|
|
|
||||||
37
src/components/mute_card/mute_card.js
Normal file
37
src/components/mute_card/mute_card.js
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
import BasicUserCard from '../basic_user_card/basic_user_card.vue'
|
||||||
|
|
||||||
|
const MuteCard = {
|
||||||
|
props: ['userId'],
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
progress: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
user () {
|
||||||
|
return this.$store.getters.userById(this.userId)
|
||||||
|
},
|
||||||
|
muted () {
|
||||||
|
return this.user.muted
|
||||||
|
}
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
BasicUserCard
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
unmuteUser () {
|
||||||
|
this.progress = true
|
||||||
|
this.$store.dispatch('unmuteUser', this.user.id).then(() => {
|
||||||
|
this.progress = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
muteUser () {
|
||||||
|
this.progress = true
|
||||||
|
this.$store.dispatch('muteUser', this.user.id).then(() => {
|
||||||
|
this.progress = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MuteCard
|
||||||
24
src/components/mute_card/mute_card.vue
Normal file
24
src/components/mute_card/mute_card.vue
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
<template>
|
||||||
|
<basic-user-card :user="user">
|
||||||
|
<template slot="secondary-area">
|
||||||
|
<button class="btn btn-default" @click="unmuteUser" :disabled="progress" v-if="muted">
|
||||||
|
<template v-if="progress">
|
||||||
|
{{ $t('user_card.unmute_progress') }}
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
{{ $t('user_card.unmute') }}
|
||||||
|
</template>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-default" @click="muteUser" :disabled="progress" v-else>
|
||||||
|
<template v-if="progress">
|
||||||
|
{{ $t('user_card.mute_progress') }}
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
{{ $t('user_card.mute') }}
|
||||||
|
</template>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
</basic-user-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./mute_card.js"></script>
|
||||||
|
|
@ -1,10 +1,23 @@
|
||||||
|
import followRequestFetcher from '../../services/follow_request_fetcher/follow_request_fetcher.service'
|
||||||
|
|
||||||
const NavPanel = {
|
const NavPanel = {
|
||||||
|
created () {
|
||||||
|
if (this.currentUser && this.currentUser.locked) {
|
||||||
|
const store = this.$store
|
||||||
|
const credentials = store.state.users.currentUser.credentials
|
||||||
|
|
||||||
|
followRequestFetcher.startFetching({ store, credentials })
|
||||||
|
}
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
currentUser () {
|
currentUser () {
|
||||||
return this.$store.state.users.currentUser
|
return this.$store.state.users.currentUser
|
||||||
},
|
},
|
||||||
chat () {
|
chat () {
|
||||||
return this.$store.state.chat.channel
|
return this.$store.state.chat.channel
|
||||||
|
},
|
||||||
|
followRequestCount () {
|
||||||
|
return this.$store.state.api.followRequests.length
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,8 @@
|
||||||
<li v-if='currentUser && currentUser.locked'>
|
<li v-if='currentUser && currentUser.locked'>
|
||||||
<router-link :to="{ name: 'friend-requests' }">
|
<router-link :to="{ name: 'friend-requests' }">
|
||||||
{{ $t("nav.friend_requests")}}
|
{{ $t("nav.friend_requests")}}
|
||||||
<span v-if='currentUser.follow_request_count > 0' class="badge follow-request-count">
|
<span v-if='followRequestCount > 0' class="badge follow-request-count">
|
||||||
{{currentUser.follow_request_count}}
|
{{followRequestCount}}
|
||||||
</span>
|
</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
<div class='text-fields'>
|
<div class='text-fields'>
|
||||||
<div class='form-group' :class="{ 'form-group--error': $v.user.username.$error }">
|
<div class='form-group' :class="{ 'form-group--error': $v.user.username.$error }">
|
||||||
<label class='form--label' for='sign-up-username'>{{$t('login.username')}}</label>
|
<label class='form--label' for='sign-up-username'>{{$t('login.username')}}</label>
|
||||||
<input :disabled="isPending" v-model.trim='$v.user.username.$model' class='form-control' id='sign-up-username' placeholder='e.g. lain'>
|
<input :disabled="isPending" v-model.trim='$v.user.username.$model' class='form-control' id='sign-up-username' :placeholder="$t('registration.username_placeholder')">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-error" v-if="$v.user.username.$dirty">
|
<div class="form-error" v-if="$v.user.username.$dirty">
|
||||||
<ul>
|
<ul>
|
||||||
|
|
@ -21,7 +21,7 @@
|
||||||
|
|
||||||
<div class='form-group' :class="{ 'form-group--error': $v.user.fullname.$error }">
|
<div class='form-group' :class="{ 'form-group--error': $v.user.fullname.$error }">
|
||||||
<label class='form--label' for='sign-up-fullname'>{{$t('registration.fullname')}}</label>
|
<label class='form--label' for='sign-up-fullname'>{{$t('registration.fullname')}}</label>
|
||||||
<input :disabled="isPending" v-model.trim='$v.user.fullname.$model' class='form-control' id='sign-up-fullname' placeholder='e.g. Lain Iwakura'>
|
<input :disabled="isPending" v-model.trim='$v.user.fullname.$model' class='form-control' id='sign-up-fullname' :placeholder="$t('registration.fullname_placeholder')">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-error" v-if="$v.user.fullname.$dirty">
|
<div class="form-error" v-if="$v.user.fullname.$dirty">
|
||||||
<ul>
|
<ul>
|
||||||
|
|
@ -44,8 +44,8 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class='form-group'>
|
<div class='form-group'>
|
||||||
<label class='form--label' for='bio'>{{$t('registration.bio')}}</label>
|
<label class='form--label' for='bio'>{{$t('registration.bio')}} ({{$t('general.optional')}})</label>
|
||||||
<input :disabled="isPending" v-model='user.bio' class='form-control' id='bio'>
|
<textarea :disabled="isPending" v-model='user.bio' class='form-control' id='bio' :placeholder="$t('registration.bio_placeholder')"></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class='form-group' :class="{ 'form-group--error': $v.user.password.$error }">
|
<div class='form-group' :class="{ 'form-group--error': $v.user.password.$error }">
|
||||||
|
|
@ -139,6 +139,10 @@ $validations-cRed: #f04124;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
min-height: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
.form-group {
|
.form-group {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ const settings = {
|
||||||
return {
|
return {
|
||||||
hideAttachmentsLocal: user.hideAttachments,
|
hideAttachmentsLocal: user.hideAttachments,
|
||||||
hideAttachmentsInConvLocal: user.hideAttachmentsInConv,
|
hideAttachmentsInConvLocal: user.hideAttachmentsInConv,
|
||||||
|
maxThumbnails: user.maxThumbnails,
|
||||||
hideNsfwLocal: user.hideNsfw,
|
hideNsfwLocal: user.hideNsfw,
|
||||||
useOneClickNsfw: user.useOneClickNsfw,
|
useOneClickNsfw: user.useOneClickNsfw,
|
||||||
hideISPLocal: user.hideISP,
|
hideISPLocal: user.hideISP,
|
||||||
|
|
@ -186,6 +187,10 @@ const settings = {
|
||||||
},
|
},
|
||||||
useContainFit (value) {
|
useContainFit (value) {
|
||||||
this.$store.dispatch('setOption', { name: 'useContainFit', value })
|
this.$store.dispatch('setOption', { name: 'useContainFit', value })
|
||||||
|
},
|
||||||
|
maxThumbnails (value) {
|
||||||
|
value = this.maxThumbnails = Math.floor(Math.max(value, 0))
|
||||||
|
this.$store.dispatch('setOption', { name: 'maxThumbnails', value })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -136,6 +136,10 @@
|
||||||
<input type="checkbox" id="hideAttachmentsInConv" v-model="hideAttachmentsInConvLocal">
|
<input type="checkbox" id="hideAttachmentsInConv" v-model="hideAttachmentsInConvLocal">
|
||||||
<label for="hideAttachmentsInConv">{{$t('settings.hide_attachments_in_convo')}}</label>
|
<label for="hideAttachmentsInConv">{{$t('settings.hide_attachments_in_convo')}}</label>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<label for="maxThumbnails">{{$t('settings.max_thumbnails')}}</label>
|
||||||
|
<input class="number-input" type="number" id="maxThumbnails" v-model.number="maxThumbnails" min="0" step="1">
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<input type="checkbox" id="hideNsfw" v-model="hideNsfwLocal">
|
<input type="checkbox" id="hideNsfw" v-model="hideNsfwLocal">
|
||||||
<label for="hideNsfw">{{$t('settings.nsfw_clickthrough')}}</label>
|
<label for="hideNsfw">{{$t('settings.nsfw_clickthrough')}}</label>
|
||||||
|
|
@ -146,7 +150,7 @@
|
||||||
<label for="preloadImage">{{$t('settings.preload_images')}}</label>
|
<label for="preloadImage">{{$t('settings.preload_images')}}</label>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<input type="checkbox" id="useOneClickNsfw" v-model="useOneClickNsfw">
|
<input :disabled="!hideNsfwLocal" type="checkbox" id="useOneClickNsfw" v-model="useOneClickNsfw">
|
||||||
<label for="useOneClickNsfw">{{$t('settings.use_one_click_nsfw')}}</label>
|
<label for="useOneClickNsfw">{{$t('settings.use_one_click_nsfw')}}</label>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
@ -316,6 +320,10 @@
|
||||||
min-width: 10em;
|
min-width: 10em;
|
||||||
padding: 0 2em;
|
padding: 0 2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.number-input {
|
||||||
|
max-width: 6em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.select-multiple {
|
.select-multiple {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,9 @@ const SideDrawer = {
|
||||||
},
|
},
|
||||||
sitename () {
|
sitename () {
|
||||||
return this.$store.state.instance.name
|
return this.$store.state.instance.name
|
||||||
|
},
|
||||||
|
followRequestCount () {
|
||||||
|
return this.$store.state.api.followRequests.length
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
|
||||||
|
|
@ -45,8 +45,8 @@
|
||||||
<li v-if="currentUser && currentUser.locked" @click="toggleDrawer">
|
<li v-if="currentUser && currentUser.locked" @click="toggleDrawer">
|
||||||
<router-link to='/friend-requests'>
|
<router-link to='/friend-requests'>
|
||||||
{{ $t("nav.friend_requests") }}
|
{{ $t("nav.friend_requests") }}
|
||||||
<span v-if='currentUser.follow_request_count > 0' class="badge follow-request-count">
|
<span v-if='followRequestCount > 0' class="badge follow-request-count">
|
||||||
{{currentUser.follow_request_count}}
|
{{followRequestCount}}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ const Status = {
|
||||||
'highlight',
|
'highlight',
|
||||||
'compact',
|
'compact',
|
||||||
'replies',
|
'replies',
|
||||||
'noReplyLinks',
|
'isPreview',
|
||||||
'noHeading',
|
'noHeading',
|
||||||
'inlineExpanded'
|
'inlineExpanded'
|
||||||
],
|
],
|
||||||
|
|
@ -40,8 +40,7 @@ const Status = {
|
||||||
expandingSubject: typeof this.$store.state.config.collapseMessageWithSubject === 'undefined'
|
expandingSubject: typeof this.$store.state.config.collapseMessageWithSubject === 'undefined'
|
||||||
? !this.$store.state.instance.collapseMessageWithSubject
|
? !this.$store.state.instance.collapseMessageWithSubject
|
||||||
: !this.$store.state.config.collapseMessageWithSubject,
|
: !this.$store.state.config.collapseMessageWithSubject,
|
||||||
betterShadow: this.$store.state.interface.browserSupport.cssFilter,
|
betterShadow: this.$store.state.interface.browserSupport.cssFilter
|
||||||
maxAttachments: 9
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
|
@ -225,7 +224,7 @@ const Status = {
|
||||||
attachmentSize () {
|
attachmentSize () {
|
||||||
if ((this.$store.state.config.hideAttachments && !this.inConversation) ||
|
if ((this.$store.state.config.hideAttachments && !this.inConversation) ||
|
||||||
(this.$store.state.config.hideAttachmentsInConv && this.inConversation) ||
|
(this.$store.state.config.hideAttachmentsInConv && this.inConversation) ||
|
||||||
(this.status.attachments.length > this.maxAttachments)) {
|
(this.status.attachments.length > this.maxThumbnails)) {
|
||||||
return 'hide'
|
return 'hide'
|
||||||
} else if (this.compact) {
|
} else if (this.compact) {
|
||||||
return 'small'
|
return 'small'
|
||||||
|
|
@ -249,6 +248,9 @@ const Status = {
|
||||||
return this.status.attachments.filter(
|
return this.status.attachments.filter(
|
||||||
file => !fileType.fileMatchesSomeType(this.galleryTypes, file)
|
file => !fileType.fileMatchesSomeType(this.galleryTypes, file)
|
||||||
)
|
)
|
||||||
|
},
|
||||||
|
maxThumbnails () {
|
||||||
|
return this.$store.state.config.maxThumbnails
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="status-el" v-if="!hideStatus" :class="[{ 'status-el_focused': isFocused }, { 'status-conversation': inlineExpanded }]">
|
<div class="status-el" v-if="!hideStatus" :class="[{ 'status-el_focused': isFocused }, { 'status-conversation': inlineExpanded }]">
|
||||||
<template v-if="muted && !noReplyLinks">
|
<template v-if="muted && !isPreview">
|
||||||
<div class="media status container muted">
|
<div class="media status container muted">
|
||||||
<small>
|
<small>
|
||||||
<router-link :to="userProfileLink">
|
<router-link :to="userProfileLink">
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div v-if="retweet && !noHeading" :class="[repeaterClass, { highlighted: repeaterStyle }]" :style="[repeaterStyle]" class="media container retweet-info">
|
<div v-if="retweet && !noHeading" :class="[repeaterClass, { highlighted: repeaterStyle }]" :style="[repeaterStyle]" class="media container retweet-info">
|
||||||
<UserAvatar v-if="retweet" :betterShadow="betterShadow" :src="statusoid.user.profile_image_url_original"/>
|
<UserAvatar class="media-left" v-if="retweet" :betterShadow="betterShadow" :src="statusoid.user.profile_image_url_original"/>
|
||||||
<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"/>
|
||||||
|
|
@ -31,57 +31,69 @@
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
<div class="status-body">
|
<div class="status-body">
|
||||||
<div class="usercard media-body" v-if="userExpanded">
|
<div class="usercard" v-if="userExpanded">
|
||||||
<user-card-content :user="status.user" :switcher="false"></user-card-content>
|
<user-card-content :user="status.user" :switcher="false"></user-card-content>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!noHeading" class="media-body container media-heading">
|
<div v-if="!noHeading" class="media-heading">
|
||||||
<div class="media-heading-left">
|
<div class="heading-name-row">
|
||||||
<div class="name-and-links">
|
<div class="name-and-account-name">
|
||||||
<h4 class="user-name" v-if="status.user.name_html" v-html="status.user.name_html"></h4>
|
<h4 class="user-name" v-if="status.user.name_html" v-html="status.user.name_html"></h4>
|
||||||
<h4 class="user-name" v-else>{{status.user.name}}</h4>
|
<h4 class="user-name" v-else>{{status.user.name}}</h4>
|
||||||
<span class="links">
|
<router-link class="account-name" :to="userProfileLink">
|
||||||
<router-link :to="userProfileLink">
|
|
||||||
{{status.user.screen_name}}
|
{{status.user.screen_name}}
|
||||||
</router-link>
|
</router-link>
|
||||||
<span v-if="isReply" class="faint reply-info">
|
|
||||||
<i class="icon-right-open"></i>
|
|
||||||
<router-link :to="replyProfileLink">
|
|
||||||
{{replyToName}}
|
|
||||||
</router-link>
|
|
||||||
</span>
|
|
||||||
<a v-if="isReply && !noReplyLinks" href="#" @click.prevent="gotoOriginal(status.in_reply_to_status_id)" :aria-label="$t('tool_tip.reply')">
|
|
||||||
<i class="button-icon icon-reply" @mouseenter="replyEnter(status.in_reply_to_status_id, $event)" @mouseout="replyLeave()"></i>
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<h4 class="replies" v-if="inConversation && !noReplyLinks">
|
|
||||||
<small v-if="replies.length">Replies:</small>
|
<span class="heading-right">
|
||||||
<small class="reply-link" v-bind:key="reply.id" v-for="reply in replies">
|
<router-link class="timeago faint-link" :to="{ name: 'conversation', params: { id: status.id } }">
|
||||||
<a href="#" @click.prevent="gotoOriginal(reply.id)" @mouseenter="replyEnter(reply.id, $event)" @mouseout="replyLeave()">{{reply.name}} </a>
|
|
||||||
</small>
|
|
||||||
</h4>
|
|
||||||
</div>
|
|
||||||
<div class="media-heading-right">
|
|
||||||
<router-link class="timeago" :to="{ name: 'conversation', params: { id: status.id } }">
|
|
||||||
<timeago :since="status.created_at" :auto-update="60"></timeago>
|
<timeago :since="status.created_at" :auto-update="60"></timeago>
|
||||||
</router-link>
|
</router-link>
|
||||||
<div class="button-icon visibility-icon" v-if="status.visibility">
|
<div class="button-icon visibility-icon" v-if="status.visibility">
|
||||||
<i :class="visibilityIcon(status.visibility)" :title="status.visibility | capitalize"></i>
|
<i :class="visibilityIcon(status.visibility)" :title="status.visibility | capitalize"></i>
|
||||||
</div>
|
</div>
|
||||||
<a :href="status.external_url" target="_blank" v-if="!status.is_local" class="source_url" title="Source">
|
<a :href="status.external_url" target="_blank" v-if="!status.is_local && !isPreview" class="source_url" title="Source">
|
||||||
<i class="button-icon icon-link-ext-alt"></i>
|
<i class="button-icon icon-link-ext-alt"></i>
|
||||||
</a>
|
</a>
|
||||||
<template v-if="expandable">
|
<template v-if="expandable && !isPreview">
|
||||||
<a href="#" @click.prevent="toggleExpanded" title="Expand">
|
<a href="#" @click.prevent="toggleExpanded" title="Expand">
|
||||||
<i class="button-icon icon-plus-squared"></i>
|
<i class="button-icon icon-plus-squared"></i>
|
||||||
</a>
|
</a>
|
||||||
</template>
|
</template>
|
||||||
<a href="#" @click.prevent="toggleMute" v-if="unmuted"><i class="button-icon icon-eye-off"></i></a>
|
<a href="#" @click.prevent="toggleMute" v-if="unmuted"><i class="button-icon icon-eye-off"></i></a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="heading-reply-row">
|
||||||
|
<div v-if="isReply" class="reply-to-and-accountname">
|
||||||
|
<a class="reply-to"
|
||||||
|
href="#" @click.prevent="gotoOriginal(status.in_reply_to_status_id)"
|
||||||
|
:aria-label="$t('tool_tip.reply')"
|
||||||
|
@mouseenter.prevent.stop="replyEnter(status.in_reply_to_status_id, $event)"
|
||||||
|
@mouseleave.prevent.stop="replyLeave()"
|
||||||
|
>
|
||||||
|
<i class="button-icon icon-reply" v-if="!isPreview"></i>
|
||||||
|
<span class="faint-link reply-to-text">{{$t('status.reply_to')}}</span>
|
||||||
|
</a>
|
||||||
|
<router-link :to="replyProfileLink">
|
||||||
|
{{replyToName}}
|
||||||
|
</router-link>
|
||||||
|
<span class="faint replies-separator" v-if="replies.length">
|
||||||
|
-
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="replies" v-if="inConversation && !isPreview">
|
||||||
|
<span class="faint" v-if="replies.length">{{$t('status.replies_list')}}</span>
|
||||||
|
<span class="reply-link faint" v-for="reply in replies">
|
||||||
|
<a href="#" @click.prevent="gotoOriginal(reply.id)" @mouseenter="replyEnter(reply.id, $event)" @mouseout="replyLeave()">{{reply.name}}</a>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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" :noReplyLinks="true" :statusoid="preview" :compact=true></status>
|
<status class="status-preview" v-if="preview" :isPreview="true" :statusoid="preview" :compact=true></status>
|
||||||
<div class="status-preview status-preview-loading" v-else>
|
<div class="status-preview status-preview-loading" v-else>
|
||||||
<i class="icon-spin4 animate-spin"></i>
|
<i class="icon-spin4 animate-spin"></i>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -123,7 +135,7 @@
|
||||||
<link-preview :card="status.card" :size="attachmentSize" :nsfw="nsfwClickthrough" />
|
<link-preview :card="status.card" :size="attachmentSize" :nsfw="nsfwClickthrough" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="!noHeading && !noReplyLinks" class='status-actions media-body'>
|
<div v-if="!noHeading && !isPreview" class='status-actions media-body'>
|
||||||
<div v-if="loggedIn">
|
<div v-if="loggedIn">
|
||||||
<a href="#" v-on:click.prevent="toggleReplying" :title="$t('tool_tip.reply')">
|
<a href="#" v-on:click.prevent="toggleReplying" :title="$t('tool_tip.reply')">
|
||||||
<i class="button-icon icon-reply" :class="{'icon-reply-active': replying}"></i>
|
<i class="button-icon icon-reply" :class="{'icon-reply-active': replying}"></i>
|
||||||
|
|
@ -147,6 +159,8 @@
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../../_variables.scss';
|
@import '../../_variables.scss';
|
||||||
|
|
||||||
|
$status-margin: 0.75em;
|
||||||
|
|
||||||
.status-body {
|
.status-body {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
|
@ -202,13 +216,16 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.media-left {
|
||||||
|
margin-right: $status-margin;
|
||||||
|
}
|
||||||
|
|
||||||
.status-el {
|
.status-el {
|
||||||
hyphens: auto;
|
hyphens: auto;
|
||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
border-left-width: 0px;
|
border-left-width: 0px;
|
||||||
line-height: 18px;
|
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
border-color: $fallback--border;
|
border-color: $fallback--border;
|
||||||
border-color: var(--border, $fallback--border);
|
border-color: var(--border, $fallback--border);
|
||||||
|
|
@ -229,22 +246,34 @@
|
||||||
.media-body {
|
.media-body {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0 0 0.25em 0.8em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.usercard {
|
.usercard {
|
||||||
margin-bottom: .7em
|
margin: 0;
|
||||||
|
margin-bottom: $status-margin;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-name {
|
||||||
|
white-space: nowrap;
|
||||||
|
font-size: 14px;
|
||||||
|
overflow: hidden;
|
||||||
|
flex-shrink: 0;
|
||||||
|
max-width: 85%;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
vertical-align: middle;
|
||||||
|
object-fit: contain
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.media-heading {
|
.media-heading {
|
||||||
flex-wrap: nowrap;
|
|
||||||
line-height: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.media-heading-left {
|
|
||||||
padding: 0;
|
padding: 0;
|
||||||
vertical-align: bottom;
|
vertical-align: bottom;
|
||||||
flex-basis: 100%;
|
flex-basis: 100%;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
|
||||||
a {
|
a {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|
@ -254,83 +283,102 @@
|
||||||
small {
|
small {
|
||||||
font-weight: lighter;
|
font-weight: lighter;
|
||||||
}
|
}
|
||||||
h4 {
|
|
||||||
white-space: nowrap;
|
.heading-name-row {
|
||||||
font-size: 14px;
|
padding: 0;
|
||||||
margin-right: 0.25em;
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
line-height: 18px;
|
||||||
|
|
||||||
|
.name-and-account-name {
|
||||||
|
display: flex;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-name {
|
||||||
|
flex-shrink: 1;
|
||||||
|
margin-right: 0.4em;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
.name-and-links {
|
|
||||||
padding: 0;
|
.account-name {
|
||||||
flex: 1 0;
|
min-width: 1.6em;
|
||||||
|
margin-right: 0.4em;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
flex: 1 1 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.heading-right {
|
||||||
|
display: flex;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeago {
|
||||||
|
margin-right: 0.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heading-reply-row {
|
||||||
|
align-content: baseline;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 18px;
|
||||||
|
max-width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
align-items: baseline;
|
align-items: stretch;
|
||||||
|
|
||||||
.user-name {
|
|
||||||
margin-right: .45em;
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 14px;
|
|
||||||
height: 14px;
|
|
||||||
vertical-align: middle;
|
|
||||||
object-fit: contain
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.links {
|
|
||||||
display: flex;
|
|
||||||
font-size: 12px;
|
|
||||||
color: $fallback--link;
|
|
||||||
color: var(--link, $fallback--link);
|
|
||||||
max-width: 100%;
|
|
||||||
a {
|
a {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
& > span {
|
}
|
||||||
text-overflow: ellipsis;
|
|
||||||
|
.reply-to-and-accountname {
|
||||||
|
display: flex;
|
||||||
|
height: 18px;
|
||||||
|
margin-right: 0.5em;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
white-space: nowrap;
|
max-width: 100%;
|
||||||
}
|
.icon-reply {
|
||||||
& > a:last-child {
|
transform: scaleX(-1);
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.reply-info {
|
.reply-info {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.reply-to {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-to-text {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
margin: 0 0.4em 0 0.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.replies-separator {
|
||||||
|
margin-left: 0.4em;
|
||||||
|
}
|
||||||
|
|
||||||
.replies {
|
.replies {
|
||||||
line-height: 16px;
|
line-height: 18px;
|
||||||
}
|
|
||||||
.reply-link {
|
|
||||||
margin-right: 0.2em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.media-heading-right {
|
|
||||||
display: inline-flex;
|
|
||||||
flex-shrink: 0;
|
|
||||||
flex-wrap: nowrap;
|
|
||||||
margin-left: .25em;
|
|
||||||
align-self: baseline;
|
|
||||||
|
|
||||||
.timeago {
|
|
||||||
margin-right: 0.2em;
|
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
align-self: last baseline;
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
& > * {
|
||||||
|
margin-right: 0.4em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> * {
|
.reply-link {
|
||||||
margin-left: 0.2em;
|
height: 17px;
|
||||||
}
|
|
||||||
a:hover i {
|
|
||||||
color: $fallback--text;
|
|
||||||
color: var(--text, $fallback--text);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -366,8 +414,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-content {
|
.status-content {
|
||||||
margin-right: 0.5em;
|
|
||||||
font-family: var(--postFont, sans-serif);
|
font-family: var(--postFont, sans-serif);
|
||||||
|
line-height: 1.4em;
|
||||||
|
|
||||||
img, video {
|
img, video {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
|
@ -390,9 +438,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
margin: 0;
|
margin: 0 0 1em 0;
|
||||||
margin-top: 0.2em;
|
}
|
||||||
margin-bottom: 0.5em;
|
|
||||||
|
p:last-child {
|
||||||
|
margin: 0 0 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
|
|
@ -417,7 +467,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.retweet-info {
|
.retweet-info {
|
||||||
padding: 0.4em 0.6em 0 0.6em;
|
padding: 0.4em $status-margin;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
||||||
.avatar.still-image {
|
.avatar.still-image {
|
||||||
|
|
@ -488,10 +538,10 @@
|
||||||
.status-actions {
|
.status-actions {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
margin-top: $status-margin;
|
||||||
|
|
||||||
div, favorite-button {
|
div, favorite-button {
|
||||||
padding-top: 0.25em;
|
max-width: 4em;
|
||||||
max-width: 6em;
|
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -517,9 +567,9 @@
|
||||||
|
|
||||||
.status {
|
.status {
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 0.6em;
|
padding: $status-margin;
|
||||||
&.is-retweet {
|
&.is-retweet {
|
||||||
padding-top: 0.1em;
|
padding-top: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import Status from '../status/status.vue'
|
import Status from '../status/status.vue'
|
||||||
import timelineFetcher from '../../services/timeline_fetcher/timeline_fetcher.service.js'
|
import timelineFetcher from '../../services/timeline_fetcher/timeline_fetcher.service.js'
|
||||||
import StatusOrConversation from '../status_or_conversation/status_or_conversation.vue'
|
import StatusOrConversation from '../status_or_conversation/status_or_conversation.vue'
|
||||||
import UserCard from '../user_card/user_card.vue'
|
|
||||||
import { throttle } from 'lodash'
|
import { throttle } from 'lodash'
|
||||||
|
|
||||||
const Timeline = {
|
const Timeline = {
|
||||||
|
|
@ -44,8 +43,7 @@ const Timeline = {
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
Status,
|
Status,
|
||||||
StatusOrConversation,
|
StatusOrConversation
|
||||||
UserCard
|
|
||||||
},
|
},
|
||||||
created () {
|
created () {
|
||||||
const store = this.$store
|
const store = this.$store
|
||||||
|
|
@ -54,6 +52,8 @@ const Timeline = {
|
||||||
|
|
||||||
window.addEventListener('scroll', this.scrollLoad)
|
window.addEventListener('scroll', this.scrollLoad)
|
||||||
|
|
||||||
|
if (this.timelineName === 'friends' && !credentials) { return false }
|
||||||
|
|
||||||
timelineFetcher.fetchAndUpdate({
|
timelineFetcher.fetchAndUpdate({
|
||||||
store,
|
store,
|
||||||
credentials,
|
credentials,
|
||||||
|
|
@ -68,14 +68,21 @@ const Timeline = {
|
||||||
document.addEventListener('visibilitychange', this.handleVisibilityChange, false)
|
document.addEventListener('visibilitychange', this.handleVisibilityChange, false)
|
||||||
this.unfocused = document.hidden
|
this.unfocused = document.hidden
|
||||||
}
|
}
|
||||||
|
window.addEventListener('keydown', this.handleShortKey)
|
||||||
},
|
},
|
||||||
destroyed () {
|
destroyed () {
|
||||||
window.removeEventListener('scroll', this.scrollLoad)
|
window.removeEventListener('scroll', this.scrollLoad)
|
||||||
|
window.removeEventListener('keydown', this.handleShortKey)
|
||||||
if (typeof document.hidden !== 'undefined') document.removeEventListener('visibilitychange', this.handleVisibilityChange, false)
|
if (typeof document.hidden !== 'undefined') document.removeEventListener('visibilitychange', this.handleVisibilityChange, false)
|
||||||
this.$store.commit('setLoading', { timeline: this.timelineName, value: false })
|
this.$store.commit('setLoading', { timeline: this.timelineName, value: false })
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
handleShortKey (e) {
|
||||||
|
if (e.key === '.') this.showNewStatuses()
|
||||||
|
},
|
||||||
showNewStatuses () {
|
showNewStatuses () {
|
||||||
|
if (this.newStatusCount === 0) return
|
||||||
|
|
||||||
if (this.timeline.flushMarker !== 0) {
|
if (this.timeline.flushMarker !== 0) {
|
||||||
this.$store.commit('clearTimeline', { timeline: this.timelineName })
|
this.$store.commit('clearTimeline', { timeline: this.timelineName })
|
||||||
this.$store.commit('queueFlush', { timeline: this.timelineName, id: 0 })
|
this.$store.commit('queueFlush', { timeline: this.timelineName, id: 0 })
|
||||||
|
|
@ -99,7 +106,7 @@ const Timeline = {
|
||||||
tag: this.tag
|
tag: this.tag
|
||||||
}).then(statuses => {
|
}).then(statuses => {
|
||||||
store.commit('setLoading', { timeline: this.timelineName, value: false })
|
store.commit('setLoading', { timeline: this.timelineName, value: false })
|
||||||
if (statuses.length === 0) {
|
if (statuses && statuses.length === 0) {
|
||||||
this.bottomedOut = true
|
this.bottomedOut = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,64 +0,0 @@
|
||||||
import UserCardContent from '../user_card_content/user_card_content.vue'
|
|
||||||
import UserAvatar from '../user_avatar/user_avatar.vue'
|
|
||||||
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
|
|
||||||
import { requestFollow, requestUnfollow } from '../../services/follow_manipulate/follow_manipulate'
|
|
||||||
|
|
||||||
const UserCard = {
|
|
||||||
props: [
|
|
||||||
'user',
|
|
||||||
'noFollowsYou',
|
|
||||||
'showApproval'
|
|
||||||
],
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
userExpanded: false,
|
|
||||||
followRequestInProgress: false,
|
|
||||||
followRequestSent: false,
|
|
||||||
updated: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
components: {
|
|
||||||
UserCardContent,
|
|
||||||
UserAvatar
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
currentUser () { return this.$store.state.users.currentUser },
|
|
||||||
following () { return this.updated ? this.updated.following : this.user.following },
|
|
||||||
showFollow () {
|
|
||||||
return !this.showApproval && (!this.following || this.updated && !this.updated.following)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
toggleUserExpanded () {
|
|
||||||
this.userExpanded = !this.userExpanded
|
|
||||||
},
|
|
||||||
approveUser () {
|
|
||||||
this.$store.state.api.backendInteractor.approveUser(this.user.id)
|
|
||||||
this.$store.dispatch('removeFollowRequest', this.user)
|
|
||||||
},
|
|
||||||
denyUser () {
|
|
||||||
this.$store.state.api.backendInteractor.denyUser(this.user.id)
|
|
||||||
this.$store.dispatch('removeFollowRequest', this.user)
|
|
||||||
},
|
|
||||||
userProfileLink (user) {
|
|
||||||
return generateProfileLink(user.id, user.screen_name, this.$store.state.instance.restrictedNicknames)
|
|
||||||
},
|
|
||||||
followUser () {
|
|
||||||
this.followRequestInProgress = true
|
|
||||||
requestFollow(this.user, this.$store).then(({ sent, updated }) => {
|
|
||||||
this.followRequestInProgress = false
|
|
||||||
this.followRequestSent = sent
|
|
||||||
this.updated = updated
|
|
||||||
})
|
|
||||||
},
|
|
||||||
unfollowUser () {
|
|
||||||
this.followRequestInProgress = true
|
|
||||||
requestUnfollow(this.user, this.$store).then(({ updated }) => {
|
|
||||||
this.followRequestInProgress = false
|
|
||||||
this.updated = updated
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default UserCard
|
|
||||||
|
|
@ -1,159 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="card">
|
|
||||||
<router-link :to="userProfileLink(user)">
|
|
||||||
<UserAvatar class="avatar" @click.prevent.native="toggleUserExpanded" :src="user.profile_image_url"/>
|
|
||||||
</router-link>
|
|
||||||
<div class="user-card-main-content">
|
|
||||||
<div class="usercard" v-if="userExpanded">
|
|
||||||
<user-card-content :user="user" :switcher="false"></user-card-content>
|
|
||||||
</div>
|
|
||||||
<div class="name-and-screen-name" v-if="!userExpanded">
|
|
||||||
<div :title="user.name" class="user-name">
|
|
||||||
<span v-if="user.name_html" v-html="user.name_html"></span>
|
|
||||||
<span v-else>{{ user.name }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="user-link-action">
|
|
||||||
<router-link class='user-screen-name' :to="userProfileLink(user)">
|
|
||||||
@{{user.screen_name}}
|
|
||||||
</router-link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="follow-box" v-if="!userExpanded">
|
|
||||||
<span class="faint" v-if="!noFollowsYou && user.follows_you">
|
|
||||||
{{ currentUser.id == user.id ? $t('user_card.its_you') : $t('user_card.follows_you') }}
|
|
||||||
</span>
|
|
||||||
<button
|
|
||||||
v-if="showFollow"
|
|
||||||
class="btn btn-default"
|
|
||||||
@click="followUser"
|
|
||||||
:disabled="followRequestInProgress"
|
|
||||||
:title="followRequestSent ? $t('user_card.follow_again') : ''"
|
|
||||||
>
|
|
||||||
<template v-if="followRequestInProgress">
|
|
||||||
{{ $t('user_card.follow_progress') }}
|
|
||||||
</template>
|
|
||||||
<template v-else-if="followRequestSent">
|
|
||||||
{{ $t('user_card.follow_sent') }}
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
{{ $t('user_card.follow') }}
|
|
||||||
</template>
|
|
||||||
</button>
|
|
||||||
<button v-if="following" class="btn btn-default pressed" @click="unfollowUser" :disabled="followRequestInProgress">
|
|
||||||
<template v-if="followRequestInProgress">
|
|
||||||
{{ $t('user_card.follow_progress') }}
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
{{ $t('user_card.follow_unfollow') }}
|
|
||||||
</template>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="approval" v-if="showApproval">
|
|
||||||
<button class="btn btn-default" @click="approveUser">{{ $t('user_card.approve') }}</button>
|
|
||||||
<button class="btn btn-default" @click="denyUser">{{ $t('user_card.deny') }}</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script src="./user_card.js"></script>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
@import '../../_variables.scss';
|
|
||||||
|
|
||||||
.user-card-main-content {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
flex: 1 1 100%;
|
|
||||||
margin-left: 0.7em;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.name-and-screen-name {
|
|
||||||
text-align: left;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
.user-name {
|
|
||||||
img {
|
|
||||||
object-fit: contain;
|
|
||||||
height: 16px;
|
|
||||||
width: 16px;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-link-action {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.card {
|
|
||||||
display: flex;
|
|
||||||
flex: 1 0;
|
|
||||||
padding-top: 0.6em;
|
|
||||||
padding-right: 1em;
|
|
||||||
padding-bottom: 0.6em;
|
|
||||||
padding-left: 1em;
|
|
||||||
border-bottom: 1px solid;
|
|
||||||
margin: 0;
|
|
||||||
border-bottom-color: $fallback--border;
|
|
||||||
border-bottom-color: var(--border, $fallback--border);
|
|
||||||
|
|
||||||
.avatar {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.follow-box {
|
|
||||||
text-align: center;
|
|
||||||
flex-shrink: 0;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-between;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
line-height: 1.5em;
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
margin-top: 0.5em;
|
|
||||||
margin-left: auto;
|
|
||||||
width: 10em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.usercard {
|
|
||||||
width: fill-available;
|
|
||||||
border-radius: $fallback--panelRadius;
|
|
||||||
border-radius: var(--panelRadius, $fallback--panelRadius);
|
|
||||||
border-style: solid;
|
|
||||||
border-color: $fallback--border;
|
|
||||||
border-color: var(--border, $fallback--border);
|
|
||||||
border-width: 1px;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
.panel-heading {
|
|
||||||
background: transparent;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: stretch;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.approval {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
button {
|
|
||||||
margin-top: 0.5em;
|
|
||||||
margin-right: 0.5em;
|
|
||||||
flex: 1 1;
|
|
||||||
max-width: 12em;
|
|
||||||
min-width: 8em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -222,6 +222,14 @@
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
margin-right: 1em;
|
margin-right: 1em;
|
||||||
|
font-size: 15px;
|
||||||
|
|
||||||
|
img {
|
||||||
|
object-fit: contain;
|
||||||
|
height: 16px;
|
||||||
|
width: 16px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-screen-name {
|
.user-screen-name {
|
||||||
|
|
@ -386,4 +394,24 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.usercard {
|
||||||
|
width: fill-available;
|
||||||
|
border-radius: $fallback--panelRadius;
|
||||||
|
border-radius: var(--panelRadius, $fallback--panelRadius);
|
||||||
|
border-style: solid;
|
||||||
|
border-color: $fallback--border;
|
||||||
|
border-color: var(--border, $fallback--border);
|
||||||
|
border-width: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.panel-heading {
|
||||||
|
background: transparent;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ const UserFinder = {
|
||||||
methods: {
|
methods: {
|
||||||
findUser (username) {
|
findUser (username) {
|
||||||
this.$router.push({ name: 'user-search', query: { query: username } })
|
this.$router.push({ name: 'user-search', query: { query: username } })
|
||||||
|
this.$refs.userSearchInput.focus()
|
||||||
},
|
},
|
||||||
toggleHidden () {
|
toggleHidden () {
|
||||||
this.hidden = !this.hidden
|
this.hidden = !this.hidden
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
<i class="icon-spin4 user-finder-icon animate-spin-slow" v-if="loading" />
|
<i class="icon-spin4 user-finder-icon animate-spin-slow" v-if="loading" />
|
||||||
<a href="#" v-if="hidden" :title="$t('finder.find_user')"><i class="icon-user-plus user-finder-icon" @click.prevent.stop="toggleHidden" /></a>
|
<a href="#" v-if="hidden" :title="$t('finder.find_user')"><i class="icon-user-plus user-finder-icon" @click.prevent.stop="toggleHidden" /></a>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<input class="user-finder-input" @keyup.enter="findUser(username)" v-model="username" :placeholder="$t('finder.find_user')" id="user-finder-input" type="text"/>
|
<input class="user-finder-input" ref="userSearchInput" @keyup.enter="findUser(username)" v-model="username" :placeholder="$t('finder.find_user')" id="user-finder-input" type="text"/>
|
||||||
<button class="btn search-button" @click="findUser(username)">
|
<button class="btn search-button" @click="findUser(username)">
|
||||||
<i class="icon-search"/>
|
<i class="icon-search"/>
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,39 @@
|
||||||
|
import { compose } from 'vue-compose'
|
||||||
|
import get from 'lodash/get'
|
||||||
import UserCardContent from '../user_card_content/user_card_content.vue'
|
import UserCardContent from '../user_card_content/user_card_content.vue'
|
||||||
import UserCard from '../user_card/user_card.vue'
|
import FollowCard from '../follow_card/follow_card.vue'
|
||||||
import Timeline from '../timeline/timeline.vue'
|
import Timeline from '../timeline/timeline.vue'
|
||||||
import FollowList from '../follow_list/follow_list.vue'
|
import withLoadMore from '../../hocs/with_load_more/with_load_more'
|
||||||
|
import withList from '../../hocs/with_list/with_list'
|
||||||
|
|
||||||
|
const FollowerList = compose(
|
||||||
|
withLoadMore({
|
||||||
|
fetch: (props, $store) => $store.dispatch('addFollowers', props.userId),
|
||||||
|
select: (props, $store) => get($store.getters.userById(props.userId), 'followers', []),
|
||||||
|
destory: (props, $store) => $store.dispatch('clearFollowers', props.userId),
|
||||||
|
childPropName: 'entries',
|
||||||
|
additionalPropNames: ['userId']
|
||||||
|
}),
|
||||||
|
withList({ getEntryProps: user => ({ user }) })
|
||||||
|
)(FollowCard)
|
||||||
|
|
||||||
|
const FriendList = compose(
|
||||||
|
withLoadMore({
|
||||||
|
fetch: (props, $store) => $store.dispatch('addFriends', props.userId),
|
||||||
|
select: (props, $store) => get($store.getters.userById(props.userId), 'friends', []),
|
||||||
|
destory: (props, $store) => $store.dispatch('clearFriends', props.userId),
|
||||||
|
childPropName: 'entries',
|
||||||
|
additionalPropNames: ['userId']
|
||||||
|
}),
|
||||||
|
withList({ getEntryProps: user => ({ user }) })
|
||||||
|
)(FollowCard)
|
||||||
|
|
||||||
const UserProfile = {
|
const UserProfile = {
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
error: false
|
||||||
|
}
|
||||||
|
},
|
||||||
created () {
|
created () {
|
||||||
this.$store.commit('clearTimeline', { timeline: 'user' })
|
this.$store.commit('clearTimeline', { timeline: 'user' })
|
||||||
this.$store.commit('clearTimeline', { timeline: 'favorites' })
|
this.$store.commit('clearTimeline', { timeline: 'favorites' })
|
||||||
|
|
@ -13,6 +43,16 @@ const UserProfile = {
|
||||||
this.startFetchFavorites()
|
this.startFetchFavorites()
|
||||||
if (!this.user.id) {
|
if (!this.user.id) {
|
||||||
this.$store.dispatch('fetchUser', this.fetchBy)
|
this.$store.dispatch('fetchUser', this.fetchBy)
|
||||||
|
.catch((reason) => {
|
||||||
|
const errorMessage = get(reason, 'error.error')
|
||||||
|
if (errorMessage === 'No user with such user_id') { // Known error
|
||||||
|
this.error = this.$t('user_profile.profile_does_not_exist')
|
||||||
|
} else if (errorMessage) {
|
||||||
|
this.error = errorMessage
|
||||||
|
} else {
|
||||||
|
this.error = this.$t('user_profile.profile_loading_error')
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
destroyed () {
|
destroyed () {
|
||||||
|
|
@ -105,9 +145,9 @@ const UserProfile = {
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
UserCardContent,
|
UserCardContent,
|
||||||
UserCard,
|
|
||||||
Timeline,
|
Timeline,
|
||||||
FollowList
|
FollowerList,
|
||||||
|
FriendList
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,16 +18,10 @@
|
||||||
:user-id="fetchBy"
|
:user-id="fetchBy"
|
||||||
/>
|
/>
|
||||||
<div :label="$t('user_card.followees')" v-if="followsTabVisible" :disabled="!user.friends_count">
|
<div :label="$t('user_card.followees')" v-if="followsTabVisible" :disabled="!user.friends_count">
|
||||||
<FollowList v-if="user.friends_count > 0" :userId="userId" :showFollowers="false" />
|
<FriendList :userId="userId" />
|
||||||
<div class="userlist-placeholder" v-else>
|
|
||||||
<i class="icon-spin3 animate-spin"></i>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div :label="$t('user_card.followers')" v-if="followersTabVisible" :disabled="!user.followers_count">
|
<div :label="$t('user_card.followers')" v-if="followersTabVisible" :disabled="!user.followers_count">
|
||||||
<FollowList v-if="user.followers_count > 0" :userId="userId" :showFollowers="true" />
|
<FollowerList :userId="userId" :entryProps="{noFollowsYou: isUs}" />
|
||||||
<div class="userlist-placeholder" v-else>
|
|
||||||
<i class="icon-spin3 animate-spin"></i>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<Timeline
|
<Timeline
|
||||||
:label="$t('user_card.media')"
|
:label="$t('user_card.media')"
|
||||||
|
|
@ -55,7 +49,8 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<i class="icon-spin3 animate-spin"></i>
|
<span v-if="error">{{ error }}</span>
|
||||||
|
<i class="icon-spin3 animate-spin" v-else></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import UserCard from '../user_card/user_card.vue'
|
import FollowCard from '../follow_card/follow_card.vue'
|
||||||
import userSearchApi from '../../services/new_api/user_search.js'
|
import userSearchApi from '../../services/new_api/user_search.js'
|
||||||
const userSearch = {
|
const userSearch = {
|
||||||
components: {
|
components: {
|
||||||
UserCard
|
FollowCard
|
||||||
},
|
},
|
||||||
props: [
|
props: [
|
||||||
'query'
|
'query'
|
||||||
|
|
@ -10,7 +10,8 @@ const userSearch = {
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
username: '',
|
username: '',
|
||||||
users: []
|
users: [],
|
||||||
|
loading: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
|
|
@ -24,14 +25,17 @@ const userSearch = {
|
||||||
methods: {
|
methods: {
|
||||||
newQuery (query) {
|
newQuery (query) {
|
||||||
this.$router.push({ name: 'user-search', query: { query } })
|
this.$router.push({ name: 'user-search', query: { query } })
|
||||||
|
this.$refs.userSearchInput.focus()
|
||||||
},
|
},
|
||||||
search (query) {
|
search (query) {
|
||||||
if (!query) {
|
if (!query) {
|
||||||
this.users = []
|
this.users = []
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
this.loading = true
|
||||||
userSearchApi.search({query, store: this.$store})
|
userSearchApi.search({query, store: this.$store})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
|
this.loading = false
|
||||||
this.users = res
|
this.users = res
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,16 @@
|
||||||
{{$t('nav.user_search')}}
|
{{$t('nav.user_search')}}
|
||||||
</div>
|
</div>
|
||||||
<div class="user-search-input-container">
|
<div class="user-search-input-container">
|
||||||
<input class="user-finder-input" @keyup.enter="newQuery(username)" v-model="username" :placeholder="$t('finder.find_user')"/>
|
<input class="user-finder-input" ref="userSearchInput" @keyup.enter="newQuery(username)" v-model="username" :placeholder="$t('finder.find_user')"/>
|
||||||
<button class="btn search-button" @click="newQuery(username)">
|
<button class="btn search-button" @click="newQuery(username)">
|
||||||
<i class="icon-search"/>
|
<i class="icon-search"/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<div v-if="loading" class="text-center loading-icon">
|
||||||
<user-card v-for="user in users" :key="user.id" :user="user" :showFollows="true"></user-card>
|
<i class="icon-spin3 animate-spin"/>
|
||||||
|
</div>
|
||||||
|
<div v-else class="panel-body">
|
||||||
|
<FollowCard v-for="user in users" :key="user.id" :user="user"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -27,4 +30,8 @@
|
||||||
margin-left: 0.5em;
|
margin-left: 0.5em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.loading-icon {
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,32 @@
|
||||||
import { unescape } from 'lodash'
|
import { compose } from 'vue-compose'
|
||||||
|
import unescape from 'lodash/unescape'
|
||||||
|
import get from 'lodash/get'
|
||||||
import TabSwitcher from '../tab_switcher/tab_switcher.js'
|
import TabSwitcher from '../tab_switcher/tab_switcher.js'
|
||||||
import ImageCropper from '../image_cropper/image_cropper.vue'
|
import ImageCropper from '../image_cropper/image_cropper.vue'
|
||||||
import StyleSwitcher from '../style_switcher/style_switcher.vue'
|
import StyleSwitcher from '../style_switcher/style_switcher.vue'
|
||||||
import fileSizeFormatService from '../../services/file_size_format/file_size_format.js'
|
import fileSizeFormatService from '../../services/file_size_format/file_size_format.js'
|
||||||
|
import BlockCard from '../block_card/block_card.vue'
|
||||||
|
import MuteCard from '../mute_card/mute_card.vue'
|
||||||
|
import withSubscription from '../../hocs/with_subscription/with_subscription'
|
||||||
|
import withList from '../../hocs/with_list/with_list'
|
||||||
|
|
||||||
|
const BlockList = compose(
|
||||||
|
withSubscription({
|
||||||
|
fetch: (props, $store) => $store.dispatch('fetchBlocks'),
|
||||||
|
select: (props, $store) => get($store.state.users.currentUser, 'blockIds', []),
|
||||||
|
childPropName: 'entries'
|
||||||
|
}),
|
||||||
|
withList({ getEntryProps: userId => ({ userId }) })
|
||||||
|
)(BlockCard)
|
||||||
|
|
||||||
|
const MuteList = compose(
|
||||||
|
withSubscription({
|
||||||
|
fetch: (props, $store) => $store.dispatch('fetchMutes'),
|
||||||
|
select: (props, $store) => get($store.state.users.currentUser, 'muteIds', []),
|
||||||
|
childPropName: 'entries'
|
||||||
|
}),
|
||||||
|
withList({ getEntryProps: userId => ({ userId }) })
|
||||||
|
)(MuteCard)
|
||||||
|
|
||||||
const UserSettings = {
|
const UserSettings = {
|
||||||
data () {
|
data () {
|
||||||
|
|
@ -38,10 +61,15 @@ const UserSettings = {
|
||||||
activeTab: 'profile'
|
activeTab: 'profile'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
created () {
|
||||||
|
this.$store.dispatch('fetchTokens')
|
||||||
|
},
|
||||||
components: {
|
components: {
|
||||||
StyleSwitcher,
|
StyleSwitcher,
|
||||||
TabSwitcher,
|
TabSwitcher,
|
||||||
ImageCropper
|
ImageCropper,
|
||||||
|
BlockList,
|
||||||
|
MuteList
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
user () {
|
user () {
|
||||||
|
|
@ -63,6 +91,15 @@ const UserSettings = {
|
||||||
},
|
},
|
||||||
currentSaveStateNotice () {
|
currentSaveStateNotice () {
|
||||||
return this.$store.state.interface.settings.currentSaveStateNotice
|
return this.$store.state.interface.settings.currentSaveStateNotice
|
||||||
|
},
|
||||||
|
oauthTokens () {
|
||||||
|
return this.$store.state.oauthTokens.tokens.map(oauthToken => {
|
||||||
|
return {
|
||||||
|
id: oauthToken.id,
|
||||||
|
appName: oauthToken.app_name,
|
||||||
|
validUntil: new Date(oauthToken.valid_until).toLocaleDateString()
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
@ -282,6 +319,11 @@ const UserSettings = {
|
||||||
logout () {
|
logout () {
|
||||||
this.$store.dispatch('logout')
|
this.$store.dispatch('logout')
|
||||||
this.$router.replace('/')
|
this.$router.replace('/')
|
||||||
|
},
|
||||||
|
revokeToken (id) {
|
||||||
|
if (window.confirm(`${this.$i18n.t('settings.revoke_token')}?`)) {
|
||||||
|
this.$store.dispatch('revokeToken', id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -121,6 +121,30 @@
|
||||||
<p v-if="changePasswordError">{{changePasswordError}}</p>
|
<p v-if="changePasswordError">{{changePasswordError}}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="setting-item">
|
||||||
|
<h2>{{$t('settings.oauth_tokens')}}</h2>
|
||||||
|
<table class="oauth-tokens">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{{$t('settings.app_name')}}</th>
|
||||||
|
<th>{{$t('settings.valid_until')}}</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="oauthToken in oauthTokens" :key="oauthToken.id">
|
||||||
|
<td>{{oauthToken.appName}}</td>
|
||||||
|
<td>{{oauthToken.validUntil}}</td>
|
||||||
|
<td class="actions">
|
||||||
|
<button class="btn btn-default" @click="revokeToken(oauthToken.id)">
|
||||||
|
{{$t('settings.revoke_token')}}
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="setting-item">
|
<div class="setting-item">
|
||||||
<h2>{{$t('settings.delete_account')}}</h2>
|
<h2>{{$t('settings.delete_account')}}</h2>
|
||||||
<p v-if="!deletingAccount">{{$t('settings.delete_account_description')}}</p>
|
<p v-if="!deletingAccount">{{$t('settings.delete_account_description')}}</p>
|
||||||
|
|
@ -162,6 +186,12 @@
|
||||||
<h2>{{$t('settings.follow_export_processing')}}</h2>
|
<h2>{{$t('settings.follow_export_processing')}}</h2>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div :label="$t('settings.blocks_tab')">
|
||||||
|
<block-list :refresh="true">
|
||||||
|
<template slot="empty">{{$t('settings.no_blocks')}}</template>
|
||||||
|
</block-list>
|
||||||
|
</div>
|
||||||
</tab-switcher>
|
</tab-switcher>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -207,5 +237,17 @@
|
||||||
border-radius: $fallback--avatarRadius;
|
border-radius: $fallback--avatarRadius;
|
||||||
border-radius: var(--avatarRadius, $fallback--avatarRadius);
|
border-radius: var(--avatarRadius, $fallback--avatarRadius);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.oauth-tokens {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
th {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import apiService from '../../services/api/api.service.js'
|
import apiService from '../../services/api/api.service.js'
|
||||||
import UserCard from '../user_card/user_card.vue'
|
import FollowCard from '../follow_card/follow_card.vue'
|
||||||
|
|
||||||
const WhoToFollow = {
|
const WhoToFollow = {
|
||||||
components: {
|
components: {
|
||||||
UserCard
|
FollowCard
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
{{$t('who_to_follow.who_to_follow')}}
|
{{$t('who_to_follow.who_to_follow')}}
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<user-card v-for="user in users" :key="user.id" :user="user" :showFollows="true"></user-card>
|
<FollowCard v-for="user in users" :key="user.id" :user="user"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
40
src/hocs/with_list/with_list.js
Normal file
40
src/hocs/with_list/with_list.js
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
import Vue from 'vue'
|
||||||
|
import map from 'lodash/map'
|
||||||
|
import isEmpty from 'lodash/isEmpty'
|
||||||
|
import './with_list.scss'
|
||||||
|
|
||||||
|
const defaultEntryPropsGetter = entry => ({ entry })
|
||||||
|
const defaultKeyGetter = entry => entry.id
|
||||||
|
|
||||||
|
const withList = ({
|
||||||
|
getEntryProps = defaultEntryPropsGetter, // function to accept entry and index values and return props to be passed into the item component
|
||||||
|
getKey = defaultKeyGetter // funciton to accept entry and index values and return key prop value
|
||||||
|
}) => (ItemComponent) => (
|
||||||
|
Vue.component('withList', {
|
||||||
|
props: [
|
||||||
|
'entries', // array of entry
|
||||||
|
'entryProps', // additional props to be passed into each entry
|
||||||
|
'entryListeners' // additional event listeners to be passed into each entry
|
||||||
|
],
|
||||||
|
render (createElement) {
|
||||||
|
return (
|
||||||
|
<div class="with-list">
|
||||||
|
{map(this.entries, (entry, index) => {
|
||||||
|
const props = {
|
||||||
|
key: getKey(entry, index),
|
||||||
|
props: {
|
||||||
|
...this.$props.entryProps,
|
||||||
|
...getEntryProps(entry, index)
|
||||||
|
},
|
||||||
|
on: this.$props.entryListeners
|
||||||
|
}
|
||||||
|
return <ItemComponent {...props} />
|
||||||
|
})}
|
||||||
|
{isEmpty(this.entries) && this.$slots.empty && <div class="with-list-empty-content faint">{this.$slots.empty}</div>}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
export default withList
|
||||||
6
src/hocs/with_list/with_list.scss
Normal file
6
src/hocs/with_list/with_list.scss
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
.with-list {
|
||||||
|
&-empty-content {
|
||||||
|
text-align: center;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
94
src/hocs/with_load_more/with_load_more.js
Normal file
94
src/hocs/with_load_more/with_load_more.js
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
import Vue from 'vue'
|
||||||
|
import isEmpty from 'lodash/isEmpty'
|
||||||
|
import { getComponentProps } from '../../services/component_utils/component_utils'
|
||||||
|
import './with_load_more.scss'
|
||||||
|
|
||||||
|
const withLoadMore = ({
|
||||||
|
fetch, // function to fetch entries and return a promise
|
||||||
|
select, // function to select data from store
|
||||||
|
destroy, // function called at "destroyed" lifecycle
|
||||||
|
childPropName = 'entries', // name of the prop to be passed into the wrapped component
|
||||||
|
additionalPropNames = [] // additional prop name list of the wrapper component
|
||||||
|
}) => (WrappedComponent) => {
|
||||||
|
const originalProps = Object.keys(getComponentProps(WrappedComponent))
|
||||||
|
const props = originalProps.filter(v => v !== childPropName).concat(additionalPropNames)
|
||||||
|
|
||||||
|
return Vue.component('withLoadMore', {
|
||||||
|
render (createElement) {
|
||||||
|
const props = {
|
||||||
|
props: {
|
||||||
|
...this.$props,
|
||||||
|
[childPropName]: this.entries
|
||||||
|
},
|
||||||
|
on: this.$listeners,
|
||||||
|
scopedSlots: this.$scopedSlots
|
||||||
|
}
|
||||||
|
const children = Object.entries(this.$slots).map(([key, value]) => createElement('template', { slot: key }, value))
|
||||||
|
return (
|
||||||
|
<div class="with-load-more">
|
||||||
|
<WrappedComponent {...props}>
|
||||||
|
{children}
|
||||||
|
</WrappedComponent>
|
||||||
|
<div class="with-load-more-footer">
|
||||||
|
{this.error && <a onClick={this.fetchEntries} class="alert error">{this.$t('general.generic_error')}</a>}
|
||||||
|
{!this.error && this.loading && <i class="icon-spin3 animate-spin"/>}
|
||||||
|
{!this.error && !this.loading && !this.bottomedOut && <a onClick={this.fetchEntries}>{this.$t('general.more')}</a>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
props,
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
bottomedOut: false,
|
||||||
|
error: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
entries () {
|
||||||
|
return select(this.$props, this.$store) || []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created () {
|
||||||
|
window.addEventListener('scroll', this.scrollLoad)
|
||||||
|
if (this.entries.length === 0) {
|
||||||
|
this.fetchEntries()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
destroyed () {
|
||||||
|
window.removeEventListener('scroll', this.scrollLoad)
|
||||||
|
destroy && destroy(this.$props, this.$store)
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fetchEntries () {
|
||||||
|
if (!this.loading) {
|
||||||
|
this.loading = true
|
||||||
|
this.error = false
|
||||||
|
fetch(this.$props, this.$store)
|
||||||
|
.then((newEntries) => {
|
||||||
|
this.loading = false
|
||||||
|
this.bottomedOut = isEmpty(newEntries)
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
this.loading = false
|
||||||
|
this.error = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scrollLoad (e) {
|
||||||
|
const bodyBRect = document.body.getBoundingClientRect()
|
||||||
|
const height = Math.max(bodyBRect.height, -(bodyBRect.y))
|
||||||
|
if (this.loading === false &&
|
||||||
|
this.bottomedOut === false &&
|
||||||
|
this.$el.offsetHeight > 0 &&
|
||||||
|
(window.innerHeight + window.pageYOffset) >= (height - 750)
|
||||||
|
) {
|
||||||
|
this.fetchEntries()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withLoadMore
|
||||||
10
src/hocs/with_load_more/with_load_more.scss
Normal file
10
src/hocs/with_load_more/with_load_more.scss
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
.with-load-more {
|
||||||
|
&-footer {
|
||||||
|
padding: 10px;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
.error {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
84
src/hocs/with_subscription/with_subscription.js
Normal file
84
src/hocs/with_subscription/with_subscription.js
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
import Vue from 'vue'
|
||||||
|
import isEmpty from 'lodash/isEmpty'
|
||||||
|
import { getComponentProps } from '../../services/component_utils/component_utils'
|
||||||
|
import './with_subscription.scss'
|
||||||
|
|
||||||
|
const withSubscription = ({
|
||||||
|
fetch, // function to fetch entries and return a promise
|
||||||
|
select, // function to select data from store
|
||||||
|
childPropName = 'content', // name of the prop to be passed into the wrapped component
|
||||||
|
additionalPropNames = [] // additional prop name list of the wrapper component
|
||||||
|
}) => (WrappedComponent) => {
|
||||||
|
const originalProps = Object.keys(getComponentProps(WrappedComponent))
|
||||||
|
const props = originalProps.filter(v => v !== childPropName).concat(additionalPropNames)
|
||||||
|
|
||||||
|
return Vue.component('withSubscription', {
|
||||||
|
props: [
|
||||||
|
...props,
|
||||||
|
'refresh' // boolean saying to force-fetch data whenever created
|
||||||
|
],
|
||||||
|
render (createElement) {
|
||||||
|
if (!this.error && !this.loading) {
|
||||||
|
const props = {
|
||||||
|
props: {
|
||||||
|
...this.$props,
|
||||||
|
[childPropName]: this.fetchedData
|
||||||
|
},
|
||||||
|
on: this.$listeners,
|
||||||
|
scopedSlots: this.$scopedSlots
|
||||||
|
}
|
||||||
|
const children = Object.entries(this.$slots).map(([key, value]) => createElement('template', { slot: key }, value))
|
||||||
|
return (
|
||||||
|
<div class="with-subscription">
|
||||||
|
<WrappedComponent {...props}>
|
||||||
|
{children}
|
||||||
|
</WrappedComponent>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<div class="with-subscription-loading">
|
||||||
|
{this.error
|
||||||
|
? <a onClick={this.fetchData} class="alert error">{this.$t('general.generic_error')}</a>
|
||||||
|
: <i class="icon-spin3 animate-spin"/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
error: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
fetchedData () {
|
||||||
|
return select(this.$props, this.$store)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created () {
|
||||||
|
if (this.refresh || isEmpty(this.fetchedData)) {
|
||||||
|
this.fetchData()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fetchData () {
|
||||||
|
if (!this.loading) {
|
||||||
|
this.loading = true
|
||||||
|
this.error = false
|
||||||
|
fetch(this.$props, this.$store)
|
||||||
|
.then(() => {
|
||||||
|
this.loading = false
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
this.error = true
|
||||||
|
this.loading = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withSubscription
|
||||||
10
src/hocs/with_subscription/with_subscription.scss
Normal file
10
src/hocs/with_subscription/with_subscription.scss
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
.with-subscription {
|
||||||
|
&-loading {
|
||||||
|
padding: 10px;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
.error {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -134,6 +134,11 @@
|
||||||
"notification_visibility_mentions": "الإشارات",
|
"notification_visibility_mentions": "الإشارات",
|
||||||
"notification_visibility_repeats": "",
|
"notification_visibility_repeats": "",
|
||||||
"nsfw_clickthrough": "",
|
"nsfw_clickthrough": "",
|
||||||
|
"oauth_tokens": "رموز OAuth",
|
||||||
|
"token": "رمز",
|
||||||
|
"refresh_token": "رمز التحديث",
|
||||||
|
"valid_until": "صالح حتى",
|
||||||
|
"revoke_token": "سحب",
|
||||||
"panelRadius": "",
|
"panelRadius": "",
|
||||||
"pause_on_unfocused": "",
|
"pause_on_unfocused": "",
|
||||||
"presets": "النماذج",
|
"presets": "النماذج",
|
||||||
|
|
|
||||||
|
|
@ -132,6 +132,11 @@
|
||||||
"notification_visibility_repeats": "Republica una entrada meva",
|
"notification_visibility_repeats": "Republica una entrada meva",
|
||||||
"no_rich_text_description": "Neteja el formatat de text de totes les entrades",
|
"no_rich_text_description": "Neteja el formatat de text de totes les entrades",
|
||||||
"nsfw_clickthrough": "Amaga el contingut NSFW darrer d'una imatge clicable",
|
"nsfw_clickthrough": "Amaga el contingut NSFW darrer d'una imatge clicable",
|
||||||
|
"oauth_tokens": "Llistats OAuth",
|
||||||
|
"token": "Token",
|
||||||
|
"refresh_token": "Actualitza el token",
|
||||||
|
"valid_until": "Vàlid fins",
|
||||||
|
"revoke_token": "Revocar",
|
||||||
"panelRadius": "Panells",
|
"panelRadius": "Panells",
|
||||||
"pause_on_unfocused": "Pausa la reproducció en continu quan la pestanya perdi el focus",
|
"pause_on_unfocused": "Pausa la reproducció en continu quan la pestanya perdi el focus",
|
||||||
"presets": "Temes",
|
"presets": "Temes",
|
||||||
|
|
|
||||||
|
|
@ -159,6 +159,11 @@
|
||||||
"hide_follows_description": "Zeige nicht, wem ich folge",
|
"hide_follows_description": "Zeige nicht, wem ich folge",
|
||||||
"hide_followers_description": "Zeige nicht, wer mir folgt",
|
"hide_followers_description": "Zeige nicht, wer mir folgt",
|
||||||
"nsfw_clickthrough": "Aktiviere ausblendbares Overlay für Anhänge, die als NSFW markiert sind",
|
"nsfw_clickthrough": "Aktiviere ausblendbares Overlay für Anhänge, die als NSFW markiert sind",
|
||||||
|
"oauth_tokens": "OAuth-Token",
|
||||||
|
"token": "Zeichen",
|
||||||
|
"refresh_token": "Token aktualisieren",
|
||||||
|
"valid_until": "Gültig bis",
|
||||||
|
"revoke_token": "Widerrufen",
|
||||||
"panelRadius": "Panel",
|
"panelRadius": "Panel",
|
||||||
"pause_on_unfocused": "Streaming pausieren, wenn das Tab nicht fokussiert ist",
|
"pause_on_unfocused": "Streaming pausieren, wenn das Tab nicht fokussiert ist",
|
||||||
"presets": "Voreinstellungen",
|
"presets": "Voreinstellungen",
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,8 @@
|
||||||
"apply": "Apply",
|
"apply": "Apply",
|
||||||
"submit": "Submit",
|
"submit": "Submit",
|
||||||
"more": "More",
|
"more": "More",
|
||||||
"generic_error": "An error occured"
|
"generic_error": "An error occured",
|
||||||
|
"optional": "optional"
|
||||||
},
|
},
|
||||||
"image_cropper": {
|
"image_cropper": {
|
||||||
"crop_picture": "Crop picture",
|
"crop_picture": "Crop picture",
|
||||||
|
|
@ -92,6 +93,9 @@
|
||||||
"token": "Invite token",
|
"token": "Invite token",
|
||||||
"captcha": "CAPTCHA",
|
"captcha": "CAPTCHA",
|
||||||
"new_captcha": "Click the image to get a new captcha",
|
"new_captcha": "Click the image to get a new captcha",
|
||||||
|
"username_placeholder": "e.g. lain",
|
||||||
|
"fullname_placeholder": "e.g. Lain Iwakura",
|
||||||
|
"bio_placeholder": "e.g.\nHi, I'm Lain\nI’m an anime girl living in suburban Japan. You may know me from the Wired.",
|
||||||
"validations": {
|
"validations": {
|
||||||
"username_required": "cannot be left blank",
|
"username_required": "cannot be left blank",
|
||||||
"fullname_required": "cannot be left blank",
|
"fullname_required": "cannot be left blank",
|
||||||
|
|
@ -102,6 +106,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
|
"app_name": "App name",
|
||||||
"attachmentRadius": "Attachments",
|
"attachmentRadius": "Attachments",
|
||||||
"attachments": "Attachments",
|
"attachments": "Attachments",
|
||||||
"autoload": "Enable automatic loading when scrolled to the bottom",
|
"autoload": "Enable automatic loading when scrolled to the bottom",
|
||||||
|
|
@ -110,6 +115,7 @@
|
||||||
"avatarRadius": "Avatars",
|
"avatarRadius": "Avatars",
|
||||||
"background": "Background",
|
"background": "Background",
|
||||||
"bio": "Bio",
|
"bio": "Bio",
|
||||||
|
"blocks_tab": "Blocks",
|
||||||
"btnRadius": "Buttons",
|
"btnRadius": "Buttons",
|
||||||
"cBlue": "Blue (Reply, follow)",
|
"cBlue": "Blue (Reply, follow)",
|
||||||
"cGreen": "Green (Retweet)",
|
"cGreen": "Green (Retweet)",
|
||||||
|
|
@ -144,6 +150,7 @@
|
||||||
"general": "General",
|
"general": "General",
|
||||||
"hide_attachments_in_convo": "Hide attachments in conversations",
|
"hide_attachments_in_convo": "Hide attachments in conversations",
|
||||||
"hide_attachments_in_tl": "Hide attachments in timeline",
|
"hide_attachments_in_tl": "Hide attachments in timeline",
|
||||||
|
"max_thumbnails": "Maximum amount of thumbnails per post",
|
||||||
"hide_isp": "Hide instance-specific panel",
|
"hide_isp": "Hide instance-specific panel",
|
||||||
"preload_images": "Preload images",
|
"preload_images": "Preload images",
|
||||||
"use_one_click_nsfw": "Open NSFW attachments with just one click",
|
"use_one_click_nsfw": "Open NSFW attachments with just one click",
|
||||||
|
|
@ -164,6 +171,7 @@
|
||||||
"lock_account_description": "Restrict your account to approved followers only",
|
"lock_account_description": "Restrict your account to approved followers only",
|
||||||
"loop_video": "Loop videos",
|
"loop_video": "Loop videos",
|
||||||
"loop_video_silent_only": "Loop only videos without sound (i.e. Mastodon's \"gifs\")",
|
"loop_video_silent_only": "Loop only videos without sound (i.e. Mastodon's \"gifs\")",
|
||||||
|
"mutes_tab": "Mutes",
|
||||||
"play_videos_in_modal": "Play videos directly in the media viewer",
|
"play_videos_in_modal": "Play videos directly in the media viewer",
|
||||||
"use_contain_fit": "Don't crop the attachment in thumbnails",
|
"use_contain_fit": "Don't crop the attachment in thumbnails",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
|
|
@ -175,11 +183,18 @@
|
||||||
"notification_visibility_mentions": "Mentions",
|
"notification_visibility_mentions": "Mentions",
|
||||||
"notification_visibility_repeats": "Repeats",
|
"notification_visibility_repeats": "Repeats",
|
||||||
"no_rich_text_description": "Strip rich text formatting from all posts",
|
"no_rich_text_description": "Strip rich text formatting from all posts",
|
||||||
|
"no_blocks": "No blocks",
|
||||||
|
"no_mutes": "No mutes",
|
||||||
"hide_follows_description": "Don't show who I'm following",
|
"hide_follows_description": "Don't show who I'm following",
|
||||||
"hide_followers_description": "Don't show who's following me",
|
"hide_followers_description": "Don't show who's following me",
|
||||||
"show_admin_badge": "Show Admin badge in my profile",
|
"show_admin_badge": "Show Admin badge in my profile",
|
||||||
"show_moderator_badge": "Show Moderator badge in my profile",
|
"show_moderator_badge": "Show Moderator badge in my profile",
|
||||||
"nsfw_clickthrough": "Enable clickthrough NSFW attachment hiding",
|
"nsfw_clickthrough": "Enable clickthrough NSFW attachment hiding",
|
||||||
|
"oauth_tokens": "OAuth tokens",
|
||||||
|
"token": "Token",
|
||||||
|
"refresh_token": "Refresh Token",
|
||||||
|
"valid_until": "Valid Until",
|
||||||
|
"revoke_token": "Revoke",
|
||||||
"panelRadius": "Panels",
|
"panelRadius": "Panels",
|
||||||
"pause_on_unfocused": "Pause streaming when tab is not focused",
|
"pause_on_unfocused": "Pause streaming when tab is not focused",
|
||||||
"presets": "Presets",
|
"presets": "Presets",
|
||||||
|
|
@ -345,6 +360,10 @@
|
||||||
"no_more_statuses": "No more statuses",
|
"no_more_statuses": "No more statuses",
|
||||||
"no_statuses": "No statuses"
|
"no_statuses": "No statuses"
|
||||||
},
|
},
|
||||||
|
"status": {
|
||||||
|
"reply_to": "Reply to",
|
||||||
|
"replies_list": "Replies:"
|
||||||
|
},
|
||||||
"user_card": {
|
"user_card": {
|
||||||
"approve": "Approve",
|
"approve": "Approve",
|
||||||
"block": "Block",
|
"block": "Block",
|
||||||
|
|
@ -366,10 +385,18 @@
|
||||||
"muted": "Muted",
|
"muted": "Muted",
|
||||||
"per_day": "per day",
|
"per_day": "per day",
|
||||||
"remote_follow": "Remote follow",
|
"remote_follow": "Remote follow",
|
||||||
"statuses": "Statuses"
|
"statuses": "Statuses",
|
||||||
|
"unblock": "Unblock",
|
||||||
|
"unblock_progress": "Unblocking...",
|
||||||
|
"block_progress": "Blocking...",
|
||||||
|
"unmute": "Unmute",
|
||||||
|
"unmute_progress": "Unmuting...",
|
||||||
|
"mute_progress": "Muting..."
|
||||||
},
|
},
|
||||||
"user_profile": {
|
"user_profile": {
|
||||||
"timeline_title": "User Timeline"
|
"timeline_title": "User Timeline",
|
||||||
|
"profile_does_not_exist": "Sorry, this profile does not exist.",
|
||||||
|
"profile_loading_error": "Sorry, there was an error loading this profile."
|
||||||
},
|
},
|
||||||
"who_to_follow": {
|
"who_to_follow": {
|
||||||
"more": "More",
|
"more": "More",
|
||||||
|
|
|
||||||
|
|
@ -171,6 +171,11 @@
|
||||||
"show_admin_badge": "Mostrar la placa de administrador en mi perfil",
|
"show_admin_badge": "Mostrar la placa de administrador en mi perfil",
|
||||||
"show_moderator_badge": "Mostrar la placa de moderador en mi perfil",
|
"show_moderator_badge": "Mostrar la placa de moderador en mi perfil",
|
||||||
"nsfw_clickthrough": "Activar el clic para ocultar los adjuntos NSFW",
|
"nsfw_clickthrough": "Activar el clic para ocultar los adjuntos NSFW",
|
||||||
|
"oauth_tokens": "Tokens de OAuth",
|
||||||
|
"token": "Token",
|
||||||
|
"refresh_token": "Actualizar el token",
|
||||||
|
"valid_until": "Válido hasta",
|
||||||
|
"revoke_token": "Revocar",
|
||||||
"panelRadius": "Paneles",
|
"panelRadius": "Paneles",
|
||||||
"pause_on_unfocused": "Parar la transmisión cuando no estés en foco.",
|
"pause_on_unfocused": "Parar la transmisión cuando no estés en foco.",
|
||||||
"presets": "Por defecto",
|
"presets": "Por defecto",
|
||||||
|
|
|
||||||
|
|
@ -133,6 +133,7 @@
|
||||||
"general": "Yleinen",
|
"general": "Yleinen",
|
||||||
"hide_attachments_in_convo": "Piilota liitteet keskusteluissa",
|
"hide_attachments_in_convo": "Piilota liitteet keskusteluissa",
|
||||||
"hide_attachments_in_tl": "Piilota liitteet aikajanalla",
|
"hide_attachments_in_tl": "Piilota liitteet aikajanalla",
|
||||||
|
"max_thumbnails": "Suurin sallittu määrä liitteitä esikatselussa",
|
||||||
"hide_isp": "Piilota palvelimenkohtainen ruutu",
|
"hide_isp": "Piilota palvelimenkohtainen ruutu",
|
||||||
"preload_images": "Esilataa kuvat",
|
"preload_images": "Esilataa kuvat",
|
||||||
"use_one_click_nsfw": "Avaa NSFW-liitteet yhdellä painalluksella",
|
"use_one_click_nsfw": "Avaa NSFW-liitteet yhdellä painalluksella",
|
||||||
|
|
@ -165,6 +166,11 @@
|
||||||
"no_rich_text_description": "Älä näytä tekstin muotoilua.",
|
"no_rich_text_description": "Älä näytä tekstin muotoilua.",
|
||||||
"hide_network_description": "Älä näytä seurauksiani tai seuraajiani",
|
"hide_network_description": "Älä näytä seurauksiani tai seuraajiani",
|
||||||
"nsfw_clickthrough": "Piilota NSFW liitteet klikkauksen taakse",
|
"nsfw_clickthrough": "Piilota NSFW liitteet klikkauksen taakse",
|
||||||
|
"oauth_tokens": "OAuth-merkit",
|
||||||
|
"token": "Token",
|
||||||
|
"refresh_token": "Päivitä token",
|
||||||
|
"valid_until": "Voimassa asti",
|
||||||
|
"revoke_token": "Peruuttaa",
|
||||||
"panelRadius": "Ruudut",
|
"panelRadius": "Ruudut",
|
||||||
"pause_on_unfocused": "Pysäytä automaattinen viestien näyttö välilehden ollessa pois fokuksesta",
|
"pause_on_unfocused": "Pysäytä automaattinen viestien näyttö välilehden ollessa pois fokuksesta",
|
||||||
"presets": "Valmiit teemat",
|
"presets": "Valmiit teemat",
|
||||||
|
|
@ -215,6 +221,10 @@
|
||||||
"up_to_date": "Ajantasalla",
|
"up_to_date": "Ajantasalla",
|
||||||
"no_more_statuses": "Ei enempää viestejä"
|
"no_more_statuses": "Ei enempää viestejä"
|
||||||
},
|
},
|
||||||
|
"status": {
|
||||||
|
"reply_to": "Vastaus",
|
||||||
|
"replies_list": "Vastaukset:"
|
||||||
|
},
|
||||||
"user_card": {
|
"user_card": {
|
||||||
"approve": "Hyväksy",
|
"approve": "Hyväksy",
|
||||||
"block": "Estä",
|
"block": "Estä",
|
||||||
|
|
|
||||||
|
|
@ -137,6 +137,11 @@
|
||||||
"notification_visibility_mentions": "Mentionnés",
|
"notification_visibility_mentions": "Mentionnés",
|
||||||
"notification_visibility_repeats": "Partages",
|
"notification_visibility_repeats": "Partages",
|
||||||
"nsfw_clickthrough": "Masquer les images marquées comme contenu adulte ou sensible",
|
"nsfw_clickthrough": "Masquer les images marquées comme contenu adulte ou sensible",
|
||||||
|
"oauth_tokens": "Jetons OAuth",
|
||||||
|
"token": "Jeton",
|
||||||
|
"refresh_token": "Refresh Token",
|
||||||
|
"valid_until": "Valable jusque",
|
||||||
|
"revoke_token": "Révoquer",
|
||||||
"panelRadius": "Fenêtres",
|
"panelRadius": "Fenêtres",
|
||||||
"pause_on_unfocused": "Suspendre le streaming lorsque l'onglet n'est pas centré",
|
"pause_on_unfocused": "Suspendre le streaming lorsque l'onglet n'est pas centré",
|
||||||
"presets": "Thèmes prédéfinis",
|
"presets": "Thèmes prédéfinis",
|
||||||
|
|
|
||||||
|
|
@ -134,6 +134,11 @@
|
||||||
"notification_visibility_repeats": "Atphostáil",
|
"notification_visibility_repeats": "Atphostáil",
|
||||||
"no_rich_text_description": "Bain formáidiú téacs saibhir ó gach post",
|
"no_rich_text_description": "Bain formáidiú téacs saibhir ó gach post",
|
||||||
"nsfw_clickthrough": "Cumasaigh an ceangaltán NSFW cliceáil ar an gcnaipe",
|
"nsfw_clickthrough": "Cumasaigh an ceangaltán NSFW cliceáil ar an gcnaipe",
|
||||||
|
"oauth_tokens": "Tocanna OAuth",
|
||||||
|
"token": "Token",
|
||||||
|
"refresh_token": "Athnuachan Comórtas",
|
||||||
|
"valid_until": "Bailí Go dtí",
|
||||||
|
"revoke_token": "Athghairm",
|
||||||
"panelRadius": "Painéil",
|
"panelRadius": "Painéil",
|
||||||
"pause_on_unfocused": "Sruthú ar sos nuair a bhíonn an fócas caillte",
|
"pause_on_unfocused": "Sruthú ar sos nuair a bhíonn an fócas caillte",
|
||||||
"presets": "Réamhshocruithe",
|
"presets": "Réamhshocruithe",
|
||||||
|
|
|
||||||
|
|
@ -129,6 +129,11 @@
|
||||||
"notification_visibility_mentions": "אזכורים",
|
"notification_visibility_mentions": "אזכורים",
|
||||||
"notification_visibility_repeats": "חזרות",
|
"notification_visibility_repeats": "חזרות",
|
||||||
"nsfw_clickthrough": "החל החבאת צירופים לא בטוחים לצפיה בעת עבודה בעזרת לחיצת עכבר",
|
"nsfw_clickthrough": "החל החבאת צירופים לא בטוחים לצפיה בעת עבודה בעזרת לחיצת עכבר",
|
||||||
|
"oauth_tokens": "אסימוני OAuth",
|
||||||
|
"token": "אסימון",
|
||||||
|
"refresh_token": "רענון האסימון",
|
||||||
|
"valid_until": "בתוקף עד",
|
||||||
|
"revoke_token": "בטל",
|
||||||
"panelRadius": "פאנלים",
|
"panelRadius": "פאנלים",
|
||||||
"pause_on_unfocused": "השהה זרימת הודעות כשהחלון לא בפוקוס",
|
"pause_on_unfocused": "השהה זרימת הודעות כשהחלון לא בפוקוס",
|
||||||
"presets": "ערכים קבועים מראש",
|
"presets": "ערכים קבועים מראש",
|
||||||
|
|
|
||||||
|
|
@ -93,6 +93,11 @@
|
||||||
"notification_visibility_mentions": "Menzioni",
|
"notification_visibility_mentions": "Menzioni",
|
||||||
"notification_visibility_repeats": "Condivisioni",
|
"notification_visibility_repeats": "Condivisioni",
|
||||||
"no_rich_text_description": "Togli la formattazione del testo da tutti i post",
|
"no_rich_text_description": "Togli la formattazione del testo da tutti i post",
|
||||||
|
"oauth_tokens": "Token OAuth",
|
||||||
|
"token": "Token",
|
||||||
|
"refresh_token": "Aggiorna token",
|
||||||
|
"valid_until": "Valido fino a",
|
||||||
|
"revoke_token": "Revocare",
|
||||||
"panelRadius": "Pannelli",
|
"panelRadius": "Pannelli",
|
||||||
"pause_on_unfocused": "Metti in pausa l'aggiornamento continuo quando la scheda non è in primo piano",
|
"pause_on_unfocused": "Metti in pausa l'aggiornamento continuo quando la scheda non è in primo piano",
|
||||||
"presets": "Valori predefiniti",
|
"presets": "Valori predefiniti",
|
||||||
|
|
|
||||||
|
|
@ -171,6 +171,11 @@
|
||||||
"show_admin_badge": "アドミンのしるしをみる",
|
"show_admin_badge": "アドミンのしるしをみる",
|
||||||
"show_moderator_badge": "モデレーターのしるしをみる",
|
"show_moderator_badge": "モデレーターのしるしをみる",
|
||||||
"nsfw_clickthrough": "NSFWなファイルをかくす",
|
"nsfw_clickthrough": "NSFWなファイルをかくす",
|
||||||
|
"oauth_tokens": "OAuthトークン",
|
||||||
|
"token": "トークン",
|
||||||
|
"refresh_token": "トークンを更新",
|
||||||
|
"valid_until": "まで有効",
|
||||||
|
"revoke_token": "取り消す",
|
||||||
"panelRadius": "パネル",
|
"panelRadius": "パネル",
|
||||||
"pause_on_unfocused": "タブにフォーカスがないときストリーミングをとめる",
|
"pause_on_unfocused": "タブにフォーカスがないときストリーミングをとめる",
|
||||||
"presets": "プリセット",
|
"presets": "プリセット",
|
||||||
|
|
|
||||||
|
|
@ -159,6 +159,11 @@
|
||||||
"hide_follows_description": "내가 팔로우하는 사람을 표시하지 않음",
|
"hide_follows_description": "내가 팔로우하는 사람을 표시하지 않음",
|
||||||
"hide_followers_description": "나를 따르는 사람을 보여주지 마라.",
|
"hide_followers_description": "나를 따르는 사람을 보여주지 마라.",
|
||||||
"nsfw_clickthrough": "NSFW 이미지 \"클릭해서 보이기\"를 활성화",
|
"nsfw_clickthrough": "NSFW 이미지 \"클릭해서 보이기\"를 활성화",
|
||||||
|
"oauth_tokens": "OAuth 토큰",
|
||||||
|
"token": "토큰",
|
||||||
|
"refresh_token": "토큰 새로 고침",
|
||||||
|
"valid_until": "까지 유효하다",
|
||||||
|
"revoke_token": "취소",
|
||||||
"panelRadius": "패널",
|
"panelRadius": "패널",
|
||||||
"pause_on_unfocused": "탭이 활성 상태가 아닐 때 스트리밍 멈추기",
|
"pause_on_unfocused": "탭이 활성 상태가 아닐 때 스트리밍 멈추기",
|
||||||
"presets": "프리셋",
|
"presets": "프리셋",
|
||||||
|
|
|
||||||
|
|
@ -132,6 +132,11 @@
|
||||||
"notification_visibility_repeats": "Gjentakelser",
|
"notification_visibility_repeats": "Gjentakelser",
|
||||||
"no_rich_text_description": "Fjern all formatering fra statuser",
|
"no_rich_text_description": "Fjern all formatering fra statuser",
|
||||||
"nsfw_clickthrough": "Krev trykk for å vise statuser som kan være upassende",
|
"nsfw_clickthrough": "Krev trykk for å vise statuser som kan være upassende",
|
||||||
|
"oauth_tokens": "OAuth Tokens",
|
||||||
|
"token": "Pollett",
|
||||||
|
"refresh_token": "Refresh Token",
|
||||||
|
"valid_until": "Gyldig til",
|
||||||
|
"revoke_token": "Tilbakekall",
|
||||||
"panelRadius": "Panel",
|
"panelRadius": "Panel",
|
||||||
"pause_on_unfocused": "Stopp henting av poster når vinduet ikke er i fokus",
|
"pause_on_unfocused": "Stopp henting av poster når vinduet ikke er i fokus",
|
||||||
"presets": "Forhåndsdefinerte tema",
|
"presets": "Forhåndsdefinerte tema",
|
||||||
|
|
|
||||||
|
|
@ -159,6 +159,11 @@
|
||||||
"no_rich_text_description": "Strip rich text formattering van alle posts",
|
"no_rich_text_description": "Strip rich text formattering van alle posts",
|
||||||
"hide_network_description": "Toon niet wie mij volgt en wie ik volg.",
|
"hide_network_description": "Toon niet wie mij volgt en wie ik volg.",
|
||||||
"nsfw_clickthrough": "Schakel doorklikbaar verbergen van NSFW bijlages in",
|
"nsfw_clickthrough": "Schakel doorklikbaar verbergen van NSFW bijlages in",
|
||||||
|
"oauth_tokens": "OAuth-tokens",
|
||||||
|
"token": "Token",
|
||||||
|
"refresh_token": "Token vernieuwen",
|
||||||
|
"valid_until": "Geldig tot",
|
||||||
|
"revoke_token": "Intrekken",
|
||||||
"panelRadius": "Panelen",
|
"panelRadius": "Panelen",
|
||||||
"pause_on_unfocused": "Pauzeer streamen wanneer de tab niet gefocused is",
|
"pause_on_unfocused": "Pauzeer streamen wanneer de tab niet gefocused is",
|
||||||
"presets": "Presets",
|
"presets": "Presets",
|
||||||
|
|
|
||||||
|
|
@ -142,6 +142,7 @@
|
||||||
"notification_visibility_mentions": "Mencions",
|
"notification_visibility_mentions": "Mencions",
|
||||||
"notification_visibility_repeats": "Repeticions",
|
"notification_visibility_repeats": "Repeticions",
|
||||||
"no_rich_text_description": "Netejar lo format tèxte de totas las publicacions",
|
"no_rich_text_description": "Netejar lo format tèxte de totas las publicacions",
|
||||||
|
"oauth_tokens": "Llistats OAuth",
|
||||||
"pause_on_unfocused": "Pausar la difusion quand l’onglet es pas seleccionat",
|
"pause_on_unfocused": "Pausar la difusion quand l’onglet es pas seleccionat",
|
||||||
"profile_tab": "Perfil",
|
"profile_tab": "Perfil",
|
||||||
"replies_in_timeline": "Responsas del flux",
|
"replies_in_timeline": "Responsas del flux",
|
||||||
|
|
|
||||||
|
|
@ -86,6 +86,11 @@
|
||||||
"name_bio": "Imię i bio",
|
"name_bio": "Imię i bio",
|
||||||
"new_password": "Nowe hasło",
|
"new_password": "Nowe hasło",
|
||||||
"nsfw_clickthrough": "Włącz domyślne ukrywanie załączników o treści nieprzyzwoitej (NSFW)",
|
"nsfw_clickthrough": "Włącz domyślne ukrywanie załączników o treści nieprzyzwoitej (NSFW)",
|
||||||
|
"oauth_tokens": "Tokeny OAuth",
|
||||||
|
"token": "Token",
|
||||||
|
"refresh_token": "Odśwież token",
|
||||||
|
"valid_until": "Ważne do",
|
||||||
|
"revoke_token": "Odwołać",
|
||||||
"panelRadius": "Panele",
|
"panelRadius": "Panele",
|
||||||
"presets": "Gotowe motywy",
|
"presets": "Gotowe motywy",
|
||||||
"profile_background": "Tło profilu",
|
"profile_background": "Tło profilu",
|
||||||
|
|
|
||||||
|
|
@ -132,6 +132,11 @@
|
||||||
"show_admin_badge": "Показывать значок администратора в моем профиле",
|
"show_admin_badge": "Показывать значок администратора в моем профиле",
|
||||||
"show_moderator_badge": "Показывать значок модератора в моем профиле",
|
"show_moderator_badge": "Показывать значок модератора в моем профиле",
|
||||||
"nsfw_clickthrough": "Включить скрытие NSFW вложений",
|
"nsfw_clickthrough": "Включить скрытие NSFW вложений",
|
||||||
|
"oauth_tokens": "OAuth токены",
|
||||||
|
"token": "Токен",
|
||||||
|
"refresh_token": "Рефреш токен",
|
||||||
|
"valid_until": "Годен до",
|
||||||
|
"revoke_token": "Удалить",
|
||||||
"panelRadius": "Панели",
|
"panelRadius": "Панели",
|
||||||
"pause_on_unfocused": "Приостановить загрузку когда вкладка не в фокусе",
|
"pause_on_unfocused": "Приостановить загрузку когда вкладка не в фокусе",
|
||||||
"presets": "Пресеты",
|
"presets": "Пресеты",
|
||||||
|
|
|
||||||
|
|
@ -134,6 +134,11 @@
|
||||||
"notification_visibility_repeats": "转发",
|
"notification_visibility_repeats": "转发",
|
||||||
"no_rich_text_description": "不显示富文本格式",
|
"no_rich_text_description": "不显示富文本格式",
|
||||||
"nsfw_clickthrough": "将不和谐附件隐藏,点击才能打开",
|
"nsfw_clickthrough": "将不和谐附件隐藏,点击才能打开",
|
||||||
|
"oauth_tokens": "OAuth令牌",
|
||||||
|
"token": "代币",
|
||||||
|
"refresh_token": "刷新令牌",
|
||||||
|
"valid_until": "有效期至",
|
||||||
|
"revoke_token": "撤消",
|
||||||
"panelRadius": "面板",
|
"panelRadius": "面板",
|
||||||
"pause_on_unfocused": "在离开页面时暂停时间线推送",
|
"pause_on_unfocused": "在离开页面时暂停时间线推送",
|
||||||
"presets": "预置",
|
"presets": "预置",
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import configModule from './modules/config.js'
|
||||||
import chatModule from './modules/chat.js'
|
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 VueTimeago from 'vue-timeago'
|
import VueTimeago from 'vue-timeago'
|
||||||
import VueI18n from 'vue-i18n'
|
import VueI18n from 'vue-i18n'
|
||||||
|
|
@ -64,7 +65,8 @@ createPersistedState(persistedStateOptions).then((persistedState) => {
|
||||||
config: configModule,
|
config: configModule,
|
||||||
chat: chatModule,
|
chat: chatModule,
|
||||||
oauth: oauthModule,
|
oauth: oauthModule,
|
||||||
mediaViewer: mediaViewerModule
|
mediaViewer: mediaViewerModule,
|
||||||
|
oauthTokens: oauthTokensModule
|
||||||
},
|
},
|
||||||
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.
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ const defaultState = {
|
||||||
collapseMessageWithSubject: undefined, // instance default
|
collapseMessageWithSubject: undefined, // instance default
|
||||||
hideAttachments: false,
|
hideAttachments: false,
|
||||||
hideAttachmentsInConv: false,
|
hideAttachmentsInConv: false,
|
||||||
|
maxThumbnails: 16,
|
||||||
hideNsfw: true,
|
hideNsfw: true,
|
||||||
preloadImage: true,
|
preloadImage: true,
|
||||||
loopVideo: true,
|
loopVideo: true,
|
||||||
|
|
|
||||||
26
src/modules/oauth_tokens.js
Normal file
26
src/modules/oauth_tokens.js
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
const oauthTokens = {
|
||||||
|
state: {
|
||||||
|
tokens: []
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
fetchTokens ({rootState, commit}) {
|
||||||
|
rootState.api.backendInteractor.fetchOAuthTokens().then((tokens) => {
|
||||||
|
commit('swapTokens', tokens)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
revokeToken ({rootState, commit, state}, id) {
|
||||||
|
rootState.api.backendInteractor.revokeOAuthToken(id).then((response) => {
|
||||||
|
if (response.status === 201) {
|
||||||
|
commit('swapTokens', state.tokens.filter(token => token.id !== id))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
swapTokens (state, tokens) {
|
||||||
|
state.tokens = tokens
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default oauthTokens
|
||||||
|
|
@ -126,7 +126,7 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us
|
||||||
// This makes sure that user timeline won't get data meant for other
|
// This makes sure that user timeline won't get data meant for other
|
||||||
// user. I.e. opening different user profiles makes request which could
|
// user. I.e. opening different user profiles makes request which could
|
||||||
// return data late after user already viewing different user profile
|
// return data late after user already viewing different user profile
|
||||||
if (timeline === 'user' && timelineObject.userId !== userId) {
|
if ((timeline === 'user' || timeline === 'media') && timelineObject.userId !== userId) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -303,6 +303,8 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot
|
||||||
setTimeout(notification.close.bind(notification), 5000)
|
setTimeout(notification.close.bind(notification), 5000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (notification.seen) {
|
||||||
|
state.notifications.idStore[notification.id].seen = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -72,19 +72,31 @@ export const mutations = {
|
||||||
},
|
},
|
||||||
// Because frontend doesn't have a reason to keep these stuff in memory
|
// Because frontend doesn't have a reason to keep these stuff in memory
|
||||||
// outside of viewing someones user profile.
|
// outside of viewing someones user profile.
|
||||||
clearFriendsAndFollowers (state, userKey) {
|
clearFriends (state, userId) {
|
||||||
const user = state.usersObject[userKey]
|
const user = state.usersObject[userId]
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
user.friends = []
|
user.friends = []
|
||||||
user.followers = []
|
|
||||||
user.friendsPage = 0
|
user.friendsPage = 0
|
||||||
|
},
|
||||||
|
clearFollowers (state, userId) {
|
||||||
|
const user = state.usersObject[userId]
|
||||||
|
if (!user) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
user.followers = []
|
||||||
user.followersPage = 0
|
user.followersPage = 0
|
||||||
},
|
},
|
||||||
addNewUsers (state, users) {
|
addNewUsers (state, users) {
|
||||||
each(users, (user) => mergeOrAdd(state.users, state.usersObject, user))
|
each(users, (user) => mergeOrAdd(state.users, state.usersObject, user))
|
||||||
},
|
},
|
||||||
|
saveBlocks (state, blockIds) {
|
||||||
|
state.currentUser.blockIds = blockIds
|
||||||
|
},
|
||||||
|
saveMutes (state, muteIds) {
|
||||||
|
state.currentUser.muteIds = muteIds
|
||||||
|
},
|
||||||
setUserForStatus (state, status) {
|
setUserForStatus (state, status) {
|
||||||
status.user = state.usersObject[status.user.id]
|
status.user = state.usersObject[status.user.id]
|
||||||
},
|
},
|
||||||
|
|
@ -134,7 +146,39 @@ const users = {
|
||||||
getters,
|
getters,
|
||||||
actions: {
|
actions: {
|
||||||
fetchUser (store, id) {
|
fetchUser (store, id) {
|
||||||
store.rootState.api.backendInteractor.fetchUser({ id })
|
return store.rootState.api.backendInteractor.fetchUser({ id })
|
||||||
|
.then((user) => store.commit('addNewUsers', [user]))
|
||||||
|
},
|
||||||
|
fetchBlocks (store) {
|
||||||
|
return store.rootState.api.backendInteractor.fetchBlocks()
|
||||||
|
.then((blocks) => {
|
||||||
|
store.commit('saveBlocks', map(blocks, 'id'))
|
||||||
|
store.commit('addNewUsers', blocks)
|
||||||
|
return blocks
|
||||||
|
})
|
||||||
|
},
|
||||||
|
blockUser (store, id) {
|
||||||
|
return store.rootState.api.backendInteractor.blockUser(id)
|
||||||
|
.then((user) => store.commit('addNewUsers', [user]))
|
||||||
|
},
|
||||||
|
unblockUser (store, id) {
|
||||||
|
return store.rootState.api.backendInteractor.unblockUser(id)
|
||||||
|
.then((user) => store.commit('addNewUsers', [user]))
|
||||||
|
},
|
||||||
|
fetchMutes (store) {
|
||||||
|
return store.rootState.api.backendInteractor.fetchMutes()
|
||||||
|
.then((mutedUsers) => {
|
||||||
|
each(mutedUsers, (user) => { user.muted = true })
|
||||||
|
store.commit('addNewUsers', mutedUsers)
|
||||||
|
store.commit('saveMutes', map(mutedUsers, 'id'))
|
||||||
|
})
|
||||||
|
},
|
||||||
|
muteUser (store, id) {
|
||||||
|
return store.state.api.backendInteractor.setUserMute({ id, muted: true })
|
||||||
|
.then((user) => store.commit('addNewUsers', [user]))
|
||||||
|
},
|
||||||
|
unmuteUser (store, id) {
|
||||||
|
return store.state.api.backendInteractor.setUserMute({ id, muted: false })
|
||||||
.then((user) => store.commit('addNewUsers', [user]))
|
.then((user) => store.commit('addNewUsers', [user]))
|
||||||
},
|
},
|
||||||
addFriends ({ rootState, commit }, fetchBy) {
|
addFriends ({ rootState, commit }, fetchBy) {
|
||||||
|
|
@ -151,20 +195,19 @@ const users = {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
addFollowers ({ rootState, commit }, fetchBy) {
|
addFollowers ({ rootState, commit }, fetchBy) {
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const user = rootState.users.usersObject[fetchBy]
|
const user = rootState.users.usersObject[fetchBy]
|
||||||
const page = user.followersPage || 1
|
const page = user.followersPage || 1
|
||||||
rootState.api.backendInteractor.fetchFollowers({ id: user.id, page })
|
return rootState.api.backendInteractor.fetchFollowers({ id: user.id, page })
|
||||||
.then((followers) => {
|
.then((followers) => {
|
||||||
commit('addFollowers', { id: user.id, followers, page })
|
commit('addFollowers', { id: user.id, followers, page })
|
||||||
resolve(followers)
|
return followers
|
||||||
}).catch(() => {
|
|
||||||
reject()
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
clearFriendsAndFollowers ({ commit }, userKey) {
|
clearFriends ({ commit }, userId) {
|
||||||
commit('clearFriendsAndFollowers', userKey)
|
commit('clearFriends', userId)
|
||||||
|
},
|
||||||
|
clearFollowers ({ commit }, userId) {
|
||||||
|
commit('clearFollowers', userId)
|
||||||
},
|
},
|
||||||
registerPushNotifications (store) {
|
registerPushNotifications (store) {
|
||||||
const token = store.state.currentUser.credentials
|
const token = store.state.currentUser.credentials
|
||||||
|
|
@ -263,6 +306,8 @@ const users = {
|
||||||
const user = data
|
const user = data
|
||||||
// user.credentials = userCredentials
|
// user.credentials = userCredentials
|
||||||
user.credentials = accessToken
|
user.credentials = accessToken
|
||||||
|
user.blockIds = []
|
||||||
|
user.muteIds = []
|
||||||
commit('setCurrentUser', user)
|
commit('setCurrentUser', user)
|
||||||
commit('addNewUsers', [user])
|
commit('addNewUsers', [user])
|
||||||
|
|
||||||
|
|
@ -279,11 +324,8 @@ const users = {
|
||||||
// Start getting fresh posts.
|
// Start getting fresh posts.
|
||||||
store.dispatch('startFetching', { timeline: 'friends' })
|
store.dispatch('startFetching', { timeline: 'friends' })
|
||||||
|
|
||||||
// Get user mutes and follower info
|
// Get user mutes
|
||||||
store.rootState.api.backendInteractor.fetchMutes().then((mutedUsers) => {
|
store.dispatch('fetchMutes')
|
||||||
each(mutedUsers, (user) => { user.muted = true })
|
|
||||||
store.commit('addNewUsers', mutedUsers)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Fetch our friends
|
// Fetch our friends
|
||||||
store.rootState.api.backendInteractor.fetchFriends({ id: user.id })
|
store.rootState.api.backendInteractor.fetchFriends({ id: user.id })
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ const MENTIONS_URL = '/api/statuses/mentions.json'
|
||||||
const DM_TIMELINE_URL = '/api/statuses/dm_timeline.json'
|
const DM_TIMELINE_URL = '/api/statuses/dm_timeline.json'
|
||||||
const FOLLOWERS_URL = '/api/statuses/followers.json'
|
const FOLLOWERS_URL = '/api/statuses/followers.json'
|
||||||
const FRIENDS_URL = '/api/statuses/friends.json'
|
const FRIENDS_URL = '/api/statuses/friends.json'
|
||||||
|
const BLOCKS_URL = '/api/statuses/blocks.json'
|
||||||
const FOLLOWING_URL = '/api/friendships/create.json'
|
const FOLLOWING_URL = '/api/friendships/create.json'
|
||||||
const UNFOLLOWING_URL = '/api/friendships/destroy.json'
|
const UNFOLLOWING_URL = '/api/friendships/destroy.json'
|
||||||
const QVITTER_USER_PREF_URL = '/api/qvitter/set_profile_pref.json'
|
const QVITTER_USER_PREF_URL = '/api/qvitter/set_profile_pref.json'
|
||||||
|
|
@ -46,6 +47,7 @@ const MASTODON_USER_FAVORITES_TIMELINE_URL = '/api/v1/favourites'
|
||||||
import { each, map } from 'lodash'
|
import { each, map } from 'lodash'
|
||||||
import { parseStatus, parseUser, parseNotification } from '../entity_normalizer/entity_normalizer.service.js'
|
import { parseStatus, parseUser, parseNotification } from '../entity_normalizer/entity_normalizer.service.js'
|
||||||
import 'whatwg-fetch'
|
import 'whatwg-fetch'
|
||||||
|
import { StatusCodeError } from '../errors/errors'
|
||||||
|
|
||||||
const oldfetch = window.fetch
|
const oldfetch = window.fetch
|
||||||
|
|
||||||
|
|
@ -243,7 +245,15 @@ const denyUser = ({id, credentials}) => {
|
||||||
const fetchUser = ({id, credentials}) => {
|
const fetchUser = ({id, credentials}) => {
|
||||||
let url = `${USER_URL}?user_id=${id}`
|
let url = `${USER_URL}?user_id=${id}`
|
||||||
return fetch(url, { headers: authHeaders(credentials) })
|
return fetch(url, { headers: authHeaders(credentials) })
|
||||||
.then((data) => data.json())
|
.then((response) => {
|
||||||
|
return new Promise((resolve, reject) => response.json()
|
||||||
|
.then((json) => {
|
||||||
|
if (!response.ok) {
|
||||||
|
return reject(new StatusCodeError(response.status, json, { url }, response))
|
||||||
|
}
|
||||||
|
return resolve(json)
|
||||||
|
}))
|
||||||
|
})
|
||||||
.then((data) => parseUser(data))
|
.then((data) => parseUser(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -518,6 +528,34 @@ const fetchMutes = ({credentials}) => {
|
||||||
}).then((data) => data.json())
|
}).then((data) => data.json())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fetchBlocks = ({page, credentials}) => {
|
||||||
|
return fetch(BLOCKS_URL, {
|
||||||
|
headers: authHeaders(credentials)
|
||||||
|
}).then((data) => {
|
||||||
|
if (data.ok) {
|
||||||
|
return data.json()
|
||||||
|
}
|
||||||
|
throw new Error('Error fetching blocks', data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchOAuthTokens = ({credentials}) => {
|
||||||
|
const url = '/api/oauth_tokens.json'
|
||||||
|
|
||||||
|
return fetch(url, {
|
||||||
|
headers: authHeaders(credentials)
|
||||||
|
}).then((data) => data.json())
|
||||||
|
}
|
||||||
|
|
||||||
|
const revokeOAuthToken = ({id, credentials}) => {
|
||||||
|
const url = `/api/oauth_tokens/${id}`
|
||||||
|
|
||||||
|
return fetch(url, {
|
||||||
|
headers: authHeaders(credentials),
|
||||||
|
method: 'DELETE'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const suggestions = ({credentials}) => {
|
const suggestions = ({credentials}) => {
|
||||||
return fetch(SUGGESTIONS_URL, {
|
return fetch(SUGGESTIONS_URL, {
|
||||||
headers: authHeaders(credentials)
|
headers: authHeaders(credentials)
|
||||||
|
|
@ -559,6 +597,9 @@ const apiService = {
|
||||||
fetchAllFollowing,
|
fetchAllFollowing,
|
||||||
setUserMute,
|
setUserMute,
|
||||||
fetchMutes,
|
fetchMutes,
|
||||||
|
fetchBlocks,
|
||||||
|
fetchOAuthTokens,
|
||||||
|
revokeOAuthToken,
|
||||||
register,
|
register,
|
||||||
getCaptcha,
|
getCaptcha,
|
||||||
updateAvatar,
|
updateAvatar,
|
||||||
|
|
|
||||||
|
|
@ -54,8 +54,8 @@ const backendInteractorService = (credentials) => {
|
||||||
return apiService.denyUser({credentials, id})
|
return apiService.denyUser({credentials, id})
|
||||||
}
|
}
|
||||||
|
|
||||||
const startFetching = ({timeline, store, userId = false}) => {
|
const startFetching = ({timeline, store, userId = false, tag}) => {
|
||||||
return timelineFetcherService.startFetching({timeline, store, credentials, userId})
|
return timelineFetcherService.startFetching({timeline, store, credentials, userId, tag})
|
||||||
}
|
}
|
||||||
|
|
||||||
const setUserMute = ({id, muted = true}) => {
|
const setUserMute = ({id, muted = true}) => {
|
||||||
|
|
@ -63,7 +63,10 @@ const backendInteractorService = (credentials) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchMutes = () => apiService.fetchMutes({credentials})
|
const fetchMutes = () => apiService.fetchMutes({credentials})
|
||||||
|
const fetchBlocks = (params) => apiService.fetchBlocks({credentials, ...params})
|
||||||
const fetchFollowRequests = () => apiService.fetchFollowRequests({credentials})
|
const fetchFollowRequests = () => apiService.fetchFollowRequests({credentials})
|
||||||
|
const fetchOAuthTokens = () => apiService.fetchOAuthTokens({credentials})
|
||||||
|
const revokeOAuthToken = (id) => apiService.revokeOAuthToken({id, credentials})
|
||||||
|
|
||||||
const getCaptcha = () => apiService.getCaptcha()
|
const getCaptcha = () => apiService.getCaptcha()
|
||||||
const register = (params) => apiService.register(params)
|
const register = (params) => apiService.register(params)
|
||||||
|
|
@ -94,6 +97,9 @@ const backendInteractorService = (credentials) => {
|
||||||
startFetching,
|
startFetching,
|
||||||
setUserMute,
|
setUserMute,
|
||||||
fetchMutes,
|
fetchMutes,
|
||||||
|
fetchBlocks,
|
||||||
|
fetchOAuthTokens,
|
||||||
|
revokeOAuthToken,
|
||||||
register,
|
register,
|
||||||
getCaptcha,
|
getCaptcha,
|
||||||
updateAvatar,
|
updateAvatar,
|
||||||
|
|
|
||||||
10
src/services/component_utils/component_utils.js
Normal file
10
src/services/component_utils/component_utils.js
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import isFunction from 'lodash/isFunction'
|
||||||
|
|
||||||
|
const getComponentOptions = (Component) => (isFunction(Component)) ? Component.options : Component
|
||||||
|
|
||||||
|
const getComponentProps = (Component) => getComponentOptions(Component).props
|
||||||
|
|
||||||
|
export {
|
||||||
|
getComponentOptions,
|
||||||
|
getComponentProps
|
||||||
|
}
|
||||||
14
src/services/errors/errors.js
Normal file
14
src/services/errors/errors.js
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
export function StatusCodeError (statusCode, body, options, response) {
|
||||||
|
this.name = 'StatusCodeError'
|
||||||
|
this.statusCode = statusCode
|
||||||
|
this.message = statusCode + ' - ' + (JSON && JSON.stringify ? JSON.stringify(body) : body)
|
||||||
|
this.error = body // legacy attribute
|
||||||
|
this.options = options
|
||||||
|
this.response = response
|
||||||
|
|
||||||
|
if (Error.captureStackTrace) { // required for non-V8 environments
|
||||||
|
Error.captureStackTrace(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StatusCodeError.prototype = Object.create(Error.prototype)
|
||||||
|
StatusCodeError.prototype.constructor = StatusCodeError
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
import apiService from '../api/api.service.js'
|
||||||
|
|
||||||
|
const fetchAndUpdate = ({ store, credentials }) => {
|
||||||
|
return apiService.fetchFollowRequests({ credentials })
|
||||||
|
.then((requests) => {
|
||||||
|
store.commit('setFollowRequests', requests)
|
||||||
|
}, () => {})
|
||||||
|
.catch(() => {})
|
||||||
|
}
|
||||||
|
|
||||||
|
const startFetching = ({credentials, store}) => {
|
||||||
|
fetchAndUpdate({ credentials, store })
|
||||||
|
const boundFetchAndUpdate = () => fetchAndUpdate({ credentials, store })
|
||||||
|
return setInterval(boundFetchAndUpdate, 10000)
|
||||||
|
}
|
||||||
|
|
||||||
|
const followRequestFetcher = {
|
||||||
|
startFetching
|
||||||
|
}
|
||||||
|
|
||||||
|
export default followRequestFetcher
|
||||||
|
|
@ -16,7 +16,17 @@ const fetchAndUpdate = ({store, credentials, older = false}) => {
|
||||||
args['until'] = timelineData.minId
|
args['until'] = timelineData.minId
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// load unread notifications repeadedly to provide consistency between browser tabs
|
||||||
|
const notifications = timelineData.data
|
||||||
|
const unread = notifications.filter(n => !n.seen).map(n => n.id)
|
||||||
|
if (!unread.length) {
|
||||||
args['since'] = timelineData.maxId
|
args['since'] = timelineData.maxId
|
||||||
|
} else {
|
||||||
|
args['since'] = Math.min(...unread) - 1
|
||||||
|
if (timelineData.maxId !== Math.max(...unread)) {
|
||||||
|
args['until'] = Math.max(...unread, args['since'] + 20)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
args['timeline'] = 'notifications'
|
args['timeline'] = 'notifications'
|
||||||
|
|
|
||||||
96
yarn.lock
96
yarn.lock
|
|
@ -38,11 +38,7 @@
|
||||||
dom-event-types "^1.0.0"
|
dom-event-types "^1.0.0"
|
||||||
lodash "^4.17.4"
|
lodash "^4.17.4"
|
||||||
|
|
||||||
abbrev@1:
|
abbrev@1, abbrev@1.0.x:
|
||||||
version "1.1.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
|
|
||||||
|
|
||||||
abbrev@1.0.x:
|
|
||||||
version "1.0.9"
|
version "1.0.9"
|
||||||
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135"
|
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135"
|
||||||
|
|
||||||
|
|
@ -1385,14 +1381,10 @@ color-convert@^1.3.0, color-convert@^1.9.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
color-name "1.1.3"
|
color-name "1.1.3"
|
||||||
|
|
||||||
color-name@1.1.3:
|
color-name@1.1.3, color-name@^1.0.0:
|
||||||
version "1.1.3"
|
version "1.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
|
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
|
||||||
|
|
||||||
color-name@^1.0.0:
|
|
||||||
version "1.1.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
|
|
||||||
|
|
||||||
color-string@^0.3.0:
|
color-string@^0.3.0:
|
||||||
version "0.3.0"
|
version "0.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/color-string/-/color-string-0.3.0.tgz#27d46fb67025c5c2fa25993bfbf579e47841b991"
|
resolved "https://registry.yarnpkg.com/color-string/-/color-string-0.3.0.tgz#27d46fb67025c5c2fa25993bfbf579e47841b991"
|
||||||
|
|
@ -2431,14 +2423,10 @@ extract-zip@^1.6.5, extract-zip@^1.6.7:
|
||||||
mkdirp "0.5.1"
|
mkdirp "0.5.1"
|
||||||
yauzl "2.4.1"
|
yauzl "2.4.1"
|
||||||
|
|
||||||
extsprintf@1.3.0:
|
extsprintf@1.3.0, extsprintf@^1.2.0:
|
||||||
version "1.3.0"
|
version "1.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
|
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
|
||||||
|
|
||||||
extsprintf@^1.2.0:
|
|
||||||
version "1.4.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
|
|
||||||
|
|
||||||
fast-deep-equal@^2.0.1:
|
fast-deep-equal@^2.0.1:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49"
|
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49"
|
||||||
|
|
@ -3024,18 +3012,12 @@ https-proxy-agent@1:
|
||||||
debug "2"
|
debug "2"
|
||||||
extend "3"
|
extend "3"
|
||||||
|
|
||||||
iconv-lite@0.4.23:
|
iconv-lite@0.4.23, iconv-lite@^0.4.4:
|
||||||
version "0.4.23"
|
version "0.4.23"
|
||||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63"
|
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63"
|
||||||
dependencies:
|
dependencies:
|
||||||
safer-buffer ">= 2.1.2 < 3"
|
safer-buffer ">= 2.1.2 < 3"
|
||||||
|
|
||||||
iconv-lite@^0.4.4:
|
|
||||||
version "0.4.24"
|
|
||||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
|
|
||||||
dependencies:
|
|
||||||
safer-buffer ">= 2.1.2 < 3"
|
|
||||||
|
|
||||||
icss-replace-symbols@^1.1.0:
|
icss-replace-symbols@^1.1.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz#06ea6f83679a7749e386cfe1fe812ae5db223ded"
|
resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz#06ea6f83679a7749e386cfe1fe812ae5db223ded"
|
||||||
|
|
@ -3480,11 +3462,7 @@ js-beautify@^1.6.3:
|
||||||
mkdirp "~0.5.0"
|
mkdirp "~0.5.0"
|
||||||
nopt "~4.0.1"
|
nopt "~4.0.1"
|
||||||
|
|
||||||
"js-tokens@^3.0.0 || ^4.0.0":
|
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^3.0.2:
|
||||||
version "4.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
|
|
||||||
|
|
||||||
js-tokens@^3.0.2:
|
|
||||||
version "3.0.2"
|
version "3.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
|
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
|
||||||
|
|
||||||
|
|
@ -4237,7 +4215,7 @@ minimatch@3.0.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
brace-expansion "^1.0.0"
|
brace-expansion "^1.0.0"
|
||||||
|
|
||||||
minimist@0.0.8:
|
minimist@0.0.8, minimist@~0.0.1:
|
||||||
version "0.0.8"
|
version "0.0.8"
|
||||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
|
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
|
||||||
|
|
||||||
|
|
@ -4245,10 +4223,6 @@ minimist@1.2.0, minimist@^1.1.3, minimist@^1.2.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
|
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
|
||||||
|
|
||||||
minimist@~0.0.1:
|
|
||||||
version "0.0.10"
|
|
||||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf"
|
|
||||||
|
|
||||||
minipass@^2.2.1, minipass@^2.3.4:
|
minipass@^2.2.1, minipass@^2.3.4:
|
||||||
version "2.3.5"
|
version "2.3.5"
|
||||||
resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.5.tgz#cacebe492022497f656b0f0f51e2682a9ed2d848"
|
resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.5.tgz#cacebe492022497f656b0f0f51e2682a9ed2d848"
|
||||||
|
|
@ -4679,7 +4653,7 @@ os-locale@^1.4.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
lcid "^1.0.0"
|
lcid "^1.0.0"
|
||||||
|
|
||||||
os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.1, os-tmpdir@~1.0.2:
|
os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.1:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
|
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
|
||||||
|
|
||||||
|
|
@ -4787,10 +4761,6 @@ path-is-inside@^1.0.1:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53"
|
resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53"
|
||||||
|
|
||||||
path-parse@^1.0.6:
|
|
||||||
version "1.0.6"
|
|
||||||
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c"
|
|
||||||
|
|
||||||
path-to-regexp@0.1.7:
|
path-to-regexp@0.1.7:
|
||||||
version "0.1.7"
|
version "0.1.7"
|
||||||
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
|
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
|
||||||
|
|
@ -5224,14 +5194,10 @@ punycode@^2.1.0:
|
||||||
version "2.1.1"
|
version "2.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
|
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
|
||||||
|
|
||||||
q@1.4.1:
|
q@1.4.1, q@^1.1.2:
|
||||||
version "1.4.1"
|
version "1.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/q/-/q-1.4.1.tgz#55705bcd93c5f3673530c2c2cbc0c2b3addc286e"
|
resolved "https://registry.yarnpkg.com/q/-/q-1.4.1.tgz#55705bcd93c5f3673530c2c2cbc0c2b3addc286e"
|
||||||
|
|
||||||
q@^1.1.2:
|
|
||||||
version "1.5.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
|
|
||||||
|
|
||||||
qjobs@^1.1.4:
|
qjobs@^1.1.4:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/qjobs/-/qjobs-1.2.0.tgz#c45e9c61800bd087ef88d7e256423bdd49e5d071"
|
resolved "https://registry.yarnpkg.com/qjobs/-/qjobs-1.2.0.tgz#c45e9c61800bd087ef88d7e256423bdd49e5d071"
|
||||||
|
|
@ -5546,16 +5512,10 @@ resolve-url@^0.2.1:
|
||||||
version "0.2.1"
|
version "0.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
|
resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
|
||||||
|
|
||||||
resolve@1.1.x:
|
resolve@1.1.x, resolve@^1.1.6:
|
||||||
version "1.1.7"
|
version "1.1.7"
|
||||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
|
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
|
||||||
|
|
||||||
resolve@^1.1.6:
|
|
||||||
version "1.9.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.9.0.tgz#a14c6fdfa8f92a7df1d996cb7105fa744658ea06"
|
|
||||||
dependencies:
|
|
||||||
path-parse "^1.0.6"
|
|
||||||
|
|
||||||
restore-cursor@^1.0.1:
|
restore-cursor@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541"
|
resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541"
|
||||||
|
|
@ -5607,14 +5567,10 @@ safe-regex@^1.1.0:
|
||||||
version "2.1.2"
|
version "2.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
||||||
|
|
||||||
samsam@1.1.2:
|
samsam@1.1.2, samsam@~1.1:
|
||||||
version "1.1.2"
|
version "1.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.1.2.tgz#bec11fdc83a9fda063401210e40176c3024d1567"
|
resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.1.2.tgz#bec11fdc83a9fda063401210e40176c3024d1567"
|
||||||
|
|
||||||
samsam@~1.1:
|
|
||||||
version "1.1.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.1.3.tgz#9f5087419b4d091f232571e7fa52e90b0f552621"
|
|
||||||
|
|
||||||
sanitize-html@^1.13.0:
|
sanitize-html@^1.13.0:
|
||||||
version "1.20.0"
|
version "1.20.0"
|
||||||
resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-1.20.0.tgz#9a602beb1c9faf960fb31f9890f61911cc4d9156"
|
resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-1.20.0.tgz#9a602beb1c9faf960fb31f9890f61911cc4d9156"
|
||||||
|
|
@ -5988,18 +5944,14 @@ static-extend@^0.1.1:
|
||||||
define-property "^0.2.5"
|
define-property "^0.2.5"
|
||||||
object-copy "^0.1.0"
|
object-copy "^0.1.0"
|
||||||
|
|
||||||
"statuses@>= 1.4.0 < 2":
|
"statuses@>= 1.4.0 < 2", statuses@~1.4.0:
|
||||||
version "1.5.0"
|
version "1.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
|
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087"
|
||||||
|
|
||||||
statuses@~1.3.1:
|
statuses@~1.3.1:
|
||||||
version "1.3.1"
|
version "1.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e"
|
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e"
|
||||||
|
|
||||||
statuses@~1.4.0:
|
|
||||||
version "1.4.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087"
|
|
||||||
|
|
||||||
stream-browserify@^2.0.1:
|
stream-browserify@^2.0.1:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.1.tgz#66266ee5f9bdb9940a4e4514cafb43bb71e5c9db"
|
resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.1.tgz#66266ee5f9bdb9940a4e4514cafb43bb71e5c9db"
|
||||||
|
|
@ -6088,7 +6040,7 @@ strip-json-comments@~2.0.1:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
|
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
|
||||||
|
|
||||||
supports-color@3.1.2:
|
supports-color@3.1.2, supports-color@^3.1.0:
|
||||||
version "3.1.2"
|
version "3.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.1.2.tgz#72a262894d9d408b956ca05ff37b2ed8a6e2a2d5"
|
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.1.2.tgz#72a262894d9d408b956ca05ff37b2ed8a6e2a2d5"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
@ -6098,7 +6050,7 @@ supports-color@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
|
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
|
||||||
|
|
||||||
supports-color@^3.1.0, supports-color@^3.2.3:
|
supports-color@^3.2.3:
|
||||||
version "3.2.3"
|
version "3.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6"
|
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
@ -6196,18 +6148,12 @@ timers-browserify@^2.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
setimmediate "^1.0.4"
|
setimmediate "^1.0.4"
|
||||||
|
|
||||||
tmp@0.0.31:
|
tmp@0.0.31, tmp@0.0.x:
|
||||||
version "0.0.31"
|
version "0.0.31"
|
||||||
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.31.tgz#8f38ab9438e17315e5dbd8b3657e8bfb277ae4a7"
|
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.31.tgz#8f38ab9438e17315e5dbd8b3657e8bfb277ae4a7"
|
||||||
dependencies:
|
dependencies:
|
||||||
os-tmpdir "~1.0.1"
|
os-tmpdir "~1.0.1"
|
||||||
|
|
||||||
tmp@0.0.x:
|
|
||||||
version "0.0.33"
|
|
||||||
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
|
|
||||||
dependencies:
|
|
||||||
os-tmpdir "~1.0.2"
|
|
||||||
|
|
||||||
to-array@0.1.4:
|
to-array@0.1.4:
|
||||||
version "0.1.4"
|
version "0.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/to-array/-/to-array-0.1.4.tgz#17e6c11f73dd4f3d74cda7a4ff3238e9ad9bf890"
|
resolved "https://registry.yarnpkg.com/to-array/-/to-array-0.1.4.tgz#17e6c11f73dd4f3d74cda7a4ff3238e9ad9bf890"
|
||||||
|
|
@ -6476,6 +6422,16 @@ vue-chat-scroll@^1.2.1:
|
||||||
version "1.3.5"
|
version "1.3.5"
|
||||||
resolved "https://registry.yarnpkg.com/vue-chat-scroll/-/vue-chat-scroll-1.3.5.tgz#a5ee5bae5058f614818a96eac5ee3be4394a2f68"
|
resolved "https://registry.yarnpkg.com/vue-chat-scroll/-/vue-chat-scroll-1.3.5.tgz#a5ee5bae5058f614818a96eac5ee3be4394a2f68"
|
||||||
|
|
||||||
|
vue-compose@^0.7.1:
|
||||||
|
version "0.7.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/vue-compose/-/vue-compose-0.7.1.tgz#1c11c4cd5e2c8f2743b03fce8ab43d78aabc20b3"
|
||||||
|
dependencies:
|
||||||
|
vue-hoc "0.x.x"
|
||||||
|
|
||||||
|
vue-hoc@0.x.x:
|
||||||
|
version "0.4.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/vue-hoc/-/vue-hoc-0.4.7.tgz#4d3322ba89b8b0e42b19045ef536c21d948a4fac"
|
||||||
|
|
||||||
vue-hot-reload-api@^2.0.11:
|
vue-hot-reload-api@^2.0.11:
|
||||||
version "2.3.1"
|
version "2.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.1.tgz#b2d3d95402a811602380783ea4f566eb875569a2"
|
resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.1.tgz#b2d3d95402a811602380783ea4f566eb875569a2"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue