feat: 一度に2つ以上の絵文字をアップロードすることができる

This commit is contained in:
NoriDev 2023-07-28 18:59:05 +09:00
parent 05d2666134
commit 90b4a7a023
11 changed files with 137 additions and 7 deletions

View file

@ -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) 데스크톱 모드에서 타임라인 옆에 위젯 영역을 배치하도록

View file

@ -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
View file

@ -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;

View file

@ -1,5 +1,7 @@
_lang_: "日本語"
addSingle: "一つだけ追加"
addMultiple: "複数追加"
showRenoteConfirmPopup: "Renoteするときに確認ポップアップを表示"
showSubNoteFooterButton: "サブノートにアクションボタンを表示"
showSubNoteFooterButtonDescription: "この設定を有効にすると、返信があるノートの親ノートにアクションボタンを表示します。"

View file

@ -1,5 +1,7 @@
---
_lang_: "한국어"
addSingle: "하나만 추가"
addMultiple: "여러 개 추가"
showRenoteConfirmPopup: "리노트할 때 확인 팝업 표시"
showSubNoteFooterButton: "서브 노트에 액션 버튼 표시"
showSubNoteFooterButtonDescription: "이 설정을 활성화하면 답글이 달린 노트의 상위 노트에 액션 버튼을 표시해요."

View file

@ -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,

View file

@ -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],

View file

@ -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);
});
}
}

View file

@ -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)

View file

@ -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; };

View file

@ -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,