From 948785649540e08c1610b1dcce6b37e99b5e8039 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 13 Aug 2023 20:12:29 +0900 Subject: feat: refine announcement (#11497) * wip * Update read-announcement.ts * wip * wip * wip * Update index.d.ts * wip * Create 1691649257651-refine-announcement.js * wip * wip * wip * wip * wip * wip * Update announcements.vue * wip * wip * Update announcements.vue * wip * Update announcements.vue * wip * Update misskey-js.api.md * Update users.ts * Create MkAnnouncementDialog.stories.impl.ts * wip * wip * Create AnnouncementService.ts --- packages/frontend/src/account.ts | 5 +- packages/frontend/src/boot/main-boot.ts | 15 +++ .../MkAnnouncementDialog.stories.impl.ts | 47 +++++++ .../src/components/MkAnnouncementDialog.vue | 104 +++++++++++++++ .../frontend/src/components/MkNoteDetailed.vue | 1 - packages/frontend/src/components/MkNoteHeader.vue | 1 - packages/frontend/src/components/MkNoteSimple.vue | 1 - packages/frontend/src/components/MkSwitch.vue | 20 ++- .../components/MkUserAnnouncementEditDialog.vue | 145 +++++++++++++++++++++ .../frontend/src/pages/admin/announcements.vue | 92 ++++++++----- packages/frontend/src/pages/announcements.vue | 141 ++++++++++++++------ packages/frontend/src/pages/emoji-edit-dialog.vue | 2 +- packages/frontend/src/pages/gallery/index.vue | 2 +- packages/frontend/src/pages/user-info.vue | 55 +++++++- .../frontend/src/ui/_common_/announcements.vue | 80 ++++++++++++ packages/frontend/src/ui/deck.vue | 2 + packages/frontend/src/ui/universal.vue | 8 +- 17 files changed, 631 insertions(+), 90 deletions(-) create mode 100644 packages/frontend/src/components/MkAnnouncementDialog.stories.impl.ts create mode 100644 packages/frontend/src/components/MkAnnouncementDialog.vue create mode 100644 packages/frontend/src/components/MkUserAnnouncementEditDialog.vue create mode 100644 packages/frontend/src/ui/_common_/announcements.vue (limited to 'packages/frontend/src') diff --git a/packages/frontend/src/account.ts b/packages/frontend/src/account.ts index c3f3337c9d..634084c750 100644 --- a/packages/frontend/src/account.ts +++ b/packages/frontend/src/account.ts @@ -96,7 +96,6 @@ export async function removeAccount(idOrToken: Account['id']) { function fetchAccount(token: string, id?: string, forceShowDialog?: boolean): Promise { return new Promise((done, fail) => { - // Fetch user window.fetch(`${apiUrl}/i`, { method: 'POST', body: JSON.stringify({ @@ -108,8 +107,8 @@ function fetchAccount(token: string, id?: string, forceShowDialog?: boolean): Pr }) .then(res => new Promise }>((done2, fail2) => { if (res.status >= 500 && res.status < 600) { - // サーバーエラー(5xx)の場合をrejectとする - // (認証エラーなど4xxはresolve) + // サーバーエラー(5xx)の場合をrejectとする + // (認証エラーなど4xxはresolve) return fail2(res); } res.json().then(done2, fail2); diff --git a/packages/frontend/src/boot/main-boot.ts b/packages/frontend/src/boot/main-boot.ts index 7459ea0fa5..9ab1f6e14c 100644 --- a/packages/frontend/src/boot/main-boot.ts +++ b/packages/frontend/src/boot/main-boot.ts @@ -83,6 +83,21 @@ export async function mainBoot() { } }); + for (const announcement of ($i.unreadAnnouncements ?? []).filter(x => x.display === 'dialog')) { + popup(defineAsyncComponent(() => import('@/components/MkAnnouncementDialog.vue')), { + announcement, + }, {}, 'closed'); + } + + stream.on('announcementCreated', (ev) => { + const announcement = ev.announcement; + if (announcement.display === 'dialog') { + popup(defineAsyncComponent(() => import('@/components/MkAnnouncementDialog.vue')), { + announcement, + }, {}, 'closed'); + } + }); + if ($i.isDeleted) { alert({ type: 'warning', diff --git a/packages/frontend/src/components/MkAnnouncementDialog.stories.impl.ts b/packages/frontend/src/components/MkAnnouncementDialog.stories.impl.ts new file mode 100644 index 0000000000..42cfb90f7c --- /dev/null +++ b/packages/frontend/src/components/MkAnnouncementDialog.stories.impl.ts @@ -0,0 +1,47 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import { StoryObj } from '@storybook/vue3'; +import MkAnnouncementDialog from './MkAnnouncementDialog.vue'; +export const Default = { + render(args) { + return { + components: { + MkAnnouncementDialog, + }, + setup() { + return { + args, + }; + }, + computed: { + props() { + return { + ...this.args, + }; + }, + }, + template: '', + }; + }, + args: { + announcement: { + id: '1', + title: 'Title', + text: 'Text', + createdAt: new Date().toISOString(), + updatedAt: null, + icon: 'info', + imageUrl: null, + display: 'dialog', + needConfirmationToRead: false, + forYou: true, + }, + }, + parameters: { + layout: 'centered', + }, +} satisfies StoryObj; diff --git a/packages/frontend/src/components/MkAnnouncementDialog.vue b/packages/frontend/src/components/MkAnnouncementDialog.vue new file mode 100644 index 0000000000..8e11053813 --- /dev/null +++ b/packages/frontend/src/components/MkAnnouncementDialog.vue @@ -0,0 +1,104 @@ + + + + + + + diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue index 8acd4e4707..2a00b8db8e 100644 --- a/packages/frontend/src/components/MkNoteDetailed.vue +++ b/packages/frontend/src/components/MkNoteDetailed.vue @@ -172,7 +172,6 @@ import { showMovedDialog } from '@/scripts/show-moved-dialog'; const props = defineProps<{ note: misskey.entities.Note; - pinned?: boolean; }>(); const inChannel = inject('inChannel', null); diff --git a/packages/frontend/src/components/MkNoteHeader.vue b/packages/frontend/src/components/MkNoteHeader.vue index 559133ef46..fcde45405e 100644 --- a/packages/frontend/src/components/MkNoteHeader.vue +++ b/packages/frontend/src/components/MkNoteHeader.vue @@ -37,7 +37,6 @@ import { userPage } from '@/filters/user'; defineProps<{ note: misskey.entities.Note; - pinned?: boolean; }>(); diff --git a/packages/frontend/src/components/MkNoteSimple.vue b/packages/frontend/src/components/MkNoteSimple.vue index 9648b7230a..e3c9e2bd5f 100644 --- a/packages/frontend/src/components/MkNoteSimple.vue +++ b/packages/frontend/src/components/MkNoteSimple.vue @@ -31,7 +31,6 @@ import { $i } from '@/account'; const props = defineProps<{ note: misskey.entities.Note; - pinned?: boolean; }>(); const showContent = $ref(false); diff --git a/packages/frontend/src/components/MkSwitch.vue b/packages/frontend/src/components/MkSwitch.vue index 96e2bad49f..8e946e7437 100644 --- a/packages/frontend/src/components/MkSwitch.vue +++ b/packages/frontend/src/components/MkSwitch.vue @@ -12,10 +12,15 @@ SPDX-License-Identifier: AGPL-3.0-only :class="$style.input" @keydown.enter="toggle" > - + - + + + + + +

@@ -28,6 +33,7 @@ import XButton from '@/components/MkSwitch.button.vue'; const props = defineProps<{ modelValue: boolean | Ref; disabled?: boolean; + helpText?: string; }>(); const emit = defineEmits<{ @@ -38,10 +44,6 @@ const checked = toRefs(props).modelValue; const toggle = () => { if (props.disabled) return; emit('update:modelValue', !checked.value); - - if (!checked.value) { - - } }; @@ -98,4 +100,10 @@ const toggle = () => { display: none; } } + +.help { + margin-left: 0.5em; + font-size: 85%; + vertical-align: top; +} diff --git a/packages/frontend/src/components/MkUserAnnouncementEditDialog.vue b/packages/frontend/src/components/MkUserAnnouncementEditDialog.vue new file mode 100644 index 0000000000..ce41b3116d --- /dev/null +++ b/packages/frontend/src/components/MkUserAnnouncementEditDialog.vue @@ -0,0 +1,145 @@ + + + + + + + diff --git a/packages/frontend/src/pages/admin/announcements.vue b/packages/frontend/src/pages/admin/announcements.vue index 2e80d028f7..bb903af459 100644 --- a/packages/frontend/src/pages/admin/announcements.vue +++ b/packages/frontend/src/pages/admin/announcements.vue @@ -7,9 +7,20 @@ SPDX-License-Identifier: AGPL-3.0-only -
-
-
+
+ {{ i18n.ts._announcement.tooManyActiveAnnouncementDescription }} + + + + + + +
@@ -19,13 +30,33 @@ SPDX-License-Identifier: AGPL-3.0-only + + + + + + + + + + + + + + + {{ i18n.ts._announcement.forExistingUsers }} + + + {{ i18n.ts._announcement.needConfirmationToRead }} +

{{ i18n.t('nUsersRead', { n: announcement.reads }) }}

{{ i18n.ts.save }} - {{ i18n.ts.remove }} + {{ i18n.ts._announcement.end }} ({{ i18n.ts.archive }}) + {{ i18n.ts.delete }}
-
+
@@ -37,9 +68,13 @@ import XHeader from './_header_.vue'; import MkButton from '@/components/MkButton.vue'; import MkInput from '@/components/MkInput.vue'; import MkTextarea from '@/components/MkTextarea.vue'; +import MkSwitch from '@/components/MkSwitch.vue'; +import MkRadios from '@/components/MkRadios.vue'; +import MkInfo from '@/components/MkInfo.vue'; import * as os from '@/os'; import { i18n } from '@/i18n'; import { definePageMetadata } from '@/scripts/page-metadata'; +import MkFolder from '@/components/MkFolder.vue'; let announcements: any[] = $ref([]); @@ -49,17 +84,22 @@ os.api('admin/announcements/list').then(announcementResponse => { function add() { announcements.unshift({ + _id: Math.random().toString(36), id: null, - title: '', + title: 'New announcement', text: '', imageUrl: null, + icon: 'info', + display: 'normal', + forExistingUsers: false, + needConfirmationToRead: false, }); } -function remove(announcement) { +function del(announcement) { os.confirm({ type: 'warning', - text: i18n.t('removeAreYouSure', { x: announcement.title }), + text: i18n.t('deleteAreYouSure', { x: announcement.title }), }).then(({ canceled }) => { if (canceled) return; announcements = announcements.filter(x => x !== announcement); @@ -67,32 +107,20 @@ function remove(announcement) { }); } -function save(announcement) { +async function archive(announcement) { + await os.apiWithDialog('admin/announcements/update', { + ...announcement, + isActive: false, + }); + refresh(); +} + +async function save(announcement) { if (announcement.id == null) { - os.api('admin/announcements/create', announcement).then(() => { - os.alert({ - type: 'success', - text: i18n.ts.saved, - }); - refresh(); - }).catch(err => { - os.alert({ - type: 'error', - text: err, - }); - }); + await os.apiWithDialog('admin/announcements/create', announcement); + refresh(); } else { - os.api('admin/announcements/update', announcement).then(() => { - os.alert({ - type: 'success', - text: i18n.ts.saved, - }); - }).catch(err => { - os.alert({ - type: 'error', - text: err, - }); - }); + os.apiWithDialog('admin/announcements/update', announcement); } } diff --git a/packages/frontend/src/pages/announcements.vue b/packages/frontend/src/pages/announcements.vue index dd166d3c51..9937eb4cbf 100644 --- a/packages/frontend/src/pages/announcements.vue +++ b/packages/frontend/src/pages/announcements.vue @@ -5,20 +5,36 @@ SPDX-License-Identifier: AGPL-3.0-only @@ -27,30 +43,64 @@ SPDX-License-Identifier: AGPL-3.0-only import { ref } from 'vue'; import MkPagination from '@/components/MkPagination.vue'; import MkButton from '@/components/MkButton.vue'; +import MkInfo from '@/components/MkInfo.vue'; import * as os from '@/os'; import { i18n } from '@/i18n'; import { definePageMetadata } from '@/scripts/page-metadata'; -import { $i } from '@/account'; +import { $i, updateAccount } from '@/account'; -const pagination = { +const paginationCurrent = { endpoint: 'announcements' as const, limit: 10, + params: { + isActive: true, + }, +}; + +const paginationPast = { + endpoint: 'announcements' as const, + limit: 10, + params: { + isActive: false, + }, }; const paginationEl = ref>(); -function read(id: string) { +const tab = ref('current'); + +async function read(announcement) { + if (announcement.needConfirmationToRead) { + const confirm = await os.confirm({ + type: 'question', + title: i18n.ts._announcement.readConfirmTitle, + text: i18n.t('_announcement.readConfirmText', { title: announcement.title }), + }); + if (confirm.canceled) return; + } + if (!paginationEl.value) return; - paginationEl.value.updateItem(id, announcement => { - announcement.isRead = true; - return announcement; + paginationEl.value.updateItem(announcement.id, a => { + a.isRead = true; + return a; + }); + os.api('i/read-announcement', { announcementId: announcement.id }); + updateAccount({ + unreadAnnouncements: $i!.unreadAnnouncements.filter(a => a.id !== announcement.id), }); - os.api('i/read-announcement', { announcementId: id }); } const headerActions = $computed(() => []); -const headerTabs = $computed(() => []); +const headerTabs = $computed(() => [{ + key: 'current', + title: i18n.ts.currentAnnouncements, + icon: 'ti ti-flare', +}, { + key: 'past', + title: i18n.ts.pastAnnouncements, + icon: 'ti ti-point', +}]); definePageMetadata({ title: i18n.ts.announcements, @@ -58,27 +108,34 @@ definePageMetadata({ }); - diff --git a/packages/frontend/src/pages/emoji-edit-dialog.vue b/packages/frontend/src/pages/emoji-edit-dialog.vue index 19c63f8000..7e91805a12 100644 --- a/packages/frontend/src/pages/emoji-edit-dialog.vue +++ b/packages/frontend/src/pages/emoji-edit-dialog.vue @@ -63,7 +63,7 @@ SPDX-License-Identifier: AGPL-3.0-only isSensitive {{ i18n.ts.localOnly }} - {{ i18n.ts.delete }} + {{ i18n.ts.delete }}
diff --git a/packages/frontend/src/pages/gallery/index.vue b/packages/frontend/src/pages/gallery/index.vue index e7c4ae88b5..af0ac2191b 100644 --- a/packages/frontend/src/pages/gallery/index.vue +++ b/packages/frontend/src/pages/gallery/index.vue @@ -86,7 +86,7 @@ const tagUsersPagination = $computed(() => ({ endpoint: 'hashtags/users' as const, limit: 30, params: { - tag: this.tag, + tag: props.tag, origin: 'combined', sort: '+follower', }, diff --git a/packages/frontend/src/pages/user-info.vue b/packages/frontend/src/pages/user-info.vue index 42b561cfe2..35cd116fad 100644 --- a/packages/frontend/src/pages/user-info.vue +++ b/packages/frontend/src/pages/user-info.vue @@ -132,6 +132,31 @@ SPDX-License-Identifier: AGPL-3.0-only
+ + + +
+ {{ i18n.ts.new }} + + + + +
+
+ @@ -185,7 +210,7 @@ SPDX-License-Identifier: AGPL-3.0-only + + diff --git a/packages/frontend/src/ui/deck.vue b/packages/frontend/src/ui/deck.vue index f047474741..3518891811 100644 --- a/packages/frontend/src/ui/deck.vue +++ b/packages/frontend/src/ui/deck.vue @@ -8,6 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
+
@@ -113,6 +114,7 @@ import XMentionsColumn from '@/ui/deck/mentions-column.vue'; import XDirectColumn from '@/ui/deck/direct-column.vue'; import XRoleTimelineColumn from '@/ui/deck/role-timeline-column.vue'; const XStatusBars = defineAsyncComponent(() => import('@/ui/_common_/statusbars.vue')); +const XAnnouncements = defineAsyncComponent(() => import('@/ui/_common_/announcements.vue')); const columnComponents = { main: XMainColumn, diff --git a/packages/frontend/src/ui/universal.vue b/packages/frontend/src/ui/universal.vue index 1c09df8394..d9cb81b5ef 100644 --- a/packages/frontend/src/ui/universal.vue +++ b/packages/frontend/src/ui/universal.vue @@ -8,7 +8,12 @@ SPDX-License-Identifier: AGPL-3.0-only - +
@@ -105,6 +110,7 @@ import { useScrollPositionManager } from '@/nirax'; const XWidgets = defineAsyncComponent(() => import('./universal.widgets.vue')); const XSidebar = defineAsyncComponent(() => import('@/ui/_common_/navbar.vue')); const XStatusBars = defineAsyncComponent(() => import('@/ui/_common_/statusbars.vue')); +const XAnnouncements = defineAsyncComponent(() => import('@/ui/_common_/announcements.vue')); const DESKTOP_THRESHOLD = 1100; const MOBILE_THRESHOLD = 500; -- cgit v1.2.3-freya