diff --git a/src/domain/session/room/timeline/TimelineViewModel.js b/src/domain/session/room/timeline/TimelineViewModel.js index d1a4947d..faf93609 100644 --- a/src/domain/session/room/timeline/TimelineViewModel.js +++ b/src/domain/session/room/timeline/TimelineViewModel.js @@ -1,5 +1,6 @@ /* Copyright 2020 Bruno Windels +Copyright 2020 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -42,13 +43,16 @@ export class TimelineViewModel { this._tiles = new TilesCollection(timeline.entries, tilesCreator({room, ownUserId})); } - // doesn't fill gaps, only loads stored entries/tiles - loadAtTop() { + /** + * @return {bool} startReached if the start of the timeline was reached + */ + async loadAtTop() { const firstTile = this._tiles.getFirst(); if (firstTile.shape === "gap") { return firstTile.fill(); } else { - return this._timeline.loadAtTop(50); + await this._timeline.loadAtTop(50); + return false; } } diff --git a/src/domain/session/room/timeline/tiles/GapTile.js b/src/domain/session/room/timeline/tiles/GapTile.js index e3ab04b1..b9f5623f 100644 --- a/src/domain/session/room/timeline/tiles/GapTile.js +++ b/src/domain/session/room/timeline/tiles/GapTile.js @@ -41,6 +41,8 @@ export class GapTile extends SimpleTile { this.emitChange("isLoading"); } } + // edgeReached will have been updated by fillGap + return this._entry.edgeReached; } updateEntry(entry, params) { diff --git a/src/matrix/room/Room.js b/src/matrix/room/Room.js index 1b038972..c922aea4 100644 --- a/src/matrix/room/Room.js +++ b/src/matrix/room/Room.js @@ -83,6 +83,9 @@ export class Room extends EventEmitter { /** @public */ async fillGap(fragmentEntry, amount) { + if (fragmentEntry.edgeReached) { + return; + } const response = await this._hsApi.messages(this._roomId, { from: fragmentEntry.token, dir: fragmentEntry.direction.asApiString(), diff --git a/src/matrix/room/timeline/entries/FragmentBoundaryEntry.js b/src/matrix/room/timeline/entries/FragmentBoundaryEntry.js index 9260d970..791c7a9f 100644 --- a/src/matrix/room/timeline/entries/FragmentBoundaryEntry.js +++ b/src/matrix/room/timeline/entries/FragmentBoundaryEntry.js @@ -60,7 +60,7 @@ export class FragmentBoundaryEntry extends BaseEntry { } get isGap() { - return !!this.token; + return !!this.token && !this.edgeReached; } get token() { @@ -79,6 +79,25 @@ export class FragmentBoundaryEntry extends BaseEntry { } } + get edgeReached() { + if (this.started) { + return this.fragment.startReached; + } else { + return this.fragment.endReached; + } + } + + set edgeReached(reached) { + + if (this.started) { + this.fragment.startReached = reached; + } else { + this.fragment.endReached = reached; + } + } + + + get linkedFragmentId() { if (this.started) { return this.fragment.previousId; diff --git a/src/matrix/room/timeline/persistence/GapWriter.js b/src/matrix/room/timeline/persistence/GapWriter.js index 34b30506..11b774d3 100644 --- a/src/matrix/room/timeline/persistence/GapWriter.js +++ b/src/matrix/room/timeline/persistence/GapWriter.js @@ -178,6 +178,14 @@ export class GapWriter { if (fragmentEntry.token !== start) { throw new Error("start is not equal to prev_batch or next_batch"); } + + // begin (or end) of timeline reached + if (chunk.length === 0) { + fragmentEntry.edgeReached = true; + await txn.timelineFragments.update(fragmentEntry.fragment); + return {entries: [fragmentEntry], fragments: []}; + } + // find last event in fragment so we get the eventIndex to begin creating keys at let lastKey = await this._findFragmentEdgeEventKey(fragmentEntry, txn); // find out if any event in chunk is already present using findFirstOrLastOccurringEventId diff --git a/src/ui/web/session/room/TimelineList.js b/src/ui/web/session/room/TimelineList.js index 49e254cf..a5f75b7c 100644 --- a/src/ui/web/session/room/TimelineList.js +++ b/src/ui/web/session/room/TimelineList.js @@ -45,7 +45,10 @@ export class TimelineList extends ListView { while (predicate()) { // fill, not enough content to fill timeline this._topLoadingPromise = this._viewModel.loadAtTop(); - await this._topLoadingPromise; + const startReached = await this._topLoadingPromise; + if (startReached) { + break; + } } } catch (err) {