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:
parent
0db88a5a3b
commit
81d2c5a4a7
5 changed files with 58 additions and 40 deletions
|
@ -15,7 +15,9 @@
|
||||||
## 13.x.x (unreleased)
|
## 13.x.x (unreleased)
|
||||||
|
|
||||||
### General
|
### General
|
||||||
-
|
- カスタム絵文字関連の変更
|
||||||
|
* ノートなどに含まれるemojis(populateEmojiの結果)は(プロキシされたURLではなく)オリジナルのURLを指すように
|
||||||
|
* MFMでx3/x4もしくはscale.x/yが2.5以上に指定されていた場合にはオリジナル品質の絵文字を使用するように
|
||||||
|
|
||||||
### Client
|
### Client
|
||||||
-
|
-
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -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' } : {}),
|
||||||
})}`;
|
})}`;
|
||||||
|
|
Loading…
Reference in a new issue