mirror of
https://github.com/sim1222/misskey.git
synced 2025-08-04 07:26:29 +09:00
feat: client side media proxy
This commit is contained in:
@ -1,13 +1,14 @@
|
||||
<template>
|
||||
<div class="xubzgfgb" :class="{ cover }" :title="title">
|
||||
<canvas v-if="!loaded" ref="canvas" :width="size" :height="size" :title="title"/>
|
||||
<img v-if="src" :src="src" :title="title" :alt="alt" @load="onLoad"/>
|
||||
<img v-if="src" :src="imgSrc" :title="title" :alt="alt" @load="onLoad"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted } from 'vue';
|
||||
import { decode } from 'blurhash';
|
||||
import { defaultStore } from '@/store';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
src?: string | null;
|
||||
@ -24,6 +25,11 @@ const props = withDefaults(defineProps<{
|
||||
cover: true,
|
||||
});
|
||||
|
||||
const imgSrc = defaultStore.state.mediaProxy ?
|
||||
defaultStore.state.mediaProxy + '?url=' + props.src
|
||||
:
|
||||
props.src;
|
||||
|
||||
const canvas = $ref<HTMLCanvasElement>();
|
||||
let loaded = $ref(false);
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<MkA v-if="url.startsWith('/')" v-user-preview="canonical" class="akbvjaqn" :class="{ isMe }" :to="url" :style="{ background: bgCss }">
|
||||
<img class="icon" :src="`/avatar/@${username}@${host}`" alt="">
|
||||
<img class="icon" :src="imageUrl" alt="">
|
||||
<span class="main">
|
||||
<span class="username">@{{ username }}</span>
|
||||
<span v-if="(host != localHost) || $store.state.showFullAcct" class="host">@{{ toUnicode(host) }}</span>
|
||||
@ -18,14 +18,20 @@
|
||||
import { toUnicode } from 'punycode';
|
||||
import { } from 'vue';
|
||||
import tinycolor from 'tinycolor2';
|
||||
import { host as localHost } from '@/config';
|
||||
import { host as localHost, url as localUrl } from '@/config';
|
||||
import { $i } from '@/account';
|
||||
import { defaultStore } from '@/store';
|
||||
|
||||
const props = defineProps<{
|
||||
username: string;
|
||||
host: string;
|
||||
}>();
|
||||
|
||||
const imageUrl = defaultStore.state.mediaProxy ?
|
||||
defaultStore.state.mediaProxy + '?url=' + `${localUrl}/avatar/@${props.username}@${props.host}`
|
||||
:
|
||||
`${localUrl}/avatar/@${props.username}@${props.host}`;
|
||||
|
||||
const canonical = props.host === localHost ? `@${props.username}` : `@${props.username}@${toUnicode(props.host)}`;
|
||||
|
||||
const url = `/${canonical}`;
|
||||
|
@ -35,9 +35,14 @@ const emit = defineEmits<{
|
||||
(ev: 'click', v: MouseEvent): void;
|
||||
}>();
|
||||
|
||||
const url = $computed(() => defaultStore.state.disableShowingAnimatedImages
|
||||
const imageSrc = defaultStore.state.disableShowingAnimatedImages
|
||||
? getStaticImageUrl(props.user.avatarUrl)
|
||||
: props.user.avatarUrl);
|
||||
: props.user.avatarUrl;
|
||||
|
||||
const url = defaultStore.state.mediaProxy ?
|
||||
defaultStore.state.mediaProxy + '?url=' + imageSrc
|
||||
:
|
||||
imageSrc;
|
||||
|
||||
function onClick(ev: MouseEvent) {
|
||||
emit('click', ev);
|
||||
|
@ -26,15 +26,17 @@ const char = computed(() => isCustom.value ? null : props.emoji);
|
||||
const useOsNativeEmojis = computed(() => defaultStore.state.useOsNativeEmojis && !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)) : null);
|
||||
const url = computed(() => {
|
||||
if (char.value) {
|
||||
return char2filePath(char.value);
|
||||
} else {
|
||||
return defaultStore.state.disableShowingAnimatedImages
|
||||
? getStaticImageUrl(customEmoji.value.url)
|
||||
: customEmoji.value.url;
|
||||
}
|
||||
});
|
||||
const imageSrc = defaultStore.state.disableShowingAnimatedImages
|
||||
? getStaticImageUrl(customEmoji.value?.url)
|
||||
: customEmoji.value?.url;
|
||||
const url = char.value ?
|
||||
char2filePath(char.value)
|
||||
:
|
||||
defaultStore.state.mediaProxy ?
|
||||
defaultStore.state.mediaProxy + '?url=' + imageSrc
|
||||
:
|
||||
imageSrc;
|
||||
|
||||
const alt = computed(() => customEmoji.value ? `:${customEmoji.value.name}:` : char.value);
|
||||
</script>
|
||||
|
||||
|
@ -35,6 +35,10 @@
|
||||
<option value="dialog">{{ i18n.ts._serverDisconnectedBehavior.dialog }}</option>
|
||||
<option value="quiet">{{ i18n.ts._serverDisconnectedBehavior.quiet }}</option>
|
||||
</FormSelect>
|
||||
<FormInput v-model="mediaProxy">
|
||||
<template #label>{{ i18n.ts.mediaProxy }}</template>
|
||||
<template #caption>{{ i18n.ts.mediaProxyDesc }}</template>
|
||||
</FormInput>
|
||||
</FormSection>
|
||||
|
||||
<FormSection>
|
||||
@ -100,6 +104,7 @@ import FormRadios from '@/components/form/radios.vue';
|
||||
import FormRange from '@/components/form/range.vue';
|
||||
import FormSection from '@/components/form/section.vue';
|
||||
import FormLink from '@/components/form/link.vue';
|
||||
import FormInput from '@/components/form/input.vue';
|
||||
import MkLink from '@/components/MkLink.vue';
|
||||
import { langs } from '@/config';
|
||||
import { defaultStore } from '@/store';
|
||||
@ -143,6 +148,7 @@ const enableInfiniteScroll = computed(defaultStore.makeGetterSetter('enableInfin
|
||||
const useReactionPickerForContextMenu = computed(defaultStore.makeGetterSetter('useReactionPickerForContextMenu'));
|
||||
const squareAvatars = computed(defaultStore.makeGetterSetter('squareAvatars'));
|
||||
const aiChanMode = computed(defaultStore.makeGetterSetter('aiChanMode'));
|
||||
const mediaProxy = computed(defaultStore.makeGetterSetter('mediaProxy'));
|
||||
|
||||
watch(lang, () => {
|
||||
localStorage.setItem('lang', lang.value as string);
|
||||
|
@ -251,6 +251,10 @@ export const defaultStore = markRaw(new Storage('base', {
|
||||
where: 'device',
|
||||
default: false,
|
||||
},
|
||||
mediaProxy: {
|
||||
where: 'device',
|
||||
default: '',
|
||||
},
|
||||
}));
|
||||
|
||||
// TODO: 他のタブと永続化されたstateを同期
|
||||
|
Reference in New Issue
Block a user