enhance: カスタム絵文字関連の変更 (#9794)

* PackedNoteなどのemojisはプロキシしていないURLを返すように

* MFMでx3/x4もしくはscale.x/yが2.5以上に指定されていた場合にはオリジナル品質の絵文字を使用する

* update CHANGELOG.md

* fix changelog

* ??

* wip

* fix

* merge

* Update packages/frontend/src/scripts/media-proxy.ts

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>

* merge

* calc scale

---------

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
This commit is contained in:
tamaina 2023-04-12 10:58:56 +09:00 committed by GitHub
parent 0db88a5a3b
commit 81d2c5a4a7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 58 additions and 40 deletions

View file

@ -15,7 +15,9 @@
## 13.x.x (unreleased) ## 13.x.x (unreleased)
### General ### General
- - カスタム絵文字関連の変更
* ートなどに含まれるemojispopulateEmojiの結果プロキシされたURLではなくオリジナルのURLを指すように
* MFMでx3/x4もしくはscale.x/yが2.5以上に指定されていた場合にはオリジナル品質の絵文字を使用するように
### Client ### Client
- -

View file

@ -267,16 +267,7 @@ export class CustomEmojiService {
const emoji = await this.cache.fetch(`${name} ${host}`, queryOrNull); const emoji = await this.cache.fetch(`${name} ${host}`, queryOrNull);
if (emoji == null) return null; if (emoji == null) return null;
return emoji.publicUrl || emoji.originalUrl; // || emoji.originalUrl してるのは後方互換性のためpublicUrlはstringなので??はだめ)
const isLocal = emoji.host == null;
const emojiUrl = emoji.publicUrl || emoji.originalUrl; // || emoji.originalUrl してるのは後方互換性のためpublicUrlはstringなので??はだめ)
const url = isLocal
? emojiUrl
: this.config.proxyRemoteFiles
? `${this.config.mediaProxy}/emoji.webp?${query({ url: emojiUrl })}`
: emojiUrl;
return url;
} }
/** /**

View file

@ -5,7 +5,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed } from 'vue'; import { computed } from 'vue';
import { getStaticImageUrl } from '@/scripts/media-proxy'; import { getProxiedImageUrl, getStaticImageUrl } from '@/scripts/media-proxy';
import { defaultStore } from '@/store'; import { defaultStore } from '@/store';
import { customEmojis } from '@/custom-emojis'; import { customEmojis } from '@/custom-emojis';
@ -15,25 +15,38 @@ const props = defineProps<{
noStyle?: boolean; noStyle?: boolean;
host?: string | null; host?: string | null;
url?: string; url?: string;
useOriginalSize?: boolean;
}>(); }>();
const customEmojiName = computed(() => (props.name[0] === ':' ? props.name.substr(1, props.name.length - 2) : props.name).replace('@.', '')); const customEmojiName = computed(() => (props.name[0] === ':' ? props.name.substr(1, props.name.length - 2) : props.name).replace('@.', ''));
const isLocal = computed(() => !props.host && (customEmojiName.value.endsWith('@.') || !customEmojiName.value.includes('@')));
const rawUrl = computed(() => { const rawUrl = computed(() => {
if (props.url) { if (props.url) {
return props.url; return props.url;
} }
if (props.host == null && !customEmojiName.value.includes('@')) { if (isLocal.value) {
return customEmojis.value.find(x => x.name === customEmojiName.value)?.url ?? null; return customEmojis.value.find(x => x.name === customEmojiName.value)?.url ?? null;
} }
return props.host ? `/emoji/${customEmojiName.value}@${props.host}.webp` : `/emoji/${customEmojiName.value}.webp`; return props.host ? `/emoji/${customEmojiName.value}@${props.host}.webp` : `/emoji/${customEmojiName.value}.webp`;
}); });
const url = computed(() => const url = computed(() => {
defaultStore.reactiveState.disableShowingAnimatedImages.value && rawUrl.value if (rawUrl.value == null) return null;
? getStaticImageUrl(rawUrl.value)
: rawUrl.value, const proxied =
); (rawUrl.value.startsWith('/emoji/') || (props.useOriginalSize && isLocal.value))
? rawUrl.value
: getProxiedImageUrl(
rawUrl.value,
props.useOriginalSize ? undefined : 'emoji',
false,
true,
);
return defaultStore.reactiveState.disableShowingAnimatedImages.value
? getStaticImageUrl(proxied)
: proxied;
});
const alt = computed(() => `:${customEmojiName.value}:`); const alt = computed(() => `:${customEmojiName.value}:`);
let errored = $ref(url.value == null); let errored = $ref(url.value == null);

View file

@ -51,6 +51,10 @@ export default defineComponent({
type: Object, type: Object,
default: null, default: null,
}, },
rootScale: {
type: Number,
default: 1,
}
}, },
render() { render() {
@ -65,7 +69,12 @@ export default defineComponent({
const useAnim = defaultStore.state.advancedMfm && defaultStore.state.animatedMfm; const useAnim = defaultStore.state.advancedMfm && defaultStore.state.animatedMfm;
const genEl = (ast: mfm.MfmNode[]) => ast.map((token): VNode | string | (VNode | string)[] => { /**
* Gen Vue Elements from MFM AST
* @param ast MFM AST
* @param scale How times large the text is
*/
const genEl = (ast: mfm.MfmNode[], scale: number) => ast.map((token): VNode | string | (VNode | string)[] => {
switch (token.type) { switch (token.type) {
case 'text': { case 'text': {
const text = token.props.text.replace(/(\r\n|\n|\r)/g, '\n'); const text = token.props.text.replace(/(\r\n|\n|\r)/g, '\n');
@ -84,17 +93,17 @@ export default defineComponent({
} }
case 'bold': { case 'bold': {
return [h('b', genEl(token.children))]; return [h('b', genEl(token.children, scale))];
} }
case 'strike': { case 'strike': {
return [h('del', genEl(token.children))]; return [h('del', genEl(token.children, scale))];
} }
case 'italic': { case 'italic': {
return h('i', { return h('i', {
style: 'font-style: oblique;', style: 'font-style: oblique;',
}, genEl(token.children)); }, genEl(token.children, scale));
} }
case 'fn': { case 'fn': {
@ -155,17 +164,17 @@ export default defineComponent({
case 'x2': { case 'x2': {
return h('span', { return h('span', {
class: defaultStore.state.advancedMfm ? 'mfm-x2' : '', class: defaultStore.state.advancedMfm ? 'mfm-x2' : '',
}, genEl(token.children)); }, genEl(token.children, scale * 2));
} }
case 'x3': { case 'x3': {
return h('span', { return h('span', {
class: defaultStore.state.advancedMfm ? 'mfm-x3' : '', class: defaultStore.state.advancedMfm ? 'mfm-x3' : '',
}, genEl(token.children)); }, genEl(token.children, scale * 3));
} }
case 'x4': { case 'x4': {
return h('span', { return h('span', {
class: defaultStore.state.advancedMfm ? 'mfm-x4' : '', class: defaultStore.state.advancedMfm ? 'mfm-x4' : '',
}, genEl(token.children)); }, genEl(token.children, scale * 4));
} }
case 'font': { case 'font': {
const family = const family =
@ -182,7 +191,7 @@ export default defineComponent({
case 'blur': { case 'blur': {
return h('span', { return h('span', {
class: '_mfm_blur_', class: '_mfm_blur_',
}, genEl(token.children)); }, genEl(token.children, scale));
} }
case 'rainbow': { case 'rainbow': {
const speed = validTime(token.props.args.speed) ?? '1s'; const speed = validTime(token.props.args.speed) ?? '1s';
@ -191,9 +200,9 @@ export default defineComponent({
} }
case 'sparkle': { case 'sparkle': {
if (!useAnim) { if (!useAnim) {
return genEl(token.children); return genEl(token.children, scale);
} }
return h(MkSparkle, {}, genEl(token.children)); return h(MkSparkle, {}, genEl(token.children, scale));
} }
case 'rotate': { case 'rotate': {
const degrees = parseFloat(token.props.args.deg ?? '90'); const degrees = parseFloat(token.props.args.deg ?? '90');
@ -215,6 +224,7 @@ export default defineComponent({
const x = Math.min(parseFloat(token.props.args.x ?? '1'), 5); const x = Math.min(parseFloat(token.props.args.x ?? '1'), 5);
const y = Math.min(parseFloat(token.props.args.y ?? '1'), 5); const y = Math.min(parseFloat(token.props.args.y ?? '1'), 5);
style = `transform: scale(${x}, ${y});`; style = `transform: scale(${x}, ${y});`;
scale = scale * Math.max(x, y);
break; break;
} }
case 'fg': { case 'fg': {
@ -231,24 +241,24 @@ export default defineComponent({
} }
} }
if (style == null) { if (style == null) {
return h('span', {}, ['$[', token.props.name, ' ', ...genEl(token.children), ']']); return h('span', {}, ['$[', token.props.name, ' ', ...genEl(token.children, scale), ']']);
} else { } else {
return h('span', { return h('span', {
style: 'display: inline-block; ' + style, style: 'display: inline-block; ' + style,
}, genEl(token.children)); }, genEl(token.children, scale));
} }
} }
case 'small': { case 'small': {
return [h('small', { return [h('small', {
style: 'opacity: 0.7;', style: 'opacity: 0.7;',
}, genEl(token.children))]; }, genEl(token.children, scale))];
} }
case 'center': { case 'center': {
return [h('div', { return [h('div', {
style: 'text-align:center;', style: 'text-align:center;',
}, genEl(token.children))]; }, genEl(token.children, scale))];
} }
case 'url': { case 'url': {
@ -264,7 +274,7 @@ export default defineComponent({
key: Math.random(), key: Math.random(),
url: token.props.url, url: token.props.url,
rel: 'nofollow noopener', rel: 'nofollow noopener',
}, genEl(token.children))]; }, genEl(token.children, scale))];
} }
case 'mention': { case 'mention': {
@ -303,11 +313,11 @@ export default defineComponent({
if (!this.nowrap) { if (!this.nowrap) {
return [h('div', { return [h('div', {
style: QUOTE_STYLE, style: QUOTE_STYLE,
}, genEl(token.children))]; }, genEl(token.children, scale))];
} else { } else {
return [h('span', { return [h('span', {
style: QUOTE_STYLE, style: QUOTE_STYLE,
}, genEl(token.children))]; }, genEl(token.children, scale))];
} }
} }
@ -319,6 +329,7 @@ export default defineComponent({
name: token.props.name, name: token.props.name,
normal: this.plain, normal: this.plain,
host: null, host: null,
useOriginalSize: scale >= 2.5,
})]; })];
} else { } else {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
@ -332,6 +343,7 @@ export default defineComponent({
url: this.emojiUrls ? this.emojiUrls[token.props.name] : null, url: this.emojiUrls ? this.emojiUrls[token.props.name] : null,
normal: this.plain, normal: this.plain,
host: this.author.host, host: this.author.host,
useOriginalSize: scale >= 2.5,
})]; })];
} }
} }
@ -360,7 +372,7 @@ export default defineComponent({
} }
case 'plain': { case 'plain': {
return [h('span', genEl(token.children))]; return [h('span', genEl(token.children, scale))];
} }
default: { default: {
@ -373,6 +385,6 @@ export default defineComponent({
}).flat(Infinity) as (VNode | string)[]; }).flat(Infinity) as (VNode | string)[];
// Parse ast to DOM // Parse ast to DOM
return h('span', genEl(ast)); return h('span', genEl(ast, this.rootScale));
}, },
}); });

