Merge branch 'master' into bwindels/e2ee
This commit is contained in:
commit
a6daa13d1d
6 changed files with 85 additions and 37 deletions
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "hydrogen-web",
|
"name": "hydrogen-web",
|
||||||
"version": "0.0.32",
|
"version": "0.0.33",
|
||||||
"description": "A javascript matrix client prototype, trying to minize RAM usage by offloading as much as possible to IndexedDB",
|
"description": "A javascript matrix client prototype, trying to minize RAM usage by offloading as much as possible to IndexedDB",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"directories": {
|
"directories": {
|
||||||
|
|
|
@ -57,24 +57,20 @@ export class SessionViewModel extends ViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
_closeCurrentRoom() {
|
_closeCurrentRoom() {
|
||||||
if (this._currentRoomViewModel) {
|
this._currentRoomTileViewModel?.close();
|
||||||
this._currentRoomViewModel = this.disposeTracked(this._currentRoomViewModel);
|
this._currentRoomViewModel = this.disposeTracked(this._currentRoomViewModel);
|
||||||
this.emitChange("currentRoom");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_openRoom(room, roomTileVM) {
|
_openRoom(room, roomTileVM) {
|
||||||
if (this._currentRoomTileViewModel) {
|
this._closeCurrentRoom();
|
||||||
this._currentRoomTileViewModel.close();
|
|
||||||
}
|
|
||||||
this._currentRoomTileViewModel = roomTileVM;
|
this._currentRoomTileViewModel = roomTileVM;
|
||||||
if (this._currentRoomViewModel) {
|
|
||||||
this._currentRoomViewModel = this.disposeTracked(this._currentRoomViewModel);
|
|
||||||
}
|
|
||||||
this._currentRoomViewModel = this.track(new RoomViewModel(this.childOptions({
|
this._currentRoomViewModel = this.track(new RoomViewModel(this.childOptions({
|
||||||
room,
|
room,
|
||||||
ownUserId: this._session.user.id,
|
ownUserId: this._session.user.id,
|
||||||
closeCallback: () => this._closeCurrentRoom(),
|
closeCallback: () => {
|
||||||
|
this._closeCurrentRoom();
|
||||||
|
this.emitChange("currentRoom");
|
||||||
|
},
|
||||||
})));
|
})));
|
||||||
this._currentRoomViewModel.load();
|
this._currentRoomViewModel.load();
|
||||||
this.emitChange("currentRoom");
|
this.emitChange("currentRoom");
|
||||||
|
|
|
@ -49,34 +49,61 @@ export class RoomTileViewModel extends ViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
compare(other) {
|
compare(other) {
|
||||||
|
/*
|
||||||
|
put unread rooms first
|
||||||
|
then put rooms with a timestamp first, and sort by name
|
||||||
|
then sort by name for rooms without a timestamp
|
||||||
|
*/
|
||||||
const myRoom = this._room;
|
const myRoom = this._room;
|
||||||
const theirRoom = other._room;
|
const theirRoom = other._room;
|
||||||
|
|
||||||
|
let buf = "";
|
||||||
|
function log(...args) {
|
||||||
|
buf = buf + args.map(a => a+"").join(" ") + "\n";
|
||||||
|
}
|
||||||
|
function logResult(result) {
|
||||||
|
if (result === 0) {
|
||||||
|
log("rooms are equal (should not happen)", result);
|
||||||
|
} else if (result > 0) {
|
||||||
|
log(`${theirRoom.name || theirRoom.id} comes first`, result);
|
||||||
|
} else {
|
||||||
|
log(`${myRoom.name || myRoom.id} comes first`, result);
|
||||||
|
}
|
||||||
|
console.info(buf);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
log(`comparing ${myRoom.name || theirRoom.id} and ${theirRoom.name || theirRoom.id} ...`);
|
||||||
|
log("comparing isUnread...");
|
||||||
if (isSortedAsUnread(this) !== isSortedAsUnread(other)) {
|
if (isSortedAsUnread(this) !== isSortedAsUnread(other)) {
|
||||||
if (isSortedAsUnread(this)) {
|
if (isSortedAsUnread(this)) {
|
||||||
return -1;
|
return logResult(-1);
|
||||||
}
|
}
|
||||||
return 1;
|
return logResult(1);
|
||||||
}
|
}
|
||||||
const myTimestamp = myRoom.lastMessageTimestamp;
|
const myTimestamp = myRoom.lastMessageTimestamp;
|
||||||
const theirTimestamp = theirRoom.lastMessageTimestamp;
|
const theirTimestamp = theirRoom.lastMessageTimestamp;
|
||||||
// rooms with a timestamp come before rooms without one
|
const myTimestampValid = Number.isSafeInteger(myTimestamp);
|
||||||
if ((myTimestamp === null) !== (theirTimestamp === null)) {
|
const theirTimestampValid = Number.isSafeInteger(theirTimestamp);
|
||||||
if (theirTimestamp === null) {
|
// if either does not have a timestamp, put the one with a timestamp first
|
||||||
return -1;
|
if (myTimestampValid !== theirTimestampValid) {
|
||||||
|
log("checking if either does not have lastMessageTimestamp ...", myTimestamp, theirTimestamp);
|
||||||
|
if (!theirTimestampValid) {
|
||||||
|
return logResult(-1);
|
||||||
}
|
}
|
||||||
return 1;
|
return logResult(1);
|
||||||
}
|
}
|
||||||
const timeDiff = theirTimestamp - myTimestamp;
|
const timeDiff = theirTimestamp - myTimestamp;
|
||||||
if (timeDiff === 0) {
|
if (timeDiff === 0 || !theirTimestampValid || !myTimestampValid) {
|
||||||
|
log("checking name ...", myTimestamp, theirTimestamp);
|
||||||
// sort alphabetically
|
// sort alphabetically
|
||||||
const nameCmp = this.name.localeCompare(other.name);
|
const nameCmp = this.name.localeCompare(other.name);
|
||||||
if (nameCmp === 0) {
|
if (nameCmp === 0) {
|
||||||
return this._room.id.localeCompare(other._room.id);
|
return logResult(this._room.id.localeCompare(other._room.id));
|
||||||
}
|
}
|
||||||
return nameCmp;
|
return logResult(nameCmp);
|
||||||
}
|
}
|
||||||
return timeDiff;
|
log("checking timestamp ...");
|
||||||
|
return logResult(timeDiff);
|
||||||
}
|
}
|
||||||
|
|
||||||
get isOpen() {
|
get isOpen() {
|
||||||
|
|
|
@ -22,12 +22,12 @@ function calculateRoomName(sortedMembers, summary) {
|
||||||
if (sortedMembers.length > 1) {
|
if (sortedMembers.length > 1) {
|
||||||
const lastMember = sortedMembers[sortedMembers.length - 1];
|
const lastMember = sortedMembers[sortedMembers.length - 1];
|
||||||
const firstMembers = sortedMembers.slice(0, sortedMembers.length - 1);
|
const firstMembers = sortedMembers.slice(0, sortedMembers.length - 1);
|
||||||
return firstMembers.map(m => m.displayName).join(", ") + " and " + lastMember.displayName;
|
return firstMembers.map(m => m.name).join(", ") + " and " + lastMember.name;
|
||||||
} else {
|
} else {
|
||||||
return sortedMembers[0].displayName;
|
return sortedMembers[0].name;
|
||||||
}
|
}
|
||||||
} else if (sortedMembers.length < countWithoutMe) {
|
} else if (sortedMembers.length < countWithoutMe) {
|
||||||
return sortedMembers.map(m => m.displayName).join(", ") + ` and ${countWithoutMe} others`;
|
return sortedMembers.map(m => m.name).join(", ") + ` and ${countWithoutMe} others`;
|
||||||
} else {
|
} else {
|
||||||
// Empty Room
|
// Empty Room
|
||||||
return null;
|
return null;
|
||||||
|
@ -81,7 +81,7 @@ export class Heroes {
|
||||||
for (const member of updatedHeroMembers) {
|
for (const member of updatedHeroMembers) {
|
||||||
this._members.set(member.userId, member);
|
this._members.set(member.userId, member);
|
||||||
}
|
}
|
||||||
const sortedMembers = Array.from(this._members.values()).sort((a, b) => a.displayName.localeCompare(b.displayName));
|
const sortedMembers = Array.from(this._members.values()).sort((a, b) => a.name.localeCompare(b.name));
|
||||||
this._roomName = calculateRoomName(sortedMembers, summary);
|
this._roomName = calculateRoomName(sortedMembers, summary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,7 +92,6 @@ export class Heroes {
|
||||||
get roomAvatarUrl() {
|
get roomAvatarUrl() {
|
||||||
if (this._members.size === 1) {
|
if (this._members.size === 1) {
|
||||||
for (const member of this._members.values()) {
|
for (const member of this._members.values()) {
|
||||||
console.log("roomAvatarUrl", member, member.avatarUrl);
|
|
||||||
return member.avatarUrl;
|
return member.avatarUrl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,21 +27,33 @@ export class RoomMember {
|
||||||
if (typeof userId !== "string") {
|
if (typeof userId !== "string") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
return this._fromMemberEventContent(roomId, userId, memberEvent.content);
|
const content = memberEvent.content;
|
||||||
|
const prevContent = memberEvent.unsigned?.prev_content;
|
||||||
|
const membership = content?.membership;
|
||||||
|
// fall back to prev_content for these as synapse doesn't (always?)
|
||||||
|
// put them on content for "leave" memberships
|
||||||
|
const displayName = content?.displayname || prevContent?.displayname;
|
||||||
|
const avatarUrl = content?.avatar_url || prevContent?.avatar_url;
|
||||||
|
return this._validateAndCreateMember(roomId, userId, membership, displayName, avatarUrl);
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Creates a (historical) member from a member event that is the next member event
|
||||||
|
* after the point in time where we need a member for. This will use `prev_content`.
|
||||||
|
*/
|
||||||
static fromReplacingMemberEvent(roomId, memberEvent) {
|
static fromReplacingMemberEvent(roomId, memberEvent) {
|
||||||
const userId = memberEvent && memberEvent.state_key;
|
const userId = memberEvent && memberEvent.state_key;
|
||||||
if (typeof userId !== "string") {
|
if (typeof userId !== "string") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
return this._fromMemberEventContent(roomId, userId, memberEvent.prev_content);
|
const content = memberEvent.unsigned?.prev_content
|
||||||
|
return this._validateAndCreateMember(roomId, userId,
|
||||||
|
content?.membership,
|
||||||
|
content?.displayname,
|
||||||
|
content?.avatar_url
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static _fromMemberEventContent(roomId, userId, content) {
|
static _validateAndCreateMember(roomId, userId, membership, displayName, avatarUrl) {
|
||||||
const membership = content?.membership;
|
|
||||||
const avatarUrl = content?.avatar_url;
|
|
||||||
const displayName = content?.displayname;
|
|
||||||
if (typeof membership !== "string") {
|
if (typeof membership !== "string") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -54,10 +66,23 @@ export class RoomMember {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {String?} the display name, if any
|
||||||
|
*/
|
||||||
get displayName() {
|
get displayName() {
|
||||||
return this._data.displayName;
|
return this._data.displayName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {String} the display name or userId
|
||||||
|
*/
|
||||||
|
get name() {
|
||||||
|
return this._data.displayName || this._data.userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {String?} the avatar mxc url, if any
|
||||||
|
*/
|
||||||
get avatarUrl() {
|
get avatarUrl() {
|
||||||
return this._data.avatarUrl;
|
return this._data.avatarUrl;
|
||||||
}
|
}
|
||||||
|
|
|
@ -142,8 +142,9 @@ export class GapWriter {
|
||||||
return RoomMember.fromReplacingMemberEvent(this._roomId, event)?.serialize();
|
return RoomMember.fromReplacingMemberEvent(this._roomId, event)?.serialize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// assuming the member hasn't changed within the chunk, just take it from state if it's there
|
// assuming the member hasn't changed within the chunk, just take it from state if it's there.
|
||||||
const stateMemberEvent = state.find(isOurUser);
|
// Don't assume state is set though, as it can be empty at the top of the timeline in some circumstances
|
||||||
|
const stateMemberEvent = state?.find(isOurUser);
|
||||||
if (stateMemberEvent) {
|
if (stateMemberEvent) {
|
||||||
return RoomMember.fromMemberEvent(this._roomId, stateMemberEvent)?.serialize();
|
return RoomMember.fromMemberEvent(this._roomId, stateMemberEvent)?.serialize();
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue