summaryrefslogtreecommitdiff
path: root/packages/frontend/src
diff options
context:
space:
mode:
authorかっこかり <67428053+kakkokari-gtyih@users.noreply.github.com>2024-05-27 17:15:11 +0900
committerGitHub <noreply@github.com>2024-05-27 17:15:11 +0900
commit3ffbf6296f44c6f8837f0b8533a3b60b64403bf9 (patch)
tree8c550ce179f61238f2a0beaf0209bdcce2799ba5 /packages/frontend/src
parentfix(backend): `read:admin:show-user` と `read:admin:show-users` を統合 (#... (diff)
downloadmisskey-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.vue142
-rw-r--r--packages/frontend/src/pages/announcements.vue25
-rw-r--r--packages/frontend/src/router/definition.ts3
-rw-r--r--packages/frontend/src/ui/_common_/announcements.vue2
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>