refactor: 絵文字URLを引き回すのをやめる (#9423)

This commit is contained in:
syuilo 2022-12-29 10:14:44 +09:00 committed by GitHub
parent 510e6ec7e9
commit 912791b3ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 79 additions and 58 deletions

View File

@ -6,8 +6,8 @@ import type { Packed } from '@/misc/schema.js';
import type { } from '@/models/entities/Blocking.js'; import type { } from '@/models/entities/Blocking.js';
import type { User } from '@/models/entities/User.js'; import type { User } from '@/models/entities/User.js';
import type { Emoji } from '@/models/entities/Emoji.js'; import type { Emoji } from '@/models/entities/Emoji.js';
import { UserEntityService } from './UserEntityService.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { UserEntityService } from './UserEntityService.js';
@Injectable() @Injectable()
export class EmojiEntityService { export class EmojiEntityService {
@ -22,6 +22,7 @@ export class EmojiEntityService {
@bindThis @bindThis
public async pack( public async pack(
src: Emoji['id'] | Emoji, src: Emoji['id'] | Emoji,
opts: { omitUrl?: boolean } = {},
): Promise<Packed<'Emoji'>> { ): Promise<Packed<'Emoji'>> {
const emoji = typeof src === 'object' ? src : await this.emojisRepository.findOneByOrFail({ id: src }); const emoji = typeof src === 'object' ? src : await this.emojisRepository.findOneByOrFail({ id: src });
@ -32,15 +33,16 @@ export class EmojiEntityService {
category: emoji.category, category: emoji.category,
host: emoji.host, host: emoji.host,
// ?? emoji.originalUrl してるのは後方互換性のため // ?? emoji.originalUrl してるのは後方互換性のため
url: emoji.publicUrl ?? emoji.originalUrl, url: opts.omitUrl ? undefined : (emoji.publicUrl ?? emoji.originalUrl),
}; };
} }
@bindThis @bindThis
public packMany( public packMany(
emojis: any[], emojis: any[],
opts: { omitUrl?: boolean } = {},
) { ) {
return Promise.all(emojis.map(x => this.pack(x))); return Promise.all(emojis.map(x => this.pack(x, opts)));
} }
} }

View File

@ -31,7 +31,7 @@ export const packedEmojiSchema = {
}, },
url: { url: {
type: 'string', type: 'string',
optional: false, nullable: false, optional: true, nullable: false,
}, },
}, },
} as const; } as const;

View File

