Apply prettier formatting
This commit is contained in:
parent
1cac306093
commit
526645c791
1576 changed files with 65385 additions and 62478 deletions
|
@ -14,35 +14,35 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { createRef, ReactNode } from 'react';
|
||||
import React, { createRef, ReactNode } from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import { NotificationCountType, Room, RoomEvent } from "matrix-js-sdk/src/models/room";
|
||||
import { MatrixEvent, MatrixEventEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { EventTimelineSet, IRoomTimelineData } from "matrix-js-sdk/src/models/event-timeline-set";
|
||||
import { Direction, EventTimeline } from "matrix-js-sdk/src/models/event-timeline";
|
||||
import { TimelineWindow } from "matrix-js-sdk/src/timeline-window";
|
||||
import { EventType, RelationType } from 'matrix-js-sdk/src/@types/event';
|
||||
import { SyncState } from 'matrix-js-sdk/src/sync';
|
||||
import { RoomMember, RoomMemberEvent } from 'matrix-js-sdk/src/models/room-member';
|
||||
import { debounce, throttle } from 'lodash';
|
||||
import { EventType, RelationType } from "matrix-js-sdk/src/@types/event";
|
||||
import { SyncState } from "matrix-js-sdk/src/sync";
|
||||
import { RoomMember, RoomMemberEvent } from "matrix-js-sdk/src/models/room-member";
|
||||
import { debounce, throttle } from "lodash";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { ClientEvent } from "matrix-js-sdk/src/client";
|
||||
import { Thread, ThreadEvent } from 'matrix-js-sdk/src/models/thread';
|
||||
import { Thread, ThreadEvent } from "matrix-js-sdk/src/models/thread";
|
||||
import { ReceiptType } from "matrix-js-sdk/src/@types/read_receipts";
|
||||
import { MatrixError } from 'matrix-js-sdk/src/http-api';
|
||||
import { ReadReceipt } from 'matrix-js-sdk/src/models/read-receipt';
|
||||
import { MatrixError } from "matrix-js-sdk/src/http-api";
|
||||
import { ReadReceipt } from "matrix-js-sdk/src/models/read-receipt";
|
||||
|
||||
import SettingsStore from "../../settings/SettingsStore";
|
||||
import { Layout } from "../../settings/enums/Layout";
|
||||
import { _t } from '../../languageHandler';
|
||||
import { _t } from "../../languageHandler";
|
||||
import { MatrixClientPeg } from "../../MatrixClientPeg";
|
||||
import RoomContext, { TimelineRenderingType } from "../../contexts/RoomContext";
|
||||
import UserActivity from "../../UserActivity";
|
||||
import Modal from "../../Modal";
|
||||
import dis from "../../dispatcher/dispatcher";
|
||||
import { Action } from '../../dispatcher/actions';
|
||||
import Timer from '../../utils/Timer';
|
||||
import shouldHideEvent from '../../shouldHideEvent';
|
||||
import { Action } from "../../dispatcher/actions";
|
||||
import Timer from "../../utils/Timer";
|
||||
import shouldHideEvent from "../../shouldHideEvent";
|
||||
import { arrayFastClone } from "../../utils/arrays";
|
||||
import MessagePanel from "./MessagePanel";
|
||||
import { IScrollState } from "./ScrollPanel";
|
||||
|
@ -50,8 +50,8 @@ import { ActionPayload } from "../../dispatcher/payloads";
|
|||
import ResizeNotifier from "../../utils/ResizeNotifier";
|
||||
import { RoomPermalinkCreator } from "../../utils/permalinks/Permalinks";
|
||||
import Spinner from "../views/elements/Spinner";
|
||||
import EditorStateTransfer from '../../utils/EditorStateTransfer';
|
||||
import ErrorDialog from '../views/dialogs/ErrorDialog';
|
||||
import EditorStateTransfer from "../../utils/EditorStateTransfer";
|
||||
import ErrorDialog from "../views/dialogs/ErrorDialog";
|
||||
import LegacyCallEventGrouper, { buildLegacyCallEventGroupers } from "./LegacyCallEventGrouper";
|
||||
import { ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
|
||||
import { getKeyBindingsManager } from "../../KeyBindingsManager";
|
||||
|
@ -232,7 +232,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
// By default, disable the timelineCap in favour of unpaginating based on
|
||||
// event tile heights. (See _unpaginateEvents)
|
||||
timelineCap: Number.MAX_VALUE,
|
||||
className: 'mx_RoomView_messagePanel',
|
||||
className: "mx_RoomView_messagePanel",
|
||||
sendReadReceiptOnLoad: true,
|
||||
hideThreadedMessages: true,
|
||||
disableGrouping: false,
|
||||
|
@ -262,7 +262,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
// but for now we just do it per room for simplicity.
|
||||
let initialReadMarker: string | null = null;
|
||||
if (this.props.manageReadMarkers) {
|
||||
const readmarker = this.props.timelineSet.room.getAccountData('m.fully_read');
|
||||
const readmarker = this.props.timelineSet.room.getAccountData("m.fully_read");
|
||||
if (readmarker) {
|
||||
initialReadMarker = readmarker.getContent().event_id;
|
||||
} else {
|
||||
|
@ -343,8 +343,10 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
const differentHighlightedEventId = prevProps.highlightedEventId != this.props.highlightedEventId;
|
||||
const differentAvoidJump = prevProps.eventScrollIntoView && !this.props.eventScrollIntoView;
|
||||
if (differentEventId || differentHighlightedEventId || differentAvoidJump) {
|
||||
logger.log(`TimelinePanel switching to eventId ${this.props.eventId} (was ${prevProps.eventId}), ` +
|
||||
`scrollIntoView: ${this.props.eventScrollIntoView} (was ${prevProps.eventScrollIntoView})`);
|
||||
logger.log(
|
||||
`TimelinePanel switching to eventId ${this.props.eventId} (was ${prevProps.eventId}), ` +
|
||||
`scrollIntoView: ${this.props.eventScrollIntoView} (was ${prevProps.eventScrollIntoView})`,
|
||||
);
|
||||
this.initTimeline(this.props);
|
||||
}
|
||||
}
|
||||
|
@ -408,9 +410,9 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
if (messagePanel) {
|
||||
const messagePanelNode = ReactDOM.findDOMNode(messagePanel) as Element;
|
||||
if (messagePanelNode) {
|
||||
const actuallyRenderedEvents = messagePanelNode.querySelectorAll('[data-event-id]');
|
||||
const actuallyRenderedEvents = messagePanelNode.querySelectorAll("[data-event-id]");
|
||||
renderedEventIds = [...actuallyRenderedEvents].map((renderedEvent) => {
|
||||
return renderedEvent.getAttribute('data-event-id');
|
||||
return renderedEvent.getAttribute("data-event-id");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -439,12 +441,16 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
// Serialize all threads in the room from theadId -> event IDs in the thread
|
||||
room.getThreads().forEach((thread) => {
|
||||
serializedThreadsMap[thread.id] = {
|
||||
events: thread.events.map(ev => ev.getId()),
|
||||
events: thread.events.map((ev) => ev.getId()),
|
||||
numTimelines: thread.timelineSet.getTimelines().length,
|
||||
liveTimeline: thread.timelineSet.getLiveTimeline().getEvents().length,
|
||||
prevTimeline: thread.timelineSet.getLiveTimeline().getNeighbouringTimeline(Direction.Backward)
|
||||
prevTimeline: thread.timelineSet
|
||||
.getLiveTimeline()
|
||||
.getNeighbouringTimeline(Direction.Backward)
|
||||
?.getEvents().length,
|
||||
nextTimeline: thread.timelineSet.getLiveTimeline().getNeighbouringTimeline(Direction.Forward)
|
||||
nextTimeline: thread.timelineSet
|
||||
.getLiveTimeline()
|
||||
.getNeighbouringTimeline(Direction.Forward)
|
||||
?.getEvents().length,
|
||||
};
|
||||
});
|
||||
|
@ -455,28 +461,30 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
|
||||
let timelineWindowEventIds: string[];
|
||||
try {
|
||||
timelineWindowEventIds = this.timelineWindow.getEvents().map(ev => ev.getId());
|
||||
timelineWindowEventIds = this.timelineWindow.getEvents().map((ev) => ev.getId());
|
||||
} catch (err) {
|
||||
logger.error(`onDumpDebugLogs: Failed to get event IDs from the timelineWindow`, err);
|
||||
}
|
||||
let pendingEventIds: string[];
|
||||
try {
|
||||
pendingEventIds = this.props.timelineSet.getPendingEvents().map(ev => ev.getId());
|
||||
pendingEventIds = this.props.timelineSet.getPendingEvents().map((ev) => ev.getId());
|
||||
} catch (err) {
|
||||
logger.error(`onDumpDebugLogs: Failed to get pending event IDs`, err);
|
||||
}
|
||||
|
||||
logger.debug(
|
||||
`TimelinePanel(${this.context.timelineRenderingType}): Debugging info for ${room?.roomId}\n` +
|
||||
`\tevents(${eventIdList.length})=${JSON.stringify(eventIdList)}\n` +
|
||||
`\trenderedEventIds(${renderedEventIds?.length ?? 0})=` +
|
||||
`${JSON.stringify(renderedEventIds)}\n` +
|
||||
`\tserializedEventIdsFromTimelineSets=${JSON.stringify(serializedEventIdsFromTimelineSets)}\n` +
|
||||
`\tserializedEventIdsFromThreadsTimelineSets=` +
|
||||
`${JSON.stringify(serializedEventIdsFromThreadsTimelineSets)}\n` +
|
||||
`\tserializedThreadsMap=${JSON.stringify(serializedThreadsMap)}\n` +
|
||||
`\ttimelineWindowEventIds(${timelineWindowEventIds.length})=${JSON.stringify(timelineWindowEventIds)}\n` +
|
||||
`\tpendingEventIds(${pendingEventIds.length})=${JSON.stringify(pendingEventIds)}`,
|
||||
`\tevents(${eventIdList.length})=${JSON.stringify(eventIdList)}\n` +
|
||||
`\trenderedEventIds(${renderedEventIds?.length ?? 0})=` +
|
||||
`${JSON.stringify(renderedEventIds)}\n` +
|
||||
`\tserializedEventIdsFromTimelineSets=${JSON.stringify(serializedEventIdsFromTimelineSets)}\n` +
|
||||
`\tserializedEventIdsFromThreadsTimelineSets=` +
|
||||
`${JSON.stringify(serializedEventIdsFromThreadsTimelineSets)}\n` +
|
||||
`\tserializedThreadsMap=${JSON.stringify(serializedThreadsMap)}\n` +
|
||||
`\ttimelineWindowEventIds(${timelineWindowEventIds.length})=${JSON.stringify(
|
||||
timelineWindowEventIds,
|
||||
)}\n` +
|
||||
`\tpendingEventIds(${pendingEventIds.length})=${JSON.stringify(pendingEventIds)}`,
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -489,11 +497,9 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
// this particular event should be the first or last to be unpaginated.
|
||||
const eventId = scrollToken;
|
||||
|
||||
const marker = this.state.events.findIndex(
|
||||
(ev) => {
|
||||
return ev.getId() === eventId;
|
||||
},
|
||||
);
|
||||
const marker = this.state.events.findIndex((ev) => {
|
||||
return ev.getId() === eventId;
|
||||
});
|
||||
|
||||
const count = backwards ? marker + 1 : this.state.events.length - marker;
|
||||
|
||||
|
@ -536,8 +542,8 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
if (!this.shouldPaginate()) return Promise.resolve(false);
|
||||
|
||||
const dir = backwards ? EventTimeline.BACKWARDS : EventTimeline.FORWARDS;
|
||||
const canPaginateKey = backwards ? 'canBackPaginate' : 'canForwardPaginate';
|
||||
const paginatingKey = backwards ? 'backPaginating' : 'forwardPaginating';
|
||||
const canPaginateKey = backwards ? "canBackPaginate" : "canForwardPaginate";
|
||||
const paginatingKey = backwards ? "backPaginating" : "forwardPaginating";
|
||||
|
||||
if (!this.state[canPaginateKey]) {
|
||||
debuglog("have given up", dir, "paginating this timeline");
|
||||
|
@ -555,13 +561,15 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
debuglog("Initiating paginate; backwards:"+backwards);
|
||||
debuglog("Initiating paginate; backwards:" + backwards);
|
||||
this.setState<null>({ [paginatingKey]: true });
|
||||
|
||||
return this.onPaginationRequest(this.timelineWindow, dir, PAGINATE_SIZE).then((r) => {
|
||||
if (this.unmounted) { return; }
|
||||
if (this.unmounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
debuglog("paginate complete backwards:"+backwards+"; success:"+r);
|
||||
debuglog("paginate complete backwards:" + backwards + "; success:" + r);
|
||||
|
||||
const { events, liveEvents, firstVisibleEventIndex } = this.getEvents();
|
||||
this.buildLegacyCallEventGroupers(events);
|
||||
|
@ -576,10 +584,9 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
// moving the window in this direction may mean that we can now
|
||||
// paginate in the other where we previously could not.
|
||||
const otherDirection = backwards ? EventTimeline.FORWARDS : EventTimeline.BACKWARDS;
|
||||
const canPaginateOtherWayKey = backwards ? 'canForwardPaginate' : 'canBackPaginate';
|
||||
if (!this.state[canPaginateOtherWayKey] &&
|
||||
this.timelineWindow.canPaginate(otherDirection)) {
|
||||
debuglog('can now', otherDirection, 'paginate again');
|
||||
const canPaginateOtherWayKey = backwards ? "canForwardPaginate" : "canBackPaginate";
|
||||
if (!this.state[canPaginateOtherWayKey] && this.timelineWindow.canPaginate(otherDirection)) {
|
||||
debuglog("can now", otherDirection, "paginate again");
|
||||
newState[canPaginateOtherWayKey] = true;
|
||||
}
|
||||
|
||||
|
@ -600,7 +607,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
});
|
||||
};
|
||||
|
||||
private onMessageListScroll = e => {
|
||||
private onMessageListScroll = (e) => {
|
||||
this.props.onScroll?.(e);
|
||||
if (this.props.manageReadMarkers) {
|
||||
this.doManageReadMarkers();
|
||||
|
@ -616,21 +623,25 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
* we really only need to update this once the user has finished scrolling,
|
||||
* not periodically while they scroll).
|
||||
*/
|
||||
private doManageReadMarkers = debounce(() => {
|
||||
const rmPosition = this.getReadMarkerPosition();
|
||||
// we hide the read marker when it first comes onto the screen, but if
|
||||
// it goes back off the top of the screen (presumably because the user
|
||||
// clicks on the 'jump to bottom' button), we need to re-enable it.
|
||||
if (rmPosition < 0) {
|
||||
this.setState({ readMarkerVisible: true });
|
||||
}
|
||||
private doManageReadMarkers = debounce(
|
||||
() => {
|
||||
const rmPosition = this.getReadMarkerPosition();
|
||||
// we hide the read marker when it first comes onto the screen, but if
|
||||
// it goes back off the top of the screen (presumably because the user
|
||||
// clicks on the 'jump to bottom' button), we need to re-enable it.
|
||||
if (rmPosition < 0) {
|
||||
this.setState({ readMarkerVisible: true });
|
||||
}
|
||||
|
||||
// if read marker position goes between 0 and -1/1,
|
||||
// (and user is active), switch timeout
|
||||
const timeout = this.readMarkerTimeout(rmPosition);
|
||||
// NO-OP when timeout already has set to the given value
|
||||
this.readMarkerActivityTimer?.changeTimeout(timeout);
|
||||
}, READ_MARKER_DEBOUNCE_MS, { leading: false, trailing: true });
|
||||
// if read marker position goes between 0 and -1/1,
|
||||
// (and user is active), switch timeout
|
||||
const timeout = this.readMarkerTimeout(rmPosition);
|
||||
// NO-OP when timeout already has set to the given value
|
||||
this.readMarkerActivityTimer?.changeTimeout(timeout);
|
||||
},
|
||||
READ_MARKER_DEBOUNCE_MS,
|
||||
{ leading: false, trailing: true },
|
||||
);
|
||||
|
||||
private onAction = (payload: ActionPayload): void => {
|
||||
switch (payload.action) {
|
||||
|
@ -652,8 +663,8 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
): void => {
|
||||
// ignore events for other timeline sets
|
||||
if (
|
||||
data.timeline.getTimelineSet() !== this.props.timelineSet
|
||||
&& data.timeline.getTimelineSet() !== this.props.overlayTimelineSet
|
||||
data.timeline.getTimelineSet() !== this.props.timelineSet &&
|
||||
data.timeline.getTimelineSet() !== this.props.overlayTimelineSet
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
@ -701,7 +712,9 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
}
|
||||
})
|
||||
.then(() => {
|
||||
if (this.unmounted) { return; }
|
||||
if (this.unmounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { events, liveEvents, firstVisibleEventIndex } = this.getEvents();
|
||||
this.buildLegacyCallEventGroupers(events);
|
||||
|
@ -715,21 +728,21 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
|
||||
let callRMUpdated = false;
|
||||
if (this.props.manageReadMarkers) {
|
||||
// when a new event arrives when the user is not watching the
|
||||
// window, but the window is in its auto-scroll mode, make sure the
|
||||
// read marker is visible.
|
||||
//
|
||||
// We ignore events we have sent ourselves; we don't want to see the
|
||||
// read-marker when a remote echo of an event we have just sent takes
|
||||
// more than the timeout on userActiveRecently.
|
||||
//
|
||||
// when a new event arrives when the user is not watching the
|
||||
// window, but the window is in its auto-scroll mode, make sure the
|
||||
// read marker is visible.
|
||||
//
|
||||
// We ignore events we have sent ourselves; we don't want to see the
|
||||
// read-marker when a remote echo of an event we have just sent takes
|
||||
// more than the timeout on userActiveRecently.
|
||||
//
|
||||
const myUserId = MatrixClientPeg.get().credentials.userId;
|
||||
callRMUpdated = false;
|
||||
if (ev.getSender() !== myUserId && !UserActivity.sharedInstance().userActiveRecently()) {
|
||||
updatedState.readMarkerVisible = true;
|
||||
} else if (lastLiveEvent && this.getReadMarkerPosition() === 0) {
|
||||
// we know we're stuckAtBottom, so we can advance the RM
|
||||
// immediately, to save a later render cycle
|
||||
// we know we're stuckAtBottom, so we can advance the RM
|
||||
// immediately, to save a later render cycle
|
||||
|
||||
this.setReadMarker(lastLiveEvent.getId() ?? null, lastLiveEvent.getTs(), true);
|
||||
updatedState.readMarkerVisible = false;
|
||||
|
@ -871,9 +884,12 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
// XXX: roomReadMarkerTsMap not updated here so it is now inconsistent. Replace
|
||||
// this mechanism of determining where the RM is relative to the view-port with
|
||||
// one supported by the server (the client needs more than an event ID).
|
||||
this.setState({
|
||||
readMarkerEventId: ev.getContent().event_id,
|
||||
}, this.props.onReadMarkerUpdated);
|
||||
this.setState(
|
||||
{
|
||||
readMarkerEventId: ev.getContent().event_id,
|
||||
},
|
||||
this.props.onReadMarkerUpdated,
|
||||
);
|
||||
};
|
||||
|
||||
private onEventDecrypted = (ev: MatrixEvent): void => {
|
||||
|
@ -901,28 +917,35 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
this.setState({ clientSyncState });
|
||||
};
|
||||
|
||||
private recheckFirstVisibleEventIndex = throttle((): void => {
|
||||
const firstVisibleEventIndex = this.checkForPreJoinUISI(this.state.events);
|
||||
if (firstVisibleEventIndex !== this.state.firstVisibleEventIndex) {
|
||||
this.setState({ firstVisibleEventIndex });
|
||||
}
|
||||
}, 500, { leading: true, trailing: true });
|
||||
private recheckFirstVisibleEventIndex = throttle(
|
||||
(): void => {
|
||||
const firstVisibleEventIndex = this.checkForPreJoinUISI(this.state.events);
|
||||
if (firstVisibleEventIndex !== this.state.firstVisibleEventIndex) {
|
||||
this.setState({ firstVisibleEventIndex });
|
||||
}
|
||||
},
|
||||
500,
|
||||
{ leading: true, trailing: true },
|
||||
);
|
||||
|
||||
private readMarkerTimeout(readMarkerPosition: number): number {
|
||||
return readMarkerPosition === 0 ?
|
||||
this.context?.readMarkerInViewThresholdMs ?? this.state.readMarkerInViewThresholdMs :
|
||||
this.context?.readMarkerOutOfViewThresholdMs ?? this.state.readMarkerOutOfViewThresholdMs;
|
||||
return readMarkerPosition === 0
|
||||
? this.context?.readMarkerInViewThresholdMs ?? this.state.readMarkerInViewThresholdMs
|
||||
: this.context?.readMarkerOutOfViewThresholdMs ?? this.state.readMarkerOutOfViewThresholdMs;
|
||||
}
|
||||
|
||||
private async updateReadMarkerOnUserActivity(): Promise<void> {
|
||||
const initialTimeout = this.readMarkerTimeout(this.getReadMarkerPosition());
|
||||
this.readMarkerActivityTimer = new Timer(initialTimeout);
|
||||
|
||||
while (this.readMarkerActivityTimer) { //unset on unmount
|
||||
while (this.readMarkerActivityTimer) {
|
||||
//unset on unmount
|
||||
UserActivity.sharedInstance().timeWhileActiveRecently(this.readMarkerActivityTimer);
|
||||
try {
|
||||
await this.readMarkerActivityTimer.finished();
|
||||
} catch (e) { continue; /* aborted */ }
|
||||
} catch (e) {
|
||||
continue; /* aborted */
|
||||
}
|
||||
// outside of try/catch to not swallow errors
|
||||
this.updateReadMarker();
|
||||
}
|
||||
|
@ -930,11 +953,14 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
|
||||
private async updateReadReceiptOnUserActivity(): Promise<void> {
|
||||
this.readReceiptActivityTimer = new Timer(READ_RECEIPT_INTERVAL_MS);
|
||||
while (this.readReceiptActivityTimer) { //unset on unmount
|
||||
while (this.readReceiptActivityTimer) {
|
||||
//unset on unmount
|
||||
UserActivity.sharedInstance().timeWhileActiveNow(this.readReceiptActivityTimer);
|
||||
try {
|
||||
await this.readReceiptActivityTimer.finished();
|
||||
} catch (e) { continue; /* aborted */ }
|
||||
} catch (e) {
|
||||
continue; /* aborted */
|
||||
}
|
||||
// outside of try/catch to not swallow errors
|
||||
this.sendReadReceipt();
|
||||
}
|
||||
|
@ -969,8 +995,11 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
// RRs) - but that is a bit of a niche case. It will sort itself out when
|
||||
// the user eventually hits the live timeline.
|
||||
//
|
||||
if (currentRREventId && currentRREventIndex === null &&
|
||||
this.timelineWindow.canPaginate(EventTimeline.FORWARDS)) {
|
||||
if (
|
||||
currentRREventId &&
|
||||
currentRREventIndex === null &&
|
||||
this.timelineWindow.canPaginate(EventTimeline.FORWARDS)
|
||||
) {
|
||||
shouldSendRR = false;
|
||||
}
|
||||
|
||||
|
@ -981,7 +1010,8 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
shouldSendRR = false;
|
||||
}
|
||||
let lastReadEvent: MatrixEvent | null = this.state.events[lastReadEventIndex ?? 0];
|
||||
shouldSendRR = shouldSendRR &&
|
||||
shouldSendRR =
|
||||
shouldSendRR &&
|
||||
// Only send a RR if the last read event is ahead in the timeline relative to
|
||||
// the current RR event.
|
||||
lastReadEventIndex > currentRREventIndex &&
|
||||
|
@ -989,8 +1019,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
this.lastRRSentEventId !== lastReadEvent?.getId();
|
||||
|
||||
// Only send a RM if the last RM sent != the one we would send
|
||||
const shouldSendRM =
|
||||
this.lastRMSentEventId != this.state.readMarkerEventId;
|
||||
const shouldSendRM = this.lastRMSentEventId != this.state.readMarkerEventId;
|
||||
|
||||
// we also remember the last read receipt we sent to avoid spamming the
|
||||
// same one at the server repeatedly
|
||||
|
@ -1010,30 +1039,27 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
`rm=${this.state.readMarkerEventId} `,
|
||||
`rr=${sendRRs ? lastReadEvent?.getId() : null} `,
|
||||
`prr=${lastReadEvent?.getId()}`,
|
||||
|
||||
);
|
||||
|
||||
if (this.props.timelineSet.thread && sendRRs && lastReadEvent) {
|
||||
// There's no support for fully read markers on threads
|
||||
// as defined by MSC3771
|
||||
cli.sendReadReceipt(
|
||||
lastReadEvent,
|
||||
sendRRs ? ReceiptType.Read : ReceiptType.ReadPrivate,
|
||||
);
|
||||
cli.sendReadReceipt(lastReadEvent, sendRRs ? ReceiptType.Read : ReceiptType.ReadPrivate);
|
||||
} else {
|
||||
cli.setRoomReadMarkers(
|
||||
roomId,
|
||||
this.state.readMarkerEventId ?? "",
|
||||
sendRRs ? (lastReadEvent ?? undefined) : undefined, // Public read receipt (could be null)
|
||||
sendRRs ? lastReadEvent ?? undefined : undefined, // Public read receipt (could be null)
|
||||
lastReadEvent ?? undefined, // Private read receipt (could be null)
|
||||
).catch(async (e) => {
|
||||
// /read_markers API is not implemented on this HS, fallback to just RR
|
||||
if (e.errcode === 'M_UNRECOGNIZED' && lastReadEvent) {
|
||||
if (e.errcode === "M_UNRECOGNIZED" && lastReadEvent) {
|
||||
if (
|
||||
!sendRRs
|
||||
&& !(await cli.doesServerSupportUnstableFeature("org.matrix.msc2285.stable"))
|
||||
&& !(await cli.isVersionSupported("v1.4"))
|
||||
) return;
|
||||
!sendRRs &&
|
||||
!(await cli.doesServerSupportUnstableFeature("org.matrix.msc2285.stable")) &&
|
||||
!(await cli.isVersionSupported("v1.4"))
|
||||
)
|
||||
return;
|
||||
try {
|
||||
return await cli.sendReadReceipt(
|
||||
lastReadEvent,
|
||||
|
@ -1060,7 +1086,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
this.props.timelineSet.room.setUnreadNotificationCount(NotificationCountType.Total, 0);
|
||||
this.props.timelineSet.room.setUnreadNotificationCount(NotificationCountType.Highlight, 0);
|
||||
dis.dispatch({
|
||||
action: 'on_room_read',
|
||||
action: "on_room_read",
|
||||
roomId: this.props.timelineSet.room.roomId,
|
||||
});
|
||||
}
|
||||
|
@ -1088,10 +1114,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
return;
|
||||
}
|
||||
const lastDisplayedEvent = this.state.events[lastDisplayedIndex];
|
||||
this.setReadMarker(
|
||||
lastDisplayedEvent.getId(),
|
||||
lastDisplayedEvent.getTs(),
|
||||
);
|
||||
this.setReadMarker(lastDisplayedEvent.getId(), lastDisplayedEvent.getTs());
|
||||
|
||||
// the read-marker should become invisible, so that if the user scrolls
|
||||
// down, they don't see it.
|
||||
|
@ -1177,15 +1200,14 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
if (ret !== null) {
|
||||
// The messagepanel knows where the RM is, so we must have loaded
|
||||
// the relevant event.
|
||||
this.messagePanel.current.scrollToEvent(this.state.readMarkerEventId,
|
||||
0, 1/3);
|
||||
this.messagePanel.current.scrollToEvent(this.state.readMarkerEventId, 0, 1 / 3);
|
||||
return;
|
||||
}
|
||||
|
||||
// Looks like we haven't loaded the event corresponding to the read-marker.
|
||||
// As with jumpToLiveTimeline, we want to reload the timeline around the
|
||||
// read-marker.
|
||||
this.loadTimeline(this.state.readMarkerEventId, 0, 1/3);
|
||||
this.loadTimeline(this.state.readMarkerEventId, 0, 1 / 3);
|
||||
};
|
||||
|
||||
/* update the read-up-to marker to match the read receipt
|
||||
|
@ -1200,7 +1222,9 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
const tl = this.props.timelineSet.getTimelineForEvent(rmId ?? "");
|
||||
let rmTs: number;
|
||||
if (tl) {
|
||||
const event = tl.getEvents().find((e) => { return e.getId() == rmId; });
|
||||
const event = tl.getEvents().find((e) => {
|
||||
return e.getId() == rmId;
|
||||
});
|
||||
if (event) {
|
||||
rmTs = event.getTs();
|
||||
}
|
||||
|
@ -1217,9 +1241,11 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
* at the end of the live timeline.
|
||||
*/
|
||||
public isAtEndOfLiveTimeline = (): boolean | undefined => {
|
||||
return this.messagePanel.current?.isAtBottom()
|
||||
&& this.timelineWindow
|
||||
&& !this.timelineWindow.canPaginate(EventTimeline.FORWARDS);
|
||||
return (
|
||||
this.messagePanel.current?.isAtBottom() &&
|
||||
this.timelineWindow &&
|
||||
!this.timelineWindow.canPaginate(EventTimeline.FORWARDS)
|
||||
);
|
||||
};
|
||||
|
||||
/* get the current scroll state. See ScrollPanel.getScrollState for
|
||||
|
@ -1228,7 +1254,9 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
* returns null if we are not mounted.
|
||||
*/
|
||||
public getScrollState = (): IScrollState => {
|
||||
if (!this.messagePanel.current) { return null; }
|
||||
if (!this.messagePanel.current) {
|
||||
return null;
|
||||
}
|
||||
return this.messagePanel.current.getScrollState();
|
||||
};
|
||||
|
||||
|
@ -1266,7 +1294,8 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
// 3. We want to show the bar if the read-marker is off the top of the screen.
|
||||
// 4. Also, if pos === null, the event might not be paginated - show the unread bar
|
||||
const pos = this.getReadMarkerPosition();
|
||||
const ret = this.state.readMarkerEventId !== null && // 1.
|
||||
const ret =
|
||||
this.state.readMarkerEventId !== null && // 1.
|
||||
(pos < 0 || pos === null); // 3., 4.
|
||||
return ret;
|
||||
};
|
||||
|
@ -1276,7 +1305,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
*
|
||||
* We pass it down to the scroll panel.
|
||||
*/
|
||||
public handleScrollKey = ev => {
|
||||
public handleScrollKey = (ev) => {
|
||||
if (!this.messagePanel.current) return;
|
||||
|
||||
// jump to the live timeline on ctrl-end, rather than the end of the
|
||||
|
@ -1307,13 +1336,15 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
const doScroll = () => {
|
||||
if (!this.messagePanel.current) return;
|
||||
if (eventId) {
|
||||
debuglog("TimelinePanel scrolling to eventId " + eventId +
|
||||
" at position " + (offsetBase * 100) + "% + " + pixelOffset);
|
||||
this.messagePanel.current.scrollToEvent(
|
||||
eventId,
|
||||
pixelOffset,
|
||||
offsetBase,
|
||||
debuglog(
|
||||
"TimelinePanel scrolling to eventId " +
|
||||
eventId +
|
||||
" at position " +
|
||||
offsetBase * 100 +
|
||||
"% + " +
|
||||
pixelOffset,
|
||||
);
|
||||
this.messagePanel.current.scrollToEvent(eventId, pixelOffset, offsetBase);
|
||||
} else {
|
||||
debuglog("TimelinePanel scrolling to bottom");
|
||||
this.messagePanel.current.scrollToBottom();
|
||||
|
@ -1373,29 +1404,32 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
// We need to skip over any which have subsequently been sent.
|
||||
this.advanceReadMarkerPastMyEvents();
|
||||
|
||||
this.setState({
|
||||
canBackPaginate: !!this.timelineWindow?.canPaginate(EventTimeline.BACKWARDS),
|
||||
canForwardPaginate: !!this.timelineWindow?.canPaginate(EventTimeline.FORWARDS),
|
||||
timelineLoading: false,
|
||||
}, () => {
|
||||
// initialise the scroll state of the message panel
|
||||
if (!this.messagePanel.current) {
|
||||
// this shouldn't happen - we know we're mounted because
|
||||
// we're in a setState callback, and we know
|
||||
// timelineLoading is now false, so render() should have
|
||||
// mounted the message panel.
|
||||
logger.log("can't initialise scroll state because messagePanel didn't load");
|
||||
return;
|
||||
}
|
||||
this.setState(
|
||||
{
|
||||
canBackPaginate: !!this.timelineWindow?.canPaginate(EventTimeline.BACKWARDS),
|
||||
canForwardPaginate: !!this.timelineWindow?.canPaginate(EventTimeline.FORWARDS),
|
||||
timelineLoading: false,
|
||||
},
|
||||
() => {
|
||||
// initialise the scroll state of the message panel
|
||||
if (!this.messagePanel.current) {
|
||||
// this shouldn't happen - we know we're mounted because
|
||||
// we're in a setState callback, and we know
|
||||
// timelineLoading is now false, so render() should have
|
||||
// mounted the message panel.
|
||||
logger.log("can't initialise scroll state because messagePanel didn't load");
|
||||
return;
|
||||
}
|
||||
|
||||
if (scrollIntoView) {
|
||||
this.scrollIntoView(eventId, pixelOffset, offsetBase);
|
||||
}
|
||||
if (scrollIntoView) {
|
||||
this.scrollIntoView(eventId, pixelOffset, offsetBase);
|
||||
}
|
||||
|
||||
if (this.props.sendReadReceiptOnLoad) {
|
||||
this.sendReadReceipt();
|
||||
}
|
||||
});
|
||||
if (this.props.sendReadReceiptOnLoad) {
|
||||
this.sendReadReceipt();
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const onError = (error: MatrixError) => {
|
||||
|
@ -1422,15 +1456,14 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
let description: string;
|
||||
if (error.errcode == 'M_FORBIDDEN') {
|
||||
if (error.errcode == "M_FORBIDDEN") {
|
||||
description = _t(
|
||||
"Tried to load a specific point in this room's timeline, but you " +
|
||||
"do not have permission to view the message in question.",
|
||||
"do not have permission to view the message in question.",
|
||||
);
|
||||
} else {
|
||||
description = _t(
|
||||
"Tried to load a specific point in this room's timeline, but was " +
|
||||
"unable to find it.",
|
||||
"Tried to load a specific point in this room's timeline, but was " + "unable to find it.",
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1507,24 +1540,27 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
|
||||
// maintain the main timeline event order as returned from the HS
|
||||
// merge overlay events at approximately the right position based on local timestamp
|
||||
const events = overlayEvents.reduce((acc: MatrixEvent[], overlayEvent: MatrixEvent) => {
|
||||
// find the first main tl event with a later timestamp
|
||||
const index = acc.findIndex(event => event.localTimestamp > overlayEvent.localTimestamp);
|
||||
// insert overlay event into timeline at approximately the right place
|
||||
if (index > -1) {
|
||||
acc.splice(index, 0, overlayEvent);
|
||||
} else {
|
||||
acc.push(overlayEvent);
|
||||
}
|
||||
return acc;
|
||||
}, [...mainEvents]);
|
||||
const events = overlayEvents.reduce(
|
||||
(acc: MatrixEvent[], overlayEvent: MatrixEvent) => {
|
||||
// find the first main tl event with a later timestamp
|
||||
const index = acc.findIndex((event) => event.localTimestamp > overlayEvent.localTimestamp);
|
||||
// insert overlay event into timeline at approximately the right place
|
||||
if (index > -1) {
|
||||
acc.splice(index, 0, overlayEvent);
|
||||
} else {
|
||||
acc.push(overlayEvent);
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
[...mainEvents],
|
||||
);
|
||||
|
||||
// `arrayFastClone` performs a shallow copy of the array
|
||||
// we want the last event to be decrypted first but displayed last
|
||||
// `reverse` is destructive and unfortunately mutates the "events" array
|
||||
arrayFastClone(events)
|
||||
.reverse()
|
||||
.forEach(event => {
|
||||
.forEach((event) => {
|
||||
const client = MatrixClientPeg.get();
|
||||
client.decryptEventIfNeeded(event);
|
||||
});
|
||||
|
@ -1538,18 +1574,21 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
// if we're at the end of the live timeline, append the pending events
|
||||
if (!this.timelineWindow?.canPaginate(EventTimeline.FORWARDS)) {
|
||||
const pendingEvents = this.props.timelineSet.getPendingEvents();
|
||||
events.push(...pendingEvents.filter(event => {
|
||||
const {
|
||||
shouldLiveInRoom,
|
||||
threadId,
|
||||
} = this.props.timelineSet.room!.eventShouldLiveIn(event, pendingEvents);
|
||||
events.push(
|
||||
...pendingEvents.filter((event) => {
|
||||
const { shouldLiveInRoom, threadId } = this.props.timelineSet.room!.eventShouldLiveIn(
|
||||
event,
|
||||
pendingEvents,
|
||||
);
|
||||
|
||||
if (this.context.timelineRenderingType === TimelineRenderingType.Thread) {
|
||||
return threadId === this.context.threadId;
|
||||
} {
|
||||
return shouldLiveInRoom;
|
||||
}
|
||||
}));
|
||||
if (this.context.timelineRenderingType === TimelineRenderingType.Thread) {
|
||||
return threadId === this.context.threadId;
|
||||
}
|
||||
{
|
||||
return shouldLiveInRoom;
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -1573,8 +1612,9 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
const cli = MatrixClientPeg.get();
|
||||
const room = this.props.timelineSet.room;
|
||||
|
||||
const isThreadTimeline = [TimelineRenderingType.Thread, TimelineRenderingType.ThreadsList]
|
||||
.includes(this.context.timelineRenderingType);
|
||||
const isThreadTimeline = [TimelineRenderingType.Thread, TimelineRenderingType.ThreadsList].includes(
|
||||
this.context.timelineRenderingType,
|
||||
);
|
||||
if (events.length === 0 || !room || !cli.isRoomEncrypted(room.roomId) || isThreadTimeline) {
|
||||
logger.info("checkForPreJoinUISI: showing all messages, skipping check");
|
||||
return 0;
|
||||
|
@ -1594,8 +1634,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
// a timeline, even though that should not happen. :(
|
||||
// https://github.com/vector-im/element-web/issues/12120
|
||||
logger.warn(
|
||||
`Event ${events[i].getId()} in room ${room.roomId} is live, ` +
|
||||
`but it does not have a timeline`,
|
||||
`Event ${events[i].getId()} in room ${room.roomId} is live, ` + `but it does not have a timeline`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
@ -1633,7 +1672,9 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
private indexForEventId(evId: string | null): number | null {
|
||||
if (evId === null) { return null; }
|
||||
if (evId === null) {
|
||||
return null;
|
||||
}
|
||||
/* Threads do not have server side support for read receipts and the concept
|
||||
is very tied to the main room timeline, we are forcing the timeline to
|
||||
send read receipts for threaded events */
|
||||
|
@ -1641,10 +1682,8 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
if (SettingsStore.getValue("feature_thread") && isThreadTimeline) {
|
||||
return 0;
|
||||
}
|
||||
const index = this.state.events.findIndex(ev => ev.getId() === evId);
|
||||
return index > -1
|
||||
? index
|
||||
: null;
|
||||
const index = this.state.events.findIndex((ev) => ev.getId() === evId);
|
||||
return index > -1 ? index : null;
|
||||
}
|
||||
|
||||
private getLastDisplayedEventIndex(opts: IEventIndexOpts = {}): number | null {
|
||||
|
@ -1697,10 +1736,11 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
adjacentInvisibleEventCount = 0;
|
||||
}
|
||||
|
||||
const shouldIgnore = !!ev.status || // local echo
|
||||
const shouldIgnore =
|
||||
!!ev.status || // local echo
|
||||
(ignoreOwn && ev.getSender() === myUserId); // own message
|
||||
const isWithoutTile = !haveRendererForEvent(ev, this.context?.showHiddenEvents) ||
|
||||
shouldHideEvent(ev, this.context);
|
||||
const isWithoutTile =
|
||||
!haveRendererForEvent(ev, this.context?.showHiddenEvents) || shouldHideEvent(ev, this.context);
|
||||
|
||||
if (isWithoutTile || !node) {
|
||||
// don't start counting if the event should be ignored,
|
||||
|
@ -1742,8 +1782,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
const myUserId = client.credentials.userId;
|
||||
const receiptStore: ReadReceipt<any, any> =
|
||||
this.props.timelineSet.thread ?? this.props.timelineSet.room;
|
||||
const receiptStore: ReadReceipt<any, any> = this.props.timelineSet.thread ?? this.props.timelineSet.room;
|
||||
return receiptStore?.getEventReadUpTo(myUserId, ignoreSynthesized);
|
||||
}
|
||||
|
||||
|
@ -1767,9 +1806,12 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
// Do the local echo of the RM
|
||||
// run the render cycle before calling the callback, so that
|
||||
// getReadMarkerPosition() returns the right thing.
|
||||
this.setState({
|
||||
readMarkerEventId: eventId,
|
||||
}, this.props.onReadMarkerUpdated);
|
||||
this.setState(
|
||||
{
|
||||
readMarkerEventId: eventId,
|
||||
},
|
||||
this.props.onReadMarkerUpdated,
|
||||
);
|
||||
}
|
||||
|
||||
private shouldPaginate(): boolean {
|
||||
|
@ -1816,7 +1858,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
if (this.state.events.length == 0 && !this.state.canBackPaginate && this.props.empty) {
|
||||
return (
|
||||
<div className={this.props.className + " mx_RoomView_messageListWrapper"}>
|
||||
<div className="mx_RoomView_empty">{ this.props.empty }</div>
|
||||
<div className="mx_RoomView_empty">{this.props.empty}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1833,10 +1875,8 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
|
||||
// If the state is PREPARED or CATCHUP, we're still waiting for the js-sdk to sync with
|
||||
// the HS and fetch the latest events, so we are effectively forward paginating.
|
||||
const forwardPaginating = (
|
||||
this.state.forwardPaginating ||
|
||||
['PREPARED', 'CATCHUP'].includes(this.state.clientSyncState)
|
||||
);
|
||||
const forwardPaginating =
|
||||
this.state.forwardPaginating || ["PREPARED", "CATCHUP"].includes(this.state.clientSyncState);
|
||||
const events = this.state.firstVisibleEventIndex
|
||||
? this.state.events.slice(this.state.firstVisibleEventIndex)
|
||||
: this.state.events;
|
||||
|
@ -1897,7 +1937,7 @@ function serializeEventIdsFromTimelineSets(timelineSets): { [key: string]: strin
|
|||
// Add a special label when it is the live timeline so we can tell
|
||||
// it apart from the others
|
||||
const isLiveTimeline = timeline === liveTimeline;
|
||||
timelineMap[isLiveTimeline ? 'liveTimeline' : `${index}`] = timeline.getEvents().map(ev => ev.getId());
|
||||
timelineMap[isLiveTimeline ? "liveTimeline" : `${index}`] = timeline.getEvents().map((ev) => ev.getId());
|
||||
});
|
||||
|
||||
return timelineMap;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue