refactor: 絵文字URLを引き回すのをやめる (#9423)
This commit is contained in:
parent
510e6ec7e9
commit
912791b3ab
28 changed files with 79 additions and 58 deletions
|
@ -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)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 => ({
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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,
|
||||||
})];
|
})];
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)) {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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"/>
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in a new issue