feat: auto delete message logic

This commit is contained in:
Tristan Yang
2022-12-27 21:29:41 +08:00
parent 1e9665dab2
commit bd12aa0e4d
6 changed files with 105 additions and 19 deletions
+2 -1
View File
@@ -12,6 +12,7 @@ export interface MessagePayload {
content: string; content: string;
expires_in?: number | null; expires_in?: number | null;
properties?: { properties?: {
local_id?: number;
content_type: string; content_type: string;
size: number; size: number;
}; };
@@ -75,7 +76,7 @@ const messageSlice = createSlice({
data.thumbnail = content; data.thumbnail = content;
} }
} }
state[mid] = data; state[mid] = { ...state[mid], ...data };
}, },
removeMessage(state, action: PayloadAction<number | number[]>) { removeMessage(state, action: PayloadAction<number | number[]>) {
const mids = Array.isArray(action.payload) ? action.payload : [action.payload]; const mids = Array.isArray(action.payload) ? action.payload : [action.payload];
+3
View File
@@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" stroke="#101828" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 6V12L16 14M22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12Z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 325 B

+2
View File
@@ -1,11 +1,13 @@
import dayjs from "dayjs"; import dayjs from "dayjs";
import "dayjs/locale/zh-cn"; import "dayjs/locale/zh-cn";
import "dayjs/locale/ja"; import "dayjs/locale/ja";
import duration from "dayjs/plugin/duration";
import relativeTime from "dayjs/plugin/relativeTime"; import relativeTime from "dayjs/plugin/relativeTime";
import isToday from "dayjs/plugin/isToday"; import isToday from "dayjs/plugin/isToday";
import isYesterday from "dayjs/plugin/isYesterday"; import isYesterday from "dayjs/plugin/isYesterday";
// import i18n from '../i18n'; // import i18n from '../i18n';
dayjs.extend(duration);
dayjs.extend(relativeTime); dayjs.extend(relativeTime);
dayjs.extend(isToday); dayjs.extend(isToday);
dayjs.extend(isYesterday); dayjs.extend(isYesterday);
@@ -0,0 +1,67 @@
import dayjs from 'dayjs';
import { FC, useEffect, useState } from 'react';
import { useDispatch } from 'react-redux';
import { removeMessage } from '../../../app/slices/message';
import IconTimer from '../../../assets/icons/timer.svg';
type Props = {
mid: number;
expires_in: number;
create_at: number;
};
const ExpireTimer: FC<Props> = ({ mid, create_at, expires_in }) => {
const [countdown, setCountdown] = useState<number | undefined>();
const dispatch = useDispatch();
useEffect(() => {
if (expires_in > 0 && create_at > 0) {
const expire_time = create_at + expires_in * 1000;
if (dayjs().isAfter(new Date(expire_time))) {
// 已过期,立即删除
dispatch(removeMessage(mid));
// dispatch()
} else {
// 倒计时
setCountdown(Math.floor((expire_time - new Date().getTime()) / 1000));
}
}
}, [expires_in, create_at, mid]);
useEffect(() => {
let timer = 0;
if (typeof countdown !== "undefined") {
if (countdown > 0) {
timer = window.setTimeout(() => {
setCountdown(prev => {
const _prev = prev ?? 0;
return _prev - 1;
});
}, 1000);
} else {
// 倒计时结束
console.log("countdown over", mid, countdown);
dispatch(removeMessage(mid));
}
}
return () => {
if (typeof countdown !== "undefined") {
clearTimeout(timer);
}
};
}, [countdown, mid]);
if (!countdown) return null;
const duration = dayjs.duration(countdown * 1000);
const day = duration.days() !== 0 ? `${duration.days()} day` : "";
const hours = duration.hours() !== 0 ? `${duration.hours().toString().padStart(2, '0')}:` : "";
const minutes = duration.minutes() !== 0 ? `${duration.minutes().toString().padStart(2, '0')}:` : "";
const formatted_countdown = `${day} ${hours}${minutes}${duration.seconds().toString().padStart(2, '0')}`;
return (
<div className='absolute bottom-1 right-2 text-xs text-gray-400 flex items-center gap-1 font-mono'>
<IconTimer className="w-4 h-4 stroke-slate-400" />
{formatted_countdown}
</div>
);
};
export default ExpireTimer;
+28 -18
View File
@@ -16,6 +16,7 @@ import ContextMenu from "./ContextMenu";
import useContextMenu from "../../hook/useContextMenu"; import useContextMenu from "../../hook/useContextMenu";
import usePinMessage from "../../hook/usePinMessage"; import usePinMessage from "../../hook/usePinMessage";
import { useAppSelector } from "../../../app/store"; import { useAppSelector } from "../../../app/store";
import ExpireTimer from "./ExpireTimer";
interface IProps { interface IProps {
readOnly?: boolean; readOnly?: boolean;
@@ -50,6 +51,19 @@ const Message: FC<IProps> = ({
setEdit((prev) => !prev); setEdit((prev) => !prev);
}; };
useEffect(() => {
if (!read) {
// 标记已读
const data =
context == "user"
? { users: [{ uid: +contextId, mid }] }
: { groups: [{ gid: +contextId, mid }] };
if (updateReadIndex) {
updateReadIndex(data);
}
}
}, [mid, read]);
const { const {
reply_mid, reply_mid,
from_uid: fromUid, from_uid: fromUid,
@@ -60,20 +74,10 @@ const Message: FC<IProps> = ({
download, download,
content_type = "text/plain", content_type = "text/plain",
edited, edited,
properties properties,
expires_in = 0
} = message; } = message;
useEffect(() => {
if (!read) {
const data =
context == "user"
? { users: [{ uid: +contextId, mid }] }
: { groups: [{ gid: +contextId, mid }] };
if (updateReadIndex) {
updateReadIndex(data);
}
}
}, [mid, read]);
const reactions = reactionMessageData[mid]; const reactions = reactionMessageData[mid];
const currUser = usersData[fromUid || 0]; const currUser = usersData[fromUid || 0];
@@ -85,15 +89,15 @@ const Message: FC<IProps> = ({
const pinInfo = getPinInfo(mid); const pinInfo = getPinInfo(mid);
// return null; // return null;
const _key = properties?.local_id || mid; const _key = properties?.local_id || mid;
const showExpire = (expires_in ?? 0) > 0;
return ( return (
<StyledWrapper <StyledWrapper
key={_key} key={_key}
onContextMenu={readOnly ? undefined : handleContextMenuEvent} onContextMenu={readOnly ? undefined : handleContextMenuEvent}
data-msg-mid={mid} data-msg-mid={mid}
ref={inviewRef} ref={inviewRef}
className={`message ${readOnly ? "readonly" : ""} ${pinInfo ? "pinned" : ""} ${ className={`message ${readOnly ? "readonly" : ""} ${showExpire ? "auto_delete" : ""} ${pinInfo ? "pinned" : ""} ${contextMenuVisible ? "contextVisible" : ""
contextMenuVisible ? "contextVisible" : "" } `}
} `}
> >
<Tippy <Tippy
key={_key} key={_key}
@@ -118,9 +122,8 @@ const Message: FC<IProps> = ({
> >
<div <div
className="details" className="details"
data-pin-tip={`pinned by ${ data-pin-tip={`pinned by ${pinInfo?.created_by ? usersData[pinInfo.created_by]?.name : ""
pinInfo?.created_by ? usersData[pinInfo.created_by]?.name : "" }`}
}`}
> >
<div className="up"> <div className="up">
<span className="name">{currUser?.name || "Deleted User"}</span> <span className="name">{currUser?.name || "Deleted User"}</span>
@@ -160,6 +163,13 @@ const Message: FC<IProps> = ({
</div> </div>
</ContextMenu> </ContextMenu>
{showExpire && (
<ExpireTimer
mid={message.mid}
expires_in={expires_in ?? 0}
create_at={time ?? 0}
/>
)}
{!edit && !readOnly && ( {!edit && !readOnly && (
<Commands <Commands
context={context} context={context}
+3
View File
@@ -11,6 +11,9 @@ const StyledMsg = styled.div`
&[data-highlight="true"] { &[data-highlight="true"] {
background: #f5f6f7; background: #f5f6f7;
} }
&.auto_delete {
background: #f1d1ca50;
}
&.pinned { &.pinned {
background: #ecfdff; background: #ecfdff;
} }