Merge branch 'develop' into qr
This commit is contained in:
commit
bd9e211f76
|
@ -39,13 +39,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts">
|
||||||
import { onMounted, nextTick, ref, watch, computed, toRefs, useSlots } from 'vue';
|
|
||||||
import { useInterval } from '@@/js/use-interval.js';
|
|
||||||
import type { VNode, VNodeChild } from 'vue';
|
|
||||||
import type { MenuItem } from '@/types/menu.js';
|
|
||||||
import * as os from '@/os.js';
|
|
||||||
|
|
||||||
type ItemOption = {
|
type ItemOption = {
|
||||||
type?: 'option';
|
type?: 'option';
|
||||||
value: string | number | null;
|
value: string | number | null;
|
||||||
|
@ -60,11 +54,32 @@ type ItemGroup = {
|
||||||
|
|
||||||
export type MkSelectItem = ItemOption | ItemGroup;
|
export type MkSelectItem = ItemOption | ItemGroup;
|
||||||
|
|
||||||
|
type ValuesOfItems<T> = T extends (infer U)[]
|
||||||
|
? U extends { type: 'group'; items: infer V }
|
||||||
|
? V extends (infer W)[]
|
||||||
|
? W extends { value: infer X }
|
||||||
|
? X
|
||||||
|
: never
|
||||||
|
: never
|
||||||
|
: U extends { value: infer Y }
|
||||||
|
? Y
|
||||||
|
: never
|
||||||
|
: never;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts" setup generic="T extends MkSelectItem[]">
|
||||||
|
import { onMounted, nextTick, ref, watch, computed, toRefs, useSlots } from 'vue';
|
||||||
|
import { useInterval } from '@@/js/use-interval.js';
|
||||||
|
import type { VNode, VNodeChild } from 'vue';
|
||||||
|
import type { MenuItem } from '@/types/menu.js';
|
||||||
|
import * as os from '@/os.js';
|
||||||
|
|
||||||
// TODO: itemsをslot内のoptionで指定する用法は廃止する(props.itemsを必須化する)
|
// TODO: itemsをslot内のoptionで指定する用法は廃止する(props.itemsを必須化する)
|
||||||
// see: https://github.com/misskey-dev/misskey/issues/15558
|
// see: https://github.com/misskey-dev/misskey/issues/15558
|
||||||
|
// あと型推論と相性が良くない
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue: string | number | null;
|
modelValue: ValuesOfItems<T>;
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
readonly?: boolean;
|
readonly?: boolean;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
@ -73,11 +88,11 @@ const props = defineProps<{
|
||||||
inline?: boolean;
|
inline?: boolean;
|
||||||
small?: boolean;
|
small?: boolean;
|
||||||
large?: boolean;
|
large?: boolean;
|
||||||
items?: MkSelectItem[];
|
items?: T;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(ev: 'update:modelValue', value: string | number | null): void;
|
(ev: 'update:modelValue', value: ValuesOfItems<T>): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const slots = useSlots();
|
const slots = useSlots();
|
||||||
|
|
|
@ -22,20 +22,46 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<option value="blocked">{{ i18n.ts.blocked }}</option>
|
<option value="blocked">{{ i18n.ts.blocked }}</option>
|
||||||
<option value="notResponding">{{ i18n.ts.notResponding }}</option>
|
<option value="notResponding">{{ i18n.ts.notResponding }}</option>
|
||||||
</MkSelect>
|
</MkSelect>
|
||||||
<MkSelect v-model="sort">
|
<MkSelect
|
||||||
|
v-model="sort" :items="[{
|
||||||
|
label: `${i18n.ts.pubSub} (${i18n.ts.descendingOrder})`,
|
||||||
|
value: '+pubSub',
|
||||||
|
}, {
|
||||||
|
label: `${i18n.ts.pubSub} (${i18n.ts.ascendingOrder})`,
|
||||||
|
value: '-pubSub',
|
||||||
|
}, {
|
||||||
|
label: `${i18n.ts.notes} (${i18n.ts.descendingOrder})`,
|
||||||
|
value: '+notes',
|
||||||
|
}, {
|
||||||
|
label: `${i18n.ts.notes} (${i18n.ts.ascendingOrder})`,
|
||||||
|
value: '-notes',
|
||||||
|
}, {
|
||||||
|
label: `${i18n.ts.users} (${i18n.ts.descendingOrder})`,
|
||||||
|
value: '+users',
|
||||||
|
}, {
|
||||||
|
label: `${i18n.ts.users} (${i18n.ts.ascendingOrder})`,
|
||||||
|
value: '-users',
|
||||||
|
}, {
|
||||||
|
label: `${i18n.ts.following} (${i18n.ts.descendingOrder})`,
|
||||||
|
value: '+following',
|
||||||
|
}, {
|
||||||
|
label: `${i18n.ts.following} (${i18n.ts.ascendingOrder})`,
|
||||||
|
value: '-following',
|
||||||
|
}, {
|
||||||
|
label: `${i18n.ts.followers} (${i18n.ts.descendingOrder})`,
|
||||||
|
value: '+followers',
|
||||||
|
}, {
|
||||||
|
label: `${i18n.ts.followers} (${i18n.ts.ascendingOrder})`,
|
||||||
|
value: '-followers',
|
||||||
|
}, {
|
||||||
|
label: `${i18n.ts.registeredAt} (${i18n.ts.descendingOrder})`,
|
||||||
|
value: '+firstRetrievedAt',
|
||||||
|
}, {
|
||||||
|
label: `${i18n.ts.registeredAt} (${i18n.ts.ascendingOrder})`,
|
||||||
|
value: '-firstRetrievedAt',
|
||||||
|
}] as const"
|
||||||
|
>
|
||||||
<template #label>{{ i18n.ts.sort }}</template>
|
<template #label>{{ i18n.ts.sort }}</template>
|
||||||
<option value="+pubSub">{{ i18n.ts.pubSub }} ({{ i18n.ts.descendingOrder }})</option>
|
|
||||||
<option value="-pubSub">{{ i18n.ts.pubSub }} ({{ i18n.ts.ascendingOrder }})</option>
|
|
||||||
<option value="+notes">{{ i18n.ts.notes }} ({{ i18n.ts.descendingOrder }})</option>
|
|
||||||
<option value="-notes">{{ i18n.ts.notes }} ({{ i18n.ts.ascendingOrder }})</option>
|
|
||||||
<option value="+users">{{ i18n.ts.users }} ({{ i18n.ts.descendingOrder }})</option>
|
|
||||||
<option value="-users">{{ i18n.ts.users }} ({{ i18n.ts.ascendingOrder }})</option>
|
|
||||||
<option value="+following">{{ i18n.ts.following }} ({{ i18n.ts.descendingOrder }})</option>
|
|
||||||
<option value="-following">{{ i18n.ts.following }} ({{ i18n.ts.ascendingOrder }})</option>
|
|
||||||
<option value="+followers">{{ i18n.ts.followers }} ({{ i18n.ts.descendingOrder }})</option>
|
|
||||||
<option value="-followers">{{ i18n.ts.followers }} ({{ i18n.ts.ascendingOrder }})</option>
|
|
||||||
<option value="+firstRetrievedAt">{{ i18n.ts.registeredAt }} ({{ i18n.ts.descendingOrder }})</option>
|
|
||||||
<option value="-firstRetrievedAt">{{ i18n.ts.registeredAt }} ({{ i18n.ts.ascendingOrder }})</option>
|
|
||||||
</MkSelect>
|
</MkSelect>
|
||||||
</FormSplit>
|
</FormSplit>
|
||||||
</div>
|
</div>
|
||||||
|
@ -52,6 +78,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, markRaw, ref } from 'vue';
|
import { computed, markRaw, ref } from 'vue';
|
||||||
|
import * as Misskey from 'misskey-js';
|
||||||
import MkInput from '@/components/MkInput.vue';
|
import MkInput from '@/components/MkInput.vue';
|
||||||
import MkSelect from '@/components/MkSelect.vue';
|
import MkSelect from '@/components/MkSelect.vue';
|
||||||
import MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
|
@ -62,7 +89,7 @@ import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
const host = ref('');
|
const host = ref('');
|
||||||
const state = ref('federating');
|
const state = ref('federating');
|
||||||
const sort = ref('+pubSub');
|
const sort = ref<NonNullable<Misskey.entities.FederationInstancesRequest['sort']>>('+pubSub');
|
||||||
const paginator = markRaw(new Paginator('federation/instances', {
|
const paginator = markRaw(new Paginator('federation/instances', {
|
||||||
limit: 10,
|
limit: 10,
|
||||||
offsetMode: true,
|
offsetMode: true,
|
||||||
|
|
|
@ -48,34 +48,22 @@ watch(tab, () => {
|
||||||
|
|
||||||
const headerActions = computed(() => []);
|
const headerActions = computed(() => []);
|
||||||
|
|
||||||
const headerTabs = computed(() => {
|
const headerTabs = computed(() => [{
|
||||||
const items = [];
|
key: 'overview',
|
||||||
|
title: i18n.ts.overview,
|
||||||
items.push({
|
}, {
|
||||||
key: 'overview',
|
key: 'emojis',
|
||||||
title: i18n.ts.overview,
|
title: i18n.ts.customEmojis,
|
||||||
}, {
|
icon: 'ti ti-icons',
|
||||||
key: 'emojis',
|
}, ...(instance.federation !== 'none' ? [{
|
||||||
title: i18n.ts.customEmojis,
|
key: 'federation',
|
||||||
icon: 'ti ti-icons',
|
title: i18n.ts.federation,
|
||||||
});
|
icon: 'ti ti-whirl',
|
||||||
|
}] : []), {
|
||||||
if (instance.federation !== 'none') {
|
key: 'charts',
|
||||||
items.push({
|
title: i18n.ts.charts,
|
||||||
key: 'federation',
|
icon: 'ti ti-chart-line',
|
||||||
title: i18n.ts.federation,
|
}]);
|
||||||
icon: 'ti ti-whirl',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
items.push({
|
|
||||||
key: 'charts',
|
|
||||||
title: i18n.ts.charts,
|
|
||||||
icon: 'ti ti-chart-line',
|
|
||||||
});
|
|
||||||
|
|
||||||
return items;
|
|
||||||
});
|
|
||||||
|
|
||||||
definePage(() => ({
|
definePage(() => ({
|
||||||
title: i18n.ts.instanceInfo,
|
title: i18n.ts.instanceInfo,
|
||||||
|
|
|
@ -6,58 +6,57 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template>
|
<template>
|
||||||
<PageWithHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs">
|
<PageWithHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs">
|
||||||
<div class="_spacer" style="--MI_SPACER-w: 600px; --MI_SPACER-min: 16px; --MI_SPACER-max: 32px;">
|
<div class="_spacer" style="--MI_SPACER-w: 600px; --MI_SPACER-min: 16px; --MI_SPACER-max: 32px;">
|
||||||
<FormSuspense :p="init">
|
<div v-if="tab === 'overview'" class="_gaps_m">
|
||||||
<div v-if="tab === 'overview'" class="_gaps_m">
|
<div class="aeakzknw">
|
||||||
<div class="aeakzknw">
|
<MkAvatar class="avatar" :user="user" indicator link preview/>
|
||||||
<MkAvatar class="avatar" :user="user" indicator link preview/>
|
<div class="body">
|
||||||
<div class="body">
|
<span class="name"><MkUserName class="name" :user="user"/></span>
|
||||||
<span class="name"><MkUserName class="name" :user="user"/></span>
|
<span class="sub"><span class="acct _monospace">@{{ acct(user) }}</span></span>
|
||||||
<span class="sub"><span class="acct _monospace">@{{ acct(user) }}</span></span>
|
<span class="state">
|
||||||
<span class="state">
|
<span v-if="suspended" class="suspended">Suspended</span>
|
||||||
<span v-if="suspended" class="suspended">Suspended</span>
|
<span v-if="silenced" class="silenced">Silenced</span>
|
||||||
<span v-if="silenced" class="silenced">Silenced</span>
|
<span v-if="moderator" class="moderator">Moderator</span>
|
||||||
<span v-if="moderator" class="moderator">Moderator</span>
|
</span>
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<MkInfo v-if="isSystem">{{ i18n.ts.isSystemAccount }}</MkInfo>
|
<MkInfo v-if="isSystem">{{ i18n.ts.isSystemAccount }}</MkInfo>
|
||||||
|
|
||||||
<FormLink v-if="user.host" :to="`/instance-info/${user.host}`">{{ i18n.ts.instanceInfo }}</FormLink>
|
<FormLink v-if="user.host" :to="`/instance-info/${user.host}`">{{ i18n.ts.instanceInfo }}</FormLink>
|
||||||
|
|
||||||
<div style="display: flex; flex-direction: column; gap: 1em;">
|
<div style="display: flex; flex-direction: column; gap: 1em;">
|
||||||
<MkKeyValue :copy="user.id" oneline>
|
<MkKeyValue :copy="user.id" oneline>
|
||||||
<template #key>ID</template>
|
<template #key>ID</template>
|
||||||
<template #value><span class="_monospace">{{ user.id }}</span></template>
|
<template #value><span class="_monospace">{{ user.id }}</span></template>
|
||||||
</MkKeyValue>
|
</MkKeyValue>
|
||||||
<!-- 要る?
|
<!-- 要る?
|
||||||
<MkKeyValue v-if="ips.length > 0" :copy="user.id" oneline>
|
<MkKeyValue v-if="ips.length > 0" :copy="user.id" oneline>
|
||||||
<template #key>IP (recent)</template>
|
<template #key>IP (recent)</template>
|
||||||
<template #value><span class="_monospace">{{ ips[0].ip }}</span></template>
|
<template #value><span class="_monospace">{{ ips[0].ip }}</span></template>
|
||||||
</MkKeyValue>
|
</MkKeyValue>
|
||||||
-->
|
-->
|
||||||
<template v-if="!isSystem">
|
<template v-if="!isSystem">
|
||||||
<MkKeyValue oneline>
|
<MkKeyValue oneline>
|
||||||
<template #key>{{ i18n.ts.createdAt }}</template>
|
<template #key>{{ i18n.ts.createdAt }}</template>
|
||||||
<template #value><span class="_monospace"><MkTime :time="user.createdAt" :mode="'detail'"/></span></template>
|
<template #value><span class="_monospace"><MkTime :time="user.createdAt" :mode="'detail'"/></span></template>
|
||||||
</MkKeyValue>
|
</MkKeyValue>
|
||||||
<MkKeyValue v-if="info" oneline>
|
<MkKeyValue v-if="info" oneline>
|
||||||
<template #key>{{ i18n.ts.lastActiveDate }}</template>
|
<template #key>{{ i18n.ts.lastActiveDate }}</template>
|
||||||
<template #value><span class="_monospace"><MkTime :time="info.lastActiveDate" :mode="'detail'"/></span></template>
|
<template #value><span class="_monospace"><MkTime :time="info.lastActiveDate" :mode="'detail'"/></span></template>
|
||||||
</MkKeyValue>
|
</MkKeyValue>
|
||||||
<MkKeyValue v-if="info" oneline>
|
<MkKeyValue v-if="info" oneline>
|
||||||
<template #key>{{ i18n.ts.email }}</template>
|
<template #key>{{ i18n.ts.email }}</template>
|
||||||
<template #value><span class="_monospace">{{ info.email }}</span></template>
|
<template #value><span class="_monospace">{{ info.email }}</span></template>
|
||||||
</MkKeyValue>
|
</MkKeyValue>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<MkTextarea v-if="!isSystem" v-model="moderationNote" manualSave>
|
<MkTextarea v-if="!isSystem" v-model="moderationNote" manualSave>
|
||||||
<template #label>{{ i18n.ts.moderationNote }}</template>
|
<template #label>{{ i18n.ts.moderationNote }}</template>
|
||||||
<template #caption>{{ i18n.ts.moderationNoteDescription }}</template>
|
<template #caption>{{ i18n.ts.moderationNoteDescription }}</template>
|
||||||
</MkTextarea>
|
</MkTextarea>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
<FormSection>
|
<FormSection>
|
||||||
<template #label>ActivityPub</template>
|
<template #label>ActivityPub</template>
|
||||||
|
|
||||||
|
@ -93,119 +92,118 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</FormSection>
|
</FormSection>
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<FormSection v-if="!isSystem">
|
<FormSection v-if="!isSystem">
|
||||||
<div class="_gaps">
|
<div class="_gaps">
|
||||||
<MkSwitch v-model="suspended" @update:modelValue="toggleSuspend">{{ i18n.ts.suspend }}</MkSwitch>
|
<MkSwitch v-model="suspended" @update:modelValue="toggleSuspend">{{ i18n.ts.suspend }}</MkSwitch>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<MkButton v-if="user.host == null" inline style="margin-right: 8px;" @click="resetPassword"><i class="ti ti-key"></i> {{ i18n.ts.resetPassword }}</MkButton>
|
<MkButton v-if="user.host == null" inline style="margin-right: 8px;" @click="resetPassword"><i class="ti ti-key"></i> {{ i18n.ts.resetPassword }}</MkButton>
|
||||||
</div>
|
|
||||||
|
|
||||||
<MkFolder>
|
|
||||||
<template #icon><i class="ti ti-license"></i></template>
|
|
||||||
<template #label>{{ i18n.ts._role.policies }}</template>
|
|
||||||
<div class="_gaps">
|
|
||||||
<div v-for="policy in Object.keys(info.policies)" :key="policy">
|
|
||||||
{{ policy }} ... {{ info.policies[policy] }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</MkFolder>
|
|
||||||
|
|
||||||
<MkFolder>
|
|
||||||
<template #icon><i class="ti ti-password"></i></template>
|
|
||||||
<template #label>IP</template>
|
|
||||||
<MkInfo v-if="!iAmAdmin" warn>{{ i18n.ts.requireAdminForView }}</MkInfo>
|
|
||||||
<MkInfo v-else>The date is the IP address was first acknowledged.</MkInfo>
|
|
||||||
<template v-if="iAmAdmin && ips">
|
|
||||||
<div v-for="record in ips" :key="record.ip" class="_monospace" :class="$style.ip" style="margin: 1em 0;">
|
|
||||||
<span class="date">{{ record.createdAt }}</span>
|
|
||||||
<span class="ip">{{ record.ip }}</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</MkFolder>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<MkButton v-if="iAmModerator" inline danger style="margin-right: 8px;" @click="unsetUserAvatar"><i class="ti ti-user-circle"></i> {{ i18n.ts.unsetUserAvatar }}</MkButton>
|
|
||||||
<MkButton v-if="iAmModerator" inline danger @click="unsetUserBanner"><i class="ti ti-photo"></i> {{ i18n.ts.unsetUserBanner }}</MkButton>
|
|
||||||
</div>
|
|
||||||
<MkButton v-if="$i.isAdmin" inline danger @click="deleteAccount">{{ i18n.ts.deleteAccount }}</MkButton>
|
|
||||||
</div>
|
</div>
|
||||||
</FormSection>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else-if="tab === 'roles'" class="_gaps">
|
<MkFolder>
|
||||||
<MkButton v-if="user.host == null" primary rounded @click="assignRole"><i class="ti ti-plus"></i> {{ i18n.ts.assign }}</MkButton>
|
<template #icon><i class="ti ti-license"></i></template>
|
||||||
|
<template #label>{{ i18n.ts._role.policies }}</template>
|
||||||
<div v-for="role in info.roles" :key="role.id">
|
<div class="_gaps">
|
||||||
<div :class="$style.roleItemMain">
|
<div v-for="policy in Object.keys(info.policies)" :key="policy">
|
||||||
<MkRolePreview :class="$style.role" :role="role" :forModeration="true"/>
|
{{ policy }} ... {{ info.policies[policy] }}
|
||||||
<button class="_button" @click="toggleRoleItem(role)"><i class="ti ti-chevron-down"></i></button>
|
|
||||||
<button v-if="role.target === 'manual'" class="_button" :class="$style.roleUnassign" @click="unassignRole(role, $event)"><i class="ti ti-x"></i></button>
|
|
||||||
<button v-else class="_button" :class="$style.roleUnassign" disabled><i class="ti ti-ban"></i></button>
|
|
||||||
</div>
|
|
||||||
<div v-if="expandedRoles.includes(role.id)" :class="$style.roleItemSub">
|
|
||||||
<div>Assigned: <MkTime :time="info.roleAssigns.find(a => a.roleId === role.id).createdAt" mode="detail"/></div>
|
|
||||||
<div v-if="info.roleAssigns.find(a => a.roleId === role.id).expiresAt">Period: {{ new Date(info.roleAssigns.find(a => a.roleId === role.id).expiresAt).toLocaleString() }}</div>
|
|
||||||
<div v-else>Period: {{ i18n.ts.indefinitely }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else-if="tab === 'announcements'" class="_gaps">
|
|
||||||
<MkButton primary rounded @click="createAnnouncement"><i class="ti ti-plus"></i> {{ i18n.ts.new }}</MkButton>
|
|
||||||
|
|
||||||
<MkSelect v-model="announcementsStatus">
|
|
||||||
<template #label>{{ i18n.ts.filter }}</template>
|
|
||||||
<option value="active">{{ i18n.ts.active }}</option>
|
|
||||||
<option value="archived">{{ i18n.ts.archived }}</option>
|
|
||||||
</MkSelect>
|
|
||||||
|
|
||||||
<MkPagination :paginator="announcementsPaginator">
|
|
||||||
<template #default="{ items }">
|
|
||||||
<div class="_gaps_s">
|
|
||||||
<div v-for="announcement in items" :key="announcement.id" v-panel :class="$style.announcementItem" @click="editAnnouncement(announcement)">
|
|
||||||
<span style="margin-right: 0.5em;">
|
|
||||||
<i v-if="announcement.icon === 'info'" class="ti ti-info-circle"></i>
|
|
||||||
<i v-else-if="announcement.icon === 'warning'" class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i>
|
|
||||||
<i v-else-if="announcement.icon === 'error'" class="ti ti-circle-x" style="color: var(--MI_THEME-error);"></i>
|
|
||||||
<i v-else-if="announcement.icon === 'success'" class="ti ti-check" style="color: var(--MI_THEME-success);"></i>
|
|
||||||
</span>
|
|
||||||
<span>{{ announcement.title }}</span>
|
|
||||||
<span v-if="announcement.reads > 0" style="margin-left: auto; opacity: 0.7;">{{ i18n.ts.messageRead }}</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</MkFolder>
|
||||||
</MkPagination>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else-if="tab === 'drive'" class="_gaps">
|
<MkFolder>
|
||||||
<MkFileListForAdmin :paginator="filesPaginator" viewMode="grid"/>
|
<template #icon><i class="ti ti-password"></i></template>
|
||||||
</div>
|
<template #label>IP</template>
|
||||||
|
<MkInfo v-if="!iAmAdmin" warn>{{ i18n.ts.requireAdminForView }}</MkInfo>
|
||||||
|
<MkInfo v-else>The date is the IP address was first acknowledged.</MkInfo>
|
||||||
|
<template v-if="iAmAdmin && ips">
|
||||||
|
<div v-for="record in ips" :key="record.ip" class="_monospace" :class="$style.ip" style="margin: 1em 0;">
|
||||||
|
<span class="date">{{ record.createdAt }}</span>
|
||||||
|
<span class="ip">{{ record.ip }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</MkFolder>
|
||||||
|
|
||||||
<div v-else-if="tab === 'chart'" class="_gaps_m">
|
<div>
|
||||||
<div class="cmhjzshm">
|
<MkButton v-if="iAmModerator" inline danger style="margin-right: 8px;" @click="unsetUserAvatar"><i class="ti ti-user-circle"></i> {{ i18n.ts.unsetUserAvatar }}</MkButton>
|
||||||
<div class="selects">
|
<MkButton v-if="iAmModerator" inline danger @click="unsetUserBanner"><i class="ti ti-photo"></i> {{ i18n.ts.unsetUserBanner }}</MkButton>
|
||||||
<MkSelect v-model="chartSrc" style="margin: 0 10px 0 0; flex: 1;">
|
|
||||||
<option value="per-user-notes">{{ i18n.ts.notes }}</option>
|
|
||||||
</MkSelect>
|
|
||||||
</div>
|
|
||||||
<div class="charts">
|
|
||||||
<div class="label">{{ i18n.tsx.recentNHours({ n: 90 }) }}</div>
|
|
||||||
<MkChart class="chart" :src="chartSrc" span="hour" :limit="90" :args="{ user, withoutAll: true }" :detailed="true"></MkChart>
|
|
||||||
<div class="label">{{ i18n.tsx.recentNDays({ n: 90 }) }}</div>
|
|
||||||
<MkChart class="chart" :src="chartSrc" span="day" :limit="90" :args="{ user, withoutAll: true }" :detailed="true"></MkChart>
|
|
||||||
</div>
|
</div>
|
||||||
|
<MkButton v-if="$i.isAdmin" inline danger @click="deleteAccount">{{ i18n.ts.deleteAccount }}</MkButton>
|
||||||
|
</div>
|
||||||
|
</FormSection>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="tab === 'roles'" class="_gaps">
|
||||||
|
<MkButton v-if="user.host == null" primary rounded @click="assignRole"><i class="ti ti-plus"></i> {{ i18n.ts.assign }}</MkButton>
|
||||||
|
|
||||||
|
<div v-for="role in info.roles" :key="role.id">
|
||||||
|
<div :class="$style.roleItemMain">
|
||||||
|
<MkRolePreview :class="$style.role" :role="role" :forModeration="true"/>
|
||||||
|
<button class="_button" @click="toggleRoleItem(role)"><i class="ti ti-chevron-down"></i></button>
|
||||||
|
<button v-if="role.target === 'manual'" class="_button" :class="$style.roleUnassign" @click="unassignRole(role, $event)"><i class="ti ti-x"></i></button>
|
||||||
|
<button v-else class="_button" :class="$style.roleUnassign" disabled><i class="ti ti-ban"></i></button>
|
||||||
|
</div>
|
||||||
|
<div v-if="expandedRoleIds.includes(role.id)" :class="$style.roleItemSub">
|
||||||
|
<div>Assigned: <MkTime :time="info.roleAssigns.find(a => a.roleId === role.id).createdAt" mode="detail"/></div>
|
||||||
|
<div v-if="info.roleAssigns.find(a => a.roleId === role.id).expiresAt">Period: {{ new Date(info.roleAssigns.find(a => a.roleId === role.id).expiresAt).toLocaleString() }}</div>
|
||||||
|
<div v-else>Period: {{ i18n.ts.indefinitely }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div v-else-if="tab === 'raw'" class="_gaps_m">
|
<div v-else-if="tab === 'announcements'" class="_gaps">
|
||||||
<MkObjectView v-if="info && $i.isAdmin" tall :value="info">
|
<MkButton primary rounded @click="createAnnouncement"><i class="ti ti-plus"></i> {{ i18n.ts.new }}</MkButton>
|
||||||
</MkObjectView>
|
|
||||||
|
|
||||||
<MkObjectView tall :value="user">
|
<MkSelect v-model="announcementsStatus">
|
||||||
</MkObjectView>
|
<template #label>{{ i18n.ts.filter }}</template>
|
||||||
|
<option value="active">{{ i18n.ts.active }}</option>
|
||||||
|
<option value="archived">{{ i18n.ts.archived }}</option>
|
||||||
|
</MkSelect>
|
||||||
|
|
||||||
|
<MkPagination :paginator="announcementsPaginator">
|
||||||
|
<template #default="{ items }">
|
||||||
|
<div class="_gaps_s">
|
||||||
|
<div v-for="announcement in items" :key="announcement.id" v-panel :class="$style.announcementItem" @click="editAnnouncement(announcement)">
|
||||||
|
<span style="margin-right: 0.5em;">
|
||||||
|
<i v-if="announcement.icon === 'info'" class="ti ti-info-circle"></i>
|
||||||
|
<i v-else-if="announcement.icon === 'warning'" class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i>
|
||||||
|
<i v-else-if="announcement.icon === 'error'" class="ti ti-circle-x" style="color: var(--MI_THEME-error);"></i>
|
||||||
|
<i v-else-if="announcement.icon === 'success'" class="ti ti-check" style="color: var(--MI_THEME-success);"></i>
|
||||||
|
</span>
|
||||||
|
<span>{{ announcement.title }}</span>
|
||||||
|
<span v-if="announcement.reads > 0" style="margin-left: auto; opacity: 0.7;">{{ i18n.ts.messageRead }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</MkPagination>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="tab === 'drive'" class="_gaps">
|
||||||
|
<MkFileListForAdmin :paginator="filesPaginator" viewMode="grid"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="tab === 'chart'" class="_gaps_m">
|
||||||
|
<div class="cmhjzshm">
|
||||||
|
<div class="selects">
|
||||||
|
<MkSelect v-model="chartSrc" style="margin: 0 10px 0 0; flex: 1;">
|
||||||
|
<option value="per-user-notes">{{ i18n.ts.notes }}</option>
|
||||||
|
</MkSelect>
|
||||||
|
</div>
|
||||||
|
<div class="charts">
|
||||||
|
<div class="label">{{ i18n.tsx.recentNHours({ n: 90 }) }}</div>
|
||||||
|
<MkChart class="chart" :src="chartSrc" span="hour" :limit="90" :args="{ user, withoutAll: true }" :detailed="true"></MkChart>
|
||||||
|
<div class="label">{{ i18n.tsx.recentNDays({ n: 90 }) }}</div>
|
||||||
|
<MkChart class="chart" :src="chartSrc" span="day" :limit="90" :args="{ user, withoutAll: true }" :detailed="true"></MkChart>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</FormSuspense>
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="tab === 'raw'" class="_gaps_m">
|
||||||
|
<MkObjectView v-if="info && $i.isAdmin" tall :value="info">
|
||||||
|
</MkObjectView>
|
||||||
|
|
||||||
|
<MkObjectView tall :value="user">
|
||||||
|
</MkObjectView>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</PageWithHeader>
|
</PageWithHeader>
|
||||||
</template>
|
</template>
|
||||||
|
@ -224,7 +222,6 @@ import MkButton from '@/components/MkButton.vue';
|
||||||
import MkFolder from '@/components/MkFolder.vue';
|
import MkFolder from '@/components/MkFolder.vue';
|
||||||
import MkKeyValue from '@/components/MkKeyValue.vue';
|
import MkKeyValue from '@/components/MkKeyValue.vue';
|
||||||
import MkSelect from '@/components/MkSelect.vue';
|
import MkSelect from '@/components/MkSelect.vue';
|
||||||
import FormSuspense from '@/components/form/suspense.vue';
|
|
||||||
import MkFileListForAdmin from '@/components/MkFileListForAdmin.vue';
|
import MkFileListForAdmin from '@/components/MkFileListForAdmin.vue';
|
||||||
import MkInfo from '@/components/MkInfo.vue';
|
import MkInfo from '@/components/MkInfo.vue';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
|
@ -232,11 +229,13 @@ import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { acct } from '@/filters/user.js';
|
import { acct } from '@/filters/user.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePage } from '@/page.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { iAmAdmin, $i, iAmModerator } from '@/i.js';
|
import { ensureSignin, iAmAdmin, iAmModerator } from '@/i.js';
|
||||||
import MkRolePreview from '@/components/MkRolePreview.vue';
|
import MkRolePreview from '@/components/MkRolePreview.vue';
|
||||||
import MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
import { Paginator } from '@/utility/paginator.js';
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
|
const $i = ensureSignin();
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
userId: string;
|
userId: string;
|
||||||
initialTab?: string;
|
initialTab?: string;
|
||||||
|
@ -244,18 +243,19 @@ const props = withDefaults(defineProps<{
|
||||||
initialTab: 'overview',
|
initialTab: 'overview',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const result = await _fetch_();
|
||||||
|
|
||||||
const tab = ref(props.initialTab);
|
const tab = ref(props.initialTab);
|
||||||
const chartSrc = ref('per-user-notes');
|
const chartSrc = ref('per-user-notes');
|
||||||
const user = ref<null | Misskey.entities.UserDetailed>();
|
const user = ref(result.user);
|
||||||
const init = ref<ReturnType<typeof createFetcher>>();
|
const info = ref(result.info);
|
||||||
const info = ref<any>();
|
const ips = ref(result.ips);
|
||||||
const ips = ref<Misskey.entities.AdminGetUserIpsResponse | null>(null);
|
|
||||||
const ap = ref<any>(null);
|
const ap = ref<any>(null);
|
||||||
const moderator = ref(false);
|
const moderator = ref(info.value.isModerator);
|
||||||
const silenced = ref(false);
|
const silenced = ref(info.value.isSilenced);
|
||||||
const suspended = ref(false);
|
const suspended = ref(info.value.isSuspended);
|
||||||
const isSystem = ref(false);
|
const isSystem = ref(user.value.host == null && user.value.username.includes('.'));
|
||||||
const moderationNote = ref('');
|
const moderationNote = ref(info.value.moderationNote);
|
||||||
const filesPaginator = markRaw(new Paginator('admin/drive/files', {
|
const filesPaginator = markRaw(new Paginator('admin/drive/files', {
|
||||||
limit: 10,
|
limit: 10,
|
||||||
computedParams: computed(() => ({
|
computedParams: computed(() => ({
|
||||||
|
@ -272,34 +272,37 @@ const announcementsPaginator = markRaw(new Paginator('admin/announcements/list',
|
||||||
status: announcementsStatus.value,
|
status: announcementsStatus.value,
|
||||||
})),
|
})),
|
||||||
}));
|
}));
|
||||||
const expandedRoles = ref([]);
|
const expandedRoleIds = ref<(typeof info.value.roles[number]['id'])[]>([]);
|
||||||
|
|
||||||
function createFetcher() {
|
function _fetch_() {
|
||||||
return () => Promise.all([misskeyApi('users/show', {
|
return Promise.all([misskeyApi('users/show', {
|
||||||
userId: props.userId,
|
userId: props.userId,
|
||||||
}), misskeyApi('admin/show-user', {
|
}), misskeyApi('admin/show-user', {
|
||||||
userId: props.userId,
|
userId: props.userId,
|
||||||
}), iAmAdmin ? misskeyApi('admin/get-user-ips', {
|
}), iAmAdmin ? misskeyApi('admin/get-user-ips', {
|
||||||
userId: props.userId,
|
userId: props.userId,
|
||||||
}) : Promise.resolve(null)]).then(([_user, _info, _ips]) => {
|
}) : Promise.resolve(null)]).then(([_user, _info, _ips]) => ({
|
||||||
user.value = _user;
|
user: _user,
|
||||||
info.value = _info;
|
info: _info,
|
||||||
ips.value = _ips;
|
ips: _ips,
|
||||||
moderator.value = info.value.isModerator;
|
}));
|
||||||
silenced.value = info.value.isSilenced;
|
|
||||||
suspended.value = info.value.isSuspended;
|
|
||||||
moderationNote.value = info.value.moderationNote;
|
|
||||||
isSystem.value = user.value.host == null && user.value.username.includes('.');
|
|
||||||
|
|
||||||
watch(moderationNote, async () => {
|
|
||||||
await misskeyApi('admin/update-user-note', { userId: user.value.id, text: moderationNote.value });
|
|
||||||
await refreshUser();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function refreshUser() {
|
watch(moderationNote, async () => {
|
||||||
init.value = createFetcher();
|
await misskeyApi('admin/update-user-note', { userId: user.value.id, text: moderationNote.value });
|
||||||
|
await refreshUser();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function refreshUser() {
|
||||||
|
const result = await _fetch_();
|
||||||
|
user.value = result.user;
|
||||||
|
info.value = result.info;
|
||||||
|
ips.value = result.ips;
|
||||||
|
moderator.value = info.value.isModerator;
|
||||||
|
silenced.value = info.value.isSilenced;
|
||||||
|
suspended.value = info.value.isSuspended;
|
||||||
|
isSystem.value = user.value.host == null && user.value.username.includes('.');
|
||||||
|
moderationNote.value = info.value.moderationNote;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateRemoteUser() {
|
async function updateRemoteUser() {
|
||||||
|
@ -456,7 +459,7 @@ async function assignRole() {
|
||||||
refreshUser();
|
refreshUser();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function unassignRole(role, ev) {
|
async function unassignRole(role: typeof info.value.roles[number], ev: MouseEvent) {
|
||||||
os.popupMenu([{
|
os.popupMenu([{
|
||||||
text: i18n.ts.unassign,
|
text: i18n.ts.unassign,
|
||||||
icon: 'ti ti-x',
|
icon: 'ti ti-x',
|
||||||
|
@ -468,11 +471,11 @@ async function unassignRole(role, ev) {
|
||||||
}], ev.currentTarget ?? ev.target);
|
}], ev.currentTarget ?? ev.target);
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleRoleItem(role) {
|
function toggleRoleItem(role: typeof info.value.roles[number]) {
|
||||||
if (expandedRoles.value.includes(role.id)) {
|
if (expandedRoleIds.value.includes(role.id)) {
|
||||||
expandedRoles.value = expandedRoles.value.filter(x => x !== role.id);
|
expandedRoleIds.value = expandedRoleIds.value.filter(x => x !== role.id);
|
||||||
} else {
|
} else {
|
||||||
expandedRoles.value.push(role.id);
|
expandedRoleIds.value.push(role.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -493,12 +496,6 @@ async function editAnnouncement(announcement) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(() => props.userId, () => {
|
|
||||||
init.value = createFetcher();
|
|
||||||
}, {
|
|
||||||
immediate: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
watch(user, () => {
|
watch(user, () => {
|
||||||
misskeyApi('ap/get', {
|
misskeyApi('ap/get', {
|
||||||
uri: user.value.uri ?? `${url}/users/${user.value.id}`,
|
uri: user.value.uri ?? `${url}/users/${user.value.id}`,
|
||||||
|
|
|
@ -41,7 +41,7 @@ async function addRelay() {
|
||||||
type: 'url',
|
type: 'url',
|
||||||
placeholder: i18n.ts.inboxUrl,
|
placeholder: i18n.ts.inboxUrl,
|
||||||
});
|
});
|
||||||
if (canceled) return;
|
if (canceled || inbox == null) return;
|
||||||
misskeyApi('admin/relays/add', {
|
misskeyApi('admin/relays/add', {
|
||||||
inbox,
|
inbox,
|
||||||
}).then((relay: any) => {
|
}).then((relay: any) => {
|
||||||
|
|
|
@ -37,27 +37,14 @@ const props = defineProps<{
|
||||||
const antenna = ref<Misskey.entities.Antenna | null>(null);
|
const antenna = ref<Misskey.entities.Antenna | null>(null);
|
||||||
const tlEl = useTemplateRef('tlEl');
|
const tlEl = useTemplateRef('tlEl');
|
||||||
|
|
||||||
async function timetravel() {
|
|
||||||
const { canceled, result: date } = await os.inputDate({
|
|
||||||
title: i18n.ts.date,
|
|
||||||
});
|
|
||||||
if (canceled) return;
|
|
||||||
|
|
||||||
tlEl.value.timetravel(date);
|
|
||||||
}
|
|
||||||
|
|
||||||
function settings() {
|
function settings() {
|
||||||
router.push('/my/antennas/:antennaId', {
|
router.push('/my/antennas/:antennaId', {
|
||||||
params: {
|
params: {
|
||||||
antennaId: props.antennaId,
|
antennaId: props.antennaId,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function focus() {
|
|
||||||
tlEl.value.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(() => props.antennaId, async () => {
|
watch(() => props.antennaId, async () => {
|
||||||
antenna.value = await misskeyApi('antennas/show', {
|
antenna.value = await misskeyApi('antennas/show', {
|
||||||
antennaId: props.antennaId,
|
antennaId: props.antennaId,
|
||||||
|
@ -65,10 +52,6 @@ watch(() => props.antennaId, async () => {
|
||||||
}, { immediate: true });
|
}, { immediate: true });
|
||||||
|
|
||||||
const headerActions = computed(() => antenna.value ? [{
|
const headerActions = computed(() => antenna.value ? [{
|
||||||
icon: 'ti ti-calendar-time',
|
|
||||||
text: i18n.ts.jumpToSpecifiedDate,
|
|
||||||
handler: timetravel,
|
|
||||||
}, {
|
|
||||||
icon: 'ti ti-settings',
|
icon: 'ti ti-settings',
|
||||||
text: i18n.ts.settings,
|
text: i18n.ts.settings,
|
||||||
handler: settings,
|
handler: settings,
|
||||||
|
|
|
@ -44,11 +44,13 @@ const name = computed(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
function cancel() {
|
function cancel() {
|
||||||
misskeyApi('auth/deny', {
|
//misskeyApi('auth/deny', {
|
||||||
token: props.session.token,
|
// token: props.session.token,
|
||||||
}).then(() => {
|
//}).then(() => {
|
||||||
emit('denied');
|
// emit('denied');
|
||||||
});
|
//});
|
||||||
|
|
||||||
|
emit('denied');
|
||||||
}
|
}
|
||||||
|
|
||||||
function accept() {
|
function accept() {
|
||||||
|
|
|
@ -131,6 +131,8 @@ watch(() => props.channelId, async () => {
|
||||||
channel.value = await misskeyApi('channels/show', {
|
channel.value = await misskeyApi('channels/show', {
|
||||||
channelId: props.channelId,
|
channelId: props.channelId,
|
||||||
});
|
});
|
||||||
|
if (channel.value == null) return; // TSを黙らすため
|
||||||
|
|
||||||
favorited.value = channel.value.isFavorited ?? false;
|
favorited.value = channel.value.isFavorited ?? false;
|
||||||
if (favorited.value || channel.value.isFollowing) {
|
if (favorited.value || channel.value.isFollowing) {
|
||||||
tab.value = 'timeline';
|
tab.value = 'timeline';
|
||||||
|
@ -150,7 +152,7 @@ function edit() {
|
||||||
router.push('/channels/:channelId/edit', {
|
router.push('/channels/:channelId/edit', {
|
||||||
params: {
|
params: {
|
||||||
channelId: props.channelId,
|
channelId: props.channelId,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkSwitch v-model="statusbar.props.shuffle">
|
<MkSwitch v-model="statusbar.props.shuffle">
|
||||||
<template #label>{{ i18n.ts.shuffle }}</template>
|
<template #label>{{ i18n.ts.shuffle }}</template>
|
||||||
</MkSwitch>
|
</MkSwitch>
|
||||||
<MkInput v-model="statusbar.props.refreshIntervalSec" manualSave type="number" min="1">
|
<MkInput v-model="statusbar.props.refreshIntervalSec" manualSave type="number" :min="1">
|
||||||
<template #label>{{ i18n.ts.refreshInterval }}</template>
|
<template #label>{{ i18n.ts.refreshInterval }}</template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
<MkRange v-model="statusbar.props.marqueeDuration" :min="5" :max="150" :step="1">
|
<MkRange v-model="statusbar.props.marqueeDuration" :min="5" :max="150" :step="1">
|
||||||
|
@ -48,7 +48,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</MkSwitch>
|
</MkSwitch>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="statusbar.type === 'federation'">
|
<template v-else-if="statusbar.type === 'federation'">
|
||||||
<MkInput v-model="statusbar.props.refreshIntervalSec" manualSave type="number" min="1">
|
<MkInput v-model="statusbar.props.refreshIntervalSec" manualSave type="number" :min="1">
|
||||||
<template #label>{{ i18n.ts.refreshInterval }}</template>
|
<template #label>{{ i18n.ts.refreshInterval }}</template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
<MkRange v-model="statusbar.props.marqueeDuration" :min="5" :max="150" :step="1">
|
<MkRange v-model="statusbar.props.marqueeDuration" :min="5" :max="150" :step="1">
|
||||||
|
@ -104,7 +104,7 @@ const props = defineProps<{
|
||||||
userLists: Misskey.entities.UserList[] | null;
|
userLists: Misskey.entities.UserList[] | null;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const statusbar = reactive(deepClone(prefer.s.statusbars.find(x => x.id === props._id)));
|
const statusbar = reactive(deepClone(prefer.s.statusbars.find(x => x.id === props._id))!);
|
||||||
|
|
||||||
watch(() => statusbar.type, () => {
|
watch(() => statusbar.type, () => {
|
||||||
if (statusbar.type === 'rss') {
|
if (statusbar.type === 'rss') {
|
||||||
|
|
Loading…
Reference in New Issue