refactor: add typescript support to project

This commit is contained in:
HD
2022-06-21 15:08:35 +08:00
parent 88ec2b742a
commit bd799ebea8
259 changed files with 1042 additions and 458 deletions
@@ -1,5 +1,4 @@
import { useState } from "react";
// import { useSelector } from "react-redux";
import styled from "styled-components";
import { hideAll } from "tippy.js";
import IconInvite from "../../assets/icons/add.person.svg";
@@ -12,7 +11,7 @@ import InviteModal from "./InviteModal";
const Styled = styled.ul`
z-index: 999;
user-select: none;
box-shadow: 0px 24px 48px -12px rgba(16, 24, 40, 0.18);
box-shadow: 0 24px 48px -12px rgba(16, 24, 40, 0.18);
border-radius: 12px;
color: #616161;
background: #fff;
@@ -64,7 +63,7 @@ export default function AddEntriesMenu() {
return !prevVisible;
});
};
const handleOpenChannelModal = (isPrivate) => {
const handleOpenChannelModal = (isPrivate: boolean) => {
setIsPrivate(isPrivate);
setChannelModalVisible(true);
hideAll();
@@ -1,5 +1,6 @@
import { useState, useEffect, memo } from "react";
import { getInitials, getInitialsAvatar } from "../utils";
const Avatar = ({ url = "", name = "unkonw name", type = "user", ...rest }) => {
// console.log("avatar url", url);
const [src, setSrc] = useState("");
@@ -1,7 +1,8 @@
import { useState } from "react";
import styled from "styled-components";
import Avatar from "../component/Avatar";
import Avatar from "./Avatar";
import uploadIcon from "../../assets/icons/upload.image.svg?url";
const StyledWrapper = styled.div`
width: 96px;
height: 96px;
@@ -59,6 +60,7 @@ const StyledWrapper = styled.div`
right: 0;
}
`;
export default function AvatarUploader({
url = "",
name = "",
@@ -1,14 +1,15 @@
import { useState } from "react";
import styled from "styled-components";
import ChannelModal from "../component/ChannelModal";
import InviteModal from "../component/InviteModal";
import { NavLink } from "react-router-dom";
import ChannelModal from "./ChannelModal";
import InviteModal from "./InviteModal";
import IconChat from "../../assets/icons/placeholder.chat.svg";
import IconAsk from "../../assets/icons/placeholder.question.svg";
import IconInvite from "../../assets/icons/placeholder.invite.svg";
import IconDownload from "../../assets/icons/placeholder.download.svg";
import { NavLink } from "react-router-dom";
import ContactsModal from "./ContactsModal";
import { useSelector } from "react-redux";
import { useAppSelector } from "../../app/store";
const Styled = styled.div`
margin-top: -50px;
display: flex;
@@ -65,8 +66,9 @@ const Styled = styled.div`
}
}
`;
export default function BlankPlaceholder({ type = "chat" }) {
const server = useSelector((store) => store.server);
const server = useAppSelector((store) => store.server);
const [inviteModalVisible, setInviteModalVisible] = useState(false);
const [createChannelVisible, setCreateChannelVisible] = useState(false);
const [contactListVisible, setContactListVisible] = useState(false);
@@ -1,8 +1,6 @@
// import React from 'react';
import styled from "styled-components";
import { useSelector } from "react-redux";
// import { useNavigate } from "react-router-dom";
import Avatar from "./Avatar";
import { useAppSelector } from "../../app/store";
const StyledWrapper = styled.div`
display: flex;
@@ -23,8 +21,8 @@ const StyledWrapper = styled.div`
}
.avatar {
cursor: pointer;
width: ${({ size }) => `${size}px`};
height: ${({ size }) => `${size}px`};
width: ${({ size }: { size: number }) => `${size}px`};
height: ${({ size }: { size: number }) => `${size}px`};
position: relative;
img {
border-radius: 50%;
@@ -47,8 +45,9 @@ const StyledWrapper = styled.div`
}
}
`;
export default function Channel({ interactive = true, id = "", compact = false, avatarSize = 32 }) {
const { channel, totalMemberCount } = useSelector((store) => {
const { channel, totalMemberCount } = useAppSelector((store) => {
return {
channel: store.channels.byId[id],
totalMemberCount: store.contacts.ids.length
@@ -1,17 +1,27 @@
// import React from 'react';
import { FC } from "react";
import styled from "styled-components";
import HashIcon from "../../assets/icons/channel.svg";
import LockHashIcon from "../../assets/icons/channel.private.svg";
import styled from "styled-components";
interface Props {
personal?: boolean;
muted?: boolean;
className: string;
}
const Styled = styled.div`
display: flex;
&.muted path {
fill: #d0d5dd;
}
`;
export default function ChannelIcon({ personal = false, muted = false, className }) {
const ChannelIcon: FC<Props> = ({ personal = false, muted = false, className }) => {
return (
<Styled className={`${muted ? "muted" : ""} ${className}`}>
{personal ? <LockHashIcon /> : <HashIcon />}
</Styled>
);
}
};
export default ChannelIcon;
@@ -1,20 +1,20 @@
import { useState, useEffect } from "react";
import toast from "react-hot-toast";
import { useNavigate } from "react-router-dom";
import { useSelector } from "react-redux";
import Modal from "../Modal";
import Button from "../styled/Button";
import ChannelIcon from "../ChannelIcon";
import Contact from "../Contact";
import StyledWrapper from "./styled";
// import StyledToggle from "../../component/styled/Toggle";
import StyledCheckbox from "../../component/styled/Checkbox";
import StyledCheckbox from "../styled/Checkbox";
import useFilteredUsers from "../../hook/useFilteredUsers";
import { useCreateChannelMutation } from "../../../app/services/channel";
import { useAppSelector } from "../../../app/store";
export default function ChannelModal({ personal = false, closeModal }) {
const { conactsData, loginUid } = useSelector((store) => {
const { conactsData, loginUid } = useAppSelector((store) => {
return { conactsData: store.contacts.byId, loginUid: store.authData.uid };
});
const navigateTo = useNavigate();
@@ -1,6 +1,4 @@
// import { useState } from "react";
import Tippy from "@tippyjs/react";
// import { useDispatch } from "react-redux";
import useContactOperation from "../../hook/useContactOperation";
import ContextMenu from "../ContextMenu";
@@ -1,5 +1,3 @@
// import React from "react";
import { useSelector } from "react-redux";
import { useNavigate } from "react-router-dom";
import Tippy from "@tippyjs/react";
import IconOwner from "../../../assets/icons/owner.svg";
@@ -8,6 +6,8 @@ import Profile from "../Profile";
import ContextMenu from "./ContextMenu";
import StyledWrapper from "./styled";
import useContextMenu from "../../hook/useContextMenu";
import { useAppSelector } from "../../../app/store";
export default function Contact({
cid = null,
owner = false,
@@ -21,7 +21,7 @@ export default function Contact({
}) {
const navigate = useNavigate();
const { visible: contextMenuVisible, handleContextMenuEvent, hideContextMenu } = useContextMenu();
const curr = useSelector((store) => store.contacts.byId[uid]);
const curr = useAppSelector((store) => store.contacts.byId[uid]);
const handleDoubleClick = () => {
navigate(`/chat/dm/${uid}`);
};
@@ -17,8 +17,8 @@ const StyledWrapper = styled.div`
}
.avatar {
cursor: pointer;
width: ${({ size }) => `${size}px`};
height: ${({ size }) => `${size}px`};
width: ${({ size }: { size: number }) => `${size}px`};
height: ${({ size }: { size: number }) => `${size}px`};
position: relative;
img {
object-fit: cover;
@@ -62,4 +62,5 @@ const StyledWrapper = styled.div`
}
}
`;
export default StyledWrapper;
@@ -1,4 +1,4 @@
import { useRef } from "react";
import { ChangeEvent, useRef, FC } from "react";
import styled from "styled-components";
import { NavLink } from "react-router-dom";
import { useOutsideClick } from "rooks";
@@ -13,12 +13,12 @@ const StyledWrapper = styled.div`
width: 440px;
max-height: 402px;
background: #fff;
box-shadow: 0px 25px 50px rgba(31, 41, 55, 0.25);
box-shadow: 0 25px 50px rgba(31, 41, 55, 0.25);
border-radius: 8px;
transition: all 0.5s ease;
/* overflow-y: scroll; */
.search {
box-shadow: 0px 1px 0px rgba(0, 0, 0, 0.1);
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.1);
padding: 8px;
width: -webkit-fill-available;
input {
@@ -51,12 +51,17 @@ const StyledWrapper = styled.div`
}
}
`;
export default function ContactsModal({ closeModal }) {
const wrapperRef = useRef();
interface Props {
closeModal: () => void;
}
const ContactsModal: FC<Props> = ({ closeModal }) => {
const wrapperRef = useRef<HTMLDivElement>(null);
const { contacts, updateInput, input } = useFilteredUsers();
useOutsideClick(wrapperRef, closeModal);
const handleSearch = (evt) => {
console.log("www");
const handleSearch = (evt: ChangeEvent<HTMLInputElement>) => {
updateInput(evt.target.value);
};
@@ -83,4 +88,6 @@ export default function ContactsModal({ closeModal }) {
</StyledWrapper>
</Modal>
);
}
};
export default ContactsModal;
@@ -1,7 +1,20 @@
// import { useRef } from "react";
// import styled from "styled-components";
import { FC, MouseEvent } from "react";
import StyledMenu from "./styled/Menu";
export default function ContextMenu({ items = [], hideMenu = null }) {
interface Item {
title: string;
icon: string;
handler: (e: MouseEvent) => void;
underline?: boolean;
danger?: boolean;
}
interface Props {
items: Item[];
hideMenu?: () => void;
}
const ContextMenu: FC<Props> = ({ items = [], hideMenu = null }) => {
return (
<StyledMenu>
{items.map((item) => {
@@ -38,4 +51,6 @@ export default function ContextMenu({ items = [], hideMenu = null }) {
})}
</StyledMenu>
);
}
};
export default ContextMenu;
@@ -1,11 +1,11 @@
// import { useEffect } from "react";
import styled from "styled-components";
import { useSelector } from "react-redux";
import soundIcon from "../../assets/icons/sound.on.svg?url";
import micIcon from "../../assets/icons/mic.on.svg?url";
import Avatar from "./Avatar";
import useConfig from "../hook/useConfig";
// import UserGuide from "./UserGuide";
import { useAppSelector } from "../../app/store";
const StyledWrapper = styled.div`
background-color: #f4f4f5;
position: sticky;
@@ -61,9 +61,10 @@ const StyledWrapper = styled.div`
}
}
`;
export default function CurrentUser() {
const { values: agoraConfig } = useConfig("agora");
const currUser = useSelector((store) => {
const currUser = useAppSelector((store) => {
return store.contacts.byId[store.authData.uid];
});
@@ -1,11 +1,16 @@
// import React from "react";
import { useState } from "react";
import { FC, useState } from "react";
import useDeleteMessage from "../hook/useDeleteMessage";
import StyledModal from "./styled/Modal";
import Button from "./styled/Button";
import Modal from "./Modal";
import PreviewMessage from "./Message/PreviewMessage";
export default function DeleteMessageConfirmModal({ closeModal, mids = [] }) {
interface Props {
closeModal: (b: boolean) => void;
mids?: number[] | number;
}
const DeleteMessageConfirmModal: FC<Props> = ({ closeModal, mids = [] }) => {
// const dispatch = useDispatch();
const mid_arr = mids ? (Array.isArray(mids) ? mids : [mids]) : [];
const [ids] = useState(mid_arr);
@@ -39,4 +44,6 @@ export default function DeleteMessageConfirmModal({ closeModal, mids = [] }) {
</StyledModal>
</Modal>
);
}
};
export default DeleteMessageConfirmModal;
@@ -1,5 +1,6 @@
// import React from "react";
import styled from "styled-components";
import { FC } from "react";
const StyledDivider = styled.hr`
display: block;
position: relative;
@@ -21,7 +22,14 @@ const StyledDivider = styled.hr`
color: #78787c;
}
`;
export default function Divider({ content }) {
interface Props {
content: string;
}
const Divider: FC<Props> = ({ content }) => {
if (!content) return null;
return <StyledDivider data-content={content}></StyledDivider>;
}
};
export default Divider;
@@ -1,9 +1,13 @@
import { useState, useEffect } from "react";
// import { NimblePicker } from "emoji-mart";
import { useState, useEffect, FC } from "react";
import { EmojiData } from "emoji-mart";
import "emoji-mart/css/emoji-mart.css";
import { Picker } from "emoji-mart";
// import data from "emoji-mart/data/";
import styled from "styled-components";
interface Props {
onSelect: (emoji: EmojiData) => void;
}
const StyledWrapper = styled.div`
filter: drop-shadow(0px 25px 50px rgba(31, 41, 55, 0.25));
border-radius: 12px;
@@ -20,7 +24,8 @@ const StyledWrapper = styled.div`
}
}
`;
export default function EmojiPicker({ onSelect, ...rest }) {
const EmojiPicker: FC<Props> = ({ onSelect, ...rest }) => {
const [visible, setVisible] = useState(false);
useEffect(() => {
const inter = setTimeout(() => {
@@ -49,4 +54,6 @@ export default function EmojiPicker({ onSelect, ...rest }) {
) : null}
</StyledWrapper>
);
}
};
export default EmojiPicker;
@@ -1,11 +1,12 @@
// import React from "react";
import styled from "styled-components";
import { useGetServerVersionQuery } from "../../app/services/server";
const Styled = styled.div`
display: flex;
flex-direction: column;
gap: 12px;
`;
export default function FAQ() {
const { data: serverVersion } = useGetServerVersionQuery();
console.log("build time", serverVersion);
@@ -13,7 +13,10 @@ import {
} from "./preview";
import { getFileIcon, formatBytes } from "../../utils";
import IconDownload from "../../../assets/icons/download.svg";
// todo
dayjs.extend(relativeTime);
const renderPreview = (data) => {
const { file_type, name, content } = data;
let preview = null;
@@ -1,5 +1,6 @@
import { useState } from "react";
import { ReactEventHandler, useState } from "react";
import styled from "styled-components";
const Styled = styled.div`
height: 100%;
display: flex;
@@ -15,12 +16,15 @@ const Styled = styled.div`
width: 100%;
}
`;
export default function Audio({ url = "" }) {
const [err, setErr] = useState(false);
const handleError = (err) => {
const handleError: ReactEventHandler<HTMLAudioElement> = (err) => {
console.log("audio err", err);
setErr(true);
};
if (!url) return null;
return (
<Styled>
@@ -1,7 +1,7 @@
import { useState, useEffect } from "react";
import styled from "styled-components";
const Styled = styled.div`
background-color: #fff;
height: 218px;
padding: 15px 15px 0 15px;
line-height: 1.4;
@@ -11,10 +11,11 @@ const Styled = styled.div`
background-color: #000;
color: #eee;
`;
export default function Code({ url = "" }) {
const [content, setContent] = useState("");
useEffect(() => {
const getContent = async (url) => {
const getContent = async (url: string) => {
if (!url) return;
const resp = await fetch(url);
const txt = await resp.text();
@@ -1,5 +1,6 @@
import { useState, useEffect } from "react";
import styled from "styled-components";
const Styled = styled.div`
background-color: #fff;
height: 218px;
@@ -9,10 +10,11 @@ const Styled = styled.div`
white-space: pre-wrap;
word-break: break-all;
`;
export default function Doc({ url = "" }) {
const [content, setContent] = useState("");
useEffect(() => {
const getContent = async (url) => {
const getContent = async (url: string) => {
if (!url) return;
const resp = await fetch(url);
const txt = await resp.text();
@@ -1,5 +1,6 @@
import React from "react";
import styled from "styled-components";
const Styled = styled.div`
height: 218px;
overflow: hidden;
@@ -9,6 +10,7 @@ const Styled = styled.div`
object-fit: cover;
}
`;
export default function Image({ url = "" }) {
if (!url) return null;
return (
@@ -1,6 +1,5 @@
// import { useState, useEffect } from "react";
import styled from "styled-components";
// import { Document, Page } from "react-pdf";
const Styled = styled.div`
padding: 8px;
/* height: 218px; */
@@ -10,6 +9,7 @@ const Styled = styled.div`
height: 100%;
}
`;
export default function Pdf({ url = "" }) {
// const [content, setContent] = useState("");
// const [pageNumber, setPageNumber] = useState(1);
@@ -1,5 +1,6 @@
import React from "react";
import styled from "styled-components";
const Styled = styled.div`
height: 218px;
video {
@@ -8,6 +9,7 @@ const Styled = styled.div`
object-fit: cover;
}
`;
export default function Video({ url = "" }) {
if (!url) return null;
return (
@@ -1,4 +1,5 @@
import styled from "styled-components";
const Styled = styled.div`
background: #f3f4f6;
border: 1px solid #d4d4d4;
@@ -67,4 +68,5 @@ const Styled = styled.div`
/* todo */
}
`;
export default Styled;
@@ -2,6 +2,8 @@ import { useState, useEffect } from "react";
import styled from "styled-components";
import { CircularProgressbar, buildStyles } from "react-circular-progressbar";
import "react-circular-progressbar/dist/styles.css";
import { getDefaultSize } from "../../utils";
const Styled = styled.div`
position: relative;
width: fit-content;
@@ -30,7 +32,7 @@ const Styled = styled.div`
}
}
`;
import { getDefaultSize } from "../../utils";
export default function ImageMessage({
uploading,
progress,
@@ -1,5 +1,6 @@
// import React from 'react'
import { FC } from "react";
import styled from "styled-components";
const Styled = styled.div`
background: #ecfdff;
border-radius: 4px;
@@ -12,10 +13,18 @@ const Styled = styled.div`
border-radius: 4px;
}
`;
export default function Progress({ value, width = "100%" }) {
interface Props {
value: number;
width?: string;
}
const Progress: FC<Props> = ({ value, width = "100%" }) => {
return (
<Styled style={{ width }}>
<div className="progress" style={{ width: `${value}%` }}></div>
</Styled>
);
}
};
export default Progress;
@@ -9,12 +9,16 @@ import useUploadFile from "../../hook/useUploadFile";
import useSendMessage from "../../hook/useSendMessage";
import Progress from "./Progress";
import { getFileIcon, formatBytes, isImage, getImageSize } from "../../utils";
// import { ReactComponent as IconDownload } from "../../../assets/icons/download.svg";
// import { ReactComponent as IconClose } from "../../../assets/icons/close.circle.svg";
import IconDownload from "../../../assets/icons/download.svg";
import IconClose from "../../../assets/icons/close.circle.svg";
// todo: move to root file
dayjs.extend(relativeTime);
const isLocalFile = (content) => {
return content && typeof content == "string" && content.startsWith("blob:");
const isLocalFile = (content: string) => {
return content.startsWith("blob:");
};
export default function FileMessage({
@@ -1,4 +1,5 @@
import styled from "styled-components";
const Styled = styled.div`
background: #f3f4f6;
border: 1px solid #d4d4d4;
@@ -58,4 +59,5 @@ const Styled = styled.div`
}
}
`;
export default Styled;
@@ -1,4 +1,4 @@
import { useState } from "react";
import { useState, MouseEvent } from "react";
// import toast from "react-hot-toast";
import Modal from "../Modal";
import Button from "../styled/Button";
@@ -13,7 +13,7 @@ import useSendMessage from "../../hook/useSendMessage";
import useFilteredChannels from "../../hook/useFilteredChannels";
import useFilteredUsers from "../../hook/useFilteredUsers";
import CloseIcon from "../../../assets/icons/close.circle.svg";
import StyledCheckbox from "../../component/styled/Checkbox";
import StyledCheckbox from "../styled/Checkbox";
import toast from "react-hot-toast";
export default function ForwardModal({ mids, closeModal }) {
@@ -31,7 +31,7 @@ export default function ForwardModal({ mids, closeModal }) {
// const { conactsData, loginUid } = useSelector((store) => {
// return { conactsData: store.contacts.byId, loginUid: store.authData.uid };
// });
const toggleCheck = ({ currentTarget }) => {
const toggleCheck = ({ currentTarget }: MouseEvent<HTMLLIElement>) => {
const { id, type = "user" } = currentTarget.dataset;
const ids = type == "user" ? selectedMembers : selectedChannels;
const updateState = type == "user" ? setSelectedMembers : setSelectedChannels;
@@ -3,6 +3,7 @@
import IconGithub from "../../assets/icons/github.svg";
import styled from "styled-components";
import Button from "./styled/Button";
const StyledSocialButton = styled(Button)`
width: 100%;
margin-bottom: 16px;
@@ -18,6 +19,7 @@ const StyledSocialButton = styled(Button)`
height: 24px;
}
`;
export default function GithubLoginButton({ config = {} }) {
const { client_id } = config;
const handleGithubLogin = () => {
@@ -1,11 +1,11 @@
import { useEffect } from "react";
import { FC, useEffect } from "react";
import { useGoogleLogin } from "react-google-login";
import googleSvg from "../../assets/icons/google.svg?url";
import toast from "react-hot-toast";
import styled from "styled-components";
import googleSvg from "../../assets/icons/google.svg?url";
import Button from "./styled/Button";
import { useLoginMutation } from "../../app/services/auth";
import toast from "react-hot-toast";
const StyledSocialButton = styled(Button)`
width: 100%;
margin-bottom: 16px;
@@ -21,8 +21,14 @@ const StyledSocialButton = styled(Button)`
height: 24px;
}
`;
export default function GoogleLoginButton({ clientId }) {
interface Props {
clientId: string;
}
const GoogleLoginButton: FC<Props> = ({ clientId }) => {
const [login, { isSuccess, isLoading }] = useLoginMutation();
const { signIn, loaded } = useGoogleLogin({
onScriptLoadFailure: (wtf) => {
console.error("google login script load failure", wtf);
@@ -39,6 +45,7 @@ export default function GoogleLoginButton({ clientId }) {
console.error("google login failure", wtf);
}
});
useEffect(() => {
if (isSuccess) {
toast.success("Login Successfully");
@@ -48,6 +55,7 @@ export default function GoogleLoginButton({ clientId }) {
const handleGoogleLogin = () => {
signIn();
};
// console.log("google login ", loaded);
return (
<StyledSocialButton disabled={!loaded || isLoading} onClick={handleGoogleLogin}>
@@ -55,4 +63,6 @@ export default function GoogleLoginButton({ clientId }) {
{loaded ? `Sign in with Google` : `Initailizing`}
</StyledSocialButton>
);
}
};
export default GoogleLoginButton;
@@ -3,6 +3,7 @@ import styled, { keyframes } from "styled-components";
import { useOutsideClick, useKey } from "rooks";
import { Ring } from "@uiball/loaders";
import Modal from "./Modal";
const AniFadeIn = keyframes`
from{
background: transparent;
@@ -11,6 +12,7 @@ to{
background: rgba(1, 1, 1, 0.9);
}
`;
const StyledWrapper = styled.div`
/* todo */
transition: all 0.5s ease;
@@ -58,6 +60,7 @@ const StyledWrapper = styled.div`
}
}
`;
export default function ImagePreviewModal({ download = true, data, closeModal }) {
const [url, setUrl] = useState(data?.thumbnail);
const [loading, setLoading] = useState(true);
@@ -1,8 +1,8 @@
// import { useEffect } from "react";
import styled from "styled-components";
import useInviteLink from "../hook/useInviteLink";
import Input from "./styled/Input";
import Button from "./styled/Button";
const StyledWrapper = styled.div`
display: flex;
flex-direction: column;
@@ -37,11 +37,13 @@ const StyledWrapper = styled.div`
margin-bottom: 20px;
}
`;
export default function InviteLink() {
const { generating, link, linkCopied, copyLink, generateNewLink } = useInviteLink();
const handleNewLink = () => {
generateNewLink();
};
return (
<StyledWrapper>
<span className="tip">Share this link to invite people to this server.</span>
@@ -1,14 +1,14 @@
import { useState, useEffect } from "react";
import styled from "styled-components";
import { useSelector } from "react-redux";
import toast from "react-hot-toast";
import Button from "../styled/Button";
import Input from "../styled/Input";
import Contact from "../Contact";
import StyledCheckbox from "../styled/Checkbox";
import { useAddMembersMutation } from "../../../app/services/channel";
import CloseIcon from "../../../assets/icons/close.svg";
import toast from "react-hot-toast";
import useFilteredUsers from "../../../common/hook/useFilteredUsers";
import useFilteredUsers from "../../hook/useFilteredUsers";
const Styled = styled.div`
padding-top: 16px;
@@ -1,7 +1,10 @@
import { useEffect, useState } from "react";
import { useEffect, useState, ChangeEvent } from "react";
import toast from "react-hot-toast";
import styled from "styled-components";
import useInviteLink from "../../hook/useInviteLink";
import Button from "../styled/Button";
import Input from "../styled/Input";
const Styled = styled.div`
padding: 16px 0;
.input {
@@ -64,8 +67,7 @@ const Styled = styled.div`
}
}
`;
import Button from "../styled/Button";
import Input from "../styled/Input";
export default function InviteByEmail({ cid = null }) {
const [email, setEmail] = useState("");
const { enableSMTP, linkCopied, link, copyLink, generateNewLink, generating } =
@@ -75,9 +77,11 @@ export default function InviteByEmail({ cid = null }) {
toast.success("Invite Link Copied!");
}
}, [linkCopied]);
const handleEmail = (evt) => {
const handleEmail = (evt: ChangeEvent<HTMLInputElement>) => {
setEmail(evt.target.value);
};
return (
<Styled>
<div className="invite">
@@ -1,9 +1,10 @@
// import React from 'react'
import styled from "styled-components";
import { useSelector } from "react-redux";
import InviteByEmail from "./InviteByEmail";
import AddMembers from "./AddMembers";
import CloseIcon from "../../../assets/icons/close.svg";
import Modal from "../Modal";
import { useAppSelector } from "../../../app/store";
const Styled = styled.div`
display: flex;
flex-direction: column;
@@ -26,10 +27,10 @@ const Styled = styled.div`
}
}
`;
import Modal from "../Modal";
// type: server,channel
export default function InviteModal({ type = "server", cid = null, title = "", closeModal }) {
const { channel, server } = useSelector((store) => {
const { channel, server } = useAppSelector((store) => {
return {
channel: store.channels.byId[cid],
server: store.server
@@ -1,4 +1,4 @@
import { useEffect } from "react";
import { useEffect, FC } from "react";
import toast from "react-hot-toast";
import { useNavigate } from "react-router-dom";
import useLeaveChannel from "../../hook/useLeaveChannel";
@@ -6,7 +6,13 @@ import Modal from "../Modal";
import StyledModal from "../styled/Modal";
import Button from "../styled/Button";
export default function LeaveConfirmModal({ id, closeModal, handleNextStep }) {
interface Props {
id: number;
closeModal: () => void;
handleNextStep: () => void;
}
const LeaveConfirmModal: FC<Props> = ({ id, closeModal, handleNextStep }) => {
const navigateTo = useNavigate();
const { isOwner, leaving, leaveChannel, leaveSuccess } = useLeaveChannel(id);
@@ -47,4 +53,6 @@ export default function LeaveConfirmModal({ id, closeModal, handleNextStep }) {
></StyledModal>
</Modal>
);
}
};
export default LeaveConfirmModal;
@@ -1,4 +1,4 @@
import { useEffect, useState } from "react";
import { useEffect, useState, FC } from "react";
import toast from "react-hot-toast";
import styled from "styled-components";
import { useNavigate } from "react-router-dom";
@@ -7,6 +7,7 @@ import useLeaveChannel from "../../hook/useLeaveChannel";
import StyledModal from "../styled/Modal";
import Button from "../styled/Button";
import Contact from "../Contact";
const UserList = styled.ul`
display: flex;
flex-direction: column;
@@ -28,7 +29,14 @@ const UserList = styled.ul`
}
}
`;
export default function TransferOwnerModal({ id, closeModal, withLeave = true }) {
interface Props {
id: number;
closeModal: () => void;
withLeave?: boolean;
}
const TransferOwnerModal: FC<Props> = ({ id, closeModal, withLeave = true }) => {
const {
transferOwner,
otherMembers,
@@ -97,4 +105,6 @@ export default function TransferOwnerModal({ id, closeModal, withLeave = true })
</StyledModal>
</Modal>
);
}
};
export default TransferOwnerModal;
@@ -1,12 +1,20 @@
import { useState } from "react";
// import styled from "styled-components";
import { useState, FC } from "react";
import TransferOwnerModal from "./TransferOwnerModal";
import LeaveConfirmModal from "./LeaveConfirmModal";
export default function LeaveChannel({ id = null, isOwner = false, closeModal }) {
interface Props {
id: number;
isOwner?: boolean;
closeModal: () => void;
}
const LeaveChannel: FC<Props> = ({ id, isOwner = false, closeModal }) => {
const [transferOwner, setTransferOwner] = useState(isOwner);
const handleNextStep = () => {
setTransferOwner(true);
};
if (transferOwner) return <TransferOwnerModal id={id} closeModal={closeModal} />;
return <LeaveConfirmModal id={id} closeModal={closeModal} handleNextStep={handleNextStep} />;
}
};
export default LeaveChannel;
@@ -1,9 +1,9 @@
// import { useState, useEffect } from "react";
import styled, { keyframes } from "styled-components";
import { Ring } from "@uiball/loaders";
import Button from "./styled/Button";
import useLogout from "../hook/useLogout";
const DelayVisible = keyframes`
from{
opacity: 0;
@@ -12,6 +12,7 @@ to{
opacity: 1;
}
`;
const StyledWrapper = styled.div`
width: 100%;
height: 100%;
@@ -37,12 +38,15 @@ const StyledWrapper = styled.div`
}
}
`;
export default function Loading({ reload = false, fullscreen = false }) {
const { clearLocalData } = useLogout();
const handleReload = () => {
clearLocalData();
// todo: only firefox has argument
location.reload(true);
};
return (
<StyledWrapper className={fullscreen ? "fullscreen" : ""}>
<Ring className="loading" size={40} lineWeight={5} speed={2} color="black" />
@@ -1,9 +1,7 @@
import { useEffect } from "react";
import styled from "styled-components";
import Tippy from "@tippyjs/react";
import { hideAll } from "tippy.js";
import { useSelector } from "react-redux";
import toast from "react-hot-toast";
import { useUpdateContactMutation } from "../../app/services/contact";
import Contact from "./Contact";
@@ -14,6 +12,8 @@ import IconOwner from "../../assets/icons/owner.svg";
import IconArrowDown from "../../assets/icons/arrow.down.mini.svg";
import IconCheck from "../../assets/icons/check.sign.svg";
import useContactOperation from "../hook/useContactOperation";
import { useAppSelector } from "../../app/store";
const StyledWrapper = styled.section`
display: flex;
flex-direction: column;
@@ -114,8 +114,9 @@ const StyledWrapper = styled.section`
}
}
`;
export default function ManageMembers({ cid = null }) {
const { contacts, channels, loginUser } = useSelector((store) => {
const { contacts, channels, loginUser } = useAppSelector((store) => {
return {
contacts: store.contacts,
channels: store.channels,
@@ -1,8 +1,10 @@
// import React from "react";
import { FC } from "react";
import styled from "styled-components";
import Modal from "../Modal";
import IconClose from "../../../assets/icons/close.svg";
import Button from "../../component/styled/Button";
// import { ReactComponent as IconClose } from "../../../assets/icons/close.svg";
import Button from "../styled/Button";
const Styled = styled.div`
position: relative;
margin-top: 15px;
@@ -11,7 +13,7 @@ const Styled = styled.div`
padding: 16px;
border-radius: 6px;
background: #fff;
box-shadow: 0px 25px 50px rgba(31, 41, 55, 0.25);
box-shadow: 0 25px 50px rgba(31, 41, 55, 0.25);
display: flex;
flex-direction: column;
gap: 12px;
@@ -44,7 +46,13 @@ const Styled = styled.div`
right: 16px;
}
`;
export default function Prompt({ handleInstall, closePrompt }) {
interface Props {
handleInstall?: () => void;
closePrompt?: () => void;
}
const Prompt: FC<Props> = ({ handleInstall, closePrompt }) => {
return (
<Modal mask={false}>
<Styled>
@@ -64,4 +72,6 @@ export default function Prompt({ handleInstall, closePrompt }) {
</Styled>
</Modal>
);
}
};
export default Prompt;
@@ -1,6 +1,4 @@
import { useEffect, useState, useRef } from "react";
// import { useGetServerQuery } from "../../../app/services/server";
// import manifest from "./manifest.json";
import Prompt from "./Prompt";
import usePrompt from "./usePrompt";
@@ -1,5 +1,6 @@
// import React from "react";
import { KEY_PWA_INSTALLED } from "../../../app/config";
export default function usePrompt() {
const resetPrompt = () => {
localStorage.removeItem(KEY_PWA_INSTALLED);
@@ -8,7 +8,7 @@ import codeSyntaxHighlight from "@toast-ui/editor-plugin-code-syntax-highlight/d
import StyledWrapper from "./styled";
import useUploadFile from "../../hook/useUploadFile";
import Button from "../../component/styled/Button";
import Button from "../styled/Button";
function MarkdownEditor({
updateDraft = null,
@@ -1,4 +1,5 @@
import styled from "styled-components";
const StyledWrapper = styled.div`
position: relative;
width: 100%;
@@ -3,11 +3,10 @@ import { useDispatch } from "react-redux";
import styled from "styled-components";
import Tippy from "@tippyjs/react";
import { hideAll } from "tippy.js";
import toast from "react-hot-toast";
import { updateSelectMessages } from "../../../app/slices/ui";
// import StyledMenu from "../styled/Menu";
import ContextMenu from "../ContextMenu";
import Tooltip from "../../component/Tooltip";
import Tooltip from "../Tooltip";
import useFavMessage from "../../hook/useFavMessage";
import useSendMessage from "../../hook/useSendMessage";
import ReactionPicker from "./ReactionPicker";
@@ -20,8 +19,8 @@ import IconForward from "../../../assets/icons/forward.svg";
import IconSelect from "../../../assets/icons/select.svg";
import IconDelete from "../../../assets/icons/delete.svg";
import moreIcon from "../../../assets/icons/more.svg?url";
import toast from "react-hot-toast";
import useMessageOperation from "./useMessageOperation";
const StyledCmds = styled.ul`
z-index: 999;
position: absolute;
@@ -62,6 +61,7 @@ const StyledCmds = styled.ul`
transform: translateX(-100%);
}
`;
export default function Commands({ context = "user", contextId = 0, mid = 0, toggleEditMessage }) {
const {
canDelete,
@@ -1,4 +1,3 @@
// import { useState } from "react";
import Tippy from "@tippyjs/react";
import { useDispatch } from "react-redux";
import ContextMenu from "../ContextMenu";
@@ -2,9 +2,10 @@ import { useState, useRef, useEffect } from "react";
import styled from "styled-components";
import TextareaAutosize from "react-textarea-autosize";
import { useKey } from "rooks";
import { useEditMessageMutation } from "../../../app/services/message";
import { useSelector } from "react-redux";
import { useEditMessageMutation } from "../../../app/services/message";
import { ContentTypes } from "../../../app/config";
const StyledWrapper = styled.div`
width: 100%;
.input {
@@ -45,8 +46,9 @@ const StyledWrapper = styled.div`
}
}
`;
export default function EditMessage({ mid, cancelEdit }) {
const inputRef = useRef();
const inputRef = useRef<HTMLTextAreaElement>(null);
const msg = useSelector((store) => store.message[mid] || {});
const [shift, setShift] = useState(false);
const [enter, setEnter] = useState(false);
@@ -95,6 +97,7 @@ export default function EditMessage({ mid, cancelEdit }) {
});
};
if (!msg) return null;
return (
<StyledWrapper>
<div className="input">
@@ -1,17 +1,17 @@
import { useEffect, useState } from "react";
// import dayjs from "dayjs";
import styled from "styled-components";
import StyledMsg from "./styled";
import renderContent from "./renderContent";
import Avatar from "../Avatar";
import useFavMessage from "../../hook/useFavMessage";
const StyledFav = styled.div`
display: flex;
flex-direction: column;
border-radius: var(--br);
background-color: #f4f4f5;
`;
const FavoritedMessage = ({ id }) => {
const { favorites } = useFavMessage({});
const [msgs, setMsgs] = useState(null);
@@ -5,6 +5,7 @@ import renderContent from "./renderContent";
import Avatar from "../Avatar";
import IconForward from "../../../assets/icons/forward.svg";
import useNormalizeMessage from "../../hook/useNormalizeMessage";
const StyledForward = styled.div`
display: flex;
flex-direction: column;
@@ -1,8 +1,9 @@
// import React from "react";
import { useSelector } from "react-redux";
import { FC } from "react";
import Tippy from "@tippyjs/react";
import Profile from "../Profile";
import styled from "styled-components";
import Profile from "../Profile";
import { useAppSelector } from "../../../app/store";
const Styled = styled.span`
padding: 0 2px;
color: #1fe1f9;
@@ -10,8 +11,16 @@ const Styled = styled.span`
cursor: pointer;
}
`;
export default function Mention({ uid, popover = true, cid, textOnly = false }) {
const contactsData = useSelector((store) => store.contacts.byId);
interface Props {
uid: number;
popover?: boolean;
cid: number;
textOnly?: boolean;
}
const Mention: FC<Props> = ({ uid, popover = true, cid, textOnly = false }) => {
const contactsData = useAppSelector((store) => store.contacts.byId);
const user = contactsData[uid];
if (!user) return null;
if (textOnly) return `@${user.name}`;
@@ -26,4 +35,6 @@ export default function Mention({ uid, popover = true, cid, textOnly = false })
<Styled className={popover ? "clickable" : ""}>{`@${user.name}`}</Styled>
</Tippy>
);
}
};
export default Mention;
@@ -1,6 +1,13 @@
// import React from "react";
import { FC } from "react";
import { useEffect } from "react";
import styled from "styled-components";
import toast from "react-hot-toast";
import usePinMessage from "../../hook/usePinMessage";
import StyledModal from "../styled/Modal";
import Button from "../styled/Button";
import Modal from "../Modal";
import PreviewMessage from "./PreviewMessage";
const StyledPinModal = styled(StyledModal)`
min-width: 406px;
.title,
@@ -15,24 +22,25 @@ const StyledPinModal = styled(StyledModal)`
overflow-x: hidden;
}
`;
import usePinMessage from "../../hook/usePinMessage";
import StyledModal from "../styled/Modal";
import Button from "../styled/Button";
import Modal from "../Modal";
import PreviewMessage from "./PreviewMessage";
import toast from "react-hot-toast";
export default function PinMessageModal({ closeModal, mid = 0, gid = 0 }) {
// const dispatch = useDispatch();
interface Props {
closeModal: () => void;
mid: number;
gid: number;
}
const PinMessageModal: FC<Props> = ({ closeModal, mid = 0, gid = 0 }) => {
const { channel, pinMessage, isPining, isSuccess } = usePinMessage(gid);
const handlePin = () => {
pinMessage(mid);
};
useEffect(() => {
if (isSuccess) {
closeModal();
toast.success("Pin Message Successfully");
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isSuccess]);
if (!mid) return null;
@@ -57,4 +65,6 @@ export default function PinMessageModal({ closeModal, mid = 0, gid = 0 }) {
</StyledPinModal>
</Modal>
);
}
};
export default PinMessageModal;
@@ -1,11 +1,11 @@
// import { useEffect, useRef, useState } from "react";
import dayjs from "dayjs";
import renderContent from "./renderContent";
import Avatar from "../Avatar";
import StyledWrapper from "./styled";
import { useSelector } from "react-redux";
import { useAppSelector } from "../../../app/store";
export default function PreviewMessage({ mid = 0 }) {
const { msg, contactsData } = useSelector((store) => {
const { msg, contactsData } = useAppSelector((store) => {
return { msg: store.message[mid], contactsData: store.contacts.byId };
});
if (!msg) return null;
@@ -1,4 +1,3 @@
// import { useState } from "react";
import { useSelector } from "react-redux";
import styled from "styled-components";
import { getEmojiDataFromNative } from "emoji-mart";
@@ -10,6 +9,7 @@ import ReactionPicker from "./ReactionPicker";
import Tooltip from "../Tooltip";
import { useReactMessageMutation } from "../../../app/services/message";
import addEmojiIcon from "../../../assets/icons/add.emoji.svg?url";
const StyledWrapper = styled.span`
position: relative;
margin-top: 8px;
@@ -1,9 +1,9 @@
import styled from "styled-components";
import { useSelector } from "react-redux";
import { useReactMessageMutation } from "../../../app/services/message";
import { Emojis } from "../../../app/config";
import Emoji from "../ReactionItem";
import { useAppSelector } from "../../../app/store";
const StyledPicker = styled.div`
background: none;
z-index: 999;
@@ -38,7 +38,7 @@ const StyledPicker = styled.div`
export default function ReactionPicker({ mid, hidePicker }) {
// const wrapperRef = useRef(null);
const [reactMessage, { isLoading }] = useReactMessageMutation();
const { reactionData, currUid } = useSelector((store) => {
const { reactionData, currUid } = useAppSelector((store) => {
return {
reactionData: store.reactionMessage[mid] || {},
currUid: store.authData.uid
@@ -1,6 +1,5 @@
import React from "react";
import React, { MouseEvent, FC } from "react";
import styled from "styled-components";
import { useSelector } from "react-redux";
import reactStringReplace from "react-string-replace";
import MrakdownRender from "../MrakdownRender";
import Mention from "./Mention";
@@ -8,6 +7,7 @@ import { ContentTypes } from "../../../app/config";
import { getFileIcon, isImage } from "../../utils";
import Avatar from "../Avatar";
import { useAppSelector } from "../../../app/store";
const Styled = styled.div`
display: flex;
align-items: flex-start;
@@ -16,19 +16,23 @@ const Styled = styled.div`
border-radius: var(--br);
gap: 8px;
margin-bottom: 4px;
&.clickable {
cursor: pointer;
}
.user {
display: flex;
align-items: center;
gap: 4px;
white-space: nowrap;
.avatar {
width: 16px;
height: 16px;
border-radius: 50%;
}
.name {
font-style: normal;
font-weight: 500;
@@ -37,6 +41,7 @@ const Styled = styled.div`
color: #06b6d4;
}
}
.content {
overflow: hidden;
font-weight: 500;
@@ -45,20 +50,22 @@ const Styled = styled.div`
color: #616161;
display: flex;
align-items: center;
.txt {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
text-overflow: ellipsis;
overflow: hidden;
-webkit-box-orient: vertical;
word-wrap: break-word;
word-break: break-all;
}
.md {
position: relative;
max-height: 152px;
overflow: hidden;
&:after {
position: absolute;
left: 0;
@@ -69,24 +76,29 @@ const Styled = styled.div`
background: linear-gradient(180deg, rgba(255, 255, 255, 0) 63.54%, #e5e7eb 93.09%);
}
}
.pic {
display: inherit;
width: 40px;
height: 40px;
object-fit: cover;
}
.icon {
width: 15px;
height: 20px;
}
.file_name {
margin-left: 5px;
font-size: 10px;
color: #555;
}
}
/* padding-left: 10px; */
`;
const renderContent = (data) => {
const { content_type, content, thumbnail, properties } = data;
let res = null;
@@ -136,13 +148,19 @@ const renderContent = (data) => {
}
return res;
};
const Reply = ({ mid, interactive = true }) => {
const { data, users } = useSelector((store) => {
interface ReplyProps {
mid: number;
interactive?: boolean;
}
const Reply: FC<ReplyProps> = ({ mid, interactive = true }) => {
const { data, users } = useAppSelector((store) => {
return { data: store.message[mid], users: store.contacts.byId };
});
const handleClick = (evt) => {
const handleClick = (evt: MouseEvent<HTMLDivElement>) => {
const { mid } = evt.currentTarget.dataset;
const msgEle = document.querySelector(`[data-msg-mid='${mid}']`);
const msgEle = document.querySelector<HTMLDivElement>(`[data-msg-mid='${mid}']`);
if (msgEle) {
msgEle.dataset.highlight = true;
msgEle.scrollIntoView({ behavior: "smooth", block: "center" });
@@ -1,7 +1,7 @@
import { useState, useEffect } from "react";
import styled from "styled-components";
import { useLazyGetOGInfoQuery } from "../../../app/services/message";
// import favUrl from "../../../assets/icons/favicon.default.svg?url";
const StyledCompact = styled.a`
background: #f3f4f6;
border: 1px solid #d4d4d4;
@@ -92,12 +92,13 @@ const StyledDetails = styled.a`
}
}
`;
export default function URLPreview({ url = "" }) {
const [favicon, setFavicon] = useState("");
const [getInfo] = useLazyGetOGInfoQuery();
const [data, setData] = useState(null);
useEffect(() => {
const getMetaData = async (url) => {
const getMetaData = async (url: string) => {
// todo
const { data } = await getInfo(url);
const title = data.title || data.site_name;
@@ -15,11 +15,13 @@ import EditMessage from "./EditMessage";
import renderContent from "./renderContent";
import Tooltip from "../Tooltip";
import ContextMenu from "./ContextMenu";
import useContextMenu from "../../hook/useContextMenu";
import usePinMessage from "../../hook/usePinMessage";
// todo: move to root file
dayjs.extend(isToday);
dayjs.extend(isYesterday);
function Message({
readOnly = false,
contextId = 0,
@@ -1,7 +1,7 @@
import React from "react";
import Linkit from "react-linkify";
import dayjs from "dayjs";
import reactStringReplace from "react-string-replace";
import { ContentTypes } from "../../../app/config";
import Mention from "./Mention";
import ForwardedMessage from "./ForwardedMessage";
@@ -9,7 +9,6 @@ import MrakdownRender from "../MrakdownRender";
import FileMessage from "../FileMessage";
import URLPreview from "./URLPreview";
import reactStringReplace from "react-string-replace";
const renderContent = ({
context = null,
to = null,
@@ -1,4 +1,5 @@
import styled from "styled-components";
const StyledMsg = styled.div`
position: relative;
display: flex;
@@ -1,7 +1,7 @@
import { useRef, useEffect } from "react";
export default function useInView() {
const ref = useRef(undefined);
export default function useInView<T extends HTMLElement>() {
const ref = useRef<T>(null);
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
@@ -17,7 +17,7 @@ export default function useInView() {
{ threshold: 0 }
);
useEffect(() => {
const currEle = ref?.current;
const currEle = ref.current;
if (currEle) {
observer.observe(ref.current);
}
@@ -4,13 +4,19 @@ import DeleteMessageConfirm from "../DeleteMessageConfirm";
import ForwardModal from "../ForwardModal";
import PinMessageModal from "./PinMessageModal";
import { ContentTypes } from "../../../app/config";
import { useSelector } from "react-redux";
import useCopy from "../../hook/useCopy";
import usePinMessage from "../../hook/usePinMessage";
import { useAppSelector } from "../../../app/store";
export default function useMessageOperation({ mid, context, contextId }) {
interface Params {
mid: number;
context: string;
contextId: number;
}
export default function useMessageOperation({ mid, context, contextId }: Params) {
const { copy } = useCopy();
const { content_type, properties, currUid, from_uid, content } = useSelector((store) => {
const { content_type, properties, currUid, from_uid, content } = useAppSelector((store) => {
return {
content: store.message[mid]?.content,
from_uid: store.message[mid]?.from_uid,
@@ -1,6 +1,5 @@
// import React from 'react'
import { Helmet } from "react-helmet";
import BASE_URL from "../../app/config";
import { useGetServerQuery } from "../../app/services/server";
@@ -4,6 +4,7 @@ import { Transforms } from "slate";
import { ReactEditor } from "slate-react";
import styled from "styled-components";
import iconClose from "../../../assets/icons/close.circle.svg?url";
const StylePicture = styled.picture`
position: relative;
display: flex;
@@ -21,6 +22,7 @@ const StylePicture = styled.picture`
right: -20px;
}
`;
export default function ImageElement({ attributes, children, element }) {
const editor = useEditorRef();
const path = ReactEditor.findPath(editor, element);
@@ -1,8 +1,9 @@
// import React from 'react'
import styled from "styled-components";
const Styled = styled.div`
display: flex;
`;
export default function StyledCombobox({ store }) {
console.log("combox wtf", store.get.state());
return <Styled>StyledCombobox</Styled>;
@@ -1,4 +1,5 @@
import styled from "styled-components";
const Styled = styled.div`
max-height: 50vh;
overflow: auto;
@@ -1,4 +1,4 @@
import { useEffect, useState, useRef } from "react";
import { useEffect, useState, useRef, FC } from "react";
import "prismjs/themes/prism.css";
import "@toast-ui/editor-plugin-code-syntax-highlight/dist/toastui-editor-plugin-code-syntax-highlight.css";
import codeSyntaxHighlight from "@toast-ui/editor-plugin-code-syntax-highlight/dist/toastui-editor-plugin-code-syntax-highlight-all.js";
@@ -11,7 +11,7 @@ import codeSyntaxHighlight from "@toast-ui/editor-plugin-code-syntax-highlight/d
import { Viewer } from "@toast-ui/react-editor";
import styled from "styled-components";
import ImagePreviewModal from "../../common/component/ImagePreviewModal";
import ImagePreviewModal from "./ImagePreviewModal";
const Styled = styled.div`
* {
@@ -23,9 +23,15 @@ const Styled = styled.div`
align-items: flex-start;
}
`;
export default function MrakdownRender({ content }) {
const mdContainer = useRef(undefined);
interface Props {
content: string;
}
const MrakdownRender: FC<Props> = ({ content }) => {
const mdContainer = useRef<HTMLDivElement>(null);
const [previewImage, setPreviewImage] = useState(null);
useEffect(() => {
const container = mdContainer?.current;
if (container) {
@@ -50,9 +56,11 @@ export default function MrakdownRender({ content }) {
);
}
}, []);
const closePreviewModal = () => {
setPreviewImage(null);
};
return (
<>
{previewImage && (
@@ -67,4 +75,6 @@ export default function MrakdownRender({ content }) {
</Styled>
</>
);
}
};
export default MrakdownRender;
@@ -1,6 +1,5 @@
import { useEffect } from "react";
import { useNavigate } from "react-router-dom";
// import toast, { Toaster } from 'react-hot-toast';
import useDeviceToken from "./useDeviceToken";
import { vapidKey } from "../../../app/config";
import { useUpdateDeviceTokenMutation } from "../../../app/services/auth";
@@ -16,7 +15,7 @@ const Notification = () => {
}, [token]);
useEffect(() => {
const handleServiceworkerMessage = (event) => {
const handleServiceworkerMessage = (event: MessageEvent) => {
const { newPath } = event.data;
navigateTo(newPath);
};
@@ -2,6 +2,7 @@ import { useState } from "react";
import { initializeApp } from "firebase/app";
import { getToken, getMessaging } from "firebase/messaging";
import { firebaseConfig } from "../../../app/config";
const useDeviceToken = (vapidKey) => {
const [token, setToken] = useState(null);
// https only
@@ -28,4 +29,5 @@ const useDeviceToken = (vapidKey) => {
}
return token;
};
export default useDeviceToken;
@@ -1,9 +1,6 @@
// import { useEffect } from "react";
import { useSelector } from "react-redux";
import { FC } from "react";
import { NavLink } from "react-router-dom";
// import toast from "react-hot-toast";
import Tippy from "@tippyjs/react";
// import { hideAll } from "tippy.js";
import IconMessage from "../../../assets/icons/message.svg";
import IconCall from "../../../assets/icons/call.svg";
import IconMore from "../../../assets/icons/more.svg";
@@ -11,8 +8,15 @@ import Avatar from "../Avatar";
import StyledWrapper from "./styled";
import StyledMenu from "../styled/Menu";
import useContactOperation from "../../hook/useContactOperation";
import { useAppSelector } from "../../../app/store";
export default function Profile({ uid = null, type = "embed", cid = null }) {
interface Props {
uid: number;
type: string;
cid: number;
}
const Profile: FC<Props> = ({ uid = null, type = "embed", cid = null }) => {
const {
canCall,
call,
@@ -24,7 +28,7 @@ export default function Profile({ uid = null, type = "embed", cid = null }) {
removeUser
} = useContactOperation({ uid, cid });
const { data } = useSelector((store) => {
const { data } = useAppSelector((store) => {
return {
data: store.contacts.byId[uid]
};
@@ -103,4 +107,6 @@ export default function Profile({ uid = null, type = "embed", cid = null }) {
</ul>
</StyledWrapper>
);
}
};
export default Profile;
@@ -1,13 +1,20 @@
// import React from 'react'
import React, { FC, ReactElement } from "react";
import { Navigate } from "react-router-dom";
import { useSelector } from "react-redux";
import { useGetInitializedQuery } from "../../app/services/auth";
import { useAppSelector } from "../../app/store";
export default function RequireAuth({ children, redirectTo = "/login" }) {
interface Props {
children: ReactElement;
redirectTo?: string;
}
const RequireAuth: FC<Props> = ({ children, redirectTo = "/login" }) => {
const { isLoading } = useGetInitializedQuery();
const { token, initialized } = useSelector((store) => store.authData);
const { token, initialized } = useAppSelector((store) => store.authData);
if (isLoading) return null;
// 未初始化 则先走setup 流程
if (!initialized) return <Navigate to={`/onboarding`} replace />;
return token ? children : <Navigate to={redirectTo} replace />;
}
};
export default RequireAuth;
@@ -1,13 +1,20 @@
// import React from 'react'
import { FC, ReactElement } from "react";
import { Navigate } from "react-router-dom";
import { useGetInitializedQuery } from "../../app/services/auth";
import { useSelector } from "react-redux";
import { useAppSelector } from "../../app/store";
export default function RequireNoAuth({ children, redirectTo = "/" }) {
interface Props {
children: ReactElement;
redirectTo?: string;
}
const RequireNoAuth: FC<Props> = ({ children, redirectTo = "/" }) => {
const { isLoading } = useGetInitializedQuery();
const { token, initialized } = useSelector((store) => store.authData);
const { token, initialized } = useAppSelector((store) => store.authData);
if (isLoading) return null;
// 未初始化 则先走setup 流程
if (!initialized) return <Navigate to={`/onboarding`} replace />;
return token ? <Navigate to={redirectTo} replace /> : children;
}
};
export default RequireNoAuth;
@@ -1,5 +1,6 @@
// import React from "react";
import { FC, MouseEvent } from "react";
import styled from "styled-components";
const StyledWrapper = styled.div`
width: 100%;
position: absolute;
@@ -47,7 +48,13 @@ const StyledWrapper = styled.div`
}
}
`;
export default function SaveTip({ saveHandler, resetHandler }) {
interface Props {
saveHandler: (e: MouseEvent) => void;
resetHandler: (e: MouseEvent) => void;
}
const SaveTip: FC<Props> = ({ saveHandler, resetHandler }) => {
return (
<StyledWrapper className="animate__animated animate__flipInX animate__faster">
<span className="txt">You have unsaved changes!</span>
@@ -61,4 +68,6 @@ export default function SaveTip({ saveHandler, resetHandler }) {
</div>
</StyledWrapper>
);
}
};
export default SaveTip;
@@ -1,12 +1,9 @@
// import { useState } from "react";
import styled from "styled-components";
import Tippy from "@tippyjs/react";
import searchIcon from "../../assets/icons/search.svg?url";
import addIcon from "../../assets/icons/add.svg?url";
import AddEntriesMenu from "./AddEntriesMenu";
import Tooltip from "../component/Tooltip";
import Tooltip from "./Tooltip";
const StyledWrapper = styled.div`
position: relative;
@@ -16,7 +13,7 @@ const StyledWrapper = styled.div`
align-items: center;
justify-content: space-between;
gap: 8px;
box-shadow: 0px 1px 0px rgba(0, 0, 0, 0.1);
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.1);
.search {
display: flex;
align-items: center;
@@ -35,6 +32,7 @@ const StyledWrapper = styled.div`
cursor: pointer;
}
`;
export default function Search() {
console.log("searching");
return (
@@ -1,7 +1,10 @@
import { useState, useRef } from "react";
import styled from "styled-components";
import { useOutsideClick } from "rooks";
import Tooltip from "../../component/Tooltip";
import Tooltip from "../Tooltip";
import Picker from "../EmojiPicker";
import SmileIcon from "../../../assets/icons/emoji.smile.svg";
const Styled = styled.div`
position: relative;
width: fit-content;
@@ -25,20 +28,20 @@ const Styled = styled.div`
}
}
`;
import Picker from "../EmojiPicker";
import SmileIcon from "../../../assets/icons/emoji.smile.svg";
export default function EmojiPicker({ selectEmoji }) {
const ref = useRef();
const ref = useRef<HTMLDivElement>(null);
const [visible, setVisible] = useState(false);
const togglePickerVisible = () => {
setVisible((prev) => !prev);
};
const handleSelect = (emoji) => {
console.log("semojii", emoji);
selectEmoji(emoji.native);
};
useOutsideClick(
ref,
(evt) => {
@@ -53,6 +56,7 @@ export default function EmojiPicker({ selectEmoji }) {
},
visible
);
return (
<Tooltip placement="top" tip="Emojis" disabled={visible}>
<Styled>
@@ -1,6 +1,5 @@
// import React from 'react'
import { useSelector } from "react-redux";
import reactStringReplace from "react-string-replace";
import styled from "styled-components";
import Mention from "../Message/Mention";
import { ContentTypes } from "../../../app/config";
import MrakdownRender from "../MrakdownRender";
@@ -8,12 +7,12 @@ import closeIcon from "../../../assets/icons/close.circle.svg?url";
import pictureIcon from "../../../assets/icons/picture.svg?url";
import { getFileIcon, isImage } from "../../utils";
import useSendMessage from "../../hook/useSendMessage";
import styled from "styled-components";
import { useAppSelector } from "../../../app/store";
const Styled = styled.div`
background-color: #f3f4f6;
z-index: 999;
display: flex;
justify-content: space-between;
align-items: flex-start;
justify-content: flex-start;
gap: 16px;
@@ -120,9 +119,10 @@ const renderContent = (data) => {
// console.log("replying data", data);
return res;
};
export default function Replying({ context, id, mid }) {
const { removeReplying } = useSendMessage({ to: id, context });
const { msg, contactsData } = useSelector((store) => {
const { msg, contactsData } = useAppSelector((store) => {
return { contactsData: store.contacts.byId, msg: store.message[mid] };
});
const removeReply = () => {
@@ -1,11 +1,12 @@
import { useRef } from "react";
import { ChangeEvent, useRef } from "react";
import styled from "styled-components";
import Tooltip from "../../component/Tooltip";
import Tooltip from "../Tooltip";
import AddIcon from "../../../assets/icons/add.solid.svg";
import MarkdownIcon from "../../../assets/icons/markdown.svg";
import FullscreenIcon from "../../../assets/icons/fullscreen.svg";
import ExitFullscreenIcon from "../../../assets/icons/fullscreen.exit.svg";
import useUploadFile from "../../hook/useUploadFile";
const Styled = styled.div`
display: flex;
align-items: center;
@@ -41,6 +42,7 @@ const Styled = styled.div`
}
}
`;
export default function Toolbar({
toggleMarkdownFullscreen,
fullscreen,
@@ -49,9 +51,10 @@ export default function Toolbar({
to,
context
}) {
// todo: check code logic
const { addStageFile } = useUploadFile({ context, id: to });
const fileInputRef = useRef(null);
const handleUpload = (evt) => {
const fileInputRef = useRef<HTMLInputElement>(null);
const handleUpload = (evt: ChangeEvent<HTMLInputElement>) => {
const files = [...evt.target.files];
console.log("files", files);
const filesData = files.map((file) => {
@@ -64,6 +67,7 @@ export default function Toolbar({
fileInputRef.current.value = "";
// setFiles([...evt.target.files]);
};
return (
<Styled className={mode}>
<div className="md">
@@ -4,6 +4,7 @@ import Modal from "../../Modal";
import Button from "../../styled/Button";
import Input from "../../styled/Input";
import CloseIcon from "../../../../assets/icons/close.svg";
const StyledWrapper = styled.div`
background: #fff;
display: flex;
@@ -1,15 +1,15 @@
import styled from "styled-components";
const Styled = styled.ul`
width: 100%;
overflow: auto;
display: flex;
justify-content: flex-start;
gap: 24px;
width: 100%;
padding: 24px 16px 16px 16px;
background: #e5e7eb;
box-shadow: 0px 1px 0px rgba(0, 0, 0, 0.05);
border-radius: 8px 8px 0px 0px;
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.05);
border-radius: 8px 8px 0 0;
.file {
position: relative;
display: flex;
@@ -68,4 +68,5 @@ const Styled = styled.ul`
}
}
`;
export default Styled;
@@ -1,5 +1,4 @@
import { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import useSendMessage from "../../hook/useSendMessage";
import useAddLocalFileMessage from "../../hook/useAddLocalFileMessage";
import { updateInputMode } from "../../../app/slices/ui";
@@ -15,6 +14,7 @@ import MarkdownEditor from "../MarkdownEditor";
import MixedInput, { useMixedEditor } from "../MixedInput";
import useDraft from "../../hook/useDraft";
import useUploadFile from "../../hook/useUploadFile";
import { useAppDispatch, useAppSelector } from "../../../app/store";
const Modes = {
text: "text",
@@ -30,7 +30,7 @@ function Send({
const editor = useMixedEditor(`${context}_${id}`);
const [markdownEditor, setMarkdownEditor] = useState(null);
const [markdownFullscreen, setMarkdownFullscreen] = useState(false);
const dispatch = useDispatch();
const dispatch = useAppDispatch();
const addLocalFileMesage = useAddLocalFileMessage({ context, to: id });
// 谁发的
const {
@@ -41,7 +41,7 @@ function Send({
channelsData,
contactsData,
uids
} = useSelector((store) => {
} = useAppSelector((store) => {
return {
channelsData: store.channels.byId,
uids: store.contacts.ids,
@@ -174,6 +174,7 @@ function Send({
</StyledSend>
);
}
export default Send;
// export default memo(Send, (prevs, nexts) => {
// console.log("send name", prevs.name, nexts.name);
@@ -1,11 +1,10 @@
// import { useState } from "react";
import styled from "styled-components";
import { useSelector } from "react-redux";
import { NavLink, useLocation } from "react-router-dom";
import Tippy from "@tippyjs/react";
import addIcon from "../../assets/icons/add.svg?url";
import Tooltip from "./Tooltip";
import { NavLink, useLocation } from "react-router-dom";
import AddEntriesMenu from "./AddEntriesMenu";
import { useAppSelector } from "../../app/store";
const StyledWrapper = styled.div`
min-height: 56px;
@@ -52,9 +51,10 @@ const StyledWrapper = styled.div`
cursor: pointer;
}
`;
export default function Server() {
const { pathname } = useLocation();
const { server, userCount } = useSelector((store) => {
const { server, userCount } = useAppSelector((store) => {
return {
userCount: store.contacts.ids.length,
server: store.server
@@ -62,6 +62,7 @@ export default function Server() {
});
// console.log("server info", server);
const { name, description, logo } = server;
return (
<StyledWrapper>
<NavLink to={`/setting?f=${pathname}`}>
@@ -1,8 +1,8 @@
// import React from 'react'
import { FC, ReactElement } from "react";
import styled from "styled-components";
import { useLocation, NavLink } from "react-router-dom";
import backIcon from "../../assets/icons/arrow.left.svg?url";
const StyledWrapper = styled.div`
width: 100vw;
height: 100vh;
@@ -81,14 +81,32 @@ const StyledWrapper = styled.div`
}
}
`;
export default function StyledSettingContainer({
interface Nav {
name?: string;
title: string;
// todo
items: [];
component: ReactElement;
}
interface Props {
closeModal: () => void;
title?: string;
navs: Nav[];
dangers: [];
nav: Nav;
children: ReactElement;
}
const StyledSettingContainer: FC<Props> = ({
closeModal,
title = "Settings",
navs = [],
dangers = [],
nav,
children
}) {
}) => {
const { pathname } = useLocation();
return (
<StyledWrapper>
@@ -129,4 +147,6 @@ export default function StyledSettingContainer({
</div>
</StyledWrapper>
);
}
};
export default StyledSettingContainer;

Some files were not shown because too many files have changed in this diff Show More