summaryrefslogtreecommitdiff
path: root/packages/frontend
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2023-02-15 13:06:06 +0900
committerGitHub <noreply@github.com>2023-02-15 13:06:06 +0900
commit8f2049bcd261c3fb10afdc8c15cf4edffe1baa71 (patch)
treedc5aa1cefc24d3f6eb36bb1723d7433f8a19e5a2 /packages/frontend
parentMerge branch 'develop' of https://github.com/misskey-dev/misskey into develop (diff)
downloadmisskey-8f2049bcd261c3fb10afdc8c15cf4edffe1baa71.tar.gz
misskey-8f2049bcd261c3fb10afdc8c15cf4edffe1baa71.tar.bz2
misskey-8f2049bcd261c3fb10afdc8c15cf4edffe1baa71.zip
drop messaging (#9919)
* drop messaging (from backend) * wip
Diffstat (limited to 'packages/frontend')
-rw-r--r--packages/frontend/src/components/MkPostForm.vue8
-rw-r--r--packages/frontend/src/init.ts9
-rw-r--r--packages/frontend/src/navbar.ts7
-rw-r--r--packages/frontend/src/pages/messaging/index.vue305
-rw-r--r--packages/frontend/src/pages/messaging/messaging-room.form.vue366
-rw-r--r--packages/frontend/src/pages/messaging/messaging-room.message.vue338
-rw-r--r--packages/frontend/src/pages/messaging/messaging-room.vue415
-rw-r--r--packages/frontend/src/pages/settings/notifications.vue5
-rw-r--r--packages/frontend/src/router.ts13
9 files changed, 0 insertions, 1466 deletions
diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue
index 620b126822..77d5adc23e 100644
--- a/packages/frontend/src/components/MkPostForm.vue
+++ b/packages/frontend/src/components/MkPostForm.vue
@@ -160,12 +160,6 @@ let hasNotSpecifiedMentions = $ref(false);
let recentHashtags = $ref(JSON.parse(miLocalStorage.getItem('hashtags') || '[]'));
let imeText = $ref('');
-const typing = throttle(3000, () => {
- if (props.channel) {
- stream.send('typingOnChannel', { channel: props.channel.id });
- }
-});
-
const draftKey = $computed((): string => {
let key = props.channel ? `channel:${props.channel.id}` : '';
@@ -447,12 +441,10 @@ function clear() {
function onKeydown(ev: KeyboardEvent) {
if ((ev.which === 10 || ev.which === 13) && (ev.ctrlKey || ev.metaKey) && canPost) post();
if (ev.which === 27) emit('esc');
- typing();
}
function onCompositionUpdate(ev: CompositionEvent) {
imeText = ev.data;
- typing();
}
function onCompositionEnd(ev: CompositionEvent) {
diff --git a/packages/frontend/src/init.ts b/packages/frontend/src/init.ts
index 64c252ce55..b013b376fb 100644
--- a/packages/frontend/src/init.ts
+++ b/packages/frontend/src/init.ts
@@ -505,15 +505,6 @@ if ($i) {
updateAccount({ hasUnreadSpecifiedNotes: false });
});
- main.on('readAllMessagingMessages', () => {
- updateAccount({ hasUnreadMessagingMessage: false });
- });
-
- main.on('unreadMessagingMessage', () => {
- updateAccount({ hasUnreadMessagingMessage: true });
- sound.play('chatBg');
- });
-
main.on('readAllAntennas', () => {
updateAccount({ hasUnreadAntenna: false });
});
diff --git a/packages/frontend/src/navbar.ts b/packages/frontend/src/navbar.ts
index 4f809d888e..b85299b07c 100644
--- a/packages/frontend/src/navbar.ts
+++ b/packages/frontend/src/navbar.ts
@@ -15,13 +15,6 @@ export const navbarItemDef = reactive({
indicated: computed(() => $i != null && $i.hasUnreadNotification),
to: '/my/notifications',
},
- messaging: {
- title: i18n.ts.messaging,
- icon: 'ti ti-messages',
- show: computed(() => $i != null),
- indicated: computed(() => $i != null && $i.hasUnreadMessagingMessage),
- to: '/my/messaging',
- },
drive: {
title: i18n.ts.drive,
icon: 'ti ti-cloud',
diff --git a/packages/frontend/src/pages/messaging/index.vue b/packages/frontend/src/pages/messaging/index.vue
deleted file mode 100644
index 3d11cf13e9..0000000000
--- a/packages/frontend/src/pages/messaging/index.vue
+++ /dev/null
@@ -1,305 +0,0 @@
-<template>
-<MkStickyContainer>
- <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
- <MkSpacer :content-max="800">
- <div class="yweeujhr">
- <MkButton primary class="start" @click="start"><i class="ti ti-plus"></i> {{ $ts.startMessaging }}</MkButton>
-
- <div v-if="messages.length > 0" class="history">
- <MkA
- v-for="(message, i) in messages"
- :key="message.id"
- v-anim="i"
- class="message _panel"
- :class="{ isMe: isMe(message), isRead: message.groupId ? message.reads.includes($i.id) : message.isRead }"
- :to="message.groupId ? `/my/messaging/group/${message.groupId}` : `/my/messaging/${getAcct(isMe(message) ? message.recipient : message.user)}`"
- :data-index="i"
- >
- <div>
- <MkAvatar class="avatar" :user="message.groupId ? message.user : isMe(message) ? message.recipient : message.user" indicator link preview/>
- <header v-if="message.groupId">
- <span class="name">{{ message.group.name }}</span>
- <MkTime :time="message.createdAt" class="time"/>
- </header>
- <header v-else>
- <span class="name"><MkUserName :user="isMe(message) ? message.recipient : message.user"/></span>
- <span class="username">@{{ acct(isMe(message) ? message.recipient : message.user) }}</span>
- <MkTime :time="message.createdAt" class="time"/>
- </header>
- <div class="body">
- <p class="text"><span v-if="isMe(message)" class="me">{{ $ts.you }}:</span>{{ message.text }}</p>
- </div>
- </div>
- </MkA>
- </div>
- <div v-if="!fetching && messages.length == 0" class="_fullinfo">
- <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/>
- <div>{{ $ts.noHistory }}</div>
- </div>
- <MkLoading v-if="fetching"/>
- </div>
- </MkSpacer>
-</MkStickyContainer>
-</template>
-
-<script lang="ts" setup>
-import { defineAsyncComponent, defineComponent, inject, markRaw, onMounted, onUnmounted } from 'vue';
-import * as Acct from 'misskey-js/built/acct';
-import MkButton from '@/components/MkButton.vue';
-import { acct } from '@/filters/user';
-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';
-
-const router = useRouter();
-
-let fetching = $ref(true);
-let moreFetching = $ref(false);
-let messages = $ref([]);
-let connection = $ref(null);
-
-const getAcct = Acct.toString;
-
-function isMe(message) {
- return message.userId === $i.id;
-}
-
-function onMessage(message) {
- 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) {
- 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 start(ev) {
- os.popupMenu([{
- text: i18n.ts.messagingWithUser,
- icon: 'ti ti-user',
- action: () => { startUser(); },
- }, {
- text: i18n.ts.messagingWithGroup,
- icon: 'ti ti-users',
- action: () => { startGroup(); },
- }], ev.currentTarget ?? ev.target);
-}
-
-async function startUser() {
- os.selectUser().then(user => {
- router.push(`/my/messaging/${Acct.toString(user)}`);
- });
-}
-
-async function startGroup() {
- 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}`);
-}
-
-onMounted(() => {
- connection = markRaw(stream.useChannel('messagingIndex'));
-
- connection.on('message', onMessage);
- connection.on('read', onRead);
-
- os.api('messaging/history', { group: false }).then(userMessages => {
- os.api('messaging/history', { group: true }).then(groupMessages => {
- const _messages = userMessages.concat(groupMessages);
- _messages.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
- messages = _messages;
- fetching = false;
- });
- });
-});
-
-onUnmounted(() => {
- if (connection) connection.dispose();
-});
-
-const headerActions = $computed(() => []);
-
-const headerTabs = $computed(() => []);
-
-definePageMetadata({
- title: i18n.ts.messaging,
- icon: 'ti ti-messages',
-});
-</script>
-
-<style lang="scss" scoped>
-.yweeujhr {
-
- > .start {
- margin: 0 auto var(--margin) auto;
- }
-
- > .history {
- > .message {
- display: block;
- text-decoration: none;
- margin-bottom: var(--margin);
-
- * {
- pointer-events: none;
- user-select: none;
- }
-
- &:hover {
- .avatar {
- filter: saturate(200%);
- }
- }
-
- &:active {
- }
-
- &.isRead,
- &.isMe {
- opacity: 0.8;
- }
-
- &:not(.isMe):not(.isRead) {
- > div {
- background-image: url("/client-assets/unread.svg");
- background-repeat: no-repeat;
- background-position: 0 center;
- }
- }
-
- &:after {
- content: "";
- display: block;
- clear: both;
- }
-
- > div {
- padding: 20px 30px;
-
- &:after {
- content: "";
- display: block;
- clear: both;
- }
-
- > header {
- display: flex;
- align-items: center;
- margin-bottom: 2px;
- white-space: nowrap;
- overflow: hidden;
-
- > .name {
- margin: 0;
- padding: 0;
- overflow: hidden;
- text-overflow: ellipsis;
- font-size: 1em;
- font-weight: bold;
- transition: all 0.1s ease;
- }
-
- > .username {
- margin: 0 8px;
- }
-
- > .time {
- margin: 0 0 0 auto;
- }
- }
-
- > .avatar {
- float: left;
- width: 54px;
- height: 54px;
- margin: 0 16px 0 0;
- border-radius: 8px;
- transition: all 0.1s ease;
- }
-
- > .body {
-
- > .text {
- display: block;
- margin: 0 0 0 0;
- padding: 0;
- overflow: hidden;
- overflow-wrap: break-word;
- font-size: 1.1em;
- color: var(--faceText);
-
- .me {
- opacity: 0.7;
- }
- }
-
- > .image {
- display: block;
- max-width: 100%;
- max-height: 512px;
- }
- }
- }
- }
- }
-}
-
-@container (max-width: 400px) {
- .yweeujhr {
- > .history {
- > .message {
- &:not(.isMe):not(.isRead) {
- > div {
- background-image: none;
- border-left: solid 4px #3aa2dc;
- }
- }
-
- > div {
- padding: 16px;
- font-size: 0.9em;
-
- > .avatar {
- margin: 0 12px 0 0;
- }
- }
- }
- }
- }
-}
-</style>
diff --git a/packages/frontend/src/pages/messaging/messaging-room.form.vue b/packages/frontend/src/pages/messaging/messaging-room.form.vue
deleted file mode 100644
index d6113668dd..0000000000
--- a/packages/frontend/src/pages/messaging/messaging-room.form.vue
+++ /dev/null
@@ -1,366 +0,0 @@
-<template>
-<div
- :class="$style['root']"
- @dragover.stop="onDragover"
- @drop.stop="onDrop"
->
- <textarea
- :class="$style['textarea']"
- class="_acrylic"
- ref="textEl"
- v-model="text"
- :placeholder="i18n.ts.inputMessageHere"
- @keydown="onKeydown"
- @compositionupdate="onCompositionUpdate"
- @paste="onPaste"
- ></textarea>
- <footer :class="$style['footer']">
- <div v-if="file" :class="$style['file']" @click="file = null">{{ file.name }}</div>
- <div :class="$style['buttons']">
- <button class="_button" :class="$style['button']" @click="chooseFile"><i class="ti ti-photo-plus"></i></button>
- <button class="_button" :class="$style['button']" @click="insertEmoji"><i class="ti ti-mood-happy"></i></button>
- <button class="_button" :class="[$style['button'], $style['send']]" :disabled="!canSend || sending" :title="i18n.ts.send" @click="send">
- <template v-if="!sending"><i class="ti ti-send"></i></template><template v-if="sending"><MkLoading :em="true"/></template>
- </button>
- </div>
- </footer>
- <input :class="$style['file-input']" ref="fileEl" type="file" @change="onChangeFile"/>
-</div>
-</template>
-
-<script lang="ts" setup>
-import { onMounted, watch } from 'vue';
-import * as Misskey from 'misskey-js';
-import autosize from 'autosize';
-//import insertTextAtCursor from 'insert-text-at-cursor';
-import { throttle } from 'throttle-debounce';
-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 { Autocomplete } from '@/scripts/autocomplete';
-import { uploadFile } from '@/scripts/upload';
-import { miLocalStorage } from '@/local-storage';
-
-const props = defineProps<{
- user?: Misskey.entities.UserDetailed | null;
- group?: Misskey.entities.UserGroup | null;
-}>();
-
-let textEl = $shallowRef<HTMLTextAreaElement>();
-let fileEl = $shallowRef<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 !== '') || 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();
- switch (ev.dataTransfer.effectAllowed) {
- case 'all':
- case 'uninitialized':
- case 'copy':
- case 'copyLink':
- case 'copyMove':
- ev.dataTransfer.dropEffect = 'copy';
- break;
- case 'linkMove':
- case 'move':
- ev.dataTransfer.dropEffect = 'move';
- break;
- default:
- ev.dataTransfer.dropEffect = 'none';
- break;
- }
- }
-}
-
-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();
- if ((ev.key === 'Enter') && (ev.ctrlKey || ev.metaKey) && canSend) {
- 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(miLocalStorage.getItem('message_drafts') || '{}');
-
- drafts[draftKey] = {
- updatedAt: new Date(),
- // eslint-disable-next-line id-denylist
- data: {
- text: text,
- file: file,
- },
- };
-
- miLocalStorage.setItem('message_drafts', JSON.stringify(drafts));
-}
-
-function deleteDraft() {
- const drafts = JSON.parse(miLocalStorage.getItem('message_drafts') || '{}');
-
- delete drafts[draftKey];
-
- miLocalStorage.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
- // TODO
- //new Autocomplete(textEl, this, { model: 'text' });
-
- // 書きかけの投稿を復元
- const draft = JSON.parse(miLocalStorage.getItem('message_drafts') || '{}')[draftKey];
- if (draft) {
- text = draft.data.text;
- file = draft.data.file;
- }
-});
-
-defineExpose({
- file,
- upload,
-});
-</script>
-
-<style lang="scss" module>
-.root {
- position: relative;
-}
-
-.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;
- 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;
- }
- }
- }
-}
-
-.file-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;
- }
-}
-
-.file-input {
- display: none;
-}
-</style>
diff --git a/packages/frontend/src/pages/messaging/messaging-room.message.vue b/packages/frontend/src/pages/messaging/messaging-room.message.vue
deleted file mode 100644
index d10798b92e..0000000000
--- a/packages/frontend/src/pages/messaging/messaging-room.message.vue
+++ /dev/null
@@ -1,338 +0,0 @@
-<template>
-<div class="thvuemwp" :class="{ isMe }">
- <MkAvatar class="avatar" :user="message.user" indicator link preview/>
- <div class="content">
- <div class="balloon" :class="{ noText: message.text == null }">
- <button v-if="isMe" class="delete-button" :title="$ts.delete" @click="del">
- <img src="/client-assets/remove.png" alt="Delete"/>
- </button>
- <div v-if="!message.isDeleted" class="content">
- <Mfm v-if="message.text" ref="text" class="text" :text="message.text" :i="$i"/>
- <div v-if="message.file" class="file">
- <a :href="message.file.url" rel="noopener" target="_blank" :title="message.file.name">
- <img v-if="message.file.type.split('/')[0] == 'image'" :src="message.file.url" :alt="message.file.name"/>
- <p v-else>{{ message.file.name }}</p>
- </a>
- </div>
- </div>
- <div v-else class="content">
- <p class="is-deleted">{{ $ts.deleted }}</p>
- </div>
- </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">{{ $ts.messageRead }} {{ message.reads.length }}</span>
- </template>
- <template v-else>
- <span v-if="isMe && message.isRead" class="read">{{ $ts.messageRead }}</span>
- </template>
- <MkTime :time="message.createdAt"/>
- <template v-if="message.is_edited"><i class="ti ti-pencil"></i></template>
- </footer>
- </div>
-</div>
-</template>
-
-<script lang="ts" setup>
-import { } from 'vue';
-import * as mfm from 'mfm-js';
-import * as Misskey from 'misskey-js';
-import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm';
-import MkUrlPreview from '@/components/MkUrlPreview.vue';
-import * as os from '@/os';
-import { $i } from '@/account';
-
-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);
-
- position: relative;
- background-color: transparent;
- display: flex;
-
- > .avatar {
- position: sticky;
- top: calc(var(--stickyTop, 0px) + 16px);
- display: block;
- width: 54px;
- height: 54px;
- 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%;
-
- &:before {
- content: "";
- pointer-events: none;
- display: block;
- position: absolute;
- top: 12px;
- }
-
- & + * {
- 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: #555;
- 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(--messageBg);
- 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;
- }
- }
- }
- }
-}
-
-@container (max-width: 400px) {
- .thvuemwp {
- > .avatar {
- width: 48px;
- height: 48px;
- }
-
- > .content {
- > .balloon {
- > .content {
- > .text {
- font-size: 0.9em;
- }
- }
- }
- }
- }
-}
-
-@container (max-width: 500px) {
- .thvuemwp {
- > .content {
- > .balloon {
- > .content {
- > .text {
- padding: 8px 16px;
- }
- }
- }
- }
- }
-}
-</style>
diff --git a/packages/frontend/src/pages/messaging/messaging-room.vue b/packages/frontend/src/pages/messaging/messaging-room.vue
deleted file mode 100644
index 0867f003a3..0000000000
--- a/packages/frontend/src/pages/messaging/messaging-room.vue
+++ /dev/null
@@ -1,415 +0,0 @@
-<template>
-<MkStickyContainer>
-<template #header>
- <MkPageHeader />
-</template>
-<div
- ref="rootEl"
- :class="$style['root']"
- @dragover.prevent.stop="onDragover"
- @drop.prevent.stop="onDrop"
->
- <div :class="$style['body']">
- <MkPagination v-if="pagination" ref="pagingComponent" :key="userAcct || groupId" :pagination="pagination">
- <template #empty>
- <div class="_fullinfo">
- <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/>
- <div>{{ i18n.ts.noMessagesYet }}</div>
- </div>
- </template>
- <template #default="{ items: messages, fetching: pFetching }">
- <MkDateSeparatedList
- v-if="messages.length > 0"
- v-slot="{ item: message }"
- :class="{ [$style['messages']]: true, 'deny-move-transition': pFetching }"
- :items="messages"
- direction="up"
- reversed
- >
- <XMessage :key="message.id" :message="message" :is-group="group != null"/>
- </MkDateSeparatedList>
- </template>
- </MkPagination>
- </div>
- <footer :class="$style['footer']">
- <div v-if="typers.length > 0" :class="$style['typers']">
- <I18n :src="i18n.ts.typingUsers" text-tag="span">
- <template #users>
- <b v-for="typer in typers" :key="typer.id" :class="$style['user']">{{ typer.username }}</b>
- </template>
- </I18n>
- <MkEllipsis/>
- </div>
- <Transition :name="animation ? 'fade' : ''">
- <div v-show="showIndicator" :class="$style['new-message']">
- <button class="_buttonPrimary" @click="onIndicatorClick" :class="$style['new-message-button']">
- <i class="fas ti-fw fa-arrow-circle-down" :class="$style['new-message-icon']"></i>{{ i18n.ts.newMessageExists }}
- </button>
- </div>
- </Transition>
- <XForm v-if="!fetching" ref="formEl" :user="user" :group="group" :class="$style['form']"/>
- </footer>
-</div>
-</MkStickyContainer>
-</template>
-
-<script lang="ts" setup>
-import { computed, watch, onMounted, nextTick, onBeforeUnmount } from 'vue';
-import * as Misskey from 'misskey-js';
-import * as Acct from 'misskey-js/built/acct';
-import XMessage from './messaging-room.message.vue';
-import XForm from './messaging-room.form.vue';
-import MkDateSeparatedList 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';
-
-const props = defineProps<{
- userAcct?: string;
- groupId?: string;
-}>();
-
-let rootEl = $shallowRef<HTMLDivElement>();
-let formEl = $shallowRef<InstanceType<typeof XForm>>();
-let pagingComponent = $shallowRef<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(() => {
- pagingComponent.inited.then(() => {
- 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) {
- switch (ev.dataTransfer.effectAllowed) {
- case 'all':
- case 'uninitialized':
- case 'copy':
- case 'copyLink':
- case 'copyMove':
- ev.dataTransfer.dropEffect = 'copy';
- break;
- case 'linkMove':
- case 'move':
- ev.dataTransfer.dropEffect = 'move';
- break;
- default:
- ev.dataTransfer.dropEffect = 'none';
- break;
- }
- } 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() {
- 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();
-});
-
-onBeforeUnmount(() => {
- connection?.dispose();
- document.removeEventListener('visibilitychange', onVisibilitychange);
- if (scrollRemove) scrollRemove();
-});
-
-definePageMetadata(computed(() => !fetching ? user ? {
- userName: user,
- avatar: user,
-} : {
- title: group?.name,
- icon: 'ti ti-users',
-} : null));
-</script>
-
-<style lang="scss" module>
-.root {
- display: content;
-}
-
-.body {
- min-height: 80%;
-}
-
-.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);
- }
- > i {
- margin-right: 4px;
- }
-}
-
-.fetching {
- cursor: wait;
-}
-
-.messages {
- padding: 16px 0 0;
-
- > * {
- margin-bottom: 16px;
- }
-}
-
-.footer {
- width: 100%;
- position: sticky;
- z-index: 2;
- padding-top: 8px;
- bottom: var(--minBottomSpacing);
-}
-
-.new-message {
- width: 100%;
- padding-bottom: 8px;
- text-align: center;
-}
-
-.new-message-button {
- display: inline-block;
- margin: 0;
- padding: 0 12px;
- line-height: 32px;
- font-size: 12px;
- border-radius: 16px;
-}
-
-.new-message-icon {
- display: inline-block;
- margin-right: 8px;
-}
-
-.typers {
- position: absolute;
- bottom: 100%;
- padding: 0 8px 0 8px;
- font-size: 0.9em;
- color: var(--fgTransparentWeak);
-}
-
-
-.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);
- border-bottom-left-radius: 0;
- border-bottom-right-radius: 0;
-}
-
-.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/frontend/src/pages/settings/notifications.vue b/packages/frontend/src/pages/settings/notifications.vue
index db32ee862c..05c7fb72e5 100644
--- a/packages/frontend/src/pages/settings/notifications.vue
+++ b/packages/frontend/src/pages/settings/notifications.vue
@@ -5,7 +5,6 @@
<div class="_gaps_m">
<FormLink @click="readAllNotifications">{{ i18n.ts.markAsReadAllNotifications }}</FormLink>
<FormLink @click="readAllUnreadNotes">{{ i18n.ts.markAsReadAllUnreadNotes }}</FormLink>
- <FormLink @click="readAllMessagingMessages">{{ i18n.ts.markAsReadAllTalkMessages }}</FormLink>
</div>
</FormSection>
<FormSection>
@@ -47,10 +46,6 @@ async function readAllUnreadNotes() {
await os.api('i/read-all-unread-notes');
}
-async function readAllMessagingMessages() {
- await os.api('i/read-all-messaging-messages');
-}
-
async function readAllNotifications() {
await os.api('notifications/mark-all-as-read');
}
diff --git a/packages/frontend/src/router.ts b/packages/frontend/src/router.ts
index 9004262689..2aa2e0ab3d 100644
--- a/packages/frontend/src/router.ts
+++ b/packages/frontend/src/router.ts
@@ -421,19 +421,6 @@ export const routes = [{
component: page(() => import('./pages/achievements.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')),
loginRequired: true,