feat: stream tags, home, and public timelines (#190)
parent
d94bed686b
commit
5560fe66cc
|
@ -2,12 +2,13 @@
|
||||||
// @ts-expect-error missing types
|
// @ts-expect-error missing types
|
||||||
import { DynamicScroller } from 'vue-virtual-scroller'
|
import { DynamicScroller } from 'vue-virtual-scroller'
|
||||||
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
|
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
|
||||||
import type { Paginator } from 'masto'
|
import type { Paginator, WsEvents } from 'masto'
|
||||||
|
|
||||||
const { paginator, keyProp = 'id', virtualScroller = false } = defineProps<{
|
const { paginator, stream, keyProp = 'id', virtualScroller = false } = defineProps<{
|
||||||
paginator: Paginator<any, any[]>
|
paginator: Paginator<any, any[]>
|
||||||
keyProp?: string
|
keyProp?: string
|
||||||
virtualScroller: boolean
|
virtualScroller: boolean
|
||||||
|
stream?: WsEvents
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
defineSlots<{
|
defineSlots<{
|
||||||
|
@ -15,14 +16,19 @@ defineSlots<{
|
||||||
item: any
|
item: any
|
||||||
active?: boolean
|
active?: boolean
|
||||||
}
|
}
|
||||||
|
updater: {
|
||||||
|
number: number
|
||||||
|
update: () => void
|
||||||
|
}
|
||||||
loading: {}
|
loading: {}
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const { items, state, endAnchor, error } = usePaginator(paginator)
|
const { items, prevItems, update, state, endAnchor, error } = usePaginator(paginator, stream)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
|
<slot v-if="prevItems.length" name="updater" v-bind="{ number: prevItems.length, update }" />
|
||||||
<template v-if="virtualScroller">
|
<template v-if="virtualScroller">
|
||||||
<DynamicScroller
|
<DynamicScroller
|
||||||
v-slot="{ item, active }"
|
v-slot="{ item, active }"
|
||||||
|
|
|
@ -1,15 +1,21 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
// @ts-expect-error missing types
|
// @ts-expect-error missing types
|
||||||
import { DynamicScrollerItem } from 'vue-virtual-scroller'
|
import { DynamicScrollerItem } from 'vue-virtual-scroller'
|
||||||
import type { Paginator, Status } from 'masto'
|
import type { Paginator, Status, WsEvents } from 'masto'
|
||||||
|
|
||||||
const { paginator } = defineProps<{
|
const { paginator, stream } = defineProps<{
|
||||||
paginator: Paginator<any, Status[]>
|
paginator: Paginator<any, Status[]>
|
||||||
|
stream?: WsEvents
|
||||||
}>()
|
}>()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<CommonPaginator :paginator="paginator" virtual-scroller>
|
<CommonPaginator v-bind="{ paginator, stream }" virtual-scroller>
|
||||||
|
<template #updater="{ number, update }">
|
||||||
|
<button py-4 border="b base" flex="~ col" p-3 w-full text-primary font-bold @click="update">
|
||||||
|
Show {{ number }} new items
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
<template #default="{ item, active }">
|
<template #default="{ item, active }">
|
||||||
<DynamicScrollerItem :item="item" :active="active" tag="article">
|
<DynamicScrollerItem :item="item" :active="active" tag="article">
|
||||||
<StatusCard
|
<StatusCard
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import type { Paginator } from 'masto'
|
import type { Paginator, WsEvents } from 'masto'
|
||||||
import { useDeactivated } from './lifecycle'
|
import { useDeactivated } from './lifecycle'
|
||||||
import type { PaginatorState } from '~/types'
|
import type { PaginatorState } from '~/types'
|
||||||
|
|
||||||
export function usePaginator<T>(paginator: Paginator<any, T[]>) {
|
export function usePaginator<T>(paginator: Paginator<any, T[]>, stream?: WsEvents) {
|
||||||
const state = ref<PaginatorState>('idle')
|
const state = ref<PaginatorState>('idle')
|
||||||
const items = ref<T[]>([])
|
const items = ref<T[]>([])
|
||||||
const newItems = ref<T[]>([])
|
const nextItems = ref<T[]>([])
|
||||||
|
const prevItems = ref<T[]>([])
|
||||||
|
|
||||||
const endAnchor = ref<HTMLDivElement>()
|
const endAnchor = ref<HTMLDivElement>()
|
||||||
const bound = reactive(useElementBounding(endAnchor))
|
const bound = reactive(useElementBounding(endAnchor))
|
||||||
|
@ -13,6 +14,22 @@ export function usePaginator<T>(paginator: Paginator<any, T[]>) {
|
||||||
const error = ref<unknown | undefined>()
|
const error = ref<unknown | undefined>()
|
||||||
const deactivated = useDeactivated()
|
const deactivated = useDeactivated()
|
||||||
|
|
||||||
|
async function update() {
|
||||||
|
items.value.unshift(...prevItems.value)
|
||||||
|
prevItems.value = []
|
||||||
|
}
|
||||||
|
|
||||||
|
stream?.on('update', (status) => {
|
||||||
|
prevItems.value.unshift(status as any)
|
||||||
|
})
|
||||||
|
|
||||||
|
// TODO: update statuses
|
||||||
|
stream?.on('status.update', (status) => {
|
||||||
|
const index = items.value.findIndex((s: any) => s.id === status.id)
|
||||||
|
if (index >= 0)
|
||||||
|
items.value[index] = status as any
|
||||||
|
})
|
||||||
|
|
||||||
async function loadNext() {
|
async function loadNext() {
|
||||||
if (state.value !== 'idle')
|
if (state.value !== 'idle')
|
||||||
return
|
return
|
||||||
|
@ -22,8 +39,8 @@ export function usePaginator<T>(paginator: Paginator<any, T[]>) {
|
||||||
const result = await paginator.next()
|
const result = await paginator.next()
|
||||||
|
|
||||||
if (result.value?.length) {
|
if (result.value?.length) {
|
||||||
newItems.value = result.value
|
nextItems.value = result.value
|
||||||
items.value.push(...newItems.value)
|
items.value.push(...nextItems.value)
|
||||||
state.value = 'idle'
|
state.value = 'idle'
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -59,7 +76,9 @@ export function usePaginator<T>(paginator: Paginator<any, T[]>) {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
items,
|
items,
|
||||||
newItems,
|
prevItems,
|
||||||
|
nextItems,
|
||||||
|
update,
|
||||||
state,
|
state,
|
||||||
error,
|
error,
|
||||||
endAnchor,
|
endAnchor,
|
||||||
|
|
|
@ -12,6 +12,8 @@ if (useRoute().path === '/signin/callback') {
|
||||||
}
|
}
|
||||||
|
|
||||||
const paginator = useMasto().timelines.getHomeIterable()
|
const paginator = useMasto().timelines.getHomeIterable()
|
||||||
|
const stream = await useMasto().stream.streamUser()
|
||||||
|
onBeforeUnmount(() => stream.disconnect())
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
useHead({
|
useHead({
|
||||||
|
@ -26,7 +28,7 @@ useHead({
|
||||||
</template>
|
</template>
|
||||||
<slot>
|
<slot>
|
||||||
<PublishWidget draft-key="home" border="b base" />
|
<PublishWidget draft-key="home" border="b base" />
|
||||||
<TimelinePaginator :paginator="paginator" />
|
<TimelinePaginator v-bind="{ paginator, stream }" />
|
||||||
</slot>
|
</slot>
|
||||||
</MainContent>
|
</MainContent>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const paginator = useMasto().timelines.getPublicIterable()
|
const paginator = useMasto().timelines.getPublicIterable()
|
||||||
|
const stream = await useMasto().stream.streamPublicTimeline()
|
||||||
|
onBeforeUnmount(() => stream.disconnect())
|
||||||
|
|
||||||
useHead({
|
useHead({
|
||||||
title: 'Federated',
|
title: 'Federated',
|
||||||
|
@ -13,7 +15,7 @@ useHead({
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<slot>
|
<slot>
|
||||||
<TimelinePaginator :paginator="paginator" />
|
<TimelinePaginator v-bind="{ paginator, stream }" />
|
||||||
</slot>
|
</slot>
|
||||||
</MainContent>
|
</MainContent>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const paginator = useMasto().timelines.getPublicIterable({ local: true })
|
const paginator = useMasto().timelines.getPublicIterable({ local: true })
|
||||||
|
const stream = await useMasto().stream.streamCommunityTimeline()
|
||||||
|
onBeforeUnmount(() => stream.disconnect())
|
||||||
|
|
||||||
useHead({
|
useHead({
|
||||||
title: 'Local',
|
title: 'Local',
|
||||||
|
@ -13,7 +15,7 @@ useHead({
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<slot>
|
<slot>
|
||||||
<TimelinePaginator :paginator="paginator" />
|
<TimelinePaginator v-bind="{ paginator, stream }" />
|
||||||
</slot>
|
</slot>
|
||||||
</MainContent>
|
</MainContent>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -3,6 +3,8 @@ const params = useRoute().params
|
||||||
const tag = $(computedEager(() => params.tag as string))
|
const tag = $(computedEager(() => params.tag as string))
|
||||||
|
|
||||||
const paginator = useMasto().timelines.getHashtagIterable(tag)
|
const paginator = useMasto().timelines.getHashtagIterable(tag)
|
||||||
|
const stream = await useMasto().stream.streamTagTimeline(tag)
|
||||||
|
onBeforeUnmount(() => stream.disconnect())
|
||||||
|
|
||||||
useHead({
|
useHead({
|
||||||
title: `#${tag}`,
|
title: `#${tag}`,
|
||||||
|
@ -16,7 +18,7 @@ useHead({
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<slot>
|
<slot>
|
||||||
<TimelinePaginator :paginator="paginator" />
|
<TimelinePaginator v-bind="{ paginator, stream }" />
|
||||||
</slot>
|
</slot>
|
||||||
</MainContent>
|
</MainContent>
|
||||||
</template>
|
</template>
|
||||||
|
|
Loading…
Reference in New Issue