Merge upstream
This commit is contained in:
commit
549202d0e9
@ -977,6 +977,7 @@ failedToUpload: "Upload failed"
|
||||
cannotUploadBecauseInappropriate: "This file could not be uploaded because parts of it have been detected as potentially inappropriate."
|
||||
cannotUploadBecauseNoFreeSpace: "Upload failed due to lack of Drive capacity."
|
||||
cannotUploadBecauseExceedsFileSizeLimit: "This file cannot be uploaded as it exceeds the file size limit."
|
||||
cannotUploadBecauseTimeout: "The file could not be uploaded due to a connection timeout."
|
||||
beta: "Beta"
|
||||
enableAutoSensitive: "Automatic marking as sensitive"
|
||||
enableAutoSensitiveDescription: "Allows automatic detection and marking of sensitive media through Machine Learning where possible. Even if this option is disabled, it may be enabled instance-wide."
|
||||
@ -1270,6 +1271,7 @@ youAreHidingSensitiveInformation: "This content was hidden by 'Private Mode'."
|
||||
temporarilySeeThis: "Nevermind, just show me this"
|
||||
sensitiveDoubleClickRequired: "Require double-click to open sensitive media"
|
||||
mutualLink: "Mutual Link"
|
||||
saveThisFile: "Save this file to Drive"
|
||||
_bubbleGame:
|
||||
howToPlay: "How to play"
|
||||
hold: "Hold"
|
||||
|
8
locales/index.d.ts
vendored
8
locales/index.d.ts
vendored
@ -3932,6 +3932,10 @@ export interface Locale extends ILocale {
|
||||
* ファイルサイズの制限を超えているためアップロードできません。
|
||||
*/
|
||||
"cannotUploadBecauseExceedsFileSizeLimit": string;
|
||||
/**
|
||||
* 接続がタイムアウトしたため、ファイルをアップロードできませんでした。
|
||||
*/
|
||||
"cannotUploadBecauseTimeout": string;
|
||||
/**
|
||||
* ベータ
|
||||
*/
|
||||
@ -5171,6 +5175,10 @@ export interface Locale extends ILocale {
|
||||
* モデレーター権限を利用するには、まず二要素認証を有効にする必要があります。
|
||||
*/
|
||||
"youNeedToEnableTwoFactor": string;
|
||||
/**
|
||||
* このファイルをドライブに保存する
|
||||
*/
|
||||
"saveThisFile": string;
|
||||
"_bubbleGame": {
|
||||
/**
|
||||
* 遊び方
|
||||
|
@ -979,6 +979,7 @@ failedToUpload: "アップロード失敗"
|
||||
cannotUploadBecauseInappropriate: "不適切な内容を含む可能性があると判定されたためアップロードできません。"
|
||||
cannotUploadBecauseNoFreeSpace: "ドライブの空き容量が無いためアップロードできません。"
|
||||
cannotUploadBecauseExceedsFileSizeLimit: "ファイルサイズの制限を超えているためアップロードできません。"
|
||||
cannotUploadBecauseTimeout: "接続がタイムアウトしたため、ファイルをアップロードできませんでした。"
|
||||
beta: "ベータ"
|
||||
enableAutoSensitive: "自動センシティブ判定"
|
||||
enableAutoSensitiveDescription: "利用可能な場合は、機械学習を利用して自動でメディアにセンシティブフラグを設定します。この機能をオフにしても、サーバーによっては自動で設定されることがあります。"
|
||||
@ -1288,6 +1289,7 @@ removeAllFollowings: "相互フォロー解除"
|
||||
areYouSureToRemoveAllFollowings: "本当に{host}とのすべてのフォロー関係を削除しますか? 実行後は元に戻せません。 相手インスタンスが閉鎖されたと判断した場合のみ実行してください。"
|
||||
mutualLink: "相互リンク"
|
||||
youNeedToEnableTwoFactor: "モデレーター権限を利用するには、まず二要素認証を有効にする必要があります。"
|
||||
saveThisFile: "このファイルをドライブに保存する"
|
||||
|
||||
_bubbleGame:
|
||||
howToPlay: "遊び方"
|
||||
|
@ -976,6 +976,7 @@ failedToUpload: "업로드 실패"
|
||||
cannotUploadBecauseInappropriate: "이 파일은 부적절한 내용을 포함한다고 판단되어 업로드할 수 없습니다."
|
||||
cannotUploadBecauseNoFreeSpace: "드라이브 용량이 부족하여 업로드할 수 없습니다."
|
||||
cannotUploadBecauseExceedsFileSizeLimit: "파일 크기가 너무 크기 때문에 업로드할 수 없습니다."
|
||||
cannotUploadBecauseTimeout: "접속 시간이 초과되어 파일을 업로드할 수 없습니다."
|
||||
beta: "베타"
|
||||
enableAutoSensitive: "자동 NSFW 탐지"
|
||||
enableAutoSensitiveDescription: "이용 가능할 경우 기계학습을 통해 자동으로 미디어 NSFW를 설정합니다. 이 기능을 해제하더라도, 서버 정책에 따라 자동으로 설정될 수 있습니다."
|
||||
@ -1273,6 +1274,7 @@ removeAllFollowings: "모든 팔로우 관계를 제거하기"
|
||||
areYouSureToRemoveAllFollowings: "정말로 {host}와의 모든 팔로우 관계를 제거하시겠습니까? 실행한 후에는 되돌릴 수 없습니다. 상대 인스턴스가 폐쇄됐다고 판단되는 경우에만 실행하세요."
|
||||
mutualLink: "서로링크"
|
||||
youNeedToEnableTwoFactor: "관리 권한을 이용하려면 먼저 2단계 인증을 활성화해야 합니다."
|
||||
saveThisFile: "이 파일을 드라이브에 저장"
|
||||
_bubbleGame:
|
||||
howToPlay: "설명"
|
||||
hold: "홀드"
|
||||
|
@ -6,7 +6,7 @@
|
||||
"type": "git",
|
||||
"url": "https://github.com/SWREI/oscar_surf.git"
|
||||
},
|
||||
"packageManager": "pnpm@9.1.2",
|
||||
"packageManager": "pnpm@9.7.1",
|
||||
"workspaces": [
|
||||
"packages/frontend",
|
||||
"packages/backend",
|
||||
|
@ -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(),
|
||||
|
@ -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配信
|
||||
|
@ -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', {
|
||||
|
@ -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",
|
||||
|
@ -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) {
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
@ -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') {
|
||||
|
@ -6915,6 +6915,8 @@ export type operations = {
|
||||
'application/json': {
|
||||
/** Format: misskey:id */
|
||||
userId: string;
|
||||
/** Format: misskey:id */
|
||||
itemId: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
@ -782,8 +782,8 @@ importers:
|
||||
specifier: 6.1.1
|
||||
version: 6.1.1
|
||||
cropperjs:
|
||||
specifier: 2.0.0-beta.4
|
||||
version: 2.0.0-beta.4
|
||||
specifier: 2.0.0-rc.0
|
||||
version: 2.0.0-rc.0
|
||||
date-fns:
|
||||
specifier: 3.6.0
|
||||
version: 3.6.0
|
||||
@ -5316,8 +5316,8 @@ packages:
|
||||
resolution: {integrity: sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
cropperjs@2.0.0-beta.4:
|
||||
resolution: {integrity: sha512-tWIQnvbou6eJvQkajwhGLOOEw03dM/i23FmnUQtMKjuzbTDSMP61kcwM77Uit8MXEWcUb5PH8n4jawyrFvj5ag==}
|
||||
cropperjs@2.0.0-rc.0:
|
||||
resolution: {integrity: sha512-/1oQT6Ten55trDVsC3OsVqp09ugf0T/QP8NKcvq0wIUTqpfDqpqD/Nk+c5sdbSKeoGyCwnswZYJJkFgLVm0HFA==}
|
||||
|
||||
cross-env@7.0.3:
|
||||
resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==}
|
||||
@ -15899,7 +15899,7 @@ snapshots:
|
||||
dependencies:
|
||||
luxon: 3.4.4
|
||||
|
||||
cropperjs@2.0.0-beta.4:
|
||||
cropperjs@2.0.0-rc.0:
|
||||
dependencies:
|
||||
'@cropper/elements': 2.0.0-beta.5
|
||||
'@cropper/utils': 2.0.0-beta.5
|
||||
|
Loading…
Reference in New Issue
Block a user