View file

@ -2,7 +2,7 @@ import { query } from '@/scripts/url';
import { url } from '@/config'; import { url } from '@/config';
import { instance } from '@/instance'; import { instance } from '@/instance';
export function getProxiedImageUrl(imageUrl: string, type?: 'preview', mustOrigin: boolean = false): string { export function getProxiedImageUrl(imageUrl: string, type?: 'preview' | 'emoji' | 'avatar', mustOrigin: boolean = false, noFallback: boolean = false): string {
const localProxy = `${url}/proxy`; const localProxy = `${url}/proxy`;
if (imageUrl.startsWith(instance.mediaProxy + '/') || imageUrl.startsWith('/proxy/') || imageUrl.startsWith(localProxy + '/')) { if (imageUrl.startsWith(instance.mediaProxy + '/') || imageUrl.startsWith('/proxy/') || imageUrl.startsWith(localProxy + '/')) {
@ -15,7 +15,7 @@ export function getProxiedImageUrl(imageUrl: string, type?: 'preview', mustOrigi
: 'image.webp' : 'image.webp'
}?${query({ }?${query({
url: imageUrl, url: imageUrl,
fallback: '1', ...(!noFallback ? { 'fallback': '1' } : {}),
...(type ? { [type]: '1' } : {}), ...(type ? { [type]: '1' } : {}),
...(mustOrigin ? { origin: '1' } : {}), ...(mustOrigin ? { origin: '1' } : {}),
})}`; })}`;