feat: favorite message

This commit is contained in:
zerosoul
2022-04-29 17:17:07 +08:00
parent 9639073f31
commit 0e2d5b9fde
20 changed files with 708 additions and 171 deletions
+6 -4
View File
@@ -103,7 +103,7 @@ export default function FileMessage({
if (!properties) return null;
const icon = getFileIcon(content_type, name);
if (!content || !fromUser || !name) return null;
if (!content || !name) return null;
console.log("file content", content, name, content_type, size);
const sending = uploadingFile || isSending;
@@ -134,9 +134,11 @@ export default function FileMessage({
<>
<i className="size">{formatBytes(size)}</i>
<i className="time">{dayjs(created_at).fromNow()}</i>
<i className="from">
by <strong>{fromUser.name}</strong>
</i>
{fromUser && (
<i className="from">
by <strong>{fromUser.name}</strong>
</i>
)}
</>
)}
</span>
+1 -1
View File
@@ -45,7 +45,7 @@ export default function ForwardModal({ mids, closeModal }) {
};
const handleForward = async () => {
await forwardMessage({
mids: mids,
mids: mids.map((mid) => +mid),
users: selectedMembers,
channels: selectedChannels,
});
+73 -12
View File
@@ -1,4 +1,4 @@
import { useState, useRef } from "react";
import { useState, useRef, useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import styled from "styled-components";
import Tippy from "@tippyjs/react";
@@ -9,15 +9,18 @@ import { addReplyingMessage } from "../../../app/slices/message";
import StyledMenu from "../styled/Menu";
import Tooltip from "../../component/Tooltip";
import DeleteMessageConfirm from "../DeleteMessageConfirm";
import useFavMessage from "../../hook/useFavMessage";
import EmojiPicker from "./EmojiPicker";
import replyIcon from "../../../assets/icons/reply.svg?url";
import reactIcon from "../../../assets/icons/reaction.svg?url";
import editIcon from "../../../assets/icons/edit.svg?url";
// import bookmarkIcon from "../../../assets/icons/bookmark.svg?url";
import IconBookmark from "../../../assets/icons/bookmark.svg";
import moreIcon from "../../../assets/icons/more.svg?url";
import ForwardModal from "../ForwardModal";
import PinMessageModal from "./PinMessageModal";
import usePinMessage from "../../hook/usePinMessage";
import { ContentTypes } from "../../../app/config";
import toast from "react-hot-toast";
const StyledCmds = styled.ul`
z-index: 9999;
position: absolute;
@@ -40,10 +43,16 @@ const StyledCmds = styled.ul`
&:hover {
background-color: #f3f4f6;
}
img {
img,
svg {
width: 24px;
height: 24px;
}
&.fav {
svg path {
fill: #667085;
}
}
}
> .picker {
position: absolute;
@@ -53,18 +62,23 @@ const StyledCmds = styled.ul`
}
`;
export default function Commands({
content_type = ContentTypes.text,
context = "user",
contextId = 0,
mid = 0,
from_uid = 0,
toggleEditMessage,
}) {
const { addFavorite, isFavorited } = useFavMessage(
context == "channel" ? contextId : null
);
const [mids, setMids] = useState([]);
const dispatch = useDispatch();
const [pinModalVisible, setPinModalVisible] = useState(false);
const [deleteModalVisible, setDeleteModalVisible] = useState(false);
const [forwardModalVisible, setForwardModalVisible] = useState(false);
const [tippyVisible, setTippyVisible] = useState(false);
const { canPin } = usePinMessage(
const { canPin, pins, unpinMessage, isUnpinSuccess } = usePinMessage(
context == "channel" ? contextId : undefined
);
const currUid = useSelector((store) => store.authData.uid);
@@ -79,6 +93,7 @@ export default function Commands({
};
const toggleForwardModal = () => {
hideAll();
console.log("midss", mids);
setForwardModalVisible((prev) => !prev);
};
const toggleDeleteModal = () => {
@@ -96,13 +111,55 @@ export default function Commands({
dispatch(updateSelectMessages({ context, id: contextId, data: mid }));
hideAll();
};
const handleUnpin = () => {
hideAll();
unpinMessage(mid);
};
const handleAddFav = async () => {
hideAll();
const faved = isFavorited(mid);
if (faved) {
toast.success("Favorited!");
return;
}
await addFavorite(mid);
toast.success("Added Favorites!");
};
useEffect(() => {
if (isUnpinSuccess) {
toast.success("Unpin Message Successfully!");
}
}, [isUnpinSuccess]);
useEffect(() => {
if (content_type == ContentTypes.archive) {
// forward message
const forwardEle = document.querySelector(
`[data-msg-mid='${mid}'] .down [data-forwarded-mids]`
);
if (forwardEle) {
const mids = forwardEle.dataset.forwardedMids.split(",");
setMids(mids);
}
} else {
setMids([mid]);
}
}, [mid, content_type]);
const enablePin = context == "channel" && canPin;
const enableEdit =
currUid == from_uid &&
[ContentTypes.text, ContentTypes.markdown].includes(content_type);
const enableReply = currUid != from_uid;
const pinned = enablePin ? pins.findIndex((p) => p.mid == mid) > -1 : false;
return (
<StyledCmds
ref={cmdsRef}
className={`cmds ${tippyVisible ? "visible" : ""}`}
>
<Tippy
duration={0}
delay={[0, 0]}
onShow={handleTippyVisible.bind(null, true)}
onHide={handleTippyVisible.bind(null, false)}
interactive
@@ -116,24 +173,25 @@ export default function Commands({
</Tooltip>
</li>
</Tippy>
{currUid == from_uid ? (
{enableEdit && (
<li className="cmd" onClick={toggleEditMessage}>
<Tooltip placement="top" tip="Edit">
<img src={editIcon} alt="icon edit" />
</Tooltip>
</li>
) : (
)}
{enableReply && (
<li className="cmd" onClick={handleReply}>
<Tooltip placement="top" tip="Reply">
<img src={replyIcon} alt="icon reply" />
</Tooltip>
</li>
)}
{/* <li className="cmd">
<li className="cmd fav" onClick={handleAddFav}>
<Tooltip placement="top" tip="Add to Favorites">
<img src={bookmarkIcon} className="toggler" alt="icon bookmark" />
<IconBookmark />
</Tooltip>
</li> */}
</li>
<Tippy
onShow={handleTippyVisible.bind(null, true)}
onHide={handleTippyVisible.bind(null, false)}
@@ -145,8 +203,11 @@ export default function Commands({
<StyledMenu className="menu">
{/* <li className="item">Edit Message</li> */}
{enablePin && (
<li className="item underline" onClick={togglePinModal}>
Pin Message
<li
className="item"
onClick={pinned ? handleUnpin : togglePinModal}
>
{pinned ? `Unpin Message` : `Pin Message`}
</li>
)}
<li className="item" onClick={toggleForwardModal}>
@@ -177,7 +238,7 @@ export default function Commands({
<DeleteMessageConfirm closeModal={toggleDeleteModal} mids={mid} />
)}
{forwardModalVisible && (
<ForwardModal mids={[mid]} closeModal={toggleForwardModal} />
<ForwardModal mids={mids} closeModal={toggleForwardModal} />
)}
{pinModalVisible && (
<PinMessageModal
@@ -0,0 +1,105 @@
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 IconForward from "../../../assets/icons/forward.svg";
import useNormalizeMessage from "../../hook/useNormalizeMessage";
const StyledForward = styled.div`
display: flex;
flex-direction: column;
border-radius: var(--br);
background-color: #f4f4f5;
padding: 8px;
> .tip {
display: flex;
align-items: center;
gap: 4px;
.icon {
width: 16px;
height: 16px;
path {
fill: #98a2b3;
}
}
font-weight: 400;
font-size: 12px;
line-height: 18px;
color: #98a2b3;
}
`;
const ForwardedMessage = ({ context, to, from_uid, id }) => {
const { normalizeMessage, messages } = useNormalizeMessage();
const [forwards, setForwards] = useState(null);
useEffect(() => {
if (id) {
normalizeMessage(id);
}
}, [id]);
useEffect(() => {
if (messages) {
const forward_mids = messages.map(({ from_mid }) => from_mid) || [];
setForwards(
<StyledForward data-forwarded-mids={forward_mids.join(",")}>
<h4 className="tip">
<IconForward className="icon" />
Forwarded
</h4>
<div className="list">
{messages.map((msg, idx) => {
const {
user = {},
created_at,
download,
content,
content_type,
properties,
thumbnail,
} = msg;
return (
<StyledMsg key={idx}>
{user && (
<div className="avatar">
<Avatar url={user.avatar} name={user.name} />
</div>
)}
<div className="details">
<div className="up">
<span className="name">{user?.name}</span>
<i className="time">
{dayjs(created_at).format("YYYY-MM-DD h:mm:ss A")}
</i>
</div>
<div className="down">
{renderContent({
download,
context,
to,
from_uid,
content,
content_type,
properties,
thumbnail,
})}
</div>
</div>
</StyledMsg>
);
})}
</div>
</StyledForward>
);
}
}, [messages, context, to, from_uid]);
console.log("archive data", messages);
if (!id) return null;
return forwards;
};
export default ForwardedMessage;
@@ -0,0 +1,87 @@
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 StyledSaved = styled.div`
display: flex;
flex-direction: column;
border-radius: var(--br);
background-color: #f4f4f5;
padding: 8px;
`;
const SavedMessage = ({ cid, id }) => {
const { favorites } = useFavMessage(cid);
const [fav, setFav] = useState(null);
const [messages, setMessages] = useState(null);
useEffect(() => {
if (id && favorites) {
const msgs = favorites.find((f) => f.id == id)?.messages;
console.log("favv", favorites, id, msgs);
setMessages(msgs);
}
}, [id, favorites]);
useEffect(() => {
if (messages) {
const fav_mids = messages.map(({ from_mid }) => from_mid) || [];
setFav(
<StyledSaved data-fav-mids={fav_mids.join(",")}>
<div className="list">
{messages.map((msg, idx) => {
const {
user = {},
created_at,
download,
content,
content_type,
properties,
thumbnail,
} = msg;
return (
<StyledMsg className="favorite" key={idx}>
{user && (
<div className="avatar">
<Avatar url={user.avatar} name={user.name} />
</div>
)}
<div className="details">
<div className="up">
<span className="name">{user?.name}</span>
<i className="time">
{dayjs(created_at).format("YYYY-MM-DD h:mm:ss A")}
</i>
</div>
<div className="down">
{renderContent({
download,
context: "channel",
to: null,
from_uid: null,
content,
content_type,
properties,
thumbnail,
})}
</div>
</div>
</StyledMsg>
);
})}
</div>
</StyledSaved>
);
}
}, [messages]);
if (!id) return null;
console.log("archive data", messages, fav);
return fav;
};
export default SavedMessage;
+1
View File
@@ -138,6 +138,7 @@ function Message({
</div>
{!edit && !readOnly && (
<Commands
content_type={content_type}
context={context}
contextId={contextId}
mid={mid}
+4 -97
View File
@@ -1,16 +1,14 @@
import React, { useState, useEffect } from "react";
import React from "react";
import Linkit from "react-linkify";
import styled from "styled-components";
import dayjs from "dayjs";
import StyledMsg from "./styled";
import { ContentTypes } from "../../../app/config";
import Mention from "./Mention";
import useNormalizeMessage from "../../hook/useNormalizeMessage";
import ForwardedMessage from "./ForwardedMessage";
import MrakdownRender from "../MrakdownRender";
import FileMessage from "../FileMessage";
import URLPreview from "./URLPreview";
import Avatar from "../Avatar";
import IconForward from "../../../assets/icons/forward.svg";
import reactStringReplace from "react-string-replace";
const renderContent = ({
context,
@@ -115,96 +113,5 @@ const renderContent = ({
}
return ctn;
};
const StyledForward = styled.div`
display: flex;
flex-direction: column;
border-radius: var(--br);
background-color: #f4f4f5;
padding: 8px;
> .tip {
display: flex;
align-items: center;
gap: 4px;
.icon {
width: 16px;
height: 16px;
path {
fill: #98a2b3;
}
}
font-weight: 400;
font-size: 12px;
line-height: 18px;
color: #98a2b3;
}
`;
const ForwardedMessage = ({ context, to, from_uid, id }) => {
const { normalizeMessage, messages } = useNormalizeMessage();
const [forwards, setForwards] = useState(null);
useEffect(() => {
if (id) {
normalizeMessage(id);
}
}, [id]);
useEffect(() => {
if (messages) {
setForwards(
<StyledForward>
<h4 className="tip">
<IconForward className="icon" />
Forwarded
</h4>
<div className="list">
{messages.map((msg, idx) => {
const {
user = {},
created_at,
download,
content,
content_type,
properties,
thumbnail,
} = msg;
return (
<StyledMsg key={idx}>
{user && (
<div className="avatar">
<Avatar url={user.avatar} name={user.name} />
</div>
)}
<div className="details">
<div className="up">
<span className="name">{user?.name}</span>
<i className="time">
{dayjs(created_at).format("YYYY-MM-DD h:mm:ss A")}
</i>
</div>
<div className="down">
{renderContent({
download,
context,
to,
from_uid,
content,
content_type,
properties,
thumbnail,
})}
</div>
</div>
</StyledMsg>
);
})}
</div>
</StyledForward>
);
}
}, [messages, context, to, from_uid]);
console.log("archive data", messages);
if (!id) return null;
return forwards;
};
export default renderContent;