enhance(frontend): チャットページの「タブをダイレクトメッセージ/グループ」に分割する

This commit is contained in:
NoriDev 2023-08-31 17:01:03 +09:00
parent 2287cff79a
commit c0b03d7136
7 changed files with 218 additions and 154 deletions

View file

@ -46,6 +46,7 @@
- Enhance: '모달에 흐림 효과 사용' 옵션이 비활성화된 경우, 이미지를 탭하여 표시할 때 표시되는 배경을 어둡게 조정
- Enhance: 대화 페이지 디자인 개선
- Enhance: 유저 페이지 헤더에 유저 메뉴, 팔로우 버튼 추가
- Enhance: 대화 페이지의 탭을 '다이렉트 메시지 / 그룹'으로 분할
- Fix: (Friendly) 흐림 효과를 사용할 때 하단 내비게이션 바의 가독성이 매우 떨어지는 문제
- Fix: (Friendly) 위젯 버튼에서 'UI 애니메이션 줄이기' 옵션이 적용되지 않는 문제
- Fix: (Friendly) 스크롤을 해도 위젯 버튼이 숨겨지지 않는 문제

View file

@ -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
View file

@ -1175,6 +1175,9 @@ export interface Locale {
"thisFlashRequiresTheFollowingPermissions": string;
"doYouWantToAllowThisPlayToAccessYourAccount": string;
"translateProfile": string;
"_messaging": {
"direct": string;
};
"_tlTutorial": {
"step1_1": string;
"step1_2": string;

View file

@ -1173,6 +1173,9 @@ thisFlashRequiresTheFollowingPermissions: "このPlayは以下の権限を要求
doYouWantToAllowThisPlayToAccessYourAccount: "このPlayによるアカウントへのアクセスを許可しますか"
translateProfile: "プロフィールを翻訳する"
_messaging:
direct: "ダイレクトメッセージ"
_tlTutorial:
step1_1: '{icon} ホームタイムラインは、あなたがフォローしているアカウントの投稿を見られます。'
step1_2: '{icon} ローカルタイムラインでは、このサーバーにいるみんなの投稿を見られます。'

View file

@ -1172,6 +1172,8 @@ additionalPermissionsForFlash: "Play에 대한 추가 권한"
thisFlashRequiresTheFollowingPermissions: "이 Play는 다음 권한을 요구해요"
doYouWantToAllowThisPlayToAccessYourAccount: "이 Play가 계정에 접근하도록 허용할까요?"
translateProfile: "프로필 번역하기"
_messaging:
direct: "다이렉트 메시지"
_tlTutorial:
step1_1: '{icon} 홈 타임라인은 내가 팔로우하고 있는 계정의 게시물을 볼 수 있어요.'
step1_2: '{icon} 로컬 타임라인은 이 서버의 모든 유저가 올린 게시물을 볼 수 있어요.'

View 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>

View file

@ -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>