Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit 4b5ca1d

Browse files
authored
Fix timeline jumping issues related to bubble layout (#7529)
1 parent 8ced6e6 commit 4b5ca1d

File tree

4 files changed

+57
-34
lines changed

4 files changed

+57
-34
lines changed

res/css/views/rooms/_EventBubbleTile.scss

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -215,13 +215,23 @@ limitations under the License.
215215
}
216216

217217
//noinspection CssReplaceWithShorthandSafely
218-
.mx_MImageBody .mx_MImageBody_thumbnail {
219-
// Note: This is intentionally not compressed because the browser gets confused
220-
// when it is all combined. We're effectively unsetting the border radius then
221-
// setting the two corners we care about manually.
222-
border-radius: unset;
223-
border-top-left-radius: var(--cornerRadius);
224-
border-top-right-radius: var(--cornerRadius);
218+
.mx_MImageBody {
219+
width: 100%;
220+
height: 100%;
221+
222+
//noinspection CssReplaceWithShorthandSafely
223+
.mx_MImageBody_thumbnail {
224+
// Note: This is intentionally not compressed because the browser gets confused
225+
// when it is all combined. We're effectively unsetting the border radius then
226+
// setting the two corners we care about manually.
227+
border-radius: unset;
228+
border-top-left-radius: var(--cornerRadius);
229+
border-top-right-radius: var(--cornerRadius);
230+
231+
&.mx_MImageBody_thumbnail--blurhash {
232+
position: unset;
233+
}
234+
}
225235
}
226236

227237
.mx_EventTile_e2eIcon {
@@ -434,7 +444,6 @@ limitations under the License.
434444
}
435445
&[aria-expanded=true] {
436446
text-align: right;
437-
margin-right: 100px;
438447
}
439448
}
440449

@@ -457,11 +466,26 @@ limitations under the License.
457466
padding: 0 49px;
458467
}
459468

469+
// ideally we'd use display=contents here for the layout to all work regardless of the *ELS but
470+
// that breaks ScrollPanel's reliance upon offsetTop so we have to have a bit more finesse.
460471
.mx_EventListSummary[data-expanded=true][data-layout=bubble] {
461-
display: contents;
472+
margin: 0;
462473

463474
.mx_EventTile {
464475
padding: 2px 0;
476+
margin-right: 0;
477+
478+
.mx_MessageActionBar {
479+
right: 127px; // align with that of right-column bubbles
480+
}
481+
482+
.mx_EventTile_readAvatars {
483+
right: -18px; // match alignment to RRs of chat bubbles
484+
}
485+
486+
&::before {
487+
right: 0; // match alignment of the hover background to that of chat bubbles
488+
}
465489
}
466490
}
467491

