mirror of
https://github.com/misskey-dev/misskey.git
synced 2026-05-22 11:44:10 +02:00
enhance(frontend): ミュート・ロール付与期間を任意の長さに設定できるように (#16766)
* enhance(frontend): ミュートの長さを自由に設定できるように * enhance(frontend): ロールアサインの長さを自由に設定できるように * Update Changelog
This commit is contained in:
@@ -18,6 +18,8 @@
|
|||||||
- Enhance: プッシュ通知を行うための権限確認をより確実に行うように
|
- Enhance: プッシュ通知を行うための権限確認をより確実に行うように
|
||||||
- Enhance: 投稿フォームのチュートリアルを追加
|
- Enhance: 投稿フォームのチュートリアルを追加
|
||||||
- Enhance: 「自動でもっと見る」をほとんどの箇所で利用可能に
|
- Enhance: 「自動でもっと見る」をほとんどの箇所で利用可能に
|
||||||
|
- Enhance: ミュートの付与期間を自由に設定できるように
|
||||||
|
- Enhance: ロールの付与期間を自由に設定できるように
|
||||||
- Fix: 紙吹雪エフェクトがアニメーション設定を考慮せず常に表示される問題を修正
|
- Fix: 紙吹雪エフェクトがアニメーション設定を考慮せず常に表示される問題を修正
|
||||||
- Fix: ナビゲーションバーのリアルタイムモード切替ボタンの状態をよりわかりやすく表示するように
|
- Fix: ナビゲーションバーのリアルタイムモード切替ボタンの状態をよりわかりやすく表示するように
|
||||||
- Fix: ページのタイトルが長いとき、はみ出る問題を修正
|
- Fix: ページのタイトルが長いとき、はみ出る問題を修正
|
||||||
|
|||||||
4
locales/index.d.ts
vendored
4
locales/index.d.ts
vendored
@@ -3846,6 +3846,10 @@ export interface Locale extends ILocale {
|
|||||||
* ミュートする期限
|
* ミュートする期限
|
||||||
*/
|
*/
|
||||||
"mutePeriod": string;
|
"mutePeriod": string;
|
||||||
|
/**
|
||||||
|
* 期限はあくまで目安です。反映が数分遅れる場合があります。
|
||||||
|
*/
|
||||||
|
"mutePeriodDescription": string;
|
||||||
/**
|
/**
|
||||||
* 期限
|
* 期限
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -957,6 +957,7 @@ instanceDefaultLightTheme: "サーバーデフォルトのライトテーマ"
|
|||||||
instanceDefaultDarkTheme: "サーバーデフォルトのダークテーマ"
|
instanceDefaultDarkTheme: "サーバーデフォルトのダークテーマ"
|
||||||
instanceDefaultThemeDescription: "オブジェクト形式のテーマコードを記入します。"
|
instanceDefaultThemeDescription: "オブジェクト形式のテーマコードを記入します。"
|
||||||
mutePeriod: "ミュートする期限"
|
mutePeriod: "ミュートする期限"
|
||||||
|
mutePeriodDescription: "期限はあくまで目安です。反映が数分遅れる場合があります。"
|
||||||
period: "期限"
|
period: "期限"
|
||||||
indefinitely: "無期限"
|
indefinitely: "無期限"
|
||||||
tenMinutes: "10分"
|
tenMinutes: "10分"
|
||||||
|
|||||||
@@ -15,17 +15,27 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="_gaps">
|
<div class="_gaps">
|
||||||
<MkSelect v-model="periodModel" :items="periodDef">
|
<FormSlot>
|
||||||
<template #label>{{ i18n.ts.mutePeriod }}</template>
|
<div class="_gaps_s">
|
||||||
</MkSelect>
|
<MkSelect v-model="periodModel" :items="periodDef">
|
||||||
<MkSelect v-model="muteTypeModel" :items="muteTypeDef">
|
<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 #label>{{ i18n.ts.muteType }}</template>
|
||||||
<template #caption>{{ i18n.ts.muteTypeDescription }}</template>
|
<template #caption>{{ i18n.ts.muteTypeDescription }}</template>
|
||||||
</MkSelect>
|
</MkSelect>
|
||||||
</div>
|
</div>
|
||||||
<div :class="$style.buttons">
|
<div :class="$style.buttons">
|
||||||
<MkButton inline rounded @click="cancel">{{ i18n.ts.cancel }}</MkButton>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</MkModal>
|
</MkModal>
|
||||||
@@ -34,6 +44,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import type { MkSelectItem, GetMkSelectValueTypesFromDef } from '@/components/MkSelect.vue';
|
import type { MkSelectItem, GetMkSelectValueTypesFromDef } from '@/components/MkSelect.vue';
|
||||||
|
import MkInput from './MkInput.vue';
|
||||||
|
|
||||||
const periodItems = [{
|
const periodItems = [{
|
||||||
value: 'indefinitely', label: i18n.ts.indefinitely,
|
value: 'indefinitely', label: i18n.ts.indefinitely,
|
||||||
@@ -45,6 +56,8 @@ const periodItems = [{
|
|||||||
value: 'oneDay', label: i18n.ts.oneDay,
|
value: 'oneDay', label: i18n.ts.oneDay,
|
||||||
}, {
|
}, {
|
||||||
value: 'oneWeek', label: i18n.ts.oneWeek,
|
value: 'oneWeek', label: i18n.ts.oneWeek,
|
||||||
|
}, {
|
||||||
|
value: 'custom', label: i18n.ts.custom,
|
||||||
}] as const satisfies MkSelectItem[];
|
}] as const satisfies MkSelectItem[];
|
||||||
|
|
||||||
const muteTypeItems = [{
|
const muteTypeItems = [{
|
||||||
@@ -53,22 +66,29 @@ const muteTypeItems = [{
|
|||||||
value: 'timelineOnly', label: i18n.ts.muteTypeTimeline,
|
value: 'timelineOnly', label: i18n.ts.muteTypeTimeline,
|
||||||
}] as const satisfies MkSelectItem[];
|
}] 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>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<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 MkModal from '@/components/MkModal.vue';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import MkSelect from '@/components/MkSelect.vue';
|
import MkSelect from '@/components/MkSelect.vue';
|
||||||
import { useMkSelect } from '@/composables/use-mkselect.js';
|
import { useMkSelect } from '@/composables/use-mkselect.js';
|
||||||
|
|
||||||
|
withDefaults(defineProps<{
|
||||||
|
withMuteType?: boolean;
|
||||||
|
}>(), {
|
||||||
|
withMuteType: false,
|
||||||
|
});
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(ev: 'done', v: MkMuteSettingDialogDoneEvent): void;
|
(ev: 'done', v: MkMuteSettingDialogDoneEvent): void;
|
||||||
(ev: 'closed'): void;
|
(ev: 'closed'): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const modal = shallowRef<InstanceType<typeof MkModal>>();
|
const modal = useTemplateRef('modal');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
def: periodDef,
|
def: periodDef,
|
||||||
@@ -78,6 +98,15 @@ const {
|
|||||||
initialValue: 'indefinitely',
|
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 {
|
const {
|
||||||
def: muteTypeDef,
|
def: muteTypeDef,
|
||||||
model: muteTypeModel,
|
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: 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
|
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();
|
modal.value?.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
187
packages/frontend/src/components/MkPeriodDialog.vue
Normal file
187
packages/frontend/src/components/MkPeriodDialog.vue
Normal file
@@ -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 { PostFormProps } from '@/types/post-form.js';
|
||||||
import type { UploaderFeatures } from '@/composables/use-uploader.js';
|
import type { UploaderFeatures } from '@/composables/use-uploader.js';
|
||||||
import type { MkSelectItem, OptionValue } from '@/components/MkSelect.vue';
|
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 MkRoleSelectDialog_TypeReferenceOnly from '@/components/MkRoleSelectDialog.vue';
|
||||||
import type MkEmojiPickerDialog_TypeReferenceOnly from '@/components/MkEmojiPickerDialog.vue';
|
import type MkEmojiPickerDialog_TypeReferenceOnly from '@/components/MkEmojiPickerDialog.vue';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
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> {
|
export function success(): Promise<void> {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
const showing = ref(true);
|
const showing = ref(true);
|
||||||
|
|||||||
@@ -447,31 +447,13 @@ async function assignRole() {
|
|||||||
});
|
});
|
||||||
if (canceled || roleId == null) return;
|
if (canceled || roleId == null) return;
|
||||||
|
|
||||||
const { canceled: canceled2, result: period } = await os.select({
|
const res = await os.selectPeriod({
|
||||||
title: i18n.ts.period + ': ' + roles.find(r => r.id === roleId)!.name,
|
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',
|
|
||||||
});
|
});
|
||||||
if (canceled2) return;
|
|
||||||
|
|
||||||
const expiresAt = period === 'indefinitely' ? null
|
if (res.canceled) return;
|
||||||
: 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, userId: user.value.id, expiresAt });
|
await os.apiWithDialog('admin/roles/assign', { roleId, userId: user.value.id, expiresAt: res.expiresAt });
|
||||||
refreshUser();
|
refreshUser();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -112,31 +112,12 @@ async function del() {
|
|||||||
async function assign() {
|
async function assign() {
|
||||||
const user = await os.selectUser({ includeSelf: true });
|
const user = await os.selectUser({ includeSelf: true });
|
||||||
|
|
||||||
const { canceled: canceled2, result: period } = await os.select({
|
const res = await os.selectPeriod({
|
||||||
title: i18n.ts.period + ': ' + role.name,
|
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',
|
|
||||||
});
|
});
|
||||||
if (canceled2) return;
|
if (res.canceled) return;
|
||||||
|
|
||||||
const expiresAt = period === 'indefinitely' ? null
|
await os.apiWithDialog('admin/roles/assign', { roleId: role.id, userId: user.id, expiresAt: res.expiresAt });
|
||||||
: 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 });
|
|
||||||
//role.users.push(user);
|
//role.users.push(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ import MkStreamingNotesTimeline from '@/components/MkStreamingNotesTimeline.vue'
|
|||||||
import XChannelFollowButton from '@/components/MkChannelFollowButton.vue';
|
import XChannelFollowButton from '@/components/MkChannelFollowButton.vue';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
|
import { openMuteSettingDialog } from '@/utility/mute-confirm.js';
|
||||||
import { $i, iAmModerator } from '@/i.js';
|
import { $i, iAmModerator } from '@/i.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePage } from '@/page.js';
|
||||||
@@ -195,33 +196,14 @@ async function mute() {
|
|||||||
if (!channel.value) return;
|
if (!channel.value) return;
|
||||||
const _channel = channel.value;
|
const _channel = channel.value;
|
||||||
|
|
||||||
const { canceled, result: period } = await os.select({
|
const res = await openMuteSettingDialog({
|
||||||
title: i18n.ts.mutePeriod,
|
withMuteType: false,
|
||||||
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',
|
|
||||||
});
|
});
|
||||||
if (canceled) return;
|
if (res.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;
|
|
||||||
|
|
||||||
os.apiWithDialog('channels/mute/create', {
|
os.apiWithDialog('channels/mute/create', {
|
||||||
channelId: _channel.id,
|
channelId: _channel.id,
|
||||||
expiresAt,
|
expiresAt: res.expiresAt,
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
_channel.isMuting = true;
|
_channel.isMuting = true;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -18,20 +18,9 @@ import { notesSearchAvailable, canSearchNonLocalNotes } from '@/utility/check-pe
|
|||||||
import { antennasCache, rolesCache, userListsCache } from '@/cache.js';
|
import { antennasCache, rolesCache, userListsCache } from '@/cache.js';
|
||||||
import { mainRouter } from '@/router.js';
|
import { mainRouter } from '@/router.js';
|
||||||
import { genEmbedCode } from '@/utility/get-embed-code.js';
|
import { genEmbedCode } from '@/utility/get-embed-code.js';
|
||||||
|
import { openMuteSettingDialog } from '@/utility/mute-confirm.js';
|
||||||
import { prefer } from '@/preferences.js';
|
import { prefer } from '@/preferences.js';
|
||||||
import { getPluginHandlers } from '@/plugin.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) {
|
export function getUserMenu(user: Misskey.entities.UserDetailed, router: Router = mainRouter) {
|
||||||
const meId = $i ? $i.id : null;
|
const meId = $i ? $i.id : null;
|
||||||
@@ -46,19 +35,14 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: Router
|
|||||||
user.isMuted = false;
|
user.isMuted = false;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const res = await muteConfirm();
|
const res = await openMuteSettingDialog({
|
||||||
|
withMuteType: true,
|
||||||
|
});
|
||||||
if (res.canceled) return;
|
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', {
|
os.apiWithDialog('mute/create', {
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
expiresAt,
|
expiresAt: res.expiresAt,
|
||||||
mutingType: res.type,
|
mutingType: res.type,
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
user.isMuted = true;
|
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 => ({
|
return roles.filter(r => r.target === 'manual').map(r => ({
|
||||||
text: r.name,
|
text: r.name,
|
||||||
action: async () => {
|
action: async () => {
|
||||||
const { canceled, result: period } = await os.select({
|
const res = await os.selectPeriod({
|
||||||
title: i18n.ts.period + ': ' + r.name,
|
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',
|
|
||||||
});
|
});
|
||||||
if (canceled) return;
|
|
||||||
|
|
||||||
const expiresAt = period === 'indefinitely' ? null
|
if (res.canceled) return;
|
||||||
: 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;
|
|
||||||
|
|
||||||
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 });
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|||||||
21
packages/frontend/src/utility/mute-confirm.ts
Normal file
21
packages/frontend/src/utility/mute-confirm.ts
Normal file
@@ -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();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user