@ -309,6 +309,7 @@ export const paramDef = {
type: 'object', type: 'object',
properties: { properties: {
detail: { type: 'boolean', default: true }, detail: { type: 'boolean', default: true },
omitEmojiUrl: { type: 'boolean', default: false },
}, },
required: [], required: [],
} as const; } as const;
@ -390,7 +391,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
backgroundImageUrl: instance.backgroundImageUrl, backgroundImageUrl: instance.backgroundImageUrl,
logoImageUrl: instance.logoImageUrl, logoImageUrl: instance.logoImageUrl,
maxNoteTextLength: MAX_NOTE_TEXT_LENGTH, // 後方互換性のため maxNoteTextLength: MAX_NOTE_TEXT_LENGTH, // 後方互換性のため
emojis: await this.emojiEntityService.packMany(emojis), emojis: await this.emojiEntityService.packMany(emojis, { omitUrl: ps.omitEmojiUrl }),
defaultLightTheme: instance.defaultLightTheme, defaultLightTheme: instance.defaultLightTheme,
defaultDarkTheme: instance.defaultDarkTheme, defaultDarkTheme: instance.defaultDarkTheme,
ads: ads.map(ad => ({ ads: ads.map(ad => ({

View File

@ -26,7 +26,7 @@ import { PageEntityService } from '@/core/entities/PageEntityService.js';
import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js'; import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js';
import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; import { ClipEntityService } from '@/core/entities/ClipEntityService.js';
import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js'; import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js';
import type { ChannelsRepository, ClipsRepository, GalleryPostsRepository, NotesRepository, PagesRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; import type { ChannelsRepository, ClipsRepository, EmojisRepository, GalleryPostsRepository, NotesRepository, PagesRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js';
import { deepClone } from '@/misc/clone.js'; import { deepClone } from '@/misc/clone.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import manifest from './manifest.json' assert { type: 'json' }; import manifest from './manifest.json' assert { type: 'json' };
@ -70,6 +70,9 @@ export class ClientServerService {
@Inject(DI.pagesRepository) @Inject(DI.pagesRepository)
private pagesRepository: PagesRepository, private pagesRepository: PagesRepository,
@Inject(DI.emojisRepository)
private emojisRepository: EmojisRepository,
private userEntityService: UserEntityService, private userEntityService: UserEntityService,
private noteEntityService: NoteEntityService, private noteEntityService: NoteEntityService,
private pageEntityService: PageEntityService, private pageEntityService: PageEntityService,
@ -217,6 +220,33 @@ export class ClientServerService {
return reply.sendFile('/apple-touch-icon.png', staticAssets); return reply.sendFile('/apple-touch-icon.png', staticAssets);
}); });
fastify.get<{ Params: { path: string } }>('/emoji/:path(.*)', async (request, reply) => {
const path = request.params.path;
if (!path.match(/^[a-zA-Z0-9\-_@\.]+?\.webp$/)) {
reply.code(404);
return;
}
const name = path.split('@')[0].replace('.webp', '');
const host = path.split('@')[1]?.replace('.webp', '');
const emoji = await this.emojisRepository.findOneBy({
host: host == null ? IsNull() : host,
name: name,
});
if (emoji == null) {
reply.code(404);
return;
}
reply.header('Content-Security-Policy', 'default-src \'none\'; style-src \'unsafe-inline\'');
// ?? emoji.originalUrl してるのは後方互換性のため
return await reply.redirect(301, emoji.publicUrl ?? emoji.originalUrl);
});
fastify.get<{ Params: { path: string } }>('/fluent-emoji/:path(.*)', async (request, reply) => { fastify.get<{ Params: { path: string } }>('/fluent-emoji/:path(.*)', async (request, reply) => {
const path = request.params.path; const path = request.params.path;

View File

@ -37,20 +37,20 @@
<MkInstanceTicker v-if="showTicker" class="ticker" :instance="appearNote.user.instance"/> <MkInstanceTicker v-if="showTicker" class="ticker" :instance="appearNote.user.instance"/>
<div class="body"> <div class="body">
<p v-if="appearNote.cw != null" class="cw"> <p v-if="appearNote.cw != null" class="cw">
<Mfm v-if="appearNote.cw != ''" class="text" :text="appearNote.cw" :author="appearNote.user" :i="$i" :custom-emojis="appearNote.emojis"/> <Mfm v-if="appearNote.cw != ''" class="text" :text="appearNote.cw" :author="appearNote.user" :i="$i"/>
<XCwButton v-model="showContent" :note="appearNote"/> <XCwButton v-model="showContent" :note="appearNote"/>
</p> </p>
<div v-show="appearNote.cw == null || showContent" class="content" :class="{ collapsed, isLong }"> <div v-show="appearNote.cw == null || showContent" class="content" :class="{ collapsed, isLong }">
<div class="text"> <div class="text">
<span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span> <span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span>
<MkA v-if="appearNote.replyId" class="reply" :to="`/notes/${appearNote.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA> <MkA v-if="appearNote.replyId" class="reply" :to="`/notes/${appearNote.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA>
<Mfm v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :i="$i" :custom-emojis="appearNote.emojis"/> <Mfm v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :i="$i"/>
<a v-if="appearNote.renote != null" class="rp">RN:</a> <a v-if="appearNote.renote != null" class="rp">RN:</a>
<div v-if="translating || translation" class="translation"> <div v-if="translating || translation" class="translation">
<MkLoading v-if="translating" mini/> <MkLoading v-if="translating" mini/>
<div v-else class="translated"> <div v-else class="translated">
<b>{{ $t('translatedFrom', { x: translation.sourceLang }) }}: </b> <b>{{ $t('translatedFrom', { x: translation.sourceLang }) }}: </b>
<Mfm :text="translation.text" :author="appearNote.user" :i="$i" :custom-emojis="appearNote.emojis"/> <Mfm :text="translation.text" :author="appearNote.user" :i="$i"/>
</div> </div>
</div> </div>
</div> </div>

View File

@ -48,20 +48,20 @@
<div class="main"> <div class="main">
<div class="body"> <div class="body">
<p v-if="appearNote.cw != null" class="cw"> <p v-if="appearNote.cw != null" class="cw">
<Mfm v-if="appearNote.cw != ''" class="text" :text="appearNote.cw" :author="appearNote.user" :i="$i" :custom-emojis="appearNote.emojis"/> <Mfm v-if="appearNote.cw != ''" class="text" :text="appearNote.cw" :author="appearNote.user" :i="$i"/>
<XCwButton v-model="showContent" :note="appearNote"/> <XCwButton v-model="showContent" :note="appearNote"/>
</p> </p>
<div v-show="appearNote.cw == null || showContent" class="content"> <div v-show="appearNote.cw == null || showContent" class="content">
<div class="text"> <div class="text">
<span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span> <span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span>
<MkA v-if="appearNote.replyId" class="reply" :to="`/notes/${appearNote.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA> <MkA v-if="appearNote.replyId" class="reply" :to="`/notes/${appearNote.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA>
<Mfm v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :i="$i" :custom-emojis="appearNote.emojis"/> <Mfm v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :i="$i"/>
<a v-if="appearNote.renote != null" class="rp">RN:</a> <a v-if="appearNote.renote != null" class="rp">RN:</a>
<div v-if="translating || translation" class="translation"> <div v-if="translating || translation" class="translation">
<MkLoading v-if="translating" mini/> <MkLoading v-if="translating" mini/>
<div v-else class="translated"> <div v-else class="translated">
<b>{{ $t('translatedFrom', { x: translation.sourceLang }) }}: </b> <b>{{ $t('translatedFrom', { x: translation.sourceLang }) }}: </b>
<Mfm :text="translation.text" :author="appearNote.user" :i="$i" :custom-emojis="appearNote.emojis"/> <Mfm :text="translation.text" :author="appearNote.user" :i="$i"/>
</div> </div>
</div> </div>
</div> </div>

View File

@ -5,7 +5,7 @@
<XNoteHeader class="header" :note="note" :mini="true"/> <XNoteHeader class="header" :note="note" :mini="true"/>
<div class="body"> <div class="body">
<p v-if="note.cw != null" class="cw"> <p v-if="note.cw != null" class="cw">
<Mfm v-if="note.cw != ''" class="text" :text="note.cw" :author="note.user" :i="$i" :custom-emojis="note.emojis"/> <Mfm v-if="note.cw != ''" class="text" :text="note.cw" :author="note.user" :i="$i"/>
<XCwButton v-model="showContent" :note="note"/> <XCwButton v-model="showContent" :note="note"/>
</p> </p>
<div v-show="note.cw == null || showContent" class="content"> <div v-show="note.cw == null || showContent" class="content">

View File

@ -6,7 +6,7 @@
<XNoteHeader class="header" :note="note" :mini="true"/> <XNoteHeader class="header" :note="note" :mini="true"/>
<div class="body"> <div class="body">
<p v-if="note.cw != null" class="cw"> <p v-if="note.cw != null" class="cw">
<Mfm v-if="note.cw != ''" class="text" :text="note.cw" :author="note.user" :i="$i" :custom-emojis="note.emojis"/> <Mfm v-if="note.cw != ''" class="text" :text="note.cw" :author="note.user" :i="$i"/>
<XCwButton v-model="showContent" :note="note"/> <XCwButton v-model="showContent" :note="note"/>
</p> </p>
<div v-show="note.cw == null || showContent" class="content"> <div v-show="note.cw == null || showContent" class="content">

View File

@ -34,31 +34,31 @@
</header> </header>
<MkA v-if="notification.type === 'reaction'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)"> <MkA v-if="notification.type === 'reaction'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
<i class="ti ti-quote"></i> <i class="ti ti-quote"></i>
<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full" :custom-emojis="notification.note.emojis"/> <Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full"/>
<i class="ti ti-quote"></i> <i class="ti ti-quote"></i>
</MkA> </MkA>
<MkA v-if="notification.type === 'renote'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note.renote)"> <MkA v-if="notification.type === 'renote'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note.renote)">
<i class="ti ti-quote"></i> <i class="ti ti-quote"></i>
<Mfm :text="getNoteSummary(notification.note.renote)" :plain="true" :nowrap="!full" :custom-emojis="notification.note.renote.emojis"/> <Mfm :text="getNoteSummary(notification.note.renote)" :plain="true" :nowrap="!full"/>
<i class="ti ti-quote"></i> <i class="ti ti-quote"></i>
</MkA> </MkA>
<MkA v-if="notification.type === 'reply'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)"> <MkA v-if="notification.type === 'reply'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full" :custom-emojis="notification.note.emojis"/> <Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full"/>
</MkA> </MkA>
<MkA v-if="notification.type === 'mention'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)"> <MkA v-if="notification.type === 'mention'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full" :custom-emojis="notification.note.emojis"/> <Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full"/>
</MkA> </MkA>
<MkA v-if="notification.type === 'quote'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)"> <MkA v-if="notification.type === 'quote'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full" :custom-emojis="notification.note.emojis"/> <Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full"/>
</MkA> </MkA>
<MkA v-if="notification.type === 'pollVote'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)"> <MkA v-if="notification.type === 'pollVote'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
<i class="ti ti-quote"></i> <i class="ti ti-quote"></i>
<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full" :custom-emojis="notification.note.emojis"/> <Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full"/>
<i class="ti ti-quote"></i> <i class="ti ti-quote"></i>
</MkA> </MkA>
<MkA v-if="notification.type === 'pollEnded'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)"> <MkA v-if="notification.type === 'pollEnded'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
<i class="ti ti-quote"></i> <i class="ti ti-quote"></i>
<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full" :custom-emojis="notification.note.emojis"/> <Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full"/>
<i class="ti ti-quote"></i> <i class="ti ti-quote"></i>
</MkA> </MkA>
<span v-if="notification.type === 'follow'" class="text" style="opacity: 0.6;">{{ i18n.ts.youGotNewFollower }}<div v-if="full"><MkFollowButton :user="notification.user" :full="true"/></div></span> <span v-if="notification.type === 'follow'" class="text" style="opacity: 0.6;">{{ i18n.ts.youGotNewFollower }}<div v-if="full"><MkFollowButton :user="notification.user" :full="true"/></div></span>

View File

@ -5,7 +5,7 @@
<div class="backdrop" :style="{ 'width': `${showResult ? (choice.votes / total * 100) : 0}%` }"></div> <div class="backdrop" :style="{ 'width': `${showResult ? (choice.votes / total * 100) : 0}%` }"></div>
<span> <span>
<template v-if="choice.isVoted"><i class="ti ti-check"></i></template> <template v-if="choice.isVoted"><i class="ti ti-check"></i></template>
<Mfm :text="choice.text" :plain="true" :custom-emojis="note.emojis"/> <Mfm :text="choice.text" :plain="true"/>
<span v-if="showResult" class="votes">({{ $t('_poll.votesCount', { n: choice.votes }) }})</span> <span v-if="showResult" class="votes">({{ $t('_poll.votesCount', { n: choice.votes }) }})</span>
</span> </span>
</li> </li>

View File

@ -1,5 +1,5 @@
<template> <template>
<MkEmoji :emoji="reaction" :custom-emojis="customEmojis || []" :is-reaction="true" :normal="true" :no-style="noStyle"/> <MkEmoji :emoji="reaction" :is-reaction="true" :normal="true" :no-style="noStyle"/>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -7,7 +7,6 @@ import { } from 'vue';
const props = defineProps<{ const props = defineProps<{
reaction: string; reaction: string;
customEmojis?: any[]; // TODO
noStyle?: boolean; noStyle?: boolean;
}>(); }>();
</script> </script>

View File

@ -1,7 +1,7 @@
<template> <template>
<MkTooltip ref="tooltip" :showing="showing" :target-element="targetElement" :max-width="340" @closed="emit('closed')"> <MkTooltip ref="tooltip" :showing="showing" :target-element="targetElement" :max-width="340" @closed="emit('closed')">
<div class="beeadbfb"> <div class="beeadbfb">
<XReactionIcon :reaction="reaction" :custom-emojis="emojis" class="icon" :no-style="true"/> <XReactionIcon :reaction="reaction" class="icon" :no-style="true"/>
<div class="name">{{ reaction.replace('@.', '') }}</div> <div class="name">{{ reaction.replace('@.', '') }}</div>
</div> </div>
</MkTooltip> </MkTooltip>
@ -15,7 +15,6 @@ import XReactionIcon from '@/components/MkReactionIcon.vue';
defineProps<{ defineProps<{
showing: boolean; showing: boolean;
reaction: string; reaction: string;
emojis: any[]; // TODO
targetElement: HTMLElement; targetElement: HTMLElement;
}>(); }>();

View File

@ -2,7 +2,7 @@
<MkTooltip ref="tooltip" :showing="showing" :target-element="targetElement" :max-width="340" @closed="emit('closed')"> <MkTooltip ref="tooltip" :showing="showing" :target-element="targetElement" :max-width="340" @closed="emit('closed')">
<div class="bqxuuuey"> <div class="bqxuuuey">
<div class="reaction"> <div class="reaction">
<XReactionIcon :reaction="reaction" :custom-emojis="emojis" class="icon" :no-style="true"/> <XReactionIcon :reaction="reaction" class="icon" :no-style="true"/>
<div class="name">{{ getReactionName(reaction) }}</div> <div class="name">{{ getReactionName(reaction) }}</div>
</div> </div>
<div class="users"> <div class="users">
@ -27,7 +27,6 @@ defineProps<{
reaction: string; reaction: string;
users: any[]; // TODO users: any[]; // TODO
count: number; count: number;
emojis: any[]; // TODO
targetElement: HTMLElement; targetElement: HTMLElement;
}>(); }>();

View File

@ -7,7 +7,7 @@
:class="{ reacted: note.myReaction == reaction, canToggle }" :class="{ reacted: note.myReaction == reaction, canToggle }"
@click="toggleReaction()" @click="toggleReaction()"
> >
<XReactionIcon class="icon" :reaction="reaction" :custom-emojis="note.emojis"/> <XReactionIcon class="icon" :reaction="reaction"/>
<span class="count">{{ count }}</span> <span class="count">{{ count }}</span>
</button> </button>
</template> </template>

View File

@ -4,7 +4,7 @@
<span v-if="note.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span> <span v-if="note.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span>
<span v-if="note.deletedAt" style="opacity: 0.5">({{ i18n.ts.deleted }})</span> <span v-if="note.deletedAt" style="opacity: 0.5">({{ i18n.ts.deleted }})</span>
<MkA v-if="note.replyId" class="reply" :to="`/notes/${note.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA> <MkA v-if="note.replyId" class="reply" :to="`/notes/${note.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA>
<Mfm v-if="note.text" :text="note.text" :author="note.user" :i="$i" :custom-emojis="note.emojis"/> <Mfm v-if="note.text" :text="note.text" :author="note.user" :i="$i"/>
<MkA v-if="note.renoteId" class="rp" :to="`/notes/${note.renoteId}`">RN: ...</MkA> <MkA v-if="note.renoteId" class="rp" :to="`/notes/${note.renoteId}`">RN: ...</MkA>
</div> </div>
<details v-if="note.files.length > 0"> <details v-if="note.files.length > 0">

View File

@ -9,7 +9,7 @@
<span v-if="$i && $i.id !== user.id && user.isFollowed" class="followed">{{ $ts.followsYou }}</span> <span v-if="$i && $i.id !== user.id && user.isFollowed" class="followed">{{ $ts.followsYou }}</span>
<div class="description"> <div class="description">
<div v-if="user.description" class="mfm"> <div v-if="user.description" class="mfm">
<Mfm :text="user.description" :author="user" :i="$i" :custom-emojis="user.emojis"/> <Mfm :text="user.description" :author="user" :i="$i"/>
</div> </div>
<span v-else style="opacity: 0.7;">{{ i18n.ts.noAccountDescription }}</span> <span v-else style="opacity: 0.7;">{{ i18n.ts.noAccountDescription }}</span>
</div> </div>

View File

@ -11,7 +11,7 @@
<p class="username"><MkAcct :user="user"/></p> <p class="username"><MkAcct :user="user"/></p>
</div> </div>
<div class="description"> <div class="description">
<Mfm v-if="user.description" :text="user.description" :author="user" :i="$i" :custom-emojis="user.emojis"/> <Mfm v-if="user.description" :text="user.description" :author="user" :i="$i"/>
</div> </div>
<div class="status"> <div class="status">
<div> <div>

View File

@ -1,5 +1,5 @@
<template> <template>
<img v-if="customEmoji" class="mk-emoji custom" :class="{ normal, noStyle }" :src="url" :alt="alt" :title="alt" decoding="async"/> <img v-if="isCustom" class="mk-emoji custom" :class="{ normal, noStyle }" :src="url" :alt="alt" :title="alt" decoding="async"/>
<img v-else-if="char && !useOsNativeEmojis" class="mk-emoji" :src="url" :alt="alt" decoding="async" @pointerenter="computeTitle"/> <img v-else-if="char && !useOsNativeEmojis" class="mk-emoji" :src="url" :alt="alt" decoding="async" @pointerenter="computeTitle"/>
<span v-else-if="char && useOsNativeEmojis" :alt="alt" @pointerenter="computeTitle">{{ char }}</span> <span v-else-if="char && useOsNativeEmojis" :alt="alt" @pointerenter="computeTitle">{{ char }}</span>
<span v-else>{{ emoji }}</span> <span v-else>{{ emoji }}</span>
@ -7,44 +7,40 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed } from 'vue'; import { computed } from 'vue';
import { CustomEmoji } from 'misskey-js/built/entities';
import { getStaticImageUrl } from '@/scripts/get-static-image-url'; import { getStaticImageUrl } from '@/scripts/get-static-image-url';
import { char2twemojiFilePath, char2fluentEmojiFilePath } from '@/scripts/emoji-base'; import { char2twemojiFilePath, char2fluentEmojiFilePath } from '@/scripts/emoji-base';
import { defaultStore } from '@/store'; import { defaultStore } from '@/store';
import { instance } from '@/instance';
import { getEmojiName } from '@/scripts/emojilist'; import { getEmojiName } from '@/scripts/emojilist';
const props = defineProps<{ const props = defineProps<{
emoji: string; emoji: string;
normal?: boolean; normal?: boolean;
noStyle?: boolean; noStyle?: boolean;
customEmojis?: CustomEmoji[];
isReaction?: boolean; isReaction?: boolean;
}>(); }>();
const char2path = defaultStore.state.emojiStyle === 'twemoji' ? char2twemojiFilePath : char2fluentEmojiFilePath; const char2path = defaultStore.state.emojiStyle === 'twemoji' ? char2twemojiFilePath : char2fluentEmojiFilePath;
const isCustom = computed(() => props.emoji.startsWith(':')); const isCustom = computed(() => props.emoji.startsWith(':'));
const customEmojiName = props.emoji.substr(1, props.emoji.length - 2);
const char = computed(() => isCustom.value ? undefined : props.emoji); const char = computed(() => isCustom.value ? undefined : props.emoji);
const useOsNativeEmojis = computed(() => defaultStore.state.emojiStyle === 'native' && !props.isReaction); const useOsNativeEmojis = computed(() => defaultStore.state.emojiStyle === 'native' && !props.isReaction);
const ce = computed(() => props.customEmojis ?? instance.emojis ?? []);
const customEmoji = computed(() => isCustom.value ? ce.value.find(x => x.name === props.emoji.substr(1, props.emoji.length - 2)) : undefined);
const url = computed(() => { const url = computed(() => {
if (char.value) { if (char.value) {
return char2path(char.value); return char2path(char.value);
} else { } else {
const rawUrl = (customEmoji.value as CustomEmoji).url; const rawUrl = '/emoji/' + customEmojiName + '.webp';
return defaultStore.state.disableShowingAnimatedImages return defaultStore.state.disableShowingAnimatedImages
? getStaticImageUrl(rawUrl) ? getStaticImageUrl(rawUrl)
: rawUrl; : rawUrl;
} }
}); });
const alt = computed(() => customEmoji.value ? `:${customEmoji.value.name}:` : char.value); const alt = computed(() => isCustom.value ? `:${customEmojiName}:` : char.value);
// Searching from an array with 2000 items for every emoji felt like too energy-consuming, so I decided to do it lazily on pointerenter // Searching from an array with 2000 items for every emoji felt like too energy-consuming, so I decided to do it lazily on pointerenter
function computeTitle(event: PointerEvent): void { function computeTitle(event: PointerEvent): void {
const title = customEmoji.value const title = isCustom.value
? `:${customEmoji.value.name}:` ? `:${customEmojiName}:`
: (getEmojiName(char.value as string) ?? char.value as string); : (getEmojiName(char.value as string) ?? char.value as string);
(event.target as HTMLElement).title = title; (event.target as HTMLElement).title = title;
} }

View File

@ -1,5 +1,5 @@
<template> <template>
<MfmCore :text="text" :plain="plain" :nowrap="nowrap" :author="author" :customEmojis="customEmojis" :isNote="isNote" class="havbbuyv" :class="{ nowrap }"/> <MfmCore :text="text" :plain="plain" :nowrap="nowrap" :author="author" :is-note="isNote" class="havbbuyv" :class="{ nowrap }"/>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -11,7 +11,6 @@ const props = withDefaults(defineProps<{
plain?: boolean; plain?: boolean;
nowrap?: boolean; nowrap?: boolean;
author?: any; author?: any;
customEmojis?: any;
isNote?: boolean; isNote?: boolean;
}>(), { }>(), {
plain: false, plain: false,

View File

@ -1,5 +1,5 @@
<template> <template>
<Mfm :text="user.name || user.username" :plain="true" :nowrap="nowrap" :custom-emojis="user.emojis"/> <Mfm :text="user.name || user.username" :plain="true" :nowrap="nowrap"/>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>

View File

@ -35,9 +35,6 @@ export default defineComponent({
type: Object, type: Object,
default: null, default: null,
}, },
customEmojis: {
required: false,
},
isNote: { isNote: {
type: Boolean, type: Boolean,
default: true, default: true,
@ -275,7 +272,6 @@ export default defineComponent({
return [h(MkEmoji, { return [h(MkEmoji, {
key: Math.random(), key: Math.random(),
emoji: `:${token.props.name}:`, emoji: `:${token.props.name}:`,
customEmojis: this.customEmojis,
normal: this.plain, normal: this.plain,
})]; })];
} }
@ -284,7 +280,6 @@ export default defineComponent({
return [h(MkEmoji, { return [h(MkEmoji, {
key: Math.random(), key: Math.random(),
emoji: token.props.emoji, emoji: token.props.emoji,
customEmojis: this.customEmojis,
normal: this.plain, normal: this.plain,
})]; })];
} }

View File

@ -15,6 +15,7 @@ export const instance: Misskey.entities.InstanceMetadata = reactive(instanceData
export async function fetchInstance() { export async function fetchInstance() {
const meta = await api('meta', { const meta = await api('meta', {
detail: false, detail: false,
omitEmojiUrl: true,
}); });
for (const [k, v] of Object.entries(meta)) { for (const [k, v] of Object.entries(meta)) {

View File

@ -9,7 +9,7 @@
<img src="/client-assets/about-icon.png" alt="" class="icon" draggable="false" @load="iconLoaded" @click="gravity"/> <img src="/client-assets/about-icon.png" alt="" class="icon" draggable="false" @load="iconLoaded" @click="gravity"/>
<div class="misskey">Misskey</div> <div class="misskey">Misskey</div>
<div class="version">v{{ version }}</div> <div class="version">v{{ version }}</div>
<span v-for="emoji in easterEggEmojis" :key="emoji.id" class="emoji" :data-physics-x="emoji.left" :data-physics-y="emoji.top" :class="{ _physics_circle_: !emoji.emoji.startsWith(':') }"><MkEmoji class="emoji" :emoji="emoji.emoji" :custom-emojis="$instance.emojis" :is-reaction="false" :normal="true" :no-style="true"/></span> <span v-for="emoji in easterEggEmojis" :key="emoji.id" class="emoji" :data-physics-x="emoji.left" :data-physics-y="emoji.top" :class="{ _physics_circle_: !emoji.emoji.startsWith(':') }"><MkEmoji class="emoji" :emoji="emoji.emoji" :is-reaction="false" :normal="true" :no-style="true"/></span>
</div> </div>
<div class="_formBlock" style="text-align: center;"> <div class="_formBlock" style="text-align: center;">
{{ i18n.ts._aboutMisskey.about }}<br><a href="https://misskey-hub.net/docs/misskey.html" target="_blank" class="_link">{{ i18n.ts.learnMore }}</a> {{ i18n.ts._aboutMisskey.about }}<br><a href="https://misskey-hub.net/docs/misskey.html" target="_blank" class="_link">{{ i18n.ts.learnMore }}</a>

View File

@ -19,7 +19,7 @@
<p class="acct">@{{ acct(req.follower) }}</p> <p class="acct">@{{ acct(req.follower) }}</p>
</div> </div>
<div v-if="req.follower.description" class="description" :title="req.follower.description"> <div v-if="req.follower.description" class="description" :title="req.follower.description">
<Mfm :text="req.follower.description" :is-note="false" :author="req.follower" :i="$i" :custom-emojis="req.follower.emojis" :plain="true" :nowrap="true"/> <Mfm :text="req.follower.description" :is-note="false" :author="req.follower" :i="$i" :plain="true" :nowrap="true"/>
</div> </div>
<div class="actions"> <div class="actions">
<button class="_button" @click="accept(req.follower)"><i class="ti ti-check"></i></button> <button class="_button" @click="accept(req.follower)"><i class="ti ti-check"></i></button>

View File

@ -41,7 +41,7 @@
</div> </div>
</div> </div>
<div class="description"> <div class="description">
<Mfm v-if="user.description" :text="user.description" :is-note="false" :author="user" :i="$i" :custom-emojis="user.emojis"/> <Mfm v-if="user.description" :text="user.description" :is-note="false" :author="user" :i="$i"/>
<p v-else class="empty">{{ i18n.ts.noAccountDescription }}</p> <p v-else class="empty">{{ i18n.ts.noAccountDescription }}</p>
</div> </div>
<div class="fields system"> <div class="fields system">
@ -61,10 +61,10 @@
<div v-if="user.fields.length > 0" class="fields"> <div v-if="user.fields.length > 0" class="fields">
<dl v-for="(field, i) in user.fields" :key="i" class="field"> <dl v-for="(field, i) in user.fields" :key="i" class="field">
<dt class="name"> <dt class="name">
<Mfm :text="field.name" :plain="true" :custom-emojis="user.emojis" :colored="false"/> <Mfm :text="field.name" :plain="true" :colored="false"/>
</dt> </dt>
<dd class="value"> <dd class="value">
<Mfm :text="field.value" :author="user" :i="$i" :custom-emojis="user.emojis" :colored="false"/> <Mfm :text="field.value" :author="user" :i="$i" :colored="false"/>
</dd> </dd>
</dl> </dl>
</div> </div>

View File

@ -4,7 +4,7 @@
<div v-for="item in items" :key="item.id" :to="`/clips/${item.id}`" class="item _panel _gap afdcfbfb"> <div v-for="item in items" :key="item.id" :to="`/clips/${item.id}`" class="item _panel _gap afdcfbfb">
<div class="header"> <div class="header">
<MkAvatar class="avatar" :user="user"/> <MkAvatar class="avatar" :user="user"/>
<MkReactionIcon class="reaction" :reaction="item.type" :custom-emojis="item.note.emojis" :no-style="true"/> <MkReactionIcon class="reaction" :reaction="item.type" :no-style="true"/>
<MkTime :time="item.createdAt" class="createdAt"/> <MkTime :time="item.createdAt" class="createdAt"/>
</div> </div>
<MkNote :key="item.id" :note="item.note"/> <MkNote :key="item.id" :note="item.note"/>

View File

@ -5,7 +5,7 @@
<div class="content _panel"> <div class="content _panel">
<div class="body"> <div class="body">
<MkA v-if="note.replyId" class="reply" :to="`/notes/${note.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA> <MkA v-if="note.replyId" class="reply" :to="`/notes/${note.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA>
<Mfm v-if="note.text" :text="note.text" :author="note.user" :i="$i" :custom-emojis="note.emojis"/> <Mfm v-if="note.text" :text="note.text" :author="note.user" :i="$i"/>
<MkA v-if="note.renoteId" class="rp" :to="`/notes/${note.renoteId}`">RN: ...</MkA> <MkA v-if="note.renoteId" class="rp" :to="`/notes/${note.renoteId}`">RN: ...</MkA>
</div> </div>
<div v-if="note.files.length > 0" class="richcontent"> <div v-if="note.files.length > 0" class="richcontent">

View File

@ -6,7 +6,7 @@
<span v-for="note in notes" :key="note.id" class="item"> <span v-for="note in notes" :key="note.id" class="item">
<img class="avatar" :src="note.user.avatarUrl" decoding="async"/> <img class="avatar" :src="note.user.avatarUrl" decoding="async"/>
<MkA class="text" :to="notePage(note)"> <MkA class="text" :to="notePage(note)">
<Mfm class="text" :text="getNoteSummary(note)" :plain="true" :nowrap="true" :custom-emojis="note.emojis"/> <Mfm class="text" :text="getNoteSummary(note)" :plain="true" :nowrap="true"/>
</MkA> </MkA>
<span class="divider"></span> <span class="divider"></span>
</span> </span>