diff options
Diffstat (limited to 'packages/frontend/src/pages/admin')
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}`); } |