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