mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 13:05:32 +09:00
fix(app): keep unsent messages and stop the unread jump on reconnect (#1861)
This commit is contained in:
1 parent
2af0405317
commit
b7f75e25ad
16 files changed
+295
-312
No files matched your search
@@ -445,7 +445,7 @@ const UnreadChannelCard = observer(function UnreadChannelCard({
|
|||||||
highlightedMessageId: null,
|
highlightedMessageId: null,
|
||||||
messageDisplayCompact,
|
messageDisplayCompact,
|
||||||
messageGroupSpacing,
|
messageGroupSpacing,
|
||||||
revealedMessageId: null,
|
unblurredMessageId: null,
|
||||||
onMessageEdit: undefined,
|
onMessageEdit: undefined,
|
||||||
onReveal: undefined,
|
onReveal: undefined,
|
||||||
messageRowClassName: styles.messageRow,
|
messageRowClassName: styles.messageRow,
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ interface RenderChannelStreamProps {
|
|||||||
highlightedMessageId: string | null;
|
highlightedMessageId: string | null;
|
||||||
messageDisplayCompact: boolean;
|
messageDisplayCompact: boolean;
|
||||||
messageGroupSpacing: number;
|
messageGroupSpacing: number;
|
||||||
revealedMessageId: string | null;
|
unblurredMessageId: string | null;
|
||||||
onMessageEdit?: (target: HTMLElement) => void;
|
onMessageEdit?: (target: HTMLElement) => void;
|
||||||
onReveal?: (messageId: string | null) => void;
|
onReveal?: (messageId: string | null) => void;
|
||||||
messageRowClassName?: string;
|
messageRowClassName?: string;
|
||||||
@@ -54,7 +54,7 @@ export function renderChannelStream(props: RenderChannelStreamProps): Array<Reac
|
|||||||
highlightedMessageId,
|
highlightedMessageId,
|
||||||
messageDisplayCompact,
|
messageDisplayCompact,
|
||||||
messageGroupSpacing,
|
messageGroupSpacing,
|
||||||
revealedMessageId,
|
unblurredMessageId,
|
||||||
onMessageEdit,
|
onMessageEdit,
|
||||||
onReveal,
|
onReveal,
|
||||||
messageRowClassName,
|
messageRowClassName,
|
||||||
@@ -182,14 +182,14 @@ export function renderChannelStream(props: RenderChannelStreamProps): Array<Reac
|
|||||||
registerKey(item.key, 'BlockedMessageGroups', i, {
|
registerKey(item.key, 'BlockedMessageGroups', i, {
|
||||||
groupId: item.key ?? null,
|
groupId: item.key ?? null,
|
||||||
itemCount: Array.isArray(item.content) ? item.content.length : 0,
|
itemCount: Array.isArray(item.content) ? item.content.length : 0,
|
||||||
revealed: item.key === revealedMessageId,
|
revealed: item.key === unblurredMessageId,
|
||||||
variant,
|
variant,
|
||||||
});
|
});
|
||||||
pushSpacerIfNeeded('regular', item.key ?? `${variant}-${i}`);
|
pushSpacerIfNeeded('regular', item.key ?? `${variant}-${i}`);
|
||||||
nodes.push(
|
nodes.push(
|
||||||
<BlockedMessageGroups
|
<BlockedMessageGroups
|
||||||
key={item.key}
|
key={item.key}
|
||||||
revealed={item.key === revealedMessageId}
|
revealed={item.key === unblurredMessageId}
|
||||||
messageGroups={item.content as Array<ChannelStreamItem>}
|
messageGroups={item.content as Array<ChannelStreamItem>}
|
||||||
onReveal={onReveal ?? (() => {})}
|
onReveal={onReveal ?? (() => {})}
|
||||||
compact={messageDisplayCompact}
|
compact={messageDisplayCompact}
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ interface MessagesStateSnapshot {
|
|||||||
lastReadStateMessageId: string | null;
|
lastReadStateMessageId: string | null;
|
||||||
messages: ChannelMessages;
|
messages: ChannelMessages;
|
||||||
messageVersion: number;
|
messageVersion: number;
|
||||||
revealedMessageId: string | null;
|
unblurredMessageId: string | null;
|
||||||
permissionVersion: number;
|
permissionVersion: number;
|
||||||
messageGroupSpacing: number;
|
messageGroupSpacing: number;
|
||||||
fontSize: number;
|
fontSize: number;
|
||||||
@@ -124,7 +124,7 @@ const readFromState = (channelId: string): MessagesStateSnapshot => {
|
|||||||
lastReadStateMessageId: ReadStates.lastMessageId(channelId),
|
lastReadStateMessageId: ReadStates.lastMessageId(channelId),
|
||||||
messages,
|
messages,
|
||||||
messageVersion: messages.version,
|
messageVersion: messages.version,
|
||||||
revealedMessageId: messages.revealedMessageId,
|
unblurredMessageId: messages.unblurredMessageId,
|
||||||
permissionVersion: Permission.version,
|
permissionVersion: Permission.version,
|
||||||
messageGroupSpacing: Accessibility.getMessageGroupSpacingValue(messageDisplayCompact),
|
messageGroupSpacing: Accessibility.getMessageGroupSpacingValue(messageDisplayCompact),
|
||||||
fontSize: Accessibility.fontSize,
|
fontSize: Accessibility.fontSize,
|
||||||
@@ -191,8 +191,8 @@ export const Messages = observer(function Messages({
|
|||||||
const jumpHighlightTimeoutRef = useRef<number | null>(null);
|
const jumpHighlightTimeoutRef = useRef<number | null>(null);
|
||||||
const lastJumpSequenceKeyRef = useRef<string | null>(null);
|
const lastJumpSequenceKeyRef = useRef<string | null>(null);
|
||||||
const handleJumpHighlight = useCallback(
|
const handleJumpHighlight = useCallback(
|
||||||
(targetMessageId: string | null, jumpSequenceId: number) => {
|
(targetMessageId: string | null, jumpTicket: number) => {
|
||||||
const jumpSequenceKey = `${channel.id}:${jumpSequenceId}`;
|
const jumpSequenceKey = `${channel.id}:${jumpTicket}`;
|
||||||
if (jumpSequenceKey === lastJumpSequenceKeyRef.current) return;
|
if (jumpSequenceKey === lastJumpSequenceKeyRef.current) return;
|
||||||
lastJumpSequenceKeyRef.current = jumpSequenceKey;
|
lastJumpSequenceKeyRef.current = jumpSequenceKey;
|
||||||
if (jumpHighlightTimeoutRef.current != null) {
|
if (jumpHighlightTimeoutRef.current != null) {
|
||||||
@@ -290,7 +290,7 @@ export const Messages = observer(function Messages({
|
|||||||
);
|
);
|
||||||
const onScrollToPresent = useCallback(() => {
|
const onScrollToPresent = useCallback(() => {
|
||||||
if (state.messages?.hasMoreAfter) {
|
if (state.messages?.hasMoreAfter) {
|
||||||
MessageCommands.jumpToPresent(channel.id, MAX_MESSAGES_PER_CHANNEL);
|
MessageCommands.jumpToLiveEdge(channel.id, MAX_MESSAGES_PER_CHANNEL);
|
||||||
} else {
|
} else {
|
||||||
scrollManager.scrollSetToBottom(false);
|
scrollManager.scrollSetToBottom(false);
|
||||||
}
|
}
|
||||||
@@ -316,7 +316,7 @@ export const Messages = observer(function Messages({
|
|||||||
}, [channel.id, state.ackMessageId, state.oldestUnreadMessageId]);
|
}, [channel.id, state.ackMessageId, state.oldestUnreadMessageId]);
|
||||||
const onScrollToPresentAndAck = useCallback(() => {
|
const onScrollToPresentAndAck = useCallback(() => {
|
||||||
if (state.messages?.hasMoreAfter) {
|
if (state.messages?.hasMoreAfter) {
|
||||||
MessageCommands.jumpToPresent(channel.id, MAX_MESSAGES_PER_CHANNEL);
|
MessageCommands.jumpToLiveEdge(channel.id, MAX_MESSAGES_PER_CHANNEL);
|
||||||
} else {
|
} else {
|
||||||
scrollManager.scrollSetToBottom(false);
|
scrollManager.scrollSetToBottom(false);
|
||||||
}
|
}
|
||||||
@@ -357,7 +357,7 @@ export const Messages = observer(function Messages({
|
|||||||
MessageEdit.subscribe(updateFromState),
|
MessageEdit.subscribe(updateFromState),
|
||||||
];
|
];
|
||||||
const onForceJumpToPresent = () => {
|
const onForceJumpToPresent = () => {
|
||||||
MessageCommands.jumpToPresent(channel.id, MAX_MESSAGES_PER_CHANNEL);
|
MessageCommands.jumpToLiveEdge(channel.id, MAX_MESSAGES_PER_CHANNEL);
|
||||||
};
|
};
|
||||||
const onScrollPageUp = () => scrollManager.scrollPageUp(true);
|
const onScrollPageUp = () => scrollManager.scrollPageUp(true);
|
||||||
const onScrollPageDown = () => scrollManager.scrollPageDown(true);
|
const onScrollPageDown = () => scrollManager.scrollPageDown(true);
|
||||||
@@ -523,16 +523,16 @@ export const Messages = observer(function Messages({
|
|||||||
}, [channel.id, state.messages?.ready, state.messageVersion]);
|
}, [channel.id, state.messages?.ready, state.messageVersion]);
|
||||||
const immediateHighlightedMessageId = (() => {
|
const immediateHighlightedMessageId = (() => {
|
||||||
const messages = state.messages;
|
const messages = state.messages;
|
||||||
if (!messages?.ready || !messages.jumped || !messages.jumpFlash || !messages.jumpTargetId) {
|
if (!messages?.ready || !messages.hasJumped || !messages.jumpHighlight || !messages.jumpDestinationId) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return messages.jumpTargetId !== channel.id ? messages.jumpTargetId : null;
|
return messages.jumpDestinationId !== channel.id ? messages.jumpDestinationId : null;
|
||||||
})();
|
})();
|
||||||
const highlightedMessageId = immediateHighlightedMessageId ?? state.highlightedMessageId;
|
const highlightedMessageId = immediateHighlightedMessageId ?? state.highlightedMessageId;
|
||||||
const collapsedMessageVisibility = useMemo(
|
const collapsedMessageVisibility = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
isMessageRevealed: (message: Message) => {
|
isMessageRevealed: (message: Message) => {
|
||||||
if (!state.revealedMessageId || !state.messages || message.channelId !== channel.id) {
|
if (!state.unblurredMessageId || !state.messages || message.channelId !== channel.id) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
@@ -541,11 +541,11 @@ export const Messages = observer(function Messages({
|
|||||||
messages: state.messages,
|
messages: state.messages,
|
||||||
messageId: message.id,
|
messageId: message.id,
|
||||||
treatSpam: true,
|
treatSpam: true,
|
||||||
}) === state.revealedMessageId
|
}) === state.unblurredMessageId
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
[channel, state.messages, state.revealedMessageId, state.messageVersion, spammerOverrideVersion],
|
[channel, state.messages, state.unblurredMessageId, state.messageVersion, spammerOverrideVersion],
|
||||||
);
|
);
|
||||||
const {canChat, canAttachFiles} = useMemo(
|
const {canChat, canAttachFiles} = useMemo(
|
||||||
() => checkPermissions(channel),
|
() => checkPermissions(channel),
|
||||||
@@ -560,7 +560,7 @@ export const Messages = observer(function Messages({
|
|||||||
highlightedMessageId,
|
highlightedMessageId,
|
||||||
messageDisplayCompact: state.messageDisplayCompact,
|
messageDisplayCompact: state.messageDisplayCompact,
|
||||||
messageGroupSpacing: state.messageGroupSpacing,
|
messageGroupSpacing: state.messageGroupSpacing,
|
||||||
revealedMessageId: state.revealedMessageId,
|
unblurredMessageId: state.unblurredMessageId,
|
||||||
onMessageEdit,
|
onMessageEdit,
|
||||||
onReveal,
|
onReveal,
|
||||||
});
|
});
|
||||||
@@ -571,7 +571,7 @@ export const Messages = observer(function Messages({
|
|||||||
highlightedMessageId,
|
highlightedMessageId,
|
||||||
state.messageDisplayCompact,
|
state.messageDisplayCompact,
|
||||||
state.messageGroupSpacing,
|
state.messageGroupSpacing,
|
||||||
state.revealedMessageId,
|
state.unblurredMessageId,
|
||||||
onMessageEdit,
|
onMessageEdit,
|
||||||
onReveal,
|
onReveal,
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ const ForwardedFromSource = observer(({message}: {message: Message}) => {
|
|||||||
const handleJumpToOriginal = useCallback(() => {
|
const handleJumpToOriginal = useCallback(() => {
|
||||||
if (message.messageReference && sourceChannel) {
|
if (message.messageReference && sourceChannel) {
|
||||||
goToMessage(message.messageReference.channel_id, message.messageReference.message_id, {
|
goToMessage(message.messageReference.channel_id, message.messageReference.message_id, {
|
||||||
returnTargetId: message.id,
|
returnToMessageId: message.id,
|
||||||
returnChannelId: message.channelId,
|
returnChannelId: message.channelId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export const PinSystemMessage = observer(({message}: PinSystemMessageProps) => {
|
|||||||
const jumpToMessage = useCallback(() => {
|
const jumpToMessage = useCallback(() => {
|
||||||
if (message.messageReference?.message_id) {
|
if (message.messageReference?.message_id) {
|
||||||
goToMessage(message.channelId, message.messageReference.message_id, {
|
goToMessage(message.channelId, message.messageReference.message_id, {
|
||||||
returnTargetId: message.id,
|
returnToMessageId: message.id,
|
||||||
returnChannelId: message.channelId,
|
returnChannelId: message.channelId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ export const ReplyPreview = observer(
|
|||||||
const jumpToRepliedMessage = useCallback(() => {
|
const jumpToRepliedMessage = useCallback(() => {
|
||||||
if (message.messageReference?.message_id) {
|
if (message.messageReference?.message_id) {
|
||||||
goToMessage(message.channelId, message.messageReference.message_id, {
|
goToMessage(message.channelId, message.messageReference.message_id, {
|
||||||
returnTargetId: message.id,
|
returnToMessageId: message.id,
|
||||||
returnChannelId: message.channelId,
|
returnChannelId: message.channelId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -185,5 +185,5 @@ function handleReadyInternal(data: ReadyPayload, context: GatewayHandlerContext)
|
|||||||
MediaEngine.handleConnectionOpen(guilds);
|
MediaEngine.handleConnectionOpen(guilds);
|
||||||
Initialization.setReady();
|
Initialization.setReady();
|
||||||
context.setReady();
|
context.setReady();
|
||||||
Messages.handleConnectionOpen((channelId) => ReadStates.getUnreadJumpAnchor(channelId));
|
Messages.handleConnectionOpen();
|
||||||
}
|
}
|
||||||
@@ -100,7 +100,7 @@ export interface JumpToMessageOptions {
|
|||||||
messageId: string;
|
messageId: string;
|
||||||
flash?: boolean;
|
flash?: boolean;
|
||||||
offset?: number;
|
offset?: number;
|
||||||
returnTargetId?: string | null;
|
returnToMessageId?: string | null;
|
||||||
returnChannelId?: string | null;
|
returnChannelId?: string | null;
|
||||||
returnGuildId?: string | null;
|
returnGuildId?: string | null;
|
||||||
jumpType?: JumpType;
|
jumpType?: JumpType;
|
||||||
@@ -141,7 +141,7 @@ function makeFetchKey(
|
|||||||
return (
|
return (
|
||||||
`${channelId}${SEP}${before ?? ''}${SEP}${after ?? ''}${SEP}${limit}${SEP}${throwOnError}${SEP}` +
|
`${channelId}${SEP}${before ?? ''}${SEP}${after ?? ''}${SEP}${limit}${SEP}${throwOnError}${SEP}` +
|
||||||
`${jump.present ? '1' : '0'}${SEP}${jump.messageId ?? ''}${SEP}${jump.offset ?? 0}${SEP}` +
|
`${jump.present ? '1' : '0'}${SEP}${jump.messageId ?? ''}${SEP}${jump.offset ?? 0}${SEP}` +
|
||||||
`${jump.flash ? '1' : '0'}${SEP}${jump.returnMessageId ?? ''}${SEP}` +
|
`${jump.flash ? '1' : '0'}${SEP}${jump.returnToMessageId ?? ''}${SEP}` +
|
||||||
`${jump.returnChannelId ?? ''}${SEP}${jump.returnGuildId ?? ''}${SEP}${jump.jumpType ?? ''}`
|
`${jump.returnChannelId ?? ''}${SEP}${jump.returnGuildId ?? ''}${SEP}${jump.jumpType ?? ''}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -291,14 +291,14 @@ interface SendMessageParams {
|
|||||||
tts?: boolean;
|
tts?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function jumpToPresent(channelId: string, limit = MAX_MESSAGES_PER_CHANNEL): void {
|
export function jumpToLiveEdge(channelId: string, limit = MAX_MESSAGES_PER_CHANNEL): void {
|
||||||
NavigationCommands.clearMessageIdForChannel(channelId);
|
NavigationCommands.clearMessageIdForChannel(channelId);
|
||||||
logger.debug(`Jumping to present in channel ${channelId}`);
|
logger.debug(`Jumping to present in channel ${channelId}`);
|
||||||
ReadStateCommands.clearStickyUnread(channelId);
|
ReadStateCommands.clearStickyUnread(channelId);
|
||||||
const jump: JumpOptions = {
|
const jump: JumpOptions = {
|
||||||
present: true,
|
present: true,
|
||||||
};
|
};
|
||||||
if (Messages.hasPresent(channelId)) {
|
if (Messages.hasNewestMessages(channelId)) {
|
||||||
Messages.handleLoadMessagesSuccessCached({channelId, jump, limit});
|
Messages.handleLoadMessagesSuccessCached({channelId, jump, limit});
|
||||||
} else {
|
} else {
|
||||||
fetchMessages(channelId, null, null, limit, jump);
|
fetchMessages(channelId, null, null, limit, jump);
|
||||||
@@ -310,7 +310,7 @@ export function jumpToMessage({
|
|||||||
messageId,
|
messageId,
|
||||||
flash = true,
|
flash = true,
|
||||||
offset,
|
offset,
|
||||||
returnTargetId,
|
returnToMessageId,
|
||||||
returnChannelId,
|
returnChannelId,
|
||||||
returnGuildId,
|
returnGuildId,
|
||||||
jumpType,
|
jumpType,
|
||||||
@@ -320,7 +320,7 @@ export function jumpToMessage({
|
|||||||
messageId: messageId as MessageId,
|
messageId: messageId as MessageId,
|
||||||
flash,
|
flash,
|
||||||
offset,
|
offset,
|
||||||
returnMessageId: returnTargetId as MessageId | null | undefined,
|
returnToMessageId: returnToMessageId as MessageId | null | undefined,
|
||||||
returnChannelId,
|
returnChannelId,
|
||||||
returnGuildId,
|
returnGuildId,
|
||||||
jumpType,
|
jumpType,
|
||||||
@@ -337,10 +337,10 @@ function getMessageFetchCacheHit(
|
|||||||
if (jump?.messageId && messages.has(jump.messageId, false)) {
|
if (jump?.messageId && messages.has(jump.messageId, false)) {
|
||||||
return 'jump';
|
return 'jump';
|
||||||
}
|
}
|
||||||
if (before && messages.hasBeforeCached(before)) {
|
if (before && messages.canServeOlderFrom(before)) {
|
||||||
return 'before';
|
return 'before';
|
||||||
}
|
}
|
||||||
if (after && messages.hasAfterCached(after)) {
|
if (after && messages.canServeNewerFrom(after)) {
|
||||||
return 'after';
|
return 'after';
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -208,7 +208,7 @@ interface JumpLinkMentionProps {
|
|||||||
channel: Channel;
|
channel: Channel;
|
||||||
guild: Guild | null;
|
guild: Guild | null;
|
||||||
messageId?: string;
|
messageId?: string;
|
||||||
returnTargetId?: string;
|
returnToMessageId?: string;
|
||||||
returnChannelId?: string;
|
returnChannelId?: string;
|
||||||
url: string;
|
url: string;
|
||||||
i18n: I18n;
|
i18n: I18n;
|
||||||
@@ -264,7 +264,7 @@ const JumpLinkMention = observer(function JumpLinkMention({
|
|||||||
channel,
|
channel,
|
||||||
guild,
|
guild,
|
||||||
messageId,
|
messageId,
|
||||||
returnTargetId,
|
returnToMessageId,
|
||||||
returnChannelId,
|
returnChannelId,
|
||||||
url,
|
url,
|
||||||
i18n,
|
i18n,
|
||||||
@@ -276,11 +276,11 @@ const JumpLinkMention = observer(function JumpLinkMention({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (messageId) {
|
if (messageId) {
|
||||||
goToMessage(channel.id, messageId, {returnTargetId, returnChannelId});
|
goToMessage(channel.id, messageId, {returnToMessageId, returnChannelId});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
NavigationCommands.selectChannel(channel.guildId ?? undefined, channel.id);
|
NavigationCommands.selectChannel(channel.guildId ?? undefined, channel.id);
|
||||||
}, [channel, messageId, returnChannelId, returnTargetId]);
|
}, [channel, messageId, returnChannelId, returnToMessageId]);
|
||||||
const handleClick = useCallback(
|
const handleClick = useCallback(
|
||||||
(event: React.MouseEvent<HTMLButtonElement | HTMLSpanElement>) => {
|
(event: React.MouseEvent<HTMLButtonElement | HTMLSpanElement>) => {
|
||||||
if (!interactive) return;
|
if (!interactive) return;
|
||||||
@@ -674,7 +674,7 @@ export const LinkRenderer = observer(function LinkRenderer({
|
|||||||
channel={jumpChannel}
|
channel={jumpChannel}
|
||||||
guild={jumpGuild}
|
guild={jumpGuild}
|
||||||
messageId={messageJumpTarget?.messageId}
|
messageId={messageJumpTarget?.messageId}
|
||||||
returnTargetId={options.messageId}
|
returnToMessageId={options.messageId}
|
||||||
returnChannelId={options.channelId}
|
returnChannelId={options.channelId}
|
||||||
url={url}
|
url={url}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
@@ -828,7 +828,7 @@ export const LinkRenderer = observer(function LinkRenderer({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
goToMessage(targetChannelId, targetMessageId, {
|
goToMessage(targetChannelId, targetMessageId, {
|
||||||
returnTargetId: options.messageId,
|
returnToMessageId: options.messageId,
|
||||||
returnChannelId: options.channelId,
|
returnChannelId: options.channelId,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export interface JumpOptions {
|
|||||||
offset?: number;
|
offset?: number;
|
||||||
present?: boolean;
|
present?: boolean;
|
||||||
flash?: boolean;
|
flash?: boolean;
|
||||||
returnMessageId?: MessageId | null;
|
returnToMessageId?: MessageId | null;
|
||||||
returnChannelId?: string | null;
|
returnChannelId?: string | null;
|
||||||
returnGuildId?: string | null;
|
returnGuildId?: string | null;
|
||||||
jumpType?: JumpType;
|
jumpType?: JumpType;
|
||||||
@@ -34,14 +34,14 @@ interface JumpToMessageOptions {
|
|||||||
messageId: string;
|
messageId: string;
|
||||||
flash?: boolean;
|
flash?: boolean;
|
||||||
offset?: number;
|
offset?: number;
|
||||||
returnTargetId?: MessageId | null;
|
returnToMessageId?: MessageId | null;
|
||||||
returnChannelId?: string | null;
|
returnChannelId?: string | null;
|
||||||
returnGuildId?: string | null;
|
returnGuildId?: string | null;
|
||||||
jumpType?: JumpType;
|
jumpType?: JumpType;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LoadCompleteOptions {
|
interface LoadCompleteOptions {
|
||||||
newMessages: Array<MessageInput>;
|
windowMessages: Array<MessageInput>;
|
||||||
isBefore?: boolean;
|
isBefore?: boolean;
|
||||||
isAfter?: boolean;
|
isAfter?: boolean;
|
||||||
jump?: JumpOptions | null;
|
jump?: JumpOptions | null;
|
||||||
@@ -61,6 +61,10 @@ function isUploadPlaceholder(message: Message): boolean {
|
|||||||
return UploadingAttachment.isInSendingMessage(message);
|
return UploadingAttachment.isInSendingMessage(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function unsentCorrelationKey(message: Message): string {
|
||||||
|
return `${message.author.id}\u0000${message.messageReference?.message_id ?? ''}\u0000${message.content}`;
|
||||||
|
}
|
||||||
|
|
||||||
function embedsEqualIgnoringId(a: unknown, b: unknown): boolean {
|
function embedsEqualIgnoringId(a: unknown, b: unknown): boolean {
|
||||||
if (a === b) return true;
|
if (a === b) return true;
|
||||||
if (a == null || b == null) return a === b;
|
if (a == null || b == null) return a === b;
|
||||||
@@ -182,7 +186,7 @@ class MessageBufferSegment {
|
|||||||
this.items = this.items.filter((m) => m.id !== id);
|
this.items = this.items.filter((m) => m.id !== id);
|
||||||
}
|
}
|
||||||
|
|
||||||
removeMany(ids: Array<string>): void {
|
removeIds(ids: Array<string>): void {
|
||||||
if (!ids.length) return;
|
if (!ids.length) return;
|
||||||
const idSet = ids.length > 1 ? new Set(ids) : null;
|
const idSet = ids.length > 1 ? new Set(ids) : null;
|
||||||
for (const id of ids) {
|
for (const id of ids) {
|
||||||
@@ -219,9 +223,7 @@ class MessageBufferSegment {
|
|||||||
if (this.items.length === 0) {
|
if (this.items.length === 0) {
|
||||||
this.reachedBoundary = boundaryAtInsert;
|
this.reachedBoundary = boundaryAtInsert;
|
||||||
}
|
}
|
||||||
const combinedSize = this.items.length + batch.length;
|
if (this.items.length + batch.length > MAX_MESSAGE_CACHE_SIZE) {
|
||||||
const truncated = combinedSize > MAX_MESSAGE_CACHE_SIZE;
|
|
||||||
if (truncated) {
|
|
||||||
this.reachedBoundary = false;
|
this.reachedBoundary = false;
|
||||||
if (batch.length >= MAX_MESSAGE_CACHE_SIZE) {
|
if (batch.length >= MAX_MESSAGE_CACHE_SIZE) {
|
||||||
this.items = this.fromOlderSide
|
this.items = this.fromOlderSide
|
||||||
@@ -229,27 +231,27 @@ class MessageBufferSegment {
|
|||||||
: batch.slice(0, MAX_MESSAGE_CACHE_SIZE);
|
: batch.slice(0, MAX_MESSAGE_CACHE_SIZE);
|
||||||
} else {
|
} else {
|
||||||
const available = MAX_MESSAGE_CACHE_SIZE - batch.length;
|
const available = MAX_MESSAGE_CACHE_SIZE - batch.length;
|
||||||
this.items = this.fromOlderSide
|
const kept = this.fromOlderSide
|
||||||
? this.items.slice(Math.max(this.items.length - available, 0))
|
? this.items.slice(Math.max(this.items.length - available, 0))
|
||||||
: this.items.slice(0, available);
|
: this.items.slice(0, available);
|
||||||
|
this.items = this.fromOlderSide ? kept.concat(batch) : batch.concat(kept);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (truncated) {
|
|
||||||
this.items = this.fromOlderSide ? this.items.concat(batch) : batch.concat(this.items);
|
|
||||||
this.keyedById = {};
|
this.keyedById = {};
|
||||||
for (const msg of this.items) {
|
for (const msg of this.items) {
|
||||||
this.keyedById[msg.id] = msg;
|
this.keyedById[msg.id] = msg;
|
||||||
}
|
}
|
||||||
} else if (this.fromOlderSide) {
|
return;
|
||||||
|
}
|
||||||
|
if (this.fromOlderSide) {
|
||||||
for (const msg of batch) {
|
for (const msg of batch) {
|
||||||
this.items.push(msg);
|
this.items.push(msg);
|
||||||
this.keyedById[msg.id] = msg;
|
this.keyedById[msg.id] = msg;
|
||||||
}
|
}
|
||||||
} else {
|
return;
|
||||||
this.items.unshift(...batch);
|
}
|
||||||
for (const msg of batch) {
|
this.items.unshift(...batch);
|
||||||
this.keyedById[msg.id] = msg;
|
for (const msg of batch) {
|
||||||
}
|
this.keyedById[msg.id] = msg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,19 +289,20 @@ export class ChannelMessages {
|
|||||||
readonly channelId: string;
|
readonly channelId: string;
|
||||||
ready = false;
|
ready = false;
|
||||||
jumpType: JumpType = JumpTypes.ANIMATED;
|
jumpType: JumpType = JumpTypes.ANIMATED;
|
||||||
jumpTargetId: string | null = null;
|
jumpDestinationId: string | null = null;
|
||||||
jumpTargetOffset = 0;
|
jumpDestinationOffset = 0;
|
||||||
jumpSequenceId = 1;
|
jumpTicket = 1;
|
||||||
jumped = false;
|
hasJumped = false;
|
||||||
jumpedToPresent = false;
|
jumpedToPresent = false;
|
||||||
jumpFlash = true;
|
landedAtLiveEdge = false;
|
||||||
jumpReturnTargetId: string | null = null;
|
jumpHighlight = true;
|
||||||
|
jumpReturnMessageId: string | null = null;
|
||||||
jumpReturnChannelId: string | null = null;
|
jumpReturnChannelId: string | null = null;
|
||||||
jumpReturnGuildId: string | null = null;
|
jumpReturnGuildId: string | null = null;
|
||||||
hasMoreBefore = true;
|
hasMoreBefore = true;
|
||||||
hasMoreAfter = false;
|
hasMoreAfter = false;
|
||||||
loadingMore = false;
|
loadingMore = false;
|
||||||
revealedMessageId: string | null = null;
|
unblurredMessageId: string | null = null;
|
||||||
cached = false;
|
cached = false;
|
||||||
error = false;
|
error = false;
|
||||||
version = 0;
|
version = 0;
|
||||||
@@ -318,8 +321,8 @@ export class ChannelMessages {
|
|||||||
return ChannelMessages.channelCache.get(channelId);
|
return ChannelMessages.channelCache.get(channelId);
|
||||||
}
|
}
|
||||||
|
|
||||||
static hasPresent(channelId: string): boolean {
|
static hasNewestMessages(channelId: string): boolean {
|
||||||
return ChannelMessages.get(channelId)?.hasPresent() ?? false;
|
return ChannelMessages.get(channelId)?.hasNewestMessages() ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static getOrCreate(channelId: string): ChannelMessages {
|
static getOrCreate(channelId: string): ChannelMessages {
|
||||||
@@ -350,7 +353,7 @@ export class ChannelMessages {
|
|||||||
ChannelMessages.retainedChannelIds.delete(channelId);
|
ChannelMessages.retainedChannelIds.delete(channelId);
|
||||||
}
|
}
|
||||||
|
|
||||||
static clearCache(channelId: string): void {
|
static dropBuffers(channelId: string): void {
|
||||||
const instance = ChannelMessages.channelCache.get(channelId);
|
const instance = ChannelMessages.channelCache.get(channelId);
|
||||||
if (!instance) return;
|
if (!instance) return;
|
||||||
instance.beforeBuffer.clear();
|
instance.beforeBuffer.clear();
|
||||||
@@ -419,7 +422,7 @@ export class ChannelMessages {
|
|||||||
this.afterBuffer = new MessageBufferSegment(false);
|
this.afterBuffer = new MessageBufferSegment(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
mutate(patch: Partial<ChannelMessages>): ChannelMessages {
|
withPatch(patch: Partial<ChannelMessages>): ChannelMessages {
|
||||||
return this.cloneAnd(patch);
|
return this.cloneAnd(patch);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -451,13 +454,13 @@ export class ChannelMessages {
|
|||||||
return this.messageList.reduce(reducer, initial);
|
return this.messageList.reduce(reducer, initial);
|
||||||
}
|
}
|
||||||
|
|
||||||
forAll(callback: (m: Message, idx: number, arr: Array<Message>) => void, thisArg?: unknown): void {
|
forEachBuffered(callback: (m: Message, idx: number, arr: Array<Message>) => void, thisArg?: unknown): void {
|
||||||
this.beforeBuffer.forEach(callback, thisArg);
|
this.beforeBuffer.forEach(callback, thisArg);
|
||||||
this.messageList.forEach(callback, thisArg);
|
this.messageList.forEach(callback, thisArg);
|
||||||
this.afterBuffer.forEach(callback, thisArg);
|
this.afterBuffer.forEach(callback, thisArg);
|
||||||
}
|
}
|
||||||
|
|
||||||
findOldest(predicate: (m: Message) => boolean): Message | undefined {
|
searchFromOldest(predicate: (m: Message) => boolean): Message | undefined {
|
||||||
return (
|
return (
|
||||||
this.beforeBuffer.messages.find(predicate) ??
|
this.beforeBuffer.messages.find(predicate) ??
|
||||||
this.messageList.find(predicate) ??
|
this.messageList.find(predicate) ??
|
||||||
@@ -465,7 +468,7 @@ export class ChannelMessages {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
findNewest(predicate: (m: Message) => boolean): Message | undefined {
|
searchFromNewest(predicate: (m: Message) => boolean): Message | undefined {
|
||||||
const after = this.afterBuffer.messages;
|
const after = this.afterBuffer.messages;
|
||||||
for (let i = after.length - 1; i >= 0; i--) {
|
for (let i = after.length - 1; i >= 0; i--) {
|
||||||
if (predicate(after[i])) return after[i];
|
if (predicate(after[i])) return after[i];
|
||||||
@@ -498,11 +501,11 @@ export class ChannelMessages {
|
|||||||
return this.beforeBuffer.get(id) ?? this.afterBuffer.get(id);
|
return this.beforeBuffer.get(id) ?? this.afterBuffer.get(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
getByIndex(index: number): Message | undefined {
|
atIndex(index: number): Message | undefined {
|
||||||
return this.messageList[index];
|
return this.messageList[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
getAfter(id: string): Message | null {
|
nextAfter(id: string): Message | null {
|
||||||
const current = this.get(id);
|
const current = this.get(id);
|
||||||
if (!current) return null;
|
if (!current) return null;
|
||||||
const idx = this.messageList.indexOf(current);
|
const idx = this.messageList.indexOf(current);
|
||||||
@@ -520,11 +523,11 @@ export class ChannelMessages {
|
|||||||
return this.messageList.findIndex((m) => m.id === id);
|
return this.messageList.findIndex((m) => m.id === id);
|
||||||
}
|
}
|
||||||
|
|
||||||
hasPresent(): boolean {
|
hasNewestMessages(): boolean {
|
||||||
return (this.afterBuffer.size > 0 && this.afterBuffer.isBoundary) || !this.hasMoreAfter;
|
return (this.afterBuffer.size > 0 && this.afterBuffer.isBoundary) || !this.hasMoreAfter;
|
||||||
}
|
}
|
||||||
|
|
||||||
hasBeforeCached(beforeId: string): boolean {
|
canServeOlderFrom(beforeId: string): boolean {
|
||||||
if (this.messageList.length === 0 || this.beforeBuffer.size === 0) {
|
if (this.messageList.length === 0 || this.beforeBuffer.size === 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -532,7 +535,7 @@ export class ChannelMessages {
|
|||||||
return Boolean(first && first.id === beforeId);
|
return Boolean(first && first.id === beforeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
hasAfterCached(afterId: string): boolean {
|
canServeNewerFrom(afterId: string): boolean {
|
||||||
if (this.messageList.length === 0 || this.afterBuffer.size === 0) {
|
if (this.messageList.length === 0 || this.afterBuffer.size === 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -587,7 +590,7 @@ export class ChannelMessages {
|
|||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
removeMany(ids: Array<string>): ChannelMessages {
|
removeIds(ids: Array<string>): ChannelMessages {
|
||||||
if (!ids.some((id) => this.has(id))) return this;
|
if (!ids.some((id) => this.has(id))) return this;
|
||||||
const idSet = ids.length > 1 ? new Set(ids) : null;
|
const idSet = ids.length > 1 ? new Set(ids) : null;
|
||||||
return this.cloneAnd((draft) => {
|
return this.cloneAnd((draft) => {
|
||||||
@@ -598,8 +601,8 @@ export class ChannelMessages {
|
|||||||
idSet !== null
|
idSet !== null
|
||||||
? draft.messageList.filter((m) => !idSet.has(m.id))
|
? draft.messageList.filter((m) => !idSet.has(m.id))
|
||||||
: draft.messageList.filter((m) => m.id !== ids[0]);
|
: draft.messageList.filter((m) => m.id !== ids[0]);
|
||||||
draft.beforeBuffer.removeMany(ids);
|
draft.beforeBuffer.removeIds(ids);
|
||||||
draft.afterBuffer.removeMany(ids);
|
draft.afterBuffer.removeIds(ids);
|
||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -651,7 +654,7 @@ export class ChannelMessages {
|
|||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
truncateTop(maxCount: number, deepCopy = true): ChannelMessages {
|
trimOldest(maxCount: number, deepCopy = true): ChannelMessages {
|
||||||
const overflow = this.messageList.length - maxCount;
|
const overflow = this.messageList.length - maxCount;
|
||||||
if (overflow <= 0) return this;
|
if (overflow <= 0) return this;
|
||||||
return this.cloneAnd((draft) => {
|
return this.cloneAnd((draft) => {
|
||||||
@@ -664,7 +667,7 @@ export class ChannelMessages {
|
|||||||
}, deepCopy);
|
}, deepCopy);
|
||||||
}
|
}
|
||||||
|
|
||||||
truncateBottom(maxCount: number, deepCopy = true): ChannelMessages {
|
trimNewest(maxCount: number, deepCopy = true): ChannelMessages {
|
||||||
if (this.messageList.length <= maxCount) return this;
|
if (this.messageList.length <= maxCount) return this;
|
||||||
return this.cloneAnd((draft) => {
|
return this.cloneAnd((draft) => {
|
||||||
for (let i = maxCount; i < this.messageList.length; i++) {
|
for (let i = maxCount; i < this.messageList.length; i++) {
|
||||||
@@ -676,18 +679,18 @@ export class ChannelMessages {
|
|||||||
}, deepCopy);
|
}, deepCopy);
|
||||||
}
|
}
|
||||||
|
|
||||||
truncate(trimBottom: boolean, trimTop: boolean): ChannelMessages {
|
trimToWindow(trimBottom: boolean, trimTop: boolean): ChannelMessages {
|
||||||
if (this.length <= MAX_LOADED_MESSAGES) return this;
|
if (this.length <= MAX_LOADED_MESSAGES) return this;
|
||||||
if (trimBottom) {
|
if (trimBottom) {
|
||||||
return this.truncateBottom(TRUNCATED_MESSAGE_VIEW_SIZE);
|
return this.trimNewest(TRUNCATED_MESSAGE_VIEW_SIZE);
|
||||||
}
|
}
|
||||||
if (trimTop) {
|
if (trimTop) {
|
||||||
return this.truncateTop(TRUNCATED_MESSAGE_VIEW_SIZE);
|
return this.trimOldest(TRUNCATED_MESSAGE_VIEW_SIZE);
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
jumpToPresent(limit: number): ChannelMessages {
|
jumpToLiveEdge(limit: number): ChannelMessages {
|
||||||
return this.cloneAnd((draft) => {
|
return this.cloneAnd((draft) => {
|
||||||
const allAfter = draft.afterBuffer.takeAll();
|
const allAfter = draft.afterBuffer.takeAll();
|
||||||
draft.hasMoreAfter = false;
|
draft.hasMoreAfter = false;
|
||||||
@@ -699,15 +702,15 @@ export class ChannelMessages {
|
|||||||
draft.clearAllMessages();
|
draft.clearAllMessages();
|
||||||
draft.mergeInto(visible);
|
draft.mergeInto(visible);
|
||||||
draft.hasMoreBefore = draft.beforeBuffer.size > 0;
|
draft.hasMoreBefore = draft.beforeBuffer.size > 0;
|
||||||
draft.jumped = true;
|
draft.hasJumped = true;
|
||||||
draft.jumpTargetId = null;
|
draft.jumpDestinationId = null;
|
||||||
draft.jumpTargetOffset = 0;
|
draft.jumpDestinationOffset = 0;
|
||||||
draft.jumpedToPresent = true;
|
draft.landedAtLiveEdge = true;
|
||||||
draft.jumpFlash = false;
|
draft.jumpHighlight = false;
|
||||||
draft.jumpReturnTargetId = null;
|
draft.jumpReturnMessageId = null;
|
||||||
draft.jumpReturnChannelId = null;
|
draft.jumpReturnChannelId = null;
|
||||||
draft.jumpReturnGuildId = null;
|
draft.jumpReturnGuildId = null;
|
||||||
draft.jumpSequenceId += 1;
|
draft.jumpTicket += 1;
|
||||||
draft.ready = true;
|
draft.ready = true;
|
||||||
draft.loadingMore = false;
|
draft.loadingMore = false;
|
||||||
}, true);
|
}, true);
|
||||||
@@ -717,22 +720,22 @@ export class ChannelMessages {
|
|||||||
messageId,
|
messageId,
|
||||||
flash = true,
|
flash = true,
|
||||||
offset,
|
offset,
|
||||||
returnTargetId,
|
returnToMessageId,
|
||||||
returnChannelId,
|
returnChannelId,
|
||||||
returnGuildId,
|
returnGuildId,
|
||||||
jumpType,
|
jumpType,
|
||||||
}: JumpToMessageOptions): ChannelMessages {
|
}: JumpToMessageOptions): ChannelMessages {
|
||||||
return this.cloneAnd((draft) => {
|
return this.cloneAnd((draft) => {
|
||||||
draft.jumped = true;
|
draft.hasJumped = true;
|
||||||
draft.jumpedToPresent = false;
|
draft.landedAtLiveEdge = false;
|
||||||
draft.jumpType = jumpType ?? JumpTypes.ANIMATED;
|
draft.jumpType = jumpType ?? JumpTypes.ANIMATED;
|
||||||
draft.jumpTargetId = messageId;
|
draft.jumpDestinationId = messageId;
|
||||||
draft.jumpTargetOffset = messageId && offset != null ? offset : 0;
|
draft.jumpDestinationOffset = messageId && offset != null ? offset : 0;
|
||||||
draft.jumpSequenceId += 1;
|
draft.jumpTicket += 1;
|
||||||
draft.jumpFlash = flash;
|
draft.jumpHighlight = flash;
|
||||||
draft.jumpReturnTargetId = returnTargetId ?? null;
|
draft.jumpReturnMessageId = returnToMessageId ?? null;
|
||||||
draft.jumpReturnChannelId = returnTargetId ? (returnChannelId ?? draft.channelId) : null;
|
draft.jumpReturnChannelId = returnToMessageId ? (returnChannelId ?? draft.channelId) : null;
|
||||||
draft.jumpReturnGuildId = returnTargetId ? (returnGuildId ?? null) : null;
|
draft.jumpReturnGuildId = returnToMessageId ? (returnGuildId ?? null) : null;
|
||||||
draft.ready = true;
|
draft.ready = true;
|
||||||
draft.loadingMore = false;
|
draft.loadingMore = false;
|
||||||
}, false);
|
}, false);
|
||||||
@@ -740,20 +743,20 @@ export class ChannelMessages {
|
|||||||
|
|
||||||
clearJumpTarget(options: {clearReturnTarget?: boolean} = {}): ChannelMessages {
|
clearJumpTarget(options: {clearReturnTarget?: boolean} = {}): ChannelMessages {
|
||||||
const patch: Partial<ChannelMessages> = {
|
const patch: Partial<ChannelMessages> = {
|
||||||
jumped: false,
|
hasJumped: false,
|
||||||
jumpedToPresent: false,
|
landedAtLiveEdge: false,
|
||||||
jumpTargetId: null,
|
jumpDestinationId: null,
|
||||||
jumpTargetOffset: 0,
|
jumpDestinationOffset: 0,
|
||||||
};
|
};
|
||||||
if (options.clearReturnTarget) {
|
if (options.clearReturnTarget) {
|
||||||
patch.jumpReturnTargetId = null;
|
patch.jumpReturnMessageId = null;
|
||||||
patch.jumpReturnChannelId = null;
|
patch.jumpReturnChannelId = null;
|
||||||
patch.jumpReturnGuildId = null;
|
patch.jumpReturnGuildId = null;
|
||||||
}
|
}
|
||||||
return this.cloneAnd(patch);
|
return this.cloneAnd(patch);
|
||||||
}
|
}
|
||||||
|
|
||||||
loadFromCache(before: boolean, limit: number): ChannelMessages {
|
drainBufferedSide(before: boolean, limit: number): ChannelMessages {
|
||||||
let next = this.cloneAnd((draft) => {
|
let next = this.cloneAnd((draft) => {
|
||||||
const buffer = before ? draft.beforeBuffer : draft.afterBuffer;
|
const buffer = before ? draft.beforeBuffer : draft.afterBuffer;
|
||||||
draft.mergeInto(buffer.take(limit), before);
|
draft.mergeInto(buffer.take(limit), before);
|
||||||
@@ -764,14 +767,14 @@ export class ChannelMessages {
|
|||||||
draft.loadingMore = false;
|
draft.loadingMore = false;
|
||||||
}, true);
|
}, true);
|
||||||
if (before) {
|
if (before) {
|
||||||
next = next.truncate(true, false);
|
next = next.trimToWindow(true, false);
|
||||||
} else {
|
} else {
|
||||||
next = next.truncate(false, true);
|
next = next.trimToWindow(false, true);
|
||||||
}
|
}
|
||||||
return next;
|
return next;
|
||||||
}
|
}
|
||||||
|
|
||||||
receiveMessage(message: MessageInput, truncateFromTop = true): ChannelMessages {
|
applyIncomingMessage(message: MessageInput, truncateFromTop = true): ChannelMessages {
|
||||||
const wire = toWireMessage(message);
|
const wire = toWireMessage(message);
|
||||||
const possibleNonce = wire.nonce ?? null;
|
const possibleNonce = wire.nonce ?? null;
|
||||||
const previous = possibleNonce ? this.get(possibleNonce, true) : null;
|
const previous = possibleNonce ? this.get(possibleNonce, true) : null;
|
||||||
@@ -804,15 +807,15 @@ export class ChannelMessages {
|
|||||||
private appendIncomingMessage(message: MessageInput, truncateFromTop: boolean): ChannelMessages {
|
private appendIncomingMessage(message: MessageInput, truncateFromTop: boolean): ChannelMessages {
|
||||||
const merged = this.merge([hydrateMessage(this, message, 'preserve')]);
|
const merged = this.merge([hydrateMessage(this, message, 'preserve')]);
|
||||||
if (truncateFromTop) {
|
if (truncateFromTop) {
|
||||||
return merged.truncateTop(IS_MOBILE_CLIENT ? MAX_MESSAGES_PER_CHANNEL : TRUNCATED_MESSAGE_VIEW_SIZE, false);
|
return merged.trimOldest(IS_MOBILE_CLIENT ? MAX_MESSAGES_PER_CHANNEL : TRUNCATED_MESSAGE_VIEW_SIZE, false);
|
||||||
}
|
}
|
||||||
if (this.length > MAX_LOADED_MESSAGES) {
|
if (this.length > MAX_LOADED_MESSAGES) {
|
||||||
return merged.truncateBottom(IS_MOBILE_CLIENT ? MAX_MESSAGES_PER_CHANNEL : TRUNCATED_MESSAGE_VIEW_SIZE, false);
|
return merged.trimNewest(IS_MOBILE_CLIENT ? MAX_MESSAGES_PER_CHANNEL : TRUNCATED_MESSAGE_VIEW_SIZE, false);
|
||||||
}
|
}
|
||||||
return merged;
|
return merged;
|
||||||
}
|
}
|
||||||
|
|
||||||
receivePushNotification(message: MessageInput): ChannelMessages {
|
applyPushPreview(message: MessageInput): ChannelMessages {
|
||||||
const wire = toWireMessage(message);
|
const wire = toWireMessage(message);
|
||||||
const possibleNonce = wire.nonce ?? null;
|
const possibleNonce = wire.nonce ?? null;
|
||||||
const existing = possibleNonce ? this.get(possibleNonce, true) : null;
|
const existing = possibleNonce ? this.get(possibleNonce, true) : null;
|
||||||
@@ -820,23 +823,23 @@ export class ChannelMessages {
|
|||||||
return this.cloneAnd({ready: true, cached: true}).merge([hydrateMessage(this, wire, 'preserve')]);
|
return this.cloneAnd({ready: true, cached: true}).merge([hydrateMessage(this, wire, 'preserve')]);
|
||||||
}
|
}
|
||||||
|
|
||||||
loadStart(jump?: JumpOptions): ChannelMessages {
|
beginLoad(jump?: JumpOptions): ChannelMessages {
|
||||||
return this.cloneAnd({
|
return this.cloneAnd({
|
||||||
loadingMore: true,
|
loadingMore: true,
|
||||||
jumped: jump != null,
|
hasJumped: jump != null,
|
||||||
jumpedToPresent: jump?.present ?? false,
|
landedAtLiveEdge: jump?.present ?? false,
|
||||||
jumpTargetId: jump?.messageId ?? null,
|
jumpDestinationId: jump?.messageId ?? null,
|
||||||
jumpTargetOffset: jump?.offset ?? 0,
|
jumpDestinationOffset: jump?.offset ?? 0,
|
||||||
jumpReturnTargetId: jump?.returnMessageId ?? null,
|
jumpReturnMessageId: jump?.returnToMessageId ?? null,
|
||||||
jumpReturnChannelId: jump?.returnMessageId ? (jump.returnChannelId ?? this.channelId) : null,
|
jumpReturnChannelId: jump?.returnToMessageId ? (jump.returnChannelId ?? this.channelId) : null,
|
||||||
jumpReturnGuildId: jump?.returnMessageId ? (jump.returnGuildId ?? null) : null,
|
jumpReturnGuildId: jump?.returnToMessageId ? (jump.returnGuildId ?? null) : null,
|
||||||
ready: jump ? false : this.ready,
|
ready: jump ? false : this.ready,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
loadComplete(options: LoadCompleteOptions): ChannelMessages {
|
applyLoadedWindow(options: LoadCompleteOptions): ChannelMessages {
|
||||||
const {
|
const {
|
||||||
newMessages,
|
windowMessages,
|
||||||
isBefore = false,
|
isBefore = false,
|
||||||
isAfter = false,
|
isAfter = false,
|
||||||
jump = null,
|
jump = null,
|
||||||
@@ -844,7 +847,7 @@ export class ChannelMessages {
|
|||||||
hasMoreAfter = false,
|
hasMoreAfter = false,
|
||||||
cached = false,
|
cached = false,
|
||||||
} = options;
|
} = options;
|
||||||
const records = [...newMessages].reverse().map((m) => hydrateMessage(this, m, 'empty'));
|
const records = [...windowMessages].reverse().map((m) => hydrateMessage(this, m, 'empty'));
|
||||||
const loadDecision = resolveChannelMessagesLoadDecision({
|
const loadDecision = resolveChannelMessagesLoadDecision({
|
||||||
isBefore,
|
isBefore,
|
||||||
isAfter,
|
isAfter,
|
||||||
@@ -853,28 +856,33 @@ export class ChannelMessages {
|
|||||||
});
|
});
|
||||||
let next: ChannelMessages;
|
let next: ChannelMessages;
|
||||||
if (loadDecision.mode === 'replace') {
|
if (loadDecision.mode === 'replace') {
|
||||||
|
const isWindowReplacement = !isBefore && !isAfter && jump?.messageId == null && jump?.offset == null;
|
||||||
|
const unsent = isWindowReplacement ? this.collectUnsentMessages(records) : null;
|
||||||
next = this.reset(records);
|
next = this.reset(records);
|
||||||
|
if (unsent && unsent.length > 0) {
|
||||||
|
next = next.merge(unsent);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
next = this.merge(records, loadDecision.prepend, true);
|
next = this.merge(records, loadDecision.prepend, true);
|
||||||
if (loadDecision.trimBottom) {
|
if (loadDecision.trimBottom) {
|
||||||
next = next.truncate(true, false);
|
next = next.trimToWindow(true, false);
|
||||||
} else if (loadDecision.trimTop) {
|
} else if (loadDecision.trimTop) {
|
||||||
next = next.truncate(false, true);
|
next = next.trimToWindow(false, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
next = next.cloneAnd({
|
next = next.cloneAnd({
|
||||||
ready: true,
|
ready: true,
|
||||||
loadingMore: false,
|
loadingMore: false,
|
||||||
jumpType: jump?.jumpType ?? JumpTypes.ANIMATED,
|
jumpType: jump?.jumpType ?? JumpTypes.ANIMATED,
|
||||||
jumpFlash: jump?.flash ?? false,
|
jumpHighlight: jump?.flash ?? false,
|
||||||
jumped: jump != null,
|
hasJumped: jump != null,
|
||||||
jumpedToPresent: jump?.present ?? false,
|
landedAtLiveEdge: jump?.present ?? false,
|
||||||
jumpTargetId: jump?.messageId ?? null,
|
jumpDestinationId: jump?.messageId ?? null,
|
||||||
jumpTargetOffset: jump && jump.messageId != null && jump.offset != null ? jump.offset : 0,
|
jumpDestinationOffset: jump && jump.messageId != null && jump.offset != null ? jump.offset : 0,
|
||||||
jumpSequenceId: jump ? next.jumpSequenceId + 1 : next.jumpSequenceId,
|
jumpTicket: jump ? next.jumpTicket + 1 : next.jumpTicket,
|
||||||
jumpReturnTargetId: jump?.returnMessageId ?? null,
|
jumpReturnMessageId: jump?.returnToMessageId ?? null,
|
||||||
jumpReturnChannelId: jump?.returnMessageId ? (jump.returnChannelId ?? this.channelId) : null,
|
jumpReturnChannelId: jump?.returnToMessageId ? (jump.returnChannelId ?? this.channelId) : null,
|
||||||
jumpReturnGuildId: jump?.returnMessageId ? (jump.returnGuildId ?? null) : null,
|
jumpReturnGuildId: jump?.returnToMessageId ? (jump.returnGuildId ?? null) : null,
|
||||||
hasMoreBefore: loadDecision.preserveHasMoreBefore ? next.hasMoreBefore : hasMoreBefore,
|
hasMoreBefore: loadDecision.preserveHasMoreBefore ? next.hasMoreBefore : hasMoreBefore,
|
||||||
hasMoreAfter: loadDecision.preserveHasMoreAfter ? next.hasMoreAfter : hasMoreAfter,
|
hasMoreAfter: loadDecision.preserveHasMoreAfter ? next.hasMoreAfter : hasMoreAfter,
|
||||||
cached,
|
cached,
|
||||||
@@ -883,6 +891,25 @@ export class ChannelMessages {
|
|||||||
return next;
|
return next;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private collectUnsentMessages(records: Array<Message>): Array<Message> {
|
||||||
|
const unsent = this.messageList.filter((message) => message.hasFailed || message.isSending);
|
||||||
|
if (unsent.length === 0) return unsent;
|
||||||
|
const unseenByKey = new Map<string, number>();
|
||||||
|
for (const record of records) {
|
||||||
|
if (this.has(record.id)) continue;
|
||||||
|
const key = unsentCorrelationKey(record);
|
||||||
|
unseenByKey.set(key, (unseenByKey.get(key) ?? 0) + 1);
|
||||||
|
}
|
||||||
|
if (unseenByKey.size === 0) return unsent;
|
||||||
|
return unsent.filter((message) => {
|
||||||
|
const key = unsentCorrelationKey(message);
|
||||||
|
const available = unseenByKey.get(key) ?? 0;
|
||||||
|
if (available === 0) return true;
|
||||||
|
unseenByKey.set(key, available - 1);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private clearAllMessages(): void {
|
private clearAllMessages(): void {
|
||||||
this.messageList = [];
|
this.messageList = [];
|
||||||
this.messageIndex = {};
|
this.messageIndex = {};
|
||||||
@@ -927,19 +954,19 @@ export class ChannelMessages {
|
|||||||
if (typeof mutator === 'function') {
|
if (typeof mutator === 'function') {
|
||||||
clone.ready = this.ready;
|
clone.ready = this.ready;
|
||||||
clone.jumpType = this.jumpType;
|
clone.jumpType = this.jumpType;
|
||||||
clone.jumpTargetId = this.jumpTargetId;
|
clone.jumpDestinationId = this.jumpDestinationId;
|
||||||
clone.jumpTargetOffset = this.jumpTargetOffset;
|
clone.jumpDestinationOffset = this.jumpDestinationOffset;
|
||||||
clone.jumpSequenceId = this.jumpSequenceId;
|
clone.jumpTicket = this.jumpTicket;
|
||||||
clone.jumped = this.jumped;
|
clone.hasJumped = this.hasJumped;
|
||||||
clone.jumpedToPresent = this.jumpedToPresent;
|
clone.landedAtLiveEdge = this.landedAtLiveEdge;
|
||||||
clone.jumpFlash = this.jumpFlash;
|
clone.jumpHighlight = this.jumpHighlight;
|
||||||
clone.jumpReturnTargetId = this.jumpReturnTargetId;
|
clone.jumpReturnMessageId = this.jumpReturnMessageId;
|
||||||
clone.jumpReturnChannelId = this.jumpReturnChannelId;
|
clone.jumpReturnChannelId = this.jumpReturnChannelId;
|
||||||
clone.jumpReturnGuildId = this.jumpReturnGuildId;
|
clone.jumpReturnGuildId = this.jumpReturnGuildId;
|
||||||
clone.hasMoreBefore = this.hasMoreBefore;
|
clone.hasMoreBefore = this.hasMoreBefore;
|
||||||
clone.hasMoreAfter = this.hasMoreAfter;
|
clone.hasMoreAfter = this.hasMoreAfter;
|
||||||
clone.loadingMore = this.loadingMore;
|
clone.loadingMore = this.loadingMore;
|
||||||
clone.revealedMessageId = this.revealedMessageId;
|
clone.unblurredMessageId = this.unblurredMessageId;
|
||||||
clone.cached = this.cached;
|
clone.cached = this.cached;
|
||||||
clone.error = this.error;
|
clone.error = this.error;
|
||||||
mutator(clone);
|
mutator(clone);
|
||||||
@@ -947,14 +974,16 @@ export class ChannelMessages {
|
|||||||
const patch = mutator as Partial<ChannelMessages>;
|
const patch = mutator as Partial<ChannelMessages>;
|
||||||
clone.ready = 'ready' in patch ? !!patch.ready : this.ready;
|
clone.ready = 'ready' in patch ? !!patch.ready : this.ready;
|
||||||
clone.jumpType = patch.jumpType ?? this.jumpType;
|
clone.jumpType = patch.jumpType ?? this.jumpType;
|
||||||
clone.jumpTargetId = 'jumpTargetId' in patch ? (patch.jumpTargetId ?? null) : this.jumpTargetId;
|
clone.jumpDestinationId =
|
||||||
clone.jumpTargetOffset = patch.jumpTargetOffset !== undefined ? patch.jumpTargetOffset : this.jumpTargetOffset;
|
'jumpDestinationId' in patch ? (patch.jumpDestinationId ?? null) : this.jumpDestinationId;
|
||||||
clone.jumpSequenceId = patch.jumpSequenceId !== undefined ? patch.jumpSequenceId : this.jumpSequenceId;
|
clone.jumpDestinationOffset =
|
||||||
clone.jumped = 'jumped' in patch ? !!patch.jumped : this.jumped;
|
patch.jumpDestinationOffset !== undefined ? patch.jumpDestinationOffset : this.jumpDestinationOffset;
|
||||||
clone.jumpedToPresent = 'jumpedToPresent' in patch ? !!patch.jumpedToPresent : this.jumpedToPresent;
|
clone.jumpTicket = patch.jumpTicket !== undefined ? patch.jumpTicket : this.jumpTicket;
|
||||||
clone.jumpFlash = 'jumpFlash' in patch ? !!patch.jumpFlash : this.jumpFlash;
|
clone.hasJumped = 'hasJumped' in patch ? !!patch.hasJumped : this.hasJumped;
|
||||||
clone.jumpReturnTargetId =
|
clone.landedAtLiveEdge = 'landedAtLiveEdge' in patch ? !!patch.landedAtLiveEdge : this.landedAtLiveEdge;
|
||||||
'jumpReturnTargetId' in patch ? (patch.jumpReturnTargetId ?? null) : this.jumpReturnTargetId;
|
clone.jumpHighlight = 'jumpHighlight' in patch ? !!patch.jumpHighlight : this.jumpHighlight;
|
||||||
|
clone.jumpReturnMessageId =
|
||||||
|
'jumpReturnMessageId' in patch ? (patch.jumpReturnMessageId ?? null) : this.jumpReturnMessageId;
|
||||||
clone.jumpReturnChannelId =
|
clone.jumpReturnChannelId =
|
||||||
'jumpReturnChannelId' in patch ? (patch.jumpReturnChannelId ?? null) : this.jumpReturnChannelId;
|
'jumpReturnChannelId' in patch ? (patch.jumpReturnChannelId ?? null) : this.jumpReturnChannelId;
|
||||||
clone.jumpReturnGuildId =
|
clone.jumpReturnGuildId =
|
||||||
@@ -962,8 +991,8 @@ export class ChannelMessages {
|
|||||||
clone.hasMoreBefore = 'hasMoreBefore' in patch ? !!patch.hasMoreBefore : this.hasMoreBefore;
|
clone.hasMoreBefore = 'hasMoreBefore' in patch ? !!patch.hasMoreBefore : this.hasMoreBefore;
|
||||||
clone.hasMoreAfter = 'hasMoreAfter' in patch ? !!patch.hasMoreAfter : this.hasMoreAfter;
|
clone.hasMoreAfter = 'hasMoreAfter' in patch ? !!patch.hasMoreAfter : this.hasMoreAfter;
|
||||||
clone.loadingMore = patch.loadingMore !== undefined ? patch.loadingMore : this.loadingMore;
|
clone.loadingMore = patch.loadingMore !== undefined ? patch.loadingMore : this.loadingMore;
|
||||||
clone.revealedMessageId =
|
clone.unblurredMessageId =
|
||||||
'revealedMessageId' in patch ? (patch.revealedMessageId ?? null) : this.revealedMessageId;
|
'unblurredMessageId' in patch ? (patch.unblurredMessageId ?? null) : this.unblurredMessageId;
|
||||||
clone.cached = patch.cached ?? this.cached;
|
clone.cached = patch.cached ?? this.cached;
|
||||||
clone.error = patch.error ?? this.error;
|
clone.error = patch.error ?? this.error;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import {ChannelMessages} from '@app/features/messaging/state/ChannelMessages';
|
|||||||
import type {ReactionEmoji} from '@app/features/messaging/utils/ReactionUtils';
|
import type {ReactionEmoji} from '@app/features/messaging/utils/ReactionUtils';
|
||||||
import SelectedChannel from '@app/features/navigation/state/SelectedChannel';
|
import SelectedChannel from '@app/features/navigation/state/SelectedChannel';
|
||||||
import SelectedGuild from '@app/features/navigation/state/SelectedGuild';
|
import SelectedGuild from '@app/features/navigation/state/SelectedGuild';
|
||||||
import type {UnreadJumpAnchor} from '@app/features/read_state/state/ReadStates';
|
|
||||||
import Relationships from '@app/features/relationship/state/Relationships';
|
import Relationships from '@app/features/relationship/state/Relationships';
|
||||||
import Dimension from '@app/features/ui/state/Dimension';
|
import Dimension from '@app/features/ui/state/Dimension';
|
||||||
import Users from '@app/features/user/state/Users';
|
import Users from '@app/features/user/state/Users';
|
||||||
@@ -39,7 +38,7 @@ export interface PendingJumpDispatch {
|
|||||||
messageId: string;
|
messageId: string;
|
||||||
flash?: boolean;
|
flash?: boolean;
|
||||||
offset?: number;
|
offset?: number;
|
||||||
returnTargetId?: string;
|
returnToMessageId?: string;
|
||||||
returnChannelId?: string | null;
|
returnChannelId?: string | null;
|
||||||
returnGuildId?: string | null;
|
returnGuildId?: string | null;
|
||||||
jumpType?: JumpType;
|
jumpType?: JumpType;
|
||||||
@@ -201,6 +200,10 @@ class Messages {
|
|||||||
return ChannelMessages.get(channelId);
|
return ChannelMessages.get(channelId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private hasLoadedPage(messages: ChannelMessages): boolean {
|
||||||
|
return messages.ready && messages.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
shouldPreloadLatestPage(channelId: string): boolean {
|
shouldPreloadLatestPage(channelId: string): boolean {
|
||||||
if (!GatewayConnection.isConnected || !Channels.getChannel(channelId)) {
|
if (!GatewayConnection.isConnected || !Channels.getChannel(channelId)) {
|
||||||
return false;
|
return false;
|
||||||
@@ -210,13 +213,13 @@ class Messages {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
preloadLatestPage(channelId: string, guildId?: string | null, unreadAnchor?: UnreadJumpAnchor | null): boolean {
|
preloadLatestPage(channelId: string, guildId?: string | null): boolean {
|
||||||
if (!this.shouldPreloadLatestPage(channelId)) {
|
if (!this.shouldPreloadLatestPage(channelId)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const channel = Channels.getChannel(channelId);
|
const channel = Channels.getChannel(channelId);
|
||||||
const resolvedGuildId = guildId ?? channel?.guildId ?? (channel?.isPrivate() ? ME : undefined);
|
const resolvedGuildId = guildId ?? channel?.guildId ?? (channel?.isPrivate() ? ME : undefined);
|
||||||
this.handleChannelSelect({guildId: resolvedGuildId ?? undefined, channelId, unreadAnchor});
|
this.handleChannelSelect({guildId: resolvedGuildId ?? undefined, channelId});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,19 +228,19 @@ class Messages {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getLastEditableMessage(channelId: string): Message | undefined {
|
getLastEditableMessage(channelId: string): Message | undefined {
|
||||||
return this.getMessages(channelId).findNewest((message) => {
|
return this.getMessages(channelId).searchFromNewest((message) => {
|
||||||
return message.isCurrentUserAuthor() && message.state === MessageStates.SENT && message.isUserMessage();
|
return message.isCurrentUserAuthor() && message.state === MessageStates.SENT && message.isUserMessage();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
jumpedMessageId(channelId: string): string | null | undefined {
|
jumpedMessageId(channelId: string): string | null | undefined {
|
||||||
const channel = ChannelMessages.get(channelId);
|
const channel = ChannelMessages.get(channelId);
|
||||||
return channel?.jumpTargetId;
|
return channel?.jumpDestinationId;
|
||||||
}
|
}
|
||||||
|
|
||||||
hasPresent(channelId: string): boolean {
|
hasNewestMessages(channelId: string): boolean {
|
||||||
const channel = ChannelMessages.get(channelId);
|
const channel = ChannelMessages.get(channelId);
|
||||||
return channel?.hasPresent() ?? false;
|
return channel?.hasNewestMessages() ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
@@ -257,7 +260,7 @@ class Messages {
|
|||||||
let didUpdate = false;
|
let didUpdate = false;
|
||||||
ChannelMessages.forEach((messages) => {
|
ChannelMessages.forEach((messages) => {
|
||||||
if (messages.loadingMore) {
|
if (messages.loadingMore) {
|
||||||
this.commitMessages(messages.mutate({loadingMore: false}));
|
this.commitMessages(messages.withPatch({loadingMore: false}));
|
||||||
didUpdate = true;
|
didUpdate = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -286,14 +289,14 @@ class Messages {
|
|||||||
@action
|
@action
|
||||||
handleResumed(): boolean {
|
handleResumed(): boolean {
|
||||||
ChannelMessages.forEach((messages) => {
|
ChannelMessages.forEach((messages) => {
|
||||||
this.commitMessages(messages.mutate({ready: true}));
|
this.commitMessages(messages.withPatch({ready: true}));
|
||||||
});
|
});
|
||||||
this.notifyChange();
|
this.notifyChange();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
handleConnectionOpen(resolveUnreadAnchor: (channelId: string) => UnreadJumpAnchor | null): boolean {
|
handleConnectionOpen(): boolean {
|
||||||
const selectedChannelId = SelectedChannel.currentChannelId;
|
const selectedChannelId = SelectedChannel.currentChannelId;
|
||||||
let didHydrateSelectedChannel = false;
|
let didHydrateSelectedChannel = false;
|
||||||
if (selectedChannelId) {
|
if (selectedChannelId) {
|
||||||
@@ -307,14 +310,10 @@ class Messages {
|
|||||||
}
|
}
|
||||||
ChannelMessages.forEach((messages) => {
|
ChannelMessages.forEach((messages) => {
|
||||||
if (messages.channelId === selectedChannelId && Channels.getChannel(messages.channelId) != null) {
|
if (messages.channelId === selectedChannelId && Channels.getChannel(messages.channelId) != null) {
|
||||||
this.startChannelHydration(messages.channelId, {
|
this.startChannelHydration(messages.channelId, {forceScrollToBottom: this.pendingFullHydration});
|
||||||
forceScrollToBottom: this.pendingFullHydration,
|
|
||||||
unreadAnchor: resolveUnreadAnchor(messages.channelId),
|
|
||||||
});
|
|
||||||
didHydrateSelectedChannel = true;
|
didHydrateSelectedChannel = true;
|
||||||
} else {
|
} else {
|
||||||
this.clearMessages(messages.channelId);
|
this.commitMessages(messages.withPatch({ready: false, loadingMore: false}));
|
||||||
Dimension.forgetChannelDimensions(messages.channelId);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (this.pendingFullHydration && !didHydrateSelectedChannel && selectedChannelId) {
|
if (this.pendingFullHydration && !didHydrateSelectedChannel && selectedChannelId) {
|
||||||
@@ -325,19 +324,8 @@ class Messages {
|
|||||||
if (!didHydrateSelectedChannel && selectedChannelId && Channels.getChannel(selectedChannelId)) {
|
if (!didHydrateSelectedChannel && selectedChannelId && Channels.getChannel(selectedChannelId)) {
|
||||||
const messages = ChannelMessages.getOrCreate(selectedChannelId);
|
const messages = ChannelMessages.getOrCreate(selectedChannelId);
|
||||||
if (!messages.ready && !messages.loadingMore && messages.length === 0) {
|
if (!messages.ready && !messages.loadingMore && messages.length === 0) {
|
||||||
this.commitMessages(messages.mutate({loadingMore: true}));
|
this.commitMessages(messages.withPatch({loadingMore: true}));
|
||||||
const unreadAnchor = resolveUnreadAnchor(selectedChannelId);
|
MessageCommands.fetchMessages(selectedChannelId, null, null, MAX_MESSAGES_PER_CHANNEL);
|
||||||
if (unreadAnchor != null) {
|
|
||||||
MessageCommands.jumpToMessage({
|
|
||||||
channelId: selectedChannelId,
|
|
||||||
messageId: unreadAnchor.messageId,
|
|
||||||
offset: unreadAnchor.offset,
|
|
||||||
flash: false,
|
|
||||||
jumpType: JumpTypes.INSTANT,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
MessageCommands.fetchMessages(selectedChannelId, null, null, MAX_MESSAGES_PER_CHANNEL);
|
|
||||||
}
|
|
||||||
didHydrateSelectedChannel = true;
|
didHydrateSelectedChannel = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -345,42 +333,19 @@ class Messages {
|
|||||||
return didHydrateSelectedChannel;
|
return didHydrateSelectedChannel;
|
||||||
}
|
}
|
||||||
|
|
||||||
private startChannelHydration(
|
private startChannelHydration(channelId: string, options: {forceScrollToBottom?: boolean} = {}): void {
|
||||||
channelId: string,
|
|
||||||
options: {
|
|
||||||
forceScrollToBottom?: boolean;
|
|
||||||
unreadAnchor?: UnreadJumpAnchor | null;
|
|
||||||
} = {},
|
|
||||||
): void {
|
|
||||||
if (!Channels.getChannel(channelId)) return;
|
if (!Channels.getChannel(channelId)) return;
|
||||||
const {forceScrollToBottom = false, unreadAnchor = null} = options;
|
const {forceScrollToBottom = false} = options;
|
||||||
const messages = ChannelMessages.getOrCreate(channelId);
|
const messages = ChannelMessages.getOrCreate(channelId);
|
||||||
this.commitMessages(messages.mutate({loadingMore: true, ready: false, error: false}));
|
this.commitMessages(messages.withPatch({loadingMore: true, ready: false, error: false}));
|
||||||
if (forceScrollToBottom) {
|
if (forceScrollToBottom) {
|
||||||
Dimension.recordChannelDimensions(channelId, 1, 1, 0);
|
Dimension.recordChannelDimensions(channelId, 1, 1, 0);
|
||||||
MessageCommands.fetchMessages(channelId, null, null, MAX_MESSAGES_PER_CHANNEL);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (unreadAnchor != null) {
|
|
||||||
MessageCommands.jumpToMessage({
|
|
||||||
channelId,
|
|
||||||
messageId: unreadAnchor.messageId,
|
|
||||||
offset: unreadAnchor.offset,
|
|
||||||
flash: false,
|
|
||||||
jumpType: JumpTypes.INSTANT,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
MessageCommands.fetchMessages(channelId, null, null, MAX_MESSAGES_PER_CHANNEL);
|
MessageCommands.fetchMessages(channelId, null, null, MAX_MESSAGES_PER_CHANNEL);
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
handleChannelSelect(action: {
|
handleChannelSelect(action: {guildId?: string; channelId?: string | null; messageId?: string}): boolean {
|
||||||
guildId?: string;
|
|
||||||
channelId?: string | null;
|
|
||||||
messageId?: string;
|
|
||||||
unreadAnchor?: UnreadJumpAnchor | null;
|
|
||||||
}): boolean {
|
|
||||||
const channelId = action.channelId ?? action.guildId;
|
const channelId = action.channelId ?? action.guildId;
|
||||||
if (channelId == null || channelId === ME) {
|
if (channelId == null || channelId === ME) {
|
||||||
return false;
|
return false;
|
||||||
@@ -401,9 +366,9 @@ class Messages {
|
|||||||
MessageCommands.jumpToMessage({channelId, ...dispatch});
|
MessageCommands.jumpToMessage({channelId, ...dispatch});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!GatewayConnection.isConnected || messages.loadingMore || messages.ready) {
|
if (!GatewayConnection.isConnected || messages.loadingMore || this.hasLoadedPage(messages)) {
|
||||||
if (messages.ready && Dimension.channelPinnedToEnd(channelId)) {
|
if (this.hasLoadedPage(messages) && Dimension.channelPinnedToEnd(channelId)) {
|
||||||
this.commitMessages(messages.truncateTop(MAX_MESSAGES_PER_CHANNEL));
|
this.commitMessages(messages.trimOldest(MAX_MESSAGES_PER_CHANNEL));
|
||||||
this.notifyChange();
|
this.notifyChange();
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -421,19 +386,8 @@ class Messages {
|
|||||||
if (!isNonGuildChannel && !guildExists) {
|
if (!isNonGuildChannel && !guildExists) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
this.commitMessages(messages.mutate({loadingMore: true}));
|
this.commitMessages(messages.withPatch({loadingMore: true}));
|
||||||
this.notifyChange();
|
this.notifyChange();
|
||||||
const unreadAnchor = action.unreadAnchor ?? null;
|
|
||||||
if (unreadAnchor != null) {
|
|
||||||
MessageCommands.jumpToMessage({
|
|
||||||
channelId,
|
|
||||||
messageId: unreadAnchor.messageId,
|
|
||||||
offset: unreadAnchor.offset,
|
|
||||||
flash: false,
|
|
||||||
jumpType: JumpTypes.INSTANT,
|
|
||||||
});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
MessageCommands.fetchMessages(channelId, null, null, MAX_MESSAGES_PER_CHANNEL);
|
MessageCommands.fetchMessages(channelId, null, null, MAX_MESSAGES_PER_CHANNEL);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -486,7 +440,7 @@ class Messages {
|
|||||||
messageId: undefined,
|
messageId: undefined,
|
||||||
});
|
});
|
||||||
if (!didChannelSelect && currentMessages && currentMessages.length === 0 && !currentMessages.ready) {
|
if (!didChannelSelect && currentMessages && currentMessages.length === 0 && !currentMessages.ready) {
|
||||||
this.commitMessages(currentMessages.mutate({loadingMore: true}));
|
this.commitMessages(currentMessages.withPatch({loadingMore: true}));
|
||||||
MessageCommands.fetchMessages(selectedChannelId, null, null, MAX_MESSAGES_PER_CHANNEL);
|
MessageCommands.fetchMessages(selectedChannelId, null, null, MAX_MESSAGES_PER_CHANNEL);
|
||||||
this.notifyChange();
|
this.notifyChange();
|
||||||
return true;
|
return true;
|
||||||
@@ -497,16 +451,16 @@ class Messages {
|
|||||||
@action
|
@action
|
||||||
handleLoadMessages(action: {channelId: string; jump?: JumpOptions}): boolean {
|
handleLoadMessages(action: {channelId: string; jump?: JumpOptions}): boolean {
|
||||||
const messages = ChannelMessages.getOrCreate(action.channelId);
|
const messages = ChannelMessages.getOrCreate(action.channelId);
|
||||||
this.commitMessages(messages.loadStart(action.jump));
|
this.commitMessages(messages.beginLoad(action.jump));
|
||||||
this.notifyChange();
|
this.notifyChange();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
handleTruncateMessages(action: {channelId: string; truncateBottom?: boolean; truncateTop?: boolean}): boolean {
|
handleTruncateMessages(action: {channelId: string; trimNewest?: boolean; trimOldest?: boolean}): boolean {
|
||||||
const messages = ChannelMessages.getOrCreate(action.channelId).truncate(
|
const messages = ChannelMessages.getOrCreate(action.channelId).trimToWindow(
|
||||||
action.truncateBottom ?? false,
|
action.trimNewest ?? false,
|
||||||
action.truncateTop ?? false,
|
action.trimOldest ?? false,
|
||||||
);
|
);
|
||||||
this.commitMessages(messages);
|
this.commitMessages(messages);
|
||||||
this.notifyChange();
|
this.notifyChange();
|
||||||
@@ -523,19 +477,19 @@ class Messages {
|
|||||||
}): boolean {
|
}): boolean {
|
||||||
let messages = ChannelMessages.getOrCreate(action.channelId);
|
let messages = ChannelMessages.getOrCreate(action.channelId);
|
||||||
if (action.jump?.present) {
|
if (action.jump?.present) {
|
||||||
messages = messages.jumpToPresent(action.limit);
|
messages = messages.jumpToLiveEdge(action.limit);
|
||||||
} else if (action.jump?.messageId) {
|
} else if (action.jump?.messageId) {
|
||||||
messages = messages.jumpToMessage({
|
messages = messages.jumpToMessage({
|
||||||
messageId: action.jump.messageId,
|
messageId: action.jump.messageId,
|
||||||
flash: action.jump.flash,
|
flash: action.jump.flash,
|
||||||
offset: action.jump.offset,
|
offset: action.jump.offset,
|
||||||
returnTargetId: action.jump.returnMessageId,
|
returnToMessageId: action.jump.returnToMessageId,
|
||||||
returnChannelId: action.jump.returnChannelId,
|
returnChannelId: action.jump.returnChannelId,
|
||||||
returnGuildId: action.jump.returnGuildId,
|
returnGuildId: action.jump.returnGuildId,
|
||||||
jumpType: action.jump.jumpType,
|
jumpType: action.jump.jumpType,
|
||||||
});
|
});
|
||||||
} else if (action.before || action.after) {
|
} else if (action.before || action.after) {
|
||||||
messages = messages.loadFromCache(action.before != null, action.limit);
|
messages = messages.drainBufferedSide(action.before != null, action.limit);
|
||||||
}
|
}
|
||||||
this.commitMessages(messages);
|
this.commitMessages(messages);
|
||||||
this.notifyChange();
|
this.notifyChange();
|
||||||
@@ -553,8 +507,8 @@ class Messages {
|
|||||||
cached?: boolean;
|
cached?: boolean;
|
||||||
messages: Array<WireMessage>;
|
messages: Array<WireMessage>;
|
||||||
}): boolean {
|
}): boolean {
|
||||||
const messages = ChannelMessages.getOrCreate(action.channelId).loadComplete({
|
const messages = ChannelMessages.getOrCreate(action.channelId).applyLoadedWindow({
|
||||||
newMessages: action.messages,
|
windowMessages: action.messages,
|
||||||
isBefore: action.isBefore,
|
isBefore: action.isBefore,
|
||||||
isAfter: action.isAfter,
|
isAfter: action.isAfter,
|
||||||
jump: action.jump,
|
jump: action.jump,
|
||||||
@@ -570,7 +524,7 @@ class Messages {
|
|||||||
@action
|
@action
|
||||||
handleLoadMessagesFailure(action: {channelId: string}): boolean {
|
handleLoadMessagesFailure(action: {channelId: string}): boolean {
|
||||||
const messages = ChannelMessages.getOrCreate(action.channelId);
|
const messages = ChannelMessages.getOrCreate(action.channelId);
|
||||||
this.commitMessages(messages.mutate({loadingMore: false, error: true}));
|
this.commitMessages(messages.withPatch({loadingMore: false, error: true}));
|
||||||
this.notifyChange();
|
this.notifyChange();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -581,7 +535,7 @@ class Messages {
|
|||||||
if (!messages.loadingMore && !messages.error) {
|
if (!messages.loadingMore && !messages.error) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
this.commitMessages(messages.mutate({loadingMore: false, error: false}));
|
this.commitMessages(messages.withPatch({loadingMore: false, error: false}));
|
||||||
this.notifyChange();
|
this.notifyChange();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -593,7 +547,7 @@ class Messages {
|
|||||||
if (!existing?.ready) {
|
if (!existing?.ready) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const updated = existing.receiveMessage(action.message, Dimension.channelPinnedToEnd(action.channelId));
|
const updated = existing.applyIncomingMessage(action.message, Dimension.channelPinnedToEnd(action.channelId));
|
||||||
this.commitMessages(updated);
|
this.commitMessages(updated);
|
||||||
this.notifyChange();
|
this.notifyChange();
|
||||||
return false;
|
return false;
|
||||||
@@ -626,9 +580,9 @@ class Messages {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
let messages = existing;
|
let messages = existing;
|
||||||
if (messages.revealedMessageId === action.id) {
|
if (messages.unblurredMessageId === action.id) {
|
||||||
const messageAfter = messages.getAfter(action.id);
|
const messageAfter = messages.nextAfter(action.id);
|
||||||
messages = messages.mutate({revealedMessageId: messageAfter?.id ?? null});
|
messages = messages.withPatch({unblurredMessageId: messageAfter?.id ?? null});
|
||||||
}
|
}
|
||||||
messages = messages.remove(action.id);
|
messages = messages.remove(action.id);
|
||||||
this.commitMessages(messages);
|
this.commitMessages(messages);
|
||||||
@@ -640,11 +594,11 @@ class Messages {
|
|||||||
handleMessageDeleteBulk(action: {ids: Array<string>; channelId: string}): boolean {
|
handleMessageDeleteBulk(action: {ids: Array<string>; channelId: string}): boolean {
|
||||||
const existing = ChannelMessages.get(action.channelId);
|
const existing = ChannelMessages.get(action.channelId);
|
||||||
if (!existing) return false;
|
if (!existing) return false;
|
||||||
let messages = existing.removeMany(action.ids);
|
let messages = existing.removeIds(action.ids);
|
||||||
if (messages === existing) return false;
|
if (messages === existing) return false;
|
||||||
if (messages.revealedMessageId != null && action.ids.includes(messages.revealedMessageId)) {
|
if (messages.unblurredMessageId != null && action.ids.includes(messages.unblurredMessageId)) {
|
||||||
const after = messages.getAfter(messages.revealedMessageId);
|
const after = messages.nextAfter(messages.unblurredMessageId);
|
||||||
messages = messages.mutate({revealedMessageId: after?.id ?? null});
|
messages = messages.withPatch({unblurredMessageId: after?.id ?? null});
|
||||||
}
|
}
|
||||||
this.commitMessages(messages);
|
this.commitMessages(messages);
|
||||||
this.notifyChange();
|
this.notifyChange();
|
||||||
@@ -752,7 +706,7 @@ class Messages {
|
|||||||
@action
|
@action
|
||||||
handleMessageReveal(action: {channelId: string; messageId: string | null}): boolean {
|
handleMessageReveal(action: {channelId: string; messageId: string | null}): boolean {
|
||||||
const messages = ChannelMessages.getOrCreate(action.channelId);
|
const messages = ChannelMessages.getOrCreate(action.channelId);
|
||||||
this.commitMessages(messages.mutate({revealedMessageId: action.messageId}));
|
this.commitMessages(messages.withPatch({unblurredMessageId: action.messageId}));
|
||||||
this.notifyChange();
|
this.notifyChange();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -762,9 +716,9 @@ class Messages {
|
|||||||
const messages = ChannelMessages.get(action.channelId);
|
const messages = ChannelMessages.get(action.channelId);
|
||||||
if (
|
if (
|
||||||
messages &&
|
messages &&
|
||||||
(messages.jumpTargetId != null ||
|
(messages.jumpDestinationId != null ||
|
||||||
messages.jumped ||
|
messages.hasJumped ||
|
||||||
(action.clearReturnTarget === true && messages.jumpReturnTargetId != null))
|
(action.clearReturnTarget === true && messages.jumpReturnMessageId != null))
|
||||||
) {
|
) {
|
||||||
this.commitMessages(messages.clearJumpTarget({clearReturnTarget: action.clearReturnTarget}));
|
this.commitMessages(messages.clearJumpTarget({clearReturnTarget: action.clearReturnTarget}));
|
||||||
this.notifyChange();
|
this.notifyChange();
|
||||||
@@ -829,7 +783,7 @@ class Messages {
|
|||||||
Channels.handleMessageCreate({message: messageData});
|
Channels.handleMessageCreate({message: messageData});
|
||||||
const channelMessages = ChannelMessages.getOrCreate(channelId);
|
const channelMessages = ChannelMessages.getOrCreate(channelId);
|
||||||
if (!channelMessages.has(messageData.id)) {
|
if (!channelMessages.has(messageData.id)) {
|
||||||
this.commitMessages(channelMessages.receiveMessage(messageData, false));
|
this.commitMessages(channelMessages.applyIncomingMessage(messageData, false));
|
||||||
hasChanges = true;
|
hasChanges = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -234,11 +234,11 @@ export function createChannelStream(props: {
|
|||||||
if (groupId === message.id) {
|
if (groupId === message.id) {
|
||||||
lastMessageInGroup = message;
|
lastMessageInGroup = message;
|
||||||
}
|
}
|
||||||
const {jumpSequenceId, jumpFlash, jumpTargetId} = messages;
|
const {jumpTicket, jumpHighlight, jumpDestinationId} = messages;
|
||||||
if (jumpFlash && message.id === jumpTargetId && jumpSequenceId != null) {
|
if (jumpHighlight && message.id === jumpDestinationId && jumpTicket != null) {
|
||||||
messageItem.flashKey = jumpSequenceId;
|
messageItem.flashKey = jumpTicket;
|
||||||
}
|
}
|
||||||
if (messages.jumpTargetId === message.id) {
|
if (messages.jumpDestinationId === message.id) {
|
||||||
messageItem.jumpTarget = true;
|
messageItem.jumpTarget = true;
|
||||||
}
|
}
|
||||||
if (collapsedGroupItem !== null) {
|
if (collapsedGroupItem !== null) {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {type JumpType, JumpTypes} from '@fluxer/constants/src/JumpConstants';
|
|||||||
interface MessageJumpOptions {
|
interface MessageJumpOptions {
|
||||||
flash?: boolean;
|
flash?: boolean;
|
||||||
offset?: number;
|
offset?: number;
|
||||||
returnTargetId?: string;
|
returnToMessageId?: string;
|
||||||
returnChannelId?: string | null;
|
returnChannelId?: string | null;
|
||||||
returnGuildId?: string | null;
|
returnGuildId?: string | null;
|
||||||
jumpType?: JumpType;
|
jumpType?: JumpType;
|
||||||
@@ -34,20 +34,20 @@ function resolveReturnGuildId(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function resolveReturnChannelId(
|
function resolveReturnChannelId(
|
||||||
returnTargetId: string | undefined,
|
returnToMessageId: string | undefined,
|
||||||
explicitChannelId: string | null | undefined,
|
explicitChannelId: string | null | undefined,
|
||||||
): string | null | undefined {
|
): string | null | undefined {
|
||||||
if (!returnTargetId) return undefined;
|
if (!returnToMessageId) return undefined;
|
||||||
if (explicitChannelId != null) return explicitChannelId;
|
if (explicitChannelId != null) return explicitChannelId;
|
||||||
return Navigation.messageId === returnTargetId ? Navigation.channelId : undefined;
|
return Navigation.messageId === returnToMessageId ? Navigation.channelId : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveSameChannelReturnChannelId(
|
function resolveSameChannelReturnChannelId(
|
||||||
targetChannelId: string,
|
targetChannelId: string,
|
||||||
returnTargetId: string | undefined,
|
returnToMessageId: string | undefined,
|
||||||
explicitReturnChannelId: string | null | undefined,
|
explicitReturnChannelId: string | null | undefined,
|
||||||
): string | undefined {
|
): string | undefined {
|
||||||
const returnChannelId = resolveReturnChannelId(returnTargetId, explicitReturnChannelId);
|
const returnChannelId = resolveReturnChannelId(returnToMessageId, explicitReturnChannelId);
|
||||||
return returnChannelId === targetChannelId ? returnChannelId : undefined;
|
return returnChannelId === targetChannelId ? returnChannelId : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,16 +56,16 @@ export function goToMessage(channelId: string, messageId: string, options?: Mess
|
|||||||
const guildId = resolveGuildId(channelId, options?.viewContext);
|
const guildId = resolveGuildId(channelId, options?.viewContext);
|
||||||
const returnChannelId = resolveSameChannelReturnChannelId(
|
const returnChannelId = resolveSameChannelReturnChannelId(
|
||||||
channelId,
|
channelId,
|
||||||
options?.returnTargetId,
|
options?.returnToMessageId,
|
||||||
options?.returnChannelId,
|
options?.returnChannelId,
|
||||||
);
|
);
|
||||||
const returnTargetId = returnChannelId ? options?.returnTargetId : undefined;
|
const returnToMessageId = returnChannelId ? options?.returnToMessageId : undefined;
|
||||||
const returnGuildId = returnTargetId ? resolveReturnGuildId(returnChannelId, options?.returnGuildId) : undefined;
|
const returnGuildId = returnToMessageId ? resolveReturnGuildId(returnChannelId, options?.returnGuildId) : undefined;
|
||||||
const dispatch = {
|
const dispatch = {
|
||||||
messageId,
|
messageId,
|
||||||
flash: options?.flash ?? true,
|
flash: options?.flash ?? true,
|
||||||
offset: options?.offset,
|
offset: options?.offset,
|
||||||
returnTargetId,
|
returnToMessageId,
|
||||||
returnChannelId,
|
returnChannelId,
|
||||||
returnGuildId,
|
returnGuildId,
|
||||||
jumpType: isSameChannel ? (options?.jumpType ?? JumpTypes.ANIMATED) : JumpTypes.INSTANT,
|
jumpType: isSameChannel ? (options?.jumpType ?? JumpTypes.ANIMATED) : JumpTypes.INSTANT,
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export interface ScrollManagerProps {
|
|||||||
handleScrollFromBottom: () => void;
|
handleScrollFromBottom: () => void;
|
||||||
additionalMessagePadding: number;
|
additionalMessagePadding: number;
|
||||||
canAutoAck: boolean;
|
canAutoAck: boolean;
|
||||||
handleJumpHighlight: (messageId: string | null, jumpSequenceId: number) => void;
|
handleJumpHighlight: (messageId: string | null, jumpTicket: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ScrollManager {
|
export class ScrollManager {
|
||||||
@@ -83,7 +83,7 @@ export class ScrollManager {
|
|||||||
} | null = null;
|
} | null = null;
|
||||||
private loadLastDirection: 'before' | 'after' | null = null;
|
private loadLastDirection: 'before' | 'after' | null = null;
|
||||||
private pinPreUpdateState: ScrollPinResult | null = null;
|
private pinPreUpdateState: ScrollPinResult | null = null;
|
||||||
private jumpSequenceId: number | null = null;
|
private jumpTicket: number | null = null;
|
||||||
private jumpCallbackToken = 0;
|
private jumpCallbackToken = 0;
|
||||||
private anchorAutomaticListeners: Array<(anchor: AnchorData | null, bottom: AnchorData | null) => void> = [];
|
private anchorAutomaticListeners: Array<(anchor: AnchorData | null, bottom: AnchorData | null) => void> = [];
|
||||||
private dimensionPersistDebounced: DebouncedFunction<() => void>;
|
private dimensionPersistDebounced: DebouncedFunction<() => void>;
|
||||||
@@ -91,7 +91,7 @@ export class ScrollManager {
|
|||||||
constructor(props: ScrollManagerProps) {
|
constructor(props: ScrollManagerProps) {
|
||||||
this.props = props;
|
this.props = props;
|
||||||
this.loadIsActive = props.messages.loadingMore;
|
this.loadIsActive = props.messages.loadingMore;
|
||||||
if (props.messages.jumpTargetId != null) {
|
if (props.messages.jumpDestinationId != null) {
|
||||||
this.pinIsAtBottom = false;
|
this.pinIsAtBottom = false;
|
||||||
} else {
|
} else {
|
||||||
const stored = Dimension.channelDimensionsFor(props.channel.id);
|
const stored = Dimension.channelDimensionsFor(props.channel.id);
|
||||||
@@ -143,10 +143,10 @@ export class ScrollManager {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private jumpBegin(jumpSequenceId: number | null): number {
|
private jumpBegin(jumpTicket: number | null): number {
|
||||||
this.jumpCallbackToken += 1;
|
this.jumpCallbackToken += 1;
|
||||||
this.jumpIsActive = true;
|
this.jumpIsActive = true;
|
||||||
this.jumpSequenceId = jumpSequenceId;
|
this.jumpTicket = jumpTicket;
|
||||||
return this.jumpCallbackToken;
|
return this.jumpCallbackToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,7 +160,7 @@ export class ScrollManager {
|
|||||||
this.ref.current?.cancelScroll();
|
this.ref.current?.cancelScroll();
|
||||||
}
|
}
|
||||||
this.jumpIsActive = false;
|
this.jumpIsActive = false;
|
||||||
this.jumpSequenceId = null;
|
this.jumpTicket = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private pinComputeState(state: ScrollerState): ScrollPinResult {
|
private pinComputeState(state: ScrollerState): ScrollPinResult {
|
||||||
@@ -310,7 +310,7 @@ export class ScrollManager {
|
|||||||
if (idx === -1) {
|
if (idx === -1) {
|
||||||
return channel.id;
|
return channel.id;
|
||||||
}
|
}
|
||||||
return messages.getByIndex(idx)?.id;
|
return messages.atIndex(idx)?.id;
|
||||||
};
|
};
|
||||||
while (true) {
|
while (true) {
|
||||||
const messageId = getMessageId(index);
|
const messageId = getMessageId(index);
|
||||||
@@ -341,8 +341,8 @@ export class ScrollManager {
|
|||||||
const direction = isBefore ? 1 : -1;
|
const direction = isBefore ? 1 : -1;
|
||||||
const startIndex = isBefore ? 0 : messages.length - 1;
|
const startIndex = isBefore ? 0 : messages.length - 1;
|
||||||
let anchor: AnchorData | null = null;
|
let anchor: AnchorData | null = null;
|
||||||
for (let i = startIndex; messages.getByIndex(i) != null; i += direction) {
|
for (let i = startIndex; messages.atIndex(i) != null; i += direction) {
|
||||||
const msg = messages.getByIndex(i)!;
|
const msg = messages.atIndex(i)!;
|
||||||
const data = this.anchorGetData(msg.id, scrollTop, undefined, layout);
|
const data = this.anchorGetData(msg.id, scrollTop, undefined, layout);
|
||||||
if (data) {
|
if (data) {
|
||||||
anchor = data;
|
anchor = data;
|
||||||
@@ -388,7 +388,7 @@ export class ScrollManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.jumpIsActive = false;
|
this.jumpIsActive = false;
|
||||||
this.jumpSequenceId = null;
|
this.jumpTicket = null;
|
||||||
this.jumpCallbackToken += 1;
|
this.jumpCallbackToken += 1;
|
||||||
const state = this.scrollGetState();
|
const state = this.scrollGetState();
|
||||||
const pinState = this.pinComputeState(state);
|
const pinState = this.pinComputeState(state);
|
||||||
@@ -828,7 +828,7 @@ export class ScrollManager {
|
|||||||
private jumpFixTarget(): void {
|
private jumpFixTarget(): void {
|
||||||
const {messages} = this.props;
|
const {messages} = this.props;
|
||||||
const jumpToken = this.jumpCallbackToken;
|
const jumpToken = this.jumpCallbackToken;
|
||||||
const targetId = messages.jumpTargetId ? resolveJumpTargetId(messages) : null;
|
const targetId = messages.jumpDestinationId ? resolveJumpTargetId(messages) : null;
|
||||||
if (targetId) {
|
if (targetId) {
|
||||||
const element = this.layoutGetElementFromMessageId(targetId);
|
const element = this.layoutGetElementFromMessageId(targetId);
|
||||||
if (element) {
|
if (element) {
|
||||||
@@ -852,7 +852,7 @@ export class ScrollManager {
|
|||||||
callback?: () => void,
|
callback?: () => void,
|
||||||
animate = true,
|
animate = true,
|
||||||
suppressPadding = false,
|
suppressPadding = false,
|
||||||
jumpToken = this.jumpBegin(this.jumpSequenceId),
|
jumpToken = this.jumpBegin(this.jumpTicket),
|
||||||
): void {
|
): void {
|
||||||
const doc = this.scrollGetDocument();
|
const doc = this.scrollGetDocument();
|
||||||
const newMessagesBar = doc?.getElementById('new-messages-bar');
|
const newMessagesBar = doc?.getElementById('new-messages-bar');
|
||||||
@@ -862,7 +862,7 @@ export class ScrollManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.jumpIsActive = false;
|
this.jumpIsActive = false;
|
||||||
this.jumpSequenceId = null;
|
this.jumpTicket = null;
|
||||||
this.anchorSetAutomatic(this.anchorFindTopVisible());
|
this.anchorSetAutomatic(this.anchorFindTopVisible());
|
||||||
callback?.();
|
callback?.();
|
||||||
if (this.jumpIsCurrent(jumpToken)) {
|
if (this.jumpIsCurrent(jumpToken)) {
|
||||||
@@ -965,13 +965,13 @@ export class ScrollManager {
|
|||||||
this.jumpStop(true);
|
this.jumpStop(true);
|
||||||
Dimension.recordChannelDimensions(channel.id, 1, 1, 0);
|
Dimension.recordChannelDimensions(channel.id, 1, 1, 0);
|
||||||
if (messages.hasMoreAfter) {
|
if (messages.hasMoreAfter) {
|
||||||
MessageCommands.jumpToPresent(channel.id, MAX_MESSAGES_PER_CHANNEL);
|
MessageCommands.jumpToLiveEdge(channel.id, MAX_MESSAGES_PER_CHANNEL);
|
||||||
} else {
|
} else {
|
||||||
this.pinIsScrollingProgrammatically = true;
|
this.pinIsScrollingProgrammatically = true;
|
||||||
this.pinIsAtBottom = true;
|
this.pinIsAtBottom = true;
|
||||||
this.scrollTo(Number.MAX_SAFE_INTEGER, animate, () => {
|
this.scrollTo(Number.MAX_SAFE_INTEGER, animate, () => {
|
||||||
this.jumpIsActive = false;
|
this.jumpIsActive = false;
|
||||||
this.jumpSequenceId = null;
|
this.jumpTicket = null;
|
||||||
this.pinIsScrollingProgrammatically = false;
|
this.pinIsScrollingProgrammatically = false;
|
||||||
this.scrollHandle();
|
this.scrollHandle();
|
||||||
});
|
});
|
||||||
@@ -1001,8 +1001,8 @@ export class ScrollManager {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const {channel, messages} = this.props;
|
const {channel, messages} = this.props;
|
||||||
const returnTargetId = messages.jumpReturnTargetId;
|
const returnToMessageId = messages.jumpReturnMessageId;
|
||||||
if (!returnTargetId) {
|
if (!returnToMessageId) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const returnChannelId = messages.jumpReturnChannelId ?? channel.id;
|
const returnChannelId = messages.jumpReturnChannelId ?? channel.id;
|
||||||
@@ -1016,11 +1016,11 @@ export class ScrollManager {
|
|||||||
this.pinIsScrollingProgrammatically = false;
|
this.pinIsScrollingProgrammatically = false;
|
||||||
Messages.handleClearJumpTarget({channelId: channel.id, clearReturnTarget: true});
|
Messages.handleClearJumpTarget({channelId: channel.id, clearReturnTarget: true});
|
||||||
const dispatch = {
|
const dispatch = {
|
||||||
messageId: returnTargetId,
|
messageId: returnToMessageId,
|
||||||
flash: false,
|
flash: false,
|
||||||
jumpType: JumpTypes.INSTANT,
|
jumpType: JumpTypes.INSTANT,
|
||||||
};
|
};
|
||||||
if (Navigation.channelId === returnChannelId && Navigation.messageId === returnTargetId) {
|
if (Navigation.channelId === returnChannelId && Navigation.messageId === returnToMessageId) {
|
||||||
MessageCommands.jumpToMessage({
|
MessageCommands.jumpToMessage({
|
||||||
channelId: returnChannelId,
|
channelId: returnChannelId,
|
||||||
...dispatch,
|
...dispatch,
|
||||||
@@ -1028,12 +1028,12 @@ export class ScrollManager {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
Messages.setPendingJumpDispatch(returnChannelId, dispatch);
|
Messages.setPendingJumpDispatch(returnChannelId, dispatch);
|
||||||
NavigationCommands.navigateToMessage(returnGuildId, returnChannelId, returnTargetId, 'replace');
|
NavigationCommands.navigateToMessage(returnGuildId, returnChannelId, returnToMessageId, 'replace');
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
jumpCancel(): boolean {
|
jumpCancel(): boolean {
|
||||||
if (this.lifecycleIsDisposed || (!this.jumpIsActive && this.props.messages.jumpTargetId == null)) {
|
if (this.lifecycleIsDisposed || (!this.jumpIsActive && this.props.messages.jumpDestinationId == null)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
this.jumpStop(true);
|
this.jumpStop(true);
|
||||||
@@ -1135,9 +1135,9 @@ export class ScrollManager {
|
|||||||
scrollToMessage(messageId: string, animate = true): void {
|
scrollToMessage(messageId: string, animate = true): void {
|
||||||
const scrollerHandle = this.ref.current;
|
const scrollerHandle = this.ref.current;
|
||||||
if (!scrollerHandle) return;
|
if (!scrollerHandle) return;
|
||||||
const {jumpFlash, jumpSequenceId, jumpTargetId} = this.props.messages;
|
const {jumpHighlight, jumpTicket, jumpDestinationId} = this.props.messages;
|
||||||
const highlightTargetId = jumpFlash && jumpTargetId !== this.props.channel.id ? jumpTargetId : null;
|
const highlightTargetId = jumpHighlight && jumpDestinationId !== this.props.channel.id ? jumpDestinationId : null;
|
||||||
this.props.handleJumpHighlight(highlightTargetId, jumpSequenceId);
|
this.props.handleJumpHighlight(highlightTargetId, jumpTicket);
|
||||||
if (messageId === this.props.channel.id) {
|
if (messageId === this.props.channel.id) {
|
||||||
this.scrollTo(0);
|
this.scrollTo(0);
|
||||||
return;
|
return;
|
||||||
@@ -1145,7 +1145,7 @@ export class ScrollManager {
|
|||||||
const element = this.layoutGetElementFromMessageId(messageId);
|
const element = this.layoutGetElementFromMessageId(messageId);
|
||||||
const scrollerNode = scrollerHandle.getViewportElement();
|
const scrollerNode = scrollerHandle.getViewportElement();
|
||||||
this.pinIsAtBottom = false;
|
this.pinIsAtBottom = false;
|
||||||
const jumpToken = this.jumpBegin(this.props.messages.jumpSequenceId);
|
const jumpToken = this.jumpBegin(this.props.messages.jumpTicket);
|
||||||
const shouldContinue = () => this.jumpIsCurrent(jumpToken);
|
const shouldContinue = () => this.jumpIsCurrent(jumpToken);
|
||||||
const onComplete = () => this.jumpComplete(element, jumpToken);
|
const onComplete = () => this.jumpComplete(element, jumpToken);
|
||||||
if (!element || !scrollerNode) {
|
if (!element || !scrollerNode) {
|
||||||
@@ -1204,33 +1204,33 @@ export class ScrollManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (nextProps.messages.jumpTargetId == null) {
|
if (nextProps.messages.jumpDestinationId == null) {
|
||||||
this.scrollTo(Number.MAX_SAFE_INTEGER);
|
this.scrollTo(Number.MAX_SAFE_INTEGER);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (nextProps.messages.jumpTargetId != null) {
|
if (nextProps.messages.jumpDestinationId != null) {
|
||||||
if (this.loadIsInProgress()) {
|
if (this.loadIsInProgress()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const targetId = resolveJumpTargetId(nextProps.messages);
|
const targetId = resolveJumpTargetId(nextProps.messages);
|
||||||
if (targetId == null) {
|
if (targetId == null) {
|
||||||
nextProps.handleJumpHighlight(null, nextProps.messages.jumpSequenceId);
|
nextProps.handleJumpHighlight(null, nextProps.messages.jumpTicket);
|
||||||
this.jumpStop();
|
this.jumpStop();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.jumpSequenceId === nextProps.messages.jumpSequenceId) {
|
if (this.jumpTicket === nextProps.messages.jumpTicket) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (nextProps.messages.jumpSequenceId === prevMessages.jumpSequenceId && this.jumpIsActiveNow()) {
|
if (nextProps.messages.jumpTicket === prevMessages.jumpTicket && this.jumpIsActiveNow()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.scrollToMessage(targetId, shouldAnimateMessageJump(nextProps.messages.jumpType));
|
this.scrollToMessage(targetId, shouldAnimateMessageJump(nextProps.messages.jumpType));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (nextProps.messages.jumpedToPresent && prevMessages.jumpSequenceId !== nextProps.messages.jumpSequenceId) {
|
if (nextProps.messages.jumpedToPresent && prevMessages.jumpTicket !== nextProps.messages.jumpTicket) {
|
||||||
nextProps.handleJumpHighlight(null, nextProps.messages.jumpSequenceId);
|
nextProps.handleJumpHighlight(null, nextProps.messages.jumpTicket);
|
||||||
this.jumpBegin(nextProps.messages.jumpSequenceId);
|
this.jumpBegin(nextProps.messages.jumpTicket);
|
||||||
this.scrollTo(0);
|
this.scrollTo(0);
|
||||||
this.scrollSetToBottom();
|
this.scrollSetToBottom();
|
||||||
return;
|
return;
|
||||||
@@ -1290,7 +1290,7 @@ export class ScrollManager {
|
|||||||
this.anchorFetch = null;
|
this.anchorFetch = null;
|
||||||
this.anchorFetchFallback = null;
|
this.anchorFetchFallback = null;
|
||||||
this.pinPreUpdateState = null;
|
this.pinPreUpdateState = null;
|
||||||
this.jumpSequenceId = null;
|
this.jumpTicket = null;
|
||||||
this.jumpCallbackToken += 1;
|
this.jumpCallbackToken += 1;
|
||||||
for (const cb of this.anchorAutomaticListeners) {
|
for (const cb of this.anchorAutomaticListeners) {
|
||||||
this.anchorRemoveAutomaticListener(cb);
|
this.anchorRemoveAutomaticListener(cb);
|
||||||
|
|||||||
@@ -75,19 +75,19 @@ export function shouldAnimateMessageJump(jumpType: JumpType): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function resolveJumpTargetId(messages: ChannelMessages): string | null {
|
export function resolveJumpTargetId(messages: ChannelMessages): string | null {
|
||||||
const {jumpTargetId, jumpTargetOffset} = messages;
|
const {jumpDestinationId, jumpDestinationOffset} = messages;
|
||||||
if (!jumpTargetId || !messages.ready) return null;
|
if (!jumpDestinationId || !messages.ready) return null;
|
||||||
if (messages.has(jumpTargetId) || (!messages.hasMoreBefore && jumpTargetId === messages.channelId)) {
|
if (messages.has(jumpDestinationId) || (!messages.hasMoreBefore && jumpDestinationId === messages.channelId)) {
|
||||||
if (jumpTargetOffset === 0) {
|
if (jumpDestinationOffset === 0) {
|
||||||
return jumpTargetId;
|
return jumpDestinationId;
|
||||||
}
|
}
|
||||||
const index = messages.indexOf(jumpTargetId);
|
const index = messages.indexOf(jumpDestinationId);
|
||||||
const targetMessage = messages.getByIndex(index + jumpTargetOffset);
|
const targetMessage = messages.atIndex(index + jumpDestinationOffset);
|
||||||
return targetMessage?.id ?? jumpTargetId;
|
return targetMessage?.id ?? jumpDestinationId;
|
||||||
}
|
}
|
||||||
const allIds = [jumpTargetId, ...messages.map((m) => m.id)].sort(compare);
|
const allIds = [jumpDestinationId, ...messages.map((m) => m.id)].sort(compare);
|
||||||
const jumpIndex = allIds.indexOf(jumpTargetId);
|
const jumpIndex = allIds.indexOf(jumpDestinationId);
|
||||||
const offset = Math.abs(jumpTargetOffset) > 0 ? jumpTargetOffset : 1;
|
const offset = Math.abs(jumpDestinationOffset) > 0 ? jumpDestinationOffset : 1;
|
||||||
const closestId = allIds[jumpIndex + offset] ?? allIds[jumpIndex - 1];
|
const closestId = allIds[jumpIndex + offset] ?? allIds[jumpIndex - 1];
|
||||||
return closestId ?? null;
|
return closestId ?? null;
|
||||||
}
|
}
|
||||||
@@ -240,7 +240,7 @@ export class ReadStateEntry {
|
|||||||
let loadedOlderMessages = false;
|
let loadedOlderMessages = false;
|
||||||
let oldestUnread: string | null = null;
|
let oldestUnread: string | null = null;
|
||||||
let loadedUnreadCount = 0;
|
let loadedUnreadCount = 0;
|
||||||
messages.forAll((message) => {
|
messages.forEachBuffered((message) => {
|
||||||
if (!foundAckMessage) {
|
if (!foundAckMessage) {
|
||||||
foundAckMessage = message.id === this.storedAckMessageId;
|
foundAckMessage = message.id === this.storedAckMessageId;
|
||||||
} else if (this.storedOldestUnreadMessageId == null) {
|
} else if (this.storedOldestUnreadMessageId == null) {
|
||||||
@@ -273,8 +273,8 @@ export class ReadStateEntry {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
const hasUnreadBoundary = foundAckMessage || loadedOlderMessages || !messages.hasMoreBefore;
|
const hasUnreadBoundary = foundAckMessage || loadedOlderMessages || !messages.hasMoreBefore;
|
||||||
const hasPresent = messages.hasPresent();
|
const hasNewestMessages = messages.hasNewestMessages();
|
||||||
this.estimated = !hasPresent || !hasUnreadBoundary;
|
this.estimated = !hasNewestMessages || !hasUnreadBoundary;
|
||||||
if (this.estimated) {
|
if (this.estimated) {
|
||||||
this.unreadCount = Math.max(previousUnreadCount, loadedUnreadCount);
|
this.unreadCount = Math.max(previousUnreadCount, loadedUnreadCount);
|
||||||
} else {
|
} else {
|
||||||
@@ -323,7 +323,7 @@ export class ReadStateEntry {
|
|||||||
const messages = Messages.getMessages(this.channelId);
|
const messages = Messages.getMessages(this.channelId);
|
||||||
const isPrivate = this.isPrivate;
|
const isPrivate = this.isPrivate;
|
||||||
let mentionCount = 0;
|
let mentionCount = 0;
|
||||||
messages.forAll((message) => {
|
messages.forEachBuffered((message) => {
|
||||||
if (snowflakeTimestamp(message.id) <= ackTimestamp) {
|
if (snowflakeTimestamp(message.id) <= ackTimestamp) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user