diff --git a/src/components/common/messaging/Message.tsx b/src/components/common/messaging/Message.tsx index f2ca22cd9ba30917aa51eb88b0536d94f748798d..8e87ae282fdb2a77be755f4ae27786409f8ff2c3 100644 --- a/src/components/common/messaging/Message.tsx +++ b/src/components/common/messaging/Message.tsx @@ -28,12 +28,14 @@ interface Props { attachContext?: boolean; queued?: QueuedMessage; message: MessageObject; + highlight?: boolean; contrast?: boolean; content?: Children; head?: boolean; } function Message({ + highlight, attachContext, message, contrast, @@ -72,6 +74,7 @@ function Message({ /> ))} <MessageBase + highlight={highlight} head={head && !(message.replies && message.replies.length > 0)} contrast={contrast} sending={typeof queued !== "undefined"} diff --git a/src/components/common/messaging/MessageBase.tsx b/src/components/common/messaging/MessageBase.tsx index e040168ef1043971b8ab184a41b0c1c3738561f3..56b82e7edc6d436261cdfcbaecdaff4dcb1281df 100644 --- a/src/components/common/messaging/MessageBase.tsx +++ b/src/components/common/messaging/MessageBase.tsx @@ -1,4 +1,4 @@ -import styled, { css } from "styled-components"; +import styled, { css, keyframes } from "styled-components"; import { decodeTime } from "ulid"; import { Text } from "preact-i18n"; @@ -17,8 +17,15 @@ export interface BaseMessageProps { blocked?: boolean; sending?: boolean; contrast?: boolean; + highlight?: boolean; } +const highlight = keyframes` + 0% { background: var(--mention); } + 66% { background: var(--mention); } + 100% { background: transparent; } +`; + export default styled.div<BaseMessageProps>` display: flex; overflow: none; @@ -70,6 +77,14 @@ export default styled.div<BaseMessageProps>` color: var(--error); `} + ${(props) => + props.highlight && + css` + animation-name: ${highlight}; + animation-timing-function: ease; + animation-duration: 3s; + `} + .detail { gap: 8px; display: flex; diff --git a/src/components/common/messaging/SystemMessage.tsx b/src/components/common/messaging/SystemMessage.tsx index c08229249b504b6ef8ce1e9273a4961b55a3bd4c..67e0d49965b469f35389ea04bc53f488819d3197 100644 --- a/src/components/common/messaging/SystemMessage.tsx +++ b/src/components/common/messaging/SystemMessage.tsx @@ -35,9 +35,10 @@ type SystemMessageParsed = interface Props { attachContext?: boolean; message: MessageObject; + highlight?: boolean; } -export function SystemMessage({ attachContext, message }: Props) { +export function SystemMessage({ attachContext, message, highlight }: Props) { const ctx = useForceUpdate(); let data: SystemMessageParsed; @@ -143,6 +144,7 @@ export function SystemMessage({ attachContext, message }: Props) { return ( <MessageBase + highlight={highlight} onContextMenu={ attachContext ? attachContextMenu("Menu", { diff --git a/src/pages/channels/messaging/MessageArea.tsx b/src/pages/channels/messaging/MessageArea.tsx index 5f5f439e5df65cddb20b4eecd556ecf6fc18df35..a5da03be07714cc6fa74e8b8181919ace957f84b 100644 --- a/src/pages/channels/messaging/MessageArea.tsx +++ b/src/pages/channels/messaging/MessageArea.tsx @@ -60,7 +60,9 @@ export function MessageArea({ id }: Props) { const status = useContext(StatusContext); const { focusTaken } = useContext(IntermediateContext); + // ? Required data for message links. const { message } = useParams<{ message: string }>(); + const [highlight, setHighlight] = useState<string | undefined>(undefined); // ? This is the scroll container. const ref = useRef<HTMLDivElement>(null); @@ -99,7 +101,7 @@ export function MessageArea({ id }: Props) { }); } else if (scrollState.current.type === "ScrollToView") { document.getElementById(scrollState.current.id) - ?.scrollIntoView(); + ?.scrollIntoView({ block: 'center' }); setScrollState({ type: "Free" }); } else if (scrollState.current.type === "OffsetTop") { @@ -170,6 +172,7 @@ export function MessageArea({ id }: Props) { // ? If message present or changes, load it as well. useEffect(() => { if (message) { + setHighlight(message); SingletonMessageRenderer.init(id, message); let channel = client.channels.get(id); @@ -284,7 +287,7 @@ export function MessageArea({ id }: Props) { </RequiresOnline> )} {state.type === "RENDER" && ( - <MessageRenderer id={id} state={state} /> + <MessageRenderer id={id} state={state} highlight={highlight} /> )} {state.type === "EMPTY" && <ConversationStart id={id} />} </div> diff --git a/src/pages/channels/messaging/MessageRenderer.tsx b/src/pages/channels/messaging/MessageRenderer.tsx index d573578b29c2ed560960845a1c4ed1ec8c02ebdc..7201da1d4680b3a1d23c8e42f7470a6a2e399d19 100644 --- a/src/pages/channels/messaging/MessageRenderer.tsx +++ b/src/pages/channels/messaging/MessageRenderer.tsx @@ -28,6 +28,7 @@ import MessageEditor from "./MessageEditor"; interface Props { id: string; state: RenderState; + highlight?: string; queue: QueuedMessage[]; } @@ -42,7 +43,7 @@ const BlockedMessage = styled.div` } `; -function MessageRenderer({ id, state, queue }: Props) { +function MessageRenderer({ id, state, queue, highlight }: Props) { if (state.type !== "RENDER") return null; const client = useContext(AppContext); @@ -132,6 +133,7 @@ function MessageRenderer({ id, state, queue }: Props) { key={message._id} message={message} attachContext + highlight={highlight === message._id} />, ); } else { @@ -158,6 +160,7 @@ function MessageRenderer({ id, state, queue }: Props) { ) : undefined } attachContext + highlight={highlight === message._id} />, ); }