diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2023-08-13 20:12:29 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-08-13 20:12:29 +0900 |
| commit | 948785649540e08c1610b1dcce6b37e99b5e8039 (patch) | |
| tree | f5e57a0ecca79c8fb244b4c6af53be8e4b7a53fd /packages/frontend/src/components | |
| parent | fix(frontend/MkUrlPreview): allow fullscreen from tweets (#11712) (diff) | |
| download | misskey-948785649540e08c1610b1dcce6b37e99b5e8039.tar.gz misskey-948785649540e08c1610b1dcce6b37e99b5e8039.tar.bz2 misskey-948785649540e08c1610b1dcce6b37e99b5e8039.zip | |
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
Diffstat (limited to 'packages/frontend/src/components')
7 files changed, 310 insertions, 9 deletions
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: '<MkAnnouncementDialog v-bind="props" />', + }; + }, + 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<typeof MkAnnouncementDialog>; 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 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<MkModal ref="modal" :zPriority="'middle'" @closed="$emit('closed')" @click="onBgClick"> + <div ref="rootEl" :class="$style.root"> + <div :class="$style.header"> + <span :class="$style.icon"> + <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> + <span :class="$style.title">{{ announcement.title }}</span> + </div> + <div :class="$style.text"><Mfm :text="announcement.text"/></div> + <MkButton primary full @click="ok">{{ i18n.ts.ok }}</MkButton> + </div> +</MkModal> +</template> + +<script lang="ts" setup> +import { onMounted, shallowRef } from 'vue'; +import * as misskey from 'misskey-js'; +import * as os from '@/os'; +import MkModal from '@/components/MkModal.vue'; +import MkButton from '@/components/MkButton.vue'; +import { i18n } from '@/i18n'; +import { $i, updateAccount } from '@/account'; + +const props = withDefaults(defineProps<{ + announcement: misskey.entities.Announcement; +}>(), { +}); + +const rootEl = shallowRef<HTMLDivElement>(); +const modal = shallowRef<InstanceType<typeof MkModal>>(); + +async function ok() { + if (props.announcement.needConfirmationToRead) { + const confirm = await os.confirm({ + type: 'question', + title: i18n.ts._announcement.readConfirmTitle, + text: i18n.t('_announcement.readConfirmText', { title: props.announcement.title }), + }); + if (confirm.canceled) return; + } + + modal.value.close(); + os.api('i/read-announcement', { announcementId: props.announcement.id }); + updateAccount({ + unreadAnnouncements: $i!.unreadAnnouncements.filter(a => a.id !== props.announcement.id), + }); +} + +function onBgClick() { + rootEl.value.animate([{ + offset: 0, + transform: 'scale(1)', + }, { + offset: 0.5, + transform: 'scale(1.1)', + }, { + offset: 1, + transform: 'scale(1)', + }], { + duration: 100, + }); +} + +onMounted(() => { +}); +</script> + +<style lang="scss" module> +.root { + margin: auto; + position: relative; + padding: 32px; + min-width: 320px; + max-width: 480px; + box-sizing: border-box; + background: var(--panel); + border-radius: var(--radius); +} + +.header { + font-size: 120%; +} + +.icon { + margin-right: 0.5em; +} + +.title { + font-weight: bold; +} + +.text { + margin: 1em 0; +} +</style> 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; }>(); </script> 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" > - <XButton :checked="checked" :disabled="disabled" @toggle="toggle" /> + <XButton :checked="checked" :disabled="disabled" @toggle="toggle"/> <span :class="$style.body"> <!-- TODO: 無名slotの方は廃止 --> - <span :class="$style.label" @click="toggle"><slot name="label"></slot><slot></slot></span> + <span :class="$style.label"> + <span @click="toggle"> + <slot name="label"></slot><slot></slot> + </span> + <span v-if="helpText" v-tooltip:dialog="helpText" class="_button _help" :class="$style.help"><i class="ti ti-help-circle"></i></span> + </span> <p :class="$style.caption"><slot name="caption"></slot></p> </span> </div> @@ -28,6 +33,7 @@ import XButton from '@/components/MkSwitch.button.vue'; const props = defineProps<{ modelValue: boolean | Ref<boolean>; 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) { - - } }; </script> @@ -98,4 +100,10 @@ const toggle = () => { display: none; } } + +.help { + margin-left: 0.5em; + font-size: 85%; + vertical-align: top; +} </style> 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 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<MkModalWindow + ref="dialog" + :width="400" + @close="dialog.close()" + @closed="$emit('closed')" +> + <template v-if="announcement" #header>:{{ announcement.title }}:</template> + <template v-else #header>New announcement</template> + + <div> + <MkSpacer :marginMin="20" :marginMax="28"> + <div class="_gaps_m"> + <MkInput v-model="title"> + <template #label>{{ i18n.ts.title }}</template> + </MkInput> + <MkTextarea v-model="text"> + <template #label>{{ i18n.ts.text }}</template> + </MkTextarea> + <MkRadios v-model="icon"> + <template #label>{{ i18n.ts.icon }}</template> + <option value="info"><i class="ti ti-info-circle"></i></option> + <option value="warning"><i class="ti ti-alert-triangle" style="color: var(--warn);"></i></option> + <option value="error"><i class="ti ti-circle-x" style="color: var(--error);"></i></option> + <option value="success"><i class="ti ti-check" style="color: var(--success);"></i></option> + </MkRadios> + <MkRadios v-model="display"> + <template #label>{{ i18n.ts.display }}</template> + <option value="normal">{{ i18n.ts.normal }}</option> + <option value="banner">{{ i18n.ts.banner }}</option> + <option value="dialog">{{ i18n.ts.dialog }}</option> + </MkRadios> + <MkSwitch v-model="needConfirmationToRead"> + {{ i18n.ts._announcement.needConfirmationToRead }} + <template #caption>{{ i18n.ts._announcement.needConfirmationToReadDescription }}</template> + </MkSwitch> + <MkButton v-if="announcement" danger @click="del()"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton> + </div> + </MkSpacer> + <div :class="$style.footer"> + <MkButton primary rounded style="margin: 0 auto;" @click="done"><i class="ti ti-check"></i> {{ props.announcement ? i18n.ts.update : i18n.ts.create }}</MkButton> + </div> + </div> +</MkModalWindow> +</template> + +<script lang="ts" setup> +import { } from 'vue'; +import * as misskey from 'misskey-js'; +import MkModalWindow from '@/components/MkModalWindow.vue'; +import MkButton from '@/components/MkButton.vue'; +import MkInput from '@/components/MkInput.vue'; +import * as os from '@/os'; +import { i18n } from '@/i18n'; +import MkTextarea from '@/components/MkTextarea.vue'; +import MkSwitch from '@/components/MkSwitch.vue'; +import MkRadios from '@/components/MkRadios.vue'; + +const props = defineProps<{ + user: misskey.entities.User, + announcement?: any, +}>(); + +let dialog = $ref(null); +let title: string = $ref(props.announcement ? props.announcement.title : ''); +let text: string = $ref(props.announcement ? props.announcement.text : ''); +let icon: string = $ref(props.announcement ? props.announcement.icon : 'info'); +let display: string = $ref(props.announcement ? props.announcement.display : 'dialog'); +let needConfirmationToRead = $ref(props.announcement ? props.announcement.needConfirmationToRead : false); + +const emit = defineEmits<{ + (ev: 'done', v: { deleted?: boolean; updated?: any; created?: any }): void, + (ev: 'closed'): void +}>(); + +async function done() { + const params = { + title: title, + text: text, + icon: icon, + imageUrl: null, + display: display, + needConfirmationToRead: needConfirmationToRead, + userId: props.user.id, + }; + + if (props.announcement) { + await os.apiWithDialog('admin/announcements/update', { + id: props.announcement.id, + ...params, + }); + + emit('done', { + updated: { + id: props.announcement.id, + ...params, + }, + }); + + dialog.close(); + } else { + const created = await os.apiWithDialog('admin/announcements/create', params); + + emit('done', { + created: created, + }); + + dialog.close(); + } +} + +async function del() { + const { canceled } = await os.confirm({ + type: 'warning', + text: i18n.t('removeAreYouSure', { x: title }), + }); + if (canceled) return; + + os.api('admin/announcements/delete', { + id: props.announcement.id, + }).then(() => { + emit('done', { + deleted: true, + }); + dialog.close(); + }); +} +</script> + +<style lang="scss" module> +.footer { + position: sticky; + bottom: 0; + left: 0; + padding: 12px; + border-top: solid 0.5px var(--divider); + -webkit-backdrop-filter: var(--blur, blur(15px)); + backdrop-filter: var(--blur, blur(15px)); +} +</style> |