jormungandr-patches/patches/remove-messaging.patch

1688 lines
37 KiB
Diff

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",