enhance(frontend): チャットページの「タブをダイレクトメッセージ/グループ」に分割する
This commit is contained in:
parent
2287cff79a
commit
c0b03d7136
|
@ -46,6 +46,7 @@
|
|||
- Enhance: '모달에 흐림 효과 사용' 옵션이 비활성화된 경우, 이미지를 탭하여 표시할 때 표시되는 배경을 어둡게 조정
|
||||
- Enhance: 대화 페이지 디자인 개선
|
||||
- Enhance: 유저 페이지 헤더에 유저 메뉴, 팔로우 버튼 추가
|
||||
- Enhance: 대화 페이지의 탭을 '다이렉트 메시지 / 그룹'으로 분할
|
||||
- Fix: (Friendly) 흐림 효과를 사용할 때 하단 내비게이션 바의 가독성이 매우 떨어지는 문제
|
||||
- Fix: (Friendly) 위젯 버튼에서 'UI 애니메이션 줄이기' 옵션이 적용되지 않는 문제
|
||||
- Fix: (Friendly) 스크롤을 해도 위젯 버튼이 숨겨지지 않는 문제
|
||||
|
|
|
@ -1171,6 +1171,8 @@ additionalPermissionsForFlash: "Allow to add permission to Play"
|
|||
thisFlashRequiresTheFollowingPermissions: "This Play requires the following permissions"
|
||||
doYouWantToAllowThisPlayToAccessYourAccount: "Do you want to allow this Play to access your account?"
|
||||
translateProfile: "Translate profile"
|
||||
_messaging:
|
||||
direct: "Direct Message"
|
||||
_tlTutorial:
|
||||
step1_1: 'The {icon} Home timeline is where you can see posts from the accounts you follow.'
|
||||
step1_2: 'The {icon} Local timeline is where you can see posts from everyone else on this server.'
|
||||
|
|
3
locales/index.d.ts
vendored
3
locales/index.d.ts
vendored
|
@ -1175,6 +1175,9 @@ export interface Locale {
|
|||
"thisFlashRequiresTheFollowingPermissions": string;
|
||||
"doYouWantToAllowThisPlayToAccessYourAccount": string;
|
||||
"translateProfile": string;
|
||||
"_messaging": {
|
||||
"direct": string;
|
||||
};
|
||||
"_tlTutorial": {
|
||||
"step1_1": string;
|
||||
"step1_2": string;
|
||||
|
|
|
@ -1173,6 +1173,9 @@ thisFlashRequiresTheFollowingPermissions: "このPlayは以下の権限を要求
|
|||
doYouWantToAllowThisPlayToAccessYourAccount: "このPlayによるアカウントへのアクセスを許可しますか?"
|
||||
translateProfile: "プロフィールを翻訳する"
|
||||
|
||||
_messaging:
|
||||
direct: "ダイレクトメッセージ"
|
||||
|
||||
_tlTutorial:
|
||||
step1_1: '{icon} ホームタイムラインは、あなたがフォローしているアカウントの投稿を見られます。'
|
||||
step1_2: '{icon} ローカルタイムラインでは、このサーバーにいるみんなの投稿を見られます。'
|
||||
|
|
|
@ -1172,6 +1172,8 @@ additionalPermissionsForFlash: "Play에 대한 추가 권한"
|
|||
thisFlashRequiresTheFollowingPermissions: "이 Play는 다음 권한을 요구해요"
|
||||
doYouWantToAllowThisPlayToAccessYourAccount: "이 Play가 계정에 접근하도록 허용할까요?"
|
||||
translateProfile: "프로필 번역하기"
|
||||
_messaging:
|
||||
direct: "다이렉트 메시지"
|
||||
_tlTutorial:
|
||||
step1_1: '{icon} 홈 타임라인은 내가 팔로우하고 있는 계정의 게시물을 볼 수 있어요.'
|
||||
step1_2: '{icon} 로컬 타임라인은 이 서버의 모든 유저가 올린 게시물을 볼 수 있어요.'
|
||||
|
|
169
packages/frontend/src/components/MkChatPreview.vue
Normal file
169
packages/frontend/src/components/MkChatPreview.vue
Normal file
|
@ -0,0 +1,169 @@
|
|||
<!--
|
||||
SPDX-FileCopyrightText: syuilo and noridev and other misskey, cherrypick contributors
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<MkA
|
||||
v-anim="i"
|
||||
class="_panel"
|
||||
:class="[$style.message, { [$style.isRead]: (isMe(message) || (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>
|
||||
<span v-if="!(isMe(message) || (message.groupId ? message.reads.includes($i.id) : message.isRead))" :class="$style.indicator"><i class="_indicatorCircle"></i></span>
|
||||
<MkAvatar :class="$style.avatar" :user="message.groupId ? message.user : isMe(message) ? message.recipient : message.user" indicator link preview/>
|
||||
<header v-if="message.groupId">
|
||||
<span :class="$style.name">{{ message.group.name }}</span>
|
||||
<MkTime :time="message.createdAt" :class="$style.time"/>
|
||||
</header>
|
||||
<header v-else>
|
||||
<span :class="$style.name"><MkUserName :user="isMe(message) ? message.recipient : message.user"/></span>
|
||||
<span :class="$style.username">@{{ acct(isMe(message) ? message.recipient : message.user) }}</span>
|
||||
<MkTime :time="message.createdAt" :class="$style.time"/>
|
||||
</header>
|
||||
<div>
|
||||
<p :class="$style.text"><span v-if="isMe(message)" :class="$style.me">{{ i18n.ts.you }}: </span>{{ message.text }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</MkA>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import * as Acct from 'cherrypick-js/built/acct';
|
||||
import { acct } from '@/filters/user';
|
||||
import { $i } from '@/account';
|
||||
import { i18n } from '@/i18n';
|
||||
|
||||
const getAcct = Acct.toString;
|
||||
|
||||
const props = defineProps<{
|
||||
message: Record<string, any>;
|
||||
}>();
|
||||
|
||||
function isMe(message) {
|
||||
return message.userId === $i.id;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.message {
|
||||
display: block;
|
||||
text-decoration: none !important;
|
||||
margin-bottom: var(--margin);
|
||||
|
||||
* {
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.avatar {
|
||||
filter: saturate(200%);
|
||||
}
|
||||
}
|
||||
|
||||
&:after {
|
||||
content: "";
|
||||
display: block;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
> div {
|
||||
padding: 25px 30px;
|
||||
|
||||
&:after {
|
||||
content: "";
|
||||
display: block;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
> header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 2px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
&.isRead {
|
||||
background: var(--chatReadBg);
|
||||
}
|
||||
}
|
||||
|
||||
.indicator {
|
||||
position: absolute;
|
||||
top: 41px;
|
||||
left: 12px;
|
||||
color: var(--indicator);
|
||||
font-size: 9px;
|
||||
}
|
||||
|
||||
.name {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-weight: bold;
|
||||
transition: all 0.1s ease;
|
||||
}
|
||||
|
||||
.username {
|
||||
margin: 0 8px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.time {
|
||||
margin: 0 0 0 auto;
|
||||
font-size: .85em;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
float: left;
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
margin: 0 16px 0 0;
|
||||
border-radius: 8px;
|
||||
transition: all 0.1s ease;
|
||||
}
|
||||
|
||||
.text {
|
||||
display: block;
|
||||
margin: 0 0 0 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
overflow-wrap: break-word;
|
||||
line-height: 1.35;
|
||||
max-height: 4.05em;
|
||||
color: var(--faceText);
|
||||
}
|
||||
|
||||
.me {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.image {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
max-height: 512px;
|
||||
}
|
||||
|
||||
@container (max-width: 500px) {
|
||||
.message {
|
||||
> div {
|
||||
padding: 20px 30px;
|
||||
font-size: .9em;
|
||||
}
|
||||
}
|
||||
|
||||
.indicator {
|
||||
top: 36px;
|
||||
left: 12px;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
margin: 0 12px 0 0;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -5,36 +5,18 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
|
||||
<template>
|
||||
<MkStickyContainer>
|
||||
<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
|
||||
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
||||
<MkSpacer :contentMax="800">
|
||||
<div>
|
||||
<div v-if="messages.length > 0">
|
||||
<MkA
|
||||
v-for="(message, i) in messages"
|
||||
:key="message.id"
|
||||
v-anim="i"
|
||||
class="_panel"
|
||||
:class="[$style.message, { [$style.isRead]: (isMe(message) || (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>
|
||||
<span v-if="!(isMe(message) || (message.groupId ? message.reads.includes($i.id) : message.isRead))" :class="$style.indicator"><i class="_indicatorCircle"></i></span>
|
||||
<MkAvatar :class="$style.avatar" :user="message.groupId ? message.user : isMe(message) ? message.recipient : message.user" indicator link preview/>
|
||||
<header v-if="message.groupId">
|
||||
<span :class="$style.name">{{ message.group.name }}</span>
|
||||
<MkTime :time="message.createdAt" :class="$style.time"/>
|
||||
</header>
|
||||
<header v-else>
|
||||
<span :class="$style.name"><MkUserName :user="isMe(message) ? message.recipient : message.user"/></span>
|
||||
<span :class="$style.username">@{{ acct(isMe(message) ? message.recipient : message.user) }}</span>
|
||||
<MkTime :time="message.createdAt" :class="$style.time"/>
|
||||
</header>
|
||||
<div>
|
||||
<p :class="$style.text"><span v-if="isMe(message)" :class="$style.me">{{ i18n.ts.you }}: </span>{{ message.text }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</MkA>
|
||||
<div v-if="tab === 'direct'">
|
||||
<MkPagination v-slot="{ items }" :pagination="directPagination">
|
||||
<MkChatPreview v-for="message in items" :key="message.id" :message="message"/>
|
||||
</MkPagination>
|
||||
</div>
|
||||
<div v-else-if="tab === 'groups'">
|
||||
<MkPagination v-slot="{ items }" :pagination="groupsPagination">
|
||||
<MkChatPreview v-for="message in items" :key="message.id" :message="message"/>
|
||||
</MkPagination>
|
||||
</div>
|
||||
<div v-if="!fetching && messages.length == 0" class="_fullinfo">
|
||||
<img src="https://xn--931a.moe/assets/info.jpg" class="_ghost" alt=""/>
|
||||
|
@ -49,7 +31,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<script lang="ts" setup>
|
||||
import { markRaw, onMounted, onUnmounted } from 'vue';
|
||||
import * as Acct from 'cherrypick-js/built/acct';
|
||||
import { acct } from '@/filters/user';
|
||||
import * as os from '@/os';
|
||||
import { useStream } from '@/stream';
|
||||
import { useRouter } from '@/router';
|
||||
|
@ -57,18 +38,31 @@ import { i18n } from '@/i18n';
|
|||
import { definePageMetadata } from '@/scripts/page-metadata';
|
||||
import { $i } from '@/account';
|
||||
import { eventBus } from '@/scripts/cherrypick/eventBus';
|
||||
import MkChatPreview from '@/components/MkChatPreview.vue';
|
||||
import MkPagination from '@/components/MkPagination.vue';
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
let tab = $ref('direct');
|
||||
|
||||
let fetching = $ref(true);
|
||||
let messages = $ref([]);
|
||||
let connection = $ref(null);
|
||||
|
||||
const getAcct = Acct.toString;
|
||||
|
||||
function isMe(message) {
|
||||
return message.userId === $i.id;
|
||||
}
|
||||
const directPagination = {
|
||||
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) {
|
||||
if (message.recipientId) {
|
||||
|
@ -146,7 +140,7 @@ onMounted(() => {
|
|||
const _messages = userMessages.concat(groupMessages);
|
||||
_messages.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
||||
messages = _messages;
|
||||
fetching = false;
|
||||
fetching = false;
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -165,7 +159,15 @@ const headerActions = $computed(() => [{
|
|||
handler: start,
|
||||
}]);
|
||||
|
||||
const headerTabs = $computed(() => []);
|
||||
const headerTabs = $computed(() => [{
|
||||
key: 'direct',
|
||||
title: i18n.ts._messaging.direct,
|
||||
icon: 'ti ti-users',
|
||||
}, {
|
||||
key: 'groups',
|
||||
title: i18n.ts.groups,
|
||||
icon: 'ti ti-users-group',
|
||||
}]);
|
||||
|
||||
definePageMetadata({
|
||||
title: i18n.ts.messaging,
|
||||
|
@ -174,122 +176,4 @@ definePageMetadata({
|
|||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.message {
|
||||
display: block;
|
||||
text-decoration: none !important;
|
||||
margin-bottom: var(--margin);
|
||||
|
||||
* {
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.avatar {
|
||||
filter: saturate(200%);
|
||||
}
|
||||
}
|
||||
|
||||
&:after {
|
||||
content: "";
|
||||
display: block;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
> div {
|
||||
padding: 25px 30px;
|
||||
|
||||
&:after {
|
||||
content: "";
|
||||
display: block;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
> header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 2px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
&.isRead {
|
||||
background: var(--chatReadBg);
|
||||
}
|
||||
}
|
||||
|
||||
.indicator {
|
||||
position: absolute;
|
||||
top: 41px;
|
||||
left: 12px;
|
||||
color: var(--indicator);
|
||||
font-size: 9px;
|
||||
}
|
||||
|
||||
.name {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-weight: bold;
|
||||
transition: all 0.1s ease;
|
||||
}
|
||||
|
||||
.username {
|
||||
margin: 0 8px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.time {
|
||||
margin: 0 0 0 auto;
|
||||
font-size: .85em;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
float: left;
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
margin: 0 16px 0 0;
|
||||
border-radius: 8px;
|
||||
transition: all 0.1s ease;
|
||||
}
|
||||
|
||||
.text {
|
||||
display: block;
|
||||
margin: 0 0 0 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
overflow-wrap: break-word;
|
||||
line-height: 1.35;
|
||||
max-height: 4.05em;
|
||||
color: var(--faceText);
|
||||
}
|
||||
|
||||
.me {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.image {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
max-height: 512px;
|
||||
}
|
||||
|
||||
@container (max-width: 500px) {
|
||||
.message {
|
||||
> div {
|
||||
padding: 20px 30px;
|
||||
font-size: .9em;
|
||||
}
|
||||
}
|
||||
|
||||
.indicator {
|
||||
top: 36px;
|
||||
left: 12px;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
margin: 0 12px 0 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
Loading…
Reference in a new issue