From b30d2f7bab43ef87c2cfe5349c84f05e009f8ebc Mon Sep 17 00:00:00 2001 From: trevor-signal <131492920+trevor-signal@users.noreply.github.com> Date: Mon, 20 Oct 2025 15:27:26 -0400 Subject: [PATCH] Improve timeline layout rendering --- ts/components/conversation/Message.dom.tsx | 55 ++------------------- ts/components/conversation/Timeline.dom.tsx | 13 ++++- 2 files changed, 17 insertions(+), 51 deletions(-) diff --git a/ts/components/conversation/Message.dom.tsx b/ts/components/conversation/Message.dom.tsx index 43421eb32a..688f841ee3 100644 --- a/ts/components/conversation/Message.dom.tsx +++ b/ts/components/conversation/Message.dom.tsx @@ -129,21 +129,6 @@ const { drop, take, unescape } = lodash; const log = createLogger('Message'); -const GUESS_METADATA_WIDTH_TIMESTAMP_SIZE = 16; -const GUESS_METADATA_WIDTH_EXPIRE_TIMER_SIZE = 18; -const GUESS_METADATA_WIDTH_SMS_SIZE = 18; -const GUESS_METADATA_WIDTH_EDITED_SIZE = 40; -const GUESS_METADATA_WIDTH_OUTGOING_SIZE: Record = { - delivered: 24, - error: 24, - paused: 18, - 'partial-sent': 24, - read: 24, - sending: 18, - sent: 24, - viewed: 24, -}; - const EXPIRATION_CHECK_MINIMUM = 2000; const EXPIRED_DELAY = 600; const GROUP_AVATAR_SIZE = AvatarSize.TWENTY_EIGHT; @@ -646,7 +631,7 @@ export class Message extends React.PureComponent { super(props); this.state = { - metadataWidth: this.#guessMetadataWidth(), + metadataWidth: 0, expiring: false, expired: false, @@ -733,6 +718,10 @@ export class Message extends React.PureComponent { this.#startDeleteForEveryoneTimerIfApplicable(); this.startGiftBadgeInterval(); + if (this.#metadataRef.current) { + this.#updateMetadataWidth(this.#metadataRef.current.offsetWidth); + } + const { isTargeted } = this.props; if (isTargeted) { this.setFocus(); @@ -888,40 +877,6 @@ export class Message extends React.PureComponent { return MetadataPlacement.InlineWithText; } - /** - * A lot of the time, we add an invisible inline spacer for messages. This spacer is the - * same size as the message metadata. Unfortunately, we don't know how wide it is until - * we render it. - * - * This will probably guess wrong, but it's valuable to get close to the real value - * because it can reduce layout jumpiness. - */ - #guessMetadataWidth(): number { - const { direction, expirationLength, isSMS, status, isEditedMessage } = - this.props; - - let result = GUESS_METADATA_WIDTH_TIMESTAMP_SIZE; - - if (isEditedMessage) { - result += GUESS_METADATA_WIDTH_EDITED_SIZE; - } - - const hasExpireTimer = Boolean(expirationLength); - if (hasExpireTimer) { - result += GUESS_METADATA_WIDTH_EXPIRE_TIMER_SIZE; - } - - if (isSMS) { - result += GUESS_METADATA_WIDTH_SMS_SIZE; - } - - if (direction === 'outgoing' && status) { - result += GUESS_METADATA_WIDTH_OUTGOING_SIZE[status]; - } - - return result; - } - public startTargetedTimer(): void { const { clearTargetedMessage, interactionMode } = this.props; const { isTargeted } = this.state; diff --git a/ts/components/conversation/Timeline.dom.tsx b/ts/components/conversation/Timeline.dom.tsx index 6b4753b454..35c76e77fd 100644 --- a/ts/components/conversation/Timeline.dom.tsx +++ b/ts/components/conversation/Timeline.dom.tsx @@ -614,7 +614,18 @@ export class Timeline extends React.Component< ' mounted without some refs' ); - this.#updateIntersectionObserver(); + if (containerEl) { + this.setState( + { + widthBreakpoint: getWidthBreakpoint(containerEl.offsetWidth), + }, + () => { + this.#updateIntersectionObserver(); + } + ); + } else { + this.#updateIntersectionObserver(); + } window.SignalContext.activeWindowService.registerForActive( this.#markNewestBottomVisibleMessageReadAfterDelay