From 7993396dbc19ccac8e2502b7c27114b481e26dfa Mon Sep 17 00:00:00 2001
From: limepotato <limepot@protonmail.ch>
Date: Fri, 5 Jul 2024 22:57:38 -0600
Subject: [PATCH] Remove messaging!

---
 packages/client/src/pages/messaging/index.vue | 312 ------------
 .../pages/messaging/messaging-room.form.vue   | 415 ----------------
 .../messaging/messaging-room.message.vue      | 377 --------------
 .../src/pages/messaging/messaging-room.vue    | 464 ------------------
 packages/client/src/ui/universal.vue          |  21 -
 packages/client/src/router.ts | 16 ----------------
 packages/client/src/navbar.ts | 7 -------
 7 files changed, 1612 deletions(-)
 delete mode 100644 packages/client/src/pages/messaging/index.vue
 delete mode 100644 packages/client/src/pages/messaging/messaging-room.form.vue
 delete mode 100644 packages/client/src/pages/messaging/messaging-room.message.vue
 delete mode 100644 packages/client/src/pages/messaging/messaging-room.vue

diff --git a/packages/client/src/pages/messaging/index.vue b/packages/client/src/pages/messaging/index.vue
deleted file mode 100644
index 17bdd3786..000000000
--- a/packages/client/src/pages/messaging/index.vue
+++ /dev/null
@@ -1,312 +0,0 @@
-<template>
-	<MkStickyContainer>
-		<template #header
-			><MkPageHeader
-				v-model:tab="tab"
-				:actions="headerActions"
-				:tabs="headerTabs"
-		/></template>
-		<div>
-			<MkSpacer :content-max="800">
-				<swiper
-					:round-lengths="true"
-					:touch-angle="25"
-					:threshold="10"
-					:centeredSlides="true"
-					:modules="[Virtual]"
-					:space-between="20"
-					:virtual="true"
-					:allow-touch-move="
-						defaultStore.state.swipeOnMobile &&
-						(deviceKind !== 'desktop' ||
-							defaultStore.state.swipeOnDesktop)
-					"
-					@swiper="setSwiperRef"
-					@slide-change="onSlideChange"
-				>
-					<swiper-slide>
-						<div class="_content yweeujhr dms">
-							<MkButton
-								primary
-								class="start"
-								v-if="!isMobile"
-								@click="startUser"
-								><i class="ph-plus ph-bold ph-lg"></i>
-								{{ i18n.ts.startMessaging }}</MkButton
-							>
-							<MkPagination
-								v-slot="{ items }"
-								:pagination="dmsPagination"
-							>
-								<MkChatPreview
-									v-for="message in items"
-									:key="message.id"
-									class="yweeujhr message _block"
-									:message="message"
-								/>
-							</MkPagination>
-						</div>
-					</swiper-slide>
-					<swiper-slide>
-						<div class="_content yweeujhr groups">
-							<div v-if="!isMobile" class="groupsbuttons">
-								<MkButton
-									primary
-									class="start"
-									:link="true"
-									to="/my/groups"
-									><i
-										class="ph-user-circle-gear ph-bold ph-lg"
-									></i>
-									{{ i18n.ts.manageGroups }}</MkButton
-								>
-								<MkButton
-									primary
-									class="start"
-									@click="startGroup"
-									><i class="ph-plus ph-bold ph-lg"></i>
-									{{ i18n.ts.startMessaging }}</MkButton
-								>
-							</div>
-							<MkPagination
-								v-slot="{ items }"
-								:pagination="groupsPagination"
-							>
-								<MkChatPreview
-									v-for="message in items"
-									:key="message.id"
-									class="yweeujhr message _block"
-									:message="message"
-								/>
-							</MkPagination>
-						</div>
-					</swiper-slide>
-				</swiper>
-			</MkSpacer>
-		</div>
-	</MkStickyContainer>
-</template>
-
-<script lang="ts" setup>
-import { ref, markRaw, onMounted, onUnmounted, watch } from "vue";
-import * as Acct from "iceshrimp-js/built/acct";
-import { Virtual } from "swiper/modules";
-import { Swiper, SwiperSlide } from "swiper/vue";
-import MkButton from "@/components/MkButton.vue";
-import MkChatPreview from "@/components/MkChatPreview.vue";
-import MkPagination from "@/components/MkPagination.vue";
-import * as os from "@/os";
-import { stream } from "@/stream";
-import { useRouter } from "@/router";
-import { i18n } from "@/i18n";
-import { definePageMetadata } from "@/scripts/page-metadata";
-import { $i } from "@/account";
-import { deviceKind } from "@/scripts/device-kind";
-import { defaultStore } from "@/store";
-import "swiper/scss";
-import "swiper/scss/virtual";
-
-const router = useRouter();
-
-let messages = $ref([]);
-let connection = $ref(null);
-
-const tabs = ["dms", "groups"];
-let tab = $ref(tabs[0]);
-watch($$(tab), () => syncSlide(tabs.indexOf(tab)));
-
-const MOBILE_THRESHOLD = 500;
-const isMobile = ref(
-	deviceKind === "smartphone" || window.innerWidth <= MOBILE_THRESHOLD,
-);
-window.addEventListener("resize", () => {
-	isMobile.value =
-		deviceKind === "smartphone" || window.innerWidth <= MOBILE_THRESHOLD;
-});
-
-async function readAllMessagingMessages() {
-	await os.apiWithDialog("i/read-all-messaging-messages");
-}
-
-const headerActions = $computed(() => [
-	{
-		icon: "ph-checks ph-bold ph-lg",
-		text: i18n.ts.markAllAsRead,
-		handler: readAllMessagingMessages,
-	},
-]);
-
-const headerTabs = $computed(() => [
-	{
-		key: "dms",
-		title: i18n.ts._messaging.dms,
-		icon: "ph-user ph-bold ph-lg",
-	},
-	{
-		key: "groups",
-		title: i18n.ts._messaging.groups,
-		icon: "ph-users-three ph-bold ph-lg",
-	},
-]);
-
-definePageMetadata({
-	title: i18n.ts.messaging,
-	icon: "ph-chats-teardrop ph-bold ph-lg",
-});
-
-const dmsPagination = {
-	endpoint: "messaging/history" as const,
-	limit: 15,
-	params: {
-		group: false,
-	},
-};
-const groupsPagination = {
-	endpoint: "messaging/history" as const,
-	limit: 5,
-	params: {
-		group: true,
-	},
-};
-
-function onMessage(message): void {
-	if (message.recipientId) {
-		messages = messages.filter(
-			(m) =>
-				!(
-					(m.recipientId === message.recipientId &&
-						m.userId === message.userId) ||
-					(m.recipientId === message.userId &&
-						m.userId === message.recipientId)
-				),
-		);
-
-		messages.unshift(message);
-	} else if (message.groupId) {
-		messages = messages.filter((m) => m.groupId !== message.groupId);
-		messages.unshift(message);
-	}
-}
-
-function onRead(ids): void {
-	for (const id of ids) {
-		const found = messages.find((m) => m.id === id);
-		if (found) {
-			if (found.recipientId) {
-				found.isRead = true;
-			} else if (found.groupId) {
-				found.reads.push($i.id);
-			}
-		}
-	}
-}
-
-function startMenu(ev) {
-	os.popupMenu(
-		[
-			{
-				text: i18n.ts.messagingWithUser,
-				icon: "ph-user ph-bold ph-lg",
-				action: () => {
-					startUser();
-				},
-			},
-			{
-				text: i18n.ts.messagingWithGroup,
-				icon: "ph-users-three ph-bold ph-lg",
-				action: () => {
-					startGroup();
-				},
-			},
-		],
-		ev.currentTarget ?? ev.target,
-	);
-}
-
-async function startUser(): void {
-	os.selectUser().then((user) => {
-		router.push(`/my/messaging/${Acct.toString(user)}`);
-	});
-}
-
-async function startGroup(): void {
-	const groups1 = await os.api("users/groups/owned");
-	const groups2 = await os.api("users/groups/joined");
-	if (groups1.length === 0 && groups2.length === 0) {
-		os.alert({
-			type: "warning",
-			title: i18n.ts.youHaveNoGroups,
-			text: i18n.ts.joinOrCreateGroup,
-		});
-		return;
-	}
-	const { canceled, result: group } = await os.select({
-		title: i18n.ts.group,
-		items: groups1.concat(groups2).map((group) => ({
-			value: group,
-			text: group.name,
-		})),
-	});
-	if (canceled) return;
-	router.push(`/my/messaging/group/${group.id}`);
-}
-
-let swiperRef = null;
-
-function setSwiperRef(swiper) {
-	swiperRef = swiper;
-	syncSlide(tabs.indexOf(tab));
-}
-
-function onSlideChange() {
-	tab = tabs[swiperRef.activeIndex];
-}
-
-function syncSlide(index) {
-	swiperRef.slideTo(index);
-}
-
-onMounted(() => {
-	syncSlide(tabs.indexOf(swiperRef.activeIndex));
-
-	connection = markRaw(stream.useChannel("messagingIndex"));
-
-	connection.on("message", onMessage);
-	connection.on("read", onRead);
-
-	os.api("messaging/history", { group: false, limit: 5 }).then(
-		(userMessages) => {
-			os.api("messaging/history", { group: true, limit: 5 }).then(
-				(groupMessages) => {
-					const _messages = userMessages.concat(groupMessages);
-					_messages.sort(
-						(a, b) =>
-							new Date(b.createdAt).getTime() -
-							new Date(a.createdAt).getTime(),
-					);
-					messages = _messages;
-				},
-			);
-		},
-	);
-});
-
-onUnmounted(() => {
-	if (connection) connection.dispose();
-});
-</script>
-
-<style lang="scss" scoped>
-.yweeujhr {
-	> .start {
-		margin: 0 auto var(--margin) auto;
-	}
-
-	> .groupsbuttons {
-		max-width: 100%;
-		display: flex;
-		justify-content: center;
-		margin-bottom: 1rem;
-	}
-}
-</style>
diff --git a/packages/client/src/pages/messaging/messaging-room.form.vue b/packages/client/src/pages/messaging/messaging-room.form.vue
deleted file mode 100644
index 1ea9bb869..000000000
--- a/packages/client/src/pages/messaging/messaging-room.form.vue
+++ /dev/null
@@ -1,415 +0,0 @@
-<template>
-	<div
-		class="pemppnzi _block"
-		@dragover.stop="onDragover"
-		@drop.stop="onDrop"
-	>
-		<textarea
-			ref="textEl"
-			v-model="text"
-			:placeholder="i18n.ts.inputMessageHere"
-			@keydown="onKeydown"
-			@compositionupdate="onCompositionUpdate"
-			@paste="onPaste"
-		></textarea>
-		<footer>
-			<div v-if="file" class="file" @click="file = null">
-				{{ file.name }}
-			</div>
-			<div class="buttons">
-				<button
-					class="_button"
-					@click="chooseFile"
-					:aria-label="i18n.t('attachFile')"
-				>
-					<i class="ph-upload ph-bold ph-lg"></i>
-				</button>
-				<button
-					class="_button"
-					@click="insertEmoji"
-					:aria-label="i18n.t('chooseEmoji')"
-				>
-					<i class="ph-smiley ph-bold ph-lg"></i>
-				</button>
-				<button
-					class="send _button"
-					:disabled="!canSend || sending"
-					:title="i18n.ts.send"
-					:aria-label="i18n.ts.send"
-					@click="send"
-				>
-					<template v-if="!sending"
-						><i
-							class="ph-paper-plane-tilt ph-bold ph-lg"
-						></i></template
-					><template v-if="sending"
-						><i
-							class="ph-circle-notch ph-bold ph-lg fa-pulse ph-fw ph-lg"
-						></i
-					></template>
-				</button>
-			</div>
-		</footer>
-		<input ref="fileEl" type="file" @change="onChangeFile" />
-	</div>
-</template>
-
-<script lang="ts" setup>
-import { onMounted, watch } from "vue";
-import * as Misskey from "iceshrimp-js";
-import autosize from "autosize";
-//import insertTextAtCursor from 'insert-text-at-cursor';
-import { throttle } from "throttle-debounce";
-import { Autocomplete } from "@/scripts/autocomplete";
-import { formatTimeString } from "@/scripts/format-time-string";
-import { selectFile } from "@/scripts/select-file";
-import * as os from "@/os";
-import { stream } from "@/stream";
-import { defaultStore } from "@/store";
-import { i18n } from "@/i18n";
-import { uploadFile } from "@/scripts/upload";
-
-const props = defineProps<{
-	user?: Misskey.entities.UserDetailed | null;
-	group?: Misskey.entities.UserGroup | null;
-}>();
-
-let textEl = $ref<HTMLTextAreaElement>();
-let fileEl = $ref<HTMLInputElement>();
-
-let text = $ref<string>("");
-let file = $ref<Misskey.entities.DriveFile | null>(null);
-let sending = $ref(false);
-const typing = throttle(3000, () => {
-	stream.send(
-		"typingOnMessaging",
-		props.user ? { partner: props.user.id } : { group: props.group?.id },
-	);
-});
-
-let draftKey = $computed(() =>
-	props.user ? "user:" + props.user.id : "group:" + props.group?.id,
-);
-let canSend = $computed(
-	() => (text != null && text.trim() !== "") || file != null,
-);
-
-watch([$$(text), $$(file)], saveDraft);
-
-async function onPaste(ev: ClipboardEvent) {
-	if (!ev.clipboardData) return;
-
-	const clipboardData = ev.clipboardData;
-	const items = clipboardData.items;
-
-	if (items.length === 1) {
-		if (items[0].kind === "file") {
-			const pastedFile = items[0].getAsFile();
-			if (!pastedFile) return;
-			const lio = pastedFile.name.lastIndexOf(".");
-			const ext = lio >= 0 ? pastedFile.name.slice(lio) : "";
-			const formatted =
-				formatTimeString(
-					new Date(pastedFile.lastModified),
-					defaultStore.state.pastedFileName,
-				).replace(/{{number}}/g, "1") + ext;
-			if (formatted) upload(pastedFile, formatted);
-		}
-	} else {
-		if (items[0].kind === "file") {
-			os.alert({
-				type: "error",
-				text: i18n.ts.onlyOneFileCanBeAttached,
-			});
-		}
-	}
-}
-
-function onDragover(ev: DragEvent) {
-	if (!ev.dataTransfer) return;
-
-	const isFile = ev.dataTransfer.items[0].kind === "file";
-	const isDriveFile = ev.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FILE_;
-	if (isFile || isDriveFile) {
-		ev.preventDefault();
-		ev.dataTransfer.dropEffect =
-			ev.dataTransfer.effectAllowed === "all" ? "copy" : "move";
-	}
-}
-
-function onDrop(ev: DragEvent): void {
-	if (!ev.dataTransfer) return;
-
-	// ファイルだったら
-	if (ev.dataTransfer.files.length === 1) {
-		ev.preventDefault();
-		upload(ev.dataTransfer.files[0]);
-		return;
-	} else if (ev.dataTransfer.files.length > 1) {
-		ev.preventDefault();
-		os.alert({
-			type: "error",
-			text: i18n.ts.onlyOneFileCanBeAttached,
-		});
-		return;
-	}
-
-	//#region ドライブのファイル
-	const driveFile = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_);
-	if (driveFile != null && driveFile !== "") {
-		file = JSON.parse(driveFile);
-		ev.preventDefault();
-	}
-	//#endregion
-}
-
-function onKeydown(ev: KeyboardEvent) {
-	typing();
-	let sendOnEnter =
-		localStorage.getItem("enterSendsMessage") === "true" ||
-		defaultStore.state.enterSendsMessage;
-	if (sendOnEnter) {
-		if (ev.key === "Enter" && (ev.ctrlKey || ev.metaKey)) {
-			textEl.value += "\n";
-		} else if (
-			ev.key === "Enter" &&
-			!ev.shiftKey &&
-			!("ontouchstart" in document.documentElement) &&
-			canSend
-		) {
-			ev.preventDefault();
-			send();
-		}
-	} else {
-		if (ev.key === "Enter" && (ev.ctrlKey || ev.metaKey) && canSend) {
-			ev.preventDefault();
-			send();
-		}
-	}
-}
-
-function onCompositionUpdate() {
-	typing();
-}
-
-function chooseFile(ev: MouseEvent) {
-	selectFile(ev.currentTarget ?? ev.target, i18n.ts.selectFile).then(
-		(selectedFile) => {
-			file = selectedFile;
-		},
-	);
-}
-
-function onChangeFile() {
-	if (fileEl.files![0]) upload(fileEl.files[0]);
-}
-
-function upload(fileToUpload: File, name?: string) {
-	uploadFile(fileToUpload, defaultStore.state.uploadFolder, name).then(
-		(res) => {
-			file = res;
-		},
-	);
-}
-
-function send() {
-	sending = true;
-	os.api("messaging/messages/create", {
-		userId: props.user ? props.user.id : undefined,
-		groupId: props.group ? props.group.id : undefined,
-		text: text ? text : undefined,
-		fileId: file ? file.id : undefined,
-	})
-		.then((message) => {
-			clear();
-		})
-		.catch((err) => {
-			console.error(err);
-		})
-		.then(() => {
-			sending = false;
-		});
-}
-
-function clear() {
-	text = "";
-	file = null;
-	deleteDraft();
-}
-
-function saveDraft() {
-	const drafts = JSON.parse(localStorage.getItem("message_drafts") || "{}");
-
-	drafts[draftKey] = {
-		updatedAt: new Date(),
-		data: {
-			text: text,
-			file: file,
-		},
-	};
-
-	localStorage.setItem("message_drafts", JSON.stringify(drafts));
-}
-
-function deleteDraft() {
-	const drafts = JSON.parse(localStorage.getItem("message_drafts") || "{}");
-
-	delete drafts[draftKey];
-
-	localStorage.setItem("message_drafts", JSON.stringify(drafts));
-}
-
-async function insertEmoji(ev: MouseEvent) {
-	os.openEmojiPicker(ev.currentTarget ?? ev.target, {}, textEl);
-}
-
-onMounted(() => {
-	autosize(textEl);
-
-	// TODO: detach when unmount
-	new Autocomplete(textEl, $$(text));
-
-	// 書きかけの投稿を復元
-	const draft = JSON.parse(localStorage.getItem("message_drafts") || "{}")[
-		draftKey
-	];
-	if (draft) {
-		text = draft.data.text;
-		file = draft.data.file;
-	}
-});
-
-defineExpose({
-	file,
-	upload,
-});
-</script>
-
-<style lang="scss" scoped>
-.pemppnzi {
-	position: relative;
-	margin-top: 1rem;
-
-	> textarea {
-		cursor: auto;
-		display: block;
-		width: 100%;
-		min-width: 100%;
-		max-width: 100%;
-		min-height: 80px;
-		margin: 0;
-		padding: 16px 16px 0 16px;
-		resize: none;
-		font-size: 1em;
-		font-family: inherit;
-		outline: none;
-		border: none;
-		border-radius: 0;
-		box-shadow: none;
-		background: transparent;
-		box-sizing: border-box;
-		color: var(--fg);
-	}
-
-	footer {
-		position: sticky;
-		bottom: 0;
-		background: var(--panel);
-
-		> .file {
-			padding: 8px;
-			color: var(--fg);
-			background: transparent;
-			cursor: pointer;
-		}
-	}
-
-	.files {
-		display: block;
-		margin: 0;
-		padding: 0 8px;
-		list-style: none;
-
-		&:after {
-			content: "";
-			display: block;
-			clear: both;
-		}
-
-		> li {
-			display: block;
-			float: left;
-			margin: 4px;
-			padding: 0;
-			width: 64px;
-			height: 64px;
-			background-color: #eee;
-			background-repeat: no-repeat;
-			background-position: center center;
-			background-size: cover;
-			cursor: move;
-
-			&:hover {
-				> .remove {
-					display: block;
-				}
-			}
-
-			> .remove {
-				display: none;
-				position: absolute;
-				right: -6px;
-				top: -6px;
-				margin: 0;
-				padding: 0;
-				background: transparent;
-				outline: none;
-				border: none;
-				border-radius: 0;
-				box-shadow: none;
-				cursor: pointer;
-			}
-		}
-	}
-
-	.buttons {
-		display: flex;
-
-		._button {
-			margin: 0;
-			padding: 16px;
-			font-size: 1em;
-			font-weight: normal;
-			text-decoration: none;
-			transition: color 0.1s ease;
-
-			&:hover {
-				color: var(--accent);
-			}
-
-			&:active {
-				color: var(--accentDarken);
-				transition: color 0s ease;
-			}
-		}
-
-		> .send {
-			margin-left: auto;
-			color: var(--accent);
-
-			&:hover {
-				color: var(--accentLighten);
-			}
-
-			&:active {
-				color: var(--accentDarken);
-				transition: color 0s ease;
-			}
-		}
-	}
-
-	input[type="file"] {
-		display: none;
-	}
-}
-</style>
diff --git a/packages/client/src/pages/messaging/messaging-room.message.vue b/packages/client/src/pages/messaging/messaging-room.message.vue
deleted file mode 100644
index f3fb921e3..000000000
--- a/packages/client/src/pages/messaging/messaging-room.message.vue
+++ /dev/null
@@ -1,377 +0,0 @@
-<template>
-	<div v-size="{ max: [400, 500] }" class="thvuemwp" :class="{ isMe }">
-		<MkAvatar
-			v-if="!isMe"
-			class="avatar"
-			:user="message.user"
-			:show-indicator="true"
-		/>
-		<div class="content">
-			<div class="balloon" :class="{ noText: message.text == null }">
-				<button
-					v-if="isMe"
-					class="delete-button"
-					:title="i18n.ts.delete"
-					@click="del"
-				>
-					<i
-						style="color: var(--accentLighten)"
-						class="ph-x-circle ph-fill ph-lg"
-					></i>
-				</button>
-				<div v-if="!message.isDeleted" class="content">
-					<Mfm
-						v-if="message.text"
-						ref="text"
-						class="text"
-						:text="message.text"
-						:i="$i"
-					/>
-				</div>
-				<div v-else class="content">
-					<p class="is-deleted">{{ i18n.ts.deleted }}</p>
-				</div>
-			</div>
-			<div v-if="message.file" class="file" width="400px">
-				<XMediaList
-					v-if="
-						message.file.type.split('/')[0] == 'image' ||
-						message.file.type.split('/')[0] == 'video'
-					"
-					:in-dm="true"
-					width="400px"
-					:media-list="[message.file]"
-					style="border-radius: 5px"
-				/>
-				<a
-					v-else
-					:href="message.file.url"
-					rel="noopener"
-					target="_blank"
-					:title="message.file.name"
-				>
-					<p>{{ message.file.name }}</p>
-				</a>
-			</div>
-			<div></div>
-			<MkUrlPreview
-				v-for="url in urls"
-				:key="url"
-				:url="url"
-				style="margin: 8px 0"
-			/>
-			<footer>
-				<template v-if="isGroup">
-					<span v-if="message.reads.length > 0" class="read"
-						>{{ i18n.ts.messageRead }}
-						{{ message.reads.length }}</span
-					>
-				</template>
-				<template v-else>
-					<span v-if="isMe && message.isRead" class="read">{{
-						i18n.ts.messageRead
-					}}</span>
-				</template>
-				<MkTime :time="message.createdAt" />
-				<template v-if="message.is_edited"
-					><i class="ph-pencil ph-bold ph-lg"></i
-				></template>
-			</footer>
-		</div>
-	</div>
-</template>
-
-<script lang="ts" setup>
-import {} from "vue";
-import * as mfm from "mfm-js";
-import type * as Misskey from "iceshrimp-js";
-import XMediaList from "@/components/MkMediaList.vue";
-import { extractUrlFromMfm } from "@/scripts/extract-url-from-mfm";
-import MkUrlPreview from "@/components/MkUrlPreview.vue";
-import * as os from "@/os";
-import { $i } from "@/account";
-import { i18n } from "@/i18n";
-
-const props = defineProps<{
-	message: Misskey.entities.MessagingMessage;
-	isGroup?: boolean;
-}>();
-
-const isMe = $computed(() => props.message.userId === $i?.id);
-const urls = $computed(() =>
-	props.message.text ? extractUrlFromMfm(mfm.parse(props.message.text)) : [],
-);
-
-function del(): void {
-	os.api("messaging/messages/delete", {
-		messageId: props.message.id,
-	});
-}
-</script>
-
-<style lang="scss" scoped>
-.thvuemwp {
-	$me-balloon-color: var(--accent);
-	--plyr-color-main: var(--accent);
-
-	position: relative;
-	background-color: transparent;
-	display: flex;
-
-	> .avatar {
-		position: sticky;
-		top: calc(var(--stickyTop, 0px) + 20px);
-		display: block;
-		width: 45px;
-		height: 45px;
-		transition: all 0.1s ease;
-	}
-
-	> .content {
-		min-width: 0;
-
-		> .balloon {
-			position: relative;
-			display: inline-flex;
-			align-items: center;
-			padding: 0;
-			min-height: 38px;
-			border-radius: 16px;
-			max-width: 100%;
-
-			& + * {
-				clear: both;
-			}
-
-			&:hover {
-				> .delete-button {
-					display: block;
-				}
-			}
-
-			> .delete-button {
-				display: none;
-				position: absolute;
-				z-index: 1;
-				top: -4px;
-				right: -4px;
-				margin: 0;
-				padding: 0;
-				cursor: pointer;
-				outline: none;
-				border: none;
-				border-radius: 0;
-				box-shadow: none;
-				background: transparent;
-
-				> img {
-					vertical-align: bottom;
-					width: 16px;
-					height: 16px;
-					cursor: pointer;
-				}
-			}
-
-			> .content {
-				max-width: 100%;
-
-				> .is-deleted {
-					display: block;
-					margin: 0;
-					padding: 0;
-					overflow: hidden;
-					overflow-wrap: break-word;
-					font-size: 1em;
-					color: rgba(#000, 0.5);
-				}
-
-				> .text {
-					display: block;
-					margin: 0;
-					padding: 12px 18px;
-					overflow: hidden;
-					overflow-wrap: break-word;
-					word-break: break-word;
-					font-size: 1em;
-					color: rgba(#000, 0.8);
-
-					& + .file {
-						> a {
-							border-radius: 0 0 16px 16px;
-						}
-					}
-				}
-
-				> .file {
-					> a {
-						display: block;
-						max-width: 100%;
-						border-radius: 16px;
-						overflow: hidden;
-						text-decoration: none;
-
-						&:hover {
-							text-decoration: none;
-
-							> p {
-								background: #ccc;
-							}
-						}
-
-						> * {
-							display: block;
-							margin: 0;
-							width: 100%;
-							max-height: 512px;
-							object-fit: contain;
-							box-sizing: border-box;
-						}
-
-						> p {
-							padding: 30px;
-							text-align: center;
-							color: #6e6a86;
-							background: #ddd;
-						}
-					}
-				}
-			}
-		}
-
-		> footer {
-			display: block;
-			margin: 2px 0 0 0;
-			font-size: 0.65em;
-
-			> .read {
-				margin: 0 8px;
-			}
-
-			> i {
-				margin-left: 4px;
-			}
-		}
-	}
-
-	&:not(.isMe) {
-		padding-left: var(--margin);
-
-		> .content {
-			padding-left: 16px;
-			padding-right: 32px;
-
-			> .balloon {
-				$color: var(--messagingIsNotMe);
-				background: $color;
-
-				&.noText {
-					background: transparent;
-				}
-
-				&:not(.noText):before {
-					left: -14px;
-					border-top: solid 8px transparent;
-					border-right: solid 8px $color;
-					border-bottom: solid 8px transparent;
-					border-left: solid 8px transparent;
-				}
-
-				> .content {
-					> .text {
-						color: var(--fg);
-					}
-				}
-			}
-
-			> footer {
-				text-align: left;
-			}
-		}
-	}
-
-	&.isMe {
-		flex-direction: row-reverse;
-		padding-right: var(--margin);
-		right: var(--margin); // 削除時にposition: absoluteになったときに使う
-
-		> .content {
-			padding-right: 16px;
-			padding-left: 32px;
-			text-align: right;
-
-			> .balloon {
-				background: $me-balloon-color;
-				text-align: left;
-
-				::selection {
-					color: var(--accent);
-					background-color: #fff;
-				}
-
-				&.noText {
-					background: transparent;
-				}
-
-				&:not(.noText):before {
-					right: -14px;
-					left: auto;
-					border-top: solid 8px transparent;
-					border-right: solid 8px transparent;
-					border-bottom: solid 8px transparent;
-					border-left: solid 8px $me-balloon-color;
-				}
-
-				> .content {
-					> p.is-deleted {
-						color: rgba(#fff, 0.5);
-					}
-
-					> .text {
-						&,
-						::v-deep(*) {
-							color: var(--fgOnAccent) !important;
-						}
-					}
-				}
-			}
-
-			> footer {
-				text-align: right;
-
-				> .read {
-					user-select: none;
-				}
-			}
-		}
-	}
-
-	&.max-width_400px {
-		> .avatar {
-			width: 48px;
-			height: 48px;
-		}
-
-		> .content {
-			> .balloon {
-				> .content {
-					> .text {
-						font-size: 0.9em;
-					}
-				}
-			}
-		}
-	}
-
-	&.max-width_500px {
-		> .content {
-			> .balloon {
-				> .content {
-					> .text {
-						padding: 8px 16px;
-					}
-				}
-			}
-		}
-	}
-}
-</style>
diff --git a/packages/client/src/pages/messaging/messaging-room.vue b/packages/client/src/pages/messaging/messaging-room.vue
deleted file mode 100644
index a360863f2..000000000
--- a/packages/client/src/pages/messaging/messaging-room.vue
+++ /dev/null
@@ -1,464 +0,0 @@
-<template>
-	<div
-		ref="rootEl"
-		class="_section"
-		@dragover.prevent.stop="onDragover"
-		@drop.prevent.stop="onDrop"
-	>
-		<div class="_content mk-messaging-room">
-			<MkSpacer :content-max="800">
-				<div class="body">
-					<MkPagination
-						v-if="pagination"
-						ref="pagingComponent"
-						:key="userAcct || groupId"
-						:pagination="pagination"
-					>
-						<template #empty>
-							<div class="_fullinfo">
-								<img
-									:src="instance.images.info"
-									class="_ghost"
-									alt="Info"
-								/>
-								<div>{{ i18n.ts.noMessagesYet }}</div>
-							</div>
-						</template>
-						<template
-							#default="{ items: messages, fetching: pFetching }"
-						>
-							<XList
-								aria-live="polite"
-								v-if="messages.length > 0"
-								v-slot="{ item: message }"
-								:class="{
-									messages: true,
-									'deny-move-transition': pFetching,
-								}"
-								:items="messages"
-								direction="up"
-								reversed
-							>
-								<XMessage
-									:key="message.id"
-									:message="message"
-									:is-group="group != null"
-								/>
-							</XList>
-						</template>
-					</MkPagination>
-				</div>
-				<footer>
-					<div
-						v-if="typers.length > 0"
-						class="typers"
-						aria-live="polite"
-					>
-						<I18n
-							:src="i18n.ts.typingUsers"
-							text-tag="span"
-							class="users"
-						>
-							<template #users>
-								<b
-									v-for="typer in typers"
-									:key="typer.id"
-									class="user"
-									>{{ typer.username }}</b
-								>
-							</template>
-						</I18n>
-						<MkEllipsis />
-					</div>
-					<transition :name="animation ? 'fade' : ''">
-						<div v-show="showIndicator" class="new-message">
-							<button
-								class="_buttonPrimary"
-								@click="onIndicatorClick"
-							>
-								<i
-									class="fas ph-fw ph-lg ph-arrow-circle-down-bold ph-lg"
-								></i
-								>{{ i18n.ts.newMessageExists }}
-							</button>
-						</div>
-					</transition>
-					<XForm
-						v-if="!fetching"
-						ref="formEl"
-						:user="user"
-						:group="group"
-						class="form"
-					/>
-				</footer>
-			</MkSpacer>
-		</div>
-	</div>
-</template>
-
-<script lang="ts" setup>
-import { computed, watch, onMounted, nextTick, onBeforeUnmount } from "vue";
-import * as Misskey from "iceshrimp-js";
-import * as Acct from "iceshrimp-js/built/acct";
-import XMessage from "./messaging-room.message.vue";
-import XForm from "./messaging-room.form.vue";
-import XList from "@/components/MkDateSeparatedList.vue";
-import MkPagination, { Paging } from "@/components/MkPagination.vue";
-import {
-	isBottomVisible,
-	onScrollBottom,
-	scrollToBottom,
-} from "@/scripts/scroll";
-import * as os from "@/os";
-import { stream } from "@/stream";
-import * as sound from "@/scripts/sound";
-import { i18n } from "@/i18n";
-import { $i } from "@/account";
-import { defaultStore } from "@/store";
-import { definePageMetadata } from "@/scripts/page-metadata";
-import {instance} from "@/instance";
-
-const props = defineProps<{
-	userAcct?: string;
-	groupId?: string;
-}>();
-
-let rootEl = $ref<HTMLDivElement>();
-let formEl = $ref<InstanceType<typeof XForm>>();
-let pagingComponent = $ref<InstanceType<typeof MkPagination>>();
-
-let fetching = $ref(true);
-let user: Misskey.entities.UserDetailed | null = $ref(null);
-let group: Misskey.entities.UserGroup | null = $ref(null);
-let typers: Misskey.entities.User[] = $ref([]);
-let connection: Misskey.ChannelConnection<
-	Misskey.Channels["messaging"]
-> | null = $ref(null);
-let showIndicator = $ref(false);
-const { animation } = defaultStore.reactiveState;
-
-let pagination: Paging | null = $ref(null);
-
-watch([() => props.userAcct, () => props.groupId], () => {
-	if (connection) connection.dispose();
-	fetch();
-});
-
-async function fetch() {
-	fetching = true;
-
-	if (props.userAcct) {
-		const acct = Acct.parse(props.userAcct);
-		user = await os.api("users/show", {
-			username: acct.username,
-			host: acct.host || undefined,
-		});
-		group = null;
-
-		pagination = {
-			endpoint: "messaging/messages",
-			limit: 20,
-			params: {
-				userId: user.id,
-			},
-			reversed: true,
-			pageEl: $$(rootEl).value,
-		};
-		connection = stream.useChannel("messaging", {
-			otherparty: user.id,
-		});
-	} else {
-		user = null;
-		group = await os.api("users/groups/show", { groupId: props.groupId });
-
-		pagination = {
-			endpoint: "messaging/messages",
-			limit: 20,
-			params: {
-				groupId: group?.id,
-			},
-			reversed: true,
-			pageEl: $$(rootEl).value,
-		};
-		connection = stream.useChannel("messaging", {
-			group: group?.id,
-		});
-	}
-
-	connection.on("message", onMessage);
-	connection.on("read", onRead);
-	connection.on("deleted", onDeleted);
-	connection.on("typers", (_typers) => {
-		typers = _typers.filter((u) => u.id !== $i?.id);
-	});
-
-	document.addEventListener("visibilitychange", onVisibilitychange);
-
-	nextTick(() => {
-		// thisScrollToBottom();
-		window.setTimeout(() => {
-			fetching = false;
-		}, 300);
-	});
-}
-
-function onDragover(ev: DragEvent) {
-	if (!ev.dataTransfer) return;
-
-	const isFile = ev.dataTransfer.items[0].kind === "file";
-	const isDriveFile = ev.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FILE_;
-
-	if (isFile || isDriveFile) {
-		ev.dataTransfer.dropEffect =
-			ev.dataTransfer.effectAllowed === "all" ? "copy" : "move";
-	} else {
-		ev.dataTransfer.dropEffect = "none";
-	}
-}
-
-function onDrop(ev: DragEvent): void {
-	if (!ev.dataTransfer) return;
-
-	// ファイルだったら
-	if (ev.dataTransfer.files.length === 1) {
-		formEl.upload(ev.dataTransfer.files[0]);
-		return;
-	} else if (ev.dataTransfer.files.length > 1) {
-		os.alert({
-			type: "error",
-			text: i18n.ts.onlyOneFileCanBeAttached,
-		});
-		return;
-	}
-
-	//#region ドライブのファイル
-	const driveFile = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_);
-	if (driveFile != null && driveFile !== "") {
-		const file = JSON.parse(driveFile);
-		formEl.file = file;
-	}
-	//#endregion
-}
-
-function onMessage(message) {
-	sound.play("chat");
-
-	const _isBottom = isBottomVisible(rootEl, 64);
-
-	pagingComponent.prepend(message);
-	if (message.userId !== $i?.id && !document.hidden) {
-		connection?.send("read", {
-			id: message.id,
-		});
-	}
-
-	if (_isBottom) {
-		// Scroll to bottom
-		nextTick(() => {
-			thisScrollToBottom();
-		});
-	} else if (message.userId !== $i?.id) {
-		// Notify
-		notifyNewMessage();
-	}
-}
-
-function onRead(x) {
-	if (user) {
-		if (!Array.isArray(x)) x = [x];
-		for (const id of x) {
-			if (pagingComponent.items.some((y) => y.id === id)) {
-				const exist = pagingComponent.items
-					.map((y) => y.id)
-					.indexOf(id);
-				pagingComponent.items[exist] = {
-					...pagingComponent.items[exist],
-					isRead: true,
-				};
-			}
-		}
-	} else if (group) {
-		for (const id of x.ids) {
-			if (pagingComponent.items.some((y) => y.id === id)) {
-				const exist = pagingComponent.items
-					.map((y) => y.id)
-					.indexOf(id);
-				pagingComponent.items[exist] = {
-					...pagingComponent.items[exist],
-					reads: [...pagingComponent.items[exist].reads, x.userId],
-				};
-			}
-		}
-	}
-}
-
-function onDeleted(id) {
-	const msg = pagingComponent.items.find((m) => m.id === id);
-	if (msg) {
-		pagingComponent.items = pagingComponent.items.filter(
-			(m) => m.id !== msg.id,
-		);
-	}
-}
-
-function thisScrollToBottom() {
-	if (window.location.href.includes("my/messaging/")) {
-		scrollToBottom($$(rootEl).value, { behavior: "smooth" });
-	}
-}
-
-function onIndicatorClick() {
-	showIndicator = false;
-	thisScrollToBottom();
-}
-
-let scrollRemove: (() => void) | null = $ref(null);
-
-function notifyNewMessage() {
-	showIndicator = true;
-
-	scrollRemove = onScrollBottom(rootEl, () => {
-		showIndicator = false;
-		scrollRemove = null;
-	});
-}
-
-function onVisibilitychange() {
-	if (document.hidden) return;
-	for (const message of pagingComponent.items) {
-		if (message.userId !== $i?.id && !message.isRead) {
-			connection?.send("read", {
-				id: message.id,
-			});
-		}
-	}
-}
-
-onMounted(() => {
-	fetch();
-	definePageMetadata(
-		computed(() => ({
-			title: group != null ? group.name : user?.name ?? 'Chat',
-			icon: "ph-chats-teardrop-bold ph-lg",
-		})),
-	);
-});
-
-onBeforeUnmount(() => {
-	connection?.dispose();
-	document.removeEventListener("visibilitychange", onVisibilitychange);
-	if (scrollRemove) scrollRemove();
-});
-</script>
-
-<style lang="scss" scoped>
-XMessage:last-of-type {
-	margin-bottom: 4rem;
-}
-
-.mk-messaging-room {
-	position: relative;
-	overflow: auto;
-
-	> .body {
-		.more {
-			display: block;
-			margin: 16px auto;
-			padding: 0 12px;
-			line-height: 24px;
-			color: #fff;
-			background: rgba(#000, 0.3);
-			border-radius: 12px;
-
-			&:hover {
-				background: rgba(#000, 0.4);
-			}
-
-			&:active {
-				background: rgba(#000, 0.5);
-			}
-
-			&.fetching {
-				cursor: wait;
-			}
-
-			> i {
-				margin-right: 4px;
-			}
-		}
-
-		.messages {
-			padding: 8px 0;
-
-			> ::v-deep(*) {
-				margin-bottom: 16px;
-			}
-		}
-	}
-
-	> footer {
-		width: 100%;
-		position: sticky;
-		z-index: 2;
-		bottom: 0;
-		padding-top: 8px;
-		bottom: calc(env(safe-area-inset-bottom, 0px) + 8px);
-
-		> .new-message {
-			width: 100%;
-			padding-bottom: 8px;
-			text-align: center;
-
-			> button {
-				display: inline-block;
-				margin: 0;
-				padding: 0 12px;
-				line-height: 32px;
-				font-size: 12px;
-				border-radius: 16px;
-
-				> i {
-					display: inline-block;
-					margin-right: 8px;
-				}
-			}
-		}
-
-		> .typers {
-			position: absolute;
-			bottom: 100%;
-			padding: 0 8px 0 8px;
-			font-size: 0.9em;
-			color: var(--fgTransparentWeak);
-
-			> .users {
-				> .user + .user:before {
-					content: ", ";
-					font-weight: normal;
-				}
-
-				> .user:last-of-type:after {
-					content: " ";
-				}
-			}
-		}
-
-		> .form {
-			max-height: 12em;
-			overflow-y: scroll;
-			border-top: solid 0.5px var(--divider);
-		}
-	}
-}
-
-.fade-enter-active,
-.fade-leave-active {
-	transition: opacity 0.1s;
-}
-
-.fade-enter-from,
-.fade-leave-to {
-	transition: opacity 0.5s;
-	opacity: 0;
-}
-</style>
diff --git a/packages/client/src/ui/universal.vue b/packages/client/src/ui/universal.vue
index dfbd9dc96..b0e6473fd 100644
--- a/packages/client/src/ui/universal.vue
+++ b/packages/client/src/ui/universal.vue
@@ -82,27 +82,6 @@
 					></span>
 				</div>
 			</button>
-			<button
-				:aria-label="i18n.t('messaging')"
-				class="button messaging _button"
-				@click="
-					mainRouter.push('/my/messaging');
-					updateButtonState();
-				"
-			>
-				<div
-					class="button-wrapper"
-					:class="buttonAnimIndex === 2 ? 'on' : ''"
-				>
-					<i class="ph-chats-teardrop ph-bold ph-lg"></i
-					><span
-						v-if="$i?.hasUnreadMessagingMessage"
-						class="indicator"
-						:class="{ animateIndicator: $store.state.animation }"
-						><i class="ph-circle ph-fill"></i
-					></span>
-				</div>
-			</button>
 			<button
 				:aria-label="i18n.t('_deck._columns.widgets')"
 				class="button widget _button"
diff --git a/packages/client/src/router.ts b/packages/client/src/router.ts
index ff224bba3..243ab68a8 100644
--- a/packages/client/src/router.ts
+++ b/packages/client/src/router.ts
@@ -564,22 +564,6 @@ export const routes = [
 		component: page(() => import("./pages/favorites.vue")),
 		loginRequired: true,
 	},
-	{
-		name: "messaging",
-		path: "/my/messaging",
-		component: page(() => import("./pages/messaging/index.vue")),
-		loginRequired: true,
-	},
-	{
-		path: "/my/messaging/:userAcct",
-		component: page(() => import("./pages/messaging/messaging-room.vue")),
-		loginRequired: true,
-	},
-	{
-		path: "/my/messaging/group/:groupId",
-		component: page(() => import("./pages/messaging/messaging-room.vue")),
-		loginRequired: true,
-	},
 	{
 		path: "/my/drive/folder/:folder",
 		component: page(() => import("./pages/drive.vue")),
diff --git a/packages/client/src/navbar.ts b/packages/client/src/navbar.ts
index da6077cef..d83d596a1 100644
--- a/packages/client/src/navbar.ts
+++ b/packages/client/src/navbar.ts
@@ -18,13 +18,6 @@ export const navbarItemDef = reactive({
 		indicated: computed(() => $i?.hasUnreadNotification),
 		to: "/my/notifications",
 	},
-	messaging: {
-		title: "messaging",
-		icon: "ph-chats-teardrop ph-bold ph-lg",
-		show: computed(() => $i != null),
-		indicated: computed(() => $i?.hasUnreadMessagingMessage),
-		to: "/my/messaging",
-	},
 	drive: {
 		title: "drive",
 		icon: "ph-cloud ph-bold ph-lg",