Merge branch 'develop' into johannes/find-myself

This commit is contained in:
Johannes Marbach 2023-02-10 08:26:16 +01:00
commit 6ee6accfc6
16 changed files with 392 additions and 774 deletions

View file

@ -146,7 +146,7 @@ export default class PasswordReset {
err.message = _t("Failed to verify email address: make sure you clicked the link in the email");
} else if (err.httpStatus === 404) {
err.message = _t(
"Your email address does not appear to be associated with a Matrix ID on this Homeserver.",
"Your email address does not appear to be associated with a Matrix ID on this homeserver.",
);
} else if (err.httpStatus) {
err.message += ` (Status ${err.httpStatus})`;

View file

@ -63,7 +63,7 @@ const SpaceContextMenu: React.FC<IProps> = ({ space, hideHeader, onFinished, ...
inviteOption = (
<IconizedContextMenuOption
data-test-id="invite-option"
data-testid="invite-option"
className="mx_SpacePanel_contextMenu_inviteButton"
iconClassName="mx_SpacePanel_iconInvite"
label={_t("Invite")}
@ -85,7 +85,7 @@ const SpaceContextMenu: React.FC<IProps> = ({ space, hideHeader, onFinished, ...
settingsOption = (
<IconizedContextMenuOption
data-test-id="settings-option"
data-testid="settings-option"
iconClassName="mx_SpacePanel_iconSettings"
label={_t("Settings")}
onClick={onSettingsClick}
@ -102,7 +102,7 @@ const SpaceContextMenu: React.FC<IProps> = ({ space, hideHeader, onFinished, ...
leaveOption = (
<IconizedContextMenuOption
data-test-id="leave-option"
data-testid="leave-option"
iconClassName="mx_SpacePanel_iconLeave"
className="mx_IconizedContextMenu_option_red"
label={_t("Leave space")}
@ -172,12 +172,12 @@ const SpaceContextMenu: React.FC<IProps> = ({ space, hideHeader, onFinished, ...
newRoomSection = (
<>
<div data-test-id="add-to-space-header" className="mx_SpacePanel_contextMenu_separatorLabel">
<div data-testid="add-to-space-header" className="mx_SpacePanel_contextMenu_separatorLabel">
{_t("Add")}
</div>
{canAddRooms && (
<IconizedContextMenuOption
data-test-id="new-room-option"
data-testid="new-room-option"
iconClassName="mx_SpacePanel_iconPlus"
label={_t("Room")}
onClick={onNewRoomClick}
@ -185,7 +185,7 @@ const SpaceContextMenu: React.FC<IProps> = ({ space, hideHeader, onFinished, ...
)}
{canAddVideoRooms && (
<IconizedContextMenuOption
data-test-id="new-video-room-option"
data-testid="new-video-room-option"
iconClassName="mx_SpacePanel_iconPlus"
label={_t("Video room")}
onClick={onNewVideoRoomClick}
@ -195,7 +195,7 @@ const SpaceContextMenu: React.FC<IProps> = ({ space, hideHeader, onFinished, ...
)}
{canAddSubSpaces && (
<IconizedContextMenuOption
data-test-id="new-subspace-option"
data-testid="new-subspace-option"
iconClassName="mx_SpacePanel_iconPlus"
label={_t("Space")}
onClick={onNewSubspaceClick}

View file

@ -191,16 +191,19 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
public replaceEmoticon(caretPosition: DocumentPosition, regex: RegExp): number {
const { model } = this.props;
const range = model.startRange(caretPosition);
// expand range max 8 characters backwards from caretPosition,
// expand range max 9 characters backwards from caretPosition,
// as a space to look for an emoticon
let n = 8;
let n = 9;
range.expandBackwardsWhile((index, offset) => {
const part = model.parts[index];
n -= 1;
return n >= 0 && [Type.Plain, Type.PillCandidate, Type.Newline].includes(part.type);
});
const emoticonMatch = regex.exec(range.text);
if (emoticonMatch) {
// ignore matches at start of proper substrings
// so xd will not match if the string was "mixd 123456"
// and we are lookinh at xd 123456 part of the string
if (emoticonMatch && (n >= 0 || emoticonMatch.index !== 0)) {
const query = emoticonMatch[1].replace("-", "");
// try both exact match and lower-case, this means that xd won't match xD but :P will match :p
const data = EMOTICON_TO_EMOJI.get(query) || EMOTICON_TO_EMOJI.get(query.toLowerCase());

View file

@ -73,7 +73,7 @@ const securityCardContent: Record<
title: _t("Unverified session"),
description: (
<>
<p>{_t(`This session doesn't support encryption, so it can't be verified.`)}</p>
<p>{_t(`This session doesn't support encryption and thus can't be verified.`)}</p>
<p>
{_t(
`You won't be able to participate in rooms where encryption is enabled when using this session.`,

View file

@ -117,7 +117,7 @@
"%(brand)s was not given permission to send notifications - please try again": "%(brand)s was not given permission to send notifications - please try again",
"Unable to enable Notifications": "Unable to enable Notifications",
"This email address was not found": "This email address was not found",
"Your email address does not appear to be associated with a Matrix ID on this Homeserver.": "Your email address does not appear to be associated with a Matrix ID on this Homeserver.",
"Your email address does not appear to be associated with a Matrix ID on this homeserver.": "Your email address does not appear to be associated with a Matrix ID on this homeserver.",
"United Kingdom": "United Kingdom",
"United States": "United States",
"Afghanistan": "Afghanistan",
@ -1826,7 +1826,7 @@
"Unverified sessions are sessions that have logged in with your credentials but have not been cross-verified.": "Unverified sessions are sessions that have logged in with your credentials but have not been cross-verified.",
"You should make especially certain that you recognise these sessions as they could represent an unauthorised use of your account.": "You should make especially certain that you recognise these sessions as they could represent an unauthorised use of your account.",
"Unverified session": "Unverified session",
"This session doesn't support encryption, so it can't be verified.": "This session doesn't support encryption, so it can't be verified.",
"This session doesn't support encryption and thus can't be verified.": "This session doesn't support encryption and thus can't be verified.",
"You won't be able to participate in rooms where encryption is enabled when using this session.": "You won't be able to participate in rooms where encryption is enabled when using this session.",
"For best security and privacy, it is recommended to use Matrix clients that support encryption.": "For best security and privacy, it is recommended to use Matrix clients that support encryption.",
"Inactive sessions": "Inactive sessions",
@ -1842,7 +1842,6 @@
"Your current session is ready for secure messaging.": "Your current session is ready for secure messaging.",
"This session is ready for secure messaging.": "This session is ready for secure messaging.",
"Verified session": "Verified session",
"This session doesn't support encryption and thus can't be verified.": "This session doesn't support encryption and thus can't be verified.",
"Verify your current session for enhanced secure messaging.": "Verify your current session for enhanced secure messaging.",
"Verify or sign out from this session for best security and reliability.": "Verify or sign out from this session for best security and reliability.",
"Verify session": "Verify session",

View file

@ -62,12 +62,12 @@ interface IStickyRoom {
*/
export class Algorithm extends EventEmitter {
private _cachedRooms: ITagMap = {};
private _cachedStickyRooms: ITagMap = {}; // a clone of the _cachedRooms, with the sticky room
private _stickyRoom: IStickyRoom = null;
private _lastStickyRoom: IStickyRoom = null; // only not-null when changing the sticky room
private sortAlgorithms: ITagSortingMap;
private listAlgorithms: IListOrderingMap;
private algorithms: IOrderingAlgorithmMap;
private _cachedStickyRooms: ITagMap | null = {}; // a clone of the _cachedRooms, with the sticky room
private _stickyRoom: IStickyRoom | null = null;
private _lastStickyRoom: IStickyRoom | null = null; // only not-null when changing the sticky room
private sortAlgorithms: ITagSortingMap | null = null;
private listAlgorithms: IListOrderingMap | null = null;
private algorithms: IOrderingAlgorithmMap | null = null;
private rooms: Room[] = [];
private roomIdsToTags: {
[roomId: string]: TagID[];
@ -86,7 +86,7 @@ export class Algorithm extends EventEmitter {
CallStore.instance.off(CallStoreEvent.ActiveCalls, this.onActiveCalls);
}
public get stickyRoom(): Room {
public get stickyRoom(): Room | null {
return this._stickyRoom ? this._stickyRoom.room : null;
}
@ -124,7 +124,7 @@ export class Algorithm extends EventEmitter {
}
}
public getTagSorting(tagId: TagID): SortAlgorithm {
public getTagSorting(tagId: TagID): SortAlgorithm | null {
if (!this.sortAlgorithms) return null;
return this.sortAlgorithms[tagId];
}
@ -132,6 +132,8 @@ export class Algorithm extends EventEmitter {
public setTagSorting(tagId: TagID, sort: SortAlgorithm): void {
if (!tagId) throw new Error("Tag ID must be defined");
if (!sort) throw new Error("Algorithm must be defined");
if (!this.sortAlgorithms) throw new Error("this.sortAlgorithms must be defined before calling setTagSorting");
if (!this.algorithms) throw new Error("this.algorithms must be defined before calling setTagSorting");
this.sortAlgorithms[tagId] = sort;
const algorithm: OrderingAlgorithm = this.algorithms[tagId];
@ -141,7 +143,7 @@ export class Algorithm extends EventEmitter {
this.recalculateActiveCallRooms(tagId);
}
public getListOrdering(tagId: TagID): ListAlgorithm {
public getListOrdering(tagId: TagID): ListAlgorithm | null {
if (!this.listAlgorithms) return null;
return this.listAlgorithms[tagId];
}
@ -149,6 +151,9 @@ export class Algorithm extends EventEmitter {
public setListOrdering(tagId: TagID, order: ListAlgorithm): void {
if (!tagId) throw new Error("Tag ID must be defined");
if (!order) throw new Error("Algorithm must be defined");
if (!this.sortAlgorithms) throw new Error("this.sortAlgorithms must be defined before calling setListOrdering");
if (!this.listAlgorithms) throw new Error("this.listAlgorithms must be defined before calling setListOrdering");
if (!this.algorithms) throw new Error("this.algorithms must be defined before calling setListOrdering");
this.listAlgorithms[tagId] = order;
const algorithm = getListAlgorithmInstance(order, tagId, this.sortAlgorithms[tagId]);
@ -160,12 +165,12 @@ export class Algorithm extends EventEmitter {
this.recalculateActiveCallRooms(tagId);
}
private updateStickyRoom(val: Room): void {
private updateStickyRoom(val: Room | null): void {
this.doUpdateStickyRoom(val);
this._lastStickyRoom = null; // clear to indicate we're done changing
}
private doUpdateStickyRoom(val: Room): void {
private doUpdateStickyRoom(val: Room | null): void {
if (val?.isSpaceRoom() && val.getMyMembership() !== "invite") {
// no-op sticky rooms for spaces - they're effectively virtual rooms
val = null;
@ -237,6 +242,10 @@ export class Algorithm extends EventEmitter {
// Lie to the algorithm and remove the room from it's field of view
this.handleRoomUpdate(val, RoomUpdateCause.RoomRemoved);
// handleRoomUpdate may have modified this._stickyRoom. Convince the
// compiler of this fact.
this._stickyRoom = this.stickyRoomMightBeModified();
// Check for tag & position changes while we're here. We also check the room to ensure
// it is still the same room.
if (this._stickyRoom) {
@ -284,6 +293,13 @@ export class Algorithm extends EventEmitter {
this.emit(LIST_UPDATED_EVENT);
}
/**
* Hack to prevent Typescript claiming this._stickyRoom is always null.
*/
private stickyRoomMightBeModified(): IStickyRoom | null {
return this._stickyRoom;
}
private onActiveCalls = (): void => {
// In case we're unsticking a room, sort it back into natural order
this.recalculateStickyRoom();
@ -310,7 +326,7 @@ export class Algorithm extends EventEmitter {
* the call.
* @param updatedTag The tag that was updated, if possible.
*/
protected recalculateStickyRoom(updatedTag: TagID = null): void {
protected recalculateStickyRoom(updatedTag: TagID | null = null): void {
// 🐉 Here be dragons.
// This function does far too much for what it should, and is called by many places.
// Not only is this responsible for ensuring the sticky room is held in place at all
@ -336,14 +352,16 @@ export class Algorithm extends EventEmitter {
if (updatedTag) {
// Update the tag indicated by the caller, if possible. This is mostly to ensure
// our cache is up to date.
this._cachedStickyRooms[updatedTag] = [...this.cachedRooms[updatedTag]]; // shallow clone
if (this._cachedStickyRooms) {
this._cachedStickyRooms[updatedTag] = [...this.cachedRooms[updatedTag]]; // shallow clone
}
}
// Now try to insert the sticky room, if we need to.
// We need to if there's no updated tag (we regenned the whole cache) or if the tag
// we might have updated from the cache is also our sticky room.
const sticky = this._stickyRoom;
if (!updatedTag || updatedTag === sticky.tag) {
if (sticky && (!updatedTag || updatedTag === sticky.tag) && this._cachedStickyRooms) {
this._cachedStickyRooms[sticky.tag].splice(sticky.position, 0, sticky.room);
}
@ -362,7 +380,7 @@ export class Algorithm extends EventEmitter {
*
* @param updatedTag The tag that was updated, if possible.
*/
protected recalculateActiveCallRooms(updatedTag: TagID = null): void {
protected recalculateActiveCallRooms(updatedTag: TagID | null = null): void {
if (!updatedTag) {
// Assume all tags need updating
// We're not modifying the map here, so can safely rely on the cached values
@ -379,7 +397,7 @@ export class Algorithm extends EventEmitter {
if (CallStore.instance.activeCalls.size) {
// We operate on the sticky rooms map
if (!this._cachedStickyRooms) this.initCachedStickyRooms();
const rooms = this._cachedStickyRooms[updatedTag];
const rooms = this._cachedStickyRooms![updatedTag];
const activeRoomIds = new Set([...CallStore.instance.activeCalls].map((call) => call.roomId));
const activeRooms: Room[] = [];
@ -390,7 +408,7 @@ export class Algorithm extends EventEmitter {
}
// Stick rooms with active calls to the top
this._cachedStickyRooms[updatedTag] = [...activeRooms, ...inactiveRooms];
this._cachedStickyRooms![updatedTag] = [...activeRooms, ...inactiveRooms];
}
}
@ -638,7 +656,7 @@ export class Algorithm extends EventEmitter {
}
// Like above, update the reference to the sticky room if we need to
if (hasTags && isSticky) {
if (hasTags && isSticky && this._stickyRoom) {
// Go directly in and set the sticky room's new reference, being careful not
// to trigger a sticky room update ourselves.
this._stickyRoom.room = room;