Mastodon: Context
parent
6e74ef10b3
commit
95fa082b94
|
@ -10,7 +10,7 @@
|
|||
<noscript>
|
||||
<strong>We're sorry but mixit doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<main id="app"></main>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -340,6 +340,7 @@ input, select, button, textarea
|
|||
float: right
|
||||
width: 1.2em
|
||||
.service-content
|
||||
flex-grow: 1
|
||||
overflow: hidden
|
||||
.service-loader
|
||||
display: inline-block
|
||||
|
|
|
@ -9,6 +9,10 @@ export default class ErrorLoadable<T, E> extends Loadable<T> {
|
|||
this.error = undefined
|
||||
}
|
||||
|
||||
get hasError() {
|
||||
return this.error !== undefined
|
||||
}
|
||||
|
||||
get isSuccess() {
|
||||
return this.loaded && this.error === undefined
|
||||
}
|
||||
|
|
|
@ -9,6 +9,10 @@ export default class Loadable<T> {
|
|||
this.reset()
|
||||
}
|
||||
|
||||
get isLoaded() {
|
||||
return this.loaded
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.loaded = false
|
||||
this.data = undefined
|
||||
|
|
|
@ -1,19 +1,36 @@
|
|||
<template lang="pug">
|
||||
.client
|
||||
.statues(@scroll.passive="onScroll")
|
||||
.statues(@scroll.passive="onScroll" v-show="!hasContext")
|
||||
.header(v-if="hasNotifications") Accueil
|
||||
success-loadable.list(:loadable="statues")
|
||||
template(v-for="status in statues.get()")
|
||||
status(v-if="showStatus(status)" :key="status.id" :status="status" :showMedia="options.showMedia" @mark="onStatusMark" @vote="onPollVote")
|
||||
status(v-if="showStatus(status)" :key="status.id" :status="status" :showMedia="options.showMedia" @mark="onStatusMark" @vote="onPollVote" @context="onShowContext")
|
||||
.status(v-show="statues.loadingMore")
|
||||
.service-loader
|
||||
|
||||
.context(v-if="hasContext")
|
||||
.header(@click="closeContext")
|
||||
| Context
|
||||
span.date(@click.stop.prevent="closeContext") ❌
|
||||
.list
|
||||
.ancestors
|
||||
template(v-if="targetContext.isSuccess")
|
||||
status(v-for="status in targetContext.get().ancestors" :key="status.id" :status="status" :showMedia="options.showMedia" @mark="onStatusMark" @vote="onPollVote" @context="onShowContext")
|
||||
.service-loader(v-else)
|
||||
status.selected(:status="targetStatus" :showMedia="options.showMedia" @mark="onStatusMark" @vote="onPollVote" @context="closeContext")
|
||||
.descendants
|
||||
template(v-if="targetContext.isSuccess")
|
||||
status(v-for="status in targetContext.get().descendants" :key="status.id" :status="status" :showMedia="options.showMedia" @mark="onStatusMark" @vote="onPollVote" @context="onShowContext")
|
||||
.service-loader(v-else)
|
||||
|
||||
.notifications(v-if="hasNotifications")
|
||||
.header
|
||||
| Notifications
|
||||
span.date(@click.stop.prevent="onNotificationsClear") ❌
|
||||
.list
|
||||
notification(v-for="notification in notifications.get()" :key="notification.id" :notification="notification"
|
||||
:showMedia="options.showMedia" @dismiss="onNotificationDismiss" @mark="onStatusMark" @vote="onPollVote")
|
||||
:showMedia="options.showMedia" @dismiss="onNotificationDismiss" @mark="onStatusMark" @vote="onPollVote" @context="onShowContext")
|
||||
|
||||
.compose-toggle(@click="showCompose = !showCompose") 🖉
|
||||
.emoji-list(v-if="options.showMedia" v-show="showCompose && showEmojis")
|
||||
img.emoji(v-for="emoji in emojis.get()" @click="addEmoji(emoji.shortcode)" :src="emoji.static_url" :alt="emoji.shortcode" :title="emoji.shortcode")
|
||||
|
@ -45,12 +62,12 @@ import { Component, Mixins, Prop } from 'vue-property-decorator'
|
|||
|
||||
import ServiceClient from '@/components/ServiceClient'
|
||||
import Lists from '@/helpers/lists/Lists'
|
||||
import AxiosLodable from '@/helpers/loadable/AxiosLoadable'
|
||||
import AxiosLodableMore from '@/helpers/loadable/AxiosLoadableMore'
|
||||
import AxiosLoadable from '@/helpers/loadable/AxiosLoadable'
|
||||
import AxiosLoadableMore from '@/helpers/loadable/AxiosLoadableMore'
|
||||
import { AUTH, getHeaders, getRest } from './Mastodon.vue'
|
||||
import Notification from './Notification.vue'
|
||||
import Status from './Status.vue'
|
||||
import { Emoji, MarkStatus, Notification as INotification, Options, Poll, PollVote, Status as IStatus, StatusPost, TimelineType } from './Types'
|
||||
import { Context, Emoji, MarkStatus, Notification as INotification, Options, Poll, PollVote, Status as IStatus, StatusPost, TimelineType } from './Types'
|
||||
|
||||
const STREAMS = {
|
||||
home: 'user',
|
||||
|
@ -63,11 +80,14 @@ export default class Client extends Mixins<ServiceClient<Options>>(ServiceClient
|
|||
|
||||
rest = getRest(this.auth, this.options.timeout)
|
||||
|
||||
statues = new AxiosLodableMore<IStatus[], object>()
|
||||
notifications = new AxiosLodable<INotification[], object>()
|
||||
emojis = new AxiosLodable<Emoji[], object>()
|
||||
statues = new AxiosLoadableMore<IStatus[], object>()
|
||||
notifications = new AxiosLoadable<INotification[], object>()
|
||||
emojis = new AxiosLoadable<Emoji[], object>()
|
||||
stream?: WebSocket = undefined
|
||||
|
||||
targetStatus: IStatus | null = null
|
||||
targetContext = new AxiosLoadable<Context, object>()
|
||||
|
||||
showCompose = false
|
||||
compose: StatusPost = {
|
||||
status: '',
|
||||
|
@ -90,6 +110,10 @@ export default class Client extends Mixins<ServiceClient<Options>>(ServiceClient
|
|||
}
|
||||
}
|
||||
|
||||
get hasContext() {
|
||||
return this.targetStatus && !this.targetContext.hasError
|
||||
}
|
||||
|
||||
created() {
|
||||
this.$watch('options.timeline', this.init, { immediate: true })
|
||||
this.notifications.load(this.get<INotification[]>('/notifications'))
|
||||
|
@ -125,6 +149,9 @@ export default class Client extends Mixins<ServiceClient<Options>>(ServiceClient
|
|||
post.spoiler_text = this.compose.spoiler_text
|
||||
}
|
||||
}
|
||||
if(this.targetStatus) {
|
||||
post.in_reply_to_id = this.targetStatus.id
|
||||
}
|
||||
this.post('/statuses', post)
|
||||
this.compose.status = ''
|
||||
}
|
||||
|
@ -159,10 +186,22 @@ export default class Client extends Mixins<ServiceClient<Options>>(ServiceClient
|
|||
onPollVote(action: PollVote) {
|
||||
this.post<Poll>(`/polls/${action.poll}/votes`, { choices: action.choices })
|
||||
.then(res => this.statues.with(
|
||||
sts => sts.find(st => st.id == action.id)!.poll = res.data
|
||||
sts => sts.find(st => st.id === action.id)!.poll = res.data
|
||||
))
|
||||
}
|
||||
|
||||
onShowContext(status: IStatus) {
|
||||
this.statues.with(sts => {
|
||||
this.targetStatus = status
|
||||
this.targetContext.load(this.get(`/statuses/${status.id}/context`), undefined, true)
|
||||
})
|
||||
}
|
||||
|
||||
closeContext() {
|
||||
this.targetStatus = null
|
||||
this.targetContext.reset()
|
||||
}
|
||||
|
||||
onNotificationDismiss(id: number) {
|
||||
this.post('/notifications/dismiss', { id })
|
||||
.then(() => this.notifications.with(
|
||||
|
@ -230,11 +269,21 @@ export default class Client extends Mixins<ServiceClient<Options>>(ServiceClient
|
|||
position: relative
|
||||
.header, .emoji-list
|
||||
@include main-tile
|
||||
.header
|
||||
margin-bottom: 0
|
||||
.list
|
||||
@include group-tile
|
||||
.statues, .notifications, .emoji-list
|
||||
flex: 1
|
||||
flex-grow: 1
|
||||
.statues, .notifications, .context, .emoji-list
|
||||
flex-grow: 1
|
||||
display: flex
|
||||
flex-direction: column
|
||||
overflow-y: auto
|
||||
.ancestors, .descendants
|
||||
.status
|
||||
font-size: .9em
|
||||
padding: $borderRadius
|
||||
@include tile
|
||||
.compose-toggle
|
||||
position: absolute
|
||||
bottom: .5em
|
||||
|
@ -309,6 +358,8 @@ export default class Client extends Mixins<ServiceClient<Options>>(ServiceClient
|
|||
.meta
|
||||
margin-left: 1em + $avatarSize
|
||||
font-size: .8em
|
||||
.fil
|
||||
float: right
|
||||
a
|
||||
margin: 0 .5em
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
.content
|
||||
template(v-if="notification.type == 'follow'") Vous suit
|
||||
status.reblog(v-else-if="notification.status" :status="notification.status"
|
||||
:showMedia="showMedia" :withAccount="notification.type != 'mention'" @mark="passMark" @vote="passVote")
|
||||
:showMedia="showMedia" :withAccount="notification.type != 'mention'" @mark="passMark" @vote="passVote" @context="passContext")
|
||||
|
||||
a.date(@click.stop.prevent="makeDismiss" style="margin-top: -1em") ❌
|
||||
</template>
|
||||
|
@ -23,7 +23,7 @@ import FromNowMixin from '@/components/FromNowMixin'
|
|||
import ShowMediaMixin from '@/components/ShowMediaMixin'
|
||||
import Account from './Account.vue'
|
||||
import Status from './Status.vue'
|
||||
import { MarkStatus, Notification as INotification, PollVote } from './Types'
|
||||
import { MarkStatus, Notification as INotification, PollVote, Status as IStatus } from './Types'
|
||||
|
||||
@Component({ components: { Account, Status } })
|
||||
export default class Notification extends Mixins(ShowMediaMixin, FromNowMixin) {
|
||||
|
@ -46,5 +46,10 @@ export default class Notification extends Mixins(ShowMediaMixin, FromNowMixin) {
|
|||
return action
|
||||
}
|
||||
|
||||
@Emit('context')
|
||||
passContext(status: IStatus) {
|
||||
return status
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
template(v-if="status.card.image")
|
||||
img(v-if="showMedia" :src="status.card.image")
|
||||
a(v-else-if="status.card.type == 'photo'" :src="status.card.image" target="_blank") Hidden media
|
||||
status.reblog(v-else :status="status.reblog" :showMedia="showMedia" @mark="passMark" @vote="passVote")
|
||||
status.reblog(v-else :status="status.reblog" :showMedia="showMedia" @mark="passMark" @vote="passVote" @context="passContext")
|
||||
|
||||
.meta(v-if="!status.reblog")
|
||||
a.replies(@click.stop.prevent="makeReply(status)")
|
||||
|
@ -53,8 +53,8 @@
|
|||
template(v-else-if="status.visibility == 'unlisted'") 👁
|
||||
template(v-else-if="status.visibility == 'private'") ⚿
|
||||
template(v-else-if="status.visibility == 'direct'") ✉
|
||||
a.fil(v-if="status.in_reply_to_id" @click.stop.prevent="showReply(status.in_reply_to_id)")
|
||||
| Voir le fil
|
||||
a.fil(@click.stop.prevent="passContext(status)")
|
||||
span.text-icon ⮃
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
@ -75,28 +75,6 @@ export default class Status extends Mixins(ParseEmojisMixin, ShowMediaMixin, Fro
|
|||
@Prop({ type: Boolean, default: true })
|
||||
readonly withAccount!: boolean
|
||||
|
||||
showReply(statusId: number) {
|
||||
throw statusId // TODO:
|
||||
}
|
||||
|
||||
makeReply(status: IStatus) {
|
||||
throw status.id // TODO:
|
||||
}
|
||||
|
||||
@Emit('mark')
|
||||
emitMark(id: number, action: 'reblog' | 'favourite', undo = false): MarkStatus {
|
||||
return {
|
||||
id, type: (undo ? 'un' : '') + action as MarkStatusType
|
||||
}
|
||||
}
|
||||
|
||||
@Emit('vote')
|
||||
emitVote(id: number, poll: string, choices: string[]): PollVote {
|
||||
return {
|
||||
id, poll, choices
|
||||
}
|
||||
}
|
||||
|
||||
@Emit('mark')
|
||||
passMark(action: MarkStatus) {
|
||||
return action
|
||||
|
@ -107,19 +85,34 @@ export default class Status extends Mixins(ParseEmojisMixin, ShowMediaMixin, Fro
|
|||
return action
|
||||
}
|
||||
|
||||
@Emit('context')
|
||||
passContext(status: IStatus) {
|
||||
return status
|
||||
}
|
||||
|
||||
makeVote(status: IStatus, elements: HTMLInputElement[]) {
|
||||
const choices = Object.values(elements).filter(e => e.checked).map(e => e.value)
|
||||
if(choices.length > 0) {
|
||||
this.emitVote(status.id, status.poll!.id, choices)
|
||||
this.passVote({ id: status.id, poll: status.poll!.id, choices })
|
||||
}
|
||||
}
|
||||
|
||||
makeMark(status: IStatus, action: string, undo: boolean) {
|
||||
this.passMark({
|
||||
id: status.id, type: (undo ? 'un' : '') + action as MarkStatusType
|
||||
})
|
||||
}
|
||||
|
||||
makeReblog(status: IStatus) {
|
||||
this.emitMark(status.id, 'reblog', status.reblogged)
|
||||
this.makeMark(status, 'reblog', status.reblogged)
|
||||
}
|
||||
|
||||
makeFav(status: IStatus) {
|
||||
this.emitMark(status.id, 'favourite', status.favourited)
|
||||
this.makeMark(status, 'favourite', status.favourited)
|
||||
}
|
||||
|
||||
makeReply(status: IStatus) {
|
||||
throw status.id // TODO:
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -94,6 +94,11 @@ export interface Media {
|
|||
type: string
|
||||
}
|
||||
|
||||
export interface Context {
|
||||
ancestors: Status[]
|
||||
descendants: Status[]
|
||||
}
|
||||
|
||||
export interface Notification {
|
||||
id: number
|
||||
account: Account
|
||||
|
|
Loading…
Reference in New Issue