summaryrefslogtreecommitdiff
path: root/packages/frontend/src/pages/admin
diff options
context:
space:
mode:
authorかっこかり <67428053+kakkokari-gtyih@users.noreply.github.com>2024-07-30 13:02:03 +0900
committerGitHub <noreply@github.com>2024-07-30 13:02:03 +0900
commitde3ddb5b4403d02b8cb7671b9cf26e87ce9a2d17 (patch)
treea9578c25dda99adfcc59da234142049c7e39ba87 /packages/frontend/src/pages/admin
parentfix(backend): type(schema) of reactionAcceptance was wrong (#14317) (diff)
downloadmisskey-de3ddb5b4403d02b8cb7671b9cf26e87ce9a2d17.tar.gz
misskey-de3ddb5b4403d02b8cb7671b9cf26e87ce9a2d17.tar.bz2
misskey-de3ddb5b4403d02b8cb7671b9cf26e87ce9a2d17.zip
enhance: 管理画面でアーカイブにしたお知らせを表示・編集できるように (#14286)
* enhance: 管理画面でアーカイブにしたお知らせを表示できるように * Update Changelog
Diffstat (limited to 'packages/frontend/src/pages/admin')
-rw-r--r--packages/frontend/src/pages/admin/_header_.vue5
-rw-r--r--packages/frontend/src/pages/admin/announcements.vue160
2 files changed, 103 insertions, 62 deletions
diff --git a/packages/frontend/src/pages/admin/_header_.vue b/packages/frontend/src/pages/admin/_header_.vue
index c5a9609e6e..b88f078598 100644
--- a/packages/frontend/src/pages/admin/_header_.vue
+++ b/packages/frontend/src/pages/admin/_header_.vue
@@ -24,8 +24,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<div class="buttons right">
<template v-if="actions">
<template v-for="action in actions">
- <MkButton v-if="action.asFullButton" class="fullButton" primary @click.stop="action.handler"><i :class="action.icon" style="margin-right: 6px;"></i>{{ action.text }}</MkButton>
- <button v-else v-tooltip.noDelay="action.text" class="_button button" :class="{ highlighted: action.highlighted }" @click.stop="action.handler" @touchstart="preventDrag"><i :class="action.icon"></i></button>
+ <MkButton v-if="action.asFullButton" class="fullButton" primary :disabled="action.disabled" @click.stop="action.handler"><i :class="action.icon" style="margin-right: 6px;"></i>{{ action.text }}</MkButton>
+ <button v-else v-tooltip.noDelay="action.text" class="_button button" :class="{ highlighted: action.highlighted }" :disabled="action.disabled" @click.stop="action.handler" @touchstart="preventDrag"><i :class="action.icon"></i></button>
</template>
</template>
</div>
@@ -56,6 +56,7 @@ const props = defineProps<{
text: string;
icon: string;
asFullButton?: boolean;
+ disabled?: boolean;
handler: (ev: MouseEvent) => void;
}[];
thin?: boolean;
diff --git a/packages/frontend/src/pages/admin/announcements.vue b/packages/frontend/src/pages/admin/announcements.vue
index e7fb62ec1d..b9e09c8d03 100644
--- a/packages/frontend/src/pages/admin/announcements.vue
+++ b/packages/frontend/src/pages/admin/announcements.vue
@@ -11,70 +11,83 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkInfo>{{ i18n.ts._announcement.shouldNotBeUsedToPresentPermanentInfo }}</MkInfo>
<MkInfo v-if="announcements.length > 5" warn>{{ i18n.ts._announcement.tooManyActiveAnnouncementDescription }}</MkInfo>
- <MkFolder v-for="announcement in announcements" :key="announcement.id ?? announcement._id" :defaultOpen="announcement.id == null">
- <template #label>{{ announcement.title }}</template>
- <template #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>
- </template>
- <template #caption>{{ announcement.text }}</template>
+ <MkSelect v-model="announcementsStatus">
+ <template #label>{{ i18n.ts.filter }}</template>
+ <option value="active">{{ i18n.ts.active }}</option>
+ <option value="archived">{{ i18n.ts.archived }}</option>
+ </MkSelect>
- <div class="_gaps_m">
- <MkInput v-model="announcement.title">
- <template #label>{{ i18n.ts.title }}</template>
- </MkInput>
- <MkTextarea v-model="announcement.text" mfmAutocomplete :mfmPreview="true">
- <template #label>{{ i18n.ts.text }}</template>
- </MkTextarea>
- <MkInput v-model="announcement.imageUrl" type="url">
- <template #label>{{ i18n.ts.imageUrl }}</template>
- </MkInput>
- <MkRadios v-model="announcement.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="announcement.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>
- <MkInfo v-if="announcement.display === 'dialog'" warn>{{ i18n.ts._announcement.dialogAnnouncementUxWarn }}</MkInfo>
- <MkSwitch v-model="announcement.forExistingUsers" :helpText="i18n.ts._announcement.forExistingUsersDescription">
- {{ i18n.ts._announcement.forExistingUsers }}
- </MkSwitch>
- <MkSwitch v-model="announcement.silence" :helpText="i18n.ts._announcement.silenceDescription">
- {{ i18n.ts._announcement.silence }}
- </MkSwitch>
- <MkSwitch v-model="announcement.needConfirmationToRead" :helpText="i18n.ts._announcement.needConfirmationToReadDescription">
- {{ i18n.ts._announcement.needConfirmationToRead }}
- </MkSwitch>
- <p v-if="announcement.reads">{{ i18n.tsx.nUsersRead({ n: announcement.reads }) }}</p>
- <div class="buttons _buttons">
- <MkButton class="button" inline primary @click="save(announcement)"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
- <MkButton v-if="announcement.id != null" class="button" inline @click="archive(announcement)"><i class="ti ti-check"></i> {{ i18n.ts._announcement.end }} ({{ i18n.ts.archive }})</MkButton>
- <MkButton v-if="announcement.id != null" class="button" inline danger @click="del(announcement)"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
+ <MkLoading v-if="loading"/>
+
+ <template v-else>
+ <MkFolder v-for="announcement in announcements" :key="announcement.id ?? announcement._id" :defaultOpen="announcement.id == null">
+ <template #label>{{ announcement.title }}</template>
+ <template #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>
+ </template>
+ <template #caption>{{ announcement.text }}</template>
+
+ <div class="_gaps_m">
+ <MkInput v-model="announcement.title">
+ <template #label>{{ i18n.ts.title }}</template>
+ </MkInput>
+ <MkTextarea v-model="announcement.text" mfmAutocomplete :mfmPreview="true">
+ <template #label>{{ i18n.ts.text }}</template>
+ </MkTextarea>
+ <MkInput v-model="announcement.imageUrl" type="url">
+ <template #label>{{ i18n.ts.imageUrl }}</template>
+ </MkInput>
+ <MkRadios v-model="announcement.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="announcement.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>
+ <MkInfo v-if="announcement.display === 'dialog'" warn>{{ i18n.ts._announcement.dialogAnnouncementUxWarn }}</MkInfo>
+ <MkSwitch v-model="announcement.forExistingUsers" :helpText="i18n.ts._announcement.forExistingUsersDescription">
+ {{ i18n.ts._announcement.forExistingUsers }}
+ </MkSwitch>
+ <MkSwitch v-model="announcement.silence" :helpText="i18n.ts._announcement.silenceDescription">
+ {{ i18n.ts._announcement.silence }}
+ </MkSwitch>
+ <MkSwitch v-model="announcement.needConfirmationToRead" :helpText="i18n.ts._announcement.needConfirmationToReadDescription">
+ {{ i18n.ts._announcement.needConfirmationToRead }}
+ </MkSwitch>
+ <p v-if="announcement.reads">{{ i18n.tsx.nUsersRead({ n: announcement.reads }) }}</p>
+ <div class="buttons _buttons">
+ <MkButton class="button" inline primary @click="save(announcement)"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
+ <MkButton v-if="announcement.id != null && announcement.isActive" class="button" inline @click="archive(announcement)"><i class="ti ti-check"></i> {{ i18n.ts._announcement.end }} ({{ i18n.ts.archive }})</MkButton>
+ <MkButton v-if="announcement.id != null && !announcement.isActive" class="button" inline @click="unarchive(announcement)"><i class="ti ti-restore"></i> {{ i18n.ts.unarchive }}</MkButton>
+ <MkButton v-if="announcement.id != null" class="button" inline danger @click="del(announcement)"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
+ </div>
</div>
- </div>
- </MkFolder>
- <MkButton class="button" @click="more()">
- <i class="ti ti-reload"></i>{{ i18n.ts.more }}
- </MkButton>
+ </MkFolder>
+ <MkLoading v-if="loadingMore"/>
+ <MkButton class="button" @click="more()">
+ <i class="ti ti-reload"></i>{{ i18n.ts.more }}
+ </MkButton>
+ </template>
</div>
</MkSpacer>
</MkStickyContainer>
</template>
<script lang="ts" setup>
-import { ref, computed } from 'vue';
+import { ref, computed, watch } from 'vue';
import XHeader from './_header_.vue';
import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue';
+import MkSelect from '@/components/MkSelect.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import MkRadios from '@/components/MkRadios.vue';
import MkInfo from '@/components/MkInfo.vue';
@@ -85,11 +98,22 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
import MkFolder from '@/components/MkFolder.vue';
import MkTextarea from '@/components/MkTextarea.vue';
+const announcementsStatus = ref<'active' | 'archived'>('active');
+
+const loading = ref(true);
+const loadingMore = ref(false);
+
const announcements = ref<any[]>([]);
-misskeyApi('admin/announcements/list').then(announcementResponse => {
- announcements.value = announcementResponse;
-});
+watch(announcementsStatus, (to) => {
+ loading.value = true;
+ misskeyApi('admin/announcements/list', {
+ status: to,
+ }).then(announcementResponse => {
+ announcements.value = announcementResponse;
+ loading.value = false;
+ });
+}, { immediate: true });
function add() {
announcements.value.unshift({
@@ -125,6 +149,14 @@ async function archive(announcement) {
refresh();
}
+async function unarchive(announcement) {
+ await os.apiWithDialog('admin/announcements/update', {
+ ...announcement,
+ isActive: true,
+ });
+ refresh();
+}
+
async function save(announcement) {
if (announcement.id == null) {
await os.apiWithDialog('admin/announcements/create', announcement);
@@ -135,24 +167,32 @@ async function save(announcement) {
}
function more() {
- misskeyApi('admin/announcements/list', { untilId: announcements.value.reduce((acc, announcement) => announcement.id != null ? announcement : acc).id }).then(announcementResponse => {
+ loadingMore.value = true;
+ misskeyApi('admin/announcements/list', {
+ status: announcementsStatus.value,
+ untilId: announcements.value.reduce((acc, announcement) => announcement.id != null ? announcement : acc).id
+ }).then(announcementResponse => {
announcements.value = announcements.value.concat(announcementResponse);
+ loadingMore.value = false;
});
}
function refresh() {
- misskeyApi('admin/announcements/list').then(announcementResponse => {
+ loading.value = true;
+ misskeyApi('admin/announcements/list', {
+ status: announcementsStatus.value,
+ }).then(announcementResponse => {
announcements.value = announcementResponse;
+ loading.value = false;
});
}
-refresh();
-
const headerActions = computed(() => [{
asFullButton: true,
icon: 'ti ti-plus',
text: i18n.ts.add,
handler: add,
+ disabled: announcementsStatus.value === 'archived',
}]);
const headerTabs = computed(() => []);