181 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
			
		
		
	
	
			181 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
| <template>
 | |
| <MkStickyContainer>
 | |
| 	<template #header><MkPageHeader v-model:tab="src" :actions="headerActions" :tabs="headerTabs" :display-my-avatar="true"/></template>
 | |
| 	<MkSpacer :content-max="800">
 | |
| 		<div ref="rootEl" v-hotkey.global="keymap">
 | |
| 			<XTutorial v-if="$store.reactiveState.tutorial.value != -1" class="_panel" style="margin-bottom: var(--margin);"/>
 | |
| 			<XPostForm v-if="$store.reactiveState.showFixedPostForm.value" :class="$style.postForm" class="post-form _panel" fixed style="margin-bottom: var(--margin);"/>
 | |
| 
 | |
| 			<div v-if="queue > 0" :class="$style.new"><button class="_buttonPrimary" @click="top()">{{ i18n.ts.newNoteRecived }}</button></div>
 | |
| 			<div :class="$style.tl">
 | |
| 				<XTimeline
 | |
| 					ref="tl" :key="src"
 | |
| 					:src="src"
 | |
| 					:sound="true"
 | |
| 					@queue="queueUpdated"
 | |
| 				/>
 | |
| 			</div>
 | |
| 		</div>
 | |
| 	</MkSpacer>
 | |
| </MkStickyContainer>
 | |
| </template>
 | |
| 
 | |
| <script lang="ts" setup>
 | |
| import { defineAsyncComponent, computed, watch } from 'vue';
 | |
| import XTimeline from '@/components/MkTimeline.vue';
 | |
| import XPostForm from '@/components/MkPostForm.vue';
 | |
| import { scroll } from '@/scripts/scroll';
 | |
| import * as os from '@/os';
 | |
| import { defaultStore } from '@/store';
 | |
| import { i18n } from '@/i18n';
 | |
| import { instance } from '@/instance';
 | |
| import { $i } from '@/account';
 | |
| import { definePageMetadata } from '@/scripts/page-metadata';
 | |
| 
 | |
| const XTutorial = defineAsyncComponent(() => import('./timeline.tutorial.vue'));
 | |
| 
 | |
| const isLocalTimelineAvailable = !instance.disableLocalTimeline || ($i != null && ($i.isModerator || $i.isAdmin));
 | |
| const isGlobalTimelineAvailable = !instance.disableGlobalTimeline || ($i != null && ($i.isModerator || $i.isAdmin));
 | |
| const keymap = {
 | |
| 	't': focus,
 | |
| };
 | |
| 
 | |
| const tlComponent = $shallowRef<InstanceType<typeof XTimeline>>();
 | |
| const rootEl = $shallowRef<HTMLElement>();
 | |
| 
 | |
| let queue = $ref(0);
 | |
| const src = $computed({ get: () => defaultStore.reactiveState.tl.value.src, set: (x) => saveSrc(x) });
 | |
| 
 | |
| watch ($$(src), () => queue = 0);
 | |
| 
 | |
| function queueUpdated(q: number): void {
 | |
| 	queue = q;
 | |
| }
 | |
| 
 | |
| function top(): void {
 | |
| 	scroll(rootEl, { top: 0 });
 | |
| }
 | |
| 
 | |
| async function chooseList(ev: MouseEvent): Promise<void> {
 | |
| 	const lists = await os.api('users/lists/list');
 | |
| 	const items = lists.map(list => ({
 | |
| 		type: 'link' as const,
 | |
| 		text: list.name,
 | |
| 		to: `/timeline/list/${list.id}`,
 | |
| 	}));
 | |
| 	os.popupMenu(items, ev.currentTarget ?? ev.target);
 | |
| }
 | |
| 
 | |
| async function chooseAntenna(ev: MouseEvent): Promise<void> {
 | |
| 	const antennas = await os.api('antennas/list');
 | |
| 	const items = antennas.map(antenna => ({
 | |
| 		type: 'link' as const,
 | |
| 		text: antenna.name,
 | |
| 		indicate: antenna.hasUnreadNote,
 | |
| 		to: `/timeline/antenna/${antenna.id}`,
 | |
| 	}));
 | |
| 	os.popupMenu(items, ev.currentTarget ?? ev.target);
 | |
| }
 | |
