안읽은메세지 개수 표시하기
1099
작성한 질문수 46
안녕하세요 제로초님
디엠부분에서는 안읽은 메시지 표시가 떠서 읽으면 없어지는데
채널부분에서는 없어지지않고 남아있습니다!
답변 1
0
이 부분도 정상 작동하는 것 확인했습니다. ㅠㅠ
0
음 채널부분은 추가하라고 하신데로
useEffect(() => {
localStorage.setItem(`${workspace}-${channel}}`, new Date().getTime().toString());
}, [workspace, channel]);이거 추가하고 EachChannel은 복붙해서 가져왔는데 보니까 채널부분은 채팅을 쳐야지만 안읽은 표시가 사라지네요
import ChatBox from '@components/ChatBox';
import ChatList from '@components/ChatList';
import InviteChannelModal from '@components/InviteChannelModal';
import useInput from '@hooks/useInput';
import useSocket from '@hooks/useSocket';
import { DragOver } from '@pages/Channel/styles';
import { Header, Container } from '@pages/DM/styles';
import { IChannel, IChat, IDM, IUser } from '@typings/db';
import fetcher from '@utils/fetcher';
import makeSection from '@utils/makeSection';
import axios from 'axios';
import { channel } from 'diagnostics_channel';
import gravatar from 'gravatar';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { Scrollbars } from 'react-custom-scrollbars';
import { Redirect, useParams } from 'react-router';
import { toast } from 'react-toastify';
import useSWR from 'swr';
import useSWRInfinite from 'swr/infinite';
const PAGE_SIZE = 20;
const Channel = () => {
const { workspace, channel } = useParams<{ workspace: string; channel: string }>();
const [showInviteChannelModal, setShowInviteChannelModal] = useState(false);
const [socket] = useSocket(workspace);
const { data: myData } = useSWR('/api/users', fetcher);
const { data: userData } = useSWR<IUser>('/api/users', fetcher);
const {
data: chatData,
mutate: mutateChat,
setSize,
} = useSWRInfinite<IChat[]>(
(index) => `/api/workspaces/${workspace}/channels/${channel}/chats?perPage=${PAGE_SIZE}&page=${index + 1}`,
fetcher,
{
onSuccess(data) {
if (data?.length === 1) {
setTimeout(() => {
scrollbarRef.current?.scrollToBottom();
}, 100);
}
},
},
);
const { data: channelMembersData } = useSWR<IUser[]>(
userData ? `/api/workspaces/${workspace}/channels/${channel}/members` : null,
fetcher,
);
const { data: channelsData } = useSWR<IChannel[]>(`/api/workspaces/${workspace}/channels`, fetcher);
const channelData = channelsData?.find((v) => v.name === channel);
const [chat, onChangeChat, setChat] = useInput('');
const scrollbarRef = useRef<Scrollbars>(null);
const [dragOver, setDragOver] = useState(false);
const isEmpty = chatData?.[0]?.length === 0;
const isReachingEnd = isEmpty || (chatData && chatData[chatData.length - 1]?.length < PAGE_SIZE);
useEffect(() => {
if (chatData?.length === 1) {
console.log('toBottomWhenLoaded', scrollbarRef.current);
setTimeout(() => {
console.log('scrollbar', scrollbarRef.current);
scrollbarRef.current?.scrollToBottom();
}, 500);
}
}, [chatData]);
const onSubmitForm = useCallback(
(e) => {
e.preventDefault();
if (chat?.trim() && chatData && channelData && userData) {
const savedChat = chat;
mutateChat((prevChatData) => {
prevChatData?.[0].unshift({
id: (chatData[0][0]?.id || 0) + 1,
content: savedChat,
UserId: myData.id,
User: myData,
ChannelId: channelData.id,
Channel: channelData,
createdAt: new Date(),
});
return prevChatData;
}, false).then(() => {
localStorage.setItem(`${workspace}-${channel}`, new Date().getTime().toString());
setChat('');
if (scrollbarRef.current) {
console.log('scrollToBottom!', scrollbarRef.current?.getValues());
scrollbarRef.current.scrollToBottom();
}
});
axios
.post(`/api/workspaces/${workspace}/channels/${channel}/chats`, {
content: chat,
})
.catch(console.error);
}
},
[chat, workspace, channel, channelData, userData, chatData, mutateChat, setChat],
);
const onClickInviteChannel = useCallback(() => {
setShowInviteChannelModal(true);
}, []);
const onCloseModal = useCallback(() => {
setShowInviteChannelModal(false);
}, []);
const onMessage = useCallback(
(data: IChat) => {
if (
data.Channel.name === channel &&
(data.content.startsWith('uploads\\') || data.content.startsWith('uploads/') || data.UserId !== userData?.id)
) {
mutateChat((chatData) => {
chatData?.[0].unshift(data);
return chatData;
}, false).then(() => {
if (scrollbarRef.current) {
if (
scrollbarRef.current.getScrollHeight() <
scrollbarRef.current.getClientHeight() + scrollbarRef.current.getScrollTop() + 150
) {
console.log('scrollToBottom!', scrollbarRef.current?.getValues());
setTimeout(() => {
scrollbarRef.current?.scrollToBottom();
}, 100);
} else {
toast.success('새 메시지가 도착했습니다.', {
onClick() {
scrollbarRef.current?.scrollToBottom();
},
closeOnClick: true,
});
}
}
});
}
},
[channel, mutateChat, userData],
);
useEffect(() => {
socket?.on('message', onMessage);
return () => {
socket?.off('message', onMessage);
};
}, [socket, onMessage]);
useEffect(() => {
localStorage.setItem(`${workspace}-${channel}}`, new Date().getTime().toString());
}, [workspace, channel]);
//화면에 직접 이미지 드래그해서 옮기기
const onDrop = useCallback(
(e) => {
e.preventDefault();
console.log(e);
const formData = new FormData();
if (e.dataTransfer.items) {
// Use DataTransferItemList interface to access the file(s)
for (let i = 0; i < e.dataTransfer.items.length; i++) {
// If dropped items aren't files, reject them
if (e.dataTransfer.items[i].kind === 'file') {
const file = e.dataTransfer.items[i].getAsFile();
console.log('... file[' + i + '].name = ' + file.name);
formData.append('image', file);
}
}
} else {
// Use DataTransfer interface to access the file(s)
for (let i = 0; i < e.dataTransfer.files.length; i++) {
console.log('... file[' + i + '].name = ' + e.dataTransfer.files[i].name);
formData.append('image', e.dataTransfer.files[i]);
}
}
axios.post(`/api/workspaces/${workspace}/dms/${channel}/images`, formData).then(() => {
setDragOver(false);
localStorage.setItem(`${workspace}-${channel}`, new Date().getTime().toString());
mutateChat();
});
},
[workspace, channel],
);
const onDragOver = useCallback((e) => {
e.preventDefault();
console.log(e);
setDragOver(true);
}, []);
if (channelsData && !channelData) {
return <Redirect to={`/workspace/${workspace}/channel/일반`} />;
}
const chatSections = makeSection(chatData ? chatData.flat().reverse() : []);
return (
<Container onDrop={onDrop} onDragOver={onDragOver}>
<Header>
<span>#{channel}</span>
<div className="header-right" style={{ display: 'flex' }}>
<span style={{ display: 'flex', alignItems: 'center' }}>{channelMembersData?.length}</span>
<button
onClick={onClickInviteChannel}
className="c-button-unstyled p-ia__view_header__button"
aria-label="Add people to #react-native"
data-sk="tooltip_parent"
type="button"
>
<i className="c-icon p-ia__view_header__button_icon c-icon--add-user" aria-hidden="true" />
</button>
</div>
</Header>
<ChatList
scrollbarRef={scrollbarRef}
isReachingEnd={isReachingEnd}
isEmpty={isEmpty}
chatSections={chatSections}
setSize={setSize}
/>
<ChatBox
onSubmitForm={onSubmitForm}
chat={chat}
onChangeChat={onChangeChat}
placeholder={`Message ${myData.nickname}`}
data={[]}
/>
<InviteChannelModal
show={showInviteChannelModal}
onCloseModal={onCloseModal}
setShowInviteChannelModal={setShowInviteChannelModal}
/>
{dragOver && <DragOver>업로드!</DragOver>}
</Container>
);
};
export default Channel;
0
https://github.com/ZeroCho/sleact/blob/master/front/components/EachChannel/index.tsx#L17-L18
이 부분도 추가하셨나요? 여기서 숫자가 있을지 없을지 판단되는 겁니다.
0
허허 혹시 EachChannel 이코드 아닌가요?
import { IChannel, IUser } from '@typings/db';
import fetcher from '@utils/fetcher';
import React, { useEffect, VFC } from 'react';
import { useParams } from 'react-router';
import { NavLink, useLocation } from 'react-router-dom';
import useSWR from 'swr';
interface Props {
channel: IChannel;
}
const EachChannel: VFC<Props> = ({ channel }) => {
const { workspace } = useParams<{ workspace?: string }>();
const location = useLocation();
const { data: userData } = useSWR<IUser>('/api/users', fetcher, {
dedupingInterval: 2000, // 2초
});
const date = localStorage.getItem(`${workspace}-${channel.name}`) || 0;
const { data: count, mutate } = useSWR<number>(
userData ? `/api/workspaces/${workspace}/channels/${channel.name}/unreads?after=${date}` : null,
fetcher,
);
useEffect(() => {
if (location.pathname === `/workspace/${workspace}/channel/${channel.name}`) {
mutate(0);
}
}, [mutate, location.pathname, workspace, channel]);
return (
<NavLink key={channel.name} activeClassName="selected" to={`/workspace/${workspace}/channel/${channel.name}`}>
<span className={count !== undefined && count > 0 ? 'bold' : undefined}># {channel.name}</span>
{count !== undefined && count > 0 && <span className="count">{count}</span>}
</NavLink>
);
};
export default EachChannel;
제가 뭐 잘못 건드렸나요... 혹시 Channel말고도 연관 되어있는 파일이 있을까요?
0
https://github.com/ZeroCho/sleact/blob/master/front/components/ChannelList/index.tsx
EachChannel은 ChannelList에서 쓰이므로 여기도 봐보세요. 저 부분 리렌더링이 안 되는 것 같기도 하고요.
0
import { CollapseButton } from '@components/DMList/styles';
import EachChannel from '@components/EachChannel';
import { IChannel, IUser } from '@typings/db';
import fetcher from '@utils/fetcher';
import React, { FC, useCallback, useState } from 'react';
import { useParams } from 'react-router';
import useSWR from 'swr';
interface Props {
channelData?: IChannel[];
userData?: IUser;
}
const ChannelList: FC<Props> = () => {
const { workspace } = useParams<{ workspace?: string }>();
const [channelCollapse, setChannelCollapse] = useState(false);
const { data: userData } = useSWR<IUser>('/api/users', fetcher, {
dedupingInterval: 2000, // 2초
});
const { data: channelData } = useSWR<IChannel[]>(userData ? `/api/workspaces/${workspace}/channels` : null, fetcher);
const toggleChannelCollapse = useCallback(() => {
setChannelCollapse((prev) => !prev);
}, []);
return (
<>
<h2>
<CollapseButton collapse={channelCollapse} onClick={toggleChannelCollapse}>
<i
className="c-icon p-channel_sidebar__section_heading_expand p-channel_sidebar__section_heading_expand--show_more_feature c-icon--caret-right c-icon--inherit c-icon--inline"
data-qa="channel-section-collapse"
aria-hidden="true"
/>
</CollapseButton>
<span>Channels</span>
</h2>
<div>
{!channelCollapse &&
channelData?.map((channel) => {
return <EachChannel key={channel.id} channel={channel} />;
})}
</div>
</>
);
};
export default ChannelList;
sleact /back 폴더에서 npm run dev하고 실행하는건 맞죠?
기본 셋팅과 관련하여
0
92
1
초기 셋팅 back과 front만 남겨두고 다 지운 후 진행 방법
0
96
2
focus 시에만 화면 업데이트 되는 이유 + 해결방법
0
150
2
useEffect 개수 관리
0
110
2
라이브러리 서치 방법
0
104
2
함수 정의 패턴
0
77
1
npm run dev 에러
0
152
3
npx webpack 후 에러
0
178
2
'void' 형식 식의 truthiness를 테스트할 수 없습니다.ts(1345)
0
144
2
사용자 가입시 에러발생 (TypeError: Cannot read properties of null (reading 'addMembers')
1
178
2
초기세팅중 packge.json 에러떠요
0
156
2
CORS - Access-Control-Allow-Origin 누락 문제
0
431
3
로그인 페이지 무한 새로고침 현상
0
598
2
Module not found: Error: Can't resolve './App' 에러
0
959
1
배포 방법
0
298
2
npm run dev 시 빌드가 매우 느려졌습니다
0
990
2
alias 경로 설정 오류
0
452
2
fetcher 함수의 data 값이 두번 찍히는 이유
0
277
1
제네릭 질문
0
218
2
ts-node 대신 tsx 사용여부
0
373
1
배포 관련 질문
0
247
1
[nginx + https] 서비스를 실행하면 niginx가 아닌 서비스 화면을 보여주게 하고 싶습니다.
0
385
2
[배포하기] webpack에 aws 퍼블릭 IPv4 주소 와 포트 주소를 작성하고 나서 빌드후 실행하면 오류가 발생합니다.
0
336
1
users 호출 시 쿠키가 담기지 않는 이슈 질문드립니다.
0
247
2






front 코드와 똑같다면 돼야 합니다 ㅠ