diff options
| author | かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com> | 2024-05-27 17:15:11 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-05-27 17:15:11 +0900 |
| commit | 3ffbf6296f44c6f8837f0b8533a3b60b64403bf9 (patch) | |
| tree | 8c550ce179f61238f2a0beaf0209bdcce2799ba5 /packages/frontend/src | |
| parent | fix(backend): `read:admin:show-user` と `read:admin:show-users` を統合 (#... (diff) | |
| download | misskey-3ffbf6296f44c6f8837f0b8533a3b60b64403bf9.tar.gz misskey-3ffbf6296f44c6f8837f0b8533a3b60b64403bf9.tar.bz2 misskey-3ffbf6296f44c6f8837f0b8533a3b60b64403bf9.zip | |
feat: 個別のお知らせにリンクで飛べるように (#13885)
* feat(announcement): 個別のお知らせにリンクで飛べるように (MisskeyIO#639)
(cherry picked from commit f6bf7f992a78e54d86a4701dbd1e4fda7ef4eb27)
* fix
Co-authored-by: まっちゃとーにゅ <17376330+u1-liquid@users.noreply.github.com>
* fix
Co-authored-by: まっちゃとーにゅ <17376330+u1-liquid@users.noreply.github.com>
* 一覧ページではお知らせpanel全体を押せるように
* お知らせバーは個別ページに飛ばすように
* Update Changelog
* spdx
* attempt to fox test
* remove unnecessary thong
* `announcement` → `announcements/show`
* リンクを押せる場所をタイトルと日付部分のみに変更
---------
Co-authored-by: まっちゃとーにゅ <17376330+u1-liquid@users.noreply.github.com>
Diffstat (limited to 'packages/frontend/src')
| -rw-r--r-- | packages/frontend/src/pages/announcement.vue | 142 | ||||
| -rw-r--r-- | packages/frontend/src/pages/announcements.vue | 25 | ||||
| -rw-r--r-- | packages/frontend/src/router/definition.ts | 3 | ||||
| -rw-r--r-- | packages/frontend/src/ui/_common_/announcements.vue | 2 |
4 files changed, 161 insertions, 11 deletions
diff --git a/packages/frontend/src/pages/announcement.vue b/packages/frontend/src/pages/announcement.vue new file mode 100644 index 0000000000..85ae9062d4 --- /dev/null +++ b/packages/frontend/src/pages/announcement.vue @@ -0,0 +1,142 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<MkStickyContainer> + <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template> + <MkSpacer :contentMax="800"> + <Transition + :enterActiveClass="defaultStore.state.animation ? $style.fadeEnterActive : ''" + :leaveActiveClass="defaultStore.state.animation ? $style.fadeLeaveActive : ''" + :enterFromClass="defaultStore.state.animation ? $style.fadeEnterFrom : ''" + :leaveToClass="defaultStore.state.animation ? $style.fadeLeaveTo : ''" + mode="out-in" + > + <div v-if="announcement" :key="announcement.id" class="_panel" :class="$style.announcement"> + <div v-if="announcement.forYou" :class="$style.forYou"><i class="ti ti-pin"></i> {{ i18n.ts.forYou }}</div> + <div :class="$style.header"> + <span v-if="$i && !announcement.silence && !announcement.isRead" style="margin-right: 0.5em;">🆕</span> + <span style="margin-right: 0.5em;"> + <i v-if="announcement.icon === 'info'" class="ti ti-info-circle"></i> + <i v-else-if="announcement.icon === 'warning'" class="ti ti-alert-triangle" style="color: var(--warn);"></i> + <i v-else-if="announcement.icon === 'error'" class="ti ti-circle-x" style="color: var(--error);"></i> + <i v-else-if="announcement.icon === 'success'" class="ti ti-check" style="color: var(--success);"></i> + </span> + <Mfm :text="announcement.title"/> + </div> + <div :class="$style.content"> + <Mfm :text="announcement.text"/> + <img v-if="announcement.imageUrl" :src="announcement.imageUrl"/> + <div style="margin-top: 8px; opacity: 0.7; font-size: 85%;"> + {{ i18n.ts.createdAt }}: <MkTime :time="announcement.createdAt" mode="detail"/> + </div> + <div v-if="announcement.updatedAt" style="opacity: 0.7; font-size: 85%;"> + {{ i18n.ts.updatedAt }}: <MkTime :time="announcement.updatedAt" mode="detail"/> + </div> + </div> + <div v-if="$i && !announcement.silence && !announcement.isRead" :class="$style.footer"> + <MkButton primary @click="read(announcement)"><i class="ti ti-check"></i> {{ i18n.ts.gotIt }}</MkButton> + </div> + </div> + <MkError v-else-if="error" @retry="fetch()"/> + <MkLoading v-else/> + </Transition> + </MkSpacer> +</MkStickyContainer> +</template> + +<script lang="ts" setup> +import { ref, computed, watch } from 'vue'; +import * as Misskey from 'misskey-js'; +import MkButton from '@/components/MkButton.vue'; +import * as os from '@/os.js'; +import { misskeyApi } from '@/scripts/misskey-api.js'; +import { i18n } from '@/i18n.js'; +import { definePageMetadata } from '@/scripts/page-metadata.js'; +import { $i, updateAccount } from '@/account.js'; +import { defaultStore } from '@/store.js'; + +const props = defineProps<{ + announcementId: string; +}>(); + +const announcement = ref<Misskey.entities.Announcement | null>(null); +const error = ref<any>(null); +const path = computed(() => props.announcementId); + +function fetch() { + announcement.value = null; + misskeyApi('announcements/show', { + announcementId: props.announcementId, + }).then(async _announcement => { + announcement.value = _announcement; + }).catch(err => { + error.value = err; + }); +} + +async function read(target: Misskey.entities.Announcement): Promise<void> { + if (target.needConfirmationToRead) { + const confirm = await os.confirm({ + type: 'question', + title: i18n.ts._announcement.readConfirmTitle, + text: i18n.tsx._announcement.readConfirmText({ title: target.title }), + }); + if (confirm.canceled) return; + } + + target.isRead = true; + await misskeyApi('i/read-announcement', { announcementId: target.id }); + if ($i) { + updateAccount({ + unreadAnnouncements: $i.unreadAnnouncements.filter((a: { id: string; }) => a.id !== target.id), + }); + } +} + +watch(() => path.value, fetch, { immediate: true }); + +const headerActions = computed(() => []); + +const headerTabs = computed(() => []); + +definePageMetadata(() => ({ + title: announcement.value ? `${i18n.ts.announcements}: ${announcement.value.title}` : i18n.ts.announcements, + icon: 'ti ti-speakerphone', +})); +</script> + +<style lang="scss" module> +.announcement { + padding: 16px; +} + +.forYou { + display: flex; + align-items: center; + line-height: 24px; + font-size: 90%; + white-space: pre; + color: #d28a3f; +} + +.header { + margin-bottom: 16px; + font-weight: bold; + font-size: 120%; +} + +.content { + > img { + display: block; + max-height: 300px; + max-width: 100%; + } +} + +.footer { + margin-top: 16px; +} +</style> diff --git a/packages/frontend/src/pages/announcements.vue b/packages/frontend/src/pages/announcements.vue index bcd6eb7c0f..e50b208775 100644 --- a/packages/frontend/src/pages/announcements.vue +++ b/packages/frontend/src/pages/announcements.vue @@ -21,14 +21,19 @@ SPDX-License-Identifier: AGPL-3.0-only <i v-else-if="announcement.icon === 'error'" class="ti ti-circle-x" style="color: var(--error);"></i> <i v-else-if="announcement.icon === 'success'" class="ti ti-check" style="color: var(--success);"></i> </span> - <span>{{ announcement.title }}</span> + <MkA :to="`/announcements/${announcement.id}`"><span>{{ announcement.title }}</span></MkA> </div> <div :class="$style.content"> <Mfm :text="announcement.text"/> <img v-if="announcement.imageUrl" :src="announcement.imageUrl"/> - <div style="opacity: 0.7; font-size: 85%;"> - <MkTime :time="announcement.updatedAt ?? announcement.createdAt" mode="detail"/> - </div> + <MkA :to="`/announcements/${announcement.id}`"> + <div style="margin-top: 8px; opacity: 0.7; font-size: 85%;"> + {{ i18n.ts.createdAt }}: <MkTime :time="announcement.createdAt" mode="detail"/> + </div> + <div v-if="announcement.updatedAt" style="opacity: 0.7; font-size: 85%;"> + {{ i18n.ts.updatedAt }}: <MkTime :time="announcement.updatedAt" mode="detail"/> + </div> + </MkA> </div> <div v-if="tab !== 'past' && $i && !announcement.silence && !announcement.isRead" :class="$style.footer"> <MkButton primary @click="read(announcement)"><i class="ti ti-check"></i> {{ i18n.ts.gotIt }}</MkButton> @@ -73,24 +78,24 @@ const paginationEl = ref<InstanceType<typeof MkPagination>>(); const tab = ref('current'); -async function read(announcement) { - if (announcement.needConfirmationToRead) { +async function read(target) { + if (target.needConfirmationToRead) { const confirm = await os.confirm({ type: 'question', title: i18n.ts._announcement.readConfirmTitle, - text: i18n.tsx._announcement.readConfirmText({ title: announcement.title }), + text: i18n.tsx._announcement.readConfirmText({ title: target.title }), }); if (confirm.canceled) return; } if (!paginationEl.value) return; - paginationEl.value.updateItem(announcement.id, a => { + paginationEl.value.updateItem(target.id, a => { a.isRead = true; return a; }); - misskeyApi('i/read-announcement', { announcementId: announcement.id }); + misskeyApi('i/read-announcement', { announcementId: target.id }); updateAccount({ - unreadAnnouncements: $i!.unreadAnnouncements.filter(a => a.id !== announcement.id), + unreadAnnouncements: $i!.unreadAnnouncements.filter(a => a.id !== target.id), }); } diff --git a/packages/frontend/src/router/definition.ts b/packages/frontend/src/router/definition.ts index c5b576f505..c12ae0fa57 100644 --- a/packages/frontend/src/router/definition.ts +++ b/packages/frontend/src/router/definition.ts @@ -194,6 +194,9 @@ const routes: RouteDef[] = [{ path: '/announcements', component: page(() => import('@/pages/announcements.vue')), }, { + path: '/announcements/:announcementId', + component: page(() => import('@/pages/announcement.vue')), +}, { path: '/about', component: page(() => import('@/pages/about.vue')), hash: 'initialTab', diff --git a/packages/frontend/src/ui/_common_/announcements.vue b/packages/frontend/src/ui/_common_/announcements.vue index 362c29e6c2..374bc20b54 100644 --- a/packages/frontend/src/ui/_common_/announcements.vue +++ b/packages/frontend/src/ui/_common_/announcements.vue @@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only v-for="announcement in $i.unreadAnnouncements.filter(x => x.display === 'banner')" :key="announcement.id" :class="$style.item" - to="/announcements" + :to="`/announcements/${announcement.id}`" > <span :class="$style.icon"> <i v-if="announcement.icon === 'info'" class="ti ti-info-circle"></i> |