misskey/packages/client/src/ui/chat/pages/timeline.vue
2022-10-16 17:57:42 -04:00

207 lines
4.6 KiB
Vue

<template>
<div class="dbiokgaf">
<div v-if="date" class="info">
<MkInfo>{{ i18n.ts.showingPastTimeline }} <button class="_textButton clear" @click="timetravel()">{{ i18n.ts.clear }}</button></MkInfo>
</div>
<div class="top">
<XPostForm/>
</div>
<div ref="body" class="tl">
<div v-if="queue > 0" class="new" :style="{ width: width + 'px', top: top + 'px' }"><button class="_buttonPrimary" @click="goTop()">{{ i18n.ts.newNoteRecived }}</button></div>
<XNotes ref="tl" class="tl" :pagination="pagination" @queue="queueUpdated"/>
</div>
</div>
</template>
<script lang="ts" setup>
import { computed, markRaw, onBeforeUnmount, ref, reactive, withDefaults } from 'vue';
import XNotes from '../notes.vue';
import * as os from '@/os';
import { stream } from '@/stream';
import * as sound from '@/scripts/sound';
import { scrollToBottom, getScrollPosition, getScrollContainer } from '@/scripts/scroll';
import XPostForm from '../post-form.vue';
import MkInfo from '@/components/MkInfo.vue';
import { definePageMetadata } from '@/scripts/page-metadata';
import { defaultStore } from '@/store';
import { i18n } from '@/i18n';
type Props = {
src: string;
};
const props = withDefaults(defineProps<Props>(), {
src: 'home',
});
const emit = defineEmits(['note']);
const baseQuery = {
includeMyRenotes: defaultStore.state.showMyRenotes,
includeRenotedMyNotes: defaultStore.state.showRenotedMyNotes,
includeLocalRenotes: defaultStore.state.showLocalRenotes
};
const connection = ref(null);
const connection2 = ref(null);
const query = reactive({});
const queue = ref(0);
const width = ref(0);
const top = ref(0);
const bottom = ref(0);
const typers = ref([]);
const date = ref(null);
const tl = ref();
const body = ref();
definePageMetadata(computed(() => ({
title: i18n.ts.timeline,
icon: 'fas fa-home',
actions: [{
icon: 'fas fa-calendar-alt',
text: i18n.ts.jumpToSpecifiedDate,
handler: timetravel
}],
})));
const prepend = note => {
(tl.value as any).prepend(note);
emit('note');
sound.play(note.userId === this.$i.id ? 'noteMy' : 'note');
};
const onChangeFollowing = () => {
if (!tl.value.backed) {
tl.value.reload();
}
};
let endpoint;
if (props.src == 'home') {
endpoint = 'notes/timeline';
connection.value = markRaw(stream.useChannel('homeTimeline'));
connection.value.on('note', prepend);
connection2.value = markRaw(stream.useChannel('main'));
connection2.value.on('follow', onChangeFollowing);
connection2.value.on('unfollow', onChangeFollowing);
} else if (props.src == 'local') {
endpoint = 'notes/local-timeline';
connection.value = markRaw(stream.useChannel('localTimeline'));
connection.value.on('note', prepend);
} else if (props.src == 'social') {
endpoint = 'notes/hybrid-timeline';
connection.value = markRaw(stream.useChannel('hybridTimeline'));
connection.value.on('note', prepend);
} else if (props.src == 'global') {
endpoint = 'notes/global-timeline';
connection.value = markRaw(stream.useChannel('globalTimeline'));
connection.value.on('note', prepend);
}
const pagination = computed(() => ({
endpoint: endpoint,
limit: 10,
params: init => ({
untilDate: date.value?.getTime(),
...baseQuery, ...query,
}),
}));
onBeforeUnmount(() => {
connection.value.dispose();
if (connection2.value) connection2.value.dispose();
});
function focus() {
body.value.focus();
};
function goTop() {
const container = getScrollContainer(body.value);
container.scrollTop = 0;
};
function queueUpdated(q) {
if (body.value.offsetWidth !== 0) {
const rect = body.value.getBoundingClientRect();
this.width = body.value.offsetWidth;
top.value = rect.top;
bottom.value = body.value.offsetHeight;
}
queue.value = q;
};
function timetravel(target?: Date) {
date.value = target;
tl.value.reload();
};
</script>
<style lang="scss" scoped>
.dbiokgaf {
display: flex;
flex-direction: column;
flex: 1;
overflow: auto;
> .info {
padding: 16px 16px 0 16px;
}
> .top {
padding: 16px 16px 0 16px;
}
> .bottom {
padding: 0 16px 16px 16px;
position: relative;
> .typers {
position: absolute;
bottom: 100%;
padding: 0 8px 0 8px;
font-size: 0.9em;
background: var(--panel);
border-radius: 0 8px 0 0;
color: var(--fgTransparentWeak);
> .users {
> .user + .user:before {
content: ", ";
font-weight: normal;
}
> .user:last-of-type:after {
content: " ";
}
}
}
}
> .tl {
position: relative;
padding: 16px 0;
flex: 1;
min-width: 0;
overflow: auto;
> .new {
position: fixed;
z-index: 1000;
pointer-events: none;
> button {
display: block;
margin: 16px auto;
padding: 8px 16px;
border-radius: 32px;
}
}
}
}
</style>