puyoskey/packages/frontend/src/pages/note.vue
dakkar 513a8e5de4 select note component in <setup>
this makes our templates much more similar to upstream's, making
merges simpler

changing note design is already marked as needing a reload, so having
non-reactive code that selects the note component is not a problem
2024-06-21 11:17:11 +01:00

199 lines
6.1 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!--
SPDX-FileCopyrightText: syuilo and misskey-project
SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<MkStickyContainer>
<template #header><MkPageHeader :actions="headerActions" :displayBackButton="true" :tabs="headerTabs"/></template>
<MkSpacer :contentMax="800">
<div>
<Transition :name="defaultStore.state.animation ? 'fade' : ''" mode="out-in">
<div v-if="note">
<div v-if="showNext" class="_margin">
<MkNotes class="" :pagination="showNext === 'channel' ? nextChannelPagination : nextUserPagination" :noGap="true" :disableAutoLoad="true"/>
</div>
<div class="_margin">
<div v-if="!showNext" class="_buttons" :class="$style.loadNext">
<MkButton v-if="note.channelId" rounded :class="$style.loadButton" @click="showNext = 'channel'"><i class="ph-caret-up ph-bold ph-lg"></i> <i class="ph-television-simple ph-bold ph-lg"></i></MkButton>
<MkButton rounded :class="$style.loadButton" @click="showNext = 'user'"><i class="ph-caret-up ph-bold ph-lg"></i> <i class="ph-user ph-bold ph-lg"></i></MkButton>
</div>
<div class="_margin _gaps_s">
<MkRemoteCaution v-if="note.user.host != null" :href="note.url ?? note.uri"/>
<MkNoteDetailed :key="note.id" v-model:note="note" :initialTab="initialTab" :class="$style.note" :expandAllCws="expandAllCws"/>
</div>
<div v-if="clips && clips.length > 0" class="_margin">
<div style="font-weight: bold; padding: 12px;">{{ i18n.ts.clip }}</div>
<div class="_gaps">
<MkClipPreview v-for="item in clips" :key="item.id" :clip="item"/>
</div>
</div>
<div v-if="!showPrev" class="_buttons" :class="$style.loadPrev">
<MkButton v-if="note.channelId" rounded :class="$style.loadButton" @click="showPrev = 'channel'"><i class="ph-caret-down ph-bold ph-lg"></i> <i class="ph-television-simple ph-bold ph-lg"></i></MkButton>
<MkButton rounded :class="$style.loadButton" @click="showPrev = 'user'"><i class="ph-caret-down ph-bold ph-lg"></i> <i class="ph-user ph-bold ph-lg"></i></MkButton>
</div>
</div>
<div v-if="showPrev" class="_margin">
<MkNotes class="" :pagination="showPrev === 'channel' ? prevChannelPagination : prevUserPagination" :noGap="true"/>
</div>
</div>
<MkError v-else-if="error" @retry="fetchNote()"/>
<MkLoading v-else/>
</Transition>
</div>
</MkSpacer>
</MkStickyContainer>
</template>
<script lang="ts" setup>
import { computed, watch, ref } from 'vue';
import * as Misskey from 'misskey-js';
import type { Paging } from '@/components/MkPagination.vue';
import MkNotes from '@/components/MkNotes.vue';
import MkRemoteCaution from '@/components/MkRemoteCaution.vue';
import MkButton from '@/components/MkButton.vue';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
import { i18n } from '@/i18n.js';
import { dateString } from '@/filters/date.js';
import MkClipPreview from '@/components/MkClipPreview.vue';
import { defaultStore } from '@/store.js';
const MkNoteDetailed = (
(defaultStore.state.noteDesign === 'misskey') ? (await import('@/components/MkNoteDetailed.vue')).default :
(defaultStore.state.noteDesign === 'sharkey') ? (await import('@/components/SkNoteDetailed.vue')).default :
null);
const props = defineProps<{
noteId: string;
initialTab?: string;
}>();
const note = ref<null | Misskey.entities.Note>();
const clips = ref<Misskey.entities.Clip[]>();
const showPrev = ref<'user' | 'channel' | false>(false);
const showNext = ref<'user' | 'channel' | false>(false);
const expandAllCws = ref(false);
const error = ref();
const prevUserPagination: Paging = {
endpoint: 'users/notes',
limit: 10,
params: computed(() => note.value ? ({
userId: note.value.userId,
untilId: note.value.id,
}) : undefined),
};
const nextUserPagination: Paging = {
reversed: true,
endpoint: 'users/notes',
limit: 10,
params: computed(() => note.value ? ({
userId: note.value.userId,
sinceId: note.value.id,
}) : undefined),
};
const prevChannelPagination: Paging = {
endpoint: 'channels/timeline',
limit: 10,
params: computed(() => note.value ? ({
channelId: note.value.channelId,
untilId: note.value.id,
}) : undefined),
};
const nextChannelPagination: Paging = {
reversed: true,
endpoint: 'channels/timeline',
limit: 10,
params: computed(() => note.value ? ({
channelId: note.value.channelId,
sinceId: note.value.id,
}) : undefined),
};
function fetchNote() {
showPrev.value = false;
showNext.value = false;
note.value = null;
misskeyApi('notes/show', {
noteId: props.noteId,
}).then(res => {
note.value = res;
// 古いートは被クリップ数をカウントしていないので、2023-10-01以前のものは強制的にnotes/clipsを叩く
if (note.value.clippedCount > 0 || new Date(note.value.createdAt).getTime() < new Date('2023-10-01').getTime()) {
misskeyApi('notes/clips', {
noteId: note.value.id,
}).then((_clips) => {
clips.value = _clips;
});
}
}).catch(err => {
error.value = err;
});
}
watch(() => props.noteId, fetchNote, {
immediate: true,
});
const headerActions = computed(() => note.value ? [
{
icon: `${expandAllCws.value ? 'ph-eye' : 'ph-eye-slash'} ph-bold ph-lg`,
text: expandAllCws.value ? i18n.ts.collapseAllCws : i18n.ts.expandAllCws,
handler: () => { expandAllCws.value = !expandAllCws.value; },
},
] : []);
const headerTabs = computed(() => []);
definePageMetadata(() => ({
title: i18n.ts.note,
...note.value ? {
subtitle: dateString(note.value.createdAt),
avatar: note.value.user,
path: `/notes/${note.value.id}`,
share: {
title: i18n.tsx.noteOf({ user: note.value.user.name }),
text: note.value.text,
},
} : {},
}));
</script>
<style lang="scss" module>
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.125s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
.loadNext,
.loadPrev {
justify-content: center;
}
.loadNext {
margin-bottom: var(--margin);
}
.loadPrev {
margin-top: var(--margin);
}
.loadButton {
min-width: 0;
}
.note {
border-radius: var(--radius);
background: var(--panel);
}
</style>