src/components/structures/ScrollPanel.tsx

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ interface IProps {
8989
* The promise should resolve to true if there is more data to be
9090
* retrieved in this direction (in which case onFillRequest may be
9191
* called again immediately), or false if there is no more data in this
92-
* directon (at this time) - which will stop the pagination cycle until
92+
* direction (at this time) - which will stop the pagination cycle until
9393
* the user scrolls again.
9494
*/
9595
onFillRequest?(backwards: boolean): Promise<boolean>;
@@ -683,7 +683,7 @@ export default class ScrollPanel extends React.Component<IProps> {
683683
return;
684684
}
685685
const scrollToken = node.dataset.scrollTokens.split(',')[0];
686-
debuglog("saving anchored scroll state to message", node && node.innerText, scrollToken);
686+
debuglog("saving anchored scroll state to message", node.innerText, scrollToken);
687687
const bottomOffset = this.topFromBottom(node);
688688
this.scrollState = {
689689
stuckAtBottom: false,
@@ -791,17 +791,16 @@ export default class ScrollPanel extends React.Component<IProps> {
791791
const scrollState = this.scrollState;
792792
const trackedNode = scrollState.trackedNode;
793793

794-
if (!trackedNode || !trackedNode.parentElement) {
795-
let node;
794+
if (!trackedNode?.parentElement) {
795+
let node: HTMLElement;
796796
const messages = this.itemlist.current.children;
797797
const scrollToken = scrollState.trackedScrollToken;
798798

799-
for (let i = messages.length-1; i >= 0; --i) {
799+
for (let i = messages.length - 1; i >= 0; --i) {
800800
const m = messages[i] as HTMLElement;
801801
// 'data-scroll-tokens' is a DOMString of comma-separated scroll tokens
802802
// There might only be one scroll token
803-
if (m.dataset.scrollTokens &&
804-
m.dataset.scrollTokens.split(',').indexOf(scrollToken) !== -1) {
803+
if (m.dataset.scrollTokens?.split(',').includes(scrollToken)) {
805804
node = m;
806805
break;
807806
}

src/components/structures/TimelinePanel.tsx

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ const DEBUG = false;
6363
let debuglog = function(...s: any[]) {};
6464
if (DEBUG) {
6565
// using bind means that we get to keep useful line numbers in the console
66-
debuglog = logger.log.bind(console);
66+
debuglog = logger.log.bind(console, "TimelinePanel debuglog:");
6767
}
6868

6969
interface IProps {
@@ -240,7 +240,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
240240
constructor(props, context) {
241241
super(props, context);
242242

243-
debuglog("TimelinePanel: mounting");
243+
debuglog("mounting");
244244

245245
// XXX: we could track RM per TimelineSet rather than per Room.
246246
// but for now we just do it per room for simplicity.
@@ -369,7 +369,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
369369
private onMessageListUnfillRequest = (backwards: boolean, scrollToken: string): void => {
370370
// If backwards, unpaginate from the back (i.e. the start of the timeline)
371371
const dir = backwards ? EventTimeline.BACKWARDS : EventTimeline.FORWARDS;
372-
debuglog("TimelinePanel: unpaginating events in direction", dir);
372+
debuglog("unpaginating events in direction", dir);
373373

374374
// All tiles are inserted by MessagePanel to have a scrollToken === eventId, and
375375
// this particular event should be the first or last to be unpaginated.
@@ -384,7 +384,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
384384
const count = backwards ? marker + 1 : this.state.events.length - marker;
385385

386386
if (count > 0) {
387-
debuglog("TimelinePanel: Unpaginating", count, "in direction", dir);
387+
debuglog("Unpaginating", count, "in direction", dir);
388388
this.timelineWindow.unpaginate(count, backwards);
389389

390390
const { events, liveEvents, firstVisibleEventIndex } = this.getEvents();
@@ -425,28 +425,28 @@ class TimelinePanel extends React.Component<IProps, IState> {
425425
const paginatingKey = backwards ? 'backPaginating' : 'forwardPaginating';
426426

427427
if (!this.state[canPaginateKey]) {
428-
debuglog("TimelinePanel: have given up", dir, "paginating this timeline");
428+
debuglog("have given up", dir, "paginating this timeline");
429429
return Promise.resolve(false);
430430
}
431431

432432
if (!this.timelineWindow.canPaginate(dir)) {
433-
debuglog("TimelinePanel: can't", dir, "paginate any further");
433+
debuglog("can't", dir, "paginate any further");
434434
this.setState<null>({ [canPaginateKey]: false });
435435
return Promise.resolve(false);
436436
}
437437

438438
if (backwards && this.state.firstVisibleEventIndex !== 0) {
439-
debuglog("TimelinePanel: won't", dir, "paginate past first visible event");
439+
debuglog("won't", dir, "paginate past first visible event");
440440
return Promise.resolve(false);
441441
}
442442

443-
debuglog("TimelinePanel: Initiating paginate; backwards:"+backwards);
443+
debuglog("Initiating paginate; backwards:"+backwards);
444444
this.setState<null>({ [paginatingKey]: true });
445445

446446
return this.onPaginationRequest(this.timelineWindow, dir, PAGINATE_SIZE).then((r) => {
447447
if (this.unmounted) { return; }
448448

449-
debuglog("TimelinePanel: paginate complete backwards:"+backwards+"; success:"+r);
449+
debuglog("paginate complete backwards:"+backwards+"; success:"+r);
450450

451451
const { events, liveEvents, firstVisibleEventIndex } = this.getEvents();
452452
const newState: Partial<IState> = {
@@ -463,7 +463,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
463463
const canPaginateOtherWayKey = backwards ? 'canForwardPaginate' : 'canBackPaginate';
464464
if (!this.state[canPaginateOtherWayKey] &&
465465
this.timelineWindow.canPaginate(otherDirection)) {
466-
debuglog('TimelinePanel: can now', otherDirection, 'paginate again');
466+
debuglog('can now', otherDirection, 'paginate again');
467467
newState[canPaginateOtherWayKey] = true;
468468
}
469469

@@ -833,7 +833,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
833833
const roomId = this.props.timelineSet.room.roomId;
834834
const hiddenRR = SettingsStore.getValue("feature_hidden_read_receipts", roomId);
835835

836-
debuglog('TimelinePanel: Sending Read Markers for ',
836+
debuglog('Sending Read Markers for ',
837837
this.props.timelineSet.room.roomId,
838838
'rm', this.state.readMarkerEventId,
839839
lastReadEvent ? 'rr ' + lastReadEvent.getId() : '',

src/components/views/messages/MImageBody.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -338,8 +338,8 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
338338
content: IMediaEventContent,
339339
forcedHeight?: number,
340340
): JSX.Element {
341-
let infoWidth;
342-
let infoHeight;
341+
let infoWidth: number;
342+
let infoHeight: number;
343343

344344
if (content && content.info && content.info.w && content.info.h) {
345345
infoWidth = content.info.w;
@@ -382,8 +382,8 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
382382
const suggestedAndPossibleHeight = Math.min(suggestedImageSize(imageSize, isPortrait).h, infoHeight);
383383
const aspectRatio = infoWidth / infoHeight;
384384

385-
let maxWidth;
386-
let maxHeight;
385+
let maxWidth: number;
386+
let maxHeight: number;
387387
const maxHeightConstraint = forcedHeight || this.props.maxImageHeight || suggestedAndPossibleHeight;
388388
if (maxHeightConstraint * aspectRatio < suggestedAndPossibleWidth || imageSize === ImageSize.Large) {
389389
// The width is dictated by the maximum height that was defined by the props or the function param `forcedHeight`
@@ -451,7 +451,7 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
451451
// This has incredibly broken types.
452452
const C = CSSTransition as any;
453453
const thumbnail = (
454-
<div className="mx_MImageBody_thumbnail_container" style={{ maxHeight: maxHeight, maxWidth: maxWidth, aspectRatio: `${infoWidth}/${infoHeight}` }}>
454+
<div className="mx_MImageBody_thumbnail_container" style={{ maxHeight, maxWidth, aspectRatio: `${infoWidth}/${infoHeight}` }}>
455455
<SwitchTransition mode="out-in">
456456
<C
457457
classNames="mx_rtg--fade"
@@ -464,8 +464,8 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
464464
className={classes}
465465
style={{
466466
// Constrain width here so that spinner appears central to the loaded thumbnail
467-
maxWidth: `min(100%, ${infoWidth}px)`,
468-
maxHeight: maxHeight,
467+
maxWidth,
468+
maxHeight,
469469
aspectRatio: `${infoWidth}/${infoHeight}`,
470470
}}
471471
>

0 commit comments

Comments
 (0)