From f9ad127aaf7875bad8fdf55f5ac98bff05997525 Mon Sep 17 00:00:00 2001 From: おさむのひと <46447427+samunohito@users.noreply.github.com> Date: Mon, 20 Jan 2025 20:35:37 +0900 Subject: feat: 新カスタム絵文字管理画面(β)の追加 (#13473) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * wip * wip * wip * wip * wip * wip * wip * wip * fix * fix * fix * fix size * fix register logs * fix img autosize * fix row selection * support delete * fix border rendering * fix display:none * tweak comments * support choose pc file and drive file * support directory drag-drop * fix * fix comment * support context menu on data area * fix autogen * wip イベント整理 * イベントの整理 * refactor grid * fix cell re-render bugs * fix row remove * fix comment * fix validation * fix utils * list maximum * add mimetype check * fix * fix number cell focus * fix over 100 file drop * remove log * fix patchData * fix performance * fix * support update and delete * support remote import * fix layout * heightやめる * fix performance * add list v2 endpoint * support pagination * fix api call * fix no clickable input text * fix limit * fix paging * fix * fix * support search * tweak logs * tweak cell selection * fix range select * block delete * add comment * fix * support import log * fix dialog * refactor * add confirm dialog * fix name * fix autogen * wip * support image change and highlight row * add columns * wip * support sort * add role name * add index to emoji * refine context menu setting * support role select * remove unused buttons * fix url * fix MkRoleSelectDialog.vue * add route * refine remote page * enter key search * fix paste bugs * fix copy/paste * fix keyEvent * fix copy/paste and delete * fix comment * fix MkRoleSelectDialog.vue and storybook scenario * fix MkRoleSelectDialog.vue and storybook scenario * add MkGrid.stories.impl.ts * fix * [wip] add custom-emojis-manager2.stories.impl.ts * [wip] add custom-emojis-manager2.stories.impl.ts * wip * 課題はまだ残っているが、ひとまず完了 * fix validation and register roles * fix upload * optimize import * patch from dev * i18n * revert excess fixes * separate sort order component * add SPDX * revert excess fixes * fix pre test * fix bugs * add type column * fix types * fix CHANGELOG.md * fix lit * lint * tweak style * refactor * fix ci * autogen * Update types.ts * CSS Module化 * fix log * 縦スクロールを無効化 * MkStickyContainer化 * regenerate locales index.d.ts * fix * fix * テスト * ランダム値によるUI変更の抑制 * テスト * tableタグやめる * fix last-child css * fix overflow css * fix endpoint.ts * tweak css * 最新への追従とレイアウト微調整 * ソートキーの指定方法を他と合わせた * fix focus * fix layout * v2エンドポイントのルールに対応 * 表示条件などを微調整 * fix MkDataCell.vue * fix error code * fix error * add comment to MkModal.vue * Update index.d.ts * fix CHANGELOG.md * fix color theme * fix CHANGELOG.md * fix CHANGELOG.md * fix center * fix: テーブルにフォーカスがあり、通常状態であるときはキーイベントの伝搬を止める * fix: ロール選択用のダイアログにてコンディショナルロールを×ボタンで除外できなかったのを修正 * fix remote list folder * sticky footers * chore: fix ci error(just single line-break diff) * fix loading * fix like * comma to space * fix ci * fix ci * removed align-center --------- Co-authored-by: osamu <46447427+sam-osamu@users.noreply.github.com> Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com> Co-authored-by: Sayamame-beans <61457993+Sayamame-beans@users.noreply.github.com> --- .../src/server/api/endpoints/admin/emoji/add.ts | 31 +++-- .../src/server/api/endpoints/admin/emoji/copy.ts | 4 +- .../src/server/api/endpoints/admin/emoji/update.ts | 6 +- .../server/api/endpoints/v2/admin/emoji/list.ts | 126 +++++++++++++++++++++ 4 files changed, 155 insertions(+), 12 deletions(-) create mode 100644 packages/backend/src/server/api/endpoints/v2/admin/emoji/list.ts (limited to 'packages/backend/src/server/api/endpoints') diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts index 796f273330..53256565f6 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts @@ -9,6 +9,7 @@ import type { DriveFilesRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { CustomEmojiService } from '@/core/CustomEmojiService.js'; import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js'; +import { FILE_TYPE_IMAGE } from '@/const.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -24,6 +25,11 @@ export const meta = { code: 'NO_SUCH_FILE', id: 'fc46b5a4-6b92-4c33-ac66-b806659bb5cf', }, + unsupportedFileType: { + message: 'Unsupported file type.', + code: 'UNSUPPORTED_FILE_TYPE', + id: 'f7599d96-8750-af68-1633-9575d625c1a7', + }, duplicateName: { message: 'Duplicate name.', code: 'DUPLICATE_NAME', @@ -47,15 +53,21 @@ export const paramDef = { nullable: true, description: 'Use `null` to reset the category.', }, - aliases: { type: 'array', items: { - type: 'string', - } }, + aliases: { + type: 'array', + items: { + type: 'string', + }, + }, license: { type: 'string', nullable: true }, isSensitive: { type: 'boolean' }, localOnly: { type: 'boolean' }, - roleIdsThatCanBeUsedThisEmojiAsReaction: { type: 'array', items: { - type: 'string', - } }, + roleIdsThatCanBeUsedThisEmojiAsReaction: { + type: 'array', + items: { + type: 'string', + }, + }, }, required: ['name', 'fileId'], } as const; @@ -67,9 +79,7 @@ export default class extends Endpoint { // eslint- constructor( @Inject(DI.driveFilesRepository) private driveFilesRepository: DriveFilesRepository, - private customEmojiService: CustomEmojiService, - private emojiEntityService: EmojiEntityService, ) { super(meta, paramDef, async (ps, me) => { @@ -77,9 +87,12 @@ export default class extends Endpoint { // eslint- if (driveFile == null) throw new ApiError(meta.errors.noSuchFile); const isDuplicate = await this.customEmojiService.checkDuplicate(ps.name); if (isDuplicate) throw new ApiError(meta.errors.duplicateName); + if (!FILE_TYPE_IMAGE.includes(driveFile.type)) throw new ApiError(meta.errors.unsupportedFileType); const emoji = await this.customEmojiService.add({ - driveFile, + originalUrl: driveFile.url, + publicUrl: driveFile.webpublicUrl ?? driveFile.url, + fileType: driveFile.webpublicType ?? driveFile.type, name: ps.name, category: ps.category ?? null, aliases: ps.aliases ?? [], diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts b/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts index 975f892df9..87b58ff6f6 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts @@ -86,7 +86,9 @@ export default class extends Endpoint { // eslint- if (isDuplicate) throw new ApiError(meta.errors.duplicateName); const addedEmoji = await this.customEmojiService.add({ - driveFile, + originalUrl: driveFile.url, + publicUrl: driveFile.webpublicUrl ?? driveFile.url, + fileType: driveFile.webpublicType ?? driveFile.type, name: emoji.name, category: emoji.category, aliases: emoji.aliases, diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts index 212cba5c5d..e3aaa051c1 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts @@ -79,13 +79,15 @@ export default class extends Endpoint { // eslint- } // JSON schemeのanyOfの型変換がうまくいっていないらしい - const required = { id: ps.id, name: ps.name } as + const required = { id: ps.id, name: ps.name } as | { id: MiEmoji['id']; name?: string } | { id?: MiEmoji['id']; name: string }; const error = await this.customEmojiService.update({ ...required, - driveFile, + originalUrl: driveFile != null ? driveFile.url : undefined, + publicUrl: driveFile != null ? (driveFile.webpublicUrl ?? driveFile.url) : undefined, + fileType: driveFile != null ? (driveFile.webpublicType ?? driveFile.type) : undefined, category: ps.category, aliases: ps.aliases, license: ps.license, diff --git a/packages/backend/src/server/api/endpoints/v2/admin/emoji/list.ts b/packages/backend/src/server/api/endpoints/v2/admin/emoji/list.ts new file mode 100644 index 0000000000..9426318e34 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/v2/admin/emoji/list.ts @@ -0,0 +1,126 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js'; +import { CustomEmojiService, fetchEmojisHostTypes, fetchEmojisSortKeys } from '@/core/CustomEmojiService.js'; + +export const meta = { + tags: ['admin'], + + requireCredential: true, + requireRolePolicy: 'canManageCustomEmojis', + kind: 'read:admin:emoji', + + res: { + type: 'object', + properties: { + emojis: { + type: 'array', + items: { + type: 'object', + ref: 'EmojiDetailedAdmin', + }, + }, + count: { type: 'integer' }, + allCount: { type: 'integer' }, + allPages: { type: 'integer' }, + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + query: { + type: 'object', + nullable: true, + properties: { + updatedAtFrom: { type: 'string' }, + updatedAtTo: { type: 'string' }, + name: { type: 'string' }, + host: { type: 'string' }, + uri: { type: 'string' }, + publicUrl: { type: 'string' }, + originalUrl: { type: 'string' }, + type: { type: 'string' }, + aliases: { type: 'string' }, + category: { type: 'string' }, + license: { type: 'string' }, + isSensitive: { type: 'boolean' }, + localOnly: { type: 'boolean' }, + hostType: { + type: 'string', + enum: fetchEmojisHostTypes, + default: 'all', + }, + roleIds: { + type: 'array', + items: { type: 'string', format: 'misskey:id' }, + }, + }, + }, + sinceId: { type: 'string', format: 'misskey:id' }, + untilId: { type: 'string', format: 'misskey:id' }, + limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, + page: { type: 'integer' }, + sortKeys: { + type: 'array', + default: ['-id'], + items: { + type: 'string', + enum: fetchEmojisSortKeys, + }, + }, + }, + required: [], +} as const; + +@Injectable() +export default class extends Endpoint { // eslint-disable-line import/no-default-export + constructor( + private customEmojiService: CustomEmojiService, + private emojiEntityService: EmojiEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const q = ps.query; + const result = await this.customEmojiService.fetchEmojis( + { + query: { + updatedAtFrom: q?.updatedAtFrom, + updatedAtTo: q?.updatedAtTo, + name: q?.name, + host: q?.host, + uri: q?.uri, + publicUrl: q?.publicUrl, + type: q?.type, + aliases: q?.aliases, + category: q?.category, + license: q?.license, + isSensitive: q?.isSensitive, + localOnly: q?.localOnly, + hostType: q?.hostType, + roleIds: q?.roleIds, + }, + sinceId: ps.sinceId, + untilId: ps.untilId, + }, + { + limit: ps.limit, + page: ps.page, + sortKeys: ps.sortKeys, + }, + ); + + return { + emojis: await this.emojiEntityService.packDetailedAdminMany(result.emojis), + count: result.count, + allCount: result.allCount, + allPages: result.allPages, + }; + }); + } +} -- cgit v1.2.3-freya