forked from AkkomaGang/akkoma-fe
Merge branch 'develop' into feature/mobile-improvements-3
This commit is contained in:
commit
31010779f6
37 changed files with 630 additions and 316 deletions
|
@ -97,6 +97,7 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => {
|
||||||
copyInstanceOption('showInstanceSpecificPanel')
|
copyInstanceOption('showInstanceSpecificPanel')
|
||||||
copyInstanceOption('scopeOptionsEnabled')
|
copyInstanceOption('scopeOptionsEnabled')
|
||||||
copyInstanceOption('formattingOptionsEnabled')
|
copyInstanceOption('formattingOptionsEnabled')
|
||||||
|
copyInstanceOption('hideMutedPosts')
|
||||||
copyInstanceOption('collapseMessageWithSubject')
|
copyInstanceOption('collapseMessageWithSubject')
|
||||||
copyInstanceOption('loginMethod')
|
copyInstanceOption('loginMethod')
|
||||||
copyInstanceOption('scopeCopy')
|
copyInstanceOption('scopeCopy')
|
||||||
|
@ -243,7 +244,7 @@ const afterStoreSetup = async ({ store, i18n }) => {
|
||||||
|
|
||||||
// Now we have the server settings and can try logging in
|
// Now we have the server settings and can try logging in
|
||||||
if (store.state.oauth.token) {
|
if (store.state.oauth.token) {
|
||||||
store.dispatch('loginUser', store.state.oauth.token)
|
await store.dispatch('loginUser', store.state.oauth.token)
|
||||||
}
|
}
|
||||||
|
|
||||||
const router = new VueRouter({
|
const router = new VueRouter({
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import Conversation from '../conversation/conversation.vue'
|
import Conversation from '../conversation/conversation.vue'
|
||||||
import { find } from 'lodash'
|
|
||||||
|
|
||||||
const conversationPage = {
|
const conversationPage = {
|
||||||
components: {
|
components: {
|
||||||
|
@ -8,8 +7,8 @@ const conversationPage = {
|
||||||
computed: {
|
computed: {
|
||||||
statusoid () {
|
statusoid () {
|
||||||
const id = this.$route.params.id
|
const id = this.$route.params.id
|
||||||
const statuses = this.$store.state.statuses.allStatuses
|
const statuses = this.$store.state.statuses.allStatusesObject
|
||||||
const status = find(statuses, {id})
|
const status = statuses[id]
|
||||||
|
|
||||||
return status
|
return status
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
<template>
|
<template>
|
||||||
<conversation :collapsable="false" :statusoid="statusoid"></conversation>
|
<conversation
|
||||||
|
:collapsable="false"
|
||||||
|
isPage="true"
|
||||||
|
:statusoid="statusoid"
|
||||||
|
></conversation>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script src="./conversation-page.js"></script>
|
<script src="./conversation-page.js"></script>
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
import { reduce, filter } from 'lodash'
|
import { reduce, filter, findIndex } from 'lodash'
|
||||||
|
import { set } from 'vue'
|
||||||
import Status from '../status/status.vue'
|
import Status from '../status/status.vue'
|
||||||
|
|
||||||
const sortById = (a, b) => {
|
const sortById = (a, b) => {
|
||||||
const seqA = Number(a.id)
|
const idA = a.type === 'retweet' ? a.retweeted_status.id : a.id
|
||||||
const seqB = Number(b.id)
|
const idB = b.type === 'retweet' ? b.retweeted_status.id : b.id
|
||||||
|
const seqA = Number(idA)
|
||||||
|
const seqB = Number(idB)
|
||||||
const isSeqA = !Number.isNaN(seqA)
|
const isSeqA = !Number.isNaN(seqA)
|
||||||
const isSeqB = !Number.isNaN(seqB)
|
const isSeqB = !Number.isNaN(seqB)
|
||||||
if (isSeqA && isSeqB) {
|
if (isSeqA && isSeqB) {
|
||||||
|
@ -13,29 +16,53 @@ const sortById = (a, b) => {
|
||||||
} else if (!isSeqA && isSeqB) {
|
} else if (!isSeqA && isSeqB) {
|
||||||
return 1
|
return 1
|
||||||
} else {
|
} else {
|
||||||
return a.id < b.id ? -1 : 1
|
return idA < idB ? -1 : 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const sortAndFilterConversation = (conversation) => {
|
const sortAndFilterConversation = (conversation, statusoid) => {
|
||||||
|
if (statusoid.type === 'retweet') {
|
||||||
|
conversation = filter(
|
||||||
|
conversation,
|
||||||
|
(status) => (status.type === 'retweet' || status.id !== statusoid.retweeted_status.id)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
conversation = filter(conversation, (status) => status.type !== 'retweet')
|
conversation = filter(conversation, (status) => status.type !== 'retweet')
|
||||||
|
}
|
||||||
return conversation.filter(_ => _).sort(sortById)
|
return conversation.filter(_ => _).sort(sortById)
|
||||||
}
|
}
|
||||||
|
|
||||||
const conversation = {
|
const conversation = {
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
highlight: null
|
highlight: null,
|
||||||
|
expanded: false,
|
||||||
|
converationStatusIds: []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
props: [
|
props: [
|
||||||
'statusoid',
|
'statusoid',
|
||||||
'collapsable'
|
'collapsable',
|
||||||
|
'isPage'
|
||||||
],
|
],
|
||||||
|
created () {
|
||||||
|
if (this.isPage) {
|
||||||
|
this.fetchConversation()
|
||||||
|
}
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
status () {
|
status () {
|
||||||
return this.statusoid
|
return this.statusoid
|
||||||
},
|
},
|
||||||
|
idsToShow () {
|
||||||
|
if (this.converationStatusIds.length > 0) {
|
||||||
|
return this.converationStatusIds
|
||||||
|
} else if (this.statusId) {
|
||||||
|
return [this.statusId]
|
||||||
|
} else {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
},
|
||||||
statusId () {
|
statusId () {
|
||||||
if (this.statusoid.retweeted_status) {
|
if (this.statusoid.retweeted_status) {
|
||||||
return this.statusoid.retweeted_status.id
|
return this.statusoid.retweeted_status.id
|
||||||
|
@ -48,10 +75,22 @@ const conversation = {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
const conversationId = this.status.statusnet_conversation_id
|
if (!this.isExpanded) {
|
||||||
const statuses = this.$store.state.statuses.allStatuses
|
return [this.status]
|
||||||
const conversation = filter(statuses, { statusnet_conversation_id: conversationId })
|
}
|
||||||
return sortAndFilterConversation(conversation)
|
|
||||||
|
const statusesObject = this.$store.state.statuses.allStatusesObject
|
||||||
|
const conversation = this.idsToShow.reduce((acc, id) => {
|
||||||
|
acc.push(statusesObject[id])
|
||||||
|
return acc
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const statusIndex = findIndex(conversation, { id: this.statusId })
|
||||||
|
if (statusIndex !== -1) {
|
||||||
|
conversation[statusIndex] = this.status
|
||||||
|
}
|
||||||
|
|
||||||
|
return sortAndFilterConversation(conversation, this.status)
|
||||||
},
|
},
|
||||||
replies () {
|
replies () {
|
||||||
let i = 1
|
let i = 1
|
||||||
|
@ -69,23 +108,34 @@ const conversation = {
|
||||||
i++
|
i++
|
||||||
return result
|
return result
|
||||||
}, {})
|
}, {})
|
||||||
|
},
|
||||||
|
isExpanded () {
|
||||||
|
return this.expanded || this.isPage
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
Status
|
Status
|
||||||
},
|
},
|
||||||
created () {
|
|
||||||
this.fetchConversation()
|
|
||||||
},
|
|
||||||
watch: {
|
watch: {
|
||||||
'$route': 'fetchConversation'
|
'$route': 'fetchConversation',
|
||||||
|
expanded (value) {
|
||||||
|
if (value) {
|
||||||
|
this.fetchConversation()
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
fetchConversation () {
|
fetchConversation () {
|
||||||
if (this.status) {
|
if (this.status) {
|
||||||
const conversationId = this.status.statusnet_conversation_id
|
this.$store.state.api.backendInteractor.fetchConversation({id: this.status.id})
|
||||||
this.$store.state.api.backendInteractor.fetchConversation({id: conversationId})
|
.then(({ancestors, descendants}) => {
|
||||||
.then((statuses) => this.$store.dispatch('addNewStatuses', { statuses }))
|
this.$store.dispatch('addNewStatuses', { statuses: ancestors })
|
||||||
|
this.$store.dispatch('addNewStatuses', { statuses: descendants })
|
||||||
|
set(this, 'converationStatusIds', [].concat(
|
||||||
|
ancestors.map(_ => _.id).filter(_ => _ !== this.statusId),
|
||||||
|
this.statusId,
|
||||||
|
descendants.map(_ => _.id).filter(_ => _ !== this.statusId)))
|
||||||
|
})
|
||||||
.then(() => this.setHighlight(this.statusId))
|
.then(() => this.setHighlight(this.statusId))
|
||||||
} else {
|
} else {
|
||||||
const id = this.$route.params.id
|
const id = this.$route.params.id
|
||||||
|
@ -98,10 +148,19 @@ const conversation = {
|
||||||
return this.replies[id] || []
|
return this.replies[id] || []
|
||||||
},
|
},
|
||||||
focused (id) {
|
focused (id) {
|
||||||
return id === this.statusId
|
return (this.isExpanded) && id === this.status.id
|
||||||
},
|
},
|
||||||
setHighlight (id) {
|
setHighlight (id) {
|
||||||
this.highlight = id
|
this.highlight = id
|
||||||
|
},
|
||||||
|
getHighlight () {
|
||||||
|
return this.isExpanded ? this.highlight : null
|
||||||
|
},
|
||||||
|
toggleExpanded () {
|
||||||
|
this.expanded = !this.expanded
|
||||||
|
if (!this.expanded) {
|
||||||
|
this.setHighlight(null)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,26 +1,42 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="timeline panel panel-default">
|
<div class="timeline panel-default" :class="[isExpanded ? 'panel' : 'panel-disabled']">
|
||||||
<div class="panel-heading conversation-heading">
|
<div v-if="isExpanded" class="panel-heading conversation-heading">
|
||||||
<span class="title"> {{ $t('timeline.conversation') }} </span>
|
<span class="title"> {{ $t('timeline.conversation') }} </span>
|
||||||
<span v-if="collapsable">
|
<span v-if="collapsable">
|
||||||
<a href="#" @click.prevent="$emit('toggleExpanded')">{{ $t('timeline.collapse') }}</a>
|
<a href="#" @click.prevent="toggleExpanded">{{ $t('timeline.collapse') }}</a>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
|
||||||
<div class="timeline">
|
|
||||||
<status
|
<status
|
||||||
v-for="status in conversation"
|
v-for="status in conversation"
|
||||||
@goto="setHighlight" :key="status.id"
|
@goto="setHighlight"
|
||||||
:inlineExpanded="collapsable" :statusoid="status"
|
@toggleExpanded="toggleExpanded"
|
||||||
:expandable='false' :focused="focused(status.id)"
|
:key="status.id"
|
||||||
:inConversation='true'
|
:inlineExpanded="collapsable"
|
||||||
:highlight="highlight"
|
:statusoid="status"
|
||||||
|
:expandable='!expanded'
|
||||||
|
:focused="focused(status.id)"
|
||||||
|
:inConversation="isExpanded"
|
||||||
|
:highlight="getHighlight()"
|
||||||
:replies="getReplies(status.id)"
|
:replies="getReplies(status.id)"
|
||||||
class="status-fadein">
|
class="status-fadein panel-body"
|
||||||
</status>
|
/>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script src="./conversation.js"></script>
|
<script src="./conversation.js"></script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@import '../../_variables.scss';
|
||||||
|
|
||||||
|
.timeline {
|
||||||
|
.panel-disabled {
|
||||||
|
.status-el {
|
||||||
|
border-left: none;
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
border-bottom-style: solid;
|
||||||
|
border-color: var(--border, $fallback--border);
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -20,7 +20,7 @@ const mediaUpload = {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
formData.append('media', file)
|
formData.append('file', file)
|
||||||
|
|
||||||
self.$emit('uploading')
|
self.$emit('uploading')
|
||||||
self.uploading = true
|
self.uploading = true
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<basic-user-card :user="user">
|
<basic-user-card :user="user">
|
||||||
<template slot="secondary-area">
|
<div class="mute-card-content-container">
|
||||||
<button class="btn btn-default" @click="unmuteUser" :disabled="progress" v-if="muted">
|
<button class="btn btn-default" @click="unmuteUser" :disabled="progress" v-if="muted">
|
||||||
<template v-if="progress">
|
<template v-if="progress">
|
||||||
{{ $t('user_card.unmute_progress') }}
|
{{ $t('user_card.unmute_progress') }}
|
||||||
|
@ -17,8 +17,18 @@
|
||||||
{{ $t('user_card.mute') }}
|
{{ $t('user_card.mute') }}
|
||||||
</template>
|
</template>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</div>
|
||||||
</basic-user-card>
|
</basic-user-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script src="./mute_card.js"></script>
|
<script src="./mute_card.js"></script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.mute-card-content-container {
|
||||||
|
margin-top: 0.5em;
|
||||||
|
text-align: right;
|
||||||
|
button {
|
||||||
|
width: 10em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -296,6 +296,8 @@ const PostStatusForm = {
|
||||||
},
|
},
|
||||||
paste (e) {
|
paste (e) {
|
||||||
if (e.clipboardData.files.length > 0) {
|
if (e.clipboardData.files.length > 0) {
|
||||||
|
// prevent pasting of file as text
|
||||||
|
e.preventDefault()
|
||||||
// Strangely, files property gets emptied after event propagation
|
// Strangely, files property gets emptied after event propagation
|
||||||
// Trying to wrap it in array doesn't work. Plus I doubt it's possible
|
// Trying to wrap it in array doesn't work. Plus I doubt it's possible
|
||||||
// to hold more than one file in clipboard.
|
// to hold more than one file in clipboard.
|
||||||
|
|
|
@ -84,10 +84,10 @@
|
||||||
<div class="media-upload-wrapper" v-for="file in newStatus.files">
|
<div class="media-upload-wrapper" v-for="file in newStatus.files">
|
||||||
<i class="fa button-icon icon-cancel" @click="removeMediaFile(file)"></i>
|
<i class="fa button-icon icon-cancel" @click="removeMediaFile(file)"></i>
|
||||||
<div class="media-upload-container attachment">
|
<div class="media-upload-container attachment">
|
||||||
<img class="thumbnail media-upload" :src="file.image" v-if="type(file) === 'image'"></img>
|
<img class="thumbnail media-upload" :src="file.url" v-if="type(file) === 'image'"></img>
|
||||||
<video v-if="type(file) === 'video'" :src="file.image" controls></video>
|
<video v-if="type(file) === 'video'" :src="file.url" controls></video>
|
||||||
<audio v-if="type(file) === 'audio'" :src="file.image" controls></audio>
|
<audio v-if="type(file) === 'audio'" :src="file.url" controls></audio>
|
||||||
<a v-if="type(file) === 'unknown'" :href="file.image">{{file.url}}</a>
|
<a v-if="type(file) === 'unknown'" :href="file.url">{{file.url}}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -287,8 +287,6 @@
|
||||||
img {
|
img {
|
||||||
width: 24px;
|
width: 24px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
border-radius: $fallback--avatarRadius;
|
|
||||||
border-radius: var(--avatarRadius, $fallback--avatarRadius);
|
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,9 @@ const registration = {
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
token () { return this.$route.params.token },
|
token () { return this.$route.params.token },
|
||||||
|
bioPlaceholder () {
|
||||||
|
return this.$t('registration.bio_placeholder').replace(/\s*\n\s*/g, ' \n')
|
||||||
|
},
|
||||||
...mapState({
|
...mapState({
|
||||||
registrationOpen: (state) => state.instance.registrationOpen,
|
registrationOpen: (state) => state.instance.registrationOpen,
|
||||||
signedIn: (state) => !!state.users.currentUser,
|
signedIn: (state) => !!state.users.currentUser,
|
||||||
|
|
|
@ -45,7 +45,7 @@
|
||||||
|
|
||||||
<div class='form-group'>
|
<div class='form-group'>
|
||||||
<label class='form--label' for='bio'>{{$t('registration.bio')}} ({{$t('general.optional')}})</label>
|
<label class='form--label' for='bio'>{{$t('registration.bio')}} ({{$t('general.optional')}})</label>
|
||||||
<textarea :disabled="isPending" v-model='user.bio' class='form-control' id='bio' :placeholder="$t('registration.bio_placeholder')"></textarea>
|
<textarea :disabled="isPending" v-model='user.bio' class='form-control' id='bio' :placeholder="bioPlaceholder"></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 }">
|
||||||
|
|
|
@ -47,6 +47,11 @@ const settings = {
|
||||||
pauseOnUnfocusedLocal: user.pauseOnUnfocused,
|
pauseOnUnfocusedLocal: user.pauseOnUnfocused,
|
||||||
hoverPreviewLocal: user.hoverPreview,
|
hoverPreviewLocal: user.hoverPreview,
|
||||||
|
|
||||||
|
hideMutedPostsLocal: typeof user.hideMutedPosts === 'undefined'
|
||||||
|
? instance.hideMutedPosts
|
||||||
|
: user.hideMutedPosts,
|
||||||
|
hideMutedPostsDefault: this.$t('settings.values.' + instance.hideMutedPosts),
|
||||||
|
|
||||||
collapseMessageWithSubjectLocal: typeof user.collapseMessageWithSubject === 'undefined'
|
collapseMessageWithSubjectLocal: typeof user.collapseMessageWithSubject === 'undefined'
|
||||||
? instance.collapseMessageWithSubject
|
? instance.collapseMessageWithSubject
|
||||||
: user.collapseMessageWithSubject,
|
: user.collapseMessageWithSubject,
|
||||||
|
@ -177,6 +182,9 @@ const settings = {
|
||||||
value = filter(value.split('\n'), (word) => trim(word).length > 0)
|
value = filter(value.split('\n'), (word) => trim(word).length > 0)
|
||||||
this.$store.dispatch('setOption', { name: 'muteWords', value })
|
this.$store.dispatch('setOption', { name: 'muteWords', value })
|
||||||
},
|
},
|
||||||
|
hideMutedPostsLocal (value) {
|
||||||
|
this.$store.dispatch('setOption', { name: 'hideMutedPosts', value })
|
||||||
|
},
|
||||||
collapseMessageWithSubjectLocal (value) {
|
collapseMessageWithSubjectLocal (value) {
|
||||||
this.$store.dispatch('setOption', { name: 'collapseMessageWithSubject', value })
|
this.$store.dispatch('setOption', { name: 'collapseMessageWithSubject', value })
|
||||||
},
|
},
|
||||||
|
|
|
@ -36,6 +36,10 @@
|
||||||
<div class="setting-item">
|
<div class="setting-item">
|
||||||
<h2>{{$t('nav.timeline')}}</h2>
|
<h2>{{$t('nav.timeline')}}</h2>
|
||||||
<ul class="setting-list">
|
<ul class="setting-list">
|
||||||
|
<li>
|
||||||
|
<input type="checkbox" id="hideMutedPosts" v-model="hideMutedPostsLocal">
|
||||||
|
<label for="hideMutedPosts">{{$t('settings.hide_muted_posts')}} {{$t('settings.instance_default', { value: hideMutedPostsDefault })}}</label>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<input type="checkbox" id="collapseMessageWithSubject" v-model="collapseMessageWithSubjectLocal">
|
<input type="checkbox" id="collapseMessageWithSubject" v-model="collapseMessageWithSubjectLocal">
|
||||||
<label for="collapseMessageWithSubject">
|
<label for="collapseMessageWithSubject">
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
import UserCard from '../user_card/user_card.vue'
|
import UserCard from '../user_card/user_card.vue'
|
||||||
import { unseenNotificationsFromStore } from '../../services/notification_utils/notification_utils'
|
import { unseenNotificationsFromStore } from '../../services/notification_utils/notification_utils'
|
||||||
|
import GestureService from '../../services/gesture_service/gesture_service'
|
||||||
// TODO: separate touch gesture stuff into their own utils if more components want them
|
|
||||||
const deltaCoord = (oldCoord, newCoord) => [newCoord[0] - oldCoord[0], newCoord[1] - oldCoord[1]]
|
|
||||||
|
|
||||||
const touchEventCoord = e => ([e.touches[0].screenX, e.touches[0].screenY])
|
|
||||||
|
|
||||||
const SideDrawer = {
|
const SideDrawer = {
|
||||||
props: [ 'logout' ],
|
props: [ 'logout' ],
|
||||||
data: () => ({
|
data: () => ({
|
||||||
closed: true,
|
closed: true,
|
||||||
touchCoord: [0, 0]
|
closeGesture: undefined
|
||||||
}),
|
}),
|
||||||
|
created () {
|
||||||
|
this.closeGesture = GestureService.swipeGesture(GestureService.DIRECTION_LEFT, this.toggleDrawer)
|
||||||
|
},
|
||||||
components: { UserCard },
|
components: { UserCard },
|
||||||
computed: {
|
computed: {
|
||||||
currentUser () {
|
currentUser () {
|
||||||
|
@ -46,13 +45,10 @@ const SideDrawer = {
|
||||||
this.toggleDrawer()
|
this.toggleDrawer()
|
||||||
},
|
},
|
||||||
touchStart (e) {
|
touchStart (e) {
|
||||||
this.touchCoord = touchEventCoord(e)
|
GestureService.beginSwipe(e, this.closeGesture)
|
||||||
},
|
},
|
||||||
touchMove (e) {
|
touchMove (e) {
|
||||||
const delta = deltaCoord(this.touchCoord, touchEventCoord(e))
|
GestureService.updateSwipe(e, this.closeGesture)
|
||||||
if (delta[0] < -30 && Math.abs(delta[1]) < Math.abs(delta[0]) && !this.closed) {
|
|
||||||
this.toggleDrawer()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
<div class="side-drawer-container"
|
<div class="side-drawer-container"
|
||||||
:class="{ 'side-drawer-container-closed': closed, 'side-drawer-container-open': !closed }"
|
:class="{ 'side-drawer-container-closed': closed, 'side-drawer-container-open': !closed }"
|
||||||
>
|
>
|
||||||
|
<div class="side-drawer-darken" :class="{ 'side-drawer-darken-closed': closed}" />
|
||||||
<div class="side-drawer"
|
<div class="side-drawer"
|
||||||
:class="{'side-drawer-closed': closed}"
|
:class="{'side-drawer-closed': closed}"
|
||||||
@touchstart="touchStart"
|
@touchstart="touchStart"
|
||||||
|
@ -106,16 +107,32 @@
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
|
transition-duration: 0s;
|
||||||
|
transition-property: transform;
|
||||||
}
|
}
|
||||||
|
|
||||||
.side-drawer-container-open {
|
.side-drawer-container-open {
|
||||||
|
transform: translate(0%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.side-drawer-container-closed {
|
||||||
|
transition-delay: 0.35s;
|
||||||
|
transform: translate(-100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.side-drawer-darken {
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
position: fixed;
|
||||||
|
z-index: -1;
|
||||||
transition: 0.35s;
|
transition: 0.35s;
|
||||||
transition-property: background-color;
|
transition-property: background-color;
|
||||||
background-color: rgba(0, 0, 0, 0.5);
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
.side-drawer-container-closed {
|
.side-drawer-darken-closed {
|
||||||
left: -100%;
|
|
||||||
background-color: rgba(0, 0, 0, 0);
|
background-color: rgba(0, 0, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,8 +142,9 @@
|
||||||
|
|
||||||
.side-drawer {
|
.side-drawer {
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
transition: 0.35s;
|
|
||||||
transition-timing-function: cubic-bezier(0, 1, 0.5, 1);
|
transition-timing-function: cubic-bezier(0, 1, 0.5, 1);
|
||||||
|
transition: 0.35s;
|
||||||
|
transition-property: transform;
|
||||||
margin: 0 0 0 -100px;
|
margin: 0 0 0 -100px;
|
||||||
padding: 0 0 1em 100px;
|
padding: 0 0 1em 100px;
|
||||||
width: 80%;
|
width: 80%;
|
||||||
|
|
|
@ -310,7 +310,6 @@ const Status = {
|
||||||
this.replying = !this.replying
|
this.replying = !this.replying
|
||||||
},
|
},
|
||||||
gotoOriginal (id) {
|
gotoOriginal (id) {
|
||||||
// only handled by conversation, not status_or_conversation
|
|
||||||
if (this.inConversation) {
|
if (this.inConversation) {
|
||||||
this.$emit('goto', id)
|
this.$emit('goto', id)
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
</div>
|
</div>
|
||||||
</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 && !inConversation" :class="[repeaterClass, { highlighted: repeaterStyle }]" :style="[repeaterStyle]" class="media container retweet-info">
|
||||||
<UserAvatar class="media-left" v-if="retweet" :betterShadow="betterShadow" :src="statusoid.user.profile_image_url_original"/>
|
<UserAvatar class="media-left" v-if="retweet" :betterShadow="betterShadow" :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">
|
||||||
|
@ -24,7 +24,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div :class="[userClass, { highlighted: userStyle, 'is-retweet': retweet }]" :style="[ userStyle ]" class="media status">
|
<div :class="[userClass, { highlighted: userStyle, 'is-retweet': retweet && !inConversation }]" :style="[ userStyle ]" class="media status">
|
||||||
<div v-if="!noHeading" class="media-left">
|
<div v-if="!noHeading" class="media-left">
|
||||||
<router-link :to="userProfileLink" @click.stop.prevent.capture.native="toggleUserExpanded">
|
<router-link :to="userProfileLink" @click.stop.prevent.capture.native="toggleUserExpanded">
|
||||||
<UserAvatar :compact="compact" :betterShadow="betterShadow" :src="status.user.profile_image_url_original"/>
|
<UserAvatar :compact="compact" :betterShadow="betterShadow" :src="status.user.profile_image_url_original"/>
|
||||||
|
@ -135,9 +135,8 @@
|
||||||
|
|
||||||
<div v-if="!noHeading && !isPreview" class='status-actions media-body'>
|
<div v-if="!noHeading && !isPreview" class='status-actions media-body'>
|
||||||
<div v-if="loggedIn">
|
<div v-if="loggedIn">
|
||||||
<a href="#" v-on:click.prevent="toggleReplying" :title="$t('tool_tip.reply')">
|
<i class="button-icon icon-reply" v-on:click.prevent="toggleReplying" :title="$t('tool_tip.reply')" :class="{'icon-reply-active': replying}"></i>
|
||||||
<i class="button-icon icon-reply" :class="{'icon-reply-active': replying}"></i>
|
<span v-if="status.replies_count > 0">{{status.replies_count}}</span>
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
<retweet-button :visibility='status.visibility' :loggedIn='loggedIn' :status='status'></retweet-button>
|
<retweet-button :visibility='status.visibility' :loggedIn='loggedIn' :status='status'></retweet-button>
|
||||||
<favorite-button :loggedIn='loggedIn' :status='status'></favorite-button>
|
<favorite-button :loggedIn='loggedIn' :status='status'></favorite-button>
|
||||||
|
@ -551,6 +550,7 @@ $status-margin: 0.75em;
|
||||||
.icon-reply:hover {
|
.icon-reply:hover {
|
||||||
color: $fallback--cBlue;
|
color: $fallback--cBlue;
|
||||||
color: var(--cBlue, $fallback--cBlue);
|
color: var(--cBlue, $fallback--cBlue);
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-reply.icon-reply-active {
|
.icon-reply.icon-reply-active {
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
import Status from '../status/status.vue'
|
|
||||||
import Conversation from '../conversation/conversation.vue'
|
|
||||||
|
|
||||||
const statusOrConversation = {
|
|
||||||
props: ['statusoid'],
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
expanded: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
components: {
|
|
||||||
Status,
|
|
||||||
Conversation
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
toggleExpanded () {
|
|
||||||
this.expanded = !this.expanded
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default statusOrConversation
|
|
|
@ -1,14 +0,0 @@
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<conversation v-if="expanded" @toggleExpanded="toggleExpanded" :collapsable="true" :statusoid="statusoid"></conversation>
|
|
||||||
<status v-if="!expanded" @toggleExpanded="toggleExpanded" :expandable="true" :inConversation="false" :focused="false" :statusoid="statusoid"></status>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script src="./status_or_conversation.js"></script>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.spacer {
|
|
||||||
height: 1em
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,6 +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 Conversation from '../conversation/conversation.vue'
|
||||||
import { throttle } from 'lodash'
|
import { throttle } from 'lodash'
|
||||||
|
|
||||||
const Timeline = {
|
const Timeline = {
|
||||||
|
@ -43,7 +43,7 @@ const Timeline = {
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
Status,
|
Status,
|
||||||
StatusOrConversation
|
Conversation
|
||||||
},
|
},
|
||||||
created () {
|
created () {
|
||||||
const store = this.$store
|
const store = this.$store
|
||||||
|
|
|
@ -16,7 +16,13 @@
|
||||||
</div>
|
</div>
|
||||||
<div :class="classes.body">
|
<div :class="classes.body">
|
||||||
<div class="timeline">
|
<div class="timeline">
|
||||||
<status-or-conversation v-for="status in timeline.visibleStatuses" :key="status.id" v-bind:statusoid="status" class="status-fadein"></status-or-conversation>
|
<conversation
|
||||||
|
v-for="status in timeline.visibleStatuses"
|
||||||
|
class="status-fadein"
|
||||||
|
:key="status.id"
|
||||||
|
:statusoid="status"
|
||||||
|
:collapsable="true"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div :class="classes.footer">
|
<div :class="classes.footer">
|
||||||
|
|
|
@ -121,24 +121,16 @@ export default {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
blockUser () {
|
blockUser () {
|
||||||
const store = this.$store
|
this.$store.dispatch('blockUser', this.user.id)
|
||||||
store.state.api.backendInteractor.blockUser(this.user.id)
|
|
||||||
.then((blockedUser) => {
|
|
||||||
store.commit('addNewUsers', [blockedUser])
|
|
||||||
store.commit('removeStatus', { timeline: 'friends', userId: this.user.id })
|
|
||||||
store.commit('removeStatus', { timeline: 'public', userId: this.user.id })
|
|
||||||
store.commit('removeStatus', { timeline: 'publicAndExternal', userId: this.user.id })
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
unblockUser () {
|
unblockUser () {
|
||||||
const store = this.$store
|
this.$store.dispatch('unblockUser', this.user.id)
|
||||||
store.state.api.backendInteractor.unblockUser(this.user.id)
|
|
||||||
.then((unblockedUser) => store.commit('addNewUsers', [unblockedUser]))
|
|
||||||
},
|
},
|
||||||
toggleMute () {
|
muteUser () {
|
||||||
const store = this.$store
|
this.$store.dispatch('muteUser', this.user.id)
|
||||||
store.commit('setMuted', {user: this.user, muted: !this.user.muted})
|
},
|
||||||
store.state.api.backendInteractor.setUserMute(this.user)
|
unmuteUser () {
|
||||||
|
this.$store.dispatch('unmuteUser', this.user.id)
|
||||||
},
|
},
|
||||||
setProfileView (v) {
|
setProfileView (v) {
|
||||||
if (this.switcher) {
|
if (this.switcher) {
|
||||||
|
|
|
@ -74,12 +74,12 @@
|
||||||
</div>
|
</div>
|
||||||
<div class='mute' v-if='isOtherUser && loggedIn'>
|
<div class='mute' v-if='isOtherUser && loggedIn'>
|
||||||
<span v-if='user.muted'>
|
<span v-if='user.muted'>
|
||||||
<button @click="toggleMute" class="pressed">
|
<button @click="unmuteUser" class="pressed">
|
||||||
{{ $t('user_card.muted') }}
|
{{ $t('user_card.muted') }}
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
<span v-if='!user.muted'>
|
<span v-if='!user.muted'>
|
||||||
<button @click="toggleMute">
|
<button @click="muteUser">
|
||||||
{{ $t('user_card.mute') }}
|
{{ $t('user_card.mute') }}
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -192,6 +192,12 @@
|
||||||
<template slot="empty">{{$t('settings.no_blocks')}}</template>
|
<template slot="empty">{{$t('settings.no_blocks')}}</template>
|
||||||
</block-list>
|
</block-list>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div :label="$t('settings.mutes_tab')">
|
||||||
|
<mute-list :refresh="true">
|
||||||
|
<template slot="empty">{{$t('settings.no_mutes')}}</template>
|
||||||
|
</mute-list>
|
||||||
|
</div>
|
||||||
</tab-switcher>
|
</tab-switcher>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -98,7 +98,7 @@
|
||||||
"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",
|
"username_placeholder": "e.g. lain",
|
||||||
"fullname_placeholder": "e.g. Lain Iwakura",
|
"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.",
|
"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",
|
||||||
|
@ -153,6 +153,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",
|
||||||
|
"hide_muted_posts": "Hide posts of muted users",
|
||||||
"max_thumbnails": "Maximum amount of thumbnails per post",
|
"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",
|
||||||
|
|
|
@ -5,6 +5,7 @@ const browserLocale = (window.navigator.language || 'en').split('-')[0]
|
||||||
|
|
||||||
const defaultState = {
|
const defaultState = {
|
||||||
colors: {},
|
colors: {},
|
||||||
|
hideMutedPosts: undefined, // instance default
|
||||||
collapseMessageWithSubject: undefined, // instance default
|
collapseMessageWithSubject: undefined, // instance default
|
||||||
hideAttachments: false,
|
hideAttachments: false,
|
||||||
hideAttachmentsInConv: false,
|
hideAttachmentsInConv: false,
|
||||||
|
|
|
@ -18,6 +18,7 @@ const defaultState = {
|
||||||
scopeOptionsEnabled: true,
|
scopeOptionsEnabled: true,
|
||||||
formattingOptionsEnabled: false,
|
formattingOptionsEnabled: false,
|
||||||
alwaysShowSubjectInput: true,
|
alwaysShowSubjectInput: true,
|
||||||
|
hideMutedPosts: false,
|
||||||
collapseMessageWithSubject: false,
|
collapseMessageWithSubject: false,
|
||||||
hidePostStats: false,
|
hidePostStats: false,
|
||||||
hideUserStats: false,
|
hideUserStats: false,
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { remove, slice, each, find, maxBy, minBy, merge, first, last, isArray, omitBy } from 'lodash'
|
import { remove, slice, each, find, maxBy, minBy, merge, first, last, isArray, omitBy } from 'lodash'
|
||||||
|
import { set } from 'vue'
|
||||||
import apiService from '../services/api/api.service.js'
|
import apiService from '../services/api/api.service.js'
|
||||||
// import parse from '../services/status_parser/status_parser.js'
|
// import parse from '../services/status_parser/status_parser.js'
|
||||||
|
|
||||||
|
@ -82,7 +83,7 @@ const mergeOrAdd = (arr, obj, item) => {
|
||||||
// This is a new item, prepare it
|
// This is a new item, prepare it
|
||||||
prepareStatus(item)
|
prepareStatus(item)
|
||||||
arr.push(item)
|
arr.push(item)
|
||||||
obj[item.id] = item
|
set(obj, item.id, item)
|
||||||
return {item, new: true}
|
return {item, new: true}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -432,13 +433,6 @@ const statuses = {
|
||||||
// Optimistic favoriting...
|
// Optimistic favoriting...
|
||||||
commit('setFavorited', { status, value: true })
|
commit('setFavorited', { status, value: true })
|
||||||
apiService.favorite({ id: status.id, credentials: rootState.users.currentUser.credentials })
|
apiService.favorite({ id: status.id, credentials: rootState.users.currentUser.credentials })
|
||||||
.then(response => {
|
|
||||||
if (response.ok) {
|
|
||||||
return response.json()
|
|
||||||
} else {
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(status => {
|
.then(status => {
|
||||||
commit('setFavoritedConfirm', { status })
|
commit('setFavoritedConfirm', { status })
|
||||||
})
|
})
|
||||||
|
@ -447,13 +441,6 @@ const statuses = {
|
||||||
// Optimistic favoriting...
|
// Optimistic favoriting...
|
||||||
commit('setFavorited', { status, value: false })
|
commit('setFavorited', { status, value: false })
|
||||||
apiService.unfavorite({ id: status.id, credentials: rootState.users.currentUser.credentials })
|
apiService.unfavorite({ id: status.id, credentials: rootState.users.currentUser.credentials })
|
||||||
.then(response => {
|
|
||||||
if (response.ok) {
|
|
||||||
return response.json()
|
|
||||||
} else {
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(status => {
|
.then(status => {
|
||||||
commit('setFavoritedConfirm', { status })
|
commit('setFavoritedConfirm', { status })
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
|
import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
|
||||||
import { compact, map, each, merge, find } from 'lodash'
|
import { compact, map, each, merge, find, last } from 'lodash'
|
||||||
import { set } from 'vue'
|
import { set } from 'vue'
|
||||||
import { registerPushNotifications, unregisterPushNotifications } from '../services/push/push.js'
|
import { registerPushNotifications, unregisterPushNotifications } from '../services/push/push.js'
|
||||||
import oauthApi from '../services/new_api/oauth'
|
import oauthApi from '../services/new_api/oauth'
|
||||||
|
@ -16,9 +16,9 @@ export const mergeOrAdd = (arr, obj, item) => {
|
||||||
} else {
|
} else {
|
||||||
// This is a new item, prepare it
|
// This is a new item, prepare it
|
||||||
arr.push(item)
|
arr.push(item)
|
||||||
obj[item.id] = item
|
set(obj, item.id, item)
|
||||||
if (item.screen_name && !item.screen_name.includes('@')) {
|
if (item.screen_name && !item.screen_name.includes('@')) {
|
||||||
obj[item.screen_name.toLowerCase()] = item
|
set(obj, item.screen_name.toLowerCase(), item)
|
||||||
}
|
}
|
||||||
return { item, new: true }
|
return { item, new: true }
|
||||||
}
|
}
|
||||||
|
@ -52,23 +52,23 @@ export const mutations = {
|
||||||
state.loggingIn = false
|
state.loggingIn = false
|
||||||
},
|
},
|
||||||
// TODO Clean after ourselves?
|
// TODO Clean after ourselves?
|
||||||
addFriends (state, { id, friends, page }) {
|
addFriends (state, { id, friends }) {
|
||||||
const user = state.usersObject[id]
|
const user = state.usersObject[id]
|
||||||
each(friends, friend => {
|
each(friends, friend => {
|
||||||
if (!find(user.friends, { id: friend.id })) {
|
if (!find(user.friends, { id: friend.id })) {
|
||||||
user.friends.push(friend)
|
user.friends.push(friend)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
user.friendsPage = page + 1
|
user.lastFriendId = last(friends).id
|
||||||
},
|
},
|
||||||
addFollowers (state, { id, followers, page }) {
|
addFollowers (state, { id, followers }) {
|
||||||
const user = state.usersObject[id]
|
const user = state.usersObject[id]
|
||||||
each(followers, follower => {
|
each(followers, follower => {
|
||||||
if (!find(user.followers, { id: follower.id })) {
|
if (!find(user.followers, { id: follower.id })) {
|
||||||
user.followers.push(follower)
|
user.followers.push(follower)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
user.followersPage = page + 1
|
user.lastFollowerId = last(followers).id
|
||||||
},
|
},
|
||||||
// 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.
|
||||||
|
@ -78,7 +78,7 @@ export const mutations = {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
user.friends = []
|
user.friends = []
|
||||||
user.friendsPage = 0
|
user.lastFriendId = null
|
||||||
},
|
},
|
||||||
clearFollowers (state, userId) {
|
clearFollowers (state, userId) {
|
||||||
const user = state.usersObject[userId]
|
const user = state.usersObject[userId]
|
||||||
|
@ -86,7 +86,7 @@ export const mutations = {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
user.followers = []
|
user.followers = []
|
||||||
user.followersPage = 0
|
user.lastFollowerId = null
|
||||||
},
|
},
|
||||||
addNewUsers (state, users) {
|
addNewUsers (state, users) {
|
||||||
each(users, (user) => mergeOrAdd(state.users, state.usersObject, user))
|
each(users, (user) => mergeOrAdd(state.users, state.usersObject, user))
|
||||||
|
@ -102,10 +102,20 @@ export const mutations = {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
saveBlocks (state, blockIds) {
|
updateBlocks (state, blockedUsers) {
|
||||||
|
// Reset statusnet_blocking of all fetched users
|
||||||
|
each(state.users, (user) => { user.statusnet_blocking = false })
|
||||||
|
each(blockedUsers, (user) => mergeOrAdd(state.users, state.usersObject, user))
|
||||||
|
},
|
||||||
|
saveBlockIds (state, blockIds) {
|
||||||
state.currentUser.blockIds = blockIds
|
state.currentUser.blockIds = blockIds
|
||||||
},
|
},
|
||||||
saveMutes (state, muteIds) {
|
updateMutes (state, mutedUsers) {
|
||||||
|
// Reset muted of all fetched users
|
||||||
|
each(state.users, (user) => { user.muted = false })
|
||||||
|
each(mutedUsers, (user) => mergeOrAdd(state.users, state.usersObject, user))
|
||||||
|
},
|
||||||
|
saveMuteIds (state, muteIds) {
|
||||||
state.currentUser.muteIds = muteIds
|
state.currentUser.muteIds = muteIds
|
||||||
},
|
},
|
||||||
setUserForStatus (state, status) {
|
setUserForStatus (state, status) {
|
||||||
|
@ -172,42 +182,47 @@ const users = {
|
||||||
fetchBlocks (store) {
|
fetchBlocks (store) {
|
||||||
return store.rootState.api.backendInteractor.fetchBlocks()
|
return store.rootState.api.backendInteractor.fetchBlocks()
|
||||||
.then((blocks) => {
|
.then((blocks) => {
|
||||||
store.commit('saveBlocks', map(blocks, 'id'))
|
store.commit('saveBlockIds', map(blocks, 'id'))
|
||||||
store.commit('addNewUsers', blocks)
|
store.commit('updateBlocks', blocks)
|
||||||
return blocks
|
return blocks
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
blockUser (store, id) {
|
blockUser (store, userId) {
|
||||||
return store.rootState.api.backendInteractor.blockUser(id)
|
return store.rootState.api.backendInteractor.blockUser(userId)
|
||||||
.then((user) => store.commit('addNewUsers', [user]))
|
.then((relationship) => {
|
||||||
|
store.commit('updateUserRelationship', [relationship])
|
||||||
|
store.commit('removeStatus', { timeline: 'friends', userId })
|
||||||
|
store.commit('removeStatus', { timeline: 'public', userId })
|
||||||
|
store.commit('removeStatus', { timeline: 'publicAndExternal', userId })
|
||||||
|
})
|
||||||
},
|
},
|
||||||
unblockUser (store, id) {
|
unblockUser (store, id) {
|
||||||
return store.rootState.api.backendInteractor.unblockUser(id)
|
return store.rootState.api.backendInteractor.unblockUser(id)
|
||||||
.then((user) => store.commit('addNewUsers', [user]))
|
.then((relationship) => store.commit('updateUserRelationship', [relationship]))
|
||||||
},
|
},
|
||||||
fetchMutes (store) {
|
fetchMutes (store) {
|
||||||
return store.rootState.api.backendInteractor.fetchMutes()
|
return store.rootState.api.backendInteractor.fetchMutes()
|
||||||
.then((mutedUsers) => {
|
.then((mutes) => {
|
||||||
each(mutedUsers, (user) => { user.muted = true })
|
store.commit('updateMutes', mutes)
|
||||||
store.commit('addNewUsers', mutedUsers)
|
store.commit('saveMuteIds', map(mutes, 'id'))
|
||||||
store.commit('saveMutes', map(mutedUsers, 'id'))
|
return mutes
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
muteUser (store, id) {
|
muteUser (store, id) {
|
||||||
return store.state.api.backendInteractor.setUserMute({ id, muted: true })
|
return store.rootState.api.backendInteractor.muteUser(id)
|
||||||
.then((user) => store.commit('addNewUsers', [user]))
|
.then((relationship) => store.commit('updateUserRelationship', [relationship]))
|
||||||
},
|
},
|
||||||
unmuteUser (store, id) {
|
unmuteUser (store, id) {
|
||||||
return store.state.api.backendInteractor.setUserMute({ id, muted: false })
|
return store.rootState.api.backendInteractor.unmuteUser(id)
|
||||||
.then((user) => store.commit('addNewUsers', [user]))
|
.then((relationship) => store.commit('updateUserRelationship', [relationship]))
|
||||||
},
|
},
|
||||||
addFriends ({ rootState, commit }, fetchBy) {
|
addFriends ({ rootState, commit }, fetchBy) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const user = rootState.users.usersObject[fetchBy]
|
const user = rootState.users.usersObject[fetchBy]
|
||||||
const page = user.friendsPage || 1
|
const maxId = user.lastFriendId
|
||||||
rootState.api.backendInteractor.fetchFriends({ id: user.id, page })
|
rootState.api.backendInteractor.fetchFriends({ id: user.id, maxId })
|
||||||
.then((friends) => {
|
.then((friends) => {
|
||||||
commit('addFriends', { id: user.id, friends, page })
|
commit('addFriends', { id: user.id, friends })
|
||||||
resolve(friends)
|
resolve(friends)
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
reject()
|
reject()
|
||||||
|
@ -216,10 +231,10 @@ const users = {
|
||||||
},
|
},
|
||||||
addFollowers ({ rootState, commit }, fetchBy) {
|
addFollowers ({ rootState, commit }, fetchBy) {
|
||||||
const user = rootState.users.usersObject[fetchBy]
|
const user = rootState.users.usersObject[fetchBy]
|
||||||
const page = user.followersPage || 1
|
const maxId = user.lastFollowerId
|
||||||
return rootState.api.backendInteractor.fetchFollowers({ id: user.id, page })
|
return rootState.api.backendInteractor.fetchFollowers({ id: user.id, maxId })
|
||||||
.then((followers) => {
|
.then((followers) => {
|
||||||
commit('addFollowers', { id: user.id, followers, page })
|
commit('addFollowers', { id: user.id, followers })
|
||||||
return followers
|
return followers
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,27 +1,7 @@
|
||||||
/* eslint-env browser */
|
/* eslint-env browser */
|
||||||
const LOGIN_URL = '/api/account/verify_credentials.json'
|
const LOGIN_URL = '/api/account/verify_credentials.json'
|
||||||
const FRIENDS_TIMELINE_URL = '/api/statuses/friends_timeline.json'
|
|
||||||
const ALL_FOLLOWING_URL = '/api/qvitter/allfollowing'
|
const ALL_FOLLOWING_URL = '/api/qvitter/allfollowing'
|
||||||
const PUBLIC_TIMELINE_URL = '/api/statuses/public_timeline.json'
|
|
||||||
const PUBLIC_AND_EXTERNAL_TIMELINE_URL = '/api/statuses/public_and_external_timeline.json'
|
|
||||||
const TAG_TIMELINE_URL = '/api/statusnet/tags/timeline'
|
|
||||||
const FAVORITE_URL = '/api/favorites/create'
|
|
||||||
const UNFAVORITE_URL = '/api/favorites/destroy'
|
|
||||||
const RETWEET_URL = '/api/statuses/retweet'
|
|
||||||
const UNRETWEET_URL = '/api/statuses/unretweet'
|
|
||||||
const STATUS_UPDATE_URL = '/api/statuses/update.json'
|
|
||||||
const STATUS_DELETE_URL = '/api/statuses/destroy'
|
|
||||||
const STATUS_URL = '/api/statuses/show'
|
|
||||||
const MEDIA_UPLOAD_URL = '/api/statusnet/media/upload'
|
|
||||||
const CONVERSATION_URL = '/api/statusnet/conversation'
|
|
||||||
const MENTIONS_URL = '/api/statuses/mentions.json'
|
const MENTIONS_URL = '/api/statuses/mentions.json'
|
||||||
const DM_TIMELINE_URL = '/api/statuses/dm_timeline.json'
|
|
||||||
const FOLLOWERS_URL = '/api/statuses/followers.json'
|
|
||||||
const FRIENDS_URL = '/api/statuses/friends.json'
|
|
||||||
const BLOCKS_URL = '/api/statuses/blocks.json'
|
|
||||||
const FOLLOWING_URL = '/api/friendships/create.json'
|
|
||||||
const UNFOLLOWING_URL = '/api/friendships/destroy.json'
|
|
||||||
const QVITTER_USER_PREF_URL = '/api/qvitter/set_profile_pref.json'
|
|
||||||
const REGISTRATION_URL = '/api/account/register.json'
|
const REGISTRATION_URL = '/api/account/register.json'
|
||||||
const AVATAR_UPDATE_URL = '/api/qvitter/update_avatar.json'
|
const AVATAR_UPDATE_URL = '/api/qvitter/update_avatar.json'
|
||||||
const BG_UPDATE_URL = '/api/qvitter/update_background_image.json'
|
const BG_UPDATE_URL = '/api/qvitter/update_background_image.json'
|
||||||
|
@ -30,8 +10,6 @@ const PROFILE_UPDATE_URL = '/api/account/update_profile.json'
|
||||||
const EXTERNAL_PROFILE_URL = '/api/externalprofile/show.json'
|
const EXTERNAL_PROFILE_URL = '/api/externalprofile/show.json'
|
||||||
const QVITTER_USER_NOTIFICATIONS_URL = '/api/qvitter/statuses/notifications.json'
|
const QVITTER_USER_NOTIFICATIONS_URL = '/api/qvitter/statuses/notifications.json'
|
||||||
const QVITTER_USER_NOTIFICATIONS_READ_URL = '/api/qvitter/statuses/notifications/read.json'
|
const QVITTER_USER_NOTIFICATIONS_READ_URL = '/api/qvitter/statuses/notifications/read.json'
|
||||||
const BLOCKING_URL = '/api/blocks/create.json'
|
|
||||||
const UNBLOCKING_URL = '/api/blocks/destroy.json'
|
|
||||||
const FOLLOW_IMPORT_URL = '/api/pleroma/follow_import'
|
const FOLLOW_IMPORT_URL = '/api/pleroma/follow_import'
|
||||||
const DELETE_ACCOUNT_URL = '/api/pleroma/delete_account'
|
const DELETE_ACCOUNT_URL = '/api/pleroma/delete_account'
|
||||||
const CHANGE_PASSWORD_URL = '/api/pleroma/change_password'
|
const CHANGE_PASSWORD_URL = '/api/pleroma/change_password'
|
||||||
|
@ -41,12 +19,35 @@ const DENY_USER_URL = '/api/pleroma/friendships/deny'
|
||||||
const SUGGESTIONS_URL = '/api/v1/suggestions'
|
const SUGGESTIONS_URL = '/api/v1/suggestions'
|
||||||
|
|
||||||
const MASTODON_USER_FAVORITES_TIMELINE_URL = '/api/v1/favourites'
|
const MASTODON_USER_FAVORITES_TIMELINE_URL = '/api/v1/favourites'
|
||||||
|
const MASTODON_FAVORITE_URL = id => `/api/v1/statuses/${id}/favourite`
|
||||||
|
const MASTODON_UNFAVORITE_URL = id => `/api/v1/statuses/${id}/unfavourite`
|
||||||
|
const MASTODON_RETWEET_URL = id => `/api/v1/statuses/${id}/reblog`
|
||||||
|
const MASTODON_UNRETWEET_URL = id => `/api/v1/statuses/${id}/unreblog`
|
||||||
|
const MASTODON_DELETE_URL = id => `/api/v1/statuses/${id}`
|
||||||
|
const MASTODON_FOLLOW_URL = id => `/api/v1/accounts/${id}/follow`
|
||||||
|
const MASTODON_UNFOLLOW_URL = id => `/api/v1/accounts/${id}/unfollow`
|
||||||
|
const MASTODON_FOLLOWING_URL = id => `/api/v1/accounts/${id}/following`
|
||||||
|
const MASTODON_FOLLOWERS_URL = id => `/api/v1/accounts/${id}/followers`
|
||||||
|
const MASTODON_DIRECT_MESSAGES_TIMELINE_URL = '/api/v1/timelines/direct'
|
||||||
|
const MASTODON_PUBLIC_TIMELINE = '/api/v1/timelines/public'
|
||||||
|
const MASTODON_USER_HOME_TIMELINE_URL = '/api/v1/timelines/home'
|
||||||
|
const MASTODON_STATUS_URL = id => `/api/v1/statuses/${id}`
|
||||||
|
const MASTODON_STATUS_CONTEXT_URL = id => `/api/v1/statuses/${id}/context`
|
||||||
const MASTODON_USER_URL = '/api/v1/accounts'
|
const MASTODON_USER_URL = '/api/v1/accounts'
|
||||||
const MASTODON_USER_RELATIONSHIPS_URL = '/api/v1/accounts/relationships'
|
const MASTODON_USER_RELATIONSHIPS_URL = '/api/v1/accounts/relationships'
|
||||||
const MASTODON_USER_TIMELINE_URL = id => `/api/v1/accounts/${id}/statuses`
|
const MASTODON_USER_TIMELINE_URL = id => `/api/v1/accounts/${id}/statuses`
|
||||||
|
const MASTODON_TAG_TIMELINE_URL = tag => `/api/v1/timelines/tag/${tag}`
|
||||||
|
const MASTODON_USER_BLOCKS_URL = '/api/v1/blocks/'
|
||||||
|
const MASTODON_USER_MUTES_URL = '/api/v1/mutes/'
|
||||||
|
const MASTODON_BLOCK_USER_URL = id => `/api/v1/accounts/${id}/block`
|
||||||
|
const MASTODON_UNBLOCK_USER_URL = id => `/api/v1/accounts/${id}/unblock`
|
||||||
|
const MASTODON_MUTE_USER_URL = id => `/api/v1/accounts/${id}/mute`
|
||||||
|
const MASTODON_UNMUTE_USER_URL = id => `/api/v1/accounts/${id}/unmute`
|
||||||
|
const MASTODON_POST_STATUS_URL = '/api/v1/statuses'
|
||||||
|
const MASTODON_MEDIA_UPLOAD_URL = '/api/v1/media'
|
||||||
|
|
||||||
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, parseAttachment } from '../entity_normalizer/entity_normalizer.service.js'
|
||||||
import 'whatwg-fetch'
|
import 'whatwg-fetch'
|
||||||
import { StatusCodeError } from '../errors/errors'
|
import { StatusCodeError } from '../errors/errors'
|
||||||
|
|
||||||
|
@ -60,6 +61,19 @@ let fetch = (url, options) => {
|
||||||
return oldfetch(fullUrl, options)
|
return oldfetch(fullUrl, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const promisedRequest = (url, options) => {
|
||||||
|
return fetch(url, options)
|
||||||
|
.then((response) => {
|
||||||
|
return new Promise((resolve, reject) => response.json()
|
||||||
|
.then((json) => {
|
||||||
|
if (!response.ok) {
|
||||||
|
return reject(new StatusCodeError(response.status, json, { url, options }, response))
|
||||||
|
}
|
||||||
|
return resolve(json)
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Params
|
// Params
|
||||||
// cropH
|
// cropH
|
||||||
// cropW
|
// cropW
|
||||||
|
@ -196,7 +210,7 @@ const externalProfile = ({profileUrl, credentials}) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const followUser = ({id, credentials}) => {
|
const followUser = ({id, credentials}) => {
|
||||||
let url = `${FOLLOWING_URL}?user_id=${id}`
|
let url = MASTODON_FOLLOW_URL(id)
|
||||||
return fetch(url, {
|
return fetch(url, {
|
||||||
headers: authHeaders(credentials),
|
headers: authHeaders(credentials),
|
||||||
method: 'POST'
|
method: 'POST'
|
||||||
|
@ -204,7 +218,7 @@ const followUser = ({id, credentials}) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const unfollowUser = ({id, credentials}) => {
|
const unfollowUser = ({id, credentials}) => {
|
||||||
let url = `${UNFOLLOWING_URL}?user_id=${id}`
|
let url = MASTODON_UNFOLLOW_URL(id)
|
||||||
return fetch(url, {
|
return fetch(url, {
|
||||||
headers: authHeaders(credentials),
|
headers: authHeaders(credentials),
|
||||||
method: 'POST'
|
method: 'POST'
|
||||||
|
@ -212,16 +226,14 @@ const unfollowUser = ({id, credentials}) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const blockUser = ({id, credentials}) => {
|
const blockUser = ({id, credentials}) => {
|
||||||
let url = `${BLOCKING_URL}?user_id=${id}`
|
return fetch(MASTODON_BLOCK_USER_URL(id), {
|
||||||
return fetch(url, {
|
|
||||||
headers: authHeaders(credentials),
|
headers: authHeaders(credentials),
|
||||||
method: 'POST'
|
method: 'POST'
|
||||||
}).then((data) => data.json())
|
}).then((data) => data.json())
|
||||||
}
|
}
|
||||||
|
|
||||||
const unblockUser = ({id, credentials}) => {
|
const unblockUser = ({id, credentials}) => {
|
||||||
let url = `${UNBLOCKING_URL}?user_id=${id}`
|
return fetch(MASTODON_UNBLOCK_USER_URL(id), {
|
||||||
return fetch(url, {
|
|
||||||
headers: authHeaders(credentials),
|
headers: authHeaders(credentials),
|
||||||
method: 'POST'
|
method: 'POST'
|
||||||
}).then((data) => data.json())
|
}).then((data) => data.json())
|
||||||
|
@ -245,16 +257,7 @@ const denyUser = ({id, credentials}) => {
|
||||||
|
|
||||||
const fetchUser = ({id, credentials}) => {
|
const fetchUser = ({id, credentials}) => {
|
||||||
let url = `${MASTODON_USER_URL}/${id}`
|
let url = `${MASTODON_USER_URL}/${id}`
|
||||||
return fetch(url, { headers: authHeaders(credentials) })
|
return promisedRequest(url, { headers: authHeaders(credentials) })
|
||||||
.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))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -272,28 +275,36 @@ const fetchUserRelationship = ({id, credentials}) => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchFriends = ({id, page, credentials}) => {
|
const fetchFriends = ({id, maxId, sinceId, limit = 20, credentials}) => {
|
||||||
let url = `${FRIENDS_URL}?user_id=${id}`
|
let url = MASTODON_FOLLOWING_URL(id)
|
||||||
if (page) {
|
const args = [
|
||||||
url = url + `&page=${page}`
|
maxId && `max_id=${maxId}`,
|
||||||
}
|
sinceId && `since_id=${sinceId}`,
|
||||||
|
limit && `limit=${limit}`
|
||||||
|
].filter(_ => _).join('&')
|
||||||
|
|
||||||
|
url = url + (args ? '?' + args : '')
|
||||||
return fetch(url, { headers: authHeaders(credentials) })
|
return fetch(url, { headers: authHeaders(credentials) })
|
||||||
.then((data) => data.json())
|
.then((data) => data.json())
|
||||||
.then((data) => data.map(parseUser))
|
.then((data) => data.map(parseUser))
|
||||||
}
|
}
|
||||||
|
|
||||||
const exportFriends = ({id, credentials}) => {
|
const exportFriends = ({id, credentials}) => {
|
||||||
let url = `${FRIENDS_URL}?user_id=${id}&all=true`
|
let url = MASTODON_FOLLOWING_URL(id) + `?all=true`
|
||||||
return fetch(url, { headers: authHeaders(credentials) })
|
return fetch(url, { headers: authHeaders(credentials) })
|
||||||
.then((data) => data.json())
|
.then((data) => data.json())
|
||||||
.then((data) => data.map(parseUser))
|
.then((data) => data.map(parseUser))
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchFollowers = ({id, page, credentials}) => {
|
const fetchFollowers = ({id, maxId, sinceId, limit = 20, credentials}) => {
|
||||||
let url = `${FOLLOWERS_URL}?user_id=${id}`
|
let url = MASTODON_FOLLOWERS_URL(id)
|
||||||
if (page) {
|
const args = [
|
||||||
url = url + `&page=${page}`
|
maxId && `max_id=${maxId}`,
|
||||||
}
|
sinceId && `since_id=${sinceId}`,
|
||||||
|
limit && `limit=${limit}`
|
||||||
|
].filter(_ => _).join('&')
|
||||||
|
|
||||||
|
url += args ? '?' + args : ''
|
||||||
return fetch(url, { headers: authHeaders(credentials) })
|
return fetch(url, { headers: authHeaders(credentials) })
|
||||||
.then((data) => data.json())
|
.then((data) => data.json())
|
||||||
.then((data) => data.map(parseUser))
|
.then((data) => data.map(parseUser))
|
||||||
|
@ -313,8 +324,8 @@ const fetchFollowRequests = ({credentials}) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchConversation = ({id, credentials}) => {
|
const fetchConversation = ({id, credentials}) => {
|
||||||
let url = `${CONVERSATION_URL}/${id}.json?count=100`
|
let urlContext = MASTODON_STATUS_CONTEXT_URL(id)
|
||||||
return fetch(url, { headers: authHeaders(credentials) })
|
return fetch(urlContext, { headers: authHeaders(credentials) })
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (data.ok) {
|
if (data.ok) {
|
||||||
return data
|
return data
|
||||||
|
@ -322,11 +333,14 @@ const fetchConversation = ({id, credentials}) => {
|
||||||
throw new Error('Error fetching timeline', data)
|
throw new Error('Error fetching timeline', data)
|
||||||
})
|
})
|
||||||
.then((data) => data.json())
|
.then((data) => data.json())
|
||||||
.then((data) => data.map(parseStatus))
|
.then(({ancestors, descendants}) => ({
|
||||||
|
ancestors: ancestors.map(parseStatus),
|
||||||
|
descendants: descendants.map(parseStatus)
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchStatus = ({id, credentials}) => {
|
const fetchStatus = ({id, credentials}) => {
|
||||||
let url = `${STATUS_URL}/${id}.json`
|
let url = MASTODON_STATUS_URL(id)
|
||||||
return fetch(url, { headers: authHeaders(credentials) })
|
return fetch(url, { headers: authHeaders(credentials) })
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (data.ok) {
|
if (data.ok) {
|
||||||
|
@ -338,34 +352,18 @@ const fetchStatus = ({id, credentials}) => {
|
||||||
.then((data) => parseStatus(data))
|
.then((data) => parseStatus(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
const setUserMute = ({id, credentials, muted = true}) => {
|
const fetchTimeline = ({timeline, credentials, since = false, until = false, userId = false, tag = false, withMuted = false}) => {
|
||||||
const form = new FormData()
|
|
||||||
|
|
||||||
const muteInteger = muted ? 1 : 0
|
|
||||||
|
|
||||||
form.append('namespace', 'qvitter')
|
|
||||||
form.append('data', muteInteger)
|
|
||||||
form.append('topic', `mute:${id}`)
|
|
||||||
|
|
||||||
return fetch(QVITTER_USER_PREF_URL, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: authHeaders(credentials),
|
|
||||||
body: form
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const fetchTimeline = ({timeline, credentials, since = false, until = false, userId = false, tag = false}) => {
|
|
||||||
const timelineUrls = {
|
const timelineUrls = {
|
||||||
public: PUBLIC_TIMELINE_URL,
|
public: MASTODON_PUBLIC_TIMELINE,
|
||||||
friends: FRIENDS_TIMELINE_URL,
|
friends: MASTODON_USER_HOME_TIMELINE_URL,
|
||||||
mentions: MENTIONS_URL,
|
mentions: MENTIONS_URL,
|
||||||
dms: DM_TIMELINE_URL,
|
dms: MASTODON_DIRECT_MESSAGES_TIMELINE_URL,
|
||||||
notifications: QVITTER_USER_NOTIFICATIONS_URL,
|
notifications: QVITTER_USER_NOTIFICATIONS_URL,
|
||||||
'publicAndExternal': PUBLIC_AND_EXTERNAL_TIMELINE_URL,
|
'publicAndExternal': MASTODON_PUBLIC_TIMELINE,
|
||||||
user: MASTODON_USER_TIMELINE_URL,
|
user: MASTODON_USER_TIMELINE_URL,
|
||||||
media: MASTODON_USER_TIMELINE_URL,
|
media: MASTODON_USER_TIMELINE_URL,
|
||||||
favorites: MASTODON_USER_FAVORITES_TIMELINE_URL,
|
favorites: MASTODON_USER_FAVORITES_TIMELINE_URL,
|
||||||
tag: TAG_TIMELINE_URL
|
tag: MASTODON_TAG_TIMELINE_URL
|
||||||
}
|
}
|
||||||
const isNotifications = timeline === 'notifications'
|
const isNotifications = timeline === 'notifications'
|
||||||
const params = []
|
const params = []
|
||||||
|
@ -383,13 +381,20 @@ const fetchTimeline = ({timeline, credentials, since = false, until = false, use
|
||||||
params.push(['max_id', until])
|
params.push(['max_id', until])
|
||||||
}
|
}
|
||||||
if (tag) {
|
if (tag) {
|
||||||
url += `/${tag}.json`
|
url = url(tag)
|
||||||
}
|
}
|
||||||
if (timeline === 'media') {
|
if (timeline === 'media') {
|
||||||
params.push(['only_media', 1])
|
params.push(['only_media', 1])
|
||||||
}
|
}
|
||||||
|
if (timeline === 'public') {
|
||||||
|
params.push(['local', true])
|
||||||
|
}
|
||||||
|
if (timeline === 'public' || timeline === 'publicAndExternal') {
|
||||||
|
params.push(['only_media', false])
|
||||||
|
}
|
||||||
|
|
||||||
params.push(['count', 20])
|
params.push(['count', 20])
|
||||||
|
params.push(['with_muted', withMuted])
|
||||||
|
|
||||||
const queryString = map(params, (param) => `${param[0]}=${param[1]}`).join('&')
|
const queryString = map(params, (param) => `${param[0]}=${param[1]}`).join('&')
|
||||||
url += `?${queryString}`
|
url += `?${queryString}`
|
||||||
|
@ -423,50 +428,82 @@ const verifyCredentials = (user) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const favorite = ({ id, credentials }) => {
|
const favorite = ({ id, credentials }) => {
|
||||||
return fetch(`${FAVORITE_URL}/${id}.json`, {
|
return fetch(MASTODON_FAVORITE_URL(id), {
|
||||||
headers: authHeaders(credentials),
|
headers: authHeaders(credentials),
|
||||||
method: 'POST'
|
method: 'POST'
|
||||||
})
|
})
|
||||||
|
.then(response => {
|
||||||
|
if (response.ok) {
|
||||||
|
return response.json()
|
||||||
|
} else {
|
||||||
|
throw new Error('Error favoriting post')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then((data) => parseStatus(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
const unfavorite = ({ id, credentials }) => {
|
const unfavorite = ({ id, credentials }) => {
|
||||||
return fetch(`${UNFAVORITE_URL}/${id}.json`, {
|
return fetch(MASTODON_UNFAVORITE_URL(id), {
|
||||||
headers: authHeaders(credentials),
|
headers: authHeaders(credentials),
|
||||||
method: 'POST'
|
method: 'POST'
|
||||||
})
|
})
|
||||||
|
.then(response => {
|
||||||
|
if (response.ok) {
|
||||||
|
return response.json()
|
||||||
|
} else {
|
||||||
|
throw new Error('Error removing favorite')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then((data) => parseStatus(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
const retweet = ({ id, credentials }) => {
|
const retweet = ({ id, credentials }) => {
|
||||||
return fetch(`${RETWEET_URL}/${id}.json`, {
|
return fetch(MASTODON_RETWEET_URL(id), {
|
||||||
headers: authHeaders(credentials),
|
headers: authHeaders(credentials),
|
||||||
method: 'POST'
|
method: 'POST'
|
||||||
})
|
})
|
||||||
|
.then(response => {
|
||||||
|
if (response.ok) {
|
||||||
|
return response.json()
|
||||||
|
} else {
|
||||||
|
throw new Error('Error repeating post')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then((data) => parseStatus(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
const unretweet = ({ id, credentials }) => {
|
const unretweet = ({ id, credentials }) => {
|
||||||
return fetch(`${UNRETWEET_URL}/${id}.json`, {
|
return fetch(MASTODON_UNRETWEET_URL(id), {
|
||||||
headers: authHeaders(credentials),
|
headers: authHeaders(credentials),
|
||||||
method: 'POST'
|
method: 'POST'
|
||||||
})
|
})
|
||||||
|
.then(response => {
|
||||||
|
if (response.ok) {
|
||||||
|
return response.json()
|
||||||
|
} else {
|
||||||
|
throw new Error('Error removing repeat')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then((data) => parseStatus(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
const postStatus = ({credentials, status, spoilerText, visibility, sensitive, mediaIds, inReplyToStatusId, contentType, noAttachmentLinks}) => {
|
const postStatus = ({credentials, status, spoilerText, visibility, sensitive, mediaIds = [], inReplyToStatusId, contentType}) => {
|
||||||
const idsText = mediaIds.join(',')
|
|
||||||
const form = new FormData()
|
const form = new FormData()
|
||||||
|
|
||||||
form.append('status', status)
|
form.append('status', status)
|
||||||
form.append('source', 'Pleroma FE')
|
form.append('source', 'Pleroma FE')
|
||||||
if (noAttachmentLinks) form.append('no_attachment_links', noAttachmentLinks)
|
|
||||||
if (spoilerText) form.append('spoiler_text', spoilerText)
|
if (spoilerText) form.append('spoiler_text', spoilerText)
|
||||||
if (visibility) form.append('visibility', visibility)
|
if (visibility) form.append('visibility', visibility)
|
||||||
if (sensitive) form.append('sensitive', sensitive)
|
if (sensitive) form.append('sensitive', sensitive)
|
||||||
if (contentType) form.append('content_type', contentType)
|
if (contentType) form.append('content_type', contentType)
|
||||||
form.append('media_ids', idsText)
|
mediaIds.forEach(val => {
|
||||||
|
form.append('media_ids[]', val)
|
||||||
|
})
|
||||||
if (inReplyToStatusId) {
|
if (inReplyToStatusId) {
|
||||||
form.append('in_reply_to_status_id', inReplyToStatusId)
|
form.append('in_reply_to_id', inReplyToStatusId)
|
||||||
}
|
}
|
||||||
|
|
||||||
return fetch(STATUS_UPDATE_URL, {
|
return fetch(MASTODON_POST_STATUS_URL, {
|
||||||
body: form,
|
body: form,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: authHeaders(credentials)
|
headers: authHeaders(credentials)
|
||||||
|
@ -484,20 +521,20 @@ const postStatus = ({credentials, status, spoilerText, visibility, sensitive, me
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteStatus = ({ id, credentials }) => {
|
const deleteStatus = ({ id, credentials }) => {
|
||||||
return fetch(`${STATUS_DELETE_URL}/${id}.json`, {
|
return fetch(MASTODON_DELETE_URL(id), {
|
||||||
headers: authHeaders(credentials),
|
headers: authHeaders(credentials),
|
||||||
method: 'POST'
|
method: 'DELETE'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const uploadMedia = ({formData, credentials}) => {
|
const uploadMedia = ({formData, credentials}) => {
|
||||||
return fetch(MEDIA_UPLOAD_URL, {
|
return fetch(MASTODON_MEDIA_UPLOAD_URL, {
|
||||||
body: formData,
|
body: formData,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: authHeaders(credentials)
|
headers: authHeaders(credentials)
|
||||||
})
|
})
|
||||||
.then((response) => response.text())
|
.then((data) => data.json())
|
||||||
.then((text) => (new DOMParser()).parseFromString(text, 'application/xml'))
|
.then((data) => parseAttachment(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
const followImport = ({params, credentials}) => {
|
const followImport = ({params, credentials}) => {
|
||||||
|
@ -538,24 +575,29 @@ const changePassword = ({credentials, password, newPassword, newPasswordConfirma
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchMutes = ({credentials}) => {
|
const fetchMutes = ({credentials}) => {
|
||||||
const url = '/api/qvitter/mutes.json'
|
return promisedRequest(MASTODON_USER_MUTES_URL, { headers: authHeaders(credentials) })
|
||||||
|
.then((users) => users.map(parseUser))
|
||||||
return fetch(url, {
|
|
||||||
headers: authHeaders(credentials)
|
|
||||||
}).then((data) => data.json())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchBlocks = ({page, credentials}) => {
|
const muteUser = ({id, credentials}) => {
|
||||||
return fetch(BLOCKS_URL, {
|
return promisedRequest(MASTODON_MUTE_USER_URL(id), {
|
||||||
headers: authHeaders(credentials)
|
headers: authHeaders(credentials),
|
||||||
}).then((data) => {
|
method: 'POST'
|
||||||
if (data.ok) {
|
|
||||||
return data.json()
|
|
||||||
}
|
|
||||||
throw new Error('Error fetching blocks', data)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const unmuteUser = ({id, credentials}) => {
|
||||||
|
return promisedRequest(MASTODON_UNMUTE_USER_URL(id), {
|
||||||
|
headers: authHeaders(credentials),
|
||||||
|
method: 'POST'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchBlocks = ({credentials}) => {
|
||||||
|
return promisedRequest(MASTODON_USER_BLOCKS_URL, { headers: authHeaders(credentials) })
|
||||||
|
.then((users) => users.map(parseUser))
|
||||||
|
}
|
||||||
|
|
||||||
const fetchOAuthTokens = ({credentials}) => {
|
const fetchOAuthTokens = ({credentials}) => {
|
||||||
const url = '/api/oauth_tokens.json'
|
const url = '/api/oauth_tokens.json'
|
||||||
|
|
||||||
|
@ -618,8 +660,9 @@ const apiService = {
|
||||||
deleteStatus,
|
deleteStatus,
|
||||||
uploadMedia,
|
uploadMedia,
|
||||||
fetchAllFollowing,
|
fetchAllFollowing,
|
||||||
setUserMute,
|
|
||||||
fetchMutes,
|
fetchMutes,
|
||||||
|
muteUser,
|
||||||
|
unmuteUser,
|
||||||
fetchBlocks,
|
fetchBlocks,
|
||||||
fetchOAuthTokens,
|
fetchOAuthTokens,
|
||||||
revokeOAuthToken,
|
revokeOAuthToken,
|
||||||
|
|
|
@ -10,16 +10,16 @@ const backendInteractorService = (credentials) => {
|
||||||
return apiService.fetchConversation({id, credentials})
|
return apiService.fetchConversation({id, credentials})
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchFriends = ({id, page}) => {
|
const fetchFriends = ({id, maxId, sinceId, limit}) => {
|
||||||
return apiService.fetchFriends({id, page, credentials})
|
return apiService.fetchFriends({id, maxId, sinceId, limit, credentials})
|
||||||
}
|
}
|
||||||
|
|
||||||
const exportFriends = ({id}) => {
|
const exportFriends = ({id}) => {
|
||||||
return apiService.exportFriends({id, credentials})
|
return apiService.exportFriends({id, credentials})
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchFollowers = ({id, page}) => {
|
const fetchFollowers = ({id, maxId, sinceId, limit}) => {
|
||||||
return apiService.fetchFollowers({id, page, credentials})
|
return apiService.fetchFollowers({id, maxId, sinceId, limit, credentials})
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchAllFollowing = ({username}) => {
|
const fetchAllFollowing = ({username}) => {
|
||||||
|
@ -62,12 +62,10 @@ const backendInteractorService = (credentials) => {
|
||||||
return timelineFetcherService.startFetching({timeline, store, credentials, userId, tag})
|
return timelineFetcherService.startFetching({timeline, store, credentials, userId, tag})
|
||||||
}
|
}
|
||||||
|
|
||||||
const setUserMute = ({id, muted = true}) => {
|
|
||||||
return apiService.setUserMute({id, muted, credentials})
|
|
||||||
}
|
|
||||||
|
|
||||||
const fetchMutes = () => apiService.fetchMutes({credentials})
|
const fetchMutes = () => apiService.fetchMutes({credentials})
|
||||||
const fetchBlocks = (params) => apiService.fetchBlocks({credentials, ...params})
|
const muteUser = (id) => apiService.muteUser({credentials, id})
|
||||||
|
const unmuteUser = (id) => apiService.unmuteUser({credentials, id})
|
||||||
|
const fetchBlocks = () => apiService.fetchBlocks({credentials})
|
||||||
const fetchFollowRequests = () => apiService.fetchFollowRequests({credentials})
|
const fetchFollowRequests = () => apiService.fetchFollowRequests({credentials})
|
||||||
const fetchOAuthTokens = () => apiService.fetchOAuthTokens({credentials})
|
const fetchOAuthTokens = () => apiService.fetchOAuthTokens({credentials})
|
||||||
const revokeOAuthToken = (id) => apiService.revokeOAuthToken({id, credentials})
|
const revokeOAuthToken = (id) => apiService.revokeOAuthToken({id, credentials})
|
||||||
|
@ -100,8 +98,9 @@ const backendInteractorService = (credentials) => {
|
||||||
fetchAllFollowing,
|
fetchAllFollowing,
|
||||||
verifyCredentials: apiService.verifyCredentials,
|
verifyCredentials: apiService.verifyCredentials,
|
||||||
startFetching,
|
startFetching,
|
||||||
setUserMute,
|
|
||||||
fetchMutes,
|
fetchMutes,
|
||||||
|
muteUser,
|
||||||
|
unmuteUser,
|
||||||
fetchBlocks,
|
fetchBlocks,
|
||||||
fetchOAuthTokens,
|
fetchOAuthTokens,
|
||||||
revokeOAuthToken,
|
revokeOAuthToken,
|
||||||
|
|
|
@ -128,14 +128,15 @@ export const parseUser = (data) => {
|
||||||
return output
|
return output
|
||||||
}
|
}
|
||||||
|
|
||||||
const parseAttachment = (data) => {
|
export const parseAttachment = (data) => {
|
||||||
const output = {}
|
const output = {}
|
||||||
const masto = !data.hasOwnProperty('oembed')
|
const masto = !data.hasOwnProperty('oembed')
|
||||||
|
|
||||||
if (masto) {
|
if (masto) {
|
||||||
// Not exactly same...
|
// Not exactly same...
|
||||||
output.mimetype = data.type
|
output.mimetype = data.pleroma ? data.pleroma.mime_type : data.type
|
||||||
output.meta = data.meta // not present in BE yet
|
output.meta = data.meta // not present in BE yet
|
||||||
|
output.id = data.id
|
||||||
} else {
|
} else {
|
||||||
output.mimetype = data.mimetype
|
output.mimetype = data.mimetype
|
||||||
// output.meta = ??? missing
|
// output.meta = ??? missing
|
||||||
|
@ -176,6 +177,7 @@ export const parseStatus = (data) => {
|
||||||
|
|
||||||
output.in_reply_to_status_id = data.in_reply_to_id
|
output.in_reply_to_status_id = data.in_reply_to_id
|
||||||
output.in_reply_to_user_id = data.in_reply_to_account_id
|
output.in_reply_to_user_id = data.in_reply_to_account_id
|
||||||
|
output.replies_count = data.replies_count
|
||||||
|
|
||||||
// Missing!! fix in UI?
|
// Missing!! fix in UI?
|
||||||
// output.in_reply_to_screen_name = ???
|
// output.in_reply_to_screen_name = ???
|
||||||
|
|
|
@ -19,7 +19,7 @@ const fetchUser = (attempt, user, store) => new Promise((resolve, reject) => {
|
||||||
export const requestFollow = (user, store) => new Promise((resolve, reject) => {
|
export const requestFollow = (user, store) => new Promise((resolve, reject) => {
|
||||||
store.state.api.backendInteractor.followUser(user.id)
|
store.state.api.backendInteractor.followUser(user.id)
|
||||||
.then((updated) => {
|
.then((updated) => {
|
||||||
store.commit('addNewUsers', [updated])
|
store.commit('updateUserRelationship', [updated])
|
||||||
|
|
||||||
// For locked users we just mark it that we sent the follow request
|
// For locked users we just mark it that we sent the follow request
|
||||||
if (updated.locked) {
|
if (updated.locked) {
|
||||||
|
@ -66,7 +66,7 @@ export const requestFollow = (user, store) => new Promise((resolve, reject) => {
|
||||||
export const requestUnfollow = (user, store) => new Promise((resolve, reject) => {
|
export const requestUnfollow = (user, store) => new Promise((resolve, reject) => {
|
||||||
store.state.api.backendInteractor.unfollowUser(user.id)
|
store.state.api.backendInteractor.unfollowUser(user.id)
|
||||||
.then((updated) => {
|
.then((updated) => {
|
||||||
store.commit('addNewUsers', [updated])
|
store.commit('updateUserRelationship', [updated])
|
||||||
resolve({
|
resolve({
|
||||||
updated
|
updated
|
||||||
})
|
})
|
||||||
|
|
74
src/services/gesture_service/gesture_service.js
Normal file
74
src/services/gesture_service/gesture_service.js
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
|
||||||
|
const DIRECTION_LEFT = [-1, 0]
|
||||||
|
const DIRECTION_RIGHT = [1, 0]
|
||||||
|
const DIRECTION_UP = [0, -1]
|
||||||
|
const DIRECTION_DOWN = [0, 1]
|
||||||
|
|
||||||
|
const deltaCoord = (oldCoord, newCoord) => [newCoord[0] - oldCoord[0], newCoord[1] - oldCoord[1]]
|
||||||
|
|
||||||
|
const touchEventCoord = e => ([e.touches[0].screenX, e.touches[0].screenY])
|
||||||
|
|
||||||
|
const vectorLength = v => Math.sqrt(v[0] * v[0] + v[1] * v[1])
|
||||||
|
|
||||||
|
const perpendicular = v => [v[1], -v[0]]
|
||||||
|
|
||||||
|
const dotProduct = (v1, v2) => v1[0] * v2[0] + v1[1] * v2[1]
|
||||||
|
|
||||||
|
const project = (v1, v2) => {
|
||||||
|
const scalar = (dotProduct(v1, v2) / dotProduct(v2, v2))
|
||||||
|
return [scalar * v2[0], scalar * v2[1]]
|
||||||
|
}
|
||||||
|
|
||||||
|
// direction: either use the constants above or an arbitrary 2d vector.
|
||||||
|
// threshold: how many Px to move from touch origin before checking if the
|
||||||
|
// callback should be called.
|
||||||
|
// divergentTolerance: a scalar for much of divergent direction we tolerate when
|
||||||
|
// above threshold. for example, with 1.0 we only call the callback if
|
||||||
|
// divergent component of delta is < 1.0 * direction component of delta.
|
||||||
|
const swipeGesture = (direction, onSwipe, threshold = 30, perpendicularTolerance = 1.0) => {
|
||||||
|
return {
|
||||||
|
direction,
|
||||||
|
onSwipe,
|
||||||
|
threshold,
|
||||||
|
perpendicularTolerance,
|
||||||
|
_startPos: [0, 0],
|
||||||
|
_swiping: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const beginSwipe = (event, gesture) => {
|
||||||
|
gesture._startPos = touchEventCoord(event)
|
||||||
|
gesture._swiping = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateSwipe = (event, gesture) => {
|
||||||
|
if (!gesture._swiping) return
|
||||||
|
// movement too small
|
||||||
|
const delta = deltaCoord(gesture._startPos, touchEventCoord(event))
|
||||||
|
if (vectorLength(delta) < gesture.threshold) return
|
||||||
|
// movement is opposite from direction
|
||||||
|
if (dotProduct(delta, gesture.direction) < 0) return
|
||||||
|
// movement perpendicular to direction is too much
|
||||||
|
const towardsDir = project(delta, gesture.direction)
|
||||||
|
const perpendicularDir = perpendicular(gesture.direction)
|
||||||
|
const towardsPerpendicular = project(delta, perpendicularDir)
|
||||||
|
if (
|
||||||
|
vectorLength(towardsDir) * gesture.perpendicularTolerance <
|
||||||
|
vectorLength(towardsPerpendicular)
|
||||||
|
) return
|
||||||
|
|
||||||
|
gesture.onSwipe()
|
||||||
|
gesture._swiping = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const GestureService = {
|
||||||
|
DIRECTION_LEFT,
|
||||||
|
DIRECTION_RIGHT,
|
||||||
|
DIRECTION_UP,
|
||||||
|
DIRECTION_DOWN,
|
||||||
|
swipeGesture,
|
||||||
|
beginSwipe,
|
||||||
|
updateSwipe
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GestureService
|
|
@ -4,7 +4,7 @@ import apiService from '../api/api.service.js'
|
||||||
const postStatus = ({ store, status, spoilerText, visibility, sensitive, media = [], inReplyToStatusId = undefined, contentType = 'text/plain' }) => {
|
const postStatus = ({ store, status, spoilerText, visibility, sensitive, media = [], inReplyToStatusId = undefined, contentType = 'text/plain' }) => {
|
||||||
const mediaIds = map(media, 'id')
|
const mediaIds = map(media, 'id')
|
||||||
|
|
||||||
return apiService.postStatus({credentials: store.state.users.currentUser.credentials, status, spoilerText, visibility, sensitive, mediaIds, inReplyToStatusId, contentType, noAttachmentLinks: store.state.instance.noAttachmentLinks})
|
return apiService.postStatus({credentials: store.state.users.currentUser.credentials, status, spoilerText, visibility, sensitive, mediaIds, inReplyToStatusId, contentType})
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (!data.error) {
|
if (!data.error) {
|
||||||
store.dispatch('addNewStatuses', {
|
store.dispatch('addNewStatuses', {
|
||||||
|
@ -26,25 +26,7 @@ const postStatus = ({ store, status, spoilerText, visibility, sensitive, media =
|
||||||
const uploadMedia = ({ store, formData }) => {
|
const uploadMedia = ({ store, formData }) => {
|
||||||
const credentials = store.state.users.currentUser.credentials
|
const credentials = store.state.users.currentUser.credentials
|
||||||
|
|
||||||
return apiService.uploadMedia({ credentials, formData }).then((xml) => {
|
return apiService.uploadMedia({ credentials, formData })
|
||||||
// Firefox and Chrome treat method differently...
|
|
||||||
let link = xml.getElementsByTagName('link')
|
|
||||||
|
|
||||||
if (link.length === 0) {
|
|
||||||
link = xml.getElementsByTagName('atom:link')
|
|
||||||
}
|
|
||||||
|
|
||||||
link = link[0]
|
|
||||||
|
|
||||||
const mediaData = {
|
|
||||||
id: xml.getElementsByTagName('media_id')[0].textContent,
|
|
||||||
url: xml.getElementsByTagName('media_url')[0].textContent,
|
|
||||||
image: link.getAttribute('href'),
|
|
||||||
mimetype: link.getAttribute('type')
|
|
||||||
}
|
|
||||||
|
|
||||||
return mediaData
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const statusPosterService = {
|
const statusPosterService = {
|
||||||
|
|
|
@ -19,6 +19,9 @@ const fetchAndUpdate = ({store, credentials, timeline = 'friends', older = false
|
||||||
const args = { timeline, credentials }
|
const args = { timeline, credentials }
|
||||||
const rootState = store.rootState || store.state
|
const rootState = store.rootState || store.state
|
||||||
const timelineData = rootState.statuses.timelines[camelCase(timeline)]
|
const timelineData = rootState.statuses.timelines[camelCase(timeline)]
|
||||||
|
const hideMutedPosts = typeof rootState.config.hideMutedPosts === 'undefined'
|
||||||
|
? rootState.instance.hideMutedPosts
|
||||||
|
: rootState.config.hideMutedPosts
|
||||||
|
|
||||||
if (older) {
|
if (older) {
|
||||||
args['until'] = until || timelineData.minId
|
args['until'] = until || timelineData.minId
|
||||||
|
@ -28,6 +31,7 @@ const fetchAndUpdate = ({store, credentials, timeline = 'friends', older = false
|
||||||
|
|
||||||
args['userId'] = userId
|
args['userId'] = userId
|
||||||
args['tag'] = tag
|
args['tag'] = tag
|
||||||
|
args['withMuted'] = !hideMutedPosts
|
||||||
|
|
||||||
const numStatusesBeforeFetch = timelineData.statuses.length
|
const numStatusesBeforeFetch = timelineData.statuses.length
|
||||||
|
|
||||||
|
|
120
test/unit/specs/services/gesture_service/gesture_service.spec.js
Normal file
120
test/unit/specs/services/gesture_service/gesture_service.spec.js
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
import GestureService from 'src/services/gesture_service/gesture_service.js'
|
||||||
|
|
||||||
|
const mockTouchEvent = (x, y) => ({
|
||||||
|
touches: [
|
||||||
|
{
|
||||||
|
screenX: x,
|
||||||
|
screenY: y
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
describe.only('GestureService', () => {
|
||||||
|
describe('swipeGesture', () => {
|
||||||
|
it('calls the callback on a successful swipe', () => {
|
||||||
|
let swiped = false
|
||||||
|
const callback = () => { swiped = true }
|
||||||
|
const gesture = GestureService.swipeGesture(
|
||||||
|
GestureService.DIRECTION_RIGHT,
|
||||||
|
callback
|
||||||
|
)
|
||||||
|
|
||||||
|
GestureService.beginSwipe(mockTouchEvent(100, 100), gesture)
|
||||||
|
GestureService.updateSwipe(mockTouchEvent(200, 100), gesture)
|
||||||
|
|
||||||
|
expect(swiped).to.eql(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('calls the callback only once per begin', () => {
|
||||||
|
let hits = 0
|
||||||
|
const callback = () => { hits += 1 }
|
||||||
|
const gesture = GestureService.swipeGesture(
|
||||||
|
GestureService.DIRECTION_RIGHT,
|
||||||
|
callback
|
||||||
|
)
|
||||||
|
|
||||||
|
GestureService.beginSwipe(mockTouchEvent(100, 100), gesture)
|
||||||
|
GestureService.updateSwipe(mockTouchEvent(150, 100), gesture)
|
||||||
|
GestureService.updateSwipe(mockTouchEvent(200, 100), gesture)
|
||||||
|
|
||||||
|
expect(hits).to.eql(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('doesn\'t call the callback on an opposite swipe', () => {
|
||||||
|
let swiped = false
|
||||||
|
const callback = () => { swiped = true }
|
||||||
|
const gesture = GestureService.swipeGesture(
|
||||||
|
GestureService.DIRECTION_RIGHT,
|
||||||
|
callback
|
||||||
|
)
|
||||||
|
|
||||||
|
GestureService.beginSwipe(mockTouchEvent(100, 100), gesture)
|
||||||
|
GestureService.updateSwipe(mockTouchEvent(0, 100), gesture)
|
||||||
|
|
||||||
|
expect(swiped).to.eql(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('doesn\'t call the callback on a swipe below threshold', () => {
|
||||||
|
let swiped = false
|
||||||
|
const callback = () => { swiped = true }
|
||||||
|
const gesture = GestureService.swipeGesture(
|
||||||
|
GestureService.DIRECTION_RIGHT,
|
||||||
|
callback,
|
||||||
|
100
|
||||||
|
)
|
||||||
|
|
||||||
|
GestureService.beginSwipe(mockTouchEvent(100, 100), gesture)
|
||||||
|
GestureService.updateSwipe(mockTouchEvent(150, 100), gesture)
|
||||||
|
|
||||||
|
expect(swiped).to.eql(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('doesn\'t call the callback on a perpendicular swipe', () => {
|
||||||
|
let swiped = false
|
||||||
|
const callback = () => { swiped = true }
|
||||||
|
const gesture = GestureService.swipeGesture(
|
||||||
|
GestureService.DIRECTION_RIGHT,
|
||||||
|
callback,
|
||||||
|
30,
|
||||||
|
0.5
|
||||||
|
)
|
||||||
|
|
||||||
|
GestureService.beginSwipe(mockTouchEvent(100, 100), gesture)
|
||||||
|
GestureService.updateSwipe(mockTouchEvent(150, 200), gesture)
|
||||||
|
|
||||||
|
expect(swiped).to.eql(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('calls the callback on perpendicular swipe if within tolerance', () => {
|
||||||
|
let swiped = false
|
||||||
|
const callback = () => { swiped = true }
|
||||||
|
const gesture = GestureService.swipeGesture(
|
||||||
|
GestureService.DIRECTION_RIGHT,
|
||||||
|
callback,
|
||||||
|
30,
|
||||||
|
2.0
|
||||||
|
)
|
||||||
|
|
||||||
|
GestureService.beginSwipe(mockTouchEvent(100, 100), gesture)
|
||||||
|
GestureService.updateSwipe(mockTouchEvent(150, 150), gesture)
|
||||||
|
|
||||||
|
expect(swiped).to.eql(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('works with any arbitrary 2d directions', () => {
|
||||||
|
let swiped = false
|
||||||
|
const callback = () => { swiped = true }
|
||||||
|
const gesture = GestureService.swipeGesture(
|
||||||
|
[-1, -1],
|
||||||
|
callback,
|
||||||
|
30,
|
||||||
|
0.1
|
||||||
|
)
|
||||||
|
|
||||||
|
GestureService.beginSwipe(mockTouchEvent(100, 100), gesture)
|
||||||
|
GestureService.updateSwipe(mockTouchEvent(60, 60), gesture)
|
||||||
|
|
||||||
|
expect(swiped).to.eql(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
Loading…
Reference in a new issue