Merge branch 'develop' into qr

This commit is contained in:
tamaina 2025-08-25 20:10:46 +09:00
commit bd9e211f76
9 changed files with 277 additions and 263 deletions

View File

@ -39,13 +39,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</template>
<script lang="ts" setup>
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';
<script lang="ts">
type ItemOption = {
type?: 'option';
value: string | number | null;
@ -60,11 +54,32 @@ type 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: itemsslotoption(props.items)
// see: https://github.com/misskey-dev/misskey/issues/15558
//
const props = defineProps<{
modelValue: string | number | null;
modelValue: ValuesOfItems<T>;
required?: boolean;
readonly?: boolean;
disabled?: boolean;
@ -73,11 +88,11 @@ const props = defineProps<{
inline?: boolean;
small?: boolean;
large?: boolean;
items?: MkSelectItem[];
items?: T;
}>();
const emit = defineEmits<{
(ev: 'update:modelValue', value: string | number | null): void;
(ev: 'update:modelValue', value: ValuesOfItems<T>): void;
}>();
const slots = useSlots();

View File

@ -22,20 +22,46 @@ SPDX-License-Identifier: AGPL-3.0-only
<option value="blocked">{{ i18n.ts.blocked }}</option>
<option value="notResponding">{{ i18n.ts.notResponding }}</option>
</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>
<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>
</FormSplit>
</div>
@ -52,6 +78,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { computed, markRaw, ref } from 'vue';
import * as Misskey from 'misskey-js';
import MkInput from '@/components/MkInput.vue';
import MkSelect from '@/components/MkSelect.vue';
import MkPagination from '@/components/MkPagination.vue';
@ -62,7 +89,7 @@ import { Paginator } from '@/utility/paginator.js';
const host = ref('');
const state = ref('federating');
const sort = ref('+pubSub');
const sort = ref<NonNullable<Misskey.entities.FederationInstancesRequest['sort']>>('+pubSub');
const paginator = markRaw(new Paginator('federation/instances', {
limit: 10,
offsetMode: true,

View File

@ -48,34 +48,22 @@ watch(tab, () => {
const headerActions = computed(() => []);
const headerTabs = computed(() => {
const items = [];
items.push({
const headerTabs = computed(() => [{
key: 'overview',
title: i18n.ts.overview,
}, {
key: 'emojis',
title: i18n.ts.customEmojis,
icon: 'ti ti-icons',
});
if (instance.federation !== 'none') {
items.push({
}, ...(instance.federation !== 'none' ? [{
key: 'federation',
title: i18n.ts.federation,
icon: 'ti ti-whirl',
});
}
items.push({
}] : []), {
key: 'charts',
title: i18n.ts.charts,
icon: 'ti ti-chart-line',
});
return items;
});
}]);
definePage(() => ({
title: i18n.ts.instanceInfo,

View File

@ -6,7 +6,6 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<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;">
<FormSuspense :p="init">
<div v-if="tab === 'overview'" class="_gaps_m">
<div class="aeakzknw">
<MkAvatar class="avatar" :user="user" indicator link preview/>
@ -143,7 +142,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<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 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>
@ -205,7 +204,6 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkObjectView tall :value="user">
</MkObjectView>
</div>
</FormSuspense>
</div>
</PageWithHeader>
</template>
@ -224,7 +222,6 @@ import MkButton from '@/components/MkButton.vue';
import MkFolder from '@/components/MkFolder.vue';
import MkKeyValue from '@/components/MkKeyValue.vue';
import MkSelect from '@/components/MkSelect.vue';
import FormSuspense from '@/components/form/suspense.vue';
import MkFileListForAdmin from '@/components/MkFileListForAdmin.vue';
import MkInfo from '@/components/MkInfo.vue';
import * as os from '@/os.js';
@ -232,11 +229,13 @@ import { misskeyApi } from '@/utility/misskey-api.js';
import { acct } from '@/filters/user.js';
import { definePage } from '@/page.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 MkPagination from '@/components/MkPagination.vue';
import { Paginator } from '@/utility/paginator.js';
const $i = ensureSignin();
const props = withDefaults(defineProps<{
userId: string;
initialTab?: string;
@ -244,18 +243,19 @@ const props = withDefaults(defineProps<{
initialTab: 'overview',
});
const result = await _fetch_();
const tab = ref(props.initialTab);
const chartSrc = ref('per-user-notes');
const user = ref<null | Misskey.entities.UserDetailed>();
const init = ref<ReturnType<typeof createFetcher>>();
const info = ref<any>();
const ips = ref<Misskey.entities.AdminGetUserIpsResponse | null>(null);
const user = ref(result.user);
const info = ref(result.info);
const ips = ref(result.ips);
const ap = ref<any>(null);
const moderator = ref(false);
const silenced = ref(false);
const suspended = ref(false);
const isSystem = ref(false);
const moderationNote = ref('');
const moderator = ref(info.value.isModerator);
const silenced = ref(info.value.isSilenced);
const suspended = ref(info.value.isSuspended);
const isSystem = ref(user.value.host == null && user.value.username.includes('.'));
const moderationNote = ref(info.value.moderationNote);
const filesPaginator = markRaw(new Paginator('admin/drive/files', {
limit: 10,
computedParams: computed(() => ({
@ -272,34 +272,37 @@ const announcementsPaginator = markRaw(new Paginator('admin/announcements/list',
status: announcementsStatus.value,
})),
}));
const expandedRoles = ref([]);
const expandedRoleIds = ref<(typeof info.value.roles[number]['id'])[]>([]);
function createFetcher() {
return () => Promise.all([misskeyApi('users/show', {
function _fetch_() {
return Promise.all([misskeyApi('users/show', {
userId: props.userId,
}), misskeyApi('admin/show-user', {
userId: props.userId,
}), iAmAdmin ? misskeyApi('admin/get-user-ips', {
userId: props.userId,
}) : Promise.resolve(null)]).then(([_user, _info, _ips]) => {
user.value = _user;
info.value = _info;
ips.value = _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('.');
}) : Promise.resolve(null)]).then(([_user, _info, _ips]) => ({
user: _user,
info: _info,
ips: _ips,
}));
}
watch(moderationNote, async () => {
await misskeyApi('admin/update-user-note', { userId: user.value.id, text: moderationNote.value });
await refreshUser();
});
});
}
function refreshUser() {
init.value = createFetcher();
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() {
@ -456,7 +459,7 @@ async function assignRole() {
refreshUser();
}
async function unassignRole(role, ev) {
async function unassignRole(role: typeof info.value.roles[number], ev: MouseEvent) {
os.popupMenu([{
text: i18n.ts.unassign,
icon: 'ti ti-x',
@ -468,11 +471,11 @@ async function unassignRole(role, ev) {
}], ev.currentTarget ?? ev.target);
}
function toggleRoleItem(role) {
if (expandedRoles.value.includes(role.id)) {
expandedRoles.value = expandedRoles.value.filter(x => x !== role.id);
function toggleRoleItem(role: typeof info.value.roles[number]) {
if (expandedRoleIds.value.includes(role.id)) {
expandedRoleIds.value = expandedRoleIds.value.filter(x => x !== role.id);
} 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, () => {
misskeyApi('ap/get', {
uri: user.value.uri ?? `${url}/users/${user.value.id}`,

View File

@ -41,7 +41,7 @@ async function addRelay() {
type: 'url',
placeholder: i18n.ts.inboxUrl,
});
if (canceled) return;
if (canceled || inbox == null) return;
misskeyApi('admin/relays/add', {
inbox,
}).then((relay: any) => {

View File

@ -37,27 +37,14 @@ const props = defineProps<{
const antenna = ref<Misskey.entities.Antenna | null>(null);
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() {
router.push('/my/antennas/:antennaId', {
params: {
antennaId: props.antennaId,
}
},
});
}
function focus() {
tlEl.value.focus();
}
watch(() => props.antennaId, async () => {
antenna.value = await misskeyApi('antennas/show', {
antennaId: props.antennaId,
@ -65,10 +52,6 @@ watch(() => props.antennaId, async () => {
}, { immediate: true });
const headerActions = computed(() => antenna.value ? [{
icon: 'ti ti-calendar-time',
text: i18n.ts.jumpToSpecifiedDate,
handler: timetravel,
}, {
icon: 'ti ti-settings',
text: i18n.ts.settings,
handler: settings,

View File

@ -44,11 +44,13 @@ const name = computed(() => {
});
function cancel() {
misskeyApi('auth/deny', {
token: props.session.token,
}).then(() => {
//misskeyApi('auth/deny', {
// token: props.session.token,
//}).then(() => {
// emit('denied');
//});
emit('denied');
});
}
function accept() {

View File

@ -131,6 +131,8 @@ watch(() => props.channelId, async () => {
channel.value = await misskeyApi('channels/show', {
channelId: props.channelId,
});
if (channel.value == null) return; // TS
favorited.value = channel.value.isFavorited ?? false;
if (favorited.value || channel.value.isFollowing) {
tab.value = 'timeline';
@ -150,7 +152,7 @@ function edit() {
router.push('/channels/:channelId/edit', {
params: {
channelId: props.channelId,
}
},
});
}

View File

@ -36,7 +36,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSwitch v-model="statusbar.props.shuffle">
<template #label>{{ i18n.ts.shuffle }}</template>
</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>
</MkInput>
<MkRange v-model="statusbar.props.marqueeDuration" :min="5" :max="150" :step="1">
@ -48,7 +48,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkSwitch>
</template>
<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>
</MkInput>
<MkRange v-model="statusbar.props.marqueeDuration" :min="5" :max="150" :step="1">
@ -104,7 +104,7 @@ const props = defineProps<{
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, () => {
if (statusbar.type === 'rss') {