From 45a147ae673750d7820ac56cd5b64c705f3860ce Mon Sep 17 00:00:00 2001 From: Tristan Yang Date: Mon, 11 Jul 2022 16:28:02 +0800 Subject: [PATCH] refactor: more TS code --- refactor.md | 8 +++ src/app/config.ts | 2 +- src/common/component/ContextMenu.tsx | 2 +- .../component/Message/PreviewMessage.tsx | 2 +- src/common/component/Message/Reaction.tsx | 2 +- src/common/component/Message/URLPreview.tsx | 14 ++--- src/common/component/Message/index.tsx | 40 +++++++++----- src/common/component/Send/index.tsx | 4 +- src/common/hook/useMessageFeed.ts | 8 ++- src/common/hook/usePinMessage.ts | 6 +-- src/routes/chat/ChannelList/NavItem.tsx | 20 ++++--- src/routes/chat/DMChat/index.tsx | 12 +++-- src/routes/chat/DMList/NavItem.tsx | 18 ++++--- src/routes/chat/FavList.tsx | 1 - src/routes/chat/Layout/index.tsx | 52 +++++++++---------- src/routes/chat/Layout/styled.tsx | 1 - src/routes/chat/SessionList/Session.tsx | 35 ++++++------- src/routes/chat/utils.tsx | 18 ++----- 18 files changed, 132 insertions(+), 113 deletions(-) create mode 100644 refactor.md diff --git a/refactor.md b/refactor.md new file mode 100644 index 00000000..83c2ec8a --- /dev/null +++ b/refactor.md @@ -0,0 +1,8 @@ +- hooks (90%) +- page/component (90%) +- service (100%) +- reducer (90%) +- utils (90%) + +- typos +- unused files/code/comments diff --git a/src/app/config.ts b/src/app/config.ts index 29399106..23513a2e 100644 --- a/src/app/config.ts +++ b/src/app/config.ts @@ -18,7 +18,7 @@ export const firebaseConfig = { appId: "1:526613312184:web:d13c92582baf470d487a4d", measurementId: "G-82RQ3YSCP7" }; -export const ChatPrefixs = { +export const ChatPrefixes = { channel: "#", user: "@" }; diff --git a/src/common/component/ContextMenu.tsx b/src/common/component/ContextMenu.tsx index 426d7a01..b7215fd5 100644 --- a/src/common/component/ContextMenu.tsx +++ b/src/common/component/ContextMenu.tsx @@ -3,7 +3,7 @@ import StyledMenu from "./styled/Menu"; export interface Item { title: string; - icon: string; + icon?: string; handler: (e: MouseEvent) => void; underline?: boolean; danger?: boolean; diff --git a/src/common/component/Message/PreviewMessage.tsx b/src/common/component/Message/PreviewMessage.tsx index e7a07587..7593c037 100644 --- a/src/common/component/Message/PreviewMessage.tsx +++ b/src/common/component/Message/PreviewMessage.tsx @@ -15,7 +15,7 @@ const PreviewMessage: FC = ({ mid = 0 }) => { }); if (!msg) return null; const { from_uid, created_at, content_type, content, thumbnail, properties } = msg; - const { name, avatar } = usersData[from_uid]; + const { name, avatar } = usersData[from_uid] || {}; return (
diff --git a/src/common/component/Message/Reaction.tsx b/src/common/component/Message/Reaction.tsx index 3291764d..841ce11d 100644 --- a/src/common/component/Message/Reaction.tsx +++ b/src/common/component/Message/Reaction.tsx @@ -143,7 +143,7 @@ export default function Reaction({ mid, reactions = null }) { return ( {Object.entries(reactions).map(([reaction, uids], idx) => { - const reacted = uids.findIndex((id) => id == currUid) > -1; + const reacted = uids.findIndex((id: number) => id == currUid) > -1; return uids.length > 0 ? ( ( + null + ); useEffect(() => { const getMetaData = async (url: string) => { // todo const { data } = await getInfo(url); - const title = data.title || data.site_name; - const description = data.description; - const ogImage = data.images.find((i) => !!i.url)?.url || ""; - const favicon = data.favicon_url || `${new URL(url).origin}/favicon.ico`; + const title = data?.title || data?.site_name || ""; + const description = data?.description || ""; + const ogImage = data?.images.find((i) => !!i.url)?.url || ""; + const favicon = data?.favicon_url || `${new URL(url).origin}/favicon.ico`; setFavicon(favicon); setData({ title, description, ogImage }); // console.log("wtf url", data); diff --git a/src/common/component/Message/index.tsx b/src/common/component/Message/index.tsx index 1f40b543..63be51b8 100644 --- a/src/common/component/Message/index.tsx +++ b/src/common/component/Message/index.tsx @@ -1,8 +1,7 @@ -import React, { useRef, useState, useEffect } from "react"; +import React, { useRef, useState, useEffect, FC } from "react"; import dayjs from "dayjs"; import isToday from "dayjs/plugin/isToday"; import isYesterday from "dayjs/plugin/isYesterday"; -import { useSelector } from "react-redux"; import useInView from "./useInView"; import Tippy from "@tippyjs/react"; import Reaction from "./Reaction"; @@ -17,29 +16,37 @@ import Tooltip from "../Tooltip"; import ContextMenu from "./ContextMenu"; import useContextMenu from "../../hook/useContextMenu"; import usePinMessage from "../../hook/usePinMessage"; +import { useAppSelector } from "../../../app/store"; // todo: move to root file dayjs.extend(isToday); dayjs.extend(isYesterday); - -function Message({ +interface IProps { + readOnly?: boolean; + contextId: number; + context?: "user" | "channel"; + read?: boolean; + mid: number; + updateReadIndex: (any) => void; +} +const Message: FC = ({ readOnly = false, - contextId = 0, - mid = "", + contextId, + mid, context = "user", updateReadIndex, read = true -}) { +}) => { const { visible: contextMenuVisible, handleContextMenuEvent, hideContextMenu } = useContextMenu(); const inviewRef = useInView(); const [edit, setEdit] = useState(false); const avatarRef = useRef(null); - const { getPinInfo } = usePinMessage(context == "channel" ? contextId : null); + const { getPinInfo } = usePinMessage(context == "channel" ? contextId : 0); const { message = {}, reactionMessageData, usersData - } = useSelector((store) => { + } = useAppSelector((store) => { return { reactionMessageData: store.reactionMessage, message: store.message[mid] || {}, @@ -75,7 +82,7 @@ function Message({ }, [mid, read]); const reactions = reactionMessageData[mid]; - const currUser = usersData[fromUid] || {}; + const currUser = usersData[fromUid]; // if (!message) return null; let timePrefix = null; const dayjsTime = dayjs(time); @@ -104,7 +111,7 @@ function Message({ content={} >
- +
-
+
- {currUser.name} + {currUser?.name} ); -} +}; export default React.memo(Message, (prevs, nexts) => { return prevs.mid == nexts.mid; }); diff --git a/src/common/component/Send/index.tsx b/src/common/component/Send/index.tsx index ba522d64..1c25c5fc 100644 --- a/src/common/component/Send/index.tsx +++ b/src/common/component/Send/index.tsx @@ -2,7 +2,7 @@ import { useEffect, useState, FC } from "react"; import useSendMessage from "../../hook/useSendMessage"; import useAddLocalFileMessage from "../../hook/useAddLocalFileMessage"; import { updateInputMode } from "../../../app/slices/ui"; -import { ContentTypes, ChatPrefixs } from "../../../app/config"; +import { ContentTypes, ChatPrefixes } from "../../../app/config"; import StyledSend from "./styled"; import UploadFileList from "./UploadFileList"; @@ -132,7 +132,7 @@ const Send: FC = ({ setMarkdownFullscreen((prev) => !prev); }; const name = context == "channel" ? channelsData[id]?.name : usersData[id]?.name; - const placeholder = `Send to ${ChatPrefixs[context]}${name} `; + const placeholder = `Send to ${ChatPrefixes[context]}${name} `; const members = context == "channel" ? (channelsData[id]?.is_public ? uids : channelsData[id]?.members) : []; return ( diff --git a/src/common/hook/useMessageFeed.ts b/src/common/hook/useMessageFeed.ts index 64fa21eb..6a490e92 100644 --- a/src/common/hook/useMessageFeed.ts +++ b/src/common/hook/useMessageFeed.ts @@ -48,7 +48,11 @@ const getFeedWithPagination = (config: Config): PageInfo => { }; let curScrollPos = 0; let oldScroll = 0; -export default function useMessageFeed({ context = "channel", id = null }) { +type Props = { + context?: "channel" | "user"; + id: number; +}; +export default function useMessageFeed({ context = "channel", id }: Props) { const [loadMoreChannelMsgs] = useLazyGetHistoryMessagesQuery(); const [loadMoreDmMsgs] = useLazyGetDMHistoryMsg(); const listRef = useRef([]); @@ -83,7 +87,7 @@ export default function useMessageFeed({ context = "channel", id = null }) { } }, [items, context, id]); useEffect(() => { - const serverMids = mids.filter((id) => { + const serverMids = mids.filter((id: number) => { const ts = +new Date(); return Math.abs(ts - id) > 10 * 1000; }); diff --git a/src/common/hook/usePinMessage.ts b/src/common/hook/usePinMessage.ts index cc70aa13..ab2f4869 100644 --- a/src/common/hook/usePinMessage.ts +++ b/src/common/hook/usePinMessage.ts @@ -15,15 +15,15 @@ export default function usePinMessage(cid: number) { const [unpin, { isError: isUnpinError, isLoading: isUnpinning, isSuccess: isUnpinSuccess }] = useUnpinMessageMutation(); const pinMessage = (mid: number) => { - if (!mid || !cid) return; + if (!mid) return; pin({ mid, gid: +cid }); }; const unpinMessage = (mid: number) => { - if (!mid || !cid) return; + if (!mid) return; unpin({ mid, gid: +cid }); }; const getPinInfo = (mid: number) => { - if (!cid || !channel) return; + if (!channel) return; const pins = channel.pinned_messages; if (!pins || pins.length == 0) return; const pinned = pins.find((p) => p.mid == mid); diff --git a/src/routes/chat/ChannelList/NavItem.tsx b/src/routes/chat/ChannelList/NavItem.tsx index beb9e19b..f648389a 100644 --- a/src/routes/chat/ChannelList/NavItem.tsx +++ b/src/routes/chat/ChannelList/NavItem.tsx @@ -1,8 +1,7 @@ -import { useState } from "react"; +import { useState, FC, MouseEvent } from "react"; import { useLocation, useNavigate } from "react-router-dom"; import { useDrop } from "react-dnd"; import { NativeTypes } from "react-dnd-html5-backend"; -import { useSelector } from "react-redux"; import Tippy from "@tippyjs/react"; import useContextMenu from "../../../common/hook/useContextMenu"; import ContextMenu from "../../../common/component/ContextMenu"; @@ -15,8 +14,13 @@ import { useUpdateMuteSettingMutation } from "../../../app/services/user"; import StyledLink from "./styled"; import ChannelIcon from "../../../common/component/ChannelIcon"; import { getUnreadCount } from "../utils"; - -const NavItem = ({ id, setFiles, toggleRemoveConfirm }) => { +import { useAppSelector } from "../../../app/store"; +interface IProps { + id: number; + setFiles: () => void; + toggleRemoveConfirm: () => void; +} +const NavItem: FC = ({ id, setFiles, toggleRemoveConfirm }) => { const { pathname } = useLocation(); const [inviteModalVisible, setInviteModalVisible] = useState(false); const navigate = useNavigate(); @@ -29,7 +33,7 @@ const NavItem = ({ id, setFiles, toggleRemoveConfirm }) => { handleContextMenuEvent, hideContextMenu } = useContextMenu(); - const { channel, mids, messageData, readIndex, muted, loginUid } = useSelector((store) => { + const { channel, mids, messageData, readIndex, muted, loginUid } = useAppSelector((store) => { return { channel: store.channels.byId[id], mids: store.channelMessage[id], @@ -39,7 +43,7 @@ const NavItem = ({ id, setFiles, toggleRemoveConfirm }) => { muted: store.footprint.muteChannels[id] }; }); - const handleChannelSetting = (evt) => { + const handleChannelSetting = (evt: MouseEvent) => { evt.preventDefault(); evt.stopPropagation(); const { id } = evt.currentTarget.dataset; @@ -142,7 +146,7 @@ const NavItem = ({ id, setFiles, toggleRemoveConfirm }) => { to={`/chat/channel/${id}`} >
- + {name}
@@ -174,7 +178,7 @@ const NavItem = ({ id, setFiles, toggleRemoveConfirm }) => { )} diff --git a/src/routes/chat/DMChat/index.tsx b/src/routes/chat/DMChat/index.tsx index bc6ea141..d75c9eef 100644 --- a/src/routes/chat/DMChat/index.tsx +++ b/src/routes/chat/DMChat/index.tsx @@ -1,4 +1,4 @@ -// import { useState, useEffect } from "react"; +import { FC } from "react"; import { useDebounce } from "rooks"; import Tippy from "@tippyjs/react"; import FavList from "../FavList"; @@ -15,8 +15,11 @@ import LoadMore from "../LoadMore"; import { renderMessageFragment } from "../utils"; import useMessageFeed from "../../../common/hook/useMessageFeed"; import { useAppSelector } from "../../../app/store"; - -export default function DMChat({ uid = 0, dropFiles = [] }) { +type Props = { + uid: number; + dropFiles: [File]; +}; +const DMChat: FC = ({ uid = 0, dropFiles }) => { const { list: msgIds, appends, @@ -104,4 +107,5 @@ export default function DMChat({ uid = 0, dropFiles = [] }) { ); -} +}; +export default DMChat; diff --git a/src/routes/chat/DMList/NavItem.tsx b/src/routes/chat/DMList/NavItem.tsx index 3e3c5dc2..e264a4a6 100644 --- a/src/routes/chat/DMList/NavItem.tsx +++ b/src/routes/chat/DMList/NavItem.tsx @@ -1,7 +1,7 @@ -import { useEffect, useState } from "react"; +import { useEffect, useState, FC } from "react"; import { NavLink, useNavigate, useMatch } from "react-router-dom"; import { useDrop } from "react-dnd"; -import { useSelector, useDispatch } from "react-redux"; +import { useDispatch } from "react-redux"; import { NativeTypes } from "react-dnd-html5-backend"; import dayjs from "dayjs"; @@ -16,13 +16,20 @@ dayjs.extend(relativeTime); import { renderPreviewMessage } from "../utils"; import User from "../../../common/component/User"; import { ContentTypes } from "../../../app/config"; -const NavItem = ({ uid, mid, unreads, setFiles }) => { +import { useAppSelector } from "../../../app/store"; +interface IProps { + uid: number; + mid?: number; + unreads: number; + setFiles: () => void; +} +const NavItem: FC = ({ uid, mid = 0, unreads, setFiles }) => { const [previewMsg, setPreviewMsg] = useState(null); const { messages: normalizedMessages, normalizeMessage } = useNormalizeMessage(); const dispatch = useDispatch(); const pathMatched = useMatch(`/chat/dm/${uid}`); const [updateReadIndex] = useReadMessageMutation(); - const { currMsg, currUser } = useSelector((store) => { + const { currMsg, currUser } = useAppSelector((store) => { return { currUser: store.users.byId[uid], currMsg: store.message[mid] @@ -73,7 +80,6 @@ const NavItem = ({ uid, mid, unreads, setFiles }) => { } }; if (!currUser) return null; - // console.log("preview msg", previewMsg, normalizedMessages); return ( { to={`/chat/dm/${uid}`} onContextMenu={handleContextMenuEvent} > - +
{currUser.name} diff --git a/src/routes/chat/FavList.tsx b/src/routes/chat/FavList.tsx index d2a56066..5ea0372e 100644 --- a/src/routes/chat/FavList.tsx +++ b/src/routes/chat/FavList.tsx @@ -102,7 +102,6 @@ export default function FavList({ cid = null, uid = null }) { ) : (
    {favorites.map(({ id }) => { - console.log("favv", id); return (
  • diff --git a/src/routes/chat/Layout/index.tsx b/src/routes/chat/Layout/index.tsx index 136e9bcb..bbc0e617 100644 --- a/src/routes/chat/Layout/index.tsx +++ b/src/routes/chat/Layout/index.tsx @@ -1,4 +1,4 @@ -import { useState, useRef, useEffect, FC, ReactElement } from "react"; +import { useState, useRef, useEffect, FC, ReactElement, MouseEvent } from "react"; import { useDrop } from "react-dnd"; import { NativeTypes } from "react-dnd-html5-backend"; import ImagePreviewModal from "../../../common/component/ImagePreviewModal"; @@ -6,17 +6,17 @@ import Send from "../../../common/component/Send"; import Styled from "./styled"; import Operations from "./Operations"; import useUploadFile from "../../../common/hook/useUploadFile"; -import { ChatPrefixs } from "../../../app/config"; +import { ChatPrefixes } from "../../../app/config"; import { useAppSelector } from "../../../app/store"; interface Props { children: ReactElement; header: ReactElement; aside: ReactElement | null; - users: ReactElement | null; - dropFiles: []; - context: string; - to: number | null; + users?: ReactElement; + dropFiles: [File]; + context: "channel" | "user"; + to: number; } const Layout: FC = ({ @@ -26,7 +26,7 @@ const Layout: FC = ({ users = null, dropFiles = [], context = "channel", - to = null + to }) => { const { addStageFile } = useUploadFile({ context, id: to }); const messagesContainer = useRef(null); @@ -42,7 +42,6 @@ const Layout: FC = ({ () => ({ accept: [NativeTypes.FILE], drop({ files }) { - console.log("drop files", files, context, to); if (files.length) { const filesData = files.map((file) => { const { size, type, name } = file; @@ -76,24 +75,23 @@ const Layout: FC = ({ useEffect(() => { const container = messagesContainer?.current; - if (container) { - // 点击查看大图 - container.addEventListener( - "click", - (evt) => { - console.log(evt); - const { target } = evt; - if (target.nodeName == "IMG" && target.classList.contains("preview")) { - const thumbnail = target.src; - const originUrl = target.dataset.origin || target.src; - const downloadLink = target.dataset.download || target.src; - const meta = JSON.parse(target.dataset.meta || "{}"); - setPreviewImage({ thumbnail, originUrl, downloadLink, ...meta }); - } - }, - true - ); - } + if (!container) return; + // 点击查看大图 + container.addEventListener( + "click", + (evt) => { + const target = evt.target as HTMLImageElement; + if (!target) return; + if (target.nodeName == "IMG" && target.classList.contains("preview")) { + const thumbnail = target.src; + const originUrl = target.dataset.origin || target.src; + const downloadLink = target.dataset.download || target.src; + const meta = JSON.parse(target.dataset.meta || "{}"); + setPreviewImage({ thumbnail, originUrl, downloadLink, ...meta }); + } + }, + true + ); }, []); const name = context == "channel" ? channelsData[to]?.name : usersData[to]?.name; @@ -116,7 +114,7 @@ const Layout: FC = ({
    -

    {`Send to ${ChatPrefixs[context]}${name}`}

    +

    {`Send to ${ChatPrefixes[context]}${name}`}

    Photos accept jpg, png, max size limit to 10M.
    diff --git a/src/routes/chat/Layout/styled.tsx b/src/routes/chat/Layout/styled.tsx index 8ad4d809..d833c6ae 100644 --- a/src/routes/chat/Layout/styled.tsx +++ b/src/routes/chat/Layout/styled.tsx @@ -111,7 +111,6 @@ const Styled = styled.article` height: 100%; background-color: rgba(0, 0, 0, 0.5); visibility: hidden; - /* pointer-events: none; */ &.visible { visibility: visible; } diff --git a/src/routes/chat/SessionList/Session.tsx b/src/routes/chat/SessionList/Session.tsx index 4ddaafba..3a1bbb67 100644 --- a/src/routes/chat/SessionList/Session.tsx +++ b/src/routes/chat/SessionList/Session.tsx @@ -1,5 +1,4 @@ -import { useState, useEffect } from "react"; -import { useSelector } from "react-redux"; +import { useState, useEffect, FC } from "react"; import dayjs from "dayjs"; import { useDrop } from "react-dnd"; import { NativeTypes } from "react-dnd-html5-backend"; @@ -8,22 +7,29 @@ import ContextMenu from "./ContextMenu"; import getUnreadCount, { renderPreviewMessage } from "../utils"; import User from "../../../common/component/User"; import Avatar from "../../../common/component/Avatar"; -import iconChannel from "../../../assets/icons/channel.svg?url"; +// import iconChannel from "../../../assets/icons/channel.svg?url"; import IconLock from "../../../assets/icons/lock.svg"; import useContextMenu from "../../../common/hook/useContextMenu"; import { useNavigate, NavLink } from "react-router-dom"; import useUploadFile from "../../../common/hook/useUploadFile"; +import { useAppSelector } from "../../../app/store"; // todo: move to root file dayjs.extend(relativeTime); - -export default function Session({ +interface IProps { + type?: "user" | "channel"; + id: number; + mid: number; + setDeleteChannelId: () => void; + setInviteChannelId: () => void; +} +const Session: FC = ({ type = "user", id, mid, setDeleteChannelId, setInviteChannelId -}) { +}) => { const navigate = useNavigate(); const { addStageFile } = useUploadFile({ context: type, id }); @@ -51,7 +57,7 @@ export default function Session({ const { visible: contextMenuVisible, handleContextMenuEvent, hideContextMenu } = useContextMenu(); const [data, setData] = useState(null); - const { messageData, userData, channelData, readIndex, loginUid, mids, muted } = useSelector( + const { messageData, userData, channelData, readIndex, loginUid, mids, muted } = useAppSelector( (store) => { return { mids: type == "user" ? store.userMessage.byId[id] : store.channelMessage[id], @@ -66,11 +72,6 @@ export default function Session({ } ); - const handleImageError = (evt) => { - evt.target.classList.add("channel_default"); - evt.target.src = iconChannel; - }; - useEffect(() => { const tmp = type == "user" ? userData[id] : channelData[id]; if (!tmp) return; @@ -108,14 +109,9 @@ export default function Session({ >
    {type == "user" ? ( - + ) : ( - // )}
    @@ -140,4 +136,5 @@ export default function Session({
  • ); -} +}; +export default Session; diff --git a/src/routes/chat/utils.tsx b/src/routes/chat/utils.tsx index f5a3bba2..31575e13 100644 --- a/src/routes/chat/utils.tsx +++ b/src/routes/chat/utils.tsx @@ -2,7 +2,7 @@ import React from "react"; import dayjs from "dayjs"; import styled from "styled-components"; import reactStringReplace from "react-string-replace"; -import { useDispatch, useSelector } from "react-redux"; +import { useDispatch } from "react-redux"; import { isImage } from "../../common/utils"; import { ContentTypes } from "../../app/config"; import Checkbox from "../../common/component/styled/Checkbox"; @@ -10,19 +10,7 @@ import Divider from "../../common/component/Divider"; import Message from "../../common/component/Message"; import { updateSelectMessages } from "../../app/slices/ui"; import Mention from "../../common/component/Message/Mention"; - -// function debounce(callback, wait = 2000, immediate = false) { -// let timeout = null; -// return function () { -// const callNow = immediate && !timeout; -// const next = () => callback.apply(this, arguments); -// clearTimeout(timeout); -// timeout = setTimeout(next, wait); -// if (callNow) { -// next(); -// } -// }; -// } +import { useAppSelector } from "../../app/store"; export function getUnreadCount({ mids = [], messageData = {}, loginUid = 0, readIndex = 0 }) { // console.log({ mids, loginUid, readIndex }); @@ -130,7 +118,7 @@ const StyledWrapper = styled.div` const MessageWrapper = ({ selectMode = false, context, id, mid, children, ...rest }) => { const dispatch = useDispatch(); - const selects = useSelector((store) => store.ui.selectMessages[`${context}_${id}`]); + const selects = useAppSelector((store) => store.ui.selectMessages[`${context}_${id}`]); const selected = !!(selects && selects.find((s) => s == mid)); const toggleSelect = () => { const operation = selected ? "remove" : "add";