Feat: 絵文字申請中のやつのテーブルを分けた
Signed-off-by: mattyatea <mattyacocacora0@gmail.com>
This commit is contained in:
parent
97590f2567
commit
fe938bf8e6
1
locales/index.d.ts
vendored
1
locales/index.d.ts
vendored
|
@ -261,6 +261,7 @@ export interface Locale {
|
||||||
"imageUrl": string;
|
"imageUrl": string;
|
||||||
"remove": string;
|
"remove": string;
|
||||||
"removed": string;
|
"removed": string;
|
||||||
|
"undraftAreYouSure": string;
|
||||||
"removeAreYouSure": string;
|
"removeAreYouSure": string;
|
||||||
"deleteAreYouSure": string;
|
"deleteAreYouSure": string;
|
||||||
"resetAreYouSure": string;
|
"resetAreYouSure": string;
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
export class AddEmojiDraftFlag1684236161625 {
|
|
||||||
name = 'AddEmojiDraftFlag1684236161625'
|
|
||||||
|
|
||||||
async up(queryRunner) {
|
|
||||||
await queryRunner.query(`ALTER TABLE "emoji" ADD "draft" boolean NOT NULL DEFAULT false`);
|
|
||||||
}
|
|
||||||
|
|
||||||
async down(queryRunner) {
|
|
||||||
await queryRunner.query(`ALTER TABLE "emoji" DROP COLUMN "draft"`);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -86,6 +86,7 @@ import { ClipEntityService } from './entities/ClipEntityService.js';
|
||||||
import { DriveFileEntityService } from './entities/DriveFileEntityService.js';
|
import { DriveFileEntityService } from './entities/DriveFileEntityService.js';
|
||||||
import { DriveFolderEntityService } from './entities/DriveFolderEntityService.js';
|
import { DriveFolderEntityService } from './entities/DriveFolderEntityService.js';
|
||||||
import { EmojiEntityService } from './entities/EmojiEntityService.js';
|
import { EmojiEntityService } from './entities/EmojiEntityService.js';
|
||||||
|
import { EmojiDraftsEntityService } from './entities/EmojiDraftsEntityService.js';
|
||||||
import { FollowingEntityService } from './entities/FollowingEntityService.js';
|
import { FollowingEntityService } from './entities/FollowingEntityService.js';
|
||||||
import { FollowRequestEntityService } from './entities/FollowRequestEntityService.js';
|
import { FollowRequestEntityService } from './entities/FollowRequestEntityService.js';
|
||||||
import { GalleryLikeEntityService } from './entities/GalleryLikeEntityService.js';
|
import { GalleryLikeEntityService } from './entities/GalleryLikeEntityService.js';
|
||||||
|
@ -217,6 +218,7 @@ const $ClipEntityService: Provider = { provide: 'ClipEntityService', useExisting
|
||||||
const $DriveFileEntityService: Provider = { provide: 'DriveFileEntityService', useExisting: DriveFileEntityService };
|
const $DriveFileEntityService: Provider = { provide: 'DriveFileEntityService', useExisting: DriveFileEntityService };
|
||||||
const $DriveFolderEntityService: Provider = { provide: 'DriveFolderEntityService', useExisting: DriveFolderEntityService };
|
const $DriveFolderEntityService: Provider = { provide: 'DriveFolderEntityService', useExisting: DriveFolderEntityService };
|
||||||
const $EmojiEntityService: Provider = { provide: 'EmojiEntityService', useExisting: EmojiEntityService };
|
const $EmojiEntityService: Provider = { provide: 'EmojiEntityService', useExisting: EmojiEntityService };
|
||||||
|
const $EmojiDraftsEntityService: Provider = { provide: 'EmojiDraftsEntityService', useExisting: EmojiDraftsEntityService };
|
||||||
const $FollowingEntityService: Provider = { provide: 'FollowingEntityService', useExisting: FollowingEntityService };
|
const $FollowingEntityService: Provider = { provide: 'FollowingEntityService', useExisting: FollowingEntityService };
|
||||||
const $FollowRequestEntityService: Provider = { provide: 'FollowRequestEntityService', useExisting: FollowRequestEntityService };
|
const $FollowRequestEntityService: Provider = { provide: 'FollowRequestEntityService', useExisting: FollowRequestEntityService };
|
||||||
const $GalleryLikeEntityService: Provider = { provide: 'GalleryLikeEntityService', useExisting: GalleryLikeEntityService };
|
const $GalleryLikeEntityService: Provider = { provide: 'GalleryLikeEntityService', useExisting: GalleryLikeEntityService };
|
||||||
|
@ -348,6 +350,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||||
DriveFileEntityService,
|
DriveFileEntityService,
|
||||||
DriveFolderEntityService,
|
DriveFolderEntityService,
|
||||||
EmojiEntityService,
|
EmojiEntityService,
|
||||||
|
EmojiDraftsEntityService,
|
||||||
FollowingEntityService,
|
FollowingEntityService,
|
||||||
FollowRequestEntityService,
|
FollowRequestEntityService,
|
||||||
GalleryLikeEntityService,
|
GalleryLikeEntityService,
|
||||||
|
@ -474,6 +477,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||||
$DriveFileEntityService,
|
$DriveFileEntityService,
|
||||||
$DriveFolderEntityService,
|
$DriveFolderEntityService,
|
||||||
$EmojiEntityService,
|
$EmojiEntityService,
|
||||||
|
$EmojiDraftsEntityService,
|
||||||
$FollowingEntityService,
|
$FollowingEntityService,
|
||||||
$FollowRequestEntityService,
|
$FollowRequestEntityService,
|
||||||
$GalleryLikeEntityService,
|
$GalleryLikeEntityService,
|
||||||
|
@ -600,6 +604,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||||
DriveFileEntityService,
|
DriveFileEntityService,
|
||||||
DriveFolderEntityService,
|
DriveFolderEntityService,
|
||||||
EmojiEntityService,
|
EmojiEntityService,
|
||||||
|
EmojiDraftsEntityService,
|
||||||
FollowingEntityService,
|
FollowingEntityService,
|
||||||
FollowRequestEntityService,
|
FollowRequestEntityService,
|
||||||
GalleryLikeEntityService,
|
GalleryLikeEntityService,
|
||||||
|
@ -725,6 +730,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||||
$DriveFileEntityService,
|
$DriveFileEntityService,
|
||||||
$DriveFolderEntityService,
|
$DriveFolderEntityService,
|
||||||
$EmojiEntityService,
|
$EmojiEntityService,
|
||||||
|
$EmojiDraftsEntityService,
|
||||||
$FollowingEntityService,
|
$FollowingEntityService,
|
||||||
$FollowRequestEntityService,
|
$FollowRequestEntityService,
|
||||||
$GalleryLikeEntityService,
|
$GalleryLikeEntityService,
|
||||||
|
|
|
@ -12,12 +12,13 @@ import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
|
||||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||||
import type { MiDriveFile } from '@/models/DriveFile.js';
|
import type { MiDriveFile } from '@/models/DriveFile.js';
|
||||||
import type { MiEmoji } from '@/models/Emoji.js';
|
import type { MiEmoji } from '@/models/Emoji.js';
|
||||||
import type { EmojisRepository, MiRole, MiUser, DriveFilesRepository } from '@/models/_.js';
|
import type { EmojisRepository, EmojiDraftsRepository, MiRole, MiUser } from '@/models/_.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { MemoryKVCache, RedisSingleCache } from '@/misc/cache.js';
|
import { MemoryKVCache, RedisSingleCache } from '@/misc/cache.js';
|
||||||
import { UtilityService } from '@/core/UtilityService.js';
|
import { UtilityService } from '@/core/UtilityService.js';
|
||||||
import type { Serialized } from '@/types.js';
|
import type { Serialized } from '@/types.js';
|
||||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||||
|
import { MiEmojiDraft } from '@/models/EmojiDraft.js';
|
||||||
|
|
||||||
const parseEmojiStrRegexp = /^(\w+)(?:@([\w.-]+))?$/;
|
const parseEmojiStrRegexp = /^(\w+)(?:@([\w.-]+))?$/;
|
||||||
|
|
||||||
|
@ -33,8 +34,8 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
||||||
@Inject(DI.emojisRepository)
|
@Inject(DI.emojisRepository)
|
||||||
private emojisRepository: EmojisRepository,
|
private emojisRepository: EmojisRepository,
|
||||||
|
|
||||||
@Inject(DI.driveFilesRepository)
|
@Inject(DI.emojiDraftsRepository)
|
||||||
private driveFilesRepository: DriveFilesRepository,
|
private emojiDraftsRepository: EmojiDraftsRepository,
|
||||||
|
|
||||||
private utilityService: UtilityService,
|
private utilityService: UtilityService,
|
||||||
private idService: IdService,
|
private idService: IdService,
|
||||||
|
@ -58,6 +59,40 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async draft(data: {
|
||||||
|
driveFile: MiDriveFile;
|
||||||
|
name: string;
|
||||||
|
category: string | null;
|
||||||
|
aliases: string[];
|
||||||
|
license: string | null;
|
||||||
|
isSensitive: boolean;
|
||||||
|
localOnly: boolean;
|
||||||
|
}, me?: MiUser): Promise<MiEmojiDraft> {
|
||||||
|
const emoji = await this.emojiDraftsRepository.insert({
|
||||||
|
id: this.idService.gen(),
|
||||||
|
updatedAt: new Date(),
|
||||||
|
name: data.name,
|
||||||
|
category: data.category,
|
||||||
|
aliases: data.aliases,
|
||||||
|
originalUrl: data.driveFile.url,
|
||||||
|
publicUrl: data.driveFile.webpublicUrl ?? data.driveFile.url,
|
||||||
|
type: data.driveFile.webpublicType ?? data.driveFile.type,
|
||||||
|
license: data.license,
|
||||||
|
isSensitive: data.isSensitive,
|
||||||
|
localOnly: data.localOnly,
|
||||||
|
fileId: data.driveFile.id,
|
||||||
|
}).then(x => this.emojiDraftsRepository.findOneByOrFail(x.identifiers[0]));
|
||||||
|
|
||||||
|
if (me) {
|
||||||
|
this.moderationLogService.log(me, 'addCustomEmoji', {
|
||||||
|
emojiId: emoji.id,
|
||||||
|
emoji: emoji,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return emoji;
|
||||||
|
}
|
||||||
@bindThis
|
@bindThis
|
||||||
public async add(data: {
|
public async add(data: {
|
||||||
driveFile: MiDriveFile;
|
driveFile: MiDriveFile;
|
||||||
|
@ -68,7 +103,6 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
||||||
license: string | null;
|
license: string | null;
|
||||||
isSensitive: boolean;
|
isSensitive: boolean;
|
||||||
localOnly: boolean;
|
localOnly: boolean;
|
||||||
draft: boolean;
|
|
||||||
roleIdsThatCanBeUsedThisEmojiAsReaction: MiRole['id'][];
|
roleIdsThatCanBeUsedThisEmojiAsReaction: MiRole['id'][];
|
||||||
}, moderator?: MiUser): Promise<MiEmoji> {
|
}, moderator?: MiUser): Promise<MiEmoji> {
|
||||||
const emoji = await this.emojisRepository.insert({
|
const emoji = await this.emojisRepository.insert({
|
||||||
|
@ -85,7 +119,6 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
||||||
isSensitive: data.isSensitive,
|
isSensitive: data.isSensitive,
|
||||||
localOnly: data.localOnly,
|
localOnly: data.localOnly,
|
||||||
roleIdsThatCanBeUsedThisEmojiAsReaction: data.roleIdsThatCanBeUsedThisEmojiAsReaction,
|
roleIdsThatCanBeUsedThisEmojiAsReaction: data.roleIdsThatCanBeUsedThisEmojiAsReaction,
|
||||||
draft: data.draft,
|
|
||||||
}).then(x => this.emojisRepository.findOneByOrFail(x.identifiers[0]));
|
}).then(x => this.emojisRepository.findOneByOrFail(x.identifiers[0]));
|
||||||
|
|
||||||
if (data.host == null) {
|
if (data.host == null) {
|
||||||
|
@ -113,44 +146,27 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
||||||
category?: string | null;
|
category?: string | null;
|
||||||
aliases?: string[];
|
aliases?: string[];
|
||||||
license?: string | null;
|
license?: string | null;
|
||||||
fileId?: string | null;
|
|
||||||
isSensitive?: boolean;
|
isSensitive?: boolean;
|
||||||
localOnly?: boolean;
|
localOnly?: boolean;
|
||||||
draft: boolean;
|
|
||||||
roleIdsThatCanBeUsedThisEmojiAsReaction?: MiRole['id'][];
|
roleIdsThatCanBeUsedThisEmojiAsReaction?: MiRole['id'][];
|
||||||
}, moderator?: MiUser): Promise<void> {
|
}, moderator?: MiUser): Promise<void> {
|
||||||
const emoji = await this.emojisRepository.findOneByOrFail({ id: id });
|
const emoji = await this.emojisRepository.findOneByOrFail({ id: id });
|
||||||
const driveFile = data.fileId !== null ? await this.driveFilesRepository.findOneBy({ id: data.fileId }) : null;
|
|
||||||
const sameNameEmoji = await this.emojisRepository.findOneBy({ name: data.name, host: IsNull() });
|
const sameNameEmoji = await this.emojisRepository.findOneBy({ name: data.name, host: IsNull() });
|
||||||
if (sameNameEmoji != null && sameNameEmoji.id !== id) throw new Error('name already exists');
|
if (sameNameEmoji != null && sameNameEmoji.id !== id) throw new Error('name already exists');
|
||||||
|
|
||||||
if (driveFile !== null) {
|
await this.emojisRepository.update(emoji.id, {
|
||||||
await this.emojisRepository.update(emoji.id, {
|
updatedAt: new Date(),
|
||||||
updatedAt: new Date(),
|
name: data.name,
|
||||||
name: data.name,
|
category: data.category,
|
||||||
category: data.category,
|
aliases: data.aliases,
|
||||||
aliases: data.aliases,
|
license: data.license,
|
||||||
license: data.license,
|
isSensitive: data.isSensitive,
|
||||||
isSensitive: data.isSensitive,
|
localOnly: data.localOnly,
|
||||||
localOnly: data.localOnly,
|
originalUrl: data.driveFile != null ? data.driveFile.url : undefined,
|
||||||
originalUrl: data.driveFile != null ? data.driveFile.url : undefined,
|
publicUrl: data.driveFile != null ? (data.driveFile.webpublicUrl ?? data.driveFile.url) : undefined,
|
||||||
publicUrl: data.driveFile != null ? (data.driveFile.webpublicUrl ?? data.driveFile.url) : undefined,
|
type: data.driveFile != null ? (data.driveFile.webpublicType ?? data.driveFile.type) : undefined,
|
||||||
type: data.driveFile != null ? (data.driveFile.webpublicType ?? data.driveFile.type) : undefined,
|
roleIdsThatCanBeUsedThisEmojiAsReaction: data.roleIdsThatCanBeUsedThisEmojiAsReaction ?? undefined,
|
||||||
draft: data.draft,
|
});
|
||||||
});
|
|
||||||
} else {
|
|
||||||
await this.emojisRepository.update(emoji.id, {
|
|
||||||
updatedAt: new Date(),
|
|
||||||
name: data.name,
|
|
||||||
category: data.category,
|
|
||||||
aliases: data.aliases,
|
|
||||||
license: data.license,
|
|
||||||
isSensitive: data.isSensitive,
|
|
||||||
localOnly: data.localOnly,
|
|
||||||
draft: data.draft,
|
|
||||||
roleIdsThatCanBeUsedThisEmojiAsReaction: data.roleIdsThatCanBeUsedThisEmojiAsReaction ?? undefined,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.localEmojisCache.refresh();
|
this.localEmojisCache.refresh();
|
||||||
|
|
||||||
|
@ -179,7 +195,35 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@bindThis
|
||||||
|
public async draftUpdate(id: MiEmoji['id'], data: {
|
||||||
|
driveFile?: MiDriveFile;
|
||||||
|
name?: string;
|
||||||
|
category?: string | null;
|
||||||
|
aliases?: string[];
|
||||||
|
license?: string | null;
|
||||||
|
isSensitive?: boolean;
|
||||||
|
localOnly?: boolean;
|
||||||
|
}, moderator?: MiUser): Promise<void> {
|
||||||
|
const emoji = await this.emojiDraftsRepository.findOneByOrFail({ id: id });
|
||||||
|
const sameNameEmoji = await this.emojiDraftsRepository.findOneBy({ name: data.name });
|
||||||
|
if (sameNameEmoji != null && sameNameEmoji.id !== id) throw new Error('name already exists');
|
||||||
|
|
||||||
|
await this.emojiDraftsRepository.update(emoji.id, {
|
||||||
|
updatedAt: new Date(),
|
||||||
|
name: data.name,
|
||||||
|
category: data.category,
|
||||||
|
aliases: data.aliases,
|
||||||
|
license: data.license,
|
||||||
|
isSensitive: data.isSensitive,
|
||||||
|
localOnly: data.localOnly,
|
||||||
|
originalUrl: data.driveFile != null ? data.driveFile.url : undefined,
|
||||||
|
publicUrl: data.driveFile != null ? (data.driveFile.webpublicUrl ?? data.driveFile.url) : undefined,
|
||||||
|
type: data.driveFile != null ? (data.driveFile.webpublicType ?? data.driveFile.type) : undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.localEmojisCache.refresh();
|
||||||
|
}
|
||||||
@bindThis
|
@bindThis
|
||||||
public async addAliasesBulk(ids: MiEmoji['id'][], aliases: string[]) {
|
public async addAliasesBulk(ids: MiEmoji['id'][], aliases: string[]) {
|
||||||
const emojis = await this.emojisRepository.findBy({
|
const emojis = await this.emojisRepository.findBy({
|
||||||
|
@ -287,7 +331,12 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@bindThis
|
||||||
|
public async draftDelete(id: MiEmojiDraft['id']) {
|
||||||
|
const emoji = await this.emojiDraftsRepository.findOneByOrFail({ id: id });
|
||||||
|
|
||||||
|
await this.emojiDraftsRepository.delete(emoji.id);
|
||||||
|
}
|
||||||
@bindThis
|
@bindThis
|
||||||
public async deleteBulk(ids: MiEmoji['id'][], moderator?: MiUser) {
|
public async deleteBulk(ids: MiEmoji['id'][], moderator?: MiUser) {
|
||||||
const emojis = await this.emojisRepository.findBy({
|
const emojis = await this.emojisRepository.findBy({
|
||||||
|
@ -408,12 +457,20 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
||||||
public checkDuplicate(name: string): Promise<boolean> {
|
public checkDuplicate(name: string): Promise<boolean> {
|
||||||
return this.emojisRepository.exist({ where: { name, host: IsNull() } });
|
return this.emojisRepository.exist({ where: { name, host: IsNull() } });
|
||||||
}
|
}
|
||||||
|
@bindThis
|
||||||
|
public checkDraftDuplicate(name: string): Promise<boolean> {
|
||||||
|
return this.emojiDraftsRepository.exist({ where: { name } });
|
||||||
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public getEmojiById(id: string): Promise<MiEmoji | null> {
|
public getEmojiById(id: string): Promise<MiEmoji | null> {
|
||||||
return this.emojisRepository.findOneBy({ id });
|
return this.emojisRepository.findOneBy({ id });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public getEmojiDraftById(id: string): Promise<MiEmojiDraft | null> {
|
||||||
|
return this.emojiDraftsRepository.findOneBy({ id });
|
||||||
|
}
|
||||||
@bindThis
|
@bindThis
|
||||||
public dispose(): void {
|
public dispose(): void {
|
||||||
this.cache.dispose();
|
this.cache.dispose();
|
||||||
|
|
|
@ -133,7 +133,13 @@ export class DriveFileEntityService {
|
||||||
}
|
}
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
@bindThis
|
||||||
|
public async getFromUrl(url: string): Promise<MiDriveFile | null> {
|
||||||
|
const file = await this.driveFilesRepository.findOneBy({ url: url });
|
||||||
|
if (file === null ) return null;
|
||||||
|
|
||||||
|
return file;
|
||||||
|
}
|
||||||
@bindThis
|
@bindThis
|
||||||
public async calcDriveUsageOf(user: MiUser['id'] | { id: MiUser['id'] }): Promise<number> {
|
public async calcDriveUsageOf(user: MiUser['id'] | { id: MiUser['id'] }): Promise<number> {
|
||||||
const id = typeof user === 'object' ? user.id : user;
|
const id = typeof user === 'object' ? user.id : user;
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import type { EmojiDraftsRepository } from '@/models/_.js';
|
||||||
|
import type { Packed } from '@/misc/json-schema.js';
|
||||||
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import { MiEmojiDraft } from '@/models/EmojiDraft.js';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class EmojiDraftsEntityService {
|
||||||
|
constructor(
|
||||||
|
@Inject(DI.emojiDraftsRepository)
|
||||||
|
private emojiDraftsRepository: EmojiDraftsRepository,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async packSimple(
|
||||||
|
src: MiEmojiDraft['id'] | MiEmojiDraft,
|
||||||
|
): Promise<Packed<'EmojiDraftSimple'>> {
|
||||||
|
const emoji = typeof src === 'object' ? src : await this.emojiDraftsRepository.findOneByOrFail({ id: src });
|
||||||
|
|
||||||
|
return {
|
||||||
|
aliases: emoji.aliases,
|
||||||
|
name: emoji.name,
|
||||||
|
category: emoji.category,
|
||||||
|
// || emoji.originalUrl してるのは後方互換性のため(publicUrlはstringなので??はだめ)
|
||||||
|
url: emoji.publicUrl,
|
||||||
|
isSensitive: emoji.isSensitive ? true : undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public packSimpleMany(
|
||||||
|
emojis: any[],
|
||||||
|
) {
|
||||||
|
return Promise.all(emojis.map(x => this.packSimple(x)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async packDetailed(
|
||||||
|
src: MiEmojiDraft['id'] | MiEmojiDraft,
|
||||||
|
): Promise<Packed<'EmojiDraftDetailed'>> {
|
||||||
|
const emoji = typeof src === 'object' ? src : await this.emojiDraftsRepository.findOneByOrFail({ id: src });
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: emoji.id,
|
||||||
|
aliases: emoji.aliases,
|
||||||
|
name: emoji.name,
|
||||||
|
category: emoji.category,
|
||||||
|
url: emoji.publicUrl,
|
||||||
|
license: emoji.license,
|
||||||
|
isSensitive: emoji.isSensitive,
|
||||||
|
localOnly: emoji.localOnly,
|
||||||
|
fileId: emoji.fileId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public packDetailedMany(
|
||||||
|
emojis: any[],
|
||||||
|
) {
|
||||||
|
return Promise.all(emojis.map(x => this.packDetailed(x)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -33,7 +33,6 @@ export class EmojiEntityService {
|
||||||
url: emoji.publicUrl || emoji.originalUrl,
|
url: emoji.publicUrl || emoji.originalUrl,
|
||||||
isSensitive: emoji.isSensitive ? true : undefined,
|
isSensitive: emoji.isSensitive ? true : undefined,
|
||||||
roleIdsThatCanBeUsedThisEmojiAsReaction: emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.length > 0 ? emoji.roleIdsThatCanBeUsedThisEmojiAsReaction : undefined,
|
roleIdsThatCanBeUsedThisEmojiAsReaction: emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.length > 0 ? emoji.roleIdsThatCanBeUsedThisEmojiAsReaction : undefined,
|
||||||
draft: emoji.draft,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,7 +61,6 @@ export class EmojiEntityService {
|
||||||
isSensitive: emoji.isSensitive,
|
isSensitive: emoji.isSensitive,
|
||||||
localOnly: emoji.localOnly,
|
localOnly: emoji.localOnly,
|
||||||
roleIdsThatCanBeUsedThisEmojiAsReaction: emoji.roleIdsThatCanBeUsedThisEmojiAsReaction,
|
roleIdsThatCanBeUsedThisEmojiAsReaction: emoji.roleIdsThatCanBeUsedThisEmojiAsReaction,
|
||||||
draft: emoji.draft,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,6 +39,7 @@ export const DI = {
|
||||||
followRequestsRepository: Symbol('followRequestsRepository'),
|
followRequestsRepository: Symbol('followRequestsRepository'),
|
||||||
instancesRepository: Symbol('instancesRepository'),
|
instancesRepository: Symbol('instancesRepository'),
|
||||||
emojisRepository: Symbol('emojisRepository'),
|
emojisRepository: Symbol('emojisRepository'),
|
||||||
|
emojiDraftsRepository: Symbol('emojiDraftsRepository'),
|
||||||
driveFilesRepository: Symbol('driveFilesRepository'),
|
driveFilesRepository: Symbol('driveFilesRepository'),
|
||||||
driveFoldersRepository: Symbol('driveFoldersRepository'),
|
driveFoldersRepository: Symbol('driveFoldersRepository'),
|
||||||
metasRepository: Symbol('metasRepository'),
|
metasRepository: Symbol('metasRepository'),
|
||||||
|
|
|
@ -33,7 +33,7 @@ import { packedClipSchema } from '@/models/json-schema/clip.js';
|
||||||
import { packedFederationInstanceSchema } from '@/models/json-schema/federation-instance.js';
|
import { packedFederationInstanceSchema } from '@/models/json-schema/federation-instance.js';
|
||||||
import { packedQueueCountSchema } from '@/models/json-schema/queue.js';
|
import { packedQueueCountSchema } from '@/models/json-schema/queue.js';
|
||||||
import { packedGalleryPostSchema } from '@/models/json-schema/gallery-post.js';
|
import { packedGalleryPostSchema } from '@/models/json-schema/gallery-post.js';
|
||||||
import { packedEmojiDetailedSchema, packedEmojiSimpleSchema } from '@/models/json-schema/emoji.js';
|
import { packedEmojiDetailedSchema, packedEmojiDraftSimpleSchema, packedEmojiSimpleSchema, packedEmojiDraftDetailedSchema } from '@/models/json-schema/emoji.js';
|
||||||
import { packedFlashSchema } from '@/models/json-schema/flash.js';
|
import { packedFlashSchema } from '@/models/json-schema/flash.js';
|
||||||
import { packedAnnouncementSchema } from '@/models/json-schema/announcement.js';
|
import { packedAnnouncementSchema } from '@/models/json-schema/announcement.js';
|
||||||
|
|
||||||
|
@ -69,7 +69,9 @@ export const refs = {
|
||||||
FederationInstance: packedFederationInstanceSchema,
|
FederationInstance: packedFederationInstanceSchema,
|
||||||
GalleryPost: packedGalleryPostSchema,
|
GalleryPost: packedGalleryPostSchema,
|
||||||
EmojiSimple: packedEmojiSimpleSchema,
|
EmojiSimple: packedEmojiSimpleSchema,
|
||||||
|
EmojiDraftSimple: packedEmojiDraftSimpleSchema,
|
||||||
EmojiDetailed: packedEmojiDetailedSchema,
|
EmojiDetailed: packedEmojiDetailedSchema,
|
||||||
|
EmojiDraftDetailed: packedEmojiDraftDetailedSchema,
|
||||||
Flash: packedFlashSchema,
|
Flash: packedFlashSchema,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -81,10 +81,4 @@ export class MiEmoji {
|
||||||
array: true, length: 128, default: '{}',
|
array: true, length: 128, default: '{}',
|
||||||
})
|
})
|
||||||
public roleIdsThatCanBeUsedThisEmojiAsReaction: string[];
|
public roleIdsThatCanBeUsedThisEmojiAsReaction: string[];
|
||||||
|
|
||||||
@Column('boolean', {
|
|
||||||
default: false,
|
|
||||||
nullable: false,
|
|
||||||
})
|
|
||||||
public draft: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
72
packages/backend/src/models/EmojiDraft.ts
Normal file
72
packages/backend/src/models/EmojiDraft.ts
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { PrimaryColumn, Entity, Index, Column } from 'typeorm';
|
||||||
|
import { id } from './util/id.js';
|
||||||
|
|
||||||
|
@Entity('emoji_draft')
|
||||||
|
@Index(['name'], { unique: true })
|
||||||
|
export class MiEmojiDraft {
|
||||||
|
@PrimaryColumn(id())
|
||||||
|
public id: string;
|
||||||
|
|
||||||
|
@Column('timestamp with time zone', {
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
public updatedAt: Date | null;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column('varchar', {
|
||||||
|
length: 128,
|
||||||
|
})
|
||||||
|
public name: string;
|
||||||
|
|
||||||
|
@Column('varchar', {
|
||||||
|
length: 128, nullable: true,
|
||||||
|
})
|
||||||
|
public category: string | null;
|
||||||
|
|
||||||
|
@Column('varchar', {
|
||||||
|
length: 512,
|
||||||
|
})
|
||||||
|
public originalUrl: string;
|
||||||
|
|
||||||
|
@Column('varchar', {
|
||||||
|
length: 512,
|
||||||
|
default: '',
|
||||||
|
})
|
||||||
|
public publicUrl: string;
|
||||||
|
|
||||||
|
// publicUrlの方のtypeが入る
|
||||||
|
@Column('varchar', {
|
||||||
|
length: 64, nullable: true,
|
||||||
|
})
|
||||||
|
public type: string | null;
|
||||||
|
|
||||||
|
@Column('varchar', {
|
||||||
|
array: true, length: 128, default: '{}',
|
||||||
|
})
|
||||||
|
public aliases: string[];
|
||||||
|
|
||||||
|
@Column('varchar', {
|
||||||
|
length: 1024, nullable: true,
|
||||||
|
})
|
||||||
|
public license: string | null;
|
||||||
|
|
||||||
|
@Column('varchar', {
|
||||||
|
length: 1024, nullable: false,
|
||||||
|
})
|
||||||
|
public fileId: string;
|
||||||
|
|
||||||
|
@Column('boolean', {
|
||||||
|
default: false,
|
||||||
|
})
|
||||||
|
public localOnly: boolean;
|
||||||
|
|
||||||
|
@Column('boolean', {
|
||||||
|
default: false,
|
||||||
|
})
|
||||||
|
public isSensitive: boolean;
|
||||||
|
}
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { MiAbuseUserReport, MiAccessToken, MiAd, MiAnnouncement, MiAnnouncementRead, MiAntenna, MiApp, MiAuthSession, MiBlocking, MiChannel, MiChannelFavorite, MiChannelFollowing, MiClip, MiClipFavorite, MiClipNote, MiDriveFile, MiDriveFolder, MiEmoji, MiFlash, MiFlashLike, MiFollowRequest, MiFollowing, MiGalleryLike, MiGalleryPost, MiHashtag, MiInstance, MiMeta, MiModerationLog, MiMuting, MiNote, MiNoteFavorite, MiNoteReaction, MiNoteThreadMuting, MiNoteUnread, MiPage, MiPageLike, MiPasswordResetRequest, MiPoll, MiPollVote, MiPromoNote, MiPromoRead, MiRegistrationTicket, MiRegistryItem, MiRelay, MiRenoteMuting, MiRetentionAggregation, MiRole, MiRoleAssignment, MiSignin, MiSwSubscription, MiUsedUsername, MiUser, MiUserIp, MiUserKeypair, MiUserList, MiUserListFavorite, MiUserListMembership, MiUserMemo, MiUserNotePining, MiUserPending, MiUserProfile, MiUserPublickey, MiUserSecurityKey, MiWebhook } from './_.js';
|
import { MiAbuseUserReport, MiAccessToken, MiAd, MiAnnouncement, MiAnnouncementRead, MiAntenna, MiApp, MiAuthSession, MiBlocking, MiChannel, MiChannelFavorite, MiChannelFollowing, MiClip, MiClipFavorite, MiClipNote, MiDriveFile, MiDriveFolder, MiEmoji, MiEmojiDraft, MiFlash, MiFlashLike, MiFollowRequest, MiFollowing, MiGalleryLike, MiGalleryPost, MiHashtag, MiInstance, MiMeta, MiModerationLog, MiMuting, MiNote, MiNoteFavorite, MiNoteReaction, MiNoteThreadMuting, MiNoteUnread, MiPage, MiPageLike, MiPasswordResetRequest, MiPoll, MiPollVote, MiPromoNote, MiPromoRead, MiRegistrationTicket, MiRegistryItem, MiRelay, MiRenoteMuting, MiRetentionAggregation, MiRole, MiRoleAssignment, MiSignin, MiSwSubscription, MiUsedUsername, MiUser, MiUserIp, MiUserKeypair, MiUserList, MiUserListFavorite, MiUserListMembership, MiUserMemo, MiUserNotePining, MiUserPending, MiUserProfile, MiUserPublickey, MiUserSecurityKey, MiWebhook } from './_.js';
|
||||||
import type { DataSource } from 'typeorm';
|
import type { DataSource } from 'typeorm';
|
||||||
import type { Provider } from '@nestjs/common';
|
import type { Provider } from '@nestjs/common';
|
||||||
|
|
||||||
|
@ -165,6 +165,12 @@ const $emojisRepository: Provider = {
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const $emojiDraftsRepository: Provider = {
|
||||||
|
provide: DI.emojiDraftsRepository,
|
||||||
|
useFactory: (db: DataSource) => db.getRepository(MiEmojiDraft),
|
||||||
|
inject: [DI.db],
|
||||||
|
};
|
||||||
|
|
||||||
const $driveFilesRepository: Provider = {
|
const $driveFilesRepository: Provider = {
|
||||||
provide: DI.driveFilesRepository,
|
provide: DI.driveFilesRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiDriveFile),
|
useFactory: (db: DataSource) => db.getRepository(MiDriveFile),
|
||||||
|
@ -423,6 +429,7 @@ const $userMemosRepository: Provider = {
|
||||||
$followRequestsRepository,
|
$followRequestsRepository,
|
||||||
$instancesRepository,
|
$instancesRepository,
|
||||||
$emojisRepository,
|
$emojisRepository,
|
||||||
|
$emojiDraftsRepository,
|
||||||
$driveFilesRepository,
|
$driveFilesRepository,
|
||||||
$driveFoldersRepository,
|
$driveFoldersRepository,
|
||||||
$metasRepository,
|
$metasRepository,
|
||||||
|
@ -489,6 +496,7 @@ const $userMemosRepository: Provider = {
|
||||||
$followRequestsRepository,
|
$followRequestsRepository,
|
||||||
$instancesRepository,
|
$instancesRepository,
|
||||||
$emojisRepository,
|
$emojisRepository,
|
||||||
|
$emojiDraftsRepository,
|
||||||
$driveFilesRepository,
|
$driveFilesRepository,
|
||||||
$driveFoldersRepository,
|
$driveFoldersRepository,
|
||||||
$metasRepository,
|
$metasRepository,
|
||||||
|
|
|
@ -67,6 +67,7 @@ import { MiRoleAssignment } from '@/models/RoleAssignment.js';
|
||||||
import { MiFlash } from '@/models/Flash.js';
|
import { MiFlash } from '@/models/Flash.js';
|
||||||
import { MiFlashLike } from '@/models/FlashLike.js';
|
import { MiFlashLike } from '@/models/FlashLike.js';
|
||||||
import { MiUserListFavorite } from '@/models/UserListFavorite.js';
|
import { MiUserListFavorite } from '@/models/UserListFavorite.js';
|
||||||
|
import { MiEmojiDraft } from '@/models/EmojiDraft.js';
|
||||||
import type { Repository } from 'typeorm';
|
import type { Repository } from 'typeorm';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
@ -87,6 +88,7 @@ export {
|
||||||
MiDriveFile,
|
MiDriveFile,
|
||||||
MiDriveFolder,
|
MiDriveFolder,
|
||||||
MiEmoji,
|
MiEmoji,
|
||||||
|
MiEmojiDraft,
|
||||||
MiFollowing,
|
MiFollowing,
|
||||||
MiFollowRequest,
|
MiFollowRequest,
|
||||||
MiGalleryLike,
|
MiGalleryLike,
|
||||||
|
@ -153,6 +155,7 @@ export type ClipFavoritesRepository = Repository<MiClipFavorite>;
|
||||||
export type DriveFilesRepository = Repository<MiDriveFile>;
|
export type DriveFilesRepository = Repository<MiDriveFile>;
|
||||||
export type DriveFoldersRepository = Repository<MiDriveFolder>;
|
export type DriveFoldersRepository = Repository<MiDriveFolder>;
|
||||||
export type EmojisRepository = Repository<MiEmoji>;
|
export type EmojisRepository = Repository<MiEmoji>;
|
||||||
|
export type EmojiDraftsRepository = Repository<MiEmojiDraft>;
|
||||||
export type FollowingsRepository = Repository<MiFollowing>;
|
export type FollowingsRepository = Repository<MiFollowing>;
|
||||||
export type FollowRequestsRepository = Repository<MiFollowRequest>;
|
export type FollowRequestsRepository = Repository<MiFollowRequest>;
|
||||||
export type GalleryLikesRepository = Repository<MiGalleryLike>;
|
export type GalleryLikesRepository = Repository<MiGalleryLike>;
|
||||||
|
|
|
@ -40,10 +40,36 @@ export const packedEmojiSimpleSchema = {
|
||||||
format: 'id',
|
format: 'id',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
draft: {
|
},
|
||||||
type: 'boolean',
|
} as const;
|
||||||
|
export const packedEmojiDraftSimpleSchema = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
aliases: {
|
||||||
|
type: 'array',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
items: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
format: 'id',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
category: {
|
||||||
|
type: 'string',
|
||||||
optional: false, nullable: true,
|
optional: false, nullable: true,
|
||||||
},
|
},
|
||||||
|
url: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
isSensitive: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: true, nullable: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
@ -85,10 +111,6 @@ export const packedEmojiDetailedSchema = {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
optional: false, nullable: true,
|
optional: false, nullable: true,
|
||||||
},
|
},
|
||||||
draft: {
|
|
||||||
type: 'boolean',
|
|
||||||
optional: false, nullable: true,
|
|
||||||
},
|
|
||||||
isSensitive: {
|
isSensitive: {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
|
@ -108,3 +130,51 @@ export const packedEmojiDetailedSchema = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
export const packedEmojiDraftDetailedSchema = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
id: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
format: 'id',
|
||||||
|
},
|
||||||
|
aliases: {
|
||||||
|
type: 'array',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
items: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
format: 'id',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
category: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: true,
|
||||||
|
},
|
||||||
|
url: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
license: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: true,
|
||||||
|
},
|
||||||
|
isSensitive: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
localOnly: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
fileId: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
|
@ -28,6 +28,7 @@ import { MiClipFavorite } from '@/models/ClipFavorite.js';
|
||||||
import { MiDriveFile } from '@/models/DriveFile.js';
|
import { MiDriveFile } from '@/models/DriveFile.js';
|
||||||
import { MiDriveFolder } from '@/models/DriveFolder.js';
|
import { MiDriveFolder } from '@/models/DriveFolder.js';
|
||||||
import { MiEmoji } from '@/models/Emoji.js';
|
import { MiEmoji } from '@/models/Emoji.js';
|
||||||
|
import { MiEmojiDraft } from '@/models/EmojiDraft.js';
|
||||||
import { MiFollowing } from '@/models/Following.js';
|
import { MiFollowing } from '@/models/Following.js';
|
||||||
import { MiFollowRequest } from '@/models/FollowRequest.js';
|
import { MiFollowRequest } from '@/models/FollowRequest.js';
|
||||||
import { MiGalleryLike } from '@/models/GalleryLike.js';
|
import { MiGalleryLike } from '@/models/GalleryLike.js';
|
||||||
|
@ -160,6 +161,7 @@ export const entities = [
|
||||||
MiPoll,
|
MiPoll,
|
||||||
MiPollVote,
|
MiPollVote,
|
||||||
MiEmoji,
|
MiEmoji,
|
||||||
|
MiEmojiDraft,
|
||||||
MiHashtag,
|
MiHashtag,
|
||||||
MiSwSubscription,
|
MiSwSubscription,
|
||||||
MiAbuseUserReport,
|
MiAbuseUserReport,
|
||||||
|
|
|
@ -37,6 +37,7 @@ import * as ep___admin_emoji_setAliasesBulk from './endpoints/admin/emoji/set-al
|
||||||
import * as ep___admin_emoji_setCategoryBulk from './endpoints/admin/emoji/set-category-bulk.js';
|
import * as ep___admin_emoji_setCategoryBulk from './endpoints/admin/emoji/set-category-bulk.js';
|
||||||
import * as ep___admin_emoji_setLicenseBulk from './endpoints/admin/emoji/set-license-bulk.js';
|
import * as ep___admin_emoji_setLicenseBulk from './endpoints/admin/emoji/set-license-bulk.js';
|
||||||
import * as ep___admin_emoji_update from './endpoints/admin/emoji/update.js';
|
import * as ep___admin_emoji_update from './endpoints/admin/emoji/update.js';
|
||||||
|
import * as ep___admin_emoji_draftUpdate from './endpoints/admin/emoji/draft-update.js';
|
||||||
import * as ep___admin_federation_deleteAllFiles from './endpoints/admin/federation/delete-all-files.js';
|
import * as ep___admin_federation_deleteAllFiles from './endpoints/admin/federation/delete-all-files.js';
|
||||||
import * as ep___admin_federation_refreshRemoteInstanceMetadata from './endpoints/admin/federation/refresh-remote-instance-metadata.js';
|
import * as ep___admin_federation_refreshRemoteInstanceMetadata from './endpoints/admin/federation/refresh-remote-instance-metadata.js';
|
||||||
import * as ep___admin_federation_removeAllFollowing from './endpoints/admin/federation/remove-all-following.js';
|
import * as ep___admin_federation_removeAllFollowing from './endpoints/admin/federation/remove-all-following.js';
|
||||||
|
@ -243,6 +244,7 @@ import * as ep___invite_list from './endpoints/invite/list.js';
|
||||||
import * as ep___invite_limit from './endpoints/invite/limit.js';
|
import * as ep___invite_limit from './endpoints/invite/limit.js';
|
||||||
import * as ep___meta from './endpoints/meta.js';
|
import * as ep___meta from './endpoints/meta.js';
|
||||||
import * as ep___emojis from './endpoints/emojis.js';
|
import * as ep___emojis from './endpoints/emojis.js';
|
||||||
|
import * as ep___emojiDrafts from './endpoints/emoji-drafts.js';
|
||||||
import * as ep___emoji from './endpoints/emoji.js';
|
import * as ep___emoji from './endpoints/emoji.js';
|
||||||
import * as ep___miauth_genToken from './endpoints/miauth/gen-token.js';
|
import * as ep___miauth_genToken from './endpoints/miauth/gen-token.js';
|
||||||
import * as ep___mute_create from './endpoints/mute/create.js';
|
import * as ep___mute_create from './endpoints/mute/create.js';
|
||||||
|
@ -388,6 +390,7 @@ const $admin_emoji_setAliasesBulk: Provider = { provide: 'ep:admin/emoji/set-ali
|
||||||
const $admin_emoji_setCategoryBulk: Provider = { provide: 'ep:admin/emoji/set-category-bulk', useClass: ep___admin_emoji_setCategoryBulk.default };
|
const $admin_emoji_setCategoryBulk: Provider = { provide: 'ep:admin/emoji/set-category-bulk', useClass: ep___admin_emoji_setCategoryBulk.default };
|
||||||
const $admin_emoji_setLicenseBulk: Provider = { provide: 'ep:admin/emoji/set-license-bulk', useClass: ep___admin_emoji_setLicenseBulk.default };
|
const $admin_emoji_setLicenseBulk: Provider = { provide: 'ep:admin/emoji/set-license-bulk', useClass: ep___admin_emoji_setLicenseBulk.default };
|
||||||
const $admin_emoji_update: Provider = { provide: 'ep:admin/emoji/update', useClass: ep___admin_emoji_update.default };
|
const $admin_emoji_update: Provider = { provide: 'ep:admin/emoji/update', useClass: ep___admin_emoji_update.default };
|
||||||
|
const $admin_emoji_draftUpdate: Provider = { provide: 'ep:admin/emoji/draft-update', useClass: ep___admin_emoji_draftUpdate.default };
|
||||||
const $admin_federation_deleteAllFiles: Provider = { provide: 'ep:admin/federation/delete-all-files', useClass: ep___admin_federation_deleteAllFiles.default };
|
const $admin_federation_deleteAllFiles: Provider = { provide: 'ep:admin/federation/delete-all-files', useClass: ep___admin_federation_deleteAllFiles.default };
|
||||||
const $admin_federation_refreshRemoteInstanceMetadata: Provider = { provide: 'ep:admin/federation/refresh-remote-instance-metadata', useClass: ep___admin_federation_refreshRemoteInstanceMetadata.default };
|
const $admin_federation_refreshRemoteInstanceMetadata: Provider = { provide: 'ep:admin/federation/refresh-remote-instance-metadata', useClass: ep___admin_federation_refreshRemoteInstanceMetadata.default };
|
||||||
const $admin_federation_removeAllFollowing: Provider = { provide: 'ep:admin/federation/remove-all-following', useClass: ep___admin_federation_removeAllFollowing.default };
|
const $admin_federation_removeAllFollowing: Provider = { provide: 'ep:admin/federation/remove-all-following', useClass: ep___admin_federation_removeAllFollowing.default };
|
||||||
|
@ -594,6 +597,7 @@ const $invite_list: Provider = { provide: 'ep:invite/list', useClass: ep___invit
|
||||||
const $invite_limit: Provider = { provide: 'ep:invite/limit', useClass: ep___invite_limit.default };
|
const $invite_limit: Provider = { provide: 'ep:invite/limit', useClass: ep___invite_limit.default };
|
||||||
const $meta: Provider = { provide: 'ep:meta', useClass: ep___meta.default };
|
const $meta: Provider = { provide: 'ep:meta', useClass: ep___meta.default };
|
||||||
const $emojis: Provider = { provide: 'ep:emojis', useClass: ep___emojis.default };
|
const $emojis: Provider = { provide: 'ep:emojis', useClass: ep___emojis.default };
|
||||||
|
const $emoji_drafts: Provider = { provide: 'ep:emoji-drafts', useClass: ep___emojiDrafts.default };
|
||||||
const $emoji: Provider = { provide: 'ep:emoji', useClass: ep___emoji.default };
|
const $emoji: Provider = { provide: 'ep:emoji', useClass: ep___emoji.default };
|
||||||
const $miauth_genToken: Provider = { provide: 'ep:miauth/gen-token', useClass: ep___miauth_genToken.default };
|
const $miauth_genToken: Provider = { provide: 'ep:miauth/gen-token', useClass: ep___miauth_genToken.default };
|
||||||
const $mute_create: Provider = { provide: 'ep:mute/create', useClass: ep___mute_create.default };
|
const $mute_create: Provider = { provide: 'ep:mute/create', useClass: ep___mute_create.default };
|
||||||
|
@ -743,6 +747,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
||||||
$admin_emoji_setCategoryBulk,
|
$admin_emoji_setCategoryBulk,
|
||||||
$admin_emoji_setLicenseBulk,
|
$admin_emoji_setLicenseBulk,
|
||||||
$admin_emoji_update,
|
$admin_emoji_update,
|
||||||
|
$admin_emoji_draftUpdate,
|
||||||
$admin_federation_deleteAllFiles,
|
$admin_federation_deleteAllFiles,
|
||||||
$admin_federation_refreshRemoteInstanceMetadata,
|
$admin_federation_refreshRemoteInstanceMetadata,
|
||||||
$admin_federation_removeAllFollowing,
|
$admin_federation_removeAllFollowing,
|
||||||
|
@ -949,6 +954,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
||||||
$invite_limit,
|
$invite_limit,
|
||||||
$meta,
|
$meta,
|
||||||
$emojis,
|
$emojis,
|
||||||
|
$emoji_drafts,
|
||||||
$emoji,
|
$emoji,
|
||||||
$miauth_genToken,
|
$miauth_genToken,
|
||||||
$mute_create,
|
$mute_create,
|
||||||
|
@ -1092,6 +1098,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
||||||
$admin_emoji_setCategoryBulk,
|
$admin_emoji_setCategoryBulk,
|
||||||
$admin_emoji_setLicenseBulk,
|
$admin_emoji_setLicenseBulk,
|
||||||
$admin_emoji_update,
|
$admin_emoji_update,
|
||||||
|
$admin_emoji_draftUpdate,
|
||||||
$admin_federation_deleteAllFiles,
|
$admin_federation_deleteAllFiles,
|
||||||
$admin_federation_refreshRemoteInstanceMetadata,
|
$admin_federation_refreshRemoteInstanceMetadata,
|
||||||
$admin_federation_removeAllFollowing,
|
$admin_federation_removeAllFollowing,
|
||||||
|
@ -1298,6 +1305,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
||||||
$invite_limit,
|
$invite_limit,
|
||||||
$meta,
|
$meta,
|
||||||
$emojis,
|
$emojis,
|
||||||
|
$emoji_drafts,
|
||||||
$emoji,
|
$emoji,
|
||||||
$miauth_genToken,
|
$miauth_genToken,
|
||||||
$mute_create,
|
$mute_create,
|
||||||
|
|
|
@ -37,6 +37,7 @@ import * as ep___admin_emoji_setAliasesBulk from './endpoints/admin/emoji/set-al
|
||||||
import * as ep___admin_emoji_setCategoryBulk from './endpoints/admin/emoji/set-category-bulk.js';
|
import * as ep___admin_emoji_setCategoryBulk from './endpoints/admin/emoji/set-category-bulk.js';
|
||||||
import * as ep___admin_emoji_setLicenseBulk from './endpoints/admin/emoji/set-license-bulk.js';
|
import * as ep___admin_emoji_setLicenseBulk from './endpoints/admin/emoji/set-license-bulk.js';
|
||||||
import * as ep___admin_emoji_update from './endpoints/admin/emoji/update.js';
|
import * as ep___admin_emoji_update from './endpoints/admin/emoji/update.js';
|
||||||
|
import * as ep___admin_emoji_draftUpdate from './endpoints/admin/emoji/draft-update.js';
|
||||||
import * as ep___admin_federation_deleteAllFiles from './endpoints/admin/federation/delete-all-files.js';
|
import * as ep___admin_federation_deleteAllFiles from './endpoints/admin/federation/delete-all-files.js';
|
||||||
import * as ep___admin_federation_refreshRemoteInstanceMetadata from './endpoints/admin/federation/refresh-remote-instance-metadata.js';
|
import * as ep___admin_federation_refreshRemoteInstanceMetadata from './endpoints/admin/federation/refresh-remote-instance-metadata.js';
|
||||||
import * as ep___admin_federation_removeAllFollowing from './endpoints/admin/federation/remove-all-following.js';
|
import * as ep___admin_federation_removeAllFollowing from './endpoints/admin/federation/remove-all-following.js';
|
||||||
|
@ -243,6 +244,7 @@ import * as ep___invite_list from './endpoints/invite/list.js';
|
||||||
import * as ep___invite_limit from './endpoints/invite/limit.js';
|
import * as ep___invite_limit from './endpoints/invite/limit.js';
|
||||||
import * as ep___meta from './endpoints/meta.js';
|
import * as ep___meta from './endpoints/meta.js';
|
||||||
import * as ep___emojis from './endpoints/emojis.js';
|
import * as ep___emojis from './endpoints/emojis.js';
|
||||||
|
import * as ep___emojiDrafts from './endpoints/emoji-drafts.js';
|
||||||
import * as ep___emoji from './endpoints/emoji.js';
|
import * as ep___emoji from './endpoints/emoji.js';
|
||||||
import * as ep___miauth_genToken from './endpoints/miauth/gen-token.js';
|
import * as ep___miauth_genToken from './endpoints/miauth/gen-token.js';
|
||||||
import * as ep___mute_create from './endpoints/mute/create.js';
|
import * as ep___mute_create from './endpoints/mute/create.js';
|
||||||
|
@ -386,6 +388,7 @@ const eps = [
|
||||||
['admin/emoji/set-category-bulk', ep___admin_emoji_setCategoryBulk],
|
['admin/emoji/set-category-bulk', ep___admin_emoji_setCategoryBulk],
|
||||||
['admin/emoji/set-license-bulk', ep___admin_emoji_setLicenseBulk],
|
['admin/emoji/set-license-bulk', ep___admin_emoji_setLicenseBulk],
|
||||||
['admin/emoji/update', ep___admin_emoji_update],
|
['admin/emoji/update', ep___admin_emoji_update],
|
||||||
|
['admin/emoji/draft-update', ep___admin_emoji_draftUpdate],
|
||||||
['admin/federation/delete-all-files', ep___admin_federation_deleteAllFiles],
|
['admin/federation/delete-all-files', ep___admin_federation_deleteAllFiles],
|
||||||
['admin/federation/refresh-remote-instance-metadata', ep___admin_federation_refreshRemoteInstanceMetadata],
|
['admin/federation/refresh-remote-instance-metadata', ep___admin_federation_refreshRemoteInstanceMetadata],
|
||||||
['admin/federation/remove-all-following', ep___admin_federation_removeAllFollowing],
|
['admin/federation/remove-all-following', ep___admin_federation_removeAllFollowing],
|
||||||
|
@ -592,6 +595,7 @@ const eps = [
|
||||||
['invite/limit', ep___invite_limit],
|
['invite/limit', ep___invite_limit],
|
||||||
['meta', ep___meta],
|
['meta', ep___meta],
|
||||||
['emojis', ep___emojis],
|
['emojis', ep___emojis],
|
||||||
|
['emoji-drafts', ep___emojiDrafts],
|
||||||
['emoji', ep___emoji],
|
['emoji', ep___emoji],
|
||||||
['miauth/gen-token', ep___miauth_genToken],
|
['miauth/gen-token', ep___miauth_genToken],
|
||||||
['mute/create', ep___mute_create],
|
['mute/create', ep___mute_create],
|
||||||
|
|
|
@ -18,6 +18,11 @@ export const meta = {
|
||||||
code: 'NO_SUCH_FILE',
|
code: 'NO_SUCH_FILE',
|
||||||
id: 'fc46b5a4-6b92-4c33-ac66-b806659bb5cf',
|
id: 'fc46b5a4-6b92-4c33-ac66-b806659bb5cf',
|
||||||
},
|
},
|
||||||
|
duplicateName: {
|
||||||
|
message: 'Duplicate name.',
|
||||||
|
code: 'DUPLICATE_NAME',
|
||||||
|
id: 'f7a3462c-4e6e-4069-8421-b9bd4f4c3975',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
@ -55,11 +60,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
private moderationLogService: ModerationLogService,
|
private moderationLogService: ModerationLogService,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
|
const isDuplicate = await this.customEmojiService.checkDuplicate(ps.name);
|
||||||
|
const isDraftDuplicate = await this.customEmojiService.checkDraftDuplicate(ps.name);
|
||||||
|
|
||||||
|
if (isDuplicate || isDraftDuplicate) throw new ApiError(meta.errors.duplicateName);
|
||||||
const driveFile = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
|
const driveFile = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
|
||||||
|
|
||||||
if (driveFile == null) throw new ApiError(meta.errors.noSuchFile);
|
if (driveFile == null) throw new ApiError(meta.errors.noSuchFile);
|
||||||
|
|
||||||
const emoji = await this.customEmojiService.add({
|
const emoji = await this.customEmojiService.draft({
|
||||||
driveFile,
|
driveFile,
|
||||||
name: ps.name,
|
name: ps.name,
|
||||||
category: ps.category ?? null,
|
category: ps.category ?? null,
|
||||||
|
@ -67,9 +76,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
license: ps.license ?? null,
|
license: ps.license ?? null,
|
||||||
isSensitive: ps.isSensitive ?? false,
|
isSensitive: ps.isSensitive ?? false,
|
||||||
localOnly: ps.localOnly ?? false,
|
localOnly: ps.localOnly ?? false,
|
||||||
host: null,
|
|
||||||
draft: true,
|
|
||||||
roleIdsThatCanBeUsedThisEmojiAsReaction: [],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.moderationLogService.log(me, 'addCustomEmoji', {
|
await this.moderationLogService.log(me, 'addCustomEmoji', {
|
||||||
|
|
|
@ -51,7 +51,7 @@ export const paramDef = {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
} },
|
} },
|
||||||
},
|
},
|
||||||
required: ['name', 'fileId', 'draft'],
|
required: ['name', 'fileId'],
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
// TODO: ロジックをサービスに切り出す
|
// TODO: ロジックをサービスに切り出す
|
||||||
|
@ -82,7 +82,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
license: ps.license ?? null,
|
license: ps.license ?? null,
|
||||||
isSensitive: ps.isSensitive ?? false,
|
isSensitive: ps.isSensitive ?? false,
|
||||||
localOnly: ps.localOnly ?? false,
|
localOnly: ps.localOnly ?? false,
|
||||||
draft: false,
|
|
||||||
roleIdsThatCanBeUsedThisEmojiAsReaction: ps.roleIdsThatCanBeUsedThisEmojiAsReaction ?? [],
|
roleIdsThatCanBeUsedThisEmojiAsReaction: ps.roleIdsThatCanBeUsedThisEmojiAsReaction ?? [],
|
||||||
}, me);
|
}, me);
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
private customEmojiService: CustomEmojiService,
|
private customEmojiService: CustomEmojiService,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
await this.customEmojiService.delete(ps.id, me);
|
const emoji = await this.customEmojiService.getEmojiById(ps.id);
|
||||||
|
const draftEmoji = await this.customEmojiService.getEmojiDraftById(ps.id);
|
||||||
|
if (emoji != null) {
|
||||||
|
await this.customEmojiService.delete(ps.id, me);
|
||||||
|
}
|
||||||
|
if (draftEmoji != null) {
|
||||||
|
await this.customEmojiService.draftDelete(ps.id);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,120 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
|
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
|
||||||
|
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
|
||||||
|
import type { DriveFilesRepository, EmojiDraftsRepository } from '@/models/_.js';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import { ApiError } from '../../../error.js';
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
tags: ['admin'],
|
||||||
|
|
||||||
|
requireCredential: true,
|
||||||
|
requireRolePolicy: 'canManageCustomEmojis',
|
||||||
|
|
||||||
|
errors: {
|
||||||
|
noSuchEmoji: {
|
||||||
|
message: 'No such emoji.',
|
||||||
|
code: 'NO_SUCH_EMOJI',
|
||||||
|
id: '684dec9d-a8c2-4364-9aa8-456c49cb1dc8',
|
||||||
|
},
|
||||||
|
noSuchFile: {
|
||||||
|
message: 'No such file.',
|
||||||
|
code: 'NO_SUCH_FILE',
|
||||||
|
id: '14fb9fd9-0731-4e2f-aeb9-f09e4740333d',
|
||||||
|
},
|
||||||
|
sameNameEmojiExists: {
|
||||||
|
message: 'Emoji that have same name already exists.',
|
||||||
|
code: 'SAME_NAME_EMOJI_EXISTS',
|
||||||
|
id: '7180fe9d-1ee3-bff9-647d-fe9896d2ffb8',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const paramDef = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
id: { type: 'string', format: 'misskey:id' },
|
||||||
|
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',
|
||||||
|
} },
|
||||||
|
draft: { type: 'boolean' },
|
||||||
|
},
|
||||||
|
required: ['id', 'name', 'aliases'],
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
|
constructor(
|
||||||
|
@Inject(DI.driveFilesRepository)
|
||||||
|
private driveFilesRepository: DriveFilesRepository,
|
||||||
|
|
||||||
|
@Inject(DI.emojiDraftsRepository)
|
||||||
|
private emojiDraftsRepository: EmojiDraftsRepository,
|
||||||
|
|
||||||
|
private customEmojiService: CustomEmojiService,
|
||||||
|
private driveFileEntityService: DriveFileEntityService,
|
||||||
|
) {
|
||||||
|
super(meta, paramDef, async (ps, me) => {
|
||||||
|
let driveFile;
|
||||||
|
const isDraft = !!ps.draft;
|
||||||
|
if (ps.fileId) {
|
||||||
|
driveFile = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
|
||||||
|
if (driveFile == null) throw new ApiError(meta.errors.noSuchFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
const emoji = await this.customEmojiService.getEmojiDraftById(ps.id);
|
||||||
|
if (emoji != null) {
|
||||||
|
if (ps.name !== emoji.name) {
|
||||||
|
const isDuplicate = await this.customEmojiService.checkDraftDuplicate(ps.name);
|
||||||
|
if (isDuplicate) throw new ApiError(meta.errors.sameNameEmojiExists);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new ApiError(meta.errors.noSuchEmoji);
|
||||||
|
}
|
||||||
|
if (!isDraft) {
|
||||||
|
const file = await this.driveFileEntityService.getFromUrl(emoji.originalUrl);
|
||||||
|
if (file === null) throw new ApiError(meta.errors.noSuchFile);
|
||||||
|
await this.customEmojiService.add({
|
||||||
|
driveFile: file,
|
||||||
|
name: ps.name,
|
||||||
|
category: ps.category ?? null,
|
||||||
|
aliases: ps.aliases ?? [],
|
||||||
|
host: null,
|
||||||
|
license: ps.license ?? null,
|
||||||
|
isSensitive: ps.isSensitive ?? false,
|
||||||
|
localOnly: ps.localOnly ?? false,
|
||||||
|
roleIdsThatCanBeUsedThisEmojiAsReaction: [],
|
||||||
|
}, me);
|
||||||
|
await this.customEmojiService.draftDelete(ps.id);
|
||||||
|
} else {
|
||||||
|
await this.customEmojiService.draftUpdate(ps.id, {
|
||||||
|
name: ps.name,
|
||||||
|
category: ps.category ?? null,
|
||||||
|
aliases: ps.aliases ?? [],
|
||||||
|
license: ps.license ?? null,
|
||||||
|
isSensitive: ps.isSensitive ?? false,
|
||||||
|
localOnly: ps.localOnly ?? false,
|
||||||
|
}, me);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,8 +5,9 @@
|
||||||
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
|
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
|
||||||
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
|
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
|
||||||
import type { DriveFilesRepository } from '@/models/_.js';
|
import type { DriveFilesRepository, EmojiDraftsRepository } from '@/models/_.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { ApiError } from '../../../error.js';
|
import { ApiError } from '../../../error.js';
|
||||||
|
|
||||||
|
@ -57,7 +58,7 @@ export const paramDef = {
|
||||||
} },
|
} },
|
||||||
draft: { type: 'boolean' },
|
draft: { type: 'boolean' },
|
||||||
},
|
},
|
||||||
required: ['id', 'name', 'aliases', 'draft'],
|
required: ['id', 'name', 'aliases'],
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
@ -67,10 +68,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
private driveFilesRepository: DriveFilesRepository,
|
private driveFilesRepository: DriveFilesRepository,
|
||||||
|
|
||||||
private customEmojiService: CustomEmojiService,
|
private customEmojiService: CustomEmojiService,
|
||||||
|
private driveFileEntityService: DriveFileEntityService,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
let driveFile;
|
let driveFile;
|
||||||
|
const isDraft = !!ps.draft;
|
||||||
if (ps.fileId) {
|
if (ps.fileId) {
|
||||||
driveFile = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
|
driveFile = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
|
||||||
if (driveFile == null) throw new ApiError(meta.errors.noSuchFile);
|
if (driveFile == null) throw new ApiError(meta.errors.noSuchFile);
|
||||||
|
@ -85,18 +87,31 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
throw new ApiError(meta.errors.noSuchEmoji);
|
throw new ApiError(meta.errors.noSuchEmoji);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.customEmojiService.update(ps.id, {
|
if (!isDraft) {
|
||||||
driveFile,
|
await this.customEmojiService.update(ps.id, {
|
||||||
name: ps.name,
|
driveFile,
|
||||||
category: ps.category ?? null,
|
name: ps.name,
|
||||||
aliases: ps.aliases,
|
category: ps.category ?? null,
|
||||||
license: ps.license ?? null,
|
aliases: ps.aliases,
|
||||||
isSensitive: ps.isSensitive,
|
license: ps.license ?? null,
|
||||||
localOnly: ps.localOnly,
|
isSensitive: ps.isSensitive,
|
||||||
roleIdsThatCanBeUsedThisEmojiAsReaction: ps.roleIdsThatCanBeUsedThisEmojiAsReaction,
|
localOnly: ps.localOnly,
|
||||||
fileId: ps.fileId ?? null,
|
roleIdsThatCanBeUsedThisEmojiAsReaction: ps.roleIdsThatCanBeUsedThisEmojiAsReaction,
|
||||||
draft: ps.draft,
|
}, me);
|
||||||
}, me);
|
} else {
|
||||||
|
const file = await this.driveFileEntityService.getFromUrl(emoji.originalUrl);
|
||||||
|
if (file === null) throw new ApiError(meta.errors.noSuchFile);
|
||||||
|
await this.customEmojiService.draft({
|
||||||
|
driveFile: file,
|
||||||
|
name: ps.name,
|
||||||
|
category: ps.category ?? null,
|
||||||
|
aliases: ps.aliases ?? [],
|
||||||
|
license: ps.license ?? null,
|
||||||
|
isSensitive: ps.isSensitive ?? false,
|
||||||
|
localOnly: ps.localOnly ?? false,
|
||||||
|
}, me);
|
||||||
|
await this.customEmojiService.delete(ps.id);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
65
packages/backend/src/server/api/endpoints/emoji-drafts.ts
Normal file
65
packages/backend/src/server/api/endpoints/emoji-drafts.ts
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { IsNull } from 'typeorm';
|
||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import type { EmojiDraftsRepository } from '@/models/_.js';
|
||||||
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
|
import { EmojiDraftsEntityService } from '@/core/entities/EmojiDraftsEntityService.js';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
tags: ['meta'],
|
||||||
|
|
||||||
|
requireCredential: false,
|
||||||
|
allowGet: true,
|
||||||
|
cacheSec: 3600,
|
||||||
|
|
||||||
|
res: {
|
||||||
|
type: 'object',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
properties: {
|
||||||
|
emojis: {
|
||||||
|
type: 'array',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
ref: 'EmojiDraftDetailed',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const paramDef = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
},
|
||||||
|
required: [],
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
|
constructor(
|
||||||
|
@Inject(DI.emojiDraftsRepository)
|
||||||
|
private emojiDraftsRepository: EmojiDraftsRepository,
|
||||||
|
|
||||||
|
private emojiDraftsEntityService: EmojiDraftsEntityService,
|
||||||
|
) {
|
||||||
|
super(meta, paramDef, async () => {
|
||||||
|
const emojis = await this.emojiDraftsRepository.find({
|
||||||
|
order: {
|
||||||
|
category: 'ASC',
|
||||||
|
name: 'ASC',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
emojis: await this.emojiDraftsEntityService.packDetailedMany(emojis),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -33,6 +33,7 @@ export const moderationLogTypes = [
|
||||||
'unsuspend',
|
'unsuspend',
|
||||||
'updateUserNote',
|
'updateUserNote',
|
||||||
'addCustomEmoji',
|
'addCustomEmoji',
|
||||||
|
'requestCustomEmoji',
|
||||||
'updateCustomEmoji',
|
'updateCustomEmoji',
|
||||||
'deleteCustomEmoji',
|
'deleteCustomEmoji',
|
||||||
'assignRole',
|
'assignRole',
|
||||||
|
@ -88,6 +89,10 @@ export type ModerationLogPayloads = {
|
||||||
emojiId: string;
|
emojiId: string;
|
||||||
emoji: any;
|
emoji: any;
|
||||||
};
|
};
|
||||||
|
requestCustomEmoji: {
|
||||||
|
emojiId: string;
|
||||||
|
emoji: any;
|
||||||
|
};
|
||||||
updateCustomEmoji: {
|
updateCustomEmoji: {
|
||||||
emojiId: string;
|
emojiId: string;
|
||||||
before: any;
|
before: any;
|
||||||
|
|
|
@ -45,18 +45,19 @@ const emojisDraftPaginationComponent = shallowRef<InstanceType<typeof MkPaginati
|
||||||
const query = ref(null);
|
const query = ref(null);
|
||||||
|
|
||||||
const paginationDraft = {
|
const paginationDraft = {
|
||||||
endpoint: 'admin/emoji/list' as const,
|
endpoint: 'emoji-drafts' as const,
|
||||||
limit: 30,
|
limit: 30,
|
||||||
params: computed(() => ({
|
params: computed(() => ({
|
||||||
query: (query.value && query.value !== '') ? query.value : null,
|
query: (query.value && query.value !== '') ? query.value : null,
|
||||||
draft: true,
|
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
|
|
||||||
const editDraft = (emoji) => {
|
const editDraft = (emoji) => {
|
||||||
|
emoji.isDraft = true;
|
||||||
os.popup(defineAsyncComponent(() => import('@/components/MkEmojiEditDialog.vue')), {
|
os.popup(defineAsyncComponent(() => import('@/components/MkEmojiEditDialog.vue')), {
|
||||||
emoji: emoji,
|
emoji: emoji,
|
||||||
isRequest: false,
|
isRequest: false,
|
||||||
|
isDraftEdit: true,
|
||||||
}, {
|
}, {
|
||||||
done: result => {
|
done: result => {
|
||||||
if (result.updated) {
|
if (result.updated) {
|
||||||
|
@ -80,16 +81,16 @@ async function undrafted(emoji) {
|
||||||
});
|
});
|
||||||
if (canceled) return;
|
if (canceled) return;
|
||||||
|
|
||||||
await os.api('admin/emoji/update', {
|
await os.api('admin/emoji/draft-update', {
|
||||||
id: emoji.id,
|
id: emoji.id,
|
||||||
|
fileId: emoji.fileId,
|
||||||
name: emoji.name,
|
name: emoji.name,
|
||||||
category: emoji.category,
|
category: emoji.category,
|
||||||
aliases: emoji.aliases,
|
aliases: emoji.aliases,
|
||||||
license: emoji.license,
|
license: emoji.license,
|
||||||
draft: false,
|
|
||||||
isSensitive: emoji.isSensitive,
|
isSensitive: emoji.isSensitive,
|
||||||
localOnly: emoji.localOnly,
|
localOnly: emoji.localOnly,
|
||||||
roleIdsThatCanBeUsedThisEmojiAsReaction: emoji.roleIdsThatCanBeUsedThisEmojiAsReaction,
|
isDraft: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
emojisDraftPaginationComponent.value.removeItem((item) => item.id === emoji.id);
|
emojisDraftPaginationComponent.value.removeItem((item) => item.id === emoji.id);
|
||||||
|
|
|
@ -46,7 +46,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkInput v-model="license">
|
<MkInput v-model="license">
|
||||||
<template #label>{{ i18n.ts.license }}</template>
|
<template #label>{{ i18n.ts.license }}</template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
<MkFolder v-if="!isRequest">
|
<MkFolder v-if="!isRequest && !isDraftEdit">
|
||||||
<template #label>{{ i18n.ts.rolesThatCanBeUsedThisEmojiAsReaction }}</template>
|
<template #label>{{ i18n.ts.rolesThatCanBeUsedThisEmojiAsReaction }}</template>
|
||||||
<template #suffix>{{ rolesThatCanBeUsedThisEmojiAsReaction.length === 0 ? i18n.ts.all : rolesThatCanBeUsedThisEmojiAsReaction.length }}</template>
|
<template #suffix>{{ rolesThatCanBeUsedThisEmojiAsReaction.length === 0 ? i18n.ts.all : rolesThatCanBeUsedThisEmojiAsReaction.length }}</template>
|
||||||
|
|
||||||
|
@ -65,7 +65,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
<MkSwitch v-model="isSensitive">isSensitive</MkSwitch>
|
<MkSwitch v-model="isSensitive">isSensitive</MkSwitch>
|
||||||
<MkSwitch v-model="localOnly">{{ i18n.ts.localOnly }}</MkSwitch>
|
<MkSwitch v-model="localOnly">{{ i18n.ts.localOnly }}</MkSwitch>
|
||||||
<MkSwitch v-if="!isRequest" v-model="draft" :disabled="isRequest">
|
<MkSwitch v-if="!isRequest" v-model="isDraft" :disabled="isRequest">
|
||||||
{{ i18n.ts.draft }}
|
{{ i18n.ts.draft }}
|
||||||
</MkSwitch>
|
</MkSwitch>
|
||||||
</div>
|
</div>
|
||||||
|
@ -100,6 +100,7 @@ import MkRolePreview from '@/components/MkRolePreview.vue';
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
emoji?: any,
|
emoji?: any,
|
||||||
isRequest: boolean,
|
isRequest: boolean,
|
||||||
|
isDraftEdit?: boolean,
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
let dialog = $ref(null);
|
let dialog = $ref(null);
|
||||||
|
@ -109,18 +110,18 @@ let aliases: string = $ref(props.emoji ? props.emoji.aliases.join(' ') : '');
|
||||||
let license: string = $ref(props.emoji ? (props.emoji.license ?? '') : '');
|
let license: string = $ref(props.emoji ? (props.emoji.license ?? '') : '');
|
||||||
let isSensitive = $ref(props.emoji ? props.emoji.isSensitive : false);
|
let isSensitive = $ref(props.emoji ? props.emoji.isSensitive : false);
|
||||||
let localOnly = $ref(props.emoji ? props.emoji.localOnly : false);
|
let localOnly = $ref(props.emoji ? props.emoji.localOnly : false);
|
||||||
let roleIdsThatCanBeUsedThisEmojiAsReaction = $ref(props.emoji ? props.emoji.roleIdsThatCanBeUsedThisEmojiAsReaction : []);
|
let roleIdsThatCanBeUsedThisEmojiAsReaction = $ref((props.emoji && props.emoji.roleIdsThatCanBeUsedThisEmojiAsReaction) ? props.emoji.roleIdsThatCanBeUsedThisEmojiAsReaction : []);
|
||||||
let rolesThatCanBeUsedThisEmojiAsReaction = $ref([]);
|
let rolesThatCanBeUsedThisEmojiAsReaction = $ref([]);
|
||||||
let file = $ref<Misskey.entities.DriveFile>();
|
let file = $ref<Misskey.entities.DriveFile>();
|
||||||
let chooseFile: DriveFile|null = $ref(null);
|
let chooseFile: DriveFile|null = $ref(null);
|
||||||
let draft = $ref(props.emoji ? props.emoji.draft : false);
|
|
||||||
let isRequest = $ref(props.isRequest);
|
let isRequest = $ref(props.isRequest);
|
||||||
|
let isDraftEdit = $ref(props.isDraftEdit ?? false);
|
||||||
|
let isDraft = $ref(!!(isDraftEdit || props.isRequest));
|
||||||
watch($$(roleIdsThatCanBeUsedThisEmojiAsReaction), async () => {
|
watch($$(roleIdsThatCanBeUsedThisEmojiAsReaction), async () => {
|
||||||
rolesThatCanBeUsedThisEmojiAsReaction = (await Promise.all(roleIdsThatCanBeUsedThisEmojiAsReaction.map((id) => os.api('admin/roles/show', { roleId: id }).catch(() => null)))).filter(x => x != null);
|
rolesThatCanBeUsedThisEmojiAsReaction = (await Promise.all(roleIdsThatCanBeUsedThisEmojiAsReaction.map((id) => os.api('admin/roles/show', { roleId: id }).catch(() => null)))).filter(x => x != null);
|
||||||
}, { immediate: true });
|
}, { immediate: true });
|
||||||
|
|
||||||
const imgUrl = computed(() => file ? file.url : props.emoji ? `/emoji/${props.emoji.name}.webp` : null);
|
const imgUrl = computed(() => file ? file.url : props.emoji && !isDraftEdit ? `/emoji/${props.emoji.name}.webp` : props.emoji && props.emoji.url ? props.emoji.url : null);
|
||||||
const validation = computed(() => {
|
const validation = computed(() => {
|
||||||
return name.match(/^[a-zA-Z0-9_]+$/) && imgUrl.value != null;
|
return name.match(/^[a-zA-Z0-9_]+$/) && imgUrl.value != null;
|
||||||
});
|
});
|
||||||
|
@ -129,28 +130,6 @@ const emit = defineEmits<{
|
||||||
(ev: 'closed'): void
|
(ev: 'closed'): void
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
async function add() {
|
|
||||||
const ret = await os.api('admin/emoji/add-draft', {
|
|
||||||
name: name,
|
|
||||||
category: category,
|
|
||||||
aliases: aliases.split(' '),
|
|
||||||
license: license === '' ? null : license,
|
|
||||||
fileId: chooseFile.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
emit('done', {
|
|
||||||
updated: {
|
|
||||||
id: ret.id,
|
|
||||||
name,
|
|
||||||
category,
|
|
||||||
aliases: aliases.split(' '),
|
|
||||||
license: license === '' ? null : license,
|
|
||||||
draft: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
dialog.close();
|
|
||||||
}
|
|
||||||
async function changeImage(ev) {
|
async function changeImage(ev) {
|
||||||
file = await selectFile(ev.currentTarget ?? ev.target, null);
|
file = await selectFile(ev.currentTarget ?? ev.target, null);
|
||||||
const candidate = file.name.replace(/\.(.+)$/, '');
|
const candidate = file.name.replace(/\.(.+)$/, '');
|
||||||
|
@ -174,37 +153,13 @@ async function addRole() {
|
||||||
async function removeRole(role, ev) {
|
async function removeRole(role, ev) {
|
||||||
rolesThatCanBeUsedThisEmojiAsReaction = rolesThatCanBeUsedThisEmojiAsReaction.filter(x => x.id !== role.id);
|
rolesThatCanBeUsedThisEmojiAsReaction = rolesThatCanBeUsedThisEmojiAsReaction.filter(x => x.id !== role.id);
|
||||||
}
|
}
|
||||||
async function update() {
|
|
||||||
await os.apiWithDialog('admin/emoji/update', {
|
|
||||||
id: props.emoji.id,
|
|
||||||
name,
|
|
||||||
category,
|
|
||||||
aliases: aliases.split(' '),
|
|
||||||
license: license === '' ? null : license,
|
|
||||||
fileId: chooseFile?.id,
|
|
||||||
draft: draft,
|
|
||||||
});
|
|
||||||
|
|
||||||
emit('done', {
|
|
||||||
updated: {
|
|
||||||
id: props.emoji.id,
|
|
||||||
name,
|
|
||||||
category,
|
|
||||||
aliases: aliases.split(' '),
|
|
||||||
license: license === '' ? null : license,
|
|
||||||
draft: draft,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
dialog.close();
|
|
||||||
}
|
|
||||||
async function done() {
|
async function done() {
|
||||||
const params = {
|
const params = {
|
||||||
name,
|
name,
|
||||||
category: category === '' ? null : category,
|
category: category === '' ? null : category,
|
||||||
aliases: aliases.replace(' ', ' ').split(' ').filter(x => x !== ''),
|
aliases: aliases.replace(' ', ' ').split(' ').filter(x => x !== ''),
|
||||||
license: license === '' ? null : license,
|
license: license === '' ? null : license,
|
||||||
draft: draft,
|
draft: isDraft,
|
||||||
isSensitive,
|
isSensitive,
|
||||||
localOnly,
|
localOnly,
|
||||||
roleIdsThatCanBeUsedThisEmojiAsReaction: rolesThatCanBeUsedThisEmojiAsReaction.map(x => x.id),
|
roleIdsThatCanBeUsedThisEmojiAsReaction: rolesThatCanBeUsedThisEmojiAsReaction.map(x => x.id),
|
||||||
|
@ -213,12 +168,19 @@ async function done() {
|
||||||
if (file) {
|
if (file) {
|
||||||
params.fileId = file.id;
|
params.fileId = file.id;
|
||||||
}
|
}
|
||||||
console.log(props.emoji);
|
|
||||||
if (props.emoji) {
|
if (props.emoji) {
|
||||||
await os.apiWithDialog('admin/emoji/update', {
|
if (isDraftEdit) {
|
||||||
id: props.emoji.id,
|
await os.apiWithDialog('admin/emoji/draft-update', {
|
||||||
...params,
|
id: props.emoji.id,
|
||||||
});
|
...params,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await os.apiWithDialog('admin/emoji/update', {
|
||||||
|
id: props.emoji.id,
|
||||||
|
...params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
emit('done', {
|
emit('done', {
|
||||||
updated: {
|
updated: {
|
||||||
|
|
|
@ -201,11 +201,13 @@ async function init(): Promise<void> {
|
||||||
...params,
|
...params,
|
||||||
limit: props.pagination.limit ?? 10,
|
limit: props.pagination.limit ?? 10,
|
||||||
}).then(res => {
|
}).then(res => {
|
||||||
|
if (props.pagination.endpoint === 'emoji-drafts') {
|
||||||
|
res = res.emojis;
|
||||||
|
}
|
||||||
for (let i = 0; i < res.length; i++) {
|
for (let i = 0; i < res.length; i++) {
|
||||||
const item = res[i];
|
const item = res[i];
|
||||||
if (i === 3) item._shouldInsertAd_ = true;
|
if (i === 3) item._shouldInsertAd_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (res.length === 0 || props.pagination.noPaging) {
|
if (res.length === 0 || props.pagination.noPaging) {
|
||||||
concatItems(res);
|
concatItems(res);
|
||||||
more.value = false;
|
more.value = false;
|
||||||
|
@ -214,7 +216,6 @@ async function init(): Promise<void> {
|
||||||
concatItems(res);
|
concatItems(res);
|
||||||
more.value = true;
|
more.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
offset.value = res.length;
|
offset.value = res.length;
|
||||||
error.value = false;
|
error.value = false;
|
||||||
fetching.value = false;
|
fetching.value = false;
|
||||||
|
@ -241,6 +242,9 @@ const fetchMore = async (): Promise<void> => {
|
||||||
untilId: Array.from(items.value.keys()).at(-1),
|
untilId: Array.from(items.value.keys()).at(-1),
|
||||||
}),
|
}),
|
||||||
}).then(res => {
|
}).then(res => {
|
||||||
|
if (props.pagination.endpoint === 'emoji-drafts') {
|
||||||
|
res = res.emojis;
|
||||||
|
}
|
||||||
for (let i = 0; i < res.length; i++) {
|
for (let i = 0; i < res.length; i++) {
|
||||||
const item = res[i];
|
const item = res[i];
|
||||||
if (i === 10) item._shouldInsertAd_ = true;
|
if (i === 10) item._shouldInsertAd_ = true;
|
||||||
|
@ -305,6 +309,9 @@ const fetchMoreAhead = async (): Promise<void> => {
|
||||||
sinceId: Array.from(items.value.keys()).at(-1),
|
sinceId: Array.from(items.value.keys()).at(-1),
|
||||||
}),
|
}),
|
||||||
}).then(res => {
|
}).then(res => {
|
||||||
|
if (props.pagination.endpoint === 'emoji-drafts') {
|
||||||
|
res = res.emojis;
|
||||||
|
}
|
||||||
if (res.length === 0) {
|
if (res.length === 0) {
|
||||||
items.value = concatMapWithArray(items.value, res);
|
items.value = concatMapWithArray(items.value, res);
|
||||||
more.value = false;
|
more.value = false;
|
||||||
|
|
|
@ -32,13 +32,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkFoldableSection v-for="category in customEmojiCategories" v-once :key="category">
|
<MkFoldableSection v-for="category in customEmojiCategories" v-once :key="category">
|
||||||
<template #header>{{ category || i18n.ts.other }}</template>
|
<template #header>{{ category || i18n.ts.other }}</template>
|
||||||
<div :class="$style.emojis">
|
<div :class="$style.emojis">
|
||||||
<XEmoji v-for="emoji in customEmojis.filter(e => e.category === category && !e.draft)" :key="emoji.name" :emoji="emoji" :draft="emoji.draft"/>
|
<XEmoji v-for="emoji in customEmojis.filter(e => e.category === category)" :key="emoji.name" :emoji="emoji"/>
|
||||||
</div>
|
</div>
|
||||||
</MkFoldableSection>
|
</MkFoldableSection>
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
<MkSpacer v-if="tab === 'draft'" :contentMax="1000" :marginMin="20">
|
<MkSpacer v-if="tab === 'draft'" :contentMax="1000" :marginMin="20">
|
||||||
<div :class="$style.emojis">
|
<div :class="$style.emojis">
|
||||||
<XEmoji v-for="emoji in draftEmojis" :key="emoji.name" :emoji="emoji" :draft="emoji.draft"/>
|
<XEmoji v-for="emoji in draftEmojis.emojis" :key="emoji.name" :emoji="emoji" :draft="true"/>
|
||||||
</div>
|
</div>
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
</MkStickyContainer>
|
</MkStickyContainer>
|
||||||
|
@ -73,8 +73,7 @@ definePageMetadata(ref({}));
|
||||||
let q = $ref('');
|
let q = $ref('');
|
||||||
let searchEmojis = $ref<Misskey.entities.CustomEmoji[]>(null);
|
let searchEmojis = $ref<Misskey.entities.CustomEmoji[]>(null);
|
||||||
let selectedTags = $ref(new Set());
|
let selectedTags = $ref(new Set());
|
||||||
const draftEmojis = customEmojis.value.filter(emoji => emoji.draft);
|
const draftEmojis = await os.apiGet('emoji-drafts');
|
||||||
|
|
||||||
function search() {
|
function search() {
|
||||||
if ((q === '' || q == null) && selectedTags.size === 0) {
|
if ((q === '' || q == null) && selectedTags.size === 0) {
|
||||||
searchEmojis = null;
|
searchEmojis = null;
|
||||||
|
|
|
@ -4,8 +4,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<button v-if="emoji.draft" class="zuvgdzyu _button emoji-draft" @click="menu">
|
<button v-if="draft" class="_button emoji-draft" :class="$style.root" @click="menu">
|
||||||
<img :src="emoji.url" class="img" loading="lazy"/>
|
<img :src="emoji.url" :class="$style.img" loading="lazy"/>
|
||||||
<div class="body">
|
<div class="body">
|
||||||
<div class="name _monospace">{{ emoji.name + ' (draft)' }}</div>
|
<div class="name _monospace">{{ emoji.name + ' (draft)' }}</div>
|
||||||
<div class="info">{{ emoji.aliases.join(' ') }}</div>
|
<div class="info">{{ emoji.aliases.join(' ') }}</div>
|
||||||
|
@ -14,8 +14,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<button v-else class="_button" :class="$style.root" @click="menu">
|
<button v-else class="_button" :class="$style.root" @click="menu">
|
||||||
<img :src="emoji.url" :class="$style.img" loading="lazy"/>
|
<img :src="emoji.url" :class="$style.img" loading="lazy"/>
|
||||||
<div :class="$style.body">
|
<div :class="$style.body">
|
||||||
<div :class="$style.name" class="_monospace">{{ emoji.name }}</div>
|
<div class="name _monospace">{{ emoji.name }}</div>
|
||||||
<div :class="$style.info">{{ emoji.aliases.join(' ') }}</div>
|
<div class="info">{{ emoji.aliases.join(' ') }}</div>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
@ -26,13 +26,13 @@ import copyToClipboard from '@/scripts/copy-to-clipboard.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
emoji: {
|
emoji: {
|
||||||
name: string;
|
name: string;
|
||||||
aliases: string[];
|
aliases: string[];
|
||||||
category: string;
|
category: string;
|
||||||
url: string;
|
url: string;
|
||||||
draft: boolean;
|
};
|
||||||
};
|
draft?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
function menu(ev) {
|
function menu(ev) {
|
||||||
|
@ -50,7 +50,7 @@ function menu(ev) {
|
||||||
text: i18n.ts.info,
|
text: i18n.ts.info,
|
||||||
icon: 'ti ti-info-circle',
|
icon: 'ti ti-info-circle',
|
||||||
action: () => {
|
action: () => {
|
||||||
os.apiGet('emoji', { name: props.emoji.name }).then(res => {
|
os.apiGet('emoji-drafts', { name: props.emoji.name }).then(res => {
|
||||||
os.alert({
|
os.alert({
|
||||||
type: 'info',
|
type: 'info',
|
||||||
text: `License: ${res.license}`,
|
text: `License: ${res.license}`,
|
||||||
|
@ -63,45 +63,45 @@ function menu(ev) {
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
.root {
|
.root {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
background: var(--panel);
|
background: var(--panel);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
border-color: var(--accent);
|
border-color: var(--accent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.img {
|
.img {
|
||||||
width: 42px;
|
width: 42px;
|
||||||
height: 42px;
|
height: 42px;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
.body {
|
.body {
|
||||||
padding: 0 0 0 8px;
|
padding: 0 0 0 8px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.name {
|
.name {
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info {
|
.info {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.emoji-draft {
|
.emoji-draft {
|
||||||
--c: rgb(255 196 0 / 15%);;
|
--c: rgb(255 196 0 / 15%);;
|
||||||
background-image: linear-gradient(45deg,var(--c) 16.67%,transparent 16.67%,transparent 50%,var(--c) 50%,var(--c) 66.67%,transparent 66.67%,transparent 100%);
|
background-image: linear-gradient(45deg,var(--c) 16.67%,transparent 16.67%,transparent 50%,var(--c) 50%,var(--c) 66.67%,transparent 66.67%,transparent 100%);
|
||||||
background-size: 16px 16px;
|
background-size: 16px 16px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
Loading…
Reference in a new issue