feat: 一度に2つ以上の絵文字をアップロードすることができる
This commit is contained in:
parent
05d2666134
commit
90b4a7a023
|
@ -22,7 +22,7 @@
|
|||
|
||||
이 문서는 CherryPick의 변경 사항만 포함합니다.
|
||||
|
||||
## 13.4.1-cp-4.2.0
|
||||
## 13.4.2-cp-4.2.0
|
||||
출시일: unreleased<br>
|
||||
전체 변경 사항을 확인하려면, [CHANGELOG.md#13xx](CHANGELOG.md#13xx) 문서를 참고하십시오.
|
||||
|
||||
|
@ -43,6 +43,7 @@
|
|||
- Byeolvit 테마 추가 ([Luminon/Byeolvit-Theme](https://github.com/Luminon/Byeolvit-Theme), [libnare/mk-castella@3c95399](https://github.com/libnare/mk-castella/commit/3c95399d0989015bb92836e48d010df07619038b))
|
||||
- buttersc.one 테마 추가 ([libnare/mk-castella@6f15fa1](https://github.com/libnare/mk-castella/commit/6f15fa10b8022d0830254b8f615153d11c441480))
|
||||
- stella.place 테마 추가 ([libnare/mk-castella@f6f77db](https://github.com/libnare/mk-castella/commit/f6f77dbd7f94b87edd3550ecf59e2bbd1fb3c708))
|
||||
- 이모지를 한 번에 두 개 이상 업로드할 수 있는 옵션 추가
|
||||
|
||||
### Client
|
||||
- (Friendly) 데스크톱 모드에서 타임라인 옆에 위젯 영역을 배치하도록
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
---
|
||||
_lang_: "English"
|
||||
addSingle: "Add just one"
|
||||
addMultiple: "Add multiple"
|
||||
showRenoteConfirmPopup: "Show confirmation popup when renote"
|
||||
showSubNoteFooterButton: "Show action buttons in subnotes"
|
||||
showSubNoteFooterButtonDescription: "Enabling this setting will show an action button on the parent note of the replied-to note."
|
||||
|
|
2
locales/index.d.ts
vendored
2
locales/index.d.ts
vendored
|
@ -3,6 +3,8 @@
|
|||
// Do not edit this file directly.
|
||||
export interface Locale {
|
||||
"_lang_": string;
|
||||
"addSingle": string;
|
||||
"addMultiple": string;
|
||||
"showRenoteConfirmPopup": string;
|
||||
"showSubNoteFooterButton": string;
|
||||
"showSubNoteFooterButtonDescription": string;
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
_lang_: "日本語"
|
||||
|
||||
addSingle: "一つだけ追加"
|
||||
addMultiple: "複数追加"
|
||||
showRenoteConfirmPopup: "Renoteするときに確認ポップアップを表示"
|
||||
showSubNoteFooterButton: "サブノートにアクションボタンを表示"
|
||||
showSubNoteFooterButtonDescription: "この設定を有効にすると、返信があるノートの親ノートにアクションボタンを表示します。"
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
---
|
||||
_lang_: "한국어"
|
||||
addSingle: "하나만 추가"
|
||||
addMultiple: "여러 개 추가"
|
||||
showRenoteConfirmPopup: "리노트할 때 확인 팝업 표시"
|
||||
showSubNoteFooterButton: "서브 노트에 액션 버튼 표시"
|
||||
showSubNoteFooterButtonDescription: "이 설정을 활성화하면 답글이 달린 노트의 상위 노트에 액션 버튼을 표시해요."
|
||||
|
|
|
@ -20,6 +20,7 @@ import * as ep___admin_drive_files from './endpoints/admin/drive/files.js';
|
|||
import * as ep___admin_drive_showFile from './endpoints/admin/drive/show-file.js';
|
||||
import * as ep___admin_emoji_addAliasesBulk from './endpoints/admin/emoji/add-aliases-bulk.js';
|
||||
import * as ep___admin_emoji_add from './endpoints/admin/emoji/add.js';
|
||||
import * as ep___admin_emoji_adds from './endpoints/admin/emoji/adds.js';
|
||||
import * as ep___admin_emoji_copy from './endpoints/admin/emoji/copy.js';
|
||||
import * as ep___admin_emoji_deleteBulk from './endpoints/admin/emoji/delete-bulk.js';
|
||||
import * as ep___admin_emoji_delete from './endpoints/admin/emoji/delete.js';
|
||||
|
@ -390,6 +391,7 @@ const $admin_drive_files: Provider = { provide: 'ep:admin/drive/files', useClass
|
|||
const $admin_drive_showFile: Provider = { provide: 'ep:admin/drive/show-file', useClass: ep___admin_drive_showFile.default };
|
||||
const $admin_emoji_addAliasesBulk: Provider = { provide: 'ep:admin/emoji/add-aliases-bulk', useClass: ep___admin_emoji_addAliasesBulk.default };
|
||||
const $admin_emoji_add: Provider = { provide: 'ep:admin/emoji/add', useClass: ep___admin_emoji_add.default };
|
||||
const $admin_emoji_adds: Provider = { provide: 'ep:admin/emoji/adds', useClass: ep___admin_emoji_adds.default };
|
||||
const $admin_emoji_copy: Provider = { provide: 'ep:admin/emoji/copy', useClass: ep___admin_emoji_copy.default };
|
||||
const $admin_emoji_deleteBulk: Provider = { provide: 'ep:admin/emoji/delete-bulk', useClass: ep___admin_emoji_deleteBulk.default };
|
||||
const $admin_emoji_delete: Provider = { provide: 'ep:admin/emoji/delete', useClass: ep___admin_emoji_delete.default };
|
||||
|
@ -764,6 +766,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
|||
$admin_drive_showFile,
|
||||
$admin_emoji_addAliasesBulk,
|
||||
$admin_emoji_add,
|
||||
$admin_emoji_adds,
|
||||
$admin_emoji_copy,
|
||||
$admin_emoji_deleteBulk,
|
||||
$admin_emoji_delete,
|
||||
|
@ -1132,6 +1135,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
|||
$admin_drive_showFile,
|
||||
$admin_emoji_addAliasesBulk,
|
||||
$admin_emoji_add,
|
||||
$admin_emoji_adds,
|
||||
$admin_emoji_copy,
|
||||
$admin_emoji_deleteBulk,
|
||||
$admin_emoji_delete,
|
||||
|
|
|
@ -20,6 +20,7 @@ import * as ep___admin_drive_files from './endpoints/admin/drive/files.js';
|
|||
import * as ep___admin_drive_showFile from './endpoints/admin/drive/show-file.js';
|
||||
import * as ep___admin_emoji_addAliasesBulk from './endpoints/admin/emoji/add-aliases-bulk.js';
|
||||
import * as ep___admin_emoji_add from './endpoints/admin/emoji/add.js';
|
||||
import * as ep___admin_emoji_adds from './endpoints/admin/emoji/adds.js';
|
||||
import * as ep___admin_emoji_copy from './endpoints/admin/emoji/copy.js';
|
||||
import * as ep___admin_emoji_deleteBulk from './endpoints/admin/emoji/delete-bulk.js';
|
||||
import * as ep___admin_emoji_delete from './endpoints/admin/emoji/delete.js';
|
||||
|
@ -388,6 +389,7 @@ const eps = [
|
|||
['admin/drive/show-file', ep___admin_drive_showFile],
|
||||
['admin/emoji/add-aliases-bulk', ep___admin_emoji_addAliasesBulk],
|
||||
['admin/emoji/add', ep___admin_emoji_add],
|
||||
['admin/emoji/adds', ep___admin_emoji_adds],
|
||||
['admin/emoji/copy', ep___admin_emoji_copy],
|
||||
['admin/emoji/delete-bulk', ep___admin_emoji_deleteBulk],
|
||||
['admin/emoji/delete', ep___admin_emoji_delete],
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { DriveFilesRepository } from '@/models/index.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
|
||||
import { secureRndstr } from '@/misc/secure-rndstr.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canManageCustomEmojis',
|
||||
|
||||
errors: {
|
||||
noSuchFile: {
|
||||
message: 'No such file.',
|
||||
code: 'NO_SUCH_FILE',
|
||||
id: 'fc46b5a4-6b92-4c33-ac66-b806659bb5cf',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string', pattern: '^[a-zA-Z0-9_]+$' },
|
||||
fileId: { type: 'string', format: 'misskey:id' },
|
||||
category: {
|
||||
type: 'string',
|
||||
nullable: true,
|
||||
description: 'Use `null` to reset the category.',
|
||||
},
|
||||
aliases: { type: 'array', items: {
|
||||
type: 'string',
|
||||
} },
|
||||
license: { type: 'string', nullable: true },
|
||||
isSensitive: { type: 'boolean' },
|
||||
localOnly: { type: 'boolean' },
|
||||
roleIdsThatCanBeUsedThisEmojiAsReaction: { type: 'array', items: {
|
||||
type: 'string',
|
||||
} },
|
||||
},
|
||||
required: ['fileId'],
|
||||
} as const;
|
||||
|
||||
// TODO: ロジックをサービスに切り出す
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
constructor(
|
||||
@Inject(DI.driveFilesRepository)
|
||||
private driveFilesRepository: DriveFilesRepository,
|
||||
|
||||
private customEmojiService: CustomEmojiService,
|
||||
|
||||
private emojiEntityService: EmojiEntityService,
|
||||
private moderationLogService: ModerationLogService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const driveFile = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
|
||||
if (driveFile == null) throw new ApiError(meta.errors.noSuchFile);
|
||||
|
||||
const name = driveFile.name.split('.')[0].match(/^[a-z0-9_]+$/) ? driveFile.name.split('.')[0] : `_${secureRndstr(8)}_`;
|
||||
|
||||
const emoji = await this.customEmojiService.add({
|
||||
driveFile,
|
||||
name,
|
||||
category: ps.category ?? null,
|
||||
aliases: ps.aliases ?? [],
|
||||
host: null,
|
||||
license: ps.license ?? null,
|
||||
isSensitive: ps.isSensitive ?? false,
|
||||
localOnly: ps.localOnly ?? false,
|
||||
roleIdsThatCanBeUsedThisEmojiAsReaction: ps.roleIdsThatCanBeUsedThisEmojiAsReaction ?? [],
|
||||
});
|
||||
|
||||
this.moderationLogService.insertModerationLog(me, 'addEmoji', {
|
||||
emojiId: emoji.id,
|
||||
});
|
||||
|
||||
return this.emojiEntityService.packDetailed(emoji);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -462,6 +462,10 @@ export type Endpoints = {
|
|||
req: TODO;
|
||||
res: TODO;
|
||||
};
|
||||
'admin/emoji/adds': {
|
||||
req: TODO;
|
||||
res: TODO;
|
||||
};
|
||||
'admin/emoji/copy': {
|
||||
req: TODO;
|
||||
res: TODO;
|
||||
|
@ -2868,7 +2872,7 @@ type UserSorting = '+follower' | '-follower' | '+createdAt' | '-createdAt' | '+u
|
|||
//
|
||||
// src/api.types.ts:16:32 - (ae-forgotten-export) The symbol "TODO" needs to be exported by the entry point index.d.ts
|
||||
// src/api.types.ts:18:25 - (ae-forgotten-export) The symbol "NoParams" needs to be exported by the entry point index.d.ts
|
||||
// src/api.types.ts:652:18 - (ae-forgotten-export) The symbol "ShowUserReq" needs to be exported by the entry point index.d.ts
|
||||
// src/api.types.ts:653:18 - (ae-forgotten-export) The symbol "ShowUserReq" needs to be exported by the entry point index.d.ts
|
||||
// src/streaming.types.ts:33:4 - (ae-forgotten-export) The symbol "FIXME" needs to be exported by the entry point index.d.ts
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
|
|
@ -49,6 +49,7 @@ export type Endpoints = {
|
|||
'admin/drive/files': { req: TODO; res: TODO; };
|
||||
'admin/drive/show-file': { req: TODO; res: TODO; };
|
||||
'admin/emoji/add': { req: TODO; res: TODO; };
|
||||
'admin/emoji/adds': { req: TODO; res: TODO; };
|
||||
'admin/emoji/copy': { req: TODO; res: TODO; };
|
||||
'admin/emoji/list-remote': { req: TODO; res: TODO; };
|
||||
'admin/emoji/list': { req: TODO; res: TODO; };
|
||||
|
|
|
@ -167,6 +167,28 @@ const remoteMenu = (emoji, ev: MouseEvent) => {
|
|||
}], ev.currentTarget ?? ev.target);
|
||||
};
|
||||
|
||||
const uploadMenu = (ev: MouseEvent) => {
|
||||
os.popupMenu([{
|
||||
icon: 'ti ti-file',
|
||||
text: i18n.ts.addSingle,
|
||||
action: add,
|
||||
}, {
|
||||
icon: 'ti ti-files',
|
||||
text: i18n.ts.addMultiple,
|
||||
action: async () => {
|
||||
const files = await selectFiles(ev.currentTarget ?? ev.target, null);
|
||||
|
||||
const promise = Promise.all(files.map(file => os.api('admin/emoji/adds', {
|
||||
fileId: file.id,
|
||||
})));
|
||||
promise.then(() => {
|
||||
emojisPaginationComponent.value.reload();
|
||||
});
|
||||
os.promiseDialog(promise);
|
||||
},
|
||||
}], ev.currentTarget ?? ev.target);
|
||||
};
|
||||
|
||||
const menu = (ev: MouseEvent) => {
|
||||
os.popupMenu([{
|
||||
icon: 'ti ti-download',
|
||||
|
@ -285,7 +307,7 @@ const headerActions = $computed(() => [{
|
|||
asFullButton: true,
|
||||
icon: 'ti ti-plus',
|
||||
text: i18n.ts.addEmoji,
|
||||
handler: add,
|
||||
handler: uploadMenu,
|
||||
}, {
|
||||
icon: 'ti ti-dots',
|
||||
handler: menu,
|
||||
|
|
Loading…
Reference in a new issue