feat: auto delete message logic
This commit is contained in:
@@ -12,6 +12,7 @@ export interface MessagePayload {
|
||||
content: string;
|
||||
expires_in?: number | null;
|
||||
properties?: {
|
||||
local_id?: number;
|
||||
content_type: string;
|
||||
size: number;
|
||||
};
|
||||
@@ -75,7 +76,7 @@ const messageSlice = createSlice({
|
||||
data.thumbnail = content;
|
||||
}
|
||||
}
|
||||
state[mid] = data;
|
||||
state[mid] = { ...state[mid], ...data };
|
||||
},
|
||||
removeMessage(state, action: PayloadAction<number | number[]>) {
|
||||
const mids = Array.isArray(action.payload) ? action.payload : [action.payload];
|
||||
|
||||
@@ -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 |
@@ -1,11 +1,13 @@
|
||||
import dayjs from "dayjs";
|
||||
import "dayjs/locale/zh-cn";
|
||||
import "dayjs/locale/ja";
|
||||
import duration from "dayjs/plugin/duration";
|
||||
import relativeTime from "dayjs/plugin/relativeTime";
|
||||
import isToday from "dayjs/plugin/isToday";
|
||||
import isYesterday from "dayjs/plugin/isYesterday";
|
||||
// import i18n from '../i18n';
|
||||
|
||||
dayjs.extend(duration);
|
||||
dayjs.extend(relativeTime);
|
||||
dayjs.extend(isToday);
|
||||
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;
|
||||
|
||||
@@ -16,6 +16,7 @@ import ContextMenu from "./ContextMenu";
|
||||
import useContextMenu from "../../hook/useContextMenu";
|
||||
import usePinMessage from "../../hook/usePinMessage";
|
||||
import { useAppSelector } from "../../../app/store";
|
||||
import ExpireTimer from "./ExpireTimer";
|
||||
|
||||
interface IProps {
|
||||
readOnly?: boolean;
|
||||
@@ -50,6 +51,19 @@ const Message: FC<IProps> = ({
|
||||
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 {
|
||||
reply_mid,
|
||||
from_uid: fromUid,
|
||||
@@ -60,20 +74,10 @@ const Message: FC<IProps> = ({
|
||||
download,
|
||||
content_type = "text/plain",
|
||||
edited,
|
||||
properties
|
||||
properties,
|
||||
expires_in = 0
|
||||
} = 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 currUser = usersData[fromUid || 0];
|
||||
@@ -85,14 +89,14 @@ const Message: FC<IProps> = ({
|
||||
const pinInfo = getPinInfo(mid);
|
||||
// return null;
|
||||
const _key = properties?.local_id || mid;
|
||||
const showExpire = (expires_in ?? 0) > 0;
|
||||
return (
|
||||
<StyledWrapper
|
||||
key={_key}
|
||||
onContextMenu={readOnly ? undefined : handleContextMenuEvent}
|
||||
data-msg-mid={mid}
|
||||
ref={inviewRef}
|
||||
className={`message ${readOnly ? "readonly" : ""} ${pinInfo ? "pinned" : ""} ${
|
||||
contextMenuVisible ? "contextVisible" : ""
|
||||
className={`message ${readOnly ? "readonly" : ""} ${showExpire ? "auto_delete" : ""} ${pinInfo ? "pinned" : ""} ${contextMenuVisible ? "contextVisible" : ""
|
||||
} `}
|
||||
>
|
||||
<Tippy
|
||||
@@ -118,8 +122,7 @@ const Message: FC<IProps> = ({
|
||||
>
|
||||
<div
|
||||
className="details"
|
||||
data-pin-tip={`pinned by ${
|
||||
pinInfo?.created_by ? usersData[pinInfo.created_by]?.name : ""
|
||||
data-pin-tip={`pinned by ${pinInfo?.created_by ? usersData[pinInfo.created_by]?.name : ""
|
||||
}`}
|
||||
>
|
||||
<div className="up">
|
||||
@@ -160,6 +163,13 @@ const Message: FC<IProps> = ({
|
||||
</div>
|
||||
</ContextMenu>
|
||||
|
||||
{showExpire && (
|
||||
<ExpireTimer
|
||||
mid={message.mid}
|
||||
expires_in={expires_in ?? 0}
|
||||
create_at={time ?? 0}
|
||||
/>
|
||||
)}
|
||||
{!edit && !readOnly && (
|
||||
<Commands
|
||||
context={context}
|
||||
|
||||
@@ -11,6 +11,9 @@ const StyledMsg = styled.div`
|
||||
&[data-highlight="true"] {
|
||||
background: #f5f6f7;
|
||||
}
|
||||
&.auto_delete {
|
||||
background: #f1d1ca50;
|
||||
}
|
||||
&.pinned {
|
||||
background: #ecfdff;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user