diff --git a/packages/client/src/components/global/a.vue b/packages/client/src/components/global/a.vue
index d3bc2235b..cf7385ca2 100644
--- a/packages/client/src/components/global/a.vue
+++ b/packages/client/src/components/global/a.vue
@@ -36,7 +36,8 @@ const active = $computed(() => {
 });
 
 function onContextmenu(ev) {
-	if (window.getSelection().toString() !== '') return;
+	const selection = window.getSelection();
+	if (selection && selection.toString() !== '') return;
 	os.contextMenu([{
 		type: 'label',
 		text: props.to,
@@ -73,7 +74,7 @@ function onContextmenu(ev) {
 	}], ev);
 }
 
-function window() {
+function openWindow() {
 	os.pageWindow(props.to);
 }
 
@@ -93,7 +94,7 @@ function nav() {
 
 	if (props.behavior) {
 		if (props.behavior === 'window') {
-			return window();
+			return openWindow();
 		} else if (props.behavior === 'modalWindow') {
 			return modalWindow();
 		}
diff --git a/packages/client/src/components/media-banner.vue b/packages/client/src/components/media-banner.vue
index 9dbfe3d0c..5093f11e9 100644
--- a/packages/client/src/components/media-banner.vue
+++ b/packages/client/src/components/media-banner.vue
@@ -6,7 +6,7 @@
 		<span>{{ $ts.clickToShow }}</span>
 	</div>
 	<div v-else-if="media.type.startsWith('audio') && media.type !== 'audio/midi'" class="audio">
-		<audio ref="audio"
+		<audio ref="audioEl"
 			class="audio"
 			:src="media.url"
 			:title="media.name"
@@ -25,34 +25,26 @@
 </div>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
-import * as os from '@/os';
+<script lang="ts" setup>
+import { onMounted } from 'vue';
+import * as misskey from 'misskey-js';
 import { ColdDeviceStorage } from '@/store';
 
-export default defineComponent({
-	props: {
-		media: {
-			type: Object,
-			required: true
-		}
-	},
-	data() {
-		return {
-			hide: true,
-		};
-	},
-	mounted() {
-		const audioTag = this.$refs.audio as HTMLAudioElement;
-		if (audioTag) audioTag.volume = ColdDeviceStorage.get('mediaVolume');
-	},
-	methods: {
-		volumechange() {
-			const audioTag = this.$refs.audio as HTMLAudioElement;
-			ColdDeviceStorage.set('mediaVolume', audioTag.volume);
-		},
-	},
-})
+const props = withDefaults(defineProps<{
+	media: misskey.entities.DriveFile;
+}>(), {
+});
+
+const audioEl = $ref<HTMLAudioElement | null>();
+let hide = $ref(true);
+
+function volumechange() {
+	if (audioEl) ColdDeviceStorage.set('mediaVolume', audioEl.volume);
+}
+
+onMounted(() => {
+	if (audioEl) audioEl.volume = ColdDeviceStorage.get('mediaVolume');
+});
 </script>
 
 <style lang="scss" scoped>
diff --git a/packages/client/src/components/url-preview.vue b/packages/client/src/components/url-preview.vue
index dff74800e..bf3b35879 100644
--- a/packages/client/src/components/url-preview.vue
+++ b/packages/client/src/components/url-preview.vue
@@ -4,7 +4,7 @@
 	<iframe :src="player.url + (player.url.match(/\?/) ? '&autoplay=1&auto_play=1' : '?autoplay=1&auto_play=1')" :width="player.width || '100%'" :heigth="player.height || 250" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen />
 </div>
 <div v-else-if="tweetId && tweetExpanded" ref="twitter" class="twitter">
-	<iframe ref="tweet" scrolling="no" frameborder="no" :style="{ position: 'relative', left: `${tweetLeft}px`, width: `${tweetLeft < 0 ? 'auto' : '100%'}`, height: `${tweetHeight}px` }" :src="`https://platform.twitter.com/embed/index.html?embedId=${embedId}&amp;hideCard=false&amp;hideThread=false&amp;lang=en&amp;theme=${$store.state.darkMode ? 'dark' : 'light'}&amp;id=${tweetId}`"></iframe>
+	<iframe ref="tweet" scrolling="no" frameborder="no" :style="{ position: 'relative', width: '100%', height: `${tweetHeight}px` }" :src="`https://platform.twitter.com/embed/index.html?embedId=${embedId}&amp;hideCard=false&amp;hideThread=false&amp;lang=en&amp;theme=${$store.state.darkMode ? 'dark' : 'light'}&amp;id=${tweetId}`"></iframe>
 </div>
 <div v-else v-size="{ max: [400, 350] }" class="mk-url-preview">
 	<transition name="zoom" mode="out-in">
@@ -32,110 +32,80 @@
 </div>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { onMounted, onUnmounted } from 'vue';
 import { url as local, lang } from '@/config';
-import * as os from '@/os';
 
-export default defineComponent({
-	props: {
-		url: {
-			type: String,
-			require: true
-		},
+const props = withDefaults(defineProps<{
+	url: string;
+	detail?: boolean;
+	compact?: boolean;
+}>(), {
+	detail: false,
+	compact: false,
+});
 
-		detail: {
-			type: Boolean,
-			required: false,
-			default: false
-		},
+const self = props.url.startsWith(local);
+const attr = self ? 'to' : 'href';
+const target = self ? null : '_blank';
+let fetching = $ref(true);
+let title = $ref<string | null>(null);
+let description = $ref<string | null>(null);
+let thumbnail = $ref<string | null>(null);
+let icon = $ref<string | null>(null);
+let sitename = $ref<string | null>(null);
+let player = $ref({
+	url: null,
+	width: null,
+	height: null
+});
+let playerEnabled = $ref(false);
+let tweetId = $ref<string | null>(null);
+let tweetExpanded = $ref(props.detail);
+const embedId = `embed${Math.random().toString().replace(/\D/,'')}`;
+let tweetHeight = $ref(150);
 
-		compact: {
-			type: Boolean,
-			required: false,
-			default: false
-		},
-	},
+const requestUrl = new URL(props.url);
 
-	data() {
-		const self = this.url.startsWith(local);
-		return {
-			local,
-			fetching: true,
-			title: null,
-			description: null,
-			thumbnail: null,
-			icon: null,
-			sitename: null,
-			player: {
-				url: null,
-				width: null,
-				height: null
-			},
-			tweetId: null,
-			tweetExpanded: this.detail,
-			embedId: `embed${Math.random().toString().replace(/\D/,'')}`,
-			tweetHeight: 150,
-			tweetLeft: 0,
-			playerEnabled: false,
-			self: self,
-			attr: self ? 'to' : 'href',
-			target: self ? null : '_blank',
-		};
-	},
+if (requestUrl.hostname == 'twitter.com') {
+	const m = requestUrl.pathname.match(/^\/.+\/status(?:es)?\/(\d+)/);
+	if (m) tweetId = m[1];
+}
 
-	created() {
-		const requestUrl = new URL(this.url);
+if (requestUrl.hostname === 'music.youtube.com' && requestUrl.pathname.match('^/(?:watch|channel)')) {
+	requestUrl.hostname = 'www.youtube.com';
+}
 
-		if (requestUrl.hostname == 'twitter.com') {
-			const m = requestUrl.pathname.match(/^\/.+\/status(?:es)?\/(\d+)/);
-			if (m) this.tweetId = m[1];
-		}
+const requestLang = (lang || 'ja-JP').replace('ja-KS', 'ja-JP');
 
-		if (requestUrl.hostname === 'music.youtube.com' && requestUrl.pathname.match('^/(?:watch|channel)')) {
-			requestUrl.hostname = 'www.youtube.com';
-		}
+requestUrl.hash = '';
 
-		const requestLang = (lang || 'ja-JP').replace('ja-KS', 'ja-JP');
+fetch(`/url?url=${encodeURIComponent(requestUrl.href)}&lang=${requestLang}`).then(res => {
+	res.json().then(info => {
+		if (info.url == null) return;
+		title = info.title;
+		description = info.description;
+		thumbnail = info.thumbnail;
+		icon = info.icon;
+		sitename = info.sitename;
+		fetching = false;
+		player = info.player;
+	})
+});
 
-		requestUrl.hash = '';
+function adjustTweetHeight(message: any) {
+	if (message.origin !== 'https://platform.twitter.com') return;
+	const embed = message.data?.['twttr.embed'];
+	if (embed?.method !== 'twttr.private.resize') return;
+	if (embed?.id !== embedId) return;
+	const height = embed?.params[0]?.height;
+	if (height) tweetHeight = height;
+}
 
-		fetch(`/url?url=${encodeURIComponent(requestUrl.href)}&lang=${requestLang}`).then(res => {
-			res.json().then(info => {
-				if (info.url == null) return;
-				this.title = info.title;
-				this.description = info.description;
-				this.thumbnail = info.thumbnail;
-				this.icon = info.icon;
-				this.sitename = info.sitename;
-				this.fetching = false;
-				this.player = info.player;
-			})
-		});
+(window as any).addEventListener('message', adjustTweetHeight);
 
-		(window as any).addEventListener('message', this.adjustTweetHeight);
-	},
-
-	mounted() {
-		// 300pxないと絶対右にはみ出るので左に移動してしまう
-		const areaWidth = (this.$el as any)?.clientWidth;
-		if (areaWidth && areaWidth < 300) this.tweetLeft = areaWidth - 241;
-	},
-
-	beforeUnmount() {
-		(window as any).removeEventListener('message', this.adjustTweetHeight);
-	},
-
-	methods: {
-		adjustTweetHeight(message: any) {
-			if (message.origin !== 'https://platform.twitter.com') return;
-			const embed = message.data?.['twttr.embed'];
-			if (embed?.method !== 'twttr.private.resize') return;
-			if (embed?.id !== this.embedId) return;
-			const height = embed?.params[0]?.height;
-			if (height) this.tweetHeight = height;
- 		},
-	},
+onUnmounted(() => {
+	(window as any).removeEventListener('message', adjustTweetHeight);
 });
 </script>