| 
 | |
| async function chooseChannel(ev: MouseEvent): Promise<void> {
 | |
| 	const channels = await os.api('channels/followed');
 | |
| 	const items = channels.map(channel => ({
 | |
| 		type: 'link' as const,
 | |
| 		text: channel.name,
 | |
| 		indicate: channel.hasUnreadNote,
 | |
| 		to: `/channels/${channel.id}`,
 | |
| 	}));
 | |
| 	os.popupMenu(items, ev.currentTarget ?? ev.target);
 | |
| }
 | |
| 
 | |
| function saveSrc(newSrc: 'home' | 'local' | 'social' | 'global'): void {
 | |
| 	defaultStore.set('tl', {
 | |
| 		...defaultStore.state.tl,
 | |
| 		src: newSrc,
 | |
| 	});
 | |
| }
 | |
| 
 | |
| async function timetravel(): Promise<void> {
 | |
| 	const { canceled, result: date } = await os.inputDate({
 | |
| 		title: i18n.ts.date,
 | |
| 	});
 | |
| 	if (canceled) return;
 | |
| 
 | |
| 	tlComponent.timetravel(date);
 | |
| }
 | |
| 
 | |
| function focus(): void {
 | |
| 	tlComponent.focus();
 | |
| }
 | |
| 
 | |
| const headerActions = $computed(() => []);
 | |
| 
 | |
| const headerTabs = $computed(() => [{
 | |
| 	key: 'home',
 | |
| 	title: i18n.ts._timelines.home,
 | |
| 	icon: 'ti ti-home',
 | |
| 	iconOnly: true,
 | |
| }, ...(isLocalTimelineAvailable ? [{
 | |
| 	key: 'local',
 | |
| 	title: i18n.ts._timelines.local,
 | |
| 	icon: 'ti ti-planet',
 | |
| 	iconOnly: true,
 | |
| }, {
 | |
| 	key: 'social',
 | |
| 	title: i18n.ts._timelines.social,
 | |
| 	icon: 'ti ti-rocket',
 | |
| 	iconOnly: true,
 | |
| }] : []), ...(isGlobalTimelineAvailable ? [{
 | |
| 	key: 'global',
 | |
| 	title: i18n.ts._timelines.global,
 | |
| 	icon: 'ti ti-whirl',
 | |
| 	iconOnly: true,
 | |
| }] : []), {
 | |
| 	icon: 'ti ti-list',
 | |
| 	title: i18n.ts.lists,
 | |
| 	iconOnly: true,
 | |
| 	onClick: chooseList,
 | |
| }, {
 | |
| 	icon: 'ti ti-antenna',
 | |
| 	title: i18n.ts.antennas,
 | |
| 	iconOnly: true,
 | |
| 	onClick: chooseAntenna,
 | |
| }, {
 | |
| 	icon: 'ti ti-device-tv',
 | |
| 	title: i18n.ts.channel,
 | |
| 	iconOnly: true,
 | |
| 	onClick: chooseChannel,
 | |
| }]);
 | |
| 
 | |
| definePageMetadata(computed(() => ({
 | |
| 	title: i18n.ts.timeline,
 | |
| 	icon: src === 'local' ? 'ti ti-planet' : src === 'social' ? 'ti ti-rocket' : src === 'global' ? 'ti ti-whirl' : 'ti ti-home',
 | |
| })));
 | |
| </script>
 | |
| 
 | |
| <style lang="scss" module>
 | |
| .new {
 | |
| 	position: sticky;
 | |
| 	top: calc(var(--stickyTop, 0px) + 16px);
 | |
| 	z-index: 1000;
 | |
| 	width: 100%;
 | |
| 
 | |
| 	> button {
 | |
| 		display: block;
 | |
| 		margin: var(--margin) auto 0 auto;
 | |
| 		padding: 8px 16px;
 | |
| 		border-radius: 32px;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| .postForm {
 | |
| 	border-radius: var(--radius);
 | |
| }
 | |
| 
 | |
| .tl {
 | |
| 	background: var(--bg);
 | |
| 	border-radius: var(--radius);
 | |
| 	overflow: clip;
 | |
| }
 | |
| </style>
 | 
