Merge upstream

This commit is contained in:
무라쿠모 2024-08-19 23:29:12 +09:00
commit 549202d0e9
No known key found for this signature in database
GPG key ID: 139D6573F92DA9F7
16 changed files with 152 additions and 24 deletions

View file

@ -134,6 +134,10 @@ export class ClipService {
throw new ClipService.NoSuchClipError();
}
if (await this.clipNotesRepository.existsBy({ clipId, noteId })) {
throw new ClipService.AlreadyAddedError();
}
const policies = await this.roleService.getUserPolicies(me.id);
const currentClipCount = await this.clipsRepository.countBy({
@ -143,6 +147,13 @@ export class ClipService {
throw new ClipService.ClipLimitExceededError();
}
const currentNoteCount = await this.clipNotesRepository.countBy({
clipId: clip.id,
});
if (currentNoteCount >= policies.noteEachClipsLimit) {
throw new ClipService.TooManyClipNotesError();
}
const currentNoteCounts = await this.clipNotesRepository
.createQueryBuilder('cn')
.select('COUNT(*)')
@ -154,13 +165,6 @@ export class ClipService {
throw new ClipService.ClipNotesLimitExceededError();
}
const currentNoteCount = await this.clipNotesRepository.countBy({
clipId: clip.id,
});
if (currentNoteCount >= policies.noteEachClipsLimit) {
throw new ClipService.TooManyClipNotesError();
}
try {
await this.clipNotesRepository.insert({
id: this.idService.gen(),

View file

@ -9,7 +9,16 @@ import { bindThis } from '@/decorators.js';
import { DI } from '@/di-symbols.js';
import type Logger from '@/logger.js';
import type { MiUser } from '@/models/User.js';
import type { FollowingsRepository, FollowRequestsRepository } from '@/models/_.js';
import type {
AntennasRepository,
ClipNotesRepository,
ClipsRepository,
FollowingsRepository,
FollowRequestsRepository,
UserListMembershipsRepository,
UserListsRepository,
WebhooksRepository,
} from '@/models/_.js';
import { QueueService } from '@/core/QueueService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
@ -30,6 +39,24 @@ export class UserSuspendService {
@Inject(DI.followRequestsRepository)
private followRequestsRepository: FollowRequestsRepository,
@Inject(DI.antennasRepository)
private antennasRepository: AntennasRepository,
@Inject(DI.webhooksRepository)
private webhooksRepository: WebhooksRepository,
@Inject(DI.userListsRepository)
private userListsRepository: UserListsRepository,
@Inject(DI.clipsRepository)
private clipsRepository: ClipsRepository,
@Inject(DI.clipNotesRepository)
private clipNotesRepository: ClipNotesRepository,
@Inject(DI.userListMembershipsRepository)
private userListMembershipsRepository: UserListMembershipsRepository,
private queueService: QueueService,
private globalEventService: GlobalEventService,
private apRendererService: ApRendererService,
@ -45,10 +72,41 @@ export class UserSuspendService {
this.globalEventService.publishInternalEvent('userChangeSuspendedState', { id: user.id, isSuspended: true });
await Promise.all([
const promises: Promise<unknown>[] = [];
let cursor = '';
while (true) { // eslint-disable-line @typescript-eslint/no-unnecessary-condition, no-constant-condition
const clipNotes = await this.clipNotesRepository.createQueryBuilder('c')
.select('c.id')
.innerJoin('c.note', 'n')
.where('n.userId = :userId', { userId: user.id })
.andWhere('c.id > :cursor', { cursor })
.orderBy('c.id', 'ASC')
.limit(500)
.getRawMany<{ id: string }>();
if (clipNotes.length === 0) break;
cursor = clipNotes.at(-1)?.id ?? '';
promises.push(this.clipNotesRepository.createQueryBuilder()
.delete()
.where('id IN (:...ids)', { ids: clipNotes.map((clipNote) => clipNote.id) })
.execute());
}
await Promise.allSettled([
this.followRequestsRepository.delete({ followeeId: user.id }),
this.followRequestsRepository.delete({ followerId: user.id }),
]).catch(() => null);
this.antennasRepository.delete({ userId: user.id }),
this.webhooksRepository.delete({ userId: user.id }),
this.userListsRepository.delete({ userId: user.id }),
this.clipsRepository.delete({ userId: user.id }),
...promises,
this.userListMembershipsRepository.delete({ userId: user.id }),
]);
if (this.userEntityService.isLocalUser(user)) {
// 知り得る全SharedInboxにDelete配信

View file

@ -19,8 +19,9 @@ export const paramDef = {
type: 'object',
properties: {
userId: { type: 'string', format: 'misskey:id' },
itemId: { type: 'string', format: 'misskey:id' },
},
required: ['userId'],
required: ['userId', 'itemId'],
} as const;
@Injectable()
@ -42,7 +43,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
}
await this.userProfilesRepository.update(user.id, {
mutualLinkSections: [],
mutualLinkSections: userProfile.mutualLinkSections.map(section => ({
...section,
mutualLinks: section.mutualLinks.filter(item => item.id !== ps.itemId),
})),
});
this.moderationLogService.log(me, 'unsetUserMutualLink', {

View file

@ -43,7 +43,7 @@
"chartjs-plugin-zoom": "2.0.1",
"chromatic": "11.7.0",
"compare-versions": "6.1.1",
"cropperjs": "2.0.0-beta.4",
"cropperjs": "2.0.0-rc.0",
"date-fns": "3.6.0",
"escape-regexp": "0.0.1",
"estree-walker": "3.0.3",

View file

@ -62,6 +62,7 @@ import { i18n } from '@/i18n.js';
import * as os from '@/os.js';
import { pleaseLogin } from '@/scripts/please-login.js';
import { $i, iAmModerator } from '@/account.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
const props = withDefaults(defineProps<{
image: Misskey.entities.DriveFile;
@ -92,6 +93,17 @@ function showMenu(ev: MouseEvent) {
action: () => {
hide.value = true;
},
}, {
text: i18n.ts.saveThisFile,
icon: 'ti ti-cloud-upload',
action: () => {
os.selectDriveFolder(false).then(async folder => {
misskeyApi('drive/files/upload-from-url', {
url: props.image.url,
folderId: folder[0]?.id,
});
});
},
}];
if ($i?.id === props.image.userId || iAmModerator) {

View file

@ -66,7 +66,19 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkButton v-if="user?.host == null" @click="resetPassword"><i class="ti ti-key"></i> {{ i18n.ts.resetPassword }}</MkButton>
<MkButton inline danger @click="unsetUserAvatar"><i class="ti ti-user-circle"></i> {{ i18n.ts.unsetUserAvatar }}</MkButton>
<MkButton inline danger @click="unsetUserBanner"><i class="ti ti-photo"></i> {{ i18n.ts.unsetUserBanner }}</MkButton>
<MkButton inline danger @click="unsetUserMutualLink"><i class="ti ti-photo"></i> {{ i18n.ts.unsetUserMutualLink }}</MkButton>
<MkFolder v-if="user?.mutualLinkSections && user?.mutualLinkSections.reduce((acc, section) => acc + section.mutualLinks.length, 0) > 0">
<template #icon><i class="ti ti-link"></i></template>
<template #label>{{ i18n.ts._profile.mutualLinksEdit }}</template>
<div v-for="mutualLinkSection in user?.mutualLinkSections">
<div v-for="mutualLink in mutualLinkSection.mutualLinks" :key="mutualLink.id" :class="$style.fields">
<p> {{ mutualLink.url }} </p>
<img :class="$style.mutualLinkImg" :src="mutualLink.imgSrc" :alt="mutualLink.description"/>
<p> {{ mutualLink.description }} </p>
<MkButton inline danger @click="unsetUserMutualLink(mutualLink.id)"><i class="ti ti-link"></i> {{ i18n.ts.unsetUserMutualLink }}</MkButton>
</div>
</div>
</MkFolder>
</div>
</MkFolder>
@ -365,15 +377,16 @@ async function unsetUserBanner() {
}).then(refreshUser);
}
async function unsetUserMutualLink() {
async function unsetUserMutualLink(mutualLinkid: string) {
const confirm = await os.confirm({
type: 'warning',
text: i18n.ts.unsetUserMutualLinkConfirm,
});
if (confirm.canceled) return;
await os.apiWithDialog('admin/unset-user-mutual-banner', {
await os.apiWithDialog('admin/unset-user-mutual-link', {
userId: user.value.id,
itemId: mutualLinkid,
}).then(refreshUser);
}
@ -705,4 +718,16 @@ definePageMetadata(() => ({
border-radius: 6px;
cursor: pointer;
}
.mutualLinkImg {
max-width: 200px;
max-height: 40px;
}
.fields {
padding: 24px;
border-bottom: solid 0.5px var(--divider);
&:last-child {
border-bottom: none;
}
}
</style>

View file

@ -112,7 +112,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #item="{ element: sectionElement, index: sectionIndex }">
<div :class="$style.mutualLinkSectionRoot">
<button v-if="!mutualLinkSectionEditMode" class="_button" :class="$style.dragItemHandle" tabindex="-1"><i class="ti ti-menu"></i></button>
<button v-if="mutualLinkSectionEditMode" :disabled="fields.length <= 1" class="_button" :class="$style.dragItemRemove" @click="deleteMutualLinkSection(sectionIndex)"><i class="ti ti-x"></i></button>
{{sectionElement.length }}
<button v-if="mutualLinkSectionEditMode" :disabled="sectionElement.length <= 1" class="_button" :class="$style.dragItemRemove" @click="deleteMutualLinkSection(sectionIndex)"><i class="ti ti-x"></i></button>
<FormSlot :style="{flexGrow: 1}">
<MkFolder>
<template #label>{{ sectionElement.name || i18n.ts._profile.sectionNameNone }}</template>
@ -136,7 +138,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #item="{ element: linkElement, index: linkIndex }">
<div :class="$style.mutualLinkRoot">
<button v-if="!mutualLinkSectionEditMode" class="_button" :class="$style.dragItemHandle" tabindex="-1"><i class="ti ti-menu"></i></button>
<button v-if="mutualLinkSectionEditMode" :disabled="fields.length <= 1" class="_button" :class="$style.dragItemRemove" @click="deleteMutualLink(sectionIndex,linkIndex)"><i class="ti ti-x"></i></button>
<button v-if="mutualLinkSectionEditMode" class="_button" :class="$style.dragItemRemove" @click="deleteMutualLink(sectionIndex,linkIndex)"><i class="ti ti-x"></i></button>
<div class="_gaps_s" :style="{flex: 1}">
<MkInfo v-if="linkIndex >= $i.policies.mutualLinkLimit" warn><Mfm :text="i18n.tsx._profile.policyDisplayLimitExceeded({ max: $i.policies.mutualLinkLimit })"/></MkInfo>

View file

@ -629,6 +629,7 @@ onUnmounted(() => {
padding: 24px;
font-size: 0.9em;
border-top: solid 0.5px var(--divider);
margin-top: 0.5px;
> .field {
display: flex;

View file

@ -98,6 +98,12 @@ export function uploadFile(
title: i18n.ts.failedToUpload,
text: i18n.ts.cannotUploadBecauseExceedsFileSizeLimit,
});
} else if (xhr.status === 524) {
alert({
type: 'error',
title: i18n.ts.failedToUpload,
text: i18n.ts.cannotUploadBecauseTimeout,
});
} else if (ev.target?.response) {
const res = JSON.parse(ev.target.response);
if (res.error?.id === 'bec5bd69-fba3-43c9-b4fb-2894b66ad5d2') {

View file

@ -6915,6 +6915,8 @@ export type operations = {
'application/json': {
/** Format: misskey:id */
userId: string;
/** Format: misskey:id */
itemId: string;
};
};
};