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
+34 -5
View File
@@ -1,11 +1,14 @@
import { createApi } from "@reduxjs/toolkit/query/react";
// import { batch } from "react-redux";
import { ContentTypes } from "../config";
import { updateReadChannels, updateReadUsers } from "../slices/footprint";
import { fullfillFavorites, populateFavorite } from "../slices/favorites";
import {
fullfillFavorites,
populateFavorite,
addFavorite,
deleteFavorite,
} from "../slices/favorites";
import { onMessageSendStarted } from "./handlers";
import { normalizeArchiveData } from "../../common/utils";
// import { updateMessage } from "../slices/message";
import baseQuery from "./base.query";
@@ -97,6 +100,30 @@ export const messageApi = createApi({
method: "POST",
body: { mid_list: mids },
}),
async onQueryStarted(mids, { dispatch, queryFulfilled }) {
try {
const { data } = await queryFulfilled;
const { created_at, id } = data;
dispatch(addFavorite({ id, created_at }));
dispatch(messageApi.endpoints.getFavoriteDetails.initiate(id));
} catch (err) {
console.log("get favorite list error", err);
}
},
}),
removeFavorite: builder.query({
query: (id) => ({
url: `/favorite/${id}`,
method: "DELETE",
}),
async onQueryStarted(id, { dispatch, queryFulfilled }) {
try {
await queryFulfilled;
dispatch(deleteFavorite(id));
} catch (err) {
console.log("get favorite list error", err);
}
},
}),
getFavoriteDetails: builder.query({
query: (id) => ({
@@ -105,7 +132,8 @@ export const messageApi = createApi({
async onQueryStarted(id, { dispatch, queryFulfilled }) {
try {
const { data } = await queryFulfilled;
dispatch(populateFavorite({ id, data }));
const messages = normalizeArchiveData(data, id);
dispatch(populateFavorite({ id, messages }));
} catch (err) {
console.log("get favorite list error", err);
}
@@ -166,6 +194,7 @@ export const messageApi = createApi({
});
export const {
useLazyRemoveFavoriteQuery,
useUnpinMessageMutation,
useLazyGetFavoritesQuery,
useFavoriteMessageMutation,
+18 -3
View File
@@ -8,14 +8,29 @@ const favoritesSlice = createSlice({
fullfillFavorites(state, action) {
return action.payload;
},
addFavorite(state, action) {
state.push(action.payload);
},
deleteFavorite(state, action) {
const id = action.payload;
const idx = state.findIndex((f) => f.id == id);
if (idx > -1) {
state.splice(idx, 1);
}
},
populateFavorite(state, action) {
const { id, data } = action.payload;
const { id, messages } = action.payload;
const idx = state.findIndex((fav) => fav.id == id);
if (idx > -1) {
state[idx].data = data;
state[idx].messages = messages;
}
},
},
});
export const { fullfillFavorites, populateFavorite } = favoritesSlice.actions;
export const {
addFavorite,
deleteFavorite,
fullfillFavorites,
populateFavorite,
} = favoritesSlice.actions;
export default favoritesSlice.reducer;
+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;
+55
View File
@@ -0,0 +1,55 @@
import { useState, useEffect } from "react";
import { useSelector } from "react-redux";
import {
useLazyRemoveFavoriteQuery,
useFavoriteMessageMutation,
} from "../../app/services/message";
export default function useFavMessage(cid = null) {
const [removeFav] = useLazyRemoveFavoriteQuery();
const [addFav] = useFavoriteMessageMutation();
const [favorites, setFavorites] = useState([]);
const { favs = [] } = useSelector((store) => {
return {
// mids: store.channelMessage.byId[cid],
favs: store.favorites,
// loginUser: store.contacts.byId[store.authData.uid],
};
});
const addFavorite = async (mid = []) => {
const mids = Array.isArray(mid) ? mid.map((i) => +i) : [+mid];
if (mids.length == 0) return;
await addFav(mids);
};
const removeFavorite = (id) => {
if (!id) return;
removeFav(id);
};
const isFavorited = (mid = null) => {
if (!mid) return false;
let mids = [];
favorites.forEach((f) => {
if (f.messages.length == 1) {
const ids = f.messages.map((m) => m.from_mid);
mids = [...mids, ...ids];
}
});
return mids.findIndex((i) => i == mid) > -1;
};
useEffect(() => {
const filtereds = cid
? favs.filter((f) => {
if (!f.messages) return false;
return f.messages.every((m) => m.source.gid == cid);
})
: favs;
console.log("filtered", filtereds);
setFavorites(filtereds);
}, [cid, favs]);
return {
isFavorited,
addFavorite,
removeFavorite,
favorites,
};
}
+2 -39
View File
@@ -1,5 +1,5 @@
import { useState, useEffect } from "react";
import BASE_URL, { ContentTypes } from "../../app/config";
import { normalizeArchiveData } from "../../common/utils";
import { useLazyGetArchiveMessageQuery } from "../../app/services/message";
export default function useNormalizeMessage() {
const [filePath, setFilePath] = useState(null);
@@ -10,44 +10,7 @@ export default function useNormalizeMessage() {
] = useLazyGetArchiveMessageQuery();
useEffect(() => {
if (data && isSuccess) {
const msgs = data.messages.map(
({
content,
file_id,
thumbnail_id,
content_type,
properties,
from_user,
}) => {
const transformedContent =
content_type == ContentTypes.file
? `${BASE_URL}/resource/archive/attachment?file_path=${filePath}&attachment_id=${file_id}`
: content;
const thumbnail =
content_type == ContentTypes.file
? `${BASE_URL}/resource/archive/attachment?file_path=${filePath}&attachment_id=${thumbnail_id}`
: "";
const download =
content_type == ContentTypes.file
? `${BASE_URL}/resource/archive/attachment?file_path=${filePath}&attachment_id=${file_id}&download=true`
: "";
let user = { ...(data.users[from_user] || {}) };
user.avatar =
user.avatar !== null
? `${BASE_URL}/resource/archive/attachment?file_path=${filePath}&attachment_id=${user.avatar}`
: "";
console.log("user data", transformedContent, user);
return {
user,
content: transformedContent,
content_type,
properties,
download,
thumbnail,
};
}
);
const msgs = normalizeArchiveData(data, filePath);
setNormalizedMessages(msgs);
}
}, [data, isSuccess, filePath]);
+47 -1
View File
@@ -1,4 +1,4 @@
import { FILE_IMAGE_SIZE } from "../app/config";
import BASE_URL, { FILE_IMAGE_SIZE, ContentTypes } from "../app/config";
import IconPdf from "../assets/icons/file.pdf.svg";
import IconAudio from "../assets/icons/file.audio.svg";
import IconVideo from "../assets/icons/file.video.svg";
@@ -187,3 +187,49 @@ export const getFileIcon = (type, name = "") => {
}
return icon;
};
export const normalizeArchiveData = (data = null, filePath = null) => {
if (!data || !filePath) return [];
const { messages, users } = data;
return messages.map(
({
source,
mid,
content,
file_id,
thumbnail_id,
content_type,
properties,
from_user,
}) => {
const transformedContent =
content_type == ContentTypes.file
? `${BASE_URL}/resource/archive/attachment?file_path=${filePath}&attachment_id=${file_id}`
: content;
const thumbnail =
content_type == ContentTypes.file
? `${BASE_URL}/resource/archive/attachment?file_path=${filePath}&attachment_id=${thumbnail_id}`
: "";
const download =
content_type == ContentTypes.file
? `${BASE_URL}/resource/archive/attachment?file_path=${filePath}&attachment_id=${file_id}&download=true`
: "";
let user = { ...(users[from_user] || {}) };
user.avatar =
user.avatar !== null
? `${BASE_URL}/resource/archive/attachment?file_path=${filePath}&attachment_id=${user.avatar}`
: "";
console.log("user data", transformedContent, user);
return {
source,
from_mid: mid,
user,
content: transformedContent,
content_type,
properties,
download,
thumbnail,
};
}
);
};
+130
View File
@@ -0,0 +1,130 @@
// import React from "react";
// import { useSelector } from "react-redux";
import styled from "styled-components";
import SavedMessage from "../../../common/component/Message/SavedMessage";
import IconSurprise from "../../../assets/icons/emoji.suprise.svg";
// import IconForward from "../../../assets/icons/forward.svg";
import IconBookmark from "../../../assets/icons/bookmark.svg";
import useFavMessage from "../../../common/hook/useFavMessage";
const Styled = styled.div`
padding: 16px;
background: #f9fafb;
filter: drop-shadow(0px 25px 50px rgba(31, 41, 55, 0.25));
border-radius: 12px;
min-width: 500px;
max-height: 500px;
overflow: auto;
> .head {
font-weight: 600;
font-size: 16px;
line-height: 24px;
color: #344054;
margin-bottom: 16px;
}
> .none {
padding: 16px;
width: 100%;
display: flex;
flex-direction: column;
gap: 8px;
align-items: center;
.tip {
width: 240px;
font-weight: 600;
font-size: 16px;
line-height: 24px;
text-align: center;
color: #475467;
}
}
> .list {
display: flex;
flex-direction: column;
gap: 8px;
.fav {
position: relative;
border: 1px solid #f2f4f7;
border-radius: var(--br);
.favorite {
background: none;
.down img {
width: 100% !important;
height: auto !important;
}
}
> .opts {
visibility: hidden;
display: flex;
align-items: center;
gap: 4px;
position: absolute;
top: 4px;
right: 4px;
padding: 4px;
border: 1px solid rgba(0, 0, 0, 0.08);
border-radius: 6px;
.btn {
display: flex;
background: none;
border: none;
svg {
width: 16px;
height: 16px;
path {
fill: #d92d20;
}
}
}
}
&:hover .opts {
visibility: visible;
}
}
}
`;
export default function FavList({ cid = null }) {
const { favorites, removeFavorite } = useFavMessage(cid);
const handleRemove = (evt) => {
const { id } = evt.currentTarget.dataset;
console.log("remove fav", id);
removeFavorite(id);
};
const noFavs = favorites.length == 0;
return (
<Styled>
<h4 className="head">Saved Message({favorites.length})</h4>
{noFavs ? (
<div className="none">
<IconSurprise />
<div className="tip">
This channel doesnt have any saved message yet.
</div>
</div>
) : (
<ul className="list">
{favorites.map(({ id }) => {
console.log("favv", id);
return (
<li key={id} className="fav">
<SavedMessage cid={cid} id={id} />
<div className="opts">
{/* <button
className="btn"
data-mid={mid}
// onClick={handleRemove}
>
<IconForward />
</button> */}
<button className="btn" data-id={id} onClick={handleRemove}>
<IconBookmark />
</button>
</div>
</li>
);
})}
</ul>
)}
</Styled>
);
}
+33
View File
@@ -2,6 +2,7 @@ import { useState } from "react";
import { useDebounce } from "rooks";
import { useSelector } from "react-redux";
import PinList from "./PinList";
import FavList from "./FavList";
import { useReadMessageMutation } from "../../../app/services/message";
import useChatScroll from "../../../common/hook/useChatScroll";
import ChannelIcon from "../../../common/component/ChannelIcon";
@@ -11,6 +12,7 @@ import Layout from "../Layout";
import { renderMessageFragment } from "../utils";
import EditIcon from "../../../assets/icons/edit.svg";
// import alertIcon from "../../../assets/icons/alert.svg?url";
import IconFav from "../../../assets/icons/bookmark.svg";
import IconPeople from "../../../assets/icons/people.svg";
import IconPin from "../../../assets/icons/pin.svg";
// import searchIcon from "../../../assets/icons/search.svg?url";
@@ -133,6 +135,37 @@ export default function ChannelChat({ cid = "", dropFiles = [] }) {
</li>
</Tippy>
</Tooltip>
<Tooltip
tip="Favorite"
placement="left"
disabled={toolVisible == "favorite"}
>
<Tippy
onShow={() => {
setToolVisible("favorite");
}}
onHide={() => {
setToolVisible("");
}}
delay={[0, 0]}
duration={0}
placement="left-start"
popperOptions={{ strategy: "fixed" }}
offset={[0, 180]}
interactive
trigger="click"
content={<FavList cid={cid} />}
>
<li
className={`tool ${
toolVisible == "favorite" ? "active" : ""
} `}
data-count={pinCount}
>
<IconFav />
</li>
</Tippy>
</Tooltip>
<li
className={`tool ${membersVisible ? "active" : ""}`}
onClick={toggleMembersVisible}
+3 -3
View File
@@ -3,8 +3,8 @@ import styled from "styled-components";
import { useDispatch, useSelector } from "react-redux";
import { useKey } from "rooks";
import useDeleteMessage from "../../../common/hook/useDeleteMessage";
import useFavMessage from "../../../common/hook/useFavMessage";
import { updateSelectMessages } from "../../../app/slices/ui";
import { useFavoriteMessageMutation } from "../../../app/services/message";
import IconForward from "../../../assets/icons/forward.svg";
import IconBookmark from "../../../assets/icons/bookmark.svg";
import IconDelete from "../../../assets/icons/delete.svg";
@@ -40,7 +40,7 @@ const Styled = styled.div`
export default function Operations({ context, id }) {
const [deleteModalVisible, setDeleteModalVisible] = useState(false);
const { canDelete } = useDeleteMessage();
const [favoriteMsg] = useFavoriteMessageMutation();
const { addFavorite } = useFavMessage(id);
const mids = useSelector(
(store) => store.ui.selectMessages[`${context}_${id}`]
);
@@ -50,7 +50,7 @@ export default function Operations({ context, id }) {
dispatch(updateSelectMessages({ context, id, operation: "reset" }));
};
const handleFav = async () => {
await favoriteMsg(mids);
await addFavorite(mids);
dispatch(updateSelectMessages({ context, id, operation: "reset" }));
toast.success("Messages Saved!");
};
+46
View File
@@ -0,0 +1,46 @@
// import React from "react";
import styled from "styled-components";
import iconSearch from "../../assets/icons/search.svg?url";
const Styled = styled.div`
width: 100%;
padding: 6px 16px;
box-shadow: 0px 1px 0px rgba(0, 0, 0, 0.1);
&.embed {
padding: 6px 8px;
box-shadow: none;
}
.search {
outline: none;
background-color: rgba(0, 0, 0, 0.08);
border-radius: 25px;
padding: 10px 8px 10px 36px;
color: #a1a1aa;
font-weight: 400;
font-size: 14px;
line-height: 20px;
background-image: url(${iconSearch});
background-repeat: no-repeat;
background-position: 8px center;
}
`;
export default function Search({
value = "",
updateSearchValue = null,
embed = false,
}) {
const handleChange = (evt) => {
if (updateSearchValue) {
updateSearchValue(evt.target.value);
}
};
return (
<Styled className={embed ? "embed" : ""}>
<input
value={value}
onChange={handleChange}
className="search"
placeholder="Search..."
/>
</Styled>
);
}
+34
View File
@@ -0,0 +1,34 @@
// import { useState, useEffect, useRef, memo } from "react";
import Styled from "./styled";
import { useSelector } from "react-redux";
import SavedMessage from "../../common/component/Message/SavedMessage";
// import Masonry from "masonry-layout";
// import waterfall from "waterfall.js/src/waterfall";
// import { Views } from "../../app/config";
// import View from "./View";
// import Search from "./Search";
// import Filter from "./Filter";
// import FileBox from "../../common/component/FileBox";
function FavsPage() {
// const listContainerRef = useRef(null);
// const [filter, setFilter] = useState({});
const { favorites } = useSelector((store) => {
return {
favorites: store.favorites,
// channelMessage: store.channelMessage,
// view: store.ui.fileListView,
};
});
return (
<Styled>
{favorites.map(({ id, created_at }) => {
return <SavedMessage key={id} id={id} />;
})}
</Styled>
);
}
export default FavsPage;
// const equals = (a, b) => a.length === b.length && a.every((v, i) => v == b[i]);
// export default memo(FavsPage, (prevs, nexts) => {
// return equals(prevs.fileMessages, nexts.fileMessages);
// });
+15
View File
@@ -0,0 +1,15 @@
import styled from "styled-components";
const Styled = styled.div`
height: 100vh;
overflow-y: scroll;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 10px;
background-color: #fff;
margin: 8px 24px 10px 0;
border-radius: 16px;
padding: 16px;
`;
export default Styled;
+12 -6
View File
@@ -11,9 +11,10 @@ import usePreload from "./usePreload";
import Tooltip from "../../common/component/Tooltip";
import Notification from "../../common/component/Notification";
import ChatIcon from "../../assets/icons/chat.svg?url";
import ContactIcon from "../../assets/icons/contact.svg?url";
import FolderIcon from "../../assets/icons/folder.svg?url";
import ChatIcon from "../../assets/icons/chat.svg";
import ContactIcon from "../../assets/icons/contact.svg";
import FavIcon from "../../assets/icons/bookmark.svg";
import FolderIcon from "../../assets/icons/folder.svg";
// const routes = ["/setting", "/setting/channel/:cid"];
export default function HomePage() {
const { pathname } = useLocation();
@@ -49,17 +50,22 @@ export default function HomePage() {
<nav className="link_navs">
<NavLink className="link" to={"/chat"}>
<Tooltip tip="Chat">
<img src={ChatIcon} alt="chat icon" />
<ChatIcon />
</Tooltip>
</NavLink>
<NavLink className="link" to={"/contacts"}>
<Tooltip tip="Members">
<img src={ContactIcon} alt="contact icon" />
<ContactIcon />
</Tooltip>
</NavLink>
<NavLink className="link" to={"/favs"}>
<Tooltip tip="Favorites">
<FavIcon className="fav" />
</Tooltip>
</NavLink>
<NavLink className="link" to={"/files"}>
<Tooltip tip="Files">
<img src={FolderIcon} alt="folder icon" />
<FolderIcon />
</Tooltip>
</NavLink>
</nav>
+2
View File
@@ -7,6 +7,7 @@ import OAuthPage from "./oauth";
import LoginPage from "./login";
import HomePage from "./home";
import ChatPage from "./chat";
import FavoritesPage from "./favs";
import ContactsPage from "./contacts";
import RequireAuth from "../common/component/RequireAuth";
import RequireNoAuth from "../common/component/RequireNoAuth";
@@ -78,6 +79,7 @@ const PageRoutes = () => {
<Route index element={<ContactsPage />} />
<Route path=":user_id" element={<ContactsPage />} />
</Route>
<Route path="favs" element={<FavoritesPage />}></Route>
<Route
path="files"
element={<ResourceManagement fileMessages={fileMessages} />}