mirror of
https://github.com/ilyakooo0/urbit.git
synced 2024-12-15 01:52:42 +03:00
Merge pull request #3513 from tylershuster/unread-marker
chat: fixes unread marker behavior
This commit is contained in:
commit
0c77b1b493
@ -1,6 +1,7 @@
|
|||||||
import React, { Component, PureComponent } from "react";
|
import React, { Component, PureComponent } from "react";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
|
import { Box } from "@tlon/indigo-react";
|
||||||
|
|
||||||
import { OverlaySigil } from './overlay-sigil';
|
import { OverlaySigil } from './overlay-sigil';
|
||||||
import { uxToHex, cite, writeText } from '~/logic/lib/util';
|
import { uxToHex, cite, writeText } from '~/logic/lib/util';
|
||||||
@ -12,14 +13,10 @@ import RemoteContent from '~/views/components/RemoteContent';
|
|||||||
|
|
||||||
export const DATESTAMP_FORMAT = '[~]YYYY.M.D';
|
export const DATESTAMP_FORMAT = '[~]YYYY.M.D';
|
||||||
|
|
||||||
export const UnreadMarker = React.forwardRef(({ dayBreak, when, style }, ref) => (
|
export const UnreadMarker = React.forwardRef(({ dayBreak, when }, ref) => (
|
||||||
<div ref={element => {
|
<div ref={ref} className="green2 flex items-center f9 absolute w-100 left-0">
|
||||||
setTimeout(() => {
|
|
||||||
element.style.opacity = '1';
|
|
||||||
}, 250);
|
|
||||||
}} className="green2 flex items-center f9 absolute w-100" style={{...style, opacity: '0'}}>
|
|
||||||
<hr className="dn-s ma0 w2 b--green2 bt-0" />
|
<hr className="dn-s ma0 w2 b--green2 bt-0" />
|
||||||
<p className="mh4">New messages below</p>
|
<p className="mh4" style={{ whiteSpace: 'normal' }}>New messages below</p>
|
||||||
<hr className="ma0 flex-grow-1 b--green2 bt-0" />
|
<hr className="ma0 flex-grow-1 b--green2 bt-0" />
|
||||||
{dayBreak
|
{dayBreak
|
||||||
? <p className="gray2 mh4">{moment(when).calendar()}</p>
|
? <p className="gray2 mh4">{moment(when).calendar()}</p>
|
||||||
@ -39,18 +36,19 @@ interface ChatMessageProps {
|
|||||||
msg: Envelope | IMessage;
|
msg: Envelope | IMessage;
|
||||||
previousMsg: Envelope | IMessage | undefined;
|
previousMsg: Envelope | IMessage | undefined;
|
||||||
nextMsg: Envelope | IMessage | undefined;
|
nextMsg: Envelope | IMessage | undefined;
|
||||||
isFirstUnread: boolean;
|
isLastRead: boolean;
|
||||||
group: Group;
|
group: Group;
|
||||||
association: Association;
|
association: Association;
|
||||||
contacts: Contacts;
|
contacts: Contacts;
|
||||||
unreadRef: React.RefObject<HTMLDivElement>;
|
|
||||||
hideAvatars: boolean;
|
hideAvatars: boolean;
|
||||||
hideNicknames: boolean;
|
hideNicknames: boolean;
|
||||||
remoteContentPolicy: LocalUpdateRemoteContentPolicy;
|
remoteContentPolicy: LocalUpdateRemoteContentPolicy;
|
||||||
className: string;
|
className?: string;
|
||||||
isPending: boolean;
|
isPending: boolean;
|
||||||
style?: any;
|
style?: any;
|
||||||
scrollWindow: HTMLDivElement;
|
scrollWindow: HTMLDivElement;
|
||||||
|
isLastMessage?: boolean;
|
||||||
|
unreadMarkerRef: React.RefObject<HTMLDivElement>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class ChatMessage extends Component<ChatMessageProps> {
|
export default class ChatMessage extends Component<ChatMessageProps> {
|
||||||
@ -72,11 +70,10 @@ export default class ChatMessage extends Component<ChatMessageProps> {
|
|||||||
msg,
|
msg,
|
||||||
previousMsg,
|
previousMsg,
|
||||||
nextMsg,
|
nextMsg,
|
||||||
isFirstUnread,
|
isLastRead,
|
||||||
group,
|
group,
|
||||||
association,
|
association,
|
||||||
contacts,
|
contacts,
|
||||||
unreadRef,
|
|
||||||
hideAvatars,
|
hideAvatars,
|
||||||
hideNicknames,
|
hideNicknames,
|
||||||
remoteContentPolicy,
|
remoteContentPolicy,
|
||||||
@ -84,7 +81,9 @@ export default class ChatMessage extends Component<ChatMessageProps> {
|
|||||||
isPending,
|
isPending,
|
||||||
style,
|
style,
|
||||||
measure,
|
measure,
|
||||||
scrollWindow
|
scrollWindow,
|
||||||
|
isLastMessage,
|
||||||
|
unreadMarkerRef
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const renderSigil = Boolean((nextMsg && msg.author !== nextMsg.author) || !nextMsg || msg.number === 1);
|
const renderSigil = Boolean((nextMsg && msg.author !== nextMsg.author) || !nextMsg || msg.number === 1);
|
||||||
@ -92,7 +91,7 @@ export default class ChatMessage extends Component<ChatMessageProps> {
|
|||||||
|
|
||||||
const containerClass = `${renderSigil
|
const containerClass = `${renderSigil
|
||||||
? `w-100 flex flex-wrap cf pr3 f7 pt4 pl3 lh-copy`
|
? `w-100 flex flex-wrap cf pr3 f7 pt4 pl3 lh-copy`
|
||||||
: `w-100 flex flex-wrap cf pr3 hide-child`} ${isPending ? ' o-40' : ''} ${className}`
|
: `w-100 flex flex-wrap cf pr3 hide-child`} ${isPending ? 'o-40' : ''} ${isLastMessage ? 'pb3' : ''} ${className}`
|
||||||
|
|
||||||
const timestamp = moment.unix(msg.when / 1000).format(renderSigil ? 'hh:mm a' : 'hh:mm');
|
const timestamp = moment.unix(msg.when / 1000).format(renderSigil ? 'hh:mm a' : 'hh:mm');
|
||||||
|
|
||||||
@ -116,15 +115,19 @@ export default class ChatMessage extends Component<ChatMessageProps> {
|
|||||||
scrollWindow
|
scrollWindow
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const unreadContainerStyle = {
|
||||||
|
height: isLastRead ? '1.66em' : '0',
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={this.divRef} className={containerClass} style={style} data-number={msg.number}>
|
<div ref={this.divRef} className={containerClass} style={style} data-number={msg.number}>
|
||||||
{dayBreak && !isFirstUnread ? <DayBreak when={msg.when} /> : null}
|
{dayBreak && !isLastRead ? <DayBreak when={msg.when} /> : null}
|
||||||
{renderSigil
|
{renderSigil
|
||||||
? <MessageWithSigil {...messageProps} />
|
? <MessageWithSigil {...messageProps} />
|
||||||
: <MessageWithoutSigil {...messageProps} />}
|
: <MessageWithoutSigil {...messageProps} />}
|
||||||
{isFirstUnread
|
<Box fontSize='0' position='relative' width='100%' overflow='hidden' style={unreadContainerStyle}>{isLastRead
|
||||||
? <UnreadMarker ref={unreadRef} dayBreak={dayBreak} when={msg.when} style={{ marginTop: (renderSigil ? "-17px" : "-6px") }} />
|
? <UnreadMarker dayBreak={dayBreak} when={msg.when} ref={unreadMarkerRef} />
|
||||||
: null}
|
: null}</Box>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -49,10 +49,12 @@ interface ChatWindowState {
|
|||||||
fetchPending: boolean;
|
fetchPending: boolean;
|
||||||
idle: boolean;
|
idle: boolean;
|
||||||
initialized: boolean;
|
initialized: boolean;
|
||||||
|
lastRead: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class ChatWindow extends Component<ChatWindowProps, ChatWindowState> {
|
export default class ChatWindow extends Component<ChatWindowProps, ChatWindowState> {
|
||||||
private virtualList: VirtualScroller | null;
|
private virtualList: VirtualScroller | null;
|
||||||
|
private unreadMarkerRef: React.RefObject<HTMLDivElement>;
|
||||||
|
|
||||||
INITIALIZATION_MAX_TIME = 1500;
|
INITIALIZATION_MAX_TIME = 1500;
|
||||||
|
|
||||||
@ -62,18 +64,20 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
|||||||
this.state = {
|
this.state = {
|
||||||
fetchPending: false,
|
fetchPending: false,
|
||||||
idle: true,
|
idle: true,
|
||||||
initialized: false
|
initialized: false,
|
||||||
|
lastRead: props.unreadCount ? props.mailboxSize - props.unreadCount : Infinity,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.dismissUnread = this.dismissUnread.bind(this);
|
this.dismissUnread = this.dismissUnread.bind(this);
|
||||||
this.initialIndex = this.initialIndex.bind(this);
|
|
||||||
this.scrollToUnread = this.scrollToUnread.bind(this);
|
this.scrollToUnread = this.scrollToUnread.bind(this);
|
||||||
this.handleWindowBlur = this.handleWindowBlur.bind(this);
|
this.handleWindowBlur = this.handleWindowBlur.bind(this);
|
||||||
this.handleWindowFocus = this.handleWindowFocus.bind(this);
|
this.handleWindowFocus = this.handleWindowFocus.bind(this);
|
||||||
this.stayLockedIfActive = this.stayLockedIfActive.bind(this);
|
this.stayLockedIfActive = this.stayLockedIfActive.bind(this);
|
||||||
this.firstUnread = this.firstUnread.bind(this);
|
this.dismissIfLineVisible = this.dismissIfLineVisible.bind(this);
|
||||||
|
this.lastRead = this.lastRead.bind(this);
|
||||||
|
|
||||||
this.virtualList = null;
|
this.virtualList = null;
|
||||||
|
this.unreadMarkerRef = React.createRef();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@ -98,14 +102,6 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
|||||||
this.setState({ idle: false });
|
this.setState({ idle: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
initialIndex() {
|
|
||||||
const { mailboxSize, unreadCount } = this.props;
|
|
||||||
return Math.min(Math.max(mailboxSize - 1 < INITIAL_LOAD
|
|
||||||
? 0
|
|
||||||
: this.firstUnread(),
|
|
||||||
0), mailboxSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
initialFetch() {
|
initialFetch() {
|
||||||
const { envelopes, mailboxSize, unreadCount } = this.props;
|
const { envelopes, mailboxSize, unreadCount } = this.props;
|
||||||
if (envelopes.length > 0) {
|
if (envelopes.length > 0) {
|
||||||
@ -113,17 +109,9 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
|||||||
this.stayLockedIfActive();
|
this.stayLockedIfActive();
|
||||||
this.fetchMessages(start, start + DEFAULT_BACKLOG_SIZE, true).then(() => {
|
this.fetchMessages(start, start + DEFAULT_BACKLOG_SIZE, true).then(() => {
|
||||||
if (!this.virtualList) return;
|
if (!this.virtualList) return;
|
||||||
const initialIndex = this.initialIndex();
|
this.setState({ idle: false });
|
||||||
this.virtualList.scrollToData(initialIndex).then(() => {
|
this.setState({ initialized: true });
|
||||||
if (
|
this.dismissIfLineVisible();
|
||||||
initialIndex === mailboxSize
|
|
||||||
|| (this.virtualList && this.virtualList.window && this.virtualList.window.scrollTop === 0)
|
|
||||||
) {
|
|
||||||
this.setState({ idle: false });
|
|
||||||
this.dismissUnread();
|
|
||||||
}
|
|
||||||
this.setState({ initialized: true });
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@ -199,9 +187,31 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
firstUnread() {
|
lastRead() {
|
||||||
const { mailboxSize, unreadCount } = this.props;
|
const { mailboxSize, unreadCount } = this.props;
|
||||||
return mailboxSize - unreadCount + 1;
|
return mailboxSize - unreadCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
onScroll({ scrollTop, scrollHeight, windowHeight }) {
|
||||||
|
if (!this.state.idle && scrollTop > IDLE_THRESHOLD) {
|
||||||
|
this.setState({ idle: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dismissIfLineVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
dismissIfLineVisible() {
|
||||||
|
if (this.props.unreadCount === 0) return;
|
||||||
|
if (!this.unreadMarkerRef.current || !this.virtualList?.window) return;
|
||||||
|
const parent = this.unreadMarkerRef.current.parentElement?.parentElement;
|
||||||
|
if (!parent) return;
|
||||||
|
const { scrollTop, scrollHeight, offsetHeight } = this.virtualList.window;
|
||||||
|
if (
|
||||||
|
(scrollHeight - parent.offsetTop > scrollTop)
|
||||||
|
&& (scrollHeight - parent.offsetTop < scrollTop + offsetHeight)
|
||||||
|
) {
|
||||||
|
this.dismissUnread();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@ -224,6 +234,8 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
|||||||
remoteContentPolicy,
|
remoteContentPolicy,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
const unreadMarkerRef = this.unreadMarkerRef;
|
||||||
|
|
||||||
const messages = new Map();
|
const messages = new Map();
|
||||||
let lastMessage = 0;
|
let lastMessage = 0;
|
||||||
|
|
||||||
@ -242,7 +254,7 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
|||||||
lastMessage = mailboxSize + index;
|
lastMessage = mailboxSize + index;
|
||||||
});
|
});
|
||||||
|
|
||||||
const messageProps = { association, group, contacts, hideAvatars, hideNicknames, remoteContentPolicy };
|
const messageProps = { association, group, contacts, hideAvatars, hideNicknames, remoteContentPolicy, unreadMarkerRef };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -262,11 +274,7 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
|||||||
this.setState({ idle: false });
|
this.setState({ idle: false });
|
||||||
this.dismissUnread();
|
this.dismissUnread();
|
||||||
}}
|
}}
|
||||||
onScroll={({ scrollTop }) => {
|
onScroll={this.onScroll.bind(this)}
|
||||||
if (!this.state.idle && scrollTop > IDLE_THRESHOLD) {
|
|
||||||
this.setState({ idle: true });
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
data={messages}
|
data={messages}
|
||||||
size={mailboxSize + stationPendingMessages.length}
|
size={mailboxSize + stationPendingMessages.length}
|
||||||
renderer={({ index, measure, scrollWindow }) => {
|
renderer={({ index, measure, scrollWindow }) => {
|
||||||
@ -276,15 +284,14 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
|||||||
return <MessagePlaceholder key={index} height="64px" index={index} />;
|
return <MessagePlaceholder key={index} height="64px" index={index} />;
|
||||||
}
|
}
|
||||||
const isPending: boolean = 'pending' in msg && Boolean(msg.pending);
|
const isPending: boolean = 'pending' in msg && Boolean(msg.pending);
|
||||||
const isFirstUnread: boolean = Boolean(unreadCount && index === this.firstUnread());
|
|
||||||
const isLastMessage: boolean = Boolean(index === lastMessage)
|
const isLastMessage: boolean = Boolean(index === lastMessage)
|
||||||
const props = { measure, scrollWindow, isPending, isFirstUnread, msg, ...messageProps };
|
const isLastRead: boolean = Boolean(!isLastMessage && index === this.state.lastRead);
|
||||||
|
const props = { measure, scrollWindow, isPending, isLastRead, isLastMessage, msg, ...messageProps };
|
||||||
return (
|
return (
|
||||||
<ChatMessage
|
<ChatMessage
|
||||||
key={index}
|
key={index}
|
||||||
previousMsg={messages.get(index + 1)}
|
previousMsg={messages.get(index + 1)}
|
||||||
nextMsg={messages.get(index - 1)}
|
nextMsg={messages.get(index - 1)}
|
||||||
className={isLastMessage ? 'pb3' : ''}
|
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
Loading…
Reference in New Issue
Block a user