enhance(frontend): ミュート・ロール付与期間を任意の長さに設定できるように (#16766)
* enhance(frontend): ミュートの長さを自由に設定できるように * enhance(frontend): ロールアサインの長さを自由に設定できるように * Update Changelog
This commit is contained in:
parent
50bbc71098
commit
855a652439
|
|
@ -18,6 +18,8 @@
|
|||
- Enhance: プッシュ通知を行うための権限確認をより確実に行うように
|
||||
- Enhance: 投稿フォームのチュートリアルを追加
|
||||
- Enhance: 「自動でもっと見る」をほとんどの箇所で利用可能に
|
||||
- Enhance: ミュートの付与期間を自由に設定できるように
|
||||
- Enhance: ロールの付与期間を自由に設定できるように
|
||||
- Fix: 紙吹雪エフェクトがアニメーション設定を考慮せず常に表示される問題を修正
|
||||
- Fix: ナビゲーションバーのリアルタイムモード切替ボタンの状態をよりわかりやすく表示するように
|
||||
- Fix: ページのタイトルが長いとき、はみ出る問題を修正
|
||||
|
|
|
|||
|
|
@ -3846,6 +3846,10 @@ export interface Locale extends ILocale {
|
|||
* ミュートする期限
|
||||
*/
|
||||
"mutePeriod": string;
|
||||
/**
|
||||
* 期限はあくまで目安です。反映が数分遅れる場合があります。
|
||||
*/
|
||||
"mutePeriodDescription": string;
|
||||
/**
|
||||
* 期限
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -957,6 +957,7 @@ instanceDefaultLightTheme: "サーバーデフォルトのライトテーマ"
|
|||
instanceDefaultDarkTheme: "サーバーデフォルトのダークテーマ"
|
||||
instanceDefaultThemeDescription: "オブジェクト形式のテーマコードを記入します。"
|
||||
mutePeriod: "ミュートする期限"
|
||||
mutePeriodDescription: "期限はあくまで目安です。反映が数分遅れる場合があります。"
|
||||
period: "期限"
|
||||
indefinitely: "無期限"
|
||||
tenMinutes: "10分"
|
||||
|
|
|
|||
|
|
@ -15,17 +15,27 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</div>
|
||||
</div>
|
||||
<div class="_gaps">
|
||||
<MkSelect v-model="periodModel" :items="periodDef">
|
||||
<template #label>{{ i18n.ts.mutePeriod }}</template>
|
||||
</MkSelect>
|
||||
<MkSelect v-model="muteTypeModel" :items="muteTypeDef">
|
||||
<FormSlot>
|
||||
<div class="_gaps_s">
|
||||
<MkSelect v-model="periodModel" :items="periodDef">
|
||||
<template #label>{{ i18n.ts.mutePeriod }}</template>
|
||||
</MkSelect>
|
||||
<MkInput
|
||||
v-if="periodModel === 'custom'"
|
||||
v-model="manualExpiresAt"
|
||||
type="datetime-local"
|
||||
></MkInput>
|
||||
</div>
|
||||
<template #caption>{{ i18n.ts.mutePeriodDescription }}</template>
|
||||
</FormSlot>
|
||||
<MkSelect v-if="withMuteType" v-model="muteTypeModel" :items="muteTypeDef">
|
||||
<template #label>{{ i18n.ts.muteType }}</template>
|
||||
<template #caption>{{ i18n.ts.muteTypeDescription }}</template>
|
||||
</MkSelect>
|
||||
</div>
|
||||
<div :class="$style.buttons">
|
||||
<MkButton inline rounded @click="cancel">{{ i18n.ts.cancel }}</MkButton>
|
||||
<MkButton inline primary rounded @click="ok">{{ i18n.ts.ok }}</MkButton>
|
||||
<MkButton inline primary rounded :disabled="!canSave" @click="ok">{{ i18n.ts.ok }}</MkButton>
|
||||
</div>
|
||||
</div>
|
||||
</MkModal>
|
||||
|
|
@ -34,6 +44,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<script lang="ts">
|
||||
import { i18n } from '@/i18n.js';
|
||||
import type { MkSelectItem, GetMkSelectValueTypesFromDef } from '@/components/MkSelect.vue';
|
||||
import MkInput from './MkInput.vue';
|
||||
|
||||
const periodItems = [{
|
||||
value: 'indefinitely', label: i18n.ts.indefinitely,
|
||||
|
|
@ -45,6 +56,8 @@ const periodItems = [{
|
|||
value: 'oneDay', label: i18n.ts.oneDay,
|
||||
}, {
|
||||
value: 'oneWeek', label: i18n.ts.oneWeek,
|
||||
}, {
|
||||
value: 'custom', label: i18n.ts.custom,
|
||||
}] as const satisfies MkSelectItem[];
|
||||
|
||||
const muteTypeItems = [{
|
||||
|
|
@ -53,22 +66,29 @@ const muteTypeItems = [{
|
|||
value: 'timelineOnly', label: i18n.ts.muteTypeTimeline,
|
||||
}] as const satisfies MkSelectItem[];
|
||||
|
||||
export type MkMuteSettingDialogDoneEvent = { canceled: true } | { canceled: false, period: GetMkSelectValueTypesFromDef<typeof periodItems>, type: GetMkSelectValueTypesFromDef<typeof muteTypeItems> };
|
||||
export type MkMuteSettingDialogDoneEvent = { canceled: true } | { canceled: false, expiresAt: number | null, type: GetMkSelectValueTypesFromDef<typeof muteTypeItems> };
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onBeforeUnmount, onMounted, shallowRef } from 'vue';
|
||||
import { onBeforeUnmount, onMounted, useTemplateRef, ref, computed } from 'vue';
|
||||
import FormSlot from '@/components/form/slot.vue';
|
||||
import MkModal from '@/components/MkModal.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import MkSelect from '@/components/MkSelect.vue';
|
||||
import { useMkSelect } from '@/composables/use-mkselect.js';
|
||||
|
||||
withDefaults(defineProps<{
|
||||
withMuteType?: boolean;
|
||||
}>(), {
|
||||
withMuteType: false,
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'done', v: MkMuteSettingDialogDoneEvent): void;
|
||||
(ev: 'closed'): void;
|
||||
}>();
|
||||
|
||||
const modal = shallowRef<InstanceType<typeof MkModal>>();
|
||||
const modal = useTemplateRef('modal');
|
||||
|
||||
const {
|
||||
def: periodDef,
|
||||
|
|
@ -78,6 +98,15 @@ const {
|
|||
initialValue: 'indefinitely',
|
||||
});
|
||||
|
||||
const now = Date.now();
|
||||
const manualExpiresAt = ref<string | null>(null);
|
||||
const canSave = computed(() => {
|
||||
if (periodModel.value === 'custom') {
|
||||
return manualExpiresAt.value != null && new Date(manualExpiresAt.value).getTime() > now;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
const {
|
||||
def: muteTypeDef,
|
||||
model: muteTypeModel,
|
||||
|
|
@ -91,7 +120,36 @@ function done(canceled: true): void;
|
|||
function done(canceled: false, period: typeof periodModel.value, type: typeof muteTypeModel.value): void; // eslint-disable-line no-redeclare
|
||||
|
||||
function done(canceled: boolean, period?: typeof periodModel.value, type?: typeof muteTypeModel.value) { // eslint-disable-line no-redeclare
|
||||
emit('done', { canceled, period: period!, type: type! });
|
||||
const expiresAt = (() => {
|
||||
if (canceled) return null;
|
||||
if (period === 'custom' && manualExpiresAt.value != null) {
|
||||
return new Date(manualExpiresAt.value!).getTime();
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
|
||||
switch (period) {
|
||||
case 'indefinitely':
|
||||
return null;
|
||||
case 'tenMinutes':
|
||||
return now + 10 * 60 * 1000;
|
||||
case 'oneHour':
|
||||
return now + 60 * 60 * 1000;
|
||||
case 'oneDay':
|
||||
return now + 24 * 60 * 60 * 1000;
|
||||
case 'oneWeek':
|
||||
return now + 7 * 24 * 60 * 60 * 1000;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
})();
|
||||
|
||||
if (canceled) {
|
||||
emit('done', { canceled: true });
|
||||
} else {
|
||||
emit('done', { canceled: false, expiresAt, type: type! });
|
||||
}
|
||||
|
||||
modal.value?.close();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,187 @@
|
|||
<!--
|
||||
SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<MkModal ref="modal" :preferType="'dialog'" :zPriority="'high'" @click="done(true)" @closed="emit('closed')">
|
||||
<div :class="$style.root">
|
||||
<div v-if="title" class="_selectable" :class="$style.header">
|
||||
<Mfm :text="title"/>
|
||||
</div>
|
||||
<div v-if="text" :class="$style.text" class="_selectable">
|
||||
<Mfm :text="text"/>
|
||||
</div>
|
||||
<div class="_gaps_s">
|
||||
<MkSelect v-model="periodModel" :items="periodDef"></MkSelect>
|
||||
<MkInput
|
||||
v-if="periodModel === 'custom'"
|
||||
v-model="manualExpiresAt"
|
||||
type="datetime-local"
|
||||
></MkInput>
|
||||
</div>
|
||||
<div :class="$style.buttons">
|
||||
<MkButton inline rounded @click="cancel">{{ i18n.ts.cancel }}</MkButton>
|
||||
<MkButton inline primary rounded :disabled="!canSave" @click="ok">{{ i18n.ts.ok }}</MkButton>
|
||||
</div>
|
||||
</div>
|
||||
</MkModal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { i18n } from '@/i18n.js';
|
||||
import type { MkSelectItem } from '@/components/MkSelect.vue';
|
||||
import MkInput from './MkInput.vue';
|
||||
|
||||
const periodItems = [{
|
||||
value: 'indefinitely', label: i18n.ts.indefinitely,
|
||||
}, {
|
||||
value: 'tenMinutes', label: i18n.ts.tenMinutes,
|
||||
}, {
|
||||
value: 'oneHour', label: i18n.ts.oneHour,
|
||||
}, {
|
||||
value: 'oneDay', label: i18n.ts.oneDay,
|
||||
}, {
|
||||
value: 'oneWeek', label: i18n.ts.oneWeek,
|
||||
}, {
|
||||
value: 'custom', label: i18n.ts.custom,
|
||||
}] as const satisfies MkSelectItem[];
|
||||
|
||||
export type MkPeriodDialogDoneEvent = { canceled: true } | { canceled: false, expiresAt: number | null };
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onBeforeUnmount, onMounted, useTemplateRef, ref, computed } from 'vue';
|
||||
import MkModal from '@/components/MkModal.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import MkSelect from '@/components/MkSelect.vue';
|
||||
import { useMkSelect } from '@/composables/use-mkselect.js';
|
||||
|
||||
defineProps<{
|
||||
title?: string;
|
||||
text?: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'done', v: MkPeriodDialogDoneEvent): void;
|
||||
(ev: 'closed'): void;
|
||||
}>();
|
||||
|
||||
const modal = useTemplateRef('modal');
|
||||
|
||||
const {
|
||||
def: periodDef,
|
||||
model: periodModel,
|
||||
} = useMkSelect({
|
||||
items: periodItems,
|
||||
initialValue: 'indefinitely',
|
||||
});
|
||||
|
||||
const now = Date.now();
|
||||
const manualExpiresAt = ref<string | null>(null);
|
||||
const canSave = computed(() => {
|
||||
if (periodModel.value === 'custom') {
|
||||
return manualExpiresAt.value != null && new Date(manualExpiresAt.value).getTime() > now;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
// overload function を使いたいので lint エラーを無視する
|
||||
function done(canceled: true): void;
|
||||
function done(canceled: false, period: typeof periodModel.value): void; // eslint-disable-line no-redeclare
|
||||
|
||||
function done(canceled: boolean, period?: typeof periodModel.value) { // eslint-disable-line no-redeclare
|
||||
const expiresAt = (() => {
|
||||
if (canceled) return null;
|
||||
if (period === 'custom' && manualExpiresAt.value != null) {
|
||||
return new Date(manualExpiresAt.value!).getTime();
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
|
||||
switch (period) {
|
||||
case 'indefinitely':
|
||||
return null;
|
||||
case 'tenMinutes':
|
||||
return now + 10 * 60 * 1000;
|
||||
case 'oneHour':
|
||||
return now + 60 * 60 * 1000;
|
||||
case 'oneDay':
|
||||
return now + 24 * 60 * 60 * 1000;
|
||||
case 'oneWeek':
|
||||
return now + 7 * 24 * 60 * 60 * 1000;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
})();
|
||||
|
||||
if (canceled) {
|
||||
emit('done', { canceled: true });
|
||||
} else {
|
||||
emit('done', { canceled: false, expiresAt });
|
||||
}
|
||||
|
||||
modal.value?.close();
|
||||
}
|
||||
|
||||
async function ok() {
|
||||
done(false, periodModel.value);
|
||||
}
|
||||
|
||||
function cancel() {
|
||||
done(true);
|
||||
}
|
||||
|
||||
/*
|
||||
function onBgClick() {
|
||||
if (props.cancelableByBgClick) cancel();
|
||||
}
|
||||
*/
|
||||
function onKeydown(evt: KeyboardEvent) {
|
||||
if (evt.key === 'Escape') cancel();
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.document.addEventListener('keydown', onKeydown);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.document.removeEventListener('keydown', onKeydown);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.root {
|
||||
position: relative;
|
||||
margin: auto;
|
||||
padding: 32px;
|
||||
min-width: 320px;
|
||||
max-width: 480px;
|
||||
box-sizing: border-box;
|
||||
background: var(--MI_THEME-panel);
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
.header {
|
||||
margin: 0 0 8px 0;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
font-size: 1.1em;
|
||||
|
||||
& + .text {
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.text {
|
||||
margin: 16px 0 0 0;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
margin-top: 16px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -15,6 +15,7 @@ import type { MenuItem } from '@/types/menu.js';
|
|||
import type { PostFormProps } from '@/types/post-form.js';
|
||||
import type { UploaderFeatures } from '@/composables/use-uploader.js';
|
||||
import type { MkSelectItem, OptionValue } from '@/components/MkSelect.vue';
|
||||
import type { MkPeriodDialogDoneEvent } from '@/components/MkPeriodDialog.vue';
|
||||
import type MkRoleSelectDialog_TypeReferenceOnly from '@/components/MkRoleSelectDialog.vue';
|
||||
import type MkEmojiPickerDialog_TypeReferenceOnly from '@/components/MkEmojiPickerDialog.vue';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
|
|
@ -530,6 +531,19 @@ export function select<C extends OptionValue, D extends C | null = null>(props:
|
|||
});
|
||||
}
|
||||
|
||||
export function selectPeriod(options: { title?: string } = {}): Promise<MkPeriodDialogDoneEvent> {
|
||||
return new Promise(async (resolve) => {
|
||||
const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkPeriodDialog.vue')), {
|
||||
title: options.title,
|
||||
}, {
|
||||
done: result => {
|
||||
resolve(result ? result : { canceled: true });
|
||||
},
|
||||
closed: () => dispose(),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function success(): Promise<void> {
|
||||
return new Promise(resolve => {
|
||||
const showing = ref(true);
|
||||
|
|
|
|||
|
|
@ -447,31 +447,13 @@ async function assignRole() {
|
|||
});
|
||||
if (canceled || roleId == null) return;
|
||||
|
||||
const { canceled: canceled2, result: period } = await os.select({
|
||||
title: i18n.ts.period + ': ' + roles.find(r => r.id === roleId)!.name,
|
||||
items: [{
|
||||
value: 'indefinitely', label: i18n.ts.indefinitely,
|
||||
}, {
|
||||
value: 'oneHour', label: i18n.ts.oneHour,
|
||||
}, {
|
||||
value: 'oneDay', label: i18n.ts.oneDay,
|
||||
}, {
|
||||
value: 'oneWeek', label: i18n.ts.oneWeek,
|
||||
}, {
|
||||
value: 'oneMonth', label: i18n.ts.oneMonth,
|
||||
}],
|
||||
default: 'indefinitely',
|
||||
const res = await os.selectPeriod({
|
||||
title: `${i18n.ts.period}: ${roles.find(r => r.id === roleId)!.name}`,
|
||||
});
|
||||
if (canceled2) return;
|
||||
|
||||
const expiresAt = period === 'indefinitely' ? null
|
||||
: period === 'oneHour' ? Date.now() + (1000 * 60 * 60)
|
||||
: period === 'oneDay' ? Date.now() + (1000 * 60 * 60 * 24)
|
||||
: period === 'oneWeek' ? Date.now() + (1000 * 60 * 60 * 24 * 7)
|
||||
: period === 'oneMonth' ? Date.now() + (1000 * 60 * 60 * 24 * 30)
|
||||
: null;
|
||||
if (res.canceled) return;
|
||||
|
||||
await os.apiWithDialog('admin/roles/assign', { roleId, userId: user.value.id, expiresAt });
|
||||
await os.apiWithDialog('admin/roles/assign', { roleId, userId: user.value.id, expiresAt: res.expiresAt });
|
||||
refreshUser();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -112,31 +112,12 @@ async function del() {
|
|||
async function assign() {
|
||||
const user = await os.selectUser({ includeSelf: true });
|
||||
|
||||
const { canceled: canceled2, result: period } = await os.select({
|
||||
title: i18n.ts.period + ': ' + role.name,
|
||||
items: [{
|
||||
value: 'indefinitely', label: i18n.ts.indefinitely,
|
||||
}, {
|
||||
value: 'oneHour', label: i18n.ts.oneHour,
|
||||
}, {
|
||||
value: 'oneDay', label: i18n.ts.oneDay,
|
||||
}, {
|
||||
value: 'oneWeek', label: i18n.ts.oneWeek,
|
||||
}, {
|
||||
value: 'oneMonth', label: i18n.ts.oneMonth,
|
||||
}],
|
||||
default: 'indefinitely',
|
||||
const res = await os.selectPeriod({
|
||||
title: `${i18n.ts.period}: ${role.name}`,
|
||||
});
|
||||
if (canceled2) return;
|
||||
if (res.canceled) return;
|
||||
|
||||
const expiresAt = period === 'indefinitely' ? null
|
||||
: period === 'oneHour' ? Date.now() + (1000 * 60 * 60)
|
||||
: period === 'oneDay' ? Date.now() + (1000 * 60 * 60 * 24)
|
||||
: period === 'oneWeek' ? Date.now() + (1000 * 60 * 60 * 24 * 7)
|
||||
: period === 'oneMonth' ? Date.now() + (1000 * 60 * 60 * 24 * 30)
|
||||
: null;
|
||||
|
||||
await os.apiWithDialog('admin/roles/assign', { roleId: role.id, userId: user.id, expiresAt });
|
||||
await os.apiWithDialog('admin/roles/assign', { roleId: role.id, userId: user.id, expiresAt: res.expiresAt });
|
||||
//role.users.push(user);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ import MkStreamingNotesTimeline from '@/components/MkStreamingNotesTimeline.vue'
|
|||
import XChannelFollowButton from '@/components/MkChannelFollowButton.vue';
|
||||
import * as os from '@/os.js';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
import { openMuteSettingDialog } from '@/utility/mute-confirm.js';
|
||||
import { $i, iAmModerator } from '@/i.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { definePage } from '@/page.js';
|
||||
|
|
@ -195,33 +196,14 @@ async function mute() {
|
|||
if (!channel.value) return;
|
||||
const _channel = channel.value;
|
||||
|
||||
const { canceled, result: period } = await os.select({
|
||||
title: i18n.ts.mutePeriod,
|
||||
items: [{
|
||||
value: 'indefinitely', label: i18n.ts.indefinitely,
|
||||
}, {
|
||||
value: 'tenMinutes', label: i18n.ts.tenMinutes,
|
||||
}, {
|
||||
value: 'oneHour', label: i18n.ts.oneHour,
|
||||
}, {
|
||||
value: 'oneDay', label: i18n.ts.oneDay,
|
||||
}, {
|
||||
value: 'oneWeek', label: i18n.ts.oneWeek,
|
||||
}],
|
||||
default: 'indefinitely',
|
||||
const res = await openMuteSettingDialog({
|
||||
withMuteType: false,
|
||||
});
|
||||
if (canceled) return;
|
||||
|
||||
const expiresAt = period === 'indefinitely' ? null
|
||||
: period === 'tenMinutes' ? Date.now() + (1000 * 60 * 10)
|
||||
: period === 'oneHour' ? Date.now() + (1000 * 60 * 60)
|
||||
: period === 'oneDay' ? Date.now() + (1000 * 60 * 60 * 24)
|
||||
: period === 'oneWeek' ? Date.now() + (1000 * 60 * 60 * 24 * 7)
|
||||
: null;
|
||||
if (res.canceled) return;
|
||||
|
||||
os.apiWithDialog('channels/mute/create', {
|
||||
channelId: _channel.id,
|
||||
expiresAt,
|
||||
expiresAt: res.expiresAt,
|
||||
}).then(() => {
|
||||
_channel.isMuting = true;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -18,20 +18,9 @@ import { notesSearchAvailable, canSearchNonLocalNotes } from '@/utility/check-pe
|
|||
import { antennasCache, rolesCache, userListsCache } from '@/cache.js';
|
||||
import { mainRouter } from '@/router.js';
|
||||
import { genEmbedCode } from '@/utility/get-embed-code.js';
|
||||
import { openMuteSettingDialog } from '@/utility/mute-confirm.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import { getPluginHandlers } from '@/plugin.js';
|
||||
import type { MkMuteSettingDialogDoneEvent } from '@/components/MkMuteSettingDialog.vue';
|
||||
|
||||
function muteConfirm(): Promise<MkMuteSettingDialogDoneEvent> {
|
||||
return new Promise(resolve => {
|
||||
const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkMuteSettingDialog.vue')), {}, {
|
||||
done: result => {
|
||||
resolve(result ? result : { canceled: true });
|
||||
},
|
||||
closed: () => dispose(),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function getUserMenu(user: Misskey.entities.UserDetailed, router: Router = mainRouter) {
|
||||
const meId = $i ? $i.id : null;
|
||||
|
|
@ -46,19 +35,14 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: Router
|
|||
user.isMuted = false;
|
||||
});
|
||||
} else {
|
||||
const res = await muteConfirm();
|
||||
const res = await openMuteSettingDialog({
|
||||
withMuteType: true,
|
||||
});
|
||||
if (res.canceled) return;
|
||||
|
||||
const expiresAt = res.period === 'indefinitely' ? null
|
||||
: res.period === 'tenMinutes' ? Date.now() + (1000 * 60 * 10)
|
||||
: res.period === 'oneHour' ? Date.now() + (1000 * 60 * 60)
|
||||
: res.period === 'oneDay' ? Date.now() + (1000 * 60 * 60 * 24)
|
||||
: res.period === 'oneWeek' ? Date.now() + (1000 * 60 * 60 * 24 * 7)
|
||||
: null;
|
||||
|
||||
os.apiWithDialog('mute/create', {
|
||||
userId: user.id,
|
||||
expiresAt,
|
||||
expiresAt: res.expiresAt,
|
||||
mutingType: res.type,
|
||||
}).then(() => {
|
||||
user.isMuted = true;
|
||||
|
|
@ -323,31 +307,13 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: Router
|
|||
return roles.filter(r => r.target === 'manual').map(r => ({
|
||||
text: r.name,
|
||||
action: async () => {
|
||||
const { canceled, result: period } = await os.select({
|
||||
title: i18n.ts.period + ': ' + r.name,
|
||||
items: [{
|
||||
value: 'indefinitely', label: i18n.ts.indefinitely,
|
||||
}, {
|
||||
value: 'oneHour', label: i18n.ts.oneHour,
|
||||
}, {
|
||||
value: 'oneDay', label: i18n.ts.oneDay,
|
||||
}, {
|
||||
value: 'oneWeek', label: i18n.ts.oneWeek,
|
||||
}, {
|
||||
value: 'oneMonth', label: i18n.ts.oneMonth,
|
||||
}],
|
||||
default: 'indefinitely',
|
||||
const res = await os.selectPeriod({
|
||||
title: `${i18n.ts.period}: ${r.name}`,
|
||||
});
|
||||
if (canceled) return;
|
||||
|
||||
const expiresAt = period === 'indefinitely' ? null
|
||||
: period === 'oneHour' ? Date.now() + (1000 * 60 * 60)
|
||||
: period === 'oneDay' ? Date.now() + (1000 * 60 * 60 * 24)
|
||||
: period === 'oneWeek' ? Date.now() + (1000 * 60 * 60 * 24 * 7)
|
||||
: period === 'oneMonth' ? Date.now() + (1000 * 60 * 60 * 24 * 30)
|
||||
: null;
|
||||
if (res.canceled) return;
|
||||
|
||||
os.apiWithDialog('admin/roles/assign', { roleId: r.id, userId: user.id, expiresAt });
|
||||
os.apiWithDialog('admin/roles/assign', { roleId: r.id, userId: user.id, expiresAt: res.expiresAt });
|
||||
},
|
||||
}));
|
||||
},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { defineAsyncComponent } from 'vue';
|
||||
import * as os from '@/os.js';
|
||||
import type { MkMuteSettingDialogDoneEvent } from '@/components/MkMuteSettingDialog.vue';
|
||||
|
||||
export function openMuteSettingDialog(opts?: { withMuteType?: boolean }): Promise<MkMuteSettingDialogDoneEvent> {
|
||||
return new Promise(resolve => {
|
||||
const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkMuteSettingDialog.vue')), opts ?? {}, {
|
||||
done: result => {
|
||||
resolve(result ? result : { canceled: true });
|
||||
},
|
||||
closed: () => {
|
||||
dispose();
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
Loading…
Reference in New Issue