chore: bug fix cleanup (#4)
This commit is contained in:
@@ -147,7 +147,7 @@ class MessageQueue extends Queue<MessageQueuePayload, HttpResponse<Message> | un
|
||||
|
||||
drain(
|
||||
message: MessageQueuePayload,
|
||||
completed: (err: RetryError | null, result?: HttpResponse<Message>) => void,
|
||||
completed: (err: RetryError | null, result?: HttpResponse<Message>, error?: unknown) => void,
|
||||
): void {
|
||||
if (isSendPayload(message)) {
|
||||
this.handleSend(message, completed);
|
||||
@@ -155,7 +155,7 @@ class MessageQueue extends Queue<MessageQueuePayload, HttpResponse<Message> | un
|
||||
this.handleEdit(message, completed);
|
||||
} else {
|
||||
logger.error('Unknown message type, completing with null');
|
||||
completed(null);
|
||||
completed(null, undefined, new Error('Unknown message queue payload'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,7 +188,7 @@ class MessageQueue extends Queue<MessageQueuePayload, HttpResponse<Message> | un
|
||||
|
||||
private async handleSend(
|
||||
payload: SendMessagePayload,
|
||||
completed: (err: RetryError | null, result?: HttpResponse<Message>) => void,
|
||||
completed: (err: RetryError | null, result?: HttpResponse<Message>, error?: unknown) => void,
|
||||
): Promise<void> {
|
||||
const {channelId, nonce, hasAttachments} = payload;
|
||||
|
||||
@@ -239,7 +239,7 @@ class MessageQueue extends Queue<MessageQueuePayload, HttpResponse<Message> | un
|
||||
this.handleSendRateLimit(httpError, completed);
|
||||
} else {
|
||||
this.handleSendError(channelId, nonce, httpError, i18n, payload.hasAttachments);
|
||||
completed(null);
|
||||
completed(null, undefined, httpError);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -309,12 +309,12 @@ class MessageQueue extends Queue<MessageQueuePayload, HttpResponse<Message> | un
|
||||
|
||||
private handleSendRateLimit(
|
||||
error: HttpError,
|
||||
completed: (err: RetryError | null, result?: HttpResponse<Message>) => void,
|
||||
completed: (err: RetryError | null, result?: HttpResponse<Message>, error?: unknown) => void,
|
||||
): void {
|
||||
const retryAfterSeconds = getApiErrorBody(error)?.retry_after ?? 0;
|
||||
const retryAfterMs = retryAfterSeconds > 0 ? retryAfterSeconds * 1000 : undefined;
|
||||
|
||||
completed({retryAfter: retryAfterMs});
|
||||
completed({retryAfter: retryAfterMs}, undefined, error);
|
||||
|
||||
this.handleRateLimitError(retryAfterSeconds);
|
||||
}
|
||||
@@ -399,7 +399,7 @@ class MessageQueue extends Queue<MessageQueuePayload, HttpResponse<Message> | un
|
||||
|
||||
private async handleEdit(
|
||||
payload: EditMessagePayload,
|
||||
completed: (err: RetryError | null, result?: HttpResponse<Message>) => void,
|
||||
completed: (err: RetryError | null, result?: HttpResponse<Message>, error?: unknown) => void,
|
||||
): Promise<void> {
|
||||
const {channelId, messageId, content, flags} = payload;
|
||||
|
||||
@@ -428,7 +428,7 @@ class MessageQueue extends Queue<MessageQueuePayload, HttpResponse<Message> | un
|
||||
this.handleEditRateLimit(httpError, completed);
|
||||
} else {
|
||||
this.showEditErrorModal(httpError);
|
||||
completed(null);
|
||||
completed(null, undefined, httpError);
|
||||
}
|
||||
} finally {
|
||||
this.abortControllers.delete(messageId);
|
||||
@@ -451,12 +451,12 @@ class MessageQueue extends Queue<MessageQueuePayload, HttpResponse<Message> | un
|
||||
|
||||
private handleEditRateLimit(
|
||||
error: HttpError,
|
||||
completed: (err: RetryError | null, result?: HttpResponse<Message>) => void,
|
||||
completed: (err: RetryError | null, result?: HttpResponse<Message>, error?: unknown) => void,
|
||||
): void {
|
||||
const retryAfterSeconds = getApiErrorBody(error)?.retry_after ?? 0;
|
||||
const retryAfterMs = retryAfterSeconds > 0 ? retryAfterSeconds * 1000 : undefined;
|
||||
|
||||
completed({retryAfter: retryAfterMs});
|
||||
completed({retryAfter: retryAfterMs}, undefined, error);
|
||||
|
||||
this.handleEditRateLimitError(retryAfterSeconds);
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import {Logger} from '~/lib/Logger';
|
||||
|
||||
export interface QueueEntry<TMessage, TResult = void> {
|
||||
message: TMessage;
|
||||
success: (result?: TResult) => void;
|
||||
success: (result?: TResult, error?: unknown) => void;
|
||||
}
|
||||
|
||||
interface RetryInfo {
|
||||
@@ -49,9 +49,12 @@ export abstract class Queue<TMessage, TResult = void> {
|
||||
this.isDraining = false;
|
||||
}
|
||||
|
||||
protected abstract drain(message: TMessage, complete: (retry: RetryInfo | null, result?: TResult) => void): void;
|
||||
protected abstract drain(
|
||||
message: TMessage,
|
||||
complete: (retry: RetryInfo | null, result?: TResult, error?: unknown) => void,
|
||||
): void;
|
||||
|
||||
enqueue(message: TMessage, success: (result?: TResult) => void): void {
|
||||
enqueue(message: TMessage, success: (result?: TResult, error?: unknown) => void): void {
|
||||
this.queue.push({message, success});
|
||||
this.maybeProcessNext();
|
||||
}
|
||||
@@ -90,7 +93,7 @@ export abstract class Queue<TMessage, TResult = void> {
|
||||
|
||||
let hasCompleted = false;
|
||||
|
||||
const complete = (retry: RetryInfo | null, result?: TResult): void => {
|
||||
const complete = (retry: RetryInfo | null, result?: TResult, error?: unknown): void => {
|
||||
if (hasCompleted) {
|
||||
this.logger.warn('Queue completion callback invoked more than once; ignoring extra call');
|
||||
return;
|
||||
@@ -105,9 +108,9 @@ export abstract class Queue<TMessage, TResult = void> {
|
||||
setTimeout(() => this.maybeProcessNext(), 0);
|
||||
|
||||
try {
|
||||
success(result);
|
||||
} catch (error) {
|
||||
this.logger.error('Error in queue success callback', error);
|
||||
success(result, error);
|
||||
} catch (callbackError) {
|
||||
this.logger.error('Error in queue success callback', callbackError);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -132,7 +135,7 @@ export abstract class Queue<TMessage, TResult = void> {
|
||||
} catch (error) {
|
||||
this.logger.error('Unhandled error while draining queue item', error);
|
||||
if (!hasCompleted) {
|
||||
complete(null);
|
||||
complete(null, undefined, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1023,6 +1023,27 @@ export class ScrollManager {
|
||||
}
|
||||
}
|
||||
|
||||
applyLayoutShift(heightDelta: number): boolean {
|
||||
if (this.isDisposed || !this.isInitialized()) return false;
|
||||
if (heightDelta === 0) return false;
|
||||
|
||||
const scrollerNode = this.ref.current?.getScrollerNode();
|
||||
if (!scrollerNode) return false;
|
||||
|
||||
const state = this.getScrollerState();
|
||||
const distanceFromBottom = Math.max(state.scrollHeight - state.offsetHeight - state.scrollTop, 0);
|
||||
const stickThreshold = Math.max(Math.abs(heightDelta) + BOTTOM_LOCK_TOLERANCE, 32);
|
||||
const shouldStick = this.isPinned() || distanceFromBottom <= stickThreshold;
|
||||
|
||||
if (!shouldStick) return false;
|
||||
|
||||
const maxScrollTop = Math.max(0, scrollerNode.scrollHeight - scrollerNode.offsetHeight);
|
||||
const targetScrollTop = Math.max(0, Math.min(state.scrollTop + heightDelta, maxScrollTop));
|
||||
|
||||
this.mergeTo(targetScrollTop, this.handleScroll);
|
||||
return true;
|
||||
}
|
||||
|
||||
private updateStoreDimensions(callback?: () => void): void {
|
||||
if (this.isDisposed) return;
|
||||
if (this.isJumping() || !this.isInitialized()) return;
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
.jumpLinkGuildIcon {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
--guild-icon-size: 1rem;
|
||||
flex-shrink: 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -129,7 +129,10 @@ const EmojiRendererInner = observer(function EmojiRendererInner({
|
||||
const tooltipQualitySuffix = `?size=${tooltipEmojiSize}&quality=lossless`;
|
||||
|
||||
const isCustomEmoji = node.kind.kind === EmojiKind.Custom;
|
||||
const emojiRecord = isCustomEmoji && emojiData.id ? EmojiStore.getEmojiById(emojiData.id) : null;
|
||||
const emojiRecord: Emoji | null =
|
||||
isCustomEmoji && emojiData.id ? (EmojiStore.getEmojiById(emojiData.id) ?? null) : null;
|
||||
const fallbackGuildId = emojiRecord?.guildId;
|
||||
const fallbackAnimated = emojiRecord?.animated ?? emojiData.isAnimated;
|
||||
|
||||
const handleOpenBottomSheet = React.useCallback(() => {
|
||||
if (!isMobile) return;
|
||||
@@ -160,12 +163,16 @@ const EmojiRendererInner = observer(function EmojiRendererInner({
|
||||
if (emojiRecord) {
|
||||
return emojiRecord;
|
||||
}
|
||||
|
||||
return {
|
||||
id: emojiData.id,
|
||||
guildId: fallbackGuildId,
|
||||
animated: fallbackAnimated,
|
||||
name: node.kind.name,
|
||||
allNamesString: node.kind.name,
|
||||
uniqueName: node.kind.name,
|
||||
};
|
||||
}, [emojiRecord, node.kind.name]);
|
||||
}, [emojiData.id, emojiData.isAnimated, emojiRecord, node.kind.name]);
|
||||
|
||||
const getTooltipData = React.useCallback(() => {
|
||||
const emojiUrl =
|
||||
|
||||
@@ -62,11 +62,21 @@ interface JumpLinkMentionProps {
|
||||
guild: GuildRecord | null;
|
||||
messageId?: string;
|
||||
i18n: I18n;
|
||||
interactive?: boolean;
|
||||
}
|
||||
|
||||
const JumpLinkMention = observer(function JumpLinkMention({channel, guild, messageId, i18n}: JumpLinkMentionProps) {
|
||||
const INLINE_REPLY_CONTEXT = 1;
|
||||
|
||||
const JumpLinkMention = observer(function JumpLinkMention({
|
||||
channel,
|
||||
guild,
|
||||
messageId,
|
||||
i18n,
|
||||
interactive = true,
|
||||
}: JumpLinkMentionProps) {
|
||||
const handleClick = React.useCallback(
|
||||
(event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
(event: React.MouseEvent<HTMLButtonElement | HTMLSpanElement>) => {
|
||||
if (!interactive) return;
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
@@ -98,17 +108,26 @@ const JumpLinkMention = observer(function JumpLinkMention({channel, guild, messa
|
||||
? i18n._(msg`Jump to ${labelText}`)
|
||||
: i18n._(msg`Jump to the linked channel`);
|
||||
|
||||
const Component = interactive ? 'button' : 'span';
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={clsx(markupStyles.mention, markupStyles.interactive, jumpLinkStyles.jumpLinkButton)}
|
||||
<Component
|
||||
{...(interactive ? {type: 'button'} : {})}
|
||||
className={clsx(markupStyles.mention, interactive && markupStyles.interactive, jumpLinkStyles.jumpLinkButton)}
|
||||
onClick={handleClick}
|
||||
aria-label={ariaLabel}
|
||||
tabIndex={interactive ? 0 : -1}
|
||||
>
|
||||
<span className={jumpLinkStyles.jumpLinkInfo}>
|
||||
{guild ? (
|
||||
<span className={jumpLinkStyles.jumpLinkGuild}>
|
||||
<GuildIcon id={guild.id} name={guild.name} icon={guild.icon} className={jumpLinkStyles.jumpLinkGuildIcon} />
|
||||
<GuildIcon
|
||||
id={guild.id}
|
||||
name={guild.name}
|
||||
icon={guild.icon}
|
||||
className={jumpLinkStyles.jumpLinkGuildIcon}
|
||||
containerProps={{'data-jump-link-guild-icon': ''}}
|
||||
/>
|
||||
<span className={jumpLinkStyles.jumpLinkGuildName}>{guild.name}</span>
|
||||
</span>
|
||||
) : shouldShowDMIconLabel ? (
|
||||
@@ -137,7 +156,7 @@ const JumpLinkMention = observer(function JumpLinkMention({channel, guild, messa
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</button>
|
||||
</Component>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -156,13 +175,20 @@ export const LinkRenderer = observer(function LinkRenderer({
|
||||
const jumpTarget = messageJumpTarget ?? parseChannelJumpLink(url);
|
||||
const jumpChannel = jumpTarget ? (ChannelStore.getChannel(jumpTarget.channelId) ?? null) : null;
|
||||
const jumpGuild = jumpChannel?.guildId ? (GuildStore.getGuild(jumpChannel.guildId) ?? null) : null;
|
||||
const isInlineReplyContext = options.context === INLINE_REPLY_CONTEXT;
|
||||
|
||||
if (jumpTarget && jumpChannel) {
|
||||
return (
|
||||
<FocusRing key={id}>
|
||||
<JumpLinkMention channel={jumpChannel} guild={jumpGuild} messageId={messageJumpTarget?.messageId} i18n={i18n} />
|
||||
</FocusRing>
|
||||
const mention = (
|
||||
<JumpLinkMention
|
||||
channel={jumpChannel}
|
||||
guild={jumpGuild}
|
||||
messageId={messageJumpTarget?.messageId}
|
||||
i18n={i18n}
|
||||
interactive={!isInlineReplyContext}
|
||||
/>
|
||||
);
|
||||
|
||||
return isInlineReplyContext ? mention : <FocusRing key={id}>{mention}</FocusRing>;
|
||||
}
|
||||
|
||||
const shouldShowAccessDeniedModal = Boolean(jumpTarget && !jumpChannel);
|
||||
|
||||
Reference in New Issue
Block a user