summaryrefslogtreecommitdiff
path: root/packages/frontend/src/pages/admin
diff options
context:
space:
mode:
Diffstat (limited to 'packages/frontend/src/pages/admin')
-rw-r--r--packages/frontend/src/pages/admin/RolesEditorFormula.vue101
-rw-r--r--packages/frontend/src/pages/admin/abuse-report/notification-recipient.editor.vue4
-rw-r--r--packages/frontend/src/pages/admin/abuse-report/notification-recipient.item.vue6
-rw-r--r--packages/frontend/src/pages/admin/abuse-report/notification-recipient.vue2
-rw-r--r--packages/frontend/src/pages/admin/abuses.vue2
-rw-r--r--packages/frontend/src/pages/admin/ads.vue33
-rw-r--r--packages/frontend/src/pages/admin/announcements.vue77
-rw-r--r--packages/frontend/src/pages/admin/bot-protection.vue18
-rw-r--r--packages/frontend/src/pages/admin/branding.vue22
-rw-r--r--packages/frontend/src/pages/admin/custom-emojis-manager.local.list.vue9
-rw-r--r--packages/frontend/src/pages/admin/custom-emojis-manager.register.vue3
-rw-r--r--packages/frontend/src/pages/admin/custom-emojis-manager.remote.vue2
-rw-r--r--packages/frontend/src/pages/admin/custom-emojis-manager2.vue4
-rw-r--r--packages/frontend/src/pages/admin/federation-job-queue.chart.chart.vue4
-rw-r--r--packages/frontend/src/pages/admin/index.vue2
-rw-r--r--packages/frontend/src/pages/admin/job-queue.vue6
-rw-r--r--packages/frontend/src/pages/admin/overview.active-users.vue2
-rw-r--r--packages/frontend/src/pages/admin/overview.vue2
-rw-r--r--packages/frontend/src/pages/admin/roles.edit.vue15
-rw-r--r--packages/frontend/src/pages/admin/roles.editor.vue15
-rw-r--r--packages/frontend/src/pages/admin/roles.role.vue27
-rw-r--r--packages/frontend/src/pages/admin/security.vue14
-rw-r--r--packages/frontend/src/pages/admin/server-rules.vue48
-rw-r--r--packages/frontend/src/pages/admin/settings.vue12
-rw-r--r--packages/frontend/src/pages/admin/system-webhook.item.vue8
-rw-r--r--packages/frontend/src/pages/admin/users.vue3
26 files changed, 261 insertions, 180 deletions
diff --git a/packages/frontend/src/pages/admin/RolesEditorFormula.vue b/packages/frontend/src/pages/admin/RolesEditorFormula.vue
index 9d9db9158d..384282262d 100644
--- a/packages/frontend/src/pages/admin/RolesEditorFormula.vue
+++ b/packages/frontend/src/pages/admin/RolesEditorFormula.vue
@@ -6,9 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<div class="_gaps">
<div :class="$style.header">
- <MkSelect v-model="type" :items="typeDef" :class="$style.typeSelect">
+ <MkSelect v-model="typeModelForMkSelect" :items="typeDef" :class="$style.typeSelect">
</MkSelect>
- <button v-if="draggable" class="drag-handle _button" :class="$style.dragHandle">
+ <button v-if="draggable" class="_button" :class="$style.dragHandle" :draggable="true" @dragstart.stop="dragStartCallback">
<i class="ti ti-menu-2"></i>
</button>
<button v-if="draggable" class="_button" :class="$style.remove" @click="removeSelf">
@@ -16,55 +16,69 @@ SPDX-License-Identifier: AGPL-3.0-only
</button>
</div>
- <div v-if="type === 'and' || type === 'or'" class="_gaps">
- <Sortable v-model="v.values" tag="div" class="_gaps" itemKey="id" handle=".drag-handle" :group="{ name: 'roleFormula' }" :animation="150" :swapThreshold="0.5">
- <template #item="{element}">
+ <div v-if="v.type === 'and' || v.type === 'or'" class="_gaps">
+ <MkDraggable
+ v-model="v.values"
+ direction="vertical"
+ withGaps
+ canNest
+ manualDragStart
+ group="roleFormula"
+ >
+ <template #default="{ item, dragStart }">
<div :class="$style.item">
- <!-- divが無いとエラーになる https://github.com/SortableJS/vue.draggable.next/issues/189 -->
- <RolesEditorFormula :modelValue="element" draggable @update:modelValue="updated => valuesItemUpdated(updated)" @remove="removeItem(element)"/>
+ <!-- divが無いとエラーになる -->
+ <RolesEditorFormula
+ :modelValue="item"
+ :dragStartCallback="dragStart"
+ draggable
+ @update:modelValue="updated => childValuesItemUpdated(updated)"
+ @remove="removeChildItem(item.id)"
+ />
</div>
</template>
- </Sortable>
- <MkButton rounded style="margin: 0 auto;" @click="addValue"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton>
+ </MkDraggable>
+ <MkButton rounded style="margin: 0 auto;" @click="addChildValue"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton>
</div>
- <div v-else-if="type === 'not'" :class="$style.item">
+ <div v-else-if="v.type === 'not'" :class="$style.item">
<RolesEditorFormula v-model="v.value"/>
</div>
- <MkInput v-else-if="type === 'createdLessThan' || type === 'createdMoreThan'" v-model="v.sec" type="number">
+ <MkInput v-else-if="v.type === 'createdLessThan' || v.type === 'createdMoreThan'" v-model="v.sec" type="number">
<template #suffix>sec</template>
</MkInput>
- <MkInput v-else-if="['followersLessThanOrEq', 'followersMoreThanOrEq', 'followingLessThanOrEq', 'followingMoreThanOrEq', 'notesLessThanOrEq', 'notesMoreThanOrEq'].includes(type)" v-model="v.value" type="number">
+ <MkInput v-else-if="v.type === 'followersLessThanOrEq' || v.type === 'followersMoreThanOrEq' || v.type === 'followingLessThanOrEq' || v.type === 'followingMoreThanOrEq' || v.type === 'notesLessThanOrEq' || v.type === 'notesMoreThanOrEq'" v-model="v.value" type="number">
</MkInput>
- <MkSelect v-else-if="type === 'roleAssignedTo'" v-model="v.roleId" :items="assignedToDef">
+ <MkSelect v-else-if="v.type === 'roleAssignedTo'" v-model="v.roleId" :items="assignedToDef">
</MkSelect>
</div>
</template>
<script lang="ts" setup>
-import { computed, defineAsyncComponent, ref, watch } from 'vue';
+import { computed, ref, watch } from 'vue';
+import * as Misskey from 'misskey-js';
+import type { GetMkSelectValueTypesFromDef, MkSelectItem } from '@/components/MkSelect.vue';
import { genId } from '@/utility/id.js';
import MkInput from '@/components/MkInput.vue';
import MkSelect from '@/components/MkSelect.vue';
-import type { GetMkSelectValueTypesFromDef, MkSelectItem } from '@/components/MkSelect.vue';
import MkButton from '@/components/MkButton.vue';
+import MkDraggable from '@/components/MkDraggable.vue';
import { i18n } from '@/i18n.js';
import { deepClone } from '@/utility/clone.js';
import { rolesCache } from '@/cache.js';
-const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));
-
const emit = defineEmits<{
- (ev: 'update:modelValue', value: any): void;
+ (ev: 'update:modelValue', value: Misskey.entities.Role['condFormula']): void;
(ev: 'remove'): void;
}>();
const props = defineProps<{
- modelValue: any;
+ modelValue: Misskey.entities.Role['condFormula'];
draggable?: boolean;
+ dragStartCallback?: (ev: DragEvent) => void;
}>();
const v = ref(deepClone(props.modelValue));
@@ -102,38 +116,51 @@ const typeDef = [
{ label: i18n.ts._role._condition.not, value: 'not' },
] as const satisfies MkSelectItem[];
-const type = computed<GetMkSelectValueTypesFromDef<typeof typeDef>>({
+type KeyOfUnion<T> = T extends T ? keyof T : never;
+
+type DistributiveOmit<T, K extends KeyOfUnion<T>> = T extends T
+ ? Omit<T, K>
+ : never;
+
+const typeModelForMkSelect = computed<GetMkSelectValueTypesFromDef<typeof typeDef>>({
get: () => v.value.type,
set: (t) => {
- if (t === 'and') v.value.values = [];
- if (t === 'or') v.value.values = [];
- if (t === 'not') v.value.value = { id: genId(), type: 'isRemote' };
- if (t === 'roleAssignedTo') v.value.roleId = '';
- if (t === 'createdLessThan') v.value.sec = 86400;
- if (t === 'createdMoreThan') v.value.sec = 86400;
- if (t === 'followersLessThanOrEq') v.value.value = 10;
- if (t === 'followersMoreThanOrEq') v.value.value = 10;
- if (t === 'followingLessThanOrEq') v.value.value = 10;
- if (t === 'followingMoreThanOrEq') v.value.value = 10;
- if (t === 'notesLessThanOrEq') v.value.value = 10;
- if (t === 'notesMoreThanOrEq') v.value.value = 10;
- v.value.type = t;
+ let newValue: DistributiveOmit<Misskey.entities.Role['condFormula'], 'id'>;
+ switch (t) {
+ case 'and': newValue = { type: 'and', values: [] }; break;
+ case 'or': newValue = { type: 'or', values: [] }; break;
+ case 'not': newValue = { type: 'not', value: { id: genId(), type: 'isRemote' } }; break;
+ case 'roleAssignedTo': newValue = { type: 'roleAssignedTo', roleId: '' }; break;
+ case 'createdLessThan': newValue = { type: 'createdLessThan', sec: 86400 }; break;
+ case 'createdMoreThan': newValue = { type: 'createdMoreThan', sec: 86400 }; break;
+ case 'followersLessThanOrEq': newValue = { type: 'followersLessThanOrEq', value: 10 }; break;
+ case 'followersMoreThanOrEq': newValue = { type: 'followersMoreThanOrEq', value: 10 }; break;
+ case 'followingLessThanOrEq': newValue = { type: 'followingLessThanOrEq', value: 10 }; break;
+ case 'followingMoreThanOrEq': newValue = { type: 'followingMoreThanOrEq', value: 10 }; break;
+ case 'notesLessThanOrEq': newValue = { type: 'notesLessThanOrEq', value: 10 }; break;
+ case 'notesMoreThanOrEq': newValue = { type: 'notesMoreThanOrEq', value: 10 }; break;
+ default: newValue = { type: t }; break;
+ }
+ v.value = { id: v.value.id, ...newValue };
},
});
const assignedToDef = computed(() => roles.filter(r => r.target === 'manual').map(r => ({ label: r.name, value: r.id })) satisfies MkSelectItem[]);
-function addValue() {
+function addChildValue() {
+ if (v.value.type !== 'and' && v.value.type !== 'or') return;
v.value.values.push({ id: genId(), type: 'isRemote' });
}
-function valuesItemUpdated(item) {
+function childValuesItemUpdated(item: Misskey.entities.Role['condFormula']) {
+ if (v.value.type !== 'and' && v.value.type !== 'or') return;
const i = v.value.values.findIndex(_item => _item.id === item.id);
v.value.values[i] = item;
}
-function removeItem(item) {
- v.value.values = v.value.values.filter(_item => _item.id !== item.id);
+function removeChildItem(itemId: string) {
+ if (v.value.type !== 'and' && v.value.type !== 'or') return;
+ v.value.values = v.value.values.filter(_item => _item.id !== itemId);
}
function removeSelf() {
diff --git a/packages/frontend/src/pages/admin/abuse-report/notification-recipient.editor.vue b/packages/frontend/src/pages/admin/abuse-report/notification-recipient.editor.vue
index 7c3f736506..591d8fa736 100644
--- a/packages/frontend/src/pages/admin/abuse-report/notification-recipient.editor.vue
+++ b/packages/frontend/src/pages/admin/abuse-report/notification-recipient.editor.vue
@@ -37,8 +37,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #label>{{ i18n.ts._abuseReport._notificationRecipient.notifiedWebhook }}</template>
</MkSelect>
<MkButton rounded :class="$style.systemWebhookEditButton" @click="onEditSystemWebhookClicked">
- <span v-if="systemWebhookId === null" class="ti ti-plus" style="line-height: normal"/>
- <span v-else class="ti ti-settings" style="line-height: normal"/>
+ <span v-if="systemWebhookId === null" class="ti ti-plus" style="line-height: normal"></span>
+ <span v-else class="ti ti-settings" style="line-height: normal"></span>
</MkButton>
</div>
</div>
diff --git a/packages/frontend/src/pages/admin/abuse-report/notification-recipient.item.vue b/packages/frontend/src/pages/admin/abuse-report/notification-recipient.item.vue
index 36d586bd23..ba5830c2e8 100644
--- a/packages/frontend/src/pages/admin/abuse-report/notification-recipient.item.vue
+++ b/packages/frontend/src/pages/admin/abuse-report/notification-recipient.item.vue
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<div :class="$style.root" class="_panel _gaps_s">
- <div :class="$style.rightDivider" style="width: 80px;"><span :class="`ti ${methodIcon}`"/> {{ methodName }}</div>
+ <div :class="$style.rightDivider" style="width: 80px;"><span :class="`ti ${methodIcon}`"></span> {{ methodName }}</div>
<div :class="$style.rightDivider" style="flex: 0.5">{{ entity.name }}</div>
<div :class="$style.rightDivider" style="flex: 1">
<div v-if="method === 'email' && user">
@@ -19,10 +19,10 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
<div :class="$style.recipientButtons" style="margin-left: auto">
<button :class="$style.recipientButton" @click="onEditButtonClicked()">
- <span class="ti ti-settings"/>
+ <span class="ti ti-settings"></span>
</button>
<button :class="$style.recipientButton" @click="onDeleteButtonClicked()">
- <span class="ti ti-trash"/>
+ <span class="ti ti-trash"></span>
</button>
</div>
</div>
diff --git a/packages/frontend/src/pages/admin/abuse-report/notification-recipient.vue b/packages/frontend/src/pages/admin/abuse-report/notification-recipient.vue
index 893bd8d6d3..a9cf372c0e 100644
--- a/packages/frontend/src/pages/admin/abuse-report/notification-recipient.vue
+++ b/packages/frontend/src/pages/admin/abuse-report/notification-recipient.vue
@@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.root" class="_gaps_m">
<div :class="$style.addButton">
<MkButton primary @click="onAddButtonClicked">
- <span class="ti ti-plus"/> {{ i18n.ts._abuseReport._notificationRecipient.createRecipient }}
+ <span class="ti ti-plus"></span> {{ i18n.ts._abuseReport._notificationRecipient.createRecipient }}
</MkButton>
</div>
<div :class="$style.subMenus" class="_gaps_s">
diff --git a/packages/frontend/src/pages/admin/abuses.vue b/packages/frontend/src/pages/admin/abuses.vue
index 76bf20b409..2d204987cb 100644
--- a/packages/frontend/src/pages/admin/abuses.vue
+++ b/packages/frontend/src/pages/admin/abuses.vue
@@ -105,7 +105,7 @@ const paginator = markRaw(new Paginator('admin/abuse-user-reports', {
})),
}));
-function resolved(reportId) {
+function resolved(reportId: string) {
paginator.removeItem(reportId);
}
diff --git a/packages/frontend/src/pages/admin/ads.vue b/packages/frontend/src/pages/admin/ads.vue
index 94940a84ae..0efd1a2e28 100644
--- a/packages/frontend/src/pages/admin/ads.vue
+++ b/packages/frontend/src/pages/admin/ads.vue
@@ -22,22 +22,17 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #label>{{ i18n.ts.imageUrl }}</template>
</MkInput>
- <MkRadios v-model="ad.place">
+ <MkRadios
+ v-model="ad.place"
+ :options="[
+ { value: 'square' },
+ { value: 'horizontal' },
+ { value: 'horizontal-big' },
+ ]"
+ >
<template #label>Form</template>
- <option value="square">square</option>
- <option value="horizontal">horizontal</option>
- <option value="horizontal-big">horizontal-big</option>
</MkRadios>
- <!--
- <div style="margin: 32px 0;">
- {{ i18n.ts.priority }}
- <MkRadio v-model="ad.priority" value="high">{{ i18n.ts.high }}</MkRadio>
- <MkRadio v-model="ad.priority" value="middle">{{ i18n.ts.middle }}</MkRadio>
- <MkRadio v-model="ad.priority" value="low">{{ i18n.ts.low }}</MkRadio>
- </div>
- -->
-
<FormSplit>
<MkInput v-model="ad.ratio" type="number">
<template #label>{{ i18n.ts.ratio }}</template>
@@ -109,7 +104,11 @@ import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js';
import { useMkSelect } from '@/composables/use-mkselect.js';
-const ads = ref<Misskey.entities.Ad[]>([]);
+type Ad = Misskey.entities.Ad & {
+ place: 'square' | 'horizontal' | 'horizontal-big';
+};
+
+const ads = ref<Ad[]>([]);
// ISO形式はTZがUTCになってしまうので、TZ分ずらして時間を初期化
const localTime = new Date();
@@ -136,7 +135,7 @@ misskeyApi('admin/ad/list', { publishing: publishing }).then(adsResponse => {
exdate.setMilliseconds(exdate.getMilliseconds() - localTimeDiff);
stdate.setMilliseconds(stdate.getMilliseconds() - localTimeDiff);
return {
- ...r,
+ ...(r as Ad),
expiresAt: exdate.toISOString().slice(0, 16),
startsAt: stdate.toISOString().slice(0, 16),
};
@@ -239,7 +238,7 @@ function more() {
exdate.setMilliseconds(exdate.getMilliseconds() - localTimeDiff);
stdate.setMilliseconds(stdate.getMilliseconds() - localTimeDiff);
return {
- ...r,
+ ...(r as Ad),
expiresAt: exdate.toISOString().slice(0, 16),
startsAt: stdate.toISOString().slice(0, 16),
};
@@ -256,7 +255,7 @@ function refresh() {
exdate.setMilliseconds(exdate.getMilliseconds() - localTimeDiff);
stdate.setMilliseconds(stdate.getMilliseconds() - localTimeDiff);
return {
- ...r,
+ ...(r as Ad),
expiresAt: exdate.toISOString().slice(0, 16),
startsAt: stdate.toISOString().slice(0, 16),
};
diff --git a/packages/frontend/src/pages/admin/announcements.vue b/packages/frontend/src/pages/admin/announcements.vue
index b90a724b17..87fc6e70f4 100644
--- a/packages/frontend/src/pages/admin/announcements.vue
+++ b/packages/frontend/src/pages/admin/announcements.vue
@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div class="_spacer" style="--MI_SPACER-w: 900px;">
<div class="_gaps">
<MkInfo>{{ i18n.ts._announcement.shouldNotBeUsedToPresentPermanentInfo }}</MkInfo>
- <MkInfo v-if="announcements.length > 5" warn>{{ i18n.ts._announcement.tooManyActiveAnnouncementDescription }}</MkInfo>
+ <MkInfo v-if="announcementsStatus === 'active' && announcements.length > 5" warn>{{ i18n.ts._announcement.tooManyActiveAnnouncementDescription }}</MkInfo>
<MkSelect v-model="announcementsStatus" :items="announcementsStatusDef">
<template #label>{{ i18n.ts.filter }}</template>
@@ -45,18 +45,26 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkInput v-model="announcement.imageUrl" type="url">
<template #label>{{ i18n.ts.imageUrl }}</template>
</MkInput>
- <MkRadios v-model="announcement.icon">
+ <MkRadios
+ v-model="announcement.icon"
+ :options="[
+ { value: 'info', icon: 'ti ti-info-circle' },
+ { value: 'warning', icon: 'ti ti-alert-triangle', iconStyle: 'color: var(--MI_THEME-warn);' },
+ { value: 'error', icon: 'ti ti-circle-x', iconStyle: 'color: var(--MI_THEME-error);' },
+ { value: 'success', icon: 'ti ti-check', iconStyle: 'color: var(--MI_THEME-success);' },
+ ]"
+ >
<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(--MI_THEME-warn);"></i></option>
- <option value="error"><i class="ti ti-circle-x" style="color: var(--MI_THEME-error);"></i></option>
- <option value="success"><i class="ti ti-check" style="color: var(--MI_THEME-success);"></i></option>
</MkRadios>
- <MkRadios v-model="announcement.display">
+ <MkRadios
+ v-model="announcement.display"
+ :options="[
+ { value: 'normal', label: i18n.ts.normal },
+ { value: 'banner', label: i18n.ts.banner },
+ { value: 'dialog', label: i18n.ts.dialog },
+ ]"
+ >
<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">
@@ -83,6 +91,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref, computed, watch } from 'vue';
+import * as Misskey from 'misskey-js';
import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue';
import MkSelect from '@/components/MkSelect.vue';
@@ -112,7 +121,12 @@ const {
const loading = ref(true);
const loadingMore = ref(false);
-const announcements = ref<any[]>([]);
+const announcements = ref<(Omit<Misskey.entities.AdminAnnouncementsListResponse[number], 'id' | 'createdAt' | 'updatedAt' | 'reads' | 'isActive'> & {
+ id: string | null;
+ _id?: string;
+ isActive?: Misskey.entities.AdminAnnouncementsListResponse[number]['isActive'];
+ reads?: Misskey.entities.AdminAnnouncementsListResponse[number]['reads'];
+})[]>([]);
watch(announcementsStatus, (to) => {
loading.value = true;
@@ -136,42 +150,55 @@ function add() {
forExistingUsers: false,
silence: false,
needConfirmationToRead: false,
+ userId: null,
});
}
-function del(announcement) {
- os.confirm({
+async function del(announcement: (typeof announcements)['value'][number]) {
+ if (announcement.id == null) return;
+ const { canceled } = await os.confirm({
type: 'warning',
text: i18n.tsx.deleteAreYouSure({ x: announcement.title }),
- }).then(({ canceled }) => {
- if (canceled) return;
- announcements.value = announcements.value.filter(x => x !== announcement);
- misskeyApi('admin/announcements/delete', announcement);
+ });
+ if (canceled) return;
+ announcements.value = announcements.value.filter(x => x !== announcement);
+ misskeyApi('admin/announcements/delete', {
+ id: announcement.id,
});
}
-async function archive(announcement) {
+async function archive(announcement: (typeof announcements)['value'][number]) {
+ if (announcement.id == null) return;
+ const { _id, ...data } = announcement; // _idを消す
await os.apiWithDialog('admin/announcements/update', {
- ...announcement,
+ ...data,
+ id: announcement.id, // TSを黙らすため
isActive: false,
});
refresh();
}
-async function unarchive(announcement) {
+async function unarchive(announcement: (typeof announcements)['value'][number]) {
+ if (announcement.id == null) return;
+ const { _id, ...data } = announcement; // _idを消す
await os.apiWithDialog('admin/announcements/update', {
- ...announcement,
+ ...data,
+ id: announcement.id, // TSを黙らすため
isActive: true,
});
refresh();
}
-async function save(announcement) {
+async function save(announcement: (typeof announcements)['value'][number]) {
+ const { _id, ...data } = announcement; // _idを消す
if (announcement.id == null) {
- await os.apiWithDialog('admin/announcements/create', announcement);
+ await os.apiWithDialog('admin/announcements/create', data);
refresh();
} else {
- os.apiWithDialog('admin/announcements/update', announcement);
+ os.apiWithDialog('admin/announcements/update', {
+ ...data,
+ id: announcement.id, // TSを黙らすため
+ });
}
}
@@ -179,7 +206,7 @@ function more() {
loadingMore.value = true;
misskeyApi('admin/announcements/list', {
status: announcementsStatus.value,
- untilId: announcements.value.reduce((acc, announcement) => announcement.id != null ? announcement : acc).id,
+ untilId: announcements.value.reduce((acc, announcement) => announcement.id != null ? announcement : acc).id!,
}).then(announcementResponse => {
announcements.value = announcements.value.concat(announcementResponse);
loadingMore.value = false;
diff --git a/packages/frontend/src/pages/admin/bot-protection.vue b/packages/frontend/src/pages/admin/bot-protection.vue
index 7ed280358a..481969e1a3 100644
--- a/packages/frontend/src/pages/admin/bot-protection.vue
+++ b/packages/frontend/src/pages/admin/bot-protection.vue
@@ -19,13 +19,17 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<div class="_gaps_m">
- <MkRadios v-model="botProtectionForm.state.provider">
- <option value="none">{{ i18n.ts.none }} ({{ i18n.ts.notRecommended }})</option>
- <option value="hcaptcha">hCaptcha</option>
- <option value="mcaptcha">mCaptcha</option>
- <option value="recaptcha">reCAPTCHA</option>
- <option value="turnstile">Turnstile</option>
- <option value="testcaptcha">testCaptcha</option>
+ <MkRadios
+ v-model="botProtectionForm.state.provider"
+ :options="[
+ { value: 'none', label: `${i18n.ts.none} (${i18n.ts.notRecommended})` },
+ { value: 'hcaptcha', label: 'hCaptcha' },
+ { value: 'mcaptcha', label: 'mCaptcha' },
+ { value: 'recaptcha', label: 'reCAPTCHA' },
+ { value: 'turnstile', label: 'Turnstile' },
+ { value: 'testcaptcha', label: 'testCaptcha' },
+ ]"
+ >
</MkRadios>
<template v-if="botProtectionForm.state.provider === 'hcaptcha'">
diff --git a/packages/frontend/src/pages/admin/branding.vue b/packages/frontend/src/pages/admin/branding.vue
index e5e0f087e1..016d1b6a89 100644
--- a/packages/frontend/src/pages/admin/branding.vue
+++ b/packages/frontend/src/pages/admin/branding.vue
@@ -9,10 +9,14 @@ SPDX-License-Identifier: AGPL-3.0-only
<SearchMarker path="/admin/branding" :label="i18n.ts.branding" :keywords="['branding']" icon="ti ti-paint">
<div class="_gaps_m">
<SearchMarker :keywords="['entrance', 'welcome', 'landing', 'front', 'home', 'page', 'style']">
- <MkRadios v-model="entrancePageStyle">
+ <MkRadios
+ v-model="entrancePageStyle"
+ :options="[
+ { value: 'classic' },
+ { value: 'simple' },
+ ]"
+ >
<template #label><SearchLabel>{{ i18n.ts._serverSettings.entrancePageStyle }}</SearchLabel></template>
- <option value="classic">Classic</option>
- <option value="simple">Simple</option>
</MkRadios>
</SearchMarker>
@@ -151,8 +155,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref, computed } from 'vue';
import JSON5 from 'json5';
+import * as Misskey from 'misskey-js';
import { host } from '@@/js/config.js';
-import type { ClientOptions } from '@/instance.js';
import MkInput from '@/components/MkInput.vue';
import MkTextarea from '@/components/MkTextarea.vue';
import * as os from '@/os.js';
@@ -168,11 +172,11 @@ import MkSwitch from '@/components/MkSwitch.vue';
const meta = await misskeyApi('admin/meta');
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
-const entrancePageStyle = ref<ClientOptions['entrancePageStyle']>(meta.clientOptions.entrancePageStyle ?? 'classic');
+const entrancePageStyle = ref<Misskey.entities.MetaClientOptions['entrancePageStyle']>(meta.clientOptions.entrancePageStyle ?? 'classic');
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
-const showTimelineForVisitor = ref<ClientOptions['showTimelineForVisitor']>(meta.clientOptions.showTimelineForVisitor ?? true);
+const showTimelineForVisitor = ref<Misskey.entities.MetaClientOptions['showTimelineForVisitor']>(meta.clientOptions.showTimelineForVisitor ?? true);
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
-const showActivitiesForVisitor = ref<ClientOptions['showActivitiesForVisitor']>(meta.clientOptions.showActivitiesForVisitor ?? true);
+const showActivitiesForVisitor = ref<Misskey.entities.MetaClientOptions['showActivitiesForVisitor']>(meta.clientOptions.showActivitiesForVisitor ?? true);
const iconUrl = ref(meta.iconUrl);
const app192IconUrl = ref(meta.app192IconUrl);
@@ -191,11 +195,11 @@ const manifestJsonOverride = ref(meta.manifestJsonOverride === '' ? '{}' : JSON.
function save() {
os.apiWithDialog('admin/update-meta', {
- clientOptions: ({
+ clientOptions: {
entrancePageStyle: entrancePageStyle.value,
showTimelineForVisitor: showTimelineForVisitor.value,
showActivitiesForVisitor: showActivitiesForVisitor.value,
- } as ClientOptions) as any,
+ },
iconUrl: iconUrl.value,
app192IconUrl: app192IconUrl.value,
app512IconUrl: app512IconUrl.value,
diff --git a/packages/frontend/src/pages/admin/custom-emojis-manager.local.list.vue b/packages/frontend/src/pages/admin/custom-emojis-manager.local.list.vue
index 250abeebe2..6f58ab9857 100644
--- a/packages/frontend/src/pages/admin/custom-emojis-manager.local.list.vue
+++ b/packages/frontend/src/pages/admin/custom-emojis-manager.local.list.vue
@@ -51,6 +51,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts">
import type { SortOrder } from '@/components/MkSortOrderEditor.define.js';
import type { GridSortOrderKey } from './custom-emojis-manager.impl.js';
+import type { PageHeaderItem } from '@/types/page-header.js';
export type EmojiSearchQuery = {
name: string | null;
@@ -250,7 +251,7 @@ function setupGrid(): GridSetting {
icon: 'ti ti-trash',
action: () => {
removeDataFromGrid(context, (cell) => {
- gridItems.value[cell.row.index][cell.column.setting.bindTo] = undefined;
+ (gridItems.value[cell.row.index] as any)[cell.column.setting.bindTo] = undefined;
});
},
},
@@ -454,7 +455,7 @@ function onGridCellValidation(event: GridCellValidationEvent) {
function onGridCellValueChange(event: GridCellValueChangeEvent) {
const { row, column, newValue } = event;
if (gridItems.value.length > row.index && column.setting.bindTo in gridItems.value[row.index]) {
- gridItems.value[row.index][column.setting.bindTo] = newValue;
+ (gridItems.value[row.index] as any)[column.setting.bindTo] = newValue;
}
}
@@ -525,7 +526,7 @@ const headerPageMetadata = computed(() => ({
icon: 'ti ti-icons',
}));
-const headerActions = computed(() => [{
+const headerActions = computed<PageHeaderItem[]>(() => [{
icon: 'ti ti-search',
text: i18n.ts.search,
handler: async () => {
@@ -552,7 +553,7 @@ const headerActions = computed(() => [{
}, {
icon: 'ti ti-list-numbers',
text: i18n.ts._customEmojisManager._gridCommon.searchLimit,
- handler: (ev: MouseEvent) => {
+ handler: (ev) => {
async function changeSearchLimit(to: number) {
if (updatedItemsCount.value > 0) {
const { canceled } = await os.confirm({
diff --git a/packages/frontend/src/pages/admin/custom-emojis-manager.register.vue b/packages/frontend/src/pages/admin/custom-emojis-manager.register.vue
index c343d88eb1..7ccb166481 100644
--- a/packages/frontend/src/pages/admin/custom-emojis-manager.register.vue
+++ b/packages/frontend/src/pages/admin/custom-emojis-manager.register.vue
@@ -58,7 +58,6 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script setup lang="ts">
-/* eslint-disable @typescript-eslint/no-non-null-assertion */
import * as Misskey from 'misskey-js';
import { computed, onMounted, ref, useCssModule } from 'vue';
import type { RequestLogItem } from '@/pages/admin/custom-emojis-manager.impl.js';
@@ -339,7 +338,7 @@ function onGridCellValidation(event: GridCellValidationEvent) {
function onGridCellValueChange(event: GridCellValueChangeEvent) {
const { row, column, newValue } = event;
if (gridItems.value.length > row.index && column.setting.bindTo in gridItems.value[row.index]) {
- gridItems.value[row.index][column.setting.bindTo] = newValue;
+ (gridItems.value[row.index] as any)[column.setting.bindTo] = newValue;
}
}
diff --git a/packages/frontend/src/pages/admin/custom-emojis-manager.remote.vue b/packages/frontend/src/pages/admin/custom-emojis-manager.remote.vue
index 6317fc0b47..d5bfdffe34 100644
--- a/packages/frontend/src/pages/admin/custom-emojis-manager.remote.vue
+++ b/packages/frontend/src/pages/admin/custom-emojis-manager.remote.vue
@@ -306,7 +306,7 @@ function onGridEvent(event: GridEvent) {
function onGridCellValueChange(event: GridCellValueChangeEvent) {
const { row, column, newValue } = event;
if (gridItems.value.length > row.index && column.setting.bindTo in gridItems.value[row.index]) {
- gridItems.value[row.index][column.setting.bindTo] = newValue;
+ (gridItems.value[row.index] as any)[column.setting.bindTo] = newValue;
}
}
diff --git a/packages/frontend/src/pages/admin/custom-emojis-manager2.vue b/packages/frontend/src/pages/admin/custom-emojis-manager2.vue
index 14773d7f04..c947dc3256 100644
--- a/packages/frontend/src/pages/admin/custom-emojis-manager2.vue
+++ b/packages/frontend/src/pages/admin/custom-emojis-manager2.vue
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<PageWithHeader v-model:tab="headerTab" :tabs="headerTabs">
<XGridLocalComponent v-if="headerTab === 'local'" :class="$style.local"/>
- <XGridRemoteComponent v-else-if="headerTab === 'remote'" :class="$style.remote"/>
- <XRegisterComponent v-else-if="headerTab === 'register'" :class="$style.register"/>
+ <XGridRemoteComponent v-else-if="headerTab === 'remote'"/>
+ <XRegisterComponent v-else-if="headerTab === 'register'"/>
</PageWithHeader>
</template>
diff --git a/packages/frontend/src/pages/admin/federation-job-queue.chart.chart.vue b/packages/frontend/src/pages/admin/federation-job-queue.chart.chart.vue
index 420219c22c..04de781a28 100644
--- a/packages/frontend/src/pages/admin/federation-job-queue.chart.chart.vue
+++ b/packages/frontend/src/pages/admin/federation-job-queue.chart.chart.vue
@@ -28,7 +28,7 @@ const { handler: externalTooltipHandler } = useChartTooltip();
let chartInstance: Chart | null = null;
-function setData(values) {
+function setData(values: number[]) {
if (chartInstance == null || chartInstance.data.labels == null) return;
for (const value of values) {
chartInstance.data.labels.push('');
@@ -41,7 +41,7 @@ function setData(values) {
chartInstance.update();
}
-function pushData(value) {
+function pushData(value: number) {
if (chartInstance == null || chartInstance.data.labels == null) return;
chartInstance.data.labels.push('');
chartInstance.data.datasets[0].data.push(value);
diff --git a/packages/frontend/src/pages/admin/index.vue b/packages/frontend/src/pages/admin/index.vue
index 94994dc94c..b3a929faf4 100644
--- a/packages/frontend/src/pages/admin/index.vue
+++ b/packages/frontend/src/pages/admin/index.vue
@@ -294,7 +294,7 @@ function invite() {
});
}
-function adminLookup(ev: MouseEvent) {
+function adminLookup(ev: PointerEvent) {
os.popupMenu([{
text: i18n.ts.user,
icon: 'ti ti-user',
diff --git a/packages/frontend/src/pages/admin/job-queue.vue b/packages/frontend/src/pages/admin/job-queue.vue
index b18049cb11..97b6c2bc67 100644
--- a/packages/frontend/src/pages/admin/job-queue.vue
+++ b/packages/frontend/src/pages/admin/job-queue.vue
@@ -97,7 +97,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #header>
<MkTabs
v-model:tab="jobState"
- :class="$style.jobsTabs" :tabs="[{
+ :tabs="[{
key: 'all',
title: 'All',
icon: 'ti ti-code-asterisk',
@@ -359,8 +359,4 @@ definePage(() => ({
font-size: 85%;
margin: 6px 0;
}
-
-.jobsTabs {
-
-}
</style>
diff --git a/packages/frontend/src/pages/admin/overview.active-users.vue b/packages/frontend/src/pages/admin/overview.active-users.vue
index 32a5a6976e..9854ca7fc6 100644
--- a/packages/frontend/src/pages/admin/overview.active-users.vue
+++ b/packages/frontend/src/pages/admin/overview.active-users.vue
@@ -47,7 +47,7 @@ async function renderChart() {
return new Date(y, m, d - ago);
};
- const format = (arr) => {
+ const format = (arr: number[]) => {
return arr.map((v, i) => ({
x: getDate(i).getTime(),
y: v,
diff --git a/packages/frontend/src/pages/admin/overview.vue b/packages/frontend/src/pages/admin/overview.vue
index 2c550bd9c3..90799647ff 100644
--- a/packages/frontend/src/pages/admin/overview.vue
+++ b/packages/frontend/src/pages/admin/overview.vue
@@ -104,7 +104,7 @@ const filesPagination = {
noPaging: true,
};
-function onInstanceClick(i) {
+function onInstanceClick(i: Misskey.entities.FederationInstance) {
os.pageWindow(`/instance-info/${i.host}`);
}
diff --git a/packages/frontend/src/pages/admin/roles.edit.vue b/packages/frontend/src/pages/admin/roles.edit.vue
index b24b640527..e806f68162 100644
--- a/packages/frontend/src/pages/admin/roles.edit.vue
+++ b/packages/frontend/src/pages/admin/roles.edit.vue
@@ -21,8 +21,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { computed, ref } from 'vue';
import * as Misskey from 'misskey-js';
-import { genId } from '@/utility/id.js';
import XEditor from './roles.editor.vue';
+import { genId } from '@/utility/id.js';
import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
@@ -37,8 +37,13 @@ const props = defineProps<{
id?: string;
}>();
+type RoleLike = Pick<Misskey.entities.Role, 'name' | 'description' | 'isAdministrator' | 'isModerator' | 'color' | 'iconUrl' | 'target' | 'isPublic' | 'isExplorable' | 'asBadge' | 'canEditMembersByModerator' | 'displayOrder' | 'preserveAssignmentOnMoveAccount'> & {
+ condFormula: any;
+ policies: any;
+};
+
const role = ref<Misskey.entities.Role | null>(null);
-const data = ref<any>(null);
+const data = ref<RoleLike | null>(null);
if (props.id) {
role.value = await misskeyApi('admin/roles/show', {
@@ -61,11 +66,13 @@ if (props.id) {
asBadge: false,
canEditMembersByModerator: false,
displayOrder: 0,
+ preserveAssignmentOnMoveAccount: false,
policies: {},
};
}
async function save() {
+ if (data.value === null) return;
rolesCache.delete();
if (role.value) {
os.apiWithDialog('admin/roles/update', {
@@ -75,7 +82,7 @@ async function save() {
router.push('/admin/roles/:id', {
params: {
id: role.value.id,
- }
+ },
});
} else {
const created = await os.apiWithDialog('admin/roles/create', {
@@ -84,7 +91,7 @@ async function save() {
router.push('/admin/roles/:id', {
params: {
id: created.id,
- }
+ },
});
}
}
diff --git a/packages/frontend/src/pages/admin/roles.editor.vue b/packages/frontend/src/pages/admin/roles.editor.vue
index 5f8950f07e..7de973a394 100644
--- a/packages/frontend/src/pages/admin/roles.editor.vue
+++ b/packages/frontend/src/pages/admin/roles.editor.vue
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<div class="_gaps">
- <MkInput v-if="readonly" :modelValue="role.id" :readonly="true">
+ <MkInput v-if="readonly && role.id != null" :modelValue="role.id" :readonly="true">
<template #label>ID</template>
</MkInput>
@@ -866,12 +866,18 @@ import { i18n } from '@/i18n.js';
import { instance } from '@/instance.js';
import { deepClone } from '@/utility/clone.js';
+type RoleLike = Pick<Misskey.entities.Role, 'name' | 'description' | 'isAdministrator' | 'isModerator' | 'color' | 'iconUrl' | 'target' | 'isPublic' | 'isExplorable' | 'asBadge' | 'canEditMembersByModerator' | 'displayOrder' | 'preserveAssignmentOnMoveAccount'> & {
+ id?: Misskey.entities.Role['id'] | null;
+ condFormula: any;
+ policies: any;
+};
+
const emit = defineEmits<{
- (ev: 'update:modelValue', v: any): void;
+ (ev: 'update:modelValue', v: RoleLike): void;
}>();
const props = defineProps<{
- modelValue: any;
+ modelValue: RoleLike;
readonly?: boolean;
}>();
@@ -910,7 +916,7 @@ const rolePermission = computed<GetMkSelectValueTypesFromDef<typeof rolePermissi
const q = ref('');
-function getPriorityIcon(option) {
+function getPriorityIcon(option: { priority: number }): string {
if (option.priority === 2) return 'ti ti-arrows-up';
if (option.priority === 1) return 'ti ti-arrow-narrow-up';
return 'ti ti-point';
@@ -936,6 +942,7 @@ const save = throttle(100, () => {
isExplorable: role.value.isExplorable,
asBadge: role.value.asBadge,
canEditMembersByModerator: role.value.canEditMembersByModerator,
+ preserveAssignmentOnMoveAccount: role.value.preserveAssignmentOnMoveAccount,
policies: role.value.policies,
};
diff --git a/packages/frontend/src/pages/admin/roles.role.vue b/packages/frontend/src/pages/admin/roles.role.vue
index 2e249eee50..7fc51979af 100644
--- a/packages/frontend/src/pages/admin/roles.role.vue
+++ b/packages/frontend/src/pages/admin/roles.role.vue
@@ -28,15 +28,15 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #default="{ items }">
<div class="_gaps_s">
- <div v-for="item in items" :key="item.user.id" :class="[$style.userItem, { [$style.userItemOpened]: expandedItems.includes(item.id) }]">
+ <div v-for="item in items" :key="item.user.id" :class="[$style.userItem, { [$style.userItemOpened]: expandedItemIds.includes(item.id) }]">
<div :class="$style.userItemMain">
<MkA :class="$style.userItemMainBody" :to="`/admin/user/${item.user.id}`">
<MkUserCardMini :user="item.user"/>
</MkA>
- <button class="_button" :class="$style.userToggle" @click="toggleItem(item)"><i :class="$style.chevron" class="ti ti-chevron-down"></i></button>
- <button class="_button" :class="$style.unassign" @click="unassign(item.user, $event)"><i class="ti ti-x"></i></button>
+ <button class="_button" :class="$style.userToggle" @click="toggleItem(item.id)"><i :class="$style.chevron" class="ti ti-chevron-down"></i></button>
+ <button class="_button" :class="$style.unassign" @click="unassign(item.user.id, $event)"><i class="ti ti-x"></i></button>
</div>
- <div v-if="expandedItems.includes(item.id)" :class="$style.userItemSub">
+ <div v-if="expandedItemIds.includes(item.id)" :class="$style.userItemSub">
<div>Assigned: <MkTime :time="item.createdAt" mode="detail"/></div>
<div v-if="item.expiresAt">Period: {{ new Date(item.expiresAt).toLocaleString() }}</div>
<div v-else>Period: {{ i18n.ts.indefinitely }}</div>
@@ -55,6 +55,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { computed, markRaw, reactive, ref } from 'vue';
+import * as Misskey from 'misskey-js';
import XEditor from './roles.editor.vue';
import MkFolder from '@/components/MkFolder.vue';
import * as os from '@/os.js';
@@ -81,7 +82,7 @@ const usersPaginator = markRaw(new Paginator('admin/roles/users', {
}) : undefined),
}));
-const expandedItems = ref<string[]>([]);
+const expandedItemIds = ref<Misskey.entities.AdminRolesUsersResponse[number]['id'][]>([]);
const role = reactive(await misskeyApi('admin/roles/show', {
roleId: props.id,
@@ -91,7 +92,7 @@ function edit() {
router.push('/admin/roles/:id/edit', {
params: {
id: role.id,
- }
+ },
});
}
@@ -140,23 +141,23 @@ async function assign() {
//role.users.push(user);
}
-async function unassign(user, ev) {
+async function unassign(userId: Misskey.entities.User['id'], ev: PointerEvent) {
os.popupMenu([{
text: i18n.ts.unassign,
icon: 'ti ti-x',
danger: true,
action: async () => {
- await os.apiWithDialog('admin/roles/unassign', { roleId: role.id, userId: user.id });
- //role.users = role.users.filter(u => u.id !== user.id);
+ await os.apiWithDialog('admin/roles/unassign', { roleId: role.id, userId: userId });
+ //role.users = role.users.filter(u => u.id !== userId);
},
}], ev.currentTarget ?? ev.target);
}
-async function toggleItem(item) {
- if (expandedItems.value.includes(item.id)) {
- expandedItems.value = expandedItems.value.filter(x => x !== item.id);
+async function toggleItem(itemId: string) {
+ if (expandedItemIds.value.includes(itemId)) {
+ expandedItemIds.value = expandedItemIds.value.filter(x => x !== itemId);
} else {
- expandedItems.value.push(item.id);
+ expandedItemIds.value.push(itemId);
}
}
diff --git a/packages/frontend/src/pages/admin/security.vue b/packages/frontend/src/pages/admin/security.vue
index fa93124daa..f310f26107 100644
--- a/packages/frontend/src/pages/admin/security.vue
+++ b/packages/frontend/src/pages/admin/security.vue
@@ -25,11 +25,15 @@ SPDX-License-Identifier: AGPL-3.0-only
<div class="_gaps_m">
<div><SearchText>{{ i18n.ts._sensitiveMediaDetection.description }}</SearchText></div>
- <MkRadios v-model="sensitiveMediaDetectionForm.state.sensitiveMediaDetection">
- <option value="none">{{ i18n.ts.none }}</option>
- <option value="all">{{ i18n.ts.all }}</option>
- <option value="local">{{ i18n.ts.localOnly }}</option>
- <option value="remote">{{ i18n.ts.remoteOnly }}</option>
+ <MkRadios
+ v-model="sensitiveMediaDetectionForm.state.sensitiveMediaDetection"
+ :options="[
+ { value: 'none', label: i18n.ts.none },
+ { value: 'all', label: i18n.ts.all },
+ { value: 'local', label: i18n.ts.localOnly },
+ { value: 'remote', label: i18n.ts.remoteOnly },
+ ]"
+ >
</MkRadios>
<SearchMarker :keywords="['sensitivity']">
diff --git a/packages/frontend/src/pages/admin/server-rules.vue b/packages/frontend/src/pages/admin/server-rules.vue
index d26f02b41c..02aad732f6 100644
--- a/packages/frontend/src/pages/admin/server-rules.vue
+++ b/packages/frontend/src/pages/admin/server-rules.vue
@@ -12,28 +12,25 @@ SPDX-License-Identifier: AGPL-3.0-only
<div class="_gaps_m">
<div><SearchText>{{ i18n.ts._serverRules.description }}</SearchText></div>
- <Sortable
+ <MkDraggable
v-model="serverRules"
- class="_gaps_m"
- :itemKey="(_, i) => i"
- :animation="150"
- :handle="'.' + $style.itemHandle"
- @start="e => e.item.classList.add('active')"
- @end="e => e.item.classList.remove('active')"
+ direction="vertical"
+ withGaps
+ manualDragStart
>
- <template #item="{element,index}">
+ <template #default="{ item, index, dragStart }">
<div :class="$style.item">
<div :class="$style.itemHeader">
- <div :class="$style.itemNumber" v-text="String(index + 1)"/>
- <span :class="$style.itemHandle"><i class="ti ti-menu"/></span>
- <button class="_button" :class="$style.itemRemove" @click="remove(index)"><i class="ti ti-x"></i></button>
+ <div :class="$style.itemNumber">{{ index + 1 }}</div>
+ <span :class="$style.itemHandle" :draggable="true" @dragstart.stop="dragStart"><i class="ti ti-menu"></i></span>
+ <button class="_button" :class="$style.itemRemove" @click="remove(item.id)"><i class="ti ti-x"></i></button>
</div>
- <MkInput v-model="serverRules[index]"/>
+ <MkInput :modelValue="item.text" @update:modelValue="serverRules[index].text = $event"/>
</div>
</template>
- </Sortable>
+ </MkDraggable>
<div :class="$style.commands">
- <MkButton rounded @click="serverRules.push('')"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton>
+ <MkButton rounded @click="add"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton>
<MkButton primary rounded @click="save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton>
</div>
</div>
@@ -42,28 +39,31 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { defineAsyncComponent, ref, computed } from 'vue';
+import { ref } from 'vue';
import * as os from '@/os.js';
import { fetchInstance, instance } from '@/instance.js';
import { i18n } from '@/i18n.js';
import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue';
import MkFolder from '@/components/MkFolder.vue';
+import MkDraggable from '@/components/MkDraggable.vue';
-const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));
+const serverRules = ref<{ text: string; id: string; }[]>(instance.serverRules.map(text => ({ text, id: Math.random().toString() })));
-const serverRules = ref<string[]>(instance.serverRules);
-
-const save = async () => {
+async function save() {
await os.apiWithDialog('admin/update-meta', {
- serverRules: serverRules.value,
+ serverRules: serverRules.value.map(r => r.text),
});
fetchInstance(true);
-};
+}
-const remove = (index: number): void => {
- serverRules.value.splice(index, 1);
-};
+function add(): void {
+ serverRules.value.push({ text: '', id: Math.random().toString() });
+}
+
+function remove(id: string): void {
+ serverRules.value = serverRules.value.filter(r => r.id !== id);
+}
</script>
<style lang="scss" module>
diff --git a/packages/frontend/src/pages/admin/settings.vue b/packages/frontend/src/pages/admin/settings.vue
index 541ee7c0cd..99d4455939 100644
--- a/packages/frontend/src/pages/admin/settings.vue
+++ b/packages/frontend/src/pages/admin/settings.vue
@@ -258,11 +258,15 @@ SPDX-License-Identifier: AGPL-3.0-only
<div class="_gaps">
<SearchMarker>
- <MkRadios v-model="federationForm.state.federation">
+ <MkRadios
+ v-model="federationForm.state.federation"
+ :options="[
+ { value: 'all', label: i18n.ts.all },
+ { value: 'specified', label: i18n.ts.specifyHost },
+ { value: 'none', label: i18n.ts.none },
+ ]"
+ >
<template #label><SearchLabel>{{ i18n.ts.behavior }}</SearchLabel><span v-if="federationForm.modifiedStates.federation" class="_modified">{{ i18n.ts.modified }}</span></template>
- <option value="all">{{ i18n.ts.all }}</option>
- <option value="specified">{{ i18n.ts.specifyHost }}</option>
- <option value="none">{{ i18n.ts.none }}</option>
</MkRadios>
</SearchMarker>
diff --git a/packages/frontend/src/pages/admin/system-webhook.item.vue b/packages/frontend/src/pages/admin/system-webhook.item.vue
index b53667e98c..9807cbb313 100644
--- a/packages/frontend/src/pages/admin/system-webhook.item.vue
+++ b/packages/frontend/src/pages/admin/system-webhook.item.vue
@@ -8,14 +8,14 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #label>{{ entity.name || entity.url }}</template>
<template v-if="entity.name != null && entity.name != ''" #caption>{{ entity.url }}</template>
<template #icon>
- <i v-if="!entity.isActive" class="ti ti-player-pause"/>
- <i v-else-if="entity.latestStatus === null" class="ti ti-circle"/>
+ <i v-if="!entity.isActive" class="ti ti-player-pause"></i>
+ <i v-else-if="entity.latestStatus === null" class="ti ti-circle"></i>
<i
v-else-if="[200, 201, 204].includes(entity.latestStatus)"
class="ti ti-check"
:style="{ color: 'var(--MI_THEME-success)' }"
- />
- <i v-else class="ti ti-alert-triangle" :style="{ color: 'var(--MI_THEME-error)' }"/>
+ ></i>
+ <i v-else class="ti ti-alert-triangle" :style="{ color: 'var(--MI_THEME-error)' }"></i>
</template>
<template #suffix>
<MkTime v-if="entity.latestSentAt" :time="entity.latestSentAt" style="margin-right: 8px"/>
diff --git a/packages/frontend/src/pages/admin/users.vue b/packages/frontend/src/pages/admin/users.vue
index 2f7ecca521..eb9806d668 100644
--- a/packages/frontend/src/pages/admin/users.vue
+++ b/packages/frontend/src/pages/admin/users.vue
@@ -46,6 +46,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { computed, markRaw, ref, watchEffect } from 'vue';
+import * as Misskey from 'misskey-js';
import { defaultMemoryStorage } from '@/memory-storage';
import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue';
@@ -146,7 +147,7 @@ async function addUser() {
});
}
-function show(user) {
+function show(user: Misskey.entities.UserDetailed) {
os.pageWindow(`/admin/user/${user.id}`);
}